@weapp-vite/miniprogram-automator 1.0.1 → 1.0.3

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.
@@ -27,7 +27,12 @@ function createAppInstance(definition) {
27
27
  return instance;
28
28
  }
29
29
  //#endregion
30
- //#region ../../mpcore/packages/simulator/src/runtime/componentInstance.ts
30
+ //#region ../../mpcore/packages/simulator/src/runtime/componentInstance/shared.ts
31
+ const ARRAY_INDEX_PATH_RE$1 = /\[(\d+)\]/g;
32
+ const ARRAY_INDEX_SEGMENT_RE$1 = /^\d+$/;
33
+ function cloneObject$1(value) {
34
+ return JSON.parse(JSON.stringify(value));
35
+ }
31
36
  function bindFunction$1(target, key, value) {
32
37
  if (typeof value !== "function") {
33
38
  target[key] = value;
@@ -35,19 +40,19 @@ function bindFunction$1(target, key, value) {
35
40
  }
36
41
  target[key] = (...args) => value.apply(target, args);
37
42
  }
38
- function cloneObject$1(value) {
39
- return JSON.parse(JSON.stringify(value));
40
- }
41
43
  function cloneValue$1(value) {
42
44
  if (Array.isArray(value)) return JSON.parse(JSON.stringify(value));
43
45
  if (value && typeof value === "object") return cloneObject$1(value);
44
46
  return value;
45
47
  }
48
+ function cloneRecord(value) {
49
+ return cloneObject$1(value);
50
+ }
46
51
  function parseDataPath$1(path) {
47
- return path.replace(/\[(\d+)\]/g, ".$1").split(".").map((segment) => segment.trim()).filter(Boolean);
52
+ return path.replace(ARRAY_INDEX_PATH_RE$1, ".$1").split(".").map((segment) => segment.trim()).filter(Boolean);
48
53
  }
49
54
  function isArrayIndexSegment$1(segment) {
50
- return /^\d+$/.test(segment);
55
+ return ARRAY_INDEX_SEGMENT_RE$1.test(segment);
51
56
  }
52
57
  function createContainerByNextSegment$1(nextSegment) {
53
58
  return isArrayIndexSegment$1(nextSegment ?? "") ? [] : {};
@@ -64,17 +69,74 @@ function assignByPath$1(target, path, value) {
64
69
  if (!next || typeof next !== "object") current[normalizedSegment] = createContainerByNextSegment$1(nextSegment);
65
70
  current = current[normalizedSegment];
66
71
  }
67
- const leafSegment = segments[segments.length - 1];
72
+ const leafSegment = segments.at(-1);
68
73
  const normalizedLeafSegment = isArrayIndexSegment$1(leafSegment) ? Number(leafSegment) : leafSegment;
69
74
  current[normalizedLeafSegment] = value;
70
75
  }
76
+ function mergeRecord(...records) {
77
+ return Object.assign({}, ...records.filter(Boolean));
78
+ }
79
+ //#endregion
80
+ //#region ../../mpcore/packages/simulator/src/runtime/componentInstance/observers.ts
81
+ function runPropertyObservers(definition, instance, changedKeys, previousProperties) {
82
+ const propOptions = definition.properties;
83
+ if (!propOptions || typeof propOptions !== "object" || Array.isArray(propOptions)) return;
84
+ for (const changedKey of changedKeys) {
85
+ const option = propOptions[changedKey];
86
+ if (!option || typeof option !== "object" || Array.isArray(option)) continue;
87
+ const observer = option.observer;
88
+ if (typeof observer !== "function") continue;
89
+ observer.call(instance, instance.properties[changedKey], previousProperties[changedKey]);
90
+ }
91
+ }
92
+ function normalizeObserverPattern(pattern) {
93
+ return pattern.split(",").map((item) => item.trim()).filter(Boolean);
94
+ }
95
+ function resolveObservedValue(instance, pattern) {
96
+ if (pattern === "**") return {
97
+ ...instance.properties,
98
+ ...instance.data
99
+ };
100
+ const segments = parseDataPath$1(pattern);
101
+ if (segments.length === 0) return;
102
+ const [rootSegment, ...restSegments] = segments;
103
+ let current = (Object.hasOwn(instance.properties, rootSegment) ? instance.properties : instance.data)?.[rootSegment];
104
+ for (const segment of restSegments) {
105
+ const normalizedSegment = isArrayIndexSegment$1(segment) ? Number(segment) : segment;
106
+ current = current?.[normalizedSegment];
107
+ }
108
+ return current;
109
+ }
110
+ function matchObserverPattern(pattern, changedKeys) {
111
+ if (pattern === "**") return changedKeys.length > 0;
112
+ const patterns = normalizeObserverPattern(pattern);
113
+ return changedKeys.some((changedKey) => {
114
+ return patterns.some((item) => {
115
+ if (item === "**") return true;
116
+ if (changedKey === item) return true;
117
+ return changedKey.startsWith(`${item}.`) || changedKey.startsWith(`${item}[`);
118
+ });
119
+ });
120
+ }
121
+ function runComponentObservers(definition, instance, changedKeys, previousProperties = {}) {
122
+ if (changedKeys.length === 0) return;
123
+ runPropertyObservers(definition, instance, changedKeys, previousProperties);
124
+ const observers = definition.observers;
125
+ if (observers && typeof observers === "object" && !Array.isArray(observers)) for (const [pattern, handler] of Object.entries(observers)) {
126
+ if (typeof handler !== "function" || !matchObserverPattern(pattern, changedKeys)) continue;
127
+ const args = normalizeObserverPattern(pattern).map((item) => resolveObservedValue(instance, item));
128
+ handler.call(instance, ...args);
129
+ }
130
+ }
131
+ //#endregion
132
+ //#region ../../mpcore/packages/simulator/src/runtime/componentInstance/properties.ts
71
133
  function resolveInitialData$1(definition) {
72
134
  const rawData = definition.data;
73
135
  if (typeof rawData === "function") {
74
136
  const next = rawData.call(definition);
75
- return next && typeof next === "object" && !Array.isArray(next) ? cloneObject$1(next) : {};
137
+ return next && typeof next === "object" && !Array.isArray(next) ? cloneRecord(next) : {};
76
138
  }
77
- return rawData && typeof rawData === "object" && !Array.isArray(rawData) ? cloneObject$1(rawData) : {};
139
+ return rawData && typeof rawData === "object" && !Array.isArray(rawData) ? cloneRecord(rawData) : {};
78
140
  }
79
141
  function isBehaviorDefinition(value) {
80
142
  return !!value && typeof value === "object" && !Array.isArray(value) && value.__isHeadlessBehavior__ === true;
@@ -89,9 +151,6 @@ function flattenBehaviors(definition) {
89
151
  }
90
152
  return flattened;
91
153
  }
92
- function mergeRecord(...records) {
93
- return Object.assign({}, ...records.filter(Boolean));
94
- }
95
154
  function normalizeComponentDefinition(definition) {
96
155
  const behaviors = flattenBehaviors(definition);
97
156
  if (behaviors.length === 0) return definition;
@@ -184,7 +243,7 @@ function resolveInitialProperties(definition, properties) {
184
243
  const resolved = {};
185
244
  const propOptions = definition.properties;
186
245
  if (propOptions && typeof propOptions === "object" && !Array.isArray(propOptions)) for (const [key, option] of Object.entries(propOptions)) {
187
- if (Object.prototype.hasOwnProperty.call(properties, key)) {
246
+ if (Object.hasOwn(properties, key)) {
188
247
  resolved[key] = coerceComponentPropertyValue(properties[key], option);
189
248
  continue;
190
249
  }
@@ -198,61 +257,13 @@ function resolvePropertyDefaultValue(option) {
198
257
  if (!option || typeof option !== "object" || Array.isArray(option) || !("value" in option)) return;
199
258
  return cloneValue$1(option.value);
200
259
  }
201
- function runPropertyObservers(definition, instance, changedKeys, previousProperties) {
202
- const propOptions = definition.properties;
203
- if (!propOptions || typeof propOptions !== "object" || Array.isArray(propOptions)) return;
204
- for (const changedKey of changedKeys) {
205
- const option = propOptions[changedKey];
206
- if (!option || typeof option !== "object" || Array.isArray(option)) continue;
207
- const observer = option.observer;
208
- if (typeof observer !== "function") continue;
209
- observer.call(instance, instance.properties[changedKey], previousProperties[changedKey]);
210
- }
211
- }
212
- function normalizeObserverPattern(pattern) {
213
- return pattern.split(",").map((item) => item.trim()).filter(Boolean);
214
- }
215
- function resolveObservedValue(instance, pattern) {
216
- if (pattern === "**") return {
217
- ...instance.properties,
218
- ...instance.data
219
- };
220
- const segments = parseDataPath$1(pattern);
221
- if (segments.length === 0) return;
222
- const [rootSegment, ...restSegments] = segments;
223
- let current = (Object.prototype.hasOwnProperty.call(instance.properties, rootSegment) ? instance.properties : instance.data)?.[rootSegment];
224
- for (const segment of restSegments) {
225
- const normalizedSegment = isArrayIndexSegment$1(segment) ? Number(segment) : segment;
226
- current = current?.[normalizedSegment];
227
- }
228
- return current;
229
- }
230
- function matchObserverPattern(pattern, changedKeys) {
231
- if (pattern === "**") return changedKeys.length > 0;
232
- const patterns = normalizeObserverPattern(pattern);
233
- return changedKeys.some((changedKey) => {
234
- return patterns.some((item) => {
235
- if (item === "**") return true;
236
- if (changedKey === item) return true;
237
- return changedKey.startsWith(`${item}.`) || changedKey.startsWith(`${item}[`);
238
- });
239
- });
240
- }
241
- function runComponentObservers(definition, instance, changedKeys, previousProperties = {}) {
242
- if (changedKeys.length === 0) return;
243
- runPropertyObservers(definition, instance, changedKeys, previousProperties);
244
- const observers = definition.observers;
245
- if (observers && typeof observers === "object" && !Array.isArray(observers)) for (const [pattern, handler] of Object.entries(observers)) {
246
- if (typeof handler !== "function" || !matchObserverPattern(pattern, changedKeys)) continue;
247
- const args = normalizeObserverPattern(pattern).map((item) => resolveObservedValue(instance, item));
248
- handler.call(instance, ...args);
249
- }
250
- }
251
260
  function normalizeComponentPropertyValue(definition, key, rawValue) {
252
261
  const option = definition.properties?.[key];
253
262
  if (rawValue == null) return resolvePropertyDefaultValue(option);
254
263
  return coerceComponentPropertyValue(rawValue, option);
255
264
  }
265
+ //#endregion
266
+ //#region ../../mpcore/packages/simulator/src/runtime/componentInstance/index.ts
256
267
  function createComponentInstance(options) {
257
268
  const definition = normalizeComponentDefinition(options.definition);
258
269
  const instance = {
@@ -2828,9 +2839,11 @@ function parseDocument(data, options) {
2828
2839
  return handler.root;
2829
2840
  }
2830
2841
  //#endregion
2831
- //#region ../../mpcore/packages/simulator/src/runtime/render.ts
2832
- const LEADING_SLASH_RE$4 = /^\/+/;
2842
+ //#region ../../mpcore/packages/simulator/src/runtime/render/shared.ts
2833
2843
  const TEMPLATE_INTERPOLATION_RE$1 = /\{\{([^{}]+)\}\}/g;
2844
+ const DATASET_NAME_RE$3 = /-([a-z])/g;
2845
+ const BRACKET_INDEX_RE = /\[(\d+)\]/g;
2846
+ const LEADING_SLASH_RE$4 = /^\/+/;
2834
2847
  const EVENT_BINDING_ATTRS = [
2835
2848
  "bindtap",
2836
2849
  "bind:tap",
@@ -2853,22 +2866,20 @@ const STRUCTURAL_ATTRS = [
2853
2866
  "wx:key"
2854
2867
  ];
2855
2868
  const WX_ELSE_ATTRS = new Set(["wx:elif", "wx:else"]);
2856
- const DATASET_NAME_RE$2 = /-([a-z])/g;
2857
- const BRACKET_INDEX_RE = /\[(\d+)\]/g;
2858
2869
  const CLASS_SPLIT_RE = /\s+/;
2859
2870
  const JS_FILE_RE = /\.js$/;
2860
2871
  function isMustacheOnly(value) {
2861
2872
  const trimmed = value.trim();
2862
2873
  return trimmed.startsWith("{{") && trimmed.endsWith("}}") && !trimmed.includes("{{", 2);
2863
2874
  }
2864
- function toDatasetKey$2(attributeName) {
2865
- return attributeName.slice(5).replace(DATASET_NAME_RE$2, (_match, char) => char.toUpperCase());
2875
+ function toDatasetKey$3(attributeName) {
2876
+ return attributeName.slice(5).replace(DATASET_NAME_RE$3, (_match, char) => char.toUpperCase());
2866
2877
  }
2867
- function collectDataset$2(node) {
2878
+ function collectDataset$3(node) {
2868
2879
  const dataset = {};
2869
2880
  for (const [key, value] of Object.entries(node.attribs ?? {})) {
2870
2881
  if (!key.startsWith("data-") || key === "data-sim-scope" || key === "data-sim-tap" || key === "data-sim-component") continue;
2871
- dataset[toDatasetKey$2(key)] = String(value);
2882
+ dataset[toDatasetKey$3(key)] = String(value);
2872
2883
  }
2873
2884
  return dataset;
2874
2885
  }
@@ -2962,21 +2973,46 @@ function resolveComponentAttributeValue(value, scope) {
2962
2973
  }
2963
2974
  return interpolateTemplate$1(value, scope.data);
2964
2975
  }
2965
- function resolveUsingComponents(ownerJsonPath, ownerFilePath) {
2966
- try {
2967
- const usingComponents = JSON.parse(fs.readFileSync(ownerJsonPath, "utf8")).usingComponents;
2968
- if (!usingComponents || typeof usingComponents !== "object" || Array.isArray(usingComponents)) return /* @__PURE__ */ new Map();
2969
- const resolved = /* @__PURE__ */ new Map();
2970
- for (const [alias, rawPath] of Object.entries(usingComponents)) {
2971
- if (typeof rawPath !== "string") continue;
2972
- const basePath = rawPath.startsWith("/") ? rawPath.replace(LEADING_SLASH_RE$4, "") : path.posix.normalize(path.posix.join(path.posix.dirname(ownerFilePath), rawPath));
2973
- resolved.set(alias, basePath.replace(LEADING_SLASH_RE$4, ""));
2976
+ function applyNodeBindings(node, scope) {
2977
+ if (!isTagNode(node)) {
2978
+ if (node.type === "text" && typeof node.data === "string") node.data = interpolateTemplate$1(node.data, scope.data);
2979
+ return;
2980
+ }
2981
+ node.attribs ??= {};
2982
+ node.attribs["data-sim-scope"] = scope.getScopeId();
2983
+ for (const key of STRUCTURAL_ATTRS) delete node.attribs[key];
2984
+ for (const [key, value] of Object.entries({ ...node.attribs })) {
2985
+ if (EVENT_BINDING_ATTRS.includes(key)) {
2986
+ node.attribs["data-sim-tap"] = value;
2987
+ continue;
2974
2988
  }
2975
- return resolved;
2976
- } catch {
2977
- return /* @__PURE__ */ new Map();
2989
+ node.attribs[key] = typeof value === "string" ? String(resolveAttributeValue(value, scope)) : String(value);
2978
2990
  }
2979
2991
  }
2992
+ function createMergedScopeData(pageData, componentProperties, componentData) {
2993
+ return {
2994
+ ...pageData,
2995
+ ...componentProperties,
2996
+ ...componentData
2997
+ };
2998
+ }
2999
+ function evaluateConditionalBranch(node, scope) {
3000
+ const condition = node.attribs?.["wx:if"] ?? node.attribs?.["wx:elif"];
3001
+ if (condition == null) return true;
3002
+ return Boolean(resolveRawValueByPath(scope.data, condition));
3003
+ }
3004
+ function createLoopScope(scope, itemName, indexName, item, index) {
3005
+ return {
3006
+ ...scope,
3007
+ data: {
3008
+ ...scope.data,
3009
+ [indexName]: index,
3010
+ [itemName]: item
3011
+ }
3012
+ };
3013
+ }
3014
+ //#endregion
3015
+ //#region ../../mpcore/packages/simulator/src/runtime/render/component.ts
2980
3016
  function resolveComponentRegistryEntry(context, ownerJsonPath, ownerFilePath, alias) {
2981
3017
  const componentBasePath = resolveUsingComponents(ownerJsonPath, ownerFilePath).get(alias);
2982
3018
  if (!componentBasePath) return null;
@@ -2991,6 +3027,21 @@ function resolveComponentRegistryEntry(context, ownerJsonPath, ownerFilePath, al
2991
3027
  absoluteTemplatePath
2992
3028
  };
2993
3029
  }
3030
+ function resolveUsingComponents(ownerJsonPath, ownerFilePath) {
3031
+ try {
3032
+ const usingComponents = JSON.parse(fs.readFileSync(ownerJsonPath, "utf8")).usingComponents;
3033
+ if (!usingComponents || typeof usingComponents !== "object" || Array.isArray(usingComponents)) return /* @__PURE__ */ new Map();
3034
+ const resolved = /* @__PURE__ */ new Map();
3035
+ for (const [alias, rawPath] of Object.entries(usingComponents)) {
3036
+ if (typeof rawPath !== "string") continue;
3037
+ const basePath = rawPath.startsWith("/") ? rawPath.replace(LEADING_SLASH_RE$4, "") : path.posix.normalize(path.posix.join(path.posix.dirname(ownerFilePath), rawPath));
3038
+ resolved.set(alias, basePath.replace(LEADING_SLASH_RE$4, ""));
3039
+ }
3040
+ return resolved;
3041
+ } catch {
3042
+ return /* @__PURE__ */ new Map();
3043
+ }
3044
+ }
2994
3045
  function collectComponentEventBindings(hostNode) {
2995
3046
  const eventBindings = /* @__PURE__ */ new Map();
2996
3047
  for (const [key, value] of Object.entries(hostNode.attribs ?? {})) {
@@ -3006,7 +3057,7 @@ function collectComponentEventBindings(hostNode) {
3006
3057
  return eventBindings;
3007
3058
  }
3008
3059
  function buildComponentTrigger(componentScopeId, context, hostNode) {
3009
- const hostDataset = collectDataset$2(hostNode);
3060
+ const hostDataset = collectDataset$3(hostNode);
3010
3061
  const hostId = hostNode.attribs?.id ?? "";
3011
3062
  return (instance, eventName, detail, triggerOptions) => {
3012
3063
  const interactionTarget = instance.__lastInteractionEvent__?.target;
@@ -3041,22 +3092,6 @@ function buildComponentTrigger(componentScopeId, context, hostNode) {
3041
3092
  }
3042
3093
  };
3043
3094
  }
3044
- function applyNodeBindings(node, scope) {
3045
- if (!isTagNode(node)) {
3046
- if (node.type === "text" && typeof node.data === "string") node.data = interpolateTemplate$1(node.data, scope.data);
3047
- return;
3048
- }
3049
- node.attribs ??= {};
3050
- node.attribs["data-sim-scope"] = scope.getScopeId();
3051
- for (const key of STRUCTURAL_ATTRS) delete node.attribs[key];
3052
- for (const [key, value] of Object.entries({ ...node.attribs })) {
3053
- if (EVENT_BINDING_ATTRS.includes(key)) {
3054
- node.attribs["data-sim-tap"] = value;
3055
- continue;
3056
- }
3057
- node.attribs[key] = typeof value === "string" ? String(resolveAttributeValue(value, scope)) : String(value);
3058
- }
3059
- }
3060
3095
  function syncComponentProperties(instance, definition, nextProperties, bindingExpressions, changedPageKeys) {
3061
3096
  const changedRootKeys = [];
3062
3097
  const previousProperties = {};
@@ -3079,31 +3114,62 @@ function syncComponentProperties(instance, definition, nextProperties, bindingEx
3079
3114
  if (changedRootKeys.length === 0) return;
3080
3115
  runComponentObservers(definition, instance, changedRootKeys, previousProperties);
3081
3116
  }
3082
- function createMergedScopeData(pageData, componentProperties, componentData) {
3117
+ function createComponentScope(clonedNode, scope, componentScopeId, componentInstance) {
3118
+ const ownerScopeId = scope.getScopeId().includes("/") ? scope.getScopeId() : void 0;
3083
3119
  return {
3084
- ...pageData,
3085
- ...componentProperties,
3086
- ...componentData
3120
+ alias: clonedNode.name,
3121
+ classList: String(clonedNode.attribs?.class ?? "").split(CLASS_SPLIT_RE).map((item) => item.trim()).filter(Boolean),
3122
+ data: createMergedScopeData(scope.data, componentInstance.properties, componentInstance.data),
3123
+ dataset: collectDataset$3(clonedNode),
3124
+ eventBindings: collectComponentEventBindings(clonedNode),
3125
+ getMethod: (methodName) => {
3126
+ const method = componentInstance?.[methodName];
3127
+ return typeof method === "function" ? method : void 0;
3128
+ },
3129
+ getScopeId: () => componentScopeId,
3130
+ hostId: typeof clonedNode.attribs?.id === "string" ? clonedNode.attribs.id : void 0,
3131
+ id: typeof clonedNode.attribs?.id === "string" ? clonedNode.attribs.id : void 0,
3132
+ listenerScopeId: scope.getScopeId(),
3133
+ ownerScopeId
3087
3134
  };
3088
3135
  }
3089
- function isTruthy(value) {
3090
- return Boolean(value);
3091
- }
3092
- function createLoopScope(scope, itemName, indexName, item, index) {
3136
+ function resolveComponentProperties(clonedNode, scope) {
3137
+ const nextProperties = {};
3138
+ const bindingExpressions = {};
3139
+ for (const [key, value] of Object.entries(clonedNode.attribs ?? {})) {
3140
+ if (key.startsWith("bind")) continue;
3141
+ if (isMustacheOnly(String(value))) bindingExpressions[key] = String(value).trim().slice(2, -2).trim();
3142
+ nextProperties[key] = resolveComponentAttributeValue(String(value), scope);
3143
+ }
3093
3144
  return {
3094
- ...scope,
3095
- data: {
3096
- ...scope.data,
3097
- [indexName]: index,
3098
- [itemName]: item
3099
- }
3145
+ nextProperties,
3146
+ bindingExpressions
3100
3147
  };
3101
3148
  }
3102
- function evaluateConditionalBranch(node, scope) {
3103
- const condition = node.attribs?.["wx:if"] ?? node.attribs?.["wx:elif"];
3104
- if (condition == null) return true;
3105
- return isTruthy(resolveRawValueByPath(scope.data, condition));
3149
+ function createRuntimeComponentInstance(componentScopeId, context, clonedNode, componentEntry, nextProperties, ownerScopeId) {
3150
+ const componentInstance = createComponentInstance({
3151
+ definition: componentEntry.definition,
3152
+ properties: nextProperties,
3153
+ triggerEvent: buildComponentTrigger(componentScopeId, context, clonedNode)
3154
+ });
3155
+ componentInstance.createIntersectionObserver = (options) => context.session.createIntersectionObserver(componentInstance, options);
3156
+ componentInstance.createMediaQueryObserver = () => context.session.createMediaQueryObserver(componentInstance);
3157
+ componentInstance.selectComponent = (selector) => context.session.selectComponentWithin(componentScopeId, selector);
3158
+ componentInstance.selectAllComponents = (selector) => context.session.selectAllComponentsWithin(componentScopeId, selector);
3159
+ componentInstance.selectOwnerComponent = () => ownerScopeId ? context.session.selectOwnerComponent(componentScopeId) : null;
3160
+ runComponentLifecycle(componentInstance, "created");
3161
+ runComponentObservers(componentInstance.__definition__ ?? componentEntry.definition, componentInstance, Object.keys(nextProperties), {});
3162
+ componentInstance.__propertySnapshots = Object.fromEntries(Object.entries(componentInstance.properties).map(([key, propertyValue]) => [key, cloneValue$1(propertyValue)]));
3163
+ runComponentLifecycle(componentInstance, "attached");
3164
+ context.componentCache.set(componentScopeId, componentInstance);
3165
+ return componentInstance;
3166
+ }
3167
+ function renderRuntimeComponentTemplate(context, componentEntry, renderNodeTree, componentScope, componentScopeId, seenComponentScopes) {
3168
+ const componentDocument = parseTemplateDocument(readTemplateSource(componentEntry.absoluteTemplatePath));
3169
+ return renderNodeTree((componentDocument.children ?? [])[0] ?? componentDocument, componentScope, context, path.resolve(context.project.miniprogramRootPath, `${componentEntry.filePath.replace(JS_FILE_RE, "")}.json`), componentEntry.filePath, componentScopeId, seenComponentScopes);
3106
3170
  }
3171
+ //#endregion
3172
+ //#region ../../mpcore/packages/simulator/src/runtime/render/index.ts
3107
3173
  function expandNodeByFor(node, scope) {
3108
3174
  const forExpression = node.attribs?.["wx:for"];
3109
3175
  if (!forExpression) return [{
@@ -3173,49 +3239,14 @@ function renderNodeTree(node, scope, context, ownerJsonPath, ownerFilePath, inst
3173
3239
  if (componentEntry) {
3174
3240
  const componentScopeId = `${instancePath}/${clonedNode.name}`;
3175
3241
  const ownerScopeId = scope.getScopeId().includes("/") ? scope.getScopeId() : void 0;
3176
- const nextProperties = {};
3177
- const bindingExpressions = {};
3178
- for (const [key, value] of Object.entries(clonedNode.attribs ?? {})) {
3179
- if (key.startsWith("bind")) continue;
3180
- if (isMustacheOnly(String(value))) bindingExpressions[key] = String(value).trim().slice(2, -2).trim();
3181
- nextProperties[key] = resolveComponentAttributeValue(String(value), scope);
3182
- }
3242
+ const { nextProperties, bindingExpressions } = resolveComponentProperties(clonedNode, scope);
3183
3243
  let componentInstance = context.componentCache.get(componentScopeId);
3184
- if (!componentInstance) {
3185
- componentInstance = createComponentInstance({
3186
- definition: componentEntry.definition,
3187
- properties: nextProperties,
3188
- triggerEvent: buildComponentTrigger(componentScopeId, context, clonedNode)
3189
- });
3190
- componentInstance.selectComponent = (selector) => context.session.selectComponentWithin(componentScopeId, selector);
3191
- componentInstance.selectAllComponents = (selector) => context.session.selectAllComponentsWithin(componentScopeId, selector);
3192
- componentInstance.selectOwnerComponent = () => ownerScopeId ? context.session.selectOwnerComponent(componentScopeId) : null;
3193
- runComponentLifecycle(componentInstance, "created");
3194
- runComponentObservers(componentInstance.__definition__ ?? componentEntry.definition, componentInstance, Object.keys(nextProperties), {});
3195
- componentInstance.__propertySnapshots = Object.fromEntries(Object.entries(componentInstance.properties).map(([key, propertyValue]) => [key, cloneValue$1(propertyValue)]));
3196
- runComponentLifecycle(componentInstance, "attached");
3197
- context.componentCache.set(componentScopeId, componentInstance);
3198
- } else syncComponentProperties(componentInstance, componentInstance.__definition__ ?? componentEntry.definition, nextProperties, bindingExpressions, context.changedPageKeys);
3244
+ if (!componentInstance) componentInstance = createRuntimeComponentInstance(componentScopeId, context, clonedNode, componentEntry, nextProperties, ownerScopeId);
3245
+ else syncComponentProperties(componentInstance, componentInstance.__definition__ ?? componentEntry.definition, nextProperties, bindingExpressions, context.changedPageKeys);
3199
3246
  seenComponentScopes.add(componentScopeId);
3200
- const componentScope = {
3201
- alias: clonedNode.name,
3202
- classList: String(clonedNode.attribs?.class ?? "").split(CLASS_SPLIT_RE).map((item) => item.trim()).filter(Boolean),
3203
- data: createMergedScopeData(scope.data, componentInstance.properties, componentInstance.data),
3204
- dataset: collectDataset$2(clonedNode),
3205
- eventBindings: collectComponentEventBindings(clonedNode),
3206
- getMethod: (methodName) => {
3207
- const method = componentInstance?.[methodName];
3208
- return typeof method === "function" ? method : void 0;
3209
- },
3210
- getScopeId: () => componentScopeId,
3211
- hostId: typeof clonedNode.attribs?.id === "string" ? clonedNode.attribs.id : void 0,
3212
- id: typeof clonedNode.attribs?.id === "string" ? clonedNode.attribs.id : void 0,
3213
- listenerScopeId: scope.getScopeId(),
3214
- ownerScopeId
3215
- };
3247
+ const componentScope = createComponentScope(clonedNode, scope, componentScopeId, componentInstance);
3216
3248
  context.componentScopes.set(componentScopeId, componentScope);
3217
- const componentDocument = parseTemplateDocument(readTemplateSource(componentEntry.absoluteTemplatePath));
3218
- const renderedComponentRoot = renderNodeTree((componentDocument.children ?? [])[0] ?? componentDocument, componentScope, context, path.resolve(context.project.miniprogramRootPath, `${componentEntry.filePath.replace(JS_FILE_RE, "")}.json`), componentEntry.filePath, componentScopeId, seenComponentScopes);
3249
+ const renderedComponentRoot = renderRuntimeComponentTemplate(context, componentEntry, renderNodeTree, componentScope, componentScopeId, seenComponentScopes);
3219
3250
  if (renderedComponentRoot.attribs) renderedComponentRoot.attribs["data-sim-component"] = clonedNode.name;
3220
3251
  if (!componentInstance.__ready__) {
3221
3252
  runComponentLifecycle(componentInstance, "ready");
@@ -3287,7 +3318,7 @@ function registerComponentDefinition(registries, definition) {
3287
3318
  return definition;
3288
3319
  }
3289
3320
  //#endregion
3290
- //#region ../../mpcore/packages/simulator/src/host/wx.ts
3321
+ //#region ../../mpcore/packages/simulator/src/host/wx/index.ts
3291
3322
  function invokeWxApi(operation, option) {
3292
3323
  try {
3293
3324
  const result = operation();
@@ -3312,9 +3343,65 @@ function resolveCapabilityValue(source, schema) {
3312
3343
  function createHeadlessWx(driver) {
3313
3344
  const capabilityTree = {
3314
3345
  canIUse: true,
3346
+ canvasToTempFilePath: true,
3347
+ chooseImage: { return: {
3348
+ errMsg: true,
3349
+ tempFilePaths: true,
3350
+ tempFiles: true
3351
+ } },
3352
+ chooseMessageFile: { return: {
3353
+ errMsg: true,
3354
+ tempFiles: true
3355
+ } },
3356
+ chooseMedia: { return: {
3357
+ errMsg: true,
3358
+ tempFiles: true,
3359
+ type: true
3360
+ } },
3361
+ chooseVideo: { return: {
3362
+ duration: true,
3363
+ errMsg: true,
3364
+ height: true,
3365
+ size: true,
3366
+ tempFilePath: true,
3367
+ width: true
3368
+ } },
3369
+ compressImage: { return: {
3370
+ errMsg: true,
3371
+ tempFilePath: true
3372
+ } },
3315
3373
  clearStorage: true,
3316
3374
  clearStorageSync: true,
3375
+ createAnimation: true,
3376
+ createCanvasContext: true,
3377
+ createIntersectionObserver: true,
3378
+ createVideoContext: true,
3317
3379
  createSelectorQuery: true,
3380
+ getImageInfo: { return: {
3381
+ errMsg: true,
3382
+ height: true,
3383
+ orientation: true,
3384
+ path: true,
3385
+ type: true,
3386
+ width: true
3387
+ } },
3388
+ getFileInfo: { return: {
3389
+ digest: true,
3390
+ errMsg: true,
3391
+ size: true
3392
+ } },
3393
+ openDocument: { return: { errMsg: true } },
3394
+ getVideoInfo: { return: {
3395
+ bitrate: true,
3396
+ duration: true,
3397
+ errMsg: true,
3398
+ fps: true,
3399
+ height: true,
3400
+ orientation: true,
3401
+ size: true,
3402
+ type: true,
3403
+ width: true
3404
+ } },
3318
3405
  getFileSystemManager: true,
3319
3406
  getSavedFileInfo: true,
3320
3407
  getSavedFileList: true,
@@ -3352,6 +3439,10 @@ function createHeadlessWx(driver) {
3352
3439
  },
3353
3440
  scene: true
3354
3441
  } },
3442
+ getClipboardData: { return: {
3443
+ data: true,
3444
+ errMsg: true
3445
+ } },
3355
3446
  getMenuButtonBoundingClientRect: { return: {
3356
3447
  bottom: true,
3357
3448
  height: true,
@@ -3426,14 +3517,18 @@ function createHeadlessWx(driver) {
3426
3517
  offNetworkStatusChange: true,
3427
3518
  onNetworkStatusChange: true,
3428
3519
  pageScrollTo: true,
3520
+ previewImage: true,
3429
3521
  reLaunch: true,
3430
3522
  redirectTo: true,
3523
+ saveImageToPhotosAlbum: true,
3524
+ saveVideoToPhotosAlbum: true,
3431
3525
  removeStorage: true,
3432
3526
  removeStorageSync: true,
3433
3527
  request: true,
3434
3528
  saveFile: true,
3435
3529
  setBackgroundColor: true,
3436
3530
  setBackgroundTextStyle: true,
3531
+ setClipboardData: true,
3437
3532
  setStorage: true,
3438
3533
  setStorageSync: true,
3439
3534
  setNavigationBarColor: true,
@@ -3450,6 +3545,7 @@ function createHeadlessWx(driver) {
3450
3545
  showLoading: true,
3451
3546
  showModal: true,
3452
3547
  showToast: true,
3548
+ startPullDownRefresh: true,
3453
3549
  stopPullDownRefresh: true,
3454
3550
  switchTab: true,
3455
3551
  uploadFile: true,
@@ -3459,11 +3555,18 @@ function createHeadlessWx(driver) {
3459
3555
  };
3460
3556
  return {
3461
3557
  canIUse: (schema) => typeof schema === "string" && schema.trim() !== "" && resolveCapabilityValue(capabilityTree, schema.trim()) != null,
3462
- clearStorage: (option) => invokeWxApi(() => {
3463
- driver.clearStorageSync();
3464
- return { errMsg: "clearStorage:ok" };
3465
- }, option),
3558
+ canvasToTempFilePath: (option) => invokeWxApi(() => driver.canvasToTempFilePath(option), option),
3559
+ chooseImage: (option) => invokeWxApi(() => driver.chooseImage(option ?? {}), option),
3560
+ chooseMessageFile: (option) => invokeWxApi(() => driver.chooseMessageFile(option ?? {}), option),
3561
+ chooseMedia: (option) => invokeWxApi(() => driver.chooseMedia(option ?? {}), option),
3562
+ chooseVideo: (option) => invokeWxApi(() => driver.chooseVideo(option ?? {}), option),
3563
+ compressImage: (option) => invokeWxApi(() => driver.compressImage(option), option),
3564
+ clearStorage: (option) => invokeWxApi(() => ({ errMsg: (driver.clearStorageSync(), "clearStorage:ok") }), option),
3466
3565
  clearStorageSync: () => driver.clearStorageSync(),
3566
+ createAnimation: (option) => driver.createAnimation(option),
3567
+ createCanvasContext: (canvasId, component) => driver.createCanvasContext(canvasId, component),
3568
+ createIntersectionObserver: (component, options) => driver.createIntersectionObserver(component, options),
3569
+ createVideoContext: (videoId, component) => driver.createVideoContext(videoId, component),
3467
3570
  createSelectorQuery: () => {
3468
3571
  const requests = [];
3469
3572
  let scope;
@@ -3522,12 +3625,16 @@ function createHeadlessWx(driver) {
3522
3625
  return query;
3523
3626
  },
3524
3627
  getEnterOptionsSync: () => driver.getEnterOptionsSync(),
3628
+ getFileInfo: (option) => invokeWxApi(() => driver.getFileInfo(option), option),
3629
+ getImageInfo: (option) => invokeWxApi(() => driver.getImageInfo(option), option),
3630
+ getVideoInfo: (option) => invokeWxApi(() => driver.getVideoInfo(option), option),
3525
3631
  getFileSystemManager: () => driver.getFileSystemManager(),
3526
3632
  getSavedFileInfo: (option) => invokeWxApi(() => driver.getSavedFileInfo(option), option),
3527
3633
  getSavedFileList: (option) => invokeWxApi(() => driver.getSavedFileList(option), option),
3528
3634
  getAppBaseInfo: (option) => invokeWxApi(() => driver.getAppBaseInfoSync(), option),
3529
3635
  getAppBaseInfoSync: () => driver.getAppBaseInfoSync(),
3530
3636
  getLaunchOptionsSync: () => driver.getLaunchOptionsSync(),
3637
+ getClipboardData: (option) => invokeWxApi(() => driver.getClipboardData(), option),
3531
3638
  getMenuButtonBoundingClientRect: () => driver.getMenuButtonBoundingClientRect(),
3532
3639
  getNetworkType: (option) => invokeWxApi(() => driver.getNetworkType(), option),
3533
3640
  getStorageInfo: (option) => invokeWxApi(() => driver.getStorageInfoSync(), option),
@@ -3553,9 +3660,11 @@ function createHeadlessWx(driver) {
3553
3660
  nextTick: (callback) => driver.nextTick(callback),
3554
3661
  offNetworkStatusChange: (callback) => driver.offNetworkStatusChange(callback),
3555
3662
  onNetworkStatusChange: (callback) => driver.onNetworkStatusChange(callback),
3663
+ openDocument: (option) => invokeWxApi(() => driver.openDocument(option), option),
3556
3664
  pageScrollTo: (option) => invokeWxApi(() => {
3557
3665
  driver.pageScrollTo(option);
3558
3666
  }, option),
3667
+ previewImage: (option) => invokeWxApi(() => driver.previewImage(option), option),
3559
3668
  reLaunch: (option) => invokeWxApi(() => {
3560
3669
  driver.reLaunch(option);
3561
3670
  }, option),
@@ -3563,6 +3672,8 @@ function createHeadlessWx(driver) {
3563
3672
  driver.redirectTo(option);
3564
3673
  }, option),
3565
3674
  removeSavedFile: (option) => invokeWxApi(() => driver.removeSavedFile(option), option),
3675
+ saveImageToPhotosAlbum: (option) => invokeWxApi(() => driver.saveImageToPhotosAlbum(option), option),
3676
+ saveVideoToPhotosAlbum: (option) => invokeWxApi(() => driver.saveVideoToPhotosAlbum(option), option),
3566
3677
  removeStorage: (option) => invokeWxApi(() => {
3567
3678
  driver.removeStorageSync(option.key);
3568
3679
  return { errMsg: "removeStorage:ok" };
@@ -3572,6 +3683,7 @@ function createHeadlessWx(driver) {
3572
3683
  saveFile: (option) => invokeWxApi(() => driver.saveFile(option), option),
3573
3684
  setBackgroundColor: (option) => invokeWxApi(() => driver.setBackgroundColor(option), option),
3574
3685
  setBackgroundTextStyle: (option) => invokeWxApi(() => driver.setBackgroundTextStyle(option), option),
3686
+ setClipboardData: (option) => invokeWxApi(() => driver.setClipboardData(option), option),
3575
3687
  setStorage: (option) => invokeWxApi(() => {
3576
3688
  driver.setStorageSync(option.key, option.data);
3577
3689
  return { errMsg: "setStorage:ok" };
@@ -3591,6 +3703,7 @@ function createHeadlessWx(driver) {
3591
3703
  showLoading: (option) => invokeWxApi(() => driver.showLoading(option), option),
3592
3704
  showModal: (option) => invokeWxApi(() => driver.showModal(option), option),
3593
3705
  showToast: (option) => invokeWxApi(() => driver.showToast(option), option),
3706
+ startPullDownRefresh: (option) => invokeWxApi(() => driver.startPullDownRefresh(), option),
3594
3707
  stopPullDownRefresh: () => driver.stopPullDownRefresh(),
3595
3708
  switchTab: (option) => invokeWxApi(() => {
3596
3709
  driver.switchTab(option);
@@ -3802,10 +3915,11 @@ function loadProject(projectPath) {
3802
3915
  }
3803
3916
  //#endregion
3804
3917
  //#region ../../mpcore/packages/simulator/src/view/selectors.ts
3805
- const WHITESPACE_RE = /\s+/;
3918
+ const WHITESPACE_RE$1 = /\s+/;
3806
3919
  const DATA_ATTR_SELECTOR_RE$1 = /^\[data-([^=\]]+)="([^"]*)"\]$/;
3920
+ const COMPOUND_SELECTOR_PART_RE$1 = /#[\w-]+|\.[\w-]+|\[data-[^=\]]+="[^"]*"\]|[A-Za-z][\w-]*/g;
3807
3921
  function getClassList(node) {
3808
- return String(node.attribs?.class ?? "").split(WHITESPACE_RE).map((item) => item.trim()).filter(Boolean);
3922
+ return String(node.attribs?.class ?? "").split(WHITESPACE_RE$1).map((item) => item.trim()).filter(Boolean);
3809
3923
  }
3810
3924
  function matchesSimpleSelector(node, selector) {
3811
3925
  if (node.type !== "tag") return false;
@@ -3819,6 +3933,15 @@ function matchesSimpleSelector(node, selector) {
3819
3933
  }
3820
3934
  return node.name === selector;
3821
3935
  }
3936
+ function parseCompoundSelector$1(selector) {
3937
+ const parts = selector.match(COMPOUND_SELECTOR_PART_RE$1) ?? [];
3938
+ return parts.join("") === selector ? parts : [];
3939
+ }
3940
+ function matchesSelectorToken(node, selector) {
3941
+ const simpleSelectors = parseCompoundSelector$1(selector);
3942
+ if (simpleSelectors.length === 0) return false;
3943
+ return simpleSelectors.every((simpleSelector) => matchesSimpleSelector(node, simpleSelector));
3944
+ }
3822
3945
  function collectDescendants(node, into) {
3823
3946
  for (const child of node.children ?? []) {
3824
3947
  into.push(child);
@@ -3826,7 +3949,7 @@ function collectDescendants(node, into) {
3826
3949
  }
3827
3950
  }
3828
3951
  function querySelectorAll(root, selector) {
3829
- const parts = selector.trim().split(WHITESPACE_RE).filter(Boolean);
3952
+ const parts = selector.trim().split(WHITESPACE_RE$1).filter(Boolean);
3830
3953
  if (parts.length === 0) return [];
3831
3954
  let current = [root];
3832
3955
  for (const part of parts) {
@@ -3835,7 +3958,7 @@ function querySelectorAll(root, selector) {
3835
3958
  const candidates = [];
3836
3959
  if (part === "page" && node.type === "tag" && node.name === "page") candidates.push(node);
3837
3960
  collectDescendants(node, candidates);
3838
- for (const candidate of candidates) if (matchesSimpleSelector(candidate, part)) next.push(candidate);
3961
+ for (const candidate of candidates) if (matchesSelectorToken(candidate, part)) next.push(candidate);
3839
3962
  }
3840
3963
  current = next;
3841
3964
  }
@@ -3843,22 +3966,22 @@ function querySelectorAll(root, selector) {
3843
3966
  }
3844
3967
  //#endregion
3845
3968
  //#region ../../mpcore/packages/simulator/src/view/nodeHandle.ts
3846
- const DATASET_NAME_RE$1 = /-([a-z])/g;
3969
+ const DATASET_NAME_RE$2 = /-([a-z])/g;
3847
3970
  function escapeText$1(text) {
3848
3971
  return text.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;");
3849
3972
  }
3850
- function toDatasetKey$1(attributeName) {
3851
- return attributeName.slice(5).replace(DATASET_NAME_RE$1, (_match, char) => char.toUpperCase());
3973
+ function toDatasetKey$2(attributeName) {
3974
+ return attributeName.slice(5).replace(DATASET_NAME_RE$2, (_match, char) => char.toUpperCase());
3852
3975
  }
3853
- function collectDataset$1(node) {
3976
+ function collectDataset$2(node) {
3854
3977
  const dataset = {};
3855
3978
  for (const [key, value] of Object.entries(node.attribs ?? {})) {
3856
3979
  if (!key.startsWith("data-") || key.startsWith("data-sim-")) continue;
3857
- dataset[toDatasetKey$1(key)] = value;
3980
+ dataset[toDatasetKey$2(key)] = value;
3858
3981
  }
3859
3982
  return dataset;
3860
3983
  }
3861
- function resolveEventBinding(node, eventName) {
3984
+ function resolveEventBinding$1(node, eventName) {
3862
3985
  const normalizedEventName = eventName.trim();
3863
3986
  if (!normalizedEventName) return null;
3864
3987
  const bindingAttrs = [
@@ -3873,8 +3996,8 @@ function resolveEventBinding(node, eventName) {
3873
3996
  }
3874
3997
  return null;
3875
3998
  }
3876
- function createEventPayload(node, eventName, event) {
3877
- const dataset = collectDataset$1(node);
3999
+ function createEventPayload$1(node, eventName, event) {
4000
+ const dataset = collectDataset$2(node);
3878
4001
  const nodeId = node.attribs?.id ?? "";
3879
4002
  return {
3880
4003
  bubbles: false,
@@ -3939,7 +4062,7 @@ var HeadlessTestingNodeHandle = class HeadlessTestingNodeHandle {
3939
4062
  return this.node.attribs?.[name];
3940
4063
  }
3941
4064
  async dataset() {
3942
- return collectDataset$1(this.node);
4065
+ return collectDataset$2(this.node);
3943
4066
  }
3944
4067
  async scope() {
3945
4068
  if (!this.interactions) throw new Error("Node interactions are not available for this headless testing node.");
@@ -3969,9 +4092,9 @@ var HeadlessTestingNodeHandle = class HeadlessTestingNodeHandle {
3969
4092
  if (!this.interactions) throw new Error("Node interactions are not available for this headless testing node.");
3970
4093
  const normalizedEventName = eventName.trim();
3971
4094
  if (!normalizedEventName) throw new Error("Event name must be a non-empty string in headless testing runtime.");
3972
- const methodName = resolveEventBinding(this.node, normalizedEventName);
4095
+ const methodName = resolveEventBinding$1(this.node, normalizedEventName);
3973
4096
  if (!methodName) throw new Error(`No ${normalizedEventName} binding was found on <${this.node.name ?? "unknown"}> in headless testing runtime.`);
3974
- return await this.interactions.callMethod(this.node.attribs?.["data-sim-scope"] ?? null, methodName, createEventPayload(this.node, normalizedEventName, event));
4097
+ return await this.interactions.callMethod(this.node.attribs?.["data-sim-scope"] ?? null, methodName, createEventPayload$1(this.node, normalizedEventName, event));
3975
4098
  }
3976
4099
  async tap(event = {}) {
3977
4100
  return await this.trigger("tap", event);
@@ -4055,18 +4178,18 @@ function renderPageTree(project, page) {
4055
4178
  }
4056
4179
  //#endregion
4057
4180
  //#region ../../mpcore/packages/simulator/src/view/selectorQuery.ts
4058
- const DATASET_NAME_RE = /-([a-z])/g;
4181
+ const DATASET_NAME_RE$1 = /-([a-z])/g;
4059
4182
  const LEADING_MARK_PREFIX_RE = /^mark[:\-]?/;
4060
4183
  const MARK_NAME_RE = /[:\-]([a-z])/g;
4061
- const NUMERIC_LIKE_VALUE_RE = /-?\d+(?:\.\d+)?/;
4062
- function toDatasetKey(attributeName) {
4063
- return attributeName.slice(5).replace(DATASET_NAME_RE, (_match, char) => char.toUpperCase());
4184
+ const NUMERIC_LIKE_VALUE_RE$1 = /-?\d+(?:\.\d+)?/;
4185
+ function toDatasetKey$1(attributeName) {
4186
+ return attributeName.slice(5).replace(DATASET_NAME_RE$1, (_match, char) => char.toUpperCase());
4064
4187
  }
4065
- function collectDataset(node) {
4188
+ function collectDataset$1(node) {
4066
4189
  const dataset = {};
4067
4190
  for (const [key, value] of Object.entries(node.attribs ?? {})) {
4068
4191
  if (!key.startsWith("data-") || key.startsWith("data-sim-")) continue;
4069
- dataset[toDatasetKey(key)] = value;
4192
+ dataset[toDatasetKey$1(key)] = value;
4070
4193
  }
4071
4194
  return dataset;
4072
4195
  }
@@ -4095,7 +4218,7 @@ function findNodeByScopeId(root, scopeId) {
4095
4218
  }
4096
4219
  return null;
4097
4220
  }
4098
- function parseStyleDeclarations(styleValue) {
4221
+ function parseStyleDeclarations$1(styleValue) {
4099
4222
  const declarations = {};
4100
4223
  if (!styleValue) return declarations;
4101
4224
  for (const declaration of styleValue.split(";")) {
@@ -4106,17 +4229,17 @@ function parseStyleDeclarations(styleValue) {
4106
4229
  }
4107
4230
  return declarations;
4108
4231
  }
4109
- function parseNumericLikeValue(value) {
4232
+ function parseNumericLikeValue$1(value) {
4110
4233
  if (!value) return 0;
4111
- const match = value.match(NUMERIC_LIKE_VALUE_RE);
4234
+ const match = value.match(NUMERIC_LIKE_VALUE_RE$1);
4112
4235
  return match ? Number(match[0]) : 0;
4113
4236
  }
4114
- function resolveRect(node) {
4115
- const style = parseStyleDeclarations(node.attribs?.style);
4116
- const left = parseNumericLikeValue(node.attribs?.["data-sim-left"] ?? style.left);
4117
- const top = parseNumericLikeValue(node.attribs?.["data-sim-top"] ?? style.top);
4118
- const width = parseNumericLikeValue(node.attribs?.["data-sim-width"] ?? style.width);
4119
- const height = parseNumericLikeValue(node.attribs?.["data-sim-height"] ?? style.height);
4237
+ function resolveRect$1(node) {
4238
+ const style = parseStyleDeclarations$1(node.attribs?.style);
4239
+ const left = parseNumericLikeValue$1(node.attribs?.["data-sim-left"] ?? style.left);
4240
+ const top = parseNumericLikeValue$1(node.attribs?.["data-sim-top"] ?? style.top);
4241
+ const width = parseNumericLikeValue$1(node.attribs?.["data-sim-width"] ?? style.width);
4242
+ const height = parseNumericLikeValue$1(node.attribs?.["data-sim-height"] ?? style.height);
4120
4243
  return {
4121
4244
  bottom: top + height,
4122
4245
  height,
@@ -4126,10 +4249,16 @@ function resolveRect(node) {
4126
4249
  width
4127
4250
  };
4128
4251
  }
4252
+ function resolveSelectorScrollTop(root, selector) {
4253
+ const normalizedSelector = selector?.trim();
4254
+ if (!normalizedSelector) return null;
4255
+ const match = querySelectorAll(root, normalizedSelector)[0];
4256
+ return match ? resolveRect$1(match).top : null;
4257
+ }
4129
4258
  function resolveScrollOffset(node) {
4130
4259
  return {
4131
- scrollLeft: parseNumericLikeValue(node.attribs?.["data-sim-scroll-left"]),
4132
- scrollTop: parseNumericLikeValue(node.attribs?.["data-sim-scroll-top"])
4260
+ scrollLeft: parseNumericLikeValue$1(node.attribs?.["data-sim-scroll-left"]),
4261
+ scrollTop: parseNumericLikeValue$1(node.attribs?.["data-sim-scroll-top"])
4133
4262
  };
4134
4263
  }
4135
4264
  function resolvePropertyValue(node, propertyName) {
@@ -4137,7 +4266,7 @@ function resolvePropertyValue(node, propertyName) {
4137
4266
  if (!normalizedPropertyName) return;
4138
4267
  if (normalizedPropertyName === "id") return node.attribs?.id ?? "";
4139
4268
  if (normalizedPropertyName === "class") return node.attribs?.class ?? "";
4140
- if (normalizedPropertyName === "dataset") return collectDataset(node);
4269
+ if (normalizedPropertyName === "dataset") return collectDataset$1(node);
4141
4270
  return node.attribs?.[normalizedPropertyName];
4142
4271
  }
4143
4272
  function pickProperties(node, propertyNames) {
@@ -4146,18 +4275,18 @@ function pickProperties(node, propertyNames) {
4146
4275
  return result;
4147
4276
  }
4148
4277
  function pickComputedStyle(node, propertyNames) {
4149
- const style = parseStyleDeclarations(node.attribs?.style);
4278
+ const style = parseStyleDeclarations$1(node.attribs?.style);
4150
4279
  const result = {};
4151
4280
  for (const propertyName of propertyNames) result[propertyName] = style[propertyName] ?? "";
4152
4281
  return result;
4153
4282
  }
4154
- function resolveFieldsResult(node, fields, _options) {
4283
+ function resolveFieldsResult(node, fields, options) {
4155
4284
  const result = {};
4156
4285
  if (fields.id) result.id = node.attribs?.id ?? "";
4157
- if (fields.dataset) result.dataset = collectDataset(node);
4286
+ if (fields.dataset) result.dataset = collectDataset$1(node);
4158
4287
  if (fields.mark) result.mark = collectMark(node);
4159
4288
  if (fields.rect || fields.size) {
4160
- const rect = resolveRect(node);
4289
+ const rect = resolveRect$1(node);
4161
4290
  if (fields.rect) Object.assign(result, rect);
4162
4291
  if (fields.size) {
4163
4292
  result.width = rect.width;
@@ -4167,7 +4296,7 @@ function resolveFieldsResult(node, fields, _options) {
4167
4296
  if (fields.scrollOffset) Object.assign(result, resolveScrollOffset(node));
4168
4297
  if (Array.isArray(fields.properties) && fields.properties.length > 0) Object.assign(result, pickProperties(node, fields.properties));
4169
4298
  if (Array.isArray(fields.computedStyle) && fields.computedStyle.length > 0) Object.assign(result, pickComputedStyle(node, fields.computedStyle));
4170
- if (fields.context) result.context = { type: "unsupported-context" };
4299
+ if (fields.context) result.context = options.resolveContext?.(node) ?? { type: "unsupported-context" };
4171
4300
  if (fields.node) result.node = { type: node.name ?? "unknown" };
4172
4301
  return result;
4173
4302
  }
@@ -4210,6 +4339,749 @@ function resolveSelectorQueryScopeRoot(root, scopeId) {
4210
4339
  return scopedNode ? createScopedRoot(scopedNode) : root;
4211
4340
  }
4212
4341
  //#endregion
4342
+ //#region ../../mpcore/packages/simulator/src/view/animation.ts
4343
+ const DEFAULT_STEP_OPTION = {
4344
+ delay: 0,
4345
+ duration: 400,
4346
+ timingFunction: "linear",
4347
+ transformOrigin: "50% 50% 0"
4348
+ };
4349
+ function normalizeStepOption(option) {
4350
+ return {
4351
+ delay: option?.delay ?? DEFAULT_STEP_OPTION.delay,
4352
+ duration: option?.duration ?? DEFAULT_STEP_OPTION.duration,
4353
+ timingFunction: option?.timingFunction ?? DEFAULT_STEP_OPTION.timingFunction,
4354
+ transformOrigin: option?.transformOrigin ?? DEFAULT_STEP_OPTION.transformOrigin
4355
+ };
4356
+ }
4357
+ function normalizeLength(value) {
4358
+ if (typeof value === "number") return `${value}px`;
4359
+ return value ?? "0px";
4360
+ }
4361
+ function createAction(type, args) {
4362
+ return {
4363
+ args,
4364
+ type
4365
+ };
4366
+ }
4367
+ function createHeadlessAnimation(defaultOption) {
4368
+ const baseOption = normalizeStepOption(defaultOption);
4369
+ let currentActions = [];
4370
+ let queue = [];
4371
+ let animation;
4372
+ const append = (type, args) => {
4373
+ currentActions.push(createAction(type, args));
4374
+ return animation;
4375
+ };
4376
+ animation = {
4377
+ backgroundColor(value) {
4378
+ return append("backgroundColor", [value]);
4379
+ },
4380
+ bottom(value) {
4381
+ return append("bottom", [normalizeLength(value)]);
4382
+ },
4383
+ export() {
4384
+ const exported = queue.map((item) => ({
4385
+ animates: item.animates.map((action) => ({
4386
+ args: [...action.args],
4387
+ type: action.type
4388
+ })),
4389
+ option: { ...item.option }
4390
+ }));
4391
+ queue = [];
4392
+ currentActions = [];
4393
+ return { actions: exported };
4394
+ },
4395
+ height(value) {
4396
+ return append("height", [normalizeLength(value)]);
4397
+ },
4398
+ left(value) {
4399
+ return append("left", [normalizeLength(value)]);
4400
+ },
4401
+ opacity(value) {
4402
+ return append("opacity", [value]);
4403
+ },
4404
+ right(value) {
4405
+ return append("right", [normalizeLength(value)]);
4406
+ },
4407
+ rotate(angle) {
4408
+ return append("rotate", [`${angle}deg`]);
4409
+ },
4410
+ scale(sx, sy) {
4411
+ return append("scale", [sx, sy ?? sx]);
4412
+ },
4413
+ step(option) {
4414
+ queue.push({
4415
+ animates: currentActions.map((action) => ({
4416
+ args: [...action.args],
4417
+ type: action.type
4418
+ })),
4419
+ option: {
4420
+ ...baseOption,
4421
+ ...option ?? {}
4422
+ }
4423
+ });
4424
+ currentActions = [];
4425
+ return animation;
4426
+ },
4427
+ top(value) {
4428
+ return append("top", [normalizeLength(value)]);
4429
+ },
4430
+ translate(tx, ty) {
4431
+ return append("translate", [normalizeLength(tx), normalizeLength(ty)]);
4432
+ },
4433
+ translate3d(tx, ty, tz) {
4434
+ return append("translate3d", [
4435
+ normalizeLength(tx),
4436
+ normalizeLength(ty),
4437
+ normalizeLength(tz)
4438
+ ]);
4439
+ },
4440
+ translateX(translation) {
4441
+ return append("translateX", [normalizeLength(translation)]);
4442
+ },
4443
+ translateY(translation) {
4444
+ return append("translateY", [normalizeLength(translation)]);
4445
+ },
4446
+ translateZ(translation) {
4447
+ return append("translateZ", [normalizeLength(translation)]);
4448
+ },
4449
+ width(value) {
4450
+ return append("width", [normalizeLength(value)]);
4451
+ }
4452
+ };
4453
+ return animation;
4454
+ }
4455
+ //#endregion
4456
+ //#region ../../mpcore/packages/simulator/src/view/canvasContext.ts
4457
+ function findCanvasNode(root, canvasId) {
4458
+ if (root.type === "tag" && root.name === "canvas" && root.attribs?.["canvas-id"] === canvasId) return root;
4459
+ for (const child of root.children ?? []) {
4460
+ const match = findCanvasNode(child, canvasId);
4461
+ if (match) return match;
4462
+ }
4463
+ return null;
4464
+ }
4465
+ function cloneCall(call) {
4466
+ return {
4467
+ args: call.args.map((arg) => Array.isArray(arg) ? [...arg] : arg),
4468
+ type: call.type
4469
+ };
4470
+ }
4471
+ function createHeadlessCanvasContext(driver, canvasId, scope) {
4472
+ const defaultState = {
4473
+ fillStyle: "#000000",
4474
+ fontSize: 16,
4475
+ globalAlpha: 1,
4476
+ lineCap: "butt",
4477
+ lineDash: [],
4478
+ lineDashOffset: 0,
4479
+ lineJoin: "miter",
4480
+ miterLimit: 10,
4481
+ lineWidth: 1,
4482
+ shadowBlur: 0,
4483
+ shadowColor: "#000000",
4484
+ shadowOffsetX: 0,
4485
+ shadowOffsetY: 0,
4486
+ strokeStyle: "#000000",
4487
+ textAlign: "start",
4488
+ textBaseline: "alphabetic"
4489
+ };
4490
+ let state = { ...defaultState };
4491
+ let stateStack = [];
4492
+ let drawCalls = [];
4493
+ let snapshot = {
4494
+ canvasId,
4495
+ drawCalls: [],
4496
+ fillStyle: state.fillStyle,
4497
+ fontSize: state.fontSize,
4498
+ globalAlpha: state.globalAlpha,
4499
+ lineCap: state.lineCap,
4500
+ lineDash: [...state.lineDash],
4501
+ lineDashOffset: state.lineDashOffset,
4502
+ lineJoin: state.lineJoin,
4503
+ miterLimit: state.miterLimit,
4504
+ lineWidth: state.lineWidth,
4505
+ reserve: false,
4506
+ shadowBlur: state.shadowBlur,
4507
+ shadowColor: state.shadowColor,
4508
+ shadowOffsetX: state.shadowOffsetX,
4509
+ shadowOffsetY: state.shadowOffsetY,
4510
+ strokeStyle: state.strokeStyle,
4511
+ textAlign: state.textAlign,
4512
+ textBaseline: state.textBaseline
4513
+ };
4514
+ const resolveCanvas = () => {
4515
+ const scopeResolution = driver.resolveScope(scope);
4516
+ if (scopeResolution.kind === "missing") return null;
4517
+ const rendered = driver.renderCurrentPage();
4518
+ const root = scopeResolution.kind === "component" ? resolveSelectorQueryScopeRoot(rendered.root, scopeResolution.scopeId) : rendered.root;
4519
+ return root ? findCanvasNode(root, canvasId) : null;
4520
+ };
4521
+ const ensureCanvas = () => {
4522
+ const node = resolveCanvas();
4523
+ if (!node) throw new Error(`Canvas with canvas-id "${canvasId}" was not found in headless runtime.`);
4524
+ return node;
4525
+ };
4526
+ const record = (type, args) => {
4527
+ ensureCanvas();
4528
+ drawCalls.push({
4529
+ args,
4530
+ type
4531
+ });
4532
+ };
4533
+ return {
4534
+ arc(x, y, r, sAngle, eAngle, counterclockwise) {
4535
+ record("arc", [
4536
+ x,
4537
+ y,
4538
+ r,
4539
+ sAngle,
4540
+ eAngle,
4541
+ Boolean(counterclockwise)
4542
+ ]);
4543
+ },
4544
+ arcTo(x1, y1, x2, y2, radius) {
4545
+ record("arcTo", [
4546
+ x1,
4547
+ y1,
4548
+ x2,
4549
+ y2,
4550
+ radius
4551
+ ]);
4552
+ },
4553
+ bezierCurveTo(cp1x, cp1y, cp2x, cp2y, x, y) {
4554
+ record("bezierCurveTo", [
4555
+ cp1x,
4556
+ cp1y,
4557
+ cp2x,
4558
+ cp2y,
4559
+ x,
4560
+ y
4561
+ ]);
4562
+ },
4563
+ __getSnapshot() {
4564
+ return {
4565
+ canvasId: snapshot.canvasId,
4566
+ drawCalls: snapshot.drawCalls.map(cloneCall),
4567
+ fillStyle: snapshot.fillStyle,
4568
+ fontSize: snapshot.fontSize,
4569
+ globalAlpha: snapshot.globalAlpha,
4570
+ lineCap: snapshot.lineCap,
4571
+ lineDash: [...snapshot.lineDash],
4572
+ lineDashOffset: snapshot.lineDashOffset,
4573
+ lineJoin: snapshot.lineJoin,
4574
+ miterLimit: snapshot.miterLimit,
4575
+ lineWidth: snapshot.lineWidth,
4576
+ reserve: snapshot.reserve,
4577
+ shadowBlur: snapshot.shadowBlur,
4578
+ shadowColor: snapshot.shadowColor,
4579
+ shadowOffsetX: snapshot.shadowOffsetX,
4580
+ shadowOffsetY: snapshot.shadowOffsetY,
4581
+ strokeStyle: snapshot.strokeStyle,
4582
+ textAlign: snapshot.textAlign,
4583
+ textBaseline: snapshot.textBaseline
4584
+ };
4585
+ },
4586
+ beginPath() {
4587
+ record("beginPath", []);
4588
+ },
4589
+ clearRect(x, y, width, height) {
4590
+ record("clearRect", [
4591
+ x,
4592
+ y,
4593
+ width,
4594
+ height
4595
+ ]);
4596
+ },
4597
+ clip(fillRule) {
4598
+ record("clip", fillRule == null ? [] : [fillRule]);
4599
+ },
4600
+ closePath() {
4601
+ record("closePath", []);
4602
+ },
4603
+ draw(reserve, callback) {
4604
+ ensureCanvas();
4605
+ snapshot = {
4606
+ canvasId,
4607
+ drawCalls: reserve ? [...snapshot.drawCalls.map(cloneCall), ...drawCalls.map(cloneCall)] : drawCalls.map(cloneCall),
4608
+ fillStyle: state.fillStyle,
4609
+ fontSize: state.fontSize,
4610
+ globalAlpha: state.globalAlpha,
4611
+ lineCap: state.lineCap,
4612
+ lineDash: [...state.lineDash],
4613
+ lineDashOffset: state.lineDashOffset,
4614
+ lineJoin: state.lineJoin,
4615
+ miterLimit: state.miterLimit,
4616
+ lineWidth: state.lineWidth,
4617
+ reserve: Boolean(reserve),
4618
+ shadowBlur: state.shadowBlur,
4619
+ shadowColor: state.shadowColor,
4620
+ shadowOffsetX: state.shadowOffsetX,
4621
+ shadowOffsetY: state.shadowOffsetY,
4622
+ strokeStyle: state.strokeStyle,
4623
+ textAlign: state.textAlign,
4624
+ textBaseline: state.textBaseline
4625
+ };
4626
+ drawCalls = [];
4627
+ state = { ...defaultState };
4628
+ stateStack = [];
4629
+ callback?.();
4630
+ },
4631
+ drawImage(image, ...args) {
4632
+ record("drawImage", [image, ...args]);
4633
+ },
4634
+ fill(fillRule) {
4635
+ record("fill", fillRule == null ? [] : [fillRule]);
4636
+ },
4637
+ fillRect(x, y, width, height) {
4638
+ record("fillRect", [
4639
+ x,
4640
+ y,
4641
+ width,
4642
+ height
4643
+ ]);
4644
+ },
4645
+ fillText(text, x, y, maxWidth) {
4646
+ record("fillText", maxWidth == null ? [
4647
+ text,
4648
+ x,
4649
+ y
4650
+ ] : [
4651
+ text,
4652
+ x,
4653
+ y,
4654
+ maxWidth
4655
+ ]);
4656
+ },
4657
+ lineTo(x, y) {
4658
+ record("lineTo", [x, y]);
4659
+ },
4660
+ measureText(text) {
4661
+ return { width: text.length * state.fontSize * .5 };
4662
+ },
4663
+ moveTo(x, y) {
4664
+ record("moveTo", [x, y]);
4665
+ },
4666
+ quadraticCurveTo(cpx, cpy, x, y) {
4667
+ record("quadraticCurveTo", [
4668
+ cpx,
4669
+ cpy,
4670
+ x,
4671
+ y
4672
+ ]);
4673
+ },
4674
+ rect(x, y, width, height) {
4675
+ record("rect", [
4676
+ x,
4677
+ y,
4678
+ width,
4679
+ height
4680
+ ]);
4681
+ },
4682
+ restore() {
4683
+ state = stateStack.pop() ?? { ...defaultState };
4684
+ record("restore", []);
4685
+ },
4686
+ rotate(rotate) {
4687
+ record("rotate", [rotate]);
4688
+ },
4689
+ save() {
4690
+ stateStack.push({ ...state });
4691
+ record("save", []);
4692
+ },
4693
+ scale(scaleWidth, scaleHeight) {
4694
+ record("scale", [scaleWidth, scaleHeight]);
4695
+ },
4696
+ setFillStyle(value) {
4697
+ state.fillStyle = String(value);
4698
+ record("setFillStyle", [value]);
4699
+ },
4700
+ setFontSize(fontSize) {
4701
+ state.fontSize = Number(fontSize);
4702
+ record("setFontSize", [fontSize]);
4703
+ },
4704
+ setGlobalAlpha(value) {
4705
+ state.globalAlpha = Number(value);
4706
+ record("setGlobalAlpha", [value]);
4707
+ },
4708
+ setLineCap(value) {
4709
+ state.lineCap = String(value);
4710
+ record("setLineCap", [value]);
4711
+ },
4712
+ setLineDash(pattern, offset) {
4713
+ state.lineDash = pattern.map((item) => Number(item));
4714
+ state.lineDashOffset = offset == null ? 0 : Number(offset);
4715
+ record("setLineDash", offset == null ? [pattern.map((item) => Number(item))] : [pattern.map((item) => Number(item)), Number(offset)]);
4716
+ },
4717
+ setLineJoin(value) {
4718
+ state.lineJoin = String(value);
4719
+ record("setLineJoin", [value]);
4720
+ },
4721
+ setMiterLimit(value) {
4722
+ state.miterLimit = Number(value);
4723
+ record("setMiterLimit", [value]);
4724
+ },
4725
+ setLineWidth(value) {
4726
+ state.lineWidth = Number(value);
4727
+ record("setLineWidth", [value]);
4728
+ },
4729
+ setShadow(offsetX, offsetY, blur, color) {
4730
+ state.shadowOffsetX = Number(offsetX);
4731
+ state.shadowOffsetY = Number(offsetY);
4732
+ state.shadowBlur = Number(blur);
4733
+ state.shadowColor = String(color);
4734
+ record("setShadow", [
4735
+ offsetX,
4736
+ offsetY,
4737
+ blur,
4738
+ color
4739
+ ]);
4740
+ },
4741
+ setStrokeStyle(value) {
4742
+ state.strokeStyle = String(value);
4743
+ record("setStrokeStyle", [value]);
4744
+ },
4745
+ setTextAlign(value) {
4746
+ state.textAlign = String(value);
4747
+ record("setTextAlign", [value]);
4748
+ },
4749
+ setTextBaseline(value) {
4750
+ state.textBaseline = String(value);
4751
+ record("setTextBaseline", [value]);
4752
+ },
4753
+ stroke() {
4754
+ record("stroke", []);
4755
+ },
4756
+ strokeRect(x, y, width, height) {
4757
+ record("strokeRect", [
4758
+ x,
4759
+ y,
4760
+ width,
4761
+ height
4762
+ ]);
4763
+ },
4764
+ strokeText(text, x, y, maxWidth) {
4765
+ record("strokeText", maxWidth == null ? [
4766
+ text,
4767
+ x,
4768
+ y
4769
+ ] : [
4770
+ text,
4771
+ x,
4772
+ y,
4773
+ maxWidth
4774
+ ]);
4775
+ },
4776
+ translate(x, y) {
4777
+ record("translate", [x, y]);
4778
+ }
4779
+ };
4780
+ }
4781
+ //#endregion
4782
+ //#region ../../mpcore/packages/simulator/src/view/intersectionObserver.ts
4783
+ const NUMERIC_LIKE_VALUE_RE = /-?\d+(?:\.\d+)?/;
4784
+ function normalizeMargins(margins) {
4785
+ return {
4786
+ bottom: Number(margins?.bottom) || 0,
4787
+ left: Number(margins?.left) || 0,
4788
+ right: Number(margins?.right) || 0,
4789
+ top: Number(margins?.top) || 0
4790
+ };
4791
+ }
4792
+ function parseStyleDeclarations(styleValue) {
4793
+ const declarations = {};
4794
+ if (!styleValue) return declarations;
4795
+ for (const declaration of styleValue.split(";")) {
4796
+ const [rawProperty, ...rawValueParts] = declaration.split(":");
4797
+ const property = rawProperty?.trim();
4798
+ if (!property) continue;
4799
+ declarations[property] = rawValueParts.join(":").trim();
4800
+ }
4801
+ return declarations;
4802
+ }
4803
+ function parseNumericLikeValue(value) {
4804
+ if (!value) return 0;
4805
+ const match = value.match(NUMERIC_LIKE_VALUE_RE);
4806
+ return match ? Number(match[0]) : 0;
4807
+ }
4808
+ function resolveRect(node) {
4809
+ const style = parseStyleDeclarations(node.attribs?.style);
4810
+ const left = parseNumericLikeValue(node.attribs?.["data-sim-left"] ?? style.left);
4811
+ const top = parseNumericLikeValue(node.attribs?.["data-sim-top"] ?? style.top);
4812
+ const width = parseNumericLikeValue(node.attribs?.["data-sim-width"] ?? style.width);
4813
+ const height = parseNumericLikeValue(node.attribs?.["data-sim-height"] ?? style.height);
4814
+ return {
4815
+ bottom: top + height,
4816
+ height,
4817
+ left,
4818
+ right: left + width,
4819
+ top,
4820
+ width
4821
+ };
4822
+ }
4823
+ function applyMargins(rect, margins) {
4824
+ const top = rect.top - (margins.top ?? 0);
4825
+ const left = rect.left - (margins.left ?? 0);
4826
+ const right = rect.right + (margins.right ?? 0);
4827
+ const bottom = rect.bottom + (margins.bottom ?? 0);
4828
+ return {
4829
+ bottom,
4830
+ height: Math.max(0, bottom - top),
4831
+ left,
4832
+ right,
4833
+ top,
4834
+ width: Math.max(0, right - left)
4835
+ };
4836
+ }
4837
+ function intersectRects(source, target) {
4838
+ const left = Math.max(source.left, target.left);
4839
+ const top = Math.max(source.top, target.top);
4840
+ const right = Math.min(source.right, target.right);
4841
+ const bottom = Math.min(source.bottom, target.bottom);
4842
+ if (right <= left || bottom <= top) return {
4843
+ bottom: top,
4844
+ height: 0,
4845
+ left,
4846
+ right: left,
4847
+ top,
4848
+ width: 0
4849
+ };
4850
+ return {
4851
+ bottom,
4852
+ height: bottom - top,
4853
+ left,
4854
+ right,
4855
+ top,
4856
+ width: right - left
4857
+ };
4858
+ }
4859
+ function resolveViewportRect(windowInfo) {
4860
+ return {
4861
+ bottom: windowInfo.windowHeight,
4862
+ height: windowInfo.windowHeight,
4863
+ left: 0,
4864
+ right: windowInfo.windowWidth,
4865
+ top: 0,
4866
+ width: windowInfo.windowWidth
4867
+ };
4868
+ }
4869
+ function createHeadlessIntersectionObserver(driver, scope, _options) {
4870
+ let active = true;
4871
+ let relativeTarget = {
4872
+ kind: "viewport",
4873
+ margins: normalizeMargins()
4874
+ };
4875
+ const resolveScopedRoot = () => {
4876
+ const scopeResolution = driver.resolveScope(scope);
4877
+ if (scopeResolution.kind === "missing") return null;
4878
+ const rendered = driver.renderCurrentPage();
4879
+ return scopeResolution.kind === "component" ? resolveSelectorQueryScopeRoot(rendered.root, scopeResolution.scopeId) : rendered.root;
4880
+ };
4881
+ const resolveTargetNode = (selector) => {
4882
+ const scopedRoot = resolveScopedRoot();
4883
+ if (!scopedRoot) return null;
4884
+ return querySelectorAll(scopedRoot, selector)[0] ?? null;
4885
+ };
4886
+ const resolveRelativeRect = () => {
4887
+ if (relativeTarget.kind === "viewport") return applyMargins(resolveViewportRect(driver.getWindowInfo()), relativeTarget.margins);
4888
+ const relativeNode = resolveTargetNode(relativeTarget.selector);
4889
+ if (!relativeNode) return null;
4890
+ return applyMargins(resolveRect(relativeNode), relativeTarget.margins);
4891
+ };
4892
+ return {
4893
+ disconnect() {
4894
+ active = false;
4895
+ },
4896
+ observe(selector, callback) {
4897
+ if (!active) return;
4898
+ const targetNode = resolveTargetNode(selector);
4899
+ const relativeRect = resolveRelativeRect();
4900
+ if (!targetNode || !relativeRect) return;
4901
+ const boundingClientRect = resolveRect(targetNode);
4902
+ const intersectionRect = intersectRects(boundingClientRect, relativeRect);
4903
+ const targetArea = boundingClientRect.width * boundingClientRect.height;
4904
+ const intersectionArea = intersectionRect.width * intersectionRect.height;
4905
+ callback({
4906
+ boundingClientRect,
4907
+ id: targetNode.attribs?.id ?? "",
4908
+ intersectionRatio: targetArea > 0 ? intersectionArea / targetArea : 0,
4909
+ intersectionRect,
4910
+ relativeRect
4911
+ });
4912
+ },
4913
+ relativeTo(selector, margins) {
4914
+ relativeTarget = {
4915
+ kind: "selector",
4916
+ margins: normalizeMargins(margins),
4917
+ selector
4918
+ };
4919
+ return this;
4920
+ },
4921
+ relativeToViewport(margins) {
4922
+ relativeTarget = {
4923
+ kind: "viewport",
4924
+ margins: normalizeMargins(margins)
4925
+ };
4926
+ return this;
4927
+ }
4928
+ };
4929
+ }
4930
+ //#endregion
4931
+ //#region ../../mpcore/packages/simulator/src/view/mediaQueryObserver.ts
4932
+ function resolveOrientation(windowInfo) {
4933
+ return windowInfo.windowWidth >= windowInfo.windowHeight ? "landscape" : "portrait";
4934
+ }
4935
+ function resolveMatches(descriptor, windowInfo) {
4936
+ if (descriptor.width != null && windowInfo.windowWidth !== descriptor.width) return false;
4937
+ if (descriptor.minWidth != null && windowInfo.windowWidth < descriptor.minWidth) return false;
4938
+ if (descriptor.maxWidth != null && windowInfo.windowWidth > descriptor.maxWidth) return false;
4939
+ if (descriptor.height != null && windowInfo.windowHeight !== descriptor.height) return false;
4940
+ if (descriptor.minHeight != null && windowInfo.windowHeight < descriptor.minHeight) return false;
4941
+ if (descriptor.maxHeight != null && windowInfo.windowHeight > descriptor.maxHeight) return false;
4942
+ if (descriptor.orientation != null && resolveOrientation(windowInfo) !== descriptor.orientation) return false;
4943
+ return true;
4944
+ }
4945
+ function createHeadlessMediaQueryObserver(driver, onDisconnect) {
4946
+ let active = true;
4947
+ let callback;
4948
+ let descriptor;
4949
+ let hasObserved = false;
4950
+ let lastMatches;
4951
+ const emit = (force) => {
4952
+ if (!active || !hasObserved || !callback || !descriptor) return;
4953
+ const matches = resolveMatches(descriptor, driver.getWindowInfo());
4954
+ if (!force && lastMatches === matches) return;
4955
+ lastMatches = matches;
4956
+ callback({ matches });
4957
+ };
4958
+ const observer = {
4959
+ disconnect() {
4960
+ if (!active) return;
4961
+ active = false;
4962
+ hasObserved = false;
4963
+ callback = void 0;
4964
+ descriptor = void 0;
4965
+ onDisconnect?.();
4966
+ },
4967
+ observe(nextDescriptor, nextCallback) {
4968
+ if (!active) return;
4969
+ descriptor = { ...nextDescriptor };
4970
+ callback = nextCallback;
4971
+ hasObserved = true;
4972
+ emit(true);
4973
+ }
4974
+ };
4975
+ return {
4976
+ disconnect: observer.disconnect,
4977
+ notify() {
4978
+ emit(false);
4979
+ },
4980
+ observer
4981
+ };
4982
+ }
4983
+ //#endregion
4984
+ //#region ../../mpcore/packages/simulator/src/view/videoContext.ts
4985
+ const DATASET_NAME_RE = /-([a-z])/g;
4986
+ function toDatasetKey(attributeName) {
4987
+ return attributeName.slice(5).replace(DATASET_NAME_RE, (_match, char) => char.toUpperCase());
4988
+ }
4989
+ function collectDataset(node) {
4990
+ const dataset = {};
4991
+ for (const [key, value] of Object.entries(node.attribs ?? {})) {
4992
+ if (!key.startsWith("data-") || key.startsWith("data-sim-")) continue;
4993
+ dataset[toDatasetKey(key)] = value;
4994
+ }
4995
+ return dataset;
4996
+ }
4997
+ function resolveEventBinding(node, eventName) {
4998
+ const normalizedEventName = eventName.trim();
4999
+ if (!normalizedEventName) return null;
5000
+ const bindingAttrs = [
5001
+ `bind${normalizedEventName}`,
5002
+ `bind:${normalizedEventName}`,
5003
+ `catch${normalizedEventName}`,
5004
+ `catch:${normalizedEventName}`
5005
+ ];
5006
+ for (const attributeName of bindingAttrs) {
5007
+ const methodName = node.attribs?.[attributeName]?.trim();
5008
+ if (methodName) return methodName;
5009
+ }
5010
+ return null;
5011
+ }
5012
+ function createEventPayload(node, eventName, detail) {
5013
+ const dataset = collectDataset(node);
5014
+ const nodeId = node.attribs?.id ?? "";
5015
+ return {
5016
+ bubbles: false,
5017
+ capturePhase: false,
5018
+ composed: false,
5019
+ currentTarget: {
5020
+ dataset,
5021
+ id: nodeId
5022
+ },
5023
+ detail,
5024
+ mark: void 0,
5025
+ target: {
5026
+ dataset,
5027
+ id: nodeId
5028
+ },
5029
+ type: eventName
5030
+ };
5031
+ }
5032
+ function createHeadlessVideoContext(driver, videoId, scope) {
5033
+ const state = {
5034
+ currentTime: 0,
5035
+ fullScreen: false,
5036
+ paused: true
5037
+ };
5038
+ const resolveNode = () => {
5039
+ const scopeResolution = driver.resolveScope(scope);
5040
+ if (scopeResolution.kind === "missing") return null;
5041
+ const rendered = driver.renderCurrentPage();
5042
+ return querySelectorAll(scopeResolution.kind === "component" ? resolveSelectorQueryScopeRoot(rendered.root, scopeResolution.scopeId) : rendered.root, `#${videoId}`)[0] ?? null;
5043
+ };
5044
+ const dispatch = (eventName, detail) => {
5045
+ const node = resolveNode();
5046
+ if (!node) return;
5047
+ const methodName = resolveEventBinding(node, eventName);
5048
+ if (!methodName) return;
5049
+ driver.callScopeMethod(node.attribs?.["data-sim-scope"] ?? null, methodName, createEventPayload(node, eventName, detail));
5050
+ };
5051
+ return {
5052
+ exitFullScreen() {
5053
+ state.fullScreen = false;
5054
+ dispatch("fullscreenchange", {
5055
+ currentTime: state.currentTime,
5056
+ fullScreen: false
5057
+ });
5058
+ },
5059
+ pause() {
5060
+ state.paused = true;
5061
+ dispatch("pause", { currentTime: state.currentTime });
5062
+ },
5063
+ play() {
5064
+ state.paused = false;
5065
+ dispatch("play", { currentTime: state.currentTime });
5066
+ },
5067
+ requestFullScreen() {
5068
+ state.fullScreen = true;
5069
+ dispatch("fullscreenchange", {
5070
+ currentTime: state.currentTime,
5071
+ fullScreen: true
5072
+ });
5073
+ },
5074
+ seek(position) {
5075
+ state.currentTime = Number.isFinite(position) ? Number(position) : 0;
5076
+ },
5077
+ stop() {
5078
+ state.currentTime = 0;
5079
+ state.paused = true;
5080
+ dispatch("pause", { currentTime: state.currentTime });
5081
+ }
5082
+ };
5083
+ }
5084
+ //#endregion
4213
5085
  //#region ../../mpcore/packages/simulator/src/runtime/moduleLoader.ts
4214
5086
  function createRequireNotFoundError(request, importer) {
4215
5087
  return /* @__PURE__ */ new Error(`Cannot resolve require("${request}") from ${normalize(importer)} in headless runtime.`);
@@ -4387,9 +5259,228 @@ const DEFAULT_MODAL_CONFIRM_TEXT = "确定";
4387
5259
  const TRAILING_SLASH_RE = /\/+$/;
4388
5260
  const SAVED_FILE_PREFIXES = ["headless://saved/", "headless://wxfile/saved/"];
4389
5261
  const textEncoder = new TextEncoder();
5262
+ const IMAGE_EXTENSION_RE = /\.([^.?#/]+)(?:[?#].*)?$/;
5263
+ const LEADING_DOT_RE = /^\.+/;
5264
+ const IMAGE_FILE_EXTENSIONS = new Set([
5265
+ "bmp",
5266
+ "gif",
5267
+ "heic",
5268
+ "jpeg",
5269
+ "jpg",
5270
+ "png",
5271
+ "webp"
5272
+ ]);
5273
+ const VIDEO_FILE_EXTENSIONS = new Set([
5274
+ "avi",
5275
+ "m4v",
5276
+ "mov",
5277
+ "mp4",
5278
+ "mpeg",
5279
+ "mpg",
5280
+ "webm"
5281
+ ]);
4390
5282
  function byteLength(input) {
4391
5283
  return textEncoder.encode(input).byteLength;
4392
5284
  }
5285
+ function rotateLeft(value, bits) {
5286
+ return (value << bits | value >>> 32 - bits) >>> 0;
5287
+ }
5288
+ function toHexWordLittleEndian(value) {
5289
+ let output = "";
5290
+ for (let index = 0; index < 4; index += 1) output += (value >>> index * 8 & 255).toString(16).padStart(2, "0");
5291
+ return output;
5292
+ }
5293
+ function toHexWordBigEndian(value) {
5294
+ return value.toString(16).padStart(8, "0");
5295
+ }
5296
+ function computeMd5Digest(input) {
5297
+ const bytes = Array.from(textEncoder.encode(input));
5298
+ const bitLength = bytes.length * 8;
5299
+ const lowBits = bitLength >>> 0;
5300
+ const highBits = Math.floor(bitLength / 4294967296) >>> 0;
5301
+ bytes.push(128);
5302
+ while (bytes.length % 64 !== 56) bytes.push(0);
5303
+ for (let index = 0; index < 4; index += 1) bytes.push(lowBits >>> index * 8 & 255);
5304
+ for (let index = 0; index < 4; index += 1) bytes.push(highBits >>> index * 8 & 255);
5305
+ const shifts = [
5306
+ 7,
5307
+ 12,
5308
+ 17,
5309
+ 22,
5310
+ 7,
5311
+ 12,
5312
+ 17,
5313
+ 22,
5314
+ 7,
5315
+ 12,
5316
+ 17,
5317
+ 22,
5318
+ 7,
5319
+ 12,
5320
+ 17,
5321
+ 22,
5322
+ 5,
5323
+ 9,
5324
+ 14,
5325
+ 20,
5326
+ 5,
5327
+ 9,
5328
+ 14,
5329
+ 20,
5330
+ 5,
5331
+ 9,
5332
+ 14,
5333
+ 20,
5334
+ 5,
5335
+ 9,
5336
+ 14,
5337
+ 20,
5338
+ 4,
5339
+ 11,
5340
+ 16,
5341
+ 23,
5342
+ 4,
5343
+ 11,
5344
+ 16,
5345
+ 23,
5346
+ 4,
5347
+ 11,
5348
+ 16,
5349
+ 23,
5350
+ 4,
5351
+ 11,
5352
+ 16,
5353
+ 23,
5354
+ 6,
5355
+ 10,
5356
+ 15,
5357
+ 21,
5358
+ 6,
5359
+ 10,
5360
+ 15,
5361
+ 21,
5362
+ 6,
5363
+ 10,
5364
+ 15,
5365
+ 21,
5366
+ 6,
5367
+ 10,
5368
+ 15,
5369
+ 21
5370
+ ];
5371
+ const table = Array.from({ length: 64 }, (_, index) => Math.floor(Math.abs(Math.sin(index + 1)) * 4294967296) >>> 0);
5372
+ let a0 = 1732584193;
5373
+ let b0 = 4023233417;
5374
+ let c0 = 2562383102;
5375
+ let d0 = 271733878;
5376
+ for (let chunkOffset = 0; chunkOffset < bytes.length; chunkOffset += 64) {
5377
+ const words = Array.from({ length: 16 }, (_, index) => {
5378
+ const wordOffset = chunkOffset + index * 4;
5379
+ return (bytes[wordOffset] | bytes[wordOffset + 1] << 8 | bytes[wordOffset + 2] << 16 | bytes[wordOffset + 3] << 24) >>> 0;
5380
+ });
5381
+ let a = a0;
5382
+ let b = b0;
5383
+ let c = c0;
5384
+ let d = d0;
5385
+ for (let index = 0; index < 64; index += 1) {
5386
+ let f = 0;
5387
+ let g = 0;
5388
+ if (index < 16) {
5389
+ f = (b & c | ~b & d) >>> 0;
5390
+ g = index;
5391
+ } else if (index < 32) {
5392
+ f = (d & b | ~d & c) >>> 0;
5393
+ g = (5 * index + 1) % 16;
5394
+ } else if (index < 48) {
5395
+ f = (b ^ c ^ d) >>> 0;
5396
+ g = (3 * index + 5) % 16;
5397
+ } else {
5398
+ f = (c ^ (b | ~d)) >>> 0;
5399
+ g = 7 * index % 16;
5400
+ }
5401
+ const next = a + f + table[index] + words[g] >>> 0;
5402
+ a = d;
5403
+ d = c;
5404
+ c = b;
5405
+ b = b + rotateLeft(next, shifts[index]) >>> 0;
5406
+ }
5407
+ a0 = a0 + a >>> 0;
5408
+ b0 = b0 + b >>> 0;
5409
+ c0 = c0 + c >>> 0;
5410
+ d0 = d0 + d >>> 0;
5411
+ }
5412
+ return [
5413
+ toHexWordLittleEndian(a0),
5414
+ toHexWordLittleEndian(b0),
5415
+ toHexWordLittleEndian(c0),
5416
+ toHexWordLittleEndian(d0)
5417
+ ].join("");
5418
+ }
5419
+ function computeSha1Digest(input) {
5420
+ const bytes = Array.from(textEncoder.encode(input));
5421
+ const bitLength = bytes.length * 8;
5422
+ const lowBits = bitLength >>> 0;
5423
+ const highBits = Math.floor(bitLength / 4294967296) >>> 0;
5424
+ bytes.push(128);
5425
+ while (bytes.length % 64 !== 56) bytes.push(0);
5426
+ for (let index = 3; index >= 0; index -= 1) bytes.push(highBits >>> index * 8 & 255);
5427
+ for (let index = 3; index >= 0; index -= 1) bytes.push(lowBits >>> index * 8 & 255);
5428
+ let h0 = 1732584193;
5429
+ let h1 = 4023233417;
5430
+ let h2 = 2562383102;
5431
+ let h3 = 271733878;
5432
+ let h4 = 3285377520;
5433
+ for (let chunkOffset = 0; chunkOffset < bytes.length; chunkOffset += 64) {
5434
+ const words = Array.from({ length: 80 }, (_, index) => {
5435
+ if (index < 16) {
5436
+ const wordOffset = chunkOffset + index * 4;
5437
+ return (bytes[wordOffset] << 24 | bytes[wordOffset + 1] << 16 | bytes[wordOffset + 2] << 8 | bytes[wordOffset + 3]) >>> 0;
5438
+ }
5439
+ return 0;
5440
+ });
5441
+ for (let index = 16; index < 80; index += 1) words[index] = rotateLeft(words[index - 3] ^ words[index - 8] ^ words[index - 14] ^ words[index - 16], 1);
5442
+ let a = h0;
5443
+ let b = h1;
5444
+ let c = h2;
5445
+ let d = h3;
5446
+ let e = h4;
5447
+ for (let index = 0; index < 80; index += 1) {
5448
+ let f = 0;
5449
+ let k = 0;
5450
+ if (index < 20) {
5451
+ f = (b & c | ~b & d) >>> 0;
5452
+ k = 1518500249;
5453
+ } else if (index < 40) {
5454
+ f = (b ^ c ^ d) >>> 0;
5455
+ k = 1859775393;
5456
+ } else if (index < 60) {
5457
+ f = (b & c | b & d | c & d) >>> 0;
5458
+ k = 2400959708;
5459
+ } else {
5460
+ f = (b ^ c ^ d) >>> 0;
5461
+ k = 3395469782;
5462
+ }
5463
+ const next = rotateLeft(a, 5) + f + e + k + words[index] >>> 0;
5464
+ e = d;
5465
+ d = c;
5466
+ c = rotateLeft(b, 30);
5467
+ b = a;
5468
+ a = next;
5469
+ }
5470
+ h0 = h0 + a >>> 0;
5471
+ h1 = h1 + b >>> 0;
5472
+ h2 = h2 + c >>> 0;
5473
+ h3 = h3 + d >>> 0;
5474
+ h4 = h4 + e >>> 0;
5475
+ }
5476
+ return [
5477
+ toHexWordBigEndian(h0),
5478
+ toHexWordBigEndian(h1),
5479
+ toHexWordBigEndian(h2),
5480
+ toHexWordBigEndian(h3),
5481
+ toHexWordBigEndian(h4)
5482
+ ].join("");
5483
+ }
4393
5484
  function cloneShareMenuSnapshot(snapshot) {
4394
5485
  return {
4395
5486
  isUpdatableMessage: snapshot.isUpdatableMessage,
@@ -4398,6 +5489,118 @@ function cloneShareMenuSnapshot(snapshot) {
4398
5489
  withShareTicket: snapshot.withShareTicket
4399
5490
  };
4400
5491
  }
5492
+ function normalizeImageType(type) {
5493
+ const normalized = (type ?? "").trim().toLowerCase();
5494
+ if (normalized === "jpg") return "jpeg";
5495
+ return normalized;
5496
+ }
5497
+ function normalizeFileExtension(extension) {
5498
+ return extension.trim().replace(LEADING_DOT_RE, "").toLowerCase();
5499
+ }
5500
+ function inferDocumentFileType(filePath) {
5501
+ const extensionMatch = filePath.match(IMAGE_EXTENSION_RE);
5502
+ return extensionMatch ? normalizeFileExtension(extensionMatch[1]) : "unknown";
5503
+ }
5504
+ function isImageFileExtension(extension) {
5505
+ return IMAGE_FILE_EXTENSIONS.has(normalizeFileExtension(extension));
5506
+ }
5507
+ function isVideoFileExtension(extension) {
5508
+ return VIDEO_FILE_EXTENSIONS.has(normalizeFileExtension(extension));
5509
+ }
5510
+ function resolveMessageFileExtensions(option) {
5511
+ const filteredExtensions = (Array.isArray(option.extension) ? option.extension.filter((item) => typeof item === "string").map(normalizeFileExtension).filter(Boolean) : []).filter((extension) => {
5512
+ if (option.type === "image") return isImageFileExtension(extension);
5513
+ if (option.type === "video") return isVideoFileExtension(extension);
5514
+ if (option.type === "file") return !isImageFileExtension(extension) && !isVideoFileExtension(extension);
5515
+ return true;
5516
+ });
5517
+ if (filteredExtensions.length > 0) return filteredExtensions;
5518
+ if (option.type === "image") return ["png", "jpg"];
5519
+ if (option.type === "video") return ["mp4"];
5520
+ if (option.type === "file") return [
5521
+ "txt",
5522
+ "pdf",
5523
+ "doc"
5524
+ ];
5525
+ return [
5526
+ "png",
5527
+ "pdf",
5528
+ "mp4"
5529
+ ];
5530
+ }
5531
+ function inferImageType(filePath, fileContent) {
5532
+ const extensionMatch = filePath.match(IMAGE_EXTENSION_RE);
5533
+ if (extensionMatch) return normalizeImageType(extensionMatch[1]);
5534
+ try {
5535
+ return normalizeImageType(JSON.parse(fileContent)?.config?.fileType) || "png";
5536
+ } catch {
5537
+ return "unknown";
5538
+ }
5539
+ }
5540
+ function inferImageSize(fileContent) {
5541
+ try {
5542
+ const config = JSON.parse(fileContent)?.config;
5543
+ const destHeight = config && "destHeight" in config ? config.destHeight : void 0;
5544
+ const destWidth = config && "destWidth" in config ? config.destWidth : void 0;
5545
+ return {
5546
+ height: destHeight ?? config?.height ?? 0,
5547
+ width: destWidth ?? config?.width ?? 0
5548
+ };
5549
+ } catch {
5550
+ return {
5551
+ height: 0,
5552
+ width: 0
5553
+ };
5554
+ }
5555
+ }
5556
+ function buildImageTempFilePayload(fileType, width, height, metadata = {}) {
5557
+ return JSON.stringify({ config: {
5558
+ fileType,
5559
+ height,
5560
+ quality: metadata.quality,
5561
+ sizeType: metadata.sizeType ?? [],
5562
+ sourceType: metadata.sourceType ?? [],
5563
+ width
5564
+ } });
5565
+ }
5566
+ function buildVideoTempFilePayload(width, height, duration, metadata = {}) {
5567
+ return JSON.stringify({ config: {
5568
+ bitrate: metadata.bitrate ?? 1500,
5569
+ compressed: metadata.compressed ?? true,
5570
+ duration,
5571
+ fps: metadata.fps ?? 30,
5572
+ height,
5573
+ maxDuration: metadata.maxDuration ?? duration,
5574
+ sourceType: metadata.sourceType ?? [],
5575
+ width
5576
+ } });
5577
+ }
5578
+ function inferVideoInfo(filePath, fileContent) {
5579
+ const size = byteLength(fileContent);
5580
+ const type = filePath.match(IMAGE_EXTENSION_RE)?.[1]?.toLowerCase() ?? "mp4";
5581
+ try {
5582
+ const payload = JSON.parse(fileContent);
5583
+ return {
5584
+ bitrate: payload.config.bitrate ?? 1500,
5585
+ duration: payload.config.duration ?? 0,
5586
+ fps: payload.config.fps ?? 30,
5587
+ height: payload.config.height ?? 0,
5588
+ size,
5589
+ type,
5590
+ width: payload.config.width ?? 0
5591
+ };
5592
+ } catch {
5593
+ return {
5594
+ bitrate: 1500,
5595
+ duration: 0,
5596
+ fps: 30,
5597
+ height: 0,
5598
+ size,
5599
+ type,
5600
+ width: 0
5601
+ };
5602
+ }
5603
+ }
4401
5604
  function createNetworkSnapshot(networkType) {
4402
5605
  return {
4403
5606
  isConnected: networkType !== "none",
@@ -4548,6 +5751,9 @@ function createHeadlessWxState() {
4548
5751
  let fileId = 0;
4549
5752
  let loading = null;
4550
5753
  let networkType = "wifi";
5754
+ let previewImage = null;
5755
+ let openedDocument = null;
5756
+ let clipboardData = "";
4551
5757
  let shareMenu = {
4552
5758
  isUpdatableMessage: false,
4553
5759
  menus: [],
@@ -4749,6 +5955,16 @@ function createHeadlessWxState() {
4749
5955
  size: savedFile.size
4750
5956
  };
4751
5957
  };
5958
+ const getFileInfo = (option) => {
5959
+ const normalizedPath = normalizeFsPath(option.filePath);
5960
+ const fileContent = files.get(normalizedPath);
5961
+ if (fileContent == null) throw new Error(`getFileInfo:fail no such file or directory, stat '${normalizedPath}'`);
5962
+ return {
5963
+ digest: (option.digestAlgorithm === "sha1" ? "sha1" : "md5") === "sha1" ? computeSha1Digest(fileContent) : computeMd5Digest(fileContent),
5964
+ errMsg: "getFileInfo:ok",
5965
+ size: byteLength(fileContent)
5966
+ };
5967
+ };
4752
5968
  const removeSavedFile = (filePath) => {
4753
5969
  const normalizedPath = normalizeFsPath(filePath);
4754
5970
  if (!savedFiles.has(normalizedPath)) throw new Error(`removeSavedFile:fail no such file or directory, unlink '${normalizedPath}'`);
@@ -4999,18 +6215,191 @@ function createHeadlessWxState() {
4999
6215
  getSavedFileInfo(option) {
5000
6216
  return getSavedFileInfo(option.filePath);
5001
6217
  },
6218
+ getFileInfo(option) {
6219
+ return getFileInfo(option);
6220
+ },
5002
6221
  getSavedFileList() {
5003
6222
  return getSavedFileList();
5004
6223
  },
5005
6224
  getFileText(filePath) {
5006
6225
  return files.get(filePath) ?? null;
5007
6226
  },
6227
+ createTempFile(fileContent, preferredPath) {
6228
+ const tempFilePath = allocateFilePath("temp", preferredPath);
6229
+ ensureDirectoryTree(tempFilePath);
6230
+ files.set(tempFilePath, String(fileContent));
6231
+ return tempFilePath;
6232
+ },
6233
+ chooseImage(option) {
6234
+ const count = Number.isFinite(option.count) ? Math.max(1, Math.min(9, Math.trunc(option.count ?? 1))) : 9;
6235
+ const sizeType = Array.isArray(option.sizeType) && option.sizeType.length > 0 ? option.sizeType.filter((item) => typeof item === "string") : ["original"];
6236
+ const sourceType = Array.isArray(option.sourceType) && option.sourceType.length > 0 ? option.sourceType.filter((item) => typeof item === "string") : ["album", "camera"];
6237
+ const fileType = sizeType.includes("compressed") ? "jpg" : "png";
6238
+ const tempFiles = Array.from({ length: count }, (_, index) => {
6239
+ const payload = buildImageTempFilePayload(fileType, 160 + index * 12, 120 + index * 8, {
6240
+ sizeType: [...sizeType],
6241
+ sourceType: [...sourceType]
6242
+ });
6243
+ return {
6244
+ path: this.createTempFile(payload, `headless://wxfile/temp/chosen-image-${String(index + 1).padStart(2, "0")}.${fileType}`),
6245
+ size: payload.length
6246
+ };
6247
+ });
6248
+ return {
6249
+ errMsg: "chooseImage:ok",
6250
+ tempFilePaths: tempFiles.map((item) => item.path),
6251
+ tempFiles
6252
+ };
6253
+ },
6254
+ chooseMessageFile(option) {
6255
+ const count = Number.isFinite(option.count) ? Math.max(1, Math.min(100, Math.trunc(option.count ?? 1))) : 1;
6256
+ const extensions = resolveMessageFileExtensions(option);
6257
+ return {
6258
+ errMsg: "chooseMessageFile:ok",
6259
+ tempFiles: Array.from({ length: count }, (_, index) => {
6260
+ const extension = extensions[index % extensions.length];
6261
+ const sequence = String(index + 1).padStart(2, "0");
6262
+ const fileName = `message-file-${sequence}.${extension}`;
6263
+ let fileContent = `headless message file ${fileName}`;
6264
+ if (isImageFileExtension(extension)) fileContent = buildImageTempFilePayload(extension, 160 + index * 12, 120 + index * 8, { sourceType: ["message"] });
6265
+ else if (isVideoFileExtension(extension)) fileContent = buildVideoTempFilePayload(640, 360, 18 + index, {
6266
+ compressed: true,
6267
+ sourceType: ["message"]
6268
+ });
6269
+ return {
6270
+ name: fileName,
6271
+ path: this.createTempFile(fileContent, `headless://wxfile/temp/chosen-message-file-${sequence}.${extension}`),
6272
+ size: byteLength(fileContent),
6273
+ time: 171e10 + index * 1e3,
6274
+ type: extension
6275
+ };
6276
+ })
6277
+ };
6278
+ },
6279
+ chooseMedia(option) {
6280
+ const count = Number.isFinite(option.count) ? Math.max(1, Math.min(9, Math.trunc(option.count ?? 1))) : 1;
6281
+ const mediaTypes = Array.isArray(option.mediaType) && option.mediaType.length > 0 ? option.mediaType.filter((item) => item === "image" || item === "video") : ["image"];
6282
+ const sourceType = Array.isArray(option.sourceType) && option.sourceType.length > 0 ? option.sourceType.filter((item) => typeof item === "string") : ["album", "camera"];
6283
+ const sizeType = Array.isArray(option.sizeType) && option.sizeType.length > 0 ? option.sizeType.filter((item) => typeof item === "string") : ["compressed"];
6284
+ const maxDuration = Number.isFinite(option.maxDuration) ? Math.max(1, Math.trunc(option.maxDuration ?? 60)) : 60;
6285
+ return {
6286
+ errMsg: "chooseMedia:ok",
6287
+ tempFiles: Array.from({ length: count }, (_, index) => {
6288
+ if (mediaTypes[index % mediaTypes.length] === "video") {
6289
+ const duration = Math.min(maxDuration, 18 + index);
6290
+ const width = 640;
6291
+ const height = 360;
6292
+ const payload = buildVideoTempFilePayload(width, height, duration, {
6293
+ compressed: sizeType.includes("compressed"),
6294
+ maxDuration,
6295
+ sourceType: [...sourceType]
6296
+ });
6297
+ const tempFilePath = this.createTempFile(payload, `headless://wxfile/temp/chosen-media-video-${String(index + 1).padStart(2, "0")}.mp4`);
6298
+ const thumbPayload = buildImageTempFilePayload("jpg", 160, 90, {
6299
+ sizeType: ["compressed"],
6300
+ sourceType: [...sourceType]
6301
+ });
6302
+ const thumbTempFilePath = this.createTempFile(thumbPayload, `headless://wxfile/temp/chosen-media-video-thumb-${String(index + 1).padStart(2, "0")}.jpg`);
6303
+ return {
6304
+ duration,
6305
+ fileType: "video",
6306
+ height,
6307
+ size: payload.length,
6308
+ tempFilePath,
6309
+ thumbTempFilePath,
6310
+ width
6311
+ };
6312
+ }
6313
+ const width = 160 + index * 12;
6314
+ const height = 120 + index * 8;
6315
+ const fileType = sizeType.includes("compressed") ? "jpg" : "png";
6316
+ const payload = buildImageTempFilePayload(fileType, width, height, {
6317
+ sizeType: [...sizeType],
6318
+ sourceType: [...sourceType]
6319
+ });
6320
+ return {
6321
+ fileType: "image",
6322
+ height,
6323
+ size: payload.length,
6324
+ tempFilePath: this.createTempFile(payload, `headless://wxfile/temp/chosen-media-image-${String(index + 1).padStart(2, "0")}.${fileType}`),
6325
+ width
6326
+ };
6327
+ }),
6328
+ type: mediaTypes.length > 1 ? "mix" : mediaTypes[0] ?? "image"
6329
+ };
6330
+ },
6331
+ chooseVideo(option) {
6332
+ const compressed = option.compressed !== false;
6333
+ const maxDuration = Number.isFinite(option.maxDuration) ? Math.max(1, Math.trunc(option.maxDuration ?? 60)) : 60;
6334
+ const sourceType = Array.isArray(option.sourceType) && option.sourceType.length > 0 ? option.sourceType.filter((item) => typeof item === "string") : ["album", "camera"];
6335
+ const payload = JSON.stringify({ config: {
6336
+ compressed,
6337
+ duration: Math.min(maxDuration, compressed ? 18 : 36),
6338
+ height: compressed ? 360 : 720,
6339
+ maxDuration,
6340
+ sourceType: [...sourceType],
6341
+ width: compressed ? 640 : 1280
6342
+ } });
6343
+ const tempFilePath = this.createTempFile(payload, "headless://wxfile/temp/chosen-video-01.mp4");
6344
+ return {
6345
+ duration: Math.min(maxDuration, compressed ? 18 : 36),
6346
+ errMsg: "chooseVideo:ok",
6347
+ height: compressed ? 360 : 720,
6348
+ size: payload.length,
6349
+ tempFilePath,
6350
+ width: compressed ? 640 : 1280
6351
+ };
6352
+ },
6353
+ compressImage(option) {
6354
+ const fileContent = files.get(option.src);
6355
+ if (fileContent == null) throw new Error(`compressImage:fail file not found: ${option.src}`);
6356
+ const sourceSize = inferImageSize(fileContent);
6357
+ const sourceType = inferImageType(option.src, fileContent);
6358
+ const width = Number.isFinite(option.compressedWidth) ? Math.max(1, Math.trunc(option.compressedWidth ?? sourceSize.width)) : Math.max(1, sourceSize.width);
6359
+ const height = Number.isFinite(option.compressedHeight) ? Math.max(1, Math.trunc(option.compressedHeight ?? sourceSize.height)) : Math.max(1, sourceSize.height);
6360
+ const normalizedType = sourceType === "unknown" ? "jpg" : sourceType;
6361
+ const fileExtension = normalizedType === "jpeg" ? "jpg" : normalizedType;
6362
+ const payload = buildImageTempFilePayload(normalizedType, width, height, { quality: Number.isFinite(option.quality) ? Number(option.quality) : void 0 });
6363
+ return {
6364
+ errMsg: "compressImage:ok",
6365
+ tempFilePath: this.createTempFile(payload, `headless://wxfile/temp/compressed-image-${String(fileId + 1).padStart(2, "0")}.${fileExtension}`)
6366
+ };
6367
+ },
5008
6368
  getNetworkType() {
5009
6369
  return {
5010
6370
  errMsg: "getNetworkType:ok",
5011
6371
  networkType
5012
6372
  };
5013
6373
  },
6374
+ getImageInfo(option) {
6375
+ const fileContent = files.get(option.src);
6376
+ if (fileContent == null) throw new Error(`getImageInfo:fail file not found: ${option.src}`);
6377
+ const size = inferImageSize(fileContent);
6378
+ return {
6379
+ errMsg: "getImageInfo:ok",
6380
+ height: size.height,
6381
+ orientation: "up",
6382
+ path: option.src,
6383
+ type: inferImageType(option.src, fileContent),
6384
+ width: size.width
6385
+ };
6386
+ },
6387
+ getVideoInfo(option) {
6388
+ const fileContent = files.get(option.src);
6389
+ if (fileContent == null) throw new Error(`getVideoInfo:fail file not found: ${option.src}`);
6390
+ const info = inferVideoInfo(option.src, fileContent);
6391
+ return {
6392
+ bitrate: info.bitrate,
6393
+ duration: info.duration,
6394
+ errMsg: "getVideoInfo:ok",
6395
+ fps: info.fps,
6396
+ height: info.height,
6397
+ orientation: "up",
6398
+ size: info.size,
6399
+ type: info.type,
6400
+ width: info.width
6401
+ };
6402
+ },
5014
6403
  getModalLogs() {
5015
6404
  return modalLogs.map((entry) => ({
5016
6405
  ...entry,
@@ -5045,6 +6434,15 @@ function createHeadlessWxState() {
5045
6434
  getStorageSnapshot() {
5046
6435
  return Object.fromEntries(Array.from(storage.entries(), ([key, value]) => [key, cloneValue(value)]));
5047
6436
  },
6437
+ getClipboardData() {
6438
+ return {
6439
+ data: clipboardData,
6440
+ errMsg: "getClipboardData:ok"
6441
+ };
6442
+ },
6443
+ getClipboardSnapshot() {
6444
+ return { data: clipboardData };
6445
+ },
5048
6446
  getStorageSync(key) {
5049
6447
  return cloneValue(storage.get(key));
5050
6448
  },
@@ -5054,6 +6452,21 @@ function createHeadlessWxState() {
5054
6452
  getLoading() {
5055
6453
  return loading ? { ...loading } : null;
5056
6454
  },
6455
+ getPreviewImage() {
6456
+ return previewImage ? {
6457
+ current: previewImage.current,
6458
+ urls: [...previewImage.urls],
6459
+ visible: previewImage.visible
6460
+ } : null;
6461
+ },
6462
+ getOpenedDocument() {
6463
+ return openedDocument ? {
6464
+ filePath: openedDocument.filePath,
6465
+ fileType: openedDocument.fileType,
6466
+ showMenu: openedDocument.showMenu,
6467
+ visible: openedDocument.visible
6468
+ } : null;
6469
+ },
5057
6470
  hideShareMenu() {
5058
6471
  shareMenu = {
5059
6472
  ...shareMenu,
@@ -5123,6 +6536,27 @@ function createHeadlessWxState() {
5123
6536
  onNetworkStatusChange(callback) {
5124
6537
  networkStatusChangeCallbacks.add(callback);
5125
6538
  },
6539
+ openDocument(option) {
6540
+ const normalizedPath = normalizeFsPath(option.filePath);
6541
+ if (!files.has(normalizedPath)) throw new Error(`openDocument:fail no such file or directory, open '${normalizedPath}'`);
6542
+ openedDocument = {
6543
+ filePath: normalizedPath,
6544
+ fileType: typeof option.fileType === "string" && option.fileType.trim() !== "" ? normalizeFileExtension(option.fileType) : inferDocumentFileType(normalizedPath),
6545
+ showMenu: option.showMenu !== false,
6546
+ visible: true
6547
+ };
6548
+ return { errMsg: "openDocument:ok" };
6549
+ },
6550
+ previewImage(option) {
6551
+ const urls = Array.isArray(option.urls) ? option.urls.filter((item) => typeof item === "string" && item.trim() !== "") : [];
6552
+ if (urls.length === 0) throw new Error("previewImage:fail invalid urls");
6553
+ previewImage = {
6554
+ current: typeof option.current === "string" && option.current.trim() !== "" ? option.current : urls[0],
6555
+ urls: [...urls],
6556
+ visible: true
6557
+ };
6558
+ return { errMsg: "previewImage:ok" };
6559
+ },
5126
6560
  removeStorageSync(key) {
5127
6561
  storage.delete(key);
5128
6562
  },
@@ -5170,6 +6604,14 @@ function createHeadlessWxState() {
5170
6604
  removeSavedFile(option) {
5171
6605
  return removeSavedFile(option.filePath);
5172
6606
  },
6607
+ saveImageToPhotosAlbum(option) {
6608
+ if (files.get(option.filePath) == null) throw new Error(`saveImageToPhotosAlbum:fail file not found: ${option.filePath}`);
6609
+ return { errMsg: "saveImageToPhotosAlbum:ok" };
6610
+ },
6611
+ saveVideoToPhotosAlbum(option) {
6612
+ if (files.get(option.filePath) == null) throw new Error(`saveVideoToPhotosAlbum:fail file not found: ${option.filePath}`);
6613
+ return { errMsg: "saveVideoToPhotosAlbum:ok" };
6614
+ },
5173
6615
  saveFile(option) {
5174
6616
  const fileContent = files.get(option.tempFilePath);
5175
6617
  if (fileContent == null) throw new Error(`saveFile:fail tempFilePath not found: ${option.tempFilePath}`);
@@ -5185,6 +6627,10 @@ function createHeadlessWxState() {
5185
6627
  setStorageSync(key, value) {
5186
6628
  storage.set(key, cloneValue(value));
5187
6629
  },
6630
+ setClipboardData(option) {
6631
+ clipboardData = String(option.data);
6632
+ return { errMsg: "setClipboardData:ok" };
6633
+ },
5188
6634
  setFile(filePath, fileContent) {
5189
6635
  const normalizedPath = normalizeFsPath(filePath);
5190
6636
  ensureDirectoryTree(normalizedPath);
@@ -5330,7 +6776,9 @@ function createHeadlessWxState() {
5330
6776
  //#region ../../mpcore/packages/simulator/src/runtime/session.ts
5331
6777
  const LEADING_SLASH_RE$1 = /^\/+/;
5332
6778
  const PAGE_STACK_LIMIT = 10;
6779
+ const WHITESPACE_RE = /\s+/;
5333
6780
  const DATA_ATTR_SELECTOR_RE = /^\[data-([^=\]]+)="([^"]*)"\]$/;
6781
+ const COMPOUND_SELECTOR_PART_RE = /#[\w-]+|\.[\w-]+|\[data-[^=\]]+="[^"]*"\]|[A-Za-z][\w-]*/g;
5334
6782
  const DATASET_KEY_RE = /-([a-z])/g;
5335
6783
  function stripLeadingSlash(route) {
5336
6784
  return route.replace(LEADING_SLASH_RE$1, "");
@@ -5351,6 +6799,28 @@ function parseNavigationUrl(url) {
5351
6799
  query: normalizeQuery(queryString)
5352
6800
  };
5353
6801
  }
6802
+ function parseCompoundSelector(selector) {
6803
+ const parts = selector.match(COMPOUND_SELECTOR_PART_RE) ?? [];
6804
+ return parts.join("") === selector ? parts : [];
6805
+ }
6806
+ function matchesComponentSelector(scope, selector) {
6807
+ const parts = parseCompoundSelector(selector);
6808
+ if (parts.length === 0) return false;
6809
+ return parts.every((part) => {
6810
+ if (part.startsWith("#")) return scope.id === part.slice(1);
6811
+ if (part.startsWith(".")) return scope.classList?.includes(part.slice(1)) ?? false;
6812
+ const dataAttrMatch = part.match(DATA_ATTR_SELECTOR_RE);
6813
+ if (dataAttrMatch) {
6814
+ const [, key, value] = dataAttrMatch;
6815
+ const datasetKey = key.replace(DATASET_KEY_RE, (_match, char) => char.toUpperCase());
6816
+ return scope.dataset?.[datasetKey] === value;
6817
+ }
6818
+ return scope.alias === part;
6819
+ });
6820
+ }
6821
+ function normalizeSelectorParts(selector) {
6822
+ return selector.trim().split(WHITESPACE_RE).filter(Boolean);
6823
+ }
5354
6824
  function createAppLaunchOptions(pathname, query) {
5355
6825
  return {
5356
6826
  path: stripLeadingSlash(pathname),
@@ -5392,6 +6862,8 @@ var HeadlessSession = class {
5392
6862
  tabBarState = /* @__PURE__ */ new Map();
5393
6863
  tabBarVisible = false;
5394
6864
  systemInfo = createDefaultSystemInfo();
6865
+ mediaQueryObservers = /* @__PURE__ */ new Set();
6866
+ canvasContexts = /* @__PURE__ */ new Map();
5395
6867
  enterOptions = createAppLaunchOptions("", {});
5396
6868
  launchOptions = createAppLaunchOptions("", {});
5397
6869
  wxState = createHeadlessWxState();
@@ -5424,7 +6896,20 @@ var HeadlessSession = class {
5424
6896
  });
5425
6897
  this.tabBarVisible = this.tabBarRoutes.size > 0;
5426
6898
  this.moduleLoader = createModuleLoader(this.registries, () => this.pages.slice(), () => this.getApp(), {
6899
+ chooseImage: (option) => this.wxState.chooseImage(option ?? {}),
6900
+ chooseMessageFile: (option) => this.wxState.chooseMessageFile(option ?? {}),
6901
+ chooseMedia: (option) => this.wxState.chooseMedia(option ?? {}),
6902
+ chooseVideo: (option) => this.wxState.chooseVideo(option ?? {}),
6903
+ compressImage: (option) => this.wxState.compressImage(option),
6904
+ createAnimation: (option) => this.createAnimation(option),
6905
+ createCanvasContext: (canvasId, scope) => this.createCanvasContext(canvasId, scope),
6906
+ canvasToTempFilePath: (option) => this.canvasToTempFilePath(option),
6907
+ createIntersectionObserver: (scope, options) => this.createIntersectionObserver(scope, options),
6908
+ createVideoContext: (videoId, scope) => this.createVideoContext(videoId, scope),
5427
6909
  executeSelectorQuery: (requests, scope) => this.executeSelectorQuery(requests, scope),
6910
+ getFileInfo: (option) => this.wxState.getFileInfo(option),
6911
+ getImageInfo: (option) => this.wxState.getImageInfo(option),
6912
+ getVideoInfo: (option) => this.wxState.getVideoInfo(option),
5428
6913
  getFileSystemManager: () => this.wxState.getFileSystemManager(),
5429
6914
  getSavedFileInfo: (option) => this.wxState.getSavedFileInfo(option),
5430
6915
  getSavedFileList: () => this.wxState.getSavedFileList(),
@@ -5445,11 +6930,13 @@ var HeadlessSession = class {
5445
6930
  extraData: { ...this.launchOptions.referrerInfo.extraData }
5446
6931
  }
5447
6932
  }),
6933
+ getClipboardData: () => this.wxState.getClipboardData(),
5448
6934
  getMenuButtonBoundingClientRect: () => deriveMenuButtonBoundingClientRect(this.systemInfo),
5449
6935
  getNetworkType: () => this.wxState.getNetworkType(),
5450
6936
  navigateBack: (option) => this.navigateBack(option?.delta),
5451
6937
  navigateTo: (option) => this.navigateTo(option.url),
5452
6938
  pageScrollTo: (option) => this.pageScrollTo(option),
6939
+ openDocument: (option) => this.wxState.openDocument(option),
5453
6940
  clearStorageSync: () => this.wxState.clearStorageSync(),
5454
6941
  getStorageInfoSync: () => this.wxState.getStorageInfoSync(),
5455
6942
  getStorageSync: (key) => this.wxState.getStorageSync(key),
@@ -5461,14 +6948,18 @@ var HeadlessSession = class {
5461
6948
  reLaunch: (option) => this.reLaunch(option.url),
5462
6949
  redirectTo: (option) => this.redirectTo(option.url),
5463
6950
  removeSavedFile: (option) => this.wxState.removeSavedFile(option),
6951
+ saveImageToPhotosAlbum: (option) => this.wxState.saveImageToPhotosAlbum(option),
6952
+ saveVideoToPhotosAlbum: (option) => this.wxState.saveVideoToPhotosAlbum(option),
5464
6953
  nextTick: (callback) => queueMicrotask(() => callback?.()),
5465
6954
  offNetworkStatusChange: (callback) => this.wxState.offNetworkStatusChange(callback),
5466
6955
  onNetworkStatusChange: (callback) => this.wxState.onNetworkStatusChange(callback),
5467
6956
  removeStorageSync: (key) => this.wxState.removeStorageSync(key),
6957
+ previewImage: (option) => this.wxState.previewImage(option),
5468
6958
  request: (option) => this.wxState.request(option),
5469
6959
  saveFile: (option) => this.wxState.saveFile(option),
5470
6960
  setBackgroundColor: (option) => this.setBackgroundColor(option),
5471
6961
  setBackgroundTextStyle: (option) => this.setBackgroundTextStyle(option.textStyle),
6962
+ setClipboardData: (option) => this.wxState.setClipboardData(option),
5472
6963
  setStorageSync: (key, value) => this.wxState.setStorageSync(key, value),
5473
6964
  setNavigationBarColor: (option) => this.setNavigationBarColor(option),
5474
6965
  setNavigationBarTitle: (option) => this.setNavigationBarTitle(option.title),
@@ -5484,6 +6975,7 @@ var HeadlessSession = class {
5484
6975
  showLoading: (option) => this.wxState.showLoading(option),
5485
6976
  showModal: (option) => this.wxState.showModal(option),
5486
6977
  showToast: (option) => this.wxState.showToast(option),
6978
+ startPullDownRefresh: () => this.startPullDownRefresh(),
5487
6979
  stopPullDownRefresh: () => this.stopPullDownRefresh(),
5488
6980
  switchTab: (option) => this.switchTab(option.url),
5489
6981
  uploadFile: (option) => this.wxState.uploadFile(option),
@@ -5542,6 +7034,8 @@ var HeadlessSession = class {
5542
7034
  moduleLoader: this.moduleLoader,
5543
7035
  project: this.project,
5544
7036
  session: {
7037
+ createIntersectionObserver: (scope, options) => this.createIntersectionObserver(scope, options),
7038
+ createMediaQueryObserver: (scope) => this.createMediaQueryObserver(scope),
5545
7039
  selectAllComponentsWithin: (scopeId, selector) => this.selectAllComponentsWithin(scopeId, selector),
5546
7040
  selectComponentWithin: (scopeId, selector) => this.selectComponentWithin(scopeId, selector),
5547
7041
  selectOwnerComponent: (scopeId) => this.selectOwnerComponent(scopeId)
@@ -5609,6 +7103,7 @@ var HeadlessSession = class {
5609
7103
  }
5610
7104
  const instance = this.componentCache.get(normalizedScopeId);
5611
7105
  if (!instance) throw new Error(`Unknown scope "${normalizedScopeId}" in headless runtime.`);
7106
+ instance.__lastInteractionEvent__ = void 0;
5612
7107
  const method = instance[methodName];
5613
7108
  if (typeof method !== "function") throw new TypeError(`Method "${methodName}" does not exist on headless component scope "${normalizedScopeId}".`);
5614
7109
  return method.apply(instance, args);
@@ -5658,6 +7153,15 @@ var HeadlessSession = class {
5658
7153
  getStorageInfo() {
5659
7154
  return this.wxState.getStorageInfoSync();
5660
7155
  }
7156
+ getClipboardData() {
7157
+ return this.wxState.getClipboardSnapshot();
7158
+ }
7159
+ getPreviewImage() {
7160
+ return this.wxState.getPreviewImage();
7161
+ }
7162
+ getOpenedDocument() {
7163
+ return this.wxState.getOpenedDocument();
7164
+ }
5661
7165
  getPullDownRefreshState() {
5662
7166
  return { ...this.pullDownRefreshState };
5663
7167
  }
@@ -5931,7 +7435,8 @@ var HeadlessSession = class {
5931
7435
  }
5932
7436
  pageScrollTo(option) {
5933
7437
  const current = this.requireCurrentPage("wx.pageScrollTo()");
5934
- current.__scrollTop__ = Number(option.scrollTop ?? 0);
7438
+ const selectorScrollTop = resolveSelectorScrollTop(this.renderCurrentPage().root, option.selector);
7439
+ current.__scrollTop__ = Number(selectorScrollTop ?? option.scrollTop ?? 0);
5935
7440
  current.onPageScroll?.({ scrollTop: current.__scrollTop__ });
5936
7441
  }
5937
7442
  triggerPullDownRefresh() {
@@ -5943,6 +7448,10 @@ var HeadlessSession = class {
5943
7448
  current.onPullDownRefresh?.();
5944
7449
  return current;
5945
7450
  }
7451
+ startPullDownRefresh() {
7452
+ this.triggerPullDownRefresh();
7453
+ return { errMsg: "startPullDownRefresh:ok" };
7454
+ }
5946
7455
  triggerReachBottom() {
5947
7456
  const current = this.requireCurrentPage("triggerReachBottom()");
5948
7457
  current.onReachBottom?.();
@@ -5953,6 +7462,7 @@ var HeadlessSession = class {
5953
7462
  applyResizeToSystemInfo(this.systemInfo, options);
5954
7463
  current.onResize?.(options);
5955
7464
  this.runPageComponentLifetime(current.route, "resize", options);
7465
+ this.notifyMediaQueryObservers();
5956
7466
  return current;
5957
7467
  }
5958
7468
  triggerRouteDone(options = {}) {
@@ -5973,6 +7483,11 @@ var HeadlessSession = class {
5973
7483
  const scopeId = scope && scope !== current ? this.getComponentScopeId(scope) : null;
5974
7484
  return executeSelectorQueryRequests(requests, {
5975
7485
  page: current,
7486
+ resolveContext: (node) => {
7487
+ if (node.name !== "canvas") return { type: "unsupported-context" };
7488
+ const canvasId = node.attribs?.["canvas-id"];
7489
+ return typeof canvasId === "string" && canvasId ? this.createCanvasContext(canvasId, scope) : { type: "unsupported-context" };
7490
+ },
5976
7491
  root: resolveSelectorQueryScopeRoot(rendered.root, scopeId),
5977
7492
  windowInfo: this.getWindowInfo()
5978
7493
  });
@@ -5986,6 +7501,8 @@ var HeadlessSession = class {
5986
7501
  background: resolveBackgroundSnapshot(this.project.appConfig, pageConfig),
5987
7502
  navigationBar: resolveNavigationBarSnapshot(this.project.appConfig, pageConfig)
5988
7503
  });
7504
+ pageInstance.createIntersectionObserver = (options) => this.createIntersectionObserver(pageInstance, options);
7505
+ pageInstance.createMediaQueryObserver = () => this.createMediaQueryObserver(pageInstance);
5989
7506
  pageInstance.selectComponent = (selector) => this.selectComponent(selector);
5990
7507
  pageInstance.selectAllComponents = (selector) => this.selectAllComponents(selector);
5991
7508
  pageInstance.onLoad?.(target.query);
@@ -6035,10 +7552,12 @@ var HeadlessSession = class {
6035
7552
  this.tabPages.clear();
6036
7553
  this.componentCache.clear();
6037
7554
  this.componentScopes.clear();
7555
+ this.clearMediaQueryObservers();
6038
7556
  this.currentPageInstance = null;
6039
7557
  }
6040
7558
  unloadPage(page) {
6041
7559
  page.onUnload?.();
7560
+ this.clearMediaQueryObservers(page);
6042
7561
  this.detachPageComponents(page.route);
6043
7562
  this.tabPages.delete(stripLeadingSlash(page.route));
6044
7563
  }
@@ -6049,21 +7568,21 @@ var HeadlessSession = class {
6049
7568
  return current;
6050
7569
  }
6051
7570
  selectComponentsWithin(rootScopeId, selector) {
6052
- const normalizedSelector = selector.trim();
6053
- if (!normalizedSelector) return [];
7571
+ const selectorParts = normalizeSelectorParts(selector);
7572
+ if (selectorParts.length === 0) return [];
6054
7573
  return [...this.componentScopes.entries()].filter(([candidateScopeId, scope]) => {
6055
7574
  if (!candidateScopeId.includes("/")) return false;
6056
7575
  if (rootScopeId && candidateScopeId === rootScopeId) return false;
6057
7576
  if (rootScopeId && !candidateScopeId.startsWith(`${rootScopeId}/`)) return false;
6058
- if (normalizedSelector.startsWith("#")) return scope.id === normalizedSelector.slice(1);
6059
- if (normalizedSelector.startsWith(".")) return scope.classList?.includes(normalizedSelector.slice(1)) ?? false;
6060
- const dataAttrMatch = normalizedSelector.match(DATA_ATTR_SELECTOR_RE);
6061
- if (dataAttrMatch) {
6062
- const [, key, value] = dataAttrMatch;
6063
- const datasetKey = key.replace(DATASET_KEY_RE, (_match, char) => char.toUpperCase());
6064
- return scope.dataset?.[datasetKey] === value;
7577
+ const lastPart = selectorParts.at(-1);
7578
+ if (!lastPart || !matchesComponentSelector(scope, lastPart)) return false;
7579
+ let currentScope = scope.ownerScopeId ? this.componentScopes.get(scope.ownerScopeId) : void 0;
7580
+ for (let partIndex = selectorParts.length - 2; partIndex >= 0; partIndex -= 1) {
7581
+ while (currentScope && !matchesComponentSelector(currentScope, selectorParts[partIndex])) currentScope = currentScope.ownerScopeId ? this.componentScopes.get(currentScope.ownerScopeId) : void 0;
7582
+ if (!currentScope) return false;
7583
+ currentScope = currentScope.ownerScopeId ? this.componentScopes.get(currentScope.ownerScopeId) : void 0;
6065
7584
  }
6066
- return scope.alias === normalizedSelector;
7585
+ return true;
6067
7586
  }).map(([candidateScopeId]) => this.componentCache.get(candidateScopeId)).filter(Boolean);
6068
7587
  }
6069
7588
  detachPageComponents(route) {
@@ -6075,6 +7594,18 @@ var HeadlessSession = class {
6075
7594
  this.componentScopes.delete(scopeId);
6076
7595
  }
6077
7596
  }
7597
+ notifyMediaQueryObservers() {
7598
+ for (const entry of [...this.mediaQueryObservers]) {
7599
+ if (entry.ownerPage !== this.currentPageInstance) continue;
7600
+ entry.notify();
7601
+ }
7602
+ }
7603
+ clearMediaQueryObservers(ownerPage) {
7604
+ for (const entry of [...this.mediaQueryObservers]) {
7605
+ if (ownerPage && entry.ownerPage !== ownerPage) continue;
7606
+ entry.disconnect();
7607
+ }
7608
+ }
6078
7609
  runPageComponentLifetime(route, lifetimeName, payload) {
6079
7610
  const prefix = `page:${stripLeadingSlash(route)}`;
6080
7611
  for (const [scopeId, instance] of this.componentCache.entries()) {
@@ -6086,6 +7617,100 @@ var HeadlessSession = class {
6086
7617
  for (const [scopeId, instance] of this.componentCache.entries()) if (instance === component) return scopeId;
6087
7618
  return null;
6088
7619
  }
7620
+ createVideoContext(videoId, scope) {
7621
+ return createHeadlessVideoContext({
7622
+ callScopeMethod: (scopeId, methodName, event) => this.callScopeMethod(scopeId, methodName, event),
7623
+ renderCurrentPage: () => this.renderCurrentPage(),
7624
+ resolveScope: (value) => {
7625
+ const current = this.currentPageInstance;
7626
+ if (!value || value === current) return { kind: "page" };
7627
+ const scopeId = this.getScopeIdForComponent(value);
7628
+ if (!scopeId) return { kind: "missing" };
7629
+ return {
7630
+ kind: "component",
7631
+ scopeId
7632
+ };
7633
+ }
7634
+ }, videoId, scope);
7635
+ }
7636
+ createAnimation(option) {
7637
+ return createHeadlessAnimation(option);
7638
+ }
7639
+ resolveCanvasScopeKey(scope) {
7640
+ const current = this.currentPageInstance;
7641
+ if (!scope || scope === current) return "page";
7642
+ const scopeId = this.getScopeIdForComponent(scope);
7643
+ return scopeId ? `component:${scopeId}` : "missing";
7644
+ }
7645
+ createCanvasContext(canvasId, scope) {
7646
+ const context = createHeadlessCanvasContext({
7647
+ renderCurrentPage: () => this.renderCurrentPage(),
7648
+ resolveScope: (value) => {
7649
+ const current = this.currentPageInstance;
7650
+ if (!value || value === current) return { kind: "page" };
7651
+ const scopeId = this.getScopeIdForComponent(value);
7652
+ if (!scopeId) return { kind: "missing" };
7653
+ return {
7654
+ kind: "component",
7655
+ scopeId
7656
+ };
7657
+ }
7658
+ }, canvasId, scope);
7659
+ this.canvasContexts.set(`${this.resolveCanvasScopeKey(scope)}:${canvasId}`, context);
7660
+ return context;
7661
+ }
7662
+ canvasToTempFilePath(option) {
7663
+ const context = this.canvasContexts.get(`${this.resolveCanvasScopeKey(option.component)}:${option.canvasId}`);
7664
+ if (!context) throw new Error(`canvasToTempFilePath:fail canvas "${option.canvasId}" has not been drawn in headless runtime.`);
7665
+ return {
7666
+ errMsg: "canvasToTempFilePath:ok",
7667
+ tempFilePath: this.wxState.createTempFile(JSON.stringify({
7668
+ canvasId: option.canvasId,
7669
+ config: {
7670
+ destHeight: option.destHeight,
7671
+ destWidth: option.destWidth,
7672
+ fileType: option.fileType,
7673
+ height: option.height,
7674
+ quality: option.quality,
7675
+ width: option.width,
7676
+ x: option.x,
7677
+ y: option.y
7678
+ },
7679
+ snapshot: context.__getSnapshot()
7680
+ }))
7681
+ };
7682
+ }
7683
+ createIntersectionObserver(scope, options) {
7684
+ return createHeadlessIntersectionObserver({
7685
+ getWindowInfo: () => this.getWindowInfo(),
7686
+ renderCurrentPage: () => this.renderCurrentPage(),
7687
+ resolveScope: (value) => {
7688
+ const current = this.currentPageInstance;
7689
+ if (!value || value === current) return { kind: "page" };
7690
+ const scopeId = this.getScopeIdForComponent(value);
7691
+ if (!scopeId) return { kind: "missing" };
7692
+ return {
7693
+ kind: "component",
7694
+ scopeId
7695
+ };
7696
+ }
7697
+ }, scope, options);
7698
+ }
7699
+ createMediaQueryObserver(scope) {
7700
+ const current = this.requireCurrentPage("createMediaQueryObserver()");
7701
+ const ownerPage = scope === current || !scope ? current : this.currentPageInstance ?? current;
7702
+ let entry;
7703
+ const controller = createHeadlessMediaQueryObserver({ getWindowInfo: () => this.getWindowInfo() }, () => {
7704
+ this.mediaQueryObservers.delete(entry);
7705
+ });
7706
+ entry = {
7707
+ disconnect: controller.disconnect,
7708
+ notify: controller.notify,
7709
+ ownerPage
7710
+ };
7711
+ this.mediaQueryObservers.add(entry);
7712
+ return controller.observer;
7713
+ }
6089
7714
  };
6090
7715
  function createHeadlessSession(options) {
6091
7716
  return new HeadlessSession(options);
@@ -6227,7 +7852,7 @@ var HeadlessTestingPageHandle = class {
6227
7852
  }
6228
7853
  };
6229
7854
  //#endregion
6230
- //#region ../../mpcore/packages/simulator/src/testing/sessionHandle.ts
7855
+ //#region ../../mpcore/packages/simulator/src/testing/sessionHandle/shared.ts
6231
7856
  const LEADING_SLASH_RE = /^\/+/;
6232
7857
  const DEFAULT_WAIT_TIMEOUT = 1e3;
6233
7858
  const DEFAULT_WAIT_INTERVAL = 10;
@@ -6238,6 +7863,28 @@ function resolvePageScopeId(scopeId) {
6238
7863
  const pagePathIndex = scopeId.indexOf("/page/");
6239
7864
  return pagePathIndex >= 0 ? scopeId.slice(0, pagePathIndex) : scopeId;
6240
7865
  }
7866
+ function normalizeNonEmptyInput(value, label) {
7867
+ const normalizedValue = value.trim();
7868
+ if (!normalizedValue) throw new Error(`${label} must be a non-empty string in headless testing runtime.`);
7869
+ return normalizedValue;
7870
+ }
7871
+ async function waitForDelay(ms = 0) {
7872
+ if (ms <= 0) return;
7873
+ await new Promise((resolve) => setTimeout(resolve, ms));
7874
+ }
7875
+ async function pollUntil(check, errorMessage, options = {}) {
7876
+ const timeout = Number.isFinite(options.timeout) ? Math.max(0, Math.trunc(options.timeout ?? DEFAULT_WAIT_TIMEOUT)) : DEFAULT_WAIT_TIMEOUT;
7877
+ const interval = Number.isFinite(options.interval) ? Math.max(1, Math.trunc(options.interval ?? DEFAULT_WAIT_INTERVAL)) : DEFAULT_WAIT_INTERVAL;
7878
+ const deadline = Date.now() + timeout;
7879
+ while (true) {
7880
+ const result = await check();
7881
+ if (result != null) return result;
7882
+ if (Date.now() >= deadline) throw new Error(errorMessage);
7883
+ await waitForDelay(interval);
7884
+ }
7885
+ }
7886
+ //#endregion
7887
+ //#region ../../mpcore/packages/simulator/src/testing/sessionHandle/scope.ts
6241
7888
  var HeadlessTestingScopeHandle = class HeadlessTestingScopeHandle {
6242
7889
  constructor(scopeId, project, session) {
6243
7890
  this.scopeId = scopeId;
@@ -6249,8 +7896,7 @@ var HeadlessTestingScopeHandle = class HeadlessTestingScopeHandle {
6249
7896
  return scopeId ? new HeadlessTestingScopeHandle(scopeId, this.project, this.session) : null;
6250
7897
  }
6251
7898
  async callMethod(methodName, ...args) {
6252
- const normalizedMethodName = methodName.trim();
6253
- if (!normalizedMethodName) throw new Error("Method name must be a non-empty string in headless testing runtime.");
7899
+ const normalizedMethodName = normalizeNonEmptyInput(methodName, "Method name");
6254
7900
  return this.session.callScopeMethodDirect(this.scopeId, normalizedMethodName, ...args);
6255
7901
  }
6256
7902
  async snapshot() {
@@ -6267,75 +7913,37 @@ var HeadlessTestingScopeHandle = class HeadlessTestingScopeHandle {
6267
7913
  return this.createScopeHandle(this.session.selectOwnerComponent(this.scopeId));
6268
7914
  }
6269
7915
  async waitForOwnerComponent(options = {}) {
6270
- const timeout = Number.isFinite(options.timeout) ? Math.max(0, Math.trunc(options.timeout ?? 1e3)) : 1e3;
6271
- const interval = Number.isFinite(options.interval) ? Math.max(1, Math.trunc(options.interval ?? 10)) : 10;
6272
- const deadline = Date.now() + timeout;
6273
- while (true) {
6274
- const owner = await this.ownerComponent();
6275
- if (owner) return owner;
6276
- if (Date.now() >= deadline) throw new Error("Timed out waiting for owner component in headless testing runtime.");
6277
- await new Promise((resolve) => setTimeout(resolve, interval));
6278
- }
7916
+ return await pollUntil(async () => await this.ownerComponent(), "Timed out waiting for owner component in headless testing runtime.", options);
6279
7917
  }
6280
7918
  async selectComponent(selector) {
6281
- const normalizedSelector = selector.trim();
6282
- if (!normalizedSelector) throw new Error("Selector must be a non-empty string in headless testing runtime.");
7919
+ const normalizedSelector = normalizeNonEmptyInput(selector, "Selector");
6283
7920
  const component = this.session.selectComponentWithin(this.scopeId, normalizedSelector);
6284
7921
  return this.createScopeHandle(component);
6285
7922
  }
6286
7923
  async selectAllComponents(selector) {
6287
- const normalizedSelector = selector.trim();
6288
- if (!normalizedSelector) throw new Error("Selector must be a non-empty string in headless testing runtime.");
7924
+ const normalizedSelector = normalizeNonEmptyInput(selector, "Selector");
6289
7925
  return this.session.selectAllComponentsWithin(this.scopeId, normalizedSelector).map((component) => this.createScopeHandle(component)).filter((handle) => Boolean(handle));
6290
7926
  }
6291
7927
  async waitForComponent(selector, options = {}) {
6292
- const normalizedSelector = selector.trim();
6293
- if (!normalizedSelector) throw new Error("Selector must be a non-empty string in headless testing runtime.");
6294
- const timeout = Number.isFinite(options.timeout) ? Math.max(0, Math.trunc(options.timeout ?? 1e3)) : 1e3;
6295
- const interval = Number.isFinite(options.interval) ? Math.max(1, Math.trunc(options.interval ?? 10)) : 10;
6296
- const deadline = Date.now() + timeout;
6297
- while (true) {
6298
- const component = await this.selectComponent(normalizedSelector);
6299
- if (component) return component;
6300
- if (Date.now() >= deadline) throw new Error(`Timed out waiting for component "${normalizedSelector}" in headless testing runtime.`);
6301
- await new Promise((resolve) => setTimeout(resolve, interval));
6302
- }
7928
+ const normalizedSelector = normalizeNonEmptyInput(selector, "Selector");
7929
+ return await pollUntil(async () => await this.selectComponent(normalizedSelector), `Timed out waiting for component "${normalizedSelector}" in headless testing runtime.`, options);
6303
7930
  }
6304
7931
  async waitForComponents(selector, count = 1, options = {}) {
6305
- const normalizedSelector = selector.trim();
6306
- if (!normalizedSelector) throw new Error("Selector must be a non-empty string in headless testing runtime.");
7932
+ const normalizedSelector = normalizeNonEmptyInput(selector, "Selector");
6307
7933
  const normalizedCount = Number.isFinite(count) ? Math.max(1, Math.trunc(count)) : 1;
6308
- const timeout = Number.isFinite(options.timeout) ? Math.max(0, Math.trunc(options.timeout ?? 1e3)) : 1e3;
6309
- const interval = Number.isFinite(options.interval) ? Math.max(1, Math.trunc(options.interval ?? 10)) : 10;
6310
- const deadline = Date.now() + timeout;
6311
- while (true) {
7934
+ return await pollUntil(async () => {
6312
7935
  const components = await this.selectAllComponents(normalizedSelector);
6313
- if (components.length >= normalizedCount) return components;
6314
- if (Date.now() >= deadline) throw new Error(`Timed out waiting for ${normalizedCount} component(s) matching "${normalizedSelector}" in headless testing runtime.`);
6315
- await new Promise((resolve) => setTimeout(resolve, interval));
6316
- }
7936
+ return components.length >= normalizedCount ? components : null;
7937
+ }, `Timed out waiting for ${normalizedCount} component(s) matching "${normalizedSelector}" in headless testing runtime.`, options);
6317
7938
  }
6318
7939
  };
7940
+ //#endregion
7941
+ //#region ../../mpcore/packages/simulator/src/testing/sessionHandle/index.ts
6319
7942
  var HeadlessTestingSessionHandle = class {
6320
7943
  constructor(project, session) {
6321
7944
  this.project = project;
6322
7945
  this.session = session;
6323
7946
  }
6324
- async waitFor(ms = 0) {
6325
- if (ms <= 0) return;
6326
- await new Promise((resolve) => setTimeout(resolve, ms));
6327
- }
6328
- async pollUntil(check, errorMessage, options = {}) {
6329
- const timeout = Number.isFinite(options.timeout) ? Math.max(0, Math.trunc(options.timeout ?? DEFAULT_WAIT_TIMEOUT)) : DEFAULT_WAIT_TIMEOUT;
6330
- const interval = Number.isFinite(options.interval) ? Math.max(1, Math.trunc(options.interval ?? DEFAULT_WAIT_INTERVAL)) : DEFAULT_WAIT_INTERVAL;
6331
- const deadline = Date.now() + timeout;
6332
- while (true) {
6333
- const result = await check();
6334
- if (result != null) return result;
6335
- if (Date.now() >= deadline) throw new Error(errorMessage);
6336
- await this.waitFor(interval);
6337
- }
6338
- }
6339
7947
  async close() {}
6340
7948
  async currentPage() {
6341
7949
  const current = this.session.getCurrentPages().at(-1);
@@ -6345,32 +7953,27 @@ var HeadlessTestingSessionHandle = class {
6345
7953
  return this.session.getCurrentPages().map((page) => new HeadlessTestingPageHandle(this.project, page, this.session));
6346
7954
  }
6347
7955
  async scopeSnapshot(scopeId) {
6348
- const normalizedScopeId = scopeId.trim();
6349
- if (!normalizedScopeId) throw new Error("Scope id must be a non-empty string in headless testing runtime.");
7956
+ const normalizedScopeId = normalizeNonEmptyInput(scopeId, "Scope id");
6350
7957
  return this.session.getScopeSnapshot(normalizedScopeId);
6351
7958
  }
6352
7959
  async selectComponent(selector) {
6353
- const normalizedSelector = selector.trim();
6354
- if (!normalizedSelector) throw new Error("Selector must be a non-empty string in headless testing runtime.");
7960
+ const normalizedSelector = normalizeNonEmptyInput(selector, "Selector");
6355
7961
  const component = this.session.selectComponent(normalizedSelector);
6356
7962
  const scopeId = this.session.getScopeIdForComponent(component);
6357
7963
  return scopeId ? new HeadlessTestingScopeHandle(scopeId, this.project, this.session) : null;
6358
7964
  }
6359
7965
  async selectAllComponents(selector) {
6360
- const normalizedSelector = selector.trim();
6361
- if (!normalizedSelector) throw new Error("Selector must be a non-empty string in headless testing runtime.");
7966
+ const normalizedSelector = normalizeNonEmptyInput(selector, "Selector");
6362
7967
  return this.session.selectAllComponents(normalizedSelector).map((component) => this.session.getScopeIdForComponent(component)).filter((scopeId) => Boolean(scopeId)).map((scopeId) => new HeadlessTestingScopeHandle(scopeId, this.project, this.session));
6363
7968
  }
6364
7969
  async waitForComponent(selector, options = {}) {
6365
- const normalizedSelector = selector.trim();
6366
- if (!normalizedSelector) throw new Error("Selector must be a non-empty string in headless testing runtime.");
6367
- return await this.pollUntil(async () => await this.selectComponent(normalizedSelector), `Timed out waiting for component "${normalizedSelector}" in headless testing runtime.`, options);
7970
+ const normalizedSelector = normalizeNonEmptyInput(selector, "Selector");
7971
+ return await pollUntil(async () => await this.selectComponent(normalizedSelector), `Timed out waiting for component "${normalizedSelector}" in headless testing runtime.`, options);
6368
7972
  }
6369
7973
  async waitForComponents(selector, count = 1, options = {}) {
6370
- const normalizedSelector = selector.trim();
6371
- if (!normalizedSelector) throw new Error("Selector must be a non-empty string in headless testing runtime.");
7974
+ const normalizedSelector = normalizeNonEmptyInput(selector, "Selector");
6372
7975
  const normalizedCount = Number.isFinite(count) ? Math.max(1, Math.trunc(count)) : 1;
6373
- return await this.pollUntil(async () => {
7976
+ return await pollUntil(async () => {
6374
7977
  const components = await this.selectAllComponents(normalizedSelector);
6375
7978
  return components.length >= normalizedCount ? components : null;
6376
7979
  }, `Timed out waiting for ${normalizedCount} component(s) matching "${normalizedSelector}" in headless testing runtime.`, options);
@@ -6382,7 +7985,7 @@ var HeadlessTestingSessionHandle = class {
6382
7985
  async waitForCurrentPage(route, options = {}) {
6383
7986
  const normalizedRoute = route?.trim();
6384
7987
  if (normalizedRoute != null && !normalizedRoute) throw new Error("Route must be a non-empty string in headless testing runtime.");
6385
- return await this.pollUntil(async () => {
7988
+ return await pollUntil(async () => {
6386
7989
  const currentPageInstance = this.session.getCurrentPages().at(-1);
6387
7990
  if (!currentPageInstance) return null;
6388
7991
  const current = new HeadlessTestingPageHandle(this.project, currentPageInstance, this.session);