@metadev/lux
Version:
Lux: Library with User Interface components for Angular.
1 lines • 305 kB
Source Map (JSON)
{"version":3,"file":"metadev-lux.mjs","sources":["../../../projects/lux/src/lib/helperFns.ts","../../../projects/lux/src/lib/autocomplete/autocomplete.component.ts","../../../projects/lux/src/lib/autocomplete/autocomplete.component.html","../../../projects/lux/src/lib/lang.ts","../../../projects/lux/src/lib/autocomplete-list/autocomplete-list.component.ts","../../../projects/lux/src/lib/autocomplete-list/autocomplete-list.component.html","../../../projects/lux/src/lib/breadcrumb/breadcrumb.component.ts","../../../projects/lux/src/lib/breadcrumb/breadcrumb.component.html","../../../projects/lux/src/lib/checkbox/checkbox.component.ts","../../../projects/lux/src/lib/checkbox/checkbox.component.html","../../../projects/lux/src/lib/datetime/datetime.component.ts","../../../projects/lux/src/lib/datetime/datetime.component.html","../../../projects/lux/src/lib/filter/filter.component.ts","../../../projects/lux/src/lib/filter/filter.component.html","../../../projects/lux/src/lib/geolocation/openlayer-loader.service.ts","../../../projects/lux/src/lib/map/map.component.ts","../../../projects/lux/src/lib/map/map.component.html","../../../projects/lux/src/lib/modal/modal-backdrop.ts","../../../projects/lux/src/lib/modal/modal-config.ts","../../../projects/lux/src/lib/modal/modal-ref.ts","../../../projects/lux/src/lib/modal/modal-dismiss-reasons.ts","../../../projects/lux/src/lib/modal/util.ts","../../../projects/lux/src/lib/modal/modal-window.ts","../../../projects/lux/src/lib/modal/modal-stack.ts","../../../projects/lux/src/lib/modal/modal.service.ts","../../../projects/lux/src/lib/geolocation/geolocation.service.ts","../../../projects/lux/src/lib/geolocation/geolocation.component.ts","../../../projects/lux/src/lib/geolocation/geolocation.component.html","../../../projects/lux/src/lib/input/regexp.service.ts","../../../projects/lux/src/lib/input/input.component.ts","../../../projects/lux/src/lib/input/input.component.html","../../../projects/lux/src/lib/tooltip/placement.ts","../../../projects/lux/src/lib/tooltip/tooltip.component.ts","../../../projects/lux/src/lib/tooltip/tooltop-content.ts","../../../projects/lux/src/lib/tooltip/tooltip.service.ts","../../../projects/lux/src/lib/tooltip/tooltip.directive.ts","../../../projects/lux/src/lib/pagination/pagination.component.ts","../../../projects/lux/src/lib/pagination/pagination.component.html","../../../projects/lux/src/lib/radiogroup/radiogroup.component.ts","../../../projects/lux/src/lib/radiogroup/radiogroup.component.html","../../../projects/lux/src/lib/select/select.component.ts","../../../projects/lux/src/lib/select/select.component.html","../../../projects/lux/src/lib/voice-recognition/voice-recognition.directive.ts","../../../projects/lux/src/lib/window/window.service.ts","../../../projects/lux/src/lib/lux.module.ts","../../../projects/lux/src/public-api.ts","../../../projects/lux/src/metadev-lux.ts"],"sourcesContent":["/* eslint-disable no-useless-escape */\r\n// undefined and null functions\r\n\r\nexport const exists = (value: any): boolean =>\r\n value !== null && value !== undefined;\r\n\r\nexport const hasValue = (value: any): boolean =>\r\n exists(value) && (typeof value === 'string' ? !isEmptyString(value) : true);\r\n\r\n// string functions\r\n\r\nexport const isEmptyString = (value: string): boolean => value.trim() === '';\r\n\r\nexport const isValidEmail = (value: string): boolean => {\r\n const re =\r\n /^[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*@(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$/;\r\n return re.test(String(value).toLowerCase().trim());\r\n};\r\n\r\nexport const isValidUrl = (value: string): boolean => {\r\n const pattern =\r\n // eslint-disable-next-line max-len\r\n /^((([a-z]+?:\\/\\/)?(((([a-z0-9]([a-z0-9-]*[a-z0-9])*)\\.)+[a-z]{2,})|((([0-9]{1,3}\\.){3}[0-9]{1,3}))|(localhost))(\\:[0-9]+)?))?((\\/[a-zA-Z0-9\\-_+=~.,:;%]+)*\\/?)((\\?|;)[a-zA-Z0-9\\-_+~.,:;%]+=[a-zA-Z0-9\\-_+~.,:;%]+(((&|;)[a-zA-Z0-9\\-_+~.,:;%]+=[a-zA-Z0-9\\-_+~.,:;%]+)*))?(#[a-zA-Z0-9\\-_+~.,:;%]+(=[a-zA-Z0-9\\*\\-_+~.,:;%]+)?)?$/;\r\n return pattern.test(value);\r\n};\r\n\r\nexport const isValidRelativeUrl = (value: string): boolean => {\r\n const pattern =\r\n // eslint-disable-next-line max-len\r\n /^((([a-z]+?:\\/\\/)?(((([a-z0-9]([a-z0-9-]*[a-z0-9])*)\\.)+[a-z]{2,})|((([0-9]{1,3}\\.){3}[0-9]{1,3}))|(localhost))(\\:[0-9]+)?)|([a-zA-Z0-9\\-_+=~.,:;%]+))?((\\/[a-zA-Z0-9\\-_+=~.,:;%]+)*\\/?)((\\?|;)[a-zA-Z0-9\\-_+~.,:;%]+=[a-zA-Z0-9\\-_+~.,:;%]+(((&|;)[a-zA-Z0-9\\-_+~.,:;%]+=[a-zA-Z0-9\\-_+~.,:;%]+)*))?(#[a-zA-Z0-9\\-_+~.,:;%]+(=[a-zA-Z0-9\\*\\-_+~.,:;%]+)?)?$/;\r\n return pattern.test(value);\r\n};\r\n\r\nexport const isValidColor = (value: string): boolean => {\r\n value = String(value).toLowerCase();\r\n // valid values for CSS color property, yet not valid colors by themselves\r\n if (\r\n value === 'currentcolor' ||\r\n value === 'inherit' ||\r\n value === 'initial' ||\r\n value === 'revert' ||\r\n value === 'unset'\r\n ) {\r\n return false;\r\n }\r\n return CSS.supports('color', value);\r\n};\r\n\r\n// date functions\r\n\r\nexport const isValidDate = (date: Date): boolean =>\r\n exists(date) ? !isNaN(date.getTime()) : false;\r\n\r\nexport const normalizeDate = (value: any): string => {\r\n if (typeof value === 'string' && value.length > 10) {\r\n return value.substr(0, 10);\r\n }\r\n return value ? value.toString() : null;\r\n};\r\n\r\nexport const addTimezoneOffset = (date: Date): Date => {\r\n if (!isValidDate(date)) {\r\n return date;\r\n } else {\r\n return new Date(date.getTime() - date.getTimezoneOffset() * 60000);\r\n }\r\n};\r\n\r\n// number functions\r\n\r\nexport const isValidNumber = (\r\n value: string | number | undefined | null\r\n): boolean => (hasValue(value) ? !Number.isNaN(Number(value)) : false);\r\n\r\nexport const numberOfDecimalDigits = (\r\n x: number | string\r\n): number | undefined => {\r\n if (isValidNumber(x)) {\r\n const xString = String(Number(x));\r\n if (xString === 'Infinity') {\r\n return 0;\r\n }\r\n const indexOfE = xString.indexOf('e');\r\n if (indexOfE >= 0) {\r\n return 0;\r\n }\r\n const indexOfDecimalPoint = xString.indexOf('.');\r\n if (indexOfDecimalPoint < 0) {\r\n return 0;\r\n } else {\r\n return xString.length - indexOfDecimalPoint - 1;\r\n }\r\n }\r\n return undefined;\r\n};\r\n\r\nexport const numberOfWholeDigits = (x: number | string): number | undefined => {\r\n if (isValidNumber(x)) {\r\n let xString = String(Number(x));\r\n if (xString.indexOf('-') === 0) {\r\n xString = xString.slice(1, xString.length);\r\n }\r\n if (xString === 'Infinity') {\r\n return Infinity;\r\n }\r\n if (xString.indexOf('0') === 0) {\r\n xString = xString.slice(1, xString.length);\r\n }\r\n const indexOfE = xString.indexOf('e');\r\n if (indexOfE >= 0) {\r\n return Number(xString.slice(indexOfE + 1, xString.length)) + 1;\r\n }\r\n const indexOfDecimalPoint = xString.indexOf('.');\r\n if (indexOfDecimalPoint < 0) {\r\n return xString.length;\r\n } else {\r\n return indexOfDecimalPoint;\r\n }\r\n }\r\n return undefined;\r\n};\r\n\r\nexport const roundToMultipleOf = (x: number, modulo: number): number => {\r\n const moduloString = String(modulo);\r\n // approximates the result\r\n // prone to inexactitude because of floating point arithmetic\r\n const approximation = Math.round(x / modulo) * modulo;\r\n const approximationString = String(approximation);\r\n // remove useless decimals\r\n const uselessDecimalsInApproximation =\r\n numberOfDecimalDigits(approximationString) -\r\n numberOfDecimalDigits(moduloString);\r\n const resultString = approximationString.slice(\r\n 0,\r\n approximationString.length - uselessDecimalsInApproximation\r\n );\r\n return Number(resultString);\r\n};\r\n\r\n// other functions\r\n\r\nexport const isInitialAndEmpty = (\r\n previousValue: any,\r\n newValue: any\r\n): boolean => {\r\n const isPrevArray = Array.isArray(previousValue);\r\n const isNewArray = Array.isArray(newValue);\r\n return !(\r\n (isPrevArray ? previousValue.length !== 0 : Boolean(previousValue)) ||\r\n (isNewArray ? newValue.length !== 0 : Boolean(newValue))\r\n );\r\n};\r\n","import {\r\n AfterViewInit,\r\n ChangeDetectorRef,\r\n Component,\r\n ElementRef,\r\n EventEmitter,\r\n forwardRef,\r\n inject,\r\n Input,\r\n OnDestroy,\r\n OnInit,\r\n Output,\r\n ViewChild\r\n} from '@angular/core';\r\nimport { DOCUMENT } from '@angular/common';\r\nimport {\r\n AbstractControl,\r\n ControlValueAccessor,\r\n FormsModule,\r\n NG_VALIDATORS,\r\n NG_VALUE_ACCESSOR,\r\n ValidationErrors,\r\n Validator\r\n} from '@angular/forms';\r\nimport { Observable, of } from 'rxjs';\r\nimport { debounceTime, first, map } from 'rxjs/operators';\r\nimport {\r\n DataSource,\r\n DataSourceItem,\r\n DecoratedDataSource,\r\n DecoratedDataSourceItem\r\n} from '../datasource';\r\nimport { isInitialAndEmpty } from '../helperFns';\r\n\r\nexport const LOST_FOCUS_TIME_WINDOW_MS = 200; // ms\r\n@Component({\r\n selector: 'lux-autocomplete',\r\n templateUrl: './autocomplete.component.html',\r\n styleUrls: ['./autocomplete.component.scss'],\r\n imports: [FormsModule],\r\n providers: [\r\n {\r\n provide: NG_VALUE_ACCESSOR,\r\n multi: true,\r\n useExisting: forwardRef(() => AutocompleteComponent)\r\n },\r\n {\r\n provide: NG_VALIDATORS,\r\n multi: true,\r\n useExisting: forwardRef(() => AutocompleteComponent)\r\n }\r\n ]\r\n})\r\nexport class AutocompleteComponent\r\n implements ControlValueAccessor, Validator, OnInit, AfterViewInit, OnDestroy\r\n{\r\n private cd = inject(ChangeDetectorRef);\r\n private document = inject(DOCUMENT);\r\n private appendToContainer: HTMLElement | null = null;\r\n\r\n static idCounter = 0;\r\n\r\n @ViewChild('i0', { static: true }) i0: ElementRef;\r\n @ViewChild('completeDiv', { static: true }) completeDiv: ElementRef;\r\n\r\n private _dataSource: DataSource<any, string>;\r\n private _placeholder: string;\r\n private _value: any;\r\n private lostFocusHandled = true;\r\n private t0 = 0;\r\n\r\n showSpinner = false;\r\n touched = false;\r\n completionList: DecoratedDataSource = [];\r\n showCompletion = false;\r\n focusItem: DataSourceItem<any, string>;\r\n\r\n @Output() valueChange = new EventEmitter<any>();\r\n @Output() dataSourceChange = new EventEmitter<DataSource<any, string>>();\r\n\r\n @Input() public inputId: string;\r\n @Input() public disabled: boolean | null = null;\r\n @Input() public readonly: boolean | null = null;\r\n @Input() label = '';\r\n /** If canAddNewValues, user can type items not present in the data-source. */\r\n @Input() canAddNewValues = false;\r\n /** After cleaning the selection should the completion list remain open or closed:\r\n * false: (default) close on filters, to clean a filter and select all.\r\n * true: keep open (when the action most likely is to pick another one).\r\n */\r\n @Input() keepOpenAfterDelete = false;\r\n /** Append dropdown to body or custom selector. Uses position absolute. */\r\n @Input() appendTo?: string;\r\n\r\n @Input()\r\n get value(): any {\r\n return this._value;\r\n }\r\n set value(v: any) {\r\n const initialAndEmpty = isInitialAndEmpty(this._value, v);\r\n this._value = v;\r\n this.onChange(v);\r\n this.completeLabel();\r\n if (!initialAndEmpty) {\r\n this.valueChange.emit(v);\r\n }\r\n }\r\n @Input()\r\n get dataSource(): DataSource<any, string> {\r\n return this._dataSource;\r\n }\r\n set dataSource(v: DataSource<any, string>) {\r\n this._dataSource = v;\r\n this.dataSourceChange.emit(v);\r\n }\r\n @Input() required = false;\r\n\r\n @Input()\r\n set placeholder(v: string) {\r\n this._placeholder = v;\r\n }\r\n get placeholder(): string {\r\n return this._placeholder ? this._placeholder : '';\r\n }\r\n\r\n @Input() resolveLabelsFunction?: (\r\n instance: any,\r\n keys: any[]\r\n ) => Observable<DataSource<any, string>> = undefined;\r\n @Input() populateFunction?: (\r\n instance: any,\r\n search: string\r\n ) => Observable<DataSource<any, string>> = undefined;\r\n @Input() instance: any;\r\n\r\n // ControlValueAccessor Interface\r\n onChange = (value): void => {};\r\n onTouched = (): void => {};\r\n\r\n writeValue(value: any): void {\r\n this.value = value;\r\n }\r\n\r\n registerOnChange(onChange: any): void {\r\n this.onChange = onChange;\r\n }\r\n registerOnTouched(onTouched: any): void {\r\n this.onTouched = onTouched;\r\n }\r\n markAsTouched(): void {\r\n if (!this.touched && !this.disabled) {\r\n this.onTouched();\r\n this.touched = true;\r\n }\r\n }\r\n\r\n setDisabledState(disabled: boolean): void {\r\n this.disabled = disabled;\r\n }\r\n // End ControlValueAccessor Interface\r\n\r\n // Validator interface\r\n registerOnValidatorChange(): void {}\r\n\r\n validate(control: AbstractControl): ValidationErrors | null {\r\n const value = control.value;\r\n if (\r\n this.required &&\r\n (value === '' || value === null || value === undefined)\r\n ) {\r\n return { required: { value, reason: 'Required field.' } };\r\n }\r\n return null;\r\n }\r\n // End of Validator interface\r\n\r\n clear(): void {\r\n this.value = null;\r\n this.toggleCompletion(this.keepOpenAfterDelete, '');\r\n }\r\n\r\n private completeLabel(): void {\r\n if (this.value) {\r\n if (this.dataSource) {\r\n this.label = findLabelForId(this.dataSource, this.value) || '';\r\n } else if (this.instance && this.resolveLabelsFunction) {\r\n this.resolveLabelsFunction(this.instance, [this.value])\r\n .pipe(debounceTime(1), first())\r\n .subscribe((data) => {\r\n this.label = findLabelForId(data, this.value) || '';\r\n });\r\n }\r\n } else {\r\n this.label = '';\r\n }\r\n }\r\n\r\n ngOnInit(): void {\r\n this.inputId = this.inputId\r\n ? this.inputId\r\n : `autocompletelist${AutocompleteComponent.idCounter++}`;\r\n this.completeLabel();\r\n }\r\n ngAfterViewInit(): void {\r\n this.setSameWidth();\r\n this.handleAppendTo();\r\n }\r\n\r\n ngOnDestroy(): void {\r\n this.removeDropdownFromContainer();\r\n }\r\n\r\n private handleAppendTo(): void {\r\n if (!this.appendTo) {\r\n return;\r\n }\r\n\r\n const container =\r\n this.appendTo === 'body'\r\n ? this.document.body\r\n : this.document.querySelector(this.appendTo);\r\n\r\n if (container) {\r\n this.appendToContainer = container as HTMLElement;\r\n this.appendToContainer.appendChild(this.completeDiv.nativeElement);\r\n }\r\n }\r\n\r\n private removeDropdownFromContainer(): void {\r\n if (this.appendToContainer && this.completeDiv) {\r\n const dropdown = this.completeDiv.nativeElement;\r\n if (dropdown.parentElement === this.appendToContainer) {\r\n this.appendToContainer.removeChild(dropdown);\r\n }\r\n }\r\n }\r\n\r\n private updateDropdownPosition(): void {\r\n if (!this.appendTo || !this.appendToContainer) {\r\n return;\r\n }\r\n\r\n const inputRect = this.i0.nativeElement.getBoundingClientRect();\r\n const dropdown = this.completeDiv.nativeElement;\r\n\r\n let top: number;\r\n let left: number;\r\n\r\n if (this.appendToContainer === this.document.body) {\r\n // For body, use viewport coordinates + scroll\r\n top = inputRect.bottom + window.scrollY;\r\n left = inputRect.left + window.scrollX;\r\n } else {\r\n // For custom containers, calculate relative position\r\n const containerRect = this.appendToContainer.getBoundingClientRect();\r\n top =\r\n inputRect.bottom -\r\n containerRect.top +\r\n this.appendToContainer.scrollTop;\r\n left =\r\n inputRect.left - containerRect.left + this.appendToContainer.scrollLeft;\r\n }\r\n\r\n dropdown.style.top = `${top}px`;\r\n dropdown.style.left = `${left}px`;\r\n dropdown.style.width = `${inputRect.width}px`;\r\n }\r\n onInputResized(): void {\r\n this.setSameWidth();\r\n }\r\n private setSameWidth(): void {\r\n if (this.appendTo) {\r\n this.updateDropdownPosition();\r\n } else {\r\n const width = this.i0.nativeElement.getBoundingClientRect().width;\r\n this.completeDiv.nativeElement.style.width = `${width}px`;\r\n }\r\n }\r\n\r\n onKeydown(event: KeyboardEvent, label: string): void {\r\n switch (event.key) {\r\n case 'Tab':\r\n if (label) {\r\n this.pickSelectionOrFirstMatch(label);\r\n }\r\n this.showCompletion = false;\r\n break;\r\n }\r\n this.markAsTouched();\r\n }\r\n onKeypress(event: KeyboardEvent, label: string): void {\r\n switch (event.key) {\r\n case 'Intro':\r\n case 'Enter':\r\n this.pickSelectionOrFirstMatch(label);\r\n event.preventDefault();\r\n break;\r\n }\r\n this.markAsTouched();\r\n }\r\n onKeyup(event: KeyboardEvent, label: string): void {\r\n switch (event.key) {\r\n case 'PageDown':\r\n this.focusOnNext(5);\r\n event.preventDefault();\r\n break;\r\n case 'ArrowDown':\r\n this.focusOnNext(1);\r\n event.preventDefault();\r\n break;\r\n case 'PageUp':\r\n this.focusOnPrevious(5);\r\n event.preventDefault();\r\n break;\r\n case 'ArrowUp':\r\n this.focusOnPrevious(1);\r\n event.preventDefault();\r\n break;\r\n case 'Escape':\r\n this.complete(null);\r\n event.preventDefault();\r\n break;\r\n case 'Intro':\r\n case 'Enter':\r\n event.preventDefault();\r\n break;\r\n default:\r\n this.showCompletionList(label);\r\n // event.preventDefault();\r\n }\r\n this.markAsTouched();\r\n }\r\n private focusOnNext(offset: number): void {\r\n const list = this.completionList || [];\r\n const index = list.findIndex(\r\n (it) => this.focusItem && it.key === this.focusItem.key\r\n );\r\n const indexNext =\r\n index !== -1 && list.length > index + offset\r\n ? index + offset\r\n : list.length - 1;\r\n const next = list[indexNext];\r\n this.focusItem = next;\r\n this.ensureItemVisible(index);\r\n }\r\n private focusOnPrevious(offset: number): void {\r\n const list = this.completionList || [];\r\n const index = list.findIndex(\r\n (it) => this.focusItem && it.key === this.focusItem.key\r\n );\r\n const indexPrevious = index !== -1 && index > offset ? index - offset : 0;\r\n const next = list[indexPrevious];\r\n this.focusItem = next;\r\n this.ensureItemVisible(index);\r\n }\r\n onLostFocus(label: string): void {\r\n this.lostFocusHandled = false;\r\n this.t0 = performance.now();\r\n // console.log('Init LostFocus');\r\n setTimeout(() => {\r\n // needs to postpone actions some milliseconds to verify if\r\n // lost focus was followed by a list selection -> then cancel\r\n // if not -> make side effect\r\n if (!this.lostFocusHandled) {\r\n // console.log(\r\n // 'Lost focus 2',\r\n // this.lostFocusHandled,\r\n // 'SIDE EFFECT',\r\n // performance.now() - this.t0,\r\n // 'label:',\r\n // label\r\n // );\r\n if (label && this.label !== label) {\r\n this.pickSelectionOrFirstMatch(label);\r\n } else {\r\n this.lostFocusHandled = true;\r\n }\r\n this.toggleCompletion(false, label);\r\n } else {\r\n // do nothing (list selection took place)\r\n // console.log(\r\n // 'onlost focus 2',\r\n // this.lostFocusHandled,\r\n // 'nothing',\r\n // performance.now() - this.t0\r\n // );\r\n }\r\n }, LOST_FOCUS_TIME_WINDOW_MS);\r\n }\r\n complete(item: DataSourceItem<Record<string, unknown>, string>): void {\r\n if (!this.lostFocusHandled) {\r\n this.lostFocusHandled = true; // prevent a previous lostFocus to trigger a side effect\r\n const ellapsed = performance.now() - this.t0;\r\n if (ellapsed > LOST_FOCUS_TIME_WINDOW_MS) {\r\n console.warn(\r\n 'complete. lostfocus->click timeout of ',\r\n LOST_FOCUS_TIME_WINDOW_MS,\r\n 'ms exceed: ',\r\n performance.now() - this.t0,\r\n ' ms'\r\n );\r\n }\r\n // console.log(\r\n // 'complete. set to true. CANCELED side effect',\r\n // performance.now() - this.t0\r\n // );\r\n }\r\n if (item !== null) {\r\n this.value = item.key;\r\n this.label = item.label;\r\n } else {\r\n this.value = null;\r\n this.label = '';\r\n }\r\n this.toggleCompletion(false, null);\r\n this.markAsTouched();\r\n }\r\n toggleCompletion(show: boolean, label: string): void {\r\n if (show && !this.disabled) {\r\n this.i0.nativeElement.focus();\r\n if (this.appendTo) {\r\n this.updateDropdownPosition();\r\n }\r\n this.showCompletionList(label);\r\n } else {\r\n this.showCompletion = false;\r\n if (this.canAddNewValues) {\r\n this.syncCustomValue(this.label);\r\n return;\r\n }\r\n }\r\n this.cd.markForCheck();\r\n }\r\n\r\n get selectedOption(): string {\r\n const index = this.completionList.findIndex(\r\n (i) => i.key === this.focusItem.key\r\n );\r\n if (index === -1 || !this.focusItem) {\r\n return null;\r\n }\r\n return `${this.inputId}_${index}`;\r\n }\r\n\r\n private ensureItemVisible(index: number): void {\r\n const target = this.completeDiv.nativeElement.querySelectorAll('li')[index];\r\n if (target) {\r\n target.scrollIntoView({ block: 'center' });\r\n }\r\n }\r\n private syncCustomValue(text: string): void {\r\n this.value = text;\r\n this.label = text;\r\n }\r\n /** Pick selection based on text filtering (ingnores drowdown state) */\r\n private pickSelectionOrFirstMatch(text: string): void {\r\n if (this.canAddNewValues) {\r\n this.syncCustomValue(text);\r\n return;\r\n }\r\n const focusIndex = this.completionList.findIndex(\r\n (it) => this.focusItem && it.key === this.focusItem.key\r\n );\r\n if (\r\n this.showCompletion &&\r\n focusIndex > 0 &&\r\n this.focusItem &&\r\n this.focusItem.label\r\n ) {\r\n if (text === this.focusItem.label && this.focusItem.key === this.value) {\r\n // do nothing if value does not change & close dropdow\r\n this.showCompletion = false;\r\n return;\r\n }\r\n // complete selected using selected item on drowdown\r\n this.complete(this.focusItem);\r\n return;\r\n }\r\n const source = (text || '').trim();\r\n if (!source) {\r\n this.showCompletion = false;\r\n // select null value\r\n if (this.value !== null) {\r\n this.value = null;\r\n }\r\n return;\r\n }\r\n this.completionList = [];\r\n this.computeCompletionList(source).subscribe((suggestions) => {\r\n const candidate =\r\n suggestions && suggestions.length > 0 ? suggestions[0] : null;\r\n this.complete(candidate);\r\n });\r\n }\r\n public showCompletionList(text: string): void {\r\n this.setSameWidth();\r\n const useSpinner = this.hasExternalDataSource();\r\n this.spinnerVisibility(useSpinner, true);\r\n setTimeout(() => {\r\n // for spinner to be shown\r\n this.computeCompletionList(text).subscribe({\r\n next: (cl) => {\r\n this.completionList = cl;\r\n this.focusItem = selectElement(this.completionList, text);\r\n this.showCompletion = true;\r\n this.spinnerVisibility(useSpinner, false);\r\n },\r\n error: () => {\r\n this.spinnerVisibility(useSpinner, false);\r\n },\r\n complete: () => {\r\n this.spinnerVisibility(useSpinner, false);\r\n }\r\n });\r\n }, 1);\r\n }\r\n\r\n private spinnerVisibility(useSpinner: boolean, value: boolean): void {\r\n if (useSpinner) {\r\n this.showSpinner = value;\r\n }\r\n }\r\n private hasExternalDataSource(): boolean {\r\n return !this.dataSource && !!this.instance && !!this.populateFunction;\r\n }\r\n private computeCompletionList(text: string): Observable<DecoratedDataSource> {\r\n const searchText = (text || '').toLowerCase();\r\n if (this.dataSource) {\r\n const ds = (this.dataSource || [])\r\n .filter((it) => ignoreAccentsInclude(it.label, searchText))\r\n .sort((a, b) => a.label.localeCompare(b.label));\r\n return of(decorateDataSource(ds, searchText));\r\n } else if (this.instance && this.populateFunction) {\r\n return this.populateFunction(this.instance, searchText).pipe(\r\n debounceTime(1),\r\n first(),\r\n map((ds) => {\r\n const dsFiltered = ds\r\n .filter((it) => ignoreAccentsInclude(it.label, searchText))\r\n .sort((a, b) => a.label.localeCompare(b.label));\r\n return decorateDataSource(dsFiltered, searchText);\r\n })\r\n );\r\n } else {\r\n return of([]);\r\n }\r\n }\r\n}\r\n\r\n/** Returns true if text includes substring. No accents considered. Ignorecase */\r\nconst ignoreAccentsInclude = (text: string, substring: string): boolean => {\r\n text = normalizedString(text).toLowerCase();\r\n substring = normalizedString(substring).toLowerCase();\r\n return text.includes(substring);\r\n};\r\n\r\n/** Returns a normalized string with no accents: used for comparison */\r\nconst normalizedString = (a: string): string =>\r\n (a || '').normalize('NFD').replace(/[\\u0300-\\u036f]/g, '');\r\n\r\nconst decorateDataSource = (\r\n dataSource: DataSource<any, string>,\r\n subString: string\r\n): DecoratedDataSource => dataSource.map((it) => decorateItem(it, subString));\r\n\r\nconst decorateItem = (\r\n item: DataSourceItem<any, string>,\r\n tx: string\r\n): DecoratedDataSourceItem => {\r\n const index = normalizedString(item.label)\r\n .toLowerCase()\r\n .indexOf(normalizedString(tx).toLowerCase());\r\n const labelPrefix = index === -1 ? item.label : item.label.substr(0, index);\r\n const labelMatch = index === -1 ? '' : item.label.substr(index, tx.length);\r\n const labelPostfix = index === -1 ? '' : item.label.substr(index + tx.length);\r\n const newItem: DecoratedDataSourceItem = {\r\n ...item,\r\n labelPrefix,\r\n labelMatch,\r\n labelPostfix\r\n };\r\n return newItem;\r\n};\r\n\r\nconst findLabelForId = (data: DataSource<any, string>, id: any): string => {\r\n const found = data.find((it) => it.key === id);\r\n return found ? found.label : null;\r\n};\r\n\r\nexport const selectElement = (\r\n completionList: DecoratedDataSource,\r\n label: string\r\n): DataSourceItem<any, string> => {\r\n label = (label || '').toLowerCase();\r\n if (!completionList || completionList.length === 0) {\r\n return null;\r\n }\r\n if (completionList.length === 1) {\r\n return completionList[0];\r\n }\r\n const found = completionList.find((it) => it.label.toLowerCase() === label);\r\n return found || completionList[0];\r\n};\r\n","<div class=\"lux-autocomplete\" (blur)=\"toggleCompletion(false, i0.value)\">\r\n <div class=\"lux-autocomplete-box\">\r\n <input\r\n #i0\r\n [id]=\"inputId\"\r\n [(ngModel)]=\"label\"\r\n [placeholder]=\"placeholder\"\r\n [attr.disabled]=\"disabled || null\"\r\n [attr.readonly]=\"readonly || null\"\r\n (keydown)=\"onKeydown($event, i0.value)\"\r\n (keypress)=\"onKeypress($event, i0.value)\"\r\n (keyup)=\"onKeyup($event, i0.value)\"\r\n (blur)=\"onLostFocus(i0.value)\"\r\n (focus)=\"toggleCompletion(true, i0.value)\"\r\n (click)=\"toggleCompletion(true, i0.value)\"\r\n (resized)=\"onInputResized()\"\r\n (window:resize)=\"onInputResized()\"\r\n role=\"combobox\"\r\n aria-autocomplete=\"list\"\r\n [attr.aria-expanded]=\"showCompletion\"\r\n aria-haspopup=\"true\"\r\n attr.aria-owns=\"{{ inputId + '_list' }}\"\r\n [attr.aria-activedescendant]=\"selectedOption\"\r\n />\r\n @if (canAddNewValues) {\r\n <div class=\"icon-suggestions\"></div>\r\n } @if (showSpinner) {\r\n <div class=\"icon-spinner\"></div>\r\n } @if (!disabled && i0.value && !showCompletion) {\r\n <button\r\n type=\"button\"\r\n class=\"icon-clear\"\r\n aria-label=\"Clear\"\r\n (click)=\"clear()\"\r\n ></button>\r\n } @else {\r\n <div\r\n class=\"icon-dropdown\"\r\n (click)=\"toggleCompletion(!showCompletion, i0.value)\"\r\n ></div>\r\n }\r\n </div>\r\n <div\r\n #completeDiv\r\n [class.showCompletion]=\"showCompletion\"\r\n [class.lux-completion-list-appended]=\"appendTo\"\r\n class=\"lux-completion-list\"\r\n id=\"{{ inputId + '_list' }}\"\r\n >\r\n <ul>\r\n @for (item of completionList; track item.key; let index = $index) {\r\n <li\r\n id=\"{{ inputId + '_' + index }}\"\r\n class=\"lux-completion-item\"\r\n [class.selected]=\"focusItem && item.key === focusItem.key\"\r\n (click)=\"complete(item)\"\r\n [attr.aria-label]=\"item.label\"\r\n >\r\n <span class=\"preserve-white-space\">{{ item.labelPrefix }}</span>\r\n <span class=\"preserve-white-space bold\">{{ item.labelMatch }}</span>\r\n <span class=\"preserve-white-space\">{{ item.labelPostfix }}</span>\r\n </li>\r\n }\r\n </ul>\r\n </div>\r\n</div>\r\n","/** Language detector based on Navigator preferences */\nexport const languageDetector = (): string => {\n const lang = navigator.language.split('-')[0];\n if (lang === 'es' || lang === 'en') {\n return lang;\n }\n return 'en'; // default\n};\n","import {\r\n Component,\r\n EventEmitter,\r\n forwardRef,\r\n Input,\r\n OnInit,\r\n Output,\r\n ViewChild\r\n} from '@angular/core';\r\nimport {\r\n AbstractControl,\r\n ControlValueAccessor,\r\n NG_VALIDATORS,\r\n NG_VALUE_ACCESSOR,\r\n ValidationErrors,\r\n Validator\r\n} from '@angular/forms';\r\nimport { Observable } from 'rxjs';\r\nimport { first } from 'rxjs/operators';\r\nimport { AutocompleteComponent } from '../autocomplete/autocomplete.component';\r\nimport { DataSource } from '../datasource';\r\nimport { isInitialAndEmpty } from '../helperFns';\r\nimport { languageDetector } from '../lang';\r\n\r\n@Component({\r\n selector: 'lux-autocomplete-list',\r\n templateUrl: './autocomplete-list.component.html',\r\n styleUrls: ['./autocomplete-list.component.scss'],\r\n imports: [AutocompleteComponent],\r\n providers: [\r\n {\r\n provide: NG_VALUE_ACCESSOR,\r\n multi: true,\r\n useExisting: forwardRef(() => AutocompleteListComponent)\r\n },\r\n {\r\n provide: NG_VALIDATORS,\r\n multi: true,\r\n useExisting: forwardRef(() => AutocompleteListComponent)\r\n }\r\n ]\r\n})\r\nexport class AutocompleteListComponent\r\n implements ControlValueAccessor, Validator, OnInit\r\n{\r\n static idCounter = 0;\r\n\r\n @ViewChild('auto') auto!: AutocompleteListComponent;\r\n\r\n literals = {\r\n en: {\r\n placeholder: 'new item',\r\n deleteLabelTemplate: 'Delete <<label>>',\r\n addMessage: 'Add'\r\n },\r\n es: {\r\n placeholder: 'nuevo elemento',\r\n deleteLabelTemplate: 'Eliminar <<label>>',\r\n addMessage: 'Añadir'\r\n }\r\n };\r\n\r\n public internalDataSource: DataSource<any, string> = [];\r\n private autoPopulate = false;\r\n\r\n private _value: any[] = [];\r\n @Input()\r\n set value(val: any[]) {\r\n if (val === this._value) {\r\n return;\r\n }\r\n const initialAndEmpty = isInitialAndEmpty(this._value, val);\r\n this._value = val;\r\n this.ensureLabelsForIds();\r\n this.populateWith('');\r\n this.onChange(this._value);\r\n if (!initialAndEmpty) {\r\n this.valueChange.emit(this._value);\r\n }\r\n }\r\n get value(): any[] {\r\n return this._value;\r\n }\r\n labels: string[] = [];\r\n newEntry: any;\r\n canAdd = false;\r\n touched = false;\r\n\r\n private _lang = languageDetector();\r\n @Input()\r\n get lang(): string {\r\n return this._lang;\r\n }\r\n set lang(l: string) {\r\n if (l === this._lang) {\r\n return;\r\n }\r\n if (Object.keys(this.literals).includes(l)) {\r\n this._lang = l;\r\n } else {\r\n this._lang = 'en';\r\n }\r\n }\r\n\r\n @Input() inputId: string;\r\n @Input() dataSource: DataSource<any, any> = [];\r\n @Input() placeholder?: string;\r\n @Input() disabled = false;\r\n @Input() deleteLabelTemplate?: string;\r\n @Input() addMessage?: string;\r\n @Input() required = false;\r\n\r\n @Input() resolveLabelsFunction?: (\r\n instance: any,\r\n ids: any[]\r\n ) => Observable<DataSource<any, string>> = undefined;\r\n @Input() populateFunction?: (\r\n instance: any,\r\n search: string\r\n ) => Observable<DataSource<any, string>> = undefined;\r\n @Input() instance: any;\r\n\r\n @Output() valueChange = new EventEmitter<any[]>();\r\n\r\n // ControlValueAccessor Interface\r\n onChange = (value): void => {};\r\n onTouched = (): void => {};\r\n\r\n writeValue(value: any): void {\r\n this.value = value;\r\n }\r\n\r\n registerOnChange(onChange: any): void {\r\n this.onChange = onChange;\r\n }\r\n registerOnTouched(onTouched: any): void {\r\n this.onTouched = onTouched;\r\n }\r\n markAsTouched(): void {\r\n if (!this.touched && !this.disabled) {\r\n this.onTouched();\r\n this.touched = true;\r\n }\r\n }\r\n setDisabledState(disabled: boolean): void {\r\n this.disabled = disabled;\r\n }\r\n // End ControlValueAccessor Interface\r\n\r\n // Validator interface\r\n registerOnValidatorChange(): void {}\r\n\r\n validate(control: AbstractControl): ValidationErrors | null {\r\n const value = control.value;\r\n if (\r\n this.required &&\r\n (value === '' || value === null || value === undefined)\r\n ) {\r\n return { required: { value, reason: 'Required field.' } };\r\n }\r\n return null;\r\n }\r\n // End of Validator interface\r\n\r\n ngOnInit(): void {\r\n this.inputId = this.inputId\r\n ? this.inputId\r\n : `autocompletelist${AutocompleteListComponent.idCounter++}`;\r\n this.autoPopulate =\r\n !!this.resolveLabelsFunction && !!this.populateFunction && this.instance;\r\n this.ensureLabelsForIds();\r\n }\r\n ensureLabelsForIds(): void {\r\n if (this.autoPopulate && this.resolveLabelsFunction) {\r\n this.resolveLabelsFunction(this.instance, this._value)\r\n .pipe(first())\r\n .subscribe((data) => {\r\n const res: string[] = [];\r\n (this._value || []).map((id) => {\r\n const found = data.find((it) => it.key === id);\r\n if (found) {\r\n res.push(found.label);\r\n } else {\r\n res.push('(unset)');\r\n }\r\n });\r\n this.labels = res;\r\n });\r\n } else if (this.dataSource) {\r\n const res: string[] = [];\r\n (this._value || []).map((id) => {\r\n const found = this.dataSource.find((it) => it.key === id);\r\n if (found) {\r\n res.push(found.label);\r\n } else {\r\n res.push('(unset)');\r\n }\r\n });\r\n this.labels = res;\r\n } else {\r\n this.labels = this._value.map((it) => (it ? it.toString() : '(unset)'));\r\n }\r\n }\r\n removeAt(index: number): void {\r\n if (this._value.length > index) {\r\n const key = this._value.splice(index, 1)[0];\r\n const label = this.labels.splice(index, 1)[0];\r\n this.internalDataSource.push({ key, label });\r\n }\r\n this.markAsTouched();\r\n }\r\n onValueChange(): void {\r\n this.updateCanAdd();\r\n }\r\n onNewEntryChange(event: KeyboardEvent, auto: AutocompleteComponent): void {\r\n if (event.key === 'Enter' && !!auto.value) {\r\n this.addNew(auto);\r\n } else if (event.key === 'Delete' || event.key === 'Backspace') {\r\n // todo\r\n } else {\r\n this.populateWith(auto.label + event.key);\r\n }\r\n this.updateCanAdd();\r\n }\r\n populateWith(searchText: string): void {\r\n if (this.autoPopulate && this.populateFunction && this.instance) {\r\n this.populateFunction(this.instance, searchText)\r\n .pipe(first())\r\n .subscribe((data) => {\r\n this.internalDataSource = data.filter(\r\n (it) => !(this._value || []).includes(it.key)\r\n );\r\n });\r\n } else if (this.dataSource) {\r\n this.internalDataSource = this.dataSource.filter(\r\n (it) => !(this._value || []).includes(it.key)\r\n );\r\n }\r\n }\r\n updateCanAdd(): void {\r\n this.canAdd =\r\n !this.disabled &&\r\n this.auto &&\r\n !!this.auto.value && // has value\r\n !this.value.find((it) => it === this.auto.value); // not already in\r\n }\r\n\r\n addNew(auto: AutocompleteComponent): void {\r\n this.value.push(auto.value);\r\n this.ensureLabelsForIds();\r\n this.newEntry = '';\r\n this.internalDataSource = this.internalDataSource.filter(\r\n (it) => !this._value.includes(it.key)\r\n );\r\n this.markAsTouched();\r\n }\r\n\r\n getDeleteMessage(label: string): string {\r\n return (\r\n this.deleteLabelTemplate ?? this.literals[this.lang].deleteLabelTemplate\r\n ).replace('<<label>>', label);\r\n }\r\n}\r\n","<div [id]=\"inputId\">\r\n <ul>\r\n @for (label of labels; track label; let index = $index) {\r\n <li>\r\n <span>{{ label }}</span>\r\n @if (!disabled) {\r\n <button\r\n class=\"remove-item\"\r\n (click)=\"removeAt(index)\"\r\n attr.aria-label=\"{{ getDeleteMessage(label) }}\"\r\n ></button>\r\n }\r\n </li>\r\n }\r\n </ul>\r\n @if (!disabled) {\r\n <div class=\"new-entry\">\r\n <lux-autocomplete\r\n #auto\r\n [dataSource]=\"internalDataSource\"\r\n [placeholder]=\"placeholder ?? literals[lang].placeholder\"\r\n [(value)]=\"newEntry\"\r\n (valueChange)=\"onValueChange()\"\r\n (keypress)=\"onNewEntryChange($event, auto)\"\r\n >\r\n </lux-autocomplete>\r\n @if (canAdd) {\r\n <button\r\n type=\"button\"\r\n class=\"add-item\"\r\n aria-label=\"addMessage ?? literals[lang].addMessage\"\r\n (click)=\"addNew(auto)\"\r\n ></button>\r\n }\r\n </div>\r\n }\r\n</div>\r\n","\r\nimport { Component, OnDestroy, OnInit, inject } from '@angular/core';\r\nimport {\r\n ActivatedRoute,\r\n ActivatedRouteSnapshot,\r\n NavigationEnd,\r\n Router,\r\n RouterModule\r\n} from '@angular/router';\r\nimport { Subscription } from 'rxjs';\r\nimport { filter } from 'rxjs/operators';\r\n\r\nexport interface BreadcrumbItem {\r\n label: string;\r\n url: string;\r\n}\r\n@Component({\r\n selector: 'lux-breadcrumb',\r\n imports: [RouterModule],\r\n templateUrl: './breadcrumb.component.html',\r\n styleUrls: ['./breadcrumb.component.scss']\r\n})\r\nexport class LuxBreadcrumbComponent implements OnInit, OnDestroy {\r\n private route = inject(Router);\r\n private activedRoute = inject(ActivatedRoute);\r\n\r\n public breadcrumbs: BreadcrumbItem[];\r\n private subs: Subscription[] = [];\r\n public imagePath = '../assets/img/arrow-forward.svg';\r\n\r\n ngOnInit(): void {\r\n this.subs.push(\r\n this.route.events\r\n .pipe(filter((event) => event instanceof NavigationEnd))\r\n .subscribe((_) => {\r\n this.breadcrumbs = [];\r\n this.addBreadcrumbs(this.activedRoute.snapshot.root, true, null);\r\n })\r\n );\r\n }\r\n\r\n ngOnDestroy(): void {\r\n this.subs.forEach((s) => s.unsubscribe());\r\n this.subs = [];\r\n }\r\n\r\n private addBreadcrumbs(\r\n activedRouteSnapshot: ActivatedRouteSnapshot,\r\n isRoot: boolean,\r\n urlPrefix: string\r\n ): void {\r\n const routeConfig = activedRouteSnapshot.routeConfig;\r\n let url = urlPrefix || '';\r\n url += routeConfig ? '/' + this.getUrl(activedRouteSnapshot) : '';\r\n const label = routeConfig\r\n ? this.getLabel(activedRouteSnapshot)\r\n : isRoot\r\n ? 'Home'\r\n : '';\r\n if (label && url !== '/') {\r\n const breadcrumb = { label, url };\r\n this.breadcrumbs.push(breadcrumb);\r\n }\r\n if (activedRouteSnapshot.children.length) {\r\n this.addBreadcrumbs(activedRouteSnapshot.children[0], false, url);\r\n }\r\n }\r\n\r\n private getUrl(activedRouteSnapshot: ActivatedRouteSnapshot): string {\r\n if (!activedRouteSnapshot.url[0]) {\r\n return '';\r\n }\r\n const id = activedRouteSnapshot.params.id;\r\n return id\r\n ? `${activedRouteSnapshot.url[0]}/${id}`\r\n : activedRouteSnapshot.routeConfig.path || '';\r\n }\r\n\r\n private getLabel(activedRouteSnapshot: ActivatedRouteSnapshot): string {\r\n const routeConfig = activedRouteSnapshot.routeConfig;\r\n if (!routeConfig) {\r\n return null;\r\n }\r\n if (activedRouteSnapshot.data && activedRouteSnapshot.data.title) {\r\n const data = activedRouteSnapshot.data;\r\n if (data.objectTitle) {\r\n return `${data.title} ${data.objectTitle}`;\r\n } else {\r\n return data.title;\r\n }\r\n }\r\n return activedRouteSnapshot.params.id || routeConfig.path;\r\n }\r\n}\r\n","<ul class=\"lux-breadcrumb\">\r\n @for (breadcrumb of breadcrumbs; track breadcrumb.url; let isLast = $last; let\r\n isFirst = $first) {\r\n <li class=\"lux-breadcrumb-item\">\r\n <a [class.first]=\"isFirst\" routerLink=\"{{ breadcrumb.url }}\">{{\r\n breadcrumb.label\r\n }}</a>\r\n @if (!isLast) {\r\n <div class=\"lux-breadcrumb-separator\"></div>\r\n }\r\n </li>\r\n }\r\n</ul>\r\n","import {\r\n AfterViewInit,\r\n ChangeDetectorRef,\r\n Component,\r\n ElementRef,\r\n EventEmitter,\r\n forwardRef,\r\n inject,\r\n Input,\r\n OnInit,\r\n Output,\r\n ViewChild\r\n} from '@angular/core';\r\nimport { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';\r\nimport { isInitialAndEmpty } from '../helperFns';\r\nimport { languageDetector } from '../lang';\r\n\r\nconst KEY_SPACE = ' ';\r\n\r\n@Component({\r\n selector: 'lux-checkbox',\r\n templateUrl: './checkbox.component.html',\r\n styleUrls: ['./checkbox.component.scss'],\r\n imports: [],\r\n providers: [\r\n {\r\n provide: NG_VALUE_ACCESSOR,\r\n multi: true,\r\n useExisting: forwardRef(() => CheckboxComponent)\r\n }\r\n ]\r\n})\r\nexport class CheckboxComponent\r\n implements ControlValueAccessor, OnInit, AfterViewInit\r\n{\r\n static idCounter = 0;\r\n\r\n @ViewChild('ck', { static: false }) ck: ElementRef;\r\n\r\n private _lang = languageDetector();\r\n @Input()\r\n set lang(l: string) {\r\n if (l === this._lang) {\r\n return;\r\n }\r\n if (Object.keys(this.literals).includes(l)) {\r\n this._lang = l;\r\n } else {\r\n this._lang = 'en';\r\n }\r\n }\r\n get lang(): string {\r\n return this._lang;\r\n }\r\n private internalValue: boolean = false;\r\n @Input()\r\n get value(): boolean {\r\n return this.internalValue;\r\n }\r\n set value(v: boolean) {\r\n if (this.internalValue === v) {\r\n return;\r\n }\r\n const initialAndEmpty = isInitialAndEmpty(this.internalValue, v);\r\n this.internalValue = v;\r\n this.syncModel();\r\n this.onChange(v);\r\n if (!initialAndEmpty) {\r\n this.valueChange.emit(v);\r\n }\r\n this.cdr.markForCheck();\r\n }\r\n\r\n get tabindexValue(): string {\r\n return this.disabled ? null : '0';\r\n }\r\n\r\n @Input() label: string = null;\r\n @Input() name: string = null;\r\n private _disabled = false;\r\n @Input()\r\n get disabled(): boolean {\r\n return this._disabled;\r\n }\r\n set disabled(v: boolean) {\r\n if (v === this._disabled) {\r\n return;\r\n }\r\n this._disabled = v;\r\n this.syncModel();\r\n this.cdr.markForCheck();\r\n }\r\n @Input() inputId: string;\r\n\r\n literals = {\r\n en: {\r\n yesLabel: 'Yes',\r\n noLabel: 'No'\r\n },\r\n es: {\r\n yesLabel: 'Sí',\r\n noLabel: 'No'\r\n }\r\n };\r\n touched = false;\r\n\r\n @Output() valueChange = new EventEmitter<boolean>();\r\n\r\n private cdr = inject(ChangeDetectorRef);\r\n\r\n // ControlValueAccessor Interface\r\n onChange = (value): void => {};\r\n onTouched = (): void => {};\r\n writeValue(value: any): void {\r\n this.value = !!value;\r\n }\r\n registerOnChange(onChange: any): void {\r\n this.onChange = onChange;\r\n }\r\n registerOnTouched(onTouched: any): void {\r\n this.onTouched = onTouched;\r\n }\r\n markAsTouched(): void {\r\n if (!this.touched) {\r\n this.onTouched();\r\n this.touched = true;\r\n }\r\n }\r\n setDisabledState(disabled: boolean): void {\r\n this.disabled = disabled;\r\n }\r\n // End ControlValueAccessor Interface\r\n\r\n ngOnInit(): void {\r\n this.inputId = this.inputId\r\n ? this.inputId\r\n : `${this.name || 'checkbox'}$${CheckboxComponent.idCounter++}`;\r\n }\r\n ngAfterViewInit(): void {\r\n setTimeout(() => {\r\n this.syncModel();\r\n });\r\n }\r\n\r\n clicked(): void {\r\n if (!this.disabled) {\r\n this.value = !this.internalValue;\r\n this.markAsTouched();\r\n }\r\n }\r\n onKey(event: KeyboardEvent): void {\r\n if (event.key === KEY_SPACE && !this.disabled) {\r\n this.value = !this.internalValue;\r\n this.markAsTouched();\r\n event.preventDefault();\r\n }\r\n }\r\n\r\n private syncModel(): void {\r\n if (this.ck) {\r\n this.ck.nativeElement.checked = this.internalValue;\r\n }\r\n }\r\n}\r\n","<div class=\"switch\">\r\n <div>\r\n @if (label) {\r\n <label [for]=\"inputId\">{{ label }}</label>\r\n }\r\n </div>\r\n @if (!disabled) {\r\n <div class=\"switch-item\" (click)=\"clicked()\">\r\n <input #ck [name]=\"name\" type=\"checkbox\" [id]=\"inputId\" />\r\n <div\r\n class=\"slider round\"\r\n [class.on]=\"value\"\r\n [class.disabled]=\"disabled\"\r\n [tabindex]=\"tabindexValue\"\r\n (keyup)=\"onKey($event)\"\r\n ></div>\r\n </div>\r\n }\r\n <div>\r\n @if (value) {\r\n <span class=\"checkbox-label\">{{ literals[lang].yesLabel }}</span>\r\n } @if (!value) {\r\n <span class=\"checkbox-label\">{{ literals[lang].noLabel }}</span>\r\n }\r\n </div>\r\n</div>\r\n","import { CommonModule } from '@angular/common';\r\nimport {\r\n Component,\r\n ElementRef,\r\n EventEmitter,\r\n Input,\r\n OnInit,\r\n Output,\r\n ViewChild,\r\n forwardRef\r\n} from '@angular/core';\r\nimport {\r\n AbstractControl,\r\n ControlValueAccessor,\r\n FormsModule,\r\n NG_VALIDATORS,\r\n NG_VALUE_ACCESSOR,\r\n ValidationErrors,\r\n Validator\r\n} from '@angular/forms';\r\nimport {\r\n addTimezoneOffset,\r\n hasValue,\r\n isInitialAndEmpty,\r\n isValidDate\r\n} from '../helperFns';\r\nimport { languageDetector } from '../lang';\r\n@Component({\r\n selector: 'lux-datetime',\r\n templateUrl: './datetime.component.html',\r\n styleUrls: ['./datetime.component.scss'],\r\n imports: [FormsModule, CommonModule],\r\n providers: [\r\n {\r\n provide: NG_VALUE_ACCESSOR,\r\n multi: true,\r\n useExisting: forwardRef(() => DatetimeComponent)\r\n },\r\n {\r\n provide: NG_VALIDATORS,\r\n multi: true,\r\n useExisting: forwardRef(() => DatetimeComponent)\r\n }\r\n ]\r\n})\r\nexport class DatetimeComponent\r\n implements OnInit, ControlValueAccessor, Validator\r\n{\r\n static idCounter = 0;\r\n\r\n @ViewChild('dateInput', { static: true }) dateInput: ElementRef;\r\n @ViewChild('timeInput', { static: true }) timeInput: ElementRef;\r\n\r\n touched = false;\r\n dirty = false;\r\n lastErrors: ValidationErrors | null = null;\r\n\r\n private _disabled: string | boolean;\r\n private _required: boolean;\r\n private _value: string;\r\n\r\n public dateValue?: string = undefined;\r\n public timeValue?: string = undefined;\r\n\r\n public userErrors = {\r\n en: {\r\n required: 'Required field.',\r\n min: 'Minimum value is $min.',\r\n max: 'Maximum value is $max.'\r\n },\r\n es: {\r\n required: 'El campo es obligatorio.',\r\n min: 'El valor mínimo es $min.',\r\n max: 'El valor máximo es $max.'\r\n }\r\n };\r\n\r\n @Input()\r\n public min?: string = '1900-01-01T00:00:00Z';\r\n @Input()\r\n public max?: string = '2100-01-01T00:00:00Z';\r\n @Input()\r\n public includeSeconds: boolean = true;\r\n @Input()\r\n public localTime: boolean = true;\r\n\r\n get className(): string {\r\n return this.checkClassName();\r\n }\r\n\r\n @Input() lang = languageDetector();\r\n @Input() public inlineErrors = false;\r\n @Input() public inputId: string;\r\n @Input('aria-label') public ariaLabel: string;\r\n @Input() public readonly: boolean | null = null;\r\n\r\n @Input()\r\n set disabled(v: string | boolean) {\r\n v = typeof v === 'string' && v !== 'false' ? true : v;\r\n this._disabled = v;\r\n }\r\n get disabled(): string | boolean {\r\n return this._disabled;\r\n }\r\n\r\n @Input()\r\n set required(v: boolean) {\r\n this._required = v;\r\n }\r\n get required(): boolean {\r\n return this._required;\r\n }\r\n\r\n @Input()\r\n set value(v: string) {\r\n if (v === this._value) {\r\n return;