@componyx/ui 0.0.0-stage → 1.0.4
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/AppSkin/AppSkin.css +1268 -0
- package/AppSkin/AppSkin.js +2748 -0
- package/AppSkin/Themes/Default.css +241 -0
- package/Base/Base.js +3407 -0
- package/Base/ComponentIcons.css +66 -0
- package/Base/Font/ComponentIcons.woff +0 -0
- package/Base/Font/UI.woff +0 -0
- package/Base/Library.js +8432 -0
- package/Base/NavigationManager.js +462 -0
- package/Base/ResponsiveTemplates.js +371 -0
- package/Base/Sanitizer.js +288 -0
- package/Base/Themes/Default.css +255 -0
- package/Base/UI.css +618 -0
- package/Base/UIResourcePath.js +6 -0
- package/Bindary/Bindary.js +2011 -0
- package/Bindary/Modules/Connection.js +216 -0
- package/Bindary/Modules/Core.js +854 -0
- package/Bindary/Modules/DataUpdater.js +351 -0
- package/Bindary/Modules/ExpressionEngine.js +347 -0
- package/Bindary/Modules/ProxyManager.js +482 -0
- package/Bindary/Modules/Renderer.js +605 -0
- package/Bindary/Modules/Router.js +291 -0
- package/Bindary/Modules/Scroller.js +89 -0
- package/Bindary/Modules/Template.js +1273 -0
- package/BookmarkSpy/BookmarkSpy.js +430 -0
- package/Box/Box.css +36 -0
- package/Box/Box.js +1685 -0
- package/Button/Button.css +470 -0
- package/Button/Button.js +1398 -0
- package/Button/Themes/Default.css +200 -0
- package/Carousel/Carousel.css +273 -0
- package/Carousel/Carousel.js +607 -0
- package/ColorButton/ColorButton.css +39 -0
- package/ColorButton/ColorButton.js +336 -0
- package/ColorPicker/ColorPicker.css +359 -0
- package/ColorPicker/ColorPicker.js +972 -0
- package/ComboBox/ComboBox.css +357 -0
- package/ComboBox/ComboBox.js +2342 -0
- package/ComboBox/Themes/Default.css +23 -0
- package/DataPager/DataPager.css +72 -0
- package/DataPager/DataPager.js +674 -0
- package/DatePicker/DatePicker.css +227 -0
- package/DatePicker/DatePicker.js +1531 -0
- package/DatePicker/Themes/Default.css +26 -0
- package/Dialog/Dialog.css +106 -0
- package/Dialog/Dialog.js +572 -0
- package/Dialog/Themes/Default.css +10 -0
- package/Editor/Data/Emoji.json +8285 -0
- package/Editor/Data/Special.json +1042 -0
- package/Editor/Editor.css +1562 -0
- package/Editor/Editor.js +4058 -0
- package/Editor/Font/Editor.woff +0 -0
- package/Editor/Modules/ComponentFactory.js +565 -0
- package/Editor/Modules/ContentManager.js +748 -0
- package/Editor/Modules/DialogManager.js +1234 -0
- package/Editor/Modules/EventManager.js +458 -0
- package/Editor/Modules/Format.js +950 -0
- package/Editor/Modules/History.js +191 -0
- package/Editor/Modules/LayoutState.js +379 -0
- package/Editor/Modules/ListManager.js +296 -0
- package/Editor/Modules/MenuManager.js +388 -0
- package/Editor/Modules/NodeManager.js +681 -0
- package/Editor/Modules/ParagraphButtons.js +122 -0
- package/Editor/Modules/Resizer.js +176 -0
- package/Editor/Modules/SelectionRange.js +564 -0
- package/Editor/Modules/SourceViewBuilder.js +212 -0
- package/Editor/Modules/SymbolPicker.js +208 -0
- package/Editor/Modules/Table.js +2327 -0
- package/Editor/Modules/TableDialog.js +757 -0
- package/Editor/Modules/Toolbar.js +375 -0
- package/Editor/Modules/TooltipMenu.js +125 -0
- package/Editor/Modules/Utility.js +131 -0
- package/Editor/Themes/Default.css +204 -0
- package/FileUpload/FileUpload.css +156 -0
- package/FileUpload/FileUpload.js +898 -0
- package/FileUpload/Themes/Default.css +17 -0
- package/Form/Form.css +1934 -0
- package/Form/Form.js +4943 -0
- package/Form/Modules/ActionManager.js +368 -0
- package/Form/Modules/BuildPanelManager.js +319 -0
- package/Form/Modules/ComponentFactory.js +468 -0
- package/Form/Modules/ComponentPanel.js +265 -0
- package/Form/Modules/ConfigPanelManager.js +685 -0
- package/Form/Modules/DataBinder.js +602 -0
- package/Form/Modules/DataObserver.js +717 -0
- package/Form/Modules/Draggable.js +756 -0
- package/Form/Modules/OptionsPanel.js +342 -0
- package/Form/Modules/Renderer.js +1889 -0
- package/Form/Modules/RuleEngine.js +585 -0
- package/Form/Modules/RulesPanel.js +1158 -0
- package/Form/Modules/Types.js +1709 -0
- package/Form/Modules/ValidationManager.js +194 -0
- package/Form/Themes/Default.css +216 -0
- package/FormField/FormField.css +468 -0
- package/FormField/FormField.js +576 -0
- package/FormField/Themes/Default.css +99 -0
- package/Grid/Grid.css +544 -0
- package/Grid/Grid.js +3761 -0
- package/Grid/Themes/Default.css +43 -0
- package/LICENSE +126 -0
- package/MaskedTextBox/MaskedTextBox.css +28 -0
- package/MaskedTextBox/MaskedTextBox.js +642 -0
- package/MaskedTextBox/Themes/Default.css +6 -0
- package/Menu/Menu.css +328 -0
- package/Menu/Menu.js +2386 -0
- package/Menu/Themes/Default.css +24 -0
- package/NumericBox/NumericBox.css +120 -0
- package/NumericBox/NumericBox.js +984 -0
- package/PanelBar/PanelBar.css +123 -0
- package/PanelBar/PanelBar.js +505 -0
- package/PanelBar/Themes/Default.css +51 -0
- package/PieChart/PieChart.css +95 -0
- package/PieChart/PieChart.js +820 -0
- package/PieChart/Themes/Default.css +9 -0
- package/README.md +178 -3
- package/Slider/Slider.css +348 -0
- package/Slider/Slider.js +1306 -0
- package/Slider/Themes/Default.css +44 -0
- package/TabStrip/TabStrip.css +52 -0
- package/TabStrip/TabStrip.js +362 -0
- package/TabStrip/Themes/Default.css +35 -0
- package/TimePicker/Themes/Default.css +30 -0
- package/TimePicker/TimePicker.css +253 -0
- package/TimePicker/TimePicker.js +1485 -0
- package/TooltipManager/Themes/Default.css +21 -0
- package/TooltipManager/TooltipManager.css +227 -0
- package/TooltipManager/TooltipManager.js +791 -0
- package/Validator/Validator.css +54 -0
- package/Validator/Validator.js +2508 -0
- package/componyx.html-data.json +619 -0
- package/index.js +27 -0
- package/package.json +43 -4
- package/types/AppSkin.d.ts +842 -0
- package/types/Base.d.ts +1034 -0
- package/types/Bindary.d.ts +1196 -0
- package/types/BookmarkSpy.d.ts +112 -0
- package/types/Box.d.ts +374 -0
- package/types/Button.d.ts +455 -0
- package/types/Carousel.d.ts +136 -0
- package/types/ColorButton.d.ts +120 -0
- package/types/ColorPicker.d.ts +225 -0
- package/types/ComboBox.d.ts +507 -0
- package/types/DataPager.d.ts +152 -0
- package/types/DatePicker.d.ts +354 -0
- package/types/Dialog.d.ts +160 -0
- package/types/Editor.d.ts +1289 -0
- package/types/FileUpload.d.ts +206 -0
- package/types/Form.d.ts +3078 -0
- package/types/FormField.d.ts +204 -0
- package/types/Grid.d.ts +911 -0
- package/types/Library.d.ts +1899 -0
- package/types/MaskedTextBox.d.ts +91 -0
- package/types/Menu.d.ts +577 -0
- package/types/NumericBox.d.ts +310 -0
- package/types/PanelBar.d.ts +223 -0
- package/types/PieChart.d.ts +224 -0
- package/types/ResponsiveTemplates.d.ts +108 -0
- package/types/Slider.d.ts +227 -0
- package/types/TabStrip.d.ts +185 -0
- package/types/TimePicker.d.ts +265 -0
- package/types/TooltipManager.d.ts +178 -0
- package/types/UI.d.ts +29 -0
- package/types/Validator.d.ts +653 -0
- package/types/index.d.ts +59 -0
- package/ui.css +11783 -0
- package/ui.js +79477 -0
- package/ui.min.css +138 -0
- package/ui.min.js +264 -0
- package/ui.min.js.map +1 -0
|
@@ -0,0 +1,462 @@
|
|
|
1
|
+
/*! Componyx.UI | (c) E.H. Daanen / Componyx | BUSL-1.1 | componyx.com/license */
|
|
2
|
+
componyx.base_modules = componyx.base_modules || {};
|
|
3
|
+
componyx.base_modules.NavigationManager = class NavigationManager
|
|
4
|
+
{
|
|
5
|
+
#keydownHandler;
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Sets focus to the correct items through keyboard navigation.
|
|
9
|
+
*
|
|
10
|
+
* @param {HTMLElement} container - Root DOM element that receives keyboard events.
|
|
11
|
+
* @param {Object} options - Configuration options for navigation behavior.
|
|
12
|
+
* @param {Array<Object>} options.itemList - Root list of items used to build the navigation path structure.
|
|
13
|
+
* @param {boolean} options.horizontalRoot - Indicates whether the root level is laid out horizontally.
|
|
14
|
+
* @param {boolean} options.tabNavigation - Indicates whether items can also be navigated with the tab key.
|
|
15
|
+
* @param {(item:Object)=>HTMLElement} options.getButton - Callback that should return the button element that receives focus.
|
|
16
|
+
* @param {(item:Object)=>boolean} options.isExpandable - Callback that should return true if the item can be expanded.
|
|
17
|
+
* @param {(item:Object)=>boolean} options.isFocusable - Callback that should return true if the item can receive focus.
|
|
18
|
+
* @param {(item:Object)=>HTMLElement} [options.getExpander] - Callback that should return the invoker element that expanded the menu.
|
|
19
|
+
* @param {(item:Object)=>boolean} [options.getCollapseTarget] - Callback that should return the target item to collapse. Falls back to collapsing the parent item when not provided.
|
|
20
|
+
* @param {(item:Object)=>boolean} [options.isExpanded] - Callback that should return true if the item is already expanded.
|
|
21
|
+
* @param {(item:Object)=>void} [options.onExpand] - Callback that should expand the given item.
|
|
22
|
+
* @param {(item:Object)=>void} [options.onCollapse] - Callback that should collapse the given item.
|
|
23
|
+
* @param {()=>void} [options.onCollapse] - Callback that should collapse all items.
|
|
24
|
+
*/
|
|
25
|
+
constructor(container, options = {})
|
|
26
|
+
{
|
|
27
|
+
this.container = container;
|
|
28
|
+
this.horizontalRoot = options.horizontalRoot || false;
|
|
29
|
+
this.tabNavigation = options.tabNavigation;
|
|
30
|
+
this.getButton = options.getButton;
|
|
31
|
+
this.isExpandable = options.isExpandable;
|
|
32
|
+
this.isFocusable = options.isFocusable;
|
|
33
|
+
this.getExpander = options.getExpander;
|
|
34
|
+
this.getCollapseTarget = options.getCollapseTarget;
|
|
35
|
+
this.isExpanded = options.isExpanded;
|
|
36
|
+
this.onExpand = options.onExpand;
|
|
37
|
+
this.onCollapse = options.onCollapse;
|
|
38
|
+
this.onCollapseAll = options.onCollapseAll;
|
|
39
|
+
|
|
40
|
+
this.moveRootToLeft = false;
|
|
41
|
+
this.expanding = {};
|
|
42
|
+
this.collapsing = {};
|
|
43
|
+
this.focusedPath = $lib.path(options.itemList, 'itemList', null, true).create();
|
|
44
|
+
this.focusedButton = null;
|
|
45
|
+
this.#keydownHandler = this.#keydown.bind(this);
|
|
46
|
+
this.attach();
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Attaches keyboard handling and sets initial focus.
|
|
51
|
+
*/
|
|
52
|
+
attach()
|
|
53
|
+
{
|
|
54
|
+
this.container.addEventListener('keydown', this.#keydownHandler);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Removes all listeners and clears focus state.
|
|
59
|
+
*/
|
|
60
|
+
destroy()
|
|
61
|
+
{
|
|
62
|
+
this.container.removeEventListener('keydown', this.#keydownHandler);
|
|
63
|
+
this.moveRootToLeft = false;
|
|
64
|
+
this.focusedButton = null;
|
|
65
|
+
this.focusedPath = null;
|
|
66
|
+
this.expanding = {};
|
|
67
|
+
this.collapsing = {};
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Checks if the item is currently expanding due to keyboard interaction.
|
|
72
|
+
* @param {any} itemId
|
|
73
|
+
* @returns {boolean} True if the item is expanding, false otherwise.
|
|
74
|
+
*/
|
|
75
|
+
isExpanding(itemId)
|
|
76
|
+
{
|
|
77
|
+
return Boolean(this.expanding[itemId]);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Checks if the item is currently collapsing due to keyboard interaction.
|
|
82
|
+
* @param {any} itemId
|
|
83
|
+
* @returns {boolean} True if the item is collapsing, false otherwise.
|
|
84
|
+
*/
|
|
85
|
+
isCollapsing(itemId)
|
|
86
|
+
{
|
|
87
|
+
return Boolean(this.collapsing[itemId]);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Sets focus on the item, unless the event originated from within its button element.
|
|
92
|
+
* @param {Object} item
|
|
93
|
+
* @param {MouseEvent} event
|
|
94
|
+
* @returns {boolean} A value indicating if the set focus succeeded.
|
|
95
|
+
*/
|
|
96
|
+
setFocusFromEvent(item, event)
|
|
97
|
+
{
|
|
98
|
+
const button = this.getButton(item);
|
|
99
|
+
|
|
100
|
+
if (button?.contains(event.target))
|
|
101
|
+
return;
|
|
102
|
+
|
|
103
|
+
return this.setFocus(item.id);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Sets focus on the specified item.
|
|
108
|
+
* @param {string} itemId The identifier of the item.
|
|
109
|
+
* @returns {boolean} A value indicating if the set focus succeeded.
|
|
110
|
+
*/
|
|
111
|
+
setFocus(itemId)
|
|
112
|
+
{
|
|
113
|
+
const itemPath = this.focusedPath?.path(i => i.id === itemId, true);
|
|
114
|
+
return this.#setFocus(itemPath);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Must be called after an expand animation completes.
|
|
119
|
+
* Moves focus to the first focusable child of the expanded item.
|
|
120
|
+
*
|
|
121
|
+
* @param {String} itemId The id of the Expanded item.
|
|
122
|
+
*/
|
|
123
|
+
expandComplete(itemId)
|
|
124
|
+
{
|
|
125
|
+
if (!this.isExpanding(itemId))
|
|
126
|
+
return;
|
|
127
|
+
|
|
128
|
+
this.expanding[itemId] = false;
|
|
129
|
+
|
|
130
|
+
const expandedPath = this.focusedPath?.path(i => i.id === itemId, true);
|
|
131
|
+
|
|
132
|
+
if (!expandedPath)
|
|
133
|
+
return;
|
|
134
|
+
|
|
135
|
+
const childPath = expandedPath.create().firstChild();
|
|
136
|
+
if (childPath)
|
|
137
|
+
this.#focusFirst(childPath);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Must be called after a collapse animation completes.
|
|
142
|
+
* Moves focus back to the collapsed parent item.
|
|
143
|
+
*
|
|
144
|
+
* @param {String} itemId The id of the collapsed item.
|
|
145
|
+
*/
|
|
146
|
+
collapseComplete(itemId)
|
|
147
|
+
{
|
|
148
|
+
if (!this.isCollapsing(itemId))
|
|
149
|
+
return;
|
|
150
|
+
|
|
151
|
+
if (!this.collapsing[itemId])
|
|
152
|
+
return;
|
|
153
|
+
|
|
154
|
+
this.collapsing[itemId] = false;
|
|
155
|
+
|
|
156
|
+
const collapsedPath = this.focusedPath?.path(i => i.id === itemId, true);
|
|
157
|
+
let focused = false;
|
|
158
|
+
|
|
159
|
+
if (collapsedPath && this.isFocusable(collapsedPath.item))
|
|
160
|
+
{
|
|
161
|
+
let path = collapsedPath;
|
|
162
|
+
|
|
163
|
+
if (this.moveRootToLeft)
|
|
164
|
+
path = this.#resolvePreviousFocusable(path);
|
|
165
|
+
|
|
166
|
+
focused = this.#setFocus(path);
|
|
167
|
+
this.moveRootToLeft = false;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
if (!focused)
|
|
171
|
+
{
|
|
172
|
+
const invoker = this.getExpander?.();
|
|
173
|
+
if (invoker)
|
|
174
|
+
invoker.focus();
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* Sets focus on the active item represented by the given path.
|
|
180
|
+
*
|
|
181
|
+
* @param {componyx.library.Path} path Path pointing to the item to focus.
|
|
182
|
+
* @returns {boolean} A value indicating if the set focus succeeded.
|
|
183
|
+
*/
|
|
184
|
+
#setFocus(path)
|
|
185
|
+
{
|
|
186
|
+
if (!path?.item)
|
|
187
|
+
return false;
|
|
188
|
+
|
|
189
|
+
this.focusedPath = path;
|
|
190
|
+
const button = this.getButton(path.item);
|
|
191
|
+
|
|
192
|
+
if (!button)
|
|
193
|
+
return false;
|
|
194
|
+
|
|
195
|
+
if (this.focusedButton?.isConnected)
|
|
196
|
+
this.focusedButton.tabIndex = -1;
|
|
197
|
+
|
|
198
|
+
this.focusedButton = button;
|
|
199
|
+
button.tabIndex = 0;
|
|
200
|
+
button.focus({ preventScroll: true });
|
|
201
|
+
|
|
202
|
+
return (button.ownerDocument.activeElement === button);
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
/**
|
|
206
|
+
* Focuses the first focusable item within the given path scope.
|
|
207
|
+
*
|
|
208
|
+
* @param {componyx.library.Path} path Path whose items are searched.
|
|
209
|
+
*/
|
|
210
|
+
#focusFirst(path)
|
|
211
|
+
{
|
|
212
|
+
const target = this.#resolveFirstFocusable(path);
|
|
213
|
+
if (target)
|
|
214
|
+
this.#setFocus(target);
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
/**
|
|
218
|
+
* Returns the first focusable item in the given path scope.
|
|
219
|
+
*
|
|
220
|
+
* @param {componyx.library.Path} path Path whose items are searched.
|
|
221
|
+
* @returns {componyx.library.Path|null}
|
|
222
|
+
*/
|
|
223
|
+
#resolveFirstFocusable(path)
|
|
224
|
+
{
|
|
225
|
+
let current = path.create().first(),
|
|
226
|
+
startItem = current.item;
|
|
227
|
+
|
|
228
|
+
while (!this.isFocusable(current.item))
|
|
229
|
+
{
|
|
230
|
+
if (!current.hasNext())
|
|
231
|
+
return null;
|
|
232
|
+
|
|
233
|
+
current.next();
|
|
234
|
+
|
|
235
|
+
if (current.item === startItem)
|
|
236
|
+
return null;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
return current;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
/**
|
|
243
|
+
* Returns the last focusable item in the given path scope.
|
|
244
|
+
*
|
|
245
|
+
* @param {componyx.library.Path} path Path whose items are searched.
|
|
246
|
+
* @returns {componyx.library.Path|null}
|
|
247
|
+
*/
|
|
248
|
+
#resolveLastFocusable(path)
|
|
249
|
+
{
|
|
250
|
+
let current = path.create().last(),
|
|
251
|
+
startItem = current.item;
|
|
252
|
+
|
|
253
|
+
while (!this.isFocusable(current.item))
|
|
254
|
+
{
|
|
255
|
+
if (!current.hasPrevious())
|
|
256
|
+
return null;
|
|
257
|
+
|
|
258
|
+
current.previous();
|
|
259
|
+
|
|
260
|
+
if (current.item === startItem)
|
|
261
|
+
return null;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
return current;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
/**
|
|
268
|
+
* Returns the next focusable item relative to the given path.
|
|
269
|
+
*
|
|
270
|
+
* @param {componyx.library.Path} path Current focus path.
|
|
271
|
+
* @returns {componyx.library.Path|null}
|
|
272
|
+
*/
|
|
273
|
+
#resolveNextFocusable(path)
|
|
274
|
+
{
|
|
275
|
+
const current = path.create(),
|
|
276
|
+
startItem = path.item;
|
|
277
|
+
|
|
278
|
+
if (!current.hasNext())
|
|
279
|
+
return this.#resolveFirstFocusable(current);
|
|
280
|
+
|
|
281
|
+
current.next();
|
|
282
|
+
|
|
283
|
+
while (!this.isFocusable(current.item))
|
|
284
|
+
{
|
|
285
|
+
if (!current.hasNext())
|
|
286
|
+
{
|
|
287
|
+
return this.#resolveFirstFocusable(current.first());
|
|
288
|
+
}
|
|
289
|
+
current.next();
|
|
290
|
+
|
|
291
|
+
if (current.item === startItem)
|
|
292
|
+
{
|
|
293
|
+
return null;
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
return current;
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
/**
|
|
301
|
+
* Returns the previous focusable item relative to the given path.
|
|
302
|
+
*
|
|
303
|
+
* @param {componyx.library.Path} path Current focus path.
|
|
304
|
+
* @returns {componyx.library.Path|null}
|
|
305
|
+
*/
|
|
306
|
+
#resolvePreviousFocusable(path)
|
|
307
|
+
{
|
|
308
|
+
const current = path.create(),
|
|
309
|
+
startItem = path.item;
|
|
310
|
+
|
|
311
|
+
if (!current.hasPrevious())
|
|
312
|
+
return this.#resolveLastFocusable(current);
|
|
313
|
+
|
|
314
|
+
current.previous();
|
|
315
|
+
|
|
316
|
+
while (!this.isFocusable(current.item))
|
|
317
|
+
{
|
|
318
|
+
if (!current.hasPrevious())
|
|
319
|
+
{
|
|
320
|
+
return this.#resolveLastFocusable(current.last());
|
|
321
|
+
}
|
|
322
|
+
current.previous();
|
|
323
|
+
|
|
324
|
+
if (current.item === startItem)
|
|
325
|
+
{
|
|
326
|
+
return null;
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
return current;
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
/**
|
|
334
|
+
* Handles keyboard navigation and focus movement.
|
|
335
|
+
*
|
|
336
|
+
* @param {KeyboardEvent} event
|
|
337
|
+
*/
|
|
338
|
+
#keydown(event)
|
|
339
|
+
{
|
|
340
|
+
if (event.target.closest('input, textarea, select, [contenteditable]'))
|
|
341
|
+
return;
|
|
342
|
+
|
|
343
|
+
if (!this.focusedPath)
|
|
344
|
+
return;
|
|
345
|
+
|
|
346
|
+
const key = event.key,
|
|
347
|
+
path = this.focusedPath,
|
|
348
|
+
isRootLevel = !path.hasParent(),
|
|
349
|
+
onHorizontalRoot = isRootLevel && this.horizontalRoot,
|
|
350
|
+
tab = key === 'Tab' && this.tabNavigation;
|
|
351
|
+
|
|
352
|
+
let handled = true,
|
|
353
|
+
targetPath = null;
|
|
354
|
+
|
|
355
|
+
if ((key === 'ArrowRight' && (!isRootLevel || !this.horizontalRoot)) // enter child level in vertical layouts
|
|
356
|
+
|| (key === 'ArrowDown' && onHorizontalRoot)) // enter child level from horizontal root
|
|
357
|
+
{
|
|
358
|
+
if (this.isExpanded?.(path.item))
|
|
359
|
+
{
|
|
360
|
+
const childPath = path.create().firstChild();
|
|
361
|
+
targetPath = this.#resolveFirstFocusable(childPath);
|
|
362
|
+
}
|
|
363
|
+
else if (this.onExpand)
|
|
364
|
+
{
|
|
365
|
+
const canExpand = this.isExpandable?.(path.item);
|
|
366
|
+
|
|
367
|
+
if (canExpand)
|
|
368
|
+
{
|
|
369
|
+
this.expanding[path.item.id] = true;
|
|
370
|
+
this.onExpand(path.item);
|
|
371
|
+
}
|
|
372
|
+
else if (key === 'ArrowRight' && this.horizontalRoot)
|
|
373
|
+
{
|
|
374
|
+
// Navigate to the top-most root item first
|
|
375
|
+
let rootPath = path;
|
|
376
|
+
while (rootPath.hasParent())
|
|
377
|
+
{
|
|
378
|
+
rootPath = rootPath.parent();
|
|
379
|
+
}
|
|
380
|
+
this.onCollapseAll?.();
|
|
381
|
+
targetPath = this.#resolveNextFocusable(rootPath); // get next focusable sibling on root level
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
else if (key === 'Escape' || (key === 'ArrowLeft' && !isRootLevel))
|
|
386
|
+
{
|
|
387
|
+
if (this.onCollapse)
|
|
388
|
+
{
|
|
389
|
+
let item = null;
|
|
390
|
+
|
|
391
|
+
if (this.getCollapseTarget)
|
|
392
|
+
item = this.getCollapseTarget(path.item);
|
|
393
|
+
else if (path.hasParent())
|
|
394
|
+
item = path.parent().item;
|
|
395
|
+
|
|
396
|
+
if (item)
|
|
397
|
+
{
|
|
398
|
+
this.collapsing[item.id] = true;
|
|
399
|
+
this.onCollapse(item);
|
|
400
|
+
|
|
401
|
+
if (key === 'ArrowLeft' && this.horizontalRoot && item.id !== 'root') // collapsing first level, check if we need to navigate on horizontal root
|
|
402
|
+
{
|
|
403
|
+
let itemPath = path.path(i => i.id === item.id, true);
|
|
404
|
+
|
|
405
|
+
if (itemPath?.item && !itemPath.hasParent())
|
|
406
|
+
this.moveRootToLeft = true;
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
else if ((key === 'ArrowLeft' && onHorizontalRoot) || (key === 'ArrowUp' && !onHorizontalRoot))
|
|
412
|
+
{
|
|
413
|
+
targetPath = this.#resolvePreviousFocusable(path);
|
|
414
|
+
}
|
|
415
|
+
else if (((key === 'ArrowRight' || tab) && onHorizontalRoot) || ((key === 'ArrowDown' || tab) && !onHorizontalRoot))
|
|
416
|
+
{
|
|
417
|
+
targetPath = this.#resolveNextFocusable(path);
|
|
418
|
+
}
|
|
419
|
+
else if (key === 'Home')
|
|
420
|
+
{
|
|
421
|
+
targetPath = this.#resolveFirstFocusable(path);
|
|
422
|
+
}
|
|
423
|
+
else if (key === 'End')
|
|
424
|
+
{
|
|
425
|
+
targetPath = this.#resolveLastFocusable(path);
|
|
426
|
+
}
|
|
427
|
+
else if (key.length === 1 && !/\s/.test(key))
|
|
428
|
+
{
|
|
429
|
+
const items = path.hasParent() ? path.parent().create().children : path.items,
|
|
430
|
+
startIndex = items.findIndex(item => item.id === path.item.id),
|
|
431
|
+
length = items.length,
|
|
432
|
+
lowerKey = key.toLowerCase();
|
|
433
|
+
|
|
434
|
+
for (let index = startIndex + 1; index < startIndex + length + 1; index++)
|
|
435
|
+
{
|
|
436
|
+
let item = items[index % length];
|
|
437
|
+
if (this.isFocusable(item) && item.text?.[0]?.toLowerCase() === lowerKey)
|
|
438
|
+
{
|
|
439
|
+
targetPath = path.path(i => i.id === item.id, true);
|
|
440
|
+
break;
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
else
|
|
445
|
+
{
|
|
446
|
+
handled = false;
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
if (targetPath)
|
|
450
|
+
this.#setFocus(targetPath);
|
|
451
|
+
|
|
452
|
+
if (handled)
|
|
453
|
+
{
|
|
454
|
+
event.preventDefault();
|
|
455
|
+
event.stopPropagation();
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
};
|
|
459
|
+
|
|
460
|
+
var NavigationManager = componyx.base_modules.NavigationManager;
|
|
461
|
+
|
|
462
|
+
export { NavigationManager as default };
|