cx 21.12.4 → 22.1.2

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.
@@ -25,108 +25,108 @@ let pending = false;
25
25
 
26
26
  export class FocusManager {
27
27
  static subscribe(callback) {
28
- let unsubscribe = subscribers.subscribe(callback);
29
- checkTimer();
30
- return unsubscribe;
28
+ let unsubscribe = subscribers.subscribe(callback);
29
+ checkTimer();
30
+ return unsubscribe;
31
31
  }
32
32
 
33
33
  static onFocusOut(el, callback) {
34
- let active = isSelfOrDescendant(el, getActiveElement());
35
- return this.subscribe((focusedEl) => {
36
- if (!active) active = isSelfOrDescendant(el, getActiveElement());
37
- else if (!isSelfOrDescendant(el, focusedEl)) {
38
- active = false;
39
- callback(focusedEl);
40
- }
41
- });
34
+ let active = isSelfOrDescendant(el, getActiveElement());
35
+ return this.subscribe((focusedEl) => {
36
+ if (!active) active = isSelfOrDescendant(el, getActiveElement());
37
+ else if (!isSelfOrDescendant(el, focusedEl)) {
38
+ active = false;
39
+ callback(focusedEl);
40
+ }
41
+ });
42
42
  }
43
43
 
44
44
  static oneFocusOut(el, callback) {
45
- this.nudge();
46
- let off = this.subscribe((focusedEl) => {
47
- if (!isSelfOrDescendant(el, focusedEl)) {
48
- callback(focusedEl);
49
- off();
50
- }
51
- });
52
- return off;
45
+ this.nudge();
46
+ let off = this.subscribe((focusedEl) => {
47
+ if (!isSelfOrDescendant(el, focusedEl)) {
48
+ callback(focusedEl);
49
+ off();
50
+ }
51
+ });
52
+ return off;
53
53
  }
54
54
 
55
55
  static nudge() {
56
- if (
57
- typeof document !== "undefined" &&
58
- getActiveElement() !== lastActiveElement
59
- ) {
60
- if (!pending) {
61
- pending = true;
62
- setTimeout(function () {
63
- pending = false;
64
- if (getActiveElement() !== lastActiveElement) {
65
- lastActiveElement = getActiveElement();
66
- batchUpdates(() => {
67
- subscribers.notify(lastActiveElement);
68
- });
69
- checkTimer();
70
- }
71
- }, 0);
72
- }
73
- }
56
+ if (
57
+ typeof document !== "undefined" &&
58
+ getActiveElement() !== lastActiveElement
59
+ ) {
60
+ if (!pending) {
61
+ pending = true;
62
+ setTimeout(function () {
63
+ pending = false;
64
+ if (getActiveElement() !== lastActiveElement) {
65
+ lastActiveElement = getActiveElement();
66
+ batchUpdates(() => {
67
+ subscribers.notify(lastActiveElement);
68
+ });
69
+ checkTimer();
70
+ }
71
+ }, 0);
72
+ }
73
+ }
74
74
  }
75
75
 
76
76
  static focus(el) {
77
- el.focus();
78
- this.nudge();
77
+ el.focus();
78
+ this.nudge();
79
79
  }
80
80
 
81
81
  static focusFirst(el) {
82
- let focusable = findFirst(el, isFocusable);
83
- if (focusable) this.focus(focusable);
84
- return focusable;
82
+ let focusable = findFirst(el, isFocusable);
83
+ if (focusable) this.focus(focusable);
84
+ return focusable;
85
85
  }
86
86
 
87
87
  static focusFirstChild(el) {
88
- let focusable = findFirstChild(el, isFocusable);
89
- if (focusable) this.focus(focusable);
90
- return focusable;
88
+ let focusable = findFirstChild(el, isFocusable);
89
+ if (focusable) this.focus(focusable);
90
+ return focusable;
91
91
  }
92
92
 
93
93
  static focusNext(el) {
94
- let next = el,
95
- skip = true;
96
- do {
97
- if (!skip) {
98
- let focusable = this.focusFirst(next);
99
- if (focusable) return focusable;
100
- }
101
-
102
- if (next.nextSibling) {
103
- next = next.nextSibling;
104
- skip = false;
105
- } else {
106
- next = next.parentNode;
107
- skip = true;
108
- }
109
- } while (next);
94
+ let next = el,
95
+ skip = true;
96
+ do {
97
+ if (!skip) {
98
+ let focusable = this.focusFirst(next);
99
+ if (focusable) return focusable;
100
+ }
101
+
102
+ if (next.nextSibling) {
103
+ next = next.nextSibling;
104
+ skip = false;
105
+ } else {
106
+ next = next.parentNode;
107
+ skip = true;
108
+ }
109
+ } while (next);
110
110
  }
111
111
 
112
112
  static setInterval(interval) {
113
- timerInterval = interval;
114
- checkTimer();
113
+ timerInterval = interval;
114
+ checkTimer();
115
115
  }
116
116
  }
