@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,41 @@
1
+ import { seriesColour } from '../series.ts'
2
+ import { num, polyline, str, useRamp, type Row } from '../charts.tsx'
3
+ import type { LineChartProps } from './line-chart.tsx'
4
+
5
+ const WIDE = 300
6
+ const TALL = 120
7
+
8
+ export function lines(props: LineChartProps, _kept?: unknown): readonly Row[] {
9
+ if (props.by === undefined) return [{ group: '' }]
10
+ return [...new Set(props.rows.map((one: Row) => str(one, props.by as string)))].map((group) => ({ group }))
11
+ }
12
+
13
+ export function uses(props: LineChartProps): {
14
+ readonly holds?: readonly string[]
15
+ readonly animates?: boolean
16
+ readonly on?: Readonly<Record<string, unknown>>
17
+ readonly ramp: readonly string[]
18
+ } {
19
+ const ramp = useRamp(lines(props).length)
20
+ return {
21
+ ...(props.rows.length === 0 ? { holds: ['empty'] as const } : {}),
22
+ on: { viewBox: `0 0 ${String(WIDE)} ${String(TALL)}` },
23
+ ramp,
24
+ }
25
+ }
26
+
27
+ export function drawing(
28
+ props: LineChartProps,
29
+ kept: { readonly ramp: readonly string[] },
30
+ row: Row,
31
+ at: number,
32
+ ): Readonly<Record<string, string | number>> {
33
+ const group = str(row, 'group')
34
+ const mine = props.rows.filter((one: Row) => props.by === undefined || str(one, props.by) === group)
35
+ return {
36
+ points: polyline(mine.map((one: Row) => num(one, props.value)), WIDE, TALL),
37
+ fill: 'none',
38
+ stroke: seriesColour(kept.ramp, at),
39
+ strokeWidth: 2,
40
+ }
41
+ }
@@ -12,4 +12,4 @@ export const Link = element('Link', (props, className) => {
12
12
  ],
13
13
  ...(kept.animates === undefined ? {} : { animates: kept.animates }), });
14
14
  return (_jsx("a", { className: surface.className, ...surface.marks, ...surface.invokes, ...surface.a11y, ...(kept.on ?? {}), children: surface.text(props.labelKey) }));
15
- }, 'e-link');
15
+ }, 'e-link', { "linkInline": "thread-link-inline" });
@@ -0,0 +1,37 @@
1
+ 'use client'
2
+
3
+ import type { MessageKey } from '../content.tsx'
4
+ import { roleClass, type FontRoleId } from '../fonts.ts'
5
+
6
+ import { type Element, element, useSurface } from '../wrappers.tsx'
7
+ import { uses } from './link.works.ts'
8
+
9
+
10
+ export interface LinkProps {
11
+ readonly navigateTo: string
12
+ readonly labelKey: MessageKey
13
+ readonly is?: FontRoleId
14
+ readonly dispatch?: string
15
+ readonly input?: Readonly<Record<string, unknown>>
16
+ readonly invokeOn?: 'press' | 'shown'
17
+ }
18
+
19
+ export const Link: Element<'Link', LinkProps> = element(
20
+ 'Link',
21
+ (props: LinkProps, className) => {
22
+ const kept = uses(props)
23
+ const surface = useSurface({ className: roleClass(props.is, className),
24
+ invokes: { ...(props.dispatch === undefined ? {} : { dispatch: props.dispatch }), ...(props.input === undefined ? {} : { input: props.input }), ...(props.invokeOn === undefined ? {} : { on: props.invokeOn }) },
25
+ holds: [
26
+ ...(kept.holds ?? []),
27
+ ],
28
+ ...(kept.animates === undefined ? {} : { animates: kept.animates }), })
29
+ return (
30
+ <a className={surface.className} {...surface.marks} {...surface.invokes} {...surface.a11y} {...(kept.on ?? {})}>
31
+ {surface.text(props.labelKey)}
32
+ </a>
33
+ )
34
+ },
35
+ 'e-link',
36
+ {"linkInline":"thread-link-inline"},
37
+ )
@@ -0,0 +1,25 @@
1
+ import type { MouseEvent } from 'react'
2
+
3
+ import { usePress } from '../wrappers.tsx'
4
+ import type { LinkProps } from './link.tsx'
5
+
6
+ export function uses(props: LinkProps): {
7
+ readonly holds?: readonly string[]
8
+ readonly animates?: boolean
9
+ readonly on: {
10
+ readonly href: string
11
+ readonly onClick: (event: MouseEvent<HTMLAnchorElement>) => void
12
+ }
13
+ } {
14
+ const press = usePress({ navigateTo: props.navigateTo }, { element: 'Link', name: props.labelKey })
15
+ return {
16
+ on: {
17
+ href: `#${props.navigateTo}`,
18
+ onClick: (event: MouseEvent<HTMLAnchorElement>): void => {
19
+ if (event.metaKey || event.ctrlKey || event.shiftKey || event.button !== 0) return
20
+ event.preventDefault()
21
+ press?.()
22
+ },
23
+ },
24
+ }
25
+ }
@@ -19,4 +19,4 @@ export const Media = element('Media', (props, className) => {
19
19
  return null;
20
20
  }
21
21
  })());
