@yuuvis/client-framework 3.14.0 → 3.15.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.
@@ -35,6 +35,14 @@ class SequenceListComponent {
35
35
  * Maximum number of sequence items (defaults to 100).
36
36
  */
37
37
  this.maxLength = input(100, ...(ngDevMode ? [{ debugName: "maxLength" }] : /* istanbul ignore next */ []));
38
+ /**
39
+ * Minimum number of sequence items that must remain (defaults to 1, matching
40
+ * the original behaviour where a list can never be emptied). Consumers that
41
+ * represent a tail of optional follow-up items (e.g. the remaining steps of
42
+ * an already-started routing list) can set this to 0 to allow removing the
43
+ * last remaining item.
44
+ */
45
+ this.minLength = input(1, ...(ngDevMode ? [{ debugName: "minLength" }] : /* istanbul ignore next */ []));
38
46
  this.maxLengthExceeded = signal(false, ...(ngDevMode ? [{ debugName: "maxLengthExceeded" }] : /* istanbul ignore next */ []));
39
47
  this.controlCount = signal(0, ...(ngDevMode ? [{ debugName: "controlCount" }] : /* istanbul ignore next */ []));
40
48
  // dynamic form for sequence items
@@ -66,7 +74,7 @@ class SequenceListComponent {
66
74
  writeValue(value) {
67
75
  this.entries = value || [];
68
76
  this.formItemArray.clear();
69
- if (this.entries.length === 0)
77
+ if (this.entries.length === 0 && this.minLength() > 0)
70
78
  this.addItem();
71
79
  this.entries.forEach((entry) => {
72
80
  this.formItemArray.push(this.#generateSequenceItem(entry));
@@ -120,11 +128,11 @@ class SequenceListComponent {
120
128
  #scrollToItem(idx) {
121
129
  afterNextRender(() => {
122
130
  const items = this.#eleRef.nativeElement.querySelectorAll('.item');
123
- items[idx]?.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
131
+ items[idx]?.scrollIntoView?.({ behavior: 'smooth', block: 'nearest' });
124
132
  }, { injector: this.#injector });
125
133
  }
126
134
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: SequenceListComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
127
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.12", type: SequenceListComponent, isStandalone: true, selector: "yuv-sequence-list", inputs: { maxLength: { classPropertyName: "maxLength", publicName: "maxLength", isSignal: true, isRequired: false, transformFunction: null } }, host: { attributes: { "tabindex": "0" } }, providers: [
135
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.12", type: SequenceListComponent, isStandalone: true, selector: "yuv-sequence-list", inputs: { maxLength: { classPropertyName: "maxLength", publicName: "maxLength", isSignal: true, isRequired: false, transformFunction: null }, minLength: { classPropertyName: "minLength", publicName: "minLength", isSignal: true, isRequired: false, transformFunction: null } }, host: { attributes: { "tabindex": "0" } }, providers: [
128
136
  {
129
137
  provide: NG_VALUE_ACCESSOR,
130
138
  useExisting: forwardRef(() => SequenceListComponent),
@@ -135,7 +143,7 @@ class SequenceListComponent {
135
143
  useExisting: forwardRef(() => SequenceListComponent),
136
144
  multi: true
137
145
  }
138
- ], viewQueries: [{ propertyName: "orgComponent", first: true, predicate: OrganizationComponent, descendants: true, isSignal: true }], ngImport: i0, template: "<form [formGroup]=\"sequenceForm\">\n <section formArrayName=\"items\">\n @for (item of sequenceForm.controls.items.controls; track item; let itemIndex = $index) {\n <div class=\"item\" [formGroup]=\"item\">\n <button mat-icon-button class=\"add before\" type=\"button\" [disabled]=\"maxLengthExceeded()\" (click)=\"addItemAt($index)\">\n <mat-icon>add</mat-icon>\n </button>\n <span class=\"index\" [attr.data-index]=\"itemIndex + 1\"></span>\n\n <div class=\"item-form\">\n <button ymtIconButton icon-button-size=\"small\" class=\"remove hidden\" [class.hidden]=\"controlCount() < 2\" type=\"button\" (click)=\"removeItem($index)\" [disabled]=\"controlCount() < 2\">\n <mat-icon>close</mat-icon>\n </button>\n <mat-form-field>\n <mat-label>{{ 'yuv.sequence-list.form.task' | translate }}</mat-label>\n <input matInput [maxLength]=\"128\" formControlName=\"title\" />\n </mat-form-field>\n <mat-form-field>\n <mat-label>{{ 'yuv.sequence-list.form.nextAssignee' | translate }}</mat-label>\n <yuv-organization [classifications]=\"['id:organization:set[user,role]']\"\n [matTooltip]=\"'yuv.sequence-list.form.nextAssignee' | translate\"\n [multiselect]=\"true\"\n [withMetadata]=\"true\"\n [required]=\"true\"\n formControlName=\"nextAssignee\">\n </yuv-organization>\n </mat-form-field>\n </div>\n @if ($last) {\n <button mat-icon-button class=\"add after\" type=\"button\" [disabled]=\"maxLengthExceeded()\" (click)=\"addItem()\">\n <mat-icon>add</mat-icon>\n </button>\n }\n </div>\n }\n </section>\n</form>\n", styles: [":host{--_sequence-list-item-background: var(--sequence-list-item-background, var(--ymt-surface-panel));--_sequence-list-item-border-color: var(--sequence-list-item-border-color, var(--ymt-outline-variant));--_sequence-list-item-border-radius: var(--sequence-list-item-border-radius, var(--ymt-spacing-xs));--_sequence-list-line-color: var(--sequence-list-line-color, var(--ymt-text-color-subtle));--_sequence-list-line-width: var(--sequence-list-line-width, 2px);--_sequence-list-line-item-background: var(--sequence-list-line-item-background, var(--ymt-surface-panel));--_sequence-list-line-item-border-radius: var(--sequence-list-line-item-border-radius, .25em)}:host form section{margin-inline-end:4px}:host form section button.add{--icon-size: 18px;border-radius:var(--_sequence-list-line-item-border-radius);padding:0;display:flex;align-items:center;justify-content:center;background-color:var(--_sequence-list-line-item-background);border:var(--_sequence-list-line-width) solid var(--_sequence-list-line-color)}:host form section button.add:focus{outline:2px solid rgb(from var(--ymt-primary) r g b/.5);outline-offset:-2px;border-color:var(--ymt-primary)}:host form section .item{display:grid;grid-template-columns:6ch 1fr;grid-template-rows:auto auto;column-gap:1em;grid-template-areas:\"addBefore .\" \"index form\" \"addAfter .\"}:host form section .item.ng-invalid.ng-dirty .item-form{border-color:var(--ymt-danger);outline:3px solid rgb(from var(--ymt-danger) r g b/.5)}:host form section .item .add.before{grid-area:addBefore;justify-self:center}:host form section .item .add.after{grid-area:addAfter;justify-self:center}:host form section .item .index{grid-area:index;display:grid;align-items:center;justify-content:center}:host form section .item .index:after{content:attr(data-index);grid-row:1;grid-column:1;background-color:var(--_sequence-list-line-item-background);font:var(--ymt-font-body-subtle);color:var(--ymt-text-color-subtle);font-weight:700;border:var(--_sequence-list-line-width) solid var(--_sequence-list-line-color);border-radius:var(--_sequence-list-line-item-border-radius);line-height:1em;padding:1px .25em;outline:3px solid rgb(from var(--_sequence-list-line-color) r g b/.1)}:host form section .item .index:before{content:\"\";grid-row:1;grid-column:1;height:100%;width:calc(50% + var(--_sequence-list-line-width) / 2);border-inline-end:var(--_sequence-list-line-width) solid var(--_sequence-list-line-color)}:host form section .item .item-form{grid-area:form;background-color:var(--_sequence-list-item-background);border:1px solid var(--_sequence-list-item-border-color);border-radius:var(--_sequence-list-item-border-radius);padding:var(--ymt-spacing-xs);display:grid;column-gap:var(--ymt-spacing-m);row-gap:4px;grid-template-columns:1fr auto;grid-template-rows:repeat(3,auto);grid-template-areas:\"task remove\" \"user user\" \"date date\"}:host form section .item .item-form input{border:1px solid transparent;border-radius:calc(var(--_sequence-list-item-border-radius) / 2);grid-area:task;outline-offset:0;line-height:1em;padding:var(--ymt-spacing-2xs);background-color:transparent;color:var(--ymt-text-color)}:host form section .item .item-form input:focus{border-color:var(--yvc-form-element-focus-border-color);outline:var(--yvc-form-element-focus-outline-with) solid var(--yvc-form-element-focus-outline-color)}:host form section .item .item-form yvc-datepicker{--form-element-border-color: transparent;grid-area:date;padding:0}:host form section .item .item-form yuv-organization{--form-element-border-color: transparent;border-radius:calc(var(--_sequence-list-item-border-radius) / 2);border:1px solid var(--form-element-border-color);grid-area:user}:host form section .item .item-form .remove{--icon-size: 18px;padding:var(--ymt-spacing-2xs);grid-area:remove;padding:0}:host form section .item .item-form .remove.hidden{visibility:hidden}:host mat-form-field{margin-block-start:var(--ymt-spacing-s)}\n"], dependencies: [{ kind: "component", type: OrganizationComponent, selector: "yuv-organization", inputs: ["situation", "multiselect", "classifications", "readonly", "excludeMe", "withMetadata"] }, { kind: "ngmodule", type: MatButtonModule }, { kind: "component", type: i1.MatIconButton, selector: "button[mat-icon-button], a[mat-icon-button], button[matIconButton], a[matIconButton]", exportAs: ["matButton", "matAnchor"] }, { 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: i3.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i3.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i3.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i3.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],[formArray],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i3.RequiredValidator, selector: ":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]", inputs: ["required"] }, { kind: "directive", type: i3.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "directive", type: i3.FormControlName, selector: "[formControlName]", inputs: ["formControlName", "disabled", "ngModel"], outputs: ["ngModelChange"] }, { kind: "directive", type: i3.FormArrayName, selector: "[formArrayName]", inputs: ["formArrayName"] }, { 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: MatInputModule }, { kind: "directive", type: i5.MatInput, selector: "input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]", inputs: ["disabled", "id", "placeholder", "name", "required", "type", "errorStateMatcher", "aria-describedby", "value", "readonly", "disabledInteractive"], exportAs: ["matInput"] }, { kind: "component", type: i5.MatFormField, selector: "mat-form-field", inputs: ["hideRequiredMarker", "color", "floatLabel", "appearance", "subscriptSizing", "hintLabel"], exportAs: ["matFormField"] }, { kind: "directive", type: i5.MatLabel, selector: "mat-label" }, { kind: "ngmodule", type: MatFormFieldModule }, { kind: "directive", type: YmtIconButtonDirective, selector: "button[ymtIconButton],button[ymt-icon-button],a[ymtIconButton],a[ymt-icon-button]", inputs: ["disabled", "disableRipple", "aria-disabled", "disabledInteractive", "icon-button-size"] }, { kind: "pipe", type: TranslatePipe, name: "translate" }] }); }
146
+ ], viewQueries: [{ propertyName: "orgComponent", first: true, predicate: OrganizationComponent, descendants: true, isSignal: true }], ngImport: i0, template: "<form [formGroup]=\"sequenceForm\">\n <section formArrayName=\"items\">\n @for (item of sequenceForm.controls.items.controls; track item; let itemIndex = $index) {\n <div class=\"item\" [formGroup]=\"item\">\n <button mat-icon-button class=\"add before\" type=\"button\" [disabled]=\"maxLengthExceeded()\" (click)=\"addItemAt($index)\">\n <mat-icon>add</mat-icon>\n </button>\n <span class=\"index\" [attr.data-index]=\"itemIndex + 1\"></span>\n\n <div class=\"item-form\">\n <button ymtIconButton icon-button-size=\"small\" class=\"remove hidden\" [class.hidden]=\"controlCount() <= minLength()\" type=\"button\" (click)=\"removeItem($index)\" [disabled]=\"controlCount() <= minLength()\">\n <mat-icon>close</mat-icon>\n </button>\n <mat-form-field>\n <mat-label>{{ 'yuv.sequence-list.form.task' | translate }}</mat-label>\n <input matInput [maxLength]=\"128\" formControlName=\"title\" />\n </mat-form-field>\n <mat-form-field>\n <mat-label>{{ 'yuv.sequence-list.form.nextAssignee' | translate }}</mat-label>\n <yuv-organization [classifications]=\"['id:organization:set[user,role]']\"\n [matTooltip]=\"'yuv.sequence-list.form.nextAssignee' | translate\"\n [multiselect]=\"true\"\n [withMetadata]=\"true\"\n [required]=\"true\"\n formControlName=\"nextAssignee\">\n </yuv-organization>\n </mat-form-field>\n </div>\n @if ($last) {\n <button mat-icon-button class=\"add after\" type=\"button\" [disabled]=\"maxLengthExceeded()\" (click)=\"addItem()\">\n <mat-icon>add</mat-icon>\n </button>\n }\n </div>\n }\n @if (controlCount() === 0) {\n <button mat-icon-button class=\"add empty\" type=\"button\" [disabled]=\"maxLengthExceeded()\" (click)=\"addItem()\">\n <mat-icon>add</mat-icon>\n </button>\n }\n </section>\n</form>\n", styles: [":host{--_sequence-list-item-background: var(--sequence-list-item-background, var(--ymt-surface-panel));--_sequence-list-item-border-color: var(--sequence-list-item-border-color, var(--ymt-outline-variant));--_sequence-list-item-border-radius: var(--sequence-list-item-border-radius, var(--ymt-spacing-xs));--_sequence-list-line-color: var(--sequence-list-line-color, var(--ymt-text-color-subtle));--_sequence-list-line-width: var(--sequence-list-line-width, 2px);--_sequence-list-line-item-background: var(--sequence-list-line-item-background, var(--ymt-surface-panel));--_sequence-list-line-item-border-radius: var(--sequence-list-line-item-border-radius, .25em)}:host form section{margin-inline-end:4px}:host form section button.add{--icon-size: 18px;border-radius:var(--_sequence-list-line-item-border-radius);padding:0;display:flex;align-items:center;justify-content:center;background-color:var(--_sequence-list-line-item-background);border:var(--_sequence-list-line-width) solid var(--_sequence-list-line-color)}:host form section button.add:focus{outline:2px solid rgb(from var(--ymt-primary) r g b/.5);outline-offset:-2px;border-color:var(--ymt-primary)}:host form section .item{display:grid;grid-template-columns:6ch 1fr;grid-template-rows:auto auto;column-gap:1em;grid-template-areas:\"addBefore .\" \"index form\" \"addAfter .\"}:host form section .item.ng-invalid.ng-dirty .item-form{border-color:var(--ymt-danger);outline:3px solid rgb(from var(--ymt-danger) r g b/.5)}:host form section .item .add.before{grid-area:addBefore;justify-self:center}:host form section .item .add.after{grid-area:addAfter;justify-self:center}:host form section .item .index{grid-area:index;display:grid;align-items:center;justify-content:center}:host form section .item .index:after{content:attr(data-index);grid-row:1;grid-column:1;background-color:var(--_sequence-list-line-item-background);font:var(--ymt-font-body-subtle);color:var(--ymt-text-color-subtle);font-weight:700;border:var(--_sequence-list-line-width) solid var(--_sequence-list-line-color);border-radius:var(--_sequence-list-line-item-border-radius);line-height:1em;padding:1px .25em;outline:3px solid rgb(from var(--_sequence-list-line-color) r g b/.1)}:host form section .item .index:before{content:\"\";grid-row:1;grid-column:1;height:100%;width:calc(50% + var(--_sequence-list-line-width) / 2);border-inline-end:var(--_sequence-list-line-width) solid var(--_sequence-list-line-color)}:host form section .item .item-form{grid-area:form;background-color:var(--_sequence-list-item-background);border:1px solid var(--_sequence-list-item-border-color);border-radius:var(--_sequence-list-item-border-radius);padding:var(--ymt-spacing-xs);display:grid;column-gap:var(--ymt-spacing-m);row-gap:4px;grid-template-columns:1fr auto;grid-template-rows:repeat(3,auto);grid-template-areas:\"task remove\" \"user user\" \"date date\"}:host form section .item .item-form input{border:1px solid transparent;border-radius:calc(var(--_sequence-list-item-border-radius) / 2);grid-area:task;outline-offset:0;line-height:1em;padding:var(--ymt-spacing-2xs);background-color:transparent;color:var(--ymt-text-color)}:host form section .item .item-form input:focus{border-color:var(--yvc-form-element-focus-border-color);outline:var(--yvc-form-element-focus-outline-with) solid var(--yvc-form-element-focus-outline-color)}:host form section .item .item-form yvc-datepicker{--form-element-border-color: transparent;grid-area:date;padding:0}:host form section .item .item-form yuv-organization{--form-element-border-color: transparent;border-radius:calc(var(--_sequence-list-item-border-radius) / 2);border:1px solid var(--form-element-border-color);grid-area:user}:host form section .item .item-form .remove{--icon-size: 18px;padding:var(--ymt-spacing-2xs);grid-area:remove;padding:0}:host form section .item .item-form .remove.hidden{visibility:hidden}:host mat-form-field{margin-block-start:var(--ymt-spacing-s)}\n"], dependencies: [{ kind: "component", type: OrganizationComponent, selector: "yuv-organization", inputs: ["situation", "multiselect", "classifications", "readonly", "excludeMe", "withMetadata"] }, { kind: "ngmodule", type: MatButtonModule }, { kind: "component", type: i1.MatIconButton, selector: "button[mat-icon-button], a[mat-icon-button], button[matIconButton], a[matIconButton]", exportAs: ["matButton", "matAnchor"] }, { 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: i3.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i3.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i3.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i3.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],[formArray],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i3.RequiredValidator, selector: ":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]", inputs: ["required"] }, { kind: "directive", type: i3.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "directive", type: i3.FormControlName, selector: "[formControlName]", inputs: ["formControlName", "disabled", "ngModel"], outputs: ["ngModelChange"] }, { kind: "directive", type: i3.FormArrayName, selector: "[formArrayName]", inputs: ["formArrayName"] }, { 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: MatInputModule }, { kind: "directive", type: i5.MatInput, selector: "input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]", inputs: ["disabled", "id", "placeholder", "name", "required", "type", "errorStateMatcher", "aria-describedby", "value", "readonly", "disabledInteractive"], exportAs: ["matInput"] }, { kind: "component", type: i5.MatFormField, selector: "mat-form-field", inputs: ["hideRequiredMarker", "color", "floatLabel", "appearance", "subscriptSizing", "hintLabel"], exportAs: ["matFormField"] }, { kind: "directive", type: i5.MatLabel, selector: "mat-label" }, { kind: "ngmodule", type: MatFormFieldModule }, { kind: "directive", type: YmtIconButtonDirective, selector: "button[ymtIconButton],button[ymt-icon-button],a[ymtIconButton],a[ymt-icon-button]", inputs: ["disabled", "disableRipple", "aria-disabled", "disabledInteractive", "icon-button-size"] }, { kind: "pipe", type: TranslatePipe, name: "translate" }] }); }
139
147
  }