117
117
 
118
118
  export function oneFocusOut(component, el, callback) {
119
119
  if (!component.oneFocusOut)
120
- component.oneFocusOut = FocusManager.oneFocusOut(el, (focus) => {
121
- delete component.oneFocusOut;
122
- callback(focus);
123
- });
120
+ component.oneFocusOut = FocusManager.oneFocusOut(el, (focus) => {
121
+ delete component.oneFocusOut;
122
+ callback(focus);
123
+ });
124
124
  }
125
125
 
126
126
  export function offFocusOut(component) {
127
127
  if (component.oneFocusOut) {
128
- component.oneFocusOut();
129
- delete component.oneFocusOut;
128
+ component.oneFocusOut();
129
+ delete component.oneFocusOut;
130
130
  }
131
131
  }
132
132
 
@@ -137,34 +137,40 @@ export function preventFocus(e) {
137
137
 
138
138
  e.preventDefault();
139
139
 
140
- //unfocus activeElement
141
- const activeElement = getActiveElement();
142
- if (e.currentTarget !== activeElement) {
143
- //find the closest focusable parent of the clicked element and focus it instead
144
- let focusableParent =
145
- closestParent(e.currentTarget, (el) => isFocusable(el)) ||
146
- document.body;
147
- if (focusableParent === document.body) activeElement.blur();
148
- else focusableParent.focus();
149
-
150
- FocusManager.nudge();
151
- }
140
+ unfocusElement(e.currentTarget, false);
152
141
  }
153
142
 
154
143
  function checkTimer() {
155
144
  let shouldRun = !subscribers.isEmpty();
156
145
 
157
146
  if (shouldRun && !timerId)
158
- timerId = setInterval(() => {
159
- FocusManager.nudge();
160
- }, timerInterval);
147
+ timerId = setInterval(() => {
148
+ FocusManager.nudge();
149
+ }, timerInterval);
161
150
 
162
151
  if (!shouldRun && timerId) {
163
- clearInterval(timerId);
164
- timerId = null;
152
+ clearInterval(timerId);
153
+ timerId = null;
165
154
  }
166
155
  }
167
156
 
168
157
  export function preventFocusOnTouch(e, force = false) {
169
158
  if (force || isTouchEvent()) preventFocus(e);
170
159
  }
160
+
161
+ export function unfocusElement(target = null, forceBlur = true) {
162
+ const activeElement = getActiveElement();
163
+
164
+ if (!target) target = activeElement;
165
+ else if (target != activeElement && !target.contains(activeElement))
166
+ return;
167
+
168
+ //find the closest focusable parent of the target element and focus it instead
169
+ let focusableParent = !forceBlur &&
170
+ closestParent(target, (el) => isFocusable(el)) ||
171
+ document.body;
172
+ if (focusableParent === document.body) activeElement.blur();
173
+ else focusableParent.focus();
174
+
175
+ FocusManager.nudge();
176
+ }
@@ -67,6 +67,9 @@ export interface TextFieldProps extends FieldProps {
67
67
 
68
68
  /** If trackFocus is set, this value will be set when the field recieves or loses focus. */
69
69
  focused?: Cx.BooleanProp;
70
+
71
+ /** Trim text to remove whitespace. Default is false. */
72
+ trim?: Cx.BooleanProp;
70
73
  }
71
74
 
72
75
  export class TextField extends Cx.Widget<TextFieldProps> {}
@@ -14,6 +14,7 @@ import { KeyCode } from "../../util/KeyCode";
14
14
  import { Localization } from "../../ui/Localization";
15
15
  import ClearIcon from "../icons/clear";
16
16
  import { autoFocus } from "../autoFocus";
17
+ import { isString } from "../../util/isString";
17
18
 
