@alaarab/ogrid-react 2.1.3 → 2.1.5
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/dist/esm/index.js +7233 -26
- package/package.json +7 -4
- package/dist/esm/components/BaseColumnHeaderMenu.js +0 -78
- package/dist/esm/components/BaseDropIndicator.js +0 -4
- package/dist/esm/components/BaseEmptyState.js +0 -4
- package/dist/esm/components/BaseInlineCellEditor.js +0 -167
- package/dist/esm/components/BaseLoadingOverlay.js +0 -4
- package/dist/esm/components/CellErrorBoundary.js +0 -43
- package/dist/esm/components/ColumnChooserProps.js +0 -6
- package/dist/esm/components/ColumnHeaderFilterContent.js +0 -33
- package/dist/esm/components/ColumnHeaderFilterRenderers.js +0 -67
- package/dist/esm/components/EmptyState.js +0 -19
- package/dist/esm/components/GridContextMenu.js +0 -35
- package/dist/esm/components/MarchingAntsOverlay.js +0 -90
- package/dist/esm/components/OGridLayout.js +0 -136
- package/dist/esm/components/PaginationControlsProps.js +0 -6
- package/dist/esm/components/SideBar.js +0 -123
- package/dist/esm/components/StatusBar.js +0 -6
- package/dist/esm/components/createOGrid.js +0 -19
- package/dist/esm/constants/domHelpers.js +0 -16
- package/dist/esm/hooks/index.js +0 -43
- package/dist/esm/hooks/useActiveCell.js +0 -75
- package/dist/esm/hooks/useCellEditing.js +0 -15
- package/dist/esm/hooks/useCellSelection.js +0 -389
- package/dist/esm/hooks/useClipboard.js +0 -106
- package/dist/esm/hooks/useColumnChooserState.js +0 -74
- package/dist/esm/hooks/useColumnHeaderFilterState.js +0 -191
- package/dist/esm/hooks/useColumnHeaderMenuState.js +0 -106
- package/dist/esm/hooks/useColumnMeta.js +0 -61
- package/dist/esm/hooks/useColumnPinning.js +0 -67
- package/dist/esm/hooks/useColumnReorder.js +0 -143
- package/dist/esm/hooks/useColumnResize.js +0 -127
- package/dist/esm/hooks/useContextMenu.js +0 -21
- package/dist/esm/hooks/useDataGridContextMenu.js +0 -24
- package/dist/esm/hooks/useDataGridEditing.js +0 -56
- package/dist/esm/hooks/useDataGridInteraction.js +0 -109
- package/dist/esm/hooks/useDataGridLayout.js +0 -172
- package/dist/esm/hooks/useDataGridState.js +0 -169
- package/dist/esm/hooks/useDataGridTableOrchestration.js +0 -199
- package/dist/esm/hooks/useDateFilterState.js +0 -34
- package/dist/esm/hooks/useDebounce.js +0 -35
- package/dist/esm/hooks/useFillHandle.js +0 -200
- package/dist/esm/hooks/useFilterOptions.js +0 -55
- package/dist/esm/hooks/useInlineCellEditorState.js +0 -38
- package/dist/esm/hooks/useKeyboardNavigation.js +0 -261
- package/dist/esm/hooks/useLatestRef.js +0 -11
- package/dist/esm/hooks/useListVirtualizer.js +0 -29
- package/dist/esm/hooks/useMultiSelectFilterState.js +0 -59
- package/dist/esm/hooks/useOGrid.js +0 -371
- package/dist/esm/hooks/useOGridDataFetching.js +0 -74
- package/dist/esm/hooks/useOGridFilters.js +0 -59
- package/dist/esm/hooks/useOGridPagination.js +0 -24
- package/dist/esm/hooks/useOGridSorting.js +0 -24
- package/dist/esm/hooks/usePaginationControls.js +0 -16
- package/dist/esm/hooks/usePeopleFilterState.js +0 -73
- package/dist/esm/hooks/useRichSelectState.js +0 -60
- package/dist/esm/hooks/useRowSelection.js +0 -69
- package/dist/esm/hooks/useSelectState.js +0 -62
- package/dist/esm/hooks/useShallowEqualMemo.js +0 -14
- package/dist/esm/hooks/useSideBarState.js +0 -39
- package/dist/esm/hooks/useTableLayout.js +0 -69
- package/dist/esm/hooks/useTextFilterState.js +0 -25
- package/dist/esm/hooks/useUndoRedo.js +0 -84
- package/dist/esm/hooks/useVirtualScroll.js +0 -69
- package/dist/esm/storybook/index.js +0 -1
- package/dist/esm/storybook/mockData.js +0 -73
- package/dist/esm/types/columnTypes.js +0 -1
- package/dist/esm/types/dataGridTypes.js +0 -1
- package/dist/esm/types/index.js +0 -1
- package/dist/esm/utils/dataGridViewModel.js +0 -54
- package/dist/esm/utils/gridRowComparator.js +0 -2
- package/dist/esm/utils/index.js +0 -5
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@alaarab/ogrid-react",
|
|
3
|
-
"version": "2.1.
|
|
3
|
+
"version": "2.1.5",
|
|
4
4
|
"description": "OGrid React – React hooks, headless components, and utilities for OGrid data grids.",
|
|
5
5
|
"main": "dist/esm/index.js",
|
|
6
6
|
"module": "dist/esm/index.js",
|
|
@@ -9,11 +9,14 @@
|
|
|
9
9
|
".": {
|
|
10
10
|
"types": "./dist/types/index.d.ts",
|
|
11
11
|
"import": "./dist/esm/index.js",
|
|
12
|
-
"
|
|
12
|
+
"default": "./dist/esm/index.js"
|
|
13
|
+
},
|
|
14
|
+
"./testing": {
|
|
15
|
+
"types": "./dist/types/testing/index.d.ts"
|
|
13
16
|
}
|
|
14
17
|
},
|
|
15
18
|
"scripts": {
|
|
16
|
-
"build": "rimraf dist && tsc -p tsconfig.build.json",
|
|
19
|
+
"build": "rimraf dist && tsup && tsc -p tsconfig.build.json",
|
|
17
20
|
"test": "jest"
|
|
18
21
|
},
|
|
19
22
|
"keywords": [
|
|
@@ -36,7 +39,7 @@
|
|
|
36
39
|
"node": ">=18"
|
|
37
40
|
},
|
|
38
41
|
"dependencies": {
|
|
39
|
-
"@alaarab/ogrid-core": "2.1.
|
|
42
|
+
"@alaarab/ogrid-core": "2.1.5",
|
|
40
43
|
"@tanstack/react-virtual": "^3.0.0"
|
|
41
44
|
},
|
|
42
45
|
"peerDependencies": {
|
|
@@ -1,78 +0,0 @@
|
|
|
1
|
-
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import * as React from 'react';
|
|
3
|
-
import { createPortal } from 'react-dom';
|
|
4
|
-
import { getColumnHeaderMenuItems } from '../utils';
|
|
5
|
-
/**
|
|
6
|
-
* Base column header dropdown menu for pin/sort/autosize actions.
|
|
7
|
-
* Uses positioned div with portal rendering.
|
|
8
|
-
* Shared by Radix and Fluent UI packages (Material uses MUI Menu instead).
|
|
9
|
-
*/
|
|
10
|
-
export function BaseColumnHeaderMenu(props) {
|
|
11
|
-
const { isOpen, anchorElement, onClose, onPinLeft, onPinRight, onUnpin, onSortAsc, onSortDesc, onClearSort, onAutosizeThis, onAutosizeAll, canPinLeft, canPinRight, canUnpin, currentSort, isSortable, isResizable, classNames, getPortalTarget, } = props;
|
|
12
|
-
const [position, setPosition] = React.useState(null);
|
|
13
|
-
const menuRef = React.useRef(null);
|
|
14
|
-
React.useEffect(() => {
|
|
15
|
-
if (!isOpen || !anchorElement) {
|
|
16
|
-
setPosition(null);
|
|
17
|
-
return;
|
|
18
|
-
}
|
|
19
|
-
const rect = anchorElement.getBoundingClientRect();
|
|
20
|
-
setPosition({
|
|
21
|
-
top: rect.bottom + 4,
|
|
22
|
-
left: rect.left,
|
|
23
|
-
});
|
|
24
|
-
const handleClickOutside = (e) => {
|
|
25
|
-
const target = e.target;
|
|
26
|
-
// Don't close if clicking inside the menu itself (portal) — let onClick fire first
|
|
27
|
-
if (menuRef.current && menuRef.current.contains(target))
|
|
28
|
-
return;
|
|
29
|
-
if (anchorElement && !anchorElement.contains(target)) {
|
|
30
|
-
onClose();
|
|
31
|
-
}
|
|
32
|
-
};
|
|
33
|
-
const handleEscape = (e) => {
|
|
34
|
-
if (e.key === 'Escape') {
|
|
35
|
-
onClose();
|
|
36
|
-
}
|
|
37
|
-
};
|
|
38
|
-
document.addEventListener('mousedown', handleClickOutside);
|
|
39
|
-
document.addEventListener('keydown', handleEscape);
|
|
40
|
-
return () => {
|
|
41
|
-
document.removeEventListener('mousedown', handleClickOutside);
|
|
42
|
-
document.removeEventListener('keydown', handleEscape);
|
|
43
|
-
};
|
|
44
|
-
}, [isOpen, anchorElement, onClose]);
|
|
45
|
-
const menuInput = React.useMemo(() => ({
|
|
46
|
-
canPinLeft,
|
|
47
|
-
canPinRight,
|
|
48
|
-
canUnpin,
|
|
49
|
-
currentSort,
|
|
50
|
-
isSortable,
|
|
51
|
-
isResizable,
|
|
52
|
-
}), [canPinLeft, canPinRight, canUnpin, currentSort, isSortable, isResizable]);
|
|
53
|
-
const items = React.useMemo(() => getColumnHeaderMenuItems(menuInput), [menuInput]);
|
|
54
|
-
const handlers = React.useMemo(() => ({
|
|
55
|
-
pinLeft: onPinLeft,
|
|
56
|
-
pinRight: onPinRight,
|
|
57
|
-
unpin: onUnpin,
|
|
58
|
-
sortAsc: onSortAsc,
|
|
59
|
-
sortDesc: onSortDesc,
|
|
60
|
-
clearSort: onClearSort,
|
|
61
|
-
autosizeThis: onAutosizeThis,
|
|
62
|
-
autosizeAll: onAutosizeAll,
|
|
63
|
-
}), [onPinLeft, onPinRight, onUnpin, onSortAsc, onSortDesc, onClearSort, onAutosizeThis, onAutosizeAll]);
|
|
64
|
-
if (!isOpen || !position)
|
|
65
|
-
return null;
|
|
66
|
-
const portalTarget = anchorElement && getPortalTarget
|
|
67
|
-
? getPortalTarget(anchorElement)
|
|
68
|
-
: document.body;
|
|
69
|
-
return createPortal(_jsx("div", { ref: menuRef, className: classNames?.content, style: {
|
|
70
|
-
position: 'fixed',
|
|
71
|
-
top: position.top,
|
|
72
|
-
left: position.left,
|
|
73
|
-
zIndex: 1000,
|
|
74
|
-
}, children: items.map((item, idx) => (_jsxs(React.Fragment, { children: [_jsx("button", { className: classNames?.item, disabled: item.disabled, onClick: () => {
|
|
75
|
-
handlers[item.id]();
|
|
76
|
-
onClose();
|
|
77
|
-
}, children: item.label }), item.divider && idx < items.length - 1 && (_jsx("div", { className: classNames?.separator }))] }, item.id))) }), portalTarget);
|
|
78
|
-
}
|
|
@@ -1,4 +0,0 @@
|
|
|
1
|
-
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
export function BaseEmptyState({ emptyState, classNames, icon }) {
|
|
3
|
-
return (_jsx("div", { className: classNames.emptyStateInGrid, children: _jsx("div", { className: classNames.emptyStateInGridInner, children: emptyState.render ? (emptyState.render()) : (_jsxs(_Fragment, { children: [icon != null && (_jsx("span", { className: classNames.emptyStateInGridIcon, "aria-hidden": true, children: icon })), _jsx("div", { className: classNames.emptyStateInGridTitle, children: "No results found" }), _jsx("div", { className: classNames.emptyStateInGridMessage, children: emptyState.message != null ? (emptyState.message) : emptyState.hasActiveFilters ? (_jsxs(_Fragment, { children: ["No items match your current filters. Try adjusting your search or", ' ', _jsx("button", { type: "button", className: classNames.emptyStateInGridLink, onClick: emptyState.onClearAll, children: "clear all filters" }), ' ', "to see all items."] })) : ('There are no items available at this time.') })] })) }) }));
|
|
4
|
-
}
|
|
@@ -1,167 +0,0 @@
|
|
|
1
|
-
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
-
import * as React from 'react';
|
|
3
|
-
import { useInlineCellEditorState, useRichSelectState, useSelectState } from '../hooks';
|
|
4
|
-
// ── Shared editor style constants (used across all 3 UI packages) ──
|
|
5
|
-
export const editorWrapperStyle = {
|
|
6
|
-
width: '100%',
|
|
7
|
-
height: '100%',
|
|
8
|
-
display: 'flex',
|
|
9
|
-
alignItems: 'center',
|
|
10
|
-
padding: '6px 10px',
|
|
11
|
-
boxSizing: 'border-box',
|
|
12
|
-
overflow: 'hidden',
|
|
13
|
-
minWidth: 0,
|
|
14
|
-
};
|
|
15
|
-
export const editorInputStyle = {
|
|
16
|
-
width: '100%',
|
|
17
|
-
padding: 0,
|
|
18
|
-
border: 'none',
|
|
19
|
-
background: 'transparent',
|
|
20
|
-
color: 'inherit',
|
|
21
|
-
font: 'inherit',
|
|
22
|
-
fontSize: '13px',
|
|
23
|
-
outline: 'none',
|
|
24
|
-
minWidth: 0,
|
|
25
|
-
};
|
|
26
|
-
export const richSelectWrapperStyle = {
|
|
27
|
-
...editorWrapperStyle,
|
|
28
|
-
position: 'relative',
|
|
29
|
-
};
|
|
30
|
-
export const richSelectDropdownStyle = {
|
|
31
|
-
position: 'absolute',
|
|
32
|
-
top: '100%',
|
|
33
|
-
left: 0,
|
|
34
|
-
right: 0,
|
|
35
|
-
maxHeight: 200,
|
|
36
|
-
overflowY: 'auto',
|
|
37
|
-
background: 'var(--ogrid-bg, #fff)',
|
|
38
|
-
border: '1px solid var(--ogrid-border, rgba(0, 0, 0, 0.12))',
|
|
39
|
-
zIndex: 10,
|
|
40
|
-
boxShadow: 'var(--ogrid-shadow, 0 4px 16px rgba(0,0,0,0.2))',
|
|
41
|
-
};
|
|
42
|
-
export const richSelectOptionStyle = {
|
|
43
|
-
padding: '6px 8px',
|
|
44
|
-
cursor: 'pointer',
|
|
45
|
-
color: 'var(--ogrid-fg, #242424)',
|
|
46
|
-
};
|
|
47
|
-
export const richSelectOptionHighlightedStyle = {
|
|
48
|
-
...richSelectOptionStyle,
|
|
49
|
-
background: 'var(--ogrid-bg-hover, #e8f0fe)',
|
|
50
|
-
};
|
|
51
|
-
export const richSelectNoMatchesStyle = {
|
|
52
|
-
padding: '6px 8px',
|
|
53
|
-
color: 'var(--ogrid-muted, #999)',
|
|
54
|
-
};
|
|
55
|
-
export const selectEditorStyle = {
|
|
56
|
-
width: '100%',
|
|
57
|
-
padding: 0,
|
|
58
|
-
border: 'none',
|
|
59
|
-
background: 'transparent',
|
|
60
|
-
color: 'inherit',
|
|
61
|
-
font: 'inherit',
|
|
62
|
-
fontSize: '13px',
|
|
63
|
-
cursor: 'pointer',
|
|
64
|
-
outline: 'none',
|
|
65
|
-
};
|
|
66
|
-
export const selectDisplayStyle = {
|
|
67
|
-
display: 'flex',
|
|
68
|
-
alignItems: 'center',
|
|
69
|
-
justifyContent: 'space-between',
|
|
70
|
-
width: '100%',
|
|
71
|
-
cursor: 'pointer',
|
|
72
|
-
fontSize: '13px',
|
|
73
|
-
color: 'inherit',
|
|
74
|
-
};
|
|
75
|
-
export const selectChevronStyle = {
|
|
76
|
-
marginLeft: 4,
|
|
77
|
-
fontSize: '10px',
|
|
78
|
-
opacity: 0.5,
|
|
79
|
-
};
|
|
80
|
-
/**
|
|
81
|
-
* Base inline cell editor with shared logic for all editor types except checkbox
|
|
82
|
-
* (which is framework-specific). Used by all 3 UI packages to avoid duplication.
|
|
83
|
-
*
|
|
84
|
-
* Text, date, select, and richSelect editors are fully shared.
|
|
85
|
-
* Checkbox is delegated via renderCheckbox render prop.
|
|
86
|
-
*/
|
|
87
|
-
export function BaseInlineCellEditor(props) {
|
|
88
|
-
const { value, column, editorType, onCommit, onCancel, renderCheckbox } = props;
|
|
89
|
-
const wrapperRef = React.useRef(null);
|
|
90
|
-
const { localValue, setLocalValue, handleKeyDown, handleBlur, commit, cancel } = useInlineCellEditorState({ value, editorType, onCommit, onCancel });
|
|
91
|
-
const editorValues = column.cellEditorParams?.values ?? [];
|
|
92
|
-
const editorFormatValue = column.cellEditorParams?.formatValue;
|
|
93
|
-
const richSelect = useRichSelectState({
|
|
94
|
-
values: editorValues,
|
|
95
|
-
formatValue: editorFormatValue,
|
|
96
|
-
initialValue: value,
|
|
97
|
-
onCommit,
|
|
98
|
-
onCancel,
|
|
99
|
-
});
|
|
100
|
-
const selectState = useSelectState({
|
|
101
|
-
values: editorValues,
|
|
102
|
-
formatValue: editorFormatValue,
|
|
103
|
-
initialValue: value,
|
|
104
|
-
onCommit,
|
|
105
|
-
onCancel,
|
|
106
|
-
});
|
|
107
|
-
// Fixed dropdown positioning to escape ancestor overflow clipping (.tableWrapper)
|
|
108
|
-
const [fixedDropdownStyle, setFixedDropdownStyle] = React.useState(null);
|
|
109
|
-
React.useLayoutEffect(() => {
|
|
110
|
-
if (editorType !== 'select' && editorType !== 'richSelect')
|
|
111
|
-
return;
|
|
112
|
-
const wrapper = wrapperRef.current;
|
|
113
|
-
if (!wrapper)
|
|
114
|
-
return;
|
|
115
|
-
const rect = wrapper.getBoundingClientRect();
|
|
116
|
-
const maxH = 200;
|
|
117
|
-
const spaceBelow = window.innerHeight - rect.bottom;
|
|
118
|
-
const flipUp = spaceBelow < maxH && rect.top > spaceBelow;
|
|
119
|
-
setFixedDropdownStyle({
|
|
120
|
-
position: 'fixed',
|
|
121
|
-
...(flipUp ? { bottom: window.innerHeight - rect.top } : { top: rect.bottom }),
|
|
122
|
-
left: rect.left,
|
|
123
|
-
width: rect.width,
|
|
124
|
-
maxHeight: maxH,
|
|
125
|
-
overflowY: 'auto',
|
|
126
|
-
background: 'var(--ogrid-bg, #fff)',
|
|
127
|
-
border: '1px solid var(--ogrid-border, rgba(0, 0, 0, 0.12))',
|
|
128
|
-
zIndex: 9999,
|
|
129
|
-
boxShadow: 'var(--ogrid-shadow, 0 4px 16px rgba(0,0,0,0.2))',
|
|
130
|
-
});
|
|
131
|
-
}, [editorType]);
|
|
132
|
-
const computedDropdownStyle = fixedDropdownStyle ?? richSelectDropdownStyle;
|
|
133
|
-
React.useEffect(() => {
|
|
134
|
-
const wrapper = wrapperRef.current;
|
|
135
|
-
if (!wrapper)
|
|
136
|
-
return;
|
|
137
|
-
const input = wrapper.querySelector('input');
|
|
138
|
-
if (input) {
|
|
139
|
-
input.focus();
|
|
140
|
-
// Select all text for easy replacement (like Excel)
|
|
141
|
-
input.select();
|
|
142
|
-
}
|
|
143
|
-
else {
|
|
144
|
-
// Focus the wrapper for keyboard events (select editor has no input)
|
|
145
|
-
wrapper.focus();
|
|
146
|
-
}
|
|
147
|
-
}, []);
|
|
148
|
-
// Rich select (shared across all frameworks)
|
|
149
|
-
if (editorType === 'richSelect') {
|
|
150
|
-
return (_jsxs("div", { ref: wrapperRef, style: richSelectWrapperStyle, children: [_jsx("input", { type: "text", value: richSelect.searchText, onChange: (e) => richSelect.setSearchText(e.target.value), onKeyDown: richSelect.handleKeyDown, placeholder: "Search...", autoFocus: true, style: editorInputStyle }), _jsxs("div", { style: computedDropdownStyle, role: "listbox", children: [richSelect.filteredValues.map((v, i) => (_jsx("div", { role: "option", "aria-selected": i === richSelect.highlightedIndex, onClick: () => richSelect.selectValue(v), style: i === richSelect.highlightedIndex ? richSelectOptionHighlightedStyle : richSelectOptionStyle, children: richSelect.getDisplayText(v) }, String(v)))), richSelect.filteredValues.length === 0 && (_jsx("div", { style: richSelectNoMatchesStyle, children: "No matches" }))] })] }));
|
|
151
|
-
}
|
|
152
|
-
// Checkbox (framework-specific)
|
|
153
|
-
if (editorType === 'checkbox') {
|
|
154
|
-
const checked = value === true;
|
|
155
|
-
return _jsx(_Fragment, { children: renderCheckbox(checked, (val) => commit(val), cancel) });
|
|
156
|
-
}
|
|
157
|
-
// Select (custom dropdown, shared across all frameworks)
|
|
158
|
-
if (editorType === 'select') {
|
|
159
|
-
return (_jsxs("div", { ref: wrapperRef, style: richSelectWrapperStyle, onKeyDown: selectState.handleKeyDown, tabIndex: 0, children: [_jsxs("div", { style: selectDisplayStyle, children: [_jsx("span", { children: selectState.getDisplayText(value) }), _jsx("span", { style: selectChevronStyle, children: "\u25BE" })] }), _jsx("div", { style: computedDropdownStyle, ref: selectState.dropdownRef, role: "listbox", children: editorValues.map((v, i) => (_jsx("div", { role: "option", "aria-selected": i === selectState.highlightedIndex, onClick: () => selectState.selectValue(v), style: i === selectState.highlightedIndex ? richSelectOptionHighlightedStyle : richSelectOptionStyle, children: selectState.getDisplayText(v) }, String(v)))) })] }));
|
|
160
|
-
}
|
|
161
|
-
// Date editor (shared across all frameworks)
|
|
162
|
-
if (editorType === 'date') {
|
|
163
|
-
return (_jsx("div", { ref: wrapperRef, style: editorWrapperStyle, children: _jsx("input", { type: "date", value: localValue, onChange: (e) => setLocalValue(e.target.value), onBlur: handleBlur, onKeyDown: handleKeyDown, style: editorInputStyle, autoFocus: true }) }));
|
|
164
|
-
}
|
|
165
|
-
// Text editor (default, shared across all frameworks)
|
|
166
|
-
return (_jsx("div", { ref: wrapperRef, style: editorWrapperStyle, children: _jsx("input", { type: "text", value: localValue, onChange: (e) => setLocalValue(e.target.value), onBlur: handleBlur, onKeyDown: handleKeyDown, style: editorInputStyle, autoFocus: true }) }));
|
|
167
|
-
}
|
|
@@ -1,4 +0,0 @@
|
|
|
1
|
-
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
export function BaseLoadingOverlay({ message, classNames }) {
|
|
3
|
-
return (_jsx("div", { className: classNames.loadingOverlay, "aria-live": "polite", children: _jsxs("div", { className: classNames.loadingOverlayContent, children: [_jsx("div", { className: classNames.spinner }), _jsx("span", { className: classNames.loadingOverlayText, children: message })] }) }));
|
|
4
|
-
}
|
|
@@ -1,43 +0,0 @@
|
|
|
1
|
-
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
-
import * as React from 'react';
|
|
3
|
-
const DEFAULT_FALLBACK_STYLE = {
|
|
4
|
-
color: 'var(--ogrid-error, #d32f2f)',
|
|
5
|
-
fontSize: '0.75rem',
|
|
6
|
-
padding: '2px 4px',
|
|
7
|
-
};
|
|
8
|
-
/**
|
|
9
|
-
* Error boundary for cell renderers and custom editors.
|
|
10
|
-
* Prevents a runtime error in a cell from crashing the entire grid.
|
|
11
|
-
*/
|
|
12
|
-
export class CellErrorBoundary extends React.Component {
|
|
13
|
-
constructor(props) {
|
|
14
|
-
super(props);
|
|
15
|
-
this.state = { hasError: false };
|
|
16
|
-
}
|
|
17
|
-
static getDerivedStateFromError() {
|
|
18
|
-
return { hasError: true };
|
|
19
|
-
}
|
|
20
|
-
componentDidCatch(error, errorInfo) {
|
|
21
|
-
if (this.props.onError) {
|
|
22
|
-
this.props.onError(error, errorInfo);
|
|
23
|
-
}
|
|
24
|
-
}
|
|
25
|
-
componentDidUpdate(prevProps) {
|
|
26
|
-
// Reset error state when children change (e.g., navigating to a different cell)
|
|
27
|
-
if (prevProps.children !== this.props.children && this.state.hasError) {
|
|
28
|
-
this.setState({ hasError: false });
|
|
29
|
-
}
|
|
30
|
-
}
|
|
31
|
-
resetErrorBoundary() {
|
|
32
|
-
this.setState({ hasError: false });
|
|
33
|
-
}
|
|
34
|
-
render() {
|
|
35
|
-
if (this.state.hasError) {
|
|
36
|
-
if (this.props.fallback !== undefined) {
|
|
37
|
-
return this.props.fallback;
|
|
38
|
-
}
|
|
39
|
-
return _jsx("span", { style: DEFAULT_FALLBACK_STYLE, children: "\u26A0 Error" });
|
|
40
|
-
}
|
|
41
|
-
return this.props.children;
|
|
42
|
-
}
|
|
43
|
-
}
|
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
-
export const DateFilterContent = ({ tempDateFrom, setTempDateFrom, tempDateTo, setTempDateTo, onApply, onClear, classNames, }) => (_jsxs(_Fragment, { children: [_jsxs("div", { style: { padding: '8px 12px', display: 'flex', flexDirection: 'column', gap: 6 }, children: [_jsxs("label", { style: { display: 'flex', alignItems: 'center', gap: 6, fontSize: 12 }, children: ["From:", _jsx("input", { type: "date", value: tempDateFrom, onChange: (e) => setTempDateFrom(e.target.value), style: { flex: 1 } })] }), _jsxs("label", { style: { display: 'flex', alignItems: 'center', gap: 6, fontSize: 12 }, children: ["To:", _jsx("input", { type: "date", value: tempDateTo, onChange: (e) => setTempDateTo(e.target.value), style: { flex: 1 } })] })] }), _jsxs("div", { className: classNames?.popoverActions, children: [_jsx("button", { className: classNames?.clearButton, onClick: onClear, disabled: !tempDateFrom && !tempDateTo, children: "Clear" }), _jsx("button", { className: classNames?.applyButton, onClick: onApply, children: "Apply" })] })] }));
|
|
3
|
-
DateFilterContent.displayName = 'DateFilterContent';
|
|
4
|
-
// ---- Utility to extract useColumnHeaderFilterState params from props ----
|
|
5
|
-
export function getColumnHeaderFilterStateParams(props) {
|
|
6
|
-
return {
|
|
7
|
-
filterType: props.filterType,
|
|
8
|
-
onSort: props.onSort,
|
|
9
|
-
selectedValues: props.selectedValues,
|
|
10
|
-
onFilterChange: props.onFilterChange,
|
|
11
|
-
options: props.options,
|
|
12
|
-
isLoadingOptions: props.isLoadingOptions ?? false,
|
|
13
|
-
textValue: props.textValue ?? '',
|
|
14
|
-
onTextChange: props.onTextChange,
|
|
15
|
-
selectedUser: props.selectedUser,
|
|
16
|
-
onUserChange: props.onUserChange,
|
|
17
|
-
peopleSearch: props.peopleSearch,
|
|
18
|
-
dateValue: props.dateValue,
|
|
19
|
-
onDateChange: props.onDateChange,
|
|
20
|
-
};
|
|
21
|
-
}
|
|
22
|
-
// ---- Helper to build date filter props from state ----
|
|
23
|
-
export function getDateFilterContentProps(state, classNames) {
|
|
24
|
-
return {
|
|
25
|
-
tempDateFrom: state.tempDateFrom,
|
|
26
|
-
setTempDateFrom: state.setTempDateFrom,
|
|
27
|
-
tempDateTo: state.tempDateTo,
|
|
28
|
-
setTempDateTo: state.setTempDateTo,
|
|
29
|
-
onApply: state.handlers.handleDateApply,
|
|
30
|
-
onClear: state.handlers.handleDateClear,
|
|
31
|
-
classNames,
|
|
32
|
-
};
|
|
33
|
-
}
|
|
@@ -1,67 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Shared filter content dispatching for ColumnHeaderFilter across all React UI packages.
|
|
3
|
-
*
|
|
4
|
-
* Each UI package provides framework-specific sub-filter components (TextFilterPopover,
|
|
5
|
-
* MultiSelectFilterPopover, PeopleFilterPopover, date content). This utility dispatches
|
|
6
|
-
* to the correct renderer based on filterType, eliminating the duplicated if/switch chain
|
|
7
|
-
* that was previously in each UI package's ColumnHeaderFilter component.
|
|
8
|
-
*/
|
|
9
|
-
/**
|
|
10
|
-
* Dispatches to the appropriate filter content renderer based on filterType.
|
|
11
|
-
* Eliminates the duplicated if/switch chain in each UI package's ColumnHeaderFilter.
|
|
12
|
-
*
|
|
13
|
-
* @param filterType - The column's filter type
|
|
14
|
-
* @param state - The result from useColumnHeaderFilterState
|
|
15
|
-
* @param options - The filter options array (for multiSelect)
|
|
16
|
-
* @param isLoadingOptions - Whether options are loading
|
|
17
|
-
* @param selectedUser - The currently selected user (for people filter)
|
|
18
|
-
* @param renderers - Framework-specific renderer functions
|
|
19
|
-
* @returns The rendered filter content, or null for unsupported filter types
|
|
20
|
-
*/
|
|
21
|
-
export function renderFilterContent(filterType, state, options, isLoadingOptions, selectedUser, renderers) {
|
|
22
|
-
if (filterType === 'multiSelect') {
|
|
23
|
-
return renderers.renderMultiSelect({
|
|
24
|
-
searchText: state.searchText,
|
|
25
|
-
onSearchChange: state.setSearchText,
|
|
26
|
-
options,
|
|
27
|
-
filteredOptions: state.filteredOptions,
|
|
28
|
-
selected: state.tempSelected,
|
|
29
|
-
onOptionToggle: state.handlers.handleCheckboxChange,
|
|
30
|
-
onSelectAll: state.handlers.handleSelectAll,
|
|
31
|
-
onClearSelection: state.handlers.handleClearSelection,
|
|
32
|
-
onApply: state.handlers.handleApplyMultiSelect,
|
|
33
|
-
isLoading: isLoadingOptions,
|
|
34
|
-
});
|
|
35
|
-
}
|
|
36
|
-
if (filterType === 'text') {
|
|
37
|
-
return renderers.renderText({
|
|
38
|
-
value: state.tempTextValue,
|
|
39
|
-
onValueChange: state.setTempTextValue,
|
|
40
|
-
onApply: state.handlers.handleTextApply,
|
|
41
|
-
onClear: state.handlers.handleTextClear,
|
|
42
|
-
});
|
|
43
|
-
}
|
|
44
|
-
if (filterType === 'people') {
|
|
45
|
-
return renderers.renderPeople({
|
|
46
|
-
selectedUser,
|
|
47
|
-
searchText: state.peopleSearchText,
|
|
48
|
-
onSearchChange: state.setPeopleSearchText,
|
|
49
|
-
suggestions: state.peopleSuggestions,
|
|
50
|
-
isLoading: state.isPeopleLoading,
|
|
51
|
-
onUserSelect: state.handlers.handleUserSelect,
|
|
52
|
-
onClearUser: state.handlers.handleClearUser,
|
|
53
|
-
inputRef: state.peopleInputRef,
|
|
54
|
-
});
|
|
55
|
-
}
|
|
56
|
-
if (filterType === 'date') {
|
|
57
|
-
return renderers.renderDate({
|
|
58
|
-
tempDateFrom: state.tempDateFrom,
|
|
59
|
-
setTempDateFrom: state.setTempDateFrom,
|
|
60
|
-
tempDateTo: state.tempDateTo,
|
|
61
|
-
setTempDateTo: state.setTempDateTo,
|
|
62
|
-
onApply: state.handlers.handleDateApply,
|
|
63
|
-
onClear: state.handlers.handleDateClear,
|
|
64
|
-
});
|
|
65
|
-
}
|
|
66
|
-
return null;
|
|
67
|
-
}
|
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
const clearButtonStyle = { background: 'none', border: 'none', color: 'inherit', textDecoration: 'underline', cursor: 'pointer', padding: 0, font: 'inherit' };
|
|
3
|
-
/**
|
|
4
|
-
* Headless empty state component with default rendering logic.
|
|
5
|
-
* Framework-specific wrappers provide styling.
|
|
6
|
-
*
|
|
7
|
-
* Default behavior:
|
|
8
|
-
* - Shows "No results found" title
|
|
9
|
-
* - If hasActiveFilters=true: shows "clear all filters" link
|
|
10
|
-
* - If message provided: shows custom message
|
|
11
|
-
* - If render provided: uses custom renderer
|
|
12
|
-
*/
|
|
13
|
-
export function EmptyState(props) {
|
|
14
|
-
const { message, hasActiveFilters, onClearAll, render } = props;
|
|
15
|
-
if (render) {
|
|
16
|
-
return _jsx(_Fragment, { children: render() });
|
|
17
|
-
}
|
|
18
|
-
return (_jsx(_Fragment, { children: message != null ? (message) : hasActiveFilters ? (_jsxs(_Fragment, { children: ["No items match your current filters. Try adjusting your search or", ' ', _jsx("button", { type: "button", onClick: onClearAll, style: clearButtonStyle, children: "clear all filters" }), ' ', "to see all items."] })) : ('There are no items available at this time.') }));
|
|
19
|
-
}
|
|
@@ -1,35 +0,0 @@
|
|
|
1
|
-
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import * as React from 'react';
|
|
3
|
-
import { GRID_CONTEXT_MENU_ITEMS, getContextMenuHandlers, formatShortcut } from '../utils';
|
|
4
|
-
const menuPositionStyle = (x, y) => ({ left: x, top: y });
|
|
5
|
-
export function GridContextMenu(props) {
|
|
6
|
-
const { x, y, hasSelection, canUndo, canRedo, onClose, onCopy, onCut, onPaste, onSelectAll, onUndo, onRedo, classNames } = props;
|
|
7
|
-
const ref = React.useRef(null);
|
|
8
|
-
const handlers = React.useMemo(() => getContextMenuHandlers({ onCopy, onCut, onPaste, onSelectAll, onUndo, onRedo, onClose }), [onCopy, onCut, onPaste, onSelectAll, onUndo, onRedo, onClose]);
|
|
9
|
-
const isDisabled = React.useCallback((item) => {
|
|
10
|
-
if (item.disabledWhenNoSelection && !hasSelection)
|
|
11
|
-
return true;
|
|
12
|
-
if (item.id === 'undo' && !canUndo)
|
|
13
|
-
return true;
|
|
14
|
-
if (item.id === 'redo' && !canRedo)
|
|
15
|
-
return true;
|
|
16
|
-
return false;
|
|
17
|
-
}, [hasSelection, canUndo, canRedo]);
|
|
18
|
-
React.useEffect(() => {
|
|
19
|
-
const handleClickOutside = (e) => {
|
|
20
|
-
if (ref.current && !ref.current.contains(e.target))
|
|
21
|
-
onClose();
|
|
22
|
-
};
|
|
23
|
-
const handleKeyDown = (e) => {
|
|
24
|
-
if (e.key === 'Escape')
|
|
25
|
-
onClose();
|
|
26
|
-
};
|
|
27
|
-
document.addEventListener('mousedown', handleClickOutside, true);
|
|
28
|
-
document.addEventListener('keydown', handleKeyDown, true);
|
|
29
|
-
return () => {
|
|
30
|
-
document.removeEventListener('mousedown', handleClickOutside, true);
|
|
31
|
-
document.removeEventListener('keydown', handleKeyDown, true);
|
|
32
|
-
};
|
|
33
|
-
}, [onClose]);
|
|
34
|
-
return (_jsx("div", { ref: ref, className: classNames?.contextMenu, role: "menu", style: menuPositionStyle(x, y), "aria-label": "Grid context menu", children: GRID_CONTEXT_MENU_ITEMS.map((item) => (_jsxs(React.Fragment, { children: [item.dividerBefore && _jsx("div", { className: classNames?.contextMenuDivider }), _jsxs("button", { type: "button", className: classNames?.contextMenuItem, onClick: handlers[item.id], disabled: isDisabled(item), children: [_jsx("span", { className: classNames?.contextMenuItemLabel, children: item.label }), item.shortcut && (_jsx("span", { className: classNames?.contextMenuItemShortcut, children: formatShortcut(item.shortcut) }))] })] }, item.id))) }));
|
|
35
|
-
}
|
|
@@ -1,90 +0,0 @@
|
|
|
1
|
-
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
/**
|
|
3
|
-
* MarchingAntsOverlay — Renders range overlays on top of the grid:
|
|
4
|
-
*
|
|
5
|
-
* 1. **Selection range**: solid green border around the current selection
|
|
6
|
-
* 2. **Copy/Cut range**: animated dashed border (marching ants) like Excel
|
|
7
|
-
*
|
|
8
|
-
* Uses SVG rects positioned via cell data-attribute measurements.
|
|
9
|
-
*/
|
|
10
|
-
import { useEffect, useRef, useState, useCallback } from 'react';
|
|
11
|
-
import { measureRange, injectGlobalStyles } from '@alaarab/ogrid-core';
|
|
12
|
-
const MARCHING_ANTS_ANIMATION = { animation: 'ogrid-marching-ants 0.5s linear infinite' };
|
|
13
|
-
export function MarchingAntsOverlay({ containerRef, selectionRange, copyRange, cutRange, colOffset, items, visibleColumns, columnSizingOverrides, columnOrder, }) {
|
|
14
|
-
const [selRect, setSelRect] = useState(null);
|
|
15
|
-
const [clipRect, setClipRect] = useState(null);
|
|
16
|
-
const rafRef = useRef(0);
|
|
17
|
-
const clipRange = copyRange ?? cutRange;
|
|
18
|
-
const measureAll = useCallback(() => {
|
|
19
|
-
const container = containerRef.current;
|
|
20
|
-
if (!container) {
|
|
21
|
-
setSelRect(null);
|
|
22
|
-
setClipRect(null);
|
|
23
|
-
return;
|
|
24
|
-
}
|
|
25
|
-
setSelRect(selectionRange ? measureRange(container, selectionRange, colOffset) : null);
|
|
26
|
-
setClipRect(clipRange ? measureRange(container, clipRange, colOffset) : null);
|
|
27
|
-
}, [selectionRange, clipRange, containerRef, colOffset]);
|
|
28
|
-
// Inject keyframes on mount
|
|
29
|
-
useEffect(() => {
|
|
30
|
-
injectGlobalStyles('ogrid-marching-ants-keyframes', '@keyframes ogrid-marching-ants{to{stroke-dashoffset:-8}}');
|
|
31
|
-
}, []);
|
|
32
|
-
// Measure when any range changes; re-measure on resize
|
|
33
|
-
useEffect(() => {
|
|
34
|
-
if (!selectionRange && !clipRange) {
|
|
35
|
-
setSelRect(null);
|
|
36
|
-
setClipRect(null);
|
|
37
|
-
return;
|
|
38
|
-
}
|
|
39
|
-
// Delay one frame so cells are rendered
|
|
40
|
-
rafRef.current = requestAnimationFrame(measureAll);
|
|
41
|
-
const container = containerRef.current;
|
|
42
|
-
let ro;
|
|
43
|
-
if (container) {
|
|
44
|
-
ro = new ResizeObserver(measureAll);
|
|
45
|
-
ro.observe(container);
|
|
46
|
-
}
|
|
47
|
-
return () => {
|
|
48
|
-
cancelAnimationFrame(rafRef.current);
|
|
49
|
-
ro?.disconnect();
|
|
50
|
-
};
|
|
51
|
-
}, [selectionRange, clipRange, measureAll, containerRef, items, visibleColumns, columnSizingOverrides, columnOrder]);
|
|
52
|
-
if (!selRect && !clipRect)
|
|
53
|
-
return null;
|
|
54
|
-
// When clipboard range matches the selection range, hide the solid selection border
|
|
55
|
-
// so the marching ants animation is clearly visible (not obscured by solid stroke underneath).
|
|
56
|
-
const clipRangeMatchesSel = selectionRange != null &&
|
|
57
|
-
clipRange != null &&
|
|
58
|
-
selectionRange.startRow === clipRange.startRow &&
|
|
59
|
-
selectionRange.startCol === clipRange.startCol &&
|
|
60
|
-
selectionRange.endRow === clipRange.endRow &&
|
|
61
|
-
selectionRange.endCol === clipRange.endCol;
|
|
62
|
-
// Round to integer pixels so the stroke aligns to the pixel grid and corners connect cleanly
|
|
63
|
-
const roundRect = (r) => ({
|
|
64
|
-
top: Math.round(r.top),
|
|
65
|
-
left: Math.round(r.left),
|
|
66
|
-
width: Math.round(r.width),
|
|
67
|
-
height: Math.round(r.height),
|
|
68
|
-
});
|
|
69
|
-
const selR = selRect ? roundRect(selRect) : null;
|
|
70
|
-
const clipR = clipRect ? roundRect(clipRect) : null;
|
|
71
|
-
return (_jsxs(_Fragment, { children: [selR && !clipRangeMatchesSel && (_jsx("svg", { style: {
|
|
72
|
-
position: 'absolute',
|
|
73
|
-
top: selR.top,
|
|
74
|
-
left: selR.left,
|
|
75
|
-
width: selR.width,
|
|
76
|
-
height: selR.height,
|
|
77
|
-
pointerEvents: 'none',
|
|
78
|
-
zIndex: 4,
|
|
79
|
-
overflow: 'visible',
|
|
80
|
-
}, "aria-hidden": "true", children: _jsx("rect", { x: "1", y: "1", width: Math.max(0, selR.width - 2), height: Math.max(0, selR.height - 2), fill: "none", stroke: "var(--ogrid-selection, #217346)", strokeWidth: "2", style: { shapeRendering: 'crispEdges' } }) })), clipR && (_jsx("svg", { style: {
|
|
81
|
-
position: 'absolute',
|
|
82
|
-
top: clipR.top,
|
|
83
|
-
left: clipR.left,
|
|
84
|
-
width: clipR.width,
|
|
85
|
-
height: clipR.height,
|
|
86
|
-
pointerEvents: 'none',
|
|
87
|
-
zIndex: 5,
|
|
88
|
-
overflow: 'visible',
|
|
89
|
-
}, "aria-hidden": "true", children: _jsx("rect", { x: "1", y: "1", width: Math.max(0, clipR.width - 2), height: Math.max(0, clipR.height - 2), fill: "none", stroke: "var(--ogrid-selection, #217346)", strokeWidth: "2", strokeDasharray: "4 4", style: { ...MARCHING_ANTS_ANIMATION, shapeRendering: 'crispEdges' } }) }))] }));
|
|
90
|
-
}
|