@swiftbrowser/web 0.26.3 → 0.26.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/src/layoutDom.ts CHANGED
@@ -161,7 +161,7 @@ function applyFrames(ctx: LayoutDomContext, result: LayoutResult, changes: BoxCh
161
161
  applyChrome(result, node, el, box, frame, scrollBottomInset(ctx, result, node, frame), previous === null, changes);
162
162
  if (node.kind === 'tab') setTabHidden(el, result.hidden.has(id));
163
163
  // A `sidebar` list draws as one (no cards or hairlines) in a regular width; in a compact one it looks like `insetGrouped`.
164
- if (node.kind === 'list') setFlag(el, 'sbListLook', node.props['style'] === 'sidebar' && isRegularWidth(ctx.env.screen) ? 'sidebar' : null);
164
+ if (node.kind === 'list') setFlag(el, 'sbListLook', (node.props['style'] === 'sidebar' || result.chrome.get(id)?.['sidebarLook']) && isRegularWidth(ctx.env.screen) ? 'sidebar' : null);
165
165
  // A child the engine did not touch kept its screen frame, but if this
166
166
  // element's box moved, the child's `left`/`top` (relative to it) did
167
167
  // not: the plus button at the bottom of a chat's composer stays put on
@@ -343,6 +343,8 @@ function placeEditControl(node: HTMLElement | null, rect: Rect | undefined, orig
343
343
  * goes, scales and fades as the items do (`TOOLBAR_TRANSITION`), in the
344
344
  * toolbar's own spring.
345
345
  */
346
+ const BAR_PLACEMENTS = new Set(['leading', 'trailing', 'bottom']);
347
+
346
348
  function syncBarGlass(
347
349
  result: LayoutResult,
348
350
  node: LayoutNode,
@@ -354,7 +356,9 @@ function syncBarGlass(
354
356
  ): void {
355
357
  for (const placement of ['leading', 'trailing', 'bottom'] as const) {
356
358
  const unmatched = new Set(screen.querySelectorAll<HTMLElement>(`:scope > .sb-bar-glass[data-sb-placement="${placement}"]:not([data-sb-exiting])`));
357
- const toolbars = node.children.filter((child) => child.kind === 'toolbar' && child.props['placement'] === placement);
359
+ // A capsule's toolbars are those whose frames lie in it, of any bar
360
+ // placement: on a Mac a bottom toolbar's items are on the trailing capsules.
361
+ const toolbars = node.children.filter((child) => child.kind === 'toolbar' && BAR_PLACEMENTS.has(String(child.props['placement'])));
358
362
  for (let count = 0, rect = chrome?.[`${placement}Glass0`]; rect; rect = chrome?.[`${placement}Glass${++count}`]) {
359
363
  const capsule = rect;
360
364
  const ids = toolbars.filter((toolbar) => centeredIn(result.frames.get(toolbar.id), capsule)).map((toolbar) => String(toolbar.id));
@@ -877,6 +881,28 @@ function applyChrome(
877
881
  }
878
882
  bindScrollUnderBar(el, frame, chrome?.['topInset'], chrome?.['largeTitle']);
879
883
  bindScrollEdgeBottom(el, chrome?.['bottomInset'], extent);
884
+ // The Mac idiom's columns form (`data-sb-form-columns`, styles.css):
885
+ // the label column's texts (chrome `formLabel<i>`) as spans in the
886
+ // content, as many as the engine placed.
887
+ setFlag(el, 'sbFormColumns', chrome?.['columns'] ? '' : null);
888
+ setFlag(el, 'sbFormGrouped', chrome?.['grouped'] ? '' : null);
889
+ if (content) {
890
+ const texts = result.chromeText.get(node.id);
891
+ const labels = content.querySelectorAll<HTMLElement>(':scope > .sb-form-label');
892
+ let i = 0;
893
+ for (; chrome?.[`formLabel${i}`]; i++) {
894
+ let label = labels[i] ?? null;
895
+ if (!label) {
896
+ label = document.createElement('span');
897
+ label.className = 'sb-form-label';
898
+ content.append(label);
899
+ }
900
+ placeChrome(label, chrome[`formLabel${i}`], frame);
901
+ const layout = texts?.[`formLabel${i}`];
902
+ if (layout) setTextLayout(label, layout);
903
+ }
904
+ for (let extra = i; extra < labels.length; extra++) labels[extra]!.remove();
905
+ }
880
906
  return;
881
907
  }
882
908
  case 'scrollview': {
@@ -976,6 +1002,17 @@ function applyChrome(
976
1002
  if (inlineTitle.style.lineHeight !== lineHeight) inlineTitle.style.lineHeight = lineHeight;
977
1003
  }
978
1004
  }
1005
+ // The Mac idiom: the subtitle under the title in a unified title bar (chrome `subtitle`, hidden otherwise).
1006
+ const subtitleEl = el.querySelector<HTMLElement>(':scope > .sb-navbar > .sb-navbar-subtitle');
1007
+ if (subtitleEl) {
1008
+ const subtitleRect = chrome?.['subtitle'];
1009
+ placeChrome(subtitleEl, subtitleRect, frame);
1010
+ if (subtitleRect) {
1011
+ if (texts?.['subtitle']) setFont(subtitleEl, texts['subtitle'].font);
1012
+ const lineHeight = `${subtitleRect.height}px`;
1013
+ if (subtitleEl.style.lineHeight !== lineHeight) subtitleEl.style.lineHeight = lineHeight;
1014
+ }
1015
+ }
979
1016
  // The More button of the collapsed `secondary` items, at the trailing end.
980
1017
  placeChrome(el.querySelector<HTMLElement>(':scope > .sb-navbar > .sb-navbar-more'), chrome?.['more'], frame);
981
1018
  const back = el.querySelector<HTMLElement>(':scope > .sb-navbar > .sb-navbar-back');
@@ -1032,6 +1069,7 @@ function applyChrome(
1032
1069
  case 'navsplit': {
1033
1070
  // Phase 7: the sidebar toggle (the sidebar column draws the hairline).
1034
1071
  placeChrome(el.querySelector<HTMLElement>(':scope > .sb-navsplit-toggle'), chrome?.['toggle'], box);
1072
+ placeChrome(el.querySelector<HTMLElement>(':scope > .sb-navsplit-panel'), chrome?.['panel'], box);
1035
1073
  setFlag(el, 'sbSidebarHidden', node.props['sidebarVisible'] === false ? '' : null);
1036
1074
  return;
1037
1075
  }
package/src/protocol.ts CHANGED
@@ -623,6 +623,16 @@ export interface ScrollViewProps {
623
623
  export type ListStyle = 'plain' | 'insetGrouped' | 'grouped' | 'sidebar';
624
624
  export interface ListProps {
625
625
  style?: ListStyle;
626
+ /** A `Form`: on a Mac the page lays its rows out in the columns style (labels in a column before their controls). */
627
+ form?: boolean;
628
+ /** `.formStyle(_:)` other than `automatic`: on a Mac `grouped` lays the form out in grouped sections. */
629
+ formStyle?: 'grouped' | 'columns';
630
+ /**
631
+ * `.environment(\.defaultMinListRowHeight, _)`: the rows are at least
632
+ * this tall (points). Absent, the platform's default: 44 on iOS, 24 on a
633
+ * Mac. A Mac app spaces a content list's rows like a sidebar's, 32.
634
+ */
635
+ minRowHeight?: number;
626
636
  }
627
637
  export interface SectionProps {
628
638
  header: string | null;
@@ -679,6 +689,8 @@ export type NavStackProps = Record<string, never>;
679
689
  export type NavDisplayMode = 'automatic' | 'large' | 'inline';
680
690
  export interface NavScreenProps {
681
691
  title: string;
692
+ /** `.navigationSubtitle`: a second line under the title in a Mac's unified title bar, after it in a plain one. */
693
+ subtitle?: string;
682
694
  displayMode: NavDisplayMode;
683
695
  /** 0 for the root screen; the renderer draws a back button when > 0. */
684
696
  depth: number;
package/src/renderer.ts CHANGED
@@ -3163,6 +3163,9 @@ function createShell(kind: ElementKind): HTMLElement {
3163
3163
  if (chevron) back.append(chevron);
3164
3164
  back.append(span('sb-navbar-back-label'));
3165
3165
  const inlineTitle = span('sb-navbar-title');
3166
+ // `.navigationSubtitle`: under the title in a Mac's unified title bar (chrome.subtitle).
3167
+ const subtitle = span('sb-navbar-subtitle');
3168
+ subtitle.hidden = true;
3166
3169
  // The More button of the collapsed `secondary` items (chrome.more).
3167
3170
  const more = document.createElement('button');
3168
3171
  more.type = 'button';
@@ -3172,7 +3175,7 @@ function createShell(kind: ElementKind): HTMLElement {
3172
3175
  more.hidden = true;
3173
3176
  const ellipsis = renderSymbol('ellipsis');
3174
3177
  if (ellipsis) more.append(ellipsis);
3175
- bar.append(back, inlineTitle, div('sb-navbar-trailing'), more);
3178
+ bar.append(back, inlineTitle, subtitle, div('sb-navbar-trailing'), more);
3176
3179
  const body = div('sb-navscreen-body');
3177
3180
  const slot = div('sb-slot sb-navscreen-content');
3178
3181
  // The `keyboard` toolbars' bar (chrome `keyboardBar`), under their items.
@@ -3369,7 +3372,10 @@ function createShell(kind: ElementKind): HTMLElement {
3369
3372
  toggle.setAttribute('aria-label', 'Toggle Sidebar');
3370
3373
  const symbol = renderSymbol('sidebar.left');
3371
3374
  if (symbol) toggle.append(symbol);
3372
- el.append(slot, toggle);
3375
+ // The Mac's floating sidebar panel, under the columns (the engine's `panel` rect).
3376
+ const panel = div('sb-navsplit-panel');
3377
+ panel.hidden = true;
3378
+ el.append(panel, slot, toggle);
3373
3379
  SLOTS.set(el, slot);
3374
3380
  return el;
3375
3381
  }
@@ -4787,6 +4793,11 @@ function applyNavScreen(el: HTMLElement, props: NavScreenProps): void {
4787
4793
  const largeTitle = largeTitleOf(el);
4788
4794
  // A large title that scrolls away collapses into the inline one (shown
4789
4795
  // with `data-sb-collapsed`, src/largeTitle.ts).
4796
+ const subtitleEl = el.querySelector<HTMLElement>(':scope > .sb-navbar > .sb-navbar-subtitle');
4797
+ if (subtitleEl) {
4798
+ const subtitle = props.subtitle ?? '';
4799
+ if (subtitleEl.textContent !== subtitle) subtitleEl.textContent = subtitle;
4800
+ }
4790
4801
  if (inlineTitle) {
4791
4802
  const appearing = inlineTitle.textContent === '' && title !== '' && inlineTitle.isConnected;
4792
4803
  inlineTitle.textContent = title;