@ornery/ui-grid-react 0.1.4
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/CLAUDE.md +283 -0
- package/demo/index.html +16 -0
- package/demo/main.tsx +95 -0
- package/demo/vite.config.ts +13 -0
- package/dist/index.d.mts +133 -0
- package/dist/index.d.ts +133 -0
- package/dist/index.js +2020 -0
- package/dist/index.mjs +2050 -0
- package/package.json +41 -0
- package/src/UiGrid.test.tsx +370 -0
- package/src/UiGrid.tsx +440 -0
- package/src/index.ts +23 -0
- package/src/ui-grid.css +521 -0
- package/src/useGridState.ts +1414 -0
- package/src/useVirtualScroll.ts +44 -0
- package/tsconfig.json +14 -0
- package/vitest.config.ts +14 -0
package/CLAUDE.md
ADDED
|
@@ -0,0 +1,283 @@
|
|
|
1
|
+
# @ornery/ui-grid-react — React Wrapper
|
|
2
|
+
|
|
3
|
+
## What to build
|
|
4
|
+
|
|
5
|
+
A React wrapper for the `@ornery/ui-grid` Angular library's pure-TypeScript core. This package publishes as `@ornery/ui-grid-react` and reuses 100% of the core logic — no code duplication. The React wrapper is a thin rendering layer.
|
|
6
|
+
|
|
7
|
+
## Critical: Core reuse
|
|
8
|
+
|
|
9
|
+
The entire core is pure TypeScript with ZERO Angular dependencies. Import everything from the sibling library path. In this monorepo, the path alias `@ornery/ui-grid` maps to `projects/ui-grid/src/public-api.ts`.
|
|
10
|
+
|
|
11
|
+
**Core files to reuse (all pure TS, no Angular):**
|
|
12
|
+
|
|
13
|
+
- `grid.core.pipeline.ts` → `buildGridPipeline()` — the entire data pipeline
|
|
14
|
+
- `grid.api.ts` → `createGridApi()`, `UiGridApi` — the API object
|
|
15
|
+
- `grid.models.ts` → `GridOptions`, `GridColumnDef`, `GridRow`, `GridLabels`, `DEFAULT_GRID_LABELS`, etc.
|
|
16
|
+
- `grid.features.ts` → `FEATURE_SORTING`, `FEATURE_FILTERING`, etc. — build-time feature flags
|
|
17
|
+
- `grid.core.viewmodel.ts` → `resolveGridLabels()`, `gridSortButtonLabel()`, `gridSortAriaSort()`, all label functions
|
|
18
|
+
- `grid.core.display.ts` → `buildGridCellContext()`, `formatGridCellDisplayValue()`
|
|
19
|
+
- `grid.core.edit.ts` → `findNextGridCell()`, `isPrintableGridKey()`, `buildGridFocusCellResult()`, etc.
|
|
20
|
+
- `grid.core.export.ts` → `exportCsvRows()`, `headerLabel()`
|
|
21
|
+
- `grid.core.types.ts` → `DisplayItem`, `GroupItem`, `RowItem`, `ExpandableItem`, `PipelineResult`, `BuildGridPipelineContext`
|
|
22
|
+
- `grid.constants.ts` → `SORT_DIRECTIONS`, `FILTER_CONDITIONS`
|
|
23
|
+
- `grid.utils.ts` → `getCellValue`, `getPathValue`, `setPathValue`, `titleize`, etc.
|
|
24
|
+
- `row-searcher.ts` → `runColumnFilter`, `setupFilters`
|
|
25
|
+
- `row-sorter.ts` → `getSortFn`
|
|
26
|
+
- `ui-grid.commands.ts` → All command functions (pure TS, depends only on grid.api + grid.core)
|
|
27
|
+
- `ui-grid.events.ts` → All event raisers (pure TS, depends only on grid.api)
|
|
28
|
+
- `ui-grid.host.ts` → `downloadGridCsvFile()` (pure TS), `observeGridHostSize()` (pure TS)
|
|
29
|
+
- `i18n/en-US.json` → Default labels
|
|
30
|
+
|
|
31
|
+
**Do NOT copy any core files. Import them.**
|
|
32
|
+
|
|
33
|
+
## File structure to create
|
|
34
|
+
|
|
35
|
+
```
|
|
36
|
+
projects/ui-grid-react/
|
|
37
|
+
package.json
|
|
38
|
+
tsconfig.json
|
|
39
|
+
CLAUDE.md ← this file
|
|
40
|
+
src/
|
|
41
|
+
index.ts ← public API exports
|
|
42
|
+
UiGrid.tsx ← main React component
|
|
43
|
+
useGridState.ts ← state management hook (replaces Angular signals)
|
|
44
|
+
useVirtualScroll.ts ← fixed-size row virtualization hook
|
|
45
|
+
ui-grid.css ← styles adapted from grid.core.styles.scss
|
|
46
|
+
UiGrid.test.tsx ← tests with vitest + @testing-library/react
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
## package.json
|
|
50
|
+
|
|
51
|
+
```json
|
|
52
|
+
{
|
|
53
|
+
"name": "@ornery/ui-grid-react",
|
|
54
|
+
"version": "0.1.0",
|
|
55
|
+
"description": "React wrapper for @ornery/ui-grid",
|
|
56
|
+
"main": "dist/index.js",
|
|
57
|
+
"module": "dist/index.mjs",
|
|
58
|
+
"types": "dist/index.d.ts",
|
|
59
|
+
"exports": {
|
|
60
|
+
".": {
|
|
61
|
+
"import": "./dist/index.mjs",
|
|
62
|
+
"require": "./dist/index.js",
|
|
63
|
+
"types": "./dist/index.d.ts"
|
|
64
|
+
},
|
|
65
|
+
"./styles": "./dist/ui-grid.css"
|
|
66
|
+
},
|
|
67
|
+
"peerDependencies": {
|
|
68
|
+
"react": "^18.0.0 || ^19.0.0",
|
|
69
|
+
"react-dom": "^18.0.0 || ^19.0.0",
|
|
70
|
+
"@ornery/ui-grid": "^0.1.0"
|
|
71
|
+
},
|
|
72
|
+
"devDependencies": {
|
|
73
|
+
"react": "^19.1.0",
|
|
74
|
+
"react-dom": "^19.1.0",
|
|
75
|
+
"@testing-library/react": "^16.0.0",
|
|
76
|
+
"@types/react": "^19.0.0",
|
|
77
|
+
"@types/react-dom": "^19.0.0",
|
|
78
|
+
"typescript": "~5.8.0",
|
|
79
|
+
"vitest": "^4.1.0",
|
|
80
|
+
"jsdom": "^26.0.0",
|
|
81
|
+
"tsup": "^8.0.0"
|
|
82
|
+
},
|
|
83
|
+
"scripts": {
|
|
84
|
+
"build": "tsup src/index.ts --format esm,cjs --dts --external react --external react-dom --external @ornery/ui-grid",
|
|
85
|
+
"test": "vitest run",
|
|
86
|
+
"test:watch": "vitest"
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
## tsconfig.json
|
|
92
|
+
|
|
93
|
+
Extend the root tsconfig. Add path alias for `@ornery/ui-grid`:
|
|
94
|
+
|
|
95
|
+
```json
|
|
96
|
+
{
|
|
97
|
+
"extends": "../../tsconfig.json",
|
|
98
|
+
"compilerOptions": {
|
|
99
|
+
"jsx": "react-jsx",
|
|
100
|
+
"outDir": "./dist",
|
|
101
|
+
"declaration": true,
|
|
102
|
+
"declarationMap": true,
|
|
103
|
+
"paths": {
|
|
104
|
+
"@ornery/ui-grid": ["../ui-grid/src/public-api.ts"]
|
|
105
|
+
}
|
|
106
|
+
},
|
|
107
|
+
"include": ["src"]
|
|
108
|
+
}
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
## useVirtualScroll hook
|
|
112
|
+
|
|
113
|
+
A lightweight fixed-size virtualizer (~50 lines). No external dependency.
|
|
114
|
+
|
|
115
|
+
**Interface:**
|
|
116
|
+
```ts
|
|
117
|
+
interface UseVirtualScrollOptions {
|
|
118
|
+
itemCount: number;
|
|
119
|
+
itemSize: number; // row height in px
|
|
120
|
+
viewportHeight: number; // container height in px
|
|
121
|
+
overscan?: number; // extra items above/below (default: 3)
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
interface UseVirtualScrollResult {
|
|
125
|
+
visibleRange: { start: number; end: number };
|
|
126
|
+
totalHeight: number;
|
|
127
|
+
offsetY: number;
|
|
128
|
+
onScroll: (event: React.UIEvent<HTMLDivElement>) => void;
|
|
129
|
+
viewportRef: React.RefObject<HTMLDivElement>;
|
|
130
|
+
scrollTop: number;
|
|
131
|
+
}
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
**Implementation:**
|
|
135
|
+
- Track `scrollTop` via `useState`
|
|
136
|
+
- Calculate `start = Math.floor(scrollTop / itemSize) - overscan`
|
|
137
|
+
- Calculate `end = start + Math.ceil(viewportHeight / itemSize) + 2 * overscan`
|
|
138
|
+
- Clamp to `[0, itemCount)`
|
|
139
|
+
- `totalHeight = itemCount * itemSize`
|
|
140
|
+
- `offsetY = start * itemSize`
|
|
141
|
+
- Return a div ref and onScroll handler
|
|
142
|
+
|
|
143
|
+
## useGridState hook
|
|
144
|
+
|
|
145
|
+
Replaces Angular signals with React state. This is the core bridge.
|
|
146
|
+
|
|
147
|
+
**Maps the Angular component's state 1:1:**
|
|
148
|
+
|
|
149
|
+
| Angular signal | React state |
|
|
150
|
+
|----------------|-------------|
|
|
151
|
+
| `activeFilters` | `useState<Record<string, string>>({})` |
|
|
152
|
+
| `groupByColumns` | `useState<string[]>([])` |
|
|
153
|
+
| `collapsedGroups` | `useState<Record<string, boolean>>({})` |
|
|
154
|
+
| `columnOrder` | `useState<string[]>([])` |
|
|
155
|
+
| `hiddenRowReasons` | `useState<Record<string, string[]>>({})` |
|
|
156
|
+
| `sortState` | `useState<SortState>(...)` |
|
|
157
|
+
| `focusedCell` | `useState<GridCellPosition \| null>(null)` |
|
|
158
|
+
| `editingCell` | `useState<GridCellPosition \| null>(null)` |
|
|
159
|
+
| `editingValue` | `useState('')` |
|
|
160
|
+
| `expandedRows` | `useState<Record<string, boolean>>({})` |
|
|
161
|
+
| `expandedTreeRows` | `useState<Record<string, boolean>>({})` |
|
|
162
|
+
| `currentPage` | `useState(1)` |
|
|
163
|
+
| `pageSize` | `useState(0)` |
|
|
164
|
+
| `benchmarkResult` | `useState<GridBenchmarkResult \| null>(null)` |
|
|
165
|
+
| `infiniteScrollState` | `useState<GridInfiniteScrollState>(...)` |
|
|
166
|
+
|
|
167
|
+
**Key behaviors:**
|
|
168
|
+
- Call `createGridApi()` once in a `useRef` with bindings that dispatch state updates
|
|
169
|
+
- Memoize `buildGridPipeline()` via `useMemo` dependent on all state inputs
|
|
170
|
+
- Memoize `visibleColumns` via `useMemo`
|
|
171
|
+
- Resolve `labels` via `useMemo(() => resolveGridLabels(options.labels), [options.labels])`
|
|
172
|
+
- Reset state when `options.id` changes (same as the Angular effect)
|
|
173
|
+
- Call `options.onRegisterApi?.(gridApi)` once
|
|
174
|
+
|
|
175
|
+
**Returns:**
|
|
176
|
+
- All computed values: `pipeline`, `visibleColumns`, `labels`, `gridTemplateColumns`, etc.
|
|
177
|
+
- All action dispatchers: `toggleSort()`, `updateFilter()`, `toggleGrouping()`, etc.
|
|
178
|
+
- `gridApi` ref
|
|
179
|
+
|
|
180
|
+
## UiGrid.tsx component
|
|
181
|
+
|
|
182
|
+
**Props:**
|
|
183
|
+
```tsx
|
|
184
|
+
interface UiGridProps {
|
|
185
|
+
options: GridOptions;
|
|
186
|
+
onRegisterApi?: (api: UiGridApi) => void;
|
|
187
|
+
/** Render prop for custom cell content. Return null to use default text. */
|
|
188
|
+
cellRenderer?: (context: GridCellTemplateContext) => React.ReactNode;
|
|
189
|
+
/** Render prop for expandable row content. */
|
|
190
|
+
expandableRenderer?: (context: GridExpandableTemplateContext) => React.ReactNode;
|
|
191
|
+
className?: string;
|
|
192
|
+
}
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
**Rendering structure:** Match the Angular template exactly — same CSS classes, same `data-*` attributes, same ARIA roles/attributes, same part attributes. Reference the Angular template at `projects/ui-grid/src/lib/grid/ui-grid.component.html`.
|
|
196
|
+
|
|
197
|
+
**Key sections:**
|
|
198
|
+
1. Hero header with title, benchmark button, export button, stats
|
|
199
|
+
2. Metrics strip
|
|
200
|
+
3. Grid frame (`role="grid"`) with:
|
|
201
|
+
- Header row (`role="row"`) with column headers (`role="columnheader"`, `aria-sort`)
|
|
202
|
+
- Sort buttons with SVG icons
|
|
203
|
+
- Group toggle buttons with SVG icons
|
|
204
|
+
- Filter row
|
|
205
|
+
- Display items via `ng-template` equivalent (just inline JSX):
|
|
206
|
+
- Group rows with disclosure chevron SVGs
|
|
207
|
+
- Expandable rows
|
|
208
|
+
- Data rows with cells (`role="gridcell"`, `tabindex="0"`)
|
|
209
|
+
- Tree toggle buttons with chevron SVGs
|
|
210
|
+
- Expand toggle buttons with chevron SVGs
|
|
211
|
+
- Cell editor input (when editing)
|
|
212
|
+
- Cell template / display value
|
|
213
|
+
- Virtual scroll viewport OR plain list
|
|
214
|
+
- Empty state
|
|
215
|
+
- Pagination footer with arrow SVG icons
|
|
216
|
+
|
|
217
|
+
**Feature flag guards:** Same pattern as Angular template — wrap sections in `{FEATURE_SORTING && ...}` etc.
|
|
218
|
+
|
|
219
|
+
**Focus management:** For cell focus/editor focus, use `useRef` + `useEffect` instead of the Angular `focusGridRenderedCell`/`focusGridEditor` (those use shadowRoot which React won't have). Query the grid container ref directly.
|
|
220
|
+
|
|
221
|
+
**Keyboard handling:** Port `handleCellKeyDown` and `handleEditorKeyDown` logic directly — it's all pure key checking + command dispatch.
|
|
222
|
+
|
|
223
|
+
## Styles (ui-grid.css)
|
|
224
|
+
|
|
225
|
+
Adapt from `projects/ui-grid/src/lib/grid/grid.core.styles.scss`:
|
|
226
|
+
- Replace `:host` selectors with `.ui-grid-host`
|
|
227
|
+
- Replace `:host *` with `.ui-grid-host *`
|
|
228
|
+
- Keep ALL CSS custom properties identical (same `--ui-grid-*` variables)
|
|
229
|
+
- Keep all class names identical
|
|
230
|
+
- Add `.ui-grid-host { display: block; color: var(--ui-grid-cell-color); }`
|
|
231
|
+
- Ship as plain CSS (no modules needed — class names are scoped by convention)
|
|
232
|
+
|
|
233
|
+
## Tests
|
|
234
|
+
|
|
235
|
+
Use vitest + @testing-library/react + jsdom.
|
|
236
|
+
|
|
237
|
+
**Port these key scenarios from the Angular spec:**
|
|
238
|
+
1. Registers the API and renders headers and rows
|
|
239
|
+
2. Filters rows and renders empty state
|
|
240
|
+
3. Sorts rows and cycles sort state from header button
|
|
241
|
+
4. Groups rows and collapses groups
|
|
242
|
+
5. Exports visible rows as CSV
|
|
243
|
+
6. Virtualizes rows when count crosses threshold
|
|
244
|
+
7. Paginates rows
|
|
245
|
+
8. Keyboard cell editing (commit, navigate, cancel)
|
|
246
|
+
9. Resolves custom i18n label overrides
|
|
247
|
+
10. Feature flags disable unused template sections
|
|
248
|
+
|
|
249
|
+
## Public API (index.ts)
|
|
250
|
+
|
|
251
|
+
```ts
|
|
252
|
+
export { UiGrid } from './UiGrid';
|
|
253
|
+
export type { UiGridProps } from './UiGrid';
|
|
254
|
+
export { useGridState } from './useGridState';
|
|
255
|
+
export { useVirtualScroll } from './useVirtualScroll';
|
|
256
|
+
|
|
257
|
+
// Re-export core types consumers need
|
|
258
|
+
export type {
|
|
259
|
+
GridOptions,
|
|
260
|
+
GridColumnDef,
|
|
261
|
+
GridRow,
|
|
262
|
+
GridRecord,
|
|
263
|
+
GridLabels,
|
|
264
|
+
GridCellTemplateContext,
|
|
265
|
+
GridExpandableTemplateContext,
|
|
266
|
+
GridCellEditableContext,
|
|
267
|
+
GridBenchmarkResult,
|
|
268
|
+
GridSavedState,
|
|
269
|
+
SortState,
|
|
270
|
+
} from '@ornery/ui-grid';
|
|
271
|
+
|
|
272
|
+
export type { UiGridApi } from '@ornery/ui-grid';
|
|
273
|
+
export { DEFAULT_GRID_LABELS } from '@ornery/ui-grid';
|
|
274
|
+
```
|
|
275
|
+
|
|
276
|
+
## Important notes
|
|
277
|
+
|
|
278
|
+
- Do NOT use Shadow DOM — React doesn't support it well. Use a regular div with `className="ui-grid-host"`.
|
|
279
|
+
- Do NOT add any Angular dependencies.
|
|
280
|
+
- The `GridOptions.cellTemplate` field is `TemplateRef` (Angular-specific). The React wrapper ignores it and uses the `cellRenderer` prop instead.
|
|
281
|
+
- Same for `expandableRowTemplate` → use `expandableRenderer` prop.
|
|
282
|
+
- The `GridOptions.onRegisterApi` still works — it's called with the same `UiGridApi` object.
|
|
283
|
+
- Keep the `GridColumnDef.cellRenderer` function (returns string) — it's already framework-agnostic.
|
package/demo/index.html
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8" />
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
|
+
<title>UI Grid React Demo</title>
|
|
7
|
+
<style>
|
|
8
|
+
*, *::before, *::after { box-sizing: border-box; margin: 0; }
|
|
9
|
+
body { font-family: system-ui, -apple-system, sans-serif; background: #f5f5f7; color: #1d1d1f; }
|
|
10
|
+
</style>
|
|
11
|
+
</head>
|
|
12
|
+
<body>
|
|
13
|
+
<div id="root"></div>
|
|
14
|
+
<script type="module" src="./main.tsx"></script>
|
|
15
|
+
</body>
|
|
16
|
+
</html>
|
package/demo/main.tsx
ADDED
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import React, { useState, useMemo } from 'react';
|
|
2
|
+
import { createRoot } from 'react-dom/client';
|
|
3
|
+
import { UiGrid } from '../src/index';
|
|
4
|
+
import type { GridOptions, UiGridApi } from '../src/index';
|
|
5
|
+
import { FILTER_CONDITIONS } from '@ornery/ui-grid';
|
|
6
|
+
import '../src/ui-grid.css';
|
|
7
|
+
|
|
8
|
+
const statuses = ['Active', 'Expansion', 'Enterprise', 'Pilot'] as const;
|
|
9
|
+
const companies = ['Northwind', 'Blue Harbor', 'Forge Group', 'Larkspur', 'Atlas'] as const;
|
|
10
|
+
const owners = ['Casey Tran', 'Jordan Silva', 'Priya Rao', 'Evan Brooks', 'Mina Patel'] as const;
|
|
11
|
+
|
|
12
|
+
function createDemoData(count = 100_000) {
|
|
13
|
+
return Array.from({ length: count }, (_, i) => ({
|
|
14
|
+
id: `row-${i + 1}`,
|
|
15
|
+
name: `Customer ${i + 1}`,
|
|
16
|
+
company: companies[i % companies.length],
|
|
17
|
+
revenue: 40000 + i * 1350,
|
|
18
|
+
status: statuses[i % statuses.length],
|
|
19
|
+
renewalDate: `2026-${String((i % 12) + 1).padStart(2, '0')}-${String((i % 27) + 1).padStart(2, '0')}`,
|
|
20
|
+
account: { owner: owners[i % owners.length] },
|
|
21
|
+
}));
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const currencyFormat = new Intl.NumberFormat('en-US', {
|
|
25
|
+
style: 'currency',
|
|
26
|
+
currency: 'USD',
|
|
27
|
+
maximumFractionDigits: 0,
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
function App() {
|
|
31
|
+
const [gridApi, setGridApi] = useState<UiGridApi | null>(null);
|
|
32
|
+
const data = useMemo(() => createDemoData(), []);
|
|
33
|
+
|
|
34
|
+
const options = useMemo<GridOptions>(
|
|
35
|
+
() => ({
|
|
36
|
+
id: 'react-demo-grid',
|
|
37
|
+
title: 'UI Grid React Demo',
|
|
38
|
+
emptyMessage: 'No rows match the current filters.',
|
|
39
|
+
rowHeight: 48,
|
|
40
|
+
viewportHeight: 620,
|
|
41
|
+
enableSorting: true,
|
|
42
|
+
enableFiltering: true,
|
|
43
|
+
enableGrouping: true,
|
|
44
|
+
enableColumnMoving: true,
|
|
45
|
+
enableVirtualization: true,
|
|
46
|
+
enableCellEditOnFocus: true,
|
|
47
|
+
virtualizationThreshold: 25,
|
|
48
|
+
grouping: { groupBy: ['status'] },
|
|
49
|
+
rowIdentity: (row) => String(row['id']),
|
|
50
|
+
onRegisterApi: (api) => setGridApi(api as UiGridApi),
|
|
51
|
+
columnDefs: [
|
|
52
|
+
{
|
|
53
|
+
name: 'name',
|
|
54
|
+
displayName: 'Customer',
|
|
55
|
+
width: 'minmax(14rem, 1.2fr)',
|
|
56
|
+
enableCellEdit: true,
|
|
57
|
+
},
|
|
58
|
+
{ name: 'company', width: 'minmax(12rem, 1fr)', enableCellEdit: true },
|
|
59
|
+
{
|
|
60
|
+
name: 'revenue',
|
|
61
|
+
type: 'number',
|
|
62
|
+
align: 'end',
|
|
63
|
+
width: 'minmax(10rem, 0.7fr)',
|
|
64
|
+
filter: { condition: FILTER_CONDITIONS.greaterThan },
|
|
65
|
+
formatter: (value) => currencyFormat.format(Number(value ?? 0)),
|
|
66
|
+
},
|
|
67
|
+
{
|
|
68
|
+
name: 'status',
|
|
69
|
+
width: 'minmax(8rem, 0.7fr)',
|
|
70
|
+
filter: { condition: FILTER_CONDITIONS.exact },
|
|
71
|
+
},
|
|
72
|
+
{
|
|
73
|
+
name: 'renewalDate',
|
|
74
|
+
type: 'date',
|
|
75
|
+
displayName: 'Renewal',
|
|
76
|
+
width: 'minmax(11rem, 0.8fr)',
|
|
77
|
+
formatter: (value) => new Date(String(value)).toLocaleDateString('en-US'),
|
|
78
|
+
},
|
|
79
|
+
{
|
|
80
|
+
name: 'owner',
|
|
81
|
+
field: 'account.owner',
|
|
82
|
+
displayName: 'Account Owner',
|
|
83
|
+
width: 'minmax(11rem, 0.8fr)',
|
|
84
|
+
enableCellEdit: true,
|
|
85
|
+
},
|
|
86
|
+
],
|
|
87
|
+
data,
|
|
88
|
+
}),
|
|
89
|
+
[data],
|
|
90
|
+
);
|
|
91
|
+
|
|
92
|
+
return <UiGrid options={options} onRegisterApi={options.onRegisterApi} />;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
createRoot(document.getElementById('root')!).render(<App />);
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { defineConfig } from 'vite';
|
|
2
|
+
import react from '@vitejs/plugin-react';
|
|
3
|
+
import path from 'path';
|
|
4
|
+
|
|
5
|
+
export default defineConfig({
|
|
6
|
+
root: __dirname,
|
|
7
|
+
plugins: [react()],
|
|
8
|
+
resolve: {
|
|
9
|
+
alias: {
|
|
10
|
+
'@ornery/ui-grid': path.resolve(__dirname, '../../ui-grid/src/public-api.react.ts'),
|
|
11
|
+
},
|
|
12
|
+
},
|
|
13
|
+
});
|
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
2
|
+
import React$1 from 'react';
|
|
3
|
+
import { GridOptions, UiGridApi, GridCellTemplateContext, GridExpandableTemplateContext, PipelineResult, GridColumnDef, GridLabels, SortState, GridCellPosition, GridBenchmarkResult, GridInfiniteScrollState, DisplayItem, GroupItem, ExpandableItem, RowItem, GridRow } from '@ornery/ui-grid';
|
|
4
|
+
export { DEFAULT_GRID_LABELS, GridBenchmarkResult, GridCellEditableContext, GridCellTemplateContext, GridColumnDef, GridExpandableTemplateContext, GridLabels, GridOptions, GridRecord, GridRow, GridSavedState, SortState, UiGridApi } from '@ornery/ui-grid';
|
|
5
|
+
|
|
6
|
+
interface UiGridProps {
|
|
7
|
+
options: GridOptions;
|
|
8
|
+
onRegisterApi?: (api: UiGridApi) => void;
|
|
9
|
+
cellRenderer?: (context: GridCellTemplateContext) => React$1.ReactNode;
|
|
10
|
+
expandableRenderer?: (context: GridExpandableTemplateContext) => React$1.ReactNode;
|
|
11
|
+
className?: string;
|
|
12
|
+
}
|
|
13
|
+
declare function UiGrid({ options, onRegisterApi, cellRenderer, expandableRenderer, className }: UiGridProps): react_jsx_runtime.JSX.Element;
|
|
14
|
+
|
|
15
|
+
interface UseGridStateResult {
|
|
16
|
+
pipeline: PipelineResult;
|
|
17
|
+
visibleColumns: GridColumnDef[];
|
|
18
|
+
labels: GridLabels;
|
|
19
|
+
gridTemplateColumns: string;
|
|
20
|
+
gridApi: UiGridApi;
|
|
21
|
+
gridContainerRef: React.RefObject<HTMLDivElement | null>;
|
|
22
|
+
activeFilters: Record<string, string>;
|
|
23
|
+
groupByColumns: string[];
|
|
24
|
+
collapsedGroups: Record<string, boolean>;
|
|
25
|
+
sortState: SortState;
|
|
26
|
+
focusedCell: GridCellPosition | null;
|
|
27
|
+
editingCell: GridCellPosition | null;
|
|
28
|
+
editingValue: string;
|
|
29
|
+
expandedRows: Record<string, boolean>;
|
|
30
|
+
expandedTreeRows: Record<string, boolean>;
|
|
31
|
+
currentPage: number;
|
|
32
|
+
pageSize: number;
|
|
33
|
+
benchmarkResult: GridBenchmarkResult | null;
|
|
34
|
+
infiniteScrollState: GridInfiniteScrollState;
|
|
35
|
+
totalRows: number;
|
|
36
|
+
visibleRowCount: number;
|
|
37
|
+
displayItems: DisplayItem[];
|
|
38
|
+
virtualizationEnabled: boolean;
|
|
39
|
+
pipelineMs: number;
|
|
40
|
+
paginationCurrentPage: number;
|
|
41
|
+
paginationTotalPages: number;
|
|
42
|
+
paginationSelectedPageSize: number;
|
|
43
|
+
rowSize: number;
|
|
44
|
+
viewportHeightPx: string;
|
|
45
|
+
headerLabel: (column: GridColumnDef) => string;
|
|
46
|
+
isGroupItem: (item: DisplayItem) => item is GroupItem;
|
|
47
|
+
isExpandableItem: (item: DisplayItem) => item is ExpandableItem;
|
|
48
|
+
isRowItem: (item: DisplayItem) => item is RowItem;
|
|
49
|
+
isOddStripedRow: (item: DisplayItem) => boolean;
|
|
50
|
+
sortButtonLabel: (column: GridColumnDef) => string;
|
|
51
|
+
sortAriaSort: (column: GridColumnDef) => string;
|
|
52
|
+
sortDirection: (column: GridColumnDef) => string;
|
|
53
|
+
groupingButtonLabel: (column: GridColumnDef) => string;
|
|
54
|
+
filterValue: (columnName: string) => string;
|
|
55
|
+
filterPlaceholder: (column: GridColumnDef) => string;
|
|
56
|
+
isFilterInputDisabled: (column: GridColumnDef) => boolean;
|
|
57
|
+
groupDisclosureLabel: (item: GroupItem) => string;
|
|
58
|
+
displayValue: (row: GridRow, column: GridColumnDef) => string;
|
|
59
|
+
isFocusedCell: (row: GridRow, column: GridColumnDef) => boolean;
|
|
60
|
+
isEditingCell: (row: GridRow, column: GridColumnDef) => boolean;
|
|
61
|
+
editorInputType: (column: GridColumnDef) => string;
|
|
62
|
+
cellContext: (row: GridRow, column: GridColumnDef) => GridCellTemplateContext;
|
|
63
|
+
expandedContext: (row: GridRow) => GridExpandableTemplateContext & Record<string, unknown>;
|
|
64
|
+
columnWidth: (column: GridColumnDef) => string;
|
|
65
|
+
isColumnSortable: (column: GridColumnDef) => boolean;
|
|
66
|
+
isColumnFilterable: (column: GridColumnDef) => boolean;
|
|
67
|
+
cellIndent: (row: GridRow, column: GridColumnDef) => string;
|
|
68
|
+
treeToggleLabel: (row: GridRow) => string;
|
|
69
|
+
isTreeRowExpanded: (row: GridRow) => boolean;
|
|
70
|
+
expandToggleLabel: (row: GridRow) => string;
|
|
71
|
+
isGrouped: (column: GridColumnDef) => boolean;
|
|
72
|
+
showTreeToggle: (row: GridRow, column: GridColumnDef) => boolean;
|
|
73
|
+
showExpandToggle: (row: GridRow, column: GridColumnDef) => boolean;
|
|
74
|
+
showPaginationControls: () => boolean;
|
|
75
|
+
paginationSummary: () => string;
|
|
76
|
+
pageSizeOptions: () => number[];
|
|
77
|
+
isCellEditable: (row: GridRow, column: GridColumnDef, triggerEvent?: Event | KeyboardEvent | null) => boolean;
|
|
78
|
+
shouldEditOnFocus: (column: GridColumnDef) => boolean;
|
|
79
|
+
sortingFeature: boolean;
|
|
80
|
+
filteringFeature: boolean;
|
|
81
|
+
groupingFeature: boolean;
|
|
82
|
+
paginationFeature: boolean;
|
|
83
|
+
cellEditFeature: boolean;
|
|
84
|
+
expandableFeature: boolean;
|
|
85
|
+
treeViewFeature: boolean;
|
|
86
|
+
infiniteScrollFeature: boolean;
|
|
87
|
+
columnMovingFeature: boolean;
|
|
88
|
+
csvExportFeature: boolean;
|
|
89
|
+
isGroupingEnabled: () => boolean;
|
|
90
|
+
isFilteringEnabled: () => boolean;
|
|
91
|
+
toggleSort: (column: GridColumnDef) => void;
|
|
92
|
+
updateFilter: (columnName: string, value: string) => void;
|
|
93
|
+
clearAllFilters: () => void;
|
|
94
|
+
toggleGrouping: (column: GridColumnDef, event?: React.MouseEvent) => void;
|
|
95
|
+
toggleGroup: (item: GroupItem) => void;
|
|
96
|
+
focusCell: (row: GridRow, column: GridColumnDef, triggerEvent?: Event | KeyboardEvent | null) => void;
|
|
97
|
+
handleCellKeyDown: (row: GridRow, column: GridColumnDef, event: React.KeyboardEvent) => void;
|
|
98
|
+
handleCellDoubleClick: (row: GridRow, column: GridColumnDef, event: React.MouseEvent) => void;
|
|
99
|
+
updateEditingValue: (value: string) => void;
|
|
100
|
+
handleEditorKeyDown: (event: React.KeyboardEvent) => void;
|
|
101
|
+
handleEditorBlur: (event: React.FocusEvent) => void;
|
|
102
|
+
toggleRowExpansion: (row: GridRow, event?: React.MouseEvent) => void;
|
|
103
|
+
toggleTreeRow: (row: GridRow, event?: React.MouseEvent) => void;
|
|
104
|
+
moveColumn: (fromIndex: number, toIndex: number) => void;
|
|
105
|
+
nextPage: () => void;
|
|
106
|
+
previousPage: () => void;
|
|
107
|
+
onPageSizeChange: (value: string) => void;
|
|
108
|
+
runBenchmark: (iterations?: number) => GridBenchmarkResult;
|
|
109
|
+
exportCsv: () => void;
|
|
110
|
+
onViewportScroll: (startIndex: number) => void;
|
|
111
|
+
}
|
|
112
|
+
declare function useGridState(options: GridOptions, onRegisterApi?: (api: UiGridApi) => void): UseGridStateResult;
|
|
113
|
+
|
|
114
|
+
interface UseVirtualScrollOptions {
|
|
115
|
+
itemCount: number;
|
|
116
|
+
itemSize: number;
|
|
117
|
+
viewportHeight: number;
|
|
118
|
+
overscan?: number;
|
|
119
|
+
}
|
|
120
|
+
interface UseVirtualScrollResult {
|
|
121
|
+
visibleRange: {
|
|
122
|
+
start: number;
|
|
123
|
+
end: number;
|
|
124
|
+
};
|
|
125
|
+
totalHeight: number;
|
|
126
|
+
offsetY: number;
|
|
127
|
+
onScroll: (event: React.UIEvent<HTMLDivElement>) => void;
|
|
128
|
+
viewportRef: React.RefObject<HTMLDivElement | null>;
|
|
129
|
+
scrollTop: number;
|
|
130
|
+
}
|
|
131
|
+
declare function useVirtualScroll(options: UseVirtualScrollOptions): UseVirtualScrollResult;
|
|
132
|
+
|
|
133
|
+
export { UiGrid, type UiGridProps, type UseGridStateResult, type UseVirtualScrollOptions, type UseVirtualScrollResult, useGridState, useVirtualScroll };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
2
|
+
import React$1 from 'react';
|
|
3
|
+
import { GridOptions, UiGridApi, GridCellTemplateContext, GridExpandableTemplateContext, PipelineResult, GridColumnDef, GridLabels, SortState, GridCellPosition, GridBenchmarkResult, GridInfiniteScrollState, DisplayItem, GroupItem, ExpandableItem, RowItem, GridRow } from '@ornery/ui-grid';
|
|
4
|
+
export { DEFAULT_GRID_LABELS, GridBenchmarkResult, GridCellEditableContext, GridCellTemplateContext, GridColumnDef, GridExpandableTemplateContext, GridLabels, GridOptions, GridRecord, GridRow, GridSavedState, SortState, UiGridApi } from '@ornery/ui-grid';
|
|
5
|
+
|
|
6
|
+
interface UiGridProps {
|
|
7
|
+
options: GridOptions;
|
|
8
|
+
onRegisterApi?: (api: UiGridApi) => void;
|
|
9
|
+
cellRenderer?: (context: GridCellTemplateContext) => React$1.ReactNode;
|
|
10
|
+
expandableRenderer?: (context: GridExpandableTemplateContext) => React$1.ReactNode;
|
|
11
|
+
className?: string;
|
|
12
|
+
}
|
|
13
|
+
declare function UiGrid({ options, onRegisterApi, cellRenderer, expandableRenderer, className }: UiGridProps): react_jsx_runtime.JSX.Element;
|
|
14
|
+
|
|
15
|
+
interface UseGridStateResult {
|
|
16
|
+
pipeline: PipelineResult;
|
|
17
|
+
visibleColumns: GridColumnDef[];
|
|
18
|
+
labels: GridLabels;
|
|
19
|
+
gridTemplateColumns: string;
|
|
20
|
+
gridApi: UiGridApi;
|
|
21
|
+
gridContainerRef: React.RefObject<HTMLDivElement | null>;
|
|
22
|
+
activeFilters: Record<string, string>;
|
|
23
|
+
groupByColumns: string[];
|
|
24
|
+
collapsedGroups: Record<string, boolean>;
|
|
25
|
+
sortState: SortState;
|
|
26
|
+
focusedCell: GridCellPosition | null;
|
|
27
|
+
editingCell: GridCellPosition | null;
|
|
28
|
+
editingValue: string;
|
|
29
|
+
expandedRows: Record<string, boolean>;
|
|
30
|
+
expandedTreeRows: Record<string, boolean>;
|
|
31
|
+
currentPage: number;
|
|
32
|
+
pageSize: number;
|
|
33
|
+
benchmarkResult: GridBenchmarkResult | null;
|
|
34
|
+
infiniteScrollState: GridInfiniteScrollState;
|
|
35
|
+
totalRows: number;
|
|
36
|
+
visibleRowCount: number;
|
|
37
|
+
displayItems: DisplayItem[];
|
|
38
|
+
virtualizationEnabled: boolean;
|
|
39
|
+
pipelineMs: number;
|
|
40
|
+
paginationCurrentPage: number;
|
|
41
|
+
paginationTotalPages: number;
|
|
42
|
+
paginationSelectedPageSize: number;
|
|
43
|
+
rowSize: number;
|
|
44
|
+
viewportHeightPx: string;
|
|
45
|
+
headerLabel: (column: GridColumnDef) => string;
|
|
46
|
+
isGroupItem: (item: DisplayItem) => item is GroupItem;
|
|
47
|
+
isExpandableItem: (item: DisplayItem) => item is ExpandableItem;
|
|
48
|
+
isRowItem: (item: DisplayItem) => item is RowItem;
|
|
49
|
+
isOddStripedRow: (item: DisplayItem) => boolean;
|
|
50
|
+
sortButtonLabel: (column: GridColumnDef) => string;
|
|
51
|
+
sortAriaSort: (column: GridColumnDef) => string;
|
|
52
|
+
sortDirection: (column: GridColumnDef) => string;
|
|
53
|
+
groupingButtonLabel: (column: GridColumnDef) => string;
|
|
54
|
+
filterValue: (columnName: string) => string;
|
|
55
|
+
filterPlaceholder: (column: GridColumnDef) => string;
|
|
56
|
+
isFilterInputDisabled: (column: GridColumnDef) => boolean;
|
|
57
|
+
groupDisclosureLabel: (item: GroupItem) => string;
|
|
58
|
+
displayValue: (row: GridRow, column: GridColumnDef) => string;
|
|
59
|
+
isFocusedCell: (row: GridRow, column: GridColumnDef) => boolean;
|
|
60
|
+
isEditingCell: (row: GridRow, column: GridColumnDef) => boolean;
|
|
61
|
+
editorInputType: (column: GridColumnDef) => string;
|
|
62
|
+
cellContext: (row: GridRow, column: GridColumnDef) => GridCellTemplateContext;
|
|
63
|
+
expandedContext: (row: GridRow) => GridExpandableTemplateContext & Record<string, unknown>;
|
|
64
|
+
columnWidth: (column: GridColumnDef) => string;
|
|
65
|
+
isColumnSortable: (column: GridColumnDef) => boolean;
|
|
66
|
+
isColumnFilterable: (column: GridColumnDef) => boolean;
|
|
67
|
+
cellIndent: (row: GridRow, column: GridColumnDef) => string;
|
|
68
|
+
treeToggleLabel: (row: GridRow) => string;
|
|
69
|
+
isTreeRowExpanded: (row: GridRow) => boolean;
|
|
70
|
+
expandToggleLabel: (row: GridRow) => string;
|
|
71
|
+
isGrouped: (column: GridColumnDef) => boolean;
|
|
72
|
+
showTreeToggle: (row: GridRow, column: GridColumnDef) => boolean;
|
|
73
|
+
showExpandToggle: (row: GridRow, column: GridColumnDef) => boolean;
|
|
74
|
+
showPaginationControls: () => boolean;
|
|
75
|
+
paginationSummary: () => string;
|
|
76
|
+
pageSizeOptions: () => number[];
|
|
77
|
+
isCellEditable: (row: GridRow, column: GridColumnDef, triggerEvent?: Event | KeyboardEvent | null) => boolean;
|
|
78
|
+
shouldEditOnFocus: (column: GridColumnDef) => boolean;
|
|
79
|
+
sortingFeature: boolean;
|
|
80
|
+
filteringFeature: boolean;
|
|
81
|
+
groupingFeature: boolean;
|
|
82
|
+
paginationFeature: boolean;
|
|
83
|
+
cellEditFeature: boolean;
|
|
84
|
+
expandableFeature: boolean;
|
|
85
|
+
treeViewFeature: boolean;
|
|
86
|
+
infiniteScrollFeature: boolean;
|
|
87
|
+
columnMovingFeature: boolean;
|
|
88
|
+
csvExportFeature: boolean;
|
|
89
|
+
isGroupingEnabled: () => boolean;
|
|
90
|
+
isFilteringEnabled: () => boolean;
|
|
91
|
+
toggleSort: (column: GridColumnDef) => void;
|
|
92
|
+
updateFilter: (columnName: string, value: string) => void;
|
|
93
|
+
clearAllFilters: () => void;
|
|
94
|
+
toggleGrouping: (column: GridColumnDef, event?: React.MouseEvent) => void;
|
|
95
|
+
toggleGroup: (item: GroupItem) => void;
|
|
96
|
+
focusCell: (row: GridRow, column: GridColumnDef, triggerEvent?: Event | KeyboardEvent | null) => void;
|
|
97
|
+
handleCellKeyDown: (row: GridRow, column: GridColumnDef, event: React.KeyboardEvent) => void;
|
|
98
|
+
handleCellDoubleClick: (row: GridRow, column: GridColumnDef, event: React.MouseEvent) => void;
|
|
99
|
+
updateEditingValue: (value: string) => void;
|
|
100
|
+
handleEditorKeyDown: (event: React.KeyboardEvent) => void;
|
|
101
|
+
handleEditorBlur: (event: React.FocusEvent) => void;
|
|
102
|
+
toggleRowExpansion: (row: GridRow, event?: React.MouseEvent) => void;
|
|
103
|
+
toggleTreeRow: (row: GridRow, event?: React.MouseEvent) => void;
|
|
104
|
+
moveColumn: (fromIndex: number, toIndex: number) => void;
|
|
105
|
+
nextPage: () => void;
|
|
106
|
+
previousPage: () => void;
|
|
107
|
+
onPageSizeChange: (value: string) => void;
|
|
108
|
+
runBenchmark: (iterations?: number) => GridBenchmarkResult;
|
|
109
|
+
exportCsv: () => void;
|
|
110
|
+
onViewportScroll: (startIndex: number) => void;
|
|
111
|
+
}
|
|
112
|
+
declare function useGridState(options: GridOptions, onRegisterApi?: (api: UiGridApi) => void): UseGridStateResult;
|
|
113
|
+
|
|
114
|
+
interface UseVirtualScrollOptions {
|
|
115
|
+
itemCount: number;
|
|
116
|
+
itemSize: number;
|
|
117
|
+
viewportHeight: number;
|
|
118
|
+
overscan?: number;
|
|
119
|
+
}
|
|
120
|
+
interface UseVirtualScrollResult {
|
|
121
|
+
visibleRange: {
|
|
122
|
+
start: number;
|
|
123
|
+
end: number;
|
|
124
|
+
};
|
|
125
|
+
totalHeight: number;
|
|
126
|
+
offsetY: number;
|
|
127
|
+
onScroll: (event: React.UIEvent<HTMLDivElement>) => void;
|
|
128
|
+
viewportRef: React.RefObject<HTMLDivElement | null>;
|
|
129
|
+
scrollTop: number;
|
|
130
|
+
}
|
|
131
|
+
declare function useVirtualScroll(options: UseVirtualScrollOptions): UseVirtualScrollResult;
|
|
132
|
+
|
|
133
|
+
export { UiGrid, type UiGridProps, type UseGridStateResult, type UseVirtualScrollOptions, type UseVirtualScrollResult, useGridState, useVirtualScroll };
|