-
-
Notifications
You must be signed in to change notification settings - Fork 2.9k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
refactor(component): render react element into lit (#6124)
See docs in https://insider.affine.pro/share/af3478a2-9c9c-4d16-864d-bffa1eb10eb6/oL1ifjA4rKv7HRn5nYzIF This PR also enables opening split view by ctrl-click a page link in a doc.
- Loading branch information
Showing
7 changed files
with
195 additions
and
20 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +1,2 @@ | ||
export { createComponent as createReactComponentFromLit } from './create-component'; | ||
export * from './lit-portal'; |
5 changes: 5 additions & 0 deletions
5
packages/frontend/component/src/lit-react/lit-portal/index.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
export { | ||
type ElementOrFactory, | ||
useLitPortal, | ||
useLitPortalFactory, | ||
} from './lite-portal'; |
148 changes: 148 additions & 0 deletions
148
packages/frontend/component/src/lit-react/lit-portal/lite-portal.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,148 @@ | ||
import { html, LitElement } from 'lit'; | ||
import { nanoid } from 'nanoid'; | ||
import { useCallback, useMemo, useState } from 'react'; | ||
import ReactDOM from 'react-dom'; | ||
|
||
type PortalEvent = { | ||
name: 'connectedCallback' | 'disconnectedCallback' | 'willUpdate'; | ||
target: LitReactPortal; | ||
previousPortalId?: string; | ||
}; | ||
|
||
type PortalListener = (event: PortalEvent) => void; | ||
const listeners: Set<PortalListener> = new Set(); | ||
|
||
export function createLitPortalAnchor(callback: (event: PortalEvent) => void) { | ||
const id = nanoid(); | ||
// todo: clean up listeners? | ||
listeners.add(event => { | ||
if (event.target.portalId !== id) { | ||
return; | ||
} | ||
callback(event); | ||
}); | ||
return html`<lit-react-portal portalId=${id}></lit-react-portal>`; | ||
} | ||
|
||
class LitReactPortal extends LitElement { | ||
portalId: string = ''; | ||
|
||
static override get properties() { | ||
return { | ||
portalId: { type: String }, | ||
}; | ||
} | ||
|
||
// do not enable shadow root | ||
override createRenderRoot() { | ||
return this; | ||
} | ||
|
||
override connectedCallback() { | ||
listeners.forEach(l => | ||
l({ | ||
name: 'connectedCallback', | ||
target: this, | ||
}) | ||
); | ||
} | ||
|
||
override disconnectedCallback() { | ||
super.disconnectedCallback(); | ||
listeners.forEach(l => | ||
l({ | ||
name: 'disconnectedCallback', | ||
target: this, | ||
}) | ||
); | ||
} | ||
|
||
override willUpdate(changedProperties: any) { | ||
super.willUpdate(changedProperties); | ||
listeners.forEach(l => | ||
l({ | ||
name: 'willUpdate', | ||
target: this, | ||
previousPortalId: changedProperties.get('portalId'), | ||
}) | ||
); | ||
} | ||
} | ||
|
||
window.customElements.define('lit-react-portal', LitReactPortal); | ||
|
||
declare global { | ||
interface HTMLElementTagNameMap { | ||
'lit-react-portal': LitReactPortal; | ||
} | ||
} | ||
|
||
export type ElementOrFactory = React.ReactElement | (() => React.ReactElement); | ||
|
||
type LitPortal = { | ||
id: string; | ||
portal: React.ReactPortal; | ||
}; | ||
|
||
// returns a factory function that renders a given element to a lit template | ||
export const useLitPortalFactory = () => { | ||
const [portals, setPortals] = useState<LitPortal[]>([]); | ||
|
||
return [ | ||
useCallback( | ||
(elementOrFactory: React.ReactElement | (() => React.ReactElement)) => { | ||
const element = | ||
typeof elementOrFactory === 'function' | ||
? elementOrFactory() | ||
: elementOrFactory; | ||
return createLitPortalAnchor(event => { | ||
const portalId = event.target.portalId; | ||
setPortals(portals => { | ||
const newPortals = portals.filter( | ||
p => p.id !== event.previousPortalId && p.id !== portalId | ||
); | ||
if (event.name !== 'disconnectedCallback') { | ||
newPortals.push({ | ||
id: portalId, | ||
portal: ReactDOM.createPortal(element, event.target), | ||
}); | ||
} | ||
return newPortals; | ||
}); | ||
}); | ||
}, | ||
[setPortals] | ||
), | ||
portals, | ||
] as const; | ||
}; | ||
|
||
// render a react element to a lit template | ||
export const useLitPortal = ( | ||
elementOrFactory: React.ReactElement | (() => React.ReactElement) | ||
) => { | ||
const [anchor, setAnchor] = useState<HTMLElement>(); | ||
const template = useMemo( | ||
() => | ||
createLitPortalAnchor(event => { | ||
if (event.name !== 'disconnectedCallback') { | ||
setAnchor(event.target as HTMLElement); | ||
} else { | ||
setAnchor(undefined); | ||
} | ||
}), | ||
[] | ||
); | ||
|
||
const element = useMemo( | ||
() => | ||
typeof elementOrFactory === 'function' | ||
? elementOrFactory() | ||
: elementOrFactory, | ||
[elementOrFactory] | ||
); | ||
return { | ||
template, | ||
portal: anchor ? ReactDOM.createPortal(element, anchor) : undefined, | ||
}; | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters