cx 25.6.2 → 25.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/charts.css +250 -250
- package/dist/charts.js +109 -109
- package/dist/data.js +19 -19
- package/dist/hooks.js +1 -1
- package/dist/manifest.js +776 -776
- package/dist/svg.js +25 -25
- package/dist/ui.js +84 -84
- package/dist/util.js +6 -9
- package/dist/widgets.css +11 -6
- package/dist/widgets.js +591 -543
- package/package.json +1 -1
- package/src/charts/Marker.d.ts +1 -1
- package/src/charts/MarkerLine.d.ts +25 -27
- package/src/data/ExposedValueView.d.ts +2 -2
- package/src/util/isFunction.d.ts +1 -1
- package/src/widgets/Sandbox.d.ts +3 -1
- package/src/widgets/Sandbox.js +2 -0
- package/src/widgets/form/MonthPicker.d.ts +8 -0
- package/src/widgets/form/MonthPicker.js +65 -23
- package/src/widgets/form/MonthPicker.scss +4 -0
- package/src/widgets/grid/Grid.js +28 -23
package/package.json
CHANGED
package/src/charts/Marker.d.ts
CHANGED
|
@@ -3,7 +3,7 @@ import { BoundedObject, BoundedObjectProps } from "../svg/BoundedObject";
|
|
|
3
3
|
|
|
4
4
|
interface MarkerProps extends BoundedObjectProps {
|
|
5
5
|
/** The `x` value binding or expression. */
|
|
6
|
-
x?: Cx.Prop<string | number>;
|
|
6
|
+
x?: Cx.Prop<string | number | Date>;
|
|
7
7
|
|
|
8
8
|
/** The `y` value binding or expression. */
|
|
9
9
|
y?: Cx.Prop<string | number>;
|
|
@@ -1,58 +1,56 @@
|
|
|
1
|
-
import * as Cx from
|
|
2
|
-
import { BoundedObject, BoundedObjectProps } from
|
|
1
|
+
import * as Cx from "../core";
|
|
2
|
+
import { BoundedObject, BoundedObjectProps } from "../svg/BoundedObject";
|
|
3
3
|
|
|
4
4
|
interface MarkerLineProps extends BoundedObjectProps {
|
|
5
|
-
|
|
6
5
|
/** The `x1` value binding or expression. */
|
|
7
|
-
x1?: Cx.
|
|
6
|
+
x1?: Cx.Prop<number | string | Date>;
|
|
8
7
|
|
|
9
8
|
/** The `y1` value binding or expression. */
|
|
10
|
-
y1?: Cx.NumberProp
|
|
9
|
+
y1?: Cx.NumberProp;
|
|
11
10
|
|
|
12
11
|
/** The `x2` value binding or expression. */
|
|
13
|
-
x2?: Cx.
|
|
12
|
+
x2?: Cx.Prop<number | string | Date>;
|
|
14
13
|
|
|
15
14
|
/** The `y2` value binding or expression. */
|
|
16
|
-
y2?: Cx.NumberProp
|
|
15
|
+
y2?: Cx.NumberProp;
|
|
17
16
|
|
|
18
17
|
/** Used to indicate if the data should affect axis span. */
|
|
19
|
-
affectsAxes?: Cx.BooleanProp
|
|
18
|
+
affectsAxes?: Cx.BooleanProp;
|
|
20
19
|
|
|
21
20
|
/** Index of a color from the standard palette of colors. 0-15. */
|
|
22
|
-
colorIndex?: Cx.NumberProp
|
|
21
|
+
colorIndex?: Cx.NumberProp;
|
|
23
22
|
|
|
24
23
|
/** Used to indicate if an item is active or not. Inactive items are shown only in the legend. */
|
|
25
|
-
active?: Cx.BooleanProp
|
|
24
|
+
active?: Cx.BooleanProp;
|
|
26
25
|
|
|
27
26
|
/** Name of the item as it will appear in the legend. */
|
|
28
|
-
name?: Cx.StringProp
|
|
27
|
+
name?: Cx.StringProp;
|
|
29
28
|
|
|
30
29
|
/** Name of the legend to be used. Default is `legend`. */
|
|
31
|
-
legend?: Cx.StringProp
|
|
30
|
+
legend?: Cx.StringProp;
|
|
32
31
|
|
|
33
32
|
/** Shared `x1` and `x2` value binding or expression. */
|
|
34
|
-
x?: Cx.
|
|
33
|
+
x?: Cx.Prop<number | string | Date>;
|
|
35
34
|
|
|
36
35
|
/** Shared `y1` and `y2` value binding or expression. */
|
|
37
|
-
y?: Cx.NumberProp
|
|
36
|
+
y?: Cx.NumberProp;
|
|
38
37
|
|
|
39
|
-
/**
|
|
40
|
-
* Name of the horizontal axis. The value should match one of the horizontal axes set
|
|
41
|
-
* in the `axes` configuration of the parent `Chart` component. Default value is `x`.
|
|
38
|
+
/**
|
|
39
|
+
* Name of the horizontal axis. The value should match one of the horizontal axes set
|
|
40
|
+
* in the `axes` configuration of the parent `Chart` component. Default value is `x`.
|
|
42
41
|
*/
|
|
43
|
-
xAxis?: string
|
|
44
|
-
|
|
45
|
-
/**
|
|
42
|
+
xAxis?: string;
|
|
43
|
+
|
|
44
|
+
/**
|
|
46
45
|
* Name of the vertical axis. The value should match one of the vertical axes set
|
|
47
|
-
* in the axes configuration if the parent Chart component. Default value is y.
|
|
46
|
+
* in the axes configuration if the parent Chart component. Default value is y.
|
|
48
47
|
*/
|
|
49
|
-
yAxis?: string
|
|
50
|
-
|
|
51
|
-
/** Base CSS class to be applied to the element. Defaults to `markerline`. */
|
|
52
|
-
baseClass?: string,
|
|
48
|
+
yAxis?: string;
|
|
53
49
|
|
|
54
|
-
|
|
50
|
+
/** Base CSS class to be applied to the element. Defaults to `markerline`. */
|
|
51
|
+
baseClass?: string;
|
|
55
52
|
|
|
53
|
+
legendAction?: string;
|
|
56
54
|
}
|
|
57
55
|
|
|
58
|
-
export class MarkerLine extends Cx.Widget<MarkerLineProps> {}
|
|
56
|
+
export class MarkerLine extends Cx.Widget<MarkerLineProps> {}
|
package/src/util/isFunction.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export function isFunction(f: any): f is
|
|
1
|
+
export function isFunction(f: any): f is Function;
|
package/src/widgets/Sandbox.d.ts
CHANGED
|
@@ -10,7 +10,9 @@ interface SandboxProps extends Cx.PureContainerProps {
|
|
|
10
10
|
|
|
11
11
|
recordName?: Cx.RecordAlias;
|
|
12
12
|
recordAlias?: Cx.RecordAlias;
|
|
13
|
-
|
|
13
|
+
|
|
14
|
+
immutable?: boolean;
|
|
15
|
+
sealed?: boolean;
|
|
14
16
|
}
|
|
15
17
|
|
|
16
18
|
export class Sandbox extends Cx.Widget<SandboxProps> {}
|
package/src/widgets/Sandbox.js
CHANGED
|
@@ -50,6 +50,7 @@ export class Sandbox extends PureContainer {
|
|
|
50
50
|
key: data.key,
|
|
51
51
|
recordName: this.recordName,
|
|
52
52
|
immutable: this.immutable,
|
|
53
|
+
sealed: this.sealed,
|
|
53
54
|
});
|
|
54
55
|
instance.clearChildrenCache();
|
|
55
56
|
}
|
|
@@ -59,5 +60,6 @@ export class Sandbox extends PureContainer {
|
|
|
59
60
|
|
|
60
61
|
Sandbox.prototype.recordName = "$page";
|
|
61
62
|
Sandbox.prototype.immutable = false;
|
|
63
|
+
Sandbox.prototype.sealed = false;
|
|
62
64
|
|
|
63
65
|
Widget.alias("sandbox", Sandbox);
|
|
@@ -84,6 +84,14 @@ interface MonthPickerProps extends FieldProps {
|
|
|
84
84
|
* When true, the quarters section will not render.
|
|
85
85
|
*/
|
|
86
86
|
hideQuarters?: boolean;
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Callback to create a function that determines if a date is selectable.
|
|
90
|
+
* Return `false` on factory method to disable specific month, quarter or a whole year.
|
|
91
|
+
*
|
|
92
|
+
* Note: Use the `onValidate` callback for validation purposes.
|
|
93
|
+
*/
|
|
94
|
+
onCreateIsMonthDateSelectable?: (validationParams: Cx.Config, instance: Instance) => (monthDate: Date) => boolean;
|
|
87
95
|
}
|
|
88
96
|
|
|
89
97
|
export class MonthPicker extends Cx.Widget<MonthPickerProps> {}
|
|
@@ -68,7 +68,8 @@ export class MonthPicker extends Field {
|
|
|
68
68
|
super.init();
|
|
69
69
|
}
|
|
70
70
|
|
|
71
|
-
prepareData(context,
|
|
71
|
+
prepareData(context, instance) {
|
|
72
|
+
let { data } = instance;
|
|
72
73
|
data.stateMods = {
|
|
73
74
|
disabled: data.disabled,
|
|
74
75
|
};
|
|
@@ -91,6 +92,14 @@ export class MonthPicker extends Field {
|
|
|
91
92
|
|
|
92
93
|
if (data.minValue) data.minValue = monthStart(parseDateInvariant(data.minValue));
|
|
93
94
|
|
|
95
|
+
if (this.onCreateIsMonthDateSelectable) {
|
|
96
|
+
instance.isMonthDateSelectable = instance.invoke(
|
|
97
|
+
"onCreateIsMonthDateSelectable",
|
|
98
|
+
data.validationParams,
|
|
99
|
+
instance,
|
|
100
|
+
);
|
|
101
|
+
}
|
|
102
|
+
|
|
94
103
|
super.prepareData(...arguments);
|
|
95
104
|
}
|
|
96
105
|
|
|
@@ -129,12 +138,13 @@ export class MonthPicker extends Field {
|
|
|
129
138
|
}
|
|
130
139
|
|
|
131
140
|
handleSelect(e, instance, date1, date2) {
|
|
132
|
-
let { data, widget } = instance;
|
|
141
|
+
let { data, widget, isMonthDateSelectable } = instance;
|
|
133
142
|
let encode = widget.encoding || Culture.getDefaultDateEncoding();
|
|
134
143
|
|
|
135
144
|
if (data.disabled) return;
|
|
136
145
|
|
|
137
|
-
if (!
|
|
146
|
+
if (isMonthDateSelectable && !isMonthDateSelectable(date1)) return;
|
|
147
|
+
if (!dateSelectableCheck(date1, data)) return;
|
|
138
148
|
|
|
139
149
|
if (this.onBeforeSelect && instance.invoke("onBeforeSelect", e, instance, date1, date2) === false) return;
|
|
140
150
|
|
|
@@ -167,7 +177,7 @@ Localization.registerPrototype("cx/widgets/MonthPicker", MonthPicker);
|
|
|
167
177
|
|
|
168
178
|
Widget.alias("month-picker", MonthPicker);
|
|
169
179
|
|
|
170
|
-
const
|
|
180
|
+
const dateSelectableCheck = (date, data) => {
|
|
171
181
|
if (data.maxValue && !upperBoundCheck(date, data.maxValue, data.maxExclusive)) return false;
|
|
172
182
|
|
|
173
183
|
if (data.minValue && !lowerBoundCheck(date, data.minValue, data.minExclusive)) return false;
|
|
@@ -429,7 +439,7 @@ export class MonthPickerComponent extends VDOM.Component {
|
|
|
429
439
|
|
|
430
440
|
render() {
|
|
431
441
|
let { instance } = this.props;
|
|
432
|
-
let { data, widget } = instance;
|
|
442
|
+
let { data, widget, isMonthDateSelectable } = instance;
|
|
433
443
|
let { CSS, baseClass, startYear, endYear, hideQuarters } = widget;
|
|
434
444
|
|
|
435
445
|
let years = [];
|
|
@@ -464,29 +474,48 @@ export class MonthPickerComponent extends VDOM.Component {
|
|
|
464
474
|
let showCursor = this.state.hover || this.state.focused;
|
|
465
475
|
|
|
466
476
|
for (let y = start; y <= end; y++) {
|
|
477
|
+
let selectableMonths = 0b111111111111;
|
|
478
|
+
// Loop through the months in a year to check if all months are unselectable
|
|
479
|
+
for (let i = 0; i < 12; i++) {
|
|
480
|
+
if (
|
|
481
|
+
(isMonthDateSelectable && !isMonthDateSelectable(new Date(y, i, 1))) ||
|
|
482
|
+
!dateSelectableCheck(new Date(y, i, 1), data)
|
|
483
|
+
) {
|
|
484
|
+
// Set month as unselectable at specified bit
|
|
485
|
+
selectableMonths &= ~(1 << i);
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
// All bits are 0 - all months are unselectable
|
|
490
|
+
const unselectableYear = selectableMonths === 0;
|
|
491
|
+
|
|
467
492
|
let rows = [];
|
|
468
493
|
for (let q = 0; q < 4; q++) {
|
|
469
494
|
let row = [];
|
|
470
|
-
if (q == 0)
|
|
495
|
+
if (q == 0) {
|
|
471
496
|
row.push(
|
|
472
497
|
<th
|
|
473
498
|
key="year"
|
|
474
499
|
rowSpan={4}
|
|
475
500
|
data-point={`Y-${y}`}
|
|
476
|
-
className={CSS.
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
501
|
+
className={CSS.expand(
|
|
502
|
+
CSS.element(baseClass, "year", {
|
|
503
|
+
cursor: showCursor && this.state.column == "Y" && y == this.state.cursorYear,
|
|
504
|
+
}),
|
|
505
|
+
CSS.state({ unselectable: unselectableYear }),
|
|
506
|
+
)}
|
|
507
|
+
onMouseEnter={unselectableYear ? null : this.handleMouseEnter}
|
|
508
|
+
onMouseDown={unselectableYear ? null : this.handleMouseDown}
|
|
509
|
+
onMouseUp={unselectableYear ? null : this.handleMouseUp}
|
|
482
510
|
>
|
|
483
511
|
{y}
|
|
484
512
|
</th>,
|
|
485
513
|
);
|
|
514
|
+
}
|
|
486
515
|
|
|
487
516
|
for (let i = 0; i < 3; i++) {
|
|
488
517
|
let m = q * 3 + i + 1;
|
|
489
|
-
|
|
518
|
+
const unselectableMonth = (selectableMonths & (1 << (m - 1))) === 0;
|
|
490
519
|
let mno = y * 12 + m - 1;
|
|
491
520
|
let handle = true; //isTouchDevice(); //mno === from || mno === to - 1;
|
|
492
521
|
row.push(
|
|
@@ -500,14 +529,14 @@ export class MonthPickerComponent extends VDOM.Component {
|
|
|
500
529
|
m == this.state.cursorMonth,
|
|
501
530
|
handle,
|
|
502
531
|
selected: mno >= from && mno < to,
|
|
503
|
-
unselectable,
|
|
532
|
+
unselectable: unselectableMonth,
|
|
504
533
|
})}
|
|
505
534
|
data-point={`Y-${y}-M-${m}`}
|
|
506
|
-
onMouseEnter={
|
|
507
|
-
onMouseDown={
|
|
508
|
-
onMouseUp={
|
|
509
|
-
onTouchStart={
|
|
510
|
-
onTouchMove={
|
|
535
|
+
onMouseEnter={unselectableMonth ? null : this.handleMouseEnter}
|
|
536
|
+
onMouseDown={unselectableMonth ? null : this.handleMouseDown}
|
|
537
|
+
onMouseUp={unselectableMonth ? null : this.handleMouseUp}
|
|
538
|
+
onTouchStart={unselectableMonth ? null : this.handleMouseDown}
|
|
539
|
+
onTouchMove={unselectableMonth ? null : this.handleTouchMove}
|
|
511
540
|
onTouchEnd={this.handleMouseUp}
|
|
512
541
|
>
|
|
513
542
|
{monthNames[m - 1].substr(0, 3)}
|
|
@@ -515,7 +544,17 @@ export class MonthPickerComponent extends VDOM.Component {
|
|
|
515
544
|
);
|
|
516
545
|
}
|
|
517
546
|
|
|
518
|
-
if (!hideQuarters)
|
|
547
|
+
if (!hideQuarters) {
|
|
548
|
+
let unselectableQuarter = true;
|
|
549
|
+
const start = q * 3;
|
|
550
|
+
for (let i = start; i < start + 3; i++) {
|
|
551
|
+
if ((selectableMonths & (1 << i)) !== 0) {
|
|
552
|
+
// found a selectable month in a quarter
|
|
553
|
+
unselectableQuarter = false;
|
|
554
|
+
break;
|
|
555
|
+
}
|
|
556
|
+
}
|
|
557
|
+
|
|
519
558
|
row.push(
|
|
520
559
|
<th
|
|
521
560
|
key={`q${q}`}
|
|
@@ -525,15 +564,18 @@ export class MonthPickerComponent extends VDOM.Component {
|
|
|
525
564
|
this.state.column == "Q" &&
|
|
526
565
|
y == this.state.cursorYear &&
|
|
527
566
|
q == this.state.cursorQuarter,
|
|
567
|
+
unselectable: unselectableQuarter,
|
|
528
568
|
})}
|
|
529
569
|
data-point={`Y-${y}-Q-${q}`}
|
|
530
|
-
onMouseEnter={this.handleMouseEnter}
|
|
531
|
-
onMouseDown={this.handleMouseDown}
|
|
532
|
-
onMouseUp={this.handleMouseUp}
|
|
570
|
+
onMouseEnter={unselectableQuarter ? null : this.handleMouseEnter}
|
|
571
|
+
onMouseDown={unselectableQuarter ? null : this.handleMouseDown}
|
|
572
|
+
onMouseUp={unselectableQuarter ? null : this.handleMouseUp}
|
|
533
573
|
>
|
|
534
574
|
{`Q${q + 1}`}
|
|
535
575
|
</th>,
|
|
536
576
|
);
|
|
577
|
+
}
|
|
578
|
+
|
|
537
579
|
rows.push(row);
|
|
538
580
|
}
|
|
539
581
|
years.push(rows);
|
package/src/widgets/grid/Grid.js
CHANGED
|
@@ -102,6 +102,7 @@ export class Grid extends Container {
|
|
|
102
102
|
colWidth: {},
|
|
103
103
|
lockedColWidth: {},
|
|
104
104
|
dimensionsVersion: 0,
|
|
105
|
+
disableDefaultSort: false
|
|
105
106
|
};
|
|
106
107
|
instance.v = 0;
|
|
107
108
|
if (this.infinite)
|
|
@@ -182,16 +183,16 @@ export class Grid extends Container {
|
|
|
182
183
|
value: isDefined(c.aggregateValue)
|
|
183
184
|
? c.aggregateValue
|
|
184
185
|
: isDefined(c.value)
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
186
|
+
? c.value
|
|
187
|
+
: c.aggregateField
|
|
188
|
+
? { bind: this.recordName + "." + c.aggregateField }
|
|
189
|
+
: null,
|
|
189
190
|
weight:
|
|
190
191
|
c.weight != null
|
|
191
192
|
? c.weight
|
|
192
193
|
: c.weightField && {
|
|
193
|
-
|
|
194
|
-
|
|
194
|
+
bind: this.recordName + "." + c.weightField,
|
|
195
|
+
},
|
|
195
196
|
type: c.aggregate,
|
|
196
197
|
};
|
|
197
198
|
} else if (c.footer && !showFooter) {
|
|
@@ -295,7 +296,8 @@ export class Grid extends Container {
|
|
|
295
296
|
data.sorters = [sorter];
|
|
296
297
|
}
|
|
297
298
|
|
|
298
|
-
|
|
299
|
+
let skipDefaultSorting = this.clearableSort && instance.state.disableDefaultSort;
|
|
300
|
+
if (!skipDefaultSorting && !isNonEmptyArray(data.sorters) && this.defaultSortField) {
|
|
299
301
|
let sorter = {
|
|
300
302
|
field: this.defaultSortField,
|
|
301
303
|
direction: this.defaultSortDirection || "ASC",
|
|
@@ -553,9 +555,8 @@ export class Grid extends Container {
|
|
|
553
555
|
let initialPosition = getCursorPos(e);
|
|
554
556
|
resizeOverlayEl.className = CSS.element(baseClass, "resize-overlay");
|
|
555
557
|
resizeOverlayEl.style.width = `${initialWidth}px`;
|
|
556
|
-
resizeOverlayEl.style.left = `${
|
|
557
|
-
|
|
558
|
-
}px`;
|
|
558
|
+
resizeOverlayEl.style.left = `${headerCell.getBoundingClientRect().left - gridEl.getBoundingClientRect().left
|
|
559
|
+
}px`;
|
|
559
560
|
gridEl.appendChild(resizeOverlayEl);
|
|
560
561
|
captureMouse2(e, {
|
|
561
562
|
onMouseMove: (e) => {
|
|
@@ -817,7 +818,7 @@ export class Grid extends Container {
|
|
|
817
818
|
widget: () => <div className={CSS.element(baseClass, "col-header-drag-clone")}>{data.text}</div>,
|
|
818
819
|
},
|
|
819
820
|
},
|
|
820
|
-
() => {},
|
|
821
|
+
() => { },
|
|
821
822
|
);
|
|
822
823
|
}
|
|
823
824
|
}
|
|
@@ -852,19 +853,22 @@ export class Grid extends Container {
|
|
|
852
853
|
if (data.sorters[0].direction == "ASC" && (!this.clearableSort || direction == "ASC")) direction = "DESC";
|
|
853
854
|
else if (data.sorters[0].direction == "DESC" && (!this.clearableSort || direction == "DESC"))
|
|
854
855
|
direction = "ASC";
|
|
855
|
-
else
|
|
856
|
+
else {
|
|
857
|
+
direction = null;
|
|
858
|
+
instance.state.disableDefaultSort = true;
|
|
859
|
+
}
|
|
856
860
|
}
|
|
857
861
|
|
|
858
862
|
let sorters = direction
|
|
859
863
|
? [
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
864
|
+
{
|
|
865
|
+
field,
|
|
866
|
+
direction,
|
|
867
|
+
value,
|
|
868
|
+
comparer,
|
|
869
|
+
sortOptions,
|
|
870
|
+
},
|
|
871
|
+
]
|
|
868
872
|
: null;
|
|
869
873
|
|
|
870
874
|
if (sorters == null) field = null;
|
|
@@ -1315,8 +1319,8 @@ class GridComponent extends VDOM.Component {
|
|
|
1315
1319
|
style={
|
|
1316
1320
|
this.rowHeight > 0
|
|
1317
1321
|
? {
|
|
1318
|
-
|
|
1319
|
-
|
|
1322
|
+
height: this.rowHeight + 1,
|
|
1323
|
+
}
|
|
1320
1324
|
: null
|
|
1321
1325
|
}
|
|
1322
1326
|
>
|
|
@@ -2598,6 +2602,7 @@ class GridComponent extends VDOM.Component {
|
|
|
2598
2602
|
instance.buffer.totalRecordCount = 0;
|
|
2599
2603
|
instance.buffer.page = 1;
|
|
2600
2604
|
this.prevFetchRecordsState = null;
|
|
2605
|
+
this.loading = false;
|
|
2601
2606
|
}
|
|
2602
2607
|
}
|
|
2603
2608
|
|
|
@@ -2726,7 +2731,7 @@ class GridComponent extends VDOM.Component {
|
|
|
2726
2731
|
hscroll = true;
|
|
2727
2732
|
item =
|
|
2728
2733
|
item.firstChild.children[
|
|
2729
|
-
|
|
2734
|
+
this.state.cursorCellIndex - this.props.instance.fixedColumnCount
|
|
2730
2735
|
];
|
|
2731
2736
|
} else {
|
|
2732
2737
|
let fixedItem = this.dom.fixedTable.querySelector(`tbody[data-record-key="${record.key}"]`);
|