@prestack/thread-ui 0.0.1 → 0.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (269) hide show
  1. package/dist/ui/animations/anm_appear.ts +13 -0
  2. package/dist/ui/animations/anm_blur_in.ts +19 -0
  3. package/dist/ui/animations/anm_blur_out.ts +18 -0
  4. package/dist/ui/animations/anm_dismiss.ts +18 -0
  5. package/dist/ui/animations/anm_drop.ts +19 -0
  6. package/dist/ui/animations/anm_fall.ts +18 -0
  7. package/dist/ui/animations/anm_flash.ts +18 -0
  8. package/dist/ui/animations/anm_glow.ts +16 -0
  9. package/dist/ui/animations/anm_lift.ts +16 -0
  10. package/dist/ui/animations/anm_nudge.ts +16 -0
  11. package/dist/ui/animations/anm_pop_in.ts +23 -0
  12. package/dist/ui/animations/anm_pop_out.ts +22 -0
  13. package/dist/ui/animations/anm_press.ts +12 -0
  14. package/dist/ui/animations/anm_pulse.ts +13 -0
  15. package/dist/ui/animations/anm_rise.ts +19 -0
  16. package/dist/ui/animations/anm_shake.ts +13 -0
  17. package/dist/ui/animations/anm_slide_in_left.ts +19 -0
  18. package/dist/ui/animations/anm_slide_in_right.ts +19 -0
  19. package/dist/ui/animations/anm_slide_out_left.ts +18 -0
  20. package/dist/ui/animations/anm_slide_out_right.ts +18 -0
  21. package/dist/ui/animations/anm_spin.ts +12 -0
  22. package/dist/ui/animations/anm_swell.ts +16 -0
  23. package/dist/ui/animations/anm_wobble.ts +24 -0
  24. package/dist/ui/animations/anm_zoom_in.ts +19 -0
  25. package/dist/ui/animations/anm_zoom_out.ts +18 -0
  26. package/dist/ui/animations.ts +317 -0
  27. package/dist/ui/attach.js +13 -4
  28. package/dist/ui/attach.ts +613 -0
  29. package/dist/ui/charts.tsx +74 -0
  30. package/dist/ui/checked.d.ts +2 -2
  31. package/dist/ui/checked.ts +32 -0
  32. package/dist/ui/collector.ts +140 -0
  33. package/dist/ui/compositions.d.ts +10 -1
  34. package/dist/ui/compositions.js +262 -23
  35. package/dist/ui/compositions.ts +640 -0
  36. package/dist/ui/consent.ts +55 -0
  37. package/dist/ui/content.tsx +39 -0
  38. package/dist/ui/contrast.ts +27 -0
  39. package/dist/ui/controls.tsx +30 -0
  40. package/dist/ui/disclosure.tsx +22 -0
  41. package/dist/ui/dismiss-layer.ts +87 -0
  42. package/dist/ui/door.ts +140 -0
  43. package/dist/ui/drag.ts +316 -0
  44. package/dist/ui/elements/accordion.js +2 -2
  45. package/dist/ui/elements/accordion.tsx +47 -0
  46. package/dist/ui/elements/accordion.works.ts +66 -0
  47. package/dist/ui/elements/aspect-ratio.js +1 -1
  48. package/dist/ui/elements/aspect-ratio.tsx +30 -0
  49. package/dist/ui/elements/aspect-ratio.works.ts +5 -0
  50. package/dist/ui/elements/avatar.js +1 -1
  51. package/dist/ui/elements/avatar.tsx +33 -0
  52. package/dist/ui/elements/avatar.works.ts +16 -0
  53. package/dist/ui/elements/badge.js +1 -1
  54. package/dist/ui/elements/badge.tsx +32 -0
  55. package/dist/ui/elements/badge.works.ts +9 -0
  56. package/dist/ui/elements/bars.js +2 -2
  57. package/dist/ui/elements/bars.tsx +49 -0
  58. package/dist/ui/elements/bars.works.ts +29 -0
  59. package/dist/ui/elements/box.js +3 -2
  60. package/dist/ui/elements/box.tsx +35 -0
  61. package/dist/ui/elements/breadcrumb.js +2 -2
  62. package/dist/ui/elements/breadcrumb.tsx +37 -0
  63. package/dist/ui/elements/breadcrumb.works.ts +24 -0
  64. package/dist/ui/elements/button.js +1 -1
  65. package/dist/ui/elements/button.tsx +45 -0
  66. package/dist/ui/elements/button.works.tsx +52 -0
  67. package/dist/ui/elements/column.js +1 -1
  68. package/dist/ui/elements/column.tsx +28 -0
  69. package/dist/ui/elements/consent.js +2 -2
  70. package/dist/ui/elements/consent.tsx +70 -0
  71. package/dist/ui/elements/consent.works.tsx +132 -0
  72. package/dist/ui/elements/donut.js +2 -2
  73. package/dist/ui/elements/donut.tsx +44 -0
  74. package/dist/ui/elements/donut.works.ts +42 -0
  75. package/dist/ui/elements/each.js +1 -1
  76. package/dist/ui/elements/each.tsx +33 -0
  77. package/dist/ui/elements/each.works.tsx +82 -0
  78. package/dist/ui/elements/empty.js +1 -1
  79. package/dist/ui/elements/empty.tsx +32 -0
  80. package/dist/ui/elements/field.js +1 -1
  81. package/dist/ui/elements/field.tsx +44 -0
  82. package/dist/ui/elements/field.works.ts +65 -0
  83. package/dist/ui/elements/funnel.js +2 -2
  84. package/dist/ui/elements/funnel.tsx +49 -0
  85. package/dist/ui/elements/funnel.works.ts +37 -0
  86. package/dist/ui/elements/gauge.js +1 -1
  87. package/dist/ui/elements/gauge.tsx +39 -0
  88. package/dist/ui/elements/gauge.works.ts +42 -0
  89. package/dist/ui/elements/glyph.js +1 -1
  90. package/dist/ui/elements/glyph.tsx +29 -0
  91. package/dist/ui/elements/glyph.works.tsx +42 -0
  92. package/dist/ui/elements/grid.js +1 -1
  93. package/dist/ui/elements/grid.tsx +28 -0
  94. package/dist/ui/elements/heatmap.js +2 -2
  95. package/dist/ui/elements/heatmap.tsx +47 -0
  96. package/dist/ui/elements/heatmap.works.ts +24 -0
  97. package/dist/ui/elements/hit-box.js +1 -1
  98. package/dist/ui/elements/hit-box.tsx +46 -0
  99. package/dist/ui/elements/hit-box.works.tsx +576 -0
  100. package/dist/ui/elements/image.js +1 -1
  101. package/dist/ui/elements/image.tsx +28 -0
  102. package/dist/ui/elements/line-chart.js +2 -2
  103. package/dist/ui/elements/line-chart.tsx +44 -0
  104. package/dist/ui/elements/line-chart.works.ts +41 -0
  105. package/dist/ui/elements/link.js +1 -1
  106. package/dist/ui/elements/link.tsx +37 -0
  107. package/dist/ui/elements/link.works.ts +25 -0
  108. package/dist/ui/elements/media.js +1 -1
  109. package/dist/ui/elements/media.tsx +56 -0
  110. package/dist/ui/elements/menu.js +2 -2
  111. package/dist/ui/elements/menu.tsx +50 -0
  112. package/dist/ui/elements/menu.works.tsx +153 -0
  113. package/dist/ui/elements/money.js +1 -1
  114. package/dist/ui/elements/money.tsx +28 -0
  115. package/dist/ui/elements/money.works.ts +9 -0
  116. package/dist/ui/elements/pagination.js +2 -2
  117. package/dist/ui/elements/pagination.tsx +38 -0
  118. package/dist/ui/elements/pagination.works.ts +20 -0
  119. package/dist/ui/elements/progress.js +1 -1
  120. package/dist/ui/elements/progress.tsx +33 -0
  121. package/dist/ui/elements/progress.works.ts +8 -0
  122. package/dist/ui/elements/radio-group.js +2 -2
  123. package/dist/ui/elements/radio-group.tsx +49 -0
  124. package/dist/ui/elements/radio-group.works.ts +33 -0
  125. package/dist/ui/elements/row.js +1 -1
  126. package/dist/ui/elements/row.tsx +28 -0
  127. package/dist/ui/elements/scatter.js +1 -1
  128. package/dist/ui/elements/scatter.tsx +41 -0
  129. package/dist/ui/elements/scatter.works.ts +38 -0
  130. package/dist/ui/elements/select.js +2 -2
  131. package/dist/ui/elements/select.tsx +42 -0
  132. package/dist/ui/elements/select.works.ts +5 -0
  133. package/dist/ui/elements/separator.js +1 -1
  134. package/dist/ui/elements/separator.tsx +30 -0
  135. package/dist/ui/elements/skeleton.js +1 -1
  136. package/dist/ui/elements/skeleton.tsx +30 -0
  137. package/dist/ui/elements/slider.js +1 -1
  138. package/dist/ui/elements/slider.tsx +33 -0
  139. package/dist/ui/elements/slider.works.ts +5 -0
  140. package/dist/ui/elements/sparkline.js +1 -1
  141. package/dist/ui/elements/sparkline.tsx +36 -0
  142. package/dist/ui/elements/sparkline.works.ts +32 -0
  143. package/dist/ui/elements/spinner.js +1 -1
  144. package/dist/ui/elements/spinner.tsx +32 -0
  145. package/dist/ui/elements/stack.js +1 -1
  146. package/dist/ui/elements/stack.tsx +28 -0
  147. package/dist/ui/elements/stat.js +1 -1
  148. package/dist/ui/elements/stat.tsx +35 -0
  149. package/dist/ui/elements/stat.works.ts +14 -0
  150. package/dist/ui/elements/surface.js +1 -1
  151. package/dist/ui/elements/surface.tsx +35 -0
  152. package/dist/ui/elements/surface.works.ts +9 -0
  153. package/dist/ui/elements/table.js +2 -2
  154. package/dist/ui/elements/table.tsx +61 -0
  155. package/dist/ui/elements/table.works.ts +30 -0
  156. package/dist/ui/elements/tabs.js +2 -2
  157. package/dist/ui/elements/tabs.tsx +47 -0
  158. package/dist/ui/elements/tabs.works.ts +84 -0
  159. package/dist/ui/elements/text.js +1 -1
  160. package/dist/ui/elements/text.tsx +32 -0
  161. package/dist/ui/elements/textarea.js +1 -1
  162. package/dist/ui/elements/textarea.tsx +37 -0
  163. package/dist/ui/elements/textarea.works.ts +5 -0
  164. package/dist/ui/elements/time.js +1 -1
  165. package/dist/ui/elements/time.tsx +34 -0
  166. package/dist/ui/elements/time.works.ts +25 -0
  167. package/dist/ui/elements/toggle.js +1 -1
  168. package/dist/ui/elements/toggle.tsx +33 -0
  169. package/dist/ui/elements/toggle.works.ts +5 -0
  170. package/dist/ui/elements/tooltip.js +1 -1
  171. package/dist/ui/elements/tooltip.tsx +39 -0
  172. package/dist/ui/elements/tooltip.works.ts +77 -0
  173. package/dist/ui/elements/value.js +1 -1
  174. package/dist/ui/elements/value.tsx +30 -0
  175. package/dist/ui/elements/value.works.ts +9 -0
  176. package/dist/ui/elements.ts +127 -0
  177. package/dist/ui/focus-scope.ts +121 -0
  178. package/dist/ui/fonts.ts +355 -0
  179. package/dist/ui/generators/dev.ts +79 -0
  180. package/dist/ui/generators/elements.d.ts +1 -11
  181. package/dist/ui/generators/elements.js +43 -13
  182. package/dist/ui/generators/elements.ts +794 -0
  183. package/dist/ui/generators/i18n.ts +301 -0
  184. package/dist/ui/generators/icons.ts +214 -0
  185. package/dist/ui/generators/react.js +10 -3
  186. package/dist/ui/generators/react.ts +268 -0
  187. package/dist/ui/generators/routes.d.ts +1 -0
  188. package/dist/ui/generators/routes.js +89 -69
  189. package/dist/ui/generators/routes.ts +739 -0
  190. package/dist/ui/generators/show.ts +259 -0
  191. package/dist/ui/generators/styles.js +1 -1
  192. package/dist/ui/generators/styles.ts +670 -0
  193. package/dist/ui/generators/variants.ts +205 -0
  194. package/dist/ui/i18n/catalogs/en.ts +191 -0
  195. package/dist/ui/i18n/format.ts +221 -0
  196. package/dist/ui/i18n/locales.ts +33 -0
  197. package/dist/ui/i18n/parse.ts +315 -0
  198. package/dist/ui/i18n/translator.ts +31 -0
  199. package/dist/ui/i18n/types.ts +96 -0
  200. package/dist/ui/icons.ts +331 -0
  201. package/dist/ui/islands/map.ts +58 -0
  202. package/dist/ui/localizations.ts +75 -0
  203. package/dist/ui/makers/message.ts +292 -0
  204. package/dist/ui/material.ts +236 -0
  205. package/dist/ui/materials/mat_bordered.ts +13 -0
  206. package/dist/ui/materials/mat_ceramic.ts +13 -0
  207. package/dist/ui/materials/mat_clay.ts +13 -0
  208. package/dist/ui/materials/mat_elevated.ts +24 -0
  209. package/dist/ui/materials/mat_flat.ts +34 -0
  210. package/dist/ui/materials/mat_glass.ts +20 -0
  211. package/dist/ui/materials/mat_lit.ts +13 -0
  212. package/dist/ui/materials/mat_moulded.ts +11 -0
  213. package/dist/ui/materials.ts +206 -0
  214. package/dist/ui/modes.ts +44 -0
  215. package/dist/ui/navigation.tsx +32 -0
  216. package/dist/ui/positioning.ts +90 -0
  217. package/dist/ui/presented.tsx +119 -0
  218. package/dist/ui/provider.d.ts +2 -0
  219. package/dist/ui/provider.js +7 -1
  220. package/dist/ui/provider.tsx +182 -0
  221. package/dist/ui/render-loop.ts +133 -0
  222. package/dist/ui/roving-focus.ts +161 -0
  223. package/dist/ui/screens.js +17 -8
  224. package/dist/ui/screens.ts +142 -0
  225. package/dist/ui/scroll-lock.ts +28 -0
  226. package/dist/ui/series.ts +97 -0
  227. package/dist/ui/signals.ts +386 -0
  228. package/dist/ui/styleguide.ts +231 -0
  229. package/dist/ui/styleguides.ts +374 -0
  230. package/dist/ui/themes.ts +22 -0
  231. package/dist/ui/token.js +1 -1
  232. package/dist/ui/token.ts +78 -0
  233. package/dist/ui/tokens/shadow.ts +25 -0
  234. package/dist/ui/tokens.ts +235 -0
  235. package/dist/ui/validators.ts +232 -0
  236. package/dist/ui/variant.d.ts +1 -0
  237. package/dist/ui/variant.js +3 -0
  238. package/dist/ui/variant.ts +352 -0
  239. package/dist/ui/variants/var_avatar-round.ts +14 -0
  240. package/dist/ui/variants/var_bubble-me.ts +14 -0
  241. package/dist/ui/variants/var_bubble-them.ts +13 -0
  242. package/dist/ui/variants/var_button-border.ts +76 -0
  243. package/dist/ui/variants/var_button-cta.ts +76 -0
  244. package/dist/ui/variants/var_button-destructive-ghost.ts +72 -0
  245. package/dist/ui/variants/var_button-destructive.ts +76 -0
  246. package/dist/ui/variants/var_button-fill.ts +76 -0
  247. package/dist/ui/variants/var_button-ghost.ts +72 -0
  248. package/dist/ui/variants/var_button-glass.ts +55 -0
  249. package/dist/ui/variants/var_button-normal.ts +76 -0
  250. package/dist/ui/variants/var_button-quiet.ts +74 -0
  251. package/dist/ui/variants/var_card.ts +17 -0
  252. package/dist/ui/variants/var_chip.ts +13 -0
  253. package/dist/ui/variants/var_choice.ts +15 -0
  254. package/dist/ui/variants/var_framed.ts +10 -0
  255. package/dist/ui/variants/var_glyph-plain.ts +11 -0
  256. package/dist/ui/variants/var_input.ts +43 -0
  257. package/dist/ui/variants/var_link-inline.ts +29 -0
  258. package/dist/ui/variants/var_list-row.ts +34 -0
  259. package/dist/ui/variants/var_rule.ts +10 -0
  260. package/dist/ui/variants/var_skeleton-block.ts +12 -0
  261. package/dist/ui/variants/var_spinner-turning.ts +12 -0
  262. package/dist/ui/variants/var_stat-tile.ts +13 -0
  263. package/dist/ui/variants/var_tab-bar.ts +15 -0
  264. package/dist/ui/variants/var_tap-target.ts +25 -0
  265. package/dist/ui/variants.ts +64 -0
  266. package/dist/ui/wrappers.d.ts +4 -1
  267. package/dist/ui/wrappers.js +60 -20
  268. package/dist/ui/wrappers.tsx +426 -0
  269. package/package.json +8 -2
