diff --git a/projects/composition/src/app/pages/autocomplete-page/autocomplete-page.component.html b/projects/composition/src/app/pages/autocomplete-page/autocomplete-page.component.html index 13e8951a5..b51048c77 100644 --- a/projects/composition/src/app/pages/autocomplete-page/autocomplete-page.component.html +++ b/projects/composition/src/app/pages/autocomplete-page/autocomplete-page.component.html @@ -215,6 +215,21 @@ + + + + + option.data.code; + get availableOptionInfo() { return this.options.map((option) => option.name).join(', '); } diff --git a/projects/composition/src/app/pages/autocomplete-page/autocomplete-page.examples.ts b/projects/composition/src/app/pages/autocomplete-page/autocomplete-page.examples.ts index 31d2417d4..49c37c629 100644 --- a/projects/composition/src/app/pages/autocomplete-page/autocomplete-page.examples.ts +++ b/projects/composition/src/app/pages/autocomplete-page/autocomplete-page.examples.ts @@ -236,6 +236,23 @@ syncVal: string[] = [];` ts: citiesOptionsTs }, + functionOptionKey: { + html: ` + +`, + ts: ` +${citiesOptionsTs.trim()} + +getCode = (option: any): string => option.data.code;` + }, + asyncValidation: { html: ` - {{ syncVal }} + [value]="[options[0].data, options[4].data]"> + + + + +
+ + {{ syncVal }} +
+ + + + + +
diff --git a/projects/composition/src/app/pages/select-page/select-page.component.ts b/projects/composition/src/app/pages/select-page/select-page.component.ts index ae3bce6e0..2aa137661 100644 --- a/projects/composition/src/app/pages/select-page/select-page.component.ts +++ b/projects/composition/src/app/pages/select-page/select-page.component.ts @@ -63,6 +63,8 @@ export class SelectPageComponent implements OnInit { openOnClearValue = this.options[3]; componentData = ComponentData; + getCode = (option: any): string => option.data.code; + // eslint-disable-next-line no-useless-constructor constructor(private _formBuilder: UntypedFormBuilder) {} diff --git a/projects/composition/src/app/pages/tree-autocomplete-page/tree-autocomplete-page.component.html b/projects/composition/src/app/pages/tree-autocomplete-page/tree-autocomplete-page.component.html index cbb019e06..6318f369c 100644 --- a/projects/composition/src/app/pages/tree-autocomplete-page/tree-autocomplete-page.component.html +++ b/projects/composition/src/app/pages/tree-autocomplete-page/tree-autocomplete-page.component.html @@ -122,8 +122,78 @@ optionInfo="attrType" optionLabel="label" [clearable]="true" - [(ngModel)]="syncVal" - [ngModelOptions]="{ standalone: true }"> + [persistentClear]="true" + [virtualScroll]="true" + [multiple]="true"> + + + + + + + +
+ + + {{ syncVal?.label }} +
+ + + + + + + {{ syncVal?.label }} diff --git a/projects/composition/src/app/pages/tree-autocomplete-page/tree-autocomplete-page.component.ts b/projects/composition/src/app/pages/tree-autocomplete-page/tree-autocomplete-page.component.ts index db3aa9fed..43ed15971 100644 --- a/projects/composition/src/app/pages/tree-autocomplete-page/tree-autocomplete-page.component.ts +++ b/projects/composition/src/app/pages/tree-autocomplete-page/tree-autocomplete-page.component.ts @@ -127,6 +127,9 @@ export class TreeAutocompletePageComponent implements OnInit { componentData = ComponentData; + getLabel = (option: any): string => + option.label + (option.attrType ? ` (${option.attrType})` : ''); + // eslint-disable-next-line no-useless-constructor constructor(private _formBuilder: UntypedFormBuilder) {} diff --git a/projects/composition/src/app/pages/tree-select-page/tree-select-page.component.html b/projects/composition/src/app/pages/tree-select-page/tree-select-page.component.html index 6e8dae9b9..c4110aff4 100644 --- a/projects/composition/src/app/pages/tree-select-page/tree-select-page.component.html +++ b/projects/composition/src/app/pages/tree-select-page/tree-select-page.component.html @@ -122,8 +122,76 @@ optionInfo="attrType" optionLabel="label" [clearable]="true" - [(ngModel)]="syncVal" - [ngModelOptions]="{ standalone: true }"> + [persistentClear]="true" + [virtualScroll]="true" + [multiple]="true"> + + + + + + + +
+ + + {{ syncVal?.label }} +
+ + + + + + + {{ syncVal?.label }} diff --git a/projects/composition/src/app/pages/tree-select-page/tree-select-page.component.ts b/projects/composition/src/app/pages/tree-select-page/tree-select-page.component.ts index 89953b611..98d444f89 100644 --- a/projects/composition/src/app/pages/tree-select-page/tree-select-page.component.ts +++ b/projects/composition/src/app/pages/tree-select-page/tree-select-page.component.ts @@ -127,6 +127,9 @@ export class TreeSelectPageComponent implements OnInit { componentData = ComponentData; + getLabel = (option: any): string => + option.label + (option.attrType ? ` (${option.attrType})` : ''); + // eslint-disable-next-line no-useless-constructor constructor(private _formBuilder: UntypedFormBuilder) {} diff --git a/projects/cps-ui-kit/src/lib/components/cps-autocomplete/cps-autocomplete.component.html b/projects/cps-ui-kit/src/lib/components/cps-autocomplete/cps-autocomplete.component.html index 56ab52767..413d984ee 100644 --- a/projects/cps-ui-kit/src/lib/components/cps-autocomplete/cps-autocomplete.component.html +++ b/projects/cps-ui-kit/src/lib/components/cps-autocomplete/cps-autocomplete.component.html @@ -58,7 +58,7 @@ data-testid="cps-autocomplete-selected-value"> {{ returnObject - ? value[optionLabel] + ? getProp(value, optionLabel) : (value | labelByValue: options : optionValue : optionLabel) }} @@ -95,7 +95,7 @@ }"> {{ returnObject - ? val[optionLabel] + ? getProp(val, optionLabel) : (val | labelByValue : options @@ -137,7 +137,7 @@ }" [label]=" returnObject - ? val[optionLabel] + ? getProp(val, optionLabel) : (val | labelByValue: options : optionValue : optionLabel) "> @@ -402,14 +402,14 @@ data-testid="cps-autocomplete-options" class="cps-autocomplete-options-option-label" [class.virtual-row]="virtualScroll" - >{{ item[optionLabel] }}{{ getProp(item, optionLabel) }} {{ item[optionInfo] }}{{ getProp(item, optionInfo) }} diff --git a/projects/cps-ui-kit/src/lib/components/cps-autocomplete/cps-autocomplete.component.spec.ts b/projects/cps-ui-kit/src/lib/components/cps-autocomplete/cps-autocomplete.component.spec.ts index 7b659ba26..0da1690a1 100644 --- a/projects/cps-ui-kit/src/lib/components/cps-autocomplete/cps-autocomplete.component.spec.ts +++ b/projects/cps-ui-kit/src/lib/components/cps-autocomplete/cps-autocomplete.component.spec.ts @@ -464,6 +464,88 @@ describe('CpsAutocompleteComponent', () => { expect(component.filteredOptions.length).toBe(3); }); + describe('function-based option keys', () => { + const nestedOptions = [ + { meta: { title: 'Alpha', id: 1 } }, + { meta: { title: 'Beta', id: 2 } }, + { meta: { title: 'Gamma', id: 3 } } + ]; + + it('getProp should return property value when key is a string', () => { + expect(component.getProp({ label: 'hello' }, 'label')).toBe('hello'); + }); + + it('getProp should invoke the function and return its result when key is a function', () => { + const fn = (o: any) => o.meta.title; + expect(component.getProp({ meta: { title: 'hello' } }, fn)).toBe('hello'); + }); + + it('should display selected label using function optionLabel', () => { + fixture.componentRef.setInput('options', nestedOptions); + fixture.componentRef.setInput('optionLabel', (o: any) => o.meta.title); + component.value = nestedOptions[0]; + fixture.changeDetectorRef.markForCheck(); + fixture.detectChanges(); + const selectedLabel = fixture.debugElement.query( + By.css('.single-item-selection span') + ); + expect(selectedLabel.nativeElement.textContent.trim()).toBe('Alpha'); + }); + + it('should filter options using function optionLabel', fakeAsync(() => { + fixture.componentRef.setInput('options', nestedOptions); + fixture.componentRef.setInput('optionLabel', (o: any) => o.meta.title); + fixture.detectChanges(); + const inputElement = fixture.debugElement.query( + By.css('.cps-autocomplete-box-input') + ); + inputElement.nativeElement.value = 'bet'; + inputElement.nativeElement.dispatchEvent(new Event('input')); + fixture.detectChanges(); + tick(component.inputChangeDebounceTime); + fixture.detectChanges(); + expect(component.filteredOptions.length).toBe(1); + expect(component.filteredOptions[0]).toBe(nestedOptions[1]); + })); + + it('should emit the function-derived value when optionValue is a function and returnObject is false', () => { + fixture.componentRef.setInput('options', nestedOptions); + fixture.componentRef.setInput('optionLabel', (o: any) => o.meta.title); + fixture.componentRef.setInput('optionValue', (o: any) => o.meta.id); + fixture.componentRef.setInput('returnObject', false); + fixture.detectChanges(); + jest.spyOn(component.valueChanged, 'emit'); + component.select(nestedOptions[1], false); + expect(component.valueChanged.emit).toHaveBeenCalledWith(2); + }); + + it('should collect all function-derived values when toggleAll is called with returnObject false', () => { + fixture.componentRef.setInput('options', nestedOptions); + fixture.componentRef.setInput('optionLabel', (o: any) => o.meta.title); + fixture.componentRef.setInput('optionValue', (o: any) => o.meta.id); + fixture.componentRef.setInput('returnObject', false); + fixture.componentRef.setInput('multiple', true); + component.value = []; + fixture.changeDetectorRef.markForCheck(); + fixture.detectChanges(); + component.toggleAll(); + expect(component.value).toEqual([1, 2, 3]); + }); + + it('should display chips with labels from function optionLabel', () => { + fixture.componentRef.setInput('options', nestedOptions); + fixture.componentRef.setInput('optionLabel', (o: any) => o.meta.title); + fixture.componentRef.setInput('multiple', true); + component.value = [nestedOptions[0], nestedOptions[2]]; + fixture.changeDetectorRef.markForCheck(); + fixture.detectChanges(); + const chipElements = fixture.debugElement.queryAll(By.css('cps-chip')); + expect(chipElements.length).toBe(2); + expect(chipElements[0].nativeElement.textContent.trim()).toBe('Alpha'); + expect(chipElements[1].nativeElement.textContent.trim()).toBe('Gamma'); + }); + }); + describe('Scroll alignment', () => { let scrollIntoView: jest.Mock; diff --git a/projects/cps-ui-kit/src/lib/components/cps-autocomplete/cps-autocomplete.component.ts b/projects/cps-ui-kit/src/lib/components/cps-autocomplete/cps-autocomplete.component.ts index 2f074fefc..5091cb0eb 100644 --- a/projects/cps-ui-kit/src/lib/components/cps-autocomplete/cps-autocomplete.component.ts +++ b/projects/cps-ui-kit/src/lib/components/cps-autocomplete/cps-autocomplete.component.ts @@ -42,6 +42,7 @@ import { LabelByValuePipe } from '../../pipes/internal/label-by-value/label-by-v import { CheckOptionSelectedPipe } from '../../pipes/internal/check-option-selected/check-option-selected.pipe'; import { isEqual } from 'lodash-es'; import { CpsTooltipPosition } from '../../directives/cps-tooltip/cps-tooltip.directive'; +import { getOptionProp, OptionKey } from '../../utils/internal/option-utils'; import { CpsMenuComponent, CpsMenuHideReason @@ -192,22 +193,22 @@ export class CpsAutocompleteComponent @Input() keepInitialOrder = false; /** - * Name of the label field of an option. + * Name of the label field of an option, or a function that receives the option and returns the label. * @group Props */ - @Input() optionLabel = 'label'; + @Input() optionLabel: OptionKey = 'label'; /** - * Name of the value field of an option. Needed only if returnObject prop is false. + * Name of the value field of an option, or a function that receives the option and returns the value. Needed only if returnObject prop is false. * @group Props */ - @Input() optionValue = 'value'; + @Input() optionValue: OptionKey = 'value'; /** - * Name of the info field of an option, shows the additional information text. + * Name of the info field of an option, or a function that receives the option and returns the info text. * @group Props */ - @Input() optionInfo = 'info'; + @Input() optionInfo: OptionKey = 'info'; /** * Hides hint and validation errors. @@ -557,6 +558,10 @@ export class CpsAutocompleteComponent this._destroy$.complete(); } + getProp(option: any, key: OptionKey): any { + return getOptionProp(option, key); + } + select( option: any, byValue: boolean, @@ -572,7 +577,7 @@ export class CpsAutocompleteComponent ? option : this.returnObject ? option - : option[this.optionValue]; + : getOptionProp(option, this.optionValue); if (this.multiple) { let res = []; if (includes(this.value, val)) { @@ -580,7 +585,9 @@ export class CpsAutocompleteComponent } else { if (this.keepInitialOrder) { this.options.forEach((o) => { - const ov = this.returnObject ? o : o[this.optionValue]; + const ov = this.returnObject + ? o + : getOptionProp(o, this.optionValue); if ( this.value.some((v: any) => isEqual(v, ov)) || isEqual(val, ov) @@ -590,12 +597,15 @@ export class CpsAutocompleteComponent }); } else { const opt = this.options.find((o) => { - return isEqual(val, this.returnObject ? o : o[this.optionValue]); + return isEqual( + val, + this.returnObject ? o : getOptionProp(o, this.optionValue) + ); }); if (opt) { res = [ ...this.value, - this.returnObject ? opt : opt[this.optionValue] + this.returnObject ? opt : getOptionProp(opt, this.optionValue) ]; } } @@ -628,7 +638,7 @@ export class CpsAutocompleteComponent res = this.options; } else { this.options.forEach((o) => { - res.push(o[this.optionValue]); + res.push(getOptionProp(o, this.optionValue)); }); } } @@ -664,7 +674,9 @@ export class CpsAutocompleteComponent this.backspaceClickedOnce = false; let _filteredOptions = this.options.filter((o: any) => { - let res = o[this.optionLabel].toLowerCase().includes(searchVal); + let res = (getOptionProp(o, this.optionLabel) || '') + .toLowerCase() + .includes(searchVal); if ( !res && this.withOptionsAliases && @@ -950,7 +962,7 @@ export class CpsAutocompleteComponent ? undefined : this.returnObject ? option - : option[this.optionValue]; + : getOptionProp(option, this.optionValue); } private _toggleOptions(show?: boolean): void { @@ -1036,7 +1048,7 @@ export class CpsAutocompleteComponent private _getValueLabel() { return this.hasSelectedValue() ? this.returnObject - ? this.value[this.optionLabel] + ? getOptionProp(this.value, this.optionLabel) : this._labelByValue.transform( this.value, this.options, @@ -1210,7 +1222,8 @@ export class CpsAutocompleteComponent } const found = this.filteredOptions.find( - (o: any) => o[this.optionLabel].toLowerCase() === searchVal + (o: any) => + (getOptionProp(o, this.optionLabel) || '').toLowerCase() === searchVal ); if (found) { this.select(found, false, true, needFocusInput); diff --git a/projects/cps-ui-kit/src/lib/components/cps-select/cps-select.component.html b/projects/cps-ui-kit/src/lib/components/cps-select/cps-select.component.html index 4f747f568..c0ec7a348 100644 --- a/projects/cps-ui-kit/src/lib/components/cps-select/cps-select.component.html +++ b/projects/cps-ui-kit/src/lib/components/cps-select/cps-select.component.html @@ -71,7 +71,7 @@ {{ returnObject - ? value[optionLabel] + ? getProp(value, optionLabel) : (value | labelByValue: options : optionValue : optionLabel) }} @@ -295,14 +295,14 @@ class="cps-select-options-option-label" data-testid="cps-select-option-label" [class.virtual-row]="virtualScroll" - >{{ item[optionLabel] }}{{ getProp(item, optionLabel) }} - {{ item[optionInfo] }} + {{ getProp(item, optionInfo) }} @if (item[optionIcon]) { isEqual(item, val)) || false; @@ -493,7 +498,7 @@ export class CpsSelectComponent ? option : this.returnObject ? option - : option[this.optionValue]; + : getOptionProp(option, this.optionValue); if (this.multiple) { let res = []; if (includes(this.value, val)) { @@ -501,7 +506,9 @@ export class CpsSelectComponent } else { if (this.keepInitialOrder) { this.options.forEach((o) => { - const ov = this.returnObject ? o : o[this.optionValue]; + const ov = this.returnObject + ? o + : getOptionProp(o, this.optionValue); if ( this.value.some((v: any) => isEqual(v, ov)) || isEqual(val, ov) @@ -511,12 +518,15 @@ export class CpsSelectComponent }); } else { const opt = this.options.find((o) => { - return isEqual(val, this.returnObject ? o : o[this.optionValue]); + return isEqual( + val, + this.returnObject ? o : getOptionProp(o, this.optionValue) + ); }); if (opt) { res = [ ...this.value, - this.returnObject ? opt : opt[this.optionValue] + this.returnObject ? opt : getOptionProp(opt, this.optionValue) ]; } } @@ -665,7 +675,7 @@ export class CpsSelectComponent res = this.options; } else { this.options.forEach((o) => { - res.push(o[this.optionValue]); + res.push(getOptionProp(o, this.optionValue)); }); } } diff --git a/projects/cps-ui-kit/src/lib/components/internal/cps-base-tree-dropdown/cps-base-tree-dropdown.component.ts b/projects/cps-ui-kit/src/lib/components/internal/cps-base-tree-dropdown/cps-base-tree-dropdown.component.ts index a562f1994..2f1f546d2 100644 --- a/projects/cps-ui-kit/src/lib/components/internal/cps-base-tree-dropdown/cps-base-tree-dropdown.component.ts +++ b/projects/cps-ui-kit/src/lib/components/internal/cps-base-tree-dropdown/cps-base-tree-dropdown.component.ts @@ -27,6 +27,7 @@ import { Subscription } from 'rxjs'; import { Tree } from 'primeng/tree'; import { isEqual } from 'lodash-es'; import { IconType, iconSizeType } from '../../cps-icon/cps-icon.component'; +import { getOptionProp, OptionKey } from '../../../utils/internal/option-utils'; import { convertSize } from '../../../utils/internal/size-utils/size-utils'; import { CpsTooltipPosition } from '../../../directives/cps-tooltip/cps-tooltip.directive'; import { CpsMenuComponent } from '../../cps-menu/cps-menu.component'; @@ -107,16 +108,16 @@ export class CpsBaseTreeDropdownComponent @Input() openOnClear = true; /** - * Name of the label field of an option. + * Name of the label field of an option, or a function that receives the option and returns the label. * @group Props */ - @Input() optionLabel = 'label'; + @Input() optionLabel: OptionKey = 'label'; /** - * Name of the info field of an option, shows the additional information text. + * Name of the info field of an option, or a function that receives the option and returns the info text. * @group Props */ - @Input() optionInfo = 'info'; + @Input() optionInfo: OptionKey = 'info'; /** * Options for hiding details. @@ -893,15 +894,15 @@ export class CpsBaseTreeDropdownComponent private _toInnerOptions(_options: any[]): TreeNode[] { const mapOption = ( o: any, - optionLabel: string, - optionInfo: string, + optionLabel: OptionKey, + optionInfo: OptionKey, key: string, originalOptionsMap: any ) => { const inner = { inner: true, - label: o[optionLabel], - info: o[optionInfo], + label: getOptionProp(o, optionLabel), + info: getOptionProp(o, optionInfo), key, styleClass: 'key-' + key } as TreeNode; diff --git a/projects/cps-ui-kit/src/lib/pipes/internal/check-option-selected/check-option-selected.pipe.ts b/projects/cps-ui-kit/src/lib/pipes/internal/check-option-selected/check-option-selected.pipe.ts index 4d2c17d8f..c66451e3b 100644 --- a/projects/cps-ui-kit/src/lib/pipes/internal/check-option-selected/check-option-selected.pipe.ts +++ b/projects/cps-ui-kit/src/lib/pipes/internal/check-option-selected/check-option-selected.pipe.ts @@ -1,5 +1,6 @@ import { Pipe, PipeTransform } from '@angular/core'; import { isEqual } from 'lodash-es'; +import { getOptionProp, OptionKey } from '../../utils/internal/option-utils'; @Pipe({ standalone: true, name: 'checkOptionSelected' }) export class CheckOptionSelectedPipe implements PipeTransform { @@ -8,7 +9,7 @@ export class CheckOptionSelectedPipe implements PipeTransform { value: any, multiple: boolean, returnObject: boolean, - optionValue: string + optionValue: OptionKey ): boolean { function includes(array: any[], val: any): boolean { return array?.some((item) => isEqual(item, val)) || false; @@ -17,9 +18,9 @@ export class CheckOptionSelectedPipe implements PipeTransform { return multiple ? returnObject ? includes(value, option) - : includes(value, option[optionValue]) + : includes(value, getOptionProp(option, optionValue)) : returnObject ? isEqual(option, value) - : isEqual(option[optionValue], value); + : isEqual(getOptionProp(option, optionValue), value); } } diff --git a/projects/cps-ui-kit/src/lib/pipes/internal/combine-labels/combine-labels.pipe.ts b/projects/cps-ui-kit/src/lib/pipes/internal/combine-labels/combine-labels.pipe.ts index 7f3f66a36..f570ac3a1 100644 --- a/projects/cps-ui-kit/src/lib/pipes/internal/combine-labels/combine-labels.pipe.ts +++ b/projects/cps-ui-kit/src/lib/pipes/internal/combine-labels/combine-labels.pipe.ts @@ -1,22 +1,25 @@ import { Pipe, PipeTransform } from '@angular/core'; import { isEqual } from 'lodash-es'; +import { getOptionProp, OptionKey } from '../../utils/internal/option-utils'; @Pipe({ standalone: true, name: 'combineLabels' }) export class CombineLabelsPipe implements PipeTransform { transform( values: any[], options: any[], - valueKey: string, - labelKey: string, + valueKey: OptionKey, + labelKey: OptionKey, returnObject: boolean ): string { return values .map((v) => { if (returnObject) { - return v[labelKey]; + return getOptionProp(v, labelKey); } else { - const option = options.find((opt) => isEqual(opt[valueKey], v)); - return option ? option[labelKey] : ''; + const option = options.find((opt) => + isEqual(getOptionProp(opt, valueKey), v) + ); + return option ? getOptionProp(option, labelKey) : ''; } }) .join(', '); diff --git a/projects/cps-ui-kit/src/lib/pipes/internal/label-by-value/label-by-value.pipe.ts b/projects/cps-ui-kit/src/lib/pipes/internal/label-by-value/label-by-value.pipe.ts index a42010055..da1d468a9 100644 --- a/projects/cps-ui-kit/src/lib/pipes/internal/label-by-value/label-by-value.pipe.ts +++ b/projects/cps-ui-kit/src/lib/pipes/internal/label-by-value/label-by-value.pipe.ts @@ -1,15 +1,18 @@ import { Pipe, PipeTransform } from '@angular/core'; import { isEqual } from 'lodash-es'; +import { getOptionProp, OptionKey } from '../../utils/internal/option-utils'; @Pipe({ standalone: true, name: 'labelByValue' }) export class LabelByValuePipe implements PipeTransform { transform( value: any, options: any[], - valueKey: string, - labelKey: string + valueKey: OptionKey, + labelKey: OptionKey ): string { - const option = options.find((opt) => isEqual(opt[valueKey], value)); - return option ? option[labelKey] : ''; + const option = options.find((opt) => + isEqual(getOptionProp(opt, valueKey), value) + ); + return option ? getOptionProp(option, labelKey) : ''; } } diff --git a/projects/cps-ui-kit/src/lib/utils/internal/option-utils.ts b/projects/cps-ui-kit/src/lib/utils/internal/option-utils.ts new file mode 100644 index 000000000..1dea77722 --- /dev/null +++ b/projects/cps-ui-kit/src/lib/utils/internal/option-utils.ts @@ -0,0 +1,6 @@ +export type OptionKey = string | ((option: any) => any); + +export function getOptionProp(option: any, key: OptionKey): any { + if (typeof key === 'function') return key(option); + return option?.[key]; +} diff --git a/projects/cps-ui-kit/src/public-api.ts b/projects/cps-ui-kit/src/public-api.ts index e5e6bbda8..ed2bba033 100644 --- a/projects/cps-ui-kit/src/public-api.ts +++ b/projects/cps-ui-kit/src/public-api.ts @@ -66,3 +66,4 @@ export * from './lib/services/cps-cron-validation/cps-cron-validation.service'; export * from './lib/services/cps-live-announcer/cps-live-announcer.service'; export * from './lib/utils/colors-utils/colors-utils'; +export { OptionKey } from './lib/utils/internal/option-utils';