@tangle-network/ui 11.51.9 → 11.51.11

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,3 +1,11 @@
1
+ ## 11.51.11
2
+
3
+ - Keep tool-row actions and navigation reachable after a hover preview. HoverCard uses its anchor for placement, preserves keyboard inspection, and dismisses previews when a nested control commits an action. Touch actions do not become hover intent.
4
+
5
+ ## 11.51.10
6
+
7
+ - Render Runtime’s recorded `down` outcome as Failed; cancellation and no-winner remain Stopped.
8
+
1
9
  ## 11.51.9
2
10
 
3
11
  - Share HoverCard intent and Popover positioning/focus, with existing DOM anchors and local theme containers for virtualized canvases.
@@ -1,5 +1,5 @@
1
1
  import { cn } from "../lib/utils.js";
2
- import { Popover, PopoverAnchor, PopoverContent, PopoverTrigger } from "./popover.js";
2
+ import { Popover, PopoverAnchor, PopoverContent } from "./popover.js";
3
3
  import { Slot } from "@radix-ui/react-slot";
4
4
  import * as React from "react";
5
5
  import { jsx, jsxs } from "react/jsx-runtime";
@@ -7,7 +7,8 @@ import { jsx, jsxs } from "react/jsx-runtime";
7
7
  const HoverIntent = React.createContext({
8
8
  enter: (_target) => {},
9
9
  leave: () => {},
10
- restore: () => {}
10
+ restore: () => {},
11
+ press: () => {}
11
12
  });
12
13
  /** One hover policy and Popover's keyboard/focus behavior; delays follow the trigger's fast token. */
