@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
package/dist/ui/material.ts
DELETED
|
@@ -1,236 +0,0 @@
|
|
|
1
|
-
import { held, naming, sheeted, type Holds, type Token, type Wearing } from './token.ts'
|
|
2
|
-
|
|
3
|
-
export interface Setting {
|
|
4
|
-
readonly property: string
|
|
5
|
-
|
|
6
|
-
readonly is?: string
|
|
7
|
-
|
|
8
|
-
readonly token?: string
|
|
9
|
-
|
|
10
|
-
readonly asks?: Readonly<Record<string, string | number>>
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
export const BOUND = {
|
|
14
|
-
background: ['colour', 'gradient', 'texture', 'noise', 'img'],
|
|
15
|
-
backgroundImage: ['gradient', 'texture', 'noise', 'img'],
|
|
16
|
-
backdropFilter: ['blur'],
|
|
17
|
-
borderWidth: ['edge'],
|
|
18
|
-
borderStyle: ['line'],
|
|
19
|
-
borderColor: ['colour'],
|
|
20
|
-
boxShadow: ['shadow'],
|
|
21
|
-
color: ['colour'],
|
|
22
|
-
opacity: ['opacity'],
|
|
23
|
-
} as const
|
|
24
|
-
|
|
25
|
-
type Property = keyof typeof BOUND
|
|
26
|
-
|
|
27
|
-
type MaterialSettingFor<P extends Property> = {
|
|
28
|
-
readonly property: P
|
|
29
|
-
|
|
30
|
-
readonly is?: string
|
|
31
|
-
|
|
32
|
-
readonly token?: (typeof BOUND)[P][number]
|
|
33
|
-
|
|
34
|
-
readonly asks?: Readonly<Record<string, string | number>>
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
type MaterialSetting = { [P in Property]: MaterialSettingFor<P> }[Property]
|
|
38
|
-
|
|
39
|
-
export interface Material {
|
|
40
|
-
readonly id: string
|
|
41
|
-
|
|
42
|
-
readonly sets: readonly MaterialSetting[]
|
|
43
|
-
|
|
44
|
-
readonly states?: readonly {
|
|
45
|
-
readonly when: readonly string[]
|
|
46
|
-
readonly sets: readonly MaterialSetting[]
|
|
47
|
-
}[]
|
|
48
|
-
|
|
49
|
-
readonly plays?: string
|
|
50
|
-
|
|
51
|
-
readonly keyframes?: readonly {
|
|
52
|
-
readonly id: string
|
|
53
|
-
readonly sets: Readonly<Record<string, readonly MaterialSetting[]>>
|
|
54
|
-
}[]
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
export const FREE = ['transition', 'outline'] as const
|
|
58
|
-
|
|
59
|
-
export const material = <const T extends Material>(
|
|
60
|
-
one: T & {
|
|
61
|
-
readonly sets: {
|
|
62
|
-
readonly [I in keyof T['sets']]: T['sets'][I] extends { readonly is: infer Said }
|
|
63
|
-
? { readonly is: Wearing<Said & string> }
|
|
64
|
-
: unknown
|
|
65
|
-
}
|
|
66
|
-
} & (T extends { readonly states: infer States }
|
|
67
|
-
? {
|
|
68
|
-
readonly states: {
|
|
69
|
-
readonly [S in keyof States]: States[S] extends { readonly sets: infer Sets }
|
|
70
|
-
? {
|
|
71
|
-
readonly sets: {
|
|
72
|
-
readonly [I in keyof Sets]: Sets[I] extends { readonly is: infer Said }
|
|
73
|
-
? { readonly is: Wearing<Said & string> }
|
|
74
|
-
: unknown
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
: unknown
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
: unknown) &
|
|
81
|
-
(T extends { readonly keyframes: infer Frames }
|
|
82
|
-
? {
|
|
83
|
-
readonly keyframes: {
|
|
84
|
-
readonly [F in keyof Frames]: Frames[F] extends { readonly sets: infer Stops }
|
|
85
|
-
? {
|
|
86
|
-
readonly sets: {
|
|
87
|
-
readonly [Stop in keyof Stops]: {
|
|
88
|
-
readonly [I in keyof Stops[Stop]]: Stops[Stop][I] extends { readonly is: infer Said }
|
|
89
|
-
? { readonly is: Wearing<Said & string> }
|
|
90
|
-
: unknown
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
}
|
|
94
|
-
: unknown
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
: unknown),
|
|
98
|
-
): T => one
|
|
99
|
-
|
|
100
|
-
function rowsIn(value: string): readonly string[] {
|
|
101
|
-
return value.split(/\s+/).filter((one) => one !== '')
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
function bounded(
|
|
105
|
-
set: Setting,
|
|
106
|
-
by: string,
|
|
107
|
-
kinds: Readonly<Record<string, string>>,
|
|
108
|
-
bound: Readonly<Record<string, readonly string[]>>,
|
|
109
|
-
): void {
|
|
110
|
-
const value = set.is ?? ''
|
|
111
|
-
|
|
112
|
-
const rows = rowsIn(value)
|
|
113
|
-
|
|
114
|
-
const allowed = bound[set.property]
|
|
115
|
-
if (allowed === undefined) return
|
|
116
|
-
|
|
117
|
-
for (const row of rows) {
|
|
118
|
-
const kind = kinds[row]
|
|
119
|
-
if (kind === undefined || allowed.includes(kind)) continue
|
|
120
|
-
const list = allowed.map((one) => `\`${one}\``).join(' · ')
|
|
121
|
-
throw new Error(
|
|
122
|
-
`\`${by}\` sets \`${set.property}\` to \`${row}\`, and \`${row}\` is a \`${kind}\` row. ` +
|
|
123
|
-
`\`${set.property}\` takes: ${list}.`,
|
|
124
|
-
)
|
|
125
|
-
}
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
function valued(
|
|
129
|
-
set: Setting,
|
|
130
|
-
named: ReadonlySet<string>,
|
|
131
|
-
tokens: Readonly<Record<string, Token>>,
|
|
132
|
-
by: string,
|
|
133
|
-
kinds: Readonly<Record<string, string>> | undefined,
|
|
134
|
-
bound: Readonly<Record<string, readonly string[]>>,
|
|
135
|
-
): Holds {
|
|
136
|
-
if (set.is !== undefined && set.token !== undefined) {
|
|
137
|
-
throw new Error(
|
|
138
|
-
`\`${by}\` states both a value and a token for \`${set.property}\`. A property is either a ` +
|
|
139
|
-
'value the style guide holds or a token asked for one, never both.',
|
|
140
|
-
)
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
if (set.is !== undefined) {
|
|
144
|
-
if (kinds !== undefined) bounded(set, by, kinds, bound)
|
|
145
|
-
return naming(named, set.is, by, set.property)
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
if (set.token === undefined) {
|
|
149
|
-
throw new Error(
|
|
150
|
-
`\`${by}\` sets \`${set.property}\` to nothing. State a value with \`is\`, or name a token ` +
|
|
151
|
-
'and what to ask it for.',
|
|
152
|
-
)
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
const token = tokens[set.token]
|
|
156
|
-
if (token === undefined) {
|
|
157
|
-
throw new Error(
|
|
158
|
-
`\`${by}\` asks \`${set.token}\` for \`${set.property}\`, and no token by that name is ` +
|
|
159
|
-
'declared. A token has to exist before anything can ask it for a value.',
|
|
160
|
-
)
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
if (token.makes === undefined) {
|
|
164
|
-
throw new Error(
|
|
165
|
-
`\`${by}\` asks \`${set.token}\` for \`${set.property}\`, and \`${set.token}\` carries no ` +
|
|
166
|
-
'rule, so there is nothing to ask. Name a style guide value instead.',
|
|
167
|
-
)
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
return held(named, token.makes(set.asks ?? {}), by)
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
export function composed(
|
|
174
|
-
sets: readonly Setting[],
|
|
175
|
-
named: ReadonlySet<string>,
|
|
176
|
-
tokens: Readonly<Record<string, Token>>,
|
|
177
|
-
by: string,
|
|
178
|
-
kinds: Readonly<Record<string, string>>,
|
|
179
|
-
bound: Readonly<Record<string, readonly string[]>>,
|
|
180
|
-
): Readonly<Record<string, Holds>> {
|
|
181
|
-
const made: Record<string, Holds> = {}
|
|
182
|
-
for (const set of sets) {
|
|
183
|
-
|
|
184
|
-
if (bound[set.property] === undefined && (set.property === 'animation' || set.property === 'animationName')) {
|
|
185
|
-
throw new Error(
|
|
186
|
-
`\`${by}\` sets \`${set.property}\` itself, which buries the keyframes name inside a ` +
|
|
187
|
-
'string where nothing can check it. Name a style guide row of the `animation` kind, or ' +
|
|
188
|
-
'declare keyframes and name them with `plays`.',
|
|
189
|
-
)
|
|
190
|
-
}
|
|
191
|
-
made[set.property] = valued(set, named, tokens, by, kinds, bound)
|
|
192
|
-
}
|
|
193
|
-
return made
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
function playing(material: Material, plays: string | undefined): Readonly<Record<string, Holds>> {
|
|
197
|
-
if (plays === undefined) return {}
|
|
198
|
-
if ((material.keyframes ?? []).some((one) => one.id === plays)) return { animationName: sheeted(material.id, plays) }
|
|
199
|
-
const declared = (material.keyframes ?? []).map((one) => one.id)
|
|
200
|
-
throw new Error(
|
|
201
|
-
`\`${material.id}\` plays \`${plays}\`, and declares no keyframes by that name. ` +
|
|
202
|
-
(declared.length === 0
|
|
203
|
-
? 'It declares none at all, so there is nothing for it to play.'
|
|
204
|
-
: `It declares: ${declared.join(' · ')}.`),
|
|
205
|
-
)
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
export function compose(
|
|
209
|
-
material: Material,
|
|
210
|
-
named: ReadonlySet<string>,
|
|
211
|
-
tokens: Readonly<Record<string, Token>>,
|
|
212
|
-
kinds: Readonly<Record<string, string>>,
|
|
213
|
-
): Readonly<Record<string, Holds>> {
|
|
214
|
-
return {
|
|
215
|
-
...composed(material.sets, named, tokens, material.id, kinds, BOUND),
|
|
216
|
-
...playing(material, material.plays),
|
|
217
|
-
}
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
export function keyframesOf(
|
|
221
|
-
material: Material,
|
|
222
|
-
named: ReadonlySet<string>,
|
|
223
|
-
tokens: Readonly<Record<string, Token>>,
|
|
224
|
-
kinds: Readonly<Record<string, string>>,
|
|
225
|
-
): readonly {
|
|
226
|
-
readonly id: string
|
|
227
|
-
readonly stops: readonly { readonly stop: string; readonly made: Readonly<Record<string, Holds>> }[]
|
|
228
|
-
}[] {
|
|
229
|
-
return (material.keyframes ?? []).map((one) => ({
|
|
230
|
-
id: one.id,
|
|
231
|
-
stops: Object.keys(one.sets).map((stop) => ({
|
|
232
|
-
stop,
|
|
233
|
-
made: composed(one.sets[stop] ?? [], named, tokens, material.id, kinds, BOUND),
|
|
234
|
-
})),
|
|
235
|
-
}))
|
|
236
|
-
}
|
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
import { material } from '../material.ts'
|
|
2
|
-
|
|
3
|
-
export const BORDERED = material({
|
|
4
|
-
id: 'bordered',
|
|
5
|
-
sets: [
|
|
6
|
-
{ property: 'background', is: 'colour-surface' },
|
|
7
|
-
{ property: 'borderWidth', is: 'edge-thick' },
|
|
8
|
-
{ property: 'borderStyle', is: 'line-solid' },
|
|
9
|
-
{ property: 'borderColor', is: 'colour-ink' },
|
|
10
|
-
{ property: 'boxShadow', is: 'shadow-hard' },
|
|
11
|
-
{ property: 'color', is: 'colour-ink' },
|
|
12
|
-
],
|
|
13
|
-
} as const)
|
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
import { material } from '../material.ts'
|
|
2
|
-
|
|
3
|
-
export const CERAMIC = material({
|
|
4
|
-
id: 'ceramic',
|
|
5
|
-
sets: [
|
|
6
|
-
{ property: 'backgroundImage', is: 'gradient-sheen' },
|
|
7
|
-
{ property: 'borderWidth', is: 'edge-hair' },
|
|
8
|
-
{ property: 'borderStyle', is: 'line-solid' },
|
|
9
|
-
{ property: 'borderColor', is: 'colour-edge-ceramic' },
|
|
10
|
-
{ property: 'boxShadow', is: 'shadow-2' },
|
|
11
|
-
{ property: 'color', is: 'colour-ink' },
|
|
12
|
-
],
|
|
13
|
-
} as const)
|
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
import { material } from '../material.ts'
|
|
2
|
-
|
|
3
|
-
export const CLAY = material({
|
|
4
|
-
id: 'clay',
|
|
5
|
-
sets: [
|
|
6
|
-
{ property: 'background', is: 'colour-surface' },
|
|
7
|
-
{ property: 'borderWidth', is: 'edge-thin' },
|
|
8
|
-
{ property: 'borderStyle', is: 'line-solid' },
|
|
9
|
-
{ property: 'borderColor', is: 'colour-edge-clay' },
|
|
10
|
-
{ property: 'boxShadow', is: 'shadow-2' },
|
|
11
|
-
{ property: 'color', is: 'colour-ink' },
|
|
12
|
-
],
|
|
13
|
-
} as const)
|
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
import { material } from '../material.ts'
|
|
2
|
-
|
|
3
|
-
export const ELEVATED = material({
|
|
4
|
-
id: 'elevated',
|
|
5
|
-
sets: [
|
|
6
|
-
{ property: 'background', is: 'colour-surface' },
|
|
7
|
-
{ property: 'boxShadow', is: 'shadow-2' },
|
|
8
|
-
{ property: 'color', is: 'colour-ink' },
|
|
9
|
-
],
|
|
10
|
-
states: [
|
|
11
|
-
{
|
|
12
|
-
when: ['hover'],
|
|
13
|
-
sets: [{ property: 'boxShadow', is: 'shadow-3' }],
|
|
14
|
-
},
|
|
15
|
-
{
|
|
16
|
-
when: ['active'],
|
|
17
|
-
sets: [{ property: 'boxShadow', is: 'shadow-1' }],
|
|
18
|
-
},
|
|
19
|
-
{
|
|
20
|
-
when: ['active', 'hover'],
|
|
21
|
-
sets: [{ property: 'boxShadow', is: 'shadow-1' }],
|
|
22
|
-
},
|
|
23
|
-
],
|
|
24
|
-
} as const)
|
|
@@ -1,34 +0,0 @@
|
|
|
1
|
-
import { material } from '../material.ts'
|
|
2
|
-
|
|
3
|
-
export const FLAT = material({
|
|
4
|
-
id: 'flat',
|
|
5
|
-
sets: [
|
|
6
|
-
{ property: 'background', is: 'colour-surface' },
|
|
7
|
-
{ property: 'borderWidth', is: 'edge-hair' },
|
|
8
|
-
{ property: 'borderStyle', is: 'line-solid' },
|
|
9
|
-
{ property: 'borderColor', is: 'colour-edge-faint' },
|
|
10
|
-
{ property: 'boxShadow', is: 'shadow-1' },
|
|
11
|
-
{ property: 'color', is: 'colour-ink' },
|
|
12
|
-
],
|
|
13
|
-
states: [
|
|
14
|
-
{
|
|
15
|
-
when: ['hover'],
|
|
16
|
-
sets: [{ property: 'boxShadow', is: 'shadow-2' }],
|
|
17
|
-
},
|
|
18
|
-
{
|
|
19
|
-
when: ['focus'],
|
|
20
|
-
sets: [
|
|
21
|
-
{ property: 'borderColor', is: 'colour-accent' },
|
|
22
|
-
{ property: 'borderWidth', is: 'edge-thick' },
|
|
23
|
-
],
|
|
24
|
-
},
|
|
25
|
-
{
|
|
26
|
-
when: ['focus', 'hover'],
|
|
27
|
-
sets: [
|
|
28
|
-
{ property: 'borderColor', is: 'colour-accent' },
|
|
29
|
-
{ property: 'borderWidth', is: 'edge-thick' },
|
|
30
|
-
{ property: 'boxShadow', is: 'shadow-2' },
|
|
31
|
-
],
|
|
32
|
-
},
|
|
33
|
-
],
|
|
34
|
-
} as const)
|
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
import { material } from '../material.ts'
|
|
2
|
-
|
|
3
|
-
export const GLASS = material({
|
|
4
|
-
id: 'glass',
|
|
5
|
-
sets: [
|
|
6
|
-
{ property: 'background', is: 'colour-glass' },
|
|
7
|
-
{ property: 'backdropFilter', is: 'blur-md' },
|
|
8
|
-
{ property: 'borderWidth', is: 'edge-hair' },
|
|
9
|
-
{ property: 'borderStyle', is: 'line-solid' },
|
|
10
|
-
{ property: 'borderColor', is: 'colour-edge-glass' },
|
|
11
|
-
{ property: 'boxShadow', is: 'shadow-3' },
|
|
12
|
-
{ property: 'color', is: 'colour-ink' },
|
|
13
|
-
],
|
|
14
|
-
states: [
|
|
15
|
-
{
|
|
16
|
-
when: ['hover'],
|
|
17
|
-
sets: [{ property: 'backdropFilter', is: 'blur-lg' }],
|
|
18
|
-
},
|
|
19
|
-
],
|
|
20
|
-
} as const)
|
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
import { material } from '../material.ts'
|
|
2
|
-
|
|
3
|
-
export const LIT = material({
|
|
4
|
-
id: 'lit',
|
|
5
|
-
sets: [
|
|
6
|
-
{ property: 'background', is: 'colour-surface-lit' },
|
|
7
|
-
{ property: 'borderWidth', is: 'edge-thin' },
|
|
8
|
-
{ property: 'borderStyle', is: 'line-solid' },
|
|
9
|
-
{ property: 'borderColor', is: 'colour-edge-lit' },
|
|
10
|
-
{ property: 'boxShadow', is: 'shadow-glow' },
|
|
11
|
-
{ property: 'color', is: 'colour-ink' },
|
|
12
|
-
],
|
|
13
|
-
} as const)
|
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
import { material } from '../material.ts'
|
|
2
|
-
|
|
3
|
-
export const MOULDED = material({
|
|
4
|
-
id: 'moulded',
|
|
5
|
-
sets: [
|
|
6
|
-
{ property: 'background', is: 'colour-page' },
|
|
7
|
-
{ property: 'borderStyle', is: 'line-off' },
|
|
8
|
-
{ property: 'boxShadow', is: 'shadow-moulded' },
|
|
9
|
-
{ property: 'color', is: 'colour-ink' },
|
|
10
|
-
],
|
|
11
|
-
} as const)
|
package/dist/ui/materials.ts
DELETED
|
@@ -1,197 +0,0 @@
|
|
|
1
|
-
import { existsSync, readFileSync, writeFileSync } from '@prestack/thread/integrations/disk'
|
|
2
|
-
import { basename, join } from 'node:path'
|
|
3
|
-
import { argv, stdout } from 'node:process'
|
|
4
|
-
import { pathToFileURL } from 'node:url'
|
|
5
|
-
|
|
6
|
-
import { exit, invoked } from '@prestack/thread/core/invoked'
|
|
7
|
-
import type { Threaded } from '@prestack/thread/core/thread'
|
|
8
|
-
import { findTree, inTree, strayFiles } from '@prestack/thread/core/tree'
|
|
9
|
-
|
|
10
|
-
export const MATERIALS = {
|
|
11
|
-
id: 'material',
|
|
12
|
-
at: 'thread/ui/materials.ts',
|
|
13
|
-
files: 'ui/mat_*.ts',
|
|
14
|
-
describes:
|
|
15
|
-
'A combination of style guide values that gives one specific appearance, worn by any variant that names it: glass, clay, a glaze. It composes and never computes, because every value it sets is a name the style guide holds and never a raw one, which is what lets one material be worn by many variants. Each row names a file prefixed `mat_`, and that file is the material; a file under it that no row names is not a material but a file somebody wrote. An app adds its own to this same list, and a row that goes missing breaks the build, because what the framework offers is not an app’s to withdraw. These are materials and not aesthetics: Neumorphism, Claymorphism and Cyberpunk are templates, and a template is what picks a material.',
|
|
16
|
-
version: 1,
|
|
17
|
-
cites: [
|
|
18
|
-
{
|
|
19
|
-
on: '2026-08-25',
|
|
20
|
-
said: 'Style guides they can be used for simple pickings that need no thinking. We can pick colours and basic effects for using the app. Materials however are for more complex and detailed work. Materials.ts is a declaration sheet, SSOT, and each row is a file, mat_',
|
|
21
|
-
},
|
|
22
|
-
],
|
|
23
|
-
declares: [
|
|
24
|
-
{ id: 'glass', at: 'thread/ui/materials/mat_glass.ts', describes: 'A pane you can see through, with what is behind it softened and desaturation corrected for.' },
|
|
25
|
-
{ id: 'flat', at: 'thread/ui/materials/mat_flat.ts', describes: 'Paper. A plain filled panel with a hairline and almost no lift — the same tokens as glass, asked more quietly.' },
|
|
26
|
-
{ id: 'elevated', at: 'thread/ui/materials/mat_elevated.ts', describes: 'Something lifted off the page, reading by its shadow rather than by any outline.' },
|
|
27
|
-
{ id: 'bordered', at: 'thread/ui/materials/mat_bordered.ts', describes: 'Ink on paper — a hard outline and a shadow with no blur, offset like a stamp.' },
|
|
28
|
-
{ id: 'moulded', at: 'thread/ui/materials/mat_moulded.ts', describes: 'Pressed out of the page rather than laid on it. Its fill is the page itself, and its relief is one shadow cast twice in opposite directions.' },
|
|
29
|
-
{ id: 'clay', at: 'thread/ui/materials/mat_clay.ts', describes: 'Something soft and inflated, lit from above — an outer shadow it sits in and an inset highlight along its top edge.' },
|
|
30
|
-
{ id: 'lit', at: 'thread/ui/materials/mat_lit.ts', describes: 'Something that emits rather than reflects. Its glow is a shadow cast from nowhere in particular, and it names no colour of its own.' },
|
|
31
|
-
{ id: 'ceramic', at: 'thread/ui/materials/mat_ceramic.ts', describes: 'A glazed surface with the light falling down its face. The one material that asks for a gradient rather than a fill.' },
|
|
32
|
-
],
|
|
33
|
-
} as const satisfies Threaded & { readonly files: string }
|
|
34
|
-
|
|
35
|
-
export function nameOf(id: string): string {
|
|
36
|
-
return id.replace(/-/g, '_').toUpperCase()
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
function starter(row: Threaded): string {
|
|
40
|
-
const name = nameOf(row.id)
|
|
41
|
-
return `import { material } from './material.ts'
|
|
42
|
-
|
|
43
|
-
export const ${name} = material({
|
|
44
|
-
id: '${row.id}',
|
|
45
|
-
sets: [],
|
|
46
|
-
} as const)
|
|
47
|
-
`
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
export function seeds(rows: readonly Threaded[], held: (at: string) => boolean): readonly { readonly path: string; readonly content: string }[] {
|
|
51
|
-
return rows.filter((row) => !held(row.at)).map((row) => ({ path: row.at, content: starter(row) }))
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
export async function mismatched(
|
|
55
|
-
rows: readonly Threaded[],
|
|
56
|
-
loader: (row: Threaded) => Promise<{ readonly id?: string } | undefined>,
|
|
57
|
-
): Promise<readonly string[]> {
|
|
58
|
-
const found: string[] = []
|
|
59
|
-
for (const row of rows) {
|
|
60
|
-
const material = await loader(row)
|
|
61
|
-
if (material === undefined) {
|
|
62
|
-
found.push(
|
|
63
|
-
`\`${row.id}\` is declared at \`${row.at}\`, and that file exports no \`${nameOf(row.id)}\`. ` +
|
|
64
|
-
'A row names a file, and the file is the thing.',
|
|
65
|
-
)
|
|
66
|
-
continue
|
|
67
|
-
}
|
|
68
|
-
if (material.id !== row.id) {
|
|
69
|
-
found.push(
|
|
70
|
-
`\`${row.at}\` declares itself \`${String(material.id)}\`, and the row that names it calls it \`${row.id}\`. ` +
|
|
71
|
-
"A material file and the row that names it have to be the same material — make the file's `id` match the row, or fix the row.",
|
|
72
|
-
)
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
return found
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
export function orphaned(rows: readonly Threaded[], strays: readonly string[]): readonly string[] {
|
|
79
|
-
const named = new Set(rows.map((row) => row.at))
|
|
80
|
-
return strays
|
|
81
|
-
.filter((one) => !named.has(one))
|
|
82
|
-
.map(
|
|
83
|
-
(one) =>
|
|
84
|
-
`\`${one}\` is under materials and no row names it — not a material, a file somebody wrote. ` +
|
|
85
|
-
'Add a row for it, or delete the file.',
|
|
86
|
-
)
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
const GUARDED = /=\s*material\s*\(/
|
|
90
|
-
|
|
91
|
-
const TYPED = /import type \{ Material \} from '([^']*)'/
|
|
92
|
-
|
|
93
|
-
const OPENS = /export const ([A-Z0-9_]+) = \{/
|
|
94
|
-
|
|
95
|
-
const CLOSES = /\} as const satisfies Material/
|
|
96
|
-
|
|
97
|
-
export function unguarded(rows: readonly Threaded[], read: (at: string) => string): readonly string[] {
|
|
98
|
-
return rows
|
|
99
|
-
.filter((row) => !GUARDED.test(read(row.at)))
|
|
100
|
-
.map(
|
|
101
|
-
(row) =>
|
|
102
|
-
`\`${row.at}\` declares \`${row.id}\` with \`satisfies Material\`, which accepts a value no ` +
|
|
103
|
-
'row holds — `satisfies` checks against `string`, so `8px` and `var(--x)` compile. Run ' +
|
|
104
|
-
'`thread materials` and it rewrites the two lines that wrap the declaration, leaving what is ' +
|
|
105
|
-
'between them alone.',
|
|
106
|
-
)
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
export function guarding(source: string): string | undefined {
|
|
110
|
-
if (GUARDED.test(source)) return undefined
|
|
111
|
-
const typed = TYPED.exec(source)
|
|
112
|
-
const opens = OPENS.exec(source)
|
|
113
|
-
if (typed === null || opens === null || !CLOSES.test(source)) return undefined
|
|
114
|
-
return source
|
|
115
|
-
.replace(typed[0], `import { material } from '${typed[1] ?? ''}'`)
|
|
116
|
-
.replace(opens[0], `export const ${opens[1] ?? ''} = material({`)
|
|
117
|
-
.replace(CLOSES, '} as const)')
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
async function run(): Promise<number> {
|
|
121
|
-
const tree = findTree()
|
|
122
|
-
const at = inTree(tree, 'materials.ts')
|
|
123
|
-
if (!existsSync(at)) {
|
|
124
|
-
stdout.write(`thread: this tree has no ${tree.name}/${basename(at)}, so it declares no material and there is nothing to seed or check.\n`)
|
|
125
|
-
return 0
|
|
126
|
-
}
|
|
127
|
-
const held = (await import(pathToFileURL(at).href)) as { MATERIAL?: { readonly declares?: readonly Threaded[] } }
|
|
128
|
-
|
|
129
|
-
if (held.MATERIAL === undefined) {
|
|
130
|
-
stdout.write(`thread: ${tree.name}/materials.ts exports no \`MATERIAL\`, so nothing declares a material here.\n`)
|
|
131
|
-
return 1
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
const rows = held.MATERIAL.declares ?? []
|
|
135
|
-
const checking = argv.includes('--check')
|
|
136
|
-
const exists = (where: string): boolean => existsSync(join(tree.at, where))
|
|
137
|
-
const made = seeds(rows, exists)
|
|
138
|
-
|
|
139
|
-
if (checking) {
|
|
140
|
-
const unseeded = rows.find((row) => !exists(row.at))
|
|
141
|
-
if (unseeded !== undefined) {
|
|
142
|
-
stdout.write(
|
|
143
|
-
`thread: \`${unseeded.id}\` is declared at \`${unseeded.at}\`, and the file is not there yet. ` +
|
|
144
|
-
'A row names a file, and `--check` never writes one — run `thread materials` and commit what it seeds.\n',
|
|
145
|
-
)
|
|
146
|
-
return 1
|
|
147
|
-
}
|
|
148
|
-
} else {
|
|
149
|
-
for (const one of made) {
|
|
150
|
-
writeFileSync(join(tree.at, one.path), one.content, 'utf8')
|
|
151
|
-
stdout.write(`thread: seeded ${one.path}\n`)
|
|
152
|
-
}
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
const wrong = (
|
|
156
|
-
await mismatched(rows, async (row) => {
|
|
157
|
-
const module = (await import(pathToFileURL(join(tree.at, row.at)).href)) as Record<string, unknown>
|
|
158
|
-
return module[nameOf(row.id)] as { readonly id?: string } | undefined
|
|
159
|
-
})
|
|
160
|
-
)[0]
|
|
161
|
-
if (wrong !== undefined) {
|
|
162
|
-
stdout.write(`thread: ${wrong}\n`)
|
|
163
|
-
return 1
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
const read = (at: string): string => readFileSync(join(tree.at, at), 'utf8')
|
|
167
|
-
if (checking) {
|
|
168
|
-
const loose = unguarded(rows, read)[0]
|
|
169
|
-
if (loose !== undefined) {
|
|
170
|
-
stdout.write(`thread: ${loose}\n`)
|
|
171
|
-
return 1
|
|
172
|
-
}
|
|
173
|
-
} else {
|
|
174
|
-
for (const row of rows) {
|
|
175
|
-
const written = guarding(read(row.at))
|
|
176
|
-
if (written === undefined) continue
|
|
177
|
-
writeFileSync(join(tree.at, row.at), written, 'utf8')
|
|
178
|
-
stdout.write(`thread: ${row.at} now declares through \`material(...)\`\n`)
|
|
179
|
-
}
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
const stray = orphaned(rows, strayFiles(tree.at, 'mat_', '.' + 'ts'))[0]
|
|
183
|
-
if (stray !== undefined) {
|
|
184
|
-
stdout.write(`thread: ${stray}\n`)
|
|
185
|
-
return 1
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
stdout.write(
|
|
189
|
-
checking
|
|
190
|
-
? `thread: ${String(rows.length)} material(s) declared, all seeded\n`
|
|
191
|
-
: `thread: ${String(rows.length)} material(s) declared, ${String(made.length)} seeded, ` +
|
|
192
|
-
`${String(rows.length - made.length)} already written\n`,
|
|
193
|
-
)
|
|
194
|
-
return 0
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
if (invoked(import.meta.url)) exit(await run())
|
package/dist/ui/modes.ts
DELETED
|
@@ -1,44 +0,0 @@
|
|
|
1
|
-
import type { Threaded } from '@prestack/thread/core/thread'
|
|
2
|
-
|
|
3
|
-
const here = 'thread/ui/modes.ts'
|
|
4
|
-
|
|
5
|
-
const said = [
|
|
6
|
-
{
|
|
7
|
-
on: '2026-08-25',
|
|
8
|
-
said: 'themes can have dark and light modes too. So they are not at all the same.',
|
|
9
|
-
},
|
|
10
|
-
]
|
|
11
|
-
|
|
12
|
-
const one = <Id extends string>(
|
|
13
|
-
id: Id,
|
|
14
|
-
is: string,
|
|
15
|
-
describes: string,
|
|
16
|
-
): Omit<Threaded, 'at'> & { readonly id: Id; readonly is: string } => ({
|
|
17
|
-
id,
|
|
18
|
-
is,
|
|
19
|
-
describes,
|
|
20
|
-
version: 1,
|
|
21
|
-
})
|
|
22
|
-
|
|
23
|
-
export const holds = [
|
|
24
|
-
one(
|
|
25
|
-
'dark',
|
|
26
|
-
'(prefers-color-scheme: dark)',
|
|
27
|
-
'The reader has asked their device for a dark interface. Nothing about the app chooses this and nothing about the app can override it.',
|
|
28
|
-
),
|
|
29
|
-
one(
|
|
30
|
-
'light',
|
|
31
|
-
'(prefers-color-scheme: light)',
|
|
32
|
-
'The reader has asked their device for a light interface. Stated rather than assumed, because the resting values are not the same thing as an answer to the question.',
|
|
33
|
-
),
|
|
34
|
-
] as const satisfies readonly (Omit<Threaded, 'at'> & { readonly is: string })[]
|
|
35
|
-
|
|
36
|
-
export const MODES = {
|
|
37
|
-
id: 'mode',
|
|
38
|
-
at: here,
|
|
39
|
-
describes:
|
|
40
|
-
'Every way a reader’s own device can ask for a different look, and the query that answers it. A mode is chosen for the app rather than by it, which is what tells it from a theme — and a theme has its own modes, so the two are dimensions rather than one list. A mode changes what the variables hold, never what a material or a variant says.',
|
|
41
|
-
version: 1,
|
|
42
|
-
cites: said,
|
|
43
|
-
holds,
|
|
44
|
-
} as const satisfies Threaded & { readonly holds: readonly (Omit<Threaded, 'at'> & { readonly is: string })[] }
|
package/dist/ui/navigation.tsx
DELETED
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
'use client'
|
|
2
|
-
|
|
3
|
-
import type { MapIsland, Screen } from './islands/map.ts'
|
|
4
|
-
|
|
5
|
-
export type ScreenName = Screen['id']
|
|
6
|
-
|
|
7
|
-
export function trailTo(map: MapIsland, to: ScreenName): readonly ScreenName[] {
|
|
8
|
-
const entries = map.screens.filter((s) => s.entry).map((s) => s.id)
|
|
9
|
-
if (entries.includes(to)) return [to]
|
|
10
|
-
|
|
11
|
-
const seen = new Set<ScreenName>(entries)
|
|
12
|
-
let frontier: readonly (readonly ScreenName[])[] = entries.map((id) => [id])
|
|
13
|
-
|
|
14
|
-
while (frontier.length > 0) {
|
|
15
|
-
const next: (readonly ScreenName[])[] = []
|
|
16
|
-
for (const path of frontier) {
|
|
17
|
-
const here = path[path.length - 1] as ScreenName
|
|
18
|
-
for (const link of map.links.filter((l) => l.from === here)) {
|
|
19
|
-
if (link.to === to) return [...path, link.to]
|
|
20
|
-
if (seen.has(link.to)) continue
|
|
21
|
-
seen.add(link.to)
|
|
22
|
-
next.push([...path, link.to])
|
|
23
|
-
}
|
|
24
|
-
}
|
|
25
|
-
frontier = next
|
|
26
|
-
}
|
|
27
|
-
return []
|
|
28
|
-
}
|
|
29
|
-
|