UNPKG

@studiohyperdrive/ngx-utils

Version:

A series of abstracts, utils, pipes and services for Angular applications.

1 lines 98.2 kB
{"version":3,"file":"studiohyperdrive-ngx-utils.mjs","sources":["../../../../libs/angular/utils/src/lib/directives/focus-click/focus-click.directive.ts","../../../../libs/angular/utils/src/lib/directives/index.ts","../../../../libs/angular/utils/src/lib/injects/query-params/query-params.inject.ts","../../../../libs/angular/utils/src/lib/services/window-service/window.service.ts","../../../../libs/angular/utils/src/lib/services/broadcast-channel/broadcast-channel.service.ts","../../../../libs/angular/utils/src/lib/services/subscription-service/subscription.service.ts","../../../../libs/angular/utils/src/lib/services/storage-service/storage.service.ts","../../../../libs/angular/utils/src/lib/services/media-query/mediaquery.service.ts","../../../../libs/angular/utils/src/lib/pipes/array-contains-one/array-contains-one.pipe.ts","../../../../libs/angular/utils/src/lib/pipes/btw/btw.pipe.ts","../../../../libs/angular/utils/src/lib/pipes/clean-array/clean-array.pipe.ts","../../../../libs/angular/utils/src/lib/pipes/entries/entries.pipe.ts","../../../../libs/angular/utils/src/lib/pipes/has-observers/has-observers.pipe.ts","../../../../libs/angular/utils/src/lib/pipes/has-own-property/has-own-property.pipe.ts","../../../../libs/angular/utils/src/lib/pipes/has-values/has-values.pipe.ts","../../../../libs/angular/utils/src/lib/pipes/highlight/ngx-highlight.pipe.ts","../../../../libs/angular/utils/src/lib/pipes/iban/iban.pipe.ts","../../../../libs/angular/utils/src/lib/pipes/is-not-empty/is-not-empty.pipe.ts","../../../../libs/angular/utils/src/lib/pipes/join/join.pipe.ts","../../../../libs/angular/utils/src/lib/pipes/limit-to/limit-to.pipe.ts","../../../../libs/angular/utils/src/lib/pipes/log/log.pipe.ts","../../../../libs/angular/utils/src/lib/pipes/merge-arrays/merge-arrays.pipe.ts","../../../../libs/angular/utils/src/lib/tokens/replace-elements.token.ts","../../../../libs/angular/utils/src/lib/pipes/replace-elements/replace-elements.pipe.ts","../../../../libs/angular/utils/src/lib/pipes/safe-html/safe-html.pipe.ts","../../../../libs/angular/utils/src/lib/pipes/strip-html/strip-html.pipe.ts","../../../../libs/angular/utils/src/lib/pipes/to-array/to-array.pipe.ts","../../../../libs/angular/utils/src/lib/pipes/transform/transform.pipe.ts","../../../../libs/angular/utils/src/lib/pipes/truncate-text/truncate-text.pipe.ts","../../../../libs/angular/utils/src/lib/pipes/unique-by/uniq-by.pipe.ts","../../../../libs/angular/utils/src/lib/pipes/with-router-links/with-router-links.config.ts","../../../../libs/angular/utils/src/lib/pipes/with-router-links/with-router-links.pipe.ts","../../../../libs/angular/utils/src/lib/pipes/index.ts","../../../../libs/angular/utils/src/lib/abstracts/query-param-form-sync/query-param-form-sync.component.abstract.ts","../../../../libs/angular/utils/src/lib/providers/replace-elements/replace-elements.provider.ts","../../../../libs/angular/utils/src/lib/utils/simple-changes/simple-changes.util.ts","../../../../libs/angular/utils/src/public-api.ts","../../../../libs/angular/utils/src/studiohyperdrive-ngx-utils.ts"],"sourcesContent":["import { Directive, EventEmitter, HostBinding, HostListener, Input, Output } from '@angular/core';\n\n@Directive({\n\tselector: '[focusClick]',\n\tstandalone: true,\n})\nexport class FocusClickDirective {\n\t// Allow the button to ignore click events when set to true\n\t@Input() public disabled: boolean = false;\n\n\t// Allow the function passed by the host to be executed\n\t// when the emit() method gets called\n\t/**\n\t * This directive replaces the default `click` directive and allows the user to execute\n\t * the `click` event by clicking the mouse **and** by using the `enter` key on focus.\n\t *\n\t * A tabindex of `0` gets added to the host.\n\t *\n\t * @memberof FocusClickDirective\n\t */\n\t@Output() public readonly focusClick: EventEmitter<void | Event> =\n\t\tnew EventEmitter<void | Event>();\n\n\t// Make every tag that uses this directive by default tabbable\n\t@HostBinding('attr.tabindex') private readonly tabIndex: number = 0;\n\n\t// Add eventhandler to the click event\n\t@HostListener('click', ['$event'])\n\tprivate isClicked(event: Event): void {\n\t\tif (!this.disabled) {\n\t\t\tthis.focusClick.emit(event);\n\t\t}\n\t}\n\n\t// Add eventhandler to keydown event When enter is pressed and the event\n\t// isn't blocked, execute the click function of the host\n\t@HostListener('keydown.enter')\n\tprivate isEntered(): void {\n\t\tif (!this.disabled) {\n\t\t\tthis.focusClick.emit();\n\t\t}\n\t}\n}\n","import { FocusClickDirective } from './focus-click/focus-click.directive';\n\nexport const Directives = [FocusClickDirective];\n\nexport { FocusClickDirective } from './focus-click/focus-click.directive';\n","import { inject } from '@angular/core';\nimport { ActivatedRoute } from '@angular/router';\nimport { Observable } from 'rxjs';\n\nexport const getQueryParams = <ParamType>(): Observable<ParamType> => {\n\treturn inject(ActivatedRoute).queryParams as Observable<ParamType>;\n};\n","import { DOCUMENT, isPlatformBrowser } from '@angular/common';\nimport { Inject, Injectable, PLATFORM_ID } from '@angular/core';\nimport { BehaviorSubject, Observable } from 'rxjs';\n\n// @dynamic\n/**\n * @deprecated: This service has been deprecated in favor of the one in @studiohyperdrive/ngx-core\n */\n@Injectable({\n\tprovidedIn: 'root',\n})\nexport class WindowService {\n\t/* eslint-disable @typescript-eslint/member-ordering */\n\tprivate widthSubject$: BehaviorSubject<number> = new BehaviorSubject<number>(1200);\n\tprivate scrollingUpSubject$: BehaviorSubject<boolean> = new BehaviorSubject(true);\n\tprivate currentScrollPositionSubject$: BehaviorSubject<number> = new BehaviorSubject<number>(0);\n\n\t/**\n\t * Observable to get the window-width, defaults to 1200 when no window is defined\n\t */\n\tpublic width$: Observable<number> = this.widthSubject$.asObservable();\n\t/**\n\t * Observable to track when the scroll has ended\n\t */\n\tpublic scrollingUp$: Observable<boolean> = this.scrollingUpSubject$.asObservable();\n\t/**\n\t * Observable of the current scroll position after the scroll has ended\n\t */\n\tpublic currentScrollPosition$: Observable<number> =\n\t\tthis.currentScrollPositionSubject$.asObservable();\n\t/**\n\t * Current scroll position after the scroll has ended\n\t */\n\tpublic currentScrollPosition: number = 0;\n\t/**\n\t * The platforms Window object\n\t */\n\tpublic window: Window;\n\t/* eslint-enable */\n\n\tconstructor(\n\t\t@Inject(DOCUMENT) public document: Document,\n\t\t// tslint:disable-next-line: ban-types\n\t\t@Inject(PLATFORM_ID) private platformId: string\n\t) {\n\t\tif (this.isBrowser() && this.hasDocument()) {\n\t\t\tthis.window = this.document.defaultView;\n\t\t\tthis.document.addEventListener('scroll', this.handleContentScroll.bind(this));\n\n\t\t\tthis.widthSubject$.next(this.window.innerWidth);\n\n\t\t\tthis.window.addEventListener('resize', () => {\n\t\t\t\tif (this.window.innerWidth && this.widthSubject$.getValue()) {\n\t\t\t\t\tthis.widthSubject$.next(this.window.innerWidth);\n\t\t\t\t}\n\t\t\t});\n\t\t}\n\t}\n\n\t/**\n\t * Scrolls to the provided position of the page\n\t *\n\t * @param offset - Offset to which we want to scroll, scrolls to top when no offset is provided\n\t */\n\tpublic scrollTo(offset: number = 0): void {\n\t\tif (!this.window) {\n\t\t\treturn;\n\t\t}\n\t\tthis.window.scrollTo(0, offset);\n\t}\n\n\t/**\n\t * Returns whether there is a document present\n\t */\n\tpublic hasDocument(): boolean {\n\t\treturn !!this.document;\n\t}\n\n\t/**\n\t * Returns whether the current platform is a browser\n\t */\n\tpublic isBrowser(): boolean {\n\t\treturn isPlatformBrowser(this.platformId);\n\t}\n\n\t/**\n\t * Run a provided function only when we're in the browser and not in a server side rendered application\n\t *\n\t * @param action - Function we want to run in the browser\n\t */\n\tpublic runInBrowser(\n\t\taction: (data: { browserWindow: Window; browserDocument: Document }) => void\n\t) {\n\t\tif (this.isBrowser) {\n\t\t\taction({ browserWindow: this.window, browserDocument: this.document });\n\t\t} else {\n\t\t\tconsole.warn('Browser depended function has not run.');\n\t\t}\n\t}\n\n\tprivate handleContentScroll(): void {\n\t\tif (window.pageYOffset > this.currentScrollPosition) {\n\t\t\tthis.scrollingUpSubject$.next(false);\n\t\t} else {\n\t\t\tthis.scrollingUpSubject$.next(true);\n\t\t}\n\n\t\tthis.currentScrollPosition = window.pageYOffset < 0 ? 0 : window.pageYOffset;\n\t\tthis.currentScrollPositionSubject$.next(this.currentScrollPosition);\n\t}\n}\n","import { Injectable } from '@angular/core';\nimport { EMPTY, fromEvent, Observable } from 'rxjs';\nimport { NgxWindowService } from '@studiohyperdrive/ngx-core';\n\n/**\n * A service that wraps the BroadCastChannel API and provides an Observable based implementation to the channel messages.\n *\n * For more information:\n * https://developer.mozilla.org/en-US/docs/Web/API/BroadcastChannel\n */\n@Injectable({\n\tprovidedIn: 'root',\n})\nexport class NgxBroadcastChannelService {\n\t/**\n\t * A record holding all the broadcast channels\n\t */\n\tprivate broadcastChannel: Record<string, BroadcastChannel> = {};\n\n\tconstructor(private readonly windowService: NgxWindowService) {}\n\n\t/**\n\t * initChannel\n\t *\n\t * The initChannel method initializes a new BroadcastChannel instance.\n\t *\n\t * @param args{ConstructorParameters<typeof BroadcastChannel>} - The arguments to pass to the BroadcastChannel constructor.\n\t */\n\tpublic initChannel(...args: ConstructorParameters<typeof BroadcastChannel>): void {\n\t\t// Iben: Only run when in browser\n\t\tthis.windowService.runInBrowser(() => {\n\t\t\tconst [channelName] = args;\n\n\t\t\tif (!channelName) {\n\t\t\t\tconsole.error(\n\t\t\t\t\t'NgxUtils: There was an attempt to initialize a BroadcastChannel without providing a name.'\n\t\t\t\t);\n\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tif (!this.broadcastChannel[channelName]) {\n\t\t\t\tthis.broadcastChannel[channelName] = new BroadcastChannel(...args);\n\t\t\t}\n\t\t});\n\t}\n\n\t/**\n\t * closeChannel\n\t *\n\t * The closeChannel method closes a selected BroadcastChannel instance.\n\t *\n\t * @param channelName{string} - The name of the Broadcast Channel.\n\t */\n\tpublic closeChannel(channelName: string): void {\n\t\tif (!channelName || !this.broadcastChannel[channelName]) {\n\t\t\treturn;\n\t\t}\n\n\t\tthis.broadcastChannel[channelName].close();\n\t\tdelete this.broadcastChannel[channelName];\n\t}\n\n\t/**\n\t * postMessage\n\t *\n\t * The postMessage method sends a message to a selected BroadcastChannel instance.\n\t *\n\t * @param channelName{string} - The name of the Broadcast Channel.\n\t * @param message{any} - The payload to send through the channel.\n\t */\n\tpublic postMessage<MessageType = any>(channelName: string, message: MessageType): void {\n\t\tif (!channelName || !this.broadcastChannel[channelName]) {\n\t\t\tconsole.error(\n\t\t\t\t'NgxUtils: There was an attempt to post a message to a channel without providing a name or the selected channel does not exist. The included message was:',\n\t\t\t\tmessage\n\t\t\t);\n\n\t\t\treturn;\n\t\t}\n\n\t\tthis.broadcastChannel[channelName].postMessage(message);\n\t}\n\n\t/**\n\t * selectChannelMessages\n\t *\n\t * The selectChannelMessages method subscribes to the `message` (bc.onmessage) event of a selected BroadcastChannel instance.\n\t *\n\t * @param channelName{string} - The name of the Broadcast Channel.\n\t * @returns Observable<MessageEvent> - The message event of the channel wrapped in an observable.\n\t */\n\tpublic selectChannelMessages<MessageType = any>(\n\t\tchannelName: string\n\t): Observable<MessageEvent<MessageType>> {\n\t\tif (!channelName || !this.broadcastChannel[channelName]) {\n\t\t\tconsole.error(\n\t\t\t\t\"NgxUtils: There was an attempt to select a BroadcastChannel's messages without providing a name or the selected channel does not exist.\"\n\t\t\t);\n\n\t\t\treturn EMPTY;\n\t\t}\n\n\t\treturn fromEvent<MessageEvent<MessageType>>(this.broadcastChannel[channelName], 'message');\n\t}\n\n\t/**\n\t * selectChannelMessageErrors\n\t *\n\t * The selectChannelMessageErrors method subscribes to the `messageerror` (bc.onmessageerror) event of a selected BroadcastChannel instance.\n\t *\n\t * @param channelName{string} - The name of the Broadcast Channel.\n\t * @returns Observable<MessageEvent> - The messageerror event of the channel wrapped in an observable.\n\t */\n\tpublic selectChannelMessageErrors<MessageType = any>(\n\t\tchannelName: string\n\t): Observable<MessageEvent<MessageType>> {\n\t\tif (!channelName || !this.broadcastChannel[channelName]) {\n\t\t\tconsole.error(\n\t\t\t\t\"NgxUtils: There was an attempt to select a BroadcastChannel's message errors without providing a name or the selected channel does not exist.\"\n\t\t\t);\n\n\t\t\treturn EMPTY;\n\t\t}\n\n\t\treturn fromEvent<MessageEvent<MessageType>>(\n\t\t\tthis.broadcastChannel[channelName],\n\t\t\t'messageerror'\n\t\t);\n\t}\n}\n","import { Injectable, OnDestroy } from '@angular/core';\nimport { Subject } from 'rxjs';\n\n@Injectable()\nexport class SubscriptionService implements OnDestroy {\n\tpublic destroyed$: Subject<boolean> = new Subject<boolean>();\n\n\tpublic ngOnDestroy(): void {\n\t\tthis.destroyed$.next(true);\n\t\tthis.destroyed$.complete();\n\t}\n}\n","import { Injectable } from '@angular/core';\nimport { BehaviorSubject, NEVER, Observable, Subject } from 'rxjs';\nimport { NgxWindowService } from '@studiohyperdrive/ngx-core';\nimport {\n\tNgxStorage,\n\tNgxStorageClearEvent,\n\tNgxStorageEvent,\n\tNgxStorageRecord,\n\tNgxStorageRemoveEvent,\n\tNgxStorageSetEvent,\n\tNgxStorageType,\n} from '../../types';\n\n/**\n * A service that provides a SSR-proof Observable based approach to the session- and localStorage.\n */\n@Injectable({ providedIn: 'root' })\nexport class NgxStorageService {\n\t/**\n\t * A record to hold the properties in the sessionStorage\n\t */\n\tprivate readonly sessionStorageRecord: NgxStorageRecord = {};\n\t/**\n\t * A record to hold the properties in the localStorage\n\t */\n\tprivate readonly localStorageRecord: NgxStorageRecord = {};\n\t/**\n\t * A subject to hold the events of the storage\n\t */\n\tprivate readonly storageEventSubject: Subject<NgxStorageEvent> = new Subject();\n\n\t/**\n\t * An observable that emits whenever the session- or the localStorage was updated\n\t */\n\tpublic readonly storageEvents$: Observable<NgxStorageEvent> =\n\t\tthis.storageEventSubject.asObservable();\n\n\tconstructor(private readonly windowService: NgxWindowService) {\n\t\t// Iben: Get the initial values of the session and the local storage\n\t\twindowService.runInBrowser(() => {\n\t\t\tthis.setupStorage(sessionStorage, this.sessionStorageRecord);\n\t\t\tthis.setupStorage(localStorage, this.localStorageRecord);\n\t\t});\n\t}\n\n\t/**\n\t * A localStorage implementation using observables\n\t */\n\tpublic get localStorage(): NgxStorage {\n\t\treturn {\n\t\t\tgetItem: <DataType = any>(key: string) => this.getItem<DataType>(key, localStorage),\n\t\t\tgetItemObservable: <DataType = any>(key: string) =>\n\t\t\t\tthis.getItemObservable<DataType>(key, this.localStorageRecord),\n\t\t\tremoveItem: (key: string) =>\n\t\t\t\tthis.removeItem(key, localStorage, this.localStorageRecord, 'local'),\n\t\t\tsetItem: <DataType = any>(key: string, item: DataType) =>\n\t\t\t\tthis.setItem(key, item, localStorage, this.localStorageRecord, 'local'),\n\t\t\tclear: () => this.clearStorage(localStorage, this.localStorageRecord, 'local'),\n\t\t};\n\t}\n\n\t/**\n\t * A sessionStorage implementation using observables\n\t */\n\tpublic get sessionStorage(): NgxStorage {\n\t\treturn {\n\t\t\tgetItem: <DataType = any>(key: string) => this.getItem<DataType>(key, sessionStorage),\n\t\t\tgetItemObservable: <DataType = any>(key: string) =>\n\t\t\t\tthis.getItemObservable<DataType>(key, this.sessionStorageRecord),\n\t\t\tremoveItem: (key: string) =>\n\t\t\t\tthis.removeItem(key, sessionStorage, this.sessionStorageRecord, 'session'),\n\t\t\tsetItem: <DataType = any>(key: string, item: DataType) =>\n\t\t\t\tthis.setItem(key, item, sessionStorage, this.sessionStorageRecord, 'session'),\n\t\t\tclear: () => this.clearStorage(sessionStorage, this.sessionStorageRecord, 'session'),\n\t\t};\n\t}\n\n\tprivate getItem<DataType = any>(key: string, storage: Storage): DataType {\n\t\treturn this.parseValue(storage.getItem(key));\n\t}\n\n\t/**\n\t * Returns an observable version of the storage value\n\t *\n\t * @param key - The key of the storage value\n\t * @param record - The storage record\n\t */\n\tprivate getItemObservable<DataType>(\n\t\tkey: string,\n\t\trecord: NgxStorageRecord\n\t): Observable<DataType> {\n\t\t// Iben: Return NEVER when not in browser\n\t\tif (!this.windowService.isBrowser()) {\n\t\t\treturn NEVER;\n\t\t}\n\n\t\t// Iben: If the subject already exists, we return the observable\n\t\tif (record[key]) {\n\t\t\treturn record[key].asObservable();\n\t\t}\n\n\t\t// Iben: If no subject exits, we create a new one\n\t\trecord[key] = new BehaviorSubject<DataType>(undefined);\n\n\t\t// Iben: Return the observable\n\t\treturn this.getItemObservable<DataType>(key, record);\n\t}\n\n\t/**\n\t * Sets an item in the storage\n\t *\n\t * @param key - The key of the item\n\t * @param item - The item in the storage\n\t * @param storage - The storage in which we want to save the item\n\t * @param record - The corresponding storage record\n\t */\n\tprivate setItem<DataType = any>(\n\t\tkey: string,\n\t\titem: DataType,\n\t\tstorage: Storage,\n\t\trecord: NgxStorageRecord,\n\t\ttype: NgxStorageType\n\t): NgxStorageSetEvent | undefined {\n\t\t// Iben: Early exit when we're in the browser\n\t\tif (!this.windowService.isBrowser()) {\n\t\t\treturn undefined;\n\t\t}\n\n\t\t// Iben: Check if there's already a subject for this item. If not, we create one\n\t\tlet subject = record[key];\n\n\t\tif (!subject) {\n\t\t\tsubject = new BehaviorSubject<DataType>(undefined);\n\t\t\tstorage[key] = subject;\n\t\t}\n\n\t\t// Iben: Store the current value of the subject\n\t\tconst oldValue = subject.getValue();\n\n\t\t// Iben: Set the item in the storage\n\t\tstorage.setItem(key, typeof item === 'string' ? item : JSON.stringify(item));\n\n\t\t// Iben: Update the subject in the record\n\t\tsubject.next(item);\n\n\t\t// Iben: Create the storage event\n\t\tconst event: NgxStorageSetEvent = {\n\t\t\tkey,\n\t\t\tnewValue: item,\n\t\t\toldValue,\n\t\t\tstorage: type,\n\t\t\ttype: 'set',\n\t\t};\n\n\t\t// Iben: Emit the storage event\n\t\tthis.storageEventSubject.next(event);\n\n\t\t// Iben: Return the storage event\n\t\treturn event;\n\t}\n\n\t/**\n\t * Remove an item from the storage and emit a remove event\n\t *\n\t * @param key - The key of the item\n\t * @param storage - The storage we wish to remove the item from\n\t * @param record - The record with the subject\n\t * @param type - The type of storage\n\t */\n\tprivate removeItem(\n\t\tkey: string,\n\t\tstorage: Storage,\n\t\trecord: NgxStorageRecord,\n\t\ttype: NgxStorageType\n\t): NgxStorageRemoveEvent | undefined {\n\t\t// Iben: Early exit when we're not in the browser\n\t\tif (!this.windowService.isBrowser()) {\n\t\t\treturn undefined;\n\t\t}\n\n\t\t// Iben: Get the old item\n\t\tconst oldValue = this.parseValue(storage.getItem(key));\n\n\t\t// Iben: Remove the item from the storage\n\t\tstorage.removeItem(key);\n\n\t\t// Iben Update the subject if it exists\n\t\trecord[key]?.next(undefined);\n\n\t\t// Iben: Create the event and return and emit it\n\t\tconst event: NgxStorageRemoveEvent = {\n\t\t\toldValue,\n\t\t\tstorage: type,\n\t\t\tkey,\n\t\t\ttype: 'remove',\n\t\t};\n\n\t\tthis.storageEventSubject.next(event);\n\t\treturn event;\n\t}\n\n\t/**\n\t * Clears the storage, completes all subjects and emits a clear event\n\t *\n\t * @param storage - The storage we wish to clear\n\t * @param record - The record with the subjects\n\t * @param type - The type of storage\n\t */\n\tprivate clearStorage(\n\t\tstorage: Storage,\n\t\trecord: NgxStorageRecord,\n\t\ttype: NgxStorageType\n\t): NgxStorageClearEvent | undefined {\n\t\t// Iben: Early exit when we're not in the browser\n\t\tif (!this.windowService.isBrowser()) {\n\t\t\treturn undefined;\n\t\t}\n\n\t\t// Iben: Clear the storage\n\t\tstorage.clear();\n\n\t\t// Iben: Clear the record and complete all subjects\n\t\tObject.entries(record).forEach(([key, subject]) => {\n\t\t\tsubject.next(undefined);\n\t\t\tsubject.complete();\n\n\t\t\trecord[key] = undefined;\n\t\t});\n\n\t\t// Iben: Create and emit event\n\t\tconst event: NgxStorageClearEvent = {\n\t\t\ttype: 'clear',\n\t\t\tstorage: type,\n\t\t};\n\n\t\tthis.storageEventSubject.next(event);\n\n\t\treturn event;\n\t}\n\n\t/**\n\t * Grabs the existing storage and updates the record\n\t *\n\t * @private\n\t * @param {Storage} storage - The current state of the storage\n\t * @param {NgxStorageRecord} record\n\t * @memberof NgxStorageService\n\t */\n\tprivate setupStorage(storage: Storage, record: NgxStorageRecord) {\n\t\tObject.entries(storage).forEach(([key, value]) => {\n\t\t\trecord[key] = new BehaviorSubject(this.parseValue(value));\n\t\t});\n\t}\n\n\t/**\n\t * Parses a string value from the storage to an actual value\n\t *\n\t * @param value - The provided string value\n\t */\n\tprivate parseValue(value: string): any {\n\t\t// Iben: If the value does not exist, return the value\n\t\tif (!value) {\n\t\t\treturn value;\n\t\t}\n\n\t\t// Iben: If the value is either true or false, return a boolean version of the value\n\t\tif (value === 'true' || value === 'false') {\n\t\t\treturn value === 'true';\n\t\t}\n\n\t\t// Iben: If the value is a number, return the parsed number\n\t\tif (value.match(/^[0-9]*[,.]{0,1}[0-9]*$/)) {\n\t\t\treturn Number(value);\n\t\t}\n\n\t\t// Iben: If the value is an object, return the parsed object\n\t\tif (value.match(/{(.*:.*[,]{0,1})*}/)) {\n\t\t\treturn JSON.parse(value);\n\t\t}\n\n\t\t// Iben: Return the string value as is\n\t\treturn value;\n\t}\n}\n","import { Injectable, OnDestroy } from '@angular/core';\nimport { filter, map, Observable, ReplaySubject } from 'rxjs';\nimport { NgxWindowService } from '@studiohyperdrive/ngx-core';\n\n/**\n * A service that can be used to track media queries and their changes. It exposes a method\n * to register media queries, which takes an array of tuples with the id of the media query\n * and the query itself. The service will then emit the id of the media query that has\n * changed when subscribed to the `getMatchingQuery$` method.\n */\n@Injectable({ providedIn: 'root' })\nexport class NgxMediaQueryService implements OnDestroy {\n\t/**\n\t * A map of media queries that are registered with the service.\n\t */\n\tprivate readonly queryListMap: Map<string, MediaQueryList> = new Map();\n\n\t/**\n\t * A map of the registered media queries with their id.\n\t */\n\tprivate readonly queryIdMap: Map<string, string> = new Map();\n\n\t/*\n\t * A map of listeners that are registered with the service.\n\t * They are saved to be able to remove them when the service is destroyed.\n\t */\n\tprivate readonly mediaQueryListenerMap: Map<\n\t\tstring,\n\t\t(queryChangedEvent: MediaQueryListEvent) => void\n\t> = new Map();\n\n\t/**\n\t * A subject that emits the id of the media query that has changed.\n\t */\n\tprivate readonly queryChangedSubject: ReplaySubject<string> = new ReplaySubject();\n\n\tconstructor(private readonly windowService: NgxWindowService) {}\n\n\t/**\n\t * Register a list of media queries that need to be tracked by the service.\n\t *\n\t * @param queries - A list of media queries that should be registered with the service.\n\t */\n\tpublic registerMediaQueries(...queries: [id: string, query: string][]): void {\n\t\tthis.windowService.runInBrowser(({ browserWindow }) => {\n\t\t\tfor (const [id, query] of queries) {\n\t\t\t\t// Wouter: Warn if the id has already been registered.\n\t\t\t\tif (this.queryIdMap.get(id)) {\n\t\t\t\t\treturn console.warn(\n\t\t\t\t\t\t`NgxMediaQueryService: Media query with id '${id}' already exists and is defined by '${this.queryIdMap.get(\n\t\t\t\t\t\t\tid\n\t\t\t\t\t\t)}'`\n\t\t\t\t\t);\n\t\t\t\t}\n\n\t\t\t\t// Wouter: If the query has already been registered, throw an error to prevent duplicate subscriptions\n\t\t\t\tif ([...this.queryIdMap].some(([_, value]) => value === query)) {\n\t\t\t\t\tconst duplicateQuery = [...this.queryIdMap].find(\n\t\t\t\t\t\t([_, value]) => value === query\n\t\t\t\t\t);\n\t\t\t\t\tthrow new Error(\n\t\t\t\t\t\t`NgxMediaQueryService: Query of ['${id}', ${query}] already exists and is defined by ['${duplicateQuery[0]}', ${duplicateQuery[1]}]`\n\t\t\t\t\t);\n\t\t\t\t}\n\n\t\t\t\t// Wouter: save the id and query\n\t\t\t\tthis.queryIdMap.set(id, query);\n\n\t\t\t\t// Wouter: For each query, create a MediaQueryList object\n\t\t\t\tconst matchedQuery = browserWindow.matchMedia(query);\n\n\t\t\t\t// Wouter: Save the query\n\t\t\t\tthis.queryListMap.set(id, matchedQuery);\n\n\t\t\t\t// Wouter: Emit the id of the query that has changed\n\t\t\t\tthis.queryChangedSubject.next(id);\n\n\t\t\t\t// Wouter: Create a listener for the query. This is done separately to be\n\t\t\t\t// able to remove the listener when the service is destroyed\n\t\t\t\tconst listener = (queryChangedEvent: MediaQueryListEvent) => {\n\t\t\t\t\tthis.queryListMap.set(id, queryChangedEvent.currentTarget as MediaQueryList);\n\n\t\t\t\t\t// Wouter: Emit the id of the query that has changed\n\t\t\t\t\tthis.queryChangedSubject.next(id);\n\t\t\t\t};\n\n\t\t\t\t// Wouter: Register the listener to the query\n\t\t\t\tmatchedQuery.addEventListener('change', listener);\n\n\t\t\t\t// Wouter: Save the listener\n\t\t\t\tthis.mediaQueryListenerMap.set(id, listener);\n\t\t\t}\n\t\t});\n\t}\n\n\t/**\n\t * Pass the id of the query whose changes need to be listened to.\n\t *\n\t * @param id - The id of the media query that should be checked.\n\t * @returns An observable that emits a boolean value whenever the requested media query changes.\n\t */\n\tpublic getMatchingQuery$(id: string): Observable<boolean> {\n\t\t// Wouter: Throw an error if the query has not been registered\n\t\tif (!this.queryIdMap.has(id)) {\n\t\t\tthrow new Error(\n\t\t\t\t`NgxMediaQueryService: No media query with id '${id}' has been registered. Please register the media query first using the 'registerMediaQueries' method.`\n\t\t\t);\n\t\t}\n\n\t\treturn this.queryChangedSubject.asObservable().pipe(\n\t\t\t// Wouter: Filter the query that has changed.\n\t\t\t// This will make sure only the [id] streams are triggered.\n\t\t\tfilter((queryId) => queryId === id),\n\t\t\tmap(() => this.queryListMap.get(id).matches)\n\t\t);\n\t}\n\n\t/**\n\t * Unregister all media query subscriptions from the service.\n\t */\n\tpublic ngOnDestroy(): void {\n\t\tthis.windowService.runInBrowser(() => {\n\t\t\t// Wouter: Remove all eventListeners\n\t\t\tfor (const [id, query] of this.queryListMap) {\n\t\t\t\tquery.removeEventListener('change', this.mediaQueryListenerMap.get(id));\n\t\t\t}\n\n\t\t\t// Wouter: Complete subscriptions\n\t\t\tthis.queryChangedSubject.next(null);\n\t\t\tthis.queryChangedSubject.complete();\n\n\t\t\t// Wouter: Clear maps\n\t\t\tthis.queryListMap.clear();\n\t\t\tthis.mediaQueryListenerMap.clear();\n\t\t});\n\t}\n}\n","import { Pipe, PipeTransform } from '@angular/core';\n\n/**\n * A pipe that will search an array to see if on of its objects contains specific values on provided keys.\n *\n * Usage:\n * value | arrayContainsOne : ['prop1', 'prop2', ...]\n *\n * Examples:\n * \t{{\n * \t\t[\n * \t\t\t{ title: 'This is the title', description: 'This is the description' },\n * \t\t\t{ title: 'This is the title' }\n * \t\t] | arrayContainsOne: ['description']\n * \t}}\n * \tOutput: true\n */\n@Pipe({\n\tname: 'arrayContainsOne',\n\tstandalone: true,\n})\nexport class ArrayContainsOnePipe implements PipeTransform {\n\tpublic transform(values: unknown[], checkProps: string[] = []): boolean {\n\t\tif (\n\t\t\t!Array.isArray(values) ||\n\t\t\tvalues.length === 0 ||\n\t\t\t!Array.isArray(checkProps) ||\n\t\t\tcheckProps.length === 0\n\t\t) {\n\t\t\treturn false;\n\t\t}\n\n\t\treturn values.some((item: unknown) =>\n\t\t\tcheckProps.some((key) => item[key] !== null && item[key] !== undefined)\n\t\t);\n\t}\n}\n","import { Pipe, PipeTransform } from '@angular/core';\n\n@Pipe({\n\tname: 'btw',\n\tstandalone: true,\n})\nexport class BtwPipe implements PipeTransform {\n\t/**\n\t * Converts a BTW number to the correct format\n\t *\n\t * @param value - The value we wish to convert\n\t */\n\tpublic transform(value: string): string {\n\t\tif (!value) {\n\t\t\t// Denis: if the value is falsy, return it without transform.\n\t\t\treturn value;\n\t\t}\n\n\t\tconst addCharAtIndex = (original: string, char: string, index: number): string => {\n\t\t\treturn original.slice(0, index) + char + original.slice(index);\n\t\t};\n\n\t\t// Iben: Convert to string if it's a number\n\t\tvalue = value.toString();\n\n\t\tif (value.replace(/\\./g, '').length === 9) {\n\t\t\tvalue = '0' + value;\n\t\t}\n\n\t\t// Iben: Format: xxxx.xxx.xxx\n\t\tif (value.charAt(4) !== '.') {\n\t\t\tvalue = addCharAtIndex(value, '.', 4);\n\t\t}\n\n\t\tif (value.charAt(8) !== '.') {\n\t\t\tvalue = addCharAtIndex(value, '.', 8);\n\t\t}\n\n\t\treturn value;\n\t}\n}\n","import { Pipe, PipeTransform } from '@angular/core';\n\n@Pipe({\n\tname: 'cleanArray',\n\tstandalone: true,\n})\nexport class CleanArrayPipe implements PipeTransform {\n\t/**\n\t * Removes all falsy values from the provided array.\n\t *\n\t * @param value The values that need to be stripped of falsy values.\n\t * @param exceptions The falsy values that may be included in the filtered array.\n\t * @returns The filtered array.\n\t */\n\tpublic transform(value: any[], exceptions: any[] = []): any[] {\n\t\tif (!exceptions.length) {\n\t\t\treturn value.filter(Boolean);\n\t\t}\n\n\t\treturn value.filter((entry) => Boolean(entry) || exceptions.includes(entry));\n\t}\n}\n","import { Pipe, PipeTransform } from '@angular/core';\nimport { isObject } from 'lodash';\n\n@Pipe({\n\tname: 'entries',\n\tstandalone: true,\n})\nexport class EntriesPipe implements PipeTransform {\n\t/**\n\t * Transforms a record into a [key, value] array\n\t *\n\t * @param value - The provided record\n\t */\n\tpublic transform(value: Record<string, unknown>): [string, unknown][] {\n\t\t// Iben: If there's no value or the value is not an object, we return an empty array to prevent frontend breaking\n\t\tif (!value || !isObject(value) || Array.isArray(value)) {\n\t\t\treturn [];\n\t\t}\n\n\t\t// Iben: Transform the record to a [key,value] array\n\t\treturn Array.from(Object.entries(value));\n\t}\n}\n","import { Pipe, PipeTransform, EventEmitter } from '@angular/core';\n\n@Pipe({\n\tname: 'hasObservers',\n\tstandalone: true,\n})\nexport class HasObserversPipe implements PipeTransform {\n\tpublic transform(output: EventEmitter<unknown>): boolean {\n\t\treturn output && output.observers.length > 0;\n\t}\n}\n","import { Pipe, PipeTransform } from '@angular/core';\n\n@Pipe({\n\tname: 'hasOwnProperty',\n\tstandalone: true,\n})\nexport class HasOwnProperty implements PipeTransform {\n\t/**\n\t * Checks whether the specified property exists within the given object.\n\t *\n\t * @param {unknown} object - The object to check for the presence of the property.\n\t * @param {string} prop - The property name to check for within the object.\n\t * @return {boolean} - Returns `true` if the property exists in the object, `false` otherwise.\n\t */\n\tpublic transform(object: unknown, prop: string): boolean {\n\t\t// Use Object.prototype.hasOwnProperty to check if the property exists in the object\n\t\tconst hasOwnProperty = Object.prototype.hasOwnProperty;\n\n\t\t// Check if the object is not null or undefined, and if it has the specified property\n\t\treturn object != null && hasOwnProperty.call(object, prop);\n\t}\n}\n","import { Pipe, PipeTransform } from '@angular/core';\nimport { isObject } from 'lodash';\nimport clean from 'obj-clean';\n\n@Pipe({\n\tname: 'hasValues',\n\tstandalone: true,\n})\nexport class HasValuesPipe implements PipeTransform {\n\t/**\n\t * Checks whether an object has values\n\t *\n\t * @param value - The provided value\n\t */\n\tpublic transform(value: Object): boolean {\n\t\t// Iben: If the value is not an object, return valse\n\t\tif (!value || !isObject(value) || Array.isArray(value)) {\n\t\t\treturn false;\n\t\t}\n\n\t\t// Iben: Return when the object has values\n\t\treturn Object.keys(clean(value)).length > 0;\n\t}\n}\n","import { Pipe, PipeTransform } from '@angular/core';\nimport { normalizeString } from '@studiohyperdrive/utils';\n\nimport { NgxHighlightConfiguration } from '../../types';\n\n@Pipe({\n\tname: 'highlight',\n})\nexport class NgxHighlightPipe implements PipeTransform {\n\t/**\n\t * Highlights the provided substring of a text with a chosen dom element\n\t *\n\t * @param value - The full text with the part we wish to highlight\n\t * @param highlight - The part of the text we wish to highlight\n\t * @param config - The configuration to determine if we want to normalize the values, to be case-sensitive, which tag and/or class to use for the highlight\n\t * @param config.normalized - Default = true\n\t * @param config.caseInsensitive - Default = true\n\t * @param config.splitTextToHighlight - Default = false\n\t * @param config.someOrEveryMatch - Default = 'every'\n\t * @param config.tag - Default = 'mark'\n\t * @param config.highlightClass - Default = 'ngx-mark-highlight'\n\t */\n\tpublic transform(\n\t\tvalue: string | null,\n\t\thighlight: string | null,\n\t\tconfig?: NgxHighlightConfiguration | null\n\t): string {\n\t\t// Femke: Setup configuration or defaults\n\t\tconst normalized = config?.normalized ?? true;\n\t\tconst caseInsensitive = config?.caseInsensitive ?? true;\n\t\tconst splitTextToHighlight = config?.splitTextToHighlight ?? false;\n\t\tconst someOrEveryMatch = config?.someOrEveryMatch || 'every';\n\t\tconst tag = config?.tag || 'mark';\n\t\tconst highlightClass = config?.highlightClass ?? 'ngx-mark-highlight';\n\n\t\t// Femke: Early exit if there's no value/highlight or the value/highlight is not a string\n\t\tif (!value || !highlight || typeof value !== 'string' || typeof highlight !== 'string') {\n\t\t\treturn value;\n\t\t}\n\n\t\t// Femke: determine which value to use (normalized or not => é â ö etc will be replaced with e a o)\n\t\tconst usableValue = normalized ? normalizeString(value) : value;\n\t\tlet usableHighlight = (normalized ? normalizeString(highlight) : highlight)\n\t\t\t// Femke: escape all regex characters\n\t\t\t.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&');\n\n\t\tif (splitTextToHighlight) {\n\t\t\tusableHighlight = usableHighlight\n\t\t\t\t// Femke: replace all multiple spaces next to each other to a single space\n\t\t\t\t.replace(/ +/gi, ' ')\n\t\t\t\t// Femke: replace space with a pipe to have an OR query instead of searching for the entire string eg search for 'some' OR 'thing' OR 'else' instead of 'some thing else'\n\t\t\t\t.replace(/ /gi, '|');\n\t\t}\n\n\t\t// Femke: remove all multiple pipes at the start\n\t\tusableHighlight = usableHighlight.replace(/^\\|+/gi, '');\n\n\t\t// Femke: remember on what indices we updated in our search text\n\t\tlet changedIndices: { original: number; updated: number }[] = [];\n\t\tusableValue.split('').forEach((char, index) => {\n\t\t\tif (value[index] !== char) {\n\t\t\t\tchangedIndices.push({ original: index, updated: index });\n\t\t\t}\n\t\t});\n\n\t\tlet regexFlags = '';\n\n\t\t// Femke: Every match should be highlighted, so the regex should apply global\n\t\tif (someOrEveryMatch === 'every') {\n\t\t\tregexFlags = 'g';\n\t\t}\n\n\t\t// Femke: Regex should be case-insensitive\n\t\tif (caseInsensitive) {\n\t\t\tregexFlags += 'i';\n\t\t}\n\n\t\tconst regEx = new RegExp(usableHighlight, regexFlags);\n\t\tconst usableClass = highlightClass ? ` class=\"${highlightClass}\"` : '';\n\n\t\t// Femke: Use a custom replacer so we can update our marked indices in case we found a match with our regex\n\t\tlet replacedResult = usableValue.replace(regEx, (match, index: number) => {\n\t\t\tif (!match) {\n\t\t\t\treturn '';\n\t\t\t}\n\n\t\t\tconst endIndex = index + match.length;\n\t\t\t// Femke: we are using the original text here to make sure we have the original accent characters within the highlight\n\t\t\tconst result = `<${tag}${usableClass}>${value.substring(index, index + match.length)}</${tag}>`;\n\n\t\t\t// Femke: filter out all changed indices that lay withing the range of our current match\n\t\t\tchangedIndices = changedIndices.filter(\n\t\t\t\t(item) => !(index <= item.original && item.original < endIndex)\n\t\t\t);\n\n\t\t\t// Femke: update all found indices if they lay after the current replacement string\n\t\t\t// Femke: we are however keeping the original position to know with what we need to replace the character later on\n\t\t\tchangedIndices = changedIndices.map((changedIndex) => {\n\t\t\t\tif (index >= changedIndex.original) {\n\t\t\t\t\treturn changedIndex;\n\t\t\t\t} else {\n\t\t\t\t\t// Femke: take the current updated position (in case of multiple hits before this index\n\t\t\t\t\t// Femke: add the length of the replacement string but subtract the length of the match since that length was there already\n\t\t\t\t\treturn {\n\t\t\t\t\t\t...changedIndex,\n\t\t\t\t\t\tupdated: changedIndex.updated + result.length - match.length,\n\t\t\t\t\t};\n\t\t\t\t}\n\t\t\t});\n\n\t\t\treturn result;\n\t\t});\n\n\t\t// Femke: Update the character on each index that was replaced when normalizing the string but was not part of any match of the search query\n\t\tchangedIndices.forEach((item) => {\n\t\t\treplacedResult =\n\t\t\t\treplacedResult.substring(0, item.updated) +\n\t\t\t\tvalue.charAt(item.original) +\n\t\t\t\treplacedResult.substring(item.updated + 1);\n\t\t});\n\n\t\treturn replacedResult;\n\t}\n}\n","import { Pipe, PipeTransform } from '@angular/core';\n\n@Pipe({\n\tname: 'IBAN',\n\tstandalone: true,\n})\nexport class IbanPipe implements PipeTransform {\n\tpublic transform(value = ''): string {\n\t\tvalue = value.replace(/\\s/g, ''); // replace all spaces\n\n\t\tlet reformat = value.replace(/(.{4})/g, function (match) {\n\t\t\treturn match + ' '; // reformat into groups of 4 succeeded with a space\n\t\t});\n\n\t\treformat = reformat.trim(); // remove trailing space\n\n\t\treturn reformat;\n\t}\n}\n","import { Pipe, PipeTransform } from '@angular/core';\n\n@Pipe({\n\tname: 'isNotEmpty',\n\tstandalone: true,\n})\nexport class IsNotEmptyPipe implements PipeTransform {\n\t/**\n\t * Checks if a given argument is an object or array and if it is empty.\n\t *\n\t * @param value - can be any value.\n\t * @param checkProps - which props to check.\n\t */\n\ttransform(value: unknown, checkProps: string[] = []): boolean {\n\t\t// Denis: check for array first, because \"typeof Array\" will return \"object\"\n\t\tif (Array.isArray(value) && value.length > 0) {\n\t\t\treturn true;\n\t\t}\n\n\t\t// Denis: check for null values second, because \"typeof null\" will return \"object\"\n\t\tif (value === null) {\n\t\t\treturn false;\n\t\t}\n\n\t\t// Denis: if the object is not empty, and checkProps is provided, check those props.\n\t\tif (typeof value === 'object' && checkProps.length > 0) {\n\t\t\tconst propsWithValues = checkProps.filter(\n\t\t\t\t(key: string) => typeof value[key] !== 'undefined' && value[key] !== null\n\t\t\t);\n\n\t\t\treturn propsWithValues.length === checkProps.length;\n\t\t}\n\n\t\t// Denis: check for empty objects.\n\t\tif (typeof value === 'object' && Object.keys(value).length > 0) {\n\t\t\treturn true;\n\t\t}\n\n\t\treturn false;\n\t}\n}\n","// Iben: This implementation is from https://github.com/nglrx/pipes/blob/master/packages/pipes/src/lib/array/join/join.pipe.ts\n// The package is no longer supported and is not compatible with more recent versions of Angular\n\nimport { Pipe, PipeTransform } from '@angular/core';\n\n@Pipe({\n\tname: 'join',\n\tstandalone: true,\n})\nexport class JoinPipe implements PipeTransform {\n\t/**\n\t * Transforms an array to a joined string\n\t *\n\t * @param values - An array of values\n\t * @param separator - A separator we wish to use\n\t */\n\tpublic transform(values: Array<string>, separator?: string): string {\n\t\treturn values && values.join(separator);\n\t}\n}\n","import { Pipe, PipeTransform } from '@angular/core';\n\n@Pipe({\n\tname: 'limitTo',\n\tstandalone: true,\n})\nexport class LimitToPipe implements PipeTransform {\n\t/**\n\t * Limits an array to a specific value\n\t *\n\t * @param value - The provided array\n\t * @param limitedTo - The number to which we want to limit the array\n\t */\n\tpublic transform(value: any[], limitedTo: number): any[] {\n\t\t// Iben: If no value is provided or the value is not an array, we early exit\n\t\tif (!value || !Array.isArray(value)) {\n\t\t\treturn [];\n\t\t}\n\n\t\t// Iben: Slice the array based on the provided limit\n\t\treturn [...value].slice(0, limitedTo);\n\t}\n}\n","import { Pipe, PipeTransform } from '@angular/core';\n\n@Pipe({\n\tname: 'log',\n\tstandalone: true,\n})\nexport class LogPipe implements PipeTransform {\n\t/**\n\t * Logs the provided value to the console.\n\t *\n\t * @param value The value to log to the console.\n\t * @param text An optional textual value to print before the piped value.\n\t */\n\tpublic transform(value: any, text?: string): void {\n\t\ttext ? console.log(text, value) : console.log(value);\n\t}\n}\n","import { Pipe, PipeTransform } from '@angular/core';\n\n@Pipe({\n\tname: 'mergeArrays',\n\tstandalone: true,\n})\nexport class MergeArraysPipe implements PipeTransform {\n\t/**\n\t * The mergeArrays pipe will take a source array and concat it with all provided additional arrays.\n\t * Undefined or null values are filtered from the flattened result.\n\t *\n\t * @param source{any[]}\n\t * @param arrays{any[]}\n\t */\n\tpublic transform(source: any[] = [], ...arrays: any[][]): any[] {\n\t\t// Denis: If the source is not a valid array, fallback to an empty array.\n\t\tif (!Array.isArray(source)) {\n\t\t\treturn [];\n\t\t}\n\n\t\treturn (\n\t\t\tsource\n\t\t\t\t// Denis: Concat the source with the provided arguments, filter out the non-array values from the arguments.\n\t\t\t\t.concat(...arrays.filter((array: any[]) => Array.isArray(array)))\n\t\t\t\t// Denis: Filter undefined or null values from the flattened result.\n\t\t\t\t.filter((value: any) => typeof value !== 'undefined' && value !== null)\n\t\t);\n\t}\n}\n","import { InjectionToken } from '@angular/core';\n\nimport { NgxReplaceElementsConfiguration } from '../types';\n\n/** The configuration token for the NgxReplaceElementsPipe */\nexport const NgxReplaceElementsConfigurationToken =\n\tnew InjectionToken<NgxReplaceElementsConfiguration>('NgxReplaceElementsConfigurationToken');\n","import { Inject, Pipe, PipeTransform } from '@angular/core';\nimport { DomSanitizer, SafeHtml } from '@angular/platform-browser';\n\nimport { NgxReplaceElementsConfigurationToken } from '../../tokens';\nimport { NgxReplaceElementsConfiguration, NgxReplaceElementsItem } from '../../types';\n\n/**\n * A pipe that allows to replace text elements with a WebComponent\n */\n@Pipe({\n\tname: 'ngxReplaceElements',\n\tstandalone: true,\n})\nexport class NgxReplaceElementsPipe implements PipeTransform {\n\tconstructor(\n\t\t@Inject(NgxReplaceElementsConfigurationToken)\n\t\tprivate readonly configuration: NgxReplaceElementsConfiguration,\n\t\tprivate readonly sanitizer: DomSanitizer\n\t) {}\n\n\t/**\n\t * Replaces all matches of a specific selector with provided WebComponents\n\t *\n\t * @param value - The original string value\n\t * @param items - The items we wish to replace\n\t */\n\ttransform(value: string, items: NgxReplaceElementsItem[]): SafeHtml {\n\t\t// Iben: If the value isn't a string we early exit and warn the user\n\t\tif (typeof value !== 'string') {\n\t\t\tconsole.warn(\n\t\t\t\t'NgxReplaceElements: A non string-value was provided to the NgxReplaceElementsPipe'\n\t\t\t);\n\t\t\treturn '';\n\t\t}\n\n\t\t// Iben: If no items were provided to replace, we just return the value\n\t\tif (!items || items.length === 0) {\n\t\t\treturn value;\n\t\t}\n\n\t\t// Iben: set up a new instance of the DOMParser and parse the value as text/html.\n\t\t// This will return a Document which we can work with to find/replace elements.\n\t\tconst parser: DOMParser = new DOMParser();\n\t\tconst body: Document = parser.parseFromString(value, 'text/html');\n\n\t\t// Iben: Loop over all items we wish to replace\n\t\titems.forEach((item) => {\n\t\t\t// Iben: Get the selector and the element we want to replace the target with\n\t\t\tconst { selector, element, includeInnerText } = this.configuration[item.elementId];\n\n\t\t\t// Iben: Select the target\n\t\t\tconst target: HTMLElement = body.querySelector(selector.replace('{{id}}', item.id));\n\n\t\t\t// Iben: If no target was found, early exit\n\t\t\tif (!target) {\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\t// Iben: Create a new element within the Document based on the provided selector.\n\t\t\t// The selector can be any native or custom web component (not an Angular component).\n\t\t\t// Keep in mind that the element will need to have a lowercase input prop for the reference.\n\t\t\tconst replacement: HTMLElement = body.createElement(element);\n\n\t\t\t// Iben: If the item included data, we set these attributes\n\t\t\tif (item.data) {\n\t\t\t\tObject.entries(item.data).forEach(([key, value]) => {\n\t\t\t\t\treplacement.setAttribute(key, value);\n\t\t\t\t});\n\t\t\t}\n\n\t\t\t// Iben: Copy the innerText of the target element to the new element if needed.\n\t\t\tif (includeInnerText) {\n\t\t\t\treplacement.innerText = target.innerText;\n\t\t\t}\n\n\t\t\t// Iben: Replace the target with the new element within the Document.\n\t\t\ttarget.replaceWith(replacement);\n\t\t});\n\n\t\t// Iben: sanitize the document and mark it as trusted HTML before returning it to the template.\n\t\treturn this.sanitizer.bypassSecurityTrustHtml(body.documentElement.innerHTML);\n\t}\n}\n","import { Pipe, PipeTransform, SecurityContext } from '@angular/core';\nimport { DomSanitizer } from '@angular/platform-browser';\n\n@Pipe({\n\tname: 'safeHtml',\n\tstandalone: true,\n})\nexport class SafeHtmlPipe implements PipeTransform {\n\tconstructor(private readonly sanitized: DomSanitizer) {}\n\n\t/**\n\t * Maps a provided string value to a sanitized string\n\t *\n\t * @param value - A provided string with HTML items\n\t */\n\tpublic transform(value: string) {\n\t\t// Iben: Early exit in case the value is not a string\n\t\tif (!value || typeof value !== 'string') {\n\t\t\treturn '';\n\t\t}\n\n\t\t// Iben: Sanitize the string\n\t\treturn this.sanitized.sanitize(SecurityContext.HTML, value);\n\t}\n}\n","import { Pipe, PipeTransform } from '@angular/core';\n\n@Pipe({\n\tname: 'stripHtml',\n\tstandalone: true,\n})\nexport class StripHtmlPipe implements PipeTransform {\n\tpublic transform(value: string, replaceWith: string = ''): string {\n\t\treturn value\n\t\t\t? String(value)\n\t\t\t\t\t.replace(/(<[^>]+>)+/gm, replaceWith)\n\t\t\t\t\t.replace(/(&nbsp;)+/gm, replaceWith)\n\t\t\t\t\t.replace(/(\\r\\n|\\n|\\r)+/gm, replaceWith)\n\t\t\t: '';\n\t}\n}\n","import { Pipe, PipeTransform } from '@angular/core';\n\n@Pipe({\n\tname: 'toArray',\n\tstandalone: true,\n})\nexport class ToArrayPipe implements PipeTransform {\n\t/**\n\t * Checks if a given argument is an array, if not, it will wrap the argument in a new array.\n\t */\n\t// Denis: Intentional any interface. The argument can be any kind of value.\n\tpublic transform(value: any): any[] {\n\t\tif (typeof value === 'undefined' || value === null) {\n\t\t\treturn [];\n\t\t}\n\n\t\tif (Array.isArray(value)) {\n\t\t\treturn value;\n\t\t}\n\n\t\treturn [value];\n\t}\n}\n","import { Pipe, PipeTransform } from '@angular/core';\n\n/**\n * A pipe to pass a transformer function to. By using this setup, we can use functions without causing rerender issues\n */\n@Pipe({\n\tname: 'transform',\n\tstandalone: true,\n})\nexport class TransformPipe implements PipeTransform {\n\t/**\n\t * Transforms a value based on a provided transform function\n\t *\n\t * @param value - The provided value we wish to transform\n\t * @param transformer - A provided transform function\n\t */\n\tpublic transform<TransformerType = any>(value: any, transformer: Function): TransformerType {\n\t\t// Iben: If no transformer is passed, we return the original value\n\t\tif (!transformer) {\n\t\t\treturn value;\n\t\t}\n\n\t\t// Iben: Transform the value and return\n\t\treturn transformer(value);\n\t}\n}\n","import { Pipe, PipeTransform } from '@angular/core';\n\n@Pipe({\n\tname: 'truncateText',\n\tstandalone: true,\n})\nexport class TruncateTextPipe implements PipeTransform {\n\tpublic transform(value: string, limit = 75): string {\n\t\treturn value.length > limit ? value.substring(0, limit) + '...' : value;\n\t}\n}\n","import { Pipe, PipeTransform } from '@angular/core';\nimport { uniqBy } from 'lodash';\n\n@Pipe({\n\tname: 'uniqBy',\n\tstandalone: true,\n})\nexport class UniqByPipe implements PipeTransform {\n\tpublic transform(value: { [key: string]: any }[], key: string): any {\n\t\tif (!Array.isArray(value) || value.length === 0) {\n\t\t\treturn [];\n\t\t}\n\n\t\treturn uniqBy(value, key);\n\t}\n}\n","import { InjectionToken } from '@angular/core';\n\nimport { WithRouterLinksConfig } from '../../types';\n\nexport const WITH_ROUTER_LINKS_CONFIG = new InjectionToken<WithRouterLinksConfig>(\n\t'withRouterLinksConfig'\n);\n","import { Inject, Pipe, PipeTransform } from '@angular/core';\nimport { DomSanitizer } from '@angular/platform-browser';\n\nimport { WithRouterLinksConfig, LinkReference } from '../../types';\nimport { WITH_ROUTER_LINKS_CONFIG } from './with-router-links.config';\n\n/**\n * An additional pipe to convert anchors to router-links\