column-fitter 15.0.8 → 15.0.9
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/README.md +1035 -13
- package/column-fitter-15.0.9.tgz +0 -0
- package/fesm2022/column-fitter.mjs +31 -25
- package/fesm2022/column-fitter.mjs.map +1 -1
- package/package.json +3 -5
- package/types/column-fitter.d.ts +60 -0
- package/column-fitter-15.0.8.tgz +0 -0
- package/esm2022/column-fitter.mjs +0 -5
- package/esm2022/lib/column-fitter-demo/column-fitter-demo.component.mjs +0 -33
- package/esm2022/lib/column-fitter.component.mjs +0 -75
- package/esm2022/lib/column-fitter.module.mjs +0 -29
- package/esm2022/lib/models/column.model.mjs +0 -11
- package/esm2022/lib/models/device-sizes.model.mjs +0 -8
- package/esm2022/lib/models/index.mjs +0 -6
- package/esm2022/public-api.mjs +0 -8
- package/index.d.ts +0 -5
- package/lib/column-fitter-demo/column-fitter-demo.component.d.ts +0 -13
- package/lib/column-fitter.component.d.ts +0 -23
- package/lib/column-fitter.module.d.ts +0 -9
- package/lib/models/column.model.d.ts +0 -11
- package/lib/models/device-sizes.model.d.ts +0 -6
- package/lib/models/index.d.ts +0 -2
- package/public-api.d.ts +0 -4
package/README.md
CHANGED
|
@@ -1,24 +1,1046 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Column Fitter Component
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
## Overview
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
The `column-fitter` library provides a responsive grid layout system that automatically adjusts the number of columns based on the detected device size. It integrates with the `screen-observer` package to monitor device changes and dynamically updates CSS Grid layouts for optimal viewing across different screen sizes.
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
> Note: Don't forget to add `--project column-fitter` or else it will be added to the default project in your `angular.json` file.
|
|
7
|
+
### Core Capabilities
|
|
9
8
|
|
|
10
|
-
|
|
9
|
+
#### 📱 Responsive Grid Layout System
|
|
11
10
|
|
|
12
|
-
|
|
11
|
+
- **Device Detection**: Automatically detects device type using screen-observer service
|
|
12
|
+
- **Dynamic Column Adjustment**: Updates column count based on current device (mobile, tablet, mini, desktop)
|
|
13
|
+
- **CSS Grid Integration**: Uses modern CSS Grid with auto-fit and minmax for flexible layouts
|
|
14
|
+
- **Flexible Configuration**: Support for both fixed columns and device-specific column settings
|
|
15
|
+
- **Responsive Behavior**: Seamlessly adapts between different screen sizes
|
|
16
|
+
- **Performance Optimized**: Uses RxJS distinctUntilChanged to prevent unnecessary updates
|
|
13
17
|
|
|
14
|
-
|
|
18
|
+
#### 🔧 Features
|
|
15
19
|
|
|
16
|
-
|
|
20
|
+
✅ **Device Size Detection** - Automatic detection via screen-observer integration
|
|
21
|
+
✅ **Dynamic Grid Updates** - Real-time column count adjustment
|
|
22
|
+
✅ **CSS Grid Foundation** - Modern CSS Grid with repeat() and auto-fit
|
|
23
|
+
✅ **Flexible Configuration** - Fixed numbers or device-specific settings
|
|
24
|
+
✅ **Customizable Styling** - Configurable gap, margins, padding, and colors
|
|
25
|
+
✅ **Performance Optimized** - Efficient change detection and updates
|
|
26
|
+
✅ **Type-Safe Configuration** - Strong typing with Column and DeviceSizes models
|
|
27
|
+
✅ **Demo Component** - Interactive demo showcasing all features
|
|
17
28
|
|
|
18
|
-
|
|
29
|
+
### Key Benefits
|
|
19
30
|
|
|
20
|
-
|
|
31
|
+
| Feature | Description |
|
|
32
|
+
|---------|-------------|
|
|
33
|
+
| **Automatic Responsiveness** | No manual media queries needed |
|
|
34
|
+
| **Device-Aware Layouts** | Optimized layouts for each device type |
|
|
35
|
+
| **Modern CSS Grid** | Leverages CSS Grid for superior performance |
|
|
36
|
+
| **Type-Safe Configuration** | Full TypeScript support with device models |
|
|
37
|
+
| **Seamless Integration** | Works with existing screen-observer implementations |
|
|
21
38
|
|
|
22
|
-
|
|
39
|
+
---
|
|
23
40
|
|
|
24
|
-
|
|
41
|
+
## Demo Component (`ColumnFitterDemoComponent`)
|
|
42
|
+
|
|
43
|
+
The demo component showcases responsive grid layouts using a bookmarks list example.
|
|
44
|
+
|
|
45
|
+
### Usage
|
|
46
|
+
|
|
47
|
+
To use the demo component in your application:
|
|
48
|
+
|
|
49
|
+
```html
|
|
50
|
+
<app-column-fitter-demo></app-column-fitter-demo>
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
### Demo Features
|
|
54
|
+
|
|
55
|
+
- **Bookmarks List**: Displays a list of classic books in responsive grid
|
|
56
|
+
- **Device-Specific Columns**:
|
|
57
|
+
- Mobile: 1 column
|
|
58
|
+
- Tablet: 4 columns
|
|
59
|
+
- Mini: 2 columns
|
|
60
|
+
- Desktop: Auto-fit with minmax
|
|
61
|
+
- **Real-time Updates**: Grid layout updates as you resize the browser
|
|
62
|
+
- **Visual Feedback**: Console logging of device changes
|
|
63
|
+
|
|
64
|
+
---
|
|
65
|
+
|
|
66
|
+
## Summary
|
|
67
|
+
|
|
68
|
+
The `column-fitter` library provides a modern, responsive grid system that automatically adapts column layouts based on device detection, making it perfect for creating responsive applications without manual media query management.
|
|
69
|
+
|
|
70
|
+
---
|
|
71
|
+
|
|
72
|
+
## Quick Start Guide
|
|
73
|
+
|
|
74
|
+
### Installation & Setup (2 minutes)
|
|
75
|
+
|
|
76
|
+
#### 1. Import Module
|
|
77
|
+
|
|
78
|
+
```typescript
|
|
79
|
+
// app.module.ts
|
|
80
|
+
import { ColumnFitterModule } from 'column-fitter';
|
|
81
|
+
|
|
82
|
+
@NgModule({
|
|
83
|
+
imports: [
|
|
84
|
+
ColumnFitterModule
|
|
85
|
+
]
|
|
86
|
+
})
|
|
87
|
+
export class AppModule { }
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
#### 2. Dependencies
|
|
91
|
+
|
|
92
|
+
The package requires the `screen-observer` package for device detection:
|
|
93
|
+
|
|
94
|
+
```bash
|
|
95
|
+
npm install screen-observer
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
### Quick Examples
|
|
99
|
+
|
|
100
|
+
#### Example 1: Fixed Column Layout
|
|
101
|
+
|
|
102
|
+
```typescript
|
|
103
|
+
import { Component } from '@angular/core';
|
|
104
|
+
|
|
105
|
+
@Component({
|
|
106
|
+
selector: 'app-fixed-grid',
|
|
107
|
+
template: `
|
|
108
|
+
<app-column-fitter
|
|
109
|
+
[columns]="4"
|
|
110
|
+
[gap]="'1rem'"
|
|
111
|
+
[padding]="'1rem'"
|
|
112
|
+
[backgroundColor]="'#f5f5f5'">
|
|
113
|
+
|
|
114
|
+
<div class="grid-item" *ngFor="let item of items">
|
|
115
|
+
{{ item.name }}
|
|
116
|
+
</div>
|
|
117
|
+
|
|
118
|
+
</app-column-fitter>
|
|
119
|
+
`
|
|
120
|
+
})
|
|
121
|
+
export class FixedGridComponent {
|
|
122
|
+
items = [
|
|
123
|
+
{ id: 1, name: 'Item 1' },
|
|
124
|
+
{ id: 2, name: 'Item 2' },
|
|
125
|
+
{ id: 3, name: 'Item 3' },
|
|
126
|
+
{ id: 4, name: 'Item 4' },
|
|
127
|
+
{ id: 5, name: 'Item 5' },
|
|
128
|
+
{ id: 6, name: 'Item 6' }
|
|
129
|
+
];
|
|
130
|
+
}
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
#### Example 2: Device-Specific Columns
|
|
134
|
+
|
|
135
|
+
```typescript
|
|
136
|
+
import { Component } from '@angular/core';
|
|
137
|
+
import { Column, DeviceSizes } from 'column-fitter';
|
|
138
|
+
|
|
139
|
+
@Component({
|
|
140
|
+
selector: 'app-responsive-grid',
|
|
141
|
+
template: `
|
|
142
|
+
<app-column-fitter
|
|
143
|
+
[columns]="responsiveColumns"
|
|
144
|
+
[gap]="'1.5rem'"
|
|
145
|
+
[margin]="'1rem'"
|
|
146
|
+
[minWidth]="'250px'"
|
|
147
|
+
[backgroundColor]="'#ffffff'"
|
|
148
|
+
[padding]="'1rem'">
|
|
149
|
+
|
|
150
|
+
<div class="product-card" *ngFor="let product of products">
|
|
151
|
+
<h3>{{ product.name }}</h3>
|
|
152
|
+
<p>{{ product.description }}</p>
|
|
153
|
+
<span class="price">{{ product.price | currency }}</span>
|
|
154
|
+
</div>
|
|
155
|
+
|
|
156
|
+
</app-column-fitter>
|
|
157
|
+
`,
|
|
158
|
+
styles: [`
|
|
159
|
+
.product-card {
|
|
160
|
+
padding: 1rem;
|
|
161
|
+
border: 1px solid #ddd;
|
|
162
|
+
border-radius: 8px;
|
|
163
|
+
background: white;
|
|
164
|
+
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
.price {
|
|
168
|
+
font-weight: bold;
|
|
169
|
+
color: #2196f3;
|
|
170
|
+
}
|
|
171
|
+
`]
|
|
172
|
+
})
|
|
173
|
+
export class ResponsiveGridComponent {
|
|
174
|
+
responsiveColumns: Column[] = [
|
|
175
|
+
{ device: DeviceSizes.MOBILE, columns: 1 },
|
|
176
|
+
{ device: DeviceSizes.MINI, columns: 2 },
|
|
177
|
+
{ device: DeviceSizes.TABLET, columns: 3 },
|
|
178
|
+
{ device: DeviceSizes.DESKTOP, columns: 4 }
|
|
179
|
+
];
|
|
180
|
+
|
|
181
|
+
products = [
|
|
182
|
+
{ id: 1, name: 'Product 1', description: 'Description 1', price: 29.99 },
|
|
183
|
+
{ id: 2, name: 'Product 2', description: 'Description 2', price: 39.99 },
|
|
184
|
+
{ id: 3, name: 'Product 3', description: 'Description 3', price: 49.99 },
|
|
185
|
+
{ id: 4, name: 'Product 4', description: 'Description 4', price: 59.99 },
|
|
186
|
+
{ id: 5, name: 'Product 5', description: 'Description 5', price: 69.99 },
|
|
187
|
+
{ id: 6, name: 'Product 6', description: 'Description 6', price: 79.99 }
|
|
188
|
+
];
|
|
189
|
+
}
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
#### Example 3: Gallery Layout
|
|
193
|
+
|
|
194
|
+
```typescript
|
|
195
|
+
import { Component } from '@angular/core';
|
|
196
|
+
import { Column, DeviceSizes } from 'column-fitter';
|
|
197
|
+
|
|
198
|
+
@Component({
|
|
199
|
+
selector: 'app-image-gallery',
|
|
200
|
+
template: `
|
|
201
|
+
<app-column-fitter
|
|
202
|
+
[columns]="galleryColumns"
|
|
203
|
+
[gap]="'0.5rem'"
|
|
204
|
+
[padding]="'0.5rem'"
|
|
205
|
+
[backgroundColor]="'#000'"
|
|
206
|
+
[minWidth]="'200px'">
|
|
207
|
+
|
|
208
|
+
<div class="gallery-item" *ngFor="let image of images">
|
|
209
|
+
<img [src]="image.url" [alt]="image.alt" />
|
|
210
|
+
<div class="overlay">
|
|
211
|
+
<h4>{{ image.title }}</h4>
|
|
212
|
+
</div>
|
|
213
|
+
</div>
|
|
214
|
+
|
|
215
|
+
</app-column-fitter>
|
|
216
|
+
`,
|
|
217
|
+
styles: [`
|
|
218
|
+
.gallery-item {
|
|
219
|
+
position: relative;
|
|
220
|
+
overflow: hidden;
|
|
221
|
+
border-radius: 4px;
|
|
222
|
+
aspect-ratio: 1;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
.gallery-item img {
|
|
226
|
+
width: 100%;
|
|
227
|
+
height: 100%;
|
|
228
|
+
object-fit: cover;
|
|
229
|
+
transition: transform 0.3s ease;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
.gallery-item:hover img {
|
|
233
|
+
transform: scale(1.1);
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
.overlay {
|
|
237
|
+
position: absolute;
|
|
238
|
+
bottom: 0;
|
|
239
|
+
left: 0;
|
|
240
|
+
right: 0;
|
|
241
|
+
background: linear-gradient(transparent, rgba(0,0,0,0.8));
|
|
242
|
+
color: white;
|
|
243
|
+
padding: 1rem;
|
|
244
|
+
transform: translateY(100%);
|
|
245
|
+
transition: transform 0.3s ease;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
.gallery-item:hover .overlay {
|
|
249
|
+
transform: translateY(0);
|
|
250
|
+
}
|
|
251
|
+
`]
|
|
252
|
+
})
|
|
253
|
+
export class ImageGalleryComponent {
|
|
254
|
+
galleryColumns: Column[] = [
|
|
255
|
+
{ device: DeviceSizes.MOBILE, columns: 2 },
|
|
256
|
+
{ device: DeviceSizes.MINI, columns: 3 },
|
|
257
|
+
{ device: DeviceSizes.TABLET, columns: 4 },
|
|
258
|
+
{ device: DeviceSizes.DESKTOP, columns: 6 }
|
|
259
|
+
];
|
|
260
|
+
|
|
261
|
+
images = [
|
|
262
|
+
{ id: 1, url: 'https://picsum.photos/300/300?random=1', alt: 'Random 1', title: 'Image 1' },
|
|
263
|
+
{ id: 2, url: 'https://picsum.photos/300/300?random=2', alt: 'Random 2', title: 'Image 2' },
|
|
264
|
+
{ id: 3, url: 'https://picsum.photos/300/300?random=3', alt: 'Random 3', title: 'Image 3' },
|
|
265
|
+
{ id: 4, url: 'https://picsum.photos/300/300?random=4', alt: 'Random 4', title: 'Image 4' },
|
|
266
|
+
{ id: 5, url: 'https://picsum.photos/300/300?random=5', alt: 'Random 5', title: 'Image 5' },
|
|
267
|
+
{ id: 6, url: 'https://picsum.photos/300/300?random=6', alt: 'Random 6', title: 'Image 6' }
|
|
268
|
+
];
|
|
269
|
+
}
|
|
270
|
+
```
|
|
271
|
+
|
|
272
|
+
#### Example 4: Dashboard Cards
|
|
273
|
+
|
|
274
|
+
```typescript
|
|
275
|
+
import { Component } from '@angular/core';
|
|
276
|
+
import { Column, DeviceSizes } from 'column-fitter';
|
|
277
|
+
|
|
278
|
+
@Component({
|
|
279
|
+
selector: 'app-dashboard',
|
|
280
|
+
template: `
|
|
281
|
+
<div class="dashboard-container">
|
|
282
|
+
<h2>Dashboard</h2>
|
|
283
|
+
|
|
284
|
+
<app-column-fitter
|
|
285
|
+
[columns]="dashboardColumns"
|
|
286
|
+
[gap]="'1rem'"
|
|
287
|
+
[padding'"
|
|
288
|
+
[background]="'1remColor]="'#f8f9fa'"
|
|
289
|
+
[minWidth]="'300px'">
|
|
290
|
+
|
|
291
|
+
<div class="stat-card" *ngFor="let stat of statistics">
|
|
292
|
+
<div class="stat-icon">
|
|
293
|
+
<mat-icon>{{ stat.icon }}</mat-icon>
|
|
294
|
+
</div>
|
|
295
|
+
<div class="stat-content">
|
|
296
|
+
<h3>{{ stat.value }}</h3>
|
|
297
|
+
<p>{{ stat.label }}</p>
|
|
298
|
+
</div>
|
|
299
|
+
</div>
|
|
300
|
+
|
|
301
|
+
</app-column-fitter>
|
|
302
|
+
</div>
|
|
303
|
+
`,
|
|
304
|
+
styles: [`
|
|
305
|
+
.dashboard-container {
|
|
306
|
+
padding: 2rem;
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
.dashboard-container h2 {
|
|
310
|
+
margin-bottom: 2rem;
|
|
311
|
+
color: #333;
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
.stat-card {
|
|
315
|
+
background: white;
|
|
316
|
+
padding: 1.5rem;
|
|
317
|
+
border-radius: 8px;
|
|
318
|
+
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
|
|
319
|
+
display: flex;
|
|
320
|
+
align-items: center;
|
|
321
|
+
gap: 1rem;
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
.stat-icon {
|
|
325
|
+
width: 48px;
|
|
326
|
+
height: 48px;
|
|
327
|
+
border-radius: 50%;
|
|
328
|
+
display: flex;
|
|
329
|
+
align-items: center;
|
|
330
|
+
justify-content: center;
|
|
331
|
+
background: #e3f2fd;
|
|
332
|
+
color: #1976d2;
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
.stat-content h3 {
|
|
336
|
+
margin: 0;
|
|
337
|
+
font-size: 1.5rem;
|
|
338
|
+
font-weight: bold;
|
|
339
|
+
color: #333;
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
.stat-content p {
|
|
343
|
+
margin: 0;
|
|
344
|
+
color: #666;
|
|
345
|
+
font-size: 0.9rem;
|
|
346
|
+
}
|
|
347
|
+
`]
|
|
348
|
+
})
|
|
349
|
+
export class DashboardComponent {
|
|
350
|
+
dashboardColumns: Column[] = [
|
|
351
|
+
{ device: DeviceSizes.MOBILE, columns: 1 },
|
|
352
|
+
{ device: DeviceSizes.TABLET, columns: 2 },
|
|
353
|
+
{ device: DeviceSizes.MINI, columns: 2 },
|
|
354
|
+
{ device: DeviceSizes.DESKTOP, columns: 4 }
|
|
355
|
+
];
|
|
356
|
+
|
|
357
|
+
statistics = [
|
|
358
|
+
{ id: 1, icon: 'people', value: '1,234', label: 'Total Users' },
|
|
359
|
+
{ id: 2, icon: 'shopping_cart', value: '$12,345', label: 'Revenue' },
|
|
360
|
+
{ id: 3, icon: 'trending_up', value: '98.5%', label: 'Growth Rate' },
|
|
361
|
+
{ id: 4, icon: 'assignment', value: '567', label: 'Tasks Completed' },
|
|
362
|
+
{ id: 5, icon: 'star', value: '4.8/5', label: 'Customer Rating' },
|
|
363
|
+
{ id: 6, icon: 'notifications', value: '23', label: 'Pending Alerts' }
|
|
364
|
+
];
|
|
365
|
+
}
|
|
366
|
+
```
|
|
367
|
+
|
|
368
|
+
---
|
|
369
|
+
|
|
370
|
+
## Component API
|
|
371
|
+
|
|
372
|
+
### Inputs
|
|
373
|
+
|
|
374
|
+
| Input | Type | Description | Default |
|
|
375
|
+
| :--- | :--- | :--- | :--- |
|
|
376
|
+
| `padding` | `string` | Padding for the grid container (CSS padding value) | `''` |
|
|
377
|
+
| `margin` | `string` | Margin for the grid container (CSS margin value) | `''` |
|
|
378
|
+
| `backgroundColor` | `string` | Background color for the grid container | `''` |
|
|
379
|
+
| `minWidth` | `string` | Minimum width for auto-fit columns (CSS length value) | `''` |
|
|
380
|
+
| `gap` | `string` | Gap between grid items (CSS gap value) | `'1rem'` |
|
|
381
|
+
| `columns` | `number \| Column[]` | Column configuration - fixed number or device-specific array | `0` |
|
|
382
|
+
|
|
383
|
+
### Dynamic Properties
|
|
384
|
+
|
|
385
|
+
| Property | Type | Description |
|
|
386
|
+
|----------|------|-------------|
|
|
387
|
+
| `gridColumns` | `string` | Current CSS grid-template-columns value |
|
|
388
|
+
| `hasColumns` | `boolean` | Whether valid column configuration exists |
|
|
389
|
+
| `subscriptions` | `Subscription` | RxJS subscription management |
|
|
390
|
+
|
|
391
|
+
---
|
|
392
|
+
|
|
393
|
+
## Model Structures
|
|
394
|
+
|
|
395
|
+
### DeviceSizes Enum
|
|
396
|
+
|
|
397
|
+
```typescript
|
|
398
|
+
export enum DeviceSizes {
|
|
399
|
+
DESKTOP = 'desktop', // Desktop/large screens
|
|
400
|
+
TABLET = 'tablet', // Tablet devices
|
|
401
|
+
MINI = 'mini', // Small tablets/large phones
|
|
402
|
+
MOBILE = 'mobile' // Mobile phones
|
|
403
|
+
}
|
|
404
|
+
```
|
|
405
|
+
|
|
406
|
+
### Column Interface
|
|
407
|
+
|
|
408
|
+
```typescript
|
|
409
|
+
export interface ColumnInterface {
|
|
410
|
+
device: DeviceSizes; // Target device type
|
|
411
|
+
columns: number; // Number of columns for this device
|
|
412
|
+
}
|
|
413
|
+
```
|
|
414
|
+
|
|
415
|
+
### Column Class
|
|
416
|
+
|
|
417
|
+
```typescript
|
|
418
|
+
export class Column implements ColumnInterface {
|
|
419
|
+
constructor(
|
|
420
|
+
public device = DeviceSizes.DESKTOP,
|
|
421
|
+
public columns = 0,
|
|
422
|
+
) {}
|
|
423
|
+
|
|
424
|
+
static adapt(item?: any): Column {
|
|
425
|
+
return new Column(
|
|
426
|
+
item?.device,
|
|
427
|
+
item?.columns
|
|
428
|
+
);
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
```
|
|
432
|
+
|
|
433
|
+
### Usage Examples
|
|
434
|
+
|
|
435
|
+
```typescript
|
|
436
|
+
// Device-specific column configurations
|
|
437
|
+
const responsiveColumns: Column[] = [
|
|
438
|
+
new Column(DeviceSizes.MOBILE, 1), // 1 column on mobile
|
|
439
|
+
new Column(DeviceSizes.MINI, 2), // 2 columns on mini devices
|
|
440
|
+
new Column(DeviceSizes.TABLET, 3), // 3 columns on tablets
|
|
441
|
+
new Column(DeviceSizes.DESKTOP, 4) // 4 columns on desktop
|
|
442
|
+
];
|
|
443
|
+
|
|
444
|
+
// Using adapt method
|
|
445
|
+
const adaptedColumns = [
|
|
446
|
+
Column.adapt({ device: DeviceSizes.MOBILE, columns: 1 }),
|
|
447
|
+
Column.adapt({ device: DeviceSizes.TABLET, columns: 3 })
|
|
448
|
+
];
|
|
449
|
+
|
|
450
|
+
// Mixed configuration
|
|
451
|
+
const mixedColumns: (number | Column[])[] = [
|
|
452
|
+
3, // Fixed 3 columns for all devices
|
|
453
|
+
// OR
|
|
454
|
+
[
|
|
455
|
+
{ device: DeviceSizes.MOBILE, columns: 1 },
|
|
456
|
+
{ device: DeviceSizes.TABLET, columns: 2 },
|
|
457
|
+
{ device: DeviceSizes.DESKTOP, columns: 4 }
|
|
458
|
+
]
|
|
459
|
+
];
|
|
460
|
+
```
|
|
461
|
+
|
|
462
|
+
---
|
|
463
|
+
|
|
464
|
+
## Grid Layout Logic
|
|
465
|
+
|
|
466
|
+
### CSS Grid Generation
|
|
467
|
+
|
|
468
|
+
The component automatically generates CSS Grid templates based on the configuration:
|
|
469
|
+
|
|
470
|
+
#### Fixed Column Mode
|
|
471
|
+
```typescript
|
|
472
|
+
// Input: columns = 3
|
|
473
|
+
// Output: gridColumns = 'repeat(3, 1fr)'
|
|
474
|
+
|
|
475
|
+
// Input: columns = 0 (disabled)
|
|
476
|
+
// Output: gridColumns = 'repeat(auto-fit, minmax(250px, 1fr))'
|
|
477
|
+
```
|
|
478
|
+
|
|
479
|
+
#### Device-Specific Mode
|
|
480
|
+
```typescript
|
|
481
|
+
// Device detection logic
|
|
482
|
+
if (device === 'desktop' && found(DeviceSizes.DESKTOP)) {
|
|
483
|
+
const cols = found(DeviceSizes.DESKTOP) as Column;
|
|
484
|
+
return `repeat(${cols.columns}, 1fr)`;
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
// Fallback for unmatched devices
|
|
488
|
+
return `repeat(auto-fit, minmax(${this.minWidth}, 1fr))`;
|
|
489
|
+
```
|
|
490
|
+
|
|
491
|
+
### Device Detection Flow
|
|
492
|
+
|
|
493
|
+
1. **Screen Observer Integration**: Subscribes to `screenObserverService.device$`
|
|
494
|
+
2. **Change Detection**: Uses RxJS `distinctUntilChanged()` to prevent unnecessary updates
|
|
495
|
+
3. **Column Calculation**: Calls `getGridTemplateColumns()` with current device
|
|
496
|
+
4. **Grid Update**: Updates `gridColumns` property with new CSS Grid value
|
|
497
|
+
|
|
498
|
+
---
|
|
499
|
+
|
|
500
|
+
## Module Configuration
|
|
501
|
+
|
|
502
|
+
### ColumnFitterModule
|
|
503
|
+
|
|
504
|
+
**No Global Configuration Required**
|
|
505
|
+
|
|
506
|
+
The `ColumnFitterModule` does not provide a `forRoot()` method or global configuration options. All configuration is done at the component level through input properties.
|
|
507
|
+
|
|
508
|
+
#### Module Structure
|
|
509
|
+
|
|
510
|
+
```typescript
|
|
511
|
+
@NgModule({
|
|
512
|
+
declarations: [
|
|
513
|
+
ColumnFitterComponent,
|
|
514
|
+
ColumnFitterDemoComponent
|
|
515
|
+
],
|
|
516
|
+
imports: [
|
|
517
|
+
// Dependencies are imported by the consuming application
|
|
518
|
+
// screen-observer must be installed separately
|
|
519
|
+
],
|
|
520
|
+
exports: [
|
|
521
|
+
ColumnFitterComponent,
|
|
522
|
+
ColumnFitterDemoComponent
|
|
523
|
+
]
|
|
524
|
+
})
|
|
525
|
+
export class ColumnFitterModule { }
|
|
526
|
+
```
|
|
527
|
+
|
|
528
|
+
#### Dependencies
|
|
529
|
+
|
|
530
|
+
- **screen-observer**: Device detection service (must be installed separately)
|
|
531
|
+
- **@angular/core**: Core Angular functionality
|
|
532
|
+
- **rxjs**: Reactive programming utilities for device change detection
|
|
533
|
+
|
|
534
|
+
---
|
|
535
|
+
|
|
536
|
+
## Styling and Customization
|
|
537
|
+
|
|
538
|
+
### CSS Grid Styling
|
|
539
|
+
|
|
540
|
+
The component uses CSS Grid with the following base styles:
|
|
541
|
+
|
|
542
|
+
```scss
|
|
543
|
+
:host {
|
|
544
|
+
display: block;
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
.grid-container {
|
|
548
|
+
display: grid;
|
|
549
|
+
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
|
|
550
|
+
gap: 1rem;
|
|
551
|
+
}
|
|
552
|
+
```
|
|
553
|
+
|
|
554
|
+
### Custom Styling Examples
|
|
555
|
+
|
|
556
|
+
#### Custom Grid Appearance
|
|
557
|
+
|
|
558
|
+
```scss
|
|
559
|
+
// Enhanced grid styling
|
|
560
|
+
:host ::ng-deep .grid-container {
|
|
561
|
+
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
|
562
|
+
border-radius: 12px;
|
|
563
|
+
padding: 2rem;
|
|
564
|
+
|
|
565
|
+
.grid-item {
|
|
566
|
+
background: white;
|
|
567
|
+
border-radius: 8px;
|
|
568
|
+
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
|
|
569
|
+
transition: transform 0.2s ease, box-shadow 0.2s ease;
|
|
570
|
+
|
|
571
|
+
&:hover {
|
|
572
|
+
transform: translateY(-2px);
|
|
573
|
+
box-shadow: 0 8px 25px rgba(0, 0, 0, 0.15);
|
|
574
|
+
}
|
|
575
|
+
}
|
|
576
|
+
}
|
|
577
|
+
```
|
|
578
|
+
|
|
579
|
+
#### Responsive Gap Adjustment
|
|
580
|
+
|
|
581
|
+
```scss
|
|
582
|
+
// Dynamic gaps based on device
|
|
583
|
+
:host ::ng-deep .grid-container {
|
|
584
|
+
gap: var(--grid-gap, 1rem);
|
|
585
|
+
|
|
586
|
+
@media (max-width: 768px) {
|
|
587
|
+
--grid-gap: 0.5rem;
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
@media (min-width: 1200px) {
|
|
591
|
+
--grid-gap: 1.5rem;
|
|
592
|
+
}
|
|
593
|
+
}
|
|
594
|
+
```
|
|
595
|
+
|
|
596
|
+
### Advanced Layout Patterns
|
|
597
|
+
|
|
598
|
+
#### Masonry-Style Layout
|
|
599
|
+
|
|
600
|
+
```scss
|
|
601
|
+
// CSS Grid with masonry-like behavior
|
|
602
|
+
.masonry-grid {
|
|
603
|
+
display: grid;
|
|
604
|
+
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
|
|
605
|
+
grid-auto-rows: 200px;
|
|
606
|
+
gap: 1rem;
|
|
607
|
+
|
|
608
|
+
.masonry-item {
|
|
609
|
+
grid-row-end: span var(--row-span, 1);
|
|
610
|
+
|
|
611
|
+
&.large {
|
|
612
|
+
--row-span: 2;
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
&.wide {
|
|
616
|
+
grid-column-end: span 2;
|
|
617
|
+
}
|
|
618
|
+
}
|
|
619
|
+
}
|
|
620
|
+
```
|
|
621
|
+
|
|
622
|
+
#### Card-Based Layout
|
|
623
|
+
|
|
624
|
+
```scss
|
|
625
|
+
// Card layout with consistent height
|
|
626
|
+
.card-grid {
|
|
627
|
+
display: grid;
|
|
628
|
+
grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
|
|
629
|
+
gap: 1.5rem;
|
|
630
|
+
align-items: stretch;
|
|
631
|
+
|
|
632
|
+
.card {
|
|
633
|
+
display: flex;
|
|
634
|
+
flex-direction: column;
|
|
635
|
+
height: 100%;
|
|
636
|
+
background: white;
|
|
637
|
+
border-radius: 8px;
|
|
638
|
+
overflow: hidden;
|
|
639
|
+
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
|
|
640
|
+
|
|
641
|
+
.card-image {
|
|
642
|
+
height: 200px;
|
|
643
|
+
overflow: hidden;
|
|
644
|
+
|
|
645
|
+
img {
|
|
646
|
+
width: 100%;
|
|
647
|
+
height: 100%;
|
|
648
|
+
object-fit: cover;
|
|
649
|
+
}
|
|
650
|
+
}
|
|
651
|
+
|
|
652
|
+
.card-content {
|
|
653
|
+
padding: 1.5rem;
|
|
654
|
+
flex: 1;
|
|
655
|
+
display: flex;
|
|
656
|
+
flex-direction: column;
|
|
657
|
+
}
|
|
658
|
+
}
|
|
659
|
+
}
|
|
660
|
+
```
|
|
661
|
+
|
|
662
|
+
---
|
|
663
|
+
|
|
664
|
+
## Integration Examples
|
|
665
|
+
|
|
666
|
+
### With Angular Material
|
|
667
|
+
|
|
668
|
+
```typescript
|
|
669
|
+
import { Component } from '@angular/core';
|
|
670
|
+
import { Column, DeviceSizes } from 'column-fitter';
|
|
671
|
+
|
|
672
|
+
@Component({
|
|
673
|
+
selector: 'app-material-grid',
|
|
674
|
+
template: `
|
|
675
|
+
<app-column-fitter
|
|
676
|
+
[columns]="materialColumns"
|
|
677
|
+
[gap]="'1rem'"
|
|
678
|
+
[padding]="'1rem'">
|
|
679
|
+
|
|
680
|
+
<mat-card class="grid-card" *ngFor="let item of materialItems">
|
|
681
|
+
<mat-card-header>
|
|
682
|
+
<mat-card-title>{{ item.title }}</mat-card-title>
|
|
683
|
+
<mat-card-subtitle>{{ item.subtitle }}</mat-card-subtitle>
|
|
684
|
+
</mat-card-header>
|
|
685
|
+
|
|
686
|
+
<img mat-card-image [src]="item.image" [alt]="item.title">
|
|
687
|
+
|
|
688
|
+
<mat-card-content>
|
|
689
|
+
<p>{{ item.description }}</p>
|
|
690
|
+
</mat-card-content>
|
|
691
|
+
|
|
692
|
+
<mat-card-actions>
|
|
693
|
+
<button mat-button>LIKE</button>
|
|
694
|
+
<button mat-button>SHARE</button>
|
|
695
|
+
</mat-card-actions>
|
|
696
|
+
</mat-card>
|
|
697
|
+
|
|
698
|
+
</app-column-fitter>
|
|
699
|
+
`
|
|
700
|
+
})
|
|
701
|
+
export class MaterialGridComponent {
|
|
702
|
+
materialColumns: Column[] = [
|
|
703
|
+
{ device: DeviceSizes.MOBILE, columns: 1 },
|
|
704
|
+
{ device: DeviceSizes.TABLET, columns: 2 },
|
|
705
|
+
{ device: DeviceSizes.DESKTOP, columns: 3 }
|
|
706
|
+
];
|
|
707
|
+
|
|
708
|
+
materialItems = [
|
|
709
|
+
{
|
|
710
|
+
title: 'Card 1',
|
|
711
|
+
subtitle: 'Subtitle 1',
|
|
712
|
+
description: 'Description for card 1',
|
|
713
|
+
image: 'https://picsum.photos/400/200?random=1'
|
|
714
|
+
},
|
|
715
|
+
// ... more items
|
|
716
|
+
];
|
|
717
|
+
}
|
|
718
|
+
```
|
|
719
|
+
|
|
720
|
+
### With Dynamic Content
|
|
721
|
+
|
|
722
|
+
```typescript
|
|
723
|
+
import { Component } from '@angular/core';
|
|
724
|
+
import { Column, DeviceSizes } from 'column-fitter';
|
|
725
|
+
|
|
726
|
+
@Component({
|
|
727
|
+
selector: 'app-dynamic-content',
|
|
728
|
+
template: `
|
|
729
|
+
<div class="controls">
|
|
730
|
+
<button (click)="addItem()">Add Item</button>
|
|
731
|
+
<button (click)="removeItem()">Remove Item</button>
|
|
732
|
+
<select [(ngModel)]="selectedLayout" (change)="changeLayout()">
|
|
733
|
+
<option value="mobile1">Mobile: 1 Col</option>
|
|
734
|
+
<option value="tablet3">Tablet: 3 Col</option>
|
|
735
|
+
<option value="desktop4">Desktop: 4 Col</option>
|
|
736
|
+
</select>
|
|
737
|
+
</div>
|
|
738
|
+
|
|
739
|
+
<app-column-fitter
|
|
740
|
+
[columns]="currentColumns"
|
|
741
|
+
[gap]="'1rem'"
|
|
742
|
+
[padding]="'1rem'">
|
|
743
|
+
|
|
744
|
+
<div class="dynamic-item" *ngFor="let item of dynamicItems; trackBy: trackById">
|
|
745
|
+
<h3>{{ item.title }}</h3>
|
|
746
|
+
<p>{{ item.content }}</p>
|
|
747
|
+
<small>ID: {{ item.id }}</small>
|
|
748
|
+
</div>
|
|
749
|
+
|
|
750
|
+
</app-column-fitter>
|
|
751
|
+
`
|
|
752
|
+
})
|
|
753
|
+
export class DynamicContentComponent {
|
|
754
|
+
currentColumns: Column[] = [
|
|
755
|
+
{ device: DeviceSizes.MOBILE, columns: 1 },
|
|
756
|
+
{ device: DeviceSizes.TABLET, columns: 3 },
|
|
757
|
+
{ device: DeviceSizes.DESKTOP, columns: 4 }
|
|
758
|
+
];
|
|
759
|
+
|
|
760
|
+
selectedLayout = 'tablet3';
|
|
761
|
+
dynamicItems = [
|
|
762
|
+
{ id: 1, title: 'Item 1', content: 'Content 1' },
|
|
763
|
+
{ id: 2, title: 'Item 2', content: 'Content 2' },
|
|
764
|
+
{ id: 3, title: 'Item 3', content: 'Content 3' }
|
|
765
|
+
];
|
|
766
|
+
|
|
767
|
+
addItem() {
|
|
768
|
+
const newItem = {
|
|
769
|
+
id: Date.now(),
|
|
770
|
+
title: `Item ${this.dynamicItems.length + 1}`,
|
|
771
|
+
content: `Content ${this.dynamicItems.length + 1}`
|
|
772
|
+
};
|
|
773
|
+
this.dynamicItems = [...this.dynamicItems, newItem];
|
|
774
|
+
}
|
|
775
|
+
|
|
776
|
+
removeItem() {
|
|
777
|
+
if (this.dynamicItems.length > 0) {
|
|
778
|
+
this.dynamicItems = this.dynamicItems.slice(0, -1);
|
|
779
|
+
}
|
|
780
|
+
}
|
|
781
|
+
|
|
782
|
+
changeLayout() {
|
|
783
|
+
switch (this.selectedLayout) {
|
|
784
|
+
case 'mobile1':
|
|
785
|
+
this.currentColumns = [
|
|
786
|
+
{ device: DeviceSizes.MOBILE, columns: 1 },
|
|
787
|
+
{ device: DeviceSizes.TABLET, columns: 1 },
|
|
788
|
+
{ device: DeviceSizes.DESKTOP, columns: 1 }
|
|
789
|
+
];
|
|
790
|
+
break;
|
|
791
|
+
case 'tablet3':
|
|
792
|
+
this.currentColumns = [
|
|
793
|
+
{ device: DeviceSizes.MOBILE, columns: 1 },
|
|
794
|
+
{ device: DeviceSizes.TABLET, columns: 3 },
|
|
795
|
+
{ device: DeviceSizes.DESKTOP, columns: 3 }
|
|
796
|
+
];
|
|
797
|
+
break;
|
|
798
|
+
case 'desktop4':
|
|
799
|
+
this.currentColumns = [
|
|
800
|
+
{ device: DeviceSizes.MOBILE, columns: 1 },
|
|
801
|
+
{ device: DeviceSizes.TABLET, columns: 2 },
|
|
802
|
+
{ device: DeviceSizes.DESKTOP, columns: 4 }
|
|
803
|
+
];
|
|
804
|
+
break;
|
|
805
|
+
}
|
|
806
|
+
}
|
|
807
|
+
|
|
808
|
+
trackById(index: number, item: any): any {
|
|
809
|
+
return item.id;
|
|
810
|
+
}
|
|
811
|
+
}
|
|
812
|
+
```
|
|
813
|
+
|
|
814
|
+
---
|
|
815
|
+
|
|
816
|
+
## Performance Optimization
|
|
817
|
+
|
|
818
|
+
### Change Detection
|
|
819
|
+
|
|
820
|
+
The component uses several performance optimizations:
|
|
821
|
+
|
|
822
|
+
1. **distinctUntilChanged()**: Prevents duplicate device updates
|
|
823
|
+
2. **Efficient Grid Calculation**: Only recalculates when device actually changes
|
|
824
|
+
3. **Subscription Management**: Properly cleans up RxJS subscriptions
|
|
825
|
+
|
|
826
|
+
### Memory Management
|
|
827
|
+
|
|
828
|
+
```typescript
|
|
829
|
+
ngOnDestroy() {
|
|
830
|
+
// Clean up subscriptions to prevent memory leaks
|
|
831
|
+
this.subscriptions.unsubscribe();
|
|
832
|
+
}
|
|
833
|
+
```
|
|
834
|
+
|
|
835
|
+
### Large Dataset Handling
|
|
836
|
+
|
|
837
|
+
```typescript
|
|
838
|
+
// Use trackBy for large lists
|
|
839
|
+
@Component({
|
|
840
|
+
template: `
|
|
841
|
+
<app-column-fitter [columns]="columns">
|
|
842
|
+
<div *ngFor="let item of largeDataset; trackBy: trackById">
|
|
843
|
+
{{ item.name }}
|
|
844
|
+
</div>
|
|
845
|
+
</app-column-fitter>
|
|
846
|
+
`
|
|
847
|
+
})
|
|
848
|
+
export class LargeDatasetComponent {
|
|
849
|
+
trackById(index: number, item: any): any {
|
|
850
|
+
return item.id; // Use unique identifier
|
|
851
|
+
}
|
|
852
|
+
}
|
|
853
|
+
```
|
|
854
|
+
|
|
855
|
+
---
|
|
856
|
+
|
|
857
|
+
## Testing
|
|
858
|
+
|
|
859
|
+
### Unit Testing Example
|
|
860
|
+
|
|
861
|
+
```typescript
|
|
862
|
+
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
|
863
|
+
import { ColumnFitterComponent } from './column-fitter.component';
|
|
864
|
+
import { Column, DeviceSizes } from './models/column.model';
|
|
865
|
+
import { ScreenObserverService } from 'screen-observer';
|
|
866
|
+
|
|
867
|
+
describe('ColumnFitterComponent', () => {
|
|
868
|
+
let component: ColumnFitterComponent;
|
|
869
|
+
let fixture: ComponentFixture<ColumnFitterComponent>;
|
|
870
|
+
let mockScreenObserverService: jasmine.SpyObj<ScreenObserverService>;
|
|
871
|
+
|
|
872
|
+
beforeEach(async () => {
|
|
873
|
+
const screenObserverSpy = jasmine.createSpyObj('ScreenObserverService', ['device$']);
|
|
874
|
+
|
|
875
|
+
await TestBed.configureTestingModule({
|
|
876
|
+
declarations: [ ColumnFitterComponent ],
|
|
877
|
+
providers: [
|
|
878
|
+
{ provide: ScreenObserverService, useValue: screenObserverSpy }
|
|
879
|
+
]
|
|
880
|
+
}).compileComponents();
|
|
881
|
+
|
|
882
|
+
fixture = TestBed.createComponent(ColumnFitterComponent);
|
|
883
|
+
component = fixture.componentInstance;
|
|
884
|
+
mockScreenObserverService = TestBed.inject(ScreenObserverService) as jasmine.SpyObj<ScreenObserverService>;
|
|
885
|
+
});
|
|
886
|
+
|
|
887
|
+
it('should create', () => {
|
|
888
|
+
expect(component).toBeTruthy();
|
|
889
|
+
});
|
|
890
|
+
|
|
891
|
+
it('should set fixed columns correctly', () => {
|
|
892
|
+
component.columns = 3;
|
|
893
|
+
fixture.detectChanges();
|
|
894
|
+
|
|
895
|
+
const result = component.getGridTemplateColumns('desktop');
|
|
896
|
+
expect(result).toBe('repeat(3, 1fr)');
|
|
897
|
+
});
|
|
898
|
+
|
|
899
|
+
it('should handle device-specific columns', () => {
|
|
900
|
+
component.columns = [
|
|
901
|
+
{ device: DeviceSizes.MOBILE, columns: 1 },
|
|
902
|
+
{ device: DeviceSizes.TABLET, columns: 3 },
|
|
903
|
+
{ device: DeviceSizes.DESKTOP, columns: 4 }
|
|
904
|
+
];
|
|
905
|
+
|
|
906
|
+
fixture.detectChanges();
|
|
907
|
+
|
|
908
|
+
expect(component.getGridTemplateColumns('mobile')).toBe('repeat(1, 1fr)');
|
|
909
|
+
expect(component.getGridTemplateColumns('tablet')).toBe('repeat(3, 1fr)');
|
|
910
|
+
expect(component.getGridTemplateColumns('desktop')).toBe('repeat(4, 1fr)');
|
|
911
|
+
});
|
|
912
|
+
|
|
913
|
+
it('should fallback to auto-fit when no matching device', () => {
|
|
914
|
+
component.columns = [
|
|
915
|
+
{ device: DeviceSizes.MOBILE, columns: 1 }
|
|
916
|
+
];
|
|
917
|
+
component.minWidth = '200px';
|
|
918
|
+
|
|
919
|
+
fixture.detectChanges();
|
|
920
|
+
|
|
921
|
+
const result = component.getGridTemplateColumns('desktop');
|
|
922
|
+
expect(result).toBe('repeat(auto-fit, minmax(200px, 1fr))');
|
|
923
|
+
});
|
|
924
|
+
|
|
925
|
+
it('should handle disabled state (columns = 0)', () => {
|
|
926
|
+
component.columns = 0;
|
|
927
|
+
component.minWidth = '250px';
|
|
928
|
+
|
|
929
|
+
fixture.detectChanges();
|
|
930
|
+
|
|
931
|
+
const result = component.getGridTemplateColumns('desktop');
|
|
932
|
+
expect(result).toBe('repeat(auto-fit, minmax(250px, 1fr))');
|
|
933
|
+
});
|
|
934
|
+
});
|
|
935
|
+
```
|
|
936
|
+
|
|
937
|
+
---
|
|
938
|
+
|
|
939
|
+
## Troubleshooting
|
|
940
|
+
|
|
941
|
+
### Common Issues
|
|
942
|
+
|
|
943
|
+
1. **No columns showing**: Ensure screen-observer package is installed and configured
|
|
944
|
+
2. **Layout not updating**: Check that device$ observable is emitting values
|
|
945
|
+
3. **Styling issues**: Verify CSS Grid is supported in target browsers
|
|
946
|
+
4. **Performance issues**: Consider using OnPush change detection for large datasets
|
|
947
|
+
|
|
948
|
+
### Debug Mode
|
|
949
|
+
|
|
950
|
+
```typescript
|
|
951
|
+
@Component({
|
|
952
|
+
template: `
|
|
953
|
+
<div class="debug-info">
|
|
954
|
+
Current Device: {{ currentDevice }}<br>
|
|
955
|
+
Grid Columns: {{ gridColumns }}<br>
|
|
956
|
+
Has Columns: {{ hasColumns }}<br>
|
|
957
|
+
Columns Config: {{ columns | json }}
|
|
958
|
+
</div>
|
|
959
|
+
|
|
960
|
+
<app-column-fitter
|
|
961
|
+
[columns]="columns"
|
|
962
|
+
[gap]="gap"
|
|
963
|
+
[minWidth]="minWidth">
|
|
964
|
+
<!-- Content -->
|
|
965
|
+
</app-column-fitter>
|
|
966
|
+
`
|
|
967
|
+
})
|
|
968
|
+
export class DebugColumnFitterComponent {
|
|
969
|
+
currentDevice = '';
|
|
970
|
+
gridColumns = '';
|
|
971
|
+
hasColumns = false;
|
|
972
|
+
columns: any = [];
|
|
973
|
+
gap = '1rem';
|
|
974
|
+
minWidth = '250px';
|
|
975
|
+
|
|
976
|
+
constructor() {
|
|
977
|
+
// Add debugging logic
|
|
978
|
+
}
|
|
979
|
+
}
|
|
980
|
+
```
|
|
981
|
+
|
|
982
|
+
### Performance Monitoring
|
|
983
|
+
|
|
984
|
+
```typescript
|
|
985
|
+
ngOnInit() {
|
|
986
|
+
const start = performance.now();
|
|
987
|
+
|
|
988
|
+
this.subscriptions.add(
|
|
989
|
+
this.screenObserverService.device$.subscribe((screen: string) => {
|
|
990
|
+
const updateStart = performance.now();
|
|
991
|
+
this.gridColumns = this.getGridTemplateColumns(screen);
|
|
992
|
+
const updateEnd = performance.now();
|
|
993
|
+
|
|
994
|
+
console.log(`Grid update took ${updateEnd - updateStart}ms for device: ${screen}`);
|
|
995
|
+
})
|
|
996
|
+
);
|
|
997
|
+
|
|
998
|
+
const initEnd = performance.now();
|
|
999
|
+
console.log(`ColumnFitter initialization took ${initEnd - start}ms`);
|
|
1000
|
+
}
|
|
1001
|
+
```
|
|
1002
|
+
|
|
1003
|
+
---
|
|
1004
|
+
|
|
1005
|
+
## Browser Support
|
|
1006
|
+
|
|
1007
|
+
### CSS Grid Support
|
|
1008
|
+
|
|
1009
|
+
The component requires CSS Grid support, which is available in:
|
|
1010
|
+
|
|
1011
|
+
- **Chrome**: 57+ (March 2017)
|
|
1012
|
+
- **Firefox**: 52+ (March 2017)
|
|
1013
|
+
- **Safari**: 10.1+ (March 2017)
|
|
1014
|
+
- **Edge**: 16+ (October 2017)
|
|
1015
|
+
|
|
1016
|
+
### Fallback for Older Browsers
|
|
1017
|
+
|
|
1018
|
+
```scss
|
|
1019
|
+
// CSS Grid fallback using Flexbox
|
|
1020
|
+
.grid-container {
|
|
1021
|
+
display: flex;
|
|
1022
|
+
flex-wrap: wrap;
|
|
1023
|
+
margin: -0.5rem;
|
|
1024
|
+
|
|
1025
|
+
.grid-item {
|
|
1026
|
+
flex: 1 1 250px; /* Minimum width of 250px */
|
|
1027
|
+
margin: 0.5rem;
|
|
1028
|
+
|
|
1029
|
+
@supports (display: grid) {
|
|
1030
|
+
display: grid;
|
|
1031
|
+
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
|
|
1032
|
+
margin: 0;
|
|
1033
|
+
}
|
|
1034
|
+
}
|
|
1035
|
+
}
|
|
1036
|
+
```
|
|
1037
|
+
|
|
1038
|
+
### Progressive Enhancement
|
|
1039
|
+
|
|
1040
|
+
```typescript
|
|
1041
|
+
// JavaScript fallback for older browsers
|
|
1042
|
+
if (!CSS.supports('display', 'grid')) {
|
|
1043
|
+
// Apply Flexbox fallback
|
|
1044
|
+
this.applyFlexboxFallback();
|
|
1045
|
+
}
|
|
1046
|
+
```
|