@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,82 @@
1
+ import { Fragment, useLayoutEffect, useRef, type ReactNode } from 'react'
2
+
3
+ import type { Row } from '../charts.tsx'
4
+ import { nameOf } from '../token.ts'
5
+ import type { EachProps } from './each.tsx'
6
+
7
+ function keyOf(row: object, by: string, at: number): string {
8
+ const value = (row as Record<string, unknown>)[by]
9
+ if (value === undefined || value === null) {
10
+ throw new Error(
11
+ `Each: row ${String(at)} has no value for "${by}", so it has no key. React reuses DOM by key, and a ` +
12
+ "row without one is matched by position — which silently keeps the previous row's state when " +
13
+ 'the list reorders. Name the field that identifies a row in this view.',
14
+ )
15
+ }
16
+ return String(value)
17
+ }
18
+
19
+ function following(marker: HTMLElement): readonly HTMLElement[] {
20
+ const found: HTMLElement[] = []
21
+ let node = marker.nextElementSibling
22
+ while (node !== null) {
23
+ found.push(node as HTMLElement)
24
+ node = node.nextElementSibling
25
+ }
26
+ return found
27
+ }
28
+
29
+ function boxes(marker: HTMLElement, ids: readonly string[]): Map<string, DOMRect> {
30
+ const found = new Map<string, DOMRect>()
31
+ following(marker).forEach((node, at) => {
32
+ const id = ids[at]
33
+ if (id !== undefined) found.set(id, node.getBoundingClientRect())
34
+ })
35
+ return found
36
+ }
37
+
38
+ function settle(marker: HTMLElement, ids: readonly string[], was: Map<string, DOMRect>, over: string, paced: string): void {
39
+ following(marker).forEach((node, at) => {
40
+ const id = ids[at]
41
+ if (id === undefined) return
42
+ const before = was.get(id)
43
+ if (before === undefined) return
44
+ const after = node.getBoundingClientRect()
45
+ const dx = before.left - after.left
46
+ const dy = before.top - after.top
47
+ if (Math.abs(dx) < 0.5 && Math.abs(dy) < 0.5) return
48
+
49
+ node.style.transition = `transform var(${nameOf('motion-instant')})`
50
+ node.style.transform = `translate(${String(dx)}px, ${String(dy)}px)`
51
+ void node.offsetWidth
52
+ node.style.transition = `transform var(--${over}) var(--${paced})`
53
+ node.style.transform = ''
54
+ })
55
+ }
56
+
57
+ export function shows(props: EachProps, _surface: unknown): ReactNode {
58
+ const within = useRef<HTMLElement | null>(null)
59
+ const was = useRef<Map<string, DOMRect>>(new Map())
60
+ const drawn = useRef('')
61
+
62
+ const order = props.rows.map((row, at) => keyOf(row, props.by, at)).join('|')
63
+
64
+ useLayoutEffect(() => {
65
+ const marker = within.current
66
+ if (marker === null || props.settles === undefined || props.paced === undefined) return
67
+ const ids = order === '' ? [] : order.split('|')
68
+ const moved = drawn.current !== '' && drawn.current !== order
69
+ if (moved) settle(marker, ids, was.current, props.settles, props.paced)
70
+ drawn.current = order
71
+ was.current = boxes(marker, ids)
72
+ }, [order, props.settles, props.paced])
73
+
74
+ return (
75
+ <>
76
+ <span ref={within} hidden />
77
+ {props.rows.map((row, at) => (
78
+ <Fragment key={keyOf(row, props.by, at)}>{props.children(row as Row, at)}</Fragment>
79
+ ))}
80
+ </>
81
+ )
82
+ }
@@ -9,4 +9,4 @@ export const Empty = element('Empty', (props, className) => {
9
9
  'empty',
10
10
  ], });
11
11
  return (_jsx("div", { className: surface.className, ...surface.marks, ...surface.invokes, ...surface.a11y, children: surface.text(props.labelKey) }));
