Files
Automaaval/dist/zacatraz/_internal/panel/models/react_component.ts
T
2026-03-14 21:48:05 +00:00

274 lines
7.1 KiB
TypeScript

import type * as p from "@bokehjs/core/properties"
import type {Transform} from "sucrase"
import {
ReactiveESM, ReactiveESMView, model_getter, model_setter,
} from "./reactive_esm"
export class ReactComponentView extends ReactiveESMView {
declare model: ReactComponent
declare style_cache: HTMLHeadElement
model_getter = model_getter
model_setter = model_setter
override render_esm(): void {
if (this.model.usesMui) {
this.style_cache = document.createElement("head")
this.shadow_el.insertBefore(this.style_cache, this.container)
}
super.render_esm()
}
override after_rendered(): void {
const handlers = (this._lifecycle_handlers.get("after_render") || [])
for (const cb of handlers) {
cb()
}
if (!this._rendered) {
for (const cb of (this._lifecycle_handlers.get("after_layout") || [])) {
cb()
}
}
this._rendered = true
}
protected override _render_code(): string {
let render_code = `
if (rendered && view.model.usesReact) {
view._changing = true
const root = createRoot(view.container)
try {
root.render(rendered)
} catch(e) {
view.render_error(e)
}
}`
let import_code
if (this.model.bundle) {
import_code = `
const ns = await view._module_cache.get(view.model.bundle)
const {React, createRoot} = ns.default`
} else {
import_code = `
import * as React from "react"
import { createRoot } from "react-dom/client"`
}
if (this.model.usesMui) {
if (this.model.bundle) {
import_code = `
const ns = await view._module_cache.get(view.model.bundle)
const {CacheProvider, React, createCache, createRoot} = ns.default`
} else {
import_code = `
${import_code}
import createCache from "@emotion/cache"
import { CacheProvider } from "@emotion/react"`
}
render_code = `
if (rendered) {
const cache = createCache({
key: 'css-${this.model.id}',
prepend: true,
container: view.style_cache,
})
rendered = React.createElement(CacheProvider, {value: cache}, rendered)
}
${render_code}`
}
return `
const view = Bokeh.index.find_one_by_id('${this.model.id}')
${import_code}
class Child extends React.Component {
get view() {
const model = this.props.parent.model.data[this.props.name]
const models = Array.isArray(model) ? model : [model]
return this.props.parent.get_child_view(models[this.props.index])
}
get element() {
const view = this.view
return view == null ? null : view.el
}
componentDidMount() {
this.view.render()
this.view.after_render()
this.props.parent.on_child_render(this.props.name, (new_views) => {
this.forceUpdate()
const view = this.view
if (new_views.includes(view)) {
view.render()
view.after_render()
}
})
}
append_child(ref) {
if (ref != null) {
const view = this.view
if (view != null) {
ref.appendChild(this.element)
}
}
}
render() {
return React.createElement('div', {className: "child-wrapper", ref: (ref) => this.append_child(ref)})
}
}
function react_getter(target, name) {
if (name == "useState") {
return (prop) => {
const data_model = target.model.data
if (Reflect.has(data_model, prop)) {
const [value, setValue] = React.useState(data_model.attributes[prop]);
react_proxy.on(prop, () => setValue(data_model.attributes[prop]))
React.useEffect(() => data_model.setv({[prop]: value}), [value])
return [value, setValue]
}
return undefined
}
} else if (name === "get_child") {
return (child) => {
const data_model = target.model.data
const value = data_model.attributes[child]
if (Array.isArray(value)) {
const children = []
for (let i = 0; i<value.length; i++) {
children.push(
React.createElement(Child, {parent: target, name: child, index: i, key: child+i})
)
}
const [children_state, set_children] = React.useState(children);
target.on_child_render(child, (new_views) => {
const value = data_model.attributes[child]
if (new_views.length && children_state.length !== value.length) {
const children = []
for (let i = 0; i<value.length; i++) {
let new_child
if (i < children_state.length) {
new_child = children_state[i]
} else {
new_child = React.createElement(Child, {parent: target, name: child, index: i, key: child+i})
}
children.push(new_child)
}
set_children(children)
}
})
return children_state
} else {
return React.createElement(Child, {parent: target, name: child, index: 0})
}
}
}
return target.model_getter(target, name)
}
const react_proxy = new Proxy(view, {
get: react_getter,
set: view.model_setter
})
class ErrorBoundary extends React.Component {
constructor(props) {
super(props);
// initialize the error state
this.state = { hasError: false };
}
// if an error happened, set the state to true
static getDerivedStateFromError(error) {
return { hasError: true };
}
componentDidCatch(error) {
this.props.view.render_error(error)
}
render() {
if (this.state.hasError) {
return React.createElement('div')
}
return React.createElement('div', {className: "error-wrapper"}, this.props.children);
}
}
class Component extends React.Component {
componentDidMount() {
this.props.view._changing = false
this.props.view.after_rendered()
}
render() {
let rendered = React.createElement(this.props.view.render_fn, this.props)
if (this.props.view.model.dev) {
rendered = React.createElement(ErrorBoundary, {view}, rendered)
}
return rendered
}
}
function render() {
const props = {view, model: react_proxy, data: view.model.data, el: view.container}
let rendered = React.createElement(Component, props)
${render_code}
}
export default {render}`
}
}
export namespace ReactComponent {
export type Attrs = p.AttrsOf<Props>
export type Props = ReactiveESM.Props
}
export interface ReactComponent extends ReactComponent.Attrs {}
export class ReactComponent extends ReactiveESM {
declare properties: ReactComponent.Props
override sucrase_transforms: Transform[] = ["typescript", "jsx"]
constructor(attrs?: Partial<ReactComponent.Attrs>) {
super(attrs)
}
get usesMui(): boolean {
if (this.importmap?.imports) {
return Object.keys(this.importmap?.imports).some(k => k.startsWith("@mui"))
}
return false
}
get usesReact(): boolean {
return this.compiled !== null && this.compiled.includes("React")
}
override compile(): string | null {
const compiled = super.compile()
if (this.bundle) {
return compiled
} else if (compiled === null || !compiled.includes("React")) {
return compiled
}
return `
import * as React from "react"
${compiled}`
}
static override __module__ = "panel.models.esm"
static {
this.prototype.default_view = ReactComponentView
}
}