18
19
  export class TextField extends Field {
19
20
  init() {
@@ -36,6 +37,7 @@ export class TextField extends Field {
36
37
  minLength: undefined,
37
38
  maxLength: undefined,
38
39
  icon: undefined,
40
+ trim: undefined
39
41
  },
40
42
  ...arguments
41
43
  );
@@ -80,6 +82,7 @@ TextField.prototype.icon = null;
80
82
  TextField.prototype.showClear = false;
81
83
  TextField.prototype.alwaysShowClear = false;
82
84
  TextField.prototype.keyboardShortcut = false;
85
+ TextField.prototype.trim = false;
83
86
 
84
87
  Localization.registerPrototype("cx/widgets/TextField", TextField);
85
88
 
@@ -125,7 +128,7 @@ class Input extends VDOM.Component {
125
128
  );
126
129
  }
127
130
 
128
- let empty = this.input ? !this.input.value : data.empty;
131
+ let empty = this.input ? !this.trimmed(this.input.value) : data.empty;
129
132
 
130
133
  return (
131
134
  <div
@@ -258,7 +261,7 @@ class Input extends VDOM.Component {
258
261
  let { widget } = instance;
259
262
 
260
263
  if (widget.reactOn.indexOf(change) != -1) {
261
- let text = e.target.value;
264
+ let text = this.trimmed(e.target.value);
262
265
  if (data.maxLength != null && text.length > data.maxLength) {
263
266
  text = text.substring(0, data.maxLength);
264
267
  this.input.value = text;
@@ -266,12 +269,18 @@ class Input extends VDOM.Component {
266
269
 
267
270
  let value = text || widget.emptyValue;
268
271
  if (!instance.set("value", value, { immediate })) {
269
- if (text != this.input.value) this.input.value = text;
272
+ if (text != this.trimmed(this.input.value)) this.input.value = text;
270
273
  } else {
271
274
  if (value) instance.setState({ visited: true });
272
275
  }
273
276
  }
274
277
  }
278
+
279
+ trimmed(value) {
280
+ if (this.props.data.trim && isString(value))
281
+ return value.trim();
282
+ return value;
283
+ }
275
284
  }
276
285
 
277
286
  Widget.alias("textfield", TextField);
@@ -305,6 +305,9 @@ interface GridProps extends Cx.StyledContainerProps {
305
305
  /** Set to true to enable cell editing. Please note that all editable columns should specify the editor field. */
306
306
  cellEditable?: boolean;
307
307
 
308
+ /** A callback function which is executed before a cell editor is initalized. Return false from the callback to prevent the cell from going into the edit mode. */
309
+ onBeforeCellEdit?: (change, record) => any;
310
+
308
311
  /** A callback function which is executed after a cell has been successfully edited. */
309
312
  onCellEdited?: (change, record) => void;
310
313
 
@@ -39,6 +39,7 @@ import { GridCellEditor } from "./GridCellEditor";
39
39
  import { batchUpdates } from "../../ui/batchUpdates";
40
40
  import { parseStyle } from "cx/src/util";
41
41
  import { StaticText } from "../../ui/StaticText";
42
+ import { unfocusElement } from "../../ui/FocusManager";
42
43
 
43
44
  export class Grid extends Widget {
44
45
  declareData(...args) {
@@ -161,16 +162,16 @@ export class Grid extends Widget {
161
162
  value: isDefined(c.aggregateValue)
162
163
  ? c.aggregateValue
163
164
  : isDefined(c.value)
164
- ? c.value
165
- : c.aggregateField
166
- ? { bind: this.recordName + "." + c.aggregateField }
167
- : null,
165
+ ? c.value
166
+ : c.aggregateField
167
+ ? { bind: this.recordName + "." + c.aggregateField }
168
+ : null,
168
169
  weight:
169
170
  c.weight != null
170
171
  ? c.weight
171
172
  : c.weightField && {
172
- bind: this.recordName + "." + c.weightField,
173
- },
173
+ bind: this.recordName + "." + c.weightField,
174
+ },
174
175
  type: c.aggregate,
175
176
  };
176
177
  }
@@ -593,8 +594,9 @@ export class Grid extends Widget {
593
594
  let initialPosition = getCursorPos(e);
594
595
  resizeOverlayEl.className = CSS.element(baseClass, "resize-overlay");
595
596
  resizeOverlayEl.style.width = `${initialWidth}px`;
596
- resizeOverlayEl.style.left = `${headerCell.getBoundingClientRect().left - gridEl.getBoundingClientRect().left
597
- }px`;
597
+ resizeOverlayEl.style.left = `${
598
+ headerCell.getBoundingClientRect().left - gridEl.getBoundingClientRect().left
599
+ }px`;
598
600
  gridEl.appendChild(resizeOverlayEl);
599
601
  captureMouse2(e, {
600
602
  onMouseMove: (e) => {
@@ -742,7 +744,7 @@ export class Grid extends Widget {
742
744
  widget: () => <div className={CSS.element(baseClass, "col-header-drag-clone")}>{data.text}</div>,
743
745
  },
744
746
  },
745
- () => { }
747
+ () => {}
746
748
  );
747
749
  }
748
750
  }
@@ -768,14 +770,14 @@ export class Grid extends Widget {
768
770
 
769
771
  let sorters = direction
770
772
  ? [
771
- {
772
- field,
773
- direction,
774
- value,
775
- comparer,
776
- sortOptions,
777
- },
778
- ]
773
+ {
774
+ field,
775
+ direction,
776
+ value,
777
+ comparer,
778
+ sortOptions,
779
+ },
780
+ ]
779
781
  : null;
780
782
 
781
783
  instance.set("sorters", sorters);
@@ -1017,7 +1019,6 @@ export class Grid extends Widget {
1017
1019
  )
1018
1020
  );
1019
1021
 
1020
-
1021
1022
  if (g.showHeader) {
1022
1023
  record.vdom.push(this.renderHeader(context, instance, record.key + "-header", false, false));
1023
1024
  if (hasFixedColumns)
@@ -1075,7 +1076,7 @@ export class Grid extends Widget {
1075
1076
  instance,
1076
1077
  record.grouping,
1077
1078
  record.level,
1078
- record.group || { "$key": "fixed-footer" },
1079
+ record.group || { $key: "fixed-footer" },
1079
1080
  record.key + "-footer",
1080
1081
  record.store,
1081
1082
  true,
@@ -1088,7 +1089,7 @@ export class Grid extends Widget {
1088
1089
  instance,
1089
1090
  record.grouping,
1090
1091
  record.level,
1091
- record.group || { "$key": "fixed-footer" },
1092
+ record.group || { $key: "fixed-footer" },
1092
1093
  record.key + "-footer",
1093
1094
  record.store,
1094
1095
  true,
@@ -1214,8 +1215,8 @@ class GridComponent extends VDOM.Component {
1214
1215
  style={
1215
1216
  this.rowHeight > 0
1216
1217
  ? {
1217
- height: this.rowHeight + 1,
1218
- }
1218
+ height: this.rowHeight + 1,
1219
+ }
1219
1220
  : null
1220
1221
  }
1221
1222
  >
@@ -1483,8 +1484,6 @@ class GridComponent extends VDOM.Component {
1483
1484
  true
1484
1485
  )
1485
1486
  );
1486
-
1487
-
1488
1487
  }
1489
1488
  }
1490
1489
  }
@@ -1914,8 +1913,7 @@ class GridComponent extends VDOM.Component {
1914
1913
  };
1915
1914
  instance.invoke("onColumnDrop", e, instance);
1916
1915
  }
