inviton-powerduck 0.0.366 → 0.0.368
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/common/dialog-utils.ts
CHANGED
|
@@ -124,22 +124,27 @@ class DialogBuilder {
|
|
|
124
124
|
hasIcon: boolean = false;
|
|
125
125
|
id: string = null;
|
|
126
126
|
labelId: string = null;
|
|
127
|
+
isBottomSheet: boolean = false;
|
|
127
128
|
|
|
128
129
|
build(args: AppDialogArgs): string {
|
|
129
130
|
this.args = args;
|
|
130
131
|
this.id = `dyn-modal-${PortalUtils.randomString(6)}`;
|
|
131
132
|
this.labelId = `${this.id}label`;
|
|
132
133
|
this.hasIcon = args.icon != null;
|
|
134
|
+
this.isBottomSheet = this._treatAsBottomSheet();
|
|
133
135
|
this.builder = '';
|
|
134
136
|
|
|
135
137
|
this.builder
|
|
136
138
|
+= `<div id="${this.id}" key="${this.id
|
|
137
139
|
}" tabindex="-1" role="dialog" aria-labelledby="${this.labelId
|
|
138
|
-
}" data-bs-backdrop="true" data-bs-keyboard="true" class="modal fade
|
|
140
|
+
}" data-bs-backdrop="true" data-bs-keyboard="true" class="modal fade${this._getMobileModeCss()}${args.cssClass ? ` ${args.cssClass}` : ''
|
|
139
141
|
}">`;
|
|
140
142
|
this.builder += ` <div role="document" class="modal-dialog${args.size != null ? ` ${args.size}` : ''}">`;
|
|
141
143
|
this.builder += ' <div class="modal-content">';
|
|
142
|
-
|
|
144
|
+
|
|
145
|
+
if (this.isBottomSheet) {
|
|
146
|
+
this.builder += ' <div class="drag-handle text-center"><div class="handle-bar"></div></div>';
|
|
147
|
+
}
|
|
143
148
|
|
|
144
149
|
this.builder += this._getHeader();
|
|
145
150
|
this.builder += this._getBody();
|
|
@@ -151,6 +156,29 @@ class DialogBuilder {
|
|
|
151
156
|
return this.builder;
|
|
152
157
|
}
|
|
153
158
|
|
|
159
|
+
/**
|
|
160
|
+
* Mirrors the `Modal` component's `treatMobileAsBottomSheet()`: a dialog only docks to the
|
|
161
|
+
* bottom as a sheet on an actual mobile device. `mobileMode` defaults to the bottom-sheet
|
|
162
|
+
* behavior (matching the pre-existing drag-handle binding); 'default'/'fullscreen' opt out.
|
|
163
|
+
*/
|
|
164
|
+
private _treatAsBottomSheet(): boolean {
|
|
165
|
+
if (!PortalUtils.treatAsMobileDevice()) {
|
|
166
|
+
return false;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
return this.args.mobileMode == null || this.args.mobileMode == 'bottom-sheet';
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
private _getMobileModeCss(): string {
|
|
173
|
+
if (this.isBottomSheet) {
|
|
174
|
+
return ' modal-bottom-sheet';
|
|
175
|
+
} else if (this.args.mobileMode == 'fullscreen') {
|
|
176
|
+
return ' modal-mobile-fullscreen';
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
return '';
|
|
180
|
+
}
|
|
181
|
+
|
|
154
182
|
private _getHeader(): string {
|
|
155
183
|
let innerBuilder = '';
|
|
156
184
|
innerBuilder += `<div class="modal-header${this.hasIcon ? ' modal-has-headericon' : ''}">`;
|
|
@@ -262,7 +290,7 @@ export class DialogUtils {
|
|
|
262
290
|
}, 50);
|
|
263
291
|
});
|
|
264
292
|
|
|
265
|
-
if (
|
|
293
|
+
if (builder.isBottomSheet) {
|
|
266
294
|
const modal = document.getElementById(builder.id);
|
|
267
295
|
ModalUtils.bindModalBottomSheetHandle(() => modal, () => ModalUtils.hideModal(modalContext));
|
|
268
296
|
}
|
|
@@ -757,7 +757,10 @@ class DataTableComponent extends TsxComponent<DataTableArgs> implements DataTabl
|
|
|
757
757
|
endTime: filterItem.DateTo,
|
|
758
758
|
};
|
|
759
759
|
} else if (filterItem.FilterType == DataTableFilterItemType.NumericRange) {
|
|
760
|
-
|
|
760
|
+
this.currentAdvancedFilterState[filterItem.PropertyName] = {
|
|
761
|
+
from: filterItem.NumFrom,
|
|
762
|
+
to: filterItem.NumTo,
|
|
763
|
+
};
|
|
761
764
|
} else if (filterItem.FilterType == DataTableFilterItemType.Text) {
|
|
762
765
|
this.currentAdvancedFilterState[filterItem.PropertyName] = filterItem.ContainsValue;
|
|
763
766
|
}
|
|
@@ -1237,6 +1240,57 @@ class DataTableComponent extends TsxComponent<DataTableArgs> implements DataTabl
|
|
|
1237
1240
|
this.reloadData();
|
|
1238
1241
|
}
|
|
1239
1242
|
|
|
1243
|
+
performNumericRangeFilter(dtColumn: TableColumn, data: { from: number | null; to: number | null } | null): void {
|
|
1244
|
+
this.currentAdvancedFilterState[dtColumn.id] = data;
|
|
1245
|
+
|
|
1246
|
+
// A one-sided range (only from, or only to) is valid; empty means both bounds are missing.
|
|
1247
|
+
const isEmpty = data == null || (data.from == null && data.to == null);
|
|
1248
|
+
if (isEmpty) {
|
|
1249
|
+
this.addFilterItem(dtColumn, {} as any);
|
|
1250
|
+
}
|
|
1251
|
+
|
|
1252
|
+
const filterValue: DataTablePostBackFilterItem = {
|
|
1253
|
+
PropertyName: dtColumn.id,
|
|
1254
|
+
NumFrom: data?.from ?? undefined,
|
|
1255
|
+
NumTo: data?.to ?? undefined,
|
|
1256
|
+
FilterType: DataTableFilterItemType.NumericRange,
|
|
1257
|
+
};
|
|
1258
|
+
|
|
1259
|
+
if (!this.filterDataHaveChanged(dtColumn, filterValue)) {
|
|
1260
|
+
return;
|
|
1261
|
+
}
|
|
1262
|
+
|
|
1263
|
+
if (isEmpty) {
|
|
1264
|
+
this.addFilterItem(dtColumn, {} as any);
|
|
1265
|
+
} else {
|
|
1266
|
+
this.addFilterItem(dtColumn, filterValue);
|
|
1267
|
+
}
|
|
1268
|
+
|
|
1269
|
+
if (this.preserveFilter == true) {
|
|
1270
|
+
if (isEmpty) {
|
|
1271
|
+
StorageHelper.storeFilter(this.id, null);
|
|
1272
|
+
} else {
|
|
1273
|
+
StorageHelper.storeFilter(this.id, filterValue);
|
|
1274
|
+
}
|
|
1275
|
+
}
|
|
1276
|
+
|
|
1277
|
+
if (this.handleClientsideFilter(false)) {
|
|
1278
|
+
return;
|
|
1279
|
+
}
|
|
1280
|
+
|
|
1281
|
+
// Immediate reload if changed, delay handled by the Dropdown component
|
|
1282
|
+
this.reloadData();
|
|
1283
|
+
}
|
|
1284
|
+
|
|
1285
|
+
private parseNumericFilterValue(raw: string): number | null {
|
|
1286
|
+
if (isNullOrEmpty(raw)) {
|
|
1287
|
+
return null;
|
|
1288
|
+
}
|
|
1289
|
+
|
|
1290
|
+
const parsed = Number(raw);
|
|
1291
|
+
return Number.isNaN(parsed) ? null : parsed;
|
|
1292
|
+
}
|
|
1293
|
+
|
|
1240
1294
|
performClientsideFilter(): void {
|
|
1241
1295
|
this.rows = this.getClientsideFilteredAndSortedRows(this.loadedRows);
|
|
1242
1296
|
}
|
|
@@ -1348,7 +1402,20 @@ class DataTableComponent extends TsxComponent<DataTableArgs> implements DataTabl
|
|
|
1348
1402
|
return false;
|
|
1349
1403
|
}
|
|
1350
1404
|
|
|
1351
|
-
|
|
1405
|
+
const numVal = typeof propVal == 'number' ? propVal : Number(propVal);
|
|
1406
|
+
if (Number.isNaN(numVal)) {
|
|
1407
|
+
return false;
|
|
1408
|
+
}
|
|
1409
|
+
|
|
1410
|
+
if (filterData.NumFrom != null && numVal < filterData.NumFrom) {
|
|
1411
|
+
return false;
|
|
1412
|
+
}
|
|
1413
|
+
|
|
1414
|
+
if (filterData.NumTo != null && numVal > filterData.NumTo) {
|
|
1415
|
+
return false;
|
|
1416
|
+
}
|
|
1417
|
+
|
|
1418
|
+
return true;
|
|
1352
1419
|
});
|
|
1353
1420
|
}
|
|
1354
1421
|
}
|
|
@@ -2038,7 +2105,10 @@ class DataTableComponent extends TsxComponent<DataTableArgs> implements DataTabl
|
|
|
2038
2105
|
endTime: filterItem.DateTo,
|
|
2039
2106
|
};
|
|
2040
2107
|
} else if (filterItem.FilterType == DataTableFilterItemType.NumericRange) {
|
|
2041
|
-
|
|
2108
|
+
return {
|
|
2109
|
+
from: filterItem.NumFrom,
|
|
2110
|
+
to: filterItem.NumTo,
|
|
2111
|
+
};
|
|
2042
2112
|
} else {
|
|
2043
2113
|
return null;
|
|
2044
2114
|
}
|
|
@@ -2402,7 +2472,7 @@ class DataTableComponent extends TsxComponent<DataTableArgs> implements DataTabl
|
|
|
2402
2472
|
null,
|
|
2403
2473
|
);
|
|
2404
2474
|
} else if (filterType == DataTableFilterItemType.NumericRange) {
|
|
2405
|
-
|
|
2475
|
+
this.performNumericRangeFilter(dtColumn, null);
|
|
2406
2476
|
}
|
|
2407
2477
|
};
|
|
2408
2478
|
|
|
@@ -2442,6 +2512,20 @@ class DataTableComponent extends TsxComponent<DataTableArgs> implements DataTabl
|
|
|
2442
2512
|
return (<DaterangePicker label={null} cssClass="dt-filter-input" wrap={false} placeholder={`${PowerduckState.getResourceValue('search')[capitalize]()}...`} value={this.currentAdvancedFilterState[dtColumn.id]} changed={(e) => { this.performDateRangeFilter(dtColumn, e); }} appendIcon={closeIcon} appendIconClicked={appendIconClicked} />);
|
|
2443
2513
|
} else if (filterType == DataTableFilterItemType.DateRangeWithTime) {
|
|
2444
2514
|
return (<DaterangePicker label={null} cssClass="dt-filter-input" wrap={false} placeholder={`${PowerduckState.getResourceValue('search')[capitalize]()}...`} value={this.currentAdvancedFilterState[dtColumn.id]} changed={(e) => { this.performDateRangeFilter(dtColumn, e); }} appendIcon={closeIcon} appendIconClicked={appendIconClicked} enableTime={true} autoClose={false} />);
|
|
2515
|
+
} else if (filterType == DataTableFilterItemType.NumericRange) {
|
|
2516
|
+
const numRange = this.currentAdvancedFilterState[dtColumn.id];
|
|
2517
|
+
const fromRes = PowerduckState.getResourceValue('from');
|
|
2518
|
+
const toRes = PowerduckState.getResourceValue('to');
|
|
2519
|
+
const searchPlaceholder = PowerduckState.getResourceValue('search')[capitalize]();
|
|
2520
|
+
const fromPlaceholder = isNullOrEmpty(fromRes) || fromRes.startsWith('{{') ? searchPlaceholder : fromRes[capitalize]();
|
|
2521
|
+
const toPlaceholder = isNullOrEmpty(toRes) || toRes.startsWith('{{') ? searchPlaceholder : toRes[capitalize]();
|
|
2522
|
+
|
|
2523
|
+
return (
|
|
2524
|
+
<div class="dt-filter-input dt-filter-numrange">
|
|
2525
|
+
<TextBox updateMode="change" cssClass="dt-filter-numrange-from" wrap={false} label={null} autoCompleteEnabled={false} placeholder={fromPlaceholder} value={numRange?.from != null ? String(numRange.from) : ''} changed={(e) => { this.performNumericRangeFilter(dtColumn, { from: this.parseNumericFilterValue(e), to: numRange?.to ?? null }); }} />
|
|
2526
|
+
<TextBox updateMode="change" cssClass="dt-filter-numrange-to" wrap={false} label={null} autoCompleteEnabled={false} placeholder={toPlaceholder} value={numRange?.to != null ? String(numRange.to) : ''} changed={(e) => { this.performNumericRangeFilter(dtColumn, { from: numRange?.from ?? null, to: this.parseNumericFilterValue(e) }); }} appendIcon={closeIcon} appendIconClicked={appendIconClicked} />
|
|
2527
|
+
</div>
|
|
2528
|
+
);
|
|
2445
2529
|
}
|
|
2446
2530
|
}
|
|
2447
2531
|
|
|
@@ -1,87 +1,96 @@
|
|
|
1
|
-
/*
|
|
2
|
-
* LoadingIndicator styles — holdon-style overlay + SpinKit "sk-rect" spinner.
|
|
3
|
-
*
|
|
4
|
-
* LoadingIndicatorComponent (./index.tsx) emits the `.holdon-overlay` /
|
|
5
|
-
* `.holdon-content` / `.sk-rect` / `.rect1..5` / `.sr-only` classes but the
|
|
6
|
-
* stylesheet for them was never shipped with the component, so the spinner
|
|
7
|
-
* bars rendered as unstyled (invisible) divs and the "Loading..." sr-only
|
|
8
|
-
* label leaked as plain text (Bootstrap 5 dropped `.sr-only`). Added as part
|
|
9
|
-
* of QA_AT-220 (DataTable loading preloader) so the reused LoadingIndicator
|
|
10
|
-
* actually shows a centered, animated spinner everywhere it is mounted.
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
.
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
.sk-rect
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
1
|
+
/*
|
|
2
|
+
* LoadingIndicator styles — holdon-style overlay + SpinKit "sk-rect" spinner.
|
|
3
|
+
*
|
|
4
|
+
* LoadingIndicatorComponent (./index.tsx) emits the `.holdon-overlay` /
|
|
5
|
+
* `.holdon-content` / `.sk-rect` / `.rect1..5` / `.sr-only` classes but the
|
|
6
|
+
* stylesheet for them was never shipped with the component, so the spinner
|
|
7
|
+
* bars rendered as unstyled (invisible) divs and the "Loading..." sr-only
|
|
8
|
+
* label leaked as plain text (Bootstrap 5 dropped `.sr-only`). Added as part
|
|
9
|
+
* of QA_AT-220 (DataTable loading preloader) so the reused LoadingIndicator
|
|
10
|
+
* actually shows a centered, animated spinner everywhere it is mounted.
|
|
11
|
+
*
|
|
12
|
+
* COLLISION CONTRACT (gap-regression follow-up): host apps commonly ship the
|
|
13
|
+
* original HoldOn.js CSS globally (e.g. an inline <style> in index.html that
|
|
14
|
+
* styles their boot overlay and injected blockers) and it targets these SAME
|
|
15
|
+
* class names. This sheet must therefore behave as a DEFAULT, never an
|
|
16
|
+
* override:
|
|
17
|
+
* - the overlay rule sits in `:where()` (zero specificity) so app-level
|
|
18
|
+
* `.holdon-overlay` styling — backgrounds in particular — keeps winning;
|
|
19
|
+
* - bar spacing uses `margin-left: 2px`, the exact HoldOn.js value, instead
|
|
20
|
+
* of flex `gap`: gap STACKS with the host's margin (2px + 3px = 5px bar
|
|
21
|
+
* gaps), while a value-identical margin is a no-op regardless of cascade
|
|
22
|
+
* order;
|
|
23
|
+
* - no `.holdon-content` rule: flex-centering it shifted the spinner inside
|
|
24
|
+
* HoldOn.js' absolutely-positioned 50x57 content box; the overlay's own
|
|
25
|
+
* flex centering already covers hosts without app-level holdon CSS.
|
|
26
|
+
*/
|
|
27
|
+
|
|
28
|
+
:where(.holdon-element.holdon-overlay) {
|
|
29
|
+
position: absolute;
|
|
30
|
+
inset: 0;
|
|
31
|
+
display: flex;
|
|
32
|
+
align-items: center;
|
|
33
|
+
justify-content: center;
|
|
34
|
+
background-color: rgba(255, 255, 255, 0.65);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/* SpinKit "rect" — five bars that stretch in sequence. */
|
|
38
|
+
.sk-rect {
|
|
39
|
+
width: 50px;
|
|
40
|
+
height: 40px;
|
|
41
|
+
display: inline-flex;
|
|
42
|
+
align-items: center;
|
|
43
|
+
justify-content: center;
|
|
44
|
+
text-align: center;
|
|
45
|
+
font-size: 10px;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
.sk-rect > div {
|
|
49
|
+
background-color: #b3b3b3;
|
|
50
|
+
width: 6px;
|
|
51
|
+
height: 100%;
|
|
52
|
+
margin-left: 2px;
|
|
53
|
+
display: inline-block;
|
|
54
|
+
animation: holdon-sk-stretchdelay 1.2s infinite ease-in-out;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
.sk-rect .rect2 {
|
|
58
|
+
animation-delay: -1.1s;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
.sk-rect .rect3 {
|
|
62
|
+
animation-delay: -1s;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
.sk-rect .rect4 {
|
|
66
|
+
animation-delay: -0.9s;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
.sk-rect .rect5 {
|
|
70
|
+
animation-delay: -0.8s;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
@keyframes holdon-sk-stretchdelay {
|
|
74
|
+
0%,
|
|
75
|
+
40%,
|
|
76
|
+
100% {
|
|
77
|
+
transform: scaleY(0.4);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
20% {
|
|
81
|
+
transform: scaleY(1);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/* Screen-reader-only: hide the "Loading..." label visually (Bootstrap 5 has no .sr-only). */
|
|
86
|
+
.holdon-element .sr-only {
|
|
87
|
+
position: absolute;
|
|
88
|
+
width: 1px;
|
|
89
|
+
height: 1px;
|
|
90
|
+
padding: 0;
|
|
91
|
+
margin: -1px;
|
|
92
|
+
overflow: hidden;
|
|
93
|
+
clip: rect(0, 0, 0, 0);
|
|
94
|
+
white-space: nowrap;
|
|
95
|
+
border: 0;
|
|
96
|
+
}
|