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
4 changes: 3 additions & 1 deletion goldens/material/timepicker/index.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,8 @@ export class MatTimepicker<D> implements OnDestroy, MatOptionParentComponent {
readonly options: InputSignal<readonly MatTimepickerOption<D>[] | null>;
// (undocumented)
protected _options: Signal<readonly MatOption<any>[]>;
// (undocumented)
protected _optionTemplate: Signal<TemplateRef<MatTimepickerOption<D>> | undefined>;
readonly panelId: string;
// (undocumented)
protected _panelTemplate: Signal<TemplateRef<unknown>>;
Expand All @@ -65,7 +67,7 @@ export class MatTimepicker<D> implements OnDestroy, MatOptionParentComponent {
// (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
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 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
1 change: 1 addition & 0 deletions src/material/timepicker/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ ng_project(
":timepicker_css",
],
deps = [
"//:node_modules/@angular/common",
"//:node_modules/@angular/core",
"//:node_modules/@angular/forms",
"//:node_modules/rxjs",
Expand Down
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() || defaultOptionTemplate"
[ngTemplateOutletContext]="{ $implicit: option }">
</ng-container>
</mat-option>
}
</div>
</ng-template>
53 changes: 52 additions & 1 deletion src/material/timepicker/timepicker.spec.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,12 @@
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 {
Expand Down Expand Up @@ -652,6 +660,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 +1538,28 @@ class TimepickerWithoutInput {
encapsulation: ViewEncapsulation.ShadowDom,
})
class TimepickerInShadowDom {}

@Component({
template: `
<input [matTimepicker]="picker" />
<mat-timepicker #picker interval="3h">
<ng-template 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],
})
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);
}
}
5 changes: 4 additions & 1 deletion src/material/timepicker/timepicker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import {
ChangeDetectionStrategy,
Component,
computed,
contentChild,
effect,
ElementRef,
inject,
Expand All @@ -33,6 +34,7 @@ import {
ViewContainerRef,
ViewEncapsulation,
} from '@angular/core';
import {NgTemplateOutlet} from '@angular/common';
import {
_animationsDisabled,
DateAdapter,
Expand Down Expand Up @@ -92,7 +94,7 @@ export const MAT_TIMEPICKER_SCROLL_STRATEGY = new InjectionToken<() => ScrollStr
styleUrl: 'timepicker.css',
changeDetection: ChangeDetectionStrategy.OnPush,
encapsulation: ViewEncapsulation.None,
imports: [MatOption],
imports: [NgTemplateOutlet, MatOption],
providers: [
{
provide: MAT_OPTION_PARENT_COMPONENT,
Expand Down Expand Up @@ -123,6 +125,7 @@ export class MatTimepicker<D> implements OnDestroy, MatOptionParentComponent {
protected _panelTemplate = viewChild.required<TemplateRef<unknown>>('panelTemplate');
protected _timeOptions: readonly MatTimepickerOption<D>[] = [];
protected _options = viewChildren(MatOption);
protected _optionTemplate = contentChild<TemplateRef<MatTimepickerOption<D>>>(TemplateRef);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually I think we may have to provide this through an input or have a directive that users apply on the ng-template. Querying for any TemplateRef inside the timepicker can pick up something we didn't intend to.


private _keyManager = new ActiveDescendantKeyManager(this._options, this._injector)
.withHomeAndEnd(true)
Expand Down
Loading