From ee172a9bae8d8870d0dc510c3aa101e6018af6d1 Mon Sep 17 00:00:00 2001 From: xelaint Date: Fri, 7 Jun 2024 09:04:27 -0600 Subject: [PATCH 1/4] feat(design): create sticky ttracker directive --- .../home/view/home-view.component.html | 1 + libs/design/src/core/public_api.ts | 1 + .../src/core/sticky-tracker/public_api.ts | 1 + .../sticky-tracker.directive.spec.ts | 0 .../sticky-tracker.directive.ts | 63 +++++++++++++++++++ 5 files changed, 66 insertions(+) create mode 100644 libs/design/src/core/sticky-tracker/public_api.ts create mode 100644 libs/design/src/core/sticky-tracker/sticky-tracker.directive.spec.ts create mode 100644 libs/design/src/core/sticky-tracker/sticky-tracker.directive.ts diff --git a/apps/daffio/src/app/content/home/view/home-view.component.html b/apps/daffio/src/app/content/home/view/home-view.component.html index 3547470f3c..c4b094b433 100644 --- a/apps/daffio/src/app/content/home/view/home-view.component.html +++ b/apps/daffio/src/app/content/home/view/home-view.component.html @@ -1,3 +1,4 @@ + diff --git a/libs/design/src/core/public_api.ts b/libs/design/src/core/public_api.ts index 38630c0c80..856550cbfa 100644 --- a/libs/design/src/core/public_api.ts +++ b/libs/design/src/core/public_api.ts @@ -17,3 +17,4 @@ export * from './focus/public_api'; export * from './sizable/public_api'; export * from './openable/public_api'; export * from './selectable/public_api'; +export * from './sticky-tracker/public_api'; diff --git a/libs/design/src/core/sticky-tracker/public_api.ts b/libs/design/src/core/sticky-tracker/public_api.ts new file mode 100644 index 0000000000..86ef051a8f --- /dev/null +++ b/libs/design/src/core/sticky-tracker/public_api.ts @@ -0,0 +1 @@ +export * from './sticky-tracker.directive'; diff --git a/libs/design/src/core/sticky-tracker/sticky-tracker.directive.spec.ts b/libs/design/src/core/sticky-tracker/sticky-tracker.directive.spec.ts new file mode 100644 index 0000000000..e69de29bb2 diff --git a/libs/design/src/core/sticky-tracker/sticky-tracker.directive.ts b/libs/design/src/core/sticky-tracker/sticky-tracker.directive.ts new file mode 100644 index 0000000000..fb1f7b2f67 --- /dev/null +++ b/libs/design/src/core/sticky-tracker/sticky-tracker.directive.ts @@ -0,0 +1,63 @@ +import { + Directive, + ElementRef, + OnDestroy, +} from '@angular/core'; + +export const sumPx = (...px: string[]): string => ( + px.reduce((acc, curr) => acc + parseInt(curr.replace(/px/, ''), 10), 0) + 'px' +); + +@Directive({ + selector: '[daffStickyTracker]', + standalone: true, +}) +export class DaffStickyTrackerDirective implements OnDestroy { + observer: IntersectionObserver; + + constructor(private elementRef: ElementRef) { + const style = getComputedStyle(this.elementRef.nativeElement); + const top = style.top; + const height = style.height; + const bottom = style.bottom; + + let rootMargin = '0px 0px 0px 0px'; + let threshold = 1; + if (bottom !== 'auto' && bottom !== '') { + rootMargin = `0px 0px ${sumPx(bottom, '-1px')} 0px`; + threshold = 1; + } + + if (top !== 'auto' && top !== '') { + rootMargin = `${sumPx(top, '-1px')} 0px 0px 0px`; + threshold = 0.98; + } + + this.observer = new IntersectionObserver( + (entries) => { + entries.forEach((entry) => { + console.log( + this.elementRef.nativeElement.tagName, + entry.intersectionRatio, + entry.isIntersecting, + entry, + ); + if (!entry.isIntersecting) { + this.elementRef.nativeElement.classList.add('daff-sticky'); + } else { + this.elementRef.nativeElement.classList.remove('daff-sticky'); + } + }); + }, + { + rootMargin, + threshold: [threshold], + }, + ); + this.observer.observe(this.elementRef.nativeElement); + } + + ngOnDestroy() { + this.observer.disconnect(); + } +} From b77d700a73a62ebcfe737875b22d0b2f7f28235c Mon Sep 17 00:00:00 2001 From: xelaint Date: Thu, 13 Jun 2024 09:05:12 -0600 Subject: [PATCH 2/4] sticky tracker updates --- libs/design/scss/global.scss | 8 ++ .../sticky-tracker.directive.ts | 132 ++++++++++++------ 2 files changed, 97 insertions(+), 43 deletions(-) diff --git a/libs/design/scss/global.scss b/libs/design/scss/global.scss index 8cb6b66533..b366bc757c 100644 --- a/libs/design/scss/global.scss +++ b/libs/design/scss/global.scss @@ -49,3 +49,11 @@ h6 { margin: 0; padding: 0; } + +.daff-sticky-tracker { + position: relative; + left: 0; + right: 0; + height: 1px; + visibility: hidden; +} diff --git a/libs/design/src/core/sticky-tracker/sticky-tracker.directive.ts b/libs/design/src/core/sticky-tracker/sticky-tracker.directive.ts index fb1f7b2f67..627c7ef667 100644 --- a/libs/design/src/core/sticky-tracker/sticky-tracker.directive.ts +++ b/libs/design/src/core/sticky-tracker/sticky-tracker.directive.ts @@ -1,63 +1,109 @@ +import { DOCUMENT } from '@angular/common'; import { Directive, ElementRef, + EventEmitter, + Inject, OnDestroy, + OnInit, + Output, } from '@angular/core'; export const sumPx = (...px: string[]): string => ( - px.reduce((acc, curr) => acc + parseInt(curr.replace(/px/, ''), 10), 0) + 'px' + px.reduce((acc, curr) => acc + parseFloat(curr.replace(/px/, '')), 0) + 'px' ); +export const negativePx = (px: string) => parseFloat(px.replace(/px/, '')) * -1 + 'px'; + @Directive({ selector: '[daffStickyTracker]', standalone: true, }) -export class DaffStickyTrackerDirective implements OnDestroy { - observer: IntersectionObserver; +export class DaffStickyTrackerDirective implements OnDestroy, OnInit { + observer: IntersectionObserver | undefined; + + private _nativeElement: HTMLElement; + + private _marker: Element | null = null; + + @Output() stickyChanged: EventEmitter = new EventEmitter(); + + constructor( + private elementRef: ElementRef, + @Inject(DOCUMENT) private document: Document, + ) { } + + private createMarker( + position: { bottom?: string; top?: string } | undefined = undefined, + ) { + const mark = this.document.createElement('div'); + mark.classList.add('daff-sticky-tracker'); + if (position?.top) { + mark.style.top = position.top; + } + if (position?.bottom) { + mark.style.bottom = negativePx(position.bottom); + } + return mark; + } - constructor(private elementRef: ElementRef) { + ngOnInit() { + this._nativeElement = this.elementRef.nativeElement; const style = getComputedStyle(this.elementRef.nativeElement); - const top = style.top; - const height = style.height; - const bottom = style.bottom; - - let rootMargin = '0px 0px 0px 0px'; - let threshold = 1; - if (bottom !== 'auto' && bottom !== '') { - rootMargin = `0px 0px ${sumPx(bottom, '-1px')} 0px`; - threshold = 1; - } - - if (top !== 'auto' && top !== '') { - rootMargin = `${sumPx(top, '-1px')} 0px 0px 0px`; - threshold = 0.98; - } - - this.observer = new IntersectionObserver( - (entries) => { - entries.forEach((entry) => { - console.log( - this.elementRef.nativeElement.tagName, - entry.intersectionRatio, - entry.isIntersecting, - entry, - ); - if (!entry.isIntersecting) { - this.elementRef.nativeElement.classList.add('daff-sticky'); - } else { - this.elementRef.nativeElement.classList.remove('daff-sticky'); - } - }); - }, - { - rootMargin, - threshold: [threshold], - }, - ); - this.observer.observe(this.elementRef.nativeElement); + if (style.position !== 'sticky') { + return; + } + + //This can happen some times during component instantiation. + if (style.top === '' || style.bottom === '') { + return; + } + + // If both are auto, do nothing. + if (style.bottom === 'auto' && style.top === 'auto') { + return; + } + + //If they're both set to specific values, I don't know what to do. + if (style.bottom !== 'auto' && style.top !== 'auto') { + return; + } + + this.observer = new IntersectionObserver((entries) => { + entries.forEach((entry) => { + this._nativeElement.classList.toggle( + 'daff-sticky', + entry.intersectionRatio < 1, + ); + }); + }); + + if (style.bottom !== 'auto') { + this._marker = this._nativeElement.insertAdjacentElement( + 'afterend', + this.createMarker({ + bottom: style.bottom, + }), + ); + } + + if (style.top !== 'auto') { + this._marker = this._nativeElement.insertAdjacentElement( + 'beforebegin', + this.createMarker({ + top: style.top, + }), + ); + } + + if (!this._marker) { + throw new Error('DaffStickyTracker error'); + } + this.observer.observe(this._marker); } ngOnDestroy() { - this.observer.disconnect(); + this.observer?.disconnect(); + this._marker?.remove(); } } From 4d28f3d9f7f7381f5a8dfe0182a03e937fa3725e Mon Sep 17 00:00:00 2001 From: Damien Retzinger Date: Tue, 9 Jul 2024 11:28:41 -0400 Subject: [PATCH 3/4] feat(design): make sticky tracker aware of entry direction --- .../sticky-tracker.directive.ts | 42 ++++++++++++++----- 1 file changed, 32 insertions(+), 10 deletions(-) diff --git a/libs/design/src/core/sticky-tracker/sticky-tracker.directive.ts b/libs/design/src/core/sticky-tracker/sticky-tracker.directive.ts index 627c7ef667..e6c3c9b871 100644 --- a/libs/design/src/core/sticky-tracker/sticky-tracker.directive.ts +++ b/libs/design/src/core/sticky-tracker/sticky-tracker.directive.ts @@ -15,6 +15,8 @@ export const sumPx = (...px: string[]): string => ( export const negativePx = (px: string) => parseFloat(px.replace(/px/, '')) * -1 + 'px'; +export const withinDeltaRange = (val: number, base: number, delta = 5) => val >= -1 * delta + base && val <= delta + base; + @Directive({ selector: '[daffStickyTracker]', standalone: true, @@ -24,6 +26,8 @@ export class DaffStickyTrackerDirective implements OnDestroy, OnInit { private _nativeElement: HTMLElement; + private kind = undefined; + private _marker: Element | null = null; @Output() stickyChanged: EventEmitter = new EventEmitter(); @@ -69,16 +73,15 @@ export class DaffStickyTrackerDirective implements OnDestroy, OnInit { return; } - this.observer = new IntersectionObserver((entries) => { - entries.forEach((entry) => { - this._nativeElement.classList.toggle( - 'daff-sticky', - entry.intersectionRatio < 1, - ); - }); - }); + if(style.bottom !== 'auto') { + this.kind = 'bottom'; + } - if (style.bottom !== 'auto') { + if(style.top !== 'auto') { + this.kind = 'top'; + } + + if (this.kind === 'bottom') { this._marker = this._nativeElement.insertAdjacentElement( 'afterend', this.createMarker({ @@ -87,7 +90,7 @@ export class DaffStickyTrackerDirective implements OnDestroy, OnInit { ); } - if (style.top !== 'auto') { + if (this.kind === 'top') { this._marker = this._nativeElement.insertAdjacentElement( 'beforebegin', this.createMarker({ @@ -99,6 +102,25 @@ export class DaffStickyTrackerDirective implements OnDestroy, OnInit { if (!this._marker) { throw new Error('DaffStickyTracker error'); } + + this.observer = new IntersectionObserver((entries) => { + entries.forEach((entry) => { + if (this.kind === 'bottom' && entry.boundingClientRect.y > 0) { + this._nativeElement.classList.toggle( + 'daff-sticky', + entry.intersectionRatio < 1, + ); + } else if ( + this.kind === 'top' && + entry.boundingClientRect.y < (entry.rootBounds?.bottom ?? 0) + ) { + this._nativeElement.classList.toggle( + 'daff-sticky', + entry.intersectionRatio < 1, + ); + } + }); + }); this.observer.observe(this._marker); } From 0190a891c3922303908012ea69696cfd16e30fd8 Mon Sep 17 00:00:00 2001 From: xelaint Date: Wed, 10 Jul 2024 11:02:36 -0600 Subject: [PATCH 4/4] remove test button in home view --- apps/daffio/src/app/content/home/view/home-view.component.html | 1 - 1 file changed, 1 deletion(-) diff --git a/apps/daffio/src/app/content/home/view/home-view.component.html b/apps/daffio/src/app/content/home/view/home-view.component.html index c4b094b433..3547470f3c 100644 --- a/apps/daffio/src/app/content/home/view/home-view.component.html +++ b/apps/daffio/src/app/content/home/view/home-view.component.html @@ -1,4 +1,3 @@ -