flexi-datagrid 1.2.6 → 1.2.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -18,7 +18,10 @@ A modern, highly customizable, and feature-rich React data grid component design
18
18
  - **Custom Cell Rendering:** Full control over how each cell is rendered via a `render` function.
19
19
  - **Conditional Row Styling (AG Grid-style `rowClassRules`):** Map class names to predicates evaluated per row to highlight/style rows based on their data.
20
20
  - **Row Click Events:** Pass `onRowClick` to react to clicks anywhere on a data row.
21
+ - **Row Spanning / Cell Merging:** Pass `rowSpanColumns` (e.g. `['city', 'department']`) to vertically merge consecutive rows that share the same value, hierarchically — like a spreadsheet merge.
21
22
  - **Virtualized Rows for Large Datasets:** Only the rows in (and just around) the visible viewport are mounted, so grids with thousands of rows stay smooth to scroll and filter.
23
+ - **Display Settings Menu:** Opt-in toolbar (`controlPanel`) with a single Settings menu button that opens a dropdown for all display configuration — toggling pin/filter icon hover visibility, showing/hiding individual columns, and resetting to defaults — with `defaultDisplayPinnedIcon`/`defaultHiddenColumns` to configure the starting display without the panel.
24
+ - **Pagination:** Opt-in (`pagination`) page-based navigation with a configurable page size, a rows-per-page selector, and first/previous/next/last controls, as an alternative to (or alongside) virtualized infinite scrolling.
22
25
  - **Full TypeScript Support:** Strongly typed configurations for robust developer experience.
23
26
 
24
27
  ---
@@ -98,6 +101,8 @@ export default function EmployeeTable() {
98
101
  'row-high-earner': (record) => record.salary > 100000,
99
102
  }}
100
103
  onRowClick={(record) => console.log('clicked', record.id)}
104
+ rowSpanColumns={['department']}
105
+ controlPanel
101
106
  actions={(record) => (
102
107
  <button onClick={(e) => { e.stopPropagation(); console.log('Edit', record.id); }}>Edit</button>
103
108
  )}
