@coreui/data-grid 0.2.0-rc.0 → 0.4.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.
- package/README.md +4 -2
- package/dist/css/data-grid.css +11 -11
- package/dist/css/data-grid.css.map +1 -1
- package/dist/css/data-grid.min.css +2 -2
- package/dist/css/data-grid.min.css.map +1 -1
- package/dist/css/data-grid.standalone.css +1066 -0
- package/dist/css/data-grid.standalone.css.map +1 -0
- package/dist/css/data-grid.standalone.min.css +5 -0
- package/dist/css/data-grid.standalone.min.css.map +1 -0
- package/dist/js/clipboard.cjs +2 -3
- package/dist/js/clipboard.mjs +2 -3
- package/dist/js/csv.cjs +5 -6
- package/dist/js/csv.cjs.map +1 -1
- package/dist/js/csv.mjs +5 -6
- package/dist/js/csv.mjs.map +1 -1
- package/dist/js/data-grid.esm.js +5685 -5422
- package/dist/js/data-grid.esm.js.map +1 -1
- package/dist/js/data-grid.esm.min.js +2 -2
- package/dist/js/data-grid.esm.min.js.map +1 -1
- package/dist/js/data-grid.js +5685 -5423
- package/dist/js/data-grid.js.map +1 -1
- package/dist/js/data-grid.lite.esm.js +2707 -2629
- package/dist/js/data-grid.lite.esm.js.map +1 -1
- package/dist/js/data-grid.lite.esm.min.js +2 -2
- package/dist/js/data-grid.lite.esm.min.js.map +1 -1
- package/dist/js/data-grid.lite.js +2707 -2630
- package/dist/js/data-grid.lite.js.map +1 -1
- package/dist/js/data-grid.lite.min.js +2 -2
- package/dist/js/data-grid.lite.min.js.map +1 -1
- package/dist/js/data-grid.lite.mjs +2707 -2629
- package/dist/js/data-grid.lite.mjs.map +1 -1
- package/dist/js/data-grid.min.js +2 -2
- package/dist/js/data-grid.min.js.map +1 -1
- package/dist/js/data-grid.mjs +5685 -5422
- package/dist/js/data-grid.mjs.map +1 -1
- package/dist/js/direction.cjs +25 -0
- package/dist/js/direction.cjs.map +1 -0
- package/dist/js/direction.mjs +23 -0
- package/dist/js/direction.mjs.map +1 -0
- package/dist/js/drag.cjs +20 -12
- package/dist/js/drag.cjs.map +1 -1
- package/dist/js/drag.mjs +20 -12
- package/dist/js/drag.mjs.map +1 -1
- package/dist/js/{export-table-CNAKStzj.mjs → export-table-BggGRmzQ.mjs} +3 -4
- package/dist/js/{export-table-CNAKStzj.mjs.map → export-table-BggGRmzQ.mjs.map} +1 -1
- package/dist/js/{export-table-doIwvE5U.cjs → export-table-BiB3_lVo.cjs} +3 -4
- package/dist/js/{export-table-doIwvE5U.cjs.map → export-table-BiB3_lVo.cjs.map} +1 -1
- package/dist/js/filters.cjs +2 -3
- package/dist/js/filters.mjs +2 -3
- package/dist/js/labels.cjs +2 -3
- package/dist/js/labels.mjs +2 -3
- package/dist/js/print.cjs +2 -3
- package/dist/js/print.mjs +2 -3
- package/dist/js/selector.cjs +22 -0
- package/dist/js/selector.cjs.map +1 -0
- package/dist/js/selector.mjs +21 -0
- package/dist/js/selector.mjs.map +1 -0
- package/dist/js/validate.cjs +2 -3
- package/dist/js/validate.mjs +2 -3
- package/dist/js/xlsx.cjs +5 -7
- package/dist/js/xlsx.cjs.map +1 -1
- package/dist/js/xlsx.mjs +5 -7
- package/dist/js/xlsx.mjs.map +1 -1
- package/dist/types/base-component.d.mts +29 -0
- package/dist/types/data-grid.d.mts +141 -0
- package/dist/types/dom/data.d.mts +15 -0
- package/dist/types/dom/event-handler.d.mts +22 -0
- package/dist/types/dom/manipulator.d.mts +16 -0
- package/dist/types/engine/active-cell.d.mts +33 -0
- package/dist/types/engine/active-cell.d.ts +1 -0
- package/dist/types/engine/data-source.d.mts +19 -0
- package/dist/types/engine/features-lite.d.mts +25 -0
- package/dist/types/engine/features.d.mts +35 -0
- package/dist/types/engine/scheduler.d.mts +11 -0
- package/dist/types/engine/table.d.mts +9 -0
- package/dist/types/engine/table.d.ts +1 -1
- package/dist/types/engine/validate.d.mts +20 -1
- package/dist/types/engine/virtualizer.d.mts +23 -0
- package/dist/types/filters.d.mts +13 -1
- package/dist/types/icons.d.mts +23 -0
- package/dist/types/index-lite.d.mts +19 -1
- package/dist/types/index.d.mts +21 -1
- package/dist/types/labels.d.mts +9 -1
- package/dist/types/types.d.mts +284 -0
- package/dist/types/ui/body.d.mts +37 -0
- package/dist/types/ui/column-chooser.d.mts +27 -0
- package/dist/types/ui/editor.d.mts +8 -0
- package/dist/types/ui/filter-menu.d.mts +21 -0
- package/dist/types/ui/header.d.mts +51 -0
- package/dist/types/ui/loading.d.mts +12 -0
- package/dist/types/ui/menu.d.mts +17 -0
- package/dist/types/ui/pagination.d.mts +17 -0
- package/dist/types/ui/pinning.d.mts +17 -0
- package/dist/types/ui/toolbar.d.mts +42 -0
- package/dist/types/ui/tooltip.d.mts +10 -0
- package/dist/types/util/clipboard.d.mts +29 -1
- package/dist/types/util/config.d.mts +28 -0
- package/dist/types/util/csv.d.mts +19 -1
- package/dist/types/util/debounce.d.mts +7 -0
- package/dist/types/util/direction.d.mts +17 -0
- package/dist/types/util/direction.d.ts +17 -0
- package/dist/types/util/drag.d.mts +48 -1
- package/dist/types/util/export-table.d.mts +47 -0
- package/dist/types/util/index.d.mts +66 -0
- package/dist/types/util/print.d.mts +32 -1
- package/dist/types/util/sanitizer.d.mts +15 -0
- package/dist/types/util/selector.d.mts +12 -0
- package/dist/types/util/selector.d.ts +12 -0
- package/dist/types/util/xlsx.d.mts +17 -1
- package/dist/types/util/zip.d.mts +11 -0
- package/package.json +42 -16
- package/scss/_base.scss +13 -0
- package/scss/_config.scss +14 -0
- package/scss/_functions.scss +1 -0
- package/scss/_mixins.scss +1 -0
- package/scss/base/_buttons.scss +113 -0
- package/scss/base/_forms.scss +157 -0
- package/scss/base/_helpers.scss +24 -0
- package/scss/base/_pagination.scss +100 -0
- package/scss/base/_reboot.scss +93 -0
- package/scss/base/_root.scss +85 -0
- package/scss/base/_spinner.scss +45 -0
- package/scss/base/_table.scss +78 -0
- package/scss/components/_data-grid.scss +807 -0
- package/scss/data-grid.scss +7 -743
- package/scss/data-grid.standalone.scss +16 -0
- package/scss/functions/_defaults.scss +25 -0
- package/scss/mixins/_tokens.scss +12 -0
- package/src/data-grid.ts +22 -6
- package/src/engine/active-cell.ts +6 -3
- package/src/engine/table.ts +9 -1
- package/src/index-lite.ts +1 -1
- package/src/index.ts +1 -1
- package/src/ui/column-chooser.ts +4 -4
- package/src/ui/filter-menu.ts +5 -5
- package/src/ui/menu.ts +4 -4
- package/src/ui/pagination.ts +1 -1
- package/src/ui/tooltip.ts +2 -2
- package/src/util/csv.ts +2 -2
- package/src/util/direction.ts +38 -0
- package/src/util/drag.ts +45 -9
- package/src/util/selector.ts +16 -0
- package/src/util/xlsx.ts +1 -1
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/*!
|
|
2
|
+
* CoreUI PRO data-grid.standalone.scss v0.1.0
|
|
3
|
+
* License (https://coreui.io/pro/license/)
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
// The grid plus the base styles it would otherwise borrow from
|
|
7
|
+
// @coreui/coreui(-pro). Load this INSTEAD of data-grid.css when the CoreUI
|
|
8
|
+
// stylesheet is not on the page.
|
|
9
|
+
|
|
10
|
+
// scss-docs-start import-stack
|
|
11
|
+
@forward "config";
|
|
12
|
+
@forward "functions";
|
|
13
|
+
@forward "mixins";
|
|
14
|
+
@forward "base";
|
|
15
|
+
@forward "components/data-grid";
|
|
16
|
+
// scss-docs-end import-stack
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
// Vendored from @coreui/coreui-pro (scss/functions/_defaults.scss, v6 line).
|
|
2
|
+
// Sync manually — do not diverge.
|
|
3
|
+
|
|
4
|
+
@use "sass:map";
|
|
5
|
+
@use "sass:meta";
|
|
6
|
+
|
|
7
|
+
// Merge overrides on top of defaults, stripping null entries.
|
|
8
|
+
// Null values let users remove map keys via @use ... with().
|
|
9
|
+
// Accepts a list as $defaults (converted to a map with `true` values).
|
|
10
|
+
@function defaults($defaults, $overrides) {
|
|
11
|
+
@if meta.type-of($defaults) == "list" {
|
|
12
|
+
$map: ();
|
|
13
|
+
@each $key in $defaults {
|
|
14
|
+
$map: map.merge($map, ($key: true));
|
|
15
|
+
}
|
|
16
|
+
$defaults: $map;
|
|
17
|
+
}
|
|
18
|
+
$merged: map.merge($defaults, $overrides);
|
|
19
|
+
@each $key, $value in $merged {
|
|
20
|
+
@if $value == null {
|
|
21
|
+
$merged: map.remove($merged, $key);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
@return $merged;
|
|
25
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
// Vendored from @coreui/coreui-pro (scss/mixins/_tokens.scss, v6 line).
|
|
2
|
+
// Sync manually — do not diverge.
|
|
3
|
+
|
|
4
|
+
// Output a token map as CSS custom properties
|
|
5
|
+
|
|
6
|
+
// scss-docs-start mixin-tokens
|
|
7
|
+
@mixin tokens($map) {
|
|
8
|
+
@each $prop, $value in $map {
|
|
9
|
+
#{$prop}: #{$value};
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
// scss-docs-end mixin-tokens
|
package/src/data-grid.ts
CHANGED
|
@@ -20,6 +20,7 @@ import {
|
|
|
20
20
|
} from './util/drag'
|
|
21
21
|
import { createScheduler, type Scheduler } from './engine/scheduler'
|
|
22
22
|
import { createGridTable, subscribeToTableState } from './engine/table'
|
|
23
|
+
import { isElementRTL, syncResizeDirection } from './util/direction'
|
|
23
24
|
import { createGridVirtualizer, type GridVirtualizer } from './engine/virtualizer'
|
|
24
25
|
import {
|
|
25
26
|
DEFAULT_COLUMN_MENU_ICON, DEFAULT_FILTER_ICON, DEFAULT_HIDE_COLUMN_ICON, DEFAULT_MOVE_END_ICON, DEFAULT_MOVE_START_ICON,
|
|
@@ -50,6 +51,7 @@ import {
|
|
|
50
51
|
} from './util/csv'
|
|
51
52
|
import { debounce } from './util/debounce'
|
|
52
53
|
import { DefaultAllowlist, sanitizeHtml, SVGAllowlist } from './util/sanitizer.js'
|
|
54
|
+
import { escapeAttributeValue } from './util/selector'
|
|
53
55
|
|
|
54
56
|
/**
|
|
55
57
|
* Constants
|
|
@@ -134,6 +136,12 @@ const STATE_STORAGE_PREFIX = 'coreui-data-grid:'
|
|
|
134
136
|
const STATE_AUTOSAVE_DEBOUNCE_MS = 250
|
|
135
137
|
const HISTORY_LIMIT = 100
|
|
136
138
|
|
|
139
|
+
// Under RTL the two horizontal keys point at the opposite logical neighbour.
|
|
140
|
+
const INVERTED_ARROWS: Record<string, string | undefined> = {
|
|
141
|
+
ArrowLeft: 'ArrowRight',
|
|
142
|
+
ArrowRight: 'ArrowLeft'
|
|
143
|
+
}
|
|
144
|
+
|
|
137
145
|
const CELL_SELECTION_DIRECTIONS: Record<string, 'up' | 'down' | 'left' | 'right' | undefined> = {
|
|
138
146
|
ArrowDown: 'down',
|
|
139
147
|
ArrowLeft: 'left',
|
|
@@ -567,7 +575,7 @@ private _undoStack: DataGridEditCommitEntry[] = []
|
|
|
567
575
|
|
|
568
576
|
private _createTable(): DataGridTable {
|
|
569
577
|
this._unsubscribeState?.()
|
|
570
|
-
const table = createGridTable(this._config)
|
|
578
|
+
const table = createGridTable(this._config, () => this._element)
|
|
571
579
|
this._unsubscribeState = subscribeToTableState(table, (previous, next) => {
|
|
572
580
|
this._notifyStateChange(previous, next)
|
|
573
581
|
this._scheduler.schedule()
|
|
@@ -1293,7 +1301,9 @@ private _undoStack: DataGridEditCommitEntry[] = []
|
|
|
1293
1301
|
// Shift+arrow grows the range from its anchor and leaves focus put; a
|
|
1294
1302
|
// plain arrow moves focus and collapses the range to the new cell.
|
|
1295
1303
|
if (this._config.cellSelection && found.cell.rowIndex >= 0 && result.cell.rowIndex >= 0) {
|
|
1296
|
-
const direction = CELL_SELECTION_DIRECTIONS[
|
|
1304
|
+
const direction = CELL_SELECTION_DIRECTIONS[
|
|
1305
|
+
isElementRTL(this._element) ? (INVERTED_ARROWS[event.key] ?? event.key) : event.key
|
|
1306
|
+
]
|
|
1297
1307
|
if (event.shiftKey && direction) {
|
|
1298
1308
|
this._table.extendCellSelection(direction)
|
|
1299
1309
|
this._scheduler.schedule()
|
|
@@ -1643,19 +1653,20 @@ private _undoStack: DataGridEditCommitEntry[] = []
|
|
|
1643
1653
|
pageStep: paginated ?
|
|
1644
1654
|
this._table.store.state.pagination.pageSize :
|
|
1645
1655
|
Math.max(1, viewportRows || rowCount),
|
|
1646
|
-
paginated
|
|
1656
|
+
paginated,
|
|
1657
|
+
rtl: isElementRTL(this._element)
|
|
1647
1658
|
}
|
|
1648
1659
|
}
|
|
1649
1660
|
|
|
1650
1661
|
private _cellElementFor(cell: ActiveCell): HTMLElement | null {
|
|
1651
1662
|
if (cell.rowIndex === -1) {
|
|
1652
1663
|
return this._header.element.querySelector<HTMLElement>(
|
|
1653
|
-
`tr:first-child th[data-coreui-column="${cell.columnId}"]`
|
|
1664
|
+
`tr:first-child th[data-coreui-column="${escapeAttributeValue(cell.columnId)}"]`
|
|
1654
1665
|
)
|
|
1655
1666
|
}
|
|
1656
1667
|
|
|
1657
|
-
const rowElement = this._body.element.querySelector<HTMLElement>(`tr[data-coreui-index="${cell.rowIndex}"]`)
|
|
1658
|
-
return rowElement?.querySelector<HTMLElement>(`td[data-coreui-column="${cell.columnId}"]`) ?? null
|
|
1668
|
+
const rowElement = this._body.element.querySelector<HTMLElement>(`tr[data-coreui-index="${escapeAttributeValue(cell.rowIndex)}"]`)
|
|
1669
|
+
return rowElement?.querySelector<HTMLElement>(`td[data-coreui-column="${escapeAttributeValue(cell.columnId)}"]`) ?? null
|
|
1659
1670
|
}
|
|
1660
1671
|
|
|
1661
1672
|
// The roving-tabindex pass: runs after every render/recycle flush, in the
|
|
@@ -1975,6 +1986,11 @@ private _undoStack: DataGridEditCommitEntry[] = []
|
|
|
1975
1986
|
event.preventDefault()
|
|
1976
1987
|
}
|
|
1977
1988
|
|
|
1989
|
+
// The direction table-core snapshotted at construction may be stale — an
|
|
1990
|
+
// app that swaps language flips dir on a live grid. A resize is the only
|
|
1991
|
+
// moment it matters, and it is rare enough to re-read here.
|
|
1992
|
+
syncResizeDirection(this._table, this._element)
|
|
1993
|
+
|
|
1978
1994
|
const header = this._table.getHeaderGroups()[0]?.headers.find(item => item.column.id === key)
|
|
1979
1995
|
header?.getResizeHandler()(event)
|
|
1980
1996
|
}
|
|
@@ -23,6 +23,9 @@ export interface ActiveCellContext {
|
|
|
23
23
|
// page size (paginated).
|
|
24
24
|
pageStep: number
|
|
25
25
|
paginated: boolean
|
|
26
|
+
// Writing direction of the grid. Arrow keys are physical, column order is
|
|
27
|
+
// logical, so under RTL the two horizontal keys trade places.
|
|
28
|
+
rtl?: boolean
|
|
26
29
|
}
|
|
27
30
|
|
|
28
31
|
export interface NavigationResult {
|
|
@@ -72,7 +75,7 @@ export const resolveNavigation = (
|
|
|
72
75
|
current: ActiveCell,
|
|
73
76
|
context: ActiveCellContext
|
|
74
77
|
): NavigationResult | null => {
|
|
75
|
-
const { columnIds, rowCount, pageStep, paginated } = context
|
|
78
|
+
const { columnIds, rowCount, pageStep, paginated, rtl } = context
|
|
76
79
|
const columnIndex = columnIds.indexOf(current.columnId)
|
|
77
80
|
if (columnIndex === -1 || columnIds.length === 0) {
|
|
78
81
|
return null
|
|
@@ -86,11 +89,11 @@ export const resolveNavigation = (
|
|
|
86
89
|
|
|
87
90
|
switch (key) {
|
|
88
91
|
case 'ArrowLeft': {
|
|
89
|
-
return cell(current.rowIndex, Math.max(columnIndex - 1, 0))
|
|
92
|
+
return cell(current.rowIndex, rtl ? Math.min(columnIndex + 1, lastColumn) : Math.max(columnIndex - 1, 0))
|
|
90
93
|
}
|
|
91
94
|
|
|
92
95
|
case 'ArrowRight': {
|
|
93
|
-
return cell(current.rowIndex, Math.min(columnIndex + 1, lastColumn))
|
|
96
|
+
return cell(current.rowIndex, rtl ? Math.max(columnIndex - 1, 0) : Math.min(columnIndex + 1, lastColumn))
|
|
94
97
|
}
|
|
95
98
|
|
|
96
99
|
case 'ArrowUp': {
|
package/src/engine/table.ts
CHANGED
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
|
|
8
8
|
import { constructTable, type FilterFn } from '@tanstack/table-core'
|
|
9
9
|
import { evaluateColumnFilter } from '../filters'
|
|
10
|
+
import { isElementRTL } from '../util/direction'
|
|
10
11
|
import type {
|
|
11
12
|
DataGridColumn, DataGridColumnFilterValue, DataGridFeatures, DataGridItem, DataGridOptions,
|
|
12
13
|
DataGridTable, DataGridTableState
|
|
@@ -27,7 +28,10 @@ const toTanStackFilterFn = (column: DataGridColumn): FilterFn<DataGridFeatures,
|
|
|
27
28
|
evaluateColumnFilter(row.getValue(columnId), filterValue as DataGridColumnFilterValue, column.filterType)
|
|
28
29
|
}
|
|
29
30
|
|
|
30
|
-
export const createGridTable = (
|
|
31
|
+
export const createGridTable = (
|
|
32
|
+
options: DataGridOptions,
|
|
33
|
+
getElement?: () => Element | null
|
|
34
|
+
): DataGridTable => {
|
|
31
35
|
const sortingOptions = typeof options.sorting === 'object' ? options.sorting : {}
|
|
32
36
|
const sizingOptions = typeof options.columnSizing === 'object' ? options.columnSizing : {}
|
|
33
37
|
const paginationOptions = typeof options.pagination === 'object' ? options.pagination : {}
|
|
@@ -112,6 +116,10 @@ export const createGridTable = (options: DataGridOptions): DataGridTable => {
|
|
|
112
116
|
}
|
|
113
117
|
}),
|
|
114
118
|
columnResizeMode: sizingOptions.mode ?? 'onChange',
|
|
119
|
+
// Construction-time only — table-core copies the options object, so a
|
|
120
|
+
// getter here would never be re-read. A direction flipped later is picked
|
|
121
|
+
// up by refreshing this through setOptions when a resize starts.
|
|
122
|
+
columnResizeDirection: isElementRTL(getElement?.()) ? 'rtl' : 'ltr',
|
|
115
123
|
data: serverSide ? [] : options.items,
|
|
116
124
|
enableCellSelection: Boolean(options.cellSelection),
|
|
117
125
|
enableColumnFilters: Boolean(options.columnFilters),
|
package/src/index-lite.ts
CHANGED
|
@@ -20,7 +20,7 @@ class DataGrid<TFeatures extends TableFeatures = DataGridLiteFeatures> extends D
|
|
|
20
20
|
// The lite preset is deliberately narrower than the options' invariant
|
|
21
21
|
// features type - the coverage assertion, not the type, polices a config
|
|
22
22
|
// the set cannot serve.
|
|
23
|
-
return { ...
|
|
23
|
+
return { ...super.Default, features: dataGridLiteFeatures as unknown as DataGridFeatures }
|
|
24
24
|
}
|
|
25
25
|
}
|
|
26
26
|
|
package/src/index.ts
CHANGED
|
@@ -16,7 +16,7 @@ import { defineJQueryPlugin } from './util/index.js'
|
|
|
16
16
|
// keeps the full set out of the lite bundle.
|
|
17
17
|
class DataGrid<TFeatures extends TableFeatures = DataGridFeatures> extends DataGridBase<TFeatures> {
|
|
18
18
|
static get Default(): DataGridOptions {
|
|
19
|
-
return { ...
|
|
19
|
+
return { ...super.Default, features: dataGridFeatures }
|
|
20
20
|
}
|
|
21
21
|
}
|
|
22
22
|
|
package/src/ui/column-chooser.ts
CHANGED
|
@@ -57,8 +57,8 @@ export const createColumnChooser = (context: GridColumnChooserContext): GridColu
|
|
|
57
57
|
|
|
58
58
|
trigger = null
|
|
59
59
|
element.remove()
|
|
60
|
-
document.removeEventListener('pointerdown', onOutsidePointerDown, true)
|
|
61
|
-
window.removeEventListener('scroll', onWindowChange, true)
|
|
60
|
+
document.removeEventListener('pointerdown', onOutsidePointerDown, { capture: true })
|
|
61
|
+
window.removeEventListener('scroll', onWindowChange, { capture: true })
|
|
62
62
|
window.removeEventListener('resize', onWindowChange)
|
|
63
63
|
}
|
|
64
64
|
|
|
@@ -156,8 +156,8 @@ export const createColumnChooser = (context: GridColumnChooserContext): GridColu
|
|
|
156
156
|
element.style.top = `${buttonRect.bottom - rootRect.top}px`
|
|
157
157
|
element.style.left = `${Math.max(buttonRect.left - rootRect.left, 0)}px`
|
|
158
158
|
|
|
159
|
-
document.addEventListener('pointerdown', onOutsidePointerDown, true)
|
|
160
|
-
window.addEventListener('scroll', onWindowChange, true)
|
|
159
|
+
document.addEventListener('pointerdown', onOutsidePointerDown, { capture: true })
|
|
160
|
+
window.addEventListener('scroll', onWindowChange, { capture: true })
|
|
161
161
|
window.addEventListener('resize', onWindowChange)
|
|
162
162
|
element.querySelector<HTMLElement>('input:not(:disabled)')?.focus()
|
|
163
163
|
}
|
package/src/ui/filter-menu.ts
CHANGED
|
@@ -82,8 +82,8 @@ export const createFilterMenu = (context: GridFilterMenuContext): GridFilterMenu
|
|
|
82
82
|
|
|
83
83
|
trigger = null
|
|
84
84
|
element.remove()
|
|
85
|
-
document.removeEventListener('pointerdown', onOutsidePointerDown, true)
|
|
86
|
-
window.removeEventListener('scroll', onWindowChange, true)
|
|
85
|
+
document.removeEventListener('pointerdown', onOutsidePointerDown, { capture: true })
|
|
86
|
+
window.removeEventListener('scroll', onWindowChange, { capture: true })
|
|
87
87
|
window.removeEventListener('resize', onWindowChange)
|
|
88
88
|
}
|
|
89
89
|
|
|
@@ -157,7 +157,6 @@ export const createFilterMenu = (context: GridFilterMenuContext): GridFilterMenu
|
|
|
157
157
|
const readCondition = (row: HTMLElement): DataGridFilterCondition | null => {
|
|
158
158
|
const operator = row.querySelector('select')!.value as DataGridFilterOperator
|
|
159
159
|
const { value } = (row.querySelector<HTMLInputElement>('[data-coreui-filter-value]')!)
|
|
160
|
-
const value2 = row.querySelector<HTMLInputElement>('[data-coreui-filter-value2]')!.value
|
|
161
160
|
|
|
162
161
|
if (VALUELESS_OPERATORS.has(operator)) {
|
|
163
162
|
return { operator }
|
|
@@ -167,6 +166,7 @@ export const createFilterMenu = (context: GridFilterMenuContext): GridFilterMenu
|
|
|
167
166
|
return null
|
|
168
167
|
}
|
|
169
168
|
|
|
169
|
+
const value2 = row.querySelector<HTMLInputElement>('[data-coreui-filter-value2]')!.value
|
|
170
170
|
return {
|
|
171
171
|
operator,
|
|
172
172
|
value,
|
|
@@ -371,8 +371,8 @@ export const createFilterMenu = (context: GridFilterMenuContext): GridFilterMenu
|
|
|
371
371
|
element.style.top = `${buttonRect.bottom - rootRect.top}px`
|
|
372
372
|
element.style.left = `${Math.max(buttonRect.right - rootRect.left - element.offsetWidth, 0)}px`
|
|
373
373
|
|
|
374
|
-
document.addEventListener('pointerdown', onOutsidePointerDown, true)
|
|
375
|
-
window.addEventListener('scroll', onWindowChange, true)
|
|
374
|
+
document.addEventListener('pointerdown', onOutsidePointerDown, { capture: true })
|
|
375
|
+
window.addEventListener('scroll', onWindowChange, { capture: true })
|
|
376
376
|
window.addEventListener('resize', onWindowChange)
|
|
377
377
|
element.querySelector<HTMLElement>('select, input')?.focus()
|
|
378
378
|
}
|
package/src/ui/menu.ts
CHANGED
|
@@ -49,8 +49,8 @@ export const createMenu = (context: GridMenuContext): GridMenu => {
|
|
|
49
49
|
|
|
50
50
|
trigger = null
|
|
51
51
|
element.remove()
|
|
52
|
-
document.removeEventListener('pointerdown', onOutsidePointerDown, true)
|
|
53
|
-
window.removeEventListener('scroll', onWindowChange, true)
|
|
52
|
+
document.removeEventListener('pointerdown', onOutsidePointerDown, { capture: true })
|
|
53
|
+
window.removeEventListener('scroll', onWindowChange, { capture: true })
|
|
54
54
|
window.removeEventListener('resize', onWindowChange)
|
|
55
55
|
}
|
|
56
56
|
|
|
@@ -191,8 +191,8 @@ export const createMenu = (context: GridMenuContext): GridMenu => {
|
|
|
191
191
|
// sits at the cell's right edge, where a left-aligned menu would overflow.
|
|
192
192
|
element.style.left = `${Math.max(buttonRect.right - rootRect.left - element.offsetWidth, 0)}px`
|
|
193
193
|
|
|
194
|
-
document.addEventListener('pointerdown', onOutsidePointerDown, true)
|
|
195
|
-
window.addEventListener('scroll', onWindowChange, true)
|
|
194
|
+
document.addEventListener('pointerdown', onOutsidePointerDown, { capture: true })
|
|
195
|
+
window.addEventListener('scroll', onWindowChange, { capture: true })
|
|
196
196
|
window.addEventListener('resize', onWindowChange)
|
|
197
197
|
items()[0]?.focus()
|
|
198
198
|
}
|
package/src/ui/pagination.ts
CHANGED
|
@@ -137,7 +137,7 @@ export const createPagination = (context: GridPaginationContext): GridPagination
|
|
|
137
137
|
|
|
138
138
|
// A configured pageSize outside pageSizeOptions would leave the select
|
|
139
139
|
// blank; surface it as an extra option in numeric position.
|
|
140
|
-
if (
|
|
140
|
+
if ([...select.options].every(option => option.value !== String(state.pageSize))) {
|
|
141
141
|
const option = document.createElement('option')
|
|
142
142
|
option.value = String(state.pageSize)
|
|
143
143
|
option.textContent = String(state.pageSize)
|
package/src/ui/tooltip.ts
CHANGED
|
@@ -96,7 +96,7 @@ export const createTooltip = (root: HTMLElement): GridTooltip => {
|
|
|
96
96
|
root.addEventListener('pointerdown', hide)
|
|
97
97
|
root.addEventListener('focusin', onFocusIn as EventListener)
|
|
98
98
|
root.addEventListener('focusout', hide)
|
|
99
|
-
window.addEventListener('scroll', hide, true)
|
|
99
|
+
window.addEventListener('scroll', hide, { capture: true })
|
|
100
100
|
|
|
101
101
|
return {
|
|
102
102
|
dispose() {
|
|
@@ -106,7 +106,7 @@ export const createTooltip = (root: HTMLElement): GridTooltip => {
|
|
|
106
106
|
root.removeEventListener('pointerdown', hide)
|
|
107
107
|
root.removeEventListener('focusin', onFocusIn as EventListener)
|
|
108
108
|
root.removeEventListener('focusout', hide)
|
|
109
|
-
window.removeEventListener('scroll', hide, true)
|
|
109
|
+
window.removeEventListener('scroll', hide, { capture: true })
|
|
110
110
|
element.remove()
|
|
111
111
|
}
|
|
112
112
|
}
|
package/src/util/csv.ts
CHANGED
|
@@ -43,12 +43,12 @@ export const exportCsv = (table: CsvTable, options: CsvOptions = {}): string =>
|
|
|
43
43
|
const value = row.getValue(column.id)
|
|
44
44
|
const formatter = formatterFor(column)
|
|
45
45
|
// Coerce: a formatter may return a number/null in plain-JS usage.
|
|
46
|
-
return formatter ?
|
|
46
|
+
return String(formatter ? formatter(value, row.original) ?? '' : value ?? '')
|
|
47
47
|
}))
|
|
48
48
|
)
|
|
49
49
|
|
|
50
50
|
const csv = [header, ...body].join('\r\n')
|
|
51
|
-
return options.bom ? `\
|
|
51
|
+
return options.bom ? `\u{FEFF}${csv}` : csv
|
|
52
52
|
}
|
|
53
53
|
|
|
54
54
|
export const downloadCsv = (table: CsvTable, options: CsvDownloadOptions = {}): void => {
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* --------------------------------------------------------------------------
|
|
3
|
+
* CoreUI PRO data-grid util/direction.ts
|
|
4
|
+
* License (https://coreui.io/pro/license/)
|
|
5
|
+
* --------------------------------------------------------------------------
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
// Read from the element rather than the document: a grid can sit inside an RTL
|
|
9
|
+
// subtree of an LTR page, which is why the vendored isRTL() (it checks
|
|
10
|
+
// documentElement.dir) is not the helper for any of this.
|
|
11
|
+
//
|
|
12
|
+
// Angular prerenders in Node, where there is no getComputedStyle — and no
|
|
13
|
+
// layout to have a direction. LTR is the right answer there: the server-side
|
|
14
|
+
// pass emits logical markup either way, and the client corrects on hydration.
|
|
15
|
+
export const isElementRTL = (element: Element | null | undefined): boolean =>
|
|
16
|
+
typeof getComputedStyle === 'function' &&
|
|
17
|
+
element !== null &&
|
|
18
|
+
element !== undefined &&
|
|
19
|
+
getComputedStyle(element).direction === 'rtl'
|
|
20
|
+
|
|
21
|
+
interface ResizeDirectionTable {
|
|
22
|
+
options: { columnResizeDirection?: 'ltr' | 'rtl' }
|
|
23
|
+
setOptions: (updater: (previous: { columnResizeDirection?: 'ltr' | 'rtl' }) => never) => void
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// table-core copies the options object at construction, so the direction it
|
|
27
|
+
// captured goes stale the moment an app swaps language on a live grid. A
|
|
28
|
+
// resize is the only place the value is read, and starting one is rare enough
|
|
29
|
+
// to re-read the element here.
|
|
30
|
+
export const syncResizeDirection = (
|
|
31
|
+
table: ResizeDirectionTable,
|
|
32
|
+
element: Element | null | undefined
|
|
33
|
+
): void => {
|
|
34
|
+
const direction = isElementRTL(element) ? 'rtl' : 'ltr'
|
|
35
|
+
if (table.options.columnResizeDirection !== direction) {
|
|
36
|
+
table.setOptions(previous => ({ ...previous, columnResizeDirection: direction }) as never)
|
|
37
|
+
}
|
|
38
|
+
}
|
package/src/util/drag.ts
CHANGED
|
@@ -5,6 +5,8 @@
|
|
|
5
5
|
* --------------------------------------------------------------------------
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
+
import { escapeAttributeValue } from './selector'
|
|
9
|
+
|
|
8
10
|
// The one drag implementation behind every reorder in the grid: column drag
|
|
9
11
|
// (axis 'x', over the header) and row drag (axis 'y', over the body). It is
|
|
10
12
|
// headless in the sense that matters — it never reads the table, only what the
|
|
@@ -124,6 +126,17 @@ export const createDragController = (options: DragControllerOptions): DragContro
|
|
|
124
126
|
|
|
125
127
|
let sourceHit: DragHit | null = null
|
|
126
128
|
let dragging = false
|
|
129
|
+
// +1 while the axis runs the way the coordinates do, -1 under RTL, where the
|
|
130
|
+
// logical column order runs against increasing x. Negating the axis keeps
|
|
131
|
+
// every comparison below — the zone tiling, the hysteresis band, both edge
|
|
132
|
+
// clamps, `index > fromIndex` — written once, for one direction. Only the
|
|
133
|
+
// three places that touch pixels again convert back: the indicator's side,
|
|
134
|
+
// the preview transform and the settling ghost.
|
|
135
|
+
//
|
|
136
|
+
// Read from the grid's own computed direction rather than the document's: a
|
|
137
|
+
// grid can sit in an RTL subtree of an LTR page, which is exactly the case
|
|
138
|
+
// the vendored isRTL() cannot see.
|
|
139
|
+
let axisSign = 1
|
|
127
140
|
let origin = { x: 0, y: 0 }
|
|
128
141
|
let pointer = { x: 0, y: 0 }
|
|
129
142
|
let drop: DragDrop | null = null
|
|
@@ -147,6 +160,18 @@ export const createDragController = (options: DragControllerOptions): DragContro
|
|
|
147
160
|
const contentStarts = new Map<string, number>()
|
|
148
161
|
const contentSizes = new Map<string, number>()
|
|
149
162
|
|
|
163
|
+
// A rect's logical start: its leading edge along the axis, in the negated
|
|
164
|
+
// space when the axis is negated, so the span stays [start, start + size].
|
|
165
|
+
const logicalStart = (rect: DOMRect): number => {
|
|
166
|
+
if (!horizontal) {
|
|
167
|
+
return rect.top
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
return axisSign === 1 ? rect.left : -rect.right
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
const logicalPointer = (): number => (horizontal ? pointer.x * axisSign : pointer.y)
|
|
174
|
+
|
|
150
175
|
const stopAutoScroll = () => {
|
|
151
176
|
if (scrollFrame) {
|
|
152
177
|
cancelAnimationFrame(scrollFrame)
|
|
@@ -212,7 +237,9 @@ export const createDragController = (options: DragControllerOptions): DragContro
|
|
|
212
237
|
const rootRect = root.getBoundingClientRect()
|
|
213
238
|
indicator.hidden = false
|
|
214
239
|
if (horizontal) {
|
|
215
|
-
|
|
240
|
+
// `after` is logical; which pixel edge that is flips with the axis.
|
|
241
|
+
const trailing = after === (axisSign === 1)
|
|
242
|
+
indicator.style.left = `${trailing ? rect.right : rect.left}px`
|
|
216
243
|
indicator.style.top = `${rootRect.top}px`
|
|
217
244
|
indicator.style.height = `${rootRect.height}px`
|
|
218
245
|
} else {
|
|
@@ -224,6 +251,7 @@ export const createDragController = (options: DragControllerOptions): DragContro
|
|
|
224
251
|
|
|
225
252
|
const startDrag = () => {
|
|
226
253
|
dragging = true
|
|
254
|
+
axisSign = horizontal && getComputedStyle(root).direction === 'rtl' ? -1 : 1
|
|
227
255
|
// Chrome focuses a button on mousedown (a press on a header lands on the
|
|
228
256
|
// sort button), and tearing a FOCUSED element out of the DOM — which the
|
|
229
257
|
// drop's header re-append does — makes the browser reset the scroller it
|
|
@@ -284,7 +312,11 @@ export const createDragController = (options: DragControllerOptions): DragContro
|
|
|
284
312
|
}
|
|
285
313
|
|
|
286
314
|
const rect = scrollElement.getBoundingClientRect()
|
|
287
|
-
|
|
315
|
+
// scrollLeft stays a physical axis in every engine (RTL just runs it
|
|
316
|
+
// negative), so it enters the negated space through the sign.
|
|
317
|
+
return horizontal ?
|
|
318
|
+
logicalStart(rect) - (axisSign * scrollElement.scrollLeft) :
|
|
319
|
+
rect.top - scrollElement.scrollTop
|
|
288
320
|
}
|
|
289
321
|
|
|
290
322
|
const captureItem = (id: string, origin: number): boolean => {
|
|
@@ -296,7 +328,7 @@ export const createDragController = (options: DragControllerOptions): DragContro
|
|
|
296
328
|
const rect = element.getBoundingClientRect()
|
|
297
329
|
// Corrected by the applied delta for items first seen mid-preview (a row
|
|
298
330
|
// revealed by auto-scroll); at drag start every delta is zero.
|
|
299
|
-
const start = (
|
|
331
|
+
const start = logicalStart(rect) - (previewDeltas.get(id) ?? 0)
|
|
300
332
|
contentStarts.set(id, start - origin)
|
|
301
333
|
contentSizes.set(id, horizontal ? rect.width : rect.height)
|
|
302
334
|
return true
|
|
@@ -312,7 +344,7 @@ export const createDragController = (options: DragControllerOptions): DragContro
|
|
|
312
344
|
}
|
|
313
345
|
|
|
314
346
|
const updateTarget = () => {
|
|
315
|
-
const position =
|
|
347
|
+
const position = logicalPointer()
|
|
316
348
|
const origin = contentOrigin()
|
|
317
349
|
|
|
318
350
|
const zones: Array<{ id: string; start: number; size: number }> = []
|
|
@@ -477,7 +509,9 @@ export const createDragController = (options: DragControllerOptions): DragContro
|
|
|
477
509
|
}
|
|
478
510
|
|
|
479
511
|
for (const element of options.items.getElements(id)) {
|
|
480
|
-
element.style.transform = horizontal ?
|
|
512
|
+
element.style.transform = horizontal ?
|
|
513
|
+
`translateX(${axisSign * delta}px)` :
|
|
514
|
+
`translateY(${delta}px)`
|
|
481
515
|
element.classList.add(CLASS_NAME_PREVIEW)
|
|
482
516
|
}
|
|
483
517
|
|
|
@@ -531,7 +565,9 @@ export const createDragController = (options: DragControllerOptions): DragContro
|
|
|
531
565
|
}
|
|
532
566
|
|
|
533
567
|
const delta = previewDeltas.get(sourceHit.id) ?? 0
|
|
534
|
-
return horizontal ?
|
|
568
|
+
return horizontal ?
|
|
569
|
+
{ x: rect.left - (axisSign * delta), y: rect.top } :
|
|
570
|
+
{ x: rect.left, y: rect.top - delta }
|
|
535
571
|
}
|
|
536
572
|
|
|
537
573
|
const cleanup = () => {
|
|
@@ -692,7 +728,7 @@ const columnItems = (root: HTMLElement): DragItems => ({
|
|
|
692
728
|
[...root.querySelectorAll<HTMLTableCellElement>('thead tr:first-child th[data-coreui-column]')]
|
|
693
729
|
.map(cell => cell.dataset.coreuiColumn ?? ''),
|
|
694
730
|
getElements(id) {
|
|
695
|
-
const header = root.querySelector<HTMLTableCellElement>(`thead th[data-coreui-column="${id}"]`)
|
|
731
|
+
const header = root.querySelector<HTMLTableCellElement>(`thead th[data-coreui-column="${escapeAttributeValue(id)}"]`)
|
|
696
732
|
if (!header) {
|
|
697
733
|
return []
|
|
698
734
|
}
|
|
@@ -741,7 +777,7 @@ export const createColumnDragController = (options: ColumnDragOptions): DragCont
|
|
|
741
777
|
// consumer's own veto — an immovable column is not somewhere to land.
|
|
742
778
|
canDrop: (sourceId, targetId) =>
|
|
743
779
|
sourceId !== targetId &&
|
|
744
|
-
Boolean(options.root.querySelector(`th[data-coreui-column="${targetId}"][data-coreui-draggable]`)) &&
|
|
780
|
+
Boolean(options.root.querySelector(`th[data-coreui-column="${escapeAttributeValue(targetId)}"][data-coreui-draggable]`)) &&
|
|
745
781
|
options.canDrop(sourceId, targetId),
|
|
746
782
|
preview: true,
|
|
747
783
|
onDrop: (sourceKey, drop) => options.onReorder(sourceKey, drop.id, drop.after)
|
|
@@ -754,7 +790,7 @@ const rowItems = (root: HTMLElement): DragItems => ({
|
|
|
754
790
|
getOrderedIds: () =>
|
|
755
791
|
[...root.querySelectorAll<HTMLElement>(`tbody ${SELECTOR_ROW}`)].map(row => row.dataset.coreuiIndex ?? ''),
|
|
756
792
|
getElements(id) {
|
|
757
|
-
const row = root.querySelector<HTMLElement>(`tbody tr[data-coreui-index="${id}"]`)
|
|
793
|
+
const row = root.querySelector<HTMLElement>(`tbody tr[data-coreui-index="${escapeAttributeValue(id)}"]`)
|
|
758
794
|
return row ? [row] : []
|
|
759
795
|
}
|
|
760
796
|
})
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* --------------------------------------------------------------------------
|
|
3
|
+
* CoreUI PRO data-grid util/selector.ts
|
|
4
|
+
* License (https://coreui.io/pro/license/)
|
|
5
|
+
* --------------------------------------------------------------------------
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Escapes a column key or row index for a double-quoted CSS attribute selector value.
|
|
10
|
+
* @param value The key or index interpolated into `[data-…="…"]`.
|
|
11
|
+
* @returns The value as a string, with `"` and `\` backslash-escaped and line breaks as hex escapes.
|
|
12
|
+
*/
|
|
13
|
+
export const escapeAttributeValue = (value: string | number): string =>
|
|
14
|
+
String(value)
|
|
15
|
+
.replace(/["\\]/g, '\\$&')
|
|
16
|
+
.replace(/[\n\r\f]/g, character => `\\${character.codePointAt(0)!.toString(16)} `)
|
package/src/util/xlsx.ts
CHANGED
|
@@ -33,7 +33,7 @@ const MAX_COLUMN_WIDTH = 120
|
|
|
33
33
|
// XML 1.0 forbids most control characters outright - Excel refuses to open a
|
|
34
34
|
// file containing them, so they are dropped rather than escaped.
|
|
35
35
|
// eslint-disable-next-line no-control-regex -- matching them is the point
|
|
36
|
-
const CONTROL_CHARACTERS = /[\
|
|
36
|
+
const CONTROL_CHARACTERS = /[\u{0}-\u{8}\u{B}\u{C}\u{E}-\u{1F}]/gu
|
|
37
37
|
|
|
38
38
|
const escapeXml = (value: string): string =>
|
|
39
39
|
value
|