@prestack/thread-ui 0.0.1 → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (279) hide show
  1. package/CHANGELOG.md +177 -0
  2. package/dist/ui/animations/anm_appear.ts +13 -0
  3. package/dist/ui/animations/anm_blur_in.ts +19 -0
  4. package/dist/ui/animations/anm_blur_out.ts +18 -0
  5. package/dist/ui/animations/anm_dismiss.ts +18 -0
  6. package/dist/ui/animations/anm_drop.ts +19 -0
  7. package/dist/ui/animations/anm_fall.ts +18 -0
  8. package/dist/ui/animations/anm_flash.ts +18 -0
  9. package/dist/ui/animations/anm_glow.ts +16 -0
  10. package/dist/ui/animations/anm_lift.ts +16 -0
  11. package/dist/ui/animations/anm_nudge.ts +16 -0
  12. package/dist/ui/animations/anm_pop_in.ts +23 -0
  13. package/dist/ui/animations/anm_pop_out.ts +22 -0
  14. package/dist/ui/animations/anm_press.ts +12 -0
  15. package/dist/ui/animations/anm_pulse.ts +13 -0
  16. package/dist/ui/animations/anm_rise.ts +19 -0
  17. package/dist/ui/animations/anm_shake.ts +13 -0
  18. package/dist/ui/animations/anm_slide_in_left.ts +19 -0
  19. package/dist/ui/animations/anm_slide_in_right.ts +19 -0
  20. package/dist/ui/animations/anm_slide_out_left.ts +18 -0
  21. package/dist/ui/animations/anm_slide_out_right.ts +18 -0
  22. package/dist/ui/animations/anm_spin.ts +12 -0
  23. package/dist/ui/animations/anm_swell.ts +16 -0
  24. package/dist/ui/animations/anm_wobble.ts +24 -0
  25. package/dist/ui/animations/anm_zoom_in.ts +19 -0
  26. package/dist/ui/animations/anm_zoom_out.ts +18 -0
  27. package/dist/ui/animations.js +2 -3
  28. package/dist/ui/animations.ts +316 -0
  29. package/dist/ui/attach.js +13 -10
  30. package/dist/ui/attach.ts +607 -0
  31. package/dist/ui/charts.tsx +74 -0
  32. package/dist/ui/checked.d.ts +2 -2
  33. package/dist/ui/checked.ts +32 -0
  34. package/dist/ui/collector.ts +140 -0
  35. package/dist/ui/compositions.d.ts +11 -2
  36. package/dist/ui/compositions.js +283 -59
  37. package/dist/ui/compositions.ts +633 -0
  38. package/dist/ui/consent.ts +55 -0
  39. package/dist/ui/content.tsx +39 -0
  40. package/dist/ui/contrast.ts +27 -0
  41. package/dist/ui/controls.tsx +30 -0
  42. package/dist/ui/disclosure.tsx +22 -0
  43. package/dist/ui/dismiss-layer.ts +87 -0
  44. package/dist/ui/door.ts +140 -0
  45. package/dist/ui/drag.ts +316 -0
  46. package/dist/ui/elements/accordion.js +2 -2
  47. package/dist/ui/elements/accordion.tsx +47 -0
  48. package/dist/ui/elements/accordion.works.ts +66 -0
  49. package/dist/ui/elements/aspect-ratio.js +1 -1
  50. package/dist/ui/elements/aspect-ratio.tsx +30 -0
  51. package/dist/ui/elements/aspect-ratio.works.ts +5 -0
  52. package/dist/ui/elements/avatar.js +1 -1
  53. package/dist/ui/elements/avatar.tsx +33 -0
  54. package/dist/ui/elements/avatar.works.ts +16 -0
  55. package/dist/ui/elements/badge.js +1 -1
  56. package/dist/ui/elements/badge.tsx +32 -0
  57. package/dist/ui/elements/badge.works.ts +9 -0
  58. package/dist/ui/elements/bars.js +2 -2
  59. package/dist/ui/elements/bars.tsx +49 -0
  60. package/dist/ui/elements/bars.works.ts +29 -0
  61. package/dist/ui/elements/box.js +3 -2
  62. package/dist/ui/elements/box.tsx +35 -0
  63. package/dist/ui/elements/breadcrumb.js +2 -2
  64. package/dist/ui/elements/breadcrumb.tsx +37 -0
  65. package/dist/ui/elements/breadcrumb.works.ts +24 -0
  66. package/dist/ui/elements/button.js +1 -1
  67. package/dist/ui/elements/button.tsx +45 -0
  68. package/dist/ui/elements/button.works.tsx +52 -0
  69. package/dist/ui/elements/column.js +1 -1
  70. package/dist/ui/elements/column.tsx +28 -0
  71. package/dist/ui/elements/consent.js +2 -2
  72. package/dist/ui/elements/consent.tsx +70 -0
  73. package/dist/ui/elements/consent.works.tsx +132 -0
  74. package/dist/ui/elements/donut.js +2 -2
  75. package/dist/ui/elements/donut.tsx +44 -0
  76. package/dist/ui/elements/donut.works.ts +42 -0
  77. package/dist/ui/elements/each.js +1 -1
  78. package/dist/ui/elements/each.tsx +33 -0
  79. package/dist/ui/elements/each.works.tsx +82 -0
  80. package/dist/ui/elements/empty.js +1 -1
  81. package/dist/ui/elements/empty.tsx +32 -0
  82. package/dist/ui/elements/field.js +1 -1
  83. package/dist/ui/elements/field.tsx +44 -0
  84. package/dist/ui/elements/field.works.ts +65 -0
  85. package/dist/ui/elements/funnel.js +2 -2
  86. package/dist/ui/elements/funnel.tsx +49 -0
  87. package/dist/ui/elements/funnel.works.ts +37 -0
  88. package/dist/ui/elements/gauge.js +1 -1
  89. package/dist/ui/elements/gauge.tsx +39 -0
  90. package/dist/ui/elements/gauge.works.ts +42 -0
  91. package/dist/ui/elements/glyph.js +1 -1
  92. package/dist/ui/elements/glyph.tsx +29 -0
  93. package/dist/ui/elements/glyph.works.tsx +42 -0
  94. package/dist/ui/elements/grid.js +1 -1
  95. package/dist/ui/elements/grid.tsx +28 -0
  96. package/dist/ui/elements/heatmap.js +2 -2
  97. package/dist/ui/elements/heatmap.tsx +47 -0
  98. package/dist/ui/elements/heatmap.works.ts +24 -0
  99. package/dist/ui/elements/hit-box.js +1 -1
  100. package/dist/ui/elements/hit-box.tsx +46 -0
  101. package/dist/ui/elements/hit-box.works.tsx +576 -0
  102. package/dist/ui/elements/image.js +1 -1
  103. package/dist/ui/elements/image.tsx +28 -0
  104. package/dist/ui/elements/line-chart.js +2 -2
  105. package/dist/ui/elements/line-chart.tsx +44 -0
  106. package/dist/ui/elements/line-chart.works.ts +41 -0
  107. package/dist/ui/elements/link.js +1 -1
  108. package/dist/ui/elements/link.tsx +37 -0
  109. package/dist/ui/elements/link.works.ts +25 -0
  110. package/dist/ui/elements/media.js +1 -1
  111. package/dist/ui/elements/media.tsx +56 -0
  112. package/dist/ui/elements/menu.js +2 -2
  113. package/dist/ui/elements/menu.tsx +50 -0
  114. package/dist/ui/elements/menu.works.tsx +153 -0
  115. package/dist/ui/elements/money.js +1 -1
  116. package/dist/ui/elements/money.tsx +28 -0
  117. package/dist/ui/elements/money.works.ts +9 -0
  118. package/dist/ui/elements/pagination.js +2 -2
  119. package/dist/ui/elements/pagination.tsx +38 -0
  120. package/dist/ui/elements/pagination.works.ts +20 -0
  121. package/dist/ui/elements/progress.js +1 -1
  122. package/dist/ui/elements/progress.tsx +33 -0
  123. package/dist/ui/elements/progress.works.ts +8 -0
  124. package/dist/ui/elements/radio-group.js +2 -2
  125. package/dist/ui/elements/radio-group.tsx +49 -0
  126. package/dist/ui/elements/radio-group.works.ts +33 -0
  127. package/dist/ui/elements/row.js +1 -1
  128. package/dist/ui/elements/row.tsx +28 -0
  129. package/dist/ui/elements/scatter.js +1 -1
  130. package/dist/ui/elements/scatter.tsx +41 -0
  131. package/dist/ui/elements/scatter.works.ts +38 -0
  132. package/dist/ui/elements/select.js +2 -2
  133. package/dist/ui/elements/select.tsx +42 -0
  134. package/dist/ui/elements/select.works.ts +5 -0
  135. package/dist/ui/elements/separator.js +1 -1
  136. package/dist/ui/elements/separator.tsx +30 -0
  137. package/dist/ui/elements/skeleton.js +1 -1
  138. package/dist/ui/elements/skeleton.tsx +30 -0
  139. package/dist/ui/elements/slider.js +1 -1
  140. package/dist/ui/elements/slider.tsx +33 -0
  141. package/dist/ui/elements/slider.works.ts +5 -0
  142. package/dist/ui/elements/sparkline.js +1 -1
  143. package/dist/ui/elements/sparkline.tsx +36 -0
  144. package/dist/ui/elements/sparkline.works.ts +32 -0
  145. package/dist/ui/elements/spinner.js +1 -1
  146. package/dist/ui/elements/spinner.tsx +32 -0
  147. package/dist/ui/elements/stack.js +1 -1
  148. package/dist/ui/elements/stack.tsx +28 -0
  149. package/dist/ui/elements/stat.js +1 -1
  150. package/dist/ui/elements/stat.tsx +35 -0
  151. package/dist/ui/elements/stat.works.ts +14 -0
  152. package/dist/ui/elements/surface.js +1 -1
  153. package/dist/ui/elements/surface.tsx +35 -0
  154. package/dist/ui/elements/surface.works.ts +9 -0
  155. package/dist/ui/elements/table.js +2 -2
  156. package/dist/ui/elements/table.tsx +61 -0
  157. package/dist/ui/elements/table.works.ts +30 -0
  158. package/dist/ui/elements/tabs.js +2 -2
  159. package/dist/ui/elements/tabs.tsx +47 -0
  160. package/dist/ui/elements/tabs.works.ts +84 -0
  161. package/dist/ui/elements/text.js +1 -1
  162. package/dist/ui/elements/text.tsx +32 -0
  163. package/dist/ui/elements/textarea.js +1 -1
  164. package/dist/ui/elements/textarea.tsx +37 -0
  165. package/dist/ui/elements/textarea.works.ts +5 -0
  166. package/dist/ui/elements/time.js +1 -1
  167. package/dist/ui/elements/time.tsx +34 -0
  168. package/dist/ui/elements/time.works.ts +25 -0
  169. package/dist/ui/elements/toggle.js +1 -1
  170. package/dist/ui/elements/toggle.tsx +33 -0
  171. package/dist/ui/elements/toggle.works.ts +5 -0
  172. package/dist/ui/elements/tooltip.js +1 -1
  173. package/dist/ui/elements/tooltip.tsx +39 -0
  174. package/dist/ui/elements/tooltip.works.ts +77 -0
  175. package/dist/ui/elements/value.js +1 -1
  176. package/dist/ui/elements/value.tsx +30 -0
  177. package/dist/ui/elements/value.works.ts +9 -0
  178. package/dist/ui/elements.ts +127 -0
  179. package/dist/ui/focus-scope.ts +121 -0
  180. package/dist/ui/fonts.ts +355 -0
  181. package/dist/ui/generators/dev.js +2 -1
  182. package/dist/ui/generators/dev.ts +80 -0
  183. package/dist/ui/generators/elements.d.ts +1 -11
  184. package/dist/ui/generators/elements.js +44 -14
  185. package/dist/ui/generators/elements.ts +794 -0
  186. package/dist/ui/generators/i18n.js +2 -3
  187. package/dist/ui/generators/i18n.ts +300 -0
  188. package/dist/ui/generators/icons.js +3 -12
  189. package/dist/ui/generators/icons.ts +214 -0
  190. package/dist/ui/generators/react.js +13 -5
  191. package/dist/ui/generators/react.ts +269 -0
  192. package/dist/ui/generators/routes.d.ts +1 -0
  193. package/dist/ui/generators/routes.js +99 -83
  194. package/dist/ui/generators/routes.ts +743 -0
  195. package/dist/ui/generators/show.js +4 -12
  196. package/dist/ui/generators/show.ts +260 -0
  197. package/dist/ui/generators/styles.js +3 -4
  198. package/dist/ui/generators/styles.ts +669 -0
  199. package/dist/ui/generators/variants.js +2 -3
  200. package/dist/ui/generators/variants.ts +204 -0
  201. package/dist/ui/i18n/catalogs/en.ts +191 -0
  202. package/dist/ui/i18n/format.ts +221 -0
  203. package/dist/ui/i18n/locales.ts +33 -0
  204. package/dist/ui/i18n/parse.ts +315 -0
  205. package/dist/ui/i18n/translator.ts +31 -0
  206. package/dist/ui/i18n/types.ts +96 -0
  207. package/dist/ui/icons.ts +331 -0
  208. package/dist/ui/islands/map.ts +58 -0
  209. package/dist/ui/localizations.ts +75 -0
  210. package/dist/ui/material.ts +236 -0
  211. package/dist/ui/materials/mat_bordered.ts +13 -0
  212. package/dist/ui/materials/mat_ceramic.ts +13 -0
  213. package/dist/ui/materials/mat_clay.ts +13 -0
  214. package/dist/ui/materials/mat_elevated.ts +24 -0
  215. package/dist/ui/materials/mat_flat.ts +34 -0
  216. package/dist/ui/materials/mat_glass.ts +20 -0
  217. package/dist/ui/materials/mat_lit.ts +13 -0
  218. package/dist/ui/materials/mat_moulded.ts +11 -0
  219. package/dist/ui/materials.js +2 -3
  220. package/dist/ui/materials.ts +205 -0
  221. package/dist/ui/modes.ts +44 -0
  222. package/dist/ui/navigation.tsx +32 -0
  223. package/dist/ui/positioning.ts +90 -0
  224. package/dist/ui/presented.tsx +119 -0
  225. package/dist/ui/provider.d.ts +2 -0
  226. package/dist/ui/provider.js +7 -1
  227. package/dist/ui/provider.tsx +182 -0
  228. package/dist/ui/render-loop.ts +133 -0
  229. package/dist/ui/roving-focus.ts +161 -0
  230. package/dist/ui/screens.js +22 -22
  231. package/dist/ui/screens.ts +141 -0
  232. package/dist/ui/scroll-lock.ts +28 -0
  233. package/dist/ui/series.ts +97 -0
  234. package/dist/ui/signals.ts +386 -0
  235. package/dist/ui/styleguide.ts +231 -0
  236. package/dist/ui/styleguides.ts +374 -0
  237. package/dist/ui/themes.ts +22 -0
  238. package/dist/ui/token.js +1 -1
  239. package/dist/ui/token.ts +78 -0
  240. package/dist/ui/tokens/shadow.ts +25 -0
  241. package/dist/ui/tokens.js +1 -2
  242. package/dist/ui/tokens.ts +234 -0
  243. package/dist/ui/validators.ts +232 -0
  244. package/dist/ui/variant.d.ts +1 -0
  245. package/dist/ui/variant.js +3 -0
  246. package/dist/ui/variant.ts +352 -0
  247. package/dist/ui/variants/var_avatar-round.ts +14 -0
  248. package/dist/ui/variants/var_bubble-me.ts +14 -0
  249. package/dist/ui/variants/var_bubble-them.ts +13 -0
  250. package/dist/ui/variants/var_button-border.ts +76 -0
  251. package/dist/ui/variants/var_button-cta.ts +76 -0
  252. package/dist/ui/variants/var_button-destructive-ghost.ts +72 -0
  253. package/dist/ui/variants/var_button-destructive.ts +76 -0
  254. package/dist/ui/variants/var_button-fill.ts +76 -0
  255. package/dist/ui/variants/var_button-ghost.ts +72 -0
  256. package/dist/ui/variants/var_button-glass.ts +55 -0
  257. package/dist/ui/variants/var_button-normal.ts +76 -0
  258. package/dist/ui/variants/var_button-quiet.ts +74 -0
  259. package/dist/ui/variants/var_card.ts +17 -0
  260. package/dist/ui/variants/var_chip.ts +13 -0
  261. package/dist/ui/variants/var_choice.ts +15 -0
  262. package/dist/ui/variants/var_framed.ts +10 -0
  263. package/dist/ui/variants/var_glyph-plain.ts +11 -0
  264. package/dist/ui/variants/var_input.ts +43 -0
  265. package/dist/ui/variants/var_link-inline.ts +29 -0
  266. package/dist/ui/variants/var_list-row.ts +34 -0
  267. package/dist/ui/variants/var_rule.ts +10 -0
  268. package/dist/ui/variants/var_skeleton-block.ts +12 -0
  269. package/dist/ui/variants/var_spinner-turning.ts +12 -0
  270. package/dist/ui/variants/var_stat-tile.ts +13 -0
  271. package/dist/ui/variants/var_tab-bar.ts +15 -0
  272. package/dist/ui/variants/var_tap-target.ts +25 -0
  273. package/dist/ui/variants.ts +64 -0
  274. package/dist/ui/wrappers.d.ts +4 -1
  275. package/dist/ui/wrappers.js +60 -20
  276. package/dist/ui/wrappers.tsx +426 -0
  277. package/package.json +8 -2
  278. package/dist/ui/makers/message.d.ts +0 -1
  279. package/dist/ui/makers/message.js +0 -230
