@opencorestack/opengridx 3.4.0 → 3.5.1
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/CHANGELOG.md +45 -0
- package/README.md +10 -3
- package/dist/ai.cjs +2 -1
- package/dist/ai.cjs.map +1 -1
- package/dist/ai.d.ts +203 -3
- package/dist/ai.es.js +2 -1
- package/dist/ai.es.js.map +1 -1
- package/dist/index.d.ts +248 -4
- package/dist/opengridx.css +1 -1
- package/dist/opengridx.es.js +7334 -5906
- package/dist/opengridx.es.js.map +1 -1
- package/dist/opengridx.umd.js +16 -16
- package/dist/opengridx.umd.js.map +1 -1
- package/docs/API_REFERENCE.md +88 -7
- package/docs/README.md +3 -1
- package/docs/architecture/datagrid-orchestration.md +11 -5
- package/docs/components/filter-panel.md +1 -0
- package/docs/components/header.md +4 -1
- package/docs/components/toolbar.md +7 -0
- package/docs/customization/theming.md +3 -1
- package/docs/features/ai-toolkit.md +99 -5
- package/docs/features/cell-selection.md +8 -2
- package/docs/features/fill-handle.md +125 -0
- package/docs/features/filtering.md +3 -0
- package/docs/features/header-filters.md +138 -0
- package/docs/features/keyboard-navigation.md +20 -2
- package/docs/features/virtualization.md +5 -5
- package/docs/performance.md +212 -1
- package/docs/roadmap.md +3 -4
- package/lib/ai/agentTools.ts +160 -0
- package/lib/ai/index.ts +19 -2
- package/lib/ai/promptHandler.ts +109 -0
- package/lib/ai/types.ts +89 -25
- package/lib/ai/validate.ts +10 -0
- package/lib/components/AiAssistant/GridAiAssistantArea.tsx +26 -0
- package/lib/components/AiAssistant/GridAiAssistantPanel.css +171 -0
- package/lib/components/AiAssistant/GridAiAssistantPanel.tsx +145 -0
- package/lib/components/AiAssistant/useSpeechInput.ts +74 -0
- package/lib/components/Cell/Cell.css +28 -0
- package/lib/components/Cell/Cell.tsx +6 -2
- package/lib/components/DataGrid/DataGrid.tsx +84 -4
- package/lib/components/DataGrid/GridLiveRegion.tsx +5 -2
- package/lib/components/DataGrid/GridPinnedRows.tsx +5 -1
- package/lib/components/DataGrid/GridVirtualRows.tsx +5 -1
- package/lib/components/FilterPanel/FilterPanel.tsx +2 -9
- package/lib/components/Header/Header.tsx +44 -23
- package/lib/components/Header/headerColumnStyle.ts +28 -0
- package/lib/components/HeaderFilter/HeaderFilter.css +160 -0
- package/lib/components/HeaderFilter/HeaderFilterCell.tsx +370 -0
- package/lib/components/HeaderFilter/HeaderFilterOperatorMenu.tsx +124 -0
- package/lib/components/HeaderFilter/HeaderFilterRow.tsx +157 -0
- package/lib/components/Toolbar/GridToolbar.tsx +51 -0
- package/lib/hooks/core/gridApiPlaceholder.ts +8 -0
- package/lib/hooks/core/gridToolbarHostContext.ts +5 -0
- package/lib/hooks/core/useGridApiMethods.ts +39 -0
- package/lib/hooks/core/useGridAriaRows.ts +5 -3
- package/lib/hooks/core/useGridCellSelection.ts +77 -11
- package/lib/hooks/core/useGridColumnsPanel.ts +24 -1
- package/lib/hooks/core/useGridControlledState.ts +27 -0
- package/lib/hooks/core/useGridDevWarnings.ts +21 -6
- package/lib/hooks/core/useGridFillHandle.ts +323 -0
- package/lib/hooks/core/useGridKeyboardNavigation.ts +29 -10
- package/lib/hooks/core/useGridMaxContentHeight.ts +16 -0
- package/lib/hooks/core/useGridToolbarProps.ts +19 -0
- package/lib/hooks/core/useGridVirtualization.ts +19 -0
- package/lib/hooks/features/useGridAiAssistant.ts +284 -0
- package/lib/hooks/features/useGridBatchEdit.ts +2 -2
- package/lib/index.ts +3 -0
- package/lib/styles/opengridx.css +2 -0
- package/lib/theme/DataGridThemeProvider.tsx +2 -0
- package/lib/theme/types.ts +2 -0
- package/lib/types/index.ts +219 -1
- package/lib/utils/aiAssistant.ts +205 -0
- package/lib/utils/aiBrand.ts +17 -0
- package/lib/utils/cellSelection.ts +25 -5
- package/lib/utils/editing/fill.ts +238 -0
- package/lib/utils/filtering/index.ts +14 -0
- package/lib/utils/focus/index.ts +9 -3
- package/lib/utils/headerFilters.ts +189 -0
- package/lib/utils/maxElementHeight.ts +53 -0
- package/llms.txt +44 -4
- package/package.json +3 -2
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
import React, { useEffect, useRef, useState } from 'react';
|
|
2
|
+
import type { GridAiAssistantPanelProps } from '../../types';
|
|
3
|
+
import { Button } from '../ui/Button';
|
|
4
|
+
import { Input } from '../ui/Input';
|
|
5
|
+
import { useSpeechInput } from './useSpeechInput';
|
|
6
|
+
|
|
7
|
+
/** The sparkle of the Ask AI button and the panel title. */
|
|
8
|
+
export function AiSparkleIcon() {
|
|
9
|
+
return (
|
|
10
|
+
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.8" strokeLinejoin="round" aria-hidden="true" focusable="false">
|
|
11
|
+
<path d="M12 3l1.9 5.1L19 10l-5.1 1.9L12 17l-1.9-5.1L5 10l5.1-1.9z" />
|
|
12
|
+
<path d="M19 15l.8 2.2L22 18l-2.2.8L19 21l-.8-2.2L16 18l2.2-.8z" />
|
|
13
|
+
</svg>
|
|
14
|
+
);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function MicIcon() {
|
|
18
|
+
return (
|
|
19
|
+
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" aria-hidden="true" focusable="false">
|
|
20
|
+
<rect x="9" y="3" width="6" height="11" rx="3" />
|
|
21
|
+
<path d="M5 11a7 7 0 0 0 14 0M12 18v3" />
|
|
22
|
+
</svg>
|
|
23
|
+
);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* The built-in `aiAssistant` panel: prompt input (Enter sends), suggestions, microphone where the
|
|
28
|
+
* browser has speech recognition, Stop while a request runs, the reply, the applied changes as
|
|
29
|
+
* removable chips with Undo, what was ignored, and the session's earlier prompts.
|
|
30
|
+
*/
|
|
31
|
+
export function GridAiAssistantPanel(props: GridAiAssistantPanelProps) {
|
|
32
|
+
const {
|
|
33
|
+
status, statusText, message, errors, chips, history, suggestions, placeholder, voiceAvailable, canUndo,
|
|
34
|
+
submit, stop, undo, removeChip, close,
|
|
35
|
+
} = props;
|
|
36
|
+
const [text, setText] = useState('');
|
|
37
|
+
const inputRef = useRef<HTMLInputElement>(null);
|
|
38
|
+
const speech = useSpeechInput(setText);
|
|
39
|
+
const running = status === 'running';
|
|
40
|
+
|
|
41
|
+
useEffect(() => {
|
|
42
|
+
if (inputRef.current) inputRef.current.focus();
|
|
43
|
+
}, []);
|
|
44
|
+
|
|
45
|
+
const send = (prompt: string) => {
|
|
46
|
+
setText(prompt);
|
|
47
|
+
submit(prompt);
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
// The control a click acts on goes away (Stop, a chip, Undo): focus stays in the panel.
|
|
51
|
+
const thenFocusInput = (action: () => void) => () => {
|
|
52
|
+
action();
|
|
53
|
+
if (inputRef.current) inputRef.current.focus();
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
const handleSubmit = (event: React.FormEvent) => {
|
|
57
|
+
event.preventDefault();
|
|
58
|
+
if (!running) submit(text);
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
return (
|
|
62
|
+
<div className="ogx-ai-panel" role="dialog" aria-label="Ask AI">
|
|
63
|
+
<div className="ogx-ai-panel__header">
|
|
64
|
+
<span className="ogx-ai-panel__title"><AiSparkleIcon />Ask AI</span>
|
|
65
|
+
<Button size="small" className="ogx-ai-panel__close" aria-label="Close" onClick={close}>×</Button>
|
|
66
|
+
</div>
|
|
67
|
+
|
|
68
|
+
<form className="ogx-ai-panel__form" onSubmit={handleSubmit}>
|
|
69
|
+
<Input
|
|
70
|
+
ref={inputRef}
|
|
71
|
+
className="ogx-ai-panel__input"
|
|
72
|
+
value={text}
|
|
73
|
+
placeholder={placeholder}
|
|
74
|
+
aria-label="Prompt"
|
|
75
|
+
autoComplete="off"
|
|
76
|
+
onChange={(event) => setText(event.target.value)}
|
|
77
|
+
/>
|
|
78
|
+
{voiceAvailable && (
|
|
79
|
+
<Button
|
|
80
|
+
size="small"
|
|
81
|
+
variant="outlined"
|
|
82
|
+
className={`ogx-ai-panel__mic${speech.listening ? ' ogx-ai-panel__mic--on' : ''}`}
|
|
83
|
+
aria-label={speech.listening ? 'Stop dictation' : 'Dictate a prompt'}
|
|
84
|
+
aria-pressed={speech.listening}
|
|
85
|
+
onClick={speech.toggle}
|
|
86
|
+
>
|
|
87
|
+
<MicIcon />
|
|
88
|
+
</Button>
|
|
89
|
+
)}
|
|
90
|
+
{/* Separate keys: one reused <button> would turn into the submit button during the Stop click and submit again. */}
|
|
91
|
+
{running
|
|
92
|
+
? <Button key="stop" size="small" variant="outlined" onClick={thenFocusInput(stop)}>Stop</Button>
|
|
93
|
+
: <Button key="ask" size="small" variant="contained" color="primary" type="submit" disabled={text.trim() === ''}>Ask</Button>}
|
|
94
|
+
</form>
|
|
95
|
+
|
|
96
|
+
{suggestions.length > 0 && (
|
|
97
|
+
<div className="ogx-ai-panel__suggestions" aria-label="Suggestions" role="group">
|
|
98
|
+
{suggestions.map((suggestion) => (
|
|
99
|
+
<button key={suggestion} type="button" className="ogx-ai-panel__suggestion" disabled={running} onClick={() => send(suggestion)}>
|
|
100
|
+
{suggestion}
|
|
101
|
+
</button>
|
|
102
|
+
))}
|
|
103
|
+
</div>
|
|
104
|
+
)}
|
|
105
|
+
|
|
106
|
+
{statusText && <div className={`ogx-ai-panel__status ogx-ai-panel__status--${status}`}>{statusText}</div>}
|
|
107
|
+
{message && <p className="ogx-ai-panel__message">{message}</p>}
|
|
108
|
+
|
|
109
|
+
{(chips.length > 0 || canUndo) && (
|
|
110
|
+
<div className="ogx-ai-panel__changes">
|
|
111
|
+
<ul className="ogx-ai-panel__chips" aria-label="Applied changes">
|
|
112
|
+
{chips.map((chip) => (
|
|
113
|
+
<li key={chip.id} className="ogx-ai-chip" data-part={chip.part}>
|
|
114
|
+
<span className="ogx-ai-chip__label">{chip.label}</span>
|
|
115
|
+
<button type="button" className="ogx-ai-chip__remove" aria-label={`Remove ${chip.label}`} onClick={thenFocusInput(() => removeChip(chip.id))}>×</button>
|
|
116
|
+
</li>
|
|
117
|
+
))}
|
|
118
|
+
</ul>
|
|
119
|
+
{canUndo && <Button size="small" variant="outlined" className="ogx-ai-panel__undo" onClick={thenFocusInput(undo)}>Undo</Button>}
|
|
120
|
+
</div>
|
|
121
|
+
)}
|
|
122
|
+
|
|
123
|
+
{errors.length > 0 && (
|
|
124
|
+
<ul className="ogx-ai-panel__errors" aria-label="Ignored">
|
|
125
|
+
{errors.map((error, i) => (
|
|
126
|
+
<li key={`${error.path}:${i}`}>{error.path ? `Ignored: ${error.message}` : error.message}</li>
|
|
127
|
+
))}
|
|
128
|
+
</ul>
|
|
129
|
+
)}
|
|
130
|
+
|
|
131
|
+
{history.length > 0 && (
|
|
132
|
+
<div className="ogx-ai-panel__history">
|
|
133
|
+
<div className="ogx-ai-panel__history-title">Earlier</div>
|
|
134
|
+
<ul>
|
|
135
|
+
{history.map((entry, i) => (
|
|
136
|
+
<li key={`${i}:${entry.prompt}`}>
|
|
137
|
+
<button type="button" className="ogx-ai-panel__history-item" onClick={() => setText(entry.prompt)}>{entry.prompt}</button>
|
|
138
|
+
</li>
|
|
139
|
+
))}
|
|
140
|
+
</ul>
|
|
141
|
+
</div>
|
|
142
|
+
)}
|
|
143
|
+
</div>
|
|
144
|
+
);
|
|
145
|
+
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { useCallback, useEffect, useLayoutEffect, useRef, useState } from 'react';
|
|
2
|
+
import { attempt } from '../../utils/attempt';
|
|
3
|
+
|
|
4
|
+
/** The part of the Web Speech API's SpeechRecognition the panel uses. */
|
|
5
|
+
interface SpeechRecognitionLike {
|
|
6
|
+
lang: string;
|
|
7
|
+
interimResults: boolean;
|
|
8
|
+
maxAlternatives: number;
|
|
9
|
+
onresult: ((event: { results: ArrayLike<ArrayLike<{ transcript: string }>> }) => void) | null;
|
|
10
|
+
onend: (() => void) | null;
|
|
11
|
+
onerror: (() => void) | null;
|
|
12
|
+
start: () => void;
|
|
13
|
+
stop: () => void;
|
|
14
|
+
abort: () => void;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
type SpeechRecognitionConstructor = new () => SpeechRecognitionLike;
|
|
18
|
+
|
|
19
|
+
function getSpeechRecognition(): SpeechRecognitionConstructor | null {
|
|
20
|
+
if (typeof window === 'undefined') return null;
|
|
21
|
+
const w = window as unknown as { SpeechRecognition?: SpeechRecognitionConstructor; webkitSpeechRecognition?: SpeechRecognitionConstructor };
|
|
22
|
+
return w.SpeechRecognition ?? w.webkitSpeechRecognition ?? null;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Dictation for the prompt input. The browser asks for the microphone only when `toggle` starts it
|
|
27
|
+
* (a click), never on load; without speech recognition `toggle` does nothing.
|
|
28
|
+
*/
|
|
29
|
+
export function useSpeechInput(onText: (text: string) => void): { listening: boolean; toggle: () => void } {
|
|
30
|
+
const [listening, setListening] = useState(false);
|
|
31
|
+
const recognitionRef = useRef<SpeechRecognitionLike | null>(null);
|
|
32
|
+
const onTextRef = useRef(onText);
|
|
33
|
+
useLayoutEffect(() => {
|
|
34
|
+
onTextRef.current = onText;
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
useEffect(() => () => {
|
|
38
|
+
const recognition = recognitionRef.current;
|
|
39
|
+
recognitionRef.current = null;
|
|
40
|
+
if (recognition) attempt(() => recognition.abort());
|
|
41
|
+
}, []);
|
|
42
|
+
|
|
43
|
+
const toggle = useCallback(() => {
|
|
44
|
+
const running = recognitionRef.current;
|
|
45
|
+
if (running) {
|
|
46
|
+
attempt(() => running.stop());
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
const Recognition = getSpeechRecognition();
|
|
50
|
+
if (!Recognition) return;
|
|
51
|
+
const created = attempt(() => new Recognition());
|
|
52
|
+
if (!created.ok) return;
|
|
53
|
+
const recognition = created.value;
|
|
54
|
+
recognition.lang = typeof navigator === 'undefined' ? 'en-US' : navigator.language;
|
|
55
|
+
recognition.interimResults = false;
|
|
56
|
+
recognition.maxAlternatives = 1;
|
|
57
|
+
recognition.onresult = (event) => {
|
|
58
|
+
const first = event.results[0];
|
|
59
|
+
const transcript = first && first[0] ? first[0].transcript : '';
|
|
60
|
+
if (transcript) onTextRef.current(transcript);
|
|
61
|
+
};
|
|
62
|
+
const done = () => {
|
|
63
|
+
if (recognitionRef.current === recognition) recognitionRef.current = null;
|
|
64
|
+
setListening(false);
|
|
65
|
+
};
|
|
66
|
+
recognition.onend = done;
|
|
67
|
+
recognition.onerror = done;
|
|
68
|
+
recognitionRef.current = recognition;
|
|
69
|
+
setListening(true);
|
|
70
|
+
if (!attempt(() => recognition.start()).ok) done();
|
|
71
|
+
}, []);
|
|
72
|
+
|
|
73
|
+
return { listening, toggle };
|
|
74
|
+
}
|
|
@@ -52,6 +52,34 @@
|
|
|
52
52
|
.ogx__cell--range-right {
|
|
53
53
|
--ogx-range-edge-right: inset -2px 0 0 0 var(--ogx-range-border);
|
|
54
54
|
}
|
|
55
|
+
/*
|
|
56
|
+
* Fill handle (v3.5): the square on the bottom-right cell of the range. The cell clips its overflow,
|
|
57
|
+
* so the 24px hit area lies inside the corner and the visible square sits in its corner.
|
|
58
|
+
* touch-action: none lets a touch drag on it fill instead of scrolling.
|
|
59
|
+
*/
|
|
60
|
+
.ogx__cell-fill-handle {
|
|
61
|
+
position: absolute;
|
|
62
|
+
right: 0;
|
|
63
|
+
bottom: 0;
|
|
64
|
+
width: 24px;
|
|
65
|
+
height: 24px;
|
|
66
|
+
max-width: 100%;
|
|
67
|
+
max-height: 100%;
|
|
68
|
+
cursor: crosshair;
|
|
69
|
+
touch-action: none;
|
|
70
|
+
z-index: 1;
|
|
71
|
+
}
|
|
72
|
+
.ogx__cell-fill-handle::after {
|
|
73
|
+
content: '';
|
|
74
|
+
position: absolute;
|
|
75
|
+
right: 1px;
|
|
76
|
+
bottom: 1px;
|
|
77
|
+
width: 7px;
|
|
78
|
+
height: 7px;
|
|
79
|
+
box-sizing: border-box;
|
|
80
|
+
background-color: var(--ogx-fill-handle-color, var(--ogx-range-border));
|
|
81
|
+
border: 1px solid var(--ogx-grid-background, #fff);
|
|
82
|
+
}
|
|
55
83
|
/* While a pointer drag selects cells, the browser must not select text as well. */
|
|
56
84
|
.ogx--range-dragging,
|
|
57
85
|
.ogx--range-dragging * {
|
|
@@ -7,7 +7,7 @@ import { GridEditInputCell } from './GridEditInputCell';
|
|
|
7
7
|
import { CellErrorBoundary } from './CellErrorBoundary';
|
|
8
8
|
import { formatValueByType } from '../../utils/values';
|
|
9
9
|
import { attempt } from '../../utils/attempt';
|
|
10
|
-
import { RANGE_BOTTOM, RANGE_CELL, RANGE_LEFT, RANGE_RIGHT, RANGE_TOP } from '../../utils/cellSelection';
|
|
10
|
+
import { RANGE_BOTTOM, RANGE_CELL, RANGE_FILL_HANDLE, RANGE_LEFT, RANGE_RIGHT, RANGE_TOP } from '../../utils/cellSelection';
|
|
11
11
|
|
|
12
12
|
export interface CellProps<R extends GridRowModel = GridRowModel> {
|
|
13
13
|
onCellClick?: (params: GridCellParams<R>) => void;
|
|
@@ -50,7 +50,8 @@ export interface CellProps<R extends GridRowModel = GridRowModel> {
|
|
|
50
50
|
valueError?: unknown;
|
|
51
51
|
/**
|
|
52
52
|
* Place of the cell in the selected cell range (`cellSelection`), as bit flags: in range 1, top
|
|
53
|
-
* edge 2, bottom 4, left 8, right 16. 0 or absent: not in a range.
|
|
53
|
+
* edge 2, bottom 4, left 8, right 16; 32 draws the fill handle (v3.5). 0 or absent: not in a range.
|
|
54
|
+
* @since v3.3
|
|
54
55
|
*/
|
|
55
56
|
rangeFlags?: number;
|
|
56
57
|
}
|
|
@@ -341,6 +342,9 @@ function CellImpl<R extends GridRowModel = GridRowModel>(props: CellProps<R>) {
|
|
|
341
342
|
formattedValue
|
|
342
343
|
)}
|
|
343
344
|
</div>
|
|
345
|
+
{(rangeFlags & RANGE_FILL_HANDLE) !== 0 && !isEditing && (
|
|
346
|
+
<div className="ogx__cell-fill-handle" aria-hidden="true" />
|
|
347
|
+
)}
|
|
344
348
|
</div>
|
|
345
349
|
);
|
|
346
350
|
}
|
|
@@ -4,6 +4,8 @@ import { useGridKeyboardNavigation } from '../../hooks/core/useGridKeyboardNavig
|
|
|
4
4
|
import { useGridControlledState } from '../../hooks/core/useGridControlledState';
|
|
5
5
|
import { useGridThemeDimensions } from '../../hooks/core/useGridThemeDimensions';
|
|
6
6
|
import { useGridRowPipeline, type GridRowPipelineResult } from '../../hooks/core/useGridRowPipeline';
|
|
7
|
+
import { useGridMaxContentHeight } from '../../hooks/core/useGridMaxContentHeight';
|
|
8
|
+
import { countReachableRows } from '../../utils/maxElementHeight';
|
|
7
9
|
import { useGridVirtualization } from '../../hooks/core/useGridVirtualization';
|
|
8
10
|
import { useGridColumns } from '../../hooks/core/useGridColumns';
|
|
9
11
|
import { useGridVisibleRows } from '../../hooks/core/useGridVisibleRows';
|
|
@@ -30,6 +32,7 @@ import { useGridEditing } from '../../hooks/features/useGridEditing';
|
|
|
30
32
|
import { useGridBatchEdit } from '../../hooks/features/useGridBatchEdit';
|
|
31
33
|
import { useGridClipboardPaste } from '../../hooks/core/useGridClipboardPaste';
|
|
32
34
|
import { useGridUndoRedo, useGridEditCommitChannel, UNDO_HISTORY_LIMIT } from '../../hooks/core/useGridUndoRedo';
|
|
35
|
+
import { useGridFillHandle } from '../../hooks/core/useGridFillHandle';
|
|
33
36
|
import { useGridSpanning } from '../../hooks/features/useGridSpanning';
|
|
34
37
|
import { useGridSpanRowWindow, useGridSpanColumnWindow } from '../../hooks/features/useGridSpanRenderWindow';
|
|
35
38
|
import { useColumnGroupReorderGuard } from '../../hooks/features/useColumnGroupReorderGuard';
|
|
@@ -43,7 +46,7 @@ import { GridLoadingOverlay } from './GridLoadingOverlay';
|
|
|
43
46
|
import { GridPinnedRows } from './GridPinnedRows';
|
|
44
47
|
import { GridVirtualRows } from './GridVirtualRows';
|
|
45
48
|
import { GridStandaloneColumnPanel } from './GridStandaloneColumnPanel';
|
|
46
|
-
import { resolveGridModes
|
|
49
|
+
import { resolveGridModes } from '../../utils/gridModes';
|
|
47
50
|
import { useGridGroupingColumn } from '../../hooks/core/useGridGroupingColumn';
|
|
48
51
|
import { useGridRowIdOf, getDefaultRowId } from '../../hooks/core/useGridRowIdOf';
|
|
49
52
|
import { useGridApiRefBinding } from '../../hooks/core/useGridApiRefBinding';
|
|
@@ -65,6 +68,9 @@ import { GridPaginationArea } from './GridPaginationArea';
|
|
|
65
68
|
import { GridLiveRegion } from './GridLiveRegion';
|
|
66
69
|
import { useGridAriaRows } from '../../hooks/core/useGridAriaRows';
|
|
67
70
|
import { useGridToolbarProps } from '../../hooks/core/useGridToolbarProps';
|
|
71
|
+
import { useGridAiAssistant } from '../../hooks/features/useGridAiAssistant';
|
|
72
|
+
import { GridAiAssistantArea } from '../AiAssistant/GridAiAssistantArea';
|
|
73
|
+
import { DEFAULT_HEADER_FILTER_HEIGHT } from '../../utils/headerFilters';
|
|
68
74
|
import type { DataGridProps, DataGridUntypedColumnsProps, GridValidRowModel, GridRowModel, GridRowId, GridColDef } from '../../types';
|
|
69
75
|
|
|
70
76
|
/**
|
|
@@ -83,6 +89,8 @@ export function DataGrid<R extends GridRowModel = GridRowModel>(props: DataGridP
|
|
|
83
89
|
rowHeight: rowHeightProp,
|
|
84
90
|
headerHeight: headerHeightProp,
|
|
85
91
|
wrapHeaderText = false,
|
|
92
|
+
headerFilters = false,
|
|
93
|
+
headerFilterHeight = DEFAULT_HEADER_FILTER_HEIGHT,
|
|
86
94
|
autoHeight = false,
|
|
87
95
|
density,
|
|
88
96
|
checkboxSelection = false,
|
|
@@ -98,6 +106,8 @@ export function DataGrid<R extends GridRowModel = GridRowModel>(props: DataGridP
|
|
|
98
106
|
onClipboardPaste,
|
|
99
107
|
onBeforeClipboardPaste,
|
|
100
108
|
undoRedo = false,
|
|
109
|
+
disableFillHandle = false,
|
|
110
|
+
onFill,
|
|
101
111
|
onHistoryChange,
|
|
102
112
|
rowSelectionModel: propRowSelectionModel,
|
|
103
113
|
onRowSelectionModelChange: propOnRowSelectionModelChange,
|
|
@@ -138,6 +148,7 @@ export function DataGrid<R extends GridRowModel = GridRowModel>(props: DataGridP
|
|
|
138
148
|
groupingColDef,
|
|
139
149
|
|
|
140
150
|
rowGroupingModel: propRowGroupingModel,
|
|
151
|
+
onRowGroupingModelChange,
|
|
141
152
|
|
|
142
153
|
aggregationModel: propAggregationModel,
|
|
143
154
|
onAggregationModelChange,
|
|
@@ -177,12 +188,14 @@ export function DataGrid<R extends GridRowModel = GridRowModel>(props: DataGridP
|
|
|
177
188
|
localeText,
|
|
178
189
|
apiRef: propApiRef,
|
|
179
190
|
overscanRowCount = 3,
|
|
191
|
+
aiAssistant,
|
|
192
|
+
onAiAssistantApply,
|
|
193
|
+
onAiAssistantError,
|
|
180
194
|
} = props;
|
|
181
195
|
// Equal inline column definitions keep one identity, so the row passes memoised on it do not re-run.
|
|
182
196
|
const columns = useStableColumns(columnsProp);
|
|
183
197
|
|
|
184
198
|
const effectiveNoRowsLabel = localeText?.noRowsLabel ?? noRowsLabel;
|
|
185
|
-
const rowGroupingModel = propRowGroupingModel || EMPTY_ROW_GROUPING_MODEL;
|
|
186
199
|
|
|
187
200
|
const controlledState = useGridControlledState({
|
|
188
201
|
initialState,
|
|
@@ -192,6 +205,8 @@ export function DataGrid<R extends GridRowModel = GridRowModel>(props: DataGridP
|
|
|
192
205
|
onFilterModelChange,
|
|
193
206
|
aggregationModel: propAggregationModel,
|
|
194
207
|
onAggregationModelChange,
|
|
208
|
+
rowGroupingModel: propRowGroupingModel,
|
|
209
|
+
onRowGroupingModelChange,
|
|
195
210
|
columnVisibilityModel: propColumnVisibilityModel,
|
|
196
211
|
onColumnVisibilityModelChange,
|
|
197
212
|
pinnedColumns: propPinnedColumns,
|
|
@@ -213,6 +228,7 @@ export function DataGrid<R extends GridRowModel = GridRowModel>(props: DataGridP
|
|
|
213
228
|
selectedRowIds: selectionModelRowIds,
|
|
214
229
|
handleRowSelectionModelChange,
|
|
215
230
|
density: effectiveDensity,
|
|
231
|
+
rowGroupingModel, handleRowGroupingModelChange,
|
|
216
232
|
} = controlledState;
|
|
217
233
|
|
|
218
234
|
const pivot = useGridPivot({
|
|
@@ -226,6 +242,8 @@ export function DataGrid<R extends GridRowModel = GridRowModel>(props: DataGridP
|
|
|
226
242
|
hierarchyRequested: treeData || rowGroupingModel.length > 0,
|
|
227
243
|
});
|
|
228
244
|
const isPivotActive = pivot.isActive;
|
|
245
|
+
// The header filter row filters the source columns; pivot columns are generated, so it is off then.
|
|
246
|
+
const showHeaderFilters = headerFilters && !isPivotActive;
|
|
229
247
|
const {
|
|
230
248
|
rowReordering, hierarchyRowGroupingModel, isTreeDataRequested, isTreeData,
|
|
231
249
|
isRowGrouping, isHierarchyEnabled, hasGroupingColumn, pagination, listView,
|
|
@@ -459,6 +477,14 @@ export function DataGrid<R extends GridRowModel = GridRowModel>(props: DataGridP
|
|
|
459
477
|
dataRows,
|
|
460
478
|
}),
|
|
461
479
|
visibleColumns: renderOrderedColumns as unknown as GridColDef[],
|
|
480
|
+
rowGroupingModel,
|
|
481
|
+
onRowGroupingModelChange: handleRowGroupingModelChange,
|
|
482
|
+
aggregationModel,
|
|
483
|
+
onAggregationModelChange: handleAggregationModelChange,
|
|
484
|
+
columnVisibilityModel,
|
|
485
|
+
onColumnVisibilityModelChange: handleColumnVisibilityModelChange,
|
|
486
|
+
pivotModel: currentPivotModel,
|
|
487
|
+
onPivotModelChange: handlePivotModelChange,
|
|
462
488
|
});
|
|
463
489
|
|
|
464
490
|
// Indices address the consumer's own rows, whatever the sort, filter, page or pinning.
|
|
@@ -561,12 +587,14 @@ export function DataGrid<R extends GridRowModel = GridRowModel>(props: DataGridP
|
|
|
561
587
|
onRowsScrollEnd, overscanRowCount, rowCount: layout.unpinnedRowsLength, autoHeight, setDimensions, viewportRef,
|
|
562
588
|
});
|
|
563
589
|
|
|
590
|
+
const maxContentHeight = useGridMaxContentHeight(containerRef);
|
|
564
591
|
const virtualization = useGridVirtualization({
|
|
565
592
|
layout, scrollTop, scrollLeft,
|
|
566
593
|
viewportWidth: state.dimensions.viewportWidth,
|
|
567
594
|
viewportHeight: state.dimensions.viewportHeight,
|
|
568
595
|
autoHeight, rowReordering, hasDetailPanel, checkboxSelection, pinCheckboxColumn, pinExpandColumn,
|
|
569
596
|
overscanRows,
|
|
597
|
+
maxContentHeight: maxContentHeight ?? Infinity,
|
|
570
598
|
});
|
|
571
599
|
|
|
572
600
|
|
|
@@ -596,6 +624,7 @@ export function DataGrid<R extends GridRowModel = GridRowModel>(props: DataGridP
|
|
|
596
624
|
pinExpandColumn,
|
|
597
625
|
rowHeight: effectiveRowHeight,
|
|
598
626
|
cellSelection: cellSelection.keyboard,
|
|
627
|
+
headerFilters: showHeaderFilters,
|
|
599
628
|
});
|
|
600
629
|
|
|
601
630
|
const { handleCellClick, handleHeaderClick } = useGridPointerFocusHandlers<R>({
|
|
@@ -650,6 +679,23 @@ export function DataGrid<R extends GridRowModel = GridRowModel>(props: DataGridP
|
|
|
650
679
|
pinnedTopRowCount: pinnedTopRows.length,
|
|
651
680
|
});
|
|
652
681
|
|
|
682
|
+
// Fill handle (cellSelection): drag the range's corner, Ctrl/Cmd+D and Ctrl/Cmd+R.
|
|
683
|
+
const fillHandle = useGridFillHandle<R>({
|
|
684
|
+
containerRef, viewportRef,
|
|
685
|
+
selection: cellSelection,
|
|
686
|
+
disableFillHandle,
|
|
687
|
+
isEditing: editingHandlers.editingCell !== null,
|
|
688
|
+
allRenderableRows,
|
|
689
|
+
getRowId: getRowIdOf,
|
|
690
|
+
navigationColumns: navigationColumns as unknown as GridColDef<R>[],
|
|
691
|
+
columnIndexMap,
|
|
692
|
+
isCellEditable,
|
|
693
|
+
rowMetaMap,
|
|
694
|
+
getSpanOrigin: spanning.getSpanOrigin,
|
|
695
|
+
batchEdit,
|
|
696
|
+
onFill,
|
|
697
|
+
});
|
|
698
|
+
|
|
653
699
|
const { handleHideColumn, handlePinColumn } = useGridColumnMenuHandlers({
|
|
654
700
|
columnVisibilityModel,
|
|
655
701
|
onColumnVisibilityModelChange: handleColumnVisibilityModelChange,
|
|
@@ -659,6 +705,7 @@ export function DataGrid<R extends GridRowModel = GridRowModel>(props: DataGridP
|
|
|
659
705
|
|
|
660
706
|
const ariaRows = useGridAriaRows({
|
|
661
707
|
columnGroupingModel,
|
|
708
|
+
headerFilterRow: showHeaderFilters,
|
|
662
709
|
pinnedTopCount: pinnedTopRows.length,
|
|
663
710
|
pinnedBottomCount: pinnedBottomRows.length,
|
|
664
711
|
pagination,
|
|
@@ -700,6 +747,9 @@ export function DataGrid<R extends GridRowModel = GridRowModel>(props: DataGridP
|
|
|
700
747
|
totalRowCount: pinnedTopRows.length + centerRows.length + pinnedBottomRows.length,
|
|
701
748
|
pinnedRowsIgnored: isHierarchyEnabled && Boolean(pinnedRows?.top?.length || pinnedRows?.bottom?.length),
|
|
702
749
|
scrollHeight: virtualization.totalHeight,
|
|
750
|
+
maxContentHeight,
|
|
751
|
+
reachableRowCount: countReachableRows(virtualization.cumulativeHeights, virtualization.virtualContainerHeight),
|
|
752
|
+
unpinnedRowCount: virtualization.cumulativeHeights.length,
|
|
703
753
|
listViewWithoutColumn: listViewRequested && !listViewColumn,
|
|
704
754
|
listViewRowCount: listView && listViewColumn ? allRenderableRows.length : 0,
|
|
705
755
|
});
|
|
@@ -713,6 +763,15 @@ export function DataGrid<R extends GridRowModel = GridRowModel>(props: DataGridP
|
|
|
713
763
|
const columnsPanel = useGridColumnsPanel();
|
|
714
764
|
useGridStylesheetWarning(containerRef);
|
|
715
765
|
|
|
766
|
+
// The aiAssistant prompt panel (v3.5): applies validated replies through apiRef; apiRef.openAiAssistant.
|
|
767
|
+
const aiAssistantPanel = useGridAiAssistant({
|
|
768
|
+
apiRef,
|
|
769
|
+
options: aiAssistant,
|
|
770
|
+
columns: activeColumns as unknown as GridColDef[],
|
|
771
|
+
onApply: onAiAssistantApply,
|
|
772
|
+
onError: onAiAssistantError,
|
|
773
|
+
});
|
|
774
|
+
|
|
716
775
|
const NoRowsOverlaySlot = slots?.noRowsOverlay;
|
|
717
776
|
const LoadingOverlaySlot = slots?.loadingOverlay;
|
|
718
777
|
const loadingOverlay = LoadingOverlaySlot ? <LoadingOverlaySlot {...slotProps?.loadingOverlay} /> : undefined;
|
|
@@ -743,6 +802,12 @@ export function DataGrid<R extends GridRowModel = GridRowModel>(props: DataGridP
|
|
|
743
802
|
disableColumnReorder, columnGroupingModel, moveColumn, resetColumnOrder,
|
|
744
803
|
forceColumnsOpen: columnsPanel.toolbarPanelRequested,
|
|
745
804
|
onColumnsPanelClose: columnsPanel.closeToolbarPanel,
|
|
805
|
+
aiAssistantEnabled: aiAssistantPanel.enabled,
|
|
806
|
+
aiAssistantOpen: aiAssistantPanel.open,
|
|
807
|
+
onAiAssistantToggle: aiAssistantPanel.togglePanel,
|
|
808
|
+
aiAssistantLabel: localeText?.aiAssistantButton,
|
|
809
|
+
forceFiltersOpen: columnsPanel.toolbarFiltersRequested,
|
|
810
|
+
onFiltersPanelClose: columnsPanel.closeToolbarFiltersPanel,
|
|
746
811
|
});
|
|
747
812
|
|
|
748
813
|
// Props the row renderers (top-pinned, centre and bottom-pinned rows) all take.
|
|
@@ -764,6 +829,7 @@ export function DataGrid<R extends GridRowModel = GridRowModel>(props: DataGridP
|
|
|
764
829
|
rowMetaMap,
|
|
765
830
|
cellRange: cellSelection.displayRange,
|
|
766
831
|
cellRangeHasRowSpan: cellSelection.hasRowSpan,
|
|
832
|
+
fillHandle: fillHandle.handle,
|
|
767
833
|
};
|
|
768
834
|
|
|
769
835
|
return (
|
|
@@ -774,7 +840,8 @@ export function DataGrid<R extends GridRowModel = GridRowModel>(props: DataGridP
|
|
|
774
840
|
...style,
|
|
775
841
|
height: height ?? style?.height,
|
|
776
842
|
'--ogx-row-height': `${effectiveRowHeight}px`,
|
|
777
|
-
'--ogx-header-height': `${headerHeight}px
|
|
843
|
+
'--ogx-header-height': `${headerHeight}px`,
|
|
844
|
+
...(showHeaderFilters ? { '--ogx-header-filter-height': `${headerFilterHeight}px` } : undefined),
|
|
778
845
|
} as unknown as React.CSSProperties}
|
|
779
846
|
aria-busy={effectiveLoading}
|
|
780
847
|
>
|
|
@@ -784,6 +851,14 @@ export function DataGrid<R extends GridRowModel = GridRowModel>(props: DataGridP
|
|
|
784
851
|
</GridToolbarHostContext.Provider>
|
|
785
852
|
)}
|
|
786
853
|
|
|
854
|
+
{aiAssistantPanel.panelProps && (
|
|
855
|
+
<GridAiAssistantArea
|
|
856
|
+
panelProps={aiAssistantPanel.panelProps}
|
|
857
|
+
slot={slots?.aiAssistantPanel}
|
|
858
|
+
slotProps={slotProps?.aiAssistantPanel}
|
|
859
|
+
/>
|
|
860
|
+
)}
|
|
861
|
+
|
|
787
862
|
{/* Used by the column menu's Manage columns when no GridToolbar can show the panel */}
|
|
788
863
|
<GridStandaloneColumnPanel<R>
|
|
789
864
|
isOpen={columnsPanel.standalonePanelOpen}
|
|
@@ -912,6 +987,10 @@ export function DataGrid<R extends GridRowModel = GridRowModel>(props: DataGridP
|
|
|
912
987
|
onPinColumn={handlePinColumn}
|
|
913
988
|
wrapHeaderText={wrapHeaderText}
|
|
914
989
|
headerHeight={headerHeight}
|
|
990
|
+
headerFilters={showHeaderFilters}
|
|
991
|
+
filterModel={filterModel}
|
|
992
|
+
onFilterModelChange={handleFilterModelChange}
|
|
993
|
+
onOpenFilterPanel={columnsPanel.openFiltersPanel}
|
|
915
994
|
/>
|
|
916
995
|
|
|
917
996
|
<GridPinnedRows<R>
|
|
@@ -936,7 +1015,7 @@ export function DataGrid<R extends GridRowModel = GridRowModel>(props: DataGridP
|
|
|
936
1015
|
|
|
937
1016
|
<GridVirtualRows<R>
|
|
938
1017
|
{...rowRenderProps}
|
|
939
|
-
virtualContainerHeight={virtualization.
|
|
1018
|
+
virtualContainerHeight={virtualization.virtualContainerHeight}
|
|
940
1019
|
offsetTop={spanRowWindow.offsetTop}
|
|
941
1020
|
effectiveLoading={effectiveLoading}
|
|
942
1021
|
visibleRows={visibleRows}
|
|
@@ -1035,6 +1114,7 @@ export function DataGrid<R extends GridRowModel = GridRowModel>(props: DataGridP
|
|
|
1035
1114
|
dataRowCount={dataRows.length}
|
|
1036
1115
|
filterModel={filterModel}
|
|
1037
1116
|
cellSelectionAnnouncement={cellSelection.announcement}
|
|
1117
|
+
aiAssistantAnnouncement={aiAssistantPanel.announcement}
|
|
1038
1118
|
/>
|
|
1039
1119
|
|
|
1040
1120
|
{showLoadingOverRows && (
|
|
@@ -13,13 +13,15 @@ interface GridLiveRegionProps {
|
|
|
13
13
|
filterModel: GridFilterModel | undefined;
|
|
14
14
|
/** The size of the selected cell range (`cellSelection`), already debounced. */
|
|
15
15
|
cellSelectionAnnouncement?: string;
|
|
16
|
+
/** The `aiAssistant` status line ("Thinking…", "Applied 3 changes", an error). */
|
|
17
|
+
aiAssistantAnnouncement?: string;
|
|
16
18
|
}
|
|
17
19
|
|
|
18
20
|
/**
|
|
19
21
|
* Polite status announcements: loading, a load error, no rows, or the row count of an active filter;
|
|
20
|
-
*
|
|
22
|
+
* the size of the selected cell range; and the AI assistant's status.
|
|
21
23
|
*/
|
|
22
|
-
export function GridLiveRegion({ loading, error, noRowsLabel, filteredRowCount, dataRowCount, filterModel, cellSelectionAnnouncement }: GridLiveRegionProps) {
|
|
24
|
+
export function GridLiveRegion({ loading, error, noRowsLabel, filteredRowCount, dataRowCount, filterModel, cellSelectionAnnouncement, aiAssistantAnnouncement }: GridLiveRegionProps) {
|
|
23
25
|
return (
|
|
24
26
|
<div className="ogx-aria-live-status" role="status" aria-live="polite">
|
|
25
27
|
{loading ? 'Loading data...' : ''}
|
|
@@ -32,6 +34,7 @@ export function GridLiveRegion({ loading, error, noRowsLabel, filteredRowCount,
|
|
|
32
34
|
: ''
|
|
33
35
|
)}
|
|
34
36
|
{cellSelectionAnnouncement ? <span className="ogx-aria-live-status__cells"> {cellSelectionAnnouncement}</span> : null}
|
|
37
|
+
{aiAssistantAnnouncement ? <span className="ogx-aria-live-status__ai"> {aiAssistantAnnouncement}</span> : null}
|
|
35
38
|
</div>
|
|
36
39
|
);
|
|
37
40
|
}
|
|
@@ -17,6 +17,7 @@ import type {
|
|
|
17
17
|
import type { CellColSpanInfo, RowSpanningCaches } from '../../hooks/features/useGridSpanning';
|
|
18
18
|
import type { GridDisplayCellRange } from '../../hooks/core/useGridCellSelection';
|
|
19
19
|
import { getRowCellRange } from '../../utils/cellSelection';
|
|
20
|
+
import type { GridFillHandlePosition } from '../../utils/cellSelection';
|
|
20
21
|
import type { GridEditingState } from '../../hooks/features/useGridEditing';
|
|
21
22
|
|
|
22
23
|
interface PinnedRowEditingHandlers {
|
|
@@ -64,6 +65,8 @@ export interface GridPinnedRowsProps<R extends GridRowModel> {
|
|
|
64
65
|
cellRange?: GridDisplayCellRange | null;
|
|
65
66
|
/** The grid has row spans: rows tell their cells how far the range bottom is. */
|
|
66
67
|
cellRangeHasRowSpan?: boolean;
|
|
68
|
+
/** Where the fill handle is drawn (v3.5), or null. */
|
|
69
|
+
fillHandle?: GridFillHandlePosition | null;
|
|
67
70
|
/** Pinned rows are edited like any other row. */
|
|
68
71
|
editingHandlers?: PinnedRowEditingHandlers;
|
|
69
72
|
isCellEditable?: (params: GridCellParams<R>) => boolean;
|
|
@@ -111,6 +114,7 @@ export function GridPinnedRows<R extends GridRowModel>({
|
|
|
111
114
|
rowReordering = false,
|
|
112
115
|
cellRange,
|
|
113
116
|
cellRangeHasRowSpan = false,
|
|
117
|
+
fillHandle,
|
|
114
118
|
}: GridPinnedRowsProps<R>) {
|
|
115
119
|
if (rows.length === 0) return null;
|
|
116
120
|
|
|
@@ -156,7 +160,7 @@ export function GridPinnedRows<R extends GridRowModel>({
|
|
|
156
160
|
onEditCellValueChange={editingHandlers?.setEditCellValue}
|
|
157
161
|
isCellEditable={isCellEditable}
|
|
158
162
|
rowReordering={rowReordering}
|
|
159
|
-
cellRange={getRowCellRange(cellRange, rowIndex, cellRangeHasRowSpan)}
|
|
163
|
+
cellRange={getRowCellRange(cellRange, rowIndex, cellRangeHasRowSpan, fillHandle)}
|
|
160
164
|
/>
|
|
161
165
|
);
|
|
162
166
|
})}
|
|
@@ -20,6 +20,7 @@ import type { GridEditingState } from '../../hooks/features/useGridEditing';
|
|
|
20
20
|
import type { CellColSpanInfo, RowSpanningCaches } from '../../hooks/features/useGridSpanning';
|
|
21
21
|
import type { GridDisplayCellRange } from '../../hooks/core/useGridCellSelection';
|
|
22
22
|
import { getRowCellRange } from '../../utils/cellSelection';
|
|
23
|
+
import type { GridFillHandlePosition } from '../../utils/cellSelection';
|
|
23
24
|
|
|
24
25
|
interface RowEditingHandlers {
|
|
25
26
|
editingCell: GridEditingState['editingCell'];
|
|
@@ -77,6 +78,8 @@ export interface GridVirtualRowsProps<R extends GridRowModel> {
|
|
|
77
78
|
cellRange?: GridDisplayCellRange | null;
|
|
78
79
|
/** The grid has row spans: rows tell their cells how far the range bottom is. */
|
|
79
80
|
cellRangeHasRowSpan?: boolean;
|
|
81
|
+
/** Where the fill handle is drawn (v3.5), or null. */
|
|
82
|
+
fillHandle?: GridFillHandlePosition | null;
|
|
80
83
|
}
|
|
81
84
|
|
|
82
85
|
export function GridVirtualRows<R extends GridRowModel>({
|
|
@@ -123,6 +126,7 @@ export function GridVirtualRows<R extends GridRowModel>({
|
|
|
123
126
|
columnIndexMap,
|
|
124
127
|
cellRange,
|
|
125
128
|
cellRangeHasRowSpan = false,
|
|
129
|
+
fillHandle,
|
|
126
130
|
}: GridVirtualRowsProps<R>) {
|
|
127
131
|
const skeletonColumns: GridColDef<R>[] = baseColumns.length > 0
|
|
128
132
|
? baseColumns
|
|
@@ -214,7 +218,7 @@ export function GridVirtualRows<R extends GridRowModel>({
|
|
|
214
218
|
rowSpanningCaches={rowSpanningCaches}
|
|
215
219
|
rowHeight={rowHeight}
|
|
216
220
|
rowMeta={rowMetaMap.get(id)}
|
|
217
|
-
cellRange={getRowCellRange(cellRange, actualIndex, cellRangeHasRowSpan)}
|
|
221
|
+
cellRange={getRowCellRange(cellRange, actualIndex, cellRangeHasRowSpan, fillHandle)}
|
|
218
222
|
/>
|
|
219
223
|
))
|
|
220
224
|
)}
|
|
@@ -6,8 +6,7 @@ import type {
|
|
|
6
6
|
GridColDef,
|
|
7
7
|
GridFilterOperator
|
|
8
8
|
} from '../../types';
|
|
9
|
-
import { getOperatorsForType, NO_VALUE_OPERATORS } from '../../utils/filtering';
|
|
10
|
-
import { toLocalDateString } from '../../utils/values';
|
|
9
|
+
import { filterValueToInputText, getOperatorsForType, NO_VALUE_OPERATORS } from '../../utils/filtering';
|
|
11
10
|
import { Input } from '../ui/Input';
|
|
12
11
|
|
|
13
12
|
|
|
@@ -74,13 +73,7 @@ function matchesOption(option: ValueOption, value: unknown): boolean {
|
|
|
74
73
|
|
|
75
74
|
/** Text shown in the value input for a committed value. Never used to emit anything. */
|
|
76
75
|
function toInputText(value: unknown, col: GridColDef): string {
|
|
77
|
-
|
|
78
|
-
if (col.type === 'date' || value instanceof Date) {
|
|
79
|
-
const day = toLocalDateString(value);
|
|
80
|
-
return day || (typeof value === 'string' ? value : '');
|
|
81
|
-
}
|
|
82
|
-
if (Array.isArray(value)) return value.map(v => String(v)).join(', ');
|
|
83
|
-
return String(value);
|
|
76
|
+
return filterValueToInputText(value, col.type);
|
|
84
77
|
}
|
|
85
78
|
|
|
86
79
|
/** The item value for typed text. isAnyOf takes a comma-separated list. */
|