@prestack/thread-ui 0.0.1 → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +177 -0
- package/dist/ui/animations/anm_appear.ts +13 -0
- package/dist/ui/animations/anm_blur_in.ts +19 -0
- package/dist/ui/animations/anm_blur_out.ts +18 -0
- package/dist/ui/animations/anm_dismiss.ts +18 -0
- package/dist/ui/animations/anm_drop.ts +19 -0
- package/dist/ui/animations/anm_fall.ts +18 -0
- package/dist/ui/animations/anm_flash.ts +18 -0
- package/dist/ui/animations/anm_glow.ts +16 -0
- package/dist/ui/animations/anm_lift.ts +16 -0
- package/dist/ui/animations/anm_nudge.ts +16 -0
- package/dist/ui/animations/anm_pop_in.ts +23 -0
- package/dist/ui/animations/anm_pop_out.ts +22 -0
- package/dist/ui/animations/anm_press.ts +12 -0
- package/dist/ui/animations/anm_pulse.ts +13 -0
- package/dist/ui/animations/anm_rise.ts +19 -0
- package/dist/ui/animations/anm_shake.ts +13 -0
- package/dist/ui/animations/anm_slide_in_left.ts +19 -0
- package/dist/ui/animations/anm_slide_in_right.ts +19 -0
- package/dist/ui/animations/anm_slide_out_left.ts +18 -0
- package/dist/ui/animations/anm_slide_out_right.ts +18 -0
- package/dist/ui/animations/anm_spin.ts +12 -0
- package/dist/ui/animations/anm_swell.ts +16 -0
- package/dist/ui/animations/anm_wobble.ts +24 -0
- package/dist/ui/animations/anm_zoom_in.ts +19 -0
- package/dist/ui/animations/anm_zoom_out.ts +18 -0
- package/dist/ui/animations.js +2 -3
- package/dist/ui/animations.ts +316 -0
- package/dist/ui/attach.js +13 -10
- package/dist/ui/attach.ts +607 -0
- package/dist/ui/charts.tsx +74 -0
- package/dist/ui/checked.d.ts +2 -2
- package/dist/ui/checked.ts +32 -0
- package/dist/ui/collector.ts +140 -0
- package/dist/ui/compositions.d.ts +11 -2
- package/dist/ui/compositions.js +283 -59
- package/dist/ui/compositions.ts +633 -0
- package/dist/ui/consent.ts +55 -0
- package/dist/ui/content.tsx +39 -0
- package/dist/ui/contrast.ts +27 -0
- package/dist/ui/controls.tsx +30 -0
- package/dist/ui/disclosure.tsx +22 -0
- package/dist/ui/dismiss-layer.ts +87 -0
- package/dist/ui/door.ts +140 -0
- package/dist/ui/drag.ts +316 -0
- package/dist/ui/elements/accordion.js +2 -2
- package/dist/ui/elements/accordion.tsx +47 -0
- package/dist/ui/elements/accordion.works.ts +66 -0
- package/dist/ui/elements/aspect-ratio.js +1 -1
- package/dist/ui/elements/aspect-ratio.tsx +30 -0
- package/dist/ui/elements/aspect-ratio.works.ts +5 -0
- package/dist/ui/elements/avatar.js +1 -1
- package/dist/ui/elements/avatar.tsx +33 -0
- package/dist/ui/elements/avatar.works.ts +16 -0
- package/dist/ui/elements/badge.js +1 -1
- package/dist/ui/elements/badge.tsx +32 -0
- package/dist/ui/elements/badge.works.ts +9 -0
- package/dist/ui/elements/bars.js +2 -2
- package/dist/ui/elements/bars.tsx +49 -0
- package/dist/ui/elements/bars.works.ts +29 -0
- package/dist/ui/elements/box.js +3 -2
- package/dist/ui/elements/box.tsx +35 -0
- package/dist/ui/elements/breadcrumb.js +2 -2
- package/dist/ui/elements/breadcrumb.tsx +37 -0
- package/dist/ui/elements/breadcrumb.works.ts +24 -0
- package/dist/ui/elements/button.js +1 -1
- package/dist/ui/elements/button.tsx +45 -0
- package/dist/ui/elements/button.works.tsx +52 -0
- package/dist/ui/elements/column.js +1 -1
- package/dist/ui/elements/column.tsx +28 -0
- package/dist/ui/elements/consent.js +2 -2
- package/dist/ui/elements/consent.tsx +70 -0
- package/dist/ui/elements/consent.works.tsx +132 -0
- package/dist/ui/elements/donut.js +2 -2
- package/dist/ui/elements/donut.tsx +44 -0
- package/dist/ui/elements/donut.works.ts +42 -0
- package/dist/ui/elements/each.js +1 -1
- package/dist/ui/elements/each.tsx +33 -0
- package/dist/ui/elements/each.works.tsx +82 -0
- package/dist/ui/elements/empty.js +1 -1
- package/dist/ui/elements/empty.tsx +32 -0
- package/dist/ui/elements/field.js +1 -1
- package/dist/ui/elements/field.tsx +44 -0
- package/dist/ui/elements/field.works.ts +65 -0
- package/dist/ui/elements/funnel.js +2 -2
- package/dist/ui/elements/funnel.tsx +49 -0
- package/dist/ui/elements/funnel.works.ts +37 -0
- package/dist/ui/elements/gauge.js +1 -1
- package/dist/ui/elements/gauge.tsx +39 -0
- package/dist/ui/elements/gauge.works.ts +42 -0
- package/dist/ui/elements/glyph.js +1 -1
- package/dist/ui/elements/glyph.tsx +29 -0
- package/dist/ui/elements/glyph.works.tsx +42 -0
- package/dist/ui/elements/grid.js +1 -1
- package/dist/ui/elements/grid.tsx +28 -0
- package/dist/ui/elements/heatmap.js +2 -2
- package/dist/ui/elements/heatmap.tsx +47 -0
- package/dist/ui/elements/heatmap.works.ts +24 -0
- package/dist/ui/elements/hit-box.js +1 -1
- package/dist/ui/elements/hit-box.tsx +46 -0
- package/dist/ui/elements/hit-box.works.tsx +576 -0
- package/dist/ui/elements/image.js +1 -1
- package/dist/ui/elements/image.tsx +28 -0
- package/dist/ui/elements/line-chart.js +2 -2
- package/dist/ui/elements/line-chart.tsx +44 -0
- package/dist/ui/elements/line-chart.works.ts +41 -0
- package/dist/ui/elements/link.js +1 -1
- package/dist/ui/elements/link.tsx +37 -0
- package/dist/ui/elements/link.works.ts +25 -0
- package/dist/ui/elements/media.js +1 -1
- package/dist/ui/elements/media.tsx +56 -0
- package/dist/ui/elements/menu.js +2 -2
- package/dist/ui/elements/menu.tsx +50 -0
- package/dist/ui/elements/menu.works.tsx +153 -0
- package/dist/ui/elements/money.js +1 -1
- package/dist/ui/elements/money.tsx +28 -0
- package/dist/ui/elements/money.works.ts +9 -0
- package/dist/ui/elements/pagination.js +2 -2
- package/dist/ui/elements/pagination.tsx +38 -0
- package/dist/ui/elements/pagination.works.ts +20 -0
- package/dist/ui/elements/progress.js +1 -1
- package/dist/ui/elements/progress.tsx +33 -0
- package/dist/ui/elements/progress.works.ts +8 -0
- package/dist/ui/elements/radio-group.js +2 -2
- package/dist/ui/elements/radio-group.tsx +49 -0
- package/dist/ui/elements/radio-group.works.ts +33 -0
- package/dist/ui/elements/row.js +1 -1
- package/dist/ui/elements/row.tsx +28 -0
- package/dist/ui/elements/scatter.js +1 -1
- package/dist/ui/elements/scatter.tsx +41 -0
- package/dist/ui/elements/scatter.works.ts +38 -0
- package/dist/ui/elements/select.js +2 -2
- package/dist/ui/elements/select.tsx +42 -0
- package/dist/ui/elements/select.works.ts +5 -0
- package/dist/ui/elements/separator.js +1 -1
- package/dist/ui/elements/separator.tsx +30 -0
- package/dist/ui/elements/skeleton.js +1 -1
- package/dist/ui/elements/skeleton.tsx +30 -0
- package/dist/ui/elements/slider.js +1 -1
- package/dist/ui/elements/slider.tsx +33 -0
- package/dist/ui/elements/slider.works.ts +5 -0
- package/dist/ui/elements/sparkline.js +1 -1
- package/dist/ui/elements/sparkline.tsx +36 -0
- package/dist/ui/elements/sparkline.works.ts +32 -0
- package/dist/ui/elements/spinner.js +1 -1
- package/dist/ui/elements/spinner.tsx +32 -0
- package/dist/ui/elements/stack.js +1 -1
- package/dist/ui/elements/stack.tsx +28 -0
- package/dist/ui/elements/stat.js +1 -1
- package/dist/ui/elements/stat.tsx +35 -0
- package/dist/ui/elements/stat.works.ts +14 -0
- package/dist/ui/elements/surface.js +1 -1
- package/dist/ui/elements/surface.tsx +35 -0
- package/dist/ui/elements/surface.works.ts +9 -0
- package/dist/ui/elements/table.js +2 -2
- package/dist/ui/elements/table.tsx +61 -0
- package/dist/ui/elements/table.works.ts +30 -0
- package/dist/ui/elements/tabs.js +2 -2
- package/dist/ui/elements/tabs.tsx +47 -0
- package/dist/ui/elements/tabs.works.ts +84 -0
- package/dist/ui/elements/text.js +1 -1
- package/dist/ui/elements/text.tsx +32 -0
- package/dist/ui/elements/textarea.js +1 -1
- package/dist/ui/elements/textarea.tsx +37 -0
- package/dist/ui/elements/textarea.works.ts +5 -0
- package/dist/ui/elements/time.js +1 -1
- package/dist/ui/elements/time.tsx +34 -0
- package/dist/ui/elements/time.works.ts +25 -0
- package/dist/ui/elements/toggle.js +1 -1
- package/dist/ui/elements/toggle.tsx +33 -0
- package/dist/ui/elements/toggle.works.ts +5 -0
- package/dist/ui/elements/tooltip.js +1 -1
- package/dist/ui/elements/tooltip.tsx +39 -0
- package/dist/ui/elements/tooltip.works.ts +77 -0
- package/dist/ui/elements/value.js +1 -1
- package/dist/ui/elements/value.tsx +30 -0
- package/dist/ui/elements/value.works.ts +9 -0
- package/dist/ui/elements.ts +127 -0
- package/dist/ui/focus-scope.ts +121 -0
- package/dist/ui/fonts.ts +355 -0
- package/dist/ui/generators/dev.js +2 -1
- package/dist/ui/generators/dev.ts +80 -0
- package/dist/ui/generators/elements.d.ts +1 -11
- package/dist/ui/generators/elements.js +44 -14
- package/dist/ui/generators/elements.ts +794 -0
- package/dist/ui/generators/i18n.js +2 -3
- package/dist/ui/generators/i18n.ts +300 -0
- package/dist/ui/generators/icons.js +3 -12
- package/dist/ui/generators/icons.ts +214 -0
- package/dist/ui/generators/react.js +13 -5
- package/dist/ui/generators/react.ts +269 -0
- package/dist/ui/generators/routes.d.ts +1 -0
- package/dist/ui/generators/routes.js +99 -83
- package/dist/ui/generators/routes.ts +743 -0
- package/dist/ui/generators/show.js +4 -12
- package/dist/ui/generators/show.ts +260 -0
- package/dist/ui/generators/styles.js +3 -4
- package/dist/ui/generators/styles.ts +669 -0
- package/dist/ui/generators/variants.js +2 -3
- package/dist/ui/generators/variants.ts +204 -0
- package/dist/ui/i18n/catalogs/en.ts +191 -0
- package/dist/ui/i18n/format.ts +221 -0
- package/dist/ui/i18n/locales.ts +33 -0
- package/dist/ui/i18n/parse.ts +315 -0
- package/dist/ui/i18n/translator.ts +31 -0
- package/dist/ui/i18n/types.ts +96 -0
- package/dist/ui/icons.ts +331 -0
- package/dist/ui/islands/map.ts +58 -0
- package/dist/ui/localizations.ts +75 -0
- package/dist/ui/material.ts +236 -0
- package/dist/ui/materials/mat_bordered.ts +13 -0
- package/dist/ui/materials/mat_ceramic.ts +13 -0
- package/dist/ui/materials/mat_clay.ts +13 -0
- package/dist/ui/materials/mat_elevated.ts +24 -0
- package/dist/ui/materials/mat_flat.ts +34 -0
- package/dist/ui/materials/mat_glass.ts +20 -0
- package/dist/ui/materials/mat_lit.ts +13 -0
- package/dist/ui/materials/mat_moulded.ts +11 -0
- package/dist/ui/materials.js +2 -3
- package/dist/ui/materials.ts +205 -0
- package/dist/ui/modes.ts +44 -0
- package/dist/ui/navigation.tsx +32 -0
- package/dist/ui/positioning.ts +90 -0
- package/dist/ui/presented.tsx +119 -0
- package/dist/ui/provider.d.ts +2 -0
- package/dist/ui/provider.js +7 -1
- package/dist/ui/provider.tsx +182 -0
- package/dist/ui/render-loop.ts +133 -0
- package/dist/ui/roving-focus.ts +161 -0
- package/dist/ui/screens.js +22 -22
- package/dist/ui/screens.ts +141 -0
- package/dist/ui/scroll-lock.ts +28 -0
- package/dist/ui/series.ts +97 -0
- package/dist/ui/signals.ts +386 -0
- package/dist/ui/styleguide.ts +231 -0
- package/dist/ui/styleguides.ts +374 -0
- package/dist/ui/themes.ts +22 -0
- package/dist/ui/token.js +1 -1
- package/dist/ui/token.ts +78 -0
- package/dist/ui/tokens/shadow.ts +25 -0
- package/dist/ui/tokens.js +1 -2
- package/dist/ui/tokens.ts +234 -0
- package/dist/ui/validators.ts +232 -0
- package/dist/ui/variant.d.ts +1 -0
- package/dist/ui/variant.js +3 -0
- package/dist/ui/variant.ts +352 -0
- package/dist/ui/variants/var_avatar-round.ts +14 -0
- package/dist/ui/variants/var_bubble-me.ts +14 -0
- package/dist/ui/variants/var_bubble-them.ts +13 -0
- package/dist/ui/variants/var_button-border.ts +76 -0
- package/dist/ui/variants/var_button-cta.ts +76 -0
- package/dist/ui/variants/var_button-destructive-ghost.ts +72 -0
- package/dist/ui/variants/var_button-destructive.ts +76 -0
- package/dist/ui/variants/var_button-fill.ts +76 -0
- package/dist/ui/variants/var_button-ghost.ts +72 -0
- package/dist/ui/variants/var_button-glass.ts +55 -0
- package/dist/ui/variants/var_button-normal.ts +76 -0
- package/dist/ui/variants/var_button-quiet.ts +74 -0
- package/dist/ui/variants/var_card.ts +17 -0
- package/dist/ui/variants/var_chip.ts +13 -0
- package/dist/ui/variants/var_choice.ts +15 -0
- package/dist/ui/variants/var_framed.ts +10 -0
- package/dist/ui/variants/var_glyph-plain.ts +11 -0
- package/dist/ui/variants/var_input.ts +43 -0
- package/dist/ui/variants/var_link-inline.ts +29 -0
- package/dist/ui/variants/var_list-row.ts +34 -0
- package/dist/ui/variants/var_rule.ts +10 -0
- package/dist/ui/variants/var_skeleton-block.ts +12 -0
- package/dist/ui/variants/var_spinner-turning.ts +12 -0
- package/dist/ui/variants/var_stat-tile.ts +13 -0
- package/dist/ui/variants/var_tab-bar.ts +15 -0
- package/dist/ui/variants/var_tap-target.ts +25 -0
- package/dist/ui/variants.ts +64 -0
- package/dist/ui/wrappers.d.ts +4 -1
- package/dist/ui/wrappers.js +60 -20
- package/dist/ui/wrappers.tsx +426 -0
- package/package.json +8 -2
- package/dist/ui/makers/message.d.ts +0 -1
- package/dist/ui/makers/message.js +0 -230
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { Animation } from '../animations.ts'
|
|
2
|
+
|
|
3
|
+
export const WOBBLE = {
|
|
4
|
+
id: 'wobble',
|
|
5
|
+
runs: 'motion-slow',
|
|
6
|
+
eases: 'ease-in-out',
|
|
7
|
+
stops: {
|
|
8
|
+
'0%': [
|
|
9
|
+
{ property: 'transform', is: 'transform-rest' },
|
|
10
|
+
],
|
|
11
|
+
'25%': [
|
|
12
|
+
{ property: 'transform', is: 'transform-tilt' },
|
|
13
|
+
],
|
|
14
|
+
'50%': [
|
|
15
|
+
{ property: 'transform', is: 'transform-rest' },
|
|
16
|
+
],
|
|
17
|
+
'75%': [
|
|
18
|
+
{ property: 'transform', is: 'transform-tilt-back' },
|
|
19
|
+
],
|
|
20
|
+
'100%': [
|
|
21
|
+
{ property: 'transform', is: 'transform-rest' },
|
|
22
|
+
],
|
|
23
|
+
},
|
|
24
|
+
} as const satisfies Animation
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { Animation } from '../animations.ts'
|
|
2
|
+
|
|
3
|
+
export const ZOOM_IN = {
|
|
4
|
+
id: 'zoom-in',
|
|
5
|
+
runs: 'motion-normal',
|
|
6
|
+
eases: 'ease-out',
|
|
7
|
+
|
|
8
|
+
fills: 'backwards',
|
|
9
|
+
stops: {
|
|
10
|
+
'0%': [
|
|
11
|
+
{ property: 'opacity', is: 'opacity-none' },
|
|
12
|
+
{ property: 'transform', is: 'transform-grow' },
|
|
13
|
+
],
|
|
14
|
+
'100%': [
|
|
15
|
+
{ property: 'opacity', is: 'opacity-full' },
|
|
16
|
+
{ property: 'transform', is: 'transform-rest' },
|
|
17
|
+
],
|
|
18
|
+
},
|
|
19
|
+
} as const satisfies Animation
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { Animation } from '../animations.ts'
|
|
2
|
+
|
|
3
|
+
export const ZOOM_OUT = {
|
|
4
|
+
id: 'zoom-out',
|
|
5
|
+
runs: 'motion-quick',
|
|
6
|
+
eases: 'ease-in',
|
|
7
|
+
fills: 'forwards',
|
|
8
|
+
stops: {
|
|
9
|
+
'0%': [
|
|
10
|
+
{ property: 'opacity', is: 'opacity-full' },
|
|
11
|
+
{ property: 'transform', is: 'transform-rest' },
|
|
12
|
+
],
|
|
13
|
+
'100%': [
|
|
14
|
+
{ property: 'opacity', is: 'opacity-none' },
|
|
15
|
+
{ property: 'transform', is: 'transform-grow' },
|
|
16
|
+
],
|
|
17
|
+
},
|
|
18
|
+
} as const satisfies Animation
|
package/dist/ui/animations.js
CHANGED
|
@@ -6,8 +6,8 @@ var __rewriteRelativeImportExtension = (this && this.__rewriteRelativeImportExte
|
|
|
6
6
|
}
|
|
7
7
|
return path;
|
|
8
8
|
};
|
|
9
|
-
import { existsSync,
|
|
10
|
-
import { basename,
|
|
9
|
+
import { existsSync, writeFileSync } from "@prestack/thread/core/files";
|
|
10
|
+
import { basename, join } from 'node:path';
|
|
11
11
|
import { argv, stdout } from 'node:process';
|
|
12
12
|
import { pathToFileURL } from 'node:url';
|
|
13
13
|
import { exit, invoked } from "@prestack/thread/core/invoked";
|
|
@@ -199,7 +199,6 @@ async function run() {
|
|
|
199
199
|
}
|
|
200
200
|
else {
|
|
201
201
|
for (const seed of made) {
|
|
202
|
-
mkdirSync(dirname(join(tree.at, seed.path)), { recursive: true });
|
|
203
202
|
writeFileSync(join(tree.at, seed.path), seed.content, 'utf8');
|
|
204
203
|
stdout.write(`thread: seeded ${seed.path}\n`);
|
|
205
204
|
}
|
|
@@ -0,0 +1,316 @@
|
|
|
1
|
+
import { existsSync, readdirSync, writeFileSync } from '@prestack/thread/core/files'
|
|
2
|
+
import { basename, dirname, 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
|
+
import { held, sheeted, type Holds, type Token } from './token.ts'
|
|
11
|
+
|
|
12
|
+
const here = 'thread/ui/animations.ts'
|
|
13
|
+
|
|
14
|
+
export const BOUND = {
|
|
15
|
+
opacity: ['opacity'],
|
|
16
|
+
transform: ['transform'],
|
|
17
|
+
filter: ['filter'],
|
|
18
|
+
backdropFilter: ['blur'],
|
|
19
|
+
background: ['colour', 'gradient'],
|
|
20
|
+
color: ['colour'],
|
|
21
|
+
borderColor: ['colour'],
|
|
22
|
+
boxShadow: ['shadow'],
|
|
23
|
+
} as const
|
|
24
|
+
|
|
25
|
+
type Property = keyof typeof BOUND
|
|
26
|
+
|
|
27
|
+
type SettingFor<P extends Property> = {
|
|
28
|
+
readonly property: P
|
|
29
|
+
readonly is?: string
|
|
30
|
+
readonly token?: (typeof BOUND)[P][number]
|
|
31
|
+
readonly asks?: Readonly<Record<string, string | number>>
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export type AnimationSetting = { [P in Property]: SettingFor<P> }[Property]
|
|
35
|
+
|
|
36
|
+
export interface Animation {
|
|
37
|
+
readonly id: string
|
|
38
|
+
|
|
39
|
+
readonly runs: string
|
|
40
|
+
|
|
41
|
+
readonly eases: string
|
|
42
|
+
|
|
43
|
+
readonly repeats?: number | 'infinite'
|
|
44
|
+
|
|
45
|
+
readonly fills?: 'none' | 'forwards' | 'backwards' | 'both'
|
|
46
|
+
|
|
47
|
+
readonly stops: Readonly<Record<string, readonly AnimationSetting[]>>
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export function keyframeName(id: string): string {
|
|
51
|
+
return `anm-${id}`
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function resolve(
|
|
55
|
+
animation: Animation,
|
|
56
|
+
set: AnimationSetting,
|
|
57
|
+
value: string,
|
|
58
|
+
named: ReadonlySet<string>,
|
|
59
|
+
kinds: Readonly<Record<string, string>>,
|
|
60
|
+
): string {
|
|
61
|
+
if (!named.has(value)) {
|
|
62
|
+
throw new Error(
|
|
63
|
+
`\`${animation.id}\` sets ${set.property} to "${value}", and the style guide holds no row by ` +
|
|
64
|
+
'that name. An animation composes what the style guide holds, or it composes nothing.',
|
|
65
|
+
)
|
|
66
|
+
}
|
|
67
|
+
const kind = kinds[value]
|
|
68
|
+
const accepted: readonly string[] = BOUND[set.property]
|
|
69
|
+
if (kind !== undefined && !accepted.includes(kind)) {
|
|
70
|
+
throw new Error(
|
|
71
|
+
`\`${animation.id}\` sets ${set.property} to \`${value}\`, and that row is a \`${kind}\`. ` +
|
|
72
|
+
`${set.property} takes ${accepted.map((one) => `\`${one}\``).join(' or ')}.`,
|
|
73
|
+
)
|
|
74
|
+
}
|
|
75
|
+
return `var(--${value})`
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function valued(
|
|
79
|
+
animation: Animation,
|
|
80
|
+
set: AnimationSetting,
|
|
81
|
+
named: ReadonlySet<string>,
|
|
82
|
+
tokens: Readonly<Record<string, Token>>,
|
|
83
|
+
kinds: Readonly<Record<string, string>>,
|
|
84
|
+
): Holds {
|
|
85
|
+
if (set.token !== undefined) {
|
|
86
|
+
const token = tokens[set.token]
|
|
87
|
+
if (token?.makes === undefined) {
|
|
88
|
+
throw new Error(
|
|
89
|
+
`\`${animation.id}\` asks \`${set.token}\` for ${set.property}, and that token carries no rule ` +
|
|
90
|
+
'to make a value from. Name a style guide row instead, or give the token a rule.',
|
|
91
|
+
)
|
|
92
|
+
}
|
|
93
|
+
return sheeted(set.token, token.makes(set.asks ?? {}))
|
|
94
|
+
}
|
|
95
|
+
if (set.is === undefined) {
|
|
96
|
+
throw new Error(
|
|
97
|
+
`\`${animation.id}\` states ${set.property} with neither a value nor a token to make one from.`,
|
|
98
|
+
)
|
|
99
|
+
}
|
|
100
|
+
return held(named, resolve(animation, set, set.is, named, kinds), animation.id)
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
export function stopsOf(
|
|
104
|
+
animation: Animation,
|
|
105
|
+
named: ReadonlySet<string>,
|
|
106
|
+
tokens: Readonly<Record<string, Token>>,
|
|
107
|
+
kinds: Readonly<Record<string, string>>,
|
|
108
|
+
): readonly { readonly stop: string; readonly made: Readonly<Record<string, Holds>> }[] {
|
|
109
|
+
return Object.keys(animation.stops).map((stop) => {
|
|
110
|
+
const made: Record<string, Holds> = {}
|
|
111
|
+
for (const set of animation.stops[stop] ?? []) made[set.property] = valued(animation, set, named, tokens, kinds)
|
|
112
|
+
return { stop, made }
|
|
113
|
+
})
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
export function shorthandOf(animation: Animation, named: ReadonlySet<string>): string {
|
|
117
|
+
for (const [what, row] of [
|
|
118
|
+
['runs', animation.runs],
|
|
119
|
+
['eases', animation.eases],
|
|
120
|
+
] as const) {
|
|
121
|
+
if (named.has(row)) continue
|
|
122
|
+
throw new Error(
|
|
123
|
+
`\`${animation.id}\` ${what} \`${row}\`, and the style guide holds no row by that name. ` +
|
|
124
|
+
'How long an animation takes and how it is paced are style guide values, named here rather than written.',
|
|
125
|
+
)
|
|
126
|
+
}
|
|
127
|
+
return [
|
|
128
|
+
keyframeName(animation.id),
|
|
129
|
+
`var(--${animation.runs})`,
|
|
130
|
+
`var(--${animation.eases})`,
|
|
131
|
+
...(animation.repeats === undefined ? [] : [String(animation.repeats)]),
|
|
132
|
+
...(animation.fills === undefined ? [] : [animation.fills]),
|
|
133
|
+
].join(' ')
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
const one = (id: string, describes: string): Threaded => ({
|
|
137
|
+
id,
|
|
138
|
+
at: `thread/ui/animations/anm_${id.replace(/-/g, '_')}.ts`,
|
|
139
|
+
describes,
|
|
140
|
+
version: 1,
|
|
141
|
+
})
|
|
142
|
+
|
|
143
|
+
export const declares: readonly Threaded[] = [
|
|
144
|
+
one('press', 'A thing shrinking very slightly and coming back, which is what makes a press feel like it landed rather than like a value changed.'),
|
|
145
|
+
one('appear', 'Something arriving in place — faded up from nothing, without moving, for a thing that was always going to be there.'),
|
|
146
|
+
one('dismiss', 'Something leaving — faded away and shrunk a little, so the eye is not asked to follow it out.'),
|
|
147
|
+
one('rise', 'Something arriving from below, faded up as it comes. What a sheet, a toast or a menu does on its way in.'),
|
|
148
|
+
one('fall', 'Something leaving downward, the exit that answers a rise.'),
|
|
149
|
+
one('pulse', 'A thing breathing in place, repeated without end, for something waiting rather than working.'),
|
|
150
|
+
one('spin', 'A full turn, repeated without end. What says a thing is working and cannot say how far along it is.'),
|
|
151
|
+
one('drop', 'Something arriving from above, faded in as it comes. The entrance that answers a rise.'),
|
|
152
|
+
one('slide-in-left', 'Something coming in from the left, for a panel or a step arriving from off-stage rather than growing in place.'),
|
|
153
|
+
one('slide-in-right', 'Something coming in from the right.'),
|
|
154
|
+
one('slide-out-left', 'Something leaving to the left.'),
|
|
155
|
+
one('slide-out-right', 'Something leaving to the right.'),
|
|
156
|
+
one('zoom-in', 'Something arriving from nearer the reader and settling back to its true size.'),
|
|
157
|
+
one('zoom-out', 'Something leaving toward the reader as it fades.'),
|
|
158
|
+
one('pop-in', 'Something arriving past its own size and coming back — the overshoot is what reads as physical rather than as a value changing.'),
|
|
159
|
+
one('pop-out', 'Something swelling once before it goes, the exit that answers a pop.'),
|
|
160
|
+
one('blur-in', 'Something sharpening into place out of softness, without moving at all.'),
|
|
161
|
+
one('blur-out', 'Something dissolving rather than leaving.'),
|
|
162
|
+
one('lift', 'A thing raised a little off the page while it is pointed at. The plainest way a surface says it is reachable.'),
|
|
163
|
+
one('swell', 'A thing grown very slightly while pointed at, for where lifting it would move its neighbours.'),
|
|
164
|
+
one('nudge', 'A marker leaning the way it would take you, played while the wording beside it is pointed at.'),
|
|
165
|
+
one('wobble', 'A thing turned one way and back to be noticed. Says look at me without saying something is wrong.'),
|
|
166
|
+
one('flash', 'A thing dipping and returning, for a value that changed underneath the reader.'),
|
|
167
|
+
one('glow', 'The light around a thing swelling and settling, for something lit rather than framed.'),
|
|
168
|
+
one('shake', 'A short refusal — a thing pushed off centre and back twice, for an answer that was not accepted.'),
|
|
169
|
+
]
|
|
170
|
+
|
|
171
|
+
export const ANIMATIONS = {
|
|
172
|
+
id: 'animation',
|
|
173
|
+
at: here,
|
|
174
|
+
files: 'ui/anm_*.ts',
|
|
175
|
+
describes:
|
|
176
|
+
'Every animation an app may play, and the only place one may be brought into existence. A row names a file prefixed `anm_`, and that file is the animation: the stops it moves through, how long it takes and how it is paced — all of it naming style guide rows rather than holding values, so an animation moves when the rows move. Naming one is the whole of using one: a variant that plays `press` says `press`, and states no duration, no easing and no keyframes. The style guide picks which animation is the default for a thing, the way it picks every other value. A file under animations that no row names is not an animation but a file somebody wrote.',
|
|
177
|
+
version: 1,
|
|
178
|
+
cites: [
|
|
179
|
+
{
|
|
180
|
+
on: '2026-08-28',
|
|
181
|
+
said: 'Animations.ts, declares rows (through threaded) that names animations files at a path, with a prefix anm_ or ani_ or we do motions.ts with mot_, don\'t know. These define a bunch of animations that style guide will pick for a bunch of things as defaults. Like these template animations we see in video editors, we just name them and it\'s done.',
|
|
182
|
+
},
|
|
183
|
+
],
|
|
184
|
+
declares,
|
|
185
|
+
} satisfies Threaded & { readonly files: string; readonly declares: readonly Threaded[] }
|
|
186
|
+
|
|
187
|
+
export function nameOf(id: string): string {
|
|
188
|
+
return id.replace(/-/g, '_').toUpperCase()
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
function starter(row: Threaded): string {
|
|
192
|
+
return `import type { Animation } from './animations.ts'
|
|
193
|
+
|
|
194
|
+
export const ${nameOf(row.id)} = {
|
|
195
|
+
id: '${row.id}',
|
|
196
|
+
runs: 'motion-normal',
|
|
197
|
+
eases: 'ease-out',
|
|
198
|
+
stops: {},
|
|
199
|
+
} as const satisfies Animation
|
|
200
|
+
`
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
export function seeds(
|
|
204
|
+
rows: readonly Threaded[],
|
|
205
|
+
has: (at: string) => boolean,
|
|
206
|
+
): readonly { readonly path: string; readonly content: string }[] {
|
|
207
|
+
return rows.filter((row) => !has(row.at)).map((row) => ({ path: row.at, content: starter(row) }))
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
export async function mismatched(
|
|
211
|
+
rows: readonly Threaded[],
|
|
212
|
+
loader: (row: Threaded) => Promise<{ readonly id?: string } | undefined>,
|
|
213
|
+
): Promise<readonly string[]> {
|
|
214
|
+
const found: string[] = []
|
|
215
|
+
for (const row of rows) {
|
|
216
|
+
const animation = await loader(row)
|
|
217
|
+
if (animation === undefined) {
|
|
218
|
+
found.push(
|
|
219
|
+
`\`${row.id}\` is declared at \`${row.at}\`, and that file exports no \`${nameOf(row.id)}\`. ` +
|
|
220
|
+
'A row names a file, and the file is the thing.',
|
|
221
|
+
)
|
|
222
|
+
continue
|
|
223
|
+
}
|
|
224
|
+
if (animation.id !== row.id) {
|
|
225
|
+
found.push(
|
|
226
|
+
`\`${row.at}\` declares itself \`${String(animation.id)}\`, and the row that names it calls it \`${row.id}\`. ` +
|
|
227
|
+
"An animation file and the row that names it have to be the same animation — make the file's `id` match the row, or fix the row.",
|
|
228
|
+
)
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
return found
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
function extensionOf(path: string): string {
|
|
235
|
+
const name = basename(path)
|
|
236
|
+
const at = name.lastIndexOf('.')
|
|
237
|
+
return at === -1 ? '' : name.slice(at)
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
const ANM_PREFIX = /^anm_/
|
|
241
|
+
|
|
242
|
+
export function orphaned(rows: readonly Threaded[], strays: readonly string[]): readonly string[] {
|
|
243
|
+
const named = new Set(rows.map((row) => row.at))
|
|
244
|
+
return strays
|
|
245
|
+
.filter((one) => !named.has(one))
|
|
246
|
+
.map(
|
|
247
|
+
(one) =>
|
|
248
|
+
`\`${one}\` is under animations and no row names it — not a animation, a file somebody wrote. ` +
|
|
249
|
+
'Add a row for it, or delete the file.',
|
|
250
|
+
)
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
async function run(): Promise<number> {
|
|
254
|
+
const tree = findTree()
|
|
255
|
+
const at = inTree(tree, 'animations.ts')
|
|
256
|
+
if (!existsSync(at)) {
|
|
257
|
+
stdout.write(`thread: this tree has no ${tree.name}/${basename(at)}, so it declares no animation and there is nothing to seed or check.\n`)
|
|
258
|
+
return 0
|
|
259
|
+
}
|
|
260
|
+
const sheet = (await import(pathToFileURL(at).href)) as {
|
|
261
|
+
ANIMATION?: { readonly declares?: readonly Threaded[] }
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
if (sheet.ANIMATION === undefined) {
|
|
265
|
+
stdout.write(`thread: ${tree.name}/animations.ts exports no \`ANIMATION\`, so nothing declares an animation here.\n`)
|
|
266
|
+
return 1
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
const rows = sheet.ANIMATION.declares ?? []
|
|
270
|
+
const checking = argv.includes('--check')
|
|
271
|
+
const exists = (where: string): boolean => existsSync(join(tree.at, where))
|
|
272
|
+
const made = seeds(rows, exists)
|
|
273
|
+
|
|
274
|
+
if (checking) {
|
|
275
|
+
const unseeded = rows.find((row) => !exists(row.at))
|
|
276
|
+
if (unseeded !== undefined) {
|
|
277
|
+
stdout.write(
|
|
278
|
+
`thread: \`${unseeded.id}\` is declared at \`${unseeded.at}\`, and the file is not there yet. ` +
|
|
279
|
+
'A row names a file, and `--check` never writes one — run `thread animations` and commit what it seeds.\n',
|
|
280
|
+
)
|
|
281
|
+
return 1
|
|
282
|
+
}
|
|
283
|
+
} else {
|
|
284
|
+
for (const seed of made) {
|
|
285
|
+
writeFileSync(join(tree.at, seed.path), seed.content, 'utf8')
|
|
286
|
+
stdout.write(`thread: seeded ${seed.path}\n`)
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
const wrong = (
|
|
291
|
+
await mismatched(rows, async (row) => {
|
|
292
|
+
const module = (await import(pathToFileURL(join(tree.at, row.at)).href)) as Record<string, unknown>
|
|
293
|
+
return module[nameOf(row.id)] as { readonly id?: string } | undefined
|
|
294
|
+
})
|
|
295
|
+
)[0]
|
|
296
|
+
if (wrong !== undefined) {
|
|
297
|
+
stdout.write(`thread: ${wrong}\n`)
|
|
298
|
+
return 1
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
const stray = orphaned(rows, strayFiles(tree.at, 'anm_', '.' + 'ts'))[0]
|
|
302
|
+
if (stray !== undefined) {
|
|
303
|
+
stdout.write(`thread: ${stray}\n`)
|
|
304
|
+
return 1
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
stdout.write(
|
|
308
|
+
checking
|
|
309
|
+
? `thread: ${String(rows.length)} animation(s) declared, all seeded\n`
|
|
310
|
+
: `thread: ${String(rows.length)} animation(s) declared, ${String(made.length)} seeded, ` +
|
|
311
|
+
`${String(rows.length - made.length)} already written\n`,
|
|
312
|
+
)
|
|
313
|
+
return 0
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
if (invoked(import.meta.url)) exit(await run())
|
package/dist/ui/attach.js
CHANGED
|
@@ -62,12 +62,6 @@ const SHIPPED = [
|
|
|
62
62
|
id: 'dev-generator',
|
|
63
63
|
source: " {\n id: 'dev-generator',\n at: 'node_modules/@prestack/thread-ui/dist/ui/generators/dev.js',\n describes: 'The command that runs the app, and the only one here that puts a pixel on a screen — until it existed the framework could build an interface nothing in it could start, because a gate answers whether declarations agree with files and cannot answer whether a button is where somebody says it is not. It is two halves in one word. First it settles this tree: every generator the declarations name is run until the checks stop complaining, which is what `thread create` and `thread run trial` already did and what starting the app was alone in not doing — an app had `thread gate` in front of `next dev`, and a gate refuses a stale file rather than writing it. Then it runs the entry the manifest declares, which is where the gate hook fires and Next is reached. Neither half is written down here: the generators are read off the rows, and `next dev` appears nowhere, so a package attached tomorrow is run by this without this changing and an app served by something else needs no edit. `--ship` swaps the entry for the build. Alone among the generators it carries no `runs:`, because a server stays up until it is stopped and a gate step that never returns is a gate that hangs.',\n version: 1,\n by: 'thread dev',\n },",
|
|
64
64
|
},
|
|
65
|
-
{
|
|
66
|
-
into: 'ui.ts',
|
|
67
|
-
list: 'GENERATORS',
|
|
68
|
-
id: 'message-maker',
|
|
69
|
-
source: " {\n id: 'message-maker',\n at: 'node_modules/@prestack/thread-ui/dist/ui/makers/message.js',\n describes: 'The maker of a message — asked for in every declared language at the moment the key is made, and refused while any answer is blank, so a key cannot be brought into existence half translated. It is also the only safe way to rename one: a rename moves the wording in every language and rewrites every file in the interface that names it, in one run, where doing it by hand leaves a key half moved and one sentence missing at run time.',\n version: 1,\n by: 'thread create message',\n },",
|
|
70
|
-
},
|
|
71
65
|
{
|
|
72
66
|
into: 'ui.ts',
|
|
73
67
|
list: 'GENERATORS',
|
|
@@ -102,7 +96,7 @@ const SHIPPED = [
|
|
|
102
96
|
into: 'ui.ts',
|
|
103
97
|
list: 'GENERATORS',
|
|
104
98
|
id: 'compositions-generator',
|
|
105
|
-
source: " {\n id: 'compositions-generator',\n at: 'node_modules/@prestack/thread-ui/dist/ui/compositions.js',\n describes: 'The seeder of composition files — it reads the compositions an app declares and writes the `cmp_` file each row names when that file is not there yet, so a composition begins by being declared rather than by somebody remembering to add a file beside the row. Run with `--check`, the mode the gate runs it in, it writes nothing and refuses instead: a row whose file is absent, a `cmp_` file no row names, and an import that is not a declared element, a declared composition or the door — so a composition that did not come from the CLI, or that reaches something nothing declares, stops the build rather than rendering.',\n version: 1,\n by: 'thread compositions',\n runs: 'thread compositions --check',\n },",
|
|
99
|
+
source: " {\n id: 'compositions-generator',\n at: 'node_modules/@prestack/thread-ui/dist/ui/compositions.js',\n describes: 'The seeder of composition files — it reads the compositions an app declares and writes the `cmp_` file each row names when that file is not there yet, so a composition begins by being declared rather than by somebody remembering to add a file beside the row. Run with `--check`, the mode the gate runs it in, it writes nothing and refuses instead: a row whose file is absent, a `cmp_` file no row names, and an import that is not a declared element, a declared composition or the door — so a composition that did not come from the CLI, or that reaches something nothing declares, stops the build rather than rendering. It also writes each of them compiled, into `app/_generated/compiled` — a variant worn as a tag becomes the class it resolves to and a named place becomes the class its arrangement declares, so a page imports a file this framework wrote rather than one a bundler plugin rewrites on the way past.',\n version: 1,\n by: 'thread compositions',\n runs: 'thread compositions --check',\n derives: [\n {\n id: 'compiled',\n at: 'app/_generated/compiled',\n describes: 'Every composition and screen as React runs them, written from the files a person wrote: a variant worn as a tag has become the class it resolves to, and a named place has become the class its arrangement declares. Nobody edits anything here, and nothing reads the sources at run time — this is what a page imports, which is why no compiler of this framework has to be installed for the app to build.',\n version: 1,\n by: 'thread compositions',\n runs: 'thread compositions --check',\n },\n ],\n },",
|
|
106
100
|
},
|
|
107
101
|
{
|
|
108
102
|
into: 'ui.ts',
|
|
@@ -222,7 +216,7 @@ function quoted(value) {
|
|
|
222
216
|
return `'${value.replace(/\\/g, '\\\\').replace(/'/g, "\\'")}'`;
|
|
223
217
|
}
|
|
224
218
|
function installed(at) {
|
|
225
|
-
return at.replace(/^thread\/
|
|
219
|
+
return at.replace(/^thread\/(.*)\.tsx?$/, `${ATTACH_HOME}/$1.js`);
|
|
226
220
|
}
|
|
227
221
|
function literal(value) {
|
|
228
222
|
return typeof value === 'number' ? String(value) : quoted(value);
|
|
@@ -246,7 +240,7 @@ function themed(held) {
|
|
|
246
240
|
return `{ ${each.join(', ')} }`;
|
|
247
241
|
}
|
|
248
242
|
const WRITTEN = new Set([
|
|
249
|
-
'id', 'at', 'describes', 'version', 'derives', 'holds',
|
|
243
|
+
'id', 'at', 'describes', 'version', 'derives', 'holds', 'runs',
|
|
250
244
|
'section', 'token', 'is', 'asks', 'sets', 'modes', 'places', 'themes', 'wears', 'selector',
|
|
251
245
|
'arranges', 'plays',
|
|
252
246
|
]);
|
|
@@ -309,6 +303,7 @@ function row(one, file, sheet) {
|
|
|
309
303
|
' ],',
|
|
310
304
|
]),
|
|
311
305
|
...(held ? [] : [` at: ${quoted(installed(one.at ?? ''))},`]),
|
|
306
|
+
...(one.runs === undefined ? [] : [` runs: ${quoted(one.runs.split(' --own').join(''))},`]),
|
|
312
307
|
` describes: ${quoted(one.describes)},`,
|
|
313
308
|
' version: 1,',
|
|
314
309
|
' },',
|
|
@@ -320,8 +315,13 @@ const ARRANGES = {
|
|
|
320
315
|
styleguide: ['themes'],
|
|
321
316
|
element: ['tag', 'raises', 'carries', 'always', 'places', 'works', 'measures', 'reads', 'gives', 'keeps', 'animates', 'shows', 'called', 'wires', 'dresses'],
|
|
322
317
|
};
|
|
318
|
+
const TEACHING = {
|
|
319
|
+
composition: `export { TEACHES, catalogue } from '${named('ui')}/compositions'`,
|
|
320
|
+
screen: `export { TEACHES, screening as catalogue } from '${named('ui')}/compositions'`,
|
|
321
|
+
};
|
|
323
322
|
function source(node, file, rows) {
|
|
324
323
|
const extra = stated(rows, ARRANGES[node.id] ?? []).map((key) => SHAPES[key] ?? '');
|
|
324
|
+
const taught = TEACHING[node.id];
|
|
325
325
|
const authors = node.declares !== undefined || rows.some((one) => one.at !== undefined && one.at !== node.at);
|
|
326
326
|
const values = node.holds !== undefined || rows.some((one) => one.at === undefined || one.at === node.at);
|
|
327
327
|
const own = node.id
|
|
@@ -356,7 +356,7 @@ export const ${node.id.toUpperCase()}: ${bespoke ? own : 'Threaded'} = {
|
|
|
356
356
|
version: 1,
|
|
357
357
|
${held.join('\n')}
|
|
358
358
|
}
|
|
359
|
-
`;
|
|
359
|
+
${taught === undefined ? '' : `\n${taught}\n`}`;
|
|
360
360
|
}
|
|
361
361
|
const LISTS = [
|
|
362
362
|
{ node: TOKENS, file: 'tokens.ts' },
|
|
@@ -463,11 +463,14 @@ export const ATTACH = {
|
|
|
463
463
|
attaches: SEEDED.attaches,
|
|
464
464
|
id: SEEDED.id,
|
|
465
465
|
needs: {
|
|
466
|
+
'@floating-ui/dom': '^1.7.0',
|
|
467
|
+
'@floating-ui/react-dom': '^2.1.0',
|
|
466
468
|
'@types/react': '^19.0.0',
|
|
467
469
|
'@types/react-dom': '^19.0.0',
|
|
468
470
|
next: '^16.0.0',
|
|
469
471
|
react: '^19.0.0',
|
|
470
472
|
'react-dom': '^19.0.0',
|
|
473
|
+
zod: '^4.5.4',
|
|
471
474
|
},
|
|
472
475
|
rows: [
|
|
473
476
|
...CONCEPTS.map((one) => ({
|