UNPKG

wacom

Version:

Module which has common services and components which can be used on all projects.

1 lines 291 kB
{"version":3,"file":"wacom.mjs","sources":["../../../projects/wacom/src/interfaces/alert.interface.ts","../../../projects/wacom/src/interfaces/config.interface.ts","../../../projects/wacom/src/interfaces/modal.interface.ts","../../../projects/wacom/src/services/meta.service.ts","../../../projects/wacom/src/guard/meta.guard.ts","../../../projects/wacom/src/components/alert/alert/alert.component.ts","../../../projects/wacom/src/components/alert/alert/alert.component.html","../../../projects/wacom/src/components/base.component.ts","../../../projects/wacom/src/components/alert/wrapper/wrapper.component.ts","../../../projects/wacom/src/components/alert/wrapper/wrapper.component.html","../../../projects/wacom/src/services/dom.service.ts","../../../projects/wacom/src/services/alert.service.ts","../../../projects/wacom/src/services/core.service.ts","../../../projects/wacom/src/components/crud.component.ts","../../../projects/wacom/src/components/loader/loader.component.ts","../../../projects/wacom/src/components/loader/loader.component.html","../../../projects/wacom/src/components/modal/modal.component.ts","../../../projects/wacom/src/components/modal/modal.component.html","../../../projects/wacom/src/directives/click-outside.directive.ts","../../../projects/wacom/src/pipes/arr.pipe.ts","../../../projects/wacom/src/pipes/mongodate.pipe.ts","../../../projects/wacom/src/pipes/number.pipe.ts","../../../projects/wacom/src/pipes/pagination.pipe.ts","../../../projects/wacom/src/pipes/safe.pipe.ts","../../../projects/wacom/src/pipes/search.pipe.ts","../../../projects/wacom/src/pipes/splice.pipe.ts","../../../projects/wacom/src/pipes/split.pipe.ts","../../../projects/wacom/src/services/base.service.ts","../../../projects/wacom/src/interfaces/http.interface.ts","../../../projects/wacom/src/services/http.service.ts","../../../projects/wacom/src/services/store.service.ts","../../../projects/wacom/src/services/crud.service.ts","../../../projects/wacom/src/components/files/files.component.ts","../../../projects/wacom/src/components/files/files.component.html","../../../projects/wacom/src/services/file.service.ts","../../../projects/wacom/src/interfaces/loader.interface.ts","../../../projects/wacom/src/services/loader.service.ts","../../../projects/wacom/src/services/modal.service.ts","../../../projects/wacom/src/services/rtc.service.ts","../../../projects/wacom/src/services/socket.service.ts","../../../projects/wacom/src/services/time.service.ts","../../../projects/wacom/src/services/util.service.ts","../../../projects/wacom/src/theme.ts","../../../projects/wacom/src/provide-wacom.ts","../../../projects/wacom/src/wacom.module.ts","../../../projects/wacom/public-api.ts","../../../projects/wacom/wacom.ts"],"sourcesContent":["import { Signal, Type } from '@angular/core';\r\n\r\n/**\r\n * Possible alert variants that control styling and default icons.\r\n */\r\nexport const ALERT_TYPES = ['info', 'error', 'success', 'warning', 'question'];\r\nexport type AlertType = (typeof ALERT_TYPES)[number];\r\n\r\n/**\r\n * Possible screen positions where alerts can be placed.\r\n */\r\nexport const ALERT_POSITIONS = [\r\n\t'topLeft',\r\n\t'top',\r\n\t'topRight',\r\n\t'left',\r\n\t'center',\r\n\t'right',\r\n\t'bottomLeft',\r\n\t'bottom',\r\n\t'bottomRight',\r\n];\r\nexport type AlertPosition = (typeof ALERT_POSITIONS)[number];\r\n\r\n/**\r\n * Configuration for a button rendered inside an alert.\r\n */\r\nexport interface AlertButton {\r\n\t/** Text displayed on the button. */\r\n\ttext: string;\r\n\t/** Optional click handler invoked when the button is pressed. */\r\n\tcallback?: () => void;\r\n}\r\n\r\n/**\r\n * Base options that can be supplied when showing an alert.\r\n */\r\nexport interface AlertConfig {\r\n\t/** Message text displayed to the user. */\r\n\ttext?: string;\r\n\t/** One of {@link ALERT_TYPES} determining alert style. */\r\n\ttype?: AlertType;\r\n\t/** Location on screen where the alert should appear. */\r\n\tposition?: AlertPosition;\r\n\t/** Optional action buttons displayed within the alert. */\r\n\tbuttons?: AlertButton[];\r\n\t/** Optional icon name to show with the message. */\r\n\ticon?: string;\r\n\t/** Custom CSS class applied to the alert container. */\r\n\tclass?: string;\r\n\t/** Identifier used to ensure only one alert with this key exists. */\r\n\tunique?: string;\r\n\t/** Whether to show a progress bar. */\r\n\tprogress?: boolean;\r\n\t/** Milliseconds before auto dismissal. */\r\n\ttimeout?: number;\r\n\t/** Callback executed when the alert is closed. */\r\n\tclose?: () => void;\r\n\tclosable?: boolean;\r\n}\r\n\r\nexport interface Alert extends AlertConfig {\r\n\t/** Unique identifier for the alert instance. */\r\n\tid?: number;\r\n\t/** Reactive signal tracking progress bar value. */\r\n\tprogressPercentage?: Signal<number>;\r\n\t/** Handler executed when the alert closes. */\r\n\tonClose?: () => void;\r\n\t/** Component rendered inside the alert body. */\r\n\tcomponent?: Type<unknown>;\r\n\t[x: string]: unknown;\r\n}\r\n\r\n/**\r\n * Default values applied when an alert is shown without specific options.\r\n */\r\nexport const DEFAULT_ALERT_CONFIG: Alert = {\r\n\ttext: '',\r\n\ttype: 'info',\r\n\tclass: '',\r\n\tprogress: true,\r\n\tposition: 'bottom',\r\n\ttimeout: 3000,\r\n\tclosable: true,\r\n\tbuttons: [],\r\n};\r\n","import { InjectionToken } from '@angular/core';\r\nimport { AlertConfig } from './alert.interface';\r\nimport { HttpConfig } from './http.interface';\r\nimport { LoaderConfig } from './loader.interface';\r\nimport { MetaConfig } from './meta.interface';\r\nimport { ModalConfig } from './modal.interface';\r\nimport { StoreConfig } from './store.interface';\r\n\r\n/**\r\n * Root configuration object used to initialize the library.\r\n * Each property allows consumers to override the default\r\n * behavior of the corresponding service.\r\n */\r\nexport interface Config {\r\n\t/** Options for the key‑value storage service. */\r\n\tstore?: StoreConfig;\r\n\t/** Defaults applied to page metadata handling. */\r\n\tmeta?: MetaConfig;\r\n\t/** Global settings for the alert service. */\r\n\talert?: AlertConfig;\r\n\t/** Default options for loader overlays. */\r\n\tloader?: LoaderConfig;\r\n\t/** Configuration for modal dialogs. */\r\n\tmodal?: ModalConfig;\r\n\t/** Base HTTP settings such as API URL and headers. */\r\n\thttp?: HttpConfig;\r\n\t/** Optional socket connection configuration. */\r\n\tsocket?: any;\r\n\t/** Raw Socket.IO client instance, if used. */\r\n\tio?: any;\r\n\ttheme?: {\r\n\t\tprimary: string;\r\n\t\tsecondary: string;\r\n\t\tinfo: string;\r\n\t\terror: string;\r\n\t\tsuccess: string;\r\n\t\twarning: string;\r\n\t\tquestion: string;\r\n\t};\r\n}\r\n\r\nexport const CONFIG_TOKEN = new InjectionToken<Config>('config');\r\n\r\nexport const DEFAULT_CONFIG: Config = {\r\n\tstore: {\r\n\t\tprefix: 'waStore',\r\n\t},\r\n\tmeta: {\r\n\t\tuseTitleSuffix: false,\r\n\t\twarnMissingGuard: true,\r\n\t\tdefaults: { links: {} },\r\n\t},\r\n\tsocket: false,\r\n\thttp: {\r\n\t\turl: '',\r\n\t\theaders: {},\r\n\t},\r\n\ttheme: {\r\n\t\tprimary: '#fff',\r\n\t\tsecondary: '#000',\r\n\t\tinfo: '#9ddeff',\r\n\t\terror: '#ffafb4',\r\n\t\tsuccess: '#a6efb8',\r\n\t\twarning: '#ffcfa5',\r\n\t\tquestion: '#fff9b2',\r\n\t},\r\n};\r\n","import { Signal, Type } from '@angular/core';\r\n\r\nexport const MODAL_SIZES = ['small', 'mid', 'big', 'full'];\r\nexport type ModalSizes = (typeof MODAL_SIZES)[number];\r\n\r\n/**\r\n * Configuration for a button rendered inside an modal.\r\n */\r\nexport interface ModalButton {\r\n\t/** Text displayed on the button. */\r\n\ttext: string;\r\n\t/** Optional click handler invoked when the button is pressed. */\r\n\tcallback?: () => void;\r\n}\r\n\r\nexport interface ModalConfig {\r\n\t/** Size of the modal window. */\r\n\tsize?: ModalSizes;\r\n\t/** Optional action buttons displayed within the Modal. */\r\n\tbuttons?: ModalButton[];\r\n\t/** Custom CSS class applied to the Modal container. */\r\n\tclass?: string;\r\n\t/** Identifier used to ensure only one Modal with this key exists. */\r\n\tunique?: string;\r\n\t/** Whether to show a progress bar. */\r\n\tprogress?: boolean;\r\n\t/** Milliseconds before auto dismissal. */\r\n\ttimeout?: number;\r\n\t/** Callback executed when the Modal is closed. */\r\n\tclose?: () => void;\r\n\t/** Allow closing the modal via UI controls. */\r\n\tclosable?: boolean;\r\n}\r\n\r\nexport interface Modal extends ModalConfig {\r\n\t/** Component used to render the modal content. */\r\n\tcomponent: Type<unknown>;\r\n\t/** Unique identifier for the modal instance. */\r\n\tid?: number;\r\n\t/** Signal emitting the current progress percentage. */\r\n\tprogressPercentage?: Signal<number>;\r\n\t/** Handler called when the user clicks outside the modal. */\r\n\tonClickOutside?: () => void;\r\n\t/** Callback executed when the modal closes. */\r\n\tonClose?: () => void;\r\n\t/** Callback executed when the modal opens. */\r\n\tonOpen?: () => void;\r\n\t[x: string]: unknown;\r\n}\r\n\r\nexport const DEFAULT_MODAL_CONFIG: ModalConfig = {\r\n\tsize: 'mid',\r\n\ttimeout: 0,\r\n\tclass: '',\r\n\tclosable: true,\r\n};\r\n","import { Inject, Injectable, Optional } from '@angular/core';\r\nimport { Meta, Title } from '@angular/platform-browser';\r\nimport { Route, Router } from '@angular/router';\r\nimport { CONFIG_TOKEN, Config, DEFAULT_CONFIG } from '../interfaces/config.interface';\r\nimport { MetaConfig, MetaDefaults } from '../interfaces/meta.interface';\r\n\r\nconst isDefined = (val: any) => typeof val !== 'undefined';\r\n\r\n@Injectable({\r\n\tprovidedIn: 'root',\r\n})\r\nexport class MetaService {\r\n\tprivate _meta: MetaConfig;\r\n\r\n\tconstructor(\r\n\t\t@Inject(CONFIG_TOKEN) @Optional() private config: Config,\r\n\t\tprivate router: Router,\r\n\t\tprivate meta: Meta,\r\n\t\tprivate titleService: Title,\r\n\t) {\r\n\t\tthis.config = this.config || DEFAULT_CONFIG;\r\n\r\n\t\tthis._meta = this.config.meta || {};\r\n\r\n\t\tthis._warnMissingGuard();\r\n\t}\r\n\r\n\t/**\r\n\t * Sets the default meta tags.\r\n\t *\r\n\t * @param defaults - The default meta tags.\r\n\t */\r\n\tsetDefaults(defaults: MetaDefaults) {\r\n\t\tthis._meta.defaults = {\r\n\t\t\t...this._meta.defaults,\r\n\t\t\t...defaults,\r\n\t\t};\r\n\t}\r\n\r\n\t/**\r\n\t * Sets the title and optional title suffix.\r\n\t *\r\n\t * @param title - The title to set.\r\n\t * @param titleSuffix - The title suffix to append.\r\n\t * @returns The MetaService instance.\r\n\t */\r\n\tsetTitle(title?: string, titleSuffix?: string): MetaService {\r\n\t\tlet titleContent = isDefined(title) ? title || '' : this._meta.defaults?.['title'] || '';\r\n\r\n\t\tif (this._meta.useTitleSuffix) {\r\n\t\t\ttitleContent += isDefined(titleSuffix) ? titleSuffix : this._meta.defaults?.['titleSuffix'] || '';\r\n\t\t}\r\n\r\n\t\tthis._updateMetaTag('title', titleContent);\r\n\r\n\t\tthis._updateMetaTag('og:title', titleContent);\r\n\r\n\t\tthis._updateMetaTag('twitter:title', titleContent);\r\n\r\n\t\tthis.titleService.setTitle(titleContent);\r\n\r\n\t\treturn this;\r\n\t}\r\n\r\n\t/**\r\n\t * Sets link tags.\r\n\t *\r\n\t * @param links - The links to set.\r\n\t * @returns The MetaService instance.\r\n\t */\r\n\tsetLink(links: { [key: string]: string }): MetaService {\r\n\t\tObject.keys(links).forEach((rel) => {\r\n\t\t\tlet link: HTMLLinkElement = document.createElement('link');\r\n\r\n\t\t\tlink.setAttribute('rel', rel);\r\n\r\n\t\t\tlink.setAttribute('href', links[rel]);\r\n\r\n\t\t\tdocument.head.appendChild(link);\r\n\t\t});\r\n\r\n\t\treturn this;\r\n\t}\r\n\r\n\t/**\r\n\t * Sets a meta tag.\r\n\t *\r\n\t * @param tag - The meta tag name.\r\n\t * @param value - The meta tag value.\r\n\t * @param prop - The meta tag property.\r\n\t */\r\n\tsetTag(tag: string, value: string, prop?: string) {\r\n\t\tif (tag === 'title' || tag === 'titleSuffix') {\r\n\t\t\tthrow new Error(\r\n\t\t\t\t`Attempt to set ${tag} through 'setTag': 'title' and 'titleSuffix' are reserved. Use 'MetaService.setTitle' instead.`,\r\n\t\t\t);\r\n\t\t}\r\n\r\n\t\tconst content = (isDefined(value) ? value || '' : this._meta.defaults?.[tag] || '') + '';\r\n\r\n\t\tthis._updateMetaTag(tag, content, prop);\r\n\r\n\t\tif (tag === 'description') {\r\n\t\t\tthis._updateMetaTag('og:description', content, prop);\r\n\r\n\t\t\tthis._updateMetaTag('twitter:description', content, prop);\r\n\t\t}\r\n\t}\r\n\r\n\t/**\r\n\t * Updates a meta tag.\r\n\t *\r\n\t * @param tag - The meta tag name.\r\n\t * @param value - The meta tag value.\r\n\t * @param prop - The meta tag property.\r\n\t */\r\n\tprivate _updateMetaTag(tag: string, value: string, prop?: string): void {\r\n\t\tprop = prop || (tag.startsWith('og:') || tag.startsWith('twitter:') ? 'property' : 'name');\r\n\r\n\t\tthis.meta.updateTag({ [prop]: tag, content: value });\r\n\t}\r\n\r\n\t/**\r\n\t * Removes a meta tag.\r\n\t *\r\n\t * @param tag - The meta tag name.\r\n\t * @param prop - The meta tag property.\r\n\t */\r\n\tremoveTag(tag: string, prop?: string): void {\r\n\t\tprop = prop || (tag.startsWith('og:') || tag.startsWith('twitter:') ? 'property' : 'name');\r\n\r\n\t\tthis.meta.removeTag(`${prop}=\"${tag}\"`);\r\n\t}\r\n\r\n\t/**\r\n\t * Warns about missing meta guards in routes.\r\n\t */\r\n\tprivate _warnMissingGuard(): void {\r\n\t\tif (isDefined(this._meta.warnMissingGuard) && !this._meta.warnMissingGuard) {\r\n\t\t\treturn;\r\n\t\t}\r\n\r\n\t\tconst hasDefaultMeta = !!Object.keys(this._meta.defaults ?? {}).length;\r\n\r\n\t\tconst hasMetaGuardInArr = (it: any) => it && it.IDENTIFIER === 'MetaGuard';\r\n\r\n\t\tlet hasShownWarnings = false;\r\n\r\n\t\tconst checkRoute = (route: Route) => {\r\n\t\t\tconst hasRouteMeta = route.data && route.data['meta'];\r\n\r\n\t\t\tconst showWarning =\r\n\t\t\t\t!isDefined(route.redirectTo) && (hasDefaultMeta || hasRouteMeta) && !(route.canActivate || []).some(hasMetaGuardInArr);\r\n\r\n\t\t\tif (showWarning) {\r\n\t\t\t\tconsole.warn(\r\n\t\t\t\t\t`Route with path \"${route.path}\" has ${\r\n\t\t\t\t\t\thasRouteMeta ? '' : 'default '\r\n\t\t\t\t\t}meta tags, but does not use MetaGuard. Please add MetaGuard to the canActivate array in your route configuration`,\r\n\t\t\t\t);\r\n\t\t\t\thasShownWarnings = true;\r\n\t\t\t}\r\n\r\n\t\t\t(route.children || []).forEach(checkRoute);\r\n\t\t};\r\n\r\n\t\tthis.router.config.forEach(checkRoute);\r\n\r\n\t\tif (hasShownWarnings) {\r\n\t\t\tconsole.warn(\r\n\t\t\t\t`To disable these warnings, set metaConfig.warnMissingGuard: false in your MetaConfig passed to MetaModule.forRoot()`,\r\n\t\t\t);\r\n\t\t}\r\n\t}\r\n}\r\n","import { Inject, Injectable, Optional } from '@angular/core';\r\nimport { ActivatedRouteSnapshot, RouterStateSnapshot } from '@angular/router';\r\nimport { CONFIG_TOKEN, Config, DEFAULT_CONFIG } from '../interfaces/config.interface';\r\nimport { MetaService } from '../services/meta.service';\r\n\r\n@Injectable({ providedIn: 'root' })\r\nexport class MetaGuard {\r\n\tpublic static IDENTIFIER = 'MetaGuard';\r\n\tprivate _meta: any;\r\n\tpublic constructor(\r\n\t\tprivate metaService: MetaService,\r\n\t\t@Inject(CONFIG_TOKEN) @Optional() private config: Config,\r\n\t) {\r\n\t\tif (!this.config) this.config = DEFAULT_CONFIG;\r\n\t\tthis._meta = this.config.meta || {};\r\n\t\tthis._meta.defaults = this._meta.defaults || {};\r\n\t}\r\n\tpublic canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): boolean {\r\n\t\tthis._processRouteMetaTags(route.data && route.data['meta']);\r\n\t\treturn true;\r\n\t}\r\n\tprivate _processRouteMetaTags(meta: any = {}) {\r\n\t\tif (meta.disableUpdate) {\r\n\t\t\treturn;\r\n\t\t}\r\n\t\tif (meta.title) {\r\n\t\t\tthis.metaService.setTitle(meta.title, meta.titleSuffix);\r\n\t\t}\r\n\t\tif (meta.links && Object.keys(meta.links).length) {\r\n\t\t\tthis.metaService.setLink(meta.links);\r\n\t\t}\r\n\t\tif (this._meta.defaults?.links && Object.keys(this._meta.defaults.links).length) {\r\n\t\t\tthis.metaService.setLink(this._meta.defaults.links);\r\n\t\t}\r\n\t\tObject.keys(meta).forEach((prop) => {\r\n\t\t\tif (prop === 'title' || prop === 'titleSuffix' || prop === 'links') {\r\n\t\t\t\treturn;\r\n\t\t\t}\r\n\t\t\tObject.keys(meta[prop]).forEach((key) => {\r\n\t\t\t\tthis.metaService.setTag(key, meta[prop][key], prop);\r\n\t\t\t});\r\n\t\t});\r\n\t\tObject.keys(this._meta.defaults).forEach((key) => {\r\n\t\t\tif (key in meta || key === 'title' || key === 'titleSuffix' || key === 'links') {\r\n\t\t\t\treturn;\r\n\t\t\t}\r\n\t\t\tthis.metaService.setTag(key, this._meta.defaults[key] as string);\r\n\t\t});\r\n\t}\r\n}\r\n","import { CommonModule } from '@angular/common';\r\nimport { AfterViewInit, Component, ElementRef, ViewChild } from '@angular/core';\r\nimport { AlertButton, AlertPosition, AlertType } from '../../../interfaces/alert.interface';\r\n\r\n@Component({\r\n\tselector: 'alert',\r\n\ttemplateUrl: './alert.component.html',\r\n\tstyleUrls: ['./alert.component.scss'],\r\n\timports: [CommonModule],\r\n})\r\n/**\r\n * Displays an individual alert message with optional icon, actions and\r\n * auto‑dismiss behaviour. All inputs are configured by the service when the\r\n * component is created dynamically.\r\n */\r\nexport class AlertComponent implements AfterViewInit {\r\n\t/** Reference to the DOM element hosting the alert. */\r\n\t@ViewChild('alertRef') alertRef!: ElementRef<HTMLDivElement>;\r\n\r\n\t/** Callback invoked to remove the alert from the DOM. */\r\n\tclose!: () => void;\r\n\r\n\t/** Text content displayed inside the alert. */\r\n\ttext!: string;\r\n\r\n\t/** Additional CSS classes applied to the alert container. */\r\n\tclass!: string;\r\n\r\n\t/** Type of alert which determines styling and icon. */\r\n\ttype: AlertType = 'info';\r\n\r\n\t/** Position on the screen where the alert appears. */\r\n\tposition: AlertPosition = 'bottom';\r\n\r\n\t/** Whether a progress bar indicating remaining time is shown. */\r\n\tprogress!: boolean;\r\n\r\n\t/** Icon name displayed alongside the message. */\r\n\ticon!: string;\r\n\r\n\t/** Time in milliseconds before the alert auto closes. */\r\n\ttimeout!: number;\r\n\r\n\t/** Determines if a manual close button is visible. */\r\n\tclosable!: boolean;\r\n\r\n\t/** Flag used to trigger the deletion animation. */\r\n\tdelete_animation = false;\r\n\r\n\t/** Optional action buttons rendered within the alert. */\r\n\tbuttons: AlertButton[] = [];\r\n\r\n\t/**\r\n\t * Starts the auto‑dismiss timer and pauses it while the alert is\r\n\t * hovered, resuming when the mouse leaves.\r\n\t */\r\n\tngAfterViewInit(): void {\r\n\t\tif (this.timeout) {\r\n\t\t\tlet remaining = JSON.parse(JSON.stringify(this.timeout));\r\n\r\n\t\t\tlet timer: number = window.setTimeout(() => {\r\n\t\t\t\tthis.remove();\r\n\t\t\t}, remaining);\r\n\r\n\t\t\tlet start = new Date();\r\n\r\n\t\t\tthis.alertRef.nativeElement.addEventListener(\r\n\t\t\t\t'mouseenter',\r\n\t\t\t\t() => {\r\n\t\t\t\t\tclearTimeout(timer);\r\n\r\n\t\t\t\t\tremaining -= new Date().getTime() - start.getTime();\r\n\t\t\t\t},\r\n\t\t\t\tfalse,\r\n\t\t\t);\r\n\r\n\t\t\tthis.alertRef.nativeElement.addEventListener(\r\n\t\t\t\t'mouseleave',\r\n\t\t\t\t() => {\r\n\t\t\t\t\tstart = new Date();\r\n\r\n\t\t\t\t\tclearTimeout(timer);\r\n\r\n\t\t\t\t\ttimer = window.setTimeout(() => {\r\n\t\t\t\t\t\tthis.remove();\r\n\t\t\t\t\t}, remaining);\r\n\t\t\t\t},\r\n\t\t\t\tfalse,\r\n\t\t\t);\r\n\t\t}\r\n\t}\r\n\r\n\t/**\r\n\t * Triggers the closing animation and invokes the provided close\r\n\t * callback once finished.\r\n\t */\r\n\tremove(callback?: () => void) {\r\n\t\tif (this._removed) return;\r\n\r\n\t\tthis._removed = true;\r\n\r\n\t\tcallback?.();\r\n\r\n\t\tthis.delete_animation = true;\r\n\r\n\t\tsetTimeout(() => {\r\n\t\t\tthis.close();\r\n\r\n\t\t\tthis.delete_animation = false;\r\n\t\t}, 350);\r\n\t}\r\n\tprivate _removed = false;\r\n}\r\n","@if (text) {\r\n\t<div class=\"wacom-alert wacom-alert--auto-height\" [class.wacom-alert--closing]=\"delete_animation\" [ngClass]=\"class\">\r\n\t\t<div [ngClass]=\"'wacom-alert__content--color-' + type\" class=\"wacom-alert__content wacom-alert__content--bounce-in-up\" #alertRef>\r\n\t\t\t@if (progress) {\r\n\t\t\t\t<div class=\"wacom-alert__progress\">\r\n\t\t\t\t\t<span\r\n\t\t\t\t\t\tclass=\"wacom-alert__progress-bar\"\r\n\t\t\t\t\t\t[ngClass]=\"'wacom-alert__progress-bar--' + type\"\r\n\t\t\t\t\t\t[ngStyle]=\"{\r\n\t\t\t\t\t\t\t'animation-duration': (timeout + 350) / 1000 + 's',\r\n\t\t\t\t\t\t}\"\r\n\t\t\t\t\t></span>\r\n\t\t\t\t</div>\r\n\t\t\t}\r\n\t\t\t<div class=\"wacom-alert__body\">\r\n\t\t\t\t<div class=\"wacom-alert__texts\">\r\n\t\t\t\t\t@if (icon) {\r\n\t\t\t\t\t\t<div class=\"{{ icon }}\"></div>\r\n\t\t\t\t\t}\r\n\t\t\t\t\t<div class=\"wacom-alert__message wacom-alert__message--slide-in\">\r\n\t\t\t\t\t\t{{ text }}\r\n\t\t\t\t\t</div>\r\n\t\t\t\t</div>\r\n\t\t\t\t@if (type === \"question\") {\r\n\t\t\t\t\t<div>\r\n\t\t\t\t\t\t@for (button of buttons; track button.text) {\r\n\t\t\t\t\t\t\t<button (click)=\"remove(button.callback)\" class=\"wacom-alert__button\">\r\n\t\t\t\t\t\t\t\t{{ button.text }}\r\n\t\t\t\t\t\t\t</button>\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t</div>\r\n\t\t\t\t}\r\n\t\t\t\t@if (closable) {\r\n\t\t\t\t\t<div class=\"wacom-alert__close\" (click)=\"remove()\"></div>\r\n\t\t\t\t}\r\n\t\t\t</div>\r\n\t\t</div>\r\n\t</div>\r\n}\r\n","/**\n * BaseComponent is an abstract class that provides basic functionality for managing the current timestamp.\n */\nexport abstract class BaseComponent {\n\t/**\n\t * The current timestamp in milliseconds since the Unix epoch.\n\t */\n\tnow = new Date().getTime();\n\n\t/**\n\t * Refreshes the `now` property with the current timestamp.\n\t */\n\trefreshNow(): void {\n\t\tthis.now = new Date().getTime();\n\t}\n}\n","import { Component } from '@angular/core';\r\n\r\n@Component({\r\n\tselector: 'lib-wrapper',\r\n\ttemplateUrl: './wrapper.component.html',\r\n\tstyleUrls: ['./wrapper.component.scss'],\r\n\timports: [],\r\n})\r\n/**\r\n * Container component that provides placeholder elements for alert instances\r\n * rendered in different screen positions.\r\n */\r\nexport class WrapperComponent {}\r\n","<div class=\"wacom-wrapper\">\r\n\t<div class=\"wacom-wrapper__alert wacom-wrapper__alert--top-left\" id=\"topLeft\"></div>\r\n\t<div class=\"wacom-wrapper__alert wacom-wrapper__alert--top\" id=\"top\"></div>\r\n\t<div class=\"wacom-wrapper__alert wacom-wrapper__alert--top-right\" id=\"topRight\"></div>\r\n\r\n\t<div class=\"wacom-wrapper__alert wacom-wrapper__alert--left\" id=\"left\"></div>\r\n\t<div class=\"wacom-wrapper__alert wacom-wrapper__alert--center\" id=\"center\"></div>\r\n\t<div class=\"wacom-wrapper__alert wacom-wrapper__alert--right\" id=\"right\"></div>\r\n\r\n\t<div class=\"wacom-wrapper__alert wacom-wrapper__alert--bottom-left\" id=\"bottomLeft\"></div>\r\n\t<div class=\"wacom-wrapper__alert wacom-wrapper__alert--bottom\" id=\"bottom\"></div>\r\n\t<div class=\"wacom-wrapper__alert wacom-wrapper__alert--bottom-right\" id=\"bottomRight\"></div>\r\n</div>\r\n","import {\r\n\tApplicationRef,\r\n\tComponentRef,\r\n\tEmbeddedViewRef,\r\n\tEnvironmentInjector,\r\n\tInjectable,\r\n\tType,\r\n\tcreateComponent,\r\n\tinject,\r\n} from '@angular/core';\r\nimport { DomComponent } from '../interfaces/dom.interface';\r\n\r\n@Injectable({\r\n\tprovidedIn: 'root',\r\n})\r\n/**\r\n * Utility service for programmatically creating and interacting with Angular\r\n * components within the DOM.\r\n */\r\nexport class DomService {\r\n\t/**\r\n\t * Appends a component to a specified element by ID.\r\n\t *\r\n\t * @param component - The component to append.\r\n\t * @param options - The options to project into the component.\r\n\t * @param id - The ID of the element to append the component to.\r\n\t * @returns An object containing the native element and the component reference.\r\n\t */\r\n\tappendById<T>(component: Type<T>, options: Partial<T> = {}, id: string): DomComponent<T> {\r\n\t\tconst componentRef = createComponent(component, {\r\n\t\t\tenvironmentInjector: this._injector,\r\n\t\t});\r\n\r\n\t\tthis.projectComponentInputs(componentRef, options);\r\n\r\n\t\tthis._appRef.attachView(componentRef.hostView);\r\n\r\n\t\tconst domElem = (componentRef.hostView as EmbeddedViewRef<T>).rootNodes[0] as HTMLElement;\r\n\r\n\t\tconst element = document.getElementById(id);\r\n\r\n\t\tif (element && typeof element.appendChild === 'function') {\r\n\t\t\telement.appendChild(domElem);\r\n\t\t}\r\n\r\n\t\treturn {\r\n\t\t\tnativeElement: domElem,\r\n\t\t\tcomponentRef: componentRef,\r\n\t\t\tremove: () => this.removeComponent(componentRef),\r\n\t\t};\r\n\t}\r\n\r\n\t/**\r\n\t * Appends a component to a specified element or to the body.\r\n\t *\r\n\t * @param component - The component to append.\r\n\t * @param options - The options to project into the component.\r\n\t * @param element - The element to append the component to. Defaults to body.\r\n\t * @returns An object containing the native element and the component reference.\r\n\t */\r\n\tappendComponent<T>(\r\n\t\tcomponent: Type<T>,\r\n\t\toptions: Partial<T & { providedIn?: string }> = {},\r\n\t\telement: HTMLElement = document.body,\r\n\t): DomComponent<T> | void {\r\n\t\tif (options.providedIn) {\r\n\t\t\tif (this._providedIn[options.providedIn]) {\r\n\t\t\t\treturn;\r\n\t\t\t}\r\n\r\n\t\t\tthis._providedIn[options.providedIn] = true;\r\n\t\t}\r\n\r\n\t\tconst componentRef = createComponent(component, {\r\n\t\t\tenvironmentInjector: this._injector,\r\n\t\t});\r\n\r\n\t\tthis.projectComponentInputs(componentRef, options);\r\n\r\n\t\tthis._appRef.attachView(componentRef.hostView);\r\n\r\n\t\tconst domElem = (componentRef.hostView as EmbeddedViewRef<T>).rootNodes[0] as HTMLElement;\r\n\r\n\t\tif (element && typeof element.appendChild === 'function') {\r\n\t\t\telement.appendChild(domElem);\r\n\t\t}\r\n\r\n\t\treturn {\r\n\t\t\tnativeElement: domElem,\r\n\t\t\tcomponentRef: componentRef,\r\n\t\t\tremove: () => this.removeComponent(componentRef, options.providedIn),\r\n\t\t};\r\n\t}\r\n\r\n\t/**\r\n\t * Gets a reference to a dynamically created component.\r\n\t *\r\n\t * @param component - The component to create.\r\n\t * @param options - The options to project into the component.\r\n\t * @returns The component reference.\r\n\t */\r\n\tgetComponentRef<T>(component: Type<T>, options: Partial<T> = {}): ComponentRef<T> {\r\n\t\tconst componentRef = createComponent(component, {\r\n\t\t\tenvironmentInjector: this._injector,\r\n\t\t});\r\n\r\n\t\tthis.projectComponentInputs(componentRef, options);\r\n\r\n\t\tthis._appRef.attachView(componentRef.hostView);\r\n\r\n\t\treturn componentRef;\r\n\t}\r\n\r\n\t/**\r\n\t * Projects the inputs onto the component.\r\n\t *\r\n\t * @param component - The component reference.\r\n\t * @param options - The options to project into the component.\r\n\t * @returns The component reference with the projected inputs.\r\n\t */\r\n\tprivate projectComponentInputs<T>(component: ComponentRef<T>, options: Partial<T>): ComponentRef<T> {\r\n\t\tif (options) {\r\n\t\t\tconst props = Object.getOwnPropertyNames(options);\r\n\r\n\t\t\tfor (const prop of props) {\r\n\t\t\t\t(component.instance as any)[prop] = (options as any)[prop];\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\treturn component;\r\n\t}\r\n\r\n\t/**\r\n\t * Removes a previously attached component and optionally clears its\r\n\t * unique `providedIn` flag.\r\n\t *\r\n\t * @param componentRef - Reference to the component to be removed.\r\n\t * @param providedIn - Optional key used to track unique instances.\r\n\t */\r\n\tremoveComponent<T>(componentRef: ComponentRef<T>, providedIn?: string): void {\r\n\t\tthis._appRef.detachView(componentRef.hostView);\r\n\r\n\t\tcomponentRef.destroy();\r\n\r\n\t\tif (providedIn) {\r\n\t\t\tdelete this._providedIn[providedIn];\r\n\t\t}\r\n\t}\r\n\r\n\t/** Reference to the root application used for view attachment. */\r\n\tprivate _appRef = inject(ApplicationRef);\r\n\r\n\t/** Injector utilized when creating dynamic components. */\r\n\tprivate _injector = inject(EnvironmentInjector);\r\n\r\n\t/**\r\n\t * Flags to ensure components with a specific `providedIn` key are only\r\n\t * instantiated once at a time.\r\n\t */\r\n\tprivate _providedIn: Record<string, boolean> = {};\r\n}\r\n","import { Inject, Injectable, Optional } from '@angular/core';\r\nimport { AlertComponent } from '../components/alert/alert/alert.component';\r\nimport { WrapperComponent } from '../components/alert/wrapper/wrapper.component';\r\nimport { Alert, AlertConfig, DEFAULT_ALERT_CONFIG } from '../interfaces/alert.interface';\r\nimport { Config, CONFIG_TOKEN } from '../interfaces/config.interface';\r\nimport { DomComponent } from '../interfaces/dom.interface';\r\nimport { DomService } from './dom.service';\r\n\r\n@Injectable({\r\n\tprovidedIn: 'root',\r\n})\r\nexport class AlertService {\r\n\t/**\r\n\t * Creates a new alert service.\r\n\t *\r\n\t * @param config Optional global configuration provided via the\r\n\t * `CONFIG_TOKEN` injection token.\r\n\t * @param _dom Service responsible for DOM manipulation and dynamic\r\n\t * component creation.\r\n\t */\r\n\tconstructor(\r\n\t\t@Inject(CONFIG_TOKEN) @Optional() config: Config,\r\n\t\tprivate _dom: DomService,\r\n\t) {\r\n\t\tthis._config = {\r\n\t\t\t...DEFAULT_ALERT_CONFIG,\r\n\t\t\t...(config?.alert || {}),\r\n\t\t};\r\n\r\n\t\tthis._container = this._dom.appendComponent(WrapperComponent)!;\r\n\t}\r\n\r\n\t/**\r\n\t * Displays an alert. Accepts either an options object or a simple string\r\n\t * which will be used as the alert text.\r\n\t *\r\n\t * @returns Reference to the created alert or embedded component\r\n\t * element.\r\n\t */\r\n\tshow(opts: Alert | string): Alert {\r\n\t\topts = this._opts(opts);\r\n\r\n\t\tif (opts.unique && this._alerts.find((m) => m.unique === opts.unique)) {\r\n\t\t\treturn this._alerts.find((m) => m.unique === opts.unique) as Alert;\r\n\t\t}\r\n\r\n\t\tthis._alerts.push(opts);\r\n\r\n\t\topts.id ||= Math.floor(Math.random() * Date.now()) + Date.now();\r\n\r\n\t\tif (!opts.type) opts.type = 'info';\r\n\r\n\t\tif (!opts.position) opts.position = 'bottomRight';\r\n\r\n\t\tlet alertComponent: DomComponent<AlertComponent> | undefined;\r\n\r\n\t\tlet content: DomComponent<any> | undefined;\r\n\r\n\t\topts.close = () => {\r\n\t\t\tcontent?.remove();\r\n\r\n\t\t\talertComponent?.remove();\r\n\r\n\t\t\tcontent = undefined;\r\n\r\n\t\t\talertComponent = undefined;\r\n\r\n\t\t\tif (typeof (opts as Alert).onClose == 'function') {\r\n\t\t\t\t(opts as Alert).onClose?.();\r\n\t\t\t}\r\n\r\n\t\t\tthis._alerts.splice(\r\n\t\t\t\tthis._alerts.findIndex((m) => m.id === opts.id),\r\n\t\t\t\t1,\r\n\t\t\t);\r\n\t\t};\r\n\r\n\t\talertComponent = this._dom.appendById(AlertComponent, opts, opts.position);\r\n\r\n\t\tif (typeof opts.component === 'function') {\r\n\t\t\tcontent = this._dom.appendComponent(\r\n\t\t\t\topts.component,\r\n\t\t\t\topts as Partial<{ providedIn?: string | undefined }>,\r\n\t\t\t\tthis._container.nativeElement.children[0].children[this._positionNumber[opts.position] || 0] as HTMLElement,\r\n\t\t\t)!;\r\n\t\t}\r\n\r\n\t\tif (typeof opts.timeout !== 'number') {\r\n\t\t\topts.timeout = 3000;\r\n\t\t}\r\n\r\n\t\tif (opts.timeout) {\r\n\t\t\tsetTimeout(() => {\r\n\t\t\t\topts.close?.();\r\n\t\t\t}, opts.timeout);\r\n\t\t}\r\n\r\n\t\treturn opts;\r\n\t}\r\n\r\n\t/**\r\n\t * Convenience alias for `show`.\r\n\t */\r\n\topen(opts: Alert) {\r\n\t\tthis.show(opts);\r\n\t}\r\n\r\n\t/**\r\n\t * Displays an informational alert.\r\n\t */\r\n\tinfo(opts: Alert) {\r\n\t\topts = this._opts(opts);\r\n\r\n\t\topts.type = 'info';\r\n\r\n\t\tthis.show(opts);\r\n\t}\r\n\r\n\t/**\r\n\t * Displays a success alert.\r\n\t */\r\n\tsuccess(opts: Alert) {\r\n\t\topts = this._opts(opts);\r\n\r\n\t\topts.type = 'success';\r\n\r\n\t\tthis.show(opts);\r\n\t}\r\n\r\n\t/**\r\n\t * Displays a warning alert.\r\n\t */\r\n\twarning(opts: Alert) {\r\n\t\topts = this._opts(opts);\r\n\r\n\t\topts.type = 'warning';\r\n\r\n\t\tthis.show(opts);\r\n\t}\r\n\r\n\t/**\r\n\t * Displays an error alert.\r\n\t */\r\n\terror(opts: Alert) {\r\n\t\topts = this._opts(opts);\r\n\r\n\t\topts.type = 'error';\r\n\r\n\t\tthis.show(opts);\r\n\t}\r\n\r\n\t/**\r\n\t * Displays a question alert.\r\n\t */\r\n\tquestion(opts: Alert) {\r\n\t\topts = this._opts(opts);\r\n\r\n\t\topts.type = 'question';\r\n\r\n\t\tthis.show(opts);\r\n\t}\r\n\r\n\t/**\r\n\t * Removes all alert elements from the document.\r\n\t */\r\n\tdestroy() {\r\n\t\tfor (let i = this._alerts.length - 1; i >= 0; i--) {\r\n\t\t\tthis._alerts[i].close?.();\r\n\t\t}\r\n\t}\r\n\tprivate _alerts: Alert[] = [];\r\n\r\n\t/** Merged configuration applied to new alerts. */\r\n\tprivate _config: AlertConfig;\r\n\r\n\t/** Wrapper component that contains all alert placeholders. */\r\n\tprivate _container: DomComponent<WrapperComponent>;\r\n\r\n\t/** Mapping of alert positions to wrapper child indexes. */\r\n\tprivate _positionNumber: Record<string, number> = {\r\n\t\ttopLeft: 0,\r\n\t\ttop: 1,\r\n\t\ttopRight: 2,\r\n\t\tleft: 3,\r\n\t\tcenter: 4,\r\n\t\tright: 5,\r\n\t\tbottomLeft: 6,\r\n\t\tbottom: 7,\r\n\t\tbottomRight: 8,\r\n\t};\r\n\r\n\tprivate _opts(opts: Alert | string): Alert {\r\n\t\treturn typeof opts === 'string'\r\n\t\t\t? {\r\n\t\t\t\t\t...this._config,\r\n\t\t\t\t\ttext: opts,\r\n\t\t\t\t}\r\n\t\t\t: {\r\n\t\t\t\t\t...this._config,\r\n\t\t\t\t\t...opts,\r\n\t\t\t\t};\r\n\t}\r\n}\r\n","import { Inject, Injectable, PLATFORM_ID, Signal, WritableSignal, signal } from '@angular/core';\r\nimport { Observable, Subject } from 'rxjs';\r\nimport { Selectitem } from '../interfaces/select.item.interface';\r\n\r\n// Add capitalize method to String prototype if it doesn't already exist\r\nif (!String.prototype.capitalize) {\r\n\tString.prototype.capitalize = function (): string {\r\n\t\tif (this.length > 0) {\r\n\t\t\treturn this.charAt(0).toUpperCase() + this.slice(1).toLowerCase();\r\n\t\t}\r\n\t\treturn '';\r\n\t};\r\n}\r\n\r\n// Extend the String interface to include the new method\r\ndeclare global {\r\n\tinterface String {\r\n\t\tcapitalize(): string;\r\n\t}\r\n}\r\n\r\n@Injectable({\r\n\tprovidedIn: 'root',\r\n})\r\nexport class CoreService {\r\n\tdeviceID = localStorage.getItem('deviceID') || (typeof crypto?.randomUUID === 'function' ? crypto.randomUUID() : this.UUID());\r\n\r\n\tconstructor(@Inject(PLATFORM_ID) private platformId: boolean) {\r\n\t\tlocalStorage.setItem('deviceID', this.deviceID);\r\n\r\n\t\tthis.detectDevice();\r\n\t}\r\n\r\n\t/**\r\n\t * Generates a UUID (Universally Unique Identifier) version 4.\r\n\t *\r\n\t * This implementation uses `Math.random()` to generate random values,\r\n\t * making it suitable for general-purpose identifiers, but **not** for\r\n\t * cryptographic or security-sensitive use cases.\r\n\t *\r\n\t * The format follows the UUID v4 standard: `xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx`\r\n\t * where:\r\n\t * - `x` is a random hexadecimal digit (0–f)\r\n\t * - `4` indicates UUID version 4\r\n\t * - `y` is one of 8, 9, A, or B\r\n\t *\r\n\t * Example: `f47ac10b-58cc-4372-a567-0e02b2c3d479`\r\n\t *\r\n\t * @returns A string containing a UUID v4.\r\n\t */\r\n\tUUID(): string {\r\n\t\treturn 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (c: string) => {\r\n\t\t\tconst r = (Math.random() * 16) | 0;\r\n\t\t\tconst v = c === 'x' ? r : (r & 0x3) | 0x8;\r\n\t\t\treturn v.toString(16);\r\n\t\t});\r\n\t}\r\n\r\n\t/**\r\n\t * Converts an object to an array. Optionally holds keys instead of values.\r\n\t *\r\n\t * @param {any} obj - The object to be converted.\r\n\t * @param {boolean} [holder=false] - If true, the keys will be held in the array; otherwise, the values will be held.\r\n\t * @returns {any[]} The resulting array.\r\n\t */\r\n\tota(obj: any, holder: boolean = false): any[] {\r\n\t\tif (Array.isArray(obj)) return obj;\r\n\t\tif (typeof obj !== 'object' || obj === null) return [];\r\n\t\tconst arr = [];\r\n\t\tfor (const each in obj) {\r\n\t\t\tif (obj.hasOwnProperty(each) && (obj[each] || typeof obj[each] === 'number' || typeof obj[each] === 'boolean')) {\r\n\t\t\t\tif (holder) {\r\n\t\t\t\t\tarr.push(each);\r\n\t\t\t\t} else {\r\n\t\t\t\t\tarr.push(obj[each]);\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\t\treturn arr;\r\n\t}\r\n\r\n\t/**\r\n\t * Removes elements from `fromArray` that are present in `removeArray` based on a comparison field.\r\n\t *\r\n\t * @param {any[]} removeArray - The array of elements to remove.\r\n\t * @param {any[]} fromArray - The array from which to remove elements.\r\n\t * @param {string} [compareField='_id'] - The field to use for comparison.\r\n\t * @returns {any[]} The modified `fromArray` with elements removed.\r\n\t */\r\n\tsplice(removeArray: any[], fromArray: any[], compareField: string = '_id'): any[] {\r\n\t\tif (!Array.isArray(removeArray) || !Array.isArray(fromArray)) {\r\n\t\t\treturn fromArray;\r\n\t\t}\r\n\r\n\t\tconst removeSet = new Set(removeArray.map((item) => item[compareField]));\r\n\t\treturn fromArray.filter((item) => !removeSet.has(item[compareField]));\r\n\t}\r\n\r\n\t/**\r\n\t * Unites multiple _id values into a single unique _id.\r\n\t * The resulting _id is unique regardless of the order of the input _id values.\r\n\t *\r\n\t * @param {...string[]} args - The _id values to be united.\r\n\t * @returns {string} The unique combined _id.\r\n\t */\r\n\tids2id(...args: string[]): string {\r\n\t\targs.sort((a, b) => {\r\n\t\t\tif (Number(a.toString().substring(0, 8)) > Number(b.toString().substring(0, 8))) {\r\n\t\t\t\treturn 1;\r\n\t\t\t}\r\n\t\t\treturn -1;\r\n\t\t});\r\n\r\n\t\treturn args.join();\r\n\t}\r\n\r\n\t// After While\r\n\tprivate _afterWhile: Record<string, number> = {};\r\n\t/**\r\n\t * Delays the execution of a callback function for a specified amount of time.\r\n\t * If called again within that time, the timer resets.\r\n\t *\r\n\t * @param {string | object | (() => void)} doc - A unique identifier for the timer, an object to host the timer, or the callback function.\r\n\t * @param {() => void} [cb] - The callback function to execute after the delay.\r\n\t * @param {number} [time=1000] - The delay time in milliseconds.\r\n\t */\r\n\tafterWhile(doc: string | object | (() => void), cb?: () => void, time: number = 1000): void {\r\n\t\tif (typeof doc === 'function') {\r\n\t\t\tcb = doc as () => void;\r\n\t\t\tdoc = 'common';\r\n\t\t}\r\n\r\n\t\tif (typeof cb === 'function' && typeof time === 'number') {\r\n\t\t\tif (typeof doc === 'string') {\r\n\t\t\t\tclearTimeout(this._afterWhile[doc]);\r\n\t\t\t\tthis._afterWhile[doc] = window.setTimeout(cb, time);\r\n\t\t\t} else if (typeof doc === 'object') {\r\n\t\t\t\tclearTimeout((doc as { __afterWhile: number }).__afterWhile);\r\n\t\t\t\t(doc as { __afterWhile: number }).__afterWhile = window.setTimeout(cb, time);\r\n\t\t\t} else {\r\n\t\t\t\tconsole.warn('badly configured after while');\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n\r\n\t/**\r\n\t * Recursively copies properties from one object to another.\r\n\t * Handles nested objects, arrays, and Date instances appropriately.\r\n\t *\r\n\t * @param from - The source object from which properties are copied.\r\n\t * @param to - The target object to which properties are copied.\r\n\t */\r\n\tcopy(from: any, to: any) {\r\n\t\tfor (const each in from) {\r\n\t\t\tif (typeof from[each] !== 'object' || from[each] instanceof Date || Array.isArray(from[each]) || from[each] === null) {\r\n\t\t\t\tto[each] = from[each];\r\n\t\t\t} else {\r\n\t\t\t\tif (typeof to[each] !== 'object' || to[each] instanceof Date || Array.isArray(to[each]) || to[each] === null) {\r\n\t\t\t\t\tto[each] = {};\r\n\t\t\t\t}\r\n\r\n\t\t\t\tthis.copy(from[each], to[each]);\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n\r\n\t// Device management\r\n\tdevice = '';\r\n\t/**\r\n\t * Detects the device type based on the user agent.\r\n\t */\r\n\tdetectDevice(): void {\r\n\t\tconst userAgent = navigator.userAgent || navigator.vendor || (window as any).opera;\r\n\t\tif (/windows phone/i.test(userAgent)) {\r\n\t\t\tthis.device = 'Windows Phone';\r\n\t\t} else if (/android/i.test(userAgent)) {\r\n\t\t\tthis.device = 'Android';\r\n\t\t} else if (/iPad|iPhone|iPod/.test(userAgent) && !(window as any).MSStream) {\r\n\t\t\tthis.device = 'iOS';\r\n\t\t} else {\r\n\t\t\tthis.device = 'Web';\r\n\t\t}\r\n\t}\r\n\r\n\t/**\r\n\t * Checks if the device is a mobile device.\r\n\t * @returns {boolean} - Returns true if the device is a mobile device.\r\n\t */\r\n\tisMobile(): boolean {\r\n\t\treturn this.device === 'Windows Phone' || this.device === 'Android' || this.device === 'iOS';\r\n\t}\r\n\r\n\t/**\r\n\t * Checks if the device is a tablet.\r\n\t * @returns {boolean} - Returns true if the device is a tablet.\r\n\t */\r\n\tisTablet(): boolean {\r\n\t\treturn this.device === 'iOS' && /iPad/.test(navigator.userAgent);\r\n\t}\r\n\r\n\t/**\r\n\t * Checks if the device is a web browser.\r\n\t * @returns {boolean} - Returns true if the device is a web browser.\r\n\t */\r\n\tisWeb(): boolean {\r\n\t\treturn this.device === 'Web';\r\n\t}\r\n\r\n\t/**\r\n\t * Checks if the device is an Android device.\r\n\t * @returns {boolean} - Returns true if the device is an Android device.\r\n\t */\r\n\tisAndroid(): boolean {\r\n\t\treturn this.device === 'Android';\r\n\t}\r\n\r\n\t/**\r\n\t * Checks if the device is an iOS device.\r\n\t * @returns {boolean} - Returns true if the device is an iOS device.\r\n\t */\r\n\tisIos(): boolean {\r\n\t\treturn this.device === 'iOS';\r\n\t}\r\n\r\n\t// Version management\r\n\tversion = '1.0.0';\r\n\r\n\tappVersion = '';\r\n\r\n\tdateVersion = '';\r\n\r\n\t/**\r\n\t * Sets the combined version string based on appVersion and dateVersion.\r\n\t */\r\n\tsetVersion(): void {\r\n\t\tthis.version = this.appVersion || '';\r\n\r\n\t\tthis.version += this.version && this.dateVersion ? ' ' : '';\r\n\r\n\t\tthis.version += this.dateVersion || '';\r\n\t}\r\n\r\n\t/**\r\n\t * Sets the app version and updates the combined version string.\r\n\t *\r\n\t * @param {string} appVersion - The application version to set.\r\n\t */\r\n\tsetAppVersion(appVersion: string): void {\r\n\t\tthis.appVersion = appVersion;\r\n\r\n\t\tthis.setVersion();\r\n\t}\r\n\r\n\t/**\r\n\t * Sets the date version and updates the combined version string.\r\n\t *\r\n\t * @param {string} dateVersion - The date version to set.\r\n\t */\r\n\tsetDateVersion(dateVersion: string): void {\r\n\t\tthis.dateVersion = dateVersion;\r\n\r\n\t\tthis.setVersion();\r\n\t}\r\n\r\n\t// Signal management\r\n\tprivate _signals: Record<string, Subject<any>> = {};\r\n\r\n\t/**\r\n\t * Emits a signal, optionally passing data to the listeners.\r\n\t * @param signal - The name of the signal to emit.\r\n\t * @param data - Optional data to pass to the listeners.\r\n\t */\r\n\temit(signal: string, data?: any): void {\r\n\t\tif (!this._signals[signal]) {\r\n\t\t\tthis._signals[signal] = new Subject<any>();\r\n\t\t}\r\n\r\n\t\tthis._signals[signal].next(data);\r\n\t}\r\n\r\n\t/**\r\n\t * Returns an Observable that emits values when the specified signal is emitted.\r\n\t * Multiple components or services can subscribe to this Observable to be notified of the signal.\r\n\t * @param signal - The name of the signal to listen for.\r\n\t * @returns An Observable that emits when the signal is emitted.\r\n\t */\r\n\ton(signal: string): Observable<any> {\r\n\t\tif (!this._signals[signal]) {\r\n\t\t\tthis._signals[signal] = new Subject<any>();\r\n\t\t}\r\n\r\n\t\treturn this._signals[signal].asObservable();\r\n\t}\r\n\r\n\t/**\r\n\t * Completes the Subject for a specific signal, effectively stopping any future emissions.\r\n\t * This also unsubscribes all listeners for the signal.\r\n\t * @param signal - The name of the signal to stop.\r\n\t */\r\n\toff(signal: string): void {\r\n\t\tif (!this._signals[signal]) return;\r\n\t\tthis._signals[signal].complete();\r\n\t\tdelete this._signals[signal];\r\n\t}\r\n\r\n\t// Await management\r\n\tprivate _completed: Record<string, unknown> = {};\r\n\r\n\tprivate _completeResolvers: Record<string, ((doc: unknown) => void)[]> = {};\r\n\r\n\t/**\r\n\t * Marks a task as complete.\r\n\t * @param task - The task to mark as complete, identified by a string.\r\n\t */\r\n\tcomplete(task: string, document: unknown = true): void {\r\n\t\tthis._completed[task] = document;\r\n\r\n\t\tif (this._completeResolvers[task]) {\r\n\t\t\tthis._completeResolvers[task].forEach((resolve) => resolve(document));\r\n\r\n\t\t\tthis._completeResolvers[task] = [];\r\n\t\t}\r\n\t}\r\n\r\n\t/**\r\n\t * Waits for one or more tasks to be marked as complete.\r\n\t *\r\n\t * @param {string | string[]} tasks - The task or array of tasks to wait for.\r\n\t * @returns {Promise<unknown>} A promise that resolves when all specified tasks are complete.\r\n\t * - If a single task is provided, resolves with its completion result.\r\n\t * - If multiple tasks are provided, resolves with an array of results in the same order.\r\n\t *\r\n\t * @remarks\r\n\t * If any task is not yet completed, a resolver is attached. The developer is responsible for managing\r\n\t * resolver cleanup if needed. Resolvers remain after resolution and are not removed automatically.\r\n\t */\r\n\tonComplete(tasks: string | string[]): Promise<unknown> {\r\n\t\tif (typeof tasks === 'string') {\r\n\t\t\ttasks = [tasks];\r\n\t\t}\r\n\r\n\t\tif (this._isCompleted(tasks)) {\r\n\t\t\treturn Promise.resolve(tasks.length > 1 ? tasks.map((task) => this._completed[task]) : this._completed[tasks[0]]);\r\n\t\t}\r\n\r\n\t\treturn new Promise((resolve) => {\r\n\t\t\tfor (const task of tasks) {\r\n\t\t\t\tif (!this._completeResolvers[task]) {\r\n\t\t\t\t\tthis._completeResolvers[task] = [];\r\n\t\t\t\t}\r\n\r\n\t\t\t\tthis._completeResolvers[task].push(this._allCompleted(tasks, resolve));\r\n\t\t\t}\r\n\t\t});\r\n\t}\r\n\r\n\t/**\r\n\t * Returns a resolver function that checks if all given tasks are completed,\r\n\t * and if so, calls the provided resolve function with their results.\r\n\t *\r\n\t * @param {string[]} tasks - The list of task names to monitor for completion.\r\n\t * @param {(value: unknown) => void} resolve - The resolver function to call once all tasks are complete.\r\n\t * @returns {(doc: unknown) => void} A function that can be registered as a resolver for each task.\r\n\t *\r\n\t * @remarks\r\n\t * This function does not manage or clean up resolvers. It assumes the developer handles any potential duplicates or memory concerns.\r\n\t */\r\n\tprivate _allCompleted(tasks: string[], resolve: (value: unknown) => void): (doc: unknown) => void {\r\n\t\treturn (doc: unknown) => {\r\n\t\t\tif (this._isCompleted(tasks)) {\r\n\t\t\t\tresolve(tasks.length > 1 ? tasks.map((task) => this._completed[task]) : this._completed[tasks[0]]);\r\n\t\t\t}\r\n\t\t};\r\n\t}\r\n\r\n\t/**\r\n\t * Checks whether all specified tasks have been marked as completed.\r\n\t *\r\n\t * @param {string[]} tasks - The array of task names to check.\r\n\t * @returns {boolean} `true` if all tasks are completed, otherwise `false`.\r\n\t */\r\n\tprivate _isCompleted(tasks: string[]): boolean {\r\n\t\tfor (const task of tasks) {\r\n\t\t\tif (!this._completed[task]) {\r\n\t\t\t\treturn false;\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\treturn true;\r\n\t}\r\n\r\n\t/**\r\n\t * Checks if a task is completed.\r\n\t * @param task - The task to check, identified by a string.\r\n\t * @returns True if the task is completed, false otherwise.\r\n\t */\r\n\tcompleted(task: string): unknown {\r\n\t\treturn this._completed[task];\r\n\t}\r\n\r\n\t/**\r\n\t * Clears the completed state for a specific task.\r\n\t *\r\n\t * This removes the task from the internal `_completed` store,\r\n\t * allowing it to be awaited again in the future if needed.\r\n\t * It does not affect pending resolvers or trigger any callbacks.\r\n\t *\r\n\t * @param task - The task identifier to clear from completed state.\r\n\t */\r\n\tclearCompleted(task: string) {\r\n\t\tdelete this._completed[task];\r\n\t}\r\n\r\n\t// Locking management\r\n\tprivate _locked: Record<string, boolean> = {};\r\n\tprivate _unlockResolvers: Record<string, (() => void)[]> = {};\r\n\r\n\t/**\r\n\t * Locks a resource to prevent concurrent access.\r\n\t * @param which - The resource to lock, identified by a string.\r\n\t */\r\n\tlock(which: string): void {\r\n\t\tthis._locked[which] = true;\r\n\r\n\t\tif (!this._unlockResolvers[which]) {\r\n\t\t\tthis._unlockResolvers[which] = [];\r\n\t\t}\r\n\t}\r\n\r\n\t/**\r\n\t * Unlocks a resource, allowing access.\r\n\t * @param which - The resource to unlock, identified by a string.\r\n\t */\r\n\tunlock(which: string): void {\r\n\t\tthis._locked[which] = false;\r\n\r\n\t\tif (this._unlockResolvers[which]) {\r\n\t\t\tthis._unlockResolvers[which].forEach((resolve) => resolve());\r\n\r\n\t\t\tthis._unlockResolvers[which] = [];\r\n\t\t}\r\n\t}\r\n\r\n\t/**\r\n\t * Returns a Promise that resolves when the specified resource is unlocked.\r\n\t * @param which - The resource to watch for unlocking, identified by a string.\r\n\t * @returns A Promise that resolves when the resource is unlocked.\r\n\t */\r\n\tonUnlock(which: string): Promise<void> {\r\n\t\tif (!this._locked[which]) {\r\n\t\t\treturn Promise.resolve();\r\n\t\t}\r\n\r\n\t\treturn new Promise((resolve) => {\r\n\t\t\tif (!this._unlockResolvers[which]) {\r\n\t\t\t\tthis._unlockResolvers[which] = [];\r\n\t\t\t}\r\n\r\n\t\t\tthis._unlockResolvers[which].push(resolve);\r\n\t\t});\r\n\t}\r\n\r\n\t/**\r\n\t * Checks if a resource is locked.\r\n\t * @param which - The resource to check, identified by a string.\r\n\t * @returns True if the resource is locked, false otherwise.\r\n\t */\r\n\tlocked(which: string): boolean {\r\n\t\treturn !!this._locked[which];\r\n\t}\r\n\r\n\t// Linking management\r\n\tlinkCollections: string[] = [];\r\n\tlinkRealCollectionName: Record<string, string> = {};\r\n\tlinkIds: Record<string, Selectitem[]> = {};\r\n\r\n\taddLink(name: string, reset: () => Selectitem[], realName = ''): void {\r\n\t\tthis.linkCollections.push(name);\r\n\r\n\t\tthis.linkReal