@swiftbrowser/web 0.3.1 → 0.5.0
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/README.md +4 -1
- package/app/index.html +1 -0
- package/package.json +5 -2
- package/src/animation.ts +154 -3
- package/src/dragDrop.ts +549 -0
- package/src/hostContent.ts +37 -0
- package/src/interaction.ts +128 -22
- package/src/largeTitle.ts +98 -54
- package/src/layout/engine.ts +582 -135
- package/src/layout/index.ts +8 -5
- package/src/layout/measure.ts +2 -0
- package/src/layout/text.ts +6 -3
- package/src/layout/types.ts +16 -1
- package/src/layout/typography.ts +30 -10
- package/src/layoutDom.ts +87 -16
- package/src/page.css +234 -0
- package/src/pointerEffects.ts +182 -0
- package/src/protocol.ts +219 -9
- package/src/renderer.ts +355 -15
- package/src/scrollEdge.ts +109 -0
- package/src/scrollTo.ts +6 -0
- package/src/styles.css +457 -251
- package/src/symbols.ts +3 -1
- package/src/typography.ts +5 -2
package/src/styles.css
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
/* ---------------------------------------------------------------------------
|
|
2
2
|
iOS design tokens. Light values live on the device frame; dark ones switch
|
|
3
|
-
with the frame's data-theme so the page
|
|
4
|
-
independent.
|
|
3
|
+
with the frame's data-theme so the page around it (page.css) and the
|
|
4
|
+
"phone" are independent.
|
|
5
5
|
------------------------------------------------------------------------- */
|
|
6
6
|
|
|
7
7
|
:root {
|
|
8
|
+
/* SF Pro's ascender minus its cap height: where a line's capitals start. */
|
|
9
|
+
--sb-cap-inset: 0.2476em;
|
|
8
10
|
/* The UINavigationController push curve: fast start, long settle. */
|
|
9
11
|
--sb-nav-easing: cubic-bezier(0.32, 0.72, 0, 1);
|
|
10
12
|
--sb-font-family: -apple-system, BlinkMacSystemFont, "SF Pro Text", "Helvetica Neue", Inter,
|
|
@@ -13,15 +15,6 @@
|
|
|
13
15
|
overrides this with the current size (see .device below). */
|
|
14
16
|
--sb-font-size-body: 17px;
|
|
15
17
|
|
|
16
|
-
/* Page chrome (not iOS) */
|
|
17
|
-
--page-bg: #e9e9ee;
|
|
18
|
-
--page-fg: #1c1c1e;
|
|
19
|
-
--page-muted: #6e6e73;
|
|
20
|
-
--page-border: rgba(0, 0, 0, 0.08);
|
|
21
|
-
--page-surface: rgba(255, 255, 255, 0.8);
|
|
22
|
-
--page-error-bg: #ffe5e5;
|
|
23
|
-
--page-error-fg: #a1000d;
|
|
24
|
-
|
|
25
18
|
/* Device dimensions (iPhone 15 Pro logical points) */
|
|
26
19
|
--device-screen-width: 393px;
|
|
27
20
|
--device-screen-height: 852px;
|
|
@@ -39,18 +32,6 @@
|
|
|
39
32
|
--sb-inset-left: env(safe-area-inset-left, 0px);
|
|
40
33
|
}
|
|
41
34
|
|
|
42
|
-
@media (prefers-color-scheme: dark) {
|
|
43
|
-
:root {
|
|
44
|
-
--page-bg: #111114;
|
|
45
|
-
--page-fg: #f2f2f7;
|
|
46
|
-
--page-muted: #98989f;
|
|
47
|
-
--page-border: rgba(255, 255, 255, 0.1);
|
|
48
|
-
--page-surface: rgba(28, 28, 30, 0.85);
|
|
49
|
-
--page-error-bg: #3a0b10;
|
|
50
|
-
--page-error-fg: #ff8a8a;
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
|
|
54
35
|
.device {
|
|
55
36
|
/* Dynamic Type: main.ts writes --sb-ts-<style>-size / -lh / -scale on the
|
|
56
37
|
device frame for the selected size (typography.ts); the fallbacks are the
|
|
@@ -162,159 +143,28 @@
|
|
|
162
143
|
}
|
|
163
144
|
|
|
164
145
|
/* ---------------------------------------------------------------------------
|
|
165
|
-
|
|
146
|
+
The renderer's boxes size by their borders. Only its own DOM: a page that
|
|
147
|
+
draws an island (@swiftbrowser/react) keeps its own box model.
|
|
166
148
|
------------------------------------------------------------------------- */
|
|
167
149
|
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
*::
|
|
150
|
+
.device,
|
|
151
|
+
.device *,
|
|
152
|
+
.device *::before,
|
|
153
|
+
.device *::after {
|
|
171
154
|
box-sizing: border-box;
|
|
172
155
|
}
|
|
173
156
|
|
|
174
|
-
html,
|
|
175
|
-
body {
|
|
176
|
-
margin: 0;
|
|
177
|
-
min-height: 100%;
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
body {
|
|
181
|
-
display: flex;
|
|
182
|
-
flex-direction: column;
|
|
183
|
-
min-height: 100vh;
|
|
184
|
-
background: var(--page-bg);
|
|
185
|
-
color: var(--page-fg);
|
|
186
|
-
font-family: var(--sb-font-family);
|
|
187
|
-
font-size: 14px;
|
|
188
|
-
-webkit-font-smoothing: antialiased;
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
.toolbar {
|
|
192
|
-
position: sticky;
|
|
193
|
-
top: 0;
|
|
194
|
-
z-index: 10;
|
|
195
|
-
display: flex;
|
|
196
|
-
align-items: center;
|
|
197
|
-
gap: 10px;
|
|
198
|
-
padding: 10px 16px;
|
|
199
|
-
background: var(--page-surface);
|
|
200
|
-
border-bottom: 1px solid var(--page-border);
|
|
201
|
-
backdrop-filter: blur(16px);
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
.toolbar-brand {
|
|
205
|
-
font-weight: 600;
|
|
206
|
-
letter-spacing: -0.01em;
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
.toolbar-sep {
|
|
210
|
-
width: 1px;
|
|
211
|
-
height: 18px;
|
|
212
|
-
background: var(--page-border);
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
.toolbar-label {
|
|
216
|
-
color: var(--page-muted);
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
.toolbar-value {
|
|
220
|
-
font-variant-numeric: tabular-nums;
|
|
221
|
-
padding: 2px 8px;
|
|
222
|
-
border-radius: 6px;
|
|
223
|
-
background: var(--page-border);
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
.toolbar-spacer {
|
|
227
|
-
flex: 1 1 0;
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
/* Hot-reload indicator, driven by the dev server's sb:build-* events. */
|
|
231
|
-
.toolbar-build {
|
|
232
|
-
display: inline-flex;
|
|
233
|
-
align-items: center;
|
|
234
|
-
gap: 6px;
|
|
235
|
-
color: var(--page-muted);
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
.toolbar-build[hidden] {
|
|
239
|
-
display: none;
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
.toolbar-build::before {
|
|
243
|
-
content: "";
|
|
244
|
-
width: 10px;
|
|
245
|
-
height: 10px;
|
|
246
|
-
border-radius: 50%;
|
|
247
|
-
border: 1.5px solid currentColor;
|
|
248
|
-
border-top-color: transparent;
|
|
249
|
-
animation: sb-spin 0.9s linear infinite;
|
|
250
|
-
}
|
|
251
|
-
|
|
252
|
-
.toolbar-build[data-state="error"] {
|
|
253
|
-
color: var(--page-error-fg);
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
.toolbar-build[data-state="error"]::before {
|
|
257
|
-
animation: none;
|
|
258
|
-
border-color: currentColor;
|
|
259
|
-
}
|
|
260
|
-
|
|
261
|
-
.toolbar-button {
|
|
262
|
-
font: inherit;
|
|
263
|
-
padding: 6px 12px;
|
|
264
|
-
border: 1px solid var(--page-border);
|
|
265
|
-
border-radius: 8px;
|
|
266
|
-
background: transparent;
|
|
267
|
-
color: var(--page-fg);
|
|
268
|
-
cursor: pointer;
|
|
269
|
-
}
|
|
270
|
-
|
|
271
|
-
.toolbar-button:hover {
|
|
272
|
-
background: var(--page-border);
|
|
273
|
-
}
|
|
274
|
-
|
|
275
|
-
.toolbar-button[aria-pressed="true"] {
|
|
276
|
-
background: var(--page-fg);
|
|
277
|
-
color: var(--page-bg);
|
|
278
|
-
border-color: transparent;
|
|
279
|
-
}
|
|
280
|
-
|
|
281
|
-
.toolbar-select {
|
|
282
|
-
font: inherit;
|
|
283
|
-
padding: 5px 8px;
|
|
284
|
-
border: 1px solid var(--page-border);
|
|
285
|
-
border-radius: 8px;
|
|
286
|
-
background: transparent;
|
|
287
|
-
color: var(--page-fg);
|
|
288
|
-
cursor: pointer;
|
|
289
|
-
}
|
|
290
|
-
|
|
291
|
-
.stage {
|
|
292
|
-
display: flex;
|
|
293
|
-
flex-direction: column;
|
|
294
|
-
align-items: center;
|
|
295
|
-
gap: 16px;
|
|
296
|
-
padding: 32px 16px 48px;
|
|
297
|
-
}
|
|
298
|
-
|
|
299
|
-
.status {
|
|
300
|
-
max-width: 560px;
|
|
301
|
-
margin: 0;
|
|
302
|
-
padding: 10px 14px;
|
|
303
|
-
border-radius: 10px;
|
|
304
|
-
background: var(--page-error-bg);
|
|
305
|
-
color: var(--page-error-fg);
|
|
306
|
-
font-family: ui-monospace, "SF Mono", Menlo, monospace;
|
|
307
|
-
font-size: 12px;
|
|
308
|
-
white-space: pre-wrap;
|
|
309
|
-
word-break: break-word;
|
|
310
|
-
}
|
|
311
|
-
|
|
312
157
|
/* ---------------------------------------------------------------------------
|
|
313
158
|
Device frame
|
|
314
159
|
------------------------------------------------------------------------- */
|
|
315
160
|
|
|
316
161
|
.device {
|
|
317
162
|
position: relative;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/* The iPhone's size and bezel: the frame's only. An island (`.sb-island`,
|
|
166
|
+
below) is the page's box, sized by the page's own CSS. */
|
|
167
|
+
.device:not(.sb-island) {
|
|
318
168
|
flex: 0 0 auto;
|
|
319
169
|
width: calc(var(--device-screen-width) + 2 * var(--device-bezel));
|
|
320
170
|
height: calc(var(--device-screen-height) + 2 * var(--device-bezel));
|
|
@@ -483,53 +333,13 @@ body {
|
|
|
483
333
|
/* ---------------------------------------------------------------------------
|
|
484
334
|
Device mode (html[data-sb-mode="device"], see src/deviceMode.ts)
|
|
485
335
|
|
|
486
|
-
On a real phone there is no
|
|
487
|
-
|
|
488
|
-
status-bar / home-indicator
|
|
489
|
-
(via --sb-inset-*), so it is
|
|
336
|
+
On a real phone there is no fake bezel: the OS draws the status bar and
|
|
337
|
+
#screen fills the viewport (the page's part, its toolbar and background,
|
|
338
|
+
is page.css). The layout engine still gets a status-bar / home-indicator
|
|
339
|
+
safe area, but from env(safe-area-inset-*) (via --sb-inset-*), so it is
|
|
340
|
+
only non-zero in standalone mode / landscape.
|
|
490
341
|
------------------------------------------------------------------------- */
|
|
491
342
|
|
|
492
|
-
html[data-sb-mode="device"] {
|
|
493
|
-
height: 100%;
|
|
494
|
-
overflow: hidden;
|
|
495
|
-
overscroll-behavior: none;
|
|
496
|
-
/* No double-tap zoom, no text inflation in landscape. */
|
|
497
|
-
touch-action: manipulation;
|
|
498
|
-
-webkit-text-size-adjust: 100%;
|
|
499
|
-
text-size-adjust: 100%;
|
|
500
|
-
}
|
|
501
|
-
|
|
502
|
-
html[data-sb-mode="device"] body {
|
|
503
|
-
min-height: 100dvh;
|
|
504
|
-
overflow: hidden;
|
|
505
|
-
overscroll-behavior: none;
|
|
506
|
-
touch-action: manipulation;
|
|
507
|
-
}
|
|
508
|
-
|
|
509
|
-
/* The page behind the app is the OS appearance's system background from the
|
|
510
|
-
first paint (Safari tints its status bar strip from it); chromeColor.ts
|
|
511
|
-
then keeps it at the color the app draws at its top edge. */
|
|
512
|
-
html[data-sb-mode="device"],
|
|
513
|
-
html[data-sb-mode="device"] body {
|
|
514
|
-
background: #ffffff;
|
|
515
|
-
}
|
|
516
|
-
|
|
517
|
-
@media (prefers-color-scheme: dark) {
|
|
518
|
-
html[data-sb-mode="device"],
|
|
519
|
-
html[data-sb-mode="device"] body {
|
|
520
|
-
background: #000000;
|
|
521
|
-
}
|
|
522
|
-
}
|
|
523
|
-
|
|
524
|
-
html[data-sb-mode="device"] .toolbar {
|
|
525
|
-
display: none;
|
|
526
|
-
}
|
|
527
|
-
|
|
528
|
-
html[data-sb-mode="device"] .stage {
|
|
529
|
-
padding: 0;
|
|
530
|
-
gap: 0;
|
|
531
|
-
}
|
|
532
|
-
|
|
533
343
|
html[data-sb-mode="device"] .device {
|
|
534
344
|
position: fixed;
|
|
535
345
|
top: 0;
|
|
@@ -556,18 +366,6 @@ html[data-sb-mode="device"] .device-screen {
|
|
|
556
366
|
border-radius: 0;
|
|
557
367
|
}
|
|
558
368
|
|
|
559
|
-
/* The error banner floats over the app, clear of the home indicator. */
|
|
560
|
-
html[data-sb-mode="device"] .status {
|
|
561
|
-
position: fixed;
|
|
562
|
-
left: 16px;
|
|
563
|
-
right: 16px;
|
|
564
|
-
bottom: calc(16px + var(--sb-inset-bottom));
|
|
565
|
-
z-index: 10;
|
|
566
|
-
max-width: none;
|
|
567
|
-
max-height: 40dvh;
|
|
568
|
-
overflow: auto;
|
|
569
|
-
}
|
|
570
|
-
|
|
571
369
|
/* ---------------------------------------------------------------------------
|
|
572
370
|
Rendered elements
|
|
573
371
|
|
|
@@ -701,11 +499,20 @@ html[data-sb-mode="device"] .status {
|
|
|
701
499
|
line-height: 1;
|
|
702
500
|
}
|
|
703
501
|
|
|
502
|
+
/* A symbol fills its box as SF Symbols do: at a text size a circle symbol is
|
|
503
|
+
about 1.28× the font size (21.7 pt at body's 17), and the glyphs here
|
|
504
|
+
occupy 20 of their 24-unit grid (a little more with their stroke), so
|
|
505
|
+
the SVG is 1.43em, centred in the
|
|
506
|
+
line-height box the engine gives the image (the box is unchanged; the
|
|
507
|
+
glyph overflows it a little, as a symbol's bounds do). The stroke, 1.4
|
|
508
|
+
units, is then 1.5 px at body, the weight iOS draws regular symbols in. */
|
|
704
509
|
.sb-image > svg {
|
|
705
510
|
display: block;
|
|
706
|
-
|
|
707
|
-
|
|
511
|
+
flex: none;
|
|
512
|
+
width: 1.43em;
|
|
513
|
+
height: 1.43em;
|
|
708
514
|
stroke: currentColor;
|
|
515
|
+
stroke-width: 1.4;
|
|
709
516
|
}
|
|
710
517
|
|
|
711
518
|
/* An indeterminate ProgressView: the activity indicator (symbols.ts), in
|
|
@@ -949,6 +756,19 @@ svg[data-sb-symbol="progress.indicator"] > line:nth-child(8) { animation-delay:
|
|
|
949
756
|
text-align: left;
|
|
950
757
|
}
|
|
951
758
|
|
|
759
|
+
/* SF Pro's capitals start 0.2476em below the top of a line SwiftUI lays out
|
|
760
|
+
(ascender 0.952em − cap height 0.705em): trim the first line to its cap
|
|
761
|
+
height and put it there, so text the engine places lands where iOS draws
|
|
762
|
+
it, whatever font the page has. */
|
|
763
|
+
@supports (text-box: trim-start cap alphabetic) {
|
|
764
|
+
.sb-text,
|
|
765
|
+
.sb-section-header,
|
|
766
|
+
.sb-section-footer {
|
|
767
|
+
text-box: trim-start cap alphabetic;
|
|
768
|
+
padding-top: var(--sb-cap-inset);
|
|
769
|
+
}
|
|
770
|
+
}
|
|
771
|
+
|
|
952
772
|
.sb-section-header[hidden],
|
|
953
773
|
.sb-section-footer[hidden] {
|
|
954
774
|
display: none;
|
|
@@ -1259,11 +1079,16 @@ svg[data-sb-symbol="progress.indicator"] > line:nth-child(8) { animation-delay:
|
|
|
1259
1079
|
}
|
|
1260
1080
|
|
|
1261
1081
|
/* The large title scrolls with the screen's root list or scroll view
|
|
1262
|
-
(src/largeTitle.ts moves it); drags on it scroll that view.
|
|
1082
|
+
(src/largeTitle.ts moves it into it); drags on it scroll that view. There
|
|
1083
|
+
it is content, under the bar. */
|
|
1263
1084
|
.sb-navbar-large-title {
|
|
1264
1085
|
pointer-events: none;
|
|
1265
1086
|
}
|
|
1266
1087
|
|
|
1088
|
+
[data-sb-under-bar] > .sb-navbar-large-title {
|
|
1089
|
+
z-index: auto;
|
|
1090
|
+
}
|
|
1091
|
+
|
|
1267
1092
|
/* Until the large title has scrolled under the bar, the inline title (whose
|
|
1268
1093
|
rect the engine reports for a title that can collapse) stays hidden. */
|
|
1269
1094
|
.sb-navscreen[data-sb-display="large"] > .sb-navbar > .sb-navbar-title {
|
|
@@ -1275,11 +1100,122 @@ svg[data-sb-symbol="progress.indicator"] > line:nth-child(8) { animation-delay:
|
|
|
1275
1100
|
opacity: 1;
|
|
1276
1101
|
}
|
|
1277
1102
|
|
|
1278
|
-
/* iOS 26's scroll edge effect
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1103
|
+
/* iOS 26's scroll edge effect: a screen's root list or scroll view runs up
|
|
1104
|
+
under the bar (the engine insets its content by the bar), and once content
|
|
1105
|
+
is under it (`data-sb-scrolled`, src/largeTitle.ts) the bar blurs it and
|
|
1106
|
+
fades it into the background toward the top of the screen, a little past
|
|
1107
|
+
the bar's bottom edge. The bar's items stay on their glass. */
|
|
1108
|
+
.sb-navscreen {
|
|
1109
|
+
--sb-edge-color: var(--sb-color-system-background);
|
|
1110
|
+
}
|
|
1111
|
+
|
|
1112
|
+
.sb-navscreen:has(> .sb-navscreen-body .sb-list:is([data-sb-under-bar], [data-sb-under-bottom]):not([data-sb-list-style="plain"])) {
|
|
1113
|
+
--sb-edge-color: var(--sb-color-grouped-background);
|
|
1114
|
+
}
|
|
1115
|
+
|
|
1116
|
+
.sb-navbar::before {
|
|
1117
|
+
content: "";
|
|
1118
|
+
position: absolute;
|
|
1119
|
+
inset: 0 0 -20px;
|
|
1120
|
+
z-index: -1;
|
|
1121
|
+
pointer-events: none;
|
|
1122
|
+
opacity: 0;
|
|
1123
|
+
transition: opacity 0.2s ease;
|
|
1124
|
+
background: linear-gradient(
|
|
1125
|
+
to bottom,
|
|
1126
|
+
var(--sb-edge-color) 0%,
|
|
1127
|
+
color-mix(in srgb, var(--sb-edge-color) 75%, transparent) 45%,
|
|
1128
|
+
transparent 100%
|
|
1129
|
+
);
|
|
1130
|
+
-webkit-backdrop-filter: blur(6px);
|
|
1131
|
+
backdrop-filter: blur(6px);
|
|
1132
|
+
-webkit-mask-image: linear-gradient(to bottom, #000 60%, transparent);
|
|
1133
|
+
mask-image: linear-gradient(to bottom, #000 60%, transparent);
|
|
1134
|
+
}
|
|
1135
|
+
|
|
1136
|
+
/* Its strength follows the content under the bar, ramping up over the
|
|
1137
|
+
first 40px scrolled (`--sb-edge-top`, src/scrollEdge.ts); the transition
|
|
1138
|
+
smooths a scroll that jumps. */
|
|
1139
|
+
.sb-navscreen[data-sb-scrolled] > .sb-navbar::before {
|
|
1140
|
+
opacity: var(--sb-edge-top, 1);
|
|
1141
|
+
transition: opacity 0.12s linear;
|
|
1142
|
+
}
|
|
1143
|
+
|
|
1144
|
+
/* At the bottom: a list or scroll view runs under the tab bar, or its
|
|
1145
|
+
screen's bottom toolbar or search field (the engine insets its content by
|
|
1146
|
+
the bar, `--sb-bottom-edge` tall), and while content is under it
|
|
1147
|
+
(`data-sb-scrolled-bottom`, src/scrollEdge.ts) its screen (over the
|
|
1148
|
+
screen's content: the body is static, so its pseudo-element is placed in
|
|
1149
|
+
the screen, whose own `::after` dims it under a pushed screen), or the
|
|
1150
|
+
tab it fills, blurs it and fades it into the background toward the bottom
|
|
1151
|
+
of the screen, from a little above the bar. The bars stay on top. */
|
|
1152
|
+
.sb-tab {
|
|
1153
|
+
--sb-edge-color: var(--sb-color-system-background);
|
|
1154
|
+
}
|
|
1155
|
+
|
|
1156
|
+
.sb-tab:has(> .sb-list[data-sb-under-bottom]:not([data-sb-list-style="plain"])) {
|
|
1157
|
+
--sb-edge-color: var(--sb-color-grouped-background);
|
|
1158
|
+
}
|
|
1159
|
+
|
|
1160
|
+
.sb-navscreen > .sb-navscreen-body::after,
|
|
1161
|
+
.sb-tab::after {
|
|
1162
|
+
content: "";
|
|
1163
|
+
position: absolute;
|
|
1164
|
+
left: 0;
|
|
1165
|
+
right: 0;
|
|
1166
|
+
bottom: 0;
|
|
1167
|
+
height: calc(var(--sb-bottom-edge, 0px) + 20px);
|
|
1168
|
+
z-index: 1;
|
|
1169
|
+
pointer-events: none;
|
|
1170
|
+
opacity: 0;
|
|
1171
|
+
transition: opacity 0.2s ease;
|
|
1172
|
+
/* iOS 26 mostly fades the content here (it stays faintly legible under
|
|
1173
|
+
the bar) and barely blurs it. */
|
|
1174
|
+
background: linear-gradient(
|
|
1175
|
+
to top,
|
|
1176
|
+
color-mix(in srgb, var(--sb-edge-color) 70%, transparent) 0%,
|
|
1177
|
+
color-mix(in srgb, var(--sb-edge-color) 55%, transparent) 45%,
|
|
1178
|
+
transparent 100%
|
|
1179
|
+
);
|
|
1180
|
+
-webkit-backdrop-filter: blur(1.5px);
|
|
1181
|
+
backdrop-filter: blur(1.5px);
|
|
1182
|
+
-webkit-mask-image: linear-gradient(to top, #000 60%, transparent);
|
|
1183
|
+
mask-image: linear-gradient(to top, #000 60%, transparent);
|
|
1184
|
+
}
|
|
1185
|
+
|
|
1186
|
+
.sb-navscreen[data-sb-scrolled-bottom] > .sb-navscreen-body::after,
|
|
1187
|
+
.sb-tab[data-sb-scrolled-bottom]::after {
|
|
1188
|
+
opacity: var(--sb-edge-bottom, 1);
|
|
1189
|
+
transition: opacity 0.12s linear;
|
|
1190
|
+
}
|
|
1191
|
+
|
|
1192
|
+
/* `.scrollEdgeEffectStyle(.hard, for:)`: an opaque backing exactly behind
|
|
1193
|
+
the bar with a dividing line, no fade; `.scrollEdgeEffectHidden`: none. */
|
|
1194
|
+
.sb-navscreen[data-sb-edge-top="hard"] > .sb-navbar::before {
|
|
1195
|
+
inset: 0;
|
|
1196
|
+
background: var(--sb-edge-color);
|
|
1197
|
+
-webkit-backdrop-filter: none;
|
|
1198
|
+
backdrop-filter: none;
|
|
1199
|
+
-webkit-mask-image: none;
|
|
1200
|
+
mask-image: none;
|
|
1201
|
+
border-bottom: 0.5px solid var(--sb-color-separator);
|
|
1202
|
+
}
|
|
1203
|
+
|
|
1204
|
+
.sb-navscreen[data-sb-edge-bottom="hard"] > .sb-navscreen-body::after,
|
|
1205
|
+
.sb-tab[data-sb-edge-bottom="hard"]::after {
|
|
1206
|
+
height: var(--sb-bottom-edge, 0px);
|
|
1207
|
+
background: var(--sb-edge-color);
|
|
1208
|
+
-webkit-backdrop-filter: none;
|
|
1209
|
+
backdrop-filter: none;
|
|
1210
|
+
-webkit-mask-image: none;
|
|
1211
|
+
mask-image: none;
|
|
1212
|
+
border-top: 0.5px solid var(--sb-color-separator);
|
|
1213
|
+
}
|
|
1214
|
+
|
|
1215
|
+
.sb-navscreen[data-sb-edge-top="hidden"] > .sb-navbar::before,
|
|
1216
|
+
.sb-navscreen[data-sb-edge-bottom="hidden"] > .sb-navscreen-body::after,
|
|
1217
|
+
.sb-tab[data-sb-edge-bottom="hidden"]::after {
|
|
1218
|
+
display: none;
|
|
1283
1219
|
}
|
|
1284
1220
|
|
|
1285
1221
|
/* NavigationLink: a tappable label. Inside a list it is a row with a chevron
|
|
@@ -1539,6 +1475,27 @@ svg[data-sb-symbol="progress.indicator"] > line:nth-child(8) { animation-delay:
|
|
|
1539
1475
|
background: var(--sb-color-system-background);
|
|
1540
1476
|
}
|
|
1541
1477
|
|
|
1478
|
+
/* `sidebar` (iPadOS; a NavigationSplitView's sidebar list): the rows sit on
|
|
1479
|
+
the list's background, without cards or hairlines, and only a selected
|
|
1480
|
+
row is filled (its rounded fill, below). The engine lays it out like
|
|
1481
|
+
`insetGrouped` with 44pt rows. */
|
|
1482
|
+
.sb-list[data-sb-list-style="sidebar"] .sb-section-rows,
|
|
1483
|
+
.sb-list[data-sb-list-style="sidebar"] .sb-section-rows > [data-sb-id],
|
|
1484
|
+
.sb-list[data-sb-list-style="sidebar"] .sb-section-rows > [data-sb-ghost],
|
|
1485
|
+
.sb-list[data-sb-list-style="sidebar"] > .sb-list-content > :not(.sb-section) {
|
|
1486
|
+
background: transparent;
|
|
1487
|
+
border-radius: 0;
|
|
1488
|
+
}
|
|
1489
|
+
|
|
1490
|
+
.sb-list[data-sb-list-style="sidebar"] .sb-section-rows > *::after,
|
|
1491
|
+
.sb-list[data-sb-list-style="sidebar"] > .sb-list-content > :not(.sb-section)::after {
|
|
1492
|
+
display: none;
|
|
1493
|
+
}
|
|
1494
|
+
|
|
1495
|
+
.sb-list[data-sb-list-style="sidebar"] :is(.sb-section-rows, .sb-list-content) > :is(input, textarea) {
|
|
1496
|
+
background-image: none;
|
|
1497
|
+
}
|
|
1498
|
+
|
|
1542
1499
|
/* ---------------------------------------------------------------------------
|
|
1543
1500
|
Phase 4: tab views. The tabs are stacked in the content slot (every tab has
|
|
1544
1501
|
the tab view's frame); the selected one is visible, the others keep their
|
|
@@ -1885,6 +1842,7 @@ svg[data-sb-symbol="progress.indicator"] > line:nth-child(8) { animation-delay:
|
|
|
1885
1842
|
.sb-grid,
|
|
1886
1843
|
.sb-gridrow,
|
|
1887
1844
|
.sb-lazyvgrid,
|
|
1845
|
+
.sb-lazyhgrid,
|
|
1888
1846
|
.sb-decorated {
|
|
1889
1847
|
display: block;
|
|
1890
1848
|
}
|
|
@@ -2152,30 +2110,50 @@ svg[data-sb-symbol="progress.indicator"] > line:nth-child(8) { animation-delay:
|
|
|
2152
2110
|
--sb-color-accent: var(--sb-color-primary);
|
|
2153
2111
|
}
|
|
2154
2112
|
|
|
2155
|
-
/*
|
|
2156
|
-
|
|
2157
|
-
|
|
2158
|
-
|
|
2113
|
+
/* Toolbar items in the bar are laid out in the screen's body, under the bar
|
|
2114
|
+
element (z 1): they stack above it, so its scroll edge effect blurs the
|
|
2115
|
+
content under the bar and not them. */
|
|
2116
|
+
.sb-toolbar:is([data-sb-placement="leading"], [data-sb-placement="trailing"], [data-sb-placement="principal"]) {
|
|
2117
|
+
z-index: 2;
|
|
2118
|
+
}
|
|
2119
|
+
|
|
2120
|
+
/* Their symbols are drawn a little larger and heavier (semibold), as iOS 26
|
|
2121
|
+
draws bar buttons: a plus is 17.7 pt with 2.33 pt bars there. */
|
|
2122
|
+
.sb-toolbar:is([data-sb-placement="leading"], [data-sb-placement="trailing"], [data-sb-placement="bottom"]) .sb-image > svg {
|
|
2123
|
+
width: 1.57em;
|
|
2124
|
+
height: 1.57em;
|
|
2125
|
+
stroke-width: 1.85;
|
|
2159
2126
|
}
|
|
2160
2127
|
|
|
2128
|
+
/* A bottom toolbar's symbols are as large but regular weight. */
|
|
2129
|
+
.sb-toolbar[data-sb-placement="bottom"] .sb-image > svg {
|
|
2130
|
+
stroke-width: 1.5;
|
|
2131
|
+
}
|
|
2132
|
+
|
|
2133
|
+
/* iOS 26: a bottom toolbar's items float over the content on Liquid Glass
|
|
2134
|
+
capsules (engine `bottomGlass0` …, src/layoutDom.ts), drawn in the label
|
|
2135
|
+
color unless tinted, above the scroll edge effect. */
|
|
2161
2136
|
.sb-toolbar[data-sb-placement="bottom"] {
|
|
2162
2137
|
z-index: 3;
|
|
2163
|
-
|
|
2164
|
-
backdrop-filter: blur(20px) saturate(180%);
|
|
2165
|
-
-webkit-backdrop-filter: blur(20px) saturate(180%);
|
|
2138
|
+
--sb-color-accent: var(--sb-color-primary);
|
|
2166
2139
|
}
|
|
2167
2140
|
|
|
2168
|
-
.sb-
|
|
2169
|
-
content: "";
|
|
2141
|
+
.sb-bottom-glass {
|
|
2170
2142
|
position: absolute;
|
|
2171
|
-
|
|
2172
|
-
|
|
2173
|
-
|
|
2174
|
-
|
|
2175
|
-
|
|
2143
|
+
z-index: 2;
|
|
2144
|
+
border-radius: 24px;
|
|
2145
|
+
background: var(--sb-glass-background);
|
|
2146
|
+
box-shadow: var(--sb-glass-shadow);
|
|
2147
|
+
backdrop-filter: blur(12px) saturate(180%);
|
|
2148
|
+
-webkit-backdrop-filter: blur(12px) saturate(180%);
|
|
2176
2149
|
pointer-events: none;
|
|
2177
2150
|
}
|
|
2178
2151
|
|
|
2152
|
+
/* A ToolbarSpacer draws nothing (the engine also hides it). */
|
|
2153
|
+
.sb-toolbar[data-sb-spacer] {
|
|
2154
|
+
display: none;
|
|
2155
|
+
}
|
|
2156
|
+
|
|
2179
2157
|
/* Search field: a capsule (engine `searchField` rect) with a
|
|
2180
2158
|
magnifier leading; the input sends `text` events like a textfield. */
|
|
2181
2159
|
.sb-searchfield {
|
|
@@ -2254,12 +2232,61 @@ svg[data-sb-symbol="progress.indicator"] > line:nth-child(8) { animation-delay:
|
|
|
2254
2232
|
background: var(--sb-color-system-background);
|
|
2255
2233
|
}
|
|
2256
2234
|
|
|
2235
|
+
/* `.inspector`: the content and a trailing panel (protocol children placed by
|
|
2236
|
+
the engine). The panel draws the hairline before it, in the 1pt gap the
|
|
2237
|
+
engine leaves, so the hairline slides with it. */
|
|
2238
|
+
.sb-inspector {
|
|
2239
|
+
overflow: hidden;
|
|
2240
|
+
}
|
|
2241
|
+
|
|
2242
|
+
.sb-inspectorpanel {
|
|
2243
|
+
background: var(--sb-color-system-background);
|
|
2244
|
+
box-shadow: -1px 0 0 var(--sb-color-separator);
|
|
2245
|
+
}
|
|
2246
|
+
|
|
2257
2247
|
.sb-navsplit-separator {
|
|
2258
2248
|
position: absolute;
|
|
2259
2249
|
background: var(--sb-color-separator);
|
|
2260
2250
|
pointer-events: none;
|
|
2261
2251
|
}
|
|
2262
2252
|
|
|
2253
|
+
/* iPadOS: the sidebar toggle, a 44pt glass circle at the leading edge of
|
|
2254
|
+
the bar row (the sidebar's, or the detail's while the sidebar is hidden),
|
|
2255
|
+
over the columns. */
|
|
2256
|
+
.sb-navsplit-toggle {
|
|
2257
|
+
position: absolute;
|
|
2258
|
+
z-index: 2;
|
|
2259
|
+
appearance: none;
|
|
2260
|
+
display: inline-flex;
|
|
2261
|
+
align-items: center;
|
|
2262
|
+
justify-content: center;
|
|
2263
|
+
margin: 0;
|
|
2264
|
+
padding: 0;
|
|
2265
|
+
border: 0;
|
|
2266
|
+
border-radius: 22px;
|
|
2267
|
+
background: var(--sb-glass-background);
|
|
2268
|
+
box-shadow: var(--sb-glass-shadow);
|
|
2269
|
+
backdrop-filter: blur(12px) saturate(180%);
|
|
2270
|
+
-webkit-backdrop-filter: blur(12px) saturate(180%);
|
|
2271
|
+
color: var(--sb-color-primary);
|
|
2272
|
+
cursor: pointer;
|
|
2273
|
+
touch-action: manipulation;
|
|
2274
|
+
-webkit-tap-highlight-color: transparent;
|
|
2275
|
+
}
|
|
2276
|
+
|
|
2277
|
+
.sb-navsplit-toggle[hidden] {
|
|
2278
|
+
display: none;
|
|
2279
|
+
}
|
|
2280
|
+
|
|
2281
|
+
.sb-navsplit-toggle:active {
|
|
2282
|
+
opacity: 0.3;
|
|
2283
|
+
}
|
|
2284
|
+
|
|
2285
|
+
.sb-navsplit-toggle > svg {
|
|
2286
|
+
width: 22px;
|
|
2287
|
+
height: 22px;
|
|
2288
|
+
}
|
|
2289
|
+
|
|
2263
2290
|
/* A selected `navlink` row (`List(selection:)`): the sidebar-style rounded
|
|
2264
2291
|
fill. The engine omits the chevron inside a navsplit sidebar; elsewhere
|
|
2265
2292
|
the chevron stays. The list-row selectors outrank the per-style row
|
|
@@ -2429,6 +2456,7 @@ svg[data-sb-symbol="progress.indicator"] > line:nth-child(8) { animation-delay:
|
|
|
2429
2456
|
.sb-grid,
|
|
2430
2457
|
.sb-gridrow,
|
|
2431
2458
|
.sb-lazyvgrid,
|
|
2459
|
+
.sb-lazyhgrid,
|
|
2432
2460
|
.sb-viewthatfits,
|
|
2433
2461
|
.sb-decorated,
|
|
2434
2462
|
.sb-gesture,
|
|
@@ -2436,8 +2464,8 @@ svg[data-sb-symbol="progress.indicator"] > line:nth-child(8) { animation-delay:
|
|
|
2436
2464
|
pointer-events: none;
|
|
2437
2465
|
}
|
|
2438
2466
|
|
|
2439
|
-
:is(.sb-vstack, .sb-hstack, .sb-zstack, .sb-styled, .sb-geometry, .sb-layout, .sb-grid, .sb-gridrow, .sb-lazyvgrid, .sb-viewthatfits, .sb-decorated, .sb-gesture, .sb-tapgesture)
|
|
2440
|
-
> [data-sb-kind]:not(.sb-vstack, .sb-hstack, .sb-zstack, .sb-spacer, .sb-styled, .sb-geometry, .sb-layout, .sb-grid, .sb-gridrow, .sb-lazyvgrid, .sb-viewthatfits, .sb-decorated, .sb-gesture, .sb-tapgesture) {
|
|
2467
|
+
:is(.sb-vstack, .sb-hstack, .sb-zstack, .sb-styled, .sb-geometry, .sb-layout, .sb-grid, .sb-gridrow, .sb-lazyvgrid, .sb-lazyhgrid, .sb-viewthatfits, .sb-decorated, .sb-gesture, .sb-tapgesture)
|
|
2468
|
+
> [data-sb-kind]:not(.sb-vstack, .sb-hstack, .sb-zstack, .sb-spacer, .sb-styled, .sb-geometry, .sb-layout, .sb-grid, .sb-gridrow, .sb-lazyvgrid, .sb-lazyhgrid, .sb-viewthatfits, .sb-decorated, .sb-gesture, .sb-tapgesture) {
|
|
2441
2469
|
pointer-events: auto;
|
|
2442
2470
|
}
|
|
2443
2471
|
|
|
@@ -2563,7 +2591,8 @@ svg[data-sb-symbol="progress.indicator"] > line:nth-child(8) { animation-delay:
|
|
|
2563
2591
|
}
|
|
2564
2592
|
|
|
2565
2593
|
/* `listrow`: the edit-mode control at the engine's `editControl` rect (hidden
|
|
2566
|
-
otherwise
|
|
2594
|
+
otherwise; its 22px circle is centred 28 from the card's edge, as on
|
|
2595
|
+
iOS); a selected row reuses the Phase 7 selected-row fill. */
|
|
2567
2596
|
.sb-listrow-control {
|
|
2568
2597
|
appearance: none;
|
|
2569
2598
|
position: absolute;
|
|
@@ -2578,6 +2607,12 @@ svg[data-sb-symbol="progress.indicator"] > line:nth-child(8) { animation-delay:
|
|
|
2578
2607
|
cursor: pointer;
|
|
2579
2608
|
touch-action: manipulation;
|
|
2580
2609
|
-webkit-tap-highlight-color: transparent;
|
|
2610
|
+
}
|
|
2611
|
+
|
|
2612
|
+
/* The controls fade in as the list starts editing (layoutDom marks them
|
|
2613
|
+
while they appear); a row moved in the DOM by a reorder keeps them put. */
|
|
2614
|
+
.sb-listrow-control[data-sb-appear],
|
|
2615
|
+
.sb-listrow-grip[data-sb-appear] {
|
|
2581
2616
|
animation: sb-fade-in 0.25s ease-out both;
|
|
2582
2617
|
}
|
|
2583
2618
|
|
|
@@ -2585,6 +2620,89 @@ svg[data-sb-symbol="progress.indicator"] > line:nth-child(8) { animation-delay:
|
|
|
2585
2620
|
display: none;
|
|
2586
2621
|
}
|
|
2587
2622
|
|
|
2623
|
+
/* `listrow`: the reorder grip of a movable row at the engine's
|
|
2624
|
+
`reorderControl` rect (edit mode only): three gray lines, which a press
|
|
2625
|
+
lifts the row by (src/dragDrop.ts). */
|
|
2626
|
+
.sb-listrow-grip {
|
|
2627
|
+
position: absolute;
|
|
2628
|
+
z-index: 2;
|
|
2629
|
+
display: flex;
|
|
2630
|
+
align-items: center;
|
|
2631
|
+
justify-content: center;
|
|
2632
|
+
color: var(--sb-color-tertiary-label);
|
|
2633
|
+
cursor: grab;
|
|
2634
|
+
touch-action: none;
|
|
2635
|
+
user-select: none;
|
|
2636
|
+
-webkit-user-select: none;
|
|
2637
|
+
-webkit-touch-callout: none;
|
|
2638
|
+
}
|
|
2639
|
+
|
|
2640
|
+
.sb-listrow-grip[hidden] {
|
|
2641
|
+
display: none;
|
|
2642
|
+
}
|
|
2643
|
+
|
|
2644
|
+
.sb-listrow-grip > svg {
|
|
2645
|
+
width: 21px;
|
|
2646
|
+
height: 11px;
|
|
2647
|
+
overflow: visible;
|
|
2648
|
+
stroke: currentColor;
|
|
2649
|
+
stroke-width: 1.33;
|
|
2650
|
+
}
|
|
2651
|
+
|
|
2652
|
+
/* A row being reordered: its neighbours slide out of its way (the
|
|
2653
|
+
transition), it follows the pointer above them with a shadow, and settles
|
|
2654
|
+
into its slot with the same slide. The rows are composited for the whole
|
|
2655
|
+
reorder (`will-change`): promoting one as its slide starts would
|
|
2656
|
+
re-rasterize it, a blink of its text and icons. */
|
|
2657
|
+
[data-sb-reordering] > .sb-listrow[data-sb-movable] {
|
|
2658
|
+
transition: transform 0.3s var(--sb-nav-easing);
|
|
2659
|
+
will-change: transform;
|
|
2660
|
+
}
|
|
2661
|
+
|
|
2662
|
+
.sb-listrow[data-sb-lifting] {
|
|
2663
|
+
z-index: 5;
|
|
2664
|
+
box-shadow: 0 6px 20px rgba(0, 0, 0, 0.18);
|
|
2665
|
+
cursor: grabbing;
|
|
2666
|
+
}
|
|
2667
|
+
|
|
2668
|
+
.sb-listrow[data-sb-lifting]:not([data-sb-settling]) {
|
|
2669
|
+
transition: none;
|
|
2670
|
+
}
|
|
2671
|
+
|
|
2672
|
+
/* `draggable` / `dropdestination`: transparent wrappers; a lifted view's
|
|
2673
|
+
copy follows the pointer over everything, scaled up a little with a
|
|
2674
|
+
shadow, while the original dims; letting go off a destination slides the
|
|
2675
|
+
copy back. A targeted destination shows nothing of its own (the app's
|
|
2676
|
+
`isTargeted` does), only that the copy may drop there. */
|
|
2677
|
+
.sb-draggable,
|
|
2678
|
+
.sb-dropdestination {
|
|
2679
|
+
-webkit-touch-callout: none;
|
|
2680
|
+
}
|
|
2681
|
+
|
|
2682
|
+
.sb-draggable {
|
|
2683
|
+
cursor: grab;
|
|
2684
|
+
}
|
|
2685
|
+
|
|
2686
|
+
.sb-draggable[data-sb-dragging] {
|
|
2687
|
+
opacity: 0.4;
|
|
2688
|
+
}
|
|
2689
|
+
|
|
2690
|
+
.sb-drag-preview {
|
|
2691
|
+
z-index: 60;
|
|
2692
|
+
pointer-events: none;
|
|
2693
|
+
opacity: 0.9;
|
|
2694
|
+
filter: drop-shadow(0 8px 20px rgba(0, 0, 0, 0.25));
|
|
2695
|
+
will-change: transform;
|
|
2696
|
+
}
|
|
2697
|
+
|
|
2698
|
+
.sb-drag-preview[data-sb-settling] {
|
|
2699
|
+
transition: transform 0.3s var(--sb-nav-easing);
|
|
2700
|
+
}
|
|
2701
|
+
|
|
2702
|
+
[data-sb-dragging] .sb-dropdestination {
|
|
2703
|
+
cursor: copy;
|
|
2704
|
+
}
|
|
2705
|
+
|
|
2588
2706
|
.sb-listrow-circle {
|
|
2589
2707
|
display: flex;
|
|
2590
2708
|
align-items: center;
|
|
@@ -2625,6 +2743,14 @@ svg[data-sb-symbol="progress.indicator"] > line:nth-child(8) { animation-delay:
|
|
|
2625
2743
|
|
|
2626
2744
|
.sb-listrow[data-sb-deletable]:not([data-sb-selectable]) .sb-listrow-circle > .sb-listrow-minus {
|
|
2627
2745
|
display: block;
|
|
2746
|
+
transition: transform 0.3s var(--sb-nav-easing);
|
|
2747
|
+
}
|
|
2748
|
+
|
|
2749
|
+
/* While the row shows its Delete button (the minus was tapped, or the row
|
|
2750
|
+
swiped) the minus stands upright, as on iOS; the content, and the grip in
|
|
2751
|
+
it, slid aside. */
|
|
2752
|
+
.sb-listrow[data-sb-swipe-open="trailing"] .sb-listrow-circle > .sb-listrow-minus {
|
|
2753
|
+
transform: rotate(-90deg);
|
|
2628
2754
|
}
|
|
2629
2755
|
|
|
2630
2756
|
.sb-listrow[data-sb-selectable] {
|
|
@@ -2638,6 +2764,26 @@ svg[data-sb-symbol="progress.indicator"] > line:nth-child(8) { animation-delay:
|
|
|
2638
2764
|
border-radius: 10px;
|
|
2639
2765
|
}
|
|
2640
2766
|
|
|
2767
|
+
/* The fill is the row's alone: its swipe content (which inherits the row's
|
|
2768
|
+
background to cover the actions behind it) and a selected link inside it
|
|
2769
|
+
would each stack another translucent layer, a darker pill around the
|
|
2770
|
+
label. */
|
|
2771
|
+
.sb-listrow[data-sb-selected] > .sb-swipe-content {
|
|
2772
|
+
background-color: transparent;
|
|
2773
|
+
}
|
|
2774
|
+
|
|
2775
|
+
.sb-listrow .sb-navlink[data-sb-selected] {
|
|
2776
|
+
background: transparent;
|
|
2777
|
+
}
|
|
2778
|
+
|
|
2779
|
+
/* Like iOS, a selected row's fill has no hairline under it or above it. */
|
|
2780
|
+
.sb-section-rows > [data-sb-selected]::after,
|
|
2781
|
+
.sb-list-content > [data-sb-selected]::after,
|
|
2782
|
+
.sb-section-rows > :has(+ [data-sb-selected])::after,
|
|
2783
|
+
.sb-list-content > :has(+ [data-sb-selected])::after {
|
|
2784
|
+
display: none;
|
|
2785
|
+
}
|
|
2786
|
+
|
|
2641
2787
|
@keyframes sb-fade-in {
|
|
2642
2788
|
from {
|
|
2643
2789
|
opacity: 0;
|
|
@@ -2654,7 +2800,8 @@ svg[data-sb-symbol="progress.indicator"] > line:nth-child(8) { animation-delay:
|
|
|
2654
2800
|
position: absolute;
|
|
2655
2801
|
left: 0;
|
|
2656
2802
|
right: 0;
|
|
2657
|
-
|
|
2803
|
+
/* Under the bar a scroll view's content starts below it. */
|
|
2804
|
+
top: var(--sb-top-inset, 0px);
|
|
2658
2805
|
height: 40px;
|
|
2659
2806
|
display: flex;
|
|
2660
2807
|
align-items: center;
|
|
@@ -2682,17 +2829,20 @@ svg[data-sb-symbol="progress.indicator"] > line:nth-child(8) { animation-delay:
|
|
|
2682
2829
|
}
|
|
2683
2830
|
|
|
2684
2831
|
.sb-list[data-sb-refreshable] > .sb-list-content,
|
|
2685
|
-
.sb-scrollview[data-sb-refreshable] > .sb-scroll-content
|
|
2832
|
+
.sb-scrollview[data-sb-refreshable] > .sb-scroll-content,
|
|
2833
|
+
[data-sb-refreshable] > .sb-navbar-large-title {
|
|
2686
2834
|
transition: transform 0.3s var(--sb-nav-easing);
|
|
2687
2835
|
}
|
|
2688
2836
|
|
|
2689
2837
|
[data-sb-pulling] > .sb-list-content,
|
|
2690
|
-
[data-sb-pulling] > .sb-scroll-content
|
|
2838
|
+
[data-sb-pulling] > .sb-scroll-content,
|
|
2839
|
+
[data-sb-pulling] > .sb-navbar-large-title {
|
|
2691
2840
|
transition: none;
|
|
2692
2841
|
}
|
|
2693
2842
|
|
|
2694
2843
|
[data-sb-refreshing] > .sb-list-content,
|
|
2695
|
-
[data-sb-refreshing] > .sb-scroll-content
|
|
2844
|
+
[data-sb-refreshing] > .sb-scroll-content,
|
|
2845
|
+
[data-sb-refreshing] > .sb-navbar-large-title {
|
|
2696
2846
|
transform: translateY(40px);
|
|
2697
2847
|
}
|
|
2698
2848
|
|
|
@@ -3270,3 +3420,59 @@ svg.sb-path {
|
|
|
3270
3420
|
display: block;
|
|
3271
3421
|
overflow: hidden;
|
|
3272
3422
|
}
|
|
3423
|
+
|
|
3424
|
+
/* ---------------------------------------------------------------------------
|
|
3425
|
+
`host` elements (src/hostContent.ts): the page's own DOM in a box the
|
|
3426
|
+
engine sizes. The content element is positioned at the box's origin and
|
|
3427
|
+
takes its width; in `fill` sizing its height too. The box clips.
|
|
3428
|
+
------------------------------------------------------------------------- */
|
|
3429
|
+
.sb-host {
|
|
3430
|
+
display: block;
|
|
3431
|
+
overflow: hidden;
|
|
3432
|
+
}
|
|
3433
|
+
|
|
3434
|
+
.sb-host > .sb-host-content {
|
|
3435
|
+
position: absolute;
|
|
3436
|
+
left: 0;
|
|
3437
|
+
top: 0;
|
|
3438
|
+
width: 100%;
|
|
3439
|
+
box-sizing: border-box;
|
|
3440
|
+
}
|
|
3441
|
+
|
|
3442
|
+
.sb-host[data-sb-sizing="fill"] > .sb-host-content {
|
|
3443
|
+
height: 100%;
|
|
3444
|
+
}
|
|
3445
|
+
|
|
3446
|
+
/* ---------------------------------------------------------------------------
|
|
3447
|
+
Islands (@swiftbrowser/react's `SwiftUIView`): a SwiftUI root inside a
|
|
3448
|
+
page's own layout, with the device's tokens and none of its frame: the
|
|
3449
|
+
frame's size, bezel and shadow are `.device:not(.sb-island)`'s above, so
|
|
3450
|
+
the page's own CSS, however specific, gives the island its width and
|
|
3451
|
+
height. In `content` fit the renderer sets the root's height
|
|
3452
|
+
from the layout.
|
|
3453
|
+
------------------------------------------------------------------------- */
|
|
3454
|
+
.device.sb-island::before,
|
|
3455
|
+
.device.sb-island::after {
|
|
3456
|
+
display: none;
|
|
3457
|
+
}
|
|
3458
|
+
|
|
3459
|
+
.sb-island .device-screen {
|
|
3460
|
+
width: 100%;
|
|
3461
|
+
height: 100%;
|
|
3462
|
+
border-radius: 0;
|
|
3463
|
+
background: none;
|
|
3464
|
+
}
|
|
3465
|
+
|
|
3466
|
+
.sb-island[data-sb-fit="content"],
|
|
3467
|
+
.sb-island[data-sb-fit="content"] .device-screen,
|
|
3468
|
+
.sb-island[data-sb-fit="content"] .screen {
|
|
3469
|
+
position: relative;
|
|
3470
|
+
inset: auto;
|
|
3471
|
+
height: auto;
|
|
3472
|
+
}
|
|
3473
|
+
|
|
3474
|
+
/* `.onHover` (pointerEffects.ts): the whole box hovers, as SwiftUI uses the
|
|
3475
|
+
view's frame, so a hover box is hit in its empty space too. */
|
|
3476
|
+
.sb-styled[data-sb-hover] {
|
|
3477
|
+
pointer-events: auto;
|
|
3478
|
+
}
|