From 6dd1311f42cd8679f0a908701643f284d449a537 Mon Sep 17 00:00:00 2001 From: fateeand Date: Thu, 7 May 2026 12:18:15 +0200 Subject: [PATCH 1/5] implement service --- api-generator/api-generator.js | 6 + .../cps-root-font-size.service.ts | 134 ++++++++++++++++++ projects/cps-ui-kit/src/public-api.ts | 1 + 3 files changed, 141 insertions(+) create mode 100644 projects/cps-ui-kit/src/lib/services/cps-root-font-size/cps-root-font-size.service.ts diff --git a/api-generator/api-generator.js b/api-generator/api-generator.js index 35ca2301..f0788959 100644 --- a/api-generator/api-generator.js +++ b/api-generator/api-generator.js @@ -724,6 +724,12 @@ async function main() { } for (const key in mergedDocs) { + const doc = mergedDocs[key]; + const isEmpty = + Object.keys(doc).length === 1 && + doc.components && + Object.keys(doc.components).length === 0; + if (isEmpty) continue; const typedocJSON = JSON.stringify(mergedDocs[key], null, 4); !fs.existsSync(outputPath) && fs.mkdirSync(outputPath); fs.writeFileSync(path.resolve(outputPath, `${key}.json`), typedocJSON); diff --git a/projects/cps-ui-kit/src/lib/services/cps-root-font-size/cps-root-font-size.service.ts b/projects/cps-ui-kit/src/lib/services/cps-root-font-size/cps-root-font-size.service.ts new file mode 100644 index 00000000..52b2a21c --- /dev/null +++ b/projects/cps-ui-kit/src/lib/services/cps-root-font-size/cps-root-font-size.service.ts @@ -0,0 +1,134 @@ +import { DOCUMENT } from '@angular/common'; +import { + inject, + Injectable, + InjectionToken, + OnDestroy, + signal, + Signal +} from '@angular/core'; + +/** + * CpsRootFontSizeService tracks the application's current root font size. + * + * The service uses a ResizeObserver strategy to reliably detect root font-size changes: + * + * **Sentinel element** (`
`) — its pixel width + * mirrors `1rem`. Any root font-size change — caused by CSS class toggles, + * stylesheet rules, direct JS assignment, or viewport resize (e.g. + * `font-size: 1.5vw`) — changes the sentinel's computed width, firing the + * observer. + * The cached value is stored in a signal and is only updated when the actual + * font-size value changes, preventing spurious updates. + * + * In microfrontend environments the sentinel element is keyed by a known DOM + * attribute (`data-cps-root-font-size-sentinel`) and reused if already present, + * so only one sentinel node exists per document regardless of how many + * instances of this service are created. + * + * Prefer injecting {@link CPS_ROOT_FONT_SIZE_SERVICE} over this class directly + * to allow consumer applications to override the behavior. + * + * @example + * ```typescript + * class MyComponent { + * private fontSizeService = inject(CPS_ROOT_FONT_SIZE_SERVICE); + * readonly fontSize = this.fontSizeService?.fontSize; + * } + * ``` + */ +@Injectable({ + providedIn: 'root' +}) +export class CpsRootFontSizeService implements OnDestroy { + private readonly _document = inject(DOCUMENT); + + private static readonly _SENTINEL_ATTR = 'data-cps-root-font-size-sentinel'; + + private readonly _fontSize = signal(this._readRootFontSize()); + + private _sentinel: HTMLElement | null = null; + private _sentinelOwned = false; + private _sentinelObserver: ResizeObserver | null = null; + + /** Reactive signal containing the current root font size in pixels. */ + readonly fontSize: Signal = this._fontSize.asReadonly(); + + constructor() { + this._setupObservers(); + } + + ngOnDestroy(): void { + this._sentinelObserver?.disconnect(); + + if (this._sentinelOwned && this._sentinel) { + this._sentinel.remove(); + } + + this._sentinelObserver = null; + this._sentinel = null; + } + + private _setupObservers(): void { + // Reuse an existing sentinel if another service instance already created one. + let sentinel = this._document.querySelector( + `[${CpsRootFontSizeService._SENTINEL_ATTR}]` + ); + + if (!sentinel) { + sentinel = this._document.createElement('div'); + sentinel.setAttribute(CpsRootFontSizeService._SENTINEL_ATTR, ''); + Object.assign(sentinel.style, { + position: 'absolute', + width: '1rem', + height: '0', + visibility: 'hidden', + pointerEvents: 'none', + userSelect: 'none', + top: '0', + left: '0' + }); + this._document.documentElement.appendChild(sentinel); + this._sentinelOwned = true; + } + + this._sentinel = sentinel; + + this._sentinelObserver = new ResizeObserver(() => this._refresh()); + this._sentinelObserver.observe(sentinel); + } + + private _refresh(): void { + const newSize = this._readRootFontSize(); + if (newSize !== this._fontSize()) { + this._fontSize.set(newSize); + } + } + + private _readRootFontSize(): number { + return parseFloat( + getComputedStyle(this._document.documentElement).fontSize + ); + } +} + +/** + * Injection token for the root font size service. + * + * By default it resolves to the singleton {@link CpsRootFontSizeService}. + * Consumer applications can override it to: + * - Supply a custom subclass + * - Provide `null` to disable dynamic tracking entirely + * + * @example Disable dynamic tracking: + * ```typescript + * providers: [ + * { provide: CPS_ROOT_FONT_SIZE_SERVICE, useValue: null } + * ] + * ``` + */ +export const CPS_ROOT_FONT_SIZE_SERVICE = + new InjectionToken('CpsRootFontSizeService', { + providedIn: 'root', + factory: () => inject(CpsRootFontSizeService) + }); diff --git a/projects/cps-ui-kit/src/public-api.ts b/projects/cps-ui-kit/src/public-api.ts index 97b6370c..448b5a7f 100644 --- a/projects/cps-ui-kit/src/public-api.ts +++ b/projects/cps-ui-kit/src/public-api.ts @@ -59,5 +59,6 @@ export * from './lib/services/cps-dialog/utils/cps-dialog-ref'; export * from './lib/services/cps-notification/cps-notification.service'; export * from './lib/services/cps-notification/utils/cps-notification-config'; +export * from './lib/services/cps-root-font-size/cps-root-font-size.service'; export * from './lib/services/cps-theme/cps-theme.service'; export * from './lib/utils/colors-utils'; From 6e7f045af6e61785b5f712bd08c8d72152338a5d Mon Sep 17 00:00:00 2001 From: fateeand Date: Thu, 7 May 2026 12:49:46 +0200 Subject: [PATCH 2/5] add ut --- .../cps-root-font-size.service.spec.ts | 156 ++++++++++++++++++ 1 file changed, 156 insertions(+) create mode 100644 projects/cps-ui-kit/src/lib/services/cps-root-font-size/cps-root-font-size.service.spec.ts diff --git a/projects/cps-ui-kit/src/lib/services/cps-root-font-size/cps-root-font-size.service.spec.ts b/projects/cps-ui-kit/src/lib/services/cps-root-font-size/cps-root-font-size.service.spec.ts new file mode 100644 index 00000000..8c023861 --- /dev/null +++ b/projects/cps-ui-kit/src/lib/services/cps-root-font-size/cps-root-font-size.service.spec.ts @@ -0,0 +1,156 @@ +import { TestBed } from '@angular/core/testing'; +import { DOCUMENT } from '@angular/common'; +import { + CpsRootFontSizeService, + CPS_ROOT_FONT_SIZE_SERVICE +} from './cps-root-font-size.service'; + +const SENTINEL_ATTR = 'data-cps-root-font-size-sentinel'; + +describe('CpsRootFontSizeService', () => { + let service: CpsRootFontSizeService; + let document: Document; + let resizeCallback: ResizeObserverCallback; + let mockObserve: jest.Mock; + let mockDisconnect: jest.Mock; + let computedFontSize: string; + + beforeEach(() => { + computedFontSize = '16px'; + mockObserve = jest.fn(); + mockDisconnect = jest.fn(); + + (globalThis as any).ResizeObserver = jest.fn( + (cb: ResizeObserverCallback) => { + resizeCallback = cb; + return { observe: mockObserve, disconnect: mockDisconnect }; + } + ); + + jest + .spyOn(window, 'getComputedStyle') + .mockReturnValue({ fontSize: computedFontSize } as CSSStyleDeclaration); + + TestBed.configureTestingModule({}); + service = TestBed.inject(CpsRootFontSizeService); + document = TestBed.inject(DOCUMENT); + }); + + afterEach(() => { + service.ngOnDestroy(); + jest.restoreAllMocks(); + delete (globalThis as any).ResizeObserver; + document + .querySelectorAll(`[${SENTINEL_ATTR}]`) + .forEach((el) => el.remove()); + }); + + it('should be created', () => { + expect(service).toBeTruthy(); + }); + + it('should initialize fontSize from getComputedStyle', () => { + expect(service.fontSize()).toBe(16); + }); + + it('should append a sentinel element to the document root', () => { + const sentinel = document.querySelector(`[${SENTINEL_ATTR}]`); + expect(sentinel).not.toBeNull(); + expect(document.documentElement.contains(sentinel)).toBe(true); + }); + + it('should set sentinel style to width:1rem and be hidden', () => { + const sentinel = document.querySelector(`[${SENTINEL_ATTR}]`)!; + expect(sentinel.style.width).toBe('1rem'); + expect(sentinel.style.height).toBe('0px'); + expect(sentinel.style.visibility).toBe('hidden'); + expect(sentinel.style.position).toBe('absolute'); + expect(sentinel.style.pointerEvents).toBe('none'); + }); + + it('should start observing the sentinel element', () => { + expect(mockObserve).toHaveBeenCalledTimes(1); + const sentinel = document.querySelector(`[${SENTINEL_ATTR}]`); + expect(mockObserve).toHaveBeenCalledWith(sentinel); + }); + + it('should update fontSize signal when ResizeObserver fires with a new size', () => { + (window.getComputedStyle as jest.Mock).mockReturnValue({ + fontSize: '20px' + } as CSSStyleDeclaration); + + resizeCallback([], null as unknown as ResizeObserver); + + expect(service.fontSize()).toBe(20); + }); + + it('should NOT update fontSize signal when the size has not changed', () => { + resizeCallback([], null as unknown as ResizeObserver); + expect(service.fontSize()).toBe(16); + }); + + describe('ngOnDestroy', () => { + it('should disconnect the ResizeObserver', () => { + service.ngOnDestroy(); + expect(mockDisconnect).toHaveBeenCalledTimes(1); + }); + + it('should remove the sentinel element when owned', () => { + const sentinel = document.querySelector(`[${SENTINEL_ATTR}]`); + expect(sentinel).not.toBeNull(); + + service.ngOnDestroy(); + + expect(document.querySelector(`[${SENTINEL_ATTR}]`)).toBeNull(); + }); + + it('should null out internal references after destroy', () => { + service.ngOnDestroy(); + expect(() => service.ngOnDestroy()).not.toThrow(); + }); + }); + + describe('sentinel reuse (microfrontend scenario)', () => { + it('should reuse an existing sentinel and NOT remove it on destroy', () => { + const service2 = TestBed.runInInjectionContext( + () => new CpsRootFontSizeService() + ); + + const sentinels = document.querySelectorAll(`[${SENTINEL_ATTR}]`); + expect(sentinels.length).toBe(1); + + service2.ngOnDestroy(); + + expect(document.querySelector(`[${SENTINEL_ATTR}]`)).not.toBeNull(); + }); + }); +}); + +describe('CPS_ROOT_FONT_SIZE_SERVICE token', () => { + beforeEach(() => { + (globalThis as any).ResizeObserver = jest.fn(() => ({ + observe: jest.fn(), + disconnect: jest.fn() + })); + jest + .spyOn(window, 'getComputedStyle') + .mockReturnValue({ fontSize: '16px' } as CSSStyleDeclaration); + + TestBed.configureTestingModule({}); + }); + + afterEach(() => { + TestBed.inject(CpsRootFontSizeService).ngOnDestroy(); + jest.restoreAllMocks(); + delete (globalThis as any).ResizeObserver; + TestBed.inject(DOCUMENT) + .querySelectorAll(`[${SENTINEL_ATTR}]`) + .forEach((el) => el.remove()); + }); + + it('should resolve to the CpsRootFontSizeService singleton', () => { + const token = TestBed.inject(CPS_ROOT_FONT_SIZE_SERVICE); + const direct = TestBed.inject(CpsRootFontSizeService); + expect(token).toBe(direct); + }); +}); From bae48bdf8316a44a0ae350ccb508494e3a2001dc Mon Sep 17 00:00:00 2001 From: fateeand Date: Thu, 7 May 2026 13:09:33 +0200 Subject: [PATCH 3/5] add platform browser check --- .../cps-root-font-size.service.spec.ts | 44 +++++++++++++++++++ .../cps-root-font-size.service.ts | 12 ++++- 2 files changed, 54 insertions(+), 2 deletions(-) diff --git a/projects/cps-ui-kit/src/lib/services/cps-root-font-size/cps-root-font-size.service.spec.ts b/projects/cps-ui-kit/src/lib/services/cps-root-font-size/cps-root-font-size.service.spec.ts index 8c023861..bac8b353 100644 --- a/projects/cps-ui-kit/src/lib/services/cps-root-font-size/cps-root-font-size.service.spec.ts +++ b/projects/cps-ui-kit/src/lib/services/cps-root-font-size/cps-root-font-size.service.spec.ts @@ -1,5 +1,6 @@ import { TestBed } from '@angular/core/testing'; import { DOCUMENT } from '@angular/common'; +import { PLATFORM_ID } from '@angular/core'; import { CpsRootFontSizeService, CPS_ROOT_FONT_SIZE_SERVICE @@ -126,6 +127,49 @@ describe('CpsRootFontSizeService', () => { }); }); +describe('CpsRootFontSizeService (SSR)', () => { + beforeEach(() => { + (globalThis as any).ResizeObserver = jest.fn(() => ({ + observe: jest.fn(), + disconnect: jest.fn() + })); + jest + .spyOn(window, 'getComputedStyle') + .mockReturnValue({ fontSize: '16px' } as CSSStyleDeclaration); + }); + + afterEach(() => { + jest.restoreAllMocks(); + delete (globalThis as any).ResizeObserver; + }); + + it('should initialize fontSize to 16 in SSR', () => { + TestBed.configureTestingModule({ + providers: [{ provide: PLATFORM_ID, useValue: 'server' }] + }); + const service = TestBed.inject(CpsRootFontSizeService); + expect(service.fontSize()).toBe(16); + }); + + it('should NOT create a sentinel element in SSR', () => { + TestBed.configureTestingModule({ + providers: [{ provide: PLATFORM_ID, useValue: 'server' }] + }); + TestBed.inject(CpsRootFontSizeService); + const doc = TestBed.inject(DOCUMENT); + expect(doc.querySelector(`[${SENTINEL_ATTR}]`)).toBeNull(); + }); + + it('should NOT create a ResizeObserver in SSR', () => { + const observerCtor = (globalThis as any).ResizeObserver as jest.Mock; + TestBed.configureTestingModule({ + providers: [{ provide: PLATFORM_ID, useValue: 'server' }] + }); + TestBed.inject(CpsRootFontSizeService); + expect(observerCtor).not.toHaveBeenCalled(); + }); +}); + describe('CPS_ROOT_FONT_SIZE_SERVICE token', () => { beforeEach(() => { (globalThis as any).ResizeObserver = jest.fn(() => ({ diff --git a/projects/cps-ui-kit/src/lib/services/cps-root-font-size/cps-root-font-size.service.ts b/projects/cps-ui-kit/src/lib/services/cps-root-font-size/cps-root-font-size.service.ts index 52b2a21c..9e09dddd 100644 --- a/projects/cps-ui-kit/src/lib/services/cps-root-font-size/cps-root-font-size.service.ts +++ b/projects/cps-ui-kit/src/lib/services/cps-root-font-size/cps-root-font-size.service.ts @@ -1,9 +1,10 @@ -import { DOCUMENT } from '@angular/common'; +import { DOCUMENT, isPlatformBrowser } from '@angular/common'; import { inject, Injectable, InjectionToken, OnDestroy, + PLATFORM_ID, signal, Signal } from '@angular/core'; @@ -26,6 +27,9 @@ import { * so only one sentinel node exists per document regardless of how many * instances of this service are created. * + * Only active in browser environments. Under SSR the `fontSize` signal is + * initialized to `16` (the standard browser default) and no DOM observers are created. + * * Prefer injecting {@link CPS_ROOT_FONT_SIZE_SERVICE} over this class directly * to allow consumer applications to override the behavior. * @@ -42,10 +46,13 @@ import { }) export class CpsRootFontSizeService implements OnDestroy { private readonly _document = inject(DOCUMENT); + private readonly _platformId = inject(PLATFORM_ID); private static readonly _SENTINEL_ATTR = 'data-cps-root-font-size-sentinel'; - private readonly _fontSize = signal(this._readRootFontSize()); + private readonly _fontSize = signal( + isPlatformBrowser(this._platformId) ? this._readRootFontSize() : 16 + ); private _sentinel: HTMLElement | null = null; private _sentinelOwned = false; @@ -55,6 +62,7 @@ export class CpsRootFontSizeService implements OnDestroy { readonly fontSize: Signal = this._fontSize.asReadonly(); constructor() { + if (!isPlatformBrowser(this._platformId)) return; this._setupObservers(); } From e9e48510e0c1ecbe893b7bc1acae6e8a3e57c5f0 Mon Sep 17 00:00:00 2001 From: fateeand Date: Thu, 7 May 2026 18:18:33 +0200 Subject: [PATCH 4/5] address copilot feedback --- api-generator/api-generator.js | 10 +-- .../cps-root-font-size.service.spec.ts | 72 +++++++++++++++---- .../cps-root-font-size.service.ts | 11 +-- 3 files changed, 68 insertions(+), 25 deletions(-) diff --git a/api-generator/api-generator.js b/api-generator/api-generator.js index f0788959..31daad8c 100644 --- a/api-generator/api-generator.js +++ b/api-generator/api-generator.js @@ -724,13 +724,13 @@ async function main() { } for (const key in mergedDocs) { - const doc = mergedDocs[key]; + const moduleDoc = mergedDocs[key]; const isEmpty = - Object.keys(doc).length === 1 && - doc.components && - Object.keys(doc.components).length === 0; + Object.keys(moduleDoc).length === 1 && + moduleDoc.components && + Object.keys(moduleDoc.components).length === 0; if (isEmpty) continue; - const typedocJSON = JSON.stringify(mergedDocs[key], null, 4); + const typedocJSON = JSON.stringify(moduleDoc, null, 4); !fs.existsSync(outputPath) && fs.mkdirSync(outputPath); fs.writeFileSync(path.resolve(outputPath, `${key}.json`), typedocJSON); } diff --git a/projects/cps-ui-kit/src/lib/services/cps-root-font-size/cps-root-font-size.service.spec.ts b/projects/cps-ui-kit/src/lib/services/cps-root-font-size/cps-root-font-size.service.spec.ts index bac8b353..ffb4717a 100644 --- a/projects/cps-ui-kit/src/lib/services/cps-root-font-size/cps-root-font-size.service.spec.ts +++ b/projects/cps-ui-kit/src/lib/services/cps-root-font-size/cps-root-font-size.service.spec.ts @@ -85,7 +85,7 @@ describe('CpsRootFontSizeService', () => { expect(service.fontSize()).toBe(20); }); - it('should NOT update fontSize signal when the size has not changed', () => { + it('should not update fontSize signal when the size has not changed', () => { resizeCallback([], null as unknown as ResizeObserver); expect(service.fontSize()).toBe(16); }); @@ -96,13 +96,9 @@ describe('CpsRootFontSizeService', () => { expect(mockDisconnect).toHaveBeenCalledTimes(1); }); - it('should remove the sentinel element when owned', () => { - const sentinel = document.querySelector(`[${SENTINEL_ATTR}]`); - expect(sentinel).not.toBeNull(); - + it('should not remove the sentinel element on destroy', () => { service.ngOnDestroy(); - - expect(document.querySelector(`[${SENTINEL_ATTR}]`)).toBeNull(); + expect(document.querySelector(`[${SENTINEL_ATTR}]`)).not.toBeNull(); }); it('should null out internal references after destroy', () => { @@ -112,14 +108,66 @@ describe('CpsRootFontSizeService', () => { }); describe('sentinel reuse (microfrontend scenario)', () => { - it('should reuse an existing sentinel and NOT remove it on destroy', () => { + it('should reuse the existing sentinel when a second instance is created', () => { const service2 = TestBed.runInInjectionContext( () => new CpsRootFontSizeService() ); - const sentinels = document.querySelectorAll(`[${SENTINEL_ATTR}]`); - expect(sentinels.length).toBe(1); + expect(document.querySelectorAll(`[${SENTINEL_ATTR}]`).length).toBe(1); + + service2.ngOnDestroy(); + }); + it('should keep the sentinel alive when the non-owning instance is destroyed', () => { + const service2 = TestBed.runInInjectionContext( + () => new CpsRootFontSizeService() + ); + + service2.ngOnDestroy(); + expect(document.querySelector(`[${SENTINEL_ATTR}]`)).not.toBeNull(); + }); + + it('should keep the sentinel alive when the owning (first) instance is destroyed while another is active', () => { + const service2 = TestBed.runInInjectionContext( + () => new CpsRootFontSizeService() + ); + + service.ngOnDestroy(); + expect(document.querySelector(`[${SENTINEL_ATTR}]`)).not.toBeNull(); + service2.ngOnDestroy(); + }); + + it('should keep tracking after the owning instance is destroyed: surviving instance still updates on resize', () => { + const callbacks: ResizeObserverCallback[] = []; + (globalThis as any).ResizeObserver = jest.fn( + (cb: ResizeObserverCallback) => { + callbacks.push(cb); + return { observe: mockObserve, disconnect: mockDisconnect }; + } + ); + + const service2 = TestBed.runInInjectionContext( + () => new CpsRootFontSizeService() + ); + + service.ngOnDestroy(); + + (window.getComputedStyle as jest.Mock).mockReturnValue({ + fontSize: '20px' + } as CSSStyleDeclaration); + + callbacks[0]([], null as unknown as ResizeObserver); + + expect(service2.fontSize()).toBe(20); + service2.ngOnDestroy(); + }); + + it('should keep the sentinel alive even after all instances are destroyed', () => { + const service2 = TestBed.runInInjectionContext( + () => new CpsRootFontSizeService() + ); + + service.ngOnDestroy(); service2.ngOnDestroy(); expect(document.querySelector(`[${SENTINEL_ATTR}]`)).not.toBeNull(); @@ -151,7 +199,7 @@ describe('CpsRootFontSizeService (SSR)', () => { expect(service.fontSize()).toBe(16); }); - it('should NOT create a sentinel element in SSR', () => { + it('should not create a sentinel element in SSR', () => { TestBed.configureTestingModule({ providers: [{ provide: PLATFORM_ID, useValue: 'server' }] }); @@ -160,7 +208,7 @@ describe('CpsRootFontSizeService (SSR)', () => { expect(doc.querySelector(`[${SENTINEL_ATTR}]`)).toBeNull(); }); - it('should NOT create a ResizeObserver in SSR', () => { + it('should not create a ResizeObserver in SSR', () => { const observerCtor = (globalThis as any).ResizeObserver as jest.Mock; TestBed.configureTestingModule({ providers: [{ provide: PLATFORM_ID, useValue: 'server' }] diff --git a/projects/cps-ui-kit/src/lib/services/cps-root-font-size/cps-root-font-size.service.ts b/projects/cps-ui-kit/src/lib/services/cps-root-font-size/cps-root-font-size.service.ts index 9e09dddd..28c4dc75 100644 --- a/projects/cps-ui-kit/src/lib/services/cps-root-font-size/cps-root-font-size.service.ts +++ b/projects/cps-ui-kit/src/lib/services/cps-root-font-size/cps-root-font-size.service.ts @@ -25,7 +25,9 @@ import { * In microfrontend environments the sentinel element is keyed by a known DOM * attribute (`data-cps-root-font-size-sentinel`) and reused if already present, * so only one sentinel node exists per document regardless of how many - * instances of this service are created. + * instances of this service are created. The sentinel is intentionally never + * removed from the DOM — it is a lightweight, invisible element and removing it + * could silently break any other live service instance still observing it. * * Only active in browser environments. Under SSR the `fontSize` signal is * initialized to `16` (the standard browser default) and no DOM observers are created. @@ -55,7 +57,6 @@ export class CpsRootFontSizeService implements OnDestroy { ); private _sentinel: HTMLElement | null = null; - private _sentinelOwned = false; private _sentinelObserver: ResizeObserver | null = null; /** Reactive signal containing the current root font size in pixels. */ @@ -68,11 +69,6 @@ export class CpsRootFontSizeService implements OnDestroy { ngOnDestroy(): void { this._sentinelObserver?.disconnect(); - - if (this._sentinelOwned && this._sentinel) { - this._sentinel.remove(); - } - this._sentinelObserver = null; this._sentinel = null; } @@ -97,7 +93,6 @@ export class CpsRootFontSizeService implements OnDestroy { left: '0' }); this._document.documentElement.appendChild(sentinel); - this._sentinelOwned = true; } this._sentinel = sentinel; From e0d735dc669fbe6346cd14b3c62528aeade94323 Mon Sep 17 00:00:00 2001 From: fateeand Date: Thu, 7 May 2026 18:33:29 +0200 Subject: [PATCH 5/5] fix lint failure --- .../cps-root-font-size/cps-root-font-size.service.spec.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/projects/cps-ui-kit/src/lib/services/cps-root-font-size/cps-root-font-size.service.spec.ts b/projects/cps-ui-kit/src/lib/services/cps-root-font-size/cps-root-font-size.service.spec.ts index ffb4717a..00b86e5d 100644 --- a/projects/cps-ui-kit/src/lib/services/cps-root-font-size/cps-root-font-size.service.spec.ts +++ b/projects/cps-ui-kit/src/lib/services/cps-root-font-size/cps-root-font-size.service.spec.ts @@ -11,7 +11,7 @@ const SENTINEL_ATTR = 'data-cps-root-font-size-sentinel'; describe('CpsRootFontSizeService', () => { let service: CpsRootFontSizeService; let document: Document; - let resizeCallback: ResizeObserverCallback; + let resizeCallback: (entries: unknown[], observer: unknown) => void; let mockObserve: jest.Mock; let mockDisconnect: jest.Mock; let computedFontSize: string; @@ -22,7 +22,7 @@ describe('CpsRootFontSizeService', () => { mockDisconnect = jest.fn(); (globalThis as any).ResizeObserver = jest.fn( - (cb: ResizeObserverCallback) => { + (cb: (entries: unknown[], observer: unknown) => void) => { resizeCallback = cb; return { observe: mockObserve, disconnect: mockDisconnect }; } @@ -138,9 +138,9 @@ describe('CpsRootFontSizeService', () => { }); it('should keep tracking after the owning instance is destroyed: surviving instance still updates on resize', () => { - const callbacks: ResizeObserverCallback[] = []; + const callbacks: ((entries: unknown[], observer: unknown) => void)[] = []; (globalThis as any).ResizeObserver = jest.fn( - (cb: ResizeObserverCallback) => { + (cb: (entries: unknown[], observer: unknown) => void) => { callbacks.push(cb); return { observe: mockObserve, disconnect: mockDisconnect }; }