@@ -0,0 +1,316 @@
1
+
2
+
3
+ export const EDGE_SCROLL_DEFAULTS = {
4
+
5
+ band: 48,
6
+
7
+ speed: 900,
8
+ } as const
9
+
10
+ export interface Subject {
11
+ readonly kind: string
12
+ readonly id: string
13
+ }
14
+
15
+ export interface DragSession {
16
+
17
+ readonly gesture: string
18
+
19
+ readonly subject: readonly Subject[]
20
+
21
+ readonly from: string
22
+
23
+ readonly x: number
24
+ readonly y: number
25
+
26
+ readonly startedAt: number
27
+
28
+ readonly at: number
29
+
30
+ readonly over?: string
31
+ readonly overSince: number
32
+ }
33
+
34
+ let SESSION: DragSession | undefined
35
+
36
+ export function dragSession(): DragSession | undefined {
37
+ return SESSION
38
+ }
39
+
40
+ const LISTENERS = new Set<() => void>()
41
+
42
+ export function subscribeDrag(listener: () => void): () => void {
43
+ LISTENERS.add(listener)
44
+ return () => {
45
+ LISTENERS.delete(listener)
46
+ }
47
+ }
48
+
49
+ function announce(): void {
50
+ for (const listener of LISTENERS) listener()
51
+ }
52
+
53
+ export type DragStatus = 'idle' | 'carrying' | 'over' | 'refusing' | 'elsewhere'
54
+
55
+ export function dragStatusOf(hitBox: string, accepts: readonly string[] | undefined): DragStatus {
56
+ const session = SESSION
57
+ if (session === undefined) return 'idle'
58
+ if (session.from === hitBox) return 'carrying'
59
+ if (session.over !== hitBox) return 'elsewhere'
60
+ return accepted(accepts, session) ? 'over' : 'refusing'
61
+ }
62
+
63
+ export interface Scroller {
64
+
65
+ readonly top: number
66
+ readonly bottom: number
67
+ readonly left: number
68
+ readonly right: number
69
+
70
+ readonly scrollsVertically: boolean
71
+ readonly scrollsHorizontally: boolean
72
+
73
+ readonly scrollBy: (dx: number, dy: number) => void
74
+ }
75
+
76
+ export interface Layout {
77
+
78
+ readonly hitBoxesAt: (x: number, y: number) => readonly string[]
79
+
80
+ readonly scrollersAt: (x: number, y: number) => readonly Scroller[]
81
+ }
82
+
83
+ export const DOM_LAYOUT: Layout = {
84
+ hitBoxesAt: (x, y) => {
85
+ if (typeof document === 'undefined' || typeof document.elementsFromPoint !== 'function') return []
86
+ const found: string[] = []
87
+ for (const el of document.elementsFromPoint(x, y)) {
88
+ const id = el.getAttribute('data-hitbox')
89
+ if (id !== null) found.push(id)
90
+ }
91
+ return found
92
+ },
93
+ scrollersAt: (x, y) => {
94
+ if (typeof document === 'undefined' || typeof document.elementsFromPoint !== 'function') return []
95
+ const found: Scroller[] = []
96
+ for (const el of document.elementsFromPoint(x, y)) {
97
+ if (!(el instanceof HTMLElement)) continue
98
+ const box = el.getBoundingClientRect()
99
+ found.push({
100
+ top: box.top,
101
+ bottom: box.bottom,
102
+ left: box.left,
103
+ right: box.right,
104
+ scrollsVertically: el.scrollHeight > el.clientHeight,
105
+ scrollsHorizontally: el.scrollWidth > el.clientWidth,
106
+ scrollBy: (dx, dy) => {
107
+ el.scrollLeft += dx
108
+ el.scrollTop += dy
109
+ },
110
+ })
111
+ }
112
+ return found
113
+ },
114
+ }
115
+
116
+ let LAYOUT: Layout = DOM_LAYOUT
117
+
118
+ export function setLayout(layout: Layout): () => void {
119
+ const previous = LAYOUT
120
+ LAYOUT = layout
121
+ return () => {
122
+ LAYOUT = previous
123
+ }
124
+ }
125
+
126
+ export function currentLayout(): Layout {
127
+ return LAYOUT
128
+ }
129
+
130
+ let CLAIM: string | undefined
131
+
132
+ export function claimGesture(hitBox: string): boolean {
133
+ if (CLAIM !== undefined && CLAIM !== hitBox) return false
134
+ CLAIM = hitBox
135
+ return true
136
+ }
137
+
138
+ export function releaseGesture(hitBox: string): void {
139
+ if (CLAIM === hitBox) CLAIM = undefined
140
+ }
141
+
142
+ export function gestureClaim(): string | undefined {
143
+ return CLAIM
144
+ }
145
+
146
+ export interface DropTarget {
147
+ readonly id: string
148
+
149
+ readonly accepts?: readonly string[]
150
+
151
+ readonly collects?: () => Subject | readonly Subject[]
152
+
153
+ readonly dwell?: number
154
+
155
+ readonly onDrop?: (session: DragSession) => void
156
+ }
157
+
158
+ const TARGETS = new Map<string, DropTarget>()
159
+
160
+ export function registerTarget(target: DropTarget): () => void {
161
+ TARGETS.set(target.id, target)
162
+ return () => {
163
+ TARGETS.delete(target.id)
164
+ }
165
+ }
166
+
167
+ export function targetFor(id: string): DropTarget | undefined {
168
+ return TARGETS.get(id)
169
+ }
170
+
171
+ export function innermostTarget(stack: readonly string[], session: DragSession | undefined = SESSION): DropTarget | undefined {
172
+ if (session === undefined) return undefined
173
+ for (const id of stack) {
174
+ const target = TARGETS.get(id)
175
+ if (target !== undefined && accepted(target.accepts, session)) return target
176
+ }
177
+ return undefined
178
+ }
179
+
180
+ export interface BeginDrag {
181
+ readonly gesture: string
182
+ readonly subject: Subject | readonly Subject[]
183
+ readonly from: string
184
+ readonly x: number
185
+ readonly y: number
186
+
187
+ readonly t: number
188
+ }
189
+
190
+ export function beginDrag(input: BeginDrag): DragSession {
191
+ if (SESSION !== undefined) {
192
+ throw new Error(
193
+ `a drag from "${SESSION.from}" is already in progress, so "${input.from}" cannot start one. ` +
194
+ 'Two live sessions would both receive moves and the drop would commit whichever wrote last.',
195
+ )
196
+ }
197
+ const subject = Array.isArray(input.subject) ? [...(input.subject as readonly Subject[])] : [input.subject as Subject]
198
+ if (subject.length === 0) throw new Error('a drag carries something — beginDrag was given an empty subject')
199
+
200
+ SESSION = {
201
+ gesture: input.gesture,
202
+ subject,
203
+ from: input.from,
204
+ x: input.x,
205
+ y: input.y,
206
+ startedAt: input.t,
207
+ at: input.t,
208
+ overSince: input.t,
209
+ }
210
+ announce()
211
+ return SESSION
212
+ }
213
+
214
+ export interface MoveDrag {
215
+ readonly x: number
216
+ readonly y: number
217
+
218
+ readonly over?: string
219
+ readonly t: number
220
+ }
221
+
222
+ export function moveDrag(input: MoveDrag): DragSession | undefined {
223
+ if (SESSION === undefined) return undefined
224
+ const arrived = input.over !== SESSION.over
225
+ SESSION = {
226
+ ...SESSION,
227
+ x: input.x,
228
+ y: input.y,
229
+ at: input.t,
230
+ ...(input.over === undefined ? { over: undefined } : { over: input.over }),
231
+ overSince: arrived ? input.t : SESSION.overSince,
232
+ }
233
+ announce()
234
+ return SESSION
235
+ }
236
+
237
+ export function collect(more: Subject | readonly Subject[]): DragSession | undefined {
238
+ if (SESSION === undefined) return undefined
239
+ const added = Array.isArray(more) ? (more as readonly Subject[]) : [more as Subject]
240
+ const have = new Set(SESSION.subject.map((s) => `${s.kind}:${s.id}`))
241
+ const fresh = added.filter((s) => !have.has(`${s.kind}:${s.id}`))
242
+ if (fresh.length === 0) return SESSION
243
+ SESSION = { ...SESSION, subject: [...SESSION.subject, ...fresh] }
244
+ announce()
245
+ return SESSION
246
+ }
247
+
248
+ export function endDrag(): DragSession | undefined {
249
+ const finished = SESSION
250
+ SESSION = undefined
251
+ announce()
252
+ return finished
253
+ }
254
+
255
+ export function resetDrag(): void {
256
+ SESSION = undefined
257
+ CLAIM = undefined
258
+ TARGETS.clear()
259
+ announce()
260
+ }
261
+
262
+ export function dwell(session: DragSession | undefined = SESSION): number {
263
+ if (session?.over === undefined) return 0
264
+ return Math.max(0, session.at - session.overSince)
265
+ }
266
+
267
+ export function accepted(kinds: readonly string[] | undefined, session: DragSession | undefined = SESSION): boolean {
268
+ if (kinds === undefined || session === undefined) return false
269
+ return session.subject.every((s) => kinds.includes(s.kind))
270
+ }
271
+
272
+ export interface EdgeScrollTuning {
273
+ readonly band?: number
274
+ readonly speed?: number
275
+ }
276
+
277
+ export function edgeScroll(
278
+ pointer: number,
279
+ start: number,
280
+ end: number,
281
+ dt: number,
282
+ { band = EDGE_SCROLL_DEFAULTS.band, speed = EDGE_SCROLL_DEFAULTS.speed }: EdgeScrollTuning = {},
283
+ ): number {
284
+ const extent = end - start
285
+ if (extent <= 0 || speed <= 0 || band <= 0 || dt <= 0) return 0
286
+
287
+ const reach = Math.min(band, extent / 2)
288
+
289
+ const intoStart = start + reach - pointer
290
+ if (intoStart > 0) return -Math.min(1, intoStart / reach) * speed * dt
291
+
292
+ const intoEnd = pointer - (end - reach)
293
+ if (intoEnd > 0) return Math.min(1, intoEnd / reach) * speed * dt
294
+
295
+ return 0
296
+ }
297
+
298
+ export function pullEdges(x: number, y: number, dt: number, tuning: EdgeScrollTuning = {}, layout: Layout = LAYOUT): { dx: number; dy: number } {
299
+ for (const scroller of layout.scrollersAt(x, y)) {
300
+ if (scroller.scrollsVertically) {
301
+ const dy = edgeScroll(y, scroller.top, scroller.bottom, dt, tuning)
302
+ if (dy !== 0) {
303
+ scroller.scrollBy(0, dy)
304
+ return { dx: 0, dy }
305
+ }
306
+ }
307
+ if (scroller.scrollsHorizontally) {
308
+ const dx = edgeScroll(x, scroller.left, scroller.right, dt, tuning)
309
+ if (dx !== 0) {
310
+ scroller.scrollBy(dx, 0)
311
+ return { dx, dy: 0 }
312
+ }
313
+ }
314
+ }
315
+ return { dx: 0, dy: 0 }
316
+ }
@@ -2,7 +2,7 @@
2
2
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
3
  import { roleClass } from "../fonts.js";
