geonetwork-ui 2.11.0-dev.ca7fb6b05 → 2.11.0-dev.cf22e64cf

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 (68) hide show
  1. package/fesm2022/geonetwork-ui.mjs +1736 -1379
  2. package/fesm2022/geonetwork-ui.mjs.map +1 -1
  3. package/index.d.ts +221 -105
  4. package/index.d.ts.map +1 -1
  5. package/package.json +2 -1
  6. package/src/libs/api/repository/src/lib/gn4/elasticsearch/elasticsearch.service.ts +120 -34
  7. package/src/libs/common/domain/src/lib/model/search/filter.model.ts +24 -2
  8. package/src/libs/feature/map/src/index.ts +0 -2
  9. package/src/libs/feature/map/src/lib/add-layer-from-file/add-layer-from-file.component.html +1 -0
  10. package/src/libs/feature/map/src/lib/feature-map.module.ts +1 -8
  11. package/src/libs/feature/notifications/src/lib/notifications.service.ts +6 -4
  12. package/src/libs/feature/notify-reuse/src/lib/notify-reuse-form/notify-reuse-form.component.ts +8 -3
  13. package/src/libs/feature/router/src/lib/default/state/query-params.utils.ts +1 -1
  14. package/src/libs/feature/search/src/index.ts +2 -0
  15. package/src/libs/feature/search/src/lib/filter-dropdown/filter-dropdown.component.html +12 -4
  16. package/src/libs/feature/search/src/lib/filter-dropdown/filter-dropdown.component.ts +63 -19
  17. package/src/libs/feature/{map/src/lib → search/src/lib/geocoding}/geocoding.service.ts +16 -7
  18. package/src/libs/feature/search/src/lib/location-search/location-search.component.html +8 -0
  19. package/src/libs/feature/search/src/lib/location-search/location-search.component.ts +40 -0
  20. package/src/libs/feature/search/src/lib/search-filters-summary-item/search-filters-summary-item.component.ts +19 -8
  21. package/src/libs/feature/search/src/lib/state/reducer.ts +1 -4
  22. package/src/libs/feature/search/src/lib/utils/service/fields.service.ts +15 -1
  23. package/src/libs/feature/search/src/lib/utils/service/fields.ts +61 -2
  24. package/src/libs/ui/elements/src/lib/service-capabilities/service-capabilities.component.ts +2 -1
  25. package/src/libs/ui/inputs/src/index.ts +2 -0
  26. package/src/libs/ui/inputs/src/lib/autocomplete/autocomplete.component.html +10 -1
  27. package/src/libs/ui/inputs/src/lib/autocomplete/autocomplete.component.ts +8 -0
  28. package/src/libs/ui/inputs/src/lib/date-adapter.providers.ts +30 -0
  29. package/src/libs/ui/inputs/src/lib/date-picker/date-picker.component.ts +3 -24
  30. package/src/libs/ui/inputs/src/lib/date-range-dropdown/date-range-dropdown.component.css +37 -3
  31. package/src/libs/ui/inputs/src/lib/date-range-dropdown/date-range-dropdown.component.html +135 -16
  32. package/src/libs/ui/inputs/src/lib/date-range-dropdown/date-range-dropdown.component.ts +166 -29
  33. package/src/libs/ui/inputs/src/lib/date-range-picker/date-range-picker.component.ts +3 -7
  34. package/src/libs/ui/inputs/src/lib/drag-and-drop-file-input/drag-and-drop-file-input.component.css +1 -0
  35. package/src/libs/ui/inputs/src/lib/drag-and-drop-file-input/drag-and-drop-file-input.component.html +20 -7
  36. package/src/libs/ui/inputs/src/lib/drag-and-drop-file-input/drag-and-drop-file-input.component.ts +46 -5
  37. package/src/libs/ui/inputs/src/lib/dropdown-multiselect/dropdown-multiselect.component.html +7 -4
  38. package/src/libs/ui/inputs/src/lib/dropdown-multiselect/dropdown-multiselect.component.ts +12 -9
  39. package/src/libs/ui/inputs/src/lib/dropdown-multiselect/dropdown-multiselect.model.ts +2 -2
  40. package/src/libs/ui/inputs/src/lib/spatial-extent-dropdown/spatial-extent-dropdown.component.html +111 -0
  41. package/src/libs/ui/inputs/src/lib/spatial-extent-dropdown/spatial-extent-dropdown.component.ts +221 -0
  42. package/src/libs/ui/layout/src/lib/block-list/block-list.component.css +1 -0
  43. package/src/libs/ui/map/src/lib/components/map-container/map-container.component.ts +2 -0
  44. package/src/libs/util/app-config/src/lib/app-config.ts +23 -0
  45. package/src/libs/util/app-config/src/lib/fixtures.ts +16 -1
  46. package/src/libs/util/app-config/src/lib/model.ts +15 -1
  47. package/src/libs/util/shared/src/index.ts +1 -0
  48. package/src/libs/util/shared/src/lib/autofocus.directive.ts +26 -0
  49. package/src/libs/util/shared/src/lib/links/link-utils.ts +19 -10
  50. package/src/libs/util/shared/src/lib/services/date.service.ts +3 -3
  51. package/src/libs/util/shared/src/lib/utils/file.ts +15 -0
  52. package/src/libs/util/shared/src/lib/utils/geojson.ts +24 -0
  53. package/src/libs/util/shared/src/lib/utils/index.ts +1 -0
  54. package/tailwind.base.css +5 -0
  55. package/translations/de.json +19 -3
  56. package/translations/en.json +18 -2
  57. package/translations/es.json +17 -1
  58. package/translations/fr.json +23 -7
  59. package/translations/it.json +17 -1
  60. package/translations/nl.json +17 -1
  61. package/translations/pt.json +17 -1
  62. package/translations/sk.json +17 -1
  63. package/src/libs/feature/map/src/lib/geocoding/geocoding.component.html +0 -41
  64. package/src/libs/feature/map/src/lib/geocoding/geocoding.component.ts +0 -112
  65. package/src/libs/feature/map/src/lib/style/index.ts +0 -2
  66. package/src/libs/feature/map/src/lib/style/map-style.fixtures.ts +0 -47
  67. package/src/libs/feature/map/src/lib/style/map-style.service.ts +0 -108
  68. /package/src/libs/{feature/map/src/lib/geocoding/geocoding.component.css → ui/inputs/src/lib/spatial-extent-dropdown/spatial-extent-dropdown.component.css} +0 -0
