@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.
- package/CHANGELOG.md +177 -0
- 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.js +2 -3
- package/dist/ui/animations.ts +316 -0
- package/dist/ui/attach.js +13 -10
- package/dist/ui/attach.ts +607 -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 +11 -2
- package/dist/ui/compositions.js +283 -59
- package/dist/ui/compositions.ts +633 -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.js +2 -1
- package/dist/ui/generators/dev.ts +80 -0
- package/dist/ui/generators/elements.d.ts +1 -11
- package/dist/ui/generators/elements.js +44 -14
- package/dist/ui/generators/elements.ts +794 -0
- package/dist/ui/generators/i18n.js +2 -3
- package/dist/ui/generators/i18n.ts +300 -0
- package/dist/ui/generators/icons.js +3 -12
- package/dist/ui/generators/icons.ts +214 -0
- package/dist/ui/generators/react.js +13 -5
- package/dist/ui/generators/react.ts +269 -0
- package/dist/ui/generators/routes.d.ts +1 -0
- package/dist/ui/generators/routes.js +99 -83
- package/dist/ui/generators/routes.ts +743 -0
- package/dist/ui/generators/show.js +4 -12
- package/dist/ui/generators/show.ts +260 -0
- package/dist/ui/generators/styles.js +3 -4
- package/dist/ui/generators/styles.ts +669 -0
- package/dist/ui/generators/variants.js +2 -3
- package/dist/ui/generators/variants.ts +204 -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/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.js +2 -3
- package/dist/ui/materials.ts +205 -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 +22 -22
- package/dist/ui/screens.ts +141 -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.js +1 -2
- package/dist/ui/tokens.ts +234 -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
- package/dist/ui/makers/message.d.ts +0 -1
- package/dist/ui/makers/message.js +0 -230
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import type { MessageKey } from '../content.tsx'
|
|
4
|
+
import { roleClass } from '../fonts.ts'
|
|
5
|
+
import type { ConsentDecision } from '../consent.ts'
|
|
6
|
+
import { Box } from './box.tsx'
|
|
7
|
+
import { Column } from './column.tsx'
|
|
8
|
+
import { Text } from './text.tsx'
|
|
9
|
+
import { Toggle } from './toggle.tsx'
|
|
10
|
+
import { Button } from './button.tsx'
|
|
11
|
+
|
|
12
|
+
import { type Element, element, useSurface } from '../wrappers.tsx'
|
|
13
|
+
import { Each } from '../elements/each.tsx'
|
|
14
|
+
import { title, policy, hasPolicy, categories, label, describe, chooser, optional, fixed, required, withdraw, withdrawable, decline, accept, acceptAll, uses } from './consent.works.tsx'
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
export interface ConsentProps {
|
|
18
|
+
readonly decision?: ConsentDecision
|
|
19
|
+
readonly onDecide: (said: ConsentDecision) => void
|
|
20
|
+
readonly policyKey?: MessageKey
|
|
21
|
+
readonly preselected: readonly string[]
|
|
22
|
+
readonly dispatch?: string
|
|
23
|
+
readonly input?: Readonly<Record<string, unknown>>
|
|
24
|
+
readonly invokeOn?: 'press' | 'shown'
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export const Consent: Element<'Consent', ConsentProps> = element(
|
|
28
|
+
'Consent',
|
|
29
|
+
(props: ConsentProps, className) => {
|
|
30
|
+
const kept = uses(props)
|
|
31
|
+
const surface = useSurface({ className: roleClass(undefined, className),
|
|
32
|
+
invokes: { ...(props.dispatch === undefined ? {} : { dispatch: props.dispatch }), ...(props.input === undefined ? {} : { input: props.input }), ...(props.invokeOn === undefined ? {} : { on: props.invokeOn }) },
|
|
33
|
+
holds: [
|
|
34
|
+
...(kept.holds ?? []),
|
|
35
|
+
],
|
|
36
|
+
...(kept.animates === undefined ? {} : { animates: kept.animates }), })
|
|
37
|
+
return (
|
|
38
|
+
<>
|
|
39
|
+
<Box.card>
|
|
40
|
+
<Column>
|
|
41
|
+
<Text {...title(props, kept)}></Text>
|
|
42
|
+
{!hasPolicy(props, kept) ? null : <Text {...policy(props, kept)}></Text>}
|
|
43
|
+
<Column>
|
|
44
|
+
<Each rows={categories(props, kept)} by={'id'}>
|
|
45
|
+
{(each0, at) => (
|
|
46
|
+
<Box>
|
|
47
|
+
<Column>
|
|
48
|
+
<Text {...label(props, kept, each0, at)}></Text>
|
|
49
|
+
<Text {...describe(props, kept, each0, at)}></Text>
|
|
50
|
+
</Column>
|
|
51
|
+
{!optional(props, kept, each0) ? null : <Toggle.input {...chooser(props, kept, each0, at)}></Toggle.input>}
|
|
52
|
+
{!required(props, kept, each0) ? null : <Text {...fixed(props, kept, each0, at)}></Text>}
|
|
53
|
+
</Box>
|
|
54
|
+
)}
|
|
55
|
+
</Each>
|
|
56
|
+
</Column>
|
|
57
|
+
<Box>
|
|
58
|
+
{!withdrawable(props, kept) ? null : <Button.buttonQuiet {...withdraw(props, kept)}></Button.buttonQuiet>}
|
|
59
|
+
<Button.buttonBorder {...decline(props, kept)}></Button.buttonBorder>
|
|
60
|
+
<Button.buttonBorder {...accept(props, kept)}></Button.buttonBorder>
|
|
61
|
+
<Button.buttonFill {...acceptAll(props, kept)}></Button.buttonFill>
|
|
62
|
+
</Box>
|
|
63
|
+
</Column>
|
|
64
|
+
</Box.card>
|
|
65
|
+
</>
|
|
66
|
+
)
|
|
67
|
+
},
|
|
68
|
+
'e-consent',
|
|
69
|
+
{},
|
|
70
|
+
)
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import { useState } from 'react'
|
|
4
|
+
|
|
5
|
+
import { consentCategories, type ConsentCategory, type ConsentDecision } from '../consent.ts'
|
|
6
|
+
import type { Row } from '../charts.tsx'
|
|
7
|
+
import type { ButtonProps } from './button.tsx'
|
|
8
|
+
import type { ConsentProps } from './consent.tsx'
|
|
9
|
+
import type { TextProps } from './text.tsx'
|
|
10
|
+
import type { ToggleProps } from './toggle.tsx'
|
|
11
|
+
|
|
12
|
+
type Kept = {
|
|
13
|
+
readonly categories: readonly ConsentCategory[]
|
|
14
|
+
readonly selected: readonly string[]
|
|
15
|
+
readonly accepted: boolean
|
|
16
|
+
readonly offered: readonly string[]
|
|
17
|
+
readonly toggle: (id: string, on: boolean) => void
|
|
18
|
+
readonly decide: (decision: ConsentDecision['decision'], accepted: readonly string[]) => void
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function firstSelection(
|
|
22
|
+
categories: readonly ConsentCategory[],
|
|
23
|
+
decision: ConsentDecision | undefined,
|
|
24
|
+
preselected: readonly string[],
|
|
25
|
+
): readonly string[] {
|
|
26
|
+
const answered = decision !== undefined
|
|
27
|
+
return categories
|
|
28
|
+
.filter(
|
|
29
|
+
(one) =>
|
|
30
|
+
one.required ||
|
|
31
|
+
(answered
|
|
32
|
+
? decision.decision === 'accepted' && decision.accepted.includes(one.id)
|
|
33
|
+
: preselected.includes(one.id)),
|
|
34
|
+
)
|
|
35
|
+
.map((one) => one.id)
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export function uses(props: ConsentProps): {
|
|
39
|
+
readonly holds?: readonly string[]
|
|
40
|
+
readonly animates?: boolean
|
|
41
|
+
readonly on?: Readonly<Record<string, unknown>>
|
|
42
|
+
readonly categories: readonly ConsentCategory[]
|
|
43
|
+
readonly selected: readonly string[]
|
|
44
|
+
readonly accepted: boolean
|
|
45
|
+
readonly offered: readonly string[]
|
|
46
|
+
readonly toggle: (id: string, on: boolean) => void
|
|
47
|
+
readonly decide: (decision: ConsentDecision['decision'], accepted: readonly string[]) => void
|
|
48
|
+
} {
|
|
49
|
+
const categories = consentCategories()
|
|
50
|
+
const [selected, setSelected] = useState<readonly string[]>(() =>
|
|
51
|
+
firstSelection(categories, props.decision, props.preselected ?? []),
|
|
52
|
+
)
|
|
53
|
+
const offered = categories.map((one) => one.id)
|
|
54
|
+
|
|
55
|
+
return {
|
|
56
|
+
categories,
|
|
57
|
+
selected,
|
|
58
|
+
offered,
|
|
59
|
+
accepted: props.decision?.decision === 'accepted',
|
|
60
|
+
toggle: (id: string, on: boolean): void => {
|
|
61
|
+
setSelected((was) => (on ? [...was, id] : was.filter((one) => one !== id)))
|
|
62
|
+
},
|
|
63
|
+
decide: (decision: ConsentDecision['decision'], accepted: readonly string[]): void => {
|
|
64
|
+
props.onDecide({ decision, accepted: [...accepted], offered })
|
|
65
|
+
},
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export function categories(_props: ConsentProps, kept: Kept): readonly Row[] {
|
|
70
|
+
return kept.categories as unknown as readonly Row[]
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export function hasPolicy(props: ConsentProps, _kept: Kept): boolean {
|
|
74
|
+
return props.policyKey !== undefined
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export function withdrawable(_props: ConsentProps, kept: Kept): boolean {
|
|
78
|
+
return kept.accepted
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export function optional(_props: ConsentProps, _kept: Kept, row: Row): boolean {
|
|
82
|
+
return row['required'] !== true
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export function required(_props: ConsentProps, _kept: Kept, row: Row): boolean {
|
|
86
|
+
return row['required'] === true
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export function title(_props: ConsentProps, _kept: Kept, _at?: number): TextProps {
|
|
90
|
+
return { k: 'consent.title', is: 'heading' }
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export function policy(props: ConsentProps, _kept: Kept, _at?: number): TextProps {
|
|
94
|
+
return { k: props.policyKey ?? 'consent.title', is: 'body' }
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export function label(_props: ConsentProps, _kept: Kept, row: Row, _at: number): TextProps {
|
|
98
|
+
return { k: String(row['labelKey']), is: 'label' }
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
export function describe(_props: ConsentProps, _kept: Kept, row: Row, _at: number): TextProps {
|
|
102
|
+
return { k: String(row['describeKey']), is: 'body' }
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
export function fixed(_props: ConsentProps, _kept: Kept, _row: Row, _at: number): TextProps {
|
|
106
|
+
return { k: 'consent.required', is: 'label' }
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
export function chooser(_props: ConsentProps, kept: Kept, row: Row, _at?: number): ToggleProps {
|
|
110
|
+
const id = String(row['id'])
|
|
111
|
+
return {
|
|
112
|
+
labelKey: String(row['labelKey']),
|
|
113
|
+
on: kept.selected.includes(id),
|
|
114
|
+
onToggle: (on: boolean): void => kept.toggle(id, on),
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
export function withdraw(_props: ConsentProps, kept: Kept): ButtonProps {
|
|
119
|
+
return { label: 'consent.withdraw', onPress: (): void => kept.decide('withdrawn', []) }
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
export function decline(_props: ConsentProps, kept: Kept): ButtonProps {
|
|
123
|
+
return { label: 'consent.decline', onPress: (): void => kept.decide('declined', []) }
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
export function accept(_props: ConsentProps, kept: Kept): ButtonProps {
|
|
127
|
+
return { label: 'consent.accept', onPress: (): void => kept.decide('accepted', kept.selected) }
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
export function acceptAll(_props: ConsentProps, kept: Kept): ButtonProps {
|
|
131
|
+
return { label: 'consent.acceptAll', onPress: (): void => kept.decide('accepted', kept.offered) }
|
|
132
|
+
}
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
3
3
|
import { roleClass } from "../fonts.js";
|
|
4
4
|
import { element, useSurface } from "../wrappers.js";
|
|
5
|
-
import { Each } from "
|
|
5
|
+
import { Each } from "../elements/each.js";
|
|
6
6
|
import { slice, uses } from "./donut.works.js";
|
|
7
7
|
export const Donut = element('Donut', (props, className) => {
|
|
8
8
|
const kept = uses(props);
|
|
@@ -13,4 +13,4 @@ export const Donut = element('Donut', (props, className) => {
|
|
|
13
13
|
],
|
|
14
14
|
...(kept.animates === undefined ? {} : { animates: kept.animates }), });
|
|
15
15
|
return (_jsx("svg", { className: surface.className, ...surface.marks, ...surface.invokes, ...surface.a11y, ...(kept.on ?? {}), role: "img", ...(props.titleKey === undefined ? {} : { 'aria-label': surface.text(props.titleKey) }), children: _jsx(Each, { rows: props.rows, by: props.by, children: (each0, at) => (_jsx("circle", { ...slice(props, kept, each0, at) })) }) }));
|
|
16
|
-
}, 'e-donut');
|
|
16
|
+
}, 'e-donut', { "card": "thread-card" });
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import type { MessageKey } from '../content.tsx'
|
|
4
|
+
import type { Row } from '../charts.tsx'
|
|
5
|
+
import { roleClass } from '../fonts.ts'
|
|
6
|
+
|
|
7
|
+
import { type Element, element, useSurface } from '../wrappers.tsx'
|
|
8
|
+
import { Each } from '../elements/each.tsx'
|
|
9
|
+
import { slice, uses } from './donut.works.ts'
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
export interface DonutProps {
|
|
13
|
+
readonly rows: readonly Row[]
|
|
14
|
+
readonly by: string
|
|
15
|
+
readonly value: string
|
|
16
|
+
readonly titleKey?: MessageKey
|
|
17
|
+
readonly dispatch?: string
|
|
18
|
+
readonly input?: Readonly<Record<string, unknown>>
|
|
19
|
+
readonly invokeOn?: 'press' | 'shown'
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export const Donut: Element<'Donut', DonutProps> = element(
|
|
23
|
+
'Donut',
|
|
24
|
+
(props: DonutProps, className) => {
|
|
25
|
+
const kept = uses(props)
|
|
26
|
+
const surface = useSurface({ className: roleClass(undefined, className),
|
|
27
|
+
invokes: { ...(props.dispatch === undefined ? {} : { dispatch: props.dispatch }), ...(props.input === undefined ? {} : { input: props.input }), ...(props.invokeOn === undefined ? {} : { on: props.invokeOn }) },
|
|
28
|
+
holds: [
|
|
29
|
+
...(kept.holds ?? []),
|
|
30
|
+
],
|
|
31
|
+
...(kept.animates === undefined ? {} : { animates: kept.animates }), })
|
|
32
|
+
return (
|
|
33
|
+
<svg className={surface.className} {...surface.marks} {...surface.invokes} {...surface.a11y} {...(kept.on ?? {})} role="img" {...(props.titleKey === undefined ? {} : { 'aria-label': surface.text(props.titleKey) })}>
|
|
34
|
+
<Each rows={props.rows} by={props.by}>
|
|
35
|
+
{(each0, at) => (
|
|
36
|
+
<circle {...slice(props, kept, each0, at)}></circle>
|
|
37
|
+
)}
|
|
38
|
+
</Each>
|
|
39
|
+
</svg>
|
|
40
|
+
)
|
|
41
|
+
},
|
|
42
|
+
'e-donut',
|
|
43
|
+
{"card":"thread-card"},
|
|
44
|
+
)
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { seriesColour } from '../series.ts'
|
|
2
|
+
import { num, useRamp, type Row } from '../charts.tsx'
|
|
3
|
+
import type { DonutProps } from './donut.tsx'
|
|
4
|
+
|
|
5
|
+
const RADIUS = 40
|
|
6
|
+
const ROUND = 2 * Math.PI * RADIUS
|
|
7
|
+
|
|
8
|
+
export function uses(props: DonutProps): {
|
|
9
|
+
readonly holds?: readonly string[]
|
|
10
|
+
readonly animates?: boolean
|
|
11
|
+
readonly on?: Readonly<Record<string, unknown>>
|
|
12
|
+
readonly ramp: readonly string[]
|
|
13
|
+
} {
|
|
14
|
+
const ramp = useRamp(props.rows.length)
|
|
15
|
+
return {
|
|
16
|
+
...(props.rows.length === 0 ? { holds: ['empty'] as const } : {}),
|
|
17
|
+
on: { viewBox: '0 0 100 100' },
|
|
18
|
+
ramp,
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function slice(
|
|
23
|
+
props: DonutProps,
|
|
24
|
+
kept: { readonly ramp: readonly string[] },
|
|
25
|
+
row: Row,
|
|
26
|
+
at: number,
|
|
27
|
+
): Readonly<Record<string, string | number>> {
|
|
28
|
+
const whole = props.rows.reduce((sum: number, one: Row) => sum + num(one, props.value), 0) || 1
|
|
29
|
+
const share = num(row, props.value) / whole
|
|
30
|
+
const before = props.rows.slice(0, at).reduce((sum: number, one: Row) => sum + num(one, props.value), 0) / whole
|
|
31
|
+
return {
|
|
32
|
+
cx: 50,
|
|
33
|
+
cy: 50,
|
|
34
|
+
r: RADIUS,
|
|
35
|
+
fill: 'none',
|
|
36
|
+
stroke: seriesColour(kept.ramp, at),
|
|
37
|
+
strokeWidth: 16,
|
|
38
|
+
strokeDasharray: `${(share * ROUND).toFixed(2)} ${ROUND.toFixed(2)}`,
|
|
39
|
+
strokeDashoffset: (-before * ROUND).toFixed(2),
|
|
40
|
+
transform: 'rotate(-90 50 50)',
|
|
41
|
+
}
|
|
42
|
+
}
|
package/dist/ui/elements/each.js
CHANGED
|
@@ -6,4 +6,4 @@ export const Each = element('Each', (props, className) => {
|
|
|
6
6
|
const surface = useSurface({ className: roleClass(undefined, className),
|
|
7
7
|
invokes: { ...(props.dispatch === undefined ? {} : { dispatch: props.dispatch }), ...(props.input === undefined ? {} : { input: props.input }), ...(props.invokeOn === undefined ? {} : { on: props.invokeOn }) }, });
|
|
8
8
|
return (shows(props, surface));
|
|
9
|
-
}, 'e-each');
|
|
9
|
+
}, 'e-each', {});
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import type { ReactNode } from 'react'
|
|
4
|
+
import type { Row } from '../charts.tsx'
|
|
5
|
+
import { roleClass } from '../fonts.ts'
|
|
6
|
+
|
|
7
|
+
import { type Element, element, useSurface } from '../wrappers.tsx'
|
|
8
|
+
import { shows } from './each.works.tsx'
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
export interface EachProps {
|
|
12
|
+
readonly rows: readonly object[]
|
|
13
|
+
readonly by: string
|
|
14
|
+
readonly children: (row: Row, at: number) => ReactNode
|
|
15
|
+
readonly settles?: string
|
|
16
|
+
readonly paced?: string
|
|
17
|
+
readonly dispatch?: string
|
|
18
|
+
readonly input?: Readonly<Record<string, unknown>>
|
|
19
|
+
readonly invokeOn?: 'press' | 'shown'
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export const Each: Element<'Each', EachProps> = element(
|
|
23
|
+
'Each',
|
|
24
|
+
(props: EachProps, className) => {
|
|
25
|
+
const surface = useSurface({ className: roleClass(undefined, className),
|
|
26
|
+
invokes: { ...(props.dispatch === undefined ? {} : { dispatch: props.dispatch }), ...(props.input === undefined ? {} : { input: props.input }), ...(props.invokeOn === undefined ? {} : { on: props.invokeOn }) }, })
|
|
27
|
+
return (
|
|
28
|
+
shows(props, surface)
|
|
29
|
+
)
|
|
30
|
+
},
|
|
31
|
+
'e-each',
|
|
32
|
+
{},
|
|
33
|
+
)
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { Fragment, useLayoutEffect, useRef, type ReactNode } from 'react'
|
|
2
|
+
|
|
3
|
+
import type { Row } from '../charts.tsx'
|
|
4
|
+
import { nameOf } from '../token.ts'
|
|
5
|
+
import type { EachProps } from './each.tsx'
|
|
6
|
+
|
|
7
|
+
function keyOf(row: object, by: string, at: number): string {
|
|
8
|
+
const value = (row as Record<string, unknown>)[by]
|
|
9
|
+
if (value === undefined || value === null) {
|
|
10
|
+
throw new Error(
|
|
11
|
+
`Each: row ${String(at)} has no value for "${by}", so it has no key. React reuses DOM by key, and a ` +
|
|
12
|
+
"row without one is matched by position — which silently keeps the previous row's state when " +
|
|
13
|
+
'the list reorders. Name the field that identifies a row in this view.',
|
|
14
|
+
)
|
|
15
|
+
}
|
|
16
|
+
return String(value)
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function following(marker: HTMLElement): readonly HTMLElement[] {
|
|
20
|
+
const found: HTMLElement[] = []
|
|
21
|
+
let node = marker.nextElementSibling
|
|
22
|
+
while (node !== null) {
|
|
23
|
+
found.push(node as HTMLElement)
|
|
24
|
+
node = node.nextElementSibling
|
|
25
|
+
}
|
|
26
|
+
return found
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function boxes(marker: HTMLElement, ids: readonly string[]): Map<string, DOMRect> {
|
|
30
|
+
const found = new Map<string, DOMRect>()
|
|
31
|
+
following(marker).forEach((node, at) => {
|
|
32
|
+
const id = ids[at]
|
|
33
|
+
if (id !== undefined) found.set(id, node.getBoundingClientRect())
|
|
34
|
+
})
|
|
35
|
+
return found
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function settle(marker: HTMLElement, ids: readonly string[], was: Map<string, DOMRect>, over: string, paced: string): void {
|
|
39
|
+
following(marker).forEach((node, at) => {
|
|
40
|
+
const id = ids[at]
|
|
41
|
+
if (id === undefined) return
|
|
42
|
+
const before = was.get(id)
|
|
43
|
+
if (before === undefined) return
|
|
44
|
+
const after = node.getBoundingClientRect()
|
|
45
|
+
const dx = before.left - after.left
|
|
46
|
+
const dy = before.top - after.top
|
|
47
|
+
if (Math.abs(dx) < 0.5 && Math.abs(dy) < 0.5) return
|
|
48
|
+
|
|
49
|
+
node.style.transition = `transform var(${nameOf('motion-instant')})`
|
|
50
|
+
node.style.transform = `translate(${String(dx)}px, ${String(dy)}px)`
|
|
51
|
+
void node.offsetWidth
|
|
52
|
+
node.style.transition = `transform var(--${over}) var(--${paced})`
|
|
53
|
+
node.style.transform = ''
|
|
54
|
+
})
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export function shows(props: EachProps, _surface: unknown): ReactNode {
|
|
58
|
+
const within = useRef<HTMLElement | null>(null)
|
|
59
|
+
const was = useRef<Map<string, DOMRect>>(new Map())
|
|
60
|
+
const drawn = useRef('')
|
|
61
|
+
|
|
62
|
+
const order = props.rows.map((row, at) => keyOf(row, props.by, at)).join('|')
|
|
63
|
+
|
|
64
|
+
useLayoutEffect(() => {
|
|
65
|
+
const marker = within.current
|
|
66
|
+
if (marker === null || props.settles === undefined || props.paced === undefined) return
|
|
67
|
+
const ids = order === '' ? [] : order.split('|')
|
|
68
|
+
const moved = drawn.current !== '' && drawn.current !== order
|
|
69
|
+
if (moved) settle(marker, ids, was.current, props.settles, props.paced)
|
|
70
|
+
drawn.current = order
|
|
71
|
+
was.current = boxes(marker, ids)
|
|
72
|
+
}, [order, props.settles, props.paced])
|
|
73
|
+
|
|
74
|
+
return (
|
|
75
|
+
<>
|
|
76
|
+
<span ref={within} hidden />
|
|
77
|
+
{props.rows.map((row, at) => (
|
|
78
|
+
<Fragment key={keyOf(row, props.by, at)}>{props.children(row as Row, at)}</Fragment>
|
|
79
|
+
))}
|
|
80
|
+
</>
|
|
81
|
+
)
|
|
82
|
+
}
|
|
@@ -9,4 +9,4 @@ export const Empty = element('Empty', (props, className) => {
|
|
|
9
9
|
'empty',
|
|
10
10
|
], });
|
|
11
11
|
return (_jsx("div", { className: surface.className, ...surface.marks, ...surface.invokes, ...surface.a11y, children: surface.text(props.labelKey) }));
|
|
12
|
-
}, 'e-empty');
|
|
12
|
+
}, 'e-empty', { "card": "thread-card" });
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import type { MessageKey } from '../content.tsx'
|
|
4
|
+
import { roleClass } from '../fonts.ts'
|
|
5
|
+
|
|
6
|
+
import { type Element, element, useSurface } from '../wrappers.tsx'
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
export interface EmptyProps {
|
|
10
|
+
readonly labelKey: MessageKey
|
|
11
|
+
readonly dispatch?: string
|
|
12
|
+
readonly input?: Readonly<Record<string, unknown>>
|
|
13
|
+
readonly invokeOn?: 'press' | 'shown'
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export const Empty: Element<'Empty', EmptyProps> = element(
|
|
17
|
+
'Empty',
|
|
18
|
+
(props: EmptyProps, className) => {
|
|
19
|
+
const surface = useSurface({ className: roleClass(undefined, className),
|
|
20
|
+
invokes: { ...(props.dispatch === undefined ? {} : { dispatch: props.dispatch }), ...(props.input === undefined ? {} : { input: props.input }), ...(props.invokeOn === undefined ? {} : { on: props.invokeOn }) },
|
|
21
|
+
holds: [
|
|
22
|
+
'empty' as const,
|
|
23
|
+
], })
|
|
24
|
+
return (
|
|
25
|
+
<div className={surface.className} {...surface.marks} {...surface.invokes} {...surface.a11y}>
|
|
26
|
+
{surface.text(props.labelKey)}
|
|
27
|
+
</div>
|
|
28
|
+
)
|
|
29
|
+
},
|
|
30
|
+
'e-empty',
|
|
31
|
+
{"card":"thread-card"},
|
|
32
|
+
)
|
|
@@ -12,4 +12,4 @@ export const Field = element('Field', (props, className) => {
|
|
|
12
12
|
],
|
|
13
13
|
...(kept.animates === undefined ? {} : { animates: kept.animates }), });
|
|
14
14
|
return (_jsxs("label", { className: surface.className, ...surface.marks, ...surface.invokes, ...surface.a11y, ...(kept.on ?? {}), children: [_jsx("span", { children: surface.text(props.labelKey) }), _jsx("input", { ...entry(props, kept) }), _jsx("span", { children: shown(props, surface, kept).fault })] }));
|
|
15
|
-
}, 'e-field');
|
|
15
|
+
}, 'e-field', { "input": "thread-input" });
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import type { MessageKey } from '../content.tsx'
|
|
4
|
+
import { roleClass } from '../fonts.ts'
|
|
5
|
+
|
|
6
|
+
import { type Element, element, useSurface } from '../wrappers.tsx'
|
|
7
|
+
import { shown, entry, uses } from './field.works.ts'
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
export interface FieldProps {
|
|
11
|
+
readonly name: string
|
|
12
|
+
readonly validator: string
|
|
13
|
+
readonly labelKey: MessageKey
|
|
14
|
+
readonly value?: string
|
|
15
|
+
readonly onValue?: (said: string) => void
|
|
16
|
+
readonly context?: Readonly<Record<string, unknown>>
|
|
17
|
+
readonly autocomplete?: string
|
|
18
|
+
readonly disabled?: boolean
|
|
19
|
+
readonly dispatch?: string
|
|
20
|
+
readonly input?: Readonly<Record<string, unknown>>
|
|
21
|
+
readonly invokeOn?: 'press' | 'shown'
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export const Field: Element<'Field', FieldProps> = element(
|
|
25
|
+
'Field',
|
|
26
|
+
(props: FieldProps, className) => {
|
|
27
|
+
const kept = uses(props)
|
|
28
|
+
const surface = useSurface({ className: roleClass(undefined, className),
|
|
29
|
+
invokes: { ...(props.dispatch === undefined ? {} : { dispatch: props.dispatch }), ...(props.input === undefined ? {} : { input: props.input }), ...(props.invokeOn === undefined ? {} : { on: props.invokeOn }) },
|
|
30
|
+
holds: [
|
|
31
|
+
...(kept.holds ?? []),
|
|
32
|
+
],
|
|
33
|
+
...(kept.animates === undefined ? {} : { animates: kept.animates }), })
|
|
34
|
+
return (
|
|
35
|
+
<label className={surface.className} {...surface.marks} {...surface.invokes} {...surface.a11y} {...(kept.on ?? {})}>
|
|
36
|
+
<span>{surface.text(props.labelKey)}</span>
|
|
37
|
+
<input {...entry(props, kept)} />
|
|
38
|
+
<span>{shown(props, surface, kept).fault}</span>
|
|
39
|
+
</label>
|
|
40
|
+
)
|
|
41
|
+
},
|
|
42
|
+
'e-field',
|
|
43
|
+
{"input":"thread-input"},
|
|
44
|
+
)
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { useState, type ReactNode } from 'react'
|
|
2
|
+
|
|
3
|
+
import { acceptsKeystroke, applyMask, entryConstraints, evaluateMoney, runValidator } from '../validators.ts'
|
|
4
|
+
import type { Surface } from '../door.ts'
|
|
5
|
+
import type { FieldProps } from './field.tsx'
|
|
6
|
+
|
|
7
|
+
export function uses(props: FieldProps): {
|
|
8
|
+
readonly holds?: readonly string[]
|
|
9
|
+
readonly animates?: boolean
|
|
10
|
+
readonly on?: Readonly<Record<string, unknown>>
|
|
11
|
+
readonly wrong: string | null
|
|
12
|
+
readonly said: (to: string | null) => void
|
|
13
|
+
} {
|
|
14
|
+
const [wrong, said] = useState<string | null>(null)
|
|
15
|
+
return { ...(props.disabled === true ? { holds: ['disabled'] as const } : {}), wrong, said }
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function entry(
|
|
19
|
+
props: FieldProps,
|
|
20
|
+
kept: { readonly wrong: string | null; readonly said: (to: string | null) => void },
|
|
21
|
+
): Readonly<Record<string, unknown>> {
|
|
22
|
+
const rules = entryConstraints(props.validator, props.context ?? {})
|
|
23
|
+
return {
|
|
24
|
+
name: props.name,
|
|
25
|
+
...(rules.secret === true ? { type: 'password' } : {}),
|
|
26
|
+
...(props.autocomplete ?? rules.autocomplete === undefined
|
|
27
|
+
? {}
|
|
28
|
+
: { autoComplete: props.autocomplete ?? rules.autocomplete }),
|
|
29
|
+
...(rules.keyboard === 'text' ? {} : { inputMode: rules.keyboard }),
|
|
30
|
+
...(rules.maxChars === undefined ? {} : { maxLength: rules.maxChars }),
|
|
31
|
+
value: props.value ?? '',
|
|
32
|
+
...(kept.wrong === null ? {} : { 'aria-invalid': true }),
|
|
33
|
+
|
|
34
|
+
onChange: (event: { readonly target: { readonly value: string } }): void => {
|
|
35
|
+
let kept_ = ''
|
|
36
|
+
for (const ch of event.target.value) {
|
|
37
|
+
if (acceptsKeystroke(props.validator, kept_, ch)) kept_ += ch
|
|
38
|
+
}
|
|
39
|
+
props.onValue?.(applyMask(props.validator, kept_))
|
|
40
|
+
},
|
|
41
|
+
|
|
42
|
+
onBlur: (): void => {
|
|
43
|
+
const text = props.value ?? ''
|
|
44
|
+
let judged: unknown = text
|
|
45
|
+
if (rules.entry === 'calculator') {
|
|
46
|
+
const currency = props.context?.['currency']
|
|
47
|
+
const resolved = typeof currency === 'string' ? evaluateMoney(text, currency) : undefined
|
|
48
|
+
if (resolved !== undefined) {
|
|
49
|
+
judged = { amount: resolved.amount, currency }
|
|
50
|
+
props.onValue?.(String(resolved.amount))
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
const result = runValidator(props.validator, judged, props.context ?? {})
|
|
54
|
+
kept.said(result.ok ? null : result.errorKey)
|
|
55
|
+
},
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export function shown(
|
|
60
|
+
_props: FieldProps,
|
|
61
|
+
surface: Surface,
|
|
62
|
+
kept: { readonly wrong: string | null },
|
|
63
|
+
): Readonly<Record<string, ReactNode>> {
|
|
64
|
+
return { fault: kept.wrong === null ? null : surface.text(kept.wrong) }
|
|
65
|
+
}
|
|
@@ -3,7 +3,7 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
3
3
|
import { roleClass } from "../fonts.js";
|
|
4
4
|
import { measured as marking } from "../charts.js";
|
|
5
5
|
import { element, useSurface } from "../wrappers.js";
|
|
6
|
-
import { Each } from "
|
|
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
|
+
)
|