@adea-ai/ui 0.82.2 → 0.83.1

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.
Files changed (41) hide show
  1. package/dist/components/composites/action-button/action-button.d.ts.map +1 -1
  2. package/dist/components/composites/action-button/action-button.js +55 -24
  3. package/dist/components/composites/action-button/action-button.js.map +1 -1
  4. package/dist/components/layout/orbit-layout/index.d.ts +3 -0
  5. package/dist/components/layout/orbit-layout/index.d.ts.map +1 -0
  6. package/dist/components/layout/orbit-layout/index.js +2 -0
  7. package/dist/components/layout/orbit-layout/orbit-layout.d.ts +38 -0
  8. package/dist/components/layout/orbit-layout/orbit-layout.d.ts.map +1 -0
  9. package/dist/components/layout/orbit-layout/orbit-layout.js +83 -0
  10. package/dist/components/layout/orbit-layout/orbit-layout.js.map +1 -0
  11. package/dist/components/layout/virtual-window/index.d.ts +3 -0
  12. package/dist/components/layout/virtual-window/index.d.ts.map +1 -0
  13. package/dist/components/layout/virtual-window/index.js +2 -0
  14. package/dist/components/layout/virtual-window/virtual-window.d.ts +22 -0
  15. package/dist/components/layout/virtual-window/virtual-window.d.ts.map +1 -0
  16. package/dist/components/layout/virtual-window/virtual-window.js +45 -0
  17. package/dist/components/layout/virtual-window/virtual-window.js.map +1 -0
  18. package/dist/components/ui/command/command.d.ts.map +1 -1
  19. package/dist/components/ui/command/command.js +1 -0
  20. package/dist/components/ui/command/command.js.map +1 -1
  21. package/dist/index.d.ts +2 -0
  22. package/dist/index.d.ts.map +1 -1
  23. package/dist/index.js +3 -1
  24. package/dist/r/orbit-layout.json +22 -0
  25. package/dist/r/registry.json +44 -0
  26. package/dist/r/src/components/composites/action-button/action-button.tsx +52 -11
  27. package/dist/r/src/components/layout/orbit-layout/index.ts +8 -0
  28. package/dist/r/src/components/layout/orbit-layout/orbit-layout.tsx +112 -0
  29. package/dist/r/src/components/layout/virtual-window/index.ts +2 -0
  30. package/dist/r/src/components/layout/virtual-window/virtual-window.tsx +55 -0
  31. package/dist/r/src/components/ui/command/command.tsx +1 -0
  32. package/dist/r/virtual-window.json +22 -0
  33. package/package.json +1 -1
  34. package/registry.json +44 -0
  35. package/src/components/composites/action-button/action-button.tsx +52 -11
  36. package/src/components/layout/orbit-layout/index.ts +8 -0
  37. package/src/components/layout/orbit-layout/orbit-layout.tsx +112 -0
  38. package/src/components/layout/virtual-window/index.ts +2 -0
  39. package/src/components/layout/virtual-window/virtual-window.tsx +55 -0
  40. package/src/components/ui/command/command.tsx +1 -0
  41. package/src/index.ts +2 -0
@@ -0,0 +1,22 @@
1
+ {
2
+ "name": "orbit-layout",
3
+ "type": "registry:ui",
4
+ "title": "Orbit Layout",
5
+ "description": "Geometry-only wrappers for arranging content around a circle.",
6
+ "dependencies": [],
7
+ "registryDependencies": [
8
+ "lib"
9
+ ],
10
+ "files": [
11
+ {
12
+ "path": "src/components/layout/orbit-layout/index.ts",
13
+ "type": "registry:ui",
14
+ "target": "components/layout/orbit-layout/index.ts"
15
+ },
16
+ {
17
+ "path": "src/components/layout/orbit-layout/orbit-layout.tsx",
18
+ "type": "registry:ui",
19
+ "target": "components/layout/orbit-layout/orbit-layout.tsx"
20
+ }
21
+ ]
22
+ }
@@ -1457,6 +1457,28 @@
1457
1457
  }
1458
1458
  ]
1459
1459
  },
