@prestack/thread-ui 0.1.10 → 0.1.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (209) hide show
  1. package/CHANGELOG.md +187 -0
  2. package/dist/ui/attach.js +6 -7
  3. package/dist/ui/compositions.d.ts +0 -8
  4. package/dist/ui/compositions.js +3 -166
  5. package/dist/ui/generators/routes.js +1 -0
  6. package/package.json +2 -2
  7. package/dist/ui/animations/anm_appear.ts +0 -13
  8. package/dist/ui/animations/anm_blur_in.ts +0 -19
  9. package/dist/ui/animations/anm_blur_out.ts +0 -18
  10. package/dist/ui/animations/anm_dismiss.ts +0 -18
  11. package/dist/ui/animations/anm_drop.ts +0 -19
  12. package/dist/ui/animations/anm_fall.ts +0 -18
  13. package/dist/ui/animations/anm_flash.ts +0 -18
  14. package/dist/ui/animations/anm_glow.ts +0 -16
  15. package/dist/ui/animations/anm_lift.ts +0 -16
  16. package/dist/ui/animations/anm_nudge.ts +0 -16
  17. package/dist/ui/animations/anm_pop_in.ts +0 -23
  18. package/dist/ui/animations/anm_pop_out.ts +0 -22
  19. package/dist/ui/animations/anm_press.ts +0 -12
  20. package/dist/ui/animations/anm_pulse.ts +0 -13
  21. package/dist/ui/animations/anm_rise.ts +0 -19
  22. package/dist/ui/animations/anm_shake.ts +0 -13
  23. package/dist/ui/animations/anm_slide_in_left.ts +0 -19
  24. package/dist/ui/animations/anm_slide_in_right.ts +0 -19
  25. package/dist/ui/animations/anm_slide_out_left.ts +0 -18
  26. package/dist/ui/animations/anm_slide_out_right.ts +0 -18
  27. package/dist/ui/animations/anm_spin.ts +0 -12
  28. package/dist/ui/animations/anm_swell.ts +0 -16
  29. package/dist/ui/animations/anm_wobble.ts +0 -24
  30. package/dist/ui/animations/anm_zoom_in.ts +0 -19
  31. package/dist/ui/animations/anm_zoom_out.ts +0 -18
  32. package/dist/ui/animations.ts +0 -308
  33. package/dist/ui/attach.ts +0 -553
  34. package/dist/ui/charts.tsx +0 -69
  35. package/dist/ui/checked.ts +0 -32
  36. package/dist/ui/collector.ts +0 -140
  37. package/dist/ui/compositions.ts +0 -613
  38. package/dist/ui/consent.ts +0 -55
  39. package/dist/ui/content.tsx +0 -27
  40. package/dist/ui/contrast.ts +0 -27
  41. package/dist/ui/controls.tsx +0 -25
  42. package/dist/ui/disclosure.tsx +0 -20
  43. package/dist/ui/dismiss-layer.ts +0 -87
  44. package/dist/ui/door.ts +0 -140
  45. package/dist/ui/drag.ts +0 -316
  46. package/dist/ui/elements/accordion.tsx +0 -47
  47. package/dist/ui/elements/accordion.works.ts +0 -66
  48. package/dist/ui/elements/aspect-ratio.tsx +0 -31
  49. package/dist/ui/elements/aspect-ratio.works.ts +0 -5
  50. package/dist/ui/elements/avatar.tsx +0 -34
  51. package/dist/ui/elements/avatar.works.ts +0 -16
  52. package/dist/ui/elements/badge.tsx +0 -33
  53. package/dist/ui/elements/badge.works.ts +0 -9
  54. package/dist/ui/elements/bars.tsx +0 -50
  55. package/dist/ui/elements/bars.works.ts +0 -29
  56. package/dist/ui/elements/box.tsx +0 -36
  57. package/dist/ui/elements/breadcrumb.tsx +0 -38
  58. package/dist/ui/elements/breadcrumb.works.ts +0 -24
  59. package/dist/ui/elements/button.tsx +0 -46
  60. package/dist/ui/elements/button.works.tsx +0 -52
  61. package/dist/ui/elements/column.tsx +0 -29
  62. package/dist/ui/elements/consent.tsx +0 -71
  63. package/dist/ui/elements/consent.works.tsx +0 -132
  64. package/dist/ui/elements/donut.tsx +0 -45
  65. package/dist/ui/elements/donut.works.ts +0 -42
  66. package/dist/ui/elements/each.tsx +0 -34
  67. package/dist/ui/elements/each.works.tsx +0 -82
  68. package/dist/ui/elements/empty.tsx +0 -33
  69. package/dist/ui/elements/field.tsx +0 -45
  70. package/dist/ui/elements/field.works.ts +0 -65
  71. package/dist/ui/elements/funnel.tsx +0 -50
  72. package/dist/ui/elements/funnel.works.ts +0 -37
  73. package/dist/ui/elements/gauge.tsx +0 -40
  74. package/dist/ui/elements/gauge.works.ts +0 -42
  75. package/dist/ui/elements/glyph.tsx +0 -30
  76. package/dist/ui/elements/glyph.works.tsx +0 -42
  77. package/dist/ui/elements/grid.tsx +0 -29
  78. package/dist/ui/elements/heatmap.tsx +0 -48
  79. package/dist/ui/elements/heatmap.works.ts +0 -24
  80. package/dist/ui/elements/hit-box.tsx +0 -47
  81. package/dist/ui/elements/hit-box.works.tsx +0 -576
  82. package/dist/ui/elements/image.tsx +0 -29
  83. package/dist/ui/elements/line-chart.tsx +0 -45
  84. package/dist/ui/elements/line-chart.works.ts +0 -41
  85. package/dist/ui/elements/link.tsx +0 -38
  86. package/dist/ui/elements/link.works.ts +0 -25
  87. package/dist/ui/elements/media.tsx +0 -57
  88. package/dist/ui/elements/menu.tsx +0 -51
  89. package/dist/ui/elements/menu.works.tsx +0 -154
  90. package/dist/ui/elements/money.tsx +0 -36
  91. package/dist/ui/elements/money.works.ts +0 -9
  92. package/dist/ui/elements/pagination.tsx +0 -39
  93. package/dist/ui/elements/pagination.works.ts +0 -20
  94. package/dist/ui/elements/progress.tsx +0 -34
  95. package/dist/ui/elements/progress.works.ts +0 -8
  96. package/dist/ui/elements/radio-group.tsx +0 -50
  97. package/dist/ui/elements/radio-group.works.ts +0 -33
  98. package/dist/ui/elements/row.tsx +0 -29
  99. package/dist/ui/elements/scatter.tsx +0 -42
  100. package/dist/ui/elements/scatter.works.ts +0 -38
  101. package/dist/ui/elements/select.tsx +0 -43
  102. package/dist/ui/elements/select.works.ts +0 -5
  103. package/dist/ui/elements/separator.tsx +0 -31
  104. package/dist/ui/elements/skeleton.tsx +0 -31
  105. package/dist/ui/elements/slider.tsx +0 -34
  106. package/dist/ui/elements/slider.works.ts +0 -5
  107. package/dist/ui/elements/sparkline.tsx +0 -37
  108. package/dist/ui/elements/sparkline.works.ts +0 -32
  109. package/dist/ui/elements/spinner.tsx +0 -33
  110. package/dist/ui/elements/stack.tsx +0 -29
  111. package/dist/ui/elements/stat.tsx +0 -36
  112. package/dist/ui/elements/stat.works.ts +0 -14
  113. package/dist/ui/elements/surface.tsx +0 -36
  114. package/dist/ui/elements/surface.works.ts +0 -9
  115. package/dist/ui/elements/table.tsx +0 -62
  116. package/dist/ui/elements/table.works.ts +0 -30
  117. package/dist/ui/elements/tabs.tsx +0 -47
  118. package/dist/ui/elements/tabs.works.ts +0 -84
  119. package/dist/ui/elements/text.tsx +0 -33
  120. package/dist/ui/elements/textarea.tsx +0 -38
  121. package/dist/ui/elements/textarea.works.ts +0 -5
  122. package/dist/ui/elements/time.tsx +0 -40
  123. package/dist/ui/elements/time.works.ts +0 -29
  124. package/dist/ui/elements/toggle.tsx +0 -34
  125. package/dist/ui/elements/toggle.works.ts +0 -5
  126. package/dist/ui/elements/tooltip.tsx +0 -40
  127. package/dist/ui/elements/tooltip.works.ts +0 -77
  128. package/dist/ui/elements/value.tsx +0 -38
  129. package/dist/ui/elements/value.works.ts +0 -9
  130. package/dist/ui/elements.ts +0 -127
  131. package/dist/ui/focus-scope.ts +0 -121
  132. package/dist/ui/fonts.ts +0 -355
  133. package/dist/ui/generators/dev.d.ts +0 -1
  134. package/dist/ui/generators/dev.js +0 -62
  135. package/dist/ui/generators/dev.ts +0 -75
  136. package/dist/ui/generators/elements.ts +0 -796
  137. package/dist/ui/generators/i18n.ts +0 -283
  138. package/dist/ui/generators/icons.ts +0 -207
  139. package/dist/ui/generators/react.ts +0 -267
  140. package/dist/ui/generators/routes.ts +0 -847
  141. package/dist/ui/generators/show.ts +0 -261
  142. package/dist/ui/generators/styles.ts +0 -677
  143. package/dist/ui/generators/variants.ts +0 -196
  144. package/dist/ui/i18n/catalogs/en.ts +0 -191
  145. package/dist/ui/i18n/format.ts +0 -221
  146. package/dist/ui/i18n/locales.ts +0 -33
  147. package/dist/ui/i18n/parse.ts +0 -315
  148. package/dist/ui/i18n/translator.ts +0 -31
  149. package/dist/ui/i18n/types.ts +0 -96
  150. package/dist/ui/icons.ts +0 -331
  151. package/dist/ui/islands/map.ts +0 -58
  152. package/dist/ui/localizations.ts +0 -75
  153. package/dist/ui/material.ts +0 -236
  154. package/dist/ui/materials/mat_bordered.ts +0 -13
  155. package/dist/ui/materials/mat_ceramic.ts +0 -13
  156. package/dist/ui/materials/mat_clay.ts +0 -13
  157. package/dist/ui/materials/mat_elevated.ts +0 -24
  158. package/dist/ui/materials/mat_flat.ts +0 -34
  159. package/dist/ui/materials/mat_glass.ts +0 -20
  160. package/dist/ui/materials/mat_lit.ts +0 -13
  161. package/dist/ui/materials/mat_moulded.ts +0 -11
  162. package/dist/ui/materials.ts +0 -197
  163. package/dist/ui/modes.ts +0 -44
  164. package/dist/ui/navigation.tsx +0 -29
  165. package/dist/ui/positioning.ts +0 -90
  166. package/dist/ui/presented.tsx +0 -119
  167. package/dist/ui/provider.tsx +0 -184
  168. package/dist/ui/render-loop.ts +0 -133
  169. package/dist/ui/roving-focus.ts +0 -161
  170. package/dist/ui/screens.ts +0 -141
  171. package/dist/ui/scroll-lock.ts +0 -28
  172. package/dist/ui/series.ts +0 -97
  173. package/dist/ui/signals.ts +0 -386
  174. package/dist/ui/styleguide.ts +0 -231
  175. package/dist/ui/styleguides.ts +0 -374
  176. package/dist/ui/themes.ts +0 -22
  177. package/dist/ui/token.ts +0 -78
  178. package/dist/ui/tokens/shadow.ts +0 -25
  179. package/dist/ui/tokens.ts +0 -234
  180. package/dist/ui/validators.ts +0 -232
  181. package/dist/ui/variant.ts +0 -349
  182. package/dist/ui/variants/var_avatar-round.ts +0 -14
  183. package/dist/ui/variants/var_bubble-me.ts +0 -14
  184. package/dist/ui/variants/var_bubble-them.ts +0 -13
  185. package/dist/ui/variants/var_button-border.ts +0 -76
  186. package/dist/ui/variants/var_button-cta.ts +0 -76
  187. package/dist/ui/variants/var_button-destructive-ghost.ts +0 -72
  188. package/dist/ui/variants/var_button-destructive.ts +0 -76
  189. package/dist/ui/variants/var_button-fill.ts +0 -76
  190. package/dist/ui/variants/var_button-ghost.ts +0 -72
  191. package/dist/ui/variants/var_button-glass.ts +0 -55
  192. package/dist/ui/variants/var_button-normal.ts +0 -76
  193. package/dist/ui/variants/var_button-quiet.ts +0 -74
  194. package/dist/ui/variants/var_card.ts +0 -17
  195. package/dist/ui/variants/var_chip.ts +0 -13
  196. package/dist/ui/variants/var_choice.ts +0 -15
  197. package/dist/ui/variants/var_framed.ts +0 -10
  198. package/dist/ui/variants/var_glyph-plain.ts +0 -11
  199. package/dist/ui/variants/var_input.ts +0 -43
  200. package/dist/ui/variants/var_link-inline.ts +0 -29
  201. package/dist/ui/variants/var_list-row.ts +0 -34
  202. package/dist/ui/variants/var_rule.ts +0 -10
  203. package/dist/ui/variants/var_skeleton-block.ts +0 -12
  204. package/dist/ui/variants/var_spinner-turning.ts +0 -12
  205. package/dist/ui/variants/var_stat-tile.ts +0 -13
  206. package/dist/ui/variants/var_tab-bar.ts +0 -15
  207. package/dist/ui/variants/var_tap-target.ts +0 -25
  208. package/dist/ui/variants.ts +0 -64
  209. package/dist/ui/wrappers.tsx +0 -429