12
- }, 'e-empty');
12
+ }, 'e-empty', { "card": "thread-card" });
@@ -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
+
8
+
9
+ export interface EmptyProps {
10
+ readonly labelKey: MessageKey
11
+ readonly dispatch?: string
12
+ readonly input?: Readonly<Record<string, unknown>>
13
+ readonly invokeOn?: 'press' | 'shown'
14
+ }
15
+
16
+ export const Empty: Element<'Empty', EmptyProps> = element(
17
+ 'Empty',
18
+ (props: EmptyProps, className) => {
19
+ const surface = useSurface({ className: roleClass(undefined, className),
20
+ invokes: { ...(props.dispatch === undefined ? {} : { dispatch: props.dispatch }), ...(props.input === undefined ? {} : { input: props.input }), ...(props.invokeOn === undefined ? {} : { on: props.invokeOn }) },
21
+ holds: [
22
+ 'empty' as const,
23
+ ], })
24
+ return (
25
+ <div className={surface.className} {...surface.marks} {...surface.invokes} {...surface.a11y}>
26
+ {surface.text(props.labelKey)}
27
+ </div>
28
+ )
29
+ },
30
+ 'e-empty',
31
+ {"card":"thread-card"},
32
+ )
@@ -12,4 +12,4 @@ export const Field = element('Field', (props, className) => {
12
12
  ],
13
13
  ...(kept.animates === undefined ? {} : { animates: kept.animates }), });
14
14
  return (_jsxs("label", { className: surface.className, ...surface.marks, ...surface.invokes, ...surface.a11y, ...(kept.on ?? {}), children: [_jsx("span", { children: surface.text(props.labelKey) }), _jsx("input", { ...entry(props, kept) }), _jsx("span", { children: shown(props, surface, kept).fault })] }));
