@prestack/thread-ui 0.1.10 → 0.1.12

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 (209) hide show
  1. package/CHANGELOG.md +375 -0
  2. package/dist/ui/attach.js +6 -7
  3. package/dist/ui/compositions.d.ts +0 -8
  4. package/dist/ui/compositions.js +3 -166
  5. package/dist/ui/generators/routes.js +1 -0
  6. package/package.json +2 -2
  7. package/dist/ui/animations/anm_appear.ts +0 -13
  8. package/dist/ui/animations/anm_blur_in.ts +0 -19
  9. package/dist/ui/animations/anm_blur_out.ts +0 -18
  10. package/dist/ui/animations/anm_dismiss.ts +0 -18
  11. package/dist/ui/animations/anm_drop.ts +0 -19
  12. package/dist/ui/animations/anm_fall.ts +0 -18
  13. package/dist/ui/animations/anm_flash.ts +0 -18
  14. package/dist/ui/animations/anm_glow.ts +0 -16
  15. package/dist/ui/animations/anm_lift.ts +0 -16
  16. package/dist/ui/animations/anm_nudge.ts +0 -16
  17. package/dist/ui/animations/anm_pop_in.ts +0 -23
  18. package/dist/ui/animations/anm_pop_out.ts +0 -22
  19. package/dist/ui/animations/anm_press.ts +0 -12
  20. package/dist/ui/animations/anm_pulse.ts +0 -13
  21. package/dist/ui/animations/anm_rise.ts +0 -19
  22. package/dist/ui/animations/anm_shake.ts +0 -13
  23. package/dist/ui/animations/anm_slide_in_left.ts +0 -19
  24. package/dist/ui/animations/anm_slide_in_right.ts +0 -19
  25. package/dist/ui/animations/anm_slide_out_left.ts +0 -18
  26. package/dist/ui/animations/anm_slide_out_right.ts +0 -18
  27. package/dist/ui/animations/anm_spin.ts +0 -12
  28. package/dist/ui/animations/anm_swell.ts +0 -16
  29. package/dist/ui/animations/anm_wobble.ts +0 -24
  30. package/dist/ui/animations/anm_zoom_in.ts +0 -19
  31. package/dist/ui/animations/anm_zoom_out.ts +0 -18
  32. package/dist/ui/animations.ts +0 -308
  33. package/dist/ui/attach.ts +0 -553
  34. package/dist/ui/charts.tsx +0 -69
  35. package/dist/ui/checked.ts +0 -32
  36. package/dist/ui/collector.ts +0 -140
  37. package/dist/ui/compositions.ts +0 -613
  38. package/dist/ui/consent.ts +0 -55
  39. package/dist/ui/content.tsx +0 -27
  40. package/dist/ui/contrast.ts +0 -27
  41. package/dist/ui/controls.tsx +0 -25
  42. package/dist/ui/disclosure.tsx +0 -20
  43. package/dist/ui/dismiss-layer.ts +0 -87
  44. package/dist/ui/door.ts +0 -140
  45. package/dist/ui/drag.ts +0 -316
  46. package/dist/ui/elements/accordion.tsx +0 -47
  47. package/dist/ui/elements/accordion.works.ts +0 -66
  48. package/dist/ui/elements/aspect-ratio.tsx +0 -31
  49. package/dist/ui/elements/aspect-ratio.works.ts +0 -5
  50. package/dist/ui/elements/avatar.tsx +0 -34
  51. package/dist/ui/elements/avatar.works.ts +0 -16
  52. package/dist/ui/elements/badge.tsx +0 -33
  53. package/dist/ui/elements/badge.works.ts +0 -9
  54. package/dist/ui/elements/bars.tsx +0 -50
  55. package/dist/ui/elements/bars.works.ts +0 -29
  56. package/dist/ui/elements/box.tsx +0 -36
  57. package/dist/ui/elements/breadcrumb.tsx +0 -38
  58. package/dist/ui/elements/breadcrumb.works.ts +0 -24
  59. package/dist/ui/elements/button.tsx +0 -46
  60. package/dist/ui/elements/button.works.tsx +0 -52
  61. package/dist/ui/elements/column.tsx +0 -29
  62. package/dist/ui/elements/consent.tsx +0 -71
  63. package/dist/ui/elements/consent.works.tsx +0 -132
  64. package/dist/ui/elements/donut.tsx +0 -45
  65. package/dist/ui/elements/donut.works.ts +0 -42
  66. package/dist/ui/elements/each.tsx +0 -34
  67. package/dist/ui/elements/each.works.tsx +0 -82
  68. package/dist/ui/elements/empty.tsx +0 -33
  69. package/dist/ui/elements/field.tsx +0 -45
  70. package/dist/ui/elements/field.works.ts +0 -65
  71. package/dist/ui/elements/funnel.tsx +0 -50
  72. package/dist/ui/elements/funnel.works.ts +0 -37
  73. package/dist/ui/elements/gauge.tsx +0 -40
  74. package/dist/ui/elements/gauge.works.ts +0 -42
  75. package/dist/ui/elements/glyph.tsx +0 -30
  76. package/dist/ui/elements/glyph.works.tsx +0 -42
  77. package/dist/ui/elements/grid.tsx +0 -29
  78. package/dist/ui/elements/heatmap.tsx +0 -48
  79. package/dist/ui/elements/heatmap.works.ts +0 -24
  80. package/dist/ui/elements/hit-box.tsx +0 -47
  81. package/dist/ui/elements/hit-box.works.tsx +0 -576
  82. package/dist/ui/elements/image.tsx +0 -29
  83. package/dist/ui/elements/line-chart.tsx +0 -45
  84. package/dist/ui/elements/line-chart.works.ts +0 -41
  85. package/dist/ui/elements/link.tsx +0 -38
  86. package/dist/ui/elements/link.works.ts +0 -25
  87. package/dist/ui/elements/media.tsx +0 -57
  88. package/dist/ui/elements/menu.tsx +0 -51
  89. package/dist/ui/elements/menu.works.tsx +0 -154
  90. package/dist/ui/elements/money.tsx +0 -36
  91. package/dist/ui/elements/money.works.ts +0 -9
  92. package/dist/ui/elements/pagination.tsx +0 -39
  93. package/dist/ui/elements/pagination.works.ts +0 -20
  94. package/dist/ui/elements/progress.tsx +0 -34
  95. package/dist/ui/elements/progress.works.ts +0 -8
  96. package/dist/ui/elements/radio-group.tsx +0 -50
  97. package/dist/ui/elements/radio-group.works.ts +0 -33
  98. package/dist/ui/elements/row.tsx +0 -29
  99. package/dist/ui/elements/scatter.tsx +0 -42
  100. package/dist/ui/elements/scatter.works.ts +0 -38
  101. package/dist/ui/elements/select.tsx +0 -43
  102. package/dist/ui/elements/select.works.ts +0 -5
  103. package/dist/ui/elements/separator.tsx +0 -31
  104. package/dist/ui/elements/skeleton.tsx +0 -31
  105. package/dist/ui/elements/slider.tsx +0 -34
  106. package/dist/ui/elements/slider.works.ts +0 -5
  107. package/dist/ui/elements/sparkline.tsx +0 -37
  108. package/dist/ui/elements/sparkline.works.ts +0 -32
  109. package/dist/ui/elements/spinner.tsx +0 -33
  110. package/dist/ui/elements/stack.tsx +0 -29
  111. package/dist/ui/elements/stat.tsx +0 -36
  112. package/dist/ui/elements/stat.works.ts +0 -14
  113. package/dist/ui/elements/surface.tsx +0 -36
  114. package/dist/ui/elements/surface.works.ts +0 -9
  115. package/dist/ui/elements/table.tsx +0 -62
  116. package/dist/ui/elements/table.works.ts +0 -30
  117. package/dist/ui/elements/tabs.tsx +0 -47
  118. package/dist/ui/elements/tabs.works.ts +0 -84
  119. package/dist/ui/elements/text.tsx +0 -33
  120. package/dist/ui/elements/textarea.tsx +0 -38
  121. package/dist/ui/elements/textarea.works.ts +0 -5
  122. package/dist/ui/elements/time.tsx +0 -40
  123. package/dist/ui/elements/time.works.ts +0 -29
  124. package/dist/ui/elements/toggle.tsx +0 -34
  125. package/dist/ui/elements/toggle.works.ts +0 -5
  126. package/dist/ui/elements/tooltip.tsx +0 -40
  127. package/dist/ui/elements/tooltip.works.ts +0 -77
  128. package/dist/ui/elements/value.tsx +0 -38
  129. package/dist/ui/elements/value.works.ts +0 -9
  130. package/dist/ui/elements.ts +0 -127
  131. package/dist/ui/focus-scope.ts +0 -121
  132. package/dist/ui/fonts.ts +0 -355
  133. package/dist/ui/generators/dev.d.ts +0 -1
  134. package/dist/ui/generators/dev.js +0 -62
  135. package/dist/ui/generators/dev.ts +0 -75
  136. package/dist/ui/generators/elements.ts +0 -796
  137. package/dist/ui/generators/i18n.ts +0 -283
  138. package/dist/ui/generators/icons.ts +0 -207
  139. package/dist/ui/generators/react.ts +0 -267
  140. package/dist/ui/generators/routes.ts +0 -847
  141. package/dist/ui/generators/show.ts +0 -261
  142. package/dist/ui/generators/styles.ts +0 -677
  143. package/dist/ui/generators/variants.ts +0 -196
  144. package/dist/ui/i18n/catalogs/en.ts +0 -191
  145. package/dist/ui/i18n/format.ts +0 -221
  146. package/dist/ui/i18n/locales.ts +0 -33
  147. package/dist/ui/i18n/parse.ts +0 -315
  148. package/dist/ui/i18n/translator.ts +0 -31
  149. package/dist/ui/i18n/types.ts +0 -96
  150. package/dist/ui/icons.ts +0 -331
  151. package/dist/ui/islands/map.ts +0 -58
  152. package/dist/ui/localizations.ts +0 -75
  153. package/dist/ui/material.ts +0 -236
  154. package/dist/ui/materials/mat_bordered.ts +0 -13
  155. package/dist/ui/materials/mat_ceramic.ts +0 -13
  156. package/dist/ui/materials/mat_clay.ts +0 -13
  157. package/dist/ui/materials/mat_elevated.ts +0 -24
  158. package/dist/ui/materials/mat_flat.ts +0 -34
  159. package/dist/ui/materials/mat_glass.ts +0 -20
  160. package/dist/ui/materials/mat_lit.ts +0 -13
  161. package/dist/ui/materials/mat_moulded.ts +0 -11
  162. package/dist/ui/materials.ts +0 -197
  163. package/dist/ui/modes.ts +0 -44
  164. package/dist/ui/navigation.tsx +0 -29
  165. package/dist/ui/positioning.ts +0 -90
  166. package/dist/ui/presented.tsx +0 -119
  167. package/dist/ui/provider.tsx +0 -184
  168. package/dist/ui/render-loop.ts +0 -133
  169. package/dist/ui/roving-focus.ts +0 -161
  170. package/dist/ui/screens.ts +0 -141
  171. package/dist/ui/scroll-lock.ts +0 -28
  172. package/dist/ui/series.ts +0 -97
  173. package/dist/ui/signals.ts +0 -386
  174. package/dist/ui/styleguide.ts +0 -231
  175. package/dist/ui/styleguides.ts +0 -374
  176. package/dist/ui/themes.ts +0 -22
  177. package/dist/ui/token.ts +0 -78
  178. package/dist/ui/tokens/shadow.ts +0 -25
  179. package/dist/ui/tokens.ts +0 -234
  180. package/dist/ui/validators.ts +0 -232
  181. package/dist/ui/variant.ts +0 -349
  182. package/dist/ui/variants/var_avatar-round.ts +0 -14
  183. package/dist/ui/variants/var_bubble-me.ts +0 -14
  184. package/dist/ui/variants/var_bubble-them.ts +0 -13
  185. package/dist/ui/variants/var_button-border.ts +0 -76
  186. package/dist/ui/variants/var_button-cta.ts +0 -76
  187. package/dist/ui/variants/var_button-destructive-ghost.ts +0 -72
  188. package/dist/ui/variants/var_button-destructive.ts +0 -76
  189. package/dist/ui/variants/var_button-fill.ts +0 -76
  190. package/dist/ui/variants/var_button-ghost.ts +0 -72
  191. package/dist/ui/variants/var_button-glass.ts +0 -55
  192. package/dist/ui/variants/var_button-normal.ts +0 -76
  193. package/dist/ui/variants/var_button-quiet.ts +0 -74
  194. package/dist/ui/variants/var_card.ts +0 -17
  195. package/dist/ui/variants/var_chip.ts +0 -13
  196. package/dist/ui/variants/var_choice.ts +0 -15
  197. package/dist/ui/variants/var_framed.ts +0 -10
  198. package/dist/ui/variants/var_glyph-plain.ts +0 -11
  199. package/dist/ui/variants/var_input.ts +0 -43
  200. package/dist/ui/variants/var_link-inline.ts +0 -29
  201. package/dist/ui/variants/var_list-row.ts +0 -34
  202. package/dist/ui/variants/var_rule.ts +0 -10
  203. package/dist/ui/variants/var_skeleton-block.ts +0 -12
  204. package/dist/ui/variants/var_spinner-turning.ts +0 -12
  205. package/dist/ui/variants/var_stat-tile.ts +0 -13
  206. package/dist/ui/variants/var_tab-bar.ts +0 -15
  207. package/dist/ui/variants/var_tap-target.ts +0 -25
  208. package/dist/ui/variants.ts +0 -64
  209. package/dist/ui/wrappers.tsx +0 -429
