Skip to content

Commit da2a981

Browse files
authored
feat: Support different options with same root
1 parent 3d8d6d1 commit da2a981

File tree

5 files changed

+103
-29
lines changed

5 files changed

+103
-29
lines changed

projects/ng-in-viewport/src/lib/in-viewport-config.ts

Lines changed: 66 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -23,16 +23,48 @@ export interface InViewportConfigOptions {
2323
checkFn?: InViewportConfigCheckFn;
2424
}
2525

26-
const DEFAULT_THRESHOLD = [0, 1];
27-
2826
export class InViewportConfig {
27+
private static readonly DEFAULT_THRESHOLD = [0, 1];
28+
29+
private static readonly STRINGIFY_DELIMITER = '|';
30+
2931
private _root: Element;
3032
private _rootMargin: string = '0px 0px 0px 0px';
31-
private _threshold: number | number[] = [...DEFAULT_THRESHOLD];
33+
private _threshold: number | number[] = [...InViewportConfig.DEFAULT_THRESHOLD];
3234
private _partial: boolean = true;
3335
private _direction: InViewportConfigDirection = InViewportConfigDirection.BOTH;
36+
private _hash: string;
3437
private _checkFn: InViewportConfigCheckFn;
3538

39+
private static stringify(input: object): string {
40+
if (Array.isArray(input)) {
41+
const stringifiedArr = [];
42+
43+
for (const v of input) {
44+
stringifiedArr.push(InViewportConfig.stringify(v));
45+
}
46+
47+
return `[${stringifiedArr.join(',')}]`;
48+
} else if (typeof input === 'object' && input !== null) {
49+
const acc = [];
50+
const sortedKeys = Object.keys(input).sort();
51+
52+
for (const k of sortedKeys) {
53+
const v = InViewportConfig.stringify(input[k]);
54+
55+
acc.push(`${k}:${v}`);
56+
}
57+
58+
return acc.join(InViewportConfig.STRINGIFY_DELIMITER);
59+
}
60+
61+
return String(input);
62+
}
63+
64+
private static hash(input: object): string {
65+
return btoa(InViewportConfig.stringify(input));
66+
}
67+
3668
constructor(options?: InViewportConfigOptions) {
3769
if (Object.prototype.toString.call(options) === '[object Object]') {
3870
['root', 'rootMargin', 'threshold', 'partial', 'direction', 'checkFn'].forEach((prop) => {
@@ -41,6 +73,14 @@ export class InViewportConfig {
4173
}
4274
});
4375
}
76+
77+
this._hash = InViewportConfig.hash({
78+
rootMargin: this.rootMargin,
79+
threshold: this.threshold,
80+
partial: this.partial,
81+
direction: this.direction,
82+
checkFn: String(this.checkFn)
83+
});
4484
}
4585

4686
public get root(): Element {
@@ -56,7 +96,24 @@ export class InViewportConfig {
5696
}
5797

5898
public set rootMargin(value: string) {
59-
this._rootMargin = value && typeof value === 'string' ? value : '0px 0px 0px 0px';
99+
if (!value || typeof value !== 'string') {
100+
this._rootMargin = '0px 0px 0px 0px';
101+
} else {
102+
const marginString: string = value || '0px';
103+
const margins: string[] = marginString.split(new RegExp('\\s+')).map((margin) => {
104+
const parts = /^(-?\d*\.?\d+)(px|%)$/.exec(margin);
105+
if (!parts) {
106+
throw new TypeError('rootMargin must be specified in pixels or percent');
107+
}
108+
return `${parts[1]}${parts[2]}`;
109+
});
110+
111+
margins[1] = margins[1] || margins[0];
112+
margins[2] = margins[2] || margins[0];
113+
margins[3] = margins[3] || margins[1];
114+
115+
this._rootMargin = margins.join(' ');
116+
}
60117
}
61118

62119
public get threshold(): number | number[] {
@@ -72,7 +129,7 @@ export class InViewportConfig {
72129
threshold = value.filter((val) => isValidThreshold(val));
73130
}
74131
if (threshold.length === 0) {
75-
threshold = [...DEFAULT_THRESHOLD];
132+
threshold = [...InViewportConfig.DEFAULT_THRESHOLD];
76133
}
77134
this._threshold = threshold;
78135
}
@@ -102,6 +159,10 @@ export class InViewportConfig {
102159
this._direction = isValidValue(value) ? value : InViewportConfigDirection.BOTH;
103160
}
104161

162+
public get hash(): string {
163+
return this._hash;
164+
}
165+
105166
public get checkFn(): InViewportConfigCheckFn {
106167
return this._checkFn;
107168
}

projects/ng-in-viewport/src/lib/in-viewport.directive.spec.ts

Lines changed: 18 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -8,18 +8,24 @@ import { InViewportDirective } from './in-viewport.directive';
88
@Component({
99
template: `
1010
<div class="list" #listElement>
11-
<div class="item inactive" inViewport
12-
[inViewportOptions]="{ partial: true, root: listElement }"
13-
(inViewportAction)="handleAction($event)">
14-
</div>
15-
<div class="item inactive" inViewport
16-
[inViewportOptions]="{ partial: true, root: listElement }"
17-
(inViewportAction)="handleAction($event)">
18-
</div>
19-
<div class="item inactive" inViewport
20-
[inViewportOptions]="{ partial: true, root: listElement }"
21-
(inViewportAction)="handleAction($event)">
22-
</div>
11+
<div
12+
class="item inactive"
13+
inViewport
14+
[inViewportOptions]="{ partial: true, root: listElement }"
15+
(inViewportAction)="handleAction($event)"
16+
></div>
17+
<div
18+
class="item inactive"
19+
inViewport
20+
[inViewportOptions]="{ partial: true, root: listElement }"
21+
(inViewportAction)="handleAction($event)"
22+
></div>
23+
<div
24+
class="item inactive"
25+
inViewport
26+
[inViewportOptions]="{ partial: true, root: listElement }"
27+
(inViewportAction)="handleAction($event)"
28+
></div>
2329
</div>
2430
`,
2531
styles: [

projects/ng-in-viewport/src/lib/in-viewport.service.ts

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { InViewportConfig } from './in-viewport-config';
44

55
export interface InViewportRegistryEntry {
66
root: Element;
7+
configHash: string;
78
targets: Set<Element>;
89
observer: IntersectionObserver;
910
}
@@ -26,28 +27,30 @@ export class InViewportService {
2627
}
2728
}
2829

29-
private getRootElement(element?: Element) {
30-
return element && element.nodeType === Node.ELEMENT_NODE ? element : undefined;
30+
private getRootElement(element?: Element): Element | null {
31+
return element && element.nodeType === Node.ELEMENT_NODE ? element : null;
3132
}
3233

33-
private findEntry(root: Element) {
34-
return this.registry.find((entry) => entry.root === this.getRootElement(root));
34+
private findEntry(root: Element, configHash: string) {
35+
return this.registry.find((entry) => entry.root === this.getRootElement(root) && entry.configHash === configHash);
3536
}
3637

3738
public register(target: Element, config: InViewportConfig): void {
3839
this.ngZone.runOutsideAngular(() => {
39-
const foundedEntry = this.findEntry(config.root);
40+
const foundedEntry = this.findEntry(config.root, config.hash);
4041
if (foundedEntry && !foundedEntry.targets.has(target)) {
4142
foundedEntry.targets.add(target);
4243
foundedEntry.observer.observe(target);
4344
} else {
45+
const root: Element | null = this.getRootElement(config.root);
4446
const options: any = {
45-
root: this.getRootElement(config.root),
47+
root: root !== null ? root : undefined,
4648
rootMargin: config.rootMargin,
4749
threshold: config.threshold
4850
};
4951
const entry: InViewportRegistryEntry = {
50-
root: this.getRootElement(config.root),
52+
root,
53+
configHash: config.hash,
5154
targets: new Set([target]),
5255
observer: new IntersectionObserver(
5356
(entries: IntersectionObserverEntry[]) => this.ngZone.run(() => this.emitTrigger(entries)),
@@ -62,7 +65,7 @@ export class InViewportService {
6265

6366
public unregister(target: Element, config: InViewportConfig): void {
6467
this.ngZone.runOutsideAngular(() => {
65-
const foundedEntry = this.findEntry(config.root);
68+
const foundedEntry = this.findEntry(config.root, config.hash);
6669
if (foundedEntry) {
6770
const { observer, targets } = foundedEntry;
6871
if (targets.has(target)) {

src/app/invp-example/invp-example.component.html

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,14 @@
11
<section #firstSection>
2-
<ng-container *ngFor="let element of elements">
3-
<div class="item" inViewport [inViewportOptions]="{ root: firstSection }" (inViewportAction)="handleAction($event)">
2+
<ng-container *ngFor="let element of elements; odd as odd">
3+
<div class="item" [class.is-odd]="odd" inViewport [inViewportOptions]="{ root: firstSection }" (inViewportAction)="handleAction($event)">
44
{{ element }}
55
</div>
66
</ng-container>
77
</section>
88

99
<section>
10-
<ng-container *ngFor="let element of elements">
11-
<div class="item" inViewport (inViewportAction)="handleAction($event)">
10+
<ng-container *ngFor="let element of elements; odd as odd">
11+
<div class="item" [class.is-odd]="odd" inViewport (inViewportAction)="handleAction($event)">
1212
{{ element }}
1313
</div>
1414
</ng-container>

src/app/invp-example/invp-example.component.scss

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,5 +63,9 @@ section {
6363
2.1213203436px 2.1213203436px 0 rgba(#6272a4, 0.5),
6464
2.4748737342px 2.4748737342px 0 rgba(#6272a4, 0.5),
6565
2.8284271247px 2.8284271247px 0 rgba(#6272a4, 0.5);
66+
67+
&.is-odd {
68+
background-color: lighten(#50fa7b, 5%);
69+
}
6670
}
6771
}

0 commit comments

Comments
 (0)