@@ -1,132 +0,0 @@
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
- }
@@ -1,45 +0,0 @@
1
- 'use client'
2
-
3
- import type { MessageKey } from '../content.tsx'
4
- import type { Row } from '../charts.tsx'
5
- import { roleClass } from '../fonts.ts'
6
-
7
- import type { Dispatching } from '../checked.ts'
8
- import { type Element, element, useSurface } from '../wrappers.tsx'
9
- import { Each } from '../elements/each.tsx'
10
- import { slice, uses } from './donut.works.ts'
11
-
12
-
13
- export interface DonutProps {
14
- readonly rows: readonly Row[]
15
- readonly by: string
16
- readonly value: string
17
- readonly titleKey?: MessageKey
18
- readonly dispatch?: keyof Dispatching
19
- readonly input?: Readonly<Record<string, unknown>>
20
- readonly invokeOn?: 'press' | 'shown'
21
- }
22
-
23
- export const Donut: Element<'Donut', DonutProps> = element(
24
- 'Donut',
25
- (props: DonutProps, className) => {
26
- const kept = uses(props)
27
- const surface = useSurface({ className: roleClass(undefined, className),
28
- invokes: { ...(props.dispatch === undefined ? {} : { dispatch: props.dispatch }), ...(props.input === undefined ? {} : { input: props.input }), ...(props.invokeOn === undefined ? {} : { on: props.invokeOn }) },
29
- holds: [
30
- ...(kept.holds ?? []),
31
- ],
32
- ...(kept.animates === undefined ? {} : { animates: kept.animates }), })
33
- return (
34
- <svg className={surface.className} {...surface.marks} {...surface.invokes} {...surface.a11y} {...(kept.on ?? {})} role="img" {...(props.titleKey === undefined ? {} : { 'aria-label': surface.text(props.titleKey) })}>
35
- <Each rows={props.rows} by={props.by}>
36
- {(each0, at) => (
37
- <circle {...slice(props, kept, each0, at)}></circle>
38
- )}
39
- </Each>
40
- </svg>
41
- )
42
- },
43
- 'e-donut',
44
- {"card":"thread-card"},
45
- )
@@ -1,42 +0,0 @@
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
- }
@@ -1,34 +0,0 @@
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 { Dispatching } from '../checked.ts'
8
- import { type Element, element, useSurface } from '../wrappers.tsx'
9
- import { shows } from './each.works.tsx'
10
-
11
-
12
- export interface EachProps {
13
- readonly rows: readonly object[]
14
- readonly by: string
15
- readonly children: (row: Row, at: number) => ReactNode
16
- readonly settles?: string
17
- readonly paced?: string
18
- readonly dispatch?: keyof Dispatching
19
- readonly input?: Readonly<Record<string, unknown>>
20
- readonly invokeOn?: 'press' | 'shown'
21
- }
22
-
23
- export const Each: Element<'Each', EachProps> = element(
24
- 'Each',
25
- (props: EachProps, className) => {
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
- return (
29
- shows(props, surface)
30
- )
31
- },
32
- 'e-each',
33
- {},
34
- )
@@ -1,82 +0,0 @@
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
- }
@@ -1,33 +0,0 @@
1
- 'use client'
2
-
3
- import type { MessageKey } from '../content.tsx'
4
- import { roleClass } from '../fonts.ts'
5
-
6
- import type { Dispatching } from '../checked.ts'
7
- import { type Element, element, useSurface } from '../wrappers.tsx'
8
-
9
-
10
- export interface EmptyProps {
11
- readonly labelKey: MessageKey
12
- readonly dispatch?: keyof Dispatching
13
- readonly input?: Readonly<Record<string, unknown>>
14
- readonly invokeOn?: 'press' | 'shown'
15
- }
16
-
17
- export const Empty: Element<'Empty', EmptyProps> = element(
18
- 'Empty',
19
- (props: EmptyProps, className) => {
20
- const surface = useSurface({ className: roleClass(undefined, className),
21
- invokes: { ...(props.dispatch === undefined ? {} : { dispatch: props.dispatch }), ...(props.input === undefined ? {} : { input: props.input }), ...(props.invokeOn === undefined ? {} : { on: props.invokeOn }) },
22
- holds: [
23
- 'empty' as const,
24
- ], })
25
- return (
26
- <div className={surface.className} {...surface.marks} {...surface.invokes} {...surface.a11y}>
27
- {surface.text(props.labelKey)}
28
- </div>
29
- )
30
- },
31
- 'e-empty',
32
- {"card":"thread-card"},
33
- )
@@ -1,45 +0,0 @@
1
- 'use client'
2
-
3
- import type { MessageKey } from '../content.tsx'
4
- import { roleClass } from '../fonts.ts'
5
-
6
- import type { Dispatching } from '../checked.ts'
7
- import { type Element, element, useSurface } from '../wrappers.tsx'
8
- import { shown, entry, uses } from './field.works.ts'
9
-
10
-
11
- export interface FieldProps {
12
- readonly name: string
13
- readonly validator: string
14
- readonly labelKey: MessageKey
15
- readonly value?: string
16
- readonly onValue?: (said: string) => void
17
- readonly context?: Readonly<Record<string, unknown>>
18
- readonly autocomplete?: string
19
- readonly disabled?: boolean
20
- readonly dispatch?: keyof Dispatching
21
- readonly input?: Readonly<Record<string, unknown>>
22
- readonly invokeOn?: 'press' | 'shown'
23
- }
24
-
25
- export const Field: Element<'Field', FieldProps> = element(
26
- 'Field',
27
- (props: FieldProps, 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
- <label className={surface.className} {...surface.marks} {...surface.invokes} {...surface.a11y} {...(kept.on ?? {})}>
37
- <span>{surface.text(props.labelKey)}</span>
38
- <input {...entry(props, kept)} />
39
- <span>{shown(props, surface, kept).fault}</span>
40
- </label>
41
- )
42
- },
43
- 'e-field',
44
- {"input":"thread-input"},
45
- )
@@ -1,65 +0,0 @@
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
- }
@@ -1,50 +0,0 @@
1
- 'use client'
2
-
3
- import type { MessageKey } from '../content.tsx'
4
- import type { Row } from '../charts.tsx'
5
- import { roleClass } from '../fonts.ts'
6
- import { measured as marking } from '../charts.tsx'
7
-
8
- import type { Dispatching } from '../checked.ts'
9
- import { type Element, element, useSurface } from '../wrappers.tsx'
10
- import { Each } from '../elements/each.tsx'
11
- import { shown, sized, uses } from './funnel.works.ts'
12
-
13
-
14
- export interface FunnelProps {
15
- readonly rows: readonly Row[]
16
- readonly by: string
17
- readonly value: string
18
- readonly format?: string
19
- readonly titleKey?: MessageKey
20
- readonly dispatch?: keyof Dispatching
21
- readonly input?: Readonly<Record<string, unknown>>
22
- readonly invokeOn?: 'press' | 'shown'
23
- }
24
-
25
- export const Funnel: Element<'Funnel', FunnelProps> = element(
26
- 'Funnel',
27
- (props: FunnelProps, 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))}>
40
- <span>{shown(props, each0, surface, kept).label}</span>
41
- <span>{shown(props, each0, surface, kept).amount}</span>
42
- </span>
43
- )}
44
- </Each>
45
- </div>
46
- )
47
- },
48
- 'e-funnel',
49
- {"card":"thread-card"},
50
- )
@@ -1,37 +0,0 @@
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
- }
@@ -1,40 +0,0 @@
1
- 'use client'
2
-
3
- import type { MessageKey } from '../content.tsx'
4
- import { roleClass } from '../fonts.ts'
5
-
6
- import type { Dispatching } from '../checked.ts'
7
- import { type Element, element, useSurface } from '../wrappers.tsx'
8
- import { track, needle, uses } from './gauge.works.ts'
9
-
10
-
11
- export interface GaugeProps {
12
- readonly value?: number
13
- readonly min?: number
14
- readonly max?: number
15
- readonly labelKey: MessageKey
16
- readonly dispatch?: keyof Dispatching
17
- readonly input?: Readonly<Record<string, unknown>>
18
- readonly invokeOn?: 'press' | 'shown'
19
- }
20
-
21
- export const Gauge: Element<'Gauge', GaugeProps> = element(
22
- 'Gauge',
23
- (props: GaugeProps, className) => {
24
- const kept = uses(props)
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
- holds: [
28
- ...(kept.holds ?? []),
29
- ],
30
- ...(kept.animates === undefined ? {} : { animates: kept.animates }), })
31
- return (
32
- <svg className={surface.className} {...surface.marks} {...surface.invokes} {...surface.a11y} {...(kept.on ?? {})} role="img" aria-label={surface.text(props.labelKey)}>
33
- <path {...track(props, kept)}></path>
34
- <path {...needle(props, kept)}></path>
35
- </svg>
36
- )
37
- },
38
- 'e-gauge',
39
- {"statTile":"thread-stat-tile"},
40
- )
@@ -1,42 +0,0 @@
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
- }