4
4
  import { element, useSurface } from "../wrappers.js";
5
- import { Each } from "./each.js";
5
+ import { Each } from "../elements/each.js";
6
6
  import { shown, section, header, region, uses } from "./accordion.works.js";
7
7
  export const Accordion = element('Accordion', (props, className) => {
8
8
  const kept = uses(props);
@@ -13,4 +13,4 @@ export const Accordion = element('Accordion', (props, className) => {
13
13
  ],
14
14
  ...(kept.animates === undefined ? {} : { animates: kept.animates }), });
15
15
  return (_jsx("div", { className: surface.className, ...surface.marks, ...surface.invokes, ...surface.a11y, ...(kept.on ?? {}), children: _jsx(Each, { rows: props.items, by: 'id', children: (each0, at) => (_jsxs("div", { ...section(props, kept, each0, at), children: [_jsx("button", { ...header(props, kept, each0, at), children: shown(props, each0, surface, kept).label }), _jsx("div", { ...region(props, kept, each0, at), children: shown(props, each0, surface, kept).inside })] })) }) }));
16
- }, 'e-accordion');
16
+ }, 'e-accordion', { "card": "thread-card" });
@@ -0,0 +1,47 @@
1
+ 'use client'
2
+
3
+ import type { MessageKey } from '../content.tsx'
4
+ import { roleClass } from '../fonts.ts'
5
+ import type { TabItem } from '../disclosure.tsx'
6
+
7
+ import { type Element, element, useSurface } from '../wrappers.tsx'
8
+ import { Each } from '../elements/each.tsx'
9
+ import { shown, section, header, region, uses } from './accordion.works.ts'
10
+
11
+
12
+ export interface AccordionProps {
13
+ readonly items: readonly TabItem[]
14
+ readonly initial: readonly string[]
15
+ readonly exclusive?: boolean
16
+ readonly onChange?: (said: readonly string[]) => void
17
+ readonly dispatch?: string
18
+ readonly input?: Readonly<Record<string, unknown>>
19
+ readonly invokeOn?: 'press' | 'shown'
20
+ }
21
+
22
+ export const Accordion: Element<'Accordion', AccordionProps> = element(
23
+ 'Accordion',
24
+ (props: AccordionProps, className) => {
25
+ const kept = uses(props)
26
+ const surface = useSurface({ className: roleClass(undefined, className),
27
+ invokes: { ...(props.dispatch === undefined ? {} : { dispatch: props.dispatch }), ...(props.input === undefined ? {} : { input: props.input }), ...(props.invokeOn === undefined ? {} : { on: props.invokeOn }) },
28
+ holds: [
29
+ ...(kept.holds ?? []),
30
+ ],
31
+ ...(kept.animates === undefined ? {} : { animates: kept.animates }), })
32
+ return (
33
+ <div className={surface.className} {...surface.marks} {...surface.invokes} {...surface.a11y} {...(kept.on ?? {})}>
34
+ <Each rows={props.items} by={'id'}>
35
+ {(each0, at) => (
36
+ <div {...section(props, kept, each0, at)}>
37
+ <button {...header(props, kept, each0, at)}>{shown(props, each0, surface, kept).label}</button>
38
+ <div {...region(props, kept, each0, at)}>{shown(props, each0, surface, kept).inside}</div>
39
+ </div>
40
+ )}
41
+ </Each>
42
+ </div>
43
+ )
44
+ },
45
+ 'e-accordion',
46
+ {"card":"thread-card"},
47
+ )
@@ -0,0 +1,66 @@
1
+ import { useId, useState, type ReactNode } from 'react'
2
+
3
+ import type { Row } from '../charts.tsx'
4
+ import type { Surface } from '../door.ts'
5
+ import type { AccordionProps } from './accordion.tsx'
6
+
7
+ type Kept = {
8
+ readonly base: string
9
+ readonly open: readonly string[]
10
+ readonly toggle: (id: string) => void
11
+ }
12
+
13
+ export function uses(props: AccordionProps): {
14
+ readonly holds?: readonly string[]
15
+ readonly animates?: boolean
16
+ readonly on?: Readonly<Record<string, unknown>>
17
+ readonly base: string
18
+ readonly open: readonly string[]
19
+ readonly toggle: (id: string) => void
20
+ } {
21
+ const base = useId()
22
+ const [open, setOpen] = useState<readonly string[]>(props.initial ?? [])
23
+ return {
24
+ base,
25
+ open,
26
+ toggle: (id: string): void => {
27
+ const next = open.includes(id)
28
+ ? open.filter((one) => one !== id)
29
+ : props.exclusive === true
30
+ ? [id]
31
+ : [...open, id]
32
+ setOpen(next)
33
+ props.onChange?.(next)
34
+ },
35
+ }
36
+ }
37
+
38
+ export function section(_props: AccordionProps, kept: Kept, row: Row, _at: number): Readonly<Record<string, unknown>> {
39
+ return { 'data-open': kept.open.includes(String(row['id'])) }
40
+ }
41
+
42
+ export function header(_props: AccordionProps, kept: Kept, row: Row, _at: number): Readonly<Record<string, unknown>> {
43
+ const id = String(row['id'])
44
+ return {
45
+ type: 'button',
46
+ id: `${kept.base}-header-${id}`,
47
+ 'aria-controls': `${kept.base}-region-${id}`,
48
+ 'aria-expanded': kept.open.includes(id),
49
+ disabled: row['disabled'] === true,
50
+ onClick: (): void => kept.toggle(id),
51
+ }
52
+ }
53
+
54
+ export function region(_props: AccordionProps, kept: Kept, row: Row, _at: number): Readonly<Record<string, unknown>> {
55
+ const id = String(row['id'])
56
+ return {
57
+ role: 'region',
58
+ id: `${kept.base}-region-${id}`,
59
+ 'aria-labelledby': `${kept.base}-header-${id}`,
60
+ hidden: !kept.open.includes(id),
61
+ }
62
+ }
63
+
64
+ export function shown(_props: AccordionProps, row: Row, surface: Surface, _kept: unknown): Readonly<Record<string, ReactNode>> {
65
+ return { label: surface.text(String(row['labelKey'])), inside: row['content'] as ReactNode }
66
+ }
@@ -6,4 +6,4 @@ export const AspectRatio = element('AspectRatio', (props, className) => {
6
6
  invokes: { ...(props.dispatch === undefined ? {} : { dispatch: props.dispatch }), ...(props.input === undefined ? {} : { input: props.input }), ...(props.invokeOn === undefined ? {} : { on: props.invokeOn }) },
7
7
  measures: { "ratio": String(props.ratio ?? '') }, });