@@ -1,576 +0,0 @@
1
- 'use client'
2
-
3
- import { gestureFor, gestureNames, holds as declared, recognise, type GesturePhase, type PointerSample } from '../signals.ts'
4
- import { useCallback, useEffect, useId, useRef, useState, useSyncExternalStore, type PointerEvent, type ReactNode } from 'react'
5
- import { createPortal } from 'react-dom'
6
-
7
- import {
8
- accepted,
9
- beginDrag,
10
- claimGesture,
11
- collect,
12
- currentLayout,
13
- dragSession,
14
- dragStatusOf,
15
- dwell,
16
- endDrag,
17
- innermostTarget,
18
- moveDrag,
19
- pullEdges,
20
- registerTarget,
21
- releaseGesture,
22
- subscribeDrag,
23
- type DragSession,
24
- type DragStatus,
25
- type DropTarget,
26
- } from '../drag.ts'
27
- import { measured, placed } from '../charts.tsx'
28
- import { nameOf } from '../token.ts'
29
- import { useSurface, useUi } from '../wrappers.tsx'
30
- import type { Dispatching } from '../checked.ts'
31
- import type { HitBoxProps } from './hit-box.tsx'
32
-
33
- export type Axis = 'x' | 'y' | 'both'
34
-
35
- const held = (row: string): string => `var(${nameOf(row)})`
36
-
37
- const TOUCH_ACTION: Record<Axis, string> = {
38
- x: 'touch-down',
39
- y: 'touch-across',
40
- both: 'touch-own',
41
- }
42
-
43
- const wallClock = (): number => (typeof performance === 'object' ? performance.now() : Date.now()) / 1000
44
-
45
- const settleMs = (): number => {
46
- if (typeof document !== 'object') return 0
47
- const said = getComputedStyle(document.documentElement).getPropertyValue('--motion-normal').trim()
48
- const ms = /^([\d.]+)ms$/.exec(said)
49
- if (ms !== null) return Number(ms[1])
50
- const seconds = /^([\d.]+)s$/.exec(said)
51
- return seconds === null ? 0 : Number(seconds[1]) * 1000
52
- }
53
-
54
- const SWING_AT = 220
55
- const SWING_MAX = 12
56
-
57
- const STIFFNESS = 180
58
- const DAMPING = 14
59
- const SETTLED = 0.05
60
-
61
- const EMPTY: Readonly<Record<string, number>> = {}
62
-
63
- interface Tracked {
64
- readonly id: number
65
- x: number
66
- y: number
67
- readonly startX: number
68
- readonly startY: number
69
- readonly startT: number
70
- down: boolean
71
- }
72
-
73
- export function uses(props: HitBoxProps) {
74
- const id = useId()
75
- const { edgeScroll: pull } = useUi()
76
-
77
- const claimed = gestureFor(props.gesture)
78
- if (claimed === undefined) {
79
- throw new Error(
80
- `<HitBox gesture="${props.gesture}"> — nothing registered that gesture, so this region would ` +
81
- `take the pointer and do nothing. Registered: ${gestureNames().join(' · ')}. Contribute one with defineGesture.`,
82
- )
83
- }
84
-
85
- const [down, setDown] = useState(false)
86
- const [phase, setPhase] = useState<GesturePhase>('idle')
87
- const [produced, setProduced] = useState<Readonly<Record<string, number>>>({})
88
-
89
- const released = useRef(Number.NEGATIVE_INFINITY)
90
-
91
- const took = useRef<{
92
- readonly width: number
93
- readonly height: number
94
- readonly gripX: number
95
- readonly gripY: number
96
- } | null>(null)
97
-
98
- const [pointer, setPointer] = useState<{ readonly x: number; readonly y: number } | null>(null)
99
-
100
- const [settling, setSettling] = useState(false)
101
-
102
- const speed = useRef(0)
103
- const moved = useRef(0)
104
-
105
- const [lean, setLean] = useState(0)
106
- const leanSpeed = useRef(0)
107
- const lastTick = useRef(0)
108
-
109
- const grabbed = useRef(props.grabs)
110
- grabbed.current = props.grabs
111
- const grabsKey = props.grabs === undefined ? '' : JSON.stringify(props.grabs)
112
-
113
- const tuned = useRef(props.tuning)
114
- tuned.current = props.tuning
115
- const tuningKey = props.tuning === undefined ? '' : JSON.stringify(props.tuning)
116
-
117
- const node = useRef<HTMLDivElement | null>(null)
118
-
119
- const clockNow = useRef(0)
120
-
121
- const wants = props.gesture === 'pinch' || props.gesture === 'rotate'
122
- const synthesised = useRef<number | null>(null)
123
-
124
- useEffect(() => {
125
- const surfaceNode = node.current
126
- if (!wants || surfaceNode === null) return undefined
127
-
128
- const SPAN = 100
129
-
130
- const put = (scale: number, radians: number): void => {
131
- const box = surfaceNode.getBoundingClientRect()
132
- const cx = box.left + box.width / 2
133
- const cy = box.top + box.height / 2
134
- const reach = (SPAN / 2) * scale
135
- const dxr = Math.cos(radians) * reach
136
- const dyr = Math.sin(radians) * reach
137
-
138
- tracked.current.set(-1, { id: -1, x: cx + dxr, y: cy + dyr, startX: cx + SPAN / 2, startY: cy, startT: clockNow.current, down: true })
139
- tracked.current.set(-2, { id: -2, x: cx - dxr, y: cy - dyr, startX: cx - SPAN / 2, startY: cy, startT: clockNow.current, down: true })
140
- at.current = { x: cx, y: cy }
141
- setDown(true)
142
-
143
- if (synthesised.current !== null) clearTimeout(synthesised.current)
144
- synthesised.current = window.setTimeout(() => {
145
- tracked.current.delete(-1)
146
- tracked.current.delete(-2)
147
- setDown(false)
148
- releaseGesture(id)
149
- setPhase('idle')
150
- setProduced(EMPTY)
151
- }, 140)
152
- }
153
-
154
- let scale = 1
155
- let radians = 0
156
-
157
- const onWheel = (e: WheelEvent): void => {
158
- if (!e.ctrlKey) return
159
- e.preventDefault()
160
- scale = Math.min(4, Math.max(0.25, scale * Math.exp(-e.deltaY / 100)))
161
- put(scale, radians)
162
- }
163
-
164
- const onGesture = (e: Event): void => {
165
- const held = e as Event & { scale?: number; rotation?: number }
166
- e.preventDefault()
167
- scale = held.scale ?? scale
168
- radians = ((held.rotation ?? 0) * Math.PI) / 180
169
- put(scale, radians)
170
- }
171
-
172
- surfaceNode.addEventListener('wheel', onWheel, { passive: false })
173
- surfaceNode.addEventListener('gesturechange', onGesture)
174
- return () => {
175
- surfaceNode.removeEventListener('wheel', onWheel)
176
- surfaceNode.removeEventListener('gesturechange', onGesture)
177
- if (synthesised.current !== null) clearTimeout(synthesised.current)
178
- }
179
- }, [wants, id])
180
-
181
- const tracked = useRef(new Map<number, Tracked>())
182
- const at = useRef({ x: 0, y: 0 })
183
- const lastFrame = useRef(0)
184
-
185
- const key = props.accepts?.join(',') ?? ''
186
- const status = useSyncExternalStore(
187
- subscribeDrag,
188
- useCallback(() => dragStatusOf(id, key === '' ? undefined : key.split(',')), [id, key]),
189
- useCallback((): DragStatus => 'idle', []),
190
- )
191
-
192
- const carrying = status === 'carrying'
193
- const raises = claimed.raises
194
-
195
- return {
196
- on: {},
197
- pull,
198
- animates: down || carrying || settling,
199
- holds: [
200
-
201
- ...(phase === 'active' || phase === 'done'
202
- ? declared.filter((one) => raises.some((s) => s.name === one.id)).map((one) => one.id)
203
- : []),
204
- ...(carrying ? (['carrying'] as const) : []),
205
- ...(status === 'over' && dwell() >= (props.dwell ?? 0) ? (['draggedOver'] as const) : []),
206
- ...(status === 'refusing' ? (['refusing'] as const) : []),
207
- ],
208
- id,
209
- claimed,
210
- down,
211
- setDown,
212
- phase,
213
- setPhase,
214
- produced,
215
- setProduced,
216
- released,
217
- took,
218
- pointer,
219
- setPointer,
220
- settling,
221
- setSettling,
222
- speed,
223
- moved,
224
- lean,
225
- setLean,
226
- leanSpeed,
227
- lastTick,
228
- grabbed,
229
- grabsKey,
230
- tuned,
231
- tuningKey,
232
- node,
233
- clockNow,
234
- wants,
235
- synthesised,
236
- tracked,
237
- at,
238
- lastFrame,
239
- key,
240
- status,
241
- carrying,
242
- raises,
243
- }
244
- }
245
-
246
- export function wires(props: HitBoxProps, kept: ReturnType<typeof uses>, surface: ReturnType<typeof useSurface>) {
247
- const { dispatch: invoke } = useUi()
248
- const { pull, id, down, setDown, phase, setPhase, setProduced, released, took, pointer, setPointer, settling, setSettling, speed, moved, lean, setLean, leanSpeed, lastTick, grabbed, grabsKey, tuned, tuningKey, node, clockNow, tracked, at, lastFrame, key, carrying } = kept
249
-
250
- useEffect(() => {
251
- if (!down) return undefined
252
- const give = (e: globalThis.PointerEvent): void => {
253
-
254
- const session = dragSession()
255
- if (session?.from === id) {
256
- const target = innermostTarget(currentLayout().hitBoxesAt(e.clientX, e.clientY))
257
- const finished = endDrag()
258
- if (target?.onDrop !== undefined && finished !== undefined && accepted(target.accepts, finished)) {
259
- target.onDrop(finished)
260
- }
261
- }
262
- tracked.current.clear()
263
- setDown(false)
264
- setPointer(null)
265
- took.current = null
266
- releaseGesture(id)
267
-
268
- setPhase('idle')
269
- }
270
- const follow = (e: globalThis.PointerEvent): void => {
271
- advance(e.pointerId, e.clientX, e.clientY)
272
- }
273
- window.addEventListener('pointermove', follow)
274
- window.addEventListener('pointerup', give)
275
- window.addEventListener('pointercancel', give)
276
- return () => {
277
- window.removeEventListener('pointermove', follow)
278
- window.removeEventListener('pointerup', give)
279
- window.removeEventListener('pointercancel', give)
280
- }
281
- }, [down, id])
282
-
283
- useEffect(
284
- () => () => {
285
- releaseGesture(id)
286
- if (dragSession()?.from === id) endDrag()
287
- },
288
- [id],
289
- )
290
-
291
- const onDrop = props.onDrop
292
- const named: keyof Dispatching | undefined = props.dispatch
293
- const carriedInput = props.input
294
- const collects = props.collects
295
- useEffect(() => {
296
- if (props.accepts === undefined) return undefined
297
- const target: DropTarget = {
298
- id,
299
- accepts: key.split(','),
300
- ...(props.dwell === undefined ? {} : { dwell: props.dwell }),
301
- ...(collects === undefined ? {} : { collects: () => collects }),
302
- ...(onDrop === undefined && named === undefined
303
- ? {}
304
- : {
305
- onDrop: (finished: DragSession) => {
306
- if (onDrop !== undefined) onDrop()
307
- if (named === undefined) return
308
- if (invoke === undefined) throw new Error(`\`${named}\` cannot be invoked from here — no dispatch is wired`)
309
- void invoke(named, { ...(carriedInput ?? {}), ...(finished.subject[0] ?? {}) })
310
- },
311
- }),
312
- }
313
- return registerTarget(target)
314
- }, [id, key, props.accepts, props.dwell, collects, onDrop, named, carriedInput, invoke])
315
-
316
- const t = surface.t
317
- clockNow.current = t
318
- useEffect(() => {
319
- if (!down) return
320
- const dt = Math.max(0, t - lastFrame.current)
321
- lastFrame.current = t
322
-
323
- const pointers: PointerSample[] = [...tracked.current.values()].map((p) => ({
324
- id: p.id,
325
- x: p.x,
326
- y: p.y,
327
- startX: p.startX,
328
- startY: p.startY,
329
- held: t - p.startT,
330
- down: p.down,
331
- }))
332
-
333
- const result = recognise(props.gesture, {
334
- pointers,
335
- t,
336
- params: tuned.current ?? {},
337
-
338
- sinceRelease: wallClock() - released.current,
339
- })
340
-
341
- const takesPointer = grabbed.current !== undefined
342
- const mine = result.phase === 'idle' || !takesPointer ? true : claimGesture(id)
343
- setPhase(mine ? result.phase : 'idle')
344
-
345
- setProduced(mine && result.values !== undefined ? result.values : EMPTY)
346
- if (!mine) return
347
-
348
- const session = dragSession()
349
-
350
- if (result.phase !== 'idle' && grabbed.current !== undefined && session === undefined) {
351
- beginDrag({ gesture: props.gesture, subject: grabbed.current, from: id, x: at.current.x, y: at.current.y, t })
352
- return
353
- }
354
-
355
- if (session === undefined || session.from !== id) return
356
-
357
- const target = innermostTarget(currentLayout().hitBoxesAt(at.current.x, at.current.y))
358
- moveDrag({ x: at.current.x, y: at.current.y, ...(target === undefined ? {} : { over: target.id }), t })
359
-
360
- if (target?.collects !== undefined && dwell() >= (target.dwell ?? 0)) collect(target.collects())
361
-
362
- pullEdges(at.current.x, at.current.y, dt, pull ?? {})
363
-
364
- }, [down, t, pointer, id, props.gesture, grabsKey, tuningKey, pull])
365
-
366
- useEffect(() => {
367
- if (phase !== 'done') return undefined
368
- const frame = requestAnimationFrame(() => {
369
- releaseGesture(id)
370
- setPhase('idle')
371
- setProduced(EMPTY)
372
- })
373
- return () => cancelAnimationFrame(frame)
374
- }, [phase, id])
375
-
376
- const onPointerDown = (e: PointerEvent<HTMLDivElement>): void => {
377
-
378
- if (grabbed.current !== undefined) {
379
- e.currentTarget.setPointerCapture?.(e.pointerId)
380
-
381
- e.preventDefault()
382
- }
383
-
384
- const box = e.currentTarget.getBoundingClientRect()
385
- setPointer({ x: e.clientX, y: e.clientY })
386
- took.current =
387
- box.width > 0 && box.height > 0
388
- ? { width: box.width, height: box.height, gripX: e.clientX - box.left, gripY: e.clientY - box.top }
389
- : null
390
- speed.current = 0
391
- moved.current = wallClock()
392
- tracked.current.set(e.pointerId, { id: e.pointerId, x: e.clientX, y: e.clientY, startX: e.clientX, startY: e.clientY, startT: t, down: true })
393
- at.current = { x: e.clientX, y: e.clientY }
394
- lastFrame.current = t
395
- setDown(true)
396
- }
397
-
398
- const advance = (pointerId: number, x: number, y: number): void => {
399
- const p = tracked.current.get(pointerId)
400
- if (p === undefined) return
401
- if (p.x === x && p.y === y) return
402
- const e = { pointerId, clientX: x, clientY: y }
403
-
404
- const at2 = wallClock()
405
- const dt = Math.max(1 / 240, at2 - moved.current)
406
- const raw = (e.clientX - p.x) / dt
407
- speed.current = speed.current * 0.7 + raw * 0.3
408
- moved.current = at2
409
-
410
- p.x = e.clientX
411
- p.y = e.clientY
412
- at.current = { x: e.clientX, y: e.clientY }
413
- setPointer({ x: e.clientX, y: e.clientY })
414
- }
415
-
416
- const onPointerMove = (e: PointerEvent<HTMLDivElement>): void => {
417
- advance(e.pointerId, e.clientX, e.clientY)
418
- }
419
-
420
- const finish = (e: PointerEvent<HTMLDivElement>, cancelled: boolean): void => {
421
- const p = tracked.current.get(e.pointerId)
422
- if (p !== undefined) {
423
-
424
- p.x = e.clientX
425
- p.y = e.clientY
426
- p.down = false
427
- at.current = { x: e.clientX, y: e.clientY }
428
- }
429
-
430
- const session = dragSession()
431
- if (session?.from === id) {
432
-
433
- const target = cancelled ? undefined : innermostTarget(currentLayout().hitBoxesAt(e.clientX, e.clientY))
434
- const finished = endDrag()
435
- if (target?.onDrop !== undefined && finished !== undefined && accepted(target.accepts, finished)) {
436
- target.onDrop(finished)
437
- }
438
-
439
- releaseGesture(id)
440
-
441
- speed.current = 0
442
- }
443
-
444
- const last = p !== undefined && tracked.current.size === 1 && !cancelled ? p : undefined
445
- let completed = false
446
- if (last !== undefined) {
447
- const result = recognise(props.gesture, {
448
- pointers: [
449
- { id: last.id, x: last.x, y: last.y, startX: last.startX, startY: last.startY, held: t - last.startT, down: false },
450
- ],
451
- t,
452
- params: props.tuning ?? {},
453
- sinceRelease: wallClock() - released.current,
454
- })
455
- if (result.phase !== 'idle' && claimGesture(id)) {
456
- setPhase(result.phase)
457
- setProduced(result.values ?? EMPTY)
458
- completed = true
459
- }
460
- }
461
-
462
- tracked.current.delete(e.pointerId)
463
- if (tracked.current.size === 0) {
464
- setDown(false)
465
-
466
- releaseGesture(id)
467
- setPointer(null)
468
- took.current = null
469
- if (!completed) setPhase('idle')
470
-
471
- if (!cancelled) released.current = wallClock()
472
- }
473
- }
474
-
475
- const session = dragSession()
476
- const mine = carrying && session !== undefined && session.from === id
477
-
478
- useEffect(() => {
479
- if (!mine && !settling) {
480
- lastTick.current = t
481
- return
482
- }
483
- const step = Math.min(1 / 20, Math.max(0, t - lastTick.current))
484
- lastTick.current = t
485
- if (step === 0) return
486
-
487
- const target = mine ? Math.max(-SWING_MAX, Math.min(SWING_MAX, speed.current / SWING_AT)) : 0
488
- const force = -STIFFNESS * (lean - target) - DAMPING * leanSpeed.current
489
- const next = lean + leanSpeed.current * step
490
- leanSpeed.current += force * step
491
-
492
- if (!mine && Math.abs(next) < SETTLED && Math.abs(leanSpeed.current) < SETTLED) {
493
- leanSpeed.current = 0
494
- if (lean !== 0) setLean(0)
495
- return
496
- }
497
- setLean(next)
498
- }, [t, mine, settling, lean])
499
-
500
- useEffect(() => {
501
- if (mine) {
502
- setSettling(true)
503
- return undefined
504
- }
505
- if (!settling) return undefined
506
-
507
- if (Math.abs(lean) < SETTLED && Math.abs(leanSpeed.current) < SETTLED) {
508
- const timer = setTimeout(() => setSettling(false), settleMs())
509
- return () => clearTimeout(timer)
510
- }
511
- return undefined
512
- }, [mine, settling, lean])
513
-
514
- const swing = lean
515
-
516
- useEffect(() => {
517
- if (!mine) return undefined
518
- const root = document.documentElement
519
- const was = root.style.userSelect
520
- root.style.userSelect = held('picking-off')
521
- return () => {
522
- root.style.userSelect = was
523
- }
524
- }, [mine])
525
-
526
- const travels = props.moves !== false
527
-
528
- const flying = mine && travels && took.current !== null && pointer !== null
529
-
530
- const layer =
531
- flying && took.current !== null && pointer !== null ? (
532
-
533
- <div
534
- aria-hidden
535
- className={placed('hit-box', 'carried')}
536
- style={measured({
537
- width: `${String(took.current.width)}px`,
538
- height: `${String(took.current.height)}px`,
539
- carry:
540
- `translate3d(${String(pointer.x - took.current.gripX)}px, ${String(pointer.y - took.current.gripY)}px, 0)` +
541
- ' var(--transform-carried, scale(1)) var(--transform-carried-turn, rotate(0deg))' +
542
- ` rotate(${swing.toFixed(2)}deg)`,
543
- grip: `${took.current.gripX.toFixed(1)}px ${took.current.gripY.toFixed(1)}px`,
544
- })}
545
- >
546
- {props.children}
547
- </div>
548
- ) : null
549
-
550
- return {
551
- on: {
552
- ref: node,
553
- 'data-hitbox': id,
554
- style: measured({
555
- touch: held(props.grabs === undefined ? 'touch-page' : (TOUCH_ACTION[(props.axis ?? 'both') as Axis] ?? 'touch-own')),
556
- pick: held(props.grabs === undefined ? 'picking-open' : 'picking-off'),
557
- shown: held(flying ? 'visibility-hidden' : 'visibility-shown'),
558
- flow: held(flying ? 'position-lifted' : 'position-flow'),
559
- }),
560
- onPointerDown,
561
- onPointerMove,
562
- onPointerUp: (e: PointerEvent<HTMLDivElement>) => finish(e, false),
563
- onPointerCancel: (e: PointerEvent<HTMLDivElement>) => finish(e, true),
564
- },
565
- layer,
566
- }
567
- }
568
-
569
- export function shown(
570
- _props: HitBoxProps,
571
- _surface: ReturnType<typeof useSurface>,
572
- _kept: ReturnType<typeof uses>,
573
- wired: ReturnType<typeof wires>,
574
- ): Readonly<Record<string, ReactNode>> {
575
- return { carried: wired.layer === null ? null : createPortal(wired.layer, document.body) }
576
- }
@@ -1,29 +0,0 @@
1
- 'use client'
2
-
3
- import type { MessageKey } from '../content.tsx'
4
- import { roleClass } from '../fonts.ts'
5
-
6
- import type { Dispatching } from '../checked.ts'
7
- import { type Element, element, useSurface } from '../wrappers.tsx'
8
-
9
-
10
- export interface ImageProps {
11
- readonly src: string
12
- readonly altKey: MessageKey
13
- readonly dispatch?: keyof Dispatching
14
- readonly input?: Readonly<Record<string, unknown>>
15
- readonly invokeOn?: 'press' | 'shown'
16
- }
17
-
18
- export const Image: Element<'Image', ImageProps> = element(
19
- 'Image',
20
- (props: ImageProps, className) => {
21
- const surface = useSurface({ className: roleClass(undefined, className),
22
- invokes: { ...(props.dispatch === undefined ? {} : { dispatch: props.dispatch }), ...(props.input === undefined ? {} : { input: props.input }), ...(props.invokeOn === undefined ? {} : { on: props.invokeOn }) }, })
23
- return (
24
- <img className={surface.className} {...surface.marks} {...surface.invokes} {...surface.a11y} src={props.src} alt={surface.text(props.altKey)} />
25
- )
26
- },
27
- 'e-image',
28
- {"framed":"thread-framed"},
29
- )
@@ -1,45 +0,0 @@
1
- 'use client'
2
-
3
- import type { MessageKey } from '../content.tsx'
4
- import type { Row } from '../charts.tsx'
5
- import { roleClass } from '../fonts.ts'
6
-
7
- import type { Dispatching } from '../checked.ts'
8
- import { type Element, element, useSurface } from '../wrappers.tsx'
9
- import { Each } from '../elements/each.tsx'
10
- import { drawing, lines, uses } from './line-chart.works.ts'
11
-
12
-
13
- export interface LineChartProps {
14
- readonly rows: readonly Row[]
15
- readonly by?: string
16
- readonly value: string
17
- readonly titleKey?: MessageKey
18
- readonly dispatch?: keyof Dispatching
19
- readonly input?: Readonly<Record<string, unknown>>
20
- readonly invokeOn?: 'press' | 'shown'
21
- }
22
-
23
- export const LineChart: Element<'LineChart', LineChartProps> = element(
24
- 'LineChart',
25
- (props: LineChartProps, className) => {
26
- const kept = uses(props)
27
- const surface = useSurface({ className: roleClass(undefined, className),
28
- invokes: { ...(props.dispatch === undefined ? {} : { dispatch: props.dispatch }), ...(props.input === undefined ? {} : { input: props.input }), ...(props.invokeOn === undefined ? {} : { on: props.invokeOn }) },
29
- holds: [
30
- ...(kept.holds ?? []),
31
- ],
32
- ...(kept.animates === undefined ? {} : { animates: kept.animates }), })
33
- return (
34
- <svg className={surface.className} {...surface.marks} {...surface.invokes} {...surface.a11y} {...(kept.on ?? {})} role="img" {...(props.titleKey === undefined ? {} : { 'aria-label': surface.text(props.titleKey) })}>
35
- <Each rows={lines(props, kept)} by={'group'}>
36
- {(each0, at) => (
37
- <polyline {...drawing(props, kept, each0, at)}></polyline>
38
- )}
39
- </Each>
40
- </svg>
41
- )
42
- },
43
- 'e-line-chart',
44
- {"card":"thread-card"},
45
- )
@@ -1,41 +0,0 @@
1
- import { seriesColour } from '../series.ts'
2
- import { num, polyline, str, useRamp, type Row } from '../charts.tsx'
3
- import type { LineChartProps } from './line-chart.tsx'
4
-
5
- const WIDE = 300
6
- const TALL = 120
7
-
8
- export function lines(props: LineChartProps, _kept?: unknown): readonly Row[] {
9
- if (props.by === undefined) return [{ group: '' }]
10
- return [...new Set(props.rows.map((one: Row) => str(one, props.by as string)))].map((group) => ({ group }))
11
- }
12
-
13
- export function uses(props: LineChartProps): {
14
- readonly holds?: readonly string[]
15
- readonly animates?: boolean
16
- readonly on?: Readonly<Record<string, unknown>>
17
- readonly ramp: readonly string[]
18
- } {
19
- const ramp = useRamp(lines(props).length)
20
- return {
21
- ...(props.rows.length === 0 ? { holds: ['empty'] as const } : {}),
22
- on: { viewBox: `0 0 ${String(WIDE)} ${String(TALL)}` },
23
- ramp,
24
- }
25
- }
26
-
27
- export function drawing(
28
- props: LineChartProps,
29
- kept: { readonly ramp: readonly string[] },
30
- row: Row,
31
- at: number,
32
- ): Readonly<Record<string, string | number>> {
33
- const group = str(row, 'group')
34
- const mine = props.rows.filter((one: Row) => props.by === undefined || str(one, props.by) === group)
35
- return {
36
- points: polyline(mine.map((one: Row) => num(one, props.value)), WIDE, TALL),
37
- fill: 'none',
38
- stroke: seriesColour(kept.ramp, at),
39
- strokeWidth: 2,
40
- }
41
- }