1let grids = []; 2 3export function init(gridElement, autoFocus) { 4 if (gridElement === undefined || gridElement === null) { 5 return; 6 }; 7 8 const controller = new AbortController(); 9 const { signal } = controller; 10 11 enableColumnResizing(gridElement, true, signal); 12 13 let start = gridElement.querySelector('td:first-child'); 14 15 if (autoFocus) { 16 start.focus(); 17 } 18 19 const bodyClickHandler = event => { 20 const columnOptionsElement = gridElement?.querySelector('.col-options'); 21 if (columnOptionsElement && event.composedPath().indexOf(columnOptionsElement) < 0) { 22 gridElement.dispatchEvent(new CustomEvent('closecolumnoptions', { bubbles: true })); 23 } 24 const columnResizeElement = gridElement?.querySelector('.col-resize'); 25 if (columnResizeElement && event.composedPath().indexOf(columnResizeElement) < 0) { 26 gridElement.dispatchEvent(new CustomEvent('closecolumnresize', { bubbles: true })); 27 } 28 }; 29 const bodyKeyDownHandler = event => { 30 if (event.key === "Escape") { 31 const columnOptionsElement = gridElement?.querySelector('.col-options'); 32 if (columnOptionsElement && columnOptionsElement.contains(event.target)) { 33 gridElement.dispatchEvent(new CustomEvent('closecolumnoptions', { bubbles: true })); 34 gridElement.focus(); 35 } 36 const columnResizeElement = gridElement?.querySelector('.col-resize'); 37 if (columnResizeElement) { 38 gridElement.dispatchEvent(new CustomEvent('closecolumnresize', { bubbles: true })); 39 gridElement.focus(); 40 } 41 } 42 }; 43 const keyboardNavigation = (sibling) => { 44 if (sibling !== null) {
45 start.focus(); 46 sibling.focus(); 47 start = sibling; 48 } 49 } 50 const keyDownHandler = event => { 51 const columnOptionsElement = gridElement?.querySelector('.col-options'); 52 if (columnOptionsElement && columnOptionsElement.contains(event.target)) { 53 if (event.key === "ArrowRight" || event.key === "ArrowLeft" || event.key === "ArrowDown" || event.key === "ArrowUp") { 54 event.stopPropagation(); 55 return; 56 } 57 } 58 59 const columnResizeElement = gridElement?.querySelector('.col-resize'); 60 if (columnResizeElement && columnResizeElement.contains(event.target)) { 61 if (event.key === "ArrowRight" || event.key === "ArrowLeft" || event.key === "ArrowDown" || event.key === "ArrowUp") { 62 event.stopPropagation(); 63 return; 64 } 65 } 66 67 if (document.activeElement.tagName.toLowerCase() != 'table' && document.activeElement.tagName.toLowerCase() != 'td' && document.activeElement.tagName.toLowerCase() != 'th') { 68 return; 69 } 70 71 // check if start is a child of gridElement 72 if (start !== null && (gridElement.contains(start) || gridElement === start) && document.activeElement === start && document.activeElement.tagName.toLowerCase() !== 'fluent-text-field' && document.activeElement.tagName.toLowerCase() !== 'fluent-menu-item') { 73 const idx = start.cellIndex; 74 const isRTL = getComputedStyle(gridElement).direction === 'rtl'; 75 76 if (event.key === "ArrowUp") { 77 // up arrow 78 const previousRow = start.parentElement.previousElementSibling; 79 if (previousRow !== null) { 80 event.preventDefault(); 81 const previousSibling = previousRow.cells[idx]; 82 keyboardNavigation(previousSibling); 83 } 84 } else if (event.key === "ArrowDown") { 85 // down arrow 86 const nextRow = start.parentElement.nextElementSibling; 87 if (nextRow !== null) { 88 event.preventDefault(); 89 const nextSibling = nextRow.cells[idx]; 90 keyboardNavigation(nextSibling); 91 } 92 } else if (event.key === "ArrowLeft") { 93 // left arrow 94 event.preventDefault(); 95 const previousSibling = isRTL ? start.nextElementSibling : start.previousElementSibling; 96 keyboardNavigation(previousSibling); 97 event.stopPropagation(); 98 } else if (event.key === "ArrowRight") { 99 // right arrow 100 event.preventDefault(); 101 const nextsibling = isRTL ? start.previousElementSibling : start.nextElementSibling; 102 keyboardNavigation(nextsibling); 103 event.stopPropagation(); 104 } 105 } 106 else { 107 start = document.activeElement; 108 } 109 110 }; 111 112 const cells = gridElement.querySelectorAll('[role="gridcell"]'); 113 cells.forEach((cell) => { 114 cell.columnDefinition = { 115 columnDataKey: "", 116 cellInternalFocusQueue: true, 117 cellFocusTargetCallback: (cell) => { 118 return cell.children[0]; 119 } 120 } 121 cell.addEventListener( 122 "keydown", 123 (event) => { 124 if (event.target.role !== "gridcell" && (event.key === "ArrowRight" || event.key === "ArrowLeft")) { 125 event.stopPropagation(); 126 } 127 }, 128 { signal } 129 ); 130 }); 131 132 document.body.addEventListener('click', bodyClickHandler, { signal }); 133 document.body.addEventListener('mousedown', bodyClickHandler, { signal }); // Otherwise it seems strange that it doesn't go away until you release the mouse button 134 document.body.addEventListener('keydown', bodyKeyDownHandler, { signal }); 135 gridElement.addEventListener('keydown', keyDownHandler, { signal }); 136 137 return { 138 stop: () => { 139 controller.abort(); 140
141 const index = grids.findIndex(grid => grid.id === gridElement.id); 142 if (index > -1) { 143 const grid = grids[index]; 144 if (grid.resizeController) { 145 grid.resizeController.abort(); 146 } 147 grids.splice(index, 1); 148 } 149 } 150 }; 151} 152 153export function checkColumnPopupPosition(gridElement, selector) { 154 const colPopup = gridElement.querySelector(selector); 155 if (colPopup) { 156 const gridRect = gridElement.getBoundingClientRect(); 157 const popupRect = colPopup.getBoundingClientRect(); 158 const leftOverhang = Math.max(0, gridRect.left - popupRect.left); 159 const rightOverhang = Math.max(0, popupRect.right - gridRect.right); 160 if (leftOverhang || rightOverhang) { 161 const applyOffset = leftOverhang && rightOverhang ? (leftOverhang - rightOverhang) / 2 : (leftOverhang - rightOverhang); 162 colPopup.style.transform = `translateX(${applyOffset}px)`; 163 } 164 165 colPopup.style.visibility = 'visible'; 166 colPopup.scrollIntoViewIfNeeded(); 167 168 const autoFocusElem = colPopup.querySelector('[autofocus]'); 169 if (autoFocusElem) { 170 autoFocusElem.focus(); 171 } 172 } 173} 174 175export function enableColumnResizing(gridElement, resizeColumnOnAllRows = true, signal = null) { 176 const columns = []; 177 const headers = gridElement.querySelectorAll('.column-header.resizable'); 178 179 if (headers.length === 0) { 180 return; 181 } 182 183 const id = gridElement.id; 184 let grid = grids.find(g => g.id === id); 185 186 if (grid?.resizeController) { 187 grid.resizeController.abort(); 188 } 189 190 const localController = signal ? null : new AbortController(); 191 const effectiveSignal = signal ?? localController.signal; 192 193 const isGrid = gridElement.classList.contains('grid') 194 195 let tableHeight = gridElement.offsetHeight; 196 // rows have not been loaded yet, so we need to calculate the height 197 if (tableHeight < 70) { 198 // by getting the aria rowcount attribute 199 const rowCount = gridElement.getAttribute('aria-rowcount'); 200 if (rowCount) { 201 const rowHeight = gridElement.querySelector('thead tr th').offsetHeight; 202 // and multiply by the itemsize (== height of the header cells) 203 tableHeight = rowCount * rowHeight; 204 } 205 } 206 207 // Determine the height based on the resizeColumnOnAllRows parameter 208 let resizeHandleHeight = tableHeight; 209 if (!resizeColumnOnAllRows) { 210 // Only use the header height when resizeColumnOnAllRows is false 211 // Use the first header's height if available 212 resizeHandleHeight = headers.length > 0 ? (headers[0].offsetHeight - 14) : 32; // fallback to 32px if no headers 213 } 214 215 headers.forEach((header) => { 216 columns.push({ 217 header, 218 size: `${header.clientWidth}px`, 219 }); 220 221 // remove any previously created divs 222 const resizedivs = header.querySelectorAll(".actual-resize-handle"); 223 resizedivs.forEach(div => div.remove()); 224 225 // Get the top of the first resize-handle 226 const resizeTop = header.querySelector('.resize-handle').offsetTop; 227 228 // add a new resize div 229 const div = createDiv(resizeHandleHeight, resizeTop); 230 header.appendChild(div); 231 setListeners(div, effectiveSignal); 232 }); 233 234 let initialWidths; 235 if (gridElement.style.gridTemplateColumns) { 236 initialWidths = gridElement.style.gridTemplateColumns; 237 } else { 238 initialWidths = columns.map(({ size }) => size).join(' '); 239 240 if (isGrid) { 241 gridElement.style.gridTemplateColumns = initialWidths; 242 } 243 } 244 245 if (!grid) { 246 grids.push({ 247 id, 248 columns, 249 initialWidths, 250 resizeController: signal ? undefined : localController, 251 }); 252 } else { 253 const columnsChanged = grid.columns.length !== columns.length; 254 grid.columns = columns; 255 if (columnsChanged) { 256 grid.initialWidths = initialWidths; 257 } 258 grid.resizeController = signal ? undefined : localController; 259 } 260 261 function setListeners(div, signal) { 262 let pageX, curCol, curColWidth; 263 264 const moveHandler = (e) => 265 requestAnimationFrame(() => { 266 gridElement.style.tableLayout = 'fixed'; 267 268 if (curCol) { 269 const isRTL = getComputedStyle(gridElement).direction === 'rtl'; 270 const diffX = isRTL ? pageX - e.pageX : e.pageX - pageX; 271 const column = columns.find(({ header }) => header === curCol); 272 273 column.size = parseInt(Math.max(parseInt(column.header.style.minWidth), curColWidth + diffX), 10) + 'px'; 274 275 columns.forEach((col) => { 276 if (col.size.startsWith('minmax')) { 277 col.size = parseInt(col.header.clientWidth, 10) + 'px'; 278 } 279 }); 280 281 if (isGrid) { 282 gridElement.style.gridTemplateColumns = columns 283 .map(({ size }) => size) 284 .join(' '); 285 } 286 else { 287 curCol.style.width = column.size; 288 } 289 } 290 }); 291 292 const upHandler = function () { 293 gridElement.removeEventListener('pointermove', moveHandler); 294 gridElement.removeEventListener('pointerup', upHandler); 295 296 curCol = undefined; 297 curColWidth = undefined; 298 pageX = undefined; 299 }; 300 301 div.addEventListener('pointerdown', function (e) { 302 curCol = e.target.parentElement; 303 pageX = e.pageX; 304 305 const padding = paddingDiff(curCol); 306 307 curColWidth = curCol.offsetWidth - padding; 308 309 gridElement.addEventListener('pointermove', moveHandler, { signal }); 310 gridElement.addEventListener('pointerup', upHandler, { signal }); 311 }, { signal }); 312 313 div.addEventListener('pointerover', function (e) { 314 e.target.style.borderInlineEnd = 'var(--fluent-data-grid-resize-handle-width) solid var(--fluent-data-grid-resize-handle-color)'; 315 e.target.previousElementSibling.style.visibility = 'hidden'; 316 }, { signal }); 317 318 div.addEventListener('pointerup', removeBorder, { signal }); 319 div.addEventListener('pointercancel', removeBorder, { signal }); 320 div.addEventListener('pointerleave', removeBorder, { signal }); 321 } 322 323 function createDiv(height, top) { 324 const div = document.createElement('div'); 325 div.className = "actual-resize-handle"; 326 div.style.top = top + 'px'; 327 div.style.position = 'absolute'; 328 div.style.cursor = 'col-resize'; 329 div.style.userSelect = 'none'; 330 div.style.height = (height - 4) + 'px'; 331 div.style.width = '6px'; 332 div.style.opacity = 'var(--fluent-data-grid-header-opacity)' 333 div.style.insetInlineEnd = '0'; 334 335 return div; 336 } 337 338 function paddingDiff(col) { 339 if (getStyleVal(col, 'box-sizing') === 'border-box') { 340 return 0; 341 } 342 343 const padLeft = getStyleVal(col, 'padding-left'); 344 const padRight = getStyleVal(col, 'padding-right'); 345 return parseInt(padLeft) + parseInt(padRight); 346 } 347 348 function getStyleVal(elm, css) { 349 return window.getComputedStyle(elm, null).getPropertyValue(css); 350 } 351 352 function removeBorder(e) { 353 e.target.style.borderInlineEnd = ''; 354 e.target.previousElementSibling.style.visibility = 'visible'; 355 } 356} 357 358 359 360export function resetColumnWidths(gridElement) { 361 const isGrid = gridElement.classList.contains('grid'); 362 const grid = grids.find(({ id }) => id === gridElement.id); 363 if (!grid) { 364 return; 365 } 366 367 if (isGrid) { 368 gridElement.style.gridTemplateColumns = grid.initialWidths; 369 370 // Force browser to recalculate so we can get accurate computed widths 371 const resolvedWidths = window.getComputedStyle(gridElement).gridTemplateColumns.split(' '); 372 373 grid.columns.forEach((column, index) => { 374 column.size = resolvedWidths[index]; 375 }); 376 } else { 377 const columnsWidths = grid.initialWidths.split(' '); 378 grid.columns.forEach((column, index) => { 379 column.size = columnsWidths[index]; 380 column.header.style.width = column.size; 381 }); 382 } 383 384 gridElement.dispatchEvent( 385 new CustomEvent('closecolumnresize', { bubbles: true }) 386 ); 387 gridElement.focus(); 388} 389 390export function resizeColumnDiscrete(gridElement, column, change) { 391 const isGrid = gridElement.classList.contains('grid'); 392 const columns = []; 393 let headerBeingResized; 394 395 if (!column) { 396 const targetElement = document.activeElement.parentElement.parentElement.parentElement.parentElement; 397 if (!(targetElement.classList.contains("column-header") && targetElement.classList.contains("resizable"))) { 398 return; 399 } 400 headerBeingResized = targetElement; 401 } 402 else { 403 headerBeingResized = gridElement.querySelector('.column-header[col-index="' + column + '"]'); 404 } 405 406 407 grids.find(({ id }) => id === gridElement.id).columns.forEach(column => { 408 if (column.header === headerBeingResized) { 409 const width = headerBeingResized.offsetWidth + change; //.getBoundingClientRect().width + change; 410 411 if (change < 0) { 412 column.size = Math.max(parseInt(column.header.style.minWidth), width) + 'px'; 413 } 414 else { 415 column.size = width + 'px'; 416 } 417 column.header.style.width = column.size; 418 } 419 if (isGrid) { 420 // for grid we need to recalculate all columns that are minmax 421 if (column.size.startsWith('minmax')) { 422 column.size = parseInt(column.header.clientWidth, 10) + 'px'; 423 } 424 columns.push(column.size); 425 } 426 });
427 428 if (isGrid) { 429 gridElement.style.gridTemplateColumns = columns.join(' '); 430 } 431} 432 433export function resizeColumnExact(gridElement, column, width) { 434 const isGrid = gridElement.classList.contains('grid'); 435 const columns = []; 436 let headerBeingResized = gridElement.querySelector('.column-header[col-index="' + column + '"]'); 437 438 if (!headerBeingResized) { 439 return; 440 } 441 442 grids.find(({ id }) => id === gridElement.id).columns.forEach(column => { 443 if (column.header === headerBeingResized) { 444 column.size = Math.max(parseInt(column.header.style.minWidth), width) + 'px'; 445 column.header.style.width = column.size; 446 } 447 if (isGrid) { 448 // for grid we need to recalculate all columns that are minmax 449 if (column.size.startsWith('minmax')) { 450 column.size = parseInt(column.header.clientWidth, 10) + 'px'; 451 } 452 column.header.style.width = column.size; 453 columns.push(column.size); 454 } 455 }); 456 457 if (isGrid) { 458 gridElement.style.gridTemplateColumns = columns.join(' '); 459 } 460 461 gridElement.dispatchEvent(new CustomEvent('closecolumnresize', { bubbles: true })); 462 gridElement.focus(); 463} 464 465export function autoFitGridColumns(gridElement, columnCount) { 466 let gridTemplateColumns = ''; 467 468 for (var i = 0; i < columnCount; i++) { 469 const columnWidths = Array 470 .from(gridElement.querySelectorAll(`[col-index="${i + 1}"]`)) 471 .flatMap((x) => x.offsetWidth); 472 473 const maxColumnWidth = Math.max(...columnWidths); 474 475 gridTemplateColumns += ` ${maxColumnWidth}px`; 476 } 477 478 gridElement.style.gridTemplateColumns = gridTemplateColumns; 479 gridElement.classList.remove("auto-fit"); 480 481 const grid = grids.find(grid => grid.id === gridElement.id); 482 if (grid) { 483 grid.initialWidths = gridTemplateColumns; 484 } 485} 486 487function calculateVisibleRows(gridElement, rowHeight) { 488 if (rowHeight <= 0) { 489 return 0; 490 } 491 492 const gridContainer = gridElement.parentElement; 493 494 if (!gridContainer) { 495 return 0; 496 } 497 498 const availableHeight = gridContainer?.clientHeight || window.visualViewport?.height || window.innerHeight; 499 500 const visibleRows = Math.max(Math.floor(availableHeight / rowHeight), 1); 501 return visibleRows; 502} 503 504export function dynamicItemsPerPage(gridElement, dotNetObject, rowSize) { 505 const observer = new ResizeObserver(() => { 506 const visibleRows = calculateVisibleRows(gridElement, rowSize) 507 dotNetObject.invokeMethodAsync('UpdateItemsPerPageAsync', visibleRows) 508 .catch(err => console.error("Error invoking Blazor method:", err)); 509 }); 510 511 const targetElement = gridElement.parentElement; 512 if (targetElement) { 513 observer.observe(targetElement); 514 } 515}
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.