codeplay-common 4.8.3 → 4.8.5

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.
@@ -174,7 +174,7 @@ const deviceProfiles = [
174
174
  },
175
175
  {
176
176
  name: 'iphone-duo',
177
- label: 'iPhone Duo inner-display App Store viewport (generic frame)',
177
+ label: 'iPhone Duo inner-display App Store viewport',
178
178
  outputDirectory: 'iPhone-Duo',
179
179
  frameType: 'iphone',
180
180
  showDynamicIsland: false,
@@ -927,21 +927,21 @@ async function getWebViewTarget(localPort) {
927
927
  && target.url !== 'about:blank'
928
928
  ));
929
929
 
930
- const visibleTarget = pageTargets.find((target) => {
930
+ for (const target of pageTargets) {
931
+ const client = new CdpClient(target.webSocketDebuggerUrl);
931
932
  try {
932
- return JSON.parse(target.description || '{}').visible !== false;
933
+ await client.connect();
934
+ const details = await getPageDetails(client);
935
+ if (details.width > 0 && details.height > 0 && details.hasAppPage) {
936
+ return target;
937
+ }
933
938
  } catch {
934
- return true;
939
+ // A target may close while discovering the active app WebView.
940
+ } finally {
941
+ client.close();
935
942
  }
936
- });
937
-
938
- const target = visibleTarget || pageTargets[0];
939
-
940
- if (!target) {
941
- throw new Error('No rendered page was found in the app WebView.');
942
943
  }
943
-
944
- return target;
944
+ throw new Error('No visible Framework7 app page was found. Open the app page and retry; ad WebViews cannot be captured.');
945
945
  }
946
946
 
