@weapp-vite/miniprogram-automator 1.0.1 → 1.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -28,6 +28,8 @@ function createAppInstance(definition) {
28
28
  }
29
29
  //#endregion
30
30
  //#region ../../mpcore/packages/simulator/src/runtime/componentInstance.ts
31
+ const ARRAY_INDEX_PATH_RE$1 = /\[(\d+)\]/g;
32
+ const ARRAY_INDEX_SEGMENT_RE$1 = /^\d+$/;
31
33
  function bindFunction$1(target, key, value) {
32
34
  if (typeof value !== "function") {
33
35
  target[key] = value;
@@ -44,10 +46,10 @@ function cloneValue$1(value) {
44
46
  return value;
45
47
  }
46
48
  function parseDataPath$1(path) {
47
- return path.replace(/\[(\d+)\]/g, ".$1").split(".").map((segment) => segment.trim()).filter(Boolean);
49
+ return path.replace(ARRAY_INDEX_PATH_RE$1, ".$1").split(".").map((segment) => segment.trim()).filter(Boolean);
48
50
  }
49
51
  function isArrayIndexSegment$1(segment) {
50
- return /^\d+$/.test(segment);
52
+ return ARRAY_INDEX_SEGMENT_RE$1.test(segment);
51
53
  }
52
54
  function createContainerByNextSegment$1(nextSegment) {
53
55
  return isArrayIndexSegment$1(nextSegment ?? "") ? [] : {};
@@ -64,7 +66,7 @@ function assignByPath$1(target, path, value) {
64
66
  if (!next || typeof next !== "object") current[normalizedSegment] = createContainerByNextSegment$1(nextSegment);
65
67
  current = current[normalizedSegment];
66
68
  }
67
- const leafSegment = segments[segments.length - 1];
69
+ const leafSegment = segments.at(-1);
68
70
  const normalizedLeafSegment = isArrayIndexSegment$1(leafSegment) ? Number(leafSegment) : leafSegment;
69
71
  current[normalizedLeafSegment] = value;
70
72
  }
@@ -184,7 +186,7 @@ function resolveInitialProperties(definition, properties) {
184
186
  const resolved = {};
185
187
  const propOptions = definition.properties;
186
188
  if (propOptions && typeof propOptions === "object" && !Array.isArray(propOptions)) for (const [key, option] of Object.entries(propOptions)) {
187
- if (Object.prototype.hasOwnProperty.call(properties, key)) {
189
+ if (Object.hasOwn(properties, key)) {
188
190
  resolved[key] = coerceComponentPropertyValue(properties[key], option);
189
191
  continue;
190
192
  }
@@ -220,7 +222,7 @@ function resolveObservedValue(instance, pattern) {
220
222
  const segments = parseDataPath$1(pattern);
221
223
  if (segments.length === 0) return;
222
224
  const [rootSegment, ...restSegments] = segments;
223
- let current = (Object.prototype.hasOwnProperty.call(instance.properties, rootSegment) ? instance.properties : instance.data)?.[rootSegment];
225
+ let current = (Object.hasOwn(instance.properties, rootSegment) ? instance.properties : instance.data)?.[rootSegment];
224
226
  for (const segment of restSegments) {
225
227
  const normalizedSegment = isArrayIndexSegment$1(segment) ? Number(segment) : segment;
226
228
  current = current?.[normalizedSegment];
@@ -2853,7 +2855,7 @@ const STRUCTURAL_ATTRS = [
2853
2855
  "wx:key"
2854
2856
  ];
2855
2857
  const WX_ELSE_ATTRS = new Set(["wx:elif", "wx:else"]);
2856
- const DATASET_NAME_RE$2 = /-([a-z])/g;
2858
+ const DATASET_NAME_RE$3 = /-([a-z])/g;
2857
2859
  const BRACKET_INDEX_RE = /\[(\d+)\]/g;
2858
2860
  const CLASS_SPLIT_RE = /\s+/;
2859
2861
  const JS_FILE_RE = /\.js$/;
@@ -2861,14 +2863,14 @@ function isMustacheOnly(value) {
2861
2863
  const trimmed = value.trim();
2862
2864
  return trimmed.startsWith("{{") && trimmed.endsWith("}}") && !trimmed.includes("{{", 2);
2863
2865
  }
2864
- function toDatasetKey$2(attributeName) {
2865
- return attributeName.slice(5).replace(DATASET_NAME_RE$2, (_match, char) => char.toUpperCase());
2866
+ function toDatasetKey$3(attributeName) {
2867
+ return attributeName.slice(5).replace(DATASET_NAME_RE$3, (_match, char) => char.toUpperCase());
2866
2868
  }
2867
- function collectDataset$2(node) {
2869
+ function collectDataset$3(node) {
2868
2870
  const dataset = {};
2869
2871
  for (const [key, value] of Object.entries(node.attribs ?? {})) {
2870
2872
  if (!key.startsWith("data-") || key === "data-sim-scope" || key === "data-sim-tap" || key === "data-sim-component") continue;
2871
- dataset[toDatasetKey$2(key)] = String(value);
2873
+ dataset[toDatasetKey$3(key)] = String(value);
2872
2874
  }
2873
2875
  return dataset;
2874
2876
  }
@@ -3006,7 +3008,7 @@ function collectComponentEventBindings(hostNode) {
3006
3008
  return eventBindings;
3007
3009
  }
3008
3010
  function buildComponentTrigger(componentScopeId, context, hostNode) {
3009
- const hostDataset = collectDataset$2(hostNode);
3011
+ const hostDataset = collectDataset$3(hostNode);
3010
3012
  const hostId = hostNode.attribs?.id ?? "";
3011
3013
  return (instance, eventName, detail, triggerOptions) => {
3012
3014
  const interactionTarget = instance.__lastInteractionEvent__?.target;
@@ -3187,6 +3189,8 @@ function renderNodeTree(node, scope, context, ownerJsonPath, ownerFilePath, inst
3187
3189
  properties: nextProperties,
3188
3190
  triggerEvent: buildComponentTrigger(componentScopeId, context, clonedNode)
3189
3191
  });
3192
+ componentInstance.createIntersectionObserver = (options) => context.session.createIntersectionObserver(componentInstance, options);
3193
+ componentInstance.createMediaQueryObserver = () => context.session.createMediaQueryObserver(componentInstance);
3190
3194
  componentInstance.selectComponent = (selector) => context.session.selectComponentWithin(componentScopeId, selector);
3191
3195
  componentInstance.selectAllComponents = (selector) => context.session.selectAllComponentsWithin(componentScopeId, selector);
3192
3196
  componentInstance.selectOwnerComponent = () => ownerScopeId ? context.session.selectOwnerComponent(componentScopeId) : null;
@@ -3201,7 +3205,7 @@ function renderNodeTree(node, scope, context, ownerJsonPath, ownerFilePath, inst
3201
3205
  alias: clonedNode.name,
3202
3206
  classList: String(clonedNode.attribs?.class ?? "").split(CLASS_SPLIT_RE).map((item) => item.trim()).filter(Boolean),
3203
3207
  data: createMergedScopeData(scope.data, componentInstance.properties, componentInstance.data),
3204
- dataset: collectDataset$2(clonedNode),
3208
+ dataset: collectDataset$3(clonedNode),
3205
3209
  eventBindings: collectComponentEventBindings(clonedNode),
3206
3210
  getMethod: (methodName) => {
3207
3211
  const method = componentInstance?.[methodName];
@@ -3312,9 +3316,65 @@ function resolveCapabilityValue(source, schema) {
3312
3316
  function createHeadlessWx(driver) {
3313
3317
  const capabilityTree = {
3314
3318
  canIUse: true,
3319
+ canvasToTempFilePath: true,
3320
+ chooseImage: { return: {
3321
+ errMsg: true,
3322
+ tempFilePaths: true,
3323
+ tempFiles: true
3324
+ } },
3325
+ chooseMessageFile: { return: {
3326
+ errMsg: true,
3327
+ tempFiles: true
3328
+ } },
3329
+ chooseMedia: { return: {
3330
+ errMsg: true,
3331
+ tempFiles: true,
3332
+ type: true
3333
+ } },
3334
+ chooseVideo: { return: {
3335
+ duration: true,
3336
+ errMsg: true,
3337
+ height: true,
3338
+ size: true,
3339
+ tempFilePath: true,
3340
+ width: true
3341
+ } },
3342
+ compressImage: { return: {
3343
+ errMsg: true,
3344
+ tempFilePath: true
3345
+ } },
3315
3346
  clearStorage: true,
3316
3347
  clearStorageSync: true,
3348
+ createAnimation: true,
3349
+ createCanvasContext: true,
3350
+ createIntersectionObserver: true,
3351
+ createVideoContext: true,
3317
3352
  createSelectorQuery: true,
3353
+ getImageInfo: { return: {
3354
+ errMsg: true,
3355
+ height: true,
3356
+ orientation: true,
3357
+ path: true,
3358
+ type: true,
3359
+ width: true
3360
+ } },
3361
+ getFileInfo: { return: {
3362
+ digest: true,
3363
+ errMsg: true,
3364
+ size: true
3365
+ } },
3366
+ openDocument: { return: { errMsg: true } },
3367
+ getVideoInfo: { return: {
3368
+ bitrate: true,
3369
+ duration: true,
3370
+ errMsg: true,
3371
+ fps: true,
3372
+ height: true,
3373
+ orientation: true,
3374
+ size: true,
3375
+ type: true,
3376
+ width: true
3377
+ } },
3318
3378
  getFileSystemManager: true,
3319
3379
  getSavedFileInfo: true,
3320
3380
  getSavedFileList: true,
@@ -3352,6 +3412,10 @@ function createHeadlessWx(driver) {
3352
3412
  },
3353
3413
  scene: true
3354
3414
  } },
3415
+ getClipboardData: { return: {
3416
+ data: true,
3417
+ errMsg: true
3418
+ } },
3355
3419
  getMenuButtonBoundingClientRect: { return: {
3356
3420
  bottom: true,
3357
3421
  height: true,
@@ -3426,14 +3490,18 @@ function createHeadlessWx(driver) {
3426
3490
  offNetworkStatusChange: true,
3427
3491
  onNetworkStatusChange: true,
3428
3492
  pageScrollTo: true,
3493
+ previewImage: true,
3429
3494
  reLaunch: true,
3430
3495
  redirectTo: true,
3496
+ saveImageToPhotosAlbum: true,
3497
+ saveVideoToPhotosAlbum: true,
3431
3498
  removeStorage: true,
3432
3499
  removeStorageSync: true,
3433
3500
  request: true,
3434
3501
  saveFile: true,
3435
3502
  setBackgroundColor: true,
3436
3503
  setBackgroundTextStyle: true,
3504
+ setClipboardData: true,
3437
3505
  setStorage: true,
3438
3506
  setStorageSync: true,
3439
3507
  setNavigationBarColor: true,
@@ -3450,6 +3518,7 @@ function createHeadlessWx(driver) {
3450
3518
  showLoading: true,
3451
3519
  showModal: true,
3452
3520
  showToast: true,
3521
+ startPullDownRefresh: true,
3453
3522
  stopPullDownRefresh: true,
3454
3523
  switchTab: true,
3455
3524
  uploadFile: true,
@@ -3459,11 +3528,21 @@ function createHeadlessWx(driver) {
3459
3528
  };
3460
3529
  return {
3461
3530
  canIUse: (schema) => typeof schema === "string" && schema.trim() !== "" && resolveCapabilityValue(capabilityTree, schema.trim()) != null,
3531
+ canvasToTempFilePath: (option) => invokeWxApi(() => driver.canvasToTempFilePath(option), option),
3532
+ chooseImage: (option) => invokeWxApi(() => driver.chooseImage(option ?? {}), option),
3533
+ chooseMessageFile: (option) => invokeWxApi(() => driver.chooseMessageFile(option ?? {}), option),
3534
+ chooseMedia: (option) => invokeWxApi(() => driver.chooseMedia(option ?? {}), option),
3535
+ chooseVideo: (option) => invokeWxApi(() => driver.chooseVideo(option ?? {}), option),
3536
+ compressImage: (option) => invokeWxApi(() => driver.compressImage(option), option),
3462
3537
  clearStorage: (option) => invokeWxApi(() => {
3463
3538
  driver.clearStorageSync();
3464
3539
  return { errMsg: "clearStorage:ok" };
3465
3540
  }, option),
3466
3541
  clearStorageSync: () => driver.clearStorageSync(),
3542
+ createAnimation: (option) => driver.createAnimation(option),
3543
+ createCanvasContext: (canvasId, component) => driver.createCanvasContext(canvasId, component),
3544
+ createIntersectionObserver: (component, options) => driver.createIntersectionObserver(component, options),
3545
+ createVideoContext: (videoId, component) => driver.createVideoContext(videoId, component),
3467
3546
  createSelectorQuery: () => {
3468
3547
  const requests = [];
3469
3548
  let scope;
@@ -3522,12 +3601,16 @@ function createHeadlessWx(driver) {
3522
3601
  return query;
3523
3602
  },
3524
3603
  getEnterOptionsSync: () => driver.getEnterOptionsSync(),
3604
+ getFileInfo: (option) => invokeWxApi(() => driver.getFileInfo(option), option),
3605
+ getImageInfo: (option) => invokeWxApi(() => driver.getImageInfo(option), option),
3606
+ getVideoInfo: (option) => invokeWxApi(() => driver.getVideoInfo(option), option),
3525
3607
  getFileSystemManager: () => driver.getFileSystemManager(),
3526
3608
  getSavedFileInfo: (option) => invokeWxApi(() => driver.getSavedFileInfo(option), option),
3527
3609
  getSavedFileList: (option) => invokeWxApi(() => driver.getSavedFileList(option), option),
3528
3610
  getAppBaseInfo: (option) => invokeWxApi(() => driver.getAppBaseInfoSync(), option),
3529
3611
  getAppBaseInfoSync: () => driver.getAppBaseInfoSync(),
3530
3612
  getLaunchOptionsSync: () => driver.getLaunchOptionsSync(),
3613
+ getClipboardData: (option) => invokeWxApi(() => driver.getClipboardData(), option),
3531
3614
  getMenuButtonBoundingClientRect: () => driver.getMenuButtonBoundingClientRect(),
3532
3615
  getNetworkType: (option) => invokeWxApi(() => driver.getNetworkType(), option),
3533
3616
  getStorageInfo: (option) => invokeWxApi(() => driver.getStorageInfoSync(), option),
@@ -3553,9 +3636,11 @@ function createHeadlessWx(driver) {
3553
3636
  nextTick: (callback) => driver.nextTick(callback),
3554
3637
  offNetworkStatusChange: (callback) => driver.offNetworkStatusChange(callback),
3555
3638
  onNetworkStatusChange: (callback) => driver.onNetworkStatusChange(callback),
3639
+ openDocument: (option) => invokeWxApi(() => driver.openDocument(option), option),
3556
3640
  pageScrollTo: (option) => invokeWxApi(() => {
3557
3641
  driver.pageScrollTo(option);
3558
3642
  }, option),
3643
+ previewImage: (option) => invokeWxApi(() => driver.previewImage(option), option),
3559
3644
  reLaunch: (option) => invokeWxApi(() => {
3560
3645
  driver.reLaunch(option);
3561
3646
  }, option),
@@ -3563,6 +3648,8 @@ function createHeadlessWx(driver) {
3563
3648
  driver.redirectTo(option);
3564
3649
  }, option),
3565
3650
  removeSavedFile: (option) => invokeWxApi(() => driver.removeSavedFile(option), option),
3651
+ saveImageToPhotosAlbum: (option) => invokeWxApi(() => driver.saveImageToPhotosAlbum(option), option),
3652
+ saveVideoToPhotosAlbum: (option) => invokeWxApi(() => driver.saveVideoToPhotosAlbum(option), option),
3566
3653
  removeStorage: (option) => invokeWxApi(() => {
3567
3654
  driver.removeStorageSync(option.key);
3568
3655
  return { errMsg: "removeStorage:ok" };
@@ -3572,6 +3659,7 @@ function createHeadlessWx(driver) {
3572
3659
  saveFile: (option) => invokeWxApi(() => driver.saveFile(option), option),
3573
3660
  setBackgroundColor: (option) => invokeWxApi(() => driver.setBackgroundColor(option), option),
3574
3661
  setBackgroundTextStyle: (option) => invokeWxApi(() => driver.setBackgroundTextStyle(option), option),
3662
+ setClipboardData: (option) => invokeWxApi(() => driver.setClipboardData(option), option),
3575
3663
  setStorage: (option) => invokeWxApi(() => {
3576
3664
  driver.setStorageSync(option.key, option.data);
3577
3665
  return { errMsg: "setStorage:ok" };
@@ -3591,6 +3679,7 @@ function createHeadlessWx(driver) {
3591
3679
  showLoading: (option) => invokeWxApi(() => driver.showLoading(option), option),
3592
3680
  showModal: (option) => invokeWxApi(() => driver.showModal(option), option),
3593
3681
  showToast: (option) => invokeWxApi(() => driver.showToast(option), option),
3682
+ startPullDownRefresh: (option) => invokeWxApi(() => driver.startPullDownRefresh(), option),
3594
3683
  stopPullDownRefresh: () => driver.stopPullDownRefresh(),
3595
3684
  switchTab: (option) => invokeWxApi(() => {
3596
3685
  driver.switchTab(option);
@@ -3802,10 +3891,11 @@ function loadProject(projectPath) {
3802
3891
  }
3803
3892
  //#endregion
3804
3893
  //#region ../../mpcore/packages/simulator/src/view/selectors.ts
3805
- const WHITESPACE_RE = /\s+/;
3894
+ const WHITESPACE_RE$1 = /\s+/;
3806
3895
  const DATA_ATTR_SELECTOR_RE$1 = /^\[data-([^=\]]+)="([^"]*)"\]$/;
3896
+ const COMPOUND_SELECTOR_PART_RE$1 = /#[\w-]+|\.[\w-]+|\[data-[^=\]]+="[^"]*"\]|[A-Za-z][\w-]*/g;
3807
3897
  function getClassList(node) {
3808
- return String(node.attribs?.class ?? "").split(WHITESPACE_RE).map((item) => item.trim()).filter(Boolean);
3898
+ return String(node.attribs?.class ?? "").split(WHITESPACE_RE$1).map((item) => item.trim()).filter(Boolean);
3809
3899
  }
3810
3900
  function matchesSimpleSelector(node, selector) {
3811
3901
  if (node.type !== "tag") return false;
@@ -3819,6 +3909,15 @@ function matchesSimpleSelector(node, selector) {
3819
3909
  }
3820
3910
  return node.name === selector;
3821
3911
  }
3912
+ function parseCompoundSelector$1(selector) {
3913
+ const parts = selector.match(COMPOUND_SELECTOR_PART_RE$1) ?? [];
3914
+ return parts.join("") === selector ? parts : [];
3915
+ }
3916
+ function matchesSelectorToken(node, selector) {
3917
+ const simpleSelectors = parseCompoundSelector$1(selector);
3918
+ if (simpleSelectors.length === 0) return false;
3919
+ return simpleSelectors.every((simpleSelector) => matchesSimpleSelector(node, simpleSelector));
3920
+ }
3822
3921
  function collectDescendants(node, into) {
3823
3922
  for (const child of node.children ?? []) {
3824
3923
  into.push(child);
@@ -3826,7 +3925,7 @@ function collectDescendants(node, into) {
3826
3925
  }
3827
3926
  }
3828
3927
  function querySelectorAll(root, selector) {
3829
- const parts = selector.trim().split(WHITESPACE_RE).filter(Boolean);
3928
+ const parts = selector.trim().split(WHITESPACE_RE$1).filter(Boolean);
3830
3929
  if (parts.length === 0) return [];
3831
3930
  let current = [root];
3832
3931
  for (const part of parts) {
@@ -3835,7 +3934,7 @@ function querySelectorAll(root, selector) {
3835
3934
  const candidates = [];
3836
3935
  if (part === "page" && node.type === "tag" && node.name === "page") candidates.push(node);
3837
3936
  collectDescendants(node, candidates);
3838
- for (const candidate of candidates) if (matchesSimpleSelector(candidate, part)) next.push(candidate);
3937
+ for (const candidate of candidates) if (matchesSelectorToken(candidate, part)) next.push(candidate);
3839
3938
  }
3840
3939
  current = next;
3841
3940
  }
@@ -3843,22 +3942,22 @@ function querySelectorAll(root, selector) {
3843
3942
  }
3844
3943
  //#endregion
3845
3944
  //#region ../../mpcore/packages/simulator/src/view/nodeHandle.ts
3846
- const DATASET_NAME_RE$1 = /-([a-z])/g;
3945
+ const DATASET_NAME_RE$2 = /-([a-z])/g;
3847
3946
  function escapeText$1(text) {
3848
3947
  return text.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;");
3849
3948
  }
3850
- function toDatasetKey$1(attributeName) {
3851
- return attributeName.slice(5).replace(DATASET_NAME_RE$1, (_match, char) => char.toUpperCase());
3949
+ function toDatasetKey$2(attributeName) {
3950
+ return attributeName.slice(5).replace(DATASET_NAME_RE$2, (_match, char) => char.toUpperCase());
3852
3951
  }
3853
- function collectDataset$1(node) {
3952
+ function collectDataset$2(node) {
3854
3953
  const dataset = {};
3855
3954
  for (const [key, value] of Object.entries(node.attribs ?? {})) {
3856
3955
  if (!key.startsWith("data-") || key.startsWith("data-sim-")) continue;
3857
- dataset[toDatasetKey$1(key)] = value;
3956
+ dataset[toDatasetKey$2(key)] = value;
3858
3957
  }
3859
3958
  return dataset;
3860
3959
  }
3861
- function resolveEventBinding(node, eventName) {
3960
+ function resolveEventBinding$1(node, eventName) {
3862
3961
  const normalizedEventName = eventName.trim();
3863
3962
  if (!normalizedEventName) return null;
3864
3963
  const bindingAttrs = [
@@ -3873,8 +3972,8 @@ function resolveEventBinding(node, eventName) {
3873
3972
  }
3874
3973
  return null;
3875
3974
  }
3876
- function createEventPayload(node, eventName, event) {
3877
- const dataset = collectDataset$1(node);
3975
+ function createEventPayload$1(node, eventName, event) {
3976
+ const dataset = collectDataset$2(node);
3878
3977
  const nodeId = node.attribs?.id ?? "";
3879
3978
  return {
3880
3979
  bubbles: false,
@@ -3939,7 +4038,7 @@ var HeadlessTestingNodeHandle = class HeadlessTestingNodeHandle {
3939
4038
  return this.node.attribs?.[name];
3940
4039
  }
3941
4040
  async dataset() {
3942
- return collectDataset$1(this.node);
4041
+ return collectDataset$2(this.node);
3943
4042
  }
3944
4043
  async scope() {
3945
4044
  if (!this.interactions) throw new Error("Node interactions are not available for this headless testing node.");
@@ -3969,9 +4068,9 @@ var HeadlessTestingNodeHandle = class HeadlessTestingNodeHandle {
3969
4068
  if (!this.interactions) throw new Error("Node interactions are not available for this headless testing node.");
3970
4069
  const normalizedEventName = eventName.trim();
3971
4070
  if (!normalizedEventName) throw new Error("Event name must be a non-empty string in headless testing runtime.");
3972
- const methodName = resolveEventBinding(this.node, normalizedEventName);
4071
+ const methodName = resolveEventBinding$1(this.node, normalizedEventName);
3973
4072
  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));
4073
+ return await this.interactions.callMethod(this.node.attribs?.["data-sim-scope"] ?? null, methodName, createEventPayload$1(this.node, normalizedEventName, event));
3975
4074
  }
3976
4075
  async tap(event = {}) {
3977
4076
  return await this.trigger("tap", event);
@@ -4055,18 +4154,18 @@ function renderPageTree(project, page) {
4055
4154
  }
4056
4155
  //#endregion
4057
4156
  //#region ../../mpcore/packages/simulator/src/view/selectorQuery.ts
4058
- const DATASET_NAME_RE = /-([a-z])/g;
4157
+ const DATASET_NAME_RE$1 = /-([a-z])/g;
4059
4158
  const LEADING_MARK_PREFIX_RE = /^mark[:\-]?/;
4060
4159
  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());
4160
+ const NUMERIC_LIKE_VALUE_RE$1 = /-?\d+(?:\.\d+)?/;
4161
+ function toDatasetKey$1(attributeName) {
4162
+ return attributeName.slice(5).replace(DATASET_NAME_RE$1, (_match, char) => char.toUpperCase());
4064
4163
  }
4065
- function collectDataset(node) {
4164
+ function collectDataset$1(node) {
4066
4165
  const dataset = {};
4067
4166
  for (const [key, value] of Object.entries(node.attribs ?? {})) {
4068
4167
  if (!key.startsWith("data-") || key.startsWith("data-sim-")) continue;
4069
- dataset[toDatasetKey(key)] = value;
4168
+ dataset[toDatasetKey$1(key)] = value;
4070
4169
  }
4071
4170
  return dataset;
4072
4171
  }
@@ -4095,7 +4194,7 @@ function findNodeByScopeId(root, scopeId) {
4095
4194
  }
4096
4195
  return null;
4097
4196
  }
4098
- function parseStyleDeclarations(styleValue) {
4197
+ function parseStyleDeclarations$1(styleValue) {
4099
4198
  const declarations = {};
4100
4199
  if (!styleValue) return declarations;
4101
4200
  for (const declaration of styleValue.split(";")) {
@@ -4106,17 +4205,17 @@ function parseStyleDeclarations(styleValue) {
4106
4205
  }
4107
4206
  return declarations;
4108
4207
  }
4109
- function parseNumericLikeValue(value) {
4208
+ function parseNumericLikeValue$1(value) {
4110
4209
  if (!value) return 0;
4111
- const match = value.match(NUMERIC_LIKE_VALUE_RE);
4210
+ const match = value.match(NUMERIC_LIKE_VALUE_RE$1);
4112
4211
  return match ? Number(match[0]) : 0;
4113
4212
  }
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);
4213
+ function resolveRect$1(node) {
4214
+ const style = parseStyleDeclarations$1(node.attribs?.style);
4215
+ const left = parseNumericLikeValue$1(node.attribs?.["data-sim-left"] ?? style.left);
4216
+ const top = parseNumericLikeValue$1(node.attribs?.["data-sim-top"] ?? style.top);
4217
+ const width = parseNumericLikeValue$1(node.attribs?.["data-sim-width"] ?? style.width);
4218
+ const height = parseNumericLikeValue$1(node.attribs?.["data-sim-height"] ?? style.height);
4120
4219
  return {
4121
4220
  bottom: top + height,
4122
4221
  height,
@@ -4126,10 +4225,16 @@ function resolveRect(node) {
4126
4225
  width
4127
4226
  };
4128
4227
  }
4228
+ function resolveSelectorScrollTop(root, selector) {
4229
+ const normalizedSelector = selector?.trim();
4230
+ if (!normalizedSelector) return null;
4231
+ const match = querySelectorAll(root, normalizedSelector)[0];
4232
+ return match ? resolveRect$1(match).top : null;
4233
+ }
4129
4234
  function resolveScrollOffset(node) {
4130
4235
  return {
4131
- scrollLeft: parseNumericLikeValue(node.attribs?.["data-sim-scroll-left"]),
4132
- scrollTop: parseNumericLikeValue(node.attribs?.["data-sim-scroll-top"])
4236
+ scrollLeft: parseNumericLikeValue$1(node.attribs?.["data-sim-scroll-left"]),
4237
+ scrollTop: parseNumericLikeValue$1(node.attribs?.["data-sim-scroll-top"])
4133
4238
  };
4134
4239
  }
4135
4240
  function resolvePropertyValue(node, propertyName) {
@@ -4137,7 +4242,7 @@ function resolvePropertyValue(node, propertyName) {
4137
4242
  if (!normalizedPropertyName) return;
4138
4243
  if (normalizedPropertyName === "id") return node.attribs?.id ?? "";
4139
4244
  if (normalizedPropertyName === "class") return node.attribs?.class ?? "";
4140
- if (normalizedPropertyName === "dataset") return collectDataset(node);
4245
+ if (normalizedPropertyName === "dataset") return collectDataset$1(node);
4141
4246
  return node.attribs?.[normalizedPropertyName];
4142
4247
  }
4143
4248
  function pickProperties(node, propertyNames) {
@@ -4146,18 +4251,18 @@ function pickProperties(node, propertyNames) {
4146
4251
  return result;
4147
4252
  }
4148
4253
  function pickComputedStyle(node, propertyNames) {
4149
- const style = parseStyleDeclarations(node.attribs?.style);
4254
+ const style = parseStyleDeclarations$1(node.attribs?.style);
4150
4255
  const result = {};
4151
4256
  for (const propertyName of propertyNames) result[propertyName] = style[propertyName] ?? "";
4152
4257
  return result;
4153
4258
  }
4154
- function resolveFieldsResult(node, fields, _options) {
4259
+ function resolveFieldsResult(node, fields, options) {
4155
4260
  const result = {};
4156
4261
  if (fields.id) result.id = node.attribs?.id ?? "";
4157
- if (fields.dataset) result.dataset = collectDataset(node);
4262
+ if (fields.dataset) result.dataset = collectDataset$1(node);
4158
4263
  if (fields.mark) result.mark = collectMark(node);
4159
4264
  if (fields.rect || fields.size) {
4160
- const rect = resolveRect(node);
4265
+ const rect = resolveRect$1(node);
4161
4266
  if (fields.rect) Object.assign(result, rect);
4162
4267
  if (fields.size) {
4163
4268
  result.width = rect.width;
@@ -4167,7 +4272,7 @@ function resolveFieldsResult(node, fields, _options) {
4167
4272
  if (fields.scrollOffset) Object.assign(result, resolveScrollOffset(node));
4168
4273
  if (Array.isArray(fields.properties) && fields.properties.length > 0) Object.assign(result, pickProperties(node, fields.properties));
4169
4274
  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" };
4275
+ if (fields.context) result.context = options.resolveContext?.(node) ?? { type: "unsupported-context" };
4171
4276
  if (fields.node) result.node = { type: node.name ?? "unknown" };
4172
4277
  return result;
4173
4278
  }
@@ -4210,6 +4315,749 @@ function resolveSelectorQueryScopeRoot(root, scopeId) {
4210
4315
  return scopedNode ? createScopedRoot(scopedNode) : root;
4211
4316
  }
4212
4317
  //#endregion
4318
+ //#region ../../mpcore/packages/simulator/src/view/animation.ts
4319
+ const DEFAULT_STEP_OPTION = {
4320
+ delay: 0,
4321
+ duration: 400,
4322
+ timingFunction: "linear",
4323
+ transformOrigin: "50% 50% 0"
4324
+ };
4325
+ function normalizeStepOption(option) {
4326
+ return {
4327
+ delay: option?.delay ?? DEFAULT_STEP_OPTION.delay,
4328
+ duration: option?.duration ?? DEFAULT_STEP_OPTION.duration,
4329
+ timingFunction: option?.timingFunction ?? DEFAULT_STEP_OPTION.timingFunction,
4330
+ transformOrigin: option?.transformOrigin ?? DEFAULT_STEP_OPTION.transformOrigin
4331
+ };
4332
+ }
4333
+ function normalizeLength(value) {
4334
+ if (typeof value === "number") return `${value}px`;
4335
+ return value ?? "0px";
4336
+ }
4337
+ function createAction(type, args) {
4338
+ return {
4339
+ args,
4340
+ type
4341
+ };
4342
+ }
4343
+ function createHeadlessAnimation(defaultOption) {
4344
+ const baseOption = normalizeStepOption(defaultOption);
4345
+ let currentActions = [];
4346
+ let queue = [];
4347
+ let animation;
4348
+ const append = (type, args) => {
4349
+ currentActions.push(createAction(type, args));
4350
+ return animation;
4351
+ };
4352
+ animation = {
4353
+ backgroundColor(value) {
4354
+ return append("backgroundColor", [value]);
4355
+ },
4356
+ bottom(value) {
4357
+ return append("bottom", [normalizeLength(value)]);
4358
+ },
4359
+ export() {
4360
+ const exported = queue.map((item) => ({
4361
+ animates: item.animates.map((action) => ({
4362
+ args: [...action.args],
4363
+ type: action.type
4364
+ })),
4365
+ option: { ...item.option }
4366
+ }));
4367
+ queue = [];
4368
+ currentActions = [];
4369
+ return { actions: exported };
4370
+ },
4371
+ height(value) {
4372
+ return append("height", [normalizeLength(value)]);
4373
+ },
4374
+ left(value) {
4375
+ return append("left", [normalizeLength(value)]);
4376
+ },
4377
+ opacity(value) {
4378
+ return append("opacity", [value]);
4379
+ },
4380
+ right(value) {
4381
+ return append("right", [normalizeLength(value)]);
4382
+ },
4383
+ rotate(angle) {
4384
+ return append("rotate", [`${angle}deg`]);
4385
+ },
4386
+ scale(sx, sy) {
4387
+ return append("scale", [sx, sy ?? sx]);
4388
+ },
4389
+ step(option) {
4390
+ queue.push({
4391
+ animates: currentActions.map((action) => ({
4392
+ args: [...action.args],
4393
+ type: action.type
4394
+ })),
4395
+ option: {
4396
+ ...baseOption,
4397
+ ...option ?? {}
4398
+ }
4399
+ });
4400
+ currentActions = [];
4401
+ return animation;
4402
+ },
4403
+ top(value) {
4404
+ return append("top", [normalizeLength(value)]);
4405
+ },
4406
+ translate(tx, ty) {
4407
+ return append("translate", [normalizeLength(tx), normalizeLength(ty)]);
4408
+ },
4409
+ translate3d(tx, ty, tz) {
4410
+ return append("translate3d", [
4411
+ normalizeLength(tx),
4412
+ normalizeLength(ty),
4413
+ normalizeLength(tz)
4414
+ ]);
4415
+ },
4416
+ translateX(translation) {
4417
+ return append("translateX", [normalizeLength(translation)]);
4418
+ },
4419
+ translateY(translation) {
4420
+ return append("translateY", [normalizeLength(translation)]);
4421
+ },
4422
+ translateZ(translation) {
4423
+ return append("translateZ", [normalizeLength(translation)]);
4424
+ },
4425
+ width(value) {
4426
+ return append("width", [normalizeLength(value)]);
4427
+ }
4428
+ };
4429
+ return animation;
4430
+ }
4431
+ //#endregion
4432
+ //#region ../../mpcore/packages/simulator/src/view/canvasContext.ts
4433
+ function findCanvasNode(root, canvasId) {
4434
+ if (root.type === "tag" && root.name === "canvas" && root.attribs?.["canvas-id"] === canvasId) return root;
4435
+ for (const child of root.children ?? []) {
4436
+ const match = findCanvasNode(child, canvasId);
4437
+ if (match) return match;
4438
+ }
4439
+ return null;
4440
+ }
4441
+ function cloneCall(call) {
4442
+ return {
4443
+ args: call.args.map((arg) => Array.isArray(arg) ? [...arg] : arg),
4444
+ type: call.type
4445
+ };
4446
+ }
4447
+ function createHeadlessCanvasContext(driver, canvasId, scope) {
4448
+ const defaultState = {
4449
+ fillStyle: "#000000",
4450
+ fontSize: 16,
4451
+ globalAlpha: 1,
4452
+ lineCap: "butt",
4453
+ lineDash: [],
4454
+ lineDashOffset: 0,
4455
+ lineJoin: "miter",
4456
+ miterLimit: 10,
4457
+ lineWidth: 1,
4458
+ shadowBlur: 0,
4459
+ shadowColor: "#000000",
4460
+ shadowOffsetX: 0,
4461
+ shadowOffsetY: 0,
4462
+ strokeStyle: "#000000",
4463
+ textAlign: "start",
4464
+ textBaseline: "alphabetic"
4465
+ };
4466
+ let state = { ...defaultState };
4467
+ let stateStack = [];
4468
+ let drawCalls = [];
4469
+ let snapshot = {
4470
+ canvasId,
4471
+ drawCalls: [],
4472
+ fillStyle: state.fillStyle,
4473
+ fontSize: state.fontSize,
4474
+ globalAlpha: state.globalAlpha,
4475
+ lineCap: state.lineCap,
4476
+ lineDash: [...state.lineDash],
4477
+ lineDashOffset: state.lineDashOffset,
4478
+ lineJoin: state.lineJoin,
4479
+ miterLimit: state.miterLimit,
4480
+ lineWidth: state.lineWidth,
4481
+ reserve: false,
4482
+ shadowBlur: state.shadowBlur,
4483
+ shadowColor: state.shadowColor,
4484
+ shadowOffsetX: state.shadowOffsetX,
4485
+ shadowOffsetY: state.shadowOffsetY,
4486
+ strokeStyle: state.strokeStyle,
4487
+ textAlign: state.textAlign,
4488
+ textBaseline: state.textBaseline
4489
+ };
4490
+ const resolveCanvas = () => {
4491
+ const scopeResolution = driver.resolveScope(scope);
4492
+ if (scopeResolution.kind === "missing") return null;
4493
+ const rendered = driver.renderCurrentPage();
4494
+ const root = scopeResolution.kind === "component" ? resolveSelectorQueryScopeRoot(rendered.root, scopeResolution.scopeId) : rendered.root;
4495
+ return root ? findCanvasNode(root, canvasId) : null;
4496
+ };
4497
+ const ensureCanvas = () => {
4498
+ const node = resolveCanvas();
4499
+ if (!node) throw new Error(`Canvas with canvas-id "${canvasId}" was not found in headless runtime.`);
4500
+ return node;
4501
+ };
4502
+ const record = (type, args) => {
4503
+ ensureCanvas();
4504
+ drawCalls.push({
4505
+ args,
4506
+ type
4507
+ });
4508
+ };
4509
+ return {
4510
+ arc(x, y, r, sAngle, eAngle, counterclockwise) {
4511
+ record("arc", [
4512
+ x,
4513
+ y,
4514
+ r,
4515
+ sAngle,
4516
+ eAngle,
4517
+ Boolean(counterclockwise)
4518
+ ]);
4519
+ },
4520
+ arcTo(x1, y1, x2, y2, radius) {
4521
+ record("arcTo", [
4522
+ x1,
4523
+ y1,
4524
+ x2,
4525
+ y2,
4526
+ radius
4527
+ ]);
4528
+ },
4529
+ bezierCurveTo(cp1x, cp1y, cp2x, cp2y, x, y) {
4530
+ record("bezierCurveTo", [
4531
+ cp1x,
4532
+ cp1y,
4533
+ cp2x,
4534
+ cp2y,
4535
+ x,
4536
+ y
4537
+ ]);
4538
+ },
4539
+ __getSnapshot() {
4540
+ return {
4541
+ canvasId: snapshot.canvasId,
4542
+ drawCalls: snapshot.drawCalls.map(cloneCall),
4543
+ fillStyle: snapshot.fillStyle,
4544
+ fontSize: snapshot.fontSize,
4545
+ globalAlpha: snapshot.globalAlpha,
4546
+ lineCap: snapshot.lineCap,
4547
+ lineDash: [...snapshot.lineDash],
4548
+ lineDashOffset: snapshot.lineDashOffset,
4549
+ lineJoin: snapshot.lineJoin,
4550
+ miterLimit: snapshot.miterLimit,
4551
+ lineWidth: snapshot.lineWidth,
4552
+ reserve: snapshot.reserve,
4553
+ shadowBlur: snapshot.shadowBlur,
4554
+ shadowColor: snapshot.shadowColor,
4555
+ shadowOffsetX: snapshot.shadowOffsetX,
4556
+ shadowOffsetY: snapshot.shadowOffsetY,
4557
+ strokeStyle: snapshot.strokeStyle,
4558
+ textAlign: snapshot.textAlign,
4559
+ textBaseline: snapshot.textBaseline
4560
+ };
4561
+ },
4562
+ beginPath() {
4563
+ record("beginPath", []);
4564
+ },
4565
+ clearRect(x, y, width, height) {
4566
+ record("clearRect", [
4567
+ x,
4568
+ y,
4569
+ width,
4570
+ height
4571
+ ]);
4572
+ },
4573
+ clip(fillRule) {
4574
+ record("clip", fillRule == null ? [] : [fillRule]);
4575
+ },
4576
+ closePath() {
4577
+ record("closePath", []);
4578
+ },
4579
+ draw(reserve, callback) {
4580
+ ensureCanvas();
4581
+ snapshot = {
4582
+ canvasId,
4583
+ drawCalls: reserve ? [...snapshot.drawCalls.map(cloneCall), ...drawCalls.map(cloneCall)] : drawCalls.map(cloneCall),
4584
+ fillStyle: state.fillStyle,
4585
+ fontSize: state.fontSize,
4586
+ globalAlpha: state.globalAlpha,
4587
+ lineCap: state.lineCap,
4588
+ lineDash: [...state.lineDash],
4589
+ lineDashOffset: state.lineDashOffset,
4590
+ lineJoin: state.lineJoin,
4591
+ miterLimit: state.miterLimit,
4592
+ lineWidth: state.lineWidth,
4593
+ reserve: Boolean(reserve),
4594
+ shadowBlur: state.shadowBlur,
4595
+ shadowColor: state.shadowColor,
4596
+ shadowOffsetX: state.shadowOffsetX,
4597
+ shadowOffsetY: state.shadowOffsetY,
4598
+ strokeStyle: state.strokeStyle,
4599
+ textAlign: state.textAlign,
4600
+ textBaseline: state.textBaseline
4601
+ };
4602
+ drawCalls = [];
4603
+ state = { ...defaultState };
4604
+ stateStack = [];
4605
+ callback?.();
4606
+ },
4607
+ drawImage(image, ...args) {
4608
+ record("drawImage", [image, ...args]);
4609
+ },
4610
+ fill(fillRule) {
4611
+ record("fill", fillRule == null ? [] : [fillRule]);
4612
+ },
4613
+ fillRect(x, y, width, height) {
4614
+ record("fillRect", [
4615
+ x,
4616
+ y,
4617
+ width,
4618
+ height
4619
+ ]);
4620
+ },
4621
+ fillText(text, x, y, maxWidth) {
4622
+ record("fillText", maxWidth == null ? [
4623
+ text,
4624
+ x,
4625
+ y
4626
+ ] : [
4627
+ text,
4628
+ x,
4629
+ y,
4630
+ maxWidth
4631
+ ]);
4632
+ },
4633
+ lineTo(x, y) {
4634
+ record("lineTo", [x, y]);
4635
+ },
4636
+ measureText(text) {
4637
+ return { width: text.length * state.fontSize * .5 };
4638
+ },
4639
+ moveTo(x, y) {
4640
+ record("moveTo", [x, y]);
4641
+ },
4642
+ quadraticCurveTo(cpx, cpy, x, y) {
4643
+ record("quadraticCurveTo", [
4644
+ cpx,
4645
+ cpy,
4646
+ x,
4647
+ y
4648
+ ]);
4649
+ },
4650
+ rect(x, y, width, height) {
4651
+ record("rect", [
4652
+ x,
4653
+ y,
4654
+ width,
4655
+ height
4656
+ ]);
4657
+ },
4658
+ restore() {
4659
+ state = stateStack.pop() ?? { ...defaultState };
4660
+ record("restore", []);
4661
+ },
4662
+ rotate(rotate) {
4663
+ record("rotate", [rotate]);
4664
+ },
4665
+ save() {
4666
+ stateStack.push({ ...state });
4667
+ record("save", []);
4668
+ },
4669
+ scale(scaleWidth, scaleHeight) {
4670
+ record("scale", [scaleWidth, scaleHeight]);
4671
+ },
4672
+ setFillStyle(value) {
4673
+ state.fillStyle = String(value);
4674
+ record("setFillStyle", [value]);
4675
+ },
4676
+ setFontSize(fontSize) {
4677
+ state.fontSize = Number(fontSize);
4678
+ record("setFontSize", [fontSize]);
4679
+ },
4680
+ setGlobalAlpha(value) {
4681
+ state.globalAlpha = Number(value);
4682
+ record("setGlobalAlpha", [value]);
4683
+ },
4684
+ setLineCap(value) {
4685
+ state.lineCap = String(value);
4686
+ record("setLineCap", [value]);
4687
+ },
4688
+ setLineDash(pattern, offset) {
4689
+ state.lineDash = pattern.map((item) => Number(item));
4690
+ state.lineDashOffset = offset == null ? 0 : Number(offset);
4691
+ record("setLineDash", offset == null ? [pattern.map((item) => Number(item))] : [pattern.map((item) => Number(item)), Number(offset)]);
4692
+ },
4693
+ setLineJoin(value) {
4694
+ state.lineJoin = String(value);
4695
+ record("setLineJoin", [value]);
4696
+ },
4697
+ setMiterLimit(value) {
4698
+ state.miterLimit = Number(value);
4699
+ record("setMiterLimit", [value]);
4700
+ },
4701
+ setLineWidth(value) {
4702
+ state.lineWidth = Number(value);
4703
+ record("setLineWidth", [value]);
4704
+ },
4705
+ setShadow(offsetX, offsetY, blur, color) {
4706
+ state.shadowOffsetX = Number(offsetX);
4707
+ state.shadowOffsetY = Number(offsetY);
4708
+ state.shadowBlur = Number(blur);
4709
+ state.shadowColor = String(color);
4710
+ record("setShadow", [
4711
+ offsetX,
4712
+ offsetY,
4713
+ blur,
4714
+ color
4715
+ ]);
4716
+ },
4717
+ setStrokeStyle(value) {
4718
+ state.strokeStyle = String(value);
4719
+ record("setStrokeStyle", [value]);
4720
+ },
4721
+ setTextAlign(value) {
4722
+ state.textAlign = String(value);
4723
+ record("setTextAlign", [value]);
4724
+ },
4725
+ setTextBaseline(value) {
4726
+ state.textBaseline = String(value);
4727
+ record("setTextBaseline", [value]);
4728
+ },
4729
+ stroke() {
4730
+ record("stroke", []);
4731
+ },
4732
+ strokeRect(x, y, width, height) {
4733
+ record("strokeRect", [
4734
+ x,
4735
+ y,
4736
+ width,
4737
+ height
4738
+ ]);
4739
+ },
4740
+ strokeText(text, x, y, maxWidth) {
4741
+ record("strokeText", maxWidth == null ? [
4742
+ text,
4743
+ x,
4744
+ y
4745
+ ] : [
4746
+ text,
4747
+ x,
4748
+ y,
4749
+ maxWidth
4750
+ ]);
4751
+ },
4752
+ translate(x, y) {
4753
+ record("translate", [x, y]);
4754
+ }
4755
+ };
4756
+ }
4757
+ //#endregion
4758
+ //#region ../../mpcore/packages/simulator/src/view/intersectionObserver.ts
4759
+ const NUMERIC_LIKE_VALUE_RE = /-?\d+(?:\.\d+)?/;
4760
+ function normalizeMargins(margins) {
4761
+ return {
4762
+ bottom: Number(margins?.bottom) || 0,
4763
+ left: Number(margins?.left) || 0,
4764
+ right: Number(margins?.right) || 0,
4765
+ top: Number(margins?.top) || 0
4766
+ };
4767
+ }
4768
+ function parseStyleDeclarations(styleValue) {
4769
+ const declarations = {};
4770
+ if (!styleValue) return declarations;
4771
+ for (const declaration of styleValue.split(";")) {
4772
+ const [rawProperty, ...rawValueParts] = declaration.split(":");
4773
+ const property = rawProperty?.trim();
4774
+ if (!property) continue;
4775
+ declarations[property] = rawValueParts.join(":").trim();
4776
+ }
4777
+ return declarations;
4778
+ }
4779
+ function parseNumericLikeValue(value) {
4780
+ if (!value) return 0;
4781
+ const match = value.match(NUMERIC_LIKE_VALUE_RE);
4782
+ return match ? Number(match[0]) : 0;
4783
+ }
4784
+ function resolveRect(node) {
4785
+ const style = parseStyleDeclarations(node.attribs?.style);
4786
+ const left = parseNumericLikeValue(node.attribs?.["data-sim-left"] ?? style.left);
4787
+ const top = parseNumericLikeValue(node.attribs?.["data-sim-top"] ?? style.top);
4788
+ const width = parseNumericLikeValue(node.attribs?.["data-sim-width"] ?? style.width);
4789
+ const height = parseNumericLikeValue(node.attribs?.["data-sim-height"] ?? style.height);
4790
+ return {
4791
+ bottom: top + height,
4792
+ height,
4793
+ left,
4794
+ right: left + width,
4795
+ top,
4796
+ width
4797
+ };
4798
+ }
4799
+ function applyMargins(rect, margins) {
4800
+ const top = rect.top - (margins.top ?? 0);
4801
+ const left = rect.left - (margins.left ?? 0);
4802
+ const right = rect.right + (margins.right ?? 0);
4803
+ const bottom = rect.bottom + (margins.bottom ?? 0);
4804
+ return {
4805
+ bottom,
4806
+ height: Math.max(0, bottom - top),
4807
+ left,
4808
+ right,
4809
+ top,
4810
+ width: Math.max(0, right - left)
4811
+ };
4812
+ }
4813
+ function intersectRects(source, target) {
4814
+ const left = Math.max(source.left, target.left);
4815
+ const top = Math.max(source.top, target.top);
4816
+ const right = Math.min(source.right, target.right);
4817
+ const bottom = Math.min(source.bottom, target.bottom);
4818
+ if (right <= left || bottom <= top) return {
4819
+ bottom: top,
4820
+ height: 0,
4821
+ left,
4822
+ right: left,
4823
+ top,
4824
+ width: 0
4825
+ };
4826
+ return {
4827
+ bottom,
4828
+ height: bottom - top,
4829
+ left,
4830
+ right,
4831
+ top,
4832
+ width: right - left
4833
+ };
4834
+ }
4835
+ function resolveViewportRect(windowInfo) {
4836
+ return {
4837
+ bottom: windowInfo.windowHeight,
4838
+ height: windowInfo.windowHeight,
4839
+ left: 0,
4840
+ right: windowInfo.windowWidth,
4841
+ top: 0,
4842
+ width: windowInfo.windowWidth
4843
+ };
4844
+ }
4845
+ function createHeadlessIntersectionObserver(driver, scope, _options) {
4846
+ let active = true;
4847
+ let relativeTarget = {
4848
+ kind: "viewport",
4849
+ margins: normalizeMargins()
4850
+ };
4851
+ const resolveScopedRoot = () => {
4852
+ const scopeResolution = driver.resolveScope(scope);
4853
+ if (scopeResolution.kind === "missing") return null;
4854
+ const rendered = driver.renderCurrentPage();
4855
+ return scopeResolution.kind === "component" ? resolveSelectorQueryScopeRoot(rendered.root, scopeResolution.scopeId) : rendered.root;
4856
+ };
4857
+ const resolveTargetNode = (selector) => {
4858
+ const scopedRoot = resolveScopedRoot();
4859
+ if (!scopedRoot) return null;
4860
+ return querySelectorAll(scopedRoot, selector)[0] ?? null;
4861
+ };
4862
+ const resolveRelativeRect = () => {
4863
+ if (relativeTarget.kind === "viewport") return applyMargins(resolveViewportRect(driver.getWindowInfo()), relativeTarget.margins);
4864
+ const relativeNode = resolveTargetNode(relativeTarget.selector);
4865
+ if (!relativeNode) return null;
4866
+ return applyMargins(resolveRect(relativeNode), relativeTarget.margins);
4867
+ };
4868
+ return {
4869
+ disconnect() {
4870
+ active = false;
4871
+ },
4872
+ observe(selector, callback) {
4873
+ if (!active) return;
4874
+ const targetNode = resolveTargetNode(selector);
4875
+ const relativeRect = resolveRelativeRect();
4876
+ if (!targetNode || !relativeRect) return;
4877
+ const boundingClientRect = resolveRect(targetNode);
4878
+ const intersectionRect = intersectRects(boundingClientRect, relativeRect);
4879
+ const targetArea = boundingClientRect.width * boundingClientRect.height;
4880
+ const intersectionArea = intersectionRect.width * intersectionRect.height;
4881
+ callback({
4882
+ boundingClientRect,
4883
+ id: targetNode.attribs?.id ?? "",
4884
+ intersectionRatio: targetArea > 0 ? intersectionArea / targetArea : 0,
4885
+ intersectionRect,
4886
+ relativeRect
4887
+ });
4888
+ },
4889
+ relativeTo(selector, margins) {
4890
+ relativeTarget = {
4891
+ kind: "selector",
4892
+ margins: normalizeMargins(margins),
4893
+ selector
4894
+ };
4895
+ return this;
4896
+ },
4897
+ relativeToViewport(margins) {
4898
+ relativeTarget = {
4899
+ kind: "viewport",
4900
+ margins: normalizeMargins(margins)
4901
+ };
4902
+ return this;
4903
+ }
4904
+ };
4905
+ }
4906
+ //#endregion
4907
+ //#region ../../mpcore/packages/simulator/src/view/mediaQueryObserver.ts
4908
+ function resolveOrientation(windowInfo) {
4909
+ return windowInfo.windowWidth >= windowInfo.windowHeight ? "landscape" : "portrait";
4910
+ }
4911
+ function resolveMatches(descriptor, windowInfo) {
4912
+ if (descriptor.width != null && windowInfo.windowWidth !== descriptor.width) return false;
4913
+ if (descriptor.minWidth != null && windowInfo.windowWidth < descriptor.minWidth) return false;
4914
+ if (descriptor.maxWidth != null && windowInfo.windowWidth > descriptor.maxWidth) return false;
4915
+ if (descriptor.height != null && windowInfo.windowHeight !== descriptor.height) return false;
4916
+ if (descriptor.minHeight != null && windowInfo.windowHeight < descriptor.minHeight) return false;
4917
+ if (descriptor.maxHeight != null && windowInfo.windowHeight > descriptor.maxHeight) return false;
4918
+ if (descriptor.orientation != null && resolveOrientation(windowInfo) !== descriptor.orientation) return false;
4919
+ return true;
4920
+ }
4921
+ function createHeadlessMediaQueryObserver(driver, onDisconnect) {
4922
+ let active = true;
4923
+ let callback;
4924
+ let descriptor;
4925
+ let hasObserved = false;
4926
+ let lastMatches;
4927
+ const emit = (force) => {
4928
+ if (!active || !hasObserved || !callback || !descriptor) return;
4929
+ const matches = resolveMatches(descriptor, driver.getWindowInfo());
4930
+ if (!force && lastMatches === matches) return;
4931
+ lastMatches = matches;
4932
+ callback({ matches });
4933
+ };
4934
+ const observer = {
4935
+ disconnect() {
4936
+ if (!active) return;
4937
+ active = false;
4938
+ hasObserved = false;
4939
+ callback = void 0;
4940
+ descriptor = void 0;
4941
+ onDisconnect?.();
4942
+ },
4943
+ observe(nextDescriptor, nextCallback) {
4944
+ if (!active) return;
4945
+ descriptor = { ...nextDescriptor };
4946
+ callback = nextCallback;
4947
+ hasObserved = true;
4948
+ emit(true);
4949
+ }
4950
+ };
4951
+ return {
4952
+ disconnect: observer.disconnect,
4953
+ notify() {
4954
+ emit(false);
4955
+ },
4956
+ observer
4957
+ };
4958
+ }
4959
+ //#endregion
4960
+ //#region ../../mpcore/packages/simulator/src/view/videoContext.ts
4961
+ const DATASET_NAME_RE = /-([a-z])/g;
4962
+ function toDatasetKey(attributeName) {
4963
+ return attributeName.slice(5).replace(DATASET_NAME_RE, (_match, char) => char.toUpperCase());
4964
+ }
4965
+ function collectDataset(node) {
4966
+ const dataset = {};
4967
+ for (const [key, value] of Object.entries(node.attribs ?? {})) {
4968
+ if (!key.startsWith("data-") || key.startsWith("data-sim-")) continue;
4969
+ dataset[toDatasetKey(key)] = value;
4970
+ }
4971
+ return dataset;
4972
+ }
4973
+ function resolveEventBinding(node, eventName) {
4974
+ const normalizedEventName = eventName.trim();
4975
+ if (!normalizedEventName) return null;
4976
+ const bindingAttrs = [
4977
+ `bind${normalizedEventName}`,
4978
+ `bind:${normalizedEventName}`,
4979
+ `catch${normalizedEventName}`,
4980
+ `catch:${normalizedEventName}`
4981
+ ];
4982
+ for (const attributeName of bindingAttrs) {
4983
+ const methodName = node.attribs?.[attributeName]?.trim();
4984
+ if (methodName) return methodName;
4985
+ }
4986
+ return null;
4987
+ }
4988
+ function createEventPayload(node, eventName, detail) {
4989
+ const dataset = collectDataset(node);
4990
+ const nodeId = node.attribs?.id ?? "";
4991
+ return {
4992
+ bubbles: false,
4993
+ capturePhase: false,
4994
+ composed: false,
4995
+ currentTarget: {
4996
+ dataset,
4997
+ id: nodeId
4998
+ },
4999
+ detail,
5000
+ mark: void 0,
5001
+ target: {
5002
+ dataset,
5003
+ id: nodeId
5004
+ },
5005
+ type: eventName
5006
+ };
5007
+ }
5008
+ function createHeadlessVideoContext(driver, videoId, scope) {
5009
+ const state = {
5010
+ currentTime: 0,
5011
+ fullScreen: false,
5012
+ paused: true
5013
+ };
5014
+ const resolveNode = () => {
5015
+ const scopeResolution = driver.resolveScope(scope);
5016
+ if (scopeResolution.kind === "missing") return null;
5017
+ const rendered = driver.renderCurrentPage();
5018
+ return querySelectorAll(scopeResolution.kind === "component" ? resolveSelectorQueryScopeRoot(rendered.root, scopeResolution.scopeId) : rendered.root, `#${videoId}`)[0] ?? null;
5019
+ };
5020
+ const dispatch = (eventName, detail) => {
5021
+ const node = resolveNode();
5022
+ if (!node) return;
5023
+ const methodName = resolveEventBinding(node, eventName);
5024
+ if (!methodName) return;
5025
+ driver.callScopeMethod(node.attribs?.["data-sim-scope"] ?? null, methodName, createEventPayload(node, eventName, detail));
5026
+ };
5027
+ return {
5028
+ exitFullScreen() {
5029
+ state.fullScreen = false;
5030
+ dispatch("fullscreenchange", {
5031
+ currentTime: state.currentTime,
5032
+ fullScreen: false
5033
+ });
5034
+ },
5035
+ pause() {
5036
+ state.paused = true;
5037
+ dispatch("pause", { currentTime: state.currentTime });
5038
+ },
5039
+ play() {
5040
+ state.paused = false;
5041
+ dispatch("play", { currentTime: state.currentTime });
5042
+ },
5043
+ requestFullScreen() {
5044
+ state.fullScreen = true;
5045
+ dispatch("fullscreenchange", {
5046
+ currentTime: state.currentTime,
5047
+ fullScreen: true
5048
+ });
5049
+ },
5050
+ seek(position) {
5051
+ state.currentTime = Number.isFinite(position) ? Number(position) : 0;
5052
+ },
5053
+ stop() {
5054
+ state.currentTime = 0;
5055
+ state.paused = true;
5056
+ dispatch("pause", { currentTime: state.currentTime });
5057
+ }
5058
+ };
5059
+ }
5060
+ //#endregion
4213
5061
  //#region ../../mpcore/packages/simulator/src/runtime/moduleLoader.ts
4214
5062
  function createRequireNotFoundError(request, importer) {
4215
5063
  return /* @__PURE__ */ new Error(`Cannot resolve require("${request}") from ${normalize(importer)} in headless runtime.`);
@@ -4387,9 +5235,228 @@ const DEFAULT_MODAL_CONFIRM_TEXT = "确定";
4387
5235
  const TRAILING_SLASH_RE = /\/+$/;
4388
5236
  const SAVED_FILE_PREFIXES = ["headless://saved/", "headless://wxfile/saved/"];
4389
5237
  const textEncoder = new TextEncoder();
5238
+ const IMAGE_EXTENSION_RE = /\.([^.?#/]+)(?:[?#].*)?$/;
5239
+ const LEADING_DOT_RE = /^\.+/;
5240
+ const IMAGE_FILE_EXTENSIONS = new Set([
5241
+ "bmp",
5242
+ "gif",
5243
+ "heic",
5244
+ "jpeg",
5245
+ "jpg",
5246
+ "png",
5247
+ "webp"
5248
+ ]);
5249
+ const VIDEO_FILE_EXTENSIONS = new Set([
5250
+ "avi",
5251
+ "m4v",
5252
+ "mov",
5253
+ "mp4",
5254
+ "mpeg",
5255
+ "mpg",
5256
+ "webm"
5257
+ ]);
4390
5258
  function byteLength(input) {
4391
5259
  return textEncoder.encode(input).byteLength;
4392
5260
  }
5261
+ function rotateLeft(value, bits) {
5262
+ return (value << bits | value >>> 32 - bits) >>> 0;
5263
+ }
5264
+ function toHexWordLittleEndian(value) {
5265
+ let output = "";
5266
+ for (let index = 0; index < 4; index += 1) output += (value >>> index * 8 & 255).toString(16).padStart(2, "0");
5267
+ return output;
5268
+ }
5269
+ function toHexWordBigEndian(value) {
5270
+ return value.toString(16).padStart(8, "0");
5271
+ }
5272
+ function computeMd5Digest(input) {
5273
+ const bytes = Array.from(textEncoder.encode(input));
5274
+ const bitLength = bytes.length * 8;
5275
+ const lowBits = bitLength >>> 0;
5276
+ const highBits = Math.floor(bitLength / 4294967296) >>> 0;
5277
+ bytes.push(128);
5278
+ while (bytes.length % 64 !== 56) bytes.push(0);
5279
+ for (let index = 0; index < 4; index += 1) bytes.push(lowBits >>> index * 8 & 255);
5280
+ for (let index = 0; index < 4; index += 1) bytes.push(highBits >>> index * 8 & 255);
5281
+ const shifts = [
5282
+ 7,
5283
+ 12,
5284
+ 17,
5285
+ 22,
5286
+ 7,
5287
+ 12,
5288
+ 17,
5289
+ 22,
5290
+ 7,
5291
+ 12,
5292
+ 17,
5293
+ 22,
5294
+ 7,
5295
+ 12,
5296
+ 17,
5297
+ 22,
5298
+ 5,
5299
+ 9,
5300
+ 14,
5301
+ 20,
5302
+ 5,
5303
+ 9,
5304
+ 14,
5305
+ 20,
5306
+ 5,
5307
+ 9,
5308
+ 14,
5309
+ 20,
5310
+ 5,
5311
+ 9,
5312
+ 14,
5313
+ 20,
5314
+ 4,
5315
+ 11,
5316
+ 16,
5317
+ 23,
5318
+ 4,
5319
+ 11,
5320
+ 16,
5321
+ 23,
5322
+ 4,
5323
+ 11,
5324
+ 16,
5325
+ 23,
5326
+ 4,
5327
+ 11,
5328
+ 16,
5329
+ 23,
5330
+ 6,
5331
+ 10,
5332
+ 15,
5333
+ 21,
5334
+ 6,
5335
+ 10,
5336
+ 15,
5337
+ 21,
5338
+ 6,
5339
+ 10,
5340
+ 15,
5341
+ 21,
5342
+ 6,
5343
+ 10,
5344
+ 15,
5345
+ 21
5346
+ ];
5347
+ const table = Array.from({ length: 64 }, (_, index) => Math.floor(Math.abs(Math.sin(index + 1)) * 4294967296) >>> 0);
5348
+ let a0 = 1732584193;
5349
+ let b0 = 4023233417;
5350
+ let c0 = 2562383102;
5351
+ let d0 = 271733878;
5352
+ for (let chunkOffset = 0; chunkOffset < bytes.length; chunkOffset += 64) {
5353
+ const words = Array.from({ length: 16 }, (_, index) => {
5354
+ const wordOffset = chunkOffset + index * 4;
5355
+ return (bytes[wordOffset] | bytes[wordOffset + 1] << 8 | bytes[wordOffset + 2] << 16 | bytes[wordOffset + 3] << 24) >>> 0;
5356
+ });
5357
+ let a = a0;
5358
+ let b = b0;
5359
+ let c = c0;
5360
+ let d = d0;
5361
+ for (let index = 0; index < 64; index += 1) {
5362
+ let f = 0;
5363
+ let g = 0;
5364
+ if (index < 16) {
5365
+ f = (b & c | ~b & d) >>> 0;
5366
+ g = index;
5367
+ } else if (index < 32) {
5368
+ f = (d & b | ~d & c) >>> 0;
5369
+ g = (5 * index + 1) % 16;
5370
+ } else if (index < 48) {
5371
+ f = (b ^ c ^ d) >>> 0;
5372
+ g = (3 * index + 5) % 16;
5373
+ } else {
5374
+ f = (c ^ (b | ~d)) >>> 0;
5375
+ g = 7 * index % 16;
5376
+ }
5377
+ const next = a + f + table[index] + words[g] >>> 0;
5378
+ a = d;
5379
+ d = c;
5380
+ c = b;
5381
+ b = b + rotateLeft(next, shifts[index]) >>> 0;
5382
+ }
5383
+ a0 = a0 + a >>> 0;
5384
+ b0 = b0 + b >>> 0;
5385
+ c0 = c0 + c >>> 0;
5386
+ d0 = d0 + d >>> 0;
5387
+ }
5388
+ return [
5389
+ toHexWordLittleEndian(a0),
5390
+ toHexWordLittleEndian(b0),
5391
+ toHexWordLittleEndian(c0),
5392
+ toHexWordLittleEndian(d0)
5393
+ ].join("");
5394
+ }
5395
+ function computeSha1Digest(input) {
5396
+ const bytes = Array.from(textEncoder.encode(input));
5397
+ const bitLength = bytes.length * 8;
5398
+ const lowBits = bitLength >>> 0;
5399
+ const highBits = Math.floor(bitLength / 4294967296) >>> 0;
5400
+ bytes.push(128);
5401
+ while (bytes.length % 64 !== 56) bytes.push(0);
5402
+ for (let index = 3; index >= 0; index -= 1) bytes.push(highBits >>> index * 8 & 255);
5403
+ for (let index = 3; index >= 0; index -= 1) bytes.push(lowBits >>> index * 8 & 255);
5404
+ let h0 = 1732584193;
5405
+ let h1 = 4023233417;
5406
+ let h2 = 2562383102;
5407
+ let h3 = 271733878;
5408
+ let h4 = 3285377520;
5409
+ for (let chunkOffset = 0; chunkOffset < bytes.length; chunkOffset += 64) {
5410
+ const words = Array.from({ length: 80 }, (_, index) => {
5411
+ if (index < 16) {
5412
+ const wordOffset = chunkOffset + index * 4;
5413
+ return (bytes[wordOffset] << 24 | bytes[wordOffset + 1] << 16 | bytes[wordOffset + 2] << 8 | bytes[wordOffset + 3]) >>> 0;
5414
+ }
5415
+ return 0;
5416
+ });
5417
+ for (let index = 16; index < 80; index += 1) words[index] = rotateLeft(words[index - 3] ^ words[index - 8] ^ words[index - 14] ^ words[index - 16], 1);
5418
+ let a = h0;
5419
+ let b = h1;
5420
+ let c = h2;
5421
+ let d = h3;
5422
+ let e = h4;
5423
+ for (let index = 0; index < 80; index += 1) {
5424
+ let f = 0;
5425
+ let k = 0;
5426
+ if (index < 20) {
5427
+ f = (b & c | ~b & d) >>> 0;
5428
+ k = 1518500249;
5429
+ } else if (index < 40) {
5430
+ f = (b ^ c ^ d) >>> 0;
5431
+ k = 1859775393;
5432
+ } else if (index < 60) {
5433
+ f = (b & c | b & d | c & d) >>> 0;
5434
+ k = 2400959708;
5435
+ } else {
5436
+ f = (b ^ c ^ d) >>> 0;
5437
+ k = 3395469782;
5438
+ }
5439
+ const next = rotateLeft(a, 5) + f + e + k + words[index] >>> 0;
5440
+ e = d;
5441
+ d = c;
5442
+ c = rotateLeft(b, 30);
5443
+ b = a;
5444
+ a = next;
5445
+ }
5446
+ h0 = h0 + a >>> 0;
5447
+ h1 = h1 + b >>> 0;
5448
+ h2 = h2 + c >>> 0;
5449
+ h3 = h3 + d >>> 0;
5450
+ h4 = h4 + e >>> 0;
5451
+ }
5452
+ return [
5453
+ toHexWordBigEndian(h0),
5454
+ toHexWordBigEndian(h1),
5455
+ toHexWordBigEndian(h2),
5456
+ toHexWordBigEndian(h3),
5457
+ toHexWordBigEndian(h4)
5458
+ ].join("");
5459
+ }
4393
5460
  function cloneShareMenuSnapshot(snapshot) {
4394
5461
  return {
4395
5462
  isUpdatableMessage: snapshot.isUpdatableMessage,
@@ -4398,6 +5465,116 @@ function cloneShareMenuSnapshot(snapshot) {
4398
5465
  withShareTicket: snapshot.withShareTicket
4399
5466
  };
4400
5467
  }
5468
+ function normalizeImageType(type) {
5469
+ const normalized = (type ?? "").trim().toLowerCase();
5470
+ if (normalized === "jpg") return "jpeg";
5471
+ return normalized;
5472
+ }
5473
+ function normalizeFileExtension(extension) {
5474
+ return extension.trim().replace(LEADING_DOT_RE, "").toLowerCase();
5475
+ }
5476
+ function inferDocumentFileType(filePath) {
5477
+ const extensionMatch = filePath.match(IMAGE_EXTENSION_RE);
5478
+ return extensionMatch ? normalizeFileExtension(extensionMatch[1]) : "unknown";
5479
+ }
5480
+ function isImageFileExtension(extension) {
5481
+ return IMAGE_FILE_EXTENSIONS.has(normalizeFileExtension(extension));
5482
+ }
5483
+ function isVideoFileExtension(extension) {
5484
+ return VIDEO_FILE_EXTENSIONS.has(normalizeFileExtension(extension));
5485
+ }
5486
+ function resolveMessageFileExtensions(option) {
5487
+ const filteredExtensions = (Array.isArray(option.extension) ? option.extension.filter((item) => typeof item === "string").map(normalizeFileExtension).filter(Boolean) : []).filter((extension) => {
5488
+ if (option.type === "image") return isImageFileExtension(extension);
5489
+ if (option.type === "video") return isVideoFileExtension(extension);
5490
+ if (option.type === "file") return !isImageFileExtension(extension) && !isVideoFileExtension(extension);
5491
+ return true;
5492
+ });
5493
+ if (filteredExtensions.length > 0) return filteredExtensions;
5494
+ if (option.type === "image") return ["png", "jpg"];
5495
+ if (option.type === "video") return ["mp4"];
5496
+ if (option.type === "file") return [
5497
+ "txt",
5498
+ "pdf",
5499
+ "doc"
5500
+ ];
5501
+ return [
5502
+ "png",
5503
+ "pdf",
5504
+ "mp4"
5505
+ ];
5506
+ }
5507
+ function inferImageType(filePath, fileContent) {
5508
+ const extensionMatch = filePath.match(IMAGE_EXTENSION_RE);
5509
+ if (extensionMatch) return normalizeImageType(extensionMatch[1]);
5510
+ try {
5511
+ return normalizeImageType(JSON.parse(fileContent)?.config?.fileType) || "png";
5512
+ } catch {
5513
+ return "unknown";
5514
+ }
5515
+ }
5516
+ function inferImageSize(fileContent) {
5517
+ try {
5518
+ const payload = JSON.parse(fileContent);
5519
+ return {
5520
+ height: payload?.config?.destHeight ?? payload?.config?.height ?? 0,
5521
+ width: payload?.config?.destWidth ?? payload?.config?.width ?? 0
5522
+ };
5523
+ } catch {
5524
+ return {
5525
+ height: 0,
5526
+ width: 0
5527
+ };
5528
+ }
5529
+ }
5530
+ function buildImageTempFilePayload(fileType, width, height, metadata = {}) {
5531
+ return JSON.stringify({ config: {
5532
+ fileType,
5533
+ height,
5534
+ quality: metadata.quality,
5535
+ sizeType: metadata.sizeType ?? [],
5536
+ sourceType: metadata.sourceType ?? [],
5537
+ width
5538
+ } });
5539
+ }
5540
+ function buildVideoTempFilePayload(width, height, duration, metadata = {}) {
5541
+ return JSON.stringify({ config: {
5542
+ bitrate: metadata.bitrate ?? 1500,
5543
+ compressed: metadata.compressed ?? true,
5544
+ duration,
5545
+ fps: metadata.fps ?? 30,
5546
+ height,
5547
+ maxDuration: metadata.maxDuration ?? duration,
5548
+ sourceType: metadata.sourceType ?? [],
5549
+ width
5550
+ } });
5551
+ }
5552
+ function inferVideoInfo(filePath, fileContent) {
5553
+ const size = byteLength(fileContent);
5554
+ const type = filePath.match(IMAGE_EXTENSION_RE)?.[1]?.toLowerCase() ?? "mp4";
5555
+ try {
5556
+ const payload = JSON.parse(fileContent);
5557
+ return {
5558
+ bitrate: payload.config.bitrate ?? 1500,
5559
+ duration: payload.config.duration ?? 0,
5560
+ fps: payload.config.fps ?? 30,
5561
+ height: payload.config.height ?? 0,
5562
+ size,
5563
+ type,
5564
+ width: payload.config.width ?? 0
5565
+ };
5566
+ } catch {
5567
+ return {
5568
+ bitrate: 1500,
5569
+ duration: 0,
5570
+ fps: 30,
5571
+ height: 0,
5572
+ size,
5573
+ type,
5574
+ width: 0
5575
+ };
5576
+ }
5577
+ }
4401
5578
  function createNetworkSnapshot(networkType) {
4402
5579
  return {
4403
5580
  isConnected: networkType !== "none",
@@ -4548,6 +5725,9 @@ function createHeadlessWxState() {
4548
5725
  let fileId = 0;
4549
5726
  let loading = null;
4550
5727
  let networkType = "wifi";
5728
+ let previewImage = null;
5729
+ let openedDocument = null;
5730
+ let clipboardData = "";
4551
5731
  let shareMenu = {
4552
5732
  isUpdatableMessage: false,
4553
5733
  menus: [],
@@ -4749,6 +5929,16 @@ function createHeadlessWxState() {
4749
5929
  size: savedFile.size
4750
5930
  };
4751
5931
  };
5932
+ const getFileInfo = (option) => {
5933
+ const normalizedPath = normalizeFsPath(option.filePath);
5934
+ const fileContent = files.get(normalizedPath);
5935
+ if (fileContent == null) throw new Error(`getFileInfo:fail no such file or directory, stat '${normalizedPath}'`);
5936
+ return {
5937
+ digest: (option.digestAlgorithm === "sha1" ? "sha1" : "md5") === "sha1" ? computeSha1Digest(fileContent) : computeMd5Digest(fileContent),
5938
+ errMsg: "getFileInfo:ok",
5939
+ size: byteLength(fileContent)
5940
+ };
5941
+ };
4752
5942
  const removeSavedFile = (filePath) => {
4753
5943
  const normalizedPath = normalizeFsPath(filePath);
4754
5944
  if (!savedFiles.has(normalizedPath)) throw new Error(`removeSavedFile:fail no such file or directory, unlink '${normalizedPath}'`);
@@ -4999,18 +6189,191 @@ function createHeadlessWxState() {
4999
6189
  getSavedFileInfo(option) {
5000
6190
  return getSavedFileInfo(option.filePath);
5001
6191
  },
6192
+ getFileInfo(option) {
6193
+ return getFileInfo(option);
6194
+ },
5002
6195
  getSavedFileList() {
5003
6196
  return getSavedFileList();
5004
6197
  },
5005
6198
  getFileText(filePath) {
5006
6199
  return files.get(filePath) ?? null;
5007
6200
  },
6201
+ createTempFile(fileContent, preferredPath) {
6202
+ const tempFilePath = allocateFilePath("temp", preferredPath);
6203
+ ensureDirectoryTree(tempFilePath);
6204
+ files.set(tempFilePath, String(fileContent));
6205
+ return tempFilePath;
6206
+ },
6207
+ chooseImage(option) {
6208
+ const count = Number.isFinite(option.count) ? Math.max(1, Math.min(9, Math.trunc(option.count ?? 1))) : 9;
6209
+ const sizeType = Array.isArray(option.sizeType) && option.sizeType.length > 0 ? option.sizeType.filter((item) => typeof item === "string") : ["original"];
6210
+ const sourceType = Array.isArray(option.sourceType) && option.sourceType.length > 0 ? option.sourceType.filter((item) => typeof item === "string") : ["album", "camera"];
6211
+ const fileType = sizeType.includes("compressed") ? "jpg" : "png";
6212
+ const tempFiles = Array.from({ length: count }, (_, index) => {
6213
+ const payload = buildImageTempFilePayload(fileType, 160 + index * 12, 120 + index * 8, {
6214
+ sizeType: [...sizeType],
6215
+ sourceType: [...sourceType]
6216
+ });
6217
+ return {
6218
+ path: this.createTempFile(payload, `headless://wxfile/temp/chosen-image-${String(index + 1).padStart(2, "0")}.${fileType}`),
6219
+ size: payload.length
6220
+ };
6221
+ });
6222
+ return {
6223
+ errMsg: "chooseImage:ok",
6224
+ tempFilePaths: tempFiles.map((item) => item.path),
6225
+ tempFiles
6226
+ };
6227
+ },
6228
+ chooseMessageFile(option) {
6229
+ const count = Number.isFinite(option.count) ? Math.max(1, Math.min(100, Math.trunc(option.count ?? 1))) : 1;
6230
+ const extensions = resolveMessageFileExtensions(option);
6231
+ return {
6232
+ errMsg: "chooseMessageFile:ok",
6233
+ tempFiles: Array.from({ length: count }, (_, index) => {
6234
+ const extension = extensions[index % extensions.length];
6235
+ const sequence = String(index + 1).padStart(2, "0");
6236
+ const fileName = `message-file-${sequence}.${extension}`;
6237
+ let fileContent = `headless message file ${fileName}`;
6238
+ if (isImageFileExtension(extension)) fileContent = buildImageTempFilePayload(extension, 160 + index * 12, 120 + index * 8, { sourceType: ["message"] });
6239
+ else if (isVideoFileExtension(extension)) fileContent = buildVideoTempFilePayload(640, 360, 18 + index, {
6240
+ compressed: true,
6241
+ sourceType: ["message"]
6242
+ });
6243
+ return {
6244
+ name: fileName,
6245
+ path: this.createTempFile(fileContent, `headless://wxfile/temp/chosen-message-file-${sequence}.${extension}`),
6246
+ size: byteLength(fileContent),
6247
+ time: 171e10 + index * 1e3,
6248
+ type: extension
6249
+ };
6250
+ })
6251
+ };
6252
+ },
6253
+ chooseMedia(option) {
6254
+ const count = Number.isFinite(option.count) ? Math.max(1, Math.min(9, Math.trunc(option.count ?? 1))) : 1;
6255
+ const mediaTypes = Array.isArray(option.mediaType) && option.mediaType.length > 0 ? option.mediaType.filter((item) => item === "image" || item === "video") : ["image"];
6256
+ const sourceType = Array.isArray(option.sourceType) && option.sourceType.length > 0 ? option.sourceType.filter((item) => typeof item === "string") : ["album", "camera"];
6257
+ const sizeType = Array.isArray(option.sizeType) && option.sizeType.length > 0 ? option.sizeType.filter((item) => typeof item === "string") : ["compressed"];
6258
+ const maxDuration = Number.isFinite(option.maxDuration) ? Math.max(1, Math.trunc(option.maxDuration ?? 60)) : 60;
6259
+ return {
6260
+ errMsg: "chooseMedia:ok",
6261
+ tempFiles: Array.from({ length: count }, (_, index) => {
6262
+ if (mediaTypes[index % mediaTypes.length] === "video") {
6263
+ const duration = Math.min(maxDuration, 18 + index);
6264
+ const width = 640;
6265
+ const height = 360;
6266
+ const payload = buildVideoTempFilePayload(width, height, duration, {
6267
+ compressed: sizeType.includes("compressed"),
6268
+ maxDuration,
6269
+ sourceType: [...sourceType]
6270
+ });
6271
+ const tempFilePath = this.createTempFile(payload, `headless://wxfile/temp/chosen-media-video-${String(index + 1).padStart(2, "0")}.mp4`);
6272
+ const thumbPayload = buildImageTempFilePayload("jpg", 160, 90, {
6273
+ sizeType: ["compressed"],
6274
+ sourceType: [...sourceType]
6275
+ });
6276
+ const thumbTempFilePath = this.createTempFile(thumbPayload, `headless://wxfile/temp/chosen-media-video-thumb-${String(index + 1).padStart(2, "0")}.jpg`);
6277
+ return {
6278
+ duration,
6279
+ fileType: "video",
6280
+ height,
6281
+ size: payload.length,
6282
+ tempFilePath,
6283
+ thumbTempFilePath,
6284
+ width
6285
+ };
6286
+ }
6287
+ const width = 160 + index * 12;
6288
+ const height = 120 + index * 8;
6289
+ const fileType = sizeType.includes("compressed") ? "jpg" : "png";
6290
+ const payload = buildImageTempFilePayload(fileType, width, height, {
6291
+ sizeType: [...sizeType],
6292
+ sourceType: [...sourceType]
6293
+ });
6294
+ return {
6295
+ fileType: "image",
6296
+ height,
6297
+ size: payload.length,
6298
+ tempFilePath: this.createTempFile(payload, `headless://wxfile/temp/chosen-media-image-${String(index + 1).padStart(2, "0")}.${fileType}`),
6299
+ width
6300
+ };
6301
+ }),
6302
+ type: mediaTypes.length > 1 ? "mix" : mediaTypes[0]
6303
+ };
6304
+ },
6305
+ chooseVideo(option) {
6306
+ const compressed = option.compressed !== false;
6307
+ const maxDuration = Number.isFinite(option.maxDuration) ? Math.max(1, Math.trunc(option.maxDuration ?? 60)) : 60;
6308
+ const sourceType = Array.isArray(option.sourceType) && option.sourceType.length > 0 ? option.sourceType.filter((item) => typeof item === "string") : ["album", "camera"];
6309
+ const payload = JSON.stringify({ config: {
6310
+ compressed,
6311
+ duration: Math.min(maxDuration, compressed ? 18 : 36),
6312
+ height: compressed ? 360 : 720,
6313
+ maxDuration,
6314
+ sourceType: [...sourceType],
6315
+ width: compressed ? 640 : 1280
6316
+ } });
6317
+ const tempFilePath = this.createTempFile(payload, "headless://wxfile/temp/chosen-video-01.mp4");
6318
+ return {
6319
+ duration: Math.min(maxDuration, compressed ? 18 : 36),
6320
+ errMsg: "chooseVideo:ok",
6321
+ height: compressed ? 360 : 720,
6322
+ size: payload.length,
6323
+ tempFilePath,
6324
+ width: compressed ? 640 : 1280
6325
+ };
6326
+ },
6327
+ compressImage(option) {
6328
+ const fileContent = files.get(option.src);
6329
+ if (fileContent == null) throw new Error(`compressImage:fail file not found: ${option.src}`);
6330
+ const sourceSize = inferImageSize(fileContent);
6331
+ const sourceType = inferImageType(option.src, fileContent);
6332
+ const width = Number.isFinite(option.compressedWidth) ? Math.max(1, Math.trunc(option.compressedWidth ?? sourceSize.width)) : Math.max(1, sourceSize.width);
6333
+ const height = Number.isFinite(option.compressedHeight) ? Math.max(1, Math.trunc(option.compressedHeight ?? sourceSize.height)) : Math.max(1, sourceSize.height);
6334
+ const normalizedType = sourceType === "unknown" ? "jpg" : sourceType;
6335
+ const fileExtension = normalizedType === "jpeg" ? "jpg" : normalizedType;
6336
+ const payload = buildImageTempFilePayload(normalizedType, width, height, { quality: Number.isFinite(option.quality) ? Number(option.quality) : void 0 });
6337
+ return {
6338
+ errMsg: "compressImage:ok",
6339
+ tempFilePath: this.createTempFile(payload, `headless://wxfile/temp/compressed-image-${String(fileId + 1).padStart(2, "0")}.${fileExtension}`)
6340
+ };
6341
+ },
5008
6342
  getNetworkType() {
5009
6343
  return {
5010
6344
  errMsg: "getNetworkType:ok",
5011
6345
  networkType
5012
6346
  };
5013
6347
  },
6348
+ getImageInfo(option) {
6349
+ const fileContent = files.get(option.src);
6350
+ if (fileContent == null) throw new Error(`getImageInfo:fail file not found: ${option.src}`);
6351
+ const size = inferImageSize(fileContent);
6352
+ return {
6353
+ errMsg: "getImageInfo:ok",
6354
+ height: size.height,
6355
+ orientation: "up",
6356
+ path: option.src,
6357
+ type: inferImageType(option.src, fileContent),
6358
+ width: size.width
6359
+ };
6360
+ },
6361
+ getVideoInfo(option) {
6362
+ const fileContent = files.get(option.src);
6363
+ if (fileContent == null) throw new Error(`getVideoInfo:fail file not found: ${option.src}`);
6364
+ const info = inferVideoInfo(option.src, fileContent);
6365
+ return {
6366
+ bitrate: info.bitrate,
6367
+ duration: info.duration,
6368
+ errMsg: "getVideoInfo:ok",
6369
+ fps: info.fps,
6370
+ height: info.height,
6371
+ orientation: "up",
6372
+ size: info.size,
6373
+ type: info.type,
6374
+ width: info.width
6375
+ };
6376
+ },
5014
6377
  getModalLogs() {
5015
6378
  return modalLogs.map((entry) => ({
5016
6379
  ...entry,
@@ -5045,6 +6408,15 @@ function createHeadlessWxState() {
5045
6408
  getStorageSnapshot() {
5046
6409
  return Object.fromEntries(Array.from(storage.entries(), ([key, value]) => [key, cloneValue(value)]));
5047
6410
  },
6411
+ getClipboardData() {
6412
+ return {
6413
+ data: clipboardData,
6414
+ errMsg: "getClipboardData:ok"
6415
+ };
6416
+ },
6417
+ getClipboardSnapshot() {
6418
+ return { data: clipboardData };
6419
+ },
5048
6420
  getStorageSync(key) {
5049
6421
  return cloneValue(storage.get(key));
5050
6422
  },
@@ -5054,6 +6426,21 @@ function createHeadlessWxState() {
5054
6426
  getLoading() {
5055
6427
  return loading ? { ...loading } : null;
5056
6428
  },
6429
+ getPreviewImage() {
6430
+ return previewImage ? {
6431
+ current: previewImage.current,
6432
+ urls: [...previewImage.urls],
6433
+ visible: previewImage.visible
6434
+ } : null;
6435
+ },
6436
+ getOpenedDocument() {
6437
+ return openedDocument ? {
6438
+ filePath: openedDocument.filePath,
6439
+ fileType: openedDocument.fileType,
6440
+ showMenu: openedDocument.showMenu,
6441
+ visible: openedDocument.visible
6442
+ } : null;
6443
+ },
5057
6444
  hideShareMenu() {
5058
6445
  shareMenu = {
5059
6446
  ...shareMenu,
@@ -5123,6 +6510,27 @@ function createHeadlessWxState() {
5123
6510
  onNetworkStatusChange(callback) {
5124
6511
  networkStatusChangeCallbacks.add(callback);
5125
6512
  },
6513
+ openDocument(option) {
6514
+ const normalizedPath = normalizeFsPath(option.filePath);
6515
+ if (!files.has(normalizedPath)) throw new Error(`openDocument:fail no such file or directory, open '${normalizedPath}'`);
6516
+ openedDocument = {
6517
+ filePath: normalizedPath,
6518
+ fileType: typeof option.fileType === "string" && option.fileType.trim() !== "" ? normalizeFileExtension(option.fileType) : inferDocumentFileType(normalizedPath),
6519
+ showMenu: option.showMenu !== false,
6520
+ visible: true
6521
+ };
6522
+ return { errMsg: "openDocument:ok" };
6523
+ },
6524
+ previewImage(option) {
6525
+ const urls = Array.isArray(option.urls) ? option.urls.filter((item) => typeof item === "string" && item.trim() !== "") : [];
6526
+ if (urls.length === 0) throw new Error("previewImage:fail invalid urls");
6527
+ previewImage = {
6528
+ current: typeof option.current === "string" && option.current.trim() !== "" ? option.current : urls[0],
6529
+ urls: [...urls],
6530
+ visible: true
6531
+ };
6532
+ return { errMsg: "previewImage:ok" };
6533
+ },
5126
6534
  removeStorageSync(key) {
5127
6535
  storage.delete(key);
5128
6536
  },
@@ -5170,6 +6578,14 @@ function createHeadlessWxState() {
5170
6578
  removeSavedFile(option) {
5171
6579
  return removeSavedFile(option.filePath);
5172
6580
  },
6581
+ saveImageToPhotosAlbum(option) {
6582
+ if (files.get(option.filePath) == null) throw new Error(`saveImageToPhotosAlbum:fail file not found: ${option.filePath}`);
6583
+ return { errMsg: "saveImageToPhotosAlbum:ok" };
6584
+ },
6585
+ saveVideoToPhotosAlbum(option) {
6586
+ if (files.get(option.filePath) == null) throw new Error(`saveVideoToPhotosAlbum:fail file not found: ${option.filePath}`);
6587
+ return { errMsg: "saveVideoToPhotosAlbum:ok" };
6588
+ },
5173
6589
  saveFile(option) {
5174
6590
  const fileContent = files.get(option.tempFilePath);
5175
6591
  if (fileContent == null) throw new Error(`saveFile:fail tempFilePath not found: ${option.tempFilePath}`);
@@ -5185,6 +6601,10 @@ function createHeadlessWxState() {
5185
6601
  setStorageSync(key, value) {
5186
6602
  storage.set(key, cloneValue(value));
5187
6603
  },
6604
+ setClipboardData(option) {
6605
+ clipboardData = String(option.data);
6606
+ return { errMsg: "setClipboardData:ok" };
6607
+ },
5188
6608
  setFile(filePath, fileContent) {
5189
6609
  const normalizedPath = normalizeFsPath(filePath);
5190
6610
  ensureDirectoryTree(normalizedPath);
@@ -5330,7 +6750,9 @@ function createHeadlessWxState() {
5330
6750
  //#region ../../mpcore/packages/simulator/src/runtime/session.ts
5331
6751
  const LEADING_SLASH_RE$1 = /^\/+/;
5332
6752
  const PAGE_STACK_LIMIT = 10;
6753
+ const WHITESPACE_RE = /\s+/;
5333
6754
  const DATA_ATTR_SELECTOR_RE = /^\[data-([^=\]]+)="([^"]*)"\]$/;
6755
+ const COMPOUND_SELECTOR_PART_RE = /#[\w-]+|\.[\w-]+|\[data-[^=\]]+="[^"]*"\]|[A-Za-z][\w-]*/g;
5334
6756
  const DATASET_KEY_RE = /-([a-z])/g;
5335
6757
  function stripLeadingSlash(route) {
5336
6758
  return route.replace(LEADING_SLASH_RE$1, "");
@@ -5351,6 +6773,28 @@ function parseNavigationUrl(url) {
5351
6773
  query: normalizeQuery(queryString)
5352
6774
  };
5353
6775
  }
6776
+ function parseCompoundSelector(selector) {
6777
+ const parts = selector.match(COMPOUND_SELECTOR_PART_RE) ?? [];
6778
+ return parts.join("") === selector ? parts : [];
6779
+ }
6780
+ function matchesComponentSelector(scope, selector) {
6781
+ const parts = parseCompoundSelector(selector);
6782
+ if (parts.length === 0) return false;
6783
+ return parts.every((part) => {
6784
+ if (part.startsWith("#")) return scope.id === part.slice(1);
6785
+ if (part.startsWith(".")) return scope.classList?.includes(part.slice(1)) ?? false;
6786
+ const dataAttrMatch = part.match(DATA_ATTR_SELECTOR_RE);
6787
+ if (dataAttrMatch) {
6788
+ const [, key, value] = dataAttrMatch;
6789
+ const datasetKey = key.replace(DATASET_KEY_RE, (_match, char) => char.toUpperCase());
6790
+ return scope.dataset?.[datasetKey] === value;
6791
+ }
6792
+ return scope.alias === part;
6793
+ });
6794
+ }
6795
+ function normalizeSelectorParts(selector) {
6796
+ return selector.trim().split(WHITESPACE_RE).filter(Boolean);
6797
+ }
5354
6798
  function createAppLaunchOptions(pathname, query) {
5355
6799
  return {
5356
6800
  path: stripLeadingSlash(pathname),
@@ -5392,6 +6836,8 @@ var HeadlessSession = class {
5392
6836
  tabBarState = /* @__PURE__ */ new Map();
5393
6837
  tabBarVisible = false;
5394
6838
  systemInfo = createDefaultSystemInfo();
6839
+ mediaQueryObservers = /* @__PURE__ */ new Set();
6840
+ canvasContexts = /* @__PURE__ */ new Map();
5395
6841
  enterOptions = createAppLaunchOptions("", {});
5396
6842
  launchOptions = createAppLaunchOptions("", {});
5397
6843
  wxState = createHeadlessWxState();
@@ -5424,7 +6870,20 @@ var HeadlessSession = class {
5424
6870
  });
5425
6871
  this.tabBarVisible = this.tabBarRoutes.size > 0;
5426
6872
  this.moduleLoader = createModuleLoader(this.registries, () => this.pages.slice(), () => this.getApp(), {
6873
+ chooseImage: (option) => this.wxState.chooseImage(option ?? {}),
6874
+ chooseMessageFile: (option) => this.wxState.chooseMessageFile(option ?? {}),
6875
+ chooseMedia: (option) => this.wxState.chooseMedia(option ?? {}),
6876
+ chooseVideo: (option) => this.wxState.chooseVideo(option ?? {}),
6877
+ compressImage: (option) => this.wxState.compressImage(option),
6878
+ createAnimation: (option) => this.createAnimation(option),
6879
+ createCanvasContext: (canvasId, scope) => this.createCanvasContext(canvasId, scope),
6880
+ canvasToTempFilePath: (option) => this.canvasToTempFilePath(option),
6881
+ createIntersectionObserver: (scope, options) => this.createIntersectionObserver(scope, options),
6882
+ createVideoContext: (videoId, scope) => this.createVideoContext(videoId, scope),
5427
6883
  executeSelectorQuery: (requests, scope) => this.executeSelectorQuery(requests, scope),
6884
+ getFileInfo: (option) => this.wxState.getFileInfo(option),
6885
+ getImageInfo: (option) => this.wxState.getImageInfo(option),
6886
+ getVideoInfo: (option) => this.wxState.getVideoInfo(option),
5428
6887
  getFileSystemManager: () => this.wxState.getFileSystemManager(),
5429
6888
  getSavedFileInfo: (option) => this.wxState.getSavedFileInfo(option),
5430
6889
  getSavedFileList: () => this.wxState.getSavedFileList(),
@@ -5445,11 +6904,13 @@ var HeadlessSession = class {
5445
6904
  extraData: { ...this.launchOptions.referrerInfo.extraData }
5446
6905
  }
5447
6906
  }),
6907
+ getClipboardData: () => this.wxState.getClipboardData(),
5448
6908
  getMenuButtonBoundingClientRect: () => deriveMenuButtonBoundingClientRect(this.systemInfo),
5449
6909
  getNetworkType: () => this.wxState.getNetworkType(),
5450
6910
  navigateBack: (option) => this.navigateBack(option?.delta),
5451
6911
  navigateTo: (option) => this.navigateTo(option.url),
5452
6912
  pageScrollTo: (option) => this.pageScrollTo(option),
6913
+ openDocument: (option) => this.wxState.openDocument(option),
5453
6914
  clearStorageSync: () => this.wxState.clearStorageSync(),
5454
6915
  getStorageInfoSync: () => this.wxState.getStorageInfoSync(),
5455
6916
  getStorageSync: (key) => this.wxState.getStorageSync(key),
@@ -5461,14 +6922,18 @@ var HeadlessSession = class {
5461
6922
  reLaunch: (option) => this.reLaunch(option.url),
5462
6923
  redirectTo: (option) => this.redirectTo(option.url),
5463
6924
  removeSavedFile: (option) => this.wxState.removeSavedFile(option),
6925
+ saveImageToPhotosAlbum: (option) => this.wxState.saveImageToPhotosAlbum(option),
6926
+ saveVideoToPhotosAlbum: (option) => this.wxState.saveVideoToPhotosAlbum(option),
5464
6927
  nextTick: (callback) => queueMicrotask(() => callback?.()),
5465
6928
  offNetworkStatusChange: (callback) => this.wxState.offNetworkStatusChange(callback),
5466
6929
  onNetworkStatusChange: (callback) => this.wxState.onNetworkStatusChange(callback),
5467
6930
  removeStorageSync: (key) => this.wxState.removeStorageSync(key),
6931
+ previewImage: (option) => this.wxState.previewImage(option),
5468
6932
  request: (option) => this.wxState.request(option),
5469
6933
  saveFile: (option) => this.wxState.saveFile(option),
5470
6934
  setBackgroundColor: (option) => this.setBackgroundColor(option),
5471
6935
  setBackgroundTextStyle: (option) => this.setBackgroundTextStyle(option.textStyle),
6936
+ setClipboardData: (option) => this.wxState.setClipboardData(option),
5472
6937
  setStorageSync: (key, value) => this.wxState.setStorageSync(key, value),
5473
6938
  setNavigationBarColor: (option) => this.setNavigationBarColor(option),
5474
6939
  setNavigationBarTitle: (option) => this.setNavigationBarTitle(option.title),
@@ -5484,6 +6949,7 @@ var HeadlessSession = class {
5484
6949
  showLoading: (option) => this.wxState.showLoading(option),
5485
6950
  showModal: (option) => this.wxState.showModal(option),
5486
6951
  showToast: (option) => this.wxState.showToast(option),
6952
+ startPullDownRefresh: () => this.startPullDownRefresh(),
5487
6953
  stopPullDownRefresh: () => this.stopPullDownRefresh(),
5488
6954
  switchTab: (option) => this.switchTab(option.url),
5489
6955
  uploadFile: (option) => this.wxState.uploadFile(option),
@@ -5542,6 +7008,8 @@ var HeadlessSession = class {
5542
7008
  moduleLoader: this.moduleLoader,
5543
7009
  project: this.project,
5544
7010
  session: {
7011
+ createIntersectionObserver: (scope, options) => this.createIntersectionObserver(scope, options),
7012
+ createMediaQueryObserver: (scope) => this.createMediaQueryObserver(scope),
5545
7013
  selectAllComponentsWithin: (scopeId, selector) => this.selectAllComponentsWithin(scopeId, selector),
5546
7014
  selectComponentWithin: (scopeId, selector) => this.selectComponentWithin(scopeId, selector),
5547
7015
  selectOwnerComponent: (scopeId) => this.selectOwnerComponent(scopeId)
@@ -5609,6 +7077,7 @@ var HeadlessSession = class {
5609
7077
  }
5610
7078
  const instance = this.componentCache.get(normalizedScopeId);
5611
7079
  if (!instance) throw new Error(`Unknown scope "${normalizedScopeId}" in headless runtime.`);
7080
+ instance.__lastInteractionEvent__ = void 0;
5612
7081
  const method = instance[methodName];
5613
7082
  if (typeof method !== "function") throw new TypeError(`Method "${methodName}" does not exist on headless component scope "${normalizedScopeId}".`);
5614
7083
  return method.apply(instance, args);
@@ -5658,6 +7127,15 @@ var HeadlessSession = class {
5658
7127
  getStorageInfo() {
5659
7128
  return this.wxState.getStorageInfoSync();
5660
7129
  }
7130
+ getClipboardData() {
7131
+ return this.wxState.getClipboardSnapshot();
7132
+ }
7133
+ getPreviewImage() {
7134
+ return this.wxState.getPreviewImage();
7135
+ }
7136
+ getOpenedDocument() {
7137
+ return this.wxState.getOpenedDocument();
7138
+ }
5661
7139
  getPullDownRefreshState() {
5662
7140
  return { ...this.pullDownRefreshState };
5663
7141
  }
@@ -5931,7 +7409,8 @@ var HeadlessSession = class {
5931
7409
  }
5932
7410
  pageScrollTo(option) {
5933
7411
  const current = this.requireCurrentPage("wx.pageScrollTo()");
5934
- current.__scrollTop__ = Number(option.scrollTop ?? 0);
7412
+ const selectorScrollTop = resolveSelectorScrollTop(this.renderCurrentPage().root, option.selector);
7413
+ current.__scrollTop__ = Number(selectorScrollTop ?? option.scrollTop ?? 0);
5935
7414
  current.onPageScroll?.({ scrollTop: current.__scrollTop__ });
5936
7415
  }
5937
7416
  triggerPullDownRefresh() {
@@ -5943,6 +7422,10 @@ var HeadlessSession = class {
5943
7422
  current.onPullDownRefresh?.();
5944
7423
  return current;
5945
7424
  }
7425
+ startPullDownRefresh() {
7426
+ this.triggerPullDownRefresh();
7427
+ return { errMsg: "startPullDownRefresh:ok" };
7428
+ }
5946
7429
  triggerReachBottom() {
5947
7430
  const current = this.requireCurrentPage("triggerReachBottom()");
5948
7431
  current.onReachBottom?.();
@@ -5953,6 +7436,7 @@ var HeadlessSession = class {
5953
7436
  applyResizeToSystemInfo(this.systemInfo, options);
5954
7437
  current.onResize?.(options);
5955
7438
  this.runPageComponentLifetime(current.route, "resize", options);
7439
+ this.notifyMediaQueryObservers();
5956
7440
  return current;
5957
7441
  }
5958
7442
  triggerRouteDone(options = {}) {
@@ -5973,6 +7457,11 @@ var HeadlessSession = class {
5973
7457
  const scopeId = scope && scope !== current ? this.getComponentScopeId(scope) : null;
5974
7458
  return executeSelectorQueryRequests(requests, {
5975
7459
  page: current,
7460
+ resolveContext: (node) => {
7461
+ if (node.name !== "canvas") return { type: "unsupported-context" };
7462
+ const canvasId = node.attribs?.["canvas-id"];
7463
+ return typeof canvasId === "string" && canvasId ? this.createCanvasContext(canvasId, scope) : { type: "unsupported-context" };
7464
+ },
5976
7465
  root: resolveSelectorQueryScopeRoot(rendered.root, scopeId),
5977
7466
  windowInfo: this.getWindowInfo()
5978
7467
  });
@@ -5986,6 +7475,8 @@ var HeadlessSession = class {
5986
7475
  background: resolveBackgroundSnapshot(this.project.appConfig, pageConfig),
5987
7476
  navigationBar: resolveNavigationBarSnapshot(this.project.appConfig, pageConfig)
5988
7477
  });
7478
+ pageInstance.createIntersectionObserver = (options) => this.createIntersectionObserver(pageInstance, options);
7479
+ pageInstance.createMediaQueryObserver = () => this.createMediaQueryObserver(pageInstance);
5989
7480
  pageInstance.selectComponent = (selector) => this.selectComponent(selector);
5990
7481
  pageInstance.selectAllComponents = (selector) => this.selectAllComponents(selector);
5991
7482
  pageInstance.onLoad?.(target.query);
@@ -6035,10 +7526,12 @@ var HeadlessSession = class {
6035
7526
  this.tabPages.clear();
6036
7527
  this.componentCache.clear();
6037
7528
  this.componentScopes.clear();
7529
+ this.clearMediaQueryObservers();
6038
7530
  this.currentPageInstance = null;
6039
7531
  }
6040
7532
  unloadPage(page) {
6041
7533
  page.onUnload?.();
7534
+ this.clearMediaQueryObservers(page);
6042
7535
  this.detachPageComponents(page.route);
6043
7536
  this.tabPages.delete(stripLeadingSlash(page.route));
6044
7537
  }
@@ -6049,21 +7542,21 @@ var HeadlessSession = class {
6049
7542
  return current;
6050
7543
  }
6051
7544
  selectComponentsWithin(rootScopeId, selector) {
6052
- const normalizedSelector = selector.trim();
6053
- if (!normalizedSelector) return [];
7545
+ const selectorParts = normalizeSelectorParts(selector);
7546
+ if (selectorParts.length === 0) return [];
6054
7547
  return [...this.componentScopes.entries()].filter(([candidateScopeId, scope]) => {
6055
7548
  if (!candidateScopeId.includes("/")) return false;
6056
7549
  if (rootScopeId && candidateScopeId === rootScopeId) return false;
6057
7550
  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;
7551
+ const lastPart = selectorParts.at(-1);
7552
+ if (!lastPart || !matchesComponentSelector(scope, lastPart)) return false;
7553
+ let currentScope = scope.ownerScopeId ? this.componentScopes.get(scope.ownerScopeId) : void 0;
7554
+ for (let partIndex = selectorParts.length - 2; partIndex >= 0; partIndex -= 1) {
7555
+ while (currentScope && !matchesComponentSelector(currentScope, selectorParts[partIndex])) currentScope = currentScope.ownerScopeId ? this.componentScopes.get(currentScope.ownerScopeId) : void 0;
7556
+ if (!currentScope) return false;
7557
+ currentScope = currentScope.ownerScopeId ? this.componentScopes.get(currentScope.ownerScopeId) : void 0;
6065
7558
  }
6066
- return scope.alias === normalizedSelector;
7559
+ return true;
6067
7560
  }).map(([candidateScopeId]) => this.componentCache.get(candidateScopeId)).filter(Boolean);
6068
7561
  }
6069
7562
  detachPageComponents(route) {
@@ -6075,6 +7568,18 @@ var HeadlessSession = class {
6075
7568
  this.componentScopes.delete(scopeId);
6076
7569
  }
6077
7570
  }
7571
+ notifyMediaQueryObservers() {
7572
+ for (const entry of [...this.mediaQueryObservers]) {
7573
+ if (entry.ownerPage !== this.currentPageInstance) continue;
7574
+ entry.notify();
7575
+ }
7576
+ }
7577
+ clearMediaQueryObservers(ownerPage) {
7578
+ for (const entry of [...this.mediaQueryObservers]) {
7579
+ if (ownerPage && entry.ownerPage !== ownerPage) continue;
7580
+ entry.disconnect();
7581
+ }
7582
+ }
6078
7583
  runPageComponentLifetime(route, lifetimeName, payload) {
6079
7584
  const prefix = `page:${stripLeadingSlash(route)}`;
6080
7585
  for (const [scopeId, instance] of this.componentCache.entries()) {
@@ -6086,6 +7591,100 @@ var HeadlessSession = class {
6086
7591
  for (const [scopeId, instance] of this.componentCache.entries()) if (instance === component) return scopeId;
6087
7592
  return null;
6088
7593
  }
7594
+ createVideoContext(videoId, scope) {
7595
+ return createHeadlessVideoContext({
7596
+ callScopeMethod: (scopeId, methodName, event) => this.callScopeMethod(scopeId, methodName, event),
7597
+ renderCurrentPage: () => this.renderCurrentPage(),
7598
+ resolveScope: (value) => {
7599
+ const current = this.currentPageInstance;
7600
+ if (!value || value === current) return { kind: "page" };
7601
+ const scopeId = this.getScopeIdForComponent(value);
7602
+ if (!scopeId) return { kind: "missing" };
7603
+ return {
7604
+ kind: "component",
7605
+ scopeId
7606
+ };
7607
+ }
7608
+ }, videoId, scope);
7609
+ }
7610
+ createAnimation(option) {
7611
+ return createHeadlessAnimation(option);
7612
+ }
7613
+ resolveCanvasScopeKey(scope) {
7614
+ const current = this.currentPageInstance;
7615
+ if (!scope || scope === current) return "page";
7616
+ const scopeId = this.getScopeIdForComponent(scope);
7617
+ return scopeId ? `component:${scopeId}` : "missing";
7618
+ }
7619
+ createCanvasContext(canvasId, scope) {
7620
+ const context = createHeadlessCanvasContext({
7621
+ renderCurrentPage: () => this.renderCurrentPage(),
7622
+ resolveScope: (value) => {
7623
+ const current = this.currentPageInstance;
7624
+ if (!value || value === current) return { kind: "page" };
7625
+ const scopeId = this.getScopeIdForComponent(value);
7626
+ if (!scopeId) return { kind: "missing" };
7627
+ return {
7628
+ kind: "component",
7629
+ scopeId
7630
+ };
7631
+ }
7632
+ }, canvasId, scope);
7633
+ this.canvasContexts.set(`${this.resolveCanvasScopeKey(scope)}:${canvasId}`, context);
7634
+ return context;
7635
+ }
7636
+ canvasToTempFilePath(option) {
7637
+ const context = this.canvasContexts.get(`${this.resolveCanvasScopeKey(option.component)}:${option.canvasId}`);
7638
+ if (!context) throw new Error(`canvasToTempFilePath:fail canvas "${option.canvasId}" has not been drawn in headless runtime.`);
7639
+ return {
7640
+ errMsg: "canvasToTempFilePath:ok",
7641
+ tempFilePath: this.wxState.createTempFile(JSON.stringify({
7642
+ canvasId: option.canvasId,
7643
+ config: {
7644
+ destHeight: option.destHeight,
7645
+ destWidth: option.destWidth,
7646
+ fileType: option.fileType,
7647
+ height: option.height,
7648
+ quality: option.quality,
7649
+ width: option.width,
7650
+ x: option.x,
7651
+ y: option.y
7652
+ },
7653
+ snapshot: context.__getSnapshot()
7654
+ }))
7655
+ };
7656
+ }
7657
+ createIntersectionObserver(scope, options) {
7658
+ return createHeadlessIntersectionObserver({
7659
+ getWindowInfo: () => this.getWindowInfo(),
7660
+ renderCurrentPage: () => this.renderCurrentPage(),
7661
+ resolveScope: (value) => {
7662
+ const current = this.currentPageInstance;
7663
+ if (!value || value === current) return { kind: "page" };
7664
+ const scopeId = this.getScopeIdForComponent(value);
7665
+ if (!scopeId) return { kind: "missing" };
7666
+ return {
7667
+ kind: "component",
7668
+ scopeId
7669
+ };
7670
+ }
7671
+ }, scope, options);
7672
+ }
7673
+ createMediaQueryObserver(scope) {
7674
+ const current = this.requireCurrentPage("createMediaQueryObserver()");
7675
+ const ownerPage = scope === current || !scope ? current : this.currentPageInstance ?? current;
7676
+ let entry;
7677
+ const controller = createHeadlessMediaQueryObserver({ getWindowInfo: () => this.getWindowInfo() }, () => {
7678
+ this.mediaQueryObservers.delete(entry);
7679
+ });
7680
+ entry = {
7681
+ disconnect: controller.disconnect,
7682
+ notify: controller.notify,
7683
+ ownerPage
7684
+ };
7685
+ this.mediaQueryObservers.add(entry);
7686
+ return controller.observer;
7687
+ }
6089
7688
  };
6090
7689
  function createHeadlessSession(options) {
6091
7690
  return new HeadlessSession(options);