lecodes-sdk 2.2.3 → 2.2.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/dist/global.d.ts +3 -1
- package/dist/types/animate/tween/spec.d.ts +4 -5
- package/dist/types/g2/Scene2D.d.ts +5 -4
- package/dist/types/gl/Scene.d.ts +5 -4
- package/dist/types/inject.d.ts +1 -1
- package/dist/types/server/db/fields.d.ts +6 -3
- package/dist/types/server/db/marci/query.d.ts +21 -2
- package/dist/types/server/db/types.d.ts +26 -12
- package/dist/types/server/errors.d.ts +4 -1
- package/dist/types/server/files/models.d.ts +4 -0
- package/dist/types/server/inject.d.ts +6 -0
- package/dist/types/ui/NativeView.d.ts +3 -3
- package/dist/types/ui/UI.d.ts +1 -1
- package/dist/types/ui/UILayer.d.ts +25 -18
- package/dist/types/ui/UINode.d.ts +8 -1
- package/dist/types/ui/UIScreen.d.ts +11 -1
- package/dist/types/ui/UIVideo.d.ts +3 -3
- package/dist/types/ui/UIVirtualizedList.d.ts +64 -18
- package/dist/types/ui/UIWidget.d.ts +24 -15
- package/dist/types/ui/presentable.d.ts +7 -7
- package/dist/types/version.d.ts +1 -1
- package/dist/types.json +1 -1
- package/package.json +2 -2
- package/prompts/3d-scene-files.md +2 -2
- package/prompts/3d-scene.md +1 -1
- package/prompts/dist/2d-game.md +13 -13
- package/prompts/dist/3d-app.md +21 -18
- package/prompts/dist/ar-app.md +13 -13
- package/prompts/dist/ui-app.md +13 -13
- package/prompts/ui.md +3 -1
- package/src/animate/tween/spec.ts +8 -13
- package/src/chisel.ts +3 -3
- package/src/compile/serverSplit.ts +5 -3
- package/src/g2/Scene2D.ts +9 -7
- package/src/gl/Scene.ts +9 -7
- package/src/inject.ts +1 -1
- package/src/server/db/defineDb.ts +17 -21
- package/src/server/db/fields.ts +7 -4
- package/src/server/db/httpTransport.ts +19 -3
- package/src/server/db/marci/query.ts +34 -8
- package/src/server/db/types.ts +33 -14
- package/src/server/errors.ts +4 -1
- package/src/server/files/db.ts +26 -7
- package/src/server/files/host.ts +45 -17
- package/src/server/files/models.ts +4 -0
- package/src/server/host.ts +3 -3
- package/src/server/inject.ts +3 -0
- package/src/server/runtime.ts +65 -4
- package/src/ui/NativeView.ts +11 -9
- package/src/ui/UI.ts +1 -1
- package/src/ui/UILayer.ts +90 -42
- package/src/ui/UINode.ts +33 -11
- package/src/ui/UIPopover.ts +3 -3
- package/src/ui/UIScreen.ts +50 -2
- package/src/ui/UIVideo.ts +11 -9
- package/src/ui/UIVirtualizedList.ts +159 -31
- package/src/ui/UIWidget.ts +41 -18
- package/src/ui/presentable.ts +8 -8
- package/src/ui/transitions.ts +1 -1
- package/src/version.ts +1 -1
- package/tests/helpers/fakeTree.ts +6 -1
package/package.json
CHANGED
|
@@ -42,10 +42,10 @@
|
|
|
42
42
|
"@types/node": "^25.9.1",
|
|
43
43
|
"typescript": "~5.8.3",
|
|
44
44
|
"gl-matrix": "^3.4.4",
|
|
45
|
-
"marcidb-embedded": "^0.
|
|
45
|
+
"marcidb-embedded": "^0.15.0",
|
|
46
46
|
"sharp": "^0.35.2"
|
|
47
47
|
},
|
|
48
|
-
"version": "2.2.
|
|
48
|
+
"version": "2.2.5",
|
|
49
49
|
"files": [
|
|
50
50
|
"src",
|
|
51
51
|
"dist",
|
|
@@ -69,7 +69,7 @@ export class Spinner extends Aspect<'spinner'> {
|
|
|
69
69
|
// a UIWidget that is the scene's CONTENT — it shows and hides together with it:
|
|
70
70
|
const hud = UIWidget(UIText('Score: 0').style({ color: 'white', fontSize: 24 }))
|
|
71
71
|
.style({ top: 'max(safe-top, 16px)', left: 16 })
|
|
72
|
-
scene.
|
|
72
|
+
scene.widgets(hud)
|
|
73
73
|
|
|
74
74
|
// ===== SCENE FILE MISTAKES — DO NOT DO THESE =====
|
|
75
75
|
|
|
@@ -107,7 +107,7 @@ const { scene, nodes } = await mainScene.open()
|
|
|
107
107
|
|
|
108
108
|
let taps = 0
|
|
109
109
|
const label = UIText('Tap the crate').style({ color: 'white', fontSize: 20, fontWeight: 700 })
|
|
110
|
-
scene.
|
|
110
|
+
scene.widgets(UIWidget(label).style({ top: 'max(safe-top, 16px)', left: 16 }))
|
|
111
111
|
|
|
112
112
|
nodes.crate.addEventListener('click', () => { label.text = `Taps: ${++taps}` })
|
|
113
113
|
</file>
|
package/prompts/3d-scene.md
CHANGED
package/prompts/dist/2d-game.md
CHANGED
|
@@ -112,7 +112,7 @@ Everything in this prompt is a global — no imports needed. A project's only im
|
|
|
112
112
|
|
|
113
113
|
Imports of the project's own files are also managed automatically: if your <edit> or <replace> makes code reference another file's export, the import is added for you — never fall back to a whole <file> rewrite just to change import lines. When writing a complete <file>, include imports normally.
|
|
114
114
|
|
|
115
|
-
Reference a project asset (image, font, video, .svg, .glb, …) by
|
|
115
|
+
Reference a project asset (image, font, video, .svg, .glb, …) by its path as a string LITERAL — `UIImage("images/hero.png")`, `"./x.png"` from the file, `"/x.png"` from the root — or with the inline `asset('./path')` macro; both are a compile-time equivalent of the import (a literal, never a variable; a path that names no file fails the compile). A `.json` path stays a string. External `https://…` URLs are used directly as strings.
|
|
116
116
|
|
|
117
117
|
## Project context
|
|
118
118
|
|
|
@@ -229,6 +229,7 @@ new WebSocket(url, headers?: Record<string, string>)
|
|
|
229
229
|
// ===== STORAGE / DEVICE / MISC =====
|
|
230
230
|
|
|
231
231
|
localStorage.getItem(key): string | null / .setItem(key, value) / .removeItem(key) // nothing else
|
|
232
|
+
signal<Line[]>([]).stored("cart") // a signal kept on the device under the key (JSON values): back on the next launch
|
|
232
233
|
|
|
233
234
|
device.platform: "ios" | "android" | "web" // getter, no ()
|
|
234
235
|
device.language: string // "en", "ru", …
|
|
@@ -794,6 +795,8 @@ const tabs = UITabs({
|
|
|
794
795
|
Router.init(tabs) // UITabs IS a UIScreen — present it directly
|
|
795
796
|
// .select(id), .tab (getter), .onSelect(cb(id, i)) — fires on a bar tap, swipe, or select()
|
|
796
797
|
// .badge(id, value) — true = dot, number/string = count pill, false/null/0 clears
|
|
798
|
+
// Ambient, from any screen, no reference threading: UITabs.current (the visible tabs, or null) and
|
|
799
|
+
// UITabs.select("profile") — the twin of UIPager.current / UIPager.push
|
|
797
800
|
// .pager — the UIPager underneath; UIPager.push(detail) from any screen keeps the bar
|
|
798
801
|
// The bar is colored by the theme's roles (accent, textMuted, surface, border) — no keys of its
|
|
799
802
|
// own; one bar's own colors: tabs.bar.theme({ accent, surface }). Custom layout: UIPager below.
|
|
@@ -876,17 +879,17 @@ UIScrollable(...children)
|
|
|
876
879
|
// visible rows (plus a buffer) are mounted. Use it instead of UIScrollable + map() whenever the item count
|
|
877
880
|
// is large, grows over time, or is unknown.
|
|
878
881
|
UIVirtualizedList<T>({
|
|
882
|
+
data?: () => T[], // the rows, read reactively: write the signal, the list follows (new array, keys matched)
|
|
883
|
+
// absent = fed by hand: .setData(items) / .append(...items) / .prepend(...items) / .update(...items) / .removeByKey(...keys)
|
|
884
|
+
// (one feed per list: a list with `data` has no append on its type)
|
|
879
885
|
keyOf: (item: T) => string, // STABLE unique id per item (never the array index)
|
|
880
886
|
render: (item: T) => UINodeChild, // builds one row; called lazily as rows enter the window
|
|
881
887
|
estimatedHeight: number | ((item: T) => number), // px guess per row (real height measured after mount)
|
|
882
888
|
overscan?: number, // extra px mounted above/below the viewport (default: one viewport)
|
|
883
889
|
inverted?: boolean, // true = chat mode: starts scrolled to the end, append at bottom auto-scrolls
|
|
884
890
|
})
|
|
885
|
-
//
|
|
886
|
-
//
|
|
887
|
-
// .append(...items) / .prepend(...items) // variadic; append = newest chat msg; prepend = history, no scroll jump
|
|
888
|
-
// .update(...items) // re-render rows with the same keyOf(); unknown keys ignored
|
|
889
|
-
// .removeByKey(...keys)
|
|
891
|
+
// Rows added at the end scroll a chat at its bottom; rows added before the first (history) move nothing; a row
|
|
892
|
+
// whose item is a new object re-renders. Never re-create the list.
|
|
890
893
|
// .itemCount (getter) / .getItem(key)
|
|
891
894
|
// .scrollTo(offset, animated?) / .scrollToKey(key, animated?) / .scrollToEnd(animated?) / .onScroll(cb(pos))
|
|
892
895
|
// .onEndReached(thresholdPx: number, cb) // threshold FIRST and required; fires near the BOTTOM — load next page
|
|
@@ -914,7 +917,7 @@ UIImage(src) // src: string url | FetchResponse | File | SvgSource | Canvas (se
|
|
|
914
917
|
// .setSourceRect(x, y, w, h) — crop to a sub-rect of the source (texture px); frame fills the box, objectFit
|
|
915
918
|
// ignored. Chainable, safe to call before mount. For atlases/spritesheets — swap the rect to change frame.
|
|
916
919
|
// Note: tintColor applies only to SVG sources — it replaces ALL fill and stroke colors (monochrome icons only)
|
|
917
|
-
// Note: a
|
|
920
|
+
// Note: a project file by its path as a string — UIImage("images/photo.png") — is the bundled asset (the compile resolves it; a path that names no file fails the compile); plain https:// strings are remote URLs
|
|
918
921
|
|
|
919
922
|
// UIVideo — the on-screen surface only; playback lives entirely on the VideoPlayer (see AUDIO / VIDEO above)
|
|
920
923
|
UIVideo(player)
|
|
@@ -944,6 +947,7 @@ UIInput() / UITextArea()
|
|
|
944
947
|
// type: "text"|"password"|"search"|"phone"|"email"|"number"|"decimal"|"url"|"date"|"time"
|
|
945
948
|
// enterKey: "done"|"go"|"next"|"search"|"send" (UIInput only) — return-key label
|
|
946
949
|
// maxLength: number; autocapitalize: "none"|"words"|"sentences"|"characters"; autocorrect: boolean
|
|
950
|
+
// autofocus: boolean — takes the focus as its screen opens (a composer, a chat, a search; once, one per screen)
|
|
947
951
|
// keyboardShrink: boolean (default true) — false: keyboard OVERLAYS the UI, no relayout (chat composers)
|
|
948
952
|
// keyboardDismiss: boolean (default true) — false: taps never dismiss the keyboard (chat composers)
|
|
949
953
|
// $focused: { borderColor, ..., duration? } — style while focused (reserved class)
|
|
@@ -1219,6 +1223,8 @@ UIImage(heart).style({ width: 24, height: 24, tintColor: "#666" }) // tintColor
|
|
|
1219
1223
|
// ===== ICONS (assetIcon) =====
|
|
1220
1224
|
// Use real icons instead of emoji. assetIcon("pack:name") resolves the icon at COMPILE time and
|
|
1221
1225
|
// inlines it as an image source (same shape as SvgSource) — no imports, no project files, offline.
|
|
1226
|
+
// It returns an SvgSource — the same type an imported .svg and SvgSource(xml) give — so a component
|
|
1227
|
+
// that takes an icon is typed (icon: SvgSource), never (icon: string).
|
|
1222
1228
|
UIImage(assetIcon("lucide:bell")).style({ width: 24, height: 24, tintColor: "#8a8f98" })
|
|
1223
1229
|
// The id must be a string literal; an unknown id is a compile error (with name suggestions).
|
|
1224
1230
|
// Recolor via the tintColor style or the { color } option (hex literal bakes in; expression tints).
|
|
@@ -1273,12 +1279,6 @@ UIRow(isGroup ? button : UIColumn()) // WRONG
|
|
|
1273
1279
|
// ✅ null is skipped in children — no phantom element
|
|
1274
1280
|
UIRow(isGroup ? button : null)
|
|
1275
1281
|
|
|
1276
|
-
// ❌ pointing UIImage / bgImage at a project file by bare path — it won't resolve to the bundled asset
|
|
1277
|
-
UIImage("./photo.png") // WRONG (a plain string works only for remote http(s) URLs)
|
|
1278
|
-
// ✅ import the asset, or wrap its path in asset()
|
|
1279
|
-
import photo from './photo.png'
|
|
1280
|
-
UIImage(photo) // or: UIImage(asset('./photo.png'))
|
|
1281
|
-
|
|
1282
1282
|
// ❌ callback-first edge callbacks on UIVirtualizedList
|
|
1283
1283
|
list.onEndReached(loadNextPage) // WRONG — threshold is the first argument, and required
|
|
1284
1284
|
// ✅ threshold (px from the edge) first
|
package/prompts/dist/3d-app.md
CHANGED
|
@@ -112,7 +112,7 @@ Everything in this prompt is a global — no imports needed. A project's only im
|
|
|
112
112
|
|
|
113
113
|
Imports of the project's own files are also managed automatically: if your <edit> or <replace> makes code reference another file's export, the import is added for you — never fall back to a whole <file> rewrite just to change import lines. When writing a complete <file>, include imports normally.
|
|
114
114
|
|
|
115
|
-
Reference a project asset (image, font, video, .svg, .glb, …) by
|
|
115
|
+
Reference a project asset (image, font, video, .svg, .glb, …) by its path as a string LITERAL — `UIImage("images/hero.png")`, `"./x.png"` from the file, `"/x.png"` from the root — or with the inline `asset('./path')` macro; both are a compile-time equivalent of the import (a literal, never a variable; a path that names no file fails the compile). A `.json` path stays a string. External `https://…` URLs are used directly as strings.
|
|
116
116
|
|
|
117
117
|
## Project context
|
|
118
118
|
|
|
@@ -229,6 +229,7 @@ new WebSocket(url, headers?: Record<string, string>)
|
|
|
229
229
|
// ===== STORAGE / DEVICE / MISC =====
|
|
230
230
|
|
|
231
231
|
localStorage.getItem(key): string | null / .setItem(key, value) / .removeItem(key) // nothing else
|
|
232
|
+
signal<Line[]>([]).stored("cart") // a signal kept on the device under the key (JSON values): back on the next launch
|
|
232
233
|
|
|
233
234
|
device.platform: "ios" | "android" | "web" // getter, no ()
|
|
234
235
|
device.language: string // "en", "ru", …
|
|
@@ -666,8 +667,11 @@ setLoop(() => { mesh.material = Material.lit({ color: next() }) })
|
|
|
666
667
|
new Scene(options?: {
|
|
667
668
|
ibl?: boolean // image-based ambient light; default TRUE — every scene has ambient
|
|
668
669
|
environmentIntensity?: number // IBL strength; default 20000
|
|
669
|
-
|
|
670
|
-
|
|
670
|
+
postProcessing?: { // the look: tone mapping, grading, a .cube LUT, bloom, vignette, AO, reflections, DoF
|
|
671
|
+
toneMapping?: 'aces' | 'neutral' | 'linear' | 'filmic', exposure?: number, contrast?: number, saturation?: number,
|
|
672
|
+
lut?: string, bloom?: boolean | { intensity?: number }, vignette?: boolean | { feather?: number },
|
|
673
|
+
ambientOcclusion?: boolean | { radius?: number }, screenSpaceReflections?: boolean, depthOfField?: boolean | { focusDistance?: number, aperture?: number }
|
|
674
|
+
} // live: scene.postProcessing.contrast = 1.1; .apply({ ... }) = the named fields only; .reset(); never assigned
|
|
671
675
|
skybox?: ColorInput // solid background / clear color
|
|
672
676
|
antialias?: boolean // 4× MSAA; default false
|
|
673
677
|
})
|
|
@@ -775,7 +779,7 @@ const hud = UIWidget(
|
|
|
775
779
|
scoreLabel = UIText('Score: 0').style({ color: 'white', fontSize: 24, fontWeight: 700 }),
|
|
776
780
|
).style({ top: 'max(safe-top, 16px)', right: 16 })
|
|
777
781
|
|
|
778
|
-
scene.
|
|
782
|
+
scene.widgets(hud)
|
|
779
783
|
scene.open()
|
|
780
784
|
</file>
|
|
781
785
|
|
|
@@ -850,7 +854,7 @@ export class Spinner extends Aspect<'spinner'> {
|
|
|
850
854
|
// a UIWidget that is the scene's CONTENT — it shows and hides together with it:
|
|
851
855
|
const hud = UIWidget(UIText('Score: 0').style({ color: 'white', fontSize: 24 }))
|
|
852
856
|
.style({ top: 'max(safe-top, 16px)', left: 16 })
|
|
853
|
-
scene.
|
|
857
|
+
scene.widgets(hud)
|
|
854
858
|
|
|
855
859
|
// ===== SCENE FILE MISTAKES — DO NOT DO THESE =====
|
|
856
860
|
|
|
@@ -888,7 +892,7 @@ const { scene, nodes } = await mainScene.open()
|
|
|
888
892
|
|
|
889
893
|
let taps = 0
|
|
890
894
|
const label = UIText('Tap the crate').style({ color: 'white', fontSize: 20, fontWeight: 700 })
|
|
891
|
-
scene.
|
|
895
|
+
scene.widgets(UIWidget(label).style({ top: 'max(safe-top, 16px)', left: 16 }))
|
|
892
896
|
|
|
893
897
|
nodes.crate.addEventListener('click', () => { label.text = `Taps: ${++taps}` })
|
|
894
898
|
</file>
|
|
@@ -936,6 +940,8 @@ const tabs = UITabs({
|
|
|
936
940
|
Router.init(tabs) // UITabs IS a UIScreen — present it directly
|
|
937
941
|
// .select(id), .tab (getter), .onSelect(cb(id, i)) — fires on a bar tap, swipe, or select()
|
|
938
942
|
// .badge(id, value) — true = dot, number/string = count pill, false/null/0 clears
|
|
943
|
+
// Ambient, from any screen, no reference threading: UITabs.current (the visible tabs, or null) and
|
|
944
|
+
// UITabs.select("profile") — the twin of UIPager.current / UIPager.push
|
|
939
945
|
// .pager — the UIPager underneath; UIPager.push(detail) from any screen keeps the bar
|
|
940
946
|
// The bar is colored by the theme's roles (accent, textMuted, surface, border) — no keys of its
|
|
941
947
|
// own; one bar's own colors: tabs.bar.theme({ accent, surface }). Custom layout: UIPager below.
|
|
@@ -1018,17 +1024,17 @@ UIScrollable(...children)
|
|
|
1018
1024
|
// visible rows (plus a buffer) are mounted. Use it instead of UIScrollable + map() whenever the item count
|
|
1019
1025
|
// is large, grows over time, or is unknown.
|
|
1020
1026
|
UIVirtualizedList<T>({
|
|
1027
|
+
data?: () => T[], // the rows, read reactively: write the signal, the list follows (new array, keys matched)
|
|
1028
|
+
// absent = fed by hand: .setData(items) / .append(...items) / .prepend(...items) / .update(...items) / .removeByKey(...keys)
|
|
1029
|
+
// (one feed per list: a list with `data` has no append on its type)
|
|
1021
1030
|
keyOf: (item: T) => string, // STABLE unique id per item (never the array index)
|
|
1022
1031
|
render: (item: T) => UINodeChild, // builds one row; called lazily as rows enter the window
|
|
1023
1032
|
estimatedHeight: number | ((item: T) => number), // px guess per row (real height measured after mount)
|
|
1024
1033
|
overscan?: number, // extra px mounted above/below the viewport (default: one viewport)
|
|
1025
1034
|
inverted?: boolean, // true = chat mode: starts scrolled to the end, append at bottom auto-scrolls
|
|
1026
1035
|
})
|
|
1027
|
-
//
|
|
1028
|
-
//
|
|
1029
|
-
// .append(...items) / .prepend(...items) // variadic; append = newest chat msg; prepend = history, no scroll jump
|
|
1030
|
-
// .update(...items) // re-render rows with the same keyOf(); unknown keys ignored
|
|
1031
|
-
// .removeByKey(...keys)
|
|
1036
|
+
// Rows added at the end scroll a chat at its bottom; rows added before the first (history) move nothing; a row
|
|
1037
|
+
// whose item is a new object re-renders. Never re-create the list.
|
|
1032
1038
|
// .itemCount (getter) / .getItem(key)
|
|
1033
1039
|
// .scrollTo(offset, animated?) / .scrollToKey(key, animated?) / .scrollToEnd(animated?) / .onScroll(cb(pos))
|
|
1034
1040
|
// .onEndReached(thresholdPx: number, cb) // threshold FIRST and required; fires near the BOTTOM — load next page
|
|
@@ -1056,7 +1062,7 @@ UIImage(src) // src: string url | FetchResponse | File | SvgSource | Canvas (se
|
|
|
1056
1062
|
// .setSourceRect(x, y, w, h) — crop to a sub-rect of the source (texture px); frame fills the box, objectFit
|
|
1057
1063
|
// ignored. Chainable, safe to call before mount. For atlases/spritesheets — swap the rect to change frame.
|
|
1058
1064
|
// Note: tintColor applies only to SVG sources — it replaces ALL fill and stroke colors (monochrome icons only)
|
|
1059
|
-
// Note: a
|
|
1065
|
+
// Note: a project file by its path as a string — UIImage("images/photo.png") — is the bundled asset (the compile resolves it; a path that names no file fails the compile); plain https:// strings are remote URLs
|
|
1060
1066
|
|
|
1061
1067
|
// UIVideo — the on-screen surface only; playback lives entirely on the VideoPlayer (see AUDIO / VIDEO above)
|
|
1062
1068
|
UIVideo(player)
|
|
@@ -1086,6 +1092,7 @@ UIInput() / UITextArea()
|
|
|
1086
1092
|
// type: "text"|"password"|"search"|"phone"|"email"|"number"|"decimal"|"url"|"date"|"time"
|
|
1087
1093
|
// enterKey: "done"|"go"|"next"|"search"|"send" (UIInput only) — return-key label
|
|
1088
1094
|
// maxLength: number; autocapitalize: "none"|"words"|"sentences"|"characters"; autocorrect: boolean
|
|
1095
|
+
// autofocus: boolean — takes the focus as its screen opens (a composer, a chat, a search; once, one per screen)
|
|
1089
1096
|
// keyboardShrink: boolean (default true) — false: keyboard OVERLAYS the UI, no relayout (chat composers)
|
|
1090
1097
|
// keyboardDismiss: boolean (default true) — false: taps never dismiss the keyboard (chat composers)
|
|
1091
1098
|
// $focused: { borderColor, ..., duration? } — style while focused (reserved class)
|
|
@@ -1361,6 +1368,8 @@ UIImage(heart).style({ width: 24, height: 24, tintColor: "#666" }) // tintColor
|
|
|
1361
1368
|
// ===== ICONS (assetIcon) =====
|
|
1362
1369
|
// Use real icons instead of emoji. assetIcon("pack:name") resolves the icon at COMPILE time and
|
|
1363
1370
|
// inlines it as an image source (same shape as SvgSource) — no imports, no project files, offline.
|
|
1371
|
+
// It returns an SvgSource — the same type an imported .svg and SvgSource(xml) give — so a component
|
|
1372
|
+
// that takes an icon is typed (icon: SvgSource), never (icon: string).
|
|
1364
1373
|
UIImage(assetIcon("lucide:bell")).style({ width: 24, height: 24, tintColor: "#8a8f98" })
|
|
1365
1374
|
// The id must be a string literal; an unknown id is a compile error (with name suggestions).
|
|
1366
1375
|
// Recolor via the tintColor style or the { color } option (hex literal bakes in; expression tints).
|
|
@@ -1415,12 +1424,6 @@ UIRow(isGroup ? button : UIColumn()) // WRONG
|
|
|
1415
1424
|
// ✅ null is skipped in children — no phantom element
|
|
1416
1425
|
UIRow(isGroup ? button : null)
|
|
1417
1426
|
|
|
1418
|
-
// ❌ pointing UIImage / bgImage at a project file by bare path — it won't resolve to the bundled asset
|
|
1419
|
-
UIImage("./photo.png") // WRONG (a plain string works only for remote http(s) URLs)
|
|
1420
|
-
// ✅ import the asset, or wrap its path in asset()
|
|
1421
|
-
import photo from './photo.png'
|
|
1422
|
-
UIImage(photo) // or: UIImage(asset('./photo.png'))
|
|
1423
|
-
|
|
1424
1427
|
// ❌ callback-first edge callbacks on UIVirtualizedList
|
|
1425
1428
|
list.onEndReached(loadNextPage) // WRONG — threshold is the first argument, and required
|
|
1426
1429
|
// ✅ threshold (px from the edge) first
|
package/prompts/dist/ar-app.md
CHANGED
|
@@ -112,7 +112,7 @@ Everything in this prompt is a global — no imports needed. A project's only im
|
|
|
112
112
|
|
|
113
113
|
Imports of the project's own files are also managed automatically: if your <edit> or <replace> makes code reference another file's export, the import is added for you — never fall back to a whole <file> rewrite just to change import lines. When writing a complete <file>, include imports normally.
|
|
114
114
|
|
|
115
|
-
Reference a project asset (image, font, video, .svg, .glb, …) by
|
|
115
|
+
Reference a project asset (image, font, video, .svg, .glb, …) by its path as a string LITERAL — `UIImage("images/hero.png")`, `"./x.png"` from the file, `"/x.png"` from the root — or with the inline `asset('./path')` macro; both are a compile-time equivalent of the import (a literal, never a variable; a path that names no file fails the compile). A `.json` path stays a string. External `https://…` URLs are used directly as strings.
|
|
116
116
|
|
|
117
117
|
## Project context
|
|
118
118
|
|
|
@@ -229,6 +229,7 @@ new WebSocket(url, headers?: Record<string, string>)
|
|
|
229
229
|
// ===== STORAGE / DEVICE / MISC =====
|
|
230
230
|
|
|
231
231
|
localStorage.getItem(key): string | null / .setItem(key, value) / .removeItem(key) // nothing else
|
|
232
|
+
signal<Line[]>([]).stored("cart") // a signal kept on the device under the key (JSON values): back on the next launch
|
|
232
233
|
|
|
233
234
|
device.platform: "ios" | "android" | "web" // getter, no ()
|
|
234
235
|
device.language: string // "en", "ru", …
|
|
@@ -812,6 +813,8 @@ const tabs = UITabs({
|
|
|
812
813
|
Router.init(tabs) // UITabs IS a UIScreen — present it directly
|
|
813
814
|
// .select(id), .tab (getter), .onSelect(cb(id, i)) — fires on a bar tap, swipe, or select()
|
|
814
815
|
// .badge(id, value) — true = dot, number/string = count pill, false/null/0 clears
|
|
816
|
+
// Ambient, from any screen, no reference threading: UITabs.current (the visible tabs, or null) and
|
|
817
|
+
// UITabs.select("profile") — the twin of UIPager.current / UIPager.push
|
|
815
818
|
// .pager — the UIPager underneath; UIPager.push(detail) from any screen keeps the bar
|
|
816
819
|
// The bar is colored by the theme's roles (accent, textMuted, surface, border) — no keys of its
|
|
817
820
|
// own; one bar's own colors: tabs.bar.theme({ accent, surface }). Custom layout: UIPager below.
|
|
@@ -894,17 +897,17 @@ UIScrollable(...children)
|
|
|
894
897
|
// visible rows (plus a buffer) are mounted. Use it instead of UIScrollable + map() whenever the item count
|
|
895
898
|
// is large, grows over time, or is unknown.
|
|
896
899
|
UIVirtualizedList<T>({
|
|
900
|
+
data?: () => T[], // the rows, read reactively: write the signal, the list follows (new array, keys matched)
|
|
901
|
+
// absent = fed by hand: .setData(items) / .append(...items) / .prepend(...items) / .update(...items) / .removeByKey(...keys)
|
|
902
|
+
// (one feed per list: a list with `data` has no append on its type)
|
|
897
903
|
keyOf: (item: T) => string, // STABLE unique id per item (never the array index)
|
|
898
904
|
render: (item: T) => UINodeChild, // builds one row; called lazily as rows enter the window
|
|
899
905
|
estimatedHeight: number | ((item: T) => number), // px guess per row (real height measured after mount)
|
|
900
906
|
overscan?: number, // extra px mounted above/below the viewport (default: one viewport)
|
|
901
907
|
inverted?: boolean, // true = chat mode: starts scrolled to the end, append at bottom auto-scrolls
|
|
902
908
|
})
|
|
903
|
-
//
|
|
904
|
-
//
|
|
905
|
-
// .append(...items) / .prepend(...items) // variadic; append = newest chat msg; prepend = history, no scroll jump
|
|
906
|
-
// .update(...items) // re-render rows with the same keyOf(); unknown keys ignored
|
|
907
|
-
// .removeByKey(...keys)
|
|
909
|
+
// Rows added at the end scroll a chat at its bottom; rows added before the first (history) move nothing; a row
|
|
910
|
+
// whose item is a new object re-renders. Never re-create the list.
|
|
908
911
|
// .itemCount (getter) / .getItem(key)
|
|
909
912
|
// .scrollTo(offset, animated?) / .scrollToKey(key, animated?) / .scrollToEnd(animated?) / .onScroll(cb(pos))
|
|
910
913
|
// .onEndReached(thresholdPx: number, cb) // threshold FIRST and required; fires near the BOTTOM — load next page
|
|
@@ -932,7 +935,7 @@ UIImage(src) // src: string url | FetchResponse | File | SvgSource | Canvas (se
|
|
|
932
935
|
// .setSourceRect(x, y, w, h) — crop to a sub-rect of the source (texture px); frame fills the box, objectFit
|
|
933
936
|
// ignored. Chainable, safe to call before mount. For atlases/spritesheets — swap the rect to change frame.
|
|
934
937
|
// Note: tintColor applies only to SVG sources — it replaces ALL fill and stroke colors (monochrome icons only)
|
|
935
|
-
// Note: a
|
|
938
|
+
// Note: a project file by its path as a string — UIImage("images/photo.png") — is the bundled asset (the compile resolves it; a path that names no file fails the compile); plain https:// strings are remote URLs
|
|
936
939
|
|
|
937
940
|
// UIVideo — the on-screen surface only; playback lives entirely on the VideoPlayer (see AUDIO / VIDEO above)
|
|
938
941
|
UIVideo(player)
|
|
@@ -962,6 +965,7 @@ UIInput() / UITextArea()
|
|
|
962
965
|
// type: "text"|"password"|"search"|"phone"|"email"|"number"|"decimal"|"url"|"date"|"time"
|
|
963
966
|
// enterKey: "done"|"go"|"next"|"search"|"send" (UIInput only) — return-key label
|
|
964
967
|
// maxLength: number; autocapitalize: "none"|"words"|"sentences"|"characters"; autocorrect: boolean
|
|
968
|
+
// autofocus: boolean — takes the focus as its screen opens (a composer, a chat, a search; once, one per screen)
|
|
965
969
|
// keyboardShrink: boolean (default true) — false: keyboard OVERLAYS the UI, no relayout (chat composers)
|
|
966
970
|
// keyboardDismiss: boolean (default true) — false: taps never dismiss the keyboard (chat composers)
|
|
967
971
|
// $focused: { borderColor, ..., duration? } — style while focused (reserved class)
|
|
@@ -1237,6 +1241,8 @@ UIImage(heart).style({ width: 24, height: 24, tintColor: "#666" }) // tintColor
|
|
|
1237
1241
|
// ===== ICONS (assetIcon) =====
|
|
1238
1242
|
// Use real icons instead of emoji. assetIcon("pack:name") resolves the icon at COMPILE time and
|
|
1239
1243
|
// inlines it as an image source (same shape as SvgSource) — no imports, no project files, offline.
|
|
1244
|
+
// It returns an SvgSource — the same type an imported .svg and SvgSource(xml) give — so a component
|
|
1245
|
+
// that takes an icon is typed (icon: SvgSource), never (icon: string).
|
|
1240
1246
|
UIImage(assetIcon("lucide:bell")).style({ width: 24, height: 24, tintColor: "#8a8f98" })
|
|
1241
1247
|
// The id must be a string literal; an unknown id is a compile error (with name suggestions).
|
|
1242
1248
|
// Recolor via the tintColor style or the { color } option (hex literal bakes in; expression tints).
|
|
@@ -1291,12 +1297,6 @@ UIRow(isGroup ? button : UIColumn()) // WRONG
|
|
|
1291
1297
|
// ✅ null is skipped in children — no phantom element
|
|
1292
1298
|
UIRow(isGroup ? button : null)
|
|
1293
1299
|
|
|
1294
|
-
// ❌ pointing UIImage / bgImage at a project file by bare path — it won't resolve to the bundled asset
|
|
1295
|
-
UIImage("./photo.png") // WRONG (a plain string works only for remote http(s) URLs)
|
|
1296
|
-
// ✅ import the asset, or wrap its path in asset()
|
|
1297
|
-
import photo from './photo.png'
|
|
1298
|
-
UIImage(photo) // or: UIImage(asset('./photo.png'))
|
|
1299
|
-
|
|
1300
1300
|
// ❌ callback-first edge callbacks on UIVirtualizedList
|
|
1301
1301
|
list.onEndReached(loadNextPage) // WRONG — threshold is the first argument, and required
|
|
1302
1302
|
// ✅ threshold (px from the edge) first
|
package/prompts/dist/ui-app.md
CHANGED
|
@@ -112,7 +112,7 @@ Everything in this prompt is a global — no imports needed. A project's only im
|
|
|
112
112
|
|
|
113
113
|
Imports of the project's own files are also managed automatically: if your <edit> or <replace> makes code reference another file's export, the import is added for you — never fall back to a whole <file> rewrite just to change import lines. When writing a complete <file>, include imports normally.
|
|
114
114
|
|
|
115
|
-
Reference a project asset (image, font, video, .svg, .glb, …) by
|
|
115
|
+
Reference a project asset (image, font, video, .svg, .glb, …) by its path as a string LITERAL — `UIImage("images/hero.png")`, `"./x.png"` from the file, `"/x.png"` from the root — or with the inline `asset('./path')` macro; both are a compile-time equivalent of the import (a literal, never a variable; a path that names no file fails the compile). A `.json` path stays a string. External `https://…` URLs are used directly as strings.
|
|
116
116
|
|
|
117
117
|
## Project context
|
|
118
118
|
|
|
@@ -229,6 +229,7 @@ new WebSocket(url, headers?: Record<string, string>)
|
|
|
229
229
|
// ===== STORAGE / DEVICE / MISC =====
|
|
230
230
|
|
|
231
231
|
localStorage.getItem(key): string | null / .setItem(key, value) / .removeItem(key) // nothing else
|
|
232
|
+
signal<Line[]>([]).stored("cart") // a signal kept on the device under the key (JSON values): back on the next launch
|
|
232
233
|
|
|
233
234
|
device.platform: "ios" | "android" | "web" // getter, no ()
|
|
234
235
|
device.language: string // "en", "ru", …
|
|
@@ -402,6 +403,8 @@ const tabs = UITabs({
|
|
|
402
403
|
Router.init(tabs) // UITabs IS a UIScreen — present it directly
|
|
403
404
|
// .select(id), .tab (getter), .onSelect(cb(id, i)) — fires on a bar tap, swipe, or select()
|
|
404
405
|
// .badge(id, value) — true = dot, number/string = count pill, false/null/0 clears
|
|
406
|
+
// Ambient, from any screen, no reference threading: UITabs.current (the visible tabs, or null) and
|
|
407
|
+
// UITabs.select("profile") — the twin of UIPager.current / UIPager.push
|
|
405
408
|
// .pager — the UIPager underneath; UIPager.push(detail) from any screen keeps the bar
|
|
406
409
|
// The bar is colored by the theme's roles (accent, textMuted, surface, border) — no keys of its
|
|
407
410
|
// own; one bar's own colors: tabs.bar.theme({ accent, surface }). Custom layout: UIPager below.
|
|
@@ -484,17 +487,17 @@ UIScrollable(...children)
|
|
|
484
487
|
// visible rows (plus a buffer) are mounted. Use it instead of UIScrollable + map() whenever the item count
|
|
485
488
|
// is large, grows over time, or is unknown.
|
|
486
489
|
UIVirtualizedList<T>({
|
|
490
|
+
data?: () => T[], // the rows, read reactively: write the signal, the list follows (new array, keys matched)
|
|
491
|
+
// absent = fed by hand: .setData(items) / .append(...items) / .prepend(...items) / .update(...items) / .removeByKey(...keys)
|
|
492
|
+
// (one feed per list: a list with `data` has no append on its type)
|
|
487
493
|
keyOf: (item: T) => string, // STABLE unique id per item (never the array index)
|
|
488
494
|
render: (item: T) => UINodeChild, // builds one row; called lazily as rows enter the window
|
|
489
495
|
estimatedHeight: number | ((item: T) => number), // px guess per row (real height measured after mount)
|
|
490
496
|
overscan?: number, // extra px mounted above/below the viewport (default: one viewport)
|
|
491
497
|
inverted?: boolean, // true = chat mode: starts scrolled to the end, append at bottom auto-scrolls
|
|
492
498
|
})
|
|
493
|
-
//
|
|
494
|
-
//
|
|
495
|
-
// .append(...items) / .prepend(...items) // variadic; append = newest chat msg; prepend = history, no scroll jump
|
|
496
|
-
// .update(...items) // re-render rows with the same keyOf(); unknown keys ignored
|
|
497
|
-
// .removeByKey(...keys)
|
|
499
|
+
// Rows added at the end scroll a chat at its bottom; rows added before the first (history) move nothing; a row
|
|
500
|
+
// whose item is a new object re-renders. Never re-create the list.
|
|
498
501
|
// .itemCount (getter) / .getItem(key)
|
|
499
502
|
// .scrollTo(offset, animated?) / .scrollToKey(key, animated?) / .scrollToEnd(animated?) / .onScroll(cb(pos))
|
|
500
503
|
// .onEndReached(thresholdPx: number, cb) // threshold FIRST and required; fires near the BOTTOM — load next page
|
|
@@ -522,7 +525,7 @@ UIImage(src) // src: string url | FetchResponse | File | SvgSource | Canvas (se
|
|
|
522
525
|
// .setSourceRect(x, y, w, h) — crop to a sub-rect of the source (texture px); frame fills the box, objectFit
|
|
523
526
|
// ignored. Chainable, safe to call before mount. For atlases/spritesheets — swap the rect to change frame.
|
|
524
527
|
// Note: tintColor applies only to SVG sources — it replaces ALL fill and stroke colors (monochrome icons only)
|
|
525
|
-
// Note: a
|
|
528
|
+
// Note: a project file by its path as a string — UIImage("images/photo.png") — is the bundled asset (the compile resolves it; a path that names no file fails the compile); plain https:// strings are remote URLs
|
|
526
529
|
|
|
527
530
|
// UIVideo — the on-screen surface only; playback lives entirely on the VideoPlayer (see AUDIO / VIDEO above)
|
|
528
531
|
UIVideo(player)
|
|
@@ -552,6 +555,7 @@ UIInput() / UITextArea()
|
|
|
552
555
|
// type: "text"|"password"|"search"|"phone"|"email"|"number"|"decimal"|"url"|"date"|"time"
|
|
553
556
|
// enterKey: "done"|"go"|"next"|"search"|"send" (UIInput only) — return-key label
|
|
554
557
|
// maxLength: number; autocapitalize: "none"|"words"|"sentences"|"characters"; autocorrect: boolean
|
|
558
|
+
// autofocus: boolean — takes the focus as its screen opens (a composer, a chat, a search; once, one per screen)
|
|
555
559
|
// keyboardShrink: boolean (default true) — false: keyboard OVERLAYS the UI, no relayout (chat composers)
|
|
556
560
|
// keyboardDismiss: boolean (default true) — false: taps never dismiss the keyboard (chat composers)
|
|
557
561
|
// $focused: { borderColor, ..., duration? } — style while focused (reserved class)
|
|
@@ -827,6 +831,8 @@ UIImage(heart).style({ width: 24, height: 24, tintColor: "#666" }) // tintColor
|
|
|
827
831
|
// ===== ICONS (assetIcon) =====
|
|
828
832
|
// Use real icons instead of emoji. assetIcon("pack:name") resolves the icon at COMPILE time and
|
|
829
833
|
// inlines it as an image source (same shape as SvgSource) — no imports, no project files, offline.
|
|
834
|
+
// It returns an SvgSource — the same type an imported .svg and SvgSource(xml) give — so a component
|
|
835
|
+
// that takes an icon is typed (icon: SvgSource), never (icon: string).
|
|
830
836
|
UIImage(assetIcon("lucide:bell")).style({ width: 24, height: 24, tintColor: "#8a8f98" })
|
|
831
837
|
// The id must be a string literal; an unknown id is a compile error (with name suggestions).
|
|
832
838
|
// Recolor via the tintColor style or the { color } option (hex literal bakes in; expression tints).
|
|
@@ -881,12 +887,6 @@ UIRow(isGroup ? button : UIColumn()) // WRONG
|
|
|
881
887
|
// ✅ null is skipped in children — no phantom element
|
|
882
888
|
UIRow(isGroup ? button : null)
|
|
883
889
|
|
|
884
|
-
// ❌ pointing UIImage / bgImage at a project file by bare path — it won't resolve to the bundled asset
|
|
885
|
-
UIImage("./photo.png") // WRONG (a plain string works only for remote http(s) URLs)
|
|
886
|
-
// ✅ import the asset, or wrap its path in asset()
|
|
887
|
-
import photo from './photo.png'
|
|
888
|
-
UIImage(photo) // or: UIImage(asset('./photo.png'))
|
|
889
|
-
|
|
890
890
|
// ❌ callback-first edge callbacks on UIVirtualizedList
|
|
891
891
|
list.onEndReached(loadNextPage) // WRONG — threshold is the first argument, and required
|
|
892
892
|
// ✅ threshold (px from the edge) first
|
package/prompts/ui.md
CHANGED
|
@@ -125,7 +125,9 @@ UIScrollable(...children)
|
|
|
125
125
|
// visible rows (plus a buffer) are mounted. Use it instead of UIScrollable + map() whenever the item count
|
|
126
126
|
// is large, grows over time, or is unknown.
|
|
127
127
|
UIVirtualizedList<T>({
|
|
128
|
-
data
|
|
128
|
+
data?: () => T[], // the rows, read reactively: write the signal, the list follows (new array, keys matched)
|
|
129
|
+
// absent = fed by hand: .setData(items) / .append(...items) / .prepend(...items) / .update(...items) / .removeByKey(...keys)
|
|
130
|
+
// (one feed per list: a list with `data` has no append on its type)
|
|
129
131
|
keyOf: (item: T) => string, // STABLE unique id per item (never the array index)
|
|
130
132
|
render: (item: T) => UINodeChild, // builds one row; called lazily as rows enter the window
|
|
131
133
|
estimatedHeight: number | ((item: T) => number), // px guess per row (real height measured after mount)
|
|
@@ -200,7 +200,7 @@ export const uiValue = (v: unknown): ({ kind: number, sig?: string } & TweenValu
|
|
|
200
200
|
|
|
201
201
|
/** Per-function argument counts of a UI transform list (degrees / px are the SDK's units). A
|
|
202
202
|
* translate named with a trailing `%` holds PERCENTS of the target's own box — a screen
|
|
203
|
-
* transition's pose (ui/transitions.ts)
|
|
203
|
+
* transition's pose (ui/transitions.ts) and a style's own `translate(-50%, -50%)` alike. */
|
|
204
204
|
const TRANSFORM_FNS: Record<string, number> = {
|
|
205
205
|
translate: 2, translateX: 1, translateY: 1, rotate: 1, scale: 2, scaleX: 1, scaleY: 1, skewX: 1, skewY: 1,
|
|
206
206
|
"translate%": 2, "translateX%": 1, "translateY%": 1,
|
|
@@ -210,11 +210,10 @@ const TRANSFORM_FNS: Record<string, number> = {
|
|
|
210
210
|
* function is unknown (the caller falls back to matrix decomposition on the host). `scale(2)` and
|
|
211
211
|
* `translate(5px)` complete their second argument like CSS.
|
|
212
212
|
*
|
|
213
|
-
*
|
|
214
|
-
*
|
|
215
|
-
*
|
|
216
|
-
|
|
217
|
-
export const parseTransformList = (s: string, percent = false): { sig: string, lanes: number[] } | null => {
|
|
213
|
+
* A `%` in a translate is a percent of the target's own box (`translateX(100%)` → `translateX%`
|
|
214
|
+
* [100]; a translate mixing the units splits into its two axes), the way the runtime reads a
|
|
215
|
+
* style's transform: the box is measured when the matrix is composed, so a track fits any size. */
|
|
216
|
+
export const parseTransformList = (s: string): { sig: string, lanes: number[] } | null => {
|
|
218
217
|
const sig: string[] = []
|
|
219
218
|
const lanes: number[] = []
|
|
220
219
|
const re = /([a-zA-Z]+)\s*\(([^)]*)\)/g
|
|
@@ -236,17 +235,13 @@ export const parseTransformList = (s: string, percent = false): { sig: string, l
|
|
|
236
235
|
if (args.length < n) { args.push(fn === "translate" ? 0 : args[0]!); units.push(units[0]!) }
|
|
237
236
|
consumed = re.lastIndex
|
|
238
237
|
if (fn.startsWith("translate") && units.includes("%")) {
|
|
239
|
-
if (
|
|
240
|
-
warnOnce("transform:%", `transform: a % in ${fn}() is read as px — percents of the box work in a screen transition only`)
|
|
241
|
-
} else if (fn === "translate" && units[0] !== units[1]) {
|
|
238
|
+
if (fn === "translate" && units[0] !== units[1]) {
|
|
242
239
|
sig.push(units[0] === "%" ? "translateX%" : "translateX", units[1] === "%" ? "translateY%" : "translateY")
|
|
243
|
-
lanes.push(...args)
|
|
244
|
-
continue
|
|
245
240
|
} else {
|
|
246
241
|
sig.push(fn + "%")
|
|
247
|
-
lanes.push(...args)
|
|
248
|
-
continue
|
|
249
242
|
}
|
|
243
|
+
lanes.push(...args)
|
|
244
|
+
continue
|
|
250
245
|
}
|
|
251
246
|
sig.push(fn)
|
|
252
247
|
lanes.push(...args)
|
package/src/chisel.ts
CHANGED
|
@@ -130,11 +130,11 @@ export const chiselVocab: ChiselVocab = {
|
|
|
130
130
|
"onScroll", "onScrollRelease", "onOverscroll", "onRefresh", "onSubmit",
|
|
131
131
|
"onSelect", "onChange", "onFocus", "onBlur", "onOverlayTap", "onDetent",
|
|
132
132
|
"onEndReached", "onStartReached",
|
|
133
|
-
"append", "insert", "remove", "setContent", "keepAlive", "url",
|
|
133
|
+
"append", "insert", "remove", "setContent", "widgets", "keepAlive", "url",
|
|
134
134
|
"detents", "dismissible", "transition", "setSourceRect",
|
|
135
135
|
"push", "pop", "popToRoot", "replace", "select",
|
|
136
|
-
// UITabs / the native views (NativeView, CameraView, MapView, QRScanner)
|
|
137
|
-
"badge", "setData",
|
|
136
|
+
// UITabs / UIVirtualizedList's hand feed / the native views (NativeView, CameraView, MapView, QRScanner)
|
|
137
|
+
"badge", "setData", "update", "prepend", "removeByKey",
|
|
138
138
|
"on", "off", "onReady", "onError", "onTap", "onMove", "onScan",
|
|
139
139
|
"flyTo", "jumpTo", "fitPoints", "setPadding", "setUserLocation",
|
|
140
140
|
],
|
|
@@ -258,13 +258,15 @@ export const compileServerBundle = async (opts: CompileServerOptions): Promise<{
|
|
|
258
258
|
// The server bundle sees the whole project map (a server module may import shared code), but the
|
|
259
259
|
// bundler's asset check runs over EVERY file in the map — so binaries and svgs must be present as
|
|
260
260
|
// stubs, or a client screen's `asset("./x.png")` fails the SERVER build ("asset not found", seen
|
|
261
|
-
// with a Figma-exported design next to app.server.ts).
|
|
262
|
-
//
|
|
261
|
+
// with a Figma-exported design next to app.server.ts). A stub's value is the resource's PATH: a
|
|
262
|
+
// row of shared data that names a picture (`photo: "images/a.jpg"`) reaches the server as the
|
|
263
|
+
// path of that picture, which a seed stores into a file field (files/db.ts). The board's `design/`
|
|
264
|
+
// files are never server code and stay out entirely.
|
|
263
265
|
const files: MemoryFiles = {}
|
|
264
266
|
for (const e of opts.entries) {
|
|
265
267
|
const path = normalizePath(e.path)
|
|
266
268
|
if (/^\/design\//.test(path)) continue
|
|
267
|
-
if (e.type !== "text" || path.toLowerCase().endsWith(".svg")) { files[path] = `export default
|
|
269
|
+
if (e.type !== "text" || path.toLowerCase().endsWith(".svg")) { files[path] = `export default ${JSON.stringify(path)}`; continue }
|
|
268
270
|
files[path] = e.text ?? ""
|
|
269
271
|
}
|
|
270
272
|
let params = opts.params
|
package/src/g2/Scene2D.ts
CHANGED
|
@@ -6,7 +6,7 @@ import { _installAspectFrames, _installTimeScale, _attachSystem, _detachSystem,
|
|
|
6
6
|
import { _bumpNavEpoch, _setCurrent, Presentable, type DestTuple, type DismissOptions, type PresentOptions } from "../ui/presentable"
|
|
7
7
|
import { _transitionId, TRANSITION_NONE } from "../ui/transitions"
|
|
8
8
|
import { TREE_DEST_SCENE2D, ensureEmitter, registerDest } from "../ui/tree"
|
|
9
|
-
import {
|
|
9
|
+
import { _surfaceWidgets, type LayerElement, type WidgetSet } from "../ui/UILayer"
|
|
10
10
|
import { cx, cy, type Vec2Like } from "../math/vec"
|
|
11
11
|
import type { ClickEvent, TouchStartEvent } from "../runtime/touch"
|
|
12
12
|
import { Camera2D } from "./Camera2D"
|
|
@@ -189,14 +189,16 @@ export class Scene2D implements Presentable {
|
|
|
189
189
|
registerDest(TREE_DEST_SCENE2D, this.id, this)
|
|
190
190
|
return [TREE_DEST_SCENE2D, this.id, "", ""]
|
|
191
191
|
}
|
|
192
|
-
/** @internal The scene's
|
|
192
|
+
/** @internal The scene's widget layer (ui/UILayer.ts), made on the first use of `widgets`. */
|
|
193
193
|
_contentLayer?: LayerElement
|
|
194
|
-
/**
|
|
195
|
-
|
|
196
|
-
|
|
194
|
+
/** @internal */
|
|
195
|
+
_widgetSet?: WidgetSet<any>
|
|
196
|
+
/** The widgets over the scene while it is presented — a HUD, a dialog, a sheet: `scene.widgets(hud)`
|
|
197
|
+
* sets them, `widgets.append` / `.remove` manage them, `widgets.list` reads them. Laid out
|
|
198
|
+
* over it; touches outside a widget's box reach the scene. */
|
|
199
|
+
get widgets(): WidgetSet<this> {
|
|
197
200
|
ensureEmitter()
|
|
198
|
-
|
|
199
|
-
return this
|
|
201
|
+
return _surfaceWidgets(this)
|
|
200
202
|
}
|
|
201
203
|
/** @internal presentation lifecycle from the runtime */
|
|
202
204
|
_emitOpen(): void { for (const cb of this.ol.slice()) cb() }
|
package/src/gl/Scene.ts
CHANGED
|
@@ -10,7 +10,7 @@ import { _installAspectFrames, _installTimeScale, _attachSystem, _detachSystem,
|
|
|
10
10
|
import { _bumpNavEpoch, _navEpoch, _setCurrent, Presentable, type DestTuple, type DismissOptions, type PresentOptions } from "../ui/presentable"
|
|
11
11
|
import { _transitionId, TRANSITION_NONE } from "../ui/transitions"
|
|
12
12
|
import { TREE_DEST_SCENE3D, TREE_FLAG_BACK, ensureEmitter, registerDest, unregisterDest } from "../ui/tree"
|
|
13
|
-
import {
|
|
13
|
+
import { _surfaceWidgets, type LayerElement, type WidgetSet } from "../ui/UILayer"
|
|
14
14
|
import { device } from "../runtime/device"
|
|
15
15
|
import type { ClickEvent, TouchStartEvent } from "../runtime/touch"
|
|
16
16
|
import type { FetchResponse } from "../runtime/fetch"
|
|
@@ -458,14 +458,16 @@ export class Scene implements Presentable {
|
|
|
458
458
|
registerDest(TREE_DEST_SCENE3D, this._id, this)
|
|
459
459
|
return [TREE_DEST_SCENE3D, this._id, "", ""]
|
|
460
460
|
}
|
|
461
|
-
/** @internal The scene's
|
|
461
|
+
/** @internal The scene's widget layer (ui/UILayer.ts), made on the first use of `widgets`. */
|
|
462
462
|
_contentLayer?: LayerElement
|
|
463
|
-
/**
|
|
464
|
-
|
|
465
|
-
|
|
463
|
+
/** @internal */
|
|
464
|
+
_widgetSet?: WidgetSet<any>
|
|
465
|
+
/** The widgets over the scene while it is presented — a HUD, a dialog, a sheet: `scene.widgets(hud)`
|
|
466
|
+
* sets them, `widgets.append` / `.remove` manage them, `widgets.list` reads them. Laid out
|
|
467
|
+
* over it; touches outside a widget's box reach the scene. */
|
|
468
|
+
get widgets(): WidgetSet<this> {
|
|
466
469
|
ensureEmitter()
|
|
467
|
-
|
|
468
|
-
return this
|
|
470
|
+
return _surfaceWidgets(this)
|
|
469
471
|
}
|
|
470
472
|
/** @internal presentation lifecycle from the runtime */
|
|
471
473
|
_emitOpen(): void { for (const cb of this.ol.slice()) cb() }
|