From 4bf707c720c6c9c41ac6b0fc860f663764e40f29 Mon Sep 17 00:00:00 2001 From: 777genius Date: Mon, 25 May 2026 23:15:52 +0300 Subject: [PATCH] fix(build): harden radix ref cleanup patches --- package.json | 8 +- patches/@radix-ui__react-checkbox@1.3.3.patch | 178 +++++++++++ patches/@radix-ui__react-menu@2.1.16.patch | 150 ++++++++++ patches/@radix-ui__react-popper@1.2.8.patch | 88 ++++++ patches/@radix-ui__react-select@2.2.6.patch | 280 ++++++++++++++++++ patches/@radix-ui__react-tooltip@1.2.8.patch | 102 +++++++ pnpm-lock.yaml | 45 ++- scripts/ci/verify-radix-presence-patch.mjs | 52 ++++ scripts/ci/verify-radix-renderer-bundle.mjs | 81 +++++ 9 files changed, 968 insertions(+), 16 deletions(-) create mode 100644 patches/@radix-ui__react-checkbox@1.3.3.patch create mode 100644 patches/@radix-ui__react-menu@2.1.16.patch create mode 100644 patches/@radix-ui__react-popper@1.2.8.patch create mode 100644 patches/@radix-ui__react-select@2.2.6.patch create mode 100644 patches/@radix-ui__react-tooltip@1.2.8.patch create mode 100644 scripts/ci/verify-radix-renderer-bundle.mjs diff --git a/package.json b/package.json index 401273f4..48c25107 100644 --- a/package.json +++ b/package.json @@ -33,6 +33,7 @@ "smoke:codex-runtime-install": "tsx scripts/smoke/codex-runtime-install.ts", "prebuild": "node ./scripts/ci/verify-radix-presence-patch.mjs && tsx scripts/fetch-pricing-data.ts && pnpm --filter agent-teams-controller build && pnpm --filter agent-teams-mcp build", "build": "node --max-old-space-size=8192 ./node_modules/electron-vite/bin/electron-vite.js build", + "postbuild": "node ./scripts/ci/verify-radix-renderer-bundle.mjs", "stage-runtime": "node ./scripts/stage-runtime.mjs", "clean:runtime": "node ./scripts/stage-runtime.mjs --clean", "pack:mac": "node ./scripts/electron-builder/dist.mjs --mac", @@ -440,7 +441,12 @@ "patchedDependencies": { "@radix-ui/react-presence@1.1.5": "patches/@radix-ui__react-presence@1.1.5.patch", "@radix-ui/react-focus-scope@1.1.7": "patches/@radix-ui__react-focus-scope@1.1.7.patch", - "@radix-ui/react-dismissable-layer@1.1.11": "patches/@radix-ui__react-dismissable-layer@1.1.11.patch" + "@radix-ui/react-dismissable-layer@1.1.11": "patches/@radix-ui__react-dismissable-layer@1.1.11.patch", + "@radix-ui/react-popper@1.2.8": "patches/@radix-ui__react-popper@1.2.8.patch", + "@radix-ui/react-select@2.2.6": "patches/@radix-ui__react-select@2.2.6.patch", + "@radix-ui/react-tooltip@1.2.8": "patches/@radix-ui__react-tooltip@1.2.8.patch", + "@radix-ui/react-menu@2.1.16": "patches/@radix-ui__react-menu@2.1.16.patch", + "@radix-ui/react-checkbox@1.3.3": "patches/@radix-ui__react-checkbox@1.3.3.patch" } }, "knip": { diff --git a/patches/@radix-ui__react-checkbox@1.3.3.patch b/patches/@radix-ui__react-checkbox@1.3.3.patch new file mode 100644 index 00000000..cfcff131 --- /dev/null +++ b/patches/@radix-ui__react-checkbox@1.3.3.patch @@ -0,0 +1,178 @@ +diff --git a/dist/index.js b/dist/index.js +index 6fb1a27897e543acf531704205f561f98eba0229..5180fb3fbd81c43ad539c3b2bf8047ee845f9602 100644 +--- a/dist/index.js ++++ b/dist/index.js +@@ -59,6 +59,29 @@ var import_jsx_runtime = require("react/jsx-runtime"); + var CHECKBOX_NAME = "Checkbox"; + var [createCheckboxContext, createCheckboxScope] = (0, import_react_context.createContextScope)(CHECKBOX_NAME); + var [CheckboxProviderImpl, useCheckboxContext] = createCheckboxContext(CHECKBOX_NAME); ++function useGuardedNodeSetter(setNode) { ++ const nodeRef = React.useRef(null); ++ const nodeCleanupGenerationRef = React.useRef(0); ++ return React.useCallback((node) => { ++ const syncNode = (nextNode) => { ++ if (nodeRef.current === nextNode) return; ++ nodeRef.current = nextNode; ++ setNode(nextNode); ++ }; ++ nodeCleanupGenerationRef.current += 1; ++ const cleanupGeneration = nodeCleanupGenerationRef.current; ++ if (node) { ++ syncNode(node); ++ return; ++ } ++ queueMicrotask(() => { ++ if (nodeCleanupGenerationRef.current !== cleanupGeneration) { ++ return; ++ } ++ syncNode(null); ++ }); ++ }, [setNode]); ++} + function CheckboxProvider(props) { + const { + __scopeCheckbox, +@@ -82,6 +105,8 @@ function CheckboxProvider(props) { + }); + const [control, setControl] = React.useState(null); + const [bubbleInput, setBubbleInput] = React.useState(null); ++ const setControlRef = useGuardedNodeSetter(setControl); ++ const setBubbleInputRef = useGuardedNodeSetter(setBubbleInput); + const hasConsumerStoppedPropagationRef = React.useRef(false); + const isFormControl = control ? !!form || !!control.closest("form") : ( + // We set this to true by default so that events bubble to forms without JS (SSR) +@@ -92,7 +117,7 @@ function CheckboxProvider(props) { + disabled, + setChecked, + control, +- setControl, ++ setControl: setControlRef, + name, + form, + value, +@@ -101,7 +126,7 @@ function CheckboxProvider(props) { + defaultChecked: isIndeterminate(defaultChecked) ? false : defaultChecked, + isFormControl, + bubbleInput, +- setBubbleInput ++ setBubbleInput: setBubbleInputRef + }; + return /* @__PURE__ */ (0, import_jsx_runtime.jsx)( + CheckboxProviderImpl, +@@ -121,13 +146,13 @@ var CheckboxTrigger = React.forwardRef( + disabled, + checked, + required, +- setControl, ++ setControl: setControlRef, + setChecked, + hasConsumerStoppedPropagationRef, + isFormControl, + bubbleInput + } = useCheckboxContext(TRIGGER_NAME, __scopeCheckbox); +- const composedRefs = (0, import_react_compose_refs.useComposedRefs)(forwardedRef, setControl); ++ const composedRefs = (0, import_react_compose_refs.useComposedRefs)(forwardedRef, setControlRef); + const initialCheckedStateRef = React.useRef(checked); + React.useEffect(() => { + const form = control?.form; +@@ -250,9 +275,9 @@ var CheckboxBubbleInput = React.forwardRef( + value, + form, + bubbleInput, +- setBubbleInput ++ setBubbleInput: setBubbleInputRef + } = useCheckboxContext(BUBBLE_INPUT_NAME, __scopeCheckbox); +- const composedRefs = (0, import_react_compose_refs.useComposedRefs)(forwardedRef, setBubbleInput); ++ const composedRefs = (0, import_react_compose_refs.useComposedRefs)(forwardedRef, setBubbleInputRef); + const prevChecked = (0, import_react_use_previous.usePrevious)(checked); + const controlSize = (0, import_react_use_size.useSize)(control); + React.useEffect(() => { +diff --git a/dist/index.mjs b/dist/index.mjs +index 3f718f60b0032a25ad9386082aab9329bd6f5c7a..0688026b0d188d36dc0c4c036c9ededcc8e4f9fe 100644 +--- a/dist/index.mjs ++++ b/dist/index.mjs +@@ -14,6 +14,29 @@ import { Fragment, jsx, jsxs } from "react/jsx-runtime"; + var CHECKBOX_NAME = "Checkbox"; + var [createCheckboxContext, createCheckboxScope] = createContextScope(CHECKBOX_NAME); + var [CheckboxProviderImpl, useCheckboxContext] = createCheckboxContext(CHECKBOX_NAME); ++function useGuardedNodeSetter(setNode) { ++ const nodeRef = React.useRef(null); ++ const nodeCleanupGenerationRef = React.useRef(0); ++ return React.useCallback((node) => { ++ const syncNode = (nextNode) => { ++ if (nodeRef.current === nextNode) return; ++ nodeRef.current = nextNode; ++ setNode(nextNode); ++ }; ++ nodeCleanupGenerationRef.current += 1; ++ const cleanupGeneration = nodeCleanupGenerationRef.current; ++ if (node) { ++ syncNode(node); ++ return; ++ } ++ queueMicrotask(() => { ++ if (nodeCleanupGenerationRef.current !== cleanupGeneration) { ++ return; ++ } ++ syncNode(null); ++ }); ++ }, [setNode]); ++} + function CheckboxProvider(props) { + const { + __scopeCheckbox, +@@ -37,6 +60,8 @@ function CheckboxProvider(props) { + }); + const [control, setControl] = React.useState(null); + const [bubbleInput, setBubbleInput] = React.useState(null); ++ const setControlRef = useGuardedNodeSetter(setControl); ++ const setBubbleInputRef = useGuardedNodeSetter(setBubbleInput); + const hasConsumerStoppedPropagationRef = React.useRef(false); + const isFormControl = control ? !!form || !!control.closest("form") : ( + // We set this to true by default so that events bubble to forms without JS (SSR) +@@ -47,7 +72,7 @@ function CheckboxProvider(props) { + disabled, + setChecked, + control, +- setControl, ++ setControl: setControlRef, + name, + form, + value, +@@ -56,7 +81,7 @@ function CheckboxProvider(props) { + defaultChecked: isIndeterminate(defaultChecked) ? false : defaultChecked, + isFormControl, + bubbleInput, +- setBubbleInput ++ setBubbleInput: setBubbleInputRef + }; + return /* @__PURE__ */ jsx( + CheckboxProviderImpl, +@@ -76,13 +101,13 @@ var CheckboxTrigger = React.forwardRef( + disabled, + checked, + required, +- setControl, ++ setControl: setControlRef, + setChecked, + hasConsumerStoppedPropagationRef, + isFormControl, + bubbleInput + } = useCheckboxContext(TRIGGER_NAME, __scopeCheckbox); +- const composedRefs = useComposedRefs(forwardedRef, setControl); ++ const composedRefs = useComposedRefs(forwardedRef, setControlRef); + const initialCheckedStateRef = React.useRef(checked); + React.useEffect(() => { + const form = control?.form; +@@ -205,9 +230,9 @@ var CheckboxBubbleInput = React.forwardRef( + value, + form, + bubbleInput, +- setBubbleInput ++ setBubbleInput: setBubbleInputRef + } = useCheckboxContext(BUBBLE_INPUT_NAME, __scopeCheckbox); +- const composedRefs = useComposedRefs(forwardedRef, setBubbleInput); ++ const composedRefs = useComposedRefs(forwardedRef, setBubbleInputRef); + const prevChecked = usePrevious(checked); + const controlSize = useSize(control); + React.useEffect(() => { diff --git a/patches/@radix-ui__react-menu@2.1.16.patch b/patches/@radix-ui__react-menu@2.1.16.patch new file mode 100644 index 00000000..783b0eaa --- /dev/null +++ b/patches/@radix-ui__react-menu@2.1.16.patch @@ -0,0 +1,150 @@ +diff --git a/dist/index.js b/dist/index.js +index 4d102d0cf4c9f82dcf13a384e5af0af36a4285d3..252229df648edad3752bd8db172ad5aa52a527de 100644 +--- a/dist/index.js ++++ b/dist/index.js +@@ -113,10 +113,34 @@ var usePopperScope = (0, import_react_popper.createPopperScope)(); + var useRovingFocusGroupScope = (0, import_react_roving_focus.createRovingFocusGroupScope)(); + var [MenuProvider, useMenuContext] = createMenuContext(MENU_NAME); + var [MenuRootProvider, useMenuRootContext] = createMenuContext(MENU_NAME); ++function useGuardedNodeSetter(setNode) { ++ const nodeRef = React.useRef(null); ++ const nodeCleanupGenerationRef = React.useRef(0); ++ return React.useCallback((node) => { ++ const syncNode = (nextNode) => { ++ if (nodeRef.current === nextNode) return; ++ nodeRef.current = nextNode; ++ setNode(nextNode); ++ }; ++ nodeCleanupGenerationRef.current += 1; ++ const cleanupGeneration = nodeCleanupGenerationRef.current; ++ if (node) { ++ syncNode(node); ++ return; ++ } ++ queueMicrotask(() => { ++ if (nodeCleanupGenerationRef.current !== cleanupGeneration) { ++ return; ++ } ++ syncNode(null); ++ }); ++ }, [setNode]); ++} + var Menu = (props) => { + const { __scopeMenu, open = false, children, dir, onOpenChange, modal = true } = props; + const popperScope = usePopperScope(__scopeMenu); + const [content, setContent] = React.useState(null); ++ const setContentRef = useGuardedNodeSetter(setContent); + const isUsingKeyboardRef = React.useRef(false); + const handleOpenChange = (0, import_react_use_callback_ref.useCallbackRef)(onOpenChange); + const direction = (0, import_react_direction.useDirection)(dir); +@@ -141,7 +165,7 @@ var Menu = (props) => { + open, + onOpenChange: handleOpenChange, + content, +- onContentChange: setContent, ++ onContentChange: setContentRef, + children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)( + MenuRootProvider, + { +@@ -653,6 +677,8 @@ var MenuSub = (props) => { + const popperScope = usePopperScope(__scopeMenu); + const [trigger, setTrigger] = React.useState(null); + const [content, setContent] = React.useState(null); ++ const setTriggerRef = useGuardedNodeSetter(setTrigger); ++ const setContentRef = useGuardedNodeSetter(setContent); + const handleOpenChange = (0, import_react_use_callback_ref.useCallbackRef)(onOpenChange); + React.useEffect(() => { + if (parentMenuContext.open === false) handleOpenChange(false); +@@ -665,7 +691,7 @@ var MenuSub = (props) => { + open, + onOpenChange: handleOpenChange, + content, +- onContentChange: setContent, ++ onContentChange: setContentRef, + children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)( + MenuSubProvider, + { +@@ -673,7 +699,7 @@ var MenuSub = (props) => { + contentId: (0, import_react_id.useId)(), + triggerId: (0, import_react_id.useId)(), + trigger, +- onTriggerChange: setTrigger, ++ onTriggerChange: setTriggerRef, + children + } + ) +diff --git a/dist/index.mjs b/dist/index.mjs +index 10eefb0533ee4fa16b7f0e42671969c2c4464835..680d56317917bff5bec1added766c553c5c00c52 100644 +--- a/dist/index.mjs ++++ b/dist/index.mjs +@@ -46,10 +46,34 @@ var usePopperScope = createPopperScope(); + var useRovingFocusGroupScope = createRovingFocusGroupScope(); + var [MenuProvider, useMenuContext] = createMenuContext(MENU_NAME); + var [MenuRootProvider, useMenuRootContext] = createMenuContext(MENU_NAME); ++function useGuardedNodeSetter(setNode) { ++ const nodeRef = React.useRef(null); ++ const nodeCleanupGenerationRef = React.useRef(0); ++ return React.useCallback((node) => { ++ const syncNode = (nextNode) => { ++ if (nodeRef.current === nextNode) return; ++ nodeRef.current = nextNode; ++ setNode(nextNode); ++ }; ++ nodeCleanupGenerationRef.current += 1; ++ const cleanupGeneration = nodeCleanupGenerationRef.current; ++ if (node) { ++ syncNode(node); ++ return; ++ } ++ queueMicrotask(() => { ++ if (nodeCleanupGenerationRef.current !== cleanupGeneration) { ++ return; ++ } ++ syncNode(null); ++ }); ++ }, [setNode]); ++} + var Menu = (props) => { + const { __scopeMenu, open = false, children, dir, onOpenChange, modal = true } = props; + const popperScope = usePopperScope(__scopeMenu); + const [content, setContent] = React.useState(null); ++ const setContentRef = useGuardedNodeSetter(setContent); + const isUsingKeyboardRef = React.useRef(false); + const handleOpenChange = useCallbackRef(onOpenChange); + const direction = useDirection(dir); +@@ -74,7 +98,7 @@ var Menu = (props) => { + open, + onOpenChange: handleOpenChange, + content, +- onContentChange: setContent, ++ onContentChange: setContentRef, + children: /* @__PURE__ */ jsx( + MenuRootProvider, + { +@@ -586,6 +610,8 @@ var MenuSub = (props) => { + const popperScope = usePopperScope(__scopeMenu); + const [trigger, setTrigger] = React.useState(null); + const [content, setContent] = React.useState(null); ++ const setTriggerRef = useGuardedNodeSetter(setTrigger); ++ const setContentRef = useGuardedNodeSetter(setContent); + const handleOpenChange = useCallbackRef(onOpenChange); + React.useEffect(() => { + if (parentMenuContext.open === false) handleOpenChange(false); +@@ -598,7 +624,7 @@ var MenuSub = (props) => { + open, + onOpenChange: handleOpenChange, + content, +- onContentChange: setContent, ++ onContentChange: setContentRef, + children: /* @__PURE__ */ jsx( + MenuSubProvider, + { +@@ -606,7 +632,7 @@ var MenuSub = (props) => { + contentId: useId(), + triggerId: useId(), + trigger, +- onTriggerChange: setTrigger, ++ onTriggerChange: setTriggerRef, + children + } + ) diff --git a/patches/@radix-ui__react-popper@1.2.8.patch b/patches/@radix-ui__react-popper@1.2.8.patch new file mode 100644 index 00000000..25dce894 --- /dev/null +++ b/patches/@radix-ui__react-popper@1.2.8.patch @@ -0,0 +1,88 @@ +diff --git a/dist/index.js b/dist/index.js +index 5cb1fd007d32021ad950d961dd4a0868576a2c0d..795a24fe0e75aaa042cad0a376a776f3e1436269 100644 +--- a/dist/index.js ++++ b/dist/index.js +@@ -61,6 +61,29 @@ var ALIGN_OPTIONS = ["start", "center", "end"]; + var POPPER_NAME = "Popper"; + var [createPopperContext, createPopperScope] = (0, import_react_context.createContextScope)(POPPER_NAME); + var [PopperProvider, usePopperContext] = createPopperContext(POPPER_NAME); ++function useGuardedNodeSetter(setNode) { ++ const nodeRef = React.useRef(null); ++ const nodeCleanupGenerationRef = React.useRef(0); ++ return React.useCallback((node) => { ++ const syncNode = (nextNode) => { ++ if (nodeRef.current === nextNode) return; ++ nodeRef.current = nextNode; ++ setNode(nextNode); ++ }; ++ nodeCleanupGenerationRef.current += 1; ++ const cleanupGeneration = nodeCleanupGenerationRef.current; ++ if (node) { ++ syncNode(node); ++ return; ++ } ++ queueMicrotask(() => { ++ if (nodeCleanupGenerationRef.current !== cleanupGeneration) { ++ return; ++ } ++ syncNode(null); ++ }); ++ }, [setNode]); ++} + var Popper = (props) => { + const { __scopePopper, children } = props; + const [anchor, setAnchor] = React.useState(null); +@@ -108,7 +131,8 @@ var PopperContent = React.forwardRef( + } = props; + const context = usePopperContext(CONTENT_NAME, __scopePopper); + const [content, setContent] = React.useState(null); +- const composedRefs = (0, import_react_compose_refs.useComposedRefs)(forwardedRef, (node) => setContent(node)); ++ const setContentRef = useGuardedNodeSetter(setContent); ++ const composedRefs = (0, import_react_compose_refs.useComposedRefs)(forwardedRef, setContentRef); + const [arrow, setArrow] = React.useState(null); + const arrowSize = (0, import_react_use_size.useSize)(arrow); + const arrowWidth = arrowSize?.width ?? 0; +diff --git a/dist/index.mjs b/dist/index.mjs +index 9f84984eab84e32d20d6c052217da0e3b8374b40..0ffdb6313708ab3113c0ce40fe4519b6b605deff 100644 +--- a/dist/index.mjs ++++ b/dist/index.mjs +@@ -26,6 +26,29 @@ var ALIGN_OPTIONS = ["start", "center", "end"]; + var POPPER_NAME = "Popper"; + var [createPopperContext, createPopperScope] = createContextScope(POPPER_NAME); + var [PopperProvider, usePopperContext] = createPopperContext(POPPER_NAME); ++function useGuardedNodeSetter(setNode) { ++ const nodeRef = React.useRef(null); ++ const nodeCleanupGenerationRef = React.useRef(0); ++ return React.useCallback((node) => { ++ const syncNode = (nextNode) => { ++ if (nodeRef.current === nextNode) return; ++ nodeRef.current = nextNode; ++ setNode(nextNode); ++ }; ++ nodeCleanupGenerationRef.current += 1; ++ const cleanupGeneration = nodeCleanupGenerationRef.current; ++ if (node) { ++ syncNode(node); ++ return; ++ } ++ queueMicrotask(() => { ++ if (nodeCleanupGenerationRef.current !== cleanupGeneration) { ++ return; ++ } ++ syncNode(null); ++ }); ++ }, [setNode]); ++} + var Popper = (props) => { + const { __scopePopper, children } = props; + const [anchor, setAnchor] = React.useState(null); +@@ -73,7 +96,8 @@ var PopperContent = React.forwardRef( + } = props; + const context = usePopperContext(CONTENT_NAME, __scopePopper); + const [content, setContent] = React.useState(null); +- const composedRefs = useComposedRefs(forwardedRef, (node) => setContent(node)); ++ const setContentRef = useGuardedNodeSetter(setContent); ++ const composedRefs = useComposedRefs(forwardedRef, setContentRef); + const [arrow, setArrow] = React.useState(null); + const arrowSize = useSize(arrow); + const arrowWidth = arrowSize?.width ?? 0; diff --git a/patches/@radix-ui__react-select@2.2.6.patch b/patches/@radix-ui__react-select@2.2.6.patch new file mode 100644 index 00000000..7c84fd8b --- /dev/null +++ b/patches/@radix-ui__react-select@2.2.6.patch @@ -0,0 +1,280 @@ +diff --git a/dist/index.js b/dist/index.js +index dc37ac4a018a086c4244a09a67215dbaa9b4de65..fc80522666f91087ce1bce3a34844b17c74cdf6c 100644 +--- a/dist/index.js ++++ b/dist/index.js +@@ -104,6 +104,29 @@ var [createSelectContext, createSelectScope] = (0, import_react_context.createCo + var usePopperScope = (0, import_react_popper.createPopperScope)(); + var [SelectProvider, useSelectContext] = createSelectContext(SELECT_NAME); + var [SelectNativeOptionsProvider, useSelectNativeOptionsContext] = createSelectContext(SELECT_NAME); ++function useGuardedNodeSetter(setNode) { ++ const nodeRef = React.useRef(null); ++ const nodeCleanupGenerationRef = React.useRef(0); ++ return React.useCallback((node) => { ++ const syncNode = (nextNode) => { ++ if (nodeRef.current === nextNode) return; ++ nodeRef.current = nextNode; ++ setNode(nextNode); ++ }; ++ nodeCleanupGenerationRef.current += 1; ++ const cleanupGeneration = nodeCleanupGenerationRef.current; ++ if (node) { ++ syncNode(node); ++ return; ++ } ++ queueMicrotask(() => { ++ if (nodeCleanupGenerationRef.current !== cleanupGeneration) { ++ return; ++ } ++ syncNode(null); ++ }); ++ }, [setNode]); ++} + var Select = (props) => { + const { + __scopeSelect, +@@ -124,6 +147,8 @@ var Select = (props) => { + const popperScope = usePopperScope(__scopeSelect); + const [trigger, setTrigger] = React.useState(null); + const [valueNode, setValueNode] = React.useState(null); ++ const setTriggerRef = useGuardedNodeSetter(setTrigger); ++ const setValueNodeRef = useGuardedNodeSetter(setValueNode); + const [valueNodeHasChildren, setValueNodeHasChildren] = React.useState(false); + const direction = (0, import_react_direction.useDirection)(dir); + const [open, setOpen] = (0, import_react_use_controllable_state.useControllableState)({ +@@ -148,9 +173,9 @@ var Select = (props) => { + required, + scope: __scopeSelect, + trigger, +- onTriggerChange: setTrigger, ++ onTriggerChange: setTriggerRef, + valueNode, +- onValueNodeChange: setValueNode, ++ onValueNodeChange: setValueNodeRef, + valueNodeHasChildren, + onValueNodeHasChildrenChange: setValueNodeHasChildren, + contentId: (0, import_react_id.useId)(), +@@ -366,11 +391,15 @@ var SelectContentImpl = React.forwardRef( + const context = useSelectContext(CONTENT_NAME, __scopeSelect); + const [content, setContent] = React.useState(null); + const [viewport, setViewport] = React.useState(null); +- const composedRefs = (0, import_react_compose_refs.useComposedRefs)(forwardedRef, (node) => setContent(node)); ++ const setContentRef = useGuardedNodeSetter(setContent); ++ const setViewportRef = useGuardedNodeSetter(setViewport); ++ const composedRefs = (0, import_react_compose_refs.useComposedRefs)(forwardedRef, setContentRef); + const [selectedItem, setSelectedItem] = React.useState(null); + const [selectedItemText, setSelectedItemText] = React.useState( + null + ); ++ const setSelectedItemRef = useGuardedNodeSetter(setSelectedItem); ++ const setSelectedItemTextRef = useGuardedNodeSetter(setSelectedItemText); + const getItems = useCollection(__scopeSelect); + const [isPositioned, setIsPositioned] = React.useState(false); + const firstValidItemFoundRef = React.useRef(false); +@@ -456,11 +485,11 @@ var SelectContentImpl = React.forwardRef( + const isFirstValidItem = !firstValidItemFoundRef.current && !disabled; + const isSelectedItem = context.value !== void 0 && context.value === value; + if (isSelectedItem || isFirstValidItem) { +- setSelectedItem(node); ++ setSelectedItemRef(node); + if (isFirstValidItem) firstValidItemFoundRef.current = true; + } + }, +- [context.value] ++ [context.value, setSelectedItemRef] + ); + const handleItemLeave = React.useCallback(() => content?.focus(), [content]); + const itemTextRefCallback = React.useCallback( +@@ -468,10 +497,10 @@ var SelectContentImpl = React.forwardRef( + const isFirstValidItem = !firstValidItemFoundRef.current && !disabled; + const isSelectedItem = context.value !== void 0 && context.value === value; + if (isSelectedItem || isFirstValidItem) { +- setSelectedItemText(node); ++ setSelectedItemTextRef(node); + } + }, +- [context.value] ++ [context.value, setSelectedItemTextRef] + ); + const SelectPosition = position === "popper" ? SelectPopperPosition : SelectItemAlignedPosition; + const popperContentProps = SelectPosition === SelectPopperPosition ? { +@@ -492,7 +521,7 @@ var SelectContentImpl = React.forwardRef( + scope: __scopeSelect, + content, + viewport, +- onViewportChange: setViewport, ++ onViewportChange: setViewportRef, + itemRefCallback, + selectedItem, + onItemLeave: handleItemLeave, +@@ -580,7 +609,9 @@ var SelectItemAlignedPosition = React.forwardRef((props, forwardedRef) => { + const contentContext = useSelectContentContext(CONTENT_NAME, __scopeSelect); + const [contentWrapper, setContentWrapper] = React.useState(null); + const [content, setContent] = React.useState(null); +- const composedRefs = (0, import_react_compose_refs.useComposedRefs)(forwardedRef, (node) => setContent(node)); ++ const setContentWrapperRef = useGuardedNodeSetter(setContentWrapper); ++ const setContentRef = useGuardedNodeSetter(setContent); ++ const composedRefs = (0, import_react_compose_refs.useComposedRefs)(forwardedRef, setContentRef); + const getItems = useCollection(__scopeSelect); + const shouldExpandOnScrollRef = React.useRef(false); + const shouldRepositionRef = React.useRef(true); +@@ -709,7 +740,7 @@ var SelectItemAlignedPosition = React.forwardRef((props, forwardedRef) => { + children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)( + "div", + { +- ref: setContentWrapper, ++ ref: setContentWrapperRef, + style: { + display: "flex", + flexDirection: "column", +@@ -971,9 +1002,10 @@ var SelectItemText = React.forwardRef( + const itemContext = useSelectItemContext(ITEM_TEXT_NAME, __scopeSelect); + const nativeOptionsContext = useSelectNativeOptionsContext(ITEM_TEXT_NAME, __scopeSelect); + const [itemTextNode, setItemTextNode] = React.useState(null); ++ const setItemTextNodeRef = useGuardedNodeSetter(setItemTextNode); + const composedRefs = (0, import_react_compose_refs.useComposedRefs)( + forwardedRef, +- (node) => setItemTextNode(node), ++ setItemTextNodeRef, + itemContext.onItemTextChange, + (node) => contentContext.itemTextRefCallback?.(node, itemContext.value, itemContext.disabled) + ); +diff --git a/dist/index.mjs b/dist/index.mjs +index f9b94f39dfddef678ef3086354f8d7413ae27e52..4a53ec0d65aa051b95e3e8ea4aff4fdd52a17406 100644 +--- a/dist/index.mjs ++++ b/dist/index.mjs +@@ -37,6 +37,29 @@ var [createSelectContext, createSelectScope] = createContextScope(SELECT_NAME, [ + var usePopperScope = createPopperScope(); + var [SelectProvider, useSelectContext] = createSelectContext(SELECT_NAME); + var [SelectNativeOptionsProvider, useSelectNativeOptionsContext] = createSelectContext(SELECT_NAME); ++function useGuardedNodeSetter(setNode) { ++ const nodeRef = React.useRef(null); ++ const nodeCleanupGenerationRef = React.useRef(0); ++ return React.useCallback((node) => { ++ const syncNode = (nextNode) => { ++ if (nodeRef.current === nextNode) return; ++ nodeRef.current = nextNode; ++ setNode(nextNode); ++ }; ++ nodeCleanupGenerationRef.current += 1; ++ const cleanupGeneration = nodeCleanupGenerationRef.current; ++ if (node) { ++ syncNode(node); ++ return; ++ } ++ queueMicrotask(() => { ++ if (nodeCleanupGenerationRef.current !== cleanupGeneration) { ++ return; ++ } ++ syncNode(null); ++ }); ++ }, [setNode]); ++} + var Select = (props) => { + const { + __scopeSelect, +@@ -57,6 +80,8 @@ var Select = (props) => { + const popperScope = usePopperScope(__scopeSelect); + const [trigger, setTrigger] = React.useState(null); + const [valueNode, setValueNode] = React.useState(null); ++ const setTriggerRef = useGuardedNodeSetter(setTrigger); ++ const setValueNodeRef = useGuardedNodeSetter(setValueNode); + const [valueNodeHasChildren, setValueNodeHasChildren] = React.useState(false); + const direction = useDirection(dir); + const [open, setOpen] = useControllableState({ +@@ -81,9 +106,9 @@ var Select = (props) => { + required, + scope: __scopeSelect, + trigger, +- onTriggerChange: setTrigger, ++ onTriggerChange: setTriggerRef, + valueNode, +- onValueNodeChange: setValueNode, ++ onValueNodeChange: setValueNodeRef, + valueNodeHasChildren, + onValueNodeHasChildrenChange: setValueNodeHasChildren, + contentId: useId(), +@@ -299,11 +324,15 @@ var SelectContentImpl = React.forwardRef( + const context = useSelectContext(CONTENT_NAME, __scopeSelect); + const [content, setContent] = React.useState(null); + const [viewport, setViewport] = React.useState(null); +- const composedRefs = useComposedRefs(forwardedRef, (node) => setContent(node)); ++ const setContentRef = useGuardedNodeSetter(setContent); ++ const setViewportRef = useGuardedNodeSetter(setViewport); ++ const composedRefs = useComposedRefs(forwardedRef, setContentRef); + const [selectedItem, setSelectedItem] = React.useState(null); + const [selectedItemText, setSelectedItemText] = React.useState( + null + ); ++ const setSelectedItemRef = useGuardedNodeSetter(setSelectedItem); ++ const setSelectedItemTextRef = useGuardedNodeSetter(setSelectedItemText); + const getItems = useCollection(__scopeSelect); + const [isPositioned, setIsPositioned] = React.useState(false); + const firstValidItemFoundRef = React.useRef(false); +@@ -389,11 +418,11 @@ var SelectContentImpl = React.forwardRef( + const isFirstValidItem = !firstValidItemFoundRef.current && !disabled; + const isSelectedItem = context.value !== void 0 && context.value === value; + if (isSelectedItem || isFirstValidItem) { +- setSelectedItem(node); ++ setSelectedItemRef(node); + if (isFirstValidItem) firstValidItemFoundRef.current = true; + } + }, +- [context.value] ++ [context.value, setSelectedItemRef] + ); + const handleItemLeave = React.useCallback(() => content?.focus(), [content]); + const itemTextRefCallback = React.useCallback( +@@ -401,10 +430,10 @@ var SelectContentImpl = React.forwardRef( + const isFirstValidItem = !firstValidItemFoundRef.current && !disabled; + const isSelectedItem = context.value !== void 0 && context.value === value; + if (isSelectedItem || isFirstValidItem) { +- setSelectedItemText(node); ++ setSelectedItemTextRef(node); + } + }, +- [context.value] ++ [context.value, setSelectedItemTextRef] + ); + const SelectPosition = position === "popper" ? SelectPopperPosition : SelectItemAlignedPosition; + const popperContentProps = SelectPosition === SelectPopperPosition ? { +@@ -425,7 +454,7 @@ var SelectContentImpl = React.forwardRef( + scope: __scopeSelect, + content, + viewport, +- onViewportChange: setViewport, ++ onViewportChange: setViewportRef, + itemRefCallback, + selectedItem, + onItemLeave: handleItemLeave, +@@ -513,7 +542,9 @@ var SelectItemAlignedPosition = React.forwardRef((props, forwardedRef) => { + const contentContext = useSelectContentContext(CONTENT_NAME, __scopeSelect); + const [contentWrapper, setContentWrapper] = React.useState(null); + const [content, setContent] = React.useState(null); +- const composedRefs = useComposedRefs(forwardedRef, (node) => setContent(node)); ++ const setContentWrapperRef = useGuardedNodeSetter(setContentWrapper); ++ const setContentRef = useGuardedNodeSetter(setContent); ++ const composedRefs = useComposedRefs(forwardedRef, setContentRef); + const getItems = useCollection(__scopeSelect); + const shouldExpandOnScrollRef = React.useRef(false); + const shouldRepositionRef = React.useRef(true); +@@ -642,7 +673,7 @@ var SelectItemAlignedPosition = React.forwardRef((props, forwardedRef) => { + children: /* @__PURE__ */ jsx( + "div", + { +- ref: setContentWrapper, ++ ref: setContentWrapperRef, + style: { + display: "flex", + flexDirection: "column", +@@ -904,9 +935,10 @@ var SelectItemText = React.forwardRef( + const itemContext = useSelectItemContext(ITEM_TEXT_NAME, __scopeSelect); + const nativeOptionsContext = useSelectNativeOptionsContext(ITEM_TEXT_NAME, __scopeSelect); + const [itemTextNode, setItemTextNode] = React.useState(null); ++ const setItemTextNodeRef = useGuardedNodeSetter(setItemTextNode); + const composedRefs = useComposedRefs( + forwardedRef, +- (node) => setItemTextNode(node), ++ setItemTextNodeRef, + itemContext.onItemTextChange, + (node) => contentContext.itemTextRefCallback?.(node, itemContext.value, itemContext.disabled) + ); diff --git a/patches/@radix-ui__react-tooltip@1.2.8.patch b/patches/@radix-ui__react-tooltip@1.2.8.patch new file mode 100644 index 00000000..ec9742b9 --- /dev/null +++ b/patches/@radix-ui__react-tooltip@1.2.8.patch @@ -0,0 +1,102 @@ +diff --git a/dist/index.js b/dist/index.js +index 2d0d314a00082c458d2f551d715572cd1fa1b5c3..1954a52d7e25a2d733d57f2f32113af8a69a18bf 100644 +--- a/dist/index.js ++++ b/dist/index.js +@@ -71,6 +71,29 @@ var PROVIDER_NAME = "TooltipProvider"; + var DEFAULT_DELAY_DURATION = 700; + var TOOLTIP_OPEN = "tooltip.open"; + var [TooltipProviderContextProvider, useTooltipProviderContext] = createTooltipContext(PROVIDER_NAME); ++function useGuardedNodeSetter(setNode) { ++ const nodeRef = React.useRef(null); ++ const nodeCleanupGenerationRef = React.useRef(0); ++ return React.useCallback((node) => { ++ const syncNode = (nextNode) => { ++ if (nodeRef.current === nextNode) return; ++ nodeRef.current = nextNode; ++ setNode(nextNode); ++ }; ++ nodeCleanupGenerationRef.current += 1; ++ const cleanupGeneration = nodeCleanupGenerationRef.current; ++ if (node) { ++ syncNode(node); ++ return; ++ } ++ queueMicrotask(() => { ++ if (nodeCleanupGenerationRef.current !== cleanupGeneration) { ++ return; ++ } ++ syncNode(null); ++ }); ++ }, [setNode]); ++} + var TooltipProvider = (props) => { + const { + __scopeTooltip, +@@ -128,6 +151,7 @@ var Tooltip = (props) => { + const providerContext = useTooltipProviderContext(TOOLTIP_NAME, props.__scopeTooltip); + const popperScope = usePopperScope(__scopeTooltip); + const [trigger, setTrigger] = React.useState(null); ++ const setTriggerRef = useGuardedNodeSetter(setTrigger); + const contentId = (0, import_react_id.useId)(); + const openTimerRef = React.useRef(0); + const disableHoverableContent = disableHoverableContentProp ?? providerContext.disableHoverableContent; +@@ -185,7 +209,7 @@ var Tooltip = (props) => { + open, + stateAttribute, + trigger, +- onTriggerChange: setTrigger, ++ onTriggerChange: setTriggerRef, + onTriggerEnter: React.useCallback(() => { + if (providerContext.isOpenDelayedRef.current) handleDelayedOpen(); + else handleOpen(); +diff --git a/dist/index.mjs b/dist/index.mjs +index 568389bf3ce8123fa6de6d298878b32d613e25cf..a809f654d8b2d84be0f5747384a180a0864c7d44 100644 +--- a/dist/index.mjs ++++ b/dist/index.mjs +@@ -24,6 +24,29 @@ var PROVIDER_NAME = "TooltipProvider"; + var DEFAULT_DELAY_DURATION = 700; + var TOOLTIP_OPEN = "tooltip.open"; + var [TooltipProviderContextProvider, useTooltipProviderContext] = createTooltipContext(PROVIDER_NAME); ++function useGuardedNodeSetter(setNode) { ++ const nodeRef = React.useRef(null); ++ const nodeCleanupGenerationRef = React.useRef(0); ++ return React.useCallback((node) => { ++ const syncNode = (nextNode) => { ++ if (nodeRef.current === nextNode) return; ++ nodeRef.current = nextNode; ++ setNode(nextNode); ++ }; ++ nodeCleanupGenerationRef.current += 1; ++ const cleanupGeneration = nodeCleanupGenerationRef.current; ++ if (node) { ++ syncNode(node); ++ return; ++ } ++ queueMicrotask(() => { ++ if (nodeCleanupGenerationRef.current !== cleanupGeneration) { ++ return; ++ } ++ syncNode(null); ++ }); ++ }, [setNode]); ++} + var TooltipProvider = (props) => { + const { + __scopeTooltip, +@@ -81,6 +104,7 @@ var Tooltip = (props) => { + const providerContext = useTooltipProviderContext(TOOLTIP_NAME, props.__scopeTooltip); + const popperScope = usePopperScope(__scopeTooltip); + const [trigger, setTrigger] = React.useState(null); ++ const setTriggerRef = useGuardedNodeSetter(setTrigger); + const contentId = useId(); + const openTimerRef = React.useRef(0); + const disableHoverableContent = disableHoverableContentProp ?? providerContext.disableHoverableContent; +@@ -138,7 +162,7 @@ var Tooltip = (props) => { + open, + stateAttribute, + trigger, +- onTriggerChange: setTrigger, ++ onTriggerChange: setTriggerRef, + onTriggerEnter: React.useCallback(() => { + if (providerContext.isOpenDelayedRef.current) handleDelayedOpen(); + else handleOpen(); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index b95bd76c..f5afc456 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -48,15 +48,30 @@ overrides: yaml: 2.9.0 patchedDependencies: + '@radix-ui/react-checkbox@1.3.3': + hash: 80a226b85c8e8a94674990c0eedf6d3709af6649df83bbed6f3ae930783e6d7d + path: patches/@radix-ui__react-checkbox@1.3.3.patch '@radix-ui/react-dismissable-layer@1.1.11': hash: c9a989c59554b1942cebb761daaaeb964304d965d236cb0bd2142c4d8f8032c3 path: patches/@radix-ui__react-dismissable-layer@1.1.11.patch '@radix-ui/react-focus-scope@1.1.7': hash: cce5af533d09e336a548ebb15555869267a2545df720cfe228aa0a98da80e829 path: patches/@radix-ui__react-focus-scope@1.1.7.patch + '@radix-ui/react-menu@2.1.16': + hash: a59dc5aa0792599fc395b6db50cae81f2bcdb76e9d28205b12ce88906d3aeaa0 + path: patches/@radix-ui__react-menu@2.1.16.patch + '@radix-ui/react-popper@1.2.8': + hash: bab709aa37cbdb3023036cd85534ddb1400f2fccfe54bd6321fe405d5d199404 + path: patches/@radix-ui__react-popper@1.2.8.patch '@radix-ui/react-presence@1.1.5': hash: afe90f800cfb3b1ce1a9c457772e2441a9202e1aa3f8658eb3b9613b3ba0ef7e path: patches/@radix-ui__react-presence@1.1.5.patch + '@radix-ui/react-select@2.2.6': + hash: eea50c1407feb65af64720d0aadd2b534ca7743474d9204e0fc1d6b93e5f31f4 + path: patches/@radix-ui__react-select@2.2.6.patch + '@radix-ui/react-tooltip@1.2.8': + hash: 92cb648a695f616d3b7222b90053cb36e162bab4303abf0fe39b517e1d9dd6b8 + path: patches/@radix-ui__react-tooltip@1.2.8.patch importers: @@ -166,7 +181,7 @@ importers: version: 1.1.15(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) '@radix-ui/react-checkbox': specifier: ^1.3.3 - version: 1.3.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + version: 1.3.3(patch_hash=80a226b85c8e8a94674990c0eedf6d3709af6649df83bbed6f3ae930783e6d7d)(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) '@radix-ui/react-collapsible': specifier: ^1.1.12 version: 1.1.12(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) @@ -193,7 +208,7 @@ importers: version: 1.1.5(patch_hash=afe90f800cfb3b1ce1a9c457772e2441a9202e1aa3f8658eb3b9613b3ba0ef7e)(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) '@radix-ui/react-select': specifier: ^2.2.6 - version: 2.2.6(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + version: 2.2.6(patch_hash=eea50c1407feb65af64720d0aadd2b534ca7743474d9204e0fc1d6b93e5f31f4)(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) '@radix-ui/react-slot': specifier: ^1.2.4 version: 1.2.4(@types/react@19.2.14)(react@19.2.4) @@ -202,7 +217,7 @@ importers: version: 1.1.13(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) '@radix-ui/react-tooltip': specifier: ^1.2.8 - version: 1.2.8(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + version: 1.2.8(patch_hash=92cb648a695f616d3b7222b90053cb36e162bab4303abf0fe39b517e1d9dd6b8)(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) '@sentry/electron': specifier: ^7.10.0 version: 7.10.0 @@ -14468,7 +14483,7 @@ snapshots: '@types/react': 19.2.14 '@types/react-dom': 19.2.3(@types/react@19.2.14) - '@radix-ui/react-checkbox@1.3.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + '@radix-ui/react-checkbox@1.3.3(patch_hash=80a226b85c8e8a94674990c0eedf6d3709af6649df83bbed6f3ae930783e6d7d)(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': dependencies: '@radix-ui/primitive': 1.1.3 '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.14)(react@19.2.4) @@ -14522,7 +14537,7 @@ snapshots: dependencies: '@radix-ui/primitive': 1.1.3 '@radix-ui/react-context': 1.1.2(@types/react@19.2.14)(react@19.2.4) - '@radix-ui/react-menu': 2.1.16(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-menu': 2.1.16(patch_hash=a59dc5aa0792599fc395b6db50cae81f2bcdb76e9d28205b12ce88906d3aeaa0)(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.14)(react@19.2.4) '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.14)(react@19.2.4) @@ -14585,7 +14600,7 @@ snapshots: '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.14)(react@19.2.4) '@radix-ui/react-context': 1.1.2(@types/react@19.2.14)(react@19.2.4) '@radix-ui/react-id': 1.1.1(@types/react@19.2.14)(react@19.2.4) - '@radix-ui/react-menu': 2.1.16(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-menu': 2.1.16(patch_hash=a59dc5aa0792599fc395b6db50cae81f2bcdb76e9d28205b12ce88906d3aeaa0)(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.14)(react@19.2.4) react: 19.2.4 @@ -14617,7 +14632,7 @@ snapshots: '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.14)(react@19.2.4) '@radix-ui/react-context': 1.1.2(@types/react@19.2.14)(react@19.2.4) '@radix-ui/react-dismissable-layer': 1.1.11(patch_hash=c9a989c59554b1942cebb761daaaeb964304d965d236cb0bd2142c4d8f8032c3)(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@radix-ui/react-popper': 1.2.8(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-popper': 1.2.8(patch_hash=bab709aa37cbdb3023036cd85534ddb1400f2fccfe54bd6321fe405d5d199404)(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) '@radix-ui/react-portal': 1.1.9(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) '@radix-ui/react-presence': 1.1.5(patch_hash=afe90f800cfb3b1ce1a9c457772e2441a9202e1aa3f8658eb3b9613b3ba0ef7e)(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) @@ -14644,7 +14659,7 @@ snapshots: '@types/react': 19.2.14 '@types/react-dom': 19.2.3(@types/react@19.2.14) - '@radix-ui/react-menu@2.1.16(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + '@radix-ui/react-menu@2.1.16(patch_hash=a59dc5aa0792599fc395b6db50cae81f2bcdb76e9d28205b12ce88906d3aeaa0)(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': dependencies: '@radix-ui/primitive': 1.1.3 '@radix-ui/react-collection': 1.1.7(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) @@ -14655,7 +14670,7 @@ snapshots: '@radix-ui/react-focus-guards': 1.1.3(@types/react@19.2.14)(react@19.2.4) '@radix-ui/react-focus-scope': 1.1.7(patch_hash=cce5af533d09e336a548ebb15555869267a2545df720cfe228aa0a98da80e829)(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) '@radix-ui/react-id': 1.1.1(@types/react@19.2.14)(react@19.2.4) - '@radix-ui/react-popper': 1.2.8(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-popper': 1.2.8(patch_hash=bab709aa37cbdb3023036cd85534ddb1400f2fccfe54bd6321fe405d5d199404)(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) '@radix-ui/react-portal': 1.1.9(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) '@radix-ui/react-presence': 1.1.5(patch_hash=afe90f800cfb3b1ce1a9c457772e2441a9202e1aa3f8658eb3b9613b3ba0ef7e)(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) @@ -14679,7 +14694,7 @@ snapshots: '@radix-ui/react-focus-guards': 1.1.3(@types/react@19.2.14)(react@19.2.4) '@radix-ui/react-focus-scope': 1.1.7(patch_hash=cce5af533d09e336a548ebb15555869267a2545df720cfe228aa0a98da80e829)(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) '@radix-ui/react-id': 1.1.1(@types/react@19.2.14)(react@19.2.4) - '@radix-ui/react-popper': 1.2.8(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-popper': 1.2.8(patch_hash=bab709aa37cbdb3023036cd85534ddb1400f2fccfe54bd6321fe405d5d199404)(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) '@radix-ui/react-portal': 1.1.9(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) '@radix-ui/react-presence': 1.1.5(patch_hash=afe90f800cfb3b1ce1a9c457772e2441a9202e1aa3f8658eb3b9613b3ba0ef7e)(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) @@ -14693,7 +14708,7 @@ snapshots: '@types/react': 19.2.14 '@types/react-dom': 19.2.3(@types/react@19.2.14) - '@radix-ui/react-popper@1.2.8(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + '@radix-ui/react-popper@1.2.8(patch_hash=bab709aa37cbdb3023036cd85534ddb1400f2fccfe54bd6321fe405d5d199404)(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': dependencies: '@floating-ui/react-dom': 2.1.7(react-dom@19.2.4(react@19.2.4))(react@19.2.4) '@radix-ui/react-arrow': 1.1.7(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) @@ -14766,7 +14781,7 @@ snapshots: '@types/react': 19.2.14 '@types/react-dom': 19.2.3(@types/react@19.2.14) - '@radix-ui/react-select@2.2.6(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + '@radix-ui/react-select@2.2.6(patch_hash=eea50c1407feb65af64720d0aadd2b534ca7743474d9204e0fc1d6b93e5f31f4)(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': dependencies: '@radix-ui/number': 1.1.1 '@radix-ui/primitive': 1.1.3 @@ -14778,7 +14793,7 @@ snapshots: '@radix-ui/react-focus-guards': 1.1.3(@types/react@19.2.14)(react@19.2.4) '@radix-ui/react-focus-scope': 1.1.7(patch_hash=cce5af533d09e336a548ebb15555869267a2545df720cfe228aa0a98da80e829)(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) '@radix-ui/react-id': 1.1.1(@types/react@19.2.14)(react@19.2.4) - '@radix-ui/react-popper': 1.2.8(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-popper': 1.2.8(patch_hash=bab709aa37cbdb3023036cd85534ddb1400f2fccfe54bd6321fe405d5d199404)(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) '@radix-ui/react-portal': 1.1.9(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) '@radix-ui/react-slot': 1.2.3(@types/react@19.2.14)(react@19.2.4) @@ -14825,14 +14840,14 @@ snapshots: '@types/react': 19.2.14 '@types/react-dom': 19.2.3(@types/react@19.2.14) - '@radix-ui/react-tooltip@1.2.8(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + '@radix-ui/react-tooltip@1.2.8(patch_hash=92cb648a695f616d3b7222b90053cb36e162bab4303abf0fe39b517e1d9dd6b8)(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': dependencies: '@radix-ui/primitive': 1.1.3 '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.14)(react@19.2.4) '@radix-ui/react-context': 1.1.2(@types/react@19.2.14)(react@19.2.4) '@radix-ui/react-dismissable-layer': 1.1.11(patch_hash=c9a989c59554b1942cebb761daaaeb964304d965d236cb0bd2142c4d8f8032c3)(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) '@radix-ui/react-id': 1.1.1(@types/react@19.2.14)(react@19.2.4) - '@radix-ui/react-popper': 1.2.8(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-popper': 1.2.8(patch_hash=bab709aa37cbdb3023036cd85534ddb1400f2fccfe54bd6321fe405d5d199404)(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) '@radix-ui/react-portal': 1.1.9(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) '@radix-ui/react-presence': 1.1.5(patch_hash=afe90f800cfb3b1ce1a9c457772e2441a9202e1aa3f8658eb3b9613b3ba0ef7e)(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) diff --git a/scripts/ci/verify-radix-presence-patch.mjs b/scripts/ci/verify-radix-presence-patch.mjs index b66d4f29..e62b7515 100644 --- a/scripts/ci/verify-radix-presence-patch.mjs +++ b/scripts/ci/verify-radix-presence-patch.mjs @@ -9,16 +9,60 @@ const patchChecks = [ { packageName: '@radix-ui/react-presence', requiredMarkers: ['nodeCleanupGenerationRef', 'syncNode(null)'], + forbiddenSnippets: ['setNode(node2);'], }, { packageName: '@radix-ui/react-focus-scope', resolverFromPackage: '@radix-ui/react-dialog', requiredMarkers: ['containerCleanupGenerationRef', 'syncContainer(null)'], + forbiddenSnippets: ['(node) => setContainer(node)'], }, { packageName: '@radix-ui/react-dismissable-layer', resolverFromPackage: '@radix-ui/react-dialog', requiredMarkers: ['nodeCleanupGenerationRef', 'syncNode(null)'], + forbiddenSnippets: ['(node2) => setNode(node2)'], + }, + { + packageName: '@radix-ui/react-select', + requiredMarkers: ['useGuardedNodeSetter', 'setContentRef', 'setItemTextNodeRef'], + forbiddenSnippets: [ + '(node) => setContent(node)', + '(node) => setItemTextNode(node)', + 'onTriggerChange: setTrigger,', + 'onValueNodeChange: setValueNode,', + 'onViewportChange: setViewport,', + 'ref: setContentWrapper,', + 'setSelectedItem(node);', + 'setSelectedItemText(node);', + ], + }, + { + packageName: '@radix-ui/react-popper', + resolverFromPackage: '@radix-ui/react-select', + requiredMarkers: ['useGuardedNodeSetter', 'setContentRef'], + forbiddenSnippets: ['(node) => setContent(node)'], + }, + { + packageName: '@radix-ui/react-tooltip', + requiredMarkers: ['useGuardedNodeSetter', 'setTriggerRef'], + forbiddenSnippets: ['onTriggerChange: setTrigger,'], + }, + { + packageName: '@radix-ui/react-menu', + resolverFromPackage: '@radix-ui/react-dropdown-menu', + requiredMarkers: ['useGuardedNodeSetter', 'setContentRef', 'setTriggerRef'], + forbiddenSnippets: ['onContentChange: setContent,', 'onTriggerChange: setTrigger,'], + }, + { + packageName: '@radix-ui/react-checkbox', + requiredMarkers: ['useGuardedNodeSetter', 'setControlRef', 'setBubbleInputRef'], + forbiddenSnippets: [ + 'useComposedRefs(forwardedRef, setControl)', + 'useComposedRefs(forwardedRef, setBubbleInput)', + 'useComposedRefs)(forwardedRef, setControl)', + 'useComposedRefs)(forwardedRef, setBubbleInput)', + ], }, ]; @@ -42,6 +86,14 @@ for (const check of patchChecks) { if (missingMarkers.length > 0) { missing.push(`${check.packageName}/${relativePath}: ${missingMarkers.join(', ')}`); } + + const forbiddenSnippets = check.forbiddenSnippets ?? []; + const presentForbiddenSnippets = forbiddenSnippets.filter((snippet) => source.includes(snippet)); + if (presentForbiddenSnippets.length > 0) { + missing.push( + `${check.packageName}/${relativePath}: forbidden snippets still present: ${presentForbiddenSnippets.join(', ')}` + ); + } } } diff --git a/scripts/ci/verify-radix-renderer-bundle.mjs b/scripts/ci/verify-radix-renderer-bundle.mjs new file mode 100644 index 00000000..bae2d7f5 --- /dev/null +++ b/scripts/ci/verify-radix-renderer-bundle.mjs @@ -0,0 +1,81 @@ +import { readdirSync, readFileSync } from 'node:fs'; +import { join } from 'node:path'; + +const assetsDir = join(process.cwd(), 'out', 'renderer', 'assets'); +const rendererBundles = readdirSync(assetsDir) + .filter((entry) => entry.endsWith('.js')) + .sort(); + +if (rendererBundles.length === 0) { + console.error('No renderer JavaScript bundles found under out/renderer/assets.'); + process.exit(1); +} + +const requiredMarkers = [ + 'nodeCleanupGenerationRef', + 'syncNode(null)', + 'useGuardedNodeSetter', + 'setTriggerRef', + 'setValueNodeRef', + 'setContentRef', + 'setViewportRef', + 'setSelectedItemRef', + 'setSelectedItemTextRef', + 'setItemTextNodeRef', + 'setControlRef', + 'setBubbleInputRef', +]; + +const forbiddenSnippets = [ + '(node) => setContent(node)', + '(node2) => setNode(node2)', + '(node) => setItemTextNode(node)', + 'onContentChange: setContent,', + 'onTriggerChange: setTrigger,', + 'onValueNodeChange: setValueNode,', + 'onViewportChange: setViewport,', + 'ref: setContentWrapper,', + 'setSelectedItem(node);', + 'setSelectedItemText(node);', + 'useComposedRefs(forwardedRef, setControl)', + 'useComposedRefs(forwardedRef, setBubbleInput)', + 'useComposedRefs)(forwardedRef, setControl)', + 'useComposedRefs)(forwardedRef, setBubbleInput)', +]; + +const failures = []; +const bundleSources = new Map(); +let combinedSource = ''; + +for (const bundleName of rendererBundles) { + const bundlePath = join(assetsDir, bundleName); + const source = readFileSync(bundlePath, 'utf8'); + bundleSources.set(bundleName, source); + combinedSource += source; +} + +const missingMarkers = requiredMarkers.filter((marker) => !combinedSource.includes(marker)); +if (missingMarkers.length > 0) { + failures.push(`renderer bundles: missing markers: ${missingMarkers.join(', ')}`); +} + +for (const [bundleName, source] of bundleSources) { + const presentForbiddenSnippets = forbiddenSnippets.filter((snippet) => source.includes(snippet)); + + if (presentForbiddenSnippets.length > 0) { + failures.push( + `${bundleName}: forbidden snippets still present: ${presentForbiddenSnippets.join(', ')}` + ); + } +} + +if (failures.length > 0) { + console.error( + [ + 'Renderer bundle was built without the complete Radix React 19 ref-cleanup guards.', + '', + ...failures, + ].join('\n') + ); + process.exit(1); +}