From 4a45cc9ba4571596ecca271752a1367c95fafb97 Mon Sep 17 00:00:00 2001 From: zzj3720 <17165520+zzj3720@users.noreply.github.com> Date: Mon, 10 Mar 2025 14:23:24 +0000 Subject: [PATCH] refactor(editor): implement uni-component in AFFiNE (#10747) --- .../affine/data-view/src/core/data-view.ts | 4 +- .../data-view/src/core/detail/detail.ts | 6 +- .../affine/data-view/src/core/detail/field.ts | 5 +- .../data-view/src/core/expression/types.ts | 3 +- .../data-view/src/core/group-by/types.ts | 3 +- .../data-view/src/core/property/base-cell.ts | 4 + .../data-view/src/core/property/manager.ts | 3 +- .../data-view/src/core/property/renderer.ts | 7 +- .../src/core/utils/uni-component/operation.ts | 7 +- .../core/utils/uni-component/uni-component.ts | 48 +++++----- .../src/core/view-manager/property.ts | 2 +- .../src/core/view-manager/single-view.ts | 2 +- .../data-view/src/core/view/data-view.ts | 3 +- .../affine/data-view/src/core/widget/types.ts | 3 +- .../src/view-presets/kanban/mobile/cell.ts | 5 +- .../src/view-presets/kanban/pc/cell.ts | 4 +- .../src/view-presets/table/mobile/cell.ts | 5 +- .../src/view-presets/table/pc/cell.ts | 5 +- blocksuite/affine/shared/src/types/index.ts | 2 +- .../affine/shared/src/types/uni-component.ts | 15 +++ .../src/lit-react/lit-portal/index.ts | 1 + .../lit-react/lit-portal/uni-component.tsx | 96 +++++++++++++++++++ .../core/src/components/context/index.tsx | 7 +- 23 files changed, 177 insertions(+), 63 deletions(-) create mode 100644 blocksuite/affine/shared/src/types/uni-component.ts create mode 100644 packages/frontend/component/src/lit-react/lit-portal/uni-component.tsx diff --git a/blocksuite/affine/data-view/src/core/data-view.ts b/blocksuite/affine/data-view/src/core/data-view.ts index 00e229ec6..c183bd17e 100644 --- a/blocksuite/affine/data-view/src/core/data-view.ts +++ b/blocksuite/affine/data-view/src/core/data-view.ts @@ -5,7 +5,7 @@ import type { import { ShadowlessElement } from '@blocksuite/block-std'; import { IS_MOBILE } from '@blocksuite/global/env'; 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 { property, state } from 'lit/decorators.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; }>(); diff --git a/blocksuite/affine/data-view/src/core/detail/detail.ts b/blocksuite/affine/data-view/src/core/detail/detail.ts index d45a62b73..6c3440d55 100644 --- a/blocksuite/affine/data-view/src/core/detail/detail.ts +++ b/blocksuite/affine/data-view/src/core/detail/detail.ts @@ -3,6 +3,7 @@ import { popMenu, popupTargetFromElement, } from '@blocksuite/affine-components/context-menu'; +import type { UniComponent } from '@blocksuite/affine-shared/types'; import { ShadowlessElement } from '@blocksuite/block-std'; import { SignalWatcher, WithDisposable } from '@blocksuite/global/lit'; import { @@ -19,10 +20,7 @@ import { repeat } from 'lit/directives/repeat.js'; import { html } from 'lit/static-html.js'; import { dataViewCommonStyle } from '../common/css-variable.js'; -import { - renderUniLit, - type UniComponent, -} from '../utils/uni-component/uni-component.js'; +import { renderUniLit } from '../utils/uni-component/uni-component.js'; import type { SingleView } from '../view-manager/single-view.js'; import { DetailSelection } from './selection.js'; diff --git a/blocksuite/affine/data-view/src/core/detail/field.ts b/blocksuite/affine/data-view/src/core/detail/field.ts index 723efb042..34c732783 100644 --- a/blocksuite/affine/data-view/src/core/detail/field.ts +++ b/blocksuite/affine/data-view/src/core/detail/field.ts @@ -11,11 +11,10 @@ import { MoveLeftIcon, MoveRightIcon, } from '@blocksuite/icons/lit'; -import { computed } from '@preact/signals-core'; +import { computed, signal } from '@preact/signals-core'; import { css } from 'lit'; import { property, state } from 'lit/decorators.js'; import { classMap } from 'lit/directives/class-map.js'; -import { createRef } from 'lit/directives/ref.js'; import { html } from 'lit/static-html.js'; import { inputConfig, typeConfig } from '../common/property-menu.js'; @@ -109,7 +108,7 @@ export class RecordField extends SignalWatcher( } `; - private readonly _cell = createRef(); + private readonly _cell = signal(); _click = (e: MouseEvent) => { e.stopPropagation(); diff --git a/blocksuite/affine/data-view/src/core/expression/types.ts b/blocksuite/affine/data-view/src/core/expression/types.ts index 13b105657..6feba73ad 100644 --- a/blocksuite/affine/data-view/src/core/expression/types.ts +++ b/blocksuite/affine/data-view/src/core/expression/types.ts @@ -1,5 +1,6 @@ +import type { UniComponent } from '@blocksuite/affine-shared/types'; + import type { TypeInstance } from '../logical/type.js'; -import type { UniComponent } from '../utils/index.js'; export type VariableRef = { type: 'ref'; diff --git a/blocksuite/affine/data-view/src/core/group-by/types.ts b/blocksuite/affine/data-view/src/core/group-by/types.ts index bb2a80c4c..78f6302dc 100644 --- a/blocksuite/affine/data-view/src/core/group-by/types.ts +++ b/blocksuite/affine/data-view/src/core/group-by/types.ts @@ -1,6 +1,7 @@ +import type { UniComponent } from '@blocksuite/affine-shared/types'; + import type { TypeInstance } from '../logical/type.js'; import type { DVJSON } from '../property/types.js'; -import type { UniComponent } from '../utils/index.js'; export interface GroupRenderProps< Data extends NonNullable = NonNullable, diff --git a/blocksuite/affine/data-view/src/core/property/base-cell.ts b/blocksuite/affine/data-view/src/core/property/base-cell.ts index 42256460b..f2c7d1dd9 100644 --- a/blocksuite/affine/data-view/src/core/property/base-cell.ts +++ b/blocksuite/affine/data-view/src/core/property/base-cell.ts @@ -13,6 +13,10 @@ export abstract class BaseCellRenderer< extends SignalWatcher(WithDisposable(ShadowlessElement)) implements DataViewCellLifeCycle, CellRenderProps { + get expose() { + return this; + } + @property({ attribute: false }) accessor cell!: Cell; diff --git a/blocksuite/affine/data-view/src/core/property/manager.ts b/blocksuite/affine/data-view/src/core/property/manager.ts index 1e27a5818..d4345d4ba 100644 --- a/blocksuite/affine/data-view/src/core/property/manager.ts +++ b/blocksuite/affine/data-view/src/core/property/manager.ts @@ -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'; export interface CellRenderProps< diff --git a/blocksuite/affine/data-view/src/core/property/renderer.ts b/blocksuite/affine/data-view/src/core/property/renderer.ts index aa7ffd798..951f453c4 100644 --- a/blocksuite/affine/data-view/src/core/property/renderer.ts +++ b/blocksuite/affine/data-view/src/core/property/renderer.ts @@ -1,7 +1,6 @@ -import { - createUniComponentFromWebComponent, - type UniComponent, -} from '../utils/uni-component/index.js'; +import type { UniComponent } from '@blocksuite/affine-shared/types'; + +import { createUniComponentFromWebComponent } from '../utils/uni-component/index.js'; import type { BaseCellRenderer } from './base-cell.js'; import type { CellRenderer, DataViewCellComponent } from './manager.js'; diff --git a/blocksuite/affine/data-view/src/core/utils/uni-component/operation.ts b/blocksuite/affine/data-view/src/core/utils/uni-component/operation.ts index 559ba1fbd..33c38e265 100644 --- a/blocksuite/affine/data-view/src/core/utils/uni-component/operation.ts +++ b/blocksuite/affine/data-view/src/core/utils/uni-component/operation.ts @@ -1,17 +1,16 @@ -import type { UniComponent } from './uni-component.js'; +import type { UniComponent } from '@blocksuite/affine-shared/types'; export const uniMap = >( component: UniComponent, map: (r: R) => T ): UniComponent => { - return (ele, props) => { - const result = component(ele, map(props)); + return (ele, props, expose) => { + const result = component(ele, map(props), expose); return { unmount: result.unmount, update: props => { result.update(map(props)); }, - expose: result.expose, }; }; }; diff --git a/blocksuite/affine/data-view/src/core/utils/uni-component/uni-component.ts b/blocksuite/affine/data-view/src/core/utils/uni-component/uni-component.ts index 0c43d7d5a..1226f2438 100644 --- a/blocksuite/affine/data-view/src/core/utils/uni-component/uni-component.ts +++ b/blocksuite/affine/data-view/src/core/utils/uni-component/uni-component.ts @@ -1,28 +1,20 @@ +import type { + UniComponent, + UniComponentReturn, +} from '@blocksuite/affine-shared/types'; import { ShadowlessElement } from '@blocksuite/block-std'; import { SignalWatcher } from '@blocksuite/global/lit'; +import type { Signal } from '@preact/signals-core'; import type { LitElement, PropertyValues, TemplateResult } from 'lit'; import { css, html } from 'lit'; import { property } from 'lit/decorators.js'; -import type { Ref } from 'lit/directives/ref.js'; import { type StyleInfo, styleMap } from 'lit/directives/style-map.js'; -export type UniComponentReturn< - Props = NonNullable, - Expose extends NonNullable = NonNullable, -> = { - update: (props: Props) => void; - unmount: () => void; - expose: Expose; -}; -export type UniComponent< - Props = NonNullable, - Expose extends NonNullable = NonNullable, -> = (ele: HTMLElement, props: Props) => UniComponentReturn; export const renderUniLit = >( uni: UniComponent | undefined, props?: Props, options?: { - ref?: Ref; + ref?: Signal; style?: Readonly; class?: string; } @@ -45,18 +37,21 @@ export class UniLit< } `; - uniReturn?: UniComponentReturn; + uniReturn?: UniComponentReturn; + + private _expose?: Expose; get expose(): Expose | undefined { - return this.uniReturn?.expose; + return this._expose; } private mount() { - this.uniReturn = this.uni?.(this, this.props); - if (this.ref) { - // @ts-expect-error FIXME: ts error - this.ref.value = this.uniReturn?.expose; - } + this.uniReturn = this.uni?.(this, this.props, value => { + if (this.ref) { + this.ref.value = value; + this._expose = value; + } + }); } private unmount() { @@ -91,7 +86,7 @@ export class UniLit< accessor props!: Props; @property({ attribute: false }) - accessor ref: Ref | undefined = undefined; + accessor ref: Signal | undefined = undefined; @property({ attribute: false }) accessor uni: UniComponent | undefined = undefined; @@ -103,10 +98,12 @@ export const createUniComponentFromWebComponent = < >( component: typeof LitElement ): UniComponent => { - return (ele, props) => { + return (ele, props, expose) => { const ins = new component(); Object.assign(ins, props); ele.append(ins); + // @ts-expect-error ins.expose may not exist in all component instances + expose(ins.expose); return { update: props => { Object.assign(ins, props); @@ -115,7 +112,6 @@ export const createUniComponentFromWebComponent = < unmount: () => { ins.remove(); }, - expose: ins as never as Expose, }; }; }; @@ -140,12 +136,13 @@ export class UniAnyRender< export const defineUniComponent = >( renderTemplate: (props: T, expose: Expose) => TemplateResult ): UniComponent => { - return (ele, props) => { + return (ele, props, expose) => { const ins = new UniAnyRender(); ins.props = props; ins.expose = {} as Expose; ins.renderTemplate = renderTemplate; ele.append(ins); + expose(ins.expose); return { update: props => { ins.props = props; @@ -154,7 +151,6 @@ export const defineUniComponent = >( unmount: () => { ins.remove(); }, - expose: ins.expose, }; }; }; diff --git a/blocksuite/affine/data-view/src/core/view-manager/property.ts b/blocksuite/affine/data-view/src/core/view-manager/property.ts index de4a6646b..50418d33d 100644 --- a/blocksuite/affine/data-view/src/core/view-manager/property.ts +++ b/blocksuite/affine/data-view/src/core/view-manager/property.ts @@ -1,9 +1,9 @@ +import type { UniComponent } from '@blocksuite/affine-shared/types'; import { computed, type ReadonlySignal } from '@preact/signals-core'; import type { TypeInstance } from '../logical/type.js'; import type { CellRenderer } from '../property/index.js'; import type { PropertyDataUpdater } from '../types.js'; -import type { UniComponent } from '../utils/uni-component/index.js'; import type { Cell } from './cell.js'; import type { SingleView } from './single-view.js'; diff --git a/blocksuite/affine/data-view/src/core/view-manager/single-view.ts b/blocksuite/affine/data-view/src/core/view-manager/single-view.ts index 37f88d10d..6569dcba5 100644 --- a/blocksuite/affine/data-view/src/core/view-manager/single-view.ts +++ b/blocksuite/affine/data-view/src/core/view-manager/single-view.ts @@ -1,3 +1,4 @@ +import type { UniComponent } from '@blocksuite/affine-shared/types'; import type { InsertToPosition } from '@blocksuite/affine-shared/utils'; 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 { TraitKey } from '../traits/key.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 Cell, CellBase } from './cell.js'; import type { Property } from './property.js'; diff --git a/blocksuite/affine/data-view/src/core/view/data-view.ts b/blocksuite/affine/data-view/src/core/view/data-view.ts index 875f5bc42..2545a3301 100644 --- a/blocksuite/affine/data-view/src/core/view/data-view.ts +++ b/blocksuite/affine/data-view/src/core/view/data-view.ts @@ -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 { ViewManager } from '../view-manager/view-manager.js'; import type { DataViewInstance, DataViewProps } from './types.js'; diff --git a/blocksuite/affine/data-view/src/core/widget/types.ts b/blocksuite/affine/data-view/src/core/widget/types.ts index 0be0f6f7b..9da068b42 100644 --- a/blocksuite/affine/data-view/src/core/widget/types.ts +++ b/blocksuite/affine/data-view/src/core/widget/types.ts @@ -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'; export type DataViewWidgetProps = { diff --git a/blocksuite/affine/data-view/src/view-presets/kanban/mobile/cell.ts b/blocksuite/affine/data-view/src/view-presets/kanban/mobile/cell.ts index bfb215627..ef4772c31 100644 --- a/blocksuite/affine/data-view/src/view-presets/kanban/mobile/cell.ts +++ b/blocksuite/affine/data-view/src/view-presets/kanban/mobile/cell.ts @@ -3,10 +3,9 @@ import { unsafeCSSVarV2 } from '@blocksuite/affine-shared/theme'; import { ShadowlessElement } from '@blocksuite/block-std'; 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 { property, state } from 'lit/decorators.js'; -import { createRef } from 'lit/directives/ref.js'; import { html } from 'lit/static-html.js'; import type { @@ -51,7 +50,7 @@ export class MobileKanbanCell extends SignalWatcher( ) { static override styles = styles; - private readonly _cell = createRef(); + private readonly _cell = signal(); isEditing$ = computed(() => { const selection = this.kanban?.props.selection$.value; diff --git a/blocksuite/affine/data-view/src/view-presets/kanban/pc/cell.ts b/blocksuite/affine/data-view/src/view-presets/kanban/pc/cell.ts index 3b90dc2a3..652610250 100644 --- a/blocksuite/affine/data-view/src/view-presets/kanban/pc/cell.ts +++ b/blocksuite/affine/data-view/src/view-presets/kanban/pc/cell.ts @@ -2,9 +2,9 @@ import { ShadowlessElement } from '@blocksuite/block-std'; import { SignalWatcher, WithDisposable } from '@blocksuite/global/lit'; +import { signal } from '@preact/signals-core'; import { css } from 'lit'; import { property, state } from 'lit/decorators.js'; -import { createRef } from 'lit/directives/ref.js'; import { html } from 'lit/static-html.js'; import type { @@ -59,7 +59,7 @@ export class KanbanCell extends SignalWatcher( ) { static override styles = styles; - private readonly _cell = createRef(); + private readonly _cell = signal(); selectCurrentCell = (editing: boolean) => { const selectionView = this.closest( diff --git a/blocksuite/affine/data-view/src/view-presets/table/mobile/cell.ts b/blocksuite/affine/data-view/src/view-presets/table/mobile/cell.ts index 3108229e4..9314fb0fd 100644 --- a/blocksuite/affine/data-view/src/view-presets/table/mobile/cell.ts +++ b/blocksuite/affine/data-view/src/view-presets/table/mobile/cell.ts @@ -1,9 +1,8 @@ import { ShadowlessElement } from '@blocksuite/block-std'; 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 { property, state } from 'lit/decorators.js'; -import { createRef } from 'lit/directives/ref.js'; import { type CellRenderProps, @@ -36,7 +35,7 @@ export class MobileTableCell extends SignalWatcher( } `; - private readonly _cell = createRef(); + private readonly _cell = signal(); @property({ attribute: false }) accessor column!: TableColumn; diff --git a/blocksuite/affine/data-view/src/view-presets/table/pc/cell.ts b/blocksuite/affine/data-view/src/view-presets/table/pc/cell.ts index d0671807a..82c92fb33 100644 --- a/blocksuite/affine/data-view/src/view-presets/table/pc/cell.ts +++ b/blocksuite/affine/data-view/src/view-presets/table/pc/cell.ts @@ -1,9 +1,8 @@ import { ShadowlessElement } from '@blocksuite/block-std'; 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 { property, state } from 'lit/decorators.js'; -import { createRef } from 'lit/directives/ref.js'; import { renderUniLit } from '../../../core/index.js'; import type { @@ -40,7 +39,7 @@ export class DatabaseCellContainer extends SignalWatcher( } `; - private readonly _cell = createRef(); + private readonly _cell = signal(); @property({ attribute: false }) accessor column!: TableColumn; diff --git a/blocksuite/affine/shared/src/types/index.ts b/blocksuite/affine/shared/src/types/index.ts index 531a19a2f..e93443874 100644 --- a/blocksuite/affine/shared/src/types/index.ts +++ b/blocksuite/affine/shared/src/types/index.ts @@ -5,7 +5,7 @@ import type { } from '@blocksuite/affine-model'; import type { BlockComponent } from '@blocksuite/block-std'; import type { BlockModel } from '@blocksuite/store'; - +export * from './uni-component'; export interface EditingState { element: BlockComponent; model: BlockModel; diff --git a/blocksuite/affine/shared/src/types/uni-component.ts b/blocksuite/affine/shared/src/types/uni-component.ts new file mode 100644 index 000000000..59ec93479 --- /dev/null +++ b/blocksuite/affine/shared/src/types/uni-component.ts @@ -0,0 +1,15 @@ +export type ExposeWrapper< + Expose extends NonNullable = NonNullable, +> = (value?: Expose) => void; +export type UniComponentReturn> = { + update: (props: Props) => void; + unmount: () => void; +}; +export type UniComponent< + Props = NonNullable, + Expose extends NonNullable = NonNullable, +> = ( + ele: HTMLElement, + props: Props, + expose: ExposeWrapper +) => UniComponentReturn; diff --git a/packages/frontend/component/src/lit-react/lit-portal/index.ts b/packages/frontend/component/src/lit-react/lit-portal/index.ts index 007705f09..c7005b207 100644 --- a/packages/frontend/component/src/lit-react/lit-portal/index.ts +++ b/packages/frontend/component/src/lit-react/lit-portal/index.ts @@ -3,3 +3,4 @@ export { useLitPortal, useLitPortalFactory, } from './lit-portal'; +export { uniReactRoot } from './uni-component'; diff --git a/packages/frontend/component/src/lit-react/lit-portal/uni-component.tsx b/packages/frontend/component/src/lit-react/lit-portal/uni-component.tsx new file mode 100644 index 000000000..dedbf3fac --- /dev/null +++ b/packages/frontend/component/src/lit-react/lit-portal/uni-component.tsx @@ -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; + dataRef: DataRef; + expose: ExposeWrapper; + }) { + const Component = props.component; + const [data, setData] = useState(props.dataRef.value); + useEffect(() => { + return props.dataRef.subscribe(() => { + setData(props.dataRef.value); + }); + }, [props.dataRef]); + return createPortal(, props.ele); + }, + () => true +); +type DataRef = { + value: T; + subscribe: (update: () => void) => void; + update: (props: T) => void; +}; +const createDataRef = (data: T): DataRef => { + 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 = new Set(); + let updateNodes = () => {}; + return { + Root: () => { + const [, forceUpdate] = useState({}); + useEffect(() => { + updateNodes = () => forceUpdate({}); + }, []); + return nodes; + }, + createUniComponent: >( + component: ComponentType + ): UniComponent => { + return (ele: HTMLElement, props: T, expose) => { + const dataRef = createDataRef(props); + const node = ( + + ); + nodes.add(node); + updateNodes(); + return { + update: (props: T) => { + dataRef.update(props); + }, + unmount: () => { + nodes.delete(node); + updateNodes(); + }, + } satisfies UniComponentReturn; + }; + }, + }; +}; +export const uniReactRoot = createUniReactRoot(); diff --git a/packages/frontend/core/src/components/context/index.tsx b/packages/frontend/core/src/components/context/index.tsx index 2e9bec4ff..dc13e8981 100644 --- a/packages/frontend/core/src/components/context/index.tsx +++ b/packages/frontend/core/src/components/context/index.tsx @@ -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 { ThemeProvider } from '@affine/core/components/theme-provider'; import type { createStore } from 'jotai'; @@ -25,6 +29,7 @@ export function AffineContext(props: AffineContextProps) { )} > {props.children} + , ); }