timepicker.mjs 48 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891
  1. import * as i0 from '@angular/core';
  2. import { InjectionToken, inject, ViewContainerRef, Injector, ANIMATION_MODULE_TYPE, signal, viewChild, viewChildren, input, output, booleanAttribute, computed, effect, ElementRef, afterNextRender, untracked, Component, ChangeDetectionStrategy, ViewEncapsulation, model, Renderer2, Directive, HostAttributeToken, NgModule } from '@angular/core';
  3. import { Directionality } from '@angular/cdk/bidi';
  4. import { Overlay } from '@angular/cdk/overlay';
  5. import { TemplatePortal } from '@angular/cdk/portal';
  6. import { _getEventTarget, _getFocusedElementPierceShadowDom } from '@angular/cdk/platform';
  7. import { TAB, ESCAPE, hasModifierKey, ENTER, DOWN_ARROW, UP_ARROW } from '@angular/cdk/keycodes';
  8. import { ActiveDescendantKeyManager, _IdGenerator } from '@angular/cdk/a11y';
  9. import { D as DateAdapter, a as MAT_DATE_FORMATS } from './date-formats-K6TQue-Y.mjs';
  10. import { M as MatOption, c as MAT_OPTION_PARENT_COMPONENT } from './option-ChV6uQgD.mjs';
  11. import { Validators, NG_VALUE_ACCESSOR, NG_VALIDATORS } from '@angular/forms';
  12. import { h as MAT_FORM_FIELD } from './form-field-DqPi4knt.mjs';
  13. import { M as MAT_INPUT_VALUE_ACCESSOR } from './input-value-accessor-D1GvPuqO.mjs';
  14. import { M as MatIconButton } from './icon-button-D1J0zeqv.mjs';
  15. import { CdkScrollableModule } from '@angular/cdk/scrolling';
  16. import 'rxjs';
  17. import './ripple-BT3tzh6F.mjs';
  18. import '@angular/cdk/coercion';
  19. import '@angular/cdk/private';
  20. import './pseudo-checkbox-CJ7seqQH.mjs';
  21. import './structural-styles-BQUT6wsL.mjs';
  22. import '@angular/common';
  23. import 'rxjs/operators';
  24. import '@angular/cdk/observers/private';
  25. import './ripple-loader-Ce3DAhPW.mjs';
  26. /** Pattern that interval strings have to match. */
  27. const INTERVAL_PATTERN = /^(\d*\.?\d+)\s*(h|hour|hours|m|min|minute|minutes|s|second|seconds)?$/i;
  28. /**
  29. * Injection token that can be used to configure the default options for the timepicker component.
  30. */
  31. const MAT_TIMEPICKER_CONFIG = new InjectionToken('MAT_TIMEPICKER_CONFIG');
  32. /** Parses an interval value into seconds. */
  33. function parseInterval(value) {
  34. let result;
  35. if (value === null) {
  36. return null;
  37. }
  38. else if (typeof value === 'number') {
  39. result = value;
  40. }
  41. else {
  42. if (value.trim().length === 0) {
  43. return null;
  44. }
  45. const parsed = value.match(INTERVAL_PATTERN);
  46. const amount = parsed ? parseFloat(parsed[1]) : null;
  47. const unit = parsed?.[2]?.toLowerCase() || null;
  48. if (!parsed || amount === null || isNaN(amount)) {
  49. return null;
  50. }
  51. if (unit === 'h' || unit === 'hour' || unit === 'hours') {
  52. result = amount * 3600;
  53. }
  54. else if (unit === 'm' || unit === 'min' || unit === 'minute' || unit === 'minutes') {
  55. result = amount * 60;
  56. }
  57. else {
  58. result = amount;
  59. }
  60. }
  61. return result;
  62. }
  63. /**
  64. * Generates the options to show in a timepicker.
  65. * @param adapter Date adapter to be used to generate the options.
  66. * @param formats Formatting config to use when displaying the options.
  67. * @param min Time from which to start generating the options.
  68. * @param max Time at which to stop generating the options.
  69. * @param interval Amount of seconds between each option.
  70. */
  71. function generateOptions(adapter, formats, min, max, interval) {
  72. const options = [];
  73. let current = adapter.compareTime(min, max) < 1 ? min : max;
  74. while (adapter.sameDate(current, min) &&
  75. adapter.compareTime(current, max) < 1 &&
  76. adapter.isValid(current)) {
  77. options.push({ value: current, label: adapter.format(current, formats.display.timeOptionLabel) });
  78. current = adapter.addSeconds(current, interval);
  79. }
  80. return options;
  81. }
  82. /** Checks whether a date adapter is set up correctly for use with the timepicker. */
  83. function validateAdapter(adapter, formats) {
  84. function missingAdapterError(provider) {
  85. return Error(`MatTimepicker: No provider found for ${provider}. You must add one of the following ` +
  86. `to your app config: provideNativeDateAdapter, provideDateFnsAdapter, ` +
  87. `provideLuxonDateAdapter, provideMomentDateAdapter, or provide a custom implementation.`);
  88. }
  89. if (!adapter) {
  90. throw missingAdapterError('DateAdapter');
  91. }
  92. if (!formats) {
  93. throw missingAdapterError('MAT_DATE_FORMATS');
  94. }
  95. if (formats.display.timeInput === undefined ||
  96. formats.display.timeOptionLabel === undefined ||
  97. formats.parse.timeInput === undefined) {
  98. throw new Error('MatTimepicker: Incomplete `MAT_DATE_FORMATS` has been provided. ' +
  99. '`MAT_DATE_FORMATS` must provide `display.timeInput`, `display.timeOptionLabel` ' +
  100. 'and `parse.timeInput` formats in order to be compatible with MatTimepicker.');
  101. }
  102. }
  103. /** Injection token used to configure the behavior of the timepicker dropdown while scrolling. */
  104. const MAT_TIMEPICKER_SCROLL_STRATEGY = new InjectionToken('MAT_TIMEPICKER_SCROLL_STRATEGY', {
  105. providedIn: 'root',
  106. factory: () => {
  107. const overlay = inject(Overlay);
  108. return () => overlay.scrollStrategies.reposition();
  109. },
  110. });
  111. /**
  112. * Renders out a listbox that can be used to select a time of day.
  113. * Intended to be used together with `MatTimepickerInput`.
  114. */
  115. class MatTimepicker {
  116. _overlay = inject(Overlay);
  117. _dir = inject(Directionality, { optional: true });
  118. _viewContainerRef = inject(ViewContainerRef);
  119. _injector = inject(Injector);
  120. _defaultConfig = inject(MAT_TIMEPICKER_CONFIG, { optional: true });
  121. _dateAdapter = inject(DateAdapter, { optional: true });
  122. _dateFormats = inject(MAT_DATE_FORMATS, { optional: true });
  123. _scrollStrategyFactory = inject(MAT_TIMEPICKER_SCROLL_STRATEGY);
  124. _animationsDisabled = inject(ANIMATION_MODULE_TYPE, { optional: true }) === 'NoopAnimations';
  125. _isOpen = signal(false);
  126. _activeDescendant = signal(null);
  127. _input = signal(null);
  128. _overlayRef = null;
  129. _portal = null;
  130. _optionsCacheKey = null;
  131. _localeChanges;
  132. _onOpenRender = null;
  133. _panelTemplate = viewChild.required('panelTemplate');
  134. _timeOptions = [];
  135. _options = viewChildren(MatOption);
  136. _keyManager = new ActiveDescendantKeyManager(this._options, this._injector)
  137. .withHomeAndEnd(true)
  138. .withPageUpDown(true)
  139. .withVerticalOrientation(true);
  140. /**
  141. * Interval between each option in the timepicker. The value can either be an amount of
  142. * seconds (e.g. 90) or a number with a unit (e.g. 45m). Supported units are `s` for seconds,
  143. * `m` for minutes or `h` for hours.
  144. */
  145. interval = input(parseInterval(this._defaultConfig?.interval || null), { transform: parseInterval });
  146. /**
  147. * Array of pre-defined options that the user can select from, as an alternative to using the
  148. * `interval` input. An error will be thrown if both `options` and `interval` are specified.
  149. */
  150. options = input(null);
  151. /** Whether the timepicker is open. */
  152. isOpen = this._isOpen.asReadonly();
  153. /** Emits when the user selects a time. */
  154. selected = output();
  155. /** Emits when the timepicker is opened. */
  156. opened = output();
  157. /** Emits when the timepicker is closed. */
  158. closed = output();
  159. /** ID of the active descendant option. */
  160. activeDescendant = this._activeDescendant.asReadonly();
  161. /** Unique ID of the timepicker's panel */
  162. panelId = inject(_IdGenerator).getId('mat-timepicker-panel-');
  163. /** Whether ripples within the timepicker should be disabled. */
  164. disableRipple = input(this._defaultConfig?.disableRipple ?? false, {
  165. transform: booleanAttribute,
  166. });
  167. /** ARIA label for the timepicker panel. */
  168. ariaLabel = input(null, {
  169. alias: 'aria-label',
  170. });
  171. /** ID of the label element for the timepicker panel. */
  172. ariaLabelledby = input(null, {
  173. alias: 'aria-labelledby',
  174. });
  175. /** Whether the timepicker is currently disabled. */
  176. disabled = computed(() => !!this._input()?.disabled());
  177. constructor() {
  178. if (typeof ngDevMode === 'undefined' || ngDevMode) {
  179. validateAdapter(this._dateAdapter, this._dateFormats);
  180. effect(() => {
  181. const options = this.options();
  182. const interval = this.interval();
  183. if (options !== null && interval !== null) {
  184. throw new Error('Cannot specify both the `options` and `interval` inputs at the same time');
  185. }
  186. else if (options?.length === 0) {
  187. throw new Error('Value of `options` input cannot be an empty array');
  188. }
  189. });
  190. }
  191. // Since the panel ID is static, we can set it once without having to maintain a host binding.
  192. const element = inject(ElementRef);
  193. element.nativeElement.setAttribute('mat-timepicker-panel-id', this.panelId);
  194. this._handleLocaleChanges();
  195. this._handleInputStateChanges();
  196. this._keyManager.change.subscribe(() => this._activeDescendant.set(this._keyManager.activeItem?.id || null));
  197. }
  198. /** Opens the timepicker. */
  199. open() {
  200. const input = this._input();
  201. if (!input) {
  202. return;
  203. }
  204. // Focus should already be on the input, but this call is in case the timepicker is opened
  205. // programmatically. We need to call this even if the timepicker is already open, because
  206. // the user might be clicking the toggle.
  207. input.focus();
  208. if (this._isOpen()) {
  209. return;
  210. }
  211. this._isOpen.set(true);
  212. this._generateOptions();
  213. const overlayRef = this._getOverlayRef();
  214. overlayRef.updateSize({ width: input.getOverlayOrigin().nativeElement.offsetWidth });
  215. this._portal ??= new TemplatePortal(this._panelTemplate(), this._viewContainerRef);
  216. // We need to check this in case `isOpen` was flipped, but change detection hasn't
  217. // had a chance to run yet. See https://github.com/angular/components/issues/30637
  218. if (!overlayRef.hasAttached()) {
  219. overlayRef.attach(this._portal);
  220. }
  221. this._onOpenRender?.destroy();
  222. this._onOpenRender = afterNextRender(() => {
  223. const options = this._options();
  224. this._syncSelectedState(input.value(), options, options[0]);
  225. this._onOpenRender = null;
  226. }, { injector: this._injector });
  227. this.opened.emit();
  228. }
  229. /** Closes the timepicker. */
  230. close() {
  231. if (this._isOpen()) {
  232. this._isOpen.set(false);
  233. this.closed.emit();
  234. if (this._animationsDisabled) {
  235. this._overlayRef?.detach();
  236. }
  237. }
  238. }
  239. /** Registers an input with the timepicker. */
  240. registerInput(input) {
  241. const currentInput = this._input();
  242. if (currentInput && input !== currentInput && (typeof ngDevMode === 'undefined' || ngDevMode)) {
  243. throw new Error('MatTimepicker can only be registered with one input at a time');
  244. }
  245. this._input.set(input);
  246. }
  247. ngOnDestroy() {
  248. this._keyManager.destroy();
  249. this._localeChanges.unsubscribe();
  250. this._onOpenRender?.destroy();
  251. this._overlayRef?.dispose();
  252. }
  253. /** Selects a specific time value. */
  254. _selectValue(option) {
  255. this.close();
  256. this._keyManager.setActiveItem(option);
  257. this._options().forEach(current => {
  258. // This is primarily here so we don't show two selected options while animating away.
  259. if (current !== option) {
  260. current.deselect(false);
  261. }
  262. });
  263. this.selected.emit({ value: option.value, source: this });
  264. this._input()?.focus();
  265. }
  266. /** Gets the value of the `aria-labelledby` attribute. */
  267. _getAriaLabelledby() {
  268. if (this.ariaLabel()) {
  269. return null;
  270. }
  271. return this.ariaLabelledby() || this._input()?._getLabelId() || null;
  272. }
  273. /** Handles animation events coming from the panel. */
  274. _handleAnimationEnd(event) {
  275. if (event.animationName === '_mat-timepicker-exit') {
  276. this._overlayRef?.detach();
  277. }
  278. }
  279. /** Creates an overlay reference for the timepicker panel. */
  280. _getOverlayRef() {
  281. if (this._overlayRef) {
  282. return this._overlayRef;
  283. }
  284. const positionStrategy = this._overlay
  285. .position()
  286. .flexibleConnectedTo(this._input().getOverlayOrigin())
  287. .withFlexibleDimensions(false)
  288. .withPush(false)
  289. .withTransformOriginOn('.mat-timepicker-panel')
  290. .withPositions([
  291. {
  292. originX: 'start',
  293. originY: 'bottom',
  294. overlayX: 'start',
  295. overlayY: 'top',
  296. },
  297. {
  298. originX: 'start',
  299. originY: 'top',
  300. overlayX: 'start',
  301. overlayY: 'bottom',
  302. panelClass: 'mat-timepicker-above',
  303. },
  304. ]);
  305. this._overlayRef = this._overlay.create({
  306. positionStrategy,
  307. scrollStrategy: this._scrollStrategyFactory(),
  308. direction: this._dir || 'ltr',
  309. hasBackdrop: false,
  310. });
  311. this._overlayRef.detachments().subscribe(() => this.close());
  312. this._overlayRef.keydownEvents().subscribe(event => this._handleKeydown(event));
  313. this._overlayRef.outsidePointerEvents().subscribe(event => {
  314. const target = _getEventTarget(event);
  315. const origin = this._input()?.getOverlayOrigin().nativeElement;
  316. if (target && origin && target !== origin && !origin.contains(target)) {
  317. this.close();
  318. }
  319. });
  320. return this._overlayRef;
  321. }
  322. /** Generates the list of options from which the user can select.. */
  323. _generateOptions() {
  324. // Default the interval to 30 minutes.
  325. const interval = this.interval() ?? 30 * 60;
  326. const options = this.options();
  327. if (options !== null) {
  328. this._timeOptions = options;
  329. }
  330. else {
  331. const input = this._input();
  332. const adapter = this._dateAdapter;
  333. const timeFormat = this._dateFormats.display.timeInput;
  334. const min = input?.min() || adapter.setTime(adapter.today(), 0, 0, 0);
  335. const max = input?.max() || adapter.setTime(adapter.today(), 23, 59, 0);
  336. const cacheKey = interval + '/' + adapter.format(min, timeFormat) + '/' + adapter.format(max, timeFormat);
  337. // Don't re-generate the options if the inputs haven't changed.
  338. if (cacheKey !== this._optionsCacheKey) {
  339. this._optionsCacheKey = cacheKey;
  340. this._timeOptions = generateOptions(adapter, this._dateFormats, min, max, interval);
  341. }
  342. }
  343. }
  344. /**
  345. * Synchronizes the internal state of the component based on a specific selected date.
  346. * @param value Currently selected date.
  347. * @param options Options rendered out in the timepicker.
  348. * @param fallback Option to set as active if no option is selected.
  349. */
  350. _syncSelectedState(value, options, fallback) {
  351. let hasSelected = false;
  352. for (const option of options) {
  353. if (value && this._dateAdapter.sameTime(option.value, value)) {
  354. option.select(false);
  355. scrollOptionIntoView(option, 'center');
  356. untracked(() => this._keyManager.setActiveItem(option));
  357. hasSelected = true;
  358. }
  359. else {
  360. option.deselect(false);
  361. }
  362. }
  363. // If no option was selected, we need to reset the key manager since
  364. // it might be holding onto an option that no longer exists.
  365. if (!hasSelected) {
  366. if (fallback) {
  367. untracked(() => this._keyManager.setActiveItem(fallback));
  368. scrollOptionIntoView(fallback, 'center');
  369. }
  370. else {
  371. untracked(() => this._keyManager.setActiveItem(-1));
  372. }
  373. }
  374. }
  375. /** Handles keyboard events while the overlay is open. */
  376. _handleKeydown(event) {
  377. const keyCode = event.keyCode;
  378. if (keyCode === TAB) {
  379. this.close();
  380. }
  381. else if (keyCode === ESCAPE && !hasModifierKey(event)) {
  382. event.preventDefault();
  383. this.close();
  384. }
  385. else if (keyCode === ENTER) {
  386. event.preventDefault();
  387. if (this._keyManager.activeItem) {
  388. this._selectValue(this._keyManager.activeItem);
  389. }
  390. else {
  391. this.close();
  392. }
  393. }
  394. else {
  395. const previousActive = this._keyManager.activeItem;
  396. this._keyManager.onKeydown(event);
  397. const currentActive = this._keyManager.activeItem;
  398. if (currentActive && currentActive !== previousActive) {
  399. scrollOptionIntoView(currentActive, 'nearest');
  400. }
  401. }
  402. }
  403. /** Sets up the logic that updates the timepicker when the locale changes. */
  404. _handleLocaleChanges() {
  405. // Re-generate the options list if the locale changes.
  406. this._localeChanges = this._dateAdapter.localeChanges.subscribe(() => {
  407. this._optionsCacheKey = null;
  408. if (this.isOpen()) {
  409. this._generateOptions();
  410. }
  411. });
  412. }
  413. /**
  414. * Sets up the logic that updates the timepicker when the state of the connected input changes.
  415. */
  416. _handleInputStateChanges() {
  417. effect(() => {
  418. const input = this._input();
  419. const options = this._options();
  420. if (this._isOpen() && input) {
  421. this._syncSelectedState(input.value(), options, null);
  422. }
  423. });
  424. }
  425. static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: MatTimepicker, deps: [], target: i0.ɵɵFactoryTarget.Component });
  426. static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.6", type: MatTimepicker, isStandalone: true, selector: "mat-timepicker", inputs: { interval: { classPropertyName: "interval", publicName: "interval", isSignal: true, isRequired: false, transformFunction: null }, options: { classPropertyName: "options", publicName: "options", isSignal: true, isRequired: false, transformFunction: null }, disableRipple: { classPropertyName: "disableRipple", publicName: "disableRipple", isSignal: true, isRequired: false, transformFunction: null }, ariaLabel: { classPropertyName: "ariaLabel", publicName: "aria-label", isSignal: true, isRequired: false, transformFunction: null }, ariaLabelledby: { classPropertyName: "ariaLabelledby", publicName: "aria-labelledby", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { selected: "selected", opened: "opened", closed: "closed" }, providers: [
  427. {
  428. provide: MAT_OPTION_PARENT_COMPONENT,
  429. useExisting: MatTimepicker,
  430. },
  431. ], viewQueries: [{ propertyName: "_panelTemplate", first: true, predicate: ["panelTemplate"], descendants: true, isSignal: true }, { propertyName: "_options", predicate: MatOption, descendants: true, isSignal: true }], exportAs: ["matTimepicker"], ngImport: i0, template: "<ng-template #panelTemplate>\n <div\n role=\"listbox\"\n class=\"mat-timepicker-panel\"\n [class.mat-timepicker-panel-animations-enabled]=\"!_animationsDisabled\"\n [class.mat-timepicker-panel-exit]=\"!isOpen()\"\n [attr.aria-label]=\"ariaLabel() || null\"\n [attr.aria-labelledby]=\"_getAriaLabelledby()\"\n [id]=\"panelId\"\n (animationend)=\"_handleAnimationEnd($event)\">\n @for (option of _timeOptions; track option.value) {\n <mat-option\n [value]=\"option.value\"\n (onSelectionChange)=\"_selectValue($event.source)\">{{option.label}}</mat-option>\n }\n </div>\n</ng-template>\n", styles: ["@keyframes _mat-timepicker-enter{from{opacity:0;transform:scaleY(0.8)}to{opacity:1;transform:none}}@keyframes _mat-timepicker-exit{from{opacity:1}to{opacity:0}}mat-timepicker{display:none}.mat-timepicker-panel{width:100%;max-height:256px;transform-origin:center top;overflow:auto;padding:8px 0;box-sizing:border-box;position:relative;border-bottom-left-radius:var(--mat-timepicker-container-shape, var(--mat-sys-corner-extra-small));border-bottom-right-radius:var(--mat-timepicker-container-shape, var(--mat-sys-corner-extra-small));box-shadow:var(--mat-timepicker-container-elevation-shadow, 0px 3px 1px -2px rgba(0, 0, 0, 0.2), 0px 2px 2px 0px rgba(0, 0, 0, 0.14), 0px 1px 5px 0px rgba(0, 0, 0, 0.12));background-color:var(--mat-timepicker-container-background-color, var(--mat-sys-surface-container))}@media(forced-colors: active){.mat-timepicker-panel{outline:solid 1px}}.mat-timepicker-above .mat-timepicker-panel{border-bottom-left-radius:0;border-bottom-right-radius:0;border-top-left-radius:var(--mat-timepicker-container-shape, var(--mat-sys-corner-extra-small));border-top-right-radius:var(--mat-timepicker-container-shape, var(--mat-sys-corner-extra-small))}.mat-timepicker-panel-animations-enabled{animation:_mat-timepicker-enter 120ms cubic-bezier(0, 0, 0.2, 1)}.mat-timepicker-panel-animations-enabled.mat-timepicker-panel-exit{animation:_mat-timepicker-exit 100ms linear}.mat-timepicker-input[readonly]{cursor:pointer}@media(forced-colors: active){.mat-timepicker-toggle-default-icon{color:CanvasText}}\n"], dependencies: [{ kind: "component", type: MatOption, selector: "mat-option", inputs: ["value", "id", "disabled"], outputs: ["onSelectionChange"], exportAs: ["matOption"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
  432. }
  433. i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: MatTimepicker, decorators: [{
  434. type: Component,
  435. args: [{ selector: 'mat-timepicker', exportAs: 'matTimepicker', changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, imports: [MatOption], providers: [
  436. {
  437. provide: MAT_OPTION_PARENT_COMPONENT,
  438. useExisting: MatTimepicker,
  439. },
  440. ], template: "<ng-template #panelTemplate>\n <div\n role=\"listbox\"\n class=\"mat-timepicker-panel\"\n [class.mat-timepicker-panel-animations-enabled]=\"!_animationsDisabled\"\n [class.mat-timepicker-panel-exit]=\"!isOpen()\"\n [attr.aria-label]=\"ariaLabel() || null\"\n [attr.aria-labelledby]=\"_getAriaLabelledby()\"\n [id]=\"panelId\"\n (animationend)=\"_handleAnimationEnd($event)\">\n @for (option of _timeOptions; track option.value) {\n <mat-option\n [value]=\"option.value\"\n (onSelectionChange)=\"_selectValue($event.source)\">{{option.label}}</mat-option>\n }\n </div>\n</ng-template>\n", styles: ["@keyframes _mat-timepicker-enter{from{opacity:0;transform:scaleY(0.8)}to{opacity:1;transform:none}}@keyframes _mat-timepicker-exit{from{opacity:1}to{opacity:0}}mat-timepicker{display:none}.mat-timepicker-panel{width:100%;max-height:256px;transform-origin:center top;overflow:auto;padding:8px 0;box-sizing:border-box;position:relative;border-bottom-left-radius:var(--mat-timepicker-container-shape, var(--mat-sys-corner-extra-small));border-bottom-right-radius:var(--mat-timepicker-container-shape, var(--mat-sys-corner-extra-small));box-shadow:var(--mat-timepicker-container-elevation-shadow, 0px 3px 1px -2px rgba(0, 0, 0, 0.2), 0px 2px 2px 0px rgba(0, 0, 0, 0.14), 0px 1px 5px 0px rgba(0, 0, 0, 0.12));background-color:var(--mat-timepicker-container-background-color, var(--mat-sys-surface-container))}@media(forced-colors: active){.mat-timepicker-panel{outline:solid 1px}}.mat-timepicker-above .mat-timepicker-panel{border-bottom-left-radius:0;border-bottom-right-radius:0;border-top-left-radius:var(--mat-timepicker-container-shape, var(--mat-sys-corner-extra-small));border-top-right-radius:var(--mat-timepicker-container-shape, var(--mat-sys-corner-extra-small))}.mat-timepicker-panel-animations-enabled{animation:_mat-timepicker-enter 120ms cubic-bezier(0, 0, 0.2, 1)}.mat-timepicker-panel-animations-enabled.mat-timepicker-panel-exit{animation:_mat-timepicker-exit 100ms linear}.mat-timepicker-input[readonly]{cursor:pointer}@media(forced-colors: active){.mat-timepicker-toggle-default-icon{color:CanvasText}}\n"] }]
  441. }], ctorParameters: () => [] });
  442. /**
  443. * Scrolls an option into view.
  444. * @param option Option to be scrolled into view.
  445. * @param position Position to which to align the option relative to the scrollable container.
  446. */
  447. function scrollOptionIntoView(option, position) {
  448. option._getHostElement().scrollIntoView({ block: position, inline: position });
  449. }
  450. /**
  451. * Input that can be used to enter time and connect to a `mat-timepicker`.
  452. */
  453. class MatTimepickerInput {
  454. _elementRef = inject(ElementRef);
  455. _dateAdapter = inject(DateAdapter, { optional: true });
  456. _dateFormats = inject(MAT_DATE_FORMATS, { optional: true });
  457. _formField = inject(MAT_FORM_FIELD, { optional: true });
  458. _onChange;
  459. _onTouched;
  460. _validatorOnChange;
  461. _cleanupClick;
  462. _accessorDisabled = signal(false);
  463. _localeSubscription;
  464. _timepickerSubscription;
  465. _validator;
  466. _lastValueValid = true;
  467. _lastValidDate = null;
  468. /** Value of the `aria-activedescendant` attribute. */
  469. _ariaActiveDescendant = computed(() => {
  470. const timepicker = this.timepicker();
  471. const isOpen = timepicker.isOpen();
  472. const activeDescendant = timepicker.activeDescendant();
  473. return isOpen && activeDescendant ? activeDescendant : null;
  474. });
  475. /** Value of the `aria-expanded` attribute. */
  476. _ariaExpanded = computed(() => this.timepicker().isOpen() + '');
  477. /** Value of the `aria-controls` attribute. */
  478. _ariaControls = computed(() => {
  479. const timepicker = this.timepicker();
  480. return timepicker.isOpen() ? timepicker.panelId : null;
  481. });
  482. /** Current value of the input. */
  483. value = model(null);
  484. /** Timepicker that the input is associated with. */
  485. timepicker = input.required({
  486. alias: 'matTimepicker',
  487. });
  488. /**
  489. * Minimum time that can be selected or typed in. Can be either
  490. * a date object (only time will be used) or a valid time string.
  491. */
  492. min = input(null, {
  493. alias: 'matTimepickerMin',
  494. transform: (value) => this._transformDateInput(value),
  495. });
  496. /**
  497. * Maximum time that can be selected or typed in. Can be either
  498. * a date object (only time will be used) or a valid time string.
  499. */
  500. max = input(null, {
  501. alias: 'matTimepickerMax',
  502. transform: (value) => this._transformDateInput(value),
  503. });
  504. /** Whether the input is disabled. */
  505. disabled = computed(() => this.disabledInput() || this._accessorDisabled());
  506. /**
  507. * Whether the input should be disabled through the template.
  508. * @docs-private
  509. */
  510. disabledInput = input(false, {
  511. transform: booleanAttribute,
  512. alias: 'disabled',
  513. });
  514. constructor() {
  515. if (typeof ngDevMode === 'undefined' || ngDevMode) {
  516. validateAdapter(this._dateAdapter, this._dateFormats);
  517. }
  518. const renderer = inject(Renderer2);
  519. this._validator = this._getValidator();
  520. this._respondToValueChanges();
  521. this._respondToMinMaxChanges();
  522. this._registerTimepicker();
  523. this._localeSubscription = this._dateAdapter.localeChanges.subscribe(() => {
  524. if (!this._hasFocus()) {
  525. this._formatValue(this.value());
  526. }
  527. });
  528. // Bind the click listener manually to the overlay origin, because we want the entire
  529. // form field to be clickable, if the timepicker is used in `mat-form-field`.
  530. this._cleanupClick = renderer.listen(this.getOverlayOrigin().nativeElement, 'click', this._handleClick);
  531. }
  532. /**
  533. * Implemented as a part of `ControlValueAccessor`.
  534. * @docs-private
  535. */
  536. writeValue(value) {
  537. // Note that we need to deserialize here, rather than depend on the value change effect,
  538. // because `getValidDateOrNull` will clobber the value if it's parseable, but not created by
  539. // the current adapter (see #30140).
  540. const deserialized = this._dateAdapter.deserialize(value);
  541. this.value.set(this._dateAdapter.getValidDateOrNull(deserialized));
  542. }
  543. /**
  544. * Implemented as a part of `ControlValueAccessor`.
  545. * @docs-private
  546. */
  547. registerOnChange(fn) {
  548. this._onChange = fn;
  549. }
  550. /**
  551. * Implemented as a part of `ControlValueAccessor`.
  552. * @docs-private
  553. */
  554. registerOnTouched(fn) {
  555. this._onTouched = fn;
  556. }
  557. /**
  558. * Implemented as a part of `ControlValueAccessor`.
  559. * @docs-private
  560. */
  561. setDisabledState(isDisabled) {
  562. this._accessorDisabled.set(isDisabled);
  563. }
  564. /**
  565. * Implemented as a part of `Validator`.
  566. * @docs-private
  567. */
  568. validate(control) {
  569. return this._validator(control);
  570. }
  571. /**
  572. * Implemented as a part of `Validator`.
  573. * @docs-private
  574. */
  575. registerOnValidatorChange(fn) {
  576. this._validatorOnChange = fn;
  577. }
  578. /** Gets the element to which the timepicker popup should be attached. */
  579. getOverlayOrigin() {
  580. return this._formField?.getConnectedOverlayOrigin() || this._elementRef;
  581. }
  582. /** Focuses the input. */
  583. focus() {
  584. this._elementRef.nativeElement.focus();
  585. }
  586. ngOnDestroy() {
  587. this._cleanupClick();
  588. this._timepickerSubscription?.unsubscribe();
  589. this._localeSubscription.unsubscribe();
  590. }
  591. /** Gets the ID of the input's label. */
  592. _getLabelId() {
  593. return this._formField?.getLabelId() || null;
  594. }
  595. /** Handles clicks on the input or the containing form field. */
  596. _handleClick = () => {
  597. if (!this.disabled()) {
  598. this.timepicker().open();
  599. }
  600. };
  601. /** Handles the `input` event. */
  602. _handleInput(value) {
  603. const currentValue = this.value();
  604. const date = this._dateAdapter.parseTime(value, this._dateFormats.parse.timeInput);
  605. const hasChanged = !this._dateAdapter.sameTime(date, currentValue);
  606. if (!date || hasChanged || !!(value && !currentValue)) {
  607. // We need to fire the CVA change event for all nulls, otherwise the validators won't run.
  608. this._assignUserSelection(date, true);
  609. }
  610. else {
  611. // Call the validator even if the value hasn't changed since
  612. // some fields change depending on what the user has entered.
  613. this._validatorOnChange?.();
  614. }
  615. }
  616. /** Handles the `blur` event. */
  617. _handleBlur() {
  618. const value = this.value();
  619. // Only reformat on blur so the value doesn't change while the user is interacting.
  620. if (value && this._isValid(value)) {
  621. this._formatValue(value);
  622. }
  623. if (!this.timepicker().isOpen()) {
  624. this._onTouched?.();
  625. }
  626. }
  627. /** Handles the `keydown` event. */
  628. _handleKeydown(event) {
  629. // All keyboard events while open are handled through the timepicker.
  630. if (this.timepicker().isOpen() || this.disabled()) {
  631. return;
  632. }
  633. if (event.keyCode === ESCAPE && !hasModifierKey(event) && this.value() !== null) {
  634. event.preventDefault();
  635. this.value.set(null);
  636. this._formatValue(null);
  637. }
  638. else if (event.keyCode === DOWN_ARROW || event.keyCode === UP_ARROW) {
  639. event.preventDefault();
  640. this.timepicker().open();
  641. }
  642. }
  643. /** Sets up the code that watches for changes in the value and adjusts the input. */
  644. _respondToValueChanges() {
  645. effect(() => {
  646. const value = this._dateAdapter.deserialize(this.value());
  647. const wasValid = this._lastValueValid;
  648. this._lastValueValid = this._isValid(value);
  649. // Reformat the value if it changes while the user isn't interacting.
  650. if (!this._hasFocus()) {
  651. this._formatValue(value);
  652. }
  653. if (value && this._lastValueValid) {
  654. this._lastValidDate = value;
  655. }
  656. // Trigger the validator if the state changed.
  657. if (wasValid !== this._lastValueValid) {
  658. this._validatorOnChange?.();
  659. }
  660. });
  661. }
  662. /** Sets up the logic that registers the input with the timepicker. */
  663. _registerTimepicker() {
  664. effect(() => {
  665. const timepicker = this.timepicker();
  666. timepicker.registerInput(this);
  667. timepicker.closed.subscribe(() => this._onTouched?.());
  668. timepicker.selected.subscribe(({ value }) => {
  669. if (!this._dateAdapter.sameTime(value, this.value())) {
  670. this._assignUserSelection(value, true);
  671. this._formatValue(value);
  672. }
  673. });
  674. });
  675. }
  676. /** Sets up the logic that adjusts the input if the min/max changes. */
  677. _respondToMinMaxChanges() {
  678. effect(() => {
  679. // Read the min/max so the effect knows when to fire.
  680. this.min();
  681. this.max();
  682. this._validatorOnChange?.();
  683. });
  684. }
  685. /**
  686. * Assigns a value set by the user to the input's model.
  687. * @param selection Time selected by the user that should be assigned.
  688. * @param propagateToAccessor Whether the value should be propagated to the ControlValueAccessor.
  689. */
  690. _assignUserSelection(selection, propagateToAccessor) {
  691. if (selection == null || !this._isValid(selection)) {
  692. this.value.set(selection);
  693. }
  694. else {
  695. // If a datepicker and timepicker are writing to the same object and the user enters an
  696. // invalid time into the timepicker, we may end up clearing their selection from the
  697. // datepicker. If the user enters a valid time afterwards, the datepicker's selection will
  698. // have been lost. This logic restores the previously-valid date and sets its time to
  699. // the newly-selected time.
  700. const adapter = this._dateAdapter;
  701. const target = adapter.getValidDateOrNull(this._lastValidDate || this.value());
  702. const hours = adapter.getHours(selection);
  703. const minutes = adapter.getMinutes(selection);
  704. const seconds = adapter.getSeconds(selection);
  705. this.value.set(target ? adapter.setTime(target, hours, minutes, seconds) : selection);
  706. }
  707. if (propagateToAccessor) {
  708. this._onChange?.(this.value());
  709. }
  710. }
  711. /** Formats the current value and assigns it to the input. */
  712. _formatValue(value) {
  713. value = this._dateAdapter.getValidDateOrNull(value);
  714. this._elementRef.nativeElement.value =
  715. value == null ? '' : this._dateAdapter.format(value, this._dateFormats.display.timeInput);
  716. }
  717. /** Checks whether a value is valid. */
  718. _isValid(value) {
  719. return !value || this._dateAdapter.isValid(value);
  720. }
  721. /** Transforms an arbitrary value into a value that can be assigned to a date-based input. */
  722. _transformDateInput(value) {
  723. const date = typeof value === 'string'
  724. ? this._dateAdapter.parseTime(value, this._dateFormats.parse.timeInput)
  725. : this._dateAdapter.deserialize(value);
  726. return date && this._dateAdapter.isValid(date) ? date : null;
  727. }
  728. /** Whether the input is currently focused. */
  729. _hasFocus() {
  730. return _getFocusedElementPierceShadowDom() === this._elementRef.nativeElement;
  731. }
  732. /** Gets a function that can be used to validate the input. */
  733. _getValidator() {
  734. return Validators.compose([
  735. () => this._lastValueValid
  736. ? null
  737. : { 'matTimepickerParse': { 'text': this._elementRef.nativeElement.value } },
  738. control => {
  739. const controlValue = this._dateAdapter.getValidDateOrNull(this._dateAdapter.deserialize(control.value));
  740. const min = this.min();
  741. return !min || !controlValue || this._dateAdapter.compareTime(min, controlValue) <= 0
  742. ? null
  743. : { 'matTimepickerMin': { 'min': min, 'actual': controlValue } };
  744. },
  745. control => {
  746. const controlValue = this._dateAdapter.getValidDateOrNull(this._dateAdapter.deserialize(control.value));
  747. const max = this.max();
  748. return !max || !controlValue || this._dateAdapter.compareTime(max, controlValue) >= 0
  749. ? null
  750. : { 'matTimepickerMax': { 'max': max, 'actual': controlValue } };
  751. },
  752. ]);
  753. }
  754. static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: MatTimepickerInput, deps: [], target: i0.ɵɵFactoryTarget.Directive });
  755. static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "19.2.6", type: MatTimepickerInput, isStandalone: true, selector: "input[matTimepicker]", inputs: { value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, timepicker: { classPropertyName: "timepicker", publicName: "matTimepicker", isSignal: true, isRequired: true, transformFunction: null }, min: { classPropertyName: "min", publicName: "matTimepickerMin", isSignal: true, isRequired: false, transformFunction: null }, max: { classPropertyName: "max", publicName: "matTimepickerMax", isSignal: true, isRequired: false, transformFunction: null }, disabledInput: { classPropertyName: "disabledInput", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { value: "valueChange" }, host: { attributes: { "role": "combobox", "type": "text", "aria-haspopup": "listbox" }, listeners: { "blur": "_handleBlur()", "input": "_handleInput($event.target.value)", "keydown": "_handleKeydown($event)" }, properties: { "attr.aria-activedescendant": "_ariaActiveDescendant()", "attr.aria-expanded": "_ariaExpanded()", "attr.aria-controls": "_ariaControls()", "attr.mat-timepicker-id": "timepicker()?.panelId", "disabled": "disabled()" }, classAttribute: "mat-timepicker-input" }, providers: [
  756. {
  757. provide: NG_VALUE_ACCESSOR,
  758. useExisting: MatTimepickerInput,
  759. multi: true,
  760. },
  761. {
  762. provide: NG_VALIDATORS,
  763. useExisting: MatTimepickerInput,
  764. multi: true,
  765. },
  766. {
  767. provide: MAT_INPUT_VALUE_ACCESSOR,
  768. useExisting: MatTimepickerInput,
  769. },
  770. ], exportAs: ["matTimepickerInput"], ngImport: i0 });
  771. }
  772. i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: MatTimepickerInput, decorators: [{
  773. type: Directive,
  774. args: [{
  775. selector: 'input[matTimepicker]',
  776. exportAs: 'matTimepickerInput',
  777. host: {
  778. 'class': 'mat-timepicker-input',
  779. 'role': 'combobox',
  780. 'type': 'text',
  781. 'aria-haspopup': 'listbox',
  782. '[attr.aria-activedescendant]': '_ariaActiveDescendant()',
  783. '[attr.aria-expanded]': '_ariaExpanded()',
  784. '[attr.aria-controls]': '_ariaControls()',
  785. '[attr.mat-timepicker-id]': 'timepicker()?.panelId',
  786. '[disabled]': 'disabled()',
  787. '(blur)': '_handleBlur()',
  788. '(input)': '_handleInput($event.target.value)',
  789. '(keydown)': '_handleKeydown($event)',
  790. },
  791. providers: [
  792. {
  793. provide: NG_VALUE_ACCESSOR,
  794. useExisting: MatTimepickerInput,
  795. multi: true,
  796. },
  797. {
  798. provide: NG_VALIDATORS,
  799. useExisting: MatTimepickerInput,
  800. multi: true,
  801. },
  802. {
  803. provide: MAT_INPUT_VALUE_ACCESSOR,
  804. useExisting: MatTimepickerInput,
  805. },
  806. ],
  807. }]
  808. }], ctorParameters: () => [] });
  809. /** Button that can be used to open a `mat-timepicker`. */
  810. class MatTimepickerToggle {
  811. _defaultConfig = inject(MAT_TIMEPICKER_CONFIG, { optional: true });
  812. _defaultTabIndex = (() => {
  813. const value = inject(new HostAttributeToken('tabindex'), { optional: true });
  814. const parsed = Number(value);
  815. return isNaN(parsed) ? null : parsed;
  816. })();
  817. _isDisabled = computed(() => {
  818. const timepicker = this.timepicker();
  819. return this.disabled() || timepicker.disabled();
  820. });
  821. /** Timepicker instance that the button will toggle. */
  822. timepicker = input.required({
  823. alias: 'for',
  824. });
  825. /** Screen-reader label for the button. */
  826. ariaLabel = input(undefined, {
  827. alias: 'aria-label',
  828. });
  829. /** Screen-reader labelled by id for the button. */
  830. ariaLabelledby = input(undefined, {
  831. alias: 'aria-labelledby',
  832. });
  833. /** Default aria-label for the toggle if none is provided. */
  834. _defaultAriaLabel = 'Open timepicker options';
  835. /** Whether the toggle button is disabled. */
  836. disabled = input(false, {
  837. transform: booleanAttribute,
  838. alias: 'disabled',
  839. });
  840. /** Tabindex for the toggle. */
  841. tabIndex = input(this._defaultTabIndex);
  842. /** Whether ripples on the toggle should be disabled. */
  843. disableRipple = input(this._defaultConfig?.disableRipple ?? false, { transform: booleanAttribute });
  844. /** Opens the connected timepicker. */
  845. _open(event) {
  846. if (this.timepicker() && !this._isDisabled()) {
  847. this.timepicker().open();
  848. event.stopPropagation();
  849. }
  850. }
  851. /**
  852. * Checks for ariaLabelledby and if empty uses custom
  853. * aria-label or defaultAriaLabel if neither is provided.
  854. */
  855. getAriaLabel() {
  856. return this.ariaLabelledby() ? null : this.ariaLabel() || this._defaultAriaLabel;
  857. }
  858. static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: MatTimepickerToggle, deps: [], target: i0.ɵɵFactoryTarget.Component });
  859. static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "19.2.6", type: MatTimepickerToggle, isStandalone: true, selector: "mat-timepicker-toggle", inputs: { timepicker: { classPropertyName: "timepicker", publicName: "for", isSignal: true, isRequired: true, transformFunction: null }, ariaLabel: { classPropertyName: "ariaLabel", publicName: "aria-label", isSignal: true, isRequired: false, transformFunction: null }, ariaLabelledby: { classPropertyName: "ariaLabelledby", publicName: "aria-labelledby", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, tabIndex: { classPropertyName: "tabIndex", publicName: "tabIndex", isSignal: true, isRequired: false, transformFunction: null }, disableRipple: { classPropertyName: "disableRipple", publicName: "disableRipple", isSignal: true, isRequired: false, transformFunction: null } }, host: { listeners: { "click": "_open($event)" }, properties: { "attr.tabindex": "null" }, classAttribute: "mat-timepicker-toggle" }, exportAs: ["matTimepickerToggle"], ngImport: i0, template: "<button\n mat-icon-button\n type=\"button\"\n aria-haspopup=\"listbox\"\n [attr.aria-label]=\"getAriaLabel()\"\n [attr.aria-labelledby]=\"ariaLabelledby()\"\n [attr.aria-expanded]=\"timepicker().isOpen()\"\n [attr.tabindex]=\"_isDisabled() ? -1 : tabIndex()\"\n [disabled]=\"_isDisabled()\"\n [disableRipple]=\"disableRipple()\">\n\n <ng-content select=\"[matTimepickerToggleIcon]\">\n <svg\n class=\"mat-timepicker-toggle-default-icon\"\n height=\"24px\"\n width=\"24px\"\n viewBox=\"0 -960 960 960\"\n fill=\"currentColor\"\n focusable=\"false\"\n aria-hidden=\"true\">\n <path d=\"m612-292 56-56-148-148v-184h-80v216l172 172ZM480-80q-83 0-156-31.5T197-197q-54-54-85.5-127T80-480q0-83 31.5-156T197-763q54-54 127-85.5T480-880q83 0 156 31.5T763-763q54 54 85.5 127T880-480q0 83-31.5 156T763-197q-54 54-127 85.5T480-80Zm0-400Zm0 320q133 0 226.5-93.5T800-480q0-133-93.5-226.5T480-800q-133 0-226.5 93.5T160-480q0 133 93.5 226.5T480-160Z\"/>\n </svg>\n </ng-content>\n</button>\n", dependencies: [{ kind: "component", type: MatIconButton, selector: "button[mat-icon-button]", exportAs: ["matButton"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
  860. }
  861. i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: MatTimepickerToggle, decorators: [{
  862. type: Component,
  863. args: [{ selector: 'mat-timepicker-toggle', host: {
  864. 'class': 'mat-timepicker-toggle',
  865. '[attr.tabindex]': 'null',
  866. // Bind the `click` on the host, rather than the inner `button`, so that we can call
  867. // `stopPropagation` on it without affecting the user's `click` handlers. We need to stop
  868. // it so that the input doesn't get focused automatically by the form field (See #21836).
  869. '(click)': '_open($event)',
  870. }, exportAs: 'matTimepickerToggle', encapsulation: ViewEncapsulation.None, changeDetection: ChangeDetectionStrategy.OnPush, imports: [MatIconButton], template: "<button\n mat-icon-button\n type=\"button\"\n aria-haspopup=\"listbox\"\n [attr.aria-label]=\"getAriaLabel()\"\n [attr.aria-labelledby]=\"ariaLabelledby()\"\n [attr.aria-expanded]=\"timepicker().isOpen()\"\n [attr.tabindex]=\"_isDisabled() ? -1 : tabIndex()\"\n [disabled]=\"_isDisabled()\"\n [disableRipple]=\"disableRipple()\">\n\n <ng-content select=\"[matTimepickerToggleIcon]\">\n <svg\n class=\"mat-timepicker-toggle-default-icon\"\n height=\"24px\"\n width=\"24px\"\n viewBox=\"0 -960 960 960\"\n fill=\"currentColor\"\n focusable=\"false\"\n aria-hidden=\"true\">\n <path d=\"m612-292 56-56-148-148v-184h-80v216l172 172ZM480-80q-83 0-156-31.5T197-197q-54-54-85.5-127T80-480q0-83 31.5-156T197-763q54-54 127-85.5T480-880q83 0 156 31.5T763-763q54 54 85.5 127T880-480q0 83-31.5 156T763-197q-54 54-127 85.5T480-80Zm0-400Zm0 320q133 0 226.5-93.5T800-480q0-133-93.5-226.5T480-800q-133 0-226.5 93.5T160-480q0 133 93.5 226.5T480-160Z\"/>\n </svg>\n </ng-content>\n</button>\n" }]
  871. }] });
  872. class MatTimepickerModule {
  873. static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: MatTimepickerModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule });
  874. static ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "19.2.6", ngImport: i0, type: MatTimepickerModule, imports: [MatTimepicker, MatTimepickerInput, MatTimepickerToggle], exports: [CdkScrollableModule, MatTimepicker, MatTimepickerInput, MatTimepickerToggle] });
  875. static ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: MatTimepickerModule, imports: [MatTimepicker, MatTimepickerToggle, CdkScrollableModule] });
  876. }
  877. i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: MatTimepickerModule, decorators: [{
  878. type: NgModule,
  879. args: [{
  880. imports: [MatTimepicker, MatTimepickerInput, MatTimepickerToggle],
  881. exports: [CdkScrollableModule, MatTimepicker, MatTimepickerInput, MatTimepickerToggle],
  882. }]
  883. }] });
  884. export { MAT_TIMEPICKER_CONFIG, MAT_TIMEPICKER_SCROLL_STRATEGY, MatTimepicker, MatTimepickerInput, MatTimepickerModule, MatTimepickerToggle };
  885. //# sourceMappingURL=timepicker.mjs.map