Skip to content

Commit b425e15

Browse files
Copilotk3nsei
andcommitted
Fix formatting issues with prettier
Co-authored-by: k3nsei <190422+k3nsei@users.noreply.github.com>
1 parent 92a45fc commit b425e15

File tree

1 file changed

+81
-90
lines changed

1 file changed

+81
-90
lines changed

.github/copilot-instructions.md

Lines changed: 81 additions & 90 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
# GitHub Copilot Instructions for ng-in-viewport
22

33
**Angular Development Guidelines**
4-
*Ignore current project patterns - use only latest Angular v20+ standards and best practices*
4+
_Ignore current project patterns - use only latest Angular v20+ standards and best practices_
55

66
## Project Overview
77

@@ -89,6 +89,7 @@ npm run serve:example # Example app - localhost:4300
8989
```
9090

9191
**Type Standards**:
92+
9293
- NEVER use `any` - use `unknown` for uncertain types
9394
- Use `satisfies` operator for type checking with inference
9495
- Prefer `readonly` for all data that shouldn't be mutated
@@ -147,12 +148,12 @@ export class ViewportElementComponent {
147148
protected readonly isInViewport = signal<boolean>(false);
148149
protected readonly visibilityRatio = signal<number>(0);
149150
private readonly lastEntry = signal<IntersectionObserverEntry | null>(null);
150-
151+
151152
// Computed values - derived state
152-
protected readonly opacity = computed(() =>
153-
this.visibilityRatio() * 0.8 + 0.2
153+
protected readonly opacity = computed(
154+
() => this.visibilityRatio() * 0.8 + 0.2
154155
);
155-
156+
156157
protected readonly viewportState = computed(() =>
157158
this.isInViewport() ? 'visible' : 'hidden'
158159
);
@@ -179,18 +180,15 @@ export class ViewportElementComponent {
179180

180181
private setupViewportObserver(): void {
181182
// Implementation with Intersection Observer
182-
this.viewportService.observe(
183-
this.elementRef.nativeElement,
184-
{
185-
threshold: this.threshold(),
186-
rootMargin: this.rootMargin(),
187-
callback: (entry) => {
188-
this.isInViewport.set(entry.isIntersecting);
189-
this.visibilityRatio.set(entry.intersectionRatio * 100);
190-
this.lastEntry.set(entry);
191-
},
192-
}
193-
);
183+
this.viewportService.observe(this.elementRef.nativeElement, {
184+
threshold: this.threshold(),
185+
rootMargin: this.rootMargin(),
186+
callback: (entry) => {
187+
this.isInViewport.set(entry.isIntersecting);
188+
this.visibilityRatio.set(entry.intersectionRatio * 100);
189+
this.lastEntry.set(entry);
190+
},
191+
});
194192
}
195193
}
196194
```
@@ -218,7 +216,7 @@ interface ViewportGlobalConfig {
218216
export class ViewportService {
219217
private readonly document = inject(DOCUMENT);
220218
private readonly platformId = inject(PLATFORM_ID);
221-
219+
222220
// Signal-based state management
223221
private readonly _elements = signal<Map<Element, ViewportConfig>>(new Map());
224222
private readonly _globalConfig = signal<ViewportGlobalConfig>({
@@ -246,8 +244,8 @@ export class ViewportService {
246244
}
247245

248246
const fullConfig = { ...this._globalConfig(), ...config } as ViewportConfig;
249-
250-
this._elements.update(elements => {
247+
248+
this._elements.update((elements) => {
251249
const newElements = new Map(elements);
252250
newElements.set(element, fullConfig);
253251
return newElements;
@@ -260,7 +258,7 @@ export class ViewportService {
260258
}
261259

262260
unobserve(element: Element): void {
263-
this._elements.update(elements => {
261+
this._elements.update((elements) => {
264262
const newElements = new Map(elements);
265263
newElements.delete(element);
266264
return newElements;
@@ -283,8 +281,8 @@ export class ViewportService {
283281

284282
private handleIntersection(entries: IntersectionObserverEntry[]): void {
285283
const elements = this._elements();
286-
287-
entries.forEach(entry => {
284+
285+
entries.forEach((entry) => {
288286
const config = elements.get(entry.target);
289287
if (config?.callback) {
290288
config.callback(entry);
@@ -301,70 +299,65 @@ export class ViewportService {
301299
```html
302300
<!-- Conditional rendering -->
303301
@if (isLoading()) {
304-
<loading-spinner />
302+
<loading-spinner />
305303
} @else if (hasError()) {
306-
<error-message [error]="error()" />
304+
<error-message [error]="error()" />
307305
} @else {
308-
<content-display [data]="data()" />
306+
<content-display [data]="data()" />
309307
}
310308

311309
<!-- Iteration -->
312310
@for (item of items(); track item.id) {
313-
<item-card
314-
[item]="item"
315-
[index]="$index"
316-
[isLast]="$last"
317-
(action)="handleAction($event, item)" />
311+
<item-card
312+
[item]="item"
313+
[index]="$index"
314+
[isLast]="$last"
315+
(action)="handleAction($event, item)" />
318316
} @empty {
319-
<empty-state message="No items found" />
317+
<empty-state message="No items found" />
320318
}
321319

322320
<!-- Switch statements -->
323-
@switch (status()) {
324-
@case ('loading') { <loading-state /> }
325-
@case ('error') { <error-state [error]="error()" /> }
326-
@case ('success') { <success-state [data]="data()" /> }
327-
@default { <unknown-state /> }
328-
}
321+
@switch (status()) { @case ('loading') { <loading-state /> } @case ('error') {
322+
<error-state [error]="error()" /> } @case ('success') {
323+
<success-state [data]="data()" /> } @default { <unknown-state /> } }
329324
```
330325

331326
**Binding Patterns**: Use direct property and class bindings:
332327

333328
```html
334329
<!-- Property bindings -->
335-
<div
330+
<div
336331
[class.active]="isActive()"
337332
[class.disabled]="isDisabled()"
338333
[attr.aria-expanded]="isExpanded()"
339334
[style.opacity]="opacity()"
340335
[style.transform]="transform()">
341-
342-
<!-- Event bindings with proper typing -->
343-
<button
344-
(click)="handleClick($event)"
345-
(keydown.enter)="handleEnter()"
346-
(keydown.space)="handleSpace()">
347-
{{ buttonText() }}
348-
</button>
349-
350-
<!-- Signal-based input binding -->
351-
<input
352-
[value]="searchTerm()"
353-
(input)="searchTerm.set($event.target.value)" />
336+
<!-- Event bindings with proper typing -->
337+
<button
338+
(click)="handleClick($event)"
339+
(keydown.enter)="handleEnter()"
340+
(keydown.space)="handleSpace()">
341+
{{ buttonText() }}
342+
</button>
343+
344+
<!-- Signal-based input binding -->
345+
<input [value]="searchTerm()" (input)="searchTerm.set($event.target.value)"
346+
/></div>
354347
```
355348

356349
### Directive Patterns
357350

358351
```typescript
359-
import {
360-
Directive,
361-
effect,
362-
inject,
363-
input,
352+
import {
353+
Directive,
354+
effect,
355+
inject,
356+
input,
364357
output,
365358
signal,
366359
ElementRef,
367-
OnDestroy
360+
OnDestroy,
368361
} from '@angular/core';
369362

370363
@Directive({
@@ -414,18 +407,15 @@ export class ViewportObserverDirective implements OnDestroy {
414407

415408
private setupObserver(): void {
416409
this.cleanup?.();
417-
418-
this.cleanup = this.viewportService.observe(
419-
this.elementRef.nativeElement,
420-
{
421-
threshold: this.threshold(),
422-
rootMargin: this.rootMargin(),
423-
callback: (entry) => {
424-
this.isInViewport.set(entry.isIntersecting);
425-
this.lastEntry.set(entry);
426-
},
427-
}
428-
);
410+
411+
this.cleanup = this.viewportService.observe(this.elementRef.nativeElement, {
412+
threshold: this.threshold(),
413+
rootMargin: this.rootMargin(),
414+
callback: (entry) => {
415+
this.isInViewport.set(entry.isIntersecting);
416+
this.lastEntry.set(entry);
417+
},
418+
});
429419
}
430420

431421
private readonly lastEntry = signal<IntersectionObserverEntry | null>(null);
@@ -456,31 +446,31 @@ describe('ViewportElementComponent', () => {
456446

457447
it('should emit visibility changes when signal updates', () => {
458448
const emitSpy = jest.spyOn(component.visibilityChange, 'emit');
459-
449+
460450
// Update signal inputs directly
461451
fixture.componentRef.setInput('threshold', 0.8);
462452
fixture.detectChanges();
463-
453+
464454
// Test signal-based state changes
465455
component['isInViewport'].set(true);
466456
component['visibilityRatio'].set(75);
467-
457+
468458
expect(component['opacity']()).toBe(0.8); // 0.75 * 0.8 + 0.2
469459
});
470460

471461
it('should compute opacity based on visibility ratio', () => {
472462
// Test computed signals
473463
component['visibilityRatio'].set(50);
474464
expect(component['opacity']()).toBe(0.6); // 0.5 * 0.8 + 0.2
475-
465+
476466
component['visibilityRatio'].set(100);
477467
expect(component['opacity']()).toBe(1.0); // 1.0 * 0.8 + 0.2
478468
});
479469

480470
it('should update viewport state based on visibility', () => {
481471
component['isInViewport'].set(true);
482472
expect(component['viewportState']()).toBe('visible');
483-
473+
484474
component['isInViewport'].set(false);
485475
expect(component['viewportState']()).toBe('hidden');
486476
});
@@ -500,9 +490,7 @@ describe('ViewportService', () => {
500490

501491
beforeEach(() => {
502492
TestBed.configureTestingModule({
503-
providers: [
504-
{ provide: PLATFORM_ID, useValue: 'browser' },
505-
],
493+
providers: [{ provide: PLATFORM_ID, useValue: 'browser' }],
506494
});
507495
service = TestBed.inject(ViewportService);
508496
mockElement = document.createElement('div');
@@ -511,11 +499,11 @@ describe('ViewportService', () => {
511499
it('should track elements correctly', () => {
512500
expect(service.trackedElementsCount()).toBe(0);
513501
expect(service.isActive()).toBe(false);
514-
502+
515503
const cleanup = service.observe(mockElement);
516504
expect(service.trackedElementsCount()).toBe(1);
517505
expect(service.isActive()).toBe(true);
518-
506+
519507
cleanup();
520508
expect(service.trackedElementsCount()).toBe(0);
521509
expect(service.isActive()).toBe(false);
@@ -524,14 +512,12 @@ describe('ViewportService', () => {
524512
it('should handle SSR gracefully', () => {
525513
TestBed.resetTestingModule();
526514
TestBed.configureTestingModule({
527-
providers: [
528-
{ provide: PLATFORM_ID, useValue: 'server' },
529-
],
515+
providers: [{ provide: PLATFORM_ID, useValue: 'server' }],
530516
});
531-
517+
532518
const ssrService = TestBed.inject(ViewportService);
533519
const cleanup = ssrService.observe(mockElement);
534-
520+
535521
// Should return no-op cleanup function
536522
expect(typeof cleanup).toBe('function');
537523
expect(ssrService.trackedElementsCount()).toBe(0);
@@ -542,11 +528,13 @@ describe('ViewportService', () => {
542528
### Performance Optimization
543529

544530
**Signal Optimization**:
531+
545532
- Use `computed()` for derived state - automatically optimized
546533
- Prefer `effect()` over manual subscriptions
547534
- Use `untracked()` to break signal dependencies when needed
548535

549536
**Intersection Observer Optimization**:
537+
550538
```typescript
551539
// Efficient observer configuration
552540
private readonly observerConfig = computed(() => ({
@@ -562,11 +550,12 @@ effect(() => {
562550
```
563551

564552
**Memory Management**:
553+
565554
```typescript
566555
// Automatic cleanup with effect cleanup
567556
effect((onCleanup) => {
568557
const cleanup = this.setupObserver();
569-
558+
570559
onCleanup(() => {
571560
cleanup();
572561
});
@@ -581,14 +570,14 @@ private readonly error = signal<Error | null>(null);
581570
private readonly isLoading = signal<boolean>(false);
582571

583572
protected readonly hasError = computed(() => this.error() !== null);
584-
protected readonly canRetry = computed(() =>
573+
protected readonly canRetry = computed(() =>
585574
this.hasError() && !this.isLoading()
586575
);
587576

588577
async performOperation(): Promise<void> {
589578
this.isLoading.set(true);
590579
this.error.set(null);
591-
580+
592581
try {
593582
await this.operation();
594583
} catch (error) {
@@ -620,7 +609,7 @@ import { PLATFORM_ID, inject } from '@angular/core';
620609

621610
constructor() {
622611
const platformId = inject(PLATFORM_ID);
623-
612+
624613
if (isPlatformBrowser(platformId)) {
625614
this.initializeObserver();
626615
}
@@ -630,6 +619,7 @@ constructor() {
630619
### Migration Strategy
631620

632621
**From Angular 17 to v20+**:
622+
633623
1. Replace all `@Input()` with `input()`
634624
2. Replace all `@Output()` with `output()`
635625
3. Convert component state to signals
@@ -639,6 +629,7 @@ constructor() {
639629
7. Replace manual subscriptions with `effect()`
640630

641631
**Breaking Changes to Expect**:
632+
642633
- Remove all structural directives (`*ngIf`, `*ngFor`)
643634
- Remove `ngClass` and `ngStyle` - use direct bindings
644635
- Remove `async` pipe for signals (not needed)
@@ -649,7 +640,7 @@ constructor() {
649640
### Manual Testing Protocol
650641

651642
1. **Demo App**: Verify all examples work with signal-based updates
652-
2. **Example App**: Test performance with rapid viewport changes
643+
2. **Example App**: Test performance with rapid viewport changes
653644
3. **Responsive Testing**: Validate across viewport sizes
654645
4. **Memory Testing**: Check for leaks during rapid scroll/resize
655646

0 commit comments

Comments
 (0)