UNPKG

@material/web

Version:
121 lines 3.93 kB
/** * @license * Copyright 2023 Google LLC * SPDX-License-Identifier: Apache-2.0 */ import { isServer } from 'lit'; /** * A key to retrieve an `Attachable` element's `AttachableController` from a * global `MutationObserver`. */ const ATTACHABLE_CONTROLLER = Symbol('attachableController'); let FOR_ATTRIBUTE_OBSERVER; if (!isServer) { /** * A global `MutationObserver` that reacts to `for` attribute changes on * `Attachable` elements. If the `for` attribute changes, the controller will * re-attach to the new referenced element. */ FOR_ATTRIBUTE_OBSERVER = new MutationObserver((records) => { for (const record of records) { // When a control's `for` attribute changes, inform its // `AttachableController` to update to a new control. record.target[ATTACHABLE_CONTROLLER]?.hostConnected(); } }); } /** * A controller that provides an implementation for `Attachable` elements. * * @example * ```ts * class MyElement extends LitElement implements Attachable { * get control() { return this.attachableController.control; } * * private readonly attachableController = new AttachableController( * this, * (previousControl, newControl) => { * previousControl?.removeEventListener('click', this.handleClick); * newControl?.addEventListener('click', this.handleClick); * } * ); * * // Implement remaining `Attachable` properties/methods that call the * // controller's properties/methods. * } * ``` */ export class AttachableController { get htmlFor() { return this.host.getAttribute('for'); } set htmlFor(htmlFor) { if (htmlFor === null) { this.host.removeAttribute('for'); } else { this.host.setAttribute('for', htmlFor); } } get control() { if (this.host.hasAttribute('for')) { if (!this.htmlFor || !this.host.isConnected) { return null; } return this.host.getRootNode().querySelector(`#${this.htmlFor}`); } return this.currentControl || this.host.parentElement; } set control(control) { if (control) { this.attach(control); } else { this.detach(); } } /** * Creates a new controller for an `Attachable` element. * * @param host The `Attachable` element. * @param onControlChange A callback with two parameters for the previous and * next control. An `Attachable` element may perform setup or teardown * logic whenever the control changes. */ constructor(host, onControlChange) { this.host = host; this.onControlChange = onControlChange; this.currentControl = null; host.addController(this); host[ATTACHABLE_CONTROLLER] = this; FOR_ATTRIBUTE_OBSERVER?.observe(host, { attributeFilter: ['for'] }); } attach(control) { if (control === this.currentControl) { return; } this.setCurrentControl(control); // When imperatively attaching, remove the `for` attribute so // that the attached control is used instead of a referenced one. this.host.removeAttribute('for'); } detach() { this.setCurrentControl(null); // When imperatively detaching, add an empty `for=""` attribute. This will // ensure the control is `null` rather than the `parentElement`. this.host.setAttribute('for', ''); } /** @private */ hostConnected() { this.setCurrentControl(this.control); } /** @private */ hostDisconnected() { this.setCurrentControl(null); } setCurrentControl(control) { this.onControlChange(this.currentControl, control); this.currentControl = control; } } //# sourceMappingURL=attachable-controller.js.map