@alaarab/ogrid-angular-material 2.0.2

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.
@@ -0,0 +1,143 @@
1
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
2
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
3
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
4
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
5
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
6
+ };
7
+ import { Component, input, output, signal, computed, ChangeDetectionStrategy } from '@angular/core';
8
+ /**
9
+ * Column visibility chooser dropdown using Angular Material styling.
10
+ * Standalone component with inline template.
11
+ */
12
+ let ColumnChooserComponent = class ColumnChooserComponent {
13
+ constructor() {
14
+ this.columns = input.required();
15
+ this.visibleColumns = input.required();
16
+ this.visibilityChange = output();
17
+ this.isOpen = signal(false);
18
+ this.visibleCount = computed(() => this.visibleColumns().size);
19
+ this.totalCount = computed(() => this.columns().length);
20
+ }
21
+ toggle() {
22
+ this.isOpen.update((v) => !v);
23
+ }
24
+ onCheckboxChange(columnKey, event) {
25
+ const checked = event.target.checked;
26
+ this.visibilityChange.emit({ columnKey, visible: checked });
27
+ }
28
+ selectAll() {
29
+ for (const col of this.columns()) {
30
+ if (!this.visibleColumns().has(col.columnId)) {
31
+ this.visibilityChange.emit({ columnKey: col.columnId, visible: true });
32
+ }
33
+ }
34
+ }
35
+ clearAll() {
36
+ for (const col of this.columns()) {
37
+ if (this.visibleColumns().has(col.columnId)) {
38
+ this.visibilityChange.emit({ columnKey: col.columnId, visible: false });
39
+ }
40
+ }
41
+ }
42
+ onDocumentClick(event) {
43
+ // Close dropdown when clicking outside
44
+ const el = event.target;
45
+ if (!el.closest('ogrid-column-chooser')) {
46
+ this.isOpen.set(false);
47
+ }
48
+ }
49
+ };
50
+ ColumnChooserComponent = __decorate([
51
+ Component({
52
+ selector: 'ogrid-column-chooser',
53
+ standalone: true,
54
+ changeDetection: ChangeDetectionStrategy.OnPush,
55
+ template: `
56
+ <div class="ogrid-column-chooser">
57
+ <button
58
+ class="ogrid-column-chooser__trigger"
59
+ (click)="toggle()"
60
+ [attr.aria-expanded]="isOpen()"
61
+ aria-haspopup="listbox"
62
+ >
63
+ &#9638; Column Visibility ({{ visibleCount() }} of {{ totalCount() }})
64
+ <span class="ogrid-column-chooser__caret">{{ isOpen() ? '&#9650;' : '&#9660;' }}</span>
65
+ </button>
66
+
67
+ @if (isOpen()) {
68
+ <div class="ogrid-column-chooser__dropdown" (click)="$event.stopPropagation()">
69
+ <div class="ogrid-column-chooser__header">
70
+ Select Columns ({{ visibleCount() }} of {{ totalCount() }})
71
+ </div>
72
+
73
+ <div class="ogrid-column-chooser__list">
74
+ @for (col of columns(); track col.columnId) {
75
+ <label class="ogrid-column-chooser__item">
76
+ <input
77
+ type="checkbox"
78
+ [checked]="visibleColumns().has(col.columnId)"
79
+ (change)="onCheckboxChange(col.columnId, $event)"
80
+ />
81
+ <span>{{ col.name }}</span>
82
+ </label>
83
+ }
84
+ </div>
85
+
86
+ <div class="ogrid-column-chooser__footer">
87
+ <button class="ogrid-column-chooser__btn" (click)="clearAll()">Clear All</button>
88
+ <button class="ogrid-column-chooser__btn ogrid-column-chooser__btn--primary" (click)="selectAll()">Select All</button>
89
+ </div>
90
+ </div>
91
+ }
92
+ </div>
93
+ `,
94
+ styles: [`
95
+ :host { display: inline-flex; position: relative; }
96
+ .ogrid-column-chooser { position: relative; }
97
+ .ogrid-column-chooser__trigger {
98
+ display: inline-flex; align-items: center; gap: 6px;
99
+ padding: 6px 12px; border: 1px solid rgba(0,0,0,0.23); border-radius: 4px;
100
+ background: transparent; cursor: pointer; font-size: 14px; font-weight: 600;
101
+ text-transform: none; white-space: nowrap;
102
+ }
103
+ .ogrid-column-chooser__caret { font-size: 10px; }
104
+ .ogrid-column-chooser__dropdown {
105
+ position: absolute; top: 100%; right: 0; z-index: 10;
106
+ min-width: 220px; margin-top: 4px;
107
+ background: #fff; border: 1px solid rgba(0,0,0,0.12); border-radius: 4px;
108
+ box-shadow: 0 2px 8px rgba(0,0,0,0.15);
109
+ }
110
+ .ogrid-column-chooser__header {
111
+ padding: 8px 12px; font-size: 14px; font-weight: 600;
112
+ border-bottom: 1px solid rgba(0,0,0,0.12); background: #fafafa;
113
+ }
114
+ .ogrid-column-chooser__list {
115
+ max-height: 320px; overflow-y: auto; padding: 4px 0;
116
+ }
117
+ .ogrid-column-chooser__item {
118
+ display: flex; align-items: center; gap: 8px;
119
+ padding: 4px 12px; min-height: 32px; cursor: pointer; font-size: 14px;
120
+ }
121
+ .ogrid-column-chooser__item:hover { background: rgba(0,0,0,0.04); }
122
+ .ogrid-column-chooser__footer {
123
+ display: flex; justify-content: flex-end; gap: 8px;
124
+ padding: 8px 12px; border-top: 1px solid rgba(0,0,0,0.12); background: #fafafa;
125
+ }
126
+ .ogrid-column-chooser__btn {
127
+ padding: 4px 12px; border: none; border-radius: 4px;
128
+ background: transparent; cursor: pointer; font-size: 13px; text-transform: none;
129
+ }
130
+ .ogrid-column-chooser__btn:hover { background: rgba(0,0,0,0.04); }
131
+ .ogrid-column-chooser__btn--primary {
132
+ background: var(--mat-sys-primary, #1976d2); color: #fff;
133
+ }
134
+ .ogrid-column-chooser__btn--primary:hover {
135
+ background: var(--mat-sys-primary, #1565c0);
136
+ }
137
+ `],
138
+ host: {
139
+ '(document:click)': 'onDocumentClick($event)',
140
+ },
141
+ })
142
+ ], ColumnChooserComponent);
143
+ export { ColumnChooserComponent };
@@ -0,0 +1,483 @@
1
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
2
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
3
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
4
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
5
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
6
+ };
7
+ import { Component, input, signal, computed, ChangeDetectionStrategy, viewChild, } from '@angular/core';
8
+ /**
9
+ * Column header filter component with sort + filter icon + popover.
10
+ * Standalone component with inline template.
11
+ */
12
+ let ColumnHeaderFilterComponent = class ColumnHeaderFilterComponent {
13
+ constructor() {
14
+ this.columnKey = input.required();
15
+ this.columnName = input.required();
16
+ this.filterType = input.required();
17
+ this.isSorted = input(false);
18
+ this.isSortedDescending = input(false);
19
+ this.onSort = input(undefined);
20
+ this.selectedValues = input(undefined);
21
+ this.onFilterChange = input(undefined);
22
+ this.options = input(undefined);
23
+ this.isLoadingOptions = input(false);
24
+ this.textValue = input('');
25
+ this.onTextChange = input(undefined);
26
+ this.selectedUser = input(undefined);
27
+ this.onUserChange = input(undefined);
28
+ this.peopleSearch = input(undefined);
29
+ this.dateValue = input(undefined);
30
+ this.onDateChange = input(undefined);
31
+ this.headerEl = viewChild('headerEl');
32
+ // Internal state
33
+ this.isFilterOpen = signal(false);
34
+ this.tempTextValue = signal('');
35
+ this.searchText = signal('');
36
+ this.tempSelected = signal(new Set());
37
+ this.peopleSearchText = signal('');
38
+ this.peopleSuggestions = signal([]);
39
+ this.isPeopleLoading = signal(false);
40
+ this.tempDateFrom = signal('');
41
+ this.tempDateTo = signal('');
42
+ // Popover position
43
+ this.popoverTop = signal(0);
44
+ this.popoverLeft = signal(0);
45
+ this.peopleDebounceTimer = null;
46
+ this.hasActiveFilter = computed(() => {
47
+ const ft = this.filterType();
48
+ if (ft === 'text')
49
+ return !!this.textValue();
50
+ if (ft === 'multiSelect')
51
+ return (this.selectedValues()?.length ?? 0) > 0;
52
+ if (ft === 'people')
53
+ return this.selectedUser() != null;
54
+ if (ft === 'date')
55
+ return this.dateValue() != null;
56
+ return false;
57
+ });
58
+ this.filteredOptions = computed(() => {
59
+ const opts = this.options() ?? [];
60
+ const search = this.searchText().toLowerCase().trim();
61
+ if (!search)
62
+ return opts;
63
+ return opts.filter((o) => o.toLowerCase().includes(search));
64
+ });
65
+ }
66
+ asInputValue(event) {
67
+ return event.target.value;
68
+ }
69
+ toggleFilter(event) {
70
+ event.stopPropagation();
71
+ if (this.isFilterOpen()) {
72
+ this.isFilterOpen.set(false);
73
+ return;
74
+ }
75
+ // Initialize temp state from current values
76
+ this.tempTextValue.set(this.textValue());
77
+ this.tempSelected.set(new Set(this.selectedValues() ?? []));
78
+ this.searchText.set('');
79
+ this.peopleSearchText.set('');
80
+ this.peopleSuggestions.set([]);
81
+ const dv = this.dateValue();
82
+ this.tempDateFrom.set(dv?.from ?? '');
83
+ this.tempDateTo.set(dv?.to ?? '');
84
+ // Compute popover position
85
+ const el = this.headerEl()?.nativeElement;
86
+ if (el) {
87
+ const rect = el.getBoundingClientRect();
88
+ this.popoverTop.set(rect.bottom + 4);
89
+ this.popoverLeft.set(rect.left);
90
+ }
91
+ this.isFilterOpen.set(true);
92
+ }
93
+ // --- Text filter ---
94
+ onTextKeydown(event) {
95
+ event.stopPropagation();
96
+ if (event.key === 'Enter') {
97
+ event.preventDefault();
98
+ this.handleTextApply();
99
+ }
100
+ }
101
+ handleTextApply() {
102
+ this.onTextChange()(this.tempTextValue());
103
+ this.isFilterOpen.set(false);
104
+ }
105
+ handleTextClear() {
106
+ this.tempTextValue.set('');
107
+ this.onTextChange()('');
108
+ this.isFilterOpen.set(false);
109
+ }
110
+ // --- MultiSelect filter ---
111
+ handleCheckboxChange(option, event) {
112
+ const checked = event.target.checked;
113
+ this.tempSelected.update((s) => {
114
+ const next = new Set(s);
115
+ if (checked)
116
+ next.add(option);
117
+ else
118
+ next.delete(option);
119
+ return next;
120
+ });
121
+ }
122
+ handleSelectAllFiltered() {
123
+ this.tempSelected.update((s) => {
124
+ const next = new Set(s);
125
+ for (const opt of this.filteredOptions())
126
+ next.add(opt);
127
+ return next;
128
+ });
129
+ }
130
+ handleClearSelection() {
131
+ this.tempSelected.set(new Set());
132
+ }
133
+ handleApplyMultiSelect() {
134
+ this.onFilterChange()(Array.from(this.tempSelected()));
135
+ this.isFilterOpen.set(false);
136
+ }
137
+ // --- People filter ---
138
+ onPeopleSearchInput(event) {
139
+ const value = event.target.value;
140
+ this.peopleSearchText.set(value);
141
+ if (this.peopleDebounceTimer)
142
+ clearTimeout(this.peopleDebounceTimer);
143
+ const query = value.trim();
144
+ if (!query) {
145
+ this.peopleSuggestions.set([]);
146
+ this.isPeopleLoading.set(false);
147
+ return;
148
+ }
149
+ this.isPeopleLoading.set(true);
150
+ this.peopleDebounceTimer = setTimeout(() => {
151
+ const fn = this.peopleSearch();
152
+ if (!fn)
153
+ return;
154
+ fn(query).then((results) => {
155
+ this.peopleSuggestions.set(results);
156
+ this.isPeopleLoading.set(false);
157
+ }).catch(() => {
158
+ this.peopleSuggestions.set([]);
159
+ this.isPeopleLoading.set(false);
160
+ });
161
+ }, 300);
162
+ }
163
+ handleUserSelect(user) {
164
+ this.onUserChange()(user);
165
+ this.isFilterOpen.set(false);
166
+ }
167
+ handleClearUser() {
168
+ this.onUserChange()(undefined);
169
+ this.isFilterOpen.set(false);
170
+ }
171
+ // --- Date filter ---
172
+ handleDateApply() {
173
+ const from = this.tempDateFrom();
174
+ const to = this.tempDateTo();
175
+ if (!from && !to) {
176
+ this.onDateChange()(undefined);
177
+ }
178
+ else {
179
+ this.onDateChange()({ from: from || undefined, to: to || undefined });
180
+ }
181
+ this.isFilterOpen.set(false);
182
+ }
183
+ handleDateClear() {
184
+ this.tempDateFrom.set('');
185
+ this.tempDateTo.set('');
186
+ this.onDateChange()(undefined);
187
+ this.isFilterOpen.set(false);
188
+ }
189
+ onDocumentClick(event) {
190
+ const el = event.target;
191
+ if (!el.closest('ogrid-column-header-filter') && !el.closest('.ogrid-header-filter__popover')) {
192
+ this.isFilterOpen.set(false);
193
+ }
194
+ }
195
+ };
196
+ ColumnHeaderFilterComponent = __decorate([
197
+ Component({
198
+ selector: 'ogrid-column-header-filter',
199
+ standalone: true,
200
+ changeDetection: ChangeDetectionStrategy.OnPush,
201
+ template: `
202
+ <div class="ogrid-header-filter" #headerEl>
203
+ <div class="ogrid-header-filter__label">
204
+ <span class="ogrid-header-filter__name" [title]="columnName()" data-header-label>
205
+ {{ columnName() }}
206
+ </span>
207
+ </div>
208
+
209
+ <div class="ogrid-header-filter__actions">
210
+ @if (onSort()) {
211
+ <button
212
+ class="ogrid-header-filter__btn"
213
+ [class.ogrid-header-filter__btn--active]="isSorted()"
214
+ (click)="onSort()!()"
215
+ [attr.aria-label]="'Sort by ' + columnName()"
216
+ [title]="isSorted() ? (isSortedDescending() ? 'Sorted descending' : 'Sorted ascending') : 'Sort'"
217
+ >
218
+ @if (isSorted() && isSortedDescending()) {
219
+ &#9660;
220
+ } @else if (isSorted()) {
221
+ &#9650;
222
+ } @else {
223
+ &#8597;
224
+ }
225
+ </button>
226
+ }
227
+
228
+ @if (filterType() !== 'none') {
229
+ <button
230
+ class="ogrid-header-filter__btn"
231
+ [class.ogrid-header-filter__btn--active]="hasActiveFilter() || isFilterOpen()"
232
+ (click)="toggleFilter($event)"
233
+ [attr.aria-label]="'Filter ' + columnName()"
234
+ [title]="'Filter ' + columnName()"
235
+ >
236
+ &#9783;
237
+ @if (hasActiveFilter()) {
238
+ <span class="ogrid-header-filter__dot"></span>
239
+ }
240
+ </button>
241
+ }
242
+ </div>
243
+ </div>
244
+
245
+ @if (isFilterOpen() && filterType() !== 'none') {
246
+ <div
247
+ class="ogrid-header-filter__popover"
248
+ [style.top.px]="popoverTop()"
249
+ [style.left.px]="popoverLeft()"
250
+ (click)="$event.stopPropagation()"
251
+ >
252
+ <div class="ogrid-header-filter__popover-header">
253
+ Filter: {{ columnName() }}
254
+ </div>
255
+
256
+ @switch (filterType()) {
257
+ @case ('text') {
258
+ <div class="ogrid-header-filter__popover-body" style="width: 260px;">
259
+ <div style="padding: 12px;">
260
+ <input
261
+ type="text"
262
+ class="ogrid-header-filter__input"
263
+ placeholder="Enter search term..."
264
+ [value]="tempTextValue()"
265
+ (input)="tempTextValue.set(asInputValue($event))"
266
+ (keydown)="onTextKeydown($event)"
267
+ autocomplete="off"
268
+ />
269
+ </div>
270
+ <div class="ogrid-header-filter__popover-actions">
271
+ <button class="ogrid-header-filter__action-btn" [disabled]="!tempTextValue()" (click)="handleTextClear()">Clear</button>
272
+ <button class="ogrid-header-filter__action-btn ogrid-header-filter__action-btn--primary" (click)="handleTextApply()">Apply</button>
273
+ </div>
274
+ </div>
275
+ }
276
+ @case ('multiSelect') {
277
+ <div class="ogrid-header-filter__popover-body" style="width: 280px;">
278
+ <div style="padding: 12px 12px 4px;">
279
+ <input
280
+ type="text"
281
+ class="ogrid-header-filter__input"
282
+ placeholder="Search..."
283
+ [value]="searchText()"
284
+ (input)="searchText.set(asInputValue($event))"
285
+ (keydown)="$event.stopPropagation()"
286
+ autocomplete="off"
287
+ />
288
+ <div class="ogrid-header-filter__options-info">
289
+ {{ filteredOptions().length }} of {{ (options() ?? []).length }} options
290
+ </div>
291
+ </div>
292
+ <div class="ogrid-header-filter__select-actions">
293
+ <button class="ogrid-header-filter__action-btn" (click)="handleSelectAllFiltered()">
294
+ Select All ({{ filteredOptions().length }})
295
+ </button>
296
+ <button class="ogrid-header-filter__action-btn" (click)="handleClearSelection()">Clear</button>
297
+ </div>
298
+ <div class="ogrid-header-filter__options-list">
299
+ @if (isLoadingOptions()) {
300
+ <div class="ogrid-header-filter__loading">Loading...</div>
301
+ } @else if (filteredOptions().length === 0) {
302
+ <div class="ogrid-header-filter__empty">No options found</div>
303
+ } @else {
304
+ @for (option of filteredOptions(); track option) {
305
+ <label class="ogrid-header-filter__option">
306
+ <input
307
+ type="checkbox"
308
+ [checked]="tempSelected().has(option)"
309
+ (change)="handleCheckboxChange(option, $event)"
310
+ />
311
+ <span>{{ option }}</span>
312
+ </label>
313
+ }
314
+ }
315
+ </div>
316
+ <div class="ogrid-header-filter__popover-actions" style="border-top: 1px solid rgba(0,0,0,0.12);">
317
+ <button class="ogrid-header-filter__action-btn" (click)="handleClearSelection()">Clear</button>
318
+ <button class="ogrid-header-filter__action-btn ogrid-header-filter__action-btn--primary" (click)="handleApplyMultiSelect()">Apply</button>
319
+ </div>
320
+ </div>
321
+ }
322
+ @case ('people') {
323
+ <div class="ogrid-header-filter__popover-body" style="width: 300px;">
324
+ @if (selectedUser()) {
325
+ <div class="ogrid-header-filter__people-selected">
326
+ <div class="ogrid-header-filter__people-info-label">Currently filtered by:</div>
327
+ <div class="ogrid-header-filter__people-card">
328
+ <div class="ogrid-header-filter__people-avatar">{{ selectedUser()!.displayName?.[0] ?? '?' }}</div>
329
+ <div class="ogrid-header-filter__people-details">
330
+ <div>{{ selectedUser()!.displayName }}</div>
331
+ <div class="ogrid-header-filter__people-email">{{ selectedUser()!.email }}</div>
332
+ </div>
333
+ <button class="ogrid-header-filter__btn" (click)="handleClearUser()" aria-label="Remove filter">&times;</button>
334
+ </div>
335
+ </div>
336
+ }
337
+ <div style="padding: 12px 12px 4px;">
338
+ <input
339
+ type="text"
340
+ class="ogrid-header-filter__input"
341
+ placeholder="Search for a person..."
342
+ [value]="peopleSearchText()"
343
+ (input)="onPeopleSearchInput($event)"
344
+ (keydown)="$event.stopPropagation()"
345
+ autocomplete="off"
346
+ />
347
+ </div>
348
+ <div class="ogrid-header-filter__options-list">
349
+ @if (isPeopleLoading() && peopleSearchText().trim()) {
350
+ <div class="ogrid-header-filter__loading">Loading...</div>
351
+ } @else if (peopleSuggestions().length === 0 && peopleSearchText().trim()) {
352
+ <div class="ogrid-header-filter__empty">No results found</div>
353
+ } @else if (peopleSearchText().trim()) {
354
+ @for (user of peopleSuggestions(); track user.id || user.email || user.displayName) {
355
+ <div class="ogrid-header-filter__people-option" (click)="handleUserSelect(user)">
356
+ <div class="ogrid-header-filter__people-avatar">{{ user.displayName?.[0] ?? '?' }}</div>
357
+ <div class="ogrid-header-filter__people-details">
358
+ <div>{{ user.displayName }}</div>
359
+ <div class="ogrid-header-filter__people-email">{{ user.email }}</div>
360
+ </div>
361
+ </div>
362
+ }
363
+ } @else {
364
+ <div class="ogrid-header-filter__empty">Type to search...</div>
365
+ }
366
+ </div>
367
+ @if (selectedUser()) {
368
+ <div style="padding: 8px 12px; border-top: 1px solid rgba(0,0,0,0.12);">
369
+ <button class="ogrid-header-filter__action-btn" style="width: 100%;" (click)="handleClearUser()">Clear Filter</button>
370
+ </div>
371
+ }
372
+ </div>
373
+ }
374
+ @case ('date') {
375
+ <div class="ogrid-header-filter__popover-body" style="padding: 12px; display: flex; flex-direction: column; gap: 8px;">
376
+ <div style="display: flex; align-items: center; gap: 8px;">
377
+ <span style="min-width: 36px; font-size: 12px;">From:</span>
378
+ <input type="date" [value]="tempDateFrom()" (input)="tempDateFrom.set(asInputValue($event))" style="flex: 1; padding: 4px 6px;" />
379
+ </div>
380
+ <div style="display: flex; align-items: center; gap: 8px;">
381
+ <span style="min-width: 36px; font-size: 12px;">To:</span>
382
+ <input type="date" [value]="tempDateTo()" (input)="tempDateTo.set(asInputValue($event))" style="flex: 1; padding: 4px 6px;" />
383
+ </div>
384
+ <div class="ogrid-header-filter__popover-actions" style="margin-top: 4px;">
385
+ <button class="ogrid-header-filter__action-btn" [disabled]="!tempDateFrom() && !tempDateTo()" (click)="handleDateClear()">Clear</button>
386
+ <button class="ogrid-header-filter__action-btn ogrid-header-filter__action-btn--primary" (click)="handleDateApply()">Apply</button>
387
+ </div>
388
+ </div>
389
+ }
390
+ }
391
+ </div>
392
+ }
393
+ `,
394
+ styles: [`
395
+ :host { display: flex; align-items: center; width: 100%; min-width: 0; position: relative; }
396
+ .ogrid-header-filter { display: flex; align-items: center; width: 100%; min-width: 0; }
397
+ .ogrid-header-filter__label { flex: 1; min-width: 0; overflow: hidden; }
398
+ .ogrid-header-filter__name {
399
+ display: block; font-weight: 600; font-size: 14px; line-height: 1.4;
400
+ white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
401
+ }
402
+ .ogrid-header-filter__actions { display: flex; align-items: center; margin-left: 4px; flex-shrink: 0; }
403
+ .ogrid-header-filter__btn {
404
+ width: 24px; height: 24px; padding: 2px; border: none; border-radius: 4px;
405
+ background: transparent; cursor: pointer; font-size: 12px; line-height: 1;
406
+ display: inline-flex; align-items: center; justify-content: center; position: relative;
407
+ color: rgba(0,0,0,0.54);
408
+ }
409
+ .ogrid-header-filter__btn:hover { background: rgba(0,0,0,0.04); }
410
+ .ogrid-header-filter__btn--active { color: var(--mat-sys-primary, #1976d2); }
411
+ .ogrid-header-filter__dot {
412
+ position: absolute; top: 2px; right: 2px;
413
+ width: 6px; height: 6px; border-radius: 50%;
414
+ background: var(--mat-sys-primary, #1976d2);
415
+ }
416
+ .ogrid-header-filter__popover {
417
+ position: fixed; z-index: 1000;
418
+ background: #fff; border: 1px solid rgba(0,0,0,0.12);
419
+ border-radius: 4px; box-shadow: 0 2px 8px rgba(0,0,0,0.15);
420
+ margin-top: 4px;
421
+ }
422
+ .ogrid-header-filter__popover-header {
423
+ padding: 8px 12px; font-size: 14px; font-weight: 600;
424
+ border-bottom: 1px solid rgba(0,0,0,0.12);
425
+ }
426
+ .ogrid-header-filter__popover-body { }
427
+ .ogrid-header-filter__popover-actions {
428
+ display: flex; justify-content: flex-end; gap: 8px; padding: 8px 12px;
429
+ }
430
+ .ogrid-header-filter__input {
431
+ width: 100%; padding: 8px 12px; border: 1px solid rgba(0,0,0,0.23);
432
+ border-radius: 4px; font-size: 14px; box-sizing: border-box;
433
+ }
434
+ .ogrid-header-filter__input:focus { outline: 2px solid var(--mat-sys-primary, #1976d2); outline-offset: -1px; }
435
+ .ogrid-header-filter__options-info { margin-top: 4px; font-size: 12px; color: rgba(0,0,0,0.6); }
436
+ .ogrid-header-filter__select-actions {
437
+ display: flex; justify-content: space-between; padding: 4px 12px;
438
+ }
439
+ .ogrid-header-filter__options-list { max-height: 240px; overflow-y: auto; padding: 0 4px; }
440
+ .ogrid-header-filter__option {
441
+ display: flex; align-items: center; gap: 8px;
442
+ padding: 4px 8px; cursor: pointer; font-size: 14px;
443
+ }
444
+ .ogrid-header-filter__option:hover { background: rgba(0,0,0,0.04); }
445
+ .ogrid-header-filter__loading, .ogrid-header-filter__empty {
446
+ padding: 16px; text-align: center; font-size: 14px; color: rgba(0,0,0,0.6);
447
+ }
448
+ .ogrid-header-filter__action-btn {
449
+ padding: 4px 12px; border: none; border-radius: 4px;
450
+ background: transparent; cursor: pointer; font-size: 13px;
451
+ }
452
+ .ogrid-header-filter__action-btn:hover { background: rgba(0,0,0,0.04); }
453
+ .ogrid-header-filter__action-btn:disabled { opacity: 0.38; cursor: default; }
454
+ .ogrid-header-filter__action-btn--primary {
455
+ background: var(--mat-sys-primary, #1976d2); color: #fff;
456
+ }
457
+ .ogrid-header-filter__action-btn--primary:hover { background: var(--mat-sys-primary, #1565c0); }
458
+ .ogrid-header-filter__people-selected {
459
+ padding: 12px; border-bottom: 1px solid rgba(0,0,0,0.12);
460
+ }
461
+ .ogrid-header-filter__people-info-label { font-size: 12px; color: rgba(0,0,0,0.6); }
462
+ .ogrid-header-filter__people-card {
463
+ display: flex; align-items: center; gap: 8px; margin-top: 4px;
464
+ }
465
+ .ogrid-header-filter__people-avatar {
466
+ width: 32px; height: 32px; border-radius: 50%; background: rgba(0,0,0,0.08);
467
+ display: flex; align-items: center; justify-content: center;
468
+ font-size: 14px; font-weight: 600; flex-shrink: 0;
469
+ }
470
+ .ogrid-header-filter__people-details { flex: 1; min-width: 0; font-size: 14px; }
471
+ .ogrid-header-filter__people-details > div { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
472
+ .ogrid-header-filter__people-email { font-size: 12px; color: rgba(0,0,0,0.6); }
473
+ .ogrid-header-filter__people-option {
474
+ display: flex; align-items: center; gap: 8px; padding: 8px 12px; cursor: pointer;
475
+ }
476
+ .ogrid-header-filter__people-option:hover { background: rgba(0,0,0,0.04); }
477
+ `],
478
+ host: {
479
+ '(document:click)': 'onDocumentClick($event)',
480
+ },
481
+ })
482
+ ], ColumnHeaderFilterComponent);
483
+ export { ColumnHeaderFilterComponent };