aeon-ui-engine 1.3.1 → 1.4.0
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 +61 -0
- package/package.json +1 -1
- package/packages/cli/README.md +13 -1
- package/packages/cli/dist/bin.js +23 -5
- package/packages/cli/dist/chunk-5L47SLNV.js +247 -0
- package/packages/cli/dist/index.d.ts +17 -1
- package/packages/cli/dist/index.js +5 -3
- package/packages/cli/src/bin.ts +21 -3
- package/packages/cli/src/index.ts +1 -0
- package/packages/cli/src/migrate.ts +170 -0
- package/packages/core/dist/index.d.ts +35 -7
- package/packages/core/dist/index.js +102 -2
- package/packages/core/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +1 -1
- package/packages/core/src/anatomy.ts +39 -3
- package/packages/core/src/identicon.test.ts +39 -0
- package/packages/core/src/identicon.ts +79 -0
- package/packages/core/src/index.ts +1 -0
- package/packages/panda/panda.config.ts +4 -0
- package/packages/panda/src/recipes/composer.ts +58 -4
- package/packages/panda/src/recipes/file-upload.ts +100 -0
- package/packages/panda/src/recipes/index.ts +2 -0
- package/packages/panda/src/recipes/range-slider.ts +59 -0
- package/packages/panda/src/recipes/thread.ts +77 -2
- package/packages/panda/styled-system/recipes/aeon-composer.d.ts +1 -1
- package/packages/panda/styled-system/recipes/aeon-composer.mjs +12 -0
- package/packages/panda/styled-system/recipes/aeon-file-upload.d.ts +31 -0
- package/packages/panda/styled-system/recipes/aeon-file-upload.mjs +61 -0
- package/packages/panda/styled-system/recipes/aeon-range-slider.d.ts +31 -0
- package/packages/panda/styled-system/recipes/aeon-range-slider.mjs +49 -0
- package/packages/panda/styled-system/recipes/aeon-thread.d.ts +1 -1
- package/packages/panda/styled-system/recipes/aeon-thread.mjs +20 -0
- package/packages/panda/styled-system/recipes/index.d.ts +2 -0
- package/packages/panda/styled-system/recipes/index.mjs +2 -0
- package/packages/panda/styled-system/styles.css +292 -0
- package/packages/primitives/dist/chunk-CV4UXXZF.js +44 -0
- package/packages/primitives/dist/chunk-IZKQSCYU.js +164 -0
- package/packages/primitives/dist/index.d.ts +2 -0
- package/packages/primitives/dist/index.js +22 -6
- package/packages/primitives/dist/machines/file-upload.d.ts +39 -0
- package/packages/primitives/dist/machines/file-upload.js +8 -0
- package/packages/primitives/dist/machines/range-slider.d.ts +129 -0
- package/packages/primitives/dist/machines/range-slider.js +12 -0
- package/packages/primitives/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +1 -1
- package/packages/primitives/src/__tests__/file-upload.test.ts +25 -0
- package/packages/primitives/src/__tests__/range-slider.test.ts +39 -0
- package/packages/primitives/src/index.ts +2 -0
- package/packages/primitives/src/machines/file-upload.ts +55 -0
- package/packages/primitives/src/machines/range-slider.ts +209 -0
- package/packages/react/dist/index.d.ts +117 -25
- package/packages/react/dist/index.js +1731 -1006
- package/packages/react/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +1 -1
- package/packages/react/src/button/index.tsx +50 -15
- package/packages/react/src/combobox/index.tsx +13 -8
- package/packages/react/src/composer/index.tsx +58 -3
- package/packages/react/src/file-upload/index.tsx +322 -0
- package/packages/react/src/index.ts +3 -0
- package/packages/react/src/prompt/index.tsx +45 -0
- package/packages/react/src/range-slider/index.tsx +322 -0
- package/packages/react/src/thread/index.tsx +74 -3
- package/packages/react/src/tooltip/index.tsx +48 -22
- package/packages/react/src/utils/slot.tsx +47 -0
- package/packages/schemas/dist/index.d.ts +73 -24
- package/packages/schemas/dist/index.js +514 -50
- package/packages/schemas/json/appNav.json +146 -0
- package/packages/schemas/json/appShell.json +151 -0
- package/packages/schemas/json/avatar.json +130 -0
- package/packages/schemas/json/bar.json +126 -0
- package/packages/schemas/json/button.json +6 -0
- package/packages/schemas/json/catalog.json +3496 -15
- package/packages/schemas/json/combobox.json +19 -7
- package/packages/schemas/json/composer.json +125 -0
- package/packages/schemas/json/content.json +167 -0
- package/packages/schemas/json/conversation.json +166 -0
- package/packages/schemas/json/entry.json +182 -0
- package/packages/schemas/json/fileUpload.json +168 -0
- package/packages/schemas/json/identity.json +147 -0
- package/packages/schemas/json/listRow.json +131 -0
- package/packages/schemas/json/metricStrip.json +132 -0
- package/packages/schemas/json/nav.json +136 -0
- package/packages/schemas/json/panel.json +143 -0
- package/packages/schemas/json/pinInput.json +112 -0
- package/packages/schemas/json/profileHeader.json +138 -0
- package/packages/schemas/json/prompt.json +155 -0
- package/packages/schemas/json/radioGroup.json +130 -0
- package/packages/schemas/json/rangeSlider.json +157 -0
- package/packages/schemas/json/separator.json +105 -0
- package/packages/schemas/json/slider.json +147 -0
- package/packages/schemas/json/statusBanner.json +149 -0
- package/packages/schemas/json/stickyBar.json +153 -0
- package/packages/schemas/json/thread.json +129 -0
- package/packages/schemas/json/tooltip.json +21 -7
- package/packages/schemas/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +1 -1
- package/packages/schemas/prompts/cursorrules.md +6 -4
- package/packages/schemas/prompts/statecharts.md +23 -2
- package/packages/schemas/prompts/system.md +2 -2
- package/packages/schemas/registry/catalog.json +313 -4
- package/packages/schemas/registry/components/appNav.json +146 -0
- package/packages/schemas/registry/components/appShell.json +151 -0
- package/packages/schemas/registry/components/avatar.json +130 -0
- package/packages/schemas/registry/components/bar.json +126 -0
- package/packages/schemas/registry/components/button.json +6 -0
- package/packages/schemas/registry/components/combobox.json +19 -7
- package/packages/schemas/registry/components/composer.json +125 -0
- package/packages/schemas/registry/components/content.json +167 -0
- package/packages/schemas/registry/components/conversation.json +166 -0
- package/packages/schemas/registry/components/entry.json +182 -0
- package/packages/schemas/registry/components/fileUpload.json +168 -0
- package/packages/schemas/registry/components/identity.json +147 -0
- package/packages/schemas/registry/components/listRow.json +131 -0
- package/packages/schemas/registry/components/metricStrip.json +132 -0
- package/packages/schemas/registry/components/nav.json +136 -0
- package/packages/schemas/registry/components/panel.json +143 -0
- package/packages/schemas/registry/components/pinInput.json +112 -0
- package/packages/schemas/registry/components/profileHeader.json +138 -0
- package/packages/schemas/registry/components/prompt.json +155 -0
- package/packages/schemas/registry/components/radioGroup.json +130 -0
- package/packages/schemas/registry/components/rangeSlider.json +157 -0
- package/packages/schemas/registry/components/separator.json +105 -0
- package/packages/schemas/registry/components/slider.json +147 -0
- package/packages/schemas/registry/components/statusBanner.json +149 -0
- package/packages/schemas/registry/components/stickyBar.json +153 -0
- package/packages/schemas/registry/components/thread.json +129 -0
- package/packages/schemas/registry/components/tooltip.json +21 -7
- package/packages/schemas/registry/machines/accordion.json +58 -0
- package/packages/schemas/registry/machines/appNav.json +108 -0
- package/packages/schemas/registry/machines/appShell.json +128 -0
- package/packages/schemas/registry/machines/contentRegion.json +314 -0
- package/packages/schemas/registry/machines/fileUpload.json +182 -0
- package/packages/schemas/registry/machines/panel.json +131 -0
- package/packages/schemas/registry/machines/popover.json +143 -0
- package/packages/schemas/registry/machines/rangeSlider.json +201 -0
- package/packages/schemas/registry/machines/slider.json +151 -0
- package/packages/schemas/registry/machines/stickyBar.json +261 -0
- package/packages/schemas/registry/machines/tabs.json +58 -0
- package/packages/schemas/registry/machines/toast.json +89 -0
- package/packages/schemas/registry/machines/toggle.json +134 -0
- package/packages/schemas/scripts/emit.ts +27 -0
- package/packages/schemas/src/components.ts +499 -6
- package/packages/schemas/src/index.test.ts +83 -0
- package/packages/schemas/src/index.ts +1 -0
- package/packages/schemas/src/machine-id.ts +7 -0
- package/packages/schemas/src/prompts.ts +25 -4
- package/packages/schemas/src/registry.ts +28 -39
- package/packages/schemas/tsconfig.json +1 -1
- package/packages/ui/dist/index.d.ts +100 -46
- package/packages/ui/dist/index.js +463 -341
- package/packages/ui/src/composer.tsx +37 -11
- package/packages/ui/src/file-upload.tsx +36 -0
- package/packages/ui/src/index.ts +2 -0
- package/packages/ui/src/prompt.tsx +3 -0
- package/packages/ui/src/range-slider.tsx +24 -0
- package/packages/ui/src/thread.tsx +40 -0
- package/packages/cli/dist/chunk-IW5NT2Z2.js +0 -103
|
@@ -0,0 +1,322 @@
|
|
|
1
|
+
import { partAttrs, partOnlyAttrs, rangeSliderAnatomy } from '@aeon-ui/core'
|
|
2
|
+
import {
|
|
3
|
+
rangeSliderMachine,
|
|
4
|
+
snapRangeSliderValue,
|
|
5
|
+
} from '@aeon-ui/primitives'
|
|
6
|
+
import {
|
|
7
|
+
createContext,
|
|
8
|
+
forwardRef,
|
|
9
|
+
useCallback,
|
|
10
|
+
useContext,
|
|
11
|
+
useEffect,
|
|
12
|
+
useMemo,
|
|
13
|
+
useRef,
|
|
14
|
+
type HTMLAttributes,
|
|
15
|
+
type KeyboardEvent,
|
|
16
|
+
type PointerEvent,
|
|
17
|
+
type ReactNode,
|
|
18
|
+
} from 'react'
|
|
19
|
+
import { useAeonMachine } from '../hooks/use-aeon-machine.js'
|
|
20
|
+
import { mergeProps } from '../utils/merge-props.js'
|
|
21
|
+
|
|
22
|
+
type ThumbIndex = 0 | 1
|
|
23
|
+
|
|
24
|
+
interface RangeSliderContextValue {
|
|
25
|
+
values: [number, number]
|
|
26
|
+
min: number
|
|
27
|
+
max: number
|
|
28
|
+
step: number
|
|
29
|
+
disabled: boolean
|
|
30
|
+
dragging: boolean
|
|
31
|
+
activeThumb: ThumbIndex | null
|
|
32
|
+
send: ReturnType<typeof useAeonMachine<typeof rangeSliderMachine>>[1]
|
|
33
|
+
setThumbFromClientX: (thumb: ThumbIndex, clientX: number) => void
|
|
34
|
+
trackRef: React.RefObject<HTMLDivElement | null>
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const RangeSliderCtx = createContext<RangeSliderContextValue | null>(null)
|
|
38
|
+
|
|
39
|
+
function useRangeSliderCtx() {
|
|
40
|
+
const ctx = useContext(RangeSliderCtx)
|
|
41
|
+
if (!ctx) throw new Error('RangeSlider parts must be used within RangeSlider.Root')
|
|
42
|
+
return ctx
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function percent(value: number, min: number, max: number) {
|
|
46
|
+
if (max <= min) return 0
|
|
47
|
+
return ((value - min) / (max - min)) * 100
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export interface RangeSliderRootProps {
|
|
51
|
+
value?: [number, number]
|
|
52
|
+
defaultValue?: [number, number]
|
|
53
|
+
min?: number
|
|
54
|
+
max?: number
|
|
55
|
+
step?: number
|
|
56
|
+
minStepsBetween?: number
|
|
57
|
+
disabled?: boolean
|
|
58
|
+
onValueChange?: (value: [number, number]) => void
|
|
59
|
+
children?: ReactNode
|
|
60
|
+
className?: string
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const Root = forwardRef<HTMLDivElement, RangeSliderRootProps>(function RangeSliderRoot(
|
|
64
|
+
{
|
|
65
|
+
value,
|
|
66
|
+
defaultValue,
|
|
67
|
+
min = 0,
|
|
68
|
+
max = 100,
|
|
69
|
+
step = 1,
|
|
70
|
+
minStepsBetween = 0,
|
|
71
|
+
disabled = false,
|
|
72
|
+
onValueChange,
|
|
73
|
+
children,
|
|
74
|
+
className,
|
|
75
|
+
...rest
|
|
76
|
+
},
|
|
77
|
+
ref,
|
|
78
|
+
) {
|
|
79
|
+
const trackRef = useRef<HTMLDivElement>(null)
|
|
80
|
+
const [snapshot, send] = useAeonMachine(rangeSliderMachine, {
|
|
81
|
+
input: {
|
|
82
|
+
values: defaultValue ?? [min, max],
|
|
83
|
+
min,
|
|
84
|
+
max,
|
|
85
|
+
step,
|
|
86
|
+
minStepsBetween,
|
|
87
|
+
disabled,
|
|
88
|
+
},
|
|
89
|
+
})
|
|
90
|
+
|
|
91
|
+
const resolvedValues = value ?? snapshot.context.values
|
|
92
|
+
const dragging = snapshot.matches({ interaction: 'dragging' })
|
|
93
|
+
const activeThumb = snapshot.context.activeThumb
|
|
94
|
+
|
|
95
|
+
useEffect(() => {
|
|
96
|
+
if (value !== undefined) send({ type: 'SET_VALUES', values: value })
|
|
97
|
+
}, [value, send])
|
|
98
|
+
|
|
99
|
+
useEffect(() => {
|
|
100
|
+
send({ type: 'SET_BOUNDS', min, max, step, minStepsBetween })
|
|
101
|
+
}, [min, max, step, minStepsBetween, send])
|
|
102
|
+
|
|
103
|
+
useEffect(() => {
|
|
104
|
+
send({ type: 'SET_DISABLED', disabled })
|
|
105
|
+
}, [disabled, send])
|
|
106
|
+
|
|
107
|
+
const setThumbFromClientX = useCallback(
|
|
108
|
+
(thumb: ThumbIndex, clientX: number) => {
|
|
109
|
+
const track = trackRef.current
|
|
110
|
+
if (!track || disabled) return
|
|
111
|
+
const rect = track.getBoundingClientRect()
|
|
112
|
+
if (rect.width <= 0) return
|
|
113
|
+
const ratio = Math.min(1, Math.max(0, (clientX - rect.left) / rect.width))
|
|
114
|
+
const next = snapRangeSliderValue(min + ratio * (max - min), min, max, step)
|
|
115
|
+
send({ type: 'POINTER_MOVE', value: next })
|
|
116
|
+
if (value === undefined) {
|
|
117
|
+
// Machine updates async via snapshot; notify after POINTER_MOVE by reading next tick
|
|
118
|
+
// Consumers in uncontrolled mode get onValueChange from effect below.
|
|
119
|
+
}
|
|
120
|
+
void thumb
|
|
121
|
+
},
|
|
122
|
+
[disabled, min, max, step, send, value],
|
|
123
|
+
)
|
|
124
|
+
|
|
125
|
+
const lastNotified = useRef<string>('')
|
|
126
|
+
useEffect(() => {
|
|
127
|
+
const key = resolvedValues.join(',')
|
|
128
|
+
if (key === lastNotified.current) return
|
|
129
|
+
lastNotified.current = key
|
|
130
|
+
onValueChange?.(resolvedValues)
|
|
131
|
+
}, [resolvedValues, onValueChange])
|
|
132
|
+
|
|
133
|
+
useEffect(() => {
|
|
134
|
+
if (!dragging) return
|
|
135
|
+
const onMove = (e: globalThis.PointerEvent) => {
|
|
136
|
+
if (activeThumb == null) return
|
|
137
|
+
setThumbFromClientX(activeThumb, e.clientX)
|
|
138
|
+
}
|
|
139
|
+
const onUp = () => send({ type: 'POINTER_UP' })
|
|
140
|
+
window.addEventListener('pointermove', onMove)
|
|
141
|
+
window.addEventListener('pointerup', onUp)
|
|
142
|
+
return () => {
|
|
143
|
+
window.removeEventListener('pointermove', onMove)
|
|
144
|
+
window.removeEventListener('pointerup', onUp)
|
|
145
|
+
}
|
|
146
|
+
}, [dragging, activeThumb, setThumbFromClientX, send])
|
|
147
|
+
|
|
148
|
+
const ctx = useMemo(
|
|
149
|
+
() => ({
|
|
150
|
+
values: resolvedValues,
|
|
151
|
+
min,
|
|
152
|
+
max,
|
|
153
|
+
step,
|
|
154
|
+
disabled,
|
|
155
|
+
dragging,
|
|
156
|
+
activeThumb,
|
|
157
|
+
send,
|
|
158
|
+
setThumbFromClientX,
|
|
159
|
+
trackRef,
|
|
160
|
+
}),
|
|
161
|
+
[
|
|
162
|
+
resolvedValues,
|
|
163
|
+
min,
|
|
164
|
+
max,
|
|
165
|
+
step,
|
|
166
|
+
disabled,
|
|
167
|
+
dragging,
|
|
168
|
+
activeThumb,
|
|
169
|
+
send,
|
|
170
|
+
setThumbFromClientX,
|
|
171
|
+
],
|
|
172
|
+
)
|
|
173
|
+
|
|
174
|
+
return (
|
|
175
|
+
<RangeSliderCtx.Provider value={ctx}>
|
|
176
|
+
<div
|
|
177
|
+
ref={ref}
|
|
178
|
+
className={className}
|
|
179
|
+
{...mergeProps(
|
|
180
|
+
partAttrs(rangeSliderAnatomy.scope, rangeSliderAnatomy.root, {
|
|
181
|
+
state: dragging ? 'dragging' : 'idle',
|
|
182
|
+
disabled,
|
|
183
|
+
}),
|
|
184
|
+
rest as HTMLAttributes<HTMLDivElement>,
|
|
185
|
+
)}
|
|
186
|
+
>
|
|
187
|
+
{children}
|
|
188
|
+
</div>
|
|
189
|
+
</RangeSliderCtx.Provider>
|
|
190
|
+
)
|
|
191
|
+
})
|
|
192
|
+
|
|
193
|
+
const Track = forwardRef<HTMLDivElement, HTMLAttributes<HTMLDivElement>>(function RangeSliderTrack(
|
|
194
|
+
{ onPointerDown, ...rest },
|
|
195
|
+
ref,
|
|
196
|
+
) {
|
|
197
|
+
const { disabled, values, send, setThumbFromClientX, trackRef, min, max } = useRangeSliderCtx()
|
|
198
|
+
|
|
199
|
+
return (
|
|
200
|
+
<div
|
|
201
|
+
ref={(node) => {
|
|
202
|
+
trackRef.current = node
|
|
203
|
+
if (typeof ref === 'function') ref(node)
|
|
204
|
+
else if (ref) ref.current = node
|
|
205
|
+
}}
|
|
206
|
+
{...mergeProps(
|
|
207
|
+
partOnlyAttrs(rangeSliderAnatomy.track),
|
|
208
|
+
{
|
|
209
|
+
onPointerDown: (e: PointerEvent<HTMLDivElement>) => {
|
|
210
|
+
onPointerDown?.(e)
|
|
211
|
+
if (disabled || e.button !== 0) return
|
|
212
|
+
e.currentTarget.setPointerCapture(e.pointerId)
|
|
213
|
+
const rect = e.currentTarget.getBoundingClientRect()
|
|
214
|
+
const ratio = Math.min(1, Math.max(0, (e.clientX - rect.left) / rect.width))
|
|
215
|
+
const pointerValue = min + ratio * (max - min)
|
|
216
|
+
const dist0 = Math.abs(pointerValue - values[0])
|
|
217
|
+
const dist1 = Math.abs(pointerValue - values[1])
|
|
218
|
+
const thumb: ThumbIndex = dist0 <= dist1 ? 0 : 1
|
|
219
|
+
send({ type: 'POINTER_DOWN', thumb })
|
|
220
|
+
setThumbFromClientX(thumb, e.clientX)
|
|
221
|
+
},
|
|
222
|
+
},
|
|
223
|
+
rest,
|
|
224
|
+
)}
|
|
225
|
+
/>
|
|
226
|
+
)
|
|
227
|
+
})
|
|
228
|
+
|
|
229
|
+
const Range = forwardRef<HTMLDivElement, HTMLAttributes<HTMLDivElement>>(function RangeSliderRange(
|
|
230
|
+
{ style, ...rest },
|
|
231
|
+
ref,
|
|
232
|
+
) {
|
|
233
|
+
const { values, min, max } = useRangeSliderCtx()
|
|
234
|
+
const left = percent(values[0], min, max)
|
|
235
|
+
const right = percent(values[1], min, max)
|
|
236
|
+
return (
|
|
237
|
+
<div
|
|
238
|
+
ref={ref}
|
|
239
|
+
aria-hidden
|
|
240
|
+
{...mergeProps(partOnlyAttrs(rangeSliderAnatomy.range), rest, {
|
|
241
|
+
style: { ...style, left: `${left}%`, width: `${Math.max(0, right - left)}%` },
|
|
242
|
+
})}
|
|
243
|
+
/>
|
|
244
|
+
)
|
|
245
|
+
})
|
|
246
|
+
|
|
247
|
+
export interface RangeSliderThumbProps extends HTMLAttributes<HTMLSpanElement> {
|
|
248
|
+
index: ThumbIndex
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
const Thumb = forwardRef<HTMLSpanElement, RangeSliderThumbProps>(function RangeSliderThumb(
|
|
252
|
+
{ index, onKeyDown, onPointerDown, style, ...rest },
|
|
253
|
+
ref,
|
|
254
|
+
) {
|
|
255
|
+
const { values, min, max, step, disabled, dragging, activeThumb, send, setThumbFromClientX } =
|
|
256
|
+
useRangeSliderCtx()
|
|
257
|
+
const value = values[index]
|
|
258
|
+
const isActive = dragging && activeThumb === index
|
|
259
|
+
|
|
260
|
+
return (
|
|
261
|
+
<span
|
|
262
|
+
ref={ref}
|
|
263
|
+
role="slider"
|
|
264
|
+
tabIndex={disabled ? -1 : 0}
|
|
265
|
+
aria-valuemin={min}
|
|
266
|
+
aria-valuemax={max}
|
|
267
|
+
aria-valuenow={value}
|
|
268
|
+
aria-disabled={disabled || undefined}
|
|
269
|
+
data-aeon-index={index}
|
|
270
|
+
{...mergeProps(
|
|
271
|
+
partOnlyAttrs(rangeSliderAnatomy.thumb, {
|
|
272
|
+
state: isActive ? 'dragging' : disabled ? 'disabled' : 'idle',
|
|
273
|
+
}),
|
|
274
|
+
{
|
|
275
|
+
style: { ...style, left: `${percent(value, min, max)}%` },
|
|
276
|
+
onPointerDown: (e: PointerEvent<HTMLSpanElement>) => {
|
|
277
|
+
onPointerDown?.(e)
|
|
278
|
+
if (disabled || e.button !== 0) return
|
|
279
|
+
e.stopPropagation()
|
|
280
|
+
e.currentTarget.setPointerCapture(e.pointerId)
|
|
281
|
+
send({ type: 'POINTER_DOWN', thumb: index })
|
|
282
|
+
setThumbFromClientX(index, e.clientX)
|
|
283
|
+
},
|
|
284
|
+
onKeyDown: (e: KeyboardEvent<HTMLSpanElement>) => {
|
|
285
|
+
onKeyDown?.(e)
|
|
286
|
+
if (disabled || e.defaultPrevented) return
|
|
287
|
+
if (e.key === 'ArrowRight' || e.key === 'ArrowUp') {
|
|
288
|
+
e.preventDefault()
|
|
289
|
+
send({ type: 'STEP', thumb: index, delta: step })
|
|
290
|
+
}
|
|
291
|
+
if (e.key === 'ArrowLeft' || e.key === 'ArrowDown') {
|
|
292
|
+
e.preventDefault()
|
|
293
|
+
send({ type: 'STEP', thumb: index, delta: -step })
|
|
294
|
+
}
|
|
295
|
+
if (e.key === 'Home') {
|
|
296
|
+
e.preventDefault()
|
|
297
|
+
send({ type: 'HOME', thumb: index })
|
|
298
|
+
}
|
|
299
|
+
if (e.key === 'End') {
|
|
300
|
+
e.preventDefault()
|
|
301
|
+
send({ type: 'END', thumb: index })
|
|
302
|
+
}
|
|
303
|
+
},
|
|
304
|
+
},
|
|
305
|
+
rest,
|
|
306
|
+
)}
|
|
307
|
+
/>
|
|
308
|
+
)
|
|
309
|
+
})
|
|
310
|
+
|
|
311
|
+
const ValueText = forwardRef<HTMLSpanElement, HTMLAttributes<HTMLSpanElement>>(
|
|
312
|
+
function RangeSliderValueText(props, ref) {
|
|
313
|
+
const { values } = useRangeSliderCtx()
|
|
314
|
+
return (
|
|
315
|
+
<span ref={ref} {...mergeProps(partOnlyAttrs(rangeSliderAnatomy.valueText), props)}>
|
|
316
|
+
{props.children ?? `${values[0]} – ${values[1]}`}
|
|
317
|
+
</span>
|
|
318
|
+
)
|
|
319
|
+
},
|
|
320
|
+
)
|
|
321
|
+
|
|
322
|
+
export const RangeSlider = { Root, Track, Range, Thumb, ValueText }
|
|
@@ -1,15 +1,26 @@
|
|
|
1
1
|
import { partAttrs, scopeAttrs, threadAnatomy } from '@aeon-ui/core'
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
forwardRef,
|
|
4
|
+
type ButtonHTMLAttributes,
|
|
5
|
+
type HTMLAttributes,
|
|
6
|
+
type ReactNode,
|
|
7
|
+
} from 'react'
|
|
3
8
|
import { mergeProps } from '../utils/merge-props.js'
|
|
4
9
|
|
|
10
|
+
/** Base alignment / delivery states. */
|
|
5
11
|
export type ThreadMessageState = 'mine' | 'theirs' | 'pending' | 'failed'
|
|
6
12
|
|
|
13
|
+
/** Structured in-thread faces (space-combinable with mine/theirs). */
|
|
14
|
+
export type ThreadItemFace = 'command-result' | 'payment-card' | 'request-card'
|
|
15
|
+
|
|
7
16
|
export interface ThreadRootProps extends HTMLAttributes<HTMLElement> {
|
|
8
17
|
children?: ReactNode
|
|
9
18
|
}
|
|
10
19
|
|
|
11
20
|
/**
|
|
12
|
-
* Thread — message stream.
|
|
21
|
+
* Thread — message stream.
|
|
22
|
+
* Item `state` projects mine | theirs | pending | failed, optionally plus
|
|
23
|
+
* command-result | payment-card | request-card (e.g. `mine payment-card`).
|
|
13
24
|
*/
|
|
14
25
|
const Root = forwardRef<HTMLElement, ThreadRootProps>(function ThreadRoot(
|
|
15
26
|
{ children, ...rest },
|
|
@@ -40,7 +51,7 @@ const List = forwardRef<HTMLDivElement, HTMLAttributes<HTMLDivElement>>(function
|
|
|
40
51
|
})
|
|
41
52
|
|
|
42
53
|
export interface ThreadItemProps extends HTMLAttributes<HTMLDivElement> {
|
|
43
|
-
state?: ThreadMessageState | `${ThreadMessageState} ${
|
|
54
|
+
state?: ThreadMessageState | ThreadItemFace | `${ThreadMessageState} ${ThreadItemFace}` | string
|
|
44
55
|
}
|
|
45
56
|
|
|
46
57
|
const Item = forwardRef<HTMLDivElement, ThreadItemProps>(function ThreadItem(
|
|
@@ -85,6 +96,61 @@ const Day = forwardRef<HTMLDivElement, HTMLAttributes<HTMLDivElement>>(function
|
|
|
85
96
|
)
|
|
86
97
|
})
|
|
87
98
|
|
|
99
|
+
/** Reply / message-binding control — must be touch-reachable (BRC-218 §4.10). */
|
|
100
|
+
const Bind = forwardRef<HTMLButtonElement, ButtonHTMLAttributes<HTMLButtonElement>>(
|
|
101
|
+
function ThreadBind(props, ref) {
|
|
102
|
+
return (
|
|
103
|
+
<button
|
|
104
|
+
ref={ref}
|
|
105
|
+
type="button"
|
|
106
|
+
{...mergeProps(partAttrs(threadAnatomy.scope, threadAnatomy.bind), props)}
|
|
107
|
+
/>
|
|
108
|
+
)
|
|
109
|
+
},
|
|
110
|
+
)
|
|
111
|
+
|
|
112
|
+
const Card = forwardRef<HTMLDivElement, HTMLAttributes<HTMLDivElement>>(function ThreadCard(
|
|
113
|
+
props,
|
|
114
|
+
ref,
|
|
115
|
+
) {
|
|
116
|
+
return (
|
|
117
|
+
<div ref={ref} {...mergeProps(partAttrs(threadAnatomy.scope, threadAnatomy.card), props)} />
|
|
118
|
+
)
|
|
119
|
+
})
|
|
120
|
+
|
|
121
|
+
const CardTitle = forwardRef<HTMLDivElement, HTMLAttributes<HTMLDivElement>>(
|
|
122
|
+
function ThreadCardTitle(props, ref) {
|
|
123
|
+
return (
|
|
124
|
+
<div
|
|
125
|
+
ref={ref}
|
|
126
|
+
{...mergeProps(partAttrs(threadAnatomy.scope, threadAnatomy.cardTitle), props)}
|
|
127
|
+
/>
|
|
128
|
+
)
|
|
129
|
+
},
|
|
130
|
+
)
|
|
131
|
+
|
|
132
|
+
const CardBody = forwardRef<HTMLDivElement, HTMLAttributes<HTMLDivElement>>(
|
|
133
|
+
function ThreadCardBody(props, ref) {
|
|
134
|
+
return (
|
|
135
|
+
<div
|
|
136
|
+
ref={ref}
|
|
137
|
+
{...mergeProps(partAttrs(threadAnatomy.scope, threadAnatomy.cardBody), props)}
|
|
138
|
+
/>
|
|
139
|
+
)
|
|
140
|
+
},
|
|
141
|
+
)
|
|
142
|
+
|
|
143
|
+
const CardActions = forwardRef<HTMLDivElement, HTMLAttributes<HTMLDivElement>>(
|
|
144
|
+
function ThreadCardActions(props, ref) {
|
|
145
|
+
return (
|
|
146
|
+
<div
|
|
147
|
+
ref={ref}
|
|
148
|
+
{...mergeProps(partAttrs(threadAnatomy.scope, threadAnatomy.cardActions), props)}
|
|
149
|
+
/>
|
|
150
|
+
)
|
|
151
|
+
},
|
|
152
|
+
)
|
|
153
|
+
|
|
88
154
|
export const Thread = {
|
|
89
155
|
Root,
|
|
90
156
|
List,
|
|
@@ -92,4 +158,9 @@ export const Thread = {
|
|
|
92
158
|
Bubble,
|
|
93
159
|
Meta,
|
|
94
160
|
Day,
|
|
161
|
+
Bind,
|
|
162
|
+
Card,
|
|
163
|
+
CardTitle,
|
|
164
|
+
CardBody,
|
|
165
|
+
CardActions,
|
|
95
166
|
}
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import { tooltipAnatomy, partAttrs } from '@aeon-ui/core'
|
|
2
|
+
import { popoverMachine } from '@aeon-ui/primitives'
|
|
2
3
|
import {
|
|
3
4
|
createContext,
|
|
4
5
|
forwardRef,
|
|
6
|
+
useCallback,
|
|
5
7
|
useContext,
|
|
6
8
|
useEffect,
|
|
7
9
|
useId,
|
|
@@ -15,6 +17,7 @@ import {
|
|
|
15
17
|
type ReactNode,
|
|
16
18
|
} from 'react'
|
|
17
19
|
import { FloatingPositioner } from '../floating/FloatingPositioner.js'
|
|
20
|
+
import { useAeonMachine } from '../hooks/use-aeon-machine.js'
|
|
18
21
|
import { useOutsideClick } from '../hooks/use-outside-click.js'
|
|
19
22
|
import { usePrefersHover } from '../hooks/use-prefers-hover.js'
|
|
20
23
|
import { mergeProps } from '../utils/merge-props.js'
|
|
@@ -76,8 +79,10 @@ const Root = forwardRef<HTMLDivElement, TooltipRootProps>(function TooltipRoot(
|
|
|
76
79
|
},
|
|
77
80
|
ref,
|
|
78
81
|
) {
|
|
79
|
-
const [
|
|
80
|
-
|
|
82
|
+
const [snapshot, send] = useAeonMachine(popoverMachine, {
|
|
83
|
+
input: { open: defaultOpen },
|
|
84
|
+
})
|
|
85
|
+
const resolvedOpen = open ?? snapshot.matches('open')
|
|
81
86
|
const showTimer = useRef<ReturnType<typeof setTimeout> | undefined>(undefined)
|
|
82
87
|
const hideTimer = useRef<ReturnType<typeof setTimeout> | undefined>(undefined)
|
|
83
88
|
const triggerRef = useRef<HTMLSpanElement>(null)
|
|
@@ -87,6 +92,10 @@ const Root = forwardRef<HTMLDivElement, TooltipRootProps>(function TooltipRoot(
|
|
|
87
92
|
const prefersHover = usePrefersHover()
|
|
88
93
|
const [outsideClickEnabled, setOutsideClickEnabled] = useState(false)
|
|
89
94
|
|
|
95
|
+
useEffect(() => {
|
|
96
|
+
if (open !== undefined) send({ type: 'SET_OPEN', open })
|
|
97
|
+
}, [open, send])
|
|
98
|
+
|
|
90
99
|
useEffect(() => {
|
|
91
100
|
if (!resolvedOpen || prefersHover) {
|
|
92
101
|
setOutsideClickEnabled(false)
|
|
@@ -97,52 +106,57 @@ const Root = forwardRef<HTMLDivElement, TooltipRootProps>(function TooltipRoot(
|
|
|
97
106
|
return () => window.clearTimeout(id)
|
|
98
107
|
}, [resolvedOpen, prefersHover, touchOutsideGraceMs])
|
|
99
108
|
|
|
100
|
-
const setOpen = (
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
109
|
+
const setOpen = useCallback(
|
|
110
|
+
(next: boolean) => {
|
|
111
|
+
if (open === undefined) send({ type: next ? 'OPEN' : 'CLOSE' })
|
|
112
|
+
onOpenChange?.(next)
|
|
113
|
+
},
|
|
114
|
+
[open, send, onOpenChange],
|
|
115
|
+
)
|
|
104
116
|
|
|
105
117
|
useEffect(() => {
|
|
106
118
|
onOpenChange?.(resolvedOpen)
|
|
107
119
|
}, [resolvedOpen, onOpenChange])
|
|
108
120
|
|
|
109
|
-
const hideNow = () => {
|
|
121
|
+
const hideNow = useCallback(() => {
|
|
110
122
|
if (showTimer.current !== undefined) clearTimeout(showTimer.current)
|
|
111
123
|
if (hideTimer.current !== undefined) clearTimeout(hideTimer.current)
|
|
112
124
|
setOpen(false)
|
|
113
|
-
}
|
|
125
|
+
}, [setOpen])
|
|
114
126
|
|
|
115
|
-
const showImmediately = () => {
|
|
127
|
+
const showImmediately = useCallback(() => {
|
|
116
128
|
if (showTimer.current !== undefined) clearTimeout(showTimer.current)
|
|
117
129
|
if (hideTimer.current !== undefined) clearTimeout(hideTimer.current)
|
|
118
130
|
setOpen(true)
|
|
119
|
-
if (!prefersHover) {
|
|
120
|
-
|
|
121
|
-
hideTimer.current = setTimeout(() => hideNow(), touchDurationMs)
|
|
122
|
-
}
|
|
131
|
+
if (!prefersHover && touchDurationMs > 0) {
|
|
132
|
+
hideTimer.current = setTimeout(() => hideNow(), touchDurationMs)
|
|
123
133
|
}
|
|
124
|
-
}
|
|
134
|
+
}, [setOpen, prefersHover, touchDurationMs, hideNow])
|
|
125
135
|
|
|
126
|
-
const scheduleShow = () => {
|
|
136
|
+
const scheduleShow = useCallback(() => {
|
|
127
137
|
if (hideTimer.current !== undefined) clearTimeout(hideTimer.current)
|
|
128
138
|
showTimer.current = setTimeout(() => setOpen(true), openDelay)
|
|
129
|
-
}
|
|
139
|
+
}, [setOpen, openDelay])
|
|
130
140
|
|
|
131
|
-
const scheduleHide = () => {
|
|
141
|
+
const scheduleHide = useCallback(() => {
|
|
132
142
|
if (showTimer.current !== undefined) clearTimeout(showTimer.current)
|
|
133
143
|
if (closeDelay <= 0) {
|
|
134
144
|
setOpen(false)
|
|
135
145
|
return
|
|
136
146
|
}
|
|
137
147
|
hideTimer.current = setTimeout(() => setOpen(false), closeDelay)
|
|
138
|
-
}
|
|
148
|
+
}, [setOpen, closeDelay])
|
|
139
149
|
|
|
140
|
-
const toggleTouch = () => {
|
|
150
|
+
const toggleTouch = useCallback(() => {
|
|
141
151
|
if (resolvedOpen) hideNow()
|
|
142
152
|
else showImmediately()
|
|
143
|
-
}
|
|
153
|
+
}, [resolvedOpen, hideNow, showImmediately])
|
|
144
154
|
|
|
145
|
-
useOutsideClick(
|
|
155
|
+
useOutsideClick(
|
|
156
|
+
[triggerRef, positionerRef],
|
|
157
|
+
resolvedOpen && !prefersHover && outsideClickEnabled,
|
|
158
|
+
hideNow,
|
|
159
|
+
)
|
|
146
160
|
|
|
147
161
|
const value = useMemo(
|
|
148
162
|
() => ({
|
|
@@ -160,7 +174,19 @@ const Root = forwardRef<HTMLDivElement, TooltipRootProps>(function TooltipRoot(
|
|
|
160
174
|
showOnFocus,
|
|
161
175
|
prefersHover,
|
|
162
176
|
}),
|
|
163
|
-
[
|
|
177
|
+
[
|
|
178
|
+
resolvedOpen,
|
|
179
|
+
setOpen,
|
|
180
|
+
triggerId,
|
|
181
|
+
contentId,
|
|
182
|
+
scheduleShow,
|
|
183
|
+
scheduleHide,
|
|
184
|
+
showImmediately,
|
|
185
|
+
hideNow,
|
|
186
|
+
toggleTouch,
|
|
187
|
+
showOnFocus,
|
|
188
|
+
prefersHover,
|
|
189
|
+
],
|
|
164
190
|
)
|
|
165
191
|
|
|
166
192
|
return (
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Children,
|
|
3
|
+
cloneElement,
|
|
4
|
+
forwardRef,
|
|
5
|
+
isValidElement,
|
|
6
|
+
type HTMLAttributes,
|
|
7
|
+
type ReactElement,
|
|
8
|
+
type ReactNode,
|
|
9
|
+
type Ref,
|
|
10
|
+
} from 'react'
|
|
11
|
+
import { mergeProps } from './merge-props.js'
|
|
12
|
+
|
|
13
|
+
function mergeRefs<T>(...refs: Array<Ref<T> | undefined>) {
|
|
14
|
+
return (node: T) => {
|
|
15
|
+
for (const ref of refs) {
|
|
16
|
+
if (!ref) continue
|
|
17
|
+
if (typeof ref === 'function') ref(node)
|
|
18
|
+
else (ref as { current: T }).current = node
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export type SlotProps = HTMLAttributes<HTMLElement> & {
|
|
24
|
+
children?: ReactNode
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Radix-style Slot — merges Aeon attrs onto a single child element
|
|
29
|
+
* (e.g. Next.js Link) instead of rendering a wrapper.
|
|
30
|
+
*/
|
|
31
|
+
export const Slot = forwardRef<HTMLElement, SlotProps>(function Slot(
|
|
32
|
+
{ children, ...props },
|
|
33
|
+
forwardedRef,
|
|
34
|
+
) {
|
|
35
|
+
const child = Children.only(children)
|
|
36
|
+
if (!isValidElement(child)) {
|
|
37
|
+
throw new Error('Slot expects a single React element child')
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const childElement = child as ReactElement<HTMLAttributes<HTMLElement> & { ref?: Ref<HTMLElement> }>
|
|
41
|
+
const childProps = childElement.props
|
|
42
|
+
|
|
43
|
+
return cloneElement(childElement, {
|
|
44
|
+
...mergeProps(props, childProps),
|
|
45
|
+
ref: mergeRefs(forwardedRef, childProps.ref),
|
|
46
|
+
} as HTMLAttributes<HTMLElement>)
|
|
47
|
+
})
|