@proteus-vue/render-backend 0.3.0-beta.11 → 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 +3054 -60
- 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/spi.d.ts +4 -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/dist/vue-dom.d.ts +1 -0
- package/package.json +23 -5
package/dist/index.js
CHANGED
|
@@ -80,6 +80,7 @@ var SEMANTIC_HEADLESS_MAP = {
|
|
|
80
80
|
"layout.split": "split",
|
|
81
81
|
"layout.safe": "safe",
|
|
82
82
|
"layout.sidebar": "sidebar",
|
|
83
|
+
"layout.formfactor": "formfactor",
|
|
83
84
|
"ui.text": "text",
|
|
84
85
|
"ui.button": "button",
|
|
85
86
|
"ui.image": "image",
|
|
@@ -110,6 +111,8 @@ var SEMANTIC_HEADLESS_MAP = {
|
|
|
110
111
|
"shell.segment": "segment",
|
|
111
112
|
"shell.popover": "popover",
|
|
112
113
|
"shell.action-sheet": "action-sheet",
|
|
114
|
+
"shell.toast": "toast",
|
|
115
|
+
"ui.loading": "loading",
|
|
113
116
|
"ui.rich-text": "rich-text",
|
|
114
117
|
"ui.avatar": "avatar",
|
|
115
118
|
"ui.media": "media",
|
|
@@ -175,6 +178,18 @@ function createHeadlessBackend() {
|
|
|
175
178
|
nodes.set(n.id, n);
|
|
176
179
|
return n;
|
|
177
180
|
},
|
|
181
|
+
// ★2026-09-26 文本保留:'#text' → 文本节点(内存树)
|
|
182
|
+
createText(text) {
|
|
183
|
+
const n = {
|
|
184
|
+
id: nextId++,
|
|
185
|
+
type: "#text",
|
|
186
|
+
props: {},
|
|
187
|
+
children: [],
|
|
188
|
+
parent: null,
|
|
189
|
+
text
|
|
190
|
+
};
|
|
191
|
+
return n;
|
|
192
|
+
},
|
|
178
193
|
insert(child, parent, anchor) {
|
|
179
194
|
const c = ensureNode(child);
|
|
180
195
|
const p = ensureNode(parent);
|
|
@@ -247,6 +262,8 @@ var SEMANTIC_WEB_MAP = {
|
|
|
247
262
|
"layout.split": { tag: "div", className: "proteus-split" },
|
|
248
263
|
"layout.safe": { tag: "div", className: "proteus-safe" },
|
|
249
264
|
"layout.sidebar": { tag: "div", className: "proteus-sidebar" },
|
|
265
|
+
// ★★Fluid System v2:柔性形态容器
|
|
266
|
+
"layout.formfactor": { tag: "div", className: "proteus-formfactor" },
|
|
250
267
|
"ui.text": { tag: "span" },
|
|
251
268
|
"ui.button": { tag: "button" },
|
|
252
269
|
"ui.image": { tag: "img" },
|
|
@@ -278,6 +295,9 @@ var SEMANTIC_WEB_MAP = {
|
|
|
278
295
|
"shell.segment": { tag: "div", className: "proteus-segment" },
|
|
279
296
|
"shell.popover": { tag: "div", className: "proteus-popover" },
|
|
280
297
|
"shell.action-sheet": { tag: "div", className: "proteus-action-sheet" },
|
|
298
|
+
// ★2026-09-30 补缺(U19/S8 上批加了 SEMANTIC_BACKEND_MAP 但后端控件表漏补——M3 readback 门禁抓出)
|
|
299
|
+
"shell.toast": { tag: "div", className: "proteus-toast" },
|
|
300
|
+
"ui.loading": { tag: "div", className: "proteus-loading" },
|
|
281
301
|
"ui.rich-text": { tag: "div", className: "proteus-rich-text" },
|
|
282
302
|
"ui.avatar": { tag: "div", className: "proteus-avatar" },
|
|
283
303
|
"ui.media": { tag: "div", className: "proteus-media" },
|
|
@@ -313,7 +333,9 @@ function createVueDomBackend(doc) {
|
|
|
313
333
|
const documentLike = doc ?? defaultDocument();
|
|
314
334
|
function ensureEl(handle) {
|
|
315
335
|
const el = handle;
|
|
316
|
-
if (!el || typeof el.tagName !== "string"
|
|
336
|
+
if (!el || typeof el.tagName !== "string" && el.nodeType !== 3) {
|
|
337
|
+
throw new Error("VueDomBackend: \u975E\u6CD5\u53E5\u67C4");
|
|
338
|
+
}
|
|
317
339
|
return el;
|
|
318
340
|
}
|
|
319
341
|
return {
|
|
@@ -331,6 +353,10 @@ function createVueDomBackend(doc) {
|
|
|
331
353
|
}
|
|
332
354
|
return documentLike.createElement(node.type);
|
|
333
355
|
},
|
|
356
|
+
// ★2026-09-26 文本保留:IR '#text' 节点 → 真实文本节点(此前 createText 无实现——IR 无文本节点)
|
|
357
|
+
createText(text) {
|
|
358
|
+
return documentLike.createTextNode(text);
|
|
359
|
+
},
|
|
334
360
|
insert(child, parent, anchor) {
|
|
335
361
|
const c = ensureEl(child);
|
|
336
362
|
const p = ensureEl(parent);
|
|
@@ -385,6 +411,9 @@ function createVueDomBackend(doc) {
|
|
|
385
411
|
}
|
|
386
412
|
|
|
387
413
|
// src/native.ts
|
|
414
|
+
function isBatchAdapter(a) {
|
|
415
|
+
return typeof a.commit === "function";
|
|
416
|
+
}
|
|
388
417
|
function createMockNativeAdapter() {
|
|
389
418
|
const ops = [];
|
|
390
419
|
return {
|
|
@@ -432,6 +461,7 @@ var SEMANTIC_NATIVE_MAPS = {
|
|
|
432
461
|
"layout.split": "UISplitViewController",
|
|
433
462
|
"layout.safe": "safeAreaLayoutGuide",
|
|
434
463
|
"layout.sidebar": "UISplitViewController.side",
|
|
464
|
+
"layout.formfactor": "UIViewController.formFactor",
|
|
435
465
|
"ui.text": "UILabel",
|
|
436
466
|
"ui.button": "UIButton",
|
|
437
467
|
"ui.image": "UIImageView",
|
|
@@ -462,6 +492,9 @@ var SEMANTIC_NATIVE_MAPS = {
|
|
|
462
492
|
"shell.segment": "UISegmentedControl",
|
|
463
493
|
"shell.popover": "UIPopoverController",
|
|
464
494
|
"shell.action-sheet": "UIAlertController.actionSheet",
|
|
495
|
+
// ★2026-09-30 补缺(同 vue-dom 注释)
|
|
496
|
+
"shell.toast": "UIView.toast",
|
|
497
|
+
"ui.loading": "UIActivityIndicatorView",
|
|
465
498
|
"ui.rich-text": "UITextView.attributed",
|
|
466
499
|
"ui.avatar": "UIImageView.avatar",
|
|
467
500
|
"ui.media": "AVPlayerView",
|
|
@@ -501,6 +534,7 @@ var SEMANTIC_NATIVE_MAPS = {
|
|
|
501
534
|
"layout.split": "SlidingPaneLayout",
|
|
502
535
|
"layout.safe": "WindowInsets",
|
|
503
536
|
"layout.sidebar": "NavigationRail",
|
|
537
|
+
"layout.formfactor": "FormFactorLayout",
|
|
504
538
|
"ui.text": "TextView",
|
|
505
539
|
"ui.button": "Button",
|
|
506
540
|
"ui.image": "ImageView",
|
|
@@ -531,6 +565,9 @@ var SEMANTIC_NATIVE_MAPS = {
|
|
|
531
565
|
"shell.segment": "TabLayout",
|
|
532
566
|
"shell.popover": "PopupWindow",
|
|
533
567
|
"shell.action-sheet": "BottomSheet",
|
|
568
|
+
// ★2026-09-30 补缺(同 vue-dom 注释)
|
|
569
|
+
"shell.toast": "Toast",
|
|
570
|
+
"ui.loading": "ProgressBar",
|
|
534
571
|
"ui.rich-text": "TextView.html",
|
|
535
572
|
"ui.avatar": "ImageView.avatar",
|
|
536
573
|
"ui.media": "VideoView",
|
|
@@ -570,6 +607,7 @@ var SEMANTIC_NATIVE_MAPS = {
|
|
|
570
607
|
"layout.split": "SideBarContainer",
|
|
571
608
|
"layout.safe": "getAvoidArea",
|
|
572
609
|
"layout.sidebar": "SideBarContainer",
|
|
610
|
+
"layout.formfactor": "GridRow.formFactor",
|
|
573
611
|
"ui.text": "Text",
|
|
574
612
|
"ui.button": "Button",
|
|
575
613
|
"ui.image": "Image",
|
|
@@ -600,6 +638,8 @@ var SEMANTIC_NATIVE_MAPS = {
|
|
|
600
638
|
"shell.segment": "Segmented",
|
|
601
639
|
"shell.popover": "Popup",
|
|
602
640
|
"shell.action-sheet": "ActionSheet",
|
|
641
|
+
"shell.toast": "promptAction.showToast",
|
|
642
|
+
"ui.loading": "LoadingProgress",
|
|
603
643
|
"ui.rich-text": "RichText",
|
|
604
644
|
"ui.avatar": "Image.avatar",
|
|
605
645
|
"ui.media": "Video",
|
|
@@ -631,11 +671,23 @@ var SEMANTIC_NATIVE_MAPS = {
|
|
|
631
671
|
}
|
|
632
672
|
};
|
|
633
673
|
function createNativeBackend(adapter, platform = "ios") {
|
|
634
|
-
const
|
|
674
|
+
const rawAdapter = adapter ?? createMockNativeAdapter();
|
|
675
|
+
const viewAdapter = rawAdapter;
|
|
676
|
+
const batchAdapter = isBatchAdapter(rawAdapter) ? rawAdapter : null;
|
|
635
677
|
const semanticMap = SEMANTIC_NATIVE_MAPS[platform];
|
|
636
678
|
const id = platform === "ios" ? "native-ios" : platform === "android" ? "native-android" : "native-harmony";
|
|
637
679
|
let nextId = 1;
|
|
638
680
|
const nodes = /* @__PURE__ */ new Map();
|
|
681
|
+
const pending = [];
|
|
682
|
+
let hostCalls = 0;
|
|
683
|
+
const queueOrCall = (op, imperative) => {
|
|
684
|
+
if (batchAdapter) {
|
|
685
|
+
pending.push(op);
|
|
686
|
+
return;
|
|
687
|
+
}
|
|
688
|
+
hostCalls++;
|
|
689
|
+
imperative();
|
|
690
|
+
};
|
|
639
691
|
function ensureNode(handle) {
|
|
640
692
|
const n = handle;
|
|
641
693
|
if (!n || typeof n.id !== "number") throw new Error("NativeBackend: \u975E\u6CD5\u53E5\u67C4");
|
|
@@ -656,10 +708,31 @@ function createNativeBackend(adapter, platform = "ios") {
|
|
|
656
708
|
text: "",
|
|
657
709
|
handle: null
|
|
658
710
|
};
|
|
659
|
-
descriptor.
|
|
711
|
+
queueOrCall({ op: "create", id: descriptor.id, type: viewType, props: descriptor.props }, () => {
|
|
712
|
+
descriptor.handle = viewAdapter.createView(descriptor);
|
|
713
|
+
});
|
|
714
|
+
if (!batchAdapter) descriptor.handle = descriptor.handle ?? descriptor;
|
|
660
715
|
nodes.set(descriptor.id, descriptor);
|
|
661
716
|
return descriptor;
|
|
662
717
|
},
|
|
718
|
+
// ★2026-09-26 文本保留:'#text' → 文本描述符(type 'text',text 字段承载内容——
|
|
719
|
+
// readback/控件树即见真实文案;真机渲染通道接线时映射 Text/Label 内容)
|
|
720
|
+
createText(text) {
|
|
721
|
+
const descriptor = {
|
|
722
|
+
id: nextId++,
|
|
723
|
+
type: "text",
|
|
724
|
+
props: {},
|
|
725
|
+
children: [],
|
|
726
|
+
parent: null,
|
|
727
|
+
text,
|
|
728
|
+
handle: null
|
|
729
|
+
};
|
|
730
|
+
descriptor.handle = descriptor;
|
|
731
|
+
queueOrCall({ op: "create", id: descriptor.id, type: "text", props: {} }, () => {
|
|
732
|
+
});
|
|
733
|
+
if (batchAdapter) pending.push({ op: "text", id: descriptor.id, text });
|
|
734
|
+
return descriptor;
|
|
735
|
+
},
|
|
663
736
|
insert(child, parent, anchor) {
|
|
664
737
|
const c = ensureNode(child);
|
|
665
738
|
const p = ensureNode(parent);
|
|
@@ -675,7 +748,9 @@ function createNativeBackend(adapter, platform = "ios") {
|
|
|
675
748
|
p.children.push(c);
|
|
676
749
|
}
|
|
677
750
|
c.parent = p;
|
|
678
|
-
|
|
751
|
+
queueOrCall({ op: "insert", id: c.id, parentId: p.id, anchorId: anchor ? ensureNode(anchor).id : void 0 }, () => {
|
|
752
|
+
viewAdapter.insertView(c.handle, p.handle, anchor ? ensureNode(anchor).handle : void 0);
|
|
753
|
+
});
|
|
679
754
|
},
|
|
680
755
|
remove(child) {
|
|
681
756
|
const c = ensureNode(child);
|
|
@@ -685,7 +760,7 @@ function createNativeBackend(adapter, platform = "ios") {
|
|
|
685
760
|
c.parent = null;
|
|
686
761
|
}
|
|
687
762
|
nodes.delete(c.id);
|
|
688
|
-
viewAdapter.removeView(c.handle);
|
|
763
|
+
queueOrCall({ op: "remove", id: c.id }, () => viewAdapter.removeView(c.handle));
|
|
689
764
|
},
|
|
690
765
|
patchProp(el, key, prev, next) {
|
|
691
766
|
const n = ensureNode(el);
|
|
@@ -694,15 +769,159 @@ function createNativeBackend(adapter, platform = "ios") {
|
|
|
694
769
|
} else {
|
|
695
770
|
n.props[key] = next;
|
|
696
771
|
}
|
|
697
|
-
viewAdapter.updateView(n.handle, key, prev, next);
|
|
772
|
+
queueOrCall({ op: "patch", id: n.id, key, prev, next }, () => viewAdapter.updateView(n.handle, key, prev, next));
|
|
698
773
|
},
|
|
699
774
|
setText(el, text) {
|
|
700
775
|
const n = ensureNode(el);
|
|
701
776
|
n.text = text;
|
|
702
|
-
viewAdapter.setViewText(n.handle, text);
|
|
777
|
+
queueOrCall({ op: "text", id: n.id, text }, () => viewAdapter.setViewText(n.handle, text));
|
|
703
778
|
},
|
|
704
779
|
measure() {
|
|
705
780
|
return { width: 0, height: 0 };
|
|
781
|
+
},
|
|
782
|
+
// ★★C1 批量模式的两个接口(逐节点模式下:flush 为空操作、hostCalls 反映真实调用数)
|
|
783
|
+
/** 提交本帧累积的操作(**一次跨边界调用**——批处理红线的落点) */
|
|
784
|
+
flush() {
|
|
785
|
+
if (!batchAdapter) return;
|
|
786
|
+
if (pending.length === 0) return;
|
|
787
|
+
hostCalls++;
|
|
788
|
+
const batch = pending.splice(0, pending.length);
|
|
789
|
+
batchAdapter.commit(batch);
|
|
790
|
+
},
|
|
791
|
+
/** 已发生的宿主调用数(判据:批量模式下应 == flush 次数,不随节点数增长) */
|
|
792
|
+
hostCalls() {
|
|
793
|
+
return hostCalls;
|
|
794
|
+
}
|
|
795
|
+
};
|
|
796
|
+
}
|
|
797
|
+
|
|
798
|
+
// src/selfdraw-batch.ts
|
|
799
|
+
function createSelfDrawBatchAdapter(host, opts) {
|
|
800
|
+
const mirror = /* @__PURE__ */ new Map();
|
|
801
|
+
const rootOrder = [];
|
|
802
|
+
let calls = 0;
|
|
803
|
+
let lastKind = null;
|
|
804
|
+
let mounted = false;
|
|
805
|
+
const normalize = opts.normalizeProps ?? ((p) => p);
|
|
806
|
+
function removeSubtree(id) {
|
|
807
|
+
const n = mirror.get(id);
|
|
808
|
+
if (!n) return;
|
|
809
|
+
for (const c of n.children) removeSubtree(c);
|
|
810
|
+
mirror.delete(id);
|
|
811
|
+
const at = rootOrder.indexOf(id);
|
|
812
|
+
if (at >= 0) rootOrder.splice(at, 1);
|
|
813
|
+
}
|
|
814
|
+
function specs() {
|
|
815
|
+
const out = [];
|
|
816
|
+
const visit = (id) => {
|
|
817
|
+
const n = mirror.get(id);
|
|
818
|
+
if (!n) return;
|
|
819
|
+
const spec = { id: n.id, parentId: n.parentId, ...normalize(n.props) };
|
|
820
|
+
if (n.text !== void 0) spec.text = n.text;
|
|
821
|
+
out.push(spec);
|
|
822
|
+
for (const c of n.children) visit(c);
|
|
823
|
+
};
|
|
824
|
+
for (const r of rootOrder) visit(r);
|
|
825
|
+
return out;
|
|
826
|
+
}
|
|
827
|
+
return {
|
|
828
|
+
commit(ops) {
|
|
829
|
+
let structural = false;
|
|
830
|
+
const patched = /* @__PURE__ */ new Map();
|
|
831
|
+
const patch = (id, key, value) => {
|
|
832
|
+
const s = patched.get(id) ?? {};
|
|
833
|
+
if (value === null || value === void 0) delete s[key];
|
|
834
|
+
else s[key] = value;
|
|
835
|
+
patched.set(id, s);
|
|
836
|
+
};
|
|
837
|
+
for (const op of ops) {
|
|
838
|
+
switch (op.op) {
|
|
839
|
+
case "create": {
|
|
840
|
+
mirror.set(op.id, {
|
|
841
|
+
id: op.id,
|
|
842
|
+
type: op.type,
|
|
843
|
+
props: { ...op.props },
|
|
844
|
+
parentId: null,
|
|
845
|
+
children: []
|
|
846
|
+
});
|
|
847
|
+
rootOrder.push(op.id);
|
|
848
|
+
structural = true;
|
|
849
|
+
break;
|
|
850
|
+
}
|
|
851
|
+
case "insert": {
|
|
852
|
+
const n = mirror.get(op.id);
|
|
853
|
+
if (!n) break;
|
|
854
|
+
if (n.parentId !== null) {
|
|
855
|
+
const old = mirror.get(n.parentId);
|
|
856
|
+
if (old) {
|
|
857
|
+
const i = old.children.indexOf(n.id);
|
|
858
|
+
if (i >= 0) old.children.splice(i, 1);
|
|
859
|
+
}
|
|
860
|
+
} else {
|
|
861
|
+
const i = rootOrder.indexOf(n.id);
|
|
862
|
+
if (i >= 0) rootOrder.splice(i, 1);
|
|
863
|
+
}
|
|
864
|
+
n.parentId = op.parentId;
|
|
865
|
+
if (op.parentId === null) {
|
|
866
|
+
rootOrder.push(n.id);
|
|
867
|
+
} else {
|
|
868
|
+
const p = mirror.get(op.parentId);
|
|
869
|
+
if (p) {
|
|
870
|
+
const at = op.anchorId === void 0 ? -1 : p.children.indexOf(op.anchorId);
|
|
871
|
+
if (at >= 0) p.children.splice(at, 0, n.id);
|
|
872
|
+
else p.children.push(n.id);
|
|
873
|
+
}
|
|
874
|
+
}
|
|
875
|
+
structural = true;
|
|
876
|
+
break;
|
|
877
|
+
}
|
|
878
|
+
case "remove": {
|
|
879
|
+
removeSubtree(op.id);
|
|
880
|
+
structural = true;
|
|
881
|
+
break;
|
|
882
|
+
}
|
|
883
|
+
case "patch": {
|
|
884
|
+
const n = mirror.get(op.id);
|
|
885
|
+
if (!n) break;
|
|
886
|
+
if (op.next === null || op.next === void 0) delete n.props[op.key];
|
|
887
|
+
else n.props[op.key] = op.next;
|
|
888
|
+
patch(op.id, op.key, op.next);
|
|
889
|
+
break;
|
|
890
|
+
}
|
|
891
|
+
case "text": {
|
|
892
|
+
const n = mirror.get(op.id);
|
|
893
|
+
if (!n) break;
|
|
894
|
+
n.text = op.text;
|
|
895
|
+
patch(op.id, "text", op.text);
|
|
896
|
+
break;
|
|
897
|
+
}
|
|
898
|
+
}
|
|
899
|
+
}
|
|
900
|
+
if (!mounted) {
|
|
901
|
+
host.mount(JSON.stringify({ viewport: opts.viewport, nodes: specs() }));
|
|
902
|
+
mounted = true;
|
|
903
|
+
lastKind = "mount";
|
|
904
|
+
} else if (structural) {
|
|
905
|
+
host.update(JSON.stringify({ viewport: opts.viewport, nodes: specs() }));
|
|
906
|
+
lastKind = "update";
|
|
907
|
+
} else {
|
|
908
|
+
const arr = [...patched.entries()].map(([id, style]) => ({ id, style }));
|
|
909
|
+
host.updatePatches(JSON.stringify(arr));
|
|
910
|
+
lastKind = "updatePatches";
|
|
911
|
+
}
|
|
912
|
+
calls++;
|
|
913
|
+
},
|
|
914
|
+
hostCalls() {
|
|
915
|
+
return calls;
|
|
916
|
+
},
|
|
917
|
+
lastCallKind() {
|
|
918
|
+
return lastKind;
|
|
919
|
+
},
|
|
920
|
+
nodeSpecs() {
|
|
921
|
+
return specs();
|
|
922
|
+
},
|
|
923
|
+
commitCount() {
|
|
924
|
+
return calls;
|
|
706
925
|
}
|
|
707
926
|
};
|
|
708
927
|
}
|
|
@@ -735,6 +954,7 @@ var SEMANTIC_FLUTTER_MAP = {
|
|
|
735
954
|
"layout.split": "Row",
|
|
736
955
|
"layout.safe": "SafeArea",
|
|
737
956
|
"layout.sidebar": "NavigationRail",
|
|
957
|
+
"layout.formfactor": "LayoutBuilder.formFactor",
|
|
738
958
|
"ui.text": "Text",
|
|
739
959
|
"ui.button": "FilledButton",
|
|
740
960
|
"ui.image": "Image",
|
|
@@ -765,6 +985,8 @@ var SEMANTIC_FLUTTER_MAP = {
|
|
|
765
985
|
"shell.segment": "SegmentedButton",
|
|
766
986
|
"shell.popover": "showMenu",
|
|
767
987
|
"shell.action-sheet": "showModalBottomSheet",
|
|
988
|
+
"shell.toast": "ScaffoldMessenger.showSnackBar",
|
|
989
|
+
"ui.loading": "CircularProgressIndicator",
|
|
768
990
|
"ui.rich-text": "RichText",
|
|
769
991
|
"ui.avatar": "CircleAvatar",
|
|
770
992
|
"ui.media": "VideoPlayer",
|
|
@@ -846,6 +1068,18 @@ function createFlutterBackend() {
|
|
|
846
1068
|
nodes.set(descriptor.id, descriptor);
|
|
847
1069
|
return descriptor;
|
|
848
1070
|
},
|
|
1071
|
+
// ★2026-09-26 文本保留:'#text' → Text widget 描述符
|
|
1072
|
+
createText(text) {
|
|
1073
|
+
const descriptor = {
|
|
1074
|
+
id: nextId++,
|
|
1075
|
+
widget: "Text",
|
|
1076
|
+
props: {},
|
|
1077
|
+
children: [],
|
|
1078
|
+
parent: null,
|
|
1079
|
+
text
|
|
1080
|
+
};
|
|
1081
|
+
return descriptor;
|
|
1082
|
+
},
|
|
849
1083
|
insert(child, parent, anchor) {
|
|
850
1084
|
const c = ensureNode(child);
|
|
851
1085
|
const p = ensureNode(parent);
|
|
@@ -990,6 +1224,10 @@ function createHybridRenderer(options) {
|
|
|
990
1224
|
}
|
|
991
1225
|
return handle;
|
|
992
1226
|
},
|
|
1227
|
+
// ★2026-09-26 文本保留:'#text' → 委托默认后端(文本无 semantic 不可路由——区域语义由父元素决定)
|
|
1228
|
+
createText(text) {
|
|
1229
|
+
return defaultBackend.createText(text);
|
|
1230
|
+
},
|
|
993
1231
|
insert(child, parent, anchor) {
|
|
994
1232
|
const b = backendOf(child);
|
|
995
1233
|
if (!b) return;
|
|
@@ -1129,8 +1367,17 @@ var TAG_SEMANTIC_MAP = {
|
|
|
1129
1367
|
"p-segment": "shell.segment",
|
|
1130
1368
|
"p-drawer": "shell.drawer",
|
|
1131
1369
|
"p-modal": "shell.modal",
|
|
1370
|
+
// ★★★GP4-c(2026-10-03):登录失效拦截弹窗——与 p-modal **同语义**(模态弹窗)、
|
|
1371
|
+
// **不同可取消性**(本组件不可取消:无关闭按钮/点遮罩不关;唯一出口是登录态恢复)。
|
|
1372
|
+
// ★不登记 ⇒ 编译期按"未注册自定义组件"输出 ⇒ MP 端不渲染(M3 门禁会当场红)
|
|
1373
|
+
"p-auth-gate": "shell.modal",
|
|
1132
1374
|
"p-popover": "shell.popover",
|
|
1133
1375
|
"p-toast": "shell.toast",
|
|
1376
|
+
// ★★★GP4-a/b(2026-10-03):两个**浮层宿主**同属其能力的语义(multi-tag 别名——同 ui.loading 的先例)。
|
|
1377
|
+
// · p-toast-host:Toast 队列的渲染端(构建期按需注入)
|
|
1378
|
+
// · p-loading-host:Loading 多实例的渲染端(同上)
|
|
1379
|
+
// ★不登记 ⇒ 编译期按"未注册自定义组件"输出 ⇒ **MP 端不渲染**(M3 门禁当场抓出)
|
|
1380
|
+
"p-toast-host": "shell.toast",
|
|
1134
1381
|
"p-action-sheet": "shell.action-sheet",
|
|
1135
1382
|
"p-split": "layout.split",
|
|
1136
1383
|
// ★已落地绑定(G-32 S10 分栏语义——layout.split 承载)
|
|
@@ -1168,7 +1415,16 @@ var TAG_SEMANTIC_MAP = {
|
|
|
1168
1415
|
// ——同一语义由两个标签提供;本条与 p-view 是并列的别名情形,非「先例」关系)
|
|
1169
1416
|
"p-aspect": "layout.aspect",
|
|
1170
1417
|
"p-zone": "layout.zone",
|
|
1418
|
+
"p-formfactor": "layout.formfactor",
|
|
1419
|
+
// ★★Fluid System v2:柔性形态容器
|
|
1171
1420
|
"p-loading": "ui.loading",
|
|
1421
|
+
// ★★★GP4-b(2026-10-03):Loading **多实例宿主**与**区域遮罩**同属 ui.loading 语义(multi-tag 别名,
|
|
1422
|
+
// 与上一行 `router-link`/`p-view` 的"同一语义多标签"同款)。
|
|
1423
|
+
// · p-loading-host:命令式多实例的渲染端(由构建期按需注入——用户一般不手写)
|
|
1424
|
+
// · p-loading-region:区域遮罩(就地包裹,用户手写)
|
|
1425
|
+
// ★不登记 ⇒ 编译期按"未注册自定义组件"输出 ⇒ MP 端不渲染(M3 门禁当场抓出——它是对的)
|
|
1426
|
+
"p-loading-host": "ui.loading",
|
|
1427
|
+
"p-loading-region": "ui.loading",
|
|
1172
1428
|
"p-scale": "ui.scale",
|
|
1173
1429
|
"p-skeleton": "ui.skeleton",
|
|
1174
1430
|
"p-mask": "shell.mask",
|
|
@@ -1219,6 +1475,8 @@ var LAYOUT = [
|
|
|
1219
1475
|
// ★#405 语义登记批:Fluid 体系剩余组件(语义层待多端映射 → planned L2——G-31.4 不足 3 端降级)
|
|
1220
1476
|
{ id: "L13", kind: "layout", semantic: "layout.aspect", tag: "p-aspect", props: ["ratio", "maxWidth"], mpEquiv: "\u65E0\uFF08\u7EB5\u6A2A\u6BD4\u5BB9\u5668\uFF09", tier: "L2", status: "planned" },
|
|
1221
1477
|
{ id: "L14", kind: "layout", semantic: "layout.zone", tag: "p-zone", props: ["designWidth"], mpEquiv: "\u65E0\uFF08\u5BB9\u5668\u65AD\u70B9\u5206\u533A\uFF09", tier: "L2", status: "planned" },
|
|
1478
|
+
// ★★Fluid System v2(2026-09-26):柔性形态容器——按设备形态自动编排布局拓扑/能力/密度/缩放
|
|
1479
|
+
{ id: "L26", kind: "layout", semantic: "layout.formfactor", tag: "p-formfactor", props: ["declared", "width", "height"], mpEquiv: "\u65E0\uFF08\u5F62\u6001\u7F16\u6392\u5BB9\u5668\uFF1Aglance/stack/duo/rail-split/rail-grid/dashboard/hero-focus-row\uFF09", tier: "L2", status: "implemented" },
|
|
1222
1480
|
// ★批次 7(2026-09-18)未登记组件补登记:安全区(刘海/折叠屏/降级)+ 侧边导航容器
|
|
1223
1481
|
{ id: "L15", kind: "layout", semantic: "layout.safe", tag: "p-safe", props: ["area", "fold", "fallback"], mpEquiv: "\u65E0\uFF08\u5B89\u5168\u533A\u8BED\u4E49\u5BB9\u5668\uFF09", tier: "L1", status: "implemented" },
|
|
1224
1482
|
{ id: "L16", kind: "layout", semantic: "layout.sidebar", tag: "p-sidebar", props: ["minSidebarWidth", "navWidth", "designWidth", "toggleLabel"], mpEquiv: "\u65E0\uFF08\u5BB9\u5668\u65AD\u70B9\u4FA7\u680F\uFF09", tier: "L1", status: "implemented" }
|
|
@@ -1243,7 +1501,9 @@ var UI = [
|
|
|
1243
1501
|
{ id: "U17", kind: "ui", semantic: "ui.picker", tag: "p-picker", props: ["mode", "start", "end", "indicatorStyle", "indicatorClass", "maskClass", "maskStyle", "immediateChange"], mpEquiv: "<picker>", tier: "L1", status: "implemented" },
|
|
1244
1502
|
{ id: "U18", kind: "ui", semantic: "ui.form", tag: "p-form", props: ["model", "rules", "layout"], mpEquiv: "\u7EC4\u5408", tier: "L1", status: "implemented" },
|
|
1245
1503
|
// ★#405 语义登记批:反馈/状态类组件
|
|
1246
|
-
|
|
1504
|
+
// ★2026-09-30 反馈组件补齐:组件本体(p-loading)与 MP 产物测试早已存在,
|
|
1505
|
+
// 缺的只是 SEMANTIC_BACKEND_MAP 的后端映射登记 ⇒ 补齐后据实转 implemented(**修记账非修能力**)
|
|
1506
|
+
{ id: "U19", kind: "ui", semantic: "ui.loading", tag: "p-loading", props: ["size", "text"], mpEquiv: "wx.showLoading \u90E8\u5206", tier: "L1", status: "implemented" },
|
|
1247
1507
|
{ id: "U20", kind: "ui", semantic: "ui.scale", tag: "p-scale", props: ["level", "density", "baseSize"], mpEquiv: "\u65E0\uFF08\u65E0\u969C\u788D\u6863\u4F4D\uFF09", tier: "L2", status: "planned" },
|
|
1248
1508
|
{ id: "U21", kind: "ui", semantic: "ui.skeleton", tag: "p-skeleton", props: ["rows", "avatar", "animated"], mpEquiv: "\u65E0", tier: "L2", status: "planned" },
|
|
1249
1509
|
// ★能力颗粒度对齐 C2:进度条 / 表单标签
|
|
@@ -1271,7 +1531,8 @@ var SHELL = [
|
|
|
1271
1531
|
{ id: "S5", kind: "shell", semantic: "shell.drawer", tag: "p-drawer", props: ["side", "width", "overlay"], mpEquiv: "\u7EC4\u5408", tier: "L1", status: "implemented" },
|
|
1272
1532
|
{ id: "S6", kind: "shell", semantic: "shell.modal", tag: "p-modal", props: ["open", "dismissible", "sheet", "dialog", "alert"], mpEquiv: "<modal>+wx.showModal", tier: "L1", status: "implemented" },
|
|
1273
1533
|
{ id: "S7", kind: "shell", semantic: "shell.popover", tag: "p-popover", props: ["trigger", "placement"], mpEquiv: "\u7EC4\u5408", tier: "L1", status: "implemented" },
|
|
1274
|
-
|
|
1534
|
+
// ★2026-09-30 同上(p-toast 已有 MP 产物测试 + 7 端映射 ⇒ 据实转 implemented)
|
|
1535
|
+
{ id: "S8", kind: "shell", semantic: "shell.toast", tag: "p-toast", props: ["message", "duration", "type"], mpEquiv: "wx.showToast", tier: "L1", status: "implemented" },
|
|
1275
1536
|
{ id: "S9", kind: "shell", semantic: "shell.action-sheet", tag: "p-action-sheet", props: ["actions", "cancel"], mpEquiv: "wx.showActionSheet", tier: "L1", status: "implemented" },
|
|
1276
1537
|
{ id: "S10", kind: "shell", semantic: "layout.split", tag: "p-split", props: ["breakpoint", "ratio", "collapse"], mpEquiv: "\u65E0\uFF08\u5206\u680F\u5E03\u5C40\uFF09", tier: "L1", status: "implemented" },
|
|
1277
1538
|
// ★已落地绑定:分栏语义由 layout.split 承载(G-32 文档为 shell.split——机器事实以实现为准)
|
|
@@ -1332,9 +1593,13 @@ var CAPABILITY = [
|
|
|
1332
1593
|
cap("C20", "calendar", "useCalendar()", "wx.addPhoneCalendar", "CalendarAPI"),
|
|
1333
1594
|
cap("C21", "phone-call", "usePhoneCall()", "wx.makePhoneCall", "Result<void>"),
|
|
1334
1595
|
cap("C22", "sms", "useSMS()", "wx.??\uFF08\u53D7\u9650\uFF09", "Result<void>"),
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1596
|
+
// ★★2026-09-30:C23/C24/C25 App 宿主腿落地(此前仅 wx/web 两桥 ⇒ App 端形同虚设)——
|
|
1597
|
+
// App 桥 `packages/api/src/capability-app.ts`(总线 + 三能力 + 虚拟栈页面事件翻译器);
|
|
1598
|
+
// 真机判据 `check:host-runtime` ①~⑤(冷启动补 launch / 真实栈驱动页面生命周期 /
|
|
1599
|
+
// 壳真实事件逐条驱动总线 / 启动参数经 job 泵 / 阶段快照)。
|
|
1600
|
+
{ id: "C23", kind: "capability", semantic: "capability.app-lifecycle", api: "useAppLifecycle()", props: ["LifecycleHooks"], mpEquiv: "App.onLaunch/onShow", tier: "L1", status: "implemented" },
|
|
1601
|
+
{ id: "C24", kind: "capability", semantic: "capability.page-lifecycle", api: "usePageLifecycle()", props: ["LifecycleHooks"], mpEquiv: "Page.onLoad/onShow", tier: "L1", status: "implemented" },
|
|
1602
|
+
{ id: "C25", kind: "capability", semantic: "capability.background", api: "useBackground()", props: ["BackgroundAPI"], mpEquiv: "wx.onBackground", tier: "L1", status: "implemented" },
|
|
1338
1603
|
// 7.3 通信/数据(10)
|
|
1339
1604
|
cap("C26", "fetch", "useFetch()", "wx.request", "Promise<T>"),
|
|
1340
1605
|
cap("C27", "websocket", "useWebSocket()", "wx.connectSocket", "WSConnection"),
|
|
@@ -1360,15 +1625,15 @@ var CAPABILITY = [
|
|
|
1360
1625
|
cap("C45", "shortcut", "useShortcut()", "wx.addToDesktop", "Result<void>"),
|
|
1361
1626
|
cap("C46", "in-app-purchase", "useInAppPurchase()", "wx.requestPayment \u6269\u5C55", "Result<Receipt>"),
|
|
1362
1627
|
// 7.5 平台特有(4——C47-C50,此前 7.4 末 C46,总 50)
|
|
1363
|
-
{ id: "C47", kind: "capability", semantic: "capability.mini-program", api: "useMiniProgram()", props: ["MPContext"], mpEquiv: "wx.navigateToMiniProgram", tier: "L1", status: "
|
|
1364
|
-
{ id: "C48", kind: "capability", semantic: "capability.embedded", api: "useEmbedded()", props: ["HostContext"], mpEquiv: "\u65E0\uFF08\u88AB\u5BBF\u4E3B\u5D4C\u5165\uFF09", tier: "L1", status: "
|
|
1628
|
+
{ id: "C47", kind: "capability", semantic: "capability.mini-program", api: "useMiniProgram()", props: ["MPContext"], mpEquiv: "wx.navigateToMiniProgram", tier: "L1", status: "implemented" },
|
|
1629
|
+
{ id: "C48", kind: "capability", semantic: "capability.embedded", api: "useEmbedded()", props: ["HostContext"], mpEquiv: "\u65E0\uFF08\u88AB\u5BBF\u4E3B\u5D4C\u5165\uFF09", tier: "L1", status: "implemented" },
|
|
1365
1630
|
{ id: "C49", kind: "capability", semantic: "capability.live", api: "useLive()", props: ["LiveRoom"], mpEquiv: "wx...\uFF08\u76F4\u64AD\u7EC4\u4EF6\uFF09", tier: "L1", status: "planned" },
|
|
1366
|
-
{ id: "C50", kind: "capability", semantic: "capability.extension", api: "useExtension()", props: ["ExtensionAPI"], mpEquiv: "\u65E0\uFF08\u63D2\u4EF6/\u6269\u5C55\u70B9 G-21\uFF09", tier: "L1", status: "
|
|
1631
|
+
{ id: "C50", kind: "capability", semantic: "capability.extension", api: "useExtension()", props: ["ExtensionAPI"], mpEquiv: "\u65E0\uFF08\u63D2\u4EF6/\u6269\u5C55\u70B9 G-21\uFF09", tier: "L1", status: "implemented" },
|
|
1367
1632
|
// ★颗粒度对齐 C(2026-09-11):C51 小程序热更新(wx.getUpdateManager)
|
|
1368
|
-
{ id: "C51", kind: "capability", semantic: "capability.update", api: "useUpdate()", props: ["UpdateManagerAPI"], mpEquiv: "wx.getUpdateManager", tier: "L1", status: "
|
|
1633
|
+
{ id: "C51", kind: "capability", semantic: "capability.update", api: "useUpdate()", props: ["UpdateManagerAPI"], mpEquiv: "wx.getUpdateManager", tier: "L1", status: "implemented" },
|
|
1369
1634
|
// ★颗粒度对齐 C3(2026-09-11):相册 / Worker(对齐小程序媒体与多线程 API——纯 Hook,无 C-IR 节点 → planned)
|
|
1370
1635
|
{ id: "C52", kind: "capability", semantic: "capability.album", api: "useAlbum()", props: ["AlbumAPI"], mpEquiv: "wx.chooseMedia/saveImageToPhotosAlbum/previewMedia", tier: "L1", status: "planned" },
|
|
1371
|
-
{ id: "C53", kind: "capability", semantic: "capability.worker", api: "useWorker()", props: ["WorkerHandle"], mpEquiv: "wx.createWorker", tier: "L1", status: "
|
|
1636
|
+
{ id: "C53", kind: "capability", semantic: "capability.worker", api: "useWorker()", props: ["WorkerHandle"], mpEquiv: "wx.createWorker", tier: "L1", status: "implemented" },
|
|
1372
1637
|
// ★颗粒度对齐 C3 批 2:收货地址 / WiFi / 微信运动(对齐小程序系统 API——纯 Hook → planned)
|
|
1373
1638
|
{ id: "C54", kind: "capability", semantic: "capability.address", api: "useAddress()", props: ["ShippingAddress"], mpEquiv: "wx.chooseAddress", tier: "L1", status: "planned" },
|
|
1374
1639
|
{ id: "C55", kind: "capability", semantic: "capability.wifi", api: "useWifi()", props: ["WifiAPI"], mpEquiv: "wx.getConnectedWifi/getWifiList/connectWifi", tier: "L1", status: "planned" },
|
|
@@ -1388,7 +1653,7 @@ var CAPABILITY = [
|
|
|
1388
1653
|
{ id: "C65", kind: "capability", semantic: "capability.privacy", api: "usePrivacy()", props: ["PrivacyAPI"], mpEquiv: "wx.getPrivacySetting/openPrivacyContract/requirePrivacyAuthorize", tier: "L1", status: "planned" },
|
|
1389
1654
|
// ★权威标尺缺口补齐批 D(2026-09-12):性能 / 预加载 / 图像编辑(通用能力,无资质门槛)
|
|
1390
1655
|
{ id: "C66", kind: "capability", semantic: "capability.performance", api: "usePerformance()", props: ["PerformanceAPI"], mpEquiv: "wx.getPerformance/reportPerformance", tier: "L1", status: "planned" },
|
|
1391
|
-
{ id: "C67", kind: "capability", semantic: "capability.preload", api: "usePreload()", props: ["PreloadAPI"], mpEquiv: "wx.preloadAssets/preloadSkylineView/preloadWebview/preDownloadSubpackage", tier: "L1", status: "
|
|
1656
|
+
{ id: "C67", kind: "capability", semantic: "capability.preload", api: "usePreload()", props: ["PreloadAPI"], mpEquiv: "wx.preloadAssets/preloadSkylineView/preloadWebview/preDownloadSubpackage", tier: "L1", status: "implemented" },
|
|
1392
1657
|
{ id: "C68", kind: "capability", semantic: "capability.image-edit", api: "useImageEdit()", props: ["ImageEditAPI"], mpEquiv: "wx.cropImage/editImage", tier: "L1", status: "planned" },
|
|
1393
1658
|
// ★权威标尺缺口补齐批 E(2026-09-12):网络底层 / 媒体高级
|
|
1394
1659
|
{ id: "C69", kind: "capability", semantic: "capability.socket", api: "useSocket()", props: ["UDPSocketHandle / TCPSocketHandle"], mpEquiv: "wx.createUDPSocket/createTCPSocket", tier: "L1", status: "planned" },
|
|
@@ -1396,16 +1661,37 @@ var CAPABILITY = [
|
|
|
1396
1661
|
// ★权威标尺缺口补齐批 F(2026-09-12):录屏/缓存/空闲/窗口/导航拦截
|
|
1397
1662
|
{ id: "C71", kind: "capability", semantic: "capability.screen-capture", api: "useScreenCapture()", props: ["ScreenCaptureAPI"], mpEquiv: "wx.getScreenRecordingState/onScreenRecordingStateChanged/onUserCaptureScreen/checkIsPictureInPictureActive", tier: "L1", status: "planned" },
|
|
1398
1663
|
{ id: "C72", kind: "capability", semantic: "capability.cache-manager", api: "useCacheManager()", props: ["CacheManagerHandle"], mpEquiv: "wx.createCacheManager", tier: "L1", status: "planned" },
|
|
1399
|
-
{ id: "C73", kind: "capability", semantic: "capability.idle", api: "useIdle()", props: ["IdleAPI"], mpEquiv: "wx.requestIdleCallback/cancelIdleCallback", tier: "L1", status: "
|
|
1400
|
-
{ id: "C74", kind: "capability", semantic: "capability.window", api: "useWindow()", props: ["WindowAPI"], mpEquiv: "wx.setWindowSize", tier: "L1", status: "
|
|
1401
|
-
{ id: "C75", kind: "capability", semantic: "capability.navigation-guard", api: "useNavigationGuard()", props: ["NavigationGuardAPI"], mpEquiv: "wx.enableAlertBeforeUnload/disableAlertBeforeUnload", tier: "L1", status: "
|
|
1664
|
+
{ id: "C73", kind: "capability", semantic: "capability.idle", api: "useIdle()", props: ["IdleAPI"], mpEquiv: "wx.requestIdleCallback/cancelIdleCallback", tier: "L1", status: "implemented" },
|
|
1665
|
+
{ id: "C74", kind: "capability", semantic: "capability.window", api: "useWindow()", props: ["WindowAPI"], mpEquiv: "wx.setWindowSize", tier: "L1", status: "implemented" },
|
|
1666
|
+
{ id: "C75", kind: "capability", semantic: "capability.navigation-guard", api: "useNavigationGuard()", props: ["NavigationGuardAPI"], mpEquiv: "wx.enableAlertBeforeUnload/disableAlertBeforeUnload", tier: "L1", status: "implemented" },
|
|
1402
1667
|
// ★权威标尺缺口补齐批 G(2026-09-12):AR/XR / iBeacon / 局域网 / 翻译 / 海报 / 设备探测
|
|
1403
1668
|
{ id: "C76", kind: "capability", semantic: "capability.ar", api: "useAR()", props: ["ARAPI"], mpEquiv: "wx.createVKSession/isVKSupport", tier: "L1", status: "planned" },
|
|
1404
1669
|
{ id: "C77", kind: "capability", semantic: "capability.beacon", api: "useBeacon()", props: ["BeaconAPI"], mpEquiv: "wx.onBeaconServiceChange/onBeaconUpdate", tier: "L1", status: "planned" },
|
|
1405
1670
|
{ id: "C78", kind: "capability", semantic: "capability.local-service", api: "useLocalService()", props: ["LocalServiceAPI"], mpEquiv: "wx.onLocalServiceFound/Lost/ResolveFail/DiscoveryStop", tier: "L1", status: "planned" },
|
|
1406
1671
|
{ id: "C79", kind: "capability", semantic: "capability.translation", api: "useTranslation()", props: ["TranslationAPI"], mpEquiv: "wx.onUserTriggerTranslation/onUserOffTranslation", tier: "L1", status: "planned" },
|
|
1407
1672
|
{ id: "C80", kind: "capability", semantic: "capability.poster", api: "usePoster()", props: ["PosterAPI"], mpEquiv: "wx.onGeneratePoster", tier: "L1", status: "planned" },
|
|
1408
|
-
{ id: "C81", kind: "capability", semantic: "capability.device-capability", api: "useDeviceCapability()", props: ["DeviceCapabilityAPI"], mpEquiv: "wx.checkDeviceSupportHevc", tier: "L1", status: "planned" }
|
|
1673
|
+
{ id: "C81", kind: "capability", semantic: "capability.device-capability", api: "useDeviceCapability()", props: ["DeviceCapabilityAPI"], mpEquiv: "wx.checkDeviceSupportHevc", tier: "L1", status: "planned" },
|
|
1674
|
+
// ★C82(2026-09-29):WebAssembly 跨平台实现——**超官方清单**能力(官方 301 API 里无 wasm)。
|
|
1675
|
+
// 三端形态有实质差异(双源取证:微信官方文档 /framework/performance/wasm.html +
|
|
1676
|
+
// miniprogram-api-typings/lib.wx.wasm.d.ts):
|
|
1677
|
+
// MP `WXWebAssembly.instantiate(path)` 只收**代码包路径**(.wasm/.wasm.br)· 无 compile/validate ·
|
|
1678
|
+
// v2.13.0+ 全局 / v2.15.0+ Worker;Web 与 App-iOS(JSC) 走标准 `WebAssembly`(收字节 · 有 compile/validate);
|
|
1679
|
+
// App-Android 由**宿主 wasm3** 执行(QuickJS 内建无 WASM——双端真机验证 add(2,40)=42)。
|
|
1680
|
+
// ⇒ 归一入口 `instantiate({bytes}|{path})` + 能力位 `supportsStreaming`/`supportsPathLoad`。
|
|
1681
|
+
{ id: "C82", kind: "capability", semantic: "capability.webassembly", api: "useWebAssembly()", props: ["WebAssemblyAPI"], mpEquiv: "WXWebAssembly.instantiate\uFF08\u5B98\u65B9\u6587\u6863 performance/wasm\uFF09", tier: "L1", status: "implemented" },
|
|
1682
|
+
// ★★NC1 首个声明式能力(2026-09-30):来源 = 权威标尺修复后浮现的真实缺口之一
|
|
1683
|
+
// (快照抽取器修复前 `wx.setKeepScreenOn` 结构性不可见,见 mp-spec-coverage 的 SPEC_PLANNED)。
|
|
1684
|
+
// 落地方式 = **声明式**(`packages/api/src/bridge-decls/keep-screen-on.ts` → 生成 → 合并进 bridge):
|
|
1685
|
+
// MP `wx.setKeepScreenOn` / Web **Screen Wake Lock**(`navigator.wakeLock.request('screen')`)——
|
|
1686
|
+
// 双端都有真实对等(非降级对等)。status 标 implemented:桥与 Hook 已落地且测试覆盖
|
|
1687
|
+
// (含双端真跑 + 幂等 + 缺 API 降级)。
|
|
1688
|
+
{ id: "C83", kind: "capability", semantic: "capability.keep-screen-on", api: "useKeepScreenOn()", props: ["KeepScreenOnState"], mpEquiv: "wx.setKeepScreenOn\uFF08Web \u5BF9\u7B49\uFF1AScreen Wake Lock API\uFF09", tier: "L1", status: "implemented" },
|
|
1689
|
+
// ★★NC1 第二个声明式能力(2026-09-30):**用户裁定**「openDocument 属原生能力落地范畴,
|
|
1690
|
+
// 目标就是 99% 的业务代码不需要写原生代码」⇒ 落地而非搁置。
|
|
1691
|
+
// 形态 = 声明式(bridge-decls/open-document.ts → 生成 → 合并);MP `wx.openDocument` /
|
|
1692
|
+
// Web `window.open`(浏览器查看器);★Web 诚实边界:office 格式通常转下载而非预览。
|
|
1693
|
+
// status = implemented:桥与 Hook 已落地 + 测试覆盖(含"弹窗被拦截 ⇒ Err"的防静默判据)。
|
|
1694
|
+
{ id: "C84", kind: "capability", semantic: "capability.document-preview", api: "useOpenDocument()", props: ["DocumentPath"], mpEquiv: "wx.openDocument\uFF08Web \u5BF9\u7B49\uFF1Awindow.open \u2192 \u6D4F\u89C8\u5668\u67E5\u770B\u5668\uFF09", tier: "L1", status: "implemented" }
|
|
1409
1695
|
];
|
|
1410
1696
|
var ENGINEERING = [
|
|
1411
1697
|
{ id: "E1", kind: "engineering", semantic: "engineering.state", api: "useState()", mpEquiv: "data", tier: "L1", status: "planned" },
|
|
@@ -1551,6 +1837,10 @@ var MP_API_GROUPS = [
|
|
|
1551
1837
|
{ mp: "wx.onUserTriggerTranslation/onUserOffTranslation\uFF08\u7FFB\u8BD1\uFF09", proteus: "useTranslation", status: "ok", group: "api" },
|
|
1552
1838
|
{ mp: "wx.onGeneratePoster\uFF08\u5206\u4EAB\u6D77\u62A5\uFF09", proteus: "usePoster", status: "ok", group: "api" },
|
|
1553
1839
|
{ mp: "wx.checkDeviceSupportHevc\uFF08\u8BBE\u5907\u80FD\u529B\u63A2\u6D4B\uFF09", proteus: "useDeviceCapability", status: "ok", group: "api" },
|
|
1840
|
+
// ★C82:**超官方清单**能力(官方 301 API 列表里无 wasm)——`WXWebAssembly` 属官方「小程序运行时」
|
|
1841
|
+
// 文档(performance/wasm)确认的运行时能力 ⇒ 矩阵按 mpEquiv 如实登记;
|
|
1842
|
+
// 不进 spec 覆盖度分母(否则分母不变而 covered 虚增,覆盖率失真——见 mp-spec-coverage 注释)
|
|
1843
|
+
{ mp: "WXWebAssembly.instantiate\uFF08WebAssembly \u8DE8\u5E73\u53F0\uFF0C\u5B98\u65B9\u6587\u6863 performance/wasm\uFF09", proteus: "useWebAssembly", status: "ok", group: "api" },
|
|
1554
1844
|
{ mp: "wx.getFileSystemManager/*\uFF08\u6587\u4EF6 30+\uFF09", proteus: "useFileSystem", status: "ok", group: "api" },
|
|
1555
1845
|
{ mp: "wx.compressFile/unzip", proteus: "useArchive", status: "ok", group: "api" },
|
|
1556
1846
|
{ mp: "wx.set/get/remove/clearStorage(+Sync)", proteus: "useStorage", status: "ok", group: "api" },
|
|
@@ -1647,38 +1937,2020 @@ function degradeProp(tag, prop) {
|
|
|
1647
1937
|
if (tag && HOST_TAGS[tag]) {
|
|
1648
1938
|
return { mp: "supported", web: "fallback", behavior: HOST_TAGS[tag] };
|
|
1649
1939
|
}
|
|
1650
|
-
const tagProp = tag ? TAG_PROP_MP_FALLBACK[tag] : void 0;
|
|
1651
|
-
const tagPropBehavior = tagProp ? tagProp[prop] : void 0;
|
|
1652
|
-
if (tagPropBehavior) return { mp: "fallback", web: "supported", behavior: tagPropBehavior };
|
|
1653
|
-
const webExt = WEB_EXTENSION_MP_FALLBACK[prop];
|
|
1654
|
-
if (webExt) return { mp: "fallback", web: "supported", behavior: webExt };
|
|
1655
|
-
return { mp: "supported", web: "supported" };
|
|
1940
|
+
const tagProp = tag ? TAG_PROP_MP_FALLBACK[tag] : void 0;
|
|
1941
|
+
const tagPropBehavior = tagProp ? tagProp[prop] : void 0;
|
|
1942
|
+
if (tagPropBehavior) return { mp: "fallback", web: "supported", behavior: tagPropBehavior };
|
|
1943
|
+
const webExt = WEB_EXTENSION_MP_FALLBACK[prop];
|
|
1944
|
+
if (webExt) return { mp: "fallback", web: "supported", behavior: webExt };
|
|
1945
|
+
return { mp: "supported", web: "supported" };
|
|
1946
|
+
}
|
|
1947
|
+
function degradationOf(prim) {
|
|
1948
|
+
const out = {};
|
|
1949
|
+
for (const p of prim.props ?? []) out[p] = degradeProp(prim.tag, p);
|
|
1950
|
+
return out;
|
|
1951
|
+
}
|
|
1952
|
+
var DEGRADATION_TABLE = (() => {
|
|
1953
|
+
const out = {};
|
|
1954
|
+
for (const prim of PRIMITIVE_CATALOG) {
|
|
1955
|
+
if (!prim.tag) continue;
|
|
1956
|
+
out[prim.tag] = degradationOf(prim);
|
|
1957
|
+
}
|
|
1958
|
+
return out;
|
|
1959
|
+
})();
|
|
1960
|
+
var MP_UNSUPPORTED_PROPS = (() => {
|
|
1961
|
+
const kebab = (s) => s.replace(/([a-z0-9])([A-Z])/g, "$1-$2").toLowerCase();
|
|
1962
|
+
const out = {};
|
|
1963
|
+
for (const [tag, table] of Object.entries(DEGRADATION_TABLE)) {
|
|
1964
|
+
const set = /* @__PURE__ */ new Set();
|
|
1965
|
+
for (const [prop, entry] of Object.entries(table)) {
|
|
1966
|
+
if (entry.mp === "unsupported") set.add(kebab(prop));
|
|
1967
|
+
}
|
|
1968
|
+
if (set.size) out[tag] = set;
|
|
1969
|
+
}
|
|
1970
|
+
return out;
|
|
1971
|
+
})();
|
|
1972
|
+
|
|
1973
|
+
// ../slot-runtime/src/opcode.ts
|
|
1974
|
+
var PropKeyTable = class _PropKeyTable {
|
|
1975
|
+
constructor() {
|
|
1976
|
+
this.keys = [];
|
|
1977
|
+
this.index = /* @__PURE__ */ new Map();
|
|
1978
|
+
}
|
|
1979
|
+
/** 分配(已存在则返回原下标——**幂等**) */
|
|
1980
|
+
intern(key) {
|
|
1981
|
+
const hit = this.index.get(key);
|
|
1982
|
+
if (hit !== void 0) return hit;
|
|
1983
|
+
const id = this.keys.length;
|
|
1984
|
+
this.keys.push(key);
|
|
1985
|
+
this.index.set(key, id);
|
|
1986
|
+
return id;
|
|
1987
|
+
}
|
|
1988
|
+
/** 反查(诊断 / explain 用;运行时不走这条路径) */
|
|
1989
|
+
keyOf(id) {
|
|
1990
|
+
return this.keys[id];
|
|
1991
|
+
}
|
|
1992
|
+
get size() {
|
|
1993
|
+
return this.keys.length;
|
|
1994
|
+
}
|
|
1995
|
+
/** 导出(进 PatchTable 产物) */
|
|
1996
|
+
toArray() {
|
|
1997
|
+
return [...this.keys];
|
|
1998
|
+
}
|
|
1999
|
+
/** 导入(消费方重建) */
|
|
2000
|
+
static fromArray(keys) {
|
|
2001
|
+
const t = new _PropKeyTable();
|
|
2002
|
+
for (const k of keys) t.intern(k);
|
|
2003
|
+
return t;
|
|
2004
|
+
}
|
|
2005
|
+
};
|
|
2006
|
+
var StringPool = class _StringPool {
|
|
2007
|
+
constructor() {
|
|
2008
|
+
this.values = [];
|
|
2009
|
+
this.index = /* @__PURE__ */ new Map();
|
|
2010
|
+
}
|
|
2011
|
+
intern(s) {
|
|
2012
|
+
const hit = this.index.get(s);
|
|
2013
|
+
if (hit !== void 0) return hit;
|
|
2014
|
+
const id = this.values.length;
|
|
2015
|
+
this.values.push(s);
|
|
2016
|
+
this.index.set(s, id);
|
|
2017
|
+
return id;
|
|
2018
|
+
}
|
|
2019
|
+
valueOf(id) {
|
|
2020
|
+
return this.values[id];
|
|
2021
|
+
}
|
|
2022
|
+
get size() {
|
|
2023
|
+
return this.values.length;
|
|
2024
|
+
}
|
|
2025
|
+
toArray() {
|
|
2026
|
+
return [...this.values];
|
|
2027
|
+
}
|
|
2028
|
+
static fromArray(values) {
|
|
2029
|
+
const p = new _StringPool();
|
|
2030
|
+
for (const v of values) p.intern(v);
|
|
2031
|
+
return p;
|
|
2032
|
+
}
|
|
2033
|
+
};
|
|
2034
|
+
|
|
2035
|
+
// ../slot-runtime/src/anim-curve.ts
|
|
2036
|
+
var AnimCurve = {
|
|
2037
|
+
LINEAR: 0,
|
|
2038
|
+
EASE_OUT_CUBIC: 1,
|
|
2039
|
+
EASE_IN_CUBIC: 2,
|
|
2040
|
+
EASE_IN_OUT_CUBIC: 3,
|
|
2041
|
+
SPRING_APPROX: 4
|
|
2042
|
+
};
|
|
2043
|
+
var TABLE_N = 65;
|
|
2044
|
+
function springApprox(u) {
|
|
2045
|
+
return 1 - Math.exp(-6 * u) * Math.cos(10 * u);
|
|
2046
|
+
}
|
|
2047
|
+
function buildTable(curve) {
|
|
2048
|
+
const t = new Float64Array(TABLE_N);
|
|
2049
|
+
for (let i = 0; i < TABLE_N; i++) {
|
|
2050
|
+
const u = i / (TABLE_N - 1);
|
|
2051
|
+
t[i] = curve === AnimCurve.EASE_OUT_CUBIC ? 1 - (1 - u) ** 3 : curve === AnimCurve.EASE_IN_CUBIC ? u ** 3 : curve === AnimCurve.EASE_IN_OUT_CUBIC ? u < 0.5 ? 4 * u ** 3 : 1 - (-2 * u + 2) ** 3 / 2 : curve === AnimCurve.SPRING_APPROX ? springApprox(u) : u;
|
|
2052
|
+
}
|
|
2053
|
+
t[0] = 0;
|
|
2054
|
+
t[TABLE_N - 1] = 1;
|
|
2055
|
+
return t;
|
|
2056
|
+
}
|
|
2057
|
+
var TABLES = [
|
|
2058
|
+
buildTable(AnimCurve.LINEAR),
|
|
2059
|
+
buildTable(AnimCurve.EASE_OUT_CUBIC),
|
|
2060
|
+
buildTable(AnimCurve.EASE_IN_CUBIC),
|
|
2061
|
+
buildTable(AnimCurve.EASE_IN_OUT_CUBIC),
|
|
2062
|
+
buildTable(AnimCurve.SPRING_APPROX)
|
|
2063
|
+
];
|
|
2064
|
+
|
|
2065
|
+
// ../slot-runtime/src/buffer.ts
|
|
2066
|
+
var OPS_MAGIC = 1347376720;
|
|
2067
|
+
var OPS_VERSION = 2;
|
|
2068
|
+
var OPS_HEADER_BYTES = 20;
|
|
2069
|
+
function opSize(op) {
|
|
2070
|
+
switch (op.op) {
|
|
2071
|
+
case 1 /* SET_PROP */:
|
|
2072
|
+
case 2 /* SET_STYLE */:
|
|
2073
|
+
return 11;
|
|
2074
|
+
case 3 /* SET_TEXT */:
|
|
2075
|
+
return 9;
|
|
2076
|
+
case 4 /* SET_ATTRS */:
|
|
2077
|
+
return 7 + 6 * op.attrs.length;
|
|
2078
|
+
case 5 /* TOGGLE_VIS */:
|
|
2079
|
+
return 6;
|
|
2080
|
+
case 16 /* INSERT_BLOCK */:
|
|
2081
|
+
return 10;
|
|
2082
|
+
case 17 /* REMOVE_NODE */:
|
|
2083
|
+
return 5;
|
|
2084
|
+
case 18 /* MOVE_NODE */:
|
|
2085
|
+
return 10;
|
|
2086
|
+
case 32 /* LIST_SET */:
|
|
2087
|
+
return 9;
|
|
2088
|
+
case 33 /* LIST_SPLICE */:
|
|
2089
|
+
return 15 + 4 * op.itemKeyRefs.length;
|
|
2090
|
+
case 34 /* LIST_UPDATE */:
|
|
2091
|
+
return 17;
|
|
2092
|
+
case 48 /* CALL_COMPONENT_UPDATE */:
|
|
2093
|
+
return 13;
|
|
2094
|
+
default: {
|
|
2095
|
+
const never = op;
|
|
2096
|
+
throw new Error(`\u672A\u77E5\u6307\u4EE4\uFF1A${JSON.stringify(never)}`);
|
|
2097
|
+
}
|
|
2098
|
+
}
|
|
2099
|
+
}
|
|
2100
|
+
var ByteWriter = class {
|
|
2101
|
+
constructor(size) {
|
|
2102
|
+
this.pos = 0;
|
|
2103
|
+
this.buf = new Uint8Array(size);
|
|
2104
|
+
}
|
|
2105
|
+
u8(v) {
|
|
2106
|
+
this.buf[this.pos++] = v & 255;
|
|
2107
|
+
}
|
|
2108
|
+
u16(v) {
|
|
2109
|
+
this.buf[this.pos++] = v & 255;
|
|
2110
|
+
this.buf[this.pos++] = v >>> 8 & 255;
|
|
2111
|
+
}
|
|
2112
|
+
u32(v) {
|
|
2113
|
+
this.buf[this.pos++] = v & 255;
|
|
2114
|
+
this.buf[this.pos++] = v >>> 8 & 255;
|
|
2115
|
+
this.buf[this.pos++] = v >>> 16 & 255;
|
|
2116
|
+
this.buf[this.pos++] = v >>> 24 & 255;
|
|
2117
|
+
}
|
|
2118
|
+
f32(v) {
|
|
2119
|
+
const f = new Float32Array(1);
|
|
2120
|
+
f[0] = v;
|
|
2121
|
+
const b = new Uint8Array(f.buffer);
|
|
2122
|
+
this.buf[this.pos++] = b[0];
|
|
2123
|
+
this.buf[this.pos++] = b[1];
|
|
2124
|
+
this.buf[this.pos++] = b[2];
|
|
2125
|
+
this.buf[this.pos++] = b[3];
|
|
2126
|
+
}
|
|
2127
|
+
str(s) {
|
|
2128
|
+
const bytes = utf8Encode(s);
|
|
2129
|
+
if (bytes.length > 65535) throw new Error(`\u5B57\u7B26\u4E32\u8FC7\u957F\uFF08>65535B\uFF09\uFF1A${bytes.length}`);
|
|
2130
|
+
this.u16(bytes.length);
|
|
2131
|
+
this.buf.set(bytes, this.pos);
|
|
2132
|
+
this.pos += bytes.length;
|
|
2133
|
+
}
|
|
2134
|
+
done() {
|
|
2135
|
+
if (this.pos !== this.buf.length) throw new Error(`\u5199\u5165\u957F\u5EA6\u4E0D\u7B26\uFF1A${this.pos} != ${this.buf.length}`);
|
|
2136
|
+
return this.buf;
|
|
2137
|
+
}
|
|
2138
|
+
};
|
|
2139
|
+
function utf8Encode(s) {
|
|
2140
|
+
const out = [];
|
|
2141
|
+
for (let i = 0; i < s.length; i++) {
|
|
2142
|
+
let cp = s.charCodeAt(i);
|
|
2143
|
+
if (cp >= 55296 && cp <= 56319 && i + 1 < s.length) {
|
|
2144
|
+
const lo = s.charCodeAt(i + 1);
|
|
2145
|
+
if (lo >= 56320 && lo <= 57343) {
|
|
2146
|
+
cp = (cp - 55296 << 10) + (lo - 56320) + 65536;
|
|
2147
|
+
i++;
|
|
2148
|
+
}
|
|
2149
|
+
}
|
|
2150
|
+
if (cp < 128) out.push(cp);
|
|
2151
|
+
else if (cp < 2048) out.push(192 | cp >> 6, 128 | cp & 63);
|
|
2152
|
+
else if (cp < 65536) out.push(224 | cp >> 12, 128 | cp >> 6 & 63, 128 | cp & 63);
|
|
2153
|
+
else out.push(240 | cp >> 18, 128 | cp >> 12 & 63, 128 | cp >> 6 & 63, 128 | cp & 63);
|
|
2154
|
+
}
|
|
2155
|
+
return Uint8Array.from(out);
|
|
2156
|
+
}
|
|
2157
|
+
var OpBuffer = class {
|
|
2158
|
+
constructor() {
|
|
2159
|
+
this.ops = [];
|
|
2160
|
+
}
|
|
2161
|
+
push(op) {
|
|
2162
|
+
this.ops.push(op);
|
|
2163
|
+
}
|
|
2164
|
+
get count() {
|
|
2165
|
+
return this.ops.length;
|
|
2166
|
+
}
|
|
2167
|
+
/** 只读快照(调试 / explain / 测试用) */
|
|
2168
|
+
snapshot() {
|
|
2169
|
+
return this.ops.slice();
|
|
2170
|
+
}
|
|
2171
|
+
clear() {
|
|
2172
|
+
this.ops.length = 0;
|
|
2173
|
+
}
|
|
2174
|
+
/**
|
|
2175
|
+
* 编码并提交,随后**清空**。
|
|
2176
|
+
*
|
|
2177
|
+
* @param keys 属性键表(进入 Header,供消费方还原 keyId → 字符串)
|
|
2178
|
+
* @param strings 字符串池(同上)
|
|
2179
|
+
* @param sink 提交出口(App 端 = 一次 JSI 调用)
|
|
2180
|
+
* @returns 本次提交的字节数
|
|
2181
|
+
*/
|
|
2182
|
+
flush(keys, strings, sink) {
|
|
2183
|
+
const bytes = encodeOps(this.ops, keys, strings);
|
|
2184
|
+
const n = this.ops.length;
|
|
2185
|
+
sink(bytes, n);
|
|
2186
|
+
this.ops.length = 0;
|
|
2187
|
+
return bytes.length;
|
|
2188
|
+
}
|
|
2189
|
+
};
|
|
2190
|
+
function collectRefs(ops) {
|
|
2191
|
+
const k = /* @__PURE__ */ new Set();
|
|
2192
|
+
const s = /* @__PURE__ */ new Set();
|
|
2193
|
+
for (const op of ops) {
|
|
2194
|
+
switch (op.op) {
|
|
2195
|
+
case 1 /* SET_PROP */:
|
|
2196
|
+
case 2 /* SET_STYLE */:
|
|
2197
|
+
k.add(op.keyId);
|
|
2198
|
+
break;
|
|
2199
|
+
case 4 /* SET_ATTRS */:
|
|
2200
|
+
for (const a of op.attrs) k.add(a.keyId);
|
|
2201
|
+
break;
|
|
2202
|
+
case 3 /* SET_TEXT */:
|
|
2203
|
+
s.add(op.textRef);
|
|
2204
|
+
break;
|
|
2205
|
+
case 33 /* LIST_SPLICE */:
|
|
2206
|
+
for (const r of op.itemKeyRefs) s.add(r);
|
|
2207
|
+
break;
|
|
2208
|
+
case 34 /* LIST_UPDATE */:
|
|
2209
|
+
s.add(op.itemKeyRef);
|
|
2210
|
+
break;
|
|
2211
|
+
// TOGGLE_VIS / INSERT_BLOCK / REMOVE_NODE / MOVE_NODE / LIST_SET /
|
|
2212
|
+
// CALL_COMPONENT_UPDATE:无池引用(若将来新增,**在此处补一支**)
|
|
2213
|
+
default:
|
|
2214
|
+
break;
|
|
2215
|
+
}
|
|
2216
|
+
}
|
|
2217
|
+
return { keyIds: [...k].sort((a, b) => a - b), strRefs: [...s].sort((a, b) => a - b) };
|
|
2218
|
+
}
|
|
2219
|
+
function remapOf(used) {
|
|
2220
|
+
const m = /* @__PURE__ */ new Map();
|
|
2221
|
+
used.forEach((old, i) => m.set(old, i));
|
|
2222
|
+
return m;
|
|
2223
|
+
}
|
|
2224
|
+
function remapOp(op, kMap, sMap) {
|
|
2225
|
+
switch (op.op) {
|
|
2226
|
+
case 1 /* SET_PROP */:
|
|
2227
|
+
case 2 /* SET_STYLE */:
|
|
2228
|
+
return { ...op, keyId: kMap.get(op.keyId) ?? 0 };
|
|
2229
|
+
case 4 /* SET_ATTRS */:
|
|
2230
|
+
return { ...op, attrs: op.attrs.map((a) => ({ keyId: kMap.get(a.keyId) ?? 0, value: a.value })) };
|
|
2231
|
+
case 3 /* SET_TEXT */:
|
|
2232
|
+
return { ...op, textRef: sMap.get(op.textRef) ?? 0 };
|
|
2233
|
+
case 33 /* LIST_SPLICE */:
|
|
2234
|
+
return { ...op, itemKeyRefs: op.itemKeyRefs.map((r) => sMap.get(r) ?? 0) };
|
|
2235
|
+
case 34 /* LIST_UPDATE */:
|
|
2236
|
+
return { ...op, itemKeyRef: sMap.get(op.itemKeyRef) ?? 0 };
|
|
2237
|
+
default:
|
|
2238
|
+
return op;
|
|
2239
|
+
}
|
|
2240
|
+
}
|
|
2241
|
+
function encodeOps(ops, keys, strings) {
|
|
2242
|
+
const allKeys = keys.toArray();
|
|
2243
|
+
const allStrs = strings.toArray();
|
|
2244
|
+
const used = collectRefs(ops);
|
|
2245
|
+
const keyArr = used.keyIds.map((i) => allKeys[i]).filter((x) => x !== void 0);
|
|
2246
|
+
const strArr = used.strRefs.map((i) => allStrs[i]).filter((x) => x !== void 0);
|
|
2247
|
+
const kMap = remapOf(used.keyIds.filter((i) => allKeys[i] !== void 0));
|
|
2248
|
+
const sMap = remapOf(used.strRefs.filter((i) => allStrs[i] !== void 0));
|
|
2249
|
+
let size = OPS_HEADER_BYTES;
|
|
2250
|
+
for (const k of keyArr) size += 2 + utf8Encode(k).length;
|
|
2251
|
+
for (const s of strArr) size += 2 + utf8Encode(s).length;
|
|
2252
|
+
for (const op of ops) size += opSize(op);
|
|
2253
|
+
const w = new ByteWriter(size);
|
|
2254
|
+
w.u32(OPS_MAGIC);
|
|
2255
|
+
w.u32(OPS_VERSION);
|
|
2256
|
+
w.u32(ops.length);
|
|
2257
|
+
w.u32(keyArr.length);
|
|
2258
|
+
w.u32(strArr.length);
|
|
2259
|
+
for (const k of keyArr) w.str(k);
|
|
2260
|
+
for (const s of strArr) w.str(s);
|
|
2261
|
+
for (const op of ops) encodeOp(w, remapOp(op, kMap, sMap));
|
|
2262
|
+
return w.done();
|
|
2263
|
+
}
|
|
2264
|
+
function encodeOp(w, op) {
|
|
2265
|
+
w.u8(op.op);
|
|
2266
|
+
switch (op.op) {
|
|
2267
|
+
case 1 /* SET_PROP */:
|
|
2268
|
+
case 2 /* SET_STYLE */:
|
|
2269
|
+
w.u32(op.nodeId);
|
|
2270
|
+
w.u16(op.keyId);
|
|
2271
|
+
w.f32(op.value);
|
|
2272
|
+
return;
|
|
2273
|
+
case 3 /* SET_TEXT */:
|
|
2274
|
+
w.u32(op.nodeId);
|
|
2275
|
+
w.u32(op.textRef);
|
|
2276
|
+
return;
|
|
2277
|
+
case 4 /* SET_ATTRS */:
|
|
2278
|
+
w.u32(op.nodeId);
|
|
2279
|
+
w.u16(op.attrs.length);
|
|
2280
|
+
for (const a of op.attrs) {
|
|
2281
|
+
w.u16(a.keyId);
|
|
2282
|
+
w.f32(a.value);
|
|
2283
|
+
}
|
|
2284
|
+
return;
|
|
2285
|
+
case 5 /* TOGGLE_VIS */:
|
|
2286
|
+
w.u32(op.nodeId);
|
|
2287
|
+
w.u8(op.visible ? 1 : 0);
|
|
2288
|
+
return;
|
|
2289
|
+
case 16 /* INSERT_BLOCK */:
|
|
2290
|
+
w.u32(op.blockId);
|
|
2291
|
+
w.u32(op.refNodeId);
|
|
2292
|
+
w.u8(op.pos);
|
|
2293
|
+
return;
|
|
2294
|
+
case 17 /* REMOVE_NODE */:
|
|
2295
|
+
w.u32(op.nodeId);
|
|
2296
|
+
return;
|
|
2297
|
+
case 18 /* MOVE_NODE */:
|
|
2298
|
+
w.u32(op.nodeId);
|
|
2299
|
+
w.u32(op.refNodeId);
|
|
2300
|
+
w.u8(op.pos);
|
|
2301
|
+
return;
|
|
2302
|
+
case 32 /* LIST_SET */:
|
|
2303
|
+
w.u32(op.listId);
|
|
2304
|
+
w.u32(op.dataRef);
|
|
2305
|
+
return;
|
|
2306
|
+
case 33 /* LIST_SPLICE */:
|
|
2307
|
+
w.u32(op.listId);
|
|
2308
|
+
w.u32(op.start);
|
|
2309
|
+
w.u32(op.delCount);
|
|
2310
|
+
w.u16(op.itemKeyRefs.length);
|
|
2311
|
+
for (const r of op.itemKeyRefs) w.u32(r);
|
|
2312
|
+
return;
|
|
2313
|
+
case 34 /* LIST_UPDATE */:
|
|
2314
|
+
w.u32(op.listId);
|
|
2315
|
+
w.u32(op.itemKeyRef);
|
|
2316
|
+
w.u32(op.slotId);
|
|
2317
|
+
w.f32(op.value);
|
|
2318
|
+
return;
|
|
2319
|
+
case 48 /* CALL_COMPONENT_UPDATE */:
|
|
2320
|
+
w.u32(op.componentId);
|
|
2321
|
+
w.u32(op.slotId);
|
|
2322
|
+
w.f32(op.value);
|
|
2323
|
+
return;
|
|
2324
|
+
default: {
|
|
2325
|
+
const never = op;
|
|
2326
|
+
throw new Error(`\u672A\u77E5\u6307\u4EE4\uFF1A${JSON.stringify(never)}`);
|
|
2327
|
+
}
|
|
2328
|
+
}
|
|
2329
|
+
}
|
|
2330
|
+
|
|
2331
|
+
// ../slot-runtime/src/slot.ts
|
|
2332
|
+
function createSlot(spec, keys, strings, initial, registry) {
|
|
2333
|
+
const nodeId = spec.nodeId;
|
|
2334
|
+
const keyId = spec.keyId ?? 0;
|
|
2335
|
+
const listId = spec.listId ?? 0;
|
|
2336
|
+
const listSlotId = spec.listSlotId ?? spec.id;
|
|
2337
|
+
const slot = {
|
|
2338
|
+
id: spec.id,
|
|
2339
|
+
nodeId,
|
|
2340
|
+
kind: spec.kind,
|
|
2341
|
+
value: initial,
|
|
2342
|
+
dirty: false,
|
|
2343
|
+
emit(next, buf) {
|
|
2344
|
+
switch (spec.kind) {
|
|
2345
|
+
case "text":
|
|
2346
|
+
buf.push({ op: 3 /* SET_TEXT */, nodeId, textRef: strings.intern(String(next)) });
|
|
2347
|
+
return;
|
|
2348
|
+
case "prop":
|
|
2349
|
+
buf.push({ op: 1 /* SET_PROP */, nodeId, keyId, value: toF32(next) });
|
|
2350
|
+
return;
|
|
2351
|
+
case "style":
|
|
2352
|
+
buf.push({ op: 2 /* SET_STYLE */, nodeId, keyId, value: toF32(next) });
|
|
2353
|
+
return;
|
|
2354
|
+
case "visibility":
|
|
2355
|
+
buf.push({ op: 5 /* TOGGLE_VIS */, nodeId, visible: Boolean(next) });
|
|
2356
|
+
return;
|
|
2357
|
+
case "list-item": {
|
|
2358
|
+
const item = next;
|
|
2359
|
+
const nodeId2 = registry?.resolveNode(listId, item.key, listSlotId);
|
|
2360
|
+
if (nodeId2 !== void 0) {
|
|
2361
|
+
if ((spec.itemKind ?? "style") === "text") {
|
|
2362
|
+
buf.push({ op: 3 /* SET_TEXT */, nodeId: nodeId2, textRef: strings.intern(String(item.value)) });
|
|
2363
|
+
} else {
|
|
2364
|
+
buf.push({ op: 2 /* SET_STYLE */, nodeId: nodeId2, keyId, value: toF32(item.value) });
|
|
2365
|
+
}
|
|
2366
|
+
return;
|
|
2367
|
+
}
|
|
2368
|
+
buf.push({
|
|
2369
|
+
op: 34 /* LIST_UPDATE */,
|
|
2370
|
+
listId,
|
|
2371
|
+
itemKeyRef: strings.intern(item.key),
|
|
2372
|
+
slotId: listSlotId,
|
|
2373
|
+
value: toF32(item.value)
|
|
2374
|
+
});
|
|
2375
|
+
return;
|
|
2376
|
+
}
|
|
2377
|
+
case "list-data":
|
|
2378
|
+
buf.push({ op: 32 /* LIST_SET */, listId, dataRef: toF32(next) });
|
|
2379
|
+
return;
|
|
2380
|
+
case "attrs": {
|
|
2381
|
+
const attrs = next;
|
|
2382
|
+
buf.push({ op: 4 /* SET_ATTRS */, nodeId, attrs });
|
|
2383
|
+
return;
|
|
2384
|
+
}
|
|
2385
|
+
case "component-prop":
|
|
2386
|
+
buf.push({ op: 48 /* CALL_COMPONENT_UPDATE */, componentId: nodeId, slotId: listSlotId, value: toF32(next) });
|
|
2387
|
+
return;
|
|
2388
|
+
default: {
|
|
2389
|
+
const never = spec.kind;
|
|
2390
|
+
throw new Error(`\u672A\u77E5\u69FD\u4F4D\u79CD\u7C7B\uFF1A${String(never)}`);
|
|
2391
|
+
}
|
|
2392
|
+
}
|
|
2393
|
+
}
|
|
2394
|
+
};
|
|
2395
|
+
return slot;
|
|
2396
|
+
}
|
|
2397
|
+
function toF32(v) {
|
|
2398
|
+
if (typeof v === "number") return v;
|
|
2399
|
+
if (typeof v === "boolean") return v ? 1 : 0;
|
|
2400
|
+
throw new Error(`\u69FD\u4F4D\u503C\u5FC5\u987B\u662F number/boolean\uFF0C\u6536\u5230 ${typeof v}\uFF08\u6587\u672C\u8BF7\u7528 kind='text'\uFF09`);
|
|
2401
|
+
}
|
|
2402
|
+
var microtaskScheduler = {
|
|
2403
|
+
schedule(cb) {
|
|
2404
|
+
void Promise.resolve().then(cb);
|
|
2405
|
+
}
|
|
2406
|
+
};
|
|
2407
|
+
var SlotRuntime = class {
|
|
2408
|
+
constructor(keys, strings, sink, scheduler = microtaskScheduler) {
|
|
2409
|
+
this.keys = keys;
|
|
2410
|
+
this.strings = strings;
|
|
2411
|
+
this.sink = sink;
|
|
2412
|
+
this.scheduler = scheduler;
|
|
2413
|
+
this.buffer = new OpBuffer();
|
|
2414
|
+
this.dirty = [];
|
|
2415
|
+
this.pending = false;
|
|
2416
|
+
this.stats = { flushes: 0, opsEmitted: 0, bytesSent: 0, shortCircuits: 0 };
|
|
2417
|
+
/**
|
|
2418
|
+
* ★★**帧号**(P2-5 新增):每次 `flush()` 调用(**包括无脏槽位的空调用**)自增。
|
|
2419
|
+
*
|
|
2420
|
+
* 【为什么需要(v-memo 的组语义要求帧边界)】memo 组在"同一帧内"只判定一次脏:
|
|
2421
|
+
* 若一帧里有多个源变化(多次 `writeSlotsOfSource`)触到同一组的多个槽位,
|
|
2422
|
+
* 第一次判定"依赖变了"之后,同帧其余槽位必须**照常写**(组语义 = 子树整体更新)。
|
|
2423
|
+
* 而判定一次的判据只有帧边界——`flush()` 是这套系统的天然帧边界。
|
|
2424
|
+
* ★与 `stats.flushes` 的区别:后者只在**真的提交了字节**时自增(空 flush 不计);
|
|
2425
|
+
* 本计数每次 flush 调用都自增(帧边界语义)。
|
|
2426
|
+
*/
|
|
2427
|
+
this.frame = 0;
|
|
2428
|
+
}
|
|
2429
|
+
/** 帧号(v-memo 门用;只读) */
|
|
2430
|
+
get frameId() {
|
|
2431
|
+
return this.frame;
|
|
2432
|
+
}
|
|
2433
|
+
/** 槽位写入(方案 §3.3):相等即短路;否则标脏 + 排帧 */
|
|
2434
|
+
setSlot(slot, next) {
|
|
2435
|
+
if (Object.is(slot.value, next)) {
|
|
2436
|
+
this.stats.shortCircuits++;
|
|
2437
|
+
return;
|
|
2438
|
+
}
|
|
2439
|
+
slot.value = next;
|
|
2440
|
+
if (!slot.dirty) {
|
|
2441
|
+
slot.dirty = true;
|
|
2442
|
+
this.dirty.push(slot);
|
|
2443
|
+
}
|
|
2444
|
+
this.scheduleFlush();
|
|
2445
|
+
}
|
|
2446
|
+
/** 排帧(幂等——同帧多次调用只排一次) */
|
|
2447
|
+
scheduleFlush() {
|
|
2448
|
+
if (this.pending) return;
|
|
2449
|
+
this.pending = true;
|
|
2450
|
+
this.scheduler.schedule(() => this.flush());
|
|
2451
|
+
}
|
|
2452
|
+
/**
|
|
2453
|
+
* 立即 flush(测试 / 确定性驱动用)
|
|
2454
|
+
*
|
|
2455
|
+
* 【为什么单独暴露】真机上「Vsync 何时来」不可控;测量装置纪律要求用例能**确定性地**
|
|
2456
|
+
* 驱动一次提交(本仓四次踩过"测量装置污染读数")。
|
|
2457
|
+
*/
|
|
2458
|
+
flush() {
|
|
2459
|
+
this.frame++;
|
|
2460
|
+
this.pending = false;
|
|
2461
|
+
for (const slot of this.dirty) {
|
|
2462
|
+
slot.emit(slot.value, this.buffer);
|
|
2463
|
+
slot.dirty = false;
|
|
2464
|
+
}
|
|
2465
|
+
this.dirty.length = 0;
|
|
2466
|
+
if (this.buffer.count === 0) return;
|
|
2467
|
+
this.stats.opsEmitted += this.buffer.count;
|
|
2468
|
+
this.stats.bytesSent += this.buffer.flush(this.keys, this.strings, this.sink);
|
|
2469
|
+
this.stats.flushes++;
|
|
2470
|
+
}
|
|
2471
|
+
getStats() {
|
|
2472
|
+
return { ...this.stats };
|
|
2473
|
+
}
|
|
2474
|
+
/** 待发射的脏槽位数(诊断) */
|
|
2475
|
+
get dirtyCount() {
|
|
2476
|
+
return this.dirty.length;
|
|
2477
|
+
}
|
|
2478
|
+
};
|
|
2479
|
+
|
|
2480
|
+
// ../slot-runtime/src/table.ts
|
|
2481
|
+
function resolveDynamicClasses(classValue, rules) {
|
|
2482
|
+
const active = collectActiveClasses(classValue);
|
|
2483
|
+
if (active.size === 0 || rules.length === 0) return {};
|
|
2484
|
+
const normal = {};
|
|
2485
|
+
const important = {};
|
|
2486
|
+
for (const r of rules) {
|
|
2487
|
+
if (r.classes.length === 0) continue;
|
|
2488
|
+
if (!r.classes.every((c) => active.has(c))) continue;
|
|
2489
|
+
for (const [k, v] of Object.entries(r.decls)) {
|
|
2490
|
+
if (r.important.includes(k)) important[k] = v;
|
|
2491
|
+
else normal[k] = v;
|
|
2492
|
+
}
|
|
2493
|
+
}
|
|
2494
|
+
return { ...normal, ...important };
|
|
2495
|
+
}
|
|
2496
|
+
function collectActiveClasses(v) {
|
|
2497
|
+
const out = /* @__PURE__ */ new Set();
|
|
2498
|
+
const walk = (x) => {
|
|
2499
|
+
if (!x) return;
|
|
2500
|
+
if (typeof x === "string") {
|
|
2501
|
+
for (const c of x.split(/\s+/)) if (c) out.add(c);
|
|
2502
|
+
return;
|
|
2503
|
+
}
|
|
2504
|
+
if (Array.isArray(x)) {
|
|
2505
|
+
for (const e of x) walk(e);
|
|
2506
|
+
return;
|
|
2507
|
+
}
|
|
2508
|
+
if (typeof x === "object") {
|
|
2509
|
+
for (const [k, vv] of Object.entries(x)) if (vv) out.add(k);
|
|
2510
|
+
return;
|
|
2511
|
+
}
|
|
2512
|
+
};
|
|
2513
|
+
walk(v);
|
|
2514
|
+
return out;
|
|
2515
|
+
}
|
|
2516
|
+
|
|
2517
|
+
// ../slot-runtime/src/dynamic-class.ts
|
|
2518
|
+
function bitmapOfDynamicClasses(active, classes) {
|
|
2519
|
+
let b = 0;
|
|
2520
|
+
for (let i = 0; i < classes.length; i++) if (active.has(classes[i])) b |= 1 << i;
|
|
2521
|
+
return b >>> 0;
|
|
2522
|
+
}
|
|
2523
|
+
function groupStateOf(bitmap, groupBits) {
|
|
2524
|
+
for (let i = 0; i < groupBits.length; i++) if ((bitmap & 1 << groupBits[i]) !== 0) return i + 1;
|
|
2525
|
+
return 0;
|
|
2526
|
+
}
|
|
2527
|
+
function applyDynamicClassPlan(classValue, plan, out) {
|
|
2528
|
+
const tables = plan.tables;
|
|
2529
|
+
const fields = Object.keys(tables);
|
|
2530
|
+
if (fields.length === 0) return 0;
|
|
2531
|
+
const active = collectActiveClasses(classValue);
|
|
2532
|
+
const bitmap = bitmapOfDynamicClasses(active, plan.classes);
|
|
2533
|
+
const groups = plan.mutexGroups;
|
|
2534
|
+
let groupStates = null;
|
|
2535
|
+
if (groups && groups.length > 0) {
|
|
2536
|
+
groupStates = groups.map((bits) => groupStateOf(bitmap, bits));
|
|
2537
|
+
}
|
|
2538
|
+
let written = 0;
|
|
2539
|
+
for (const field of fields) {
|
|
2540
|
+
const t = tables[field];
|
|
2541
|
+
let v;
|
|
2542
|
+
if (t.kind === "bits") {
|
|
2543
|
+
const masked = (bitmap & (t.mask ?? 0)) >>> 0;
|
|
2544
|
+
v = t.values[t.map[masked]];
|
|
2545
|
+
} else {
|
|
2546
|
+
v = t.values[groupStates ? groupStates[t.group ?? 0] : 0];
|
|
2547
|
+
}
|
|
2548
|
+
out[field] = v;
|
|
2549
|
+
written++;
|
|
2550
|
+
}
|
|
2551
|
+
return written;
|
|
2552
|
+
}
|
|
2553
|
+
|
|
2554
|
+
// ../slot-runtime/src/expr.ts
|
|
2555
|
+
var PURE_CALLS = {
|
|
2556
|
+
// —— Math(纯计算)——
|
|
2557
|
+
"Math.abs": Math.abs,
|
|
2558
|
+
"Math.ceil": Math.ceil,
|
|
2559
|
+
"Math.floor": Math.floor,
|
|
2560
|
+
"Math.round": Math.round,
|
|
2561
|
+
"Math.trunc": Math.trunc,
|
|
2562
|
+
"Math.sign": Math.sign,
|
|
2563
|
+
"Math.sqrt": Math.sqrt,
|
|
2564
|
+
"Math.cbrt": Math.cbrt,
|
|
2565
|
+
"Math.pow": Math.pow,
|
|
2566
|
+
"Math.exp": Math.exp,
|
|
2567
|
+
"Math.log": Math.log,
|
|
2568
|
+
"Math.log2": Math.log2,
|
|
2569
|
+
"Math.log10": Math.log10,
|
|
2570
|
+
"Math.min": Math.min,
|
|
2571
|
+
"Math.max": Math.max,
|
|
2572
|
+
// —— 类型转换(call 形态,非 new)——
|
|
2573
|
+
"String": String,
|
|
2574
|
+
"Number": Number,
|
|
2575
|
+
"Boolean": Boolean,
|
|
2576
|
+
// —— 解析 / 判定(全局)——
|
|
2577
|
+
"parseInt": parseInt,
|
|
2578
|
+
"parseFloat": parseFloat,
|
|
2579
|
+
"isNaN": isNaN,
|
|
2580
|
+
"isFinite": isFinite,
|
|
2581
|
+
"Number.isFinite": Number.isFinite,
|
|
2582
|
+
"Number.isInteger": Number.isInteger,
|
|
2583
|
+
"Number.isNaN": Number.isNaN,
|
|
2584
|
+
// —— 数组判定 ——
|
|
2585
|
+
"Array.isArray": Array.isArray
|
|
2586
|
+
};
|
|
2587
|
+
var PURE_METHODS = {
|
|
2588
|
+
// —— Array(非变异)——
|
|
2589
|
+
join: (recv, sep) => Array.isArray(recv) ? recv.join(sep === void 0 ? "," : String(sep)) : void 0,
|
|
2590
|
+
slice: (recv, a, b) => typeof recv === "string" || Array.isArray(recv) ? recv.slice(a, b) : void 0,
|
|
2591
|
+
concat: (recv, ...rest) => Array.isArray(recv) ? recv.concat(...rest) : void 0,
|
|
2592
|
+
indexOf: (recv, x) => typeof recv === "string" || Array.isArray(recv) ? recv.indexOf(x) : void 0,
|
|
2593
|
+
includes: (recv, x) => typeof recv === "string" || Array.isArray(recv) ? recv.includes(x) : void 0,
|
|
2594
|
+
// —— String(非变异)——
|
|
2595
|
+
toUpperCase: (recv) => typeof recv === "string" ? recv.toUpperCase() : void 0,
|
|
2596
|
+
toLowerCase: (recv) => typeof recv === "string" ? recv.toLowerCase() : void 0,
|
|
2597
|
+
trim: (recv) => typeof recv === "string" ? recv.trim() : void 0,
|
|
2598
|
+
trimStart: (recv) => typeof recv === "string" ? recv.trimStart() : void 0,
|
|
2599
|
+
trimEnd: (recv) => typeof recv === "string" ? recv.trimEnd() : void 0,
|
|
2600
|
+
charAt: (recv, i) => typeof recv === "string" ? recv.charAt(i) : void 0,
|
|
2601
|
+
padStart: (recv, n, pad) => typeof recv === "string" ? recv.padStart(n, pad) : void 0,
|
|
2602
|
+
padEnd: (recv, n, pad) => typeof recv === "string" ? recv.padEnd(n, pad) : void 0,
|
|
2603
|
+
repeat: (recv, n) => typeof recv === "string" ? recv.repeat(n) : void 0,
|
|
2604
|
+
substring: (recv, a, b) => typeof recv === "string" ? recv.substring(a, b) : void 0,
|
|
2605
|
+
startsWith: (recv, x) => typeof recv === "string" ? recv.startsWith(x) : void 0,
|
|
2606
|
+
endsWith: (recv, x) => typeof recv === "string" ? recv.endsWith(x) : void 0,
|
|
2607
|
+
// —— Number(非变异)——
|
|
2608
|
+
toFixed: (recv, n) => typeof recv === "number" ? recv.toFixed(n) : void 0,
|
|
2609
|
+
toString: (recv) => recv === void 0 || recv === null ? void 0 : String(recv)
|
|
2610
|
+
};
|
|
2611
|
+
var GLOBAL_CONST_MEMBERS = {
|
|
2612
|
+
"Math.PI": Math.PI,
|
|
2613
|
+
"Math.E": Math.E,
|
|
2614
|
+
"Math.LN2": Math.LN2,
|
|
2615
|
+
"Math.LN10": Math.LN10,
|
|
2616
|
+
"Math.LOG2E": Math.LOG2E,
|
|
2617
|
+
"Math.LOG10E": Math.LOG10E,
|
|
2618
|
+
"Math.SQRT2": Math.SQRT2,
|
|
2619
|
+
"Math.SQRT1_2": Math.SQRT1_2,
|
|
2620
|
+
"Number.MAX_SAFE_INTEGER": Number.MAX_SAFE_INTEGER,
|
|
2621
|
+
"Number.MIN_SAFE_INTEGER": Number.MIN_SAFE_INTEGER,
|
|
2622
|
+
"Number.EPSILON": Number.EPSILON,
|
|
2623
|
+
"Number.MAX_VALUE": Number.MAX_VALUE,
|
|
2624
|
+
"Number.MIN_VALUE": Number.MIN_VALUE
|
|
2625
|
+
};
|
|
2626
|
+
function toNum(v) {
|
|
2627
|
+
if (typeof v === "number") return v;
|
|
2628
|
+
if (typeof v === "boolean") return v ? 1 : 0;
|
|
2629
|
+
if (typeof v === "string") return v.trim() === "" ? 0 : Number(v);
|
|
2630
|
+
if (v === null) return 0;
|
|
2631
|
+
if (v === void 0) return NaN;
|
|
2632
|
+
return NaN;
|
|
2633
|
+
}
|
|
2634
|
+
function evalExpr(p, ctx) {
|
|
2635
|
+
switch (p.k) {
|
|
2636
|
+
case "root":
|
|
2637
|
+
return ctx.read(p.name);
|
|
2638
|
+
case "lit":
|
|
2639
|
+
return p.v;
|
|
2640
|
+
case "undef":
|
|
2641
|
+
return void 0;
|
|
2642
|
+
case "mem": {
|
|
2643
|
+
const o = evalExpr(p.obj, ctx);
|
|
2644
|
+
if (o == null) return void 0;
|
|
2645
|
+
return o[p.key];
|
|
2646
|
+
}
|
|
2647
|
+
case "memdyn": {
|
|
2648
|
+
const o = evalExpr(p.obj, ctx);
|
|
2649
|
+
if (o == null) return void 0;
|
|
2650
|
+
const k = evalExpr(p.key, ctx);
|
|
2651
|
+
return o[String(k)];
|
|
2652
|
+
}
|
|
2653
|
+
case "un": {
|
|
2654
|
+
const v = evalExpr(p.arg, ctx);
|
|
2655
|
+
const uop = p.op;
|
|
2656
|
+
switch (uop) {
|
|
2657
|
+
case "!":
|
|
2658
|
+
return !v;
|
|
2659
|
+
case "-":
|
|
2660
|
+
return -toNum(v);
|
|
2661
|
+
case "+":
|
|
2662
|
+
return +toNum(v);
|
|
2663
|
+
default:
|
|
2664
|
+
throw new Error(`\u672A\u77E5\u4E00\u5143\u8FD0\u7B97\u7B26\uFF1A${String(uop)}`);
|
|
2665
|
+
}
|
|
2666
|
+
}
|
|
2667
|
+
case "bin": {
|
|
2668
|
+
const l = evalExpr(p.l, ctx);
|
|
2669
|
+
const r = evalExpr(p.r, ctx);
|
|
2670
|
+
const bop = p.op;
|
|
2671
|
+
switch (bop) {
|
|
2672
|
+
case "+":
|
|
2673
|
+
if (typeof l === "string" || typeof r === "string") return String(l) + String(r);
|
|
2674
|
+
return toNum(l) + toNum(r);
|
|
2675
|
+
case "-":
|
|
2676
|
+
return toNum(l) - toNum(r);
|
|
2677
|
+
case "*":
|
|
2678
|
+
return toNum(l) * toNum(r);
|
|
2679
|
+
case "/":
|
|
2680
|
+
return toNum(l) / toNum(r);
|
|
2681
|
+
case "%":
|
|
2682
|
+
return toNum(l) % toNum(r);
|
|
2683
|
+
case "===":
|
|
2684
|
+
return l === r;
|
|
2685
|
+
case "!==":
|
|
2686
|
+
return l !== r;
|
|
2687
|
+
case "<":
|
|
2688
|
+
return l < r;
|
|
2689
|
+
case ">":
|
|
2690
|
+
return l > r;
|
|
2691
|
+
case "<=":
|
|
2692
|
+
return l <= r;
|
|
2693
|
+
case ">=":
|
|
2694
|
+
return l >= r;
|
|
2695
|
+
default:
|
|
2696
|
+
throw new Error(`\u672A\u77E5\u4E8C\u5143\u8FD0\u7B97\u7B26\uFF1A${String(bop)}`);
|
|
2697
|
+
}
|
|
2698
|
+
}
|
|
2699
|
+
case "logi": {
|
|
2700
|
+
const l = evalExpr(p.l, ctx);
|
|
2701
|
+
if (p.op === "&&") return l ? evalExpr(p.r, ctx) : l;
|
|
2702
|
+
if (p.op === "||") return l ? l : evalExpr(p.r, ctx);
|
|
2703
|
+
return l === null || l === void 0 ? evalExpr(p.r, ctx) : l;
|
|
2704
|
+
}
|
|
2705
|
+
case "cond":
|
|
2706
|
+
return evalExpr(p.t, ctx) ? evalExpr(p.c, ctx) : evalExpr(p.a, ctx);
|
|
2707
|
+
case "obj": {
|
|
2708
|
+
const o = {};
|
|
2709
|
+
for (const pr of p.props) o[pr.key] = evalExpr(pr.value, ctx);
|
|
2710
|
+
return o;
|
|
2711
|
+
}
|
|
2712
|
+
case "arr":
|
|
2713
|
+
return p.items.map((x) => evalExpr(x, ctx));
|
|
2714
|
+
case "mcall": {
|
|
2715
|
+
const recv = evalExpr(p.recv, ctx);
|
|
2716
|
+
if (recv === void 0 || recv === null) return void 0;
|
|
2717
|
+
const impl = PURE_METHODS[p.method];
|
|
2718
|
+
if (!impl) {
|
|
2719
|
+
throw new Error(`\u8868\u8FBE\u5F0F\u7A0B\u5E8F\u5F15\u7528\u4E86\u975E\u767D\u540D\u5355\u65B9\u6CD5\uFF1A${p.method}\uFF08\u89C1 slot-runtime/expr.ts \u7684 PURE_METHODS\uFF09`);
|
|
2720
|
+
}
|
|
2721
|
+
const args = p.args.map((a) => evalExpr(a, ctx));
|
|
2722
|
+
return impl(recv, ...args);
|
|
2723
|
+
}
|
|
2724
|
+
case "call": {
|
|
2725
|
+
const fn = PURE_CALLS[p.fn];
|
|
2726
|
+
if (typeof fn !== "function") {
|
|
2727
|
+
throw new Error(`\u8868\u8FBE\u5F0F\u7A0B\u5E8F\u5F15\u7528\u4E86\u975E\u767D\u540D\u5355\u51FD\u6570\uFF1A${p.fn}\uFF08\u89C1 slot-runtime/expr.ts \u7684 PURE_CALLS\uFF09`);
|
|
2728
|
+
}
|
|
2729
|
+
const args = p.args.map((a) => evalExpr(a, ctx));
|
|
2730
|
+
return fn(...args);
|
|
2731
|
+
}
|
|
2732
|
+
default: {
|
|
2733
|
+
const never = p;
|
|
2734
|
+
throw new Error(`\u672A\u77E5\u8868\u8FBE\u5F0F\u8282\u70B9\uFF1A${JSON.stringify(never)}`);
|
|
2735
|
+
}
|
|
2736
|
+
}
|
|
2737
|
+
}
|
|
2738
|
+
|
|
2739
|
+
// ../slot-runtime/src/list-registry.ts
|
|
2740
|
+
var ListRegistry = class {
|
|
2741
|
+
constructor() {
|
|
2742
|
+
/** listId → itemKey → (itemSlotId → nodeId) */
|
|
2743
|
+
this.items = /* @__PURE__ */ new Map();
|
|
2744
|
+
/** 统计(诊断:解释"这次 item 更新为什么没走快路径") */
|
|
2745
|
+
this.hits = 0;
|
|
2746
|
+
this.misses = 0;
|
|
2747
|
+
}
|
|
2748
|
+
/**
|
|
2749
|
+
* 登记一个列表项
|
|
2750
|
+
*
|
|
2751
|
+
* @param listId 编译期分配的列表 id
|
|
2752
|
+
* @param itemKey 该行的稳定 key(**绝不用下标**——下标在 splice 后会命中错项,方案坑位 #5)
|
|
2753
|
+
* @param slotNodes item 内槽位 id → 该槽位对应的**节点 id**
|
|
2754
|
+
*/
|
|
2755
|
+
registerItem(listId, itemKey, slotNodes) {
|
|
2756
|
+
let byKey = this.items.get(listId);
|
|
2757
|
+
if (!byKey) {
|
|
2758
|
+
byKey = /* @__PURE__ */ new Map();
|
|
2759
|
+
this.items.set(listId, byKey);
|
|
2760
|
+
}
|
|
2761
|
+
const m = slotNodes instanceof Map ? slotNodes : new Map(Object.entries(slotNodes).map(([k, v]) => [Number(k), v]));
|
|
2762
|
+
byKey.set(itemKey, m);
|
|
2763
|
+
}
|
|
2764
|
+
/** 批量登记(列表首帧渲染后一次性登记全部项——避免逐项调用) */
|
|
2765
|
+
registerItems(listId, entries) {
|
|
2766
|
+
for (const e of entries) this.registerItem(listId, e.itemKey, e.slotNodes);
|
|
2767
|
+
}
|
|
2768
|
+
/** 解析:该列表项内某槽位对应的节点 id(未登记 ⇒ undefined,调用方回退 LIST_UPDATE) */
|
|
2769
|
+
resolveNode(listId, itemKey, slotId) {
|
|
2770
|
+
const hit = this.items.get(listId)?.get(itemKey)?.get(slotId);
|
|
2771
|
+
if (hit === void 0) this.misses++;
|
|
2772
|
+
else this.hits++;
|
|
2773
|
+
return hit;
|
|
2774
|
+
}
|
|
2775
|
+
/** 移除项(splice 删除时调用,防止 key 被复用时命中已删项) */
|
|
2776
|
+
removeItem(listId, itemKey) {
|
|
2777
|
+
return this.items.get(listId)?.delete(itemKey) ?? false;
|
|
2778
|
+
}
|
|
2779
|
+
/** 整体清空某个列表(LIST_SET 整体换数据源时) */
|
|
2780
|
+
clearList(listId) {
|
|
2781
|
+
this.items.delete(listId);
|
|
2782
|
+
}
|
|
2783
|
+
/** 清空全部(卸载页面时) */
|
|
2784
|
+
clear() {
|
|
2785
|
+
this.items.clear();
|
|
2786
|
+
}
|
|
2787
|
+
get size() {
|
|
2788
|
+
let n = 0;
|
|
2789
|
+
for (const byKey of this.items.values()) n += byKey.size;
|
|
2790
|
+
return n;
|
|
2791
|
+
}
|
|
2792
|
+
get stats() {
|
|
2793
|
+
return { hits: this.hits, misses: this.misses, items: this.size };
|
|
2794
|
+
}
|
|
2795
|
+
};
|
|
2796
|
+
|
|
2797
|
+
// ../slot-runtime/src/runtime.ts
|
|
2798
|
+
var VaporRuntime = class {
|
|
2799
|
+
constructor(table, rt, evaluators, registry, onComponentProp, nodeIdOffset = 0, skipNodeIds, onPaintProp) {
|
|
2800
|
+
this.table = table;
|
|
2801
|
+
this.rt = rt;
|
|
2802
|
+
this.evaluators = evaluators;
|
|
2803
|
+
this.registry = registry;
|
|
2804
|
+
this.onComponentProp = onComponentProp;
|
|
2805
|
+
this.nodeIdOffset = nodeIdOffset;
|
|
2806
|
+
this.skipNodeIds = skipNodeIds;
|
|
2807
|
+
this.onPaintProp = onPaintProp;
|
|
2808
|
+
this.slots = /* @__PURE__ */ new Map();
|
|
2809
|
+
this.slotById = /* @__PURE__ */ new Map();
|
|
2810
|
+
this.evalImpls = /* @__PURE__ */ new Map();
|
|
2811
|
+
this.sourcesOfSlot = /* @__PURE__ */ new Map();
|
|
2812
|
+
/**
|
|
2813
|
+
* ★★未能实例化求值器的槽位(**诊断,不许静默**)
|
|
2814
|
+
*
|
|
2815
|
+
* 【为什么必须有(本仓实测)】表达式引用外层别名或含运算时编译器给 `expr` 形态,
|
|
2816
|
+
* 而 `expr` 只支持纯路径 ⇒ `impl` 为 undefined。首版**直接 continue** ⇒
|
|
2817
|
+
* 该槽位永不写、且无任何提示(静默不更新的典型)。
|
|
2818
|
+
*/
|
|
2819
|
+
this.uninstantiatedSlots = [];
|
|
2820
|
+
/** ★行内槽位的按键值缓存(`slotId:key` → 上次值)——只发变化行 */
|
|
2821
|
+
/**
|
|
2822
|
+
* ★★行内值缓存(**免字符串拼接**,2026-09-29 优化)
|
|
2823
|
+
*
|
|
2824
|
+
* 【为什么改(本仓实测的瓶颈)】原实现每 (槽位 × 行) 都做一次
|
|
2825
|
+
* `` `${spec.slotId}:${key}` `` 字符串拼接 + Map 查存;1000 行 × 3 槽位 = 3000 次
|
|
2826
|
+
* ⇒ 在 `V11` 测得单行更新 **JS 侧 6.76ms**(扫描成本主导,而增量只有 1 条指令)。
|
|
2827
|
+
* ⇒ 改**嵌套 Map**(listId → slotId → key → value):键是数字/字符串原值,无拼接、无临时字符串。
|
|
2828
|
+
* ★正确性不变(同一 (listId, slotId, key) 三元组仍是唯一键)。
|
|
2829
|
+
*/
|
|
2830
|
+
this.itemValueCache = /* @__PURE__ */ new Map();
|
|
2831
|
+
this.loaded = false;
|
|
2832
|
+
/* ── ★★P2-5(2026-10-03):v-once / v-memo 的运行时状态 ── */
|
|
2833
|
+
/**
|
|
2834
|
+
* v-once:已写过的槽位 id(**只写一次**——首次写入后永久冻结)。
|
|
2835
|
+
* ★为什么记在运行时(而不是把槽位从表里删掉):表是编译产物(只读、可序列化);
|
|
2836
|
+
* 而"写过没有"是**实例态**(页面重挂载 ⇒ 重新写一次,与官方"重挂载重新渲染"一致)。
|
|
2837
|
+
*/
|
|
2838
|
+
this.onceWritten = /* @__PURE__ */ new Set();
|
|
2839
|
+
/** ★批次 30:每节点上次由动态 `:class` 施加的引擎字段(关掉类 ⇒ 需清除这些字段) */
|
|
2840
|
+
this.lastClassFields = /* @__PURE__ */ new Map();
|
|
2841
|
+
/** v-memo:各组的**依赖基线**(上一次比较时的值;缺省 = 还没建过基线 ⇒ 首帧必脏) */
|
|
2842
|
+
this.memoBaseline = /* @__PURE__ */ new Map();
|
|
2843
|
+
/**
|
|
2844
|
+
* v-memo:组在本**帧**已被判定为"依赖变了"的标记(值 = 帧号)。
|
|
2845
|
+
*
|
|
2846
|
+
* 【为什么按帧记(组语义的关键)】一帧里多个源变化可能触到同组多个槽位——
|
|
2847
|
+
* 第一次判定"变了"之后,**同帧其余槽位必须照常写**(组 = 子树整体更新);
|
|
2848
|
+
* 而"一次判定管一帧"的边界只有 `flush()`(见 `SlotRuntime.frameId`)。
|
|
2849
|
+
*/
|
|
2850
|
+
this.memoDirtyFrame = /* @__PURE__ */ new Map();
|
|
2851
|
+
/* ── ★★★P3-3(2026-10-03):可见性变化日志(`<Transition>` 的**驱动源**)──
|
|
2852
|
+
*
|
|
2853
|
+
* 【为什么运行时记、桥来消费】`v-show` / `v-if` 的切换在运行时表现为 `visible` 槽位写入
|
|
2854
|
+
* ⇒ 编成 `TOGGLE_VIS` 指令(内核只管**应用**可见性,不知道"要不要过渡")。
|
|
2855
|
+
* 而"这个节点有没有过渡声明"是**编译产物**(`LayoutNode.transition`)、
|
|
2856
|
+
* "把动画交给谁播"是**宿主**(`proteusHost.animStart`)——桥正好两头都有。
|
|
2857
|
+
* ⇒ 运行时只负责**记事实**(谁、变成什么),桥负责**查声明 + 转发**(分层正确、可单测)。
|
|
2858
|
+
*
|
|
2859
|
+
* 【为什么要去重】`relink` 会重写全部槽位(含未变的可见性)⇒ 不去重会把"每次 relink"
|
|
2860
|
+
* 当成一次切换(过渡被反复触发)。⇒ 与上一状态比较,**只在真的翻转时记**。
|
|
2861
|
+
*/
|
|
2862
|
+
this.visibilityLog = [];
|
|
2863
|
+
this.lastVisible = /* @__PURE__ */ new Map();
|
|
2864
|
+
}
|
|
2865
|
+
/**
|
|
2866
|
+
* 从订阅表重建求值函数(把**可序列化的声明**变成可执行函数)
|
|
2867
|
+
*
|
|
2868
|
+
* 【为什么单独一步(而不是直接吃函数)】方案 §4.4 要求产物可序列化
|
|
2869
|
+
* (跨端禁 eval)⇒ 声明与实现分离:声明随产物下发,实现在各端本地重建。
|
|
2870
|
+
* 本方法就是「本地重建」的参考实现:
|
|
2871
|
+
* · `member` —— 纯路径访问,**免解析**(热路径主力)
|
|
2872
|
+
* · `const` —— 常量
|
|
2873
|
+
* · `expr` —— 表达式文本(各端按自己的能力求值;本实现给出最简单的成员回退)
|
|
2874
|
+
*/
|
|
2875
|
+
static buildEvaluators(specs) {
|
|
2876
|
+
const out = /* @__PURE__ */ new Map();
|
|
2877
|
+
for (const s of specs) {
|
|
2878
|
+
switch (s.form) {
|
|
2879
|
+
case "member": {
|
|
2880
|
+
const segs = (s.path ?? "").split(".").filter(Boolean);
|
|
2881
|
+
const [root, ...rest] = segs;
|
|
2882
|
+
out.set(s.evaluatorId, (ctx) => {
|
|
2883
|
+
let v = ctx.read(root);
|
|
2884
|
+
for (const seg of rest) {
|
|
2885
|
+
if (v == null || typeof v !== "object") return void 0;
|
|
2886
|
+
v = v[seg];
|
|
2887
|
+
}
|
|
2888
|
+
return v;
|
|
2889
|
+
});
|
|
2890
|
+
break;
|
|
2891
|
+
}
|
|
2892
|
+
case "program": {
|
|
2893
|
+
const prog = s.program;
|
|
2894
|
+
if (!prog) break;
|
|
2895
|
+
out.set(s.evaluatorId, (ctx) => evalExpr(prog, ctx));
|
|
2896
|
+
break;
|
|
2897
|
+
}
|
|
2898
|
+
case "const": {
|
|
2899
|
+
const text = (s.expr ?? "").trim();
|
|
2900
|
+
const parsed = /^-?\d+(\.\d+)?$/.test(text) ? Number(text) : text === "true" ? true : text === "false" ? false : text.replace(/^['"]|['"]$/g, "");
|
|
2901
|
+
out.set(s.evaluatorId, () => parsed);
|
|
2902
|
+
break;
|
|
2903
|
+
}
|
|
2904
|
+
case "expr": {
|
|
2905
|
+
const expr = (s.expr ?? "").trim();
|
|
2906
|
+
if (/^[A-Za-z_$][\w$]*(\.[A-Za-z_$][\w$]*)*$/.test(expr)) {
|
|
2907
|
+
const segs = expr.split(".");
|
|
2908
|
+
const [root, ...rest] = segs;
|
|
2909
|
+
out.set(s.evaluatorId, (ctx) => {
|
|
2910
|
+
let v = ctx.read(root);
|
|
2911
|
+
for (const seg of rest) {
|
|
2912
|
+
if (v == null || typeof v !== "object") return void 0;
|
|
2913
|
+
v = v[seg];
|
|
2914
|
+
}
|
|
2915
|
+
return v;
|
|
2916
|
+
});
|
|
2917
|
+
}
|
|
2918
|
+
break;
|
|
2919
|
+
}
|
|
2920
|
+
default:
|
|
2921
|
+
break;
|
|
2922
|
+
}
|
|
2923
|
+
}
|
|
2924
|
+
return out;
|
|
2925
|
+
}
|
|
2926
|
+
/** 注册订阅(方案 §4.3 Step 5:源变化 → 求值 → 直写槽位) */
|
|
2927
|
+
load(ctx, subscribe) {
|
|
2928
|
+
const unknownSources = [];
|
|
2929
|
+
const unsupportedEvaluators = [];
|
|
2930
|
+
let l1Slots = 0;
|
|
2931
|
+
for (const src of this.table.sources) {
|
|
2932
|
+
for (const spec of src.slots) {
|
|
2933
|
+
const impl = this.evaluators.get(spec.evaluatorId);
|
|
2934
|
+
if (!impl) {
|
|
2935
|
+
unsupportedEvaluators.push({ evaluatorId: spec.evaluatorId, reason: "\u6C42\u503C\u51FD\u6570\u672A\u5B9E\u4F8B\u5316\uFF08\u5F62\u6001\u4E0D\u652F\u6301\uFF09" });
|
|
2936
|
+
continue;
|
|
2937
|
+
}
|
|
2938
|
+
let slot = this.slots.get(spec.slotId);
|
|
2939
|
+
if (!slot) {
|
|
2940
|
+
slot = createSlot(
|
|
2941
|
+
{
|
|
2942
|
+
id: spec.slotId,
|
|
2943
|
+
// ★P1-3:子组件的 nodeId 统一加偏移(内核/宿主在**父树空间**里认节点)
|
|
2944
|
+
nodeId: spec.nodeId + this.nodeIdOffset,
|
|
2945
|
+
kind: spec.kind,
|
|
2946
|
+
keyId: this.rt.keys.intern(spec.propKey),
|
|
2947
|
+
listId: spec.listId ?? spec.slotId,
|
|
2948
|
+
listSlotId: spec.itemSlotId ?? spec.slotId,
|
|
2949
|
+
itemKind: spec.itemKind,
|
|
2950
|
+
itemValueField: spec.itemValueField,
|
|
2951
|
+
itemKeyField: spec.itemKeyField,
|
|
2952
|
+
scope: spec.scope
|
|
2953
|
+
},
|
|
2954
|
+
this.rt.keys,
|
|
2955
|
+
this.rt.strings,
|
|
2956
|
+
void 0,
|
|
2957
|
+
this.registry
|
|
2958
|
+
// ★传入注册表(缺省 ⇒ list-item 回退 LIST_UPDATE,保持 V3 行为)
|
|
2959
|
+
);
|
|
2960
|
+
this.slots.set(spec.slotId, slot);
|
|
2961
|
+
}
|
|
2962
|
+
this.slotById.set(spec.slotId, { slot, spec, evalId: spec.evaluatorId });
|
|
2963
|
+
const deps = this.sourcesOfSlot.get(spec.slotId) ?? [];
|
|
2964
|
+
deps.push(src.sourceName);
|
|
2965
|
+
this.sourcesOfSlot.set(spec.slotId, deps);
|
|
2966
|
+
l1Slots++;
|
|
2967
|
+
}
|
|
2968
|
+
}
|
|
2969
|
+
for (const src of this.table.sources) {
|
|
2970
|
+
subscribe(src.sourceName, () => {
|
|
2971
|
+
this.writeSlotsOfSource(src.sourceName, ctx);
|
|
2972
|
+
});
|
|
2973
|
+
}
|
|
2974
|
+
for (const src of this.table.sources) {
|
|
2975
|
+
for (const spec of src.slots) {
|
|
2976
|
+
if (spec.kind !== "list-item") continue;
|
|
2977
|
+
if (!this.evaluators.get(spec.evaluatorId)) {
|
|
2978
|
+
this.uninstantiatedSlots.push({ slotId: spec.slotId, evaluatorId: spec.evaluatorId, propKey: spec.propKey });
|
|
2979
|
+
}
|
|
2980
|
+
}
|
|
2981
|
+
}
|
|
2982
|
+
this.loaded = true;
|
|
2983
|
+
return {
|
|
2984
|
+
l1Slots,
|
|
2985
|
+
l0Slots: this.table.l0Slots.length,
|
|
2986
|
+
unknownSources,
|
|
2987
|
+
unsupportedEvaluators,
|
|
2988
|
+
// ★行内槽位的未实例化清单(与 unsupportedEvaluators 同性质:**上报而非静默**)
|
|
2989
|
+
uninstantiatedSlots: this.uninstantiatedSlots.slice()
|
|
2990
|
+
};
|
|
2991
|
+
}
|
|
2992
|
+
/** 写入某源驱动的全部槽位(源变化时由订阅回调触发) */
|
|
2993
|
+
writeSlotsOfSource(sourceName, ctx) {
|
|
2994
|
+
for (const src of this.table.sources) {
|
|
2995
|
+
if (src.sourceName !== sourceName) continue;
|
|
2996
|
+
this.writeListItems(src, ctx);
|
|
2997
|
+
for (const spec of src.slots) {
|
|
2998
|
+
if (spec.kind === "list-item") continue;
|
|
2999
|
+
if (spec.kind === "list-data") continue;
|
|
3000
|
+
if (spec.once && this.onceWritten.has(spec.slotId)) continue;
|
|
3001
|
+
if (this.skipNodeIds?.has(spec.nodeId + this.nodeIdOffset)) continue;
|
|
3002
|
+
if (spec.propKey === "paint.class" && this.onPaintProp) {
|
|
3003
|
+
const implC = this.evaluators.get(spec.evaluatorId);
|
|
3004
|
+
if (implC) {
|
|
3005
|
+
const cv = implC(ctx);
|
|
3006
|
+
const plan = this.table.classPlans?.[String(spec.nodeId)];
|
|
3007
|
+
let fields;
|
|
3008
|
+
if (plan) {
|
|
3009
|
+
fields = {};
|
|
3010
|
+
applyDynamicClassPlan(cv, plan, fields);
|
|
3011
|
+
} else {
|
|
3012
|
+
fields = resolveDynamicClasses(cv, this.table.classRules ?? []);
|
|
3013
|
+
}
|
|
3014
|
+
const nid = spec.nodeId + this.nodeIdOffset;
|
|
3015
|
+
const nextKeys = new Set(Object.keys(fields));
|
|
3016
|
+
const prev = this.lastClassFields.get(nid);
|
|
3017
|
+
if (prev) {
|
|
3018
|
+
for (const k of prev) if (!nextKeys.has(k)) this.onPaintProp(nid, `paint.${k}`, void 0);
|
|
3019
|
+
}
|
|
3020
|
+
for (const [fk, fv] of Object.entries(fields)) this.onPaintProp(nid, `paint.${fk}`, fv);
|
|
3021
|
+
this.lastClassFields.set(nid, nextKeys);
|
|
3022
|
+
const eC = this.slotById.get(spec.slotId);
|
|
3023
|
+
if (eC) eC.slot.value = cv;
|
|
3024
|
+
}
|
|
3025
|
+
continue;
|
|
3026
|
+
}
|
|
3027
|
+
if (spec.propKey.startsWith("paint.") && this.onPaintProp) {
|
|
3028
|
+
const impl2 = this.evaluators.get(spec.evaluatorId);
|
|
3029
|
+
if (impl2) {
|
|
3030
|
+
const v = impl2(ctx);
|
|
3031
|
+
this.onPaintProp(spec.nodeId + this.nodeIdOffset, spec.propKey, v);
|
|
3032
|
+
const e2 = this.slotById.get(spec.slotId);
|
|
3033
|
+
if (e2) e2.slot.value = v;
|
|
3034
|
+
}
|
|
3035
|
+
continue;
|
|
3036
|
+
}
|
|
3037
|
+
if (spec.memoId !== void 0 && !this.memoGroupDirty(spec.memoId, ctx)) continue;
|
|
3038
|
+
const entry = this.slotById.get(spec.slotId);
|
|
3039
|
+
const impl = this.evaluators.get(spec.evaluatorId);
|
|
3040
|
+
if (!entry || !impl) continue;
|
|
3041
|
+
const value = impl(ctx);
|
|
3042
|
+
if (spec.kind === "component-prop" && this.onComponentProp) {
|
|
3043
|
+
const propName = spec.propKey.startsWith("component.") ? spec.propKey.slice("component.".length) : spec.propKey;
|
|
3044
|
+
this.onComponentProp(spec.nodeId, propName, value);
|
|
3045
|
+
entry.slot.value = value;
|
|
3046
|
+
continue;
|
|
3047
|
+
}
|
|
3048
|
+
this.rt.setSlot(entry.slot, value);
|
|
3049
|
+
if (spec.once) this.onceWritten.add(spec.slotId);
|
|
3050
|
+
if (spec.kind === "visibility") {
|
|
3051
|
+
const now = Boolean(value);
|
|
3052
|
+
const prev = this.lastVisible.get(spec.nodeId);
|
|
3053
|
+
if (prev !== void 0 && prev !== now) {
|
|
3054
|
+
this.visibilityLog.push({ nodeId: spec.nodeId, visible: now });
|
|
3055
|
+
}
|
|
3056
|
+
this.lastVisible.set(spec.nodeId, now);
|
|
3057
|
+
}
|
|
3058
|
+
}
|
|
3059
|
+
}
|
|
3060
|
+
}
|
|
3061
|
+
/**
|
|
3062
|
+
* ★★**v-memo 组脏判定**(P2-5):依赖逐项比较(`Object.is`),任一变化即"脏"。
|
|
3063
|
+
*
|
|
3064
|
+
* 【语义(对齐 Vue `withMemo`)】依赖未变 ⇒ **跳过**该子树更新;变了 ⇒ 照常更新,
|
|
3065
|
+
* 并把本次依赖值记为基线供下轮比较。
|
|
3066
|
+
* 【诚实边界】组定义缺失(产物异常)⇒ 返回 true(照常更新)——**不静默冻结**:
|
|
3067
|
+
* "少一层优化"可接受,"该更新的不更新"不可接受。
|
|
3068
|
+
*/
|
|
3069
|
+
memoGroupDirty(memoId, ctx) {
|
|
3070
|
+
const fid = this.rt.frameId;
|
|
3071
|
+
if (this.memoDirtyFrame.get(memoId) === fid) return true;
|
|
3072
|
+
const group = this.table.memoGroups?.find((g) => g.memoId === memoId);
|
|
3073
|
+
if (!group) return true;
|
|
3074
|
+
const now = [];
|
|
3075
|
+
for (const prog of group.deps) {
|
|
3076
|
+
try {
|
|
3077
|
+
now.push(evalExpr(prog, ctx));
|
|
3078
|
+
} catch {
|
|
3079
|
+
now.push(void 0);
|
|
3080
|
+
}
|
|
3081
|
+
}
|
|
3082
|
+
const prev = this.memoBaseline.get(memoId);
|
|
3083
|
+
const changed = !prev || now.some((v, i) => !Object.is(v, prev[i]));
|
|
3084
|
+
if (changed) {
|
|
3085
|
+
this.memoBaseline.set(memoId, now);
|
|
3086
|
+
this.memoDirtyFrame.set(memoId, fid);
|
|
3087
|
+
}
|
|
3088
|
+
return changed;
|
|
3089
|
+
}
|
|
3090
|
+
/**
|
|
3091
|
+
* ★列表行内槽位通道:按行求值 → 与上一轮**按 key 缓存**的值 diff → 只发变化行
|
|
3092
|
+
*
|
|
3093
|
+
* 【关键:行作用域求值】`item.w` 的求值需要**当前行**绑定到 v-for 作用域。
|
|
3094
|
+
* 故每行构造一个 `rowCtx`:`read(scope)` 返回该行,其余名透传原 ctx。
|
|
3095
|
+
* (编译器在槽位上给了 `scope` 才能这么做——见 SlotSubscription.scope)
|
|
3096
|
+
*/
|
|
3097
|
+
writeListItems(src, ctx) {
|
|
3098
|
+
const itemSlots = src.slots.filter((x) => x.kind === "list-item");
|
|
3099
|
+
if (itemSlots.length === 0) return;
|
|
3100
|
+
const rowsCache = /* @__PURE__ */ new Map();
|
|
3101
|
+
const rowsOfList2 = (listId, spec) => {
|
|
3102
|
+
const cached = rowsCache.get(listId);
|
|
3103
|
+
if (cached) return cached;
|
|
3104
|
+
const out = [];
|
|
3105
|
+
const keyOf2 = (r, i) => spec.itemKeyField && r && r[spec.itemKeyField] !== void 0 ? String(r[spec.itemKeyField]) : String(i);
|
|
3106
|
+
const segs = (spec.sourceExpr ?? "").split(".").filter(Boolean);
|
|
3107
|
+
const topRows = ctx.read(src.sourceName);
|
|
3108
|
+
if (!Array.isArray(topRows)) return out;
|
|
3109
|
+
const walkSegs = segs[0] === src.sourceName ? segs.slice(1) : segs;
|
|
3110
|
+
let current = topRows.map((row) => ({ row, ancestors: [] }));
|
|
3111
|
+
for (let seg = 0; seg < walkSegs.length; seg++) {
|
|
3112
|
+
const field = walkSegs[seg];
|
|
3113
|
+
const next = [];
|
|
3114
|
+
for (const c of current) {
|
|
3115
|
+
const arr = c.row?.[field];
|
|
3116
|
+
if (!Array.isArray(arr)) continue;
|
|
3117
|
+
for (const x of arr) {
|
|
3118
|
+
next.push({ row: x, ancestors: [...c.ancestors, c.row] });
|
|
3119
|
+
}
|
|
3120
|
+
}
|
|
3121
|
+
current = next;
|
|
3122
|
+
}
|
|
3123
|
+
for (let i = 0; i < current.length; i++) {
|
|
3124
|
+
out.push({ key: keyOf2(current[i].row, i), row: current[i].row, ancestors: current[i].ancestors });
|
|
3125
|
+
}
|
|
3126
|
+
rowsCache.set(listId, out);
|
|
3127
|
+
return out;
|
|
3128
|
+
};
|
|
3129
|
+
const rowCtxCache = /* @__PURE__ */ new Map();
|
|
3130
|
+
for (const spec of itemSlots) {
|
|
3131
|
+
const impl = this.evaluators.get(spec.evaluatorId);
|
|
3132
|
+
if (!impl) {
|
|
3133
|
+
this.uninstantiatedSlots.push({ slotId: spec.slotId, evaluatorId: spec.evaluatorId, propKey: spec.propKey });
|
|
3134
|
+
continue;
|
|
3135
|
+
}
|
|
3136
|
+
const scope = spec.scope ?? "";
|
|
3137
|
+
const rows = rowsOfList2(spec.listId ?? -1, spec);
|
|
3138
|
+
for (const rowRef of rows) {
|
|
3139
|
+
const key = rowRef.key;
|
|
3140
|
+
const row = rowRef.row;
|
|
3141
|
+
const ancestors = rowRef.ancestors;
|
|
3142
|
+
const cached = rowCtxCache.get(rowRef);
|
|
3143
|
+
const rowCtx = cached && cached.scope === scope ? cached.ctx : this.makeRowCtx(scope, row, ancestors, ctx);
|
|
3144
|
+
if (!(cached && cached.scope === scope)) rowCtxCache.set(rowRef, { scope, ctx: rowCtx });
|
|
3145
|
+
const value = impl(rowCtx);
|
|
3146
|
+
this.diffAndEmit(spec, key, value);
|
|
3147
|
+
}
|
|
3148
|
+
}
|
|
3149
|
+
}
|
|
3150
|
+
/**
|
|
3151
|
+
* ★★行级失效:**只重算指定行**的 list-item 槽位(2026-09-29 新增)
|
|
3152
|
+
*
|
|
3153
|
+
* 【为什么需要(本仓真机实测的瓶颈)】粗粒度触发(`triggers.get('list')` ⇒ `relink`)只知道
|
|
3154
|
+
* "源变了"、不知道"哪一行变了" ⇒ 只能**全表重扫**:1000 行 × 3 槽位 = 3000 次求值 + diff。
|
|
3155
|
+
* 真机 `V11` 实测该段 **7.03ms**(列表更新总 11ms)——而 Vapor 的设计本意是
|
|
3156
|
+
* **O(1) 槽位直写**(改哪行算哪行)。本方法就是那个 O(1) 入口。
|
|
3157
|
+
*
|
|
3158
|
+
* 【调用方责任】提供**变更行的身份**(`key` + 行对象 + 祖先链)——它天然知道(编译器产出的
|
|
3159
|
+
* 行作用域效应 / 应用层的不可变更新都携带这些)。**不知道时不要调用**,走 `relink` 全扫(正确但慢)。
|
|
3160
|
+
*
|
|
3161
|
+
* 【正确性】与全扫路径**共用** `makeRowCtx` / `diffAndEmit` / `itemValueCache`(唯一实现
|
|
3162
|
+
* ⇒ 两条路径不会漂移);缓存是实例级的 ⇒ 全扫与行级失效交替调用也保持一致。
|
|
3163
|
+
*
|
|
3164
|
+
* @param listId 目标列表 id(订阅表里的 `listId`)
|
|
3165
|
+
* @param key 行标识(= `itemKeyField` 对应的值,与 `emitListItem` 的键一致)
|
|
3166
|
+
* @param row 该行的**数据对象**
|
|
3167
|
+
* @param ancestors 该行的祖先行链(顶层列表传空数组;嵌套列表按「自外向内」)
|
|
3168
|
+
*/
|
|
3169
|
+
relinkRow(listId, key, row, ancestors = [], ctx) {
|
|
3170
|
+
const evalCtx = ctx ?? this.lastCtx ?? { read: () => void 0 };
|
|
3171
|
+
for (const spec of this.listSpecsOf(listId)) {
|
|
3172
|
+
const impl = this.evaluators.get(spec.evaluatorId);
|
|
3173
|
+
if (!impl) continue;
|
|
3174
|
+
const rowCtx = this.makeRowCtx(spec.scope ?? "", row, ancestors, evalCtx);
|
|
3175
|
+
this.diffAndEmit(spec, key, impl(rowCtx));
|
|
3176
|
+
}
|
|
3177
|
+
}
|
|
3178
|
+
/** scope(v-for 别名)→ listId 反查(`relinkRow` 用;作用域由订阅表声明,不猜) */
|
|
3179
|
+
listIdOfScope(scope) {
|
|
3180
|
+
for (const src of this.table.sources) {
|
|
3181
|
+
for (const sl of src.slots) if (sl.kind === "list-item" && (sl.scope ?? "") === scope) return sl.listId ?? -1;
|
|
3182
|
+
}
|
|
3183
|
+
return void 0;
|
|
3184
|
+
}
|
|
3185
|
+
/** 该列表的全部 list-item 槽位(`relinkRow` 用;与 `writeListItems` 同一数据源) */
|
|
3186
|
+
listSpecsOf(listId) {
|
|
3187
|
+
const out = [];
|
|
3188
|
+
for (const src of this.table.sources) {
|
|
3189
|
+
for (const sl of src.slots) if (sl.kind === "list-item" && (sl.listId ?? -1) === listId) out.push(sl);
|
|
3190
|
+
}
|
|
3191
|
+
return out;
|
|
3192
|
+
}
|
|
3193
|
+
/** ★行作用域上下文(**唯一实现**):当前行别名 + 各层祖先别名 —— `writeListItems` 与 `relinkRow` 共用 */
|
|
3194
|
+
makeRowCtx(scope, row, ancestors, ctx) {
|
|
3195
|
+
if (!scope) return ctx;
|
|
3196
|
+
const lid = this.listIdOfScope(scope);
|
|
3197
|
+
const ancestorScopes = lid === void 0 ? [] : this.ancestorScopesOf(lid);
|
|
3198
|
+
return makeScopedRead(scope, row, ancestors, ancestorScopes, ctx);
|
|
3199
|
+
}
|
|
3200
|
+
/** ★值 diff + 发射(**唯一实现**):嵌套 Map 免拼接:listId/slotId 均为数字键 */
|
|
3201
|
+
diffAndEmit(spec, key, value) {
|
|
3202
|
+
const listId = spec.listId ?? -1;
|
|
3203
|
+
let bySlot = this.itemValueCache.get(listId);
|
|
3204
|
+
if (!bySlot) {
|
|
3205
|
+
bySlot = /* @__PURE__ */ new Map();
|
|
3206
|
+
this.itemValueCache.set(listId, bySlot);
|
|
3207
|
+
}
|
|
3208
|
+
let byKey = bySlot.get(spec.slotId);
|
|
3209
|
+
if (!byKey) {
|
|
3210
|
+
byKey = /* @__PURE__ */ new Map();
|
|
3211
|
+
bySlot.set(spec.slotId, byKey);
|
|
3212
|
+
}
|
|
3213
|
+
if (byKey.get(key) === value) return;
|
|
3214
|
+
byKey.set(key, value);
|
|
3215
|
+
this.emitListItem(spec, key, value);
|
|
3216
|
+
}
|
|
3217
|
+
/** 发一条行内更新指令:解析得到 nodeId 就发普通指令,否则回退 LIST_UPDATE */
|
|
3218
|
+
emitListItem(spec, key, value) {
|
|
3219
|
+
const nodeId = this.registry?.resolveNode(spec.listId ?? -1, key, spec.itemSlotId ?? -1);
|
|
3220
|
+
if (nodeId !== void 0 && this.skipNodeIds?.has(nodeId)) return;
|
|
3221
|
+
if (nodeId !== void 0) {
|
|
3222
|
+
if ((spec.itemKind ?? "style") === "text") {
|
|
3223
|
+
this.rt.buffer.push({ op: 3 /* SET_TEXT */, nodeId, textRef: this.rt.strings.intern(String(value)) });
|
|
3224
|
+
} else {
|
|
3225
|
+
this.rt.buffer.push({
|
|
3226
|
+
op: 2 /* SET_STYLE */,
|
|
3227
|
+
nodeId,
|
|
3228
|
+
keyId: this.rt.keys.intern(spec.propKey),
|
|
3229
|
+
value: typeof value === "number" ? value : Number(value) || 0
|
|
3230
|
+
});
|
|
3231
|
+
}
|
|
3232
|
+
} else {
|
|
3233
|
+
this.rt.buffer.push({
|
|
3234
|
+
op: 34 /* LIST_UPDATE */,
|
|
3235
|
+
listId: spec.listId ?? -1,
|
|
3236
|
+
itemKeyRef: this.rt.strings.intern(key),
|
|
3237
|
+
slotId: spec.itemSlotId ?? -1,
|
|
3238
|
+
value: typeof value === "number" ? value : Number(value) || 0
|
|
3239
|
+
});
|
|
3240
|
+
}
|
|
3241
|
+
}
|
|
3242
|
+
/** ★首帧同步:把所有 L1 槽位按当前源值写一遍(否则首屏不会出现这些值) */
|
|
3243
|
+
relink(ctx) {
|
|
3244
|
+
this.lastCtx = ctx;
|
|
3245
|
+
for (const src of this.table.sources) {
|
|
3246
|
+
this.writeSlotsOfSource(src.sourceName, ctx);
|
|
3247
|
+
}
|
|
3248
|
+
}
|
|
3249
|
+
/**
|
|
3250
|
+
* 某列表的**别名链**(自外向内;末位是它自身的 scope)
|
|
3251
|
+
*
|
|
3252
|
+
* 【为什么需要】`rowCtx` 要按位置把「祖先行」绑给对应的外层别名——
|
|
3253
|
+
* 而位置对应关系依赖"自外向内"的稳定顺序(由 `parentListId` 逐级上溯构造)。
|
|
3254
|
+
*/
|
|
3255
|
+
ancestorScopesOf(listId) {
|
|
3256
|
+
const chain = [];
|
|
3257
|
+
let cur = listId;
|
|
3258
|
+
const allSlots = this.table.sources.flatMap((x) => x.slots);
|
|
3259
|
+
let guard = 0;
|
|
3260
|
+
while (cur !== void 0 && guard < 32) {
|
|
3261
|
+
const spec = allSlots.find((x) => x.kind === "list-item" && x.listId === cur);
|
|
3262
|
+
if (!spec) break;
|
|
3263
|
+
chain.unshift(spec.scope ?? "");
|
|
3264
|
+
cur = spec.parentListId;
|
|
3265
|
+
guard++;
|
|
3266
|
+
}
|
|
3267
|
+
return chain;
|
|
3268
|
+
}
|
|
3269
|
+
/** 取某源的行数组(列表源 ⇒ 数组;非数组返回空)——仅供「无 :key 时用下标兜底」 */
|
|
3270
|
+
rowsOfSource(sourceName, ctx) {
|
|
3271
|
+
const v = ctx.read(sourceName);
|
|
3272
|
+
return Array.isArray(v) ? v : [];
|
|
3273
|
+
}
|
|
3274
|
+
/**
|
|
3275
|
+
* ★★★**取走可见性变化**(P3-3;取走即复位——与 `takePatches` 同款"自上次取走以来"语义)。
|
|
3276
|
+
*
|
|
3277
|
+
* 桥侧用法:`relink`/手势回调后 drain ⇒ 对每个变化查模板的 `transition` 声明 ⇒
|
|
3278
|
+
* 有声明则调 `proteusHost.animStart(...)`(宿主把动画交给内核)。
|
|
3279
|
+
*/
|
|
3280
|
+
takeVisibilityChanges() {
|
|
3281
|
+
if (this.visibilityLog.length === 0) return [];
|
|
3282
|
+
const out = this.visibilityLog.slice();
|
|
3283
|
+
this.visibilityLog.length = 0;
|
|
3284
|
+
return out;
|
|
3285
|
+
}
|
|
3286
|
+
/** 某槽位是否已建立订阅(诊断:确认"这个槽位真的被接管了") */
|
|
3287
|
+
hasSlot(slotId) {
|
|
3288
|
+
return this.slots.has(slotId);
|
|
3289
|
+
}
|
|
3290
|
+
get loaded_() {
|
|
3291
|
+
return this.loaded;
|
|
3292
|
+
}
|
|
3293
|
+
/** 该槽位由哪些源驱动(诊断 / explain) */
|
|
3294
|
+
depsOf(slotId) {
|
|
3295
|
+
return this.sourcesOfSlot.get(slotId) ?? [];
|
|
3296
|
+
}
|
|
3297
|
+
/** L1 槽位表(供宿主对账:哪些槽位由 L1 接管) */
|
|
3298
|
+
slotIds() {
|
|
3299
|
+
return [...this.slots.keys()].sort((a, b) => a - b);
|
|
3300
|
+
}
|
|
3301
|
+
};
|
|
3302
|
+
function makeScopedRead(scope, row, ancestors, ancestorScopes, ctx) {
|
|
3303
|
+
if (!scope) return ctx;
|
|
3304
|
+
return {
|
|
3305
|
+
read: (n) => {
|
|
3306
|
+
if (n === scope) return row;
|
|
3307
|
+
const idx = ancestorScopes.indexOf(n);
|
|
3308
|
+
if (idx >= 0 && idx < ancestors.length) return ancestors[idx];
|
|
3309
|
+
return ctx.read(n);
|
|
3310
|
+
}
|
|
3311
|
+
};
|
|
3312
|
+
}
|
|
3313
|
+
|
|
3314
|
+
// ../slot-runtime/src/instantiate.ts
|
|
3315
|
+
function applyStyleField(target, propKey, key, v, classRules, classPlan) {
|
|
3316
|
+
if (propKey === "paint.class") {
|
|
3317
|
+
let fields;
|
|
3318
|
+
if (classPlan) {
|
|
3319
|
+
fields = {};
|
|
3320
|
+
applyDynamicClassPlan(v, classPlan, fields);
|
|
3321
|
+
} else {
|
|
3322
|
+
fields = resolveDynamicClasses(v, classRules ?? []);
|
|
3323
|
+
}
|
|
3324
|
+
for (const [k, val] of Object.entries(fields)) target[k] = val;
|
|
3325
|
+
return;
|
|
3326
|
+
}
|
|
3327
|
+
;
|
|
3328
|
+
target[key] = v;
|
|
3329
|
+
}
|
|
3330
|
+
function setNormText(obj, value) {
|
|
3331
|
+
const t = value === void 0 || value === null ? "" : String(value);
|
|
3332
|
+
obj["text"] = normalizeWhiteSpace(t, obj["whiteSpace"]);
|
|
3333
|
+
}
|
|
3334
|
+
function normalizeWhiteSpace(text, ws) {
|
|
3335
|
+
switch (ws) {
|
|
3336
|
+
case "pre":
|
|
3337
|
+
case "pre-wrap":
|
|
3338
|
+
return text;
|
|
3339
|
+
// 原样保留
|
|
3340
|
+
case "pre-line":
|
|
3341
|
+
return text.replace(/[ \t\r\f\v]+/g, " ").split("\n").map((l) => l.trim()).join("\n");
|
|
3342
|
+
default:
|
|
3343
|
+
return text.replace(/\s+/g, " ").trim();
|
|
3344
|
+
}
|
|
3345
|
+
}
|
|
3346
|
+
function engineFieldOf(propKey) {
|
|
3347
|
+
if (propKey === "text.content") return { kind: "text" };
|
|
3348
|
+
const m = propKey.match(/^(?:layout|paint|text)\.(.+)$/);
|
|
3349
|
+
if (!m) return null;
|
|
3350
|
+
return { kind: "style", key: m[1] };
|
|
3351
|
+
}
|
|
3352
|
+
function evalTextSegments(segs, read) {
|
|
3353
|
+
let out = "";
|
|
3354
|
+
for (const s of segs) {
|
|
3355
|
+
if ("text" in s) {
|
|
3356
|
+
out += s.text;
|
|
3357
|
+
continue;
|
|
3358
|
+
}
|
|
3359
|
+
try {
|
|
3360
|
+
const v = evalExpr(s.expr, { read });
|
|
3361
|
+
out += v === void 0 || v === null ? "" : String(v);
|
|
3362
|
+
} catch {
|
|
3363
|
+
}
|
|
3364
|
+
}
|
|
3365
|
+
return out;
|
|
3366
|
+
}
|
|
3367
|
+
function rowsOfList(listId, meta, table, read) {
|
|
3368
|
+
if (!table) return [];
|
|
3369
|
+
const allSlots = table.sources.flatMap((s) => s.slots);
|
|
3370
|
+
const itemSlots = allSlots.filter((x) => x.kind === "list-item" && x.listId === listId);
|
|
3371
|
+
const spec = itemSlots[0] ?? allSlots.find((x) => x.kind === "list-data" && x.listId === listId);
|
|
3372
|
+
if (!spec) return [];
|
|
3373
|
+
const srcName = table.sources.find((s) => s.slots.some((x) => x.listId === listId))?.sourceName ?? "";
|
|
3374
|
+
const segs = (meta?.sourceField || spec.sourceExpr || "").split(".").filter(Boolean);
|
|
3375
|
+
const topRows = read(srcName);
|
|
3376
|
+
if (!Array.isArray(topRows)) return [];
|
|
3377
|
+
const walkSegs = segs[0] === srcName ? segs.slice(1) : segs;
|
|
3378
|
+
let cur = topRows;
|
|
3379
|
+
for (const field of walkSegs) {
|
|
3380
|
+
const next = [];
|
|
3381
|
+
for (const r of cur) {
|
|
3382
|
+
const arr = r?.[field];
|
|
3383
|
+
if (!Array.isArray(arr)) continue;
|
|
3384
|
+
for (const x of arr) next.push(x);
|
|
3385
|
+
}
|
|
3386
|
+
cur = next;
|
|
3387
|
+
}
|
|
3388
|
+
return cur;
|
|
3389
|
+
}
|
|
3390
|
+
function instantiateTemplate(tpl, opts) {
|
|
3391
|
+
const nodes = [];
|
|
3392
|
+
const idOffset = opts.idOffset ?? 0;
|
|
3393
|
+
const instNotes = [];
|
|
3394
|
+
const maxTemplateId = tpl.nodes.reduce((m, n) => Math.max(m, n.id), 0);
|
|
3395
|
+
let nextId = opts.firstRowInstanceId ?? maxTemplateId + 1;
|
|
3396
|
+
let allocated = 0;
|
|
3397
|
+
let reused = 0;
|
|
3398
|
+
const rowLists = new Map(tpl.lists.map((l) => [l.listId, l]));
|
|
3399
|
+
const byId = /* @__PURE__ */ new Map();
|
|
3400
|
+
let valuesFilled = 0;
|
|
3401
|
+
const evaluators = opts.table ? VaporRuntime.buildEvaluators(opts.table.evaluators) : /* @__PURE__ */ new Map();
|
|
3402
|
+
const ancestorScopesOf = (listId) => {
|
|
3403
|
+
const chain = [];
|
|
3404
|
+
let cur = listId;
|
|
3405
|
+
let guard = 0;
|
|
3406
|
+
while (cur !== void 0 && guard < 32) {
|
|
3407
|
+
const meta = rowLists.get(cur);
|
|
3408
|
+
if (!meta) break;
|
|
3409
|
+
chain.unshift(meta.scope ?? "");
|
|
3410
|
+
cur = meta.parentListId;
|
|
3411
|
+
guard++;
|
|
3412
|
+
}
|
|
3413
|
+
return chain;
|
|
3414
|
+
};
|
|
3415
|
+
const evalInitial = (evaluatorId, ctx) => {
|
|
3416
|
+
const impl = evaluators.get(evaluatorId);
|
|
3417
|
+
if (!impl) return { ok: false };
|
|
3418
|
+
try {
|
|
3419
|
+
return { ok: true, value: impl(ctx) };
|
|
3420
|
+
} catch {
|
|
3421
|
+
return { ok: false };
|
|
3422
|
+
}
|
|
3423
|
+
};
|
|
3424
|
+
const virtualRows = [];
|
|
3425
|
+
const emit = (n, id, parentId, ctx = { read: opts.read }) => {
|
|
3426
|
+
const out = { id, parentId };
|
|
3427
|
+
if (n.style) for (const [k, v] of Object.entries(n.style)) {
|
|
3428
|
+
;
|
|
3429
|
+
out[k] = v;
|
|
3430
|
+
}
|
|
3431
|
+
if (n.text !== void 0) setNormText(out, n.text);
|
|
3432
|
+
if (n.textSegments && n.textSegments.length > 0) {
|
|
3433
|
+
setNormText(out, evalTextSegments(n.textSegments, ctx.read));
|
|
3434
|
+
}
|
|
3435
|
+
if (n.tag) out.tag = n.tag;
|
|
3436
|
+
if (n.component) out.component = n.component;
|
|
3437
|
+
if (n.slotOutlet) out.slotOutlet = n.slotOutlet;
|
|
3438
|
+
if (n.slotFor) out.slotFor = n.slotFor;
|
|
3439
|
+
out.id = id + idOffset;
|
|
3440
|
+
out.parentId = parentId === null ? null : parentId + idOffset;
|
|
3441
|
+
nodes.push(out);
|
|
3442
|
+
byId.set(id, out);
|
|
3443
|
+
};
|
|
3444
|
+
const cloneRow = (listId, row, itemKey, first, rowIndex, parentOverrideId, collect, ancestors = []) => {
|
|
3445
|
+
const meta = rowLists.get(listId);
|
|
3446
|
+
const idMap = /* @__PURE__ */ new Map();
|
|
3447
|
+
let rowRootId = 0;
|
|
3448
|
+
const myIds = [];
|
|
3449
|
+
for (const tplId of meta.subtreeIds) {
|
|
3450
|
+
const engineId = first ? tplId : nextId++;
|
|
3451
|
+
if (!first) allocated++;
|
|
3452
|
+
else reused++;
|
|
3453
|
+
idMap.set(tplId, engineId);
|
|
3454
|
+
if (tplId === meta.rowRootId) rowRootId = engineId;
|
|
3455
|
+
}
|
|
3456
|
+
const rowRead = makeScopedRead(meta.scope ?? "", row, ancestors, ancestorScopesOf(listId), { read: opts.read });
|
|
3457
|
+
for (const tplId of meta.subtreeIds) {
|
|
3458
|
+
const tn = tpl.nodes.find((x) => x.id === tplId);
|
|
3459
|
+
const engineId = idMap.get(tplId);
|
|
3460
|
+
const tplParent = tn.parentId;
|
|
3461
|
+
let parentId = tplParent === null ? null : idMap.get(tplParent) ?? tplParent;
|
|
3462
|
+
if (parentOverrideId !== void 0 && tplId === meta.rowRootId) parentId = parentOverrideId;
|
|
3463
|
+
emit(tn, engineId, parentId, rowRead);
|
|
3464
|
+
myIds.push(engineId);
|
|
3465
|
+
}
|
|
3466
|
+
for (const inner of tpl.lists) {
|
|
3467
|
+
if (inner.parentListId !== listId) continue;
|
|
3468
|
+
let arr = row;
|
|
3469
|
+
for (const seg of (inner.sourceField ?? "").split(".").filter(Boolean)) {
|
|
3470
|
+
arr = arr?.[seg];
|
|
3471
|
+
}
|
|
3472
|
+
if (!Array.isArray(arr)) continue;
|
|
3473
|
+
const innerKeyField = opts.table?.sources.flatMap((s) => s.slots).find((x) => x.kind === "list-item" && x.listId === inner.listId)?.itemKeyField;
|
|
3474
|
+
for (let j = 0; j < arr.length; j++) {
|
|
3475
|
+
const innerRow = arr[j];
|
|
3476
|
+
const innerKey = innerKeyField && innerRow?.[innerKeyField] !== void 0 ? String(innerRow[innerKeyField]) : String(j);
|
|
3477
|
+
cloneRow(inner.listId, innerRow, innerKey, first && j === 0, j, rowRootId, myIds, [...ancestors, row]);
|
|
3478
|
+
}
|
|
3479
|
+
}
|
|
3480
|
+
virtualRows.push({
|
|
3481
|
+
index: rowIndex,
|
|
3482
|
+
key: itemKey,
|
|
3483
|
+
root: rowRootId + idOffset,
|
|
3484
|
+
ids: myIds.map((x) => x + idOffset)
|
|
3485
|
+
});
|
|
3486
|
+
if (collect) collect.push(...myIds);
|
|
3487
|
+
if (opts.table) {
|
|
3488
|
+
const itemSlots = opts.table.sources.flatMap((s) => s.slots).filter((x) => x.kind === "list-item" && x.listId === listId);
|
|
3489
|
+
if (itemSlots.length > 0) {
|
|
3490
|
+
const slotNodes = {};
|
|
3491
|
+
const rowCtx = makeScopedRead(meta.scope ?? "", row, ancestors, ancestorScopesOf(listId), {
|
|
3492
|
+
read: opts.read
|
|
3493
|
+
});
|
|
3494
|
+
for (const sl of itemSlots) {
|
|
3495
|
+
const mapped = idMap.get(sl.nodeId);
|
|
3496
|
+
if (mapped !== void 0) slotNodes[sl.itemSlotId] = mapped;
|
|
3497
|
+
if (mapped === void 0) continue;
|
|
3498
|
+
const target = byId.get(mapped);
|
|
3499
|
+
if (!target) continue;
|
|
3500
|
+
const f = engineFieldOf(sl.propKey);
|
|
3501
|
+
if (!f) continue;
|
|
3502
|
+
const ev0 = evalInitial(sl.evaluatorId, rowCtx);
|
|
3503
|
+
let v;
|
|
3504
|
+
if (ev0.ok) {
|
|
3505
|
+
v = ev0.value;
|
|
3506
|
+
} else {
|
|
3507
|
+
const field = sl.itemValueField;
|
|
3508
|
+
if (!field) continue;
|
|
3509
|
+
v = row[field];
|
|
3510
|
+
if (v === void 0) continue;
|
|
3511
|
+
}
|
|
3512
|
+
if (f.kind === "text") {
|
|
3513
|
+
setNormText(target, v);
|
|
3514
|
+
} else {
|
|
3515
|
+
applyStyleField(target, sl.propKey, f.key, v, opts.table?.classRules, opts.table?.classPlans?.[String(sl.nodeId)]);
|
|
3516
|
+
}
|
|
3517
|
+
valuesFilled++;
|
|
3518
|
+
}
|
|
3519
|
+
if (opts.registry && Object.keys(slotNodes).length > 0) {
|
|
3520
|
+
const shifted = {};
|
|
3521
|
+
for (const [k, v] of Object.entries(slotNodes)) shifted[Number(k)] = v + idOffset;
|
|
3522
|
+
opts.registry.registerItem(listId, itemKey, shifted);
|
|
3523
|
+
}
|
|
3524
|
+
}
|
|
3525
|
+
}
|
|
3526
|
+
return rowRootId;
|
|
3527
|
+
};
|
|
3528
|
+
const rowMemberIds = /* @__PURE__ */ new Set();
|
|
3529
|
+
for (const l of tpl.lists) for (const id of l.subtreeIds) rowMemberIds.add(id);
|
|
3530
|
+
for (const n of tpl.nodes) {
|
|
3531
|
+
if (rowMemberIds.has(n.id)) {
|
|
3532
|
+
const meta = tpl.lists.find((l) => l.rowRootId === n.id);
|
|
3533
|
+
if (!meta) continue;
|
|
3534
|
+
if (meta.parentListId !== void 0) continue;
|
|
3535
|
+
const rows = rowsOfList(meta.listId, meta, opts.table, opts.read);
|
|
3536
|
+
for (let i = 0; i < rows.length; i++) {
|
|
3537
|
+
const row = rows[i];
|
|
3538
|
+
const keyOf2 = () => {
|
|
3539
|
+
const keyField = opts.table?.sources.flatMap((s) => s.slots).find((x) => x.kind === "list-item" && x.listId === meta.listId)?.itemKeyField;
|
|
3540
|
+
return keyField && row[keyField] !== void 0 ? String(row[keyField]) : String(i);
|
|
3541
|
+
};
|
|
3542
|
+
cloneRow(meta.listId, row, keyOf2(), i === 0, i);
|
|
3543
|
+
}
|
|
3544
|
+
continue;
|
|
3545
|
+
}
|
|
3546
|
+
emit(n, n.id, n.parentId);
|
|
3547
|
+
}
|
|
3548
|
+
if (opts.table) {
|
|
3549
|
+
for (const sl of opts.table.constantSlots ?? []) {
|
|
3550
|
+
const target = byId.get(sl.nodeId);
|
|
3551
|
+
if (!target) continue;
|
|
3552
|
+
const f = engineFieldOf(sl.propKey);
|
|
3553
|
+
if (!f) continue;
|
|
3554
|
+
const evS = evalInitial(sl.evaluatorId, { read: opts.read });
|
|
3555
|
+
if (!evS.ok) continue;
|
|
3556
|
+
const v = evS.value;
|
|
3557
|
+
if (f.kind === "text") {
|
|
3558
|
+
setNormText(target, v);
|
|
3559
|
+
} else {
|
|
3560
|
+
applyStyleField(target, sl.propKey, f.key, v, opts.table?.classRules, opts.table?.classPlans?.[String(sl.nodeId)]);
|
|
3561
|
+
}
|
|
3562
|
+
valuesFilled++;
|
|
3563
|
+
}
|
|
3564
|
+
for (const src of opts.table.sources) {
|
|
3565
|
+
for (const sl of src.slots) {
|
|
3566
|
+
if (sl.kind === "list-item" || sl.kind === "list-data" || sl.kind === "component-prop") continue;
|
|
3567
|
+
const target = byId.get(sl.nodeId);
|
|
3568
|
+
if (!target) continue;
|
|
3569
|
+
const f = engineFieldOf(sl.propKey);
|
|
3570
|
+
if (!f) continue;
|
|
3571
|
+
const evS = evalInitial(sl.evaluatorId, { read: opts.read });
|
|
3572
|
+
let v;
|
|
3573
|
+
if (evS.ok) {
|
|
3574
|
+
v = evS.value;
|
|
3575
|
+
} else {
|
|
3576
|
+
v = opts.read(src.sourceName);
|
|
3577
|
+
if (v === void 0) continue;
|
|
3578
|
+
}
|
|
3579
|
+
if (f.kind === "text") {
|
|
3580
|
+
setNormText(target, v);
|
|
3581
|
+
} else {
|
|
3582
|
+
applyStyleField(target, sl.propKey, f.key, v, opts.table?.classRules, opts.table?.classPlans?.[String(sl.nodeId)]);
|
|
3583
|
+
}
|
|
3584
|
+
valuesFilled++;
|
|
3585
|
+
}
|
|
3586
|
+
}
|
|
3587
|
+
}
|
|
3588
|
+
const componentMounts = [];
|
|
3589
|
+
const slotMounts = [];
|
|
3590
|
+
const droppedNodeIds = /* @__PURE__ */ new Set();
|
|
3591
|
+
let componentNodes = 0;
|
|
3592
|
+
const subtreeOf = (list, rootId) => {
|
|
3593
|
+
const doomed = /* @__PURE__ */ new Set([rootId]);
|
|
3594
|
+
let grew = true;
|
|
3595
|
+
while (grew) {
|
|
3596
|
+
grew = false;
|
|
3597
|
+
for (const x of list) {
|
|
3598
|
+
if (x.parentId !== null && !doomed.has(x.id) && doomed.has(x.parentId)) {
|
|
3599
|
+
doomed.add(x.id);
|
|
3600
|
+
grew = true;
|
|
3601
|
+
}
|
|
3602
|
+
}
|
|
3603
|
+
}
|
|
3604
|
+
return doomed;
|
|
3605
|
+
};
|
|
3606
|
+
const dissolveOutlets = (list, fills, evalProps) => {
|
|
3607
|
+
const consumed = /* @__PURE__ */ new Set();
|
|
3608
|
+
const placed = [];
|
|
3609
|
+
for (const outlet of list.filter((x) => x.slotOutlet)) {
|
|
3610
|
+
if (list.indexOf(outlet) < 0) continue;
|
|
3611
|
+
const name = outlet.slotOutlet.name;
|
|
3612
|
+
const fill = fills.get(name);
|
|
3613
|
+
const useFill = fill !== void 0 && fill.length > 0 && !consumed.has(name);
|
|
3614
|
+
if (useFill) {
|
|
3615
|
+
consumed.add(name);
|
|
3616
|
+
const outletProps = evalProps ? evalProps(outlet) : {};
|
|
3617
|
+
const doomed = subtreeOf(list, outlet.id);
|
|
3618
|
+
for (const id of doomed) droppedNodeIds.add(id);
|
|
3619
|
+
const idx = list.findIndex((x) => x.id === outlet.id);
|
|
3620
|
+
const kept = list.filter((x) => !doomed.has(x.id));
|
|
3621
|
+
kept.splice(Math.min(idx, kept.length), 0, ...fill);
|
|
3622
|
+
list.length = 0;
|
|
3623
|
+
list.push(...kept);
|
|
3624
|
+
for (const r of fill) {
|
|
3625
|
+
r.parentId = outlet.parentId;
|
|
3626
|
+
delete r.slotFor;
|
|
3627
|
+
}
|
|
3628
|
+
slotMounts.push({ outletNodeId: outlet.id, name, filled: true, contentIds: fill.map((x) => x.id), fallbackIds: [] });
|
|
3629
|
+
placed.push({ name, fill, outlet, props: outletProps });
|
|
3630
|
+
} else {
|
|
3631
|
+
const kids = list.filter((x) => x.parentId === outlet.id);
|
|
3632
|
+
const fallbackIds = [];
|
|
3633
|
+
if (kids.length > 0) {
|
|
3634
|
+
for (const k of kids) k.parentId = outlet.parentId;
|
|
3635
|
+
fallbackIds.push(...kids.map((x) => x.id));
|
|
3636
|
+
list.splice(list.indexOf(outlet), 1);
|
|
3637
|
+
droppedNodeIds.add(outlet.id);
|
|
3638
|
+
} else if (typeof outlet.text === "string" && outlet.text !== "") {
|
|
3639
|
+
delete outlet.slotOutlet;
|
|
3640
|
+
outlet.tag = "p-text";
|
|
3641
|
+
} else {
|
|
3642
|
+
list.splice(list.indexOf(outlet), 1);
|
|
3643
|
+
droppedNodeIds.add(outlet.id);
|
|
3644
|
+
}
|
|
3645
|
+
slotMounts.push({ outletNodeId: outlet.id, name, filled: false, contentIds: [], fallbackIds });
|
|
3646
|
+
}
|
|
3647
|
+
}
|
|
3648
|
+
return { consumed, placed };
|
|
3649
|
+
};
|
|
3650
|
+
if (opts.table?.componentIs && opts.table.componentIs.length > 0) {
|
|
3651
|
+
for (const ci of opts.table.componentIs) {
|
|
3652
|
+
const target = byId.get(ci.nodeId);
|
|
3653
|
+
if (!target) continue;
|
|
3654
|
+
const impl = evaluators.get(ci.evaluatorId);
|
|
3655
|
+
if (!impl) {
|
|
3656
|
+
instNotes.push(`\u52A8\u6001\u7EC4\u4EF6 \`:is="${ci.expr}"\`\uFF08\u8282\u70B9 ${ci.nodeId}\uFF09\u6C42\u503C\u5668\u672A\u5B9E\u4F8B\u5316 \u21D2 \u672A\u89E3\u6790`);
|
|
3657
|
+
continue;
|
|
3658
|
+
}
|
|
3659
|
+
let name;
|
|
3660
|
+
try {
|
|
3661
|
+
name = impl({ read: opts.read });
|
|
3662
|
+
} catch (e) {
|
|
3663
|
+
instNotes.push(`\u52A8\u6001\u7EC4\u4EF6 \`:is="${ci.expr}"\` \u6C42\u503C\u629B\u9519\uFF08${String(e?.message ?? e)}\uFF09\u21D2 \u672A\u89E3\u6790`);
|
|
3664
|
+
continue;
|
|
3665
|
+
}
|
|
3666
|
+
if (name === void 0 || name === null || name === "") {
|
|
3667
|
+
const doomed = subtreeOf(nodes, ci.nodeId);
|
|
3668
|
+
for (const id of doomed) droppedNodeIds.add(id);
|
|
3669
|
+
for (let i = nodes.length - 1; i >= 0; i--) if (doomed.has(nodes[i].id)) nodes.splice(i, 1);
|
|
3670
|
+
instNotes.push(`\u52A8\u6001\u7EC4\u4EF6 \`:is="${ci.expr}"\` \u6C42\u503C\u4E3A\u5047\uFF08${String(name)}\uFF09\u21D2 \u8282\u70B9\u5DF2\u6458\u9664\uFF08Vue \u540C\uFF09`);
|
|
3671
|
+
continue;
|
|
3672
|
+
}
|
|
3673
|
+
if (typeof name !== "string") {
|
|
3674
|
+
instNotes.push(
|
|
3675
|
+
`\u52A8\u6001\u7EC4\u4EF6 \`:is="${ci.expr}"\` \u6C42\u503C\u4E0D\u662F\u5B57\u7B26\u4E32\uFF08${typeof name}\uFF09\u21D2 \u672A\u89E3\u6790\uFF08\u672C\u5B9E\u73B0\u6309**\u7EC4\u4EF6\u540D**\u67E5\u6CE8\u518C\u8868\uFF1B\u7EC4\u4EF6\u5BF9\u8C61\u5F62\u6001\u4E3A\u540E\u7EED\u6279\u6B21\uFF09`
|
|
3676
|
+
);
|
|
3677
|
+
continue;
|
|
3678
|
+
}
|
|
3679
|
+
target.component = name;
|
|
3680
|
+
delete target.componentIs;
|
|
3681
|
+
instNotes.push(`\u52A8\u6001\u7EC4\u4EF6 \`:is="${ci.expr}"\` \u21D2 \u89E3\u6790\u4E3A ${name}\uFF08\u5B9E\u4F8B\u5316\u671F\u4E00\u6B21\u6027\u89E3\u6790\uFF09`);
|
|
3682
|
+
}
|
|
3683
|
+
}
|
|
3684
|
+
if (opts.components) {
|
|
3685
|
+
const depth = opts.componentDepth ?? 0;
|
|
3686
|
+
const boundaries = nodes.filter((n) => n.component);
|
|
3687
|
+
for (const boundary of boundaries) {
|
|
3688
|
+
if (droppedNodeIds.has(boundary.id)) continue;
|
|
3689
|
+
const name = boundary.component;
|
|
3690
|
+
const def = opts.components[name];
|
|
3691
|
+
if (!def) {
|
|
3692
|
+
instNotes.push(`\u7EC4\u4EF6 ${name}\uFF08\u8FB9\u754C\u8282\u70B9 ${boundary.id}\uFF09\u672A\u5728\u6CE8\u518C\u8868\u91CC \u21D2 \u5185\u90E8\u7559\u7A7A\uFF08\u5360\u4F4D\uFF09`);
|
|
3693
|
+
continue;
|
|
3694
|
+
}
|
|
3695
|
+
if (depth >= 8) {
|
|
3696
|
+
instNotes.push(`\u7EC4\u4EF6 ${name} \u5C55\u5F00\u6DF1\u5EA6\u8D85\u4E0A\u9650\uFF088\uFF09\u21D2 \u505C\u6B62\u9012\u5F52\uFF08\u9632\u81EA\u5F15\u7528\uFF09`);
|
|
3697
|
+
continue;
|
|
3698
|
+
}
|
|
3699
|
+
const boundaryLocalId = boundary.id - idOffset;
|
|
3700
|
+
const props = {};
|
|
3701
|
+
if (opts.table) {
|
|
3702
|
+
const allSlots = [
|
|
3703
|
+
...opts.table.sources.flatMap((src) => src.slots.map((sl) => ({ sl, srcName: src.sourceName }))),
|
|
3704
|
+
...(opts.table.constantSlots ?? []).map((sl) => ({ sl, srcName: void 0 }))
|
|
3705
|
+
];
|
|
3706
|
+
for (const { sl, srcName } of allSlots) {
|
|
3707
|
+
if (sl.kind !== "component-prop" || sl.nodeId !== boundaryLocalId) continue;
|
|
3708
|
+
const propName = sl.propKey.startsWith("component.") ? sl.propKey.slice("component.".length) : sl.propKey;
|
|
3709
|
+
if (srcName !== void 0) {
|
|
3710
|
+
props[propName] = opts.read(srcName);
|
|
3711
|
+
} else {
|
|
3712
|
+
const impl = evaluators.get(sl.evaluatorId);
|
|
3713
|
+
if (impl) {
|
|
3714
|
+
try {
|
|
3715
|
+
props[propName] = impl({ read: opts.read });
|
|
3716
|
+
} catch {
|
|
3717
|
+
props[propName] = void 0;
|
|
3718
|
+
}
|
|
3719
|
+
}
|
|
3720
|
+
}
|
|
3721
|
+
}
|
|
3722
|
+
}
|
|
3723
|
+
const childRead = (n) => {
|
|
3724
|
+
if (Object.prototype.hasOwnProperty.call(props, n)) return props[n];
|
|
3725
|
+
const d = def.data;
|
|
3726
|
+
if (d && Object.prototype.hasOwnProperty.call(d, n)) return d[n];
|
|
3727
|
+
return opts.read(n);
|
|
3728
|
+
};
|
|
3729
|
+
const ctx = { read: childRead };
|
|
3730
|
+
const childRegistry = opts.registry ? new ListRegistry() : void 0;
|
|
3731
|
+
const childOffset = nextId;
|
|
3732
|
+
const childInst = instantiateTemplate(def.template, {
|
|
3733
|
+
viewport: opts.viewport,
|
|
3734
|
+
read: childRead,
|
|
3735
|
+
table: def.table,
|
|
3736
|
+
registry: childRegistry,
|
|
3737
|
+
idOffset: idOffset + childOffset,
|
|
3738
|
+
components: opts.components,
|
|
3739
|
+
componentDepth: depth + 1
|
|
3740
|
+
});
|
|
3741
|
+
const contentRoots = nodes.filter((x) => x.parentId === boundary.id && x.slotFor);
|
|
3742
|
+
if (contentRoots.length > 0 || childInst.slotMounts?.length) {
|
|
3743
|
+
const fills = /* @__PURE__ */ new Map();
|
|
3744
|
+
const scopeOf = /* @__PURE__ */ new Map();
|
|
3745
|
+
const bindingsOf = /* @__PURE__ */ new Map();
|
|
3746
|
+
for (const r of contentRoots) {
|
|
3747
|
+
const sf = r.slotFor;
|
|
3748
|
+
const arr = fills.get(sf.name) ?? [];
|
|
3749
|
+
arr.push(r);
|
|
3750
|
+
fills.set(sf.name, arr);
|
|
3751
|
+
if (!scopeOf.has(sf.name)) scopeOf.set(sf.name, sf.scope);
|
|
3752
|
+
if (!bindingsOf.has(sf.name)) bindingsOf.set(sf.name, sf.scopeBindings);
|
|
3753
|
+
}
|
|
3754
|
+
const childEvaluators = def.table ? VaporRuntime.buildEvaluators(def.table.evaluators) : /* @__PURE__ */ new Map();
|
|
3755
|
+
const evalOutletProps = (outlet) => {
|
|
3756
|
+
const out = {};
|
|
3757
|
+
const names = outlet.slotOutlet.props ?? [];
|
|
3758
|
+
const outletLocal = outlet.id - (idOffset + childOffset);
|
|
3759
|
+
const allSlots = def.table ? [...def.table.sources.flatMap((s) => s.slots), ...def.table.constantSlots ?? []] : [];
|
|
3760
|
+
for (const propName of names) {
|
|
3761
|
+
const sl = allSlots.find((s) => s.nodeId === outletLocal && s.propKey === `attr.${propName}`);
|
|
3762
|
+
if (!sl) continue;
|
|
3763
|
+
const impl = childEvaluators.get(sl.evaluatorId);
|
|
3764
|
+
if (!impl) continue;
|
|
3765
|
+
try {
|
|
3766
|
+
out[propName] = impl({ read: childRead });
|
|
3767
|
+
} catch {
|
|
3768
|
+
out[propName] = void 0;
|
|
3769
|
+
}
|
|
3770
|
+
}
|
|
3771
|
+
return out;
|
|
3772
|
+
};
|
|
3773
|
+
const { consumed, placed } = dissolveOutlets(childInst.nodes, fills, evalOutletProps);
|
|
3774
|
+
for (const p of placed) {
|
|
3775
|
+
const scopeVar = scopeOf.get(p.name);
|
|
3776
|
+
const scopeBindings = bindingsOf.get(p.name);
|
|
3777
|
+
if (!scopeVar && !scopeBindings) continue;
|
|
3778
|
+
const rootIds = new Set(p.fill.map((x) => x.id));
|
|
3779
|
+
const byIdOfChild = new Map(childInst.nodes.map((x) => [x.id, x]));
|
|
3780
|
+
const subtree = [];
|
|
3781
|
+
for (const x of childInst.nodes) {
|
|
3782
|
+
let cur = x;
|
|
3783
|
+
let guard = 0;
|
|
3784
|
+
while (cur !== void 0 && guard++ < 64) {
|
|
3785
|
+
if (rootIds.has(cur.id)) {
|
|
3786
|
+
subtree.push(x);
|
|
3787
|
+
break;
|
|
3788
|
+
}
|
|
3789
|
+
cur = cur.parentId === null ? void 0 : byIdOfChild.get(cur.parentId);
|
|
3790
|
+
}
|
|
3791
|
+
}
|
|
3792
|
+
const scopedRead = (n2) => {
|
|
3793
|
+
if (scopeVar && n2 === scopeVar) return p.props;
|
|
3794
|
+
if (scopeBindings) {
|
|
3795
|
+
const hit = scopeBindings.find((b) => b.local === n2);
|
|
3796
|
+
if (hit) return p.props[hit.key];
|
|
3797
|
+
}
|
|
3798
|
+
return opts.read(n2);
|
|
3799
|
+
};
|
|
3800
|
+
const scopeTag = scopeVar ?? scopeBindings.map((b) => b.local).join(",");
|
|
3801
|
+
for (const node of subtree) {
|
|
3802
|
+
const tn = tpl.nodes.find((x) => x.id === node.id - idOffset);
|
|
3803
|
+
if (tn?.textSegments && tn.textSegments.length > 0) {
|
|
3804
|
+
setNormText(node, evalTextSegments(tn.textSegments, scopedRead));
|
|
3805
|
+
}
|
|
3806
|
+
}
|
|
3807
|
+
let appliedScoped = 0;
|
|
3808
|
+
for (const sc of opts.table?.slotScopedSlots ?? []) {
|
|
3809
|
+
if (scopeVar !== void 0 ? sc.scope !== scopeVar : !scopeBindings.some((b) => b.local === sc.scope)) continue;
|
|
3810
|
+
const target = subtree.find((x) => x.id === sc.nodeId + idOffset);
|
|
3811
|
+
if (!target) continue;
|
|
3812
|
+
const impl = evaluators.get(sc.evaluatorId);
|
|
3813
|
+
if (!impl) continue;
|
|
3814
|
+
const f = engineFieldOf(sc.propKey);
|
|
3815
|
+
if (!f) continue;
|
|
3816
|
+
let v;
|
|
3817
|
+
try {
|
|
3818
|
+
v = impl({ read: scopedRead });
|
|
3819
|
+
} catch {
|
|
3820
|
+
continue;
|
|
3821
|
+
}
|
|
3822
|
+
if (f.kind === "text") {
|
|
3823
|
+
setNormText(target, v);
|
|
3824
|
+
} else {
|
|
3825
|
+
applyStyleField(target, sc.propKey, f.key, v, opts.table?.classRules, opts.table?.classPlans?.[String(sc.nodeId)]);
|
|
3826
|
+
}
|
|
3827
|
+
appliedScoped++;
|
|
3828
|
+
valuesFilled++;
|
|
3829
|
+
}
|
|
3830
|
+
instNotes.push(
|
|
3831
|
+
`\u63D2\u69FD ${name}#${p.name}\uFF1A\u4F5C\u7528\u57DF ${scopeVar ? `\`${scopeVar}\`` : `\u89E3\u6784 \`{ ${scopeBindings.map((b) => b.local).join(", ")} }\``} \u7ED1\u5B9A\u51FA\u53E3 props ${JSON.stringify(p.props)}\uFF08\u6587\u672C\u6BB5\u91CD\u6C42\u503C ${subtree.filter((x) => tpl.nodes.find((t) => t.id === x.id - idOffset)?.textSegments?.length).length} \u8282\u70B9 \xB7 \u4F5C\u7528\u57DF\u6837\u5F0F ${appliedScoped} \u5904\uFF09`
|
|
3832
|
+
);
|
|
3833
|
+
}
|
|
3834
|
+
for (const [nm, roots] of fills) {
|
|
3835
|
+
if (consumed.has(nm)) {
|
|
3836
|
+
for (const r of roots) {
|
|
3837
|
+
const i = nodes.indexOf(r);
|
|
3838
|
+
if (i >= 0) nodes.splice(i, 1);
|
|
3839
|
+
}
|
|
3840
|
+
continue;
|
|
3841
|
+
}
|
|
3842
|
+
for (const r of roots) {
|
|
3843
|
+
const doomed = subtreeOf(nodes, r.id);
|
|
3844
|
+
for (const id of doomed) droppedNodeIds.add(id);
|
|
3845
|
+
for (let i = nodes.length - 1; i >= 0; i--) if (doomed.has(nodes[i].id)) nodes.splice(i, 1);
|
|
3846
|
+
}
|
|
3847
|
+
instNotes.push(`\u63D2\u69FD ${name}#${nm} \u7684\u5185\u5BB9\u65E0\u51FA\u53E3\u63A5\u4F4F\uFF08\u5B50\u7EC4\u4EF6\u6CA1\u6709\u540C\u540D <slot>\uFF09\u21D2 \u672A\u6E32\u67D3`);
|
|
3848
|
+
}
|
|
3849
|
+
}
|
|
3850
|
+
for (const r of childInst.nodes) if (r.parentId === null) r.parentId = boundary.id;
|
|
3851
|
+
nodes.push(...childInst.nodes);
|
|
3852
|
+
componentNodes += childInst.nodes.length;
|
|
3853
|
+
valuesFilled += childInst.stats.valuesFilled;
|
|
3854
|
+
nextId = childOffset + childInst.stats.maxLocalId + 1;
|
|
3855
|
+
componentMounts.push({
|
|
3856
|
+
boundaryNodeId: boundary.id,
|
|
3857
|
+
name,
|
|
3858
|
+
props,
|
|
3859
|
+
ctx,
|
|
3860
|
+
table: def.table,
|
|
3861
|
+
registry: childRegistry,
|
|
3862
|
+
nodeIds: childInst.nodes.map((x) => x.id),
|
|
3863
|
+
idOffset: idOffset + childOffset,
|
|
3864
|
+
// ★P1-3 emits:边界在**本树**的 id 空间 —— 宿主要加 idOffset 才是它看到的 id
|
|
3865
|
+
treeOffset: idOffset
|
|
3866
|
+
});
|
|
3867
|
+
for (const m of childInst.componentMounts ?? []) componentMounts.push(m);
|
|
3868
|
+
for (const sm of childInst.slotMounts ?? []) slotMounts.push(sm);
|
|
3869
|
+
for (const d of childInst.stats.droppedNodeIds ?? []) droppedNodeIds.add(d);
|
|
3870
|
+
for (const nt of childInst.notes ?? []) instNotes.push(nt);
|
|
3871
|
+
}
|
|
3872
|
+
}
|
|
3873
|
+
if ((opts.componentDepth ?? 0) === 0) {
|
|
3874
|
+
dissolveOutlets(nodes, /* @__PURE__ */ new Map());
|
|
3875
|
+
for (const x of nodes) {
|
|
3876
|
+
const rec = x;
|
|
3877
|
+
if (rec.slotFor !== void 0) delete rec.slotFor;
|
|
3878
|
+
if (rec.slotOutlet !== void 0) delete rec.slotOutlet;
|
|
3879
|
+
}
|
|
3880
|
+
}
|
|
3881
|
+
return {
|
|
3882
|
+
viewport: opts.viewport,
|
|
3883
|
+
nodes,
|
|
3884
|
+
stats: {
|
|
3885
|
+
reusedTemplateIds: reused,
|
|
3886
|
+
allocatedIds: allocated,
|
|
3887
|
+
rows: nodes.length,
|
|
3888
|
+
valuesFilled,
|
|
3889
|
+
// ★P1-3:本树 local 高水位(父级据此推进自己的分配器——**不含** idOffset)
|
|
3890
|
+
maxLocalId: nextId - 1,
|
|
3891
|
+
componentNodes,
|
|
3892
|
+
// ★P1-3 插槽分发:丢弃节点(最终 id 空间)——桥传给运行时 `skipNodeIds`
|
|
3893
|
+
...droppedNodeIds.size > 0 ? { droppedNodeIds: [...droppedNodeIds] } : {}
|
|
3894
|
+
},
|
|
3895
|
+
// ★只有**恰好一个**列表时才给虚拟化描述(多个列表 ⇒ 行号空间不同源,宿主按行号二分会错配)
|
|
3896
|
+
virtual: virtualRows.length > 0 && tpl.lists.length === 1 ? { rows: virtualRows } : void 0,
|
|
3897
|
+
...componentMounts.length > 0 ? { componentMounts } : {},
|
|
3898
|
+
...slotMounts.length > 0 ? { slotMounts } : {},
|
|
3899
|
+
...instNotes.length > 0 ? { notes: instNotes } : {}
|
|
3900
|
+
};
|
|
1656
3901
|
}
|
|
1657
|
-
|
|
1658
|
-
|
|
1659
|
-
|
|
1660
|
-
return
|
|
3902
|
+
|
|
3903
|
+
// ../slot-runtime/src/dispatch.ts
|
|
3904
|
+
function createDispatchState() {
|
|
3905
|
+
return { onceFired: /* @__PURE__ */ new Set() };
|
|
1661
3906
|
}
|
|
1662
|
-
var
|
|
1663
|
-
|
|
1664
|
-
|
|
1665
|
-
|
|
1666
|
-
|
|
1667
|
-
|
|
1668
|
-
|
|
1669
|
-
|
|
1670
|
-
|
|
1671
|
-
const
|
|
1672
|
-
|
|
1673
|
-
|
|
1674
|
-
|
|
1675
|
-
|
|
1676
|
-
|
|
3907
|
+
var keyOf = (nodeId, event, handler) => `${nodeId}:${event}:${handler}`;
|
|
3908
|
+
function indexEventBindings(bindings) {
|
|
3909
|
+
const byNodeEvent = /* @__PURE__ */ new Map();
|
|
3910
|
+
for (const b of bindings) byNodeEvent.set(`${b.nodeId}:${b.event}`, b);
|
|
3911
|
+
return byNodeEvent;
|
|
3912
|
+
}
|
|
3913
|
+
function dispatchGesture(chain, event, index, state, run) {
|
|
3914
|
+
const fired = [];
|
|
3915
|
+
const skippedSelf = [];
|
|
3916
|
+
const skippedOnce = [];
|
|
3917
|
+
let handler = "";
|
|
3918
|
+
let stopped = false;
|
|
3919
|
+
const hit = chain.length > 0 ? chain[0] : -1;
|
|
3920
|
+
for (const id of chain) {
|
|
3921
|
+
const b = index.get(`${id}:${event}`) ?? index.get(`${id}:tap`);
|
|
3922
|
+
if (!b) continue;
|
|
3923
|
+
if (b.componentEmit) continue;
|
|
3924
|
+
if (b.self && id !== hit) {
|
|
3925
|
+
skippedSelf.push(id);
|
|
3926
|
+
continue;
|
|
3927
|
+
}
|
|
3928
|
+
const onceKey = keyOf(id, b.event, b.handler);
|
|
3929
|
+
if (b.once && state.onceFired.has(onceKey)) {
|
|
3930
|
+
skippedOnce.push(id);
|
|
3931
|
+
continue;
|
|
3932
|
+
}
|
|
3933
|
+
if (!run(b.handler, id)) continue;
|
|
3934
|
+
if (b.once) state.onceFired.add(onceKey);
|
|
3935
|
+
fired.push(id);
|
|
3936
|
+
if (!handler) handler = b.handler;
|
|
3937
|
+
if (b.stop) {
|
|
3938
|
+
stopped = true;
|
|
3939
|
+
break;
|
|
1677
3940
|
}
|
|
1678
|
-
if (set.size) out[tag] = set;
|
|
1679
3941
|
}
|
|
1680
|
-
return
|
|
1681
|
-
}
|
|
3942
|
+
return { fired, handler, stopped, skippedSelf, skippedOnce };
|
|
3943
|
+
}
|
|
3944
|
+
|
|
3945
|
+
// ../slot-runtime/src/directives.ts
|
|
3946
|
+
var HOST_DIRECTIVE_SPECS = {
|
|
3947
|
+
animate: {
|
|
3948
|
+
argKind: "anim-preset",
|
|
3949
|
+
argHint: "\u52A8\u753B\u9884\u8BBE\uFF08fade / slide-up / slide-down / slide-left / slide-right / zoom / fade-slide-up\uFF09",
|
|
3950
|
+
desc: "\u503C\u53D8\u5316\uFF08\u6216\u9996\u6B21\u6C42\u503C\u4E3A\u771F\uFF09\u65F6\u5728\u8BE5\u8282\u70B9**\u64AD\u4E00\u6B21**\u9884\u8BBE\u52A8\u753B\uFF08\u8D70\u5185\u6838\u52A8\u753B\u901A\u9053\uFF0C\u4E0E <Transition> \u540C\u4E00\u5957\uFF09\u3002\u8BED\u4E49\u5BF9\u9F50\uFF1A\u6307\u4EE4\u7684 mounted\uFF08\u9996\u8BC4 truthy \u5373\u64AD\uFF09\u4E0E updated\uFF08\u503C\u53D8\u5316\u5373\u64AD\uFF09\u3002"
|
|
3951
|
+
}
|
|
3952
|
+
};
|
|
3953
|
+
var HOST_DIRECTIVE_NAMES = Object.keys(HOST_DIRECTIVE_SPECS);
|
|
1682
3954
|
|
|
1683
3955
|
// src/dispatcher.ts
|
|
1684
3956
|
var DispatcherError = class extends Error {
|
|
@@ -1782,6 +4054,7 @@ function createNodeOpsDispatcher(initialBackend) {
|
|
|
1782
4054
|
};
|
|
1783
4055
|
}
|
|
1784
4056
|
function renderIRTree(backend, ir, insertInto) {
|
|
4057
|
+
if (ir.type === "#text") return backend.createText(ir.text ?? "");
|
|
1785
4058
|
const root = backend.createElement(ir);
|
|
1786
4059
|
for (const child of ir.children) {
|
|
1787
4060
|
const c = renderIRTree(backend, child);
|
|
@@ -2212,6 +4485,133 @@ function createWebHostRuntime(opts = {}) {
|
|
|
2212
4485
|
return rt;
|
|
2213
4486
|
}
|
|
2214
4487
|
|
|
4488
|
+
// src/quickjs-host.ts
|
|
4489
|
+
function createQuickJsHostRuntime(opts = {}) {
|
|
4490
|
+
const id = opts.id ?? "quickjs";
|
|
4491
|
+
const capabilities = {
|
|
4492
|
+
threads: { main: true, background: false, count: 1 },
|
|
4493
|
+
// ★诚实声明:无后台线程
|
|
4494
|
+
engine: opts.engine ?? "quickjs",
|
|
4495
|
+
// 桥**机制**恒在(registerNativeHandler/invokeNative/拒绝协议都存在);transport 是可选出口
|
|
4496
|
+
nativeBridge: true,
|
|
4497
|
+
lifecycle: "full",
|
|
4498
|
+
// 宿主壳转发 onPause/onResume ⇒ full
|
|
4499
|
+
frameDriver: opts.frameDriver ?? "manual"
|
|
4500
|
+
};
|
|
4501
|
+
const refusals = [];
|
|
4502
|
+
const lifecycleEvents = [];
|
|
4503
|
+
const handlers = /* @__PURE__ */ new Map();
|
|
4504
|
+
const refuse = (op, reason) => {
|
|
4505
|
+
refusals.push({ op, reason, state: rt.state });
|
|
4506
|
+
throw new Error(`[${id}] \u804C\u8D23\u8FB9\u754C\u62D2\u7EDD ${op}\uFF1A${reason}\uFF08state=${rt.state}\uFF09`);
|
|
4507
|
+
};
|
|
4508
|
+
const rt = {
|
|
4509
|
+
id,
|
|
4510
|
+
state: "created",
|
|
4511
|
+
threads: ["main"],
|
|
4512
|
+
workers: [],
|
|
4513
|
+
queue: [],
|
|
4514
|
+
capabilities,
|
|
4515
|
+
lifecycleEvents,
|
|
4516
|
+
refusals,
|
|
4517
|
+
nativeHandlers: [],
|
|
4518
|
+
bootstrap() {
|
|
4519
|
+
if (rt.state === "destroyed") refuse("bootstrap", "\u5DF2\u9500\u6BC1\u7684\u8FD0\u884C\u65F6\u4E0D\u53EF\u590D\u6D3B\uFF08G-39.1\uFF1A\u751F\u547D\u5468\u671F\u552F\u4E00\u62E5\u6709\uFF09");
|
|
4520
|
+
if (rt.state === "suspended") refuse("bootstrap", "\u5DF2\u6302\u8D77\u72B6\u6001\u4E0B\u8BF7\u7528 resume()\uFF08bootstrap \u53EA\u8D1F\u8D23 created \u8D77\u6B65\uFF09");
|
|
4521
|
+
rt.state = "running";
|
|
4522
|
+
return rt;
|
|
4523
|
+
},
|
|
4524
|
+
suspend() {
|
|
4525
|
+
if (rt.state !== "running") refuse("suspend", "\u4EC5 running \u53EF\u6302\u8D77\uFF08\u91CD\u590D suspend / \u672A bootstrap \u90FD\u88AB\u62D2\u7EDD\uFF09");
|
|
4526
|
+
rt.state = "suspended";
|
|
4527
|
+
lifecycleEvents.push("suspend");
|
|
4528
|
+
},
|
|
4529
|
+
resume() {
|
|
4530
|
+
if (rt.state !== "suspended") refuse("resume", '\u4EC5 suspended \u53EF\u6062\u590D\uFF08\u9632"\u6CA1\u6302\u8D77\u8FC7\u5374\u4E0A\u62A5 resume"\u7684\u5047\u4E8B\u4EF6\uFF09');
|
|
4531
|
+
rt.state = "running";
|
|
4532
|
+
lifecycleEvents.push("resume");
|
|
4533
|
+
},
|
|
4534
|
+
destroy() {
|
|
4535
|
+
if (rt.state === "destroyed") refuse("destroy", "\u91CD\u590D\u9500\u6BC1\uFF08\u7B2C\u4E8C\u6B21 destroy \u901A\u5E38\u662F\u4E0A\u5C42\u7684\u91CD\u590D\u6E05\u7406\u2014\u2014\u771F\u5B9E\u7F3A\u9677\uFF09");
|
|
4536
|
+
rt.state = "destroyed";
|
|
4537
|
+
rt.queue = [];
|
|
4538
|
+
rt.workers = [];
|
|
4539
|
+
rt.threads = ["main"];
|
|
4540
|
+
lifecycleEvents.push("destroy");
|
|
4541
|
+
},
|
|
4542
|
+
createWorker() {
|
|
4543
|
+
if (rt.state === "destroyed") refuse("createWorker", "\u5DF2\u9500\u6BC1\u7684\u8FD0\u884C\u65F6\u4E0D\u80FD\u521B\u5EFA\u6267\u884C\u57DF");
|
|
4544
|
+
const w = {
|
|
4545
|
+
id: `w${rt.workers.length + 1}`,
|
|
4546
|
+
thread: `worker${rt.workers.length + 1}`,
|
|
4547
|
+
real: false
|
|
4548
|
+
};
|
|
4549
|
+
rt.workers.push(w);
|
|
4550
|
+
rt.threads.push(w.thread);
|
|
4551
|
+
return w;
|
|
4552
|
+
},
|
|
4553
|
+
postMessage() {
|
|
4554
|
+
return true;
|
|
4555
|
+
},
|
|
4556
|
+
enqueue(task, priority = 2) {
|
|
4557
|
+
if (rt.state === "destroyed") refuse("enqueue", "\u5DF2\u9500\u6BC1\u7684\u8FD0\u884C\u65F6\u4E0D\u518D\u63A5\u6536\u4EFB\u52A1\uFF08\u9632\u60AC\u7A7A\u56DE\u8C03\uFF09");
|
|
4558
|
+
rt.queue.push({ task, priority });
|
|
4559
|
+
},
|
|
4560
|
+
nextTick(fn) {
|
|
4561
|
+
if (rt.state === "destroyed") refuse("nextTick", "\u5DF2\u9500\u6BC1\u7684\u8FD0\u884C\u65F6\u4E0D\u518D\u63A5\u6536\u4EFB\u52A1");
|
|
4562
|
+
rt.queue.push({ task: fn, priority: 0 });
|
|
4563
|
+
},
|
|
4564
|
+
drain() {
|
|
4565
|
+
rt.queue.sort((a, b) => a.priority - b.priority);
|
|
4566
|
+
const out = [];
|
|
4567
|
+
while (rt.queue.length) {
|
|
4568
|
+
const { task } = rt.queue.shift();
|
|
4569
|
+
out.push(task());
|
|
4570
|
+
}
|
|
4571
|
+
return out;
|
|
4572
|
+
},
|
|
4573
|
+
/** ★宿主帧驱动(Android Choreographer / iOS CADisplayLink):一帧消费一次队列 */
|
|
4574
|
+
pumpFrame() {
|
|
4575
|
+
if (rt.state === "destroyed") return 0;
|
|
4576
|
+
if (rt.state === "suspended") return 0;
|
|
4577
|
+
rt.queue.sort((a, b) => a.priority - b.priority);
|
|
4578
|
+
let n = 0;
|
|
4579
|
+
while (rt.queue.length) {
|
|
4580
|
+
const { task } = rt.queue.shift();
|
|
4581
|
+
task();
|
|
4582
|
+
n++;
|
|
4583
|
+
}
|
|
4584
|
+
return n;
|
|
4585
|
+
},
|
|
4586
|
+
registerNativeHandler(name, handler) {
|
|
4587
|
+
handlers.set(name, handler);
|
|
4588
|
+
rt.nativeHandlers.push(name);
|
|
4589
|
+
},
|
|
4590
|
+
runOnThread(thread, task) {
|
|
4591
|
+
if (thread === "main") {
|
|
4592
|
+
rt.enqueue(task, 2);
|
|
4593
|
+
return;
|
|
4594
|
+
}
|
|
4595
|
+
return refuse("runOnThread(background)", "\u672C\u5BBF\u4E3B capabilities.threads.background=false\uFF08\u5355\u7EBF\u7A0B JS \u5F15\u64CE\uFF1B\u8C03\u7528\u65B9\u5E94\u67E5 capabilities \u540E\u964D\u7EA7\uFF09");
|
|
4596
|
+
},
|
|
4597
|
+
async invokeNative(name, args) {
|
|
4598
|
+
if (rt.state === "destroyed") refuse("invokeNative", "\u5DF2\u9500\u6BC1\u7684\u8FD0\u884C\u65F6\u4E0D\u80FD\u8C03\u539F\u751F");
|
|
4599
|
+
const h = handlers.get(name);
|
|
4600
|
+
if (h) return h(args);
|
|
4601
|
+
if (opts.transport) {
|
|
4602
|
+
const json = opts.transport.call(name, JSON.stringify(args ?? null));
|
|
4603
|
+
try {
|
|
4604
|
+
return JSON.parse(json);
|
|
4605
|
+
} catch {
|
|
4606
|
+
return json;
|
|
4607
|
+
}
|
|
4608
|
+
}
|
|
4609
|
+
return refuse("invokeNative", `\u539F\u751F\u65B9\u6CD5\u300C${name}\u300D\u672A\u6CE8\u518C\u4E14\u65E0 transport\uFF08\u5DF2\u6CE8\u518C\uFF1A${rt.nativeHandlers.join(", ") || "\uFF08\u7A7A\uFF09"}\uFF09`);
|
|
4610
|
+
}
|
|
4611
|
+
};
|
|
4612
|
+
return rt;
|
|
4613
|
+
}
|
|
4614
|
+
|
|
2215
4615
|
// src/hot-switch.ts
|
|
2216
4616
|
function createBackendSwitcher(dispatch) {
|
|
2217
4617
|
let currentIR = null;
|
|
@@ -3521,7 +5921,7 @@ function createSinglePageContainer(opts = {}) {
|
|
|
3521
5921
|
const emit = (event, payload) => {
|
|
3522
5922
|
for (const fn of events[event] ?? []) fn(payload);
|
|
3523
5923
|
};
|
|
3524
|
-
function
|
|
5924
|
+
function createSlot2(config) {
|
|
3525
5925
|
const ownership = opts.ownership ? createPageOwnership(config.pageId ?? `page-${++seq}`, { graph: opts.ownership.graph, quotaBytes: opts.ownership.quotaBytes }) : null;
|
|
3526
5926
|
const page = makeSlotPage(seq, config, ownership);
|
|
3527
5927
|
if (!config.pageId) seq++;
|
|
@@ -3530,7 +5930,7 @@ function createSinglePageContainer(opts = {}) {
|
|
|
3530
5930
|
return page;
|
|
3531
5931
|
}
|
|
3532
5932
|
function createPage(config) {
|
|
3533
|
-
return
|
|
5933
|
+
return createSlot2(config);
|
|
3534
5934
|
}
|
|
3535
5935
|
async function destroyPage(handle) {
|
|
3536
5936
|
const page = pages.get(handle.pageId);
|
|
@@ -3568,7 +5968,7 @@ function createSinglePageContainer(opts = {}) {
|
|
|
3568
5968
|
destroyPage,
|
|
3569
5969
|
async push(config) {
|
|
3570
5970
|
for (const p of [...pages.values()]) await destroyPage(p);
|
|
3571
|
-
const page =
|
|
5971
|
+
const page = createSlot2(config);
|
|
3572
5972
|
await mountPage(page);
|
|
3573
5973
|
current = page;
|
|
3574
5974
|
return page;
|
|
@@ -3995,10 +6395,10 @@ function analyzeOwnershipSource(source, opts = {}) {
|
|
|
3995
6395
|
if (src) src.activeBorrows++;
|
|
3996
6396
|
continue;
|
|
3997
6397
|
}
|
|
3998
|
-
const
|
|
3999
|
-
if (
|
|
4000
|
-
const name =
|
|
4001
|
-
const op =
|
|
6398
|
+
const call2 = txt.match(CALL_RE);
|
|
6399
|
+
if (call2) {
|
|
6400
|
+
const name = call2[1];
|
|
6401
|
+
const op = call2[2];
|
|
4002
6402
|
const info = vars.get(name);
|
|
4003
6403
|
if (!info) continue;
|
|
4004
6404
|
if (op === "transferTo") {
|
|
@@ -4343,10 +6743,10 @@ function insertScopeDrops(source) {
|
|
|
4343
6743
|
}
|
|
4344
6744
|
continue;
|
|
4345
6745
|
}
|
|
4346
|
-
const
|
|
4347
|
-
if (
|
|
6746
|
+
const call2 = t.match(/([A-Za-z_$][\w$]*)\s*\.\s*(?:drop|transferTo)\s*\(/);
|
|
6747
|
+
if (call2) {
|
|
4348
6748
|
for (const p of pending) {
|
|
4349
|
-
if (p.varName ===
|
|
6749
|
+
if (p.varName === call2[1] && !p.disposed) {
|
|
4350
6750
|
p.disposed = true;
|
|
4351
6751
|
break;
|
|
4352
6752
|
}
|
|
@@ -4566,6 +6966,587 @@ function formatMatrixReport(report) {
|
|
|
4566
6966
|
}
|
|
4567
6967
|
return lines.join("\n");
|
|
4568
6968
|
}
|
|
6969
|
+
|
|
6970
|
+
// ../contracts/src/layers.ts
|
|
6971
|
+
var LAYER_PRIMITIVES = ["layer-content", "layer-navigation", "layer-mask", "layer-popout"];
|
|
6972
|
+
|
|
6973
|
+
// ../contracts/src/mount-layers.ts
|
|
6974
|
+
var MOUNT_LAYERS = ["global", "page", "overlay"];
|
|
6975
|
+
var MOUNT_LAYER_ORDER = {
|
|
6976
|
+
global: 0,
|
|
6977
|
+
page: 1,
|
|
6978
|
+
overlay: 2
|
|
6979
|
+
};
|
|
6980
|
+
var MOUNT_LAYER_NODE_OFFSET = {
|
|
6981
|
+
global: 1,
|
|
6982
|
+
page: 2,
|
|
6983
|
+
overlay: 3
|
|
6984
|
+
};
|
|
6985
|
+
function mountLayerContainerPlans() {
|
|
6986
|
+
return [...MOUNT_LAYERS].sort((a, b) => MOUNT_LAYER_ORDER[a] - MOUNT_LAYER_ORDER[b]).map((layer) => ({
|
|
6987
|
+
layer,
|
|
6988
|
+
nodeOffset: MOUNT_LAYER_NODE_OFFSET[layer],
|
|
6989
|
+
order: MOUNT_LAYER_ORDER[layer],
|
|
6990
|
+
frame: "fullscreen"
|
|
6991
|
+
}));
|
|
6992
|
+
}
|
|
6993
|
+
|
|
6994
|
+
// ../contracts/src/scroll.ts
|
|
6995
|
+
var DOCK_REQUIRES_LAYER = LAYER_PRIMITIVES.includes("layer-navigation");
|
|
6996
|
+
|
|
6997
|
+
// src/screen-executor.ts
|
|
6998
|
+
function screenContentFromLayoutTemplate(template, viewport) {
|
|
6999
|
+
const nodes = template.nodes.map((n) => {
|
|
7000
|
+
const out = {
|
|
7001
|
+
id: n.id,
|
|
7002
|
+
parentId: n.parentId,
|
|
7003
|
+
// ★展平:`style` 的引擎字段提到顶层(内核 NodeDto 读顶层键)
|
|
7004
|
+
...n.style ?? {}
|
|
7005
|
+
};
|
|
7006
|
+
if (typeof n.text === "string" && n.text.length > 0) out.text = n.text;
|
|
7007
|
+
if (typeof n.tag === "string" && n.tag.length > 0) out.semantic = n.tag;
|
|
7008
|
+
return out;
|
|
7009
|
+
});
|
|
7010
|
+
return viewport ? { viewport, nodes } : { nodes };
|
|
7011
|
+
}
|
|
7012
|
+
function createScreenExecutor(opts) {
|
|
7013
|
+
const { host, anim, plan, contentOf, onScreenMounted, onEvent } = opts;
|
|
7014
|
+
const nodes = /* @__PURE__ */ new Map();
|
|
7015
|
+
const stats = {
|
|
7016
|
+
commands: 0,
|
|
7017
|
+
transitions: 0,
|
|
7018
|
+
forward: 0,
|
|
7019
|
+
back: 0,
|
|
7020
|
+
skippedNone: 0,
|
|
7021
|
+
destroyed: { pop: 0, reset: 0, freeze: 0 },
|
|
7022
|
+
visibility: { shown: 0, hidden: 0 },
|
|
7023
|
+
mounts: { first: 0, rebuild: 0 },
|
|
7024
|
+
errors: []
|
|
7025
|
+
};
|
|
7026
|
+
let txn = { exiting: null, mounted: /* @__PURE__ */ new Map() };
|
|
7027
|
+
const emit = (type, screenId, detail) => {
|
|
7028
|
+
onEvent?.({ type, screenId, detail });
|
|
7029
|
+
};
|
|
7030
|
+
async function runCommand(cmd) {
|
|
7031
|
+
stats.commands++;
|
|
7032
|
+
switch (cmd.op) {
|
|
7033
|
+
case "mount": {
|
|
7034
|
+
const rebuild = cmd.rebuild === true;
|
|
7035
|
+
const name = cmd.name ?? cmd.screenId;
|
|
7036
|
+
const path = cmd.path ?? "";
|
|
7037
|
+
const content = contentOf?.({ name, path });
|
|
7038
|
+
const node = await host.mountScreen({
|
|
7039
|
+
screenId: cmd.screenId,
|
|
7040
|
+
name,
|
|
7041
|
+
path,
|
|
7042
|
+
params: cmd.params ?? null,
|
|
7043
|
+
rebuild,
|
|
7044
|
+
...content ? { content } : {},
|
|
7045
|
+
// ★GP3-c:三层容器计划(由**契约**算,宿主只消费——偏移量而非绝对 id,见类型注释)
|
|
7046
|
+
layerContainers: mountLayerContainerPlans()
|
|
7047
|
+
});
|
|
7048
|
+
nodes.set(cmd.screenId, node);
|
|
7049
|
+
txn.mounted.set(cmd.screenId, rebuild);
|
|
7050
|
+
if (rebuild) stats.mounts.rebuild++;
|
|
7051
|
+
else stats.mounts.first++;
|
|
7052
|
+
emit("mount", cmd.screenId, { rebuild, node, contentNodes: content?.nodes.length ?? 0 });
|
|
7053
|
+
onScreenMounted?.(cmd.screenId, rebuild);
|
|
7054
|
+
return;
|
|
7055
|
+
}
|
|
7056
|
+
case "exit": {
|
|
7057
|
+
txn.exiting = { screenId: cmd.screenId, transition: cmd.transition, destroy: null };
|
|
7058
|
+
emit("exit", cmd.screenId, { transition: cmd.transition });
|
|
7059
|
+
return;
|
|
7060
|
+
}
|
|
7061
|
+
case "unmount": {
|
|
7062
|
+
const reason = cmd.reason ?? "pop";
|
|
7063
|
+
if (txn.exiting && txn.exiting.screenId === cmd.screenId) {
|
|
7064
|
+
txn.exiting.destroy = reason;
|
|
7065
|
+
emit("unmount-deferred", cmd.screenId, { reason });
|
|
7066
|
+
return;
|
|
7067
|
+
}
|
|
7068
|
+
await host.destroyScreen(cmd.screenId, reason, nodes.get(cmd.screenId));
|
|
7069
|
+
nodes.delete(cmd.screenId);
|
|
7070
|
+
stats.destroyed[reason]++;
|
|
7071
|
+
emit("destroy", cmd.screenId, { reason });
|
|
7072
|
+
return;
|
|
7073
|
+
}
|
|
7074
|
+
case "enter": {
|
|
7075
|
+
const incomingNode = nodes.get(cmd.screenId);
|
|
7076
|
+
if (incomingNode === void 0) {
|
|
7077
|
+
stats.errors.push(`enter(${cmd.screenId})\uFF1A\u5C4F\u5B50\u6811\u4E0D\u5B58\u5728\uFF08mount \u672A\u6267\u884C\uFF1F\uFF09`);
|
|
7078
|
+
emit("error", cmd.screenId, "enter-without-subtree");
|
|
7079
|
+
return;
|
|
7080
|
+
}
|
|
7081
|
+
const exiting = txn.exiting;
|
|
7082
|
+
const outgoingNode = exiting ? nodes.get(exiting.screenId) : void 0;
|
|
7083
|
+
const mountedHere = txn.mounted.get(cmd.screenId);
|
|
7084
|
+
const direction = mountedHere === false ? "forward" : "back";
|
|
7085
|
+
const transition = cmd.transition;
|
|
7086
|
+
const batch = plan(transition, { incoming: incomingNode, outgoing: outgoingNode }, { direction });
|
|
7087
|
+
const normalized = typeof transition === "string" ? transition : "none";
|
|
7088
|
+
await host.setScreenVisible(cmd.screenId, true, incomingNode);
|
|
7089
|
+
stats.visibility.shown++;
|
|
7090
|
+
emit("visible", cmd.screenId, true);
|
|
7091
|
+
const hasAnims = batch.incoming.anims.length > 0 || batch.outgoing.anims.length > 0;
|
|
7092
|
+
if (hasAnims) {
|
|
7093
|
+
await anim.playRouteTransition(batch, { direction, transition: normalized });
|
|
7094
|
+
stats.transitions++;
|
|
7095
|
+
if (direction === "forward") stats.forward++;
|
|
7096
|
+
else stats.back++;
|
|
7097
|
+
emit("transition", cmd.screenId, { direction, transition: normalized, anims: batch.incoming.anims.length + batch.outgoing.anims.length });
|
|
7098
|
+
} else {
|
|
7099
|
+
stats.skippedNone++;
|
|
7100
|
+
emit("transition-skipped", cmd.screenId, { transition: normalized });
|
|
7101
|
+
}
|
|
7102
|
+
if (exiting) {
|
|
7103
|
+
if (exiting.destroy !== null) {
|
|
7104
|
+
await host.destroyScreen(exiting.screenId, exiting.destroy, outgoingNode);
|
|
7105
|
+
nodes.delete(exiting.screenId);
|
|
7106
|
+
stats.destroyed[exiting.destroy]++;
|
|
7107
|
+
emit("destroy", exiting.screenId, { reason: exiting.destroy, deferred: true });
|
|
7108
|
+
} else if (outgoingNode !== void 0) {
|
|
7109
|
+
await host.setScreenVisible(exiting.screenId, false, outgoingNode);
|
|
7110
|
+
stats.visibility.hidden++;
|
|
7111
|
+
emit("visible", exiting.screenId, false);
|
|
7112
|
+
}
|
|
7113
|
+
}
|
|
7114
|
+
txn = { exiting: null, mounted: /* @__PURE__ */ new Map() };
|
|
7115
|
+
return;
|
|
7116
|
+
}
|
|
7117
|
+
}
|
|
7118
|
+
}
|
|
7119
|
+
let queue = Promise.resolve();
|
|
7120
|
+
return {
|
|
7121
|
+
applyCommands(commands) {
|
|
7122
|
+
queue = queue.then(async () => {
|
|
7123
|
+
for (const c of commands) await runCommand(c);
|
|
7124
|
+
});
|
|
7125
|
+
return queue;
|
|
7126
|
+
},
|
|
7127
|
+
stats: () => JSON.parse(JSON.stringify(stats)),
|
|
7128
|
+
subtreeNode: (screenId) => nodes.get(screenId)
|
|
7129
|
+
};
|
|
7130
|
+
}
|
|
7131
|
+
|
|
7132
|
+
// src/screen-executor-host.ts
|
|
7133
|
+
var SCREEN_ANIM_DONE_KEY = "__proteusHostScreenAnimDone";
|
|
7134
|
+
var instSeq = 0;
|
|
7135
|
+
function call(channel, method, args) {
|
|
7136
|
+
const raw = channel(method, JSON.stringify(args ?? null));
|
|
7137
|
+
let parsed;
|
|
7138
|
+
try {
|
|
7139
|
+
parsed = JSON.parse(raw);
|
|
7140
|
+
} catch (e) {
|
|
7141
|
+
throw new Error(`[screen-host] ${method} \u56DE\u6267\u975E JSON\uFF1A${String(raw).slice(0, 120)}`);
|
|
7142
|
+
}
|
|
7143
|
+
if (parsed && parsed.ok === false) {
|
|
7144
|
+
throw new Error(`[screen-host] ${method} \u5931\u8D25${parsed.missing ? "\uFF08\u672A\u5B9E\u73B0\uFF09" : ""}\uFF1A${parsed.reason ?? "\u65E0\u539F\u56E0"}`);
|
|
7145
|
+
}
|
|
7146
|
+
return parsed && typeof parsed === "object" && "data" in parsed ? parsed.data : parsed;
|
|
7147
|
+
}
|
|
7148
|
+
function createHostScreenPorts(opts) {
|
|
7149
|
+
const channel = opts.invoke;
|
|
7150
|
+
const pending = /* @__PURE__ */ new Map();
|
|
7151
|
+
let tokenSeq = 0;
|
|
7152
|
+
let hookInstalled = false;
|
|
7153
|
+
const INSTANCE_ID = `s${++instSeq}-`;
|
|
7154
|
+
const resolveOne = (token, payload) => {
|
|
7155
|
+
const r = pending.get(token);
|
|
7156
|
+
if (!r) return false;
|
|
7157
|
+
pending.delete(token);
|
|
7158
|
+
r(payload);
|
|
7159
|
+
return true;
|
|
7160
|
+
};
|
|
7161
|
+
if (opts.installAnimDoneHook !== false) {
|
|
7162
|
+
const g = globalThis;
|
|
7163
|
+
const REGISTRY_KEY = `${SCREEN_ANIM_DONE_KEY}__registry`;
|
|
7164
|
+
let registry = g[REGISTRY_KEY];
|
|
7165
|
+
if (!Array.isArray(registry)) {
|
|
7166
|
+
registry = [];
|
|
7167
|
+
g[REGISTRY_KEY] = registry;
|
|
7168
|
+
}
|
|
7169
|
+
const handler = (token, resultJson) => {
|
|
7170
|
+
if (typeof token !== "string") return "bad-token";
|
|
7171
|
+
const r = registry;
|
|
7172
|
+
for (let i = 0; i < r.length; i++) {
|
|
7173
|
+
if (r[i](token, resultJson)) return "ok";
|
|
7174
|
+
}
|
|
7175
|
+
return "unknown-token";
|
|
7176
|
+
};
|
|
7177
|
+
registry.push((token, payload) => resolveOne(token, payload));
|
|
7178
|
+
try {
|
|
7179
|
+
g[SCREEN_ANIM_DONE_KEY] = handler;
|
|
7180
|
+
hookInstalled = true;
|
|
7181
|
+
} catch {
|
|
7182
|
+
try {
|
|
7183
|
+
Object.defineProperty(g, SCREEN_ANIM_DONE_KEY, { value: handler, writable: true, configurable: true });
|
|
7184
|
+
hookInstalled = true;
|
|
7185
|
+
} catch {
|
|
7186
|
+
}
|
|
7187
|
+
}
|
|
7188
|
+
}
|
|
7189
|
+
const tree = {
|
|
7190
|
+
mountScreen(screen) {
|
|
7191
|
+
const d = call(channel, "screen.mount", {
|
|
7192
|
+
screenId: screen.screenId,
|
|
7193
|
+
name: screen.name,
|
|
7194
|
+
path: screen.path,
|
|
7195
|
+
params: screen.params,
|
|
7196
|
+
rebuild: screen.rebuild,
|
|
7197
|
+
// ★★★GP3-c:**三层容器计划**随 mount 下发(宿主照此建树——偏移量,宿主加到自己分配的根 id 上)。
|
|
7198
|
+
// 执行器已按契约算好(见 screen-executor.ts);本层只做**透传**(不再重复计算——
|
|
7199
|
+
// 本仓纪律:同一件事两份实现 = 修一份等于没修)。
|
|
7200
|
+
...screen.layerContainers ? { layerContainers: screen.layerContainers } : {},
|
|
7201
|
+
// ★★★阶段 1(2026-10-04 · App 三端对齐 B1+B2):**屏内容**(真实页面渲染产物)透传。
|
|
7202
|
+
// 执行器按 `contentOf` 提供者解析后放入 mountScreen 入参;本层只做转发(与上方同款纪律)。
|
|
7203
|
+
...screen.content ? { content: screen.content } : {}
|
|
7204
|
+
});
|
|
7205
|
+
const rootNodeId = Number(d?.rootNodeId ?? 0);
|
|
7206
|
+
if (!rootNodeId) {
|
|
7207
|
+
throw new Error(`[screen-host] screen.mount \u672A\u8FD4\u56DE rootNodeId\uFF08${JSON.stringify(d)}\uFF09\u2014\u2014\u5BBF\u4E3B\u5B9E\u73B0\u4E0D\u5B8C\u6574`);
|
|
7208
|
+
}
|
|
7209
|
+
return rootNodeId;
|
|
7210
|
+
},
|
|
7211
|
+
setScreenVisible(screenId, visible, rootNodeId) {
|
|
7212
|
+
call(channel, "screen.visible", { screenId, visible, rootNodeId });
|
|
7213
|
+
},
|
|
7214
|
+
destroyScreen(screenId, reason, rootNodeId) {
|
|
7215
|
+
call(channel, "screen.destroy", { screenId, reason, rootNodeId });
|
|
7216
|
+
}
|
|
7217
|
+
};
|
|
7218
|
+
const anim = {
|
|
7219
|
+
playRouteTransition(plan, ctx) {
|
|
7220
|
+
const anims = [...plan.incoming.anims, ...plan.outgoing.anims];
|
|
7221
|
+
if (anims.length === 0) return;
|
|
7222
|
+
const token = `${INSTANCE_ID}screen-anim-${++tokenSeq}`;
|
|
7223
|
+
const d = call(channel, "screen.anim", {
|
|
7224
|
+
anims,
|
|
7225
|
+
durationMs: plan.durationMs,
|
|
7226
|
+
direction: ctx.direction,
|
|
7227
|
+
transition: ctx.transition,
|
|
7228
|
+
token
|
|
7229
|
+
});
|
|
7230
|
+
if (d?.immediate === true) return;
|
|
7231
|
+
return new Promise((resolve) => {
|
|
7232
|
+
pending.set(token, () => resolve());
|
|
7233
|
+
});
|
|
7234
|
+
}
|
|
7235
|
+
};
|
|
7236
|
+
return {
|
|
7237
|
+
tree,
|
|
7238
|
+
anim,
|
|
7239
|
+
shared: {
|
|
7240
|
+
async rect(screenId, nodeId) {
|
|
7241
|
+
const d = call(channel, "screen.rect", { screenId, nodeId });
|
|
7242
|
+
if (!d || typeof d.x !== "number") {
|
|
7243
|
+
throw new Error(`[screen-host] screen.rect \u672A\u8FD4\u56DE\u51E0\u4F55\uFF08${JSON.stringify(d)}\uFF09`);
|
|
7244
|
+
}
|
|
7245
|
+
return { x: d.x, y: d.y ?? 0, w: d.width ?? 0, h: d.height ?? 0 };
|
|
7246
|
+
},
|
|
7247
|
+
fly(opts2) {
|
|
7248
|
+
const token = `screen-shared-${++tokenSeq}`;
|
|
7249
|
+
const d = call(channel, "screen.shared", { ...opts2, token });
|
|
7250
|
+
if (!d || (d.started ?? 0) < 1) {
|
|
7251
|
+
return Promise.reject(new Error(`[screen-host] screen.shared \u672A\u542F\u52A8\uFF08${JSON.stringify(d)}\uFF09`));
|
|
7252
|
+
}
|
|
7253
|
+
const geom = { fromRect: d.fromRect, toRect: d.toRect };
|
|
7254
|
+
return new Promise((resolve) => {
|
|
7255
|
+
pending.set(token, (v) => {
|
|
7256
|
+
let extra = {};
|
|
7257
|
+
if (typeof v === "string") {
|
|
7258
|
+
try {
|
|
7259
|
+
const o = JSON.parse(v);
|
|
7260
|
+
if (o && typeof o === "object") extra = o;
|
|
7261
|
+
} catch {
|
|
7262
|
+
}
|
|
7263
|
+
} else if (v && typeof v === "object") {
|
|
7264
|
+
extra = v;
|
|
7265
|
+
}
|
|
7266
|
+
resolve({ ...geom, ...extra });
|
|
7267
|
+
});
|
|
7268
|
+
});
|
|
7269
|
+
}
|
|
7270
|
+
},
|
|
7271
|
+
get pendingAnimations() {
|
|
7272
|
+
return pending.size;
|
|
7273
|
+
},
|
|
7274
|
+
get animDoneHookInstalled() {
|
|
7275
|
+
return hookInstalled;
|
|
7276
|
+
}
|
|
7277
|
+
};
|
|
7278
|
+
}
|
|
7279
|
+
|
|
7280
|
+
// src/screen-runtime.ts
|
|
7281
|
+
function createScreenRuntime(opts) {
|
|
7282
|
+
const base = opts.contentIdBase;
|
|
7283
|
+
const instances = /* @__PURE__ */ new Map();
|
|
7284
|
+
const note = opts.onNote ?? (() => {
|
|
7285
|
+
});
|
|
7286
|
+
function make(name) {
|
|
7287
|
+
const art = opts.artifacts[name];
|
|
7288
|
+
if (!art) throw new Error(`[screen-runtime] \u65E0\u8BE5\u5C4F\u4EA7\u7269\uFF1A${name}`);
|
|
7289
|
+
const data = { ...art.data, ...opts.seedData?.[name] ?? {} };
|
|
7290
|
+
const read = (n) => data[n];
|
|
7291
|
+
let inst = instantiateTemplate(art.tpl, { viewport: opts.viewport, read, table: art.table, registry: new ListRegistry() });
|
|
7292
|
+
let localIdsOrdered = inst.nodes.map((n) => n.id);
|
|
7293
|
+
function rebuild() {
|
|
7294
|
+
inst = instantiateTemplate(art.tpl, { viewport: opts.viewport, read, table: art.table, registry: new ListRegistry() });
|
|
7295
|
+
localIdsOrdered = inst.nodes.map((n) => n.id);
|
|
7296
|
+
}
|
|
7297
|
+
if (inst.stats.componentNodes === 0 && inst.nodes.some((n) => n.component)) {
|
|
7298
|
+
note(`[screen-runtime] ${name}: \u6A21\u677F\u542B\u7EC4\u4EF6\u8FB9\u754C\u4F46\u672C\u7248\u672A\u5C55\u5F00\uFF08\u8BDA\u5B9E\u8FB9\u754C\uFF09`);
|
|
7299
|
+
}
|
|
7300
|
+
const keys = new PropKeyTable();
|
|
7301
|
+
const strings = new StringPool();
|
|
7302
|
+
const slotRt = new SlotRuntime(keys, strings, (bytes) => {
|
|
7303
|
+
opts.applyOps(JSON.stringify(Array.from(bytes)));
|
|
7304
|
+
});
|
|
7305
|
+
const vapor = new VaporRuntime(art.table, slotRt, VaporRuntime.buildEvaluators(art.table.evaluators), new ListRegistry());
|
|
7306
|
+
const evalCtx = { read };
|
|
7307
|
+
vapor.load(evalCtx, () => {
|
|
7308
|
+
});
|
|
7309
|
+
vapor.relink(evalCtx);
|
|
7310
|
+
slotRt.flush();
|
|
7311
|
+
const byNodeEvent = indexEventBindings(art.events);
|
|
7312
|
+
const dispatchState = createDispatchState();
|
|
7313
|
+
function runHandler(handlerName, _nodeId, payload) {
|
|
7314
|
+
const acts = art.handlers[handlerName];
|
|
7315
|
+
if (!acts) return false;
|
|
7316
|
+
for (const a of acts) {
|
|
7317
|
+
if (a.op === "emit") {
|
|
7318
|
+
note(`[screen-runtime] ${name}: handler\u300C${handlerName}\u300D\u542B $emit\u2014\u2014\u672C\u7248\u65E0\u53BB\u5904\uFF08\u5DF2\u5FFD\u7565\uFF09`);
|
|
7319
|
+
continue;
|
|
7320
|
+
}
|
|
7321
|
+
if (a.op === "nav") {
|
|
7322
|
+
const tgt = a.target;
|
|
7323
|
+
if (tgt && opts.navigate) opts.navigate(tgt);
|
|
7324
|
+
else note(`[screen-runtime] ${name}: $nav('${tgt ?? ""}') \u65E0 navigate \u51FA\u53E3\uFF08\u672A\u88C5\u914D\uFF09`);
|
|
7325
|
+
continue;
|
|
7326
|
+
}
|
|
7327
|
+
const ctx2 = { read: (n) => n === "$event" ? payload : data[n] };
|
|
7328
|
+
const v = evalExpr(a.program, ctx2);
|
|
7329
|
+
if (!a.source) continue;
|
|
7330
|
+
if (a.op === "add") {
|
|
7331
|
+
const cur = data[a.source];
|
|
7332
|
+
data[a.source] = (typeof cur === "number" ? cur : 0) + (typeof v === "number" ? v : 0);
|
|
7333
|
+
} else if (a.op === "set") {
|
|
7334
|
+
data[a.source] = v;
|
|
7335
|
+
}
|
|
7336
|
+
}
|
|
7337
|
+
return true;
|
|
7338
|
+
}
|
|
7339
|
+
function localIdOf(kernelId) {
|
|
7340
|
+
if (typeof base !== "number") return kernelId;
|
|
7341
|
+
const idx = kernelId - base;
|
|
7342
|
+
return idx >= 0 && idx < localIdsOrdered.length ? localIdsOrdered[idx] : kernelId;
|
|
7343
|
+
}
|
|
7344
|
+
function refreshData() {
|
|
7345
|
+
rebuild();
|
|
7346
|
+
vapor.relink(evalCtx);
|
|
7347
|
+
slotRt.flush();
|
|
7348
|
+
}
|
|
7349
|
+
return {
|
|
7350
|
+
content() {
|
|
7351
|
+
return { viewport: inst.viewport, nodes: inst.nodes };
|
|
7352
|
+
},
|
|
7353
|
+
dispatch(type, chain) {
|
|
7354
|
+
const localChain = chain.map(localIdOf);
|
|
7355
|
+
const r = dispatchGesture(localChain, type, byNodeEvent, dispatchState, (h, id) => runHandler(h, id));
|
|
7356
|
+
if (r.fired.length > 0) refreshData();
|
|
7357
|
+
return { handled: r.fired.length > 0, fired: r.fired };
|
|
7358
|
+
},
|
|
7359
|
+
flush() {
|
|
7360
|
+
slotRt.flush();
|
|
7361
|
+
},
|
|
7362
|
+
refresh() {
|
|
7363
|
+
refreshData();
|
|
7364
|
+
},
|
|
7365
|
+
data() {
|
|
7366
|
+
return data;
|
|
7367
|
+
},
|
|
7368
|
+
snapshot() {
|
|
7369
|
+
return { ...data };
|
|
7370
|
+
}
|
|
7371
|
+
};
|
|
7372
|
+
}
|
|
7373
|
+
return {
|
|
7374
|
+
has: (name) => Boolean(opts.artifacts[name]),
|
|
7375
|
+
instance(name) {
|
|
7376
|
+
let it = instances.get(name);
|
|
7377
|
+
if (!it) {
|
|
7378
|
+
it = make(name);
|
|
7379
|
+
instances.set(name, it);
|
|
7380
|
+
}
|
|
7381
|
+
return it;
|
|
7382
|
+
},
|
|
7383
|
+
/** ★跨调用状态导出(每屏数据浅拷贝)——一次性 VM 宿主持有、下次回灌(见 seedData) */
|
|
7384
|
+
snapshot() {
|
|
7385
|
+
const out = {};
|
|
7386
|
+
for (const [name, it] of instances) out[name] = it.snapshot();
|
|
7387
|
+
return out;
|
|
7388
|
+
}
|
|
7389
|
+
};
|
|
7390
|
+
}
|
|
7391
|
+
|
|
7392
|
+
// src/host-invoke-contract.ts
|
|
7393
|
+
var HOST_SCREEN_METHODS = [
|
|
7394
|
+
"screen.mount",
|
|
7395
|
+
"screen.visible",
|
|
7396
|
+
"screen.destroy",
|
|
7397
|
+
"screen.anim",
|
|
7398
|
+
"screen.rect",
|
|
7399
|
+
"screen.shared",
|
|
7400
|
+
"screen.stats"
|
|
7401
|
+
];
|
|
7402
|
+
var HOST_CAPABILITY_METHODS = [
|
|
7403
|
+
// 宿主上下文 / 热更新 / 窗口
|
|
7404
|
+
"host.context",
|
|
7405
|
+
"update.check",
|
|
7406
|
+
"update.apply",
|
|
7407
|
+
"window.setSize",
|
|
7408
|
+
// Worker(G-39 后台线程)
|
|
7409
|
+
"worker.create",
|
|
7410
|
+
"worker.post",
|
|
7411
|
+
"worker.terminate",
|
|
7412
|
+
// 空闲回调 / 预加载 / 扩展
|
|
7413
|
+
"idle.request",
|
|
7414
|
+
"idle.cancel",
|
|
7415
|
+
"preload.assets",
|
|
7416
|
+
"extension.load",
|
|
7417
|
+
// 跳其他小程序(App 端对齐 Web:通常无对等)
|
|
7418
|
+
"mini-program.navigate",
|
|
7419
|
+
// WebAssembly(宿主运行时;★引擎内置 WASM 的端可不实现——见 HOST_METHOD_OPTIONAL)
|
|
7420
|
+
"webassembly.instantiate",
|
|
7421
|
+
"webassembly.validate",
|
|
7422
|
+
"webassembly.call",
|
|
7423
|
+
"webassembly.release",
|
|
7424
|
+
// 宿主自报调用记账(判据"能力经壳执行")
|
|
7425
|
+
"native.calls"
|
|
7426
|
+
];
|
|
7427
|
+
var HOST_METHOD_OPTIONAL = {
|
|
7428
|
+
// iOS JSC 内建 WebAssembly(`typeof WebAssembly === 'object'`)⇒ 壳不实现 `webassembly.*`。
|
|
7429
|
+
ios: ["webassembly.instantiate", "webassembly.validate", "webassembly.call", "webassembly.release"]
|
|
7430
|
+
// 鸿蒙:能力族尚未接线(memUsage/gc 之外)——★**缺失已登记**(非合法省略;门禁以"覆盖率"报告,不静默)。
|
|
7431
|
+
};
|
|
7432
|
+
var HOST_INVOKE_METHODS = [...HOST_CAPABILITY_METHODS, ...HOST_SCREEN_METHODS];
|
|
7433
|
+
|
|
7434
|
+
// src/tab-bar-spec.ts
|
|
7435
|
+
var TAB_BAR_SPEC = {
|
|
7436
|
+
height: 56,
|
|
7437
|
+
surface: "#ffffff",
|
|
7438
|
+
line: "#dcdfe5",
|
|
7439
|
+
brand: "#5b5bd6",
|
|
7440
|
+
text3: "#5f6673",
|
|
7441
|
+
badgeBg: "#d64545",
|
|
7442
|
+
iconSize: 19,
|
|
7443
|
+
labelSize: 10,
|
|
7444
|
+
badge: { radius: 8, fontSize: 11, offsetX: 4, offsetY: 4, padX: 4 },
|
|
7445
|
+
icons: { index: "\u2302", messages: "\u2709", mine: "\u263A" },
|
|
7446
|
+
labels: { index: "\u9996\u9875", messages: "\u6D88\u606F", mine: "\u6211\u7684" }
|
|
7447
|
+
};
|
|
7448
|
+
|
|
7449
|
+
// src/superapp-runtime.ts
|
|
7450
|
+
function createSuperappRuntime(opts) {
|
|
7451
|
+
const note = opts.onNote ?? (() => {
|
|
7452
|
+
});
|
|
7453
|
+
const cbName = opts.gestureCbName ?? "__proteusRuntimeGesture";
|
|
7454
|
+
const rt = createScreenRuntime({
|
|
7455
|
+
artifacts: opts.artifacts,
|
|
7456
|
+
applyOps: (ops) => {
|
|
7457
|
+
opts.host.applyOps(ops);
|
|
7458
|
+
},
|
|
7459
|
+
viewport: opts.viewport,
|
|
7460
|
+
...opts.navigate ? { navigate: opts.navigate } : {},
|
|
7461
|
+
...opts.seedData ? { seedData: opts.seedData } : {},
|
|
7462
|
+
onNote: note
|
|
7463
|
+
});
|
|
7464
|
+
let cur = "";
|
|
7465
|
+
let lastHostReply = null;
|
|
7466
|
+
if (typeof opts.host.onGesture === "function") {
|
|
7467
|
+
opts.host.onGesture(cbName);
|
|
7468
|
+
}
|
|
7469
|
+
;
|
|
7470
|
+
globalThis[cbName] = (type, _kernelId, chainJson) => {
|
|
7471
|
+
let chain = [];
|
|
7472
|
+
try {
|
|
7473
|
+
chain = chainJson ? JSON.parse(chainJson) : [];
|
|
7474
|
+
} catch {
|
|
7475
|
+
note(`[superapp-runtime] \u624B\u52BF\u94FE JSON \u89E3\u6790\u5931\u8D25\uFF1A${String(chainJson).slice(0, 40)}`);
|
|
7476
|
+
}
|
|
7477
|
+
const r = dispatch(type, chain);
|
|
7478
|
+
return JSON.stringify(r);
|
|
7479
|
+
};
|
|
7480
|
+
function hostOk(reply) {
|
|
7481
|
+
try {
|
|
7482
|
+
const o = JSON.parse(reply);
|
|
7483
|
+
return o?.ok !== false;
|
|
7484
|
+
} catch {
|
|
7485
|
+
return true;
|
|
7486
|
+
}
|
|
7487
|
+
}
|
|
7488
|
+
function dispatch(type, chain) {
|
|
7489
|
+
if (!cur) return { handled: false, fired: [] };
|
|
7490
|
+
return rt.instance(cur).dispatch(type, chain);
|
|
7491
|
+
}
|
|
7492
|
+
const scrollByScreen = /* @__PURE__ */ new Map();
|
|
7493
|
+
const history = [];
|
|
7494
|
+
let curIdx = -1;
|
|
7495
|
+
return {
|
|
7496
|
+
mountScreen(name) {
|
|
7497
|
+
if (!rt.has(name)) {
|
|
7498
|
+
note(`[superapp-runtime] \u65E0\u8BE5\u5C4F\u8FD0\u884C\u671F\u4EA7\u7269\uFF1A${name}`);
|
|
7499
|
+
return false;
|
|
7500
|
+
}
|
|
7501
|
+
if (name === cur) return true;
|
|
7502
|
+
if (cur && typeof opts.host.getScroll === "function") {
|
|
7503
|
+
scrollByScreen.set(cur, opts.host.getScroll());
|
|
7504
|
+
}
|
|
7505
|
+
const existing = history.indexOf(name);
|
|
7506
|
+
let restore = 0;
|
|
7507
|
+
if (existing >= 0 && existing < curIdx) {
|
|
7508
|
+
history.length = existing + 1;
|
|
7509
|
+
curIdx = existing;
|
|
7510
|
+
restore = scrollByScreen.get(name) ?? 0;
|
|
7511
|
+
} else {
|
|
7512
|
+
history.length = curIdx + 1;
|
|
7513
|
+
history.push(name);
|
|
7514
|
+
curIdx = history.length - 1;
|
|
7515
|
+
scrollByScreen.set(name, 0);
|
|
7516
|
+
restore = 0;
|
|
7517
|
+
}
|
|
7518
|
+
const inst = rt.instance(name);
|
|
7519
|
+
const reply = opts.host.mount(JSON.stringify({ viewport: inst.content().viewport, nodes: inst.content().nodes }));
|
|
7520
|
+
lastHostReply = reply;
|
|
7521
|
+
if (!hostOk(reply)) {
|
|
7522
|
+
note(`[superapp-runtime] \u5BBF\u4E3B mount \u5931\u8D25\uFF1A${String(reply).slice(0, 200)}`);
|
|
7523
|
+
return false;
|
|
7524
|
+
}
|
|
7525
|
+
if (typeof opts.host.setScroll === "function") opts.host.setScroll(restore);
|
|
7526
|
+
cur = name;
|
|
7527
|
+
return true;
|
|
7528
|
+
},
|
|
7529
|
+
mountScreenInto(name) {
|
|
7530
|
+
if (!rt.has(name)) {
|
|
7531
|
+
note(`[superapp-runtime] \u65E0\u8BE5\u5C4F\u8FD0\u884C\u671F\u4EA7\u7269\uFF1A${name}`);
|
|
7532
|
+
return false;
|
|
7533
|
+
}
|
|
7534
|
+
const inst = rt.instance(name);
|
|
7535
|
+
const reply = opts.host.mount(JSON.stringify({ viewport: inst.content().viewport, nodes: inst.content().nodes }));
|
|
7536
|
+
lastHostReply = reply;
|
|
7537
|
+
if (!hostOk(reply)) {
|
|
7538
|
+
note(`[superapp-runtime] \u5BBF\u4E3B mount\uFF08\u91CD\u6302\uFF09\u5931\u8D25\uFF1A${String(reply).slice(0, 200)}`);
|
|
7539
|
+
return false;
|
|
7540
|
+
}
|
|
7541
|
+
cur = name;
|
|
7542
|
+
return true;
|
|
7543
|
+
},
|
|
7544
|
+
dispatchGesture: dispatch,
|
|
7545
|
+
current: () => cur,
|
|
7546
|
+
snapshot: () => rt.snapshot(),
|
|
7547
|
+
lastHostReply: () => lastHostReply
|
|
7548
|
+
};
|
|
7549
|
+
}
|
|
4569
7550
|
export {
|
|
4570
7551
|
Borrow,
|
|
4571
7552
|
CONTAINER_PROFILES,
|
|
@@ -4575,7 +7556,11 @@ export {
|
|
|
4575
7556
|
FIVE_ATOMIC_STEPS,
|
|
4576
7557
|
FORK_SIGNATURES,
|
|
4577
7558
|
HOSTS,
|
|
7559
|
+
HOST_CAPABILITY_METHODS,
|
|
4578
7560
|
HOST_ENGINE_MATRIX,
|
|
7561
|
+
HOST_INVOKE_METHODS,
|
|
7562
|
+
HOST_METHOD_OPTIONAL,
|
|
7563
|
+
HOST_SCREEN_METHODS,
|
|
4579
7564
|
Managed,
|
|
4580
7565
|
ManagedRegistry,
|
|
4581
7566
|
OWNERSHIP_ERRORS,
|
|
@@ -4583,7 +7568,9 @@ export {
|
|
|
4583
7568
|
OwnershipError,
|
|
4584
7569
|
OwnershipGraph,
|
|
4585
7570
|
PAGE_STATE_TRANSITIONS,
|
|
7571
|
+
SCREEN_ANIM_DONE_KEY,
|
|
4586
7572
|
SENSITIVE_CAPABILITIES,
|
|
7573
|
+
TAB_BAR_SPEC,
|
|
4587
7574
|
Weak,
|
|
4588
7575
|
analyzeOwnershipSource,
|
|
4589
7576
|
analyzePss,
|
|
@@ -4601,6 +7588,7 @@ export {
|
|
|
4601
7588
|
createFlutterBackend,
|
|
4602
7589
|
createHeadlessBackend,
|
|
4603
7590
|
createHostRuntimeStub,
|
|
7591
|
+
createHostScreenPorts,
|
|
4604
7592
|
createHybridRenderer,
|
|
4605
7593
|
createMiniProgramContainer,
|
|
4606
7594
|
createMockNativeAdapter,
|
|
@@ -4611,12 +7599,17 @@ export {
|
|
|
4611
7599
|
createPageOwnership,
|
|
4612
7600
|
createProteusRenderer,
|
|
4613
7601
|
createProteusRendererForBackend,
|
|
7602
|
+
createQuickJsHostRuntime,
|
|
4614
7603
|
createQuotaManager,
|
|
4615
7604
|
createQuotaTracker,
|
|
4616
7605
|
createResourcePool,
|
|
7606
|
+
createScreenExecutor,
|
|
7607
|
+
createScreenRuntime,
|
|
7608
|
+
createSelfDrawBatchAdapter,
|
|
4617
7609
|
createSinglePageContainer,
|
|
4618
7610
|
createStackContainer,
|
|
4619
7611
|
createSuperAppContainer,
|
|
7612
|
+
createSuperappRuntime,
|
|
4620
7613
|
createVueDomBackend,
|
|
4621
7614
|
createVueRendererOptions,
|
|
4622
7615
|
createWebHostRuntime,
|
|
@@ -4642,6 +7635,7 @@ export {
|
|
|
4642
7635
|
runHybridConformance,
|
|
4643
7636
|
runPss,
|
|
4644
7637
|
scanRepoForFork,
|
|
7638
|
+
screenContentFromLayoutTemplate,
|
|
4645
7639
|
semanticSequence,
|
|
4646
7640
|
textureRef,
|
|
4647
7641
|
toPlainTree,
|