@prestack/thread-ui 0.1.9 → 0.1.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +366 -0
- package/dist/ui/attach.js +6 -7
- package/dist/ui/compositions.d.ts +0 -2
- package/dist/ui/compositions.js +2 -112
- package/dist/ui/generators/routes.js +1 -0
- package/dist/ui/variant.d.ts +1 -3
- package/package.json +2 -2
- package/dist/ui/animations/anm_appear.ts +0 -13
- package/dist/ui/animations/anm_blur_in.ts +0 -19
- package/dist/ui/animations/anm_blur_out.ts +0 -18
- package/dist/ui/animations/anm_dismiss.ts +0 -18
- package/dist/ui/animations/anm_drop.ts +0 -19
- package/dist/ui/animations/anm_fall.ts +0 -18
- package/dist/ui/animations/anm_flash.ts +0 -18
- package/dist/ui/animations/anm_glow.ts +0 -16
- package/dist/ui/animations/anm_lift.ts +0 -16
- package/dist/ui/animations/anm_nudge.ts +0 -16
- package/dist/ui/animations/anm_pop_in.ts +0 -23
- package/dist/ui/animations/anm_pop_out.ts +0 -22
- package/dist/ui/animations/anm_press.ts +0 -12
- package/dist/ui/animations/anm_pulse.ts +0 -13
- package/dist/ui/animations/anm_rise.ts +0 -19
- package/dist/ui/animations/anm_shake.ts +0 -13
- package/dist/ui/animations/anm_slide_in_left.ts +0 -19
- package/dist/ui/animations/anm_slide_in_right.ts +0 -19
- package/dist/ui/animations/anm_slide_out_left.ts +0 -18
- package/dist/ui/animations/anm_slide_out_right.ts +0 -18
- package/dist/ui/animations/anm_spin.ts +0 -12
- package/dist/ui/animations/anm_swell.ts +0 -16
- package/dist/ui/animations/anm_wobble.ts +0 -24
- package/dist/ui/animations/anm_zoom_in.ts +0 -19
- package/dist/ui/animations/anm_zoom_out.ts +0 -18
- package/dist/ui/animations.ts +0 -308
- package/dist/ui/attach.ts +0 -553
- package/dist/ui/charts.tsx +0 -69
- package/dist/ui/checked.ts +0 -32
- package/dist/ui/collector.ts +0 -140
- package/dist/ui/compositions.ts +0 -555
- package/dist/ui/consent.ts +0 -55
- package/dist/ui/content.tsx +0 -27
- package/dist/ui/contrast.ts +0 -27
- package/dist/ui/controls.tsx +0 -25
- package/dist/ui/disclosure.tsx +0 -20
- package/dist/ui/dismiss-layer.ts +0 -87
- package/dist/ui/door.ts +0 -140
- package/dist/ui/drag.ts +0 -316
- package/dist/ui/elements/accordion.tsx +0 -47
- package/dist/ui/elements/accordion.works.ts +0 -66
- package/dist/ui/elements/aspect-ratio.tsx +0 -31
- package/dist/ui/elements/aspect-ratio.works.ts +0 -5
- package/dist/ui/elements/avatar.tsx +0 -34
- package/dist/ui/elements/avatar.works.ts +0 -16
- package/dist/ui/elements/badge.tsx +0 -33
- package/dist/ui/elements/badge.works.ts +0 -9
- package/dist/ui/elements/bars.tsx +0 -50
- package/dist/ui/elements/bars.works.ts +0 -29
- package/dist/ui/elements/box.tsx +0 -36
- package/dist/ui/elements/breadcrumb.tsx +0 -38
- package/dist/ui/elements/breadcrumb.works.ts +0 -24
- package/dist/ui/elements/button.tsx +0 -46
- package/dist/ui/elements/button.works.tsx +0 -52
- package/dist/ui/elements/column.tsx +0 -29
- package/dist/ui/elements/consent.tsx +0 -71
- package/dist/ui/elements/consent.works.tsx +0 -132
- package/dist/ui/elements/donut.tsx +0 -45
- package/dist/ui/elements/donut.works.ts +0 -42
- package/dist/ui/elements/each.tsx +0 -34
- package/dist/ui/elements/each.works.tsx +0 -82
- package/dist/ui/elements/empty.tsx +0 -33
- package/dist/ui/elements/field.tsx +0 -45
- package/dist/ui/elements/field.works.ts +0 -65
- package/dist/ui/elements/funnel.tsx +0 -50
- package/dist/ui/elements/funnel.works.ts +0 -37
- package/dist/ui/elements/gauge.tsx +0 -40
- package/dist/ui/elements/gauge.works.ts +0 -42
- package/dist/ui/elements/glyph.tsx +0 -30
- package/dist/ui/elements/glyph.works.tsx +0 -42
- package/dist/ui/elements/grid.tsx +0 -29
- package/dist/ui/elements/heatmap.tsx +0 -48
- package/dist/ui/elements/heatmap.works.ts +0 -24
- package/dist/ui/elements/hit-box.tsx +0 -47
- package/dist/ui/elements/hit-box.works.tsx +0 -576
- package/dist/ui/elements/image.tsx +0 -29
- package/dist/ui/elements/line-chart.tsx +0 -45
- package/dist/ui/elements/line-chart.works.ts +0 -41
- package/dist/ui/elements/link.tsx +0 -38
- package/dist/ui/elements/link.works.ts +0 -25
- package/dist/ui/elements/media.tsx +0 -57
- package/dist/ui/elements/menu.tsx +0 -51
- package/dist/ui/elements/menu.works.tsx +0 -154
- package/dist/ui/elements/money.tsx +0 -36
- package/dist/ui/elements/money.works.ts +0 -9
- package/dist/ui/elements/pagination.tsx +0 -39
- package/dist/ui/elements/pagination.works.ts +0 -20
- package/dist/ui/elements/progress.tsx +0 -34
- package/dist/ui/elements/progress.works.ts +0 -8
- package/dist/ui/elements/radio-group.tsx +0 -50
- package/dist/ui/elements/radio-group.works.ts +0 -33
- package/dist/ui/elements/row.tsx +0 -29
- package/dist/ui/elements/scatter.tsx +0 -42
- package/dist/ui/elements/scatter.works.ts +0 -38
- package/dist/ui/elements/select.tsx +0 -43
- package/dist/ui/elements/select.works.ts +0 -5
- package/dist/ui/elements/separator.tsx +0 -31
- package/dist/ui/elements/skeleton.tsx +0 -31
- package/dist/ui/elements/slider.tsx +0 -34
- package/dist/ui/elements/slider.works.ts +0 -5
- package/dist/ui/elements/sparkline.tsx +0 -37
- package/dist/ui/elements/sparkline.works.ts +0 -32
- package/dist/ui/elements/spinner.tsx +0 -33
- package/dist/ui/elements/stack.tsx +0 -29
- package/dist/ui/elements/stat.tsx +0 -36
- package/dist/ui/elements/stat.works.ts +0 -14
- package/dist/ui/elements/surface.tsx +0 -36
- package/dist/ui/elements/surface.works.ts +0 -9
- package/dist/ui/elements/table.tsx +0 -62
- package/dist/ui/elements/table.works.ts +0 -30
- package/dist/ui/elements/tabs.tsx +0 -47
- package/dist/ui/elements/tabs.works.ts +0 -84
- package/dist/ui/elements/text.tsx +0 -33
- package/dist/ui/elements/textarea.tsx +0 -38
- package/dist/ui/elements/textarea.works.ts +0 -5
- package/dist/ui/elements/time.tsx +0 -40
- package/dist/ui/elements/time.works.ts +0 -29
- package/dist/ui/elements/toggle.tsx +0 -34
- package/dist/ui/elements/toggle.works.ts +0 -5
- package/dist/ui/elements/tooltip.tsx +0 -40
- package/dist/ui/elements/tooltip.works.ts +0 -77
- package/dist/ui/elements/value.tsx +0 -38
- package/dist/ui/elements/value.works.ts +0 -9
- package/dist/ui/elements.ts +0 -127
- package/dist/ui/focus-scope.ts +0 -121
- package/dist/ui/fonts.ts +0 -355
- package/dist/ui/generators/dev.d.ts +0 -1
- package/dist/ui/generators/dev.js +0 -61
- package/dist/ui/generators/dev.ts +0 -74
- package/dist/ui/generators/elements.ts +0 -796
- package/dist/ui/generators/i18n.ts +0 -283
- package/dist/ui/generators/icons.ts +0 -207
- package/dist/ui/generators/react.ts +0 -267
- package/dist/ui/generators/routes.ts +0 -847
- package/dist/ui/generators/show.ts +0 -261
- package/dist/ui/generators/styles.ts +0 -677
- package/dist/ui/generators/variants.ts +0 -196
- package/dist/ui/i18n/catalogs/en.ts +0 -191
- package/dist/ui/i18n/format.ts +0 -221
- package/dist/ui/i18n/locales.ts +0 -33
- package/dist/ui/i18n/parse.ts +0 -315
- package/dist/ui/i18n/translator.ts +0 -31
- package/dist/ui/i18n/types.ts +0 -96
- package/dist/ui/icons.ts +0 -331
- package/dist/ui/islands/map.ts +0 -58
- package/dist/ui/localizations.ts +0 -75
- package/dist/ui/material.ts +0 -236
- package/dist/ui/materials/mat_bordered.ts +0 -13
- package/dist/ui/materials/mat_ceramic.ts +0 -13
- package/dist/ui/materials/mat_clay.ts +0 -13
- package/dist/ui/materials/mat_elevated.ts +0 -24
- package/dist/ui/materials/mat_flat.ts +0 -34
- package/dist/ui/materials/mat_glass.ts +0 -20
- package/dist/ui/materials/mat_lit.ts +0 -13
- package/dist/ui/materials/mat_moulded.ts +0 -11
- package/dist/ui/materials.ts +0 -197
- package/dist/ui/modes.ts +0 -44
- package/dist/ui/navigation.tsx +0 -29
- package/dist/ui/positioning.ts +0 -90
- package/dist/ui/presented.tsx +0 -119
- package/dist/ui/provider.tsx +0 -184
- package/dist/ui/render-loop.ts +0 -133
- package/dist/ui/roving-focus.ts +0 -161
- package/dist/ui/screens.ts +0 -141
- package/dist/ui/scroll-lock.ts +0 -28
- package/dist/ui/series.ts +0 -97
- package/dist/ui/signals.ts +0 -386
- package/dist/ui/styleguide.ts +0 -231
- package/dist/ui/styleguides.ts +0 -374
- package/dist/ui/themes.ts +0 -22
- package/dist/ui/token.ts +0 -78
- package/dist/ui/tokens/shadow.ts +0 -25
- package/dist/ui/tokens.ts +0 -234
- package/dist/ui/validators.ts +0 -232
- package/dist/ui/variant.ts +0 -352
- package/dist/ui/variants/var_avatar-round.ts +0 -14
- package/dist/ui/variants/var_bubble-me.ts +0 -14
- package/dist/ui/variants/var_bubble-them.ts +0 -13
- package/dist/ui/variants/var_button-border.ts +0 -76
- package/dist/ui/variants/var_button-cta.ts +0 -76
- package/dist/ui/variants/var_button-destructive-ghost.ts +0 -72
- package/dist/ui/variants/var_button-destructive.ts +0 -76
- package/dist/ui/variants/var_button-fill.ts +0 -76
- package/dist/ui/variants/var_button-ghost.ts +0 -72
- package/dist/ui/variants/var_button-glass.ts +0 -55
- package/dist/ui/variants/var_button-normal.ts +0 -76
- package/dist/ui/variants/var_button-quiet.ts +0 -74
- package/dist/ui/variants/var_card.ts +0 -17
- package/dist/ui/variants/var_chip.ts +0 -13
- package/dist/ui/variants/var_choice.ts +0 -15
- package/dist/ui/variants/var_framed.ts +0 -10
- package/dist/ui/variants/var_glyph-plain.ts +0 -11
- package/dist/ui/variants/var_input.ts +0 -43
- package/dist/ui/variants/var_link-inline.ts +0 -29
- package/dist/ui/variants/var_list-row.ts +0 -34
- package/dist/ui/variants/var_rule.ts +0 -10
- package/dist/ui/variants/var_skeleton-block.ts +0 -12
- package/dist/ui/variants/var_spinner-turning.ts +0 -12
- package/dist/ui/variants/var_stat-tile.ts +0 -13
- package/dist/ui/variants/var_tab-bar.ts +0 -15
- package/dist/ui/variants/var_tap-target.ts +0 -25
- package/dist/ui/variants.ts +0 -64
- package/dist/ui/wrappers.tsx +0 -429
|
@@ -1,677 +0,0 @@
|
|
|
1
|
-
import { existsSync, readFileSync, writeFileSync } from '@prestack/thread/integrations/disk'
|
|
2
|
-
import { dirname, join, relative, resolve } from 'node:path'
|
|
3
|
-
import { argv, stderr, stdout } from 'node:process'
|
|
4
|
-
import { fileURLToPath, pathToFileURL } from 'node:url'
|
|
5
|
-
|
|
6
|
-
import { exit } from '@prestack/thread/core/invoked'
|
|
7
|
-
import { findTree, inTree } from '@prestack/thread/core/tree'
|
|
8
|
-
import { CHECKED_AT } from '@prestack/thread/operations/capabilities'
|
|
9
|
-
|
|
10
|
-
import { keyframeName, shorthandOf, stopsOf, type Animation } from '../animations.ts'
|
|
11
|
-
import { classOfRole, familyFor, fontStack, fontVariable, setNamed, settingsOf, type FontRole } from '../fonts.ts'
|
|
12
|
-
import { sizeNamed, sizeRows, SIZE_PREFIX } from '../icons.ts'
|
|
13
|
-
import { compose, keyframesOf, type Material } from '../material.ts'
|
|
14
|
-
import { checkInteractions, knowSignals } from '../signals.ts'
|
|
15
|
-
import { chosen, shifted, themedBy, type StyleGuide } from '../styleguide.ts'
|
|
16
|
-
import { naming, nameOf, sheeted, type Holds, type Token } from '../token.ts'
|
|
17
|
-
import { classOf, classOfComposition, classOfElement, classOfScreen, complete, statesOf, structural, under, undeclaredStates, unreacted, boundHeld, type Variant } from '../variant.ts'
|
|
18
|
-
|
|
19
|
-
interface Row {
|
|
20
|
-
readonly id: string
|
|
21
|
-
readonly at?: string
|
|
22
|
-
readonly token?: string
|
|
23
|
-
readonly is?: string
|
|
24
|
-
readonly asks?: Readonly<Record<string, string | number>>
|
|
25
|
-
readonly wears?: readonly string[]
|
|
26
|
-
readonly sets?: Readonly<Record<string, string>>
|
|
27
|
-
readonly places?: Readonly<Record<string, Readonly<Record<string, string>>>>
|
|
28
|
-
readonly modes?: Readonly<Record<string, string | Readonly<Record<string, string>>>>
|
|
29
|
-
readonly themes?: Readonly<Record<string, { readonly is?: string; readonly modes?: Readonly<Record<string, string>> }>>
|
|
30
|
-
readonly plays?: { readonly in?: string; readonly out?: string }
|
|
31
|
-
readonly measures?: readonly string[]
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
interface Node {
|
|
35
|
-
readonly id?: string
|
|
36
|
-
readonly at?: string
|
|
37
|
-
readonly declares?: readonly Row[]
|
|
38
|
-
readonly holds?: readonly Row[]
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
function inside(node: Node): readonly Row[] {
|
|
42
|
-
return [...(node.holds ?? []), ...(node.declares ?? [])]
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
const LAYERS = ['token', 'font', 'animation', 'material', 'element', 'variant', 'composition', 'screen'] as const
|
|
46
|
-
|
|
47
|
-
const compare = (a: string, b: string): number => (a < b ? -1 : a > b ? 1 : 0)
|
|
48
|
-
|
|
49
|
-
const exported = (id: string): string => id.replace(/-/g, '_').toUpperCase()
|
|
50
|
-
|
|
51
|
-
function dashed(property: string): string {
|
|
52
|
-
return property.replace(/([a-z0-9])([A-Z])/g, '$1-$2').toLowerCase()
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
function block(selector: string, made: Readonly<Record<string, Holds>>, indent: string): readonly string[] {
|
|
56
|
-
const properties = Object.keys(made).sort(compare)
|
|
57
|
-
if (properties.length === 0) return []
|
|
58
|
-
const lines = [`${indent}${selector} {`]
|
|
59
|
-
for (const property of properties) lines.push(`${indent} ${dashed(property)}: ${made[property] ?? ''};`)
|
|
60
|
-
lines.push(`${indent}}`, '')
|
|
61
|
-
return lines
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
function sheet(
|
|
65
|
-
tokens: Readonly<Record<string, Token>>,
|
|
66
|
-
guide: StyleGuide,
|
|
67
|
-
materials: Readonly<Record<string, Material>>,
|
|
68
|
-
variants: Readonly<Record<string, Variant>>,
|
|
69
|
-
modes: readonly { readonly id: string; readonly is: string }[],
|
|
70
|
-
themes: readonly { readonly id: string }[],
|
|
71
|
-
screens: readonly Row[],
|
|
72
|
-
compositions: readonly Row[],
|
|
73
|
-
elements: readonly Row[],
|
|
74
|
-
fonts: Node,
|
|
75
|
-
icons: Node,
|
|
76
|
-
animations: Readonly<Record<string, Animation>>,
|
|
77
|
-
): string {
|
|
78
|
-
const wrong = checkInteractions()[0]
|
|
79
|
-
if (wrong !== undefined) throw new Error(`${wrong.path}: ${wrong.message}`)
|
|
80
|
-
|
|
81
|
-
const sizes = sizeRows(inside(icons))
|
|
82
|
-
if (sizes.length === 0) {
|
|
83
|
-
throw new Error(
|
|
84
|
-
'The icons sheet declares no size row, so nothing says how large a symbol is drawn. The style ' +
|
|
85
|
-
'guide holds no icon size — one row per size in `icons.ts`, holding a length.',
|
|
86
|
-
)
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
const values = { ...chosen(guide, tokens), ...Object.fromEntries(sizes.map((one) => [one.id, sheeted(one.id, one.is)])) }
|
|
90
|
-
const named = new Set(Object.keys(values))
|
|
91
|
-
const themeIds = new Set(themes.map((theme) => theme.id))
|
|
92
|
-
|
|
93
|
-
const kinds: Record<string, string> = {}
|
|
94
|
-
for (const pick of guide.picks) kinds[pick.id] = pick.token
|
|
95
|
-
for (const size of sizes) kinds[size.id] = 'icon'
|
|
96
|
-
|
|
97
|
-
const played = (made: Readonly<Record<string, Holds>>): Readonly<Record<string, Holds>> => {
|
|
98
|
-
const out: Record<string, Holds> = {}
|
|
99
|
-
for (const [id, value] of Object.entries(made)) {
|
|
100
|
-
if (kinds[id] !== 'animation') {
|
|
101
|
-
out[id] = value
|
|
102
|
-
continue
|
|
103
|
-
}
|
|
104
|
-
const animation = animations[value]
|
|
105
|
-
if (animation === undefined) {
|
|
106
|
-
const has = Object.keys(animations).sort().map((one) => `\`${one}\``)
|
|
107
|
-
throw new Error(
|
|
108
|
-
`\`${id}\` names the animation \`${value}\`, and no row declares one by that name. ` +
|
|
109
|
-
(has.length === 0
|
|
110
|
-
? 'The app declares no animations.'
|
|
111
|
-
: `The declared animations are ${has.join(', ')}.`),
|
|
112
|
-
)
|
|
113
|
-
}
|
|
114
|
-
out[id] = sheeted(value, shorthandOf(animation, named))
|
|
115
|
-
}
|
|
116
|
-
return out
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
const variables = (
|
|
120
|
-
made: Readonly<Record<string, Holds>>,
|
|
121
|
-
selector: string,
|
|
122
|
-
indent: string,
|
|
123
|
-
): readonly string[] => {
|
|
124
|
-
const names = Object.keys(made).sort(compare)
|
|
125
|
-
if (names.length === 0) return []
|
|
126
|
-
const held = [`${indent}${selector} {`]
|
|
127
|
-
for (const id of names) held.push(`${indent} ${nameOf(id)}: ${made[id] ?? ''};`)
|
|
128
|
-
held.push(`${indent}}`, '')
|
|
129
|
-
return held
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
const lines: string[] = [
|
|
133
|
-
`@layer ${LAYERS.join(', ')};`,
|
|
134
|
-
'',
|
|
135
|
-
'@layer token {',
|
|
136
|
-
...variables(played(values), ':root', ' '),
|
|
137
|
-
]
|
|
138
|
-
|
|
139
|
-
for (const mode of modes) {
|
|
140
|
-
const held = variables(played(shifted(guide, tokens, mode.id, undefined, themeIds)), ':root', ' ')
|
|
141
|
-
if (held.length === 0) continue
|
|
142
|
-
lines.push(` @media ${mode.is} {`, ...held, ' }', '')
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
for (const theme of themes) {
|
|
146
|
-
lines.push(...variables(played(shifted(guide, tokens, undefined, theme.id, themeIds)), themedBy(theme.id), ' '))
|
|
147
|
-
for (const mode of modes) {
|
|
148
|
-
const held = variables(played(shifted(guide, tokens, mode.id, theme.id, themeIds)), themedBy(theme.id), ' ')
|
|
149
|
-
if (held.length === 0) continue
|
|
150
|
-
lines.push(` @media ${mode.is} {`, ...held, ' }', '')
|
|
151
|
-
}
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
const asked = modes.map((mode) => mode.id).join(' ')
|
|
155
|
-
lines.push(
|
|
156
|
-
...(asked === '' ? [] : [' :root {', ` color-scheme: ${asked};`, ' }', '']),
|
|
157
|
-
' body {',
|
|
158
|
-
' background: var(--colour-page);',
|
|
159
|
-
' color: var(--colour-ink);',
|
|
160
|
-
' }',
|
|
161
|
-
'',
|
|
162
|
-
)
|
|
163
|
-
|
|
164
|
-
lines.push('}', '')
|
|
165
|
-
|
|
166
|
-
const type = typography(fonts)
|
|
167
|
-
lines.push('@layer font {', ...type.lines, '}', '')
|
|
168
|
-
|
|
169
|
-
lines.push('@layer animation {')
|
|
170
|
-
for (const id of Object.keys(animations).sort(compare)) {
|
|
171
|
-
const animation = animations[id]
|
|
172
|
-
if (animation === undefined) continue
|
|
173
|
-
lines.push(` @keyframes ${keyframeName(id)} {`)
|
|
174
|
-
for (const stop of stopsOf(animation, named, tokens, kinds)) lines.push(...block(stop.stop, stop.made, ' '))
|
|
175
|
-
lines.push(' }', '')
|
|
176
|
-
}
|
|
177
|
-
lines.push('}', '')
|
|
178
|
-
|
|
179
|
-
const moving = new Map<
|
|
180
|
-
string,
|
|
181
|
-
{
|
|
182
|
-
readonly by: string
|
|
183
|
-
readonly stops: readonly { readonly stop: string; readonly made: Readonly<Record<string, Holds>> }[]
|
|
184
|
-
}
|
|
185
|
-
>()
|
|
186
|
-
for (const id of Object.keys(materials).sort(compare)) {
|
|
187
|
-
const material = materials[id]
|
|
188
|
-
if (material === undefined) continue
|
|
189
|
-
for (const one of keyframesOf(material, named, tokens, kinds)) {
|
|
190
|
-
const already = moving.get(one.id)
|
|
191
|
-
if (already !== undefined) {
|
|
192
|
-
throw new Error(
|
|
193
|
-
`\`${id}\` and \`${already.by}\` both declare keyframes called \`${one.id}\`, and a ` +
|
|
194
|
-
'stylesheet holds one rule per name, so whichever came last would silently win.',
|
|
195
|
-
)
|
|
196
|
-
}
|
|
197
|
-
moving.set(one.id, { by: id, stops: one.stops })
|
|
198
|
-
}
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
lines.push('@layer material {')
|
|
202
|
-
for (const name of [...moving.keys()].sort(compare)) {
|
|
203
|
-
lines.push(` @keyframes ${name} {`)
|
|
204
|
-
for (const stop of moving.get(name)?.stops ?? []) lines.push(...block(stop.stop, stop.made, ' '))
|
|
205
|
-
lines.push(' }', '')
|
|
206
|
-
}
|
|
207
|
-
for (const id of Object.keys(materials).sort(compare)) {
|
|
208
|
-
const material = materials[id]
|
|
209
|
-
if (material === undefined) continue
|
|
210
|
-
const gap = undeclaredStates(material)[0]
|
|
211
|
-
if (gap !== undefined) throw new Error(`\`${id}\`: ${gap}`)
|
|
212
|
-
compose(material, named, tokens, kinds)
|
|
213
|
-
under(material, named, tokens, kinds)
|
|
214
|
-
}
|
|
215
|
-
lines.push('}', '')
|
|
216
|
-
|
|
217
|
-
lines.push('@layer variant {')
|
|
218
|
-
for (const id of Object.keys(variants).sort(compare)) {
|
|
219
|
-
const variant = variants[id]
|
|
220
|
-
if (variant === undefined) continue
|
|
221
|
-
const whole = statesOf(variant, materials)
|
|
222
|
-
const first = undeclaredStates(whole)[0]
|
|
223
|
-
if (first !== undefined) throw new Error(`\`${id}\`: ${first}`)
|
|
224
|
-
const unclaimed = unreacted(variant, materials)[0]
|
|
225
|
-
if (unclaimed !== undefined) throw new Error(unclaimed)
|
|
226
|
-
|
|
227
|
-
let typed: Readonly<Record<string, Holds>> = {}
|
|
228
|
-
if (variant.type !== undefined) {
|
|
229
|
-
const role = type.roles.get(variant.type)
|
|
230
|
-
if (role === undefined) {
|
|
231
|
-
const has = [...type.roles.keys()].sort(compare).map((one) => `\`${one}\``)
|
|
232
|
-
throw new Error(
|
|
233
|
-
`\`${id}\` sets its wording in the \`${variant.type}\` role, and the fonts sheet declares ` +
|
|
234
|
-
`no role by that name. The declared roles are ${has.join(', ')}.`,
|
|
235
|
-
)
|
|
236
|
-
}
|
|
237
|
-
typed = role
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
const made = { ...typed, ...complete(variant, materials, named, tokens, kinds) }
|
|
241
|
-
lines.push(...block(`.${classOf(id)}`, made, ' '))
|
|
242
|
-
lines.push(...playing(`.${classOf(id)}`, variant.plays, named, id))
|
|
243
|
-
lines.push(...symbols(id, variant.icon, named, made['display'] !== undefined))
|
|
244
|
-
for (const state of under(whole, named, tokens, kinds)) {
|
|
245
|
-
lines.push(...block(`.${classOf(id)}${state.marks}`, state.made, ' '))
|
|
246
|
-
}
|
|
247
|
-
}
|
|
248
|
-
lines.push('}', '')
|
|
249
|
-
|
|
250
|
-
for (const held of [
|
|
251
|
-
|
|
252
|
-
{ layer: 'element', named: classOfElement, rows: elements },
|
|
253
|
-
{ layer: 'composition', named: classOfComposition, rows: compositions },
|
|
254
|
-
{ layer: 'screen', named: classOfScreen, rows: screens },
|
|
255
|
-
]) {
|
|
256
|
-
lines.push(`@layer ${held.layer} {`)
|
|
257
|
-
for (const row of [...held.rows].sort((one, two) => compare(one.id, two.id))) {
|
|
258
|
-
const selector = `.${held.named(row.id)}`
|
|
259
|
-
lines.push(...block(selector, resolved(row.sets ?? {}, guide, row.id, row.measures), ' '))
|
|
260
|
-
lines.push(...playing(selector, row.plays, named, row.id))
|
|
261
|
-
for (const [id, made] of Object.entries(row.places ?? {})) {
|
|
262
|
-
if (!PLACE.test(id)) {
|
|
263
|
-
throw new Error(
|
|
264
|
-
`\`${row.id}\` gives a place the name "${id}", and a place is named in the markup as \`at="${id}"\` ` +
|
|
265
|
-
'and becomes part of a class, so it has to be lowercase words joined by dashes. Anything else ' +
|
|
266
|
-
'produces a class the child can never match, and nothing would look wrong until you saw the screen.',
|
|
267
|
-
)
|
|
268
|
-
}
|
|
269
|
-
|
|
270
|
-
const looks = Object.fromEntries(Object.entries(made).filter(([key]) => !SAID.has(key)))
|
|
271
|
-
lines.push(...block(`${selector}-${id}`, resolved(looks, guide, `${row.id} place \`${id}\``, row.measures), ' '))
|
|
272
|
-
}
|
|
273
|
-
for (const [id, made] of Object.entries(row.modes ?? {})) {
|
|
274
|
-
if (typeof made === 'string') continue
|
|
275
|
-
const query = modes.find((one) => one.id === id)?.is
|
|
276
|
-
if (query === undefined) {
|
|
277
|
-
throw new Error(
|
|
278
|
-
`\`${row.id}\` arranges itself under "${id}", and no mode by that name is declared, so nothing ` +
|
|
279
|
-
'can tell when it applies. A breakpoint is a mode — declare it beside dark and light, with the ' +
|
|
280
|
-
'width as its query.',
|
|
281
|
-
)
|
|
282
|
-
}
|
|
283
|
-
lines.push(` @media ${query} {`, ...block(selector, resolved(made, guide, row.id), ' '), ' }', '')
|
|
284
|
-
}
|
|
285
|
-
}
|
|
286
|
-
lines.push('}', '')
|
|
287
|
-
}
|
|
288
|
-
|
|
289
|
-
return lines.join('\n')
|
|
290
|
-
}
|
|
291
|
-
|
|
292
|
-
const LAID = 'layout-flex'
|
|
293
|
-
|
|
294
|
-
const CENTRED = 'align-centre'
|
|
295
|
-
|
|
296
|
-
function symbols(id: string, size: string | undefined, named: ReadonlySet<string>, laid: boolean): readonly string[] {
|
|
297
|
-
if (size === undefined) return []
|
|
298
|
-
const row = sizeNamed(size)
|
|
299
|
-
if (!named.has(row)) {
|
|
300
|
-
const has = [...named].filter((one) => one.startsWith(SIZE_PREFIX)).sort(compare).map((one) => `\`${one}\``)
|
|
301
|
-
throw new Error(
|
|
302
|
-
`\`${id}\` draws the symbol inside it at \`${row}\`, and the icons sheet declares no size by ` +
|
|
303
|
-
`that name. The declared sizes are ${has.join(', ')}.`,
|
|
304
|
-
)
|
|
305
|
-
}
|
|
306
|
-
const drawn = naming(named, row, id, 'the size its symbol is drawn at')
|
|
307
|
-
|
|
308
|
-
const centred =
|
|
309
|
-
laid || !named.has(LAID) || !named.has(CENTRED)
|
|
310
|
-
? []
|
|
311
|
-
: block(
|
|
312
|
-
`.${classOf(id)}`,
|
|
313
|
-
{
|
|
314
|
-
display: naming(named, LAID, id, 'how what it holds is laid out'),
|
|
315
|
-
alignItems: naming(named, CENTRED, id, 'where its symbol sits across that'),
|
|
316
|
-
},
|
|
317
|
-
' ',
|
|
318
|
-
)
|
|
319
|
-
|
|
320
|
-
return [
|
|
321
|
-
...centred,
|
|
322
|
-
...block(`.${classOf(id)} > svg`, { width: drawn, height: drawn, minWidth: drawn, minHeight: drawn }, ' '),
|
|
323
|
-
]
|
|
324
|
-
}
|
|
325
|
-
|
|
326
|
-
function typography(node: Node): {
|
|
327
|
-
readonly lines: readonly string[]
|
|
328
|
-
readonly roles: ReadonlyMap<string, Readonly<Record<string, Holds>>>
|
|
329
|
-
} {
|
|
330
|
-
const rows = inside(node)
|
|
331
|
-
const roles = new Map<string, Readonly<Record<string, Holds>>>()
|
|
332
|
-
|
|
333
|
-
const picked = rows.find((row) => row.id === 'family-set')
|
|
334
|
-
if (picked?.is === undefined) {
|
|
335
|
-
throw new Error(
|
|
336
|
-
'The fonts sheet declares no `family-set` row, so nothing says which set of families the app ' +
|
|
337
|
-
'draws from. One row, naming a registered set — every face is drawn from it.',
|
|
338
|
-
)
|
|
339
|
-
}
|
|
340
|
-
const set = setNamed(picked.is)
|
|
341
|
-
|
|
342
|
-
const faces: Record<string, string> = {}
|
|
343
|
-
const classes: string[] = []
|
|
344
|
-
|
|
345
|
-
for (const row of rows) {
|
|
346
|
-
if (row.id === 'family-set') continue
|
|
347
|
-
if (row.sets === undefined) {
|
|
348
|
-
throw new Error(
|
|
349
|
-
`\`${row.id}\` is declared a font role and states nothing about how it is set. A role names a ` +
|
|
350
|
-
'face and states its size, weight, leading and tracking.',
|
|
351
|
-
)
|
|
352
|
-
}
|
|
353
|
-
const role = row.sets as unknown as FontRole
|
|
354
|
-
const family = familyFor(set, role.face, row.id)
|
|
355
|
-
faces[role.face] = fontStack(family)
|
|
356
|
-
|
|
357
|
-
const made = settingsOf(row.id, role)
|
|
358
|
-
roles.set(row.id, made)
|
|
359
|
-
|
|
360
|
-
classes.push(...block(`.${classOfRole(row.id)}`, made, ' '))
|
|
361
|
-
|
|
362
|
-
}
|
|
363
|
-
|
|
364
|
-
const held = [' :root {']
|
|
365
|
-
for (const face of Object.keys(faces).sort(compare)) held.push(` ${fontVariable(face)}: ${faces[face] ?? ''};`)
|
|
366
|
-
held.push(' }', '')
|
|
367
|
-
|
|
368
|
-
return { lines: [...held, ...classes], roles }
|
|
369
|
-
}
|
|
370
|
-
|
|
371
|
-
function playing(
|
|
372
|
-
selector: string,
|
|
373
|
-
plays: { readonly in?: string; readonly out?: string } | undefined,
|
|
374
|
-
named: ReadonlySet<string>,
|
|
375
|
-
by: string,
|
|
376
|
-
): readonly string[] {
|
|
377
|
-
const held: string[] = []
|
|
378
|
-
for (const [moment, row, on] of [
|
|
379
|
-
['in', plays?.in, `${selector}[data-arriving]`],
|
|
380
|
-
['out', plays?.out, `${selector}[data-leaving]`],
|
|
381
|
-
] as const) {
|
|
382
|
-
if (row === undefined) continue
|
|
383
|
-
if (!named.has(row)) {
|
|
384
|
-
throw new Error(
|
|
385
|
-
`\`${by}\` plays \`${row}\` on the way ${moment}, and the style guide holds no row by that name. ` +
|
|
386
|
-
'What a thing plays is a style guide value like any other — name a row, and the row names the animation.',
|
|
387
|
-
)
|
|
388
|
-
}
|
|
389
|
-
held.push(...block(on, { animation: naming(named, row, by, `what it plays on the way ${moment}`) }, ' '))
|
|
390
|
-
}
|
|
391
|
-
return held
|
|
392
|
-
}
|
|
393
|
-
|
|
394
|
-
const PLACE = /^[a-z][a-z0-9]*(-[a-z0-9]+)*$/
|
|
395
|
-
|
|
396
|
-
const SAID = structural
|
|
397
|
-
|
|
398
|
-
const RAW = /\d\s*(px|rem|em|ch|vh|vw)\b|\d%|#[0-9a-fA-F]{3,8}\b|\brgba?\(|\bhsla?\(/
|
|
399
|
-
|
|
400
|
-
function resolved(
|
|
401
|
-
sets: Readonly<Record<string, string>>,
|
|
402
|
-
guide: StyleGuide,
|
|
403
|
-
by: string,
|
|
404
|
-
measures: readonly string[] = [],
|
|
405
|
-
): Readonly<Record<string, Holds>> {
|
|
406
|
-
const made: Record<string, Holds> = {}
|
|
407
|
-
const bound = boundHeld()
|
|
408
|
-
for (const [property, whole] of Object.entries(sets)) {
|
|
409
|
-
|
|
410
|
-
const words = whole.trim().split(/\s+/)
|
|
411
|
-
const held: string[] = []
|
|
412
|
-
for (const value of words) {
|
|
413
|
-
if (measures.includes(value)) {
|
|
414
|
-
held.push(`var(--${value})`)
|
|
415
|
-
continue
|
|
416
|
-
}
|
|
417
|
-
if (RAW.test(value)) {
|
|
418
|
-
throw new Error(
|
|
419
|
-
`\`${by}\` sets ${property} to "${value}", and that is a value written where a name belongs. ` +
|
|
420
|
-
'An arrangement names a style guide row and the row holds the value — so the whole app moves ' +
|
|
421
|
-
'when the row moves. Add a row for it, or name one that exists.',
|
|
422
|
-
)
|
|
423
|
-
}
|
|
424
|
-
const row = guide.picks.find((one) => one.id === value)
|
|
425
|
-
if (row === undefined) {
|
|
426
|
-
throw new Error(
|
|
427
|
-
`\`${by}\` sets ${property} to "${value}", and that names neither a style guide row nor one ` +
|
|
428
|
-
"of this element's measures. A name that is neither is written into the stylesheet as-is " +
|
|
429
|
-
'and thrown away by the browser. Declare it in `measures`, or name a row.',
|
|
430
|
-
)
|
|
431
|
-
}
|
|
432
|
-
|
|
433
|
-
const kinds = bound[property]
|
|
434
|
-
if (kinds !== undefined && !kinds.includes(String(row.token))) {
|
|
435
|
-
throw new Error(
|
|
436
|
-
`\`${by}\` sets ${property} to \`${value}\`, which is a \`${String(row.token)}\` row. ` +
|
|
437
|
-
`${property} takes ${kinds.map((one) => `\`${one}\``).join(' or ')}. A row of the wrong kind ` +
|
|
438
|
-
'is written into the stylesheet and dropped by the browser without a word.',
|
|
439
|
-
)
|
|
440
|
-
}
|
|
441
|
-
|
|
442
|
-
held.push(`var(--${value})`)
|
|
443
|
-
}
|
|
444
|
-
made[property] = sheeted(whole, held.join(' '))
|
|
445
|
-
}
|
|
446
|
-
return made
|
|
447
|
-
}
|
|
448
|
-
|
|
449
|
-
async function fileFor<T>(root: string, at: string, id: string, what: string): Promise<T> {
|
|
450
|
-
const module = (await import(pathToFileURL(resolve(root, at)).href)) as Record<string, unknown>
|
|
451
|
-
const one = module[exported(id)]
|
|
452
|
-
if (one === undefined) {
|
|
453
|
-
throw new Error(
|
|
454
|
-
`${what} \`${id}\` is declared at ${at}, and that file exports no \`${exported(id)}\`. ` +
|
|
455
|
-
'A row names a file, and the file is the thing.',
|
|
456
|
-
)
|
|
457
|
-
}
|
|
458
|
-
return one as T
|
|
459
|
-
}
|
|
460
|
-
|
|
461
|
-
async function loaded<T>(root: string, node: Node, what: string): Promise<Readonly<Record<string, T>>> {
|
|
462
|
-
const found: Record<string, T> = {}
|
|
463
|
-
for (const row of inside(node)) {
|
|
464
|
-
if (row.at === undefined) {
|
|
465
|
-
throw new Error(`${what} \`${row.id}\` names no file, so there is nothing to read it from.`)
|
|
466
|
-
}
|
|
467
|
-
found[row.id] = await fileFor<T>(root, row.at, row.id, what)
|
|
468
|
-
}
|
|
469
|
-
return found
|
|
470
|
-
}
|
|
471
|
-
|
|
472
|
-
async function tokensIn(root: string, node: Node): Promise<Readonly<Record<string, Token>>> {
|
|
473
|
-
const found: Record<string, Token> = {}
|
|
474
|
-
for (const row of inside(node)) {
|
|
475
|
-
found[row.id] =
|
|
476
|
-
row.at === undefined || row.at === node.at
|
|
477
|
-
? { id: row.id }
|
|
478
|
-
: await fileFor<Token>(root, row.at, row.id, 'token')
|
|
479
|
-
}
|
|
480
|
-
return found
|
|
481
|
-
}
|
|
482
|
-
|
|
483
|
-
function guideIn(node: Node): StyleGuide {
|
|
484
|
-
return {
|
|
485
|
-
id: node.id ?? 'styleguide',
|
|
486
|
-
picks: inside(node).map((row) => {
|
|
487
|
-
if (row.token === undefined) {
|
|
488
|
-
throw new Error(
|
|
489
|
-
`\`${row.id}\` names no token. A style guide row is one value OF a token, so it has to ` +
|
|
490
|
-
'say which token it is one of.',
|
|
491
|
-
)
|
|
492
|
-
}
|
|
493
|
-
return {
|
|
494
|
-
id: row.id,
|
|
495
|
-
|
|
496
|
-
token: row.token as StyleGuide['picks'][number]['token'],
|
|
497
|
-
...(row.is === undefined ? {} : { is: row.is }),
|
|
498
|
-
...(row.asks === undefined ? {} : { asks: row.asks }),
|
|
499
|
-
...(row.modes === undefined
|
|
500
|
-
? {}
|
|
501
|
-
: {
|
|
502
|
-
modes: Object.fromEntries(
|
|
503
|
-
Object.entries(row.modes).filter((one): one is [string, string] => typeof one[1] === 'string'),
|
|
504
|
-
),
|
|
505
|
-
}),
|
|
506
|
-
|
|
507
|
-
...(row.themes === undefined ? {} : { themes: row.themes }),
|
|
508
|
-
}
|
|
509
|
-
}),
|
|
510
|
-
}
|
|
511
|
-
}
|
|
512
|
-
|
|
513
|
-
function pascal(id: string): string {
|
|
514
|
-
return id
|
|
515
|
-
.split('-')
|
|
516
|
-
.map((part) => `${part.charAt(0).toUpperCase()}${part.slice(1)}`)
|
|
517
|
-
.join('')
|
|
518
|
-
}
|
|
519
|
-
|
|
520
|
-
function unwearable(variants: Node, elements: Node): readonly string[] {
|
|
521
|
-
const declared = new Set((elements.declares ?? []).map((row) => pascal(row.id)))
|
|
522
|
-
const missing: string[] = []
|
|
523
|
-
for (const row of variants.declares ?? []) {
|
|
524
|
-
for (const element of row.wears ?? []) {
|
|
525
|
-
if (declared.has(element)) continue
|
|
526
|
-
missing.push(
|
|
527
|
-
`\`${row.id}\` says \`${element}\` accepts it, and no element by that name is declared, ` +
|
|
528
|
-
'so nothing would ever wear it.',
|
|
529
|
-
)
|
|
530
|
-
}
|
|
531
|
-
}
|
|
532
|
-
return missing
|
|
533
|
-
}
|
|
534
|
-
|
|
535
|
-
function modesIn(node: Node): readonly { readonly id: string; readonly is: string }[] {
|
|
536
|
-
return inside(node).map((row) => {
|
|
537
|
-
if (row.is === undefined) {
|
|
538
|
-
throw new Error(
|
|
539
|
-
`\`${row.id}\` is declared a mode and states no query, so nothing could tell when it applies. ` +
|
|
540
|
-
'A mode is the question a device asks — give it the media query that answers it.',
|
|
541
|
-
)
|
|
542
|
-
}
|
|
543
|
-
return { id: row.id, is: row.is }
|
|
544
|
-
})
|
|
545
|
-
}
|
|
546
|
-
|
|
547
|
-
const OWN_AT = '../..'
|
|
548
|
-
|
|
549
|
-
async function node(root: string, file: string, name: string): Promise<Node> {
|
|
550
|
-
const own = argv.includes('--own')
|
|
551
|
-
const at = own ? resolve(fileURLToPath(import.meta.url), OWN_AT, file) : inTree(findTree(), file)
|
|
552
|
-
if (!own && !existsSync(at)) {
|
|
553
|
-
stdout.write(`styles: this tree has no ${findTree().name}/${file}, so it declares nothing to style and there is no stylesheet to write or check.\n`)
|
|
554
|
-
exit(0)
|
|
555
|
-
}
|
|
556
|
-
const module = (await import(pathToFileURL(at).href)) as Record<string, unknown>
|
|
557
|
-
const one = module[own ? `${name}S` : name]
|
|
558
|
-
if (one === undefined) {
|
|
559
|
-
throw new Error(
|
|
560
|
-
own ? `this package's own ${file} exports no \`${name}S\`.` : `_threads/${file} exports no \`${name}\`.`,
|
|
561
|
-
)
|
|
562
|
-
}
|
|
563
|
-
return one as Node
|
|
564
|
-
}
|
|
565
|
-
|
|
566
|
-
const STYLESHEET = 'app/_generated/tokens.css'
|
|
567
|
-
|
|
568
|
-
const DECLARED = 'app/_generated/declared.ts'
|
|
569
|
-
|
|
570
|
-
async function run(): Promise<number> {
|
|
571
|
-
const own = argv.includes('--own')
|
|
572
|
-
const root = own ? resolve(fileURLToPath(import.meta.url), '../../../../..') : findTree().at
|
|
573
|
-
|
|
574
|
-
knowSignals(inside(await node(root, 'signals.ts', 'SIGNAL')).map((one) => one.id))
|
|
575
|
-
|
|
576
|
-
const tokens = await tokensIn(root, await node(root, 'tokens.ts', 'TOKEN'))
|
|
577
|
-
const guide = guideIn(await node(root, 'styleguides.ts', 'STYLEGUIDE'))
|
|
578
|
-
const materials = await loaded<Material>(root, await node(root, 'materials.ts', 'MATERIAL'), 'material')
|
|
579
|
-
const declaring = await node(root, 'variants.ts', 'VARIANT')
|
|
580
|
-
const variants = await loaded<Variant>(root, declaring, 'variant')
|
|
581
|
-
|
|
582
|
-
const unworn = unwearable(declaring, await node(root, 'elements.ts', 'ELEMENT'))[0]
|
|
583
|
-
if (unworn !== undefined) throw new Error(unworn)
|
|
584
|
-
|
|
585
|
-
const fonts = await node(root, 'fonts.ts', 'FONT')
|
|
586
|
-
const icons = await node(root, `icons.${'ts'}`, 'ICON')
|
|
587
|
-
const animations = await loaded<Animation>(root, await node(root, 'animations.ts', 'ANIMATION'), 'animation')
|
|
588
|
-
|
|
589
|
-
const modes = modesIn(await node(root, 'modes.ts', 'MODE'))
|
|
590
|
-
const themes = inside(await node(root, 'themes.ts', 'THEME'))
|
|
591
|
-
const screens = (await node(root, 'screens.ts', 'SCREEN')).declares ?? []
|
|
592
|
-
const compositions = (await node(root, 'compositions.ts', 'COMPOSITION')).declares ?? []
|
|
593
|
-
|
|
594
|
-
const elements = ((await node(root, 'elements.ts', 'ELEMENT')).declares ?? []).filter(
|
|
595
|
-
(one) => one.sets !== undefined || one.places !== undefined,
|
|
596
|
-
)
|
|
597
|
-
|
|
598
|
-
const camelOf = (id: string): string => id.replace(/-([a-z0-9])/g, (_, c: string) => c.toUpperCase())
|
|
599
|
-
|
|
600
|
-
const worn = ((declaring.declares ?? []) as readonly { readonly id?: string; readonly wears?: readonly string[] }[])
|
|
601
|
-
.filter((one) => typeof one.id === 'string' && (one.wears ?? []).length > 0)
|
|
602
|
-
.map((one) => ` readonly ${camelOf(String(one.id))}: ${(one.wears ?? []).map((e) => `'${e}'`).join(' | ')}`)
|
|
603
|
-
|
|
604
|
-
const asking = (((await node(root, 'signals.ts', 'SIGNAL')).holds ?? []) as readonly {
|
|
605
|
-
readonly id?: string
|
|
606
|
-
readonly selector?: string
|
|
607
|
-
readonly when?: string
|
|
608
|
-
}[]).filter((one) => typeof one.id === 'string')
|
|
609
|
-
|
|
610
|
-
const checked = `./${relative(dirname(DECLARED), CHECKED_AT)}`
|
|
611
|
-
|
|
612
|
-
const wearingSource = [
|
|
613
|
-
`import type {} from '${checked}'`,
|
|
614
|
-
'',
|
|
615
|
-
`declare module '${checked}' {`,
|
|
616
|
-
...(worn.length === 0 ? [' interface Wearing {}'] : [' interface Wearing {', ...worn, ' }']),
|
|
617
|
-
'}',
|
|
618
|
-
'',
|
|
619
|
-
'export const ASKING = [',
|
|
620
|
-
...asking.map(
|
|
621
|
-
(one) =>
|
|
622
|
-
` { id: '${String(one.id)}', selector: '${one.selector ?? `[data-${String(one.id)}]`}'${typeof one.when === 'string' ? `, when: '${one.when}'` : ''} },`,
|
|
623
|
-
),
|
|
624
|
-
'] as const',
|
|
625
|
-
'',
|
|
626
|
-
].join('\n')
|
|
627
|
-
|
|
628
|
-
const at = join(root, STYLESHEET)
|
|
629
|
-
const made = sheet(tokens, guide, materials, variants, modes, themes, screens, compositions, elements, fonts, icons, animations)
|
|
630
|
-
|
|
631
|
-
if (own) {
|
|
632
|
-
stdout.write(
|
|
633
|
-
`styles: this framework's own declarations build a stylesheet — ${String(guide.picks.length)} values, ` +
|
|
634
|
-
`${String(Object.keys(materials).length)} materials, ${String(Object.keys(variants).length)} variants, ` +
|
|
635
|
-
`${String(Object.keys(animations).length)} animations, every one naming rows that exist. ` +
|
|
636
|
-
'Nothing is written: a stylesheet belongs to an app, and building it is the check.\n',
|
|
637
|
-
)
|
|
638
|
-
return 0
|
|
639
|
-
}
|
|
640
|
-
|
|
641
|
-
if (argv.includes('--check')) {
|
|
642
|
-
const drifted = [
|
|
643
|
-
{ named: STYLESHEET, text: made },
|
|
644
|
-
{ named: DECLARED, text: wearingSource },
|
|
645
|
-
].filter((one) => {
|
|
646
|
-
try {
|
|
647
|
-
return readFileSync(join(root, one.named), 'utf8') !== one.text
|
|
648
|
-
} catch {
|
|
649
|
-
return true
|
|
650
|
-
}
|
|
651
|
-
})
|
|
652
|
-
if (drifted.length === 0) {
|
|
653
|
-
stdout.write(`styles: ${STYLESHEET} and ${DECLARED} are the bytes the declarations produce\n`)
|
|
654
|
-
return 0
|
|
655
|
-
}
|
|
656
|
-
stderr.write(
|
|
657
|
-
`styles: ${drifted.map((one) => one.named).join(' and ')} ${drifted.length === 1 ? 'is' : 'are'} not what the declarations produce. ` +
|
|
658
|
-
'A derived file that differs from its generator is a hand edit wearing a generated name, and every reader ' +
|
|
659
|
-
'downstream believes the wrong one. Run `thread styles` and commit what it writes.\n',
|
|
660
|
-
)
|
|
661
|
-
return 1
|
|
662
|
-
}
|
|
663
|
-
|
|
664
|
-
writeFileSync(at, made, 'utf8')
|
|
665
|
-
writeFileSync(join(root, DECLARED), wearingSource, 'utf8')
|
|
666
|
-
|
|
667
|
-
stdout.write(
|
|
668
|
-
`thread: wrote ${STYLESHEET} and ${DECLARED} — ${String(Object.keys(tokens).length)} tokens, ` +
|
|
669
|
-
`${String(guide.picks.length)} values, ${String(Object.keys(materials).length)} materials, ` +
|
|
670
|
-
`${String(Object.keys(variants).length)} variants, ${String(inside(fonts).length - 1)} font roles, ` +
|
|
671
|
-
`${String(Object.keys(animations).length)} animations, ${String(modes.length)} modes, ` +
|
|
672
|
-
`${String(themes.length)} themes, ${String(compositions.length)} compositions, ${String(screens.length)} screens\n`,
|
|
673
|
-
)
|
|
674
|
-
return 0
|
|
675
|
-
}
|
|
676
|
-
|
|
677
|
-
if (argv[1] !== undefined) exit(await run())
|