@yuuvis/client-framework 3.16.0 → 3.17.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -6,7 +6,7 @@ import { FormControl, Validators, ReactiveFormsModule, FormBuilder, NG_VALUE_ACC
6
6
  import { MatFormFieldControl, MatFormFieldModule } from '@angular/material/form-field';
7
7
  import * as i1 from '@angular/material/select';
8
8
  import { MatSelectModule } from '@angular/material/select';
9
- import { SystemService, TranslateService, Classification, TranslatePipe, Situation, Operator, OperatorLabel, Utils, CatalogService, LocaleNumberPipe, FileSizePipe, IdmService, UserService, SearchUtils, LocaleDatePipe, SearchService, DmsService, ObjectConfigService, DmsObject, BaseObjectTypeField, ClassificationPrefix } from '@yuuvis/client-core';
9
+ import { SystemService, TranslateService, Classification, TranslatePipe, Situation, Operator, OperatorLabel, Utils, CatalogService, LocaleNumberPipe, FileSizePipe, IdmService, UserService, AppCacheService, FREE_TEXT_OPTION, USER_ID_REGEX, STORAGE_ROLES_KEY, SearchUtils, LocaleDatePipe, SearchService, DmsService, ObjectConfigService, DmsObject, BaseObjectTypeField, ClassificationPrefix } from '@yuuvis/client-core';
10
10
  import { AbstractMatFormField, injectNgControl, FormTranslateService, DialogComponent, ScrollButtonsComponent } from '@yuuvis/client-framework/common';
11
11
  import * as i1$3 from '@angular/common';
12
12
  import { NgClass, CommonModule } from '@angular/common';
@@ -32,9 +32,9 @@ import * as i2$1 from '@yuuvis/client-framework/datepicker';
32
32
  import { DatepickerComponent, YuvDatepickerModule } from '@yuuvis/client-framework/datepicker';
33
33
  import * as i2$2 from '@yuuvis/client-framework/autocomplete';
34
34
  import { YuvAutocompleteModule } from '@yuuvis/client-framework/autocomplete';
35
- import { map as map$1, catchError } from 'rxjs/operators';
36
- import { ShellService } from '@yuuvis/client-shell-core';
37
35
  import { ENTER, COMMA } from '@angular/cdk/keycodes';
36
+ import { map as map$1, switchMap, catchError } from 'rxjs/operators';
37
+ import { ShellService } from '@yuuvis/client-shell-core';
38
38
  import * as i1$4 from '@angular/material/chips';
39
39
  import { MatChipsModule } from '@angular/material/chips';
40
40
 
@@ -292,7 +292,6 @@ class DataGridComponent {
292
292
  this.selectedRow = signal(null, ...(ngDevMode ? [{ debugName: "selectedRow" }] : /* istanbul ignore next */ []));
293
293
  this.isRequired = false;
294
294
  this.isInvalid = false;
295
- this.initalTableUpdate = true;
296
295
  this.icons = {
297
296
  add: YUV_ICONS.add,
298
297
  more: YUV_ICONS.more
@@ -310,38 +309,13 @@ class DataGridComponent {
310
309
  computation: (readonly, previous) => readonly ? (previous?.value || []).filter((c) => c.columnDef !== 'actions') : previous?.value || [] });
311
310
  this.#loadData = effect(() => {
312
311
  const formElement = this.formElement();
313
- // if the form element is created from the ObjectTypeField it contains 'columnDefinitions' instead of 'elements'
314
- // therefore we need to map them here for proper table rendering
315
- if (formElement && formElement['columnDefinitions']) {
316
- // map columnDefinitions to elements for table rendering
317
- formElement['elements'] = formElement['columnDefinitions'].map((colDef) => ({
318
- ...colDef,
319
- name: colDef.id,
320
- label: this.#systemService.getLocalizedLabel(colDef.id) || colDef.id,
321
- type: colDef.propertyType
322
- }));
323
- }
312
+ const elements = this.#resolveElements(formElement);
324
313
  this.mappedFormElement.set(formElement);
325
- const elements = formElement ? formElement['elements'] || [] : [];
326
- let data = formElement ? formElement['value'] || [] : [];
327
- const columns = elements.map((e) => e.name || e.id);
328
- data = data.map((row) => {
329
- if (Array.isArray(row)) {
330
- const obj = {};
331
- for (let i = 0; i < columns.length; i++) {
332
- obj[columns[i]] = row[i];
333
- }
334
- return obj;
335
- }
336
- return row;
337
- });
314
+ const data = this.#toRowObjects(formElement ? formElement['value'] || [] : [], elements);
315
+ // rendering only the value already lives in the form model, so a (re)bind
316
+ // of the formElement input must never propagate back or dirty the control
338
317
  untracked(() => formElement && this.#updateTable(elements, data));
339
318
  }, ...(ngDevMode ? [{ debugName: "#loadData" }] : /* istanbul ignore next */ []));
340
- this.#sourceData = effect(() => {
341
- const dataSource = this.dataSource();
342
- !this.initalTableUpdate && this.writeValue(dataSource.filter((row) => !row.isAddRow));
343
- this.initalTableUpdate = false;
344
- }, ...(ngDevMode ? [{ debugName: "#sourceData" }] : /* istanbul ignore next */ []));
345
319
  this.propagateChange = () => { };
346
320
  this.onTouched = () => { };
347
321
  }
@@ -349,7 +323,36 @@ class DataGridComponent {
349
323
  #systemService;
350
324
  #cdRef;
351
325
  #loadData;
352
- #sourceData;
326
+ // if the form element is created from the ObjectTypeField it contains 'columnDefinitions' instead of 'elements'
327
+ // therefore we need to map them here for proper table rendering
328
+ #resolveElements(formElement) {
329
+ if (!formElement)
330
+ return [];
331
+ if (formElement['columnDefinitions']) {
332
+ formElement['elements'] = formElement['columnDefinitions'].map((colDef) => ({
333
+ ...colDef,
334
+ name: colDef.id,
335
+ label: this.#systemService.getLocalizedLabel(colDef.id) || colDef.id,
336
+ type: colDef.propertyType
337
+ }));
338
+ }
339
+ return formElement['elements'] || [];
340
+ }
341
+ // table values may arrive as 2D arrays ([[a, b], ...]); map them to row objects
342
+ // keyed by the column names so the table can render them
343
+ #toRowObjects(data, elements) {
344
+ const columns = elements.map((e) => e.name || e.id);
345
+ return data.map((row) => {
346
+ if (Array.isArray(row)) {
347
+ const obj = {};
348
+ for (let i = 0; i < columns.length; i++) {
349
+ obj[columns[i]] = row[i];
350
+ }
351
+ return obj;
352
+ }
353
+ return row;
354
+ });
355
+ }
353
356
  #openEditOverlay(elementData, adding = false) {
354
357
  return this.#dialog
355
358
  .open(EditTableDataComponent, {
@@ -378,7 +381,10 @@ class DataGridComponent {
378
381
  if (result) {
379
382
  const updatedData = this.dataSource().map((item) => JSON.stringify(item) === JSON.stringify(element) ? result : item);
380
383
  const formElement = this.mappedFormElement();
381
- formElement && this.#updateTable(formElement['elements'], [...updatedData]);
384
+ if (formElement) {
385
+ this.#updateTable(formElement['elements'], [...updatedData]);
386
+ this.#propagateRows();
387
+ }
382
388
  }
383
389
  this.selectedRow.set(null);
384
390
  });
@@ -390,14 +396,29 @@ class DataGridComponent {
390
396
  if (result) {
391
397
  data.push(result);
392
398
  const formElement = this.mappedFormElement();
393
- formElement && this.#updateTable(formElement['elements'], data);
399
+ if (formElement) {
400
+ this.#updateTable(formElement['elements'], data);
401
+ this.#propagateRows();
402
+ }
394
403
  }
395
404
  });
