@angular/material 19.1.0-next.3 → 19.1.0-rc.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (75) hide show
  1. package/LICENSE +1 -1
  2. package/autocomplete/index.d.ts +1 -1
  3. package/badge/index.d.ts +2 -2
  4. package/button/index.d.ts +4 -4
  5. package/checkbox/index.d.ts +4 -4
  6. package/chips/index.d.ts +3 -3
  7. package/core/index.d.ts +0 -1
  8. package/datepicker/index.d.ts +16 -40
  9. package/fesm2022/autocomplete.mjs.map +1 -1
  10. package/fesm2022/badge.mjs +2 -2
  11. package/fesm2022/badge.mjs.map +1 -1
  12. package/fesm2022/button.mjs +2 -2
  13. package/fesm2022/button.mjs.map +1 -1
  14. package/fesm2022/checkbox.mjs +2 -2
  15. package/fesm2022/checkbox.mjs.map +1 -1
  16. package/fesm2022/chips.mjs +2 -2
  17. package/fesm2022/chips.mjs.map +1 -1
  18. package/fesm2022/core.mjs +11 -9
  19. package/fesm2022/core.mjs.map +1 -1
  20. package/fesm2022/datepicker.mjs +750 -751
  21. package/fesm2022/datepicker.mjs.map +1 -1
  22. package/fesm2022/form-field.mjs +2 -2
  23. package/fesm2022/form-field.mjs.map +1 -1
  24. package/fesm2022/icon.mjs +2 -2
  25. package/fesm2022/icon.mjs.map +1 -1
  26. package/fesm2022/list.mjs +5 -4
  27. package/fesm2022/list.mjs.map +1 -1
  28. package/fesm2022/menu.mjs +14 -11
  29. package/fesm2022/menu.mjs.map +1 -1
  30. package/fesm2022/paginator.mjs +12 -15
  31. package/fesm2022/paginator.mjs.map +1 -1
  32. package/fesm2022/progress-bar.mjs +2 -2
  33. package/fesm2022/progress-bar.mjs.map +1 -1
  34. package/fesm2022/progress-spinner.mjs +2 -2
  35. package/fesm2022/progress-spinner.mjs.map +1 -1
  36. package/fesm2022/radio.mjs +4 -4
  37. package/fesm2022/radio.mjs.map +1 -1
  38. package/fesm2022/select.mjs.map +1 -1
  39. package/fesm2022/sidenav.mjs +113 -90
  40. package/fesm2022/sidenav.mjs.map +1 -1
  41. package/fesm2022/slide-toggle.mjs +2 -2
  42. package/fesm2022/slide-toggle.mjs.map +1 -1
  43. package/fesm2022/slider.mjs +2 -2
  44. package/fesm2022/slider.mjs.map +1 -1
  45. package/fesm2022/sort.mjs +2 -2
  46. package/fesm2022/sort.mjs.map +1 -1
  47. package/fesm2022/stepper.mjs +6 -6
  48. package/fesm2022/stepper.mjs.map +1 -1
  49. package/fesm2022/tabs.mjs +8 -8
  50. package/fesm2022/tabs.mjs.map +1 -1
  51. package/fesm2022/timepicker.mjs +3 -1
  52. package/fesm2022/timepicker.mjs.map +1 -1
  53. package/fesm2022/toolbar.mjs +2 -2
  54. package/fesm2022/toolbar.mjs.map +1 -1
  55. package/fesm2022/tooltip.mjs +91 -60
  56. package/fesm2022/tooltip.mjs.map +1 -1
  57. package/form-field/index.d.ts +4 -4
  58. package/icon/index.d.ts +4 -4
  59. package/list/index.d.ts +6 -5
  60. package/menu/index.d.ts +5 -1
  61. package/package.json +2 -2
  62. package/paginator/index.d.ts +5 -5
  63. package/progress-bar/index.d.ts +4 -4
  64. package/progress-spinner/index.d.ts +4 -4
  65. package/radio/index.d.ts +6 -6
  66. package/schematics/ng-add/index.js +1 -1
  67. package/schematics/ng-add/index.mjs +1 -1
  68. package/select/index.d.ts +1 -1
  69. package/sidenav/index.d.ts +11 -10
  70. package/slide-toggle/index.d.ts +4 -4
  71. package/slider/index.d.ts +2 -2
  72. package/stepper/index.d.ts +6 -6
  73. package/tabs/index.d.ts +8 -8
  74. package/toolbar/index.d.ts +2 -2
  75. package/tooltip/index.d.ts +8 -4
@@ -2,7 +2,7 @@ import { _IdGenerator, CdkMonitorFocus, CdkTrapFocus, A11yModule } from '@angula
2
2
  import { Overlay, FlexibleConnectedPositionStrategy, OverlayConfig, OverlayModule } from '@angular/cdk/overlay';
3
3
  import { ComponentPortal, CdkPortalOutlet, TemplatePortal, PortalModule } from '@angular/cdk/portal';
4
4
  import * as i0 from '@angular/core';
5
- import { Injectable, inject, ElementRef, NgZone, EventEmitter, Injector, Renderer2, afterNextRender, Component, ViewEncapsulation, ChangeDetectionStrategy, Input, Output, Optional, SkipSelf, InjectionToken, ChangeDetectorRef, ViewChild, ViewContainerRef, booleanAttribute, Directive, forwardRef, signal, HostAttributeToken, ContentChild, TemplateRef, NgModule } from '@angular/core';
5
+ import { Injectable, inject, ElementRef, NgZone, EventEmitter, Injector, afterNextRender, Component, ViewEncapsulation, ChangeDetectionStrategy, Input, Output, Optional, SkipSelf, InjectionToken, ChangeDetectorRef, ViewChild, ViewContainerRef, booleanAttribute, Directive, forwardRef, signal, HostAttributeToken, ContentChild, TemplateRef, NgModule } from '@angular/core';
6
6
  import { MatButton, MatIconButton, MatButtonModule } from '@angular/material/button';
7
7
  import { CdkScrollableModule } from '@angular/cdk/scrolling';
8
8
  import * as i1 from '@angular/material/core';
@@ -10,13 +10,13 @@ import { _StructuralStylesLoader, DateAdapter, MAT_DATE_FORMATS, ErrorStateMatch
10
10
  import { Subject, Subscription, merge, of } from 'rxjs';
11
11
  import { ESCAPE, hasModifierKey, SPACE, ENTER, PAGE_DOWN, PAGE_UP, END, HOME, DOWN_ARROW, UP_ARROW, RIGHT_ARROW, LEFT_ARROW, BACKSPACE } from '@angular/cdk/keycodes';
12
12
  import { Directionality } from '@angular/cdk/bidi';
13
- import { Platform, _bindEventWithOptions, _getFocusedElementPierceShadowDom } from '@angular/cdk/platform';
13
+ import { normalizePassiveListenerOptions, Platform, _getFocusedElementPierceShadowDom } from '@angular/cdk/platform';
14
14
  import { NgClass, DOCUMENT } from '@angular/common';
15
15
  import { _CdkPrivateStyleLoader, _VisuallyHiddenLoader } from '@angular/cdk/private';
16
16
  import { startWith, take, filter } from 'rxjs/operators';
17
17
  import { coerceStringArray } from '@angular/cdk/coercion';
18
18
  import { trigger, transition, animate, keyframes, style, state } from '@angular/animations';
19
- import { NG_VALUE_ACCESSOR, NG_VALIDATORS, Validators, NgForm, FormGroupDirective, NgControl, ControlContainer } from '@angular/forms';
19
+ import { NG_VALUE_ACCESSOR, NG_VALIDATORS, Validators, ControlContainer, NgForm, FormGroupDirective, NgControl } from '@angular/forms';
20
20
  import { MAT_FORM_FIELD, MatFormFieldControl } from '@angular/material/form-field';
21
21
  import { MAT_INPUT_VALUE_ACCESSOR } from '@angular/material/input';
22
22
 
@@ -113,17 +113,17 @@ class MatCalendarCell {
113
113
  }
114
114
  }
115
115
  /** Event options that can be used to bind an active, capturing event. */
116
- const activeCapturingEventOptions = {
116
+ const activeCapturingEventOptions = normalizePassiveListenerOptions({
117
117
  passive: false,
118
118
  capture: true,
119
- };
119
+ });
120
120
  /** Event options that can be used to bind a passive, capturing event. */
121
- const passiveCapturingEventOptions = {
121
+ const passiveCapturingEventOptions = normalizePassiveListenerOptions({
122
122
  passive: true,
123
123
  capture: true,
124
- };
124
+ });
125
125
  /** Event options that can be used to bind a passive, non-capturing event. */
126
- const passiveEventOptions = { passive: true };
126
+ const passiveEventOptions = normalizePassiveListenerOptions({ passive: true });
127
127
  /**
128
128
  * An internal component used to display calendar data in a table.
129
129
  * @docs-private
@@ -133,7 +133,6 @@ class MatCalendarBody {
133
133
  _ngZone = inject(NgZone);
134
134
  _platform = inject(Platform);
135
135
  _intl = inject(MatDatepickerIntl);
136
- _eventCleanups;
137
136
  /**
138
137
  * Used to skip the next focus event when rendering the preview range.
139
138
  * We need a flag like this, because some browsers fire focus events asynchronously.
@@ -217,7 +216,6 @@ class MatCalendarBody {
217
216
  */
218
217
  _trackRow = (row) => row;
219
218
  constructor() {
220
- const renderer = inject(Renderer2);
221
219
  const idGenerator = inject(_IdGenerator);
222
220
  this._startDateLabelId = idGenerator.getId('mat-calendar-body-start-');
223
221
  this._endDateLabelId = idGenerator.getId('mat-calendar-body-end-');
@@ -226,20 +224,18 @@ class MatCalendarBody {
226
224
  inject(_CdkPrivateStyleLoader).load(_StructuralStylesLoader);
227
225
  this._ngZone.runOutsideAngular(() => {
228
226
  const element = this._elementRef.nativeElement;
229
- const cleanups = [
230
- // `touchmove` is active since we need to call `preventDefault`.
231
- _bindEventWithOptions(renderer, element, 'touchmove', this._touchmoveHandler, activeCapturingEventOptions),
232
- _bindEventWithOptions(renderer, element, 'mouseenter', this._enterHandler, passiveCapturingEventOptions),
233
- _bindEventWithOptions(renderer, element, 'focus', this._enterHandler, passiveCapturingEventOptions),
234
- _bindEventWithOptions(renderer, element, 'mouseleave', this._leaveHandler, passiveCapturingEventOptions),
235
- _bindEventWithOptions(renderer, element, 'blur', this._leaveHandler, passiveCapturingEventOptions),
236
- _bindEventWithOptions(renderer, element, 'mousedown', this._mousedownHandler, passiveEventOptions),
237
- _bindEventWithOptions(renderer, element, 'touchstart', this._mousedownHandler, passiveEventOptions),
238
- ];
227
+ // `touchmove` is active since we need to call `preventDefault`.
228
+ element.addEventListener('touchmove', this._touchmoveHandler, activeCapturingEventOptions);
229
+ element.addEventListener('mouseenter', this._enterHandler, passiveCapturingEventOptions);
230
+ element.addEventListener('focus', this._enterHandler, passiveCapturingEventOptions);
231
+ element.addEventListener('mouseleave', this._leaveHandler, passiveCapturingEventOptions);
232
+ element.addEventListener('blur', this._leaveHandler, passiveCapturingEventOptions);
233
+ element.addEventListener('mousedown', this._mousedownHandler, passiveEventOptions);
234
+ element.addEventListener('touchstart', this._mousedownHandler, passiveEventOptions);
239
235
  if (this._platform.isBrowser) {
240
- cleanups.push(renderer.listen('window', 'mouseup', this._mouseupHandler), renderer.listen('window', 'touchend', this._touchendHandler));
236
+ window.addEventListener('mouseup', this._mouseupHandler);
237
+ window.addEventListener('touchend', this._touchendHandler);
241
238
  }
242
- this._eventCleanups = cleanups;
243
239
  });
244
240
  }
245
241
  /** Called when a cell is clicked. */
@@ -276,7 +272,18 @@ class MatCalendarBody {
276
272
  }
277
273
  }
278
274
  ngOnDestroy() {
279
- this._eventCleanups.forEach(cleanup => cleanup());
275
+ const element = this._elementRef.nativeElement;
276
+ element.removeEventListener('touchmove', this._touchmoveHandler, activeCapturingEventOptions);
277
+ element.removeEventListener('mouseenter', this._enterHandler, passiveCapturingEventOptions);
278
+ element.removeEventListener('focus', this._enterHandler, passiveCapturingEventOptions);
279
+ element.removeEventListener('mouseleave', this._leaveHandler, passiveCapturingEventOptions);
280
+ element.removeEventListener('blur', this._leaveHandler, passiveCapturingEventOptions);
281
+ element.removeEventListener('mousedown', this._mousedownHandler, passiveEventOptions);
282
+ element.removeEventListener('touchstart', this._mousedownHandler, passiveEventOptions);
283
+ if (this._platform.isBrowser) {
284
+ window.removeEventListener('mouseup', this._mouseupHandler);
285
+ window.removeEventListener('touchend', this._touchendHandler);
286
+ }
280
287
  }
281
288
  /** Returns whether a cell is active. */
282
289
  _isActiveCell(rowIndex, colIndex) {
@@ -2507,10 +2514,10 @@ class MatDatepickerContent {
2507
2514
  _calendar;
2508
2515
  /**
2509
2516
  * Theme color of the internal calendar. This API is supported in M2 themes
2510
- * only, it has no effect in M3 themes.
2517
+ * only, it has no effect in M3 themes. For color customization in M3, see https://material.angular.io/components/datepicker/styling.
2511
2518
  *
2512
2519
  * For information on applying color variants in M3, see
2513
- * https://material.angular.io/guide/theming#using-component-color-variants.
2520
+ * https://material.angular.io/guide/material-2-theming#optional-add-backwards-compatibility-styles-for-color-variants
2514
2521
  */
2515
2522
  color;
2516
2523
  /** Reference to the datepicker that created the overlay. */
@@ -2663,10 +2670,10 @@ class MatDatepickerBase {
2663
2670
  startView = 'month';
2664
2671
  /**
2665
2672
  * Theme color of the datepicker's calendar. This API is supported in M2 themes only, it
2666
- * has no effect in M3 themes.
2673
+ * has no effect in M3 themes. For color customization in M3, see https://material.angular.io/components/datepicker/styling.
2667
2674
  *
2668
2675
  * For information on applying color variants in M3, see
2669
- * https://material.angular.io/guide/theming#using-component-color-variants.
2676
+ * https://material.angular.io/guide/material-2-theming#optional-add-backwards-compatibility-styles-for-color-variants
2670
2677
  */
2671
2678
  get color() {
2672
2679
  return (this._color || (this.datepickerInput ? this.datepickerInput.getThemePalette() : undefined));
@@ -3700,819 +3707,811 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.1.0-next.3",
3700
3707
  args: ['button']
3701
3708
  }] } });
3702
3709
 