140
148
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: SequenceListComponent, decorators: [{
141
149
  type: Component,
@@ -162,8 +170,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.12", ngImpo
162
170
  }
163
171
  ], host: {
164
172
  tabindex: '0'
165
- }, template: "<form [formGroup]=\"sequenceForm\">\n <section formArrayName=\"items\">\n @for (item of sequenceForm.controls.items.controls; track item; let itemIndex = $index) {\n <div class=\"item\" [formGroup]=\"item\">\n <button mat-icon-button class=\"add before\" type=\"button\" [disabled]=\"maxLengthExceeded()\" (click)=\"addItemAt($index)\">\n <mat-icon>add</mat-icon>\n </button>\n <span class=\"index\" [attr.data-index]=\"itemIndex + 1\"></span>\n\n <div class=\"item-form\">\n <button ymtIconButton icon-button-size=\"small\" class=\"remove hidden\" [class.hidden]=\"controlCount() < 2\" type=\"button\" (click)=\"removeItem($index)\" [disabled]=\"controlCount() < 2\">\n <mat-icon>close</mat-icon>\n </button>\n <mat-form-field>\n <mat-label>{{ 'yuv.sequence-list.form.task' | translate }}</mat-label>\n <input matInput [maxLength]=\"128\" formControlName=\"title\" />\n </mat-form-field>\n <mat-form-field>\n <mat-label>{{ 'yuv.sequence-list.form.nextAssignee' | translate }}</mat-label>\n <yuv-organization [classifications]=\"['id:organization:set[user,role]']\"\n [matTooltip]=\"'yuv.sequence-list.form.nextAssignee' | translate\"\n [multiselect]=\"true\"\n [withMetadata]=\"true\"\n [required]=\"true\"\n formControlName=\"nextAssignee\">\n </yuv-organization>\n </mat-form-field>\n </div>\n @if ($last) {\n <button mat-icon-button class=\"add after\" type=\"button\" [disabled]=\"maxLengthExceeded()\" (click)=\"addItem()\">\n <mat-icon>add</mat-icon>\n </button>\n }\n </div>\n }\n </section>\n</form>\n", styles: [":host{--_sequence-list-item-background: var(--sequence-list-item-background, var(--ymt-surface-panel));--_sequence-list-item-border-color: var(--sequence-list-item-border-color, var(--ymt-outline-variant));--_sequence-list-item-border-radius: var(--sequence-list-item-border-radius, var(--ymt-spacing-xs));--_sequence-list-line-color: var(--sequence-list-line-color, var(--ymt-text-color-subtle));--_sequence-list-line-width: var(--sequence-list-line-width, 2px);--_sequence-list-line-item-background: var(--sequence-list-line-item-background, var(--ymt-surface-panel));--_sequence-list-line-item-border-radius: var(--sequence-list-line-item-border-radius, .25em)}:host form section{margin-inline-end:4px}:host form section button.add{--icon-size: 18px;border-radius:var(--_sequence-list-line-item-border-radius);padding:0;display:flex;align-items:center;justify-content:center;background-color:var(--_sequence-list-line-item-background);border:var(--_sequence-list-line-width) solid var(--_sequence-list-line-color)}:host form section button.add:focus{outline:2px solid rgb(from var(--ymt-primary) r g b/.5);outline-offset:-2px;border-color:var(--ymt-primary)}:host form section .item{display:grid;grid-template-columns:6ch 1fr;grid-template-rows:auto auto;column-gap:1em;grid-template-areas:\"addBefore .\" \"index form\" \"addAfter .\"}:host form section .item.ng-invalid.ng-dirty .item-form{border-color:var(--ymt-danger);outline:3px solid rgb(from var(--ymt-danger) r g b/.5)}:host form section .item .add.before{grid-area:addBefore;justify-self:center}:host form section .item .add.after{grid-area:addAfter;justify-self:center}:host form section .item .index{grid-area:index;display:grid;align-items:center;justify-content:center}:host form section .item .index:after{content:attr(data-index);grid-row:1;grid-column:1;background-color:var(--_sequence-list-line-item-background);font:var(--ymt-font-body-subtle);color:var(--ymt-text-color-subtle);font-weight:700;border:var(--_sequence-list-line-width) solid var(--_sequence-list-line-color);border-radius:var(--_sequence-list-line-item-border-radius);line-height:1em;padding:1px .25em;outline:3px solid rgb(from var(--_sequence-list-line-color) r g b/.1)}:host form section .item .index:before{content:\"\";grid-row:1;grid-column:1;height:100%;width:calc(50% + var(--_sequence-list-line-width) / 2);border-inline-end:var(--_sequence-list-line-width) solid var(--_sequence-list-line-color)}:host form section .item .item-form{grid-area:form;background-color:var(--_sequence-list-item-background);border:1px solid var(--_sequence-list-item-border-color);border-radius:var(--_sequence-list-item-border-radius);padding:var(--ymt-spacing-xs);display:grid;column-gap:var(--ymt-spacing-m);row-gap:4px;grid-template-columns:1fr auto;grid-template-rows:repeat(3,auto);grid-template-areas:\"task remove\" \"user user\" \"date date\"}:host form section .item .item-form input{border:1px solid transparent;border-radius:calc(var(--_sequence-list-item-border-radius) / 2);grid-area:task;outline-offset:0;line-height:1em;padding:var(--ymt-spacing-2xs);background-color:transparent;color:var(--ymt-text-color)}:host form section .item .item-form input:focus{border-color:var(--yvc-form-element-focus-border-color);outline:var(--yvc-form-element-focus-outline-with) solid var(--yvc-form-element-focus-outline-color)}:host form section .item .item-form yvc-datepicker{--form-element-border-color: transparent;grid-area:date;padding:0}:host form section .item .item-form yuv-organization{--form-element-border-color: transparent;border-radius:calc(var(--_sequence-list-item-border-radius) / 2);border:1px solid var(--form-element-border-color);grid-area:user}:host form section .item .item-form .remove{--icon-size: 18px;padding:var(--ymt-spacing-2xs);grid-area:remove;padding:0}:host form section .item .item-form .remove.hidden{visibility:hidden}:host mat-form-field{margin-block-start:var(--ymt-spacing-s)}\n"] }]
166
- }], ctorParameters: () => [], propDecorators: { orgComponent: [{ type: i0.ViewChild, args: [i0.forwardRef(() => OrganizationComponent), { isSignal: true }] }], maxLength: [{ type: i0.Input, args: [{ isSignal: true, alias: "maxLength", required: false }] }] } });
173
+ }, template: "<form [formGroup]=\"sequenceForm\">\n <section formArrayName=\"items\">\n @for (item of sequenceForm.controls.items.controls; track item; let itemIndex = $index) {\n <div class=\"item\" [formGroup]=\"item\">\n <button mat-icon-button class=\"add before\" type=\"button\" [disabled]=\"maxLengthExceeded()\" (click)=\"addItemAt($index)\">\n <mat-icon>add</mat-icon>\n </button>\n <span class=\"index\" [attr.data-index]=\"itemIndex + 1\"></span>\n\n <div class=\"item-form\">\n <button ymtIconButton icon-button-size=\"small\" class=\"remove hidden\" [class.hidden]=\"controlCount() <= minLength()\" type=\"button\" (click)=\"removeItem($index)\" [disabled]=\"controlCount() <= minLength()\">\n <mat-icon>close</mat-icon>\n </button>\n <mat-form-field>\n <mat-label>{{ 'yuv.sequence-list.form.task' | translate }}</mat-label>\n <input matInput [maxLength]=\"128\" formControlName=\"title\" />\n </mat-form-field>\n <mat-form-field>\n <mat-label>{{ 'yuv.sequence-list.form.nextAssignee' | translate }}</mat-label>\n <yuv-organization [classifications]=\"['id:organization:set[user,role]']\"\n [matTooltip]=\"'yuv.sequence-list.form.nextAssignee' | translate\"\n [multiselect]=\"true\"\n [withMetadata]=\"true\"\n [required]=\"true\"\n formControlName=\"nextAssignee\">\n </yuv-organization>\n </mat-form-field>\n </div>\n @if ($last) {\n <button mat-icon-button class=\"add after\" type=\"button\" [disabled]=\"maxLengthExceeded()\" (click)=\"addItem()\">\n <mat-icon>add</mat-icon>\n </button>\n }\n </div>\n }\n @if (controlCount() === 0) {\n <button mat-icon-button class=\"add empty\" type=\"button\" [disabled]=\"maxLengthExceeded()\" (click)=\"addItem()\">\n <mat-icon>add</mat-icon>\n </button>\n }\n </section>\n</form>\n", styles: [":host{--_sequence-list-item-background: var(--sequence-list-item-background, var(--ymt-surface-panel));--_sequence-list-item-border-color: var(--sequence-list-item-border-color, var(--ymt-outline-variant));--_sequence-list-item-border-radius: var(--sequence-list-item-border-radius, var(--ymt-spacing-xs));--_sequence-list-line-color: var(--sequence-list-line-color, var(--ymt-text-color-subtle));--_sequence-list-line-width: var(--sequence-list-line-width, 2px);--_sequence-list-line-item-background: var(--sequence-list-line-item-background, var(--ymt-surface-panel));--_sequence-list-line-item-border-radius: var(--sequence-list-line-item-border-radius, .25em)}:host form section{margin-inline-end:4px}:host form section button.add{--icon-size: 18px;border-radius:var(--_sequence-list-line-item-border-radius);padding:0;display:flex;align-items:center;justify-content:center;background-color:var(--_sequence-list-line-item-background);border:var(--_sequence-list-line-width) solid var(--_sequence-list-line-color)}:host form section button.add:focus{outline:2px solid rgb(from var(--ymt-primary) r g b/.5);outline-offset:-2px;border-color:var(--ymt-primary)}:host form section .item{display:grid;grid-template-columns:6ch 1fr;grid-template-rows:auto auto;column-gap:1em;grid-template-areas:\"addBefore .\" \"index form\" \"addAfter .\"}:host form section .item.ng-invalid.ng-dirty .item-form{border-color:var(--ymt-danger);outline:3px solid rgb(from var(--ymt-danger) r g b/.5)}:host form section .item .add.before{grid-area:addBefore;justify-self:center}:host form section .item .add.after{grid-area:addAfter;justify-self:center}:host form section .item .index{grid-area:index;display:grid;align-items:center;justify-content:center}:host form section .item .index:after{content:attr(data-index);grid-row:1;grid-column:1;background-color:var(--_sequence-list-line-item-background);font:var(--ymt-font-body-subtle);color:var(--ymt-text-color-subtle);font-weight:700;border:var(--_sequence-list-line-width) solid var(--_sequence-list-line-color);border-radius:var(--_sequence-list-line-item-border-radius);line-height:1em;padding:1px .25em;outline:3px solid rgb(from var(--_sequence-list-line-color) r g b/.1)}:host form section .item .index:before{content:\"\";grid-row:1;grid-column:1;height:100%;width:calc(50% + var(--_sequence-list-line-width) / 2);border-inline-end:var(--_sequence-list-line-width) solid var(--_sequence-list-line-color)}:host form section .item .item-form{grid-area:form;background-color:var(--_sequence-list-item-background);border:1px solid var(--_sequence-list-item-border-color);border-radius:var(--_sequence-list-item-border-radius);padding:var(--ymt-spacing-xs);display:grid;column-gap:var(--ymt-spacing-m);row-gap:4px;grid-template-columns:1fr auto;grid-template-rows:repeat(3,auto);grid-template-areas:\"task remove\" \"user user\" \"date date\"}:host form section .item .item-form input{border:1px solid transparent;border-radius:calc(var(--_sequence-list-item-border-radius) / 2);grid-area:task;outline-offset:0;line-height:1em;padding:var(--ymt-spacing-2xs);background-color:transparent;color:var(--ymt-text-color)}:host form section .item .item-form input:focus{border-color:var(--yvc-form-element-focus-border-color);outline:var(--yvc-form-element-focus-outline-with) solid var(--yvc-form-element-focus-outline-color)}:host form section .item .item-form yvc-datepicker{--form-element-border-color: transparent;grid-area:date;padding:0}:host form section .item .item-form yuv-organization{--form-element-border-color: transparent;border-radius:calc(var(--_sequence-list-item-border-radius) / 2);border:1px solid var(--form-element-border-color);grid-area:user}:host form section .item .item-form .remove{--icon-size: 18px;padding:var(--ymt-spacing-2xs);grid-area:remove;padding:0}:host form section .item .item-form .remove.hidden{visibility:hidden}:host mat-form-field{margin-block-start:var(--ymt-spacing-s)}\n"] }]
174
+ }], ctorParameters: () => [], propDecorators: { orgComponent: [{ type: i0.ViewChild, args: [i0.forwardRef(() => OrganizationComponent), { isSignal: true }] }], maxLength: [{ type: i0.Input, args: [{ isSignal: true, alias: "maxLength", required: false }] }], minLength: [{ type: i0.Input, args: [{ isSignal: true, alias: "minLength", required: false }] }] } });
167
175
 
