@studiohyperdrive/ngx-utils
Version:
A series of abstracts, utils, pipes and services for Angular applications.
1,112 lines (1,088 loc) • 69.4 kB
JavaScript
import * as i0 from '@angular/core';
import { EventEmitter, Directive, Input, Output, HostBinding, HostListener, inject, PLATFORM_ID, Injectable, Inject, Pipe, InjectionToken, SecurityContext } from '@angular/core';
import * as i1$2 from '@angular/router';
import { ActivatedRoute } from '@angular/router';
import { isPlatformBrowser, DOCUMENT } from '@angular/common';
import { BehaviorSubject, EMPTY, fromEvent, Subject, NEVER, ReplaySubject, filter, map, tap, takeUntil, take } from 'rxjs';
import * as i1 from '@studiohyperdrive/ngx-core';
import { isObject, uniqBy } from 'lodash';
import clean from 'obj-clean';
import { normalizeString } from '@studiohyperdrive/utils';
import * as i1$1 from '@angular/platform-browser';
class FocusClickDirective {
constructor() {
// Allow the button to ignore click events when set to true
this.disabled = false;
// Allow the function passed by the host to be executed
// when the emit() method gets called
/**
* This directive replaces the default `click` directive and allows the user to execute
* the `click` event by clicking the mouse **and** by using the `enter` key on focus.
*
* A tabindex of `0` gets added to the host.
*
* @memberof FocusClickDirective
*/
this.focusClick = new EventEmitter();
// Make every tag that uses this directive by default tabbable
this.tabIndex = 0;
}
// Add eventhandler to the click event
isClicked(event) {
if (!this.disabled) {
this.focusClick.emit(event);
}
}
// Add eventhandler to keydown event When enter is pressed and the event
// isn't blocked, execute the click function of the host
isEntered() {
if (!this.disabled) {
this.focusClick.emit();
}
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.0.3", ngImport: i0, type: FocusClickDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "19.0.3", type: FocusClickDirective, isStandalone: true, selector: "[focusClick]", inputs: { disabled: "disabled" }, outputs: { focusClick: "focusClick" }, host: { listeners: { "click": "isClicked($event)", "keydown.enter": "isEntered()" }, properties: { "attr.tabindex": "this.tabIndex" } }, ngImport: i0 }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.0.3", ngImport: i0, type: FocusClickDirective, decorators: [{
type: Directive,
args: [{
selector: '[focusClick]',
standalone: true,
}]
}], propDecorators: { disabled: [{
type: Input
}], focusClick: [{
type: Output
}], tabIndex: [{
type: HostBinding,
args: ['attr.tabindex']
}], isClicked: [{
type: HostListener,
args: ['click', ['$event']]
}], isEntered: [{
type: HostListener,
args: ['keydown.enter']
}] } });
const Directives = [FocusClickDirective];
const getQueryParams = () => {
return inject(ActivatedRoute).queryParams;
};
// @dynamic
/**
* @deprecated: This service has been deprecated in favor of the one in @studiohyperdrive/ngx-core
*/
class WindowService {
/* eslint-enable */
constructor(document,
// tslint:disable-next-line: ban-types
platformId) {
this.document = document;
this.platformId = platformId;
/* eslint-disable @typescript-eslint/member-ordering */
this.widthSubject$ = new BehaviorSubject(1200);
this.scrollingUpSubject$ = new BehaviorSubject(true);
this.currentScrollPositionSubject$ = new BehaviorSubject(0);
/**
* Observable to get the window-width, defaults to 1200 when no window is defined
*/
this.width$ = this.widthSubject$.asObservable();
/**
* Observable to track when the scroll has ended
*/
this.scrollingUp$ = this.scrollingUpSubject$.asObservable();
/**
* Observable of the current scroll position after the scroll has ended
*/
this.currentScrollPosition$ = this.currentScrollPositionSubject$.asObservable();
/**
* Current scroll position after the scroll has ended
*/
this.currentScrollPosition = 0;
if (this.isBrowser() && this.hasDocument()) {
this.window = this.document.defaultView;
this.document.addEventListener('scroll', this.handleContentScroll.bind(this));
this.widthSubject$.next(this.window.innerWidth);
this.window.addEventListener('resize', () => {
if (this.window.innerWidth && this.widthSubject$.getValue()) {
this.widthSubject$.next(this.window.innerWidth);
}
});
}
}
/**
* Scrolls to the provided position of the page
*
* @param offset - Offset to which we want to scroll, scrolls to top when no offset is provided
*/
scrollTo(offset = 0) {
if (!this.window) {
return;
}
this.window.scrollTo(0, offset);
}
/**
* Returns whether there is a document present
*/
hasDocument() {
return !!this.document;
}
/**
* Returns whether the current platform is a browser
*/
isBrowser() {
return isPlatformBrowser(this.platformId);
}
/**
* Run a provided function only when we're in the browser and not in a server side rendered application
*
* @param action - Function we want to run in the browser
*/
runInBrowser(action) {
if (this.isBrowser) {
action({ browserWindow: this.window, browserDocument: this.document });
}
else {
console.warn('Browser depended function has not run.');
}
}
handleContentScroll() {
if (window.pageYOffset > this.currentScrollPosition) {
this.scrollingUpSubject$.next(false);
}
else {
this.scrollingUpSubject$.next(true);
}
this.currentScrollPosition = window.pageYOffset < 0 ? 0 : window.pageYOffset;
this.currentScrollPositionSubject$.next(this.currentScrollPosition);
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.0.3", ngImport: i0, type: WindowService, deps: [{ token: DOCUMENT }, { token: PLATFORM_ID }], target: i0.ɵɵFactoryTarget.Injectable }); }
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.0.3", ngImport: i0, type: WindowService, providedIn: 'root' }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.0.3", ngImport: i0, type: WindowService, decorators: [{
type: Injectable,
args: [{
providedIn: 'root',
}]
}], ctorParameters: () => [{ type: Document, decorators: [{
type: Inject,
args: [DOCUMENT]
}] }, { type: undefined, decorators: [{
type: Inject,
args: [PLATFORM_ID]
}] }] });
/**
* A service that wraps the BroadCastChannel API and provides an Observable based implementation to the channel messages.
*
* For more information:
* https://developer.mozilla.org/en-US/docs/Web/API/BroadcastChannel
*/
class NgxBroadcastChannelService {
constructor(windowService) {
this.windowService = windowService;
/**
* A record holding all the broadcast channels
*/
this.broadcastChannel = {};
}
/**
* initChannel
*
* The initChannel method initializes a new BroadcastChannel instance.
*
* @param args{ConstructorParameters<typeof BroadcastChannel>} - The arguments to pass to the BroadcastChannel constructor.
*/
initChannel(...args) {
// Iben: Only run when in browser
this.windowService.runInBrowser(() => {
const [channelName] = args;
if (!channelName) {
console.error('NgxUtils: There was an attempt to initialize a BroadcastChannel without providing a name.');
return;
}
if (!this.broadcastChannel[channelName]) {
this.broadcastChannel[channelName] = new BroadcastChannel(...args);
}
});
}
/**
* closeChannel
*
* The closeChannel method closes a selected BroadcastChannel instance.
*
* @param channelName{string} - The name of the Broadcast Channel.
*/
closeChannel(channelName) {
if (!channelName || !this.broadcastChannel[channelName]) {
return;
}
this.broadcastChannel[channelName].close();
delete this.broadcastChannel[channelName];
}
/**
* postMessage
*
* The postMessage method sends a message to a selected BroadcastChannel instance.
*
* @param channelName{string} - The name of the Broadcast Channel.
* @param message{any} - The payload to send through the channel.
*/
postMessage(channelName, message) {
if (!channelName || !this.broadcastChannel[channelName]) {
console.error('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:', message);
return;
}
this.broadcastChannel[channelName].postMessage(message);
}
/**
* selectChannelMessages
*
* The selectChannelMessages method subscribes to the `message` (bc.onmessage) event of a selected BroadcastChannel instance.
*
* @param channelName{string} - The name of the Broadcast Channel.
* @returns Observable<MessageEvent> - The message event of the channel wrapped in an observable.
*/
selectChannelMessages(channelName) {
if (!channelName || !this.broadcastChannel[channelName]) {
console.error("NgxUtils: There was an attempt to select a BroadcastChannel's messages without providing a name or the selected channel does not exist.");
return EMPTY;
}
return fromEvent(this.broadcastChannel[channelName], 'message');
}
/**
* selectChannelMessageErrors
*
* The selectChannelMessageErrors method subscribes to the `messageerror` (bc.onmessageerror) event of a selected BroadcastChannel instance.
*
* @param channelName{string} - The name of the Broadcast Channel.
* @returns Observable<MessageEvent> - The messageerror event of the channel wrapped in an observable.
*/
selectChannelMessageErrors(channelName) {
if (!channelName || !this.broadcastChannel[channelName]) {
console.error("NgxUtils: There was an attempt to select a BroadcastChannel's message errors without providing a name or the selected channel does not exist.");
return EMPTY;
}
return fromEvent(this.broadcastChannel[channelName], 'messageerror');
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.0.3", ngImport: i0, type: NgxBroadcastChannelService, deps: [{ token: i1.NgxWindowService }], target: i0.ɵɵFactoryTarget.Injectable }); }
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.0.3", ngImport: i0, type: NgxBroadcastChannelService, providedIn: 'root' }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.0.3", ngImport: i0, type: NgxBroadcastChannelService, decorators: [{
type: Injectable,
args: [{
providedIn: 'root',
}]
}], ctorParameters: () => [{ type: i1.NgxWindowService }] });
class SubscriptionService {
constructor() {
this.destroyed$ = new Subject();
}
ngOnDestroy() {
this.destroyed$.next(true);
this.destroyed$.complete();
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.0.3", ngImport: i0, type: SubscriptionService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.0.3", ngImport: i0, type: SubscriptionService }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.0.3", ngImport: i0, type: SubscriptionService, decorators: [{
type: Injectable
}] });
/**
* A service that provides a SSR-proof Observable based approach to the session- and localStorage.
*/
class NgxStorageService {
constructor(windowService) {
this.windowService = windowService;
/**
* A record to hold the properties in the sessionStorage
*/
this.sessionStorageRecord = {};
/**
* A record to hold the properties in the localStorage
*/
this.localStorageRecord = {};
/**
* A subject to hold the events of the storage
*/
this.storageEventSubject = new Subject();
/**
* An observable that emits whenever the session- or the localStorage was updated
*/
this.storageEvents$ = this.storageEventSubject.asObservable();
// Iben: Get the initial values of the session and the local storage
windowService.runInBrowser(() => {
this.setupStorage(sessionStorage, this.sessionStorageRecord);
this.setupStorage(localStorage, this.localStorageRecord);
});
}
/**
* A localStorage implementation using observables
*/
get localStorage() {
return {
getItem: (key) => this.getItem(key, localStorage),
getItemObservable: (key) => this.getItemObservable(key, this.localStorageRecord),
removeItem: (key) => this.removeItem(key, localStorage, this.localStorageRecord, 'local'),
setItem: (key, item) => this.setItem(key, item, localStorage, this.localStorageRecord, 'local'),
clear: () => this.clearStorage(localStorage, this.localStorageRecord, 'local'),
};
}
/**
* A sessionStorage implementation using observables
*/
get sessionStorage() {
return {
getItem: (key) => this.getItem(key, sessionStorage),
getItemObservable: (key) => this.getItemObservable(key, this.sessionStorageRecord),
removeItem: (key) => this.removeItem(key, sessionStorage, this.sessionStorageRecord, 'session'),
setItem: (key, item) => this.setItem(key, item, sessionStorage, this.sessionStorageRecord, 'session'),
clear: () => this.clearStorage(sessionStorage, this.sessionStorageRecord, 'session'),
};
}
getItem(key, storage) {
return this.parseValue(storage.getItem(key));
}
/**
* Returns an observable version of the storage value
*
* @param key - The key of the storage value
* @param record - The storage record
*/
getItemObservable(key, record) {
// Iben: Return NEVER when not in browser
if (!this.windowService.isBrowser()) {
return NEVER;
}
// Iben: If the subject already exists, we return the observable
if (record[key]) {
return record[key].asObservable();
}
// Iben: If no subject exits, we create a new one
record[key] = new BehaviorSubject(undefined);
// Iben: Return the observable
return this.getItemObservable(key, record);
}
/**
* Sets an item in the storage
*
* @param key - The key of the item
* @param item - The item in the storage
* @param storage - The storage in which we want to save the item
* @param record - The corresponding storage record
*/
setItem(key, item, storage, record, type) {
// Iben: Early exit when we're in the browser
if (!this.windowService.isBrowser()) {
return undefined;
}
// Iben: Check if there's already a subject for this item. If not, we create one
let subject = record[key];
if (!subject) {
subject = new BehaviorSubject(undefined);
storage[key] = subject;
}
// Iben: Store the current value of the subject
const oldValue = subject.getValue();
// Iben: Set the item in the storage
storage.setItem(key, typeof item === 'string' ? item : JSON.stringify(item));
// Iben: Update the subject in the record
subject.next(item);
// Iben: Create the storage event
const event = {
key,
newValue: item,
oldValue,
storage: type,
type: 'set',
};
// Iben: Emit the storage event
this.storageEventSubject.next(event);
// Iben: Return the storage event
return event;
}
/**
* Remove an item from the storage and emit a remove event
*
* @param key - The key of the item
* @param storage - The storage we wish to remove the item from
* @param record - The record with the subject
* @param type - The type of storage
*/
removeItem(key, storage, record, type) {
// Iben: Early exit when we're not in the browser
if (!this.windowService.isBrowser()) {
return undefined;
}
// Iben: Get the old item
const oldValue = this.parseValue(storage.getItem(key));
// Iben: Remove the item from the storage
storage.removeItem(key);
// Iben Update the subject if it exists
record[key]?.next(undefined);
// Iben: Create the event and return and emit it
const event = {
oldValue,
storage: type,
key,
type: 'remove',
};
this.storageEventSubject.next(event);
return event;
}
/**
* Clears the storage, completes all subjects and emits a clear event
*
* @param storage - The storage we wish to clear
* @param record - The record with the subjects
* @param type - The type of storage
*/
clearStorage(storage, record, type) {
// Iben: Early exit when we're not in the browser
if (!this.windowService.isBrowser()) {
return undefined;
}
// Iben: Clear the storage
storage.clear();
// Iben: Clear the record and complete all subjects
Object.entries(record).forEach(([key, subject]) => {
subject.next(undefined);
subject.complete();
record[key] = undefined;
});
// Iben: Create and emit event
const event = {
type: 'clear',
storage: type,
};
this.storageEventSubject.next(event);
return event;
}
/**
* Grabs the existing storage and updates the record
*
* @private
* @param {Storage} storage - The current state of the storage
* @param {NgxStorageRecord} record
* @memberof NgxStorageService
*/
setupStorage(storage, record) {
Object.entries(storage).forEach(([key, value]) => {
record[key] = new BehaviorSubject(this.parseValue(value));
});
}
/**
* Parses a string value from the storage to an actual value
*
* @param value - The provided string value
*/
parseValue(value) {
// Iben: If the value does not exist, return the value
if (!value) {
return value;
}
// Iben: If the value is either true or false, return a boolean version of the value
if (value === 'true' || value === 'false') {
return value === 'true';
}
// Iben: If the value is a number, return the parsed number
if (value.match(/^[0-9]*[,.]{0,1}[0-9]*$/)) {
return Number(value);
}
// Iben: If the value is an object, return the parsed object
if (value.match(/{(.*:.*[,]{0,1})*}/)) {
return JSON.parse(value);
}
// Iben: Return the string value as is
return value;
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.0.3", ngImport: i0, type: NgxStorageService, deps: [{ token: i1.NgxWindowService }], target: i0.ɵɵFactoryTarget.Injectable }); }
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.0.3", ngImport: i0, type: NgxStorageService, providedIn: 'root' }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.0.3", ngImport: i0, type: NgxStorageService, decorators: [{
type: Injectable,
args: [{ providedIn: 'root' }]
}], ctorParameters: () => [{ type: i1.NgxWindowService }] });
/**
* A service that can be used to track media queries and their changes. It exposes a method
* to register media queries, which takes an array of tuples with the id of the media query
* and the query itself. The service will then emit the id of the media query that has
* changed when subscribed to the `getMatchingQuery$` method.
*/
class NgxMediaQueryService {
constructor(windowService) {
this.windowService = windowService;
/**
* A map of media queries that are registered with the service.
*/
this.queryListMap = new Map();
/**
* A map of the registered media queries with their id.
*/
this.queryIdMap = new Map();
/*
* A map of listeners that are registered with the service.
* They are saved to be able to remove them when the service is destroyed.
*/
this.mediaQueryListenerMap = new Map();
/**
* A subject that emits the id of the media query that has changed.
*/
this.queryChangedSubject = new ReplaySubject();
}
/**
* Register a list of media queries that need to be tracked by the service.
*
* @param queries - A list of media queries that should be registered with the service.
*/
registerMediaQueries(...queries) {
this.windowService.runInBrowser(({ browserWindow }) => {
for (const [id, query] of queries) {
// Wouter: Warn if the id has already been registered.
if (this.queryIdMap.get(id)) {
return console.warn(`NgxMediaQueryService: Media query with id '${id}' already exists and is defined by '${this.queryIdMap.get(id)}'`);
}
// Wouter: If the query has already been registered, throw an error to prevent duplicate subscriptions
if ([...this.queryIdMap].some(([_, value]) => value === query)) {
const duplicateQuery = [...this.queryIdMap].find(([_, value]) => value === query);
throw new Error(`NgxMediaQueryService: Query of ['${id}', ${query}] already exists and is defined by ['${duplicateQuery[0]}', ${duplicateQuery[1]}]`);
}
// Wouter: save the id and query
this.queryIdMap.set(id, query);
// Wouter: For each query, create a MediaQueryList object
const matchedQuery = browserWindow.matchMedia(query);
// Wouter: Save the query
this.queryListMap.set(id, matchedQuery);
// Wouter: Emit the id of the query that has changed
this.queryChangedSubject.next(id);
// Wouter: Create a listener for the query. This is done separately to be
// able to remove the listener when the service is destroyed
const listener = (queryChangedEvent) => {
this.queryListMap.set(id, queryChangedEvent.currentTarget);
// Wouter: Emit the id of the query that has changed
this.queryChangedSubject.next(id);
};
// Wouter: Register the listener to the query
matchedQuery.addEventListener('change', listener);
// Wouter: Save the listener
this.mediaQueryListenerMap.set(id, listener);
}
});
}
/**
* Pass the id of the query whose changes need to be listened to.
*
* @param id - The id of the media query that should be checked.
* @returns An observable that emits a boolean value whenever the requested media query changes.
*/
getMatchingQuery$(id) {
// Wouter: Throw an error if the query has not been registered
if (!this.queryIdMap.has(id)) {
throw new Error(`NgxMediaQueryService: No media query with id '${id}' has been registered. Please register the media query first using the 'registerMediaQueries' method.`);
}
return this.queryChangedSubject.asObservable().pipe(
// Wouter: Filter the query that has changed.
// This will make sure only the [id] streams are triggered.
filter((queryId) => queryId === id), map(() => this.queryListMap.get(id).matches));
}
/**
* Unregister all media query subscriptions from the service.
*/
ngOnDestroy() {
this.windowService.runInBrowser(() => {
// Wouter: Remove all eventListeners
for (const [id, query] of this.queryListMap) {
query.removeEventListener('change', this.mediaQueryListenerMap.get(id));
}
// Wouter: Complete subscriptions
this.queryChangedSubject.next(null);
this.queryChangedSubject.complete();
// Wouter: Clear maps
this.queryListMap.clear();
this.mediaQueryListenerMap.clear();
});
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.0.3", ngImport: i0, type: NgxMediaQueryService, deps: [{ token: i1.NgxWindowService }], target: i0.ɵɵFactoryTarget.Injectable }); }
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.0.3", ngImport: i0, type: NgxMediaQueryService, providedIn: 'root' }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.0.3", ngImport: i0, type: NgxMediaQueryService, decorators: [{
type: Injectable,
args: [{ providedIn: 'root' }]
}], ctorParameters: () => [{ type: i1.NgxWindowService }] });
/**
* A pipe that will search an array to see if on of its objects contains specific values on provided keys.
*
* Usage:
* value | arrayContainsOne : ['prop1', 'prop2', ...]
*
* Examples:
* {{
* [
* { title: 'This is the title', description: 'This is the description' },
* { title: 'This is the title' }
* ] | arrayContainsOne: ['description']
* }}
* Output: true
*/
class ArrayContainsOnePipe {
transform(values, checkProps = []) {
if (!Array.isArray(values) ||
values.length === 0 ||
!Array.isArray(checkProps) ||
checkProps.length === 0) {
return false;
}
return values.some((item) => checkProps.some((key) => item[key] !== null && item[key] !== undefined));
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.0.3", ngImport: i0, type: ArrayContainsOnePipe, deps: [], target: i0.ɵɵFactoryTarget.Pipe }); }
static { this.ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "19.0.3", ngImport: i0, type: ArrayContainsOnePipe, isStandalone: true, name: "arrayContainsOne" }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.0.3", ngImport: i0, type: ArrayContainsOnePipe, decorators: [{
type: Pipe,
args: [{
name: 'arrayContainsOne',
standalone: true,
}]
}] });
class BtwPipe {
/**
* Converts a BTW number to the correct format
*
* @param value - The value we wish to convert
*/
transform(value) {
if (!value) {
// Denis: if the value is falsy, return it without transform.
return value;
}
const addCharAtIndex = (original, char, index) => {
return original.slice(0, index) + char + original.slice(index);
};
// Iben: Convert to string if it's a number
value = value.toString();
if (value.replace(/\./g, '').length === 9) {
value = '0' + value;
}
// Iben: Format: xxxx.xxx.xxx
if (value.charAt(4) !== '.') {
value = addCharAtIndex(value, '.', 4);
}
if (value.charAt(8) !== '.') {
value = addCharAtIndex(value, '.', 8);
}
return value;
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.0.3", ngImport: i0, type: BtwPipe, deps: [], target: i0.ɵɵFactoryTarget.Pipe }); }
static { this.ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "19.0.3", ngImport: i0, type: BtwPipe, isStandalone: true, name: "btw" }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.0.3", ngImport: i0, type: BtwPipe, decorators: [{
type: Pipe,
args: [{
name: 'btw',
standalone: true,
}]
}] });
class CleanArrayPipe {
/**
* Removes all falsy values from the provided array.
*
* @param value The values that need to be stripped of falsy values.
* @param exceptions The falsy values that may be included in the filtered array.
* @returns The filtered array.
*/
transform(value, exceptions = []) {
if (!exceptions.length) {
return value.filter(Boolean);
}
return value.filter((entry) => Boolean(entry) || exceptions.includes(entry));
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.0.3", ngImport: i0, type: CleanArrayPipe, deps: [], target: i0.ɵɵFactoryTarget.Pipe }); }
static { this.ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "19.0.3", ngImport: i0, type: CleanArrayPipe, isStandalone: true, name: "cleanArray" }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.0.3", ngImport: i0, type: CleanArrayPipe, decorators: [{
type: Pipe,
args: [{
name: 'cleanArray',
standalone: true,
}]
}] });
class EntriesPipe {
/**
* Transforms a record into a [key, value] array
*
* @param value - The provided record
*/
transform(value) {
// Iben: If there's no value or the value is not an object, we return an empty array to prevent frontend breaking
if (!value || !isObject(value) || Array.isArray(value)) {
return [];
}
// Iben: Transform the record to a [key,value] array
return Array.from(Object.entries(value));
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.0.3", ngImport: i0, type: EntriesPipe, deps: [], target: i0.ɵɵFactoryTarget.Pipe }); }
static { this.ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "19.0.3", ngImport: i0, type: EntriesPipe, isStandalone: true, name: "entries" }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.0.3", ngImport: i0, type: EntriesPipe, decorators: [{
type: Pipe,
args: [{
name: 'entries',
standalone: true,
}]
}] });
class HasObserversPipe {
transform(output) {
return output && output.observers.length > 0;
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.0.3", ngImport: i0, type: HasObserversPipe, deps: [], target: i0.ɵɵFactoryTarget.Pipe }); }
static { this.ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "19.0.3", ngImport: i0, type: HasObserversPipe, isStandalone: true, name: "hasObservers" }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.0.3", ngImport: i0, type: HasObserversPipe, decorators: [{
type: Pipe,
args: [{
name: 'hasObservers',
standalone: true,
}]
}] });
class HasOwnProperty {
/**
* Checks whether the specified property exists within the given object.
*
* @param {unknown} object - The object to check for the presence of the property.
* @param {string} prop - The property name to check for within the object.
* @return {boolean} - Returns `true` if the property exists in the object, `false` otherwise.
*/
transform(object, prop) {
// Use Object.prototype.hasOwnProperty to check if the property exists in the object
const hasOwnProperty = Object.prototype.hasOwnProperty;
// Check if the object is not null or undefined, and if it has the specified property
return object != null && hasOwnProperty.call(object, prop);
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.0.3", ngImport: i0, type: HasOwnProperty, deps: [], target: i0.ɵɵFactoryTarget.Pipe }); }
static { this.ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "19.0.3", ngImport: i0, type: HasOwnProperty, isStandalone: true, name: "hasOwnProperty" }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.0.3", ngImport: i0, type: HasOwnProperty, decorators: [{
type: Pipe,
args: [{
name: 'hasOwnProperty',
standalone: true,
}]
}] });
class HasValuesPipe {
/**
* Checks whether an object has values
*
* @param value - The provided value
*/
transform(value) {
// Iben: If the value is not an object, return valse
if (!value || !isObject(value) || Array.isArray(value)) {
return false;
}
// Iben: Return when the object has values
return Object.keys(clean(value)).length > 0;
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.0.3", ngImport: i0, type: HasValuesPipe, deps: [], target: i0.ɵɵFactoryTarget.Pipe }); }
static { this.ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "19.0.3", ngImport: i0, type: HasValuesPipe, isStandalone: true, name: "hasValues" }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.0.3", ngImport: i0, type: HasValuesPipe, decorators: [{
type: Pipe,
args: [{
name: 'hasValues',
standalone: true,
}]
}] });
class NgxHighlightPipe {
/**
* Highlights the provided substring of a text with a chosen dom element
*
* @param value - The full text with the part we wish to highlight
* @param highlight - The part of the text we wish to highlight
* @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
* @param config.normalized - Default = true
* @param config.caseInsensitive - Default = true
* @param config.splitTextToHighlight - Default = false
* @param config.someOrEveryMatch - Default = 'every'
* @param config.tag - Default = 'mark'
* @param config.highlightClass - Default = 'ngx-mark-highlight'
*/
transform(value, highlight, config) {
// Femke: Setup configuration or defaults
const normalized = config?.normalized ?? true;
const caseInsensitive = config?.caseInsensitive ?? true;
const splitTextToHighlight = config?.splitTextToHighlight ?? false;
const someOrEveryMatch = config?.someOrEveryMatch || 'every';
const tag = config?.tag || 'mark';
const highlightClass = config?.highlightClass ?? 'ngx-mark-highlight';
// Femke: Early exit if there's no value/highlight or the value/highlight is not a string
if (!value || !highlight || typeof value !== 'string' || typeof highlight !== 'string') {
return value;
}
// Femke: determine which value to use (normalized or not => é â ö etc will be replaced with e a o)
const usableValue = normalized ? normalizeString(value) : value;
let usableHighlight = (normalized ? normalizeString(highlight) : highlight)
// Femke: escape all regex characters
.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
if (splitTextToHighlight) {
usableHighlight = usableHighlight
// Femke: replace all multiple spaces next to each other to a single space
.replace(/ +/gi, ' ')
// 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'
.replace(/ /gi, '|');
}
// Femke: remove all multiple pipes at the start
usableHighlight = usableHighlight.replace(/^\|+/gi, '');
// Femke: remember on what indices we updated in our search text
let changedIndices = [];
usableValue.split('').forEach((char, index) => {
if (value[index] !== char) {
changedIndices.push({ original: index, updated: index });
}
});
let regexFlags = '';
// Femke: Every match should be highlighted, so the regex should apply global
if (someOrEveryMatch === 'every') {
regexFlags = 'g';
}
// Femke: Regex should be case-insensitive
if (caseInsensitive) {
regexFlags += 'i';
}
const regEx = new RegExp(usableHighlight, regexFlags);
const usableClass = highlightClass ? ` class="${highlightClass}"` : '';
// Femke: Use a custom replacer so we can update our marked indices in case we found a match with our regex
let replacedResult = usableValue.replace(regEx, (match, index) => {
if (!match) {
return '';
}
const endIndex = index + match.length;
// Femke: we are using the original text here to make sure we have the original accent characters within the highlight
const result = `<${tag}${usableClass}>${value.substring(index, index + match.length)}</${tag}>`;
// Femke: filter out all changed indices that lay withing the range of our current match
changedIndices = changedIndices.filter((item) => !(index <= item.original && item.original < endIndex));
// Femke: update all found indices if they lay after the current replacement string
// Femke: we are however keeping the original position to know with what we need to replace the character later on
changedIndices = changedIndices.map((changedIndex) => {
if (index >= changedIndex.original) {
return changedIndex;
}
else {
// Femke: take the current updated position (in case of multiple hits before this index
// Femke: add the length of the replacement string but subtract the length of the match since that length was there already
return {
...changedIndex,
updated: changedIndex.updated + result.length - match.length,
};
}
});
return result;
});
// 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
changedIndices.forEach((item) => {
replacedResult =
replacedResult.substring(0, item.updated) +
value.charAt(item.original) +
replacedResult.substring(item.updated + 1);
});
return replacedResult;
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.0.3", ngImport: i0, type: NgxHighlightPipe, deps: [], target: i0.ɵɵFactoryTarget.Pipe }); }
static { this.ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "19.0.3", ngImport: i0, type: NgxHighlightPipe, isStandalone: true, name: "highlight" }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.0.3", ngImport: i0, type: NgxHighlightPipe, decorators: [{
type: Pipe,
args: [{
name: 'highlight',
}]
}] });
class IbanPipe {
transform(value = '') {
value = value.replace(/\s/g, ''); // replace all spaces
let reformat = value.replace(/(.{4})/g, function (match) {
return match + ' '; // reformat into groups of 4 succeeded with a space
});
reformat = reformat.trim(); // remove trailing space
return reformat;
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.0.3", ngImport: i0, type: IbanPipe, deps: [], target: i0.ɵɵFactoryTarget.Pipe }); }
static { this.ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "19.0.3", ngImport: i0, type: IbanPipe, isStandalone: true, name: "IBAN" }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.0.3", ngImport: i0, type: IbanPipe, decorators: [{
type: Pipe,
args: [{
name: 'IBAN',
standalone: true,
}]
}] });
class IsNotEmptyPipe {
/**
* Checks if a given argument is an object or array and if it is empty.
*
* @param value - can be any value.
* @param checkProps - which props to check.
*/
transform(value, checkProps = []) {
// Denis: check for array first, because "typeof Array" will return "object"
if (Array.isArray(value) && value.length > 0) {
return true;
}
// Denis: check for null values second, because "typeof null" will return "object"
if (value === null) {
return false;
}
// Denis: if the object is not empty, and checkProps is provided, check those props.
if (typeof value === 'object' && checkProps.length > 0) {
const propsWithValues = checkProps.filter((key) => typeof value[key] !== 'undefined' && value[key] !== null);
return propsWithValues.length === checkProps.length;
}
// Denis: check for empty objects.
if (typeof value === 'object' && Object.keys(value).length > 0) {
return true;
}
return false;
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.0.3", ngImport: i0, type: IsNotEmptyPipe, deps: [], target: i0.ɵɵFactoryTarget.Pipe }); }
static { this.ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "19.0.3", ngImport: i0, type: IsNotEmptyPipe, isStandalone: true, name: "isNotEmpty" }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.0.3", ngImport: i0, type: IsNotEmptyPipe, decorators: [{
type: Pipe,
args: [{
name: 'isNotEmpty',
standalone: true,
}]
}] });
// Iben: This implementation is from https://github.com/nglrx/pipes/blob/master/packages/pipes/src/lib/array/join/join.pipe.ts
// The package is no longer supported and is not compatible with more recent versions of Angular
class JoinPipe {
/**
* Transforms an array to a joined string
*
* @param values - An array of values
* @param separator - A separator we wish to use
*/
transform(values, separator) {
return values && values.join(separator);
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.0.3", ngImport: i0, type: JoinPipe, deps: [], target: i0.ɵɵFactoryTarget.Pipe }); }
static { this.ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "19.0.3", ngImport: i0, type: JoinPipe, isStandalone: true, name: "join" }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.0.3", ngImport: i0, type: JoinPipe, decorators: [{
type: Pipe,
args: [{
name: 'join',
standalone: true,
}]
}] });
class LimitToPipe {
/**
* Limits an array to a specific value
*
* @param value - The provided array
* @param limitedTo - The number to which we want to limit the array
*/
transform(value, limitedTo) {
// Iben: If no value is provided or the value is not an array, we early exit
if (!value || !Array.isArray(value)) {
return [];
}
// Iben: Slice the array based on the provided limit
return [...value].slice(0, limitedTo);
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.0.3", ngImport: i0, type: LimitToPipe, deps: [], target: i0.ɵɵFactoryTarget.Pipe }); }
static { this.ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "19.0.3", ngImport: i0, type: LimitToPipe, isStandalone: true, name: "limitTo" }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.0.3", ngImport: i0, type: LimitToPipe, decorators: [{
type: Pipe,
args: [{
name: 'limitTo',
standalone: true,
}]
}] });
class LogPipe {
/**
* Logs the provided value to the console.
*
* @param value The value to log to the console.
* @param text An optional textual value to print before the piped value.
*/
transform(value, text) {
text ? console.log(text, value) : console.log(value);
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.0.3", ngImport: i0, type: LogPipe, deps: [], target: i0.ɵɵFactoryTarget.Pipe }); }
static { this.ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "19.0.3", ngImport: i0, type: LogPipe, isStandalone: true, name: "log" }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.0.3", ngImport: i0, type: LogPipe, decorators: [{
type: Pipe,
args: [{
name: 'log',
standalone: true,
}]
}] });
class MergeArraysPipe {
/**
* The mergeArrays pipe will take a source array and concat it with all provided additional arrays.
* Undefined or null values are filtered from the flattened result.
*
* @param source{any[]}
* @param arrays{any[]}
*/
transform(source = [], ...arrays) {
// Denis: If the source is not a valid array, fallback to an empty array.
if (!Array.isArray(source)) {
return [];
}
return (source
// Denis: Concat the source with the provided arguments, filter out the non-array values from the arguments.
.concat(...arrays.filter((array) => Array.isArray(array)))
// Denis: Filter undefined or null values from the flattened result.
.filter((value) => typeof value !== 'undefined' && value !== null));
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.0.3", ngImport: i0, type: MergeArraysPipe, deps: [], target: i0.ɵɵFactoryTarget.Pipe }); }
static { this.ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "19.0.3", ngImport: i0, type: MergeArraysPipe, isStandalone: true, name: "mergeArrays" }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.0.3", ngImport: i0, type: MergeArraysPipe, decorators: [{
type: Pipe,
args: [{
name: 'mergeArrays',
standalone: true,
}]
}] });
/** The configuration token for the NgxReplaceElementsPipe */
const NgxReplaceElementsConfigurationToken = new InjectionToken('NgxReplaceElementsConfigurationToken');
/**
* A pipe that allows to replace text elements with a WebComponent
*/
class NgxReplaceElementsPipe {
constructor(configuration, sanitizer) {
this.configuration = configuration;
this.sanitizer = sanitizer;
}
/**
* Replaces all matches of a specific selector with provided WebComponents
*
* @param value - The original string value
* @param items - The items we wish to replace
*/
transform(value, items) {
// Iben: If the value isn't a string we early exit and warn the user
if (typeof value !== 'string') {
console.warn('NgxReplaceElements: A non string-value was provided to the NgxReplaceElementsPipe');
return '';
}
// Iben: If no items were provided to replace, we just return the value
if (!items || items.length === 0) {
return value;
}
// Iben: set up a new instance of the DOMParser and parse the value as text/html.
// This will return a Document which we can work with to find/replace elements.
const parser = new DOMParser();
const body = parser.parseFromString(value, 'text/html');
// Iben: Loop over all items we wish to replace
items.forEach((item) => {
// Iben: Get the selector and the element we want to replace the target with
const { selector, element, includeInnerText } = this.configuration[item.elementId];
// Iben: Select the target
const target = body.querySelector(selector.replace('{{id}}', item.id));
// Iben: If no target was found, early exit
if (!target) {
return;
}
// Iben: Create a new element within the Do