@@ -158,6 +163,15 @@ Each entry in `columns` describes one column (or a group of columns, via `childr
158
163
  | `onColumnReorder` | `(orderedLeafKeys: string[]) => void` | Called whenever columns are reordered via drag-and-drop, with the full leaf column order. |
159
164
  | `rowClassRules` | `Record<string, (record: T, index: number) => boolean>` | AG Grid-style conditional row classes: maps a class name to a predicate. Every rule whose predicate returns `true` for a row is applied to that row's `<tr>`. Does not apply to `pinnedBottomRowData` rows (use `pinnedRowClassName` for those). |
160
165
  | `onRowClick` | `(record: T, index: number, event: React.MouseEvent) => void` | Called when a data row is clicked, with the row's record, its index in the currently rendered (filtered/sorted/grouped) rows, and the click event. Adds a pointer cursor to rows when set. |
166
+ | `rowSpanColumns` | `string[]` | Column keys, in priority order, to vertically merge across consecutive rows sharing the same value — e.g. `['city', 'department']`. See [Row Spanning](#row-spanning--cell-merging). |
167
+ | `controlPanel` | `boolean` | Renders an opt-in toolbar above the grid with a Settings menu button for end-user display configuration (pin/filter icon hover visibility, column show/hide). Defaults to `false` — existing grids are unaffected unless you turn it on. See [Control Panel](#control-panel). |
168
+ | `defaultDisplayPinnedIcon` | `boolean` | Initial value of the flag that controls whether the pin and filter icons in leaf column headers reveal on hover at all. When `false`, neither icon is shown on hover, regardless of `controlPanel`. Defaults to `true` (matches prior behavior). |
169
+ | `defaultHiddenColumns` | `string[]` | Leaf column keys hidden by default, independent of `controlPanel`. Defaults to `[]`. |
170
+ | `onColumnVisibilityChange` | `(hiddenKeys: string[]) => void` | Called whenever the set of hidden columns changes via the control panel, with the full list of currently hidden leaf column keys. |
171
+ | `pagination` | `boolean` | Enables page-based navigation, rendering a pagination panel below the grid instead of relying solely on scrolling. Defaults to `false`. See [Pagination](#pagination). |
172
+ | `defaultPageSize` | `number` | Initial number of rows per page when `pagination` is enabled. Defaults to `10`. |
173
+ | `pageSizeOptions` | `number[]` | Options shown in the rows-per-page selector. Defaults to `[10, 25, 50, 100]`. |
174
+ | `onPageChange` | `(page: number, pageSize: number) => void` | Called whenever the current page or page size changes. |
161
175
 
162
176
  ### Filtering
163
177
 
@@ -205,6 +219,74 @@ Pass `onRowClick` to be notified when a data row is clicked; the grid adds a poi
205
219
  />
206
220
  ```
207
221
 
222
+ ### Row Spanning / Cell Merging
223
+
224
+ Pass `rowSpanColumns` — an ordered array of column keys — to merge a column's cell vertically across consecutive rows that share the same value, similar to a merged-cell spreadsheet. The order is a priority/hierarchy: a later key only continues merging while every key before it also matches on that run of rows. For example, with `rowSpanColumns={['city', 'department']}`, `city` merges across every consecutive row with the same city, and `department` merges only across consecutive rows that share *both* the same city and the same department — so department merges nest inside city merges rather than spanning across a city boundary.
225
+
226
+ ```tsx
227
+ const data: Employee[] = [
228
+ { id: 1, city: 'Austin', department: 'Engineering', name: 'Ava Thompson' },
229
+ { id: 2, city: 'Austin', department: 'Engineering', name: 'Liam Carter' },
230
+ { id: 3, city: 'Austin', department: 'Design', name: 'Sofia Ramirez' },
231
+ { id: 4, city: 'Denver', department: 'Sales', name: 'Noah Patel' },
232
+ ];
233
+
234
+ <DataTable
235
+ columns={columns}
236
+ data={data}
237
+ rowKey="id"
238
+ rowSpanColumns={['city', 'department']}
239
+ />
240
+ ```
241
+
242
+ Here `city` spans 3 rows (Austin) then 1 row (Denver); `department` spans 2 rows (Engineering, within Austin) then 1 row each for Design and Sales.
243
+
244
+ Notes:
245
+
246
+ - Rows must already be sorted or grouped so that equal values for a spanned column are contiguous — spanning does not itself sort or reorder rows. Combine with `sortable` columns, `defaultGroupBy`, or pre-sorted `data` as needed.
247
+ - Values are compared with the column's `getValue` (if provided) or `record[key]`, same as sorting/filtering.
248
+ - Spanning only merges the cells of plain data rows; group header rows (from `groupBy`) always break a run, and `pinnedBottomRowData` rows are never merged.
249
+ - Because rows are virtualized, spans are computed within the currently rendered window rather than across the entire dataset — a run that starts above the visible viewport will restart at the top of the window instead of continuing an off-screen merge. In practice this is invisible during normal scrolling since the affected rows aren't on screen at the same time.
250
+
251
+ ### Control Panel
252
+
253
+ Pass `controlPanel` to render a small toolbar above the grid (and above the grouping drop zone, if `groupBy` is also on) with a single **Settings** menu button. Clicking it opens a dropdown with every display option in one place, instead of a row of separate buttons:
254
+
255
+ - **Show pin/filter icons on hover** — toggles `displayPinnedIcon`. When off, neither the pin button nor the filter button reveals on hovering a column header, regardless of whether the column is `filterable`; when on, hovering a leaf column header's action area reveals its filter icon (if `filterable`) and pin icon.
256
+ - **Columns** — a checklist of every leaf column (by `header` label) to show or hide it. Hiding a column removes it from both the header and body, and adjusts any parent group header's `colSpan` automatically.
257
+ - **Reset to defaults** — restores pin/filter icon hover visibility and hidden columns to the values passed via `defaultDisplayPinnedIcon` / `defaultHiddenColumns` (not to "all columns visible" — it resets to *your configured defaults*, so a column you deliberately hid by default stays hidden after reset).
258
+
259
+ ```tsx
260
+ <DataTable
261
+ columns={columns}
262
+ data={data}
263
+ rowKey="id"
264
+ controlPanel
265
+ defaultHiddenColumns={['role']}
266
+ onColumnVisibilityChange={(hiddenKeys) => console.log('hidden columns', hiddenKeys)}
267
+ />
268
+ ```
269
+
270
+ `controlPanel` is opt-in and defaults to `false`, so existing grids render exactly as before unless you enable it. `defaultDisplayPinnedIcon` and `defaultHiddenColumns` are independent of `controlPanel` — you can use either to fix a display configuration (e.g. permanently hide a column, or suppress pin/filter icons everywhere) without showing the panel UI at all.
271
+
272
+ ### Pagination
273
+
274
+ Pass `pagination` to render a pagination panel below the grid: a rows-per-page selector (`pageSizeOptions`, defaulting to `[10, 25, 50, 100]`), the current range/total (e.g. "1–10 of 42"), and first/previous/next/last page controls.
275
+
276
+ ```tsx
277
+ <DataTable
278
+ columns={columns}
279
+ data={data}
280
+ rowKey="id"
281
+ pagination
282
+ defaultPageSize={25}
283
+ pageSizeOptions={[25, 50, 100]}
284
+ onPageChange={(page, pageSize) => console.log('page', page, 'size', pageSize)}
285
+ />
286
+ ```
287
+
288
+ Pagination is applied after filtering and sorting, and before row grouping — so with `groupBy` also enabled, each page groups only the rows that landed on it. Changing a filter, the sort, or the page size automatically resets you to page 1. `pagination` is independent of virtualization (`rowHeight`/`overscan`): you can use either or both, though with a small `pageSize` a bounded, scrollable `containerStyle` is rarely necessary since a page's rows will usually fit without scrolling.
289
+
208
290
  ### Row Grouping
209
291
 
210
292
  Set `groupBy` to enable a drop zone above the table. Dragging a sortable/leaf column header into it groups rows by that column's value, with collapsible group rows and per-group row counts. Multiple columns can be stacked to create nested groups; use `defaultGroupBy` to pre-group on mount.
@@ -287,13 +369,128 @@ Row rendering is virtualized: only the rows visible inside the scrollable contai
287
369
  Virtualization requires a bounded, scrollable container and a known row height:
288
370
 
289
371
  - Give the grid a `maxHeight` via `containerStyle` so `flexi-table-wrap` actually scrolls (see [Sticky Header](#sticky-header)) — without a bounded height, the browser page itself scrolls and every row has to be rendered.
290
- - The default row height is `48px`, matching the built-in CSS. If you override row height via custom CSS, pass the same value through the `rowHeight` prop so the virtualizer's scroll math stays accurate.
372
+ - The default row height is `48px`, matching the built-in CSS. If you override row height via custom CSS, pass the same value through the `rowHeight` prop so the virtualizer's scroll math stays accurate — group header rows (from `groupBy`) are also sized to `rowHeight` internally for the same reason, so a custom `rowHeight` no longer causes drift (previously visible as blank gaps or an inability to fully reach the last rows once scrolled to the bottom).
291
373
  - Tune `overscan` (default `8`) if you see blank flashes on very fast scrolling — a higher value trades a bit of extra rendering for smoother scroll.
292
374
 
293
375
  Filtering is also optimized for large datasets: a filter column's unique values are computed once per column (not on every keystroke), and column lookups used during filtering/sorting/grouping are indexed by key instead of scanned linearly.
294
376
 
295
377
  ---
296
378
 
379
+ ## Full Example (Every Feature)
380
+
381
+ A single grid wiring up every prop at once — nested/grouped headers, pinning, resizing, reordering, filtering, sorting, row grouping, row spanning, the control panel, pagination, pinned bottom totals, conditional row styling, row clicks, and virtualization:
382
+
383
+ ```tsx
384
+ import { DataTable, type ColumnConfig } from 'flexi-datagrid';
385
+ import 'flexi-datagrid/dist/flexi-datagrid.css';
386
+
387
+ interface Employee {
388
+ id: number;
389
+ city: string;
390
+ department: string;
391
+ name: string;
392
+ role: string;
393
+ baseSalary: number;
394
+ bonus: number;
395
+ }
396
+
397
+ const data: Employee[] = [
398
+ { id: 1, city: 'Austin', department: 'Engineering', name: 'Ava Thompson', role: 'Frontend Developer', baseSalary: 95000, bonus: 8000 },
399
+ { id: 2, city: 'Austin', department: 'Engineering', name: 'Liam Carter', role: 'Backend Developer', baseSalary: 102000, bonus: 9500 },
400
+ { id: 3, city: 'Austin', department: 'Design', name: 'Sofia Ramirez', role: 'Product Designer', baseSalary: 88000, bonus: 4000 },
401
+ { id: 4, city: 'Denver', department: 'Sales', name: 'Noah Patel', role: 'Account Executive', baseSalary: 76000, bonus: 12000 },
402
+ ];
403
+
404
+ const columns: ColumnConfig<Employee>[] = [
405
+ { key: 'city', header: 'City', sortable: true, filterable: true, minWidth: 100, maxWidth: 240 },
406
+ { key: 'department', header: 'Department', sortable: true, filterable: true, minWidth: 120 },
407
+ {
408
+ key: 'name',
409
+ header: 'Name',
410
+ sortable: true,
411
+ filterable: true,
412
+ pinned: 'left',
413
+ minWidth: 160,
414
+ maxWidth: 500,
415
+ style: { width: '220px' },
416
+ },
417
+ { key: 'role', header: 'Role', sortable: true, filterable: true, resizable: false },
418
+ {
419
+ key: 'compensation',
420
+ header: 'Compensation',
421
+ children: [
422
+ {
423
+ key: 'baseSalary',
424
+ header: 'Base Salary',
425
+ sortable: true,
426
+ pinned: 'right',
427
+ render: (value) => `$${value.toLocaleString()}`,
428
+ pinnedRender: () => <strong>Total</strong>,
429
+ },
430
+ {
431
+ key: 'bonus',
432
+ header: 'Bonus',
433
+ sortable: true,
434
+ render: (value) => `$${value.toLocaleString()}`,
435
+ pinnedRender: (value) => <strong>${value.toLocaleString()}</strong>,
436
+ },
437
+ ],
438
+ },
439
+ ];
440
+
441
+ export default function EmployeeTable() {
442
+ return (
443
+ <DataTable<Employee>
444
+ columns={columns}
445
+ data={data}
446
+ rowKey="id"
447
+ bordered
448
+ containerStyle={{ maxHeight: 480 }}
449
+ tableStyle={{ minWidth: 900 }}
450
+ rowHeight={48}
451
+ overscan={8}
452
+ groupBy
453
+ defaultGroupBy={[]}
454
+ onGroupChange={(groupedKeys) => console.log('grouped by', groupedKeys)}
455
+ rowSpanColumns={['city', 'department']}
456
+ onColumnResize={(key, width) => console.log('resized', key, width)}
457
+ onColumnReorder={(orderedKeys) => console.log('reordered', orderedKeys)}
458
+ onPinChange={(key, isPinned, side) => console.log('pin change', key, isPinned, side)}
459
+ controlPanel
460
+ defaultDisplayPinnedIcon={true}
461
+ defaultHiddenColumns={[]}
462
+ onColumnVisibilityChange={(hiddenKeys) => console.log('hidden columns', hiddenKeys)}
463
+ pagination
464
+ defaultPageSize={25}
465
+ pageSizeOptions={[25, 50, 100]}
466
+ onPageChange={(page, pageSize) => console.log('page', page, 'size', pageSize)}
467
+ pinnedBottomRowData={[
468
+ { id: -1, city: '', department: '', name: '', role: '', baseSalary: 0, bonus: data.reduce((sum, r) => sum + r.bonus, 0) },
469
+ ]}
470
+ pinnedRowClassName="totals-row"
471
+ rowClassRules={{
472
+ 'row-high-earner': (record) => record.baseSalary > 100000,
473
+ }}
474
+ onRowClick={(record, index) => console.log('clicked row', index, record.name)}
475
+ actions={(record) => (
476
+ <button onClick={(e) => { e.stopPropagation(); console.log('Edit', record.id); }}>Edit</button>
477
+ )}
478
+ />
479
+ );
480
+ }
481
+ ```
482
+
483
+ Notes on combining features in this example:
484
+
485
+ - `city`/`department` are left as plain leaf columns (not pinned/nested) so `rowSpanColumns={['city', 'department']}` can merge them — `data` is already sorted by city then department, which the merge requires (see [Row Spanning](#row-spanning--cell-merging)).
486
+ - `compensation` demonstrates a nested/grouped header: `baseSalary` and `bonus` render under a shared "Compensation" header cell, and `baseSalary` is independently pinned right.
487
+ - `pinnedRender` on `baseSalary`/`bonus` overrides how the totals row renders those two cells only; every other column in `pinnedBottomRowData` falls back to its normal `render`.
488
+ - `groupBy` is enabled with an empty `defaultGroupBy` — the grid starts flat (so the row spans above are visible) but a user can still drag `role`, for example, into the drop zone to group by it on top of everything else.
489
+ - `controlPanel` adds the Settings menu button for pin/filter icon hover visibility and column visibility toggling on top of the `defaultDisplayPinnedIcon`/`defaultHiddenColumns` starting state — omit `controlPanel` (or leave it `false`) to keep those two props as a fixed, non-interactive configuration instead.
490
+ - `pagination` slices the filtered/sorted rows into pages of `defaultPageSize` (25 here) before grouping — combine cautiously with `rowSpanColumns`, since spans only merge cells within the current page's rows.
491
+
492
+ ---
493
+
297
494
  ## 🛠 Local Development
298
495
 
299
496
  ```bash
@@ -34,20 +34,21 @@ interface DataTableProps<T> {
34
34
  bordered?: boolean;
35
35
  pinnedBottomRowData?: T[];
36
36
  pinnedRowClassName?: string | ((record: T, index: number) => string);
37
- /** Row height in px, used for virtualized row rendering. Must match the CSS row height. */
38
37
  rowHeight?: number;
39
- /** Number of off-screen rows rendered above/below the viewport to smooth scrolling. */
40
38
  overscan?: number;
41
39
  onColumnResize?: (columnKey: string, width: number) => void;
42
40
  onColumnReorder?: (orderedLeafKeys: string[]) => void;
43
- /**
44
- * AG Grid-style conditional row classes: maps a class name to a predicate that receives
45
- * the row's record and its index within the currently rendered (filtered/sorted/flattened)
46
- * rows. Every rule whose predicate returns true is applied; rules are re-evaluated whenever
47
- * `data` or its derived filtered/sorted/grouped order changes.
48
- */
49
41
  rowClassRules?: Record<string, (record: T, index: number) => boolean>;
50
42
  onRowClick?: (record: T, index: number, event: React.MouseEvent<HTMLTableRowElement>) => void;
43
+ rowSpanColumns?: string[];
44
+ controlPanel?: boolean;
45
+ defaultDisplayPinnedIcon?: boolean;
46
+ defaultHiddenColumns?: string[];
47
+ onColumnVisibilityChange?: (hiddenKeys: string[]) => void;
48
+ pagination?: boolean;
49
+ defaultPageSize?: number;
50
+ pageSizeOptions?: number[];
51
+ onPageChange?: (page: number, pageSize: number) => void;
51
52
  }
52
- export declare function DataTable<T>({ columns, data, rowKey, actions, isLoading, groupBy, defaultGroupBy, onPinChange, onGroupChange, containerStyle, tableStyle, bordered, pinnedBottomRowData, pinnedRowClassName, rowHeight, overscan, onColumnResize, onColumnReorder, rowClassRules, onRowClick, }: DataTableProps<T>): React.JSX.Element;
53
+ export declare function DataTable<T>({ columns, data, rowKey, actions, isLoading, groupBy, defaultGroupBy, onPinChange, onGroupChange, containerStyle, tableStyle, bordered, pinnedBottomRowData, pinnedRowClassName, rowHeight, overscan, onColumnResize, onColumnReorder, rowClassRules, onRowClick, rowSpanColumns, controlPanel, defaultDisplayPinnedIcon, defaultHiddenColumns, onColumnVisibilityChange, pagination, defaultPageSize, pageSizeOptions, onPageChange, }: DataTableProps<T>): React.JSX.Element;
53
54
  export {};
@@ -1,2 +1,2 @@
1
- .flexi-root{width:100%;position:relative}.flexi-root>*+*{margin-top:.75rem}.flexi-groupzone{box-sizing:border-box;background-color:#f8fafc80;border:1px dashed #e2e8f0;border-radius:.75rem;align-items:center;gap:.5rem;width:100%;min-height:52px;padding:.5rem 1rem;transition:all .2s;display:flex}.flexi-groupzone--dragover{background-color:#eff6ff80;border:1px solid #3b82f6;transform:scale(.99)}.flexi-groupzone-hint{color:#64748b;-webkit-user-select:none;user-select:none;font-size:12px;font-weight:500}.flexi-groupzone-chips{flex-wrap:wrap;align-items:center;gap:.375rem;display:flex}.flexi-groupzone-label{text-transform:uppercase;letter-spacing:.05em;color:#64748b;margin-right:.25rem;font-size:11px;font-weight:700}.flexi-chip{color:#64748b;background-color:#fff;border:1px solid #e2e8f0;border-radius:.5rem;align-items:center;gap:.375rem;padding:.25rem .25rem .25rem .5rem;font-size:12px;font-weight:600;display:flex;box-shadow:0 1px 2px #0000000d}.flexi-chip-remove{border-radius:5px;width:18px;height:18px}.flexi-table-wrap{box-sizing:border-box;background-color:#fff;border:1px solid #f1f5f9;border-radius:.75rem;width:100%;overflow:auto;box-shadow:0 1px 2px #0000000d}.flexi-table{caption-side:bottom;border-collapse:collapse;border-spacing:0;table-layout:fixed;width:100%;font-size:.875rem;position:relative}.flexi-table--bordered th,.flexi-table--bordered td{border:1px solid #e2e8f0}.flexi-thead{-webkit-user-select:none;user-select:none;z-index:40;position:sticky}.flexi-header-row{border-bottom:1px solid #f1f5f9}.flexi-group-spacer-th{border-right:1px solid #f1f5f9;width:48px}.flexi-th{text-align:center;vertical-align:middle;z-index:25;background-color:#f8fafc;border-bottom:1px solid #f1f5f9;border-right:1px solid #f1f5f9;height:48px;padding:0 1rem;font-size:12px;font-weight:700;position:sticky;top:0}.flexi-th--draggable{cursor:grab}.flexi-th--draggable:active{cursor:grabbing}.flexi-th--pinned{background-color:#f8fafc}.flexi-th-inner{justify-content:space-between;align-items:center;gap:.25rem;width:100%;min-width:0;display:flex}.flexi-th-label{white-space:nowrap;text-overflow:ellipsis;flex:auto;align-items:center;gap:.375rem;min-width:0;display:flex;overflow:hidden}.flexi-th-label--sortable{cursor:pointer;-webkit-user-select:none;user-select:none}.flexi-th-label--sortable:hover{color:#2563eb}.flexi-th-text{white-space:nowrap;text-overflow:ellipsis;flex:auto;min-width:0;overflow:hidden}.flexi-sort-icon{color:#94a3b8;opacity:0;transition:color .15s,opacity .15s}.flexi-sort-icon--active{color:#2563eb;opacity:1}.flexi-th-inner:hover .flexi-sort-icon{opacity:1}.flexi-th-actions{flex:none;align-items:center;gap:2px;display:flex}.flexi-th-resizer{cursor:col-resize;touch-action:none;-webkit-user-select:none;user-select:none;z-index:5;width:6px;position:absolute;top:0;bottom:0;right:0}.flexi-th-resizer:hover,.flexi-th-resizer:active{background-color:#93c5fd}.flexi-icon-btn{color:#64748b;background-color:#0000;border-radius:6px;justify-content:center;align-items:center;width:22px;height:22px;margin:0;padding:0;transition:background-color .15s,color .15s,transform .1s;display:inline-flex}.flexi-icon-btn:hover{color:#334155;background-color:#e2e8f0}.flexi-icon-btn:active{background-color:#cbd5e1;transform:scale(.92)}.flexi-icon-btn:focus-visible{outline-offset:1px;outline:2px solid #93c5fd}.flexi-filter-btn{color:#64748b;opacity:0}.flexi-filter-btn--active{color:#2563eb;opacity:1}.flexi-filter-btn--active:hover{color:#2563eb;background-color:#dbeafe}.flexi-pin-btn{color:#cbd5e1;opacity:0}.flexi-pin-btn--pinned{color:#3b82f6;opacity:1}.flexi-pin-btn--pinned:hover{color:#2563eb;background-color:#dbeafe}.flexi-th-inner:hover .flexi-filter-btn,.flexi-th-inner:hover .flexi-pin-btn{opacity:1}.flexi-actions-th{vertical-align:middle;text-align:right;height:48px;padding:0 1rem;font-size:12px;font-weight:700;box-shadow:-1px 0 #e2e8f0}.flexi-filter-dropdown{color:#334155;text-align:left;pointer-events:auto;background-color:#fff;border:1px solid #e2e8f0;border-radius:.5rem;flex-direction:column;width:13rem;padding:.5rem 0;font-weight:400;display:flex;position:absolute;box-shadow:0 10px 15px -3px #0000001a,0 4px 6px -4px #0000001a}.flexi-filter-search{border-bottom:1px solid #f1f5f9;margin-bottom:.25rem;padding:0 .5rem .5rem}.flexi-input{color:#475569;box-sizing:border-box;border:1px solid #e2e8f0;border-radius:.25rem;width:100%;padding:.25rem .5rem;font-size:12px;font-weight:500}.flexi-input:focus{border-color:#3b82f6;outline:none}.flexi-filter-list{flex-direction:column;gap:.25rem;max-height:10rem;padding:0 .5rem;display:flex;overflow-y:auto}.flexi-filter-empty{color:#64748b;text-align:center;padding:.5rem;font-size:11px}.flexi-filter-option{cursor:pointer;-webkit-user-select:none;user-select:none;border-radius:.25rem;align-items:center;gap:.5rem;padding:.375rem .5rem;font-size:12px;display:flex}.flexi-filter-option:hover{background-color:#f8fafc}.flexi-filter-option-text{white-space:nowrap;text-overflow:ellipsis;overflow:hidden}.flexi-checkbox{accent-color:#2563eb;border:1px solid #cbd5e1;border-radius:.25rem;width:.875rem;height:.875rem}.flexi-checkbox:focus{outline-offset:1px;outline:2px solid #3b82f6}.flexi-filter-footer{border-top:1px solid #f1f5f9;justify-content:flex-end;margin-top:.5rem;padding:.25rem .5rem 0;display:flex}.flexi-filter-clear{color:#ef4444;padding:.125rem .5rem;font-size:11px;font-weight:500}.flexi-filter-clear:hover{text-decoration:underline}.flexi-tbody{display:table-row-group}.flexi-tbody tr+tr{border-top:1px solid #f1f5f9}.flexi-empty-cell{text-align:center;color:#64748b;padding:2rem;font-size:12px;font-weight:500}.flexi-group-row{-webkit-user-select:none;user-select:none;background-color:#f8fafc99;height:48px;transition:background-color .15s}.flexi-group-row:hover{background-color:#f1f5f9cc}.flexi-group-cell{color:#334155;border-right:1px solid #f1f5f9;padding:.75rem 1rem;font-size:12px;font-weight:600}.flexi-group-toggle{text-align:left;border-radius:6px;align-items:center;gap:.375rem;margin:-4px -8px -4px -4px;padding:4px 8px 4px 4px;transition:background-color .15s;display:flex}.flexi-group-toggle:hover{background-color:#e2e8f099}.flexi-group-toggle:focus-visible{outline-offset:1px;outline:2px solid #93c5fd}.flexi-icon-muted{color:#64748b}.flexi-group-value{color:#1e293b;white-space:nowrap;text-overflow:ellipsis;width:10rem;font-weight:700;overflow:hidden}.flexi-group-count{color:#64748b;background-color:#e2e8f0b3;border-radius:9999px;margin-left:.25rem;padding:.05rem .375rem;font-size:10px;font-weight:500}.flexi-group-filler{background-color:#f8fafc4d}.flexi-row{border-bottom:1px solid #f1f5f9;height:48px;transition:background-color .15s}.flexi-row:hover{background-color:#f8fafccc}.flexi-row--clickable{cursor:pointer}.flexi-row-indent-cell{background-color:#fff;border-right:1px solid #f1f5f9;width:48px}.flexi-cell{vertical-align:middle;padding:.25rem 1rem;transition:background-color .15s}.flexi-row:hover .flexi-cell--pinned{background-color:#f1f5f9}.flexi-cell-text{color:#64748b;white-space:nowrap;text-overflow:ellipsis;font-size:12px;font-weight:500;display:block;overflow:hidden}.flexi-actions-cell{vertical-align:middle;text-align:right;background-color:#fff;padding:.25rem 1rem;box-shadow:-1px 0 #e2e8f0}.flexi-row:hover .flexi-actions-cell{background-color:#f8fafccc}.flexi-tbody--pinned-bottom{box-shadow:0 -2px 5px -2px #0000001a}.flexi-row--pinned-bottom{background-color:#f8fafc;font-weight:600}.flexi-row--pinned-bottom:hover,.flexi-row--pinned-bottom .flexi-cell,.flexi-row--pinned-bottom .flexi-actions-cell{background-color:#f8fafc}.flexi-row--pinned-bottom .flexi-cell-text{color:#334155}.flexi-actions-inner{opacity:0;justify-content:flex-end;gap:.25rem;transition:opacity .15s;display:flex}.flexi-row:hover .flexi-actions-inner{opacity:1}.flexi-icon-xs{width:.75rem;height:.75rem}.flexi-icon-sm{width:.875rem;height:.875rem}.flexi-icon-md{width:1rem;height:1rem}.flexi-root button{appearance:none;cursor:pointer;font:inherit;color:inherit;background:0 0;border:none;margin:0;padding:0;line-height:normal}.flexi-root input{font:inherit}.flexi-root table{border-collapse:collapse;border-spacing:0}
1
+ .flexi-root{width:100%;position:relative}.flexi-root>*+*{margin-top:.75rem}.flexi-settings-menu{z-index:50;background-color:#fff;border:1px solid #e2e8f0;border-radius:.5rem;flex-direction:column;width:13rem;max-height:20rem;padding:.5rem;font-weight:400;display:flex;position:absolute;top:calc(100% + 4px);overflow-y:auto;box-shadow:0 10px 15px -3px #0000001a,0 4px 6px -4px #0000001a}.flexi-settings-section{flex-direction:column;gap:.125rem;display:flex}.flexi-settings-heading{text-transform:uppercase;letter-spacing:.05em;color:#94a3b8;padding:.25rem .5rem;font-size:11px;font-weight:700}.flexi-settings-divider{background-color:#f1f5f9;height:1px;margin:.375rem 0}.flexi-settings-reset{color:#ef4444;border-radius:.375rem;align-items:center;gap:.375rem;padding:.375rem .5rem;font-size:12px;font-weight:600;display:inline-flex}.flexi-settings-reset:hover{background-color:#fef2f2}.flexi-groupzone{box-sizing:border-box;background-color:#f8fafc80;border:1px dashed #e2e8f0;border-radius:.75rem;align-items:center;gap:.5rem;width:100%;min-height:52px;padding:.5rem 1rem;transition:all .2s;display:flex}.flexi-groupzone--dragover{background-color:#eff6ff80;border:1px solid #3b82f6;transform:scale(.99)}.flexi-groupzone-hint{color:#64748b;-webkit-user-select:none;user-select:none;font-size:12px;font-weight:500}.flexi-groupzone-chips{flex-wrap:wrap;align-items:center;gap:.375rem;display:flex}.flexi-groupzone-label{text-transform:uppercase;letter-spacing:.05em;color:#64748b;margin-right:.25rem;font-size:11px;font-weight:700}.flexi-chip{color:#64748b;background-color:#fff;border:1px solid #e2e8f0;border-radius:.5rem;align-items:center;gap:.375rem;padding:.25rem .25rem .25rem .5rem;font-size:12px;font-weight:600;display:flex;box-shadow:0 1px 2px #0000000d}.flexi-chip-remove{border-radius:5px;width:18px;height:18px}.flexi-table-wrap{box-sizing:border-box;background-color:#fff;border:1px solid #f1f5f9;border-radius:.75rem;width:100%;position:relative;overflow:auto;box-shadow:0 1px 2px #0000000d}.flexi-table{caption-side:bottom;border-collapse:collapse;border-spacing:0;table-layout:fixed;width:100%;font-size:.875rem;position:relative}.flexi-table--bordered th,.flexi-table--bordered td{border:1px solid #e2e8f0}.flexi-thead{-webkit-user-select:none;user-select:none;z-index:40;position:sticky}.flexi-header-row{border-bottom:1px solid #f1f5f9}.flexi-group-spacer-th{border-right:1px solid #f1f5f9;width:48px}.flexi-th{text-align:center;vertical-align:middle;z-index:25;background-color:#f8fafc;border-bottom:1px solid #f1f5f9;border-right:1px solid #f1f5f9;height:48px;padding:0 1rem;font-size:12px;font-weight:700;position:sticky;top:0}.flexi-th--draggable{cursor:grab}.flexi-th--draggable:active{cursor:grabbing}.flexi-th--pinned{background-color:#f8fafc}.flexi-th-inner{justify-content:space-between;align-items:center;gap:.25rem;width:100%;min-width:0;display:flex}.flexi-th-label{white-space:nowrap;text-overflow:ellipsis;flex:auto;align-items:center;gap:.375rem;min-width:0;display:flex;overflow:hidden}.flexi-th-label--sortable{cursor:pointer;-webkit-user-select:none;user-select:none}.flexi-th-label--sortable:hover{color:#2563eb}.flexi-th-text{white-space:nowrap;text-overflow:ellipsis;flex:auto;min-width:0;overflow:hidden}.flexi-sort-icon,.flexi-filter-btn,.flexi-pin-btn{color:#94a3b8;opacity:0;transition:color .15s,opacity .15s}.flexi-sort-icon--active{color:#2563eb;opacity:1}.flexi-th-inner:hover .flexi-sort-icon,.flexi-th-inner:hover .flexi-filter-btn,.flexi-th-inner:hover .flexi-pin-btn{opacity:1}.flexi-th-actions{flex:none;align-items:center;gap:2px;display:flex}.flexi-th-resizer{cursor:col-resize;touch-action:none;-webkit-user-select:none;user-select:none;z-index:5;width:6px;position:absolute;top:0;bottom:0;right:0}.flexi-th-resizer:hover,.flexi-th-resizer:active{background-color:#93c5fd}.flexi-icon-btn{color:#64748b;background-color:#0000;border-radius:6px;justify-content:center;align-items:center;width:22px;height:22px;margin:0;padding:0;transition:background-color .15s,color .15s,transform .1s;display:inline-flex}.flexi-icon-btn:hover{color:#334155;background-color:#e2e8f0}.flexi-icon-btn:active{background-color:#cbd5e1;transform:scale(.92)}.flexi-icon-btn:focus-visible{outline-offset:1px;outline:2px solid #93c5fd}.flexi-filter-btn--active{color:#2563eb;opacity:1}.flexi-filter-btn--active:hover{color:#2563eb;opacity:1;background-color:#dbeafe}.flexi-pin-btn--pinned{color:#3b82f6;opacity:1}.flexi-pin-btn--pinned:hover{color:#2563eb;opacity:1;background-color:#dbeafe}.flexi-actions-th{vertical-align:middle;text-align:right;height:48px;padding:0 1rem;font-size:12px;font-weight:700;box-shadow:-1px 0 #e2e8f0}.flexi-filter-dropdown{color:#334155;text-align:left;pointer-events:auto;background-color:#fff;border:1px solid #e2e8f0;border-radius:.5rem;flex-direction:column;width:13rem;padding:.5rem 0;font-weight:400;display:flex;position:absolute;box-shadow:0 10px 15px -3px #0000001a,0 4px 6px -4px #0000001a}.flexi-filter-search{border-bottom:1px solid #f1f5f9;margin-bottom:.25rem;padding:0 .5rem .5rem}.flexi-input{color:#475569;box-sizing:border-box;border:1px solid #e2e8f0;border-radius:.25rem;width:100%;padding:.25rem .5rem;font-size:12px;font-weight:500}.flexi-input:focus{border-color:#3b82f6;outline:none}.flexi-filter-list{flex-direction:column;gap:.25rem;max-height:10rem;padding:0 .5rem;display:flex;overflow-y:auto}.flexi-filter-empty{color:#64748b;text-align:center;padding:.5rem;font-size:11px}.flexi-filter-option{cursor:pointer;-webkit-user-select:none;user-select:none;border-radius:.25rem;align-items:center;gap:.5rem;padding:.375rem .5rem;font-size:12px;display:flex}.flexi-filter-option:hover{background-color:#f8fafc}.flexi-filter-option-text{white-space:nowrap;text-overflow:ellipsis;overflow:hidden}.flexi-checkbox{accent-color:#2563eb;border:1px solid #cbd5e1;border-radius:.25rem;width:.875rem;height:.875rem}.flexi-checkbox:focus{outline-offset:1px;outline:2px solid #3b82f6}.flexi-filter-footer{border-top:1px solid #f1f5f9;justify-content:flex-end;margin-top:.5rem;padding:.25rem .5rem 0;display:flex}.flexi-filter-clear{color:#ef4444;padding:.125rem .5rem;font-size:11px;font-weight:500}.flexi-filter-clear:hover{text-decoration:underline}.flexi-tbody{display:table-row-group}.flexi-tbody tr+tr{border-top:1px solid #f1f5f9}.flexi-empty-cell{text-align:center;color:#64748b;padding:2rem;font-size:12px;font-weight:500}.flexi-group-row{-webkit-user-select:none;user-select:none;background-color:#f8fafc99;height:48px;transition:background-color .15s}.flexi-group-row:hover{background-color:#f1f5f9cc}.flexi-group-cell{color:#334155;border-right:1px solid #f1f5f9;padding:.75rem 1rem;font-size:12px;font-weight:600}.flexi-group-toggle{text-align:left;border-radius:6px;align-items:center;gap:.375rem;margin:-4px -8px -4px -4px;padding:4px 8px 4px 4px;transition:background-color .15s;display:flex}.flexi-group-toggle:hover{background-color:#e2e8f099}.flexi-group-toggle:focus-visible{outline-offset:1px;outline:2px solid #93c5fd}.flexi-icon-muted{color:#64748b}.flexi-group-value{color:#1e293b;white-space:nowrap;text-overflow:ellipsis;width:10rem;font-weight:700;overflow:hidden}.flexi-group-count{color:#64748b;background-color:#e2e8f0b3;border-radius:9999px;margin-left:.25rem;padding:.05rem .375rem;font-size:10px;font-weight:500}.flexi-group-filler{background-color:#f8fafc4d}.flexi-row{border-bottom:1px solid #f1f5f9;height:48px;transition:background-color .15s}.flexi-row:hover{background-color:#f8fafccc}.flexi-row--clickable{cursor:pointer}.flexi-row-indent-cell{background-color:#fff;border-right:1px solid #f1f5f9;width:48px}.flexi-cell{vertical-align:middle;padding:.25rem 1rem;transition:background-color .15s}.flexi-row:hover .flexi-cell--pinned{background-color:#f1f5f9}.flexi-cell-text{color:#64748b;white-space:nowrap;text-overflow:ellipsis;font-size:12px;font-weight:500;display:block;overflow:hidden}.flexi-actions-cell{vertical-align:middle;text-align:right;background-color:#fff;padding:.25rem 1rem;box-shadow:-1px 0 #e2e8f0}.flexi-row:hover .flexi-actions-cell{background-color:#f8fafccc}.flexi-tbody--pinned-bottom{box-shadow:0 -2px 5px -2px #0000001a}.flexi-row--pinned-bottom{background-color:#f8fafc;font-weight:600}.flexi-row--pinned-bottom:hover,.flexi-row--pinned-bottom .flexi-cell,.flexi-row--pinned-bottom .flexi-actions-cell{background-color:#f8fafc}.flexi-row--pinned-bottom .flexi-cell-text{color:#334155}.flexi-actions-inner{opacity:0;justify-content:flex-end;gap:.25rem;transition:opacity .15s;display:flex}.flexi-row:hover .flexi-actions-inner{opacity:1}.flexi-icon-xs{width:.75rem;height:.75rem}.flexi-icon-sm{width:.875rem;height:.875rem}.flexi-icon-md{width:1rem;height:1rem}.flexi-pagination{color:#475569;box-sizing:border-box;background-color:#f8fafc80;border:1px solid #e2e8f0;border-radius:.75rem;justify-content:space-between;align-items:center;gap:1rem;width:100%;padding:.5rem .75rem;font-size:12px;font-weight:500;display:flex}.flexi-pagination-size{align-items:center;gap:.5rem;display:flex}.flexi-pagination-select{color:#334155;background-color:#fff;border:1px solid #e2e8f0;border-radius:.375rem;padding:.25rem .5rem;font-size:12px;font-weight:500}.flexi-pagination-info{text-align:center;flex:auto}.flexi-pagination-controls{align-items:center;gap:.25rem;display:flex}.flexi-pagination-controls .flexi-icon-btn:disabled{opacity:.4;cursor:not-allowed}.flexi-pagination-controls .flexi-icon-btn:disabled:hover{background-color:#0000}.flexi-pagination-page{white-space:nowrap;padding:0 .5rem}.flexi-root button{appearance:none;cursor:pointer;font:inherit;color:inherit;background:0 0;border:none;margin:0;padding:0 5px;line-height:normal}.flexi-root input{font:inherit}.flexi-root table{border-collapse:collapse;border-spacing:0}
2
2
  /*$vite$:1*/
package/dist/index.d.ts CHANGED
@@ -1,3 +1,3 @@
1
1
  import './index.css';
2
- export { DataTable } from './DataTable';
3
- export type { ColumnConfig } from './DataTable';
2
+ export { DataTable } from './Datatable';
3
+ export type { ColumnConfig } from './Datatable';