@proteus-vue/render-backend 0.3.0-beta.21 → 0.3.0-beta.23
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/app-navigation.d.ts +57 -0
- package/dist/app-navigation.js +3017 -0
- package/dist/host-conformance.d.ts +7 -0
- package/dist/host-invoke-contract.d.ts +8 -0
- package/dist/host-invoke-contract.js +47 -0
- package/dist/index.d.ts +16 -0
- package/dist/index.js +2989 -56
- package/dist/native.d.ts +67 -1
- package/dist/quickjs-host.d.ts +77 -0
- package/dist/screen-executor-host.d.ts +67 -0
- package/dist/screen-executor-host.js +151 -0
- package/dist/screen-executor.d.ts +239 -0
- package/dist/screen-executor.js +165 -0
- package/dist/screen-runtime.d.ts +96 -0
- package/dist/selfdraw-batch.d.ts +47 -0
- package/dist/superapp-runtime.d.ts +83 -0
- package/dist/tab-bar-spec.d.ts +35 -0
- package/dist/tab-bar-spec.js +17 -0
- package/package.json +23 -5
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
// ../contracts/src/layers.ts
|
|
2
|
+
var LAYER_PRIMITIVES = ["layer-content", "layer-navigation", "layer-mask", "layer-popout"];
|
|
3
|
+
|
|
4
|
+
// ../contracts/src/mount-layers.ts
|
|
5
|
+
var MOUNT_LAYERS = ["global", "page", "overlay"];
|
|
6
|
+
var MOUNT_LAYER_ORDER = {
|
|
7
|
+
global: 0,
|
|
8
|
+
page: 1,
|
|
9
|
+
overlay: 2
|
|
10
|
+
};
|
|
11
|
+
var MOUNT_LAYER_NODE_OFFSET = {
|
|
12
|
+
global: 1,
|
|
13
|
+
page: 2,
|
|
14
|
+
overlay: 3
|
|
15
|
+
};
|
|
16
|
+
function mountLayerContainerPlans() {
|
|
17
|
+
return [...MOUNT_LAYERS].sort((a, b) => MOUNT_LAYER_ORDER[a] - MOUNT_LAYER_ORDER[b]).map((layer) => ({
|
|
18
|
+
layer,
|
|
19
|
+
nodeOffset: MOUNT_LAYER_NODE_OFFSET[layer],
|
|
20
|
+
order: MOUNT_LAYER_ORDER[layer],
|
|
21
|
+
frame: "fullscreen"
|
|
22
|
+
}));
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// ../contracts/src/scroll.ts
|
|
26
|
+
var DOCK_REQUIRES_LAYER = LAYER_PRIMITIVES.includes("layer-navigation");
|
|
27
|
+
|
|
28
|
+
// src/screen-executor.ts
|
|
29
|
+
function screenContentFromLayoutTemplate(template, viewport) {
|
|
30
|
+
const nodes = template.nodes.map((n) => {
|
|
31
|
+
const out = {
|
|
32
|
+
id: n.id,
|
|
33
|
+
parentId: n.parentId,
|
|
34
|
+
// ★展平:`style` 的引擎字段提到顶层(内核 NodeDto 读顶层键)
|
|
35
|
+
...n.style ?? {}
|
|
36
|
+
};
|
|
37
|
+
if (typeof n.text === "string" && n.text.length > 0) out.text = n.text;
|
|
38
|
+
if (typeof n.tag === "string" && n.tag.length > 0) out.semantic = n.tag;
|
|
39
|
+
return out;
|
|
40
|
+
});
|
|
41
|
+
return viewport ? { viewport, nodes } : { nodes };
|
|
42
|
+
}
|
|
43
|
+
function createScreenExecutor(opts) {
|
|
44
|
+
const { host, anim, plan, contentOf, onScreenMounted, onEvent } = opts;
|
|
45
|
+
const nodes = /* @__PURE__ */ new Map();
|
|
46
|
+
const stats = {
|
|
47
|
+
commands: 0,
|
|
48
|
+
transitions: 0,
|
|
49
|
+
forward: 0,
|
|
50
|
+
back: 0,
|
|
51
|
+
skippedNone: 0,
|
|
52
|
+
destroyed: { pop: 0, reset: 0, freeze: 0 },
|
|
53
|
+
visibility: { shown: 0, hidden: 0 },
|
|
54
|
+
mounts: { first: 0, rebuild: 0 },
|
|
55
|
+
errors: []
|
|
56
|
+
};
|
|
57
|
+
let txn = { exiting: null, mounted: /* @__PURE__ */ new Map() };
|
|
58
|
+
const emit = (type, screenId, detail) => {
|
|
59
|
+
onEvent?.({ type, screenId, detail });
|
|
60
|
+
};
|
|
61
|
+
async function runCommand(cmd) {
|
|
62
|
+
stats.commands++;
|
|
63
|
+
switch (cmd.op) {
|
|
64
|
+
case "mount": {
|
|
65
|
+
const rebuild = cmd.rebuild === true;
|
|
66
|
+
const name = cmd.name ?? cmd.screenId;
|
|
67
|
+
const path = cmd.path ?? "";
|
|
68
|
+
const content = contentOf?.({ name, path });
|
|
69
|
+
const node = await host.mountScreen({
|
|
70
|
+
screenId: cmd.screenId,
|
|
71
|
+
name,
|
|
72
|
+
path,
|
|
73
|
+
params: cmd.params ?? null,
|
|
74
|
+
rebuild,
|
|
75
|
+
...content ? { content } : {},
|
|
76
|
+
// ★GP3-c:三层容器计划(由**契约**算,宿主只消费——偏移量而非绝对 id,见类型注释)
|
|
77
|
+
layerContainers: mountLayerContainerPlans()
|
|
78
|
+
});
|
|
79
|
+
nodes.set(cmd.screenId, node);
|
|
80
|
+
txn.mounted.set(cmd.screenId, rebuild);
|
|
81
|
+
if (rebuild) stats.mounts.rebuild++;
|
|
82
|
+
else stats.mounts.first++;
|
|
83
|
+
emit("mount", cmd.screenId, { rebuild, node, contentNodes: content?.nodes.length ?? 0 });
|
|
84
|
+
onScreenMounted?.(cmd.screenId, rebuild);
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
case "exit": {
|
|
88
|
+
txn.exiting = { screenId: cmd.screenId, transition: cmd.transition, destroy: null };
|
|
89
|
+
emit("exit", cmd.screenId, { transition: cmd.transition });
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
case "unmount": {
|
|
93
|
+
const reason = cmd.reason ?? "pop";
|
|
94
|
+
if (txn.exiting && txn.exiting.screenId === cmd.screenId) {
|
|
95
|
+
txn.exiting.destroy = reason;
|
|
96
|
+
emit("unmount-deferred", cmd.screenId, { reason });
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
await host.destroyScreen(cmd.screenId, reason, nodes.get(cmd.screenId));
|
|
100
|
+
nodes.delete(cmd.screenId);
|
|
101
|
+
stats.destroyed[reason]++;
|
|
102
|
+
emit("destroy", cmd.screenId, { reason });
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
case "enter": {
|
|
106
|
+
const incomingNode = nodes.get(cmd.screenId);
|
|
107
|
+
if (incomingNode === void 0) {
|
|
108
|
+
stats.errors.push(`enter(${cmd.screenId})\uFF1A\u5C4F\u5B50\u6811\u4E0D\u5B58\u5728\uFF08mount \u672A\u6267\u884C\uFF1F\uFF09`);
|
|
109
|
+
emit("error", cmd.screenId, "enter-without-subtree");
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
const exiting = txn.exiting;
|
|
113
|
+
const outgoingNode = exiting ? nodes.get(exiting.screenId) : void 0;
|
|
114
|
+
const mountedHere = txn.mounted.get(cmd.screenId);
|
|
115
|
+
const direction = mountedHere === false ? "forward" : "back";
|
|
116
|
+
const transition = cmd.transition;
|
|
117
|
+
const batch = plan(transition, { incoming: incomingNode, outgoing: outgoingNode }, { direction });
|
|
118
|
+
const normalized = typeof transition === "string" ? transition : "none";
|
|
119
|
+
await host.setScreenVisible(cmd.screenId, true, incomingNode);
|
|
120
|
+
stats.visibility.shown++;
|
|
121
|
+
emit("visible", cmd.screenId, true);
|
|
122
|
+
const hasAnims = batch.incoming.anims.length > 0 || batch.outgoing.anims.length > 0;
|
|
123
|
+
if (hasAnims) {
|
|
124
|
+
await anim.playRouteTransition(batch, { direction, transition: normalized });
|
|
125
|
+
stats.transitions++;
|
|
126
|
+
if (direction === "forward") stats.forward++;
|
|
127
|
+
else stats.back++;
|
|
128
|
+
emit("transition", cmd.screenId, { direction, transition: normalized, anims: batch.incoming.anims.length + batch.outgoing.anims.length });
|
|
129
|
+
} else {
|
|
130
|
+
stats.skippedNone++;
|
|
131
|
+
emit("transition-skipped", cmd.screenId, { transition: normalized });
|
|
132
|
+
}
|
|
133
|
+
if (exiting) {
|
|
134
|
+
if (exiting.destroy !== null) {
|
|
135
|
+
await host.destroyScreen(exiting.screenId, exiting.destroy, outgoingNode);
|
|
136
|
+
nodes.delete(exiting.screenId);
|
|
137
|
+
stats.destroyed[exiting.destroy]++;
|
|
138
|
+
emit("destroy", exiting.screenId, { reason: exiting.destroy, deferred: true });
|
|
139
|
+
} else if (outgoingNode !== void 0) {
|
|
140
|
+
await host.setScreenVisible(exiting.screenId, false, outgoingNode);
|
|
141
|
+
stats.visibility.hidden++;
|
|
142
|
+
emit("visible", exiting.screenId, false);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
txn = { exiting: null, mounted: /* @__PURE__ */ new Map() };
|
|
146
|
+
return;
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
let queue = Promise.resolve();
|
|
151
|
+
return {
|
|
152
|
+
applyCommands(commands) {
|
|
153
|
+
queue = queue.then(async () => {
|
|
154
|
+
for (const c of commands) await runCommand(c);
|
|
155
|
+
});
|
|
156
|
+
return queue;
|
|
157
|
+
},
|
|
158
|
+
stats: () => JSON.parse(JSON.stringify(stats)),
|
|
159
|
+
subtreeNode: (screenId) => nodes.get(screenId)
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
export {
|
|
163
|
+
createScreenExecutor,
|
|
164
|
+
screenContentFromLayoutTemplate
|
|
165
|
+
};
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import type { LayoutTemplate, SubscriptionTable, EventBinding, DispatchState } from '@proteus-vue/slot-runtime';
|
|
2
|
+
/** 一屏的运行期产物(与 `packages/cli/src/app-runtime-content.ts` 的 `ScreenRuntimeArtifact` 同形) */
|
|
3
|
+
export interface ScreenRuntimeArtifact {
|
|
4
|
+
tpl: LayoutTemplate;
|
|
5
|
+
table: SubscriptionTable;
|
|
6
|
+
events: EventBinding[];
|
|
7
|
+
handlers: Record<string, Array<{
|
|
8
|
+
op: string;
|
|
9
|
+
source?: string;
|
|
10
|
+
program?: unknown;
|
|
11
|
+
event?: string;
|
|
12
|
+
}>>;
|
|
13
|
+
data: Record<string, unknown>;
|
|
14
|
+
}
|
|
15
|
+
export interface CreateScreenRuntimeOptions {
|
|
16
|
+
/** 各屏运行期产物(键 = 屏名) */
|
|
17
|
+
artifacts: Record<string, ScreenRuntimeArtifact>;
|
|
18
|
+
/** 指令出口:把 `SlotRuntime` 编出的二进制指令(number[] JSON)交给宿主应用(同 `applyOps`) */
|
|
19
|
+
applyOps: (opsJson: string) => void;
|
|
20
|
+
/** 视口(实例化用;宿主在挂载时给真实尺寸) */
|
|
21
|
+
viewport: {
|
|
22
|
+
width: number;
|
|
23
|
+
height: number;
|
|
24
|
+
};
|
|
25
|
+
/**
|
|
26
|
+
* 手势命中的**内核 id → 内容 local id** 的映射基址。两条宿主路径口径不同:
|
|
27
|
+
* · **不传(缺省)** = 恒等:`VaporRenderHost.mount` **直接用节点 id** 建树(App 壳走这条)⇒
|
|
28
|
+
* `kernelId === localId`,无需重映射;
|
|
29
|
+
* · **传数字** = `kernelId = base + 数组序`:`ScreenHost.screen.mount` 按**数组序**重映射内容
|
|
30
|
+
* 节点(Android 基址 1000)⇒ 反查 `index = kernelId − base`、`localId = 节点序[index]`。
|
|
31
|
+
*/
|
|
32
|
+
contentIdBase?: number;
|
|
33
|
+
/**
|
|
34
|
+
* ★★★跨调用**状态种子**(2026-10-07 · 鸿蒙一次性 VM):`{ 屏名: { 变量: 值 } }`。
|
|
35
|
+
* 【为什么需要】鸿蒙宿主为规避持久 VM 崩溃(决策 #540)**每次交互都新建一次性 VM**
|
|
36
|
+
* ⇒ 运行期实例态(如 `count`)随 VM 销毁而丢 ⇒ 「点击计数不累加」「改数据不生效」。
|
|
37
|
+
* ⇒ 宿主把上次的 `snapshot()` 回灌为种子,实例化即恢复态——**宿主充当状态持有者**,
|
|
38
|
+
* 一次性 VM 变"无状态执行器"(本模式的必然形态)。缺省 ⇒ 用构建期 `data`。
|
|
39
|
+
*/
|
|
40
|
+
seedData?: Record<string, Record<string, unknown>>;
|
|
41
|
+
/** 诊断出口(不静默) */
|
|
42
|
+
onNote?: (note: string) => void;
|
|
43
|
+
/**
|
|
44
|
+
* ★导航出口(B1):handler 里的 `$nav('目标')` 动作 → 交给宿主/装配层执行导航
|
|
45
|
+
* (App 壳 = `router.push(target)`;与 `<navigator>` 同语义)。缺省 ⇒ 只记 note(不静默)。
|
|
46
|
+
*/
|
|
47
|
+
navigate?: (target: string) => void;
|
|
48
|
+
}
|
|
49
|
+
/** 一屏的运行期实例(挂载 + 事件 + 增量) */
|
|
50
|
+
export interface ScreenRuntimeInstance {
|
|
51
|
+
/** 供 `screen.mount` 的内容载荷(`{viewport,nodes}`,节点已实例化) */
|
|
52
|
+
content(): {
|
|
53
|
+
viewport: {
|
|
54
|
+
width: number;
|
|
55
|
+
height: number;
|
|
56
|
+
};
|
|
57
|
+
nodes: unknown[];
|
|
58
|
+
};
|
|
59
|
+
/** 派发一次语义手势(宿主 collected 命中链 → 这里);返回是否跑了 handler */
|
|
60
|
+
dispatch(type: string, chain: readonly number[], state?: DispatchState): {
|
|
61
|
+
handled: boolean;
|
|
62
|
+
fired: number[];
|
|
63
|
+
};
|
|
64
|
+
/** 立即把脏槽位编成指令并交给 `applyOps`(确定性驱动入口) */
|
|
65
|
+
flush(): void;
|
|
66
|
+
/**
|
|
67
|
+
* ★★★**数据变更后重实例化**(2026-10-07):用当前 `data` 重建节点树 + 重算槽位。
|
|
68
|
+
* 给"`applyOps` 为 no-op"的宿主(鸿蒙一次性 VM,无驻留内核指令流)用——它们拿不到细粒度
|
|
69
|
+
* 增量,只能整树重建。`content()` 之后即反映新数据。(有 applyOps 的宿主无需调本方法。)
|
|
70
|
+
*/
|
|
71
|
+
refresh(): void;
|
|
72
|
+
/** 当前数据快照(诊断/判据读) */
|
|
73
|
+
data(): Record<string, unknown>;
|
|
74
|
+
/** ★跨调用状态导出(浅拷贝)——供宿主(一次性 VM)在下次调用回灌为 `seedData`(见 CreateScreenRuntimeOptions.seedData) */
|
|
75
|
+
snapshot(): Record<string, unknown>;
|
|
76
|
+
}
|
|
77
|
+
export interface ScreenRuntime {
|
|
78
|
+
/** 取(或按需创建)某屏的运行期实例 */
|
|
79
|
+
instance(name: string): ScreenRuntimeInstance;
|
|
80
|
+
/** 是否有该屏的运行期产物 */
|
|
81
|
+
has(name: string): boolean;
|
|
82
|
+
/** ★跨调用状态导出(`{屏名: 数据}`)——一次性 VM 宿主回灌用 */
|
|
83
|
+
snapshot(): Record<string, Record<string, unknown>>;
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* 创建 App 壳的统一运行期。
|
|
87
|
+
*
|
|
88
|
+
* 用法(壳 JS 入口,QuickJS/JSC 里):
|
|
89
|
+
* ```ts
|
|
90
|
+
* const rt = createScreenRuntime({ artifacts, applyOps: (ops) => proteusHost.applyOps(ops), viewport })
|
|
91
|
+
* // 挂载某屏:把 rt.instance('index').content() 交给 screen.mount;记下它回报的 contentIdBase
|
|
92
|
+
* // 手势:proteusHost.onGesture((type, kernelId, chain) => rt.instance(cur).dispatch(type, chain))
|
|
93
|
+
* // 每帧:rt.instance(cur).flush()(或在数据变更后调)
|
|
94
|
+
* ```
|
|
95
|
+
*/
|
|
96
|
+
export declare function createScreenRuntime(opts: CreateScreenRuntimeOptions): ScreenRuntime;
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import type { NativeBatchAdapter } from './native';
|
|
2
|
+
/** 真机宿主桥(iOS/Android/鸿蒙 同形——三端宿主都实现这三个入口) */
|
|
3
|
+
export interface SelfDrawHostBridge {
|
|
4
|
+
/** 首帧建树:`{viewport:{width,height}, nodes:[spec]}` */
|
|
5
|
+
mount(treeJson: string): string;
|
|
6
|
+
/** 结构变化:整树重发 */
|
|
7
|
+
update(treeJson: string): string;
|
|
8
|
+
/** 样式/文本增量:`[{id, style:{…}}]`(宿主同通道处理 text——`style:{text}`) */
|
|
9
|
+
updatePatches(patchesJson: string): string;
|
|
10
|
+
}
|
|
11
|
+
/** 本桥的诊断面(判据/可观测性——不含在 SPI 契约里) */
|
|
12
|
+
export interface SelfDrawBatchAdapter extends NativeBatchAdapter {
|
|
13
|
+
/** 已发生的**宿主调用次数**(★批处理红线判据:应恒等于 commit 次数) */
|
|
14
|
+
hostCalls(): number;
|
|
15
|
+
/** 最近一次走的是哪个宿主入口(诊断:mount / update / updatePatches) */
|
|
16
|
+
lastCallKind(): 'mount' | 'update' | 'updatePatches' | null;
|
|
17
|
+
/** 镜像树的节点表(判据用:节点数 / 树形 / 文本) */
|
|
18
|
+
nodeSpecs(): Array<Record<string, unknown>>;
|
|
19
|
+
}
|
|
20
|
+
export interface SelfDrawBatchOptions {
|
|
21
|
+
viewport: {
|
|
22
|
+
width: number;
|
|
23
|
+
height: number;
|
|
24
|
+
};
|
|
25
|
+
/**
|
|
26
|
+
* props 归一化钩子(IR props → 宿主 spec 字段)。
|
|
27
|
+
*
|
|
28
|
+
* ★缺省 = **原样透传**(宿主自行解释;测试/简单宿主够用)。
|
|
29
|
+
* ★真机接线建议传入 renderer-app 的既有归一化(`paintOf` 等),
|
|
30
|
+
* 而**不要**在本文件里再写一份——复制必然漂移(本仓纪律 #22)。
|
|
31
|
+
*/
|
|
32
|
+
normalizeProps?: (props: Record<string, unknown>) => Record<string, unknown>;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* 创建 selfdraw 批量宿主适配器。
|
|
36
|
+
*
|
|
37
|
+
* 用法(真机接线):
|
|
38
|
+
* ```ts
|
|
39
|
+
* const backend = createNativeBackend(
|
|
40
|
+
* createSelfDrawBatchAdapter(proteusSelfDraw, { viewport: { width: 390, height: 844 } }),
|
|
41
|
+
* 'ios',
|
|
42
|
+
* )
|
|
43
|
+
* // …正常走 SPI nodeOps…
|
|
44
|
+
* backend.flush() // ← 一次 flush = 一次宿主调用
|
|
45
|
+
* ```
|
|
46
|
+
*/
|
|
47
|
+
export declare function createSelfDrawBatchAdapter(host: SelfDrawHostBridge, opts: SelfDrawBatchOptions): SelfDrawBatchAdapter;
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import type { ScreenRuntimeArtifact } from './screen-runtime';
|
|
2
|
+
/** 宿主原语(App 壳各端 `proteusHost` 的**子集**;平台无关形状) */
|
|
3
|
+
export interface SuperappHostPorts {
|
|
4
|
+
/** 建树 + 上屏(内核排版 + 宿主自绘);返回宿主回执 JSON 串 */
|
|
5
|
+
mount(treeJson: string): string;
|
|
6
|
+
/** 应用二进制指令流(`number[]` JSON)——增量更新 */
|
|
7
|
+
applyOps(opsJson: string): string;
|
|
8
|
+
/** 注册手势反向回调名(宿主在"语义手势 + 命中节点 + 冒泡链"时回调该全局函数) */
|
|
9
|
+
onGesture?(cbName: string): void;
|
|
10
|
+
/**
|
|
11
|
+
* ★★★**每屏滚动进度记忆**(用户:「返回去的页面滚动进度应该保留,前进的页面才重置」——与手机系统 App 一致)。
|
|
12
|
+
*
|
|
13
|
+
* 【为什么需要这对原语(而非单一 resetScroll)】滚动偏移是**视图属性**、不是树属性 —— 跨屏会泄漏。
|
|
14
|
+
* 但**正确语义不是"每次挂载都归零"**:浏览器/系统 App 的返回(pop)**保留**上一页滚动位置,
|
|
15
|
+
* 只有**前进**(push 到新页)才从顶部开始。⇒ 运行期需要"读当前偏移 / 写目标偏移"两个原语,
|
|
16
|
+
* 由运行期用**导航历史栈**判断 前进(重置)vs 返回(恢复)。
|
|
17
|
+
*/
|
|
18
|
+
getScroll?(): number;
|
|
19
|
+
/** 设置滚动偏移(逻辑像素;前进=0 / 返回=该页上次的值)。 */
|
|
20
|
+
setScroll?(offset: number): void;
|
|
21
|
+
}
|
|
22
|
+
export interface SuperappRuntimeOptions {
|
|
23
|
+
/** 各屏运行期产物(`APP_RUNTIME_CONTENT`) */
|
|
24
|
+
artifacts: Record<string, ScreenRuntimeArtifact>;
|
|
25
|
+
/** 宿主原语 */
|
|
26
|
+
host: SuperappHostPorts;
|
|
27
|
+
/** 视口(逻辑像素) */
|
|
28
|
+
viewport: {
|
|
29
|
+
width: number;
|
|
30
|
+
height: number;
|
|
31
|
+
};
|
|
32
|
+
/** 手势回调全局函数名(缺省 `__proteusRuntimeGesture`) */
|
|
33
|
+
gestureCbName?: string;
|
|
34
|
+
/** 导航出口(`$nav('目标')` → 这里;App 壳 = router.push) */
|
|
35
|
+
navigate?: (target: string) => void;
|
|
36
|
+
/**
|
|
37
|
+
* ★★★跨调用**状态种子**(鸿蒙一次性 VM):上次调用 `snapshot()` 的返回,回灌以恢复实例态
|
|
38
|
+
* (`{屏名: {变量: 值}}`)。宿主持有、每次新建 VM 时回传——见 `ScreenRuntimeOptions.seedData`。
|
|
39
|
+
*/
|
|
40
|
+
seedData?: Record<string, Record<string, unknown>>;
|
|
41
|
+
/** 诊断(不静默) */
|
|
42
|
+
onNote?: (note: string) => void;
|
|
43
|
+
}
|
|
44
|
+
export interface SuperappRuntime {
|
|
45
|
+
/** 挂载(或重挂载)某屏:实例化 → `host.mount`;并记忆为"当前屏"(手势派发用) */
|
|
46
|
+
mountScreen(name: string): boolean;
|
|
47
|
+
/**
|
|
48
|
+
* ★★★**强制挂载**(2026-10-07 · 鸿蒙一次性 VM):无条件重实例化 + `host.mount`(同屏也重挂)。
|
|
49
|
+
* 一次性 VM 每次交互都是新 VM ⇒ 必须从 `content()` 拿到**反映当前 state** 的整树(不能 like
|
|
50
|
+
* `mountScreen` 那样"同屏短路")。也用于"数据变更后重挂"。
|
|
51
|
+
*/
|
|
52
|
+
mountScreenInto(name: string): boolean;
|
|
53
|
+
/** 派发一次手势:宿主报的内核 id + 冒泡链 → 当前屏实例派发 */
|
|
54
|
+
dispatchGesture(type: string, chain: readonly number[]): {
|
|
55
|
+
handled: boolean;
|
|
56
|
+
fired: number[];
|
|
57
|
+
};
|
|
58
|
+
/** 当前屏名 */
|
|
59
|
+
current(): string;
|
|
60
|
+
/** ★跨调用状态导出(`{屏名: 数据}`)——一次性 VM 宿主持有、下次回灌 `seedData` */
|
|
61
|
+
snapshot(): Record<string, Record<string, unknown>>;
|
|
62
|
+
/**
|
|
63
|
+
* ★★★**最近一次 `host.mount` 的原始回执**(2026-10-08 · 用户抓出「iOS/鸿蒙背景页导航后仍是首页」)。
|
|
64
|
+
*
|
|
65
|
+
* 【为什么必须暴露(本仓实测的静默失效)】宿主 `mount` 是**有回执的**(`{ok:true,...}` /
|
|
66
|
+
* `{ok:false,error}`),而运行期此前**丢弃**它、只看"有没有该屏产物" ⇒ 内核建树失败
|
|
67
|
+
* (`proteus_layout_create` 返回 0)时**整屏保持旧内容却仍报 ok:true** —— 与「hook 静默失效」
|
|
68
|
+
* 同源:**没有回执校验 = 失败不可观测**。宿主持有本值即可在报告里如实落盘失败原因。
|
|
69
|
+
*/
|
|
70
|
+
lastHostReply(): string | null;
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* 创建 App 壳运行期驱动。
|
|
74
|
+
*
|
|
75
|
+
* 用法(entry-superapp,宿主 eval bundle 后):
|
|
76
|
+
* ```ts
|
|
77
|
+
* const rt = createSuperappRuntime({ artifacts: APP_RUNTIME_CONTENT, host: proteusHost, viewport, navigate })
|
|
78
|
+
* rt.mountScreen('index') // 首屏
|
|
79
|
+
* globalThis.__proteusRuntimeGesture = (type, id, chainJson) => rt.dispatchGesture(type, JSON.parse(chainJson))
|
|
80
|
+
* // 导航后:rt.mountScreen(router.current().name)
|
|
81
|
+
* ```
|
|
82
|
+
*/
|
|
83
|
+
export declare function createSuperappRuntime(opts: SuperappRuntimeOptions): SuperappRuntime;
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
export interface TabBarSpec {
|
|
2
|
+
/** 栏高(逻辑像素;不含底部安全区——宿主自行叠加 safeBottom) */
|
|
3
|
+
height: number;
|
|
4
|
+
/** 底色(CSS 颜色字面量) */
|
|
5
|
+
surface: string;
|
|
6
|
+
/** 顶边线色 */
|
|
7
|
+
line: string;
|
|
8
|
+
/** 选中项色(品牌色) */
|
|
9
|
+
brand: string;
|
|
10
|
+
/** 未选中项色 */
|
|
11
|
+
text3: string;
|
|
12
|
+
/** 角标底色(提示红) */
|
|
13
|
+
badgeBg: string;
|
|
14
|
+
/** 图标字号(逻辑像素) */
|
|
15
|
+
iconSize: number;
|
|
16
|
+
/** 标签字号(逻辑像素) */
|
|
17
|
+
labelSize: number;
|
|
18
|
+
/** IM 角标规格 */
|
|
19
|
+
badge: {
|
|
20
|
+
radius: number;
|
|
21
|
+
fontSize: number;
|
|
22
|
+
offsetX: number;
|
|
23
|
+
offsetY: number;
|
|
24
|
+
padX: number;
|
|
25
|
+
};
|
|
26
|
+
/**
|
|
27
|
+
* 图标字形(符号/emoji;宿主按名字查)。★Web 用 SVG/字体图标——此处给 App 端的**字形回退**。
|
|
28
|
+
* (U+FE0E 变体选择符让 emoji 走文本呈现——见各宿主实现。)
|
|
29
|
+
*/
|
|
30
|
+
icons: Record<string, string>;
|
|
31
|
+
/** 标签文案(符号键 → 文案;缺省用 tabLabels 或键名) */
|
|
32
|
+
labels: Record<string, string>;
|
|
33
|
+
}
|
|
34
|
+
/** App 壳 tab 栏视觉规格(基准 = superapp/App.vue `.sa-tabbar` + global.css token)。 */
|
|
35
|
+
export declare const TAB_BAR_SPEC: TabBarSpec;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
// src/tab-bar-spec.ts
|
|
2
|
+
var TAB_BAR_SPEC = {
|
|
3
|
+
height: 56,
|
|
4
|
+
surface: "#ffffff",
|
|
5
|
+
line: "#dcdfe5",
|
|
6
|
+
brand: "#5b5bd6",
|
|
7
|
+
text3: "#5f6673",
|
|
8
|
+
badgeBg: "#d64545",
|
|
9
|
+
iconSize: 19,
|
|
10
|
+
labelSize: 10,
|
|
11
|
+
badge: { radius: 8, fontSize: 11, offsetX: 4, offsetY: 4, padX: 4 },
|
|
12
|
+
icons: { index: "\u2302", messages: "\u2709", mine: "\u263A" },
|
|
13
|
+
labels: { index: "\u9996\u9875", messages: "\u6D88\u606F", mine: "\u6211\u7684" }
|
|
14
|
+
};
|
|
15
|
+
export {
|
|
16
|
+
TAB_BAR_SPEC
|
|
17
|
+
};
|
package/package.json
CHANGED
|
@@ -1,12 +1,15 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@proteus-vue/render-backend",
|
|
3
|
-
"version": "0.3.0-beta.
|
|
3
|
+
"version": "0.3.0-beta.23",
|
|
4
4
|
"description": "G-27 ProteusRenderBackend SPI:可插拔渲染后端接口 + conformance test + 官方后端原型(Headless/VueDom) + G-41 nodeOps Dispatcher——Render anywhere, on any engine(M1.4)",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "Apache-2.0",
|
|
7
7
|
"dependencies": {
|
|
8
|
-
"@proteus-vue/
|
|
9
|
-
"@proteus-vue/
|
|
8
|
+
"@proteus-vue/animation": "0.3.0-beta.23",
|
|
9
|
+
"@proteus-vue/component-ir": "0.3.0-beta.23",
|
|
10
|
+
"@proteus-vue/contracts": "0.3.0-beta.23",
|
|
11
|
+
"@proteus-vue/router": "0.3.0-beta.23",
|
|
12
|
+
"@proteus-vue/slot-runtime": "0.3.0-beta.23"
|
|
10
13
|
},
|
|
11
14
|
"peerDependencies": {
|
|
12
15
|
"@vue/runtime-core": "^3.4.0"
|
|
@@ -19,13 +22,28 @@
|
|
|
19
22
|
"import": "./dist/index.js",
|
|
20
23
|
"default": "./dist/index.js"
|
|
21
24
|
},
|
|
22
|
-
"./package.json": "./package.json"
|
|
25
|
+
"./package.json": "./package.json",
|
|
26
|
+
"./app-navigation": {
|
|
27
|
+
"types": "./dist/app-navigation.d.ts",
|
|
28
|
+
"import": "./dist/app-navigation.js",
|
|
29
|
+
"default": "./dist/app-navigation.js"
|
|
30
|
+
},
|
|
31
|
+
"./screen-executor": {
|
|
32
|
+
"types": "./dist/screen-executor.d.ts",
|
|
33
|
+
"import": "./dist/screen-executor.js",
|
|
34
|
+
"default": "./dist/screen-executor.js"
|
|
35
|
+
},
|
|
36
|
+
"./screen-executor-host": {
|
|
37
|
+
"types": "./dist/screen-executor-host.d.ts",
|
|
38
|
+
"import": "./dist/screen-executor-host.js",
|
|
39
|
+
"default": "./dist/screen-executor-host.js"
|
|
40
|
+
}
|
|
23
41
|
},
|
|
24
42
|
"files": [
|
|
25
43
|
"dist",
|
|
26
44
|
"README.md"
|
|
27
45
|
],
|
|
28
46
|
"scripts": {
|
|
29
|
-
"build": "rm -rf dist && tsc -p tsconfig.build.json --emitDeclarationOnly && esbuild src/index.ts --bundle --format=esm --platform=neutral --
|
|
47
|
+
"build": "rm -rf dist && tsc -p tsconfig.build.json --emitDeclarationOnly && esbuild src/index.ts src/app-navigation.ts src/screen-executor.ts src/screen-executor-host.ts src/tab-bar-spec.ts src/host-invoke-contract.ts --bundle --format=esm --platform=neutral --outdir=dist --external:@vue/runtime-core"
|
|
30
48
|
}
|
|
31
49
|
}
|