@@ -1,5 +1,5 @@
1
1
  import { Component, Input, OnInit, inject } from '@angular/core'
2
- import { CommonModule, DatePipe } from '@angular/common'
2
+ import { CommonModule } from '@angular/common'
3
3
  import {
4
4
  catchError,
5
5
  firstValueFrom,
@@ -15,11 +15,15 @@ import { FieldType, FieldValue } from '../utils/service/fields'
15
15
  import { SearchFacade } from '../state/search.facade'
16
16
  import { SearchService } from '../utils/service/search.service'
17
17
  import { FieldsService } from '../utils/service/fields.service'
18
- import { formatUserInfo } from '../../../../../../libs/util/shared/src'
18
+ import { DateService, formatUserInfo } from '../../../../../../libs/util/shared/src'
19
19
  import { marker } from '@biesbjerg/ngx-translate-extract-marker'
20
20
 
21
21
  marker('search.filters.summaryLabel.user')
22
22
  marker('search.filters.summaryLabel.changeDate')
23
+ marker('search.filters.summaryLabel.resourceCreationRevisionDate')
24
+ marker('search.filters.summaryLabel.temporalExtent')
25
+
26
+ const OPEN_BOUND = '…'
23
27
 
24
28
  interface DisplayedValue {
25
29
  label: string
@@ -32,13 +36,12 @@ interface DisplayedValue {
32
36
  imports: [CommonModule, BadgeComponent],
33
37
  templateUrl: './search-filters-summary-item.component.html',
34
38
  styleUrls: ['./search-filters-summary-item.component.css'],
35
- providers: [DatePipe],
36
39
  })
37
40
  export class SearchFiltersSummaryItemComponent implements OnInit {
38
41
  private searchFacade = inject(SearchFacade)
39
42
  private searchService = inject(SearchService)
40
43
  private fieldsService = inject(FieldsService)
41
- private datePipe = inject(DatePipe)
44
+ private dateService = inject(DateService)
42
45
  private translate = inject(TranslateService)
43
46
 
44
47
  @Input() fieldName: string
@@ -82,12 +85,10 @@ export class SearchFiltersSummaryItemComponent implements OnInit {
82
85
  getReadableValues(fieldValues: FieldValue[] | DateRange[]): DisplayedValue[] {
83
86
  return fieldValues.map((value) => {
84
87
  if (this.fieldType === 'dateRange') {
88
+ const { start, end } = value as DateRange
85
89
  return {
86
90
  value,
87
- label: `${this.datePipe.transform(
88
- value.start,
89
- 'dd.MM.yyyy'
90
- )} - ${this.datePipe.transform(value.end, 'dd.MM.yyyy')}`,
91
+ label: `${this.formatBound(start)} - ${this.formatBound(end)}`,
91
92
  }
92
93
  } else if (this.fieldName === 'user') {
93
94
  return { value, label: formatUserInfo(value) }
@@ -97,6 +98,16 @@ export class SearchFiltersSummaryItemComponent implements OnInit {
97
98
  })
98
99
  }
99
100
 
101
+ private formatBound(date: Date | undefined): string {
102
+ return date
103
+ ? this.dateService.formatDate(date, {
104
+ day: '2-digit',
105
+ month: '2-digit',
106
+ year: 'numeric',
107
+ })
108
+ : OPEN_BOUND
109
+ }
110
+
100
111
  async removeFilterValue(fieldValue: FieldValue | DateRange) {
101
112
  const currentFieldValues: DisplayedValue[] = await firstValueFrom(
102
113
  this.fieldValues$
@@ -8,7 +8,6 @@ import {
8
8
  SortByField,
9
9
  } from '../../../../../../libs/common/domain/src/lib/model/search'
10
10
  import { DEFAULT_PAGE_SIZE, FIELDS_SUMMARY } from '../constants'
11
- import { getOptionalSearchConfig } from '../../../../../../libs/util/app-config/src'
12
11
  import { CatalogRecord } from '../../../../../../libs/common/domain/src/lib/model/record'
13
12
 
14
13
  export const SEARCH_FEATURE_KEY = 'searchState'
@@ -56,9 +55,7 @@ export const initSearch = (): SearchStateSearch => {
56
55
  },
57
56
  params: {
58
57
  filters: {},
59
- pageSize: getOptionalSearchConfig()?.LIMIT
60
- ? getOptionalSearchConfig().LIMIT
61
- : DEFAULT_PAGE_SIZE,
58
+ pageSize: DEFAULT_PAGE_SIZE,
62
59
  currentPage: 0,
63
60
  favoritesOnly: false,
64
61
  useSpatialFilter: true,
@@ -2,6 +2,7 @@ import { Injectable, Injector, inject } from '@angular/core'
2
2
  import {
3
3
  AbstractSearchField,
4
4
  AvailableServicesField,
5
+ BoundingBoxSearchField,
5
6
  DateRangeSearchField,
6
7
  FieldValue,
7
8
  FullTextSearchField,
@@ -10,6 +11,7 @@ import {
10
11
  MultilingualSearchField,
11
12
  OrganizationSearchField,
12
13
  OwnerSearchField,
14
+ ResourceCreationRevisionDateSearchField,
13
15
  ResourceTypeLegacyField,
14
16
  SimpleSearchField,
15
17
  TranslatedSearchField,
@@ -42,7 +44,9 @@ marker('search.filters.producerOrg')
42
44
  marker('search.filters.publisherOrg')
43
45
  marker('search.filters.user')
44
46
  marker('search.filters.changeDate')
45
-
47
+ marker('search.filters.resourceCreationRevisionDate')
48
+ marker('search.filters.temporalExtent')
49
+ marker('search.filters.spatialExtent')
46
50
  @Injectable({
47
51
  providedIn: 'root',
48
52
  })
@@ -94,7 +98,17 @@ export class FieldsService {
94
98
  ),
95
99
  user: new UserSearchField(this.injector),
96
100
  changeDate: new DateRangeSearchField('changeDate', this.injector, 'desc'),
101
+ resourceCreationRevisionDate: new ResourceCreationRevisionDateSearchField(
102
+ this.injector,
103
+ 'desc'
104
+ ),
105
+ temporalExtent: new DateRangeSearchField(
106
+ 'resourceTemporalExtentDateRange',
107
+ this.injector,
108
+ 'desc'
109
+ ),
97
110
  availableServices: new AvailableServicesField(this.injector),
111
+ spatialExtent: new BoundingBoxSearchField('spatialExtent', this.injector),
98
112
  } as Record<string, AbstractSearchField>
99
113
 
100
114
  get supportedFields() {
@@ -20,10 +20,14 @@ import {
20
20
  isDateRange,
21
21
  METADATA_LANGUAGE,
22
22
  } from '../../../../../../../libs/api/repository/src'
23
- import { formatUserInfo } from '../../../../../../../libs/util/shared/src'
23
+ import {
24
+ BoundingBox,
25
+ formatUserInfo,
26
+ isBoundingBox,
27
+ } from '../../../../../../../libs/util/shared/src'
24
28
  import { PossibleResourceTypes } from '../../../../../../../libs/api/metadata-converter/src'
25
29
 
26
- export type FieldType = 'values' | 'dateRange'
30
+ export type FieldType = 'values' | 'dateRange' | 'spatialExtent'
27
31
 
28
32
  export type FieldValue = string | number
29
33
  export interface FieldAvailableValue {
@@ -431,6 +435,61 @@ export class DateRangeSearchField extends SimpleSearchField {
431
435
  }
432
436
  }
433
437
 
438
+ // treats a resource's creation/revision dates as an interval (its min and max), so a record matches
439
+ // when that interval intersects the filter range, not just when a single date falls inside it
440
+ export class ResourceCreationRevisionDateSearchField extends DateRangeSearchField {
441
+ constructor(injector: Injector, order: 'asc' | 'desc' = 'desc') {
442
+ super('resourceCreationRevisionDate', injector, order)
443
+ const dateSourceFields = [
444
+ 'creationDateForResource',
445
+ 'revisionDateForResource',
446
+ ]
447
+ const collectSortedDates = dateSourceFields
448
+ .map(
449
+ (field) => `if (doc.containsKey('${field}')) {
450
+ for (def date : doc['${field}']) { dates.add(date.millis); }
451
+ }`
452
+ )
453
+ .join('\n')
454
+
455
+ this.esService.registerRuntimeField(
456
+ 'resourceCreationRevisionDateMin',
457
+ `def dates = [];
458
+ ${collectSortedDates}
459
+ if (dates.length > 0) { dates.sort(null); emit(dates[0]); }`,
460
+ 'date'
461
+ )
462
+ this.esService.registerRuntimeField(
463
+ 'resourceCreationRevisionDateMax',
464
+ `def dates = [];
465
+ ${collectSortedDates}
466
+ if (dates.length > 0) { dates.sort(null); emit(dates[dates.length - 1]); }`,
467
+ 'date'
468
+ )
469
+ }
470
+ }
471
+
472
+ export class BoundingBoxSearchField extends SimpleSearchField {
473
+ getAvailableValues(): Observable<FieldAvailableValue[]> {
474
+ return of([])
475
+ }
476
+
477
+ getFiltersForValues(values: FieldValue[]): Observable<FieldFilters> {
478
+ return of({
479
+ [this.esFieldName]: values.map(Number) as BoundingBox,
480
+ })
481
+ }
482
+
483
+ getValuesForFilter(filters: FieldFilters): Observable<FieldValue[]> {
484
+ const filter = filters[this.esFieldName]
485
+ return of(isBoundingBox(filter) ? filter : [])
486
+ }
487
+
488
+ getType(): FieldType {
489
+ return 'spatialExtent'
490
+ }
491
+ }
492
+
434
493
  marker('search.filters.availableServices.view')
435
494
  marker('search.filters.availableServices.download')
436
495
 
@@ -128,7 +128,8 @@ export class ServiceCapabilitiesComponent implements OnInit {
128
128
  this.loading = true
129
129
  this.availableLayers = await getLayers(
130
130
  this.apiLinks[0].url.href,
131
- this.apiLinks[0].accessServiceProtocol
131
+ this.apiLinks[0].accessServiceProtocol,
132
+ true
132
133
  )
133
134
  this.loading = false
134
135
  this.cdr.detectChanges()
@@ -4,6 +4,7 @@ export * from './lib/button/button.component'
4
4
  export * from './lib/check-toggle/check-toggle.component'
5
5
  export * from './lib/checkbox/checkbox.component'
6
6
  export * from './lib/copy-text-button/copy-text-button.component'
7
+ export * from './lib/date-adapter.providers'
7
8
  export * from './lib/date-picker/date-picker.component'
8
9
  export * from './lib/date-range-dropdown/date-range-dropdown.component'
9
10
  export * from './lib/date-range-picker/date-range-picker.component'
@@ -25,3 +26,4 @@ export * from './lib/url-input/url-input.component'
25
26
  export * from './lib/viewport-intersector/viewport-intersector.component'
26
27
  export * from './lib/search-feature-catalog/search-feature-catalog.component'
27
28
  export * from './lib/date-range-inputs/date-range-inputs.component'
29
+ export * from './lib/spatial-extent-dropdown/spatial-extent-dropdown.component'
@@ -83,7 +83,16 @@
83
83
  >
84
84
  @for (suggestion of suggestions$ | async; track suggestion) {
85
85
  <mat-option [value]="suggestion" class="p-2 suggestion text-start">
86
- {{ displayWithFnInternal(suggestion) }}
86
+ @if (displayWithTemplate) {
87
+ <ng-container
88
+ *ngTemplateOutlet="
89
+ displayWithTemplate;
90
+ context: { $implicit: suggestion }
91
+ "
92
+ ></ng-container>
93
+ } @else {
94
+ {{ displayWithFnInternal(suggestion) }}
95
+ }
87
96
  </mat-option>
88
97
  }
89
98
  </mat-autocomplete>
@@ -11,6 +11,7 @@ import {
11
11
  OnInit,
12
12
  Output,
13
13
  SimpleChanges,
14
+ TemplateRef,
14
15
  ViewChild,
15
16
  inject,
16
17
  } from '@angular/core'
@@ -97,6 +98,13 @@ export class AutocompleteComponent
97
98
  @ViewChild(MatAutocompleteTrigger) triggerRef: MatAutocompleteTrigger
98
99
  @ViewChild(MatAutocomplete) autocomplete: MatAutocomplete
99
100
  @ViewChild('searchInput') inputRef: ElementRef<HTMLInputElement>
101
+ /**
102
+ * Custom template to render each suggestion. Takes precedence over
103
+ * `displayWithFn` when set.
104
+ */
105
+ @Input() displayWithTemplate?: TemplateRef<{
106
+ $implicit: AutocompleteItem
107
+ }>
100
108
 
101
109
  searching: boolean
102
110
  control = new UntypedFormControl()
@@ -0,0 +1,30 @@
1
+ import { inject, Provider } from '@angular/core'
2
+ import {
3
+ DateAdapter,
4
+ MAT_DATE_FORMATS,
5
+ MAT_DATE_LOCALE,
6
+ } from '@angular/material/core'
7
+ import {
8
+ DateFnsAdapter,
9
+ MAT_DATE_FNS_FORMATS,
10
+ } from '@angular/material-date-fns-adapter'
11
+ import { enUS } from 'date-fns/locale/en-US'
12
+ import { DateService } from '../../../../../libs/util/shared/src'
13
+
14
+ export function provideLocalizedDateAdapter(): Provider[] {
15
+ return [
16
+ { provide: MAT_DATE_LOCALE, useValue: enUS },
17
+ { provide: MAT_DATE_FORMATS, useValue: MAT_DATE_FNS_FORMATS },
18
+ {
19
+ provide: DateAdapter,
20
+ useFactory: () => {
21
+ const dateService = inject(DateService)
22
+ const adapter = new DateFnsAdapter()
23
+ dateService
24
+ .getDateFnsLocale()
25
+ .then((locale) => adapter.setLocale(locale))
26
+ return adapter
27
+ },
28
+ },
29
+ ]
30
+ }
@@ -2,15 +2,9 @@ import {
2
2
  ChangeDetectionStrategy,
3
3
  Component,
4
4
  EventEmitter,
5
- inject,
6
5
  Input,
7
6
  Output,
8
7
  } from '@angular/core'
9
- import {
10
- DateAdapter,
11
- MAT_DATE_LOCALE,
12
- MatNativeDateModule,
13
- } from '@angular/material/core'
14
8
  import { MatDatepickerModule } from '@angular/material/datepicker'
15
9
  import { ButtonComponent } from '../button/button.component'
16
10
  import {
@@ -19,7 +13,7 @@ import {
19
13
  provideNgIconsConfig,
20
14
  } from '@ng-icons/core'
21
15
  import { iconoirCalendar } from '@ng-icons/iconoir'
22
- import { TranslateService } from '@ngx-translate/core'
16
+ import { provideLocalizedDateAdapter } from '../date-adapter.providers'
23
17
 
24
18
  @Component({
25
19
  selector: 'gn-ui-date-picker',
@@ -27,31 +21,16 @@ import { TranslateService } from '@ngx-translate/core'
27
21
  styleUrls: ['./date-picker.component.css'],
28
22
  changeDetection: ChangeDetectionStrategy.OnPush,
29
23
  standalone: true,
30
- imports: [
31
- MatNativeDateModule,
32
- MatDatepickerModule,
33
- ButtonComponent,
34
- NgIconComponent,
35
- ],
24
+ imports: [MatDatepickerModule, ButtonComponent, NgIconComponent],
36
25
  providers: [
37
26
  provideIcons({ iconoirCalendar }),
38
27
  provideNgIconsConfig({
39
28
  size: '1.5rem',
40
29
  }),
41
- {
42
- provide: MAT_DATE_LOCALE,
43
- useFactory: (locale: string) => locale,
44
- },
30
+ provideLocalizedDateAdapter(),
45
31
  ],
46
32
  })
47
33
  export class DatePickerComponent {
48
- private dateAdapter = inject<DateAdapter<Date>>(DateAdapter)
49
- private translate = inject(TranslateService)
50
-
51
34
  @Input() date: Date
52
35
  @Output() dateChange = new EventEmitter<Date>()
53
-
54
- constructor() {
55
- this.dateAdapter.setLocale(this.translate.getCurrentLang())
56
- }
57
36
  }
@@ -1,5 +1,39 @@
1
- :host {
2
- .mat-date-range-input-container {
3
- display: none;
1
+ mat-calendar {
2
+ width: 100%;
3
+ }
4
+
5
+ .calendar {
6
+ display: grid;
7
+ grid-template-rows: 1fr;
8
+ }
9
+
10
+ .calendar > mat-calendar {
11
+ min-height: 0;
12
+ overflow: hidden;
13
+ }
14
+
15
+ .calendar.expand {
16
+ animation: open 200ms ease-out;
17
+ }
18
+
19
+ .calendar.collapse {
20
+ animation: open 200ms ease-in reverse;
21
+ }
22
+
23
+ @keyframes open {
24
+ from {
25
+ grid-template-rows: 0fr;
26
+ opacity: 0;
27
+ }
28
+ to {
29
+ grid-template-rows: 1fr;
30
+ opacity: 1;
31
+ }
32
+ }
33
+
34
+ @media (prefers-reduced-motion: reduce) {
35
+ .calendar.expand,
36
+ .calendar.collapse {
37
+ animation-duration: 1ms;
4
38
  }
5
39
  }
@@ -1,30 +1,149 @@
1
1
  <gn-ui-button
2
2
  type="outline"
3
3
  extraClass="bg-background w-full !p-[8px] !pl-[16px]"
4
- (buttonClick)="picker.open()"
4
+ [title]="title"
5
+ (buttonClick)="openOverlay()"
6
+ cdkOverlayOrigin
7
+ #overlayOrigin="cdkOverlayOrigin"
5
8
  >
6
9
  <div class="grow flex items-center mr-2 gap-2 overflow-hidden">
7
10
  <div class="text-left font-medium truncate py-1">
8
11
  {{ title }}
9
12
  </div>
13
+ @if (selectedDatesCount) {
14
+ <div
15
+ class="gn-ui-multiselect-counter shrink-0 rounded-full font-bold text-[12px] w-5 h-5 flex items-center justify-center mr-1 selected-count"
16
+ >
17
+ {{ selectedDatesCount }}
18
+ </div>
19
+ }
10
20
  </div>
21
+ <button
22
+ class="h-6 w-6 flex items-center justify-center"
23
+ data-test="dropdown-clear"
24
+ >
25
+ @if (selectedDatesCount && !overlayOpen) {
26
+ <ng-icon
27
+ class="shrink-0 opacity-40 hover:opacity-80 transition-colors clear-btn"
28
+ (click)="clearDates($event)"
29
+ name="matClose"
30
+ ></ng-icon>
31
+ }
32
+ </button>
11
33
  <ng-icon
12
- [name]="isPickerDisplayed ? 'matExpandLess' : 'matExpandMore'"
34
+ data-test="dropdown-toggle"
35
+ [name]="overlayOpen ? 'matExpandLess' : 'matExpandMore'"
13
36
  class="shrink-0 opacity-40"
14
37
  >
15
38
  </ng-icon>
16
39
  </gn-ui-button>
17
- <!--date range input is just present for output events and hidden from the DOM-->
18
- <mat-date-range-input [rangePicker]="picker">
19
- <input
20
- matStartDate
21
- [value]="startDate"
22
- (dateInput)="startDateChange.emit($event.value)"
23
- />
24
- <input
25
- matEndDate
26
- [value]="endDate"
27
- (dateInput)="endDateChange.emit($event.value)"
28
- />
29
- </mat-date-range-input>
30
- <mat-date-range-picker #picker></mat-date-range-picker>
40
+
41
+ <ng-template
42
+ cdkConnectedOverlay
43
+ cdkConnectedOverlayHasBackdrop
44
+ cdkConnectedOverlayBackdropClass="cdk-overlay-transparent-backdrop"
45
+ [cdkConnectedOverlayOrigin]="overlayOrigin"
46
+ [cdkConnectedOverlayOpen]="overlayOpen"
47
+ [cdkConnectedOverlayPositions]="overlayPositions"
48
+ [cdkConnectedOverlayScrollStrategy]="scrollStrategy"
49
+ (overlayOutsideClick)="closeOverlay()"
50
+ (detach)="closeOverlay()"
51
+ >
52
+ <div
53
+ class="w-[296px] flex flex-col gap-2 bg-white border border-gray-400 rounded px-4 py-3 shadow-lg text-main text-[16px] leading-[24px]"
54
+ data-test="date-range-panel"
55
+ >
56
+ <div class="flex flex-col justify-center border-b border-gray-300 pb-1">
57
+ <ng-container
58
+ [ngTemplateOutlet]="boundRow"
59
+ [ngTemplateOutletContext]="{
60
+ bound: 'start',
61
+ label: 'daterange.filter.from',
62
+ date: startDate,
63
+ }"
64
+ ></ng-container>
65
+ @if (expandedBound === 'start') {
66
+ <div class="calendar" animate.enter="expand" animate.leave="collapse">
67
+ <mat-calendar
68
+ data-test="start-date-calendar"
69
+ [selected]="startDate"
70
+ [startAt]="startDate ?? endDate"
71
+ [maxDate]="endDate"
72
+ (selectedChange)="setStartDate($event)"
73
+ ></mat-calendar>
74
+ </div>
75
+ }
76
+ </div>
77
+ <div class="flex flex-col justify-center">
78
+ <ng-container
79
+ [ngTemplateOutlet]="boundRow"
80
+ [ngTemplateOutletContext]="{
81
+ bound: 'end',
82
+ label: 'daterange.filter.to',
83
+ date: endDate,
84
+ }"
85
+ ></ng-container>
86
+ @if (expandedBound === 'end') {
87
+ <div class="calendar" animate.enter="expand" animate.leave="collapse">
88
+ <mat-calendar
89
+ data-test="end-date-calendar"
90
+ [selected]="endDate"
91
+ [startAt]="endDate ?? startDate"
92
+ [minDate]="startDate"
93
+ (selectedChange)="setEndDate($event)"
94
+ ></mat-calendar>
95
+ </div>
96
+ }
97
+ </div>
98
+ </div>
99
+ </ng-template>
100
+
101
+ <ng-template #boundRow let-bound="bound" let-label="label" let-date="date">
102
+ <div class="flex items-center gap-1 w-full">
103
+ <span class="w-[80px] shrink-0" [translate]="label"></span>
104
+ <div
105
+ class="flex-1 min-w-0 flex items-center gap-1 border rounded-lg px-2 py-1 transition-colors"
106
+ [class]="
107
+ invalidBounds[bound]
108
+ ? 'border-red-400 text-red-800 focus-within:border-red-800 hover:border-red-800'
109
+ : 'border-gray-400 focus-within:border-main hover:border-main'
110
+ "
111
+ >
112
+ <ng-icon
113
+ name="iconoirCalendar"
114
+ class="shrink-0"
115
+ aria-hidden="true"
116
+ ></ng-icon>
117
+ <input
118
+ type="text"
119
+ inputmode="numeric"
120
+ autocomplete="off"
121
+ [gnUiAutofocus]="bound === expandedBound"
122
+ class="min-w-0 grow bg-transparent focus:outline-none"
123
+ [value]="date ? formatDate(date) : ''"
124
+ [placeholder]="'daterange.filter.datePlaceholder' | translate"
125
+ [attr.aria-label]="label | translate"
126
+ [attr.aria-invalid]="invalidBounds[bound] || null"
127
+ [attr.data-test]="bound + '-date-input'"
128
+ (change)="onDateInput(bound, $event)"
129
+ (blur)="normalizeDateInput(bound, $event)"
130
+ (click)="expandedBound !== bound && toggleBound(bound)"
131
+ />
132
+ </div>
133
+ <div class="shrink-0 flex items-center justify-end">
134
+ <gn-ui-button
135
+ type="light"
136
+ [style.--gn-ui-button-padding]="0"
137
+ [style.--gn-ui-button-width]="'24px'"
138
+ [style.--gn-ui-button-height]="'24px'"
139
+ [attr.aria-expanded]="expandedBound === bound"
140
+ [attr.data-test]="bound + '-date-toggle'"
141
+ (buttonClick)="toggleBound(bound)"
142
+ >
143
+ <ng-icon
144
+ [name]="expandedBound === bound ? 'matExpandLess' : 'matExpandMore'"
145
+ ></ng-icon>
146
+ </gn-ui-button>
147
+ </div>
148
+ </div>
149
+ </ng-template>