947
947
  async function getPageDetails(client) {
@@ -952,6 +952,14 @@ async function getPageDetails(client) {
952
952
  width: innerWidth,
953
953
  height: innerHeight,
954
954
  devicePixelRatio: devicePixelRatio,
955
+ hasAppPage: [...document.querySelectorAll('.view .page')].some((page) => {
956
+ const bounds = page.getBoundingClientRect();
957
+ const style = getComputedStyle(page);
958
+ return style.display !== 'none' && style.visibility !== 'hidden'
959
+ && bounds.width > 0 && bounds.height > 0
960
+ && bounds.bottom > 0 && bounds.right > 0
961
+ && bounds.top < innerHeight && bounds.left < innerWidth;
962
+ }),
955
963
  readyState: document.readyState
956
964
  })`,
957
965
  returnByValue: true,
@@ -1041,7 +1049,7 @@ async function capturePageSnapshot(client) {
1041
1049
  ? element.closest('label, .item-content, .input')
1042
1050
  : element;
1043
1051
  const preferredLabel = labelRoot?.querySelector(
1044
- '.item-title, .button-label, strong, .dialog-button-text',
1052
+ '.item-title, .button-label, strong, .dialog-button-text, .homeboxtitle',
1045
1053
  );
1046
1054
  const textRoot = preferredLabel || labelRoot || element;
1047
1055
  const clone = textRoot.cloneNode(true);
@@ -1065,7 +1073,7 @@ async function capturePageSnapshot(client) {
1065
1073
  if (!root) return [];
1066
1074
  const records = [];
1067
1075
  const seen = new Set();
1068
- const selector = 'button, a.button, [role="button"], .list-button, .dialog-button';
1076
+ const selector = 'button, .button, [role="button"], .list-button, .dialog-button';
1069
1077
  for (const element of root.querySelectorAll(selector)) {
1070
1078
  if (!isVisible(element)) continue;
1071
1079
  const label = getElementLabel(element);
@@ -1137,6 +1145,9 @@ async function capturePageSnapshot(client) {
1137
1145
  const activePage = visiblePages.find((page) => page.classList.contains('page-current'))
1138
1146
  || visiblePages[visiblePages.length - 1]
1139
1147
  || null;
1148
+ if (!activePage) {
1149
+ throw new Error('No visible app page was found; screenshot information was not captured.');
1150
+ }
1140
1151
  const overlaySelector = [
1141
1152
  '.dialog.modal-in',
1142
1153
  '.popup.modal-in',
@@ -1161,12 +1172,12 @@ async function capturePageSnapshot(client) {
1161
1172
  ].join(',');
1162
1173
  const pageHeadings = collectUiText(
1163
1174
  activePage,
1164
- 'h1, h2, h3, .navbar .title, .t-section-heading, .headTitle',
1175
+ 'h1, h2, h3, .navbar .title, .t-section-heading, .headTitle, .home-hero-title, .home-section-title',
1165
1176
  10,
1166
1177
  );
1167
1178
  const pageControls = collectUiText(
1168
1179
  activePage,
1169
- 'button, [role="button"], label, .item-title, .item-subtitle, .list-button, option:checked',
1180
+ 'button, [role="button"], label, .item-title, .item-subtitle, .list-button, option:checked, .homeboxtitle',
1170
1181
  30,
1171
1182
  );
1172
1183
  const overlayText = collectUiText(activeOverlay, textSelector, 35);
@@ -1184,6 +1195,8 @@ async function capturePageSnapshot(client) {
1184
1195
  params: Object.keys(f7Route.params || {}),
1185
1196
  query: Object.keys(f7Route.query || {}),
1186
1197
  } : null;
1198
+ const themeAttribute = document.documentElement.getAttribute('data-theme')
1199
+ || document.body.getAttribute('data-theme');
1187
1200
  const screenInfo = {
1188
1201
  documentTitle: normalizeUiText(document.title),
1189
1202
  route: {
@@ -1195,16 +1208,16 @@ async function capturePageSnapshot(client) {
1195
1208
  dataName: activePage.getAttribute('data-name') || '',
1196
1209
  id: activePage.id || '',
1197
1210
  classes: getStableClasses(activePage),
1198
- navbarTitle: collectUiText(activePage, '.navbar .title', 1)[0] || '',
1211
+ navbarTitle: collectUiText(activePage, '.navbar .title, .navbar .home-brand-text', 1)[0] || '',
1199
1212
  headings: pageHeadings,
1200
1213
  sections: collectUiText(
1201
1214
  activePage,
1202
- 'h1, h2, h3, legend, .t-section-heading, .t-output-choice-heading, .headTitle',
1215
+ 'h1, h2, h3, legend, .t-section-heading, .t-output-choice-heading, .headTitle, .home-hero-title, .home-section-title',
1203
1216
  20,
1204
1217
  ),
1205
1218
  descriptions: collectUiText(
1206
1219
  activePage,
1207
- 'p, small, .item-subtitle, .item-after, .t-settings-footnote',
1220
+ 'p, small, .item-subtitle, .item-after, .t-settings-footnote, .home-hero-kicker, .home-hero-subtitle, .home-stat, .homebox-meta',
1208
1221
  30,
1209
1222
  ),
1210
1223
  controls: pageControls,
@@ -1232,10 +1245,12 @@ async function capturePageSnapshot(client) {
1232
1245
  )),
1233
1246
  inputs: overlayInputs,
1234
1247
  } : null,
1235
- theme: document.documentElement.classList.contains('theme-dark')
1236
- || document.body.classList.contains('theme-dark')
1237
- ? 'dark'
1238
- : 'light',
1248
+ theme: themeAttribute === 'dark' || themeAttribute === 'light'
1249
+ ? themeAttribute
1250
+ : document.documentElement.classList.contains('theme-dark')
1251
+ || document.body.classList.contains('theme-dark')
1252
+ ? 'dark'
1253
+ : 'light',
1239
1254
  viewport: {
1240
1255
  width: innerWidth,
1241
1256
  height: innerHeight,
@@ -2250,6 +2265,40 @@ function createDeviceFrameHtml(
2250
2265
  border-radius: 999px;
2251
2266
  background: #000;
2252
2267
  }
2268
+ ${profile.name === 'iphone-duo' ? `
2269
+ .device.iphone-duo {
2270
+ --duo-radius: clamp(24px, 4vw, 100px);
2271
+ --duo-bezel: clamp(5px, 0.65vw, 14px);
2272
+ --duo-edge: clamp(2px, 0.2vw, 5px);
2273
+ container-type: inline-size;
2274
+ padding: var(--duo-bezel);
2275
+ border-width: var(--duo-edge);
2276
+ border-radius: 8px var(--duo-radius) var(--duo-radius) 8px;
2277
+ }
2278
+ .device.iphone-duo .screen {
2279
+ border-radius: 2px calc(var(--duo-radius) - var(--duo-bezel) - var(--duo-edge)) calc(var(--duo-radius) - var(--duo-bezel) - var(--duo-edge)) 2px;
2280
+ }
2281
+ .iphone-duo .ios-statusbar { height: 4.5%; }
2282
+ .iphone-duo .status-time { margin-left: 4%; font-size: 2.4cqw; line-height: 1; }
2283
+ .iphone-duo .ios-statusbar svg { width: 17%; height: 54%; margin-right: 8%; }
2284
+ .duo-camera {
2285
+ position: absolute;
2286
+ z-index: 3;
2287
+ top: 2.25%;
2288
+ right: 3%;
2289
+ width: 1.8%;
2290
+ aspect-ratio: 1;
2291
+ transform: translateY(-50%);
2292
+ border-radius: 50%;
2293
+ background: #000;
2294
+ }
2295
+ @media (orientation: landscape) {
2296
+ .device.iphone-duo { border-radius: var(--duo-radius); }
2297
+ .device.iphone-duo .screen { border-radius: calc(var(--duo-radius) - var(--duo-bezel) - var(--duo-edge)); }
2298
+ .iphone-duo .status-time { margin-left: max(4%, calc(var(--duo-radius) * 0.6)); font-size: 1.8cqw; }
2299
+ .iphone-duo .ios-statusbar svg { width: 13%; margin-right: max(4%, calc(var(--duo-radius) * 0.6)); }
2300
+ .duo-camera { display: none; }
2301
+ }` : ''}
2253
2302
  .android-statusbar { height: 4%; background: ${statusBarStyle?.background || 'transparent'}; color: ${statusBarStyle?.foreground || '#050505'}; font-family: Roboto, Arial, sans-serif; }
2254
2303
  .android-notification { width: 3.4%; height: 46%; margin-left: 2%; }
2255
2304
  .android-status-icons { width: 32%; height: 58%; margin-left: auto; margin-right: 4%; }
@@ -2567,8 +2616,8 @@ function createDeviceFrameHtml(
2567
2616
  <body class="${phonePlatform ? 'phone-frame' : ''} ${screenshotCopy ? 'has-copy' : ''}">
2568
2617
  ${createPromotionalDecorations(palette)}
2569
2618
  ${createScreenshotCopyMarkup(screenshotCopy)}
2570
- <main class="device ${profile.frameType}">
2571
- <div class="screen">${phoneStatusBar}<img src="${screenshotDataUrl}" />${phoneNavigation}</div>
2619
+ <main class="device ${profile.frameType}${profile.name === 'iphone-duo' ? ' iphone-duo' : ''}">
2620
+ <div class="screen">${phoneStatusBar}<img src="${screenshotDataUrl}" />${phoneNavigation}${profile.name === 'iphone-duo' ? '<div class="duo-camera" aria-hidden="true"></div>' : ''}</div>
2572
2621
  ${getFrameAdornment(profile.frameType)}
2573
2622
  </main>
2574
2623
  </body>
@@ -3365,7 +3414,25 @@ async function captureFramedScreenshot(
3365
3414
  await client.send('Runtime.evaluate', {
3366
3415
  expression: `new Promise((resolve) => {
3367
3416
  const image = document.querySelector('img');
3368
- const ready = () => requestAnimationFrame(() => requestAnimationFrame(resolve));
3417
+ const ready = () => requestAnimationFrame(() => {
3418
+ const copy = document.querySelector('.promo-copy');
3419
+ const device = document.querySelector('.device');
3420
+ if (copy && device && window.innerHeight > window.innerWidth) {
3421
+ const gap = Math.max(16, Math.min(40, window.innerHeight * 0.02));
3422
+ const deviceTop = copy.getBoundingClientRect().bottom + gap;
3423
+ const deviceBounds = device.getBoundingClientRect();
3424
+ const availableHeight = window.innerHeight * 0.97 - deviceTop;
3425
+ const availableWidth = window.innerWidth * 0.9;
3426
+ const scale = Math.min(1, availableHeight / deviceBounds.height, availableWidth / deviceBounds.width);
3427
+ if (scale > 0) {
3428
+ device.style.top = deviceTop + 'px';
3429
+ device.style.bottom = 'auto';
3430
+ device.style.transformOrigin = 'top center';
3431
+ device.style.transform = 'translateX(-50%) scale(' + scale + ')';
3432
+ }
3433
+ }
3434
+ requestAnimationFrame(resolve);
3435
+ });
3369
3436
  if (image.complete) ready();
3370
3437
  else {
3371
3438
  image.addEventListener('load', ready, { once: true });
@@ -3438,6 +3505,21 @@ async function captureProfile(client, snapshot, profile, screenshotFileName) {
3438
3505
  frameId: frameTree.frameTree.frame.id,
3439
3506
  html: snapshot.html,
3440
3507
  });
3508
+ if (profile.name === 'iphone-duo') {
3509
+ // The Duo frame supplies its own system bars; do not capture Android insets again.
3510
+ await client.send('Runtime.evaluate', {
3511
+ expression: `(() => {
3512
+ const style = document.createElement('style');
3513
+ style.dataset.duoCaptureInsets = '';
3514
+ style.textContent = 'html, body, .safe-areas, .popup, .sheet-modal, .panel-left, .panel-right {'
3515
+ + '--android-system-bar-top: 0px !important; --android-system-bar-bottom: 0px !important;'
3516
+ + '--android-system-bar-left: 0px !important; --android-system-bar-right: 0px !important;'
3517
+ + '--f7-safe-area-top: 0px !important; --f7-safe-area-bottom: 0px !important;'
3518
+ + '--f7-safe-area-left: 0px !important; --f7-safe-area-right: 0px !important; }';
3519
+ document.head.append(style);
3520
+ })()`,
3521
+ });
3522
+ }
3441
3523
  const replayLayout = await waitForReplayLayout(client, snapshot);
3442
3524
 
3443
3525
  if (replayLayout.documentWidth > replayLayout.viewportWidth + 1) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "codeplay-common",
3
- "version": "4.8.3",
3
+ "version": "4.8.5",
4
4
  "description": "Common build scripts and files",
5
5
  "scripts": {
6
6
  "postinstall": "node scripts/sync-files.js",