1917
- }
1918
- catch (err) {
1916
+ } catch (err) {
1919
1917
  console.error("Grid drop operation failed. Please fix this error:", err);
1920
1918
  }
1921
1919
 
@@ -2462,7 +2460,7 @@ class GridComponent extends VDOM.Component {
2462
2460
  if (!futureState.cellEdit && wasCellEditing) {
2463
2461
  //If cell editing is in progress, moving the cursor may cause that the cell editor is unmounted before
2464
2462
  //the blur event which may cause data loss for components which do not have reactOn=change set, e.g. NumberField.
2465
- getActiveElement().blur();
2463
+ unfocusElement();
2466
2464
  let record = this.getRecordAt(prevState.cursor);
2467
2465
  if ((!this.cellEditorValid || cancelEdit) && this.cellEditUndoData)
2468
2466
  record.store.set(widget.recordName, this.cellEditUndoData);
@@ -2482,8 +2480,26 @@ class GridComponent extends VDOM.Component {
2482
2480
  }
2483
2481
  }
2484
2482
 
2485
- if (futureState.cellEdit && !wasCellEditing)
2486
- this.cellEditUndoData = this.getRecordAt(futureState.cursor).data;
2483
+ if (futureState.cellEdit && !wasCellEditing) {
2484
+ let record = this.getRecordAt(futureState.cursor);
2485
+ let cellEditUndoData = record.data;
2486
+
2487
+ if (
2488
+ widget.onBeforeCellEdit &&
2489
+ this.props.instance.invoke(
2490
+ "onBeforeCellEdit",
2491
+ {
2492
+ column: visibleColumns[futureState.cursorCellIndex],
2493
+ data: cellEditUndoData,
2494
+ field: visibleColumns[futureState.cursorCellIndex].field,
2495
+ },
2496
+ record
2497
+ ) === false
2498
+ )
2499
+ return;
2500
+
2501
+ this.cellEditUndoData = cellEditUndoData;
2502
+ }
2487
2503
 
2488
2504
  this.setState(newState, () => {
2489
2505
  if (this.state.focused && !this.state.cellEdit && wasCellEditing) FocusManager.focus(this.dom.el);
@@ -2500,7 +2516,7 @@ class GridComponent extends VDOM.Component {
2500
2516
  hscroll = true;
2501
2517
  item =
2502
2518
  item.firstChild.children[
2503
- this.state.cursorCellIndex - this.props.instance.fixedColumnCount
2519
+ this.state.cursorCellIndex - this.props.instance.fixedColumnCount
2504
2520
  ];
2505
2521
  } else {
2506
2522
  let fixedItem = this.dom.fixedTable.querySelector(`tbody[data-record-key="${record.key}"]`);
@@ -3154,7 +3170,6 @@ class AvgHeight {
3154
3170
  }
3155
3171
  }
3156
3172
 
3157
-
3158
3173
  function getDragDropEvent(ev) {
3159
3174
  return {
3160
3175
  event: ev,
@@ -3163,7 +3178,7 @@ function getDragDropEvent(ev) {
3163
3178
  source: {
3164
3179
  width: 32,
3165
3180
  height: 32,
3166
- margin: []
3167
- }
3181
+ margin: [],
3182
+ },
3168
3183
  };
3169
- }
3184
+ }
@@ -1,12 +1,11 @@
1
- import { ValidationGroup } from "../../widgets/form/ValidationGroup";
1
+ import { preventFocusOnTouch, unfocusElement } from "../../ui/FocusManager";
2
2
  import { VDOM } from "../../ui/Widget";
3
- import { ddMouseDown, ddMouseUp, ddDetect, isDragHandleEvent } from "../drag-drop/ops";
3
+ import { closest } from "../../util/DOM";
4
4
  import { isTouchEvent } from "../../util/isTouchEvent";
5
- import { preventFocusOnTouch } from "../../ui/FocusManager";
6
- import { GridRowLine } from "./GridRowLine";
7
- import { closest, isFocusedDeep } from "../../util/DOM";
8
5
  import { KeyCode } from "../../util/KeyCode";
9
- import { getActiveElement } from "../../util/getActiveElement";
6
+ import { ValidationGroup } from "../../widgets/form/ValidationGroup";
7
+ import { ddDetect, ddMouseDown, ddMouseUp, isDragHandleEvent } from "../drag-drop/ops";
8
+ import { GridRowLine } from "./GridRowLine";
10
9
 
11
10
  export class GridRow extends ValidationGroup {
12
11
  declareData(...args) {
@@ -120,7 +119,7 @@ export class GridRowComponent extends VDOM.Component {
120
119
  e.stopPropagation();
121
120
 
122
121
  //close context menu
123
- if (!getActiveElement().contains(e.target)) document.activeElement.blur();
122
+ unfocusElement(e.target);
124
123
  }
125
124
  }
126
125
 
@@ -19,6 +19,7 @@ import {
19
19
  } from "../overlay/tooltip-ops";
20
20
  import { yesNo } from "../overlay/alerts";
21
21
  import { isTextInputElement } from "../../util";
22
+ import { unfocusElement } from "../../ui/FocusManager";
22
23
 
23
24
  /*
24
25
  Functionality:
@@ -309,7 +310,7 @@ class MenuItemComponent extends VDOM.Component {
309
310
  debug(menuFlag, "MenuItem", "mouseLeave", this.el);
310
311
  this.clearAutoFocusTimer();
311
312
 
312
- if (widget.hoverToOpen && document.activeElement == this.el) this.el.blur();
313
+ if (widget.hoverToOpen && document.activeElement == this.el) unfocusElement(this.el);
313
314
  }
314
315
 
315
316
  tooltipMouseLeave(e, this.props.instance, widget.tooltip);
@@ -402,7 +403,7 @@ class MenuItemComponent extends VDOM.Component {
402
403
  }
403
404
  }
404
405
 
405
- if (widget.autoClose) getActiveElement().blur();
406
+ if (widget.autoClose) unfocusElement(this.el);
406
407
  }
407
408
 
408
409
  onFocus() {