Skip to content

Commit 2a4db32

Browse files
authored
Merge branch 'main' into md-filter-chip-icons
2 parents b30d09e + 739cf33 commit 2a4db32

16 files changed

+299
-18
lines changed

CODE_OF_CONDUCT.md

Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
# Contributor Covenant Code of Conduct
2+
3+
## Our Pledge
4+
5+
We as members, contributors, and leaders pledge to make participation in our
6+
community a harassment-free experience for everyone, regardless of age, body
7+
size, visible or invisible disability, ethnicity, sex characteristics, gender
8+
identity and expression, level of experience, education, socio-economic status,
9+
nationality, personal appearance, race, religion, or sexual identity and
10+
orientation.
11+
12+
We pledge to act and interact in ways that contribute to an open, welcoming,
13+
diverse, inclusive, and healthy community.
14+
15+
## Our Standards
16+
17+
Examples of behavior that contributes to a positive environment for our
18+
community include:
19+
20+
* Demonstrating empathy and kindness toward other people
21+
* Being respectful of differing opinions, viewpoints, and experiences
22+
* Giving and gracefully accepting constructive feedback
23+
* Accepting responsibility and apologizing to those affected by our mistakes,
24+
and learning from the experience
25+
* Focusing on what is best not just for us as individuals, but for the overall
26+
community
27+
28+
Examples of unacceptable behavior include:
29+
30+
* The use of sexualized language or imagery, and sexual attention or advances
31+
of any kind
32+
* Trolling, insulting or derogatory comments, and personal or political
33+
attacks
34+
* Public or private harassment
35+
* Publishing others' private information, such as a physical or email address,
36+
without their explicit permission
37+
* Other conduct which could reasonably be considered inappropriate in a
38+
professional setting
39+
40+
## Enforcement Responsibilities
41+
42+
Community leaders are responsible for clarifying and enforcing our standards of
43+
acceptable behavior and will take appropriate and fair corrective action in
44+
response to any behavior that they deem inappropriate, threatening, offensive,
45+
or harmful.
46+
47+
Community leaders have the right and responsibility to remove, edit, or reject
48+
comments, commits, code, wiki edits, issues, and other contributions that are
49+
not aligned to this Code of Conduct, and will communicate reasons for moderation
50+
decisions when appropriate.
51+
52+
## Scope
53+
54+
This Code of Conduct applies within all community spaces, and also applies when
55+
an individual is officially representing the community in public spaces.
56+
Examples of representing our community include using an official e-mail address,
57+
posting via an official social media account, or acting as an appointed
58+
representative at an online or offline event.
59+
60+
## Enforcement
61+
62+
Instances of abusive, harassing, or otherwise unacceptable behavior may be
63+
reported to the community leaders responsible for enforcement at
64+
material-web-contact@google.com. All complaints will be reviewed and
65+
investigated promptly and fairly.
66+
67+
All community leaders are obligated to respect the privacy and security of the
68+
reporter of any incident.
69+
70+
## Enforcement Guidelines
71+
72+
Community leaders will follow these Community Impact Guidelines in determining
73+
the consequences for any action they deem in violation of this Code of Conduct:
74+
75+
### 1. Correction
76+
77+
**Community Impact**: Use of inappropriate language or other behavior deemed
78+
unprofessional or unwelcome in the community.
79+
80+
**Consequence**: A private, written warning from community leaders, providing
81+
clarity around the nature of the violation and an explanation of why the
82+
behavior was inappropriate. A public apology may be requested.
83+
84+
### 2. Warning
85+
86+
**Community Impact**: A violation through a single incident or series of
87+
actions.
88+
89+
**Consequence**: A warning with consequences for continued behavior. No
90+
interaction with the people involved, including unsolicited interaction with
91+
those enforcing the Code of Conduct, for a specified period of time. This
92+
includes avoiding interactions in community spaces as well as external channels
93+
like social media. Violating these terms may lead to a temporary or permanent
94+
ban.
95+
96+
### 3. Temporary Ban
97+
98+
**Community Impact**: A serious violation of community standards, including
99+
sustained inappropriate behavior.
100+
101+
**Consequence**: A temporary ban from any sort of interaction or public
102+
communication with the community for a specified period of time. No public or
103+
private interaction with the people involved, including unsolicited interaction
104+
with those enforcing the Code of Conduct, is allowed during this period.
105+
Violating these terms may lead to a permanent ban.
106+
107+
### 4. Permanent Ban
108+
109+
**Community Impact**: Demonstrating a pattern of violation of community
110+
standards, including sustained inappropriate behavior, harassment of an
111+
individual, or aggression toward or disparagement of classes of individuals.
112+
113+
**Consequence**: A permanent ban from any sort of public interaction within the
114+
community.
115+
116+
## Attribution
117+
118+
This Code of Conduct is adapted from the [Contributor Covenant][homepage],
119+
version 2.0, available at
120+
https://www.contributor-covenant.org/version/2/0/code_of_conduct.html.
121+
122+
Community Impact Guidelines were inspired by
123+
[Mozilla's code of conduct enforcement ladder](https://github.com/mozilla/diversity).
124+
125+
[homepage]: https://www.contributor-covenant.org
126+
127+
For answers to common questions about this code of conduct, see the FAQ at
128+
https://www.contributor-covenant.org/faq. Translations are available at
129+
https://www.contributor-covenant.org/translations.

SECURITY.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
# Security Policy
2+
3+
## Supported Versions
4+
5+
Security updates are only applied to the latest published version.
6+
7+
## Reporting a Vulnerability
8+
9+
Report vulnerabilities in a private
10+
[GitHub security advisory](https://github.com/material-components/material-web/security/advisories/new).
11+
12+
**Please do not disclose security vulnerabilities in public issues.**

checkbox/internal/checkbox.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,7 @@ export class Checkbox extends checkboxBaseClass {
153153
?required=${this.required}
154154
.indeterminate=${this.indeterminate}
155155
.checked=${this.checked}
156+
@input=${this.handleInput}
156157
@change=${this.handleChange} />
157158
158159
<div class="outline"></div>
@@ -167,11 +168,15 @@ export class Checkbox extends checkboxBaseClass {
167168
`;
168169
}
169170

170-
private handleChange(event: Event) {
171+
private handleInput(event: Event) {
171172
const target = event.target as HTMLInputElement;
172173
this.checked = target.checked;
173174
this.indeterminate = target.indeterminate;
175+
// <input> 'input' event bubbles and is composed, don't re-dispatch it.
176+
}
174177

178+
private handleChange(event: Event) {
179+
// <input> 'change' event is not composed, re-dispatch it.
175180
redispatchEvent(this, event);
176181
}
177182

checkbox/internal/checkbox_test.ts

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,42 @@ describe('checkbox', () => {
8282
expect(inputHandler).toHaveBeenCalledTimes(1);
8383
expect(inputHandler).toHaveBeenCalledWith(jasmine.any(Event));
8484
});
85+
86+
it('checkbox state is updated during input event listeners', async () => {
87+
const {harness} = await setupTest();
88+
let state = false;
89+
const inputHandler = jasmine
90+
.createSpy('inputHandler')
91+
.and.callFake(() => {
92+
state = harness.element.checked;
93+
});
94+
95+
harness.element.addEventListener('input', inputHandler);
96+
97+
await harness.clickWithMouse();
98+
expect(inputHandler).withContext('input listener').toHaveBeenCalled();
99+
expect(state)
100+
.withContext('checkbox.checked during input listener')
101+
.toBeTrue();
102+
});
103+
104+
it('checkbox state is updated during change event listeners', async () => {
105+
const {harness} = await setupTest();
106+
let state = false;
107+
const changeHandler = jasmine
108+
.createSpy('changeHandler')
109+
.and.callFake(() => {
110+
state = harness.element.checked;
111+
});
112+
113+
harness.element.addEventListener('change', changeHandler);
114+
115+
await harness.clickWithMouse();
116+
expect(changeHandler).withContext('change listener').toHaveBeenCalled();
117+
expect(state)
118+
.withContext('checkbox.checked during change listener')
119+
.toBeTrue();
120+
});
85121
});
86122

87123
describe('checked', () => {

chips/internal/_shared.scss

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -89,16 +89,19 @@
8989
border: none;
9090
border-radius: inherit;
9191
display: flex;
92-
gap: 8px;
9392
outline: none;
9493
padding: 0;
9594
position: relative;
9695
text-decoration: none;
9796
}
9897

9998
.primary.action {
100-
padding-inline-start: 8px;
101-
padding-inline-end: 16px;
99+
padding-inline-start: var(--_leading-space);
100+
padding-inline-end: var(--_trailing-space);
101+
}
102+
103+
.has-icon .primary.action {
104+
padding-inline-start: var(--_with-leading-icon-leading-space);
102105
}
103106

104107
.touch {
@@ -190,6 +193,11 @@
190193
color: var(--_leading-icon-color);
191194
}
192195

196+
.leading.icon ::slotted(*),
197+
.leading.icon svg {
198+
margin-inline-end: var(--_icon-label-space);
199+
}
200+
193201
:where(:hover) .leading.icon {
194202
color: var(--_hover-leading-icon-color);
195203
}

chips/internal/_trailing-icon.scss

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,8 @@
1111
.trailing.action {
1212
align-items: center;
1313
justify-content: center;
14-
padding: 0 8px;
14+
padding-inline-start: var(--_icon-label-space);
15+
padding-inline-end: var(--_with-trailing-icon-trailing-space);
1516
}
1617

1718
.trailing.action :is(md-ripple, md-focus-ring) {

chips/internal/chip.ts

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,15 @@ export abstract class Chip extends LitElement {
5151
*/
5252
@property() label = '';
5353

54+
/**
55+
* Only needed for SSR.
56+
*
57+
* Add this attribute when a chip has a `slot="icon"` to avoid a Flash Of
58+
* Unstyled Content.
59+
*/
60+
@property({type: Boolean, reflect: true, attribute: 'has-icon'}) hasIcon =
61+
false;
62+
5463
/**
5564
* The `id` of the action the primary focus ring and ripple are for.
5665
* TODO(b/310046938): use the same id for both elements
@@ -90,6 +99,7 @@ export abstract class Chip extends LitElement {
9099
protected getContainerClasses(): ClassInfo {
91100
return {
92101
'disabled': this.disabled,
102+
'has-icon': this.hasIcon,
93103
};
94104
}
95105

@@ -109,7 +119,7 @@ export abstract class Chip extends LitElement {
109119
}
110120

111121
protected renderLeadingIcon(): TemplateResult {
112-
return html`<slot name="icon"></slot>`;
122+
return html`<slot name="icon" @slotchange=${this.handleIconChange}></slot>`;
113123
}
114124

115125
protected abstract renderPrimaryAction(content: unknown): unknown;
@@ -123,4 +133,9 @@ export abstract class Chip extends LitElement {
123133
<span class="touch"></span>
124134
`;
125135
}
136+
137+
private handleIconChange(event: Event) {
138+
const slot = event.target as HTMLSlotElement;
139+
this.hasIcon = slot.assignedElements({flatten: true}).length > 0;
140+
}
126141
}

chips/internal/filter-chip.ts

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,15 @@ export class FilterChip extends MultiActionChip {
2525
@property({type: Boolean, attribute: 'has-trailing'}) hasTrailing = false;
2626
@property({type: Boolean, reflect: true}) selected = false;
2727

28+
/**
29+
* Only needed for SSR.
30+
*
31+
* Add this attribute when a filter chip has a `slot="selected-icon"` to avoid
32+
* a Flash Of Unstyled Content.
33+
*/
34+
@property({type: Boolean, reflect: true, attribute: 'has-selected-icon'})
35+
hasSelectedIcon = false;
36+
2837
protected get primaryId() {
2938
return 'button';
3039
}
@@ -39,7 +48,8 @@ export class FilterChip extends MultiActionChip {
3948
...super.getContainerClasses(),
4049
elevated: this.elevated,
4150
selected: this.selected,
42-
'has-trailing': this.hasTrailing,
51+
'has-trailing': this.removable,
52+
'has-icon': this.hasIcon || this.selected,
4353
};
4454
}
4555

@@ -64,10 +74,12 @@ export class FilterChip extends MultiActionChip {
6474
}
6575

6676
return html`
67-
<svg class="checkmark" viewBox="0 0 18 18" aria-hidden="true">
68-
<path
69-
d="M6.75012 12.1274L3.62262 8.99988L2.55762 10.0574L6.75012 14.2499L15.7501 5.24988L14.6926 4.19238L6.75012 12.1274Z" />
70-
</svg>
77+
<slot name="selected-icon">
78+
<svg class="checkmark" viewBox="0 0 18 18" aria-hidden="true">
79+
<path
80+
d="M6.75012 12.1274L3.62262 8.99988L2.55762 10.0574L6.75012 14.2499L15.7501 5.24988L14.6926 4.19238L6.75012 12.1274Z" />
81+
</svg>
82+
</slot>
7183
`;
7284
}
7385

internal/controller/form-submitter.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -93,9 +93,9 @@ export function setupFormSubmitter(ctor: FormSubmitterConstructor) {
9393
return;
9494
}
9595

96-
// Wait a microtask for event bubbling to complete.
96+
// Wait a full task for event bubbling to complete.
9797
await new Promise<void>((resolve) => {
98-
resolve();
98+
setTimeout(resolve);
9999
});
100100

101101
if (event.defaultPrevented) {

internal/controller/form-submitter_test.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,8 @@ describe('setupFormSubmitter()', () => {
6868
spyOn(form, 'requestSubmit');
6969
spyOn(form, 'reset');
7070
await harness.clickWithMouse();
71+
// Submission happens after a task
72+
await env.waitForStability();
7173

7274
expect(form.requestSubmit).toHaveBeenCalled();
7375
expect(form.reset).not.toHaveBeenCalled();
@@ -80,6 +82,8 @@ describe('setupFormSubmitter()', () => {
8082
spyOn(form, 'requestSubmit');
8183
spyOn(form, 'reset');
8284
await harness.clickWithMouse();
85+
// Submission happens after a task
86+
await env.waitForStability();
8387

8488
expect(form.requestSubmit).not.toHaveBeenCalled();
8589
expect(form.reset).toHaveBeenCalled();
@@ -100,6 +104,8 @@ describe('setupFormSubmitter()', () => {
100104
);
101105

102106
await harness.clickWithMouse();
107+
// Submission happens after a task
108+
await env.waitForStability();
103109

104110
expect(form.requestSubmit).not.toHaveBeenCalled();
105111
});
@@ -115,6 +121,8 @@ describe('setupFormSubmitter()', () => {
115121
form.addEventListener('submit', submitListener);
116122

117123
await harness.clickWithMouse();
124+
// Submission happens after a task
125+
await env.waitForStability();
118126

119127
expect(submitListener).toHaveBeenCalled();
120128
const event = submitListener.calls.argsFor(0)[0] as SubmitEvent;
@@ -133,6 +141,8 @@ describe('setupFormSubmitter()', () => {
133141
harness.element.value = 'bar';
134142

135143
await harness.clickWithMouse();
144+
// Submission happens after a task
145+
await env.waitForStability();
136146

137147
const formData = Array.from(new FormData(form));
138148
expect(formData.length).withContext('formData.length').toBe(1);

0 commit comments

Comments
 (0)