mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-07-12 15:46:29 +08:00
refactor(editor): separate lit and slot in global (#10666)
This commit is contained in:
@@ -1,100 +0,0 @@
|
||||
type DisposeCallback = () => void;
|
||||
|
||||
export interface Disposable {
|
||||
dispose: DisposeCallback;
|
||||
}
|
||||
|
||||
export interface DisposableManager extends Disposable {
|
||||
add(d: Disposable | DisposeCallback): void;
|
||||
}
|
||||
|
||||
export class DisposableGroup implements DisposableManager {
|
||||
private _disposables: Disposable[] = [];
|
||||
|
||||
private _disposed = false;
|
||||
|
||||
get disposed() {
|
||||
return this._disposed;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add to group to be disposed with others.
|
||||
* This will be immediately disposed if this group has already been disposed.
|
||||
*/
|
||||
add(d: Disposable | DisposeCallback) {
|
||||
if (typeof d === 'function') {
|
||||
if (this._disposed) d();
|
||||
else this._disposables.push({ dispose: d });
|
||||
} else {
|
||||
if (this._disposed) d.dispose();
|
||||
else this._disposables.push(d);
|
||||
}
|
||||
}
|
||||
|
||||
addFromEvent<N extends keyof WindowEventMap>(
|
||||
element: Window,
|
||||
eventName: N,
|
||||
handler: (e: WindowEventMap[N]) => void,
|
||||
options?: boolean | AddEventListenerOptions
|
||||
): void;
|
||||
addFromEvent<N extends keyof DocumentEventMap>(
|
||||
element: Document,
|
||||
eventName: N,
|
||||
handler: (e: DocumentEventMap[N]) => void,
|
||||
eventOptions?: boolean | AddEventListenerOptions
|
||||
): void;
|
||||
addFromEvent<N extends keyof HTMLElementEventMap>(
|
||||
element: HTMLElement,
|
||||
eventName: N,
|
||||
handler: (e: HTMLElementEventMap[N]) => void,
|
||||
eventOptions?: boolean | AddEventListenerOptions
|
||||
): void;
|
||||
addFromEvent<N extends keyof VisualViewportEventMap>(
|
||||
element: VisualViewport,
|
||||
eventName: N,
|
||||
handler: (e: VisualViewportEventMap[N]) => void,
|
||||
eventOptions?: boolean | AddEventListenerOptions
|
||||
): void;
|
||||
addFromEvent<N extends keyof VirtualKeyboardEventMap>(
|
||||
element: VirtualKeyboard,
|
||||
eventName: N,
|
||||
handler: (e: VirtualKeyboardEventMap[N]) => void,
|
||||
eventOptions?: boolean | AddEventListenerOptions
|
||||
): void;
|
||||
|
||||
addFromEvent(
|
||||
target: HTMLElement | Window | Document | VisualViewport | VirtualKeyboard,
|
||||
type: string,
|
||||
handler: (e: Event) => void,
|
||||
eventOptions?: boolean | AddEventListenerOptions
|
||||
) {
|
||||
this.add({
|
||||
dispose: () => {
|
||||
target.removeEventListener(type, handler as () => void, eventOptions);
|
||||
},
|
||||
});
|
||||
target.addEventListener(type, handler as () => void, eventOptions);
|
||||
}
|
||||
|
||||
dispose() {
|
||||
disposeAll(this._disposables);
|
||||
this._disposables = [];
|
||||
this._disposed = true;
|
||||
}
|
||||
}
|
||||
|
||||
export function flattenDisposables(disposables: Disposable[]): Disposable {
|
||||
return {
|
||||
dispose: () => disposeAll(disposables),
|
||||
};
|
||||
}
|
||||
|
||||
function disposeAll(disposables: Disposable[]) {
|
||||
for (const disposable of disposables) {
|
||||
try {
|
||||
disposable.dispose();
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,4 @@
|
||||
export * from './crypto.js';
|
||||
export * from './disposable.js';
|
||||
export * from './function.js';
|
||||
export * from './logger.js';
|
||||
export * from './signal-watcher.js';
|
||||
export * from './slot.js';
|
||||
export * from './types.js';
|
||||
export * from './with-disposable.js';
|
||||
|
||||
@@ -1,73 +0,0 @@
|
||||
/**
|
||||
* @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;
|
||||
}
|
||||
@@ -1,86 +0,0 @@
|
||||
import { type Disposable, flattenDisposables } from './disposable.js';
|
||||
|
||||
export class Slot<T = void> implements Disposable {
|
||||
private _callbacks: ((v: T) => unknown)[] = [];
|
||||
|
||||
private _disposables: Disposable[] = [];
|
||||
|
||||
private _emitting = false;
|
||||
|
||||
dispose() {
|
||||
flattenDisposables(this._disposables).dispose();
|
||||
this._callbacks = [];
|
||||
this._disposables = [];
|
||||
}
|
||||
|
||||
emit(v: T) {
|
||||
// Prevent recursive emit calls
|
||||
if (this._emitting) {
|
||||
return;
|
||||
}
|
||||
|
||||
const prevEmitting = this._emitting;
|
||||
this._emitting = true;
|
||||
try {
|
||||
this._callbacks.forEach(f => {
|
||||
try {
|
||||
f(v);
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
}
|
||||
});
|
||||
} finally {
|
||||
this._emitting = prevEmitting;
|
||||
}
|
||||
}
|
||||
|
||||
on(callback: (v: T) => unknown): Disposable {
|
||||
if (this._emitting) {
|
||||
const newCallback = [...this._callbacks, callback];
|
||||
this._callbacks = newCallback;
|
||||
} else {
|
||||
this._callbacks.push(callback);
|
||||
}
|
||||
return {
|
||||
dispose: () => {
|
||||
if (this._emitting) {
|
||||
this._callbacks = this._callbacks.filter(v => v !== callback);
|
||||
} else {
|
||||
const index = this._callbacks.indexOf(callback);
|
||||
if (index > -1) {
|
||||
this._callbacks.splice(index, 1); // remove one item only
|
||||
}
|
||||
}
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
once(callback: (v: T) => unknown): Disposable {
|
||||
let dispose: Disposable['dispose'] | undefined = undefined;
|
||||
const handler = (v: T) => {
|
||||
callback(v);
|
||||
if (dispose) {
|
||||
dispose();
|
||||
}
|
||||
};
|
||||
const disposable = this.on(handler);
|
||||
dispose = disposable.dispose;
|
||||
return disposable;
|
||||
}
|
||||
|
||||
filter(testFun: (v: T) => boolean): Slot<T> {
|
||||
const result = new Slot<T>();
|
||||
// if the original slot is disposed, dispose the filtered one
|
||||
this._disposables.push({
|
||||
dispose: () => result.dispose(),
|
||||
});
|
||||
|
||||
this.on((v: T) => {
|
||||
if (testFun(v)) {
|
||||
result.emit(v);
|
||||
}
|
||||
});
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
@@ -1,53 +0,0 @@
|
||||
import type { LitElement } from 'lit';
|
||||
|
||||
import { DisposableGroup } from './disposable.js';
|
||||
import type { Constructor } from './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>;
|
||||
}
|
||||
Reference in New Issue
Block a user