slots.tsx 2.1 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465
  1. import type { TuiPluginApi, TuiSlotContext, TuiSlotMap, TuiSlotProps } from "@opencode-ai/plugin/tui"
  2. import { createSlot, createSolidSlotRegistry, type JSX, type SolidPlugin } from "@opentui/solid"
  3. import { createSignal } from "solid-js"
  4. import { isRecord } from "../util/record"
  5. type RuntimeSlotMap = TuiSlotMap<Record<string, object>>
  6. type SlotView = <Name extends string>(props: TuiSlotProps<Name>) => JSX.Element | null
  7. export type HostSlotPlugin<Slots extends Record<string, object> = {}> = SolidPlugin<TuiSlotMap<Slots>, TuiSlotContext>
  8. export type HostPluginApi = TuiPluginApi
  9. export type HostSlots = {
  10. register: {
  11. (plugin: HostSlotPlugin): () => void
  12. <Slots extends Record<string, object>>(plugin: HostSlotPlugin<Slots>): () => void
  13. }
  14. dispose: () => void
  15. }
  16. function isHostSlotPlugin(value: unknown): value is HostSlotPlugin<Record<string, object>> {
  17. if (!isRecord(value)) return false
  18. if (typeof value.id !== "string") return false
  19. return isRecord(value.slots)
  20. }
  21. export function createSlots() {
  22. const empty: SlotView = () => null
  23. const [view, setView] = createSignal<SlotView>(empty)
  24. const Slot: SlotView = (props) => view()(props)
  25. return {
  26. Slot,
  27. setup(api: HostPluginApi): HostSlots {
  28. const registry = createSolidSlotRegistry<RuntimeSlotMap, TuiSlotContext>(
  29. api.renderer,
  30. { theme: api.theme },
  31. {
  32. onPluginError(event) {
  33. console.error("[tui.slot] plugin error", {
  34. plugin: event.pluginId,
  35. slot: event.slot,
  36. phase: event.phase,
  37. source: event.source,
  38. message: event.error.message,
  39. })
  40. },
  41. },
  42. )
  43. const slot = createSlot<RuntimeSlotMap, TuiSlotContext>(registry)
  44. setView(() => (props: TuiSlotProps<string>) => slot(props))
  45. return {
  46. register(plugin: HostSlotPlugin) {
  47. if (!isHostSlotPlugin(plugin)) return () => {}
  48. return registry.register(plugin)
  49. },
  50. dispose() {
  51. setView(() => empty)
  52. },
  53. }
  54. },
  55. clear() {
  56. setView(() => empty)
  57. },
  58. }
  59. }