@swiftbrowser/web 0.4.0 → 0.5.1

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/styles.css CHANGED
@@ -1,7 +1,7 @@
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 chrome and the "phone" are
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 {
@@ -15,15 +15,6 @@
15
15
  overrides this with the current size (see .device below). */
16
16
  --sb-font-size-body: 17px;
17
17
 
18
- /* Page chrome (not iOS) */
19
- --page-bg: #e9e9ee;
20
- --page-fg: #1c1c1e;
21
- --page-muted: #6e6e73;
22
- --page-border: rgba(0, 0, 0, 0.08);
23
- --page-surface: rgba(255, 255, 255, 0.8);
24
- --page-error-bg: #ffe5e5;
25
- --page-error-fg: #a1000d;
26
-
27
18
  /* Device dimensions (iPhone 15 Pro logical points) */
28
19
  --device-screen-width: 393px;
29
20
  --device-screen-height: 852px;
@@ -41,18 +32,6 @@
41
32
  --sb-inset-left: env(safe-area-inset-left, 0px);
42
33
  }
43
34
 
44
- @media (prefers-color-scheme: dark) {
45
- :root {
46
- --page-bg: #111114;
47
- --page-fg: #f2f2f7;
48
- --page-muted: #98989f;
49
- --page-border: rgba(255, 255, 255, 0.1);
50
- --page-surface: rgba(28, 28, 30, 0.85);
51
- --page-error-bg: #3a0b10;
52
- --page-error-fg: #ff8a8a;
53
- }
54
- }
55
-
56
35
  .device {
57
36
  /* Dynamic Type: main.ts writes --sb-ts-<style>-size / -lh / -scale on the
58
37
  device frame for the selected size (typography.ts); the fallbacks are the
@@ -164,159 +143,28 @@
164
143
  }
165
144
 
166
145
  /* ---------------------------------------------------------------------------
167
- Page layout
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.
168
148
  ------------------------------------------------------------------------- */
169
149
 
170
- *,
171
- *::before,
172
- *::after {
150
+ .device,
151
+ .device *,
152
+ .device *::before,
153
+ .device *::after {
173
154
  box-sizing: border-box;
174
155
  }
175
156
 
176
- html,
177
- body {
178
- margin: 0;
179
- min-height: 100%;
180
- }
181
-
182
- body {
183
- display: flex;
184
- flex-direction: column;
185
- min-height: 100vh;
186
- background: var(--page-bg);
187
- color: var(--page-fg);
188
- font-family: var(--sb-font-family);
189
- font-size: 14px;
190
- -webkit-font-smoothing: antialiased;
191
- }
192
-
193
- .toolbar {
194
- position: sticky;
195
- top: 0;
196
- z-index: 10;
197
- display: flex;
198
- align-items: center;
199
- gap: 10px;
200
- padding: 10px 16px;
201
- background: var(--page-surface);
202
- border-bottom: 1px solid var(--page-border);
203
- backdrop-filter: blur(16px);
204
- }
205
-
206
- .toolbar-brand {
207
- font-weight: 600;
208
- letter-spacing: -0.01em;
209
- }
210
-
211
- .toolbar-sep {
212
- width: 1px;
213
- height: 18px;
214
- background: var(--page-border);
215
- }
216
-
217
- .toolbar-label {
218
- color: var(--page-muted);
219
- }
220
-
221
- .toolbar-value {
222
- font-variant-numeric: tabular-nums;
223
- padding: 2px 8px;
224
- border-radius: 6px;
225
- background: var(--page-border);
226
- }
227
-
228
- .toolbar-spacer {
229
- flex: 1 1 0;
230
- }
231
-
232
- /* Hot-reload indicator, driven by the dev server's sb:build-* events. */
233
- .toolbar-build {
234
- display: inline-flex;
235
- align-items: center;
236
- gap: 6px;
237
- color: var(--page-muted);
238
- }
239
-
240
- .toolbar-build[hidden] {
241
- display: none;
242
- }
243
-
244
- .toolbar-build::before {
245
- content: "";
246
- width: 10px;
247
- height: 10px;
248
- border-radius: 50%;
249
- border: 1.5px solid currentColor;
250
- border-top-color: transparent;
251
- animation: sb-spin 0.9s linear infinite;
252
- }
253
-
254
- .toolbar-build[data-state="error"] {
255
- color: var(--page-error-fg);
256
- }
257
-
258
- .toolbar-build[data-state="error"]::before {
259
- animation: none;
260
- border-color: currentColor;
261
- }
262
-
263
- .toolbar-button {
264
- font: inherit;
265
- padding: 6px 12px;
266
- border: 1px solid var(--page-border);
267
- border-radius: 8px;
268
- background: transparent;
269
- color: var(--page-fg);
270
- cursor: pointer;
271
- }
272
-
273
- .toolbar-button:hover {
274
- background: var(--page-border);
275
- }
276
-
277
- .toolbar-button[aria-pressed="true"] {
278
- background: var(--page-fg);
279
- color: var(--page-bg);
280
- border-color: transparent;
281
- }
282
-
283
- .toolbar-select {
284
- font: inherit;
285
- padding: 5px 8px;
286
- border: 1px solid var(--page-border);
287
- border-radius: 8px;
288
- background: transparent;
289
- color: var(--page-fg);
290
- cursor: pointer;
291
- }
292
-
293
- .stage {
294
- display: flex;
295
- flex-direction: column;
296
- align-items: center;
297
- gap: 16px;
298
- padding: 32px 16px 48px;
299
- }
300
-
301
- .status {
302
- max-width: 560px;
303
- margin: 0;
304
- padding: 10px 14px;
305
- border-radius: 10px;
306
- background: var(--page-error-bg);
307
- color: var(--page-error-fg);
308
- font-family: ui-monospace, "SF Mono", Menlo, monospace;
309
- font-size: 12px;
310
- white-space: pre-wrap;
311
- word-break: break-word;
312
- }
313
-
314
157
  /* ---------------------------------------------------------------------------
315
158
  Device frame
316
159
  ------------------------------------------------------------------------- */
317
160
 
318
161
  .device {
319
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) {
320
168
  flex: 0 0 auto;
321
169
  width: calc(var(--device-screen-width) + 2 * var(--device-bezel));
322
170
  height: calc(var(--device-screen-height) + 2 * var(--device-bezel));
@@ -485,53 +333,13 @@ body {
485
333
  /* ---------------------------------------------------------------------------
486
334
  Device mode (html[data-sb-mode="device"], see src/deviceMode.ts)
487
335
 
488
- On a real phone there is no toolbar and no fake bezel: the OS draws the
489
- status bar and #screen fills the viewport. The layout engine still gets a
490
- status-bar / home-indicator safe area, but from env(safe-area-inset-*)
491
- (via --sb-inset-*), so it is only non-zero in standalone mode / landscape.
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.
492
341
  ------------------------------------------------------------------------- */
493
342
 
494
- html[data-sb-mode="device"] {
495
- height: 100%;
496
- overflow: hidden;
497
- overscroll-behavior: none;
498
- /* No double-tap zoom, no text inflation in landscape. */
499
- touch-action: manipulation;
500
- -webkit-text-size-adjust: 100%;
501
- text-size-adjust: 100%;
502
- }
503
-
504
- html[data-sb-mode="device"] body {
505
- min-height: 100dvh;
506
- overflow: hidden;
507
- overscroll-behavior: none;
508
- touch-action: manipulation;
509
- }
510
-
511
- /* The page behind the app is the OS appearance's system background from the
512
- first paint (Safari tints its status bar strip from it); chromeColor.ts
513
- then keeps it at the color the app draws at its top edge. */
514
- html[data-sb-mode="device"],
515
- html[data-sb-mode="device"] body {
516
- background: #ffffff;
517
- }
518
-
519
- @media (prefers-color-scheme: dark) {
520
- html[data-sb-mode="device"],
521
- html[data-sb-mode="device"] body {
522
- background: #000000;
523
- }
524
- }
525
-
526
- html[data-sb-mode="device"] .toolbar {
527
- display: none;
528
- }
529
-
530
- html[data-sb-mode="device"] .stage {
531
- padding: 0;
532
- gap: 0;
533
- }
534
-
535
343
  html[data-sb-mode="device"] .device {
536
344
  position: fixed;
537
345
  top: 0;
@@ -558,18 +366,6 @@ html[data-sb-mode="device"] .device-screen {
558
366
  border-radius: 0;
559
367
  }
560
368
 
561
- /* The error banner floats over the app, clear of the home indicator. */
562
- html[data-sb-mode="device"] .status {
563
- position: fixed;
564
- left: 16px;
565
- right: 16px;
566
- bottom: calc(16px + var(--sb-inset-bottom));
567
- z-index: 10;
568
- max-width: none;
569
- max-height: 40dvh;
570
- overflow: auto;
571
- }
572
-
573
369
  /* ---------------------------------------------------------------------------
574
370
  Rendered elements
575
371
 
@@ -1205,6 +1001,41 @@ svg[data-sb-symbol="progress.indicator"] > line:nth-child(8) { animation-delay:
1205
1001
  display: none;
1206
1002
  }
1207
1003
 
1004
+ /* The More button of the collapsed `.secondaryAction` items: the ellipsis
1005
+ in a 44pt glass circle at the engine's `more` rect (iOS 26). */
1006
+ .sb-navbar-more {
1007
+ appearance: none;
1008
+ display: inline-flex;
1009
+ align-items: center;
1010
+ justify-content: center;
1011
+ margin: 0;
1012
+ padding: 0;
1013
+ border: 0;
1014
+ border-radius: 22px;
1015
+ background: var(--sb-glass-background);
1016
+ box-shadow: var(--sb-glass-shadow);
1017
+ backdrop-filter: blur(12px) saturate(180%);
1018
+ -webkit-backdrop-filter: blur(12px) saturate(180%);
1019
+ color: var(--sb-color-primary);
1020
+ font-size: var(--sb-font-size-body);
1021
+ cursor: pointer;
1022
+ touch-action: manipulation;
1023
+ -webkit-tap-highlight-color: transparent;
1024
+ }
1025
+
1026
+ .sb-navbar-more[hidden] {
1027
+ display: none;
1028
+ }
1029
+
1030
+ .sb-navbar-more:active {
1031
+ opacity: 0.3;
1032
+ }
1033
+
1034
+ .sb-navbar-more > svg {
1035
+ width: 1.2em;
1036
+ height: 1.2em;
1037
+ }
1038
+
1208
1039
  .sb-navbar-back:active {
1209
1040
  opacity: 0.3;
1210
1041
  }
@@ -1337,8 +1168,12 @@ svg[data-sb-symbol="progress.indicator"] > line:nth-child(8) { animation-delay:
1337
1168
  mask-image: linear-gradient(to bottom, #000 60%, transparent);
1338
1169
  }
1339
1170
 
1171
+ /* Its strength follows the content under the bar, ramping up over the
1172
+ first 40px scrolled (`--sb-edge-top`, src/scrollEdge.ts); the transition
1173
+ smooths a scroll that jumps. */
1340
1174
  .sb-navscreen[data-sb-scrolled] > .sb-navbar::before {
1341
- opacity: 1;
1175
+ opacity: var(--sb-edge-top, 1);
1176
+ transition: opacity 0.12s linear;
1342
1177
  }
1343
1178
 
1344
1179
  /* At the bottom: a list or scroll view runs under the tab bar, or its
@@ -1369,21 +1204,24 @@ svg[data-sb-symbol="progress.indicator"] > line:nth-child(8) { animation-delay:
1369
1204
  pointer-events: none;
1370
1205
  opacity: 0;
1371
1206
  transition: opacity 0.2s ease;
1207
+ /* iOS 26 mostly fades the content here (it stays faintly legible under
1208
+ the bar) and barely blurs it. */
1372
1209
  background: linear-gradient(
1373
1210
  to top,
1374
- var(--sb-edge-color) 0%,
1375
- color-mix(in srgb, var(--sb-edge-color) 75%, transparent) 45%,
1211
+ color-mix(in srgb, var(--sb-edge-color) 70%, transparent) 0%,
1212
+ color-mix(in srgb, var(--sb-edge-color) 55%, transparent) 45%,
1376
1213
  transparent 100%
1377
1214
  );
1378
- -webkit-backdrop-filter: blur(6px);
1379
- backdrop-filter: blur(6px);
1215
+ -webkit-backdrop-filter: blur(1.5px);
1216
+ backdrop-filter: blur(1.5px);
1380
1217
  -webkit-mask-image: linear-gradient(to top, #000 60%, transparent);
1381
1218
  mask-image: linear-gradient(to top, #000 60%, transparent);
1382
1219
  }
1383
1220
 
1384
1221
  .sb-navscreen[data-sb-scrolled-bottom] > .sb-navscreen-body::after,
1385
1222
  .sb-tab[data-sb-scrolled-bottom]::after {
1386
- opacity: 1;
1223
+ opacity: var(--sb-edge-bottom, 1);
1224
+ transition: opacity 0.12s linear;
1387
1225
  }
1388
1226
 
1389
1227
  /* `.scrollEdgeEffectStyle(.hard, for:)`: an opaque backing exactly behind
@@ -2039,6 +1877,7 @@ svg[data-sb-symbol="progress.indicator"] > line:nth-child(8) { animation-delay:
2039
1877
  .sb-grid,
2040
1878
  .sb-gridrow,
2041
1879
  .sb-lazyvgrid,
1880
+ .sb-lazyhgrid,
2042
1881
  .sb-decorated {
2043
1882
  display: block;
2044
1883
  }
@@ -2293,6 +2132,13 @@ svg[data-sb-symbol="progress.indicator"] > line:nth-child(8) { animation-delay:
2293
2132
  display: block;
2294
2133
  }
2295
2134
 
2135
+ /* A toolbar the engine hid (a spacer, `secondary` items collapsed into the
2136
+ More menu), and its items collapsed one by one, draw nothing. */
2137
+ .sb-toolbar[hidden],
2138
+ .sb-toolbar > [data-sb-id][hidden] {
2139
+ display: none;
2140
+ }
2141
+
2296
2142
  /* iOS 26: a bar's leading and trailing items share a glass capsule (the
2297
2143
  engine sizes the group: 44 tall, each item in a slot at least 44 wide),
2298
2144
  drawn in the label color unless tinted. */
@@ -2315,30 +2161,41 @@ svg[data-sb-symbol="progress.indicator"] > line:nth-child(8) { animation-delay:
2315
2161
 
2316
2162
  /* Their symbols are drawn a little larger and heavier (semibold), as iOS 26
2317
2163
  draws bar buttons: a plus is 17.7 pt with 2.33 pt bars there. */
2318
- .sb-toolbar:is([data-sb-placement="leading"], [data-sb-placement="trailing"]) .sb-image > svg {
2164
+ .sb-toolbar:is([data-sb-placement="leading"], [data-sb-placement="trailing"], [data-sb-placement="bottom"]) .sb-image > svg {
2319
2165
  width: 1.57em;
2320
2166
  height: 1.57em;
2321
2167
  stroke-width: 1.85;
2322
2168
  }
2323
2169
 
2170
+ /* A bottom toolbar's symbols are as large but regular weight. */
2171
+ .sb-toolbar[data-sb-placement="bottom"] .sb-image > svg {
2172
+ stroke-width: 1.5;
2173
+ }
2174
+
2175
+ /* iOS 26: a bottom toolbar's items float over the content on Liquid Glass
2176
+ capsules (engine `bottomGlass0` …, src/layoutDom.ts), drawn in the label
2177
+ color unless tinted, above the scroll edge effect. */
2324
2178
  .sb-toolbar[data-sb-placement="bottom"] {
2325
2179
  z-index: 3;
2326
- background: var(--sb-color-tabbar-background);
2327
- backdrop-filter: blur(20px) saturate(180%);
2328
- -webkit-backdrop-filter: blur(20px) saturate(180%);
2180
+ --sb-color-accent: var(--sb-color-primary);
2329
2181
  }
2330
2182
 
2331
- .sb-toolbar[data-sb-placement="bottom"]::before {
2332
- content: "";
2183
+ .sb-bottom-glass {
2333
2184
  position: absolute;
2334
- left: 0;
2335
- right: 0;
2336
- top: 0;
2337
- height: 1px;
2338
- background: var(--sb-color-separator);
2185
+ z-index: 2;
2186
+ border-radius: 24px;
2187
+ background: var(--sb-glass-background);
2188
+ box-shadow: var(--sb-glass-shadow);
2189
+ backdrop-filter: blur(12px) saturate(180%);
2190
+ -webkit-backdrop-filter: blur(12px) saturate(180%);
2339
2191
  pointer-events: none;
2340
2192
  }
2341
2193
 
2194
+ /* A ToolbarSpacer draws nothing (the engine also hides it). */
2195
+ .sb-toolbar[data-sb-spacer] {
2196
+ display: none;
2197
+ }
2198
+
2342
2199
  /* Search field: a capsule (engine `searchField` rect) with a
2343
2200
  magnifier leading; the input sends `text` events like a textfield. */
2344
2201
  .sb-searchfield {
@@ -2641,6 +2498,7 @@ svg[data-sb-symbol="progress.indicator"] > line:nth-child(8) { animation-delay:
2641
2498
  .sb-grid,
2642
2499
  .sb-gridrow,
2643
2500
  .sb-lazyvgrid,
2501
+ .sb-lazyhgrid,
2644
2502
  .sb-viewthatfits,
2645
2503
  .sb-decorated,
2646
2504
  .sb-gesture,
@@ -2648,8 +2506,8 @@ svg[data-sb-symbol="progress.indicator"] > line:nth-child(8) { animation-delay:
2648
2506
  pointer-events: none;
2649
2507
  }
2650
2508
 
2651
- :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)
2652
- > [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) {
2509
+ :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)
2510
+ > [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) {
2653
2511
  pointer-events: auto;
2654
2512
  }
2655
2513
 
@@ -3629,19 +3487,12 @@ svg.sb-path {
3629
3487
 
3630
3488
  /* ---------------------------------------------------------------------------
3631
3489
  Islands (@swiftbrowser/react's `SwiftUIView`): a SwiftUI root inside a
3632
- page's own layout, with the device's tokens and none of its frame. The
3633
- page sizes the island; in `content` fit the renderer sets the root's height
3490
+ page's own layout, with the device's tokens and none of its frame: the
3491
+ frame's size, bezel and shadow are `.device:not(.sb-island)`'s above, so
3492
+ the page's own CSS, however specific, gives the island its width and
3493
+ height. In `content` fit the renderer sets the root's height
3634
3494
  from the layout.
3635
3495
  ------------------------------------------------------------------------- */
3636
- .device.sb-island {
3637
- width: auto;
3638
- padding: 0;
3639
- border-radius: 0;
3640
- background: none;
3641
- box-shadow: none;
3642
- flex: initial;
3643
- }
3644
-
3645
3496
  .device.sb-island::before,
3646
3497
  .device.sb-island::after {
3647
3498
  display: none;
package/src/symbols.ts CHANGED
@@ -215,6 +215,7 @@ import {
215
215
  Link2,
216
216
  List,
217
217
  ListChecks,
218
+ ListFilter,
218
219
  ListIndentIncrease,
219
220
  ListMusic,
220
221
  ListOrdered,
@@ -571,7 +572,8 @@ export const SF_SYMBOLS: Readonly<Record<string, SymbolGlyph>> = {
571
572
  'tag.fill': solid(Tag),
572
573
  textformat: outline(CaseSensitive),
573
574
  'line.3.horizontal': outline(Menu),
574
- 'line.3.horizontal.decrease': outline(Funnel),
575
+ // Three lines, each shorter than the one above.
576
+ 'line.3.horizontal.decrease': outline(ListFilter),
575
577
  'line.3.horizontal.decrease.circle': outline(Funnel),
576
578
  'slider.horizontal.3': outline(SlidersHorizontal),
577
579
  'list.bullet': outline(List),