@koobiq/components 20.3.1 → 20.3.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/accordion/accordion.scss +4 -1
- package/accordion/index.d.ts +36 -12
- package/alert/index.d.ts +8 -0
- package/breadcrumbs/index.d.ts +2 -0
- package/button-toggle/button-toggle-tokens.scss +14 -0
- package/button-toggle/button-toggle.scss +75 -3
- package/core/index.d.ts +33 -1
- package/core/styles/common/_select.scss +98 -3
- package/core/styles/typography/_typography.scss +8 -0
- package/ellipsis-center/index.d.ts +35 -2
- package/fesm2022/koobiq-components-accordion.mjs +143 -44
- package/fesm2022/koobiq-components-accordion.mjs.map +1 -1
- package/fesm2022/koobiq-components-alert.mjs +17 -1
- package/fesm2022/koobiq-components-alert.mjs.map +1 -1
- package/fesm2022/koobiq-components-autocomplete.mjs +2 -2
- package/fesm2022/koobiq-components-autocomplete.mjs.map +1 -1
- package/fesm2022/koobiq-components-breadcrumbs.mjs +8 -3
- package/fesm2022/koobiq-components-breadcrumbs.mjs.map +1 -1
- package/fesm2022/koobiq-components-button-toggle.mjs +2 -2
- package/fesm2022/koobiq-components-button-toggle.mjs.map +1 -1
- package/fesm2022/koobiq-components-code-block.mjs +10 -5
- package/fesm2022/koobiq-components-code-block.mjs.map +1 -1
- package/fesm2022/koobiq-components-core.mjs +43 -5
- package/fesm2022/koobiq-components-core.mjs.map +1 -1
- package/fesm2022/koobiq-components-dropdown.mjs +2 -2
- package/fesm2022/koobiq-components-dropdown.mjs.map +1 -1
- package/fesm2022/koobiq-components-ellipsis-center.mjs +77 -22
- package/fesm2022/koobiq-components-ellipsis-center.mjs.map +1 -1
- package/fesm2022/koobiq-components-file-upload.mjs +7 -7
- package/fesm2022/koobiq-components-file-upload.mjs.map +1 -1
- package/fesm2022/koobiq-components-filter-bar.mjs +76 -41
- package/fesm2022/koobiq-components-filter-bar.mjs.map +1 -1
- package/fesm2022/koobiq-components-list.mjs +3 -3
- package/fesm2022/koobiq-components-list.mjs.map +1 -1
- package/fesm2022/koobiq-components-markdown.mjs +6 -2
- package/fesm2022/koobiq-components-markdown.mjs.map +1 -1
- package/fesm2022/koobiq-components-modal.mjs +48 -7
- package/fesm2022/koobiq-components-modal.mjs.map +1 -1
- package/fesm2022/koobiq-components-select.mjs +98 -11
- package/fesm2022/koobiq-components-select.mjs.map +1 -1
- package/fesm2022/koobiq-components-sidebar.mjs +5 -1
- package/fesm2022/koobiq-components-sidebar.mjs.map +1 -1
- package/fesm2022/koobiq-components-sidepanel.mjs +25 -6
- package/fesm2022/koobiq-components-sidepanel.mjs.map +1 -1
- package/fesm2022/koobiq-components-tabs.mjs +7 -1
- package/fesm2022/koobiq-components-tabs.mjs.map +1 -1
- package/fesm2022/koobiq-components-time-range.mjs +388 -74
- package/fesm2022/koobiq-components-time-range.mjs.map +1 -1
- package/fesm2022/koobiq-components-timezone.mjs +2 -2
- package/fesm2022/koobiq-components-timezone.mjs.map +1 -1
- package/fesm2022/koobiq-components-title.mjs +15 -3
- package/fesm2022/koobiq-components-title.mjs.map +1 -1
- package/fesm2022/koobiq-components-tree-select.mjs +2 -2
- package/fesm2022/koobiq-components-tree-select.mjs.map +1 -1
- package/fesm2022/koobiq-components-tree.mjs +14 -4
- package/fesm2022/koobiq-components-tree.mjs.map +1 -1
- package/file-upload/file-upload.scss +11 -0
- package/file-upload/index.d.ts +1 -1
- package/file-upload/multiple-file-upload.component.scss +15 -3
- package/file-upload/single-file-upload.component.scss +10 -2
- package/filter-bar/filter-bar-tokens.scss +2 -1
- package/filter-bar/index.d.ts +25 -4
- package/filter-bar/pipes/base-pipe.scss +13 -6
- package/filter-bar/pipes/pipe-input.scss +3 -1
- package/link/_link-theme.scss +50 -15
- package/markdown/index.d.ts +2 -0
- package/modal/index.d.ts +16 -7
- package/navbar/_navbar-item_horizontal.scss +5 -0
- package/package.json +3 -3
- package/prebuilt-themes/dark-theme.css +1 -1
- package/prebuilt-themes/light-theme.css +1 -1
- package/prebuilt-themes/theme.css +1 -1
- package/schematics/migrations/alert-signals/index.js +4 -1
- package/schematics/migrations/autocomplete-panel-width-auto/index.js +3 -34
- package/schematics/migrations/empty-state-size-attr/index.js +3 -34
- package/schematics/migrations/filter-bar-signals/index.js +4 -1
- package/schematics/migrations/loader-overlay-size-attr/index.js +3 -34
- package/schematics/migrations/v20-upgrade/index.js +4 -1
- package/schematics/ng-add/index.js +1 -1
- package/schematics/utils/angular-parsing.js +11 -12
- package/schematics/utils/package-config.js +5 -4
- package/schematics/utils/typescript.js +24 -0
- package/select/_select-theme.scss +9 -1
- package/select/index.d.ts +12 -2
- package/select/select.scss +13 -0
- package/sidebar/index.d.ts +1 -0
- package/sidepanel/index.d.ts +9 -0
- package/tabs/index.d.ts +1 -0
- package/time-range/index.d.ts +140 -5
- package/time-range/time-range.scss +16 -1
- package/title/index.d.ts +10 -2
- package/tree/index.d.ts +1 -0
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import * as i0 from '@angular/core';
|
|
2
|
-
import { InjectionToken,
|
|
2
|
+
import { InjectionToken, Injectable, inject, signal, computed, ElementRef, input, viewChildren, ViewEncapsulation, ChangeDetectionStrategy, Component, Injector, booleanAttribute, output, Directive, NgModule } from '@angular/core';
|
|
3
|
+
import { DOCUMENT, NgTemplateOutlet } from '@angular/common';
|
|
3
4
|
import { takeUntilDestroyed, toObservable } from '@angular/core/rxjs-interop';
|
|
4
5
|
import * as i1 from '@angular/forms';
|
|
5
6
|
import { FormGroup, FormControl, ReactiveFormsModule, NG_VALUE_ACCESSOR, NG_VALIDATORS, NgControl } from '@angular/forms';
|
|
@@ -8,21 +9,20 @@ import { KbqButtonModule } from '@koobiq/components/button';
|
|
|
8
9
|
import { DateAdapter, DateFormatter, ruRULocaleData, KBQ_LOCALE_SERVICE, PopUpSizes, PopUpPlacements } from '@koobiq/components/core';
|
|
9
10
|
import * as i2$2 from '@koobiq/components/popover';
|
|
10
11
|
import { KbqPopoverTrigger, KbqPopoverModule } from '@koobiq/components/popover';
|
|
11
|
-
import { NgTemplateOutlet } from '@angular/common';
|
|
12
12
|
import * as i2 from '@koobiq/components/datepicker';
|
|
13
13
|
import { KbqDatepickerModule } from '@koobiq/components/datepicker';
|
|
14
14
|
import * as i3 from '@koobiq/components/form-field';
|
|
15
|
-
import { KbqFieldset, KbqFieldsetItem, KbqFormFieldControl } from '@koobiq/components/form-field';
|
|
15
|
+
import { KbqError, KbqFieldset, KbqFieldsetItem, KbqHint, KbqFormFieldControl } from '@koobiq/components/form-field';
|
|
16
16
|
import * as i2$1 from '@koobiq/components/icon';
|
|
17
17
|
import { KbqIcon, KbqIconModule } from '@koobiq/components/icon';
|
|
18
18
|
import * as i5 from '@koobiq/components/radio';
|
|
19
19
|
import { KbqRadioModule } from '@koobiq/components/radio';
|
|
20
20
|
import * as i4 from '@koobiq/components/timepicker';
|
|
21
21
|
import { KbqTimepicker, TimeFormats, KbqTimepickerModule } from '@koobiq/components/timepicker';
|
|
22
|
-
import { merge, Subject } from 'rxjs';
|
|
23
22
|
import { map, distinctUntilChanged } from 'rxjs/operators';
|
|
24
23
|
import * as i1$1 from '@koobiq/components/link';
|
|
25
24
|
import { KbqLinkModule } from '@koobiq/components/link';
|
|
25
|
+
import { Subject } from 'rxjs';
|
|
26
26
|
|
|
27
27
|
/** Preset of selectable time ranges, provided by DI. */
|
|
28
28
|
const KBQ_DEFAULT_TIME_RANGE_TYPES = new InjectionToken('KBQ_DEFAULT_TIME_RANGE_TYPES');
|
|
@@ -59,12 +59,39 @@ const rangeValidator = (timeRangeService) => {
|
|
|
59
59
|
}
|
|
60
60
|
const fromDateTime = timeRangeService.combineDateAndTime(form.fromDate, form.fromTime);
|
|
61
61
|
const toDateTime = timeRangeService.combineDateAndTime(form.toDate, form.toTime);
|
|
62
|
-
return timeRangeService.dateAdapter.
|
|
62
|
+
return timeRangeService.dateAdapter.compareDateTime(fromDateTime, toDateTime) > 0
|
|
63
63
|
? { fromIsGreaterThanTo: fromDateTime }
|
|
64
64
|
: null;
|
|
65
65
|
};
|
|
66
66
|
};
|
|
67
67
|
|
|
68
|
+
/**
|
|
69
|
+
* Channel between the popover footer, which `KbqTimeRange` owns, and the editor rendered inside the
|
|
70
|
+
* popover. Provided by `KbqTimeRange`, so there is exactly one instance per component.
|
|
71
|
+
*
|
|
72
|
+
* @docs-private
|
|
73
|
+
*/
|
|
74
|
+
class KbqTimeRangeEditorBridge {
|
|
75
|
+
constructor() {
|
|
76
|
+
/**
|
|
77
|
+
* Raised while a pointer is held down on the footer. `focusout` on its own cannot tell "the user is
|
|
78
|
+
* pressing apply" apart from "the user clicked a blank spot": Safari does not move focus onto a
|
|
79
|
+
* `<button>` when it is clicked, so both arrive with a `null` `relatedTarget`.
|
|
80
|
+
*/
|
|
81
|
+
this.applyGestureInProgress = false;
|
|
82
|
+
/**
|
|
83
|
+
* Registered by the editor. Reveals the errors of both borders and moves focus to the first field
|
|
84
|
+
* carrying one; returns whether the editor is free of errors.
|
|
85
|
+
*/
|
|
86
|
+
this.revealErrors = () => true;
|
|
87
|
+
}
|
|
88
|
+
/** @nocollapse */ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.30", ngImport: i0, type: KbqTimeRangeEditorBridge, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
89
|
+
/** @nocollapse */ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.30", ngImport: i0, type: KbqTimeRangeEditorBridge }); }
|
|
90
|
+
}
|
|
91
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.30", ngImport: i0, type: KbqTimeRangeEditorBridge, decorators: [{
|
|
92
|
+
type: Injectable
|
|
93
|
+
}] });
|
|
94
|
+
|
|
68
95
|
class KbqTimeRangeService {
|
|
69
96
|
constructor() {
|
|
70
97
|
// Optional so the constructor can name the missing provider instead of letting DI throw a bare
|
|
@@ -74,6 +101,18 @@ class KbqTimeRangeService {
|
|
|
74
101
|
this.providedDefaultTimeRangeTypes = inject(KBQ_DEFAULT_TIME_RANGE_TYPES, { optional: true }) || defaultTimeRangeTypes;
|
|
75
102
|
this.customTimeRangeTypes = inject(KBQ_CUSTOM_TIME_RANGE_TYPES, { optional: true });
|
|
76
103
|
this.DEFAULT_RANGE_TYPE = 'lastHour';
|
|
104
|
+
this.boundsSource = signal({
|
|
105
|
+
min: signal(null),
|
|
106
|
+
max: signal(null)
|
|
107
|
+
}, ...(ngDevMode ? [{ debugName: "boundsSource" }] : []));
|
|
108
|
+
/**
|
|
109
|
+
* Bounds of the owning component, so that every default and every check reads one pair. Both are
|
|
110
|
+
* exact instants rather than whole days: a `maxDate` that should admit its own day has to carry the
|
|
111
|
+
* end of that day.
|
|
112
|
+
*/
|
|
113
|
+
this.minDate = computed(() => this.boundsSource().min() ?? null, ...(ngDevMode ? [{ debugName: "minDate" }] : []));
|
|
114
|
+
/** @see minDate */
|
|
115
|
+
this.maxDate = computed(() => this.boundsSource().max() ?? null, ...(ngDevMode ? [{ debugName: "maxDate" }] : []));
|
|
77
116
|
this.timeRangeConfig = {
|
|
78
117
|
lastMinute: { units: { minutes: -1 }, translationType: 'minutes' },
|
|
79
118
|
last5Minutes: { units: { minutes: -5 }, translationType: 'minutes' },
|
|
@@ -111,21 +150,39 @@ class KbqTimeRangeService {
|
|
|
111
150
|
add({ type, ...customTimeRangeConfig }) {
|
|
112
151
|
this.timeRangeConfig[type] = customTimeRangeConfig;
|
|
113
152
|
}
|
|
153
|
+
/**
|
|
154
|
+
* Hands the owning component's own bound inputs over, rather than a copy of their values: a signal
|
|
155
|
+
* input reads as its initial value until the first change detection, so a mirror would leave every
|
|
156
|
+
* default computed in a constructor clamped against no bounds at all.
|
|
157
|
+
*/
|
|
158
|
+
bindBounds(min, max) {
|
|
159
|
+
this.boundsSource.set({ min, max });
|
|
160
|
+
}
|
|
114
161
|
getTimeRangeTypeUnits(type) {
|
|
115
162
|
return this.timeRangeConfig[type].units;
|
|
116
163
|
}
|
|
117
164
|
getTimeRangeUnitByType(type) {
|
|
118
165
|
return this.timeRangeConfig[type].translationType;
|
|
119
166
|
}
|
|
167
|
+
/**
|
|
168
|
+
* "Yesterday to today", pulled inside {@link minDate}/{@link maxDate} when they are set - otherwise
|
|
169
|
+
* the editor opens on a range it would itself report as out of bounds.
|
|
170
|
+
*
|
|
171
|
+
* Milliseconds are dropped from "today" *before* clamping, so a clamped end lands exactly on its
|
|
172
|
+
* bound rather than a fraction of a second below it.
|
|
173
|
+
*/
|
|
120
174
|
getDefaultRangeValue() {
|
|
121
|
-
const
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
175
|
+
const today = this.omitMilliseconds(this.dateAdapter.today());
|
|
176
|
+
// `from` is derived from the clamped `to`, so the one-day window survives bounds that lie
|
|
177
|
+
// entirely in the past - clamping both ends independently would collapse it onto `maxDate`.
|
|
178
|
+
const to = this.clampToBounds(today);
|
|
179
|
+
const from = this.clampToBounds(this.dateAdapter.addCalendarUnits(to, { days: -1 }));
|
|
180
|
+
// Bounds lying entirely in the future leave no room below `to`, and both ends land on `minDate`.
|
|
181
|
+
// The window is opened upwards instead, which is the same day in the other direction.
|
|
182
|
+
const end = this.dateAdapter.compareDateTime(from, to) === 0
|
|
183
|
+
? this.clampToBounds(this.dateAdapter.addCalendarUnits(from, { days: 1 }))
|
|
184
|
+
: to;
|
|
185
|
+
return { fromTime: from, fromDate: from, toTime: end, toDate: end };
|
|
129
186
|
}
|
|
130
187
|
getTimeRangeDefaultValue(rangeValue, availableTimeRangeTypes = []) {
|
|
131
188
|
const defaultType = availableTimeRangeTypes[0] ?? this.providedDefaultTimeRangeTypes[0] ?? this.DEFAULT_RANGE_TYPE;
|
|
@@ -182,6 +239,30 @@ class KbqTimeRangeService {
|
|
|
182
239
|
}
|
|
183
240
|
return result;
|
|
184
241
|
}
|
|
242
|
+
/** Which bound, if any, an instant falls outside of. */
|
|
243
|
+
checkBounds(dateTime) {
|
|
244
|
+
const minDate = this.minDate();
|
|
245
|
+
const maxDate = this.maxDate();
|
|
246
|
+
if (minDate && this.dateAdapter.compareDateTime(dateTime, minDate) < 0)
|
|
247
|
+
return 'min';
|
|
248
|
+
if (maxDate && this.dateAdapter.compareDateTime(dateTime, maxDate) > 0)
|
|
249
|
+
return 'max';
|
|
250
|
+
return null;
|
|
251
|
+
}
|
|
252
|
+
/**
|
|
253
|
+
* `DateAdapter.clampDate` compares whole days only, while a border is rejected by its time as well -
|
|
254
|
+
* a value clamped to the day would still land outside the bounds.
|
|
255
|
+
*/
|
|
256
|
+
clampToBounds(date) {
|
|
257
|
+
switch (this.checkBounds(date)) {
|
|
258
|
+
case 'min':
|
|
259
|
+
return this.minDate();
|
|
260
|
+
case 'max':
|
|
261
|
+
return this.maxDate();
|
|
262
|
+
default:
|
|
263
|
+
return date;
|
|
264
|
+
}
|
|
265
|
+
}
|
|
185
266
|
omitMilliseconds(date) {
|
|
186
267
|
return this.dateAdapter.createDateTime(this.dateAdapter.getYear(date), this.dateAdapter.getMonth(date), this.dateAdapter.getDate(date), this.dateAdapter.getHours(date), this.dateAdapter.getMinutes(date), this.dateAdapter.getSeconds(date), 0);
|
|
187
268
|
}
|
|
@@ -192,15 +273,25 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.30", ngImpo
|
|
|
192
273
|
type: Injectable
|
|
193
274
|
}], ctorParameters: () => [] });
|
|
194
275
|
|
|
276
|
+
const borders = ['from', 'to'];
|
|
277
|
+
const halves = ['Time', 'Date'];
|
|
278
|
+
/**
|
|
279
|
+
* Errors are reported only once the user has left the border they belong to, and go quiet again as soon
|
|
280
|
+
* as typing resumes. The validators themselves always run, so that `validate()` stays honest about
|
|
281
|
+
* whether the range could be saved - only the painting waits.
|
|
282
|
+
*/
|
|
195
283
|
class RangeErrorStateMatcher {
|
|
196
|
-
isErrorState(control
|
|
197
|
-
return !!
|
|
284
|
+
isErrorState(control) {
|
|
285
|
+
return !!control?.invalid && !!control.touched;
|
|
198
286
|
}
|
|
199
287
|
}
|
|
200
288
|
/** @docs-private */
|
|
201
289
|
class KbqTimeRangeEditor {
|
|
202
290
|
constructor() {
|
|
203
291
|
this.timeRangeService = inject(KbqTimeRangeService);
|
|
292
|
+
this.editorBridge = inject(KbqTimeRangeEditorBridge, { optional: true });
|
|
293
|
+
this.elementRef = inject(ElementRef);
|
|
294
|
+
this.document = inject(DOCUMENT);
|
|
204
295
|
/** The maximum selectable date. */
|
|
205
296
|
this.maxDate = input(null, ...(ngDevMode ? [{ debugName: "maxDate" }] : []));
|
|
206
297
|
/** The minimum selectable date. */
|
|
@@ -227,13 +318,31 @@ class KbqTimeRangeEditor {
|
|
|
227
318
|
formattedValue: this.getFormattedOption(type, localeConfig)
|
|
228
319
|
}));
|
|
229
320
|
}, ...(ngDevMode ? [{ debugName: "timeRangeTypesWithoutRange" }] : []));
|
|
321
|
+
/**
|
|
322
|
+
* Caption under the "range" option, spelling out the bounds the border has to stay within. Read from
|
|
323
|
+
* the service rather than from this component's own inputs, so that it can never disagree with what
|
|
324
|
+
* the border validators go on to enforce.
|
|
325
|
+
* @docs-private
|
|
326
|
+
*/
|
|
327
|
+
this.boundsHint = computed(() => {
|
|
328
|
+
const { dateFormatter } = this.timeRangeService;
|
|
329
|
+
const minDate = this.timeRangeService.minDate();
|
|
330
|
+
const maxDate = this.timeRangeService.maxDate();
|
|
331
|
+
if (!minDate && !maxDate)
|
|
332
|
+
return '';
|
|
333
|
+
// The time half earns a place in the caption only once a bound actually carries one.
|
|
334
|
+
return [minDate, maxDate].some((bound) => bound && this.carriesTime(bound))
|
|
335
|
+
? dateFormatter.rangeShortDateTime(minDate, maxDate ?? undefined)
|
|
336
|
+
: dateFormatter.rangeShortDate(minDate, maxDate ?? undefined);
|
|
337
|
+
}, ...(ngDevMode ? [{ debugName: "boundsHint" }] : []));
|
|
230
338
|
/** @docs-private */
|
|
231
339
|
this.timepickerList = viewChildren(KbqTimepicker, ...(ngDevMode ? [{ debugName: "timepickerList" }] : []));
|
|
232
340
|
/** @docs-private */
|
|
233
341
|
this.timepickerFormat = TimeFormats.HHmmss;
|
|
234
342
|
/** @docs-private */
|
|
235
343
|
this.rangeStateMatcher = new RangeErrorStateMatcher();
|
|
236
|
-
|
|
344
|
+
/** Raised by the first "apply", after which a border left wholly empty counts as an error too. */
|
|
345
|
+
this.applyAttempted = false;
|
|
237
346
|
/** @docs-private */
|
|
238
347
|
this.onChange = (_value) => { };
|
|
239
348
|
/** @docs-private */
|
|
@@ -247,21 +356,21 @@ class KbqTimeRangeEditor {
|
|
|
247
356
|
fromDate: new FormControl(defaultRangeValue.fromDate, { nonNullable: true }),
|
|
248
357
|
toTime: new FormControl(defaultRangeValue.toTime, { nonNullable: true }),
|
|
249
358
|
toDate: new FormControl(defaultRangeValue.toDate, { nonNullable: true })
|
|
250
|
-
}
|
|
251
|
-
const
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
.
|
|
359
|
+
});
|
|
360
|
+
const { fromTime, fromDate, toTime, toDate } = this.form.controls;
|
|
361
|
+
this.borderControls = {
|
|
362
|
+
from: { Time: fromTime, Date: fromDate },
|
|
363
|
+
to: { Time: toTime, Date: toDate }
|
|
364
|
+
};
|
|
365
|
+
this.rangeControls = borders.flatMap((border) => this.borderHalves(border));
|
|
366
|
+
borders.forEach((border) => halves.forEach((half) => this.borderControls[border][half].addValidators(this.borderValidator(border, half))));
|
|
367
|
+
if (this.editorBridge)
|
|
368
|
+
this.editorBridge.revealErrors = () => this.revealErrors();
|
|
260
369
|
this.form.valueChanges
|
|
261
370
|
.pipe(map((formValue) => formValue.type), distinctUntilChanged(), takeUntilDestroyed())
|
|
262
371
|
.subscribe((type) => {
|
|
263
372
|
const isDisabled = type !== 'range';
|
|
264
|
-
rangeControls.forEach((control) => {
|
|
373
|
+
this.rangeControls.forEach((control) => {
|
|
265
374
|
if (isDisabled) {
|
|
266
375
|
control.disable({ emitEvent: false });
|
|
267
376
|
}
|
|
@@ -271,16 +380,13 @@ class KbqTimeRangeEditor {
|
|
|
271
380
|
});
|
|
272
381
|
});
|
|
273
382
|
this.form.valueChanges.pipe(takeUntilDestroyed()).subscribe((formValue) => {
|
|
383
|
+
// Each half is checked against its sibling, so editing one has to re-run the other. Silent,
|
|
384
|
+
// or this would re-enter through `valueChanges`.
|
|
385
|
+
this.revalidateRange();
|
|
274
386
|
const range = this.mapTimeRange(formValue);
|
|
275
387
|
if (range)
|
|
276
388
|
this.onChange(range);
|
|
277
389
|
});
|
|
278
|
-
this.form.statusChanges.pipe(takeUntilDestroyed()).subscribe((status) => {
|
|
279
|
-
const timepickerList = this.timepickerList();
|
|
280
|
-
if (timepickerList.at(0)) {
|
|
281
|
-
timepickerList.at(0).errorState = status === 'INVALID';
|
|
282
|
-
}
|
|
283
|
-
});
|
|
284
390
|
}
|
|
285
391
|
ngOnInit() {
|
|
286
392
|
const defaultRangeValue = this.rangeValue();
|
|
@@ -295,7 +401,7 @@ class KbqTimeRangeEditor {
|
|
|
295
401
|
}
|
|
296
402
|
/** @docs-private */
|
|
297
403
|
validate() {
|
|
298
|
-
return this.
|
|
404
|
+
return this.concatControlValidationErrors();
|
|
299
405
|
}
|
|
300
406
|
/** Implemented as part of ControlValueAccessor */
|
|
301
407
|
writeValue(value) {
|
|
@@ -304,8 +410,10 @@ class KbqTimeRangeEditor {
|
|
|
304
410
|
const corrected = this.timeRangeService.checkAndCorrectTimeRangeValue(value, this.availableTimeRangeTypes(), this.form.value);
|
|
305
411
|
this.form.controls.type.setValue(corrected.type);
|
|
306
412
|
if (corrected.type === 'range' && corrected.startDateTime && corrected.endDateTime) {
|
|
307
|
-
const
|
|
308
|
-
|
|
413
|
+
const { dateAdapter } = this.timeRangeService;
|
|
414
|
+
// An unparseable end leaves its halves empty, which the border validators then report.
|
|
415
|
+
const from = dateAdapter.deserialize(corrected.startDateTime);
|
|
416
|
+
const to = dateAdapter.deserialize(corrected.endDateTime);
|
|
309
417
|
this.form.patchValue({
|
|
310
418
|
fromTime: from,
|
|
311
419
|
fromDate: from,
|
|
@@ -322,19 +430,200 @@ class KbqTimeRangeEditor {
|
|
|
322
430
|
registerOnTouched(fn) {
|
|
323
431
|
this.onTouch = fn;
|
|
324
432
|
}
|
|
433
|
+
/**
|
|
434
|
+
* Leaving one border reports what is wrong with it. Angular marks a control touched on its own blur,
|
|
435
|
+
* which is a finer grain than the spec asks for - so moving between a border's own date and time
|
|
436
|
+
* takes the touch back off, and the pair is judged only once focus is out of both.
|
|
437
|
+
* @docs-private
|
|
438
|
+
*/
|
|
439
|
+
onBorderFocusOut(border, { currentTarget, relatedTarget }) {
|
|
440
|
+
this.setBorderTouched(border, !this.staysWithin(currentTarget, relatedTarget));
|
|
441
|
+
}
|
|
442
|
+
/**
|
|
443
|
+
* Typing puts a border back to its neutral state, the way `validation-on-blur` does for a single
|
|
444
|
+
* field. What is wrong with it is said again when focus leaves it.
|
|
445
|
+
* @docs-private
|
|
446
|
+
*/
|
|
447
|
+
onBorderInput(border) {
|
|
448
|
+
this.setBorderTouched(border, false);
|
|
449
|
+
}
|
|
450
|
+
/**
|
|
451
|
+
* What is said when the range as a whole has left the bounds. Said once, below the last of the two
|
|
452
|
+
* ends, and only when both of them are at fault: a single end out of bounds is already pointed at by
|
|
453
|
+
* the fields painted around it. Silent until the ends have been left, keeping step with that
|
|
454
|
+
* painting.
|
|
455
|
+
* @docs-private
|
|
456
|
+
*/
|
|
457
|
+
outOfBoundsMessage() {
|
|
458
|
+
const bothOutOfBounds = borders.every((border) => this.borderHalves(border).some((control) => control.touched && !!control.errors?.kbqTimeRangeOutOfBounds));
|
|
459
|
+
if (!bothOutOfBounds)
|
|
460
|
+
return '';
|
|
461
|
+
const template = this.localeConfiguration().editor.outOfBoundsError ??
|
|
462
|
+
ruRULocaleData.timeRange.editor.outOfBoundsError ??
|
|
463
|
+
'';
|
|
464
|
+
return template.replace('{{ value }}', this.boundsHint());
|
|
465
|
+
}
|
|
466
|
+
/**
|
|
467
|
+
* Once focus leaves the manual range fields, a reversed range is silently swapped back into order -
|
|
468
|
+
* except on the way to "apply", where the swap is left to the emitted value so that the popover does
|
|
469
|
+
* not visibly reorder itself in the same gesture that closes it.
|
|
470
|
+
* @docs-private
|
|
471
|
+
*/
|
|
472
|
+
onRangeFocusOut({ currentTarget, relatedTarget }) {
|
|
473
|
+
const next = relatedTarget;
|
|
474
|
+
if (this.staysWithin(currentTarget, next))
|
|
475
|
+
return;
|
|
476
|
+
// Safari reports a `null` `relatedTarget` for a click on a button, so the footer is recognised by
|
|
477
|
+
// the pointer gesture it started rather than by where focus went.
|
|
478
|
+
if (this.editorBridge?.applyGestureInProgress || next?.closest('.kbq-time-range__buttons'))
|
|
479
|
+
return;
|
|
480
|
+
// Focus went nowhere because the whole window lost it - the user is still mid-edit.
|
|
481
|
+
if (!next && !this.document.hasFocus())
|
|
482
|
+
return;
|
|
483
|
+
this.swapIfReversed();
|
|
484
|
+
}
|
|
485
|
+
/**
|
|
486
|
+
* Reveals both borders and sends the user to the first field at fault, reporting whether there is
|
|
487
|
+
* anything to fix at all.
|
|
488
|
+
*/
|
|
489
|
+
revealErrors() {
|
|
490
|
+
this.applyAttempted = true;
|
|
491
|
+
// `applyAttempted` is read by the validators, so they have to be run again before anything is said
|
|
492
|
+
// about the result.
|
|
493
|
+
this.revalidateRange();
|
|
494
|
+
borders.forEach((border) => this.setBorderTouched(border, true));
|
|
495
|
+
const firstInvalid = borders
|
|
496
|
+
.flatMap((border) => halves.map((half) => ({ border, half })))
|
|
497
|
+
.find(({ border, half }) => this.borderControls[border][half].invalid);
|
|
498
|
+
if (!firstInvalid)
|
|
499
|
+
return true;
|
|
500
|
+
// The only place the two halves of a name are put back together, and it builds a selector rather
|
|
501
|
+
// than a control key: the attribute in the template is spelled the same way.
|
|
502
|
+
this.elementRef.nativeElement
|
|
503
|
+
.querySelector(`[data-time-range-field="${firstInvalid.border}${firstInvalid.half}"]`)
|
|
504
|
+
?.focus();
|
|
505
|
+
return false;
|
|
506
|
+
}
|
|
507
|
+
/** Whether a bound is a moment within its day rather than the day itself. */
|
|
508
|
+
carriesTime(date) {
|
|
509
|
+
const { dateAdapter } = this.timeRangeService;
|
|
510
|
+
return !!(dateAdapter.getHours(date) || dateAdapter.getMinutes(date) || dateAdapter.getSeconds(date));
|
|
511
|
+
}
|
|
512
|
+
/** Whether focus is still inside `group`, counting the datepicker calendar as part of it. */
|
|
513
|
+
staysWithin(group, next) {
|
|
514
|
+
// The calendar is rendered in a separate overlay, so focus moving into it is still editing.
|
|
515
|
+
return group.contains(next) || !!next?.closest('.kbq-datepicker__popup');
|
|
516
|
+
}
|
|
517
|
+
/** Both halves of a border are painted, or neither: the pair is what the user is judged on. */
|
|
518
|
+
setBorderTouched(border, touched) {
|
|
519
|
+
this.borderHalves(border).forEach((control) => {
|
|
520
|
+
if (touched) {
|
|
521
|
+
control.markAsTouched();
|
|
522
|
+
}
|
|
523
|
+
else {
|
|
524
|
+
control.markAsUntouched();
|
|
525
|
+
}
|
|
526
|
+
});
|
|
527
|
+
}
|
|
528
|
+
/** Both halves of one end, in the order they are painted. */
|
|
529
|
+
borderHalves(border) {
|
|
530
|
+
return halves.map((half) => this.borderControls[border][half]);
|
|
531
|
+
}
|
|
532
|
+
/** Each half is checked against its sibling, so a change to one has to re-run the other. */
|
|
533
|
+
revalidateRange() {
|
|
534
|
+
this.rangeControls.forEach((control) => control.updateValueAndValidity({ emitEvent: false }));
|
|
535
|
+
}
|
|
536
|
+
swapIfReversed() {
|
|
537
|
+
if (this.hasBorderProblem())
|
|
538
|
+
return;
|
|
539
|
+
const { fromTime, fromDate, toTime, toDate } = this.form.getRawValue();
|
|
540
|
+
if (!this.isReversed(fromDate, fromTime, toDate, toTime))
|
|
541
|
+
return;
|
|
542
|
+
this.form.patchValue({ fromTime: toTime, fromDate: toDate, toTime: fromTime, toDate: fromDate });
|
|
543
|
+
}
|
|
544
|
+
/**
|
|
545
|
+
* Whether anything about the borders would stop the range being saved. Deliberately blind to which
|
|
546
|
+
* borders have been revealed: a swap rewrites the user's values, so it has to answer "is this range
|
|
547
|
+
* actually sound", not "has the user been shown the problem yet".
|
|
548
|
+
*/
|
|
549
|
+
hasBorderProblem() {
|
|
550
|
+
if (this.form.controls.type.value !== 'range')
|
|
551
|
+
return false;
|
|
552
|
+
// The validators run whether or not the user has been shown the result, so the controls already
|
|
553
|
+
// hold the answer - including the pickers' own format errors.
|
|
554
|
+
return this.rangeControls.some((control) => !!control.errors);
|
|
555
|
+
}
|
|
556
|
+
/**
|
|
557
|
+
* What is wrong with each half of a border, whether or not the user has been shown it. A missing half
|
|
558
|
+
* is flagged on its own; a bound is judged in two steps, so that the field actually at fault is the
|
|
559
|
+
* one painted.
|
|
560
|
+
*/
|
|
561
|
+
borderErrors(border) {
|
|
562
|
+
const { value: date } = this.borderControls[border].Date;
|
|
563
|
+
const { value: time } = this.borderControls[border].Time;
|
|
564
|
+
if (!date && !time) {
|
|
565
|
+
const error = this.applyAttempted ? { kbqTimeRangeRequired: true } : null;
|
|
566
|
+
return { Date: error, Time: error };
|
|
567
|
+
}
|
|
568
|
+
// Only the missing half is painted, so the user is pointed at the field to fill in.
|
|
569
|
+
if (!date || !time) {
|
|
570
|
+
const error = { kbqTimeRangeIncomplete: true };
|
|
571
|
+
return { Date: date ? null : error, Time: time ? null : error };
|
|
572
|
+
}
|
|
573
|
+
const outOfBounds = this.checkBorderBounds(date, time);
|
|
574
|
+
if (!outOfBounds)
|
|
575
|
+
return { Date: null, Time: null };
|
|
576
|
+
const error = { kbqTimeRangeOutOfBounds: { bound: outOfBounds.bound } };
|
|
577
|
+
// A day outside the bounds is wrong whatever time sits next to it, so both halves are painted.
|
|
578
|
+
// A day on the boundary itself can only be pushed out by its time, so only the time is.
|
|
579
|
+
return { Date: outOfBounds.scope === 'date' ? error : null, Time: error };
|
|
580
|
+
}
|
|
581
|
+
/** Which bound a border falls outside of, and whether its day or only its time is to blame. */
|
|
582
|
+
checkBorderBounds(date, time) {
|
|
583
|
+
const service = this.timeRangeService;
|
|
584
|
+
const { dateAdapter } = service;
|
|
585
|
+
const minDate = service.minDate();
|
|
586
|
+
const maxDate = service.maxDate();
|
|
587
|
+
if (minDate && dateAdapter.compareDate(date, minDate) < 0)
|
|
588
|
+
return { bound: 'min', scope: 'date' };
|
|
589
|
+
if (maxDate && dateAdapter.compareDate(date, maxDate) > 0)
|
|
590
|
+
return { bound: 'max', scope: 'date' };
|
|
591
|
+
const combined = service.combineDateAndTime(date, time);
|
|
592
|
+
if (minDate && dateAdapter.compareDateTime(combined, minDate) < 0)
|
|
593
|
+
return { bound: 'min', scope: 'time' };
|
|
594
|
+
if (maxDate && dateAdapter.compareDateTime(combined, maxDate) > 0)
|
|
595
|
+
return { bound: 'max', scope: 'time' };
|
|
596
|
+
return null;
|
|
597
|
+
}
|
|
598
|
+
isReversed(fromDate, fromTime, toDate, toTime) {
|
|
599
|
+
if (!fromDate || !fromTime || !toDate || !toTime)
|
|
600
|
+
return false;
|
|
601
|
+
const service = this.timeRangeService;
|
|
602
|
+
return (service.dateAdapter.compareDateTime(service.combineDateAndTime(fromDate, fromTime), service.combineDateAndTime(toDate, toTime)) > 0);
|
|
603
|
+
}
|
|
604
|
+
/**
|
|
605
|
+
* A border reports only what the pickers do not: an empty half next to a filled one, a border left
|
|
606
|
+
* wholly empty once "apply" was pressed, and a border outside the bounds. Format is the pickers' own
|
|
607
|
+
* business, and so is the calendar restriction.
|
|
608
|
+
*/
|
|
609
|
+
borderValidator(border, half) {
|
|
610
|
+
return () => {
|
|
611
|
+
if (this.form.controls.type.value !== 'range')
|
|
612
|
+
return null;
|
|
613
|
+
return this.borderErrors(border)[half];
|
|
614
|
+
};
|
|
615
|
+
}
|
|
325
616
|
mapTimeRange({ type }) {
|
|
326
617
|
if (!type)
|
|
327
618
|
return;
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
})
|
|
337
|
-
};
|
|
619
|
+
const { fromTime, fromDate, toTime, toDate } = this.form.getRawValue();
|
|
620
|
+
// Ordered here as well as on blur, so that pressing "apply" straight out of a reversed field is
|
|
621
|
+
// safe. A range with something wrong in it is left exactly as typed: it cannot be saved anyway,
|
|
622
|
+
// and reordering it would move the fields out from under the user while they fix it.
|
|
623
|
+
const ordered = !this.hasBorderProblem() && this.isReversed(fromDate, fromTime, toDate, toTime)
|
|
624
|
+
? { fromTime: toTime, fromDate: toDate, toTime: fromTime, toDate: fromDate }
|
|
625
|
+
: { fromTime, fromDate, toTime, toDate };
|
|
626
|
+
return { type, ...this.timeRangeService.calculateTimeRange(type, ordered) };
|
|
338
627
|
}
|
|
339
628
|
getFormattedOption(type, localeConfig) {
|
|
340
629
|
const translationType = this.timeRangeService.getTimeRangeUnitByType(type);
|
|
@@ -344,7 +633,7 @@ class KbqTimeRangeEditor {
|
|
|
344
633
|
return this.timeRangeService.dateFormatter.duration(this.timeRangeService.dateAdapter.deserialize(range.startDateTime), this.timeRangeService.dateAdapter.today(), [translationType], false, localeConfig.durationTemplate.option);
|
|
345
634
|
}
|
|
346
635
|
concatControlValidationErrors() {
|
|
347
|
-
let result =
|
|
636
|
+
let result = null;
|
|
348
637
|
Object.values(this.form.controls).forEach((control) => {
|
|
349
638
|
if (control.errors) {
|
|
350
639
|
result = {
|
|
@@ -367,15 +656,17 @@ class KbqTimeRangeEditor {
|
|
|
367
656
|
provide: NG_VALIDATORS,
|
|
368
657
|
useExisting: KbqTimeRangeEditor
|
|
369
658
|
}
|
|
370
|
-
], viewQueries: [{ propertyName: "timepickerList", predicate: KbqTimepicker, descendants: true, isSignal: true }], ngImport: i0, template: "<form [formGroup]=\"form\">\n <kbq-radio-group class=\"kbq-time-range-editor__container\" [formControl]=\"form.controls.type\">\n @let types = timeRangeTypesWithoutRange();\n\n @if (types.length) {\n <div class=\"kbq-time-range-editor__predefined\">\n @for (type of types; track type) {\n <kbq-radio-button class=\"kbq-time-range-editor__predefined-option\" [value]=\"type.type\">\n <ng-container\n *ngTemplateOutlet=\"optionTemplate() || defaultOptionTemplate; context: { $implicit: type }\"\n />\n </kbq-radio-button>\n }\n </div>\n }\n\n @if (isRangeVisible()) {\n @let localeConfig = localeConfiguration();\n\n <div class=\"kbq-time-range-editor__range\" role=\"group\">\n @if (types.length) {\n <kbq-radio-button [value]=\"'range'\">{{ localeConfig.editor.rangeLabel }}</kbq-radio-button>\n }\n\n <div class=\"kbq-time-range-editor__date-time\">\n <span class=\"kbq-time-range-editor__date-time-prefix\" [attr.aria-label]=\"localeConfig.editor.from\">\n {{ localeConfig.editor.from }}\n </span>\n <kbq-fieldset>\n <kbq-form-field kbqFieldsetItem>\n <i kbq-icon=\"kbq-clock_16\" kbqPrefix [autoColor]=\"true\"></i>\n <input kbqTimepicker [format]=\"timepickerFormat\" [formControl]=\"form.controls.fromTime\" />\n </kbq-form-field>\n\n <kbq-form-field>\n <input\n [min]=\"minDate()\"\n [max]=\"maxDate()\"\n [kbqDatepicker]=\"datepickerFrom\"\n [formControl]=\"form.controls.fromDate\"\n [errorStateMatcher]=\"rangeStateMatcher\"\n />\n <kbq-datepicker-toggle-icon kbqSuffix [for]=\"datepickerFrom\" />\n <kbq-datepicker #datepickerFrom [minDate]=\"minDate()\" [maxDate]=\"maxDate()\" />\n </kbq-form-field>\n </kbq-fieldset>\n </div>\n\n <div class=\"kbq-time-range-editor__date-time\">\n <span class=\"kbq-time-range-editor__date-time-prefix\" [attr.aria-label]=\"localeConfig.editor.to\">\n {{ localeConfig.editor.to }}\n </span>\n <kbq-fieldset>\n <kbq-form-field kbqFieldsetItem>\n <i kbq-icon=\"kbq-clock_16\" kbqPrefix [autoColor]=\"true\"></i>\n <input kbqTimepicker [format]=\"timepickerFormat\" [formControl]=\"form.controls.toTime\" />\n </kbq-form-field>\n\n <kbq-form-field>\n <input\n [min]=\"minDate()\"\n [max]=\"maxDate()\"\n [kbqDatepicker]=\"datepickerTo\"\n [formControl]=\"form.controls.toDate\"\n [errorStateMatcher]=\"rangeStateMatcher\"\n />\n <kbq-datepicker-toggle-icon kbqSuffix [for]=\"datepickerTo\" />\n <kbq-datepicker #datepickerTo [minDate]=\"minDate()\" [maxDate]=\"maxDate()\" />\n </kbq-form-field>\n </kbq-fieldset>\n </div>\n </div>\n }\n </kbq-radio-group>\n</form>\n\n<ng-template #defaultOptionTemplate let-context>\n @let localeConfig = localeConfiguration();\n @switch (context.translationType) {\n @case ('other') {\n @switch (context.type) {\n @case ('allTime') {\n {{ localeConfig.editor.allTimeOption ?? localeConfig.editor.allTime }}\n }\n @case ('currentQuarter') {\n {{ localeConfig.editor.currentQuarterOption ?? localeConfig.editor.currentQuarter }}\n }\n @case ('currentYear') {\n {{ localeConfig.editor.currentYearOption ?? localeConfig.editor.currentYear }}\n }\n }\n }\n @default {\n {{ context.formattedValue }}\n }\n }\n</ng-template>\n", dependencies: [{ kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i1.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.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i1.FormControlDirective, selector: "[formControl]", inputs: ["formControl", "disabled", "ngModel"], outputs: ["ngModelChange"], exportAs: ["ngForm"] }, { kind: "directive", type: i1.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "component", type: KbqFieldset, selector: "kbq-fieldset" }, { kind: "directive", type: KbqFieldsetItem, selector: "[kbqFieldsetItem]" }, { kind: "ngmodule", type: KbqDatepickerModule }, { kind: "component", type: i2.KbqDatepicker, selector: "kbq-datepicker", inputs: ["hasBackdrop", "startAt", "disabled", "opened", "minDate", "maxDate", "panelClass", "dateClass", "backdropClass"], outputs: ["yearSelected", "monthSelected", "opened", "closed"], exportAs: ["kbqDatepicker"] }, { kind: "directive", type: i2.KbqDatepickerInput, selector: "input[kbqDatepicker], input[kbqCalendar]", inputs: ["errorStateMatcher", "placeholder", "required", "kbqDatepicker", "kbqCalendar", "kbqDatepickerFilter", "value", "min", "max", "disabled", "id", "kbqValidationTooltip"], outputs: ["incorrectInput", "dateChange", "dateInput"], exportAs: ["kbqDatepickerInput"] }, { kind: "component", type: i2.KbqDatepickerToggleIconComponent, selector: "kbq-datepicker-toggle-icon", inputs: ["disabled", "for"] }, { kind: "component", type: i3.KbqFormField, selector: "kbq-form-field", inputs: ["noBorders", "inOverlay", "horizontal", "labelClass", "contentClass"], outputs: ["inOverlayChange"], exportAs: ["kbqFormField"] }, { kind: "directive", type: i3.KbqPrefix, selector: "[kbqPrefix]", exportAs: ["kbqPrefix"] }, { kind: "directive", type: i3.KbqSuffix, selector: "[kbqSuffix]" }, { kind: "ngmodule", type: KbqTimepickerModule }, { kind: "directive", type: i4.KbqTimepicker, selector: "input[kbqTimepicker]", inputs: ["errorStateMatcher", "placeholder", "disabled", "id", "required", "format", "min", "max", "value", "kbqValidationTooltip"], outputs: ["incorrectInput"], exportAs: ["kbqTimepicker"] }, { kind: "ngmodule", type: KbqRadioModule }, { kind: "directive", type: i5.KbqRadioGroup, selector: "kbq-radio-group", inputs: ["big", "name", "labelPosition", "value", "selected", "disabled", "required"], outputs: ["change"], exportAs: ["kbqRadioGroup"] }, { kind: "component", type: i5.KbqRadioButton, selector: "kbq-radio-button", inputs: ["checked", "value", "disabled", "tabIndex", "required", "labelPosition", "name", "isFocused", "id"], outputs: ["change"], exportAs: ["kbqRadioButton"] }, { kind: "component", type: KbqIcon, selector: "[kbq-icon]", inputs: ["small", "autoColor", "kbq-icon"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None }); }
|
|
659
|
+
], viewQueries: [{ propertyName: "timepickerList", predicate: KbqTimepicker, descendants: true, isSignal: true }], ngImport: i0, template: "<form [formGroup]=\"form\">\n <kbq-radio-group class=\"kbq-time-range-editor__container\" [formControl]=\"form.controls.type\">\n @let types = timeRangeTypesWithoutRange();\n\n @if (types.length) {\n <div class=\"kbq-time-range-editor__predefined\">\n @for (type of types; track type) {\n <kbq-radio-button class=\"kbq-time-range-editor__predefined-option\" [value]=\"type.type\">\n <ng-container\n *ngTemplateOutlet=\"optionTemplate() || defaultOptionTemplate; context: { $implicit: type }\"\n />\n </kbq-radio-button>\n }\n </div>\n }\n\n @if (isRangeVisible()) {\n @let localeConfig = localeConfiguration();\n\n <div class=\"kbq-time-range-editor__range\" role=\"group\" (focusout)=\"onRangeFocusOut($event)\">\n @let bounds = boundsHint();\n\n @if (types.length) {\n <kbq-radio-button [value]=\"'range'\">\n {{ localeConfig.editor.rangeLabel }}\n @if (bounds) {\n <kbq-hint>{{ bounds }}</kbq-hint>\n }\n </kbq-radio-button>\n }\n\n <div\n class=\"kbq-time-range-editor__date-time\"\n (input)=\"onBorderInput('from')\"\n (focusout)=\"onBorderFocusOut('from', $event)\"\n >\n <span class=\"kbq-time-range-editor__date-time-prefix\" [attr.aria-label]=\"localeConfig.editor.from\">\n {{ localeConfig.editor.from }}\n </span>\n <kbq-fieldset>\n <kbq-form-field kbqFieldsetItem>\n <i kbq-icon=\"kbq-clock_16\" kbqPrefix [autoColor]=\"true\"></i>\n <input\n kbqTimepicker\n data-time-range-field=\"fromTime\"\n [format]=\"timepickerFormat\"\n [formControl]=\"form.controls.fromTime\"\n [errorStateMatcher]=\"rangeStateMatcher\"\n />\n </kbq-form-field>\n\n <kbq-form-field>\n <input\n data-time-range-field=\"fromDate\"\n [kbqDatepicker]=\"datepickerFrom\"\n [formControl]=\"form.controls.fromDate\"\n [errorStateMatcher]=\"rangeStateMatcher\"\n />\n <kbq-datepicker-toggle-icon kbqSuffix [for]=\"datepickerFrom\" />\n <kbq-datepicker #datepickerFrom [minDate]=\"minDate()\" [maxDate]=\"maxDate()\" />\n </kbq-form-field>\n </kbq-fieldset>\n </div>\n\n <div\n class=\"kbq-time-range-editor__date-time\"\n (input)=\"onBorderInput('to')\"\n (focusout)=\"onBorderFocusOut('to', $event)\"\n >\n <span class=\"kbq-time-range-editor__date-time-prefix\" [attr.aria-label]=\"localeConfig.editor.to\">\n {{ localeConfig.editor.to }}\n </span>\n <kbq-fieldset>\n <kbq-form-field kbqFieldsetItem>\n <i kbq-icon=\"kbq-clock_16\" kbqPrefix [autoColor]=\"true\"></i>\n <input\n kbqTimepicker\n data-time-range-field=\"toTime\"\n [format]=\"timepickerFormat\"\n [formControl]=\"form.controls.toTime\"\n [errorStateMatcher]=\"rangeStateMatcher\"\n />\n </kbq-form-field>\n\n <kbq-form-field>\n <input\n data-time-range-field=\"toDate\"\n [kbqDatepicker]=\"datepickerTo\"\n [formControl]=\"form.controls.toDate\"\n [errorStateMatcher]=\"rangeStateMatcher\"\n />\n <kbq-datepicker-toggle-icon kbqSuffix [for]=\"datepickerTo\" />\n <kbq-datepicker #datepickerTo [minDate]=\"minDate()\" [maxDate]=\"maxDate()\" />\n </kbq-form-field>\n\n <!-- The range is judged as a whole, so what is wrong with it is said once, below the\n last of its two ends - in place of the caption, which spells the same bounds out. -->\n @if (outOfBoundsMessage(); as message) {\n <kbq-error>{{ message }}</kbq-error>\n } @else if (!types.length && bounds) {\n <!-- With no presets there is no option to hang the bounds off, so they go in the\n fieldset's own hint area rather than loose in the block. -->\n <kbq-hint>{{ bounds }}</kbq-hint>\n }\n </kbq-fieldset>\n </div>\n </div>\n }\n </kbq-radio-group>\n</form>\n\n<ng-template #defaultOptionTemplate let-context>\n @let localeConfig = localeConfiguration();\n @switch (context.translationType) {\n @case ('other') {\n @switch (context.type) {\n @case ('allTime') {\n {{ localeConfig.editor.allTimeOption ?? localeConfig.editor.allTime }}\n }\n @case ('currentQuarter') {\n {{ localeConfig.editor.currentQuarterOption ?? localeConfig.editor.currentQuarter }}\n }\n @case ('currentYear') {\n {{ localeConfig.editor.currentYearOption ?? localeConfig.editor.currentYear }}\n }\n }\n }\n @default {\n {{ context.formattedValue }}\n }\n }\n</ng-template>\n", dependencies: [{ kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i1.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.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i1.FormControlDirective, selector: "[formControl]", inputs: ["formControl", "disabled", "ngModel"], outputs: ["ngModelChange"], exportAs: ["ngForm"] }, { kind: "directive", type: i1.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "component", type: KbqError, selector: "kbq-error", exportAs: ["kbqError"] }, { kind: "component", type: KbqFieldset, selector: "kbq-fieldset" }, { kind: "directive", type: KbqFieldsetItem, selector: "[kbqFieldsetItem]" }, { kind: "component", type: KbqHint, selector: "kbq-hint", inputs: ["id", "fillTextOff", "compact"], exportAs: ["kbqHint"] }, { kind: "ngmodule", type: KbqDatepickerModule }, { kind: "component", type: i2.KbqDatepicker, selector: "kbq-datepicker", inputs: ["hasBackdrop", "startAt", "disabled", "opened", "minDate", "maxDate", "panelClass", "dateClass", "backdropClass"], outputs: ["yearSelected", "monthSelected", "opened", "closed"], exportAs: ["kbqDatepicker"] }, { kind: "directive", type: i2.KbqDatepickerInput, selector: "input[kbqDatepicker], input[kbqCalendar]", inputs: ["errorStateMatcher", "placeholder", "required", "kbqDatepicker", "kbqCalendar", "kbqDatepickerFilter", "value", "min", "max", "disabled", "id", "kbqValidationTooltip"], outputs: ["incorrectInput", "dateChange", "dateInput"], exportAs: ["kbqDatepickerInput"] }, { kind: "component", type: i2.KbqDatepickerToggleIconComponent, selector: "kbq-datepicker-toggle-icon", inputs: ["disabled", "for"] }, { kind: "component", type: i3.KbqFormField, selector: "kbq-form-field", inputs: ["noBorders", "inOverlay", "horizontal", "labelClass", "contentClass"], outputs: ["inOverlayChange"], exportAs: ["kbqFormField"] }, { kind: "directive", type: i3.KbqPrefix, selector: "[kbqPrefix]", exportAs: ["kbqPrefix"] }, { kind: "directive", type: i3.KbqSuffix, selector: "[kbqSuffix]" }, { kind: "ngmodule", type: KbqTimepickerModule }, { kind: "directive", type: i4.KbqTimepicker, selector: "input[kbqTimepicker]", inputs: ["errorStateMatcher", "placeholder", "disabled", "id", "required", "format", "min", "max", "value", "kbqValidationTooltip"], outputs: ["incorrectInput"], exportAs: ["kbqTimepicker"] }, { kind: "ngmodule", type: KbqRadioModule }, { kind: "directive", type: i5.KbqRadioGroup, selector: "kbq-radio-group", inputs: ["big", "name", "labelPosition", "value", "selected", "disabled", "required"], outputs: ["change"], exportAs: ["kbqRadioGroup"] }, { kind: "component", type: i5.KbqRadioButton, selector: "kbq-radio-button", inputs: ["checked", "value", "disabled", "tabIndex", "required", "labelPosition", "name", "isFocused", "id"], outputs: ["change"], exportAs: ["kbqRadioButton"] }, { kind: "component", type: KbqIcon, selector: "[kbq-icon]", inputs: ["small", "autoColor", "kbq-icon"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None }); }
|
|
371
660
|
}
|
|
372
661
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.30", ngImport: i0, type: KbqTimeRangeEditor, decorators: [{
|
|
373
662
|
type: Component,
|
|
374
663
|
args: [{ selector: 'kbq-time-range-editor', imports: [
|
|
375
664
|
NgTemplateOutlet,
|
|
376
665
|
ReactiveFormsModule,
|
|
666
|
+
KbqError,
|
|
377
667
|
KbqFieldset,
|
|
378
668
|
KbqFieldsetItem,
|
|
669
|
+
KbqHint,
|
|
379
670
|
KbqDatepickerModule,
|
|
380
671
|
KbqTimepickerModule,
|
|
381
672
|
KbqRadioModule,
|
|
@@ -393,7 +684,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.30", ngImpo
|
|
|
393
684
|
}
|
|
394
685
|
], changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, host: {
|
|
395
686
|
class: 'kbq-time-range__editor'
|
|
396
|
-
}, template: "<form [formGroup]=\"form\">\n <kbq-radio-group class=\"kbq-time-range-editor__container\" [formControl]=\"form.controls.type\">\n @let types = timeRangeTypesWithoutRange();\n\n @if (types.length) {\n <div class=\"kbq-time-range-editor__predefined\">\n @for (type of types; track type) {\n <kbq-radio-button class=\"kbq-time-range-editor__predefined-option\" [value]=\"type.type\">\n <ng-container\n *ngTemplateOutlet=\"optionTemplate() || defaultOptionTemplate; context: { $implicit: type }\"\n />\n </kbq-radio-button>\n }\n </div>\n }\n\n @if (isRangeVisible()) {\n @let localeConfig = localeConfiguration();\n\n <div class=\"kbq-time-range-editor__range\" role=\"group\">\n @if (types.length) {\n <kbq-radio-button [value]=\"'range'\"
|
|
687
|
+
}, template: "<form [formGroup]=\"form\">\n <kbq-radio-group class=\"kbq-time-range-editor__container\" [formControl]=\"form.controls.type\">\n @let types = timeRangeTypesWithoutRange();\n\n @if (types.length) {\n <div class=\"kbq-time-range-editor__predefined\">\n @for (type of types; track type) {\n <kbq-radio-button class=\"kbq-time-range-editor__predefined-option\" [value]=\"type.type\">\n <ng-container\n *ngTemplateOutlet=\"optionTemplate() || defaultOptionTemplate; context: { $implicit: type }\"\n />\n </kbq-radio-button>\n }\n </div>\n }\n\n @if (isRangeVisible()) {\n @let localeConfig = localeConfiguration();\n\n <div class=\"kbq-time-range-editor__range\" role=\"group\" (focusout)=\"onRangeFocusOut($event)\">\n @let bounds = boundsHint();\n\n @if (types.length) {\n <kbq-radio-button [value]=\"'range'\">\n {{ localeConfig.editor.rangeLabel }}\n @if (bounds) {\n <kbq-hint>{{ bounds }}</kbq-hint>\n }\n </kbq-radio-button>\n }\n\n <div\n class=\"kbq-time-range-editor__date-time\"\n (input)=\"onBorderInput('from')\"\n (focusout)=\"onBorderFocusOut('from', $event)\"\n >\n <span class=\"kbq-time-range-editor__date-time-prefix\" [attr.aria-label]=\"localeConfig.editor.from\">\n {{ localeConfig.editor.from }}\n </span>\n <kbq-fieldset>\n <kbq-form-field kbqFieldsetItem>\n <i kbq-icon=\"kbq-clock_16\" kbqPrefix [autoColor]=\"true\"></i>\n <input\n kbqTimepicker\n data-time-range-field=\"fromTime\"\n [format]=\"timepickerFormat\"\n [formControl]=\"form.controls.fromTime\"\n [errorStateMatcher]=\"rangeStateMatcher\"\n />\n </kbq-form-field>\n\n <kbq-form-field>\n <input\n data-time-range-field=\"fromDate\"\n [kbqDatepicker]=\"datepickerFrom\"\n [formControl]=\"form.controls.fromDate\"\n [errorStateMatcher]=\"rangeStateMatcher\"\n />\n <kbq-datepicker-toggle-icon kbqSuffix [for]=\"datepickerFrom\" />\n <kbq-datepicker #datepickerFrom [minDate]=\"minDate()\" [maxDate]=\"maxDate()\" />\n </kbq-form-field>\n </kbq-fieldset>\n </div>\n\n <div\n class=\"kbq-time-range-editor__date-time\"\n (input)=\"onBorderInput('to')\"\n (focusout)=\"onBorderFocusOut('to', $event)\"\n >\n <span class=\"kbq-time-range-editor__date-time-prefix\" [attr.aria-label]=\"localeConfig.editor.to\">\n {{ localeConfig.editor.to }}\n </span>\n <kbq-fieldset>\n <kbq-form-field kbqFieldsetItem>\n <i kbq-icon=\"kbq-clock_16\" kbqPrefix [autoColor]=\"true\"></i>\n <input\n kbqTimepicker\n data-time-range-field=\"toTime\"\n [format]=\"timepickerFormat\"\n [formControl]=\"form.controls.toTime\"\n [errorStateMatcher]=\"rangeStateMatcher\"\n />\n </kbq-form-field>\n\n <kbq-form-field>\n <input\n data-time-range-field=\"toDate\"\n [kbqDatepicker]=\"datepickerTo\"\n [formControl]=\"form.controls.toDate\"\n [errorStateMatcher]=\"rangeStateMatcher\"\n />\n <kbq-datepicker-toggle-icon kbqSuffix [for]=\"datepickerTo\" />\n <kbq-datepicker #datepickerTo [minDate]=\"minDate()\" [maxDate]=\"maxDate()\" />\n </kbq-form-field>\n\n <!-- The range is judged as a whole, so what is wrong with it is said once, below the\n last of its two ends - in place of the caption, which spells the same bounds out. -->\n @if (outOfBoundsMessage(); as message) {\n <kbq-error>{{ message }}</kbq-error>\n } @else if (!types.length && bounds) {\n <!-- With no presets there is no option to hang the bounds off, so they go in the\n fieldset's own hint area rather than loose in the block. -->\n <kbq-hint>{{ bounds }}</kbq-hint>\n }\n </kbq-fieldset>\n </div>\n </div>\n }\n </kbq-radio-group>\n</form>\n\n<ng-template #defaultOptionTemplate let-context>\n @let localeConfig = localeConfiguration();\n @switch (context.translationType) {\n @case ('other') {\n @switch (context.type) {\n @case ('allTime') {\n {{ localeConfig.editor.allTimeOption ?? localeConfig.editor.allTime }}\n }\n @case ('currentQuarter') {\n {{ localeConfig.editor.currentQuarterOption ?? localeConfig.editor.currentQuarter }}\n }\n @case ('currentYear') {\n {{ localeConfig.editor.currentYearOption ?? localeConfig.editor.currentYear }}\n }\n }\n }\n @default {\n {{ context.formattedValue }}\n }\n }\n</ng-template>\n" }]
|
|
397
688
|
}], ctorParameters: () => [], propDecorators: { maxDate: [{ type: i0.Input, args: [{ isSignal: true, alias: "maxDate", required: false }] }], minDate: [{ type: i0.Input, args: [{ isSignal: true, alias: "minDate", required: false }] }], availableTimeRangeTypes: [{ type: i0.Input, args: [{ isSignal: true, alias: "availableTimeRangeTypes", required: false }] }], rangeValue: [{ type: i0.Input, args: [{ isSignal: true, alias: "rangeValue", required: false }] }], showRangeAsDefault: [{ type: i0.Input, args: [{ isSignal: true, alias: "showRangeAsDefault", required: true }] }], localeConfiguration: [{ type: i0.Input, args: [{ isSignal: true, alias: "localeConfiguration", required: true }] }], optionTemplate: [{ type: i0.Input, args: [{ isSignal: true, alias: "optionTemplate", required: false }] }], timepickerList: [{ type: i0.ViewChildren, args: [i0.forwardRef(() => KbqTimepicker), { isSignal: true }] }] } });
|
|
398
689
|
|
|
399
690
|
/** @docs-private */
|
|
@@ -509,6 +800,8 @@ class KbqTimeRange {
|
|
|
509
800
|
constructor() {
|
|
510
801
|
this.timeRangeService = inject(KbqTimeRangeService);
|
|
511
802
|
this.localeService = inject(KBQ_LOCALE_SERVICE, { optional: true });
|
|
803
|
+
this.editorBridge = inject(KbqTimeRangeEditorBridge);
|
|
804
|
+
this.document = inject(DOCUMENT);
|
|
512
805
|
/** @docs-private */
|
|
513
806
|
this.ngControl = inject(NgControl, { optional: true, self: true });
|
|
514
807
|
/** The minimum selectable date. */
|
|
@@ -555,14 +848,20 @@ class KbqTimeRange {
|
|
|
555
848
|
if (types.includes(this.rangeEditorControl.value.type) || this.rangeEditorControl.value.type === 'range') {
|
|
556
849
|
return;
|
|
557
850
|
}
|
|
558
|
-
const timeRangeDefaultValue = this.
|
|
559
|
-
|
|
851
|
+
const timeRangeDefaultValue = this.defaultEditorValue(types);
|
|
852
|
+
// The editor still needs something to open on, whether or not the trigger shows a value.
|
|
560
853
|
this.rangeEditorControl.setValue(timeRangeDefaultValue);
|
|
854
|
+
// A nullable trigger that has never been given a value keeps its placeholder: replacing the types
|
|
855
|
+
// is not the user picking a range, and emitting here would put one into the bound control.
|
|
856
|
+
if (!this.nonNullable() && this.titleValue() === null)
|
|
857
|
+
return;
|
|
858
|
+
this.titleValue.set(timeRangeDefaultValue);
|
|
561
859
|
this.onChange(timeRangeDefaultValue);
|
|
562
860
|
};
|
|
563
861
|
if (this.ngControl) {
|
|
564
862
|
this.ngControl.valueAccessor = this;
|
|
565
863
|
}
|
|
864
|
+
this.timeRangeService.bindBounds(this.minDate, this.maxDate);
|
|
566
865
|
const defaultValue = this.timeRangeService.getTimeRangeDefaultValue(this.normalizedDefaultRangeValue(), this.availableTimeRangeTypes());
|
|
567
866
|
this.titleValue = signal(this.nonNullable() ? defaultValue : null, ...(ngDevMode ? [{ debugName: "titleValue" }] : []));
|
|
568
867
|
this.rangeEditorControl = new FormControl(defaultValue, { nonNullable: true });
|
|
@@ -589,21 +888,42 @@ class KbqTimeRange {
|
|
|
589
888
|
this.titleValue.set(nonNullable || value !== null ? corrected : null);
|
|
590
889
|
this.rangeEditorControl.setValue(corrected);
|
|
591
890
|
}
|
|
592
|
-
/**
|
|
891
|
+
/**
|
|
892
|
+
* Lets the editor tell an "apply" gesture from a plain focus loss before the click even lands - the
|
|
893
|
+
* `focusout` it reacts to is dispatched between this `mousedown` and the `click` below.
|
|
894
|
+
* @docs-private
|
|
895
|
+
*/
|
|
896
|
+
onFooterPointerDown() {
|
|
897
|
+
this.editorBridge.applyGestureInProgress = true;
|
|
898
|
+
this.document.addEventListener('mouseup', () => (this.editorBridge.applyGestureInProgress = false), {
|
|
899
|
+
once: true
|
|
900
|
+
});
|
|
901
|
+
}
|
|
902
|
+
/**
|
|
903
|
+
* Applying is always offered; an invalid editor keeps the popover open and sends the user to the
|
|
904
|
+
* first field at fault instead.
|
|
905
|
+
* @docs-private
|
|
906
|
+
*/
|
|
593
907
|
onApply(popover) {
|
|
908
|
+
this.editorBridge.applyGestureInProgress = false;
|
|
909
|
+
if (!this.editorBridge.revealErrors())
|
|
910
|
+
return;
|
|
594
911
|
this.titleValue.set(this.rangeEditorControl.value);
|
|
595
912
|
this.onChange(this.rangeEditorControl.value);
|
|
596
913
|
popover.hide();
|
|
597
914
|
}
|
|
598
915
|
/** @docs-private */
|
|
599
916
|
onCancel(popover) {
|
|
917
|
+
this.editorBridge.applyGestureInProgress = false;
|
|
600
918
|
popover.hide();
|
|
601
919
|
}
|
|
602
920
|
onVisibleChange(isVisible) {
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
921
|
+
if (isVisible)
|
|
922
|
+
return;
|
|
923
|
+
// Closing throws the edits away, applied or not. With nothing on the trigger yet there is no value
|
|
924
|
+
// to go back to, so the editor returns to its default - otherwise an abandoned edit, which may well
|
|
925
|
+
// be one the editor refused to apply, would be waiting the next time it opens.
|
|
926
|
+
this.rangeEditorControl.setValue(this.titleValue() ?? this.defaultEditorValue(this.availableTimeRangeTypes()));
|
|
607
927
|
}
|
|
608
928
|
/** Implemented as part of ControlValueAccessor */
|
|
609
929
|
registerOnChange(fn) {
|
|
@@ -613,8 +933,12 @@ class KbqTimeRange {
|
|
|
613
933
|
registerOnTouched(fn) {
|
|
614
934
|
this.onTouch = fn;
|
|
615
935
|
}
|
|
936
|
+
/** What the editor opens on: the first offered type, or a plain range when none are offered. */
|
|
937
|
+
defaultEditorValue(types) {
|
|
938
|
+
return this.timeRangeService.getTimeRangeDefaultValue(this.normalizedDefaultRangeValue(), types.length ? types : ['range']);
|
|
939
|
+
}
|
|
616
940
|
/** @nocollapse */ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.30", ngImport: i0, type: KbqTimeRange, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
617
|
-
/** @nocollapse */ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "20.3.30", type: KbqTimeRange, isStandalone: true, selector: "kbq-time-range", inputs: { minDate: { classPropertyName: "minDate", publicName: "minDate", isSignal: true, isRequired: false, transformFunction: null }, maxDate: { classPropertyName: "maxDate", publicName: "maxDate", isSignal: true, isRequired: false, transformFunction: null }, defaultRangeValue: { classPropertyName: "defaultRangeValue", publicName: "defaultRangeValue", isSignal: true, isRequired: false, transformFunction: null }, availableTimeRangeTypes: { classPropertyName: "availableTimeRangeTypes", publicName: "availableTimeRangeTypes", isSignal: true, isRequired: false, transformFunction: null }, titleTemplate: { classPropertyName: "titleTemplate", publicName: "titleTemplate", isSignal: true, isRequired: false, transformFunction: null }, optionTemplate: { classPropertyName: "optionTemplate", publicName: "optionTemplate", isSignal: true, isRequired: false, transformFunction: null }, arrow: { classPropertyName: "arrow", publicName: "arrow", isSignal: true, isRequired: false, transformFunction: null }, showRangeAsDefault: { classPropertyName: "showRangeAsDefault", publicName: "showRangeAsDefault", isSignal: true, isRequired: false, transformFunction: null }, nonNullable: { classPropertyName: "nonNullable", publicName: "nonNullable", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { valueCorrected: "valueCorrected" }, host: { classAttribute: "kbq-time-range" }, providers: [KbqTimeRangeService], ngImport: i0, template: `
|
|
941
|
+
/** @nocollapse */ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "20.3.30", type: KbqTimeRange, isStandalone: true, selector: "kbq-time-range", inputs: { minDate: { classPropertyName: "minDate", publicName: "minDate", isSignal: true, isRequired: false, transformFunction: null }, maxDate: { classPropertyName: "maxDate", publicName: "maxDate", isSignal: true, isRequired: false, transformFunction: null }, defaultRangeValue: { classPropertyName: "defaultRangeValue", publicName: "defaultRangeValue", isSignal: true, isRequired: false, transformFunction: null }, availableTimeRangeTypes: { classPropertyName: "availableTimeRangeTypes", publicName: "availableTimeRangeTypes", isSignal: true, isRequired: false, transformFunction: null }, titleTemplate: { classPropertyName: "titleTemplate", publicName: "titleTemplate", isSignal: true, isRequired: false, transformFunction: null }, optionTemplate: { classPropertyName: "optionTemplate", publicName: "optionTemplate", isSignal: true, isRequired: false, transformFunction: null }, arrow: { classPropertyName: "arrow", publicName: "arrow", isSignal: true, isRequired: false, transformFunction: null }, showRangeAsDefault: { classPropertyName: "showRangeAsDefault", publicName: "showRangeAsDefault", isSignal: true, isRequired: false, transformFunction: null }, nonNullable: { classPropertyName: "nonNullable", publicName: "nonNullable", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { valueCorrected: "valueCorrected" }, host: { classAttribute: "kbq-time-range" }, providers: [KbqTimeRangeService, KbqTimeRangeEditorBridge], ngImport: i0, template: `
|
|
618
942
|
@let localeConfig = localeConfiguration();
|
|
619
943
|
<kbq-time-range-title
|
|
620
944
|
#popover="kbqPopover"
|
|
@@ -646,20 +970,15 @@ class KbqTimeRange {
|
|
|
646
970
|
</ng-template>
|
|
647
971
|
|
|
648
972
|
<ng-template #timeRangePopoverFooter>
|
|
649
|
-
<div class="kbq-time-range__buttons" role="group">
|
|
650
|
-
<button
|
|
651
|
-
kbq-button
|
|
652
|
-
[color]="'contrast'"
|
|
653
|
-
[disabled]="rangeEditorControl.invalid"
|
|
654
|
-
(click)="onApply(popover)"
|
|
655
|
-
>
|
|
973
|
+
<div class="kbq-time-range__buttons" role="group" (mousedown)="onFooterPointerDown()">
|
|
974
|
+
<button kbq-button [color]="'contrast'" (click)="onApply(popover)">
|
|
656
975
|
{{ localeConfig.editor.apply }}
|
|
657
976
|
</button>
|
|
658
977
|
|
|
659
978
|
<button kbq-button (click)="onCancel(popover)">{{ localeConfig.editor.cancel }}</button>
|
|
660
979
|
</div>
|
|
661
980
|
</ng-template>
|
|
662
|
-
`, isInline: true, styles: [".kbq-time-range .kbq-time-range__trigger{display:block}.kbq-time-range__popover .kbq-time-range__buttons{display:flex;flex-direction:row;gap:var(--kbq-size-l)}.kbq-time-range__editor{display:block}.kbq-time-range__editor .kbq-time-range-editor__container{width:100%}.kbq-time-range__editor .kbq-time-range-editor__predefined{column-count:2}.kbq-time-range__editor .kbq-time-range-editor__predefined .kbq-time-range-editor__predefined-option{display:block;margin-bottom:var(--kbq-size-s)}.kbq-time-range__editor .kbq-time-range-editor__range{display:flex;flex-direction:column;gap:var(--kbq-size-s)}.kbq-time-range__editor .kbq-time-range-editor__range .kbq-time-range-editor__date-time{display:flex;flex-direction:row;justify-content:space-between;gap:var(--kbq-size-s)}.kbq-time-range__editor .kbq-time-range-editor__range .kbq-time-range-editor__date-time .kbq-time-range-editor__date-time-prefix{align-
|
|
981
|
+
`, isInline: true, styles: [".kbq-time-range .kbq-time-range__trigger{display:block}.kbq-time-range__popover .kbq-time-range__buttons{display:flex;flex-direction:row;gap:var(--kbq-size-l)}.kbq-time-range__editor{display:block}.kbq-time-range__editor .kbq-time-range-editor__container{width:100%}.kbq-time-range__editor .kbq-time-range-editor__predefined{column-count:2}.kbq-time-range__editor .kbq-time-range-editor__predefined .kbq-time-range-editor__predefined-option{display:block;margin-bottom:var(--kbq-size-s)}.kbq-time-range__editor .kbq-time-range-editor__range{display:flex;flex-direction:column;gap:var(--kbq-size-s)}.kbq-time-range__editor .kbq-time-range-editor__range .kbq-time-range-editor__date-time{display:flex;flex-direction:row;justify-content:space-between;gap:var(--kbq-size-s)}.kbq-time-range__editor .kbq-time-range-editor__range .kbq-time-range-editor__date-time .kbq-time-range-editor__date-time-prefix{display:flex;align-items:center;align-self:flex-start;min-height:var(--kbq-size-3xl);flex:none;width:57px}.kbq-time-range__editor .kbq-time-range-editor__range .kbq-time-range-editor__date-time .kbq-fieldset{flex:1;min-width:0}.kbq-time-range-title-as-form-field{white-space:nowrap;text-overflow:ellipsis;min-height:calc(var(--kbq-form-field-size-height) - 2 * var(--kbq-size-border-width))}.kbq-time-range-title-as-form-field .kbq-time-range-title__placeholder{color:var(--kbq-foreground-contrast-tertiary)}\n"], dependencies: [{ kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.FormControlDirective, selector: "[formControl]", inputs: ["formControl", "disabled", "ngModel"], outputs: ["ngModelChange"], exportAs: ["ngForm"] }, { kind: "ngmodule", type: KbqPopoverModule }, { kind: "directive", type: i2$2.KbqPopoverTrigger, selector: "[kbqPopover]", inputs: ["hideIfNotInViewPort", "kbqPopoverPreventClose", "defaultPaddings", "kbqPopoverVisible", "kbqPopoverPlacement", "kbqPopoverPlacementPriority", "kbqPopoverStickToWindow", "container", "hasBackdrop", "kbqPopoverHeader", "kbqPopoverContent", "kbqPopoverFooter", "kbqPopoverDisabled", "kbqTrigger", "kbqPopoverSize", "kbqPopoverClass", "kbqPopoverContext", "hasCloseButton", "closeOnScroll", "backdropClass", "kbqPopoverArrow", "kbqPopoverOffset", "kbqLeaveDelay"], outputs: ["kbqPopoverPlacementChange", "kbqPopoverVisibleChange"], exportAs: ["kbqPopover"] }, { kind: "ngmodule", type: KbqButtonModule }, { kind: "component", type: i3$1.KbqButton, selector: "[kbq-button]", inputs: ["kbqStyle", "disabled", "tabIndex"] }, { kind: "directive", type: i3$1.KbqButtonCssStyler, selector: "[kbq-button]" }, { kind: "component", type: KbqTimeRangeEditor, selector: "kbq-time-range-editor", inputs: ["maxDate", "minDate", "availableTimeRangeTypes", "rangeValue", "showRangeAsDefault", "localeConfiguration", "optionTemplate"] }, { kind: "component", type: KbqTimeRangeTitle, selector: "kbq-time-range-title", inputs: ["timeRange", "localeConfiguration", "titleTemplate"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None }); }
|
|
663
982
|
}
|
|
664
983
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.30", ngImport: i0, type: KbqTimeRange, decorators: [{
|
|
665
984
|
type: Component,
|
|
@@ -701,22 +1020,17 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.30", ngImpo
|
|
|
701
1020
|
</ng-template>
|
|
702
1021
|
|
|
703
1022
|
<ng-template #timeRangePopoverFooter>
|
|
704
|
-
<div class="kbq-time-range__buttons" role="group">
|
|
705
|
-
<button
|
|
706
|
-
kbq-button
|
|
707
|
-
[color]="'contrast'"
|
|
708
|
-
[disabled]="rangeEditorControl.invalid"
|
|
709
|
-
(click)="onApply(popover)"
|
|
710
|
-
>
|
|
1023
|
+
<div class="kbq-time-range__buttons" role="group" (mousedown)="onFooterPointerDown()">
|
|
1024
|
+
<button kbq-button [color]="'contrast'" (click)="onApply(popover)">
|
|
711
1025
|
{{ localeConfig.editor.apply }}
|
|
712
1026
|
</button>
|
|
713
1027
|
|
|
714
1028
|
<button kbq-button (click)="onCancel(popover)">{{ localeConfig.editor.cancel }}</button>
|
|
715
1029
|
</div>
|
|
716
1030
|
</ng-template>
|
|
717
|
-
`, providers: [KbqTimeRangeService], changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, host: {
|
|
1031
|
+
`, providers: [KbqTimeRangeService, KbqTimeRangeEditorBridge], changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, host: {
|
|
718
1032
|
class: 'kbq-time-range'
|
|
719
|
-
}, styles: [".kbq-time-range .kbq-time-range__trigger{display:block}.kbq-time-range__popover .kbq-time-range__buttons{display:flex;flex-direction:row;gap:var(--kbq-size-l)}.kbq-time-range__editor{display:block}.kbq-time-range__editor .kbq-time-range-editor__container{width:100%}.kbq-time-range__editor .kbq-time-range-editor__predefined{column-count:2}.kbq-time-range__editor .kbq-time-range-editor__predefined .kbq-time-range-editor__predefined-option{display:block;margin-bottom:var(--kbq-size-s)}.kbq-time-range__editor .kbq-time-range-editor__range{display:flex;flex-direction:column;gap:var(--kbq-size-s)}.kbq-time-range__editor .kbq-time-range-editor__range .kbq-time-range-editor__date-time{display:flex;flex-direction:row;justify-content:space-between;gap:var(--kbq-size-s)}.kbq-time-range__editor .kbq-time-range-editor__range .kbq-time-range-editor__date-time .kbq-time-range-editor__date-time-prefix{align-
|
|
1033
|
+
}, styles: [".kbq-time-range .kbq-time-range__trigger{display:block}.kbq-time-range__popover .kbq-time-range__buttons{display:flex;flex-direction:row;gap:var(--kbq-size-l)}.kbq-time-range__editor{display:block}.kbq-time-range__editor .kbq-time-range-editor__container{width:100%}.kbq-time-range__editor .kbq-time-range-editor__predefined{column-count:2}.kbq-time-range__editor .kbq-time-range-editor__predefined .kbq-time-range-editor__predefined-option{display:block;margin-bottom:var(--kbq-size-s)}.kbq-time-range__editor .kbq-time-range-editor__range{display:flex;flex-direction:column;gap:var(--kbq-size-s)}.kbq-time-range__editor .kbq-time-range-editor__range .kbq-time-range-editor__date-time{display:flex;flex-direction:row;justify-content:space-between;gap:var(--kbq-size-s)}.kbq-time-range__editor .kbq-time-range-editor__range .kbq-time-range-editor__date-time .kbq-time-range-editor__date-time-prefix{display:flex;align-items:center;align-self:flex-start;min-height:var(--kbq-size-3xl);flex:none;width:57px}.kbq-time-range__editor .kbq-time-range-editor__range .kbq-time-range-editor__date-time .kbq-fieldset{flex:1;min-width:0}.kbq-time-range-title-as-form-field{white-space:nowrap;text-overflow:ellipsis;min-height:calc(var(--kbq-form-field-size-height) - 2 * var(--kbq-size-border-width))}.kbq-time-range-title-as-form-field .kbq-time-range-title__placeholder{color:var(--kbq-foreground-contrast-tertiary)}\n"] }]
|
|
720
1034
|
}], ctorParameters: () => [], propDecorators: { minDate: [{ type: i0.Input, args: [{ isSignal: true, alias: "minDate", required: false }] }], maxDate: [{ type: i0.Input, args: [{ isSignal: true, alias: "maxDate", required: false }] }], defaultRangeValue: [{ type: i0.Input, args: [{ isSignal: true, alias: "defaultRangeValue", required: false }] }], availableTimeRangeTypes: [{ type: i0.Input, args: [{ isSignal: true, alias: "availableTimeRangeTypes", required: false }] }], titleTemplate: [{ type: i0.Input, args: [{ isSignal: true, alias: "titleTemplate", required: false }] }], optionTemplate: [{ type: i0.Input, args: [{ isSignal: true, alias: "optionTemplate", required: false }] }], arrow: [{ type: i0.Input, args: [{ isSignal: true, alias: "arrow", required: false }] }], showRangeAsDefault: [{ type: i0.Input, args: [{ isSignal: true, alias: "showRangeAsDefault", required: false }] }], nonNullable: [{ type: i0.Input, args: [{ isSignal: true, alias: "nonNullable", required: false }] }], valueCorrected: [{ type: i0.Output, args: ["valueCorrected"] }] } });
|
|
721
1035
|
|
|
722
1036
|
/** Directive for easy using styles of time-range placeholder publicly. */
|