@alaarab/ogrid-js 2.1.3 → 2.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.
Files changed (35) hide show
  1. package/dist/esm/index.js +6343 -32
  2. package/package.json +4 -4
  3. package/dist/esm/OGrid.js +0 -578
  4. package/dist/esm/OGridEventWiring.js +0 -178
  5. package/dist/esm/OGridRendering.js +0 -269
  6. package/dist/esm/components/ColumnChooser.js +0 -91
  7. package/dist/esm/components/ContextMenu.js +0 -125
  8. package/dist/esm/components/HeaderFilter.js +0 -281
  9. package/dist/esm/components/InlineCellEditor.js +0 -434
  10. package/dist/esm/components/MarchingAntsOverlay.js +0 -156
  11. package/dist/esm/components/PaginationControls.js +0 -85
  12. package/dist/esm/components/SideBar.js +0 -353
  13. package/dist/esm/components/StatusBar.js +0 -34
  14. package/dist/esm/renderer/TableRenderer.js +0 -846
  15. package/dist/esm/state/ClipboardState.js +0 -111
  16. package/dist/esm/state/ColumnPinningState.js +0 -82
  17. package/dist/esm/state/ColumnReorderState.js +0 -135
  18. package/dist/esm/state/ColumnResizeState.js +0 -55
  19. package/dist/esm/state/EventEmitter.js +0 -28
  20. package/dist/esm/state/FillHandleState.js +0 -206
  21. package/dist/esm/state/GridState.js +0 -324
  22. package/dist/esm/state/HeaderFilterState.js +0 -213
  23. package/dist/esm/state/KeyboardNavState.js +0 -216
  24. package/dist/esm/state/RowSelectionState.js +0 -72
  25. package/dist/esm/state/SelectionState.js +0 -109
  26. package/dist/esm/state/SideBarState.js +0 -41
  27. package/dist/esm/state/TableLayoutState.js +0 -97
  28. package/dist/esm/state/UndoRedoState.js +0 -71
  29. package/dist/esm/state/VirtualScrollState.js +0 -128
  30. package/dist/esm/types/columnTypes.js +0 -1
  31. package/dist/esm/types/gridTypes.js +0 -1
  32. package/dist/esm/types/index.js +0 -2
  33. package/dist/esm/utils/debounce.js +0 -2
  34. package/dist/esm/utils/getCellCoordinates.js +0 -15
  35. package/dist/esm/utils/index.js +0 -2
