refactor(editor): separate lit and slot in global (#10666)

This commit is contained in:
Saul-Mirone
2025-03-06 10:24:59 +00:00
parent 56b842f2e1
commit 84e2dda3f8
322 changed files with 366 additions and 353 deletions
@@ -0,0 +1,2 @@
export * from './signal-watcher.js';
export * from './with-disposable.js';
@@ -0,0 +1,73 @@
/**
* @license
* Copyright 2023 Google LLC
* SPDX-License-Identifier: BSD-3-Clause
*/
import { effect } from '@preact/signals-core';
import type { ReactiveElement } from 'lit';
type ReactiveElementConstructor = abstract new (
...args: any[]
) => ReactiveElement;
/**
* Adds the ability for a LitElement or other ReactiveElement class to
* watch for access to Preact signals during the update lifecycle and
* trigger a new update when signals values change.
*/
export function SignalWatcher<T extends ReactiveElementConstructor>(
Base: T
): T {
abstract class SignalWatcher extends Base {
private __dispose?: () => void;
override connectedCallback(): void {
super.connectedCallback();
// In order to listen for signals again after re-connection, we must
// re-render to capture all the current signal accesses.
this.requestUpdate();
}
override disconnectedCallback(): void {
super.disconnectedCallback();
this.__dispose?.();
}
override performUpdate() {
// ReactiveElement.performUpdate() also does this check, so we want to
// also bail early so we don't erroneously appear to not depend on any
// signals.
if (this.isUpdatePending === false || this.isConnected === false) {
return;
}
// If we have a previous effect, dispose it
this.__dispose?.();
// Tracks whether the effect callback is triggered by this performUpdate
// call directly, or by a signal change.
let updateFromLit = true;
// We create a new effect to capture all signal access within the
// performUpdate phase (update, render, updated, etc) of the element.
// Q: Do we need to create a new effect each render?
// TODO: test various combinations of render triggers:
// - from requestUpdate()
// - from signals
// - from both (do we get one or two re-renders)
// and see if we really need a new effect here.
this.__dispose = effect(() => {
if (updateFromLit) {
updateFromLit = false;
super.performUpdate();
} else {
// This branch is an effect run from Preact signals.
// This will cause another call into performUpdate, which will
// then create a new effect watching that update pass.
this.requestUpdate();
}
});
}
}
return SignalWatcher;
}
@@ -0,0 +1,53 @@
import type { LitElement } from 'lit';
import { DisposableGroup } from '../slot/disposable.js';
import type { Constructor } from '../utils/types.js';
// See https://lit.dev/docs/composition/mixins/#mixins-in-typescript
// This definition should be exported, see https://github.com/microsoft/TypeScript/issues/30355#issuecomment-839834550
export declare class DisposableClass {
protected _disposables: DisposableGroup;
readonly disposables: DisposableGroup;
}
/**
* Mixin that adds a `_disposables: DisposableGroup` property to the class.
*
* The `_disposables` property is initialized in `connectedCallback` and disposed in `disconnectedCallback`.
*
* see https://lit.dev/docs/composition/mixins/
*
* @example
* ```ts
* class MyElement extends WithDisposable(ShadowlessElement) {
* onClick() {
* this._disposables.add(...);
* }
* }
* ```
*/
export function WithDisposable<T extends Constructor<LitElement>>(
SuperClass: T
) {
class DerivedClass extends SuperClass {
protected _disposables = new DisposableGroup();
get disposables() {
return this._disposables;
}
override connectedCallback() {
super.connectedCallback();
if (this._disposables.disposed) {
this._disposables = new DisposableGroup();
}
}
override disconnectedCallback() {
super.disconnectedCallback();
this._disposables.dispose();
}
}
return DerivedClass as unknown as T & Constructor<DisposableClass>;
}