cx 26.4.2 → 26.4.3
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/build/jsx-dev-runtime.d.ts +1 -0
- package/build/jsx-dev-runtime.d.ts.map +1 -1
- package/build/jsx-dev-runtime.js +1 -0
- package/dist/manifest.js +889 -889
- package/package.json +1 -1
- package/src/charts/BarGraph.scss +31 -31
- package/src/charts/Legend.scss +57 -57
- package/src/charts/LegendEntry.scss +35 -35
- package/src/charts/LineGraph.scss +28 -28
- package/src/charts/helpers/SnapPointFinder.ts +136 -136
- package/src/charts/helpers/ValueAtFinder.ts +72 -72
- package/src/data/createAccessorModelProxy.ts +66 -66
- package/src/jsx-dev-runtime.ts +1 -0
- package/src/ui/DataProxy.ts +55 -55
- package/src/ui/Repeater.spec.tsx +181 -181
- package/src/ui/Rescope.ts +50 -50
- package/src/ui/adapter/ArrayAdapter.ts +229 -229
- package/src/ui/exprHelpers.ts +96 -96
- package/src/util/scss/include.scss +69 -69
- package/src/widgets/Button.maps.scss +103 -103
- package/src/widgets/Sandbox.ts +104 -104
- package/src/widgets/form/Calendar.tsx +772 -772
- package/src/widgets/form/ColorField.scss +112 -112
- package/src/widgets/form/DateTimeField.scss +111 -111
- package/src/widgets/form/LookupField.maps.scss +26 -26
- package/src/widgets/form/LookupField.scss +227 -227
- package/src/widgets/form/MonthField.scss +113 -113
- package/src/widgets/form/NumberField.scss +72 -72
- package/src/widgets/form/Select.scss +104 -104
- package/src/widgets/form/TextField.scss +66 -66
- package/src/widgets/grid/Grid.scss +657 -657
- package/src/widgets/grid/variables.scss +47 -47
- package/src/widgets/index.ts +63 -63
- package/src/widgets/nav/MenuItem.scss +150 -150
- package/src/widgets/nav/MenuItem.tsx +525 -525
- package/src/widgets/nav/Tab.ts +122 -122
- package/src/widgets/overlay/Overlay.tsx +1029 -1029
- package/src/widgets/variables.scss +61 -61
|
@@ -1,525 +1,525 @@
|
|
|
1
|
-
/** @jsxImportSource react */
|
|
2
|
-
import { Widget, VDOM } from "../../ui/Widget";
|
|
3
|
-
import { Cx } from "../../ui/Cx";
|
|
4
|
-
import { HtmlElement, HtmlElementConfigBase, HtmlElementInstance } from "../HtmlElement";
|
|
5
|
-
import { Instance } from "../../ui/Instance";
|
|
6
|
-
import { RenderingContext } from "../../ui/RenderingContext";
|
|
7
|
-
import { findFirstChild, isFocusable, isSelfOrDescendant, closest, isFocusedDeep, isFocused } from "../../util/DOM";
|
|
8
|
-
import { Dropdown, DropdownConfig } from "../overlay/Dropdown";
|
|
9
|
-
import { FocusManager, oneFocusOut, offFocusOut } from "../../ui/FocusManager";
|
|
10
|
-
import { debug, menuFlag } from "../../util/Debug";
|
|
11
|
-
import DropdownIcon from "../icons/drop-down";
|
|
12
|
-
import { Icon } from "../Icon";
|
|
13
|
-
import { Localization } from "../../ui/Localization";
|
|
14
|
-
import { KeyCode } from "../../util/KeyCode";
|
|
15
|
-
import { registerKeyboardShortcut, KeyboardShortcut } from "../../ui/keyboardShortcuts";
|
|
16
|
-
import { getActiveElement } from "../../util/getActiveElement";
|
|
17
|
-
import {
|
|
18
|
-
tooltipMouseLeave,
|
|
19
|
-
tooltipMouseMove,
|
|
20
|
-
tooltipParentWillUnmount,
|
|
21
|
-
tooltipParentDidMount,
|
|
22
|
-
} from "../overlay/tooltip-ops";
|
|
23
|
-
import { yesNo } from "../overlay/alerts";
|
|
24
|
-
import { isTextInputElement, stopPropagation } from "../../util";
|
|
25
|
-
import { unfocusElement } from "../../ui/FocusManager";
|
|
26
|
-
import { BooleanProp, Prop, StringProp } from "../../ui/Prop";
|
|
27
|
-
import { Config } from "../../ui/Prop";
|
|
28
|
-
|
|
29
|
-
/*
|
|
30
|
-
Functionality:
|
|
31
|
-
- renders dropdown when focused
|
|
32
|
-
- tracks focus and closes if focusElement goes outside the dropdown
|
|
33
|
-
- switches focus to the dropdown when right key pressed
|
|
34
|
-
- listens to dropdown's key events and captures focus back when needed
|
|
35
|
-
- automatically opens the dropdown if mouse is held over for a period of time
|
|
36
|
-
*/
|
|
37
|
-
|
|
38
|
-
export interface MenuItemConfig extends HtmlElementConfigBase {
|
|
39
|
-
baseClass?: string;
|
|
40
|
-
hoverFocusTimeout?: number;
|
|
41
|
-
clickToOpen?: boolean;
|
|
42
|
-
hoverToOpen?: boolean;
|
|
43
|
-
horizontal?: boolean;
|
|
44
|
-
arrow?: BooleanProp;
|
|
45
|
-
dropdownOptions?: Partial<DropdownConfig>;
|
|
46
|
-
showCursor?: boolean;
|
|
47
|
-
pad?: boolean;
|
|
48
|
-
placement?: string;
|
|
49
|
-
placementOrder?: string;
|
|
50
|
-
autoClose?: boolean;
|
|
51
|
-
icons?: boolean;
|
|
52
|
-
icon?: StringProp;
|
|
53
|
-
keyboardShortcut?: KeyboardShortcut | false;
|
|
54
|
-
tooltip?: string | Config;
|
|
55
|
-
openOnFocus?: boolean;
|
|
56
|
-
disabled?: BooleanProp;
|
|
57
|
-
checked?: BooleanProp;
|
|
58
|
-
confirm?: Prop<string | Config>;
|
|
59
|
-
checkedIcon?: string;
|
|
60
|
-
uncheckedIcon?: string;
|
|
61
|
-
padding?: string;
|
|
62
|
-
hideCursor?: boolean;
|
|
63
|
-
dropdown?: any;
|
|
64
|
-
onClick?: string | ((e: React.MouseEvent | null, instance: HtmlElementInstance<MenuItem>) => void);
|
|
65
|
-
onMouseDown?: string | ((e: React.MouseEvent, instance: HtmlElementInstance<MenuItem>) => void);
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
export class MenuItemInstance extends HtmlElementInstance<MenuItem> {
|
|
69
|
-
declare horizontal?: boolean;
|
|
70
|
-
declare padding?: string;
|
|
71
|
-
declare icons?: boolean;
|
|
72
|
-
declare parentPositionChangeEvent?: any;
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
export class MenuItem extends HtmlElement<MenuItemConfig, MenuItemInstance> {
|
|
76
|
-
declare public baseClass: string;
|
|
77
|
-
declare public hoverFocusTimeout: number;
|
|
78
|
-
declare public clickToOpen: boolean;
|
|
79
|
-
declare public hoverToOpen: boolean;
|
|
80
|
-
declare public horizontal: boolean;
|
|
81
|
-
declare public arrow: BooleanProp;
|
|
82
|
-
declare public dropdownOptions: Partial<DropdownConfig> | null;
|
|
83
|
-
declare public showCursor: boolean;
|
|
84
|
-
declare public pad: boolean;
|
|
85
|
-
declare public placement: string | null;
|
|
86
|
-
declare public placementOrder: string | null;
|
|
87
|
-
declare public autoClose: boolean;
|
|
88
|
-
declare public checkedIcon: string;
|
|
89
|
-
declare public uncheckedIcon: string;
|
|
90
|
-
declare public keyboardShortcut: KeyboardShortcut | false;
|
|
91
|
-
declare public openOnFocus: boolean;
|
|
92
|
-
declare public hideCursor?: boolean;
|
|
93
|
-
declare public checked?: BooleanProp;
|
|
94
|
-
declare public padding?: string;
|
|
95
|
-
declare public dropdown?: any;
|
|
96
|
-
init() {
|
|
97
|
-
if (this.hideCursor) this.showCursor = false;
|
|
98
|
-
super.init();
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
declareData() {
|
|
102
|
-
super.declareData(...arguments, {
|
|
103
|
-
icon: undefined,
|
|
104
|
-
disabled: undefined,
|
|
105
|
-
checked: false,
|
|
106
|
-
arrow: undefined,
|
|
107
|
-
confirm: undefined,
|
|
108
|
-
});
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
explore(context: RenderingContext, instance: MenuItemInstance) {
|
|
112
|
-
instance.horizontal = this.horizontal;
|
|
113
|
-
let { lastMenu } = context;
|
|
114
|
-
if (lastMenu) {
|
|
115
|
-
instance.horizontal = lastMenu.horizontal;
|
|
116
|
-
instance.padding = lastMenu.itemPadding;
|
|
117
|
-
instance.icons = lastMenu.icons;
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
instance.parentPositionChangeEvent = context.parentPositionChangeEvent;
|
|
121
|
-
|
|
122
|
-
if (!instance.padding && this.pad == true) instance.padding = "medium";
|
|
123
|
-
|
|
124
|
-
if (this.padding) instance.padding = this.padding;
|
|
125
|
-
|
|
126
|
-
context.push("lastMenuItem", this);
|
|
127
|
-
super.explore(context, instance);
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
exploreCleanup(context: RenderingContext, instance: MenuItemInstance) {
|
|
131
|
-
context.pop("lastMenuItem");
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
render(context: RenderingContext, instance: MenuItemInstance, key: string) {
|
|
135
|
-
return (
|
|
136
|
-
<MenuItemComponent key={key} instance={instance} data={instance.data}>
|
|
137
|
-
{instance.data.text ? <span>{instance.data.text}</span> : this.renderChildren(context, instance)}
|
|
138
|
-
</MenuItemComponent>
|
|
139
|
-
);
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
add(element: any) {
|
|
143
|
-
if (element && typeof element == "object" && element.putInto == "dropdown") {
|
|
144
|
-
this.dropdown = { ...element };
|
|
145
|
-
delete this.dropdown.putInto;
|
|
146
|
-
} else super.add(...arguments);
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
addText(text: any) {
|
|
150
|
-
this.add({
|
|
151
|
-
type: HtmlElement,
|
|
152
|
-
tag: "span",
|
|
153
|
-
text: text,
|
|
154
|
-
});
|
|
155
|
-
}
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
MenuItem.prototype.baseClass = "menuitem";
|
|
159
|
-
MenuItem.prototype.hoverFocusTimeout = 500;
|
|
160
|
-
MenuItem.prototype.hoverToOpen = false;
|
|
161
|
-
MenuItem.prototype.clickToOpen = false;
|
|
162
|
-
MenuItem.prototype.horizontal = true;
|
|
163
|
-
MenuItem.prototype.arrow = false;
|
|
164
|
-
MenuItem.prototype.dropdownOptions = null;
|
|
165
|
-
MenuItem.prototype.showCursor = true;
|
|
166
|
-
MenuItem.prototype.pad = true;
|
|
167
|
-
MenuItem.prototype.placement = null; //default dropdown placement
|
|
168
|
-
MenuItem.prototype.placementOrder = null; //allowed menu placements
|
|
169
|
-
MenuItem.prototype.autoClose = false;
|
|
170
|
-
MenuItem.prototype.checkedIcon = "check";
|
|
171
|
-
MenuItem.prototype.uncheckedIcon = "dummy";
|
|
172
|
-
MenuItem.prototype.keyboardShortcut = false;
|
|
173
|
-
MenuItem.prototype.openOnFocus = true;
|
|
174
|
-
|
|
175
|
-
Widget.alias("submenu", MenuItem);
|
|
176
|
-
Localization.registerPrototype("cx/widgets/MenuItem", MenuItem);
|
|
177
|
-
|
|
178
|
-
interface MenuItemComponentProps {
|
|
179
|
-
instance: MenuItemInstance;
|
|
180
|
-
data: any;
|
|
181
|
-
children?: any;
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
interface MenuItemComponentState {
|
|
185
|
-
dropdownOpen: boolean;
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
class MenuItemComponent extends VDOM.Component<MenuItemComponentProps, MenuItemComponentState> {
|
|
189
|
-
declare dropdown?: Widget;
|
|
190
|
-
declare el?: HTMLElement;
|
|
191
|
-
validateDropdownPosition?: () => void;
|
|
192
|
-
unregisterKeyboardShortcut?: () => void;
|
|
193
|
-
declare autoFocusTimerId?: number;
|
|
194
|
-
declare initialScreenPosition?: any;
|
|
195
|
-
offParentPositionChange?: () => void;
|
|
196
|
-
|
|
197
|
-
constructor(props: MenuItemComponentProps) {
|
|
198
|
-
super(props);
|
|
199
|
-
this.state = {
|
|
200
|
-
dropdownOpen: false,
|
|
201
|
-
};
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
getDefaultPlacementOrder(horizontal?: boolean) {
|
|
205
|
-
return horizontal
|
|
206
|
-
? "down-right down down-left up-right up up-left"
|
|
207
|
-
: "right-down right right-up left-down left left-up";
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
getDropdown() {
|
|
211
|
-
let { horizontal, widget, parentPositionChangeEvent } = this.props.instance;
|
|
212
|
-
if (!this.dropdown && widget.dropdown) {
|
|
213
|
-
this.dropdown = Widget.create(Dropdown, {
|
|
214
|
-
matchWidth: false,
|
|
215
|
-
placementOrder: widget.placementOrder || this.getDefaultPlacementOrder(horizontal),
|
|
216
|
-
trackScroll: true,
|
|
217
|
-
inline: true,
|
|
218
|
-
onClick: stopPropagation,
|
|
219
|
-
...widget.dropdownOptions,
|
|
220
|
-
relatedElement: this.el!.parentElement,
|
|
221
|
-
placement: widget.placement,
|
|
222
|
-
onKeyDown: this.onDropdownKeyDown.bind(this),
|
|
223
|
-
onMouseDown: stopPropagation,
|
|
224
|
-
items: widget.dropdown,
|
|
225
|
-
parentPositionChangeEvent,
|
|
226
|
-
pipeValidateDropdownPosition: (cb: any) => {
|
|
227
|
-
this.validateDropdownPosition = cb;
|
|
228
|
-
},
|
|
229
|
-
onDismissAfterScroll: () => {
|
|
230
|
-
this.closeDropdown();
|
|
231
|
-
return false;
|
|
232
|
-
},
|
|
233
|
-
});
|
|
234
|
-
}
|
|
235
|
-
return this.dropdown;
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
render() {
|
|
239
|
-
let { instance, data, children } = this.props;
|
|
240
|
-
let { widget } = instance;
|
|
241
|
-
let { CSS, baseClass } = widget;
|
|
242
|
-
let dropdown = this.state.dropdownOpen && (
|
|
243
|
-
<Cx widget={this.getDropdown()} options={{ name: "submenu" }} parentInstance={instance} subscribe />
|
|
244
|
-
);
|
|
245
|
-
|
|
246
|
-
let arrow = data.arrow && <DropdownIcon className={CSS.element(baseClass, "arrow")} />;
|
|
247
|
-
|
|
248
|
-
let icon = null;
|
|
249
|
-
|
|
250
|
-
let checkbox = widget.checked != null;
|
|
251
|
-
|
|
252
|
-
if (checkbox) {
|
|
253
|
-
data.icon = data.checked ? widget.checkedIcon : widget.uncheckedIcon;
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
if (data.icon) {
|
|
257
|
-
icon = (
|
|
258
|
-
<div
|
|
259
|
-
className={CSS.element(baseClass, "button")}
|
|
260
|
-
onClick={(e) => {
|
|
261
|
-
e.preventDefault();
|
|
262
|
-
if (!instance.set("checked", !data.checked)) this.onClick(e);
|
|
263
|
-
}}
|
|
264
|
-
onMouseDown={(e) => {
|
|
265
|
-
if (checkbox) e.stopPropagation();
|
|
266
|
-
}}
|
|
267
|
-
>
|
|
268
|
-
{Icon.render(data.icon, { className: CSS.element(baseClass, "icon") })}
|
|
269
|
-
</div>
|
|
270
|
-
);
|
|
271
|
-
}
|
|
272
|
-
|
|
273
|
-
let empty = !children || (Array.isArray(children) && children.length == 0);
|
|
274
|
-
|
|
275
|
-
let classNames = CSS.expand(
|
|
276
|
-
data.classNames,
|
|
277
|
-
CSS.state({
|
|
278
|
-
open: this.state.dropdownOpen,
|
|
279
|
-
horizontal: instance.horizontal,
|
|
280
|
-
vertical: !instance.horizontal,
|
|
281
|
-
arrow: data.arrow,
|
|
282
|
-
cursor: widget.showCursor,
|
|
283
|
-
[instance.padding + "-padding"]: instance.padding,
|
|
284
|
-
icon: !!icon || instance.icons,
|
|
285
|
-
disabled: data.disabled,
|
|
286
|
-
empty,
|
|
287
|
-
}),
|
|
288
|
-
);
|
|
289
|
-
|
|
290
|
-
if (empty) children = <span className={CSS.element(baseClass, "baseline")}> </span>;
|
|
291
|
-
|
|
292
|
-
return (
|
|
293
|
-
<div
|
|
294
|
-
className={classNames}
|
|
295
|
-
style={data.style}
|
|
296
|
-
tabIndex={!data.disabled && (widget.dropdown || widget.onClick || widget.checked) ? 0 : undefined}
|
|
297
|
-
ref={(el: any) => {
|
|
298
|
-
this.el = el;
|
|
299
|
-
}}
|
|
300
|
-
onKeyDown={this.onKeyDown.bind(this)}
|
|
301
|
-
onMouseDown={this.onMouseDown.bind(this)}
|
|
302
|
-
onMouseEnter={this.onMouseEnter.bind(this)}
|
|
303
|
-
onMouseLeave={this.onMouseLeave.bind(this)}
|
|
304
|
-
onFocus={this.onFocus.bind(this)}
|
|
305
|
-
onClick={this.onClick.bind(this)}
|
|
306
|
-
onBlur={this.onBlur.bind(this)}
|
|
307
|
-
>
|
|
308
|
-
{icon}
|
|
309
|
-
{children}
|
|
310
|
-
{arrow}
|
|
311
|
-
{dropdown}
|
|
312
|
-
</div>
|
|
313
|
-
);
|
|
314
|
-
}
|
|
315
|
-
|
|
316
|
-
componentDidUpdate() {
|
|
317
|
-
if (this.state.dropdownOpen && this.validateDropdownPosition) {
|
|
318
|
-
this.validateDropdownPosition();
|
|
319
|
-
}
|
|
320
|
-
}
|
|
321
|
-
|
|
322
|
-
componentDidMount() {
|
|
323
|
-
let { widget } = this.props.instance;
|
|
324
|
-
if (widget.keyboardShortcut)
|
|
325
|
-
this.unregisterKeyboardShortcut = registerKeyboardShortcut(widget.keyboardShortcut, (e: any) => {
|
|
326
|
-
this.el!.focus(); //open the dropdown
|
|
327
|
-
this.onClick(e); //execute the onClick handler
|
|
328
|
-
});
|
|
329
|
-
|
|
330
|
-
tooltipParentDidMount(this.el!, this.props.instance, widget.tooltip);
|
|
331
|
-
}
|
|
332
|
-
|
|
333
|
-
onDropdownKeyDown(e: React.KeyboardEvent) {
|
|
334
|
-
debug(menuFlag, "MenuItem", "dropdownKeyDown");
|
|
335
|
-
let { horizontal } = this.props.instance;
|
|
336
|
-
if (
|
|
337
|
-
e.keyCode == KeyCode.esc ||
|
|
338
|
-
(!isTextInputElement(e.currentTarget) && (horizontal ? e.keyCode == KeyCode.up : e.keyCode == KeyCode.left))
|
|
339
|
-
) {
|
|
340
|
-
FocusManager.focus(this.el!);
|
|
341
|
-
e.preventDefault();
|
|
342
|
-
e.stopPropagation();
|
|
343
|
-
}
|
|
344
|
-
}
|
|
345
|
-
|
|
346
|
-
clearAutoFocusTimer() {
|
|
347
|
-
if (this.autoFocusTimerId) {
|
|
348
|
-
debug(menuFlag, "MenuItem", "autoFocusCancel");
|
|
349
|
-
clearTimeout(this.autoFocusTimerId);
|
|
350
|
-
delete this.autoFocusTimerId;
|
|
351
|
-
}
|
|
352
|
-
}
|
|
353
|
-
|
|
354
|
-
onMouseEnter(e: React.MouseEvent) {
|
|
355
|
-
debug(menuFlag, "MenuItem", "mouseEnter", this.el);
|
|
356
|
-
let { widget } = this.props.instance;
|
|
357
|
-
if (widget.dropdown && !this.state.dropdownOpen) {
|
|
358
|
-
this.clearAutoFocusTimer();
|
|
359
|
-
|
|
360
|
-
if (widget.hoverToOpen) FocusManager.focus(this.el!);
|
|
361
|
-
else if (!widget.clickToOpen) {
|
|
362
|
-
// Automatically open the dropdown only if parent menu is focused
|
|
363
|
-
let commonParentMenu = closest(this.el!, (el) => el.tagName == "UL" && el.contains(getActiveElement()));
|
|
364
|
-
if (commonParentMenu)
|
|
365
|
-
this.autoFocusTimerId = setTimeout(() => {
|
|
366
|
-
delete this.autoFocusTimerId;
|
|
367
|
-
if (!this.state.dropdownOpen) {
|
|
368
|
-
debug(menuFlag, "MenuItem", "hoverFocusTimeout:before", this.el);
|
|
369
|
-
FocusManager.focus(this.el!);
|
|
370
|
-
debug(menuFlag, "MenuItem", "hoverFocusTimeout:after", this.el, getActiveElement());
|
|
371
|
-
}
|
|
372
|
-
}, widget.hoverFocusTimeout) as any;
|
|
373
|
-
}
|
|
374
|
-
|
|
375
|
-
e.stopPropagation();
|
|
376
|
-
e.preventDefault();
|
|
377
|
-
}
|
|
378
|
-
|
|
379
|
-
tooltipMouseMove(e, this.props.instance, widget.tooltip);
|
|
380
|
-
}
|
|
381
|
-
|
|
382
|
-
onMouseLeave(e: React.MouseEvent) {
|
|
383
|
-
let { widget } = this.props.instance;
|
|
384
|
-
if (widget.dropdown) {
|
|
385
|
-
debug(menuFlag, "MenuItem", "mouseLeave", this.el);
|
|
386
|
-
this.clearAutoFocusTimer();
|
|
387
|
-
|
|
388
|
-
if (widget.hoverToOpen && document.activeElement == this.el) unfocusElement(this.el!);
|
|
389
|
-
}
|
|
390
|
-
|
|
391
|
-
tooltipMouseLeave(e, this.props.instance, widget.tooltip);
|
|
392
|
-
}
|
|
393
|
-
|
|
394
|
-
onKeyDown(e: React.KeyboardEvent) {
|
|
395
|
-
debug(menuFlag, "MenuItem", "keyDown", this.el);
|
|
396
|
-
let { horizontal, widget } = this.props.instance;
|
|
397
|
-
if (widget.dropdown) {
|
|
398
|
-
if (
|
|
399
|
-
!this.state.dropdownOpen &&
|
|
400
|
-
e.target == this.el &&
|
|
401
|
-
(e.keyCode == KeyCode.enter || (horizontal ? e.keyCode == KeyCode.down : e.keyCode == KeyCode.right))
|
|
402
|
-
) {
|
|
403
|
-
this.openDropdown(() => {
|
|
404
|
-
let focusableChild = findFirstChild(this.el!, isFocusable);
|
|
405
|
-
if (focusableChild) FocusManager.focus(focusableChild);
|
|
406
|
-
});
|
|
407
|
-
e.preventDefault();
|
|
408
|
-
e.stopPropagation();
|
|
409
|
-
}
|
|
410
|
-
|
|
411
|
-
if (e.keyCode == KeyCode.esc) {
|
|
412
|
-
if (!isFocused(this.el!)) {
|
|
413
|
-
FocusManager.focus(this.el!);
|
|
414
|
-
e.preventDefault();
|
|
415
|
-
e.stopPropagation();
|
|
416
|
-
}
|
|
417
|
-
this.closeDropdown();
|
|
418
|
-
}
|
|
419
|
-
} else {
|
|
420
|
-
if (e.keyCode == KeyCode.enter && widget.onClick) this.onClick(e);
|
|
421
|
-
}
|
|
422
|
-
}
|
|
423
|
-
|
|
424
|
-
onMouseDown(e: React.MouseEvent) {
|
|
425
|
-
let { widget } = this.props.instance;
|
|
426
|
-
if (widget.dropdown) {
|
|
427
|
-
e.stopPropagation();
|
|
428
|
-
if (this.state.dropdownOpen && !widget.hoverToOpen) this.closeDropdown();
|
|
429
|
-
else {
|
|
430
|
-
//IE sometimes does not focus parent on child click
|
|
431
|
-
if (!isFocusedDeep(this.el!)) FocusManager.focus(this.el!);
|
|
432
|
-
this.openDropdown();
|
|
433
|
-
|
|
434
|
-
//If one of the elements is auto focused prevent stealing focus
|
|
435
|
-
if (isFocusedDeep(this.el!)) e.preventDefault();
|
|
436
|
-
}
|
|
437
|
-
}
|
|
438
|
-
}
|
|
439
|
-
|
|
440
|
-
openDropdown(callback?: any) {
|
|
441
|
-
let { widget } = this.props.instance;
|
|
442
|
-
if (widget.dropdown) {
|
|
443
|
-
if (!this.state.dropdownOpen) {
|
|
444
|
-
this.setState(
|
|
445
|
-
{
|
|
446
|
-
dropdownOpen: true,
|
|
447
|
-
},
|
|
448
|
-
callback,
|
|
449
|
-
);
|
|
450
|
-
|
|
451
|
-
//hide tooltip if dropdown is open
|
|
452
|
-
tooltipMouseLeave(null as any, this.props.instance, widget.tooltip);
|
|
453
|
-
} else if (callback) callback(this.state);
|
|
454
|
-
}
|
|
455
|
-
}
|
|
456
|
-
|
|
457
|
-
onClick(e: any) {
|
|
458
|
-
e.stopPropagation();
|
|
459
|
-
|
|
460
|
-
let { instance } = this.props;
|
|
461
|
-
let { data } = instance;
|
|
462
|
-
if (data.disabled) {
|
|
463
|
-
e.preventDefault();
|
|
464
|
-
return;
|
|
465
|
-
}
|
|
466
|
-
|
|
467
|
-
let { widget } = instance;
|
|
468
|
-
if (widget.dropdown) e.preventDefault();
|
|
469
|
-
//prevent navigation
|
|
470
|
-
else {
|
|
471
|
-
instance.set("checked", !instance.data.checked);
|
|
472
|
-
|
|
473
|
-
if (widget.onClick) {
|
|
474
|
-
if (data.confirm) {
|
|
475
|
-
yesNo(data.confirm).then((btn) => {
|
|
476
|
-
if (btn == "yes") instance.invoke("onClick", null, instance);
|
|
477
|
-
});
|
|
478
|
-
} else instance.invoke("onClick", e, instance);
|
|
479
|
-
}
|
|
480
|
-
}
|
|
481
|
-
|
|
482
|
-
if (widget.autoClose) unfocusElement(this.el, true);
|
|
483
|
-
}
|
|
484
|
-
|
|
485
|
-
onFocus() {
|
|
486
|
-
let { widget } = this.props.instance;
|
|
487
|
-
if (widget.dropdown) {
|
|
488
|
-
oneFocusOut(this, this.el!, this.onFocusOut.bind(this));
|
|
489
|
-
debug(menuFlag, "MenuItem", "focus", this.el, document.activeElement);
|
|
490
|
-
this.clearAutoFocusTimer();
|
|
491
|
-
if (widget.openOnFocus) this.openDropdown();
|
|
492
|
-
}
|
|
493
|
-
}
|
|
494
|
-
|
|
495
|
-
onBlur() {
|
|
496
|
-
FocusManager.nudge();
|
|
497
|
-
}
|
|
498
|
-
|
|
499
|
-
closeDropdown() {
|
|
500
|
-
this.setState({
|
|
501
|
-
dropdownOpen: false,
|
|
502
|
-
});
|
|
503
|
-
delete this.initialScreenPosition;
|
|
504
|
-
}
|
|
505
|
-
|
|
506
|
-
onFocusOut(focusedElement: any) {
|
|
507
|
-
debug(menuFlag, "MenuItem", "focusout", this.el, focusedElement);
|
|
508
|
-
this.clearAutoFocusTimer();
|
|
509
|
-
if (this.el && !isSelfOrDescendant(this.el, focusedElement)) {
|
|
510
|
-
debug(menuFlag, "MenuItem", "closing dropdown", this.el, focusedElement);
|
|
511
|
-
this.closeDropdown();
|
|
512
|
-
}
|
|
513
|
-
}
|
|
514
|
-
|
|
515
|
-
componentWillUnmount() {
|
|
516
|
-
this.clearAutoFocusTimer();
|
|
517
|
-
offFocusOut(this);
|
|
518
|
-
|
|
519
|
-
if (this.offParentPositionChange) this.offParentPositionChange();
|
|
520
|
-
|
|
521
|
-
if (this.unregisterKeyboardShortcut) this.unregisterKeyboardShortcut();
|
|
522
|
-
|
|
523
|
-
tooltipParentWillUnmount(this.props.instance);
|
|
524
|
-
}
|
|
525
|
-
}
|
|
1
|
+
/** @jsxImportSource react */
|
|
2
|
+
import { Widget, VDOM } from "../../ui/Widget";
|
|
3
|
+
import { Cx } from "../../ui/Cx";
|
|
4
|
+
import { HtmlElement, HtmlElementConfigBase, HtmlElementInstance } from "../HtmlElement";
|
|
5
|
+
import { Instance } from "../../ui/Instance";
|
|
6
|
+
import { RenderingContext } from "../../ui/RenderingContext";
|
|
7
|
+
import { findFirstChild, isFocusable, isSelfOrDescendant, closest, isFocusedDeep, isFocused } from "../../util/DOM";
|
|
8
|
+
import { Dropdown, DropdownConfig } from "../overlay/Dropdown";
|
|
9
|
+
import { FocusManager, oneFocusOut, offFocusOut } from "../../ui/FocusManager";
|
|
10
|
+
import { debug, menuFlag } from "../../util/Debug";
|
|
11
|
+
import DropdownIcon from "../icons/drop-down";
|
|
12
|
+
import { Icon } from "../Icon";
|
|
13
|
+
import { Localization } from "../../ui/Localization";
|
|
14
|
+
import { KeyCode } from "../../util/KeyCode";
|
|
15
|
+
import { registerKeyboardShortcut, KeyboardShortcut } from "../../ui/keyboardShortcuts";
|
|
16
|
+
import { getActiveElement } from "../../util/getActiveElement";
|
|
17
|
+
import {
|
|
18
|
+
tooltipMouseLeave,
|
|
19
|
+
tooltipMouseMove,
|
|
20
|
+
tooltipParentWillUnmount,
|
|
21
|
+
tooltipParentDidMount,
|
|
22
|
+
} from "../overlay/tooltip-ops";
|
|
23
|
+
import { yesNo } from "../overlay/alerts";
|
|
24
|
+
import { isTextInputElement, stopPropagation } from "../../util";
|
|
25
|
+
import { unfocusElement } from "../../ui/FocusManager";
|
|
26
|
+
import { BooleanProp, Prop, StringProp } from "../../ui/Prop";
|
|
27
|
+
import { Config } from "../../ui/Prop";
|
|
28
|
+
|
|
29
|
+
/*
|
|
30
|
+
Functionality:
|
|
31
|
+
- renders dropdown when focused
|
|
32
|
+
- tracks focus and closes if focusElement goes outside the dropdown
|
|
33
|
+
- switches focus to the dropdown when right key pressed
|
|
34
|
+
- listens to dropdown's key events and captures focus back when needed
|
|
35
|
+
- automatically opens the dropdown if mouse is held over for a period of time
|
|
36
|
+
*/
|
|
37
|
+
|
|
38
|
+
export interface MenuItemConfig extends HtmlElementConfigBase {
|
|
39
|
+
baseClass?: string;
|
|
40
|
+
hoverFocusTimeout?: number;
|
|
41
|
+
clickToOpen?: boolean;
|
|
42
|
+
hoverToOpen?: boolean;
|
|
43
|
+
horizontal?: boolean;
|
|
44
|
+
arrow?: BooleanProp;
|
|
45
|
+
dropdownOptions?: Partial<DropdownConfig>;
|
|
46
|
+
showCursor?: boolean;
|
|
47
|
+
pad?: boolean;
|
|
48
|
+
placement?: string;
|
|
49
|
+
placementOrder?: string;
|
|
50
|
+
autoClose?: boolean;
|
|
51
|
+
icons?: boolean;
|
|
52
|
+
icon?: StringProp;
|
|
53
|
+
keyboardShortcut?: KeyboardShortcut | false;
|
|
54
|
+
tooltip?: string | Config;
|
|
55
|
+
openOnFocus?: boolean;
|
|
56
|
+
disabled?: BooleanProp;
|
|
57
|
+
checked?: BooleanProp;
|
|
58
|
+
confirm?: Prop<string | Config>;
|
|
59
|
+
checkedIcon?: string;
|
|
60
|
+
uncheckedIcon?: string;
|
|
61
|
+
padding?: string;
|
|
62
|
+
hideCursor?: boolean;
|
|
63
|
+
dropdown?: any;
|
|
64
|
+
onClick?: string | ((e: React.MouseEvent | null, instance: HtmlElementInstance<MenuItem>) => void);
|
|
65
|
+
onMouseDown?: string | ((e: React.MouseEvent, instance: HtmlElementInstance<MenuItem>) => void);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export class MenuItemInstance extends HtmlElementInstance<MenuItem> {
|
|
69
|
+
declare horizontal?: boolean;
|
|
70
|
+
declare padding?: string;
|
|
71
|
+
declare icons?: boolean;
|
|
72
|
+
declare parentPositionChangeEvent?: any;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export class MenuItem extends HtmlElement<MenuItemConfig, MenuItemInstance> {
|
|
76
|
+
declare public baseClass: string;
|
|
77
|
+
declare public hoverFocusTimeout: number;
|
|
78
|
+
declare public clickToOpen: boolean;
|
|
79
|
+
declare public hoverToOpen: boolean;
|
|
80
|
+
declare public horizontal: boolean;
|
|
81
|
+
declare public arrow: BooleanProp;
|
|
82
|
+
declare public dropdownOptions: Partial<DropdownConfig> | null;
|
|
83
|
+
declare public showCursor: boolean;
|
|
84
|
+
declare public pad: boolean;
|
|
85
|
+
declare public placement: string | null;
|
|
86
|
+
declare public placementOrder: string | null;
|
|
87
|
+
declare public autoClose: boolean;
|
|
88
|
+
declare public checkedIcon: string;
|
|
89
|
+
declare public uncheckedIcon: string;
|
|
90
|
+
declare public keyboardShortcut: KeyboardShortcut | false;
|
|
91
|
+
declare public openOnFocus: boolean;
|
|
92
|
+
declare public hideCursor?: boolean;
|
|
93
|
+
declare public checked?: BooleanProp;
|
|
94
|
+
declare public padding?: string;
|
|
95
|
+
declare public dropdown?: any;
|
|
96
|
+
init() {
|
|
97
|
+
if (this.hideCursor) this.showCursor = false;
|
|
98
|
+
super.init();
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
declareData() {
|
|
102
|
+
super.declareData(...arguments, {
|
|
103
|
+
icon: undefined,
|
|
104
|
+
disabled: undefined,
|
|
105
|
+
checked: false,
|
|
106
|
+
arrow: undefined,
|
|
107
|
+
confirm: undefined,
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
explore(context: RenderingContext, instance: MenuItemInstance) {
|
|
112
|
+
instance.horizontal = this.horizontal;
|
|
113
|
+
let { lastMenu } = context;
|
|
114
|
+
if (lastMenu) {
|
|
115
|
+
instance.horizontal = lastMenu.horizontal;
|
|
116
|
+
instance.padding = lastMenu.itemPadding;
|
|
117
|
+
instance.icons = lastMenu.icons;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
instance.parentPositionChangeEvent = context.parentPositionChangeEvent;
|
|
121
|
+
|
|
122
|
+
if (!instance.padding && this.pad == true) instance.padding = "medium";
|
|
123
|
+
|
|
124
|
+
if (this.padding) instance.padding = this.padding;
|
|
125
|
+
|
|
126
|
+
context.push("lastMenuItem", this);
|
|
127
|
+
super.explore(context, instance);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
exploreCleanup(context: RenderingContext, instance: MenuItemInstance) {
|
|
131
|
+
context.pop("lastMenuItem");
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
render(context: RenderingContext, instance: MenuItemInstance, key: string) {
|
|
135
|
+
return (
|
|
136
|
+
<MenuItemComponent key={key} instance={instance} data={instance.data}>
|
|
137
|
+
{instance.data.text ? <span>{instance.data.text}</span> : this.renderChildren(context, instance)}
|
|
138
|
+
</MenuItemComponent>
|
|
139
|
+
);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
add(element: any) {
|
|
143
|
+
if (element && typeof element == "object" && element.putInto == "dropdown") {
|
|
144
|
+
this.dropdown = { ...element };
|
|
145
|
+
delete this.dropdown.putInto;
|
|
146
|
+
} else super.add(...arguments);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
addText(text: any) {
|
|
150
|
+
this.add({
|
|
151
|
+
type: HtmlElement,
|
|
152
|
+
tag: "span",
|
|
153
|
+
text: text,
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
MenuItem.prototype.baseClass = "menuitem";
|
|
159
|
+
MenuItem.prototype.hoverFocusTimeout = 500;
|
|
160
|
+
MenuItem.prototype.hoverToOpen = false;
|
|
161
|
+
MenuItem.prototype.clickToOpen = false;
|
|
162
|
+
MenuItem.prototype.horizontal = true;
|
|
163
|
+
MenuItem.prototype.arrow = false;
|
|
164
|
+
MenuItem.prototype.dropdownOptions = null;
|
|
165
|
+
MenuItem.prototype.showCursor = true;
|
|
166
|
+
MenuItem.prototype.pad = true;
|
|
167
|
+
MenuItem.prototype.placement = null; //default dropdown placement
|
|
168
|
+
MenuItem.prototype.placementOrder = null; //allowed menu placements
|
|
169
|
+
MenuItem.prototype.autoClose = false;
|
|
170
|
+
MenuItem.prototype.checkedIcon = "check";
|
|
171
|
+
MenuItem.prototype.uncheckedIcon = "dummy";
|
|
172
|
+
MenuItem.prototype.keyboardShortcut = false;
|
|
173
|
+
MenuItem.prototype.openOnFocus = true;
|
|
174
|
+
|
|
175
|
+
Widget.alias("submenu", MenuItem);
|
|
176
|
+
Localization.registerPrototype("cx/widgets/MenuItem", MenuItem);
|
|
177
|
+
|
|
178
|
+
interface MenuItemComponentProps {
|
|
179
|
+
instance: MenuItemInstance;
|
|
180
|
+
data: any;
|
|
181
|
+
children?: any;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
interface MenuItemComponentState {
|
|
185
|
+
dropdownOpen: boolean;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
class MenuItemComponent extends VDOM.Component<MenuItemComponentProps, MenuItemComponentState> {
|
|
189
|
+
declare dropdown?: Widget;
|
|
190
|
+
declare el?: HTMLElement;
|
|
191
|
+
validateDropdownPosition?: () => void;
|
|
192
|
+
unregisterKeyboardShortcut?: () => void;
|
|
193
|
+
declare autoFocusTimerId?: number;
|
|
194
|
+
declare initialScreenPosition?: any;
|
|
195
|
+
offParentPositionChange?: () => void;
|
|
196
|
+
|
|
197
|
+
constructor(props: MenuItemComponentProps) {
|
|
198
|
+
super(props);
|
|
199
|
+
this.state = {
|
|
200
|
+
dropdownOpen: false,
|
|
201
|
+
};
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
getDefaultPlacementOrder(horizontal?: boolean) {
|
|
205
|
+
return horizontal
|
|
206
|
+
? "down-right down down-left up-right up up-left"
|
|
207
|
+
: "right-down right right-up left-down left left-up";
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
getDropdown() {
|
|
211
|
+
let { horizontal, widget, parentPositionChangeEvent } = this.props.instance;
|
|
212
|
+
if (!this.dropdown && widget.dropdown) {
|
|
213
|
+
this.dropdown = Widget.create(Dropdown, {
|
|
214
|
+
matchWidth: false,
|
|
215
|
+
placementOrder: widget.placementOrder || this.getDefaultPlacementOrder(horizontal),
|
|
216
|
+
trackScroll: true,
|
|
217
|
+
inline: true,
|
|
218
|
+
onClick: stopPropagation,
|
|
219
|
+
...widget.dropdownOptions,
|
|
220
|
+
relatedElement: this.el!.parentElement,
|
|
221
|
+
placement: widget.placement,
|
|
222
|
+
onKeyDown: this.onDropdownKeyDown.bind(this),
|
|
223
|
+
onMouseDown: stopPropagation,
|
|
224
|
+
items: widget.dropdown,
|
|
225
|
+
parentPositionChangeEvent,
|
|
226
|
+
pipeValidateDropdownPosition: (cb: any) => {
|
|
227
|
+
this.validateDropdownPosition = cb;
|
|
228
|
+
},
|
|
229
|
+
onDismissAfterScroll: () => {
|
|
230
|
+
this.closeDropdown();
|
|
231
|
+
return false;
|
|
232
|
+
},
|
|
233
|
+
});
|
|
234
|
+
}
|
|
235
|
+
return this.dropdown;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
render() {
|
|
239
|
+
let { instance, data, children } = this.props;
|
|
240
|
+
let { widget } = instance;
|
|
241
|
+
let { CSS, baseClass } = widget;
|
|
242
|
+
let dropdown = this.state.dropdownOpen && (
|
|
243
|
+
<Cx widget={this.getDropdown()} options={{ name: "submenu" }} parentInstance={instance} subscribe />
|
|
244
|
+
);
|
|
245
|
+
|
|
246
|
+
let arrow = data.arrow && <DropdownIcon className={CSS.element(baseClass, "arrow")} />;
|
|
247
|
+
|
|
248
|
+
let icon = null;
|
|
249
|
+
|
|
250
|
+
let checkbox = widget.checked != null;
|
|
251
|
+
|
|
252
|
+
if (checkbox) {
|
|
253
|
+
data.icon = data.checked ? widget.checkedIcon : widget.uncheckedIcon;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
if (data.icon) {
|
|
257
|
+
icon = (
|
|
258
|
+
<div
|
|
259
|
+
className={CSS.element(baseClass, "button")}
|
|
260
|
+
onClick={(e) => {
|
|
261
|
+
e.preventDefault();
|
|
262
|
+
if (!instance.set("checked", !data.checked)) this.onClick(e);
|
|
263
|
+
}}
|
|
264
|
+
onMouseDown={(e) => {
|
|
265
|
+
if (checkbox) e.stopPropagation();
|
|
266
|
+
}}
|
|
267
|
+
>
|
|
268
|
+
{Icon.render(data.icon, { className: CSS.element(baseClass, "icon") })}
|
|
269
|
+
</div>
|
|
270
|
+
);
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
let empty = !children || (Array.isArray(children) && children.length == 0);
|
|
274
|
+
|
|
275
|
+
let classNames = CSS.expand(
|
|
276
|
+
data.classNames,
|
|
277
|
+
CSS.state({
|
|
278
|
+
open: this.state.dropdownOpen,
|
|
279
|
+
horizontal: instance.horizontal,
|
|
280
|
+
vertical: !instance.horizontal,
|
|
281
|
+
arrow: data.arrow,
|
|
282
|
+
cursor: widget.showCursor,
|
|
283
|
+
[instance.padding + "-padding"]: instance.padding,
|
|
284
|
+
icon: !!icon || instance.icons,
|
|
285
|
+
disabled: data.disabled,
|
|
286
|
+
empty,
|
|
287
|
+
}),
|
|
288
|
+
);
|
|
289
|
+
|
|
290
|
+
if (empty) children = <span className={CSS.element(baseClass, "baseline")}> </span>;
|
|
291
|
+
|
|
292
|
+
return (
|
|
293
|
+
<div
|
|
294
|
+
className={classNames}
|
|
295
|
+
style={data.style}
|
|
296
|
+
tabIndex={!data.disabled && (widget.dropdown || widget.onClick || widget.checked) ? 0 : undefined}
|
|
297
|
+
ref={(el: any) => {
|
|
298
|
+
this.el = el;
|
|
299
|
+
}}
|
|
300
|
+
onKeyDown={this.onKeyDown.bind(this)}
|
|
301
|
+
onMouseDown={this.onMouseDown.bind(this)}
|
|
302
|
+
onMouseEnter={this.onMouseEnter.bind(this)}
|
|
303
|
+
onMouseLeave={this.onMouseLeave.bind(this)}
|
|
304
|
+
onFocus={this.onFocus.bind(this)}
|
|
305
|
+
onClick={this.onClick.bind(this)}
|
|
306
|
+
onBlur={this.onBlur.bind(this)}
|
|
307
|
+
>
|
|
308
|
+
{icon}
|
|
309
|
+
{children}
|
|
310
|
+
{arrow}
|
|
311
|
+
{dropdown}
|
|
312
|
+
</div>
|
|
313
|
+
);
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
componentDidUpdate() {
|
|
317
|
+
if (this.state.dropdownOpen && this.validateDropdownPosition) {
|
|
318
|
+
this.validateDropdownPosition();
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
componentDidMount() {
|
|
323
|
+
let { widget } = this.props.instance;
|
|
324
|
+
if (widget.keyboardShortcut)
|
|
325
|
+
this.unregisterKeyboardShortcut = registerKeyboardShortcut(widget.keyboardShortcut, (e: any) => {
|
|
326
|
+
this.el!.focus(); //open the dropdown
|
|
327
|
+
this.onClick(e); //execute the onClick handler
|
|
328
|
+
});
|
|
329
|
+
|
|
330
|
+
tooltipParentDidMount(this.el!, this.props.instance, widget.tooltip);
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
onDropdownKeyDown(e: React.KeyboardEvent) {
|
|
334
|
+
debug(menuFlag, "MenuItem", "dropdownKeyDown");
|
|
335
|
+
let { horizontal } = this.props.instance;
|
|
336
|
+
if (
|
|
337
|
+
e.keyCode == KeyCode.esc ||
|
|
338
|
+
(!isTextInputElement(e.currentTarget) && (horizontal ? e.keyCode == KeyCode.up : e.keyCode == KeyCode.left))
|
|
339
|
+
) {
|
|
340
|
+
FocusManager.focus(this.el!);
|
|
341
|
+
e.preventDefault();
|
|
342
|
+
e.stopPropagation();
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
clearAutoFocusTimer() {
|
|
347
|
+
if (this.autoFocusTimerId) {
|
|
348
|
+
debug(menuFlag, "MenuItem", "autoFocusCancel");
|
|
349
|
+
clearTimeout(this.autoFocusTimerId);
|
|
350
|
+
delete this.autoFocusTimerId;
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
onMouseEnter(e: React.MouseEvent) {
|
|
355
|
+
debug(menuFlag, "MenuItem", "mouseEnter", this.el);
|
|
356
|
+
let { widget } = this.props.instance;
|
|
357
|
+
if (widget.dropdown && !this.state.dropdownOpen) {
|
|
358
|
+
this.clearAutoFocusTimer();
|
|
359
|
+
|
|
360
|
+
if (widget.hoverToOpen) FocusManager.focus(this.el!);
|
|
361
|
+
else if (!widget.clickToOpen) {
|
|
362
|
+
// Automatically open the dropdown only if parent menu is focused
|
|
363
|
+
let commonParentMenu = closest(this.el!, (el) => el.tagName == "UL" && el.contains(getActiveElement()));
|
|
364
|
+
if (commonParentMenu)
|
|
365
|
+
this.autoFocusTimerId = setTimeout(() => {
|
|
366
|
+
delete this.autoFocusTimerId;
|
|
367
|
+
if (!this.state.dropdownOpen) {
|
|
368
|
+
debug(menuFlag, "MenuItem", "hoverFocusTimeout:before", this.el);
|
|
369
|
+
FocusManager.focus(this.el!);
|
|
370
|
+
debug(menuFlag, "MenuItem", "hoverFocusTimeout:after", this.el, getActiveElement());
|
|
371
|
+
}
|
|
372
|
+
}, widget.hoverFocusTimeout) as any;
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
e.stopPropagation();
|
|
376
|
+
e.preventDefault();
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
tooltipMouseMove(e, this.props.instance, widget.tooltip);
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
onMouseLeave(e: React.MouseEvent) {
|
|
383
|
+
let { widget } = this.props.instance;
|
|
384
|
+
if (widget.dropdown) {
|
|
385
|
+
debug(menuFlag, "MenuItem", "mouseLeave", this.el);
|
|
386
|
+
this.clearAutoFocusTimer();
|
|
387
|
+
|
|
388
|
+
if (widget.hoverToOpen && document.activeElement == this.el) unfocusElement(this.el!);
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
tooltipMouseLeave(e, this.props.instance, widget.tooltip);
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
onKeyDown(e: React.KeyboardEvent) {
|
|
395
|
+
debug(menuFlag, "MenuItem", "keyDown", this.el);
|
|
396
|
+
let { horizontal, widget } = this.props.instance;
|
|
397
|
+
if (widget.dropdown) {
|
|
398
|
+
if (
|
|
399
|
+
!this.state.dropdownOpen &&
|
|
400
|
+
e.target == this.el &&
|
|
401
|
+
(e.keyCode == KeyCode.enter || (horizontal ? e.keyCode == KeyCode.down : e.keyCode == KeyCode.right))
|
|
402
|
+
) {
|
|
403
|
+
this.openDropdown(() => {
|
|
404
|
+
let focusableChild = findFirstChild(this.el!, isFocusable);
|
|
405
|
+
if (focusableChild) FocusManager.focus(focusableChild);
|
|
406
|
+
});
|
|
407
|
+
e.preventDefault();
|
|
408
|
+
e.stopPropagation();
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
if (e.keyCode == KeyCode.esc) {
|
|
412
|
+
if (!isFocused(this.el!)) {
|
|
413
|
+
FocusManager.focus(this.el!);
|
|
414
|
+
e.preventDefault();
|
|
415
|
+
e.stopPropagation();
|
|
416
|
+
}
|
|
417
|
+
this.closeDropdown();
|
|
418
|
+
}
|
|
419
|
+
} else {
|
|
420
|
+
if (e.keyCode == KeyCode.enter && widget.onClick) this.onClick(e);
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
onMouseDown(e: React.MouseEvent) {
|
|
425
|
+
let { widget } = this.props.instance;
|
|
426
|
+
if (widget.dropdown) {
|
|
427
|
+
e.stopPropagation();
|
|
428
|
+
if (this.state.dropdownOpen && !widget.hoverToOpen) this.closeDropdown();
|
|
429
|
+
else {
|
|
430
|
+
//IE sometimes does not focus parent on child click
|
|
431
|
+
if (!isFocusedDeep(this.el!)) FocusManager.focus(this.el!);
|
|
432
|
+
this.openDropdown();
|
|
433
|
+
|
|
434
|
+
//If one of the elements is auto focused prevent stealing focus
|
|
435
|
+
if (isFocusedDeep(this.el!)) e.preventDefault();
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
openDropdown(callback?: any) {
|
|
441
|
+
let { widget } = this.props.instance;
|
|
442
|
+
if (widget.dropdown) {
|
|
443
|
+
if (!this.state.dropdownOpen) {
|
|
444
|
+
this.setState(
|
|
445
|
+
{
|
|
446
|
+
dropdownOpen: true,
|
|
447
|
+
},
|
|
448
|
+
callback,
|
|
449
|
+
);
|
|
450
|
+
|
|
451
|
+
//hide tooltip if dropdown is open
|
|
452
|
+
tooltipMouseLeave(null as any, this.props.instance, widget.tooltip);
|
|
453
|
+
} else if (callback) callback(this.state);
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
onClick(e: any) {
|
|
458
|
+
e.stopPropagation();
|
|
459
|
+
|
|
460
|
+
let { instance } = this.props;
|
|
461
|
+
let { data } = instance;
|
|
462
|
+
if (data.disabled) {
|
|
463
|
+
e.preventDefault();
|
|
464
|
+
return;
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
let { widget } = instance;
|
|
468
|
+
if (widget.dropdown) e.preventDefault();
|
|
469
|
+
//prevent navigation
|
|
470
|
+
else {
|
|
471
|
+
instance.set("checked", !instance.data.checked);
|
|
472
|
+
|
|
473
|
+
if (widget.onClick) {
|
|
474
|
+
if (data.confirm) {
|
|
475
|
+
yesNo(data.confirm).then((btn) => {
|
|
476
|
+
if (btn == "yes") instance.invoke("onClick", null, instance);
|
|
477
|
+
});
|
|
478
|
+
} else instance.invoke("onClick", e, instance);
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
if (widget.autoClose) unfocusElement(this.el, true);
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
onFocus() {
|
|
486
|
+
let { widget } = this.props.instance;
|
|
487
|
+
if (widget.dropdown) {
|
|
488
|
+
oneFocusOut(this, this.el!, this.onFocusOut.bind(this));
|
|
489
|
+
debug(menuFlag, "MenuItem", "focus", this.el, document.activeElement);
|
|
490
|
+
this.clearAutoFocusTimer();
|
|
491
|
+
if (widget.openOnFocus) this.openDropdown();
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
onBlur() {
|
|
496
|
+
FocusManager.nudge();
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
closeDropdown() {
|
|
500
|
+
this.setState({
|
|
501
|
+
dropdownOpen: false,
|
|
502
|
+
});
|
|
503
|
+
delete this.initialScreenPosition;
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
onFocusOut(focusedElement: any) {
|
|
507
|
+
debug(menuFlag, "MenuItem", "focusout", this.el, focusedElement);
|
|
508
|
+
this.clearAutoFocusTimer();
|
|
509
|
+
if (this.el && !isSelfOrDescendant(this.el, focusedElement)) {
|
|
510
|
+
debug(menuFlag, "MenuItem", "closing dropdown", this.el, focusedElement);
|
|
511
|
+
this.closeDropdown();
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
componentWillUnmount() {
|
|
516
|
+
this.clearAutoFocusTimer();
|
|
517
|
+
offFocusOut(this);
|
|
518
|
+
|
|
519
|
+
if (this.offParentPositionChange) this.offParentPositionChange();
|
|
520
|
+
|
|
521
|
+
if (this.unregisterKeyboardShortcut) this.unregisterKeyboardShortcut();
|
|
522
|
+
|
|
523
|
+
tooltipParentWillUnmount(this.props.instance);
|
|
524
|
+
}
|
|
525
|
+
}
|