396
405
  }
397
406
  removeRow(element) {
398
407
  const updatedData = this.dataSource().filter((item) => JSON.stringify(item) !== JSON.stringify(element));
399
408
  const formElement = this.mappedFormElement();
400
- formElement && this.#updateTable(formElement['elements'], updatedData);
409
+ if (formElement) {
410
+ this.#updateTable(formElement['elements'], updatedData);
411
+ this.#propagateRows();
412
+ }
413
+ }
414
+ /**
415
+ * View → model, called from user-edit paths only (add/edit/remove row).
416
+ * The propagated value shape is the rendered one: an array of row objects
417
+ * keyed by column name ([{col1: a, col2: b}, ...]) — the same shape
418
+ * ObjectFormService.extractFormData() reads from the control.
419
+ */
420
+ #propagateRows() {
421
+ this.propagateChange(this.dataSource().filter((row) => !row.isAddRow));
401
422
  }
402
423
  #updateTable(elements, data = []) {
403
424
  if (elements.length > 0) {
@@ -425,8 +446,15 @@ class DataGridComponent {
425
446
  this.displayedColumnsWithActions.set(displayedColumns);
426
447
  this.dataSource.set(data);
427
448
  }
449
+ /**
450
+ * Model → view only. Renders the incoming value (row objects or 2D arrays,
451
+ * which are converted via the column definitions) into the table. Never
452
+ * propagates back to the model — programmatic writes (setValue/patchValue/
453
+ * reset) and rebinds must not change the control's value or dirty state.
454
+ */
428
455
  writeValue(obj) {
429
- this.propagateChange(obj);
456
+ const elements = this.#resolveElements(this.formElement());
457
+ this.#updateTable(elements, this.#toRowObjects(Array.isArray(obj) ? obj : [], elements));
430
458
  }
431
459
  registerOnChange(fn) {
432
460
  this.propagateChange = fn;
@@ -438,9 +466,6 @@ class DataGridComponent {
438
466
  setDisabledState(isDisabled) {
439
467
  // console.log('setDisabledState: ', isDisabled);
440
468
  }
441
- onValueChange(e) {
442
- this.propagateChange(e);
443
- }
444
469
  validate(control) {
445
470
  //Has to be delayed here because form init in object-form.component is also delayed
446
471
  timer(300)
@@ -1598,28 +1623,28 @@ class OrganizationSetComponent extends AbstractMatFormField {
1598
1623
  return undefined;
1599
1624
  }
1600
1625
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.20", ngImport: i0, type: OrganizationSetComponent, deps: null, target: i0.ɵɵFactoryTarget.Component }); }
1601
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "21.2.20", type: OrganizationSetComponent, isStandalone: true, selector: "yuv-organization-set", inputs: { situation: { classPropertyName: "situation", publicName: "situation", isSignal: true, isRequired: false, transformFunction: null }, multiselect: { classPropertyName: "multiselect", publicName: "multiselect", isSignal: true, isRequired: false, transformFunction: null }, readonly: { classPropertyName: "readonly", publicName: "readonly", isSignal: true, isRequired: false, transformFunction: null }, withMetadata: { classPropertyName: "withMetadata", publicName: "withMetadata", isSignal: true, isRequired: false, transformFunction: null }, autocompleteMinLength: { classPropertyName: "autocompleteMinLength", publicName: "autocompleteMinLength", isSignal: true, isRequired: false, transformFunction: null }, classifications: { classPropertyName: "classifications", publicName: "classifications", isSignal: true, isRequired: false, transformFunction: null }, types: { classPropertyName: "types", publicName: "types", isSignal: true, isRequired: false, transformFunction: null } }, providers: [{ provide: MatFormFieldControl, useExisting: OrganizationSetComponent }], usesInheritance: true, ngImport: i0, template: "<yuv-autocomplete\n [busy]=\"busy()\"\n [formControl]=\"acFormControl\"\n #autocomplete\n [placeholder]=\"placeholder\"\n [disabled]=\"readonly()\"\n [autocompleteValues]=\"autocompleteRes\"\n [forceSelection]=\"true\"\n (autocompleteFnc)=\"autocompleteFn($event)\"\n [multiple]=\"true\"\n [maxItems]=\"multiselect() ? -1 : 1\"\n>\n <!-- template for item inside the dropdown -->\n <ng-template #optionTemplate let-item>\n <span class=\"chip\">{{ item.value.title }}</span>\n </ng-template>\n\n <!-- template for chip -->\n <ng-template #chipTemplate let-item>\n <span class=\"chip\">\n {{ item.value.title || '...' }}\n </span>\n </ng-template>\n</yuv-autocomplete>\n\n<mat-icon class=\"ymt-icon--size-s\" [matTooltip]=\"'yuv.form.element.organization-set.classify.icon.title' | translate\">\n {{ multiselect() ? 'group' : 'person' }}\n</mat-icon>\n", styles: [":host{display:flex;align-items:center}:host yuv-autocomplete{flex:1}:host mat-icon{color:var(--ymt-text-color-subtle)}\n"], dependencies: [{ kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "ngmodule", type: YuvAutocompleteModule }, { kind: "component", type: i2$2.AutocompleteComponent, selector: "yuv-autocomplete", inputs: ["ariaLabel", "busy", "multiple", "distinctValues", "addOnBlur", "minLength", "maxItems", "forceSelection", "autocompleteValues"], outputs: ["autocompleteFnc", "acBlur"] }, { kind: "ngmodule", type: MatTooltipModule }, { kind: "directive", type: i4.MatTooltip, selector: "[matTooltip]", inputs: ["matTooltipPosition", "matTooltipPositionAtOrigin", "matTooltipDisabled", "matTooltipShowDelay", "matTooltipHideDelay", "matTooltipTouchGestures", "matTooltip", "matTooltipClass"], exportAs: ["matTooltip"] }, { kind: "ngmodule", type: MatIconModule }, { kind: "component", type: i2.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1$1.FormControlDirective, selector: "[formControl]", inputs: ["formControl", "disabled", "ngModel"], outputs: ["ngModelChange"], exportAs: ["ngForm"] }, { kind: "pipe", type: TranslatePipe$1, name: "translate" }] }); }
1626
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "21.2.20", type: OrganizationSetComponent, isStandalone: true, selector: "yuv-organization-set", inputs: { situation: { classPropertyName: "situation", publicName: "situation", isSignal: true, isRequired: false, transformFunction: null }, multiselect: { classPropertyName: "multiselect", publicName: "multiselect", isSignal: true, isRequired: false, transformFunction: null }, readonly: { classPropertyName: "readonly", publicName: "readonly", isSignal: true, isRequired: false, transformFunction: null }, withMetadata: { classPropertyName: "withMetadata", publicName: "withMetadata", isSignal: true, isRequired: false, transformFunction: null }, autocompleteMinLength: { classPropertyName: "autocompleteMinLength", publicName: "autocompleteMinLength", isSignal: true, isRequired: false, transformFunction: null }, classifications: { classPropertyName: "classifications", publicName: "classifications", isSignal: true, isRequired: false, transformFunction: null }, types: { classPropertyName: "types", publicName: "types", isSignal: true, isRequired: false, transformFunction: null } }, providers: [{ provide: MatFormFieldControl, useExisting: OrganizationSetComponent }], usesInheritance: true, ngImport: i0, template: "<yuv-autocomplete\n [busy]=\"busy()\"\n [formControl]=\"acFormControl\"\n #autocomplete\n [placeholder]=\"placeholder\"\n [disabled]=\"readonly()\"\n [autocompleteValues]=\"autocompleteRes\"\n [forceSelection]=\"true\"\n (autocompleteFnc)=\"autocompleteFn($event)\"\n [multiple]=\"true\"\n [maxItems]=\"multiselect() ? -1 : 1\"\n>\n <!-- template for item inside the dropdown -->\n <ng-template #optionTemplate let-item>\n <span class=\"chip\">{{ item.value.title }}</span>\n </ng-template>\n\n <!-- template for chip -->\n <ng-template #chipTemplate let-item>\n <span class=\"chip\">\n {{ item.value.title || '...' }}\n </span>\n </ng-template>\n</yuv-autocomplete>\n\n<mat-icon class=\"ymt-icon--size-s\" [matTooltip]=\"'yuv.form.element.organization-set.classify.icon.title' | translate\">\n {{ multiselect() ? 'group' : 'person' }}\n</mat-icon>\n", styles: [":host{display:flex;align-items:center}:host yuv-autocomplete{flex:1}:host mat-icon{color:var(--ymt-text-color-subtle)}\n"], dependencies: [{ kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "ngmodule", type: YuvAutocompleteModule }, { kind: "component", type: i2$2.AutocompleteComponent, selector: "yuv-autocomplete", inputs: ["ariaLabel", "busy", "multiple", "distinctValues", "addOnBlur", "minLength", "maxItems", "forceSelection", "separatorKeys", "autocompleteValues"], outputs: ["autocompleteFnc", "acBlur"] }, { kind: "ngmodule", type: MatTooltipModule }, { kind: "directive", type: i4.MatTooltip, selector: "[matTooltip]", inputs: ["matTooltipPosition", "matTooltipPositionAtOrigin", "matTooltipDisabled", "matTooltipShowDelay", "matTooltipHideDelay", "matTooltipTouchGestures", "matTooltip", "matTooltipClass"], exportAs: ["matTooltip"] }, { kind: "ngmodule", type: MatIconModule }, { kind: "component", type: i2.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1$1.FormControlDirective, selector: "[formControl]", inputs: ["formControl", "disabled", "ngModel"], outputs: ["ngModelChange"], exportAs: ["ngForm"] }, { kind: "pipe", type: TranslatePipe$1, name: "translate" }] }); }
1602
1627
  }