1460
+ {
1461
+ "name": "orbit-layout",
1462
+ "type": "registry:ui",
1463
+ "title": "Orbit Layout",
1464
+ "description": "Geometry-only wrappers for arranging content around a circle.",
1465
+ "dependencies": [],
1466
+ "registryDependencies": [
1467
+ "lib"
1468
+ ],
1469
+ "files": [
1470
+ {
1471
+ "path": "src/components/layout/orbit-layout/index.ts",
1472
+ "type": "registry:ui",
1473
+ "target": "components/layout/orbit-layout/index.ts"
1474
+ },
1475
+ {
1476
+ "path": "src/components/layout/orbit-layout/orbit-layout.tsx",
1477
+ "type": "registry:ui",
1478
+ "target": "components/layout/orbit-layout/orbit-layout.tsx"
1479
+ }
1480
+ ]
1481
+ },
1460
1482
  {
1461
1483
  "name": "page",
1462
1484
  "type": "registry:ui",
@@ -1640,6 +1662,28 @@
1640
1662
  }
1641
1663
  ]
1642
1664
  },
1665
+ {
1666
+ "name": "virtual-window",
1667
+ "type": "registry:ui",
1668
+ "title": "Virtual Window",
1669
+ "description": "Geometry wrappers for a virtual list; scrolling and range ownership stay with the caller.",
1670
+ "dependencies": [],
1671
+ "registryDependencies": [
1672
+ "lib"
1673
+ ],
1674
+ "files": [
1675
+ {
1676
+ "path": "src/components/layout/virtual-window/index.ts",
1677
+ "type": "registry:ui",
1678
+ "target": "components/layout/virtual-window/index.ts"
1679
+ },
1680
+ {
1681
+ "path": "src/components/layout/virtual-window/virtual-window.tsx",
1682
+ "type": "registry:ui",
1683
+ "target": "components/layout/virtual-window/virtual-window.tsx"
1684
+ }
1685
+ ]
1686
+ },
1643
1687
  {
1644
1688
  "name": "account-menu",
1645
1689
  "type": "registry:ui",
@@ -1,4 +1,11 @@
1
- import { Show, splitProps, type ComponentProps, type JSX, type ValidComponent } from 'solid-js'
1
+ import {
2
+ Show,
3
+ createSignal,
4
+ splitProps,
5
+ type ComponentProps,
6
+ type JSX,
7
+ type ValidComponent,
8
+ } from 'solid-js'
2
9
  import { Button, type ButtonProps } from '../../ui/button'
3
10
  import { Spinner } from '../../ui/spinner'
4
11
  import { Tooltip, TooltipContent, TooltipTrigger } from '../../ui/tooltip'
@@ -27,7 +34,28 @@ export type ActionButtonProps<T extends ValidComponent = 'button'> = ButtonProps
27
34
  busyLabel?: string
28
35
  }
29
36
 
