lecodes-cli 0.19.0 → 0.19.2
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/index.js +730 -235
- package/package.json +14 -13
- package/runtime/scene-harness.json +1 -1
- package/runtime/sdk-types.json +1 -1
- package/src/api.ts +302 -0
- package/src/browserAuth.ts +87 -0
- package/src/cmgenTool.ts +104 -0
- package/src/commands/app.ts +891 -0
- package/src/commands/appAndroid.ts +603 -0
- package/src/commands/appDesktop.ts +326 -0
- package/src/commands/appDesktopMac.ts +470 -0
- package/src/commands/appIcon.ts +187 -0
- package/src/commands/appShared.ts +448 -0
- package/src/commands/appTemplates.ts +883 -0
- package/src/commands/appTemplatesAndroid.ts +599 -0
- package/src/commands/appTemplatesGradlew.ts +9 -0
- package/src/commands/assets.ts +28 -0
- package/src/commands/clone.ts +60 -0
- package/src/commands/compile.ts +253 -0
- package/src/commands/create.ts +59 -0
- package/src/commands/design.ts +863 -0
- package/src/commands/designTemplates.ts +11 -0
- package/src/commands/desktop.ts +122 -0
- package/src/commands/dev.ts +214 -0
- package/src/commands/diff.ts +59 -0
- package/src/commands/init.ts +191 -0
- package/src/commands/install.ts +198 -0
- package/src/commands/lightmap.ts +290 -0
- package/src/commands/link.ts +147 -0
- package/src/commands/login.ts +24 -0
- package/src/commands/navmesh.ts +225 -0
- package/src/commands/pn.ts +305 -0
- package/src/commands/projectTemplates.ts +129 -0
- package/src/commands/pull.ts +109 -0
- package/src/commands/push.ts +173 -0
- package/src/commands/render.ts +414 -0
- package/src/commands/scene.ts +148 -0
- package/src/commands/shaders.ts +157 -0
- package/src/commands/status.ts +29 -0
- package/src/commands/test.ts +370 -0
- package/src/commands/thumbs.ts +178 -0
- package/src/commands/types.ts +87 -0
- package/src/commands/update.ts +190 -0
- package/src/compile/assetIcons.ts +214 -0
- package/src/compile/collect.ts +46 -0
- package/src/compile/collectLocal.ts +37 -0
- package/src/compile/designCompile.ts +104 -0
- package/src/compile/fonts.ts +156 -0
- package/src/compile/headlessBundle.ts +129 -0
- package/src/compile/nativeStack.ts +21 -0
- package/src/compile/projectCompile.ts +109 -0
- package/src/compile/sceneCompile.ts +113 -0
- package/src/compile/screenEntry.ts +127 -0
- package/src/compile/shaders.ts +245 -0
- package/src/config.ts +42 -0
- package/src/designMeta.ts +35 -0
- package/src/desktopRenderer.ts +532 -0
- package/src/desktopScript.ts +276 -0
- package/src/dev/clientTemplates.ts +160 -0
- package/src/dev/devServer.ts +292 -0
- package/src/dev/wsServer.ts +144 -0
- package/src/distRoot.ts +20 -0
- package/src/ignore.ts +163 -0
- package/src/index.ts +491 -0
- package/src/lecodes-3d-editor.d.ts +41 -0
- package/src/lecodes-assets.d.ts +7 -0
- package/src/lecodes-design.d.ts +191 -0
- package/src/lecodes-renderer.d.ts +131 -0
- package/src/localFiles.ts +144 -0
- package/src/manifest.ts +46 -0
- package/src/matcTool.ts +137 -0
- package/src/peers.ts +40 -0
- package/src/project.ts +61 -0
- package/src/projectEnv.ts +94 -0
- package/src/qrcode-terminal.d.ts +9 -0
- package/src/releases.ts +125 -0
- package/src/serverDiff.ts +78 -0
- package/src/textDiff.ts +103 -0
- package/src/types.ts +0 -0
- package/src/util.ts +146 -0
- package/runtime/sdk/animate/animate.ts +0 -238
- package/runtime/sdk/animate/bezier.ts +0 -138
- package/runtime/sdk/animate/easings.ts +0 -126
- package/runtime/sdk/canvas/Canvas.ts +0 -305
- package/runtime/sdk/compile/aspectMacro.ts +0 -86
- package/runtime/sdk/compile/assetIconMacro.ts +0 -384
- package/runtime/sdk/compile/assetMacro.ts +0 -146
- package/runtime/sdk/compile/assetName.ts +0 -50
- package/runtime/sdk/compile/bundler.ts +0 -287
- package/runtime/sdk/compile/compileProject.ts +0 -129
- package/runtime/sdk/compile/detectEntry.ts +0 -128
- package/runtime/sdk/compile/fontMacro.ts +0 -459
- package/runtime/sdk/compile/fontRegistry.ts +0 -78
- package/runtime/sdk/compile/header.ts +0 -67
- package/runtime/sdk/compile/index.ts +0 -100
- package/runtime/sdk/compile/libraryImports.ts +0 -52
- package/runtime/sdk/compile/liteMaterial.ts +0 -247
- package/runtime/sdk/compile/sceneEditor.ts +0 -88
- package/runtime/sdk/compile/serverSplit.ts +0 -233
- package/runtime/sdk/compile/serverTypes.ts +0 -227
- package/runtime/sdk/compile/sfnt.ts +0 -98
- package/runtime/sdk/compile/shaderSchema.ts +0 -202
- package/runtime/sdk/compile/shaderTargets.ts +0 -81
- package/runtime/sdk/compile/sourcemap.ts +0 -25
- package/runtime/sdk/core/Aspect.ts +0 -568
- package/runtime/sdk/core/InspectorUI.ts +0 -212
- package/runtime/sdk/core/StateMachine.ts +0 -308
- package/runtime/sdk/core/color.ts +0 -66
- package/runtime/sdk/core/compWrite.ts +0 -42
- package/runtime/sdk/core/events.ts +0 -38
- package/runtime/sdk/core/fields.ts +0 -120
- package/runtime/sdk/core/registry.ts +0 -23
- package/runtime/sdk/core/signals.ts +0 -277
- package/runtime/sdk/core/time.ts +0 -84
- package/runtime/sdk/g2/Camera2D.ts +0 -40
- package/runtime/sdk/g2/CharacterController2D.ts +0 -276
- package/runtime/sdk/g2/Node2D.ts +0 -267
- package/runtime/sdk/g2/OneWay2D.ts +0 -66
- package/runtime/sdk/g2/Physics2D.ts +0 -346
- package/runtime/sdk/g2/Scene2D.ts +0 -209
- package/runtime/sdk/g2/Shape2D.ts +0 -259
- package/runtime/sdk/g2/Sprite.ts +0 -89
- package/runtime/sdk/g2/SpriteAnimation.ts +0 -171
- package/runtime/sdk/g2/SpriteSheet.ts +0 -166
- package/runtime/sdk/g2/Texture2D.ts +0 -47
- package/runtime/sdk/g2/Tilemap.ts +0 -41
- package/runtime/sdk/g2/Tileset.ts +0 -71
- package/runtime/sdk/g2/Trigger2D.ts +0 -77
- package/runtime/sdk/g2/autotile.ts +0 -433
- package/runtime/sdk/g2/cells.ts +0 -91
- package/runtime/sdk/g2/defineScene2d.ts +0 -381
- package/runtime/sdk/g2/groups2d.ts +0 -106
- package/runtime/sdk/g2/loop.ts +0 -50
- package/runtime/sdk/g2/scenarios2d.ts +0 -69
- package/runtime/sdk/g2/touch.ts +0 -83
- package/runtime/sdk/gl/Camera.ts +0 -160
- package/runtime/sdk/gl/CameraPlace.ts +0 -52
- package/runtime/sdk/gl/CharacterController.ts +0 -238
- package/runtime/sdk/gl/Gearbox.ts +0 -212
- package/runtime/sdk/gl/Geometry.ts +0 -279
- package/runtime/sdk/gl/IK.ts +0 -193
- package/runtime/sdk/gl/InstancedMesh.ts +0 -132
- package/runtime/sdk/gl/Light.ts +0 -107
- package/runtime/sdk/gl/Lightmap.ts +0 -256
- package/runtime/sdk/gl/Material.ts +0 -260
- package/runtime/sdk/gl/Mesh.ts +0 -83
- package/runtime/sdk/gl/Model.ts +0 -124
- package/runtime/sdk/gl/NavAgent.ts +0 -337
- package/runtime/sdk/gl/NavMesh.ts +0 -397
- package/runtime/sdk/gl/Node.ts +0 -350
- package/runtime/sdk/gl/Noise.ts +0 -30
- package/runtime/sdk/gl/Particles.ts +0 -676
- package/runtime/sdk/gl/Physics.ts +0 -222
- package/runtime/sdk/gl/Plane.ts +0 -53
- package/runtime/sdk/gl/Ragdoll.ts +0 -270
- package/runtime/sdk/gl/Ray.ts +0 -16
- package/runtime/sdk/gl/Scene.ts +0 -486
- package/runtime/sdk/gl/Shape.ts +0 -409
- package/runtime/sdk/gl/Terrain.ts +0 -1092
- package/runtime/sdk/gl/Texture.ts +0 -63
- package/runtime/sdk/gl/Trigger.ts +0 -45
- package/runtime/sdk/gl/Vehicle.ts +0 -473
- package/runtime/sdk/gl/Wheel.ts +0 -240
- package/runtime/sdk/gl/animation/AnimationClip.ts +0 -204
- package/runtime/sdk/gl/animation/Animator.ts +0 -120
- package/runtime/sdk/gl/animation/Layer.ts +0 -31
- package/runtime/sdk/gl/animation/Loop.ts +0 -30
- package/runtime/sdk/gl/animation/Playback.ts +0 -44
- package/runtime/sdk/gl/animation/core.ts +0 -336
- package/runtime/sdk/gl/controls.ts +0 -95
- package/runtime/sdk/gl/physicsEvents.ts +0 -20
- package/runtime/sdk/gl/scenarios.ts +0 -291
- package/runtime/sdk/gl/state.ts +0 -6
- package/runtime/sdk/gl/terrainMesh.ts +0 -219
- package/runtime/sdk/gl/touch.ts +0 -68
- package/runtime/sdk/inject.ts +0 -197
- package/runtime/sdk/math/Mathf.ts +0 -118
- package/runtime/sdk/math/mat4.ts +0 -278
- package/runtime/sdk/math/quat.ts +0 -232
- package/runtime/sdk/math/vec.ts +0 -255
- package/runtime/sdk/net/codec.ts +0 -119
- package/runtime/sdk/net/core.ts +0 -384
- package/runtime/sdk/net/index.ts +0 -181
- package/runtime/sdk/net/replication.ts +0 -622
- package/runtime/sdk/plugins/camera.ts +0 -81
- package/runtime/sdk/plugins/geolocation.ts +0 -123
- package/runtime/sdk/plugins/oauth.ts +0 -61
- package/runtime/sdk/plugins/permission.ts +0 -7
- package/runtime/sdk/plugins/push.ts +0 -132
- package/runtime/sdk/plugins/qr.ts +0 -73
- package/runtime/sdk/plugins/service.ts +0 -47
- package/runtime/sdk/runtime/app.ts +0 -101
- package/runtime/sdk/runtime/appEvents.ts +0 -54
- package/runtime/sdk/runtime/channel.ts +0 -61
- package/runtime/sdk/runtime/clipboard.ts +0 -20
- package/runtime/sdk/runtime/datetime.ts +0 -329
- package/runtime/sdk/runtime/device.ts +0 -293
- package/runtime/sdk/runtime/fetch.ts +0 -77
- package/runtime/sdk/runtime/files.ts +0 -23
- package/runtime/sdk/runtime/input.ts +0 -175
- package/runtime/sdk/runtime/media.ts +0 -111
- package/runtime/sdk/runtime/misc.ts +0 -16
- package/runtime/sdk/runtime/net.ts +0 -36
- package/runtime/sdk/runtime/rpc.ts +0 -218
- package/runtime/sdk/runtime/service.ts +0 -83
- package/runtime/sdk/runtime/share.ts +0 -9
- package/runtime/sdk/runtime/storage.ts +0 -13
- package/runtime/sdk/runtime/touch.ts +0 -76
- package/runtime/sdk/scene/defineScene.ts +0 -1353
- package/runtime/sdk/scene/editorPlugins.ts +0 -110
- package/runtime/sdk/scene/gizmos.ts +0 -148
- package/runtime/sdk/scene/grammar.ts +0 -120
- package/runtime/sdk/scene/material.ts +0 -188
- package/runtime/sdk/server/auth/appConfig.ts +0 -12
- package/runtime/sdk/server/auth/global.ts +0 -80
- package/runtime/sdk/server/auth/host.ts +0 -318
- package/runtime/sdk/server/auth/models.ts +0 -83
- package/runtime/sdk/server/auth/types.ts +0 -50
- package/runtime/sdk/server/channel.ts +0 -56
- package/runtime/sdk/server/context.ts +0 -36
- package/runtime/sdk/server/db/defineDb.ts +0 -237
- package/runtime/sdk/server/db/fields.ts +0 -132
- package/runtime/sdk/server/db/httpTransport.ts +0 -93
- package/runtime/sdk/server/db/index.ts +0 -7
- package/runtime/sdk/server/db/marci/query.ts +0 -412
- package/runtime/sdk/server/db/types.ts +0 -202
- package/runtime/sdk/server/errors.ts +0 -12
- package/runtime/sdk/server/host.ts +0 -74
- package/runtime/sdk/server/inject.ts +0 -13
- package/runtime/sdk/server/runtime.ts +0 -133
- package/runtime/sdk/server/validate.ts +0 -87
- package/runtime/sdk/ui/NativeView.ts +0 -142
- package/runtime/sdk/ui/UI.ts +0 -39
- package/runtime/sdk/ui/UIBottomSheet.ts +0 -139
- package/runtime/sdk/ui/UIButton.ts +0 -101
- package/runtime/sdk/ui/UIContainer.ts +0 -60
- package/runtime/sdk/ui/UIImage.ts +0 -83
- package/runtime/sdk/ui/UIInput.ts +0 -185
- package/runtime/sdk/ui/UIModal.ts +0 -139
- package/runtime/sdk/ui/UINode.ts +0 -830
- package/runtime/sdk/ui/UIPager.ts +0 -362
- package/runtime/sdk/ui/UIPopover.ts +0 -100
- package/runtime/sdk/ui/UIScreen.ts +0 -123
- package/runtime/sdk/ui/UIScrollable.ts +0 -87
- package/runtime/sdk/ui/UISpacer.ts +0 -14
- package/runtime/sdk/ui/UITabs.ts +0 -236
- package/runtime/sdk/ui/UIText.ts +0 -51
- package/runtime/sdk/ui/UIVideo.ts +0 -88
- package/runtime/sdk/ui/UIVirtualizedList.ts +0 -241
- package/runtime/sdk/ui/UIWidget.ts +0 -127
- package/runtime/sdk/ui/fonts.ts +0 -13
- package/runtime/sdk/ui/presentable.ts +0 -117
- package/runtime/sdk/ui/router.ts +0 -132
- package/runtime/sdk/ui/theme.ts +0 -84
|
@@ -1,74 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* `sdk/server` — the HOST side of the app backend, imported normally by the runner
|
|
3
|
-
* (packages/lecodes-runner), the backend and tests. Nothing here is bundled into project code; the
|
|
4
|
-
* bundle carries its own SDK copy and meets this module only through the `globalThis.__lecodes*`
|
|
5
|
-
* seams (see ./runtime.ts).
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
export {
|
|
9
|
-
loadServerModules, createInvoker, createJoinGuard, setChannelPublisher, setRequestProvider,
|
|
10
|
-
} from "./runtime"
|
|
11
|
-
export type { LoadedServer, InvokeResult, InvokerOptions, ServerRegistration } from "./runtime"
|
|
12
|
-
export type { RequestContext } from "./context"
|
|
13
|
-
export { setDbTransport } from "./db/defineDb"
|
|
14
|
-
export { marciHttpTransport, marciRequest, encodeMarciId, MarciHttpError } from "./db/httpTransport"
|
|
15
|
-
export type { MarciHttpOptions } from "./db/httpTransport"
|
|
16
|
-
export type { MarciOp, MarciTransport } from "./db/marci/query"
|
|
17
|
-
export type { ServerManifest, ServerExport } from "../compile/serverSplit"
|
|
18
|
-
export { validateArgs, ValidationError } from "./validate"
|
|
19
|
-
export type { ParamSchema, Schema as ParamSchemaNode } from "./validate"
|
|
20
|
-
export { createAuthHost, signNonce, verifyNonce, sha256 } from "./auth/host"
|
|
21
|
-
export type { AuthHost, AuthHostConfig, AuthMail } from "./auth/host"
|
|
22
|
-
export type { AuthUser, AuthState, AuthOps, OAuthProfile, OAuthCredential } from "./auth/types"
|
|
23
|
-
export type { AuthProvider, AppConfig } from "./auth/appConfig"
|
|
24
|
-
|
|
25
|
-
import type { LoadedServer } from "./runtime"
|
|
26
|
-
import type { Field, FieldDef } from "./db/fields"
|
|
27
|
-
import type { Model } from "./db/types"
|
|
28
|
-
|
|
29
|
-
/** JSON view of one field for the platform (Data tab, docs): `FieldDef` with nested struct fields flattened
|
|
30
|
-
* to defs and functions/undefineds dropped. */
|
|
31
|
-
export type FieldDescription = Omit<FieldDef, "fields" | "default"> & { fields?: Record<string, FieldDescription>, default?: unknown }
|
|
32
|
-
|
|
33
|
-
/** What the runner reports to the platform after loading a bundle: schema text + models + the
|
|
34
|
-
* endpoint/channel lists (with the manifest's param schemas). */
|
|
35
|
-
export type ServerDescription = {
|
|
36
|
-
/** `.marci` schema text ("" when the project defines no db). */
|
|
37
|
-
marci: string
|
|
38
|
-
models: Record<string, { fields: Record<string, FieldDescription> }>
|
|
39
|
-
endpoints: { id: string, params: unknown[] | null }[]
|
|
40
|
-
channels: string[]
|
|
41
|
-
/** Which models the auth runtime binds to (`null` = the project has no db) and the app.json providers. */
|
|
42
|
-
auth: { user: string | null, session: string | null, providers: string[] }
|
|
43
|
-
/** `app.json` name (when the compile embedded one). */
|
|
44
|
-
appName?: string
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
const describeField = (f: Field<any, any>): FieldDescription => {
|
|
48
|
-
const def = f.def
|
|
49
|
-
const out: FieldDescription = { ...def, fields: undefined, default: undefined }
|
|
50
|
-
delete out.fields
|
|
51
|
-
delete out.default
|
|
52
|
-
if (def.hasDefault) out.default = def.default === undefined ? null : def.default
|
|
53
|
-
if (def.fields) out.fields = Object.fromEntries(Object.entries(def.fields).map(([k, v]) => [k, describeField(v)]))
|
|
54
|
-
return JSON.parse(JSON.stringify(out))
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
export const describeServer = (server: LoadedServer): ServerDescription => {
|
|
58
|
-
const db = server.dbs[0]
|
|
59
|
-
const models: ServerDescription["models"] = {}
|
|
60
|
-
if (db) {
|
|
61
|
-
for (const [name, m] of Object.entries(db.$models as Record<string, Model>)) {
|
|
62
|
-
models[name] = { fields: Object.fromEntries(Object.entries(m.fields as Record<string, Field<any, any>>).map(([k, f]) => [k, describeField(f)])) }
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
const params = (server.manifest.params ?? {}) as Record<string, unknown[]>
|
|
66
|
-
return {
|
|
67
|
-
marci: db?.$schema ?? "",
|
|
68
|
-
models,
|
|
69
|
-
endpoints: [...server.endpoints.keys()].map(id => ({ id, params: params[id] ?? null })),
|
|
70
|
-
channels: [...server.channels.keys()],
|
|
71
|
-
auth: { user: db?.$auth.user ?? null, session: db?.$auth.session ?? null, providers: server.manifest.app?.auth?.providers ?? [] },
|
|
72
|
-
appName: server.manifest.app?.name,
|
|
73
|
-
}
|
|
74
|
-
}
|
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
/*
|
|
2
|
-
* The SERVER inject module — the globals available in `*.server.ts` files (docs/backend-plan.md §4).
|
|
3
|
-
* chisel injects these into the server bundle exactly like ./inject.ts does for the client bundle;
|
|
4
|
-
* nothing here may reach the client bundle. Keep this the ONLY file that exports server globals.
|
|
5
|
-
*
|
|
6
|
-
* Globals: t, model, defineDb, ref (schema + queries), ApiError, channel, request, auth.
|
|
7
|
-
*/
|
|
8
|
-
|
|
9
|
-
export { t, model, defineDb, ref } from "./db"
|
|
10
|
-
export { ApiError } from "./errors"
|
|
11
|
-
export { channel } from "./channel"
|
|
12
|
-
export { request } from "./context"
|
|
13
|
-
export { auth } from "./auth/global"
|
|
@@ -1,133 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Host side of a compiled server bundle — used by the runner (packages/lecodes-runner) and by tests.
|
|
3
|
-
* Imported normally (Node/Bun), NOT through chisel: the bundle carries its own SDK copy and talks
|
|
4
|
-
* to this module only through globals (`__lecodesServer`, `__lecodesRequest`, `__lecodesPublish`).
|
|
5
|
-
*
|
|
6
|
-
* evaluate the bundle (it sets globalThis.__lecodesServer)
|
|
7
|
-
* const server = loadServerModules() // classify exports, name channels
|
|
8
|
-
* const invoke = createInvoker(server) // validate → run in request context → envelope
|
|
9
|
-
* await invoke("notes.server.ts#listNotes", [20], { headers, ip })
|
|
10
|
-
*/
|
|
11
|
-
|
|
12
|
-
import { AsyncLocalStorage } from "node:async_hooks"
|
|
13
|
-
import type { ServerManifest } from "../compile/serverSplit"
|
|
14
|
-
import type { AuthHost } from "./auth/host"
|
|
15
|
-
import type { AuthState } from "./auth/types"
|
|
16
|
-
import { setChannelPublisher, type Channel } from "./channel"
|
|
17
|
-
import { setRequestProvider, type RequestContext } from "./context"
|
|
18
|
-
import type { Db } from "./db/types"
|
|
19
|
-
import { takeRegisteredDbs } from "./db/defineDb"
|
|
20
|
-
import { validateArgs, ValidationError, type ParamSchema } from "./validate"
|
|
21
|
-
|
|
22
|
-
export type ServerRegistration = { modules: Record<string, Record<string, unknown>>, manifest: ServerManifest }
|
|
23
|
-
|
|
24
|
-
export type LoadedServer = {
|
|
25
|
-
endpoints: Map<string, (...args: any[]) => unknown>
|
|
26
|
-
channels: Map<string, Channel<any>>
|
|
27
|
-
/** Every `defineDb` handle — exported or not (the bundle registers them on globalThis; normally exactly one). */
|
|
28
|
-
dbs: Db<any>[]
|
|
29
|
-
manifest: ServerManifest
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
const isDb = (v: unknown): v is Db<any> => typeof v === "object" && v !== null && typeof (v as Db<any>).$schema === "string"
|
|
33
|
-
const isChannel = (v: unknown): v is Channel<any> => typeof v === "object" && v !== null && (v as Channel<any>).__channel === true
|
|
34
|
-
|
|
35
|
-
/** Read the registration a server bundle left on `globalThis.__lecodesServer` (or a given one). */
|
|
36
|
-
export const loadServerModules = (
|
|
37
|
-
reg: ServerRegistration | undefined = (globalThis as any).__lecodesServer,
|
|
38
|
-
): LoadedServer => {
|
|
39
|
-
if (!reg) throw new Error("loadServerModules: no server bundle registered (globalThis.__lecodesServer)")
|
|
40
|
-
const out: LoadedServer = { endpoints: new Map(), channels: new Map(), dbs: [], manifest: reg.manifest }
|
|
41
|
-
for (const [mod, exports] of Object.entries(reg.modules)) {
|
|
42
|
-
for (const [name, value] of Object.entries(exports)) {
|
|
43
|
-
const id = `${mod}#${name}`
|
|
44
|
-
if (typeof value === "function") out.endpoints.set(id, value as (...args: any[]) => unknown)
|
|
45
|
-
else if (isChannel(value)) { value.__id = id; out.channels.set(id, value) }
|
|
46
|
-
else if (isDb(value)) out.dbs.push(value)
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
// Non-exported handles (`const db = defineDb(...)`): the bundle's SDK copy registered them on globalThis.
|
|
50
|
-
for (const db of takeRegisteredDbs()) if (!out.dbs.includes(db)) out.dbs.push(db)
|
|
51
|
-
return out
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
/** The wire envelope. `session` = a token the client must store from now on (new guest session / rotation). */
|
|
55
|
-
export type InvokeResult =
|
|
56
|
-
| { ok: true, result: unknown, session?: string }
|
|
57
|
-
| { ok: false, status: number, message: string, session?: string }
|
|
58
|
-
|
|
59
|
-
export type InvokerOptions = {
|
|
60
|
-
/** Validate args against the manifest's param schemas (default true; endpoints without a schema pass). */
|
|
61
|
-
validate?: boolean
|
|
62
|
-
/** Where unexpected (non-ApiError) failures are reported. Default: console.error. */
|
|
63
|
-
onError?: (id: string, error: unknown) => void
|
|
64
|
-
/** The auth host (./auth/host.ts): resolves the bearer token into `ctx.auth` before each call. Absent = no sessions. */
|
|
65
|
-
auth?: AuthHost | null
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
// The bundle's ApiError is a different class object than ours (its own SDK copy) — duck-type it.
|
|
69
|
-
const apiStatus = (e: unknown): number | null =>
|
|
70
|
-
e && typeof e === "object" && (e as { name?: string }).name === "ApiError" && typeof (e as { status?: unknown }).status === "number"
|
|
71
|
-
? (e as { status: number }).status : null
|
|
72
|
-
|
|
73
|
-
/**
|
|
74
|
-
* Build the invoker: `invoke(id, args, ctx)` runs endpoint `id` inside an AsyncLocalStorage-scoped
|
|
75
|
-
* request context (what the `request`/`auth` globals read) and returns the wire envelope.
|
|
76
|
-
*/
|
|
77
|
-
// One request store for endpoints AND channel guards (a second store would steal the provider).
|
|
78
|
-
const als = new AsyncLocalStorage<RequestContext>()
|
|
79
|
-
const installProvider = () => setRequestProvider(() => als.getStore())
|
|
80
|
-
|
|
81
|
-
// A token minted/rotated during the call rides on the envelope (the client transport stores it).
|
|
82
|
-
const withSession = (r: InvokeResult, rc: RequestContext): InvokeResult => {
|
|
83
|
-
const issued = (rc.auth as AuthState | undefined)?.issuedToken
|
|
84
|
-
return issued ? { ...r, session: issued } : r
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
export const createInvoker = (server: LoadedServer, opts: InvokerOptions = {}) => {
|
|
88
|
-
installProvider()
|
|
89
|
-
const validate = opts.validate ?? true
|
|
90
|
-
const onError = opts.onError ?? ((id, e) => console.error(`[server] ${id}:`, e))
|
|
91
|
-
const params = server.manifest.params as Record<string, ParamSchema[]> | undefined
|
|
92
|
-
const auth = opts.auth ?? null
|
|
93
|
-
|
|
94
|
-
return async (id: string, args: unknown[], ctx: Omit<RequestContext, "id"> = { headers: {} }): Promise<InvokeResult> => {
|
|
95
|
-
const fn = server.endpoints.get(id)
|
|
96
|
-
if (!fn) return { ok: false, status: 404, message: `Unknown endpoint ${id}` }
|
|
97
|
-
if (!Array.isArray(args)) return { ok: false, status: 400, message: "args must be an array" }
|
|
98
|
-
const rc: RequestContext = { ...ctx, id }
|
|
99
|
-
try {
|
|
100
|
-
if (validate && params?.[id]) validateArgs(params[id], args)
|
|
101
|
-
if (auth) await auth.resolve(rc)
|
|
102
|
-
const result = await als.run(rc, () => fn(...args))
|
|
103
|
-
return withSession({ ok: true, result: result === undefined ? null : result }, rc)
|
|
104
|
-
} catch (e) {
|
|
105
|
-
const status = apiStatus(e)
|
|
106
|
-
if (status !== null) return withSession({ ok: false, status, message: (e as Error).message }, rc)
|
|
107
|
-
if (e instanceof ValidationError) return { ok: false, status: 400, message: e.message }
|
|
108
|
-
onError(id, e)
|
|
109
|
-
return withSession({ ok: false, status: 500, message: "Internal error" }, rc)
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
/** Run a channel's `onJoin` guard for a subscription (same request scoping as endpoints). */
|
|
115
|
-
export const createJoinGuard = (server: LoadedServer, opts: Pick<InvokerOptions, "auth"> = {}) => {
|
|
116
|
-
installProvider()
|
|
117
|
-
const auth = opts.auth ?? null
|
|
118
|
-
return async (channelId: string, topic: string, ctx: Omit<RequestContext, "id"> = { headers: {} }): Promise<InvokeResult> => {
|
|
119
|
-
const ch = server.channels.get(channelId)
|
|
120
|
-
if (!ch) return { ok: false, status: 404, message: `Unknown channel ${channelId}` }
|
|
121
|
-
const rc: RequestContext = { ...ctx, id: channelId }
|
|
122
|
-
try {
|
|
123
|
-
if (auth) await auth.resolve(rc)
|
|
124
|
-
await als.run(rc, () => ch.options.onJoin?.(topic))
|
|
125
|
-
return withSession({ ok: true, result: null }, rc)
|
|
126
|
-
} catch (e) {
|
|
127
|
-
const status = apiStatus(e)
|
|
128
|
-
return withSession({ ok: false, status: status ?? 500, message: status !== null ? (e as Error).message : "Internal error" }, rc)
|
|
129
|
-
}
|
|
130
|
-
}
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
export { setChannelPublisher, setRequestProvider }
|
|
@@ -1,87 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Runtime validation of endpoint arguments against the compact schemas the compiler derives from the
|
|
3
|
-
* TypeScript parameter types (src/compile/serverTypes.ts). Dependency-free; the schema is JSON so it
|
|
4
|
-
* travels inside the server bundle's manifest.
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
export type Schema =
|
|
8
|
-
| { t: "any" }
|
|
9
|
-
| { t: "string" } | { t: "number" } | { t: "boolean" } | { t: "null" } | { t: "undefined" }
|
|
10
|
-
| { t: "lit", v: string | number | boolean }
|
|
11
|
-
| { t: "array", of: Schema }
|
|
12
|
-
| { t: "tuple", items: Schema[], rest?: Schema }
|
|
13
|
-
| { t: "object", props: Record<string, Schema>, optional?: string[], index?: Schema }
|
|
14
|
-
| { t: "union", of: Schema[] }
|
|
15
|
-
|
|
16
|
-
/** One endpoint parameter: its schema and whether it may be omitted (`?`, default value, rest). */
|
|
17
|
-
export type ParamSchema = { name: string, schema: Schema, optional?: boolean, rest?: boolean }
|
|
18
|
-
|
|
19
|
-
export class ValidationError extends Error {
|
|
20
|
-
readonly status = 400
|
|
21
|
-
constructor(message: string) { super(message); this.name = "ValidationError" }
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
const typeName = (v: unknown) => v === null ? "null" : Array.isArray(v) ? "array" : typeof v
|
|
25
|
-
|
|
26
|
-
/** Returns the first problem as a message, or null when `value` matches. */
|
|
27
|
-
export const check = (schema: Schema, value: unknown, path: string): string | null => {
|
|
28
|
-
switch (schema.t) {
|
|
29
|
-
case "any": return null
|
|
30
|
-
case "string": case "number": case "boolean":
|
|
31
|
-
return typeof value === schema.t ? null : `${path}: expected ${schema.t}, got ${typeName(value)}`
|
|
32
|
-
case "null": return value === null ? null : `${path}: expected null, got ${typeName(value)}`
|
|
33
|
-
case "undefined": return value === undefined ? null : `${path}: expected undefined, got ${typeName(value)}`
|
|
34
|
-
case "lit": return value === schema.v ? null : `${path}: expected ${JSON.stringify(schema.v)}, got ${JSON.stringify(value)}`
|
|
35
|
-
case "array":
|
|
36
|
-
if (!Array.isArray(value)) return `${path}: expected array, got ${typeName(value)}`
|
|
37
|
-
for (let i = 0; i < value.length; i++) { const e = check(schema.of, value[i], `${path}[${i}]`); if (e) return e }
|
|
38
|
-
return null
|
|
39
|
-
case "tuple": {
|
|
40
|
-
if (!Array.isArray(value)) return `${path}: expected tuple, got ${typeName(value)}`
|
|
41
|
-
if (value.length < schema.items.length || (!schema.rest && value.length > schema.items.length))
|
|
42
|
-
return `${path}: expected ${schema.items.length} items, got ${value.length}`
|
|
43
|
-
for (let i = 0; i < value.length; i++) {
|
|
44
|
-
const s = i < schema.items.length ? schema.items[i] : schema.rest!
|
|
45
|
-
const e = check(s, value[i], `${path}[${i}]`); if (e) return e
|
|
46
|
-
}
|
|
47
|
-
return null
|
|
48
|
-
}
|
|
49
|
-
case "object": {
|
|
50
|
-
if (typeof value !== "object" || value === null || Array.isArray(value)) return `${path}: expected object, got ${typeName(value)}`
|
|
51
|
-
const obj = value as Record<string, unknown>
|
|
52
|
-
const optional = new Set(schema.optional ?? [])
|
|
53
|
-
for (const [k, s] of Object.entries(schema.props)) {
|
|
54
|
-
if (!(k in obj) || obj[k] === undefined) { if (optional.has(k)) continue; return `${path}.${k}: required` }
|
|
55
|
-
const e = check(s, obj[k], `${path}.${k}`); if (e) return e
|
|
56
|
-
}
|
|
57
|
-
for (const k of Object.keys(obj)) {
|
|
58
|
-
if (k in schema.props) continue
|
|
59
|
-
if (schema.index) { const e = check(schema.index, obj[k], `${path}.${k}`); if (e) return e }
|
|
60
|
-
// unknown keys without an index signature are ignored (structural typing)
|
|
61
|
-
}
|
|
62
|
-
return null
|
|
63
|
-
}
|
|
64
|
-
case "union": {
|
|
65
|
-
const errors: string[] = []
|
|
66
|
-
for (const s of schema.of) { const e = check(s, value, path); if (!e) return null; errors.push(e) }
|
|
67
|
-
return `${path}: no union member matched (${errors.join("; ")})`
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
/** Validate a positional argument list. Extra arguments are ignored (JS call semantics). */
|
|
73
|
-
export const validateArgs = (params: ParamSchema[], args: unknown[]): void => {
|
|
74
|
-
for (let i = 0; i < params.length; i++) {
|
|
75
|
-
const p = params[i]
|
|
76
|
-
if (p.rest) {
|
|
77
|
-
const rest = args.slice(i)
|
|
78
|
-
const e = check(p.schema, rest, p.name)
|
|
79
|
-
if (e) throw new ValidationError(e)
|
|
80
|
-
return
|
|
81
|
-
}
|
|
82
|
-
const v = args[i]
|
|
83
|
-
if (v === undefined) { if (p.optional) continue; throw new ValidationError(`${p.name}: required`) }
|
|
84
|
-
const e = check(p.schema, v, p.name)
|
|
85
|
-
if (e) throw new ValidationError(e)
|
|
86
|
-
}
|
|
87
|
-
}
|
|
@@ -1,142 +0,0 @@
|
|
|
1
|
-
// A host-registered platform view (map, QR scanner, camera preview, …) — the plugin surface of
|
|
2
|
-
// the Presentable model (docs/navigation-presentable-plan.md). The host registers a *capability*
|
|
3
|
-
// (`engine.registerView("map") { params, channel -> MapView(...) }`); when and with what to show
|
|
4
|
-
// it is app code. Dual-natured like every Presentable: a fullscreen destination
|
|
5
|
-
// (`Router.push(map)` / `map.open()`) AND an embeddable layout node (wire type `"native"` — place
|
|
6
|
-
// it among a screen's children, sized by its styles). One instance lives in one place at a time;
|
|
7
|
-
// pushing a mounted instance fullscreen is promotion (the native view reparents, state intact).
|
|
8
|
-
//
|
|
9
|
-
// Plugins ship a typed TS wrapper over the raw `call`/`on` channel — see QRScanner for the
|
|
10
|
-
// wrapper convention.
|
|
11
|
-
|
|
12
|
-
import { _channelCall, _channelEmit, _channelOff, _channelOn, type ChannelListeners } from "../runtime/channel"
|
|
13
|
-
import { _bumpNavEpoch, _navSupported, _setCurrent, Presentable, type PresentOptions } from "./presentable"
|
|
14
|
-
import { Element, type BaseStyle, type DrawableStyle, type ElementStyle, type UIElementBase } from "./UINode"
|
|
15
|
-
|
|
16
|
-
export type UINativeViewStyle = ElementStyle & DrawableStyle
|
|
17
|
-
|
|
18
|
-
/** An instance of a host-registered platform view (map, QR scanner, camera preview, …) —
|
|
19
|
-
* embeddable among a screen's children AND fullscreen-presentable (`open()`); one instance lives
|
|
20
|
-
* in one place at a time. Talk to it via the `call`/`on` channel. */
|
|
21
|
-
export interface NativeView extends UIElementBase<UINativeViewStyle, DrawableStyle & BaseStyle> {
|
|
22
|
-
readonly type: "native",
|
|
23
|
-
/** The registered view kind this instance resolves to (the `registerView` name). */
|
|
24
|
-
readonly viewName: string,
|
|
25
|
-
/** Creation params, passed to the host factory (JSON-serializable). */
|
|
26
|
-
readonly params: any,
|
|
27
|
-
|
|
28
|
-
/** Show fullscreen as the current destination (see Presentable). */
|
|
29
|
-
open(options?: PresentOptions): void,
|
|
30
|
-
/** Dismiss if this is the visible destination. */
|
|
31
|
-
close(): void,
|
|
32
|
-
|
|
33
|
-
/** Became the visible destination. */
|
|
34
|
-
onOpen(callback: () => void): this
|
|
35
|
-
/** Stopped being the visible destination. */
|
|
36
|
-
onClose(callback: () => void): this
|
|
37
|
-
/** Hardware/system back while current. */
|
|
38
|
-
onBackPressed(callback: () => void): this
|
|
39
|
-
|
|
40
|
-
/** Invoke a method on the native view (`map.call("setCenter", [lng, lat])`). Args and the
|
|
41
|
-
* result are JSON-serialized. Rejects if the host has no such view/method. */
|
|
42
|
-
call(method: string, ...args: any[]): Promise<any>
|
|
43
|
-
/** Subscribe to an event the native view emits (`map.on("markerTap", cb)`). */
|
|
44
|
-
on(event: string, callback: (data?: any) => void): this
|
|
45
|
-
off(event: string, callback: (data?: any) => void): this
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
let nextViewId = 1
|
|
49
|
-
|
|
50
|
-
/** @internal Base for typed plugin wrappers (QRScanner, CameraView — src/plugins/): subclass it,
|
|
51
|
-
* pin the viewName/params, and layer typed methods over the raw `call`/`on` channel. Not a
|
|
52
|
-
* public global — apps use the `NativeView(name)` factory below. */
|
|
53
|
-
export class NativeViewElement extends Element<"native"> implements Presentable {
|
|
54
|
-
readonly viewName: string
|
|
55
|
-
readonly params: any
|
|
56
|
-
/** @internal stable per-instance handle for the viewCall channel + promotion identity. */
|
|
57
|
-
readonly _viewId: number
|
|
58
|
-
/** @internal event listeners by event name — the host invokes these (vlist pcl/psl convention)
|
|
59
|
-
* or calls _emitViewEvent. */
|
|
60
|
-
readonly nvl: ChannelListeners = {}
|
|
61
|
-
readonly ol: (() => void)[] = []
|
|
62
|
-
readonly cl: (() => void)[] = []
|
|
63
|
-
_backButtonCallback?: () => void
|
|
64
|
-
private _vd?: object
|
|
65
|
-
|
|
66
|
-
constructor(viewName: string, params: any) {
|
|
67
|
-
super("native", {})
|
|
68
|
-
this.viewName = viewName
|
|
69
|
-
this.params = params
|
|
70
|
-
this._viewId = nextViewId++
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
/** @internal stable wire descriptor; hosts hand it back via router onChange (`_p` = this). */
|
|
74
|
-
_viewDesc(): object {
|
|
75
|
-
return this._vd ??= { type: "native", viewName: this.viewName, params: this.params, viewId: this._viewId, _p: this }
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
open(options?: PresentOptions): void {
|
|
79
|
-
if (!_navSupported()) {
|
|
80
|
-
throw new Error(`NativeView("${this.viewName}") is not supported on this host (requires _creatorUI.openView)`)
|
|
81
|
-
}
|
|
82
|
-
_bumpNavEpoch()
|
|
83
|
-
_creatorUI.openView!(this._viewDesc(), options?.transition ?? "none")
|
|
84
|
-
_setCurrent(this)
|
|
85
|
-
}
|
|
86
|
-
close(): void {
|
|
87
|
-
_bumpNavEpoch()
|
|
88
|
-
if (_navSupported()) _creatorUI.closeView!()
|
|
89
|
-
if (Presentable.current === this) _setCurrent(null)
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
onOpen(callback: () => void): this {
|
|
93
|
-
this.ol.push(callback)
|
|
94
|
-
return this
|
|
95
|
-
}
|
|
96
|
-
onClose(callback: () => void): this {
|
|
97
|
-
this.cl.push(callback)
|
|
98
|
-
return this
|
|
99
|
-
}
|
|
100
|
-
onBackPressed(callback: () => void): this {
|
|
101
|
-
this._backButtonCallback = callback
|
|
102
|
-
return this
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
call(method: string, ...args: any[]): Promise<any> {
|
|
106
|
-
return _channelCall(
|
|
107
|
-
typeof _creatorUI !== "undefined" ? _creatorUI : undefined, "viewCall",
|
|
108
|
-
`NativeView("${this.viewName}") is not supported on this host (requires _creatorUI.viewCall)`,
|
|
109
|
-
this._viewId, method, args)
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
on(event: string, callback: (data?: any) => void): this {
|
|
113
|
-
_channelOn(this.nvl, event, callback)
|
|
114
|
-
return this
|
|
115
|
-
}
|
|
116
|
-
off(event: string, callback: (data?: any) => void): this {
|
|
117
|
-
_channelOff(this.nvl, event, callback)
|
|
118
|
-
return this
|
|
119
|
-
}
|
|
120
|
-
/** @internal host entry: deliver a native event (payload as a JSON string, or omitted). */
|
|
121
|
-
_emitViewEvent(event: string, dataJson?: string): void {
|
|
122
|
-
_channelEmit(this.nvl, event, dataJson)
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
/**
|
|
127
|
-
* Create an instance of a host-registered platform view. `NativeView.isSupported(name)` reports
|
|
128
|
-
* whether this host registered a factory for it. Style via the chained `.style()`.
|
|
129
|
-
*/
|
|
130
|
-
// PURE IIFE (not a plain function + expando assignment) so an app that never uses NativeView
|
|
131
|
-
// tree-shakes it away — top-level property assignments read as side effects to the bundler.
|
|
132
|
-
export const NativeView: {
|
|
133
|
-
(name: string, params?: object): NativeView
|
|
134
|
-
/** Whether this host registered a `registerView` factory under `name`. */
|
|
135
|
-
isSupported(name: string): boolean
|
|
136
|
-
} = /*#__PURE__*/ (() => {
|
|
137
|
-
const factory = (name: string, params: object = {}): NativeView =>
|
|
138
|
-
new NativeViewElement(name, params) as unknown as NativeView
|
|
139
|
-
factory.isSupported = (name: string): boolean =>
|
|
140
|
-
typeof _creatorUI !== "undefined" && typeof _creatorUI.isViewSupported === "function" && _creatorUI.isViewSupported(name)
|
|
141
|
-
return factory
|
|
142
|
-
})()
|
package/runtime/sdk/ui/UI.ts
DELETED
|
@@ -1,39 +0,0 @@
|
|
|
1
|
-
// Renderer-side contract + per-renderer status (DOM / canvas-ui / desktop) for every component
|
|
2
|
-
// below is tracked in packages/canvas-ui/COMPONENTS.md.
|
|
3
|
-
export { UIButton, type UIButtonStyle } from './UIButton'
|
|
4
|
-
export { UIScreen, type UIScreenStyle } from './UIScreen'
|
|
5
|
-
export { UISpacer, type UISpacerStyle } from './UISpacer'
|
|
6
|
-
export { UIText, type UITextStyle } from './UIText'
|
|
7
|
-
export { registerFont } from './fonts'
|
|
8
|
-
export { UIImage, type UIImageStyle } from './UIImage'
|
|
9
|
-
export { UIVideo, type UIVideoStyle } from './UIVideo'
|
|
10
|
-
export { UIInput, UITextArea, type UIInputStyle } from './UIInput'
|
|
11
|
-
|
|
12
|
-
export { UIWidget, type UIWidgetStyle } from './UIWidget'
|
|
13
|
-
export { UIModal, type UIModalStyle } from './UIModal'
|
|
14
|
-
export { UIBottomSheet, type UIBottomSheetStyle } from './UIBottomSheet'
|
|
15
|
-
export { UIPopover, type UIPopoverStyle, type UIPopoverAnchor } from './UIPopover'
|
|
16
|
-
|
|
17
|
-
export { UIRow, UIColumn, UIBox, type UIContainerStyle } from './UIContainer'
|
|
18
|
-
export { UIScrollable, type UIScrollableStyle } from './UIScrollable'
|
|
19
|
-
export { UIPager, type UIPagerStyle } from './UIPager'
|
|
20
|
-
export { UITabs, type UITabDef, defineTabs, type TabDef, type TabsHandle } from './UITabs'
|
|
21
|
-
|
|
22
|
-
export { UIVirtualizedList } from './UIVirtualizedList'
|
|
23
|
-
|
|
24
|
-
export { Router } from './router'
|
|
25
|
-
export { NativeView, type UINativeViewStyle } from './NativeView'
|
|
26
|
-
// Presentable is both the interface and its runtime companion (Presentable.current).
|
|
27
|
-
export { Presentable } from './presentable'
|
|
28
|
-
export type { PresentOptions, Transition, TransitionName, TransitionSpec, TransitionTransform } from './presentable'
|
|
29
|
-
|
|
30
|
-
// Internal: memoized map for reactive children — the chisel `reactiveUi` pass rewrites
|
|
31
|
-
// `list.map(fn)` inside children bindings to `__uiMap(list, fn, slot)` calls.
|
|
32
|
-
export { __uiMap } from './UINode'
|
|
33
|
-
// Compiler fast paths (chisel `flatten_ui` raw lowering): dispatch-free construction for calls
|
|
34
|
-
// whose arguments the compiler proved to be plain children. Never written by hand.
|
|
35
|
-
export { __UIColumn, __UIRow, __UIBox } from './UIContainer'
|
|
36
|
-
export { __UIButton } from './UIButton'
|
|
37
|
-
export { __UIScreen } from './UIScreen'
|
|
38
|
-
export { __UIScrollable } from './UIScrollable'
|
|
39
|
-
export { __UIWidget } from './UIWidget'
|
|
@@ -1,139 +0,0 @@
|
|
|
1
|
-
import { ModalElement, type UIModalStyle } from "./UIModal"
|
|
2
|
-
import type { UIWidget } from "./UIWidget"
|
|
3
|
-
import { buildUI, type ChildrenFn, type UIChildArg, type UINodeChild } from "./UINode"
|
|
4
|
-
|
|
5
|
-
export type UIBottomSheetStyle = UIModalStyle
|
|
6
|
-
|
|
7
|
-
const SHEET_ANIM_MS = 250
|
|
8
|
-
|
|
9
|
-
/**
|
|
10
|
-
* A bottom sheet: a `UIWidget` pinned to the bottom edge. By default it is **content-sized** —
|
|
11
|
-
* as tall as what's inside (capped at the screen), one position, drag down to dismiss (the
|
|
12
|
-
* action-sheet shape). Call `detents()` for the multi-position map-app model instead: snap
|
|
13
|
-
* positions the user drags between on native hosts. Ships as a widget node on the wire (no new
|
|
14
|
-
* node type — docs/bottom-sheet-plan.md): hosts without sheet support show a static bottom panel.
|
|
15
|
-
*
|
|
16
|
-
* With detents, content lays out at the HIGHEST detent's size; lower detents show the top
|
|
17
|
-
* slice. The scrim, `onOpen`/`onClose`, back-button and scrim-tap dismissal all come from the
|
|
18
|
-
* modal machinery.
|
|
19
|
-
*
|
|
20
|
-
* ```ts
|
|
21
|
-
* const actions = UIBottomSheet({ bgColor: "#1c1c1e", borderRadius: 16 }, [rows]) // content-sized
|
|
22
|
-
*
|
|
23
|
-
* const mapSheet = UIBottomSheet({ bgColor: "#1c1c1e", borderRadius: 16 }, [results])
|
|
24
|
-
* .detents([0.3, 0.6, 1])
|
|
25
|
-
* .onDetentChange(i => map.setPadding(...))
|
|
26
|
-
* ```
|
|
27
|
-
*/
|
|
28
|
-
export interface UIBottomSheet extends UIWidget {
|
|
29
|
-
/** Snap positions as fractions of screen height, ascending. Also sizes the sheet: its box is
|
|
30
|
-
* the last (highest) detent; set this before `show()`. Without a `detents()` call the sheet
|
|
31
|
-
* is CONTENT-SIZED instead — one position, as tall as its children (up to the screen). */
|
|
32
|
-
detents(list: number[]): this,
|
|
33
|
-
/** Snap to a detent (animated). Clamped to the detents range; fires `onDetentChange`. */
|
|
34
|
-
setDetent(index: number): void,
|
|
35
|
-
/** Current detent index. */
|
|
36
|
-
readonly detent: number,
|
|
37
|
-
/** Fires on every settle: a finger snap on native hosts, or a `setDetent()` call. */
|
|
38
|
-
onDetentChange(callback: (index: number) => void): this,
|
|
39
|
-
|
|
40
|
-
/** Mount and slide in to the current detent. No-op while already open. */
|
|
41
|
-
show(): void,
|
|
42
|
-
/** Slide out, fade the scrim, then unmount. No-op while closed. */
|
|
43
|
-
hide(): void,
|
|
44
|
-
/** True from `show()` until `hide()` starts. */
|
|
45
|
-
readonly isOpen: boolean,
|
|
46
|
-
|
|
47
|
-
/** Called when `show()` mounts the sheet. */
|
|
48
|
-
onOpen(callback: () => void): this,
|
|
49
|
-
/** Called when the sheet starts closing — scrim tap, back button, drag-dismiss, or `hide()`. */
|
|
50
|
-
onClose(callback: () => void): this,
|
|
51
|
-
/** `dismissible(false)`: scrim taps, the back button and dragging below the lowest detent no
|
|
52
|
-
* longer close the sheet (a drag collapses to the lowest detent instead). Default `true`. */
|
|
53
|
-
dismissible(enabled: boolean): this,
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
export class BottomSheetElement extends ModalElement {
|
|
57
|
-
_detents: number[] = [1]
|
|
58
|
-
_detent = 0
|
|
59
|
-
/** Host-facing (the `sl`/`otl` contract): hosts read `dtl` at mount and call it with the
|
|
60
|
-
* settled detent index for GESTURE settles only — programmatic `setDetent` fires JS-side.
|
|
61
|
-
* `-1` = dragged below the lowest detent while dismissible. */
|
|
62
|
-
readonly dtl: any[] = []
|
|
63
|
-
readonly _detentListeners: ((index: number) => void)[] = []
|
|
64
|
-
|
|
65
|
-
constructor(style: UIBottomSheetStyle, children: UINodeChild[] | ChildrenFn) {
|
|
66
|
-
// No default height: an absolute node with only bottom set is CONTENT-sized by yoga. A
|
|
67
|
-
// single detent makes the geometry height-independent on every host (shown = offset 0,
|
|
68
|
-
// hidden = own height), so content mode needs no host support beyond the detents path.
|
|
69
|
-
super({ left: 0, right: 0, bottom: 0, maxHeight: "100%", sheetDetents: "1", ...style } as UIModalStyle, children)
|
|
70
|
-
// The inherited show/hide pose animates ONLY the scrim: sheet geometry rides the
|
|
71
|
-
// sheetDetent style key, never the pose system.
|
|
72
|
-
this._transition = { duration: SHEET_ANIM_MS }
|
|
73
|
-
this.dtl.push((index: number) => {
|
|
74
|
-
if (index < 0) { this.hide(); return }
|
|
75
|
-
this._detent = index
|
|
76
|
-
for (const callback of this._detentListeners) callback(index)
|
|
77
|
-
})
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
detents(list: number[]): this {
|
|
81
|
-
const sorted = [...list].filter(d => d > 0).sort((a, b) => a - b)
|
|
82
|
-
if (sorted.length === 0) return this
|
|
83
|
-
this._detents = sorted
|
|
84
|
-
if (this._detent >= sorted.length) this._detent = sorted.length - 1
|
|
85
|
-
this.style({
|
|
86
|
-
height: `${sorted[sorted.length - 1] * 100}%`,
|
|
87
|
-
sheetDetents: sorted.join(","),
|
|
88
|
-
} as UIBottomSheetStyle)
|
|
89
|
-
return this
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
get detent(): number {
|
|
93
|
-
return this._detent
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
setDetent(index: number): void {
|
|
97
|
-
const clamped = Math.max(0, Math.min(Math.floor(index), this._detents.length - 1))
|
|
98
|
-
this._detent = clamped
|
|
99
|
-
if (!this._open) return // applied by the next show()
|
|
100
|
-
this.style({ sheetDetent: clamped } as UIBottomSheetStyle)
|
|
101
|
-
for (const callback of this._detentListeners) callback(clamped)
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
onDetentChange(callback: (index: number) => void): this {
|
|
105
|
-
this._detentListeners.push(callback)
|
|
106
|
-
return this
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
show(): void {
|
|
110
|
-
if (this._open) return
|
|
111
|
-
// Before the mount, so the built node carries the target and hosts play the entrance to it.
|
|
112
|
-
this.style({ sheetDetent: this._detent } as UIBottomSheetStyle)
|
|
113
|
-
super.show()
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
hide(): void {
|
|
117
|
-
if (!this._open) return
|
|
118
|
-
this.style({ sheetDetent: -1 } as UIBottomSheetStyle) // hosts slide out; unmount follows
|
|
119
|
-
super.hide()
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
dismissible(enabled: boolean): this {
|
|
123
|
-
super.dismissible(enabled)
|
|
124
|
-
this.style({ sheetDismissible: enabled ? 1 : 0 } as UIBottomSheetStyle)
|
|
125
|
-
return this
|
|
126
|
-
}
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
const makeBottomSheet = (style: any, children: any) => new BottomSheetElement(style, children)
|
|
130
|
-
|
|
131
|
-
/** Create a bottom sheet (see {@link UIBottomSheet}) — content-sized by default; call
|
|
132
|
-
* `.detents([...])` for snap positions. Same argument forms as `UIColumn`. */
|
|
133
|
-
export function UIBottomSheet(...children: UIChildArg[]): UIBottomSheet;
|
|
134
|
-
export function UIBottomSheet(children: UINodeChild[] | ChildrenFn): UIBottomSheet;
|
|
135
|
-
/** @deprecated Style as the first argument is legacy — use chained `.style({...})`. */
|
|
136
|
-
export function UIBottomSheet(style: UIBottomSheetStyle, children?: UINodeChild[] | ChildrenFn): UIBottomSheet;
|
|
137
|
-
export function UIBottomSheet(...args: any[]): UIBottomSheet {
|
|
138
|
-
return buildUI(args, null, makeBottomSheet)
|
|
139
|
-
}
|