1603
1628
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.20", ngImport: i0, type: OrganizationSetComponent, decorators: [{
1604
1629
  type: Component,
1605
1630
  args: [{ selector: 'yuv-organization-set', imports: [FormsModule, YuvAutocompleteModule, MatTooltipModule, MatIconModule, ReactiveFormsModule, TranslatePipe$1], providers: [{ provide: MatFormFieldControl, useExisting: OrganizationSetComponent }], template: "<yuv-autocomplete\n [busy]=\"busy()\"\n [formControl]=\"acFormControl\"\n #autocomplete\n [placeholder]=\"placeholder\"\n [disabled]=\"readonly()\"\n [autocompleteValues]=\"autocompleteRes\"\n [forceSelection]=\"true\"\n (autocompleteFnc)=\"autocompleteFn($event)\"\n [multiple]=\"true\"\n [maxItems]=\"multiselect() ? -1 : 1\"\n>\n <!-- template for item inside the dropdown -->\n <ng-template #optionTemplate let-item>\n <span class=\"chip\">{{ item.value.title }}</span>\n </ng-template>\n\n <!-- template for chip -->\n <ng-template #chipTemplate let-item>\n <span class=\"chip\">\n {{ item.value.title || '...' }}\n </span>\n </ng-template>\n</yuv-autocomplete>\n\n<mat-icon class=\"ymt-icon--size-s\" [matTooltip]=\"'yuv.form.element.organization-set.classify.icon.title' | translate\">\n {{ multiselect() ? 'group' : 'person' }}\n</mat-icon>\n", styles: [":host{display:flex;align-items:center}:host yuv-autocomplete{flex:1}:host mat-icon{color:var(--ymt-text-color-subtle)}\n"] }]
1606
1631
  }], propDecorators: { situation: [{ type: i0.Input, args: [{ isSignal: true, alias: "situation", required: false }] }], multiselect: [{ type: i0.Input, args: [{ isSignal: true, alias: "multiselect", required: false }] }], readonly: [{ type: i0.Input, args: [{ isSignal: true, alias: "readonly", required: false }] }], withMetadata: [{ type: i0.Input, args: [{ isSignal: true, alias: "withMetadata", required: false }] }], autocompleteMinLength: [{ type: i0.Input, args: [{ isSignal: true, alias: "autocompleteMinLength", required: false }] }], classifications: [{ type: i0.Input, args: [{ isSignal: true, alias: "classifications", required: false }] }], types: [{ type: i0.Input, args: [{ isSignal: true, alias: "types", required: false }] }] } });
1607
1632
 
