Skip to content

Commit

Permalink
Merge branch 'material-components:main' into cg-fix-validator-custom-…
Browse files Browse the repository at this point in the history
…error
  • Loading branch information
christophe-g authored Dec 21, 2023
2 parents d19e809 + c9360e2 commit d3289ca
Show file tree
Hide file tree
Showing 28 changed files with 797 additions and 219 deletions.
5 changes: 2 additions & 3 deletions .github/workflows/update-size-on-main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,6 @@ jobs:
committer: lit-robot <[email protected]>
title: 'chore: update sizes'
body: This PR was auto generated by the update-size-on-main GitHub action.
reviewers: e111077,asyncliz
branch: auto-update-size
# Don't automatically add Ready for Google label until we're ready
branch: auto-update-sizes
# Don't automatically add reviewers or Ready for Google label until we're ready
# since this will be noisy.
59 changes: 59 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
# Contributing

Thank you for your interest in contributing! The following sections describe
ways to get involved.

## Code of conduct

Please review and follow our [code of conduct](CODE_OF_CONDUCT.md).

## Feedback

User feedback is the most valuable to us. It's a great way to start
contributing!

- [File new issues](https://github.com/material-components/material-web/issues/new/choose)
for bugs you run into or feature requests you have.

- [Create a discussion](https://github.com/material-components/material-web/discussions/new/choose)
for help, feedback on changes, or feature proposals.

## Discord

Join the `#material` channel on [Lit's Discord](https://lit.dev/discord) to chat
directly with the team and other users.

## Pull requests

Pull requests are welcome! Keep a few things in mind:

- Create an
[issue](https://github.com/material-components/material-web/issues/new/choose)
or
[discussion](https://github.com/material-components/material-web/discussions/new/choose)
before opening a pull request.
- Trivial changes, such as documentation, don't need an issue.
- Create draft PRs in the `@material/web/labs` folder for new features.
- Please be patient! It may take a while for the team to review. Keep changes
small and scoped to speed things up.

### New components

Please understand that new component implementations are difficult for us to
directly accept. They need to complete several engineer, design, and
accessibility reviews that are not easy with our externally available workflows.

If you want to help build a new component, create a
[new discussion](https://github.com/material-components/material-web/discussions/new/choose).
Add any design docs, code samples, reference implementations in draft PRs, and
get community feedback.

### Contributor License Agreement

Code contributions must
[sign Google's CLA](https://cla.developers.google.com/clas). When you open a
pull request, our friendly bot will check and provide help if you haven't
signed.

[Set your email in git](https://help.github.com/articles/setting-your-email-in-git/)
to the same email used to sign the CLA.
8 changes: 4 additions & 4 deletions button/internal/button.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,15 +12,15 @@ import {property, query, queryAssignedElements} from 'lit/decorators.js';

import {ARIAMixinStrict} from '../../internal/aria/aria.js';
import {requestUpdateOnAriaChange} from '../../internal/aria/delegate.js';
import {
dispatchActivationClick,
isActivationClick,
} from '../../internal/controller/events.js';
import {
FormSubmitter,
FormSubmitterType,
setupFormSubmitter,
} from '../../internal/controller/form-submitter.js';
import {
dispatchActivationClick,
isActivationClick,
} from '../../internal/events/form-label-activation.js';
import {
internals,
mixinElementInternals,
Expand Down
4 changes: 2 additions & 2 deletions checkbox/internal/checkbox.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,8 @@ import {requestUpdateOnAriaChange} from '../../internal/aria/delegate.js';
import {
dispatchActivationClick,
isActivationClick,
redispatchEvent,
} from '../../internal/controller/events.js';
} from '../../internal/events/form-label-activation.js';
import {redispatchEvent} from '../../internal/events/redispatch-event.js';
import {
createValidator,
getValidityAnchor,
Expand Down
2 changes: 1 addition & 1 deletion chips/internal/filter-chip.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import {html, nothing} from 'lit';
import {property, query} from 'lit/decorators.js';

import {ARIAMixinStrict} from '../../internal/aria/aria.js';
import {redispatchEvent} from '../../internal/controller/events.js';
import {redispatchEvent} from '../../internal/events/redispatch-event.js';

import {MultiActionChip} from './multi-action-chip.js';
import {renderRemoveButton} from './trailing-icons.js';
Expand Down
44 changes: 43 additions & 1 deletion dialog/internal/dialog.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import {classMap} from 'lit/directives/class-map.js';

import {ARIAMixinStrict} from '../../internal/aria/aria.js';
import {requestUpdateOnAriaChange} from '../../internal/aria/delegate.js';
import {redispatchEvent} from '../../internal/controller/events.js';
import {redispatchEvent} from '../../internal/events/redispatch-event.js';

import {
DIALOG_DEFAULT_CLOSE_ANIMATION,
Expand Down Expand Up @@ -114,6 +114,21 @@ export class Dialog extends LitElement {
@state() private hasActions = false;
@state() private hasIcon = false;

// See https://bugs.chromium.org/p/chromium/issues/detail?id=1512224
// Chrome v120 has a bug where escape keys do not trigger cancels. If we get
// a dialog "close" event that is triggered without a "cancel" after an escape
// keydown, then we need to manually trigger our closing logic.
//
// This bug occurs when pressing escape to close a dialog without first
// interacting with the dialog's content.
//
// Cleanup tracking:
// https://github.com/material-components/material-web/issues/5330
// This can be removed when full CloseWatcher support added and the above bug
// in Chromium is fixed to fire 'cancel' with one escape press and close with
// multiple.
private escapePressedWithoutCancel = false;

constructor() {
super();
if (!isServer) {
Expand Down Expand Up @@ -243,6 +258,8 @@ export class Dialog extends LitElement {
role=${this.type === 'alert' ? 'alertdialog' : nothing}
@cancel=${this.handleCancel}
@click=${this.handleDialogClick}
@close=${this.handleClose}
@keydown=${this.handleKeydown}
.returnValue=${this.returnValue || nothing}>
<div class="container" @click=${this.handleContentClick}>
<div class="headline">
Expand Down Expand Up @@ -328,6 +345,7 @@ export class Dialog extends LitElement {
return;
}

this.escapePressedWithoutCancel = false;
const preventDefault = !redispatchEvent(this, event);
// We always prevent default on the original dialog event since we'll
// animate closing it before it actually closes.
Expand All @@ -339,6 +357,30 @@ export class Dialog extends LitElement {
this.close();
}

private handleClose() {
if (!this.escapePressedWithoutCancel) {
return;
}

this.escapePressedWithoutCancel = false;
this.dialog?.dispatchEvent(new Event('cancel', {cancelable: true}));
}

private handleKeydown(event: KeyboardEvent) {
if (event.key !== 'Escape') {
return;
}

// An escape key was pressed. If a "close" event fires next without a
// "cancel" event first, then we know we're in the Chrome v120 bug.
this.escapePressedWithoutCancel = true;
// Wait a full task for the cancel/close event listeners to fire, then
// reset the flag.
setTimeout(() => {
this.escapePressedWithoutCancel = false;
});
}

private async animateDialog(animation: DialogAnimation) {
const {dialog, scrim, container, headline, content, actions} = this;
if (!dialog || !scrim || !container || !headline || !content || !actions) {
Expand Down
6 changes: 3 additions & 3 deletions docs/components/progress.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ ssrOnly: true

<!--*
# Document freshness: For more information, see go/fresh-source.
freshness: { owner: 'lizmitchell' reviewed: '2023-08-23' }
freshness: { owner: 'lizmitchell' reviewed: '2023-12-18' }
tag: 'docType:reference'
*-->

Expand Down Expand Up @@ -261,7 +261,7 @@ Token | Default value
> Note: the active indicator width must be specified as a unit-less percentage
> of the size.
* [All tokens](https://github.com/material-components/material-web/blob/main/tokens/_md-comp-circular-progress-indicator.scss)
* [All tokens](https://github.com/material-components/material-web/blob/main/tokens/_md-comp-circular-progress.scss)
<!-- {.external} -->

### Circular progress example
Expand Down Expand Up @@ -320,7 +320,7 @@ Token | Default value
`--md-linear-progress-active-indicator-color` | `--md-sys-color-primary`
`--md-linear-progress-active-indicator-height` | `4px`

* [All tokens](https://github.com/material-components/material-web/blob/main/tokens/_md-comp-linear-progress-indicator.scss)
* [All tokens](https://github.com/material-components/material-web/blob/main/tokens/_md-comp-linear-progress.scss)
<!-- {.external} -->

### Linear progress example
Expand Down
176 changes: 176 additions & 0 deletions internal/events/dispatch-hooks.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,176 @@
/**
* @license
* Copyright 2023 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/

/**
* A symbol used to access dispatch hooks on an event.
*/
const dispatchHooks = Symbol('dispatchHooks');

/**
* An `Event` with additional symbols for dispatch hooks.
*/
interface EventWithDispatchHooks extends Event {
[dispatchHooks]: EventTarget;
}

/**
* Add a hook for an event that is called after the event is dispatched and
* propagates to other event listeners.
*
* This is useful for behaviors that need to check if an event is canceled.
*
* The callback is invoked synchronously, which allows for better integration
* with synchronous platform APIs (like `<form>` or `<label>` clicking).
*
* Note: `setupDispatchHooks()` must be called on the element before adding any
* other event listeners. Call it in the constructor of an element or
* controller.
*
* @example
* ```ts
* class MyControl extends LitElement {
* constructor() {
* super();
* setupDispatchHooks(this, 'click');
* this.addEventListener('click', event => {
* afterDispatch(event, () => {
* if (event.defaultPrevented) {
* return
* }
*
* // ... perform logic
* });
* });
* }
* }
* ```
*
* @example
* ```ts
* class MyController implements ReactiveController {
* constructor(host: ReactiveElement) {
* // setupDispatchHooks() may be called multiple times for the same
* // element and events, making it safe for multiple controllers to use it.
* setupDispatchHooks(host, 'click');
* host.addEventListener('click', event => {
* afterDispatch(event, () => {
* if (event.defaultPrevented) {
* return;
* }
*
* // ... perform logic
* });
* });
* }
* }
* ```
*
* @param event The event to add a hook to.
* @param callback A hook that is called after the event finishes dispatching.
*/
export function afterDispatch(event: Event, callback: () => void) {
const hooks = (event as EventWithDispatchHooks)[dispatchHooks];
if (!hooks) {
throw new Error(`'${event.type}' event needs setupDispatchHooks().`);
}

hooks.addEventListener('after', callback);
}

/**
* A lookup map of elements and event types that have a dispatch hook listener
* set up. Used to ensure we don't set up multiple hook listeners on the same
* element for the same event.
*/
const ELEMENT_DISPATCH_HOOK_TYPES = new WeakMap<Element, Set<string>>();

/**
* Sets up an element to add dispatch hooks to given event types. This must be
* called before adding any event listeners that need to use dispatch hooks
* like `afterDispatch()`.
*
* This function is safe to call multiple times with the same element or event
* types. Call it in the constructor of elements, mixins, and controllers to
* ensure it is set up before external listeners.
*
* @example
* ```ts
* class MyControl extends LitElement {
* constructor() {
* super();
* setupDispatchHooks(this, 'click');
* this.addEventListener('click', this.listenerUsingAfterDispatch);
* }
* }
* ```
*
* @param element The element to set up event dispatch hooks for.
* @param eventTypes The event types to add dispatch hooks to.
*/
export function setupDispatchHooks(
element: Element,
...eventTypes: [string, ...string[]]
) {
let typesAlreadySetUp = ELEMENT_DISPATCH_HOOK_TYPES.get(element);
if (!typesAlreadySetUp) {
typesAlreadySetUp = new Set();
ELEMENT_DISPATCH_HOOK_TYPES.set(element, typesAlreadySetUp);
}

for (const eventType of eventTypes) {
// Don't register multiple dispatch hook listeners. A second registration
// would lead to the second listener re-dispatching a re-dispatched event,
// which can cause an infinite loop inside the other one.
if (typesAlreadySetUp.has(eventType)) {
continue;
}

// When we re-dispatch the event, it's going to immediately trigger this
// listener again. Use a flag to ignore it.
let isRedispatching = false;
element.addEventListener(
eventType,
(event: Event) => {
if (isRedispatching) {
return;
}

// Do not let the event propagate to any other listener (not just
// bubbling listeners with `stopPropagation()`).
event.stopImmediatePropagation();
// Make a copy.
const eventCopy = Reflect.construct(event.constructor, [
event.type,
event,
]);

// Add hooks onto the event.
const hooks = new EventTarget();
(eventCopy as EventWithDispatchHooks)[dispatchHooks] = hooks;

// Re-dispatch the event. We can't reuse `redispatchEvent()` since we
// need to add the hooks to the copy before it's dispatched.
isRedispatching = true;
const dispatched = element.dispatchEvent(eventCopy);
isRedispatching = false;
if (!dispatched) {
event.preventDefault();
}

// Synchronously call afterDispatch() hooks.
hooks.dispatchEvent(new Event('after'));
},
{
// Ensure this listener runs before other listeners.
// `setupDispatchHooks()` should be called in constructors to also
// ensure they run before any other externally-added capture listeners.
capture: true,
},
);

typesAlreadySetUp.add(eventType);
}
}
Loading

0 comments on commit d3289ca

Please sign in to comment.