15
- }, 'e-field');
15
+ }, 'e-field', { "input": "thread-input" });
@@ -0,0 +1,44 @@
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, entry, uses } from './field.works.ts'
8
+
9
+
10
+ export interface FieldProps {
11
+ readonly name: string
12
+ readonly validator: string
13
+ readonly labelKey: MessageKey
14
+ readonly value?: string
15
+ readonly onValue?: (said: string) => void
16
+ readonly context?: Readonly<Record<string, unknown>>
17
+ readonly autocomplete?: string
18
+ readonly disabled?: boolean
19
+ readonly dispatch?: string
20
+ readonly input?: Readonly<Record<string, unknown>>
21
+ readonly invokeOn?: 'press' | 'shown'
22
+ }
23
+
24
+ export const Field: Element<'Field', FieldProps> = element(
25
+ 'Field',
26
+ (props: FieldProps, 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
+ <label className={surface.className} {...surface.marks} {...surface.invokes} {...surface.a11y} {...(kept.on ?? {})}>
36
+ <span>{surface.text(props.labelKey)}</span>
37
+ <input {...entry(props, kept)} />
38
+ <span>{shown(props, surface, kept).fault}</span>
39
+ </label>
40
+ )
41
+ },
42
+ 'e-field',
43
+ {"input":"thread-input"},
44
+ )
@@ -0,0 +1,65 @@
1
+ import { useState, type ReactNode } from 'react'
2
+
3
+ import { acceptsKeystroke, applyMask, entryConstraints, evaluateMoney, runValidator } from '../validators.ts'
4
+ import type { Surface } from '../door.ts'
5
+ import type { FieldProps } from './field.tsx'
6
+
7
+ export function uses(props: FieldProps): {
8
+ readonly holds?: readonly string[]
9
+ readonly animates?: boolean
10
+ readonly on?: Readonly<Record<string, unknown>>
11
+ readonly wrong: string | null
12
+ readonly said: (to: string | null) => void
13
+ } {
14
+ const [wrong, said] = useState<string | null>(null)
15
+ return { ...(props.disabled === true ? { holds: ['disabled'] as const } : {}), wrong, said }
16
+ }
17
+
18
+ export function entry(
19
+ props: FieldProps,
20
+ kept: { readonly wrong: string | null; readonly said: (to: string | null) => void },
21
+ ): Readonly<Record<string, unknown>> {
22
+ const rules = entryConstraints(props.validator, props.context ?? {})
23
+ return {
24
+ name: props.name,
25
+ ...(rules.secret === true ? { type: 'password' } : {}),
26
+ ...(props.autocomplete ?? rules.autocomplete === undefined
27
+ ? {}
28
+ : { autoComplete: props.autocomplete ?? rules.autocomplete }),
29
+ ...(rules.keyboard === 'text' ? {} : { inputMode: rules.keyboard }),
30
+ ...(rules.maxChars === undefined ? {} : { maxLength: rules.maxChars }),
31
+ value: props.value ?? '',
32
+ ...(kept.wrong === null ? {} : { 'aria-invalid': true }),
33
+
34
+ onChange: (event: { readonly target: { readonly value: string } }): void => {
35
+ let kept_ = ''
36
+ for (const ch of event.target.value) {
37
+ if (acceptsKeystroke(props.validator, kept_, ch)) kept_ += ch
38
+ }
39
+ props.onValue?.(applyMask(props.validator, kept_))
40
+ },
41
+
42
+ onBlur: (): void => {
43
+ const text = props.value ?? ''
44
+ let judged: unknown = text
45
+ if (rules.entry === 'calculator') {
46
+ const currency = props.context?.['currency']
47
+ const resolved = typeof currency === 'string' ? evaluateMoney(text, currency) : undefined
48
+ if (resolved !== undefined) {
49
+ judged = { amount: resolved.amount, currency }
50
+ props.onValue?.(String(resolved.amount))
51
+ }
52
+ }
53
+ const result = runValidator(props.validator, judged, props.context ?? {})
54
+ kept.said(result.ok ? null : result.errorKey)
55
+ },
56
+ }
57
+ }
58
+
59
+ export function shown(
60
+ _props: FieldProps,
61
+ surface: Surface,
62
+ kept: { readonly wrong: string | null },
63
+ ): Readonly<Record<string, ReactNode>> {
64
+ return { fault: kept.wrong === null ? null : surface.text(kept.wrong) }
65
+ }
@@ -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 "./funnel.works.js";
8
8
  export const Funnel = element('Funnel', (props, className) => {
9
9
  const kept = uses(props);
@@ -14,4 +14,4 @@ export const Funnel = element('Funnel', (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-funnel');
17
+ }, 'e-funnel', { "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 './funnel.works.ts'
11
+
12
+
13
+ export interface FunnelProps {
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 Funnel: Element<'Funnel', FunnelProps> = element(
25
+ 'Funnel',
26
+ (props: FunnelProps, 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-funnel',
48
+ {"card":"thread-card"},
49
+ )
@@ -0,0 +1,37 @@
1
+ import { formatValue } from '../validators.ts'
2
+ import { seriesColour } from '../series.ts'
3
+ import { num, str, useRamp, type Row } from '../charts.tsx'
4
+ import type { FunnelProps } from './funnel.tsx'
5
+
6
+ export function uses(props: FunnelProps): {
7
+ readonly holds?: readonly string[]
8
+ readonly animates?: boolean
9
+ readonly on?: Readonly<Record<string, unknown>>
10
+ readonly ramp: readonly string[]
11
+ } {
12
+ const ramp = useRamp(props.rows.length)
13
+ return { ...(props.rows.length === 0 ? { holds: ['empty'] as const } : {}), ramp }
14
+ }
15
+
16
+ export function sized(
17
+ props: FunnelProps,
18
+ kept: { readonly ramp: readonly string[] },
19
+ row: Row,
20
+ at: number,
21
+ ): Readonly<Record<string, string | number>> {
22
+ const top = num(props.rows[0] as Row, props.value) || 1
23
+ const before = at === 0 ? null : num(props.rows[at - 1] as Row, props.value)
24
+ const drop = before === null || before === 0 ? null : 1 - num(row, props.value) / before
25
+ return {
26
+ series: seriesColour(kept.ramp, at),
27
+ fraction: `${((num(row, props.value) / top) * 100).toFixed(1)}%`,
28
+ ...(drop === null ? {} : { drop: `${(drop * 100).toFixed(1)}%` }),
29
+ }
30
+ }
31
+
32
+ export function shown(props: FunnelProps, row: Row, _surface: unknown, _kept?: unknown): Readonly<Record<string, string>> {
33
+ return {
34
+ label: str(row, props.by),
35
+ amount: String(formatValue(props.format ?? 'number', num(row, props.value))),
36
+ }
37
+ }
@@ -12,4 +12,4 @@ export const Gauge = element('Gauge', (props, className) => {
12
12
  ],
13
13
  ...(kept.animates === undefined ? {} : { animates: kept.animates }), });
14
14
  return (_jsxs("svg", { className: surface.className, ...surface.marks, ...surface.invokes, ...surface.a11y, ...(kept.on ?? {}), role: "img", "aria-label": surface.text(props.labelKey), children: [_jsx("path", { ...track(props, kept) }), _jsx("path", { ...needle(props, kept) })] }));
15
- }, 'e-gauge');
15
+ }, 'e-gauge', { "statTile": "thread-stat-tile" });
@@ -0,0 +1,39 @@
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 { track, needle, uses } from './gauge.works.ts'
8
+
9
+
10
+ export interface GaugeProps {
11
+ readonly value?: number
12
+ readonly min?: number
13
+ readonly max?: number
14
+ readonly labelKey: MessageKey
15
+ readonly dispatch?: string
16
+ readonly input?: Readonly<Record<string, unknown>>
17
+ readonly invokeOn?: 'press' | 'shown'
18
+ }
19
+
20
+ export const Gauge: Element<'Gauge', GaugeProps> = element(
21
+ 'Gauge',
22
+ (props: GaugeProps, className) => {
23
+ const kept = uses(props)
24
+ const surface = useSurface({ className: roleClass(undefined, className),
25
+ invokes: { ...(props.dispatch === undefined ? {} : { dispatch: props.dispatch }), ...(props.input === undefined ? {} : { input: props.input }), ...(props.invokeOn === undefined ? {} : { on: props.invokeOn }) },
26
+ holds: [
27
+ ...(kept.holds ?? []),
28
+ ],
29
+ ...(kept.animates === undefined ? {} : { animates: kept.animates }), })
30
+ return (
31
+ <svg className={surface.className} {...surface.marks} {...surface.invokes} {...surface.a11y} {...(kept.on ?? {})} role="img" aria-label={surface.text(props.labelKey)}>
32
+ <path {...track(props, kept)}></path>
33
+ <path {...needle(props, kept)}></path>
34
+ </svg>
35
+ )
36
+ },
37
+ 'e-gauge',
38
+ {"statTile":"thread-stat-tile"},
39
+ )
@@ -0,0 +1,42 @@
1
+ import { useRamp } from '../charts.tsx'
2
+ import type { GaugeProps } from './gauge.tsx'
3
+
4
+ const LEFT = 10
5
+ const RIGHT = 90
6
+ const MIDDLE = 50
7
+ const RADIUS = 40
8
+
9
+ export function uses(_props: GaugeProps): {
10
+ readonly holds?: readonly string[]
11
+ readonly animates?: boolean
12
+ readonly on?: Readonly<Record<string, unknown>>
13
+ readonly ramp: readonly string[]
14
+ } {
15
+ return { on: { viewBox: `0 0 100 56` }, ramp: useRamp(1) }
16
+ }
17
+
18
+ export function track(_props: GaugeProps, _kept: unknown): Readonly<Record<string, string | number>> {
19
+ return {
20
+ d: `M ${String(LEFT)} ${String(MIDDLE)} A ${String(RADIUS)} ${String(RADIUS)} 0 0 1 ${String(RIGHT)} ${String(MIDDLE)}`,
21
+ fill: 'none',
22
+ stroke: 'currentColor',
23
+ strokeOpacity: 0.15,
24
+ strokeWidth: 8,
25
+ }
26
+ }
27
+
28
+ export function needle(props: GaugeProps, kept: { readonly ramp: readonly string[] }): Readonly<Record<string, string | number>> {
29
+ const least = props.min ?? 0
30
+ const most = props.max ?? 0
31
+ const span = most - least
32
+ const part = span <= 0 ? 0 : Math.min(1, Math.max(0, ((props.value ?? 0) - least) / span))
33
+ const angle = Math.PI * (1 - part)
34
+ return {
35
+ d:
36
+ `M ${String(LEFT)} ${String(MIDDLE)} A ${String(RADIUS)} ${String(RADIUS)} 0 0 1 ` +
37
+ `${(MIDDLE + RADIUS * Math.cos(angle)).toFixed(2)} ${(MIDDLE - RADIUS * Math.sin(angle)).toFixed(2)}`,
38
+ fill: 'none',
39
+ stroke: kept.ramp[0] ?? 'currentColor',
40
+ strokeWidth: 8,
41
+ }
42
+ }
@@ -6,4 +6,4 @@ export const Glyph = element('Glyph', (props, className) => {
6
6
  const surface = useSurface({ className: roleClass(undefined, className),
7
7
  invokes: { ...(props.dispatch === undefined ? {} : { dispatch: props.dispatch }), ...(props.input === undefined ? {} : { input: props.input }), ...(props.invokeOn === undefined ? {} : { on: props.invokeOn }) }, });
8
8
  return (shows(props, surface));
9
- }, 'e-glyph');
9
+ }, 'e-glyph', { "glyphPlain": "thread-glyph-plain" });
@@ -0,0 +1,29 @@
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 './glyph.works.tsx'
8
+
9
+
10
+ export interface GlyphProps {
11
+ readonly name: string
12
+ readonly labelKey?: MessageKey
13
+ readonly dispatch?: string
14
+ readonly input?: Readonly<Record<string, unknown>>
15
+ readonly invokeOn?: 'press' | 'shown'
16
+ }
17
+
18
+ export const Glyph: Element<'Glyph', GlyphProps> = element(
19
+ 'Glyph',
20
+ (props: GlyphProps, 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
+ shows(props, surface)
25
+ )
26
+ },
27
+ 'e-glyph',
28
+ {"glyphPlain":"thread-glyph-plain"},
29
+ )
@@ -0,0 +1,42 @@
1
+ import type { ReactNode } from 'react'
2
+
3
+ import type { Surface } from '../door.ts'
4
+ import { drawnFor, iconSize, installedSet, strokeFor } from '../icons.ts'
5
+ import type { GlyphProps } from './glyph.tsx'
6
+
7
+ type Drawn = (props: {
8
+ readonly size?: number
9
+ readonly strokeWidth?: number
10
+ readonly className?: string
11
+ readonly 'aria-hidden'?: boolean
12
+ readonly 'aria-label'?: string
13
+ readonly role?: string
14
+ }) => ReactNode
15
+
16
+ export function drawn(name: string, semantics: Readonly<Record<string, unknown>>, className?: string): ReactNode {
17
+ const Symbol = drawnFor(name) as Drawn
18
+ const set = installedSet()
19
+ const stroke = set === undefined ? undefined : strokeFor(set)
20
+
21
+ return (
22
+ <Symbol
23
+ size={iconSize()}
24
+ {...(className === undefined ? {} : { className })}
25
+ {...(stroke === undefined ? {} : { strokeWidth: stroke })}
26
+ {...semantics}
27
+ />
28
+ )
29
+ }
30
+
31
+ export function shows(props: GlyphProps, surface: Surface): ReactNode {
32
+ return drawn(
33
+ props.name,
34
+ {
35
+ ...surface.marks,
36
+ ...(props.labelKey === undefined
37
+ ? ({ 'aria-hidden': true } as const)
38
+ : ({ role: 'img', 'aria-label': surface.text(props.labelKey) } as const)),
39
+ },
40
+ surface.className,
41
+ )
42
+ }
@@ -5,4 +5,4 @@ export const Grid = element('Grid', (props, className) => {
5
5
  const surface = useSurface({ className,
6
6
  invokes: { ...(props.dispatch === undefined ? {} : { dispatch: props.dispatch }), ...(props.input === undefined ? {} : { input: props.input }), ...(props.invokeOn === undefined ? {} : { on: props.invokeOn }) }, });
7
7
  return (_jsx("div", { className: surface.className, ...surface.marks, ...surface.invokes, ...surface.a11y, children: props.children }));
8
- }, 'e-grid');
8
+ }, 'e-grid', {});
@@ -0,0 +1,28 @@
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 GridProps {
9
+ readonly children: ReactNode
10
+ readonly dispatch?: string
11
+ readonly input?: Readonly<Record<string, unknown>>
12
+ readonly invokeOn?: 'press' | 'shown'
13
+ }
14
+
15
+ export const Grid: Element<'Grid', GridProps> = element(
16
+ 'Grid',
17
+ (props: GridProps, className) => {
18
+ const surface = useSurface({ className,
19
+ invokes: { ...(props.dispatch === undefined ? {} : { dispatch: props.dispatch }), ...(props.input === undefined ? {} : { input: props.input }), ...(props.invokeOn === undefined ? {} : { on: props.invokeOn }) }, })
20
+ return (
21
+ <div className={surface.className} {...surface.marks} {...surface.invokes} {...surface.a11y}>
22
+ {props.children}
23
+ </div>
24
+ )
25
+ },
26
+ 'e-grid',
27
+ {},
28
+ )
@@ -3,7 +3,7 @@ import { jsx as _jsx } 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 { sized, uses } from "./heatmap.works.js";
8
8
  export const Heatmap = element('Heatmap', (props, className) => {
9
9
  const kept = uses(props);
@@ -14,4 +14,4 @@ export const Heatmap = element('Heatmap', (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) => (_jsx("span", { style: marking(sized(props, kept, each0, at)) })) }) }));
17
- }, 'e-heatmap');
17
+ }, 'e-heatmap', { "card": "thread-card" });
@@ -0,0 +1,47 @@
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 { sized, uses } from './heatmap.works.ts'
11
+
12
+
13
+ export interface HeatmapProps {
14
+ readonly rows: readonly Row[]
15
+ readonly by: string
16
+ readonly x: string
17
+ readonly y: string
18
+ readonly value?: string
19
+ readonly titleKey?: MessageKey
20
+ readonly dispatch?: string
21
+ readonly input?: Readonly<Record<string, unknown>>
22
+ readonly invokeOn?: 'press' | 'shown'
23
+ }
24
+
25
+ export const Heatmap: Element<'Heatmap', HeatmapProps> = element(
26
+ 'Heatmap',
27
+ (props: HeatmapProps, className) => {
28
+ const kept = uses(props)
29
+ const surface = useSurface({ className: roleClass(undefined, className),
30
+ invokes: { ...(props.dispatch === undefined ? {} : { dispatch: props.dispatch }), ...(props.input === undefined ? {} : { input: props.input }), ...(props.invokeOn === undefined ? {} : { on: props.invokeOn }) },
31
+ holds: [
32
+ ...(kept.holds ?? []),
33
+ ],
34
+ ...(kept.animates === undefined ? {} : { animates: kept.animates }), })
35
+ return (
36
+ <div className={surface.className} {...surface.marks} {...surface.invokes} {...surface.a11y} {...(kept.on ?? {})} role="img" {...(props.titleKey === undefined ? {} : { 'aria-label': surface.text(props.titleKey) })}>
37
+ <Each rows={props.rows} by={props.by}>
38
+ {(each0, at) => (
39
+ <span style={marking(sized(props, kept, each0, at))}></span>
40
+ )}
41
+ </Each>
42
+ </div>
43
+ )
44
+ },
45
+ 'e-heatmap',
46
+ {"card":"thread-card"},
47
+ )
@@ -0,0 +1,24 @@
1
+ import { num, useRamp, type Row } from '../charts.tsx'
2
+ import type { HeatmapProps } from './heatmap.tsx'
3
+
4
+ export function uses(props: HeatmapProps): {
5
+ readonly holds?: readonly string[]
6
+ readonly animates?: boolean
7
+ readonly on?: Readonly<Record<string, unknown>>
8
+ readonly ramp: readonly string[]
9
+ } {
10
+ const ramp = useRamp(1)
11
+ return { ...(props.rows.length === 0 ? { holds: ['empty'] as const } : {}), ramp }
12
+ }
13
+
14
+ export function sized(
15
+ props: HeatmapProps,
16
+ kept: { readonly ramp: readonly string[] },
17
+ row: Row,
18
+ _at: number,
19
+ ): Readonly<Record<string, string | number>> {
20
+ const readings = props.rows.map((one: Row) => (props.value === undefined ? 1 : num(one, props.value)))
21
+ const most = Math.max(...readings, 0) || 1
22
+ const here = props.value === undefined ? 1 : num(row, props.value)
23
+ return { series: kept.ramp[0] ?? '', share: Math.max(0.08, here / most) }
24
+ }
@@ -12,4 +12,4 @@ export const HitBox = element('HitBox', (props, className) => {
12
12
  ...(kept.animates === undefined ? {} : { animates: kept.animates }), });
13
13
  const wired = wires(props, kept, surface);
14
14
  return (_jsxs("div", { className: surface.className, ...surface.marks, ...surface.invokes, ...surface.a11y, ...(kept.on ?? {}), ...(wired.on ?? {}), children: [props.children, _jsx("div", { className: "e-hit-box-carried", children: shown(props, surface, kept, wired).carried })] }));
15
- }, 'e-hit-box');
15
+ }, 'e-hit-box', { "tapTarget": "thread-tap-target" });