@alaarab/ogrid-angular-radix 2.0.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 (33) hide show
  1. package/README.md +76 -0
  2. package/dist/esm/column-chooser/column-chooser.component.js +199 -0
  3. package/dist/esm/column-header-filter/column-header-filter.component.js +497 -0
  4. package/dist/esm/datagrid-table/datagrid-table.component.js +573 -0
  5. package/dist/esm/index.js +14 -0
  6. package/dist/esm/ogrid/ogrid.component.js +77 -0
  7. package/dist/esm/pagination-controls/pagination-controls.component.js +189 -0
  8. package/dist/types/column-chooser/column-chooser.component.d.ts +26 -0
  9. package/dist/types/column-header-filter/column-header-filter.component.d.ts +67 -0
  10. package/dist/types/datagrid-table/datagrid-table.component.d.ts +131 -0
  11. package/dist/types/index.d.ts +12 -0
  12. package/dist/types/ogrid/ogrid.component.d.ts +14 -0
  13. package/dist/types/pagination-controls/pagination-controls.component.d.ts +15 -0
  14. package/jest-mocks/angular-cdk-overlay.cjs.js +38 -0
  15. package/jest-mocks/style-mock.js +1 -0
  16. package/jest.config.js +43 -0
  17. package/package.json +37 -0
  18. package/scripts/compile-styles.js +53 -0
  19. package/src/__tests__/column-chooser.component.spec.ts.skip +195 -0
  20. package/src/__tests__/column-header-filter.component.spec.ts.skip +401 -0
  21. package/src/__tests__/datagrid-table.component.spec.ts.skip +417 -0
  22. package/src/__tests__/exports.test.ts +54 -0
  23. package/src/__tests__/ogrid.component.spec.ts.skip +236 -0
  24. package/src/__tests__/pagination-controls.component.spec.ts.skip +190 -0
  25. package/src/column-chooser/column-chooser.component.ts +204 -0
  26. package/src/column-header-filter/column-header-filter.component.ts +528 -0
  27. package/src/datagrid-table/datagrid-table.component.scss +289 -0
  28. package/src/datagrid-table/datagrid-table.component.ts +636 -0
  29. package/src/index.ts +16 -0
  30. package/src/ogrid/ogrid.component.ts +78 -0
  31. package/src/pagination-controls/pagination-controls.component.ts +187 -0
  32. package/tsconfig.build.json +9 -0
  33. package/tsconfig.json +21 -0
