@angular/material
Version:
Angular Material
1,266 lines (1,259 loc) • 43.6 kB
JavaScript
import * as i0 from '@angular/core';
import { InjectionToken, inject, Injector, ViewContainerRef, 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 { createRepositionScrollStrategy, createFlexibleConnectedPositionStrategy, createOverlayRef } 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 { DateAdapter, MAT_DATE_FORMATS } from './_date-formats-chunk.mjs';
import { _animationsDisabled } from './_animation-chunk.mjs';
import { MatOption, MAT_OPTION_PARENT_COMPONENT } from './_option-chunk.mjs';
import { Validators, NG_VALUE_ACCESSOR, NG_VALIDATORS } from '@angular/forms';
import { MAT_FORM_FIELD } from './_form-field-chunk.mjs';
import { MAT_INPUT_VALUE_ACCESSOR } from './_input-value-accessor-chunk.mjs';
import { MatIconButton } from './_icon-button-chunk.mjs';
import { CdkScrollableModule } from '@angular/cdk/scrolling';
import 'rxjs';
import '@angular/cdk/layout';
import './_ripple-chunk.mjs';
import '@angular/cdk/coercion';
import '@angular/cdk/private';
import './_pseudo-checkbox-chunk.mjs';
import './_structural-styles-chunk.mjs';
import '@angular/common';
import 'rxjs/operators';
import '@angular/cdk/observers/private';
import './_ripple-loader-chunk.mjs';
const INTERVAL_PATTERN = /^(\d*\.?\d+)\s*(h|hour|hours|m|min|minute|minutes|s|second|seconds)?$/i;
const MAT_TIMEPICKER_CONFIG = new InjectionToken('MAT_TIMEPICKER_CONFIG');
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;
}
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;
}
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.');
}
}
const MAT_TIMEPICKER_SCROLL_STRATEGY = new InjectionToken('MAT_TIMEPICKER_SCROLL_STRATEGY', {
providedIn: 'root',
factory: () => {
const injector = inject(Injector);
return () => createRepositionScrollStrategy(injector);
}
});
class MatTimepicker {
_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 = _animationsDisabled();
_isOpen = signal(false, ...(ngDevMode ? [{
debugName: "_isOpen"
}] : []));
_activeDescendant = signal(null, ...(ngDevMode ? [{
debugName: "_activeDescendant"
}] : []));
_input = signal(null, ...(ngDevMode ? [{
debugName: "_input"
}] : []));
_overlayRef = null;
_portal = null;
_optionsCacheKey = null;
_localeChanges;
_onOpenRender = null;
_panelTemplate = viewChild.required('panelTemplate');
_timeOptions = [];
_options = viewChildren(MatOption, ...(ngDevMode ? [{
debugName: "_options"
}] : []));
_keyManager = new ActiveDescendantKeyManager(this._options, this._injector).withHomeAndEnd(true).withPageUpDown(true).withVerticalOrientation(true);
interval = input(parseInterval(this._defaultConfig?.interval || null), {
...(ngDevMode ? {
debugName: "interval"
} : {}),
transform: parseInterval
});
options = input(null, ...(ngDevMode ? [{
debugName: "options"
}] : []));
isOpen = this._isOpen.asReadonly();
selected = output();
opened = output();
closed = output();
activeDescendant = this._activeDescendant.asReadonly();
panelId = inject(_IdGenerator).getId('mat-timepicker-panel-');
disableRipple = input(this._defaultConfig?.disableRipple ?? false, {
...(ngDevMode ? {
debugName: "disableRipple"
} : {}),
transform: booleanAttribute
});
ariaLabel = input(null, {
...(ngDevMode ? {
debugName: "ariaLabel"
} : {}),
alias: 'aria-label'
});
ariaLabelledby = input(null, {
...(ngDevMode ? {
debugName: "ariaLabelledby"
} : {}),
alias: 'aria-labelledby'
});
disabled = computed(() => !!this._input()?.disabled(), ...(ngDevMode ? [{
debugName: "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');
}
});
}
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));
}
open() {
const input = this._input();
if (!input) {
return;
}
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);
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();
}
close() {
if (this._isOpen()) {
this._isOpen.set(false);
this.closed.emit();
if (this._animationsDisabled) {
this._overlayRef?.detach();
}
}
}
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();
}
_getOverlayHost() {
return this._overlayRef?.hostElement;
}
_selectValue(option) {
this.close();
this._keyManager.setActiveItem(option);
this._options().forEach(current => {
if (current !== option) {
current.deselect(false);
}
});
this._input()?.timepickerValueAssigned(option.value);
this.selected.emit({
value: option.value,
source: this
});
this._input()?.focus();
}
_getAriaLabelledby() {
if (this.ariaLabel()) {
return null;
}
return this.ariaLabelledby() || this._input()?.getLabelId() || null;
}
_handleAnimationEnd(event) {
if (event.animationName === '_mat-timepicker-exit') {
this._overlayRef?.detach();
}
}
_getOverlayRef() {
if (this._overlayRef) {
return this._overlayRef;
}
const positionStrategy = createFlexibleConnectedPositionStrategy(this._injector, this._input().getOverlayOrigin()).withFlexibleDimensions(false).withPush(false).withTransformOriginOn('.mat-timepicker-panel').withPopoverLocation('inline').withPositions([{
originX: 'start',
originY: 'bottom',
overlayX: 'start',
overlayY: 'top'
}, {
originX: 'start',
originY: 'top',
overlayX: 'start',
overlayY: 'bottom',
panelClass: 'mat-timepicker-above'
}]);
this._overlayRef = createOverlayRef(this._injector, {
positionStrategy,
scrollStrategy: this._scrollStrategyFactory(),
direction: this._dir || 'ltr',
hasBackdrop: false,
disableAnimations: this._animationsDisabled
});
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;
}
_generateOptions() {
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);
if (cacheKey !== this._optionsCacheKey) {
this._optionsCacheKey = cacheKey;
this._timeOptions = generateOptions(adapter, this._dateFormats, min, max, interval);
}
}
}
_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 (!hasSelected) {
if (fallback) {
untracked(() => this._keyManager.setActiveItem(fallback));
scrollOptionIntoView(fallback, 'center');
} else {
untracked(() => this._keyManager.setActiveItem(-1));
}
}
}
_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');
}
}
}
_handleLocaleChanges() {
this._localeChanges = this._dateAdapter.localeChanges.subscribe(() => {
this._optionsCacheKey = null;
if (this.isOpen()) {
this._generateOptions();
}
});
}
_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: "21.0.3",
ngImport: i0,
type: MatTimepicker,
deps: [],
target: i0.ɵɵFactoryTarget.Component
});
static ɵcmp = i0.ɵɵngDeclareComponent({
minVersion: "17.0.0",
version: "21.0.3",
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: "21.0.3",
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: () => [],
propDecorators: {
_panelTemplate: [{
type: i0.ViewChild,
args: ['panelTemplate', {
isSignal: true
}]
}],
_options: [{
type: i0.ViewChildren,
args: [i0.forwardRef(() => MatOption), {
isSignal: true
}]
}],
interval: [{
type: i0.Input,
args: [{
isSignal: true,
alias: "interval",
required: false
}]
}],
options: [{
type: i0.Input,
args: [{
isSignal: true,
alias: "options",
required: false
}]
}],
selected: [{
type: i0.Output,
args: ["selected"]
}],
opened: [{
type: i0.Output,
args: ["opened"]
}],
closed: [{
type: i0.Output,
args: ["closed"]
}],
disableRipple: [{
type: i0.Input,
args: [{
isSignal: true,
alias: "disableRipple",
required: false
}]
}],
ariaLabel: [{
type: i0.Input,
args: [{
isSignal: true,
alias: "aria-label",
required: false
}]
}],
ariaLabelledby: [{
type: i0.Input,
args: [{
isSignal: true,
alias: "aria-labelledby",
required: false
}]
}]
}
});
function scrollOptionIntoView(option, position) {
option._getHostElement().scrollIntoView({
block: position,
inline: position
});
}
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, ...(ngDevMode ? [{
debugName: "_accessorDisabled"
}] : []));
_localeSubscription;
_timepickerSubscription;
_validator;
_lastValueValid = true;
_minValid = true;
_maxValid = true;
_lastValidDate = null;
_ariaActiveDescendant = computed(() => {
const timepicker = this.timepicker();
const isOpen = timepicker.isOpen();
const activeDescendant = timepicker.activeDescendant();
return isOpen && activeDescendant ? activeDescendant : null;
}, ...(ngDevMode ? [{
debugName: "_ariaActiveDescendant"
}] : []));
_ariaExpanded = computed(() => this.timepicker().isOpen() + '', ...(ngDevMode ? [{
debugName: "_ariaExpanded"
}] : []));
_ariaControls = computed(() => {
const timepicker = this.timepicker();
return timepicker.isOpen() ? timepicker.panelId : null;
}, ...(ngDevMode ? [{
debugName: "_ariaControls"
}] : []));
value = model(null, ...(ngDevMode ? [{
debugName: "value"
}] : []));
timepicker = input.required({
...(ngDevMode ? {
debugName: "timepicker"
} : {}),
alias: 'matTimepicker'
});
min = input(null, {
...(ngDevMode ? {
debugName: "min"
} : {}),
alias: 'matTimepickerMin',
transform: value => this._transformDateInput(value)
});
max = input(null, {
...(ngDevMode ? {
debugName: "max"
} : {}),
alias: 'matTimepickerMax',
transform: value => this._transformDateInput(value)
});
openOnClick = input(true, {
...(ngDevMode ? {
debugName: "openOnClick"
} : {}),
alias: 'matTimepickerOpenOnClick',
transform: booleanAttribute
});
disabled = computed(() => this.disabledInput() || this._accessorDisabled(), ...(ngDevMode ? [{
debugName: "disabled"
}] : []));
disabledInput = input(false, {
...(ngDevMode ? {
debugName: "disabledInput"
} : {}),
transform: booleanAttribute,
alias: 'disabled'
});
constructor() {
if (typeof ngDevMode === 'undefined' || ngDevMode) {
validateAdapter(this._dateAdapter, this._dateFormats);
}
const renderer = inject(Renderer2);
this._validator = this._getValidator();
this._updateFormsState();
this._registerTimepicker();
this._localeSubscription = this._dateAdapter.localeChanges.subscribe(() => {
if (!this._hasFocus()) {
this._formatValue(this.value());
}
});
this._cleanupClick = renderer.listen(this.getOverlayOrigin().nativeElement, 'click', this._handleClick);
}
writeValue(value) {
const deserialized = this._dateAdapter.deserialize(value);
this.value.set(this._dateAdapter.getValidDateOrNull(deserialized));
}
registerOnChange(fn) {
this._onChange = fn;
}
registerOnTouched(fn) {
this._onTouched = fn;
}
setDisabledState(isDisabled) {
this._accessorDisabled.set(isDisabled);
}
validate(control) {
return this._validator(control);
}
registerOnValidatorChange(fn) {
this._validatorOnChange = fn;
}
getOverlayOrigin() {
return this._formField?.getConnectedOverlayOrigin() || this._elementRef;
}
focus() {
this._elementRef.nativeElement.focus();
}
ngOnDestroy() {
this._cleanupClick();
this._timepickerSubscription?.unsubscribe();
this._localeSubscription.unsubscribe();
}
getLabelId() {
return this._formField?.getLabelId() || null;
}
_handleClick = event => {
if (this.disabled() || !this.openOnClick()) {
return;
}
const target = _getEventTarget(event);
const overlayHost = this.timepicker()._getOverlayHost();
if (!target || !overlayHost || !overlayHost.contains(target)) {
this.timepicker().open();
}
};
_handleInput(event) {
const value = event.target.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)) {
this._assignUserSelection(date, true);
} else {
this._validatorOnChange?.();
}
}
_handleBlur() {
const value = this.value();
if (value && this._isValid(value)) {
this._formatValue(value);
}
if (!this.timepicker().isOpen()) {
this._onTouched?.();
}
}
_handleKeydown(event) {
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();
}
}
timepickerValueAssigned(value) {
if (!this._dateAdapter.sameTime(value, this.value())) {
this._assignUserSelection(value, true);
this._formatValue(value);
}
}
_updateFormsState() {
effect(() => {
const {
_dateAdapter: adapter,
_lastValueValid: prevValueValid,
_minValid: prevMinValid,
_maxValid: prevMaxValid
} = this;
const value = adapter.deserialize(this.value());
const min = this.min();
const max = this.max();
const valueValid = this._lastValueValid = this._isValid(value);
this._minValid = !min || !value || !valueValid || adapter.compareTime(min, value) <= 0;
this._maxValid = !max || !value || !valueValid || adapter.compareTime(max, value) >= 0;
const stateChanged = prevValueValid !== valueValid || prevMinValid !== this._minValid || prevMaxValid !== this._maxValid;
if (!this._hasFocus()) {
this._formatValue(value);
}
if (value && valueValid) {
this._lastValidDate = value;
}
if (stateChanged) {
this._validatorOnChange?.();
}
});
}
_registerTimepicker() {
effect(() => {
const timepicker = this.timepicker();
timepicker.registerInput(this);
timepicker.closed.subscribe(() => this._onTouched?.());
});
}
_assignUserSelection(selection, propagateToAccessor) {
let toAssign;
if (selection == null || !this._isValid(selection)) {
toAssign = selection;
} else {
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);
toAssign = target ? adapter.setTime(target, hours, minutes, seconds) : selection;
}
if (propagateToAccessor) {
this._onChange?.(toAssign);
}
this.value.set(toAssign);
}
_formatValue(value) {
value = this._dateAdapter.getValidDateOrNull(value);
this._elementRef.nativeElement.value = value == null ? '' : this._dateAdapter.format(value, this._dateFormats.display.timeInput);
}
_isValid(value) {
return !value || this._dateAdapter.isValid(value);
}
_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;
}
_hasFocus() {
return _getFocusedElementPierceShadowDom() === this._elementRef.nativeElement;
}
_getValidator() {
return Validators.compose([() => this._lastValueValid ? null : {
'matTimepickerParse': {
'text': this._elementRef.nativeElement.value
}
}, control => this._minValid ? null : {
'matTimepickerMin': {
'min': this.min(),
'actual': this._dateAdapter.getValidDateOrNull(this._dateAdapter.deserialize(control.value))
}
}, control => this._maxValid ? null : {
'matTimepickerMax': {
'max': this.max(),
'actual': this._dateAdapter.getValidDateOrNull(this._dateAdapter.deserialize(control.value))
}
}]);
}
static ɵfac = i0.ɵɵngDeclareFactory({
minVersion: "12.0.0",
version: "21.0.3",
ngImport: i0,
type: MatTimepickerInput,
deps: [],
target: i0.ɵɵFactoryTarget.Directive
});
static ɵdir = i0.ɵɵngDeclareDirective({
minVersion: "17.1.0",
version: "21.0.3",
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
},
openOnClick: {
classPropertyName: "openOnClick",
publicName: "matTimepickerOpenOnClick",
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)",
"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: "21.0.3",
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)',
'(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: () => [],
propDecorators: {
value: [{
type: i0.Input,
args: [{
isSignal: true,
alias: "value",
required: false
}]
}, {
type: i0.Output,
args: ["valueChange"]
}],
timepicker: [{
type: i0.Input,
args: [{
isSignal: true,
alias: "matTimepicker",
required: true
}]
}],
min: [{
type: i0.Input,
args: [{
isSignal: true,
alias: "matTimepickerMin",
required: false
}]
}],
max: [{
type: i0.Input,
args: [{
isSignal: true,
alias: "matTimepickerMax",
required: false
}]
}],
openOnClick: [{
type: i0.Input,
args: [{
isSignal: true,
alias: "matTimepickerOpenOnClick",
required: false
}]
}],
disabledInput: [{
type: i0.Input,
args: [{
isSignal: true,
alias: "disabled",
required: false
}]
}]
}
});
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();
}, ...(ngDevMode ? [{
debugName: "_isDisabled"
}] : []));
timepicker = input.required({
...(ngDevMode ? {
debugName: "timepicker"
} : {}),
alias: 'for'
});
ariaLabel = input(undefined, {
...(ngDevMode ? {
debugName: "ariaLabel"
} : {}),
alias: 'aria-label'
});
ariaLabelledby = input(undefined, {
...(ngDevMode ? {
debugName: "ariaLabelledby"
} : {}),
alias: 'aria-labelledby'
});
_defaultAriaLabel = 'Open timepicker options';
disabled = input(false, {
...(ngDevMode ? {
debugName: "disabled"
} : {}),
transform: booleanAttribute,
alias: 'disabled'
});
tabIndex = input(this._defaultTabIndex, ...(ngDevMode ? [{
debugName: "tabIndex"
}] : []));
disableRipple = input(this._defaultConfig?.disableRipple ?? false, {
...(ngDevMode ? {
debugName: "disableRipple"
} : {}),
transform: booleanAttribute
});
_open(event) {
if (this.timepicker() && !this._isDisabled()) {
this.timepicker().open();
event.stopPropagation();
}
}
getAriaLabel() {
return this.ariaLabelledby() ? null : this.ariaLabel() || this._defaultAriaLabel;
}
static ɵfac = i0.ɵɵngDeclareFactory({
minVersion: "12.0.0",
version: "21.0.3",
ngImport: i0,
type: MatTimepickerToggle,
deps: [],
target: i0.ɵɵFactoryTarget.Component
});
static ɵcmp = i0.ɵɵngDeclareComponent({
minVersion: "17.1.0",
version: "21.0.3",
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 matIconButton\n type=\"button\"\n aria-haspopup=\"listbox\"\n [attr.aria-label]=\"getAriaLabel()\"\n [attr.aria-labelledby]=\"ariaLabelledby()\"\n [attr.aria-expanded]=\"timepicker().isOpen()\"\n [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], a[mat-icon-button], button[matIconButton], a[matIconButton]",
exportAs: ["matButton", "matAnchor"]
}],
changeDetection: i0.ChangeDetectionStrategy.OnPush,
encapsulation: i0.ViewEncapsulation.None
});
}
i0.ɵɵngDeclareClassMetadata({
minVersion: "12.0.0",
version: "21.0.3",
ngImport: i0,
type: MatTimepickerToggle,
decorators: [{
type: Component,
args: [{
selector: 'mat-timepicker-toggle',
host: {
'class': 'mat-timepicker-toggle',
'[attr.tabindex]': 'null',
'(click)': '_open($event)'
},
exportAs: 'matTimepickerToggle',
encapsulation: ViewEncapsulation.None,
changeDetection: ChangeDetectionStrategy.OnPush,
imports: [MatIconButton],
template: "<button\n matIconButton\n type=\"button\"\n aria-haspopup=\"listbox\"\n [attr.aria-label]=\"getAriaLabel()\"\n [attr.aria-labelledby]=\"ariaLabelledby()\"\n [attr.aria-expanded]=\"timepicker().isOpen()\"\n [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"
}]
}],
propDecorators: {
timepicker: [{
type: i0.Input,
args: [{
isSignal: true,
alias: "for",
required: true
}]
}],
ariaLabel: [{
type: i0.Input,
args: [{
isSignal: true,
alias: "aria-label",
required: false
}]
}],
ariaLabelledby: [{
type: i0.Input,
args: [{
isSignal: true,
alias: "aria-labelledby",
required: false
}]
}],
disabled: [{
type: i0.Input,
args: [{
isSignal: true,
alias: "disabled",
required: false
}]
}],
tabIndex: [{
type: i0.Input,
args: [{
isSignal: true,
alias: "tabIndex",
required: false
}]
}],
disableRipple: [{
type: i0.Input,
args: [{
isSignal: true,
alias: "disableRipple",
required: false
}]
}]
}
});
class MatTimepickerModule {
static ɵfac = i0.ɵɵngDeclareFactory({
minVersion: "12.0.0",
version: "21.0.3",
ngImport: i0,
type: MatTimepickerModule,
deps: [],
target: i0.ɵɵFactoryTarget.NgModule
});
static ɵmod = i0.ɵɵngDeclareNgModule({
minVersion: "14.0.0",
version: "21.0.3",
ngImport: i0,
type: MatTimepickerModule,
imports: [MatTimepicker, MatTimepickerInput, MatTimepickerToggle],
exports: [CdkScrollableModule, MatTimepicker, MatTimepickerInput, MatTimepickerToggle]
});
static ɵinj = i0.ɵɵngDeclareInjector({
minVersion: "12.0.0",
version: "21.0.3",
ngImport: i0,
type: MatTimepickerModule,
imports: [MatTimepicker, MatTimepickerToggle, CdkScrollableModule]
});
}
i0.ɵɵngDeclareClassMetadata({
minVersion: "12.0.0",
version: "21.0.3",
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