refactor(editor): implement uni-component in AFFiNE (#10747)
This commit is contained in:
@@ -5,7 +5,7 @@ import type {
|
|||||||
import { ShadowlessElement } from '@blocksuite/block-std';
|
import { ShadowlessElement } from '@blocksuite/block-std';
|
||||||
import { IS_MOBILE } from '@blocksuite/global/env';
|
import { IS_MOBILE } from '@blocksuite/global/env';
|
||||||
import { SignalWatcher, WithDisposable } from '@blocksuite/global/lit';
|
import { SignalWatcher, WithDisposable } from '@blocksuite/global/lit';
|
||||||
import { computed, type ReadonlySignal } from '@preact/signals-core';
|
import { computed, type ReadonlySignal, signal } from '@preact/signals-core';
|
||||||
import { css, unsafeCSS } from 'lit';
|
import { css, unsafeCSS } from 'lit';
|
||||||
import { property, state } from 'lit/decorators.js';
|
import { property, state } from 'lit/decorators.js';
|
||||||
import { classMap } from 'lit/directives/class-map.js';
|
import { classMap } from 'lit/directives/class-map.js';
|
||||||
@@ -63,7 +63,7 @@ export class DataViewRenderer extends SignalWatcher(
|
|||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
private readonly _view = createRef<{
|
private readonly _view = signal<{
|
||||||
expose: DataViewInstance;
|
expose: DataViewInstance;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import {
|
|||||||
popMenu,
|
popMenu,
|
||||||
popupTargetFromElement,
|
popupTargetFromElement,
|
||||||
} from '@blocksuite/affine-components/context-menu';
|
} from '@blocksuite/affine-components/context-menu';
|
||||||
|
import type { UniComponent } from '@blocksuite/affine-shared/types';
|
||||||
import { ShadowlessElement } from '@blocksuite/block-std';
|
import { ShadowlessElement } from '@blocksuite/block-std';
|
||||||
import { SignalWatcher, WithDisposable } from '@blocksuite/global/lit';
|
import { SignalWatcher, WithDisposable } from '@blocksuite/global/lit';
|
||||||
import {
|
import {
|
||||||
@@ -19,10 +20,7 @@ import { repeat } from 'lit/directives/repeat.js';
|
|||||||
import { html } from 'lit/static-html.js';
|
import { html } from 'lit/static-html.js';
|
||||||
|
|
||||||
import { dataViewCommonStyle } from '../common/css-variable.js';
|
import { dataViewCommonStyle } from '../common/css-variable.js';
|
||||||
import {
|
import { renderUniLit } from '../utils/uni-component/uni-component.js';
|
||||||
renderUniLit,
|
|
||||||
type UniComponent,
|
|
||||||
} from '../utils/uni-component/uni-component.js';
|
|
||||||
import type { SingleView } from '../view-manager/single-view.js';
|
import type { SingleView } from '../view-manager/single-view.js';
|
||||||
import { DetailSelection } from './selection.js';
|
import { DetailSelection } from './selection.js';
|
||||||
|
|
||||||
|
|||||||
@@ -11,11 +11,10 @@ import {
|
|||||||
MoveLeftIcon,
|
MoveLeftIcon,
|
||||||
MoveRightIcon,
|
MoveRightIcon,
|
||||||
} from '@blocksuite/icons/lit';
|
} from '@blocksuite/icons/lit';
|
||||||
import { computed } from '@preact/signals-core';
|
import { computed, signal } from '@preact/signals-core';
|
||||||
import { css } from 'lit';
|
import { css } from 'lit';
|
||||||
import { property, state } from 'lit/decorators.js';
|
import { property, state } from 'lit/decorators.js';
|
||||||
import { classMap } from 'lit/directives/class-map.js';
|
import { classMap } from 'lit/directives/class-map.js';
|
||||||
import { createRef } from 'lit/directives/ref.js';
|
|
||||||
import { html } from 'lit/static-html.js';
|
import { html } from 'lit/static-html.js';
|
||||||
|
|
||||||
import { inputConfig, typeConfig } from '../common/property-menu.js';
|
import { inputConfig, typeConfig } from '../common/property-menu.js';
|
||||||
@@ -109,7 +108,7 @@ export class RecordField extends SignalWatcher(
|
|||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
private readonly _cell = createRef<DataViewCellLifeCycle>();
|
private readonly _cell = signal<DataViewCellLifeCycle>();
|
||||||
|
|
||||||
_click = (e: MouseEvent) => {
|
_click = (e: MouseEvent) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
|
import type { UniComponent } from '@blocksuite/affine-shared/types';
|
||||||
|
|
||||||
import type { TypeInstance } from '../logical/type.js';
|
import type { TypeInstance } from '../logical/type.js';
|
||||||
import type { UniComponent } from '../utils/index.js';
|
|
||||||
|
|
||||||
export type VariableRef = {
|
export type VariableRef = {
|
||||||
type: 'ref';
|
type: 'ref';
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
|
import type { UniComponent } from '@blocksuite/affine-shared/types';
|
||||||
|
|
||||||
import type { TypeInstance } from '../logical/type.js';
|
import type { TypeInstance } from '../logical/type.js';
|
||||||
import type { DVJSON } from '../property/types.js';
|
import type { DVJSON } from '../property/types.js';
|
||||||
import type { UniComponent } from '../utils/index.js';
|
|
||||||
|
|
||||||
export interface GroupRenderProps<
|
export interface GroupRenderProps<
|
||||||
Data extends NonNullable<unknown> = NonNullable<unknown>,
|
Data extends NonNullable<unknown> = NonNullable<unknown>,
|
||||||
|
|||||||
@@ -13,6 +13,10 @@ export abstract class BaseCellRenderer<
|
|||||||
extends SignalWatcher(WithDisposable(ShadowlessElement))
|
extends SignalWatcher(WithDisposable(ShadowlessElement))
|
||||||
implements DataViewCellLifeCycle, CellRenderProps<Data, Value>
|
implements DataViewCellLifeCycle, CellRenderProps<Data, Value>
|
||||||
{
|
{
|
||||||
|
get expose() {
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
@property({ attribute: false })
|
@property({ attribute: false })
|
||||||
accessor cell!: Cell<Value, Data>;
|
accessor cell!: Cell<Value, Data>;
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import type { UniComponent } from '../utils/uni-component/index.js';
|
import type { UniComponent } from '@blocksuite/affine-shared/types';
|
||||||
|
|
||||||
import type { Cell } from '../view-manager/cell.js';
|
import type { Cell } from '../view-manager/cell.js';
|
||||||
|
|
||||||
export interface CellRenderProps<
|
export interface CellRenderProps<
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
import {
|
import type { UniComponent } from '@blocksuite/affine-shared/types';
|
||||||
createUniComponentFromWebComponent,
|
|
||||||
type UniComponent,
|
import { createUniComponentFromWebComponent } from '../utils/uni-component/index.js';
|
||||||
} from '../utils/uni-component/index.js';
|
|
||||||
import type { BaseCellRenderer } from './base-cell.js';
|
import type { BaseCellRenderer } from './base-cell.js';
|
||||||
import type { CellRenderer, DataViewCellComponent } from './manager.js';
|
import type { CellRenderer, DataViewCellComponent } from './manager.js';
|
||||||
|
|
||||||
|
|||||||
@@ -1,17 +1,16 @@
|
|||||||
import type { UniComponent } from './uni-component.js';
|
import type { UniComponent } from '@blocksuite/affine-shared/types';
|
||||||
|
|
||||||
export const uniMap = <T, R, P extends NonNullable<unknown>>(
|
export const uniMap = <T, R, P extends NonNullable<unknown>>(
|
||||||
component: UniComponent<T, P>,
|
component: UniComponent<T, P>,
|
||||||
map: (r: R) => T
|
map: (r: R) => T
|
||||||
): UniComponent<R, P> => {
|
): UniComponent<R, P> => {
|
||||||
return (ele, props) => {
|
return (ele, props, expose) => {
|
||||||
const result = component(ele, map(props));
|
const result = component(ele, map(props), expose);
|
||||||
return {
|
return {
|
||||||
unmount: result.unmount,
|
unmount: result.unmount,
|
||||||
update: props => {
|
update: props => {
|
||||||
result.update(map(props));
|
result.update(map(props));
|
||||||
},
|
},
|
||||||
expose: result.expose,
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,28 +1,20 @@
|
|||||||
|
import type {
|
||||||
|
UniComponent,
|
||||||
|
UniComponentReturn,
|
||||||
|
} from '@blocksuite/affine-shared/types';
|
||||||
import { ShadowlessElement } from '@blocksuite/block-std';
|
import { ShadowlessElement } from '@blocksuite/block-std';
|
||||||
import { SignalWatcher } from '@blocksuite/global/lit';
|
import { SignalWatcher } from '@blocksuite/global/lit';
|
||||||
|
import type { Signal } from '@preact/signals-core';
|
||||||
import type { LitElement, PropertyValues, TemplateResult } from 'lit';
|
import type { LitElement, PropertyValues, TemplateResult } from 'lit';
|
||||||
import { css, html } from 'lit';
|
import { css, html } from 'lit';
|
||||||
import { property } from 'lit/decorators.js';
|
import { property } from 'lit/decorators.js';
|
||||||
import type { Ref } from 'lit/directives/ref.js';
|
|
||||||
import { type StyleInfo, styleMap } from 'lit/directives/style-map.js';
|
import { type StyleInfo, styleMap } from 'lit/directives/style-map.js';
|
||||||
|
|
||||||
export type UniComponentReturn<
|
|
||||||
Props = NonNullable<unknown>,
|
|
||||||
Expose extends NonNullable<unknown> = NonNullable<unknown>,
|
|
||||||
> = {
|
|
||||||
update: (props: Props) => void;
|
|
||||||
unmount: () => void;
|
|
||||||
expose: Expose;
|
|
||||||
};
|
|
||||||
export type UniComponent<
|
|
||||||
Props = NonNullable<unknown>,
|
|
||||||
Expose extends NonNullable<unknown> = NonNullable<unknown>,
|
|
||||||
> = (ele: HTMLElement, props: Props) => UniComponentReturn<Props, Expose>;
|
|
||||||
export const renderUniLit = <Props, Expose extends NonNullable<unknown>>(
|
export const renderUniLit = <Props, Expose extends NonNullable<unknown>>(
|
||||||
uni: UniComponent<Props, Expose> | undefined,
|
uni: UniComponent<Props, Expose> | undefined,
|
||||||
props?: Props,
|
props?: Props,
|
||||||
options?: {
|
options?: {
|
||||||
ref?: Ref<Expose>;
|
ref?: Signal<Expose | undefined>;
|
||||||
style?: Readonly<StyleInfo>;
|
style?: Readonly<StyleInfo>;
|
||||||
class?: string;
|
class?: string;
|
||||||
}
|
}
|
||||||
@@ -45,18 +37,21 @@ export class UniLit<
|
|||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
uniReturn?: UniComponentReturn<Props, Expose>;
|
uniReturn?: UniComponentReturn<Props>;
|
||||||
|
|
||||||
|
private _expose?: Expose;
|
||||||
|
|
||||||
get expose(): Expose | undefined {
|
get expose(): Expose | undefined {
|
||||||
return this.uniReturn?.expose;
|
return this._expose;
|
||||||
}
|
}
|
||||||
|
|
||||||
private mount() {
|
private mount() {
|
||||||
this.uniReturn = this.uni?.(this, this.props);
|
this.uniReturn = this.uni?.(this, this.props, value => {
|
||||||
if (this.ref) {
|
if (this.ref) {
|
||||||
// @ts-expect-error FIXME: ts error
|
this.ref.value = value;
|
||||||
this.ref.value = this.uniReturn?.expose;
|
this._expose = value;
|
||||||
}
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private unmount() {
|
private unmount() {
|
||||||
@@ -91,7 +86,7 @@ export class UniLit<
|
|||||||
accessor props!: Props;
|
accessor props!: Props;
|
||||||
|
|
||||||
@property({ attribute: false })
|
@property({ attribute: false })
|
||||||
accessor ref: Ref<Expose> | undefined = undefined;
|
accessor ref: Signal<Expose | undefined> | undefined = undefined;
|
||||||
|
|
||||||
@property({ attribute: false })
|
@property({ attribute: false })
|
||||||
accessor uni: UniComponent<Props, Expose> | undefined = undefined;
|
accessor uni: UniComponent<Props, Expose> | undefined = undefined;
|
||||||
@@ -103,10 +98,12 @@ export const createUniComponentFromWebComponent = <
|
|||||||
>(
|
>(
|
||||||
component: typeof LitElement
|
component: typeof LitElement
|
||||||
): UniComponent<T, Expose> => {
|
): UniComponent<T, Expose> => {
|
||||||
return (ele, props) => {
|
return (ele, props, expose) => {
|
||||||
const ins = new component();
|
const ins = new component();
|
||||||
Object.assign(ins, props);
|
Object.assign(ins, props);
|
||||||
ele.append(ins);
|
ele.append(ins);
|
||||||
|
// @ts-expect-error ins.expose may not exist in all component instances
|
||||||
|
expose(ins.expose);
|
||||||
return {
|
return {
|
||||||
update: props => {
|
update: props => {
|
||||||
Object.assign(ins, props);
|
Object.assign(ins, props);
|
||||||
@@ -115,7 +112,6 @@ export const createUniComponentFromWebComponent = <
|
|||||||
unmount: () => {
|
unmount: () => {
|
||||||
ins.remove();
|
ins.remove();
|
||||||
},
|
},
|
||||||
expose: ins as never as Expose,
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
@@ -140,12 +136,13 @@ export class UniAnyRender<
|
|||||||
export const defineUniComponent = <T, Expose extends NonNullable<unknown>>(
|
export const defineUniComponent = <T, Expose extends NonNullable<unknown>>(
|
||||||
renderTemplate: (props: T, expose: Expose) => TemplateResult
|
renderTemplate: (props: T, expose: Expose) => TemplateResult
|
||||||
): UniComponent<T, Expose> => {
|
): UniComponent<T, Expose> => {
|
||||||
return (ele, props) => {
|
return (ele, props, expose) => {
|
||||||
const ins = new UniAnyRender<T, Expose>();
|
const ins = new UniAnyRender<T, Expose>();
|
||||||
ins.props = props;
|
ins.props = props;
|
||||||
ins.expose = {} as Expose;
|
ins.expose = {} as Expose;
|
||||||
ins.renderTemplate = renderTemplate;
|
ins.renderTemplate = renderTemplate;
|
||||||
ele.append(ins);
|
ele.append(ins);
|
||||||
|
expose(ins.expose);
|
||||||
return {
|
return {
|
||||||
update: props => {
|
update: props => {
|
||||||
ins.props = props;
|
ins.props = props;
|
||||||
@@ -154,7 +151,6 @@ export const defineUniComponent = <T, Expose extends NonNullable<unknown>>(
|
|||||||
unmount: () => {
|
unmount: () => {
|
||||||
ins.remove();
|
ins.remove();
|
||||||
},
|
},
|
||||||
expose: ins.expose,
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
|
import type { UniComponent } from '@blocksuite/affine-shared/types';
|
||||||
import { computed, type ReadonlySignal } from '@preact/signals-core';
|
import { computed, type ReadonlySignal } from '@preact/signals-core';
|
||||||
|
|
||||||
import type { TypeInstance } from '../logical/type.js';
|
import type { TypeInstance } from '../logical/type.js';
|
||||||
import type { CellRenderer } from '../property/index.js';
|
import type { CellRenderer } from '../property/index.js';
|
||||||
import type { PropertyDataUpdater } from '../types.js';
|
import type { PropertyDataUpdater } from '../types.js';
|
||||||
import type { UniComponent } from '../utils/uni-component/index.js';
|
|
||||||
import type { Cell } from './cell.js';
|
import type { Cell } from './cell.js';
|
||||||
import type { SingleView } from './single-view.js';
|
import type { SingleView } from './single-view.js';
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import type { UniComponent } from '@blocksuite/affine-shared/types';
|
||||||
import type { InsertToPosition } from '@blocksuite/affine-shared/utils';
|
import type { InsertToPosition } from '@blocksuite/affine-shared/utils';
|
||||||
import { computed, type ReadonlySignal, signal } from '@preact/signals-core';
|
import { computed, type ReadonlySignal, signal } from '@preact/signals-core';
|
||||||
|
|
||||||
@@ -8,7 +9,6 @@ import type { TypeInstance } from '../logical/type.js';
|
|||||||
import type { PropertyMetaConfig } from '../property/property-config.js';
|
import type { PropertyMetaConfig } from '../property/property-config.js';
|
||||||
import type { TraitKey } from '../traits/key.js';
|
import type { TraitKey } from '../traits/key.js';
|
||||||
import type { DatabaseFlags } from '../types.js';
|
import type { DatabaseFlags } from '../types.js';
|
||||||
import type { UniComponent } from '../utils/uni-component/index.js';
|
|
||||||
import type { DataViewDataType, ViewMeta } from '../view/data-view.js';
|
import type { DataViewDataType, ViewMeta } from '../view/data-view.js';
|
||||||
import { type Cell, CellBase } from './cell.js';
|
import { type Cell, CellBase } from './cell.js';
|
||||||
import type { Property } from './property.js';
|
import type { Property } from './property.js';
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import type { UniComponent } from '../utils/uni-component/index.js';
|
import type { UniComponent } from '@blocksuite/affine-shared/types';
|
||||||
|
|
||||||
import type { SingleView } from '../view-manager/single-view.js';
|
import type { SingleView } from '../view-manager/single-view.js';
|
||||||
import type { ViewManager } from '../view-manager/view-manager.js';
|
import type { ViewManager } from '../view-manager/view-manager.js';
|
||||||
import type { DataViewInstance, DataViewProps } from './types.js';
|
import type { DataViewInstance, DataViewProps } from './types.js';
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import type { UniComponent } from '../utils/uni-component/index.js';
|
import type { UniComponent } from '@blocksuite/affine-shared/types';
|
||||||
|
|
||||||
import type { DataViewInstance } from '../view/types.js';
|
import type { DataViewInstance } from '../view/types.js';
|
||||||
|
|
||||||
export type DataViewWidgetProps = {
|
export type DataViewWidgetProps = {
|
||||||
|
|||||||
@@ -3,10 +3,9 @@
|
|||||||
import { unsafeCSSVarV2 } from '@blocksuite/affine-shared/theme';
|
import { unsafeCSSVarV2 } from '@blocksuite/affine-shared/theme';
|
||||||
import { ShadowlessElement } from '@blocksuite/block-std';
|
import { ShadowlessElement } from '@blocksuite/block-std';
|
||||||
import { SignalWatcher, WithDisposable } from '@blocksuite/global/lit';
|
import { SignalWatcher, WithDisposable } from '@blocksuite/global/lit';
|
||||||
import { computed, effect } from '@preact/signals-core';
|
import { computed, effect, signal } from '@preact/signals-core';
|
||||||
import { css } from 'lit';
|
import { css } from 'lit';
|
||||||
import { property, state } from 'lit/decorators.js';
|
import { property, state } from 'lit/decorators.js';
|
||||||
import { createRef } from 'lit/directives/ref.js';
|
|
||||||
import { html } from 'lit/static-html.js';
|
import { html } from 'lit/static-html.js';
|
||||||
|
|
||||||
import type {
|
import type {
|
||||||
@@ -51,7 +50,7 @@ export class MobileKanbanCell extends SignalWatcher(
|
|||||||
) {
|
) {
|
||||||
static override styles = styles;
|
static override styles = styles;
|
||||||
|
|
||||||
private readonly _cell = createRef<DataViewCellLifeCycle>();
|
private readonly _cell = signal<DataViewCellLifeCycle>();
|
||||||
|
|
||||||
isEditing$ = computed(() => {
|
isEditing$ = computed(() => {
|
||||||
const selection = this.kanban?.props.selection$.value;
|
const selection = this.kanban?.props.selection$.value;
|
||||||
|
|||||||
@@ -2,9 +2,9 @@
|
|||||||
|
|
||||||
import { ShadowlessElement } from '@blocksuite/block-std';
|
import { ShadowlessElement } from '@blocksuite/block-std';
|
||||||
import { SignalWatcher, WithDisposable } from '@blocksuite/global/lit';
|
import { SignalWatcher, WithDisposable } from '@blocksuite/global/lit';
|
||||||
|
import { signal } from '@preact/signals-core';
|
||||||
import { css } from 'lit';
|
import { css } from 'lit';
|
||||||
import { property, state } from 'lit/decorators.js';
|
import { property, state } from 'lit/decorators.js';
|
||||||
import { createRef } from 'lit/directives/ref.js';
|
|
||||||
import { html } from 'lit/static-html.js';
|
import { html } from 'lit/static-html.js';
|
||||||
|
|
||||||
import type {
|
import type {
|
||||||
@@ -59,7 +59,7 @@ export class KanbanCell extends SignalWatcher(
|
|||||||
) {
|
) {
|
||||||
static override styles = styles;
|
static override styles = styles;
|
||||||
|
|
||||||
private readonly _cell = createRef<DataViewCellLifeCycle>();
|
private readonly _cell = signal<DataViewCellLifeCycle>();
|
||||||
|
|
||||||
selectCurrentCell = (editing: boolean) => {
|
selectCurrentCell = (editing: boolean) => {
|
||||||
const selectionView = this.closest(
|
const selectionView = this.closest(
|
||||||
|
|||||||
@@ -1,9 +1,8 @@
|
|||||||
import { ShadowlessElement } from '@blocksuite/block-std';
|
import { ShadowlessElement } from '@blocksuite/block-std';
|
||||||
import { SignalWatcher, WithDisposable } from '@blocksuite/global/lit';
|
import { SignalWatcher, WithDisposable } from '@blocksuite/global/lit';
|
||||||
import { computed, effect } from '@preact/signals-core';
|
import { computed, effect, signal } from '@preact/signals-core';
|
||||||
import { css } from 'lit';
|
import { css } from 'lit';
|
||||||
import { property, state } from 'lit/decorators.js';
|
import { property, state } from 'lit/decorators.js';
|
||||||
import { createRef } from 'lit/directives/ref.js';
|
|
||||||
|
|
||||||
import {
|
import {
|
||||||
type CellRenderProps,
|
type CellRenderProps,
|
||||||
@@ -36,7 +35,7 @@ export class MobileTableCell extends SignalWatcher(
|
|||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
private readonly _cell = createRef<DataViewCellLifeCycle>();
|
private readonly _cell = signal<DataViewCellLifeCycle>();
|
||||||
|
|
||||||
@property({ attribute: false })
|
@property({ attribute: false })
|
||||||
accessor column!: TableColumn;
|
accessor column!: TableColumn;
|
||||||
|
|||||||
@@ -1,9 +1,8 @@
|
|||||||
import { ShadowlessElement } from '@blocksuite/block-std';
|
import { ShadowlessElement } from '@blocksuite/block-std';
|
||||||
import { SignalWatcher, WithDisposable } from '@blocksuite/global/lit';
|
import { SignalWatcher, WithDisposable } from '@blocksuite/global/lit';
|
||||||
import { computed } from '@preact/signals-core';
|
import { computed, signal } from '@preact/signals-core';
|
||||||
import { css } from 'lit';
|
import { css } from 'lit';
|
||||||
import { property, state } from 'lit/decorators.js';
|
import { property, state } from 'lit/decorators.js';
|
||||||
import { createRef } from 'lit/directives/ref.js';
|
|
||||||
|
|
||||||
import { renderUniLit } from '../../../core/index.js';
|
import { renderUniLit } from '../../../core/index.js';
|
||||||
import type {
|
import type {
|
||||||
@@ -40,7 +39,7 @@ export class DatabaseCellContainer extends SignalWatcher(
|
|||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
private readonly _cell = createRef<DataViewCellLifeCycle>();
|
private readonly _cell = signal<DataViewCellLifeCycle>();
|
||||||
|
|
||||||
@property({ attribute: false })
|
@property({ attribute: false })
|
||||||
accessor column!: TableColumn;
|
accessor column!: TableColumn;
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import type {
|
|||||||
} from '@blocksuite/affine-model';
|
} from '@blocksuite/affine-model';
|
||||||
import type { BlockComponent } from '@blocksuite/block-std';
|
import type { BlockComponent } from '@blocksuite/block-std';
|
||||||
import type { BlockModel } from '@blocksuite/store';
|
import type { BlockModel } from '@blocksuite/store';
|
||||||
|
export * from './uni-component';
|
||||||
export interface EditingState {
|
export interface EditingState {
|
||||||
element: BlockComponent;
|
element: BlockComponent;
|
||||||
model: BlockModel;
|
model: BlockModel;
|
||||||
|
|||||||
15
blocksuite/affine/shared/src/types/uni-component.ts
Normal file
15
blocksuite/affine/shared/src/types/uni-component.ts
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
export type ExposeWrapper<
|
||||||
|
Expose extends NonNullable<unknown> = NonNullable<unknown>,
|
||||||
|
> = (value?: Expose) => void;
|
||||||
|
export type UniComponentReturn<Props = NonNullable<unknown>> = {
|
||||||
|
update: (props: Props) => void;
|
||||||
|
unmount: () => void;
|
||||||
|
};
|
||||||
|
export type UniComponent<
|
||||||
|
Props = NonNullable<unknown>,
|
||||||
|
Expose extends NonNullable<unknown> = NonNullable<unknown>,
|
||||||
|
> = (
|
||||||
|
ele: HTMLElement,
|
||||||
|
props: Props,
|
||||||
|
expose: ExposeWrapper<Expose>
|
||||||
|
) => UniComponentReturn<Props>;
|
||||||
@@ -3,3 +3,4 @@ export {
|
|||||||
useLitPortal,
|
useLitPortal,
|
||||||
useLitPortalFactory,
|
useLitPortalFactory,
|
||||||
} from './lit-portal';
|
} from './lit-portal';
|
||||||
|
export { uniReactRoot } from './uni-component';
|
||||||
|
|||||||
@@ -0,0 +1,96 @@
|
|||||||
|
import type {
|
||||||
|
ExposeWrapper,
|
||||||
|
UniComponent,
|
||||||
|
UniComponentReturn,
|
||||||
|
} from '@blocksuite/affine-shared/types';
|
||||||
|
import { nanoid } from 'nanoid';
|
||||||
|
import {
|
||||||
|
type ComponentType,
|
||||||
|
memo,
|
||||||
|
type ReactNode,
|
||||||
|
useEffect,
|
||||||
|
useState,
|
||||||
|
} from 'react';
|
||||||
|
import { createPortal } from 'react-dom';
|
||||||
|
|
||||||
|
const UniReactNode = memo(
|
||||||
|
function UniReactNode(props: {
|
||||||
|
ele: HTMLElement;
|
||||||
|
component: ComponentType<any>;
|
||||||
|
dataRef: DataRef<any>;
|
||||||
|
expose: ExposeWrapper<any>;
|
||||||
|
}) {
|
||||||
|
const Component = props.component;
|
||||||
|
const [data, setData] = useState(props.dataRef.value);
|
||||||
|
useEffect(() => {
|
||||||
|
return props.dataRef.subscribe(() => {
|
||||||
|
setData(props.dataRef.value);
|
||||||
|
});
|
||||||
|
}, [props.dataRef]);
|
||||||
|
return createPortal(<Component ref={props.expose} {...data} />, props.ele);
|
||||||
|
},
|
||||||
|
() => true
|
||||||
|
);
|
||||||
|
type DataRef<T> = {
|
||||||
|
value: T;
|
||||||
|
subscribe: (update: () => void) => void;
|
||||||
|
update: (props: T) => void;
|
||||||
|
};
|
||||||
|
const createDataRef = <T,>(data: T): DataRef<T> => {
|
||||||
|
let listener = () => {};
|
||||||
|
const ref = {
|
||||||
|
value: data,
|
||||||
|
subscribe: (update: () => void) => {
|
||||||
|
listener = update;
|
||||||
|
return () => {
|
||||||
|
listener = () => {};
|
||||||
|
};
|
||||||
|
},
|
||||||
|
update: (props: T) => {
|
||||||
|
ref.value = props;
|
||||||
|
listener();
|
||||||
|
},
|
||||||
|
};
|
||||||
|
return ref;
|
||||||
|
};
|
||||||
|
export const createUniReactRoot = () => {
|
||||||
|
const nodes: Set<ReactNode> = new Set();
|
||||||
|
let updateNodes = () => {};
|
||||||
|
return {
|
||||||
|
Root: () => {
|
||||||
|
const [, forceUpdate] = useState({});
|
||||||
|
useEffect(() => {
|
||||||
|
updateNodes = () => forceUpdate({});
|
||||||
|
}, []);
|
||||||
|
return nodes;
|
||||||
|
},
|
||||||
|
createUniComponent: <T, E extends NonNullable<unknown>>(
|
||||||
|
component: ComponentType<T>
|
||||||
|
): UniComponent<T, E> => {
|
||||||
|
return (ele: HTMLElement, props: T, expose) => {
|
||||||
|
const dataRef = createDataRef(props);
|
||||||
|
const node = (
|
||||||
|
<UniReactNode
|
||||||
|
key={nanoid()}
|
||||||
|
expose={expose}
|
||||||
|
ele={ele}
|
||||||
|
component={component}
|
||||||
|
dataRef={dataRef}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
nodes.add(node);
|
||||||
|
updateNodes();
|
||||||
|
return {
|
||||||
|
update: (props: T) => {
|
||||||
|
dataRef.update(props);
|
||||||
|
},
|
||||||
|
unmount: () => {
|
||||||
|
nodes.delete(node);
|
||||||
|
updateNodes();
|
||||||
|
},
|
||||||
|
} satisfies UniComponentReturn<T>;
|
||||||
|
};
|
||||||
|
},
|
||||||
|
};
|
||||||
|
};
|
||||||
|
export const uniReactRoot = createUniReactRoot();
|
||||||
@@ -1,4 +1,8 @@
|
|||||||
import { ConfirmModalProvider, PromptModalProvider } from '@affine/component';
|
import {
|
||||||
|
ConfirmModalProvider,
|
||||||
|
PromptModalProvider,
|
||||||
|
uniReactRoot,
|
||||||
|
} from '@affine/component';
|
||||||
import { ProviderComposer } from '@affine/component/provider-composer';
|
import { ProviderComposer } from '@affine/component/provider-composer';
|
||||||
import { ThemeProvider } from '@affine/core/components/theme-provider';
|
import { ThemeProvider } from '@affine/core/components/theme-provider';
|
||||||
import type { createStore } from 'jotai';
|
import type { createStore } from 'jotai';
|
||||||
@@ -25,6 +29,7 @@ export function AffineContext(props: AffineContextProps) {
|
|||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{props.children}
|
{props.children}
|
||||||
|
<uniReactRoot.Root />,
|
||||||
</ProviderComposer>
|
</ProviderComposer>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user