@tft/crispr-forms 21.0.18 → 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.
Files changed (39) hide show
  1. package/README.md +21 -4
  2. package/fesm2022/tft-crispr-forms.mjs +2071 -1
  3. package/fesm2022/tft-crispr-forms.mjs.map +1 -1
  4. package/package.json +7 -7
  5. package/types/tft-crispr-forms.d.ts +238 -212
  6. package/fesm2022/tft-crispr-forms-autocomplete-field.component-BgQZYqB0.mjs +0 -95
  7. package/fesm2022/tft-crispr-forms-autocomplete-field.component-BgQZYqB0.mjs.map +0 -1
  8. package/fesm2022/tft-crispr-forms-index-3aw152gK.mjs +0 -18
  9. package/fesm2022/tft-crispr-forms-index-3aw152gK.mjs.map +0 -1
  10. package/fesm2022/tft-crispr-forms-index-BFzx83fR.mjs +0 -39
  11. package/fesm2022/tft-crispr-forms-index-BFzx83fR.mjs.map +0 -1
  12. package/fesm2022/tft-crispr-forms-index-BMZQakFs.mjs +0 -88
  13. package/fesm2022/tft-crispr-forms-index-BMZQakFs.mjs.map +0 -1
  14. package/fesm2022/tft-crispr-forms-index-B_lhA83w.mjs +0 -2
  15. package/fesm2022/tft-crispr-forms-index-B_lhA83w.mjs.map +0 -1
  16. package/fesm2022/tft-crispr-forms-index-BawhjTMp.mjs +0 -183
  17. package/fesm2022/tft-crispr-forms-index-BawhjTMp.mjs.map +0 -1
  18. package/fesm2022/tft-crispr-forms-index-CXXD0vdZ.mjs +0 -172
  19. package/fesm2022/tft-crispr-forms-index-CXXD0vdZ.mjs.map +0 -1
  20. package/fesm2022/tft-crispr-forms-index-CmVahfkJ.mjs +0 -24
  21. package/fesm2022/tft-crispr-forms-index-CmVahfkJ.mjs.map +0 -1
  22. package/fesm2022/tft-crispr-forms-index-CoUwTugQ.mjs +0 -309
  23. package/fesm2022/tft-crispr-forms-index-CoUwTugQ.mjs.map +0 -1
  24. package/fesm2022/tft-crispr-forms-index-D99OHDPc.mjs +0 -40
  25. package/fesm2022/tft-crispr-forms-index-D99OHDPc.mjs.map +0 -1
  26. package/fesm2022/tft-crispr-forms-index-DYWkX7D3.mjs +0 -83
  27. package/fesm2022/tft-crispr-forms-index-DYWkX7D3.mjs.map +0 -1
  28. package/fesm2022/tft-crispr-forms-index-DysioRya.mjs +0 -39
  29. package/fesm2022/tft-crispr-forms-index-DysioRya.mjs.map +0 -1
  30. package/fesm2022/tft-crispr-forms-index-GlYQbbbv.mjs +0 -36
  31. package/fesm2022/tft-crispr-forms-index-GlYQbbbv.mjs.map +0 -1
  32. package/fesm2022/tft-crispr-forms-index-UX-Wdyk1.mjs +0 -40
  33. package/fesm2022/tft-crispr-forms-index-UX-Wdyk1.mjs.map +0 -1
  34. package/fesm2022/tft-crispr-forms-index-bTdYHJPk.mjs +0 -68
  35. package/fesm2022/tft-crispr-forms-index-bTdYHJPk.mjs.map +0 -1
  36. package/fesm2022/tft-crispr-forms-index-kah2HT_N.mjs +0 -54
  37. package/fesm2022/tft-crispr-forms-index-kah2HT_N.mjs.map +0 -1
  38. package/fesm2022/tft-crispr-forms-tft-crispr-forms-8liD7eof.mjs +0 -943
  39. package/fesm2022/tft-crispr-forms-tft-crispr-forms-8liD7eof.mjs.map +0 -1
@@ -1,2 +1,2072 @@
1
- export { A as AbstractAutocompleteComponent, d as ControlType, a as CrisprControlComponent, g as CrisprDisplayFieldComponent, C as CrisprFieldComponent, h as CrisprFormComponent, i as CrisprFormsModule, D as DEFAULT_EMPTY_OPTIONS_MESSAGE, F as FieldContainerComponent, f as FileDropzoneDirective, j as FileUploadFieldComponent, H as HeadingComponent, I as InfoComponent, S as SelectedFileComponent, k as allowedFileExtValidator, l as allowedFileType, b as buildFormGroupFromConfig, m as callOptionsIfFunction, e as convertBytesToMb, n as createControlForType, p as filterOptionsByLabel, q as isControlConfig, r as maxFileSizeValidator, o as observablifyOptions, v as valueIn } from './tft-crispr-forms-tft-crispr-forms-8liD7eof.mjs';
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