inviton-powerduck 0.0.366 → 0.0.367

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.
@@ -19,6 +19,8 @@ export interface IPowerduckSystemResources {
19
19
  save: string;
20
20
  search: string;
21
21
  searchedValue: string;
22
+ from?: string;
23
+ to?: string;
22
24
  image: string;
23
25
  noResultsFound: string;
24
26
  colVisLabel: string;
@@ -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 modal-bottom-sheet${args.cssClass ? ` ${args.cssClass}` : ''
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
- this.builder += ' <div class="drag-handle text-center"><div class="handle-bar"></div></div>';
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 (args.mobileMode == null || args.mobileMode == 'bottom-sheet') {
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
- // TODO: Implement
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
- return filterData.NumFrom <= propVal && filterData.NumTo >= propVal;
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
- console.warn('Numeric range filtering not implemented');
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
- console.warn('Numeric range filtering not implemented');
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
 
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "inviton-powerduck",
3
3
  "type": "module",
4
- "version": "0.0.366",
4
+ "version": "0.0.367",
5
5
  "files": [
6
6
  "app/",
7
7
  "common/",