Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 17 additions & 5 deletions goldens/material/timepicker/index.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ export class MatTimepicker<D> implements OnDestroy, MatOptionParentComponent {
constructor();
readonly activeDescendant: Signal<string | null>;
// (undocumented)
protected _animationsDisabled: boolean;
protected readonly _animationsDisabled: boolean;
readonly ariaLabel: InputSignal<string | null>;
readonly ariaLabelledby: InputSignal<string | null>;
close(): void;
Expand All @@ -55,17 +55,19 @@ export class MatTimepicker<D> implements OnDestroy, MatOptionParentComponent {
readonly opened: OutputEmitterRef<void>;
readonly options: InputSignal<readonly MatTimepickerOption<D>[] | null>;
// (undocumented)
protected _options: Signal<readonly MatOption<any>[]>;
protected readonly _options: Signal<readonly MatOption<any>[]>;
// (undocumented)
protected readonly _optionTemplate: Signal<MatTimepickerOptionTemplate<D> | undefined>;
readonly panelId: string;
// (undocumented)
protected _panelTemplate: Signal<TemplateRef<unknown>>;
protected readonly _panelTemplate: Signal<TemplateRef<unknown>>;
registerInput(input: MatTimepickerConnectedInput<D>): void;
readonly selected: OutputEmitterRef<MatTimepickerSelected<D>>;
protected _selectValue(option: MatOption<D>): void;
// (undocumented)
protected _timeOptions: readonly MatTimepickerOption<D>[];
// (undocumented)
static ɵcmp: i0.ɵɵComponentDeclaration<MatTimepicker<any>, "mat-timepicker", ["matTimepicker"], { "interval": { "alias": "interval"; "required": false; "isSignal": true; }; "options": { "alias": "options"; "required": false; "isSignal": true; }; "disableRipple": { "alias": "disableRipple"; "required": false; "isSignal": true; }; "ariaLabel": { "alias": "aria-label"; "required": false; "isSignal": true; }; "ariaLabelledby": { "alias": "aria-labelledby"; "required": false; "isSignal": true; }; }, { "selected": "selected"; "opened": "opened"; "closed": "closed"; }, never, never, true, never>;
static ɵcmp: i0.ɵɵComponentDeclaration<MatTimepicker<any>, "mat-timepicker", ["matTimepicker"], { "interval": { "alias": "interval"; "required": false; "isSignal": true; }; "options": { "alias": "options"; "required": false; "isSignal": true; }; "disableRipple": { "alias": "disableRipple"; "required": false; "isSignal": true; }; "ariaLabel": { "alias": "aria-label"; "required": false; "isSignal": true; }; "ariaLabelledby": { "alias": "aria-labelledby"; "required": false; "isSignal": true; }; }, { "selected": "selected"; "opened": "opened"; "closed": "closed"; }, ["_optionTemplate"], never, true, never>;
// (undocumented)
static ɵfac: i0.ɵɵFactoryDeclaration<MatTimepicker<any>, never>;
}
Expand Down Expand Up @@ -129,7 +131,7 @@ export class MatTimepickerModule {
// (undocumented)
static ɵinj: i0.ɵɵInjectorDeclaration<MatTimepickerModule>;
// (undocumented)
static ɵmod: i0.ɵɵNgModuleDeclaration<MatTimepickerModule, never, [typeof MatTimepicker, typeof MatTimepickerInput, typeof MatTimepickerToggle], [typeof i1.CdkScrollableModule, typeof MatTimepicker, typeof MatTimepickerInput, typeof MatTimepickerToggle]>;
static ɵmod: i0.ɵɵNgModuleDeclaration<MatTimepickerModule, never, [typeof MatTimepicker, typeof MatTimepickerInput, typeof MatTimepickerOptionTemplate, typeof MatTimepickerToggle], [typeof i1.CdkScrollableModule, typeof MatTimepicker, typeof MatTimepickerInput, typeof MatTimepickerOptionTemplate, typeof MatTimepickerToggle]>;
}

// @public
Expand All @@ -138,6 +140,16 @@ export interface MatTimepickerOption<D = unknown> {
value: D;
}

// @public
export class MatTimepickerOptionTemplate<D> {
// (undocumented)
readonly template: TemplateRef<MatTimepickerOption<D>>;
// (undocumented)
static ɵdir: i0.ɵɵDirectiveDeclaration<MatTimepickerOptionTemplate<any>, "ng-template[matTimepickerOption]", never, {}, {}, never, never, true, never>;
// (undocumented)
static ɵfac: i0.ɵɵFactoryDeclaration<MatTimepickerOptionTemplate<any>, never>;
}

// @public
export interface MatTimepickerSelected<D> {
// (undocumented)
Expand Down
1 change: 1 addition & 0 deletions src/components-examples/material/timepicker/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,5 @@ export {TimepickerValidationExample} from './timepicker-validation/timepicker-va
export {TimepickerOptionsExample} from './timepicker-options/timepicker-options-example';
export {TimepickerCustomIconExample} from './timepicker-custom-icon/timepicker-custom-icon-example';
export {TimepickerLocaleExample} from './timepicker-locale/timepicker-locale-example';
export {TimepickerOptionTemplateExample} from './timepicker-option-template/timepicker-option-template-example';
export {TimepickerHarnessExample} from './timepicker-harness/timepicker-harness-example';
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<mat-form-field>
<mat-label>Pick a time</mat-label>
<input matInput [matTimepicker]="picker">
<mat-timepicker-toggle matIconSuffix [for]="picker"/>
<mat-timepicker #picker>
<ng-template matTimepickerOption let-option>
<mat-icon>
{{ (dateAdapter.compareTime(option.value, sunrise) > 0 && dateAdapter.compareTime(option.value, sunset) < 0) ? 'sunny' : 'bedtime' }}
</mat-icon>
{{ option.label }}
</ng-template>
</mat-timepicker>
</mat-form-field>
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import {ChangeDetectionStrategy, Component, inject} from '@angular/core';
import {MatTimepickerModule} from '@angular/material/timepicker';
import {MatIcon} from '@angular/material/icon';
import {MatInputModule} from '@angular/material/input';
import {MatFormFieldModule} from '@angular/material/form-field';
import {DateAdapter, provideNativeDateAdapter} from '@angular/material/core';

/** @title Timepicker with option template. */
@Component({
selector: 'timepicker-option-template-example',
templateUrl: 'timepicker-option-template-example.html',
providers: [provideNativeDateAdapter()],
imports: [MatFormFieldModule, MatIcon, MatInputModule, MatTimepickerModule],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class TimepickerOptionTemplateExample {
readonly dateAdapter = inject<DateAdapter<Date>>(DateAdapter);
readonly sunrise: Date;
readonly sunset: Date;

constructor() {
this.sunrise = new Date();
this.sunrise.setHours(7, 0, 0);
this.sunset = new Date();
this.sunset.setHours(17, 30, 0);
}
}
2 changes: 1 addition & 1 deletion src/material/slider/_slider-theme.scss
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@
@include token-utils.batch-create-token-values($tokens, _define-overrides());
}

/// Outputs all (base, color, typography, and density) theme styles for the mat-option.
/// Outputs all (base, color, typography, and density) theme styles for the mat-slider.
/// @param {Map} $theme The theme to generate styles for.
/// @param {String} $color-variant The color variant to use for the component (M3 only)
@mixin theme($theme, $color-variant: null) {
Expand Down
2 changes: 2 additions & 0 deletions src/material/timepicker/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ ng_project(
"timepicker.ts",
"timepicker-input.ts",
"timepicker-module.ts",
"timepicker-option.ts",
"timepicker-toggle.ts",
"util.ts",
],
Expand All @@ -77,6 +78,7 @@ ng_project(
":timepicker_css",
],
deps = [
"//:node_modules/@angular/common",
"//:node_modules/@angular/core",
"//:node_modules/@angular/forms",
"//:node_modules/rxjs",
Expand Down
1 change: 1 addition & 0 deletions src/material/timepicker/public-api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

export * from './timepicker';
export * from './timepicker-input';
export * from './timepicker-option';
export * from './timepicker-toggle';
export * from './timepicker-module';
export {MatTimepickerOption, MAT_TIMEPICKER_CONFIG, MatTimepickerConfig} from './util';
11 changes: 9 additions & 2 deletions src/material/timepicker/timepicker-module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,17 @@ import {NgModule} from '@angular/core';
import {CdkScrollableModule} from '@angular/cdk/scrolling';
import {MatTimepicker} from './timepicker';
import {MatTimepickerInput} from './timepicker-input';
import {MatTimepickerOptionTemplate} from './timepicker-option';
import {MatTimepickerToggle} from './timepicker-toggle';

@NgModule({
imports: [MatTimepicker, MatTimepickerInput, MatTimepickerToggle],
exports: [CdkScrollableModule, MatTimepicker, MatTimepickerInput, MatTimepickerToggle],
imports: [MatTimepicker, MatTimepickerInput, MatTimepickerOptionTemplate, MatTimepickerToggle],
exports: [
CdkScrollableModule,
MatTimepicker,
MatTimepickerInput,
MatTimepickerOptionTemplate,
MatTimepickerToggle,
],
})
export class MatTimepickerModule {}
18 changes: 18 additions & 0 deletions src/material/timepicker/timepicker-option.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.dev/license
*/

import {Directive, TemplateRef, inject} from '@angular/core';
import {MatTimepickerOption} from './util';

/** Template to be used to override the timepicker's option labels. */
@Directive({
selector: 'ng-template[matTimepickerOption]',
})
export class MatTimepickerOptionTemplate<D> {
readonly template = inject<TemplateRef<MatTimepickerOption<D>>>(TemplateRef);
}
8 changes: 7 additions & 1 deletion src/material/timepicker/timepicker.html
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
<ng-template #defaultOptionTemplate let-option>{{ option.label }}</ng-template>

<ng-template #panelTemplate>
<div
role="listbox"
Expand All @@ -11,7 +13,11 @@
@for (option of _timeOptions; track option.value) {
<mat-option
[value]="option.value"
(onSelectionChange)="_selectValue($event.source)">{{option.label}}</mat-option>
(onSelectionChange)="_selectValue($event.source)">
<ng-container [ngTemplateOutlet]="_optionTemplate()?.template || defaultOptionTemplate"
[ngTemplateOutletContext]="{ $implicit: option }">
</ng-container>
</mat-option>
}
</div>
</ng-template>
68 changes: 60 additions & 8 deletions src/material/timepicker/timepicker.spec.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,13 @@
import {Component, Injector, Provider, signal, ViewChild, ViewEncapsulation} from '@angular/core';
import {
Component,
inject,
Injector,
Provider,
signal,
ViewChild,
ViewEncapsulation,
} from '@angular/core';
import {ComponentFixture, fakeAsync, flush, TestBed} from '@angular/core/testing';
import {DateAdapter, MATERIAL_ANIMATIONS, provideNativeDateAdapter} from '../core';
import {
clearElement,
dispatchFakeEvent,
Expand All @@ -20,16 +27,18 @@ import {
TAB,
UP_ARROW,
} from '@angular/cdk/keycodes';
import {MatInput} from '../input';
import {createCloseScrollStrategy} from '@angular/cdk/overlay';
import {ScrollDispatcher} from '@angular/cdk/scrolling';
import {FormControl, ReactiveFormsModule, Validators} from '@angular/forms';
import {Subject} from 'rxjs';
import {DateAdapter, MATERIAL_ANIMATIONS, provideNativeDateAdapter} from '../core';
import {MatFormField, MatLabel, MatSuffix} from '../form-field';
import {MatInput} from '../input';
import {MatTimepickerInput} from './timepicker-input';
import {MAT_TIMEPICKER_SCROLL_STRATEGY, MatTimepicker} from './timepicker';
import {MatTimepickerOptionTemplate} from './timepicker-option';
import {MatTimepickerToggle} from './timepicker-toggle';
import {MAT_TIMEPICKER_SCROLL_STRATEGY, MatTimepicker} from './timepicker';
import {MAT_TIMEPICKER_CONFIG, MatTimepickerOption} from './util';
import {FormControl, ReactiveFormsModule, Validators} from '@angular/forms';
import {ScrollDispatcher} from '@angular/cdk/scrolling';
import {createCloseScrollStrategy} from '@angular/cdk/overlay';
import {Subject} from 'rxjs';

describe('MatTimepicker', () => {
let adapter: DateAdapter<Date>;
Expand Down Expand Up @@ -652,6 +661,24 @@ describe('MatTimepicker', () => {
fixture.detectChanges();
expect(fixture.componentInstance.timepicker.interval()).toBe(null);
});

it('should be able to apply custom options template', () => {
const fixture = TestBed.createComponent(TimepickerOptionTemplate);
fixture.detectChanges();

getInput(fixture).click();
fixture.detectChanges();
expect(getOptions().map(o => o.textContent.trim())).toEqual([
'12:00 AM (off-hours)',
'3:00 AM (off-hours)',
'6:00 AM (off-hours)',
'9:00 AM (working hours)',
'12:00 PM (working hours)',
'3:00 PM (working hours)',
'6:00 PM (off-hours)',
'9:00 PM (off-hours)',
]);
});
});

describe('mat-form-field integration', () => {
Expand Down Expand Up @@ -1512,3 +1539,28 @@ class TimepickerWithoutInput {
encapsulation: ViewEncapsulation.ShadowDom,
})
class TimepickerInShadowDom {}

@Component({
template: `
<input [matTimepicker]="picker" />
<mat-timepicker #picker interval="3h">
<ng-template matTimepickerOption let-option>
{{ option.label }}
{{ (dateAdapter.compareTime(option.value, workStartTime) > 0 && dateAdapter.compareTime(option.value, workEndTime) < 0) ? '(working hours)' : '(off-hours)' }}
</ng-template>
</mat-timepicker>
`,
imports: [MatTimepicker, MatTimepickerInput, MatTimepickerOptionTemplate],
})
class TimepickerOptionTemplate {
readonly dateAdapter = inject<DateAdapter<Date>>(DateAdapter);
readonly workStartTime: Date;
readonly workEndTime: Date;

constructor() {
this.workStartTime = new Date();
this.workStartTime.setHours(8, 30, 0);
this.workEndTime = new Date();
this.workEndTime.setHours(17, 0, 0);
}
}
Loading
Loading