123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891 |
- import * as i0 from '@angular/core';
- 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';
- import { Directionality } from '@angular/cdk/bidi';
- import { Overlay } from '@angular/cdk/overlay';
- import { TemplatePortal } from '@angular/cdk/portal';
- import { _getEventTarget, _getFocusedElementPierceShadowDom } from '@angular/cdk/platform';
- import { TAB, ESCAPE, hasModifierKey, ENTER, DOWN_ARROW, UP_ARROW } from '@angular/cdk/keycodes';
- import { ActiveDescendantKeyManager, _IdGenerator } from '@angular/cdk/a11y';
- import { D as DateAdapter, a as MAT_DATE_FORMATS } from './date-formats-K6TQue-Y.mjs';
- import { M as MatOption, c as MAT_OPTION_PARENT_COMPONENT } from './option-ChV6uQgD.mjs';
- import { Validators, NG_VALUE_ACCESSOR, NG_VALIDATORS } from '@angular/forms';
- import { h as MAT_FORM_FIELD } from './form-field-DqPi4knt.mjs';
- import { M as MAT_INPUT_VALUE_ACCESSOR } from './input-value-accessor-D1GvPuqO.mjs';
- import { M as MatIconButton } from './icon-button-D1J0zeqv.mjs';
- import { CdkScrollableModule } from '@angular/cdk/scrolling';
- import 'rxjs';
- import './ripple-BT3tzh6F.mjs';
- import '@angular/cdk/coercion';
- import '@angular/cdk/private';
- import './pseudo-checkbox-CJ7seqQH.mjs';
- import './structural-styles-BQUT6wsL.mjs';
- import '@angular/common';
- import 'rxjs/operators';
- import '@angular/cdk/observers/private';
- import './ripple-loader-Ce3DAhPW.mjs';
- /** Pattern that interval strings have to match. */
- const INTERVAL_PATTERN = /^(\d*\.?\d+)\s*(h|hour|hours|m|min|minute|minutes|s|second|seconds)?$/i;
- /**
- * Injection token that can be used to configure the default options for the timepicker component.
- */
- const MAT_TIMEPICKER_CONFIG = new InjectionToken('MAT_TIMEPICKER_CONFIG');
- /** Parses an interval value into seconds. */
- function parseInterval(value) {
- let result;
- if (value === null) {
- return null;
- }
- else if (typeof value === 'number') {
- result = value;
- }
- else {
- if (value.trim().length === 0) {
- return null;
- }
- const parsed = value.match(INTERVAL_PATTERN);
- const amount = parsed ? parseFloat(parsed[1]) : null;
- const unit = parsed?.[2]?.toLowerCase() || null;
- if (!parsed || amount === null || isNaN(amount)) {
- return null;
- }
- if (unit === 'h' || unit === 'hour' || unit === 'hours') {
- result = amount * 3600;
- }
- else if (unit === 'm' || unit === 'min' || unit === 'minute' || unit === 'minutes') {
- result = amount * 60;
- }
- else {
- result = amount;
- }
- }
- return result;
- }
- /**
- * Generates the options to show in a timepicker.
- * @param adapter Date adapter to be used to generate the options.
- * @param formats Formatting config to use when displaying the options.
- * @param min Time from which to start generating the options.
- * @param max Time at which to stop generating the options.
- * @param interval Amount of seconds between each option.
- */
- function generateOptions(adapter, formats, min, max, interval) {
- const options = [];
- let current = adapter.compareTime(min, max) < 1 ? min : max;
- while (adapter.sameDate(current, min) &&
- adapter.compareTime(current, max) < 1 &&
- adapter.isValid(current)) {
- options.push({ value: current, label: adapter.format(current, formats.display.timeOptionLabel) });
- current = adapter.addSeconds(current, interval);
- }
- return options;
- }
- /** Checks whether a date adapter is set up correctly for use with the timepicker. */
- function validateAdapter(adapter, formats) {
- function missingAdapterError(provider) {
- return Error(`MatTimepicker: No provider found for ${provider}. You must add one of the following ` +
- `to your app config: provideNativeDateAdapter, provideDateFnsAdapter, ` +
- `provideLuxonDateAdapter, provideMomentDateAdapter, or provide a custom implementation.`);
- }
- if (!adapter) {
- throw missingAdapterError('DateAdapter');
- }
- if (!formats) {
- throw missingAdapterError('MAT_DATE_FORMATS');
- }
- if (formats.display.timeInput === undefined ||
- formats.display.timeOptionLabel === undefined ||
- formats.parse.timeInput === undefined) {
- throw new Error('MatTimepicker: Incomplete `MAT_DATE_FORMATS` has been provided. ' +
- '`MAT_DATE_FORMATS` must provide `display.timeInput`, `display.timeOptionLabel` ' +
- 'and `parse.timeInput` formats in order to be compatible with MatTimepicker.');
- }
- }
- /** Injection token used to configure the behavior of the timepicker dropdown while scrolling. */
- const MAT_TIMEPICKER_SCROLL_STRATEGY = new InjectionToken('MAT_TIMEPICKER_SCROLL_STRATEGY', {
- providedIn: 'root',
- factory: () => {
- const overlay = inject(Overlay);
- return () => overlay.scrollStrategies.reposition();
- },
- });
- /**
- * Renders out a listbox that can be used to select a time of day.
- * Intended to be used together with `MatTimepickerInput`.
- */
- class MatTimepicker {
- _overlay = inject(Overlay);
- _dir = inject(Directionality, { optional: true });
- _viewContainerRef = inject(ViewContainerRef);
- _injector = inject(Injector);
- _defaultConfig = inject(MAT_TIMEPICKER_CONFIG, { optional: true });
- _dateAdapter = inject(DateAdapter, { optional: true });
- _dateFormats = inject(MAT_DATE_FORMATS, { optional: true });
- _scrollStrategyFactory = inject(MAT_TIMEPICKER_SCROLL_STRATEGY);
- _animationsDisabled = inject(ANIMATION_MODULE_TYPE, { optional: true }) === 'NoopAnimations';
- _isOpen = signal(false);
- _activeDescendant = signal(null);
- _input = signal(null);
- _overlayRef = null;
- _portal = null;
- _optionsCacheKey = null;
- _localeChanges;
- _onOpenRender = null;
- _panelTemplate = viewChild.required('panelTemplate');
- _timeOptions = [];
- _options = viewChildren(MatOption);
- _keyManager = new ActiveDescendantKeyManager(this._options, this._injector)
- .withHomeAndEnd(true)
- .withPageUpDown(true)
- .withVerticalOrientation(true);
- /**
- * Interval between each option in the timepicker. The value can either be an amount of
- * seconds (e.g. 90) or a number with a unit (e.g. 45m). Supported units are `s` for seconds,
- * `m` for minutes or `h` for hours.
- */
- interval = input(parseInterval(this._defaultConfig?.interval || null), { transform: parseInterval });
- /**
- * Array of pre-defined options that the user can select from, as an alternative to using the
- * `interval` input. An error will be thrown if both `options` and `interval` are specified.
- */
- options = input(null);
- /** Whether the timepicker is open. */
- isOpen = this._isOpen.asReadonly();
- /** Emits when the user selects a time. */
- selected = output();
- /** Emits when the timepicker is opened. */
- opened = output();
- /** Emits when the timepicker is closed. */
- closed = output();
- /** ID of the active descendant option. */
- activeDescendant = this._activeDescendant.asReadonly();
- /** Unique ID of the timepicker's panel */
- panelId = inject(_IdGenerator).getId('mat-timepicker-panel-');
- /** Whether ripples within the timepicker should be disabled. */
- disableRipple = input(this._defaultConfig?.disableRipple ?? false, {
- transform: booleanAttribute,
- });
- /** ARIA label for the timepicker panel. */
- ariaLabel = input(null, {
- alias: 'aria-label',
- });
- /** ID of the label element for the timepicker panel. */
- ariaLabelledby = input(null, {
- alias: 'aria-labelledby',
- });
- /** Whether the timepicker is currently disabled. */
- disabled = computed(() => !!this._input()?.disabled());
- constructor() {
- if (typeof ngDevMode === 'undefined' || ngDevMode) {
- validateAdapter(this._dateAdapter, this._dateFormats);
- effect(() => {
- const options = this.options();
- const interval = this.interval();
- if (options !== null && interval !== null) {
- throw new Error('Cannot specify both the `options` and `interval` inputs at the same time');
- }
- else if (options?.length === 0) {
- throw new Error('Value of `options` input cannot be an empty array');
- }
- });
- }
- // Since the panel ID is static, we can set it once without having to maintain a host binding.
- const element = inject(ElementRef);
- element.nativeElement.setAttribute('mat-timepicker-panel-id', this.panelId);
- this._handleLocaleChanges();
- this._handleInputStateChanges();
- this._keyManager.change.subscribe(() => this._activeDescendant.set(this._keyManager.activeItem?.id || null));
- }
- /** Opens the timepicker. */
- open() {
- const input = this._input();
- if (!input) {
- return;
- }
- // Focus should already be on the input, but this call is in case the timepicker is opened
- // programmatically. We need to call this even if the timepicker is already open, because
- // the user might be clicking the toggle.
- input.focus();
- if (this._isOpen()) {
- return;
- }
- this._isOpen.set(true);
- this._generateOptions();
- const overlayRef = this._getOverlayRef();
- overlayRef.updateSize({ width: input.getOverlayOrigin().nativeElement.offsetWidth });
- this._portal ??= new TemplatePortal(this._panelTemplate(), this._viewContainerRef);
- // We need to check this in case `isOpen` was flipped, but change detection hasn't
- // had a chance to run yet. See https://github.com/angular/components/issues/30637
- if (!overlayRef.hasAttached()) {
- overlayRef.attach(this._portal);
- }
- this._onOpenRender?.destroy();
- this._onOpenRender = afterNextRender(() => {
- const options = this._options();
- this._syncSelectedState(input.value(), options, options[0]);
- this._onOpenRender = null;
- }, { injector: this._injector });
- this.opened.emit();
- }
- /** Closes the timepicker. */
- close() {
- if (this._isOpen()) {
- this._isOpen.set(false);
- this.closed.emit();
- if (this._animationsDisabled) {
- this._overlayRef?.detach();
- }
- }
- }
- /** Registers an input with the timepicker. */
- registerInput(input) {
- const currentInput = this._input();
- if (currentInput && input !== currentInput && (typeof ngDevMode === 'undefined' || ngDevMode)) {
- throw new Error('MatTimepicker can only be registered with one input at a time');
- }
- this._input.set(input);
- }
- ngOnDestroy() {
- this._keyManager.destroy();
- this._localeChanges.unsubscribe();
- this._onOpenRender?.destroy();
- this._overlayRef?.dispose();
- }
- /** Selects a specific time value. */
- _selectValue(option) {
- this.close();
- this._keyManager.setActiveItem(option);
- this._options().forEach(current => {
- // This is primarily here so we don't show two selected options while animating away.
- if (current !== option) {
- current.deselect(false);
- }
- });
- this.selected.emit({ value: option.value, source: this });
- this._input()?.focus();
- }
- /** Gets the value of the `aria-labelledby` attribute. */
- _getAriaLabelledby() {
- if (this.ariaLabel()) {
- return null;
- }
- return this.ariaLabelledby() || this._input()?._getLabelId() || null;
- }
- /** Handles animation events coming from the panel. */
- _handleAnimationEnd(event) {
- if (event.animationName === '_mat-timepicker-exit') {
- this._overlayRef?.detach();
- }
- }
- /** Creates an overlay reference for the timepicker panel. */
- _getOverlayRef() {
- if (this._overlayRef) {
- return this._overlayRef;
- }
- const positionStrategy = this._overlay
- .position()
- .flexibleConnectedTo(this._input().getOverlayOrigin())
- .withFlexibleDimensions(false)
- .withPush(false)
- .withTransformOriginOn('.mat-timepicker-panel')
- .withPositions([
- {
- originX: 'start',
- originY: 'bottom',
- overlayX: 'start',
- overlayY: 'top',
- },
- {
- originX: 'start',
- originY: 'top',
- overlayX: 'start',
- overlayY: 'bottom',
- panelClass: 'mat-timepicker-above',
- },
- ]);
- this._overlayRef = this._overlay.create({
- positionStrategy,
- scrollStrategy: this._scrollStrategyFactory(),
- direction: this._dir || 'ltr',
- hasBackdrop: false,
- });
- this._overlayRef.detachments().subscribe(() => this.close());
- this._overlayRef.keydownEvents().subscribe(event => this._handleKeydown(event));
- this._overlayRef.outsidePointerEvents().subscribe(event => {
- const target = _getEventTarget(event);
- const origin = this._input()?.getOverlayOrigin().nativeElement;
- if (target && origin && target !== origin && !origin.contains(target)) {
- this.close();
- }
- });
- return this._overlayRef;
- }
- /** Generates the list of options from which the user can select.. */
- _generateOptions() {
- // Default the interval to 30 minutes.
- const interval = this.interval() ?? 30 * 60;
- const options = this.options();
- if (options !== null) {
- this._timeOptions = options;
- }
- else {
- const input = this._input();
- const adapter = this._dateAdapter;
- const timeFormat = this._dateFormats.display.timeInput;
- const min = input?.min() || adapter.setTime(adapter.today(), 0, 0, 0);
- const max = input?.max() || adapter.setTime(adapter.today(), 23, 59, 0);
- const cacheKey = interval + '/' + adapter.format(min, timeFormat) + '/' + adapter.format(max, timeFormat);
- // Don't re-generate the options if the inputs haven't changed.
- if (cacheKey !== this._optionsCacheKey) {
- this._optionsCacheKey = cacheKey;
- this._timeOptions = generateOptions(adapter, this._dateFormats, min, max, interval);
- }
- }
- }
- /**
- * Synchronizes the internal state of the component based on a specific selected date.
- * @param value Currently selected date.
- * @param options Options rendered out in the timepicker.
- * @param fallback Option to set as active if no option is selected.
- */
- _syncSelectedState(value, options, fallback) {
- let hasSelected = false;
- for (const option of options) {
- if (value && this._dateAdapter.sameTime(option.value, value)) {
- option.select(false);
- scrollOptionIntoView(option, 'center');
- untracked(() => this._keyManager.setActiveItem(option));
- hasSelected = true;
- }
- else {
- option.deselect(false);
- }
- }
- // If no option was selected, we need to reset the key manager since
- // it might be holding onto an option that no longer exists.
- if (!hasSelected) {
- if (fallback) {
- untracked(() => this._keyManager.setActiveItem(fallback));
- scrollOptionIntoView(fallback, 'center');
- }
- else {
- untracked(() => this._keyManager.setActiveItem(-1));
- }
- }
- }
- /** Handles keyboard events while the overlay is open. */
- _handleKeydown(event) {
- const keyCode = event.keyCode;
- if (keyCode === TAB) {
- this.close();
- }
- else if (keyCode === ESCAPE && !hasModifierKey(event)) {
- event.preventDefault();
- this.close();
- }
- else if (keyCode === ENTER) {
- event.preventDefault();
- if (this._keyManager.activeItem) {
- this._selectValue(this._keyManager.activeItem);
- }
- else {
- this.close();
- }
- }
- else {
- const previousActive = this._keyManager.activeItem;
- this._keyManager.onKeydown(event);
- const currentActive = this._keyManager.activeItem;
- if (currentActive && currentActive !== previousActive) {
- scrollOptionIntoView(currentActive, 'nearest');
- }
- }
- }
- /** Sets up the logic that updates the timepicker when the locale changes. */
- _handleLocaleChanges() {
- // Re-generate the options list if the locale changes.
- this._localeChanges = this._dateAdapter.localeChanges.subscribe(() => {
- this._optionsCacheKey = null;
- if (this.isOpen()) {
- this._generateOptions();
- }
- });
- }
- /**
- * Sets up the logic that updates the timepicker when the state of the connected input changes.
- */
- _handleInputStateChanges() {
- effect(() => {
- const input = this._input();
- const options = this._options();
- if (this._isOpen() && input) {
- this._syncSelectedState(input.value(), options, null);
- }
- });
- }
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: MatTimepicker, deps: [], target: i0.ɵɵFactoryTarget.Component });
- 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: [
- {
- provide: MAT_OPTION_PARENT_COMPONENT,
- useExisting: MatTimepicker,
- },
- ], 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 });
- }
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: MatTimepicker, decorators: [{
- type: Component,
- args: [{ selector: 'mat-timepicker', exportAs: 'matTimepicker', changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, imports: [MatOption], providers: [
- {
- provide: MAT_OPTION_PARENT_COMPONENT,
- useExisting: MatTimepicker,
- },
- ], 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"] }]
- }], ctorParameters: () => [] });
- /**
- * Scrolls an option into view.
- * @param option Option to be scrolled into view.
- * @param position Position to which to align the option relative to the scrollable container.
- */
- function scrollOptionIntoView(option, position) {
- option._getHostElement().scrollIntoView({ block: position, inline: position });
- }
- /**
- * Input that can be used to enter time and connect to a `mat-timepicker`.
- */
- class MatTimepickerInput {
- _elementRef = inject(ElementRef);
- _dateAdapter = inject(DateAdapter, { optional: true });
- _dateFormats = inject(MAT_DATE_FORMATS, { optional: true });
- _formField = inject(MAT_FORM_FIELD, { optional: true });
- _onChange;
- _onTouched;
- _validatorOnChange;
- _cleanupClick;
- _accessorDisabled = signal(false);
- _localeSubscription;
- _timepickerSubscription;
- _validator;
- _lastValueValid = true;
- _lastValidDate = null;
- /** Value of the `aria-activedescendant` attribute. */
- _ariaActiveDescendant = computed(() => {
- const timepicker = this.timepicker();
- const isOpen = timepicker.isOpen();
- const activeDescendant = timepicker.activeDescendant();
- return isOpen && activeDescendant ? activeDescendant : null;
- });
- /** Value of the `aria-expanded` attribute. */
- _ariaExpanded = computed(() => this.timepicker().isOpen() + '');
- /** Value of the `aria-controls` attribute. */
- _ariaControls = computed(() => {
- const timepicker = this.timepicker();
- return timepicker.isOpen() ? timepicker.panelId : null;
- });
- /** Current value of the input. */
- value = model(null);
- /** Timepicker that the input is associated with. */
- timepicker = input.required({
- alias: 'matTimepicker',
- });
- /**
- * Minimum time that can be selected or typed in. Can be either
- * a date object (only time will be used) or a valid time string.
- */
- min = input(null, {
- alias: 'matTimepickerMin',
- transform: (value) => this._transformDateInput(value),
- });
- /**
- * Maximum time that can be selected or typed in. Can be either
- * a date object (only time will be used) or a valid time string.
- */
- max = input(null, {
- alias: 'matTimepickerMax',
- transform: (value) => this._transformDateInput(value),
- });
- /** Whether the input is disabled. */
- disabled = computed(() => this.disabledInput() || this._accessorDisabled());
- /**
- * Whether the input should be disabled through the template.
- * @docs-private
- */
- disabledInput = input(false, {
- transform: booleanAttribute,
- alias: 'disabled',
- });
- constructor() {
- if (typeof ngDevMode === 'undefined' || ngDevMode) {
- validateAdapter(this._dateAdapter, this._dateFormats);
- }
- const renderer = inject(Renderer2);
- this._validator = this._getValidator();
- this._respondToValueChanges();
- this._respondToMinMaxChanges();
- this._registerTimepicker();
- this._localeSubscription = this._dateAdapter.localeChanges.subscribe(() => {
- if (!this._hasFocus()) {
- this._formatValue(this.value());
- }
- });
- // Bind the click listener manually to the overlay origin, because we want the entire
- // form field to be clickable, if the timepicker is used in `mat-form-field`.
- this._cleanupClick = renderer.listen(this.getOverlayOrigin().nativeElement, 'click', this._handleClick);
- }
- /**
- * Implemented as a part of `ControlValueAccessor`.
- * @docs-private
- */
- writeValue(value) {
- // Note that we need to deserialize here, rather than depend on the value change effect,
- // because `getValidDateOrNull` will clobber the value if it's parseable, but not created by
- // the current adapter (see #30140).
- const deserialized = this._dateAdapter.deserialize(value);
- this.value.set(this._dateAdapter.getValidDateOrNull(deserialized));
- }
- /**
- * Implemented as a part of `ControlValueAccessor`.
- * @docs-private
- */
- registerOnChange(fn) {
- this._onChange = fn;
- }
- /**
- * Implemented as a part of `ControlValueAccessor`.
- * @docs-private
- */
- registerOnTouched(fn) {
- this._onTouched = fn;
- }
- /**
- * Implemented as a part of `ControlValueAccessor`.
- * @docs-private
- */
- setDisabledState(isDisabled) {
- this._accessorDisabled.set(isDisabled);
- }
- /**
- * Implemented as a part of `Validator`.
- * @docs-private
- */
- validate(control) {
- return this._validator(control);
- }
- /**
- * Implemented as a part of `Validator`.
- * @docs-private
- */
- registerOnValidatorChange(fn) {
- this._validatorOnChange = fn;
- }
- /** Gets the element to which the timepicker popup should be attached. */
- getOverlayOrigin() {
- return this._formField?.getConnectedOverlayOrigin() || this._elementRef;
- }
- /** Focuses the input. */
- focus() {
- this._elementRef.nativeElement.focus();
- }
- ngOnDestroy() {
- this._cleanupClick();
- this._timepickerSubscription?.unsubscribe();
- this._localeSubscription.unsubscribe();
- }
- /** Gets the ID of the input's label. */
- _getLabelId() {
- return this._formField?.getLabelId() || null;
- }
- /** Handles clicks on the input or the containing form field. */
- _handleClick = () => {
- if (!this.disabled()) {
- this.timepicker().open();
- }
- };
- /** Handles the `input` event. */
- _handleInput(value) {
- const currentValue = this.value();
- const date = this._dateAdapter.parseTime(value, this._dateFormats.parse.timeInput);
- const hasChanged = !this._dateAdapter.sameTime(date, currentValue);
- if (!date || hasChanged || !!(value && !currentValue)) {
- // We need to fire the CVA change event for all nulls, otherwise the validators won't run.
- this._assignUserSelection(date, true);
- }
- else {
- // Call the validator even if the value hasn't changed since
- // some fields change depending on what the user has entered.
- this._validatorOnChange?.();
- }
- }
- /** Handles the `blur` event. */
- _handleBlur() {
- const value = this.value();
- // Only reformat on blur so the value doesn't change while the user is interacting.
- if (value && this._isValid(value)) {
- this._formatValue(value);
- }
- if (!this.timepicker().isOpen()) {
- this._onTouched?.();
- }
- }
- /** Handles the `keydown` event. */
- _handleKeydown(event) {
- // All keyboard events while open are handled through the timepicker.
- if (this.timepicker().isOpen() || this.disabled()) {
- return;
- }
- if (event.keyCode === ESCAPE && !hasModifierKey(event) && this.value() !== null) {
- event.preventDefault();
- this.value.set(null);
- this._formatValue(null);
- }
- else if (event.keyCode === DOWN_ARROW || event.keyCode === UP_ARROW) {
- event.preventDefault();
- this.timepicker().open();
- }
- }
- /** Sets up the code that watches for changes in the value and adjusts the input. */
- _respondToValueChanges() {
- effect(() => {
- const value = this._dateAdapter.deserialize(this.value());
- const wasValid = this._lastValueValid;
- this._lastValueValid = this._isValid(value);
- // Reformat the value if it changes while the user isn't interacting.
- if (!this._hasFocus()) {
- this._formatValue(value);
- }
- if (value && this._lastValueValid) {
- this._lastValidDate = value;
- }
- // Trigger the validator if the state changed.
- if (wasValid !== this._lastValueValid) {
- this._validatorOnChange?.();
- }
- });
- }
- /** Sets up the logic that registers the input with the timepicker. */
- _registerTimepicker() {
- effect(() => {
- const timepicker = this.timepicker();
- timepicker.registerInput(this);
- timepicker.closed.subscribe(() => this._onTouched?.());
- timepicker.selected.subscribe(({ value }) => {
- if (!this._dateAdapter.sameTime(value, this.value())) {
- this._assignUserSelection(value, true);
- this._formatValue(value);
- }
- });
- });
- }
- /** Sets up the logic that adjusts the input if the min/max changes. */
- _respondToMinMaxChanges() {
- effect(() => {
- // Read the min/max so the effect knows when to fire.
- this.min();
- this.max();
- this._validatorOnChange?.();
- });
- }
- /**
- * Assigns a value set by the user to the input's model.
- * @param selection Time selected by the user that should be assigned.
- * @param propagateToAccessor Whether the value should be propagated to the ControlValueAccessor.
- */
- _assignUserSelection(selection, propagateToAccessor) {
- if (selection == null || !this._isValid(selection)) {
- this.value.set(selection);
- }
- else {
- // If a datepicker and timepicker are writing to the same object and the user enters an
- // invalid time into the timepicker, we may end up clearing their selection from the
- // datepicker. If the user enters a valid time afterwards, the datepicker's selection will
- // have been lost. This logic restores the previously-valid date and sets its time to
- // the newly-selected time.
- const adapter = this._dateAdapter;
- const target = adapter.getValidDateOrNull(this._lastValidDate || this.value());
- const hours = adapter.getHours(selection);
- const minutes = adapter.getMinutes(selection);
- const seconds = adapter.getSeconds(selection);
- this.value.set(target ? adapter.setTime(target, hours, minutes, seconds) : selection);
- }
- if (propagateToAccessor) {
- this._onChange?.(this.value());
- }
- }
- /** Formats the current value and assigns it to the input. */
- _formatValue(value) {
- value = this._dateAdapter.getValidDateOrNull(value);
- this._elementRef.nativeElement.value =
- value == null ? '' : this._dateAdapter.format(value, this._dateFormats.display.timeInput);
- }
- /** Checks whether a value is valid. */
- _isValid(value) {
- return !value || this._dateAdapter.isValid(value);
- }
- /** Transforms an arbitrary value into a value that can be assigned to a date-based input. */
- _transformDateInput(value) {
- const date = typeof value === 'string'
- ? this._dateAdapter.parseTime(value, this._dateFormats.parse.timeInput)
- : this._dateAdapter.deserialize(value);
- return date && this._dateAdapter.isValid(date) ? date : null;
- }
- /** Whether the input is currently focused. */
- _hasFocus() {
- return _getFocusedElementPierceShadowDom() === this._elementRef.nativeElement;
- }
- /** Gets a function that can be used to validate the input. */
- _getValidator() {
- return Validators.compose([
- () => this._lastValueValid
- ? null
- : { 'matTimepickerParse': { 'text': this._elementRef.nativeElement.value } },
- control => {
- const controlValue = this._dateAdapter.getValidDateOrNull(this._dateAdapter.deserialize(control.value));
- const min = this.min();
- return !min || !controlValue || this._dateAdapter.compareTime(min, controlValue) <= 0
- ? null
- : { 'matTimepickerMin': { 'min': min, 'actual': controlValue } };
- },
- control => {
- const controlValue = this._dateAdapter.getValidDateOrNull(this._dateAdapter.deserialize(control.value));
- const max = this.max();
- return !max || !controlValue || this._dateAdapter.compareTime(max, controlValue) >= 0
- ? null
- : { 'matTimepickerMax': { 'max': max, 'actual': controlValue } };
- },
- ]);
- }
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: MatTimepickerInput, deps: [], target: i0.ɵɵFactoryTarget.Directive });
- 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: [
- {
- provide: NG_VALUE_ACCESSOR,
- useExisting: MatTimepickerInput,
- multi: true,
- },
- {
- provide: NG_VALIDATORS,
- useExisting: MatTimepickerInput,
- multi: true,
- },
- {
- provide: MAT_INPUT_VALUE_ACCESSOR,
- useExisting: MatTimepickerInput,
- },
- ], exportAs: ["matTimepickerInput"], ngImport: i0 });
- }
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: MatTimepickerInput, decorators: [{
- type: Directive,
- args: [{
- selector: 'input[matTimepicker]',
- exportAs: 'matTimepickerInput',
- host: {
- 'class': 'mat-timepicker-input',
- 'role': 'combobox',
- 'type': 'text',
- 'aria-haspopup': 'listbox',
- '[attr.aria-activedescendant]': '_ariaActiveDescendant()',
- '[attr.aria-expanded]': '_ariaExpanded()',
- '[attr.aria-controls]': '_ariaControls()',
- '[attr.mat-timepicker-id]': 'timepicker()?.panelId',
- '[disabled]': 'disabled()',
- '(blur)': '_handleBlur()',
- '(input)': '_handleInput($event.target.value)',
- '(keydown)': '_handleKeydown($event)',
- },
- providers: [
- {
- provide: NG_VALUE_ACCESSOR,
- useExisting: MatTimepickerInput,
- multi: true,
- },
- {
- provide: NG_VALIDATORS,
- useExisting: MatTimepickerInput,
- multi: true,
- },
- {
- provide: MAT_INPUT_VALUE_ACCESSOR,
- useExisting: MatTimepickerInput,
- },
- ],
- }]
- }], ctorParameters: () => [] });
- /** Button that can be used to open a `mat-timepicker`. */
- class MatTimepickerToggle {
- _defaultConfig = inject(MAT_TIMEPICKER_CONFIG, { optional: true });
- _defaultTabIndex = (() => {
- const value = inject(new HostAttributeToken('tabindex'), { optional: true });
- const parsed = Number(value);
- return isNaN(parsed) ? null : parsed;
- })();
- _isDisabled = computed(() => {
- const timepicker = this.timepicker();
- return this.disabled() || timepicker.disabled();
- });
- /** Timepicker instance that the button will toggle. */
- timepicker = input.required({
- alias: 'for',
- });
- /** Screen-reader label for the button. */
- ariaLabel = input(undefined, {
- alias: 'aria-label',
- });
- /** Screen-reader labelled by id for the button. */
- ariaLabelledby = input(undefined, {
- alias: 'aria-labelledby',
- });
- /** Default aria-label for the toggle if none is provided. */
- _defaultAriaLabel = 'Open timepicker options';
- /** Whether the toggle button is disabled. */
- disabled = input(false, {
- transform: booleanAttribute,
- alias: 'disabled',
- });
- /** Tabindex for the toggle. */
- tabIndex = input(this._defaultTabIndex);
- /** Whether ripples on the toggle should be disabled. */
- disableRipple = input(this._defaultConfig?.disableRipple ?? false, { transform: booleanAttribute });
- /** Opens the connected timepicker. */
- _open(event) {
- if (this.timepicker() && !this._isDisabled()) {
- this.timepicker().open();
- event.stopPropagation();
- }
- }
- /**
- * Checks for ariaLabelledby and if empty uses custom
- * aria-label or defaultAriaLabel if neither is provided.
- */
- getAriaLabel() {
- return this.ariaLabelledby() ? null : this.ariaLabel() || this._defaultAriaLabel;
- }
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: MatTimepickerToggle, deps: [], target: i0.ɵɵFactoryTarget.Component });
- 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 });
- }
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: MatTimepickerToggle, decorators: [{
- type: Component,
- args: [{ selector: 'mat-timepicker-toggle', host: {
- 'class': 'mat-timepicker-toggle',
- '[attr.tabindex]': 'null',
- // Bind the `click` on the host, rather than the inner `button`, so that we can call
- // `stopPropagation` on it without affecting the user's `click` handlers. We need to stop
- // it so that the input doesn't get focused automatically by the form field (See #21836).
- '(click)': '_open($event)',
- }, 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" }]
- }] });
- class MatTimepickerModule {
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: MatTimepickerModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule });
- 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] });
- static ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: MatTimepickerModule, imports: [MatTimepicker, MatTimepickerToggle, CdkScrollableModule] });
- }
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.6", ngImport: i0, type: MatTimepickerModule, decorators: [{
- type: NgModule,
- args: [{
- imports: [MatTimepicker, MatTimepickerInput, MatTimepickerToggle],
- exports: [CdkScrollableModule, MatTimepicker, MatTimepickerInput, MatTimepickerToggle],
- }]
- }] });
- export { MAT_TIMEPICKER_CONFIG, MAT_TIMEPICKER_SCROLL_STRATEGY, MatTimepicker, MatTimepickerInput, MatTimepickerModule, MatTimepickerToggle };
- //# sourceMappingURL=timepicker.mjs.map
|