30
- function guardedClick(handler: unknown, disabled: () => boolean) {
37
+ function invokeHandler(handler: unknown, event: Event) {
38
+ if (Array.isArray(handler)) {
39
+ const [callback, data] = handler as [(data: unknown, event: Event) => void, unknown]
40
+ callback(data, event)
41
+ } else if (typeof handler === 'function') {
42
+ const callback = handler as (event: Event) => void
43
+ callback(event)
44
+ }
45
+ }
46
+
47
+ function chainedHandler(handler: unknown, before: (event: Event) => void) {
48
+ return (event: Event) => {
49
+ before(event)
50
+ invokeHandler(handler, event)
51
+ }
52
+ }
53
+
54
+ function guardedClick(
55
+ handler: unknown,
56
+ disabled: () => boolean,
57
+ onActivate: (event: MouseEvent) => void = () => {}
58
+ ) {
31
59
  return (event: MouseEvent) => {
32
60
  if (disabled()) {
33
61
  event.preventDefault()
@@ -35,13 +63,8 @@ function guardedClick(handler: unknown, disabled: () => boolean) {
35
63
  return
36
64
  }
37
65
 
38
- if (Array.isArray(handler)) {
39
- const [callback, data] = handler as [(data: unknown, event: MouseEvent) => void, unknown]
40
- callback(data, event)
41
- } else if (typeof handler === 'function') {
42
- const callback = handler as (event: MouseEvent) => void
43
- callback(event)
44
- }
66
+ onActivate(event)
67
+ invokeHandler(handler, event)
45
68
  }
46
69
  }
47
70
 
@@ -73,6 +96,8 @@ export function ActionButton<T extends ValidComponent = 'button'>(props: ActionB
73
96
  const busy = () => local.busy ?? false
74
97
  const disabled = () => busy() || !!local.disabled
75
98
  const busyStatus = () => local.busyLabel ?? 'Working'
99
+ const [tooltipOpen, setTooltipOpen] = createSignal(false)
100
+ const [tooltipSuppression, setTooltipSuppression] = createSignal<'pointer' | 'focus' | null>(null)
76
101
  const onClickCapture = (rest as { onClickCapture?: unknown }).onClickCapture
77
102
  const PolymorphicButton = Button as (buttonProps: ButtonProps<T>) => JSX.Element
78
103
  const PolymorphicButtonTarget = ButtonTarget as (targetProps: ButtonTargetProps<T>) => JSX.Element
@@ -90,7 +115,17 @@ export function ActionButton<T extends ValidComponent = 'button'>(props: ActionB
90
115
  role: local.as === 'a' && disabled() ? 'link' : rest.role,
91
116
  href: local.as === 'a' && disabled() ? undefined : (rest as { href?: string }).href,
92
117
  'aria-busy': busy() || local['aria-busy'],
93
- onClick: guardedClick(rest.onClick, disabled),
118
+ onClick: guardedClick(rest.onClick, disabled, (event) => {
119
+ if (!local.tooltip) return
120
+ setTooltipSuppression(event.detail > 0 ? 'pointer' : 'focus')
121
+ setTooltipOpen(false)
122
+ }),
123
+ onPointerLeave: chainedHandler(rest.onPointerLeave, () => {
124
+ if (tooltipSuppression() === 'pointer') setTooltipSuppression(null)
125
+ }),
126
+ onBlur: chainedHandler(rest.onBlur, () => {
127
+ if (tooltipSuppression() === 'focus') setTooltipSuppression(null)
128
+ }),
94
129
  onClickCapture: onClickCapture ? guardedClick(onClickCapture, disabled) : undefined,
95
130
  'on:click': guardedClick((rest as Record<string, unknown>)['on:click'], disabled),
96
131
  'oncapture:click': guardedClick((rest as Record<string, unknown>)['oncapture:click'], disabled),
@@ -118,7 +153,13 @@ export function ActionButton<T extends ValidComponent = 'button'>(props: ActionB
118
153
  </PolymorphicButton>
119
154
  }
120
155
  >
121
- <Tooltip>
156
+ <Tooltip
157
+ open={tooltipOpen()}
158
+ onOpenChange={(open) => {
159
+ if (open && tooltipSuppression()) return
160
+ setTooltipOpen(open)
161
+ }}
162
+ >
122
163
  <TooltipTrigger
123
164
  as={PolymorphicButtonTarget}
124
165
  {...(buttonProps() as unknown as ComponentProps<typeof PolymorphicButtonTarget>)}
@@ -0,0 +1,8 @@
1
+ /** Geometry-only wrappers for arranging content around a circle. */
2
+ export {
3
+ OrbitItem,
4
+ OrbitLayout,
5
+ type OrbitItemProps,
6
+ type OrbitLayoutProps,
7
+ type OrbitRadius,
8
+ } from './orbit-layout'
@@ -0,0 +1,112 @@
1
+ import type { ComponentProps } from 'solid-js'
2
+ import { createContext, createMemo, splitProps, useContext, type Accessor } from 'solid-js'
3
+ import { cn } from '../../../lib/utils'
4
+
5
+ /** A CSS length or expression used to place an item away from the orbit center. */
6
+ export type OrbitRadius = number | string
7
+ export type OrbitLayoutMode = 'radial' | 'flow'
8
+
9
+ const DEFAULT_ORBIT_RADIUS = 'calc(50% - 2rem)'
10
+ const OrbitRadiusContext = createContext<Accessor<string>>()
11
+ const OrbitModeContext = createContext<Accessor<OrbitLayoutMode>>()
12
+
13
+ function resolveRadius(radius: OrbitRadius | undefined): string {
14
+ if (typeof radius === 'number') return `${Number.isFinite(radius) ? Math.max(0, radius) : 0}px`
15
+ return radius?.trim() || DEFAULT_ORBIT_RADIUS
16
+ }
17
+
18
+ /**
19
+ * OrbitLayout.
20
+ *
21
+ * Provides a positioned canvas and a shared radius for its OrbitItems. It does
22
+ * not assign list semantics or alter DOM order: callers choose semantics, and
23
+ * keyboard and screen-reader order remain the source order of the children.
24
+ */
25
+ export type OrbitLayoutProps = Omit<ComponentProps<'div'>, 'style'> & {
26
+ /** JSX style props are owned by this geometry primitive. */
27
+ style?: never
28
+ /** Responsive radius used by every OrbitItem unless that item overrides it. */
29
+ radius?: OrbitRadius
30
+ /** `flow` places items in source order; `radial` positions them around the host. */
31
+ mode?: OrbitLayoutMode
32
+ }
33
+
34
+ export function OrbitLayout(props: OrbitLayoutProps) {
35
+ const [local, rest] = splitProps(props, ['class', 'radius', 'mode', 'style', 'children'])
36
+ const radius = createMemo(() => resolveRadius(local.radius))
37
+ const mode = createMemo(() => local.mode ?? 'radial')
38
+
39
+ return (
40
+ <OrbitRadiusContext.Provider value={radius}>
41
+ <OrbitModeContext.Provider value={mode}>
42
+ <div
43
+ {...rest}
44
+ data-slot="orbit-layout"
45
+ class={cn(mode() === 'flow' ? 'grid gap-3' : 'relative size-full', local.class)}
46
+ >
47
+ {local.children}
48
+ </div>
49
+ </OrbitModeContext.Provider>
50
+ </OrbitRadiusContext.Provider>
51
+ )
52
+ }
53
+
54
+ /**
55
+ * OrbitItem.
56
+ *
57
+ * Positions one ordinary child around its OrbitLayout. This is geometry only:
58
+ * the wrapper has no role, tabindex, or interaction of its own.
59
+ */
60
+ export type OrbitItemProps = Omit<ComponentProps<'div'>, 'style'> & {
61
+ /** JSX style props are owned by this geometry primitive. */
62
+ style?: never
63
+ /** Zero-based position in the rendered sequence. */
64
+ index: number
65
+ /** Number of items sharing this orbit. */
66
+ count: number
67
+ /** Per-item override for OrbitLayout's responsive radius. */
68
+ radius?: OrbitRadius
69
+ }
70
+
71
+ export function OrbitItem(props: OrbitItemProps) {
72
+ const [local, rest] = splitProps(props, [
73
+ 'class',
74
+ 'index',
75
+ 'count',
76
+ 'radius',
77
+ 'style',
78
+ 'children',
79
+ ])
80
+ const inheritedRadius = useContext(OrbitRadiusContext)
81
+ const inheritedMode = useContext(OrbitModeContext)
82
+ const mode = () => inheritedMode?.() ?? 'radial'
83
+ const radius = () =>
84
+ local.radius !== undefined
85
+ ? resolveRadius(local.radius)
86
+ : (inheritedRadius?.() ?? DEFAULT_ORBIT_RADIUS)
87
+ const angle = createMemo(() => {
88
+ const count = Number.isFinite(local.count) ? Math.max(1, Math.trunc(local.count)) : 1
89
+ const rawIndex = Number.isFinite(local.index) ? Math.trunc(local.index) : 0
90
+ const index = ((rawIndex % count) + count) % count
91
+ return `${(index / count) * Math.PI * 2}rad`
92
+ })
93
+ const positionStyle = () =>
94
+ mode() === 'flow'
95
+ ? undefined
96
+ : {
97
+ top: `calc(50% + sin(${angle()}) * ${radius()})`,
98
+ left: `calc(50% + cos(${angle()}) * ${radius()})`,
99
+ translate: '-50% -50%',
100
+ }
101
+
102
+ return (
103
+ <div
104
+ {...rest}
105
+ data-slot="orbit-item"
106
+ class={cn(mode() === 'flow' ? undefined : 'absolute', local.class)}
107
+ style={positionStyle()}
108
+ >
109
+ {local.children}
110
+ </div>
111
+ )
112
+ }
@@ -0,0 +1,2 @@
1
+ /** Geometry wrappers for a virtual list; scrolling and range ownership stay with the caller. */
2
+ export { VirtualWindow, type VirtualWindowProps } from './virtual-window'
@@ -0,0 +1,55 @@
1
+ import type { ComponentProps, JSX } from 'solid-js'
2
+ import { splitProps } from 'solid-js'
3
+ import { cn } from '../../../lib/utils'
4
+
5
+ /**
6
+ * VirtualWindow.
7
+ *
8
+ * Supplies the two geometry wrappers a virtualized viewport needs: a full-height
9
+ * scroll space and a translated window containing the mounted rows. The caller
10
+ * owns the scroll region, its semantics, keyboard model, and range calculation;
11
+ * this component only keeps those rows at their measured position.
12
+ */
13
+ export type VirtualWindowProps = Omit<ComponentProps<'div'>, 'children' | 'style'> & {
14
+ /** JSX style props are owned by this geometry primitive. */
15
+ style?: never
16
+ /** The full scrollable height, in pixels. */
17
+ totalSize: number
18
+ /** The mounted window's top offset from the start of the full list, in pixels. */
19
+ offset?: number
20
+ /** Optional layout classes for the translated row container. */
21
+ contentClass?: string
22
+ children?: JSX.Element
23
+ }
24
+
25
+ function finitePixels(value: number): string {
26
+ return `${Number.isFinite(value) ? value : 0}px`
27
+ }
28
+
29
+ export function VirtualWindow(props: VirtualWindowProps) {
30
+ const [local, rest] = splitProps(props, [
31
+ 'class',
32
+ 'children',
33
+ 'totalSize',
34
+ 'offset',
35
+ 'contentClass',
36
+ 'style',
37
+ ])
38
+
39
+ return (
40
+ <div
41
+ {...rest}
42
+ data-slot="virtual-window-space"
43
+ class={cn('relative', local.class)}
44
+ style={{ height: finitePixels(Math.max(0, local.totalSize)) }}
45
+ >
46
+ <div
47
+ data-slot="virtual-window-content"
48
+ class={local.contentClass}
49
+ style={{ transform: `translateY(${finitePixels(local.offset ?? 0)})` }}
50
+ >
51
+ {local.children}
52
+ </div>
53
+ </div>
54
+ )
55
+ }
@@ -110,6 +110,7 @@ export function CommandDialog(props: CommandDialogProps) {
110
110
  <KobalteDialog.Content
111
111
  aria-label={commandProps.label}
112
112
  cmdk-dialog=""
113
+ role="dialog"
113
114
  class={cn(
114
115
  'bg-popover text-popover-foreground fixed top-1/3 left-1/2 z-(--z-dialog) w-full max-w-lg -translate-x-1/2 -translate-y-1/2',
115
116
  'overflow-hidden rounded-xl border border-border shadow-xl',
@@ -0,0 +1,22 @@
1
+ {
2
+ "name": "virtual-window",
3
+ "type": "registry:ui",
4
+ "title": "Virtual Window",
5
+ "description": "Geometry wrappers for a virtual list; scrolling and range ownership stay with the caller.",
6
+ "dependencies": [],
7
+ "registryDependencies": [
8
+ "lib"
9
+ ],
10
+ "files": [
11
+ {
12
+ "path": "src/components/layout/virtual-window/index.ts",
13
+ "type": "registry:ui",
14
+ "target": "components/layout/virtual-window/index.ts"
15
+ },
16
+ {
17
+ "path": "src/components/layout/virtual-window/virtual-window.tsx",
18
+ "type": "registry:ui",
19
+ "target": "components/layout/virtual-window/virtual-window.tsx"
20
+ }
21
+ ]
22
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adea-ai/ui",
3
- "version": "0.82.2",
3
+ "version": "0.83.1",
4
4
  "description": "The Adea design system: Kobalte-backed Solid components, semantic OKLCH tokens, and the app-shell layout primitives shared by Adea and Cortana.",
5
5
  "keywords": [
6
6
  "adea",
package/registry.json CHANGED
@@ -1457,6 +1457,28 @@
1457
1457
  }
1458
1458
  ]
1459
1459
  },
1460
+ {
1461
+ "name": "orbit-layout",
1462
+ "type": "registry:ui",
1463
+ "title": "Orbit Layout",
1464
+ "description": "Geometry-only wrappers for arranging content around a circle.",
1465
+ "dependencies": [],
1466
+ "registryDependencies": [
1467
+ "lib"
1468
+ ],
1469
+ "files": [
1470
+ {
1471
+ "path": "src/components/layout/orbit-layout/index.ts",
1472
+ "type": "registry:ui",
1473
+ "target": "components/layout/orbit-layout/index.ts"
1474
+ },
1475
+ {
1476
+ "path": "src/components/layout/orbit-layout/orbit-layout.tsx",
1477
+ "type": "registry:ui",
1478
+ "target": "components/layout/orbit-layout/orbit-layout.tsx"
1479
+ }
1480
+ ]
1481
+ },
1460
1482
  {
1461
1483
  "name": "page",
1462
1484
  "type": "registry:ui",
@@ -1640,6 +1662,28 @@
1640
1662
  }
1641
1663
  ]
1642
1664
  },
1665
+ {
1666
+ "name": "virtual-window",
1667
+ "type": "registry:ui",
1668
+ "title": "Virtual Window",
1669
+ "description": "Geometry wrappers for a virtual list; scrolling and range ownership stay with the caller.",
1670
+ "dependencies": [],
1671
+ "registryDependencies": [
1672
+ "lib"
1673
+ ],
1674
+ "files": [
1675
+ {
1676
+ "path": "src/components/layout/virtual-window/index.ts",
1677
+ "type": "registry:ui",
1678
+ "target": "components/layout/virtual-window/index.ts"
1679
+ },
1680
+ {
1681
+ "path": "src/components/layout/virtual-window/virtual-window.tsx",
1682
+ "type": "registry:ui",
1683
+ "target": "components/layout/virtual-window/virtual-window.tsx"
1684
+ }
1685
+ ]
1686
+ },
1643
1687
  {
1644
1688
  "name": "account-menu",
1645
1689
  "type": "registry:ui",
@@ -1,4 +1,11 @@
1
- import { Show, splitProps, type ComponentProps, type JSX, type ValidComponent } from 'solid-js'
1
+ import {
2
+ Show,
3
+ createSignal,
4
+ splitProps,
5
+ type ComponentProps,
6
+ type JSX,
7
+ type ValidComponent,
8
+ } from 'solid-js'
2
9
  import { Button, type ButtonProps } from '../../ui/button'
3
10
  import { Spinner } from '../../ui/spinner'
4
11
  import { Tooltip, TooltipContent, TooltipTrigger } from '../../ui/tooltip'
@@ -27,7 +34,28 @@ export type ActionButtonProps<T extends ValidComponent = 'button'> = ButtonProps
27
34
  busyLabel?: string
28
35
  }
29
36
 
30
- function guardedClick(handler: unknown, disabled: () => boolean) {
37
+ function invokeHandler(handler: unknown, event: Event) {
38
+ if (Array.isArray(handler)) {
39
+ const [callback, data] = handler as [(data: unknown, event: Event) => void, unknown]
40
+ callback(data, event)
41
+ } else if (typeof handler === 'function') {
42
+ const callback = handler as (event: Event) => void
43
+ callback(event)
44
+ }
45
+ }
46
+
47
+ function chainedHandler(handler: unknown, before: (event: Event) => void) {
48
+ return (event: Event) => {
49
+ before(event)
50
+ invokeHandler(handler, event)
51
+ }
52
+ }
53
+
54
+ function guardedClick(
55
+ handler: unknown,
56
+ disabled: () => boolean,
57
+ onActivate: (event: MouseEvent) => void = () => {}
58
+ ) {
31
59
  return (event: MouseEvent) => {
32
60
  if (disabled()) {
33
61
  event.preventDefault()
@@ -35,13 +63,8 @@ function guardedClick(handler: unknown, disabled: () => boolean) {
35
63
  return
36
64
  }
37
65
 
38
- if (Array.isArray(handler)) {
39
- const [callback, data] = handler as [(data: unknown, event: MouseEvent) => void, unknown]
40
- callback(data, event)
41
- } else if (typeof handler === 'function') {
42
- const callback = handler as (event: MouseEvent) => void
43
- callback(event)
44
- }
66
+ onActivate(event)
67
+ invokeHandler(handler, event)
45
68
  }
46
69
  }
47
70
 
@@ -73,6 +96,8 @@ export function ActionButton<T extends ValidComponent = 'button'>(props: ActionB
73
96
  const busy = () => local.busy ?? false
74
97
  const disabled = () => busy() || !!local.disabled
75
98
  const busyStatus = () => local.busyLabel ?? 'Working'
99
+ const [tooltipOpen, setTooltipOpen] = createSignal(false)
100
+ const [tooltipSuppression, setTooltipSuppression] = createSignal<'pointer' | 'focus' | null>(null)
76
101
  const onClickCapture = (rest as { onClickCapture?: unknown }).onClickCapture
77
102
  const PolymorphicButton = Button as (buttonProps: ButtonProps<T>) => JSX.Element
78
103
  const PolymorphicButtonTarget = ButtonTarget as (targetProps: ButtonTargetProps<T>) => JSX.Element
@@ -90,7 +115,17 @@ export function ActionButton<T extends ValidComponent = 'button'>(props: ActionB
90
115
  role: local.as === 'a' && disabled() ? 'link' : rest.role,
91
116
  href: local.as === 'a' && disabled() ? undefined : (rest as { href?: string }).href,
92
117
  'aria-busy': busy() || local['aria-busy'],
93
- onClick: guardedClick(rest.onClick, disabled),
118
+ onClick: guardedClick(rest.onClick, disabled, (event) => {
119
+ if (!local.tooltip) return
120
+ setTooltipSuppression(event.detail > 0 ? 'pointer' : 'focus')
121
+ setTooltipOpen(false)
122
+ }),
123
+ onPointerLeave: chainedHandler(rest.onPointerLeave, () => {
124
+ if (tooltipSuppression() === 'pointer') setTooltipSuppression(null)
125
+ }),
126
+ onBlur: chainedHandler(rest.onBlur, () => {
127
+ if (tooltipSuppression() === 'focus') setTooltipSuppression(null)
128
+ }),
94
129
  onClickCapture: onClickCapture ? guardedClick(onClickCapture, disabled) : undefined,
95
130
  'on:click': guardedClick((rest as Record<string, unknown>)['on:click'], disabled),
96
131
  'oncapture:click': guardedClick((rest as Record<string, unknown>)['oncapture:click'], disabled),
@@ -118,7 +153,13 @@ export function ActionButton<T extends ValidComponent = 'button'>(props: ActionB
118
153
  </PolymorphicButton>
119
154
  }
120
155
  >
121
- <Tooltip>
156
+ <Tooltip
157
+ open={tooltipOpen()}
158
+ onOpenChange={(open) => {
159
+ if (open && tooltipSuppression()) return
160
+ setTooltipOpen(open)
161
+ }}
162
+ >
122
163
  <TooltipTrigger
123
164
  as={PolymorphicButtonTarget}
124
165
  {...(buttonProps() as unknown as ComponentProps<typeof PolymorphicButtonTarget>)}
@@ -0,0 +1,8 @@
1
+ /** Geometry-only wrappers for arranging content around a circle. */
2
+ export {
3
+ OrbitItem,
4
+ OrbitLayout,
5
+ type OrbitItemProps,
6
+ type OrbitLayoutProps,
7
+ type OrbitRadius,
8
+ } from './orbit-layout'