@swiftbrowser/web 0.17.3 → 0.17.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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@swiftbrowser/web",
3
- "version": "0.17.3",
3
+ "version": "0.17.5",
4
4
  "type": "module",
5
5
  "description": "SwiftBrowser web renderer and dev server: applies render ops from a Swift/Wasm app to the DOM inside an iPhone frame.",
6
6
  "repository": {
@@ -162,6 +162,10 @@ const NAV_BAR_HEIGHT = 44;
162
162
  const NAV_BAR_INLINE_BOTTOM = 10;
163
163
  // iOS 26: the large title's row is 62 (52 before).
164
164
  const LARGE_TITLE_HEIGHT = 62;
165
+ // iOS 26: `.largeTitle` toolbar items take the title's place 10 below the
166
+ // bar's 44 row, in a row exactly their height: the content starts right
167
+ // under them (measured in the iOS 26 simulator, Native's LargeTitleTests).
168
+ const LARGE_TITLE_ITEM_TOP = 10;
165
169
  const SHEET_GRABBER_ROW = 20;
166
170
  const SHEET_GRABBER: Size = { width: 36, height: 5 };
167
171
  const SHEET_GRABBER_TOP = 8;
@@ -3390,8 +3394,9 @@ export class LayoutPass {
3390
3394
 
3391
3395
  /**
3392
3396
  * The navigation bar is the status bar plus a 44 row (plus 62 for a large
3393
- * title); a Phase 6 `searchfield` is a capsule floating above the bottom
3394
- * (iOS 26); `bottom` toolbars float their items on glass in a 48 row 28
3397
+ * title, or 10 and the height of its `.largeTitle` items); a Phase 6
3398
+ * `searchfield` is a capsule floating above the bottom (iOS 26); `bottom`
3399
+ * toolbars float their items on glass in a 48 row 28
3395
3400
  * above the bottom (`placeBottomToolbars`); the content is proposed the
3396
3401
  * rest. Toolbar items are measured at their ideal size and laid out in iOS
3397
3402
  * 26 glass capsules in the 44 row: adjacent `leading` (or `trailing`)
@@ -3403,6 +3408,18 @@ export class LayoutPass {
3403
3408
  * inline title (`chrome.title`) is centered and truncated to the room the
3404
3409
  * bar items leave.
3405
3410
  */
3411
+ /** The tallest `.largeTitle` toolbar item at its ideal size, or null when the screen has none (the text title shows). */
3412
+ private largeTitleItemsHeight(toolbars: readonly LayoutNode[], cctx: Context, width: number): number | null {
3413
+ let height: number | null = null;
3414
+ for (const toolbar of toolbars) {
3415
+ if (str(toolbar.props['placement']) !== 'largeTitle' || str(toolbar.props['spacer']) !== null) continue;
3416
+ const itemCtx = this.childContext(toolbar, cctx);
3417
+ const room: Proposal = { width: Math.max(0, width - 2 * NAV_ITEM_MARGIN), height: null };
3418
+ for (const item of toolbar.children) height = Math.max(height ?? 0, this.measure(item, itemCtx, room).height);
3419
+ }
3420
+ return height;
3421
+ }
3422
+
3406
3423
  private placeNavScreen(node: LayoutNode, cctx: Context, x: number, y: number, size: Size): void {
3407
3424
  const { safeArea, screen, dynamicTypeSize } = this.env;
3408
3425
  const statusBar = y <= 0 ? safeArea.top : (cctx.barInset ?? 0);
@@ -3410,10 +3427,12 @@ export class LayoutPass {
3410
3427
  const depth = num(node.props['depth']) ?? 0;
3411
3428
  const large = displayMode === 'large' || (displayMode === 'automatic' && depth === 0);
3412
3429
  const barHeight = statusBar + NAV_BAR_HEIGHT + (!large && y <= 0 ? NAV_BAR_INLINE_BOTTOM : 0);
3413
- const largeHeight = large ? LARGE_TITLE_HEIGHT : 0;
3414
3430
  const parts = this.navScreenParts(node);
3415
3431
  const reachesBottom = y + size.height >= screen.height - EPSILON;
3416
3432
  const isSpacer = (t: LayoutNode): boolean => str(t.props['spacer']) !== null;
3433
+ // `.largeTitle` items size the large title row; the text title is 62.
3434
+ const titleItemsHeight = large ? this.largeTitleItemsHeight(parts.toolbars, cctx, size.width) : null;
3435
+ const largeHeight = !large ? 0 : titleItemsHeight !== null ? LARGE_TITLE_ITEM_TOP + titleItemsHeight : LARGE_TITLE_HEIGHT;
3417
3436
  const bottomToolbars = parts.toolbars.filter((t) => str(t.props['placement']) === 'bottom');
3418
3437
  const searchMargin = reachesBottom ? SEARCH_BOTTOM_MARGIN : SEARCH_BOTTOM_MARGIN_INSET;
3419
3438
  const bottomBarHeight = bottomToolbars.some((t) => !isSpacer(t))
@@ -3443,7 +3462,7 @@ export class LayoutPass {
3443
3462
  barRow: { x, y: y + statusBar, width: size.width, height: NAV_BAR_HEIGHT },
3444
3463
  content,
3445
3464
  };
3446
- if (large) chrome['largeTitle'] = { x, y: y + barHeight, width: size.width, height: LARGE_TITLE_HEIGHT };
3465
+ if (large) chrome['largeTitle'] = { x, y: y + barHeight, width: size.width, height: largeHeight };
3447
3466
  if (parts.search) {
3448
3467
  const marginH = searchOnTop ? SEARCH_FIELD_MARGIN_H : SEARCH_BOTTOM_MARGIN_H;
3449
3468
  chrome['searchField'] = {
@@ -3711,9 +3730,11 @@ export class LayoutPass {
3711
3730
  const itemCtx = this.childContext(toolbar, cctx);
3712
3731
  const room: Proposal = { width: Math.max(0, row.width - 2 * NAV_ITEM_MARGIN), height: null };
3713
3732
  let cursor = row.x + NAV_ITEM_MARGIN;
3733
+ // The items' band: the row below its 10 top, as tall as the tallest item.
3734
+ const band = row.height - LARGE_TITLE_ITEM_TOP;
3714
3735
  for (const item of toolbar.children) {
3715
3736
  const itemSize = this.measure(item, itemCtx, room);
3716
- this.place(item, itemCtx, room, cursor, row.y + (row.height - itemSize.height) / 2);
3737
+ this.place(item, itemCtx, room, cursor, row.y + LARGE_TITLE_ITEM_TOP + (band - itemSize.height) / 2);
3717
3738
  cursor += itemSize.width + NAV_ITEM_SPACING;
3718
3739
  }
3719
3740
  this.setFrame(toolbar, row.x, row.y, { width: row.width, height: row.height });
package/src/layoutDom.ts CHANGED
@@ -944,6 +944,9 @@ function applyChrome(
944
944
  applyChartChrome(el, node, result, box);
945
945
  return;
946
946
  case 'listrow':
947
+ // A selectable or editable row is the list's row: the engine reads a
948
+ // badge through it and puts the badge's rect on it, so it draws it.
949
+ applyBadge(result, node, el, box);
947
950
  // Phase 8: the edit-mode control (selection circle / minus) at the
948
951
  // engine's `editControl` rect and the reorder grip at `reorderControl`;
949
952
  // hidden when the list is not editing.