1608
- const mapOrganizationNode = (innerValue) => map$1((node) => node
1609
- .filter((e) => !innerValue?.some((value) => value?.id === e.id))
1610
- .map((e) => ({
1611
- label: e.title,
1633
+ const mapOrganizationNode = (innerValue) => map$1((nodes) => nodes
1634
+ .filter((node) => !innerValue?.some((value) => value?.id === node.id))
1635
+ .map((node) => ({
1636
+ label: node.title,
1612
1637
  value: {
1613
- id: e.id,
1614
- title: e.title,
1615
- type: 'user' in e ? 'user' : 'role'
1638
+ id: node.id,
1639
+ title: node.title,
1640
+ type: 'user' in node ? 'user' : 'role'
1616
1641
  }
1617
1642
  })));
1618
1643
  /**
1619
1644
  * Creates form input for organization values.
1620
1645
  *
1621
1646
  * @example
1622
- * <yuv-organization [multiselect]="true"></yuv-organization>
1647
+ * <yuv-organization [multiselect]="true" />
1623
1648
  */
1624
1649
  class OrganizationComponent extends AbstractMatFormField {
1625
1650
  constructor() {
@@ -1627,16 +1652,24 @@ class OrganizationComponent extends AbstractMatFormField {
1627
1652
  this.#system = inject(SystemService);
1628
1653
  this.#idmService = inject(IdmService);
1629
1654
  this.#userService = inject(UserService);
1655
+ this.#appCache = inject(AppCacheService);
1630
1656
  this.#dRef = inject(DestroyRef);
1631
1657
  this.translate = inject(TranslateService);
1632
1658
  this.minLength = 2;
1633
1659
  this.busy = signal(false, ...(ngDevMode ? [{ debugName: "busy" }] : /* istanbul ignore next */ []));
1660
+ /**
1661
+ * ENTER only — organization titles contain commas (`Lastname, Firstname (username)`),
1662
+ * so committing free text on COMMA would split a typed name into two chips.
1663
+ */
1664
+ this.separatorKeys = [ENTER];
1634
1665
  this.acFormControl = new FormControl(undefined);
1635
1666
  this.ngControl = injectNgControl(this);
1636
1667
  this._innerValue = [];
1637
1668
  this.autocompleteRes = [];
1638
1669
  /**
1639
- * Possibles values are `EDIT` (default),`SEARCH`,`CREATE`. In search situation validation of the form element will be turned off, so you are able to enter search terms that do not meet the elements validators.
1670
+ * Possibles values are `EDIT` (default),`SEARCH`,`CREATE`.
1671
+ * In search situation validation of the form element will be turned off,
1672
+ * so you are able to enter search terms that do not meet the elements validators.
1640
1673
  */
1641
1674
  this.situation = input(undefined, ...(ngDevMode ? [{ debugName: "situation" }] : /* istanbul ignore next */ []));
1642
1675
  /**
@@ -1648,9 +1681,9 @@ class OrganizationComponent extends AbstractMatFormField {
1648
1681
  */
1649
1682
  // #organizationType: Classification = Classification.STRING_ORGANIZATION;
1650
1683
  this.#organizationType = computed(() => {
1651
- const c = this.classifications();
1652
- if (c?.length) {
1653
- const classifications = this.#system.getClassifications(c);
1684
+ const classificationsInput = this.classifications();
1685
+ if (classificationsInput?.length) {
1686
+ const classifications = this.#system.getClassifications(classificationsInput);
1654
1687
  if (classifications.has(Classification.STRING_ORGANIZATION_SET)) {
1655
1688
  return Classification.STRING_ORGANIZATION_SET;
1656
1689
  }
@@ -1658,20 +1691,26 @@ class OrganizationComponent extends AbstractMatFormField {
1658
1691
  return Classification.STRING_ORGANIZATION;
1659
1692
  }, ...(ngDevMode ? [{ debugName: "#organizationType" }] : /* istanbul ignore next */ []));
1660
1693
  this.classifications = input(undefined, ...(ngDevMode ? [{ debugName: "classifications" }] : /* istanbul ignore next */ []));
1661
- this.#filterRoles = computed(() => {
1662
- const t = this.#organizationType();
1663
- const c = this.classifications();
1664
- if (c?.length) {
1665
- const classifications = this.#system.getClassifications(c);
1666
- if (t === Classification.STRING_ORGANIZATION) {
1694
+ /**
1695
+ * Options of the organization classification, e.g. `['user', 'role']` for
1696
+ * `id:organization:set[user,role]`.
1697
+ */
1698
+ this.#classificationOptions = computed(() => {
1699
+ const orgaType = this.#organizationType();
1700
+ const classificationsInput = this.classifications();
1701
+ if (classificationsInput?.length) {
1702
+ const classifications = this.#system.getClassifications(classificationsInput);
1703
+ if (orgaType === Classification.STRING_ORGANIZATION) {
1667
1704
  return classifications.get(Classification.STRING_ORGANIZATION).options;
1668
1705
  }
1669
- else if (t === Classification.STRING_ORGANIZATION_SET) {
1706
+ else if (orgaType === Classification.STRING_ORGANIZATION_SET) {
1670
1707
  return classifications.get(Classification.STRING_ORGANIZATION_SET).options;
1671
1708
  }
1672
1709
  }
1673
1710
  return [];
1674
- }, ...(ngDevMode ? [{ debugName: "#filterRoles" }] : /* istanbul ignore next */ []));
1711
+ }, ...(ngDevMode ? [{ debugName: "#classificationOptions" }] : /* istanbul ignore next */ []));
1712
+ // The free text option is not a role — it must not end up in the /idm/users role filter.
1713
+ this.#filterRoles = computed(() => this.#classificationOptions().filter((opt) => opt !== FREE_TEXT_OPTION), ...(ngDevMode ? [{ debugName: "#filterRoles" }] : /* istanbul ignore next */ []));
1675
1714
  /**
1676
1715
  * Will prevent the input from being changed (default: false)
1677
1716
  */
@@ -1685,15 +1724,23 @@ class OrganizationComponent extends AbstractMatFormField {
1685
1724
  * or, if set to false only the ID
1686
1725
  */
1687
1726
  this.withMetadata = input(false, ...(ngDevMode ? [{ debugName: "withMetadata" }] : /* istanbul ignore next */ []));
1727
+ /**
1728
+ * Whether or not values that the autocomplete cannot resolve may be entered as free
1729
+ * text (default: false). Can also be enabled per field through the `freeText` option
1730
+ * of the organization classification, e.g. `id:organization[freeText]`.
1731
+ */
1732
+ this.allowFreeText = input(false, ...(ngDevMode ? [{ debugName: "allowFreeText" }] : /* istanbul ignore next */ []));
1733
+ this.freeText = computed(() => this.allowFreeText() || this.#classificationOptions().includes(FREE_TEXT_OPTION), ...(ngDevMode ? [{ debugName: "freeText" }] : /* istanbul ignore next */ []));
1688
1734
  // eslint-disable-next-line @typescript-eslint/no-empty-function
1689
1735
  this.propagateChange = (_) => { };
1690
1736
  }
1691
1737
  #system;
1692
1738
  #idmService;
1693
1739
  #userService;
1740
+ #appCache;
1694
1741
  #dRef;
1695
- set innerValue(iv) {
1696
- this._innerValue = iv || [];
1742
+ set innerValue(iValue) {
1743
+ this._innerValue = iValue || [];
1697
1744
  }
1698
1745
  get innerValue() {
1699
1746
  return this._innerValue;
@@ -1703,6 +1750,12 @@ class OrganizationComponent extends AbstractMatFormField {
1703
1750
  */
1704
1751
  // #organizationType: Classification = Classification.STRING_ORGANIZATION;
1705
1752
  #organizationType;
1753
+ /**
1754
+ * Options of the organization classification, e.g. `['user', 'role']` for
1755
+ * `id:organization:set[user,role]`.
1756
+ */
1757
+ #classificationOptions;
1758
+ // The free text option is not a role — it must not end up in the /idm/users role filter.
1706
1759
  #filterRoles;
1707
1760
  writeValue(value) {
1708
1761
  this.value = value;
@@ -1725,10 +1778,6 @@ class OrganizationComponent extends AbstractMatFormField {
1725
1778
  }
1726
1779
  // eslint-disable-next-line @typescript-eslint/no-empty-function
1727
1780
  registerOnTouched(fn) { }
1728
- propagate() {
1729
- this.value = this.#getPropagateValue();
1730
- this.propagateChange(this.value);
1731
- }
1732
1781
  setDisabledState(isDisabled) {
1733
1782
  if (isDisabled) {
1734
1783
  this.acFormControl.disable();
@@ -1738,65 +1787,16 @@ class OrganizationComponent extends AbstractMatFormField {
1738
1787
  }
1739
1788
  this.disabled = isDisabled;
1740
1789
  }
1741
- #getPropagateValue() {
1742
- const value = this.innerValue.map((v) => this.withMetadata() ? JSON.stringify({ id: v.id, title: v.title, type: v.type }) : v.id);
1743
- return this.multiselect() ? value : value[0];
1744
- }
1745
1790
  resolveFn(value) {
1746
- // check if entry is a user
1747
- const userRegExp = new RegExp('^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$');
1748
- const tasks = value.map((v) => v.match(userRegExp)
1749
- ? this.#idmService.getUserById(v).pipe(map$1((res) => {
1750
- return res
1751
- ? {
1752
- id: res.id,
1753
- title: res.title,
1754
- type: 'user'
1755
- }
1756
- : {
1757
- id: v,
1758
- title: v,
1759
- type: 'user',
1760
- notFound: true
1761
- };
1762
- }))
1763
- : of({
1764
- id: v,
1765
- title: v,
1766
- type: 'role'
1767
- }));
1768
- forkJoin(tasks).subscribe((data) => {
1791
+ // Free text and role names are both plain strings on the wire, so telling them apart
1792
+ // requires the role catalog. Only load it when free text is actually enabled.
1793
+ const roleNames$ = this.freeText() ? this.#roleNames() : of(null);
1794
+ roleNames$
1795
+ .pipe(switchMap((roleNames) => forkJoin(value.map((v) => this.#resolveEntry(v, roleNames)))), takeUntilDestroyed(this.#dRef))
1796
+ .subscribe((data) => {
1769
1797
  this.#updateAutocompleteControl(data);
1770
1798
  });
1771
1799
  }
1772
- #updateAutocompleteControl(data) {
1773
- this.innerValue = data;
1774
- const mapped = this.innerValue.map((n) => ({
1775
- label: n.title,
1776
- value: n
1777
- }));
1778
- this.acFormControl.setValue(this.multiselect() ? mapped : [mapped[0]], { emitEvent: false });
1779
- this.acFormControl.updateValueAndValidity({ emitEvent: false });
1780
- }
1781
- #resolveOrganization(query, excludeMe, filterRoles) {
1782
- return this.#userService.queryUser(query, excludeMe, filterRoles).pipe(mapOrganizationNode(this.innerValue), catchError(() => of([])));
1783
- }
1784
- #resolveOrganizationSet(query, filterRoles) {
1785
- return this.#idmService.queryOrganizationEntity(query, filterRoles).pipe(mapOrganizationNode(this.innerValue), catchError(() => of([])));
1786
- }
1787
- #getMetadataString(value) {
1788
- try {
1789
- if (Array.isArray(value)) {
1790
- return value.map((v) => JSON.parse(v));
1791
- }
1792
- else {
1793
- return JSON.parse(value);
1794
- }
1795
- }
1796
- catch (e) {
1797
- return undefined;
1798
- }
1799
- }
1800
1800
  autocompleteFn(query) {
1801
1801
  if (query.length >= this.minLength) {
1802
1802
  this.busy.set(true);
@@ -1824,18 +1824,126 @@ class OrganizationComponent extends AbstractMatFormField {
1824
1824
  }
1825
1825
  });
1826
1826
  this.acFormControl.updateValueAndValidity({ emitEvent: false });
1827
- this.acFormControl.valueChanges.subscribe((v) => {
1827
+ this.acFormControl.valueChanges.pipe(takeUntilDestroyed(this.#dRef)).subscribe((v) => {
1828
1828
  if (!Array.isArray(v))
1829
1829
  v = v ? [v] : [];
1830
- this.innerValue = v.map((i) => i.value);
1830
+ this.innerValue = v.map((i) => this.#toNode(i));
1831
1831
  this.propagate();
1832
1832
  });
1833
1833
  }
1834
1834
  ngOnDestroy() {
1835
1835
  super.onNgOnDestroy();
1836
1836
  }
1837
+ #resolveEntry(value, roleNames) {
1838
+ // check if entry is a user
1839
+ if (USER_ID_REGEX.test(value)) {
1840
+ return this.#idmService.getUserById(value).pipe(map$1((res) => {
1841
+ return res
1842
+ ? {
1843
+ id: res.id,
1844
+ title: res.title,
1845
+ type: 'user'
1846
+ }
1847
+ : {
1848
+ id: value,
1849
+ title: value,
1850
+ type: 'user',
1851
+ notFound: true
1852
+ };
1853
+ }));
1854
+ }
1855
+ // Roles are a closed set, so a value the catalog does not know can only have been typed.
1856
+ if (roleNames && !roleNames.includes(value)) {
1857
+ return of({
1858
+ id: value,
1859
+ title: value,
1860
+ type: 'text'
1861
+ });
1862
+ }
1863
+ return of({
1864
+ id: value,
1865
+ title: value,
1866
+ type: 'role'
1867
+ });
1868
+ }
1869
+ /**
1870
+ * Role names from the shared cache, falling back to IDM. Mirrors the organization
1871
+ * renderer so both sides partition stored values identically and share one request.
1872
+ */
1873
+ #roleNames() {
1874
+ return this.#appCache.getItem(STORAGE_ROLES_KEY).pipe(switchMap((cachedRoles) => cachedRoles
1875
+ ? of(cachedRoles)
1876
+ : this.#idmService.getRoles().pipe(
1877
+ // Only persist when the backend actually returned something — caching an
1878
+ // empty array would mask later successful fetches.
1879
+ switchMap((roles) => roles.length ? this.#appCache.setItem(STORAGE_ROLES_KEY, roles).pipe(map$1(() => roles)) : of(roles)))), map$1((roles) => roles.map((role) => role.name)), catchError(() => of([])));
1880
+ }
1881
+ #getPropagateValue() {
1882
+ const value = this.innerValue.map((v) => this.withMetadata()
1883
+ ? // `text` is a client-side distinction only — the stored value stays a plain string and
1884
+ // the metadata `type` keeps its existing vocabulary, so no consumer sees a new value.
1885
+ // Trade-off: in metadata mode free text is not marked as such again after a reload.
1886
+ JSON.stringify({ id: v.id, title: v.title, type: v.type === 'text' ? 'role' : v.type })
1887
+ : v.id);
1888
+ return this.multiselect() ? value : value[0];
1889
+ }
1890
+ /**
1891
+ * The autocomplete emits free text as a raw string instead of a node, so it has to be
1892
+ * lifted back into an {@link OrganizationNode} before it reaches the form control value.
1893
+ */
1894
+ #toNode(item) {
1895
+ return typeof item.value === 'string'
1896
+ ? { id: item.value, title: item.value, type: 'text' }
1897
+ : item.value;
1898
+ }
1899
+ #updateAutocompleteControl(data) {
1900
+ this.innerValue = data;
1901
+ const freeTextHint = this.translate.instant('yuv.form.element.organization.freetext.tooltip');
1902
+ const mapped = this.innerValue.map((iVal) => ({
1903
+ label: iVal.title,
1904
+ value: iVal,
1905
+ // Marks stored free text as such again, so the chip looks the same before and after reload.
1906
+ custom: iVal.type === 'text',
1907
+ hint: iVal.type === 'text' ? freeTextHint : undefined
1908
+ }));
1909
+ this.acFormControl.setValue(this.multiselect() ? mapped : [mapped[0]], { emitEvent: false });
1910
+ this.acFormControl.updateValueAndValidity({ emitEvent: false });
1911
+ }
1912
+ #resolveOrganization(query, excludeMe, filterRoles) {
1913
+ return this.#userService.queryUser(query, excludeMe, filterRoles).pipe(mapOrganizationNode(this.innerValue), catchError(() => of([])));
1914
+ }
1915
+ #resolveOrganizationSet(query, filterRoles) {
1916
+ return this.#idmService.queryOrganizationEntity(query, filterRoles).pipe(mapOrganizationNode(this.innerValue), catchError(() => of([])));
1917
+ }
1918
+ #getMetadataString(value) {
1919
+ try {
1920
+ if (Array.isArray(value)) {
1921
+ const parsed = value.map((val) => JSON.parse(val));
1922
+ return parsed.every((entry) => this.#isMetadata(entry)) ? parsed : undefined;
1923
+ }
1924
+ else {
1925
+ const parsed = JSON.parse(value);
1926
+ return this.#isMetadata(parsed) ? parsed : undefined;
1927
+ }
1928
+ }
1929
+ catch (error) {
1930
+ return undefined;
1931
+ }
1932
+ }
1933
+ /**
1934
+ * Guards against plain values that happen to be valid JSON — free text like `42`, `true`
1935
+ * or `[1,2]` would otherwise be mistaken for metadata and yield a chip without id/title.
1936
+ * Deliberately permissive about the id's runtime type so real metadata is never rejected.
1937
+ */
1938
+ #isMetadata(parsed) {
1939
+ return !!parsed && typeof parsed === 'object' && !Array.isArray(parsed) && 'id' in parsed;
1940
+ }
1941
+ propagate() {
1942
+ this.value = this.#getPropagateValue();
1943
+ this.propagateChange(this.value);
1944
+ }
1837
1945
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.20", ngImport: i0, type: OrganizationComponent, deps: null, target: i0.ɵɵFactoryTarget.Component }); }
1838
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.20", type: OrganizationComponent, isStandalone: true, selector: "yuv-organization", inputs: { situation: { classPropertyName: "situation", publicName: "situation", isSignal: true, isRequired: false, transformFunction: null }, multiselect: { classPropertyName: "multiselect", publicName: "multiselect", isSignal: true, isRequired: false, transformFunction: null }, classifications: { classPropertyName: "classifications", publicName: "classifications", isSignal: true, isRequired: false, transformFunction: null }, readonly: { classPropertyName: "readonly", publicName: "readonly", isSignal: true, isRequired: false, transformFunction: null }, excludeMe: { classPropertyName: "excludeMe", publicName: "excludeMe", isSignal: true, isRequired: false, transformFunction: null }, withMetadata: { classPropertyName: "withMetadata", publicName: "withMetadata", isSignal: true, isRequired: false, transformFunction: null } }, providers: [{ provide: MatFormFieldControl, useExisting: OrganizationComponent }], usesInheritance: true, ngImport: i0, template: "<yuv-autocomplete\n [required]=\"required\"\n [busy]=\"busy()\"\n [formControl]=\"acFormControl\"\n #autocomplete\n [placeholder]=\"placeholder\"\n [disabled]=\"readonly()\"\n [autocompleteValues]=\"autocompleteRes\"\n [forceSelection]=\"true\"\n [distinctValues]=\"true\"\n (autocompleteFnc)=\"autocompleteFn($event)\"\n [multiple]=\"true\"\n [maxItems]=\"multiselect() ? -1 : 1\"\n>\n <!-- template for item inside the dropdown -->\n <ng-template #optionTemplate let-item>\n @if (item.value) {\n <span class=\"chip\">{{ item.value.title }}</span>\n }\n </ng-template>\n\n <!-- template for chip -->\n <ng-template #chipTemplate let-item>\n @if (item.value) {\n <span class=\"chip\" [ngClass]=\"{ notFound: item.value.notFound }\" [matTooltip]=\"item.value.titleString\">\n {{ item.value.title || '...' }}\n </span>\n }\n </ng-template>\n</yuv-autocomplete>\n\n<mat-icon class=\"ymt-icon--size-s\" [matTooltip]=\"'yuv.form.element.organization.classify.icon.title' | translate\">\n {{ multiselect() ? 'group' : 'person' }}\n</mat-icon>\n", styles: [":host{display:flex;align-items:center}:host .chip.notFound{color:var(--ymt-on-danger-container);text-decoration:line-through}:host .chip.notFound:before{content:\"!\";display:inline-block;background-color:var(--ymt-danger-container);color:#fff;border-radius:2px;padding-inline:.3em;text-decoration:none;margin-inline-end:.75em}:host yuv-autocomplete{flex:1}:host mat-icon{color:var(--ymt-text-color-subtle)}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1$3.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$1.RequiredValidator, selector: ":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]", inputs: ["required"] }, { kind: "ngmodule", type: YuvAutocompleteModule }, { kind: "component", type: i2$2.AutocompleteComponent, selector: "yuv-autocomplete", inputs: ["ariaLabel", "busy", "multiple", "distinctValues", "addOnBlur", "minLength", "maxItems", "forceSelection", "autocompleteValues"], outputs: ["autocompleteFnc", "acBlur"] }, { kind: "ngmodule", type: MatTooltipModule }, { kind: "directive", type: i4.MatTooltip, selector: "[matTooltip]", inputs: ["matTooltipPosition", "matTooltipPositionAtOrigin", "matTooltipDisabled", "matTooltipShowDelay", "matTooltipHideDelay", "matTooltipTouchGestures", "matTooltip", "matTooltipClass"], exportAs: ["matTooltip"] }, { kind: "ngmodule", type: MatIconModule }, { kind: "component", type: i2.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1$1.FormControlDirective, selector: "[formControl]", inputs: ["formControl", "disabled", "ngModel"], outputs: ["ngModelChange"], exportAs: ["ngForm"] }, { kind: "pipe", type: TranslatePipe, name: "translate" }] }); }
1946
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.20", type: OrganizationComponent, isStandalone: true, selector: "yuv-organization", inputs: { situation: { classPropertyName: "situation", publicName: "situation", isSignal: true, isRequired: false, transformFunction: null }, multiselect: { classPropertyName: "multiselect", publicName: "multiselect", isSignal: true, isRequired: false, transformFunction: null }, classifications: { classPropertyName: "classifications", publicName: "classifications", isSignal: true, isRequired: false, transformFunction: null }, readonly: { classPropertyName: "readonly", publicName: "readonly", isSignal: true, isRequired: false, transformFunction: null }, excludeMe: { classPropertyName: "excludeMe", publicName: "excludeMe", isSignal: true, isRequired: false, transformFunction: null }, withMetadata: { classPropertyName: "withMetadata", publicName: "withMetadata", isSignal: true, isRequired: false, transformFunction: null }, allowFreeText: { classPropertyName: "allowFreeText", publicName: "allowFreeText", isSignal: true, isRequired: false, transformFunction: null } }, providers: [{ provide: MatFormFieldControl, useExisting: OrganizationComponent }], usesInheritance: true, ngImport: i0, template: "<yuv-autocomplete\n [required]=\"required\"\n [busy]=\"busy()\"\n [formControl]=\"acFormControl\"\n #autocomplete\n [placeholder]=\"placeholder\"\n [disabled]=\"readonly()\"\n [autocompleteValues]=\"autocompleteRes\"\n [forceSelection]=\"!freeText()\"\n [distinctValues]=\"true\"\n [separatorKeys]=\"separatorKeys\"\n (autocompleteFnc)=\"autocompleteFn($event)\"\n [multiple]=\"true\"\n [maxItems]=\"multiselect() ? -1 : 1\"\n>\n <!-- template for item inside the dropdown -->\n <ng-template #optionTemplate let-item>\n @if (item.value) {\n <span class=\"chip\">{{ item.value.title }}</span>\n }\n </ng-template>\n\n <!-- template for chip -->\n <ng-template #chipTemplate let-item>\n @if (item.value) {\n <span class=\"chip\" [ngClass]=\"{ notFound: item.value.notFound }\" [matTooltip]=\"item.value.titleString\">\n {{ item.value.title || '...' }}\n </span>\n }\n </ng-template>\n</yuv-autocomplete>\n\n<mat-icon class=\"ymt-icon--size-s\" [matTooltip]=\"'yuv.form.element.organization.classify.icon.title' | translate\">\n {{ multiselect() ? 'group' : 'person' }}\n</mat-icon>\n", styles: [":host{display:flex;align-items:center}:host .chip.notFound{color:var(--ymt-on-danger-container);text-decoration:line-through}:host .chip.notFound:before{content:\"!\";display:inline-block;background-color:var(--ymt-danger-container);color:#fff;border-radius:2px;padding-inline:.3em;text-decoration:none;margin-inline-end:.75em}:host yuv-autocomplete{flex:1}:host mat-icon{color:var(--ymt-text-color-subtle)}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1$3.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$1.RequiredValidator, selector: ":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]", inputs: ["required"] }, { kind: "ngmodule", type: YuvAutocompleteModule }, { kind: "component", type: i2$2.AutocompleteComponent, selector: "yuv-autocomplete", inputs: ["ariaLabel", "busy", "multiple", "distinctValues", "addOnBlur", "minLength", "maxItems", "forceSelection", "separatorKeys", "autocompleteValues"], outputs: ["autocompleteFnc", "acBlur"] }, { kind: "ngmodule", type: MatTooltipModule }, { kind: "directive", type: i4.MatTooltip, selector: "[matTooltip]", inputs: ["matTooltipPosition", "matTooltipPositionAtOrigin", "matTooltipDisabled", "matTooltipShowDelay", "matTooltipHideDelay", "matTooltipTouchGestures", "matTooltip", "matTooltipClass"], exportAs: ["matTooltip"] }, { kind: "ngmodule", type: MatIconModule }, { kind: "component", type: i2.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1$1.FormControlDirective, selector: "[formControl]", inputs: ["formControl", "disabled", "ngModel"], outputs: ["ngModelChange"], exportAs: ["ngForm"] }, { kind: "pipe", type: TranslatePipe, name: "translate" }] }); }
1839
1947
  }
1840
1948
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.20", ngImport: i0, type: OrganizationComponent, decorators: [{
1841
1949
  type: Component,
@@ -1847,8 +1955,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.20", ngImpo
1847
1955
  MatIconModule,
1848
1956
  ReactiveFormsModule,
1849
1957
  TranslatePipe
1850
- ], providers: [{ provide: MatFormFieldControl, useExisting: OrganizationComponent }], template: "<yuv-autocomplete\n [required]=\"required\"\n [busy]=\"busy()\"\n [formControl]=\"acFormControl\"\n #autocomplete\n [placeholder]=\"placeholder\"\n [disabled]=\"readonly()\"\n [autocompleteValues]=\"autocompleteRes\"\n [forceSelection]=\"true\"\n [distinctValues]=\"true\"\n (autocompleteFnc)=\"autocompleteFn($event)\"\n [multiple]=\"true\"\n [maxItems]=\"multiselect() ? -1 : 1\"\n>\n <!-- template for item inside the dropdown -->\n <ng-template #optionTemplate let-item>\n @if (item.value) {\n <span class=\"chip\">{{ item.value.title }}</span>\n }\n </ng-template>\n\n <!-- template for chip -->\n <ng-template #chipTemplate let-item>\n @if (item.value) {\n <span class=\"chip\" [ngClass]=\"{ notFound: item.value.notFound }\" [matTooltip]=\"item.value.titleString\">\n {{ item.value.title || '...' }}\n </span>\n }\n </ng-template>\n</yuv-autocomplete>\n\n<mat-icon class=\"ymt-icon--size-s\" [matTooltip]=\"'yuv.form.element.organization.classify.icon.title' | translate\">\n {{ multiselect() ? 'group' : 'person' }}\n</mat-icon>\n", styles: [":host{display:flex;align-items:center}:host .chip.notFound{color:var(--ymt-on-danger-container);text-decoration:line-through}:host .chip.notFound:before{content:\"!\";display:inline-block;background-color:var(--ymt-danger-container);color:#fff;border-radius:2px;padding-inline:.3em;text-decoration:none;margin-inline-end:.75em}:host yuv-autocomplete{flex:1}:host mat-icon{color:var(--ymt-text-color-subtle)}\n"] }]
1851
- }], propDecorators: { situation: [{ type: i0.Input, args: [{ isSignal: true, alias: "situation", required: false }] }], multiselect: [{ type: i0.Input, args: [{ isSignal: true, alias: "multiselect", required: false }] }], classifications: [{ type: i0.Input, args: [{ isSignal: true, alias: "classifications", required: false }] }], readonly: [{ type: i0.Input, args: [{ isSignal: true, alias: "readonly", required: false }] }], excludeMe: [{ type: i0.Input, args: [{ isSignal: true, alias: "excludeMe", required: false }] }], withMetadata: [{ type: i0.Input, args: [{ isSignal: true, alias: "withMetadata", required: false }] }] } });
1958
+ ], providers: [{ provide: MatFormFieldControl, useExisting: OrganizationComponent }], template: "<yuv-autocomplete\n [required]=\"required\"\n [busy]=\"busy()\"\n [formControl]=\"acFormControl\"\n #autocomplete\n [placeholder]=\"placeholder\"\n [disabled]=\"readonly()\"\n [autocompleteValues]=\"autocompleteRes\"\n [forceSelection]=\"!freeText()\"\n [distinctValues]=\"true\"\n [separatorKeys]=\"separatorKeys\"\n (autocompleteFnc)=\"autocompleteFn($event)\"\n [multiple]=\"true\"\n [maxItems]=\"multiselect() ? -1 : 1\"\n>\n <!-- template for item inside the dropdown -->\n <ng-template #optionTemplate let-item>\n @if (item.value) {\n <span class=\"chip\">{{ item.value.title }}</span>\n }\n </ng-template>\n\n <!-- template for chip -->\n <ng-template #chipTemplate let-item>\n @if (item.value) {\n <span class=\"chip\" [ngClass]=\"{ notFound: item.value.notFound }\" [matTooltip]=\"item.value.titleString\">\n {{ item.value.title || '...' }}\n </span>\n }\n </ng-template>\n</yuv-autocomplete>\n\n<mat-icon class=\"ymt-icon--size-s\" [matTooltip]=\"'yuv.form.element.organization.classify.icon.title' | translate\">\n {{ multiselect() ? 'group' : 'person' }}\n</mat-icon>\n", styles: [":host{display:flex;align-items:center}:host .chip.notFound{color:var(--ymt-on-danger-container);text-decoration:line-through}:host .chip.notFound:before{content:\"!\";display:inline-block;background-color:var(--ymt-danger-container);color:#fff;border-radius:2px;padding-inline:.3em;text-decoration:none;margin-inline-end:.75em}:host yuv-autocomplete{flex:1}:host mat-icon{color:var(--ymt-text-color-subtle)}\n"] }]
1959
+ }], propDecorators: { situation: [{ type: i0.Input, args: [{ isSignal: true, alias: "situation", required: false }] }], multiselect: [{ type: i0.Input, args: [{ isSignal: true, alias: "multiselect", required: false }] }], classifications: [{ type: i0.Input, args: [{ isSignal: true, alias: "classifications", required: false }] }], readonly: [{ type: i0.Input, args: [{ isSignal: true, alias: "readonly", required: false }] }], excludeMe: [{ type: i0.Input, args: [{ isSignal: true, alias: "excludeMe", required: false }] }], withMetadata: [{ type: i0.Input, args: [{ isSignal: true, alias: "withMetadata", required: false }] }], allowFreeText: [{ type: i0.Input, args: [{ isSignal: true, alias: "allowFreeText", required: false }] }] } });
1852
1960
 
