summaryrefslogtreecommitdiffhomepage
path: root/packages/ui/src
diff options
context:
space:
mode:
authorBrendan Allan <[email protected]>2026-04-02 17:40:03 +0800
committerGitHub <[email protected]>2026-04-02 09:40:03 +0000
commit69d047ae7dd84d4c4de41e09b1ecee88e3fdc3d3 (patch)
tree832dc97a20d9e1ee0ac4fba7b9daa689691a18c3 /packages/ui/src
parent327f62526a7f60c1c67ae017d1b105466bb730e9 (diff)
downloadopencode-69d047ae7dd84d4c4de41e09b1ecee88e3fdc3d3.tar.gz
opencode-69d047ae7dd84d4c4de41e09b1ecee88e3fdc3d3.zip
cleanup event listeners with solid-primitives/event-listener (#20619)
Diffstat (limited to 'packages/ui/src')
-rw-r--r--packages/ui/src/components/file.tsx16
-rw-r--r--packages/ui/src/components/list.tsx6
-rw-r--r--packages/ui/src/components/popover.tsx24
-rw-r--r--packages/ui/src/context/dialog.tsx4
-rw-r--r--packages/ui/src/hooks/create-auto-scroll.tsx18
-rw-r--r--packages/ui/src/pierre/file-find.ts24
-rw-r--r--packages/ui/src/theme/context.tsx13
7 files changed, 35 insertions, 70 deletions
diff --git a/packages/ui/src/components/file.tsx b/packages/ui/src/components/file.tsx
index 15915dd52..fb488729e 100644
--- a/packages/ui/src/components/file.tsx
+++ b/packages/ui/src/components/file.tsx
@@ -16,6 +16,7 @@ import {
} from "@pierre/diffs"
import { type PreloadMultiFileDiffResult } from "@pierre/diffs/ssr"
import { createMediaQuery } from "@solid-primitives/media"
+import { makeEventListener } from "@solid-primitives/event-listener"
import { ComponentProps, createEffect, createMemo, createSignal, onCleanup, onMount, Show, splitProps } from "solid-js"
import { createDefaultOptions, styleVariables } from "../pierre"
import { markCommentedDiffLines, markCommentedFileLines } from "../pierre/commented-lines"
@@ -286,17 +287,10 @@ function useFileViewer(config: ViewerConfig) {
createEffect(() => {
if (!config.enableLineSelection()) return
- container.addEventListener("mousedown", handleMouseDown)
- container.addEventListener("mousemove", handleMouseMove)
- window.addEventListener("mouseup", handleMouseUp)
- document.addEventListener("selectionchange", handleSelectionChange)
-
- onCleanup(() => {
- container.removeEventListener("mousedown", handleMouseDown)
- container.removeEventListener("mousemove", handleMouseMove)
- window.removeEventListener("mouseup", handleMouseUp)
- document.removeEventListener("selectionchange", handleSelectionChange)
- })
+ makeEventListener(container, "mousedown", handleMouseDown)
+ makeEventListener(container, "mousemove", handleMouseMove)
+ makeEventListener(window, "mouseup", handleMouseUp)
+ makeEventListener(document, "selectionchange", handleSelectionChange)
})
onCleanup(() => {
diff --git a/packages/ui/src/components/list.tsx b/packages/ui/src/components/list.tsx
index 8ce45bc5c..b5879624e 100644
--- a/packages/ui/src/components/list.tsx
+++ b/packages/ui/src/components/list.tsx
@@ -1,6 +1,7 @@
import { type FilteredListProps, useFilteredList } from "@opencode-ai/ui/hooks"
-import { createEffect, For, onCleanup, type JSX, on, Show } from "solid-js"
+import { createEffect, For, type JSX, on, Show } from "solid-js"
import { createStore } from "solid-js/store"
+import { makeEventListener } from "@solid-primitives/event-listener"
import { useI18n } from "../context/i18n"
import { Icon, type IconProps } from "./icon"
import { IconButton } from "./icon-button"
@@ -228,9 +229,8 @@ export function List<T>(props: ListProps<T> & { ref?: (ref: ListRef) => void })
setState("stuck", rect.top <= scrollRect.top + 1 && scroll.scrollTop > 0)
}
- scroll.addEventListener("scroll", handler, { passive: true })
+ makeEventListener(scroll, "scroll", handler, { passive: true })
handler()
- onCleanup(() => scroll.removeEventListener("scroll", handler))
})
return (
diff --git a/packages/ui/src/components/popover.tsx b/packages/ui/src/components/popover.tsx
index 9d3da4109..8263640a5 100644
--- a/packages/ui/src/components/popover.tsx
+++ b/packages/ui/src/components/popover.tsx
@@ -1,15 +1,7 @@
import { Popover as Kobalte } from "@kobalte/core/popover"
-import {
- ComponentProps,
- JSXElement,
- ParentProps,
- Show,
- createEffect,
- onCleanup,
- splitProps,
- ValidComponent,
-} from "solid-js"
+import { ComponentProps, JSXElement, ParentProps, Show, createEffect, splitProps, ValidComponent } from "solid-js"
import { createStore } from "solid-js/store"
+import { makeEventListener } from "@solid-primitives/event-listener"
import { useI18n } from "../context/i18n"
import { IconButton } from "./icon-button"
@@ -104,15 +96,9 @@ export function Popover<T extends ValidComponent = "div">(props: PopoverProps<T>
close("outside")
}
- window.addEventListener("keydown", onKeyDown, true)
- window.addEventListener("pointerdown", onPointerDown, true)
- window.addEventListener("focusin", onFocusIn, true)
-
- onCleanup(() => {
- window.removeEventListener("keydown", onKeyDown, true)
- window.removeEventListener("pointerdown", onPointerDown, true)
- window.removeEventListener("focusin", onFocusIn, true)
- })
+ makeEventListener(window, "keydown", onKeyDown, { capture: true })
+ makeEventListener(window, "pointerdown", onPointerDown, { capture: true })
+ makeEventListener(window, "focusin", onFocusIn, { capture: true })
})
const content = () => (
diff --git a/packages/ui/src/context/dialog.tsx b/packages/ui/src/context/dialog.tsx
index afba5f648..c1c56212b 100644
--- a/packages/ui/src/context/dialog.tsx
+++ b/packages/ui/src/context/dialog.tsx
@@ -12,6 +12,7 @@ import {
type JSX,
} from "solid-js"
import { Dialog as Kobalte } from "@kobalte/core/dialog"
+import { makeEventListener } from "@solid-primitives/event-listener"
type DialogElement = () => JSX.Element
@@ -68,8 +69,7 @@ function init() {
event.stopPropagation()
}
- window.addEventListener("keydown", onKeyDown, true)
- onCleanup(() => window.removeEventListener("keydown", onKeyDown, true))
+ makeEventListener(window, "keydown", onKeyDown, { capture: true })
})
const show = (element: DialogElement, owner: Owner, onClose?: () => void) => {
diff --git a/packages/ui/src/hooks/create-auto-scroll.tsx b/packages/ui/src/hooks/create-auto-scroll.tsx
index 3dc520c62..9733b094e 100644
--- a/packages/ui/src/hooks/create-auto-scroll.tsx
+++ b/packages/ui/src/hooks/create-auto-scroll.tsx
@@ -1,5 +1,6 @@
-import { createEffect, on, onCleanup } from "solid-js"
+import { createEffect, createSignal, on, onCleanup } from "solid-js"
import { createStore } from "solid-js/store"
+import { makeEventListener } from "@solid-primitives/event-listener"
import { createResizeObserver } from "@solid-primitives/resize-observer"
export interface AutoScrollOptions {
@@ -14,7 +15,6 @@ export function createAutoScroll(options: AutoScrollOptions) {
let settling = false
let settleTimer: ReturnType<typeof setTimeout> | undefined
let autoTimer: ReturnType<typeof setTimeout> | undefined
- let cleanup: (() => void) | undefined
let auto: { top: number; time: number } | undefined
const threshold = () => options.bottomThreshold ?? 10
@@ -216,26 +216,14 @@ export function createAutoScroll(options: AutoScrollOptions) {
onCleanup(() => {
if (settleTimer) clearTimeout(settleTimer)
if (autoTimer) clearTimeout(autoTimer)
- if (cleanup) cleanup()
})
return {
scrollRef: (el: HTMLElement | undefined) => {
- if (cleanup) {
- cleanup()
- cleanup = undefined
- }
-
- scroll = el
-
if (!el) return
updateOverflowAnchor(el)
- el.addEventListener("wheel", handleWheel, { passive: true })
-
- cleanup = () => {
- el.removeEventListener("wheel", handleWheel)
- }
+ makeEventListener(el, "wheel", handleWheel, { passive: true })
},
contentRef: (el: HTMLElement | undefined) => setStore("contentRef", el),
handleScroll,
diff --git a/packages/ui/src/pierre/file-find.ts b/packages/ui/src/pierre/file-find.ts
index 841b57edc..d1cf6dd30 100644
--- a/packages/ui/src/pierre/file-find.ts
+++ b/packages/ui/src/pierre/file-find.ts
@@ -1,4 +1,5 @@
-import { createEffect, onCleanup, onMount } from "solid-js"
+import { createEffect, createSignal, onCleanup, onMount } from "solid-js"
+import { makeEventListener } from "@solid-primitives/event-listener"
import { createResizeObserver } from "@solid-primitives/resize-observer"
import { createStore } from "solid-js/store"
@@ -105,9 +106,9 @@ type CreateFileFindOptions = {
export function createFileFind(opts: CreateFileFindOptions) {
let input: HTMLInputElement | undefined
let overlayFrame: number | undefined
- let overlayScroll: HTMLElement[] = []
let mode: "highlights" | "overlay" = "overlay"
let hits: Range[] = []
+ const [overlayScroll, setOverlayScroll] = createSignal<HTMLElement[]>([])
const [state, setState] = createStore({
open: false,
@@ -123,8 +124,7 @@ export function createFileFind(opts: CreateFileFindOptions) {
const pos = () => state.pos
const clearOverlayScroll = () => {
- for (const el of overlayScroll) el.removeEventListener("scroll", scheduleOverlay)
- overlayScroll = []
+ setOverlayScroll([])
}
const clearOverlay = () => {
@@ -197,11 +197,11 @@ export function createFileFind(opts: CreateFileFindOptions) {
(node): node is HTMLElement => node instanceof HTMLElement,
)
: []
- if (next.length === overlayScroll.length && next.every((el, i) => el === overlayScroll[i])) return
+ const current = overlayScroll()
+ if (next.length === current.length && next.every((el, i) => el === current[i])) return
clearOverlayScroll()
- overlayScroll = next
- for (const el of overlayScroll) el.addEventListener("scroll", scheduleOverlay, { passive: true })
+ setOverlayScroll(next)
}
const clearFind = () => {
@@ -404,6 +404,10 @@ export function createFileFind(opts: CreateFileFindOptions) {
close,
}
+ createEffect(() => {
+ for (const el of overlayScroll()) makeEventListener(el, "scroll", scheduleOverlay, { passive: true })
+ })
+
onMount(() => {
mode = supportsHighlights() ? "highlights" : "overlay"
installShortcuts()
@@ -425,16 +429,12 @@ export function createFileFind(opts: CreateFileFindOptions) {
const update = () => positionBar()
requestAnimationFrame(update)
- window.addEventListener("resize", update, { passive: true })
+ makeEventListener(window, "resize", update, { passive: true })
const wrapper = opts.wrapper()
if (!wrapper) return
const root = scrollParent(wrapper) ?? wrapper
createResizeObserver(root, update)
-
- onCleanup(() => {
- window.removeEventListener("resize", update)
- })
})
onCleanup(() => {
diff --git a/packages/ui/src/theme/context.tsx b/packages/ui/src/theme/context.tsx
index 7d25ac397..5664eeebd 100644
--- a/packages/ui/src/theme/context.tsx
+++ b/packages/ui/src/theme/context.tsx
@@ -1,5 +1,6 @@
-import { createEffect, onCleanup, onMount } from "solid-js"
+import { createEffect, onMount } from "solid-js"
import { createStore } from "solid-js/store"
+import { makeEventListener } from "@solid-primitives/event-listener"
import { createSimpleContext } from "../context/helper"
import oc2ThemeJson from "./themes/oc-2.json"
import { resolveThemeVariant, themeToCss } from "./resolve"
@@ -237,19 +238,15 @@ export const { use: useTheme, provider: ThemeProvider } = createSimpleContext({
}
}
- if (typeof window === "object") {
- window.addEventListener("storage", onStorage)
- onCleanup(() => window.removeEventListener("storage", onStorage))
- }
-
onMount(() => {
+ makeEventListener(window, "storage", onStorage)
+
const mediaQuery = window.matchMedia("(prefers-color-scheme: dark)")
const onMedia = () => {
if (store.colorScheme !== "system") return
setStore("mode", getSystemMode())
}
- mediaQuery.addEventListener("change", onMedia)
- onCleanup(() => mediaQuery.removeEventListener("change", onMedia))
+ makeEventListener(mediaQuery, "change", onMedia)
const rawTheme = read(STORAGE_KEYS.THEME_ID)
const savedTheme = normalize(rawTheme ?? props.defaultTheme) ?? "oc-2"