Skip to content

Commit 4c5aab9

Browse files
committed
fix: ssr compatibility (#DS-3964)
1 parent 683d6eb commit 4c5aab9

10 files changed

Lines changed: 87 additions & 35 deletions

File tree

packages/components/core/option/option.ts

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import {
2020
import { ENTER, SPACE } from '@koobiq/cdk/keycodes';
2121
import { Subject } from 'rxjs';
2222
import { KBQ_TITLE_TEXT_REF, KbqTitleTextRef } from '../title';
23+
import { isFunction } from '../utils';
2324
import { KbqOptgroup } from './optgroup';
2425

2526
/**
@@ -237,7 +238,7 @@ export class KbqOption extends KbqOptionBase implements AfterViewChecked, OnDest
237238
private mostRecentViewValue = '';
238239

239240
constructor(
240-
private readonly element: ElementRef<HTMLElement>,
241+
private readonly elementRef: ElementRef<HTMLElement>,
241242
private readonly changeDetectorRef: ChangeDetectorRef,
242243
@Optional() @Inject(KBQ_OPTION_PARENT_COMPONENT) private readonly parent: KbqOptionParentComponent,
243244
@Optional() readonly group: KbqOptgroup
@@ -265,10 +266,14 @@ export class KbqOption extends KbqOptionBase implements AfterViewChecked, OnDest
265266
this.stateChanges.complete();
266267
}
267268

269+
/** @docs-private */
268270
getHeight(): number {
269-
const DOMRect: DOMRect = this.element.nativeElement.getClientRects()[0];
271+
const element = this.elementRef.nativeElement;
270272

271-
return DOMRect ? DOMRect.height : 0;
273+
// For SSR compatibility
274+
if (!isFunction(element.getClientRects)) return 0;
275+
276+
return element.getClientRects()[0]?.height ?? 0;
272277
}
273278

274279
select(emitEvent: boolean = true): void {
@@ -357,7 +362,7 @@ export class KbqOption extends KbqOptionBase implements AfterViewChecked, OnDest
357362
}
358363

359364
getHostElement(): HTMLElement {
360-
return this.element.nativeElement;
365+
return this.elementRef.nativeElement;
361366
}
362367
}
363368

packages/components/core/utils/utils.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,12 @@ export const isNull = (value: unknown): value is null => {
1212
return value === null;
1313
};
1414

15+
/** Whether the value is a function. */
16+
// eslint-disable-next-line @typescript-eslint/ban-types
17+
export const isFunction = (value: unknown): value is Function => {
18+
return typeof value === 'function';
19+
};
20+
1521
/**
1622
* Will be removed in the next major release
1723
*

packages/components/filter-bar/pipes/base-pipe.ts

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -54,8 +54,12 @@ export abstract class KbqBasePipe<V> implements AfterViewInit {
5454
/** TemplateRef for selecting an option */
5555
protected valueTemplate?: TemplateRef<any> | string;
5656

57-
/** Whether the current platform is a Mac. */
58-
isMac = isMac();
57+
/**
58+
* Whether the current platform is a Mac.
59+
*
60+
* @docs-private
61+
*/
62+
isMac = false;
5963

6064
/** Data for the pipe.
6165
* @docs-private */
@@ -85,6 +89,10 @@ export abstract class KbqBasePipe<V> implements AfterViewInit {
8589
});
8690

8791
this.filterBar?.internalTemplatesChanges.pipe(takeUntilDestroyed()).subscribe(this.updateTemplates);
92+
93+
afterNextRender(() => {
94+
this.isMac = isMac();
95+
});
8896
}
8997