3703
- // This file contains the `_computeAriaAccessibleName` function, which computes what the *expected*
3704
- // ARIA accessible name would be for a given element. Implements a subset of ARIA specification
3705
- // [Accessible Name and Description Computation 1.2](https://www.w3.org/TR/accname-1.2/).
3706
- //
3707
- // Specification accname-1.2 can be summarized by returning the result of the first method
3708
- // available.
3709
- //
3710
- // 1. `aria-labelledby` attribute
3711
- // ```
3712
- // <!-- example using aria-labelledby-->
3713
- // <label id='label-id'>Start Date</label>
3714
- // <input aria-labelledby='label-id'/>
3715
- // ```
3716
- // 2. `aria-label` attribute (e.g. `<input aria-label="Departure"/>`)
3717
- // 3. Label with `for`/`id`
3718
- // ```
3719
- // <!-- example using for/id -->
3720
- // <label for="current-node">Label</label>
3721
- // <input id="current-node"/>
3722
- // ```
3723
- // 4. `placeholder` attribute (e.g. `<input placeholder="06/03/1990"/>`)
3724
- // 5. `title` attribute (e.g. `<input title="Check-In"/>`)
3725
- // 6. text content
3726
- // ```
3727
- // <!-- example using text content -->
3728
- // <label for="current-node"><span>Departure</span> Date</label>
3729
- // <input id="current-node"/>
3730
- // ```
3731
- /**
3732
- * Computes the *expected* ARIA accessible name for argument element based on [accname-1.2
3733
- * specification](https://www.w3.org/TR/accname-1.2/). Implements a subset of accname-1.2,
3734
- * and should only be used for the Datepicker's specific use case.
3735
- *
3736
- * Intended use:
3737
- * This is not a general use implementation. Only implements the parts of accname-1.2 that are
3738
- * required for the Datepicker's specific use case. This function is not intended for any other
3739
- * use.
3740
- *
3741
- * Limitations:
3742
- * - Only covers the needs of `matStartDate` and `matEndDate`. Does not support other use cases.
3743
- * - See NOTES's in implementation for specific details on what parts of the accname-1.2
3744
- * specification are not implemented.
3745
- *
3746
- * @param element {HTMLInputElement} native &lt;input/&gt; element of `matStartDate` or
3747
- * `matEndDate` component. Corresponds to the 'Root Element' from accname-1.2
3748
- *
3749
- * @return expected ARIA accessible name of argument &lt;input/&gt;
3750
- */
3751
- function _computeAriaAccessibleName(element) {
3752
- return _computeAriaAccessibleNameInternal(element, true);
3753
- }
3754
- /**
3755
- * Determine if argument node is an Element based on `nodeType` property. This function is safe to
3756
- * use with server-side rendering.
3757
- */
3758
- function ssrSafeIsElement(node) {
3759
- return node.nodeType === Node.ELEMENT_NODE;
3760
- }
3761
- /**
3762
- * Determine if argument node is an HTMLInputElement based on `nodeName` property. This funciton is
3763
- * safe to use with server-side rendering.
3764
- */
3765
- function ssrSafeIsHTMLInputElement(node) {
3766
- return node.nodeName === 'INPUT';
3767
- }
3768
- /**
3769
- * Determine if argument node is an HTMLTextAreaElement based on `nodeName` property. This
3770
- * funciton is safe to use with server-side rendering.
3771
- */
3772
- function ssrSafeIsHTMLTextAreaElement(node) {
3773
- return node.nodeName === 'TEXTAREA';
3774
- }
3775
- /**
3776
- * Calculate the expected ARIA accessible name for given DOM Node. Given DOM Node may be either the
3777
- * "Root node" passed to `_computeAriaAccessibleName` or "Current node" as result of recursion.
3778
- *
3779
- * @return the accessible name of argument DOM Node
3780
- *
3781
- * @param currentNode node to determine accessible name of
3782
- * @param isDirectlyReferenced true if `currentNode` is the root node to calculate ARIA accessible
3783
- * name of. False if it is a result of recursion.
3784
- */
3785
- function _computeAriaAccessibleNameInternal(currentNode, isDirectlyReferenced) {
3786
- // NOTE: this differs from accname-1.2 specification.
3787
- // - Does not implement Step 1. of accname-1.2: '''If `currentNode`'s role prohibits naming,
3788
- // return the empty string ("")'''.
3789
- // - Does not implement Step 2.A. of accname-1.2: '''if current node is hidden and not directly
3790
- // referenced by aria-labelledby... return the empty string.'''
3791
- // acc-name-1.2 Step 2.B.: aria-labelledby
3792
- if (ssrSafeIsElement(currentNode) && isDirectlyReferenced) {
3793
- const labelledbyIds = currentNode.getAttribute?.('aria-labelledby')?.split(/\s+/g) || [];
3794
- const validIdRefs = labelledbyIds.reduce((validIds, id) => {
3795
- const elem = document.getElementById(id);
3796
- if (elem) {
3797
- validIds.push(elem);
3798
- }
3799
- return validIds;
3800
- }, []);
3801
- if (validIdRefs.length) {
3802
- return validIdRefs
3803
- .map(idRef => {
3804
- return _computeAriaAccessibleNameInternal(idRef, false);
3805
- })
3806
- .join(' ');
3807
- }
3808
- }
3809
- // acc-name-1.2 Step 2.C.: aria-label
3810
- if (ssrSafeIsElement(currentNode)) {
3811
- const ariaLabel = currentNode.getAttribute('aria-label')?.trim();
3812
- if (ariaLabel) {
3813
- return ariaLabel;
3814
- }
3710
+ class MatDateRangeInput {
3711
+ _changeDetectorRef = inject(ChangeDetectorRef);
3712
+ _elementRef = inject(ElementRef);
3713
+ _dateAdapter = inject(DateAdapter, { optional: true });
3714
+ _formField = inject(MAT_FORM_FIELD, { optional: true });
3715
+ _closedSubscription = Subscription.EMPTY;
3716
+ _openedSubscription = Subscription.EMPTY;
3717
+ _startInput;
3718
+ _endInput;
3719
+ /** Current value of the range input. */
3720
+ get value() {
3721
+ return this._model ? this._model.selection : null;
3815
3722
  }
3816
- // acc-name-1.2 Step 2.D. attribute or element that defines a text alternative
3817
- //
3818
- // NOTE: this differs from accname-1.2 specification.
3819
- // Only implements Step 2.D. for `<label>`,`<input/>`, and `<textarea/>` element. Does not
3820
- // implement other elements that have an attribute or element that defines a text alternative.
3821
- if (ssrSafeIsHTMLInputElement(currentNode) || ssrSafeIsHTMLTextAreaElement(currentNode)) {
3822
- // use label with a `for` attribute referencing the current node
3823
- if (currentNode.labels?.length) {
3824
- return Array.from(currentNode.labels)
3825
- .map(x => _computeAriaAccessibleNameInternal(x, false))
3826
- .join(' ');
3827
- }
3828
- // use placeholder if available
3829
- const placeholder = currentNode.getAttribute('placeholder')?.trim();
3830
- if (placeholder) {
3831
- return placeholder;
3832
- }
3833
- // use title if available
3834
- const title = currentNode.getAttribute('title')?.trim();
3835
- if (title) {
3836
- return title;
3837
- }
3723
+ /** Unique ID for the group. */
3724
+ id = inject(_IdGenerator).getId('mat-date-range-input-');
3725
+ /** Whether the control is focused. */
3726
+ focused = false;
3727
+ /** Whether the control's label should float. */
3728
+ get shouldLabelFloat() {
3729
+ return this.focused || !this.empty;
3838
3730
  }
3839
- // NOTE: this differs from accname-1.2 specification.
3840
- // - does not implement acc-name-1.2 Step 2.E.: '''if the current node is a control embedded
3841
- // within the label... then include the embedded control as part of the text alternative in
3842
- // the following manner...'''. Step 2E applies to embedded controls such as textbox, listbox,
3843
- // range, etc.
3844
- // - does not implement acc-name-1.2 step 2.F.: check that '''role allows name from content''',
3845
- // which applies to `currentNode` and its children.
3846
- // - does not implement acc-name-1.2 Step 2.F.ii.: '''Check for CSS generated textual content'''
3847
- // (e.g. :before and :after).
3848
- // - does not implement acc-name-1.2 Step 2.I.: '''if the current node has a Tooltip attribute,
3849
- // return its value'''
3850
- // Return text content with whitespace collapsed into a single space character. Accomplish
3851
- // acc-name-1.2 steps 2F, 2G, and 2H.
3852
- return (currentNode.textContent || '').replace(/\s+/g, ' ').trim();
3853
- }
3854
-
3855
- /**
3856
- * Used to provide the date range input wrapper component
3857
- * to the parts without circular dependencies.
3858
- */
3859
- const MAT_DATE_RANGE_INPUT_PARENT = new InjectionToken('MAT_DATE_RANGE_INPUT_PARENT');
3860
- /**
3861
- * Base class for the individual inputs that can be projected inside a `mat-date-range-input`.
3862
- */
3863
- class MatDateRangeInputPartBase extends MatDatepickerInputBase {
3864
- _rangeInput = inject(MAT_DATE_RANGE_INPUT_PARENT);
3865
- _elementRef = inject(ElementRef);
3866
- _defaultErrorStateMatcher = inject(ErrorStateMatcher);
3867
- _injector = inject(Injector);
3868
- _parentForm = inject(NgForm, { optional: true });
3869
- _parentFormGroup = inject(FormGroupDirective, { optional: true });
3731
+ /** Name of the form control. */
3732
+ controlType = 'mat-date-range-input';
3870
3733
  /**
3871
- * Form control bound to this input part.
3734
+ * Implemented as a part of `MatFormFieldControl`.
3735
+ * Set the placeholder attribute on `matStartDate` and `matEndDate`.
3872
3736
  * @docs-private
3873
3737
  */
3874
- ngControl;
3875
- _dir = inject(Directionality, { optional: true });
3876
- _errorStateTracker;
3877
- /** Object used to control when error messages are shown. */
3878
- get errorStateMatcher() {
3879
- return this._errorStateTracker.matcher;
3880
- }
3881
- set errorStateMatcher(value) {
3882
- this._errorStateTracker.matcher = value;
3738
+ get placeholder() {
3739
+ const start = this._startInput?._getPlaceholder() || '';
3740
+ const end = this._endInput?._getPlaceholder() || '';
3741
+ return start || end ? `${start} ${this.separator} ${end}` : '';
3883
3742
  }
3884
- /** Whether the input is in an error state. */
3885
- get errorState() {
3886
- return this._errorStateTracker.errorState;
3743
+ /** The range picker that this input is associated with. */
3744
+ get rangePicker() {
3745
+ return this._rangePicker;
3887
3746
  }
3888
- set errorState(value) {
3889
- this._errorStateTracker.errorState = value;
3747
+ set rangePicker(rangePicker) {
3748
+ if (rangePicker) {
3749
+ this._model = rangePicker.registerInput(this);
3750
+ this._rangePicker = rangePicker;
3751
+ this._closedSubscription.unsubscribe();
3752
+ this._openedSubscription.unsubscribe();
3753
+ this._ariaOwns.set(this.rangePicker.opened ? rangePicker.id : null);
3754
+ this._closedSubscription = rangePicker.closedStream.subscribe(() => {
3755
+ this._startInput?._onTouched();
3756
+ this._endInput?._onTouched();
3757
+ this._ariaOwns.set(null);
3758
+ });
3759
+ this._openedSubscription = rangePicker.openedStream.subscribe(() => {
3760
+ this._ariaOwns.set(rangePicker.id);
3761
+ });
3762
+ this._registerModel(this._model);
3763
+ }
3890
3764
  }
3891
- constructor() {
3892
- super();
3893
- this._errorStateTracker = new _ErrorStateTracker(this._defaultErrorStateMatcher, null, this._parentFormGroup, this._parentForm, this.stateChanges);
3765
+ _rangePicker;
3766
+ /** The id of the panel owned by this input. */
3767
+ _ariaOwns = signal(null);
3768
+ /** Whether the input is required. */
3769
+ get required() {
3770
+ return (this._required ??
3771
+ (this._isTargetRequired(this) ||
3772
+ this._isTargetRequired(this._startInput) ||
3773
+ this._isTargetRequired(this._endInput)) ??
3774
+ false);
3894
3775
  }
3895
- ngOnInit() {
3896
- // We need the date input to provide itself as a `ControlValueAccessor` and a `Validator`, while
3897
- // injecting its `NgControl` so that the error state is handled correctly. This introduces a
3898
- // circular dependency, because both `ControlValueAccessor` and `Validator` depend on the input
3899
- // itself. Usually we can work around it for the CVA, but there's no API to do it for the
3900
- // validator. We work around it here by injecting the `NgControl` in `ngOnInit`, after
3901
- // everything has been resolved.
3902
- const ngControl = this._injector.get(NgControl, null, { optional: true, self: true });
3903
- if (ngControl) {
3904
- this.ngControl = ngControl;
3905
- this._errorStateTracker.ngControl = ngControl;
3906
- }
3776
+ set required(value) {
3777
+ this._required = value;
3907
3778
  }
3908
- ngDoCheck() {
3909
- if (this.ngControl) {
3910
- // We need to re-evaluate this on every change detection cycle, because there are some
3911
- // error triggers that we can't subscribe to (e.g. parent form submissions). This means
3912
- // that whatever logic is in here has to be super lean or we risk destroying the performance.
3913
- this.updateErrorState();
3914
- }
3915
- }
3916
- /** Gets whether the input is empty. */
3917
- isEmpty() {
3918
- return this._elementRef.nativeElement.value.length === 0;
3919
- }
3920
- /** Gets the placeholder of the input. */
3921
- _getPlaceholder() {
3922
- return this._elementRef.nativeElement.placeholder;
3923
- }
3924
- /** Focuses the input. */
3925
- focus() {
3926
- this._elementRef.nativeElement.focus();
3779
+ _required;
3780
+ /** Function that can be used to filter out dates within the date range picker. */
3781
+ get dateFilter() {
3782
+ return this._dateFilter;
3927
3783
  }
3928
- /** Gets the value that should be used when mirroring the input's size. */
3929
- getMirrorValue() {
3930
- const element = this._elementRef.nativeElement;
3931
- const value = element.value;
3932
- return value.length > 0 ? value : element.placeholder;
3784
+ set dateFilter(value) {
3785
+ const start = this._startInput;
3786
+ const end = this._endInput;
3787
+ const wasMatchingStart = start && start._matchesFilter(start.value);
3788
+ const wasMatchingEnd = end && end._matchesFilter(start.value);
3789
+ this._dateFilter = value;
3790
+ if (start && start._matchesFilter(start.value) !== wasMatchingStart) {
3791
+ start._validatorOnChange();
3792
+ }
3793
+ if (end && end._matchesFilter(end.value) !== wasMatchingEnd) {
3794
+ end._validatorOnChange();
3795
+ }
3933
3796
  }
3934
- /** Refreshes the error state of the input. */
3935
- updateErrorState() {
3936
- this._errorStateTracker.updateErrorState();
3797
+ _dateFilter;
3798
+ /** The minimum valid date. */
3799
+ get min() {
3800
+ return this._min;
3937
3801
  }
3938
- /** Handles `input` events on the input element. */
3939
- _onInput(value) {
3940
- super._onInput(value);
3941
- this._rangeInput._handleChildValueChange();
3802
+ set min(value) {
3803
+ const validValue = this._dateAdapter.getValidDateOrNull(this._dateAdapter.deserialize(value));
3804
+ if (!this._dateAdapter.sameDate(validValue, this._min)) {
3805
+ this._min = validValue;
3806
+ this._revalidate();
3807
+ }
3942
3808
  }
3943
- /** Opens the datepicker associated with the input. */
3944
- _openPopup() {
3945
- this._rangeInput._openDatepicker();
3809
+ _min;
3810
+ /** The maximum valid date. */
3811
+ get max() {
3812
+ return this._max;
3946
3813
  }
3947
- /** Gets the minimum date from the range input. */
3948
- _getMinDate() {
3949
- return this._rangeInput.min;
3814
+ set max(value) {
3815
+ const validValue = this._dateAdapter.getValidDateOrNull(this._dateAdapter.deserialize(value));
3816
+ if (!this._dateAdapter.sameDate(validValue, this._max)) {
3817
+ this._max = validValue;
3818
+ this._revalidate();
3819
+ }
3950
3820
  }
3951
- /** Gets the maximum date from the range input. */
3952
- _getMaxDate() {
3953
- return this._rangeInput.max;
3821
+ _max;
3822
+ /** Whether the input is disabled. */
3823
+ get disabled() {
3824
+ return this._startInput && this._endInput
3825
+ ? this._startInput.disabled && this._endInput.disabled
3826
+ : this._groupDisabled;
3954
3827
  }
3955
- /** Gets the date filter function from the range input. */
3956
- _getDateFilter() {
3957
- return this._rangeInput.dateFilter;
3828
+ set disabled(value) {
3829
+ if (value !== this._groupDisabled) {
3830
+ this._groupDisabled = value;
3831
+ this.stateChanges.next(undefined);
3832
+ }
3958
3833
  }
3959
- _parentDisabled() {
3960
- return this._rangeInput._groupDisabled;
3834
+ _groupDisabled = false;
3835
+ /** Whether the input is in an error state. */
3836
+ get errorState() {
3837
+ if (this._startInput && this._endInput) {
3838
+ return this._startInput.errorState || this._endInput.errorState;
3839
+ }
3840
+ return false;
3961
3841
  }
3962
- _shouldHandleChangeEvent({ source }) {
3963
- return source !== this._rangeInput._startInput && source !== this._rangeInput._endInput;
3842
+ /** Whether the datepicker input is empty. */
3843
+ get empty() {
3844
+ const startEmpty = this._startInput ? this._startInput.isEmpty() : false;
3845
+ const endEmpty = this._endInput ? this._endInput.isEmpty() : false;
3846
+ return startEmpty && endEmpty;
3964
3847
  }
3965
- _assignValueProgrammatically(value) {
3966
- super._assignValueProgrammatically(value);
3967
- const opposite = (this === this._rangeInput._startInput
3968
- ? this._rangeInput._endInput
3969
- : this._rangeInput._startInput);
3970
- opposite?._validatorOnChange();
3848
+ /** Value for the `aria-describedby` attribute of the inputs. */
3849
+ _ariaDescribedBy = null;
3850
+ /** Date selection model currently registered with the input. */
3851
+ _model;
3852
+ /** Separator text to be shown between the inputs. */
3853
+ separator = '–';
3854
+ /** Start of the comparison range that should be shown in the calendar. */
3855
+ comparisonStart = null;
3856
+ /** End of the comparison range that should be shown in the calendar. */
3857
+ comparisonEnd = null;
3858
+ /**
3859
+ * Implemented as a part of `MatFormFieldControl`.
3860
+ * TODO(crisbeto): change type to `AbstractControlDirective` after #18206 lands.
3861
+ * @docs-private
3862
+ */
3863
+ ngControl;
3864
+ /** Emits when the input's state has changed. */
3865
+ stateChanges = new Subject();
3866
+ /**
3867
+ * Disable the automatic labeling to avoid issues like #27241.
3868
+ * @docs-private
3869
+ */
3870
+ disableAutomaticLabeling = true;
3871
+ constructor() {
3872
+ if (!this._dateAdapter && (typeof ngDevMode === 'undefined' || ngDevMode)) {
3873
+ throw createMissingDateImplError('DateAdapter');
3874
+ }
3875
+ // The datepicker module can be used both with MDC and non-MDC form fields. We have
3876
+ // to conditionally add the MDC input class so that the range picker looks correctly.
3877
+ if (this._formField?._elementRef.nativeElement.classList.contains('mat-mdc-form-field')) {
3878
+ this._elementRef.nativeElement.classList.add('mat-mdc-input-element', 'mat-mdc-form-field-input-control', 'mdc-text-field__input');
3879
+ }
3880
+ // TODO(crisbeto): remove `as any` after #18206 lands.
3881
+ this.ngControl = inject(ControlContainer, { optional: true, self: true });
3971
3882
  }
3972
- /** return the ARIA accessible name of the input element */
3973
- _getAccessibleName() {
3974
- return _computeAriaAccessibleName(this._elementRef.nativeElement);
3883
+ /**
3884
+ * Implemented as a part of `MatFormFieldControl`.
3885
+ * @docs-private
3886
+ */
3887
+ setDescribedByIds(ids) {
3888
+ this._ariaDescribedBy = ids.length ? ids.join(' ') : null;
3975
3889
  }
3976
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.1.0-next.3", ngImport: i0, type: MatDateRangeInputPartBase, deps: [], target: i0.ɵɵFactoryTarget.Directive });
3977
- static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "19.1.0-next.3", type: MatDateRangeInputPartBase, isStandalone: true, inputs: { errorStateMatcher: "errorStateMatcher" }, usesInheritance: true, ngImport: i0 });
3978
- }
3979
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.1.0-next.3", ngImport: i0, type: MatDateRangeInputPartBase, decorators: [{
3980
- type: Directive
3981
- }], ctorParameters: () => [], propDecorators: { errorStateMatcher: [{
3982
- type: Input
3983
- }] } });
3984
- /** Input for entering the start date in a `mat-date-range-input`. */
3985
- class MatStartDate extends MatDateRangeInputPartBase {
3986
- /** Validator that checks that the start date isn't after the end date. */
3987
- _startValidator = (control) => {
3988
- const start = this._dateAdapter.getValidDateOrNull(this._dateAdapter.deserialize(control.value));
3989
- const end = this._model ? this._model.selection.end : null;
3990
- return !start || !end || this._dateAdapter.compareDate(start, end) <= 0
3991
- ? null
3992
- : { 'matStartDateInvalid': { 'end': end, 'actual': start } };
3993
- };
3994
- _validator = Validators.compose([...super._getValidators(), this._startValidator]);
3995
- _getValueFromModel(modelValue) {
3996
- return modelValue.start;
3890
+ /**
3891
+ * Implemented as a part of `MatFormFieldControl`.
3892
+ * @docs-private
3893
+ */
3894
+ onContainerClick() {
3895
+ if (!this.focused && !this.disabled) {
3896
+ if (!this._model || !this._model.selection.start) {
3897
+ this._startInput.focus();
3898
+ }
3899
+ else {
3900
+ this._endInput.focus();
3901
+ }
3902
+ }
3997
3903
  }
3998
- _shouldHandleChangeEvent(change) {
3999
- if (!super._shouldHandleChangeEvent(change)) {
4000
- return false;
3904
+ ngAfterContentInit() {
3905
+ if (typeof ngDevMode === 'undefined' || ngDevMode) {
3906
+ if (!this._startInput) {
3907
+ throw Error('mat-date-range-input must contain a matStartDate input');
3908
+ }
3909
+ if (!this._endInput) {
3910
+ throw Error('mat-date-range-input must contain a matEndDate input');
3911
+ }
4001
3912
  }
4002
- else {
4003
- return !change.oldValue?.start
4004
- ? !!change.selection.start
4005
- : !change.selection.start ||
4006
- !!this._dateAdapter.compareDate(change.oldValue.start, change.selection.start);
3913
+ if (this._model) {
3914
+ this._registerModel(this._model);
4007
3915
  }
3916
+ // We don't need to unsubscribe from this, because we
3917
+ // know that the input streams will be completed on destroy.
3918
+ merge(this._startInput.stateChanges, this._endInput.stateChanges).subscribe(() => {
3919
+ this.stateChanges.next(undefined);
3920
+ });
4008
3921
  }
4009
- _assignValueToModel(value) {
4010
- if (this._model) {
4011
- const range = new DateRange(value, this._model.selection.end);
4012
- this._model.updateSelection(range, this);
3922
+ ngOnChanges(changes) {
3923
+ if (dateInputsHaveChanged(changes, this._dateAdapter)) {
3924
+ this.stateChanges.next(undefined);
4013
3925
  }
4014
3926
  }
4015
- _formatValue(value) {
4016
- super._formatValue(value);
4017
- // Any time the input value is reformatted we need to tell the parent.
4018
- this._rangeInput._handleChildValueChange();
3927
+ ngOnDestroy() {
3928
+ this._closedSubscription.unsubscribe();
3929
+ this._openedSubscription.unsubscribe();
3930
+ this.stateChanges.complete();
4019
3931
  }
4020
- _onKeydown(event) {
4021
- const endInput = this._rangeInput._endInput;
4022
- const element = this._elementRef.nativeElement;
4023
- const isLtr = this._dir?.value !== 'rtl';
4024
- // If the user hits RIGHT (LTR) when at the end of the input (and no
4025
- // selection), move the cursor to the start of the end input.
4026
- if (((event.keyCode === RIGHT_ARROW && isLtr) || (event.keyCode === LEFT_ARROW && !isLtr)) &&
4027
- element.selectionStart === element.value.length &&
4028
- element.selectionEnd === element.value.length) {
4029
- event.preventDefault();
4030
- endInput._elementRef.nativeElement.setSelectionRange(0, 0);
4031
- endInput.focus();
4032
- }
4033
- else {
4034
- super._onKeydown(event);
4035
- }
3932
+ /** Gets the date at which the calendar should start. */
3933
+ getStartValue() {
3934
+ return this.value ? this.value.start : null;
4036
3935
  }
4037
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.1.0-next.3", ngImport: i0, type: MatStartDate, deps: null, target: i0.ɵɵFactoryTarget.Directive });
4038
- static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "19.1.0-next.3", type: MatStartDate, isStandalone: true, selector: "input[matStartDate]", outputs: { dateChange: "dateChange", dateInput: "dateInput" }, host: { attributes: { "type": "text" }, listeners: { "input": "_onInput($event.target.value)", "change": "_onChange()", "keydown": "_onKeydown($event)", "blur": "_onBlur()" }, properties: { "disabled": "disabled", "attr.aria-haspopup": "_rangeInput.rangePicker ? \"dialog\" : null", "attr.aria-owns": "_rangeInput._ariaOwns\n ? _rangeInput._ariaOwns()\n : (_rangeInput.rangePicker?.opened && _rangeInput.rangePicker.id) || null", "attr.min": "_getMinDate() ? _dateAdapter.toIso8601(_getMinDate()) : null", "attr.max": "_getMaxDate() ? _dateAdapter.toIso8601(_getMaxDate()) : null" }, classAttribute: "mat-start-date mat-date-range-input-inner" }, providers: [
4039
- { provide: NG_VALUE_ACCESSOR, useExisting: MatStartDate, multi: true },
4040
- { provide: NG_VALIDATORS, useExisting: MatStartDate, multi: true },
4041
- ], usesInheritance: true, ngImport: i0 });
4042
- }
4043
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.1.0-next.3", ngImport: i0, type: MatStartDate, decorators: [{
4044
- type: Directive,
4045
- args: [{
4046
- selector: 'input[matStartDate]',
4047
- host: {
4048
- 'class': 'mat-start-date mat-date-range-input-inner',
4049
- '[disabled]': 'disabled',
4050
- '(input)': '_onInput($event.target.value)',
4051
- '(change)': '_onChange()',
4052
- '(keydown)': '_onKeydown($event)',
4053
- '[attr.aria-haspopup]': '_rangeInput.rangePicker ? "dialog" : null',
4054
- '[attr.aria-owns]': `_rangeInput._ariaOwns
4055
- ? _rangeInput._ariaOwns()
4056
- : (_rangeInput.rangePicker?.opened && _rangeInput.rangePicker.id) || null`,
4057
- '[attr.min]': '_getMinDate() ? _dateAdapter.toIso8601(_getMinDate()) : null',
4058
- '[attr.max]': '_getMaxDate() ? _dateAdapter.toIso8601(_getMaxDate()) : null',
4059
- '(blur)': '_onBlur()',
4060
- 'type': 'text',
4061
- },
4062
- providers: [
4063
- { provide: NG_VALUE_ACCESSOR, useExisting: MatStartDate, multi: true },
4064
- { provide: NG_VALIDATORS, useExisting: MatStartDate, multi: true },
4065
- ],
4066
- // These need to be specified explicitly, because some tooling doesn't
4067
- // seem to pick them up from the base class. See #20932.
4068
- outputs: ['dateChange', 'dateInput'],
4069
- }]
4070
- }] });
4071
- /** Input for entering the end date in a `mat-date-range-input`. */
4072
- class MatEndDate extends MatDateRangeInputPartBase {
4073
- /** Validator that checks that the end date isn't before the start date. */
4074
- _endValidator = (control) => {
4075
- const end = this._dateAdapter.getValidDateOrNull(this._dateAdapter.deserialize(control.value));
4076
- const start = this._model ? this._model.selection.start : null;
4077
- return !end || !start || this._dateAdapter.compareDate(end, start) >= 0
4078
- ? null
4079
- : { 'matEndDateInvalid': { 'start': start, 'actual': end } };
4080
- };
4081
- _validator = Validators.compose([...super._getValidators(), this._endValidator]);
4082
- _getValueFromModel(modelValue) {
4083
- return modelValue.end;
4084
- }
4085
- _shouldHandleChangeEvent(change) {
4086
- if (!super._shouldHandleChangeEvent(change)) {
4087
- return false;
4088
- }
4089
- else {
4090
- return !change.oldValue?.end
4091
- ? !!change.selection.end
4092
- : !change.selection.end ||
4093
- !!this._dateAdapter.compareDate(change.oldValue.end, change.selection.end);
4094
- }
3936
+ /** Gets the input's theme palette. */
3937
+ getThemePalette() {
3938
+ return this._formField ? this._formField.color : undefined;
4095
3939
  }
4096
- _assignValueToModel(value) {
4097
- if (this._model) {
4098
- const range = new DateRange(this._model.selection.start, value);
4099
- this._model.updateSelection(range, this);
4100
- }
3940
+ /** Gets the element to which the calendar overlay should be attached. */
3941
+ getConnectedOverlayOrigin() {
3942
+ return this._formField ? this._formField.getConnectedOverlayOrigin() : this._elementRef;
4101
3943
  }
4102
- _moveCaretToEndOfStartInput() {
4103
- const startInput = this._rangeInput._startInput._elementRef.nativeElement;
4104
- const value = startInput.value;
4105
- if (value.length > 0) {
4106
- startInput.setSelectionRange(value.length, value.length);
4107
- }
4108
- startInput.focus();
3944
+ /** Gets the ID of an element that should be used a description for the calendar overlay. */
3945
+ getOverlayLabelId() {
3946
+ return this._formField ? this._formField.getLabelId() : null;
4109
3947
  }
4110
- _onKeydown(event) {
4111
- const element = this._elementRef.nativeElement;
4112
- const isLtr = this._dir?.value !== 'rtl';
4113
- // If the user is pressing backspace on an empty end input, move focus back to the start.
4114
- if (event.keyCode === BACKSPACE && !element.value) {
4115
- this._moveCaretToEndOfStartInput();
4116
- }
4117
- // If the user hits LEFT (LTR) when at the start of the input (and no
4118
- // selection), move the cursor to the end of the start input.
4119
- else if (((event.keyCode === LEFT_ARROW && isLtr) || (event.keyCode === RIGHT_ARROW && !isLtr)) &&
4120
- element.selectionStart === 0 &&
4121
- element.selectionEnd === 0) {
4122
- event.preventDefault();
4123
- this._moveCaretToEndOfStartInput();
4124
- }
4125
- else {
4126
- super._onKeydown(event);
4127
- }
3948
+ /** Gets the value that is used to mirror the state input. */
3949
+ _getInputMirrorValue(part) {
3950
+ const input = part === 'start' ? this._startInput : this._endInput;
3951
+ return input ? input.getMirrorValue() : '';
4128
3952
  }
4129
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.1.0-next.3", ngImport: i0, type: MatEndDate, deps: null, target: i0.ɵɵFactoryTarget.Directive });
4130
- static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "19.1.0-next.3", type: MatEndDate, isStandalone: true, selector: "input[matEndDate]", outputs: { dateChange: "dateChange", dateInput: "dateInput" }, host: { attributes: { "type": "text" }, listeners: { "input": "_onInput($event.target.value)", "change": "_onChange()", "keydown": "_onKeydown($event)", "blur": "_onBlur()" }, properties: { "disabled": "disabled", "attr.aria-haspopup": "_rangeInput.rangePicker ? \"dialog\" : null", "attr.aria-owns": "_rangeInput._ariaOwns\n ? _rangeInput._ariaOwns()\n : (_rangeInput.rangePicker?.opened && _rangeInput.rangePicker.id) || null", "attr.min": "_getMinDate() ? _dateAdapter.toIso8601(_getMinDate()) : null", "attr.max": "_getMaxDate() ? _dateAdapter.toIso8601(_getMaxDate()) : null" }, classAttribute: "mat-end-date mat-date-range-input-inner" }, providers: [
4131
- { provide: NG_VALUE_ACCESSOR, useExisting: MatEndDate, multi: true },
4132
- { provide: NG_VALIDATORS, useExisting: MatEndDate, multi: true },
4133
- ], usesInheritance: true, ngImport: i0 });
4134
- }
4135
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.1.0-next.3", ngImport: i0, type: MatEndDate, decorators: [{
4136
- type: Directive,
4137
- args: [{
4138
- selector: 'input[matEndDate]',
4139
- host: {
4140
- 'class': 'mat-end-date mat-date-range-input-inner',
4141
- '[disabled]': 'disabled',
4142
- '(input)': '_onInput($event.target.value)',
4143
- '(change)': '_onChange()',
4144
- '(keydown)': '_onKeydown($event)',
4145
- '[attr.aria-haspopup]': '_rangeInput.rangePicker ? "dialog" : null',
4146
- '[attr.aria-owns]': `_rangeInput._ariaOwns
4147
- ? _rangeInput._ariaOwns()
4148
- : (_rangeInput.rangePicker?.opened && _rangeInput.rangePicker.id) || null`,
4149
- '[attr.min]': '_getMinDate() ? _dateAdapter.toIso8601(_getMinDate()) : null',
4150
- '[attr.max]': '_getMaxDate() ? _dateAdapter.toIso8601(_getMaxDate()) : null',
4151
- '(blur)': '_onBlur()',
4152
- 'type': 'text',
4153
- },
4154
- providers: [
4155
- { provide: NG_VALUE_ACCESSOR, useExisting: MatEndDate, multi: true },
4156
- { provide: NG_VALIDATORS, useExisting: MatEndDate, multi: true },
4157
- ],
4158
- // These need to be specified explicitly, because some tooling doesn't
4159
- // seem to pick them up from the base class. See #20932.
4160
- outputs: ['dateChange', 'dateInput'],
4161
- }]
4162
- }] });
4163
-
4164
- class MatDateRangeInput {
4165
- _changeDetectorRef = inject(ChangeDetectorRef);
4166
- _elementRef = inject(ElementRef);
4167
- _dateAdapter = inject(DateAdapter, { optional: true });
4168
- _formField = inject(MAT_FORM_FIELD, { optional: true });
4169
- _closedSubscription = Subscription.EMPTY;
4170
- _openedSubscription = Subscription.EMPTY;
4171
- /** Current value of the range input. */
4172
- get value() {
4173
- return this._model ? this._model.selection : null;
3953
+ /** Whether the input placeholders should be hidden. */
3954
+ _shouldHidePlaceholders() {
3955
+ return this._startInput ? !this._startInput.isEmpty() : false;
4174
3956
  }
4175
- /** Unique ID for the group. */
4176
- id = inject(_IdGenerator).getId('mat-date-range-input-');
4177
- /** Whether the control is focused. */
4178
- focused = false;
4179
- /** Whether the control's label should float. */
4180
- get shouldLabelFloat() {
4181
- return this.focused || !this.empty;
3957
+ /** Handles the value in one of the child inputs changing. */
3958
+ _handleChildValueChange() {
3959
+ this.stateChanges.next(undefined);
3960
+ this._changeDetectorRef.markForCheck();
4182
3961
  }
4183
- /** Name of the form control. */
4184
- controlType = 'mat-date-range-input';
4185
- /**
4186
- * Implemented as a part of `MatFormFieldControl`.
4187
- * Set the placeholder attribute on `matStartDate` and `matEndDate`.
4188
- * @docs-private
4189
- */
4190
- get placeholder() {
4191
- const start = this._startInput?._getPlaceholder() || '';
4192
- const end = this._endInput?._getPlaceholder() || '';
4193
- return start || end ? `${start} ${this.separator} ${end}` : '';
3962
+ /** Opens the date range picker associated with the input. */
3963
+ _openDatepicker() {
3964
+ if (this._rangePicker) {
3965
+ this._rangePicker.open();
3966
+ }
4194
3967
  }
4195
- /** The range picker that this input is associated with. */
4196
- get rangePicker() {
4197
- return this._rangePicker;
3968
+ /** Whether the separate text should be hidden. */
3969
+ _shouldHideSeparator() {
3970
+ return ((!this._formField ||
3971
+ (this._formField.getLabelId() && !this._formField._shouldLabelFloat())) &&
3972
+ this.empty);
4198
3973
  }
4199
- set rangePicker(rangePicker) {
4200
- if (rangePicker) {
4201
- this._model = rangePicker.registerInput(this);
4202
- this._rangePicker = rangePicker;
4203
- this._closedSubscription.unsubscribe();
4204
- this._openedSubscription.unsubscribe();
4205
- this._ariaOwns.set(this.rangePicker.opened ? rangePicker.id : null);
4206
- this._closedSubscription = rangePicker.closedStream.subscribe(() => {
4207
- this._startInput?._onTouched();
4208
- this._endInput?._onTouched();
4209
- this._ariaOwns.set(null);
4210
- });
4211
- this._openedSubscription = rangePicker.openedStream.subscribe(() => {
4212
- this._ariaOwns.set(rangePicker.id);
4213
- });
4214
- this._registerModel(this._model);
4215
- }
3974
+ /** Gets the value for the `aria-labelledby` attribute of the inputs. */
3975
+ _getAriaLabelledby() {
3976
+ const formField = this._formField;
3977
+ return formField && formField._hasFloatingLabel() ? formField._labelId : null;
4216
3978
  }
4217
- _rangePicker;
4218
- /** The id of the panel owned by this input. */
4219
- _ariaOwns = signal(null);
4220
- /** Whether the input is required. */
4221
- get required() {
4222
- return (this._required ??
4223
- (this._isTargetRequired(this) ||
4224
- this._isTargetRequired(this._startInput) ||
4225
- this._isTargetRequired(this._endInput)) ??
4226
- false);
3979
+ _getStartDateAccessibleName() {
3980
+ return this._startInput._getAccessibleName();
4227
3981
  }
4228
- set required(value) {
4229
- this._required = value;
3982
+ _getEndDateAccessibleName() {
3983
+ return this._endInput._getAccessibleName();
4230
3984
  }
4231
- _required;
4232
- /** Function that can be used to filter out dates within the date range picker. */
4233
- get dateFilter() {
4234
- return this._dateFilter;
3985
+ /** Updates the focused state of the range input. */
3986
+ _updateFocus(origin) {
3987
+ this.focused = origin !== null;
3988
+ this.stateChanges.next();
4235
3989
  }
4236
- set dateFilter(value) {
4237
- const start = this._startInput;
4238
- const end = this._endInput;
4239
- const wasMatchingStart = start && start._matchesFilter(start.value);
4240
- const wasMatchingEnd = end && end._matchesFilter(start.value);
4241
- this._dateFilter = value;
4242
- if (start && start._matchesFilter(start.value) !== wasMatchingStart) {
4243
- start._validatorOnChange();
3990
+ /** Re-runs the validators on the start/end inputs. */
3991
+ _revalidate() {
3992
+ if (this._startInput) {
3993
+ this._startInput._validatorOnChange();
4244
3994
  }
4245
- if (end && end._matchesFilter(end.value) !== wasMatchingEnd) {
4246
- end._validatorOnChange();
3995
+ if (this._endInput) {
3996
+ this._endInput._validatorOnChange();
4247
3997
  }
4248
3998
  }
4249
- _dateFilter;
4250
- /** The minimum valid date. */
4251
- get min() {
4252
- return this._min;
4253
- }
4254
- set min(value) {
4255
- const validValue = this._dateAdapter.getValidDateOrNull(this._dateAdapter.deserialize(value));
4256
- if (!this._dateAdapter.sameDate(validValue, this._min)) {
4257
- this._min = validValue;
4258
- this._revalidate();
3999
+ /** Registers the current date selection model with the start/end inputs. */
4000
+ _registerModel(model) {
4001
+ if (this._startInput) {
4002
+ this._startInput._registerModel(model);
4003
+ }
4004
+ if (this._endInput) {
4005
+ this._endInput._registerModel(model);
4259
4006
  }
4260
4007
  }
4261
- _min;
4262
- /** The maximum valid date. */
4263
- get max() {
4264
- return this._max;
4008
+ /** Checks whether a specific range input directive is required. */
4009
+ _isTargetRequired(target) {
4010
+ return target?.ngControl?.control?.hasValidator(Validators.required);
4265
4011
  }
4266
- set max(value) {
4267
- const validValue = this._dateAdapter.getValidDateOrNull(this._dateAdapter.deserialize(value));
4268
- if (!this._dateAdapter.sameDate(validValue, this._max)) {
4269
- this._max = validValue;
4270
- this._revalidate();
4012
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.1.0-next.3", ngImport: i0, type: MatDateRangeInput, deps: [], target: i0.ɵɵFactoryTarget.Component });
4013
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "16.1.0", version: "19.1.0-next.3", type: MatDateRangeInput, isStandalone: true, selector: "mat-date-range-input", inputs: { rangePicker: "rangePicker", required: ["required", "required", booleanAttribute], dateFilter: "dateFilter", min: "min", max: "max", disabled: ["disabled", "disabled", booleanAttribute], separator: "separator", comparisonStart: "comparisonStart", comparisonEnd: "comparisonEnd" }, host: { attributes: { "role": "group" }, properties: { "class.mat-date-range-input-hide-placeholders": "_shouldHidePlaceholders()", "class.mat-date-range-input-required": "required", "attr.id": "id", "attr.aria-labelledby": "_getAriaLabelledby()", "attr.aria-describedby": "_ariaDescribedBy", "attr.data-mat-calendar": "rangePicker ? rangePicker.id : null" }, classAttribute: "mat-date-range-input" }, providers: [{ provide: MatFormFieldControl, useExisting: MatDateRangeInput }], exportAs: ["matDateRangeInput"], usesOnChanges: true, ngImport: i0, template: "<div\n class=\"mat-date-range-input-container\"\n cdkMonitorSubtreeFocus\n (cdkFocusChange)=\"_updateFocus($event)\">\n <div class=\"mat-date-range-input-wrapper\">\n <ng-content select=\"input[matStartDate]\"></ng-content>\n <span\n class=\"mat-date-range-input-mirror\"\n aria-hidden=\"true\">{{_getInputMirrorValue('start')}}</span>\n </div>\n\n <span\n class=\"mat-date-range-input-separator\"\n [class.mat-date-range-input-separator-hidden]=\"_shouldHideSeparator()\">{{separator}}</span>\n\n <div class=\"mat-date-range-input-wrapper mat-date-range-input-end-wrapper\">\n <ng-content select=\"input[matEndDate]\"></ng-content>\n <span\n class=\"mat-date-range-input-mirror\"\n aria-hidden=\"true\">{{_getInputMirrorValue('end')}}</span>\n </div>\n</div>\n\n", styles: [".mat-date-range-input{display:block;width:100%}.mat-date-range-input-container{display:flex;align-items:center}.mat-date-range-input-separator{transition:opacity 400ms 133.3333333333ms cubic-bezier(0.25, 0.8, 0.25, 1);margin:0 4px;color:var(--mat-datepicker-range-input-separator-color, var(--mat-sys-on-surface))}.mat-form-field-disabled .mat-date-range-input-separator{color:var(--mat-datepicker-range-input-disabled-state-separator-color, color-mix(in srgb, var(--mat-sys-on-surface) 38%, transparent))}._mat-animation-noopable .mat-date-range-input-separator{transition:none}.mat-date-range-input-separator-hidden{-webkit-user-select:none;user-select:none;opacity:0;transition:none}.mat-date-range-input-wrapper{position:relative;overflow:hidden;max-width:calc(50% - 4px)}.mat-date-range-input-end-wrapper{flex-grow:1}.mat-date-range-input-inner{position:absolute;top:0;left:0;font:inherit;background:rgba(0,0,0,0);color:currentColor;border:none;outline:none;padding:0;margin:0;vertical-align:bottom;text-align:inherit;-webkit-appearance:none;width:100%;height:100%}.mat-date-range-input-inner:-moz-ui-invalid{box-shadow:none}.mat-date-range-input-inner::placeholder{transition:color 400ms 133.3333333333ms cubic-bezier(0.25, 0.8, 0.25, 1)}.mat-date-range-input-inner::-moz-placeholder{transition:color 400ms 133.3333333333ms cubic-bezier(0.25, 0.8, 0.25, 1)}.mat-date-range-input-inner::-webkit-input-placeholder{transition:color 400ms 133.3333333333ms cubic-bezier(0.25, 0.8, 0.25, 1)}.mat-date-range-input-inner:-ms-input-placeholder{transition:color 400ms 133.3333333333ms cubic-bezier(0.25, 0.8, 0.25, 1)}.mat-date-range-input-inner[disabled]{color:var(--mat-datepicker-range-input-disabled-state-text-color, color-mix(in srgb, var(--mat-sys-on-surface) 38%, transparent))}.mat-form-field-hide-placeholder .mat-date-range-input-inner::placeholder,.mat-date-range-input-hide-placeholders .mat-date-range-input-inner::placeholder{-webkit-user-select:none;user-select:none;color:rgba(0,0,0,0) !important;-webkit-text-fill-color:rgba(0,0,0,0);transition:none}@media(forced-colors: active){.mat-form-field-hide-placeholder .mat-date-range-input-inner::placeholder,.mat-date-range-input-hide-placeholders .mat-date-range-input-inner::placeholder{opacity:0}}.mat-form-field-hide-placeholder .mat-date-range-input-inner::-moz-placeholder,.mat-date-range-input-hide-placeholders .mat-date-range-input-inner::-moz-placeholder{-webkit-user-select:none;user-select:none;color:rgba(0,0,0,0) !important;-webkit-text-fill-color:rgba(0,0,0,0);transition:none}@media(forced-colors: active){.mat-form-field-hide-placeholder .mat-date-range-input-inner::-moz-placeholder,.mat-date-range-input-hide-placeholders .mat-date-range-input-inner::-moz-placeholder{opacity:0}}.mat-form-field-hide-placeholder .mat-date-range-input-inner::-webkit-input-placeholder,.mat-date-range-input-hide-placeholders .mat-date-range-input-inner::-webkit-input-placeholder{-webkit-user-select:none;user-select:none;color:rgba(0,0,0,0) !important;-webkit-text-fill-color:rgba(0,0,0,0);transition:none}@media(forced-colors: active){.mat-form-field-hide-placeholder .mat-date-range-input-inner::-webkit-input-placeholder,.mat-date-range-input-hide-placeholders .mat-date-range-input-inner::-webkit-input-placeholder{opacity:0}}.mat-form-field-hide-placeholder .mat-date-range-input-inner:-ms-input-placeholder,.mat-date-range-input-hide-placeholders .mat-date-range-input-inner:-ms-input-placeholder{-webkit-user-select:none;user-select:none;color:rgba(0,0,0,0) !important;-webkit-text-fill-color:rgba(0,0,0,0);transition:none}@media(forced-colors: active){.mat-form-field-hide-placeholder .mat-date-range-input-inner:-ms-input-placeholder,.mat-date-range-input-hide-placeholders .mat-date-range-input-inner:-ms-input-placeholder{opacity:0}}._mat-animation-noopable .mat-date-range-input-inner::placeholder{transition:none}._mat-animation-noopable .mat-date-range-input-inner::-moz-placeholder{transition:none}._mat-animation-noopable .mat-date-range-input-inner::-webkit-input-placeholder{transition:none}._mat-animation-noopable .mat-date-range-input-inner:-ms-input-placeholder{transition:none}.mat-date-range-input-mirror{-webkit-user-select:none;user-select:none;visibility:hidden;white-space:nowrap;display:inline-block;min-width:2px}.mat-mdc-form-field-type-mat-date-range-input .mat-mdc-form-field-infix{width:200px}"], dependencies: [{ kind: "directive", type: CdkMonitorFocus, selector: "[cdkMonitorElementFocus], [cdkMonitorSubtreeFocus]", outputs: ["cdkFocusChange"], exportAs: ["cdkMonitorFocus"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
4014
+ }
4015
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.1.0-next.3", ngImport: i0, type: MatDateRangeInput, decorators: [{
4016
+ type: Component,
4017
+ args: [{ selector: 'mat-date-range-input', exportAs: 'matDateRangeInput', host: {
4018
+ 'class': 'mat-date-range-input',
4019
+ '[class.mat-date-range-input-hide-placeholders]': '_shouldHidePlaceholders()',
4020
+ '[class.mat-date-range-input-required]': 'required',
4021
+ '[attr.id]': 'id',
4022
+ 'role': 'group',
4023
+ '[attr.aria-labelledby]': '_getAriaLabelledby()',
4024
+ '[attr.aria-describedby]': '_ariaDescribedBy',
4025
+ // Used by the test harness to tie this input to its calendar. We can't depend on
4026
+ // `aria-owns` for this, because it's only defined while the calendar is open.
4027
+ '[attr.data-mat-calendar]': 'rangePicker ? rangePicker.id : null',
4028
+ }, changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, providers: [{ provide: MatFormFieldControl, useExisting: MatDateRangeInput }], imports: [CdkMonitorFocus], template: "<div\n class=\"mat-date-range-input-container\"\n cdkMonitorSubtreeFocus\n (cdkFocusChange)=\"_updateFocus($event)\">\n <div class=\"mat-date-range-input-wrapper\">\n <ng-content select=\"input[matStartDate]\"></ng-content>\n <span\n class=\"mat-date-range-input-mirror\"\n aria-hidden=\"true\">{{_getInputMirrorValue('start')}}</span>\n </div>\n\n <span\n class=\"mat-date-range-input-separator\"\n [class.mat-date-range-input-separator-hidden]=\"_shouldHideSeparator()\">{{separator}}</span>\n\n <div class=\"mat-date-range-input-wrapper mat-date-range-input-end-wrapper\">\n <ng-content select=\"input[matEndDate]\"></ng-content>\n <span\n class=\"mat-date-range-input-mirror\"\n aria-hidden=\"true\">{{_getInputMirrorValue('end')}}</span>\n </div>\n</div>\n\n", styles: [".mat-date-range-input{display:block;width:100%}.mat-date-range-input-container{display:flex;align-items:center}.mat-date-range-input-separator{transition:opacity 400ms 133.3333333333ms cubic-bezier(0.25, 0.8, 0.25, 1);margin:0 4px;color:var(--mat-datepicker-range-input-separator-color, var(--mat-sys-on-surface))}.mat-form-field-disabled .mat-date-range-input-separator{color:var(--mat-datepicker-range-input-disabled-state-separator-color, color-mix(in srgb, var(--mat-sys-on-surface) 38%, transparent))}._mat-animation-noopable .mat-date-range-input-separator{transition:none}.mat-date-range-input-separator-hidden{-webkit-user-select:none;user-select:none;opacity:0;transition:none}.mat-date-range-input-wrapper{position:relative;overflow:hidden;max-width:calc(50% - 4px)}.mat-date-range-input-end-wrapper{flex-grow:1}.mat-date-range-input-inner{position:absolute;top:0;left:0;font:inherit;background:rgba(0,0,0,0);color:currentColor;border:none;outline:none;padding:0;margin:0;vertical-align:bottom;text-align:inherit;-webkit-appearance:none;width:100%;height:100%}.mat-date-range-input-inner:-moz-ui-invalid{box-shadow:none}.mat-date-range-input-inner::placeholder{transition:color 400ms 133.3333333333ms cubic-bezier(0.25, 0.8, 0.25, 1)}.mat-date-range-input-inner::-moz-placeholder{transition:color 400ms 133.3333333333ms cubic-bezier(0.25, 0.8, 0.25, 1)}.mat-date-range-input-inner::-webkit-input-placeholder{transition:color 400ms 133.3333333333ms cubic-bezier(0.25, 0.8, 0.25, 1)}.mat-date-range-input-inner:-ms-input-placeholder{transition:color 400ms 133.3333333333ms cubic-bezier(0.25, 0.8, 0.25, 1)}.mat-date-range-input-inner[disabled]{color:var(--mat-datepicker-range-input-disabled-state-text-color, color-mix(in srgb, var(--mat-sys-on-surface) 38%, transparent))}.mat-form-field-hide-placeholder .mat-date-range-input-inner::placeholder,.mat-date-range-input-hide-placeholders .mat-date-range-input-inner::placeholder{-webkit-user-select:none;user-select:none;color:rgba(0,0,0,0) !important;-webkit-text-fill-color:rgba(0,0,0,0);transition:none}@media(forced-colors: active){.mat-form-field-hide-placeholder .mat-date-range-input-inner::placeholder,.mat-date-range-input-hide-placeholders .mat-date-range-input-inner::placeholder{opacity:0}}.mat-form-field-hide-placeholder .mat-date-range-input-inner::-moz-placeholder,.mat-date-range-input-hide-placeholders .mat-date-range-input-inner::-moz-placeholder{-webkit-user-select:none;user-select:none;color:rgba(0,0,0,0) !important;-webkit-text-fill-color:rgba(0,0,0,0);transition:none}@media(forced-colors: active){.mat-form-field-hide-placeholder .mat-date-range-input-inner::-moz-placeholder,.mat-date-range-input-hide-placeholders .mat-date-range-input-inner::-moz-placeholder{opacity:0}}.mat-form-field-hide-placeholder .mat-date-range-input-inner::-webkit-input-placeholder,.mat-date-range-input-hide-placeholders .mat-date-range-input-inner::-webkit-input-placeholder{-webkit-user-select:none;user-select:none;color:rgba(0,0,0,0) !important;-webkit-text-fill-color:rgba(0,0,0,0);transition:none}@media(forced-colors: active){.mat-form-field-hide-placeholder .mat-date-range-input-inner::-webkit-input-placeholder,.mat-date-range-input-hide-placeholders .mat-date-range-input-inner::-webkit-input-placeholder{opacity:0}}.mat-form-field-hide-placeholder .mat-date-range-input-inner:-ms-input-placeholder,.mat-date-range-input-hide-placeholders .mat-date-range-input-inner:-ms-input-placeholder{-webkit-user-select:none;user-select:none;color:rgba(0,0,0,0) !important;-webkit-text-fill-color:rgba(0,0,0,0);transition:none}@media(forced-colors: active){.mat-form-field-hide-placeholder .mat-date-range-input-inner:-ms-input-placeholder,.mat-date-range-input-hide-placeholders .mat-date-range-input-inner:-ms-input-placeholder{opacity:0}}._mat-animation-noopable .mat-date-range-input-inner::placeholder{transition:none}._mat-animation-noopable .mat-date-range-input-inner::-moz-placeholder{transition:none}._mat-animation-noopable .mat-date-range-input-inner::-webkit-input-placeholder{transition:none}._mat-animation-noopable .mat-date-range-input-inner:-ms-input-placeholder{transition:none}.mat-date-range-input-mirror{-webkit-user-select:none;user-select:none;visibility:hidden;white-space:nowrap;display:inline-block;min-width:2px}.mat-mdc-form-field-type-mat-date-range-input .mat-mdc-form-field-infix{width:200px}"] }]
4029
+ }], ctorParameters: () => [], propDecorators: { rangePicker: [{
4030
+ type: Input
4031
+ }], required: [{
4032
+ type: Input,
4033
+ args: [{ transform: booleanAttribute }]
4034
+ }], dateFilter: [{
4035
+ type: Input
4036
+ }], min: [{
4037
+ type: Input
4038
+ }], max: [{
4039
+ type: Input
4040
+ }], disabled: [{
4041
+ type: Input,
4042
+ args: [{ transform: booleanAttribute }]
4043
+ }], separator: [{
4044
+ type: Input
4045
+ }], comparisonStart: [{
4046
+ type: Input
4047
+ }], comparisonEnd: [{
4048
+ type: Input
4049
+ }] } });
4050
+
4051
+ // This file contains the `_computeAriaAccessibleName` function, which computes what the *expected*
4052
+ // ARIA accessible name would be for a given element. Implements a subset of ARIA specification
4053
+ // [Accessible Name and Description Computation 1.2](https://www.w3.org/TR/accname-1.2/).
4054
+ //
4055
+ // Specification accname-1.2 can be summarized by returning the result of the first method
4056
+ // available.
4057
+ //
4058
+ // 1. `aria-labelledby` attribute
4059
+ // ```
4060
+ // <!-- example using aria-labelledby-->
4061
+ // <label id='label-id'>Start Date</label>
4062
+ // <input aria-labelledby='label-id'/>
4063
+ // ```
4064
+ // 2. `aria-label` attribute (e.g. `<input aria-label="Departure"/>`)
4065
+ // 3. Label with `for`/`id`
4066
+ // ```
4067
+ // <!-- example using for/id -->
4068
+ // <label for="current-node">Label</label>
4069
+ // <input id="current-node"/>
4070
+ // ```
4071
+ // 4. `placeholder` attribute (e.g. `<input placeholder="06/03/1990"/>`)
4072
+ // 5. `title` attribute (e.g. `<input title="Check-In"/>`)
4073
+ // 6. text content
4074
+ // ```
4075
+ // <!-- example using text content -->
4076
+ // <label for="current-node"><span>Departure</span> Date</label>
4077
+ // <input id="current-node"/>
4078
+ // ```
4079
+ /**
4080
+ * Computes the *expected* ARIA accessible name for argument element based on [accname-1.2
4081
+ * specification](https://www.w3.org/TR/accname-1.2/). Implements a subset of accname-1.2,
4082
+ * and should only be used for the Datepicker's specific use case.
4083
+ *
4084
+ * Intended use:
4085
+ * This is not a general use implementation. Only implements the parts of accname-1.2 that are
4086
+ * required for the Datepicker's specific use case. This function is not intended for any other
4087
+ * use.
4088
+ *
4089
+ * Limitations:
4090
+ * - Only covers the needs of `matStartDate` and `matEndDate`. Does not support other use cases.
4091
+ * - See NOTES's in implementation for specific details on what parts of the accname-1.2
4092
+ * specification are not implemented.
4093
+ *
4094
+ * @param element {HTMLInputElement} native &lt;input/&gt; element of `matStartDate` or
4095
+ * `matEndDate` component. Corresponds to the 'Root Element' from accname-1.2
4096
+ *
4097
+ * @return expected ARIA accessible name of argument &lt;input/&gt;
4098
+ */
4099
+ function _computeAriaAccessibleName(element) {
4100
+ return _computeAriaAccessibleNameInternal(element, true);
4101
+ }
4102
+ /**
4103
+ * Determine if argument node is an Element based on `nodeType` property. This function is safe to
4104
+ * use with server-side rendering.
4105
+ */
4106
+ function ssrSafeIsElement(node) {
4107
+ return node.nodeType === Node.ELEMENT_NODE;
4108
+ }
4109
+ /**
4110
+ * Determine if argument node is an HTMLInputElement based on `nodeName` property. This funciton is
4111
+ * safe to use with server-side rendering.
4112
+ */
4113
+ function ssrSafeIsHTMLInputElement(node) {
4114
+ return node.nodeName === 'INPUT';
4115
+ }
4116
+ /**
4117
+ * Determine if argument node is an HTMLTextAreaElement based on `nodeName` property. This
4118
+ * funciton is safe to use with server-side rendering.
4119
+ */
4120
+ function ssrSafeIsHTMLTextAreaElement(node) {
4121
+ return node.nodeName === 'TEXTAREA';
4122
+ }
4123
+ /**
4124
+ * Calculate the expected ARIA accessible name for given DOM Node. Given DOM Node may be either the
4125
+ * "Root node" passed to `_computeAriaAccessibleName` or "Current node" as result of recursion.
4126
+ *
4127
+ * @return the accessible name of argument DOM Node
4128
+ *
4129
+ * @param currentNode node to determine accessible name of
4130
+ * @param isDirectlyReferenced true if `currentNode` is the root node to calculate ARIA accessible
4131
+ * name of. False if it is a result of recursion.
4132
+ */
4133
+ function _computeAriaAccessibleNameInternal(currentNode, isDirectlyReferenced) {
4134
+ // NOTE: this differs from accname-1.2 specification.
4135
+ // - Does not implement Step 1. of accname-1.2: '''If `currentNode`'s role prohibits naming,
4136
+ // return the empty string ("")'''.
4137
+ // - Does not implement Step 2.A. of accname-1.2: '''if current node is hidden and not directly
4138
+ // referenced by aria-labelledby... return the empty string.'''
4139
+ // acc-name-1.2 Step 2.B.: aria-labelledby
4140
+ if (ssrSafeIsElement(currentNode) && isDirectlyReferenced) {
4141
+ const labelledbyIds = currentNode.getAttribute?.('aria-labelledby')?.split(/\s+/g) || [];
4142
+ const validIdRefs = labelledbyIds.reduce((validIds, id) => {
4143
+ const elem = document.getElementById(id);
4144
+ if (elem) {
4145
+ validIds.push(elem);
4146
+ }
4147
+ return validIds;
4148
+ }, []);
4149
+ if (validIdRefs.length) {
4150
+ return validIdRefs
4151
+ .map(idRef => {
4152
+ return _computeAriaAccessibleNameInternal(idRef, false);
4153
+ })
4154
+ .join(' ');
4271
4155
  }
4272
4156
  }
4273
- _max;
4274
- /** Whether the input is disabled. */
4275
- get disabled() {
4276
- return this._startInput && this._endInput
4277
- ? this._startInput.disabled && this._endInput.disabled
4278
- : this._groupDisabled;
4279
- }
4280
- set disabled(value) {
4281
- if (value !== this._groupDisabled) {
4282
- this._groupDisabled = value;
4283
- this.stateChanges.next(undefined);
4157
+ // acc-name-1.2 Step 2.C.: aria-label
4158
+ if (ssrSafeIsElement(currentNode)) {
4159
+ const ariaLabel = currentNode.getAttribute('aria-label')?.trim();
4160
+ if (ariaLabel) {
4161
+ return ariaLabel;
4284
4162
  }
4285
4163
  }
4286
- _groupDisabled = false;
4287
- /** Whether the input is in an error state. */
4288
- get errorState() {
4289
- if (this._startInput && this._endInput) {
4290
- return this._startInput.errorState || this._endInput.errorState;
4164
+ // acc-name-1.2 Step 2.D. attribute or element that defines a text alternative
4165
+ //
4166
+ // NOTE: this differs from accname-1.2 specification.
4167
+ // Only implements Step 2.D. for `<label>`,`<input/>`, and `<textarea/>` element. Does not
4168
+ // implement other elements that have an attribute or element that defines a text alternative.
4169
+ if (ssrSafeIsHTMLInputElement(currentNode) || ssrSafeIsHTMLTextAreaElement(currentNode)) {
4170
+ // use label with a `for` attribute referencing the current node
4171
+ if (currentNode.labels?.length) {
4172
+ return Array.from(currentNode.labels)
4173
+ .map(x => _computeAriaAccessibleNameInternal(x, false))
4174
+ .join(' ');
4291
4175
  }
4292
- return false;
4293
- }
4294
- /** Whether the datepicker input is empty. */
4295
- get empty() {
4296
- const startEmpty = this._startInput ? this._startInput.isEmpty() : false;
4297
- const endEmpty = this._endInput ? this._endInput.isEmpty() : false;
4298
- return startEmpty && endEmpty;
4299
- }
4300
- /** Value for the `aria-describedby` attribute of the inputs. */
4301
- _ariaDescribedBy = null;
4302
- /** Date selection model currently registered with the input. */
4303
- _model;
4304
- /** Separator text to be shown between the inputs. */
4305
- separator = '–';
4306
- /** Start of the comparison range that should be shown in the calendar. */
4307
- comparisonStart = null;
4308
- /** End of the comparison range that should be shown in the calendar. */
4309
- comparisonEnd = null;
4310
- _startInput;
4311
- _endInput;
4312
- /**
4313
- * Implemented as a part of `MatFormFieldControl`.
4314
- * TODO(crisbeto): change type to `AbstractControlDirective` after #18206 lands.
4315
- * @docs-private
4316
- */
4317
- ngControl;
4318
- /** Emits when the input's state has changed. */
4319
- stateChanges = new Subject();
4320
- /**
4321
- * Disable the automatic labeling to avoid issues like #27241.
4322
- * @docs-private
4323
- */
4324
- disableAutomaticLabeling = true;
4325
- constructor() {
4326
- if (!this._dateAdapter && (typeof ngDevMode === 'undefined' || ngDevMode)) {
4327
- throw createMissingDateImplError('DateAdapter');
4176
+ // use placeholder if available
4177
+ const placeholder = currentNode.getAttribute('placeholder')?.trim();
4178
+ if (placeholder) {
4179
+ return placeholder;
4328
4180
  }
4329
- // The datepicker module can be used both with MDC and non-MDC form fields. We have
4330
- // to conditionally add the MDC input class so that the range picker looks correctly.
4331
- if (this._formField?._elementRef.nativeElement.classList.contains('mat-mdc-form-field')) {
4332
- this._elementRef.nativeElement.classList.add('mat-mdc-input-element', 'mat-mdc-form-field-input-control', 'mdc-text-field__input');
4181
+ // use title if available
4182
+ const title = currentNode.getAttribute('title')?.trim();
4183
+ if (title) {
4184
+ return title;
4333
4185
  }
4334
- // TODO(crisbeto): remove `as any` after #18206 lands.
4335
- this.ngControl = inject(ControlContainer, { optional: true, self: true });
4336
4186
  }
4187
+ // NOTE: this differs from accname-1.2 specification.
4188
+ // - does not implement acc-name-1.2 Step 2.E.: '''if the current node is a control embedded
4189
+ // within the label... then include the embedded control as part of the text alternative in
4190
+ // the following manner...'''. Step 2E applies to embedded controls such as textbox, listbox,
4191
+ // range, etc.
4192
+ // - does not implement acc-name-1.2 step 2.F.: check that '''role allows name from content''',
4193
+ // which applies to `currentNode` and its children.
4194
+ // - does not implement acc-name-1.2 Step 2.F.ii.: '''Check for CSS generated textual content'''
4195
+ // (e.g. :before and :after).
4196
+ // - does not implement acc-name-1.2 Step 2.I.: '''if the current node has a Tooltip attribute,
4197
+ // return its value'''
4198
+ // Return text content with whitespace collapsed into a single space character. Accomplish
4199
+ // acc-name-1.2 steps 2F, 2G, and 2H.
4200
+ return (currentNode.textContent || '').replace(/\s+/g, ' ').trim();
4201
+ }
4202
+
4203
+ /**
4204
+ * Base class for the individual inputs that can be projected inside a `mat-date-range-input`.
4205
+ */
4206
+ class MatDateRangeInputPartBase extends MatDatepickerInputBase {
4207
+ _rangeInput = inject(MatDateRangeInput);
4208
+ _elementRef = inject(ElementRef);
4209
+ _defaultErrorStateMatcher = inject(ErrorStateMatcher);
4210
+ _injector = inject(Injector);
4211
+ _parentForm = inject(NgForm, { optional: true });
4212
+ _parentFormGroup = inject(FormGroupDirective, { optional: true });
4337
4213
  /**
4338
- * Implemented as a part of `MatFormFieldControl`.
4214
+ * Form control bound to this input part.
4339
4215
  * @docs-private
4340
4216
  */
4341
- setDescribedByIds(ids) {
4342
- this._ariaDescribedBy = ids.length ? ids.join(' ') : null;
4217
+ ngControl;
4218
+ _dir = inject(Directionality, { optional: true });
4219
+ _errorStateTracker;
4220
+ /** Object used to control when error messages are shown. */
4221
+ get errorStateMatcher() {
4222
+ return this._errorStateTracker.matcher;
4343
4223
  }
4344
- /**
4345
- * Implemented as a part of `MatFormFieldControl`.
4346
- * @docs-private
4347
- */
4348
- onContainerClick() {
4349
- if (!this.focused && !this.disabled) {
4350
- if (!this._model || !this._model.selection.start) {
4351
- this._startInput.focus();
4352
- }
4353
- else {
4354
- this._endInput.focus();
4355
- }
4224
+ set errorStateMatcher(value) {
4225
+ this._errorStateTracker.matcher = value;
4226
+ }
4227
+ /** Whether the input is in an error state. */
4228
+ get errorState() {
4229
+ return this._errorStateTracker.errorState;
4230
+ }
4231
+ set errorState(value) {
4232
+ this._errorStateTracker.errorState = value;
4233
+ }
4234
+ constructor() {
4235
+ super();
4236
+ this._errorStateTracker = new _ErrorStateTracker(this._defaultErrorStateMatcher, null, this._parentFormGroup, this._parentForm, this.stateChanges);
4237
+ }
4238
+ ngOnInit() {
4239
+ // We need the date input to provide itself as a `ControlValueAccessor` and a `Validator`, while
4240
+ // injecting its `NgControl` so that the error state is handled correctly. This introduces a
4241
+ // circular dependency, because both `ControlValueAccessor` and `Validator` depend on the input
4242
+ // itself. Usually we can work around it for the CVA, but there's no API to do it for the
4243
+ // validator. We work around it here by injecting the `NgControl` in `ngOnInit`, after
4244
+ // everything has been resolved.
4245
+ const ngControl = this._injector.get(NgControl, null, { optional: true, self: true });
4246
+ if (ngControl) {
4247
+ this.ngControl = ngControl;
4248
+ this._errorStateTracker.ngControl = ngControl;
4356
4249
  }
4357
4250
  }
4358
4251
  ngAfterContentInit() {
4359
- if (typeof ngDevMode === 'undefined' || ngDevMode) {
4360
- if (!this._startInput) {
4361
- throw Error('mat-date-range-input must contain a matStartDate input');
4362
- }
4363
- if (!this._endInput) {
4364
- throw Error('mat-date-range-input must contain a matEndDate input');
4365
- }
4366
- }
4367
- if (this._model) {
4368
- this._registerModel(this._model);
4369
- }
4370
- // We don't need to unsubscribe from this, because we
4371
- // know that the input streams will be completed on destroy.
4372
- merge(this._startInput.stateChanges, this._endInput.stateChanges).subscribe(() => {
4373
- this.stateChanges.next(undefined);
4374
- });
4252
+ this._register();
4375
4253
  }
4376
- ngOnChanges(changes) {
4377
- if (dateInputsHaveChanged(changes, this._dateAdapter)) {
4378
- this.stateChanges.next(undefined);
4254
+ ngDoCheck() {
4255
+ if (this.ngControl) {
4256
+ // We need to re-evaluate this on every change detection cycle, because there are some
4257
+ // error triggers that we can't subscribe to (e.g. parent form submissions). This means
4258
+ // that whatever logic is in here has to be super lean or we risk destroying the performance.
4259
+ this.updateErrorState();
4379
4260
  }
4380
4261
  }
4381
- ngOnDestroy() {
4382
- this._closedSubscription.unsubscribe();
4383
- this._openedSubscription.unsubscribe();
4384
- this.stateChanges.complete();
4262
+ /** Gets whether the input is empty. */
4263
+ isEmpty() {
4264
+ return this._elementRef.nativeElement.value.length === 0;
4385
4265
  }
4386
- /** Gets the date at which the calendar should start. */
4387
- getStartValue() {
4388
- return this.value ? this.value.start : null;
4266
+ /** Gets the placeholder of the input. */
4267
+ _getPlaceholder() {
4268
+ return this._elementRef.nativeElement.placeholder;
4389
4269
  }
4390
- /** Gets the input's theme palette. */
4391
- getThemePalette() {
4392
- return this._formField ? this._formField.color : undefined;
4270
+ /** Focuses the input. */
4271
+ focus() {
4272
+ this._elementRef.nativeElement.focus();
4393
4273
  }
4394
- /** Gets the element to which the calendar overlay should be attached. */
4395
- getConnectedOverlayOrigin() {
4396
- return this._formField ? this._formField.getConnectedOverlayOrigin() : this._elementRef;
4274
+ /** Gets the value that should be used when mirroring the input's size. */
4275
+ getMirrorValue() {
4276
+ const element = this._elementRef.nativeElement;
4277
+ const value = element.value;
4278
+ return value.length > 0 ? value : element.placeholder;
4397
4279
  }
4398
- /** Gets the ID of an element that should be used a description for the calendar overlay. */
4399
- getOverlayLabelId() {
4400
- return this._formField ? this._formField.getLabelId() : null;
4280
+ /** Refreshes the error state of the input. */
4281
+ updateErrorState() {
4282
+ this._errorStateTracker.updateErrorState();
4401
4283
  }
4402
- /** Gets the value that is used to mirror the state input. */
4403
- _getInputMirrorValue(part) {
4404
- const input = part === 'start' ? this._startInput : this._endInput;
4405
- return input ? input.getMirrorValue() : '';
4284
+ /** Handles `input` events on the input element. */
4285
+ _onInput(value) {
4286
+ super._onInput(value);
4287
+ this._rangeInput._handleChildValueChange();
4288
+ }
4289
+ /** Opens the datepicker associated with the input. */
4290
+ _openPopup() {
4291
+ this._rangeInput._openDatepicker();
4292
+ }
4293
+ /** Gets the minimum date from the range input. */
4294
+ _getMinDate() {
4295
+ return this._rangeInput.min;
4296
+ }
4297
+ /** Gets the maximum date from the range input. */
4298
+ _getMaxDate() {
4299
+ return this._rangeInput.max;
4300
+ }
4301
+ /** Gets the date filter function from the range input. */
4302
+ _getDateFilter() {
4303
+ return this._rangeInput.dateFilter;
4304
+ }
4305
+ _parentDisabled() {
4306
+ return this._rangeInput._groupDisabled;
4307
+ }
4308
+ _shouldHandleChangeEvent({ source }) {
4309
+ return source !== this._rangeInput._startInput && source !== this._rangeInput._endInput;
4310
+ }
4311
+ _assignValueProgrammatically(value) {
4312
+ super._assignValueProgrammatically(value);
4313
+ const opposite = (this === this._rangeInput._startInput
4314
+ ? this._rangeInput._endInput
4315
+ : this._rangeInput._startInput);
4316
+ opposite?._validatorOnChange();
4406
4317
  }
4407
- /** Whether the input placeholders should be hidden. */
4408
- _shouldHidePlaceholders() {
4409
- return this._startInput ? !this._startInput.isEmpty() : false;
4318
+ /** return the ARIA accessible name of the input element */
4319
+ _getAccessibleName() {
4320
+ return _computeAriaAccessibleName(this._elementRef.nativeElement);
4410
4321
  }
4411
- /** Handles the value in one of the child inputs changing. */
4412
- _handleChildValueChange() {
4413
- this.stateChanges.next(undefined);
4414
- this._changeDetectorRef.markForCheck();
4322
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.1.0-next.3", ngImport: i0, type: MatDateRangeInputPartBase, deps: [], target: i0.ɵɵFactoryTarget.Directive });
4323
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "19.1.0-next.3", type: MatDateRangeInputPartBase, isStandalone: true, inputs: { errorStateMatcher: "errorStateMatcher" }, usesInheritance: true, ngImport: i0 });
4324
+ }
4325
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.1.0-next.3", ngImport: i0, type: MatDateRangeInputPartBase, decorators: [{
4326
+ type: Directive
4327
+ }], ctorParameters: () => [], propDecorators: { errorStateMatcher: [{
4328
+ type: Input
4329
+ }] } });
4330
+ /** Input for entering the start date in a `mat-date-range-input`. */
4331
+ class MatStartDate extends MatDateRangeInputPartBase {
4332
+ /** Validator that checks that the start date isn't after the end date. */
4333
+ _startValidator = (control) => {
4334
+ const start = this._dateAdapter.getValidDateOrNull(this._dateAdapter.deserialize(control.value));
4335
+ const end = this._model ? this._model.selection.end : null;
4336
+ return !start || !end || this._dateAdapter.compareDate(start, end) <= 0
4337
+ ? null
4338
+ : { 'matStartDateInvalid': { 'end': end, 'actual': start } };
4339
+ };
4340
+ _validator = Validators.compose([...super._getValidators(), this._startValidator]);
4341
+ _register() {
4342
+ this._rangeInput._startInput = this;
4415
4343
  }
4416
- /** Opens the date range picker associated with the input. */
4417
- _openDatepicker() {
4418
- if (this._rangePicker) {
4419
- this._rangePicker.open();
4344
+ _getValueFromModel(modelValue) {
4345
+ return modelValue.start;
4346
+ }
4347
+ _shouldHandleChangeEvent(change) {
4348
+ if (!super._shouldHandleChangeEvent(change)) {
4349
+ return false;
4350
+ }
4351
+ else {
4352
+ return !change.oldValue?.start
4353
+ ? !!change.selection.start
4354
+ : !change.selection.start ||
4355
+ !!this._dateAdapter.compareDate(change.oldValue.start, change.selection.start);
4420
4356
  }
4421
4357
  }
4422
- /** Whether the separate text should be hidden. */
4423
- _shouldHideSeparator() {
4424
- return ((!this._formField ||
4425
- (this._formField.getLabelId() && !this._formField._shouldLabelFloat())) &&
4426
- this.empty);
4358
+ _assignValueToModel(value) {
4359
+ if (this._model) {
4360
+ const range = new DateRange(value, this._model.selection.end);
4361
+ this._model.updateSelection(range, this);
4362
+ }
4427
4363
  }
4428
- /** Gets the value for the `aria-labelledby` attribute of the inputs. */
4429
- _getAriaLabelledby() {
4430
- const formField = this._formField;
4431
- return formField && formField._hasFloatingLabel() ? formField._labelId : null;
4364
+ _formatValue(value) {
4365
+ super._formatValue(value);
4366
+ // Any time the input value is reformatted we need to tell the parent.
4367
+ this._rangeInput._handleChildValueChange();
4432
4368
  }
4433
- _getStartDateAccessibleName() {
4434
- return this._startInput._getAccessibleName();
4369
+ _onKeydown(event) {
4370
+ const endInput = this._rangeInput._endInput;
4371
+ const element = this._elementRef.nativeElement;
4372
+ const isLtr = this._dir?.value !== 'rtl';
4373
+ // If the user hits RIGHT (LTR) when at the end of the input (and no
4374
+ // selection), move the cursor to the start of the end input.
4375
+ if (((event.keyCode === RIGHT_ARROW && isLtr) || (event.keyCode === LEFT_ARROW && !isLtr)) &&
4376
+ element.selectionStart === element.value.length &&
4377
+ element.selectionEnd === element.value.length) {
4378
+ event.preventDefault();
4379
+ endInput._elementRef.nativeElement.setSelectionRange(0, 0);
4380
+ endInput.focus();
4381
+ }
4382
+ else {
4383
+ super._onKeydown(event);
4384
+ }
4435
4385
  }
4436
- _getEndDateAccessibleName() {
4437
- return this._endInput._getAccessibleName();
4386
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.1.0-next.3", ngImport: i0, type: MatStartDate, deps: null, target: i0.ɵɵFactoryTarget.Directive });
4387
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "19.1.0-next.3", type: MatStartDate, isStandalone: true, selector: "input[matStartDate]", outputs: { dateChange: "dateChange", dateInput: "dateInput" }, host: { attributes: { "type": "text" }, listeners: { "input": "_onInput($event.target.value)", "change": "_onChange()", "keydown": "_onKeydown($event)", "blur": "_onBlur()" }, properties: { "disabled": "disabled", "attr.aria-haspopup": "_rangeInput.rangePicker ? \"dialog\" : null", "attr.aria-owns": "_rangeInput._ariaOwns\n ? _rangeInput._ariaOwns()\n : (_rangeInput.rangePicker?.opened && _rangeInput.rangePicker.id) || null", "attr.min": "_getMinDate() ? _dateAdapter.toIso8601(_getMinDate()) : null", "attr.max": "_getMaxDate() ? _dateAdapter.toIso8601(_getMaxDate()) : null" }, classAttribute: "mat-start-date mat-date-range-input-inner" }, providers: [
4388
+ { provide: NG_VALUE_ACCESSOR, useExisting: MatStartDate, multi: true },
4389
+ { provide: NG_VALIDATORS, useExisting: MatStartDate, multi: true },
4390
+ ], usesInheritance: true, ngImport: i0 });
4391
+ }
4392
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.1.0-next.3", ngImport: i0, type: MatStartDate, decorators: [{
4393
+ type: Directive,
4394
+ args: [{
4395
+ selector: 'input[matStartDate]',
4396
+ host: {
4397
+ 'class': 'mat-start-date mat-date-range-input-inner',
4398
+ '[disabled]': 'disabled',
4399
+ '(input)': '_onInput($event.target.value)',
4400
+ '(change)': '_onChange()',
4401
+ '(keydown)': '_onKeydown($event)',
4402
+ '[attr.aria-haspopup]': '_rangeInput.rangePicker ? "dialog" : null',
4403
+ '[attr.aria-owns]': `_rangeInput._ariaOwns
4404
+ ? _rangeInput._ariaOwns()
4405
+ : (_rangeInput.rangePicker?.opened && _rangeInput.rangePicker.id) || null`,
4406
+ '[attr.min]': '_getMinDate() ? _dateAdapter.toIso8601(_getMinDate()) : null',
4407
+ '[attr.max]': '_getMaxDate() ? _dateAdapter.toIso8601(_getMaxDate()) : null',
4408
+ '(blur)': '_onBlur()',
4409
+ 'type': 'text',
4410
+ },
4411
+ providers: [
4412
+ { provide: NG_VALUE_ACCESSOR, useExisting: MatStartDate, multi: true },
4413
+ { provide: NG_VALIDATORS, useExisting: MatStartDate, multi: true },
4414
+ ],
4415
+ // These need to be specified explicitly, because some tooling doesn't
4416
+ // seem to pick them up from the base class. See #20932.
4417
+ outputs: ['dateChange', 'dateInput'],
4418
+ }]
4419
+ }] });
4420
+ /** Input for entering the end date in a `mat-date-range-input`. */
4421
+ class MatEndDate extends MatDateRangeInputPartBase {
4422
+ /** Validator that checks that the end date isn't before the start date. */
4423
+ _endValidator = (control) => {
4424
+ const end = this._dateAdapter.getValidDateOrNull(this._dateAdapter.deserialize(control.value));
4425
+ const start = this._model ? this._model.selection.start : null;
4426
+ return !end || !start || this._dateAdapter.compareDate(end, start) >= 0
4427
+ ? null
4428
+ : { 'matEndDateInvalid': { 'start': start, 'actual': end } };
4429
+ };
4430
+ _register() {
4431
+ this._rangeInput._endInput = this;
4438
4432
  }
4439
- /** Updates the focused state of the range input. */
4440
- _updateFocus(origin) {
4441
- this.focused = origin !== null;
4442
- this.stateChanges.next();
4433
+ _validator = Validators.compose([...super._getValidators(), this._endValidator]);
4434
+ _getValueFromModel(modelValue) {
4435
+ return modelValue.end;
4443
4436
  }
4444
- /** Re-runs the validators on the start/end inputs. */
4445
- _revalidate() {
4446
- if (this._startInput) {
4447
- this._startInput._validatorOnChange();
4437
+ _shouldHandleChangeEvent(change) {
4438
+ if (!super._shouldHandleChangeEvent(change)) {
4439
+ return false;
4448
4440
  }
4449
- if (this._endInput) {
4450
- this._endInput._validatorOnChange();
4441
+ else {
4442
+ return !change.oldValue?.end
4443
+ ? !!change.selection.end
4444
+ : !change.selection.end ||
4445
+ !!this._dateAdapter.compareDate(change.oldValue.end, change.selection.end);
4451
4446
  }
4452
4447
  }
4453
- /** Registers the current date selection model with the start/end inputs. */
4454
- _registerModel(model) {
4455
- if (this._startInput) {
4456
- this._startInput._registerModel(model);
4448
+ _assignValueToModel(value) {
4449
+ if (this._model) {
4450
+ const range = new DateRange(this._model.selection.start, value);
4451
+ this._model.updateSelection(range, this);
4457
4452
  }
4458
- if (this._endInput) {
4459
- this._endInput._registerModel(model);
4453
+ }
4454
+ _moveCaretToEndOfStartInput() {
4455
+ const startInput = this._rangeInput._startInput._elementRef.nativeElement;
4456
+ const value = startInput.value;
4457
+ if (value.length > 0) {
4458
+ startInput.setSelectionRange(value.length, value.length);
4460
4459
  }
4460
+ startInput.focus();
4461
4461
  }
4462
- /** Checks whether a specific range input directive is required. */
4463
- _isTargetRequired(target) {
4464
- return target?.ngControl?.control?.hasValidator(Validators.required);
4462
+ _onKeydown(event) {
4463
+ const element = this._elementRef.nativeElement;
4464
+ const isLtr = this._dir?.value !== 'rtl';
4465
+ // If the user is pressing backspace on an empty end input, move focus back to the start.
4466
+ if (event.keyCode === BACKSPACE && !element.value) {
4467
+ this._moveCaretToEndOfStartInput();
4468
+ }
4469
+ // If the user hits LEFT (LTR) when at the start of the input (and no
4470
+ // selection), move the cursor to the end of the start input.
4471
+ else if (((event.keyCode === LEFT_ARROW && isLtr) || (event.keyCode === RIGHT_ARROW && !isLtr)) &&
4472
+ element.selectionStart === 0 &&
4473
+ element.selectionEnd === 0) {
4474
+ event.preventDefault();
4475
+ this._moveCaretToEndOfStartInput();
4476
+ }
4477
+ else {
4478
+ super._onKeydown(event);
4479
+ }
4465
4480
  }
4466
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.1.0-next.3", ngImport: i0, type: MatDateRangeInput, deps: [], target: i0.ɵɵFactoryTarget.Component });
4467
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "16.1.0", version: "19.1.0-next.3", type: MatDateRangeInput, isStandalone: true, selector: "mat-date-range-input", inputs: { rangePicker: "rangePicker", required: ["required", "required", booleanAttribute], dateFilter: "dateFilter", min: "min", max: "max", disabled: ["disabled", "disabled", booleanAttribute], separator: "separator", comparisonStart: "comparisonStart", comparisonEnd: "comparisonEnd" }, host: { attributes: { "role": "group" }, properties: { "class.mat-date-range-input-hide-placeholders": "_shouldHidePlaceholders()", "class.mat-date-range-input-required": "required", "attr.id": "id", "attr.aria-labelledby": "_getAriaLabelledby()", "attr.aria-describedby": "_ariaDescribedBy", "attr.data-mat-calendar": "rangePicker ? rangePicker.id : null" }, classAttribute: "mat-date-range-input" }, providers: [
4468
- { provide: MatFormFieldControl, useExisting: MatDateRangeInput },
4469
- { provide: MAT_DATE_RANGE_INPUT_PARENT, useExisting: MatDateRangeInput },
4470
- ], queries: [{ propertyName: "_startInput", first: true, predicate: MatStartDate, descendants: true }, { propertyName: "_endInput", first: true, predicate: MatEndDate, descendants: true }], exportAs: ["matDateRangeInput"], usesOnChanges: true, ngImport: i0, template: "<div\n class=\"mat-date-range-input-container\"\n cdkMonitorSubtreeFocus\n (cdkFocusChange)=\"_updateFocus($event)\">\n <div class=\"mat-date-range-input-wrapper\">\n <ng-content select=\"input[matStartDate]\"></ng-content>\n <span\n class=\"mat-date-range-input-mirror\"\n aria-hidden=\"true\">{{_getInputMirrorValue('start')}}</span>\n </div>\n\n <span\n class=\"mat-date-range-input-separator\"\n [class.mat-date-range-input-separator-hidden]=\"_shouldHideSeparator()\">{{separator}}</span>\n\n <div class=\"mat-date-range-input-wrapper mat-date-range-input-end-wrapper\">\n <ng-content select=\"input[matEndDate]\"></ng-content>\n <span\n class=\"mat-date-range-input-mirror\"\n aria-hidden=\"true\">{{_getInputMirrorValue('end')}}</span>\n </div>\n</div>\n\n", styles: [".mat-date-range-input{display:block;width:100%}.mat-date-range-input-container{display:flex;align-items:center}.mat-date-range-input-separator{transition:opacity 400ms 133.3333333333ms cubic-bezier(0.25, 0.8, 0.25, 1);margin:0 4px;color:var(--mat-datepicker-range-input-separator-color, var(--mat-sys-on-surface))}.mat-form-field-disabled .mat-date-range-input-separator{color:var(--mat-datepicker-range-input-disabled-state-separator-color, color-mix(in srgb, var(--mat-sys-on-surface) 38%, transparent))}._mat-animation-noopable .mat-date-range-input-separator{transition:none}.mat-date-range-input-separator-hidden{-webkit-user-select:none;user-select:none;opacity:0;transition:none}.mat-date-range-input-wrapper{position:relative;overflow:hidden;max-width:calc(50% - 4px)}.mat-date-range-input-end-wrapper{flex-grow:1}.mat-date-range-input-inner{position:absolute;top:0;left:0;font:inherit;background:rgba(0,0,0,0);color:currentColor;border:none;outline:none;padding:0;margin:0;vertical-align:bottom;text-align:inherit;-webkit-appearance:none;width:100%;height:100%}.mat-date-range-input-inner:-moz-ui-invalid{box-shadow:none}.mat-date-range-input-inner::placeholder{transition:color 400ms 133.3333333333ms cubic-bezier(0.25, 0.8, 0.25, 1)}.mat-date-range-input-inner::-moz-placeholder{transition:color 400ms 133.3333333333ms cubic-bezier(0.25, 0.8, 0.25, 1)}.mat-date-range-input-inner::-webkit-input-placeholder{transition:color 400ms 133.3333333333ms cubic-bezier(0.25, 0.8, 0.25, 1)}.mat-date-range-input-inner:-ms-input-placeholder{transition:color 400ms 133.3333333333ms cubic-bezier(0.25, 0.8, 0.25, 1)}.mat-date-range-input-inner[disabled]{color:var(--mat-datepicker-range-input-disabled-state-text-color, color-mix(in srgb, var(--mat-sys-on-surface) 38%, transparent))}.mat-form-field-hide-placeholder .mat-date-range-input-inner::placeholder,.mat-date-range-input-hide-placeholders .mat-date-range-input-inner::placeholder{-webkit-user-select:none;user-select:none;color:rgba(0,0,0,0) !important;-webkit-text-fill-color:rgba(0,0,0,0);transition:none}@media(forced-colors: active){.mat-form-field-hide-placeholder .mat-date-range-input-inner::placeholder,.mat-date-range-input-hide-placeholders .mat-date-range-input-inner::placeholder{opacity:0}}.mat-form-field-hide-placeholder .mat-date-range-input-inner::-moz-placeholder,.mat-date-range-input-hide-placeholders .mat-date-range-input-inner::-moz-placeholder{-webkit-user-select:none;user-select:none;color:rgba(0,0,0,0) !important;-webkit-text-fill-color:rgba(0,0,0,0);transition:none}@media(forced-colors: active){.mat-form-field-hide-placeholder .mat-date-range-input-inner::-moz-placeholder,.mat-date-range-input-hide-placeholders .mat-date-range-input-inner::-moz-placeholder{opacity:0}}.mat-form-field-hide-placeholder .mat-date-range-input-inner::-webkit-input-placeholder,.mat-date-range-input-hide-placeholders .mat-date-range-input-inner::-webkit-input-placeholder{-webkit-user-select:none;user-select:none;color:rgba(0,0,0,0) !important;-webkit-text-fill-color:rgba(0,0,0,0);transition:none}@media(forced-colors: active){.mat-form-field-hide-placeholder .mat-date-range-input-inner::-webkit-input-placeholder,.mat-date-range-input-hide-placeholders .mat-date-range-input-inner::-webkit-input-placeholder{opacity:0}}.mat-form-field-hide-placeholder .mat-date-range-input-inner:-ms-input-placeholder,.mat-date-range-input-hide-placeholders .mat-date-range-input-inner:-ms-input-placeholder{-webkit-user-select:none;user-select:none;color:rgba(0,0,0,0) !important;-webkit-text-fill-color:rgba(0,0,0,0);transition:none}@media(forced-colors: active){.mat-form-field-hide-placeholder .mat-date-range-input-inner:-ms-input-placeholder,.mat-date-range-input-hide-placeholders .mat-date-range-input-inner:-ms-input-placeholder{opacity:0}}._mat-animation-noopable .mat-date-range-input-inner::placeholder{transition:none}._mat-animation-noopable .mat-date-range-input-inner::-moz-placeholder{transition:none}._mat-animation-noopable .mat-date-range-input-inner::-webkit-input-placeholder{transition:none}._mat-animation-noopable .mat-date-range-input-inner:-ms-input-placeholder{transition:none}.mat-date-range-input-mirror{-webkit-user-select:none;user-select:none;visibility:hidden;white-space:nowrap;display:inline-block;min-width:2px}.mat-mdc-form-field-type-mat-date-range-input .mat-mdc-form-field-infix{width:200px}"], dependencies: [{ kind: "directive", type: CdkMonitorFocus, selector: "[cdkMonitorElementFocus], [cdkMonitorSubtreeFocus]", outputs: ["cdkFocusChange"], exportAs: ["cdkMonitorFocus"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
4481
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.1.0-next.3", ngImport: i0, type: MatEndDate, deps: null, target: i0.ɵɵFactoryTarget.Directive });
4482
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "19.1.0-next.3", type: MatEndDate, isStandalone: true, selector: "input[matEndDate]", outputs: { dateChange: "dateChange", dateInput: "dateInput" }, host: { attributes: { "type": "text" }, listeners: { "input": "_onInput($event.target.value)", "change": "_onChange()", "keydown": "_onKeydown($event)", "blur": "_onBlur()" }, properties: { "disabled": "disabled", "attr.aria-haspopup": "_rangeInput.rangePicker ? \"dialog\" : null", "attr.aria-owns": "_rangeInput._ariaOwns\n ? _rangeInput._ariaOwns()\n : (_rangeInput.rangePicker?.opened && _rangeInput.rangePicker.id) || null", "attr.min": "_getMinDate() ? _dateAdapter.toIso8601(_getMinDate()) : null", "attr.max": "_getMaxDate() ? _dateAdapter.toIso8601(_getMaxDate()) : null" }, classAttribute: "mat-end-date mat-date-range-input-inner" }, providers: [
4483
+ { provide: NG_VALUE_ACCESSOR, useExisting: MatEndDate, multi: true },
4484
+ { provide: NG_VALIDATORS, useExisting: MatEndDate, multi: true },
4485
+ ], usesInheritance: true, ngImport: i0 });
4471
4486
  }
4472
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.1.0-next.3", ngImport: i0, type: MatDateRangeInput, decorators: [{
4473
- type: Component,
4474
- args: [{ selector: 'mat-date-range-input', exportAs: 'matDateRangeInput', host: {
4475
- 'class': 'mat-date-range-input',
4476
- '[class.mat-date-range-input-hide-placeholders]': '_shouldHidePlaceholders()',
4477
- '[class.mat-date-range-input-required]': 'required',
4478
- '[attr.id]': 'id',
4479
- 'role': 'group',
4480
- '[attr.aria-labelledby]': '_getAriaLabelledby()',
4481
- '[attr.aria-describedby]': '_ariaDescribedBy',
4482
- // Used by the test harness to tie this input to its calendar. We can't depend on
4483
- // `aria-owns` for this, because it's only defined while the calendar is open.
4484
- '[attr.data-mat-calendar]': 'rangePicker ? rangePicker.id : null',
4485
- }, changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, providers: [
4486
- { provide: MatFormFieldControl, useExisting: MatDateRangeInput },
4487
- { provide: MAT_DATE_RANGE_INPUT_PARENT, useExisting: MatDateRangeInput },
4488
- ], imports: [CdkMonitorFocus], template: "<div\n class=\"mat-date-range-input-container\"\n cdkMonitorSubtreeFocus\n (cdkFocusChange)=\"_updateFocus($event)\">\n <div class=\"mat-date-range-input-wrapper\">\n <ng-content select=\"input[matStartDate]\"></ng-content>\n <span\n class=\"mat-date-range-input-mirror\"\n aria-hidden=\"true\">{{_getInputMirrorValue('start')}}</span>\n </div>\n\n <span\n class=\"mat-date-range-input-separator\"\n [class.mat-date-range-input-separator-hidden]=\"_shouldHideSeparator()\">{{separator}}</span>\n\n <div class=\"mat-date-range-input-wrapper mat-date-range-input-end-wrapper\">\n <ng-content select=\"input[matEndDate]\"></ng-content>\n <span\n class=\"mat-date-range-input-mirror\"\n aria-hidden=\"true\">{{_getInputMirrorValue('end')}}</span>\n </div>\n</div>\n\n", styles: [".mat-date-range-input{display:block;width:100%}.mat-date-range-input-container{display:flex;align-items:center}.mat-date-range-input-separator{transition:opacity 400ms 133.3333333333ms cubic-bezier(0.25, 0.8, 0.25, 1);margin:0 4px;color:var(--mat-datepicker-range-input-separator-color, var(--mat-sys-on-surface))}.mat-form-field-disabled .mat-date-range-input-separator{color:var(--mat-datepicker-range-input-disabled-state-separator-color, color-mix(in srgb, var(--mat-sys-on-surface) 38%, transparent))}._mat-animation-noopable .mat-date-range-input-separator{transition:none}.mat-date-range-input-separator-hidden{-webkit-user-select:none;user-select:none;opacity:0;transition:none}.mat-date-range-input-wrapper{position:relative;overflow:hidden;max-width:calc(50% - 4px)}.mat-date-range-input-end-wrapper{flex-grow:1}.mat-date-range-input-inner{position:absolute;top:0;left:0;font:inherit;background:rgba(0,0,0,0);color:currentColor;border:none;outline:none;padding:0;margin:0;vertical-align:bottom;text-align:inherit;-webkit-appearance:none;width:100%;height:100%}.mat-date-range-input-inner:-moz-ui-invalid{box-shadow:none}.mat-date-range-input-inner::placeholder{transition:color 400ms 133.3333333333ms cubic-bezier(0.25, 0.8, 0.25, 1)}.mat-date-range-input-inner::-moz-placeholder{transition:color 400ms 133.3333333333ms cubic-bezier(0.25, 0.8, 0.25, 1)}.mat-date-range-input-inner::-webkit-input-placeholder{transition:color 400ms 133.3333333333ms cubic-bezier(0.25, 0.8, 0.25, 1)}.mat-date-range-input-inner:-ms-input-placeholder{transition:color 400ms 133.3333333333ms cubic-bezier(0.25, 0.8, 0.25, 1)}.mat-date-range-input-inner[disabled]{color:var(--mat-datepicker-range-input-disabled-state-text-color, color-mix(in srgb, var(--mat-sys-on-surface) 38%, transparent))}.mat-form-field-hide-placeholder .mat-date-range-input-inner::placeholder,.mat-date-range-input-hide-placeholders .mat-date-range-input-inner::placeholder{-webkit-user-select:none;user-select:none;color:rgba(0,0,0,0) !important;-webkit-text-fill-color:rgba(0,0,0,0);transition:none}@media(forced-colors: active){.mat-form-field-hide-placeholder .mat-date-range-input-inner::placeholder,.mat-date-range-input-hide-placeholders .mat-date-range-input-inner::placeholder{opacity:0}}.mat-form-field-hide-placeholder .mat-date-range-input-inner::-moz-placeholder,.mat-date-range-input-hide-placeholders .mat-date-range-input-inner::-moz-placeholder{-webkit-user-select:none;user-select:none;color:rgba(0,0,0,0) !important;-webkit-text-fill-color:rgba(0,0,0,0);transition:none}@media(forced-colors: active){.mat-form-field-hide-placeholder .mat-date-range-input-inner::-moz-placeholder,.mat-date-range-input-hide-placeholders .mat-date-range-input-inner::-moz-placeholder{opacity:0}}.mat-form-field-hide-placeholder .mat-date-range-input-inner::-webkit-input-placeholder,.mat-date-range-input-hide-placeholders .mat-date-range-input-inner::-webkit-input-placeholder{-webkit-user-select:none;user-select:none;color:rgba(0,0,0,0) !important;-webkit-text-fill-color:rgba(0,0,0,0);transition:none}@media(forced-colors: active){.mat-form-field-hide-placeholder .mat-date-range-input-inner::-webkit-input-placeholder,.mat-date-range-input-hide-placeholders .mat-date-range-input-inner::-webkit-input-placeholder{opacity:0}}.mat-form-field-hide-placeholder .mat-date-range-input-inner:-ms-input-placeholder,.mat-date-range-input-hide-placeholders .mat-date-range-input-inner:-ms-input-placeholder{-webkit-user-select:none;user-select:none;color:rgba(0,0,0,0) !important;-webkit-text-fill-color:rgba(0,0,0,0);transition:none}@media(forced-colors: active){.mat-form-field-hide-placeholder .mat-date-range-input-inner:-ms-input-placeholder,.mat-date-range-input-hide-placeholders .mat-date-range-input-inner:-ms-input-placeholder{opacity:0}}._mat-animation-noopable .mat-date-range-input-inner::placeholder{transition:none}._mat-animation-noopable .mat-date-range-input-inner::-moz-placeholder{transition:none}._mat-animation-noopable .mat-date-range-input-inner::-webkit-input-placeholder{transition:none}._mat-animation-noopable .mat-date-range-input-inner:-ms-input-placeholder{transition:none}.mat-date-range-input-mirror{-webkit-user-select:none;user-select:none;visibility:hidden;white-space:nowrap;display:inline-block;min-width:2px}.mat-mdc-form-field-type-mat-date-range-input .mat-mdc-form-field-infix{width:200px}"] }]
4489
- }], ctorParameters: () => [], propDecorators: { rangePicker: [{
4490
- type: Input
4491
- }], required: [{
4492
- type: Input,
4493
- args: [{ transform: booleanAttribute }]
4494
- }], dateFilter: [{
4495
- type: Input
4496
- }], min: [{
4497
- type: Input
4498
- }], max: [{
4499
- type: Input
4500
- }], disabled: [{
4501
- type: Input,
4502
- args: [{ transform: booleanAttribute }]
4503
- }], separator: [{
4504
- type: Input
4505
- }], comparisonStart: [{
4506
- type: Input
4507
- }], comparisonEnd: [{
4508
- type: Input
4509
- }], _startInput: [{
4510
- type: ContentChild,
4511
- args: [MatStartDate]
4512
- }], _endInput: [{
4513
- type: ContentChild,
4514
- args: [MatEndDate]
4515
- }] } });
4487
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.1.0-next.3", ngImport: i0, type: MatEndDate, decorators: [{
4488
+ type: Directive,
4489
+ args: [{
4490
+ selector: 'input[matEndDate]',
4491
+ host: {
4492
+ 'class': 'mat-end-date mat-date-range-input-inner',
4493
+ '[disabled]': 'disabled',
4494
+ '(input)': '_onInput($event.target.value)',
4495
+ '(change)': '_onChange()',
4496
+ '(keydown)': '_onKeydown($event)',
4497
+ '[attr.aria-haspopup]': '_rangeInput.rangePicker ? "dialog" : null',
4498
+ '[attr.aria-owns]': `_rangeInput._ariaOwns
4499
+ ? _rangeInput._ariaOwns()
4500
+ : (_rangeInput.rangePicker?.opened && _rangeInput.rangePicker.id) || null`,
4501
+ '[attr.min]': '_getMinDate() ? _dateAdapter.toIso8601(_getMinDate()) : null',
4502
+ '[attr.max]': '_getMaxDate() ? _dateAdapter.toIso8601(_getMaxDate()) : null',
4503
+ '(blur)': '_onBlur()',
4504
+ 'type': 'text',
4505
+ },
4506
+ providers: [
4507
+ { provide: NG_VALUE_ACCESSOR, useExisting: MatEndDate, multi: true },
4508
+ { provide: NG_VALIDATORS, useExisting: MatEndDate, multi: true },
4509
+ ],
4510
+ // These need to be specified explicitly, because some tooling doesn't
4511
+ // seem to pick them up from the base class. See #20932.
4512
+ outputs: ['dateChange', 'dateInput'],
4513
+ }]
4514
+ }] });
4516
4515
 
4517
4516
  // TODO(mmalerba): We use a component instead of a directive here so the user can use implicit
4518
4517
  // template reference variables (e.g. #d vs #d="matDateRangePicker"). We can change this to a