8
8
  return (_jsx("div", { className: surface.className, ...surface.marks, ...surface.invokes, ...surface.a11y, style: surface.measures, children: props.children }));
9
- }, 'e-aspect-ratio');
9
+ }, 'e-aspect-ratio', { "framed": "thread-framed" });
@@ -0,0 +1,30 @@
1
+ 'use client'
2
+
3
+ import type { ReactNode } from 'react'
4
+
5
+ import { type Element, element, useSurface } from '../wrappers.tsx'
6
+
7
+
8
+ export interface AspectRatioProps {
9
+ readonly children: ReactNode
10
+ readonly ratio: number
11
+ readonly dispatch?: string
12
+ readonly input?: Readonly<Record<string, unknown>>
13
+ readonly invokeOn?: 'press' | 'shown'
14
+ }
15
+
16
+ export const AspectRatio: Element<'AspectRatio', AspectRatioProps> = element(
17
+ 'AspectRatio',
18
+ (props: AspectRatioProps, className) => {
19
+ const surface = useSurface({ className,
20
+ invokes: { ...(props.dispatch === undefined ? {} : { dispatch: props.dispatch }), ...(props.input === undefined ? {} : { input: props.input }), ...(props.invokeOn === undefined ? {} : { on: props.invokeOn }) },
21
+ measures: { "ratio": String(props.ratio ?? '') }, })
22
+ return (
23
+ <div className={surface.className} {...surface.marks} {...surface.invokes} {...surface.a11y} style={surface.measures}>
24
+ {props.children}
25
+ </div>
26
+ )
27
+ },
28
+ 'e-aspect-ratio',
29
+ {"framed":"thread-framed"},
30
+ )
@@ -0,0 +1,5 @@
1
+ import type { AspectRatioProps } from './aspect-ratio.tsx'
2
+
3
+ export function measured(props: AspectRatioProps): Readonly<Record<string, string | number>> {
4
+ return { ratio: props.ratio }
5
+ }
@@ -7,4 +7,4 @@ export const Avatar = element('Avatar', (props, className) => {
7
7
  const surface = useSurface({ className: roleClass(undefined, className),
8
8
  invokes: { ...(props.dispatch === undefined ? {} : { dispatch: props.dispatch }), ...(props.input === undefined ? {} : { input: props.input }), ...(props.invokeOn === undefined ? {} : { on: props.invokeOn }) }, });
9
9
  return (_jsxs("span", { className: surface.className, ...surface.marks, ...surface.invokes, ...surface.a11y, role: "img", "aria-label": surface.text(props.labelKey), children: [props.src === undefined ? null : _jsx("img", { src: props.src, alt: "" }), _jsx("span", { children: shown(props, surface).lettering })] }));
10
- }, 'e-avatar');
10
+ }, 'e-avatar', { "avatarRound": "thread-avatar-round" });
@@ -0,0 +1,33 @@
1
+ 'use client'
2
+
3
+ import type { MessageKey } from '../content.tsx'
4
+ import { roleClass } from '../fonts.ts'
5
+
6
+ import { type Element, element, useSurface } from '../wrappers.tsx'
7
+ import { shown } from './avatar.works.ts'
8
+
9
+
10
+ export interface AvatarProps {
11
+ readonly labelKey: MessageKey
12
+ readonly src?: string
13
+ readonly initials?: string
14
+ readonly dispatch?: string
15
+ readonly input?: Readonly<Record<string, unknown>>
16
+ readonly invokeOn?: 'press' | 'shown'
17
+ }
18
+
19
+ export const Avatar: Element<'Avatar', AvatarProps> = element(
20
+ 'Avatar',
21
+ (props: AvatarProps, className) => {
22
+ const surface = useSurface({ className: roleClass(undefined, className),
23
+ invokes: { ...(props.dispatch === undefined ? {} : { dispatch: props.dispatch }), ...(props.input === undefined ? {} : { input: props.input }), ...(props.invokeOn === undefined ? {} : { on: props.invokeOn }) }, })
24
+ return (
25
+ <span className={surface.className} {...surface.marks} {...surface.invokes} {...surface.a11y} role="img" aria-label={surface.text(props.labelKey)}>
26
+ {props.src === undefined ? null : <img src={props.src} alt="" />}
27
+ <span>{shown(props, surface).lettering}</span>
28
+ </span>
29
+ )
30
+ },
31
+ 'e-avatar',
32
+ {"avatarRound":"thread-avatar-round"},
33
+ )
@@ -0,0 +1,16 @@
1
+ import type { AvatarProps } from './avatar.tsx'
2
+
3
+ export function shown(props: AvatarProps, surface: { readonly text: (key: AvatarProps['labelKey']) => string }): Readonly<Record<string, string | null>> {
4
+ if (props.src !== undefined) return { lettering: null }
5
+ const said = props.initials
6
+ if (said !== undefined) return { lettering: said }
7
+ return {
8
+ lettering: surface
9
+ .text(props.labelKey)
10
+ .split(/\s+/)
11
+ .filter(Boolean)
12
+ .slice(0, 2)
13
+ .map((part) => part.charAt(0).toUpperCase())
14
+ .join(''),
15
+ }
16
+ }
@@ -7,4 +7,4 @@ export const Badge = element('Badge', (props, className) => {
7
7
  const surface = useSurface({ className: roleClass(undefined, className),
8
8
  invokes: { ...(props.dispatch === undefined ? {} : { dispatch: props.dispatch }), ...(props.input === undefined ? {} : { input: props.input }), ...(props.invokeOn === undefined ? {} : { on: props.invokeOn }) }, });
9
9
  return (_jsx("span", { className: surface.className, ...surface.marks, ...surface.invokes, ...surface.a11y, children: shows(props, surface) }));
10
- }, 'e-badge');
10
+ }, 'e-badge', { "chip": "thread-chip" });
@@ -0,0 +1,32 @@
1
+ 'use client'
2
+
3
+ import type { MessageKey } from '../content.tsx'
4
+ import { roleClass } from '../fonts.ts'
5
+
6
+ import { type Element, element, useSurface } from '../wrappers.tsx'
7
+ import { shows } from './badge.works.ts'
8
+
9
+
10
+ export interface BadgeProps {
11
+ readonly labelKey: MessageKey
12
+ readonly count?: number
13
+ readonly max?: number
14
+ readonly dispatch?: string
15
+ readonly input?: Readonly<Record<string, unknown>>
16
+ readonly invokeOn?: 'press' | 'shown'
17
+ }
18
+
19
+ export const Badge: Element<'Badge', BadgeProps> = element(
20
+ 'Badge',
21
+ (props: BadgeProps, className) => {
22
+ const surface = useSurface({ className: roleClass(undefined, className),
23
+ invokes: { ...(props.dispatch === undefined ? {} : { dispatch: props.dispatch }), ...(props.input === undefined ? {} : { input: props.input }), ...(props.invokeOn === undefined ? {} : { on: props.invokeOn }) }, })
24
+ return (
25
+ <span className={surface.className} {...surface.marks} {...surface.invokes} {...surface.a11y}>
26
+ {shows(props, surface)}
27
+ </span>
28
+ )
29
+ },
30
+ 'e-badge',
31
+ {"chip":"thread-chip"},
32
+ )
@@ -0,0 +1,9 @@
1
+ import type { ReactNode } from 'react'
2
+
3
+ import type { BadgeProps } from './badge.tsx'
4
+
5
+ export function shows(props: BadgeProps, surface: { readonly text: (key: BadgeProps['labelKey']) => string }): ReactNode {
6
+ const max = props.max ?? 99
7
+ if (props.count === undefined) return surface.text(props.labelKey)
8
+ return props.count > max ? `${max}+` : String(props.count)
9
+ }
@@ -3,7 +3,7 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
3
  import { roleClass } from "../fonts.js";
