Skip to content

Commit 4e9bff5

Browse files
authored
Merge branch 'angular:main' into chips-overflow
2 parents a5da903 + 8e9fc04 commit 4e9bff5

File tree

59 files changed

+1174
-872
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

59 files changed

+1174
-872
lines changed

.bazelrc

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
# Frozen lockfile
2+
common --lockfile_mode=error
3+
14
# Required by `rules_ts`.
25
common --@aspect_rules_ts//ts:skipLibCheck=always
36
common --@aspect_rules_ts//ts:default_to_tsc_transpiler

CHANGELOG.md

Lines changed: 55 additions & 101 deletions
Large diffs are not rendered by default.

goldens/cdk/menu/index.api.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,7 @@ export class CdkMenuItem implements FocusableOption, FocusableElement, Toggler,
138138
getLabel(): string;
139139
getMenu(): Menu | undefined;
140140
getMenuTrigger(): CdkMenuTrigger | null;
141+
protected _handleClick(event: MouseEvent): void;
141142
get hasMenu(): boolean;
142143
isMenuOpen(): boolean;
143144
// (undocumented)

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@
5252
"ci-docs-monitor-test": "node --no-warnings=ExperimentalWarning --loader ts-node/esm/transpile-only scripts/docs-deploy/monitoring/ci-test.mts",
5353
"prepare": "husky"
5454
},
55-
"version": "20.3.0-next.0",
55+
"version": "21.0.0-next.0",
5656
"dependencies": {
5757
"@angular-devkit/core": "catalog:",
5858
"@angular-devkit/schematics": "catalog:",

renovate.json

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,17 @@
11
{
22
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
33
"extends": ["github>angular/dev-infra//renovate-presets/default.json5"],
4-
"packageRules": [],
4+
"baseBranchPatterns": ["main", "20.2.x"],
55
"ignoreDeps": ["stylelint", "selenium-webdriver", "@types/selenium-webdriver", "typescript"],
6-
"ignorePaths": ["docs/src/assets/stackblitz/**", "integration/**"]
6+
"ignorePaths": ["docs/src/assets/stackblitz/**", "integration/**"],
7+
"packageRules": [
8+
{
9+
"matchBaseBranches": ["main"],
10+
"addLabels": ["target: minor"]
11+
},
12+
{
13+
"matchBaseBranches": ["!main"],
14+
"addLabels": ["target: patch"]
15+
}
16+
]
717
}

src/cdk-experimental/ui-patterns/behaviors/list-selection/list-selection.spec.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -168,6 +168,13 @@ describe('List Selection', () => {
168168
selection.deselectAll(); // []
169169
expect(selection.inputs.value().length).toBe(0);
170170
});
171+
172+
it('should deselect items that are not in the list', () => {
173+
const selection = getSelection({multi: signal(true)});
174+
selection.inputs.value.update(() => [5]);
175+
selection.deselectAll();
176+
expect(selection.inputs.value().length).toBe(0);
177+
});
171178
});
172179