@@ -0,0 +1,70 @@
1
+ 'use client'
2
+
3
+ import type { MessageKey } from '../content.tsx'
4
+ import { roleClass } from '../fonts.ts'
5
+ import type { ConsentDecision } from '../consent.ts'
6
+ import { Box } from './box.tsx'
7
+ import { Column } from './column.tsx'
8
+ import { Text } from './text.tsx'
9
+ import { Toggle } from './toggle.tsx'
10
+ import { Button } from './button.tsx'
11
+
12
+ import { type Element, element, useSurface } from '../wrappers.tsx'
13
+ import { Each } from '../elements/each.tsx'
14
+ import { title, policy, hasPolicy, categories, label, describe, chooser, optional, fixed, required, withdraw, withdrawable, decline, accept, acceptAll, uses } from './consent.works.tsx'
15
+
16
+
17
+ export interface ConsentProps {
18
+ readonly decision?: ConsentDecision
19
+ readonly onDecide: (said: ConsentDecision) => void
20
+ readonly policyKey?: MessageKey
21
+ readonly preselected: readonly string[]
22
+ readonly dispatch?: string
23
+ readonly input?: Readonly<Record<string, unknown>>
24
+ readonly invokeOn?: 'press' | 'shown'
25
+ }
26
+
27
+ export const Consent: Element<'Consent', ConsentProps> = element(
28
+ 'Consent',
29
+ (props: ConsentProps, className) => {
30
+ const kept = uses(props)
31
+ const surface = useSurface({ className: roleClass(undefined, className),
32
+ invokes: { ...(props.dispatch === undefined ? {} : { dispatch: props.dispatch }), ...(props.input === undefined ? {} : { input: props.input }), ...(props.invokeOn === undefined ? {} : { on: props.invokeOn }) },
33
+ holds: [
34
+ ...(kept.holds ?? []),
35
+ ],
36
+ ...(kept.animates === undefined ? {} : { animates: kept.animates }), })
37
+ return (
38
+ <>
39
+ <Box.card>
40
+ <Column>
41
+ <Text {...title(props, kept)}></Text>
42
+ {!hasPolicy(props, kept) ? null : <Text {...policy(props, kept)}></Text>}
43
+ <Column>
44
+ <Each rows={categories(props, kept)} by={'id'}>
45
+ {(each0, at) => (
46
+ <Box>
47
+ <Column>
48
+ <Text {...label(props, kept, each0, at)}></Text>
49
+ <Text {...describe(props, kept, each0, at)}></Text>
50
+ </Column>
51
+ {!optional(props, kept, each0) ? null : <Toggle.input {...chooser(props, kept, each0, at)}></Toggle.input>}
52
+ {!required(props, kept, each0) ? null : <Text {...fixed(props, kept, each0, at)}></Text>}
53
+ </Box>
54
+ )}
55
+ </Each>
56
+ </Column>
57
+ <Box>
58
+ {!withdrawable(props, kept) ? null : <Button.buttonQuiet {...withdraw(props, kept)}></Button.buttonQuiet>}
59
+ <Button.buttonBorder {...decline(props, kept)}></Button.buttonBorder>
60
+ <Button.buttonBorder {...accept(props, kept)}></Button.buttonBorder>
61
+ <Button.buttonFill {...acceptAll(props, kept)}></Button.buttonFill>
62
+ </Box>
63
+ </Column>
64
+ </Box.card>
65
+ </>
66
+ )
67
+ },
68
+ 'e-consent',
69
+ {},
70
+ )
@@ -0,0 +1,132 @@
1
+ 'use client'
2
+
3
+ import { useState } from 'react'
4
+
5
+ import { consentCategories, type ConsentCategory, type ConsentDecision } from '../consent.ts'
6
+ import type { Row } from '../charts.tsx'
7
+ import type { ButtonProps } from './button.tsx'
8
+ import type { ConsentProps } from './consent.tsx'
9
+ import type { TextProps } from './text.tsx'
10
+ import type { ToggleProps } from './toggle.tsx'
11
+
12
+ type Kept = {
13
+ readonly categories: readonly ConsentCategory[]
14
+ readonly selected: readonly string[]
15
+ readonly accepted: boolean
16
+ readonly offered: readonly string[]
17
+ readonly toggle: (id: string, on: boolean) => void
18
+ readonly decide: (decision: ConsentDecision['decision'], accepted: readonly string[]) => void
19
+ }
20
+
21
+ function firstSelection(
22
+ categories: readonly ConsentCategory[],
23
+ decision: ConsentDecision | undefined,
24
+ preselected: readonly string[],
25
+ ): readonly string[] {
26
+ const answered = decision !== undefined
27
+ return categories
28
+ .filter(
29
+ (one) =>
30
+ one.required ||
31
+ (answered
32
+ ? decision.decision === 'accepted' && decision.accepted.includes(one.id)
33
+ : preselected.includes(one.id)),
34
+ )
35
+ .map((one) => one.id)
36
+ }
37
+
38
+ export function uses(props: ConsentProps): {
39
+ readonly holds?: readonly string[]
40
+ readonly animates?: boolean
41
+ readonly on?: Readonly<Record<string, unknown>>
42
+ readonly categories: readonly ConsentCategory[]
43
+ readonly selected: readonly string[]
44
+ readonly accepted: boolean
45
+ readonly offered: readonly string[]
46
+ readonly toggle: (id: string, on: boolean) => void
47
+ readonly decide: (decision: ConsentDecision['decision'], accepted: readonly string[]) => void
48
+ } {
49
+ const categories = consentCategories()
50
+ const [selected, setSelected] = useState<readonly string[]>(() =>
51
+ firstSelection(categories, props.decision, props.preselected ?? []),
52
+ )
53
+ const offered = categories.map((one) => one.id)
54
+
55
+ return {
56
+ categories,
57
+ selected,
58
+ offered,
59
+ accepted: props.decision?.decision === 'accepted',
60
+ toggle: (id: string, on: boolean): void => {
61
+ setSelected((was) => (on ? [...was, id] : was.filter((one) => one !== id)))
62
+ },
63
+ decide: (decision: ConsentDecision['decision'], accepted: readonly string[]): void => {
64
+ props.onDecide({ decision, accepted: [...accepted], offered })
65
+ },
66
+ }
67
+ }
68
+
69
+ export function categories(_props: ConsentProps, kept: Kept): readonly Row[] {
70
+ return kept.categories as unknown as readonly Row[]
71
+ }
72
+
73
+ export function hasPolicy(props: ConsentProps, _kept: Kept): boolean {
74
+ return props.policyKey !== undefined
75
+ }
76
+
77
+ export function withdrawable(_props: ConsentProps, kept: Kept): boolean {
78
+ return kept.accepted
79
+ }
80
+
81
+ export function optional(_props: ConsentProps, _kept: Kept, row: Row): boolean {
82
+ return row['required'] !== true
83
+ }
84
+
85
+ export function required(_props: ConsentProps, _kept: Kept, row: Row): boolean {
86
+ return row['required'] === true
87
+ }
88
+
89
+ export function title(_props: ConsentProps, _kept: Kept, _at?: number): TextProps {
90
+ return { k: 'consent.title', is: 'heading' }
91
+ }
92
+
93
+ export function policy(props: ConsentProps, _kept: Kept, _at?: number): TextProps {
94
+ return { k: props.policyKey ?? 'consent.title', is: 'body' }
95
+ }
96
+
97
+ export function label(_props: ConsentProps, _kept: Kept, row: Row, _at: number): TextProps {
98
+ return { k: String(row['labelKey']), is: 'label' }
99
+ }
100
+
101
+ export function describe(_props: ConsentProps, _kept: Kept, row: Row, _at: number): TextProps {
102
+ return { k: String(row['describeKey']), is: 'body' }
103
+ }
104
+
105
+ export function fixed(_props: ConsentProps, _kept: Kept, _row: Row, _at: number): TextProps {
106
+ return { k: 'consent.required', is: 'label' }
107
+ }
108
+
109
+ export function chooser(_props: ConsentProps, kept: Kept, row: Row, _at?: number): ToggleProps {
110
+ const id = String(row['id'])
111
+ return {
112
+ labelKey: String(row['labelKey']),
113
+ on: kept.selected.includes(id),
114
+ onToggle: (on: boolean): void => kept.toggle(id, on),
115
+ }
116
+ }
117
+
118
+ export function withdraw(_props: ConsentProps, kept: Kept): ButtonProps {
119
+ return { label: 'consent.withdraw', onPress: (): void => kept.decide('withdrawn', []) }
120
+ }
121
+
122
+ export function decline(_props: ConsentProps, kept: Kept): ButtonProps {
123
+ return { label: 'consent.decline', onPress: (): void => kept.decide('declined', []) }
124
+ }
125
+
126
+ export function accept(_props: ConsentProps, kept: Kept): ButtonProps {
127
+ return { label: 'consent.accept', onPress: (): void => kept.decide('accepted', kept.selected) }
128
+ }
129
+
130
+ export function acceptAll(_props: ConsentProps, kept: Kept): ButtonProps {
131
+ return { label: 'consent.acceptAll', onPress: (): void => kept.decide('accepted', kept.offered) }
132
+ }
@@ -2,7 +2,7 @@
2
2
  import { jsx as _jsx } 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 { slice, uses } from "./donut.works.js";
