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.
Files changed (38) hide show
  1. package/build/jsx-dev-runtime.d.ts +1 -0
  2. package/build/jsx-dev-runtime.d.ts.map +1 -1
  3. package/build/jsx-dev-runtime.js +1 -0
  4. package/dist/manifest.js +889 -889
  5. package/package.json +1 -1
  6. package/src/charts/BarGraph.scss +31 -31
  7. package/src/charts/Legend.scss +57 -57
  8. package/src/charts/LegendEntry.scss +35 -35
  9. package/src/charts/LineGraph.scss +28 -28
  10. package/src/charts/helpers/SnapPointFinder.ts +136 -136
  11. package/src/charts/helpers/ValueAtFinder.ts +72 -72
  12. package/src/data/createAccessorModelProxy.ts +66 -66
  13. package/src/jsx-dev-runtime.ts +1 -0
  14. package/src/ui/DataProxy.ts +55 -55
  15. package/src/ui/Repeater.spec.tsx +181 -181
  16. package/src/ui/Rescope.ts +50 -50
  17. package/src/ui/adapter/ArrayAdapter.ts +229 -229
  18. package/src/ui/exprHelpers.ts +96 -96
  19. package/src/util/scss/include.scss +69 -69
  20. package/src/widgets/Button.maps.scss +103 -103
  21. package/src/widgets/Sandbox.ts +104 -104
  22. package/src/widgets/form/Calendar.tsx +772 -772
  23. package/src/widgets/form/ColorField.scss +112 -112
  24. package/src/widgets/form/DateTimeField.scss +111 -111
  25. package/src/widgets/form/LookupField.maps.scss +26 -26
  26. package/src/widgets/form/LookupField.scss +227 -227
  27. package/src/widgets/form/MonthField.scss +113 -113
  28. package/src/widgets/form/NumberField.scss +72 -72
  29. package/src/widgets/form/Select.scss +104 -104
  30. package/src/widgets/form/TextField.scss +66 -66
  31. package/src/widgets/grid/Grid.scss +657 -657
  32. package/src/widgets/grid/variables.scss +47 -47
  33. package/src/widgets/index.ts +63 -63
  34. package/src/widgets/nav/MenuItem.scss +150 -150
  35. package/src/widgets/nav/MenuItem.tsx +525 -525
  36. package/src/widgets/nav/Tab.ts +122 -122
  37. package/src/widgets/overlay/Overlay.tsx +1029 -1029
  38. 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")}>&nbsp;</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")}>&nbsp;</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
+ }