@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,794 @@
|
|
|
1
|
+
import { existsSync, readFileSync, writeFileSync } from 'node:fs'
|
|
2
|
+
import { join, resolve } from 'node:path'
|
|
3
|
+
import { argv, stdout } from 'node:process'
|
|
4
|
+
import { fileURLToPath, pathToFileURL } from 'node:url'
|
|
5
|
+
|
|
6
|
+
import { exit, invoked } from '@prestack/thread/core/invoked'
|
|
7
|
+
import { findTree, inTree } from '@prestack/thread/core/tree'
|
|
8
|
+
|
|
9
|
+
import { camel, classOf, classOfElement, looking } from '../variant.ts'
|
|
10
|
+
|
|
11
|
+
const idOf = (name: string): string => name.replace(/([a-z0-9])([A-Z])/g, '$1-$2').toLowerCase()
|
|
12
|
+
|
|
13
|
+
export function pascal(id: string): string {
|
|
14
|
+
return id
|
|
15
|
+
.split('-')
|
|
16
|
+
.map((part) => `${part.charAt(0).toUpperCase()}${part.slice(1)}`)
|
|
17
|
+
.join('')
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const TAG = /<([A-Z][A-Za-z0-9]*)/g
|
|
21
|
+
|
|
22
|
+
export const OWNED_AT = 'ui/'
|
|
23
|
+
|
|
24
|
+
export function owned(
|
|
25
|
+
body: string,
|
|
26
|
+
rows: readonly { readonly id: string; readonly at?: string }[],
|
|
27
|
+
): readonly string[] {
|
|
28
|
+
const named = new Map<string, string>()
|
|
29
|
+
for (const row of rows) {
|
|
30
|
+
const at = row.at
|
|
31
|
+
if (typeof at !== 'string' || !at.startsWith(OWNED_AT)) continue
|
|
32
|
+
named.set(pascal(row.id), at)
|
|
33
|
+
}
|
|
34
|
+
const found = new Set<string>()
|
|
35
|
+
for (const [, tag] of body.matchAll(TAG)) if (tag !== undefined && named.has(tag)) found.add(tag)
|
|
36
|
+
return [...found].sort().map((one) => `import { ${one} } from '../${named.get(one) ?? ''}'`)
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export function translated(
|
|
40
|
+
name: string,
|
|
41
|
+
tag: string | Readonly<Record<string, string>>,
|
|
42
|
+
arranges: boolean,
|
|
43
|
+
raises: readonly string[],
|
|
44
|
+
from: { readonly wrappers: string; readonly content: string; readonly rows: string; readonly validators: string; readonly fonts: string; readonly map: string; readonly parts: string; readonly consent: string; readonly drag: string },
|
|
45
|
+
carries: Readonly<Record<string, string>> = {},
|
|
46
|
+
always: readonly string[] = [],
|
|
47
|
+
places: Readonly<Record<string, Readonly<Record<string, string>>>> = {},
|
|
48
|
+
works?: string,
|
|
49
|
+
measures: readonly string[] = [],
|
|
50
|
+
reads?: string,
|
|
51
|
+
gives: readonly string[] = [],
|
|
52
|
+
keeps = false,
|
|
53
|
+
animates = false,
|
|
54
|
+
says?: string,
|
|
55
|
+
called?: string,
|
|
56
|
+
wires?: string,
|
|
57
|
+
dresses?: string,
|
|
58
|
+
): string {
|
|
59
|
+
const made = pascal(name)
|
|
60
|
+
|
|
61
|
+
const dressing = wears.get(name)
|
|
62
|
+
if (dressing === undefined) {
|
|
63
|
+
throw new Error(
|
|
64
|
+
`\`${name}\` is written before the variant rows that wear it were read, so it would ship wearing none of them`,
|
|
65
|
+
)
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const invoking = [
|
|
69
|
+
...(carries['dispatch'] === undefined ? [' readonly dispatch?: string'] : []),
|
|
70
|
+
...(carries['input'] === undefined ? [' readonly input?: Readonly<Record<string, unknown>>'] : []),
|
|
71
|
+
" readonly invokeOn?: 'press' | 'shown'",
|
|
72
|
+
]
|
|
73
|
+
|
|
74
|
+
const stated = Object.entries(carries)
|
|
75
|
+
|
|
76
|
+
const names = stated.find(([, kind]) => NAMES(kind))
|
|
77
|
+
const source = stated.find(([, kind]) => SOURCES(kind))
|
|
78
|
+
|
|
79
|
+
const silent = reads !== undefined && READS[reads]?.hidden === true
|
|
80
|
+
|
|
81
|
+
const bare = typeof tag === 'string' && tag === ''
|
|
82
|
+
|
|
83
|
+
const worded = silent || bare || called !== undefined || Object.keys(places).length > 0 || (works !== undefined && !arranges) || stated.some(([, kind]) => NAMES(kind) || KINDS[kind]?.from === 'content')
|
|
84
|
+
|
|
85
|
+
const chosen = typeof tag === 'string' ? undefined : tag
|
|
86
|
+
const first = typeof tag === 'string' ? tag : (Object.entries(tag).find(([key]) => key !== 'by')?.[1] ?? 'div')
|
|
87
|
+
|
|
88
|
+
const titled = called === undefined ? '' : ` aria-label={surface.text('${called}')}`
|
|
89
|
+
|
|
90
|
+
const naming = (one: string): string =>
|
|
91
|
+
names === undefined || SPEAKS.has(one)
|
|
92
|
+
? ''
|
|
93
|
+
: KINDS[names[1]]?.needed === false
|
|
94
|
+
? ` {...(props.${names[0]} === undefined ? {} : { 'aria-label': surface.text(props.${names[0]}) })}`
|
|
95
|
+
: one === 'img'
|
|
96
|
+
? ` alt={surface.text(props.${names[0]})}`
|
|
97
|
+
: ` aria-label={surface.text(props.${names[0]})}`
|
|
98
|
+
const sourced = (one: string): string =>
|
|
99
|
+
source === undefined ? '' : ` ${SOURCE[one] ?? 'src'}={props.${source[0]}}`
|
|
100
|
+
|
|
101
|
+
const props = [
|
|
102
|
+
...(arranges
|
|
103
|
+
? [' readonly children: ReactNode']
|
|
104
|
+
: worded
|
|
105
|
+
? []
|
|
106
|
+
: [' readonly labelKey: MessageKey']),
|
|
107
|
+
|
|
108
|
+
...stated.map(([id, kind]) => {
|
|
109
|
+
const held = KINDS[kind]
|
|
110
|
+
|
|
111
|
+
const picks = chosen !== undefined && chosen['by'] === id
|
|
112
|
+
const must = held?.needed === true || measures.includes(id) || picks
|
|
113
|
+
const is = picks
|
|
114
|
+
? Object.keys(chosen).filter((key) => key !== 'by').map((key) => `'${key}'`).join(' | ')
|
|
115
|
+
: (held?.is ?? 'never')
|
|
116
|
+
return ` readonly ${id}${must ? '' : '?'}: ${is}`
|
|
117
|
+
}),
|
|
118
|
+
...raises.map((one) => ` readonly ${one}?: boolean`),
|
|
119
|
+
]
|
|
120
|
+
|
|
121
|
+
const SAID = new Set(['tag', 'shows', 'reads', 'holds', 'each', 'by', 'takes', 'marks', 'gives', 'keyed', 'when'])
|
|
122
|
+
|
|
123
|
+
const named = [
|
|
124
|
+
...new Set(
|
|
125
|
+
Object.values(places)
|
|
126
|
+
.map((made) => (made as Record<string, string> | undefined)?.['tag'])
|
|
127
|
+
.filter((one): one is string => one !== undefined && /^[A-Z]/.test(one))
|
|
128
|
+
.map((one) => one.split('.')[0] ?? ''),
|
|
129
|
+
),
|
|
130
|
+
]
|
|
131
|
+
|
|
132
|
+
const worn = named.filter((one) => declared.has(idOf(one)))
|
|
133
|
+
const built = named.filter((one) => !declared.has(idOf(one)))
|
|
134
|
+
|
|
135
|
+
const nested = new Set<string>()
|
|
136
|
+
for (const made of Object.values(places)) {
|
|
137
|
+
const held = (made as Record<string, string> | undefined)?.['holds']
|
|
138
|
+
if (typeof held === 'string') for (const one of held.split(/\s+/).filter(Boolean)) nested.add(one)
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
const region = (place: string, depth: number, rows: readonly string[] = [], looped = false): string => {
|
|
142
|
+
const made = (places[place] ?? {}) as Record<string, string>
|
|
143
|
+
const row = rows[rows.length - 1]
|
|
144
|
+
const gave = rows.length === 0 ? '' : `, ${rows.join(', ')}`
|
|
145
|
+
const pad = ' ' + ' '.repeat(depth)
|
|
146
|
+
const as = made['tag'] ?? 'span'
|
|
147
|
+
const meant = made['reads'] === undefined ? undefined : READS[made['reads']]
|
|
148
|
+
const from = made['shows']
|
|
149
|
+
|
|
150
|
+
const kind = from === undefined ? undefined : carries[from]
|
|
151
|
+
const shape = kind === undefined ? undefined : KINDS[kind]
|
|
152
|
+
|
|
153
|
+
const points = shape?.from === 'source' && from !== undefined
|
|
154
|
+
|
|
155
|
+
const pointed = points ? ` src={props.${String(from)}}${as === 'img' ? ' alt=""' : ''}` : ''
|
|
156
|
+
|
|
157
|
+
const shown =
|
|
158
|
+
points
|
|
159
|
+
? ''
|
|
160
|
+
: from === undefined
|
|
161
|
+
? ''
|
|
162
|
+
: row !== undefined
|
|
163
|
+
? shape === undefined
|
|
164
|
+
? `{shown(props${gave}, surface${keeps ? ', kept' : ''}).${from}}`
|
|
165
|
+
: `{surface.text(String(${row}.${from}))}`
|
|
166
|
+
: shape === undefined
|
|
167
|
+
? `{shown(props, surface${keeps ? ', kept' : ''}${wires === undefined ? '' : ', wired'}).${from}}`
|
|
168
|
+
: shape.from === 'content'
|
|
169
|
+
? `{surface.text(props.${from})}`
|
|
170
|
+
: `{props.${from}}`
|
|
171
|
+
|
|
172
|
+
const facing =
|
|
173
|
+
meant?.orients === undefined || carries[meant.orients] === undefined
|
|
174
|
+
? ''
|
|
175
|
+
: ` aria-orientation={props.${meant.orients} === 'vertical' ? 'vertical' : 'horizontal'}`
|
|
176
|
+
|
|
177
|
+
const taken = (made['takes'] ?? '')
|
|
178
|
+
.split(/\s+/)
|
|
179
|
+
.filter(Boolean)
|
|
180
|
+
.map((one) => GIVES[one] ?? '')
|
|
181
|
+
.join('')
|
|
182
|
+
|
|
183
|
+
const sized =
|
|
184
|
+
made['marks'] === undefined || row === undefined
|
|
185
|
+
? ''
|
|
186
|
+
: ` style={marking(${made['marks']}(props${keeps ? ', kept' : ''}${gave}, at))}`
|
|
187
|
+
|
|
188
|
+
const handed =
|
|
189
|
+
made['gives'] === undefined
|
|
190
|
+
? ''
|
|
191
|
+
: ` {...${made['gives']}(props${keeps ? ', kept' : ''}${gave}${row === undefined ? '' : ', at'})}`
|
|
192
|
+
|
|
193
|
+
const owns =
|
|
194
|
+
row === undefined || IDENTIFIES[as] === undefined || made['by'] === undefined
|
|
195
|
+
? ''
|
|
196
|
+
: ` ${String(IDENTIFIES[as])}={String(${row}.${String(made['by'])})}`
|
|
197
|
+
|
|
198
|
+
const bound =
|
|
199
|
+
owns +
|
|
200
|
+
handed +
|
|
201
|
+
sized +
|
|
202
|
+
pointed +
|
|
203
|
+
facing +
|
|
204
|
+
taken +
|
|
205
|
+
(meant === undefined
|
|
206
|
+
? ''
|
|
207
|
+
: (meant.control ?? '') +
|
|
208
|
+
|
|
209
|
+
(from === undefined || row !== undefined || meant.state === undefined || carries[from] === undefined
|
|
210
|
+
? ''
|
|
211
|
+
: ` ${meant.state}={props.${from}${meant.state === 'checked' ? ' === true' : " ?? ''"}}`) +
|
|
212
|
+
(meant.answers ?? ''))
|
|
213
|
+
|
|
214
|
+
const held = (made['holds'] ?? '').split(/\s+/).filter(Boolean)
|
|
215
|
+
const within = held.map((one) => region(one, depth + 1, rows)).join('\n')
|
|
216
|
+
|
|
217
|
+
const body = within === '' ? (meant?.state === undefined ? shown : '') : `\n${within}\n${pad}`
|
|
218
|
+
|
|
219
|
+
const dressed = /^[A-Z]/.test(as)
|
|
220
|
+
? ''
|
|
221
|
+
: place === dresses
|
|
222
|
+
? ` className={\`e-${name}-${place} \${surface.className ?? ''}\`}`
|
|
223
|
+
: looking(made)
|
|
224
|
+
? ` className="e-${name}-${place}"`
|
|
225
|
+
: ''
|
|
226
|
+
|
|
227
|
+
const written = SHUT.has(as)
|
|
228
|
+
? `<${as}${dressed}${bound} />`
|
|
229
|
+
: `<${as}${dressed}${bound}>${body}</${as}>`
|
|
230
|
+
|
|
231
|
+
const only = made['when']
|
|
232
|
+
|
|
233
|
+
const one =
|
|
234
|
+
only !== undefined
|
|
235
|
+
? `${pad}{!${only}(props${keeps ? ', kept' : ''}${gave}) ? null : ${written}}`
|
|
236
|
+
: shape !== undefined && !shape.needed && from !== undefined && row === undefined
|
|
237
|
+
? `${pad}{props.${from} === undefined ? null : ${written}}`
|
|
238
|
+
: `${pad}${written}`
|
|
239
|
+
|
|
240
|
+
const over = made['each']
|
|
241
|
+
if (looped || over === undefined) return one
|
|
242
|
+
|
|
243
|
+
if (KINDS[carries[over] ?? '']?.is === 'number') {
|
|
244
|
+
return (
|
|
245
|
+
`${pad}{Array.from({ length: Math.max(1, props.${over} ?? 1) }, (_, at) => (\n` +
|
|
246
|
+
`${region(place, depth + 2, rows, true).replace(`className="e-${name}-${place}"`, `key={at} className="e-${name}-${place}"`)}\n` +
|
|
247
|
+
`${pad}))}`
|
|
248
|
+
)
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
const by = made['by']
|
|
252
|
+
if (by === undefined) {
|
|
253
|
+
if (made['keyed'] !== 'position') {
|
|
254
|
+
throw new Error(
|
|
255
|
+
`\`${name}\` repeats the place \`${place}\` over \`${String(over)}\` and names no field to identify a ` +
|
|
256
|
+
'row by. React matches an unkeyed list by position and keeps the previous row\'s state when it ' +
|
|
257
|
+
'reorders. Name the field with `by`, or state `keyed: \'position\'` where position IS identity.',
|
|
258
|
+
)
|
|
259
|
+
}
|
|
260
|
+
return (
|
|
261
|
+
`${pad}{props.${over}.map((${`each${String(rows.length)}`}, at) => (\n` +
|
|
262
|
+
`${region(place, depth + 2, [...rows, `each${String(rows.length)}`], true).replace(`<${as}`, `<${as} key={at}`)}\n` +
|
|
263
|
+
`${pad}))}`
|
|
264
|
+
)
|
|
265
|
+
}
|
|
266
|
+
return (
|
|
267
|
+
`${pad}<Each rows={${carries[over] === undefined ? `${over}(props${keeps ? ', kept' : ''})` : `props.${over}`}} by={${carries[by] === undefined ? `'${by}'` : `props.${by}`}}>\n` +
|
|
268
|
+
`${pad} {(${`each${String(rows.length)}`}, at) => (\n${region(place, depth + 2, [...rows, `each${String(rows.length)}`], true)}\n${pad} )}\n` +
|
|
269
|
+
`${pad}</Each>`
|
|
270
|
+
)
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
const repeats = Object.values(places).some((made) => {
|
|
274
|
+
const over = (made as Record<string, string> | undefined)?.['each']
|
|
275
|
+
return over !== undefined && KINDS[carries[over] ?? '']?.is !== 'number' && (made as Record<string, string>)['keyed'] !== 'position'
|
|
276
|
+
})
|
|
277
|
+
const worked = Object.entries(places).some(([, made]) => {
|
|
278
|
+
const from = (made as Record<string, string> | undefined)?.['shows']
|
|
279
|
+
return from !== undefined && carries[from] === undefined
|
|
280
|
+
})
|
|
281
|
+
|
|
282
|
+
const answers = (reads !== undefined && READS[reads]?.answers !== undefined) || Object.values(places).some((made) => {
|
|
283
|
+
const said = (made as Record<string, string> | undefined)?.['reads']
|
|
284
|
+
return said !== undefined && READS[said]?.answers !== undefined
|
|
285
|
+
})
|
|
286
|
+
|
|
287
|
+
const inside = Object.keys(places)
|
|
288
|
+
.filter((place) => !nested.has(place))
|
|
289
|
+
.map((place) => region(place, 0))
|
|
290
|
+
.join('\n')
|
|
291
|
+
|
|
292
|
+
const roles = stated.find(([, kind]) => KINDS[kind]?.from === 'fonts')
|
|
293
|
+
const roled =
|
|
294
|
+
roles !== undefined
|
|
295
|
+
? `: roleClass(props.${roles[0]}, className)`
|
|
296
|
+
: arranges
|
|
297
|
+
? ''
|
|
298
|
+
: ': roleClass(undefined, className)'
|
|
299
|
+
|
|
300
|
+
const measured = measures.length === 0 || wires !== undefined ? '' : ' style={surface.measures}'
|
|
301
|
+
|
|
302
|
+
const meant = reads === undefined ? undefined : READS[reads]
|
|
303
|
+
|
|
304
|
+
const wired =
|
|
305
|
+
meant === undefined
|
|
306
|
+
? ''
|
|
307
|
+
: (meant.control ?? '') +
|
|
308
|
+
(meant.binds === undefined || carries[meant.binds] === undefined
|
|
309
|
+
? ''
|
|
310
|
+
: ` ${String(meant.state)}={props.${meant.binds} ?? ${KINDS[carries[meant.binds] ?? '']?.is === 'number' ? '0' : "''"}}`) +
|
|
311
|
+
(meant.answers ?? '')
|
|
312
|
+
|
|
313
|
+
const gathered = (gives ?? [])
|
|
314
|
+
.map((one) => GIVES[one] ?? '')
|
|
315
|
+
.join('')
|
|
316
|
+
|
|
317
|
+
const wired2 = wires === undefined ? '' : ' {...(wired.on ?? {})}'
|
|
318
|
+
|
|
319
|
+
const meaning =
|
|
320
|
+
meant === undefined
|
|
321
|
+
? ''
|
|
322
|
+
: (meant.role === undefined || (meant.needs !== undefined && carries[meant.needs] === undefined)
|
|
323
|
+
? ''
|
|
324
|
+
: meant.needs === undefined || KINDS[carries[meant.needs] ?? '']?.needed === true
|
|
325
|
+
? ` role="${meant.role}"`
|
|
326
|
+
: ` {...(props.${meant.needs} === undefined ? {} : { role: '${meant.role}' as const })}`) +
|
|
327
|
+
(meant.hidden === true ? ' aria-hidden="true"' : '') +
|
|
328
|
+
(meant.orients === undefined || carries[meant.orients] === undefined
|
|
329
|
+
? ''
|
|
330
|
+
: ` aria-orientation={props.${meant.orients} === 'vertical' ? 'vertical' : 'horizontal'}`) +
|
|
331
|
+
(meant.reports === undefined || !measures.includes(meant.reports)
|
|
332
|
+
? ''
|
|
333
|
+
: ` aria-valuenow={Number(measured(props).${meant.reports})} aria-valuemin={0} aria-valuemax={100}`)
|
|
334
|
+
|
|
335
|
+
const opening = (one: string): string =>
|
|
336
|
+
`<${one}${dresses === undefined ? ' className={surface.className}' : ''} {...surface.marks} {...surface.invokes} {...surface.a11y}${keeps ? ' {...(kept.on ?? {})}' : ''}${meaning}${titled}${announcing}${wired}${wired2}${gathered}${measured}${sourced(one)}${naming(one)}${EXTRA[one] ?? ''}${source === undefined ? '' : (FETCHES[one] ?? '')}`
|
|
337
|
+
|
|
338
|
+
const empty = typeof tag === 'string' && SHUT.has(tag)
|
|
339
|
+
|
|
340
|
+
const holds = stated.find(([, kind]) => kind === 'holding')
|
|
341
|
+
|
|
342
|
+
const telling =
|
|
343
|
+
says === undefined
|
|
344
|
+
? undefined
|
|
345
|
+
: KINDS[carries[says] ?? '']?.from === 'content'
|
|
346
|
+
? `{surface.text(props.${says}${holds === undefined ? '' : `, props.${holds[0]}`})}`
|
|
347
|
+
: `{props.${says}}`
|
|
348
|
+
|
|
349
|
+
const announced = stated.find(([, kind]) => kind === 'announcing')
|
|
350
|
+
const announcing = announced === undefined ? '' : ` role={props.${announced[0]}}`
|
|
351
|
+
|
|
352
|
+
const content =
|
|
353
|
+
telling !== undefined
|
|
354
|
+
? telling
|
|
355
|
+
: works !== undefined && !arranges && !empty && inside === ''
|
|
356
|
+
? `{shows(props, surface${keeps ? ', kept' : ''})}`
|
|
357
|
+
: inside === ''
|
|
358
|
+
? `{${arranges ? 'props.children' : names === undefined ? 'surface.text(props.labelKey)' : 'null'}}`
|
|
359
|
+
: arranges
|
|
360
|
+
? `{props.children}\n${inside}`
|
|
361
|
+
: undefined
|
|
362
|
+
|
|
363
|
+
const spoken = (one: string): string =>
|
|
364
|
+
SPEAKS.has(one) && names !== undefined ? `{surface.text(props.${names[0]})}` : (content ?? inside)
|
|
365
|
+
|
|
366
|
+
const written = (one: string): string =>
|
|
367
|
+
SHUT.has(one)
|
|
368
|
+
? `${opening(one)} />`
|
|
369
|
+
: `${opening(one)}>\n${SPEAKS.has(one) || content !== undefined ? ` ${spoken(one)}` : inside}\n </${one}>`
|
|
370
|
+
|
|
371
|
+
const shut =
|
|
372
|
+
bare && inside !== ''
|
|
373
|
+
? `<>\n${inside}\n </>`
|
|
374
|
+
: bare
|
|
375
|
+
? `shows(props, surface${keeps ? ', kept' : ''})`
|
|
376
|
+
: chosen === undefined
|
|
377
|
+
? written(first)
|
|
378
|
+
: `(() => {\n switch (props.${String(chosen['by'])}) {\n${Object.entries(chosen)
|
|
379
|
+
.filter(([key]) => key !== 'by')
|
|
380
|
+
.map(([key, one]) => ` case '${key}':\n return (\n ${written(one).split('\n').join('\n ')}\n )`)
|
|
381
|
+
.join('\n')}\n default:\n return null\n }\n })()`
|
|
382
|
+
|
|
383
|
+
const fromProps = measures.filter((one) => carries[one] !== undefined)
|
|
384
|
+
const computes = measures.filter((one) => carries[one] === undefined)
|
|
385
|
+
const passed = fromProps.map((one) => `${JSON.stringify(one)}: String(props.${one} ?? '')`).join(', ')
|
|
386
|
+
|
|
387
|
+
const taken =
|
|
388
|
+
measures.length === 0 || wires !== undefined
|
|
389
|
+
? ''
|
|
390
|
+
: computes.length === 0
|
|
391
|
+
? `\n measures: { ${passed} },`
|
|
392
|
+
: fromProps.length === 0
|
|
393
|
+
? `\n measures: measured(props${keeps ? ', kept' : ''}),`
|
|
394
|
+
: `\n measures: { ...measured(props${keeps ? ', kept' : ''}), ${passed} },`
|
|
395
|
+
|
|
396
|
+
const wanted = [...(arranges || empty || says !== undefined || inside !== '' ? [] : ['shows']), ...(worked ? ['shown'] : []), ...(computes.length === 0 || wires !== undefined ? [] : ['measured']), ...[...new Set(Object.values(places).flatMap((made) => [(made as Record<string, string> | undefined)?.['marks'], (made as Record<string, string> | undefined)?.['gives'], (made as Record<string, string> | undefined)?.['when'], ((made as Record<string, string> | undefined)?.['each'] !== undefined && carries[(made as Record<string, string>)['each'] ?? ''] === undefined) ? (made as Record<string, string>)['each'] : undefined]).filter((one): one is string => one !== undefined))], ...(answers ? ['does'] : []), ...(keeps ? ['uses'] : []), ...(wires === undefined ? [] : [wires])]
|
|
397
|
+
|
|
398
|
+
const held =
|
|
399
|
+
always.length === 0 && raises.length === 0 && !keeps
|
|
400
|
+
? ''
|
|
401
|
+
: `\n holds: [\n${[
|
|
402
|
+
...always.map((one) => ` '${one}' as const,`),
|
|
403
|
+
...raises.map((one) => ` ...(props.${one} === true ? ['${one}' as const] : []),`),
|
|
404
|
+
...(keeps ? [' ...(kept.holds ?? []),'] : []),
|
|
405
|
+
].join('\n')}\n ],${raises.length === 0 ? '' : `\n raises: [${raises.map((one) => `'${one}' as const`).join(', ')}],`}`
|
|
406
|
+
|
|
407
|
+
return `'use client'
|
|
408
|
+
|
|
409
|
+
${[
|
|
410
|
+
...(arranges || stated.some(([, kind]) => KINDS[kind]?.from === 'react' || KINDS[kind]?.from === 'both')
|
|
411
|
+
? ["import type { ReactNode } from 'react'"]
|
|
412
|
+
: []),
|
|
413
|
+
...((!arranges && !silent && !bare && called === undefined) || stated.some(([, kind]) => KINDS[kind]?.from === 'content')
|
|
414
|
+
? [`import type { MessageKey } from '${from.content}'`]
|
|
415
|
+
: []),
|
|
416
|
+
...(stated.some(([, kind]) => KINDS[kind]?.from === 'rows' || KINDS[kind]?.from === 'both') ? [`import type { Row } from '${from.rows}'`] : []),
|
|
417
|
+
...(roles !== undefined
|
|
418
|
+
? [`import { roleClass, type FontRoleId } from '${from.fonts}'`]
|
|
419
|
+
: arranges
|
|
420
|
+
? []
|
|
421
|
+
: [`import { roleClass } from '${from.fonts}'`]),
|
|
422
|
+
...(stated.some(([, kind]) => KINDS[kind]?.from === 'map') ? [`import type { MapIsland } from '${from.map}'`] : []),
|
|
423
|
+
...(stated.some(([, kind]) => KINDS[kind]?.from === 'parts') ? [`import type { TabItem } from '${from.parts}'`] : []),
|
|
424
|
+
...(stated.some(([, kind]) => KINDS[kind]?.from === 'consent') ? [`import type { ConsentDecision } from '${from.consent}'`] : []),
|
|
425
|
+
...(stated.some(([, kind]) => KINDS[kind]?.from === 'drag') ? [`import type { Subject } from '${from.drag}'`] : []),
|
|
426
|
+
...worn.map((one) => `import { ${one} } from './${idOf(one)}.tsx'`),
|
|
427
|
+
...(built.length === 0 || works === undefined ? [] : [`import { ${built.join(', ')} } from '${works}'`]),
|
|
428
|
+
...(Object.values(places).some((made) => (made as Record<string, string> | undefined)?.['marks'] !== undefined)
|
|
429
|
+
? [`import { measured as marking } from '${from.rows}'`]
|
|
430
|
+
: []),
|
|
431
|
+
].join('\n')}
|
|
432
|
+
|
|
433
|
+
import { type Element, element, useSurface } from '${from.wrappers}'
|
|
434
|
+
${Object.values(places).some((made) => ((made as Record<string, string> | undefined)?.['takes'] ?? '').split(/\s+/).includes('validator')) ? `import { limited } from '${from.validators}'\n` : ''}${repeats ? `import { Each } from '../elements/each.tsx'\n` : ''}${works === undefined || wanted.length === 0 ? '' : `import { ${wanted.join(', ')} } from '${works}'\n`}
|
|
435
|
+
|
|
436
|
+
export interface ${made}Props {
|
|
437
|
+
${[...props, ...invoking].join('\n')}
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
export const ${made}: Element<'${made}', ${made}Props> = element(
|
|
441
|
+
'${made}',
|
|
442
|
+
(props: ${made}Props, className) => {
|
|
443
|
+
${keeps ? ' const kept = uses(props)\n' : ''} const surface = useSurface({ className${roled},\n invokes: { ...(props.dispatch === undefined ? {} : { dispatch: props.dispatch }), ...(props.input === undefined ? {} : { input: props.input }), ...(props.invokeOn === undefined ? {} : { on: props.invokeOn }) },${held}${taken}${animates ? '\n animates: true,' : ''}${keeps ? "\n ...(kept.animates === undefined ? {} : { animates: kept.animates })," : ''} })${wires === undefined ? '' : `\n const wired = ${wires}(props${keeps ? ', kept' : ''}, surface)`}
|
|
444
|
+
return (
|
|
445
|
+
${shut}
|
|
446
|
+
)
|
|
447
|
+
},
|
|
448
|
+
'${classOfElement(name)}',
|
|
449
|
+
${JSON.stringify(dressing)},
|
|
450
|
+
)
|
|
451
|
+
`
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
const KINDS: Readonly<Record<string, { readonly is: string; readonly needed: boolean; readonly from?: 'react' | 'content' | 'rows' | 'source' | 'fonts' | 'map' | 'parts' | 'consent' | 'both' | 'drag' }>> = {
|
|
455
|
+
children: { is: 'ReactNode', needed: true, from: 'react' },
|
|
456
|
+
wording: { is: 'MessageKey', needed: true, from: 'content' },
|
|
457
|
+
naming: { is: 'MessageKey', needed: true, from: 'content' },
|
|
458
|
+
flag: { is: 'boolean', needed: false },
|
|
459
|
+
count: { is: 'number', needed: false },
|
|
460
|
+
words: { is: 'string', needed: false },
|
|
461
|
+
|
|
462
|
+
source: { is: 'string', needed: true },
|
|
463
|
+
|
|
464
|
+
amount: { is: 'unknown', needed: false },
|
|
465
|
+
|
|
466
|
+
holding: { is: 'Readonly<Record<string, unknown>>', needed: false },
|
|
467
|
+
|
|
468
|
+
tuning: { is: 'Readonly<Record<string, number>>', needed: false },
|
|
469
|
+
|
|
470
|
+
mapping: { is: 'MapIsland', needed: true, from: 'map' },
|
|
471
|
+
|
|
472
|
+
parting: { is: 'readonly TabItem[]', needed: true, from: 'parts' },
|
|
473
|
+
|
|
474
|
+
judging: { is: 'ConsentDecision', needed: false, from: 'consent' },
|
|
475
|
+
judged: { is: '(said: ConsentDecision) => void', needed: true, from: 'consent' },
|
|
476
|
+
|
|
477
|
+
ruling: { is: 'string', needed: true },
|
|
478
|
+
|
|
479
|
+
subjecting: { is: 'Subject | readonly Subject[]', needed: false, from: 'drag' },
|
|
480
|
+
|
|
481
|
+
announcing: { is: "'alert' | 'status'", needed: false },
|
|
482
|
+
|
|
483
|
+
noting: { is: 'MessageKey', needed: false, from: 'content' },
|
|
484
|
+
|
|
485
|
+
titling: { is: 'MessageKey', needed: false, from: 'content' },
|
|
486
|
+
|
|
487
|
+
picture: { is: 'string', needed: false, from: 'source' },
|
|
488
|
+
|
|
489
|
+
styling: { is: 'FontRoleId', needed: false, from: 'fonts' },
|
|
490
|
+
|
|
491
|
+
telling: { is: '(said: boolean) => void', needed: false },
|
|
492
|
+
|
|
493
|
+
doing: { is: '() => void', needed: false },
|
|
494
|
+
|
|
495
|
+
picking: { is: '(said: string) => void', needed: false },
|
|
496
|
+
|
|
497
|
+
moving: { is: '(said: number) => void', needed: false },
|
|
498
|
+
|
|
499
|
+
choosing: { is: '(said: readonly string[]) => void', needed: false },
|
|
500
|
+
|
|
501
|
+
listing: { is: 'readonly Row[]', needed: true, from: 'rows' },
|
|
502
|
+
|
|
503
|
+
ordering: { is: 'readonly string[]', needed: true },
|
|
504
|
+
|
|
505
|
+
anything: { is: 'readonly object[]', needed: true },
|
|
506
|
+
|
|
507
|
+
drawing: { is: '(row: Row, at: number) => ReactNode', needed: true, from: 'both' },
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
const SOURCES = (kind: string): boolean => kind === 'source'
|
|
511
|
+
|
|
512
|
+
const NAMES = (kind: string): boolean => kind === 'naming' || kind === 'titling'
|
|
513
|
+
|
|
514
|
+
const SHUT = new Set(['img', 'hr', 'input', 'br', 'area', 'col', 'embed', 'source', 'track', 'wbr'])
|
|
515
|
+
|
|
516
|
+
const SOURCE: Readonly<Record<string, string>> = { a: 'href' }
|
|
517
|
+
|
|
518
|
+
const EXTRA: Readonly<Record<string, string>> = {
|
|
519
|
+
video: ' controls preload="metadata"',
|
|
520
|
+
audio: ' controls preload="metadata"',
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
const FETCHES: Readonly<Record<string, string>> = { a: ' download' }
|
|
524
|
+
|
|
525
|
+
const SPEAKS = new Set(['a'])
|
|
526
|
+
|
|
527
|
+
const IDENTIFIES: Readonly<Record<string, string>> = { option: 'value' }
|
|
528
|
+
|
|
529
|
+
const GIVES: Readonly<Record<string, string>> = {
|
|
530
|
+
name: ' name={props.name}',
|
|
531
|
+
rows: ' rows={props.rows ?? 4}',
|
|
532
|
+
validator: ' {...limited(props.validator)}',
|
|
533
|
+
min: ' min={props.min}',
|
|
534
|
+
max: ' max={props.max}',
|
|
535
|
+
step: ' step={props.step ?? 1}',
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
interface Reading {
|
|
539
|
+
readonly role?: string
|
|
540
|
+
readonly reports?: string
|
|
541
|
+
|
|
542
|
+
readonly control?: string
|
|
543
|
+
readonly state?: string
|
|
544
|
+
readonly answers?: string
|
|
545
|
+
|
|
546
|
+
readonly hidden?: boolean
|
|
547
|
+
|
|
548
|
+
readonly needs?: string
|
|
549
|
+
|
|
550
|
+
readonly orients?: string
|
|
551
|
+
|
|
552
|
+
readonly binds?: string
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
const READS: Readonly<Record<string, Reading>> = {
|
|
556
|
+
|
|
557
|
+
choice: {
|
|
558
|
+
control: ' type="checkbox" disabled={surface.marked(\'disabled\')}',
|
|
559
|
+
state: 'checked',
|
|
560
|
+
|
|
561
|
+
answers: ' onChange={(event) => does(props).changed(event.target.checked)}',
|
|
562
|
+
},
|
|
563
|
+
|
|
564
|
+
writing: {
|
|
565
|
+
control: " disabled={surface.marked('disabled')}",
|
|
566
|
+
state: 'value',
|
|
567
|
+
answers: ' onChange={(event) => does(props).wrote(event.target.value)}',
|
|
568
|
+
},
|
|
569
|
+
|
|
570
|
+
sliding: {
|
|
571
|
+
control: " type=\"range\" disabled={surface.marked('disabled')}",
|
|
572
|
+
state: 'value',
|
|
573
|
+
binds: 'value',
|
|
574
|
+
answers: ' onChange={(event) => does(props).moved(Number(event.target.value))}',
|
|
575
|
+
},
|
|
576
|
+
|
|
577
|
+
choosing: {
|
|
578
|
+
control: " type=\"radio\" disabled={surface.marked('disabled')}",
|
|
579
|
+
},
|
|
580
|
+
|
|
581
|
+
choices: { role: 'radiogroup', needs: 'labelKey' },
|
|
582
|
+
|
|
583
|
+
chooser: {
|
|
584
|
+
control: " disabled={surface.marked('disabled')}",
|
|
585
|
+
state: 'value',
|
|
586
|
+
answers: ' onChange={(event) => does(props).chose(event.target.value)}',
|
|
587
|
+
},
|
|
588
|
+
|
|
589
|
+
progress: { role: 'progressbar', reports: 'fraction' },
|
|
590
|
+
picture: { role: 'img' },
|
|
591
|
+
divider: { role: 'separator', orients: 'orientation' },
|
|
592
|
+
|
|
593
|
+
grouping: { role: 'group', needs: 'labelKey' },
|
|
594
|
+
|
|
595
|
+
placeholder: { hidden: true },
|
|
596
|
+
notice: { role: 'status' },
|
|
597
|
+
alarm: { role: 'alert' },
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
export const CARRIES: readonly string[] = Object.keys(KINDS)
|
|
601
|
+
|
|
602
|
+
export const READINGS: readonly string[] = Object.keys(READS)
|
|
603
|
+
|
|
604
|
+
const BESIDE = { wrappers: '../wrappers.tsx', content: '../content.tsx', rows: '../charts.tsx', validators: '../validators.ts', fonts: '../fonts.ts', map: '../islands/map.ts', parts: '../disclosure.tsx', consent: '../consent.ts', drag: '../drag.ts' } as const
|
|
605
|
+
|
|
606
|
+
interface Row {
|
|
607
|
+
readonly id: string
|
|
608
|
+
readonly at?: string
|
|
609
|
+
readonly by?: string
|
|
610
|
+
readonly tag?: string | Readonly<Record<string, string>>
|
|
611
|
+
readonly arranges?: boolean
|
|
612
|
+
readonly carries?: Readonly<Record<string, string>>
|
|
613
|
+
readonly always?: readonly string[]
|
|
614
|
+
readonly places?: Readonly<Record<string, Readonly<Record<string, string>>>>
|
|
615
|
+
readonly works?: string
|
|
616
|
+
readonly measures?: readonly string[]
|
|
617
|
+
readonly reads?: string
|
|
618
|
+
readonly gives?: readonly string[]
|
|
619
|
+
readonly keeps?: boolean
|
|
620
|
+
readonly animates?: boolean
|
|
621
|
+
readonly shows?: string
|
|
622
|
+
readonly called?: string
|
|
623
|
+
readonly wires?: string
|
|
624
|
+
readonly dresses?: string
|
|
625
|
+
readonly raises?: readonly string[]
|
|
626
|
+
readonly derives?: readonly { readonly at?: string; readonly by?: string }[]
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
export const declared = new Set<string>()
|
|
630
|
+
|
|
631
|
+
export const wears = new Map<string, Readonly<Record<string, string>>>()
|
|
632
|
+
|
|
633
|
+
const SHEET = `elements.${'ts'}`
|
|
634
|
+
|
|
635
|
+
const BY = 'thread elements'
|
|
636
|
+
|
|
637
|
+
const INSTALLED = 'node_' + 'modules/'
|
|
638
|
+
|
|
639
|
+
function derived(row: Row, own: boolean): boolean {
|
|
640
|
+
if (typeof row.at !== 'string' || row.at.startsWith(INSTALLED)) return false
|
|
641
|
+
if ((row.derives ?? []).some((one) => one.by === BY && one.at === row.at)) return true
|
|
642
|
+
return own ? row.by === BY : true
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
const VARIANTS_SHEET = `variants.${'ts'}`
|
|
646
|
+
|
|
647
|
+
async function variants(own: boolean): Promise<readonly { readonly id: string; readonly wears: readonly string[] }[]> {
|
|
648
|
+
if (own) {
|
|
649
|
+
const held = (await import(pathToFileURL(resolve(fileURLToPath(import.meta.url), '../..', VARIANTS_SHEET)).href)) as {
|
|
650
|
+
VARIANTS?: { readonly declares?: readonly { readonly id: string; readonly wears: readonly string[] }[] }
|
|
651
|
+
}
|
|
652
|
+
return held.VARIANTS?.declares ?? []
|
|
653
|
+
}
|
|
654
|
+
const at = inTree(findTree(), VARIANTS_SHEET)
|
|
655
|
+
if (!existsSync(at)) return []
|
|
656
|
+
const held = (await import(pathToFileURL(at).href)) as {
|
|
657
|
+
VARIANT?: { readonly declares?: readonly { readonly id: string; readonly wears: readonly string[] }[] }
|
|
658
|
+
}
|
|
659
|
+
return held.VARIANT?.declares ?? []
|
|
660
|
+
}
|
|
661
|
+
|
|
662
|
+
async function run(): Promise<number> {
|
|
663
|
+
const own = argv.includes('--own')
|
|
664
|
+
|
|
665
|
+
const held = own
|
|
666
|
+
? ((await import(pathToFileURL(resolve(fileURLToPath(import.meta.url), '../../elements.ts')).href)) as {
|
|
667
|
+
ELEMENTS?: { readonly declares?: readonly Row[] }
|
|
668
|
+
})
|
|
669
|
+
: undefined
|
|
670
|
+
|
|
671
|
+
const tree = own ? undefined : findTree()
|
|
672
|
+
|
|
673
|
+
if (tree !== undefined && !existsSync(inTree(tree, SHEET))) {
|
|
674
|
+
stdout.write(
|
|
675
|
+
`thread: this tree has no ${tree.name}/${SHEET}, so it declares no element and there is nothing to write or check.\n`,
|
|
676
|
+
)
|
|
677
|
+
return 0
|
|
678
|
+
}
|
|
679
|
+
|
|
680
|
+
const sheet =
|
|
681
|
+
tree === undefined
|
|
682
|
+
? { ELEMENT: held?.ELEMENTS }
|
|
683
|
+
: ((await import(pathToFileURL(inTree(tree, SHEET)).href)) as {
|
|
684
|
+
ELEMENT?: { readonly declares?: readonly Row[] }
|
|
685
|
+
})
|
|
686
|
+
|
|
687
|
+
if (sheet.ELEMENT === undefined) {
|
|
688
|
+
stdout.write(
|
|
689
|
+
own
|
|
690
|
+
? 'elements: thread/ui/elements.ts exports no `ELEMENTS`.\n'
|
|
691
|
+
: `thread: ${String(tree?.name)}/${SHEET} exports no \`ELEMENT\`, so nothing declares an element here.\n`,
|
|
692
|
+
)
|
|
693
|
+
return 1
|
|
694
|
+
}
|
|
695
|
+
|
|
696
|
+
const root = findTree().at
|
|
697
|
+
const from = BESIDE
|
|
698
|
+
|
|
699
|
+
for (const one of sheet.ELEMENT.declares ?? []) declared.add(one.id)
|
|
700
|
+
|
|
701
|
+
const dressed = await variants(own)
|
|
702
|
+
for (const one of sheet.ELEMENT.declares ?? []) {
|
|
703
|
+
const held: Record<string, string> = {}
|
|
704
|
+
for (const row of dressed) {
|
|
705
|
+
if (!row.wears.includes(pascal(one.id))) continue
|
|
706
|
+
held[camel(row.id)] = classOf(row.id)
|
|
707
|
+
}
|
|
708
|
+
wears.set(one.id, held)
|
|
709
|
+
}
|
|
710
|
+
|
|
711
|
+
const rows = (sheet.ELEMENT.declares ?? []).filter((row) => derived(row, own))
|
|
712
|
+
const checking = argv.includes('--check')
|
|
713
|
+
|
|
714
|
+
for (const row of rows) {
|
|
715
|
+
if (row.at === undefined) {
|
|
716
|
+
stdout.write(`elements: \`${row.id}\` derives a file and names none, so nothing can be written for it.\n`)
|
|
717
|
+
return 1
|
|
718
|
+
}
|
|
719
|
+
if (row.tag === undefined && row.works !== undefined) {
|
|
720
|
+
const made = translated(row.id, '', row.arranges === true, row.raises ?? [], from, row.carries, row.always, row.places, row.works, row.measures, row.reads, row.gives, row.keeps === true, row.animates === true, row.shows, row.called, row.wires, row.dresses)
|
|
721
|
+
const at = join(root, row.at)
|
|
722
|
+
if (!checking) {
|
|
723
|
+
writeFileSync(at, made, 'utf8')
|
|
724
|
+
stdout.write(`elements: wrote ${row.at}\n`)
|
|
725
|
+
continue
|
|
726
|
+
}
|
|
727
|
+
if (!existsSync(at) || readFileSync(at, 'utf8') !== made) {
|
|
728
|
+
stdout.write(
|
|
729
|
+
`elements: ${row.at} is not what the row produces. A derived file that differs from its ` +
|
|
730
|
+
'generator is a hand edit wearing a generated name. Run `thread elements` and commit what it writes.\n',
|
|
731
|
+
)
|
|
732
|
+
return 1
|
|
733
|
+
}
|
|
734
|
+
continue
|
|
735
|
+
}
|
|
736
|
+
if (row.tag === undefined) {
|
|
737
|
+
stdout.write(
|
|
738
|
+
`elements: \`${row.id}\` is written by \`${BY}\` and states no tag, and a tag is what this ` +
|
|
739
|
+
'writes the element out of. State one on the row, or take the `derives` off and keep the file.\n',
|
|
740
|
+
)
|
|
741
|
+
return 1
|
|
742
|
+
}
|
|
743
|
+
|
|
744
|
+
if (row.reads !== undefined && READS[row.reads] === undefined) {
|
|
745
|
+
stdout.write(
|
|
746
|
+
`elements: \`${row.id}\` says it reads as \`${String(row.reads)}\`, and that is not something an ` +
|
|
747
|
+
`element reads as. It reads as one of: ${Object.keys(READS).join(' · ')}.\n`,
|
|
748
|
+
)
|
|
749
|
+
return 1
|
|
750
|
+
}
|
|
751
|
+
|
|
752
|
+
const unknown = Object.entries(row.carries ?? {}).find(([, kind]) => KINDS[kind] === undefined)
|
|
753
|
+
if (unknown !== undefined) {
|
|
754
|
+
stdout.write(
|
|
755
|
+
`elements: \`${row.id}\` says it carries \`${unknown[0]}\` as a \`${unknown[1]}\`, and that is ` +
|
|
756
|
+
`not a kind an element carries. It carries one of: ${Object.keys(KINDS).join(' · ')}.\n`,
|
|
757
|
+
)
|
|
758
|
+
return 1
|
|
759
|
+
}
|
|
760
|
+
|
|
761
|
+
const text = translated(row.id, row.tag, row.arranges === true, row.raises ?? [], from, row.carries, row.always, row.places, row.works, row.measures, row.reads, row.gives, row.keeps === true, row.animates === true, row.shows, row.called, row.wires, row.dresses)
|
|
762
|
+
const where = join(root, row.at)
|
|
763
|
+
|
|
764
|
+
if (!checking) {
|
|
765
|
+
writeFileSync(where, text, 'utf8')
|
|
766
|
+
stdout.write(`elements: wrote ${row.at}\n`)
|
|
767
|
+
continue
|
|
768
|
+
}
|
|
769
|
+
|
|
770
|
+
if (!existsSync(where)) {
|
|
771
|
+
stdout.write(
|
|
772
|
+
`elements: \`${row.id}\` is declared at \`${row.at}\`, and the file is not there. ` +
|
|
773
|
+
'A row names a file and `--check` never writes one — run `thread elements` and commit what it writes.\n',
|
|
774
|
+
)
|
|
775
|
+
return 1
|
|
776
|
+
}
|
|
777
|
+
if (readFileSync(where, 'utf8') !== text) {
|
|
778
|
+
stdout.write(
|
|
779
|
+
`elements: ${row.at} is not what the row produces. A derived file that differs from its ` +
|
|
780
|
+
'generator is a hand edit wearing a generated name. Run `thread elements` and commit what it writes.\n',
|
|
781
|
+
)
|
|
782
|
+
return 1
|
|
783
|
+
}
|
|
784
|
+
}
|
|
785
|
+
|
|
786
|
+
stdout.write(
|
|
787
|
+
checking
|
|
788
|
+
? `thread: ${String(rows.length)} element(s) written from their rows, all agreeing\n`
|
|
789
|
+
: `thread: wrote ${String(rows.length)} element(s) from their rows\n`,
|
|
790
|
+
)
|
|
791
|
+
return 0
|
|
792
|
+
}
|
|
793
|
+
|
|
794
|
+
if (invoked(import.meta.url)) exit(await run())
|