@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,434 +0,0 @@
1
- import { getCellValue } from '@alaarab/ogrid-core';
2
- const EDITOR_STYLE = {
3
- position: 'absolute',
4
- zIndex: '1000',
5
- boxSizing: 'border-box',
6
- border: '2px solid var(--ogrid-selection, #217346)',
7
- background: 'var(--ogrid-bg, #fff)',
8
- color: 'var(--ogrid-fg, #242424)',
9
- outline: 'none',
10
- fontFamily: 'inherit',
11
- fontSize: 'inherit',
12
- };
13
- export class InlineCellEditor {
14
- constructor(container) {
15
- this.editor = null;
16
- this.editingCell = null;
17
- this.editingCellElement = null;
18
- this.onCommit = null;
19
- this.onCancel = null;
20
- this.onAfterCommit = null;
21
- this.container = container;
22
- }
23
- startEdit(rowId, columnId, item, column, cell, onCommit, onCancel, onAfterCommit) {
24
- this.closeEditor();
25
- this.editingCell = { rowId, columnId };
26
- this.editingCellElement = cell;
27
- this.onCommit = onCommit;
28
- this.onCancel = onCancel;
29
- this.onAfterCommit = onAfterCommit ?? null;
30
- const value = getCellValue(item, column);
31
- const rect = cell.getBoundingClientRect();
32
- const containerRect = this.container.getBoundingClientRect();
33
- const editor = this.createEditor(column, item, value, cell);
34
- editor.style.position = 'absolute';
35
- editor.style.left = `${rect.left - containerRect.left + this.container.scrollLeft}px`;
36
- editor.style.top = `${rect.top - containerRect.top + this.container.scrollTop}px`;
37
- editor.style.width = `${rect.width}px`;
38
- editor.style.height = `${rect.height}px`;
39
- this.editor = editor;
40
- this.container.appendChild(editor);
41
- editor.focus();
42
- // Position dropdown with fixed positioning to escape container overflow
43
- const dropdownEl = editor.querySelector('[role="listbox"]');
44
- if (dropdownEl) {
45
- const maxH = 200;
46
- const spaceBelow = window.innerHeight - rect.bottom;
47
- const flipUp = spaceBelow < maxH && rect.top > spaceBelow;
48
- dropdownEl.style.position = 'fixed';
49
- dropdownEl.style.left = `${rect.left}px`;
50
- dropdownEl.style.width = `${rect.width}px`;
51
- dropdownEl.style.maxHeight = `${maxH}px`;
52
- dropdownEl.style.zIndex = '9999';
53
- dropdownEl.style.right = 'auto';
54
- if (flipUp) {
55
- dropdownEl.style.top = 'auto';
56
- dropdownEl.style.bottom = `${window.innerHeight - rect.top}px`;
57
- }
58
- else {
59
- dropdownEl.style.top = `${rect.bottom}px`;
60
- }
61
- }
62
- }
63
- /** Returns the cell currently being edited, or null if no editor is open. */
64
- getEditingCell() {
65
- return this.editingCell;
66
- }
67
- closeEditor() {
68
- // Reset visibility on the cell that was being edited (Bug 1 & 2 fix:
69
- // the renderer sets visibility:hidden on the editing cell, and it may
70
- // not re-render before the next click lands, so we clear it explicitly).
71
- // Look up the cell by data attributes since the original element reference
72
- // may have been replaced by a re-render.
73
- if (this.editingCell && this.container.isConnected) {
74
- const { rowId, columnId } = this.editingCell;
75
- const row = this.container.querySelector(`tr[data-row-id="${rowId}"]`);
76
- if (row) {
77
- const td = row.querySelector(`td[data-column-id="${columnId}"]`);
78
- if (td) {
79
- td.style.visibility = '';
80
- }
81
- }
82
- }
83
- if (this.editingCellElement) {
84
- // Also reset the original element if it's still connected in the DOM
85
- if (this.editingCellElement.isConnected) {
86
- this.editingCellElement.style.visibility = '';
87
- }
88
- this.editingCellElement = null;
89
- }
90
- if (this.editor) {
91
- this.editor.remove();
92
- this.editor = null;
93
- }
94
- this.editingCell = null;
95
- this.onCommit = null;
96
- this.onCancel = null;
97
- this.onAfterCommit = null;
98
- }
99
- createEditor(column, item, value, cell) {
100
- const editorType = column.cellEditor;
101
- if (typeof editorType === 'function') {
102
- const context = {
103
- value,
104
- onValueChange: (newValue) => {
105
- if (this.editingCell) {
106
- this.onCommit?.(this.editingCell.rowId, this.editingCell.columnId, newValue);
107
- }
108
- },
109
- onCommit: () => this.closeEditor(),
110
- onCancel: () => {
111
- this.onCancel?.();
112
- this.closeEditor();
113
- },
114
- item,
115
- column,
116
- cell,
117
- cellEditorParams: column.cellEditorParams,
118
- };
119
- return editorType(context);
120
- }
121
- // Built-in editor types
122
- if (editorType === 'checkbox' || column.type === 'boolean') {
123
- return this.createCheckboxEditor(value);
124
- }
125
- if (editorType === 'select') {
126
- return this.createSelectEditor(value, column);
127
- }
128
- if (editorType === 'richSelect') {
129
- return this.createRichSelectEditor(value, column);
130
- }
131
- if (editorType === 'date' || column.type === 'date') {
132
- return this.createDateEditor(value);
133
- }
134
- // Default: text editor
135
- return this.createTextEditor(value);
136
- }
137
- /**
138
- * Shared factory for text/date input editors — both types have identical event handling,
139
- * differing only in input.type and initial value formatting.
140
- */
141
- createInputEditor(type, initialValue) {
142
- const input = document.createElement('input');
143
- input.type = type;
144
- input.value = initialValue;
145
- Object.assign(input.style, EDITOR_STYLE);
146
- input.addEventListener('keydown', (e) => {
147
- if (e.key === 'Enter') {
148
- e.preventDefault();
149
- e.stopPropagation(); // Prevent grid wrapper from re-opening the editor
150
- if (this.editingCell) {
151
- this.onCommit?.(this.editingCell.rowId, this.editingCell.columnId, input.value);
152
- }
153
- const afterCommit = this.onAfterCommit;
154
- this.closeEditor();
155
- afterCommit?.(); // Move active cell down after closing
156
- }
157
- else if (e.key === 'Escape') {
158
- e.preventDefault();
159
- e.stopPropagation();
160
- this.onCancel?.();
161
- this.closeEditor();
162
- }
163
- });
164
- input.addEventListener('blur', () => {
165
- if (this.editingCell) {
166
- this.onCommit?.(this.editingCell.rowId, this.editingCell.columnId, input.value);
167
- }
168
- this.closeEditor();
169
- });
170
- setTimeout(() => input.select(), 0);
171
- return input;
172
- }
173
- createTextEditor(value) {
174
- return this.createInputEditor('text', value != null ? String(value) : '');
175
- }
176
- createCheckboxEditor(value) {
177
- const input = document.createElement('input');
178
- input.type = 'checkbox';
179
- input.checked = Boolean(value);
180
- Object.assign(input.style, EDITOR_STYLE);
181
- input.style.width = '20px';
182
- input.style.height = '20px';
183
- input.addEventListener('change', () => {
184
- if (this.editingCell) {
185
- this.onCommit?.(this.editingCell.rowId, this.editingCell.columnId, input.checked);
186
- }
187
- this.closeEditor();
188
- });
189
- input.addEventListener('keydown', (e) => {
190
- if (e.key === 'Escape') {
191
- e.preventDefault();
192
- e.stopPropagation();
193
- this.onCancel?.();
194
- this.closeEditor();
195
- }
196
- });
197
- return input;
198
- }
199
- createDateEditor(value) {
200
- let initialValue = '';
201
- if (value != null) {
202
- const dateStr = String(value);
203
- if (dateStr.match(/^\d{4}-\d{2}-\d{2}/)) {
204
- initialValue = dateStr.substring(0, 10);
205
- }
206
- }
207
- return this.createInputEditor('date', initialValue);
208
- }
209
- createSelectEditor(value, column) {
210
- const values = column.cellEditorParams?.values ?? [];
211
- const formatValue = column.cellEditorParams?.formatValue;
212
- const getDisplayText = (v) => formatValue ? formatValue(v) : (v != null ? String(v) : '');
213
- const wrapper = document.createElement('div');
214
- Object.assign(wrapper.style, EDITOR_STYLE);
215
- wrapper.style.padding = '6px 10px';
216
- wrapper.style.display = 'flex';
217
- wrapper.style.alignItems = 'center';
218
- wrapper.tabIndex = 0;
219
- // Display current value + chevron
220
- const display = document.createElement('div');
221
- display.style.display = 'flex';
222
- display.style.alignItems = 'center';
223
- display.style.justifyContent = 'space-between';
224
- display.style.width = '100%';
225
- display.style.cursor = 'pointer';
226
- display.style.fontSize = '13px';
227
- const valueSpan = document.createElement('span');
228
- valueSpan.textContent = getDisplayText(value);
229
- display.appendChild(valueSpan);
230
- const chevron = document.createElement('span');
231
- chevron.textContent = '\u25BE';
232
- chevron.style.marginLeft = '4px';
233
- chevron.style.fontSize = '10px';
234
- chevron.style.opacity = '0.5';
235
- display.appendChild(chevron);
236
- wrapper.appendChild(display);
237
- // Dropdown list
238
- const dropdown = document.createElement('div');
239
- dropdown.setAttribute('role', 'listbox');
240
- dropdown.style.position = 'absolute';
241
- dropdown.style.top = '100%';
242
- dropdown.style.left = '0';
243
- dropdown.style.right = '0';
244
- dropdown.style.maxHeight = '200px';
245
- dropdown.style.overflowY = 'auto';
246
- dropdown.style.backgroundColor = 'var(--ogrid-bg, #fff)';
247
- dropdown.style.border = '1px solid var(--ogrid-border, rgba(0, 0, 0, 0.12))';
248
- dropdown.style.zIndex = '1001';
249
- dropdown.style.boxShadow = '0 4px 16px rgba(0,0,0,0.2)';
250
- wrapper.appendChild(dropdown);
251
- let highlightedIndex = Math.max(values.findIndex((v) => String(v) === String(value)), 0);
252
- // Build all option elements once
253
- const buildOptions = () => {
254
- dropdown.innerHTML = '';
255
- for (let i = 0; i < values.length; i++) {
256
- const val = values[i];
257
- const option = document.createElement('div');
258
- option.setAttribute('role', 'option');
259
- option.setAttribute('aria-selected', String(i === highlightedIndex));
260
- option.textContent = getDisplayText(val);
261
- option.style.padding = '6px 8px';
262
- option.style.cursor = 'pointer';
263
- option.style.color = 'var(--ogrid-fg, #242424)';
264
- if (i === highlightedIndex) {
265
- option.style.background = 'var(--ogrid-bg-hover, #e8f0fe)';
266
- }
267
- option.addEventListener('mousedown', (e) => {
268
- e.preventDefault();
269
- if (this.editingCell) {
270
- this.onCommit?.(this.editingCell.rowId, this.editingCell.columnId, val);
271
- }
272
- this.closeEditor();
273
- });
274
- dropdown.appendChild(option);
275
- }
276
- };
277
- // Only update CSS class on old/new highlighted item — avoids rebuilding the DOM
278
- const updateHighlight = (prevIndex, nextIndex) => {
279
- const prev = dropdown.children[prevIndex];
280
- const next = dropdown.children[nextIndex];
281
- if (prev) {
282
- prev.style.background = '';
283
- prev.setAttribute('aria-selected', 'false');
284
- }
285
- if (next) {
286
- next.style.background = 'var(--ogrid-bg-hover, #e8f0fe)';
287
- next.setAttribute('aria-selected', 'true');
288
- }
289
- };
290
- const scrollHighlightedIntoView = () => {
291
- const highlighted = dropdown.children[highlightedIndex];
292
- highlighted?.scrollIntoView({ block: 'nearest' });
293
- };
294
- buildOptions();
295
- wrapper.addEventListener('keydown', (e) => {
296
- switch (e.key) {
297
- case 'ArrowDown': {
298
- e.preventDefault();
299
- const prevDown = highlightedIndex;
300
- highlightedIndex = Math.min(highlightedIndex + 1, values.length - 1);
301
- updateHighlight(prevDown, highlightedIndex);
302
- scrollHighlightedIntoView();
303
- break;
304
- }
305
- case 'ArrowUp': {
306
- e.preventDefault();
307
- const prevUp = highlightedIndex;
308
- highlightedIndex = Math.max(highlightedIndex - 1, 0);
309
- updateHighlight(prevUp, highlightedIndex);
310
- scrollHighlightedIntoView();
311
- break;
312
- }
313
- case 'Enter':
314
- e.preventDefault();
315
- e.stopPropagation();
316
- if (values.length > 0 && highlightedIndex < values.length) {
317
- if (this.editingCell) {
318
- this.onCommit?.(this.editingCell.rowId, this.editingCell.columnId, values[highlightedIndex]);
319
- }
320
- const afterCommit = this.onAfterCommit;
321
- this.closeEditor();
322
- afterCommit?.();
323
- }
324
- break;
325
- case 'Tab':
326
- e.preventDefault();
327
- if (values.length > 0 && highlightedIndex < values.length) {
328
- if (this.editingCell) {
329
- this.onCommit?.(this.editingCell.rowId, this.editingCell.columnId, values[highlightedIndex]);
330
- }
331
- this.closeEditor();
332
- }
333
- break;
334
- case 'Escape':
335
- e.preventDefault();
336
- e.stopPropagation();
337
- this.onCancel?.();
338
- this.closeEditor();
339
- break;
340
- }
341
- });
342
- return wrapper;
343
- }
344
- createRichSelectEditor(value, column) {
345
- const wrapper = document.createElement('div');
346
- Object.assign(wrapper.style, EDITOR_STYLE);
347
- wrapper.style.padding = '0';
348
- const input = document.createElement('input');
349
- input.type = 'text';
350
- input.value = value != null ? String(value) : '';
351
- input.style.width = '100%';
352
- input.style.border = 'none';
353
- input.style.outline = 'none';
354
- input.style.padding = '4px';
355
- input.style.boxSizing = 'border-box';
356
- input.style.background = 'var(--ogrid-bg, #fff)';
357
- input.style.color = 'var(--ogrid-fg, rgba(0, 0, 0, 0.87))';
358
- wrapper.appendChild(input);
359
- const dropdown = document.createElement('div');
360
- dropdown.style.position = 'absolute';
361
- dropdown.style.top = '100%';
362
- dropdown.style.left = '0';
363
- dropdown.style.width = '100%';
364
- dropdown.style.maxHeight = '200px';
365
- dropdown.style.overflowY = 'auto';
366
- dropdown.style.backgroundColor = 'var(--ogrid-bg, #fff)';
367
- dropdown.style.border = '1px solid var(--ogrid-border, rgba(0, 0, 0, 0.12))';
368
- dropdown.style.zIndex = '1001';
369
- wrapper.appendChild(dropdown);
370
- const values = column.cellEditorParams?.values ?? [];
371
- const formatValue = column.cellEditorParams?.formatValue ?? ((v) => String(v));
372
- const renderOptions = (filter) => {
373
- dropdown.innerHTML = '';
374
- const filtered = values.filter((v) => String(formatValue(v)).toLowerCase().includes(filter.toLowerCase()));
375
- for (const val of filtered) {
376
- const option = document.createElement('div');
377
- option.textContent = String(formatValue(val));
378
- option.style.padding = '4px 8px';
379
- option.style.cursor = 'pointer';
380
- option.addEventListener('mousedown', (e) => {
381
- e.preventDefault();
382
- if (this.editingCell) {
383
- this.onCommit?.(this.editingCell.rowId, this.editingCell.columnId, val);
384
- }
385
- this.closeEditor();
386
- });
387
- option.addEventListener('mouseenter', () => {
388
- option.style.backgroundColor = 'var(--ogrid-hover-bg, rgba(0, 0, 0, 0.04))';
389
- }, { passive: true });
390
- option.addEventListener('mouseleave', () => {
391
- option.style.backgroundColor = 'var(--ogrid-bg, #fff)';
392
- }, { passive: true });
393
- dropdown.appendChild(option);
394
- }
395
- };
396
- input.addEventListener('input', () => {
397
- renderOptions(input.value);
398
- });
399
- input.addEventListener('keydown', (e) => {
400
- if (e.key === 'Enter') {
401
- e.preventDefault();
402
- e.stopPropagation(); // Prevent grid wrapper from re-opening the editor
403
- if (this.editingCell) {
404
- this.onCommit?.(this.editingCell.rowId, this.editingCell.columnId, input.value);
405
- }
406
- const afterCommit = this.onAfterCommit;
407
- this.closeEditor();
408
- afterCommit?.(); // Move active cell down after closing
409
- }
410
- else if (e.key === 'Escape') {
411
- e.preventDefault();
412
- e.stopPropagation();
413
- this.onCancel?.();
414
- this.closeEditor();
415
- }
416
- });
417
- input.addEventListener('blur', (e) => {
418
- const related = e.relatedTarget;
419
- if (related && this.editor?.contains(related)) {
420
- return; // Focus moved within the editor (e.g., to dropdown), don't close
421
- }
422
- if (this.editingCell) {
423
- this.onCommit?.(this.editingCell.rowId, this.editingCell.columnId, input.value);
424
- }
425
- this.closeEditor();
426
- });
427
- renderOptions('');
428
- setTimeout(() => input.select(), 0);
429
- return wrapper;
430
- }
431
- destroy() {
432
- this.closeEditor();
433
- }
434
- }
@@ -1,156 +0,0 @@
1
- import { injectGlobalStyles, measureRange as measureRangeCore, rangesEqual } from '@alaarab/ogrid-core';
2
- /**
3
- * Measure the bounding rect of a range within a container, with scroll offsets.
4
- * This variant adds scroll offsets for the JS implementation's scrollable container.
5
- */
6
- function measureRange(container, range, colOffset) {
7
- const rect = measureRangeCore(container, range, colOffset);
8
- if (!rect)
9
- return null;
10
- // Add scroll offsets for JS implementation's scrollable container
11
- return {
12
- top: rect.top + container.scrollTop,
13
- left: rect.left + container.scrollLeft,
14
- width: rect.width,
15
- height: rect.height,
16
- };
17
- }
18
- /**
19
- * MarchingAntsOverlay — renders SVG overlays on top of the grid:
20
- * 1. Selection range: solid green border
21
- * 2. Copy/Cut range: animated dashed border (marching ants)
22
- *
23
- * Vanilla JS equivalent of React's MarchingAntsOverlay component.
24
- */
25
- export class MarchingAntsOverlay {
26
- constructor(container, colOffset = 0) {
27
- this.selSvg = null;
28
- this.clipSvg = null;
29
- this.selectionRange = null;
30
- this.copyRange = null;
31
- this.cutRange = null;
32
- this.rafHandle = 0;
33
- this.layoutVersion = 0; // Tracks layout changes to force re-measurement
34
- this.container = container;
35
- this.colOffset = colOffset;
36
- injectGlobalStyles('ogrid-marching-ants-keyframes', '@keyframes ogrid-marching-ants{to{stroke-dashoffset:-8}}');
37
- // The container must be positioned for absolute SVGs
38
- const pos = getComputedStyle(container).position;
39
- if (pos === 'static' || pos === '') {
40
- container.style.position = 'relative';
41
- }
42
- }
43
- update(selectionRange, copyRange, cutRange, layoutVersion) {
44
- // Track layout changes separately from range changes
45
- const layoutChanged = layoutVersion !== undefined && layoutVersion !== this.layoutVersion;
46
- if (layoutChanged && layoutVersion !== undefined) {
47
- this.layoutVersion = layoutVersion;
48
- }
49
- // Skip if nothing changed (ranges or layout)
50
- if (!layoutChanged &&
51
- rangesEqual(this.selectionRange, selectionRange) &&
52
- rangesEqual(this.copyRange, copyRange) &&
53
- rangesEqual(this.cutRange, cutRange)) {
54
- return;
55
- }
56
- this.selectionRange = selectionRange;
57
- this.copyRange = copyRange;
58
- this.cutRange = cutRange;
59
- // Delay one frame so cells are rendered
60
- if (this.rafHandle)
61
- cancelAnimationFrame(this.rafHandle);
62
- this.rafHandle = requestAnimationFrame(() => {
63
- this.rafHandle = 0;
64
- this.render();
65
- });
66
- }
67
- render() {
68
- const clipRange = this.copyRange ?? this.cutRange;
69
- // Selection range SVG
70
- const selRect = this.selectionRange
71
- ? measureRange(this.container, this.selectionRange, this.colOffset)
72
- : null;
73
- // When clipboard range matches selection, hide selection border so marching ants show
74
- const clipRangeMatchesSel = this.selectionRange != null &&
75
- clipRange != null &&
76
- rangesEqual(this.selectionRange, clipRange);
77
- if (selRect && !clipRangeMatchesSel) {
78
- if (!this.selSvg) {
79
- this.selSvg = this.createSvg(4);
80
- this.container.appendChild(this.selSvg);
81
- }
82
- this.positionSvg(this.selSvg, selRect);
83
- const rect = this.selSvg.querySelector('rect');
84
- if (rect) {
85
- rect.setAttribute('width', String(Math.max(0, selRect.width - 2)));
86
- rect.setAttribute('height', String(Math.max(0, selRect.height - 2)));
87
- rect.setAttribute('stroke', 'var(--ogrid-selection, #217346)');
88
- rect.setAttribute('stroke-width', '2');
89
- rect.removeAttribute('stroke-dasharray');
90
- rect.style.animation = '';
91
- }
92
- }
93
- else {
94
- this.removeSvg('sel');
95
- }
96
- // Copy/Cut range SVG (marching ants)
97
- const clipRect = clipRange
98
- ? measureRange(this.container, clipRange, this.colOffset)
99
- : null;
100
- if (clipRect) {
101
- if (!this.clipSvg) {
102
- this.clipSvg = this.createSvg(5);
103
- this.container.appendChild(this.clipSvg);
104
- }
105
- this.positionSvg(this.clipSvg, clipRect);
106
- const rect = this.clipSvg.querySelector('rect');
107
- if (rect) {
108
- rect.setAttribute('width', String(Math.max(0, clipRect.width - 2)));
109
- rect.setAttribute('height', String(Math.max(0, clipRect.height - 2)));
110
- rect.setAttribute('stroke', 'var(--ogrid-selection, #217346)');
111
- rect.setAttribute('stroke-width', '2');
112
- rect.setAttribute('stroke-dasharray', '4 4');
113
- rect.style.animation = 'ogrid-marching-ants 0.5s linear infinite';
114
- }
115
- }
116
- else {
117
- this.removeSvg('clip');
118
- }
119
- }
120
- createSvg(zIndex) {
121
- const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
122
- svg.setAttribute('aria-hidden', 'true');
123
- svg.style.position = 'absolute';
124
- svg.style.pointerEvents = 'none';
125
- svg.style.zIndex = String(zIndex);
126
- svg.style.overflow = 'visible';
127
- const rect = document.createElementNS('http://www.w3.org/2000/svg', 'rect');
128
- rect.setAttribute('x', '1');
129
- rect.setAttribute('y', '1');
130
- rect.setAttribute('fill', 'none');
131
- svg.appendChild(rect);
132
- return svg;
133
- }
134
- positionSvg(svg, rect) {
135
- svg.style.top = `${rect.top}px`;
136
- svg.style.left = `${rect.left}px`;
137
- svg.style.width = `${rect.width}px`;
138
- svg.style.height = `${rect.height}px`;
139
- }
140
- removeSvg(which) {
141
- if (which === 'sel' && this.selSvg) {
142
- this.selSvg.remove();
143
- this.selSvg = null;
144
- }
145
- else if (which === 'clip' && this.clipSvg) {
146
- this.clipSvg.remove();
147
- this.clipSvg = null;
148
- }
149
- }
150
- destroy() {
151
- if (this.rafHandle)
152
- cancelAnimationFrame(this.rafHandle);
153
- this.removeSvg('sel');
154
- this.removeSvg('clip');
155
- }
156
- }
@@ -1,85 +0,0 @@
1
- import { getPaginationViewModel } from '@alaarab/ogrid-core';
2
- export class PaginationControls {
3
- constructor(container, state) {
4
- this.el = null;
5
- this.container = container;
6
- this.state = state;
7
- }
8
- render(totalCount, pageSizeOptions) {
9
- if (this.el)
10
- this.el.remove();
11
- const vm = getPaginationViewModel(this.state.page, this.state.pageSize, totalCount, pageSizeOptions ? { pageSizeOptions } : undefined);
12
- if (!vm)
13
- return; // No pagination if totalCount is 0
14
- this.el = document.createElement('div');
15
- this.el.className = 'ogrid-pagination';
16
- // Page size selector
17
- const pageSizeDiv = document.createElement('div');
18
- pageSizeDiv.className = 'ogrid-pagination-size';
19
- const label = document.createElement('span');
20
- label.textContent = 'Rows per page: ';
21
- pageSizeDiv.appendChild(label);
22
- const select = document.createElement('select');
23
- select.className = 'ogrid-page-size-select';
24
- for (const size of vm.pageSizeOptions) {
25
- const option = document.createElement('option');
26
- option.value = String(size);
27
- option.textContent = String(size);
28
- option.selected = size === this.state.pageSize;
29
- select.appendChild(option);
30
- }
31
- select.addEventListener('change', () => {
32
- this.state.setPageSize(Number(select.value));
33
- });
34
- pageSizeDiv.appendChild(select);
35
- this.el.appendChild(pageSizeDiv);
36
- // Page info
37
- const info = document.createElement('span');
38
- info.className = 'ogrid-pagination-info';
39
- info.textContent = `${vm.startItem}-${vm.endItem} of ${totalCount}`;
40
- this.el.appendChild(info);
41
- // Navigation buttons
42
- const nav = document.createElement('div');
43
- nav.className = 'ogrid-pagination-nav';
44
- const prevBtn = document.createElement('button');
45
- prevBtn.textContent = '\u25C0';
46
- prevBtn.className = 'ogrid-pagination-btn';
47
- prevBtn.disabled = this.state.page === 1;
48
- prevBtn.addEventListener('click', () => this.state.setPage(this.state.page - 1));
49
- nav.appendChild(prevBtn);
50
- // Start ellipsis
51
- if (vm.showStartEllipsis) {
52
- const ellipsis = document.createElement('span');
53
- ellipsis.textContent = '...';
54
- ellipsis.className = 'ogrid-pagination-ellipsis';
55
- nav.appendChild(ellipsis);
56
- }
57
- // Page number buttons
58
- for (const pageNum of vm.pageNumbers) {
59
- const btn = document.createElement('button');
60
- btn.textContent = String(pageNum);
61
- btn.className = 'ogrid-pagination-btn' + (pageNum === this.state.page ? ' ogrid-pagination-active' : '');
62
- btn.addEventListener('click', () => this.state.setPage(pageNum));
63
- nav.appendChild(btn);
64
- }
65
- // End ellipsis
66
- if (vm.showEndEllipsis) {
67
- const ellipsis = document.createElement('span');
68
- ellipsis.textContent = '...';
69
- ellipsis.className = 'ogrid-pagination-ellipsis';
70
- nav.appendChild(ellipsis);
71
- }
72
- const nextBtn = document.createElement('button');
73
- nextBtn.textContent = '\u25B6';
74
- nextBtn.className = 'ogrid-pagination-btn';
75
- nextBtn.disabled = this.state.page === vm.totalPages;
76
- nextBtn.addEventListener('click', () => this.state.setPage(this.state.page + 1));
77
- nav.appendChild(nextBtn);
78
- this.el.appendChild(nav);
79
- this.container.appendChild(this.el);
80
- }
81
- destroy() {
82
- this.el?.remove();
83
- this.el = null;
84
- }
85
- }