lecodes-sdk 2.0.3 → 2.0.5
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/README.md +104 -76
- package/dist/global.d.ts +3 -5
- package/dist/host.d.ts +3 -0
- package/dist/types/inject.d.ts +4 -4
- package/dist/types/net/codec.d.ts +3 -2
- package/dist/types/net/core.d.ts +10 -1
- package/dist/types/net/index.d.ts +22 -5
- package/dist/types/net/replication.d.ts +23 -2
- package/dist/types/runtime/device.d.ts +7 -0
- package/dist/types/runtime/rpc.d.ts +11 -17
- package/dist/types/runtime/wire.d.ts +53 -0
- package/dist/types/server/auth/api.d.ts +42 -0
- package/dist/types/server/auth/appConfig.d.ts +1 -5
- package/dist/types/server/auth/models.d.ts +119 -70
- package/dist/types/server/auth/types.d.ts +19 -43
- package/dist/types/server/channel.d.ts +57 -19
- package/dist/types/server/context.d.ts +2 -2
- package/dist/types/server/db/defineDb.d.ts +10 -0
- package/dist/types/server/db/index.d.ts +1 -1
- package/dist/types/server/db/types.d.ts +76 -6
- package/dist/types/server/inject.d.ts +0 -1
- package/dist/types/ui/UINode.d.ts +19 -5
- package/dist/types/ui/UIScreen.d.ts +1 -0
- package/dist/types/ui/UITabs.d.ts +8 -6
- package/dist/types/ui/theme.d.ts +48 -13
- package/dist/types/version.d.ts +1 -1
- package/dist/types.json +1 -1
- package/package.json +4 -2
- package/prompts/README.md +1 -1
- package/prompts/design.md +19 -19
- package/prompts/dist/2d-game.md +45 -31
- package/prompts/dist/3d-app.md +45 -31
- package/prompts/dist/ar-app.md +45 -31
- package/prompts/dist/design.md +25 -24
- package/prompts/dist/ui-app.md +45 -31
- package/prompts/ui-design.md +6 -5
- package/prompts/ui.md +25 -22
- package/src/animate/tween/read.ts +146 -0
- package/src/bridges/device.d.ts +9 -0
- package/src/bridges/tree.d.ts +5 -0
- package/src/canvas/gen/cssColor.ts +1 -1
- package/src/canvas/gen/recorder.ts +1 -1
- package/src/canvas/gen/spec.ts +1 -1
- package/src/chisel.ts +1 -1
- package/src/compile/bundler.ts +6 -0
- package/src/compile/compileProject.ts +3 -1
- package/src/compile/index.ts +3 -1
- package/src/compile/serverSplit.ts +58 -11
- package/src/compile/serverTypes.ts +189 -8
- package/src/host.d.ts +3 -0
- package/src/inject.ts +7 -7
- package/src/net/codec.ts +19 -10
- package/src/net/core.ts +18 -5
- package/src/net/index.ts +30 -9
- package/src/net/replication.ts +63 -28
- package/src/runtime/device.ts +12 -0
- package/src/runtime/rpc.ts +101 -40
- package/src/runtime/wire.ts +35 -0
- package/src/server/auth/api.ts +94 -0
- package/src/server/auth/appConfig.ts +2 -3
- package/src/server/auth/host.ts +244 -174
- package/src/server/auth/models.ts +45 -62
- package/src/server/auth/types.ts +19 -34
- package/src/server/channel.ts +97 -29
- package/src/server/channelHub.ts +153 -0
- package/src/server/context.ts +2 -2
- package/src/server/db/defineDb.ts +96 -36
- package/src/server/db/index.ts +1 -1
- package/src/server/db/types.ts +76 -8
- package/src/server/host.ts +25 -10
- package/src/server/inject.ts +2 -2
- package/src/server/runtime.ts +34 -12
- package/src/ui/UINode.ts +22 -5
- package/src/ui/UIScreen.ts +5 -0
- package/src/ui/UITabs.ts +19 -17
- package/src/ui/styleColor.ts +10 -1
- package/src/ui/theme.ts +96 -41
- package/src/version.ts +1 -1
- package/tests/helpers/fakeTree.ts +1 -0
- package/dist/types/plugins/oauth.d.ts +0 -25
- package/dist/types/server/auth/global.d.ts +0 -56
- package/src/plugins/oauth.ts +0 -61
- package/src/server/auth/global.ts +0 -80
- package/tests/helpers/memoryMarci.ts +0 -124
package/prompts/dist/design.md
CHANGED
|
@@ -191,7 +191,7 @@ const label: Style<UIText> = { fontSize: 12, fontWeight: 600, letterSpacing: 2 }
|
|
|
191
191
|
// Padding: p, px, py, pt, pb, pl, pr (≡ padding, paddingHorizontal, paddingVertical, paddingTop, ...)
|
|
192
192
|
// Margin: m, mx, my, mt, mb, ml, mr — margins also accept "auto" (mx: "auto" centers a fixed-width element)
|
|
193
193
|
// Background (containers/screen/button; UIText/UIImage/UISpacer have bgColor only):
|
|
194
|
-
// bgColor, bgImage (string | imported asset), bgSize ("cover"|"contain"|"tile"), bgGradient
|
|
194
|
+
// bgColor, bgImage (string | imported asset), bgSize ("cover"|"contain"|"fill"|"tile"), bgGradient
|
|
195
195
|
// bgGradient: a CSS linear-gradient() or radial-gradient() string, e.g. "linear-gradient(to top, rgba(0,0,0,0.7), transparent)".
|
|
196
196
|
// Layers paint bottom-to-top: bgColor → bgImage → bgGradient (gradient over image = text scrim).
|
|
197
197
|
// bgImage is decoration BEHIND children — an image that IS the content belongs in UIImage.
|
|
@@ -221,10 +221,11 @@ const label: Style<UIText> = { fontSize: 12, fontWeight: 600, letterSpacing: 2 }
|
|
|
221
221
|
|
|
222
222
|
// --- Theme variables ---
|
|
223
223
|
// theme({...}) merges app vars into one table; styles read them with "var(--name)" (numbers are px):
|
|
224
|
-
const T = theme({
|
|
225
|
-
label.style({ color: T.
|
|
226
|
-
//
|
|
227
|
-
//
|
|
224
|
+
const T = theme({ accent: "#0A84FF" })
|
|
225
|
+
label.style({ color: T.accent }) // or "var(--accent)"
|
|
226
|
+
// ROLES the SDK reads itself: bg (screens) · surface / border (the tab bar) · text / fontFamily
|
|
227
|
+
// (text that names none) · textMuted (inactive tabs) · accent (the active tab). "var(--name,
|
|
228
|
+
// fallback)" applies the fallback while the key is unset.
|
|
228
229
|
|
|
229
230
|
// --- Defaults that surprise ---
|
|
230
231
|
// flexShrink: 0 — elements don't shrink to fit (exception: UIScrollable defaults flexShrink: 1 so it scrolls
|
|
@@ -327,20 +328,20 @@ composition:
|
|
|
327
328
|
|
|
328
329
|
```ts
|
|
329
330
|
// design/shared/tokens.ts — surfaces dark-to-light, text in tiers, accents as a family.
|
|
330
|
-
// ONE theme() call registers the vars and returns the accessors ("var(--x)" strings)
|
|
331
|
-
//
|
|
332
|
-
//
|
|
333
|
-
const
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
331
|
+
// ONE theme() call registers the vars and returns the accessors ("var(--x)" strings). Re-calling
|
|
332
|
+
// theme() with new values (dark mode, brand swap) restyles every screen — and later the built app
|
|
333
|
+
// — live.
|
|
334
|
+
export const colors = theme({
|
|
335
|
+
// the ROLES — the SDK reads these too: bg = every screen's background, text = text that names
|
|
336
|
+
// no color, surface / border / textMuted / accent = the tab bar (defineTabs here, UITabs in the
|
|
337
|
+
// built app). Name the palette by them; there are no separate keys for the chrome.
|
|
338
|
+
bg: "#080808", surface: "#161618", border: "#2a2a2e",
|
|
339
|
+
text: "#ffffff", textMuted: "#8a8a93", accent: "#00BFC2",
|
|
340
|
+
// the design's own
|
|
341
|
+
surfaceAlt: "#1d1d20", field: "#101012", faint: "#5a5b62",
|
|
342
|
+
accentPressed: "#00999b", accentDeep: "#0c4e4f", onAccent: "#05201f",
|
|
337
343
|
success: "#30D158", danger: "#F83131", scrim: "#000000b3",
|
|
338
|
-
|
|
339
|
-
// the other four brand the tab bar (defineTabs here, UITabs in the built app). Mirror the brand:
|
|
340
|
-
color: "#ffffff",
|
|
341
|
-
primaryColor: "#00BFC2", mutedColor: "#8a8a93", tabbarBg: "#161618", screenBg: "#080808",
|
|
342
|
-
}
|
|
343
|
-
export const colors: { [K in keyof typeof palette]: string } = theme(palette)
|
|
344
|
+
})
|
|
344
345
|
export const spacing = { xs: 4, sm: 8, md: 12, lg: 16, xl: 20, xxl: 24 }
|
|
345
346
|
export const radius = { sm: 8, md: 12, lg: 16, pill: 999 }
|
|
346
347
|
```
|
|
@@ -350,7 +351,7 @@ export const radius = { sm: 8, md: 12, lg: 16, pill: 999 }
|
|
|
350
351
|
import { colors, spacing, radius } from "./tokens"
|
|
351
352
|
|
|
352
353
|
export const PrimaryButton = (label: string, o: { name?: string, disabled?: boolean } = {}) =>
|
|
353
|
-
UIButton(UIText(label).style({ fontSize: 16, fontWeight: 700, color: o.disabled ? colors.
|
|
354
|
+
UIButton(UIText(label).style({ fontSize: 16, fontWeight: 700, color: o.disabled ? colors.textMuted : colors.onAccent }))
|
|
354
355
|
.style({ height: 52, borderRadius: radius.md, bgColor: o.disabled ? colors.accentDeep : colors.accent,
|
|
355
356
|
justifyContent: "center", alignItems: "center", name: o.name, $pressed: { bgColor: colors.accentPressed } })
|
|
356
357
|
|
|
@@ -361,7 +362,7 @@ export const SectionLabel = (t: string) =>
|
|
|
361
362
|
export const Field = (o: { label?: string, value?: string, placeholder?: string, focused?: boolean }) =>
|
|
362
363
|
UIColumn(
|
|
363
364
|
o.label ? SectionLabel(o.label) : null,
|
|
364
|
-
UIRow(UIText(o.value ?? o.placeholder ?? "").style({ flexGrow: 1, fontSize: 16, color: o.value ? colors.text : colors.
|
|
365
|
+
UIRow(UIText(o.value ?? o.placeholder ?? "").style({ flexGrow: 1, fontSize: 16, color: o.value ? colors.text : colors.textMuted }))
|
|
365
366
|
.style({ height: 52, px: 16, alignItems: "center", bgColor: colors.field, borderRadius: radius.md,
|
|
366
367
|
border: `1px solid ${o.focused ? colors.accent : colors.border}` }),
|
|
367
368
|
).style({ gap: spacing.sm })
|
|
@@ -380,9 +381,9 @@ export const ConfirmDialog = (o: { title: string, body?: string, confirmLabel: s
|
|
|
380
381
|
UIWidget(
|
|
381
382
|
UIColumn(
|
|
382
383
|
UIText(o.title).style({ fontSize: 17, fontWeight: 700, color: colors.text, textAlign: "center" }),
|
|
383
|
-
o.body ? UIText(o.body).style({ fontSize: 14, color: colors.
|
|
384
|
+
o.body ? UIText(o.body).style({ fontSize: 14, color: colors.textMuted, textAlign: "center", lineHeight: "1.4em" }) : null,
|
|
384
385
|
PrimaryButton("Cancel", { name: "cancel" }),
|
|
385
|
-
).style({ bgColor: colors.
|
|
386
|
+
).style({ bgColor: colors.surface, borderRadius: radius.lg, p: spacing.xl, gap: spacing.lg, width: "86%", maxWidth: 360 }),
|
|
386
387
|
).style({ top: 0, left: 0, right: 0, bottom: 0, justifyContent: "center", alignItems: "center", overlayColor: colors.scrim })
|
|
387
388
|
```
|
|
388
389
|
|
|
@@ -479,8 +480,8 @@ export const mainTabs = defineTabs({
|
|
|
479
480
|
A screen that shows the bar mounts it with a literal tab id — roots AND pushed details that keep
|
|
480
481
|
the bar; a screen that doesn't mount it is a full-screen push. Mount the bar alone when the screen
|
|
481
482
|
owns its layout (`mainTabs.bar("schedule")`, as above) or wrap content with
|
|
482
|
-
`mainTabs.screen("home", [...])`.
|
|
483
|
-
`
|
|
483
|
+
`mainTabs.screen("home", [...])`. It is colored by the theme's roles — `accent`,
|
|
484
|
+
`textMuted`, `surface`, `border`, `bg`, set in `shared/tokens.ts` — never by reimplementing the
|
|
484
485
|
bar. Keep the `defineTabs({...})` declaration and `.screen("<tab>", ...)` / `.bar("<tab>")` call
|
|
485
486
|
shapes intact — the board parses them.
|
|
486
487
|
|
package/prompts/dist/ui-app.md
CHANGED
|
@@ -403,8 +403,8 @@ Router.init(tabs) // UITabs IS a UIScreen — present it directly
|
|
|
403
403
|
// .select(id), .tab (getter), .onSelect(cb(id, i)) — fires on a bar tap, swipe, or select()
|
|
404
404
|
// .badge(id, value) — true = dot, number/string = count pill, false/null/0 clears
|
|
405
405
|
// .pager — the UIPager underneath; UIPager.push(detail) from any screen keeps the bar
|
|
406
|
-
//
|
|
407
|
-
//
|
|
406
|
+
// The bar is colored by the theme's roles (accent, textMuted, surface, border) — no keys of its
|
|
407
|
+
// own; one bar's own colors: tabs.bar.theme({ accent, surface }). Custom layout: UIPager below.
|
|
408
408
|
|
|
409
409
|
// UIPager — the navigation primitive under UITabs: sibling tabs that swipe natively, each tab its
|
|
410
410
|
// OWN push/pop stack. Reach for it directly for a plain stack (one-screen pager) or a fully custom
|
|
@@ -620,7 +620,7 @@ const btn: Style<UIButton> = { bgColor: "#333", borderRadius: 12 }
|
|
|
620
620
|
// Padding: p, px, py, pt, pb, pl, pr (≡ padding, paddingHorizontal, paddingVertical, paddingTop, ...)
|
|
621
621
|
// Margin: m, mx, my, mt, mb, ml, mr — margins also accept "auto" (mx: "auto" centers a fixed-width element)
|
|
622
622
|
// Background (containers/screen/button/input/video; UIText/UIImage/UISpacer have bgColor only):
|
|
623
|
-
// bgColor, bgImage (string | FetchResponse | File), bgSize ("cover"|"contain"|"tile"), bgGradient
|
|
623
|
+
// bgColor, bgImage (string | FetchResponse | File | SvgSource), bgSize ("cover"|"contain"|"fill"|"tile"), bgGradient
|
|
624
624
|
// bgGradient: a CSS linear-gradient() or radial-gradient() string, e.g. "linear-gradient(to top, rgba(0,0,0,0.7), transparent)"
|
|
625
625
|
// or "radial-gradient(circle at 50% 40%, #7B2FF7, #0a0a1a)". Comma-separate several gradients to stack them (first = on top).
|
|
626
626
|
// Layers paint bottom-to-top: bgColor → bgImage → bgGradient (gradient over image = text scrim).
|
|
@@ -647,23 +647,27 @@ const btn: Style<UIButton> = { bgColor: "#333", borderRadius: 12 }
|
|
|
647
647
|
// Env keywords resolving to the device insets (notch, home indicator):
|
|
648
648
|
// "safe-top" | "safe-bottom" | "safe-left" | "safe-right"; "safe-all" — p / m shorthands only
|
|
649
649
|
// Comfort tokens — where content comfortably starts, never collapse to 0: max(safe-edge, knob) on clearance
|
|
650
|
-
// insets (iOS notch/home indicator
|
|
651
|
-
// knob ADDS instead. Knob defaults: 12 top,
|
|
650
|
+
// insets (iOS notch/home indicator); past exact-height system BARS (Android status/nav bar) the
|
|
651
|
+
// knob ADDS instead. Knob defaults: 12 top, 8 bottom, 16 horizontal (the page gutter):
|
|
652
652
|
// "comfort-top" | "comfort-bottom" | "comfort-left" | "comfort-right"
|
|
653
653
|
// "comfort-x" — px/mx | "comfort-y" — py/my | "comfort-all" — p / m
|
|
654
654
|
// Accepted on per-side padding & margin, position offsets (top/left/bottom/right), and inside calc()/min()/max():
|
|
655
|
-
.style({ pb: "comfort-bottom" }) // bottom bar: 34 over a home indicator,
|
|
655
|
+
.style({ pb: "comfort-bottom" }) // bottom bar: 34 over a home indicator, 56 above Android's button bar, 8 on inset-less devices
|
|
656
656
|
.style({ px: "comfort-x" }) // page gutter that also clears the notch in landscape
|
|
657
657
|
.style({ pb: "calc(comfort-bottom + 56px)" }) // scroll content clearing a 56px floating bar
|
|
658
658
|
|
|
659
659
|
// --- Theme variables ---
|
|
660
660
|
// theme({...}) merges app vars into one table; styles read them with "var(--name)" (numbers are px
|
|
661
661
|
// lengths, null removes a key). Re-calling re-styles the LIVE UI — dark mode is a second call.
|
|
662
|
-
// Returned accessors ARE the var() strings
|
|
663
|
-
const T = theme({
|
|
664
|
-
label.style({ color: T.
|
|
665
|
-
//
|
|
666
|
-
//
|
|
662
|
+
// ONE call per app. Returned accessors ARE the var() strings:
|
|
663
|
+
const T = theme({ accent: "#0A84FF", "comfort-left": 20 })
|
|
664
|
+
label.style({ color: T.accent, pl: "var(--comfort-left)" })
|
|
665
|
+
// ROLES — keys the SDK reads itself; use them as your palette and add your own keys beside them:
|
|
666
|
+
// bg (every screen's background) · surface (cards, bars; the tab bar) · border (hairlines)
|
|
667
|
+
// text (text with no color of its own) · textMuted (secondary; inactive tabs) · accent (active tab)
|
|
668
|
+
// fontFamily (text with no font of its own). Light theme = set text once, not a color per label.
|
|
669
|
+
// SCOPE: el.theme({...}) — the same keys for ONE subtree (a card, tabs.bar, a whole screen); its
|
|
670
|
+
// descendants read them first. A pushed screen / a widget is its own root: give it its own.
|
|
667
671
|
// Bare comfort-* tokens apply the safe-area formula; "var(--comfort-top)" reads the raw knob.
|
|
668
672
|
// "var(--name, fallback)" applies the fallback while the key is unset. Env names (safe-*, vw/vh) are not theme keys.
|
|
669
673
|
|
|
@@ -695,7 +699,7 @@ screen.style({ flexDirection: "column", p: 16, $landscape: { flexDirection: "row
|
|
|
695
699
|
// alignItems: "stretch" — children FILL the cross axis by default; set a size or alignSelf to opt out.
|
|
696
700
|
// overflow: "hidden" — children are clipped to the parent's box; set overflow: "visible" to let them escape.
|
|
697
701
|
// boxSizing: "border-box" — width/height include padding & border.
|
|
698
|
-
// Screen bg is black, default text is WHITE at fontSize 14 (retarget once via theme({
|
|
702
|
+
// Screen bg is black, default text is WHITE at fontSize 14 (retarget once via theme({ bg, text, fontFamily })).
|
|
699
703
|
// On any light surface — white cards, sheets, inputs — white text renders invisible: use dark text there
|
|
700
704
|
// ("#111"/black), checking every UIText against the surface it actually sits on, not the screen.
|
|
701
705
|
|
|
@@ -769,14 +773,25 @@ UIButton().onLayout(({ width }) => { buttonWidth = width })
|
|
|
769
773
|
// ===== ROUTER (multi-page apps) =====
|
|
770
774
|
// Tabs are UITabs' job, in-tab stacks UIPager's (see UI COMPONENTS) — the Router owns what sits ABOVE the shell.
|
|
771
775
|
Router.init(homeScreen, opts?: { showDefaultBackButton?: boolean }) // call once in the entry file (default false)
|
|
772
|
-
Router.push(screen)
|
|
773
|
-
Router.pop(to?: number
|
|
776
|
+
Router.push(screen, opts?: { transition, popTransition }) // push onto the stack, screen becomes active (default "push")
|
|
777
|
+
Router.pop(to?: number, opts?: { transition })
|
|
778
|
+
// default -1 = one back; negative = relative (-2 = back two), 0/positive = absolute
|
|
774
779
|
// stack index (0 = home). Popped screens' native trees are destroyed; the UIScreen
|
|
775
|
-
// object stays reusable (push remounts it).
|
|
776
|
-
Router.replace(screen, opts?: { transition }) // swap the top screen
|
|
777
|
-
// "slide-from-right" | "slide-from-top" | "slide-from-bottom" | "zoom" | "zoom-out" | "zoom-in" | "fade" | "none" (default "fade")
|
|
780
|
+
// object stays reusable (push remounts it). Plays the way back the screen remembers.
|
|
781
|
+
Router.replace(screen, opts?: { transition, popTransition }) // swap the top screen (default "none"; theme({ replaceTransition }))
|
|
778
782
|
Router.current // the active UIScreen (getter)
|
|
779
|
-
Router.hide() / Router.restore() // hide/restore the whole router — stack stays alive; fires the top screen's onClose/onOpen
|
|
783
|
+
Router.hide(opts?) / Router.restore(opts?) // hide/restore the whole router — stack stays alive; fires the top screen's onClose/onOpen
|
|
784
|
+
// TRANSITIONS — every call above, and open() / close() of any destination, takes { transition }:
|
|
785
|
+
// a name: "push" | "pop" | "slide-from-left" | "slide-from-right" | "slide-from-top" | "slide-from-bottom" |
|
|
786
|
+
// "zoom" | "zoom-in" | "zoom-out" | "fade" | "none"
|
|
787
|
+
// or your own: { enter?: pose, exit?: pose, onTop?: "enter" | "exit" } — the incoming screen comes FROM `enter`,
|
|
788
|
+
// the outgoing one goes TO `exit`. pose = { transform?, opacity?, dim?, duration? (ms, 300), delay?, easing? };
|
|
789
|
+
// arrays are keyframes; in a pose a % in translate is a percent of the screen ("translateY(100%)").
|
|
790
|
+
// e.g. Router.push(page, { transition: { enter: { transform: "translateY(100%)", duration: 400 }, exit: { dim: 0.3 } } })
|
|
791
|
+
// popTransition = the way BACK the screen remembers (Router.pop(), the back gesture, the system's back play it).
|
|
792
|
+
// Default: the transition it came with, the other way round (a slide from the bottom leaves downwards;
|
|
793
|
+
// a zoom / a fade go back with a fade). A replace without one keeps the way back of the screen it replaces.
|
|
794
|
+
// A screen's own transform / opacity are not seen while a transition moves it — style a child instead.
|
|
780
795
|
Router.addEventListener("change", (screen: UIScreen) => ...) / .removeEventListener("change", cb) // after every navigation
|
|
781
796
|
// Screens BELOW the top stay mounted — element trees and state survive, they're just not rendered.
|
|
782
797
|
// push fires the outgoing screen's onClose + the incoming one's onOpen; pop fires them in reverse and
|
|
@@ -798,7 +813,7 @@ font("ibm-plex-sans", { weights: [400, 600], italic: true }) // narrow/extend t
|
|
|
798
813
|
// A project's own file: font("./fonts/Brand.ttf") → real family name read from the file itself
|
|
799
814
|
// (weight/italic too — no options); two weight files of one family return the same name.
|
|
800
815
|
// registerFont(family, url, {weight, style}) survives ONLY for runtime-computed URLs — rare.
|
|
801
|
-
//
|
|
816
|
+
// The app's font: theme({ fontFamily }). A second family is set per UIText (extract a shared Style<UIText>).
|
|
802
817
|
|
|
803
818
|
// ===== SVG IMAGES =====
|
|
804
819
|
// SvgSource wraps raw SVG XML for use as an image source: UIImage(SvgSource(`<svg ...>`)).
|
|
@@ -900,17 +915,16 @@ screen.open()
|
|
|
900
915
|
</file>
|
|
901
916
|
|
|
902
917
|
// === EXAMPLE 2: Theme tokens + fetched list with loading / error states ===
|
|
903
|
-
// A tokens module every screen imports, light-themed
|
|
904
|
-
//
|
|
918
|
+
// A tokens module every screen imports, light-themed. ONE theme() call: the roles (bg, surface,
|
|
919
|
+
// border, text, textMuted, accent) color the screens, the default text and the tab bar — no
|
|
920
|
+
// color: "#111" on every label — and the app's own keys sit beside them.
|
|
905
921
|
<file name="tokens.ts">
|
|
906
|
-
const palette = {
|
|
907
|
-
bg: "#F4F6F5", card: "#FFFFFF", border: "#E4E8E6",
|
|
908
|
-
text: "#131A17", muted: "#606B65",
|
|
909
|
-
accent: "#15A34A", accentSoft: "#E7F6ED", onAccent: "#FFFFFF",
|
|
910
|
-
}
|
|
911
|
-
theme({ color: palette.text, primaryColor: palette.accent })
|
|
912
922
|
// Accessors ARE "var(--x)" strings — re-calling theme() with new values restyles the live app.
|
|
913
|
-
export const colors
|
|
923
|
+
export const colors = theme({
|
|
924
|
+
bg: "#F4F6F5", surface: "#FFFFFF", border: "#E4E8E6",
|
|
925
|
+
text: "#131A17", textMuted: "#606B65", accent: "#15A34A",
|
|
926
|
+
accentSoft: "#E7F6ED", onAccent: "#FFFFFF",
|
|
927
|
+
})
|
|
914
928
|
export const font = { // type scale — spread into styles: .style({ ...font.h2 })
|
|
915
929
|
h2: { fontSize: 22, fontWeight: 700 }, bodyStrong: { fontSize: 16, fontWeight: 600 },
|
|
916
930
|
small: { fontSize: 14 }, tiny: { fontSize: 12, fontWeight: 500 },
|
|
@@ -927,9 +941,9 @@ const Row = (u: User) => UIRow(
|
|
|
927
941
|
justifyContent: "center", alignItems: "center" }),
|
|
928
942
|
UIColumn(
|
|
929
943
|
UIText(u.name).style({ ...font.bodyStrong }),
|
|
930
|
-
UIText(u.email).style({ ...font.small, color: colors.
|
|
944
|
+
UIText(u.email).style({ ...font.small, color: colors.textMuted }),
|
|
931
945
|
).style({ flexGrow: 1, flexShrink: 1, gap: 2, alignItems: "flex-start" }),
|
|
932
|
-
).style({ alignItems: "center", gap: 12, bgColor: colors.
|
|
946
|
+
).style({ alignItems: "center", gap: 12, bgColor: colors.surface, borderRadius: 16,
|
|
933
947
|
border: `1px solid ${colors.border}`, p: 14 })
|
|
934
948
|
|
|
935
949
|
const Centered = (...children: UINodeChild[]) =>
|
|
@@ -938,7 +952,7 @@ const Centered = (...children: UINodeChild[]) =>
|
|
|
938
952
|
let body: UIColumn
|
|
939
953
|
|
|
940
954
|
const loadData = async () => {
|
|
941
|
-
body.setContent([Centered(UIText("Loading…").style({ color: colors.
|
|
955
|
+
body.setContent([Centered(UIText("Loading…").style({ color: colors.textMuted }))])
|
|
942
956
|
const res = await fetch("https://jsonplaceholder.typicode.com/users")
|
|
943
957
|
if (res.status !== 200) {
|
|
944
958
|
body.setContent([Centered(
|
package/prompts/ui-design.md
CHANGED
|
@@ -92,7 +92,7 @@ const label: Style<UIText> = { fontSize: 12, fontWeight: 600, letterSpacing: 2 }
|
|
|
92
92
|
// Padding: p, px, py, pt, pb, pl, pr (≡ padding, paddingHorizontal, paddingVertical, paddingTop, ...)
|
|
93
93
|
// Margin: m, mx, my, mt, mb, ml, mr — margins also accept "auto" (mx: "auto" centers a fixed-width element)
|
|
94
94
|
// Background (containers/screen/button; UIText/UIImage/UISpacer have bgColor only):
|
|
95
|
-
// bgColor, bgImage (string | imported asset), bgSize ("cover"|"contain"|"tile"), bgGradient
|
|
95
|
+
// bgColor, bgImage (string | imported asset), bgSize ("cover"|"contain"|"fill"|"tile"), bgGradient
|
|
96
96
|
// bgGradient: a CSS linear-gradient() or radial-gradient() string, e.g. "linear-gradient(to top, rgba(0,0,0,0.7), transparent)".
|
|
97
97
|
// Layers paint bottom-to-top: bgColor → bgImage → bgGradient (gradient over image = text scrim).
|
|
98
98
|
// bgImage is decoration BEHIND children — an image that IS the content belongs in UIImage.
|
|
@@ -122,10 +122,11 @@ const label: Style<UIText> = { fontSize: 12, fontWeight: 600, letterSpacing: 2 }
|
|
|
122
122
|
|
|
123
123
|
// --- Theme variables ---
|
|
124
124
|
// theme({...}) merges app vars into one table; styles read them with "var(--name)" (numbers are px):
|
|
125
|
-
const T = theme({
|
|
126
|
-
label.style({ color: T.
|
|
127
|
-
//
|
|
128
|
-
//
|
|
125
|
+
const T = theme({ accent: "#0A84FF" })
|
|
126
|
+
label.style({ color: T.accent }) // or "var(--accent)"
|
|
127
|
+
// ROLES the SDK reads itself: bg (screens) · surface / border (the tab bar) · text / fontFamily
|
|
128
|
+
// (text that names none) · textMuted (inactive tabs) · accent (the active tab). "var(--name,
|
|
129
|
+
// fallback)" applies the fallback while the key is unset.
|
|
129
130
|
|
|
130
131
|
// --- Defaults that surprise ---
|
|
131
132
|
// flexShrink: 0 — elements don't shrink to fit (exception: UIScrollable defaults flexShrink: 1 so it scrolls
|
package/prompts/ui.md
CHANGED
|
@@ -42,8 +42,8 @@ Router.init(tabs) // UITabs IS a UIScreen — present it directly
|
|
|
42
42
|
// .select(id), .tab (getter), .onSelect(cb(id, i)) — fires on a bar tap, swipe, or select()
|
|
43
43
|
// .badge(id, value) — true = dot, number/string = count pill, false/null/0 clears
|
|
44
44
|
// .pager — the UIPager underneath; UIPager.push(detail) from any screen keeps the bar
|
|
45
|
-
//
|
|
46
|
-
//
|
|
45
|
+
// The bar is colored by the theme's roles (accent, textMuted, surface, border) — no keys of its
|
|
46
|
+
// own; one bar's own colors: tabs.bar.theme({ accent, surface }). Custom layout: UIPager below.
|
|
47
47
|
|
|
48
48
|
// UIPager — the navigation primitive under UITabs: sibling tabs that swipe natively, each tab its
|
|
49
49
|
// OWN push/pop stack. Reach for it directly for a plain stack (one-screen pager) or a fully custom
|
|
@@ -259,7 +259,7 @@ const btn: Style<UIButton> = { bgColor: "#333", borderRadius: 12 }
|
|
|
259
259
|
// Padding: p, px, py, pt, pb, pl, pr (≡ padding, paddingHorizontal, paddingVertical, paddingTop, ...)
|
|
260
260
|
// Margin: m, mx, my, mt, mb, ml, mr — margins also accept "auto" (mx: "auto" centers a fixed-width element)
|
|
261
261
|
// Background (containers/screen/button/input/video; UIText/UIImage/UISpacer have bgColor only):
|
|
262
|
-
// bgColor, bgImage (string | FetchResponse | File), bgSize ("cover"|"contain"|"tile"), bgGradient
|
|
262
|
+
// bgColor, bgImage (string | FetchResponse | File | SvgSource), bgSize ("cover"|"contain"|"fill"|"tile"), bgGradient
|
|
263
263
|
// bgGradient: a CSS linear-gradient() or radial-gradient() string, e.g. "linear-gradient(to top, rgba(0,0,0,0.7), transparent)"
|
|
264
264
|
// or "radial-gradient(circle at 50% 40%, #7B2FF7, #0a0a1a)". Comma-separate several gradients to stack them (first = on top).
|
|
265
265
|
// Layers paint bottom-to-top: bgColor → bgImage → bgGradient (gradient over image = text scrim).
|
|
@@ -298,11 +298,15 @@ const btn: Style<UIButton> = { bgColor: "#333", borderRadius: 12 }
|
|
|
298
298
|
// --- Theme variables ---
|
|
299
299
|
// theme({...}) merges app vars into one table; styles read them with "var(--name)" (numbers are px
|
|
300
300
|
// lengths, null removes a key). Re-calling re-styles the LIVE UI — dark mode is a second call.
|
|
301
|
-
// Returned accessors ARE the var() strings
|
|
302
|
-
const T = theme({
|
|
303
|
-
label.style({ color: T.
|
|
304
|
-
//
|
|
305
|
-
//
|
|
301
|
+
// ONE call per app. Returned accessors ARE the var() strings:
|
|
302
|
+
const T = theme({ accent: "#0A84FF", "comfort-left": 20 })
|
|
303
|
+
label.style({ color: T.accent, pl: "var(--comfort-left)" })
|
|
304
|
+
// ROLES — keys the SDK reads itself; use them as your palette and add your own keys beside them:
|
|
305
|
+
// bg (every screen's background) · surface (cards, bars; the tab bar) · border (hairlines)
|
|
306
|
+
// text (text with no color of its own) · textMuted (secondary; inactive tabs) · accent (active tab)
|
|
307
|
+
// fontFamily (text with no font of its own). Light theme = set text once, not a color per label.
|
|
308
|
+
// SCOPE: el.theme({...}) — the same keys for ONE subtree (a card, tabs.bar, a whole screen); its
|
|
309
|
+
// descendants read them first. A pushed screen / a widget is its own root: give it its own.
|
|
306
310
|
// Bare comfort-* tokens apply the safe-area formula; "var(--comfort-top)" reads the raw knob.
|
|
307
311
|
// "var(--name, fallback)" applies the fallback while the key is unset. Env names (safe-*, vw/vh) are not theme keys.
|
|
308
312
|
|
|
@@ -334,7 +338,7 @@ screen.style({ flexDirection: "column", p: 16, $landscape: { flexDirection: "row
|
|
|
334
338
|
// alignItems: "stretch" — children FILL the cross axis by default; set a size or alignSelf to opt out.
|
|
335
339
|
// overflow: "hidden" — children are clipped to the parent's box; set overflow: "visible" to let them escape.
|
|
336
340
|
// boxSizing: "border-box" — width/height include padding & border.
|
|
337
|
-
// Screen bg is black, default text is WHITE at fontSize 14 (retarget once via theme({
|
|
341
|
+
// Screen bg is black, default text is WHITE at fontSize 14 (retarget once via theme({ bg, text, fontFamily })).
|
|
338
342
|
// On any light surface — white cards, sheets, inputs — white text renders invisible: use dark text there
|
|
339
343
|
// ("#111"/black), checking every UIText against the surface it actually sits on, not the screen.
|
|
340
344
|
|
|
@@ -448,7 +452,7 @@ font("ibm-plex-sans", { weights: [400, 600], italic: true }) // narrow/extend t
|
|
|
448
452
|
// A project's own file: font("./fonts/Brand.ttf") → real family name read from the file itself
|
|
449
453
|
// (weight/italic too — no options); two weight files of one family return the same name.
|
|
450
454
|
// registerFont(family, url, {weight, style}) survives ONLY for runtime-computed URLs — rare.
|
|
451
|
-
//
|
|
455
|
+
// The app's font: theme({ fontFamily }). A second family is set per UIText (extract a shared Style<UIText>).
|
|
452
456
|
|
|
453
457
|
// ===== SVG IMAGES =====
|
|
454
458
|
// SvgSource wraps raw SVG XML for use as an image source: UIImage(SvgSource(`<svg ...>`)).
|
|
@@ -550,17 +554,16 @@ screen.open()
|
|
|
550
554
|
</file>
|
|
551
555
|
|
|
552
556
|
// === EXAMPLE 2: Theme tokens + fetched list with loading / error states ===
|
|
553
|
-
// A tokens module every screen imports, light-themed
|
|
554
|
-
//
|
|
557
|
+
// A tokens module every screen imports, light-themed. ONE theme() call: the roles (bg, surface,
|
|
558
|
+
// border, text, textMuted, accent) color the screens, the default text and the tab bar — no
|
|
559
|
+
// color: "#111" on every label — and the app's own keys sit beside them.
|
|
555
560
|
<file name="tokens.ts">
|
|
556
|
-
const palette = {
|
|
557
|
-
bg: "#F4F6F5", card: "#FFFFFF", border: "#E4E8E6",
|
|
558
|
-
text: "#131A17", muted: "#606B65",
|
|
559
|
-
accent: "#15A34A", accentSoft: "#E7F6ED", onAccent: "#FFFFFF",
|
|
560
|
-
}
|
|
561
|
-
theme({ color: palette.text, primaryColor: palette.accent })
|
|
562
561
|
// Accessors ARE "var(--x)" strings — re-calling theme() with new values restyles the live app.
|
|
563
|
-
export const colors
|
|
562
|
+
export const colors = theme({
|
|
563
|
+
bg: "#F4F6F5", surface: "#FFFFFF", border: "#E4E8E6",
|
|
564
|
+
text: "#131A17", textMuted: "#606B65", accent: "#15A34A",
|
|
565
|
+
accentSoft: "#E7F6ED", onAccent: "#FFFFFF",
|
|
566
|
+
})
|
|
564
567
|
export const font = { // type scale — spread into styles: .style({ ...font.h2 })
|
|
565
568
|
h2: { fontSize: 22, fontWeight: 700 }, bodyStrong: { fontSize: 16, fontWeight: 600 },
|
|
566
569
|
small: { fontSize: 14 }, tiny: { fontSize: 12, fontWeight: 500 },
|
|
@@ -577,9 +580,9 @@ const Row = (u: User) => UIRow(
|
|
|
577
580
|
justifyContent: "center", alignItems: "center" }),
|
|
578
581
|
UIColumn(
|
|
579
582
|
UIText(u.name).style({ ...font.bodyStrong }),
|
|
580
|
-
UIText(u.email).style({ ...font.small, color: colors.
|
|
583
|
+
UIText(u.email).style({ ...font.small, color: colors.textMuted }),
|
|
581
584
|
).style({ flexGrow: 1, flexShrink: 1, gap: 2, alignItems: "flex-start" }),
|
|
582
|
-
).style({ alignItems: "center", gap: 12, bgColor: colors.
|
|
585
|
+
).style({ alignItems: "center", gap: 12, bgColor: colors.surface, borderRadius: 16,
|
|
583
586
|
border: `1px solid ${colors.border}`, p: 14 })
|
|
584
587
|
|
|
585
588
|
const Centered = (...children: UINodeChild[]) =>
|
|
@@ -588,7 +591,7 @@ const Centered = (...children: UINodeChild[]) =>
|
|
|
588
591
|
let body: UIColumn
|
|
589
592
|
|
|
590
593
|
const loadData = async () => {
|
|
591
|
-
body.setContent([Centered(UIText("Loading…").style({ color: colors.
|
|
594
|
+
body.setContent([Centered(UIText("Loading…").style({ color: colors.textMuted }))])
|
|
592
595
|
const res = await fetch("https://jsonplaceholder.typicode.com/users")
|
|
593
596
|
if (res.status !== 200) {
|
|
594
597
|
body.setContent([Centered(
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
// The READER of the tween blob: `buildBlob`'s layout (./spec.ts) back into tracks and keys. For a
|
|
2
|
+
// host that plays an animation with no creator-tween in it (Web Lite hands the tracks to the
|
|
3
|
+
// browser's animations) — creator-tween reads the same layout in C (blob.h).
|
|
4
|
+
//
|
|
5
|
+
// The reader is the builder's twin and lives beside it: tests/animate/blob.test.ts builds a spec,
|
|
6
|
+
// reads it back and holds the two against each other, so a change of the layout that reaches one
|
|
7
|
+
// side only fails there.
|
|
8
|
+
|
|
9
|
+
import { EASE_TABLE, EASE_TABLE_SIZE, evalEasing, type EasingSpec } from "./easing"
|
|
10
|
+
|
|
11
|
+
/** The version of the layout this reader knows (./spec.ts BLOB_VERSION). */
|
|
12
|
+
const VERSION = 1
|
|
13
|
+
// The kinds whose one lane is an index into the string table (./spec.ts KIND_*).
|
|
14
|
+
const KIND_TRANSFORM_MATRIX = 4, KIND_DISCRETE = 8, KIND_STRING = 9
|
|
15
|
+
|
|
16
|
+
export type BlobKey = {
|
|
17
|
+
/** 0..1 within the track. */
|
|
18
|
+
t: number
|
|
19
|
+
easing: EasingSpec
|
|
20
|
+
/** Absent = the target's value when the animation first plays. */
|
|
21
|
+
value?: { lanes: number[] } | { str: string }
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export type BlobTrack = {
|
|
25
|
+
domain: number
|
|
26
|
+
/** An index into the `targets` handed over next to the blob (a role, in a transition's blob). */
|
|
27
|
+
target: number
|
|
28
|
+
prop: string
|
|
29
|
+
kind: number
|
|
30
|
+
lanes: number
|
|
31
|
+
/** The unit of a FLOAT_UNIT, the function list of a TRANSFORM. */
|
|
32
|
+
sig?: string
|
|
33
|
+
atMs: number
|
|
34
|
+
durMs: number
|
|
35
|
+
commit: boolean
|
|
36
|
+
keys: BlobKey[]
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export type ReadBlob = {
|
|
40
|
+
clock: number
|
|
41
|
+
/** One iteration, ms. */
|
|
42
|
+
durationMs: number
|
|
43
|
+
delayMs: number
|
|
44
|
+
/** -1 = forever. */
|
|
45
|
+
iterations: number
|
|
46
|
+
pingPong: boolean
|
|
47
|
+
rate: number
|
|
48
|
+
/** Time-callbacks, ms; the index is what the host reports. */
|
|
49
|
+
calls: number[]
|
|
50
|
+
tracks: BlobTrack[]
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/** The blob as tracks, or null for data that is not a blob of this version (or is cut short). */
|
|
54
|
+
export const readBlob = (data: ArrayLike<number>, strings: readonly string[]): ReadBlob | null => {
|
|
55
|
+
if (data.length < 10 || data[0] !== VERSION) return null
|
|
56
|
+
const nTracks = data[7]!, nCalls = data[8]!, nTables = data[9]!
|
|
57
|
+
let at = 10
|
|
58
|
+
if (at + nCalls + nTables * EASE_TABLE_SIZE > data.length) return null
|
|
59
|
+
const calls: number[] = []
|
|
60
|
+
for (let i = 0; i < nCalls; i++) calls.push(data[at++]!)
|
|
61
|
+
const tables: Float32Array[] = []
|
|
62
|
+
for (let i = 0; i < nTables; i++) {
|
|
63
|
+
const table = new Float32Array(EASE_TABLE_SIZE)
|
|
64
|
+
for (let k = 0; k < EASE_TABLE_SIZE; k++) table[k] = data[at++]!
|
|
65
|
+
tables.push(table)
|
|
66
|
+
}
|
|
67
|
+
const str = (index: number): string | undefined => (index >= 0 ? strings[index] : undefined)
|
|
68
|
+
|
|
69
|
+
const tracks: BlobTrack[] = []
|
|
70
|
+
for (let t = 0; t < nTracks; t++) {
|
|
71
|
+
if (at + 10 > data.length) return null
|
|
72
|
+
const kind = data[at + 3]!, lanes = data[at + 4]!, nKeys = data[at + 8]!
|
|
73
|
+
const track: BlobTrack = {
|
|
74
|
+
domain: data[at]!, target: data[at + 1]!, prop: str(data[at + 2]!) ?? "", kind, lanes,
|
|
75
|
+
sig: str(data[at + 9]!), atMs: data[at + 5]!, durMs: data[at + 6]!, commit: data[at + 7]! !== 0, keys: [],
|
|
76
|
+
}
|
|
77
|
+
at += 10
|
|
78
|
+
if (at + nKeys * (7 + lanes) > data.length) return null
|
|
79
|
+
const stringLane = kind === KIND_TRANSFORM_MATRIX || kind === KIND_DISCRETE || kind === KIND_STRING
|
|
80
|
+
for (let k = 0; k < nKeys; k++) {
|
|
81
|
+
const easeKind = data[at + 1]!
|
|
82
|
+
const p: [number, number, number, number] = [data[at + 2]!, data[at + 3]!, data[at + 4]!, data[at + 5]!]
|
|
83
|
+
const easing: EasingSpec = easeKind === EASE_TABLE ? { kind: easeKind, p, table: tables[p[0]] } : { kind: easeKind, p }
|
|
84
|
+
const key: BlobKey = { t: data[at]!, easing }
|
|
85
|
+
if (data[at + 6]! !== 0) {
|
|
86
|
+
if (stringLane) key.value = { str: str(data[at + 7]!) ?? "" }
|
|
87
|
+
else {
|
|
88
|
+
const values: number[] = []
|
|
89
|
+
for (let l = 0; l < lanes; l++) values.push(data[at + 7 + l]!)
|
|
90
|
+
key.value = { lanes: values }
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
at += 7 + lanes
|
|
94
|
+
track.keys.push(key)
|
|
95
|
+
}
|
|
96
|
+
tracks.push(track)
|
|
97
|
+
}
|
|
98
|
+
if (at !== data.length) return null
|
|
99
|
+
return { clock: data[1]!, durationMs: data[2]!, delayMs: data[3]!, iterations: data[4]!, pingPong: data[5]! !== 0, rate: data[6]!, calls, tracks }
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// ---- a track's value at a time -------------------------------------------------------------------
|
|
103
|
+
|
|
104
|
+
const KIND_QUAT = 6, KIND_MAT4 = 11
|
|
105
|
+
|
|
106
|
+
const slerp = (out: number[], o: number, a: number[], b: number[], s: number): void => {
|
|
107
|
+
let bx = b[o] ?? 0, by = b[o + 1] ?? 0, bz = b[o + 2] ?? 0, bw = b[o + 3] ?? 1
|
|
108
|
+
const ax = a[o] ?? 0, ay = a[o + 1] ?? 0, az = a[o + 2] ?? 0, aw = a[o + 3] ?? 1
|
|
109
|
+
let cosom = ax * bx + ay * by + az * bz + aw * bw
|
|
110
|
+
if (cosom < 0) { cosom = -cosom; bx = -bx; by = -by; bz = -bz; bw = -bw }
|
|
111
|
+
let k0 = 1 - s, k1 = s
|
|
112
|
+
if (1 - cosom > 1e-6) {
|
|
113
|
+
const omega = Math.acos(Math.min(1, cosom)), sinom = Math.sin(omega)
|
|
114
|
+
k0 = Math.sin((1 - s) * omega) / sinom
|
|
115
|
+
k1 = Math.sin(s * omega) / sinom
|
|
116
|
+
}
|
|
117
|
+
out[o] = k0 * ax + k1 * bx; out[o + 1] = k0 * ay + k1 * by; out[o + 2] = k0 * az + k1 * bz; out[o + 3] = k0 * aw + k1 * bw
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/** The two keys around the local time `ms` of a track and how far between them the track is,
|
|
121
|
+
* eased: the first key holds before the track, the last one after it. */
|
|
122
|
+
export const segmentAt = (track: BlobTrack, ms: number): { from: BlobKey, to: BlobKey, s: number } | null => {
|
|
123
|
+
const keys = track.keys
|
|
124
|
+
if (keys.length === 0) return null
|
|
125
|
+
const u = track.durMs <= 0 ? (ms >= track.atMs ? 1 : 0) : Math.min(1, Math.max(0, (ms - track.atMs) / track.durMs))
|
|
126
|
+
const first = keys[0]!, last = keys[keys.length - 1]!
|
|
127
|
+
if (keys.length === 1 || u <= first.t) return { from: first, to: first, s: 0 }
|
|
128
|
+
if (u >= last.t) return { from: last, to: last, s: 0 }
|
|
129
|
+
let i = 0
|
|
130
|
+
while (i + 1 < keys.length && keys[i + 1]!.t <= u) i++
|
|
131
|
+
const from = keys[i]!, to = keys[i + 1]!
|
|
132
|
+
const span = to.t - from.t
|
|
133
|
+
return { from, to, s: evalEasing(from.easing, span > 0 ? (u - from.t) / span : 1) }
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/** The lanes of a numeric track at the local time `ms`, into `out` (a key with no value reads as zeros). */
|
|
137
|
+
export const sampleTrack = (track: BlobTrack, ms: number, out: number[]): void => {
|
|
138
|
+
const at = segmentAt(track, ms)
|
|
139
|
+
if (!at) return
|
|
140
|
+
const lanesOf = (key: BlobKey): number[] => (key.value && "lanes" in key.value ? key.value.lanes : [])
|
|
141
|
+
const a = lanesOf(at.from), b = lanesOf(at.to), s = at.s
|
|
142
|
+
const lerp = (from: number, to: number) => { for (let k = from; k < to; k++) out[k] = (a[k] ?? 0) + ((b[k] ?? 0) - (a[k] ?? 0)) * s }
|
|
143
|
+
if (track.kind === KIND_QUAT) { slerp(out, 0, a, b, s); return }
|
|
144
|
+
if (track.kind === KIND_MAT4) { lerp(0, 3); slerp(out, 3, a, b, s); lerp(7, 10); return }
|
|
145
|
+
lerp(0, track.lanes)
|
|
146
|
+
}
|
package/src/bridges/device.d.ts
CHANGED
|
@@ -28,6 +28,15 @@ declare global {
|
|
|
28
28
|
/** Fire a one-shot semantic haptic (device.vibrate). `style` is a HapticStyle string. Optional:
|
|
29
29
|
* hosts without haptic hardware (web, headless, iPad / older iPhones) simply don't implement it. */
|
|
30
30
|
vibrate?(style: string): void
|
|
31
|
+
/** The HOST's performance overlay (device.statsOverlay): frames per second as the display got
|
|
32
|
+
* them, how evenly, the GPU's and the main thread's milliseconds — a line the host draws over
|
|
33
|
+
* everything itself, about once a second, with no JS, tree or layout behind it. It is the
|
|
34
|
+
* host's state, not a world's: it stays through `_creatorApp.run()`, so a launcher turns it
|
|
35
|
+
* on for whatever it opens next. Optional, as a pair: a host with no overlay (web, headless)
|
|
36
|
+
* leaves both out and the SDK reads `false`. */
|
|
37
|
+
setStatsOverlay?(enabled: boolean): void
|
|
38
|
+
/** Whether the overlay is on. */
|
|
39
|
+
statsOverlay?(): boolean
|
|
31
40
|
|
|
32
41
|
// ---- device.motion (orientation sensor; pull-per-frame model) --------------------------------
|
|
33
42
|
/** Does this device have the motion sensors (gyro)? Surfaced as device.motion.available. */
|
package/src/bridges/tree.d.ts
CHANGED
|
@@ -67,6 +67,11 @@ declare global {
|
|
|
67
67
|
// Theme variables: MERGES `values` (null removes a key, numbers are px lengths, a color is a
|
|
68
68
|
// string) and re-resolves live styles. Env names (safe-*, vw/vh/…) are reserved.
|
|
69
69
|
setTheme(values: Record<string, string | f64 | null>): void
|
|
70
|
+
// The same for ONE node's subtree (`el.theme({...})`): a SCOPE. A `var(--name)` on a node reads
|
|
71
|
+
// the nearest scope up its tree — the node, its ancestors, and for a pager's page or a vlist's
|
|
72
|
+
// item the pager / list it is in — then the app's table. A destination pushed over the node and
|
|
73
|
+
// a widget are roots of their own: no scope reaches them. The comfort-* knobs stay app-wide.
|
|
74
|
+
setNodeTheme(id: TreeNodeId, values: Record<string, string | f64 | null>): void
|
|
70
75
|
|
|
71
76
|
// ---- presentation -------------------------------------------------------------------------
|
|
72
77
|
// A transition (sdk/src/ui/transitions.ts): a tween blob whose targets are ROLES — `roles[i]` is
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// GENERATED by scripts/vendor-canvas.ts from engines/canvas/recorders/ts/src/cssColor.ts @
|
|
1
|
+
// GENERATED by scripts/vendor-canvas.ts from engines/canvas/recorders/ts/src/cssColor.ts @ 72e545dab1f5 — do not edit here.
|
|
2
2
|
// Bump the submodule and run `bun run vendor:canvas`; `bun run check:canvas-ops` fails when this copy is stale.
|
|
3
3
|
// The CSS color parser — the TypeScript twin of core/include/anycanvas/css_color.h: the same grammar,
|
|
4
4
|
// the same name table, float32 emulated op for op (Math.fround, in the header's order), so both give
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// GENERATED by scripts/vendor-canvas.ts from engines/canvas/recorders/ts/src/recorder.ts @
|
|
1
|
+
// GENERATED by scripts/vendor-canvas.ts from engines/canvas/recorders/ts/src/recorder.ts @ 72e545dab1f5 — do not edit here.
|
|
2
2
|
// Bump the submodule and run `bun run vendor:canvas`; `bun run check:canvas-ops` fails when this copy is stale.
|
|
3
3
|
// The TypeScript recorder: a Canvas2D-shaped API whose calls WRITE the opcode stream of spec/ops.h.
|
|
4
4
|
// Nothing is drawn here — the stream goes to the core interpreter (native, or wasm on the web), which
|
package/src/canvas/gen/spec.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// GENERATED by scripts/vendor-canvas.ts from engines/canvas/spec/gen/spec.ts @
|
|
1
|
+
// GENERATED by scripts/vendor-canvas.ts from engines/canvas/spec/gen/spec.ts @ 72e545dab1f5 — do not edit here.
|
|
2
2
|
// Bump the submodule and run `bun run vendor:canvas`; `bun run check:canvas-ops` fails when this copy is stale.
|
|
3
3
|
// GENERATED by spec/generate.ts from spec/*.h — do not edit.
|
|
4
4
|
|
package/src/chisel.ts
CHANGED
|
@@ -123,7 +123,7 @@ export const chiselVocab: ChiselVocab = {
|
|
|
123
123
|
// Exactly the builder methods that return the node (tests/compile/chisel-vocab.test.ts proves it against
|
|
124
124
|
// the interfaces): `animateTo` / `animateFrom` return an Animation, so they are NOT here.
|
|
125
125
|
chain: [
|
|
126
|
-
"style", "class", "named",
|
|
126
|
+
"style", "class", "named", "theme",
|
|
127
127
|
"onClick", "onTouchStart", "onLongPress", "onMouseEnter", "onLayout", "onOpen", "onClose", "onBack",
|
|
128
128
|
"onScroll", "onScrollRelease", "onOverscroll", "onRefresh", "onSubmit",
|
|
129
129
|
"onSelect", "onChange", "onFocus", "onBlur", "onOverlayTap", "onDetent",
|
package/src/compile/bundler.ts
CHANGED
|
@@ -69,6 +69,12 @@ export const SERVER_INJECT = "/__sdk/server/inject.ts"
|
|
|
69
69
|
* contract, `__sceneEditor` (src/inject.editor.ts → src/scene/editor.ts). Never in a shipped bundle. */
|
|
70
70
|
export const EDITOR_INJECT = "/__sdk/inject.editor.ts"
|
|
71
71
|
|
|
72
|
+
/** The text of the two inject modules — the lists of what is a global on each side (serverTypes.ts). */
|
|
73
|
+
export const readInjectSources = (): { client: string, server: string } => {
|
|
74
|
+
const client = resolveInject()
|
|
75
|
+
return { client: readFileSync(client, "utf8"), server: readFileSync(join(dirname(client), "server", "inject.ts"), "utf8") }
|
|
76
|
+
}
|
|
77
|
+
|
|
72
78
|
/** The SDK source root (parent of `inject.ts`). chisel needs the source tree, not a prebuilt bundle. */
|
|
73
79
|
const resolveSdkSrc = (): string => {
|
|
74
80
|
const inject = resolveInject()
|