22
- }, 'e-media');
22
+ }, 'e-media', { "card": "thread-card", "framed": "thread-framed" });
@@ -0,0 +1,56 @@
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 MediaProps {
10
+ readonly as: 'image' | 'video' | 'audio' | 'file'
11
+ readonly src: string
12
+ readonly altKey: MessageKey
13
+ readonly dispatch?: string
14
+ readonly input?: Readonly<Record<string, unknown>>
15
+ readonly invokeOn?: 'press' | 'shown'
16
+ }
17
+
18
+ export const Media: Element<'Media', MediaProps> = element(
19
+ 'Media',
20
+ (props: MediaProps, 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
+ (() => {
25
+ switch (props.as) {
26
+ case 'image':
27
+ return (
28
+ <img className={surface.className} {...surface.marks} {...surface.invokes} {...surface.a11y} src={props.src} alt={surface.text(props.altKey)} />
29
+ )
30
+ case 'video':
31
+ return (
32
+ <video className={surface.className} {...surface.marks} {...surface.invokes} {...surface.a11y} src={props.src} aria-label={surface.text(props.altKey)} controls preload="metadata">
33
+ {null}
34
+ </video>
35
+ )
36
+ case 'audio':
37
+ return (
38
+ <audio className={surface.className} {...surface.marks} {...surface.invokes} {...surface.a11y} src={props.src} aria-label={surface.text(props.altKey)} controls preload="metadata">
39
+ {null}
40
+ </audio>
41
+ )
42
+ case 'file':
43
+ return (
44
+ <a className={surface.className} {...surface.marks} {...surface.invokes} {...surface.a11y} href={props.src} download>
45
+ {surface.text(props.altKey)}
46
+ </a>
47
+ )
48
+ default:
49
+ return null
50
+ }
51
+ })()
52
+ )
53
+ },
54
+ 'e-media',
55
+ {"card":"thread-card","framed":"thread-framed"},
56
+ )
@@ -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 { Presented, MenuRow } from "./menu.works.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 { measured, opener, portal, listing, row, uses } from "./menu.works.js";
8
8
  export const Menu = element('Menu', (props, className) => {
9
9
  const kept = uses(props);
@@ -15,4 +15,4 @@ export const Menu = element('Menu', (props, className) => {
15
15
  measures: measured(props, kept),
16
16
  ...(kept.animates === undefined ? {} : { animates: kept.animates }), });
17
17
  return (_jsxs("span", { ...surface.marks, ...surface.invokes, ...surface.a11y, ...(kept.on ?? {}), style: surface.measures, children: [_jsx("button", { className: `e-menu-opener ${surface.className ?? ''}`, ...opener(props, kept), children: surface.text(props.labelKey) }), _jsx(Presented, { ...portal(props, kept), children: _jsx("div", { className: "e-menu-layer", ...listing(props, kept), children: _jsx(Each, { rows: props.items, by: props.labelKey, children: (each0, at) => (_jsx(MenuRow, { ...row(props, kept, each0, at) })) }) }) })] }));
18
- }, 'e-menu');
18
+ }, 'e-menu', { "card": "thread-card" });
@@ -0,0 +1,50 @@
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 { Presented, MenuRow } from './menu.works.tsx'
7
+
8
+ import { type Element, element, useSurface } from '../wrappers.tsx'
9
+ import { Each } from '../elements/each.tsx'
10
+ import { measured, opener, portal, listing, row, uses } from './menu.works.tsx'
11
+
12
+
13
+ export interface MenuProps {
14
+ readonly labelKey: MessageKey
15
+ readonly items: readonly Row[]
16
+ readonly side?: string
17
+ readonly dispatch?: string
18
+ readonly input?: Readonly<Record<string, unknown>>
19
+ readonly invokeOn?: 'press' | 'shown'
20
+ }
21
+
22
+ export const Menu: Element<'Menu', MenuProps> = element(
23
+ 'Menu',
24
+ (props: MenuProps, 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
+ measures: measured(props, kept),
32
+ ...(kept.animates === undefined ? {} : { animates: kept.animates }), })
33
+ return (
34
+ <span {...surface.marks} {...surface.invokes} {...surface.a11y} {...(kept.on ?? {})} style={surface.measures}>
35
+ <button className={`e-menu-opener ${surface.className ?? ''}`} {...opener(props, kept)}>{surface.text(props.labelKey)}</button>
36
+ <Presented {...portal(props, kept)}>
37
+ <div className="e-menu-layer" {...listing(props, kept)}>
38
+ <Each rows={props.items} by={props.labelKey}>
39
+ {(each0, at) => (
40
+ <MenuRow {...row(props, kept, each0, at)}></MenuRow>
41
+ )}
42
+ </Each>
43
+ </div>
44
+ </Presented>
45
+ </span>
46
+ )
47
+ },
48
+ 'e-menu',
49
+ {"card":"thread-card"},
50
+ )
@@ -0,0 +1,153 @@
1
+ 'use client'
2
+
3
+ import { useEffect, useId, useRef, useState, type ReactNode } from 'react'
4
+
5
+ import type { MessageKey } from '../content.tsx'
6
+ import { positionOf, type AnchorOptions, type Position } from '../positioning.ts'
7
+ import { ITEM_ATTR, useRovingFocus } from '../roving-focus.ts'
8
+ import { pressOf, usePress, useSurface } from '../wrappers.tsx'
9
+ import { drawn } from './glyph.works.tsx'
10
+ import type { MenuProps } from './menu.tsx'
11
+
12
+ export interface MenuItem {
13
+ readonly labelKey: MessageKey
14
+ readonly dispatch?: string
15
+ readonly navigateTo?: string
16
+ readonly icon?: string
17
+ readonly disabled?: boolean
18
+ }
19
+
20
+ export { Presented } from '../presented.tsx'
21
+ import type { PresentedProps } from '../presented.tsx'
22
+
23
+ const SIDE = 'bottom'
24
+
25
+ export function MenuRow({
26
+ item,
27
+ onDone,
28
+ }: {
29
+ readonly item: MenuItem
30
+ readonly onDone: () => void
31
+ }): ReactNode {
32
+
33
+ const { text } = useSurface({})
34
+ const press = usePress(pressOf(item), { element: 'Menu', name: item.labelKey })
35
+ return (
36
+ <button
37
+ {...{ [ITEM_ATTR]: '' }}
38
+ type="button"
39
+ role="menuitem"
40
+ disabled={item.disabled === true}
41
+ onClick={() => {
42
+ press?.()
43
+ onDone()
44
+ }}
45
+ >
46
+ {item.icon === undefined ? null : drawn(item.icon, { 'aria-hidden': true })}
47
+ {text(item.labelKey)}
48
+ </button>
49
+ )
50
+ }
51
+
52
+ type Kept = {
53
+ readonly trigger: React.RefObject<HTMLButtonElement | null>
54
+ readonly layer: React.RefObject<HTMLDivElement | null>
55
+ readonly open: boolean
56
+ readonly at: Position | null
57
+ readonly names: { readonly trigger: string; readonly menu: string }
58
+ readonly show: (to: boolean | ((was: boolean) => boolean)) => void
59
+ }
60
+
61
+ export function uses(props: MenuProps): {
62
+ readonly holds?: readonly string[]
63
+ readonly animates?: boolean
64
+ readonly on?: Readonly<Record<string, unknown>>
65
+ readonly trigger: React.RefObject<HTMLButtonElement | null>
66
+ readonly layer: React.RefObject<HTMLDivElement | null>
67
+ readonly open: boolean
68
+ readonly at: Position | null
69
+ readonly names: { readonly trigger: string; readonly menu: string }
70
+ readonly show: (to: boolean | ((was: boolean) => boolean)) => void
71
+ } {
72
+ const trigger = useRef<HTMLButtonElement>(null)
73
+ const layer = useRef<HTMLDivElement>(null)
74
+ const [open, setOpen] = useState(false)
75
+ const [at, setAt] = useState<Position | null>(null)
76
+ const base = useId()
77
+
78
+ useRovingFocus(layer, { orientation: 'vertical', wrap: true, active: open })
79
+
80
+ useEffect(() => {
81
+ if (!open) return undefined
82
+ const anchor = trigger.current
83
+ const floating = layer.current
84
+ if (anchor === null || floating === null) return undefined
85
+ let live = true
86
+ void positionOf(anchor, floating, {
87
+ placement: (props.side ?? SIDE) as NonNullable<AnchorOptions['placement']>,
88
+ }).then((position) => {
89
+ if (live) setAt(position)
90
+ })
91
+ return () => {
92
+ live = false
93
+ }
94
+ }, [open, props.side])
95
+
96
+ return {
97
+ trigger,
98
+ layer,
99
+ open,
100
+ at,
101
+ names: { trigger: `${base}-trigger`, menu: `${base}-menu` },
102
+ show: setOpen,
103
+ }
104
+ }
105
+
106
+ export function opener(_props: MenuProps, kept: Kept): Readonly<Record<string, unknown>> {
107
+ return {
108
+ ref: kept.trigger,
109
+ type: 'button',
110
+ id: kept.names.trigger,
111
+ 'aria-haspopup': 'menu',
112
+ 'aria-expanded': kept.open,
113
+ ...(kept.open ? { 'aria-controls': kept.names.menu } : {}),
114
+ onPointerDown: (event: React.PointerEvent<HTMLButtonElement>): void => {
115
+ if (event.button > 0 || event.ctrlKey) return
116
+ event.currentTarget.focus()
117
+ event.preventDefault()
118
+ kept.show((was) => !was)
119
+ },
120
+ onKeyDown: (event: React.KeyboardEvent<HTMLButtonElement>): void => {
121
+ if (event.key !== 'Enter' && event.key !== ' ' && event.key !== 'ArrowDown') return
122
+ event.preventDefault()
123
+ if (event.key === 'ArrowDown') kept.show(true)
124
+ else kept.show((was) => !was)
125
+ },
126
+ }
127
+ }
128
+
129
+ export function portal(_props: MenuProps, kept: Kept): PresentedProps {
130
+ return { presentation: 'popover', open: kept.open, onDismiss: () => kept.show(false), children: null }
131
+ }
132
+
133
+ export function listing(props: MenuProps, kept: Kept): Readonly<Record<string, unknown>> {
134
+ return {
135
+ ref: kept.layer,
136
+ id: kept.names.menu,
137
+ role: 'menu',
138
+ 'aria-labelledby': kept.names.trigger,
139
+ 'data-side': kept.at?.placement ?? props.side ?? SIDE,
140
+ onKeyDown: (event: React.KeyboardEvent<HTMLDivElement>): void => {
141
+ if (event.key !== 'Tab') return
142
+ kept.show(false)
143
+ },
144
+ }
145
+ }
146
+
147
+ export function row(_props: MenuProps, kept: Kept, item: Record<string, unknown>, _at: number): { readonly item: MenuItem; readonly onDone: () => void } {
148
+ return { item: item as unknown as MenuItem, onDone: () => kept.show(false) }
149
+ }
150
+
151
+ export function measured(_props: MenuProps, kept: Kept): Readonly<Record<string, string | number>> {
152
+ return kept.at === null ? {} : { x: `${String(kept.at.x)}px`, y: `${String(kept.at.y)}px` }
153
+ }
@@ -6,4 +6,4 @@ export const Money = element('Money', (props, className) => {
6
6
  const surface = useSurface({ className: roleClass(props.is, 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-money');
9
+ }, 'e-money', {});
@@ -0,0 +1,28 @@
1
+ 'use client'
2
+
3
+ import { roleClass, type FontRoleId } from '../fonts.ts'
4
+
5
+ import { type Element, element, useSurface } from '../wrappers.tsx'
6
+ import { shows } from './money.works.ts'
7
+
8
+
9
+ export interface MoneyProps {
10
+ readonly value?: unknown
11
+ readonly is?: FontRoleId
12
+ readonly dispatch?: string
13
+ readonly input?: Readonly<Record<string, unknown>>
14
+ readonly invokeOn?: 'press' | 'shown'
15
+ }
16
+
17
+ export const Money: Element<'Money', MoneyProps> = element(
18
+ 'Money',
19
+ (props: MoneyProps, className) => {
20
+ const surface = useSurface({ className: roleClass(props.is, className),
21
+ invokes: { ...(props.dispatch === undefined ? {} : { dispatch: props.dispatch }), ...(props.input === undefined ? {} : { input: props.input }), ...(props.invokeOn === undefined ? {} : { on: props.invokeOn }) }, })
22
+ return (
23
+ shows(props, surface)
24
+ )
25
+ },
26
+ 'e-money',
27
+ {},
28
+ )
@@ -0,0 +1,9 @@
1
+ import type { ReactNode } from 'react'
2
+
3
+ import { renderValue } from '../content.tsx'
4
+ import type { Surface } from '../door.ts'
5
+ import type { MoneyProps } from './money.tsx'
6
+
7
+ export function shows(props: MoneyProps, surface: Surface): ReactNode {
8
+ return renderValue({ validator: 'money', value: props.value }, surface)
9
+ }
@@ -3,10 +3,10 @@ import { jsx as _jsx } from "react/jsx-runtime";
3
3
  import { roleClass } from "../fonts.js";
4
4
  import { Link } from "./link.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 { step, steps } from "./pagination.works.js";
8
8
  export const Pagination = element('Pagination', (props, className) => {
9
9
  const surface = useSurface({ className: roleClass(undefined, className),
10
10
  invokes: { ...(props.dispatch === undefined ? {} : { dispatch: props.dispatch }), ...(props.input === undefined ? {} : { input: props.input }), ...(props.invokeOn === undefined ? {} : { on: props.invokeOn }) }, });
11
11
  return (_jsx("nav", { className: surface.className, ...surface.marks, ...surface.invokes, ...surface.a11y, "aria-label": surface.text('nav.pagination'), children: _jsx(Each, { rows: steps(props), by: 'id', children: (each0, at) => (_jsx(Link.linkInline, { ...step(props, each0, at) })) }) }));
12
- }, 'e-pagination');
12
+ }, 'e-pagination', { "tabBar": "thread-tab-bar" });
@@ -0,0 +1,38 @@
1
+ 'use client'
2
+
3
+ import { roleClass } from '../fonts.ts'
4
+ import type { MapIsland } from '../islands/map.ts'
5
+ import { Link } from './link.tsx'
6
+
7
+ import { type Element, element, useSurface } from '../wrappers.tsx'
8
+ import { Each } from '../elements/each.tsx'
9
+ import { step, steps } from './pagination.works.ts'
10
+
11
+
12
+ export interface PaginationProps {
13
+ readonly map: MapIsland
14
+ readonly sequence: readonly string[]
15
+ readonly current: string
16
+ readonly dispatch?: string
17
+ readonly input?: Readonly<Record<string, unknown>>
18
+ readonly invokeOn?: 'press' | 'shown'
19
+ }
20
+
21
+ export const Pagination: Element<'Pagination', PaginationProps> = element(
22
+ 'Pagination',
23
+ (props: PaginationProps, className) => {
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
+ return (
27
+ <nav className={surface.className} {...surface.marks} {...surface.invokes} {...surface.a11y} aria-label={surface.text('nav.pagination')}>
28
+ <Each rows={steps(props)} by={'id'}>
29
+ {(each0, at) => (
30
+ <Link.linkInline {...step(props, each0, at)}></Link.linkInline>
31
+ )}
32
+ </Each>
33
+ </nav>
34
+ )
35
+ },
36
+ 'e-pagination',
37
+ {"tabBar":"thread-tab-bar"},
38
+ )
@@ -0,0 +1,20 @@
1
+ import type { Row } from '../charts.tsx'
2
+ import type { LinkProps } from './link.tsx'
3
+ import type { PaginationProps } from './pagination.tsx'
4
+
5
+ export function steps(props: PaginationProps): readonly Row[] {
6
+ const known = new Map(props.map.screens.map((one) => [one.id, one]))
7
+ const walk = props.sequence.filter((id) => known.has(id))
8
+ const at = walk.indexOf(props.current)
9
+ if (at === -1) return []
10
+
11
+ const either = [
12
+ ...(at > 0 ? [{ id: walk[at - 1] as string, way: 'previous' }] : []),
13
+ ...(at < walk.length - 1 ? [{ id: walk[at + 1] as string, way: 'next' }] : []),
14
+ ]
15
+ return either.map((one) => ({ ...one, nameKey: known.get(one.id)?.nameKey ?? `nav.${one.way}` }))
16
+ }
17
+
18
+ export function step(_props: PaginationProps, row: Row, _at: number): LinkProps {
19
+ return { navigateTo: String(row['id']), labelKey: String(row['nameKey']), is: 'label' }
20
+ }
@@ -8,4 +8,4 @@ export const Progress = element('Progress', (props, className) => {
8
8
  invokes: { ...(props.dispatch === undefined ? {} : { dispatch: props.dispatch }), ...(props.input === undefined ? {} : { input: props.input }), ...(props.invokeOn === undefined ? {} : { on: props.invokeOn }) },
9
9
  measures: measured(props), });
10
10
  return (_jsx("div", { className: surface.className, ...surface.marks, ...surface.invokes, ...surface.a11y, role: "progressbar", "aria-valuenow": Number(measured(props).fraction), "aria-valuemin": 0, "aria-valuemax": 100, style: surface.measures, "aria-label": surface.text(props.labelKey), children: _jsx("span", { className: "e-progress-bar" }) }));
11
- }, 'e-progress');
11
+ }, 'e-progress', { "statTile": "thread-stat-tile" });
@@ -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 { measured } from './progress.works.ts'
8
+
9
+
10
+ export interface ProgressProps {
11
+ readonly value?: number
12
+ readonly target?: number
13
+ readonly labelKey: MessageKey
14
+ readonly dispatch?: string
15
+ readonly input?: Readonly<Record<string, unknown>>
16
+ readonly invokeOn?: 'press' | 'shown'
17
+ }
18
+
19
+ export const Progress: Element<'Progress', ProgressProps> = element(
20
+ 'Progress',
21
+ (props: ProgressProps, 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
+ measures: measured(props), })
25
+ return (
26
+ <div className={surface.className} {...surface.marks} {...surface.invokes} {...surface.a11y} role="progressbar" aria-valuenow={Number(measured(props).fraction)} aria-valuemin={0} aria-valuemax={100} style={surface.measures} aria-label={surface.text(props.labelKey)}>
27
+ <span className="e-progress-bar"></span>
28
+ </div>
29
+ )
30
+ },
31
+ 'e-progress',
32
+ {"statTile":"thread-stat-tile"},
33
+ )
@@ -0,0 +1,8 @@
1
+ import type { ProgressProps } from './progress.tsx'
2
+
3
+ export function measured(props: ProgressProps): Readonly<Record<string, string | number>> {
4
+ const target = props.target ?? 0
5
+ const part = target <= 0 ? 0 : Math.min(1, Math.max(0, (props.value ?? 0) / target))
6
+ const whole = Math.round(part * 100)
7
+ return { fraction: whole, span: `${String(whole)}%` }
8
+ }
@@ -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, option, uses } from "./radio-group.works.js";
7
7
  export const RadioGroup = element('RadioGroup', (props, className) => {
8
8
  const kept = uses(props);
@@ -13,4 +13,4 @@ export const RadioGroup = element('RadioGroup', (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 ?? {}), role: "radiogroup", "aria-label": surface.text(props.labelKey), children: _jsx(Each, { rows: props.options, by: 'value', children: (each0, at) => (_jsxs("label", { children: [_jsx("input", { ...option(props, kept, each0, at), type: "radio", disabled: surface.marked('disabled') }), _jsx("span", { children: shown(props, each0, surface, kept).caption })] })) }) }));
16
- }, 'e-radio-group');
16
+ }, 'e-radio-group', { "choice": "thread-choice" });
@@ -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
+
7
+ import { type Element, element, useSurface } from '../wrappers.tsx'
8
+ import { Each } from '../elements/each.tsx'
9
+ import { shown, option, uses } from './radio-group.works.ts'
10
+
11
+
12
+ export interface RadioGroupProps {
13
+ readonly name: string
14
+ readonly labelKey: MessageKey
15
+ readonly options: readonly Row[]
16
+ readonly chosen?: string
17
+ readonly onValue?: (said: string) => void
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 RadioGroup: Element<'RadioGroup', RadioGroupProps> = element(
25
+ 'RadioGroup',
26
+ (props: RadioGroupProps, 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="radiogroup" aria-label={surface.text(props.labelKey)}>
36
+ <Each rows={props.options} by={'value'}>
37
+ {(each0, at) => (
38
+ <label>
39
+ <input {...option(props, kept, each0, at)} type="radio" disabled={surface.marked('disabled')} />
40
+ <span>{shown(props, each0, surface, kept).caption}</span>
41
+ </label>
42
+ )}
43
+ </Each>
44
+ </div>
45
+ )
46
+ },
47
+ 'e-radio-group',
48
+ {"choice":"thread-choice"},
49
+ )