13
14
  function HoverCard({ anchor, open: controlled, defaultOpen = false, onOpenChange, openDelay, closeDelay, children, ...props }) {
@@ -31,12 +32,12 @@ function HoverCard({ anchor, open: controlled, defaultOpen = false, onOpenChange
31
32
  element
32
33
  ]);
33
34
  const delay = (target) => {
34
- if (typeof document === "undefined") return 0;
35
35
  const value = getComputedStyle(target ?? document.documentElement).getPropertyValue("--duration-fast").trim();
36
36
  return (parseFloat(value) || 0) * (value.endsWith("ms") ? 1 : 1e3);
37
37
  };
38
38
  const enter = React.useCallback((target) => {
39
- if (intent.current.restoring) return;
39
+ if (intent.current.restoring || intent.current.pressed && target.closest("[data-hover-card]")) return;
40
+ intent.current.pressed = false;
40
41
  if (!target.closest("[data-hover-card]")) intent.current.target = target;
41
42
  clearTimeout(intent.current.timer);
42
43
  intent.current.timer = setTimeout(() => change(true), openDelay ?? delay(target));
@@ -82,7 +83,11 @@ function HoverCard({ anchor, open: controlled, defaultOpen = false, onOpenChange
82
83
  value: {
83
84
  enter,
84
85
  leave,
85
- restore
86
+ restore,
87
+ press: () => {
88
+ intent.current.pressed = true;
89
+ change(false);
90
+ }
86
91
  },
87
92
  children: /* @__PURE__ */ jsxs(Popover, {
88
93
  ...props,
@@ -92,9 +97,9 @@ function HoverCard({ anchor, open: controlled, defaultOpen = false, onOpenChange
92
97
  })
93
98
  });
94
99
  }
95
- const HoverCardTrigger = React.forwardRef(({ className, onPointerEnter, onPointerLeave, onFocus, onBlur, asChild, ...props }, ref) => {
100
+ const HoverCardTrigger = React.forwardRef(({ className, onPointerEnter, onPointerLeave, onFocus, onBlur, onClickCapture, asChild, ...props }, ref) => {
96
101
  const intent = React.useContext(HoverIntent);
97
- return /* @__PURE__ */ jsx(PopoverTrigger, {
102
+ return /* @__PURE__ */ jsx(PopoverAnchor, {
98
103
  asChild: true,
99
104
  children: /* @__PURE__ */ jsx(asChild ? Slot : "a", {
100
105
  ref,
@@ -102,7 +107,7 @@ const HoverCardTrigger = React.forwardRef(({ className, onPointerEnter, onPointe
102
107
  className: cn("cursor-pointer underline-offset-4 hover:underline", className),
103
108
  onPointerEnter: (event) => {
104
109
  onPointerEnter?.(event);
105
- if (!event.defaultPrevented) intent.enter(event.currentTarget);
110
+ if (!event.defaultPrevented && event.pointerType !== "touch") intent.enter(event.currentTarget);
106
111
  },
107
112
  onPointerLeave: (event) => {
108
113
  onPointerLeave?.(event);
@@ -110,7 +115,12 @@ const HoverCardTrigger = React.forwardRef(({ className, onPointerEnter, onPointe
110
115
  },
111
116
  onFocus: (event) => {
112
117
  onFocus?.(event);
113
- if (!event.defaultPrevented) intent.enter(event.currentTarget);
118
+ if (!event.defaultPrevented && event.target.matches(":focus-visible")) intent.enter(event.currentTarget);
119
+ },
120
+ onClickCapture: (event) => {
121
+ onClickCapture?.(event);
122
+ const action = event.target instanceof Element ? event.target.closest("a,button,input,select,textarea,[role=\"button\"]") : null;
123
+ if (!event.defaultPrevented && action && action !== event.currentTarget) intent.press();
114
124
  },
115
125
  onBlur: (event) => {
116
126
  onBlur?.(event);
@@ -66,7 +66,7 @@ const ALIASES = {
66
66
  error: "failed",
67
67
  errored: "failed",
68
68
  "driver-failed": "failed",
69
- down: "stopped",
69
+ down: "failed",
70
70
  "no-winner": "stopped",
71
71
  cancelled: "stopped",
72
72
  canceled: "stopped",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tangle-network/ui",
3
- "version": "11.51.9",
3
+ "version": "11.51.11",
4
4
  "description": "Generic React UI components for Tangle products — primitives, chat, run, files, editor, markdown.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -1,9 +1,9 @@
1
1
  import { cn } from "../lib/utils";
2
2
  import * as React from "react";
3
3
  import { Slot } from "@radix-ui/react-slot";
4
- import { Popover, PopoverAnchor, PopoverContent, PopoverTrigger } from "./popover";
4
+ import { Popover, PopoverAnchor, PopoverContent } from "./popover";
5
5
 
6
- const HoverIntent = React.createContext({ enter: (_target: Element) => {}, leave: () => {}, restore: () => {} });
6
+ const HoverIntent = React.createContext({ enter: (_target: Element) => {}, leave: () => {}, restore: () => {}, press: () => {} });
7
7
  type HoverCardProps = React.ComponentProps<typeof Popover> & {
8
8
  /** Existing DOM trigger for delegated canvas or virtual-list previews. */
9
9
  anchor?: HTMLElement | { getBoundingClientRect: () => DOMRect; contextElement: HTMLElement } | null;
@@ -15,15 +15,14 @@ type HoverCardProps = React.ComponentProps<typeof Popover> & {
15
15
  export function HoverCard({ anchor, open: controlled, defaultOpen = false, onOpenChange, openDelay, closeDelay, children, ...props }: HoverCardProps) {
16
16
  const element = anchor && ("contextElement" in anchor ? anchor.contextElement : anchor);
17
17
  const [own, setOwn] = React.useState({ anchor: element, open: defaultOpen });
18
- const intent = React.useRef<{ timer?: ReturnType<typeof setTimeout>; target?: Element; restoring?: boolean }>({});
18
+ const intent = React.useRef<{ timer?: ReturnType<typeof setTimeout>; target?: Element; restoring?: boolean; pressed?: boolean }>({});
19
19
  if (element) intent.current.target = element;
20
20
  const change = React.useCallback((next: boolean) => { clearTimeout(intent.current.timer); if (controlled === undefined) setOwn({ anchor: element, open: next }); onOpenChange?.(next); }, [controlled, onOpenChange, element]);
21
21
  const delay = (target: Element | null | undefined) => {
22
- if (typeof document === "undefined") return 0;
23
22
  const value = getComputedStyle(target ?? document.documentElement).getPropertyValue("--duration-fast").trim();
24
23
  return (parseFloat(value) || 0) * (value.endsWith("ms") ? 1 : 1000);
25
24
  };
26
- const enter = React.useCallback((target: Element) => { if (intent.current.restoring) return; if (!target.closest("[data-hover-card]")) intent.current.target = target; clearTimeout(intent.current.timer); intent.current.timer = setTimeout(() => change(true), openDelay ?? delay(target)); }, [change, openDelay]);
25
+ const enter = React.useCallback((target: Element) => { if (intent.current.restoring || (intent.current.pressed && target.closest("[data-hover-card]"))) return; intent.current.pressed = false; if (!target.closest("[data-hover-card]")) intent.current.target = target; clearTimeout(intent.current.timer); intent.current.timer = setTimeout(() => change(true), openDelay ?? delay(target)); }, [change, openDelay]);
27
26
  const leave = React.useCallback(() => { clearTimeout(intent.current.timer); intent.current.timer = setTimeout(() => change(false), closeDelay ?? delay(element ?? intent.current.target)); }, [change, closeDelay, element]);
28
27
  React.useEffect(() => {
29
28
  if (!element) return () => clearTimeout(intent.current.timer);
@@ -36,20 +35,21 @@ export function HoverCard({ anchor, open: controlled, defaultOpen = false, onOpe
36
35
  if (document.activeElement === document.body && intent.current.target instanceof HTMLElement) intent.current.target.focus({ preventScroll: true });
37
36
  queueMicrotask(() => { intent.current.restoring = false; });
38
37
  };
39
- return <HoverIntent.Provider value={{ enter, leave, restore }}><Popover {...props} open={anchor === null ? false : controlled ?? (own.anchor === element && own.open)} onOpenChange={change}>
38
+ return <HoverIntent.Provider value={{ enter, leave, restore, press: () => { intent.current.pressed = true; change(false); } }}><Popover {...props} open={anchor === null ? false : controlled ?? (own.anchor === element && own.open)} onOpenChange={change}>
40
39
  {anchor && <PopoverAnchor virtualRef={{ current: anchor }} />}{children}
41
40
  </Popover></HoverIntent.Provider>;
42
41
  }
43
42
 
44
43
  export const HoverCardTrigger = React.forwardRef<HTMLAnchorElement, React.AnchorHTMLAttributes<HTMLAnchorElement> & { asChild?: boolean }>(
45
- ({ className, onPointerEnter, onPointerLeave, onFocus, onBlur, asChild, ...props }, ref) => {
44
+ ({ className, onPointerEnter, onPointerLeave, onFocus, onBlur, onClickCapture, asChild, ...props }, ref) => {
46
45
  const intent = React.useContext(HoverIntent);
47
46
  const Trigger = asChild ? Slot : "a";
48
- return <PopoverTrigger asChild><Trigger ref={ref} tabIndex={0} className={cn("cursor-pointer underline-offset-4 hover:underline", className)}
49
- onPointerEnter={event => { onPointerEnter?.(event); if (!event.defaultPrevented) intent.enter(event.currentTarget); }}
47
+ return <PopoverAnchor asChild><Trigger ref={ref} tabIndex={0} className={cn("cursor-pointer underline-offset-4 hover:underline", className)}
48
+ onPointerEnter={event => { onPointerEnter?.(event); if (!event.defaultPrevented && event.pointerType !== "touch") intent.enter(event.currentTarget); }}
50
49
  onPointerLeave={event => { onPointerLeave?.(event); if (!event.defaultPrevented) intent.leave(); }}
51
- onFocus={event => { onFocus?.(event); if (!event.defaultPrevented) intent.enter(event.currentTarget); }}
52
- onBlur={event => { onBlur?.(event); if (!event.defaultPrevented && !(event.relatedTarget instanceof Element && event.relatedTarget.closest("[data-hover-card]"))) intent.leave(); }} {...props} /></PopoverTrigger>;
50
+ onFocus={event => { onFocus?.(event); if (!event.defaultPrevented && event.target.matches(":focus-visible")) intent.enter(event.currentTarget); }}
51
+ onClickCapture={event => { onClickCapture?.(event); const action = event.target instanceof Element ? event.target.closest('a,button,input,select,textarea,[role="button"]') : null; if (!event.defaultPrevented && action && action !== event.currentTarget) intent.press(); }}
52
+ onBlur={event => { onBlur?.(event); if (!event.defaultPrevented && !(event.relatedTarget instanceof Element && event.relatedTarget.closest("[data-hover-card]"))) intent.leave(); }} {...props} /></PopoverAnchor>;
53
53
  },
54
54
  );
55
55
  HoverCardTrigger.displayName = "HoverCardTrigger";
@@ -46,8 +46,8 @@ const ALIASES: Record<string, RunStatus> = {
46
46
  pending: "waiting", queued: "waiting", blocked: "waiting",
47
47
  "attention-needed": "needs-you", needs_you: "needs-you", needs_input: "needs-you", awaiting_input: "needs-you",
48
48
  winner: "done", completed: "done", complete: "done", success: "done", succeeded: "done", shipped: "done",
49
- error: "failed", errored: "failed", "driver-failed": "failed",
50
- down: "stopped", "no-winner": "stopped", cancelled: "stopped", canceled: "stopped", aborted: "stopped",
49
+ error: "failed", errored: "failed", "driver-failed": "failed", down: "failed",
50
+ "no-winner": "stopped", cancelled: "stopped", canceled: "stopped", aborted: "stopped",
51
51
  };
52
52
 
53
53
  /** Any status string as a `RunStatus`; a value nothing names reads as Unknown, never as a guess. */