7
7
  export const Donut = element('Donut', (props, className) => {
8
8
  const kept = uses(props);
@@ -13,4 +13,4 @@ export const Donut = element('Donut', (props, className) => {
13
13
  ],
14
14
  ...(kept.animates === undefined ? {} : { animates: kept.animates }), });
15
15
  return (_jsx("svg", { 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("circle", { ...slice(props, kept, each0, at) })) }) }));
16
- }, 'e-donut');
16
+ }, 'e-donut', { "card": "thread-card" });
@@ -0,0 +1,44 @@
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 Element, element, useSurface } from '../wrappers.tsx'
8
+ import { Each } from '../elements/each.tsx'
9
+ import { slice, uses } from './donut.works.ts'
10
+
11
+
12
+ export interface DonutProps {
13
+ readonly rows: readonly Row[]
14
+ readonly by: string
15
+ readonly value: string
16
+ readonly titleKey?: MessageKey
17
+ readonly dispatch?: string
18
+ readonly input?: Readonly<Record<string, unknown>>
19
+ readonly invokeOn?: 'press' | 'shown'
20
+ }
21
+
22
+ export const Donut: Element<'Donut', DonutProps> = element(
23
+ 'Donut',
24
+ (props: DonutProps, 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
+ <svg className={surface.className} {...surface.marks} {...surface.invokes} {...surface.a11y} {...(kept.on ?? {})} role="img" {...(props.titleKey === undefined ? {} : { 'aria-label': surface.text(props.titleKey) })}>
34
+ <Each rows={props.rows} by={props.by}>
35
+ {(each0, at) => (
36
+ <circle {...slice(props, kept, each0, at)}></circle>
37
+ )}
38
+ </Each>
39
+ </svg>
40
+ )
41
+ },
42
+ 'e-donut',
43
+ {"card":"thread-card"},
44
+ )
@@ -0,0 +1,42 @@
1
+ import { seriesColour } from '../series.ts'
2
+ import { num, useRamp, type Row } from '../charts.tsx'
3
+ import type { DonutProps } from './donut.tsx'
4
+
5
+ const RADIUS = 40
6
+ const ROUND = 2 * Math.PI * RADIUS
7
+
8
+ export function uses(props: DonutProps): {
9
+ readonly holds?: readonly string[]
10
+ readonly animates?: boolean
11
+ readonly on?: Readonly<Record<string, unknown>>
12
+ readonly ramp: readonly string[]
13
+ } {
14
+ const ramp = useRamp(props.rows.length)
15
+ return {
16
+ ...(props.rows.length === 0 ? { holds: ['empty'] as const } : {}),
17
+ on: { viewBox: '0 0 100 100' },
18
+ ramp,
19
+ }
20
+ }
21
+
22
+ export function slice(
23
+ props: DonutProps,
24
+ kept: { readonly ramp: readonly string[] },
25
+ row: Row,
26
+ at: number,
27
+ ): Readonly<Record<string, string | number>> {
28
+ const whole = props.rows.reduce((sum: number, one: Row) => sum + num(one, props.value), 0) || 1
29
+ const share = num(row, props.value) / whole
30
+ const before = props.rows.slice(0, at).reduce((sum: number, one: Row) => sum + num(one, props.value), 0) / whole
31
+ return {
32
+ cx: 50,
33
+ cy: 50,
34
+ r: RADIUS,
35
+ fill: 'none',
36
+ stroke: seriesColour(kept.ramp, at),
37
+ strokeWidth: 16,
38
+ strokeDasharray: `${(share * ROUND).toFixed(2)} ${ROUND.toFixed(2)}`,
39
+ strokeDashoffset: (-before * ROUND).toFixed(2),
40
+ transform: 'rotate(-90 50 50)',
41
+ }
42
+ }
@@ -6,4 +6,4 @@ export const Each = element('Each', (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-each');
9
+ }, 'e-each', {});
@@ -0,0 +1,33 @@
1
+ 'use client'
2
+
3
+ import type { ReactNode } from 'react'
4
+ import type { Row } from '../charts.tsx'
5
+ import { roleClass } from '../fonts.ts'
6
+
7
+ import { type Element, element, useSurface } from '../wrappers.tsx'
8
+ import { shows } from './each.works.tsx'
9
+
10
+
11
+ export interface EachProps {
12
+ readonly rows: readonly object[]
13
+ readonly by: string
14
+ readonly children: (row: Row, at: number) => ReactNode
15
+ readonly settles?: string
16
+ readonly paced?: string
17
+ readonly dispatch?: string
18
+ readonly input?: Readonly<Record<string, unknown>>
19
+ readonly invokeOn?: 'press' | 'shown'
20
+ }
21
+
22
+ export const Each: Element<'Each', EachProps> = element(
23
+ 'Each',
24
+ (props: EachProps, className) => {
25
+ const surface = useSurface({ className: roleClass(undefined, className),
26
+ invokes: { ...(props.dispatch === undefined ? {} : { dispatch: props.dispatch }), ...(props.input === undefined ? {} : { input: props.input }), ...(props.invokeOn === undefined ? {} : { on: props.invokeOn }) }, })
27
+ return (
28
+ shows(props, surface)
29
+ )
30
+ },
31
+ 'e-each',
32
+ {},
33
+ )
@@ -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
+ )