9098
ngAfterViewInit(): void {

packages/components/filter-bar/pipes/pipe-title.ts

Lines changed: 18 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import {
2+
afterNextRender,
23
AfterViewInit,
34
ContentChild,
45
ContentChildren,
@@ -62,6 +63,14 @@ export class KbqPipeTitleDirective extends KbqTooltipTrigger implements AfterVie
6263
@ContentChild('kbqTitleContainer')
6364
private parentContainer: ElementRef;
6465

66+
constructor() {
67+
super();
68+
69+
afterNextRender(() => {
70+
this.listenToMutationObserver();
71+
});
72+
}
73+
6574
ngAfterViewInit() {
6675
this.parentContainer = this.parentContainer || this.componentInstance?.parentTextElement || this.elementRef;
6776
this.childContainer = this.childContainer || this.componentInstance?.textElement || this.elementRef;
@@ -71,13 +80,6 @@ export class KbqPipeTitleDirective extends KbqTooltipTrigger implements AfterVie
7180
.pipe(debounceTime(this.debounceInterval))
7281
.subscribe(() => (this.disabled = !this.isOverflown));
7382

74-
this.mutationSubscription = this.createMutationObserver()
75-
.pipe(throttleTime(this.debounceInterval))
76-
.subscribe(() => {
77-
this.disabled = !this.isOverflown;
78-
this.content = this.viewValue;
79-
});
80-
8183
this.focusMonitorSubscription = this.focusMonitor
8284
.monitor(this.elementRef)
8385
.subscribe((origin) => (origin === 'keyboard' ? this.handleElementEnter() : this.hideTooltip()));
@@ -100,6 +102,15 @@ export class KbqPipeTitleDirective extends KbqTooltipTrigger implements AfterVie
100102
this.disabled = true;
101103
}
102104

105+
private listenToMutationObserver(): void {
106+
this.mutationSubscription = this.createMutationObserver()
107+
.pipe(throttleTime(this.debounceInterval))
108+
.subscribe(() => {
109+
this.disabled = !this.isOverflown;
110+
this.content = this.viewValue;
111+
});
112+
}
113+
103114
private createMutationObserver(): Observable<MutationRecord[]> {
104115
return new Observable((observer) => {
105116
const mutationObserver = new MutationObserver((mutations) => observer.next(mutations));

packages/components/list/list-selection.component.ts

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ import {
5050
UP_ARROW
5151
} from '@koobiq/cdk/keycodes';
5252
import {
53+
isFunction,
5354
KBQ_OPTION_ACTION_PARENT,
5455
KBQ_TITLE_TEXT_REF,
5556
KbqOptgroup,
@@ -439,10 +440,14 @@ export class KbqListSelection implements AfterContentInit, AfterViewInit, OnDest
439440
return !(this.noUnselectLast && this.selectionModel.selected.length === 1 && listOption.selected);
440441
}
441442

443+
/** @docs-private */
442444
getHeight(): number {
443-
const clientRects = this.elementRef.nativeElement.getClientRects();
445+
const element = this.elementRef.nativeElement;
444446

445-
return clientRects.length ? clientRects[0].height : 0;
447+
// For SSR compatibility
448+
if (!isFunction(element.getClientRects)) return 0;
449+
450+
return element.getClientRects()[0]?.height ?? 0;
446451
}
447452

448453
// View to model callback that should be called if the list or its options lost focus.
@@ -822,10 +827,14 @@ export class KbqListOption implements OnDestroy, OnInit, IFocusableOption, KbqTi
822827
this.changeDetector.markForCheck();
823828
}
824829

830+
/** @docs-private */
825831
getHeight(): number {
826-
const clientRects = this.elementRef.nativeElement.getClientRects();
832+
const element = this.elementRef.nativeElement;
833+
834+
// For SSR compatibility
835+
if (!isFunction(element.getClientRects)) return 0;
827836

828-
return clientRects.length ? clientRects[0].height : 0;
837+
return element.getClientRects()[0]?.height ?? 0;
829838
}
830839

831840
/** Handles click events on the list option. */
Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,14 @@
1+
/** @docs-private */
12
export interface IClickPosition {
23
x: number;
34
y: number;
45
}
56

7+
/** @docs-private */
68
export class ModalUtil {
79
private lastPosition: IClickPosition;
810

9-
constructor(private document: Document) {
11+
constructor(private document?: Document) {
1012
this.lastPosition = { x: -1, y: -1 };
1113
this.listenDocumentClick();
1214
}
@@ -16,11 +18,15 @@ export class ModalUtil {
1618
}
1719

1820
listenDocumentClick(): void {
19-
this.document.addEventListener('click', (event: MouseEvent) => {
21+
this.document?.addEventListener('click', (event: MouseEvent) => {
2022
this.lastPosition = { x: event.clientX, y: event.clientY };
2123
});
2224
}
2325
}
2426

25-
// eslint-disable-next-line no-restricted-globals
26-
export const modalUtilObject = new ModalUtil(document);
27+
/** @docs-private */
28+
export const modalUtilObject = new ModalUtil(
29+
// For SSR compatibility
30+
// eslint-disable-next-line no-restricted-globals
31+
typeof document === 'undefined' ? undefined : document
32+
);

packages/components/navbar/navbar.component.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import {
2121
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
2222
import { FocusKeyManager } from '@koobiq/cdk/a11y';
2323
import { isHorizontalMovement, isVerticalMovement, LEFT_ARROW, RIGHT_ARROW, TAB } from '@koobiq/cdk/keycodes';
24+
import { isFunction } from '@koobiq/components/core';
2425
import { merge, Observable, Subject, Subscription } from 'rxjs';
2526
import { debounceTime, startWith } from 'rxjs/operators';
2627
import {
@@ -205,7 +206,12 @@ export class KbqNavbar extends KbqFocusableComponent implements AfterViewInit, A
205206
private readonly resizeDebounceInterval: number = 100;
206207

207208
private get width(): number {
208-
return this.elementRef.nativeElement.getBoundingClientRect().width;
209+
const element = this.elementRef.nativeElement;
210+
211+
// For SSR compatibility
212+
if (!isFunction(element.getBoundingClientRect)) return 0;
213+
214+
return element.getBoundingClientRect().width;
209215
}
210216

211217
private get totalItemsWidth(): number {

packages/components/scrollbar/scrollbar.directive.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
/* eslint-disable no-restricted-globals */
21
import { CdkScrollable } from '@angular/cdk/overlay';
32
import { Directive, Inject, Input, NgZone, OnDestroy } from '@angular/core';
43
import { OverlayScrollbars } from 'overlayscrollbars';
@@ -10,8 +9,9 @@ type Defer = [
109
];
1110

1211
const createDefer = (): Defer => {
12+
// For SSR compatibility
13+
// eslint-disable-next-line no-restricted-globals
1314
if (typeof window === 'undefined') {
14-
// mock ssr calls with "noop"
1515
const noop = () => {};
1616

1717
return [noop, noop];
@@ -20,6 +20,7 @@ const createDefer = (): Defer => {
2020
let idleId: number;
2121
let rafId: number;
2222

23+
// eslint-disable-next-line no-restricted-globals
2324
const { requestIdleCallback, requestAnimationFrame, cancelAnimationFrame, cancelIdleCallback } = window;
2425

2526
const idleSupported = typeof requestIdleCallback === 'function';

packages/components/tree/tree-option.component.ts

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import {
1919
} from '@angular/core';
2020
import { hasModifierKey, TAB } from '@koobiq/cdk/keycodes';
2121
import {
22+
isFunction,
2223
KBQ_OPTION_ACTION_PARENT,
2324
KBQ_TITLE_TEXT_REF,
2425
KbqOptionActionComponent,
@@ -301,14 +302,14 @@ export class KbqTreeOption extends KbqTreeNode<KbqTreeOption> implements AfterCo
301302
});
302303
}
303304

305+
/** @docs-private */
304306
getHeight(): number {
305-
const clientRects = this.elementRef.nativeElement.getClientRects();
307+
const element = this.elementRef.nativeElement;
306308

307-
if (clientRects.length) {
308-
return clientRects[0].height;
309-
}
309+
// For SSR compatibility
310+
if (!isFunction(element.getClientRects)) return 0;
310311

311-
return 0;
312+
return element.getClientRects()[0]?.height ?? 0;
312313
}
313314

314315
select(setFocus = true): void {

packages/components/tree/tree-selection.component.ts

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ import {
4545
TAB,
4646
UP_ARROW
4747
} from '@koobiq/cdk/keycodes';
48-
import { getKbqSelectNonArrayValueError, MultipleMode } from '@koobiq/components/core';
48+
import { getKbqSelectNonArrayValueError, isFunction, MultipleMode } from '@koobiq/components/core';
4949
import { merge, Observable, Subscription } from 'rxjs';
5050
import { AsyncScheduler } from 'rxjs/internal/scheduler/AsyncScheduler';
5151
import { delay } from 'rxjs/operators';
@@ -610,13 +610,12 @@ export class KbqTreeSelection
610610
}
611611

612612
private getHeight(): number {
613-
const clientRects = this.elementRef.nativeElement.getClientRects();
613+
const element = this.elementRef.nativeElement;
614614

615-
if (clientRects.length) {
616-
return clientRects[0].height;
617-
}
615+
// For SSR compatibility
616+
if (!isFunction(element.getClientRects)) return 0;
618617

619-
return 0;
618+
return element.getClientRects()[0]?.height ?? 0;
620619
}
621620

622621
private updateTabIndex(): void {

0 commit comments

Comments
 (0)