4
4
  import { measured as marking } from "../charts.js";
5
5
  import { element, useSurface } from "../wrappers.js";
6
- import { Each } from "./each.js";
6
+ import { Each } from "../elements/each.js";
7
7
  import { shown, sized, uses } from "./bars.works.js";
8
8
  export const Bars = element('Bars', (props, className) => {
9
9
  const kept = uses(props);
@@ -14,4 +14,4 @@ export const Bars = element('Bars', (props, className) => {
14
14
  ],
15
15
  ...(kept.animates === undefined ? {} : { animates: kept.animates }), });
16
16
  return (_jsx("div", { className: surface.className, ...surface.marks, ...surface.invokes, ...surface.a11y, ...(kept.on ?? {}), role: "img", ...(props.titleKey === undefined ? {} : { 'aria-label': surface.text(props.titleKey) }), children: _jsx(Each, { rows: props.rows, by: props.by, children: (each0, at) => (_jsxs("span", { style: marking(sized(props, kept, each0, at)), children: [_jsx("span", { children: shown(props, each0, surface, kept).label }), _jsx("span", { children: shown(props, each0, surface, kept).amount })] })) }) }));
17
- }, 'e-bars');
17
+ }, 'e-bars', { "card": "thread-card" });
@@ -0,0 +1,49 @@
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
+ import { measured as marking } from '../charts.tsx'
7
+
8
+ import { type Element, element, useSurface } from '../wrappers.tsx'
9
+ import { Each } from '../elements/each.tsx'
10
+ import { shown, sized, uses } from './bars.works.ts'
11
+
12
+
13
+ export interface BarsProps {
14
+ readonly rows: readonly Row[]
15
+ readonly by: string
16
+ readonly value: string
17
+ readonly format?: string
18
+ readonly titleKey?: MessageKey
19
+ readonly dispatch?: string
20
+ readonly input?: Readonly<Record<string, unknown>>
21
+ readonly invokeOn?: 'press' | 'shown'
22
+ }
23
+
24
+ export const Bars: Element<'Bars', BarsProps> = element(
25
+ 'Bars',
26
+ (props: BarsProps, className) => {
27
+ const kept = uses(props)
28
+ const surface = useSurface({ className: roleClass(undefined, className),
29
+ invokes: { ...(props.dispatch === undefined ? {} : { dispatch: props.dispatch }), ...(props.input === undefined ? {} : { input: props.input }), ...(props.invokeOn === undefined ? {} : { on: props.invokeOn }) },
30
+ holds: [
31
+ ...(kept.holds ?? []),
32
+ ],
33
+ ...(kept.animates === undefined ? {} : { animates: kept.animates }), })
34
+ return (
35
+ <div className={surface.className} {...surface.marks} {...surface.invokes} {...surface.a11y} {...(kept.on ?? {})} role="img" {...(props.titleKey === undefined ? {} : { 'aria-label': surface.text(props.titleKey) })}>
36
+ <Each rows={props.rows} by={props.by}>
37
+ {(each0, at) => (
38
+ <span style={marking(sized(props, kept, each0, at))}>
39
+ <span>{shown(props, each0, surface, kept).label}</span>
40
+ <span>{shown(props, each0, surface, kept).amount}</span>
41
+ </span>
42
+ )}
43
+ </Each>
44
+ </div>
45
+ )
46
+ },
47
+ 'e-bars',
48
+ {"card":"thread-card"},
49
+ )