@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/package.json +1 -1
- package/src/deviceFrame.ts +2 -2
- package/src/layout/engine.ts +663 -188
- package/src/layoutDom.ts +40 -2
- package/src/protocol.ts +12 -0
- package/src/renderer.ts +13 -2
- package/src/styles.css +490 -150
- package/src/symbols.ts +3 -0
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
|
-
|
|
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
|
-
|
|
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;
|