@schneideress/dashboardframework 20.0.26 → 20.0.29

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.
@@ -4976,12 +4976,313 @@ class RADashboardArea {
4976
4976
  this.isElementLoaded(this.widgetList[this.currentLoadingCount]?.widgetElement) ?
4977
4977
  this.initialWidgetCount++ : null;
4978
4978
  }
4979
+ /**
4980
+ * Whether expanded view is active. Uses bulk action as source of truth (works in both normal browser and kiosk).
4981
+ * Handles bulkActionData as string (from API) or object (from event). Falls back to URL param for edge cases (e.g. shared link).
4982
+ */
4983
+ isExpandedView() {
4984
+ if (this.bulkActionData) {
4985
+ try {
4986
+ const data = typeof this.bulkActionData === 'string'
4987
+ ? JSON.parse(this.bulkActionData)
4988
+ : this.bulkActionData;
4989
+ if (data?.allSelections?.expandedView === true)
4990
+ return true;
4991
+ }
4992
+ catch (_) {
4993
+ // ignore parse errors
4994
+ }
4995
+ }
4996
+ return typeof location !== 'undefined' && new URLSearchParams(window.location.search).get('expanded') === 'true';
4997
+ }
4979
4998
  dataLoaded(widgetInfo) {
4980
4999
  setTimeout(() => {
4981
5000
  this.currentLoadingCount--;
4982
5001
  }, 100);
5002
+ // For expanded view, override widget dimensions after data loads (from bulk action or URL)
5003
+ if (this.isExpandedView() && widgetInfo && widgetInfo.widgetInstanceId) {
5004
+ this.overrideWidgetDimensions(widgetInfo);
5005
+ }
4983
5006
  // this.loadData()
4984
5007
  }
