@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.
Files changed (269) hide show
  1. package/dist/ui/animations/anm_appear.ts +13 -0
  2. package/dist/ui/animations/anm_blur_in.ts +19 -0
  3. package/dist/ui/animations/anm_blur_out.ts +18 -0
  4. package/dist/ui/animations/anm_dismiss.ts +18 -0
  5. package/dist/ui/animations/anm_drop.ts +19 -0
  6. package/dist/ui/animations/anm_fall.ts +18 -0
  7. package/dist/ui/animations/anm_flash.ts +18 -0
  8. package/dist/ui/animations/anm_glow.ts +16 -0
  9. package/dist/ui/animations/anm_lift.ts +16 -0
  10. package/dist/ui/animations/anm_nudge.ts +16 -0
  11. package/dist/ui/animations/anm_pop_in.ts +23 -0
  12. package/dist/ui/animations/anm_pop_out.ts +22 -0
  13. package/dist/ui/animations/anm_press.ts +12 -0
  14. package/dist/ui/animations/anm_pulse.ts +13 -0
  15. package/dist/ui/animations/anm_rise.ts +19 -0
  16. package/dist/ui/animations/anm_shake.ts +13 -0
  17. package/dist/ui/animations/anm_slide_in_left.ts +19 -0
  18. package/dist/ui/animations/anm_slide_in_right.ts +19 -0
  19. package/dist/ui/animations/anm_slide_out_left.ts +18 -0
  20. package/dist/ui/animations/anm_slide_out_right.ts +18 -0
  21. package/dist/ui/animations/anm_spin.ts +12 -0
  22. package/dist/ui/animations/anm_swell.ts +16 -0
  23. package/dist/ui/animations/anm_wobble.ts +24 -0
  24. package/dist/ui/animations/anm_zoom_in.ts +19 -0
  25. package/dist/ui/animations/anm_zoom_out.ts +18 -0
  26. package/dist/ui/animations.ts +317 -0
  27. package/dist/ui/attach.js +13 -4
  28. package/dist/ui/attach.ts +613 -0
  29. package/dist/ui/charts.tsx +74 -0
  30. package/dist/ui/checked.d.ts +2 -2
  31. package/dist/ui/checked.ts +32 -0
  32. package/dist/ui/collector.ts +140 -0
  33. package/dist/ui/compositions.d.ts +10 -1
  34. package/dist/ui/compositions.js +262 -23
  35. package/dist/ui/compositions.ts +640 -0
  36. package/dist/ui/consent.ts +55 -0
  37. package/dist/ui/content.tsx +39 -0
  38. package/dist/ui/contrast.ts +27 -0
  39. package/dist/ui/controls.tsx +30 -0
  40. package/dist/ui/disclosure.tsx +22 -0
  41. package/dist/ui/dismiss-layer.ts +87 -0
  42. package/dist/ui/door.ts +140 -0
  43. package/dist/ui/drag.ts +316 -0
  44. package/dist/ui/elements/accordion.js +2 -2
  45. package/dist/ui/elements/accordion.tsx +47 -0
  46. package/dist/ui/elements/accordion.works.ts +66 -0
  47. package/dist/ui/elements/aspect-ratio.js +1 -1
  48. package/dist/ui/elements/aspect-ratio.tsx +30 -0
  49. package/dist/ui/elements/aspect-ratio.works.ts +5 -0
  50. package/dist/ui/elements/avatar.js +1 -1
  51. package/dist/ui/elements/avatar.tsx +33 -0
  52. package/dist/ui/elements/avatar.works.ts +16 -0
  53. package/dist/ui/elements/badge.js +1 -1
  54. package/dist/ui/elements/badge.tsx +32 -0
  55. package/dist/ui/elements/badge.works.ts +9 -0
  56. package/dist/ui/elements/bars.js +2 -2
  57. package/dist/ui/elements/bars.tsx +49 -0
  58. package/dist/ui/elements/bars.works.ts +29 -0
  59. package/dist/ui/elements/box.js +3 -2
  60. package/dist/ui/elements/box.tsx +35 -0
  61. package/dist/ui/elements/breadcrumb.js +2 -2
  62. package/dist/ui/elements/breadcrumb.tsx +37 -0
  63. package/dist/ui/elements/breadcrumb.works.ts +24 -0
  64. package/dist/ui/elements/button.js +1 -1
  65. package/dist/ui/elements/button.tsx +45 -0
  66. package/dist/ui/elements/button.works.tsx +52 -0
  67. package/dist/ui/elements/column.js +1 -1
  68. package/dist/ui/elements/column.tsx +28 -0
  69. package/dist/ui/elements/consent.js +2 -2
  70. package/dist/ui/elements/consent.tsx +70 -0
  71. package/dist/ui/elements/consent.works.tsx +132 -0
  72. package/dist/ui/elements/donut.js +2 -2
  73. package/dist/ui/elements/donut.tsx +44 -0
  74. package/dist/ui/elements/donut.works.ts +42 -0
  75. package/dist/ui/elements/each.js +1 -1
  76. package/dist/ui/elements/each.tsx +33 -0
  77. package/dist/ui/elements/each.works.tsx +82 -0
  78. package/dist/ui/elements/empty.js +1 -1
  79. package/dist/ui/elements/empty.tsx +32 -0
  80. package/dist/ui/elements/field.js +1 -1
  81. package/dist/ui/elements/field.tsx +44 -0
  82. package/dist/ui/elements/field.works.ts +65 -0
  83. package/dist/ui/elements/funnel.js +2 -2
  84. package/dist/ui/elements/funnel.tsx +49 -0
  85. package/dist/ui/elements/funnel.works.ts +37 -0
  86. package/dist/ui/elements/gauge.js +1 -1
  87. package/dist/ui/elements/gauge.tsx +39 -0
  88. package/dist/ui/elements/gauge.works.ts +42 -0
  89. package/dist/ui/elements/glyph.js +1 -1
  90. package/dist/ui/elements/glyph.tsx +29 -0
  91. package/dist/ui/elements/glyph.works.tsx +42 -0
  92. package/dist/ui/elements/grid.js +1 -1
  93. package/dist/ui/elements/grid.tsx +28 -0
  94. package/dist/ui/elements/heatmap.js +2 -2
  95. package/dist/ui/elements/heatmap.tsx +47 -0
  96. package/dist/ui/elements/heatmap.works.ts +24 -0
  97. package/dist/ui/elements/hit-box.js +1 -1
  98. package/dist/ui/elements/hit-box.tsx +46 -0
  99. package/dist/ui/elements/hit-box.works.tsx +576 -0
  100. package/dist/ui/elements/image.js +1 -1
  101. package/dist/ui/elements/image.tsx +28 -0
  102. package/dist/ui/elements/line-chart.js +2 -2
  103. package/dist/ui/elements/line-chart.tsx +44 -0
  104. package/dist/ui/elements/line-chart.works.ts +41 -0
  105. package/dist/ui/elements/link.js +1 -1
  106. package/dist/ui/elements/link.tsx +37 -0
  107. package/dist/ui/elements/link.works.ts +25 -0
  108. package/dist/ui/elements/media.js +1 -1
  109. package/dist/ui/elements/media.tsx +56 -0
  110. package/dist/ui/elements/menu.js +2 -2
  111. package/dist/ui/elements/menu.tsx +50 -0
  112. package/dist/ui/elements/menu.works.tsx +153 -0
  113. package/dist/ui/elements/money.js +1 -1
  114. package/dist/ui/elements/money.tsx +28 -0
  115. package/dist/ui/elements/money.works.ts +9 -0
  116. package/dist/ui/elements/pagination.js +2 -2
  117. package/dist/ui/elements/pagination.tsx +38 -0
  118. package/dist/ui/elements/pagination.works.ts +20 -0
  119. package/dist/ui/elements/progress.js +1 -1
  120. package/dist/ui/elements/progress.tsx +33 -0
  121. package/dist/ui/elements/progress.works.ts +8 -0
  122. package/dist/ui/elements/radio-group.js +2 -2
  123. package/dist/ui/elements/radio-group.tsx +49 -0
  124. package/dist/ui/elements/radio-group.works.ts +33 -0
  125. package/dist/ui/elements/row.js +1 -1
  126. package/dist/ui/elements/row.tsx +28 -0
  127. package/dist/ui/elements/scatter.js +1 -1
  128. package/dist/ui/elements/scatter.tsx +41 -0
  129. package/dist/ui/elements/scatter.works.ts +38 -0
  130. package/dist/ui/elements/select.js +2 -2
  131. package/dist/ui/elements/select.tsx +42 -0
  132. package/dist/ui/elements/select.works.ts +5 -0
  133. package/dist/ui/elements/separator.js +1 -1
  134. package/dist/ui/elements/separator.tsx +30 -0
  135. package/dist/ui/elements/skeleton.js +1 -1
  136. package/dist/ui/elements/skeleton.tsx +30 -0
  137. package/dist/ui/elements/slider.js +1 -1
  138. package/dist/ui/elements/slider.tsx +33 -0
  139. package/dist/ui/elements/slider.works.ts +5 -0
  140. package/dist/ui/elements/sparkline.js +1 -1
  141. package/dist/ui/elements/sparkline.tsx +36 -0
  142. package/dist/ui/elements/sparkline.works.ts +32 -0
  143. package/dist/ui/elements/spinner.js +1 -1
  144. package/dist/ui/elements/spinner.tsx +32 -0
  145. package/dist/ui/elements/stack.js +1 -1
  146. package/dist/ui/elements/stack.tsx +28 -0
  147. package/dist/ui/elements/stat.js +1 -1
  148. package/dist/ui/elements/stat.tsx +35 -0
  149. package/dist/ui/elements/stat.works.ts +14 -0
  150. package/dist/ui/elements/surface.js +1 -1
  151. package/dist/ui/elements/surface.tsx +35 -0
  152. package/dist/ui/elements/surface.works.ts +9 -0
  153. package/dist/ui/elements/table.js +2 -2
  154. package/dist/ui/elements/table.tsx +61 -0
  155. package/dist/ui/elements/table.works.ts +30 -0
  156. package/dist/ui/elements/tabs.js +2 -2
  157. package/dist/ui/elements/tabs.tsx +47 -0
  158. package/dist/ui/elements/tabs.works.ts +84 -0
  159. package/dist/ui/elements/text.js +1 -1
  160. package/dist/ui/elements/text.tsx +32 -0
  161. package/dist/ui/elements/textarea.js +1 -1
  162. package/dist/ui/elements/textarea.tsx +37 -0
  163. package/dist/ui/elements/textarea.works.ts +5 -0
  164. package/dist/ui/elements/time.js +1 -1
  165. package/dist/ui/elements/time.tsx +34 -0
  166. package/dist/ui/elements/time.works.ts +25 -0
  167. package/dist/ui/elements/toggle.js +1 -1
  168. package/dist/ui/elements/toggle.tsx +33 -0
  169. package/dist/ui/elements/toggle.works.ts +5 -0
  170. package/dist/ui/elements/tooltip.js +1 -1
  171. package/dist/ui/elements/tooltip.tsx +39 -0
  172. package/dist/ui/elements/tooltip.works.ts +77 -0
  173. package/dist/ui/elements/value.js +1 -1
  174. package/dist/ui/elements/value.tsx +30 -0
  175. package/dist/ui/elements/value.works.ts +9 -0
  176. package/dist/ui/elements.ts +127 -0
  177. package/dist/ui/focus-scope.ts +121 -0
  178. package/dist/ui/fonts.ts +355 -0
  179. package/dist/ui/generators/dev.ts +79 -0
  180. package/dist/ui/generators/elements.d.ts +1 -11
  181. package/dist/ui/generators/elements.js +43 -13
  182. package/dist/ui/generators/elements.ts +794 -0
  183. package/dist/ui/generators/i18n.ts +301 -0
  184. package/dist/ui/generators/icons.ts +214 -0
  185. package/dist/ui/generators/react.js +10 -3
  186. package/dist/ui/generators/react.ts +268 -0
  187. package/dist/ui/generators/routes.d.ts +1 -0
  188. package/dist/ui/generators/routes.js +89 -69
  189. package/dist/ui/generators/routes.ts +739 -0
  190. package/dist/ui/generators/show.ts +259 -0
  191. package/dist/ui/generators/styles.js +1 -1
  192. package/dist/ui/generators/styles.ts +670 -0
  193. package/dist/ui/generators/variants.ts +205 -0
  194. package/dist/ui/i18n/catalogs/en.ts +191 -0
  195. package/dist/ui/i18n/format.ts +221 -0
  196. package/dist/ui/i18n/locales.ts +33 -0
  197. package/dist/ui/i18n/parse.ts +315 -0
  198. package/dist/ui/i18n/translator.ts +31 -0
  199. package/dist/ui/i18n/types.ts +96 -0
  200. package/dist/ui/icons.ts +331 -0
  201. package/dist/ui/islands/map.ts +58 -0
  202. package/dist/ui/localizations.ts +75 -0
  203. package/dist/ui/makers/message.ts +292 -0
  204. package/dist/ui/material.ts +236 -0
  205. package/dist/ui/materials/mat_bordered.ts +13 -0
  206. package/dist/ui/materials/mat_ceramic.ts +13 -0
  207. package/dist/ui/materials/mat_clay.ts +13 -0
  208. package/dist/ui/materials/mat_elevated.ts +24 -0
  209. package/dist/ui/materials/mat_flat.ts +34 -0
  210. package/dist/ui/materials/mat_glass.ts +20 -0
  211. package/dist/ui/materials/mat_lit.ts +13 -0
  212. package/dist/ui/materials/mat_moulded.ts +11 -0
  213. package/dist/ui/materials.ts +206 -0
  214. package/dist/ui/modes.ts +44 -0
  215. package/dist/ui/navigation.tsx +32 -0
  216. package/dist/ui/positioning.ts +90 -0
  217. package/dist/ui/presented.tsx +119 -0
  218. package/dist/ui/provider.d.ts +2 -0
  219. package/dist/ui/provider.js +7 -1
  220. package/dist/ui/provider.tsx +182 -0
  221. package/dist/ui/render-loop.ts +133 -0
  222. package/dist/ui/roving-focus.ts +161 -0
  223. package/dist/ui/screens.js +17 -8
  224. package/dist/ui/screens.ts +142 -0
  225. package/dist/ui/scroll-lock.ts +28 -0
  226. package/dist/ui/series.ts +97 -0
  227. package/dist/ui/signals.ts +386 -0
  228. package/dist/ui/styleguide.ts +231 -0
  229. package/dist/ui/styleguides.ts +374 -0
  230. package/dist/ui/themes.ts +22 -0
  231. package/dist/ui/token.js +1 -1
  232. package/dist/ui/token.ts +78 -0
  233. package/dist/ui/tokens/shadow.ts +25 -0
  234. package/dist/ui/tokens.ts +235 -0
  235. package/dist/ui/validators.ts +232 -0
  236. package/dist/ui/variant.d.ts +1 -0
  237. package/dist/ui/variant.js +3 -0
  238. package/dist/ui/variant.ts +352 -0
  239. package/dist/ui/variants/var_avatar-round.ts +14 -0
  240. package/dist/ui/variants/var_bubble-me.ts +14 -0
  241. package/dist/ui/variants/var_bubble-them.ts +13 -0
  242. package/dist/ui/variants/var_button-border.ts +76 -0
  243. package/dist/ui/variants/var_button-cta.ts +76 -0
  244. package/dist/ui/variants/var_button-destructive-ghost.ts +72 -0
  245. package/dist/ui/variants/var_button-destructive.ts +76 -0
  246. package/dist/ui/variants/var_button-fill.ts +76 -0
  247. package/dist/ui/variants/var_button-ghost.ts +72 -0
  248. package/dist/ui/variants/var_button-glass.ts +55 -0
  249. package/dist/ui/variants/var_button-normal.ts +76 -0
  250. package/dist/ui/variants/var_button-quiet.ts +74 -0
  251. package/dist/ui/variants/var_card.ts +17 -0
  252. package/dist/ui/variants/var_chip.ts +13 -0
  253. package/dist/ui/variants/var_choice.ts +15 -0
  254. package/dist/ui/variants/var_framed.ts +10 -0
  255. package/dist/ui/variants/var_glyph-plain.ts +11 -0
  256. package/dist/ui/variants/var_input.ts +43 -0
  257. package/dist/ui/variants/var_link-inline.ts +29 -0
  258. package/dist/ui/variants/var_list-row.ts +34 -0
  259. package/dist/ui/variants/var_rule.ts +10 -0
  260. package/dist/ui/variants/var_skeleton-block.ts +12 -0
  261. package/dist/ui/variants/var_spinner-turning.ts +12 -0
  262. package/dist/ui/variants/var_stat-tile.ts +13 -0
  263. package/dist/ui/variants/var_tab-bar.ts +15 -0
  264. package/dist/ui/variants/var_tap-target.ts +25 -0
  265. package/dist/ui/variants.ts +64 -0
  266. package/dist/ui/wrappers.d.ts +4 -1
  267. package/dist/ui/wrappers.js +60 -20
  268. package/dist/ui/wrappers.tsx +426 -0
  269. 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())