Skip to content

Commit

Permalink
main - 0296713 fix(material/select): add opt-in input that allows sel…
Browse files Browse the repository at this point in the history
…ection of nullable options (#30142)
  • Loading branch information
crisbeto committed Dec 10, 2024
1 parent d7facf9 commit 7a2b62b
Show file tree
Hide file tree
Showing 26 changed files with 211 additions and 41 deletions.
31 changes: 31 additions & 0 deletions docs-content/api-docs/material-select.html
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,23 @@ <h4 id="MatSelect" class="docs-header-link docs-api-h4 docs-api-class-name">



<tr class="docs-api-properties-row">
<td class="docs-api-properties-name-cell"><div class="docs-api-input-marker">@Input(<span class="docs-api-input-alias">{ transform: booleanAttribute }</span>)
</div><p class="docs-api-property-name">
<code>canSelectNullableOptions: boolean</code>
</p>
</td>
<td class="docs-api-property-description"><p>By default selecting an option with a <code>null</code> or <code>undefined</code> value will reset the select&#39;s
value. Enable this option if the reset behavior doesn&#39;t match your requirements and instead
the nullable options should become selected. The value of this input can be controlled app-wide
using the <code>MAT_SELECT_CONFIG</code> injection token.</p>
</td>
</tr>





<tr class="docs-api-properties-row">
<td class="docs-api-properties-name-cell"><div class="docs-api-input-marker">
@Input()</div><p class="docs-api-property-name">
Expand Down Expand Up @@ -703,6 +720,20 @@ <h4 id="MatSelectConfig" class="docs-header-link docs-api-h4 docs-api-interface-



<tr class="docs-api-properties-row">
<td class="docs-api-properties-name-cell"><p class="docs-api-property-name">
<code>canSelectNullableOptions: boolean</code>
</p>
</td>
<td class="docs-api-property-description"><p>Whether nullable options can be selected by default.
See <code>MatSelect.canSelectNullableOptions</code> for more information.</p>
</td>
</tr>





<tr class="docs-api-properties-row">
<td class="docs-api-properties-name-cell"><p class="docs-api-property-name">
<code>disableOptionCentering: boolean</code>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,5 @@
<span class="hljs-keyword">export</span> {SelectValueBindingExample} <span class="hljs-keyword">from</span> <span class="hljs-string">&#x27;./select-value-binding/select-value-binding-example&#x27;</span>;
<span class="hljs-keyword">export</span> {SelectReactiveFormExample} <span class="hljs-keyword">from</span> <span class="hljs-string">&#x27;./select-reactive-form/select-reactive-form-example&#x27;</span>;
<span class="hljs-keyword">export</span> {SelectInitialValueExample} <span class="hljs-keyword">from</span> <span class="hljs-string">&#x27;./select-initial-value/select-initial-value-example&#x27;</span>;
<span class="hljs-keyword">export</span> {SelectSelectableNullExample} <span class="hljs-keyword">from</span> <span class="hljs-string">&#x27;./select-selectable-null/select-selectable-null-example&#x27;</span>;
<span class="hljs-keyword">export</span> {SelectHarnessExample} <span class="hljs-keyword">from</span> <span class="hljs-string">&#x27;./select-harness/select-harness-example&#x27;</span>;
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<span class="hljs-tag">&lt;<span class="hljs-name">h4</span>&gt;</span>mat-select allowing selection of nullable options<span class="hljs-tag">&lt;/<span class="hljs-name">h4</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">mat-form-field</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">mat-label</span>&gt;</span>State<span class="hljs-tag">&lt;/<span class="hljs-name">mat-label</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">mat-select</span> [(<span class="hljs-attr">ngModel</span>)]=<span class="hljs-string">&quot;value&quot;</span> <span class="hljs-attr">canSelectNullableOptions</span>&gt;</span>
@for (option of options; track option) {
<span class="hljs-tag">&lt;<span class="hljs-name">mat-option</span> [<span class="hljs-attr">value</span>]=<span class="hljs-string">&quot;option.value&quot;</span>&gt;</span>{{option.label}}<span class="hljs-tag">&lt;/<span class="hljs-name">mat-option</span>&gt;</span>
}
<span class="hljs-tag">&lt;/<span class="hljs-name">mat-select</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">mat-form-field</span>&gt;</span>

<span class="hljs-tag">&lt;<span class="hljs-name">h4</span>&gt;</span>mat-select with default configuration<span class="hljs-tag">&lt;/<span class="hljs-name">h4</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">mat-form-field</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">mat-label</span>&gt;</span>State<span class="hljs-tag">&lt;/<span class="hljs-name">mat-label</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">mat-select</span> [(<span class="hljs-attr">ngModel</span>)]=<span class="hljs-string">&quot;value&quot;</span>&gt;</span>
@for (option of options; track option) {
<span class="hljs-tag">&lt;<span class="hljs-name">mat-option</span> [<span class="hljs-attr">value</span>]=<span class="hljs-string">&quot;option.value&quot;</span>&gt;</span>{{option.label}}<span class="hljs-tag">&lt;/<span class="hljs-name">mat-option</span>&gt;</span>
}
<span class="hljs-tag">&lt;/<span class="hljs-name">mat-select</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">mat-form-field</span>&gt;</span>
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<span class="hljs-keyword">import</span> {Component} <span class="hljs-keyword">from</span> <span class="hljs-string">&#x27;@angular/core&#x27;</span>;
<span class="hljs-keyword">import</span> {FormsModule} <span class="hljs-keyword">from</span> <span class="hljs-string">&#x27;@angular/forms&#x27;</span>;
<span class="hljs-keyword">import</span> {MatInputModule} <span class="hljs-keyword">from</span> <span class="hljs-string">&#x27;@angular/material/input&#x27;</span>;
<span class="hljs-keyword">import</span> {MatSelectModule} <span class="hljs-keyword">from</span> <span class="hljs-string">&#x27;@angular/material/select&#x27;</span>;
<span class="hljs-keyword">import</span> {MatFormFieldModule} <span class="hljs-keyword">from</span> <span class="hljs-string">&#x27;@angular/material/form-field&#x27;</span>;

<span class="hljs-comment">/** <span class="hljs-doctag">@title </span>Select with selectable null options */</span>
<span class="hljs-meta">@Component</span>({
<span class="hljs-attr">selector</span>: <span class="hljs-string">&#x27;select-selectable-null-example&#x27;</span>,
<span class="hljs-attr">templateUrl</span>: <span class="hljs-string">&#x27;select-selectable-null-example.html&#x27;</span>,
<span class="hljs-attr">imports</span>: [MatFormFieldModule, MatSelectModule, MatInputModule, FormsModule],
})
<span class="hljs-keyword">export</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">SelectSelectableNullExample</span> </span>{
<span class="hljs-attr">value</span>: <span class="hljs-built_in">number</span> | <span class="hljs-literal">null</span> = <span class="hljs-literal">null</span>;
options = [
{<span class="hljs-attr">label</span>: <span class="hljs-string">&#x27;None&#x27;</span>, <span class="hljs-attr">value</span>: <span class="hljs-literal">null</span>},
{<span class="hljs-attr">label</span>: <span class="hljs-string">&#x27;One&#x27;</span>, <span class="hljs-attr">value</span>: <span class="hljs-number">1</span>},
{<span class="hljs-attr">label</span>: <span class="hljs-string">&#x27;Two&#x27;</span>, <span class="hljs-attr">value</span>: <span class="hljs-number">2</span>},
{<span class="hljs-attr">label</span>: <span class="hljs-string">&#x27;Three&#x27;</span>, <span class="hljs-attr">value</span>: <span class="hljs-number">3</span>},
];
}
1 change: 1 addition & 0 deletions docs-content/examples-source/material/select/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,5 @@ export {SelectResetExample} from './select-reset/select-reset-example';
export {SelectValueBindingExample} from './select-value-binding/select-value-binding-example';
export {SelectReactiveFormExample} from './select-reactive-form/select-reactive-form-example';
export {SelectInitialValueExample} from './select-initial-value/select-initial-value-example';
export {SelectSelectableNullExample} from './select-selectable-null/select-selectable-null-example';
export {SelectHarnessExample} from './select-harness/select-harness-example';
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<h4>mat-select allowing selection of nullable options</h4>
<mat-form-field>
<mat-label>State</mat-label>
<mat-select [(ngModel)]="value" canSelectNullableOptions>
@for (option of options; track option) {
<mat-option [value]="option.value">{{option.label}}</mat-option>
}
</mat-select>
</mat-form-field>

<h4>mat-select with default configuration</h4>
<mat-form-field>
<mat-label>State</mat-label>
<mat-select [(ngModel)]="value">
@for (option of options; track option) {
<mat-option [value]="option.value">{{option.label}}</mat-option>
}
</mat-select>
</mat-form-field>
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import {Component} from '@angular/core';
import {FormsModule} from '@angular/forms';
import {MatInputModule} from '@angular/material/input';
import {MatSelectModule} from '@angular/material/select';
import {MatFormFieldModule} from '@angular/material/form-field';

/** @title Select with selectable null options */
@Component({
selector: 'select-selectable-null-example',
templateUrl: 'select-selectable-null-example.html',
imports: [MatFormFieldModule, MatSelectModule, MatInputModule, FormsModule],
})
export class SelectSelectableNullExample {
value: number | null = null;
options = [
{label: 'None', value: null},
{label: 'One', value: 1},
{label: 'Two', value: 2},
{label: 'Three', value: 3},
];
}
11 changes: 11 additions & 0 deletions docs-content/overviews/material/select/select.html
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,17 @@ <h3 id="resetting-the-select-value" class="docs-header-link">
<div material-docs-example="select-reset"></div>


<h3 id="allowing-nullable-options-to-be-selected" class="docs-header-link">
<span header-link="allowing-nullable-options-to-be-selected"></span>
Allowing nullable options to be selected
</h3>
<p>By default any options with a <code>null</code> or <code>undefined</code> value will reset the select&#39;s value. If instead
you want the nullable options to be selectable, you can enable the <code>canSelectNullableOptions</code> input.
The default value for the input can be controlled application-wide through the <code>MAT_SELECT_CONFIG</code>
injection token.</p>
<div material-docs-example="select-selectable-null"></div>


<h3 id="creating-groups-of-options" class="docs-header-link">
<span header-link="creating-groups-of-options"></span>
Creating groups of options
Expand Down
2 changes: 1 addition & 1 deletion fesm2022/cdk/a11y.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ class FocusMonitorFocusViaExample {
return origin ? origin + ' focused' : 'blurred';
}
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.1.0-next.2", ngImport: i0, type: FocusMonitorFocusViaExample, deps: [], target: i0.ɵɵFactoryTarget.Component });
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.1.0-next.2", type: FocusMonitorFocusViaExample, isStandalone: true, selector: "focus-monitor-focus-via-example", viewQueries: [{ propertyName: "monitoredEl", first: true, predicate: ["monitored"], descendants: true }], ngImport: i0, template: "<div class=\"example-focus-monitor\">\n <button #monitored>1. Focus Monitored Element ({{origin}})</button>\n <button #unmonitored>2. Not Monitored</button>\n</div>\n\n<mat-form-field>\n <mat-label>Simulated focus origin</mat-label>\n <mat-select #simulatedOrigin value=\"mouse\">\n <mat-option value=\"mouse\">Mouse</mat-option>\n <mat-option value=\"keyboard\">Keyboard</mat-option>\n <mat-option value=\"touch\">Touch</mat-option>\n <mat-option value=\"program\">Programmatic</mat-option>\n </mat-select>\n</mat-form-field>\n\n<button (click)=\"focusMonitor.focusVia(monitored, simulatedOrigin.value)\">\n Focus button #1\n</button>\n<button (click)=\"focusMonitor.focusVia(unmonitored, simulatedOrigin.value)\">\n Focus button #2\n</button>\n", styles: [".example-focus-monitor {\n padding: 20px;\n}\n\n.example-focus-monitor .cdk-mouse-focused {\n background: rgba(255, 0, 0, 0.5);\n}\n\n.example-focus-monitor .cdk-keyboard-focused {\n background: rgba(0, 255, 0, 0.5);\n}\n\n.example-focus-monitor .cdk-touch-focused {\n background: rgba(0, 0, 255, 0.5);\n}\n\n.example-focus-monitor .cdk-program-focused {\n background: rgba(255, 0, 255, 0.5);\n}\n\n.example-focus-monitor button:focus {\n box-shadow: 0 0 30px cyan;\n}\n\nmat-form-field,\nbutton {\n margin-right: 12px;\n}\n"], dependencies: [{ kind: "ngmodule", type: MatFormFieldModule }, { kind: "component", type: i1$1.MatFormField, selector: "mat-form-field", inputs: ["hideRequiredMarker", "color", "floatLabel", "appearance", "subscriptSizing", "hintLabel"], exportAs: ["matFormField"] }, { kind: "directive", type: i1$1.MatLabel, selector: "mat-label" }, { kind: "ngmodule", type: MatSelectModule }, { kind: "component", type: i2.MatSelect, selector: "mat-select", inputs: ["aria-describedby", "panelClass", "disabled", "disableRipple", "tabIndex", "hideSingleSelectionIndicator", "placeholder", "required", "multiple", "disableOptionCentering", "compareWith", "value", "aria-label", "aria-labelledby", "errorStateMatcher", "typeaheadDebounceInterval", "sortComparator", "id", "panelWidth"], outputs: ["openedChange", "opened", "closed", "selectionChange", "valueChange"], exportAs: ["matSelect"] }, { kind: "component", type: i3.MatOption, selector: "mat-option", inputs: ["value", "id", "disabled"], outputs: ["onSelectionChange"], exportAs: ["matOption"] }] });
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.1.0-next.2", type: FocusMonitorFocusViaExample, isStandalone: true, selector: "focus-monitor-focus-via-example", viewQueries: [{ propertyName: "monitoredEl", first: true, predicate: ["monitored"], descendants: true }], ngImport: i0, template: "<div class=\"example-focus-monitor\">\n <button #monitored>1. Focus Monitored Element ({{origin}})</button>\n <button #unmonitored>2. Not Monitored</button>\n</div>\n\n<mat-form-field>\n <mat-label>Simulated focus origin</mat-label>\n <mat-select #simulatedOrigin value=\"mouse\">\n <mat-option value=\"mouse\">Mouse</mat-option>\n <mat-option value=\"keyboard\">Keyboard</mat-option>\n <mat-option value=\"touch\">Touch</mat-option>\n <mat-option value=\"program\">Programmatic</mat-option>\n </mat-select>\n</mat-form-field>\n\n<button (click)=\"focusMonitor.focusVia(monitored, simulatedOrigin.value)\">\n Focus button #1\n</button>\n<button (click)=\"focusMonitor.focusVia(unmonitored, simulatedOrigin.value)\">\n Focus button #2\n</button>\n", styles: [".example-focus-monitor {\n padding: 20px;\n}\n\n.example-focus-monitor .cdk-mouse-focused {\n background: rgba(255, 0, 0, 0.5);\n}\n\n.example-focus-monitor .cdk-keyboard-focused {\n background: rgba(0, 255, 0, 0.5);\n}\n\n.example-focus-monitor .cdk-touch-focused {\n background: rgba(0, 0, 255, 0.5);\n}\n\n.example-focus-monitor .cdk-program-focused {\n background: rgba(255, 0, 255, 0.5);\n}\n\n.example-focus-monitor button:focus {\n box-shadow: 0 0 30px cyan;\n}\n\nmat-form-field,\nbutton {\n margin-right: 12px;\n}\n"], dependencies: [{ kind: "ngmodule", type: MatFormFieldModule }, { kind: "component", type: i1$1.MatFormField, selector: "mat-form-field", inputs: ["hideRequiredMarker", "color", "floatLabel", "appearance", "subscriptSizing", "hintLabel"], exportAs: ["matFormField"] }, { kind: "directive", type: i1$1.MatLabel, selector: "mat-label" }, { kind: "ngmodule", type: MatSelectModule }, { kind: "component", type: i2.MatSelect, selector: "mat-select", inputs: ["aria-describedby", "panelClass", "disabled", "disableRipple", "tabIndex", "hideSingleSelectionIndicator", "placeholder", "required", "multiple", "disableOptionCentering", "compareWith", "value", "aria-label", "aria-labelledby", "errorStateMatcher", "typeaheadDebounceInterval", "sortComparator", "id", "panelWidth", "canSelectNullableOptions"], outputs: ["openedChange", "opened", "closed", "selectionChange", "valueChange"], exportAs: ["matSelect"] }, { kind: "component", type: i3.MatOption, selector: "mat-option", inputs: ["value", "id", "disabled"], outputs: ["onSelectionChange"], exportAs: ["matOption"] }] });
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.1.0-next.2", ngImport: i0, type: FocusMonitorFocusViaExample, decorators: [{
type: Component,
Expand Down
Loading

0 comments on commit 7a2b62b

Please sign in to comment.