173180
describe('#toggleAll', () => {

src/cdk-experimental/ui-patterns/behaviors/list-selection/list-selection.ts

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -102,8 +102,25 @@ export class ListSelection<T extends ListSelectionItem<V>, V> {
102102

103103
/** Deselects all items in the list. */
104104
deselectAll() {
105-
for (const item of this.inputs.items()) {
106-
this.deselect(item);
105+
// If an item is not in the list, it forcefully gets deselected.
106+
// This actually creates a bug for the following edge case:
107+
//
108+
// Setup: An item is not in the list (maybe it's lazily loaded), and it is disabled & selected.
109+
// Expected: If deselectAll() is called, it should NOT get deselected (because it is disabled).
110+
// Actual: Calling deselectAll() will still deselect the item.
111+
//
112+
// Why? Because we can't check if the item is disabled if it's not in the list.
113+
//
114+
// Alternatively, we could NOT deselect items that are not in the list, but this has the
115+
// inverse (and more common) effect of keeping enabled items selected when they aren't in the
116+
// list.
117+
118+
for (const value of this.inputs.value()) {
119+
const item = this.inputs.items().find(i => i.value() === value);
120+
121+
item
122+
? this.deselect(item)
123+
: this.inputs.value.update(values => values.filter(v => v !== value));
107124
}
108125
}
109126

src/cdk/menu/menu-base.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -196,7 +196,11 @@ export abstract class CdkMenuBase
196196

197197
/** Setup the FocusKeyManager with the correct orientation for the menu. */
198198
private _setKeyManager() {
199-
this.keyManager = new FocusKeyManager(this.items).withWrap().withTypeAhead().withHomeAndEnd();
199+
this.keyManager = new FocusKeyManager(this.items)
200+
.withWrap()
201+
.withTypeAhead()
202+
.withHomeAndEnd()
203+
.skipPredicate(() => false);
200204

201205
if (this.orientation === 'horizontal') {
202206
this.keyManager.withHorizontalOrientation(this.dir?.value || 'ltr');

src/cdk/menu/menu-item.spec.ts

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import {Component, Type} from '@angular/core';
22
import {ComponentFixture, TestBed} from '@angular/core/testing';
3-
import {dispatchKeyboardEvent} from '../testing/private';
3+
import {dispatchFakeEvent, dispatchKeyboardEvent} from '../testing/private';
44
import {By} from '@angular/platform-browser';
55
import {ENTER} from '../keycodes';
66
import {CdkMenuModule} from './menu-module';
@@ -44,6 +44,33 @@ describe('MenuItem', () => {
4444
expect(nativeButton.hasAttribute('aria-disabled')).toBeFalse();
4545
});
4646

47+
it('should toggle a class when the item is disabled', () => {
48+
expect(nativeButton.classList).not.toContain('cdk-menu-item-disabled');
49+
50+
menuItem.disabled = true;
51+
fixture.changeDetectorRef.markForCheck();
52+
fixture.detectChanges();
53+
54+
expect(nativeButton.classList).toContain('cdk-menu-item-disabled');
55+
56+
menuItem.disabled = false;
57+
fixture.changeDetectorRef.markForCheck();
58+
fixture.detectChanges();
59+
60+
expect(nativeButton.classList).not.toContain('cdk-menu-item-disabled');
61+
});
62+
63+
it('should prevent the default click action when clicking on a disabled button', () => {
64+
menuItem.disabled = true;
65+
fixture.changeDetectorRef.markForCheck();
66+
fixture.detectChanges();
67+
68+
const event = dispatchFakeEvent(nativeButton, 'click');
69+
fixture.detectChanges();
70+
71+
expect(event.defaultPrevented).toBe(true);
72+
});
73+
4774
it('should not have a menu', () => {
4875
expect(menuItem.hasMenu).toBeFalse();
4976
});

src/cdk/menu/menu-item.ts

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,11 +40,12 @@ import {eventDispatchesNativeClick} from './event-detection';
4040
host: {
4141
'role': 'menuitem',
4242
'class': 'cdk-menu-item',
43+
'[class.cdk-menu-item-disabled]': 'disabled',
4344
'[tabindex]': '_tabindex',
4445
'[attr.aria-disabled]': 'disabled || null',
4546
'(blur)': '_resetTabIndex()',
4647
'(focus)': '_setTabIndex()',
47-
'(click)': 'trigger()',
48+
'(click)': '_handleClick($event)',
4849
'(keydown)': '_onKeydown($event)',
4950
},
5051
})
@@ -181,6 +182,16 @@ export class CdkMenuItem implements FocusableOption, FocusableElement, Toggler,
181182
}
182183
}
183184

185+
/** Handles click events on the item. */
186+
protected _handleClick(event: MouseEvent) {
187+
if (this.disabled) {
188+
event.preventDefault();
189+
event.stopPropagation();
190+
} else {
191+
this.trigger();
192+
}
193+
}
194+
184195
/**
185196
* Handles keyboard events for the menu item, specifically either triggering the user defined
186197
* callback or opening/closing the current menu based on whether the left or right arrow key was

0 commit comments

Comments
 (0)