@@ -0,0 +1,190 @@
1
+ import { ComponentFixture, TestBed } from '@angular/core/testing';
2
+ import { DebugElement } from '@angular/core';
3
+ import { By } from '@angular/platform-browser';
4
+ import { PaginationControlsComponent } from '../pagination-controls/pagination-controls.component';
5
+
6
+ describe('PaginationControlsComponent', () => {
7
+ let component: PaginationControlsComponent;
8
+ let fixture: ComponentFixture<PaginationControlsComponent>;
9
+
10
+ beforeEach(async () => {
11
+ await TestBed.configureTestingModule({
12
+ imports: [PaginationControlsComponent],
13
+ }).compileComponents();
14
+
15
+ fixture = TestBed.createComponent(PaginationControlsComponent);
16
+ component = fixture.componentInstance;
17
+ });
18
+
19
+ it('should create', () => {
20
+ expect(component).toBeTruthy();
21
+ });
22
+
23
+ it('should return null when totalCount is 0', () => {
24
+ fixture.componentRef.setInput('currentPage', 1);
25
+ fixture.componentRef.setInput('pageSize', 10);
26
+ fixture.componentRef.setInput('totalCount', 0);
27
+ fixture.detectChanges();
28
+
29
+ const nav = fixture.debugElement.query(By.css('nav'));
30
+ expect(nav).toBeNull();
31
+ });
32
+
33
+ it('should render summary text and page buttons', () => {
34
+ fixture.componentRef.setInput('currentPage', 2);
35
+ fixture.componentRef.setInput('pageSize', 10);
36
+ fixture.componentRef.setInput('totalCount', 50);
37
+ fixture.detectChanges();
38
+
39
+ const summaryText = fixture.nativeElement.querySelector('.ogrid-pagination__info');
40
+ expect(summaryText.textContent).toContain('Showing 11 to 20 of 50 items');
41
+
42
+ const activeButton = fixture.debugElement.query(
43
+ By.css('button[aria-current="page"]')
44
+ );
45
+ expect(activeButton).toBeTruthy();
46
+ expect(activeButton.nativeElement.getAttribute('aria-label')).toBe('Page 2');
47
+ });
48
+
49
+ it('should disable first and previous on first page', () => {
50
+ fixture.componentRef.setInput('currentPage', 1);
51
+ fixture.componentRef.setInput('pageSize', 10);
52
+ fixture.componentRef.setInput('totalCount', 50);
53
+ fixture.detectChanges();
54
+
55
+ const firstButton = fixture.debugElement.query(
56
+ By.css('button[aria-label="First page"]')
57
+ );
58
+ const prevButton = fixture.debugElement.query(
59
+ By.css('button[aria-label="Previous page"]')
60
+ );
61
+
62
+ expect(firstButton.nativeElement.disabled).toBe(true);
63
+ expect(prevButton.nativeElement.disabled).toBe(true);
64
+ });
65
+
66
+ it('should disable last and next on last page', () => {
67
+ fixture.componentRef.setInput('currentPage', 5);
68
+ fixture.componentRef.setInput('pageSize', 10);
69
+ fixture.componentRef.setInput('totalCount', 50);
70
+ fixture.detectChanges();
71
+
72
+ const lastButton = fixture.debugElement.query(
73
+ By.css('button[aria-label="Last page"]')
74
+ );
75
+ const nextButton = fixture.debugElement.query(
76
+ By.css('button[aria-label="Next page"]')
77
+ );
78
+
79
+ expect(lastButton.nativeElement.disabled).toBe(true);
80
+ expect(nextButton.nativeElement.disabled).toBe(true);
81
+ });
82
+
83
+ it('should emit pageChange for navigation buttons', () => {
84
+ fixture.componentRef.setInput('currentPage', 2);
85
+ fixture.componentRef.setInput('pageSize', 10);
86
+ fixture.componentRef.setInput('totalCount', 50);
87
+ fixture.detectChanges();
88
+
89
+ const pageChangeSpy = jest.fn();
90
+ component.pageChange.subscribe(pageChangeSpy);
91
+
92
+ // Click next
93
+ const nextButton = fixture.debugElement.query(
94
+ By.css('button[aria-label="Next page"]')
95
+ );
96
+ nextButton.nativeElement.click();
97
+ expect(pageChangeSpy).toHaveBeenCalledWith(3);
98
+
99
+ // Click previous
100
+ const prevButton = fixture.debugElement.query(
101
+ By.css('button[aria-label="Previous page"]')
102
+ );
103
+ prevButton.nativeElement.click();
104
+ expect(pageChangeSpy).toHaveBeenCalledWith(1);
105
+ });
106
+
107
+ it('should emit pageChange when clicking page number buttons', () => {
108
+ fixture.componentRef.setInput('currentPage', 2);
109
+ fixture.componentRef.setInput('pageSize', 10);
110
+ fixture.componentRef.setInput('totalCount', 50);
111
+ fixture.detectChanges();
112
+
113
+ const pageChangeSpy = jest.fn();
114
+ component.pageChange.subscribe(pageChangeSpy);
115
+
116
+ const page3Button = fixture.debugElement.query(
117
+ By.css('button[aria-label="Page 3"]')
118
+ );
119
+ page3Button.nativeElement.click();
120
+ expect(pageChangeSpy).toHaveBeenCalledWith(3);
121
+ });
122
+
123
+ it('should emit pageChange for first and last buttons', () => {
124
+ fixture.componentRef.setInput('currentPage', 3);
125
+ fixture.componentRef.setInput('pageSize', 10);
126
+ fixture.componentRef.setInput('totalCount', 50);
127
+ fixture.detectChanges();
128
+
129
+ const pageChangeSpy = jest.fn();
130
+ component.pageChange.subscribe(pageChangeSpy);
131
+
132
+ // Click first
133
+ const firstButton = fixture.debugElement.query(
134
+ By.css('button[aria-label="First page"]')
135
+ );
136
+ firstButton.nativeElement.click();
137
+ expect(pageChangeSpy).toHaveBeenCalledWith(1);
138
+
139
+ // Click last
140
+ const lastButton = fixture.debugElement.query(
141
+ By.css('button[aria-label="Last page"]')
142
+ );
143
+ lastButton.nativeElement.click();
144
+ expect(pageChangeSpy).toHaveBeenCalledWith(5);
145
+ });
146
+
147
+ it('should emit pageSizeChange when page size is changed', () => {
148
+ fixture.componentRef.setInput('currentPage', 1);
149
+ fixture.componentRef.setInput('pageSize', 10);
150
+ fixture.componentRef.setInput('totalCount', 50);
151
+ fixture.detectChanges();
152
+
153
+ const pageSizeChangeSpy = jest.fn();
154
+ component.pageSizeChange.subscribe(pageSizeChangeSpy);
155
+
156
+ const select = fixture.debugElement.query(
157
+ By.css('select[aria-label="Rows per page"]')
158
+ );
159
+ select.nativeElement.value = '25';
160
+ select.nativeElement.dispatchEvent(new Event('change'));
161
+ fixture.detectChanges();
162
+
163
+ expect(pageSizeChangeSpy).toHaveBeenCalledWith(25);
164
+ });
165
+
166
+ it('should use custom entityLabelPlural when provided', () => {
167
+ fixture.componentRef.setInput('currentPage', 1);
168
+ fixture.componentRef.setInput('pageSize', 10);
169
+ fixture.componentRef.setInput('totalCount', 50);
170
+ fixture.componentRef.setInput('entityLabelPlural', 'projects');
171
+ fixture.detectChanges();
172
+
173
+ const summaryText = fixture.nativeElement.querySelector('.ogrid-pagination__info');
174
+ expect(summaryText.textContent).toContain('projects');
175
+ });
176
+
177
+ it('should render custom page size options', () => {
178
+ fixture.componentRef.setInput('currentPage', 1);
179
+ fixture.componentRef.setInput('pageSize', 20);
180
+ fixture.componentRef.setInput('totalCount', 100);
181
+ fixture.componentRef.setInput('pageSizeOptions', [20, 50, 100]);
182
+ fixture.detectChanges();
183
+
184
+ const options = fixture.debugElement.queryAll(By.css('select option'));
185
+ expect(options.length).toBe(3);
186
+ expect(options[0].nativeElement.value).toBe('20');
187
+ expect(options[1].nativeElement.value).toBe('50');
188
+ expect(options[2].nativeElement.value).toBe('100');
189
+ });
190
+ });
@@ -0,0 +1,204 @@
1
+ import { Component, input, output, signal, computed, ChangeDetectionStrategy } from '@angular/core';
2
+ import type { IColumnDefinition } from '@alaarab/ogrid-angular';
3
+
4
+ export interface IColumnChooserProps {
5
+ columns: IColumnDefinition[];
6
+ visibleColumns: Set<string>;
7
+ onVisibilityChange: (columnKey: string, visible: boolean) => void;
8
+ }
9
+
10
+ /**
11
+ * Column visibility chooser dropdown for Angular Radix (lightweight styling).
12
+ * Standalone component with inline template and CSS variables for theming.
13
+ */
14
+ @Component({
15
+ selector: 'ogrid-column-chooser',
16
+ standalone: true,
17
+ changeDetection: ChangeDetectionStrategy.OnPush,
18
+ template: `
19
+ <div class="ogrid-column-chooser">
20
+ <button
21
+ class="ogrid-column-chooser__trigger"
22
+ (click)="toggle()"
23
+ [attr.aria-expanded]="isOpen()"
24
+ aria-haspopup="listbox"
25
+ >
26
+ ☰ Columns ({{ visibleCount() }}/{{ totalCount() }})
27
+ <span class="ogrid-column-chooser__caret">{{ isOpen() ? '▲' : '▼' }}</span>
28
+ </button>
29
+
30
+ @if (isOpen()) {
31
+ <div class="ogrid-column-chooser__dropdown" (click)="$event.stopPropagation()">
32
+ <div class="ogrid-column-chooser__header">
33
+ Select Columns ({{ visibleCount() }} of {{ totalCount() }})
34
+ </div>
35
+
36
+ <div class="ogrid-column-chooser__list">
37
+ @for (col of columns(); track col.columnId) {
38
+ <label class="ogrid-column-chooser__item">
39
+ <input
40
+ type="checkbox"
41
+ [checked]="visibleColumns().has(col.columnId)"
42
+ (change)="onCheckboxChange(col.columnId, $event)"
43
+ />
44
+ <span>{{ col.name }}</span>
45
+ </label>
46
+ }
47
+ </div>
48
+
49
+ <div class="ogrid-column-chooser__footer">
50
+ <button class="ogrid-column-chooser__btn" (click)="clearAll()">Clear All</button>
51
+ <button class="ogrid-column-chooser__btn ogrid-column-chooser__btn--primary" (click)="selectAll()">Select All</button>
52
+ </div>
53
+ </div>
54
+ }
55
+ </div>
56
+ `,
57
+ styles: [`
58
+ :host { display: inline-flex; position: relative; }
59
+ .ogrid-column-chooser { position: relative; }
60
+ .ogrid-column-chooser__trigger {
61
+ display: inline-flex;
62
+ align-items: center;
63
+ gap: 6px;
64
+ padding: 6px 12px;
65
+ border: 1px solid var(--ogrid-border, #e0e0e0);
66
+ border-radius: 4px;
67
+ background: var(--ogrid-bg, #ffffff);
68
+ color: var(--ogrid-fg, #242424);
69
+ cursor: pointer;
70
+ font-size: 14px;
71
+ font-weight: 600;
72
+ text-transform: none;
73
+ white-space: nowrap;
74
+ transition: all 0.15s ease;
75
+ }
76
+ .ogrid-column-chooser__trigger:hover {
77
+ background: var(--ogrid-hover-bg, #f0f0f0);
78
+ border-color: var(--ogrid-active-border, #0078d4);
79
+ }
80
+ .ogrid-column-chooser__caret {
81
+ font-size: 10px;
82
+ opacity: 0.7;
83
+ }
84
+ .ogrid-column-chooser__dropdown {
85
+ position: absolute;
86
+ top: 100%;
87
+ right: 0;
88
+ z-index: 1000;
89
+ min-width: 220px;
90
+ margin-top: 4px;
91
+ background: var(--ogrid-bg, #ffffff);
92
+ border: 1px solid var(--ogrid-border, #e0e0e0);
93
+ border-radius: 4px;
94
+ box-shadow: 0 4px 12px rgba(0,0,0,0.15);
95
+ }
96
+ .ogrid-column-chooser__header {
97
+ padding: 8px 12px;
98
+ font-size: 14px;
99
+ font-weight: 600;
100
+ color: var(--ogrid-fg, #242424);
101
+ border-bottom: 1px solid var(--ogrid-border, #e0e0e0);
102
+ background: var(--ogrid-header-bg, #f5f5f5);
103
+ }
104
+ .ogrid-column-chooser__list {
105
+ max-height: 320px;
106
+ overflow-y: auto;
107
+ padding: 4px 0;
108
+ }
109
+ .ogrid-column-chooser__item {
110
+ display: flex;
111
+ align-items: center;
112
+ gap: 8px;
113
+ padding: 6px 12px;
114
+ min-height: 32px;
115
+ cursor: pointer;
116
+ font-size: 14px;
117
+ color: var(--ogrid-fg, #242424);
118
+ transition: background 0.15s ease;
119
+ }
120
+ .ogrid-column-chooser__item:hover {
121
+ background: var(--ogrid-hover-bg, #f0f0f0);
122
+ }
123
+ .ogrid-column-chooser__item input[type="checkbox"] {
124
+ cursor: pointer;
125
+ }
126
+ .ogrid-column-chooser__footer {
127
+ display: flex;
128
+ justify-content: flex-end;
129
+ gap: 8px;
130
+ padding: 8px 12px;
131
+ border-top: 1px solid var(--ogrid-border, #e0e0e0);
132
+ background: var(--ogrid-header-bg, #f5f5f5);
133
+ }
134
+ .ogrid-column-chooser__btn {
135
+ padding: 6px 12px;
136
+ border: 1px solid var(--ogrid-border, #e0e0e0);
137
+ border-radius: 4px;
138
+ background: var(--ogrid-bg, #ffffff);
139
+ color: var(--ogrid-fg, #242424);
140
+ cursor: pointer;
141
+ font-size: 13px;
142
+ text-transform: none;
143
+ transition: all 0.15s ease;
144
+ }
145
+ .ogrid-column-chooser__btn:hover {
146
+ background: var(--ogrid-hover-bg, #f0f0f0);
147
+ }
148
+ .ogrid-column-chooser__btn--primary {
149
+ background: var(--ogrid-active-border, #0078d4);
150
+ color: #ffffff;
151
+ border-color: var(--ogrid-active-border, #0078d4);
152
+ }
153
+ .ogrid-column-chooser__btn--primary:hover {
154
+ opacity: 0.9;
155
+ }
156
+ `],
157
+ host: {
158
+ '(document:click)': 'onDocumentClick($event)',
159
+ },
160
+ })
161
+ export class ColumnChooserComponent {
162
+ readonly columns = input.required<IColumnDefinition[]>();
163
+ readonly visibleColumns = input.required<Set<string>>();
164
+
165
+ readonly visibilityChange = output<{ columnKey: string; visible: boolean }>();
166
+
167
+ readonly isOpen = signal(false);
168
+
169
+ readonly visibleCount = computed(() => this.visibleColumns().size);
170
+ readonly totalCount = computed(() => this.columns().length);
171
+
172
+ toggle(): void {
173
+ this.isOpen.update((v) => !v);
174
+ }
175
+
176
+ onCheckboxChange(columnKey: string, event: Event): void {
177
+ const checked = (event.target as HTMLInputElement).checked;
178
+ this.visibilityChange.emit({ columnKey, visible: checked });
179
+ }
180
+
181
+ selectAll(): void {
182
+ for (const col of this.columns()) {
183
+ if (!this.visibleColumns().has(col.columnId)) {
184
+ this.visibilityChange.emit({ columnKey: col.columnId, visible: true });
185
+ }
186
+ }
187
+ }
188
+
189
+ clearAll(): void {
190
+ for (const col of this.columns()) {
191
+ if (this.visibleColumns().has(col.columnId)) {
192
+ this.visibilityChange.emit({ columnKey: col.columnId, visible: false });
193
+ }
194
+ }
195
+ }
196
+
197
+ onDocumentClick(event: MouseEvent): void {
198
+ // Close dropdown when clicking outside
199
+ const el = event.target as HTMLElement;
200
+ if (!el.closest('ogrid-column-chooser')) {
201
+ this.isOpen.set(false);
202
+ }
203
+ }
204
+ }