@yuuvis/client-framework 3.22.0 → 3.23.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.
- package/fesm2022/yuuvis-client-framework-forms.mjs +129 -14
- package/fesm2022/yuuvis-client-framework-forms.mjs.map +1 -1
- package/fesm2022/yuuvis-client-framework-smart-search.mjs +2 -2
- package/fesm2022/yuuvis-client-framework-smart-search.mjs.map +1 -1
- package/lib/assets/i18n/de.json +1 -0
- package/lib/assets/i18n/en.json +1 -0
- package/package.json +5 -5
- package/types/yuuvis-client-framework-forms.d.ts +32 -1
|
@@ -9,7 +9,7 @@ import { MatSelectModule } from '@angular/material/select';
|
|
|
9
9
|
import { SystemService, TranslateService, Classification, TranslatePipe, Situation, Utils, Operator, OperatorLabel, 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
|
-
import {
|
|
12
|
+
import { CommonModule, NgClass } from '@angular/common';
|
|
13
13
|
import { MatButtonModule } from '@angular/material/button';
|
|
14
14
|
import { MAT_DIALOG_DATA, MatDialogRef, MatDialogModule, MatDialog } from '@angular/material/dialog';
|
|
15
15
|
import * as i2 from '@angular/material/icon';
|
|
@@ -152,6 +152,96 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.20", ngImpo
|
|
|
152
152
|
args: [{ selector: 'yuv-catalog', imports: [MatSelectModule, ReactiveFormsModule, TranslatePipe], providers: [{ provide: MatFormFieldControl, useExisting: CatalogComponent }], template: "<mat-select [multiple]=\"multiple()\" [disabled]=\"readonly()\" [formControl]=\"formCtrl\">\n @if (!multiple() && !required) {\n <mat-option [value]=\"null\">{{ 'yuv.form.element.catalog.option.none' | translate }}</mat-option>\n }\n @for (o of _options(); track o) {\n <mat-option [value]=\"o.value\">{{ o.label }}</mat-option>\n }\n</mat-select>\n" }]
|
|
153
153
|
}], propDecorators: { readonly: [{ type: i0.Input, args: [{ isSignal: true, alias: "readonly", required: false }] }], multiple: [{ type: i0.Input, args: [{ isSignal: true, alias: "multiple", required: false }] }], options: [{ type: i0.Input, args: [{ isSignal: true, alias: "options", required: false }] }], classifications: [{ type: i0.Input, args: [{ isSignal: true, alias: "classifications", required: false }] }], situation: [{ type: i0.Input, args: [{ isSignal: true, alias: "situation", required: false }] }] } });
|
|
154
154
|
|
|
155
|
+
const HEX_PATTERN = /^#([0-9a-fA-F]{3}|[0-9a-fA-F]{6})$/;
|
|
156
|
+
/**
|
|
157
|
+
* Form input for a color value. Renders a hex text input alongside a native color picker
|
|
158
|
+
* swatch; both stay in sync. The value is a hex color string (e.g. `#2A70AC`).
|
|
159
|
+
*
|
|
160
|
+
* Implements `ControlValueAccessor` so it can be used within Angular forms and Material form
|
|
161
|
+
* fields.
|
|
162
|
+
*
|
|
163
|
+
* @example
|
|
164
|
+
* <mat-form-field>
|
|
165
|
+
* <mat-label>Primary color</mat-label>
|
|
166
|
+
* <yuv-color formControlName="primaryColor"></yuv-color>
|
|
167
|
+
* </mat-form-field>
|
|
168
|
+
*/
|
|
169
|
+
class ColorComponent extends AbstractMatFormField {
|
|
170
|
+
constructor() {
|
|
171
|
+
super(...arguments);
|
|
172
|
+
this.#dRef = inject(DestroyRef);
|
|
173
|
+
/** the color swatch (native `<input type="color">`, always a full 6-digit hex) */
|
|
174
|
+
this.colorFc = new FormControl('#000000', { nonNullable: true });
|
|
175
|
+
/** the editable hex text input (source of truth for the value) */
|
|
176
|
+
this.textFc = new FormControl(null);
|
|
177
|
+
this.ngControl = injectNgControl(this);
|
|
178
|
+
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
|
179
|
+
this.propagateChange = (_) => { };
|
|
180
|
+
}
|
|
181
|
+
#dRef;
|
|
182
|
+
writeValue(value) {
|
|
183
|
+
this.value = value;
|
|
184
|
+
this.textFc.setValue(value ?? null, { emitEvent: false });
|
|
185
|
+
if (value && HEX_PATTERN.test(value)) {
|
|
186
|
+
this.colorFc.setValue(value, { emitEvent: false });
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
registerOnChange(fn) {
|
|
190
|
+
this.propagateChange = fn;
|
|
191
|
+
}
|
|
192
|
+
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
|
193
|
+
registerOnTouched() { }
|
|
194
|
+
setDisabledState(isDisabled) {
|
|
195
|
+
if (isDisabled) {
|
|
196
|
+
this.colorFc.disable();
|
|
197
|
+
this.textFc.disable();
|
|
198
|
+
}
|
|
199
|
+
else {
|
|
200
|
+
this.colorFc.enable();
|
|
201
|
+
this.textFc.enable();
|
|
202
|
+
}
|
|
203
|
+
this.disabled = isDisabled;
|
|
204
|
+
}
|
|
205
|
+
ngOnInit() {
|
|
206
|
+
const validators = [(control) => this.#hexValidator(control.value)];
|
|
207
|
+
if (this.required)
|
|
208
|
+
validators.push(() => (this.value ? null : { required: true }));
|
|
209
|
+
this.textFc.setValidators(validators);
|
|
210
|
+
this.textFc.statusChanges.pipe(takeUntilDestroyed(this.#dRef)).subscribe(() => {
|
|
211
|
+
this.errorState = FormUtils.getErrorState(this.textFc);
|
|
212
|
+
this.ngControl?.control?.setErrors(this.textFc.errors);
|
|
213
|
+
});
|
|
214
|
+
// hex text drives the value; keep the swatch in sync when the text is a valid hex
|
|
215
|
+
this.textFc.valueChanges.pipe(takeUntilDestroyed(this.#dRef)).subscribe((v) => {
|
|
216
|
+
const hex = v?.trim() || null;
|
|
217
|
+
this.value = hex;
|
|
218
|
+
if (hex && HEX_PATTERN.test(hex)) {
|
|
219
|
+
this.colorFc.setValue(hex, { emitEvent: false });
|
|
220
|
+
}
|
|
221
|
+
this.propagateChange(hex);
|
|
222
|
+
});
|
|
223
|
+
// swatch changes update the hex text (which re-propagates)
|
|
224
|
+
this.colorFc.valueChanges.pipe(takeUntilDestroyed(this.#dRef)).subscribe((v) => {
|
|
225
|
+
this.textFc.setValue(v);
|
|
226
|
+
});
|
|
227
|
+
this.textFc.updateValueAndValidity();
|
|
228
|
+
}
|
|
229
|
+
ngOnDestroy() {
|
|
230
|
+
super.onNgOnDestroy();
|
|
231
|
+
}
|
|
232
|
+
#hexValidator(value) {
|
|
233
|
+
if (!value)
|
|
234
|
+
return null;
|
|
235
|
+
return HEX_PATTERN.test(value.trim()) ? null : { color: { valid: false } };
|
|
236
|
+
}
|
|
237
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.20", ngImport: i0, type: ColorComponent, deps: null, target: i0.ɵɵFactoryTarget.Component }); }
|
|
238
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "21.2.20", type: ColorComponent, isStandalone: true, selector: "yuv-color", providers: [{ provide: MatFormFieldControl, useExisting: ColorComponent }], usesInheritance: true, ngImport: i0, template: "<span class=\"yuv-color\">\n <label class=\"yuv-color__swatch\" [style.background-color]=\"colorFc.value\">\n <input type=\"color\" [formControl]=\"colorFc\" aria-hidden=\"true\" tabindex=\"-1\" />\n </label>\n <input class=\"yuv-color__hex\" type=\"text\" autocomplete=\"off\" spellcheck=\"false\" [formControl]=\"textFc\" />\n</span>\n", styles: [".yuv-color{display:inline-flex;align-items:center;gap:var(--ymt-spacing-xs);width:100%;margin-block-start:var(--ymt-spacing-xs)}.yuv-color__swatch{position:relative;flex:0 0 auto;inline-size:var(--ymt-sizing-m);block-size:var(--ymt-sizing-m);border-radius:var(--ymt-corner-xs);border:var(--ymt-outline-width) solid var(--ymt-outline-variant);overflow:hidden;cursor:pointer;outline:2px solid currentColor;outline-offset:-1px}.yuv-color__swatch input[type=color]{position:absolute;inset:0;inline-size:100%;block-size:100%;padding:0;border:none;opacity:0;cursor:pointer}.yuv-color__hex{flex:1 1 auto;min-inline-size:0;border:none;outline:none;background:transparent;color:var(--ymt-text-color);font:var(--ymt-font-body)}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1$1.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: i1$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$1.FormControlDirective, selector: "[formControl]", inputs: ["formControl", "disabled", "ngModel"], outputs: ["ngModelChange"], exportAs: ["ngForm"] }] }); }
|
|
239
|
+
}
|
|
240
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.20", ngImport: i0, type: ColorComponent, decorators: [{
|
|
241
|
+
type: Component,
|
|
242
|
+
args: [{ selector: 'yuv-color', standalone: true, imports: [CommonModule, ReactiveFormsModule], providers: [{ provide: MatFormFieldControl, useExisting: ColorComponent }], template: "<span class=\"yuv-color\">\n <label class=\"yuv-color__swatch\" [style.background-color]=\"colorFc.value\">\n <input type=\"color\" [formControl]=\"colorFc\" aria-hidden=\"true\" tabindex=\"-1\" />\n </label>\n <input class=\"yuv-color__hex\" type=\"text\" autocomplete=\"off\" spellcheck=\"false\" [formControl]=\"textFc\" />\n</span>\n", styles: [".yuv-color{display:inline-flex;align-items:center;gap:var(--ymt-spacing-xs);width:100%;margin-block-start:var(--ymt-spacing-xs)}.yuv-color__swatch{position:relative;flex:0 0 auto;inline-size:var(--ymt-sizing-m);block-size:var(--ymt-sizing-m);border-radius:var(--ymt-corner-xs);border:var(--ymt-outline-width) solid var(--ymt-outline-variant);overflow:hidden;cursor:pointer;outline:2px solid currentColor;outline-offset:-1px}.yuv-color__swatch input[type=color]{position:absolute;inset:0;inline-size:100%;block-size:100%;padding:0;border:none;opacity:0;cursor:pointer}.yuv-color__hex{flex:1 1 auto;min-inline-size:0;border:none;outline:none;background:transparent;color:var(--ymt-text-color);font:var(--ymt-font-body)}\n"] }]
|
|
243
|
+
}] });
|
|
244
|
+
|
|
155
245
|
class EditTableDataComponent {
|
|
156
246
|
constructor() {
|
|
157
247
|
this.#fb = inject(FormBuilder);
|
|
@@ -1062,6 +1152,26 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.20", ngImpo
|
|
|
1062
1152
|
}], ctorParameters: () => [], propDecorators: { readonly: [{ type: i0.Input, args: [{ isSignal: true, alias: "readonly", required: false }] }], multiple: [{ type: i0.Input, args: [{ isSignal: true, alias: "multiple", required: false }] }], propertyName: [{ type: i0.Input, args: [{ isSignal: true, alias: "propertyName", required: false }] }], options: [{ type: i0.Input, args: [{ isSignal: true, alias: "options", required: false }] }], classifications: [{ type: i0.Input, args: [{ isSignal: true, alias: "classifications", required: false }] }], situation: [{ type: i0.Input, args: [{ isSignal: true, alias: "situation", required: false }] }] } });
|
|
1063
1153
|
|
|
1064
1154
|
var _a;
|
|
1155
|
+
/**
|
|
1156
|
+
* Splits a number into the digits of its mantissa and its decimal exponent, so that
|
|
1157
|
+
* digit counting stays correct for values JavaScript renders in exponential notation
|
|
1158
|
+
* (`1e-7`, `1e21`).
|
|
1159
|
+
*/
|
|
1160
|
+
function splitDigits(value) {
|
|
1161
|
+
const [mantissa, exponent] = Math.abs(value).toString().split('e');
|
|
1162
|
+
const [intPart, fractionPart = ''] = mantissa.split('.');
|
|
1163
|
+
return { intPart, fractionPart, exponent: Number(exponent ?? 0) };
|
|
1164
|
+
}
|
|
1165
|
+
/** Decimal places of a number: `12.5` → 1, `12` → 0, `1e-7` → 7. */
|
|
1166
|
+
function decimalPlaces(value) {
|
|
1167
|
+
const { fractionPart, exponent } = splitDigits(value);
|
|
1168
|
+
return Math.max(0, fractionPart.length - exponent);
|
|
1169
|
+
}
|
|
1170
|
+
/** Digits before the decimal separator, sign excluded: `-1234.5` → 4, `0.5` → 1. */
|
|
1171
|
+
function integerDigits(value) {
|
|
1172
|
+
const { intPart, exponent } = splitDigits(value);
|
|
1173
|
+
return Math.max(1, intPart.length + exponent);
|
|
1174
|
+
}
|
|
1065
1175
|
/**
|
|
1066
1176
|
* Creates form input for number values.
|
|
1067
1177
|
*
|
|
@@ -1207,16 +1317,16 @@ class NumberComponent extends AbstractMatFormField {
|
|
|
1207
1317
|
errors.push({ key: 'number' });
|
|
1208
1318
|
}
|
|
1209
1319
|
else {
|
|
1210
|
-
|
|
1320
|
+
// Digits are counted on the parsed value, never on the typed text: the text
|
|
1321
|
+
// carries locale separators, a sign and — for the filesize classification —
|
|
1322
|
+
// a unit ("1.5 MB"), none of which say anything about the value being stored.
|
|
1211
1323
|
// check precision
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
errors.push({ key: 'precision', params: { prePointDigits } });
|
|
1216
|
-
}
|
|
1324
|
+
const prePointDigits = this.#precision() - this.#scale();
|
|
1325
|
+
if (integerDigits(val) > prePointDigits) {
|
|
1326
|
+
errors.push({ key: 'precision', params: { prePointDigits } });
|
|
1217
1327
|
}
|
|
1218
|
-
// check scale
|
|
1219
|
-
if (
|
|
1328
|
+
// check scale: a scale of 0 (set explicitly or left unset) allows no decimals at all
|
|
1329
|
+
if (decimalPlaces(val) > this.#scale()) {
|
|
1220
1330
|
errors.push({ key: 'scale', params: { scale: this.#scale() } });
|
|
1221
1331
|
}
|
|
1222
1332
|
// min max
|
|
@@ -1632,7 +1742,12 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.20", ngImpo
|
|
|
1632
1742
|
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 || item.label || '...' }}\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"] }]
|
|
1633
1743
|
}], 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 }] }] } });
|
|
1634
1744
|
|
|
1635
|
-
|
|
1745
|
+
/**
|
|
1746
|
+
* Maps IDM results to autocomplete items. The `user` / `role` discriminator is supplied by the
|
|
1747
|
+
* caller instead of being inferred from the node's shape: the organization set already carries
|
|
1748
|
+
* a correct `type`, and every result of `queryUser()` is a user by definition.
|
|
1749
|
+
*/
|
|
1750
|
+
const mapOrganizationNode = (innerValue, nodeType, excludeUserId) => map$1((nodes) => nodes
|
|
1636
1751
|
.filter((node) => !innerValue?.some((value) => value?.id === node.id))
|
|
1637
1752
|
// `excludeMe` cannot be left to the backend: `/idm/search` has no such parameter at all,
|
|
1638
1753
|
// and `/idm/users?excludeMe=true` is not guaranteed to honour it. Dropping the current
|
|
@@ -1644,7 +1759,7 @@ const mapOrganizationNode = (innerValue, excludeUserId) => map$1((nodes) => node
|
|
|
1644
1759
|
value: {
|
|
1645
1760
|
id: node.id,
|
|
1646
1761
|
title: node.title,
|
|
1647
|
-
type:
|
|
1762
|
+
type: nodeType(node)
|
|
1648
1763
|
}
|
|
1649
1764
|
})));
|
|
1650
1765
|
/**
|
|
@@ -1928,10 +2043,10 @@ class OrganizationComponent extends AbstractMatFormField {
|
|
|
1928
2043
|
this.acFormControl.updateValueAndValidity({ emitEvent: false });
|
|
1929
2044
|
}
|
|
1930
2045
|
#resolveOrganization(query, excludeMe, filterRoles) {
|
|
1931
|
-
return this.#userService.queryUser(query, excludeMe, filterRoles).pipe(mapOrganizationNode(this.innerValue, excludeMe ? this.#ownUserId() : undefined), catchError(() => of([])));
|
|
2046
|
+
return this.#userService.queryUser(query, excludeMe, filterRoles).pipe(mapOrganizationNode(this.innerValue, () => 'user', excludeMe ? this.#ownUserId() : undefined), catchError(() => of([])));
|
|
1932
2047
|
}
|
|
1933
2048
|
#resolveOrganizationSet(query, excludeMe, filterRoles) {
|
|
1934
|
-
return this.#idmService.queryOrganizationEntity(query, filterRoles).pipe(mapOrganizationNode(this.innerValue, excludeMe ? this.#ownUserId() : undefined), catchError(() => of([])));
|
|
2049
|
+
return this.#idmService.queryOrganizationEntity(query, filterRoles).pipe(mapOrganizationNode(this.innerValue, (node) => node.type, excludeMe ? this.#ownUserId() : undefined), catchError(() => of([])));
|
|
1935
2050
|
}
|
|
1936
2051
|
/** Id of the signed-in user — the value {@link excludeMe} filters out of the suggestions. */
|
|
1937
2052
|
#ownUserId() {
|
|
@@ -2878,5 +2993,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.20", ngImpo
|
|
|
2878
2993
|
* Generated bundle index. Do not edit.
|
|
2879
2994
|
*/
|
|
2880
2995
|
|
|
2881
|
-
export { CatalogComponent, DataGridComponent, DatetimeComponent, DatetimeRangeComponent, DynamicCatalogComponent, FormUtils, I18nCatalogComponent, NumberComponent, NumberRangeComponent, OrganizationComponent, OrganizationSetComponent, RangeSelectDateComponent, RangeSelectFilesizeComponent, ReferenceComponent, StringComponent, YuvFormsModule };
|
|
2996
|
+
export { CatalogComponent, ColorComponent, DataGridComponent, DatetimeComponent, DatetimeRangeComponent, DynamicCatalogComponent, FormUtils, I18nCatalogComponent, NumberComponent, NumberRangeComponent, OrganizationComponent, OrganizationSetComponent, RangeSelectDateComponent, RangeSelectFilesizeComponent, ReferenceComponent, StringComponent, YuvFormsModule };
|
|
2882
2997
|
//# sourceMappingURL=yuuvis-client-framework-forms.mjs.map
|