Files
supabase/apps/studio/components/interfaces/SQLEditor/InlineWidget.tsx
T
Gildas Garcia 86a3f8b03d chore: upgrade to react-19 (#45886)
- Most changes are related to either types or `useRef` usages (it now
requires an initial value).
- also updated `vaul` to its latest version and haven't noticed any
change ([design-system
demo](https://design-system-git-react-19-supabase.vercel.app/design-system/docs/components/drawer))

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **New Features**
  * Upgraded workspace to React 19.

* **Bug Fixes**
* Improved null-safety and ref handling across editors, UI components,
shortcuts, and markdown/image rendering to reduce runtime errors.
* Safer event/timeout/interval cleanup and more robust command/context
handling.

* **Chores**
  * Bumped vaul dependency versions.

* **Documentation**
* Type and TypeScript accuracy improvements for clearer developer
feedback.

<!-- review_stack_entry_start -->

[![Review Change
Stack](https://storage.googleapis.com/coderabbit_public_assets/review-stack-in-coderabbit-ui.svg)](https://app.coderabbit.ai/change-stack/supabase/supabase/pull/45886)

<!-- review_stack_entry_end -->
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-05-15 16:04:41 +02:00

130 lines
3.8 KiB
TypeScript

import type { editor } from 'monaco-editor'
import { PropsWithChildren, useEffect, useMemo, useRef } from 'react'
import { createPortal } from 'react-dom'
export interface InlineWidgetProps {
/**
* The Monaco editor instance.
*/
editor: editor.IStandaloneCodeEditor | editor.IStandaloneDiffEditor
/**
* ID used by Monaco to reference this widget
*/
id: string
/**
* The line number after or before which this zone should appear.
* Use 0 to place a view zone before the first line number.
*/
afterLineNumber: number
beforeLineNumber?: number
/**
* The height in lines of the view zone.
* @default 1
*/
heightInLines?: number
}
/**
* Adds an inline widget to a Monaco editor instance. Acts as a
* container for custom widgets that are rendered inline.
*
* Implemented using the same techniques VS Code uses for their inline
* widgets (such as "Go to References"), but built for React.
* Uses a combination of a view zone and overlay widget.
*/
const InlineWidget = ({
children,
editor,
id,
beforeLineNumber,
afterLineNumber = 0,
heightInLines = 1,
}: PropsWithChildren<InlineWidgetProps>) => {
const lineNumber = beforeLineNumber ?? afterLineNumber
const key = `${id}-${lineNumber.toString()}`
const containerElement = useMemo(() => document.createElement('div'), [])
const zoneIdRef = useRef<string>(null)
const viewZoneRef = useRef<{
top: number
height: number
heightInLines: number
}>({ top: 0, height: 0, heightInLines: heightInLines })
// Get the appropriate editor instance for diff editor
const targetEditor = 'getModifiedEditor' in editor ? editor.getModifiedEditor() : editor
const recalculateLayout = () => {
const layoutInfo = targetEditor.getLayoutInfo()
if (!layoutInfo) {
return
}
containerElement.style.left = `${layoutInfo.contentLeft}px`
containerElement.style.top = `${viewZoneRef.current.top}px`
containerElement.style.width = `${layoutInfo.width - layoutInfo.contentLeft - 20}px`
containerElement.style.height = `${viewZoneRef.current.height}px`
}
const createViewZone = () => {
targetEditor.changeViewZones((accessor) => {
// Remove existing zone if it exists
if (zoneIdRef.current) {
accessor.removeZone(zoneIdRef.current)
}
// Create new zone with current height
zoneIdRef.current = accessor.addZone({
afterLineNumber: beforeLineNumber ?? afterLineNumber,
heightInLines: viewZoneRef.current.heightInLines,
domNode: document.createElement('div'),
onDomNodeTop: (top) => {
viewZoneRef.current.top = top
recalculateLayout()
},
onComputedHeight: (height) => {
viewZoneRef.current.height = height
recalculateLayout()
},
})
})
}
// Initial setup of view zone and overlay widget
useEffect(() => {
const overlayWidget: editor.IOverlayWidget = {
getId: () => id,
getDomNode: () => containerElement,
getPosition: () => null,
}
createViewZone()
targetEditor.addOverlayWidget(overlayWidget)
// Remove the view zone & overlay widget on unmount
return () => {
targetEditor.changeViewZones((accessor) => {
if (zoneIdRef.current) {
accessor.removeZone(zoneIdRef.current)
}
targetEditor.removeOverlayWidget(overlayWidget)
})
}
}, [targetEditor, id, beforeLineNumber, afterLineNumber]) // Note: heightInLines removed from deps
// Update view zone height when heightInLines changes
useEffect(() => {
if (heightInLines !== viewZoneRef.current.heightInLines) {
viewZoneRef.current.heightInLines = heightInLines
createViewZone()
}
}, [heightInLines])
return createPortal(children, containerElement, key)
}
export default InlineWidget