@open-grid/svelte-ui 0.1.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.
@@ -0,0 +1,2032 @@
1
+ <script lang="ts">
2
+ import type {
3
+ CellContext,
4
+ CellCoordinate,
5
+ CellFillOptions,
6
+ ClipboardPasteOptions,
7
+ ClipboardPasteResult,
8
+ Column,
9
+ ColumnId,
10
+ ColumnLayout,
11
+ ColumnSizingState,
12
+ Grid,
13
+ GridOptions,
14
+ Header,
15
+ HeaderContext,
16
+ HeaderGroup,
17
+ Row,
18
+ } from "@open-grid/core";
19
+ import {
20
+ addPointerMoveUpCancelListeners,
21
+ addPointerMoveUpListeners,
22
+ applyResizeObserverMeasuredSizes,
23
+ createClickSuppressionController,
24
+ createResizeObserver,
25
+ disconnectResizeObserver,
26
+ GROUPING_PANEL_EMPTY_MESSAGE,
27
+ HEADER_ACTION_MENU_TRIGGER_TEXT,
28
+ getCellEditorProps,
29
+ getCellEditorKeyboardAction,
30
+ getCellEditorOptionProps,
31
+ getCellEditorOptionText,
32
+ getCellProps,
33
+ getCellDisplayText,
34
+ getCellEditText,
35
+ getCellEditValidationMessage,
36
+ getCellFillHandleProps,
37
+ getCellFillHandleVisible,
38
+ getCellLayoutProps,
39
+ getCellValidationMessageProps,
40
+ getCanApplyColumnResize,
41
+ getCanCancelCellEdit,
42
+ getCanContinuePointerDrag,
43
+ getCanEndPointerDrag,
44
+ getCanRestoreFocusedCell,
45
+ getShouldCancelFocusedCellRestoreOnFrameSync,
46
+ getCanRunGridKeyboardShortcut,
47
+ getCanStartColumnResize,
48
+ getCanStartCellPointerDrag,
49
+ getCanStartCellEdit,
50
+ getCanStartFocusedCellEdit,
51
+ getCanToggleRowSelection,
52
+ focusCellEditorElement,
53
+ focusElement,
54
+ getColumnById,
55
+ getColumnFilterCellProps,
56
+ getColumnFilterInputProps,
57
+ getColumnFilterText,
58
+ getColumnIndexById,
59
+ getColumnLayoutById,
60
+ getColumnLayoutTotalWidth,
61
+ getColumnHeaderText,
62
+ getColumnVisibilityCheckboxProps,
63
+ getColumnVisibilityControlsProps,
64
+ getColumnVisibilityEmptyText,
65
+ getColumnVisibilityListProps,
66
+ getColumnVisibilityResetButtonProps,
67
+ getColumnVisibilityResetButtonText,
68
+ getColumnVisibilitySearchInputProps,
69
+ getColumnVisibilityStatusProps,
70
+ getColumnVisibilityStatusText,
71
+ getColumnVisibilitySummaryProps,
72
+ getColumnVisibilitySummaryText,
73
+ getFilteredColumnVisibilityColumns,
74
+ getDensityButtonProps,
75
+ getDensityButtonText,
76
+ getDensityControlsProps,
77
+ getColumnPinningButtonProps,
78
+ getColumnPinningButtonText,
79
+ getColumnPinningControlsProps,
80
+ getColumnResizeFinalSize,
81
+ getColumnResizeHandleProps,
82
+ getColumnResizeKeyboardSize,
83
+ getColumnResizePointerSize,
84
+ getColumnResizeStartSize,
85
+ getColumnSpacerProps,
86
+ getElementOffsetBlockSize,
87
+ getGridBodyProps,
88
+ getGridBodyRowIndexOffset,
89
+ getGridEmptyCellProps,
90
+ getGridEmptyRowProps,
91
+ getGridErrorOverlayProps,
92
+ getGridErrorRetryButtonProps,
93
+ getGridErrorRetryButtonText,
94
+ getGridErrorText,
95
+ getFocusedRowSelectionTarget,
96
+ getGridHeaderProps,
97
+ getGridHeaderRowProps,
98
+ getGridKeyboardEditAction,
99
+ getGridKeyboardFocusMove,
100
+ getGridKeyboardShortcutAction,
101
+ getGridLoadingOverlayProps,
102
+ getGridLoadingText,
103
+ getGridProps,
104
+ getGridDensityProps,
105
+ getGridDensityRowHeight,
106
+ getNextColumnFilters,
107
+ getPaginationButtonProps,
108
+ getPaginationButtonText,
109
+ getPaginationPageSizeOptions,
110
+ getPaginationPageSizeOptionText,
111
+ getPaginationPageSizeSelectProps,
112
+ getPaginationPageText,
113
+ getPaginationProps,
114
+ getPaginationStatusProps,
115
+ getQuickFilterClearButtonProps,
116
+ getQuickFilterClearButtonText,
117
+ getQuickFilterInputProps,
118
+ getQuickFilterProps,
119
+ getRowSelectionCheckboxProps,
120
+ getRowSelectionCheckboxText,
121
+ getRowSelectionClearButtonProps,
122
+ getRowSelectionClearButtonText,
123
+ getRowSelectionControlsProps,
124
+ getRowSelectionStatusProps,
125
+ getRowSelectionStatusText,
126
+ getGroupCellIndentStyleText,
127
+ getGroupRowCountText,
128
+ getGroupRowLabel,
129
+ getGroupingPanelChipProps,
130
+ getGroupingPanelMoveButtonProps,
131
+ getGroupingPanelPlaceholderProps,
132
+ getGroupingPanelProps,
133
+ getGroupingPanelRemoveButtonProps,
134
+ getHeaderActionMenuDefaultItemDescriptors,
135
+ getHeaderActionMenuFocusTarget,
136
+ getHeaderActionMenuId,
137
+ getHeaderActionMenuTriggerId,
138
+ getHeaderActionMenuCustomItemProps,
139
+ getHeaderActionMenuCustomItemUserProps,
140
+ getHeaderActionMenuItemProps,
141
+ getHeaderActionMenuKeyboardAction,
142
+ getHeaderActionMenuLabelProps,
143
+ getHeaderActionMenuProps,
144
+ getHeaderActionMenuSeparatorProps,
145
+ getHeaderActionMenuTriggerProps,
146
+ getHeaderActionMenuTriggerFocusPosition,
147
+ getHeaderButtonProps,
148
+ getHeaderCellLayoutProps,
149
+ getHeaderCellProps,
150
+ getHeaderDragEndAction,
151
+ getHeaderKeyboardMoveDirection,
152
+ getHeaderPlaceholderCellProps,
153
+ getHeaderSortIndicatorProps,
154
+ getHeaderSortIndicatorText,
155
+ getFocusedCellScrollOptionsFromElement,
156
+ getInlineSizeStyleText,
157
+ getPinnedColumnOffsetStyleText,
158
+ getPointerCaptureTarget,
159
+ getScrollForFocusedCell,
160
+ getScrollFrameOptionsFromElement,
161
+ getShouldPreventEventDefault,
162
+ getVirtualBodyStyleText,
163
+ getVirtualizedInlineSizeStyleText,
164
+ getVirtualRowStyleText,
165
+ preventDefaultAndStopPropagation,
166
+ preventEventDefault,
167
+ stopEventPropagation,
168
+ isCellCoordinateEqual,
169
+ isPrimaryPointerButton,
170
+ getIsGroupLabelCell,
171
+ isHeaderActionMenuActionItem,
172
+ isHeaderActionMenuCloseAction,
173
+ isHeaderActionMenuCustomItem,
174
+ isHeaderActionMenuFocusAction,
175
+ isHeaderActionMenuItem,
176
+ isHeaderActionMenuLabelItem,
177
+ isHeaderActionMenuSeparatorItem,
178
+ isHeaderActionMenuTabCloseAction,
179
+ isHeaderDragGroupAction,
180
+ isHeaderDragMoveAction,
181
+ focusFocusedCellInGrid,
182
+ focusHeaderActionMenuItem,
183
+ focusHeaderActionMenuItemById,
184
+ focusHeaderActionMenuTriggerById,
185
+ resolveColumnVirtualizationOptions,
186
+ resolveRowVirtualizationOptions,
187
+ getRowExpansionSpacerProps,
188
+ getRowExpansionToggleProps,
189
+ getRowExpansionToggleText,
190
+ getRowById,
191
+ getRowLayoutProps,
192
+ getRowProps,
193
+ getResizeObserverEntryBlockSize,
194
+ getResizeObserverEntryInlineSize,
195
+ getMeasuredElementBlockSize,
196
+ getMeasuredElementBlockSizeFromRect,
197
+ getMeasuredElementInlineSize,
198
+ isGridInteractiveKeyboardEventTarget,
199
+ moveColumnToTarget,
200
+ moveGroupedColumn,
201
+ moveVisibleColumn,
202
+ observeElements,
203
+ parseCellEditValue,
204
+ readClipboardText,
205
+ removeObservedElement,
206
+ escapeGridHtml,
207
+ replaceObservedElement,
208
+ scrollElementToPosition,
209
+ setCheckboxIndeterminate,
210
+ setElementDatasetId,
211
+ tryReleasePointerCapture,
212
+ trySetPointerCapture,
213
+ updatePointerDragDidMovePastThreshold,
214
+ GRID_DENSITIES,
215
+ type ColumnVirtualizationPrimitiveOptions,
216
+ type GridDensity,
217
+ type ResolvedColumnVirtualizationOptions,
218
+ type ResolvedRowVirtualizationOptions,
219
+ type RowVirtualizationPrimitiveOptions,
220
+ writeClipboardText,
221
+ } from "@open-grid/primitives";
222
+ import { createGridStore } from "@open-grid/svelte";
223
+ import { createMeasuredSizeCache, createMeasuredSizeResolver, getColumnCellRenderItems, getColumnLayoutMeasurementSignature, getColumnRenderItems, getHeaderRenderItemKey, getHeaderRenderItems, getInitialScrollFrame, getMeasuredColumnLayoutFromCache, getScrollFrame, getSizedColumnLayout, getVirtualRowRange, getVirtualRowItems, isHeaderRenderSpacerItem, syncMeasuredColumnLayoutCache, type ColumnCellRenderItem, type ColumnRenderItem, type VirtualItem, type VirtualRange } from "@open-grid/virtual";
224
+ import { onDestroy, onMount, tick } from "svelte";
225
+
226
+ export type RowVirtualizationOptions = RowVirtualizationPrimitiveOptions;
227
+
228
+ export type ColumnVirtualizationOptions = ColumnVirtualizationPrimitiveOptions;
229
+
230
+ export interface HeaderActionMenuActionItem<TData = unknown> {
231
+ type?: "action";
232
+ id: string;
233
+ label: string;
234
+ disabled?: boolean;
235
+ onSelect: (context: HeaderActionMenuContext<TData>) => void;
236
+ }
237
+
238
+ export interface HeaderActionMenuLabelItem {
239
+ type: "label";
240
+ id: string;
241
+ label: string;
242
+ }
243
+
244
+ export interface HeaderActionMenuSeparatorItem {
245
+ type: "separator";
246
+ id: string;
247
+ label?: string;
248
+ }
249
+
250
+ export interface HeaderActionMenuCustomItem<TData = unknown> {
251
+ type: "custom";
252
+ id: string;
253
+ label?: string;
254
+ component: unknown;
255
+ props?: Record<string, unknown>;
256
+ }
257
+
258
+ export type HeaderActionMenuItem<TData = unknown> =
259
+ | HeaderActionMenuActionItem<TData>
260
+ | HeaderActionMenuLabelItem
261
+ | HeaderActionMenuSeparatorItem
262
+ | HeaderActionMenuCustomItem<TData>;
263
+
264
+ type HeaderActionMenuCustomComponent = any;
265
+
266
+ export interface HeaderActionMenuContext<TData = unknown> {
267
+ grid: Grid<TData>;
268
+ column: Column<TData, unknown>;
269
+ sortDirection: "asc" | "desc" | false;
270
+ pinningPosition: "left" | "right" | false;
271
+ isGrouped: boolean;
272
+ canMoveLeft: boolean;
273
+ canMoveRight: boolean;
274
+ defaultItems: Array<HeaderActionMenuActionItem<TData>>;
275
+ }
276
+
277
+ export type HeaderActionMenuItems<TData = unknown> = (context: HeaderActionMenuContext<TData>) => Array<HeaderActionMenuItem<TData> | null | false | undefined>;
278
+
279
+ export let ariaLabel = "Data grid";
280
+ export let options: GridOptions<unknown>;
281
+ export let emptyState: unknown = "No rows";
282
+ export let error = false;
283
+ export let errorState: unknown = getGridErrorText();
284
+ export let onRetry: (() => void) | undefined = undefined;
285
+ export let loading = false;
286
+ export let loadingState: unknown = getGridLoadingText();
287
+ export let onGridReady: ((grid: Grid<unknown>) => void | (() => void)) | undefined = undefined;
288
+ export let getRowClassName: ((row: Row<unknown>) => string | undefined) | undefined = undefined;
289
+ export let getHeaderClassName: ((context: HeaderContext<unknown, unknown>) => string | undefined) | undefined = undefined;
290
+ export let getCellClassName: ((context: CellContext<unknown, unknown>) => string | undefined) | undefined = undefined;
291
+ export let groupingPanel = false;
292
+ export let quickFilterControl = false;
293
+ export let rowSelectionControls = false;
294
+ export let columnVisibilityControls = false;
295
+ export let density: GridDensity | undefined = undefined;
296
+ export let defaultDensity: GridDensity | undefined = undefined;
297
+ export let densityControl = false;
298
+ export let onDensityChange: ((density: GridDensity) => void) | undefined = undefined;
299
+ export let columnFilterControls = false;
300
+ export let paginationControls = false;
301
+ export let pageSizeOptions: readonly number[] | undefined = undefined;
302
+ export let columnPinningControls = false;
303
+ export let headerActionMenu = false;
304
+ export let headerActionMenuItems: HeaderActionMenuItems<unknown> | undefined = undefined;
305
+ export let rowVirtualization: boolean | RowVirtualizationOptions = false;
306
+ export let columnVirtualization: boolean | ColumnVirtualizationOptions = false;
307
+ export let cellFillOptions: Omit<CellFillOptions, "sourceEvent"> | undefined = undefined;
308
+ export let clipboardPasteOptions: Omit<ClipboardPasteOptions, "sourceEvent"> | undefined = undefined;
309
+ export let onClipboardPaste: ((result: ClipboardPasteResult<unknown>) => void) | undefined = undefined;
310
+ export let style: string | undefined = undefined;
311
+ let className: string | undefined = undefined;
312
+ export { className as class };
313
+
314
+ const gridStore = createGridStore(options);
315
+ const { grid, state } = gridStore;
316
+ let scrollerElement: HTMLElement | null = null;
317
+ let bodyElement: HTMLElement | null = null;
318
+ let headerElement: HTMLElement | null = null;
319
+ let groupingPanelElement: HTMLElement | null = null;
320
+ let scrollFrame = getInitialScrollFrame();
321
+ let allRows: Row<unknown>[] = [];
322
+ let allColumns: Column<unknown, unknown>[] = [];
323
+ let visibleColumns: Column<unknown, unknown>[] = [];
324
+ let filteredVisibilityColumns: Column<unknown, unknown>[] = [];
325
+ let columnVisibilityQuery = "";
326
+ let uncontrolledDensity: GridDensity = defaultDensity ?? "standard";
327
+ let resolvedDensity: GridDensity = density ?? uncontrolledDensity;
328
+ let densitySizingEnabled = densityControl || density !== undefined || defaultDensity !== undefined;
329
+ let measuredDensitySignature = "";
330
+ let groupingColumns: Column<unknown, unknown>[] = [];
331
+ let columnLayout: ColumnLayout[] = [];
332
+ let sizedColumnLayout: ColumnLayout[] = [];
333
+ let measuredColumnLayout: ColumnLayout[] = [];
334
+ let layoutById = new Map<ColumnId, ColumnLayout>();
335
+ let totalMeasuredWidth = 0;
336
+ let rowVirtualOptions = resolveRowVirtualizationOptions(rowVirtualization);
337
+ let columnVirtualOptions = resolveColumnVirtualizationOptions(columnVirtualization);
338
+ const rowSizeCache = createMeasuredSizeCache();
339
+ let rowSizeVersion = rowSizeCache.version;
340
+ let rowSizeResolver: (index: number) => number = () => rowVirtualOptions.estimateRowHeight;
341
+ let rowRangeEstimate: number | ((index: number) => number) = rowVirtualOptions.estimateRowHeight;
342
+ let rowResizeObserver: ResizeObserver | null = null;
343
+ const observedRowElements = new Map<string, HTMLElement>();
344
+ const columnSizeCache = createMeasuredSizeCache();
345
+ let columnSizeVersion = columnSizeCache.version;
346
+ let columnResizeObserver: ResizeObserver | null = null;
347
+ const observedColumnElements = new Map<string, HTMLElement>();
348
+ let measuredCoreLayoutSignature = "";
349
+ let coreColumnLayoutSignature = "";
350
+ let virtualRange: VirtualRange | null = null;
351
+ let visibleRowItems: Array<{ row: Row<unknown>; rowIndex: number; virtualItem: VirtualItem | null }> = [];
352
+ let columnRenderItems: ColumnRenderItem[] = [];
353
+ type ResolvedColumnCellRenderItem = ColumnCellRenderItem & {
354
+ column: Column<unknown, unknown>;
355
+ columnIndex: number;
356
+ };
357
+
358
+ let columnCellRenderItems: ResolvedColumnCellRenderItem[] = [];
359
+ let simpleFocusedCellElement: HTMLElement | null = null;
360
+ let simpleCellRendering = false;
361
+ let deferDetailedCellRendering = false;
362
+ let editDraft = "";
363
+ let editValidationMessage: string | null = null;
364
+ let headerMenuColumnId: ColumnId | null = null;
365
+ let rangeDrag: { anchor: CellCoordinate; pointerId: number; didDrag: boolean } | null = null;
366
+ let fillDrag: { pointerId: number; target: CellCoordinate } | null = null;
367
+ let headerDrag: {
368
+ sourceColumnId: string;
369
+ targetColumnId: string;
370
+ pointerId: number;
371
+ startX: number;
372
+ startY: number;
373
+ didDrag: boolean;
374
+ } | null = null;
375
+ const cellClickSuppression = createClickSuppressionController();
376
+ const headerClickSuppression = createClickSuppressionController();
377
+ let shouldRestoreFocusedCell = false;
378
+ let focusRestoreInProgress = false;
379
+ let additionalHeaderRowCount = 0;
380
+ let bodyRowIndexOffset = 0;
381
+ let gridProps = getGridProps(grid, { ariaLabel, error, loading });
382
+ let resolvedPageSizeOptions = getPaginationPageSizeOptions(pageSizeOptions, grid.getState().pagination.pageSize);
383
+ let paginationPageText = getPaginationPageText(grid);
384
+ let canPreviousPage = grid.getCanPreviousPage();
385
+ let canNextPage = grid.getCanNextPage();
386
+ let allPageRowsSelected = rowSelectionControls ? grid.getIsAllPageRowsSelected() : false;
387
+ let somePageRowsSelected = rowSelectionControls ? grid.getIsSomePageRowsSelected() : false;
388
+ let selectedRowCount = rowSelectionControls ? grid.getSelectedRowModel().rows.length : 0;
389
+ let selectionCheckboxElement: HTMLInputElement | null = null;
390
+
391
+ $: gridStore.setOptions(options, { notify: false });
392
+ $: allRows = ($state, grid.getRowModel().rows);
393
+ $: additionalHeaderRowCount = columnFilterControls ? 1 : 0;
394
+ $: resolvedDensity = density ?? uncontrolledDensity;
395
+ $: densitySizingEnabled = densityControl || density !== undefined || defaultDensity !== undefined;
396
+ $: {
397
+ const densitySignature = `${densitySizingEnabled}:${resolvedDensity}`;
398
+ if (densitySignature !== measuredDensitySignature) {
399
+ measuredDensitySignature = densitySignature;
400
+ if (rowSizeCache.clear()) {
401
+ rowSizeVersion = rowSizeCache.version;
402
+ }
403
+ }
404
+ }
405
+ $: gridProps = ($state, additionalHeaderRowCount, ariaLabel, error, loading, resolvedDensity, {
406
+ ...getGridProps(grid, { additionalHeaderRowCount, ariaLabel, error, loading }),
407
+ ...(densitySizingEnabled ? getGridDensityProps(resolvedDensity) : {}),
408
+ });
409
+ $: resolvedPageSizeOptions = ($state.pagination.pageSize, pageSizeOptions, getPaginationPageSizeOptions(pageSizeOptions, $state.pagination.pageSize));
410
+ $: paginationPageText = ($state.pagination, getPaginationPageText(grid));
411
+ $: canPreviousPage = ($state.pagination.pageIndex, grid.getCanPreviousPage());
412
+ $: canNextPage = ($state.pagination, grid.getCanNextPage());
413
+ $: allPageRowsSelected = ($state.rowSelection, allRows, rowSelectionControls ? grid.getIsAllPageRowsSelected() : false);
414
+ $: somePageRowsSelected = ($state.rowSelection, allRows, rowSelectionControls ? grid.getIsSomePageRowsSelected() : false);
415
+ $: selectedRowCount = ($state.rowSelection, allRows, rowSelectionControls ? grid.getSelectedRowModel().rows.length : 0);
416
+ $: setCheckboxIndeterminate(selectionCheckboxElement, somePageRowsSelected);
417
+ $: bodyRowIndexOffset = ($state, additionalHeaderRowCount, getGridBodyRowIndexOffset(grid, { additionalHeaderRowCount }));
418
+ $: allColumns = ($state, grid.getAllLeafColumns());
419
+ $: visibleColumns = ($state, grid.getVisibleLeafColumns());
420
+ $: filteredVisibilityColumns = getFilteredColumnVisibilityColumns(allColumns, columnVisibilityQuery);
421
+ $: groupingColumns = $state.grouping.flatMap((columnId) => {
422
+ const column = getColumnById(columnId, grid.getAllLeafColumns());
423
+ return column ? [column] : [];
424
+ });
425
+ $: columnLayout = ($state.columnSizing, $state.columnOrder, $state.columnPinning, grid.getColumnLayout());
426
+ $: rowVirtualOptions = resolveRowVirtualizationOptions(rowVirtualization);
427
+ $: columnVirtualOptions = resolveColumnVirtualizationOptions(columnVirtualization);
428
+ $: sizedColumnLayout = getSizedColumnLayout(columnLayout, {
429
+ getColumnSize: (columnId) => grid.getColumnSize(columnId),
430
+ getFallbackSize: (columnId) => getColumnById(columnId, visibleColumns)?.getSize(),
431
+ });
432
+ $: coreColumnLayoutSignature = getColumnLayoutMeasurementSignature(sizedColumnLayout);
433
+ $: {
434
+ const syncResult = syncMeasuredColumnLayoutCache({
435
+ cache: columnSizeCache,
436
+ currentSignature: measuredCoreLayoutSignature,
437
+ enabled: columnVirtualOptions.enabled && columnVirtualOptions.measureColumnWidth,
438
+ nextSignature: coreColumnLayoutSignature,
439
+ });
440
+
441
+ measuredCoreLayoutSignature = syncResult.signature;
442
+
443
+ if (syncResult.changed) {
444
+ columnSizeVersion = columnSizeCache.version;
445
+ }
446
+ }
447
+ $: {
448
+ columnSizeVersion;
449
+ measuredColumnLayout = getMeasuredColumnLayoutFromCache(sizedColumnLayout, {
450
+ cache: columnSizeCache,
451
+ enabled: columnVirtualOptions.enabled && columnVirtualOptions.measureColumnWidth && measuredCoreLayoutSignature === coreColumnLayoutSignature,
452
+ });
453
+ }
454
+ $: layoutById = getColumnLayoutById(measuredColumnLayout);
455
+ $: totalMeasuredWidth = getColumnLayoutTotalWidth(measuredColumnLayout);
456
+ $: resolvedRowEstimate = densitySizingEnabled
457
+ ? getGridDensityRowHeight(resolvedDensity)
458
+ : rowVirtualOptions.estimateRowHeight;
459
+ $: rowSizeResolver = rowVirtualOptions.measureRowHeight
460
+ ? createMeasuredSizeResolver(rowSizeCache, (index) => allRows[index]?.id ?? index, resolvedRowEstimate)
461
+ : () => resolvedRowEstimate;
462
+ $: rowRangeEstimate = rowVirtualOptions.measureRowHeight ? rowSizeResolver : resolvedRowEstimate;
463
+ $: if (rowVirtualOptions.measureRowHeight && rowSizeCache.prune(allRows.map((row) => row.id))) {
464
+ rowSizeVersion = rowSizeCache.version;
465
+ }
466
+ $: if (columnVirtualOptions.enabled && columnVirtualOptions.measureColumnWidth && columnSizeCache.prune(columnLayout.map((column) => column.id))) {
467
+ columnSizeVersion = columnSizeCache.version;
468
+ }
469
+ $: virtualRange = getVirtualRowRange({
470
+ count: allRows.length,
471
+ scrollFrame,
472
+ enabled: rowVirtualOptions.enabled,
473
+ estimateSize: (rowSizeVersion, rowRangeEstimate),
474
+ overscan: rowVirtualOptions.overscan,
475
+ });
476
+ $: visibleRowItems = getVirtualRowItems(allRows, virtualRange);
477
+ $: columnRenderItems = getColumnRenderItems(measuredColumnLayout, scrollFrame, columnVirtualOptions);
478
+ $: columnCellRenderItems = getColumnCellRenderItems(columnRenderItems).map((item) => {
479
+ const column = getColumnById(item.layout.id, visibleColumns)!;
480
+
481
+ return {
482
+ ...item,
483
+ column,
484
+ columnIndex: getColumnIndexById(column.id, visibleColumns),
485
+ };
486
+ });
487
+ $: simpleCellRendering = !$state.editingCell
488
+ && (!$state.cellSelectionRange || deferDetailedCellRendering)
489
+ && !visibleRowItems.some(({ row }) =>
490
+ row.getIsGrouped() || row.getIsGroupFooter() || row.depth > 0 || row.getCanExpand(),
491
+ );
492
+ $: {
493
+ const focusedCell = $state.focusedCell;
494
+ const focusedRowIndex = focusedCell
495
+ ? visibleRowItems.findIndex(({ row }) => row.id === focusedCell.rowId)
496
+ : -1;
497
+ const focusedColumnIndex = focusedCell
498
+ ? columnCellRenderItems.findIndex(({ column }) => column.id === focusedCell.columnId)
499
+ : -1;
500
+ void tick().then(() => syncSimpleFocusedCell(
501
+ bodyElement,
502
+ simpleCellRendering,
503
+ focusedRowIndex,
504
+ focusedColumnIndex,
505
+ ));
506
+ }
507
+
508
+ onMount(() => {
509
+ const cleanupGridReady = onGridReady?.(grid);
510
+ syncFrame();
511
+
512
+ const updateHeaderDrag = (event: PointerEvent) => {
513
+ if (!getCanContinuePointerDrag(headerDrag, event)) {
514
+ return;
515
+ }
516
+
517
+ updatePointerDragDidMovePastThreshold(headerDrag, event);
518
+ };
519
+
520
+ const clearPointerDrags = (event: PointerEvent) => {
521
+ const endingHeaderDrag = getCanEndPointerDrag(headerDrag, event) ? headerDrag : null;
522
+
523
+ if (rangeDrag?.didDrag) {
524
+ cellClickSuppression.suppress();
525
+ }
526
+
527
+ if (fillDrag) {
528
+ grid.fillCellRange(fillDrag.target, { ...cellFillOptions, sourceEvent: event });
529
+ cellClickSuppression.suppress();
530
+ }
531
+
532
+ const headerDragAction = endingHeaderDrag
533
+ ? getHeaderDragEndAction(endingHeaderDrag, {
534
+ event,
535
+ groupingPanel: Boolean(groupingPanel),
536
+ groupingPanelElement,
537
+ columns: grid.getAllLeafColumns(),
538
+ })
539
+ : null;
540
+
541
+ if (isHeaderDragGroupAction(headerDragAction)) {
542
+ grid.toggleColumnGrouping(headerDragAction.columnId, true);
543
+ } else if (isHeaderDragMoveAction(headerDragAction)) {
544
+ moveColumnToTarget(grid, headerDragAction.sourceColumnId, headerDragAction.targetColumnId);
545
+ headerClickSuppression.suppress();
546
+ }
547
+
548
+ if (rangeDrag && !rangeDrag.didDrag) {
549
+ scheduleDetailedCellRendering();
550
+ }
551
+ rangeDrag = null;
552
+ fillDrag = null;
553
+ if (endingHeaderDrag) {
554
+ headerDrag = null;
555
+ }
556
+ };
557
+
558
+ const cleanupPointerListeners = addPointerMoveUpCancelListeners({
559
+ move: updateHeaderDrag,
560
+ up: clearPointerDrags,
561
+ cancel: clearPointerDrags,
562
+ });
563
+
564
+ rowResizeObserver = createResizeObserver((entries) => {
565
+ if (
566
+ applyResizeObserverMeasuredSizes(entries, {
567
+ cache: rowSizeCache,
568
+ datasetKey: "rowId",
569
+ getSize: getResizeObserverEntryBlockSize,
570
+ })
571
+ ) {
572
+ rowSizeVersion = rowSizeCache.version;
573
+ }
574
+ });
575
+
576
+ if (rowResizeObserver) {
577
+ observeElements(rowResizeObserver, observedRowElements.values());
578
+ }
579
+
580
+ columnResizeObserver = createResizeObserver((entries) => {
581
+ if (
582
+ applyResizeObserverMeasuredSizes(entries, {
583
+ cache: columnSizeCache,
584
+ datasetKey: "columnId",
585
+ getSize: getResizeObserverEntryInlineSize,
586
+ })
587
+ ) {
588
+ columnSizeVersion = columnSizeCache.version;
589
+ }
590
+ });
591
+
592
+ if (columnResizeObserver) {
593
+ observeElements(columnResizeObserver, observedColumnElements.values());
594
+ }
595
+
596
+ return () => {
597
+ cleanupGridReady?.();
598
+ cleanupPointerListeners();
599
+ disconnectResizeObserver(rowResizeObserver);
600
+ rowResizeObserver = null;
601
+ observedRowElements.clear();
602
+ disconnectResizeObserver(columnResizeObserver);
603
+ columnResizeObserver = null;
604
+ observedColumnElements.clear();
605
+ };
606
+ });
607
+
608
+ onDestroy(() => {
609
+ gridStore.dispose();
610
+ });
611
+
612
+ const getResizeHandleProps = (
613
+ column: Column<unknown, unknown>,
614
+ layout: ColumnLayout | undefined,
615
+ _columnSizing: ColumnSizingState,
616
+ ) =>
617
+ getColumnResizeHandleProps(column, {
618
+ valueNow: layout?.size ?? grid.getColumnSize(column.id) ?? column.getSize(),
619
+ });
620
+
621
+ const getHeaderLabel = (header: Header<unknown>) => {
622
+ return getColumnHeaderText(header.column);
623
+ };
624
+ const getCanUseCompactHeaderRendering = (
625
+ headerGroup: HeaderGroup<unknown>,
626
+ pinningControls: boolean,
627
+ actionMenu: boolean,
628
+ ) => !pinningControls && !actionMenu && headerGroup.headers.every((header) =>
629
+ !header.isPlaceholder && header.column.columns.length === 0,
630
+ );
631
+ const getColumnLabel = (column: Column<unknown, unknown>) => {
632
+ return getColumnHeaderText(column);
633
+ };
634
+ const handleColumnFilterInput = (column: Column<unknown, unknown>, event: Event) => {
635
+ const value = event.currentTarget instanceof HTMLInputElement ? event.currentTarget.value : "";
636
+ grid.setState((previous) => ({
637
+ ...previous,
638
+ columnFilters: getNextColumnFilters(previous.columnFilters, column.id, value),
639
+ pagination: { ...previous.pagination, pageIndex: 0 },
640
+ }));
641
+ };
642
+ const resetPaginationScroll = () => {
643
+ if (scrollerElement) {
644
+ scrollElementToPosition(scrollerElement, { scrollTop: 0, scrollLeft: scrollerElement.scrollLeft });
645
+ syncFrame();
646
+ }
647
+ };
648
+ const setQuickFilter = (value: string) => {
649
+ grid.setState((previous) => ({
650
+ ...previous,
651
+ globalFilter: value,
652
+ pagination: { ...previous.pagination, pageIndex: 0 },
653
+ }));
654
+ resetPaginationScroll();
655
+ };
656
+ const handleQuickFilterInput = (event: Event) => {
657
+ setQuickFilter(event.currentTarget instanceof HTMLInputElement ? event.currentTarget.value : "");
658
+ };
659
+ const runPaginationAction = (action: () => void) => {
660
+ action();
661
+ resetPaginationScroll();
662
+ };
663
+
664
+ const setDensity = (nextDensity: GridDensity) => {
665
+ if (density === undefined) {
666
+ uncontrolledDensity = nextDensity;
667
+ }
668
+ onDensityChange?.(nextDensity);
669
+ };
670
+ const handlePageSizeChange = (event: Event) => {
671
+ const pageSize = event.currentTarget instanceof HTMLSelectElement ? Number(event.currentTarget.value) : 1;
672
+ grid.setPageSize(pageSize);
673
+ resetPaginationScroll();
674
+ };
675
+
676
+ const getCellValue = (row: Row<unknown>, column: Column<unknown, unknown>) => {
677
+ const value = row.getValue(column.id);
678
+ return getCellDisplayText(value);
679
+ };
680
+ const getGroupLabel = (row: Row<unknown>, column: Column<unknown, unknown>) =>
681
+ getGroupRowLabel(row, column, { fallback: getCellValue(row, column) });
682
+ const getColumnStyle = (
683
+ size: number,
684
+ layout: ColumnLayout | undefined,
685
+ _columnSizing: ColumnSizingState,
686
+ beforeSpacerSize = 0,
687
+ afterSpacerSize = 0,
688
+ ) => {
689
+ const declarations = [`flex: 0 0 ${size}px`, `width: ${size}px`];
690
+ if (beforeSpacerSize > 0) declarations.push(`margin-left: ${beforeSpacerSize}px`);
691
+ if (afterSpacerSize > 0) declarations.push(`margin-right: ${afterSpacerSize}px`);
692
+ const pinnedOffsetStyle = getPinnedColumnOffsetStyleText(layout);
693
+
694
+ if (pinnedOffsetStyle) {
695
+ declarations.push(pinnedOffsetStyle);
696
+ }
697
+
698
+ return declarations.join("; ");
699
+ };
700
+ const getSimpleCellsHtml = (
701
+ row: Row<unknown>,
702
+ rowIndex: number,
703
+ items: readonly ResolvedColumnCellRenderItem[],
704
+ columnSizing: ColumnSizingState,
705
+ rowIndexOffset: number,
706
+ cellClassName: typeof getCellClassName,
707
+ ) => items.map((item) => {
708
+ const { column } = item;
709
+ const pinned = column.getIsPinned();
710
+ const pinnedEdge = item.layout.pinnedEdge;
711
+ const className = ["og-grid__cell", cellClassName?.({ grid, row, column, value: row.getValue(column.id) })].filter(Boolean).join(" ");
712
+ const style = getColumnStyle(item.layout.size, item.layout, columnSizing, item.beforeSpacerSize, item.afterSpacerSize);
713
+
714
+ return `<div role="gridcell" aria-colindex="${item.columnIndex + 1}" data-row-id="${escapeGridHtml(row.id)}" data-column-id="${escapeGridHtml(column.id)}"${pinned ? ` data-pinned="${pinned}"` : ""} tabindex="-1"${pinnedEdge ? ` data-pinned-edge="${pinnedEdge}"` : ""} class="${escapeGridHtml(className)}" style="${escapeGridHtml(style)}">${escapeGridHtml(getCellValue(row, column))}</div>`;
715
+ }).join("");
716
+ const syncSimpleFocusedCell = (
717
+ body: HTMLElement | null,
718
+ enabled: boolean,
719
+ rowIndex: number,
720
+ columnIndex: number,
721
+ ) => {
722
+ if (!body) {
723
+ simpleFocusedCellElement = null;
724
+ return;
725
+ }
726
+
727
+ if (!enabled) {
728
+ simpleFocusedCellElement = null;
729
+ return;
730
+ }
731
+
732
+ const previousFocusedCell = body.contains(simpleFocusedCellElement) ? simpleFocusedCellElement : null;
733
+ const nextFocusedCell = rowIndex < 0 || columnIndex < 0
734
+ ? null
735
+ : body.children[rowIndex]?.children[columnIndex] as HTMLElement | undefined ?? null;
736
+
737
+ if (previousFocusedCell && previousFocusedCell !== nextFocusedCell) {
738
+ previousFocusedCell.tabIndex = -1;
739
+ previousFocusedCell.removeAttribute("data-focused");
740
+ }
741
+ if (nextFocusedCell) {
742
+ nextFocusedCell.tabIndex = 0;
743
+ nextFocusedCell.setAttribute("data-focused", "true");
744
+ }
745
+ simpleFocusedCellElement = nextFocusedCell;
746
+ };
747
+ const syncFrame = () => {
748
+ if (!scrollerElement) {
749
+ return;
750
+ }
751
+
752
+ if (getShouldCancelFocusedCellRestoreOnFrameSync({ inProgress: focusRestoreInProgress })) {
753
+ shouldRestoreFocusedCell = false;
754
+ }
755
+
756
+ scrollFrame = getScrollFrame(
757
+ getScrollFrameOptionsFromElement(scrollerElement, {
758
+ stickyTopOffset: getElementOffsetBlockSize(headerElement, 42),
759
+ }),
760
+ );
761
+ };
762
+
763
+ const restoreFocusedCell = async (
764
+ focusedCell: CellCoordinate | null,
765
+ _rowSizeVersion: number,
766
+ _scrollTop: number,
767
+ _scrollLeft: number,
768
+ layout: readonly ColumnLayout[],
769
+ rows: readonly Row<unknown>[],
770
+ currentRowVirtualOptions: ResolvedRowVirtualizationOptions,
771
+ currentColumnVirtualOptions: ResolvedColumnVirtualizationOptions,
772
+ ) => {
773
+ if (
774
+ !getCanRestoreFocusedCell({
775
+ focusedCell,
776
+ scroller: scrollerElement,
777
+ shouldRestore: shouldRestoreFocusedCell,
778
+ inProgress: focusRestoreInProgress,
779
+ })
780
+ ) {
781
+ return;
782
+ }
783
+
784
+ if (!focusedCell || !scrollerElement) {
785
+ return;
786
+ }
787
+
788
+ focusRestoreInProgress = true;
789
+
790
+ try {
791
+ for (let attempt = 0; attempt < 4; attempt += 1) {
792
+ const nextScroll = getScrollForFocusedCell(
793
+ getFocusedCellScrollOptionsFromElement(scrollerElement, {
794
+ focusedCell,
795
+ layout,
796
+ rows,
797
+ headerHeight: getElementOffsetBlockSize(headerElement, 42),
798
+ getRowSize: rowSizeResolver,
799
+ rowVirtualOptions: currentRowVirtualOptions,
800
+ columnVirtualOptions: currentColumnVirtualOptions,
801
+ }),
802
+ );
803
+
804
+ if (nextScroll) {
805
+ scrollElementToPosition(scrollerElement, nextScroll);
806
+ scrollFrame = getScrollFrame(
807
+ getScrollFrameOptionsFromElement(scrollerElement, {
808
+ scrollTop: nextScroll.scrollTop,
809
+ scrollLeft: nextScroll.scrollLeft,
810
+ stickyTopOffset: getElementOffsetBlockSize(headerElement, 42),
811
+ }),
812
+ );
813
+ }
814
+
815
+ await tick();
816
+
817
+ if (focusFocusedCellInGrid(scrollerElement)) {
818
+ shouldRestoreFocusedCell = false;
819
+ break;
820
+ }
821
+
822
+ if (!nextScroll) {
823
+ break;
824
+ }
825
+ }
826
+ } finally {
827
+ focusRestoreInProgress = false;
828
+ }
829
+ };
830
+
831
+ const scheduleFocusedCellRestore = () => {
832
+ shouldRestoreFocusedCell = true;
833
+ void tick().then(() =>
834
+ restoreFocusedCell(
835
+ $state.focusedCell,
836
+ rowSizeVersion,
837
+ scrollFrame.scrollTop,
838
+ scrollFrame.scrollLeft,
839
+ measuredColumnLayout,
840
+ allRows,
841
+ rowVirtualOptions,
842
+ columnVirtualOptions,
843
+ ),
844
+ );
845
+ };
846
+
847
+ const moveFocusWithRestore = (direction: Parameters<Grid<unknown>["moveFocus"]>[0], options?: Parameters<Grid<unknown>["moveFocus"]>[1]) => {
848
+ grid.moveFocus(direction, options);
849
+ scheduleFocusedCellRestore();
850
+ };
851
+
852
+ const focusEditor = (node: HTMLInputElement | HTMLSelectElement) => {
853
+ focusCellEditorElement(node);
854
+
855
+ return {};
856
+ };
857
+
858
+ const measureVirtualRow = (element: HTMLElement, rowId: string) => {
859
+ let currentRowId = rowId;
860
+
861
+ if (!rowVirtualOptions.enabled || !rowVirtualOptions.measureRowHeight) {
862
+ return;
863
+ }
864
+
865
+ const observe = () => {
866
+ replaceObservedElement(rowResizeObserver, observedRowElements, currentRowId, element);
867
+
868
+ if (rowSizeCache.set(currentRowId, getMeasuredElementBlockSizeFromRect(element))) {
869
+ rowSizeVersion = rowSizeCache.version;
870
+ }
871
+ };
872
+
873
+ const unobserve = () => {
874
+ removeObservedElement(rowResizeObserver, observedRowElements, currentRowId);
875
+ };
876
+
877
+ observe();
878
+
879
+ return {
880
+ update(nextRowId: string) {
881
+ if (nextRowId === currentRowId) {
882
+ return;
883
+ }
884
+
885
+ unobserve();
886
+ currentRowId = nextRowId;
887
+ observe();
888
+ },
889
+ destroy() {
890
+ unobserve();
891
+ },
892
+ };
893
+ };
894
+
895
+ const measureVirtualColumn = (element: HTMLElement, options: { columnId: string; enabled: boolean }) => {
896
+ let currentColumnId = options.columnId;
897
+ let currentEnabled = options.enabled;
898
+
899
+ const observe = () => {
900
+ if (!currentEnabled) {
901
+ return;
902
+ }
903
+
904
+ setElementDatasetId(element, "columnId", currentColumnId);
905
+ replaceObservedElement(columnResizeObserver, observedColumnElements, currentColumnId, element);
906
+
907
+ if (columnSizeCache.set(currentColumnId, getMeasuredElementInlineSize(element))) {
908
+ columnSizeVersion = columnSizeCache.version;
909
+ }
910
+ };
911
+
912
+ const unobserve = () => {
913
+ removeObservedElement(columnResizeObserver, observedColumnElements, currentColumnId);
914
+ };
915
+
916
+ observe();
917
+
918
+ return {
919
+ update(nextOptions: { columnId: string; enabled: boolean }) {
920
+ if (nextOptions.columnId === currentColumnId && nextOptions.enabled === currentEnabled) {
921
+ return;
922
+ }
923
+
924
+ unobserve();
925
+ currentColumnId = nextOptions.columnId;
926
+ currentEnabled = nextOptions.enabled;
927
+ observe();
928
+ },
929
+ destroy() {
930
+ unobserve();
931
+ },
932
+ };
933
+ };
934
+
935
+ const handleGridKeyDown = (event: KeyboardEvent) => {
936
+ if (isGridInteractiveKeyboardEventTarget(event)) {
937
+ return;
938
+ }
939
+
940
+ const shortcutAction = getGridKeyboardShortcutAction(event);
941
+ const focusMove = getGridKeyboardFocusMove(event);
942
+ const editAction = getGridKeyboardEditAction(event);
943
+ const focusedCell = $state.focusedCell;
944
+ const editingCell = $state.editingCell;
945
+ const canRunShortcut = getCanRunGridKeyboardShortcut(editingCell);
946
+
947
+ if (editAction === "start-edit" && getCanStartFocusedCellEdit(focusedCell, editingCell)) {
948
+ preventEventDefault(event);
949
+ shouldRestoreFocusedCell = false;
950
+ startCellEdit(focusedCell.rowId, focusedCell.columnId, event);
951
+ } else if (editAction === "cancel-edit" && getCanCancelCellEdit(editingCell)) {
952
+ preventEventDefault(event);
953
+ editValidationMessage = null;
954
+ grid.cancelCellEdit(event);
955
+ scheduleFocusedCellRestore();
956
+ } else if (shortcutAction === "copy" && canRunShortcut) {
957
+ const clipboardText = grid.getClipboardText();
958
+
959
+ if (clipboardText) {
960
+ preventEventDefault(event);
961
+ void writeClipboardText(clipboardText);
962
+ }
963
+ } else if (shortcutAction === "paste" && canRunShortcut) {
964
+ preventEventDefault(event);
965
+ void readClipboardText().then((clipboardText) => {
966
+ if (clipboardText) {
967
+ const result = grid.pasteClipboardText(clipboardText, { ...clipboardPasteOptions, sourceEvent: event });
968
+ onClipboardPaste?.(result);
969
+ scheduleFocusedCellRestore();
970
+ }
971
+ });
972
+ } else if (shortcutAction === "undo-edit" && canRunShortcut && grid.getCanUndoCellEdit()) {
973
+ preventEventDefault(event);
974
+ grid.undoCellEdit(event);
975
+ scheduleFocusedCellRestore();
976
+ } else if (shortcutAction === "redo-edit" && canRunShortcut && grid.getCanRedoCellEdit()) {
977
+ preventEventDefault(event);
978
+ grid.redoCellEdit(event);
979
+ scheduleFocusedCellRestore();
980
+ } else if (shortcutAction === "row-selection" && canRunShortcut) {
981
+ const rowSelectionTarget = getFocusedRowSelectionTarget(allRows, focusedCell);
982
+
983
+ if (rowSelectionTarget) {
984
+ preventEventDefault(event);
985
+ const { row, rowIndex } = rowSelectionTarget;
986
+ const rowEvent = grid.emitRowEvent({ type: "keydown", row, rowIndex, sourceEvent: event });
987
+
988
+ if (getCanToggleRowSelection(rowEvent)) {
989
+ grid.toggleRowSelected(row.id);
990
+ }
991
+ }
992
+ } else if (focusMove) {
993
+ preventEventDefault(event);
994
+ moveFocusWithRestore(focusMove.direction, focusMove.options);
995
+ }
996
+ };
997
+
998
+ const handleRowClick = (row: Row<unknown>, rowIndex: number, event: MouseEvent) => {
999
+ const rowEvent = grid.emitRowEvent({ type: "click", row, rowIndex, sourceEvent: event });
1000
+
1001
+ if (getCanToggleRowSelection(rowEvent)) {
1002
+ grid.toggleRowSelected(row.id);
1003
+ }
1004
+ };
1005
+
1006
+ const handleCellClick = (
1007
+ row: Row<unknown>,
1008
+ rowIndex: number,
1009
+ column: Column<unknown, unknown>,
1010
+ columnIndex: number,
1011
+ event: MouseEvent,
1012
+ ) => {
1013
+ deferDetailedCellRendering = false;
1014
+ scheduleFocusedCellRestore();
1015
+
1016
+ if (cellClickSuppression.consume(event)) {
1017
+ return;
1018
+ }
1019
+
1020
+ const cellEvent = grid.emitCellEvent({ type: "click", row, rowIndex, column, columnIndex, sourceEvent: event });
1021
+
1022
+ if (getShouldPreventEventDefault(cellEvent)) {
1023
+ preventEventDefault(event);
1024
+ }
1025
+ };
1026
+
1027
+ const getSimpleRowEventContext = (event: Event) => {
1028
+ const bodyElement = event.currentTarget;
1029
+ const target = event.target;
1030
+
1031
+ if (!(bodyElement instanceof HTMLElement) || !(target instanceof Element)) {
1032
+ return null;
1033
+ }
1034
+
1035
+ const rowElement = target.closest<HTMLElement>(".og-grid__row");
1036
+ const rowId = rowElement?.dataset.rowId;
1037
+ if (!rowElement || rowElement.parentElement !== bodyElement || !rowId) {
1038
+ return null;
1039
+ }
1040
+
1041
+ return visibleRowItems.find(({ row }) => row.id === rowId) ?? null;
1042
+ };
1043
+
1044
+ const getSimpleCellEventContext = (event: Event) => {
1045
+ const rowItem = getSimpleRowEventContext(event);
1046
+ const target = event.target;
1047
+ if (!rowItem || !(target instanceof Element)) {
1048
+ return null;
1049
+ }
1050
+
1051
+ const cell = target.closest<HTMLElement>(".og-grid__cell");
1052
+ const columnId = cell?.dataset.columnId;
1053
+ const columnItem = columnId
1054
+ ? columnCellRenderItems.find(({ column }) => column.id === columnId)
1055
+ : undefined;
1056
+ if (!cell || !columnItem) {
1057
+ return null;
1058
+ }
1059
+
1060
+ return { cell, ...columnItem, ...rowItem };
1061
+ };
1062
+
1063
+ const handleSimpleBodyFocusIn = (event: FocusEvent) => {
1064
+ if (!simpleCellRendering) return;
1065
+ const context = getSimpleCellEventContext(event);
1066
+ if (context) {
1067
+ grid.setFocusedCell({ rowId: context.row.id, columnId: context.column.id });
1068
+ }
1069
+ };
1070
+
1071
+ const handleSimpleBodyPointerDown = (event: PointerEvent) => {
1072
+ if (!simpleCellRendering) return;
1073
+ const context = getSimpleCellEventContext(event);
1074
+ if (context) {
1075
+ handleCellPointerDown({ rowId: context.row.id, columnId: context.column.id }, event);
1076
+ }
1077
+ };
1078
+
1079
+ const handleSimpleBodyPointerOver = (event: PointerEvent) => {
1080
+ if (!simpleCellRendering) return;
1081
+ const context = getSimpleCellEventContext(event);
1082
+ const relatedCell = event.relatedTarget instanceof Element
1083
+ ? event.relatedTarget.closest<HTMLElement>(".og-grid__cell")
1084
+ : null;
1085
+
1086
+ if (context && context.cell !== relatedCell) {
1087
+ handleCellPointerEnter({ rowId: context.row.id, columnId: context.column.id }, event);
1088
+ }
1089
+ };
1090
+
1091
+ const handleSimpleBodyPointerUp = (event: PointerEvent) => {
1092
+ if (simpleCellRendering) handleCellPointerUp(event);
1093
+ };
1094
+
1095
+ const handleSimpleBodyClick = (event: MouseEvent) => {
1096
+ if (!simpleCellRendering) return;
1097
+ const context = getSimpleCellEventContext(event);
1098
+ if (context) {
1099
+ handleCellClick(context.row, context.rowIndex, context.column, context.columnIndex, event);
1100
+ handleRowClick(context.row, context.rowIndex, event);
1101
+ return;
1102
+ }
1103
+
1104
+ const rowContext = getSimpleRowEventContext(event);
1105
+ if (rowContext) {
1106
+ handleRowClick(rowContext.row, rowContext.rowIndex, event);
1107
+ }
1108
+ };
1109
+
1110
+ const handleSimpleBodyDoubleClick = (event: MouseEvent) => {
1111
+ if (!simpleCellRendering) return;
1112
+ const context = getSimpleCellEventContext(event);
1113
+ if (context) {
1114
+ handleCellDoubleClick(context.row, context.column, event);
1115
+ }
1116
+ };
1117
+
1118
+ const handleCellPointerDown = (coordinate: CellCoordinate, event: PointerEvent) => {
1119
+ if (!getCanStartCellPointerDrag(event, $state.editingCell)) {
1120
+ return;
1121
+ }
1122
+
1123
+ deferDetailedCellRendering = simpleCellRendering;
1124
+ rangeDrag = { anchor: coordinate, pointerId: event.pointerId, didDrag: false };
1125
+ grid.selectCellRange(coordinate);
1126
+ };
1127
+
1128
+ const handleCellPointerEnter = (coordinate: CellCoordinate, event: PointerEvent) => {
1129
+ if (getCanContinuePointerDrag(fillDrag, event)) {
1130
+ fillDrag.target = coordinate;
1131
+ }
1132
+
1133
+ if (!getCanContinuePointerDrag(rangeDrag, event)) {
1134
+ return;
1135
+ }
1136
+
1137
+ if (!isCellCoordinateEqual(rangeDrag.anchor, coordinate)) {
1138
+ rangeDrag.didDrag = true;
1139
+ deferDetailedCellRendering = false;
1140
+ }
1141
+
1142
+ grid.selectCellRange(rangeDrag.anchor, coordinate);
1143
+ };
1144
+
1145
+ const handleCellPointerUp = (event: PointerEvent) => {
1146
+ if (!getCanEndPointerDrag(rangeDrag, event)) {
1147
+ return;
1148
+ }
1149
+
1150
+ if (rangeDrag.didDrag) {
1151
+ cellClickSuppression.suppress();
1152
+ } else {
1153
+ scheduleDetailedCellRendering();
1154
+ }
1155
+
1156
+ rangeDrag = null;
1157
+ };
1158
+
1159
+ const scheduleDetailedCellRendering = () => {
1160
+ setTimeout(() => {
1161
+ deferDetailedCellRendering = false;
1162
+ }, 0);
1163
+ };
1164
+
1165
+ const handleFillPointerDown = (coordinate: CellCoordinate, event: PointerEvent) => {
1166
+ if (!getCanStartCellPointerDrag(event, $state.editingCell)) {
1167
+ return;
1168
+ }
1169
+
1170
+ preventDefaultAndStopPropagation(event);
1171
+ fillDrag = { pointerId: event.pointerId, target: coordinate };
1172
+ };
1173
+
1174
+ const getFillHandleVisible = (row: Row<unknown>, column: Column<unknown, unknown>, editing: boolean, rangeSelected: boolean) => {
1175
+ const selectedCoordinates = grid.getSelectedCellCoordinates();
1176
+ const fillHandleCoordinate = selectedCoordinates[selectedCoordinates.length - 1];
1177
+
1178
+ return getCellFillHandleVisible(row, column, {
1179
+ activeEditing: Boolean($state.editingCell),
1180
+ editing,
1181
+ fillHandleCoordinate,
1182
+ rangeSelected,
1183
+ });
1184
+ };
1185
+
1186
+ const handleCellDoubleClick = (
1187
+ row: Row<unknown>,
1188
+ column: Column<unknown, unknown>,
1189
+ event: MouseEvent,
1190
+ ) => {
1191
+ if (!getCanStartCellEdit(row, column)) {
1192
+ return;
1193
+ }
1194
+
1195
+ preventDefaultAndStopPropagation(event);
1196
+ startCellEdit(row.id, column.id, event);
1197
+ };
1198
+
1199
+ const startCellEdit = (rowId: string, columnId: string, sourceEvent?: unknown) => {
1200
+ const row = getRowById(rowId, grid.getRowModel().rows);
1201
+ const column = getColumnById(columnId, grid.getVisibleLeafColumns());
1202
+
1203
+ if (!row || !column) {
1204
+ return;
1205
+ }
1206
+
1207
+ editDraft = getCellEditText(row.getValue(column.id));
1208
+ editValidationMessage = null;
1209
+ grid.startCellEdit(rowId, columnId, sourceEvent);
1210
+ };
1211
+
1212
+ const commitCellEdit = (row: Row<unknown>, column: Column<unknown, unknown>, sourceEvent?: unknown) => {
1213
+ const event = grid.commitCellEdit(parseCellEditValue(grid, row, column, editDraft), sourceEvent);
1214
+ editValidationMessage = getCellEditValidationMessage(event);
1215
+
1216
+ if (event && !event.defaultPrevented) {
1217
+ scheduleFocusedCellRestore();
1218
+ }
1219
+ };
1220
+
1221
+ const handleResizePointerDown = (column: Column<unknown, unknown>, columnIndex: number, event: PointerEvent) => {
1222
+ preventDefaultAndStopPropagation(event);
1223
+
1224
+ const startX = event.clientX;
1225
+ const startSize = getColumnResizeStartSize(grid, column);
1226
+ const startEvent = grid.emitColumnResizeEvent({
1227
+ phase: "start",
1228
+ column,
1229
+ columnIndex,
1230
+ startSize,
1231
+ size: startSize,
1232
+ sourceEvent: event,
1233
+ });
1234
+
1235
+ if (!getCanStartColumnResize(startEvent)) {
1236
+ return;
1237
+ }
1238
+
1239
+ const target = getPointerCaptureTarget(event);
1240
+ let lastSize = startSize;
1241
+ let cleanupPointerListeners: (() => void) | null = null;
1242
+
1243
+ trySetPointerCapture(target, event.pointerId);
1244
+
1245
+ const handlePointerMove = (moveEvent: PointerEvent) => {
1246
+ const size = getColumnResizePointerSize(startSize, startX, moveEvent);
1247
+ const resizeEvent = grid.emitColumnResizeEvent({
1248
+ phase: "move",
1249
+ column,
1250
+ columnIndex,
1251
+ startSize,
1252
+ size,
1253
+ sourceEvent: moveEvent,
1254
+ });
1255
+
1256
+ if (getCanApplyColumnResize(resizeEvent)) {
1257
+ lastSize = resizeEvent.size;
1258
+ grid.setColumnSize(column.id, resizeEvent.size);
1259
+ }
1260
+ };
1261
+
1262
+ const handlePointerUp = (upEvent: PointerEvent) => {
1263
+ grid.emitColumnResizeEvent({
1264
+ phase: "end",
1265
+ column,
1266
+ columnIndex,
1267
+ startSize,
1268
+ size: lastSize,
1269
+ sourceEvent: upEvent,
1270
+ });
1271
+
1272
+ tryReleasePointerCapture(target, upEvent.pointerId);
1273
+ cleanupPointerListeners?.();
1274
+ cleanupPointerListeners = null;
1275
+ };
1276
+
1277
+ cleanupPointerListeners = addPointerMoveUpListeners({ move: handlePointerMove, up: handlePointerUp });
1278
+ };
1279
+
1280
+ const handleResizeKeyDown = (column: Column<unknown, unknown>, columnIndex: number, event: KeyboardEvent) => {
1281
+ const startSize = getColumnResizeStartSize(grid, column);
1282
+ const nextSize = getColumnResizeKeyboardSize(column, startSize, event);
1283
+
1284
+ if (nextSize === null) {
1285
+ return;
1286
+ }
1287
+
1288
+ preventDefaultAndStopPropagation(event);
1289
+
1290
+ const startEvent = grid.emitColumnResizeEvent({
1291
+ phase: "start",
1292
+ column,
1293
+ columnIndex,
1294
+ startSize,
1295
+ size: startSize,
1296
+ sourceEvent: event,
1297
+ });
1298
+
1299
+ if (!getCanStartColumnResize(startEvent)) {
1300
+ return;
1301
+ }
1302
+
1303
+ const resizeEvent = grid.emitColumnResizeEvent({
1304
+ phase: "move",
1305
+ column,
1306
+ columnIndex,
1307
+ startSize,
1308
+ size: nextSize,
1309
+ sourceEvent: event,
1310
+ });
1311
+ const finalSize = getColumnResizeFinalSize(startSize, resizeEvent);
1312
+
1313
+ if (getCanApplyColumnResize(resizeEvent)) {
1314
+ grid.setColumnSize(column.id, finalSize);
1315
+ }
1316
+
1317
+ grid.emitColumnResizeEvent({
1318
+ phase: "end",
1319
+ column,
1320
+ columnIndex,
1321
+ startSize,
1322
+ size: finalSize,
1323
+ sourceEvent: event,
1324
+ });
1325
+ };
1326
+
1327
+ const handleHeaderButtonKeyDown = (column: Column<unknown, unknown>, event: KeyboardEvent) => {
1328
+ const direction = getHeaderKeyboardMoveDirection(event);
1329
+
1330
+ if (!direction) {
1331
+ return;
1332
+ }
1333
+
1334
+ preventDefaultAndStopPropagation(event);
1335
+ moveVisibleColumn(grid, column.id, direction);
1336
+ };
1337
+
1338
+ const getHeaderMenuId = (columnId: ColumnId) => getHeaderActionMenuId(columnId);
1339
+
1340
+ const getHeaderActionMenu = (
1341
+ column: Column<unknown, unknown>,
1342
+ sortDirection: "asc" | "desc" | false,
1343
+ pinningPosition: "left" | "right" | false,
1344
+ isGrouped: boolean,
1345
+ canMoveLeft: boolean,
1346
+ canMoveRight: boolean,
1347
+ ) => {
1348
+ const defaultItems = getHeaderActionMenuDefaultItemDescriptors({
1349
+ sortDirection,
1350
+ pinningPosition,
1351
+ isGrouped,
1352
+ canGroup: column.getCanGroup(),
1353
+ canMoveLeft,
1354
+ canMoveRight,
1355
+ }).map((descriptor): HeaderActionMenuActionItem<unknown> => {
1356
+ if (descriptor.id === "sort-asc") {
1357
+ return { ...descriptor, onSelect: () => grid.toggleColumnSorting(column.id, false) };
1358
+ }
1359
+ if (descriptor.id === "sort-desc") {
1360
+ return { ...descriptor, onSelect: () => grid.toggleColumnSorting(column.id, true) };
1361
+ }
1362
+ if (descriptor.id === "clear-sort") {
1363
+ return { ...descriptor, onSelect: () => grid.setSorting((previous) => previous.filter((rule) => rule.id !== column.id)) };
1364
+ }
1365
+ if (descriptor.id === "move-left") {
1366
+ return { ...descriptor, onSelect: () => moveVisibleColumn(grid, column.id, "left") };
1367
+ }
1368
+ if (descriptor.id === "move-right") {
1369
+ return { ...descriptor, onSelect: () => moveVisibleColumn(grid, column.id, "right") };
1370
+ }
1371
+ if (descriptor.id === "pin-left") {
1372
+ return { ...descriptor, onSelect: () => grid.pinColumn(column.id, "left") };
1373
+ }
1374
+ if (descriptor.id === "unpin") {
1375
+ return { ...descriptor, onSelect: () => grid.pinColumn(column.id, false) };
1376
+ }
1377
+ if (descriptor.id === "pin-right") {
1378
+ return { ...descriptor, onSelect: () => grid.pinColumn(column.id, "right") };
1379
+ }
1380
+
1381
+ return { ...descriptor, onSelect: () => grid.toggleColumnGrouping(column.id, !isGrouped) };
1382
+ });
1383
+ const context: HeaderActionMenuContext<unknown> = {
1384
+ grid,
1385
+ column,
1386
+ sortDirection,
1387
+ pinningPosition,
1388
+ isGrouped,
1389
+ canMoveLeft,
1390
+ canMoveRight,
1391
+ defaultItems,
1392
+ };
1393
+
1394
+ return {
1395
+ context,
1396
+ items: (headerActionMenuItems ? headerActionMenuItems(context) : defaultItems).filter(isHeaderActionMenuItem),
1397
+ };
1398
+ };
1399
+
1400
+ const runHeaderMenuAction = (item: HeaderActionMenuActionItem<unknown>, context: HeaderActionMenuContext<unknown>) => {
1401
+ item.onSelect(context);
1402
+ headerMenuColumnId = null;
1403
+ };
1404
+
1405
+ const getHeaderActionMenuCustomComponent = (item: HeaderActionMenuItem<unknown>): HeaderActionMenuCustomComponent | undefined =>
1406
+ isHeaderActionMenuCustomItem(item) ? item.component : undefined;
1407
+
1408
+ const handleHeaderPointerDown = (column: Column<unknown, unknown>, event: PointerEvent) => {
1409
+ if (!isPrimaryPointerButton(event)) {
1410
+ return;
1411
+ }
1412
+
1413
+ headerDrag = {
1414
+ sourceColumnId: column.id,
1415
+ targetColumnId: column.id,
1416
+ pointerId: event.pointerId,
1417
+ startX: event.clientX,
1418
+ startY: event.clientY,
1419
+ didDrag: false,
1420
+ };
1421
+ };
1422
+
1423
+ const handleHeaderPointerEnter = (column: Column<unknown, unknown>, event: PointerEvent) => {
1424
+ if (!getCanContinuePointerDrag(headerDrag, event)) {
1425
+ return;
1426
+ }
1427
+
1428
+ headerDrag.targetColumnId = column.id;
1429
+ };
1430
+
1431
+ const handleHeaderClick = (column: Column<unknown, unknown>, canSort: boolean, event: MouseEvent) => {
1432
+ if (headerClickSuppression.consume(event)) {
1433
+ return;
1434
+ }
1435
+
1436
+ if (canSort) {
1437
+ grid.toggleColumnSorting(column.id, undefined, event.shiftKey);
1438
+ }
1439
+ };
1440
+
1441
+ </script>
1442
+
1443
+ {#if quickFilterControl || rowSelectionControls || columnVisibilityControls || densityControl}
1444
+ <div class="og-grid__controls">
1445
+ {#if rowSelectionControls}
1446
+ <div {...getRowSelectionControlsProps()} class="og-grid__selection-controls">
1447
+ <label class="og-grid__selection-toggle">
1448
+ <input
1449
+ {...getRowSelectionCheckboxProps({ allSelected: allPageRowsSelected, someSelected: somePageRowsSelected, disabled: allRows.length === 0 })}
1450
+ bind:this={selectionCheckboxElement}
1451
+ on:change={() => grid.toggleAllPageRowsSelected(!allPageRowsSelected)}
1452
+ />
1453
+ <span>{getRowSelectionCheckboxText()}</span>
1454
+ </label>
1455
+ <span {...getRowSelectionStatusProps()} class="og-grid__selection-status">{getRowSelectionStatusText(selectedRowCount)}</span>
1456
+ <button {...getRowSelectionClearButtonProps(selectedRowCount === 0)} class="og-grid__selection-clear" on:click={() => grid.resetRowSelection()}>{getRowSelectionClearButtonText()}</button>
1457
+ </div>
1458
+ {/if}
1459
+ {#if columnVisibilityControls}
1460
+ <details {...getColumnVisibilityControlsProps()} class="og-grid__column-visibility">
1461
+ <summary {...getColumnVisibilitySummaryProps(visibleColumns.length, allColumns.length)} class="og-grid__column-visibility-summary">
1462
+ {getColumnVisibilitySummaryText(visibleColumns.length, allColumns.length)}
1463
+ </summary>
1464
+ <div class="og-grid__column-visibility-panel">
1465
+ <input
1466
+ {...getColumnVisibilitySearchInputProps(columnVisibilityQuery)}
1467
+ class="og-grid__column-visibility-search"
1468
+ on:input={(event) => (columnVisibilityQuery = event.currentTarget.value)}
1469
+ />
1470
+ <span {...getColumnVisibilityStatusProps()} class="og-grid__column-visibility-status">
1471
+ {getColumnVisibilityStatusText(visibleColumns.length, allColumns.length)}
1472
+ </span>
1473
+ <div {...getColumnVisibilityListProps()} class="og-grid__column-visibility-list">
1474
+ {#if filteredVisibilityColumns.length > 0}
1475
+ {#each filteredVisibilityColumns as column (column.id)}
1476
+ {@const label = getColumnHeaderText(column)}
1477
+ {@const visible = grid.getIsColumnVisible(column.id)}
1478
+ <label class="og-grid__column-visibility-item">
1479
+ <input
1480
+ {...getColumnVisibilityCheckboxProps({
1481
+ columnId: column.id,
1482
+ label,
1483
+ visible,
1484
+ visibleCount: visibleColumns.length,
1485
+ })}
1486
+ on:change={(event) => grid.toggleColumnVisibility(column.id, event.currentTarget.checked)}
1487
+ />
1488
+ <span>{label}</span>
1489
+ </label>
1490
+ {/each}
1491
+ {:else}
1492
+ <span class="og-grid__column-visibility-empty">{getColumnVisibilityEmptyText()}</span>
1493
+ {/if}
1494
+ </div>
1495
+ <button
1496
+ {...getColumnVisibilityResetButtonProps(allColumns.length - visibleColumns.length)}
1497
+ class="og-grid__column-visibility-reset"
1498
+ on:click={() => grid.resetColumnVisibility()}
1499
+ >{getColumnVisibilityResetButtonText()}</button>
1500
+ </div>
1501
+ </details>
1502
+ {/if}
1503
+ {#if densityControl}
1504
+ <div {...getDensityControlsProps()} class="og-grid__density-controls">
1505
+ {#each GRID_DENSITIES as densityOption (densityOption)}
1506
+ <button
1507
+ {...getDensityButtonProps(densityOption, resolvedDensity)}
1508
+ class="og-grid__density-button"
1509
+ on:click={() => setDensity(densityOption)}
1510
+ >{getDensityButtonText(densityOption)}</button>
1511
+ {/each}
1512
+ </div>
1513
+ {/if}
1514
+ {#if quickFilterControl}
1515
+ <div {...getQuickFilterProps()} class="og-grid__quick-filter">
1516
+ <input {...getQuickFilterInputProps({ value: $state.globalFilter })} class="og-grid__quick-filter-input" on:input={handleQuickFilterInput} />
1517
+ <button {...getQuickFilterClearButtonProps($state.globalFilter)} class="og-grid__quick-filter-clear" on:click={() => setQuickFilter("")}>{getQuickFilterClearButtonText()}</button>
1518
+ </div>
1519
+ {/if}
1520
+ </div>
1521
+ {/if}
1522
+ <div
1523
+ class={["og-grid", className].filter(Boolean).join(" ")}
1524
+ data-density={densitySizingEnabled ? resolvedDensity : undefined}
1525
+ {style}
1526
+ >
1527
+ {#if groupingPanel}
1528
+ <div
1529
+ bind:this={groupingPanelElement}
1530
+ {...getGroupingPanelProps({ empty: groupingColumns.length === 0 })}
1531
+ class="og-grid__grouping-panel"
1532
+ >
1533
+ {#if groupingColumns.length === 0}
1534
+ <span {...getGroupingPanelPlaceholderProps()} class="og-grid__grouping-placeholder">{GROUPING_PANEL_EMPTY_MESSAGE}</span>
1535
+ {:else}
1536
+ {#each groupingColumns as column, index (column.id)}
1537
+ <span {...getGroupingPanelChipProps(column)} class="og-grid__grouping-chip">
1538
+ <span class="og-grid__grouping-chip-label">{getColumnLabel(column)}</span>
1539
+ <button
1540
+ {...getGroupingPanelMoveButtonProps(column, { direction: "left", disabled: index === 0 })}
1541
+ class="og-grid__grouping-move"
1542
+ on:click={() => moveGroupedColumn(grid, groupingColumns, column.id, "left")}
1543
+ >
1544
+ {"<"}
1545
+ </button>
1546
+ <button
1547
+ {...getGroupingPanelMoveButtonProps(column, { direction: "right", disabled: index === groupingColumns.length - 1 })}
1548
+ class="og-grid__grouping-move"
1549
+ on:click={() => moveGroupedColumn(grid, groupingColumns, column.id, "right")}
1550
+ >
1551
+ {">"}
1552
+ </button>
1553
+ <button
1554
+ {...getGroupingPanelRemoveButtonProps(column)}
1555
+ class="og-grid__grouping-remove"
1556
+ on:click={() => grid.toggleColumnGrouping(column.id, false)}
1557
+ >
1558
+ x
1559
+ </button>
1560
+ </span>
1561
+ {/each}
1562
+ {/if}
1563
+ </div>
1564
+ {/if}
1565
+ <div {...gridProps} class="og-grid__scroller" role="grid" bind:this={scrollerElement} on:keydown={handleGridKeyDown} on:scroll={syncFrame}>
1566
+ <div class="og-grid__canvas" style={getInlineSizeStyleText(totalMeasuredWidth)}>
1567
+ <div {...getGridHeaderProps()} class="og-grid__header" bind:this={headerElement}>
1568
+ {#each grid.getHeaderGroups() as headerGroup, headerRowIndex (headerGroup.id)}
1569
+ <div
1570
+ {...getGridHeaderRowProps({ columnVirtualized: columnVirtualOptions.enabled, rowIndex: headerRowIndex })}
1571
+ class="og-grid__row og-grid__row--header"
1572
+ style={getVirtualizedInlineSizeStyleText(totalMeasuredWidth, columnVirtualOptions.enabled)}
1573
+ >
1574
+ {#if getCanUseCompactHeaderRendering(headerGroup, columnPinningControls, headerActionMenu)}
1575
+ {#each getHeaderRenderItems(headerGroup, columnRenderItems, layoutById) as item (getHeaderRenderItemKey(headerGroup.id, item))}
1576
+ {#if isHeaderRenderSpacerItem(item)}
1577
+ <div {...getColumnSpacerProps()} class="og-grid__column-spacer" style={getInlineSizeStyleText(item.size)}></div>
1578
+ {:else}
1579
+ {@const header = item.header}
1580
+ {@const firstLeafId = item.leafColumnIds[0]}
1581
+ {@const columnIndex = getColumnIndexById(firstLeafId, visibleColumns)}
1582
+ {@const layout = layoutById.get(firstLeafId)}
1583
+ {@const canSort = header.column.getCanSort()}
1584
+ {@const sortDirection = grid.getColumnSortDirection(header.column.id)}
1585
+ <div
1586
+ {...getHeaderCellProps(grid, header.column, Math.max(0, columnIndex), { pinned: layout?.pinned ?? false })}
1587
+ {...getHeaderCellLayoutProps({
1588
+ colSpan: item.leafColumnIds.length,
1589
+ rowSpan: header.rowSpan,
1590
+ grouped: false,
1591
+ placeholder: false,
1592
+ pinnedEdge: layout?.pinnedEdge,
1593
+ draggable: true,
1594
+ })}
1595
+ class={["og-grid__header-cell", getHeaderClassName?.({ grid, column: header.column, header })].filter(Boolean).join(" ")}
1596
+ style={getColumnStyle(item.size, layout, $state.columnSizing)}
1597
+ use:measureVirtualColumn={{ columnId: header.column.id, enabled: columnVirtualOptions.enabled && columnVirtualOptions.measureColumnWidth }}
1598
+ on:pointerdown={(event) => handleHeaderPointerDown(header.column, event)}
1599
+ on:pointerenter={(event) => handleHeaderPointerEnter(header.column, event)}
1600
+ ><button
1601
+ {...getHeaderButtonProps({ disabled: false })}
1602
+ class="og-grid__header-button"
1603
+ on:click={(event) => handleHeaderClick(header.column, canSort, event)}
1604
+ on:keydown={(event) => handleHeaderButtonKeyDown(header.column, event)}
1605
+ ><span class="og-grid__header-label">{getHeaderLabel(header)}</span><span {...getHeaderSortIndicatorProps()} class="og-grid__sort-indicator">{getHeaderSortIndicatorText(sortDirection, { visible: true })}</span></button><span
1606
+ {...getResizeHandleProps(header.column, layout, $state.columnSizing)}
1607
+ class="og-grid__resize-handle"
1608
+ on:keydown={(event) => handleResizeKeyDown(header.column, Math.max(0, columnIndex), event)}
1609
+ on:pointerdown={(event) => handleResizePointerDown(header.column, Math.max(0, columnIndex), event)}
1610
+ ></span></div>
1611
+ {/if}
1612
+ {/each}
1613
+ {:else}
1614
+ {#each getHeaderRenderItems(headerGroup, columnRenderItems, layoutById) as item (getHeaderRenderItemKey(headerGroup.id, item))}
1615
+ {#if isHeaderRenderSpacerItem(item)}
1616
+ <div {...getColumnSpacerProps()} class="og-grid__column-spacer" style={getInlineSizeStyleText(item.size)}></div>
1617
+ {:else}
1618
+ {@const header = item.header}
1619
+ {@const firstLeafId = item.leafColumnIds[0]}
1620
+ {@const columnIndex = firstLeafId ? getColumnIndexById(firstLeafId, visibleColumns) : 0}
1621
+ {@const layout = firstLeafId ? layoutById.get(firstLeafId) : undefined}
1622
+ {@const canInteract = !header.isPlaceholder && header.column.columns.length === 0}
1623
+ {@const canSort = canInteract && header.column.getCanSort()}
1624
+ {@const sortDirection = grid.getColumnSortDirection(header.column.id)}
1625
+ {@const pinningPosition = layout?.pinned ?? false}
1626
+ {@const canMoveLeft = columnIndex > 0}
1627
+ {@const canMoveRight = columnIndex >= 0 && columnIndex < visibleColumns.length - 1}
1628
+ {@const isGrouped = $state.grouping.includes(header.column.id)}
1629
+ {@const menuId = getHeaderMenuId(header.column.id)}
1630
+ {@const menuTriggerId = getHeaderActionMenuTriggerId(header.column.id)}
1631
+ {@const headerMenu = getHeaderActionMenu(header.column, sortDirection, pinningPosition, isGrouped, canMoveLeft, canMoveRight)}
1632
+ <div
1633
+ {...(header.isPlaceholder
1634
+ ? getHeaderPlaceholderCellProps(header.column)
1635
+ : getHeaderCellProps(grid, header.column, Math.max(0, columnIndex), { pinned: layout?.pinned ?? false }))}
1636
+ {...getHeaderCellLayoutProps({
1637
+ colSpan: item.leafColumnIds.length,
1638
+ rowSpan: header.rowSpan,
1639
+ grouped: header.column.columns.length > 0,
1640
+ placeholder: header.isPlaceholder,
1641
+ pinnedEdge: layout?.pinnedEdge,
1642
+ draggable: canInteract,
1643
+ })}
1644
+ class={["og-grid__header-cell", getHeaderClassName?.({ grid, column: header.column, header })].filter(Boolean).join(" ")}
1645
+ style={getColumnStyle(item.size, layout, $state.columnSizing)}
1646
+ use:measureVirtualColumn={{ columnId: header.column.id, enabled: columnVirtualOptions.enabled && columnVirtualOptions.measureColumnWidth && canInteract }}
1647
+ on:pointerdown={(event) => {
1648
+ if (canInteract) {
1649
+ handleHeaderPointerDown(header.column, event);
1650
+ }
1651
+ }}
1652
+ on:pointerenter={(event) => {
1653
+ if (canInteract) {
1654
+ handleHeaderPointerEnter(header.column, event);
1655
+ }
1656
+ }}
1657
+ >
1658
+ {#if !header.isPlaceholder}
1659
+ <button
1660
+ {...getHeaderButtonProps({ disabled: !canInteract })}
1661
+ class="og-grid__header-button"
1662
+ on:click={(event) => handleHeaderClick(header.column, canSort, event)}
1663
+ on:keydown={(event) => handleHeaderButtonKeyDown(header.column, event)}
1664
+ >
1665
+ <span class="og-grid__header-label">{getHeaderLabel(header)}</span>
1666
+ <span {...getHeaderSortIndicatorProps()} class="og-grid__sort-indicator">
1667
+ {getHeaderSortIndicatorText(sortDirection, { visible: canInteract })}
1668
+ </span>
1669
+ </button>
1670
+ {#if canInteract && columnPinningControls}
1671
+ <span {...getColumnPinningControlsProps(header.column)} class="og-grid__pinning-controls">
1672
+ <button
1673
+ {...getColumnPinningButtonProps(header.column, { position: "left", active: pinningPosition === "left" })}
1674
+ class="og-grid__pinning-button"
1675
+ on:pointerdown|stopPropagation={() => undefined}
1676
+ on:click|stopPropagation={() => grid.pinColumn(header.column.id, "left")}
1677
+ >
1678
+ {getColumnPinningButtonText("left")}
1679
+ </button>
1680
+ <button
1681
+ {...getColumnPinningButtonProps(header.column, { position: false, active: pinningPosition === false })}
1682
+ class="og-grid__pinning-button"
1683
+ on:pointerdown|stopPropagation={() => undefined}
1684
+ on:click|stopPropagation={() => grid.pinColumn(header.column.id, false)}
1685
+ >
1686
+ {getColumnPinningButtonText(false)}
1687
+ </button>
1688
+ <button
1689
+ {...getColumnPinningButtonProps(header.column, { position: "right", active: pinningPosition === "right" })}
1690
+ class="og-grid__pinning-button"
1691
+ on:pointerdown|stopPropagation={() => undefined}
1692
+ on:click|stopPropagation={() => grid.pinColumn(header.column.id, "right")}
1693
+ >
1694
+ {getColumnPinningButtonText("right")}
1695
+ </button>
1696
+ </span>
1697
+ {/if}
1698
+ {#if canInteract && headerActionMenu}
1699
+ <span class="og-grid__header-menu">
1700
+ <button
1701
+ {...getHeaderActionMenuTriggerProps(header.column, { expanded: headerMenuColumnId === header.column.id, controls: menuId })}
1702
+ id={menuTriggerId}
1703
+ class="og-grid__header-menu-trigger"
1704
+ on:pointerdown|stopPropagation={() => undefined}
1705
+ on:click|stopPropagation={() => {
1706
+ headerMenuColumnId = headerMenuColumnId === header.column.id ? null : header.column.id;
1707
+ }}
1708
+ on:keydown={(event) => {
1709
+ const focusPosition = getHeaderActionMenuTriggerFocusPosition(event);
1710
+
1711
+ if (!focusPosition) {
1712
+ return;
1713
+ }
1714
+
1715
+ preventDefaultAndStopPropagation(event);
1716
+ headerMenuColumnId = header.column.id;
1717
+ void tick().then(() => focusHeaderActionMenuItemById(document, menuId, focusPosition));
1718
+ }}
1719
+ >
1720
+ {HEADER_ACTION_MENU_TRIGGER_TEXT}
1721
+ </button>
1722
+ {#if headerMenuColumnId === header.column.id}
1723
+ <div
1724
+ {...getHeaderActionMenuProps(header.column)}
1725
+ class="og-grid__header-menu-popover"
1726
+ id={menuId}
1727
+ on:pointerdown|stopPropagation={() => undefined}
1728
+ on:keydown={(event) => {
1729
+ const menuAction = getHeaderActionMenuKeyboardAction(event);
1730
+
1731
+ if (!menuAction) {
1732
+ return;
1733
+ }
1734
+
1735
+ if (isHeaderActionMenuCloseAction(menuAction)) {
1736
+ preventDefaultAndStopPropagation(event);
1737
+ headerMenuColumnId = null;
1738
+ void tick().then(() => focusHeaderActionMenuTriggerById(document, menuTriggerId));
1739
+ } else if (isHeaderActionMenuFocusAction(menuAction)) {
1740
+ preventDefaultAndStopPropagation(event);
1741
+ focusHeaderActionMenuItem(getHeaderActionMenuFocusTarget(event), menuAction.position);
1742
+ } else if (isHeaderActionMenuTabCloseAction(menuAction)) {
1743
+ headerMenuColumnId = null;
1744
+ }
1745
+ }}
1746
+ >
1747
+ {#each headerMenu.items as item (item.id)}
1748
+ {#if isHeaderActionMenuActionItem(item)}
1749
+ <button
1750
+ {...getHeaderActionMenuItemProps({ disabled: item.disabled })}
1751
+ on:click={() => runHeaderMenuAction(item, headerMenu.context)}
1752
+ >
1753
+ {item.label}
1754
+ </button>
1755
+ {:else if isHeaderActionMenuCustomItem(item)}
1756
+ {@const CustomHeaderMenuItem = getHeaderActionMenuCustomComponent(item)}
1757
+ <span {...getHeaderActionMenuCustomItemProps({ label: item.label })} class="og-grid__header-menu-custom">
1758
+ {#if CustomHeaderMenuItem}
1759
+ <svelte:component this={CustomHeaderMenuItem} {...getHeaderActionMenuCustomItemUserProps(item)} context={headerMenu.context} />
1760
+ {/if}
1761
+ </span>
1762
+ {:else if isHeaderActionMenuLabelItem(item)}
1763
+ <span {...getHeaderActionMenuLabelProps()} class="og-grid__header-menu-label">{item.label}</span>
1764
+ {:else if isHeaderActionMenuSeparatorItem(item)}
1765
+ <span {...getHeaderActionMenuSeparatorProps({ label: item.label })} class="og-grid__header-menu-separator"></span>
1766
+ {/if}
1767
+ {/each}
1768
+ </div>
1769
+ {/if}
1770
+ </span>
1771
+ {/if}
1772
+ {#if canInteract}
1773
+ <span
1774
+ {...getResizeHandleProps(header.column, layout, $state.columnSizing)}
1775
+ class="og-grid__resize-handle"
1776
+ on:keydown={(event) => handleResizeKeyDown(header.column, Math.max(0, columnIndex), event)}
1777
+ on:pointerdown={(event) => handleResizePointerDown(header.column, Math.max(0, columnIndex), event)}
1778
+ ></span>
1779
+ {/if}
1780
+ {/if}
1781
+ </div>
1782
+ {/if}
1783
+ {/each}
1784
+ {/if}
1785
+ </div>
1786
+ {/each}
1787
+ {#if columnFilterControls}
1788
+ <div
1789
+ {...getGridHeaderRowProps({ columnVirtualized: columnVirtualOptions.enabled, rowIndex: grid.getHeaderGroups().length })}
1790
+ class="og-grid__row og-grid__row--filter"
1791
+ style={getVirtualizedInlineSizeStyleText(totalMeasuredWidth, columnVirtualOptions.enabled)}
1792
+ >
1793
+ {#each columnCellRenderItems as { layout, beforeSpacerSize, afterSpacerSize, column, columnIndex } (layout.id)}
1794
+ <div
1795
+ {...getColumnFilterCellProps(column, {
1796
+ columnIndex,
1797
+ rowIndex: grid.getHeaderGroups().length,
1798
+ pinned: layout.pinned,
1799
+ pinnedEdge: layout.pinnedEdge,
1800
+ })}
1801
+ class="og-grid__filter-cell"
1802
+ style={getColumnStyle(layout.size, layout, $state.columnSizing, beforeSpacerSize, afterSpacerSize)}
1803
+ >
1804
+ {#if column.getCanFilter()}
1805
+ <input
1806
+ {...getColumnFilterInputProps(column, {
1807
+ label: getColumnHeaderText(column),
1808
+ value: getColumnFilterText($state.columnFilters, column.id),
1809
+ })}
1810
+ class="og-grid__filter-input"
1811
+ on:input={(event) => handleColumnFilterInput(column, event)}
1812
+ />
1813
+ {/if}
1814
+ </div>
1815
+ {/each}
1816
+ </div>
1817
+ {/if}
1818
+ </div>
1819
+
1820
+ <div
1821
+ {...getGridBodyProps({ virtualized: rowVirtualOptions.enabled, columnOrder: $state.columnOrder })}
1822
+ class="og-grid__body"
1823
+ bind:this={bodyElement}
1824
+ style={getVirtualBodyStyleText(virtualRange)}
1825
+ on:focusin={handleSimpleBodyFocusIn}
1826
+ on:pointerdown={handleSimpleBodyPointerDown}
1827
+ on:pointerover={handleSimpleBodyPointerOver}
1828
+ on:pointerup={handleSimpleBodyPointerUp}
1829
+ on:click={handleSimpleBodyClick}
1830
+ on:dblclick={handleSimpleBodyDoubleClick}
1831
+ >
1832
+ {#if allRows.length === 0}
1833
+ <div {...getGridEmptyRowProps({ rowIndexOffset: bodyRowIndexOffset })} class="og-grid__empty">
1834
+ <div {...getGridEmptyCellProps({ rowIndexOffset: bodyRowIndexOffset, columnCount: visibleColumns.length })} class="og-grid__empty-cell" style={getInlineSizeStyleText(totalMeasuredWidth)}>{String(emptyState ?? "")}</div>
1835
+ </div>
1836
+ {:else if simpleCellRendering}
1837
+ {#each visibleRowItems as { row, rowIndex, virtualItem } (row.id)}
1838
+ <div
1839
+ {...getRowProps(row, rowIndex, { selected: grid.getIsRowSelected(row.id), rowIndexOffset: bodyRowIndexOffset })}
1840
+ {...getRowLayoutProps(row, {
1841
+ expanded: grid.getIsRowExpanded(row.id),
1842
+ virtualIndex: virtualItem?.index,
1843
+ columnVirtualized: columnVirtualOptions.enabled,
1844
+ })}
1845
+ class={["og-grid__row", getRowClassName?.(row)].filter(Boolean).join(" ")}
1846
+ style={getVirtualRowStyleText(virtualItem)}
1847
+ use:measureVirtualRow={row.id}
1848
+ >{@html getSimpleCellsHtml(row, rowIndex, columnCellRenderItems, $state.columnSizing, bodyRowIndexOffset, getCellClassName)}</div>
1849
+ {/each}
1850
+ {:else}
1851
+ {#each visibleRowItems as { row, rowIndex, virtualItem } (row.id)}
1852
+ <div
1853
+ {...getRowProps(row, rowIndex, { selected: grid.getIsRowSelected(row.id), rowIndexOffset: bodyRowIndexOffset })}
1854
+ {...getRowLayoutProps(row, {
1855
+ expanded: grid.getIsRowExpanded(row.id),
1856
+ virtualIndex: virtualItem?.index,
1857
+ columnVirtualized: columnVirtualOptions.enabled,
1858
+ })}
1859
+ class={["og-grid__row", getRowClassName?.(row)].filter(Boolean).join(" ")}
1860
+ style={getVirtualRowStyleText(virtualItem)}
1861
+ use:measureVirtualRow={row.id}
1862
+ on:click={(event) => handleRowClick(row, rowIndex, event)}
1863
+ >
1864
+ {#each columnCellRenderItems as { layout, beforeSpacerSize, afterSpacerSize, column, columnIndex } (layout.id)}
1865
+ {@const editing = grid.getIsCellEditing(row.id, column.id)}
1866
+ {@const rangeSelected = grid.getIsCellRangeSelected(row.id, column.id)}
1867
+ {@const groupLabelCell = getIsGroupLabelCell(row, column, visibleColumns)}
1868
+ {@const fillHandleVisible = getFillHandleVisible(row, column, editing, rangeSelected)}
1869
+ <div
1870
+ {...getCellProps(row, column, rowIndex, columnIndex, { focusedCell: $state.focusedCell, editing, rangeSelected, rowIndexOffset: bodyRowIndexOffset })}
1871
+ {...getCellLayoutProps({ invalid: Boolean(editing && editValidationMessage), pinnedEdge: layout.pinnedEdge })}
1872
+ class={["og-grid__cell", getCellClassName?.({ grid, row, column, value: row.getValue(column.id) })].filter(Boolean).join(" ")}
1873
+ style={getColumnStyle(layout.size, layout, $state.columnSizing, beforeSpacerSize, afterSpacerSize)}
1874
+ on:focus={() => grid.setFocusedCell({ rowId: row.id, columnId: column.id })}
1875
+ on:pointerdown={(event) => handleCellPointerDown({ rowId: row.id, columnId: column.id }, event)}
1876
+ on:pointerenter={(event) => handleCellPointerEnter({ rowId: row.id, columnId: column.id }, event)}
1877
+ on:pointerup={handleCellPointerUp}
1878
+ on:click={(event) => handleCellClick(row, rowIndex, column, columnIndex, event)}
1879
+ on:dblclick={(event) => handleCellDoubleClick(row, column, event)}
1880
+ >
1881
+ {#if fillHandleVisible}
1882
+ <span
1883
+ {...getCellFillHandleProps()}
1884
+ class="og-grid__fill-handle"
1885
+ on:pointerdown={(event) => handleFillPointerDown({ rowId: row.id, columnId: column.id }, event)}
1886
+ ></span>
1887
+ {/if}
1888
+ {#if editing}
1889
+ {#if column.columnDef.editOptions}
1890
+ <select
1891
+ {...getCellEditorProps(column, { invalid: Boolean(editValidationMessage) })}
1892
+ use:focusEditor
1893
+ class="og-grid__cell-editor"
1894
+ bind:value={editDraft}
1895
+ on:change={() => {
1896
+ editValidationMessage = null;
1897
+ }}
1898
+ on:click|stopPropagation
1899
+ on:dblclick|stopPropagation
1900
+ on:keydown={(event) => {
1901
+ stopEventPropagation(event);
1902
+
1903
+ const editorAction = getCellEditorKeyboardAction(event);
1904
+
1905
+ if (!editorAction) {
1906
+ return;
1907
+ }
1908
+
1909
+ preventEventDefault(event);
1910
+
1911
+ if (editorAction === "commit-edit") {
1912
+ commitCellEdit(row, column, event);
1913
+ } else {
1914
+ editValidationMessage = null;
1915
+ grid.cancelCellEdit(event);
1916
+ scheduleFocusedCellRestore();
1917
+ }
1918
+ }}
1919
+ on:blur={(event) => {
1920
+ if (grid.getIsCellEditing(row.id, column.id)) {
1921
+ commitCellEdit(row, column, event);
1922
+ }
1923
+ }}
1924
+ >
1925
+ {#each column.columnDef.editOptions as option}
1926
+ <option {...getCellEditorOptionProps(option)}>{getCellEditorOptionText(option)}</option>
1927
+ {/each}
1928
+ </select>
1929
+ {:else}
1930
+ <input
1931
+ {...getCellEditorProps(column, { invalid: Boolean(editValidationMessage) })}
1932
+ use:focusEditor
1933
+ class="og-grid__cell-editor"
1934
+ bind:value={editDraft}
1935
+ on:input={() => {
1936
+ editValidationMessage = null;
1937
+ }}
1938
+ on:click|stopPropagation
1939
+ on:dblclick|stopPropagation
1940
+ on:keydown={(event) => {
1941
+ stopEventPropagation(event);
1942
+
1943
+ const editorAction = getCellEditorKeyboardAction(event);
1944
+
1945
+ if (!editorAction) {
1946
+ return;
1947
+ }
1948
+
1949
+ preventEventDefault(event);
1950
+
1951
+ if (editorAction === "commit-edit") {
1952
+ commitCellEdit(row, column, event);
1953
+ } else {
1954
+ editValidationMessage = null;
1955
+ grid.cancelCellEdit(event);
1956
+ scheduleFocusedCellRestore();
1957
+ }
1958
+ }}
1959
+ on:blur={(event) => {
1960
+ if (grid.getIsCellEditing(row.id, column.id)) {
1961
+ commitCellEdit(row, column, event);
1962
+ }
1963
+ }}
1964
+ />
1965
+ {/if}
1966
+ {#if editValidationMessage}
1967
+ <span {...getCellValidationMessageProps()} class="og-grid__cell-validation">{editValidationMessage}</span>
1968
+ {/if}
1969
+ {:else if groupLabelCell}
1970
+ {@const groupCountText = getGroupRowCountText(row)}
1971
+ <span class="og-grid__group-cell" style={getGroupCellIndentStyleText(row)}>
1972
+ {#if row.getCanExpand()}
1973
+ <button
1974
+ {...getRowExpansionToggleProps(row, { expanded: grid.getIsRowExpanded(row.id), label: String(getGroupLabel(row, column)) })}
1975
+ class="og-grid__group-toggle"
1976
+ on:pointerdown|stopPropagation
1977
+ on:click={(event) => {
1978
+ preventDefaultAndStopPropagation(event);
1979
+ grid.toggleRowExpanded(row.id);
1980
+ }}
1981
+ >
1982
+ {getRowExpansionToggleText(grid.getIsRowExpanded(row.id))}
1983
+ </button>
1984
+ {:else}
1985
+ <span {...getRowExpansionSpacerProps()} class="og-grid__group-toggle-spacer"></span>
1986
+ {/if}
1987
+ <span class="og-grid__group-label">{getGroupLabel(row, column)}</span>
1988
+ {#if groupCountText}
1989
+ <span class="og-grid__group-count">{groupCountText}</span>
1990
+ {/if}
1991
+ </span>
1992
+ {:else}
1993
+ {getCellValue(row, column)}
1994
+ {/if}
1995
+ </div>
1996
+ {/each}
1997
+ </div>
1998
+ {/each}
1999
+ {/if}
2000
+ </div>
2001
+ </div>
2002
+ </div>
2003
+ {#if error}
2004
+ <div {...getGridErrorOverlayProps()} class="og-grid__status-overlay og-grid__error-overlay">
2005
+ <span class="og-grid__status-text">{String(errorState ?? "")}</span>
2006
+ {#if onRetry}
2007
+ <button {...getGridErrorRetryButtonProps()} class="og-grid__retry-button" on:click={onRetry}>{getGridErrorRetryButtonText()}</button>
2008
+ {/if}
2009
+ </div>
2010
+ {:else if loading}
2011
+ <div {...getGridLoadingOverlayProps()} class="og-grid__status-overlay og-grid__loading-overlay">
2012
+ <span class="og-grid__loading-spinner" aria-hidden="true"></span>
2013
+ <span class="og-grid__status-text">{String(loadingState ?? "")}</span>
2014
+ </div>
2015
+ {/if}
2016
+ </div>
2017
+ {#if paginationControls}
2018
+ <nav {...getPaginationProps()} class="og-grid__pagination">
2019
+ <div class="og-grid__pagination-buttons">
2020
+ <button {...getPaginationButtonProps({ action: "first", disabled: !canPreviousPage })} class="og-grid__pagination-button" on:click={() => runPaginationAction(() => grid.firstPage())}>{getPaginationButtonText("first")}</button>
2021
+ <button {...getPaginationButtonProps({ action: "previous", disabled: !canPreviousPage })} class="og-grid__pagination-button" on:click={() => runPaginationAction(() => grid.previousPage())}>{getPaginationButtonText("previous")}</button>
2022
+ <span {...getPaginationStatusProps()} class="og-grid__pagination-status">{paginationPageText}</span>
2023
+ <button {...getPaginationButtonProps({ action: "next", disabled: !canNextPage })} class="og-grid__pagination-button" on:click={() => runPaginationAction(() => grid.nextPage())}>{getPaginationButtonText("next")}</button>
2024
+ <button {...getPaginationButtonProps({ action: "last", disabled: !canNextPage })} class="og-grid__pagination-button" on:click={() => runPaginationAction(() => grid.lastPage())}>{getPaginationButtonText("last")}</button>
2025
+ </div>
2026
+ <select {...getPaginationPageSizeSelectProps($state.pagination.pageSize)} class="og-grid__pagination-size" on:change={handlePageSizeChange}>
2027
+ {#each resolvedPageSizeOptions as pageSize (pageSize)}
2028
+ <option value={pageSize}>{getPaginationPageSizeOptionText(pageSize)}</option>
2029
+ {/each}
2030
+ </select>
2031
+ </nav>
2032
+ {/if}