5008
+ /**
5009
+ * Override widget height and width for expanded view based on actual data content
5010
+ * Reinitializes gridster after updating dimensions
5011
+ */
5012
+ overrideWidgetDimensions(widgetInfo) {
5013
+ // Find the gridster item for this widget
5014
+ const gridsterItem = this.widgetList.find((item) => item.widgetInfo && item.widgetInfo.widgetInstanceId === widgetInfo.widgetInstanceId);
5015
+ if (gridsterItem && gridsterItem.widgetElement) {
5016
+ // Wait for DOM to update after data load - use multiple attempts to catch async rendering
5017
+ this.measureAndUpdateWidgetDimensions(gridsterItem, 0);
5018
+ }
5019
+ }
5020
+ /**
5021
+ * Measure widget content and update dimensions, with retry logic for async data loading
5022
+ */
5023
+ measureAndUpdateWidgetDimensions(gridsterItem, attempt = 0, maxAttempts = 8) {
5024
+ setTimeout(() => {
5025
+ // Get the actual rendered height of the widget content
5026
+ const widgetElement = gridsterItem.widgetElement;
5027
+ let element = null;
5028
+ // widgetElement is the ElementRef from widgetLoaded event (wcWrapper.nativeElement)
5029
+ if (widgetElement) {
5030
+ if (widgetElement.nativeElement) {
5031
+ element = widgetElement.nativeElement;
5032
+ }
5033
+ else if (widgetElement instanceof HTMLElement) {
5034
+ element = widgetElement;
5035
+ }
5036
+ }
5037
+ if (!element || this.gridcellHeight <= 0) {
5038
+ if (attempt < maxAttempts) {
5039
+ this.measureAndUpdateWidgetDimensions(gridsterItem, attempt + 1, maxAttempts);
5040
+ }
5041
+ return;
5042
+ }
5043
+ // Find the table body or grid content that contains the actual data rows
5044
+ const tbody = element.querySelector('tbody');
5045
+ const table = element.querySelector('table');
5046
+ const wcBody = element.querySelector('.wcBody');
5047
+ const wc = element.querySelector('.wc');
5048
+ const kendoGridContent = element.querySelector('.k-grid-content');
5049
+ const kendoGridTable = element.querySelector('.k-grid-table');
5050
+ // Measure the actual content height - try multiple strategies to get accurate measurement
5051
+ let contentHeight = 0;
5052
+ let measuredElement = null;
5053
+ // Strategy 1: Measure table body (most accurate for row count)
5054
+ if (tbody) {
5055
+ measuredElement = tbody;
5056
+ // Count actual rows and calculate height
5057
+ const rows = tbody.querySelectorAll('tr');
5058
+ if (rows.length > 0) {
5059
+ // Get height of first row to estimate
5060
+ const firstRow = rows[0];
5061
+ const rowHeight = firstRow.offsetHeight || 30; // Default row height if not available
5062
+ // Calculate total height based on row count
5063
+ const estimatedHeight = rows.length * rowHeight;
5064
+ // Use scrollHeight if available, otherwise use estimated
5065
+ contentHeight = Math.max(tbody.scrollHeight, estimatedHeight);
5066
+ }
5067
+ else {
5068
+ contentHeight = tbody.scrollHeight;
5069
+ }
5070
+ }
5071
+ // Strategy 2: Measure Kendo grid content
5072
+ else if (kendoGridContent) {
5073
+ measuredElement = kendoGridContent;
5074
+ contentHeight = kendoGridContent.scrollHeight;
5075
+ // Also check the table inside
5076
+ if (kendoGridTable) {
5077
+ const tableHeight = kendoGridTable.scrollHeight;
5078
+ contentHeight = Math.max(contentHeight, tableHeight);
5079
+ }
5080
+ }
5081
+ // Strategy 3: Measure table
5082
+ else if (table) {
5083
+ measuredElement = table;
5084
+ contentHeight = table.scrollHeight;
5085
+ }
5086
+ // Strategy 4: Measure widget content
5087
+ else if (wc) {
5088
+ measuredElement = wc;
5089
+ contentHeight = wc.scrollHeight;
5090
+ }
5091
+ // Strategy 5: Measure widget body
5092
+ else if (wcBody) {
5093
+ measuredElement = wcBody;
5094
+ contentHeight = wcBody.scrollHeight;
5095
+ }
5096
+ // Fallback: measure the wrapper
5097
+ else {
5098
+ measuredElement = element;
5099
+ contentHeight = element.scrollHeight;
5100
+ }
5101
+ // If scrollHeight seems constrained, try alternative measurements
5102
+ if (measuredElement) {
5103
+ // Check if scrollHeight is accurate (should be > clientHeight if content overflows)
5104
+ const scrollH = measuredElement.scrollHeight;
5105
+ const clientH = measuredElement.clientHeight;
5106
+ const offsetH = measuredElement.offsetHeight;
5107
+ // If scrollHeight equals clientHeight and seems too small, content might be constrained
5108
+ if (scrollH === clientH && scrollH < 200) {
5109
+ // Try offsetHeight
5110
+ contentHeight = Math.max(contentHeight, offsetH);
5111
+ }
5112
+ // For tables, also try measuring all rows directly
5113
+ if (tbody || table) {
5114
+ const allRows = (tbody || table)?.querySelectorAll('tr') || [];
5115
+ if (allRows.length > 0) {
5116
+ let totalRowHeight = 0;
5117
+ allRows.forEach((row) => {
5118
+ totalRowHeight += row.offsetHeight || 30;
5119
+ });
5120
+ // Use the maximum of scrollHeight and calculated row heights
5121
+ contentHeight = Math.max(contentHeight, totalRowHeight);
5122
+ }
5123
+ }
5124
+ }
5125
+ // Get header height
5126
+ const header = element.querySelector('.wcheader');
5127
+ const headerHeight = header ? header.offsetHeight : 46;
5128
+ // Calculate total height needed (content + header + padding)
5129
+ // Add sufficient padding to ensure no scrolling
5130
+ const padding = 40; // Padding for margins and spacing
5131
+ const totalHeight = contentHeight + headerHeight + padding;
5132
+ // Check if content is loaded (reasonable height)
5133
+ // For expanded view, we expect significant content
5134
+ const hasValidContent = contentHeight > 100 || (attempt >= 3 && contentHeight > 50);
5135
+ if (!hasValidContent && attempt < maxAttempts) {
5136
+ // Content not fully loaded, retry with longer delay
5137
+ this.measureAndUpdateWidgetDimensions(gridsterItem, attempt + 1, maxAttempts);
5138
+ return;
5139
+ }
5140
+ // Calculate rows needed based on grid cell height
5141
+ // Use Math.ceil to ensure we have enough space (round up)
5142
+ const calculatedRows = Math.ceil(totalHeight / this.gridcellHeight);
5143
+ // Add a small buffer (0.5 rows) to ensure no scrolling, then round up
5144
+ // This accounts for any rounding errors or minor spacing issues
5145
+ const bufferedRows = calculatedRows + 0.5;
5146
+ const newRows = Math.max(Math.ceil(bufferedRows), 4);
5147
+ // For expanded view, use full available width (no spacing needed since widgets stack vertically)
5148
+ const maxCols = this.responsiveService.maxColsDesktop || 12;
5149
+ const newCols = maxCols; // Full width
5150
+ // Update gridster item dimensions if changed
5151
+ const needsUpdate = gridsterItem.rows !== newRows || gridsterItem.cols !== newCols || gridsterItem.x !== 0;
5152
+ if (needsUpdate) {
5153
+ gridsterItem.rows = newRows;
5154
+ gridsterItem.cols = newCols;
5155
+ gridsterItem.x = 0; // Always start at the beginning of the row (full width)
5156
+ // Update widget info
5157
+ if (gridsterItem.widgetInfo) {
5158
+ gridsterItem.widgetInfo.height = newRows;
5159
+ gridsterItem.widgetInfo.width = newCols;
5160
+ gridsterItem.widgetInfo.position_x = 0; // Start at position 0 for full width
5161
+ }
5162
+ // Reposition widgets to prevent overlapping after dimension change
5163
+ this.repositionWidgetsAfterDimensionChange();
5164
+ // Force gridster to recalculate layout
5165
+ this.ngZone.run(() => {
5166
+ // Trigger change detection by updating the array reference
5167
+ this.widgetList = [...this.widgetList];
5168
+ // Call optionsChanged if API is available
5169
+ if (this.options && this.options.api) {
5170
+ this.options.api?.optionsChanged();
5171
+ }
5172
+ // Update area height after dimensions change
5173
+ setTimeout(() => {
5174
+ this.setAreaHeight();
5175
+ // Re-verify height after layout update to ensure accuracy
5176
+ // Check if there's still scrolling and adjust if needed
5177
+ if (attempt < maxAttempts - 1) {
5178
+ setTimeout(() => {
5179
+ // Re-measure to verify the height is correct
5180
+ const widgetEl = gridsterItem.widgetElement;
5181
+ let el = null;
5182
+ if (widgetEl) {
5183
+ el = widgetEl.nativeElement || widgetEl;
5184
+ }
5185
+ if (el) {
5186
+ const wcBody = el.querySelector('.wcBody');
5187
+ const tbody = el.querySelector('tbody');
5188
+ const contentEl = tbody || wcBody || el;
5189
+ // Check if there's scrolling (scrollHeight > clientHeight)
5190
+ if (contentEl && contentEl.scrollHeight > contentEl.clientHeight + 10) {
5191
+ // Still has scrolling, need more height
5192
+ const additionalHeight = contentEl.scrollHeight - contentEl.clientHeight;
5193
+ const additionalRows = Math.ceil(additionalHeight / this.gridcellHeight);
5194
+ if (additionalRows > 0) {
5195
+ gridsterItem.rows = gridsterItem.rows + additionalRows;
5196
+ if (gridsterItem.widgetInfo) {
5197
+ gridsterItem.widgetInfo.height = gridsterItem.rows;
5198
+ }
5199
+ this.widgetList = [...this.widgetList];
5200
+ if (this.options && this.options.api) {
5201
+ this.options.api?.optionsChanged();
5202
+ }
5203
+ setTimeout(() => this.setAreaHeight(), 100);
5204
+ }
5205
+ }
5206
+ }
5207
+ // Continue with normal retry logic
5208
+ this.measureAndUpdateWidgetDimensions(gridsterItem, attempt + 1, maxAttempts);
5209
+ }, 400);
5210
+ }
5211
+ }, 200);
5212
+ });
5213
+ }
5214
+ else if (attempt < maxAttempts - 1) {
5215
+ // Dimensions didn't change, but verify one more time that content is fully loaded
5216
+ this.measureAndUpdateWidgetDimensions(gridsterItem, attempt + 1, maxAttempts);
5217
+ }
5218
+ }, attempt === 0 ? 1500 : attempt < 3 ? 800 : 500); // Longer initial wait for data to fully render
5219
+ }
5220
+ /**
5221
+ * Reposition widgets to prevent overlapping after dimension changes in expanded view
5222
+ * When 2 widgets are in the same row, the 2nd widget moves to next row after the 1st widget's height
5223
+ */
5224
+ repositionWidgetsAfterDimensionChange() {
5225
+ if (this.widgetList.length === 0)
5226
+ return;
5227
+ // Sort widgets by their original Y position (row), then by X position (left to right)
5228
+ const sortedWidgets = [...this.widgetList].sort((a, b) => {
5229
+ const aY = a.y || 0;
5230
+ const bY = b.y || 0;
5231
+ if (aY !== bY) {
5232
+ return aY - bY;
5233
+ }
5234
+ // If same row, sort by X position (left to right)
5235
+ return (a.x || 0) - (b.x || 0);
5236
+ });
5237
+ // Group widgets by their original row (Y position) before repositioning
5238
+ const rowGroups = new Map();
5239
+ sortedWidgets.forEach(widget => {
5240
+ const originalY = widget.y || 0;
5241
+ if (!rowGroups.has(originalY)) {
5242
+ rowGroups.set(originalY, []);
5243
+ }
5244
+ rowGroups.get(originalY).push(widget);
5245
+ });
5246
+ // Sort rows by Y position
5247
+ const sortedRows = Array.from(rowGroups.entries()).sort((a, b) => a[0] - b[0]);
5248
+ let currentY = 0;
5249
+ // Process each original row
5250
+ sortedRows.forEach(([originalRowY, widgetsInRow]) => {
5251
+ // Sort widgets in this row by X position (left to right)
5252
+ widgetsInRow.sort((a, b) => (a.x || 0) - (b.x || 0));
5253
+ // Process widgets sequentially: first widget at currentY, subsequent widgets after previous widget's height
5254
+ // All widgets get x=0 since they take full width and stack vertically
5255
+ widgetsInRow.forEach((widget, index) => {
5256
+ if (index === 0) {
5257
+ // First widget in row: place at currentY
5258
+ widget.y = currentY;
5259
+ }
5260
+ else {
5261
+ // Subsequent widgets: place after the previous widget's bottom edge
5262
+ const previousWidget = widgetsInRow[index - 1];
5263
+ const previousWidgetHeight = previousWidget.rows || 0;
5264
+ const previousWidgetY = previousWidget.y || 0;
5265
+ widget.y = previousWidgetY + previousWidgetHeight;
5266
+ }
5267
+ // Set x to 0 for all widgets (full width, no horizontal spacing)
5268
+ widget.x = 0;
5269
+ // Update widget info
5270
+ if (widget.widgetInfo) {
5271
+ widget.widgetInfo.position_y = widget.y;
5272
+ widget.widgetInfo.position_x = 0; // Full width, start at position 0
5273
+ }
5274
+ });
5275
+ // Calculate where the next row should start
5276
+ // If multiple widgets in row, use the last widget's bottom edge
5277
+ // Otherwise, use the first widget's bottom edge
5278
+ if (widgetsInRow.length > 0) {
5279
+ const lastWidget = widgetsInRow[widgetsInRow.length - 1];
5280
+ const lastWidgetY = lastWidget.y || 0;
5281
+ const lastWidgetHeight = lastWidget.rows || 0;
5282
+ currentY = lastWidgetY + lastWidgetHeight;
5283
+ }
5284
+ });
5285
+ }
4985
5286
  isElementLoaded(el) {
4986
5287
  if (el) {
4987
5288
  let rect = el.getBoundingClientRect();
@@ -5122,6 +5423,8 @@ class RADashboardArea {
5122
5423
  };
5123
5424
  let _the = this;
5124
5425
  this.isSiteDetailDashboard = this.dashboardInfo?.templateName === 'Site Detail';
5426
+ // Check if expanded view is requested
5427
+ //const isExpandedView = new URLSearchParams(window.location.search).get('expanded') === 'true';
5125
5428
  this.dashboardService.getWidgetsByDashboardArea(widgetInfo, this.appConfig).subscribe(widgets => {
5126
5429
  if (this.isSiteDetailDashboard) {
5127
5430
  widgets = this.arrangeWidgetsInTwoColumnGrid(widgets);