1853
1961
  class DateRangePickerComponent {
1854
1962
  constructor() {
@@ -2402,7 +2510,7 @@ class ReferenceComponent extends AbstractMatFormField {
2402
2510
  super.onNgOnDestroy();
2403
2511
  }
2404
2512
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.20", ngImport: i0, type: ReferenceComponent, deps: null, target: i0.ɵɵFactoryTarget.Component }); }
2405
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.20", type: ReferenceComponent, isStandalone: true, selector: "yuv-reference", inputs: { situation: { classPropertyName: "situation", publicName: "situation", isSignal: true, isRequired: false, transformFunction: null }, multiselect: { classPropertyName: "multiselect", publicName: "multiselect", isSignal: true, isRequired: false, transformFunction: null }, classifications: { classPropertyName: "classifications", publicName: "classifications", isSignal: true, isRequired: false, transformFunction: null }, readonly: { classPropertyName: "readonly", publicName: "readonly", isSignal: true, isRequired: false, transformFunction: null }, minChars: { classPropertyName: "minChars", publicName: "minChars", isSignal: true, isRequired: false, transformFunction: null }, maxSuggestions: { classPropertyName: "maxSuggestions", publicName: "maxSuggestions", isSignal: true, isRequired: false, transformFunction: null } }, providers: [{ provide: MatFormFieldControl, useExisting: ReferenceComponent }], usesInheritance: true, ngImport: i0, template: "<yuv-autocomplete\n [required]=\"required\"\n [busy]=\"busy()\"\n [formControl]=\"acFormControl\"\n #autocomplete\n [placeholder]=\"placeholder\"\n [disabled]=\"readonly()\"\n [autocompleteValues]=\"autocompleteRes\"\n [minLength]=\"minChars()\"\n [forceSelection]=\"true\"\n [distinctValues]=\"true\"\n (autocompleteFnc)=\"autocompleteFn($event)\"\n [multiple]=\"true\"\n [maxItems]=\"multiselect() ? -1 : 1\"\n>\n <!-- template for item inside the dropdown -->\n <ng-template #optionTemplate let-item>\n @if (item.value) {\n <span class=\"option\">{{ item.value.title }}</span>\n }\n </ng-template>\n\n <!-- template for chip -->\n <ng-template #chipTemplate let-item>\n @if (item.value) {\n <span class=\"chip\" [class.notFound]=\"item.value.notFound\" [matTooltip]=\"item.value.title\">\n {{ item.value.title || '...' }}\n @if (canOpen(item.value)) {\n <mat-icon\n class=\"open ymt-icon--size-s\"\n (click)=\"open(item.value); $event.stopPropagation()\"\n [matTooltip]=\"'yuv.form.element.reference.open.title' | translate\"\n >\n open_in_new\n </mat-icon>\n }\n </span>\n }\n </ng-template>\n</yuv-autocomplete>\n\n<mat-icon class=\"ymt-icon--size-s\" [matTooltip]=\"'yuv.form.element.reference.classify.icon.title' | translate\">link</mat-icon>\n", styles: [":host{display:flex;align-items:center}:host .chip{display:inline-flex;align-items:center;gap:.25em}:host .chip .open{cursor:pointer;color:var(--ymt-text-color-subtle)}:host .chip .open:hover{color:var(--ymt-primary)}:host .chip.notFound{color:var(--ymt-on-danger-container);text-decoration:line-through}:host .chip.notFound:before{content:\"!\";display:inline-block;background-color:var(--ymt-danger-container);color:var(--ymt-on-danger-container);border-radius:2px;padding-inline:.3em;text-decoration:none;margin-inline-end:.75em}:host yuv-autocomplete{flex:1}:host mat-icon{color:var(--ymt-text-color-subtle)}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$1.RequiredValidator, selector: ":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]", inputs: ["required"] }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1$1.FormControlDirective, selector: "[formControl]", inputs: ["formControl", "disabled", "ngModel"], outputs: ["ngModelChange"], exportAs: ["ngForm"] }, { kind: "ngmodule", type: YuvAutocompleteModule }, { kind: "component", type: i2$2.AutocompleteComponent, selector: "yuv-autocomplete", inputs: ["ariaLabel", "busy", "multiple", "distinctValues", "addOnBlur", "minLength", "maxItems", "forceSelection", "autocompleteValues"], outputs: ["autocompleteFnc", "acBlur"] }, { kind: "ngmodule", type: MatIconModule }, { kind: "component", type: i2.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "ngmodule", type: MatTooltipModule }, { kind: "directive", type: i4.MatTooltip, selector: "[matTooltip]", inputs: ["matTooltipPosition", "matTooltipPositionAtOrigin", "matTooltipDisabled", "matTooltipShowDelay", "matTooltipHideDelay", "matTooltipTouchGestures", "matTooltip", "matTooltipClass"], exportAs: ["matTooltip"] }, { kind: "pipe", type: TranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
2513
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.20", type: ReferenceComponent, isStandalone: true, selector: "yuv-reference", inputs: { situation: { classPropertyName: "situation", publicName: "situation", isSignal: true, isRequired: false, transformFunction: null }, multiselect: { classPropertyName: "multiselect", publicName: "multiselect", isSignal: true, isRequired: false, transformFunction: null }, classifications: { classPropertyName: "classifications", publicName: "classifications", isSignal: true, isRequired: false, transformFunction: null }, readonly: { classPropertyName: "readonly", publicName: "readonly", isSignal: true, isRequired: false, transformFunction: null }, minChars: { classPropertyName: "minChars", publicName: "minChars", isSignal: true, isRequired: false, transformFunction: null }, maxSuggestions: { classPropertyName: "maxSuggestions", publicName: "maxSuggestions", isSignal: true, isRequired: false, transformFunction: null } }, providers: [{ provide: MatFormFieldControl, useExisting: ReferenceComponent }], usesInheritance: true, ngImport: i0, template: "<yuv-autocomplete\n [required]=\"required\"\n [busy]=\"busy()\"\n [formControl]=\"acFormControl\"\n #autocomplete\n [placeholder]=\"placeholder\"\n [disabled]=\"readonly()\"\n [autocompleteValues]=\"autocompleteRes\"\n [minLength]=\"minChars()\"\n [forceSelection]=\"true\"\n [distinctValues]=\"true\"\n (autocompleteFnc)=\"autocompleteFn($event)\"\n [multiple]=\"true\"\n [maxItems]=\"multiselect() ? -1 : 1\"\n>\n <!-- template for item inside the dropdown -->\n <ng-template #optionTemplate let-item>\n @if (item.value) {\n <span class=\"option\">{{ item.value.title }}</span>\n }\n </ng-template>\n\n <!-- template for chip -->\n <ng-template #chipTemplate let-item>\n @if (item.value) {\n <span class=\"chip\" [class.notFound]=\"item.value.notFound\" [matTooltip]=\"item.value.title\">\n {{ item.value.title || '...' }}\n @if (canOpen(item.value)) {\n <mat-icon\n class=\"open ymt-icon--size-s\"\n (click)=\"open(item.value); $event.stopPropagation()\"\n [matTooltip]=\"'yuv.form.element.reference.open.title' | translate\"\n >\n open_in_new\n </mat-icon>\n }\n </span>\n }\n </ng-template>\n</yuv-autocomplete>\n\n<mat-icon class=\"ymt-icon--size-s\" [matTooltip]=\"'yuv.form.element.reference.classify.icon.title' | translate\">link</mat-icon>\n", styles: [":host{display:flex;align-items:center}:host .chip{display:inline-flex;align-items:center;gap:.25em}:host .chip .open{cursor:pointer;color:var(--ymt-text-color-subtle)}:host .chip .open:hover{color:var(--ymt-primary)}:host .chip.notFound{color:var(--ymt-on-danger-container);text-decoration:line-through}:host .chip.notFound:before{content:\"!\";display:inline-block;background-color:var(--ymt-danger-container);color:var(--ymt-on-danger-container);border-radius:2px;padding-inline:.3em;text-decoration:none;margin-inline-end:.75em}:host yuv-autocomplete{flex:1}:host mat-icon{color:var(--ymt-text-color-subtle)}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$1.RequiredValidator, selector: ":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]", inputs: ["required"] }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1$1.FormControlDirective, selector: "[formControl]", inputs: ["formControl", "disabled", "ngModel"], outputs: ["ngModelChange"], exportAs: ["ngForm"] }, { kind: "ngmodule", type: YuvAutocompleteModule }, { kind: "component", type: i2$2.AutocompleteComponent, selector: "yuv-autocomplete", inputs: ["ariaLabel", "busy", "multiple", "distinctValues", "addOnBlur", "minLength", "maxItems", "forceSelection", "separatorKeys", "autocompleteValues"], outputs: ["autocompleteFnc", "acBlur"] }, { kind: "ngmodule", type: MatIconModule }, { kind: "component", type: i2.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "ngmodule", type: MatTooltipModule }, { kind: "directive", type: i4.MatTooltip, selector: "[matTooltip]", inputs: ["matTooltipPosition", "matTooltipPositionAtOrigin", "matTooltipDisabled", "matTooltipShowDelay", "matTooltipHideDelay", "matTooltipTouchGestures", "matTooltip", "matTooltipClass"], exportAs: ["matTooltip"] }, { kind: "pipe", type: TranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
2406
2514
  }
2407
2515
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.20", ngImport: i0, type: ReferenceComponent, decorators: [{
2408
2516
  type: Component,