@tft/crispr-forms 21.0.17 → 21.1.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/README.md +21 -4
- package/fesm2022/tft-crispr-forms.mjs +2071 -1
- package/fesm2022/tft-crispr-forms.mjs.map +1 -1
- package/package.json +7 -7
- package/types/tft-crispr-forms.d.ts +238 -212
- package/fesm2022/tft-crispr-forms-autocomplete-field.component-BgQZYqB0.mjs +0 -95
- package/fesm2022/tft-crispr-forms-autocomplete-field.component-BgQZYqB0.mjs.map +0 -1
- package/fesm2022/tft-crispr-forms-index-3aw152gK.mjs +0 -18
- package/fesm2022/tft-crispr-forms-index-3aw152gK.mjs.map +0 -1
- package/fesm2022/tft-crispr-forms-index-BFzx83fR.mjs +0 -39
- package/fesm2022/tft-crispr-forms-index-BFzx83fR.mjs.map +0 -1
- package/fesm2022/tft-crispr-forms-index-BMZQakFs.mjs +0 -88
- package/fesm2022/tft-crispr-forms-index-BMZQakFs.mjs.map +0 -1
- package/fesm2022/tft-crispr-forms-index-B_lhA83w.mjs +0 -2
- package/fesm2022/tft-crispr-forms-index-B_lhA83w.mjs.map +0 -1
- package/fesm2022/tft-crispr-forms-index-BawhjTMp.mjs +0 -183
- package/fesm2022/tft-crispr-forms-index-BawhjTMp.mjs.map +0 -1
- package/fesm2022/tft-crispr-forms-index-CXXD0vdZ.mjs +0 -172
- package/fesm2022/tft-crispr-forms-index-CXXD0vdZ.mjs.map +0 -1
- package/fesm2022/tft-crispr-forms-index-CmVahfkJ.mjs +0 -24
- package/fesm2022/tft-crispr-forms-index-CmVahfkJ.mjs.map +0 -1
- package/fesm2022/tft-crispr-forms-index-CoUwTugQ.mjs +0 -309
- package/fesm2022/tft-crispr-forms-index-CoUwTugQ.mjs.map +0 -1
- package/fesm2022/tft-crispr-forms-index-D99OHDPc.mjs +0 -40
- package/fesm2022/tft-crispr-forms-index-D99OHDPc.mjs.map +0 -1
- package/fesm2022/tft-crispr-forms-index-DYWkX7D3.mjs +0 -83
- package/fesm2022/tft-crispr-forms-index-DYWkX7D3.mjs.map +0 -1
- package/fesm2022/tft-crispr-forms-index-DysioRya.mjs +0 -39
- package/fesm2022/tft-crispr-forms-index-DysioRya.mjs.map +0 -1
- package/fesm2022/tft-crispr-forms-index-GlYQbbbv.mjs +0 -36
- package/fesm2022/tft-crispr-forms-index-GlYQbbbv.mjs.map +0 -1
- package/fesm2022/tft-crispr-forms-index-UX-Wdyk1.mjs +0 -40
- package/fesm2022/tft-crispr-forms-index-UX-Wdyk1.mjs.map +0 -1
- package/fesm2022/tft-crispr-forms-index-bTdYHJPk.mjs +0 -68
- package/fesm2022/tft-crispr-forms-index-bTdYHJPk.mjs.map +0 -1
- package/fesm2022/tft-crispr-forms-index-kah2HT_N.mjs +0 -54
- package/fesm2022/tft-crispr-forms-index-kah2HT_N.mjs.map +0 -1
- package/fesm2022/tft-crispr-forms-tft-crispr-forms-8liD7eof.mjs +0 -943
- package/fesm2022/tft-crispr-forms-tft-crispr-forms-8liD7eof.mjs.map +0 -1
|
@@ -1,2 +1,2072 @@
|
|
|
1
|
-
|
|
1
|
+
import * as i0 from '@angular/core';
|
|
2
|
+
import { input, computed, Directive, inject, DestroyRef, Injector, output, effect, afterNextRender, ViewContainerRef, Renderer2, Pipe, untracked, viewChild, ChangeDetectionStrategy, Component, HostListener, signal, NgModule } from '@angular/core';
|
|
3
|
+
import { takeUntilDestroyed, outputFromObservable, toObservable, toSignal } from '@angular/core/rxjs-interop';
|
|
4
|
+
import * as i1$1 from '@angular/forms';
|
|
5
|
+
import { FormArray, FormControl, FormGroup, ReactiveFormsModule, NgControl } from '@angular/forms';
|
|
6
|
+
import { map, startWith, debounceTime, distinctUntilChanged, switchMap, shareReplay, tap, take } from 'rxjs/operators';
|
|
7
|
+
import * as i1 from '@angular/common';
|
|
8
|
+
import { CommonModule, AsyncPipe } from '@angular/common';
|
|
9
|
+
import { from, of, isObservable, BehaviorSubject, combineLatest, Subject, Observable, concat, defer, filter, switchMap as switchMap$1, map as map$1, startWith as startWith$1, debounceTime as debounceTime$1 } from 'rxjs';
|
|
10
|
+
import { ControlErrorContainerDirective, ControlErrorsFormDirective, ControlErrorsDirective } from '@tft/form-validation-handler';
|
|
11
|
+
import * as i3 from '@angular/material/button';
|
|
12
|
+
import { MatButtonModule, MatButton } from '@angular/material/button';
|
|
13
|
+
import * as i2$2 from '@angular/material/form-field';
|
|
14
|
+
import { MatFormFieldModule, MatFormField, MatLabel } from '@angular/material/form-field';
|
|
15
|
+
import * as i2 from '@angular/material/icon';
|
|
16
|
+
import { MatIconModule } from '@angular/material/icon';
|
|
17
|
+
import * as i3$1 from '@angular/material/input';
|
|
18
|
+
import { MatInputModule, MatInput } from '@angular/material/input';
|
|
19
|
+
import * as i2$1 from '@angular/material/tooltip';
|
|
20
|
+
import { MatTooltipModule } from '@angular/material/tooltip';
|
|
21
|
+
import * as i1$2 from '@angular/material/progress-bar';
|
|
22
|
+
import { MatProgressBarModule } from '@angular/material/progress-bar';
|
|
23
|
+
import * as i2$3 from '@angular/material/autocomplete';
|
|
24
|
+
import { MatAutocompleteTrigger, MatAutocompleteModule } from '@angular/material/autocomplete';
|
|
25
|
+
import { ENTER } from '@angular/cdk/keycodes';
|
|
26
|
+
import * as i2$5 from '@angular/cdk/overlay';
|
|
27
|
+
import { CdkOverlayOrigin, CdkConnectedOverlay, OverlayModule } from '@angular/cdk/overlay';
|
|
28
|
+
import * as i5 from '@angular/material/chips';
|
|
29
|
+
import { MatChipsModule } from '@angular/material/chips';
|
|
30
|
+
import { MatOptionModule } from '@angular/material/core';
|
|
31
|
+
import * as i1$3 from '@angular/material/checkbox';
|
|
32
|
+
import { MatCheckboxModule } from '@angular/material/checkbox';
|
|
33
|
+
import * as i1$4 from '@angular/material/datepicker';
|
|
34
|
+
import { MatDatepickerModule } from '@angular/material/datepicker';
|
|
35
|
+
import { MatDateFnsModule } from '@angular/material-date-fns-adapter';
|
|
36
|
+
import * as i5$1 from '@angular/cdk/portal';
|
|
37
|
+
import { ComponentPortal, PortalModule } from '@angular/cdk/portal';
|
|
38
|
+
import * as i2$4 from '@angular/material/divider';
|
|
39
|
+
import { MatDividerModule } from '@angular/material/divider';
|
|
40
|
+
import { DomSanitizer } from '@angular/platform-browser';
|
|
41
|
+
import imageCompression from 'browser-image-compression';
|
|
42
|
+
import { MapGeocoder, MapInfoWindow, GoogleMap, MapMarker } from '@angular/google-maps';
|
|
43
|
+
import * as i2$6 from '@angular/material/radio';
|
|
44
|
+
import { MatRadioModule } from '@angular/material/radio';
|
|
45
|
+
import * as i3$2 from '@angular/material/select';
|
|
46
|
+
import { MatSelectModule } from '@angular/material/select';
|
|
47
|
+
import * as i2$7 from '@angular/material/slider';
|
|
48
|
+
import { MatSliderModule } from '@angular/material/slider';
|
|
49
|
+
|
|
50
|
+
function valueIn(values) {
|
|
51
|
+
return map((value) => {
|
|
52
|
+
if (value) {
|
|
53
|
+
return values.some(v => {
|
|
54
|
+
if (isRegex(v)) {
|
|
55
|
+
const trimmedStringForRegex = v.substr(1, v.length - 2);
|
|
56
|
+
const regex = new RegExp(trimmedStringForRegex);
|
|
57
|
+
return regex.test(value);
|
|
58
|
+
}
|
|
59
|
+
else {
|
|
60
|
+
return v === value;
|
|
61
|
+
}
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
else {
|
|
65
|
+
return false;
|
|
66
|
+
}
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
function isRegex(string) {
|
|
70
|
+
return string.startsWith('/') && string.endsWith('/');
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const DEFAULT_EMPTY_OPTIONS_MESSAGE = 'No Items';
|
|
74
|
+
|
|
75
|
+
// export type ControlType = keyof typeof FIELD_COMPONENTS;
|
|
76
|
+
var ControlType;
|
|
77
|
+
(function (ControlType) {
|
|
78
|
+
ControlType["AUTOCOMPLETE"] = "autocomplete";
|
|
79
|
+
ControlType["AUTOCOMPLETE_CHIPLIST"] = "autocompleteChiplist";
|
|
80
|
+
ControlType["BUTTON"] = "button";
|
|
81
|
+
ControlType["CHECKBOX"] = "checkbox";
|
|
82
|
+
ControlType["CUSTOM"] = "custom";
|
|
83
|
+
ControlType["DATEPICKER"] = "datepicker";
|
|
84
|
+
ControlType["DIVIDER"] = "divider";
|
|
85
|
+
ControlType["FILE_UPLOAD"] = "fileUpload";
|
|
86
|
+
ControlType["GROUP_LIST"] = "groupList";
|
|
87
|
+
ControlType["HEADING"] = "heading";
|
|
88
|
+
ControlType["IMAGE_UPLOAD"] = "imageUpload";
|
|
89
|
+
ControlType["INPUT"] = "input";
|
|
90
|
+
ControlType["MAP"] = "map";
|
|
91
|
+
ControlType["RADIO"] = "radio";
|
|
92
|
+
ControlType["SELECT"] = "select";
|
|
93
|
+
ControlType["SLIDER"] = "slider";
|
|
94
|
+
ControlType["SUB_GROUP"] = "subGroup";
|
|
95
|
+
ControlType["TEXTAREA"] = "textarea";
|
|
96
|
+
ControlType["UNIT_CONVERSION"] = "unitConversion";
|
|
97
|
+
})(ControlType || (ControlType = {}));
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Analyze the config and build a form control to spec. Notice we don't use FormBuilder here
|
|
101
|
+
* as we want to keep this function pure.
|
|
102
|
+
* @param controlConfig the configuration object for the control to build
|
|
103
|
+
* @param value an initial value to use if passed in
|
|
104
|
+
*/
|
|
105
|
+
function createControlForType(controlConfig, value = null) {
|
|
106
|
+
// build form control out based on the control type
|
|
107
|
+
const control = controlConfig.controlType === ControlType.SUB_GROUP
|
|
108
|
+
? buildFormGroupFromConfig(controlConfig, value)
|
|
109
|
+
: controlConfig.controlType === ControlType.GROUP_LIST
|
|
110
|
+
? new FormArray([], controlConfig.validators)
|
|
111
|
+
: new FormControl(value || null, controlConfig.validators);
|
|
112
|
+
return control;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Determines if the field config is for a form control (not a divider, heading or other non-control field)
|
|
117
|
+
*/
|
|
118
|
+
function isControlConfig(fieldConfig) {
|
|
119
|
+
return 'controlName' in fieldConfig;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* builds out form group based on config
|
|
124
|
+
* @param config a configuration for a form group
|
|
125
|
+
* @param value an object of initial values to pass in
|
|
126
|
+
* @param group the form group to modify and build out
|
|
127
|
+
*/
|
|
128
|
+
function buildFormGroupFromConfig(config, value = null, group = new FormGroup({})) {
|
|
129
|
+
config.fields.forEach((controlConfig) => {
|
|
130
|
+
if (isControlConfig(controlConfig)) {
|
|
131
|
+
// then add a control to the group using the controlName from configuration
|
|
132
|
+
const { controlName } = controlConfig;
|
|
133
|
+
if (group.get(controlName)) {
|
|
134
|
+
group.removeControl(controlName);
|
|
135
|
+
}
|
|
136
|
+
// if there's a value object and it has a non-null/undefined value for this field use it.
|
|
137
|
+
// Otherwise, default to null
|
|
138
|
+
const controlValue = (value && value[controlName]) ?? null;
|
|
139
|
+
const control = createControlForType(controlConfig, controlValue);
|
|
140
|
+
group.addControl(controlName, control);
|
|
141
|
+
}
|
|
142
|
+
});
|
|
143
|
+
group.setValidators(config.validators);
|
|
144
|
+
return group;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
function callOptionsIfFunction(options, parentGroup, searchString) {
|
|
148
|
+
return options instanceof Function ? options(parentGroup, searchString) : options;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* A basic filter function that filters the search string against the label of the options object
|
|
153
|
+
* @param options the array of options to filter
|
|
154
|
+
* @param searchString the string from the input
|
|
155
|
+
*/
|
|
156
|
+
function filterOptionsByLabel(options, searchString) {
|
|
157
|
+
return options.filter(option => {
|
|
158
|
+
return option.label && option.label.toLowerCase().includes(searchString.toLowerCase());
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* The user can give us select options as an array, a function that resolves to a promise,
|
|
164
|
+
* or an observable. This functions consumes any of those and returns an observable that
|
|
165
|
+
* resolves an array of options
|
|
166
|
+
*
|
|
167
|
+
* @param options the options passed in from the config
|
|
168
|
+
* @param emptyMessageOption the message to display when the array is empty
|
|
169
|
+
*/
|
|
170
|
+
function observablifyOptions(options, parentGroup, searchString) {
|
|
171
|
+
const calledOptions = callOptionsIfFunction(options, parentGroup, searchString);
|
|
172
|
+
// if options are a promise
|
|
173
|
+
const observableOptions = calledOptions instanceof Promise
|
|
174
|
+
// convert to observable
|
|
175
|
+
? from(calledOptions)
|
|
176
|
+
// if array that isn't empty
|
|
177
|
+
: Array.isArray(calledOptions) && calledOptions.length
|
|
178
|
+
// convert to observable
|
|
179
|
+
? of(calledOptions)
|
|
180
|
+
// if observable
|
|
181
|
+
: isObservable(calledOptions)
|
|
182
|
+
// return it
|
|
183
|
+
? calledOptions
|
|
184
|
+
// else an empty array
|
|
185
|
+
: of([]);
|
|
186
|
+
return observableOptions;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
function convertBytesToMb(bytes) {
|
|
190
|
+
return bytes / 1048576;
|
|
191
|
+
}
|
|
192
|
+
function maxFileSizeValidator(maxFileSize) {
|
|
193
|
+
return (control) => {
|
|
194
|
+
const controlValue = control.value;
|
|
195
|
+
if (!(controlValue instanceof File)) {
|
|
196
|
+
return null;
|
|
197
|
+
}
|
|
198
|
+
const actualFileSize = +convertBytesToMb(controlValue.size).toPrecision(3);
|
|
199
|
+
if (maxFileSize > actualFileSize)
|
|
200
|
+
return null;
|
|
201
|
+
return { maxFileSize: { maxFileSize, actualFileSize } };
|
|
202
|
+
};
|
|
203
|
+
}
|
|
204
|
+
function allowedFileExtValidator(allowedFileExtensions, onlyAllowFiles = true) {
|
|
205
|
+
return (control) => {
|
|
206
|
+
const controlValue = control.value;
|
|
207
|
+
const isFile = controlValue instanceof File;
|
|
208
|
+
if (!onlyAllowFiles && !isFile)
|
|
209
|
+
return null;
|
|
210
|
+
const actualExtension = isFile ? controlValue.name.split('.').pop().toLocaleLowerCase() : 'Not a file';
|
|
211
|
+
const isAllowedExtension = allowedFileExtensions.includes(actualExtension);
|
|
212
|
+
if (isAllowedExtension) {
|
|
213
|
+
return null;
|
|
214
|
+
}
|
|
215
|
+
return { allowedExtension: { allowedFileExtensions, actualExtension } };
|
|
216
|
+
};
|
|
217
|
+
}
|
|
218
|
+
function allowedFileType(matchExp) {
|
|
219
|
+
return (formControl) => {
|
|
220
|
+
const value = formControl.value;
|
|
221
|
+
if (!(value instanceof File))
|
|
222
|
+
return null;
|
|
223
|
+
const type = value.type;
|
|
224
|
+
const matches = type.match(matchExp);
|
|
225
|
+
if (!type || !matches?.length) {
|
|
226
|
+
return {
|
|
227
|
+
allowedFileType: {
|
|
228
|
+
actualType: type,
|
|
229
|
+
allowedTypeExp: matchExp
|
|
230
|
+
}
|
|
231
|
+
};
|
|
232
|
+
}
|
|
233
|
+
else {
|
|
234
|
+
return null;
|
|
235
|
+
}
|
|
236
|
+
};
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
class CrisprFieldComponent {
|
|
240
|
+
constructor() {
|
|
241
|
+
this.inputConfig = input(null, { ...(ngDevMode ? { debugName: "inputConfig" } : {}), alias: 'config' });
|
|
242
|
+
this.config = computed(() => {
|
|
243
|
+
const c = this.inputConfig();
|
|
244
|
+
return c && this.defaultConfig ? { ...this.defaultConfig, ...c } : c;
|
|
245
|
+
}, ...(ngDevMode ? [{ debugName: "config" }] : []));
|
|
246
|
+
this.testIdPrefix = input(undefined, ...(ngDevMode ? [{ debugName: "testIdPrefix" }] : []));
|
|
247
|
+
this.testId = computed(() => {
|
|
248
|
+
const config = this.config();
|
|
249
|
+
const id = config?.testId || config?.controlName;
|
|
250
|
+
if (!id)
|
|
251
|
+
return undefined;
|
|
252
|
+
const prefix = this.testIdPrefix();
|
|
253
|
+
return prefix ? `${prefix}.${id}` : id;
|
|
254
|
+
}, ...(ngDevMode ? [{ debugName: "testId" }] : []));
|
|
255
|
+
}
|
|
256
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.6", ngImport: i0, type: CrisprFieldComponent, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
|
|
257
|
+
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "21.1.6", type: CrisprFieldComponent, isStandalone: true, inputs: { inputConfig: { classPropertyName: "inputConfig", publicName: "config", isSignal: true, isRequired: false, transformFunction: null }, testIdPrefix: { classPropertyName: "testIdPrefix", publicName: "testIdPrefix", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0 }); }
|
|
258
|
+
}
|
|
259
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.6", ngImport: i0, type: CrisprFieldComponent, decorators: [{
|
|
260
|
+
type: Directive,
|
|
261
|
+
args: [{
|
|
262
|
+
standalone: true
|
|
263
|
+
}]
|
|
264
|
+
}], propDecorators: { inputConfig: [{ type: i0.Input, args: [{ isSignal: true, alias: "config", required: false }] }], testIdPrefix: [{ type: i0.Input, args: [{ isSignal: true, alias: "testIdPrefix", required: false }] }] } });
|
|
265
|
+
|
|
266
|
+
class CrisprControlComponent extends CrisprFieldComponent {
|
|
267
|
+
constructor() {
|
|
268
|
+
super();
|
|
269
|
+
this.destroyRef = inject(DestroyRef);
|
|
270
|
+
this.injector = inject(Injector);
|
|
271
|
+
this.group = input(null, ...(ngDevMode ? [{ debugName: "group" }] : []));
|
|
272
|
+
this.control = computed(() => this.group()?.get(this.config().controlName), ...(ngDevMode ? [{ debugName: "control" }] : []));
|
|
273
|
+
this.valueChanges = output();
|
|
274
|
+
this.value = input(null, ...(ngDevMode ? [{ debugName: "value" }] : []));
|
|
275
|
+
effect(() => {
|
|
276
|
+
this.setControlValue(this.value());
|
|
277
|
+
});
|
|
278
|
+
}
|
|
279
|
+
ngOnInit() {
|
|
280
|
+
this.control().valueChanges.pipe(takeUntilDestroyed(this.destroyRef)).subscribe((value) => {
|
|
281
|
+
this.valueChanges.emit(value);
|
|
282
|
+
});
|
|
283
|
+
// Computed fields derive their value from sibling controls, which only exist once the whole form has
|
|
284
|
+
// rendered, so re-run validation (and valueChanges) after the first render to compute initial values.
|
|
285
|
+
afterNextRender(() => {
|
|
286
|
+
this.control().updateValueAndValidity();
|
|
287
|
+
}, { injector: this.injector });
|
|
288
|
+
}
|
|
289
|
+
setControlValue(value) {
|
|
290
|
+
if (value && this.control() && this.config().controlType !== ControlType.SUB_GROUP) {
|
|
291
|
+
this.control().setValue(value);
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.6", ngImport: i0, type: CrisprControlComponent, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
|
|
295
|
+
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "21.1.6", type: CrisprControlComponent, isStandalone: true, inputs: { group: { classPropertyName: "group", publicName: "group", isSignal: true, isRequired: false, transformFunction: null }, value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { valueChanges: "valueChanges" }, usesInheritance: true, ngImport: i0 }); }
|
|
296
|
+
}
|
|
297
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.6", ngImport: i0, type: CrisprControlComponent, decorators: [{
|
|
298
|
+
type: Directive,
|
|
299
|
+
args: [{
|
|
300
|
+
standalone: true
|
|
301
|
+
}]
|
|
302
|
+
}], ctorParameters: () => [], propDecorators: { group: [{ type: i0.Input, args: [{ isSignal: true, alias: "group", required: false }] }], valueChanges: [{ type: i0.Output, args: ["valueChanges"] }], value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }] } });
|
|
303
|
+
|
|
304
|
+
// eslint-disable-next-line @angular-eslint/directive-class-suffix
|
|
305
|
+
class AbstractAutocompleteComponent extends CrisprControlComponent {
|
|
306
|
+
constructor() {
|
|
307
|
+
super(...arguments);
|
|
308
|
+
// we need a separate control for the UI because of the way the material autocomplete chiplist works
|
|
309
|
+
this.autocompleteInputControl = new FormControl('');
|
|
310
|
+
// Narrows the parent's computed config type to include C-specific properties (e.g. chiplist-only fields)
|
|
311
|
+
// Must re-implement the defaultConfig merge from CrisprFieldComponent because this override replaces it.
|
|
312
|
+
this.config = computed(() => {
|
|
313
|
+
const c = this.inputConfig();
|
|
314
|
+
return (c && this.defaultConfig)
|
|
315
|
+
? { ...this.defaultConfig, ...c }
|
|
316
|
+
: c;
|
|
317
|
+
}, ...(ngDevMode ? [{ debugName: "config" }] : []));
|
|
318
|
+
}
|
|
319
|
+
ngOnInit() {
|
|
320
|
+
super.ngOnInit();
|
|
321
|
+
this.group().addControl(this.config().controlName, new FormControl());
|
|
322
|
+
// filter options by the search string using either the default filter function or one passed in through config()
|
|
323
|
+
this.options$ = this.autocompleteInputControl.valueChanges.pipe(
|
|
324
|
+
// this is needed to have the options panel to open on focus
|
|
325
|
+
startWith(''), debounceTime(this.config().typeDebounceTime), map((searchText) => typeof searchText === 'string' ? searchText?.trim() : ''), distinctUntilChanged(), switchMap((searchText) => observablifyOptions(this.config().options, this.group(), searchText)), shareReplay(1));
|
|
326
|
+
}
|
|
327
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.6", ngImport: i0, type: AbstractAutocompleteComponent, deps: null, target: i0.ɵɵFactoryTarget.Directive }); }
|
|
328
|
+
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.1.6", type: AbstractAutocompleteComponent, isStandalone: true, usesInheritance: true, ngImport: i0 }); }
|
|
329
|
+
}
|
|
330
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.6", ngImport: i0, type: AbstractAutocompleteComponent, decorators: [{
|
|
331
|
+
type: Directive
|
|
332
|
+
}] });
|
|
333
|
+
|
|
334
|
+
// eslint-disable-next-line @angular-eslint/directive-class-suffix
|
|
335
|
+
class CrisprDisplayFieldComponent extends CrisprControlComponent {
|
|
336
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.6", ngImport: i0, type: CrisprDisplayFieldComponent, deps: null, target: i0.ɵɵFactoryTarget.Directive }); }
|
|
337
|
+
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.1.6", type: CrisprDisplayFieldComponent, isStandalone: true, usesInheritance: true, ngImport: i0 }); }
|
|
338
|
+
}
|
|
339
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.6", ngImport: i0, type: CrisprDisplayFieldComponent, decorators: [{
|
|
340
|
+
type: Directive
|
|
341
|
+
}] });
|
|
342
|
+
|
|
343
|
+
const FIELD_COMPONENTS = {
|
|
344
|
+
groupList: () => Promise.resolve().then(function () { return index$1; }).then(({ FormGroupListComponent }) => FormGroupListComponent),
|
|
345
|
+
subGroup: () => Promise.resolve().then(function () { return index; }).then(({ SubGroupComponent }) => SubGroupComponent),
|
|
346
|
+
button: () => Promise.resolve().then(function () { return index$e; }).then(({ ButtonComponent }) => ButtonComponent),
|
|
347
|
+
input: () => Promise.resolve().then(function () { return index$9; }).then(({ InputFieldComponent }) => InputFieldComponent),
|
|
348
|
+
select: () => Promise.resolve().then(function () { return index$6; }).then(({ SelectFieldComponent }) => SelectFieldComponent),
|
|
349
|
+
radio: () => Promise.resolve().then(function () { return index$7; }).then(({ RadioFieldComponent }) => RadioFieldComponent),
|
|
350
|
+
autocomplete: () => Promise.resolve().then(function () { return index$f; }).then(({ AutocompleteFieldComponent }) => AutocompleteFieldComponent),
|
|
351
|
+
autocompleteChiplist: () => Promise.resolve().then(function () { return index$g; }).then(({ AutocompleteChiplistFieldComponent }) => AutocompleteChiplistFieldComponent),
|
|
352
|
+
map: () => Promise.resolve().then(function () { return index$8; }).then(({ MapFieldComponent }) => MapFieldComponent),
|
|
353
|
+
textarea: () => Promise.resolve().then(function () { return index$4; }).then(({ TextareaFieldComponent }) => TextareaFieldComponent),
|
|
354
|
+
checkbox: () => Promise.resolve().then(function () { return index$d; }).then(({ CheckboxFieldComponent }) => CheckboxFieldComponent),
|
|
355
|
+
slider: () => Promise.resolve().then(function () { return index$5; }).then(({ SliderFieldComponent }) => SliderFieldComponent),
|
|
356
|
+
datepicker: () => Promise.resolve().then(function () { return index$c; }).then(({ DatepickerFieldComponent }) => DatepickerFieldComponent),
|
|
357
|
+
fileUpload: () => Promise.resolve().then(function () { return index$h; }).then(({ FileUploadFieldComponent }) => FileUploadFieldComponent),
|
|
358
|
+
imageUpload: () => Promise.resolve().then(function () { return index$a; }).then(({ ImageUploadFieldComponent }) => ImageUploadFieldComponent),
|
|
359
|
+
heading: () => Promise.resolve().then(function () { return index$2; }).then(({ HeadingComponent }) => HeadingComponent),
|
|
360
|
+
divider: () => Promise.resolve().then(function () { return index$b; }).then(({ DividerComponent }) => DividerComponent),
|
|
361
|
+
unitConversion: () => Promise.resolve().then(function () { return index$3; }).then(({ UnitConversionFieldComponent }) => UnitConversionFieldComponent),
|
|
362
|
+
};
|
|
363
|
+
function isControlComponent(component) {
|
|
364
|
+
return component && isControlConfig(component.config()) && 'value' in component;
|
|
365
|
+
}
|
|
366
|
+
function isControlOrButtonComponent(component) {
|
|
367
|
+
return (isControlComponent(component) ||
|
|
368
|
+
component.config().controlType === ControlType.BUTTON);
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
class CrisprFieldDirective {
|
|
372
|
+
constructor() {
|
|
373
|
+
this.config = input(...(ngDevMode ? [undefined, { debugName: "config" }] : []));
|
|
374
|
+
this.group = input(...(ngDevMode ? [undefined, { debugName: "group" }] : []));
|
|
375
|
+
this.testIdPrefix = input(undefined, ...(ngDevMode ? [{ debugName: "testIdPrefix" }] : []));
|
|
376
|
+
this.value = input(...(ngDevMode ? [undefined, { debugName: "value" }] : []));
|
|
377
|
+
this.container = inject(ViewContainerRef);
|
|
378
|
+
this.renderer = inject(Renderer2);
|
|
379
|
+
effect(() => {
|
|
380
|
+
this.updateComponentValue(this.value());
|
|
381
|
+
});
|
|
382
|
+
}
|
|
383
|
+
async ngOnInit() {
|
|
384
|
+
const component = this.config().component ||
|
|
385
|
+
(await FIELD_COMPONENTS[this.config().controlType]());
|
|
386
|
+
this.componentRef =
|
|
387
|
+
this.container.createComponent(component);
|
|
388
|
+
this.component = this.componentRef.instance;
|
|
389
|
+
this.componentRef.setInput('config', this.config());
|
|
390
|
+
if (isControlOrButtonComponent(this.component)) {
|
|
391
|
+
this.componentRef.setInput('group', this.group());
|
|
392
|
+
}
|
|
393
|
+
this.componentRef.setInput('testIdPrefix', this.testIdPrefix());
|
|
394
|
+
this.updateComponentValue(this.value());
|
|
395
|
+
if (!this.config().classes)
|
|
396
|
+
return;
|
|
397
|
+
this.config().classes.forEach((klass) => {
|
|
398
|
+
if (klass.includes(' ')) {
|
|
399
|
+
throw Error(`Improperly formatted class. '${klass}' cannot contain spaces.`);
|
|
400
|
+
}
|
|
401
|
+
else {
|
|
402
|
+
this.renderer.addClass(this.componentRef.location.nativeElement, klass);
|
|
403
|
+
}
|
|
404
|
+
});
|
|
405
|
+
}
|
|
406
|
+
updateComponentValue(value) {
|
|
407
|
+
if (this.componentRef && isControlComponent(this.component)) {
|
|
408
|
+
this.componentRef.setInput('value', value);
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.6", ngImport: i0, type: CrisprFieldDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
|
|
412
|
+
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "21.1.6", type: CrisprFieldDirective, isStandalone: true, selector: "[crisprField]", inputs: { config: { classPropertyName: "config", publicName: "config", isSignal: true, isRequired: false, transformFunction: null }, group: { classPropertyName: "group", publicName: "group", isSignal: true, isRequired: false, transformFunction: null }, testIdPrefix: { classPropertyName: "testIdPrefix", publicName: "testIdPrefix", isSignal: true, isRequired: false, transformFunction: null }, value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0 }); }
|
|
413
|
+
}
|
|
414
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.6", ngImport: i0, type: CrisprFieldDirective, decorators: [{
|
|
415
|
+
type: Directive,
|
|
416
|
+
args: [{
|
|
417
|
+
selector: '[crisprField]',
|
|
418
|
+
standalone: true,
|
|
419
|
+
}]
|
|
420
|
+
}], ctorParameters: () => [], propDecorators: { config: [{ type: i0.Input, args: [{ isSignal: true, alias: "config", required: false }] }], group: [{ type: i0.Input, args: [{ isSignal: true, alias: "group", required: false }] }], testIdPrefix: [{ type: i0.Input, args: [{ isSignal: true, alias: "testIdPrefix", required: false }] }], value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }] } });
|
|
421
|
+
|
|
422
|
+
class InitialControlValuePipe {
|
|
423
|
+
// TODO: does changing any to unknown break anything?
|
|
424
|
+
transform(initialFormValue, fieldConfig) {
|
|
425
|
+
return initialFormValue && isControlConfig(fieldConfig)
|
|
426
|
+
? initialFormValue[fieldConfig.controlName] || null
|
|
427
|
+
: null;
|
|
428
|
+
}
|
|
429
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.6", ngImport: i0, type: InitialControlValuePipe, deps: [], target: i0.ɵɵFactoryTarget.Pipe }); }
|
|
430
|
+
static { this.ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "21.1.6", ngImport: i0, type: InitialControlValuePipe, isStandalone: true, name: "initialControlValue" }); }
|
|
431
|
+
}
|
|
432
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.6", ngImport: i0, type: InitialControlValuePipe, decorators: [{
|
|
433
|
+
type: Pipe,
|
|
434
|
+
args: [{ name: 'initialControlValue' }]
|
|
435
|
+
}] });
|
|
436
|
+
|
|
437
|
+
class CrisprFormComponent {
|
|
438
|
+
constructor() {
|
|
439
|
+
this.config = input.required(...(ngDevMode ? [{ debugName: "config" }] : []));
|
|
440
|
+
this.form = input(new FormGroup({}), ...(ngDevMode ? [{ debugName: "form" }] : []));
|
|
441
|
+
this.value = input(null, ...(ngDevMode ? [{ debugName: "value" }] : []));
|
|
442
|
+
this.submitted = output();
|
|
443
|
+
/** Patch form values whenever the [value] input changes without firing valueChanges. */
|
|
444
|
+
this.patchValueEffect = effect(() => {
|
|
445
|
+
const value = this.value();
|
|
446
|
+
untracked(() => {
|
|
447
|
+
if (value) {
|
|
448
|
+
this.form().patchValue(value, { emitEvent: false });
|
|
449
|
+
}
|
|
450
|
+
});
|
|
451
|
+
}, ...(ngDevMode ? [{ debugName: "patchValueEffect" }] : []));
|
|
452
|
+
// proxy value and status change events through to consuming component
|
|
453
|
+
this.valueChanges = outputFromObservable(toObservable(this.form).pipe(switchMap((f) => f.valueChanges)));
|
|
454
|
+
this.statusChanges = outputFromObservable(toObservable(this.form).pipe(switchMap((f) => f.statusChanges)));
|
|
455
|
+
this.submitTrigger = viewChild('submitTrigger', ...(ngDevMode ? [{ debugName: "submitTrigger" }] : []));
|
|
456
|
+
}
|
|
457
|
+
ngOnInit() {
|
|
458
|
+
// build out the form, note that we pass in the form as the third argument and the function modifies it
|
|
459
|
+
buildFormGroupFromConfig(this.config(), this.value(), this.form());
|
|
460
|
+
}
|
|
461
|
+
handleSubmit() {
|
|
462
|
+
this.form().markAllAsTouched();
|
|
463
|
+
this.submitted.emit(this.form());
|
|
464
|
+
}
|
|
465
|
+
triggerSubmit() {
|
|
466
|
+
this.submitTrigger().nativeElement.click();
|
|
467
|
+
}
|
|
468
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.6", ngImport: i0, type: CrisprFormComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
469
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.6", type: CrisprFormComponent, isStandalone: true, selector: "crispr-form", inputs: { config: { classPropertyName: "config", publicName: "config", isSignal: true, isRequired: true, transformFunction: null }, form: { classPropertyName: "form", publicName: "form", isSignal: true, isRequired: false, transformFunction: null }, value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { submitted: "submitted", valueChanges: "valueChanges", statusChanges: "statusChanges" }, host: { properties: { "attr.data-density": "config().density ?? null" } }, viewQueries: [{ propertyName: "submitTrigger", first: true, predicate: ["submitTrigger"], descendants: true, isSignal: true }], ngImport: i0, template: "<form\n class=\"crispr-form\"\n [ngClass]=\"config().classes\"\n [formGroup]=\"form()\"\n (ngSubmit)=\"handleSubmit()\"\n [autocomplete]=\"config().autocomplete || 'off'\"\n [errorDictionary]=\"config().errorDictionary\"\n #ngFormElement=\"ngForm\">\n\n @for (field of config().fields; track field) {\n <ng-container crisprField\n [value]=\"value() | initialControlValue : field\"\n [config]=\"field\"\n [group]=\"form()\" />\n }\n <!-- Hidden button, programmatically clicked when crispr form triggerSubmit function called.\n Uses a hidden button with type=\"submit\" because it will trigger both native form submit event\n (which the error handler has to listen for) and ngSubmit event -->\n <button #submitTrigger class=\"submit-trigger\" data-testid=\"submit-trigger\" type=\"submit\">\n </button>\n</form>\n", styles: [".submit-trigger{display:none}:host[data-density=\"-1\"] ::ng-deep{--mat-form-field-container-height: 52px;--mat-form-field-filled-label-display: block;--mat-form-field-container-vertical-padding: 14px;--mat-form-field-filled-with-label-container-padding-top: 22px;--mat-form-field-filled-with-label-container-padding-bottom: 6px;--mat-select-arrow-transform: translateY(-8px)}:host[data-density=\"-1\"] ::ng-deep .mat-calendar-controls{--mat-icon-button-touch-target-display: none}:host[data-density=\"-1\"] ::ng-deep .mat-calendar-controls .mat-mdc-icon-button.mat-mdc-button-base{--mdc-icon-button-state-layer-size: 40px;--mat-icon-button-state-layer-size: 40px;width:var(--mat-icon-button-state-layer-size);height:var(--mat-icon-button-state-layer-size);padding:8px}:host[data-density=\"-1\"] ::ng-deep{--mat-checkbox-touch-target-display: block;--mat-checkbox-state-layer-size: 36px;--mat-radio-state-layer-size: 36px;--mat-radio-touch-target-display: block;--mat-button-filled-container-height: 32px;--mat-button-filled-touch-target-display: block;--mat-button-outlined-container-height: 32px;--mat-button-outlined-touch-target-display: block;--mat-button-protected-container-height: 32px;--mat-button-protected-touch-target-display: block;--mat-button-text-container-height: 32px;--mat-button-text-touch-target-display: block;--mat-button-tonal-container-height: 32px;--mat-button-tonal-touch-target-display: block;--mat-icon-button-touch-target-display: block}:host[data-density=\"-1\"] ::ng-deep .mat-mdc-icon-button.mat-mdc-button-base{--mdc-icon-button-state-layer-size: 44px;--mat-icon-button-state-layer-size: 44px;width:var(--mat-icon-button-state-layer-size);height:var(--mat-icon-button-state-layer-size);padding:10px}:host[data-density=\"-2\"] ::ng-deep{--mat-form-field-container-height: 48px;--mat-form-field-filled-label-display: none;--mat-form-field-container-vertical-padding: 12px;--mat-form-field-filled-with-label-container-padding-top: 12px;--mat-form-field-filled-with-label-container-padding-bottom: 12px;--mat-select-arrow-transform: none}:host[data-density=\"-2\"] ::ng-deep .mat-calendar-controls{--mat-icon-button-touch-target-display: none}:host[data-density=\"-2\"] ::ng-deep .mat-calendar-controls .mat-mdc-icon-button.mat-mdc-button-base{--mdc-icon-button-state-layer-size: 40px;--mat-icon-button-state-layer-size: 40px;width:var(--mat-icon-button-state-layer-size);height:var(--mat-icon-button-state-layer-size);padding:8px}:host[data-density=\"-2\"] ::ng-deep{--mat-checkbox-touch-target-display: none;--mat-checkbox-state-layer-size: 32px;--mat-radio-state-layer-size: 32px;--mat-radio-touch-target-display: none;--mat-button-filled-container-height: 28px;--mat-button-filled-touch-target-display: none;--mat-button-outlined-container-height: 28px;--mat-button-outlined-touch-target-display: none;--mat-button-protected-container-height: 28px;--mat-button-protected-touch-target-display: none;--mat-button-text-container-height: 28px;--mat-button-text-touch-target-display: none;--mat-button-tonal-container-height: 28px;--mat-button-tonal-touch-target-display: none;--mat-icon-button-touch-target-display: none}:host[data-density=\"-2\"] ::ng-deep .mat-mdc-icon-button.mat-mdc-button-base{--mdc-icon-button-state-layer-size: 40px;--mat-icon-button-state-layer-size: 40px;width:var(--mat-icon-button-state-layer-size);height:var(--mat-icon-button-state-layer-size);padding:8px}:host[data-density=\"-3\"] ::ng-deep{--mat-form-field-container-height: 44px;--mat-form-field-filled-label-display: none;--mat-form-field-container-vertical-padding: 10px;--mat-form-field-filled-with-label-container-padding-top: 10px;--mat-form-field-filled-with-label-container-padding-bottom: 10px;--mat-select-arrow-transform: none}:host[data-density=\"-3\"] ::ng-deep .mat-calendar-controls{--mat-icon-button-touch-target-display: none}:host[data-density=\"-3\"] ::ng-deep .mat-calendar-controls .mat-mdc-icon-button.mat-mdc-button-base{--mdc-icon-button-state-layer-size: 40px;--mat-icon-button-state-layer-size: 40px;width:var(--mat-icon-button-state-layer-size);height:var(--mat-icon-button-state-layer-size);padding:8px}:host[data-density=\"-3\"] ::ng-deep{--mat-checkbox-touch-target-display: none;--mat-checkbox-state-layer-size: 28px;--mat-radio-state-layer-size: 28px;--mat-radio-touch-target-display: none;--mat-button-filled-container-height: 24px;--mat-button-filled-touch-target-display: none;--mat-button-outlined-container-height: 24px;--mat-button-outlined-touch-target-display: none;--mat-button-protected-container-height: 24px;--mat-button-protected-touch-target-display: none;--mat-button-text-container-height: 24px;--mat-button-text-touch-target-display: none;--mat-button-tonal-container-height: 24px;--mat-button-tonal-touch-target-display: none;--mat-icon-button-touch-target-display: none}:host[data-density=\"-3\"] ::ng-deep .mat-mdc-icon-button.mat-mdc-button-base{--mdc-icon-button-state-layer-size: 36px;--mat-icon-button-state-layer-size: 36px;width:var(--mat-icon-button-state-layer-size);height:var(--mat-icon-button-state-layer-size);padding:6px}:host[data-density=\"-4\"] ::ng-deep{--mat-form-field-container-height: 40px;--mat-form-field-filled-label-display: none;--mat-form-field-container-vertical-padding: 8px;--mat-form-field-filled-with-label-container-padding-top: 8px;--mat-form-field-filled-with-label-container-padding-bottom: 8px;--mat-select-arrow-transform: none}:host[data-density=\"-4\"] ::ng-deep .mat-calendar-controls{--mat-icon-button-touch-target-display: none}:host[data-density=\"-4\"] ::ng-deep .mat-calendar-controls .mat-mdc-icon-button.mat-mdc-button-base{--mdc-icon-button-state-layer-size: 40px;--mat-icon-button-state-layer-size: 40px;width:var(--mat-icon-button-state-layer-size);height:var(--mat-icon-button-state-layer-size);padding:8px}:host[data-density=\"-4\"] ::ng-deep{--mat-checkbox-touch-target-display: none;--mat-checkbox-state-layer-size: 28px;--mat-radio-state-layer-size: 28px;--mat-radio-touch-target-display: none;--mat-button-filled-container-height: 24px;--mat-button-filled-touch-target-display: none;--mat-button-outlined-container-height: 24px;--mat-button-outlined-touch-target-display: none;--mat-button-protected-container-height: 24px;--mat-button-protected-touch-target-display: none;--mat-button-text-container-height: 24px;--mat-button-text-touch-target-display: none;--mat-button-tonal-container-height: 24px;--mat-button-tonal-touch-target-display: none;--mat-icon-button-touch-target-display: none}:host[data-density=\"-4\"] ::ng-deep .mat-mdc-icon-button.mat-mdc-button-base{--mdc-icon-button-state-layer-size: 32px;--mat-icon-button-state-layer-size: 32px;width:var(--mat-icon-button-state-layer-size);height:var(--mat-icon-button-state-layer-size);padding:4px}:host[data-density=\"-5\"] ::ng-deep{--mat-form-field-container-height: 36px;--mat-form-field-filled-label-display: none;--mat-form-field-container-vertical-padding: 6px;--mat-form-field-filled-with-label-container-padding-top: 6px;--mat-form-field-filled-with-label-container-padding-bottom: 6px;--mat-select-arrow-transform: none}:host[data-density=\"-5\"] ::ng-deep .mat-calendar-controls{--mat-icon-button-touch-target-display: none}:host[data-density=\"-5\"] ::ng-deep .mat-calendar-controls .mat-mdc-icon-button.mat-mdc-button-base{--mdc-icon-button-state-layer-size: 40px;--mat-icon-button-state-layer-size: 40px;width:var(--mat-icon-button-state-layer-size);height:var(--mat-icon-button-state-layer-size);padding:8px}:host[data-density=\"-5\"] ::ng-deep{--mat-checkbox-touch-target-display: none;--mat-checkbox-state-layer-size: 28px;--mat-radio-state-layer-size: 28px;--mat-radio-touch-target-display: none;--mat-button-filled-container-height: 24px;--mat-button-filled-touch-target-display: none;--mat-button-outlined-container-height: 24px;--mat-button-outlined-touch-target-display: none;--mat-button-protected-container-height: 24px;--mat-button-protected-touch-target-display: none;--mat-button-text-container-height: 24px;--mat-button-text-touch-target-display: none;--mat-button-tonal-container-height: 24px;--mat-button-tonal-touch-target-display: none;--mat-icon-button-touch-target-display: none}:host[data-density=\"-5\"] ::ng-deep .mat-mdc-icon-button.mat-mdc-button-base{--mdc-icon-button-state-layer-size: 28px;--mat-icon-button-state-layer-size: 28px;width:var(--mat-icon-button-state-layer-size);height:var(--mat-icon-button-state-layer-size);padding:2px}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: CrisprFieldDirective, selector: "[crisprField]", inputs: ["config", "group", "testIdPrefix", "value"] }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1$1.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i1$1.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],[formArray],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i1$1.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "directive", type: ControlErrorContainerDirective, selector: "[controlErrorContainer], [formGroup]" }, { kind: "directive", type: ControlErrorsFormDirective, selector: "form", inputs: ["errorDictionary"] }, { kind: "pipe", type: InitialControlValuePipe, name: "initialControlValue" }], changeDetection: i0.ChangeDetectionStrategy.OnPush, preserveWhitespaces: true }); }
|
|
470
|
+
}
|
|
471
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.6", ngImport: i0, type: CrisprFormComponent, decorators: [{
|
|
472
|
+
type: Component,
|
|
473
|
+
args: [{ selector: 'crispr-form', changeDetection: ChangeDetectionStrategy.OnPush, standalone: true, host: {
|
|
474
|
+
'[attr.data-density]': 'config().density ?? null',
|
|
475
|
+
}, imports: [
|
|
476
|
+
CommonModule,
|
|
477
|
+
CrisprFieldDirective,
|
|
478
|
+
ReactiveFormsModule,
|
|
479
|
+
ControlErrorContainerDirective,
|
|
480
|
+
ControlErrorsFormDirective,
|
|
481
|
+
InitialControlValuePipe
|
|
482
|
+
], template: "<form\n class=\"crispr-form\"\n [ngClass]=\"config().classes\"\n [formGroup]=\"form()\"\n (ngSubmit)=\"handleSubmit()\"\n [autocomplete]=\"config().autocomplete || 'off'\"\n [errorDictionary]=\"config().errorDictionary\"\n #ngFormElement=\"ngForm\">\n\n @for (field of config().fields; track field) {\n <ng-container crisprField\n [value]=\"value() | initialControlValue : field\"\n [config]=\"field\"\n [group]=\"form()\" />\n }\n <!-- Hidden button, programmatically clicked when crispr form triggerSubmit function called.\n Uses a hidden button with type=\"submit\" because it will trigger both native form submit event\n (which the error handler has to listen for) and ngSubmit event -->\n <button #submitTrigger class=\"submit-trigger\" data-testid=\"submit-trigger\" type=\"submit\">\n </button>\n</form>\n", styles: [".submit-trigger{display:none}:host[data-density=\"-1\"] ::ng-deep{--mat-form-field-container-height: 52px;--mat-form-field-filled-label-display: block;--mat-form-field-container-vertical-padding: 14px;--mat-form-field-filled-with-label-container-padding-top: 22px;--mat-form-field-filled-with-label-container-padding-bottom: 6px;--mat-select-arrow-transform: translateY(-8px)}:host[data-density=\"-1\"] ::ng-deep .mat-calendar-controls{--mat-icon-button-touch-target-display: none}:host[data-density=\"-1\"] ::ng-deep .mat-calendar-controls .mat-mdc-icon-button.mat-mdc-button-base{--mdc-icon-button-state-layer-size: 40px;--mat-icon-button-state-layer-size: 40px;width:var(--mat-icon-button-state-layer-size);height:var(--mat-icon-button-state-layer-size);padding:8px}:host[data-density=\"-1\"] ::ng-deep{--mat-checkbox-touch-target-display: block;--mat-checkbox-state-layer-size: 36px;--mat-radio-state-layer-size: 36px;--mat-radio-touch-target-display: block;--mat-button-filled-container-height: 32px;--mat-button-filled-touch-target-display: block;--mat-button-outlined-container-height: 32px;--mat-button-outlined-touch-target-display: block;--mat-button-protected-container-height: 32px;--mat-button-protected-touch-target-display: block;--mat-button-text-container-height: 32px;--mat-button-text-touch-target-display: block;--mat-button-tonal-container-height: 32px;--mat-button-tonal-touch-target-display: block;--mat-icon-button-touch-target-display: block}:host[data-density=\"-1\"] ::ng-deep .mat-mdc-icon-button.mat-mdc-button-base{--mdc-icon-button-state-layer-size: 44px;--mat-icon-button-state-layer-size: 44px;width:var(--mat-icon-button-state-layer-size);height:var(--mat-icon-button-state-layer-size);padding:10px}:host[data-density=\"-2\"] ::ng-deep{--mat-form-field-container-height: 48px;--mat-form-field-filled-label-display: none;--mat-form-field-container-vertical-padding: 12px;--mat-form-field-filled-with-label-container-padding-top: 12px;--mat-form-field-filled-with-label-container-padding-bottom: 12px;--mat-select-arrow-transform: none}:host[data-density=\"-2\"] ::ng-deep .mat-calendar-controls{--mat-icon-button-touch-target-display: none}:host[data-density=\"-2\"] ::ng-deep .mat-calendar-controls .mat-mdc-icon-button.mat-mdc-button-base{--mdc-icon-button-state-layer-size: 40px;--mat-icon-button-state-layer-size: 40px;width:var(--mat-icon-button-state-layer-size);height:var(--mat-icon-button-state-layer-size);padding:8px}:host[data-density=\"-2\"] ::ng-deep{--mat-checkbox-touch-target-display: none;--mat-checkbox-state-layer-size: 32px;--mat-radio-state-layer-size: 32px;--mat-radio-touch-target-display: none;--mat-button-filled-container-height: 28px;--mat-button-filled-touch-target-display: none;--mat-button-outlined-container-height: 28px;--mat-button-outlined-touch-target-display: none;--mat-button-protected-container-height: 28px;--mat-button-protected-touch-target-display: none;--mat-button-text-container-height: 28px;--mat-button-text-touch-target-display: none;--mat-button-tonal-container-height: 28px;--mat-button-tonal-touch-target-display: none;--mat-icon-button-touch-target-display: none}:host[data-density=\"-2\"] ::ng-deep .mat-mdc-icon-button.mat-mdc-button-base{--mdc-icon-button-state-layer-size: 40px;--mat-icon-button-state-layer-size: 40px;width:var(--mat-icon-button-state-layer-size);height:var(--mat-icon-button-state-layer-size);padding:8px}:host[data-density=\"-3\"] ::ng-deep{--mat-form-field-container-height: 44px;--mat-form-field-filled-label-display: none;--mat-form-field-container-vertical-padding: 10px;--mat-form-field-filled-with-label-container-padding-top: 10px;--mat-form-field-filled-with-label-container-padding-bottom: 10px;--mat-select-arrow-transform: none}:host[data-density=\"-3\"] ::ng-deep .mat-calendar-controls{--mat-icon-button-touch-target-display: none}:host[data-density=\"-3\"] ::ng-deep .mat-calendar-controls .mat-mdc-icon-button.mat-mdc-button-base{--mdc-icon-button-state-layer-size: 40px;--mat-icon-button-state-layer-size: 40px;width:var(--mat-icon-button-state-layer-size);height:var(--mat-icon-button-state-layer-size);padding:8px}:host[data-density=\"-3\"] ::ng-deep{--mat-checkbox-touch-target-display: none;--mat-checkbox-state-layer-size: 28px;--mat-radio-state-layer-size: 28px;--mat-radio-touch-target-display: none;--mat-button-filled-container-height: 24px;--mat-button-filled-touch-target-display: none;--mat-button-outlined-container-height: 24px;--mat-button-outlined-touch-target-display: none;--mat-button-protected-container-height: 24px;--mat-button-protected-touch-target-display: none;--mat-button-text-container-height: 24px;--mat-button-text-touch-target-display: none;--mat-button-tonal-container-height: 24px;--mat-button-tonal-touch-target-display: none;--mat-icon-button-touch-target-display: none}:host[data-density=\"-3\"] ::ng-deep .mat-mdc-icon-button.mat-mdc-button-base{--mdc-icon-button-state-layer-size: 36px;--mat-icon-button-state-layer-size: 36px;width:var(--mat-icon-button-state-layer-size);height:var(--mat-icon-button-state-layer-size);padding:6px}:host[data-density=\"-4\"] ::ng-deep{--mat-form-field-container-height: 40px;--mat-form-field-filled-label-display: none;--mat-form-field-container-vertical-padding: 8px;--mat-form-field-filled-with-label-container-padding-top: 8px;--mat-form-field-filled-with-label-container-padding-bottom: 8px;--mat-select-arrow-transform: none}:host[data-density=\"-4\"] ::ng-deep .mat-calendar-controls{--mat-icon-button-touch-target-display: none}:host[data-density=\"-4\"] ::ng-deep .mat-calendar-controls .mat-mdc-icon-button.mat-mdc-button-base{--mdc-icon-button-state-layer-size: 40px;--mat-icon-button-state-layer-size: 40px;width:var(--mat-icon-button-state-layer-size);height:var(--mat-icon-button-state-layer-size);padding:8px}:host[data-density=\"-4\"] ::ng-deep{--mat-checkbox-touch-target-display: none;--mat-checkbox-state-layer-size: 28px;--mat-radio-state-layer-size: 28px;--mat-radio-touch-target-display: none;--mat-button-filled-container-height: 24px;--mat-button-filled-touch-target-display: none;--mat-button-outlined-container-height: 24px;--mat-button-outlined-touch-target-display: none;--mat-button-protected-container-height: 24px;--mat-button-protected-touch-target-display: none;--mat-button-text-container-height: 24px;--mat-button-text-touch-target-display: none;--mat-button-tonal-container-height: 24px;--mat-button-tonal-touch-target-display: none;--mat-icon-button-touch-target-display: none}:host[data-density=\"-4\"] ::ng-deep .mat-mdc-icon-button.mat-mdc-button-base{--mdc-icon-button-state-layer-size: 32px;--mat-icon-button-state-layer-size: 32px;width:var(--mat-icon-button-state-layer-size);height:var(--mat-icon-button-state-layer-size);padding:4px}:host[data-density=\"-5\"] ::ng-deep{--mat-form-field-container-height: 36px;--mat-form-field-filled-label-display: none;--mat-form-field-container-vertical-padding: 6px;--mat-form-field-filled-with-label-container-padding-top: 6px;--mat-form-field-filled-with-label-container-padding-bottom: 6px;--mat-select-arrow-transform: none}:host[data-density=\"-5\"] ::ng-deep .mat-calendar-controls{--mat-icon-button-touch-target-display: none}:host[data-density=\"-5\"] ::ng-deep .mat-calendar-controls .mat-mdc-icon-button.mat-mdc-button-base{--mdc-icon-button-state-layer-size: 40px;--mat-icon-button-state-layer-size: 40px;width:var(--mat-icon-button-state-layer-size);height:var(--mat-icon-button-state-layer-size);padding:8px}:host[data-density=\"-5\"] ::ng-deep{--mat-checkbox-touch-target-display: none;--mat-checkbox-state-layer-size: 28px;--mat-radio-state-layer-size: 28px;--mat-radio-touch-target-display: none;--mat-button-filled-container-height: 24px;--mat-button-filled-touch-target-display: none;--mat-button-outlined-container-height: 24px;--mat-button-outlined-touch-target-display: none;--mat-button-protected-container-height: 24px;--mat-button-protected-touch-target-display: none;--mat-button-text-container-height: 24px;--mat-button-text-touch-target-display: none;--mat-button-tonal-container-height: 24px;--mat-button-tonal-touch-target-display: none;--mat-icon-button-touch-target-display: none}:host[data-density=\"-5\"] ::ng-deep .mat-mdc-icon-button.mat-mdc-button-base{--mdc-icon-button-state-layer-size: 28px;--mat-icon-button-state-layer-size: 28px;width:var(--mat-icon-button-state-layer-size);height:var(--mat-icon-button-state-layer-size);padding:2px}\n"] }]
|
|
483
|
+
}], propDecorators: { config: [{ type: i0.Input, args: [{ isSignal: true, alias: "config", required: true }] }], form: [{ type: i0.Input, args: [{ isSignal: true, alias: "form", required: false }] }], value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }], submitted: [{ type: i0.Output, args: ["submitted"] }], valueChanges: [{ type: i0.Output, args: ["valueChanges"] }], statusChanges: [{ type: i0.Output, args: ["statusChanges"] }], submitTrigger: [{ type: i0.ViewChild, args: ['submitTrigger', { isSignal: true }] }] } });
|
|
484
|
+
|
|
485
|
+
class FileDropzoneDirective {
|
|
486
|
+
constructor() {
|
|
487
|
+
this.fileDrop = output();
|
|
488
|
+
this.crisprFileDropzone = input(true, ...(ngDevMode ? [{ debugName: "crisprFileDropzone" }] : []));
|
|
489
|
+
}
|
|
490
|
+
onDrop($event) {
|
|
491
|
+
if (this.crisprFileDropzone()) {
|
|
492
|
+
$event.preventDefault();
|
|
493
|
+
this.fileDrop.emit($event.dataTransfer.files);
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
// Prevents DOM from trying to open file in browser
|
|
497
|
+
onDragOver($event) {
|
|
498
|
+
if (this.crisprFileDropzone()) {
|
|
499
|
+
$event.preventDefault();
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.6", ngImport: i0, type: FileDropzoneDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
|
|
503
|
+
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "21.1.6", type: FileDropzoneDirective, isStandalone: true, selector: "[crisprFileDropzone]", inputs: { crisprFileDropzone: { classPropertyName: "crisprFileDropzone", publicName: "crisprFileDropzone", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { fileDrop: "fileDrop" }, host: { listeners: { "drop": "onDrop($event)", "dragover": "onDragOver($event)" } }, ngImport: i0 }); }
|
|
504
|
+
}
|
|
505
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.6", ngImport: i0, type: FileDropzoneDirective, decorators: [{
|
|
506
|
+
type: Directive,
|
|
507
|
+
args: [{
|
|
508
|
+
selector: '[crisprFileDropzone]',
|
|
509
|
+
standalone: true,
|
|
510
|
+
}]
|
|
511
|
+
}], propDecorators: { fileDrop: [{ type: i0.Output, args: ["fileDrop"] }], crisprFileDropzone: [{ type: i0.Input, args: [{ isSignal: true, alias: "crisprFileDropzone", required: false }] }], onDrop: [{
|
|
512
|
+
type: HostListener,
|
|
513
|
+
args: ['drop', ['$event']]
|
|
514
|
+
}], onDragOver: [{
|
|
515
|
+
type: HostListener,
|
|
516
|
+
args: ['dragover', ['$event']]
|
|
517
|
+
}] } });
|
|
518
|
+
|
|
519
|
+
class InfoComponent {
|
|
520
|
+
constructor() {
|
|
521
|
+
this.info = input.required(...(ngDevMode ? [{ debugName: "info" }] : []));
|
|
522
|
+
}
|
|
523
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.6", ngImport: i0, type: InfoComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
524
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.6", type: InfoComponent, isStandalone: true, selector: "crispr-info", inputs: { info: { classPropertyName: "info", publicName: "info", isSignal: true, isRequired: true, transformFunction: null } }, ngImport: i0, template: "@if (info()) {\n <mat-icon\n [matTooltip]=\"info().content\"\n [matTooltipPosition]=\"info().tooltipPosition || 'below'\">\n {{info().iconName || 'info'}}\n </mat-icon>\n}\n", styles: [":host{margin-left:4px}\n"], dependencies: [{ 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: i2$1.MatTooltip, selector: "[matTooltip]", inputs: ["matTooltipPosition", "matTooltipPositionAtOrigin", "matTooltipDisabled", "matTooltipShowDelay", "matTooltipHideDelay", "matTooltipTouchGestures", "matTooltip", "matTooltipClass"], exportAs: ["matTooltip"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush, preserveWhitespaces: true }); }
|
|
525
|
+
}
|
|
526
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.6", ngImport: i0, type: InfoComponent, decorators: [{
|
|
527
|
+
type: Component,
|
|
528
|
+
args: [{ selector: 'crispr-info', changeDetection: ChangeDetectionStrategy.OnPush, standalone: true, imports: [
|
|
529
|
+
MatIconModule,
|
|
530
|
+
MatTooltipModule
|
|
531
|
+
], template: "@if (info()) {\n <mat-icon\n [matTooltip]=\"info().content\"\n [matTooltipPosition]=\"info().tooltipPosition || 'below'\">\n {{info().iconName || 'info'}}\n </mat-icon>\n}\n", styles: [":host{margin-left:4px}\n"] }]
|
|
532
|
+
}], propDecorators: { info: [{ type: i0.Input, args: [{ isSignal: true, alias: "info", required: true }] }] } });
|
|
533
|
+
|
|
534
|
+
const defaultConfig$9 = {
|
|
535
|
+
typographyClass: 'mat-h3',
|
|
536
|
+
};
|
|
537
|
+
class HeadingComponent extends CrisprFieldComponent {
|
|
538
|
+
constructor() {
|
|
539
|
+
super(...arguments);
|
|
540
|
+
this.defaultConfig = defaultConfig$9;
|
|
541
|
+
}
|
|
542
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.6", ngImport: i0, type: HeadingComponent, deps: null, target: i0.ɵɵFactoryTarget.Component }); }
|
|
543
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.6", type: HeadingComponent, isStandalone: true, selector: "crispr-heading", usesInheritance: true, ngImport: i0, template: "@if (!!config()?.label) {\n <label\n [ngClass]=\"config().typographyClass\">\n {{ config().label }}\n @if (config().info) {\n <crispr-info matSuffix\n [info]=\"config().info\" />\n }\n </label>\n}\n", styles: [":host{display:block}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "ngmodule", type: MatFormFieldModule }, { kind: "directive", type: i2$2.MatSuffix, selector: "[matSuffix], [matIconSuffix], [matTextSuffix]", inputs: ["matTextSuffix"] }, { kind: "component", type: InfoComponent, selector: "crispr-info", inputs: ["info"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush, preserveWhitespaces: true }); }
|
|
544
|
+
}
|
|
545
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.6", ngImport: i0, type: HeadingComponent, decorators: [{
|
|
546
|
+
type: Component,
|
|
547
|
+
args: [{ selector: 'crispr-heading', changeDetection: ChangeDetectionStrategy.OnPush, imports: [CommonModule, MatFormFieldModule, InfoComponent], standalone: true, template: "@if (!!config()?.label) {\n <label\n [ngClass]=\"config().typographyClass\">\n {{ config().label }}\n @if (config().info) {\n <crispr-info matSuffix\n [info]=\"config().info\" />\n }\n </label>\n}\n", styles: [":host{display:block}\n"] }]
|
|
548
|
+
}] });
|
|
549
|
+
|
|
550
|
+
class FieldContainerComponent {
|
|
551
|
+
constructor() {
|
|
552
|
+
// the configuration object for the field
|
|
553
|
+
this.config = input.required(...(ngDevMode ? [{ debugName: "config" }] : []));
|
|
554
|
+
// the parent formGroup
|
|
555
|
+
this.group = input.required(...(ngDevMode ? [{ debugName: "group" }] : []));
|
|
556
|
+
// boolean whether field-container is inline
|
|
557
|
+
this.inlineField = input(false, ...(ngDevMode ? [{ debugName: "inlineField" }] : []));
|
|
558
|
+
this.isDisabled = signal(false, ...(ngDevMode ? [{ debugName: "isDisabled" }] : []));
|
|
559
|
+
this.isHidden = computed(() => this.isDisabled() && !!this.config().hideDisabled, ...(ngDevMode ? [{ debugName: "isHidden" }] : []));
|
|
560
|
+
this.subs = [];
|
|
561
|
+
}
|
|
562
|
+
ngOnInit() {
|
|
563
|
+
this.disabled$ = this.connectDisabledCallback(this.group(), this.config());
|
|
564
|
+
this.subs.push(this.disabled$.subscribe(disabled => this.isDisabled.set(disabled)));
|
|
565
|
+
// we tried moving this into afterViewChecked and afterContentChecked lifecycle hooks without any luck
|
|
566
|
+
if (this.config().computeValue) {
|
|
567
|
+
this.subs.push(this.config()
|
|
568
|
+
.computeValue(this.group())
|
|
569
|
+
.pipe(distinctUntilChanged(), tap((computedValue) => this.group().get(this.config().controlName).setValue(computedValue)))
|
|
570
|
+
.subscribe());
|
|
571
|
+
}
|
|
572
|
+
}
|
|
573
|
+
ngOnDestroy() {
|
|
574
|
+
this.subs.forEach((sub) => sub.unsubscribe);
|
|
575
|
+
}
|
|
576
|
+
/**
|
|
577
|
+
* Connects user defined disabledCallback function to the the view, so that the control is enabled/disabled
|
|
578
|
+
* appropriately. It also will hide disabled fields in the UI unless `hideDisabled` is set to
|
|
579
|
+
* false in the controls config
|
|
580
|
+
* @param group used to get valueChanges from control
|
|
581
|
+
* @param config configuration object used to
|
|
582
|
+
*/
|
|
583
|
+
connectDisabledCallback(group, config = null) {
|
|
584
|
+
const control = group.get(config.controlName);
|
|
585
|
+
// If the disabled$ function exists on the field config then call it with the disabledCallbackConfig
|
|
586
|
+
// as a parameter otherwise return an observable of true.
|
|
587
|
+
return config?.disabledCallback &&
|
|
588
|
+
config.disabledCallback instanceof Function
|
|
589
|
+
? config.disabledCallback(group).pipe(
|
|
590
|
+
// This enables/disables when callback conditions are met
|
|
591
|
+
tap((shouldDisable) => shouldDisable
|
|
592
|
+
? control.disable({ emitEvent: false })
|
|
593
|
+
: control.enable({ emitEvent: false })))
|
|
594
|
+
: of(false);
|
|
595
|
+
}
|
|
596
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.6", ngImport: i0, type: FieldContainerComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
597
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.6", type: FieldContainerComponent, isStandalone: true, selector: "crispr-field-container", inputs: { config: { classPropertyName: "config", publicName: "config", isSignal: true, isRequired: true, transformFunction: null }, group: { classPropertyName: "group", publicName: "group", isSignal: true, isRequired: true, transformFunction: null }, inlineField: { classPropertyName: "inlineField", publicName: "inlineField", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "style.display": "isHidden() ? \"none\" : \"\"" } }, ngImport: i0, template: "@if (!isHidden()) {\n <div\n [ngClass]=\"config().classes\"\n [class.inline-field]=\"inlineField()\"\n class=\"field-container\">\n @if (config().heading) {\n <crispr-heading [config]=\"config().heading\" />\n }\n <ng-content />\n </div>\n}\n", styles: ["::ng-deep mat-form-field.mat-mdc-form-field{display:block;padding:.25em 0}.inline-field{display:inline-block}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "component", type: HeadingComponent, selector: "crispr-heading" }], changeDetection: i0.ChangeDetectionStrategy.OnPush, preserveWhitespaces: true }); }
|
|
598
|
+
}
|
|
599
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.6", ngImport: i0, type: FieldContainerComponent, decorators: [{
|
|
600
|
+
type: Component,
|
|
601
|
+
args: [{ selector: 'crispr-field-container', changeDetection: ChangeDetectionStrategy.OnPush, standalone: true, imports: [CommonModule, HeadingComponent], host: {
|
|
602
|
+
'[style.display]': 'isHidden() ? "none" : ""',
|
|
603
|
+
}, template: "@if (!isHidden()) {\n <div\n [ngClass]=\"config().classes\"\n [class.inline-field]=\"inlineField()\"\n class=\"field-container\">\n @if (config().heading) {\n <crispr-heading [config]=\"config().heading\" />\n }\n <ng-content />\n </div>\n}\n", styles: ["::ng-deep mat-form-field.mat-mdc-form-field{display:block;padding:.25em 0}.inline-field{display:inline-block}\n"] }]
|
|
604
|
+
}], propDecorators: { config: [{ type: i0.Input, args: [{ isSignal: true, alias: "config", required: true }] }], group: [{ type: i0.Input, args: [{ isSignal: true, alias: "group", required: true }] }], inlineField: [{ type: i0.Input, args: [{ isSignal: true, alias: "inlineField", required: false }] }] } });
|
|
605
|
+
|
|
606
|
+
class SelectedFileComponent {
|
|
607
|
+
constructor() {
|
|
608
|
+
this.fileName = input.required(...(ngDevMode ? [{ debugName: "fileName" }] : []));
|
|
609
|
+
this.progress = input(...(ngDevMode ? [undefined, { debugName: "progress" }] : []));
|
|
610
|
+
this.showProgress = input(...(ngDevMode ? [undefined, { debugName: "showProgress" }] : []));
|
|
611
|
+
this.color = input(...(ngDevMode ? [undefined, { debugName: "color" }] : []));
|
|
612
|
+
}
|
|
613
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.6", ngImport: i0, type: SelectedFileComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
614
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.6", type: SelectedFileComponent, isStandalone: true, selector: "crispr-selected-file", inputs: { fileName: { classPropertyName: "fileName", publicName: "fileName", isSignal: true, isRequired: true, transformFunction: null }, progress: { classPropertyName: "progress", publicName: "progress", isSignal: true, isRequired: false, transformFunction: null }, showProgress: { classPropertyName: "showProgress", publicName: "showProgress", isSignal: true, isRequired: false, transformFunction: null }, color: { classPropertyName: "color", publicName: "color", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: "<h3 class=\"mat-h3\">{{ fileName() }}</h3>\n@if (showProgress()) {\n <mat-progress-bar mode=\"determinate\"\n [color]=\"color()\"\n [value]=\"progress()\" />\n}\n", styles: ["mat-progress-bar{margin-bottom:8px}\n"], dependencies: [{ kind: "ngmodule", type: MatProgressBarModule }, { kind: "component", type: i1$2.MatProgressBar, selector: "mat-progress-bar", inputs: ["color", "value", "bufferValue", "mode"], outputs: ["animationEnd"], exportAs: ["matProgressBar"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush, preserveWhitespaces: true }); }
|
|
615
|
+
}
|
|
616
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.6", ngImport: i0, type: SelectedFileComponent, decorators: [{
|
|
617
|
+
type: Component,
|
|
618
|
+
args: [{ selector: 'crispr-selected-file', changeDetection: ChangeDetectionStrategy.OnPush, standalone: true, imports: [
|
|
619
|
+
MatProgressBarModule
|
|
620
|
+
], template: "<h3 class=\"mat-h3\">{{ fileName() }}</h3>\n@if (showProgress()) {\n <mat-progress-bar mode=\"determinate\"\n [color]=\"color()\"\n [value]=\"progress()\" />\n}\n", styles: ["mat-progress-bar{margin-bottom:8px}\n"] }]
|
|
621
|
+
}], propDecorators: { fileName: [{ type: i0.Input, args: [{ isSignal: true, alias: "fileName", required: true }] }], progress: [{ type: i0.Input, args: [{ isSignal: true, alias: "progress", required: false }] }], showProgress: [{ type: i0.Input, args: [{ isSignal: true, alias: "showProgress", required: false }] }], color: [{ type: i0.Input, args: [{ isSignal: true, alias: "color", required: false }] }] } });
|
|
622
|
+
|
|
623
|
+
/* eslint-disable @nx/enforce-module-boundaries */
|
|
624
|
+
class FileUploadFieldComponent extends CrisprControlComponent {
|
|
625
|
+
/**
|
|
626
|
+
* Plain boolean view of `uploaded`, kept only for the public `AbstractUploadComponent` contract so
|
|
627
|
+
* consumers of the `uploadFiles` callback can still read and assign `isUploaded`.
|
|
628
|
+
* Internal code and the template use the `uploaded` signal directly.
|
|
629
|
+
*/
|
|
630
|
+
get isUploaded() {
|
|
631
|
+
return this.uploaded();
|
|
632
|
+
}
|
|
633
|
+
set isUploaded(value) {
|
|
634
|
+
this.uploaded.set(value);
|
|
635
|
+
}
|
|
636
|
+
// ngControl: NgControl = null;
|
|
637
|
+
constructor() {
|
|
638
|
+
super();
|
|
639
|
+
this.ngControl = inject(NgControl, { optional: true, self: true });
|
|
640
|
+
this.defaultConfig = {
|
|
641
|
+
allowMultipleFiles: false,
|
|
642
|
+
acceptedTypes: '*.*',
|
|
643
|
+
dropZoneText: 'DROP FILE HERE',
|
|
644
|
+
selectFilesButtonType: 'button',
|
|
645
|
+
selectButtonText: 'SELECT',
|
|
646
|
+
showClearFilesButton: true,
|
|
647
|
+
clearFilesButtonText: 'CLEAR',
|
|
648
|
+
uploadButtonText: 'UPLOAD',
|
|
649
|
+
uploadButtonType: 'button',
|
|
650
|
+
showUploadProgress: false,
|
|
651
|
+
disableOnUpload: true,
|
|
652
|
+
};
|
|
653
|
+
this.fileInputRef = viewChild('fileInput', ...(ngDevMode ? [{ debugName: "fileInputRef" }] : []));
|
|
654
|
+
this.selectedFiles = signal(null, ...(ngDevMode ? [{ debugName: "selectedFiles" }] : []));
|
|
655
|
+
this.selectedFilesArray = computed(() => this.selectedFiles() ? Array.from(this.selectedFiles()) : [], ...(ngDevMode ? [{ debugName: "selectedFilesArray" }] : []));
|
|
656
|
+
/**
|
|
657
|
+
* Whether the files have been uploaded. A signal so that changes made asynchronously (e.g. from the
|
|
658
|
+
* `uploadFiles` callback once an upload finishes) re-render the OnPush/zoneless component.
|
|
659
|
+
*/
|
|
660
|
+
this.uploaded = signal(false, ...(ngDevMode ? [{ debugName: "uploaded" }] : []));
|
|
661
|
+
this.fileProgress = [];
|
|
662
|
+
const ngControl = this.ngControl;
|
|
663
|
+
if (this.ngControl != null) {
|
|
664
|
+
// Setting the value accessor directly (instead of using
|
|
665
|
+
// the providers) to avoid running into a circular import.
|
|
666
|
+
ngControl.valueAccessor = this;
|
|
667
|
+
}
|
|
668
|
+
}
|
|
669
|
+
ngOnInit() {
|
|
670
|
+
super.ngOnInit();
|
|
671
|
+
this.disabled$ = this.config().disabledCallback
|
|
672
|
+
? this.config().disabledCallback(this.group())
|
|
673
|
+
: of(false);
|
|
674
|
+
}
|
|
675
|
+
filesChanged(files) {
|
|
676
|
+
if (files && files.length > 0) {
|
|
677
|
+
this.selectedFiles.set(files);
|
|
678
|
+
this.control().patchValue(files);
|
|
679
|
+
this.config().filesChanged?.(this.group(), files);
|
|
680
|
+
}
|
|
681
|
+
}
|
|
682
|
+
uploadFiles() {
|
|
683
|
+
if (this.selectedFiles() && this.config().uploadFiles) {
|
|
684
|
+
this.uploaded.set(true);
|
|
685
|
+
this.config().uploadFiles(this.group(), this.selectedFiles(), this);
|
|
686
|
+
}
|
|
687
|
+
}
|
|
688
|
+
resetFileInput() {
|
|
689
|
+
this.fileInputRef().nativeElement.value = '';
|
|
690
|
+
this.control().patchValue(null);
|
|
691
|
+
this.uploaded.set(false);
|
|
692
|
+
}
|
|
693
|
+
writeValue(value) {
|
|
694
|
+
// clear file input
|
|
695
|
+
// this.host.nativeElement.value = '';
|
|
696
|
+
// this.file = null;
|
|
697
|
+
}
|
|
698
|
+
registerOnChange(fn) {
|
|
699
|
+
this.onChange = fn;
|
|
700
|
+
}
|
|
701
|
+
registerOnTouched(fn) { }
|
|
702
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.6", ngImport: i0, type: FileUploadFieldComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
703
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.6", type: FileUploadFieldComponent, isStandalone: true, selector: "crispr-file-upload-field", viewQueries: [{ propertyName: "fileInputRef", first: true, predicate: ["fileInput"], descendants: true, isSignal: true }], usesInheritance: true, ngImport: i0, template: "<crispr-field-container\n [config]=\"config()\"\n [group]=\"group()\">\n <mat-form-field floatLabel=\"always\"\n [attr.data-testid]=\"testId()\"\n [color]=\"config().color\"\n appearance=\"outline\"\n [crisprFileDropzone]=\"!(disabled$ | async) && !(config().disableOnUpload && uploaded())\"\n [formGroup]=\"group()\"\n (fileDrop)=\"filesChanged($event)\">\n\n <input [formControlName]=\"config().controlName\" matInput type=\"text\"/>\n <input\n #fileInput\n type=\"file\"\n style=\"display: none\"\n [accept]=\"config().acceptedTypes\"\n [multiple]=\"config().allowMultipleFiles\"\n (change)=\"filesChanged($event.target.files)\"/>\n <mat-label>{{config().label}}</mat-label>\n\n @if (!selectedFiles() || selectedFiles().length === 0) {\n <div class=\"drop-info\">\n <mat-icon class=\"drop-icon\" inline=\"true\">cloud_upload</mat-icon>\n <h2 class=\"mat-h2\">{{config().dropZoneText}}</h2>\n or\n <button\n mat-raised-button\n type=\"button\"\n color=\"primary\"\n [disabled]=\"(disabled$ | async) || config().disableOnUpload && uploaded()\"\n (click)=\"fileInput.click()\"\n [attr.aria-label]=\"config().controlName\">\n {{config().selectButtonText}}\n </button>\n </div>\n } @else {\n <div class=\"selected-files-container\">\n <div class=\"selected-files-list\">\n @for (file of selectedFilesArray(); track file; let i = $index) {\n <crispr-selected-file [color]=\"config().color === 'primary' ? 'accent' : 'primary'\"\n [fileName]=\"file.name\"\n [showProgress]=\"config().showUploadProgress\"\n [progress]=\"fileProgress && (fileProgress[i] | async)\" />\n }\n </div>\n </div>\n }\n @if (config().hint && (control().valid || !control().touched)) {<mat-hint>{{config().hint}}</mat-hint>}\n </mat-form-field>\n @if ((selectedFiles())?.length > 0) {\n <div class=\"button-container\">\n <button\n mat-button\n (click)=\"resetFileInput()\"\n type=\"button\"\n [disabled]=\"(disabled$ | async) || (config().disableOnUpload && uploaded())\"\n aria-label=\"Remove Selected File(s)\">\n {{config().clearFilesButtonText}}\n <mat-icon>delete</mat-icon>\n </button>\n @if (!!config().uploadFiles) {\n <button\n mat-raised-button\n type=\"button\"\n color=\"accent\"\n class=\"file-input-button\"\n [disabled]=\"(disabled$ | async) || (config().disableOnUpload && uploaded())\"\n (click)=\"uploadFiles()\"\n [attr.aria-label]=\"config().uploadButtonText\">\n {{ config().uploadButtonText }}\n </button>\n }\n </div>\n }\n</crispr-field-container>\n", styles: [":host ::ng-deep .mat-mdc-form-field .mat-mdc-form-field-infix{padding-top:0;display:flex;justify-content:center}:host .drop-info{display:flex;flex-direction:column;align-items:center}:host .drop-info .drop-icon{font-size:36px}:host .drop-info .mat-h2{margin:0}:host .button-container{display:flex;justify-content:flex-end;margin-top:-16px}:host .button-container button{margin-left:8px}:host .selected-files-container{width:100%}:host [matInput]{position:absolute;max-height:0;visibility:hidden}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "component", type: FieldContainerComponent, selector: "crispr-field-container", inputs: ["config", "group", "inlineField"] }, { 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.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],[formArray],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i1$1.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "directive", type: i1$1.FormControlName, selector: "[formControlName]", inputs: ["formControlName", "disabled", "ngModel"], outputs: ["ngModelChange"] }, { kind: "ngmodule", type: MatIconModule }, { kind: "component", type: i2.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "ngmodule", type: MatButtonModule }, { kind: "component", type: i3.MatButton, selector: " button[matButton], a[matButton], button[mat-button], button[mat-raised-button], button[mat-flat-button], button[mat-stroked-button], a[mat-button], a[mat-raised-button], a[mat-flat-button], a[mat-stroked-button] ", inputs: ["matButton"], exportAs: ["matButton", "matAnchor"] }, { kind: "ngmodule", type: MatFormFieldModule }, { kind: "component", type: i2$2.MatFormField, selector: "mat-form-field", inputs: ["hideRequiredMarker", "color", "floatLabel", "appearance", "subscriptSizing", "hintLabel"], exportAs: ["matFormField"] }, { kind: "directive", type: i2$2.MatLabel, selector: "mat-label" }, { kind: "directive", type: i2$2.MatHint, selector: "mat-hint", inputs: ["align", "id"] }, { kind: "ngmodule", type: MatInputModule }, { kind: "directive", type: i3$1.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: SelectedFileComponent, selector: "crispr-selected-file", inputs: ["fileName", "progress", "showProgress", "color"] }, { kind: "directive", type: ControlErrorContainerDirective, selector: "[controlErrorContainer], [formGroup]" }, { kind: "directive", type: ControlErrorsDirective, selector: "[formControl], [formControlName]" }, { kind: "directive", type: FileDropzoneDirective, selector: "[crisprFileDropzone]", inputs: ["crisprFileDropzone"], outputs: ["fileDrop"] }, { kind: "pipe", type: i1.AsyncPipe, name: "async" }], changeDetection: i0.ChangeDetectionStrategy.OnPush, preserveWhitespaces: true }); }
|
|
704
|
+
}
|
|
705
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.6", ngImport: i0, type: FileUploadFieldComponent, decorators: [{
|
|
706
|
+
type: Component,
|
|
707
|
+
args: [{ selector: 'crispr-file-upload-field', changeDetection: ChangeDetectionStrategy.OnPush, imports: [
|
|
708
|
+
CommonModule,
|
|
709
|
+
FieldContainerComponent,
|
|
710
|
+
ReactiveFormsModule,
|
|
711
|
+
MatIconModule,
|
|
712
|
+
MatButtonModule,
|
|
713
|
+
MatFormFieldModule,
|
|
714
|
+
MatInputModule,
|
|
715
|
+
SelectedFileComponent,
|
|
716
|
+
ControlErrorContainerDirective,
|
|
717
|
+
ControlErrorsDirective,
|
|
718
|
+
FileDropzoneDirective
|
|
719
|
+
], standalone: true, template: "<crispr-field-container\n [config]=\"config()\"\n [group]=\"group()\">\n <mat-form-field floatLabel=\"always\"\n [attr.data-testid]=\"testId()\"\n [color]=\"config().color\"\n appearance=\"outline\"\n [crisprFileDropzone]=\"!(disabled$ | async) && !(config().disableOnUpload && uploaded())\"\n [formGroup]=\"group()\"\n (fileDrop)=\"filesChanged($event)\">\n\n <input [formControlName]=\"config().controlName\" matInput type=\"text\"/>\n <input\n #fileInput\n type=\"file\"\n style=\"display: none\"\n [accept]=\"config().acceptedTypes\"\n [multiple]=\"config().allowMultipleFiles\"\n (change)=\"filesChanged($event.target.files)\"/>\n <mat-label>{{config().label}}</mat-label>\n\n @if (!selectedFiles() || selectedFiles().length === 0) {\n <div class=\"drop-info\">\n <mat-icon class=\"drop-icon\" inline=\"true\">cloud_upload</mat-icon>\n <h2 class=\"mat-h2\">{{config().dropZoneText}}</h2>\n or\n <button\n mat-raised-button\n type=\"button\"\n color=\"primary\"\n [disabled]=\"(disabled$ | async) || config().disableOnUpload && uploaded()\"\n (click)=\"fileInput.click()\"\n [attr.aria-label]=\"config().controlName\">\n {{config().selectButtonText}}\n </button>\n </div>\n } @else {\n <div class=\"selected-files-container\">\n <div class=\"selected-files-list\">\n @for (file of selectedFilesArray(); track file; let i = $index) {\n <crispr-selected-file [color]=\"config().color === 'primary' ? 'accent' : 'primary'\"\n [fileName]=\"file.name\"\n [showProgress]=\"config().showUploadProgress\"\n [progress]=\"fileProgress && (fileProgress[i] | async)\" />\n }\n </div>\n </div>\n }\n @if (config().hint && (control().valid || !control().touched)) {<mat-hint>{{config().hint}}</mat-hint>}\n </mat-form-field>\n @if ((selectedFiles())?.length > 0) {\n <div class=\"button-container\">\n <button\n mat-button\n (click)=\"resetFileInput()\"\n type=\"button\"\n [disabled]=\"(disabled$ | async) || (config().disableOnUpload && uploaded())\"\n aria-label=\"Remove Selected File(s)\">\n {{config().clearFilesButtonText}}\n <mat-icon>delete</mat-icon>\n </button>\n @if (!!config().uploadFiles) {\n <button\n mat-raised-button\n type=\"button\"\n color=\"accent\"\n class=\"file-input-button\"\n [disabled]=\"(disabled$ | async) || (config().disableOnUpload && uploaded())\"\n (click)=\"uploadFiles()\"\n [attr.aria-label]=\"config().uploadButtonText\">\n {{ config().uploadButtonText }}\n </button>\n }\n </div>\n }\n</crispr-field-container>\n", styles: [":host ::ng-deep .mat-mdc-form-field .mat-mdc-form-field-infix{padding-top:0;display:flex;justify-content:center}:host .drop-info{display:flex;flex-direction:column;align-items:center}:host .drop-info .drop-icon{font-size:36px}:host .drop-info .mat-h2{margin:0}:host .button-container{display:flex;justify-content:flex-end;margin-top:-16px}:host .button-container button{margin-left:8px}:host .selected-files-container{width:100%}:host [matInput]{position:absolute;max-height:0;visibility:hidden}\n"] }]
|
|
720
|
+
}], ctorParameters: () => [], propDecorators: { fileInputRef: [{ type: i0.ViewChild, args: ['fileInput', { isSignal: true }] }] } });
|
|
721
|
+
|
|
722
|
+
var index$h = /*#__PURE__*/Object.freeze({
|
|
723
|
+
__proto__: null,
|
|
724
|
+
FileUploadFieldComponent: FileUploadFieldComponent
|
|
725
|
+
});
|
|
726
|
+
|
|
727
|
+
class OptionComponent {
|
|
728
|
+
constructor() {
|
|
729
|
+
this.option = input.required(...(ngDevMode ? [{ debugName: "option" }] : []));
|
|
730
|
+
}
|
|
731
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.6", ngImport: i0, type: OptionComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
732
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.6", type: OptionComponent, isStandalone: true, selector: "crispr-option", inputs: { option: { classPropertyName: "option", publicName: "option", isSignal: true, isRequired: true, transformFunction: null } }, ngImport: i0, template: "<div>\n @if (option().imageUrl) {\n <img\n [src]=\"option().imageUrl\"\n alt=\"image of {{option().label}}\"\n style=\"vertical-align:middle;\"\n aria-hidden\n height=\"25\"/>\n }\n {{option().label}}\n</div>\n\n@if (option().info; as info) {\n <crispr-info [info]=\"info\" />\n}\n", styles: [":host{display:flex;justify-content:space-between}:host img{margin-right:4px}\n"], dependencies: [{ kind: "component", type: InfoComponent, selector: "crispr-info", inputs: ["info"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush, preserveWhitespaces: true }); }
|
|
733
|
+
}
|
|
734
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.6", ngImport: i0, type: OptionComponent, decorators: [{
|
|
735
|
+
type: Component,
|
|
736
|
+
args: [{ selector: 'crispr-option', changeDetection: ChangeDetectionStrategy.OnPush, standalone: true, imports: [InfoComponent], template: "<div>\n @if (option().imageUrl) {\n <img\n [src]=\"option().imageUrl\"\n alt=\"image of {{option().label}}\"\n style=\"vertical-align:middle;\"\n aria-hidden\n height=\"25\"/>\n }\n {{option().label}}\n</div>\n\n@if (option().info; as info) {\n <crispr-info [info]=\"info\" />\n}\n", styles: [":host{display:flex;justify-content:space-between}:host img{margin-right:4px}\n"] }]
|
|
737
|
+
}], propDecorators: { option: [{ type: i0.Input, args: [{ isSignal: true, alias: "option", required: true }] }] } });
|
|
738
|
+
|
|
739
|
+
/* eslint-disable @nx/enforce-module-boundaries */
|
|
740
|
+
const defaultConfig$8 = {
|
|
741
|
+
chipsSelectable: true,
|
|
742
|
+
areChipsRemovable: true,
|
|
743
|
+
tabToSelect: true,
|
|
744
|
+
autoActiveFirstOption: true,
|
|
745
|
+
imageUrlParam: 'image',
|
|
746
|
+
typeDebounceTime: 500,
|
|
747
|
+
allowDuplicates: false,
|
|
748
|
+
duplicateCompareFunction: (chip, option) => chip.value === option.value,
|
|
749
|
+
separatorKeyCodes: [ENTER],
|
|
750
|
+
emptyOptionsMessage: DEFAULT_EMPTY_OPTIONS_MESSAGE,
|
|
751
|
+
};
|
|
752
|
+
/** Sentinel used to detect "Create New" selection — never matches a real SelectOption */
|
|
753
|
+
const CREATE_NEW_SENTINEL = Symbol('CREATE_NEW');
|
|
754
|
+
class AutocompleteChiplistFieldComponent extends AbstractAutocompleteComponent {
|
|
755
|
+
constructor() {
|
|
756
|
+
super(...arguments);
|
|
757
|
+
this.defaultConfig = defaultConfig$8;
|
|
758
|
+
this.chips$ = new BehaviorSubject([]);
|
|
759
|
+
this.selectedChip = signal(null, ...(ngDevMode ? [{ debugName: "selectedChip" }] : []));
|
|
760
|
+
this.controlValue$ = this.chips$.pipe(tap((chips) => {
|
|
761
|
+
this.control().setValue(chips.map((chip) => chip.value));
|
|
762
|
+
}));
|
|
763
|
+
// We need use ViewChild twice on the same template ref to access as different types
|
|
764
|
+
// - the MatAutocompleteTrigger is used to for selecting value on tab
|
|
765
|
+
// - the ElementRef<HTMLInputElement> is used to clear the value from input element on selection
|
|
766
|
+
this.chipInput = viewChild('autoInput', { ...(ngDevMode ? { debugName: "chipInput" } : {}), read: MatAutocompleteTrigger });
|
|
767
|
+
this.chipInputRef = viewChild('autoInput', ...(ngDevMode ? [{ debugName: "chipInputRef" }] : []));
|
|
768
|
+
this.overlayOrigin = viewChild(CdkOverlayOrigin, ...(ngDevMode ? [{ debugName: "overlayOrigin" }] : []));
|
|
769
|
+
this.createNewSentinel = CREATE_NEW_SENTINEL;
|
|
770
|
+
this.showCreateForm = signal(false, ...(ngDevMode ? [{ debugName: "showCreateForm" }] : []));
|
|
771
|
+
this.createFormGroup = new FormGroup({});
|
|
772
|
+
this.createFormPositions = [
|
|
773
|
+
{ originX: 'start', originY: 'bottom', overlayX: 'start', overlayY: 'top' },
|
|
774
|
+
{ originX: 'start', originY: 'top', overlayX: 'start', overlayY: 'bottom' }
|
|
775
|
+
];
|
|
776
|
+
}
|
|
777
|
+
get overlayWidth() {
|
|
778
|
+
return this.overlayOrigin()?.elementRef.nativeElement.offsetWidth ?? 0;
|
|
779
|
+
}
|
|
780
|
+
ngOnInit() {
|
|
781
|
+
super.ngOnInit();
|
|
782
|
+
this.remainingOptions$ = combineLatest([this.chips$, this.options$]).pipe(map(([chips, options]) => {
|
|
783
|
+
return this.config().allowDuplicates
|
|
784
|
+
? options
|
|
785
|
+
: options?.filter((option) => {
|
|
786
|
+
const compare = this.config().duplicateCompareFunction
|
|
787
|
+
?? ((chip, opt) => chip.value === opt.value);
|
|
788
|
+
return !chips.some((chip) => compare(chip, option));
|
|
789
|
+
});
|
|
790
|
+
}));
|
|
791
|
+
}
|
|
792
|
+
setControlValue(value) {
|
|
793
|
+
if (value) {
|
|
794
|
+
this.chips$.next(value);
|
|
795
|
+
}
|
|
796
|
+
}
|
|
797
|
+
handleSelect(event) {
|
|
798
|
+
if (event.option.value === CREATE_NEW_SENTINEL) {
|
|
799
|
+
this.openCreateForm();
|
|
800
|
+
return;
|
|
801
|
+
}
|
|
802
|
+
const selectedOption = event.option.value;
|
|
803
|
+
this.chips$.next([...this.chips$.value, selectedOption]);
|
|
804
|
+
this.chipInputRef().nativeElement.value = '';
|
|
805
|
+
this.autocompleteInputControl.setValue('');
|
|
806
|
+
// A macrotask is needed here (not a change detection workaround): MatAutocomplete only closes its panel
|
|
807
|
+
// after this selection event has finished, so reopening it synchronously is a no-op.
|
|
808
|
+
setTimeout(() => this.chipInput().openPanel());
|
|
809
|
+
}
|
|
810
|
+
openCreateForm() {
|
|
811
|
+
this.createFormGroup = buildFormGroupFromConfig({ fields: this.config().createChip.fields });
|
|
812
|
+
this.chipInputRef().nativeElement.value = '';
|
|
813
|
+
this.autocompleteInputControl.setValue('');
|
|
814
|
+
// Deferred to a macrotask so that the click that opened the form has finished bubbling to the document;
|
|
815
|
+
// otherwise the overlay's outside-click handling would close the form straight away (zone.js or not).
|
|
816
|
+
setTimeout(() => this.showCreateForm.set(true));
|
|
817
|
+
}
|
|
818
|
+
submitNewChip() {
|
|
819
|
+
this.config().createChip.submitChip(this.createFormGroup)
|
|
820
|
+
.pipe(take(1))
|
|
821
|
+
.subscribe((newChip) => {
|
|
822
|
+
this.chips$.next([...this.chips$.value, newChip]);
|
|
823
|
+
this.showCreateForm.set(false);
|
|
824
|
+
});
|
|
825
|
+
}
|
|
826
|
+
closeCreateForm() {
|
|
827
|
+
this.showCreateForm.set(false);
|
|
828
|
+
}
|
|
829
|
+
/**
|
|
830
|
+
* To follow ARIA standards we want to select the active option on blur.
|
|
831
|
+
* matChipInputAddOnBlur should do this but it causes buggy behaviour
|
|
832
|
+
* @param event blur event that triggers the handle blur
|
|
833
|
+
*/
|
|
834
|
+
handleTab(_event) {
|
|
835
|
+
const chipInput = this.chipInput();
|
|
836
|
+
if (chipInput.activeOption &&
|
|
837
|
+
(this.config().tabToSelect || this.config().addChipOnBlur)) {
|
|
838
|
+
const chip = chipInput.activeOption.value;
|
|
839
|
+
this.chips$.next([...this.chips$.value, chip]);
|
|
840
|
+
}
|
|
841
|
+
}
|
|
842
|
+
/**
|
|
843
|
+
* Handles selection via key tokens passed through separatorKeyCodes property
|
|
844
|
+
* NOTE: If TAB is used focus does not move on to next component
|
|
845
|
+
* @param event
|
|
846
|
+
*/
|
|
847
|
+
handleTokenEnd(event) {
|
|
848
|
+
const chipInput = this.chipInput();
|
|
849
|
+
if (chipInput.activeOption) {
|
|
850
|
+
chipInput.activeOption.select();
|
|
851
|
+
}
|
|
852
|
+
event.chipInput?.clear();
|
|
853
|
+
}
|
|
854
|
+
mapToLabel(option) {
|
|
855
|
+
if (!option || option === CREATE_NEW_SENTINEL)
|
|
856
|
+
return '';
|
|
857
|
+
return option.label ?? '';
|
|
858
|
+
}
|
|
859
|
+
removeChip(removedChip) {
|
|
860
|
+
const remainingChips = this.chips$.value.filter((chip) => chip !== removedChip);
|
|
861
|
+
this.chips$.next(remainingChips);
|
|
862
|
+
}
|
|
863
|
+
highlightChip(chip) {
|
|
864
|
+
if (this.config().chipsSelectable) {
|
|
865
|
+
this.selectedChip.set(chip);
|
|
866
|
+
}
|
|
867
|
+
}
|
|
868
|
+
blurField() {
|
|
869
|
+
this.selectedChip.set(null);
|
|
870
|
+
}
|
|
871
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.6", ngImport: i0, type: AutocompleteChiplistFieldComponent, deps: null, target: i0.ɵɵFactoryTarget.Component }); }
|
|
872
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.6", type: AutocompleteChiplistFieldComponent, isStandalone: true, selector: "crispr-autocomplete-chiplist-field", viewQueries: [{ propertyName: "chipInput", first: true, predicate: ["autoInput"], descendants: true, read: MatAutocompleteTrigger, isSignal: true }, { propertyName: "chipInputRef", first: true, predicate: ["autoInput"], descendants: true, isSignal: true }, { propertyName: "overlayOrigin", first: true, predicate: CdkOverlayOrigin, descendants: true, isSignal: true }], usesInheritance: true, ngImport: i0, template: "<crispr-field-container [config]=\"config()\" [group]=\"group()\">\n\n <mat-form-field cdkOverlayOrigin [attr.data-testid]=\"testId()\" [color]=\"config().color\" [appearance]=\"config().appearance\" [formGroup]=\"group()\">\n @if (config().label) {<mat-label>{{config().label}}</mat-label>}\n\n <mat-chip-grid #chipList>\n <!-- [selectable]=\"config().chipsSelectable\" -->\n @for (chip of controlValue$ | async; track chip; let i = $index) {\n <mat-chip-row class=\"refinement-chip\" [removable]=\"config().areChipsRemovable\"\n [highlighted]=\"chip?.value === selectedChip()?.value\" (click)=\"highlightChip(chip)\" (removed)=\"removeChip(chip)\">\n @if (chip.imageUrl) {<img [src]=\"chip.imageUrl\" matChipAvatar alt=\"image of {{chip.label}}\">}\n {{chip.label}}\n @if (config().areChipsRemovable) {<mat-icon matChipRemove>cancel</mat-icon>}\n </mat-chip-row>\n }\n <input class=\"hidden-error-directive-anchor\" (blur)=\"blurField()\" [formControlName]=\"config().controlName\"\n type=\"text\" />\n\n <input matInput [formControl]=\"autocompleteInputControl\" [placeholder]=\"config().placeholder\"\n [matChipInputFor]=\"chipList\" [matAutocomplete]=\"auto\" (keydown.tab)=\"handleTab($event)\"\n [matChipInputSeparatorKeyCodes]=\"config().separatorKeyCodes\" (matChipInputTokenEnd)=\"handleTokenEnd($event)\"\n role=\"searchbox\" type=\"text\" autocapitalize=\"off\" autocorrect=\"off\" spellcheck=\"false\" (blur)=\"blurField()\"\n #autoInput />\n @if (config().fieldSuffix) {<span matSuffix>{{config().fieldSuffix}}</span>}\n @if (config().info) {<crispr-info matSuffix [info]=\"config().info \" />}\n\n </mat-chip-grid>\n <mat-autocomplete #auto=\"matAutocomplete\" [autoActiveFirstOption]=\"this.config().autoActiveFirstOption\"\n (optionSelected)=\"handleSelect($event)\" [displayWith]=\"mapToLabel\">\n @if (remainingOptions$ | async; as remainingOptions) {\n @if (remainingOptions.length > 0) {\n @for (option of remainingOptions$ | async; track option.value) {\n <mat-option [value]=\"option\">\n <crispr-option [option]=\"option\" />\n </mat-option>\n }\n } @else {\n <mat-option disabled=\"true\">\n {{config().emptyOptionsMessage}}\n </mat-option>\n }\n } @else {\n <mat-option disabled=\"true\">\n {{config().emptyOptionsMessage}}\n </mat-option>\n }\n @if (config().createChip) {\n <mat-option [value]=\"createNewSentinel\" class=\"create-new-option\">\n {{ config().createChip?.createLabel ?? '+ Create New' }}\n </mat-option>\n }\n </mat-autocomplete>\n\n @if (config().hint && (control().valid || !control().touched)) {<mat-hint>{{config().hint}}</mat-hint>}\n\n </mat-form-field>\n\n <ng-template\n cdkConnectedOverlay\n [cdkConnectedOverlayOrigin]=\"overlayOrigin()\"\n [cdkConnectedOverlayOpen]=\"showCreateForm()\"\n [cdkConnectedOverlayWidth]=\"overlayWidth\"\n [cdkConnectedOverlayPositions]=\"createFormPositions\"\n (overlayOutsideClick)=\"closeCreateForm()\">\n <div class=\"create-chip-panel mat-elevation-z4\">\n @for (field of config().createChip?.fields ?? []; track field) {\n <ng-container crisprField [config]=\"field\" [group]=\"createFormGroup\" />\n }\n <div class=\"create-chip-actions\">\n <button mat-stroked-button type=\"button\" (click)=\"closeCreateForm()\">Cancel</button>\n <button mat-raised-button color=\"primary\" type=\"button\" (click)=\"submitNewChip()\">\n {{ config().createChip?.submitLabel ?? 'Add' }}\n </button>\n </div>\n </div>\n </ng-template>\n\n</crispr-field-container>\n", styles: [".refinement-chip .image-container{width:26px;height:26px;border-radius:50%;overflow:hidden;margin:-2px 4px -2px -8px}.refinement-chip .image-container img{width:100%;height:100%;object-fit:cover}.hidden-error-directive-anchor{display:none}::ng-deep .create-chip-panel{background:var(--mat-sys-surface, white);border-radius:4px;box-shadow:0 2px 4px -1px #0003,0 4px 5px #00000024,0 1px 10px #0000001f;padding:16px;width:100%;box-sizing:border-box;display:flex;flex-direction:column;gap:8px}.create-chip-actions{display:flex;justify-content:flex-end;gap:8px;margin-top:8px}.create-new-option{font-style:italic;color:var(--mat-sys-primary, #6750a4)}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "component", type: InfoComponent, selector: "crispr-info", inputs: ["info"] }, { kind: "component", type: OptionComponent, selector: "crispr-option", inputs: ["option"] }, { 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.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],[formArray],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i1$1.FormControlDirective, selector: "[formControl]", inputs: ["formControl", "disabled", "ngModel"], outputs: ["ngModelChange"], exportAs: ["ngForm"] }, { kind: "directive", type: i1$1.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "directive", type: i1$1.FormControlName, selector: "[formControlName]", inputs: ["formControlName", "disabled", "ngModel"], outputs: ["ngModelChange"] }, { kind: "component", type: FieldContainerComponent, selector: "crispr-field-container", inputs: ["config", "group", "inlineField"] }, { kind: "ngmodule", type: MatAutocompleteModule }, { kind: "component", type: i2$3.MatAutocomplete, selector: "mat-autocomplete", inputs: ["aria-label", "aria-labelledby", "displayWith", "autoActiveFirstOption", "autoSelectActiveOption", "requireSelection", "panelWidth", "disableRipple", "class", "hideSingleSelectionIndicator"], outputs: ["optionSelected", "opened", "closed", "optionActivated"], exportAs: ["matAutocomplete"] }, { kind: "component", type: i2$3.MatOption, selector: "mat-option", inputs: ["value", "id", "disabled"], outputs: ["onSelectionChange"], exportAs: ["matOption"] }, { kind: "directive", type: i2$3.MatAutocompleteTrigger, selector: "input[matAutocomplete], textarea[matAutocomplete]", inputs: ["matAutocomplete", "matAutocompletePosition", "matAutocompleteConnectedTo", "autocomplete", "matAutocompleteDisabled"], exportAs: ["matAutocompleteTrigger"] }, { kind: "ngmodule", type: MatInputModule }, { kind: "directive", type: i3$1.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: i2$2.MatFormField, selector: "mat-form-field", inputs: ["hideRequiredMarker", "color", "floatLabel", "appearance", "subscriptSizing", "hintLabel"], exportAs: ["matFormField"] }, { kind: "directive", type: i2$2.MatLabel, selector: "mat-label" }, { kind: "directive", type: i2$2.MatHint, selector: "mat-hint", inputs: ["align", "id"] }, { kind: "directive", type: i2$2.MatSuffix, selector: "[matSuffix], [matIconSuffix], [matTextSuffix]", inputs: ["matTextSuffix"] }, { kind: "ngmodule", type: MatOptionModule }, { kind: "ngmodule", type: MatChipsModule }, { kind: "directive", type: i5.MatChipAvatar, selector: "mat-chip-avatar, [matChipAvatar]" }, { kind: "component", type: i5.MatChipGrid, selector: "mat-chip-grid", inputs: ["disabled", "placeholder", "required", "value", "errorStateMatcher"], outputs: ["change", "valueChange"] }, { kind: "directive", type: i5.MatChipInput, selector: "input[matChipInputFor]", inputs: ["matChipInputFor", "matChipInputAddOnBlur", "matChipInputSeparatorKeyCodes", "placeholder", "id", "disabled", "readonly", "matChipInputDisabledInteractive"], outputs: ["matChipInputTokenEnd"], exportAs: ["matChipInput", "matChipInputFor"] }, { kind: "directive", type: i5.MatChipRemove, selector: "[matChipRemove]" }, { kind: "component", type: i5.MatChipRow, selector: "mat-chip-row, [mat-chip-row], mat-basic-chip-row, [mat-basic-chip-row]", inputs: ["editable"], outputs: ["edited"] }, { kind: "ngmodule", type: MatIconModule }, { kind: "component", type: i2.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "ngmodule", type: MatButtonModule }, { kind: "component", type: i3.MatButton, selector: " button[matButton], a[matButton], button[mat-button], button[mat-raised-button], button[mat-flat-button], button[mat-stroked-button], a[mat-button], a[mat-raised-button], a[mat-flat-button], a[mat-stroked-button] ", inputs: ["matButton"], exportAs: ["matButton", "matAnchor"] }, { kind: "directive", type: ControlErrorContainerDirective, selector: "[controlErrorContainer], [formGroup]" }, { kind: "directive", type: ControlErrorsDirective, selector: "[formControl], [formControlName]" }, { kind: "directive", type: CdkConnectedOverlay, selector: "[cdk-connected-overlay], [connected-overlay], [cdkConnectedOverlay]", inputs: ["cdkConnectedOverlayOrigin", "cdkConnectedOverlayPositions", "cdkConnectedOverlayPositionStrategy", "cdkConnectedOverlayOffsetX", "cdkConnectedOverlayOffsetY", "cdkConnectedOverlayWidth", "cdkConnectedOverlayHeight", "cdkConnectedOverlayMinWidth", "cdkConnectedOverlayMinHeight", "cdkConnectedOverlayBackdropClass", "cdkConnectedOverlayPanelClass", "cdkConnectedOverlayViewportMargin", "cdkConnectedOverlayScrollStrategy", "cdkConnectedOverlayOpen", "cdkConnectedOverlayDisableClose", "cdkConnectedOverlayTransformOriginOn", "cdkConnectedOverlayHasBackdrop", "cdkConnectedOverlayLockPosition", "cdkConnectedOverlayFlexibleDimensions", "cdkConnectedOverlayGrowAfterOpen", "cdkConnectedOverlayPush", "cdkConnectedOverlayDisposeOnNavigation", "cdkConnectedOverlayUsePopover", "cdkConnectedOverlayMatchWidth", "cdkConnectedOverlay"], outputs: ["backdropClick", "positionChange", "attach", "detach", "overlayKeydown", "overlayOutsideClick"], exportAs: ["cdkConnectedOverlay"] }, { kind: "directive", type: CdkOverlayOrigin, selector: "[cdk-overlay-origin], [overlay-origin], [cdkOverlayOrigin]", exportAs: ["cdkOverlayOrigin"] }, { kind: "directive", type: CrisprFieldDirective, selector: "[crisprField]", inputs: ["config", "group", "testIdPrefix", "value"] }, { kind: "pipe", type: i1.AsyncPipe, name: "async" }], changeDetection: i0.ChangeDetectionStrategy.OnPush, preserveWhitespaces: true }); }
|
|
873
|
+
}
|
|
874
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.6", ngImport: i0, type: AutocompleteChiplistFieldComponent, decorators: [{
|
|
875
|
+
type: Component,
|
|
876
|
+
args: [{ selector: 'crispr-autocomplete-chiplist-field', changeDetection: ChangeDetectionStrategy.OnPush, imports: [
|
|
877
|
+
CommonModule,
|
|
878
|
+
InfoComponent,
|
|
879
|
+
OptionComponent,
|
|
880
|
+
ReactiveFormsModule,
|
|
881
|
+
FieldContainerComponent,
|
|
882
|
+
MatAutocompleteModule,
|
|
883
|
+
MatInputModule,
|
|
884
|
+
MatOptionModule,
|
|
885
|
+
MatChipsModule,
|
|
886
|
+
MatIconModule,
|
|
887
|
+
MatButtonModule,
|
|
888
|
+
ControlErrorContainerDirective,
|
|
889
|
+
ControlErrorsDirective,
|
|
890
|
+
CdkConnectedOverlay,
|
|
891
|
+
CdkOverlayOrigin,
|
|
892
|
+
CrisprFieldDirective
|
|
893
|
+
], standalone: true, template: "<crispr-field-container [config]=\"config()\" [group]=\"group()\">\n\n <mat-form-field cdkOverlayOrigin [attr.data-testid]=\"testId()\" [color]=\"config().color\" [appearance]=\"config().appearance\" [formGroup]=\"group()\">\n @if (config().label) {<mat-label>{{config().label}}</mat-label>}\n\n <mat-chip-grid #chipList>\n <!-- [selectable]=\"config().chipsSelectable\" -->\n @for (chip of controlValue$ | async; track chip; let i = $index) {\n <mat-chip-row class=\"refinement-chip\" [removable]=\"config().areChipsRemovable\"\n [highlighted]=\"chip?.value === selectedChip()?.value\" (click)=\"highlightChip(chip)\" (removed)=\"removeChip(chip)\">\n @if (chip.imageUrl) {<img [src]=\"chip.imageUrl\" matChipAvatar alt=\"image of {{chip.label}}\">}\n {{chip.label}}\n @if (config().areChipsRemovable) {<mat-icon matChipRemove>cancel</mat-icon>}\n </mat-chip-row>\n }\n <input class=\"hidden-error-directive-anchor\" (blur)=\"blurField()\" [formControlName]=\"config().controlName\"\n type=\"text\" />\n\n <input matInput [formControl]=\"autocompleteInputControl\" [placeholder]=\"config().placeholder\"\n [matChipInputFor]=\"chipList\" [matAutocomplete]=\"auto\" (keydown.tab)=\"handleTab($event)\"\n [matChipInputSeparatorKeyCodes]=\"config().separatorKeyCodes\" (matChipInputTokenEnd)=\"handleTokenEnd($event)\"\n role=\"searchbox\" type=\"text\" autocapitalize=\"off\" autocorrect=\"off\" spellcheck=\"false\" (blur)=\"blurField()\"\n #autoInput />\n @if (config().fieldSuffix) {<span matSuffix>{{config().fieldSuffix}}</span>}\n @if (config().info) {<crispr-info matSuffix [info]=\"config().info \" />}\n\n </mat-chip-grid>\n <mat-autocomplete #auto=\"matAutocomplete\" [autoActiveFirstOption]=\"this.config().autoActiveFirstOption\"\n (optionSelected)=\"handleSelect($event)\" [displayWith]=\"mapToLabel\">\n @if (remainingOptions$ | async; as remainingOptions) {\n @if (remainingOptions.length > 0) {\n @for (option of remainingOptions$ | async; track option.value) {\n <mat-option [value]=\"option\">\n <crispr-option [option]=\"option\" />\n </mat-option>\n }\n } @else {\n <mat-option disabled=\"true\">\n {{config().emptyOptionsMessage}}\n </mat-option>\n }\n } @else {\n <mat-option disabled=\"true\">\n {{config().emptyOptionsMessage}}\n </mat-option>\n }\n @if (config().createChip) {\n <mat-option [value]=\"createNewSentinel\" class=\"create-new-option\">\n {{ config().createChip?.createLabel ?? '+ Create New' }}\n </mat-option>\n }\n </mat-autocomplete>\n\n @if (config().hint && (control().valid || !control().touched)) {<mat-hint>{{config().hint}}</mat-hint>}\n\n </mat-form-field>\n\n <ng-template\n cdkConnectedOverlay\n [cdkConnectedOverlayOrigin]=\"overlayOrigin()\"\n [cdkConnectedOverlayOpen]=\"showCreateForm()\"\n [cdkConnectedOverlayWidth]=\"overlayWidth\"\n [cdkConnectedOverlayPositions]=\"createFormPositions\"\n (overlayOutsideClick)=\"closeCreateForm()\">\n <div class=\"create-chip-panel mat-elevation-z4\">\n @for (field of config().createChip?.fields ?? []; track field) {\n <ng-container crisprField [config]=\"field\" [group]=\"createFormGroup\" />\n }\n <div class=\"create-chip-actions\">\n <button mat-stroked-button type=\"button\" (click)=\"closeCreateForm()\">Cancel</button>\n <button mat-raised-button color=\"primary\" type=\"button\" (click)=\"submitNewChip()\">\n {{ config().createChip?.submitLabel ?? 'Add' }}\n </button>\n </div>\n </div>\n </ng-template>\n\n</crispr-field-container>\n", styles: [".refinement-chip .image-container{width:26px;height:26px;border-radius:50%;overflow:hidden;margin:-2px 4px -2px -8px}.refinement-chip .image-container img{width:100%;height:100%;object-fit:cover}.hidden-error-directive-anchor{display:none}::ng-deep .create-chip-panel{background:var(--mat-sys-surface, white);border-radius:4px;box-shadow:0 2px 4px -1px #0003,0 4px 5px #00000024,0 1px 10px #0000001f;padding:16px;width:100%;box-sizing:border-box;display:flex;flex-direction:column;gap:8px}.create-chip-actions{display:flex;justify-content:flex-end;gap:8px;margin-top:8px}.create-new-option{font-style:italic;color:var(--mat-sys-primary, #6750a4)}\n"] }]
|
|
894
|
+
}], propDecorators: { chipInput: [{ type: i0.ViewChild, args: ['autoInput', { ...{ read: MatAutocompleteTrigger }, isSignal: true }] }], chipInputRef: [{ type: i0.ViewChild, args: ['autoInput', { isSignal: true }] }], overlayOrigin: [{ type: i0.ViewChild, args: [i0.forwardRef(() => CdkOverlayOrigin), { isSignal: true }] }] } });
|
|
895
|
+
|
|
896
|
+
var index$g = /*#__PURE__*/Object.freeze({
|
|
897
|
+
__proto__: null,
|
|
898
|
+
AutocompleteChiplistFieldComponent: AutocompleteChiplistFieldComponent
|
|
899
|
+
});
|
|
900
|
+
|
|
901
|
+
/* eslint-disable @nx/enforce-module-boundaries */
|
|
902
|
+
const defaultConfig$7 = {
|
|
903
|
+
autoActiveFirstOption: true,
|
|
904
|
+
typeDebounceTime: 500,
|
|
905
|
+
emptyOptionsMessage: DEFAULT_EMPTY_OPTIONS_MESSAGE,
|
|
906
|
+
};
|
|
907
|
+
class AutocompleteFieldComponent extends AbstractAutocompleteComponent {
|
|
908
|
+
constructor() {
|
|
909
|
+
super(...arguments);
|
|
910
|
+
this.defaultConfig = defaultConfig$7;
|
|
911
|
+
this.autoInput = viewChild('autoInput', { ...(ngDevMode ? { debugName: "autoInput" } : {}), read: MatAutocompleteTrigger });
|
|
912
|
+
this.autoInputRef = viewChild('autoInput', ...(ngDevMode ? [{ debugName: "autoInputRef" }] : []));
|
|
913
|
+
}
|
|
914
|
+
ngOnInit() {
|
|
915
|
+
super.ngOnInit();
|
|
916
|
+
this.clearFieldSubscription =
|
|
917
|
+
this.autocompleteInputControl.valueChanges.subscribe((text) => {
|
|
918
|
+
if (typeof text === 'string' && text?.trim() === '') {
|
|
919
|
+
this.control().setValue(null);
|
|
920
|
+
}
|
|
921
|
+
});
|
|
922
|
+
}
|
|
923
|
+
ngOnDestroy() {
|
|
924
|
+
this.clearFieldSubscription.unsubscribe();
|
|
925
|
+
}
|
|
926
|
+
setControlValue(value) {
|
|
927
|
+
if (this.control() && value) {
|
|
928
|
+
// sets the initial value on the control if one is passed
|
|
929
|
+
this.control().setValue(value.value || '');
|
|
930
|
+
this.autocompleteInputControl.setValue(value.label || '');
|
|
931
|
+
}
|
|
932
|
+
}
|
|
933
|
+
/**
|
|
934
|
+
* The material autocomplete defaults to displaying the options value instead of its label.
|
|
935
|
+
* We only have the value from the selected option to work with, so we have to pass the options
|
|
936
|
+
* through a function called in the template and return the function that the material displayWith
|
|
937
|
+
* input is expecting.
|
|
938
|
+
* @param options the array of options to search for option with corresponding value
|
|
939
|
+
*/
|
|
940
|
+
displayLabel(options) {
|
|
941
|
+
if (this.config().displayWith) {
|
|
942
|
+
return this.config().displayWith(options);
|
|
943
|
+
}
|
|
944
|
+
return (value) => {
|
|
945
|
+
const label = !!options && Array.isArray(options)
|
|
946
|
+
? options.find((option) => JSON.stringify(option.value) === JSON.stringify(value))?.label
|
|
947
|
+
: value;
|
|
948
|
+
return label;
|
|
949
|
+
};
|
|
950
|
+
}
|
|
951
|
+
handleSelect(event) {
|
|
952
|
+
this.control().setValue(event.option.value);
|
|
953
|
+
this.autoInputRef().nativeElement.blur();
|
|
954
|
+
}
|
|
955
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.6", ngImport: i0, type: AutocompleteFieldComponent, deps: null, target: i0.ɵɵFactoryTarget.Component }); }
|
|
956
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.6", type: AutocompleteFieldComponent, isStandalone: true, selector: "crispr-autocomplete-field", viewQueries: [{ propertyName: "autoInput", first: true, predicate: ["autoInput"], descendants: true, read: MatAutocompleteTrigger, isSignal: true }, { propertyName: "autoInputRef", first: true, predicate: ["autoInput"], descendants: true, isSignal: true }], usesInheritance: true, ngImport: i0, template: "\n<crispr-field-container\n [config]=\"config()\"\n [group]=\"group()\">\n\n <mat-form-field\n [attr.data-testid]=\"testId()\"\n [color]=\"config().color\"\n [appearance]=\"config().appearance\"\n [formGroup]=\"group()\">\n @if (config().label) {<mat-label>{{config().label}}</mat-label>}\n\n <input class=\"hidden-error-directive-anchor\" [formControlName]=\"config().controlName\" type=\"text\"/>\n\n <input\n matInput\n [formControl]=\"autocompleteInputControl\"\n [placeholder]=\"config().placeholder\"\n [matAutocomplete]=\"auto\"\n role=\"textbox\"\n type=\"textbox\"\n autocapitalize=\"off\"\n autocorrect=\"off\"\n spellcheck=\"false\"\n #autoInput>\n\n @if (config().fieldSuffix) {<span matSuffix>{{config().fieldSuffix}}</span>}\n @if (config().info) {<crispr-info matSuffix [info]=\"config().info \" />}\n\n <mat-autocomplete\n #auto=\"matAutocomplete\"\n [autoActiveFirstOption]=\"config().autoActiveFirstOption\"\n (optionSelected)=\"handleSelect($event)\"\n [displayWith]=\"displayLabel(options$ | async)\">\n\n @if (options$ | async; as options) {\n @if (options.length > 0) {\n @for (option of options; track option.value) {\n <mat-option\n [value]=\"option.value\">\n <crispr-option [option]=\"option\" />\n </mat-option>\n }\n } @else {\n <mat-option disabled=\"true\">\n {{config().emptyOptionsMessage}}\n </mat-option>\n }\n } @else {\n <mat-option disabled=\"true\">\n {{config().emptyOptionsMessage}}\n </mat-option>\n }\n </mat-autocomplete>\n\n @if (config().hint && (control().valid || !control().touched)) {<mat-hint>{{config().hint}}</mat-hint>}\n\n </mat-form-field>\n</crispr-field-container>\n", styles: [".hidden-error-directive-anchor{display:none}crispr-info[matSuffix]{margin-right:.75em}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "component", type: InfoComponent, selector: "crispr-info", inputs: ["info"] }, { kind: "component", type: OptionComponent, selector: "crispr-option", inputs: ["option"] }, { 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.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],[formArray],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i1$1.FormControlDirective, selector: "[formControl]", inputs: ["formControl", "disabled", "ngModel"], outputs: ["ngModelChange"], exportAs: ["ngForm"] }, { kind: "directive", type: i1$1.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "directive", type: i1$1.FormControlName, selector: "[formControlName]", inputs: ["formControlName", "disabled", "ngModel"], outputs: ["ngModelChange"] }, { kind: "component", type: FieldContainerComponent, selector: "crispr-field-container", inputs: ["config", "group", "inlineField"] }, { kind: "ngmodule", type: MatAutocompleteModule }, { kind: "component", type: i2$3.MatAutocomplete, selector: "mat-autocomplete", inputs: ["aria-label", "aria-labelledby", "displayWith", "autoActiveFirstOption", "autoSelectActiveOption", "requireSelection", "panelWidth", "disableRipple", "class", "hideSingleSelectionIndicator"], outputs: ["optionSelected", "opened", "closed", "optionActivated"], exportAs: ["matAutocomplete"] }, { kind: "component", type: i2$3.MatOption, selector: "mat-option", inputs: ["value", "id", "disabled"], outputs: ["onSelectionChange"], exportAs: ["matOption"] }, { kind: "directive", type: i2$3.MatAutocompleteTrigger, selector: "input[matAutocomplete], textarea[matAutocomplete]", inputs: ["matAutocomplete", "matAutocompletePosition", "matAutocompleteConnectedTo", "autocomplete", "matAutocompleteDisabled"], exportAs: ["matAutocompleteTrigger"] }, { kind: "ngmodule", type: MatInputModule }, { kind: "directive", type: i3$1.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: i2$2.MatFormField, selector: "mat-form-field", inputs: ["hideRequiredMarker", "color", "floatLabel", "appearance", "subscriptSizing", "hintLabel"], exportAs: ["matFormField"] }, { kind: "directive", type: i2$2.MatLabel, selector: "mat-label" }, { kind: "directive", type: i2$2.MatHint, selector: "mat-hint", inputs: ["align", "id"] }, { kind: "directive", type: i2$2.MatSuffix, selector: "[matSuffix], [matIconSuffix], [matTextSuffix]", inputs: ["matTextSuffix"] }, { kind: "ngmodule", type: MatOptionModule }, { kind: "directive", type: ControlErrorContainerDirective, selector: "[controlErrorContainer], [formGroup]" }, { kind: "directive", type: ControlErrorsDirective, selector: "[formControl], [formControlName]" }, { kind: "pipe", type: i1.AsyncPipe, name: "async" }], changeDetection: i0.ChangeDetectionStrategy.OnPush, preserveWhitespaces: true }); }
|
|
957
|
+
}
|
|
958
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.6", ngImport: i0, type: AutocompleteFieldComponent, decorators: [{
|
|
959
|
+
type: Component,
|
|
960
|
+
args: [{ selector: 'crispr-autocomplete-field', changeDetection: ChangeDetectionStrategy.OnPush, imports: [
|
|
961
|
+
CommonModule,
|
|
962
|
+
InfoComponent,
|
|
963
|
+
OptionComponent,
|
|
964
|
+
ReactiveFormsModule,
|
|
965
|
+
FieldContainerComponent,
|
|
966
|
+
MatAutocompleteModule,
|
|
967
|
+
MatInputModule,
|
|
968
|
+
MatOptionModule,
|
|
969
|
+
ControlErrorContainerDirective,
|
|
970
|
+
ControlErrorsDirective
|
|
971
|
+
], standalone: true, template: "\n<crispr-field-container\n [config]=\"config()\"\n [group]=\"group()\">\n\n <mat-form-field\n [attr.data-testid]=\"testId()\"\n [color]=\"config().color\"\n [appearance]=\"config().appearance\"\n [formGroup]=\"group()\">\n @if (config().label) {<mat-label>{{config().label}}</mat-label>}\n\n <input class=\"hidden-error-directive-anchor\" [formControlName]=\"config().controlName\" type=\"text\"/>\n\n <input\n matInput\n [formControl]=\"autocompleteInputControl\"\n [placeholder]=\"config().placeholder\"\n [matAutocomplete]=\"auto\"\n role=\"textbox\"\n type=\"textbox\"\n autocapitalize=\"off\"\n autocorrect=\"off\"\n spellcheck=\"false\"\n #autoInput>\n\n @if (config().fieldSuffix) {<span matSuffix>{{config().fieldSuffix}}</span>}\n @if (config().info) {<crispr-info matSuffix [info]=\"config().info \" />}\n\n <mat-autocomplete\n #auto=\"matAutocomplete\"\n [autoActiveFirstOption]=\"config().autoActiveFirstOption\"\n (optionSelected)=\"handleSelect($event)\"\n [displayWith]=\"displayLabel(options$ | async)\">\n\n @if (options$ | async; as options) {\n @if (options.length > 0) {\n @for (option of options; track option.value) {\n <mat-option\n [value]=\"option.value\">\n <crispr-option [option]=\"option\" />\n </mat-option>\n }\n } @else {\n <mat-option disabled=\"true\">\n {{config().emptyOptionsMessage}}\n </mat-option>\n }\n } @else {\n <mat-option disabled=\"true\">\n {{config().emptyOptionsMessage}}\n </mat-option>\n }\n </mat-autocomplete>\n\n @if (config().hint && (control().valid || !control().touched)) {<mat-hint>{{config().hint}}</mat-hint>}\n\n </mat-form-field>\n</crispr-field-container>\n", styles: [".hidden-error-directive-anchor{display:none}crispr-info[matSuffix]{margin-right:.75em}\n"] }]
|
|
972
|
+
}], propDecorators: { autoInput: [{ type: i0.ViewChild, args: ['autoInput', { ...{ read: MatAutocompleteTrigger }, isSignal: true }] }], autoInputRef: [{ type: i0.ViewChild, args: ['autoInput', { isSignal: true }] }] } });
|
|
973
|
+
|
|
974
|
+
var index$f = /*#__PURE__*/Object.freeze({
|
|
975
|
+
__proto__: null,
|
|
976
|
+
AutocompleteFieldComponent: AutocompleteFieldComponent
|
|
977
|
+
});
|
|
978
|
+
|
|
979
|
+
class ButtonComponent extends CrisprFieldComponent {
|
|
980
|
+
constructor() {
|
|
981
|
+
super(...arguments);
|
|
982
|
+
this.defaultConfig = {
|
|
983
|
+
buttonType: 'raised',
|
|
984
|
+
type: 'submit',
|
|
985
|
+
};
|
|
986
|
+
this.group = input(null, ...(ngDevMode ? [{ debugName: "group" }] : []));
|
|
987
|
+
}
|
|
988
|
+
get matButtonClass() {
|
|
989
|
+
return this.config().buttonType ? `mat-${this.config().buttonType}-button` : '';
|
|
990
|
+
}
|
|
991
|
+
ngOnInit() {
|
|
992
|
+
this.formValid$ = this.group().statusChanges.pipe(map((status) => status === 'VALID'), startWith(this.group().valid));
|
|
993
|
+
}
|
|
994
|
+
handleClick(group, event) {
|
|
995
|
+
if (this.config().callback) {
|
|
996
|
+
this.config().callback(group, event);
|
|
997
|
+
}
|
|
998
|
+
}
|
|
999
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.6", ngImport: i0, type: ButtonComponent, deps: null, target: i0.ɵɵFactoryTarget.Component }); }
|
|
1000
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.6", type: ButtonComponent, isStandalone: true, selector: "crispr-button", inputs: { group: { classPropertyName: "group", publicName: "group", isSignal: true, isRequired: false, transformFunction: null } }, usesInheritance: true, ngImport: i0, template: "<button\n [attr.data-testid]=\"testId()\"\n [formGroup]=\"group()\"\n mat-button\n [ngClass]=\"matButtonClass\"\n [color]=\"config().color\"\n [disabled]=\"config().disabledOnInvalidForm && !(formValid$ | async)\"\n [type]=\"config().type\"\n (click)=\"handleClick(group(), $event)\"\n [matTooltip]=\"config().info?.content\"\n [matTooltipPosition]=\"config().info?.tooltipPosition || 'below'\">\n {{ config().label }}\n @if (config().icon) {<mat-icon>{{ config().icon}}</mat-icon>}\n</button>\n\n", styles: [""], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "ngmodule", type: MatButtonModule }, { kind: "component", type: i3.MatButton, selector: " button[matButton], a[matButton], button[mat-button], button[mat-raised-button], button[mat-flat-button], button[mat-stroked-button], a[mat-button], a[mat-raised-button], a[mat-flat-button], a[mat-stroked-button] ", inputs: ["matButton"], 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: MatTooltipModule }, { kind: "directive", type: i2$1.MatTooltip, selector: "[matTooltip]", inputs: ["matTooltipPosition", "matTooltipPositionAtOrigin", "matTooltipDisabled", "matTooltipShowDelay", "matTooltipHideDelay", "matTooltipTouchGestures", "matTooltip", "matTooltipClass"], exportAs: ["matTooltip"] }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1$1.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],[formArray],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i1$1.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "directive", type: ControlErrorContainerDirective, selector: "[controlErrorContainer], [formGroup]" }, { kind: "pipe", type: i1.AsyncPipe, name: "async" }], changeDetection: i0.ChangeDetectionStrategy.OnPush, preserveWhitespaces: true }); }
|
|
1001
|
+
}
|
|
1002
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.6", ngImport: i0, type: ButtonComponent, decorators: [{
|
|
1003
|
+
type: Component,
|
|
1004
|
+
args: [{ selector: 'crispr-button', changeDetection: ChangeDetectionStrategy.OnPush, imports: [
|
|
1005
|
+
CommonModule,
|
|
1006
|
+
MatButtonModule,
|
|
1007
|
+
MatIconModule,
|
|
1008
|
+
MatTooltipModule,
|
|
1009
|
+
ReactiveFormsModule,
|
|
1010
|
+
ControlErrorContainerDirective
|
|
1011
|
+
], standalone: true, template: "<button\n [attr.data-testid]=\"testId()\"\n [formGroup]=\"group()\"\n mat-button\n [ngClass]=\"matButtonClass\"\n [color]=\"config().color\"\n [disabled]=\"config().disabledOnInvalidForm && !(formValid$ | async)\"\n [type]=\"config().type\"\n (click)=\"handleClick(group(), $event)\"\n [matTooltip]=\"config().info?.content\"\n [matTooltipPosition]=\"config().info?.tooltipPosition || 'below'\">\n {{ config().label }}\n @if (config().icon) {<mat-icon>{{ config().icon}}</mat-icon>}\n</button>\n\n" }]
|
|
1012
|
+
}], propDecorators: { group: [{ type: i0.Input, args: [{ isSignal: true, alias: "group", required: false }] }] } });
|
|
1013
|
+
|
|
1014
|
+
var index$e = /*#__PURE__*/Object.freeze({
|
|
1015
|
+
__proto__: null,
|
|
1016
|
+
ButtonComponent: ButtonComponent
|
|
1017
|
+
});
|
|
1018
|
+
|
|
1019
|
+
/* eslint-disable @nx/enforce-module-boundaries */
|
|
1020
|
+
const defaultConfig$6 = {
|
|
1021
|
+
labelPosition: 'after',
|
|
1022
|
+
inline: false,
|
|
1023
|
+
};
|
|
1024
|
+
class CheckboxFieldComponent extends CrisprControlComponent {
|
|
1025
|
+
constructor() {
|
|
1026
|
+
super(...arguments);
|
|
1027
|
+
this.defaultConfig = defaultConfig$6;
|
|
1028
|
+
}
|
|
1029
|
+
ngOnInit() {
|
|
1030
|
+
super.ngOnInit();
|
|
1031
|
+
}
|
|
1032
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.6", ngImport: i0, type: CheckboxFieldComponent, deps: null, target: i0.ɵɵFactoryTarget.Component }); }
|
|
1033
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.6", type: CheckboxFieldComponent, isStandalone: true, selector: "crispr-checkbox-field", usesInheritance: true, ngImport: i0, template: "<crispr-field-container\n [config]=\"config()\"\n [group]=\"group()\"\n [inlineField]=\"config().inline\">\n <div\n [formGroup]=\"group()\">\n <mat-checkbox\n [attr.data-testid]=\"testId()\"\n [color]=\"config().color\"\n [labelPosition]=\"config().labelPosition\"\n [formControlName]=\"config().controlName\">\n {{config().label}}\n @if (config().info) {\n <crispr-info matSuffix\n [info]=\"config().info\" />\n }\n </mat-checkbox>\n </div>\n</crispr-field-container>\n", styles: ["mat-checkbox{height:42px}\n"], dependencies: [{ kind: "component", type: InfoComponent, selector: "crispr-info", inputs: ["info"] }, { kind: "ngmodule", type: MatCheckboxModule }, { kind: "component", type: i1$3.MatCheckbox, selector: "mat-checkbox", inputs: ["aria-label", "aria-labelledby", "aria-describedby", "aria-expanded", "aria-controls", "aria-owns", "id", "required", "labelPosition", "name", "value", "disableRipple", "tabIndex", "color", "disabledInteractive", "checked", "disabled", "indeterminate"], outputs: ["change", "indeterminateChange"], exportAs: ["matCheckbox"] }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$1.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],[formArray],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i1$1.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "directive", type: i1$1.FormControlName, selector: "[formControlName]", inputs: ["formControlName", "disabled", "ngModel"], outputs: ["ngModelChange"] }, { kind: "component", type: FieldContainerComponent, selector: "crispr-field-container", inputs: ["config", "group", "inlineField"] }, { kind: "directive", type: ControlErrorContainerDirective, selector: "[controlErrorContainer], [formGroup]" }, { kind: "directive", type: ControlErrorsDirective, selector: "[formControl], [formControlName]" }], changeDetection: i0.ChangeDetectionStrategy.OnPush, preserveWhitespaces: true }); }
|
|
1034
|
+
}
|
|
1035
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.6", ngImport: i0, type: CheckboxFieldComponent, decorators: [{
|
|
1036
|
+
type: Component,
|
|
1037
|
+
args: [{ selector: 'crispr-checkbox-field', changeDetection: ChangeDetectionStrategy.OnPush, imports: [
|
|
1038
|
+
InfoComponent,
|
|
1039
|
+
MatCheckboxModule,
|
|
1040
|
+
ReactiveFormsModule,
|
|
1041
|
+
FieldContainerComponent,
|
|
1042
|
+
ControlErrorContainerDirective,
|
|
1043
|
+
ControlErrorsDirective
|
|
1044
|
+
], standalone: true, template: "<crispr-field-container\n [config]=\"config()\"\n [group]=\"group()\"\n [inlineField]=\"config().inline\">\n <div\n [formGroup]=\"group()\">\n <mat-checkbox\n [attr.data-testid]=\"testId()\"\n [color]=\"config().color\"\n [labelPosition]=\"config().labelPosition\"\n [formControlName]=\"config().controlName\">\n {{config().label}}\n @if (config().info) {\n <crispr-info matSuffix\n [info]=\"config().info\" />\n }\n </mat-checkbox>\n </div>\n</crispr-field-container>\n", styles: ["mat-checkbox{height:42px}\n"] }]
|
|
1045
|
+
}] });
|
|
1046
|
+
|
|
1047
|
+
var index$d = /*#__PURE__*/Object.freeze({
|
|
1048
|
+
__proto__: null,
|
|
1049
|
+
CheckboxFieldComponent: CheckboxFieldComponent
|
|
1050
|
+
});
|
|
1051
|
+
|
|
1052
|
+
/* eslint-disable @nx/enforce-module-boundaries */
|
|
1053
|
+
const defaultConfig$5 = {
|
|
1054
|
+
startView: 'month',
|
|
1055
|
+
};
|
|
1056
|
+
class DatepickerFieldComponent extends CrisprControlComponent {
|
|
1057
|
+
constructor() {
|
|
1058
|
+
super(...arguments);
|
|
1059
|
+
this.defaultConfig = defaultConfig$5;
|
|
1060
|
+
}
|
|
1061
|
+
ngOnInit() {
|
|
1062
|
+
super.ngOnInit();
|
|
1063
|
+
const config = this.config();
|
|
1064
|
+
this.startAt$ =
|
|
1065
|
+
config.startAt instanceof Date
|
|
1066
|
+
? of(config.startAt)
|
|
1067
|
+
: config.startAt instanceof Function
|
|
1068
|
+
? config.startAt(this.group())
|
|
1069
|
+
: of(null);
|
|
1070
|
+
this.dateClass = config?.cellClassFunction || null;
|
|
1071
|
+
this.dateClass$ = config.dateClass
|
|
1072
|
+
? config?.dateClass(this.group())
|
|
1073
|
+
: of(() => '');
|
|
1074
|
+
}
|
|
1075
|
+
ngAfterContentInit() {
|
|
1076
|
+
if (this.config().datepickerActions) {
|
|
1077
|
+
this.calendarFooterPortal = new ComponentPortal(this.config().datepickerActions);
|
|
1078
|
+
}
|
|
1079
|
+
}
|
|
1080
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.6", ngImport: i0, type: DatepickerFieldComponent, deps: null, target: i0.ɵɵFactoryTarget.Component }); }
|
|
1081
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.6", type: DatepickerFieldComponent, isStandalone: true, selector: "crispr-datepicker-field", usesInheritance: true, ngImport: i0, template: "<crispr-field-container\n [config]=\"config()\"\n [group]=\"group()\">\n <mat-form-field\n [attr.data-testid]=\"testId()\"\n [color]=\"config().color\"\n [appearance]=\"config().appearance\"\n [formGroup]=\"group()\">\n @if (config().label) {<mat-label>{{config().label}}</mat-label>}\n <input\n matInput\n [formControlName]=\"config().controlName\"\n [min]=\"config().min\"\n [max]=\"config().max\"\n [matDatepicker]=\"picker\"\n [matDatepickerFilter]=\"config().datepickerFilter\">\n <mat-datepicker-toggle matSuffix [for]=\"picker\" />\n <mat-datepicker\n [touchUi]=\"config().touchUi\"\n [startView]=\"config().startView\"\n [startAt]=\"startAt$ | async\"\n [dateClass]=\"dateClass || (dateClass$ | async)\"\n #picker>\n @if (calendarFooterPortal) {\n <mat-datepicker-actions>\n <ng-template [cdkPortalOutlet]=\"calendarFooterPortal\" />\n </mat-datepicker-actions>\n }\n </mat-datepicker>\n\n @if (config().hint && (control().valid || !control().touched)) {<mat-hint>{{config().hint}}</mat-hint>}\n\n </mat-form-field>\n</crispr-field-container>\n\n", styles: [""], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "component", type: FieldContainerComponent, selector: "crispr-field-container", inputs: ["config", "group", "inlineField"] }, { kind: "ngmodule", type: MatDatepickerModule }, { kind: "component", type: i1$4.MatDatepicker, selector: "mat-datepicker", exportAs: ["matDatepicker"] }, { kind: "directive", type: i1$4.MatDatepickerInput, selector: "input[matDatepicker]", inputs: ["matDatepicker", "min", "max", "matDatepickerFilter"], exportAs: ["matDatepickerInput"] }, { kind: "component", type: i1$4.MatDatepickerToggle, selector: "mat-datepicker-toggle", inputs: ["for", "tabIndex", "aria-label", "disabled", "disableRipple"], exportAs: ["matDatepickerToggle"] }, { kind: "component", type: i1$4.MatDatepickerActions, selector: "mat-datepicker-actions, mat-date-range-picker-actions" }, { kind: "ngmodule", type: MatFormFieldModule }, { kind: "component", type: i2$2.MatFormField, selector: "mat-form-field", inputs: ["hideRequiredMarker", "color", "floatLabel", "appearance", "subscriptSizing", "hintLabel"], exportAs: ["matFormField"] }, { kind: "directive", type: i2$2.MatLabel, selector: "mat-label" }, { kind: "directive", type: i2$2.MatHint, selector: "mat-hint", inputs: ["align", "id"] }, { kind: "directive", type: i2$2.MatSuffix, selector: "[matSuffix], [matIconSuffix], [matTextSuffix]", inputs: ["matTextSuffix"] }, { 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.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],[formArray],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i1$1.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "directive", type: i1$1.FormControlName, selector: "[formControlName]", inputs: ["formControlName", "disabled", "ngModel"], outputs: ["ngModelChange"] }, { kind: "ngmodule", type: MatInputModule }, { kind: "directive", type: i3$1.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: "ngmodule", type: MatDateFnsModule }, { kind: "ngmodule", type: PortalModule }, { kind: "directive", type: i5$1.CdkPortalOutlet, selector: "[cdkPortalOutlet]", inputs: ["cdkPortalOutlet"], outputs: ["attached"], exportAs: ["cdkPortalOutlet"] }, { kind: "directive", type: ControlErrorContainerDirective, selector: "[controlErrorContainer], [formGroup]" }, { kind: "directive", type: ControlErrorsDirective, selector: "[formControl], [formControlName]" }, { kind: "pipe", type: i1.AsyncPipe, name: "async" }], changeDetection: i0.ChangeDetectionStrategy.OnPush, preserveWhitespaces: true }); }
|
|
1082
|
+
}
|
|
1083
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.6", ngImport: i0, type: DatepickerFieldComponent, decorators: [{
|
|
1084
|
+
type: Component,
|
|
1085
|
+
args: [{ selector: 'crispr-datepicker-field', changeDetection: ChangeDetectionStrategy.OnPush, imports: [
|
|
1086
|
+
CommonModule,
|
|
1087
|
+
FieldContainerComponent,
|
|
1088
|
+
MatDatepickerModule,
|
|
1089
|
+
MatFormFieldModule,
|
|
1090
|
+
ReactiveFormsModule,
|
|
1091
|
+
MatInputModule,
|
|
1092
|
+
MatDateFnsModule,
|
|
1093
|
+
PortalModule,
|
|
1094
|
+
ControlErrorContainerDirective,
|
|
1095
|
+
ControlErrorsDirective
|
|
1096
|
+
], standalone: true, template: "<crispr-field-container\n [config]=\"config()\"\n [group]=\"group()\">\n <mat-form-field\n [attr.data-testid]=\"testId()\"\n [color]=\"config().color\"\n [appearance]=\"config().appearance\"\n [formGroup]=\"group()\">\n @if (config().label) {<mat-label>{{config().label}}</mat-label>}\n <input\n matInput\n [formControlName]=\"config().controlName\"\n [min]=\"config().min\"\n [max]=\"config().max\"\n [matDatepicker]=\"picker\"\n [matDatepickerFilter]=\"config().datepickerFilter\">\n <mat-datepicker-toggle matSuffix [for]=\"picker\" />\n <mat-datepicker\n [touchUi]=\"config().touchUi\"\n [startView]=\"config().startView\"\n [startAt]=\"startAt$ | async\"\n [dateClass]=\"dateClass || (dateClass$ | async)\"\n #picker>\n @if (calendarFooterPortal) {\n <mat-datepicker-actions>\n <ng-template [cdkPortalOutlet]=\"calendarFooterPortal\" />\n </mat-datepicker-actions>\n }\n </mat-datepicker>\n\n @if (config().hint && (control().valid || !control().touched)) {<mat-hint>{{config().hint}}</mat-hint>}\n\n </mat-form-field>\n</crispr-field-container>\n\n" }]
|
|
1097
|
+
}] });
|
|
1098
|
+
|
|
1099
|
+
var index$c = /*#__PURE__*/Object.freeze({
|
|
1100
|
+
__proto__: null,
|
|
1101
|
+
DatepickerFieldComponent: DatepickerFieldComponent
|
|
1102
|
+
});
|
|
1103
|
+
|
|
1104
|
+
class DividerComponent extends CrisprFieldComponent {
|
|
1105
|
+
constructor() {
|
|
1106
|
+
super(...arguments);
|
|
1107
|
+
// TODO: pull in type after configs are better organized
|
|
1108
|
+
this.defaultConfig = {};
|
|
1109
|
+
}
|
|
1110
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.6", ngImport: i0, type: DividerComponent, deps: null, target: i0.ɵɵFactoryTarget.Component }); }
|
|
1111
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "21.1.6", type: DividerComponent, isStandalone: true, selector: "crispr-divider", usesInheritance: true, ngImport: i0, template: "<mat-divider [ngClass]=\"config().classes\" />", styles: [":host{display:block;padding:1em 0}::ng-deep .mat-divider.mat-divider-horizontal{position:relative}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "ngmodule", type: MatDividerModule }, { kind: "component", type: i2$4.MatDivider, selector: "mat-divider", inputs: ["vertical", "inset"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush, preserveWhitespaces: true }); }
|
|
1112
|
+
}
|
|
1113
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.6", ngImport: i0, type: DividerComponent, decorators: [{
|
|
1114
|
+
type: Component,
|
|
1115
|
+
args: [{ selector: 'crispr-divider', changeDetection: ChangeDetectionStrategy.OnPush, imports: [CommonModule, MatDividerModule], standalone: true, template: "<mat-divider [ngClass]=\"config().classes\" />", styles: [":host{display:block;padding:1em 0}::ng-deep .mat-divider.mat-divider-horizontal{position:relative}\n"] }]
|
|
1116
|
+
}] });
|
|
1117
|
+
|
|
1118
|
+
var index$b = /*#__PURE__*/Object.freeze({
|
|
1119
|
+
__proto__: null,
|
|
1120
|
+
DividerComponent: DividerComponent
|
|
1121
|
+
});
|
|
1122
|
+
|
|
1123
|
+
/* eslint-disable @nx/enforce-module-boundaries */
|
|
1124
|
+
class ImageUploadFieldComponent extends CrisprControlComponent {
|
|
1125
|
+
/** Compresses (when configured) and wraps a file. Never rejects, failures are returned. */
|
|
1126
|
+
async processFile(file, signal) {
|
|
1127
|
+
if (!file || !(file instanceof File))
|
|
1128
|
+
return { file: null };
|
|
1129
|
+
try {
|
|
1130
|
+
const { compressImage, targetCompressedImageFileSizeMb, minCompressionThresholdMb, maxWidthOrHeight, useWebWorker, } = this.config();
|
|
1131
|
+
const fileSizeMb = convertBytesToMb(file.size);
|
|
1132
|
+
const shouldCompress = compressImage &&
|
|
1133
|
+
targetCompressedImageFileSizeMb < fileSizeMb &&
|
|
1134
|
+
minCompressionThresholdMb < fileSizeMb;
|
|
1135
|
+
let processed = file;
|
|
1136
|
+
if (shouldCompress) {
|
|
1137
|
+
const options = {
|
|
1138
|
+
// Use maxWidthOrHeight if it was provided, else use target compressed size
|
|
1139
|
+
...(maxWidthOrHeight
|
|
1140
|
+
? { maxWidthOrHeight }
|
|
1141
|
+
: { maxSizeMB: targetCompressedImageFileSizeMb }),
|
|
1142
|
+
useWebWorker,
|
|
1143
|
+
maxIterations: 30,
|
|
1144
|
+
signal,
|
|
1145
|
+
onProgress: (progress) => {
|
|
1146
|
+
// ignore progress from a compression that a newer selection has superseded
|
|
1147
|
+
if (this.destroyed || this.latestFile !== file)
|
|
1148
|
+
return;
|
|
1149
|
+
this.progress.set(progress);
|
|
1150
|
+
},
|
|
1151
|
+
};
|
|
1152
|
+
processed = await imageCompression(file, options);
|
|
1153
|
+
}
|
|
1154
|
+
const { type, name, lastModified } = processed;
|
|
1155
|
+
return {
|
|
1156
|
+
file: new File([processed], this.config().fileName || name, {
|
|
1157
|
+
type,
|
|
1158
|
+
lastModified,
|
|
1159
|
+
}),
|
|
1160
|
+
};
|
|
1161
|
+
}
|
|
1162
|
+
catch {
|
|
1163
|
+
return { file: null, failed: file };
|
|
1164
|
+
}
|
|
1165
|
+
}
|
|
1166
|
+
async toImageUrl(file) {
|
|
1167
|
+
try {
|
|
1168
|
+
const compressedStringImage = await imageCompression.getDataUrlFromFile(file);
|
|
1169
|
+
return this.domSanitizer.bypassSecurityTrustResourceUrl(compressedStringImage);
|
|
1170
|
+
}
|
|
1171
|
+
catch {
|
|
1172
|
+
return null;
|
|
1173
|
+
}
|
|
1174
|
+
}
|
|
1175
|
+
constructor() {
|
|
1176
|
+
super();
|
|
1177
|
+
this.ngControl = inject(NgControl, { optional: true, self: true });
|
|
1178
|
+
this.domSanitizer = inject(DomSanitizer);
|
|
1179
|
+
this.defaultConfig = {
|
|
1180
|
+
// allowMultipleFiles: false,
|
|
1181
|
+
acceptedTypes: 'image/*',
|
|
1182
|
+
dropZoneText: 'DROP FILE HERE',
|
|
1183
|
+
selectFilesButtonType: 'button',
|
|
1184
|
+
selectButtonText: 'SELECT',
|
|
1185
|
+
showClearFilesButton: true,
|
|
1186
|
+
clearFilesButtonText: 'CLEAR',
|
|
1187
|
+
resetFilesButtonText: 'RESET',
|
|
1188
|
+
imagePreviewHeight: '250px',
|
|
1189
|
+
compressImage: false,
|
|
1190
|
+
targetCompressedImageFileSizeMb: 0.4,
|
|
1191
|
+
minCompressionThresholdMb: 0.6,
|
|
1192
|
+
useWebWorker: true,
|
|
1193
|
+
};
|
|
1194
|
+
this.inputImageFileSubject = new Subject();
|
|
1195
|
+
this.inputImageFile$ = this.inputImageFileSubject
|
|
1196
|
+
.asObservable()
|
|
1197
|
+
.pipe(shareReplay(1));
|
|
1198
|
+
/**
|
|
1199
|
+
* The compression progress (0-100), undefined when nothing is compressing
|
|
1200
|
+
*/
|
|
1201
|
+
this.progress = signal(undefined, ...(ngDevMode ? [{ debugName: "progress" }] : []));
|
|
1202
|
+
/**
|
|
1203
|
+
* Whether the field is idle, processing (compressing) a selected image, or failed to process it.
|
|
1204
|
+
* While `processing` or `failed` the control is invalid.
|
|
1205
|
+
*/
|
|
1206
|
+
this.processingState = signal('idle', ...(ngDevMode ? [{ debugName: "processingState" }] : []));
|
|
1207
|
+
/** The message describing why processing failed, null unless `processingState` is `failed` */
|
|
1208
|
+
this.failureMessage = signal(null, ...(ngDevMode ? [{ debugName: "failureMessage" }] : []));
|
|
1209
|
+
/** The name of the file currently being processed */
|
|
1210
|
+
this.processingFileName = signal('', ...(ngDevMode ? [{ debugName: "processingFileName" }] : []));
|
|
1211
|
+
/**
|
|
1212
|
+
* The most recently selected file (null once cleared/reset). Used to ignore progress
|
|
1213
|
+
* reports from compressions that were superseded by a newer selection.
|
|
1214
|
+
*/
|
|
1215
|
+
this.latestFile = null;
|
|
1216
|
+
/** Set on destroy so that an in-flight compression can no longer touch the control or call back */
|
|
1217
|
+
this.destroyed = false;
|
|
1218
|
+
/**
|
|
1219
|
+
* Makes the control invalid while an image is processing or after processing failed.
|
|
1220
|
+
* Implemented as a validator because Angular clears manually set errors whenever
|
|
1221
|
+
* the control's validity is recalculated.
|
|
1222
|
+
*/
|
|
1223
|
+
this.imageProcessingValidator = () =>
|
|
1224
|
+
// untracked: this runs inside effects (e.g. setControlValue) which must not depend on the state
|
|
1225
|
+
untracked(() => {
|
|
1226
|
+
switch (this.processingState()) {
|
|
1227
|
+
case 'processing':
|
|
1228
|
+
return { imageProcessing: true };
|
|
1229
|
+
case 'failed':
|
|
1230
|
+
return {
|
|
1231
|
+
imageProcessingFailed: {
|
|
1232
|
+
message: this.failureMessage() ||
|
|
1233
|
+
'The image could not be processed. Please try a different image.',
|
|
1234
|
+
},
|
|
1235
|
+
};
|
|
1236
|
+
default:
|
|
1237
|
+
return null;
|
|
1238
|
+
}
|
|
1239
|
+
});
|
|
1240
|
+
/**
|
|
1241
|
+
* Compresses (when configured) and wraps each selected file. Failures are caught inside the
|
|
1242
|
+
* switchMap so that one bad file never errors out, and so kills, the stream.
|
|
1243
|
+
*/
|
|
1244
|
+
this.compressedFile$ = this.inputImageFile$.pipe(
|
|
1245
|
+
// Each selection gets its own AbortController. switchMap unsubscribing (new selection, clear,
|
|
1246
|
+
// reset) runs the teardown below which aborts the superseded compression, and an aborted
|
|
1247
|
+
// compression never emits so it can not produce a `failed` state.
|
|
1248
|
+
switchMap((file) => new Observable((subscriber) => {
|
|
1249
|
+
const controller = new AbortController();
|
|
1250
|
+
let finished = false;
|
|
1251
|
+
this.processFile(file, controller.signal).then((result) => {
|
|
1252
|
+
if (controller.signal.aborted)
|
|
1253
|
+
return;
|
|
1254
|
+
finished = true;
|
|
1255
|
+
subscriber.next(result);
|
|
1256
|
+
subscriber.complete();
|
|
1257
|
+
});
|
|
1258
|
+
return () => {
|
|
1259
|
+
if (!finished)
|
|
1260
|
+
controller.abort();
|
|
1261
|
+
};
|
|
1262
|
+
})),
|
|
1263
|
+
// unsubscribing from the switchMap on destroy aborts any in-flight compression
|
|
1264
|
+
takeUntilDestroyed(),
|
|
1265
|
+
// switchMap has already discarded results of superseded selections, so state
|
|
1266
|
+
// transitions made here always belong to the latest selection
|
|
1267
|
+
tap(({ file, failed }) => {
|
|
1268
|
+
if (this.destroyed)
|
|
1269
|
+
return;
|
|
1270
|
+
const control = this.control();
|
|
1271
|
+
if (failed) {
|
|
1272
|
+
this.progress.set(undefined);
|
|
1273
|
+
this.setProcessingState('failed', `Unable to process "${failed.name}". Please try a different image.`, () => control.updateValueAndValidity());
|
|
1274
|
+
}
|
|
1275
|
+
else if (file) {
|
|
1276
|
+
this.progress.set(undefined);
|
|
1277
|
+
// only set the value if there is a file because resetToInitialValue sets the control value
|
|
1278
|
+
// to initial value and clears the compressed image, so setting it to null here would undo that
|
|
1279
|
+
this.setProcessingState('idle', null, () => control.setValue(file));
|
|
1280
|
+
this.config().filesChanged?.(this.group(), file);
|
|
1281
|
+
}
|
|
1282
|
+
if (file || this.value()) {
|
|
1283
|
+
control.markAsTouched();
|
|
1284
|
+
}
|
|
1285
|
+
}), map(({ file }) => file), shareReplay(1));
|
|
1286
|
+
this.compressedImageUrlString$ = this.compressedFile$.pipe(
|
|
1287
|
+
// null is emitted synchronously whenever a file starts (or no file is left) so that a
|
|
1288
|
+
// resubscribing template (shareReplay replays the latest value) can never see the url of a
|
|
1289
|
+
// previous file, or of one that has since been cleared or failed, while the new url is pending
|
|
1290
|
+
switchMap((file) => file ? concat(of(null), from(this.toImageUrl(file))) : of(null)), shareReplay(1));
|
|
1291
|
+
this.fileInputRef = viewChild('fileInput', ...(ngDevMode ? [{ debugName: "fileInputRef" }] : []));
|
|
1292
|
+
/**
|
|
1293
|
+
* Whether the hint is shown, i.e. the control is valid or has not been touched. Mirrors the control's
|
|
1294
|
+
* state into a signal so that changes made asynchronously (e.g. when image processing finishes)
|
|
1295
|
+
* re-render this OnPush/zoneless component.
|
|
1296
|
+
*/
|
|
1297
|
+
this.hintVisible = signal(true, ...(ngDevMode ? [{ debugName: "hintVisible" }] : []));
|
|
1298
|
+
const ngControl = this.ngControl;
|
|
1299
|
+
if (this.ngControl != null) {
|
|
1300
|
+
// Setting the value accessor directly (instead of using
|
|
1301
|
+
// the providers) to avoid running into a circular import.
|
|
1302
|
+
ngControl.valueAccessor = this;
|
|
1303
|
+
}
|
|
1304
|
+
}
|
|
1305
|
+
ngOnInit() {
|
|
1306
|
+
super.ngOnInit();
|
|
1307
|
+
const control = this.control();
|
|
1308
|
+
control.addValidators(this.imageProcessingValidator);
|
|
1309
|
+
const updateHintVisible = () => this.hintVisible.set(control.valid || !control.touched);
|
|
1310
|
+
updateHintVisible();
|
|
1311
|
+
control.events.pipe(takeUntilDestroyed(this.destroyRef)).subscribe(updateHintVisible);
|
|
1312
|
+
this.destroyRef.onDestroy(() => {
|
|
1313
|
+
this.destroyed = true;
|
|
1314
|
+
this.inputImageFileSubject.complete();
|
|
1315
|
+
control.removeValidators(this.imageProcessingValidator);
|
|
1316
|
+
// a destroyed field must not leave the form invalid because of a processing/failed state
|
|
1317
|
+
control.updateValueAndValidity();
|
|
1318
|
+
if (this.processingState() !== 'idle') {
|
|
1319
|
+
this.processingState.set('idle');
|
|
1320
|
+
this.failureMessage.set(null);
|
|
1321
|
+
this.config().processingStateChange?.('idle', this.group());
|
|
1322
|
+
}
|
|
1323
|
+
});
|
|
1324
|
+
this.imageSrcUrl$ = combineLatest([
|
|
1325
|
+
this.compressedImageUrlString$.pipe(startWith(null)),
|
|
1326
|
+
// defer: start with the control's value at subscribe time, the template resubscribes after
|
|
1327
|
+
// every processing phase and must not replay the value the control had at init
|
|
1328
|
+
defer(() => this.control().valueChanges.pipe(startWith(this.control().value), map((controlValue) => {
|
|
1329
|
+
if (!controlValue)
|
|
1330
|
+
return null;
|
|
1331
|
+
if (controlValue instanceof File)
|
|
1332
|
+
return null;
|
|
1333
|
+
return (this.config().mapInputValueToUrl?.(controlValue) ||
|
|
1334
|
+
controlValue);
|
|
1335
|
+
})))
|
|
1336
|
+
]).pipe(map(([compressedImageUrlString, controlUrl]) => {
|
|
1337
|
+
return compressedImageUrlString || controlUrl || null;
|
|
1338
|
+
}));
|
|
1339
|
+
this.disabled$ = this.config().disabledCallback
|
|
1340
|
+
? this.config().disabledCallback(this.group())
|
|
1341
|
+
: of(false);
|
|
1342
|
+
}
|
|
1343
|
+
filesChanged(files) {
|
|
1344
|
+
if (files && files.length > 0) {
|
|
1345
|
+
const imageFile = files[0];
|
|
1346
|
+
this.latestFile = imageFile;
|
|
1347
|
+
this.progress.set(undefined);
|
|
1348
|
+
this.processingFileName.set(imageFile.name);
|
|
1349
|
+
// We clear the control here and flag the field as processing so that the control is invalid
|
|
1350
|
+
// while compressing, the input pipeline will take care of setting the processed value
|
|
1351
|
+
this.setProcessingState('processing', null, () => {
|
|
1352
|
+
this.control().patchValue(null);
|
|
1353
|
+
this.control().markAsTouched();
|
|
1354
|
+
});
|
|
1355
|
+
this.inputImageFileSubject.next(imageFile);
|
|
1356
|
+
}
|
|
1357
|
+
}
|
|
1358
|
+
clearFileInput() {
|
|
1359
|
+
this.latestFile = null;
|
|
1360
|
+
this.inputImageFileSubject.next(null);
|
|
1361
|
+
this.fileInputRef().nativeElement.value = '';
|
|
1362
|
+
this.progress.set(undefined);
|
|
1363
|
+
this.setProcessingState('idle', null, () => {
|
|
1364
|
+
this.control().patchValue(null);
|
|
1365
|
+
this.control().markAsTouched();
|
|
1366
|
+
});
|
|
1367
|
+
}
|
|
1368
|
+
resetToInitialValue(initialValue) {
|
|
1369
|
+
this.latestFile = null;
|
|
1370
|
+
this.inputImageFileSubject.next(null);
|
|
1371
|
+
this.progress.set(undefined);
|
|
1372
|
+
this.setProcessingState('idle', null, () => this.control().patchValue(initialValue));
|
|
1373
|
+
}
|
|
1374
|
+
/**
|
|
1375
|
+
* Transitions the processing state. `applyValue` updates the control (which revalidates it),
|
|
1376
|
+
* so validity is current before `processingStateChange` is called. The callback only fires
|
|
1377
|
+
* when the state actually changes.
|
|
1378
|
+
*/
|
|
1379
|
+
setProcessingState(state, message, applyValue) {
|
|
1380
|
+
const changed = this.processingState() !== state || this.failureMessage() !== message;
|
|
1381
|
+
this.processingState.set(state);
|
|
1382
|
+
this.failureMessage.set(message);
|
|
1383
|
+
applyValue();
|
|
1384
|
+
if (changed) {
|
|
1385
|
+
this.config().processingStateChange?.(state, this.group());
|
|
1386
|
+
}
|
|
1387
|
+
}
|
|
1388
|
+
writeValue(value) { }
|
|
1389
|
+
registerOnChange(fn) {
|
|
1390
|
+
this.onChange = fn;
|
|
1391
|
+
}
|
|
1392
|
+
registerOnTouched(fn) { }
|
|
1393
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.6", ngImport: i0, type: ImageUploadFieldComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
1394
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.6", type: ImageUploadFieldComponent, isStandalone: true, selector: "crispr-image-upload-field", viewQueries: [{ propertyName: "fileInputRef", first: true, predicate: ["fileInput"], descendants: true, isSignal: true }], usesInheritance: true, ngImport: i0, template: "<crispr-field-container\n [config]=\"config()\"\n [group]=\"group()\">\n\n <mat-form-field floatLabel=\"always\"\n [attr.data-testid]=\"testId()\"\n [color]=\"config().color\"\n [appearance]=\"config().appearance\"\n [formGroup]=\"group()\">\n <mat-label>{{config().label}}</mat-label>\n <input [formControlName]=\"config().controlName\" matInput type=\"text\"/>\n <input\n #fileInput\n type=\"file\"\n [accept]=\"config().acceptedTypes\"\n (change)=\"filesChanged($event.target.files)\"/>\n <div class=\"ui-container\">\n\n <div class=\"image-preview\" [style.height]=\"config().imagePreviewHeight\">\n @if (processingState() === 'processing') {\n <crispr-selected-file data-testid=\"image-processing\"\n [color]=\"config().color === 'primary' ? 'accent' : 'primary'\"\n [fileName]=\"(config().compressImage ? 'Compressing: ' : 'Processing: ') + processingFileName()\"\n [showProgress]=\"true\"\n [progress]=\"progress()\" />\n } @else {\n <!-- <img *ngIf=\"(initialUrlString$ | async) as initialUrlString;\" [src]=\"initialUrlString\" alt=\"\"> -->\n @if (imageSrcUrl$ | async; as srcUrl) {\n <img [src]=\"srcUrl\" alt=\"\">\n } @else {\n <div class=\"drop-container\"\n [crisprFileDropzone]=\"!(disabled$ | async)\"\n (fileDrop)=\"filesChanged($event)\">\n <mat-icon class=\"drop-icon\" inline=\"true\">cloud_upload</mat-icon>\n <h2 class=\"mat-h2\">{{config().dropZoneText}}</h2>\n </div>\n }\n @if (processingState() === 'failed') {\n <p class=\"processing-failed mat-body-2\" role=\"alert\" data-testid=\"image-processing-failed\">\n <mat-icon inline=\"true\">error</mat-icon>\n {{failureMessage()}}\n </p>\n }\n }\n\n </div>\n\n\n\n <!-- </ng-template> -->\n @if (true) {\n <div class=\"button-container\">\n <button\n mat-button\n (click)=\"clearFileInput()\"\n type=\"button\"\n [disabled]=\"(disabled$ | async)\"\n aria-label=\"Remove Selected File(s)\">\n <mat-icon>delete</mat-icon>\n {{config().clearFilesButtonText}}\n </button>\n @if (value()) {\n <button\n mat-button\n (click)=\"resetToInitialValue(value())\"\n type=\"button\"\n [disabled]=\"(disabled$ | async)\"\n aria-label=\"Remove Selected File(s)\">\n <mat-icon>replay</mat-icon>\n {{config().resetFilesButtonText}}\n </button>\n }\n <button\n mat-button\n type=\"button\"\n color=\"primary\"\n [disabled]=\"(disabled$ | async)\"\n (click)=\"fileInput.click()\"\n [attr.aria-label]=\"config().controlName\">\n <mat-icon>\n camera\n </mat-icon>\n SELECT\n </button>\n </div>\n }\n </div>\n\n @if (config().hint && hintVisible()) {<mat-hint>{{config().hint}}</mat-hint>}\n\n</mat-form-field>\n\n</crispr-field-container>\n", styles: [":host ::ng-deep .mat-mdc-form-field .mat-mdc-form-field-infix{padding-top:0}:host ::ng-deep .mat-mdc-form-field .mat-button-wrapper{display:flex;flex-direction:column;align-items:center;padding-top:8px}:host .ui-container{display:flex;flex-direction:column}:host .ui-container .image-preview{display:flex;flex-direction:column;align-content:space-between;justify-content:center;align-items:center;margin:8px 0;flex-grow:1}:host .ui-container .image-preview crispr-selected-file{width:100%}:host .ui-container .image-preview .drop-container{display:flex;box-sizing:border-box;flex-direction:column;align-items:center;border:3px dashed gray;height:100%;width:100%;align-content:center;justify-content:center;border-radius:8px}:host .ui-container .image-preview .drop-container .drop-icon{height:32px;width:32px;font-size:36px}:host .ui-container .image-preview .drop-container .mat-h2{margin:0}:host .ui-container .image-preview .processing-failed{display:flex;align-items:center;gap:4px;margin:8px 0 0;color:var(--mat-sys-error, #b3261e)}:host .ui-container .image-preview img{height:inherit;max-width:100%}:host .ui-container .button-container{display:flex;flex-direction:var(--image-upload-button-container-direction);justify-content:flex-end}:host .ui-container .button-container button{margin-left:8px}:host input{position:absolute;max-height:0;visibility:hidden}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "component", type: FieldContainerComponent, selector: "crispr-field-container", inputs: ["config", "group", "inlineField"] }, { 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.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],[formArray],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i1$1.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "directive", type: i1$1.FormControlName, selector: "[formControlName]", inputs: ["formControlName", "disabled", "ngModel"], outputs: ["ngModelChange"] }, { kind: "ngmodule", type: MatIconModule }, { kind: "component", type: i2.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "ngmodule", type: MatButtonModule }, { kind: "component", type: i3.MatButton, selector: " button[matButton], a[matButton], button[mat-button], button[mat-raised-button], button[mat-flat-button], button[mat-stroked-button], a[mat-button], a[mat-raised-button], a[mat-flat-button], a[mat-stroked-button] ", inputs: ["matButton"], exportAs: ["matButton", "matAnchor"] }, { kind: "ngmodule", type: MatFormFieldModule }, { kind: "component", type: i2$2.MatFormField, selector: "mat-form-field", inputs: ["hideRequiredMarker", "color", "floatLabel", "appearance", "subscriptSizing", "hintLabel"], exportAs: ["matFormField"] }, { kind: "directive", type: i2$2.MatLabel, selector: "mat-label" }, { kind: "directive", type: i2$2.MatHint, selector: "mat-hint", inputs: ["align", "id"] }, { kind: "ngmodule", type: MatInputModule }, { kind: "directive", type: i3$1.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: SelectedFileComponent, selector: "crispr-selected-file", inputs: ["fileName", "progress", "showProgress", "color"] }, { kind: "directive", type: FileDropzoneDirective, selector: "[crisprFileDropzone]", inputs: ["crisprFileDropzone"], outputs: ["fileDrop"] }, { kind: "directive", type: ControlErrorContainerDirective, selector: "[controlErrorContainer], [formGroup]" }, { kind: "directive", type: ControlErrorsDirective, selector: "[formControl], [formControlName]" }, { kind: "pipe", type: i1.AsyncPipe, name: "async" }], changeDetection: i0.ChangeDetectionStrategy.OnPush, preserveWhitespaces: true }); }
|
|
1395
|
+
}
|
|
1396
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.6", ngImport: i0, type: ImageUploadFieldComponent, decorators: [{
|
|
1397
|
+
type: Component,
|
|
1398
|
+
args: [{ selector: 'crispr-image-upload-field', changeDetection: ChangeDetectionStrategy.OnPush, imports: [
|
|
1399
|
+
CommonModule,
|
|
1400
|
+
FieldContainerComponent,
|
|
1401
|
+
ReactiveFormsModule,
|
|
1402
|
+
MatIconModule,
|
|
1403
|
+
MatButtonModule,
|
|
1404
|
+
MatFormFieldModule,
|
|
1405
|
+
MatInputModule,
|
|
1406
|
+
SelectedFileComponent,
|
|
1407
|
+
FileDropzoneDirective,
|
|
1408
|
+
ControlErrorContainerDirective,
|
|
1409
|
+
ControlErrorsDirective
|
|
1410
|
+
], standalone: true, template: "<crispr-field-container\n [config]=\"config()\"\n [group]=\"group()\">\n\n <mat-form-field floatLabel=\"always\"\n [attr.data-testid]=\"testId()\"\n [color]=\"config().color\"\n [appearance]=\"config().appearance\"\n [formGroup]=\"group()\">\n <mat-label>{{config().label}}</mat-label>\n <input [formControlName]=\"config().controlName\" matInput type=\"text\"/>\n <input\n #fileInput\n type=\"file\"\n [accept]=\"config().acceptedTypes\"\n (change)=\"filesChanged($event.target.files)\"/>\n <div class=\"ui-container\">\n\n <div class=\"image-preview\" [style.height]=\"config().imagePreviewHeight\">\n @if (processingState() === 'processing') {\n <crispr-selected-file data-testid=\"image-processing\"\n [color]=\"config().color === 'primary' ? 'accent' : 'primary'\"\n [fileName]=\"(config().compressImage ? 'Compressing: ' : 'Processing: ') + processingFileName()\"\n [showProgress]=\"true\"\n [progress]=\"progress()\" />\n } @else {\n <!-- <img *ngIf=\"(initialUrlString$ | async) as initialUrlString;\" [src]=\"initialUrlString\" alt=\"\"> -->\n @if (imageSrcUrl$ | async; as srcUrl) {\n <img [src]=\"srcUrl\" alt=\"\">\n } @else {\n <div class=\"drop-container\"\n [crisprFileDropzone]=\"!(disabled$ | async)\"\n (fileDrop)=\"filesChanged($event)\">\n <mat-icon class=\"drop-icon\" inline=\"true\">cloud_upload</mat-icon>\n <h2 class=\"mat-h2\">{{config().dropZoneText}}</h2>\n </div>\n }\n @if (processingState() === 'failed') {\n <p class=\"processing-failed mat-body-2\" role=\"alert\" data-testid=\"image-processing-failed\">\n <mat-icon inline=\"true\">error</mat-icon>\n {{failureMessage()}}\n </p>\n }\n }\n\n </div>\n\n\n\n <!-- </ng-template> -->\n @if (true) {\n <div class=\"button-container\">\n <button\n mat-button\n (click)=\"clearFileInput()\"\n type=\"button\"\n [disabled]=\"(disabled$ | async)\"\n aria-label=\"Remove Selected File(s)\">\n <mat-icon>delete</mat-icon>\n {{config().clearFilesButtonText}}\n </button>\n @if (value()) {\n <button\n mat-button\n (click)=\"resetToInitialValue(value())\"\n type=\"button\"\n [disabled]=\"(disabled$ | async)\"\n aria-label=\"Remove Selected File(s)\">\n <mat-icon>replay</mat-icon>\n {{config().resetFilesButtonText}}\n </button>\n }\n <button\n mat-button\n type=\"button\"\n color=\"primary\"\n [disabled]=\"(disabled$ | async)\"\n (click)=\"fileInput.click()\"\n [attr.aria-label]=\"config().controlName\">\n <mat-icon>\n camera\n </mat-icon>\n SELECT\n </button>\n </div>\n }\n </div>\n\n @if (config().hint && hintVisible()) {<mat-hint>{{config().hint}}</mat-hint>}\n\n</mat-form-field>\n\n</crispr-field-container>\n", styles: [":host ::ng-deep .mat-mdc-form-field .mat-mdc-form-field-infix{padding-top:0}:host ::ng-deep .mat-mdc-form-field .mat-button-wrapper{display:flex;flex-direction:column;align-items:center;padding-top:8px}:host .ui-container{display:flex;flex-direction:column}:host .ui-container .image-preview{display:flex;flex-direction:column;align-content:space-between;justify-content:center;align-items:center;margin:8px 0;flex-grow:1}:host .ui-container .image-preview crispr-selected-file{width:100%}:host .ui-container .image-preview .drop-container{display:flex;box-sizing:border-box;flex-direction:column;align-items:center;border:3px dashed gray;height:100%;width:100%;align-content:center;justify-content:center;border-radius:8px}:host .ui-container .image-preview .drop-container .drop-icon{height:32px;width:32px;font-size:36px}:host .ui-container .image-preview .drop-container .mat-h2{margin:0}:host .ui-container .image-preview .processing-failed{display:flex;align-items:center;gap:4px;margin:8px 0 0;color:var(--mat-sys-error, #b3261e)}:host .ui-container .image-preview img{height:inherit;max-width:100%}:host .ui-container .button-container{display:flex;flex-direction:var(--image-upload-button-container-direction);justify-content:flex-end}:host .ui-container .button-container button{margin-left:8px}:host input{position:absolute;max-height:0;visibility:hidden}\n"] }]
|
|
1411
|
+
}], ctorParameters: () => [], propDecorators: { fileInputRef: [{ type: i0.ViewChild, args: ['fileInput', { isSignal: true }] }] } });
|
|
1412
|
+
|
|
1413
|
+
var index$a = /*#__PURE__*/Object.freeze({
|
|
1414
|
+
__proto__: null,
|
|
1415
|
+
ImageUploadFieldComponent: ImageUploadFieldComponent
|
|
1416
|
+
});
|
|
1417
|
+
|
|
1418
|
+
/* eslint-disable @nx/enforce-module-boundaries */
|
|
1419
|
+
class InputFieldComponent extends CrisprControlComponent {
|
|
1420
|
+
constructor() {
|
|
1421
|
+
super();
|
|
1422
|
+
this.defaultConfig = { inputType: 'text' };
|
|
1423
|
+
}
|
|
1424
|
+
ngOnInit() {
|
|
1425
|
+
super.ngOnInit();
|
|
1426
|
+
}
|
|
1427
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.6", ngImport: i0, type: InputFieldComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
1428
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.6", type: InputFieldComponent, isStandalone: true, selector: "crispr-input-field", usesInheritance: true, ngImport: i0, template: "<crispr-field-container [config]=\"config()\" [group]=\"group()\">\n <mat-form-field [attr.data-testid]=\"testId()\" [color]=\"config().color\" [appearance]=\"config().appearance\"\n [formGroup]=\"group()\">\n <mat-label>\n {{config().label}}\n </mat-label>\n <input [type]=\"config().inputType\" matInput [placeholder]=\"config().placeholder\"\n [formControlName]=\"config().controlName\" [min]=\"config().min\" [max]=\"config().max\"\n [maxlength]=\"config().maxLength\" [step]=\"config().step\" [autofocus]=\"config().autofocus\"\n [pattern]=\"config().pattern\" [spellcheck]=\"config().spellcheck\" [autocomplete]=\"config().autoComplete\"\n [autocapitalize]=\"config().autoCapitalize\">\n @if (config().fieldSuffix) {<span matSuffix>{{config().fieldSuffix}}</span>}\n @if (config().info) {<crispr-info matSuffix [info]=\"config().info\" />}\n @if (config().hint && (control().valid || !control().touched)) {<mat-hint>{{config().hint}}</mat-hint>}\n\n </mat-form-field>\n\n\n</crispr-field-container>", styles: ["crispr-info[matSuffix]{margin-right:.75em}\n"], dependencies: [{ kind: "component", type: InfoComponent, selector: "crispr-info", inputs: ["info"] }, { 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.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],[formArray],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i1$1.MaxLengthValidator, selector: "[maxlength][formControlName],[maxlength][formControl],[maxlength][ngModel]", inputs: ["maxlength"] }, { kind: "directive", type: i1$1.PatternValidator, selector: "[pattern][formControlName],[pattern][formControl],[pattern][ngModel]", inputs: ["pattern"] }, { kind: "directive", type: i1$1.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "directive", type: i1$1.FormControlName, selector: "[formControlName]", inputs: ["formControlName", "disabled", "ngModel"], outputs: ["ngModelChange"] }, { kind: "component", type: FieldContainerComponent, selector: "crispr-field-container", inputs: ["config", "group", "inlineField"] }, { kind: "ngmodule", type: MatFormFieldModule }, { kind: "component", type: i2$2.MatFormField, selector: "mat-form-field", inputs: ["hideRequiredMarker", "color", "floatLabel", "appearance", "subscriptSizing", "hintLabel"], exportAs: ["matFormField"] }, { kind: "directive", type: i2$2.MatLabel, selector: "mat-label" }, { kind: "directive", type: i2$2.MatHint, selector: "mat-hint", inputs: ["align", "id"] }, { kind: "directive", type: i2$2.MatSuffix, selector: "[matSuffix], [matIconSuffix], [matTextSuffix]", inputs: ["matTextSuffix"] }, { kind: "ngmodule", type: MatInputModule }, { kind: "directive", type: i3$1.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: "directive", type: ControlErrorContainerDirective, selector: "[controlErrorContainer], [formGroup]" }, { kind: "directive", type: ControlErrorsDirective, selector: "[formControl], [formControlName]" }, { kind: "ngmodule", type: MatIconModule }], changeDetection: i0.ChangeDetectionStrategy.OnPush, preserveWhitespaces: true }); }
|
|
1429
|
+
}
|
|
1430
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.6", ngImport: i0, type: InputFieldComponent, decorators: [{
|
|
1431
|
+
type: Component,
|
|
1432
|
+
args: [{ selector: 'crispr-input-field', changeDetection: ChangeDetectionStrategy.OnPush, imports: [
|
|
1433
|
+
InfoComponent,
|
|
1434
|
+
ReactiveFormsModule,
|
|
1435
|
+
FieldContainerComponent,
|
|
1436
|
+
MatFormFieldModule,
|
|
1437
|
+
MatInputModule,
|
|
1438
|
+
ControlErrorContainerDirective,
|
|
1439
|
+
ControlErrorsDirective,
|
|
1440
|
+
MatIconModule
|
|
1441
|
+
], template: "<crispr-field-container [config]=\"config()\" [group]=\"group()\">\n <mat-form-field [attr.data-testid]=\"testId()\" [color]=\"config().color\" [appearance]=\"config().appearance\"\n [formGroup]=\"group()\">\n <mat-label>\n {{config().label}}\n </mat-label>\n <input [type]=\"config().inputType\" matInput [placeholder]=\"config().placeholder\"\n [formControlName]=\"config().controlName\" [min]=\"config().min\" [max]=\"config().max\"\n [maxlength]=\"config().maxLength\" [step]=\"config().step\" [autofocus]=\"config().autofocus\"\n [pattern]=\"config().pattern\" [spellcheck]=\"config().spellcheck\" [autocomplete]=\"config().autoComplete\"\n [autocapitalize]=\"config().autoCapitalize\">\n @if (config().fieldSuffix) {<span matSuffix>{{config().fieldSuffix}}</span>}\n @if (config().info) {<crispr-info matSuffix [info]=\"config().info\" />}\n @if (config().hint && (control().valid || !control().touched)) {<mat-hint>{{config().hint}}</mat-hint>}\n\n </mat-form-field>\n\n\n</crispr-field-container>", styles: ["crispr-info[matSuffix]{margin-right:.75em}\n"] }]
|
|
1442
|
+
}], ctorParameters: () => [] });
|
|
1443
|
+
|
|
1444
|
+
var index$9 = /*#__PURE__*/Object.freeze({
|
|
1445
|
+
__proto__: null,
|
|
1446
|
+
InputFieldComponent: InputFieldComponent
|
|
1447
|
+
});
|
|
1448
|
+
|
|
1449
|
+
const defaultConfig$4 = {
|
|
1450
|
+
debounceTime: 500,
|
|
1451
|
+
};
|
|
1452
|
+
class MapFieldComponent extends CrisprControlComponent {
|
|
1453
|
+
constructor() {
|
|
1454
|
+
super(...arguments);
|
|
1455
|
+
/**
|
|
1456
|
+
* The overlay is positioned to align the top-right corner with the origin's bottom-right corner,
|
|
1457
|
+
* and the top-left corner with the origin's bottom-left corner.
|
|
1458
|
+
*/
|
|
1459
|
+
this.overlayPositions = [
|
|
1460
|
+
{
|
|
1461
|
+
originX: 'start',
|
|
1462
|
+
originY: 'bottom',
|
|
1463
|
+
overlayX: 'start',
|
|
1464
|
+
overlayY: 'top',
|
|
1465
|
+
offsetY: -26
|
|
1466
|
+
},
|
|
1467
|
+
{
|
|
1468
|
+
originX: 'end',
|
|
1469
|
+
originY: 'bottom',
|
|
1470
|
+
overlayX: 'end',
|
|
1471
|
+
overlayY: 'top',
|
|
1472
|
+
}
|
|
1473
|
+
];
|
|
1474
|
+
this.geoCoder = inject(MapGeocoder);
|
|
1475
|
+
this.infoWindow = viewChild(MapInfoWindow, ...(ngDevMode ? [{ debugName: "infoWindow" }] : []));
|
|
1476
|
+
this.selectedMarker = signal(null, ...(ngDevMode ? [{ debugName: "selectedMarker" }] : []));
|
|
1477
|
+
this.mapOpen = signal(false, ...(ngDevMode ? [{ debugName: "mapOpen" }] : []));
|
|
1478
|
+
this.defaultConfig = defaultConfig$4;
|
|
1479
|
+
this.locationControl = new FormControl(this.config()?.location);
|
|
1480
|
+
this.locationGroup = new FormGroup({
|
|
1481
|
+
location: this.locationControl,
|
|
1482
|
+
});
|
|
1483
|
+
this.locationConfig = {
|
|
1484
|
+
controlType: ControlType.AUTOCOMPLETE,
|
|
1485
|
+
controlName: 'location',
|
|
1486
|
+
label: 'Location',
|
|
1487
|
+
placeholder: 'Enter a location',
|
|
1488
|
+
options: async (_group, searchTerm) => {
|
|
1489
|
+
if (searchTerm) {
|
|
1490
|
+
return new Promise((resolve) => {
|
|
1491
|
+
const request = {
|
|
1492
|
+
includedPrimaryTypes: [
|
|
1493
|
+
'postal_code',
|
|
1494
|
+
'administrative_area_level_2',
|
|
1495
|
+
'administrative_area_level_3',
|
|
1496
|
+
],
|
|
1497
|
+
language: "en-US",
|
|
1498
|
+
region: "us",
|
|
1499
|
+
...this.config().searchAutocompleteRequest,
|
|
1500
|
+
input: searchTerm,
|
|
1501
|
+
};
|
|
1502
|
+
const options = google.maps.places.AutocompleteSuggestion.fetchAutocompleteSuggestions(request)
|
|
1503
|
+
.then((response) => response.suggestions.map(suggestion => ({
|
|
1504
|
+
value: suggestion,
|
|
1505
|
+
label: suggestion.placePrediction.text.text,
|
|
1506
|
+
})));
|
|
1507
|
+
resolve(options);
|
|
1508
|
+
});
|
|
1509
|
+
}
|
|
1510
|
+
return [];
|
|
1511
|
+
}
|
|
1512
|
+
};
|
|
1513
|
+
this.inputCenter = this.locationControl.valueChanges.pipe(filter(value => !!value && typeof value !== 'string'), switchMap$1((value) => {
|
|
1514
|
+
const request = {
|
|
1515
|
+
address: value.placePrediction.text.text,
|
|
1516
|
+
};
|
|
1517
|
+
return this.geoCoder.geocode(request);
|
|
1518
|
+
}), map$1((res) => {
|
|
1519
|
+
const { geometry } = res.results?.[0] || {};
|
|
1520
|
+
const { location } = geometry || {};
|
|
1521
|
+
const center = location?.toJSON();
|
|
1522
|
+
return center;
|
|
1523
|
+
}), startWith$1(this.config()?.center || undefined));
|
|
1524
|
+
this.currentMap = new BehaviorSubject(null);
|
|
1525
|
+
this.displayValue = toSignal(toObservable(this.control).pipe(switchMap$1(control => control
|
|
1526
|
+
? control.valueChanges.pipe(startWith$1(control.value))
|
|
1527
|
+
: of('')), map$1(value => this.getDisplay(value))));
|
|
1528
|
+
this.currentMarkers = toSignal(this.currentMap.pipe(debounceTime$1(500), switchMap$1((map) => {
|
|
1529
|
+
return this.config()?.markers?.(map, this.group()) || of([]);
|
|
1530
|
+
})));
|
|
1531
|
+
}
|
|
1532
|
+
/**
|
|
1533
|
+
* `mapInitialized` emits the native `google.maps.Map`, but `markers`/`onMove` are typed to receive the
|
|
1534
|
+
* `GoogleMap` component, so the template passes its `#map` ref instead.
|
|
1535
|
+
*/
|
|
1536
|
+
onMapInit(map) {
|
|
1537
|
+
this.currentMap.next(map);
|
|
1538
|
+
}
|
|
1539
|
+
onMarkerClick(clickedMarker) {
|
|
1540
|
+
const clickedPosition = clickedMarker.getPosition();
|
|
1541
|
+
if (!clickedPosition)
|
|
1542
|
+
return;
|
|
1543
|
+
// A simple title match is less prone to float precision issues than position
|
|
1544
|
+
const originalMarker = this.currentMarkers().find(m => m.title === clickedMarker.getTitle());
|
|
1545
|
+
if (originalMarker) {
|
|
1546
|
+
this.selectedMarker.set(originalMarker);
|
|
1547
|
+
this.infoWindow().open(clickedMarker);
|
|
1548
|
+
}
|
|
1549
|
+
}
|
|
1550
|
+
selectMarker(marker) {
|
|
1551
|
+
const valueToSet = marker.value || marker.title;
|
|
1552
|
+
this.control().setValue(valueToSet);
|
|
1553
|
+
this.closeMap();
|
|
1554
|
+
this.infoWindow()?.close();
|
|
1555
|
+
}
|
|
1556
|
+
onMove(map, group) {
|
|
1557
|
+
this.config()?.onMove?.(map, group);
|
|
1558
|
+
this.currentMap.next(map);
|
|
1559
|
+
}
|
|
1560
|
+
async onSelectLocation(suggestion) {
|
|
1561
|
+
if (!google.maps.places || !suggestion.place_id)
|
|
1562
|
+
return;
|
|
1563
|
+
const place = new google.maps.places.Place({ id: suggestion.place_id });
|
|
1564
|
+
// TODO: the fetched location is not used yet, it should recenter the map on the selected suggestion
|
|
1565
|
+
await place.fetchFields({ fields: ['location'] });
|
|
1566
|
+
}
|
|
1567
|
+
openMap() {
|
|
1568
|
+
this.mapOpen.set(true);
|
|
1569
|
+
}
|
|
1570
|
+
closeMap() {
|
|
1571
|
+
this.mapOpen.set(false);
|
|
1572
|
+
}
|
|
1573
|
+
getDisplay(value) {
|
|
1574
|
+
if (typeof value !== 'object' || value === null) {
|
|
1575
|
+
return value == null ? '' : String(value);
|
|
1576
|
+
}
|
|
1577
|
+
const record = value;
|
|
1578
|
+
const displayKey = this.config()?.valueDisplayKey;
|
|
1579
|
+
if (displayKey && record[displayKey]) {
|
|
1580
|
+
return String(record[displayKey]);
|
|
1581
|
+
}
|
|
1582
|
+
// Attempt to find a sensible display property
|
|
1583
|
+
return String(record['name'] || record['title'] || 'Selected Location');
|
|
1584
|
+
}
|
|
1585
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.6", ngImport: i0, type: MapFieldComponent, deps: null, target: i0.ɵɵFactoryTarget.Component }); }
|
|
1586
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.6", type: MapFieldComponent, isStandalone: true, selector: "crispr-map", viewQueries: [{ propertyName: "infoWindow", first: true, predicate: MapInfoWindow, descendants: true, isSignal: true }], usesInheritance: true, ngImport: i0, template: "<crispr-field-container [config]=\"config()\" [group]=\"group()\">\n\n <mat-form-field [attr.data-testid]=\"testId()\" [color]=\"config().color\" [appearance]=\"config().appearance\" [formGroup]=\"group()\"\n #trigger=\"cdkOverlayOrigin\" (click)=\"openMap()\" cdkOverlayOrigin>\n @if (config().label) {<mat-label>{{config().label}}</mat-label>}\n <input matInput [formControlName]=\"config().controlName\" style=\"display:none\">\n <input matInput readonly [value]=\"displayValue() || ''\" [placeholder]=\"config().placeholder\">\n </mat-form-field>\n\n <ng-template cdkConnectedOverlay (overlayOutsideClick)=\"closeMap()\" [cdkConnectedOverlayOrigin]=\"trigger\"\n [cdkConnectedOverlayOpen]=\"mapOpen()\" [cdkConnectedOverlayPositions]=\"overlayPositions\"\n [cdkConnectedOverlayWidth]=\"trigger.elementRef.nativeElement.offsetWidth\"\n [cdkConnectedOverlayMinWidth]=\"trigger.elementRef.nativeElement.offsetWidth\" (detach)=\"closeMap()\"\n (backdropClick)=\"closeMap()\">\n <crispr-autocomplete-field class=\"search-field\" [config]=\"locationConfig\" [group]=\"locationGroup\"\n (valueChanges)=\"onSelectLocation($event)\" aria-label=\"Search for a location\" />\n <google-map [center]=\"(inputCenter | async) || config().center\" [zoom]=\"11\" [options]=\"config().options\"\n (mapInitialized)=\"onMapInit(map)\" (idle)=\"onMove(map, group())\" #map>\n\n @for (marker of currentMarkers() || []; track $index) {\n <map-marker #markerRef=\"mapMarker\" [position]=\"marker.position\" [title]=\"marker.title\"\n (mapClick)=\"onMarkerClick(markerRef)\" />\n\n }\n <map-info-window>\n @if(selectedMarker(); as marker) {\n <div class=\"info-window-content\">\n <p>{{ marker.title }}</p>\n <button mat-button (click)=\"selectMarker(marker)\">Select</button>\n </div>\n }\n </map-info-window>\n\n </google-map>\n </ng-template>\n</crispr-field-container>", styles: [":host .marker{background-color:#0ff}.search-field{position:absolute;top:.4rem;left:.4rem;z-index:1000}\n"], dependencies: [{ kind: "component", type: GoogleMap, selector: "google-map", inputs: ["height", "width", "mapId", "mapTypeId", "center", "zoom", "options"], outputs: ["mapInitialized", "authFailure", "boundsChanged", "centerChanged", "mapClick", "mapDblclick", "mapDrag", "mapDragend", "mapDragstart", "headingChanged", "idle", "maptypeidChanged", "mapMousemove", "mapMouseout", "mapMouseover", "projectionChanged", "mapRightclick", "tilesloaded", "tiltChanged", "zoomChanged"], exportAs: ["googleMap"] }, { kind: "directive", type: MapInfoWindow, selector: "map-info-window", inputs: ["options", "position"], outputs: ["closeclick", "contentChanged", "domready", "positionChanged", "zindexChanged", "infoWindowInitialized"], exportAs: ["mapInfoWindow"] }, { kind: "directive", type: MapMarker, selector: "map-marker", inputs: ["title", "position", "label", "clickable", "options", "icon", "visible"], outputs: ["animationChanged", "mapClick", "clickableChanged", "cursorChanged", "mapDblclick", "mapDrag", "mapDragend", "draggableChanged", "mapDragstart", "flatChanged", "iconChanged", "mapMousedown", "mapMouseout", "mapMouseover", "mapMouseup", "positionChanged", "mapRightclick", "shapeChanged", "titleChanged", "visibleChanged", "zindexChanged", "markerInitialized"], exportAs: ["mapMarker"] }, { 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.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],[formArray],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i1$1.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "directive", type: i1$1.FormControlName, selector: "[formControlName]", inputs: ["formControlName", "disabled", "ngModel"], outputs: ["ngModelChange"] }, { kind: "component", type: MatFormField, selector: "mat-form-field", inputs: ["hideRequiredMarker", "color", "floatLabel", "appearance", "subscriptSizing", "hintLabel"], exportAs: ["matFormField"] }, { kind: "directive", type: 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: "directive", type: MatLabel, selector: "mat-label" }, { kind: "component", type: AutocompleteFieldComponent, selector: "crispr-autocomplete-field" }, { kind: "component", type: FieldContainerComponent, selector: "crispr-field-container", inputs: ["config", "group", "inlineField"] }, { kind: "component", type: MatButton, selector: " button[matButton], a[matButton], button[mat-button], button[mat-raised-button], button[mat-flat-button], button[mat-stroked-button], a[mat-button], a[mat-raised-button], a[mat-flat-button], a[mat-stroked-button] ", inputs: ["matButton"], exportAs: ["matButton", "matAnchor"] }, { kind: "ngmodule", type: OverlayModule }, { kind: "directive", type: i2$5.CdkConnectedOverlay, selector: "[cdk-connected-overlay], [connected-overlay], [cdkConnectedOverlay]", inputs: ["cdkConnectedOverlayOrigin", "cdkConnectedOverlayPositions", "cdkConnectedOverlayPositionStrategy", "cdkConnectedOverlayOffsetX", "cdkConnectedOverlayOffsetY", "cdkConnectedOverlayWidth", "cdkConnectedOverlayHeight", "cdkConnectedOverlayMinWidth", "cdkConnectedOverlayMinHeight", "cdkConnectedOverlayBackdropClass", "cdkConnectedOverlayPanelClass", "cdkConnectedOverlayViewportMargin", "cdkConnectedOverlayScrollStrategy", "cdkConnectedOverlayOpen", "cdkConnectedOverlayDisableClose", "cdkConnectedOverlayTransformOriginOn", "cdkConnectedOverlayHasBackdrop", "cdkConnectedOverlayLockPosition", "cdkConnectedOverlayFlexibleDimensions", "cdkConnectedOverlayGrowAfterOpen", "cdkConnectedOverlayPush", "cdkConnectedOverlayDisposeOnNavigation", "cdkConnectedOverlayUsePopover", "cdkConnectedOverlayMatchWidth", "cdkConnectedOverlay"], outputs: ["backdropClick", "positionChange", "attach", "detach", "overlayKeydown", "overlayOutsideClick"], exportAs: ["cdkConnectedOverlay"] }, { kind: "directive", type: i2$5.CdkOverlayOrigin, selector: "[cdk-overlay-origin], [overlay-origin], [cdkOverlayOrigin]", exportAs: ["cdkOverlayOrigin"] }, { kind: "pipe", type: AsyncPipe, name: "async" }], changeDetection: i0.ChangeDetectionStrategy.OnPush, preserveWhitespaces: true }); }
|
|
1587
|
+
}
|
|
1588
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.6", ngImport: i0, type: MapFieldComponent, decorators: [{
|
|
1589
|
+
type: Component,
|
|
1590
|
+
args: [{ selector: 'crispr-map', imports: [
|
|
1591
|
+
GoogleMap,
|
|
1592
|
+
MapInfoWindow,
|
|
1593
|
+
MapMarker,
|
|
1594
|
+
ReactiveFormsModule,
|
|
1595
|
+
MatFormField,
|
|
1596
|
+
MatInput,
|
|
1597
|
+
MatLabel,
|
|
1598
|
+
AsyncPipe,
|
|
1599
|
+
AutocompleteFieldComponent,
|
|
1600
|
+
FieldContainerComponent,
|
|
1601
|
+
MatButton,
|
|
1602
|
+
OverlayModule
|
|
1603
|
+
], changeDetection: ChangeDetectionStrategy.OnPush, standalone: true, template: "<crispr-field-container [config]=\"config()\" [group]=\"group()\">\n\n <mat-form-field [attr.data-testid]=\"testId()\" [color]=\"config().color\" [appearance]=\"config().appearance\" [formGroup]=\"group()\"\n #trigger=\"cdkOverlayOrigin\" (click)=\"openMap()\" cdkOverlayOrigin>\n @if (config().label) {<mat-label>{{config().label}}</mat-label>}\n <input matInput [formControlName]=\"config().controlName\" style=\"display:none\">\n <input matInput readonly [value]=\"displayValue() || ''\" [placeholder]=\"config().placeholder\">\n </mat-form-field>\n\n <ng-template cdkConnectedOverlay (overlayOutsideClick)=\"closeMap()\" [cdkConnectedOverlayOrigin]=\"trigger\"\n [cdkConnectedOverlayOpen]=\"mapOpen()\" [cdkConnectedOverlayPositions]=\"overlayPositions\"\n [cdkConnectedOverlayWidth]=\"trigger.elementRef.nativeElement.offsetWidth\"\n [cdkConnectedOverlayMinWidth]=\"trigger.elementRef.nativeElement.offsetWidth\" (detach)=\"closeMap()\"\n (backdropClick)=\"closeMap()\">\n <crispr-autocomplete-field class=\"search-field\" [config]=\"locationConfig\" [group]=\"locationGroup\"\n (valueChanges)=\"onSelectLocation($event)\" aria-label=\"Search for a location\" />\n <google-map [center]=\"(inputCenter | async) || config().center\" [zoom]=\"11\" [options]=\"config().options\"\n (mapInitialized)=\"onMapInit(map)\" (idle)=\"onMove(map, group())\" #map>\n\n @for (marker of currentMarkers() || []; track $index) {\n <map-marker #markerRef=\"mapMarker\" [position]=\"marker.position\" [title]=\"marker.title\"\n (mapClick)=\"onMarkerClick(markerRef)\" />\n\n }\n <map-info-window>\n @if(selectedMarker(); as marker) {\n <div class=\"info-window-content\">\n <p>{{ marker.title }}</p>\n <button mat-button (click)=\"selectMarker(marker)\">Select</button>\n </div>\n }\n </map-info-window>\n\n </google-map>\n </ng-template>\n</crispr-field-container>", styles: [":host .marker{background-color:#0ff}.search-field{position:absolute;top:.4rem;left:.4rem;z-index:1000}\n"] }]
|
|
1604
|
+
}], propDecorators: { infoWindow: [{ type: i0.ViewChild, args: [i0.forwardRef(() => MapInfoWindow), { isSignal: true }] }] } });
|
|
1605
|
+
|
|
1606
|
+
var index$8 = /*#__PURE__*/Object.freeze({
|
|
1607
|
+
__proto__: null,
|
|
1608
|
+
MapFieldComponent: MapFieldComponent
|
|
1609
|
+
});
|
|
1610
|
+
|
|
1611
|
+
/* eslint-disable @nx/enforce-module-boundaries */
|
|
1612
|
+
class RadioFieldComponent extends CrisprControlComponent {
|
|
1613
|
+
constructor() {
|
|
1614
|
+
super(...arguments);
|
|
1615
|
+
this.defaultConfig = {};
|
|
1616
|
+
/** The radio group's control, typed for the `[formControl]` binding. */
|
|
1617
|
+
this.radioControl = computed(() => this.control(), ...(ngDevMode ? [{ debugName: "radioControl" }] : []));
|
|
1618
|
+
}
|
|
1619
|
+
ngOnInit() {
|
|
1620
|
+
super.ngOnInit();
|
|
1621
|
+
// options$ can be passed as an array, promise that resolves array, or observable that resolves array
|
|
1622
|
+
// this functions accounts for all possibilities and converts to observable that resolves array
|
|
1623
|
+
this.options$ = observablifyOptions(this.config().options, this.group());
|
|
1624
|
+
}
|
|
1625
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.6", ngImport: i0, type: RadioFieldComponent, deps: null, target: i0.ɵɵFactoryTarget.Component }); }
|
|
1626
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.6", type: RadioFieldComponent, isStandalone: true, selector: "crispr-radio-field", usesInheritance: true, ngImport: i0, template: "<crispr-field-container\n [config]=\"config()\"\n [group]=\"group()\">\n\n <mat-radio-group\n [attr.data-testid]=\"testId()\"\n [formControl]=\"radioControl()\"\n [color]=\"config().color\">\n @for (option of options$ | async; track option) {\n <mat-radio-button class=\"example-radio-button\" [value]=\"option.value\">\n {{option.label}}\n </mat-radio-button>\n }\n </mat-radio-group>\n</crispr-field-container>\n", styles: ["mat-radio-button{padding:12px}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: ReactiveFormsModule }, { 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"] }, { kind: "component", type: FieldContainerComponent, selector: "crispr-field-container", inputs: ["config", "group", "inlineField"] }, { kind: "ngmodule", type: MatRadioModule }, { kind: "directive", type: i2$6.MatRadioGroup, selector: "mat-radio-group", inputs: ["color", "name", "labelPosition", "value", "selected", "disabled", "required", "disabledInteractive"], outputs: ["change"], exportAs: ["matRadioGroup"] }, { kind: "component", type: i2$6.MatRadioButton, selector: "mat-radio-button", inputs: ["id", "name", "aria-label", "aria-labelledby", "aria-describedby", "disableRipple", "tabIndex", "checked", "value", "labelPosition", "disabled", "required", "color", "disabledInteractive"], outputs: ["change"], exportAs: ["matRadioButton"] }, { kind: "directive", type: ControlErrorsDirective, selector: "[formControl], [formControlName]" }, { kind: "pipe", type: i1.AsyncPipe, name: "async" }], changeDetection: i0.ChangeDetectionStrategy.OnPush, preserveWhitespaces: true }); }
|
|
1627
|
+
}
|
|
1628
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.6", ngImport: i0, type: RadioFieldComponent, decorators: [{
|
|
1629
|
+
type: Component,
|
|
1630
|
+
args: [{ selector: 'crispr-radio-field', changeDetection: ChangeDetectionStrategy.OnPush, imports: [
|
|
1631
|
+
CommonModule,
|
|
1632
|
+
ReactiveFormsModule,
|
|
1633
|
+
FieldContainerComponent,
|
|
1634
|
+
MatRadioModule,
|
|
1635
|
+
ControlErrorsDirective
|
|
1636
|
+
], standalone: true, template: "<crispr-field-container\n [config]=\"config()\"\n [group]=\"group()\">\n\n <mat-radio-group\n [attr.data-testid]=\"testId()\"\n [formControl]=\"radioControl()\"\n [color]=\"config().color\">\n @for (option of options$ | async; track option) {\n <mat-radio-button class=\"example-radio-button\" [value]=\"option.value\">\n {{option.label}}\n </mat-radio-button>\n }\n </mat-radio-group>\n</crispr-field-container>\n", styles: ["mat-radio-button{padding:12px}\n"] }]
|
|
1637
|
+
}] });
|
|
1638
|
+
|
|
1639
|
+
var index$7 = /*#__PURE__*/Object.freeze({
|
|
1640
|
+
__proto__: null,
|
|
1641
|
+
RadioFieldComponent: RadioFieldComponent
|
|
1642
|
+
});
|
|
1643
|
+
|
|
1644
|
+
/* eslint-disable @nx/enforce-module-boundaries */
|
|
1645
|
+
class SelectFieldComponent extends CrisprControlComponent {
|
|
1646
|
+
constructor() {
|
|
1647
|
+
super(...arguments);
|
|
1648
|
+
this.defaultConfig = {
|
|
1649
|
+
enableToggleAll: false,
|
|
1650
|
+
color: 'primary',
|
|
1651
|
+
};
|
|
1652
|
+
this.allSelected = signal(false, ...(ngDevMode ? [{ debugName: "allSelected" }] : []));
|
|
1653
|
+
this.selectField = viewChild('selectField', ...(ngDevMode ? [{ debugName: "selectField" }] : []));
|
|
1654
|
+
}
|
|
1655
|
+
ngOnInit() {
|
|
1656
|
+
super.ngOnInit();
|
|
1657
|
+
// options$ can be passed as an array, promise that resolves array, or observable that resolves array
|
|
1658
|
+
// this functions accounts for all possibilities and converts to observable that resolves array
|
|
1659
|
+
this.options$ = observablifyOptions(this.config().options, this.group()).pipe(tap(() => {
|
|
1660
|
+
// mat-select.writeValue() runs before async options render, so _setSelectionByValue
|
|
1661
|
+
// finds no matches. Re-apply the value after options are in the DOM.
|
|
1662
|
+
afterNextRender(() => {
|
|
1663
|
+
const ctrl = this.control();
|
|
1664
|
+
if (ctrl?.value != null) {
|
|
1665
|
+
ctrl.setValue(ctrl.value, { emitEvent: false });
|
|
1666
|
+
}
|
|
1667
|
+
}, { injector: this.injector });
|
|
1668
|
+
}));
|
|
1669
|
+
}
|
|
1670
|
+
ngAfterViewInit() {
|
|
1671
|
+
if (this.config().multiple &&
|
|
1672
|
+
this.config().enableToggleAll &&
|
|
1673
|
+
this.selectField().options?.length === this.value()?.length) {
|
|
1674
|
+
this.allSelected.set(true);
|
|
1675
|
+
}
|
|
1676
|
+
}
|
|
1677
|
+
toggleAll(isSelected) {
|
|
1678
|
+
if (isSelected) {
|
|
1679
|
+
this.selectField().options.forEach((option) => option.deselect());
|
|
1680
|
+
}
|
|
1681
|
+
else {
|
|
1682
|
+
this.selectField().options.forEach((option) => option.select());
|
|
1683
|
+
}
|
|
1684
|
+
this.allSelected.update((selected) => !selected);
|
|
1685
|
+
}
|
|
1686
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.6", ngImport: i0, type: SelectFieldComponent, deps: null, target: i0.ɵɵFactoryTarget.Component }); }
|
|
1687
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.6", type: SelectFieldComponent, isStandalone: true, selector: "crispr-select-field", viewQueries: [{ propertyName: "selectField", first: true, predicate: ["selectField"], descendants: true, isSignal: true }], usesInheritance: true, ngImport: i0, template: "<crispr-field-container\n [config]=\"config()\"\n [group]=\"group()\">\n\n <mat-form-field\n [attr.data-testid]=\"testId()\"\n [color]=\"config().color\"\n [appearance]=\"config().appearance\"\n [formGroup]=\"group()\">\n @if (config().label) {<mat-label>{{config().label}}</mat-label>}\n <mat-select\n #selectField\n [formControlName]=\"config().controlName\"\n [placeholder]=\"config().placeholder\"\n [multiple]=\"config().multiple\">\n\n @if (config()?.multiple && config().enableToggleAll) {\n <mat-checkbox\n [color]=\"config().color\"\n [checked]=\"allSelected()\"\n (click)=\"toggleAll(allSelected())\">\n Select All\n </mat-checkbox>\n }\n @for (option of options$ | async; track option) {\n <mat-option\n [value]=\"option.value\">\n <crispr-option [option]=\"option\" />\n </mat-option>\n }\n\n </mat-select>\n\n @if (config().fieldSuffix) {<span matSuffix>{{config().fieldSuffix}}</span>}\n @if (config().info) {<crispr-info matSuffix [info]=\"config().info\" />}\n @if (config().hint && (control().valid || !control().touched)) {<mat-hint>{{config().hint}}</mat-hint>}\n\n </mat-form-field>\n\n</crispr-field-container>\n", styles: ["mat-checkbox{display:block;padding:16px}crispr-info[matSuffix]{margin-right:.75em}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "component", type: InfoComponent, selector: "crispr-info", inputs: ["info"] }, { kind: "component", type: OptionComponent, selector: "crispr-option", inputs: ["option"] }, { kind: "ngmodule", type: MatCheckboxModule }, { kind: "component", type: i1$3.MatCheckbox, selector: "mat-checkbox", inputs: ["aria-label", "aria-labelledby", "aria-describedby", "aria-expanded", "aria-controls", "aria-owns", "id", "required", "labelPosition", "name", "value", "disableRipple", "tabIndex", "color", "disabledInteractive", "checked", "disabled", "indeterminate"], outputs: ["change", "indeterminateChange"], exportAs: ["matCheckbox"] }, { kind: "ngmodule", type: MatSelectModule }, { kind: "component", type: i2$2.MatFormField, selector: "mat-form-field", inputs: ["hideRequiredMarker", "color", "floatLabel", "appearance", "subscriptSizing", "hintLabel"], exportAs: ["matFormField"] }, { kind: "directive", type: i2$2.MatLabel, selector: "mat-label" }, { kind: "directive", type: i2$2.MatHint, selector: "mat-hint", inputs: ["align", "id"] }, { kind: "directive", type: i2$2.MatSuffix, selector: "[matSuffix], [matIconSuffix], [matTextSuffix]", inputs: ["matTextSuffix"] }, { kind: "component", type: i3$2.MatSelect, selector: "mat-select", inputs: ["aria-describedby", "panelClass", "disabled", "disableRipple", "tabIndex", "hideSingleSelectionIndicator", "placeholder", "required", "multiple", "disableOptionCentering", "compareWith", "value", "aria-label", "aria-labelledby", "errorStateMatcher", "typeaheadDebounceInterval", "sortComparator", "id", "panelWidth", "canSelectNullableOptions"], outputs: ["openedChange", "opened", "closed", "selectionChange", "valueChange"], exportAs: ["matSelect"] }, { kind: "component", type: i2$3.MatOption, selector: "mat-option", inputs: ["value", "id", "disabled"], outputs: ["onSelectionChange"], exportAs: ["matOption"] }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$1.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],[formArray],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i1$1.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "directive", type: i1$1.FormControlName, selector: "[formControlName]", inputs: ["formControlName", "disabled", "ngModel"], outputs: ["ngModelChange"] }, { kind: "directive", type: ControlErrorContainerDirective, selector: "[controlErrorContainer], [formGroup]" }, { kind: "directive", type: ControlErrorsDirective, selector: "[formControl], [formControlName]" }, { kind: "component", type: FieldContainerComponent, selector: "crispr-field-container", inputs: ["config", "group", "inlineField"] }, { kind: "ngmodule", type: MatFormFieldModule }, { kind: "pipe", type: i1.AsyncPipe, name: "async" }], changeDetection: i0.ChangeDetectionStrategy.OnPush, preserveWhitespaces: true }); }
|
|
1688
|
+
}
|
|
1689
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.6", ngImport: i0, type: SelectFieldComponent, decorators: [{
|
|
1690
|
+
type: Component,
|
|
1691
|
+
args: [{ selector: 'crispr-select-field', changeDetection: ChangeDetectionStrategy.OnPush, imports: [
|
|
1692
|
+
CommonModule,
|
|
1693
|
+
InfoComponent,
|
|
1694
|
+
OptionComponent,
|
|
1695
|
+
MatCheckboxModule,
|
|
1696
|
+
MatSelectModule,
|
|
1697
|
+
ReactiveFormsModule,
|
|
1698
|
+
ControlErrorContainerDirective,
|
|
1699
|
+
ControlErrorsDirective,
|
|
1700
|
+
FieldContainerComponent,
|
|
1701
|
+
MatFormFieldModule
|
|
1702
|
+
], standalone: true, template: "<crispr-field-container\n [config]=\"config()\"\n [group]=\"group()\">\n\n <mat-form-field\n [attr.data-testid]=\"testId()\"\n [color]=\"config().color\"\n [appearance]=\"config().appearance\"\n [formGroup]=\"group()\">\n @if (config().label) {<mat-label>{{config().label}}</mat-label>}\n <mat-select\n #selectField\n [formControlName]=\"config().controlName\"\n [placeholder]=\"config().placeholder\"\n [multiple]=\"config().multiple\">\n\n @if (config()?.multiple && config().enableToggleAll) {\n <mat-checkbox\n [color]=\"config().color\"\n [checked]=\"allSelected()\"\n (click)=\"toggleAll(allSelected())\">\n Select All\n </mat-checkbox>\n }\n @for (option of options$ | async; track option) {\n <mat-option\n [value]=\"option.value\">\n <crispr-option [option]=\"option\" />\n </mat-option>\n }\n\n </mat-select>\n\n @if (config().fieldSuffix) {<span matSuffix>{{config().fieldSuffix}}</span>}\n @if (config().info) {<crispr-info matSuffix [info]=\"config().info\" />}\n @if (config().hint && (control().valid || !control().touched)) {<mat-hint>{{config().hint}}</mat-hint>}\n\n </mat-form-field>\n\n</crispr-field-container>\n", styles: ["mat-checkbox{display:block;padding:16px}crispr-info[matSuffix]{margin-right:.75em}\n"] }]
|
|
1703
|
+
}], propDecorators: { selectField: [{ type: i0.ViewChild, args: ['selectField', { isSignal: true }] }] } });
|
|
1704
|
+
|
|
1705
|
+
var index$6 = /*#__PURE__*/Object.freeze({
|
|
1706
|
+
__proto__: null,
|
|
1707
|
+
SelectFieldComponent: SelectFieldComponent
|
|
1708
|
+
});
|
|
1709
|
+
|
|
1710
|
+
/* eslint-disable @nx/enforce-module-boundaries */
|
|
1711
|
+
const defaultConfig$3 = {
|
|
1712
|
+
displayLimits: true,
|
|
1713
|
+
displayWith: (val) => val,
|
|
1714
|
+
};
|
|
1715
|
+
class SliderFieldComponent extends CrisprControlComponent {
|
|
1716
|
+
constructor() {
|
|
1717
|
+
super();
|
|
1718
|
+
this.defaultConfig = defaultConfig$3;
|
|
1719
|
+
/** `MatSlider` expects the label as a string, the config allows numbers too. */
|
|
1720
|
+
this.displayWith = computed(() => {
|
|
1721
|
+
const displayWith = this.config().displayWith;
|
|
1722
|
+
return (value) => `${displayWith ? displayWith(value) : value}`;
|
|
1723
|
+
}, ...(ngDevMode ? [{ debugName: "displayWith" }] : []));
|
|
1724
|
+
}
|
|
1725
|
+
ngOnInit() {
|
|
1726
|
+
super.ngOnInit();
|
|
1727
|
+
}
|
|
1728
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.6", ngImport: i0, type: SliderFieldComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
1729
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.6", type: SliderFieldComponent, isStandalone: true, selector: "crispr-slider-field", usesInheritance: true, ngImport: i0, template: "<crispr-field-container [config]=\"config()\" [group]=\"group()\">\n @if (config().label) {\n <label>\n {{config().label}}\n @if (config().info) {\n <crispr-info [info]=\"config().info\" />\n }\n </label>\n }\n <div class=\"slider-container\">\n @if (config().displayLimits) {\n <span>\n {{config().min}}\n </span>\n }\n <mat-slider [attr.data-testid]=\"testId()\" [discrete]=\"config().discrete\"\n [displayWith]=\"displayWith()\" class=\"example-margin\" [formGroup]=\"group()\" [max]=\"config().max\"\n [min]=\"config().min\" [step]=\"config().step\">\n <input matSliderThumb [formControlName]=\"config().controlName\">\n </mat-slider>\n @if (config().displayLimits) {\n <span>\n {{config().max}}\n </span>\n }\n </div>\n\n</crispr-field-container>", styles: [".slider-container{display:flex;align-items:center}.slider-container mat-slider{flex:1}\n"], dependencies: [{ kind: "component", type: InfoComponent, selector: "crispr-info", inputs: ["info"] }, { kind: "component", type: FieldContainerComponent, selector: "crispr-field-container", inputs: ["config", "group", "inlineField"] }, { 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.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],[formArray],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i1$1.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "directive", type: i1$1.FormControlName, selector: "[formControlName]", inputs: ["formControlName", "disabled", "ngModel"], outputs: ["ngModelChange"] }, { kind: "ngmodule", type: MatSliderModule }, { kind: "component", type: i2$7.MatSlider, selector: "mat-slider", inputs: ["disabled", "discrete", "showTickMarks", "min", "color", "disableRipple", "max", "step", "displayWith"], exportAs: ["matSlider"] }, { kind: "directive", type: i2$7.MatSliderThumb, selector: "input[matSliderThumb]", inputs: ["value"], outputs: ["valueChange", "dragStart", "dragEnd"], exportAs: ["matSliderThumb"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush, preserveWhitespaces: true }); }
|
|
1730
|
+
}
|
|
1731
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.6", ngImport: i0, type: SliderFieldComponent, decorators: [{
|
|
1732
|
+
type: Component,
|
|
1733
|
+
args: [{ selector: 'crispr-slider-field', changeDetection: ChangeDetectionStrategy.OnPush, imports: [
|
|
1734
|
+
InfoComponent,
|
|
1735
|
+
FieldContainerComponent,
|
|
1736
|
+
ReactiveFormsModule,
|
|
1737
|
+
MatSliderModule
|
|
1738
|
+
], template: "<crispr-field-container [config]=\"config()\" [group]=\"group()\">\n @if (config().label) {\n <label>\n {{config().label}}\n @if (config().info) {\n <crispr-info [info]=\"config().info\" />\n }\n </label>\n }\n <div class=\"slider-container\">\n @if (config().displayLimits) {\n <span>\n {{config().min}}\n </span>\n }\n <mat-slider [attr.data-testid]=\"testId()\" [discrete]=\"config().discrete\"\n [displayWith]=\"displayWith()\" class=\"example-margin\" [formGroup]=\"group()\" [max]=\"config().max\"\n [min]=\"config().min\" [step]=\"config().step\">\n <input matSliderThumb [formControlName]=\"config().controlName\">\n </mat-slider>\n @if (config().displayLimits) {\n <span>\n {{config().max}}\n </span>\n }\n </div>\n\n</crispr-field-container>", styles: [".slider-container{display:flex;align-items:center}.slider-container mat-slider{flex:1}\n"] }]
|
|
1739
|
+
}], ctorParameters: () => [] });
|
|
1740
|
+
|
|
1741
|
+
var index$5 = /*#__PURE__*/Object.freeze({
|
|
1742
|
+
__proto__: null,
|
|
1743
|
+
SliderFieldComponent: SliderFieldComponent
|
|
1744
|
+
});
|
|
1745
|
+
|
|
1746
|
+
/* eslint-disable @nx/enforce-module-boundaries */
|
|
1747
|
+
const defaultConfig$2 = { rows: 5 };
|
|
1748
|
+
class TextareaFieldComponent extends CrisprControlComponent {
|
|
1749
|
+
constructor() {
|
|
1750
|
+
super(...arguments);
|
|
1751
|
+
this.defaultConfig = defaultConfig$2;
|
|
1752
|
+
}
|
|
1753
|
+
ngOnInit() {
|
|
1754
|
+
super.ngOnInit();
|
|
1755
|
+
}
|
|
1756
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.6", ngImport: i0, type: TextareaFieldComponent, deps: null, target: i0.ɵɵFactoryTarget.Component }); }
|
|
1757
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.6", type: TextareaFieldComponent, isStandalone: true, selector: "crispr-textarea-field", usesInheritance: true, ngImport: i0, template: "<crispr-field-container [config]=\"config()\" [group]=\"group()\">\n <mat-form-field [attr.data-testid]=\"testId()\" [color]=\"config().color\" [appearance]=\"config().appearance\"\n [formGroup]=\"group()\">\n @if (config().label) {<mat-label>{{config().label}}</mat-label>}\n <textarea [rows]=\"config().rows\" matInput [placeholder]=\"config().placeholder\"\n [formControlName]=\"config().controlName\">\n </textarea>\n @if (config().info) {<crispr-info [info]=\"config().info\" />}\n @if (config().fieldSuffix) {<span matSuffix>{{config().fieldSuffix}}</span>}\n @if (config().hint && control().valid) {<mat-hint>{{config().hint}}</mat-hint>}\n\n </mat-form-field>\n\n</crispr-field-container>", styles: ["crispr-info{position:absolute;top:.75em;right:-.25em}\n"], dependencies: [{ kind: "component", type: InfoComponent, selector: "crispr-info", inputs: ["info"] }, { 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.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],[formArray],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i1$1.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "directive", type: i1$1.FormControlName, selector: "[formControlName]", inputs: ["formControlName", "disabled", "ngModel"], outputs: ["ngModelChange"] }, { kind: "component", type: FieldContainerComponent, selector: "crispr-field-container", inputs: ["config", "group", "inlineField"] }, { kind: "ngmodule", type: MatFormFieldModule }, { kind: "component", type: i2$2.MatFormField, selector: "mat-form-field", inputs: ["hideRequiredMarker", "color", "floatLabel", "appearance", "subscriptSizing", "hintLabel"], exportAs: ["matFormField"] }, { kind: "directive", type: i2$2.MatLabel, selector: "mat-label" }, { kind: "directive", type: i2$2.MatHint, selector: "mat-hint", inputs: ["align", "id"] }, { kind: "directive", type: i2$2.MatSuffix, selector: "[matSuffix], [matIconSuffix], [matTextSuffix]", inputs: ["matTextSuffix"] }, { kind: "ngmodule", type: MatInputModule }, { kind: "directive", type: i3$1.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: "directive", type: ControlErrorContainerDirective, selector: "[controlErrorContainer], [formGroup]" }, { kind: "directive", type: ControlErrorsDirective, selector: "[formControl], [formControlName]" }], changeDetection: i0.ChangeDetectionStrategy.OnPush, preserveWhitespaces: true }); }
|
|
1758
|
+
}
|
|
1759
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.6", ngImport: i0, type: TextareaFieldComponent, decorators: [{
|
|
1760
|
+
type: Component,
|
|
1761
|
+
args: [{ selector: 'crispr-textarea-field', changeDetection: ChangeDetectionStrategy.OnPush, imports: [
|
|
1762
|
+
InfoComponent,
|
|
1763
|
+
ReactiveFormsModule,
|
|
1764
|
+
FieldContainerComponent,
|
|
1765
|
+
MatFormFieldModule,
|
|
1766
|
+
MatInputModule,
|
|
1767
|
+
ControlErrorContainerDirective,
|
|
1768
|
+
ControlErrorsDirective
|
|
1769
|
+
], standalone: true, template: "<crispr-field-container [config]=\"config()\" [group]=\"group()\">\n <mat-form-field [attr.data-testid]=\"testId()\" [color]=\"config().color\" [appearance]=\"config().appearance\"\n [formGroup]=\"group()\">\n @if (config().label) {<mat-label>{{config().label}}</mat-label>}\n <textarea [rows]=\"config().rows\" matInput [placeholder]=\"config().placeholder\"\n [formControlName]=\"config().controlName\">\n </textarea>\n @if (config().info) {<crispr-info [info]=\"config().info\" />}\n @if (config().fieldSuffix) {<span matSuffix>{{config().fieldSuffix}}</span>}\n @if (config().hint && control().valid) {<mat-hint>{{config().hint}}</mat-hint>}\n\n </mat-form-field>\n\n</crispr-field-container>", styles: ["crispr-info{position:absolute;top:.75em;right:-.25em}\n"] }]
|
|
1770
|
+
}] });
|
|
1771
|
+
|
|
1772
|
+
var index$4 = /*#__PURE__*/Object.freeze({
|
|
1773
|
+
__proto__: null,
|
|
1774
|
+
TextareaFieldComponent: TextareaFieldComponent
|
|
1775
|
+
});
|
|
1776
|
+
|
|
1777
|
+
/* eslint-disable @nx/enforce-module-boundaries */
|
|
1778
|
+
const defaultConfig$1 = {};
|
|
1779
|
+
class UnitConversionFieldComponent extends CrisprControlComponent {
|
|
1780
|
+
constructor() {
|
|
1781
|
+
super(...arguments);
|
|
1782
|
+
this.defaultConfig = defaultConfig$1;
|
|
1783
|
+
this.displayValueControl = new FormControl();
|
|
1784
|
+
this.unitSelectControl = new FormControl();
|
|
1785
|
+
}
|
|
1786
|
+
ngOnInit() {
|
|
1787
|
+
const config = this.config();
|
|
1788
|
+
super.ngOnInit();
|
|
1789
|
+
// TODO: Validators need work. validation is tricky because the input needs to be validated against but the validator will compare against the controls value
|
|
1790
|
+
// this.displayValueControl.setValidators(this.config.validators);
|
|
1791
|
+
const controlValuePipeline = [
|
|
1792
|
+
this.displayValueControl.valueChanges.pipe(startWith(this.displayValueControl.value))
|
|
1793
|
+
];
|
|
1794
|
+
// setup all required pieces when showing a field for selecting units
|
|
1795
|
+
if (config.showUnitSelect === true) {
|
|
1796
|
+
this.unitOptions$ = observablifyOptions(config.selectableUnits, this.group());
|
|
1797
|
+
controlValuePipeline.push(this.unitSelectControl.valueChanges.pipe(startWith(config?.initialDisplayUnit || null)));
|
|
1798
|
+
this.unitSelectControl.setValue(config?.initialDisplayUnit || null);
|
|
1799
|
+
}
|
|
1800
|
+
this.transformationSubscription = combineLatest(controlValuePipeline).subscribe(([displayValue, unitValue]) => {
|
|
1801
|
+
const computedValue = this.config().storedValueConversion(displayValue, unitValue);
|
|
1802
|
+
this.control().setValue(computedValue);
|
|
1803
|
+
});
|
|
1804
|
+
}
|
|
1805
|
+
ngOnDestroy() {
|
|
1806
|
+
this.transformationSubscription.unsubscribe();
|
|
1807
|
+
}
|
|
1808
|
+
setInitialDisplayValue(storedValue, unit) {
|
|
1809
|
+
const initialDisplayValue = this.config().initialDisplayValueConversion(storedValue || null, unit);
|
|
1810
|
+
this.displayValueControl.setValue(initialDisplayValue);
|
|
1811
|
+
}
|
|
1812
|
+
/**
|
|
1813
|
+
* We override the 'setControlValue' we inherited from crisprControlMixin and add our custom logic,
|
|
1814
|
+
* This function gets called onInit because were object oriented. it's so great, so great that I'm
|
|
1815
|
+
* typing a paragraph to explain what the hell is going on...
|
|
1816
|
+
* @param value the initial value for the control passed down from the form components input
|
|
1817
|
+
*/
|
|
1818
|
+
setControlValue(value) {
|
|
1819
|
+
const config = this.config();
|
|
1820
|
+
const initialUnitValue = config.showUnitSelect
|
|
1821
|
+
? config?.initialDisplayUnit
|
|
1822
|
+
: null;
|
|
1823
|
+
this.setInitialDisplayValue(value, initialUnitValue);
|
|
1824
|
+
}
|
|
1825
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.6", ngImport: i0, type: UnitConversionFieldComponent, deps: null, target: i0.ɵɵFactoryTarget.Component }); }
|
|
1826
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.6", type: UnitConversionFieldComponent, isStandalone: true, selector: "crispr-unit-conversion-field", usesInheritance: true, ngImport: i0, template: "<crispr-field-container [config]=\"config()\" [group]=\"group()\">\n <mat-form-field [attr.data-testid]=\"testId()\" [style.width.%]=\"config().showUnitSelect ? 60 : 100\" class=\"number-field\" [color]=\"config().color\"\n [appearance]=\"config().appearance\">\n @if (config().label) {<mat-label>{{config().label}}</mat-label>}\n <input type=\"number\" matInput [formControl]=\"displayValueControl\" [placeholder]=\"config().placeholder\"\n [min]=\"config().min\" [max]=\"config().max\" [step]=\"config().step\" [autofocus]=\"config().autofocus\">\n @if (config().fieldSuffix) {<span matSuffix>{{config().fieldSuffix}}</span>}\n @if (config().info) {<crispr-info matSuffix [info]=\"config().info\" />}\n </mat-form-field>\n\n @if (config().showUnitSelect) {\n <mat-form-field [color]=\"config().color\" [appearance]=\"config().appearance\" [formGroup]=\"group()\" class=\"unit-field\">\n @if (config().label) {<mat-label>{{config().label}}</mat-label>}\n <mat-select #selectField [formControl]=\"unitSelectControl\">\n @for (option of unitOptions$ | async; track option) {\n <mat-option [value]=\"option.value\">\n <crispr-option [option]=\"option\" />\n </mat-option>\n }\n </mat-select>\n @if (config().hint && (control().valid || !control().touched)) {<mat-hint>{{config().hint}}</mat-hint>}\n </mat-form-field>\n }\n\n</crispr-field-container>", styles: [".unit-field{width:40%;margin-left:16px}:host ::ng-deep .field-container{display:flex}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "component", type: InfoComponent, selector: "crispr-info", inputs: ["info"] }, { kind: "component", type: OptionComponent, selector: "crispr-option", inputs: ["option"] }, { 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.NumberValueAccessor, selector: "input[type=number][formControlName],input[type=number][formControl],input[type=number][ngModel]" }, { kind: "directive", type: i1$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$1.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],[formArray],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i1$1.MinValidator, selector: "input[type=number][min][formControlName],input[type=number][min][formControl],input[type=number][min][ngModel]", inputs: ["min"] }, { kind: "directive", type: i1$1.MaxValidator, selector: "input[type=number][max][formControlName],input[type=number][max][formControl],input[type=number][max][ngModel]", inputs: ["max"] }, { kind: "directive", type: i1$1.FormControlDirective, selector: "[formControl]", inputs: ["formControl", "disabled", "ngModel"], outputs: ["ngModelChange"], exportAs: ["ngForm"] }, { kind: "directive", type: i1$1.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "component", type: FieldContainerComponent, selector: "crispr-field-container", inputs: ["config", "group", "inlineField"] }, { kind: "ngmodule", type: MatInputModule }, { kind: "directive", type: i3$1.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: i2$2.MatFormField, selector: "mat-form-field", inputs: ["hideRequiredMarker", "color", "floatLabel", "appearance", "subscriptSizing", "hintLabel"], exportAs: ["matFormField"] }, { kind: "directive", type: i2$2.MatLabel, selector: "mat-label" }, { kind: "directive", type: i2$2.MatHint, selector: "mat-hint", inputs: ["align", "id"] }, { kind: "directive", type: i2$2.MatSuffix, selector: "[matSuffix], [matIconSuffix], [matTextSuffix]", inputs: ["matTextSuffix"] }, { kind: "ngmodule", type: MatSelectModule }, { kind: "component", type: i3$2.MatSelect, selector: "mat-select", inputs: ["aria-describedby", "panelClass", "disabled", "disableRipple", "tabIndex", "hideSingleSelectionIndicator", "placeholder", "required", "multiple", "disableOptionCentering", "compareWith", "value", "aria-label", "aria-labelledby", "errorStateMatcher", "typeaheadDebounceInterval", "sortComparator", "id", "panelWidth", "canSelectNullableOptions"], outputs: ["openedChange", "opened", "closed", "selectionChange", "valueChange"], exportAs: ["matSelect"] }, { kind: "component", type: i2$3.MatOption, selector: "mat-option", inputs: ["value", "id", "disabled"], outputs: ["onSelectionChange"], exportAs: ["matOption"] }, { kind: "ngmodule", type: MatOptionModule }, { kind: "directive", type: ControlErrorContainerDirective, selector: "[controlErrorContainer], [formGroup]" }, { kind: "directive", type: ControlErrorsDirective, selector: "[formControl], [formControlName]" }, { kind: "pipe", type: i1.AsyncPipe, name: "async" }], changeDetection: i0.ChangeDetectionStrategy.OnPush, preserveWhitespaces: true }); }
|
|
1827
|
+
}
|
|
1828
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.6", ngImport: i0, type: UnitConversionFieldComponent, decorators: [{
|
|
1829
|
+
type: Component,
|
|
1830
|
+
args: [{ selector: 'crispr-unit-conversion-field', changeDetection: ChangeDetectionStrategy.OnPush, imports: [
|
|
1831
|
+
CommonModule,
|
|
1832
|
+
InfoComponent,
|
|
1833
|
+
OptionComponent,
|
|
1834
|
+
ReactiveFormsModule,
|
|
1835
|
+
FieldContainerComponent,
|
|
1836
|
+
MatInputModule,
|
|
1837
|
+
MatSelectModule,
|
|
1838
|
+
MatOptionModule,
|
|
1839
|
+
ControlErrorContainerDirective,
|
|
1840
|
+
ControlErrorsDirective
|
|
1841
|
+
], standalone: true, template: "<crispr-field-container [config]=\"config()\" [group]=\"group()\">\n <mat-form-field [attr.data-testid]=\"testId()\" [style.width.%]=\"config().showUnitSelect ? 60 : 100\" class=\"number-field\" [color]=\"config().color\"\n [appearance]=\"config().appearance\">\n @if (config().label) {<mat-label>{{config().label}}</mat-label>}\n <input type=\"number\" matInput [formControl]=\"displayValueControl\" [placeholder]=\"config().placeholder\"\n [min]=\"config().min\" [max]=\"config().max\" [step]=\"config().step\" [autofocus]=\"config().autofocus\">\n @if (config().fieldSuffix) {<span matSuffix>{{config().fieldSuffix}}</span>}\n @if (config().info) {<crispr-info matSuffix [info]=\"config().info\" />}\n </mat-form-field>\n\n @if (config().showUnitSelect) {\n <mat-form-field [color]=\"config().color\" [appearance]=\"config().appearance\" [formGroup]=\"group()\" class=\"unit-field\">\n @if (config().label) {<mat-label>{{config().label}}</mat-label>}\n <mat-select #selectField [formControl]=\"unitSelectControl\">\n @for (option of unitOptions$ | async; track option) {\n <mat-option [value]=\"option.value\">\n <crispr-option [option]=\"option\" />\n </mat-option>\n }\n </mat-select>\n @if (config().hint && (control().valid || !control().touched)) {<mat-hint>{{config().hint}}</mat-hint>}\n </mat-form-field>\n }\n\n</crispr-field-container>", styles: [".unit-field{width:40%;margin-left:16px}:host ::ng-deep .field-container{display:flex}\n"] }]
|
|
1842
|
+
}] });
|
|
1843
|
+
|
|
1844
|
+
var index$3 = /*#__PURE__*/Object.freeze({
|
|
1845
|
+
__proto__: null,
|
|
1846
|
+
UnitConversionFieldComponent: UnitConversionFieldComponent
|
|
1847
|
+
});
|
|
1848
|
+
|
|
1849
|
+
var index$2 = /*#__PURE__*/Object.freeze({
|
|
1850
|
+
__proto__: null,
|
|
1851
|
+
AutocompleteChiplistFieldComponent: AutocompleteChiplistFieldComponent,
|
|
1852
|
+
AutocompleteFieldComponent: AutocompleteFieldComponent,
|
|
1853
|
+
ButtonComponent: ButtonComponent,
|
|
1854
|
+
CheckboxFieldComponent: CheckboxFieldComponent,
|
|
1855
|
+
DatepickerFieldComponent: DatepickerFieldComponent,
|
|
1856
|
+
DividerComponent: DividerComponent,
|
|
1857
|
+
FieldContainerComponent: FieldContainerComponent,
|
|
1858
|
+
FileDropzoneDirective: FileDropzoneDirective,
|
|
1859
|
+
FileUploadFieldComponent: FileUploadFieldComponent,
|
|
1860
|
+
HeadingComponent: HeadingComponent,
|
|
1861
|
+
ImageUploadFieldComponent: ImageUploadFieldComponent,
|
|
1862
|
+
InfoComponent: InfoComponent,
|
|
1863
|
+
InputFieldComponent: InputFieldComponent,
|
|
1864
|
+
MapFieldComponent: MapFieldComponent,
|
|
1865
|
+
OptionComponent: OptionComponent,
|
|
1866
|
+
RadioFieldComponent: RadioFieldComponent,
|
|
1867
|
+
SelectFieldComponent: SelectFieldComponent,
|
|
1868
|
+
SelectedFileComponent: SelectedFileComponent,
|
|
1869
|
+
SliderFieldComponent: SliderFieldComponent,
|
|
1870
|
+
TextareaFieldComponent: TextareaFieldComponent,
|
|
1871
|
+
UnitConversionFieldComponent: UnitConversionFieldComponent
|
|
1872
|
+
});
|
|
1873
|
+
|
|
1874
|
+
/**
|
|
1875
|
+
* @deprecated Import the standalone pieces (CrisprFormComponent, FieldContainerComponent, …) directly.
|
|
1876
|
+
* This module will be removed in the next major version.
|
|
1877
|
+
*/
|
|
1878
|
+
class CrisprFormsModule {
|
|
1879
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.6", ngImport: i0, type: CrisprFormsModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule }); }
|
|
1880
|
+
static { this.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "21.1.6", ngImport: i0, type: CrisprFormsModule, imports: [CrisprFormComponent, FieldContainerComponent], exports: [CrisprFormComponent, FieldContainerComponent] }); }
|
|
1881
|
+
static { this.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "21.1.6", ngImport: i0, type: CrisprFormsModule, imports: [CrisprFormComponent, FieldContainerComponent] }); }
|
|
1882
|
+
}
|
|
1883
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.6", ngImport: i0, type: CrisprFormsModule, decorators: [{
|
|
1884
|
+
type: NgModule,
|
|
1885
|
+
args: [{
|
|
1886
|
+
imports: [CrisprFormComponent, FieldContainerComponent],
|
|
1887
|
+
exports: [CrisprFormComponent, FieldContainerComponent],
|
|
1888
|
+
}]
|
|
1889
|
+
}] });
|
|
1890
|
+
|
|
1891
|
+
class CrisprDisplayFieldDirective {
|
|
1892
|
+
constructor() {
|
|
1893
|
+
this.container = inject(ViewContainerRef);
|
|
1894
|
+
this.group = input(...(ngDevMode ? [undefined, { debugName: "group" }] : []));
|
|
1895
|
+
this.value = input(...(ngDevMode ? [undefined, { debugName: "value" }] : []));
|
|
1896
|
+
this.component = input(...(ngDevMode ? [undefined, { debugName: "component" }] : []));
|
|
1897
|
+
this.index = input(...(ngDevMode ? [undefined, { debugName: "index" }] : []));
|
|
1898
|
+
effect(() => {
|
|
1899
|
+
this.updateComponentValue(this.value());
|
|
1900
|
+
});
|
|
1901
|
+
}
|
|
1902
|
+
async ngOnInit() {
|
|
1903
|
+
if (this.component()) {
|
|
1904
|
+
const componentRef = this.container.createComponent(this.component());
|
|
1905
|
+
this.componentRef = componentRef;
|
|
1906
|
+
this.componentInstance = componentRef.instance;
|
|
1907
|
+
this.componentInstance.index = this.index();
|
|
1908
|
+
this.updateComponentValue(this.value());
|
|
1909
|
+
}
|
|
1910
|
+
}
|
|
1911
|
+
updateComponentValue(value) {
|
|
1912
|
+
if (!this.componentRef)
|
|
1913
|
+
return;
|
|
1914
|
+
this.componentRef.setInput('value', value);
|
|
1915
|
+
}
|
|
1916
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.6", ngImport: i0, type: CrisprDisplayFieldDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
|
|
1917
|
+
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "21.1.6", type: CrisprDisplayFieldDirective, isStandalone: true, selector: "[crisprDisplayField]", inputs: { group: { classPropertyName: "group", publicName: "group", isSignal: true, isRequired: false, transformFunction: null }, value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, component: { classPropertyName: "component", publicName: "component", isSignal: true, isRequired: false, transformFunction: null }, index: { classPropertyName: "index", publicName: "index", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0 }); }
|
|
1918
|
+
}
|
|
1919
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.6", ngImport: i0, type: CrisprDisplayFieldDirective, decorators: [{
|
|
1920
|
+
type: Directive,
|
|
1921
|
+
args: [{
|
|
1922
|
+
selector: '[crisprDisplayField]',
|
|
1923
|
+
standalone: true,
|
|
1924
|
+
}]
|
|
1925
|
+
}], ctorParameters: () => [], propDecorators: { group: [{ type: i0.Input, args: [{ isSignal: true, alias: "group", required: false }] }], value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }], component: [{ type: i0.Input, args: [{ isSignal: true, alias: "component", required: false }] }], index: [{ type: i0.Input, args: [{ isSignal: true, alias: "index", required: false }] }] } });
|
|
1926
|
+
|
|
1927
|
+
const defaultConfig = {
|
|
1928
|
+
addButtonLabel: 'ADD ITEM',
|
|
1929
|
+
minListLength: 1,
|
|
1930
|
+
};
|
|
1931
|
+
class FormGroupListComponent extends CrisprControlComponent {
|
|
1932
|
+
constructor() {
|
|
1933
|
+
super(...arguments);
|
|
1934
|
+
this.defaultConfig = defaultConfig;
|
|
1935
|
+
/**
|
|
1936
|
+
* By setting this to true when adding/removing items from array,
|
|
1937
|
+
* we can block original values from overwriting form values
|
|
1938
|
+
* */
|
|
1939
|
+
this.blockValue = signal(false, ...(ngDevMode ? [{ debugName: "blockValue" }] : []));
|
|
1940
|
+
this.selectedIndex = signal(null, ...(ngDevMode ? [{ debugName: "selectedIndex" }] : []));
|
|
1941
|
+
/** The item groups of the list's form array, typed for the template bindings. */
|
|
1942
|
+
this.itemGroups = computed(() => this.control()?.controls, ...(ngDevMode ? [{ debugName: "itemGroups" }] : []));
|
|
1943
|
+
}
|
|
1944
|
+
ngOnInit() {
|
|
1945
|
+
super.ngOnInit();
|
|
1946
|
+
// TODO: this is required to ngFor of controls in template, must be a better way
|
|
1947
|
+
}
|
|
1948
|
+
setControlValue(values) {
|
|
1949
|
+
const formArray = this.control();
|
|
1950
|
+
if (formArray) {
|
|
1951
|
+
if (values?.length > 0) {
|
|
1952
|
+
// clear any existing values so that new values don't concatenate onto the old
|
|
1953
|
+
const currentValues = formArray.value;
|
|
1954
|
+
if (currentValues.length) {
|
|
1955
|
+
formArray.clear();
|
|
1956
|
+
}
|
|
1957
|
+
values.forEach((value) => this.addGroup(value));
|
|
1958
|
+
}
|
|
1959
|
+
else if (this.config().displayInitialItem) {
|
|
1960
|
+
this.addGroup();
|
|
1961
|
+
}
|
|
1962
|
+
}
|
|
1963
|
+
}
|
|
1964
|
+
clickDelete(index) {
|
|
1965
|
+
this.blockValue.set(true);
|
|
1966
|
+
this.deleteGroup(index);
|
|
1967
|
+
}
|
|
1968
|
+
clickAdd() {
|
|
1969
|
+
this.blockValue.set(true);
|
|
1970
|
+
this.addGroup();
|
|
1971
|
+
}
|
|
1972
|
+
addGroup(value = null) {
|
|
1973
|
+
const formArray = this.control();
|
|
1974
|
+
formArray.push(createControlForType(this.config().itemConfig, value));
|
|
1975
|
+
}
|
|
1976
|
+
deleteGroup(index) {
|
|
1977
|
+
const formArray = this.control();
|
|
1978
|
+
formArray.removeAt(index);
|
|
1979
|
+
}
|
|
1980
|
+
clickEditItem(index) {
|
|
1981
|
+
this.selectedIndex.set(index);
|
|
1982
|
+
}
|
|
1983
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.6", ngImport: i0, type: FormGroupListComponent, deps: null, target: i0.ɵɵFactoryTarget.Component }); }
|
|
1984
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.6", type: FormGroupListComponent, isStandalone: true, selector: "crispr-form-group-list", usesInheritance: true, ngImport: i0, template: "<crispr-field-container [config]=\"config()\" [group]=\"group()\" [formGroup]=\"group()\">\n\n <div [formArrayName]=\"config().controlName\">\n\n <!-- Don't add trackBy function, it confuses the state of the array -->\n @for (itemControl of itemGroups(); track itemControl; let i = $index; let last = $last) {\n @if (!config().displayItemComponent || i === selectedIndex() || (last && selectedIndex() ?? true)) {\n <!-- TODO: use content projection to allow custom item label component -->\n <div class=\"item-header\">\n <span class=\"mat-h2\"> {{ config().itemLabelBuilder ? config().itemLabelBuilder(i) : 'Item ' + (i + 1) }} </span>\n @if (i + 1 > config().minListLength) {\n <button mat-icon-button type=\"button\">\n <mat-icon (click)=\"clickDelete(i)\">delete</mat-icon>\n </button>\n }\n </div>\n <div [formGroup]=\"itemControl\">\n <!-- TODO: if chiplists didn't need to have labels mixed in with their values we could use [value]=\"control.value\" below -->\n @for (field of config().itemConfig.fields; track field) {\n <ng-container crisprField [value]=\"blockValue() ? null : value()?.[i] | initialControlValue : field\"\n [config]=\"field\" [group]=\"itemControl\" [testIdPrefix]=\"testId() + '[' + i + ']'\" />\n }\n </div>\n } @else {\n <div class=\"display-field-container\" crisprDisplayField [component]=\"config().displayItemComponent\"\n [value]=\"itemControl.value\" [index]=\"i\">\n <button mat-icon-button (click)=\"clickEditItem(i)\">\n <mat-icon>edit</mat-icon>\n </button>\n </div>\n }\n <ng-template #display>\n <div class=\"display-field-container\" crisprDisplayField [component]=\"config().displayItemComponent\"\n [value]=\"itemControl.value\" [index]=\"i\">\n <button mat-icon-button (click)=\"clickEditItem(i)\">\n <mat-icon>edit</mat-icon>\n </button>\n </div>\n </ng-template>\n }\n </div>\n <div class=\"group-footer\">\n <button type=\"button\" class=\"add\" mat-raised-button [color]=\"config().addButtonColor\" (click)=\"clickAdd()\">\n {{config().addItemLabel || config().addButtonLabel}}\n </button>\n </div>\n\n</crispr-field-container>", styles: [".item-header{display:flex;justify-content:space-between}.group-footer{display:flex;justify-content:flex-end}.group-footer button.add{margin-bottom:16px}.display-field-container{position:relative}.display-field-container button{position:absolute;top:0;right:0}\n"], dependencies: [{ kind: "ngmodule", type: MatButtonModule }, { kind: "component", type: i3.MatButton, selector: " button[matButton], a[matButton], button[mat-button], button[mat-raised-button], button[mat-flat-button], button[mat-stroked-button], a[mat-button], a[mat-raised-button], a[mat-flat-button], a[mat-stroked-button] ", inputs: ["matButton"], exportAs: ["matButton", "matAnchor"] }, { kind: "component", type: i3.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: "component", type: FieldContainerComponent, selector: "crispr-field-container", inputs: ["config", "group", "inlineField"] }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1$1.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],[formArray],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i1$1.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "directive", type: i1$1.FormArrayName, selector: "[formArrayName]", inputs: ["formArrayName"] }, { kind: "directive", type: CrisprFieldDirective, selector: "[crisprField]", inputs: ["config", "group", "testIdPrefix", "value"] }, { kind: "directive", type: CrisprDisplayFieldDirective, selector: "[crisprDisplayField]", inputs: ["group", "value", "component", "index"] }, { kind: "directive", type: ControlErrorContainerDirective, selector: "[controlErrorContainer], [formGroup]" }, { kind: "pipe", type: InitialControlValuePipe, name: "initialControlValue" }], changeDetection: i0.ChangeDetectionStrategy.OnPush, preserveWhitespaces: true }); }
|
|
1985
|
+
}
|
|
1986
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.6", ngImport: i0, type: FormGroupListComponent, decorators: [{
|
|
1987
|
+
type: Component,
|
|
1988
|
+
args: [{ selector: 'crispr-form-group-list', changeDetection: ChangeDetectionStrategy.OnPush, imports: [
|
|
1989
|
+
MatButtonModule,
|
|
1990
|
+
MatIconModule,
|
|
1991
|
+
FieldContainerComponent,
|
|
1992
|
+
ReactiveFormsModule,
|
|
1993
|
+
CrisprFieldDirective,
|
|
1994
|
+
CrisprDisplayFieldDirective,
|
|
1995
|
+
InitialControlValuePipe,
|
|
1996
|
+
ControlErrorContainerDirective
|
|
1997
|
+
], standalone: true, template: "<crispr-field-container [config]=\"config()\" [group]=\"group()\" [formGroup]=\"group()\">\n\n <div [formArrayName]=\"config().controlName\">\n\n <!-- Don't add trackBy function, it confuses the state of the array -->\n @for (itemControl of itemGroups(); track itemControl; let i = $index; let last = $last) {\n @if (!config().displayItemComponent || i === selectedIndex() || (last && selectedIndex() ?? true)) {\n <!-- TODO: use content projection to allow custom item label component -->\n <div class=\"item-header\">\n <span class=\"mat-h2\"> {{ config().itemLabelBuilder ? config().itemLabelBuilder(i) : 'Item ' + (i + 1) }} </span>\n @if (i + 1 > config().minListLength) {\n <button mat-icon-button type=\"button\">\n <mat-icon (click)=\"clickDelete(i)\">delete</mat-icon>\n </button>\n }\n </div>\n <div [formGroup]=\"itemControl\">\n <!-- TODO: if chiplists didn't need to have labels mixed in with their values we could use [value]=\"control.value\" below -->\n @for (field of config().itemConfig.fields; track field) {\n <ng-container crisprField [value]=\"blockValue() ? null : value()?.[i] | initialControlValue : field\"\n [config]=\"field\" [group]=\"itemControl\" [testIdPrefix]=\"testId() + '[' + i + ']'\" />\n }\n </div>\n } @else {\n <div class=\"display-field-container\" crisprDisplayField [component]=\"config().displayItemComponent\"\n [value]=\"itemControl.value\" [index]=\"i\">\n <button mat-icon-button (click)=\"clickEditItem(i)\">\n <mat-icon>edit</mat-icon>\n </button>\n </div>\n }\n <ng-template #display>\n <div class=\"display-field-container\" crisprDisplayField [component]=\"config().displayItemComponent\"\n [value]=\"itemControl.value\" [index]=\"i\">\n <button mat-icon-button (click)=\"clickEditItem(i)\">\n <mat-icon>edit</mat-icon>\n </button>\n </div>\n </ng-template>\n }\n </div>\n <div class=\"group-footer\">\n <button type=\"button\" class=\"add\" mat-raised-button [color]=\"config().addButtonColor\" (click)=\"clickAdd()\">\n {{config().addItemLabel || config().addButtonLabel}}\n </button>\n </div>\n\n</crispr-field-container>", styles: [".item-header{display:flex;justify-content:space-between}.group-footer{display:flex;justify-content:flex-end}.group-footer button.add{margin-bottom:16px}.display-field-container{position:relative}.display-field-container button{position:absolute;top:0;right:0}\n"] }]
|
|
1998
|
+
}] });
|
|
1999
|
+
|
|
2000
|
+
var index$1 = /*#__PURE__*/Object.freeze({
|
|
2001
|
+
__proto__: null,
|
|
2002
|
+
FormGroupListComponent: FormGroupListComponent
|
|
2003
|
+
});
|
|
2004
|
+
|
|
2005
|
+
class SubGroupComponent extends CrisprControlComponent {
|
|
2006
|
+
constructor() {
|
|
2007
|
+
super(...arguments);
|
|
2008
|
+
/** The sub group's control, typed for the nested `[group]` binding. */
|
|
2009
|
+
this.subGroup = computed(() => this.control(), ...(ngDevMode ? [{ debugName: "subGroup" }] : []));
|
|
2010
|
+
}
|
|
2011
|
+
ngOnInit() {
|
|
2012
|
+
super.ngOnInit();
|
|
2013
|
+
}
|
|
2014
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.6", ngImport: i0, type: SubGroupComponent, deps: null, target: i0.ɵɵFactoryTarget.Component }); }
|
|
2015
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.6", type: SubGroupComponent, isStandalone: true, selector: "crispr-sub-group", usesInheritance: true, ngImport: i0, template: "<crispr-field-container [config]=\"config()\" [group]=\"group()\">\n @for (fieldConfig of config().fields; track fieldConfig) {\n <ng-container crisprField [value]=\"value() | initialControlValue : fieldConfig\" [config]=\"fieldConfig\"\n [group]=\"subGroup()\" [testIdPrefix]=\"testId()\" />\n }\n</crispr-field-container>", styles: [""], dependencies: [{ kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: CrisprFieldDirective, selector: "[crisprField]", inputs: ["config", "group", "testIdPrefix", "value"] }, { kind: "component", type: FieldContainerComponent, selector: "crispr-field-container", inputs: ["config", "group", "inlineField"] }, { kind: "pipe", type: InitialControlValuePipe, name: "initialControlValue" }], changeDetection: i0.ChangeDetectionStrategy.OnPush, preserveWhitespaces: true }); }
|
|
2016
|
+
}
|
|
2017
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.6", ngImport: i0, type: SubGroupComponent, decorators: [{
|
|
2018
|
+
type: Component,
|
|
2019
|
+
args: [{ selector: 'crispr-sub-group', changeDetection: ChangeDetectionStrategy.OnPush, standalone: true, imports: [
|
|
2020
|
+
ReactiveFormsModule,
|
|
2021
|
+
CrisprFieldDirective,
|
|
2022
|
+
FieldContainerComponent,
|
|
2023
|
+
InitialControlValuePipe
|
|
2024
|
+
], template: "<crispr-field-container [config]=\"config()\" [group]=\"group()\">\n @for (fieldConfig of config().fields; track fieldConfig) {\n <ng-container crisprField [value]=\"value() | initialControlValue : fieldConfig\" [config]=\"fieldConfig\"\n [group]=\"subGroup()\" [testIdPrefix]=\"testId()\" />\n }\n</crispr-field-container>" }]
|
|
2025
|
+
}] });
|
|
2026
|
+
|
|
2027
|
+
/**
|
|
2028
|
+
* @deprecated Import the standalone SubGroupComponent directly.
|
|
2029
|
+
* This module will be removed in the next major version.
|
|
2030
|
+
*/
|
|
2031
|
+
class SubGroupModule {
|
|
2032
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.6", ngImport: i0, type: SubGroupModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule }); }
|
|
2033
|
+
static { this.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "21.1.6", ngImport: i0, type: SubGroupModule, imports: [SubGroupComponent], exports: [SubGroupComponent] }); }
|
|
2034
|
+
static { this.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "21.1.6", ngImport: i0, type: SubGroupModule, imports: [SubGroupComponent] }); }
|
|
2035
|
+
}
|
|
2036
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.6", ngImport: i0, type: SubGroupModule, decorators: [{
|
|
2037
|
+
type: NgModule,
|
|
2038
|
+
args: [{
|
|
2039
|
+
imports: [SubGroupComponent],
|
|
2040
|
+
exports: [SubGroupComponent],
|
|
2041
|
+
}]
|
|
2042
|
+
}] });
|
|
2043
|
+
|
|
2044
|
+
var index = /*#__PURE__*/Object.freeze({
|
|
2045
|
+
__proto__: null,
|
|
2046
|
+
SubGroupComponent: SubGroupComponent,
|
|
2047
|
+
SubGroupModule: SubGroupModule
|
|
2048
|
+
});
|
|
2049
|
+
|
|
2050
|
+
/**
|
|
2051
|
+
* @deprecated Import the standalone InitialControlValuePipe directly.
|
|
2052
|
+
* This module will be removed in the next major version.
|
|
2053
|
+
*/
|
|
2054
|
+
class CrisprPipesModule {
|
|
2055
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.6", ngImport: i0, type: CrisprPipesModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule }); }
|
|
2056
|
+
static { this.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "21.1.6", ngImport: i0, type: CrisprPipesModule, imports: [InitialControlValuePipe], exports: [InitialControlValuePipe] }); }
|
|
2057
|
+
static { this.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "21.1.6", ngImport: i0, type: CrisprPipesModule }); }
|
|
2058
|
+
}
|
|
2059
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.6", ngImport: i0, type: CrisprPipesModule, decorators: [{
|
|
2060
|
+
type: NgModule,
|
|
2061
|
+
args: [{
|
|
2062
|
+
imports: [InitialControlValuePipe],
|
|
2063
|
+
exports: [InitialControlValuePipe],
|
|
2064
|
+
}]
|
|
2065
|
+
}] });
|
|
2066
|
+
|
|
2067
|
+
/**
|
|
2068
|
+
* Generated bundle index. Do not edit.
|
|
2069
|
+
*/
|
|
2070
|
+
|
|
2071
|
+
export { AbstractAutocompleteComponent, AutocompleteChiplistFieldComponent, AutocompleteFieldComponent, ButtonComponent, CheckboxFieldComponent, ControlType, CrisprControlComponent, CrisprDisplayFieldComponent, CrisprDisplayFieldDirective, CrisprFieldComponent, CrisprFieldDirective, CrisprFormComponent, CrisprFormsModule, CrisprPipesModule, DEFAULT_EMPTY_OPTIONS_MESSAGE, DatepickerFieldComponent, DividerComponent, FieldContainerComponent, FileDropzoneDirective, FileUploadFieldComponent, FormGroupListComponent, HeadingComponent, ImageUploadFieldComponent, InfoComponent, InitialControlValuePipe, InputFieldComponent, MapFieldComponent, OptionComponent, RadioFieldComponent, SelectFieldComponent, SelectedFileComponent, SliderFieldComponent, SubGroupComponent, SubGroupModule, TextareaFieldComponent, UnitConversionFieldComponent, allowedFileExtValidator, allowedFileType, buildFormGroupFromConfig, callOptionsIfFunction, convertBytesToMb, createControlForType, filterOptionsByLabel, isControlConfig, maxFileSizeValidator, observablifyOptions, valueIn };
|
|
2
2072
|
//# sourceMappingURL=tft-crispr-forms.mjs.map
|