@alaarab/ogrid-js 2.1.2 → 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 +7 -5
  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,353 +0,0 @@
1
- const PANEL_WIDTH = 240;
2
- const TAB_WIDTH = 36;
3
- const PANEL_LABELS = {
4
- columns: 'Columns',
5
- filters: 'Filters',
6
- };
7
- const PANEL_ICONS = {
8
- columns: '\u2261',
9
- filters: '\u2A65',
10
- };
11
- export class SideBar {
12
- constructor(container, state) {
13
- this.el = null;
14
- this.config = null;
15
- this.container = container;
16
- this.state = state;
17
- }
18
- setConfig(config) {
19
- this.config = config;
20
- }
21
- render() {
22
- if (this.el)
23
- this.el.remove();
24
- if (!this.state.isEnabled || !this.config)
25
- return;
26
- this.el = document.createElement('div');
27
- this.el.className = 'ogrid-sidebar';
28
- this.el.setAttribute('role', 'complementary');
29
- this.el.setAttribute('aria-label', 'Side bar');
30
- this.el.style.display = 'flex';
31
- this.el.style.flexDirection = 'row';
32
- this.el.style.flexShrink = '0';
33
- const position = this.state.position;
34
- const tabStrip = this.createTabStrip(position);
35
- const panel = this.createPanel(position);
36
- if (position === 'left') {
37
- this.el.appendChild(tabStrip);
38
- if (panel)
39
- this.el.appendChild(panel);
40
- }
41
- else {
42
- if (panel)
43
- this.el.appendChild(panel);
44
- this.el.appendChild(tabStrip);
45
- }
46
- this.container.appendChild(this.el);
47
- }
48
- createTabStrip(position) {
49
- const strip = document.createElement('div');
50
- strip.style.display = 'flex';
51
- strip.style.flexDirection = 'column';
52
- strip.style.width = `${TAB_WIDTH}px`;
53
- strip.style.background = 'var(--ogrid-header-bg, #f5f5f5)';
54
- strip.setAttribute('role', 'tablist');
55
- strip.setAttribute('aria-label', 'Side bar tabs');
56
- if (position === 'right') {
57
- strip.style.borderLeft = '1px solid var(--ogrid-border, #e0e0e0)';
58
- }
59
- else {
60
- strip.style.borderRight = '1px solid var(--ogrid-border, #e0e0e0)';
61
- }
62
- for (const panelId of this.state.panels) {
63
- const btn = document.createElement('button');
64
- btn.setAttribute('role', 'tab');
65
- btn.setAttribute('aria-selected', String(this.state.activePanel === panelId));
66
- btn.setAttribute('aria-label', PANEL_LABELS[panelId]);
67
- btn.title = PANEL_LABELS[panelId];
68
- btn.textContent = PANEL_ICONS[panelId];
69
- btn.className = 'ogrid-sidebar-tab';
70
- btn.style.width = `${TAB_WIDTH}px`;
71
- btn.style.height = `${TAB_WIDTH}px`;
72
- btn.style.border = 'none';
73
- btn.style.cursor = 'pointer';
74
- btn.style.color = 'var(--ogrid-fg, #242424)';
75
- btn.style.fontSize = '14px';
76
- btn.style.display = 'flex';
77
- btn.style.alignItems = 'center';
78
- btn.style.justifyContent = 'center';
79
- if (this.state.activePanel === panelId) {
80
- btn.style.background = 'var(--ogrid-bg, #fff)';
81
- btn.style.fontWeight = 'bold';
82
- }
83
- else {
84
- btn.style.background = 'transparent';
85
- btn.style.fontWeight = 'normal';
86
- }
87
- btn.addEventListener('click', () => {
88
- this.state.toggle(panelId);
89
- });
90
- strip.appendChild(btn);
91
- }
92
- return strip;
93
- }
94
- createPanel(position) {
95
- if (!this.state.isOpen || !this.state.activePanel)
96
- return null;
97
- const panelContainer = document.createElement('div');
98
- panelContainer.setAttribute('role', 'tabpanel');
99
- panelContainer.setAttribute('aria-label', PANEL_LABELS[this.state.activePanel]);
100
- panelContainer.className = 'ogrid-sidebar-panel';
101
- panelContainer.style.width = `${PANEL_WIDTH}px`;
102
- panelContainer.style.display = 'flex';
103
- panelContainer.style.flexDirection = 'column';
104
- panelContainer.style.overflow = 'hidden';
105
- panelContainer.style.background = 'var(--ogrid-bg, #fff)';
106
- panelContainer.style.color = 'var(--ogrid-fg, #242424)';
107
- if (position === 'right') {
108
- panelContainer.style.borderLeft = '1px solid var(--ogrid-border, #e0e0e0)';
109
- }
110
- else {
111
- panelContainer.style.borderRight = '1px solid var(--ogrid-border, #e0e0e0)';
112
- }
113
- // Header
114
- const header = document.createElement('div');
115
- header.style.display = 'flex';
116
- header.style.justifyContent = 'space-between';
117
- header.style.alignItems = 'center';
118
- header.style.padding = '8px 12px';
119
- header.style.borderBottom = '1px solid var(--ogrid-border, #e0e0e0)';
120
- header.style.fontWeight = '600';
121
- const title = document.createElement('span');
122
- title.textContent = PANEL_LABELS[this.state.activePanel];
123
- header.appendChild(title);
124
- const closeBtn = document.createElement('button');
125
- closeBtn.innerHTML = '×';
126
- closeBtn.setAttribute('aria-label', 'Close panel');
127
- closeBtn.style.border = 'none';
128
- closeBtn.style.background = 'transparent';
129
- closeBtn.style.cursor = 'pointer';
130
- closeBtn.style.fontSize = '16px';
131
- closeBtn.style.color = 'var(--ogrid-fg, #242424)';
132
- closeBtn.addEventListener('click', () => this.state.close());
133
- header.appendChild(closeBtn);
134
- panelContainer.appendChild(header);
135
- // Body
136
- const body = document.createElement('div');
137
- body.style.flex = '1';
138
- body.style.overflowY = 'auto';
139
- body.style.padding = '8px 12px';
140
- if (this.state.activePanel === 'columns') {
141
- this.renderColumnsPanel(body);
142
- }
143
- else if (this.state.activePanel === 'filters') {
144
- this.renderFiltersPanel(body);
145
- }
146
- panelContainer.appendChild(body);
147
- return panelContainer;
148
- }
149
- renderColumnsPanel(body) {
150
- if (!this.config)
151
- return;
152
- const { columns, visibleColumns, onVisibilityChange, onSetVisibleColumns } = this.config;
153
- const allVisible = columns.every(c => visibleColumns.has(c.columnId));
154
- // Button row
155
- const btnRow = document.createElement('div');
156
- btnRow.style.display = 'flex';
157
- btnRow.style.gap = '8px';
158
- btnRow.style.marginBottom = '8px';
159
- const selectAllBtn = document.createElement('button');
160
- selectAllBtn.textContent = 'Select All';
161
- selectAllBtn.disabled = allVisible;
162
- selectAllBtn.className = 'ogrid-sidebar-action-btn';
163
- this.applyActionButtonStyle(selectAllBtn);
164
- selectAllBtn.addEventListener('click', () => {
165
- const next = new Set(visibleColumns);
166
- columns.forEach(c => next.add(c.columnId));
167
- onSetVisibleColumns(next);
168
- });
169
- btnRow.appendChild(selectAllBtn);
170
- const clearAllBtn = document.createElement('button');
171
- clearAllBtn.textContent = 'Clear All';
172
- clearAllBtn.className = 'ogrid-sidebar-action-btn';
173
- this.applyActionButtonStyle(clearAllBtn);
174
- clearAllBtn.addEventListener('click', () => {
175
- const next = new Set();
176
- columns.forEach(c => {
177
- if (c.required && visibleColumns.has(c.columnId))
178
- next.add(c.columnId);
179
- });
180
- onSetVisibleColumns(next);
181
- });
182
- btnRow.appendChild(clearAllBtn);
183
- body.appendChild(btnRow);
184
- // Column checkboxes
185
- for (const col of columns) {
186
- const label = document.createElement('label');
187
- label.style.display = 'flex';
188
- label.style.alignItems = 'center';
189
- label.style.gap = '6px';
190
- label.style.padding = '2px 0';
191
- label.style.cursor = 'pointer';
192
- const checkbox = document.createElement('input');
193
- checkbox.type = 'checkbox';
194
- checkbox.checked = visibleColumns.has(col.columnId);
195
- checkbox.disabled = !!col.required;
196
- checkbox.addEventListener('change', () => {
197
- onVisibilityChange(col.columnId, checkbox.checked);
198
- });
199
- const text = document.createElement('span');
200
- text.textContent = col.name;
201
- label.appendChild(checkbox);
202
- label.appendChild(text);
203
- body.appendChild(label);
204
- }
205
- }
206
- renderFiltersPanel(body) {
207
- if (!this.config)
208
- return;
209
- const { filterableColumns, filters, onFilterChange, filterOptions } = this.config;
210
- if (filterableColumns.length === 0) {
211
- const msg = document.createElement('div');
212
- msg.style.color = 'var(--ogrid-muted, #999)';
213
- msg.style.fontStyle = 'italic';
214
- msg.textContent = 'No filterable columns';
215
- body.appendChild(msg);
216
- return;
217
- }
218
- for (const col of filterableColumns) {
219
- const group = document.createElement('div');
220
- group.style.marginBottom = '12px';
221
- const labelEl = document.createElement('div');
222
- labelEl.style.fontWeight = '500';
223
- labelEl.style.marginBottom = '4px';
224
- labelEl.style.fontSize = '13px';
225
- labelEl.textContent = col.name;
226
- group.appendChild(labelEl);
227
- if (col.filterType === 'text') {
228
- const input = document.createElement('input');
229
- input.type = 'text';
230
- const fv = filters[col.filterField];
231
- input.value = fv?.type === 'text' ? fv.value : '';
232
- input.placeholder = `Filter ${col.name}...`;
233
- input.setAttribute('aria-label', `Filter ${col.name}`);
234
- this.applyTextInputStyle(input);
235
- input.addEventListener('input', () => {
236
- onFilterChange(col.filterField, input.value ? { type: 'text', value: input.value } : undefined);
237
- });
238
- group.appendChild(input);
239
- }
240
- else if (col.filterType === 'date') {
241
- const container = document.createElement('div');
242
- container.style.display = 'flex';
243
- container.style.flexDirection = 'column';
244
- container.style.gap = '4px';
245
- const fvDate = filters[col.filterField];
246
- const existingDate = fvDate?.type === 'date' ? fvDate.value : {};
247
- // From date
248
- const fromLabel = document.createElement('label');
249
- fromLabel.style.display = 'flex';
250
- fromLabel.style.alignItems = 'center';
251
- fromLabel.style.gap = '4px';
252
- fromLabel.style.fontSize = '12px';
253
- fromLabel.textContent = 'From: ';
254
- const fromInput = document.createElement('input');
255
- fromInput.type = 'date';
256
- fromInput.value = existingDate.from ?? '';
257
- fromInput.setAttribute('aria-label', `${col.name} from date`);
258
- this.applyDateInputStyle(fromInput);
259
- fromLabel.appendChild(fromInput);
260
- container.appendChild(fromLabel);
261
- // To date
262
- const toLabel = document.createElement('label');
263
- toLabel.style.display = 'flex';
264
- toLabel.style.alignItems = 'center';
265
- toLabel.style.gap = '4px';
266
- toLabel.style.fontSize = '12px';
267
- toLabel.textContent = 'To: ';
268
- const toInput = document.createElement('input');
269
- toInput.type = 'date';
270
- toInput.value = existingDate.to ?? '';
271
- toInput.setAttribute('aria-label', `${col.name} to date`);
272
- this.applyDateInputStyle(toInput);
273
- toLabel.appendChild(toInput);
274
- container.appendChild(toLabel);
275
- const updateDate = () => {
276
- const from = fromInput.value || undefined;
277
- const to = toInput.value || undefined;
278
- onFilterChange(col.filterField, from || to ? { type: 'date', value: { from, to } } : undefined);
279
- };
280
- fromInput.addEventListener('change', updateDate);
281
- toInput.addEventListener('change', updateDate);
282
- group.appendChild(container);
283
- }
284
- else if (col.filterType === 'multiSelect') {
285
- const opts = filterOptions[col.filterField] ?? [];
286
- const container = document.createElement('div');
287
- container.style.maxHeight = '120px';
288
- container.style.overflowY = 'auto';
289
- container.setAttribute('role', 'group');
290
- container.setAttribute('aria-label', `${col.name} options`);
291
- const fvMulti = filters[col.filterField];
292
- const currentValues = fvMulti?.type === 'multiSelect' ? fvMulti.value : [];
293
- for (const opt of opts) {
294
- const optLabel = document.createElement('label');
295
- optLabel.style.display = 'flex';
296
- optLabel.style.alignItems = 'center';
297
- optLabel.style.gap = '4px';
298
- optLabel.style.padding = '1px 0';
299
- optLabel.style.cursor = 'pointer';
300
- optLabel.style.fontSize = '13px';
301
- const checkbox = document.createElement('input');
302
- checkbox.type = 'checkbox';
303
- checkbox.checked = currentValues.includes(opt);
304
- checkbox.addEventListener('change', () => {
305
- const currentFv = filters[col.filterField];
306
- const current = currentFv?.type === 'multiSelect' ? [...currentFv.value] : [];
307
- const next = checkbox.checked
308
- ? [...current, opt]
309
- : current.filter(v => v !== opt);
310
- onFilterChange(col.filterField, next.length > 0 ? { type: 'multiSelect', value: next } : undefined);
311
- });
312
- const text = document.createElement('span');
313
- text.textContent = opt;
314
- optLabel.appendChild(checkbox);
315
- optLabel.appendChild(text);
316
- container.appendChild(optLabel);
317
- }
318
- group.appendChild(container);
319
- }
320
- body.appendChild(group);
321
- }
322
- }
323
- applyActionButtonStyle(btn) {
324
- btn.style.flex = '1';
325
- btn.style.cursor = 'pointer';
326
- btn.style.background = 'var(--ogrid-bg-subtle, #f3f2f1)';
327
- btn.style.color = 'var(--ogrid-fg, #242424)';
328
- btn.style.border = '1px solid var(--ogrid-border, #e0e0e0)';
329
- btn.style.borderRadius = '4px';
330
- btn.style.padding = '4px 8px';
331
- }
332
- applyTextInputStyle(input) {
333
- input.style.width = '100%';
334
- input.style.boxSizing = 'border-box';
335
- input.style.padding = '4px 6px';
336
- input.style.background = 'var(--ogrid-bg, #fff)';
337
- input.style.color = 'var(--ogrid-fg, #242424)';
338
- input.style.border = '1px solid var(--ogrid-border, #e0e0e0)';
339
- input.style.borderRadius = '4px';
340
- }
341
- applyDateInputStyle(input) {
342
- input.style.flex = '1';
343
- input.style.padding = '2px 4px';
344
- input.style.background = 'var(--ogrid-bg, #fff)';
345
- input.style.color = 'var(--ogrid-fg, #242424)';
346
- input.style.border = '1px solid var(--ogrid-border, #e0e0e0)';
347
- input.style.borderRadius = '4px';
348
- }
349
- destroy() {
350
- this.el?.remove();
351
- this.el = null;
352
- }
353
- }
@@ -1,34 +0,0 @@
1
- import { getStatusBarParts } from '@alaarab/ogrid-core';
2
- export class StatusBar {
3
- constructor(container) {
4
- this.el = null;
5
- this.container = container;
6
- }
7
- render(props) {
8
- if (this.el)
9
- this.el.remove();
10
- const parts = getStatusBarParts(props);
11
- if (parts.length === 0 && !props.aggregation)
12
- return;
13
- this.el = document.createElement('div');
14
- this.el.className = 'ogrid-status-bar';
15
- for (const part of parts) {
16
- const span = document.createElement('span');
17
- span.className = 'ogrid-status-part';
18
- span.textContent = `${part.label}: ${part.value}`;
19
- this.el.appendChild(span);
20
- }
21
- if (props.aggregation) {
22
- const agg = props.aggregation;
23
- const aggSpan = document.createElement('span');
24
- aggSpan.className = 'ogrid-status-aggregation';
25
- aggSpan.textContent = `Sum: ${agg.sum.toLocaleString()} | Avg: ${agg.avg.toFixed(2)} | Min: ${agg.min} | Max: ${agg.max} | Count: ${agg.count}`;
26
- this.el.appendChild(aggSpan);
27
- }
28
- this.container.appendChild(this.el);
29
- }
30
- destroy() {
31
- this.el?.remove();
32
- this.el = null;
33
- }
34
- }