168
176
  /**
169
177
  * Generated bundle index. Do not edit.
@@ -1 +1 @@
1
- {"version":3,"file":"yuuvis-client-framework-sequence-list.mjs","sources":["../../../../../libs/yuuvis/client-framework/sequence-list/src/lib/sequence-list.component.ts","../../../../../libs/yuuvis/client-framework/sequence-list/src/lib/sequence-list.component.html","../../../../../libs/yuuvis/client-framework/sequence-list/src/yuuvis-client-framework-sequence-list.ts"],"sourcesContent":["import {\n afterNextRender,\n Component,\n ElementRef,\n forwardRef,\n inject,\n Injector,\n input,\n signal,\n viewChild\n} from '@angular/core';\nimport { takeUntilDestroyed } from '@angular/core/rxjs-interop';\nimport {\n ControlValueAccessor,\n FormArray,\n FormControl,\n FormGroup,\n NG_VALIDATORS,\n NG_VALUE_ACCESSOR,\n NonNullableFormBuilder,\n ReactiveFormsModule,\n ValidationErrors,\n Validator,\n Validators\n} from '@angular/forms';\nimport { MatButtonModule } from '@angular/material/button';\nimport { MatFormFieldModule } from '@angular/material/form-field';\nimport { MatIconModule } from '@angular/material/icon';\nimport { MatInputModule } from '@angular/material/input';\nimport { MatTooltipModule } from '@angular/material/tooltip';\nimport { TranslatePipe } from '@yuuvis/client-core';\nimport { OrganizationComponent } from '@yuuvis/client-framework/forms';\nimport { YmtIconButtonDirective } from '@yuuvis/material';\nimport { SequenceItem, SequenceListAssignee } from './sequence-list.interface';\n\ntype SequenceFormItem = FormGroup<{\n title: FormControl<string>;\n nextAssignee: FormControl<SequenceListAssignee[]>;\n expiryDatetime: FormControl<any>;\n}>;\ntype SequenceForm = FormGroup<{\n items: FormArray<SequenceFormItem>;\n}>;\n\n/**\n * Task sequence list.\n */\n@Component({\n selector: 'yuv-sequence-list',\n standalone: true,\n imports: [\n TranslatePipe,\n OrganizationComponent,\n MatButtonModule,\n MatIconModule,\n ReactiveFormsModule,\n MatTooltipModule,\n MatInputModule,\n MatFormFieldModule,\n YmtIconButtonDirective\n ],\n templateUrl: './sequence-list.component.html',\n styleUrls: ['./sequence-list.component.scss'],\n providers: [\n {\n provide: NG_VALUE_ACCESSOR,\n useExisting: forwardRef(() => SequenceListComponent),\n multi: true\n },\n {\n provide: NG_VALIDATORS,\n useExisting: forwardRef(() => SequenceListComponent),\n multi: true\n }\n ],\n host: {\n tabindex: '0'\n }\n})\nexport class SequenceListComponent implements ControlValueAccessor, Validator {\n #formBuilder = inject(NonNullableFormBuilder);\n #eleRef = inject(ElementRef);\n #injector = inject(Injector);\n\n readonly orgComponent = viewChild.required(OrganizationComponent);\n\n /**\n * Maximum number of sequence items (defaults to 100).\n */\n maxLength = input<number>(100);\n maxLengthExceeded = signal<boolean>(false);\n readonly controlCount = signal<number>(0);\n\n // dynamic form for sequence items\n sequenceForm: SequenceForm = this.#formBuilder.group({\n items: this.#formBuilder.array<SequenceFormItem>([this.#generateSequenceItem()])\n });\n\n entries: SequenceItem[] = [];\n\n get formItemArray(): FormArray<SequenceFormItem> {\n return this.sequenceForm.controls.items as FormArray<SequenceFormItem>;\n }\n\n constructor() {\n this.#updateState();\n this.sequenceForm.valueChanges.pipe(takeUntilDestroyed()).subscribe(() => {\n this.#propagate();\n });\n }\n\n addItem(): void {\n this.sequenceForm.controls.items.push(this.#generateSequenceItem());\n this.#updateState();\n this.#scrollToItem(this.formItemArray.length - 1);\n }\n\n addItemAt(idx: number): void {\n this.sequenceForm.controls.items.insert(idx, this.#generateSequenceItem());\n this.#updateState();\n this.#scrollToItem(idx);\n }\n\n removeItem(idx: number): void {\n this.sequenceForm.controls.items.removeAt(idx);\n this.#updateState();\n }\n\n writeValue(value: SequenceItem[]): void {\n this.entries = value || [];\n this.formItemArray.clear();\n if (this.entries.length === 0) this.addItem();\n this.entries.forEach((entry) => {\n this.formItemArray.push(this.#generateSequenceItem(entry));\n });\n this.#updateState();\n }\n\n registerOnChange(fn: any): void {\n this.propagateChange = fn;\n }\n\n validate(): ValidationErrors | null {\n const maxLength = this.maxLength();\n const mlExceeded = maxLength !== undefined && this.formItemArray.length > maxLength;\n const valid = this.sequenceForm.valid && !mlExceeded;\n\n return !valid\n ? {\n sequencelist: {\n invalid: this.sequenceForm.invalid,\n maxLengthExceeded: mlExceeded\n }\n }\n : null;\n }\n\n // eslint-disable-next-line @typescript-eslint/no-empty-function\n propagateChange = (_: any): void => {};\n // eslint-disable-next-line @typescript-eslint/no-empty-function\n registerOnTouched(): void {}\n\n #updateState(): void {\n const maxLength = this.maxLength();\n this.maxLengthExceeded.set(maxLength !== undefined && this.formItemArray.length >= maxLength);\n this.controlCount.set(this.sequenceForm.controls.items.length);\n }\n\n #generateSequenceItem(sequenceItem?: SequenceItem): SequenceFormItem {\n return this.#formBuilder.group({\n title: [sequenceItem?.title || '', Validators.required],\n nextAssignee: [sequenceItem?.nextAssignee || [], Validators.required],\n expiryDatetime: [sequenceItem?.expiryDatetime || undefined]\n });\n }\n\n #propagate(): void {\n const res: SequenceItem[] = [];\n (this.sequenceForm.value.items || []).forEach((item: any) => {\n const i: SequenceItem = {\n title: item.title,\n nextAssignee: item.nextAssignee\n };\n if (item.expiryDatetime) {\n i.expiryDatetime = item.expiryDatetime;\n }\n res.push(i);\n });\n\n this.propagateChange(res);\n }\n\n #scrollToItem(idx: number): void {\n afterNextRender(\n () => {\n const items = this.#eleRef.nativeElement.querySelectorAll('.item');\n items[idx]?.scrollIntoView({ behavior: 'smooth', block: 'nearest' });\n },\n { injector: this.#injector }\n );\n }\n}\n","<form [formGroup]=\"sequenceForm\">\n <section formArrayName=\"items\">\n @for (item of sequenceForm.controls.items.controls; track item; let itemIndex = $index) {\n <div class=\"item\" [formGroup]=\"item\">\n <button mat-icon-button class=\"add before\" type=\"button\" [disabled]=\"maxLengthExceeded()\" (click)=\"addItemAt($index)\">\n <mat-icon>add</mat-icon>\n </button>\n <span class=\"index\" [attr.data-index]=\"itemIndex + 1\"></span>\n\n <div class=\"item-form\">\n <button ymtIconButton icon-button-size=\"small\" class=\"remove hidden\" [class.hidden]=\"controlCount() < 2\" type=\"button\" (click)=\"removeItem($index)\" [disabled]=\"controlCount() < 2\">\n <mat-icon>close</mat-icon>\n </button>\n <mat-form-field>\n <mat-label>{{ 'yuv.sequence-list.form.task' | translate }}</mat-label>\n <input matInput [maxLength]=\"128\" formControlName=\"title\" />\n </mat-form-field>\n <mat-form-field>\n <mat-label>{{ 'yuv.sequence-list.form.nextAssignee' | translate }}</mat-label>\n <yuv-organization [classifications]=\"['id:organization:set[user,role]']\"\n [matTooltip]=\"'yuv.sequence-list.form.nextAssignee' | translate\"\n [multiselect]=\"true\"\n [withMetadata]=\"true\"\n [required]=\"true\"\n formControlName=\"nextAssignee\">\n </yuv-organization>\n </mat-form-field>\n </div>\n @if ($last) {\n <button mat-icon-button class=\"add after\" type=\"button\" [disabled]=\"maxLengthExceeded()\" (click)=\"addItem()\">\n <mat-icon>add</mat-icon>\n </button>\n }\n </div>\n }\n </section>\n</form>\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './index';\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"yuuvis-client-framework-sequence-list.mjs","sources":["../../../../../libs/yuuvis/client-framework/sequence-list/src/lib/sequence-list.component.ts","../../../../../libs/yuuvis/client-framework/sequence-list/src/lib/sequence-list.component.html","../../../../../libs/yuuvis/client-framework/sequence-list/src/yuuvis-client-framework-sequence-list.ts"],"sourcesContent":["import {\n afterNextRender,\n Component,\n ElementRef,\n forwardRef,\n inject,\n Injector,\n input,\n signal,\n viewChild\n} from '@angular/core';\nimport { takeUntilDestroyed } from '@angular/core/rxjs-interop';\nimport {\n ControlValueAccessor,\n FormArray,\n FormControl,\n FormGroup,\n NG_VALIDATORS,\n NG_VALUE_ACCESSOR,\n NonNullableFormBuilder,\n ReactiveFormsModule,\n ValidationErrors,\n Validator,\n Validators\n} from '@angular/forms';\nimport { MatButtonModule } from '@angular/material/button';\nimport { MatFormFieldModule } from '@angular/material/form-field';\nimport { MatIconModule } from '@angular/material/icon';\nimport { MatInputModule } from '@angular/material/input';\nimport { MatTooltipModule } from '@angular/material/tooltip';\nimport { TranslatePipe } from '@yuuvis/client-core';\nimport { OrganizationComponent } from '@yuuvis/client-framework/forms';\nimport { YmtIconButtonDirective } from '@yuuvis/material';\nimport { SequenceItem, SequenceListAssignee } from './sequence-list.interface';\n\ntype SequenceFormItem = FormGroup<{\n title: FormControl<string>;\n nextAssignee: FormControl<SequenceListAssignee[]>;\n expiryDatetime: FormControl<any>;\n}>;\ntype SequenceForm = FormGroup<{\n items: FormArray<SequenceFormItem>;\n}>;\n\n/**\n * Task sequence list.\n */\n@Component({\n selector: 'yuv-sequence-list',\n standalone: true,\n imports: [\n TranslatePipe,\n OrganizationComponent,\n MatButtonModule,\n MatIconModule,\n ReactiveFormsModule,\n MatTooltipModule,\n MatInputModule,\n MatFormFieldModule,\n YmtIconButtonDirective\n ],\n templateUrl: './sequence-list.component.html',\n styleUrls: ['./sequence-list.component.scss'],\n providers: [\n {\n provide: NG_VALUE_ACCESSOR,\n useExisting: forwardRef(() => SequenceListComponent),\n multi: true\n },\n {\n provide: NG_VALIDATORS,\n useExisting: forwardRef(() => SequenceListComponent),\n multi: true\n }\n ],\n host: {\n tabindex: '0'\n }\n})\nexport class SequenceListComponent implements ControlValueAccessor, Validator {\n #formBuilder = inject(NonNullableFormBuilder);\n #eleRef = inject(ElementRef);\n #injector = inject(Injector);\n\n readonly orgComponent = viewChild.required(OrganizationComponent);\n\n /**\n * Maximum number of sequence items (defaults to 100).\n */\n maxLength = input<number>(100);\n /**\n * Minimum number of sequence items that must remain (defaults to 1, matching\n * the original behaviour where a list can never be emptied). Consumers that\n * represent a tail of optional follow-up items (e.g. the remaining steps of\n * an already-started routing list) can set this to 0 to allow removing the\n * last remaining item.\n */\n minLength = input<number>(1);\n maxLengthExceeded = signal<boolean>(false);\n readonly controlCount = signal<number>(0);\n\n // dynamic form for sequence items\n sequenceForm: SequenceForm = this.#formBuilder.group({\n items: this.#formBuilder.array<SequenceFormItem>([this.#generateSequenceItem()])\n });\n\n entries: SequenceItem[] = [];\n\n get formItemArray(): FormArray<SequenceFormItem> {\n return this.sequenceForm.controls.items as FormArray<SequenceFormItem>;\n }\n\n constructor() {\n this.#updateState();\n this.sequenceForm.valueChanges.pipe(takeUntilDestroyed()).subscribe(() => {\n this.#propagate();\n });\n }\n\n addItem(): void {\n this.sequenceForm.controls.items.push(this.#generateSequenceItem());\n this.#updateState();\n this.#scrollToItem(this.formItemArray.length - 1);\n }\n\n addItemAt(idx: number): void {\n this.sequenceForm.controls.items.insert(idx, this.#generateSequenceItem());\n this.#updateState();\n this.#scrollToItem(idx);\n }\n\n removeItem(idx: number): void {\n this.sequenceForm.controls.items.removeAt(idx);\n this.#updateState();\n }\n\n writeValue(value: SequenceItem[]): void {\n this.entries = value || [];\n this.formItemArray.clear();\n if (this.entries.length === 0 && this.minLength() > 0) this.addItem();\n this.entries.forEach((entry) => {\n this.formItemArray.push(this.#generateSequenceItem(entry));\n });\n this.#updateState();\n }\n\n registerOnChange(fn: any): void {\n this.propagateChange = fn;\n }\n\n validate(): ValidationErrors | null {\n const maxLength = this.maxLength();\n const mlExceeded = maxLength !== undefined && this.formItemArray.length > maxLength;\n const valid = this.sequenceForm.valid && !mlExceeded;\n\n return !valid\n ? {\n sequencelist: {\n invalid: this.sequenceForm.invalid,\n maxLengthExceeded: mlExceeded\n }\n }\n : null;\n }\n\n // eslint-disable-next-line @typescript-eslint/no-empty-function\n propagateChange = (_: any): void => {};\n // eslint-disable-next-line @typescript-eslint/no-empty-function\n registerOnTouched(): void {}\n\n #updateState(): void {\n const maxLength = this.maxLength();\n this.maxLengthExceeded.set(maxLength !== undefined && this.formItemArray.length >= maxLength);\n this.controlCount.set(this.sequenceForm.controls.items.length);\n }\n\n #generateSequenceItem(sequenceItem?: SequenceItem): SequenceFormItem {\n return this.#formBuilder.group({\n title: [sequenceItem?.title || '', Validators.required],\n nextAssignee: [sequenceItem?.nextAssignee || [], Validators.required],\n expiryDatetime: [sequenceItem?.expiryDatetime || undefined]\n });\n }\n\n #propagate(): void {\n const res: SequenceItem[] = [];\n (this.sequenceForm.value.items || []).forEach((item: any) => {\n const i: SequenceItem = {\n title: item.title,\n nextAssignee: item.nextAssignee\n };\n if (item.expiryDatetime) {\n i.expiryDatetime = item.expiryDatetime;\n }\n res.push(i);\n });\n\n this.propagateChange(res);\n }\n\n #scrollToItem(idx: number): void {\n afterNextRender(\n () => {\n const items = this.#eleRef.nativeElement.querySelectorAll('.item');\n items[idx]?.scrollIntoView?.({ behavior: 'smooth', block: 'nearest' });\n },\n { injector: this.#injector }\n );\n }\n}\n","<form [formGroup]=\"sequenceForm\">\n <section formArrayName=\"items\">\n @for (item of sequenceForm.controls.items.controls; track item; let itemIndex = $index) {\n <div class=\"item\" [formGroup]=\"item\">\n <button mat-icon-button class=\"add before\" type=\"button\" [disabled]=\"maxLengthExceeded()\" (click)=\"addItemAt($index)\">\n <mat-icon>add</mat-icon>\n </button>\n <span class=\"index\" [attr.data-index]=\"itemIndex + 1\"></span>\n\n <div class=\"item-form\">\n <button ymtIconButton icon-button-size=\"small\" class=\"remove hidden\" [class.hidden]=\"controlCount() <= minLength()\" type=\"button\" (click)=\"removeItem($index)\" [disabled]=\"controlCount() <= minLength()\">\n <mat-icon>close</mat-icon>\n </button>\n <mat-form-field>\n <mat-label>{{ 'yuv.sequence-list.form.task' | translate }}</mat-label>\n <input matInput [maxLength]=\"128\" formControlName=\"title\" />\n </mat-form-field>\n <mat-form-field>\n <mat-label>{{ 'yuv.sequence-list.form.nextAssignee' | translate }}</mat-label>\n <yuv-organization [classifications]=\"['id:organization:set[user,role]']\"\n [matTooltip]=\"'yuv.sequence-list.form.nextAssignee' | translate\"\n [multiselect]=\"true\"\n [withMetadata]=\"true\"\n [required]=\"true\"\n formControlName=\"nextAssignee\">\n </yuv-organization>\n </mat-form-field>\n </div>\n @if ($last) {\n <button mat-icon-button class=\"add after\" type=\"button\" [disabled]=\"maxLengthExceeded()\" (click)=\"addItem()\">\n <mat-icon>add</mat-icon>\n </button>\n }\n </div>\n }\n @if (controlCount() === 0) {\n <button mat-icon-button class=\"add empty\" type=\"button\" [disabled]=\"maxLengthExceeded()\" (click)=\"addItem()\">\n <mat-icon>add</mat-icon>\n </button>\n }\n </section>\n</form>\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './index';\n"],"names":[],"mappings":"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"}
@@ -102,13 +102,21 @@ const defaultHaloFocusOffset = 3;
102
102
  * **Style Properties:**
103
103
  * - `position: 'fixed'` - Keeps halo in viewport regardless of scroll position
104
104
  * - `pointerEvents: 'none'` - Allows click-through; halo doesn't block interactions
105
- * - `zIndex: '9999'` - Ensures halo appears above most other elements
105
+ * - `zIndex: '9999'` - Stacking fallback for browsers without the Popover API. The halo normally
106
+ * joins the browser's top layer instead (see {@link HaloFocusService}), where z-index does not
107
+ * apply, because Angular CDK renders overlays there as well.
106
108
  * - `border` - Primary border using CSS variable `--ymt-primary` (2px solid)
107
109
  * - `boxShadow` - Soft shadow with 20% opacity for subtle depth effect
108
110
  * - `borderRadius` - Default 8px rounded corners for modern appearance
109
111
  * - `opacity: '0'` - Initially hidden; visibility controlled by service
110
112
  * - `transform: 'translateZ(0)'` - Creates GPU layer for smoother animations
111
113
  *
114
+ * **Popover user-agent resets:**
115
+ * Once the halo carries `popover="manual"` the UA stylesheet applies
116
+ * `inset: 0; margin: auto; padding: 0.25em; overflow: auto; background-color: Canvas` to it. Those
117
+ * defaults would re-center the halo and paint it as an opaque filled box over the content, so they
118
+ * are neutralised here. This mirrors the resets the CDK applies to its own `.cdk-overlay-popover`.
119
+ *
112
120
  * **CSS Variable Dependencies:**
113
121
  * - `--ymt-primary` - Primary theme color for border and shadow
114
122
  *
@@ -119,6 +127,12 @@ const defaultHaloFocusOffset = 3;
119
127
  */
120
128
  const haloFocusStyles = {
121
129
  position: 'fixed',
130
+ // Popover UA resets - `left`/`top`/`width`/`height` are set per update by HaloUtilityService.
131
+ inset: 'auto',
132
+ margin: '0',
133
+ padding: '0',
134
+ background: 'none',
135
+ overflow: 'visible',
122
136
  pointerEvents: 'none',
123
137
  zIndex: '9999',
124
138
  border: '2px solid var(--ymt-primary)',
@@ -305,6 +319,7 @@ var ChannelMessage;
305
319
  * - Automatic visibility detection (handles clipping, hidden elements, etc.)
306
320
  * - Responsive to dynamic DOM changes via ResizeObserver and MutationObserver
307
321
  * - Smart exclusion for Angular Material form fields (prevents redundant styling)
322
+ * - Renders in the browser's top layer, so it stays visible inside CDK overlays
308
323
  *
309
324
  * **Usage:**
310
325
  * 1. Add `provideHaloFocus()` to your providers array in app.config.ts
@@ -359,6 +374,13 @@ class HaloFocusService {
359
374
  #haloElement = null;
360
375
  #currentElement = null;
361
376
  #lastInteractionWasKeyboard = false;
377
+ /** Whether the browser supports the Popover API, i.e. whether the halo can join the top layer. */
378
+ #supportsPopover = false;
379
+ /**
380
+ * Tracked manually instead of via `matches(':popover-open')`, because `matches()` throws on
381
+ * pseudo-classes the environment does not know (jsdom).
382
+ */
383
+ #popoverOpen = false;
362
384
  #rafId = null; //Request Animation Frame ID
363
385
  #resizeObserver;
364
386
  #mutationObserver;
@@ -369,7 +391,8 @@ class HaloFocusService {
369
391
  *
370
392
  * This method performs the following initialization steps:
371
393
  * 1. Applies optional configuration for halo appearance (inner and outer colors)
372
- * 2. Creates and appends the halo DIV element to the document body
394
+ * 2. Creates and appends the halo DIV element to the document body, as a `popover` so it can
395
+ * join the browser's top layer, and marks the body with `yuv-halo-focus`
373
396
  * 3. Attaches global event listeners for focus, blur, keyboard, mouse, scroll, and resize events
374
397
  * 4. Sets up ResizeObserver and MutationObserver for tracking dynamic DOM changes
375
398
  *
@@ -403,7 +426,17 @@ class HaloFocusService {
403
426
  this.#haloElement.id = 'focus-halo';
404
427
  this.#haloElement.setAttribute('aria-hidden', 'true');
405
428
  Object.assign(this.#haloElement.style, this.#utility.getHaloFocusStyles(this.#config));
429
+ // Angular CDK renders overlays (dialogs, menus, selects, autocompletes) in the browser's top
430
+ // layer via popover="manual". Nothing in the normal DOM can paint above the top layer, no
431
+ // matter its z-index, so the halo has to join it to stay visible inside an overlay.
432
+ this.#supportsPopover = 'showPopover' in document.body;
433
+ if (this.#supportsPopover) {
434
+ this.#haloElement.setAttribute('popover', 'manual');
435
+ }
406
436
  document.body.appendChild(this.#haloElement);
437
+ // Lets @yuuvis/material suppress its global :focus-visible ring, so keyboard focus is not
438
+ // indicated twice by two differently coloured rings.
439
+ document.body.classList.add('yuv-halo-focus');
407
440
  }
408
441
  #initListeners() {
409
442
  document.addEventListener('focus', this.#onFocus, true);
@@ -428,18 +461,56 @@ class HaloFocusService {
428
461
  }
429
462
  }
430
463
  // #endregion
464
+ //#region Top layer
465
+ /**
466
+ * Re-enters the browser's top layer so the halo becomes its topmost entry.
467
+ *
468
+ * Top layer order is "last shown wins", so an overlay opened after the halo would paint over it.
469
+ * Re-showing the popover moves the halo back to the front. Called once per focus change rather
470
+ * than per render frame: an overlay that appears above the halo also moves focus into itself, so
471
+ * a focus change is the only moment the order can turn stale.
472
+ */
473
+ #raiseToTopLayer() {
474
+ if (!this.#supportsPopover || !this.#haloElement)
475
+ return;
476
+ this.#leaveTopLayer();
477
+ try {
478
+ this.#haloElement.showPopover();
479
+ this.#popoverOpen = true;
480
+ }
481
+ catch {
482
+ // Element not connected, or popover already open in a browser we misdetected.
483
+ }
484
+ }
485
+ /** Leaves the top layer again, so a hidden halo does not linger in it. */
486
+ #leaveTopLayer() {
487
+ if (!this.#supportsPopover || !this.#haloElement || !this.#popoverOpen)
488
+ return;
489
+ try {
490
+ this.#haloElement.hidePopover();
491
+ }
492
+ catch {
493
+ // Already hidden.
494
+ }
495
+ this.#popoverOpen = false;
496
+ }
497
+ #hideHalo() {
498
+ if (this.#haloElement) {
499
+ this.#haloElement.style.opacity = '0';
500
+ }
501
+ this.#leaveTopLayer();
502
+ }
503
+ //#endregion
431
504
  //#region Rendering
432
505
  #updateHaloRect = () => {
433
506
  if (!this.#currentElement || !this.#haloElement || !document.contains(this.#currentElement)) {
434
- if (this.#haloElement) {
435
- this.#haloElement.style.opacity = '0';
436
- }
507
+ this.#hideHalo();
437
508
  this.#currentElement = null;
438
509
  return;
439
510
  }
440
511
  // Check if element is actually visible
441
512
  if (!this.#utility.isElementVisible(this.#currentElement)) {
442
- this.#haloElement.style.opacity = '0';
513
+ this.#hideHalo();
443
514
  return;
444
515
  }
445
516
  this.#utility.setHaloElementSize(this.#currentElement, this.#haloElement);
@@ -453,6 +524,9 @@ class HaloFocusService {
453
524
  };
454
525
  #startTracking(target) {
455
526
  this.#currentElement = target;
527
+ // Raise before the update is scheduled; the halo is still at opacity 0 until #updateHaloRect
528
+ // has repositioned it, so it cannot flash at its previous position.
529
+ this.#raiseToTopLayer();
456
530
  this.#scheduleUpdate();
457
531
  // Track size changes on target element
458
532
  if (this.#resizeObserver) {
@@ -498,7 +572,7 @@ class HaloFocusService {
498
572
  #onMouseDown = () => {
499
573
  this.#lastInteractionWasKeyboard = false;
500
574
  if (this.#haloElement) {
501
- this.#haloElement.style.opacity = '0';
575
+ this.#hideHalo();
502
576
  this.#currentElement = null;
503
577
  this.#stopTracking();
504
578
  }
@@ -513,9 +587,7 @@ class HaloFocusService {
513
587
  this.#utility.shouldSkipElementInMatFormField(target) ||
514
588
  container?.hasAttribute('halo-container-skip') ||
515
589
  !this.#utility.matchesFocusVisible(target, this.#lastInteractionWasKeyboard)) {
516
- if (this.#haloElement) {
517
- this.#haloElement.style.opacity = '0';
518
- }
590
+ this.#hideHalo();
519
591
  this.#currentElement = null;
520
592
  return;
521
593
  }
@@ -524,7 +596,7 @@ class HaloFocusService {
524
596
  };
525
597
  #onBlur = (focusEvent) => {
526
598
  if (focusEvent.target === this.#currentElement && this.#haloElement) {
527
- this.#haloElement.style.opacity = '0';
599
+ this.#hideHalo();
528
600
  this.#currentElement = null;
529
601
  this.#stopTracking();
530
602
  }