mirror of
https://github.com/material-components/material-web.git
synced 2026-01-09 07:21:09 +08:00
chore(behaviors): add ElementInternals mixin
PiperOrigin-RevId: 576937116
This commit is contained in:
parent
0ebd7c786b
commit
e7bc633e18
@ -14,7 +14,6 @@ import {literal, html as staticHtml} from 'lit/static-html.js';
|
||||
|
||||
import {ARIAMixinStrict} from '../../internal/aria/aria.js';
|
||||
import {requestUpdateOnAriaChange} from '../../internal/aria/delegate.js';
|
||||
import {internals} from '../../internal/controller/element-internals.js';
|
||||
import {
|
||||
dispatchActivationClick,
|
||||
isActivationClick,
|
||||
@ -24,11 +23,18 @@ import {
|
||||
FormSubmitterType,
|
||||
setupFormSubmitter,
|
||||
} from '../../internal/controller/form-submitter.js';
|
||||
import {
|
||||
internals,
|
||||
mixinElementInternals,
|
||||
} from '../../labs/behaviors/element-internals.js';
|
||||
|
||||
// Separate variable needed for closure.
|
||||
const buttonBaseClass = mixinElementInternals(LitElement);
|
||||
|
||||
/**
|
||||
* A button component.
|
||||
*/
|
||||
export abstract class Button extends LitElement implements FormSubmitter {
|
||||
export abstract class Button extends buttonBaseClass implements FormSubmitter {
|
||||
static {
|
||||
requestUpdateOnAriaChange(Button);
|
||||
setupFormSubmitter(Button);
|
||||
@ -95,10 +101,6 @@ export abstract class Button extends LitElement implements FormSubmitter {
|
||||
@queryAssignedElements({slot: 'icon', flatten: true})
|
||||
private readonly assignedIcons!: HTMLElement[];
|
||||
|
||||
/** @private */
|
||||
[internals] = (this as HTMLElement) /* needed for closure */
|
||||
.attachInternals();
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
if (!isServer) {
|
||||
|
||||
@ -14,18 +14,24 @@ import {literal, html as staticHtml} from 'lit/static-html.js';
|
||||
|
||||
import {ARIAMixinStrict} from '../../internal/aria/aria.js';
|
||||
import {requestUpdateOnAriaChange} from '../../internal/aria/delegate.js';
|
||||
import {internals} from '../../internal/controller/element-internals.js';
|
||||
import {
|
||||
FormSubmitter,
|
||||
FormSubmitterType,
|
||||
setupFormSubmitter,
|
||||
} from '../../internal/controller/form-submitter.js';
|
||||
import {isRtl} from '../../internal/controller/is-rtl.js';
|
||||
import {
|
||||
internals,
|
||||
mixinElementInternals,
|
||||
} from '../../labs/behaviors/element-internals.js';
|
||||
|
||||
type LinkTarget = '_blank' | '_parent' | '_self' | '_top';
|
||||
|
||||
// Separate variable needed for closure.
|
||||
const iconButtonBaseClass = mixinElementInternals(LitElement);
|
||||
|
||||
// tslint:disable-next-line:enforce-comments-on-exported-symbols
|
||||
export class IconButton extends LitElement implements FormSubmitter {
|
||||
export class IconButton extends iconButtonBaseClass implements FormSubmitter {
|
||||
static {
|
||||
requestUpdateOnAriaChange(IconButton);
|
||||
setupFormSubmitter(IconButton);
|
||||
@ -106,10 +112,6 @@ export class IconButton extends LitElement implements FormSubmitter {
|
||||
|
||||
@state() private flipIcon = isRtl(this, this.flipIconInRtl);
|
||||
|
||||
/** @private */
|
||||
[internals] = (this as HTMLElement) /* needed for closure */
|
||||
.attachInternals();
|
||||
|
||||
/**
|
||||
* Link buttons cannot be disabled.
|
||||
*/
|
||||
|
||||
@ -1,37 +0,0 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2023 Google LLC
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* A unique symbol used for protected access to an instance's
|
||||
* `ElementInternals`.
|
||||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* class MyElement extends LitElement {
|
||||
* static formAssociated = true;
|
||||
*
|
||||
* [internals] = this.attachInternals();
|
||||
* }
|
||||
*
|
||||
* function getForm(element: MyElement) {
|
||||
* return element[internals].form;
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
export const internals = Symbol('internals');
|
||||
|
||||
/**
|
||||
* An instance with `ElementInternals`.
|
||||
*
|
||||
* Use this when protected access is needed for an instance's `ElementInternals`
|
||||
* from other files. A unique symbol is used to access the internals.
|
||||
*/
|
||||
export interface WithInternals {
|
||||
/**
|
||||
* An instance's `ElementInternals`.
|
||||
*/
|
||||
[internals]: ElementInternals;
|
||||
}
|
||||
@ -6,7 +6,10 @@
|
||||
|
||||
import {isServer, ReactiveElement} from 'lit';
|
||||
|
||||
import {internals, WithInternals} from './element-internals.js';
|
||||
import {
|
||||
internals,
|
||||
WithElementInternals,
|
||||
} from '../../labs/behaviors/element-internals.js';
|
||||
|
||||
/**
|
||||
* A string indicating the form submission behavior of the element.
|
||||
@ -23,7 +26,7 @@ export type FormSubmitterType = 'button' | 'submit' | 'reset';
|
||||
* An element that can submit or reset a `<form>`, similar to
|
||||
* `<button type="submit">`.
|
||||
*/
|
||||
export interface FormSubmitter extends ReactiveElement, WithInternals {
|
||||
export interface FormSubmitter extends ReactiveElement, WithElementInternals {
|
||||
/**
|
||||
* A string indicating the form submission behavior of the element.
|
||||
*
|
||||
|
||||
@ -9,10 +9,9 @@
|
||||
import {html, LitElement} from 'lit';
|
||||
import {customElement, property} from 'lit/decorators.js';
|
||||
|
||||
import {mixinElementInternals} from '../../labs/behaviors/element-internals.js';
|
||||
import {Environment} from '../../testing/environment.js';
|
||||
import {Harness} from '../../testing/harness.js';
|
||||
|
||||
import {internals} from './element-internals.js';
|
||||
import {FormSubmitterType, setupFormSubmitter} from './form-submitter.js';
|
||||
|
||||
declare global {
|
||||
@ -22,7 +21,7 @@ declare global {
|
||||
}
|
||||
|
||||
@customElement('test-form-submitter-button')
|
||||
class FormSubmitterButton extends LitElement {
|
||||
class FormSubmitterButton extends mixinElementInternals(LitElement) {
|
||||
static {
|
||||
setupFormSubmitter(FormSubmitterButton);
|
||||
}
|
||||
@ -32,8 +31,6 @@ class FormSubmitterButton extends LitElement {
|
||||
type: FormSubmitterType = 'submit';
|
||||
@property({reflect: true}) name = '';
|
||||
value = '';
|
||||
|
||||
[internals] = this.attachInternals();
|
||||
}
|
||||
|
||||
describe('setupFormSubmitter()', () => {
|
||||
|
||||
62
labs/behaviors/element-internals.ts
Normal file
62
labs/behaviors/element-internals.ts
Normal file
@ -0,0 +1,62 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2023 Google LLC
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import {LitElement} from 'lit';
|
||||
|
||||
import {MixinBase, MixinReturn} from './mixin.js';
|
||||
|
||||
/**
|
||||
* A unique symbol used for protected access to an instance's
|
||||
* `ElementInternals`.
|
||||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* class MyElement extends mixinElementInternals(LitElement) {
|
||||
* constructor() {
|
||||
* super();
|
||||
* this[internals].role = 'button';
|
||||
* }
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
export const internals = Symbol('internals');
|
||||
|
||||
/**
|
||||
* An instance with an `internals` symbol property for the component's
|
||||
* `ElementInternals`.
|
||||
*
|
||||
* Use this when protected access is needed for an instance's `ElementInternals`
|
||||
* from other files. A unique symbol is used to access the internals.
|
||||
*/
|
||||
export interface WithElementInternals {
|
||||
/**
|
||||
* An instance's `ElementInternals`.
|
||||
*/
|
||||
[internals]: ElementInternals;
|
||||
}
|
||||
|
||||
/**
|
||||
* Mixes in an attached `ElementInternals` instance.
|
||||
*
|
||||
* This mixin is only needed when other shared code needs access to a
|
||||
* component's `ElementInternals`, such as form-associated mixins.
|
||||
*
|
||||
* @param base The class to mix functionality into.
|
||||
* @return The provided class with `WithElementInternals` mixed in.
|
||||
*/
|
||||
export function mixinElementInternals<T extends MixinBase<LitElement>>(
|
||||
base: T,
|
||||
): MixinReturn<T, WithElementInternals> {
|
||||
abstract class WithElementInternalsElement
|
||||
extends base
|
||||
implements WithElementInternals
|
||||
{
|
||||
// Cast needed for closure
|
||||
[internals] = (this as HTMLElement).attachInternals();
|
||||
}
|
||||
|
||||
return WithElementInternalsElement;
|
||||
}
|
||||
37
labs/behaviors/element-internals_test.ts
Normal file
37
labs/behaviors/element-internals_test.ts
Normal file
@ -0,0 +1,37 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2023 Google LLC
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
// import 'jasmine'; (google3-only)
|
||||
|
||||
import {html, LitElement} from 'lit';
|
||||
import {customElement} from 'lit/decorators.js';
|
||||
|
||||
import {Environment} from '../../testing/environment.js';
|
||||
|
||||
import {internals, mixinElementInternals} from './element-internals.js';
|
||||
|
||||
describe('mixinElementInternals()', () => {
|
||||
@customElement('test-element-internals')
|
||||
class TestElementInternals extends mixinElementInternals(LitElement) {}
|
||||
|
||||
const env = new Environment();
|
||||
|
||||
async function setupTest() {
|
||||
const root = env.render(
|
||||
html`<test-element-internals></test-element-internals>`,
|
||||
);
|
||||
const element = root.querySelector(
|
||||
'test-element-internals',
|
||||
) as TestElementInternals;
|
||||
await env.waitForStability();
|
||||
return element;
|
||||
}
|
||||
|
||||
it('should provide an `ElementInternals` instance', async () => {
|
||||
const element = await setupTest();
|
||||
expect(element[internals]).toBeInstanceOf(ElementInternals);
|
||||
});
|
||||
});
|
||||
Loading…
x
Reference in New Issue
Block a user