@alaarab/ogrid-angular 2.5.9 → 2.6.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/esm/components/base-column-chooser.component.js +77 -0
- package/dist/esm/components/base-column-header-filter.component.js +267 -0
- package/dist/esm/components/base-column-header-menu.component.js +80 -0
- package/dist/esm/components/base-datagrid-table.component.js +768 -0
- package/dist/esm/components/base-inline-cell-editor.component.js +380 -0
- package/dist/esm/components/base-ogrid.component.js +36 -0
- package/dist/esm/components/base-pagination-controls.component.js +68 -0
- package/dist/esm/components/base-popover-cell-editor.component.js +122 -0
- package/dist/esm/components/empty-state.component.js +68 -0
- package/dist/esm/components/formula-bar.component.js +99 -0
- package/dist/esm/components/formula-ref-overlay.component.js +115 -0
- package/dist/esm/components/grid-context-menu.component.js +197 -0
- package/dist/esm/components/inline-cell-editor-template.js +134 -0
- package/dist/esm/components/marching-ants-overlay.component.js +177 -0
- package/dist/esm/components/ogrid-layout.component.js +302 -0
- package/dist/esm/components/sheet-tabs.component.js +83 -0
- package/dist/esm/components/sidebar.component.js +431 -0
- package/dist/esm/components/status-bar.component.js +92 -0
- package/dist/esm/index.js +39 -819
- package/dist/esm/services/column-reorder.service.js +176 -0
- package/dist/esm/services/datagrid-editing.service.js +59 -0
- package/dist/esm/services/datagrid-interaction.service.js +744 -0
- package/dist/esm/services/datagrid-layout.service.js +157 -0
- package/dist/esm/services/datagrid-state.service.js +636 -0
- package/dist/esm/services/formula-engine.service.js +223 -0
- package/dist/esm/services/ogrid.service.js +1094 -0
- package/dist/esm/services/virtual-scroll.service.js +114 -0
- package/dist/esm/styles/ogrid-theme-vars.js +112 -0
- package/dist/esm/types/columnTypes.js +1 -0
- package/dist/esm/types/dataGridTypes.js +1 -0
- package/dist/esm/types/index.js +1 -0
- package/dist/esm/utils/dataGridViewModel.js +6 -0
- package/dist/esm/utils/debounce.js +68 -0
- package/dist/esm/utils/index.js +8 -0
- package/dist/esm/utils/latestRef.js +41 -0
- package/dist/types/components/base-column-chooser.component.d.ts +3 -0
- package/dist/types/components/base-column-header-filter.component.d.ts +3 -0
- package/dist/types/components/base-column-header-menu.component.d.ts +3 -0
- package/dist/types/components/base-datagrid-table.component.d.ts +3 -0
- package/dist/types/components/base-inline-cell-editor.component.d.ts +7 -0
- package/dist/types/components/base-ogrid.component.d.ts +3 -0
- package/dist/types/components/base-pagination-controls.component.d.ts +3 -0
- package/dist/types/components/base-popover-cell-editor.component.d.ts +3 -0
- package/dist/types/components/empty-state.component.d.ts +3 -0
- package/dist/types/components/formula-bar.component.d.ts +3 -8
- package/dist/types/components/formula-ref-overlay.component.d.ts +3 -6
- package/dist/types/components/grid-context-menu.component.d.ts +3 -0
- package/dist/types/components/inline-cell-editor-template.d.ts +2 -2
- package/dist/types/components/marching-ants-overlay.component.d.ts +3 -0
- package/dist/types/components/ogrid-layout.component.d.ts +3 -0
- package/dist/types/components/sheet-tabs.component.d.ts +3 -8
- package/dist/types/components/sidebar.component.d.ts +3 -0
- package/dist/types/components/status-bar.component.d.ts +3 -0
- package/dist/types/services/column-reorder.service.d.ts +3 -0
- package/dist/types/services/datagrid-interaction.service.d.ts +1 -0
- package/dist/types/services/datagrid-state.service.d.ts +5 -0
- package/dist/types/services/formula-engine.service.d.ts +3 -9
- package/dist/types/services/ogrid.service.d.ts +8 -2
- package/dist/types/services/virtual-scroll.service.d.ts +3 -0
- package/package.json +4 -3
|
@@ -0,0 +1,380 @@
|
|
|
1
|
+
import { Directive, Input, Output, EventEmitter, signal, computed, ViewChild } from '@angular/core';
|
|
2
|
+
import { formatDateForDisplay, parseUserInputDate, getDateInputPlaceholder } from '@alaarab/ogrid-core';
|
|
3
|
+
import * as i0 from "@angular/core";
|
|
4
|
+
const _c0 = ["inputEl"];
|
|
5
|
+
const _c1 = ["selectWrapper"];
|
|
6
|
+
const _c2 = ["selectDropdown"];
|
|
7
|
+
const _c3 = ["richSelectWrapper"];
|
|
8
|
+
const _c4 = ["richSelectInput"];
|
|
9
|
+
const _c5 = ["richSelectDropdown"];
|
|
10
|
+
const _c6 = ["richSelectOptions"];
|
|
11
|
+
/**
|
|
12
|
+
* Abstract base class for Angular inline cell editors.
|
|
13
|
+
* Contains all shared signals, lifecycle hooks, keyboard handlers,
|
|
14
|
+
* dropdown positioning, and display logic.
|
|
15
|
+
*
|
|
16
|
+
* Subclasses only need a @Component decorator with selector + template.
|
|
17
|
+
*/
|
|
18
|
+
export class BaseInlineCellEditorComponent {
|
|
19
|
+
constructor() {
|
|
20
|
+
this.commit = new EventEmitter();
|
|
21
|
+
this.cancel = new EventEmitter();
|
|
22
|
+
this.localValue = signal('', ...(ngDevMode ? [{ debugName: "localValue" }] : []));
|
|
23
|
+
this.highlightedIndex = signal(0, ...(ngDevMode ? [{ debugName: "highlightedIndex" }] : []));
|
|
24
|
+
this.selectOptions = signal([], ...(ngDevMode ? [{ debugName: "selectOptions" }] : []));
|
|
25
|
+
this.searchText = signal('', ...(ngDevMode ? [{ debugName: "searchText" }] : []));
|
|
26
|
+
this.scrollCleanup = null;
|
|
27
|
+
this.suppressNextBlur = false;
|
|
28
|
+
this.filteredOptions = computed(() => {
|
|
29
|
+
const options = this.selectOptions();
|
|
30
|
+
const search = this.searchText().trim().toLowerCase();
|
|
31
|
+
if (!search)
|
|
32
|
+
return options;
|
|
33
|
+
return options.filter((v) => this.getDisplayText(v).toLowerCase().includes(search));
|
|
34
|
+
}, ...(ngDevMode ? [{ debugName: "filteredOptions" }] : []));
|
|
35
|
+
this._initialized = false;
|
|
36
|
+
}
|
|
37
|
+
ngOnInit() {
|
|
38
|
+
this._initialized = true;
|
|
39
|
+
this.syncFromInputs();
|
|
40
|
+
}
|
|
41
|
+
ngOnChanges() {
|
|
42
|
+
if (this._initialized) {
|
|
43
|
+
this.syncFromInputs();
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
syncFromInputs() {
|
|
47
|
+
const v = this.value;
|
|
48
|
+
let strVal = v != null ? String(v) : '';
|
|
49
|
+
if (this.editorType === 'date') {
|
|
50
|
+
const dateFormat = this.getDateFormat();
|
|
51
|
+
const cellEditorType = this.getCellEditorType();
|
|
52
|
+
if (cellEditorType === 'native') {
|
|
53
|
+
// Native <input type="date"> requires YYYY-MM-DD
|
|
54
|
+
strVal = strVal.match(/^\d{4}-\d{2}-\d{2}/) ? strVal.substring(0, 10) : strVal;
|
|
55
|
+
}
|
|
56
|
+
else {
|
|
57
|
+
// text/calendar: format for display
|
|
58
|
+
strVal = formatDateForDisplay(strVal, dateFormat) ?? strVal;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
this.localValue.set(strVal);
|
|
62
|
+
const col = this.column;
|
|
63
|
+
if (col?.cellEditorParams?.values) {
|
|
64
|
+
const vals = col.cellEditorParams.values;
|
|
65
|
+
this.selectOptions.set(vals);
|
|
66
|
+
const initialIdx = vals.findIndex((opt) => String(opt) === String(v));
|
|
67
|
+
this.highlightedIndex.set(Math.max(initialIdx, 0));
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
ngAfterViewInit() {
|
|
71
|
+
setTimeout(() => {
|
|
72
|
+
const richSelectInput = this.richSelectInput?.nativeElement;
|
|
73
|
+
if (richSelectInput) {
|
|
74
|
+
richSelectInput.focus();
|
|
75
|
+
richSelectInput.select();
|
|
76
|
+
this.positionFixedDropdown(this.richSelectWrapper, this.richSelectDropdown);
|
|
77
|
+
this.attachScrollClose(this.richSelectWrapper?.nativeElement);
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
const selectWrap = this.selectWrapper?.nativeElement;
|
|
81
|
+
if (selectWrap) {
|
|
82
|
+
selectWrap.focus();
|
|
83
|
+
this.positionFixedDropdown(this.selectWrapper, this.selectDropdown);
|
|
84
|
+
this.attachScrollClose(selectWrap);
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
const el = this.inputEl?.nativeElement;
|
|
88
|
+
if (el) {
|
|
89
|
+
el.focus();
|
|
90
|
+
if (el instanceof HTMLInputElement && el.type === 'text') {
|
|
91
|
+
el.select();
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
ngOnDestroy() {
|
|
97
|
+
this.scrollCleanup?.();
|
|
98
|
+
}
|
|
99
|
+
/** Attach scroll listeners to close the editor when the grid scrolls.
|
|
100
|
+
* Delayed via RAF to skip spurious scroll events fired during mount
|
|
101
|
+
* (e.g. focus-triggered scroll, layout-shift scroll from DOM changes). */
|
|
102
|
+
attachScrollClose(wrapper) {
|
|
103
|
+
if (!wrapper)
|
|
104
|
+
return;
|
|
105
|
+
const scrollParent = wrapper.closest('[data-ogrid-scroll-container]') ?? wrapper.closest('[style*="overflow"]');
|
|
106
|
+
const handleScroll = () => this.cancel.emit();
|
|
107
|
+
const raf = requestAnimationFrame(() => {
|
|
108
|
+
if (scrollParent)
|
|
109
|
+
scrollParent.addEventListener('scroll', handleScroll, { passive: true });
|
|
110
|
+
window.addEventListener('scroll', handleScroll, { passive: true });
|
|
111
|
+
});
|
|
112
|
+
this.scrollCleanup = () => {
|
|
113
|
+
cancelAnimationFrame(raf);
|
|
114
|
+
if (scrollParent)
|
|
115
|
+
scrollParent.removeEventListener('scroll', handleScroll);
|
|
116
|
+
window.removeEventListener('scroll', handleScroll);
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
commitValue(value) {
|
|
120
|
+
this.commit.emit(value);
|
|
121
|
+
}
|
|
122
|
+
commitAndSuppressBlur(value) {
|
|
123
|
+
this.suppressNextBlur = true;
|
|
124
|
+
this.commitValue(value);
|
|
125
|
+
}
|
|
126
|
+
cancelAndSuppressBlur() {
|
|
127
|
+
this.suppressNextBlur = true;
|
|
128
|
+
this.cancel.emit();
|
|
129
|
+
}
|
|
130
|
+
shouldSkipBlur() {
|
|
131
|
+
if (!this.suppressNextBlur)
|
|
132
|
+
return false;
|
|
133
|
+
this.suppressNextBlur = false;
|
|
134
|
+
return true;
|
|
135
|
+
}
|
|
136
|
+
onTextKeyDown(e) {
|
|
137
|
+
if (e.key === 'Enter') {
|
|
138
|
+
e.preventDefault();
|
|
139
|
+
this.commitDateOrValue(true);
|
|
140
|
+
}
|
|
141
|
+
else if (e.key === 'Escape') {
|
|
142
|
+
e.preventDefault();
|
|
143
|
+
this.cancelAndSuppressBlur();
|
|
144
|
+
}
|
|
145
|
+
else if (e.key === 'Tab') {
|
|
146
|
+
e.preventDefault();
|
|
147
|
+
this.commitDateOrValue(true);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
commitDateOrValue(suppressBlur = false) {
|
|
151
|
+
if (this.editorType === 'date') {
|
|
152
|
+
const cellEditorType = this.getCellEditorType();
|
|
153
|
+
if (cellEditorType !== 'native') {
|
|
154
|
+
const rawStr = String(this.localValue());
|
|
155
|
+
if (!rawStr.trim()) {
|
|
156
|
+
if (suppressBlur)
|
|
157
|
+
this.commitAndSuppressBlur(null);
|
|
158
|
+
else
|
|
159
|
+
this.commitValue(null);
|
|
160
|
+
return;
|
|
161
|
+
}
|
|
162
|
+
const parsed = parseUserInputDate(rawStr, this.getDateFormat());
|
|
163
|
+
// Convert Date to YYYY-MM-DD format; if parsing fails, pass through raw string
|
|
164
|
+
const toCommit = parsed instanceof Date ? parsed.toISOString().substring(0, 10) : rawStr;
|
|
165
|
+
if (suppressBlur)
|
|
166
|
+
this.commitAndSuppressBlur(toCommit);
|
|
167
|
+
else
|
|
168
|
+
this.commitValue(toCommit);
|
|
169
|
+
return;
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
if (suppressBlur)
|
|
173
|
+
this.commitAndSuppressBlur(this.localValue());
|
|
174
|
+
else
|
|
175
|
+
this.commitValue(this.localValue());
|
|
176
|
+
}
|
|
177
|
+
getDateFormat() {
|
|
178
|
+
return this.column?.cellEditorParams?.['dateFormat'] ?? this.column?.dateFormat ?? 'YYYY-MM-DD';
|
|
179
|
+
}
|
|
180
|
+
getCellEditorType() {
|
|
181
|
+
return this.column?.cellEditorParams?.['editorType'] ?? 'text';
|
|
182
|
+
}
|
|
183
|
+
getDatePlaceholder() {
|
|
184
|
+
return getDateInputPlaceholder(this.getDateFormat());
|
|
185
|
+
}
|
|
186
|
+
getDisplayText(value) {
|
|
187
|
+
const formatValue = this.column?.cellEditorParams?.formatValue;
|
|
188
|
+
if (formatValue)
|
|
189
|
+
return formatValue(value);
|
|
190
|
+
return value != null ? String(value) : '';
|
|
191
|
+
}
|
|
192
|
+
onCustomSelectKeyDown(e) {
|
|
193
|
+
const options = this.selectOptions();
|
|
194
|
+
switch (e.key) {
|
|
195
|
+
case 'ArrowDown':
|
|
196
|
+
e.preventDefault();
|
|
197
|
+
this.highlightedIndex.set(Math.min(this.highlightedIndex() + 1, options.length - 1));
|
|
198
|
+
this.scrollOptionIntoView(this.selectDropdown);
|
|
199
|
+
break;
|
|
200
|
+
case 'ArrowUp':
|
|
201
|
+
e.preventDefault();
|
|
202
|
+
this.highlightedIndex.set(Math.max(this.highlightedIndex() - 1, 0));
|
|
203
|
+
this.scrollOptionIntoView(this.selectDropdown);
|
|
204
|
+
break;
|
|
205
|
+
case 'Enter':
|
|
206
|
+
e.preventDefault();
|
|
207
|
+
e.stopPropagation();
|
|
208
|
+
if (options.length > 0 && this.highlightedIndex() < options.length) {
|
|
209
|
+
this.commitValue(options[this.highlightedIndex()]);
|
|
210
|
+
}
|
|
211
|
+
break;
|
|
212
|
+
case 'Tab':
|
|
213
|
+
e.preventDefault();
|
|
214
|
+
if (options.length > 0 && this.highlightedIndex() < options.length) {
|
|
215
|
+
this.commitValue(options[this.highlightedIndex()]);
|
|
216
|
+
}
|
|
217
|
+
break;
|
|
218
|
+
case 'Escape':
|
|
219
|
+
e.preventDefault();
|
|
220
|
+
e.stopPropagation();
|
|
221
|
+
this.cancel.emit();
|
|
222
|
+
break;
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
onRichSelectSearch(text) {
|
|
226
|
+
this.searchText.set(text);
|
|
227
|
+
this.highlightedIndex.set(0);
|
|
228
|
+
}
|
|
229
|
+
onRichSelectKeyDown(e) {
|
|
230
|
+
const options = this.filteredOptions();
|
|
231
|
+
switch (e.key) {
|
|
232
|
+
case 'ArrowDown':
|
|
233
|
+
e.preventDefault();
|
|
234
|
+
this.highlightedIndex.set(Math.min(this.highlightedIndex() + 1, options.length - 1));
|
|
235
|
+
this.scrollOptionIntoView(this.richSelectOptions);
|
|
236
|
+
break;
|
|
237
|
+
case 'ArrowUp':
|
|
238
|
+
e.preventDefault();
|
|
239
|
+
this.highlightedIndex.set(Math.max(this.highlightedIndex() - 1, 0));
|
|
240
|
+
this.scrollOptionIntoView(this.richSelectOptions);
|
|
241
|
+
break;
|
|
242
|
+
case 'Enter':
|
|
243
|
+
e.preventDefault();
|
|
244
|
+
e.stopPropagation();
|
|
245
|
+
if (options.length > 0 && this.highlightedIndex() < options.length) {
|
|
246
|
+
this.commitValue(options[this.highlightedIndex()]);
|
|
247
|
+
}
|
|
248
|
+
break;
|
|
249
|
+
case 'Escape':
|
|
250
|
+
e.preventDefault();
|
|
251
|
+
e.stopPropagation();
|
|
252
|
+
this.cancel.emit();
|
|
253
|
+
break;
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
onCheckboxKeyDown(e) {
|
|
257
|
+
if (e.key === 'Escape') {
|
|
258
|
+
e.preventDefault();
|
|
259
|
+
this.cancel.emit();
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
onTextBlur() {
|
|
263
|
+
if (this.shouldSkipBlur())
|
|
264
|
+
return;
|
|
265
|
+
if (this.editorType === 'date') {
|
|
266
|
+
const cellEditorType = this.getCellEditorType();
|
|
267
|
+
if (cellEditorType !== 'native') {
|
|
268
|
+
const rawStr = String(this.localValue());
|
|
269
|
+
if (!rawStr.trim()) {
|
|
270
|
+
this.commitValue(null);
|
|
271
|
+
return;
|
|
272
|
+
}
|
|
273
|
+
const parsed = parseUserInputDate(rawStr, this.getDateFormat());
|
|
274
|
+
const toCommit = parsed instanceof Date ? parsed.toISOString() : rawStr;
|
|
275
|
+
this.commitValue(toCommit);
|
|
276
|
+
return;
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
this.commitValue(this.localValue());
|
|
280
|
+
}
|
|
281
|
+
getInputStyle() {
|
|
282
|
+
const baseStyle = 'width:100%;min-width:0;padding:0;border:none;outline:none;font:inherit;font-size:var(--ogrid-cell-font-size, 13px);line-height:inherit;background:transparent;color:inherit;box-sizing:border-box;';
|
|
283
|
+
const col = this.column;
|
|
284
|
+
if (col.type === 'numeric') {
|
|
285
|
+
return baseStyle + 'text-align:right;';
|
|
286
|
+
}
|
|
287
|
+
return baseStyle;
|
|
288
|
+
}
|
|
289
|
+
/** Position a dropdown using fixed positioning to escape overflow clipping. */
|
|
290
|
+
positionFixedDropdown(wrapperRef, dropdownRef) {
|
|
291
|
+
const wrapper = wrapperRef?.nativeElement;
|
|
292
|
+
const dropdown = dropdownRef?.nativeElement;
|
|
293
|
+
if (!wrapper || !dropdown)
|
|
294
|
+
return;
|
|
295
|
+
const rect = wrapper.getBoundingClientRect();
|
|
296
|
+
const maxH = 200;
|
|
297
|
+
const spaceBelow = window.innerHeight - rect.bottom;
|
|
298
|
+
const flipUp = spaceBelow < maxH && rect.top > spaceBelow;
|
|
299
|
+
dropdown.style.position = 'fixed';
|
|
300
|
+
dropdown.style.left = `${rect.left}px`;
|
|
301
|
+
dropdown.style.width = `${rect.width}px`;
|
|
302
|
+
dropdown.style.maxHeight = `${maxH}px`;
|
|
303
|
+
dropdown.style.zIndex = '9999';
|
|
304
|
+
dropdown.style.right = 'auto';
|
|
305
|
+
dropdown.style.textAlign = 'left';
|
|
306
|
+
if (flipUp) {
|
|
307
|
+
dropdown.style.top = 'auto';
|
|
308
|
+
dropdown.style.bottom = `${window.innerHeight - rect.top}px`;
|
|
309
|
+
}
|
|
310
|
+
else {
|
|
311
|
+
dropdown.style.top = `${rect.bottom}px`;
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
/** Scroll the highlighted option into view within a dropdown element. */
|
|
315
|
+
scrollOptionIntoView(dropdownRef) {
|
|
316
|
+
setTimeout(() => {
|
|
317
|
+
const dropdown = dropdownRef?.nativeElement;
|
|
318
|
+
if (!dropdown)
|
|
319
|
+
return;
|
|
320
|
+
const highlighted = dropdown.children[this.highlightedIndex()];
|
|
321
|
+
highlighted?.scrollIntoView({ block: 'nearest' });
|
|
322
|
+
});
|
|
323
|
+
}
|
|
324
|
+
static { this.ɵfac = function BaseInlineCellEditorComponent_Factory(__ngFactoryType__) { return new (__ngFactoryType__ || BaseInlineCellEditorComponent)(); }; }
|
|
325
|
+
static { this.ɵdir = /*@__PURE__*/ i0.ɵɵdefineDirective({ type: BaseInlineCellEditorComponent, viewQuery: function BaseInlineCellEditorComponent_Query(rf, ctx) { if (rf & 1) {
|
|
326
|
+
i0.ɵɵviewQuery(_c0, 5)(_c1, 5)(_c2, 5)(_c3, 5)(_c4, 5)(_c5, 5)(_c6, 5);
|
|
327
|
+
} if (rf & 2) {
|
|
328
|
+
let _t;
|
|
329
|
+
i0.ɵɵqueryRefresh(_t = i0.ɵɵloadQuery()) && (ctx.inputEl = _t.first);
|
|
330
|
+
i0.ɵɵqueryRefresh(_t = i0.ɵɵloadQuery()) && (ctx.selectWrapper = _t.first);
|
|
331
|
+
i0.ɵɵqueryRefresh(_t = i0.ɵɵloadQuery()) && (ctx.selectDropdown = _t.first);
|
|
332
|
+
i0.ɵɵqueryRefresh(_t = i0.ɵɵloadQuery()) && (ctx.richSelectWrapper = _t.first);
|
|
333
|
+
i0.ɵɵqueryRefresh(_t = i0.ɵɵloadQuery()) && (ctx.richSelectInput = _t.first);
|
|
334
|
+
i0.ɵɵqueryRefresh(_t = i0.ɵɵloadQuery()) && (ctx.richSelectDropdown = _t.first);
|
|
335
|
+
i0.ɵɵqueryRefresh(_t = i0.ɵɵloadQuery()) && (ctx.richSelectOptions = _t.first);
|
|
336
|
+
} }, inputs: { value: "value", item: "item", column: "column", rowIndex: "rowIndex", editorType: "editorType" }, outputs: { commit: "commit", cancel: "cancel" }, features: [i0.ɵɵNgOnChangesFeature] }); }
|
|
337
|
+
}
|
|
338
|
+
(() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(BaseInlineCellEditorComponent, [{
|
|
339
|
+
type: Directive
|
|
340
|
+
}], null, { value: [{
|
|
341
|
+
type: Input,
|
|
342
|
+
args: [{ required: true }]
|
|
343
|
+
}], item: [{
|
|
344
|
+
type: Input,
|
|
345
|
+
args: [{ required: true }]
|
|
346
|
+
}], column: [{
|
|
347
|
+
type: Input,
|
|
348
|
+
args: [{ required: true }]
|
|
349
|
+
}], rowIndex: [{
|
|
350
|
+
type: Input,
|
|
351
|
+
args: [{ required: true }]
|
|
352
|
+
}], editorType: [{
|
|
353
|
+
type: Input,
|
|
354
|
+
args: [{ required: true }]
|
|
355
|
+
}], commit: [{
|
|
356
|
+
type: Output
|
|
357
|
+
}], cancel: [{
|
|
358
|
+
type: Output
|
|
359
|
+
}], inputEl: [{
|
|
360
|
+
type: ViewChild,
|
|
361
|
+
args: ['inputEl']
|
|
362
|
+
}], selectWrapper: [{
|
|
363
|
+
type: ViewChild,
|
|
364
|
+
args: ['selectWrapper']
|
|
365
|
+
}], selectDropdown: [{
|
|
366
|
+
type: ViewChild,
|
|
367
|
+
args: ['selectDropdown']
|
|
368
|
+
}], richSelectWrapper: [{
|
|
369
|
+
type: ViewChild,
|
|
370
|
+
args: ['richSelectWrapper']
|
|
371
|
+
}], richSelectInput: [{
|
|
372
|
+
type: ViewChild,
|
|
373
|
+
args: ['richSelectInput']
|
|
374
|
+
}], richSelectDropdown: [{
|
|
375
|
+
type: ViewChild,
|
|
376
|
+
args: ['richSelectDropdown']
|
|
377
|
+
}], richSelectOptions: [{
|
|
378
|
+
type: ViewChild,
|
|
379
|
+
args: ['richSelectOptions']
|
|
380
|
+
}] }); })();
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Base class for OGrid top-level components (Material, Radix).
|
|
3
|
+
* Contains all shared TypeScript logic. Subclasses provide a @Component
|
|
4
|
+
* decorator with their own selector, template, and imports.
|
|
5
|
+
*/
|
|
6
|
+
import { Directive, Input, signal, effect, inject } from '@angular/core';
|
|
7
|
+
import { OGridService } from '../services/ogrid.service';
|
|
8
|
+
import * as i0 from "@angular/core";
|
|
9
|
+
export class BaseOGridComponent {
|
|
10
|
+
set props(value) {
|
|
11
|
+
this.propsSignal.set(value);
|
|
12
|
+
}
|
|
13
|
+
constructor() {
|
|
14
|
+
this.propsSignal = signal(undefined, ...(ngDevMode ? [{ debugName: "propsSignal" }] : []));
|
|
15
|
+
this.ogridService = inject(OGridService);
|
|
16
|
+
effect(() => {
|
|
17
|
+
const p = this.propsSignal();
|
|
18
|
+
if (p)
|
|
19
|
+
this.ogridService.configure(p);
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
get showToolbar() {
|
|
23
|
+
return this.ogridService.columnChooserPlacement() === 'toolbar' || this.ogridService.toolbar() != null || this.ogridService.fullScreen();
|
|
24
|
+
}
|
|
25
|
+
onPageSizeChange(size) {
|
|
26
|
+
this.ogridService.pagination().setPageSize(size);
|
|
27
|
+
}
|
|
28
|
+
static { this.ɵfac = function BaseOGridComponent_Factory(__ngFactoryType__) { return new (__ngFactoryType__ || BaseOGridComponent)(); }; }
|
|
29
|
+
static { this.ɵdir = /*@__PURE__*/ i0.ɵɵdefineDirective({ type: BaseOGridComponent, inputs: { props: "props" } }); }
|
|
30
|
+
}
|
|
31
|
+
(() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(BaseOGridComponent, [{
|
|
32
|
+
type: Directive
|
|
33
|
+
}], () => [], { props: [{
|
|
34
|
+
type: Input,
|
|
35
|
+
args: [{ required: true }]
|
|
36
|
+
}] }); })();
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { Directive, signal, computed, Input, Output, EventEmitter } from '@angular/core';
|
|
2
|
+
import { getPaginationViewModel } from '@alaarab/ogrid-core';
|
|
3
|
+
import * as i0 from "@angular/core";
|
|
4
|
+
/**
|
|
5
|
+
* Abstract base class containing all shared TypeScript logic for PaginationControls components.
|
|
6
|
+
* Framework-specific UI packages extend this with their templates and style overrides.
|
|
7
|
+
*
|
|
8
|
+
* Subclasses must:
|
|
9
|
+
* 1. Provide a @Component decorator with template and styles
|
|
10
|
+
*
|
|
11
|
+
* Uses @Input setter + signal pattern so computed() can track dependencies.
|
|
12
|
+
* (Plain @Input properties are NOT tracked by computed() - only signals are.)
|
|
13
|
+
*/
|
|
14
|
+
export class BasePaginationControlsComponent {
|
|
15
|
+
constructor() {
|
|
16
|
+
this._currentPage = signal(1, ...(ngDevMode ? [{ debugName: "_currentPage" }] : []));
|
|
17
|
+
this._pageSize = signal(25, ...(ngDevMode ? [{ debugName: "_pageSize" }] : []));
|
|
18
|
+
this._totalCount = signal(0, ...(ngDevMode ? [{ debugName: "_totalCount" }] : []));
|
|
19
|
+
this._pageSizeOptions = signal(undefined, ...(ngDevMode ? [{ debugName: "_pageSizeOptions" }] : []));
|
|
20
|
+
this._entityLabelPlural = signal('items', ...(ngDevMode ? [{ debugName: "_entityLabelPlural" }] : []));
|
|
21
|
+
this.pageChange = new EventEmitter();
|
|
22
|
+
this.pageSizeChange = new EventEmitter();
|
|
23
|
+
this.labelPlural = computed(() => this._entityLabelPlural() ?? 'items', ...(ngDevMode ? [{ debugName: "labelPlural" }] : []));
|
|
24
|
+
this.vm = computed(() => {
|
|
25
|
+
const opts = this._pageSizeOptions();
|
|
26
|
+
return getPaginationViewModel(this._currentPage(), this._pageSize(), this._totalCount(), opts ? { pageSizeOptions: opts } : undefined);
|
|
27
|
+
}, ...(ngDevMode ? [{ debugName: "vm" }] : []));
|
|
28
|
+
}
|
|
29
|
+
set currentPage(v) { this._currentPage.set(v); }
|
|
30
|
+
get currentPage() { return this._currentPage(); }
|
|
31
|
+
set pageSize(v) { this._pageSize.set(v); }
|
|
32
|
+
get pageSize() { return this._pageSize(); }
|
|
33
|
+
set totalCount(v) { this._totalCount.set(v); }
|
|
34
|
+
get totalCount() { return this._totalCount(); }
|
|
35
|
+
set pageSizeOptions(v) { this._pageSizeOptions.set(v); }
|
|
36
|
+
get pageSizeOptions() { return this._pageSizeOptions(); }
|
|
37
|
+
set entityLabelPlural(v) { this._entityLabelPlural.set(v); }
|
|
38
|
+
get entityLabelPlural() { return this._entityLabelPlural(); }
|
|
39
|
+
onPageSizeSelect(event) {
|
|
40
|
+
const value = Number(event.target.value);
|
|
41
|
+
this.pageSizeChange.emit(value);
|
|
42
|
+
}
|
|
43
|
+
onPageSizeChange(value) {
|
|
44
|
+
this.pageSizeChange.emit(Number(value));
|
|
45
|
+
}
|
|
46
|
+
static { this.ɵfac = function BasePaginationControlsComponent_Factory(__ngFactoryType__) { return new (__ngFactoryType__ || BasePaginationControlsComponent)(); }; }
|
|
47
|
+
static { this.ɵdir = /*@__PURE__*/ i0.ɵɵdefineDirective({ type: BasePaginationControlsComponent, inputs: { currentPage: "currentPage", pageSize: "pageSize", totalCount: "totalCount", pageSizeOptions: "pageSizeOptions", entityLabelPlural: "entityLabelPlural" }, outputs: { pageChange: "pageChange", pageSizeChange: "pageSizeChange" } }); }
|
|
48
|
+
}
|
|
49
|
+
(() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(BasePaginationControlsComponent, [{
|
|
50
|
+
type: Directive
|
|
51
|
+
}], null, { currentPage: [{
|
|
52
|
+
type: Input,
|
|
53
|
+
args: [{ required: true }]
|
|
54
|
+
}], pageSize: [{
|
|
55
|
+
type: Input,
|
|
56
|
+
args: [{ required: true }]
|
|
57
|
+
}], totalCount: [{
|
|
58
|
+
type: Input,
|
|
59
|
+
args: [{ required: true }]
|
|
60
|
+
}], pageSizeOptions: [{
|
|
61
|
+
type: Input
|
|
62
|
+
}], entityLabelPlural: [{
|
|
63
|
+
type: Input
|
|
64
|
+
}], pageChange: [{
|
|
65
|
+
type: Output
|
|
66
|
+
}], pageSizeChange: [{
|
|
67
|
+
type: Output
|
|
68
|
+
}] }); })();
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import { Directive, Input, ViewChild, Injector, EnvironmentInjector, inject, signal, effect, createComponent } from '@angular/core';
|
|
2
|
+
import * as i0 from "@angular/core";
|
|
3
|
+
const _c0 = ["anchorEl"];
|
|
4
|
+
const _c1 = ["editorContainer"];
|
|
5
|
+
/**
|
|
6
|
+
* Shared popover cell editor template used by all Angular UI packages.
|
|
7
|
+
*/
|
|
8
|
+
export const POPOVER_CELL_EDITOR_TEMPLATE = `
|
|
9
|
+
<div #anchorEl
|
|
10
|
+
class="ogrid-popover-anchor"
|
|
11
|
+
[attr.data-row-index]="rowIndex"
|
|
12
|
+
[attr.data-col-index]="globalColIndex"
|
|
13
|
+
>
|
|
14
|
+
{{ displayValue }}
|
|
15
|
+
</div>
|
|
16
|
+
@if (showEditor()) {
|
|
17
|
+
<div class="ogrid-popover-editor-overlay" (click)="handleOverlayClick()">
|
|
18
|
+
<div class="ogrid-popover-editor-content" #editorContainer></div>
|
|
19
|
+
</div>
|
|
20
|
+
}
|
|
21
|
+
`;
|
|
22
|
+
/**
|
|
23
|
+
* Shared overlay + content styles for popover cell editors.
|
|
24
|
+
* Subclasses provide their own .ogrid-popover-anchor styles.
|
|
25
|
+
*/
|
|
26
|
+
export const POPOVER_CELL_EDITOR_OVERLAY_STYLES = `
|
|
27
|
+
:host { display: contents; }
|
|
28
|
+
.ogrid-popover-editor-overlay {
|
|
29
|
+
position: fixed; inset: 0; z-index: 1000;
|
|
30
|
+
background: rgba(0,0,0,0.3);
|
|
31
|
+
display: flex; align-items: center; justify-content: center;
|
|
32
|
+
}
|
|
33
|
+
.ogrid-popover-editor-content {
|
|
34
|
+
background: var(--ogrid-bg, #ffffff); border-radius: 4px; padding: 16px;
|
|
35
|
+
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
|
|
36
|
+
max-width: 90vw; max-height: 90vh; overflow: auto;
|
|
37
|
+
color: var(--ogrid-fg, rgba(0, 0, 0, 0.87));
|
|
38
|
+
}
|
|
39
|
+
`;
|
|
40
|
+
/**
|
|
41
|
+
* Abstract base class for Angular popover cell editors.
|
|
42
|
+
* Contains all shared inputs, ViewChild refs, effects, and overlay click handling.
|
|
43
|
+
*
|
|
44
|
+
* Subclasses only need a @Component decorator with selector, template, and
|
|
45
|
+
* framework-specific .ogrid-popover-anchor CSS styles.
|
|
46
|
+
*/
|
|
47
|
+
export class BasePopoverCellEditorComponent {
|
|
48
|
+
constructor() {
|
|
49
|
+
this.injector = inject(Injector);
|
|
50
|
+
this.envInjector = inject(EnvironmentInjector);
|
|
51
|
+
this.showEditor = signal(false, ...(ngDevMode ? [{ debugName: "showEditor" }] : []));
|
|
52
|
+
// Show editor after anchor is rendered
|
|
53
|
+
effect(() => {
|
|
54
|
+
const anchor = this.anchorRef;
|
|
55
|
+
if (anchor) {
|
|
56
|
+
setTimeout(() => this.showEditor.set(true), 0);
|
|
57
|
+
}
|
|
58
|
+
});
|
|
59
|
+
// Render custom editor component when container is available.
|
|
60
|
+
// Angular's effect() ignores return values - use onCleanup() for cleanup.
|
|
61
|
+
effect((onCleanup) => {
|
|
62
|
+
const container = this.editorContainerRef;
|
|
63
|
+
const props = this.editorProps;
|
|
64
|
+
const col = this.column;
|
|
65
|
+
if (!container || !this.showEditor() || typeof col.cellEditor !== 'function')
|
|
66
|
+
return;
|
|
67
|
+
const EditorComponent = col.cellEditor;
|
|
68
|
+
const componentRef = createComponent(EditorComponent, {
|
|
69
|
+
environmentInjector: this.envInjector,
|
|
70
|
+
elementInjector: this.injector,
|
|
71
|
+
});
|
|
72
|
+
// Pass props to component instance
|
|
73
|
+
Object.assign(componentRef.instance, props);
|
|
74
|
+
componentRef.changeDetectorRef.detectChanges();
|
|
75
|
+
// Append to DOM
|
|
76
|
+
container.nativeElement.appendChild(componentRef.location.nativeElement);
|
|
77
|
+
// Cleanup when effect re-runs or component is destroyed
|
|
78
|
+
onCleanup(() => componentRef.destroy());
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
handleOverlayClick() {
|
|
82
|
+
this.onCancel();
|
|
83
|
+
}
|
|
84
|
+
static { this.ɵfac = function BasePopoverCellEditorComponent_Factory(__ngFactoryType__) { return new (__ngFactoryType__ || BasePopoverCellEditorComponent)(); }; }
|
|
85
|
+
static { this.ɵdir = /*@__PURE__*/ i0.ɵɵdefineDirective({ type: BasePopoverCellEditorComponent, viewQuery: function BasePopoverCellEditorComponent_Query(rf, ctx) { if (rf & 1) {
|
|
86
|
+
i0.ɵɵviewQuery(_c0, 5)(_c1, 5);
|
|
87
|
+
} if (rf & 2) {
|
|
88
|
+
let _t;
|
|
89
|
+
i0.ɵɵqueryRefresh(_t = i0.ɵɵloadQuery()) && (ctx.anchorRef = _t.first);
|
|
90
|
+
i0.ɵɵqueryRefresh(_t = i0.ɵɵloadQuery()) && (ctx.editorContainerRef = _t.first);
|
|
91
|
+
} }, inputs: { item: "item", column: "column", rowIndex: "rowIndex", globalColIndex: "globalColIndex", displayValue: "displayValue", editorProps: "editorProps", onCancel: "onCancel" } }); }
|
|
92
|
+
}
|
|
93
|
+
(() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(BasePopoverCellEditorComponent, [{
|
|
94
|
+
type: Directive
|
|
95
|
+
}], () => [], { item: [{
|
|
96
|
+
type: Input,
|
|
97
|
+
args: [{ required: true }]
|
|
98
|
+
}], column: [{
|
|
99
|
+
type: Input,
|
|
100
|
+
args: [{ required: true }]
|
|
101
|
+
}], rowIndex: [{
|
|
102
|
+
type: Input,
|
|
103
|
+
args: [{ required: true }]
|
|
104
|
+
}], globalColIndex: [{
|
|
105
|
+
type: Input,
|
|
106
|
+
args: [{ required: true }]
|
|
107
|
+
}], displayValue: [{
|
|
108
|
+
type: Input,
|
|
109
|
+
args: [{ required: true }]
|
|
110
|
+
}], editorProps: [{
|
|
111
|
+
type: Input,
|
|
112
|
+
args: [{ required: true }]
|
|
113
|
+
}], onCancel: [{
|
|
114
|
+
type: Input,
|
|
115
|
+
args: [{ required: true }]
|
|
116
|
+
}], anchorRef: [{
|
|
117
|
+
type: ViewChild,
|
|
118
|
+
args: ['anchorEl']
|
|
119
|
+
}], editorContainerRef: [{
|
|
120
|
+
type: ViewChild,
|
|
121
|
+
args: ['editorContainer']
|
|
122
|
+
}] }); })();
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { Component, Input, Output, EventEmitter, ChangeDetectionStrategy } from '@angular/core';
|
|
2
|
+
import { NgTemplateOutlet } from '@angular/common';
|
|
3
|
+
import * as i0 from "@angular/core";
|
|
4
|
+
function EmptyStateComponent_Conditional_0_Template(rf, ctx) { if (rf & 1) {
|
|
5
|
+
i0.ɵɵelementContainer(0, 0);
|
|
6
|
+
} if (rf & 2) {
|
|
7
|
+
const ctx_r0 = i0.ɵɵnextContext();
|
|
8
|
+
i0.ɵɵproperty("ngTemplateOutlet", ctx_r0.render);
|
|
9
|
+
} }
|
|
10
|
+
function EmptyStateComponent_Conditional_1_Template(rf, ctx) { if (rf & 1) {
|
|
11
|
+
i0.ɵɵtext(0);
|
|
12
|
+
} if (rf & 2) {
|
|
13
|
+
const ctx_r0 = i0.ɵɵnextContext();
|
|
14
|
+
i0.ɵɵtextInterpolate1(" ", ctx_r0.message, " ");
|
|
15
|
+
} }
|
|
16
|
+
function EmptyStateComponent_Conditional_2_Template(rf, ctx) { if (rf & 1) {
|
|
17
|
+
const _r2 = i0.ɵɵgetCurrentView();
|
|
18
|
+
i0.ɵɵtext(0, " No items match your current filters. Try adjusting your search or ");
|
|
19
|
+
i0.ɵɵelementStart(1, "button", 1);
|
|
20
|
+
i0.ɵɵlistener("click", function EmptyStateComponent_Conditional_2_Template_button_click_1_listener() { i0.ɵɵrestoreView(_r2); const ctx_r0 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r0.clearAll.emit()); });
|
|
21
|
+
i0.ɵɵtext(2, " clear all filters ");
|
|
22
|
+
i0.ɵɵelementEnd();
|
|
23
|
+
i0.ɵɵtext(3, " to see all items. ");
|
|
24
|
+
} }
|
|
25
|
+
function EmptyStateComponent_Conditional_3_Template(rf, ctx) { if (rf & 1) {
|
|
26
|
+
i0.ɵɵtext(0, " There are no items available at this time. ");
|
|
27
|
+
} }
|
|
28
|
+
export class EmptyStateComponent {
|
|
29
|
+
constructor() {
|
|
30
|
+
this.message = undefined;
|
|
31
|
+
this.hasActiveFilters = false;
|
|
32
|
+
this.render = undefined;
|
|
33
|
+
this.clearAll = new EventEmitter();
|
|
34
|
+
}
|
|
35
|
+
static { this.ɵfac = function EmptyStateComponent_Factory(__ngFactoryType__) { return new (__ngFactoryType__ || EmptyStateComponent)(); }; }
|
|
36
|
+
static { this.ɵcmp = /*@__PURE__*/ i0.ɵɵdefineComponent({ type: EmptyStateComponent, selectors: [["ogrid-empty-state"]], inputs: { message: "message", hasActiveFilters: "hasActiveFilters", render: "render" }, outputs: { clearAll: "clearAll" }, decls: 4, vars: 1, consts: [[3, "ngTemplateOutlet"], ["type", "button", 1, "ogrid-empty-state-clear-btn", 3, "click"]], template: function EmptyStateComponent_Template(rf, ctx) { if (rf & 1) {
|
|
37
|
+
i0.ɵɵconditionalCreate(0, EmptyStateComponent_Conditional_0_Template, 1, 1, "ng-container", 0)(1, EmptyStateComponent_Conditional_1_Template, 1, 1)(2, EmptyStateComponent_Conditional_2_Template, 4, 0)(3, EmptyStateComponent_Conditional_3_Template, 1, 0);
|
|
38
|
+
} if (rf & 2) {
|
|
39
|
+
i0.ɵɵconditional(ctx.render ? 0 : ctx.message ? 1 : ctx.hasActiveFilters ? 2 : 3);
|
|
40
|
+
} }, dependencies: [NgTemplateOutlet], styles: [".ogrid-empty-state-clear-btn[_ngcontent-%COMP%] {\n background: none; border: none; color: inherit;\n text-decoration: underline; cursor: pointer; padding: 0; font: inherit;\n }"], changeDetection: 0 }); }
|
|
41
|
+
}
|
|
42
|
+
(() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(EmptyStateComponent, [{
|
|
43
|
+
type: Component,
|
|
44
|
+
args: [{ selector: 'ogrid-empty-state', standalone: true, changeDetection: ChangeDetectionStrategy.OnPush, imports: [NgTemplateOutlet], template: `
|
|
45
|
+
@if (render) {
|
|
46
|
+
<ng-container [ngTemplateOutlet]="render"></ng-container>
|
|
47
|
+
} @else if (message) {
|
|
48
|
+
{{ message }}
|
|
49
|
+
} @else if (hasActiveFilters) {
|
|
50
|
+
No items match your current filters. Try adjusting your search or
|
|
51
|
+
<button type="button" (click)="clearAll.emit()" class="ogrid-empty-state-clear-btn">
|
|
52
|
+
clear all filters
|
|
53
|
+
</button>
|
|
54
|
+
to see all items.
|
|
55
|
+
} @else {
|
|
56
|
+
There are no items available at this time.
|
|
57
|
+
}
|
|
58
|
+
`, styles: ["\n .ogrid-empty-state-clear-btn {\n background: none; border: none; color: inherit;\n text-decoration: underline; cursor: pointer; padding: 0; font: inherit;\n }\n "] }]
|
|
59
|
+
}], null, { message: [{
|
|
60
|
+
type: Input
|
|
61
|
+
}], hasActiveFilters: [{
|
|
62
|
+
type: Input
|
|
63
|
+
}], render: [{
|
|
64
|
+
type: Input
|
|
65
|
+
}], clearAll: [{
|
|
66
|
+
type: Output
|
|
67
|
+
}] }); })();
|
|
68
|
+
(() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassDebugInfo(EmptyStateComponent, { className: "EmptyStateComponent", filePath: "components/empty-state.component.ts", lineNumber: 31 }); })();
|