@prestack/thread-ui 0.0.1 → 0.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/ui/animations/anm_appear.ts +13 -0
- package/dist/ui/animations/anm_blur_in.ts +19 -0
- package/dist/ui/animations/anm_blur_out.ts +18 -0
- package/dist/ui/animations/anm_dismiss.ts +18 -0
- package/dist/ui/animations/anm_drop.ts +19 -0
- package/dist/ui/animations/anm_fall.ts +18 -0
- package/dist/ui/animations/anm_flash.ts +18 -0
- package/dist/ui/animations/anm_glow.ts +16 -0
- package/dist/ui/animations/anm_lift.ts +16 -0
- package/dist/ui/animations/anm_nudge.ts +16 -0
- package/dist/ui/animations/anm_pop_in.ts +23 -0
- package/dist/ui/animations/anm_pop_out.ts +22 -0
- package/dist/ui/animations/anm_press.ts +12 -0
- package/dist/ui/animations/anm_pulse.ts +13 -0
- package/dist/ui/animations/anm_rise.ts +19 -0
- package/dist/ui/animations/anm_shake.ts +13 -0
- package/dist/ui/animations/anm_slide_in_left.ts +19 -0
- package/dist/ui/animations/anm_slide_in_right.ts +19 -0
- package/dist/ui/animations/anm_slide_out_left.ts +18 -0
- package/dist/ui/animations/anm_slide_out_right.ts +18 -0
- package/dist/ui/animations/anm_spin.ts +12 -0
- package/dist/ui/animations/anm_swell.ts +16 -0
- package/dist/ui/animations/anm_wobble.ts +24 -0
- package/dist/ui/animations/anm_zoom_in.ts +19 -0
- package/dist/ui/animations/anm_zoom_out.ts +18 -0
- package/dist/ui/animations.ts +317 -0
- package/dist/ui/attach.js +13 -4
- package/dist/ui/attach.ts +613 -0
- package/dist/ui/charts.tsx +74 -0
- package/dist/ui/checked.d.ts +2 -2
- package/dist/ui/checked.ts +32 -0
- package/dist/ui/collector.ts +140 -0
- package/dist/ui/compositions.d.ts +10 -1
- package/dist/ui/compositions.js +262 -23
- package/dist/ui/compositions.ts +640 -0
- package/dist/ui/consent.ts +55 -0
- package/dist/ui/content.tsx +39 -0
- package/dist/ui/contrast.ts +27 -0
- package/dist/ui/controls.tsx +30 -0
- package/dist/ui/disclosure.tsx +22 -0
- package/dist/ui/dismiss-layer.ts +87 -0
- package/dist/ui/door.ts +140 -0
- package/dist/ui/drag.ts +316 -0
- package/dist/ui/elements/accordion.js +2 -2
- package/dist/ui/elements/accordion.tsx +47 -0
- package/dist/ui/elements/accordion.works.ts +66 -0
- package/dist/ui/elements/aspect-ratio.js +1 -1
- package/dist/ui/elements/aspect-ratio.tsx +30 -0
- package/dist/ui/elements/aspect-ratio.works.ts +5 -0
- package/dist/ui/elements/avatar.js +1 -1
- package/dist/ui/elements/avatar.tsx +33 -0
- package/dist/ui/elements/avatar.works.ts +16 -0
- package/dist/ui/elements/badge.js +1 -1
- package/dist/ui/elements/badge.tsx +32 -0
- package/dist/ui/elements/badge.works.ts +9 -0
- package/dist/ui/elements/bars.js +2 -2
- package/dist/ui/elements/bars.tsx +49 -0
- package/dist/ui/elements/bars.works.ts +29 -0
- package/dist/ui/elements/box.js +3 -2
- package/dist/ui/elements/box.tsx +35 -0
- package/dist/ui/elements/breadcrumb.js +2 -2
- package/dist/ui/elements/breadcrumb.tsx +37 -0
- package/dist/ui/elements/breadcrumb.works.ts +24 -0
- package/dist/ui/elements/button.js +1 -1
- package/dist/ui/elements/button.tsx +45 -0
- package/dist/ui/elements/button.works.tsx +52 -0
- package/dist/ui/elements/column.js +1 -1
- package/dist/ui/elements/column.tsx +28 -0
- package/dist/ui/elements/consent.js +2 -2
- package/dist/ui/elements/consent.tsx +70 -0
- package/dist/ui/elements/consent.works.tsx +132 -0
- package/dist/ui/elements/donut.js +2 -2
- package/dist/ui/elements/donut.tsx +44 -0
- package/dist/ui/elements/donut.works.ts +42 -0
- package/dist/ui/elements/each.js +1 -1
- package/dist/ui/elements/each.tsx +33 -0
- package/dist/ui/elements/each.works.tsx +82 -0
- package/dist/ui/elements/empty.js +1 -1
- package/dist/ui/elements/empty.tsx +32 -0
- package/dist/ui/elements/field.js +1 -1
- package/dist/ui/elements/field.tsx +44 -0
- package/dist/ui/elements/field.works.ts +65 -0
- package/dist/ui/elements/funnel.js +2 -2
- package/dist/ui/elements/funnel.tsx +49 -0
- package/dist/ui/elements/funnel.works.ts +37 -0
- package/dist/ui/elements/gauge.js +1 -1
- package/dist/ui/elements/gauge.tsx +39 -0
- package/dist/ui/elements/gauge.works.ts +42 -0
- package/dist/ui/elements/glyph.js +1 -1
- package/dist/ui/elements/glyph.tsx +29 -0
- package/dist/ui/elements/glyph.works.tsx +42 -0
- package/dist/ui/elements/grid.js +1 -1
- package/dist/ui/elements/grid.tsx +28 -0
- package/dist/ui/elements/heatmap.js +2 -2
- package/dist/ui/elements/heatmap.tsx +47 -0
- package/dist/ui/elements/heatmap.works.ts +24 -0
- package/dist/ui/elements/hit-box.js +1 -1
- package/dist/ui/elements/hit-box.tsx +46 -0
- package/dist/ui/elements/hit-box.works.tsx +576 -0
- package/dist/ui/elements/image.js +1 -1
- package/dist/ui/elements/image.tsx +28 -0
- package/dist/ui/elements/line-chart.js +2 -2
- package/dist/ui/elements/line-chart.tsx +44 -0
- package/dist/ui/elements/line-chart.works.ts +41 -0
- package/dist/ui/elements/link.js +1 -1
- package/dist/ui/elements/link.tsx +37 -0
- package/dist/ui/elements/link.works.ts +25 -0
- package/dist/ui/elements/media.js +1 -1
- package/dist/ui/elements/media.tsx +56 -0
- package/dist/ui/elements/menu.js +2 -2
- package/dist/ui/elements/menu.tsx +50 -0
- package/dist/ui/elements/menu.works.tsx +153 -0
- package/dist/ui/elements/money.js +1 -1
- package/dist/ui/elements/money.tsx +28 -0
- package/dist/ui/elements/money.works.ts +9 -0
- package/dist/ui/elements/pagination.js +2 -2
- package/dist/ui/elements/pagination.tsx +38 -0
- package/dist/ui/elements/pagination.works.ts +20 -0
- package/dist/ui/elements/progress.js +1 -1
- package/dist/ui/elements/progress.tsx +33 -0
- package/dist/ui/elements/progress.works.ts +8 -0
- package/dist/ui/elements/radio-group.js +2 -2
- package/dist/ui/elements/radio-group.tsx +49 -0
- package/dist/ui/elements/radio-group.works.ts +33 -0
- package/dist/ui/elements/row.js +1 -1
- package/dist/ui/elements/row.tsx +28 -0
- package/dist/ui/elements/scatter.js +1 -1
- package/dist/ui/elements/scatter.tsx +41 -0
- package/dist/ui/elements/scatter.works.ts +38 -0
- package/dist/ui/elements/select.js +2 -2
- package/dist/ui/elements/select.tsx +42 -0
- package/dist/ui/elements/select.works.ts +5 -0
- package/dist/ui/elements/separator.js +1 -1
- package/dist/ui/elements/separator.tsx +30 -0
- package/dist/ui/elements/skeleton.js +1 -1
- package/dist/ui/elements/skeleton.tsx +30 -0
- package/dist/ui/elements/slider.js +1 -1
- package/dist/ui/elements/slider.tsx +33 -0
- package/dist/ui/elements/slider.works.ts +5 -0
- package/dist/ui/elements/sparkline.js +1 -1
- package/dist/ui/elements/sparkline.tsx +36 -0
- package/dist/ui/elements/sparkline.works.ts +32 -0
- package/dist/ui/elements/spinner.js +1 -1
- package/dist/ui/elements/spinner.tsx +32 -0
- package/dist/ui/elements/stack.js +1 -1
- package/dist/ui/elements/stack.tsx +28 -0
- package/dist/ui/elements/stat.js +1 -1
- package/dist/ui/elements/stat.tsx +35 -0
- package/dist/ui/elements/stat.works.ts +14 -0
- package/dist/ui/elements/surface.js +1 -1
- package/dist/ui/elements/surface.tsx +35 -0
- package/dist/ui/elements/surface.works.ts +9 -0
- package/dist/ui/elements/table.js +2 -2
- package/dist/ui/elements/table.tsx +61 -0
- package/dist/ui/elements/table.works.ts +30 -0
- package/dist/ui/elements/tabs.js +2 -2
- package/dist/ui/elements/tabs.tsx +47 -0
- package/dist/ui/elements/tabs.works.ts +84 -0
- package/dist/ui/elements/text.js +1 -1
- package/dist/ui/elements/text.tsx +32 -0
- package/dist/ui/elements/textarea.js +1 -1
- package/dist/ui/elements/textarea.tsx +37 -0
- package/dist/ui/elements/textarea.works.ts +5 -0
- package/dist/ui/elements/time.js +1 -1
- package/dist/ui/elements/time.tsx +34 -0
- package/dist/ui/elements/time.works.ts +25 -0
- package/dist/ui/elements/toggle.js +1 -1
- package/dist/ui/elements/toggle.tsx +33 -0
- package/dist/ui/elements/toggle.works.ts +5 -0
- package/dist/ui/elements/tooltip.js +1 -1
- package/dist/ui/elements/tooltip.tsx +39 -0
- package/dist/ui/elements/tooltip.works.ts +77 -0
- package/dist/ui/elements/value.js +1 -1
- package/dist/ui/elements/value.tsx +30 -0
- package/dist/ui/elements/value.works.ts +9 -0
- package/dist/ui/elements.ts +127 -0
- package/dist/ui/focus-scope.ts +121 -0
- package/dist/ui/fonts.ts +355 -0
- package/dist/ui/generators/dev.ts +79 -0
- package/dist/ui/generators/elements.d.ts +1 -11
- package/dist/ui/generators/elements.js +43 -13
- package/dist/ui/generators/elements.ts +794 -0
- package/dist/ui/generators/i18n.ts +301 -0
- package/dist/ui/generators/icons.ts +214 -0
- package/dist/ui/generators/react.js +10 -3
- package/dist/ui/generators/react.ts +268 -0
- package/dist/ui/generators/routes.d.ts +1 -0
- package/dist/ui/generators/routes.js +89 -69
- package/dist/ui/generators/routes.ts +739 -0
- package/dist/ui/generators/show.ts +259 -0
- package/dist/ui/generators/styles.js +1 -1
- package/dist/ui/generators/styles.ts +670 -0
- package/dist/ui/generators/variants.ts +205 -0
- package/dist/ui/i18n/catalogs/en.ts +191 -0
- package/dist/ui/i18n/format.ts +221 -0
- package/dist/ui/i18n/locales.ts +33 -0
- package/dist/ui/i18n/parse.ts +315 -0
- package/dist/ui/i18n/translator.ts +31 -0
- package/dist/ui/i18n/types.ts +96 -0
- package/dist/ui/icons.ts +331 -0
- package/dist/ui/islands/map.ts +58 -0
- package/dist/ui/localizations.ts +75 -0
- package/dist/ui/makers/message.ts +292 -0
- package/dist/ui/material.ts +236 -0
- package/dist/ui/materials/mat_bordered.ts +13 -0
- package/dist/ui/materials/mat_ceramic.ts +13 -0
- package/dist/ui/materials/mat_clay.ts +13 -0
- package/dist/ui/materials/mat_elevated.ts +24 -0
- package/dist/ui/materials/mat_flat.ts +34 -0
- package/dist/ui/materials/mat_glass.ts +20 -0
- package/dist/ui/materials/mat_lit.ts +13 -0
- package/dist/ui/materials/mat_moulded.ts +11 -0
- package/dist/ui/materials.ts +206 -0
- package/dist/ui/modes.ts +44 -0
- package/dist/ui/navigation.tsx +32 -0
- package/dist/ui/positioning.ts +90 -0
- package/dist/ui/presented.tsx +119 -0
- package/dist/ui/provider.d.ts +2 -0
- package/dist/ui/provider.js +7 -1
- package/dist/ui/provider.tsx +182 -0
- package/dist/ui/render-loop.ts +133 -0
- package/dist/ui/roving-focus.ts +161 -0
- package/dist/ui/screens.js +17 -8
- package/dist/ui/screens.ts +142 -0
- package/dist/ui/scroll-lock.ts +28 -0
- package/dist/ui/series.ts +97 -0
- package/dist/ui/signals.ts +386 -0
- package/dist/ui/styleguide.ts +231 -0
- package/dist/ui/styleguides.ts +374 -0
- package/dist/ui/themes.ts +22 -0
- package/dist/ui/token.js +1 -1
- package/dist/ui/token.ts +78 -0
- package/dist/ui/tokens/shadow.ts +25 -0
- package/dist/ui/tokens.ts +235 -0
- package/dist/ui/validators.ts +232 -0
- package/dist/ui/variant.d.ts +1 -0
- package/dist/ui/variant.js +3 -0
- package/dist/ui/variant.ts +352 -0
- package/dist/ui/variants/var_avatar-round.ts +14 -0
- package/dist/ui/variants/var_bubble-me.ts +14 -0
- package/dist/ui/variants/var_bubble-them.ts +13 -0
- package/dist/ui/variants/var_button-border.ts +76 -0
- package/dist/ui/variants/var_button-cta.ts +76 -0
- package/dist/ui/variants/var_button-destructive-ghost.ts +72 -0
- package/dist/ui/variants/var_button-destructive.ts +76 -0
- package/dist/ui/variants/var_button-fill.ts +76 -0
- package/dist/ui/variants/var_button-ghost.ts +72 -0
- package/dist/ui/variants/var_button-glass.ts +55 -0
- package/dist/ui/variants/var_button-normal.ts +76 -0
- package/dist/ui/variants/var_button-quiet.ts +74 -0
- package/dist/ui/variants/var_card.ts +17 -0
- package/dist/ui/variants/var_chip.ts +13 -0
- package/dist/ui/variants/var_choice.ts +15 -0
- package/dist/ui/variants/var_framed.ts +10 -0
- package/dist/ui/variants/var_glyph-plain.ts +11 -0
- package/dist/ui/variants/var_input.ts +43 -0
- package/dist/ui/variants/var_link-inline.ts +29 -0
- package/dist/ui/variants/var_list-row.ts +34 -0
- package/dist/ui/variants/var_rule.ts +10 -0
- package/dist/ui/variants/var_skeleton-block.ts +12 -0
- package/dist/ui/variants/var_spinner-turning.ts +12 -0
- package/dist/ui/variants/var_stat-tile.ts +13 -0
- package/dist/ui/variants/var_tab-bar.ts +15 -0
- package/dist/ui/variants/var_tap-target.ts +25 -0
- package/dist/ui/variants.ts +64 -0
- package/dist/ui/wrappers.d.ts +4 -1
- package/dist/ui/wrappers.js +60 -20
- package/dist/ui/wrappers.tsx +426 -0
- package/package.json +8 -2
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { Fragment, useLayoutEffect, useRef, type ReactNode } from 'react'
|
|
2
|
+
|
|
3
|
+
import type { Row } from '../charts.tsx'
|
|
4
|
+
import { nameOf } from '../token.ts'
|
|
5
|
+
import type { EachProps } from './each.tsx'
|
|
6
|
+
|
|
7
|
+
function keyOf(row: object, by: string, at: number): string {
|
|
8
|
+
const value = (row as Record<string, unknown>)[by]
|
|
9
|
+
if (value === undefined || value === null) {
|
|
10
|
+
throw new Error(
|
|
11
|
+
`Each: row ${String(at)} has no value for "${by}", so it has no key. React reuses DOM by key, and a ` +
|
|
12
|
+
"row without one is matched by position — which silently keeps the previous row's state when " +
|
|
13
|
+
'the list reorders. Name the field that identifies a row in this view.',
|
|
14
|
+
)
|
|
15
|
+
}
|
|
16
|
+
return String(value)
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function following(marker: HTMLElement): readonly HTMLElement[] {
|
|
20
|
+
const found: HTMLElement[] = []
|
|
21
|
+
let node = marker.nextElementSibling
|
|
22
|
+
while (node !== null) {
|
|
23
|
+
found.push(node as HTMLElement)
|
|
24
|
+
node = node.nextElementSibling
|
|
25
|
+
}
|
|
26
|
+
return found
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function boxes(marker: HTMLElement, ids: readonly string[]): Map<string, DOMRect> {
|
|
30
|
+
const found = new Map<string, DOMRect>()
|
|
31
|
+
following(marker).forEach((node, at) => {
|
|
32
|
+
const id = ids[at]
|
|
33
|
+
if (id !== undefined) found.set(id, node.getBoundingClientRect())
|
|
34
|
+
})
|
|
35
|
+
return found
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function settle(marker: HTMLElement, ids: readonly string[], was: Map<string, DOMRect>, over: string, paced: string): void {
|
|
39
|
+
following(marker).forEach((node, at) => {
|
|
40
|
+
const id = ids[at]
|
|
41
|
+
if (id === undefined) return
|
|
42
|
+
const before = was.get(id)
|
|
43
|
+
if (before === undefined) return
|
|
44
|
+
const after = node.getBoundingClientRect()
|
|
45
|
+
const dx = before.left - after.left
|
|
46
|
+
const dy = before.top - after.top
|
|
47
|
+
if (Math.abs(dx) < 0.5 && Math.abs(dy) < 0.5) return
|
|
48
|
+
|
|
49
|
+
node.style.transition = `transform var(${nameOf('motion-instant')})`
|
|
50
|
+
node.style.transform = `translate(${String(dx)}px, ${String(dy)}px)`
|
|
51
|
+
void node.offsetWidth
|
|
52
|
+
node.style.transition = `transform var(--${over}) var(--${paced})`
|
|
53
|
+
node.style.transform = ''
|
|
54
|
+
})
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export function shows(props: EachProps, _surface: unknown): ReactNode {
|
|
58
|
+
const within = useRef<HTMLElement | null>(null)
|
|
59
|
+
const was = useRef<Map<string, DOMRect>>(new Map())
|
|
60
|
+
const drawn = useRef('')
|
|
61
|
+
|
|
62
|
+
const order = props.rows.map((row, at) => keyOf(row, props.by, at)).join('|')
|
|
63
|
+
|
|
64
|
+
useLayoutEffect(() => {
|
|
65
|
+
const marker = within.current
|
|
66
|
+
if (marker === null || props.settles === undefined || props.paced === undefined) return
|
|
67
|
+
const ids = order === '' ? [] : order.split('|')
|
|
68
|
+
const moved = drawn.current !== '' && drawn.current !== order
|
|
69
|
+
if (moved) settle(marker, ids, was.current, props.settles, props.paced)
|
|
70
|
+
drawn.current = order
|
|
71
|
+
was.current = boxes(marker, ids)
|
|
72
|
+
}, [order, props.settles, props.paced])
|
|
73
|
+
|
|
74
|
+
return (
|
|
75
|
+
<>
|
|
76
|
+
<span ref={within} hidden />
|
|
77
|
+
{props.rows.map((row, at) => (
|
|
78
|
+
<Fragment key={keyOf(row, props.by, at)}>{props.children(row as Row, at)}</Fragment>
|
|
79
|
+
))}
|
|
80
|
+
</>
|
|
81
|
+
)
|
|
82
|
+
}
|
|
@@ -9,4 +9,4 @@ export const Empty = element('Empty', (props, className) => {
|
|
|
9
9
|
'empty',
|
|
10
10
|
], });
|
|
11
11
|
return (_jsx("div", { className: surface.className, ...surface.marks, ...surface.invokes, ...surface.a11y, children: surface.text(props.labelKey) }));
|
|
12
|
-
}, 'e-empty');
|
|
12
|
+
}, 'e-empty', { "card": "thread-card" });
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import type { MessageKey } from '../content.tsx'
|
|
4
|
+
import { roleClass } from '../fonts.ts'
|
|
5
|
+
|
|
6
|
+
import { type Element, element, useSurface } from '../wrappers.tsx'
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
export interface EmptyProps {
|
|
10
|
+
readonly labelKey: MessageKey
|
|
11
|
+
readonly dispatch?: string
|
|
12
|
+
readonly input?: Readonly<Record<string, unknown>>
|
|
13
|
+
readonly invokeOn?: 'press' | 'shown'
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export const Empty: Element<'Empty', EmptyProps> = element(
|
|
17
|
+
'Empty',
|
|
18
|
+
(props: EmptyProps, className) => {
|
|
19
|
+
const surface = useSurface({ className: roleClass(undefined, className),
|
|
20
|
+
invokes: { ...(props.dispatch === undefined ? {} : { dispatch: props.dispatch }), ...(props.input === undefined ? {} : { input: props.input }), ...(props.invokeOn === undefined ? {} : { on: props.invokeOn }) },
|
|
21
|
+
holds: [
|
|
22
|
+
'empty' as const,
|
|
23
|
+
], })
|
|
24
|
+
return (
|
|
25
|
+
<div className={surface.className} {...surface.marks} {...surface.invokes} {...surface.a11y}>
|
|
26
|
+
{surface.text(props.labelKey)}
|
|
27
|
+
</div>
|
|
28
|
+
)
|
|
29
|
+
},
|
|
30
|
+
'e-empty',
|
|
31
|
+
{"card":"thread-card"},
|
|
32
|
+
)
|
|
@@ -12,4 +12,4 @@ export const Field = element('Field', (props, className) => {
|
|
|
12
12
|
],
|
|
13
13
|
...(kept.animates === undefined ? {} : { animates: kept.animates }), });
|
|
14
14
|
return (_jsxs("label", { className: surface.className, ...surface.marks, ...surface.invokes, ...surface.a11y, ...(kept.on ?? {}), children: [_jsx("span", { children: surface.text(props.labelKey) }), _jsx("input", { ...entry(props, kept) }), _jsx("span", { children: shown(props, surface, kept).fault })] }));
|
|
15
|
-
}, 'e-field');
|
|
15
|
+
}, 'e-field', { "input": "thread-input" });
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import type { MessageKey } from '../content.tsx'
|
|
4
|
+
import { roleClass } from '../fonts.ts'
|
|
5
|
+
|
|
6
|
+
import { type Element, element, useSurface } from '../wrappers.tsx'
|
|
7
|
+
import { shown, entry, uses } from './field.works.ts'
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
export interface FieldProps {
|
|
11
|
+
readonly name: string
|
|
12
|
+
readonly validator: string
|
|
13
|
+
readonly labelKey: MessageKey
|
|
14
|
+
readonly value?: string
|
|
15
|
+
readonly onValue?: (said: string) => void
|
|
16
|
+
readonly context?: Readonly<Record<string, unknown>>
|
|
17
|
+
readonly autocomplete?: string
|
|
18
|
+
readonly disabled?: boolean
|
|
19
|
+
readonly dispatch?: string
|
|
20
|
+
readonly input?: Readonly<Record<string, unknown>>
|
|
21
|
+
readonly invokeOn?: 'press' | 'shown'
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export const Field: Element<'Field', FieldProps> = element(
|
|
25
|
+
'Field',
|
|
26
|
+
(props: FieldProps, className) => {
|
|
27
|
+
const kept = uses(props)
|
|
28
|
+
const surface = useSurface({ className: roleClass(undefined, className),
|
|
29
|
+
invokes: { ...(props.dispatch === undefined ? {} : { dispatch: props.dispatch }), ...(props.input === undefined ? {} : { input: props.input }), ...(props.invokeOn === undefined ? {} : { on: props.invokeOn }) },
|
|
30
|
+
holds: [
|
|
31
|
+
...(kept.holds ?? []),
|
|
32
|
+
],
|
|
33
|
+
...(kept.animates === undefined ? {} : { animates: kept.animates }), })
|
|
34
|
+
return (
|
|
35
|
+
<label className={surface.className} {...surface.marks} {...surface.invokes} {...surface.a11y} {...(kept.on ?? {})}>
|
|
36
|
+
<span>{surface.text(props.labelKey)}</span>
|
|
37
|
+
<input {...entry(props, kept)} />
|
|
38
|
+
<span>{shown(props, surface, kept).fault}</span>
|
|
39
|
+
</label>
|
|
40
|
+
)
|
|
41
|
+
},
|
|
42
|
+
'e-field',
|
|
43
|
+
{"input":"thread-input"},
|
|
44
|
+
)
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { useState, type ReactNode } from 'react'
|
|
2
|
+
|
|
3
|
+
import { acceptsKeystroke, applyMask, entryConstraints, evaluateMoney, runValidator } from '../validators.ts'
|
|
4
|
+
import type { Surface } from '../door.ts'
|
|
5
|
+
import type { FieldProps } from './field.tsx'
|
|
6
|
+
|
|
7
|
+
export function uses(props: FieldProps): {
|
|
8
|
+
readonly holds?: readonly string[]
|
|
9
|
+
readonly animates?: boolean
|
|
10
|
+
readonly on?: Readonly<Record<string, unknown>>
|
|
11
|
+
readonly wrong: string | null
|
|
12
|
+
readonly said: (to: string | null) => void
|
|
13
|
+
} {
|
|
14
|
+
const [wrong, said] = useState<string | null>(null)
|
|
15
|
+
return { ...(props.disabled === true ? { holds: ['disabled'] as const } : {}), wrong, said }
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function entry(
|
|
19
|
+
props: FieldProps,
|
|
20
|
+
kept: { readonly wrong: string | null; readonly said: (to: string | null) => void },
|
|
21
|
+
): Readonly<Record<string, unknown>> {
|
|
22
|
+
const rules = entryConstraints(props.validator, props.context ?? {})
|
|
23
|
+
return {
|
|
24
|
+
name: props.name,
|
|
25
|
+
...(rules.secret === true ? { type: 'password' } : {}),
|
|
26
|
+
...(props.autocomplete ?? rules.autocomplete === undefined
|
|
27
|
+
? {}
|
|
28
|
+
: { autoComplete: props.autocomplete ?? rules.autocomplete }),
|
|
29
|
+
...(rules.keyboard === 'text' ? {} : { inputMode: rules.keyboard }),
|
|
30
|
+
...(rules.maxChars === undefined ? {} : { maxLength: rules.maxChars }),
|
|
31
|
+
value: props.value ?? '',
|
|
32
|
+
...(kept.wrong === null ? {} : { 'aria-invalid': true }),
|
|
33
|
+
|
|
34
|
+
onChange: (event: { readonly target: { readonly value: string } }): void => {
|
|
35
|
+
let kept_ = ''
|
|
36
|
+
for (const ch of event.target.value) {
|
|
37
|
+
if (acceptsKeystroke(props.validator, kept_, ch)) kept_ += ch
|
|
38
|
+
}
|
|
39
|
+
props.onValue?.(applyMask(props.validator, kept_))
|
|
40
|
+
},
|
|
41
|
+
|
|
42
|
+
onBlur: (): void => {
|
|
43
|
+
const text = props.value ?? ''
|
|
44
|
+
let judged: unknown = text
|
|
45
|
+
if (rules.entry === 'calculator') {
|
|
46
|
+
const currency = props.context?.['currency']
|
|
47
|
+
const resolved = typeof currency === 'string' ? evaluateMoney(text, currency) : undefined
|
|
48
|
+
if (resolved !== undefined) {
|
|
49
|
+
judged = { amount: resolved.amount, currency }
|
|
50
|
+
props.onValue?.(String(resolved.amount))
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
const result = runValidator(props.validator, judged, props.context ?? {})
|
|
54
|
+
kept.said(result.ok ? null : result.errorKey)
|
|
55
|
+
},
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export function shown(
|
|
60
|
+
_props: FieldProps,
|
|
61
|
+
surface: Surface,
|
|
62
|
+
kept: { readonly wrong: string | null },
|
|
63
|
+
): Readonly<Record<string, ReactNode>> {
|
|
64
|
+
return { fault: kept.wrong === null ? null : surface.text(kept.wrong) }
|
|
65
|
+
}
|
|
@@ -3,7 +3,7 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
3
3
|
import { roleClass } from "../fonts.js";
|
|
4
4
|
import { measured as marking } from "../charts.js";
|
|
5
5
|
import { element, useSurface } from "../wrappers.js";
|
|
6
|
-
import { Each } from "
|
|
6
|
+
import { Each } from "../elements/each.js";
|
|
7
7
|
import { shown, sized, uses } from "./funnel.works.js";
|
|
8
8
|
export const Funnel = element('Funnel', (props, className) => {
|
|
9
9
|
const kept = uses(props);
|
|
@@ -14,4 +14,4 @@ export const Funnel = element('Funnel', (props, className) => {
|
|
|
14
14
|
],
|
|
15
15
|
...(kept.animates === undefined ? {} : { animates: kept.animates }), });
|
|
16
16
|
return (_jsx("div", { className: surface.className, ...surface.marks, ...surface.invokes, ...surface.a11y, ...(kept.on ?? {}), role: "img", ...(props.titleKey === undefined ? {} : { 'aria-label': surface.text(props.titleKey) }), children: _jsx(Each, { rows: props.rows, by: props.by, children: (each0, at) => (_jsxs("span", { style: marking(sized(props, kept, each0, at)), children: [_jsx("span", { children: shown(props, each0, surface, kept).label }), _jsx("span", { children: shown(props, each0, surface, kept).amount })] })) }) }));
|
|
17
|
-
}, 'e-funnel');
|
|
17
|
+
}, 'e-funnel', { "card": "thread-card" });
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import type { MessageKey } from '../content.tsx'
|
|
4
|
+
import type { Row } from '../charts.tsx'
|
|
5
|
+
import { roleClass } from '../fonts.ts'
|
|
6
|
+
import { measured as marking } from '../charts.tsx'
|
|
7
|
+
|
|
8
|
+
import { type Element, element, useSurface } from '../wrappers.tsx'
|
|
9
|
+
import { Each } from '../elements/each.tsx'
|
|
10
|
+
import { shown, sized, uses } from './funnel.works.ts'
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
export interface FunnelProps {
|
|
14
|
+
readonly rows: readonly Row[]
|
|
15
|
+
readonly by: string
|
|
16
|
+
readonly value: string
|
|
17
|
+
readonly format?: string
|
|
18
|
+
readonly titleKey?: MessageKey
|
|
19
|
+
readonly dispatch?: string
|
|
20
|
+
readonly input?: Readonly<Record<string, unknown>>
|
|
21
|
+
readonly invokeOn?: 'press' | 'shown'
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export const Funnel: Element<'Funnel', FunnelProps> = element(
|
|
25
|
+
'Funnel',
|
|
26
|
+
(props: FunnelProps, className) => {
|
|
27
|
+
const kept = uses(props)
|
|
28
|
+
const surface = useSurface({ className: roleClass(undefined, className),
|
|
29
|
+
invokes: { ...(props.dispatch === undefined ? {} : { dispatch: props.dispatch }), ...(props.input === undefined ? {} : { input: props.input }), ...(props.invokeOn === undefined ? {} : { on: props.invokeOn }) },
|
|
30
|
+
holds: [
|
|
31
|
+
...(kept.holds ?? []),
|
|
32
|
+
],
|
|
33
|
+
...(kept.animates === undefined ? {} : { animates: kept.animates }), })
|
|
34
|
+
return (
|
|
35
|
+
<div className={surface.className} {...surface.marks} {...surface.invokes} {...surface.a11y} {...(kept.on ?? {})} role="img" {...(props.titleKey === undefined ? {} : { 'aria-label': surface.text(props.titleKey) })}>
|
|
36
|
+
<Each rows={props.rows} by={props.by}>
|
|
37
|
+
{(each0, at) => (
|
|
38
|
+
<span style={marking(sized(props, kept, each0, at))}>
|
|
39
|
+
<span>{shown(props, each0, surface, kept).label}</span>
|
|
40
|
+
<span>{shown(props, each0, surface, kept).amount}</span>
|
|
41
|
+
</span>
|
|
42
|
+
)}
|
|
43
|
+
</Each>
|
|
44
|
+
</div>
|
|
45
|
+
)
|
|
46
|
+
},
|
|
47
|
+
'e-funnel',
|
|
48
|
+
{"card":"thread-card"},
|
|
49
|
+
)
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { formatValue } from '../validators.ts'
|
|
2
|
+
import { seriesColour } from '../series.ts'
|
|
3
|
+
import { num, str, useRamp, type Row } from '../charts.tsx'
|
|
4
|
+
import type { FunnelProps } from './funnel.tsx'
|
|
5
|
+
|
|
6
|
+
export function uses(props: FunnelProps): {
|
|
7
|
+
readonly holds?: readonly string[]
|
|
8
|
+
readonly animates?: boolean
|
|
9
|
+
readonly on?: Readonly<Record<string, unknown>>
|
|
10
|
+
readonly ramp: readonly string[]
|
|
11
|
+
} {
|
|
12
|
+
const ramp = useRamp(props.rows.length)
|
|
13
|
+
return { ...(props.rows.length === 0 ? { holds: ['empty'] as const } : {}), ramp }
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function sized(
|
|
17
|
+
props: FunnelProps,
|
|
18
|
+
kept: { readonly ramp: readonly string[] },
|
|
19
|
+
row: Row,
|
|
20
|
+
at: number,
|
|
21
|
+
): Readonly<Record<string, string | number>> {
|
|
22
|
+
const top = num(props.rows[0] as Row, props.value) || 1
|
|
23
|
+
const before = at === 0 ? null : num(props.rows[at - 1] as Row, props.value)
|
|
24
|
+
const drop = before === null || before === 0 ? null : 1 - num(row, props.value) / before
|
|
25
|
+
return {
|
|
26
|
+
series: seriesColour(kept.ramp, at),
|
|
27
|
+
fraction: `${((num(row, props.value) / top) * 100).toFixed(1)}%`,
|
|
28
|
+
...(drop === null ? {} : { drop: `${(drop * 100).toFixed(1)}%` }),
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function shown(props: FunnelProps, row: Row, _surface: unknown, _kept?: unknown): Readonly<Record<string, string>> {
|
|
33
|
+
return {
|
|
34
|
+
label: str(row, props.by),
|
|
35
|
+
amount: String(formatValue(props.format ?? 'number', num(row, props.value))),
|
|
36
|
+
}
|
|
37
|
+
}
|
|
@@ -12,4 +12,4 @@ export const Gauge = element('Gauge', (props, className) => {
|
|
|
12
12
|
],
|
|
13
13
|
...(kept.animates === undefined ? {} : { animates: kept.animates }), });
|
|
14
14
|
return (_jsxs("svg", { className: surface.className, ...surface.marks, ...surface.invokes, ...surface.a11y, ...(kept.on ?? {}), role: "img", "aria-label": surface.text(props.labelKey), children: [_jsx("path", { ...track(props, kept) }), _jsx("path", { ...needle(props, kept) })] }));
|
|
15
|
-
}, 'e-gauge');
|
|
15
|
+
}, 'e-gauge', { "statTile": "thread-stat-tile" });
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import type { MessageKey } from '../content.tsx'
|
|
4
|
+
import { roleClass } from '../fonts.ts'
|
|
5
|
+
|
|
6
|
+
import { type Element, element, useSurface } from '../wrappers.tsx'
|
|
7
|
+
import { track, needle, uses } from './gauge.works.ts'
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
export interface GaugeProps {
|
|
11
|
+
readonly value?: number
|
|
12
|
+
readonly min?: number
|
|
13
|
+
readonly max?: number
|
|
14
|
+
readonly labelKey: MessageKey
|
|
15
|
+
readonly dispatch?: string
|
|
16
|
+
readonly input?: Readonly<Record<string, unknown>>
|
|
17
|
+
readonly invokeOn?: 'press' | 'shown'
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export const Gauge: Element<'Gauge', GaugeProps> = element(
|
|
21
|
+
'Gauge',
|
|
22
|
+
(props: GaugeProps, className) => {
|
|
23
|
+
const kept = uses(props)
|
|
24
|
+
const surface = useSurface({ className: roleClass(undefined, className),
|
|
25
|
+
invokes: { ...(props.dispatch === undefined ? {} : { dispatch: props.dispatch }), ...(props.input === undefined ? {} : { input: props.input }), ...(props.invokeOn === undefined ? {} : { on: props.invokeOn }) },
|
|
26
|
+
holds: [
|
|
27
|
+
...(kept.holds ?? []),
|
|
28
|
+
],
|
|
29
|
+
...(kept.animates === undefined ? {} : { animates: kept.animates }), })
|
|
30
|
+
return (
|
|
31
|
+
<svg className={surface.className} {...surface.marks} {...surface.invokes} {...surface.a11y} {...(kept.on ?? {})} role="img" aria-label={surface.text(props.labelKey)}>
|
|
32
|
+
<path {...track(props, kept)}></path>
|
|
33
|
+
<path {...needle(props, kept)}></path>
|
|
34
|
+
</svg>
|
|
35
|
+
)
|
|
36
|
+
},
|
|
37
|
+
'e-gauge',
|
|
38
|
+
{"statTile":"thread-stat-tile"},
|
|
39
|
+
)
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { useRamp } from '../charts.tsx'
|
|
2
|
+
import type { GaugeProps } from './gauge.tsx'
|
|
3
|
+
|
|
4
|
+
const LEFT = 10
|
|
5
|
+
const RIGHT = 90
|
|
6
|
+
const MIDDLE = 50
|
|
7
|
+
const RADIUS = 40
|
|
8
|
+
|
|
9
|
+
export function uses(_props: GaugeProps): {
|
|
10
|
+
readonly holds?: readonly string[]
|
|
11
|
+
readonly animates?: boolean
|
|
12
|
+
readonly on?: Readonly<Record<string, unknown>>
|
|
13
|
+
readonly ramp: readonly string[]
|
|
14
|
+
} {
|
|
15
|
+
return { on: { viewBox: `0 0 100 56` }, ramp: useRamp(1) }
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function track(_props: GaugeProps, _kept: unknown): Readonly<Record<string, string | number>> {
|
|
19
|
+
return {
|
|
20
|
+
d: `M ${String(LEFT)} ${String(MIDDLE)} A ${String(RADIUS)} ${String(RADIUS)} 0 0 1 ${String(RIGHT)} ${String(MIDDLE)}`,
|
|
21
|
+
fill: 'none',
|
|
22
|
+
stroke: 'currentColor',
|
|
23
|
+
strokeOpacity: 0.15,
|
|
24
|
+
strokeWidth: 8,
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function needle(props: GaugeProps, kept: { readonly ramp: readonly string[] }): Readonly<Record<string, string | number>> {
|
|
29
|
+
const least = props.min ?? 0
|
|
30
|
+
const most = props.max ?? 0
|
|
31
|
+
const span = most - least
|
|
32
|
+
const part = span <= 0 ? 0 : Math.min(1, Math.max(0, ((props.value ?? 0) - least) / span))
|
|
33
|
+
const angle = Math.PI * (1 - part)
|
|
34
|
+
return {
|
|
35
|
+
d:
|
|
36
|
+
`M ${String(LEFT)} ${String(MIDDLE)} A ${String(RADIUS)} ${String(RADIUS)} 0 0 1 ` +
|
|
37
|
+
`${(MIDDLE + RADIUS * Math.cos(angle)).toFixed(2)} ${(MIDDLE - RADIUS * Math.sin(angle)).toFixed(2)}`,
|
|
38
|
+
fill: 'none',
|
|
39
|
+
stroke: kept.ramp[0] ?? 'currentColor',
|
|
40
|
+
strokeWidth: 8,
|
|
41
|
+
}
|
|
42
|
+
}
|
|
@@ -6,4 +6,4 @@ export const Glyph = element('Glyph', (props, className) => {
|
|
|
6
6
|
const surface = useSurface({ className: roleClass(undefined, className),
|
|
7
7
|
invokes: { ...(props.dispatch === undefined ? {} : { dispatch: props.dispatch }), ...(props.input === undefined ? {} : { input: props.input }), ...(props.invokeOn === undefined ? {} : { on: props.invokeOn }) }, });
|
|
8
8
|
return (shows(props, surface));
|
|
9
|
-
}, 'e-glyph');
|
|
9
|
+
}, 'e-glyph', { "glyphPlain": "thread-glyph-plain" });
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import type { MessageKey } from '../content.tsx'
|
|
4
|
+
import { roleClass } from '../fonts.ts'
|
|
5
|
+
|
|
6
|
+
import { type Element, element, useSurface } from '../wrappers.tsx'
|
|
7
|
+
import { shows } from './glyph.works.tsx'
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
export interface GlyphProps {
|
|
11
|
+
readonly name: string
|
|
12
|
+
readonly labelKey?: MessageKey
|
|
13
|
+
readonly dispatch?: string
|
|
14
|
+
readonly input?: Readonly<Record<string, unknown>>
|
|
15
|
+
readonly invokeOn?: 'press' | 'shown'
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export const Glyph: Element<'Glyph', GlyphProps> = element(
|
|
19
|
+
'Glyph',
|
|
20
|
+
(props: GlyphProps, className) => {
|
|
21
|
+
const surface = useSurface({ className: roleClass(undefined, className),
|
|
22
|
+
invokes: { ...(props.dispatch === undefined ? {} : { dispatch: props.dispatch }), ...(props.input === undefined ? {} : { input: props.input }), ...(props.invokeOn === undefined ? {} : { on: props.invokeOn }) }, })
|
|
23
|
+
return (
|
|
24
|
+
shows(props, surface)
|
|
25
|
+
)
|
|
26
|
+
},
|
|
27
|
+
'e-glyph',
|
|
28
|
+
{"glyphPlain":"thread-glyph-plain"},
|
|
29
|
+
)
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import type { ReactNode } from 'react'
|
|
2
|
+
|
|
3
|
+
import type { Surface } from '../door.ts'
|
|
4
|
+
import { drawnFor, iconSize, installedSet, strokeFor } from '../icons.ts'
|
|
5
|
+
import type { GlyphProps } from './glyph.tsx'
|
|
6
|
+
|
|
7
|
+
type Drawn = (props: {
|
|
8
|
+
readonly size?: number
|
|
9
|
+
readonly strokeWidth?: number
|
|
10
|
+
readonly className?: string
|
|
11
|
+
readonly 'aria-hidden'?: boolean
|
|
12
|
+
readonly 'aria-label'?: string
|
|
13
|
+
readonly role?: string
|
|
14
|
+
}) => ReactNode
|
|
15
|
+
|
|
16
|
+
export function drawn(name: string, semantics: Readonly<Record<string, unknown>>, className?: string): ReactNode {
|
|
17
|
+
const Symbol = drawnFor(name) as Drawn
|
|
18
|
+
const set = installedSet()
|
|
19
|
+
const stroke = set === undefined ? undefined : strokeFor(set)
|
|
20
|
+
|
|
21
|
+
return (
|
|
22
|
+
<Symbol
|
|
23
|
+
size={iconSize()}
|
|
24
|
+
{...(className === undefined ? {} : { className })}
|
|
25
|
+
{...(stroke === undefined ? {} : { strokeWidth: stroke })}
|
|
26
|
+
{...semantics}
|
|
27
|
+
/>
|
|
28
|
+
)
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export function shows(props: GlyphProps, surface: Surface): ReactNode {
|
|
32
|
+
return drawn(
|
|
33
|
+
props.name,
|
|
34
|
+
{
|
|
35
|
+
...surface.marks,
|
|
36
|
+
...(props.labelKey === undefined
|
|
37
|
+
? ({ 'aria-hidden': true } as const)
|
|
38
|
+
: ({ role: 'img', 'aria-label': surface.text(props.labelKey) } as const)),
|
|
39
|
+
},
|
|
40
|
+
surface.className,
|
|
41
|
+
)
|
|
42
|
+
}
|
package/dist/ui/elements/grid.js
CHANGED
|
@@ -5,4 +5,4 @@ export const Grid = element('Grid', (props, className) => {
|
|
|
5
5
|
const surface = useSurface({ className,
|
|
6
6
|
invokes: { ...(props.dispatch === undefined ? {} : { dispatch: props.dispatch }), ...(props.input === undefined ? {} : { input: props.input }), ...(props.invokeOn === undefined ? {} : { on: props.invokeOn }) }, });
|
|
7
7
|
return (_jsx("div", { className: surface.className, ...surface.marks, ...surface.invokes, ...surface.a11y, children: props.children }));
|
|
8
|
-
}, 'e-grid');
|
|
8
|
+
}, 'e-grid', {});
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import type { ReactNode } from 'react'
|
|
4
|
+
|
|
5
|
+
import { type Element, element, useSurface } from '../wrappers.tsx'
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
export interface GridProps {
|
|
9
|
+
readonly children: ReactNode
|
|
10
|
+
readonly dispatch?: string
|
|
11
|
+
readonly input?: Readonly<Record<string, unknown>>
|
|
12
|
+
readonly invokeOn?: 'press' | 'shown'
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export const Grid: Element<'Grid', GridProps> = element(
|
|
16
|
+
'Grid',
|
|
17
|
+
(props: GridProps, className) => {
|
|
18
|
+
const surface = useSurface({ className,
|
|
19
|
+
invokes: { ...(props.dispatch === undefined ? {} : { dispatch: props.dispatch }), ...(props.input === undefined ? {} : { input: props.input }), ...(props.invokeOn === undefined ? {} : { on: props.invokeOn }) }, })
|
|
20
|
+
return (
|
|
21
|
+
<div className={surface.className} {...surface.marks} {...surface.invokes} {...surface.a11y}>
|
|
22
|
+
{props.children}
|
|
23
|
+
</div>
|
|
24
|
+
)
|
|
25
|
+
},
|
|
26
|
+
'e-grid',
|
|
27
|
+
{},
|
|
28
|
+
)
|
|
@@ -3,7 +3,7 @@ import { jsx as _jsx } from "react/jsx-runtime";
|
|
|
3
3
|
import { roleClass } from "../fonts.js";
|
|
4
4
|
import { measured as marking } from "../charts.js";
|
|
5
5
|
import { element, useSurface } from "../wrappers.js";
|
|
6
|
-
import { Each } from "
|
|
6
|
+
import { Each } from "../elements/each.js";
|
|
7
7
|
import { sized, uses } from "./heatmap.works.js";
|
|
8
8
|
export const Heatmap = element('Heatmap', (props, className) => {
|
|
9
9
|
const kept = uses(props);
|
|
@@ -14,4 +14,4 @@ export const Heatmap = element('Heatmap', (props, className) => {
|
|
|
14
14
|
],
|
|
15
15
|
...(kept.animates === undefined ? {} : { animates: kept.animates }), });
|
|
16
16
|
return (_jsx("div", { className: surface.className, ...surface.marks, ...surface.invokes, ...surface.a11y, ...(kept.on ?? {}), role: "img", ...(props.titleKey === undefined ? {} : { 'aria-label': surface.text(props.titleKey) }), children: _jsx(Each, { rows: props.rows, by: props.by, children: (each0, at) => (_jsx("span", { style: marking(sized(props, kept, each0, at)) })) }) }));
|
|
17
|
-
}, 'e-heatmap');
|
|
17
|
+
}, 'e-heatmap', { "card": "thread-card" });
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import type { MessageKey } from '../content.tsx'
|
|
4
|
+
import type { Row } from '../charts.tsx'
|
|
5
|
+
import { roleClass } from '../fonts.ts'
|
|
6
|
+
import { measured as marking } from '../charts.tsx'
|
|
7
|
+
|
|
8
|
+
import { type Element, element, useSurface } from '../wrappers.tsx'
|
|
9
|
+
import { Each } from '../elements/each.tsx'
|
|
10
|
+
import { sized, uses } from './heatmap.works.ts'
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
export interface HeatmapProps {
|
|
14
|
+
readonly rows: readonly Row[]
|
|
15
|
+
readonly by: string
|
|
16
|
+
readonly x: string
|
|
17
|
+
readonly y: string
|
|
18
|
+
readonly value?: string
|
|
19
|
+
readonly titleKey?: MessageKey
|
|
20
|
+
readonly dispatch?: string
|
|
21
|
+
readonly input?: Readonly<Record<string, unknown>>
|
|
22
|
+
readonly invokeOn?: 'press' | 'shown'
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export const Heatmap: Element<'Heatmap', HeatmapProps> = element(
|
|
26
|
+
'Heatmap',
|
|
27
|
+
(props: HeatmapProps, className) => {
|
|
28
|
+
const kept = uses(props)
|
|
29
|
+
const surface = useSurface({ className: roleClass(undefined, className),
|
|
30
|
+
invokes: { ...(props.dispatch === undefined ? {} : { dispatch: props.dispatch }), ...(props.input === undefined ? {} : { input: props.input }), ...(props.invokeOn === undefined ? {} : { on: props.invokeOn }) },
|
|
31
|
+
holds: [
|
|
32
|
+
...(kept.holds ?? []),
|
|
33
|
+
],
|
|
34
|
+
...(kept.animates === undefined ? {} : { animates: kept.animates }), })
|
|
35
|
+
return (
|
|
36
|
+
<div className={surface.className} {...surface.marks} {...surface.invokes} {...surface.a11y} {...(kept.on ?? {})} role="img" {...(props.titleKey === undefined ? {} : { 'aria-label': surface.text(props.titleKey) })}>
|
|
37
|
+
<Each rows={props.rows} by={props.by}>
|
|
38
|
+
{(each0, at) => (
|
|
39
|
+
<span style={marking(sized(props, kept, each0, at))}></span>
|
|
40
|
+
)}
|
|
41
|
+
</Each>
|
|
42
|
+
</div>
|
|
43
|
+
)
|
|
44
|
+
},
|
|
45
|
+
'e-heatmap',
|
|
46
|
+
{"card":"thread-card"},
|
|
47
|
+
)
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { num, useRamp, type Row } from '../charts.tsx'
|
|
2
|
+
import type { HeatmapProps } from './heatmap.tsx'
|
|
3
|
+
|
|
4
|
+
export function uses(props: HeatmapProps): {
|
|
5
|
+
readonly holds?: readonly string[]
|
|
6
|
+
readonly animates?: boolean
|
|
7
|
+
readonly on?: Readonly<Record<string, unknown>>
|
|
8
|
+
readonly ramp: readonly string[]
|
|
9
|
+
} {
|
|
10
|
+
const ramp = useRamp(1)
|
|
11
|
+
return { ...(props.rows.length === 0 ? { holds: ['empty'] as const } : {}), ramp }
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function sized(
|
|
15
|
+
props: HeatmapProps,
|
|
16
|
+
kept: { readonly ramp: readonly string[] },
|
|
17
|
+
row: Row,
|
|
18
|
+
_at: number,
|
|
19
|
+
): Readonly<Record<string, string | number>> {
|
|
20
|
+
const readings = props.rows.map((one: Row) => (props.value === undefined ? 1 : num(one, props.value)))
|
|
21
|
+
const most = Math.max(...readings, 0) || 1
|
|
22
|
+
const here = props.value === undefined ? 1 : num(row, props.value)
|
|
23
|
+
return { series: kept.ramp[0] ?? '', share: Math.max(0.08, here / most) }
|
|
24
|
+
}
|
|
@@ -12,4 +12,4 @@ export const HitBox = element('HitBox', (props, className) => {
|
|
|
12
12
|
...(kept.animates === undefined ? {} : { animates: kept.animates }), });
|
|
13
13
|
const wired = wires(props, kept, surface);
|
|
14
14
|
return (_jsxs("div", { className: surface.className, ...surface.marks, ...surface.invokes, ...surface.a11y, ...(kept.on ?? {}), ...(wired.on ?? {}), children: [props.children, _jsx("div", { className: "e-hit-box-carried", children: shown(props, surface, kept, wired).carried })] }));
|
|
15
|
-
}, 'e-hit-box');
|
|
15
|
+
}, 'e-hit-box', { "tapTarget": "thread-tap-target" });
|