@tangle-network/ui 11.38.0 → 11.38.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,17 @@
1
1
  # @tangle-network/ui
2
2
 
3
+ ## 11.38.2
4
+
5
+ ### Patch Changes
6
+
7
+ - b33914e: Keep accepted Tooltip reopens open while exit content is retained. Preserve controlled/defaultOpen APIs, native disabled-trigger descriptions and sibling-tooltip dismissal through the shared Root owner.
8
+
9
+ ## 11.38.1
10
+
11
+ ### Patch Changes
12
+
13
+ - c049dc4: Bound shared surface dismissal by its scoped role deadline so missing CSS animation events cannot strand Radix cleanup or focus return. Preserve retained motion values on reopen.
14
+
3
15
  ## 11.38.0
4
16
 
5
17
  ### Minor Changes
@@ -4,9 +4,10 @@ import * as React from "react";
4
4
  import { jsx } from "react/jsx-runtime";
5
5
  import { animate, motionValue, styleEffect } from "motion";
6
6
  //#region src/lib/surface-motion.tsx
7
- /** Radix owns state, focus and presence; Motion only retargets the visible object. */
7
+ /** Radix owns state and focus; visual retirement cannot wait for a CSS event. */
8
8
  const SurfaceMotion = React.forwardRef(({ kind, side = "right", asChild, "data-state": state, ...props }, forwardedRef) => {
9
9
  const element = React.useRef(null);
10
+ const [retired, setRetired] = React.useState(false);
10
11
  const values = React.useRef(null);
11
12
  const controls = React.useRef([]);
12
13
  const ref = React.useCallback((node) => {
@@ -24,7 +25,10 @@ const SurfaceMotion = React.forwardRef(({ kind, side = "right", asChild, "data-s
24
25
  if (!node) return;
25
26
  const preference = typeof matchMedia === "function" ? matchMedia("(prefers-reduced-motion: reduce)") : void 0;
26
27
  let cancelRender;
28
+ let retirement;
27
29
  const run = () => {
30
+ clearTimeout(retirement);
31
+ setRetired(false);
28
32
  const closed = state === "closed";
29
33
  const bounds = node.getBoundingClientRect();
30
34
  const hidden = {
@@ -46,6 +50,10 @@ const SurfaceMotion = React.forwardRef(({ kind, side = "right", asChild, "data-s
46
50
  node.style.opacity = String(value.opacity);
47
51
  node.style.transform = `translateX(${value.x}px) translateY(${value.y}px) scale(${value.scale})`;
48
52
  };
53
+ if (closed) {
54
+ if (preference?.matches || seconds <= 0 || style.animationName === "none") setRetired(true);
55
+ else retirement = setTimeout(() => setRetired(true), seconds * 1e3);
56
+ }
49
57
  if (preference?.matches || seconds <= 0 || style.animationName === "none") {
50
58
  controls.current.forEach((control) => control.stop());
51
59
  if (values.current) for (const key of [
@@ -84,6 +92,7 @@ const SurfaceMotion = React.forwardRef(({ kind, side = "right", asChild, "data-s
84
92
  run();
85
93
  preference?.addEventListener("change", run);
86
94
  return () => {
95
+ clearTimeout(retirement);
87
96
  preference?.removeEventListener("change", run);
88
97
  cancelRender?.();
89
98
  };
@@ -94,7 +103,7 @@ const SurfaceMotion = React.forwardRef(({ kind, side = "right", asChild, "data-s
94
103
  props.style?.animation
95
104
  ]);
96
105
  React.useEffect(() => () => controls.current.forEach((control) => control.stop()), []);
97
- return /* @__PURE__ */ jsx(asChild ? Slot : "div", {
106
+ return state === "closed" && retired ? null : /* @__PURE__ */ jsx(asChild ? Slot : "div", {
98
107
  ...props,
99
108
  "data-state": state,
100
109
  ref
@@ -5,7 +5,10 @@ declare function TooltipProvider({ delayDuration, skipDelayDuration, ...props }:
5
5
  declare namespace TooltipProvider {
6
6
  var displayName: string;
7
7
  }
8
- declare const Tooltip: React.FC<TooltipPrimitive.TooltipProps>;
8
+ declare function Tooltip({ open, defaultOpen, onOpenChange, ...props }: React.ComponentProps<typeof TooltipPrimitive.Root>): React.JSX.Element;
9
+ declare namespace Tooltip {
10
+ var displayName: string;
11
+ }
9
12
  declare const TooltipTrigger: React.ForwardRefExoticComponent<TooltipPrimitive.TooltipTriggerProps & React.RefAttributes<HTMLButtonElement>>;
10
13
  /**
11
14
  * Inverse-surface label for a control whose name is otherwise only an icon.
@@ -17,7 +17,18 @@ const TooltipProvider = ({ delayDuration = 400, skipDelayDuration = 300, ...prop
17
17
  ...props
18
18
  });
19
19
  TooltipProvider.displayName = "TooltipProvider";
20
- const Tooltip = TooltipPrimitive.Root;
20
+ const Tooltip = ({ open, defaultOpen = false, onOpenChange, ...props }) => {
21
+ const [localOpen, setLocalOpen] = React.useState(defaultOpen);
22
+ return /* @__PURE__ */ jsx(TooltipPrimitive.Root, {
23
+ ...props,
24
+ open: open ?? localOpen,
25
+ onOpenChange: (next) => {
26
+ if (open === void 0) setLocalOpen(next);
27
+ onOpenChange?.(next);
28
+ }
29
+ });
30
+ };
31
+ Tooltip.displayName = "Tooltip";
21
32
  const TooltipTrigger = TooltipPrimitive.Trigger;
22
33
  /**
23
34
  * Inverse-surface label for a control whose name is otherwise only an icon.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tangle-network/ui",
3
- "version": "11.38.0",
3
+ "version": "11.38.2",
4
4
  "description": "Generic React UI components for Tangle products — primitives, chat, run, files, editor, markdown.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -12,10 +12,11 @@ type SurfaceProps = React.HTMLAttributes<HTMLDivElement> & {
12
12
  "data-state"?: string;
13
13
  };
14
14
 
15
- /** Radix owns state, focus and presence; Motion only retargets the visible object. */
15
+ /** Radix owns state and focus; visual retirement cannot wait for a CSS event. */
16
16
  export const SurfaceMotion = React.forwardRef<HTMLDivElement, SurfaceProps>(
17
17
  ({ kind, side = "right", asChild, "data-state": state, ...props }, forwardedRef) => {
18
18
  const element = React.useRef<HTMLDivElement | null>(null);
19
+ const [retired, setRetired] = React.useState(false);
19
20
  const values = React.useRef<Record<"opacity" | "scale" | "x" | "y", MotionValue<number>> | null>(null);
20
21
  const controls = React.useRef<Array<ReturnType<typeof animate>>>([]);
21
22
  const ref = React.useCallback<React.RefCallback<HTMLDivElement>>((node) => {
@@ -31,7 +32,10 @@ export const SurfaceMotion = React.forwardRef<HTMLDivElement, SurfaceProps>(
31
32
  if (!node) return;
32
33
  const preference = typeof matchMedia === "function" ? matchMedia("(prefers-reduced-motion: reduce)") : undefined;
33
34
  let cancelRender: (() => void) | undefined;
35
+ let retirement: ReturnType<typeof setTimeout> | undefined;
34
36
  const run = () => {
37
+ clearTimeout(retirement);
38
+ setRetired(false);
35
39
  const closed = state === "closed";
36
40
  const bounds = node.getBoundingClientRect();
37
41
  const hidden = {
@@ -49,6 +53,14 @@ export const SurfaceMotion = React.forwardRef<HTMLDivElement, SurfaceProps>(
49
53
  node.style.opacity = String(value.opacity);
50
54
  node.style.transform = `translateX(${value.x}px) translateY(${value.y}px) scale(${value.scale})`;
51
55
  };
56
+ // Radix may retain an exit until animationend. Retire this DOM node at
57
+ // the scoped deadline even when keyframes/events are missing or paused;
58
+ // its null ref lets Radix release presence, focus and modal resources.
59
+ // Reopen cancels retirement and keeps the same node and Motion values.
60
+ if (closed) {
61
+ if (preference?.matches || seconds <= 0 || style.animationName === "none") setRetired(true);
62
+ else retirement = setTimeout(() => setRetired(true), seconds * 1000);
63
+ }
52
64
  // Missing styles and reduced motion settle synchronously. No semantic
53
65
  // transition waits for a JavaScript completion callback.
54
66
  if (preference?.matches || seconds <= 0 || style.animationName === "none") {
@@ -73,13 +85,13 @@ export const SurfaceMotion = React.forwardRef<HTMLDivElement, SurfaceProps>(
73
85
  };
74
86
  run();
75
87
  preference?.addEventListener("change", run);
76
- return () => { preference?.removeEventListener("change", run); cancelRender?.(); };
88
+ return () => { clearTimeout(retirement); preference?.removeEventListener("change", run); cancelRender?.(); };
77
89
  // Radix Popper temporarily suppresses animation until placement resolves.
78
90
  // Start the same retained values when that inline override clears.
79
91
  }, [state, kind, side, props.style?.animation]);
80
92
  React.useEffect(() => () => controls.current.forEach(control => control.stop()), []);
81
93
  const Component = asChild ? Slot : "div";
82
- return <Component {...props} data-state={state} ref={ref} />;
94
+ return state === "closed" && retired ? null : <Component {...props} data-state={state} ref={ref} />;
83
95
  },
84
96
  );
85
97
  SurfaceMotion.displayName = "SurfaceMotion";
@@ -24,7 +24,21 @@ const TooltipProvider = ({
24
24
  );
25
25
  TooltipProvider.displayName = "TooltipProvider";
26
26
 
27
- const Tooltip = TooltipPrimitive.Root;
27
+ // Keep Radix on its controlled path even for the public uncontrolled API.
28
+ // Its global tooltip.open event then runs before the new open state commits:
29
+ // a retained content listener cannot turn its own accepted reopen into Close.
30
+ const Tooltip = ({ open, defaultOpen = false, onOpenChange, ...props }: React.ComponentProps<typeof TooltipPrimitive.Root>) => {
31
+ const [localOpen, setLocalOpen] = React.useState(defaultOpen);
32
+ return <TooltipPrimitive.Root
33
+ {...props}
34
+ open={open ?? localOpen}
35
+ onOpenChange={(next) => {
36
+ if (open === undefined) setLocalOpen(next);
37
+ onOpenChange?.(next);
38
+ }}
39
+ />;
40
+ };
41
+ Tooltip.displayName = "Tooltip";
28
42
 
29
43
  const TooltipTrigger = TooltipPrimitive.Trigger;
30
44