@@ -1,178 +0,0 @@
1
- import { SelectionState } from './state/SelectionState';
2
- import { KeyboardNavState } from './state/KeyboardNavState';
3
- import { ClipboardState } from './state/ClipboardState';
4
- import { UndoRedoState } from './state/UndoRedoState';
5
- import { ColumnResizeState } from './state/ColumnResizeState';
6
- import { FillHandleState } from './state/FillHandleState';
7
- import { ColumnReorderState } from './state/ColumnReorderState';
8
- import { MarchingAntsOverlay } from './components/MarchingAntsOverlay';
9
- import { InlineCellEditor } from './components/InlineCellEditor';
10
- import { ContextMenu } from './components/ContextMenu';
11
- import { getCellCoordinates } from './utils/getCellCoordinates';
12
- export class OGridEventWiring {
13
- /**
14
- * Creates all interaction states, subscribes to their events, and returns
15
- * the state objects so OGrid can store them.
16
- */
17
- initializeInteraction(options, state, renderer, tableContainer, layoutState, rowSelectionState, pinningState, callbacks) {
18
- const { editable } = options;
19
- const colOffset = rowSelectionState ? 1 : 0;
20
- const unsubscribes = [];
21
- // Create interaction states
22
- const selectionState = new SelectionState();
23
- const resizeState = new ColumnResizeState();
24
- const contextMenu = new ContextMenu();
25
- const cellEditor = new InlineCellEditor(tableContainer);
26
- // Undo/Redo (wraps onCellValueChanged if editable)
27
- const onCellValueChanged = options.onCellValueChanged;
28
- const undoRedoState = new UndoRedoState(onCellValueChanged);
29
- // Clipboard
30
- const clipboardState = new ClipboardState({
31
- items: [],
32
- visibleCols: [],
33
- colOffset,
34
- editable,
35
- onCellValueChanged: undoRedoState.getWrappedCallback(),
36
- }, () => selectionState.activeCell ?? null, () => selectionState.selectionRange ?? null);
37
- // Fill handle
38
- const fillHandleState = new FillHandleState({
39
- items: [],
40
- visibleCols: [],
41
- editable,
42
- onCellValueChanged: undoRedoState.getWrappedCallback(),
43
- colOffset,
44
- beginBatch: () => undoRedoState.beginBatch(),
45
- endBatch: () => undoRedoState.endBatch(),
46
- }, () => selectionState.selectionRange ?? null, (range) => {
47
- selectionState.setSelectionRange(range);
48
- callbacks.updateRendererInteractionState();
49
- }, (cell) => {
50
- selectionState.setActiveCell(cell);
51
- });
52
- // Keyboard navigation
53
- const keyboardNavState = new KeyboardNavState({
54
- items: [],
55
- visibleCols: [],
56
- colOffset,
57
- getRowId: state.getRowId,
58
- editable,
59
- onCellValueChanged: undoRedoState.getWrappedCallback(),
60
- onCopy: () => clipboardState.handleCopy(),
61
- onCut: () => clipboardState.handleCut(),
62
- onPaste: async () => { await clipboardState.handlePaste(); },
63
- onUndo: () => undoRedoState.undo(),
64
- onRedo: () => undoRedoState.redo(),
65
- onContextMenu: (x, y) => callbacks.showContextMenu(x, y),
66
- onStartEdit: (rowId, columnId) => callbacks.startCellEdit(rowId, columnId),
67
- clearClipboardRanges: () => clipboardState.clearClipboardRanges(),
68
- }, () => selectionState.activeCell ?? null, () => selectionState.selectionRange ?? null, (cell) => selectionState.setActiveCell(cell), (range) => selectionState.setSelectionRange(range));
69
- // Subscribe to selection changes
70
- unsubscribes.push(selectionState.onSelectionChange(() => {
71
- callbacks.updateRendererInteractionState();
72
- }));
73
- // Subscribe to clipboard range changes
74
- unsubscribes.push(clipboardState.onRangesChange(() => {
75
- callbacks.updateRendererInteractionState();
76
- }));
77
- // Subscribe to column resize changes
78
- unsubscribes.push(resizeState.onColumnWidthChange(() => {
79
- callbacks.updateRendererInteractionState();
80
- }));
81
- // Column reorder
82
- const reorderState = new ColumnReorderState();
83
- unsubscribes.push(reorderState.onStateChange(({ isDragging, dropIndicatorX }) => {
84
- renderer.updateDropIndicator(dropIndicatorX, isDragging);
85
- }));
86
- unsubscribes.push(reorderState.onReorder(({ columnOrder }) => {
87
- state.setColumnOrder(columnOrder);
88
- }));
89
- // Attach keyboard handler to wrapper
90
- const wrapper = renderer.getWrapperElement();
91
- let marchingAnts = null;
92
- if (wrapper) {
93
- wrapper.addEventListener('keydown', keyboardNavState.handleKeyDown);
94
- keyboardNavState.setWrapperRef(wrapper);
95
- fillHandleState.setWrapperRef(wrapper);
96
- // Initialize marching ants overlay
97
- marchingAnts = new MarchingAntsOverlay(wrapper, colOffset);
98
- }
99
- // Attach global mouse handlers for resize and drag
100
- const globalUnsubs = this.attachGlobalHandlers(selectionState, resizeState, layoutState, renderer, callbacks);
101
- unsubscribes.push(...globalUnsubs);
102
- return {
103
- selectionState,
104
- keyboardNavState,
105
- clipboardState,
106
- undoRedoState,
107
- resizeState,
108
- fillHandleState,
109
- reorderState,
110
- marchingAnts,
111
- cellEditor,
112
- contextMenu,
113
- unsubscribes,
114
- };
115
- }
116
- attachGlobalHandlers(selectionState, resizeState, layoutState, renderer, callbacks) {
117
- const unsubs = [];
118
- let resizing = false;
119
- const handleMouseMove = (e) => {
120
- if (resizing && resizeState) {
121
- const newWidth = resizeState.updateResize(e.clientX);
122
- if (newWidth !== null && resizeState.resizingColumnId) {
123
- layoutState.setColumnOverride(resizeState.resizingColumnId, newWidth);
124
- callbacks.updateRendererInteractionState();
125
- }
126
- }
127
- if (selectionState?.isDragging) {
128
- const target = e.target;
129
- if (target.tagName === 'TD') {
130
- const coords = getCellCoordinates(target);
131
- if (coords && coords.rowIndex >= 0 && coords.colIndex >= 0) {
132
- selectionState.updateDrag(coords.rowIndex, coords.colIndex, () => callbacks.updateDragAttributes());
133
- }
134
- }
135
- }
136
- };
137
- const handleMouseUp = (e) => {
138
- if (resizing && resizeState) {
139
- const colId = resizeState.resizingColumnId;
140
- resizeState.endResize(e.clientX);
141
- if (colId) {
142
- const width = resizeState.getColumnWidth(colId);
143
- if (width)
144
- layoutState.setColumnOverride(colId, width);
145
- }
146
- resizing = false;
147
- document.body.style.cursor = '';
148
- callbacks.updateRendererInteractionState();
149
- }
150
- if (selectionState?.isDragging) {
151
- selectionState.endDrag();
152
- callbacks.clearCachedDragCells();
153
- }
154
- };
155
- const handleResizeStart = (columnId, clientX, currentWidth) => {
156
- resizing = true;
157
- document.body.style.cursor = 'col-resize';
158
- resizeState.startResize(columnId, clientX, currentWidth);
159
- };
160
- document.addEventListener('mousemove', handleMouseMove, { passive: true });
161
- document.addEventListener('mouseup', handleMouseUp, { passive: true });
162
- unsubs.push(() => {
163
- document.removeEventListener('mousemove', handleMouseMove);
164
- document.removeEventListener('mouseup', handleMouseUp);
165
- });
166
- // Pass resize handler to renderer
167
- renderer.setInteractionState({
168
- activeCell: null,
169
- selectionRange: null,
170
- copyRange: null,
171
- cutRange: null,
172
- editingCell: null,
173
- columnWidths: {},
174
- onResizeStart: handleResizeStart,
175
- });
176
- return unsubs;
177
- }
178
- }
@@ -1,269 +0,0 @@
1
- import { normalizeSelectionRange, isInSelectionRange, CHECKBOX_COLUMN_WIDTH } from '@alaarab/ogrid-core';
2
- import { getCellCoordinates } from './utils/getCellCoordinates';
3
- export class OGridRendering {
4
- constructor(ctx) {
5
- this.layoutVersion = 0;
6
- /** Cached DOM cells during drag to avoid querySelectorAll on every RAF frame. */
7
- this.cachedDragCells = null;
8
- this.ctx = ctx;
9
- }
10
- /** Increment layout version (e.g., when items, columns, sizing change). */
11
- incrementLayoutVersion() {
12
- this.layoutVersion++;
13
- }
14
- /** Clear cached drag cells. */
15
- clearCachedDragCells() {
16
- this.cachedDragCells = null;
17
- }
18
- /** Get current layout version. */
19
- getLayoutVersion() {
20
- return this.layoutVersion;
21
- }
22
- updateRendererInteractionState() {
23
- const { selectionState, clipboardState, resizeState, state, layoutState, pinningState, rowSelectionState, cellEditor, renderer, reorderState, marchingAnts, fillHandleState, options } = this.ctx;
24
- if (!selectionState || !clipboardState || !resizeState)
25
- return;
26
- const { items } = state.getProcessedItems();
27
- const visibleCols = state.visibleColumnDefs;
28
- // Compute pinning offsets
29
- const columnWidths = layoutState.getAllColumnWidths();
30
- const leftOffsets = pinningState?.computeLeftOffsets(visibleCols, columnWidths, 120, !!rowSelectionState, CHECKBOX_COLUMN_WIDTH, !!options.showRowNumbers) ?? {};
31
- const rightOffsets = pinningState?.computeRightOffsets(visibleCols, columnWidths, 120) ?? {};
32
- renderer.setInteractionState({
33
- activeCell: selectionState.activeCell,
34
- selectionRange: selectionState.selectionRange,
35
- copyRange: clipboardState.copyRange,
36
- cutRange: clipboardState.cutRange,
37
- editingCell: cellEditor?.getEditingCell() ?? null,
38
- columnWidths,
39
- onCellClick: (ce) => this.ctx.handleCellClick(ce.rowIndex, ce.colIndex),
40
- onCellMouseDown: (ce) => { if (ce.event)
41
- this.ctx.handleCellMouseDown(ce.rowIndex, ce.colIndex, ce.event); },
42
- onCellDoubleClick: (ce) => { if (ce.rowId != null && ce.columnId)
43
- this.ctx.startCellEdit(ce.rowId, ce.columnId); },
44
- onCellContextMenu: (ce) => { if (ce.event)
45
- this.ctx.handleCellContextMenu(ce.rowIndex, ce.colIndex, ce.event); },
46
- onResizeStart: renderer.getOnResizeStart(),
47
- // Fill handle
48
- onFillHandleMouseDown: options.editable !== false ? (e) => fillHandleState?.startFillDrag(e) : undefined,
49
- // Row selection
50
- rowSelectionMode: rowSelectionState?.rowSelection ?? 'none',
51
- selectedRowIds: rowSelectionState?.selectedRowIds,
52
- onRowCheckboxChange: (rowId, checked, rowIndex, shiftKey) => {
53
- rowSelectionState?.handleRowCheckboxChange(rowId, checked, rowIndex, shiftKey, items);
54
- },
55
- onSelectAll: (checked) => {
56
- rowSelectionState?.handleSelectAll(checked, items);
57
- },
58
- allSelected: rowSelectionState?.isAllSelected(items),
59
- someSelected: rowSelectionState?.isSomeSelected(items),
60
- // Row numbers
61
- showRowNumbers: options.showRowNumbers,
62
- // Column pinning
63
- pinnedColumns: pinningState?.pinnedColumns,
64
- leftOffsets,
65
- rightOffsets,
66
- // Column reorder
67
- onColumnReorderStart: reorderState ? (columnId, event) => {
68
- const tableEl = renderer.getTableElement();
69
- if (!tableEl)
70
- return;
71
- reorderState?.startDrag(columnId, event, visibleCols, state.columnOrder, pinningState?.pinnedColumns, tableEl);
72
- } : undefined,
73
- });
74
- renderer.update();
75
- // Update marching ants overlay
76
- marchingAnts?.update(selectionState.selectionRange, clipboardState.copyRange, clipboardState.cutRange, this.layoutVersion);
77
- }
78
- updateDragAttributes() {
79
- const wrapper = this.ctx.renderer.getWrapperElement();
80
- const selectionState = this.ctx.selectionState;
81
- if (!wrapper || !selectionState)
82
- return;
83
- const range = selectionState.getDragRange();
84
- if (!range)
85
- return;
86
- const norm = normalizeSelectionRange(range);
87
- const anchor = selectionState.dragAnchor;
88
- // Cache the querySelectorAll result on first drag call; reuse for subsequent RAF frames
89
- if (!this.cachedDragCells) {
90
- this.cachedDragCells = wrapper.querySelectorAll('td[data-row-index][data-col-index]');
91
- }
92
- const cells = this.cachedDragCells;
93
- for (let _i = 0; _i < cells.length; _i++) {
94
- const cell = cells[_i];
95
- const el = cell;
96
- const coords = getCellCoordinates(el);
97
- if (!coords)
98
- continue;
99
- const rowIndex = coords.rowIndex;
100
- const colIndex = coords.colIndex;
101
- if (isInSelectionRange(norm, rowIndex, colIndex)) {
102
- el.setAttribute('data-drag-range', 'true');
103
- // Anchor cell (white background)
104
- const isAnchor = anchor && rowIndex === anchor.rowIndex && colIndex === anchor.columnIndex;
105
- if (isAnchor) {
106
- el.setAttribute('data-drag-anchor', '');
107
- }
108
- else {
109
- el.removeAttribute('data-drag-anchor');
110
- }
111
- // Edge borders via CSS class instead of inline box-shadow
112
- el.classList.add('ogrid-drag-target');
113
- }
114
- else {
115
- el.removeAttribute('data-drag-range');
116
- el.removeAttribute('data-drag-anchor');
117
- el.classList.remove('ogrid-drag-target');
118
- }
119
- }
120
- }
121
- renderAll() {
122
- // Increment layout version to trigger marching ants re-measurement
123
- this.layoutVersion++;
124
- const { state, options, headerFilterState, rowSelectionState, keyboardNavState, clipboardState, undoRedoState, fillHandleState, virtualScrollState, pagination, statusBar, columnChooser, renderer } = this.ctx;
125
- const colOffset = rowSelectionState ? 1 : 0;
126
- // Update header filter state with current filters and options
127
- headerFilterState.setFilters(state.filters);
128
- headerFilterState.setFilterOptions(state.filterOptions);
129
- // Update interaction states with current data
130
- const { items, totalCount } = state.getProcessedItems();
131
- if (keyboardNavState && clipboardState) {
132
- const visibleCols = state.visibleColumnDefs;
133
- keyboardNavState.updateParams({
134
- items,
135
- visibleCols: visibleCols,
136
- colOffset,
137
- getRowId: state.getRowId,
138
- editable: options.editable,
139
- onCellValueChanged: undoRedoState?.getWrappedCallback(),
140
- onCopy: () => clipboardState?.handleCopy(),
141
- onCut: () => clipboardState?.handleCut(),
142
- onPaste: async () => { await clipboardState?.handlePaste(); },
143
- onUndo: () => undoRedoState?.undo(),
144
- onRedo: () => undoRedoState?.redo(),
145
- onContextMenu: (x, y) => this.ctx.showContextMenu(x, y),
146
- onStartEdit: (rowId, columnId) => this.ctx.startCellEdit(rowId, columnId),
147
- clearClipboardRanges: () => clipboardState?.clearClipboardRanges(),
148
- });
149
- clipboardState.updateParams({
150
- items,
151
- visibleCols: visibleCols,
152
- colOffset,
153
- editable: options.editable,
154
- onCellValueChanged: undoRedoState?.getWrappedCallback(),
155
- });
156
- // Update fill handle params
157
- fillHandleState?.updateParams({
158
- items,
159
- visibleCols: visibleCols,
160
- editable: options.editable,
161
- onCellValueChanged: undoRedoState?.getWrappedCallback(),
162
- colOffset,
163
- beginBatch: () => undoRedoState?.beginBatch(),
164
- endBatch: () => undoRedoState?.endBatch(),
165
- });
166
- // Update renderer interaction state before rendering
167
- this.updateRendererInteractionState();
168
- }
169
- else {
170
- renderer.update();
171
- }
172
- // Update virtual scroll with current total row count
173
- virtualScrollState?.setTotalRows(totalCount);
174
- pagination.render(totalCount, options.pageSizeOptions);
175
- statusBar.render({ totalCount });
176
- columnChooser.render();
177
- this.renderSideBar();
178
- this.renderLoadingOverlay();
179
- }
180
- renderHeaderFilterPopover() {
181
- const { headerFilterState, headerFilterComponent, filterConfigs } = this.ctx;
182
- const openId = headerFilterState.openColumnId;
183
- if (!openId) {
184
- headerFilterComponent.cleanup();
185
- return;
186
- }
187
- const config = filterConfigs.get(openId);
188
- if (!config)
189
- return;
190
- headerFilterComponent.render(config);
191
- // Update the popover element reference for click-outside detection
192
- const popoverEl = document.querySelector('.ogrid-header-filter-popover');
193
- headerFilterState.setPopoverEl(popoverEl);
194
- }
195
- renderSideBar() {
196
- const { sideBarComponent, sideBarState, state } = this.ctx;
197
- if (!sideBarComponent || !sideBarState)
198
- return;
199
- const columns = state.columns.map(c => ({
200
- columnId: c.columnId,
201
- name: c.name,
202
- required: c.required === true,
203
- }));
204
- const filterableColumns = state.columns
205
- .filter(c => c.filterable && typeof c.filterable === 'object' && c.filterable.type)
206
- .map(c => ({
207
- columnId: c.columnId,
208
- name: c.name,
209
- filterField: c.filterable.filterField ?? c.columnId,
210
- filterType: c.filterable.type,
211
- }));
212
- sideBarComponent.setConfig({
213
- columns,
214
- visibleColumns: state.visibleColumns,
215
- onVisibilityChange: (columnKey, visible) => {
216
- const next = new Set(state.visibleColumns);
217
- if (visible)
218
- next.add(columnKey);
219
- else
220
- next.delete(columnKey);
221
- state.setVisibleColumns(next);
222
- },
223
- onSetVisibleColumns: (cols) => state.setVisibleColumns(cols),
224
- filterableColumns,
225
- filters: state.filters,
226
- onFilterChange: (key, value) => state.setFilter(key, value),
227
- filterOptions: state.filterOptions,
228
- });
229
- sideBarComponent.render();
230
- }
231
- renderLoadingOverlay() {
232
- const { state, tableContainer } = this.ctx;
233
- if (state.isLoading) {
234
- // Ensure the container has minimum height during loading so overlay is visible
235
- const { items } = state.getProcessedItems();
236
- tableContainer.style.minHeight = (!items || items.length === 0) ? '200px' : '';
237
- let loadingOverlay = this.ctx.loadingOverlay;
238
- if (!loadingOverlay) {
239
- loadingOverlay = document.createElement('div');
240
- loadingOverlay.className = 'ogrid-loading-overlay';
241
- loadingOverlay.style.position = 'absolute';
242
- loadingOverlay.style.top = '0';
243
- loadingOverlay.style.left = '0';
244
- loadingOverlay.style.right = '0';
245
- loadingOverlay.style.bottom = '0';
246
- loadingOverlay.style.display = 'flex';
247
- loadingOverlay.style.alignItems = 'center';
248
- loadingOverlay.style.justifyContent = 'center';
249
- loadingOverlay.style.background = 'var(--ogrid-loading-overlay, rgba(255, 255, 255, 0.7))';
250
- loadingOverlay.style.zIndex = '100';
251
- const spinner = document.createElement('div');
252
- spinner.className = 'ogrid-loading-spinner';
253
- spinner.textContent = 'Loading...';
254
- loadingOverlay.appendChild(spinner);
255
- this.ctx.setLoadingOverlay(loadingOverlay);
256
- }
257
- if (!tableContainer.contains(loadingOverlay)) {
258
- tableContainer.appendChild(loadingOverlay);
259
- }
260
- }
261
- else {
262
- tableContainer.style.minHeight = '';
263
- const loadingOverlay = this.ctx.loadingOverlay;
264
- if (loadingOverlay && tableContainer.contains(loadingOverlay)) {
265
- loadingOverlay.remove();
266
- }
267
- }
268
- }
269
- }
@@ -1,91 +0,0 @@
1
- export class ColumnChooser {
2
- constructor(container, state) {
3
- this.el = null;
4
- this.dropdown = null;
5
- this.isOpen = false;
6
- this.initialized = false;
7
- this.container = container;
8
- this.state = state;
9
- }
10
- render() {
11
- if (!this.initialized) {
12
- this.createDOM();
13
- this.initialized = true;
14
- }
15
- // If dropdown is open, update checkbox states without destroying/recreating
16
- if (this.isOpen && this.dropdown) {
17
- this.updateDropdownState();
18
- }
19
- }
20
- /** Initial DOM creation — called once. */
21
- createDOM() {
22
- this.el = document.createElement('div');
23
- this.el.className = 'ogrid-column-chooser';
24
- const btn = document.createElement('button');
25
- btn.className = 'ogrid-column-chooser-btn';
26
- btn.textContent = 'Columns';
27
- btn.addEventListener('click', () => this.toggle());
28
- this.el.appendChild(btn);
29
- this.container.appendChild(this.el);
30
- }
31
- /** Update checkbox checked states without destroying the dropdown. */
32
- updateDropdownState() {
33
- if (!this.dropdown)
34
- return;
35
- const checkboxes = this.dropdown.querySelectorAll('input[type="checkbox"]');
36
- const columns = this.state.columns;
37
- checkboxes.forEach((checkbox, idx) => {
38
- if (idx < columns.length) {
39
- checkbox.checked = this.state.visibleColumns.has(columns[idx].columnId);
40
- }
41
- });
42
- }
43
- toggle() {
44
- if (this.isOpen) {
45
- this.close();
46
- }
47
- else {
48
- this.open();
49
- }
50
- }
51
- open() {
52
- if (this.dropdown)
53
- return;
54
- this.isOpen = true;
55
- this.dropdown = document.createElement('div');
56
- this.dropdown.className = 'ogrid-column-chooser-dropdown';
57
- for (const col of this.state.columns) {
58
- const label = document.createElement('label');
59
- label.className = 'ogrid-column-chooser-item';
60
- const checkbox = document.createElement('input');
61
- checkbox.type = 'checkbox';
62
- checkbox.checked = this.state.visibleColumns.has(col.columnId);
63
- checkbox.disabled = !!col.required;
64
- checkbox.addEventListener('change', () => {
65
- const next = new Set(this.state.visibleColumns);
66
- if (checkbox.checked) {
67
- next.add(col.columnId);
68
- }
69
- else {
70
- next.delete(col.columnId);
71
- }
72
- this.state.setVisibleColumns(next);
73
- });
74
- label.appendChild(checkbox);
75
- label.appendChild(document.createTextNode(' ' + col.name));
76
- this.dropdown.appendChild(label);
77
- }
78
- this.el?.appendChild(this.dropdown);
79
- }
80
- close() {
81
- this.isOpen = false;
82
- this.dropdown?.remove();
83
- this.dropdown = null;
84
- }
85
- destroy() {
86
- this.close();
87
- this.el?.remove();
88
- this.el = null;
89
- this.initialized = false;
90
- }
91
- }
@@ -1,125 +0,0 @@
1
- import { GRID_CONTEXT_MENU_ITEMS, formatShortcut } from '@alaarab/ogrid-core';
2
- const MENU_STYLE = {
3
- position: 'fixed',
4
- backgroundColor: 'var(--ogrid-bg, #fff)',
5
- border: '1px solid var(--ogrid-border, #e0e0e0)',
6
- boxShadow: 'var(--ogrid-shadow, 0 4px 16px rgba(0, 0, 0, 0.12))',
7
- borderRadius: '6px',
8
- zIndex: '10000',
9
- minWidth: '180px',
10
- padding: '4px 0',
11
- fontFamily: 'system-ui, -apple-system, sans-serif',
12
- fontSize: '14px',
13
- color: 'var(--ogrid-fg, #242424)',
14
- };
15
- const ITEM_STYLE = {
16
- padding: '6px 12px',
17
- cursor: 'pointer',
18
- display: 'flex',
19
- justifyContent: 'space-between',
20
- alignItems: 'center',
21
- };
22
- const DIVIDER_STYLE = {
23
- height: '1px',
24
- backgroundColor: 'var(--ogrid-border, #e0e0e0)',
25
- margin: '4px 0',
26
- };
27
- export class ContextMenu {
28
- constructor() {
29
- this.menu = null;
30
- this.handlers = null;
31
- }
32
- show(x, y, handlers, canUndo, canRedo, selectionRange) {
33
- this.close();
34
- this.handlers = handlers;
35
- this.menu = document.createElement('div');
36
- Object.assign(this.menu.style, MENU_STYLE);
37
- this.menu.style.left = `${x}px`;
38
- this.menu.style.top = `${y}px`;
39
- for (const item of GRID_CONTEXT_MENU_ITEMS) {
40
- if (item.dividerBefore) {
41
- const divider = document.createElement('div');
42
- Object.assign(divider.style, DIVIDER_STYLE);
43
- this.menu.appendChild(divider);
44
- }
45
- const menuItem = document.createElement('div');
46
- Object.assign(menuItem.style, ITEM_STYLE);
47
- const label = document.createElement('span');
48
- label.textContent = item.label;
49
- menuItem.appendChild(label);
50
- if (item.shortcut) {
51
- const shortcut = document.createElement('span');
52
- shortcut.textContent = formatShortcut(item.shortcut);
53
- shortcut.style.marginLeft = '20px';
54
- shortcut.style.color = 'var(--ogrid-muted, #666)';
55
- shortcut.style.fontSize = '12px';
56
- menuItem.appendChild(shortcut);
57
- }
58
- const isDisabled = (item.id === 'undo' && !canUndo) ||
59
- (item.id === 'redo' && !canRedo) ||
60
- (item.disabledWhenNoSelection && selectionRange == null);
61
- if (isDisabled) {
62
- menuItem.style.color = 'var(--ogrid-fg-muted, rgba(0, 0, 0, 0.4))';
63
- menuItem.style.opacity = '0.5';
64
- menuItem.style.cursor = 'not-allowed';
65
- }
66
- else {
67
- menuItem.addEventListener('mouseenter', () => {
68
- menuItem.style.backgroundColor = 'var(--ogrid-bg-hover, #f5f5f5)';
69
- }, { passive: true });
70
- menuItem.addEventListener('mouseleave', () => {
71
- menuItem.style.backgroundColor = '';
72
- }, { passive: true });
73
- menuItem.addEventListener('click', () => {
74
- this.handleItemClick(item.id);
75
- }, { passive: true });
76
- }
77
- this.menu.appendChild(menuItem);
78
- }
79
- document.body.appendChild(this.menu);
80
- const handleClickOutside = (e) => {
81
- if (this.menu && !this.menu.contains(e.target)) {
82
- this.close();
83
- document.removeEventListener('mousedown', handleClickOutside);
84
- }
85
- };
86
- setTimeout(() => {
87
- document.addEventListener('mousedown', handleClickOutside, { passive: true });
88
- }, 0);
89
- }
90
- close() {
91
- if (this.menu) {
92
- this.menu.remove();
93
- this.menu = null;
94
- }
95
- this.handlers = null;
96
- }
97
- handleItemClick(id) {
98
- if (!this.handlers)
99
- return;
100
- switch (id) {
101
- case 'undo':
102
- this.handlers.onUndo();
103
- break;
104
- case 'redo':
105
- this.handlers.onRedo();
106
- break;
107
- case 'copy':
108
- this.handlers.onCopy();
109
- break;
110
- case 'cut':
111
- this.handlers.onCut();
112
- break;
113
- case 'paste':
114
- this.handlers.onPaste();
115
- break;
116
- case 'selectAll':
117
- this.handlers.onSelectAll();
118
- break;
119
- }
120
- this.close();
121
- }
122
- destroy() {
123
- this.close();
124
- }
125
- }