@yuuvis/client-framework 3.11.0 → 3.12.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-actions.mjs +7 -2
- package/fesm2022/yuuvis-client-framework-actions.mjs.map +1 -1
- package/fesm2022/yuuvis-client-framework-forms.mjs +315 -29
- package/fesm2022/yuuvis-client-framework-forms.mjs.map +1 -1
- package/fesm2022/yuuvis-client-framework-metadata-form-defaults.mjs +4 -3
- package/fesm2022/yuuvis-client-framework-metadata-form-defaults.mjs.map +1 -1
- package/fesm2022/yuuvis-client-framework-object-details.mjs +51 -6
- package/fesm2022/yuuvis-client-framework-object-details.mjs.map +1 -1
- package/fesm2022/yuuvis-client-framework-renderer.mjs +26 -6
- package/fesm2022/yuuvis-client-framework-renderer.mjs.map +1 -1
- package/fesm2022/yuuvis-client-framework-tile-list.mjs +18 -7
- package/fesm2022/yuuvis-client-framework-tile-list.mjs.map +1 -1
- package/lib/assets/i18n/de.json +9 -7
- package/lib/assets/i18n/en.json +22 -20
- package/package.json +5 -5
- package/types/yuuvis-client-framework-forms.d.ts +110 -3
- package/types/yuuvis-client-framework-object-details.d.ts +29 -0
- package/types/yuuvis-client-framework-tile-list.d.ts +12 -2
|
@@ -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, ClassificationPrefix } from '@yuuvis/client-core';
|
|
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';
|
|
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';
|
|
@@ -33,6 +33,7 @@ import { DatepickerComponent, YuvDatepickerModule } from '@yuuvis/client-framewo
|
|
|
33
33
|
import * as i2$2 from '@yuuvis/client-framework/autocomplete';
|
|
34
34
|
import { YuvAutocompleteModule } from '@yuuvis/client-framework/autocomplete';
|
|
35
35
|
import { map as map$1, catchError } from 'rxjs/operators';
|
|
36
|
+
import { ShellService } from '@yuuvis/client-shell-core';
|
|
36
37
|
import { ENTER, COMMA } from '@angular/cdk/keycodes';
|
|
37
38
|
import * as i1$4 from '@angular/material/chips';
|
|
38
39
|
import { MatChipsModule } from '@angular/material/chips';
|
|
@@ -1049,7 +1050,6 @@ class NumberComponent extends AbstractMatFormField {
|
|
|
1049
1050
|
this.#dRef = inject(DestroyRef);
|
|
1050
1051
|
this.translate = inject(TranslateService);
|
|
1051
1052
|
// innerValue: string | null = null;
|
|
1052
|
-
this.validationErrors = [];
|
|
1053
1053
|
this.transformPipe = new LocaleNumberPipe(this.translate);
|
|
1054
1054
|
this.innerCtrl = new FormControl(null);
|
|
1055
1055
|
this.ngControl = injectNgControl(this);
|
|
@@ -1130,27 +1130,54 @@ class NumberComponent extends AbstractMatFormField {
|
|
|
1130
1130
|
}
|
|
1131
1131
|
// called when the input looses focus
|
|
1132
1132
|
format() {
|
|
1133
|
-
|
|
1134
|
-
|
|
1133
|
+
// The blur handler runs before Angular's value accessor marks the control as
|
|
1134
|
+
// touched, so mark it explicitly to make sure the error becomes visible.
|
|
1135
|
+
this.innerCtrl.markAsTouched();
|
|
1136
|
+
if (!this.readonly() && typeof this.value === 'number' && this.innerCtrl.valid) {
|
|
1137
|
+
const formatted = this.transformPipe.numberToString(this.value, this.#grouping(), this.groupPattern(), this.#scale());
|
|
1138
|
+
this.innerCtrl.patchValue(formatted, { emitEvent: false });
|
|
1135
1139
|
}
|
|
1140
|
+
this.#syncErrorState();
|
|
1136
1141
|
}
|
|
1137
1142
|
// called when the input get focus
|
|
1138
1143
|
unformat() {
|
|
1139
|
-
if (!this.readonly() && typeof this.value === 'number' && this.
|
|
1144
|
+
if (!this.readonly() && typeof this.value === 'number' && this.innerCtrl.valid) {
|
|
1140
1145
|
this.innerCtrl.patchValue(this.transformPipe.transform(this.value, false), {
|
|
1141
1146
|
emitEvent: false
|
|
1142
1147
|
});
|
|
1143
1148
|
}
|
|
1144
1149
|
}
|
|
1150
|
+
/**
|
|
1151
|
+
* Mirrors the inner control's validity onto the component's `errorState` and the
|
|
1152
|
+
* hosting `ngControl`. Invoked from the `statusChanges` subscription and from
|
|
1153
|
+
* `format()` (blur): blur re-formats via an `emitEvent: false` patch, which does
|
|
1154
|
+
* not fire `statusChanges`, so the error state has to be refreshed by hand there.
|
|
1155
|
+
*/
|
|
1156
|
+
#syncErrorState() {
|
|
1157
|
+
this.errorState = FormUtils.getErrorState(this.innerCtrl);
|
|
1158
|
+
if (this.ngControl?.control) {
|
|
1159
|
+
this.ngControl.control.setErrors(this.innerCtrl.errors);
|
|
1160
|
+
}
|
|
1161
|
+
}
|
|
1162
|
+
/** Updates the model value and notifies the form, but only when it actually changed. */
|
|
1163
|
+
#setValue(val) {
|
|
1164
|
+
if (this.value !== val) {
|
|
1165
|
+
this.value = val;
|
|
1166
|
+
this.propagateChange(this.value);
|
|
1167
|
+
}
|
|
1168
|
+
}
|
|
1145
1169
|
#getValidator() {
|
|
1146
1170
|
return (control) => {
|
|
1147
|
-
|
|
1171
|
+
// Pure validator: derive errors from the control value only, never mutate
|
|
1172
|
+
// component state here. State written as a side-effect gets out of sync
|
|
1173
|
+
// with the displayed value (validators also run on `emitEvent: false` writes).
|
|
1174
|
+
const errors = [];
|
|
1148
1175
|
if (Utils.isEmpty(control.value))
|
|
1149
1176
|
return null;
|
|
1150
1177
|
const val = this.transformPipe.stringToNumber(control.value);
|
|
1151
1178
|
// general number validation
|
|
1152
1179
|
if (isNaN(val) || typeof val !== 'number') {
|
|
1153
|
-
|
|
1180
|
+
errors.push({ key: 'number' });
|
|
1154
1181
|
}
|
|
1155
1182
|
else {
|
|
1156
1183
|
const split = control.value.replaceAll(this.transformPipe.separator, '').split(this.transformPipe.decimalSeparator);
|
|
@@ -1158,28 +1185,27 @@ class NumberComponent extends AbstractMatFormField {
|
|
|
1158
1185
|
if (this.#precision() !== undefined) {
|
|
1159
1186
|
const prePointDigits = this.#precision() - this.#scale();
|
|
1160
1187
|
if (split[0]?.length > prePointDigits) {
|
|
1161
|
-
|
|
1188
|
+
errors.push({ key: 'precision', params: { prePointDigits } });
|
|
1162
1189
|
}
|
|
1163
1190
|
}
|
|
1164
1191
|
// check scale
|
|
1165
1192
|
if (this.#scale() && split[1]?.length > this.#scale()) {
|
|
1166
|
-
|
|
1193
|
+
errors.push({ key: 'scale', params: { scale: this.#scale() } });
|
|
1167
1194
|
}
|
|
1168
1195
|
// min max
|
|
1169
1196
|
if (!_a.betweenTwoNumbers(val, this.minValue(), this.maxValue())) {
|
|
1170
1197
|
if (Utils.isEmpty(this.minValue())) {
|
|
1171
|
-
|
|
1198
|
+
errors.push({ key: 'maxvalue', params: { maxValue: this.maxValue() } });
|
|
1172
1199
|
}
|
|
1173
1200
|
else if (Utils.isEmpty(this.maxValue())) {
|
|
1174
|
-
|
|
1201
|
+
errors.push({ key: 'minvalue', params: { minValue: this.minValue() } });
|
|
1175
1202
|
}
|
|
1176
1203
|
else {
|
|
1177
|
-
|
|
1204
|
+
errors.push({ key: 'minmax', params: { minValue: this.minValue(), maxValue: this.maxValue() } });
|
|
1178
1205
|
}
|
|
1179
1206
|
}
|
|
1180
1207
|
}
|
|
1181
|
-
|
|
1182
|
-
return this.validationErrors.length ? e : null;
|
|
1208
|
+
return errors.length ? Utils.arrayToObject(errors, 'key', (err) => ({ valid: false, ...err })) : null;
|
|
1183
1209
|
};
|
|
1184
1210
|
}
|
|
1185
1211
|
ngOnInit() {
|
|
@@ -1188,25 +1214,20 @@ class NumberComponent extends AbstractMatFormField {
|
|
|
1188
1214
|
if (this.required)
|
|
1189
1215
|
validators.push(Validators.required);
|
|
1190
1216
|
this.innerCtrl.setValidators(validators);
|
|
1191
|
-
this.innerCtrl.statusChanges.pipe(takeUntilDestroyed(this.#dRef)).subscribe((
|
|
1192
|
-
this.errorState = FormUtils.getErrorState(this.innerCtrl);
|
|
1193
|
-
if (this.ngControl?.control) {
|
|
1194
|
-
this.ngControl.control.setErrors(this.innerCtrl.errors);
|
|
1195
|
-
}
|
|
1196
|
-
});
|
|
1217
|
+
this.innerCtrl.statusChanges.pipe(takeUntilDestroyed(this.#dRef)).subscribe(() => this.#syncErrorState());
|
|
1197
1218
|
this.innerCtrl.updateValueAndValidity();
|
|
1198
1219
|
this.innerCtrl.valueChanges.pipe(takeUntilDestroyed(this.#dRef)).subscribe((v) => {
|
|
1199
1220
|
if (Utils.isEmpty(v)) {
|
|
1200
|
-
this
|
|
1201
|
-
this.propagateChange(this.value);
|
|
1221
|
+
this.#setValue(null);
|
|
1202
1222
|
}
|
|
1203
|
-
else {
|
|
1223
|
+
else if (this.innerCtrl.valid) {
|
|
1204
1224
|
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1225
|
+
this.#setValue(this.transformPipe.stringToNumber(v));
|
|
1226
|
+
}
|
|
1227
|
+
else {
|
|
1228
|
+
// Invalid input: do not keep the previous value silently in the background —
|
|
1229
|
+
// clear the model so the form value matches what the user sees (an error).
|
|
1230
|
+
this.#setValue(null);
|
|
1210
1231
|
}
|
|
1211
1232
|
});
|
|
1212
1233
|
}
|
|
@@ -2116,6 +2137,271 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.12", ngImpo
|
|
|
2116
2137
|
args: [{ selector: 'yuv-range-select-filesize', standalone: true, imports: [MatSelectModule, ReactiveFormsModule], providers: [{ provide: MatFormFieldControl, useExisting: RangeSelectFilesizeComponent }], template: "<mat-select [panelWidth]=\"null\" [hideSingleSelectionIndicator]=\"true\" [formControl]=\"fc\">\n @for (o of options(); track $index) {\n <mat-option [value]=\"o.value\">{{ o.label }}</mat-option>\n }\n</mat-select>\n", styles: [":host{display:flex}:host mat-select{flex:1}\n"] }]
|
|
2117
2138
|
}], ctorParameters: () => [], propDecorators: { ranges: [{ type: i0.Input, args: [{ isSignal: true, alias: "ranges", required: false }] }] } });
|
|
2118
2139
|
|
|
2140
|
+
/** Escape single quotes and backslashes for safe embedding in a CMIS statement. */
|
|
2141
|
+
function escapeCmis(value) {
|
|
2142
|
+
return value.replace(/'/g, "''").replace(/\\/g, '\\\\');
|
|
2143
|
+
}
|
|
2144
|
+
/**
|
|
2145
|
+
* Form input for references to other dms objects. As the user types, a CMIS
|
|
2146
|
+
* fulltext search (narrowed to the allowed target types from the field's
|
|
2147
|
+
* `id:reference[...]` classification) provides suggestions. The selected object
|
|
2148
|
+
* id(s) are stored; on load they are resolved back to display chips.
|
|
2149
|
+
*
|
|
2150
|
+
* ## Label retrieval
|
|
2151
|
+
*
|
|
2152
|
+
* The chip label shown for a referenced object is its resolved object-config
|
|
2153
|
+
* title, not a raw property. Both search hits and objects resolved on load are
|
|
2154
|
+
* turned into a {@link ReferenceEntry} by `#toEntryFromDms`, which asks
|
|
2155
|
+
* {@link ObjectConfigService.getResolvedObjectConfig} for the title of the
|
|
2156
|
+
* object's data. The tricky part is picking the right config: reference targets
|
|
2157
|
+
* are usually secondary object types (SOTs) applied to a generic primary type,
|
|
2158
|
+
* and the title-defining config is keyed by the SOT's virtual config id (colons
|
|
2159
|
+
* replaced by dots, e.g. `appCasem.caseDocument`) inside the owning app's
|
|
2160
|
+
* bucket — not under the primary object type id. `#resolveConfigType` therefore
|
|
2161
|
+
* probes each allowed target type the hit actually carries (as primary type or
|
|
2162
|
+
* SOT), trying the dotted and raw config ids, and uses the first bucket that
|
|
2163
|
+
* defines one; it falls back to the primary object type's default config, and
|
|
2164
|
+
* ultimately to the raw object id when no title can be resolved. Objects that
|
|
2165
|
+
* cannot be fetched at all (e.g. deleted or access-denied) become `notFound`
|
|
2166
|
+
* entries labelled with their id.
|
|
2167
|
+
*
|
|
2168
|
+
* ## Opening a reference entry
|
|
2169
|
+
*
|
|
2170
|
+
* A chip can be opened only when a loaded feature app claims responsibility for
|
|
2171
|
+
* the referenced object's type. `#openAppId` maps the entry's `objectTypeId` to
|
|
2172
|
+
* an owning app id via {@link ObjectConfigService.getAppIdForType}, and
|
|
2173
|
+
* `canOpen` gates the action on {@link ShellService.canOpenObjectFor} (and on the
|
|
2174
|
+
* entry not being `notFound`) — this drives whether the chip is rendered as an
|
|
2175
|
+
* openable link. Invoking `open` delegates to {@link ShellService.openObject},
|
|
2176
|
+
* passing the app id and the entry's cached `data` so the app can route to /
|
|
2177
|
+
* display the object without an extra fetch.
|
|
2178
|
+
*
|
|
2179
|
+
* @example
|
|
2180
|
+
* <yuv-reference [multiselect]="true" [classifications]="['id:reference[appCasem:correctionDocument]']" />
|
|
2181
|
+
*/
|
|
2182
|
+
class ReferenceComponent extends AbstractMatFormField {
|
|
2183
|
+
constructor() {
|
|
2184
|
+
super(...arguments);
|
|
2185
|
+
this.#system = inject(SystemService);
|
|
2186
|
+
this.#search = inject(SearchService);
|
|
2187
|
+
this.#dms = inject(DmsService);
|
|
2188
|
+
this.#objectConfig = inject(ObjectConfigService);
|
|
2189
|
+
this.#shell = inject(ShellService);
|
|
2190
|
+
this.#dRef = inject(DestroyRef);
|
|
2191
|
+
this.busy = signal(false, ...(ngDevMode ? [{ debugName: "busy" }] : /* istanbul ignore next */ []));
|
|
2192
|
+
this.acFormControl = new FormControl(undefined);
|
|
2193
|
+
this.ngControl = injectNgControl(this);
|
|
2194
|
+
this._innerValue = [];
|
|
2195
|
+
this.autocompleteRes = [];
|
|
2196
|
+
/**
|
|
2197
|
+
* 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.
|
|
2198
|
+
*/
|
|
2199
|
+
this.situation = input(undefined, ...(ngDevMode ? [{ debugName: "situation" }] : /* istanbul ignore next */ []));
|
|
2200
|
+
/**
|
|
2201
|
+
* Indicator that multiple references could be selected, they will be rendered as chips (default: false).
|
|
2202
|
+
*/
|
|
2203
|
+
this.multiselect = input(false, ...(ngDevMode ? [{ debugName: "multiselect" }] : /* istanbul ignore next */ []));
|
|
2204
|
+
/**
|
|
2205
|
+
* Additional semantics for the form element. The `id:reference[...]` classification
|
|
2206
|
+
* restricts the suggestions to the target types listed in the brackets.
|
|
2207
|
+
*/
|
|
2208
|
+
this.classifications = input(undefined, ...(ngDevMode ? [{ debugName: "classifications" }] : /* istanbul ignore next */ []));
|
|
2209
|
+
/**
|
|
2210
|
+
* Will prevent the input from being changed (default: false)
|
|
2211
|
+
*/
|
|
2212
|
+
this.readonly = input(false, ...(ngDevMode ? [{ debugName: "readonly" }] : /* istanbul ignore next */ []));
|
|
2213
|
+
/**
|
|
2214
|
+
* Minimal number of characters to trigger the search (default: 2)
|
|
2215
|
+
*/
|
|
2216
|
+
this.minChars = input(2, ...(ngDevMode ? [{ debugName: "minChars" }] : /* istanbul ignore next */ []));
|
|
2217
|
+
/**
|
|
2218
|
+
* Maximal number of suggestions for the given search term (default: 10)
|
|
2219
|
+
*/
|
|
2220
|
+
this.maxSuggestions = input(10, ...(ngDevMode ? [{ debugName: "maxSuggestions" }] : /* istanbul ignore next */ []));
|
|
2221
|
+
/** Allowed target object/secondary object types parsed from the classification. */
|
|
2222
|
+
this.#allowedTargetTypes = computed(() => {
|
|
2223
|
+
const c = this.classifications();
|
|
2224
|
+
return c?.length ? this.#system.getClassifications(c).get(Classification.STRING_REFERENCE)?.options ?? [] : [];
|
|
2225
|
+
}, ...(ngDevMode ? [{ debugName: "#allowedTargetTypes" }] : /* istanbul ignore next */ []));
|
|
2226
|
+
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
|
2227
|
+
this.propagateChange = (_) => { };
|
|
2228
|
+
}
|
|
2229
|
+
#system;
|
|
2230
|
+
#search;
|
|
2231
|
+
#dms;
|
|
2232
|
+
#objectConfig;
|
|
2233
|
+
#shell;
|
|
2234
|
+
#dRef;
|
|
2235
|
+
set innerValue(iv) {
|
|
2236
|
+
this._innerValue = iv || [];
|
|
2237
|
+
}
|
|
2238
|
+
get innerValue() {
|
|
2239
|
+
return this._innerValue;
|
|
2240
|
+
}
|
|
2241
|
+
/** Allowed target object/secondary object types parsed from the classification. */
|
|
2242
|
+
#allowedTargetTypes;
|
|
2243
|
+
writeValue(value) {
|
|
2244
|
+
this.value = value;
|
|
2245
|
+
const ids = value ? (Array.isArray(value) ? value : [value]) : [];
|
|
2246
|
+
if (ids.length) {
|
|
2247
|
+
this.resolveFn(ids);
|
|
2248
|
+
}
|
|
2249
|
+
else {
|
|
2250
|
+
this.value = null;
|
|
2251
|
+
this.innerValue = [];
|
|
2252
|
+
this.acFormControl.setValue([], { emitEvent: false });
|
|
2253
|
+
}
|
|
2254
|
+
}
|
|
2255
|
+
registerOnChange(fn) {
|
|
2256
|
+
this.propagateChange = fn;
|
|
2257
|
+
}
|
|
2258
|
+
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
|
2259
|
+
registerOnTouched(fn) { }
|
|
2260
|
+
setDisabledState(isDisabled) {
|
|
2261
|
+
if (isDisabled) {
|
|
2262
|
+
this.acFormControl.disable();
|
|
2263
|
+
}
|
|
2264
|
+
else {
|
|
2265
|
+
this.acFormControl.enable();
|
|
2266
|
+
}
|
|
2267
|
+
this.disabled = isDisabled;
|
|
2268
|
+
}
|
|
2269
|
+
propagate() {
|
|
2270
|
+
this.value = this.multiselect() ? this.innerValue.map((v) => v.id) : this.innerValue[0]?.id;
|
|
2271
|
+
this.propagateChange(this.value);
|
|
2272
|
+
}
|
|
2273
|
+
/** Resolve stored object ids back to display entries. */
|
|
2274
|
+
resolveFn(ids) {
|
|
2275
|
+
this.#dms
|
|
2276
|
+
.getDmsObjects(ids)
|
|
2277
|
+
.pipe(catchError(() => of([])))
|
|
2278
|
+
.subscribe((objs) => {
|
|
2279
|
+
const byId = new Map(objs.map((o) => [o.id, o]));
|
|
2280
|
+
const entries = ids.map((id) => {
|
|
2281
|
+
const o = byId.get(id);
|
|
2282
|
+
return o instanceof DmsObject
|
|
2283
|
+
? this.#toEntryFromDms(o)
|
|
2284
|
+
: { id, objectTypeId: '', title: id, data: {}, notFound: true };
|
|
2285
|
+
});
|
|
2286
|
+
this.#updateAutocompleteControl(entries);
|
|
2287
|
+
});
|
|
2288
|
+
}
|
|
2289
|
+
#updateAutocompleteControl(data) {
|
|
2290
|
+
this.innerValue = data;
|
|
2291
|
+
const mapped = data.map((n) => ({ label: n.title, value: n }));
|
|
2292
|
+
this.acFormControl.setValue(this.multiselect() ? mapped : mapped.slice(0, 1), { emitEvent: false });
|
|
2293
|
+
this.acFormControl.updateValueAndValidity({ emitEvent: false });
|
|
2294
|
+
}
|
|
2295
|
+
autocompleteFn(query) {
|
|
2296
|
+
if (query.length < this.minChars()) {
|
|
2297
|
+
this.autocompleteRes = [];
|
|
2298
|
+
return;
|
|
2299
|
+
}
|
|
2300
|
+
const types = this.#allowedTargetTypes();
|
|
2301
|
+
// A reference target type may be a hit's primary object type OR be carried in its
|
|
2302
|
+
// `system:secondaryObjectTypeIds` (a floating SOT applied to an object of a different
|
|
2303
|
+
// primary type). Match both columns so hits are not missed in the latter case.
|
|
2304
|
+
const quoted = types.map((type) => `'${escapeCmis(type)}'`).join(', ');
|
|
2305
|
+
const typeCond = types.length
|
|
2306
|
+
? `objectTypeId IN (${quoted}) OR ${BaseObjectTypeField.SECONDARY_OBJECT_TYPE_IDS} IN (${quoted})`
|
|
2307
|
+
: '';
|
|
2308
|
+
const where = [typeCond ? `(${typeCond})` : '', `CONTAINS('${escapeCmis(query)}*')`].filter(Boolean).join(' AND ');
|
|
2309
|
+
const statement = `SELECT * FROM system:object WHERE ${where}`;
|
|
2310
|
+
this.busy.set(true);
|
|
2311
|
+
this.#search
|
|
2312
|
+
.searchCmis(statement, this.maxSuggestions())
|
|
2313
|
+
.pipe(catchError(() => of({ items: [] })))
|
|
2314
|
+
.subscribe((res) => {
|
|
2315
|
+
this.busy.set(false);
|
|
2316
|
+
this.autocompleteRes = res.items
|
|
2317
|
+
.map((i) => this.#toEntry(i))
|
|
2318
|
+
.filter((e) => !this.innerValue.some((v) => v.id === e.id))
|
|
2319
|
+
.map((e) => ({ label: e.title, value: e }));
|
|
2320
|
+
});
|
|
2321
|
+
}
|
|
2322
|
+
#toEntry(item) {
|
|
2323
|
+
return this.#toEntryFromDms(new DmsObject(item));
|
|
2324
|
+
}
|
|
2325
|
+
#toEntryFromDms(dms) {
|
|
2326
|
+
const { type, bucket } = this.#resolveConfigType(dms);
|
|
2327
|
+
const roc = this.#objectConfig.getResolvedObjectConfig(dms.data, type, bucket);
|
|
2328
|
+
return {
|
|
2329
|
+
id: dms.id,
|
|
2330
|
+
objectTypeId: dms.objectTypeId,
|
|
2331
|
+
title: roc?.title?.value || dms.id,
|
|
2332
|
+
data: dms.data
|
|
2333
|
+
};
|
|
2334
|
+
}
|
|
2335
|
+
/**
|
|
2336
|
+
* Pick the object config type + bucket used to resolve a hit's display label.
|
|
2337
|
+
*
|
|
2338
|
+
* Reference targets are typically secondary object types (SOTs) applied to a
|
|
2339
|
+
* generic primary type (e.g. an `appCasem:caseDocument` SOT carried by a
|
|
2340
|
+
* `system:document`). The config that defines the display title is keyed by the
|
|
2341
|
+
* SOT's virtual config id — conventionally the SOT id with colons replaced by
|
|
2342
|
+
* dots (`appCasem.caseDocument`) — and stored in the owning app's bucket, never
|
|
2343
|
+
* under the primary object type id. Resolving by `dms.objectTypeId` alone
|
|
2344
|
+
* therefore misses it and the label falls back to the raw object id.
|
|
2345
|
+
*
|
|
2346
|
+
* For each allowed target type the hit actually carries (as its primary type or
|
|
2347
|
+
* a SOT) we probe the dotted and raw config ids and use the first that a bucket
|
|
2348
|
+
* defines. Falls back to the primary object type (default config) when nothing
|
|
2349
|
+
* matches.
|
|
2350
|
+
*/
|
|
2351
|
+
#resolveConfigType(dms) {
|
|
2352
|
+
const carried = new Set([dms.objectTypeId, ...dms.sots]);
|
|
2353
|
+
const candidateIds = this.#allowedTargetTypes()
|
|
2354
|
+
.filter((t) => carried.has(t))
|
|
2355
|
+
.flatMap((t) => (t.includes(':') ? [t.replace(/:/g, '.'), t] : [t]));
|
|
2356
|
+
for (const id of candidateIds) {
|
|
2357
|
+
const bucket = this.#objectConfig.findBucketForConfigType(id);
|
|
2358
|
+
if (bucket)
|
|
2359
|
+
return { type: { id }, bucket };
|
|
2360
|
+
}
|
|
2361
|
+
return { type: { id: dms.objectTypeId } };
|
|
2362
|
+
}
|
|
2363
|
+
/** App id able to open the referenced object, if any. */
|
|
2364
|
+
#openAppId(entry) {
|
|
2365
|
+
return entry.objectTypeId ? this.#objectConfig.getAppIdForType(entry.objectTypeId) : undefined;
|
|
2366
|
+
}
|
|
2367
|
+
/** Whether the referenced object can be opened by a loaded app. */
|
|
2368
|
+
canOpen(entry) {
|
|
2369
|
+
return !entry.notFound && this.#shell.canOpenObjectFor(this.#openAppId(entry));
|
|
2370
|
+
}
|
|
2371
|
+
/** Open the referenced object via the owning app's registered open handler. */
|
|
2372
|
+
open(entry) {
|
|
2373
|
+
const appId = this.#openAppId(entry);
|
|
2374
|
+
if (appId && this.#shell.canOpenObjectFor(appId)) {
|
|
2375
|
+
this.#shell.openObject(appId, entry.data);
|
|
2376
|
+
}
|
|
2377
|
+
}
|
|
2378
|
+
ngOnInit() {
|
|
2379
|
+
if (this.required)
|
|
2380
|
+
this.acFormControl.setValidators(Validators.required);
|
|
2381
|
+
this.acFormControl.statusChanges.pipe(takeUntilDestroyed(this.#dRef)).subscribe(() => {
|
|
2382
|
+
this.errorState = FormUtils.getErrorState(this.acFormControl);
|
|
2383
|
+
if (this.ngControl?.control) {
|
|
2384
|
+
this.ngControl.control.setErrors(this.acFormControl.errors);
|
|
2385
|
+
}
|
|
2386
|
+
});
|
|
2387
|
+
this.acFormControl.updateValueAndValidity({ emitEvent: false });
|
|
2388
|
+
this.acFormControl.valueChanges.pipe(takeUntilDestroyed(this.#dRef)).subscribe((v) => {
|
|
2389
|
+
const items = Array.isArray(v) ? v : v ? [v] : [];
|
|
2390
|
+
this.innerValue = items.map((i) => i.value);
|
|
2391
|
+
this.propagate();
|
|
2392
|
+
});
|
|
2393
|
+
}
|
|
2394
|
+
ngOnDestroy() {
|
|
2395
|
+
super.onNgOnDestroy();
|
|
2396
|
+
}
|
|
2397
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: ReferenceComponent, deps: null, target: i0.ɵɵFactoryTarget.Component }); }
|
|
2398
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.12", 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 }); }
|
|
2399
|
+
}
|
|
2400
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: ReferenceComponent, decorators: [{
|
|
2401
|
+
type: Component,
|
|
2402
|
+
args: [{ selector: 'yuv-reference', standalone: true, imports: [CommonModule, FormsModule, ReactiveFormsModule, YuvAutocompleteModule, MatIconModule, MatTooltipModule, TranslatePipe], changeDetection: ChangeDetectionStrategy.OnPush, providers: [{ provide: MatFormFieldControl, useExisting: ReferenceComponent }], 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"] }]
|
|
2403
|
+
}], 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 }] }], minChars: [{ type: i0.Input, args: [{ isSignal: true, alias: "minChars", required: false }] }], maxSuggestions: [{ type: i0.Input, args: [{ isSignal: true, alias: "maxSuggestions", required: false }] }] } });
|
|
2404
|
+
|
|
2119
2405
|
/**
|
|
2120
2406
|
* Creates form input for strings. Based on the input values different kinds of inputs will be generated.
|
|
2121
2407
|
*
|
|
@@ -2455,5 +2741,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.12", ngImpo
|
|
|
2455
2741
|
* Generated bundle index. Do not edit.
|
|
2456
2742
|
*/
|
|
2457
2743
|
|
|
2458
|
-
export { CatalogComponent, DataGridComponent, DatetimeComponent, DatetimeRangeComponent, DynamicCatalogComponent, FormUtils, I18nCatalogComponent, NumberComponent, NumberRangeComponent, OrganizationComponent, OrganizationSetComponent, RangeSelectDateComponent, RangeSelectFilesizeComponent, StringComponent, YuvFormsModule };
|
|
2744
|
+
export { CatalogComponent, DataGridComponent, DatetimeComponent, DatetimeRangeComponent, DynamicCatalogComponent, FormUtils, I18nCatalogComponent, NumberComponent, NumberRangeComponent, OrganizationComponent, OrganizationSetComponent, RangeSelectDateComponent, RangeSelectFilesizeComponent, ReferenceComponent, StringComponent, YuvFormsModule };
|
|
2459
2745
|
//# sourceMappingURL=yuuvis-client-framework-forms.mjs.map
|