@swiftbrowser/web 0.18.3 → 0.21.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/app/index.html +11 -7
- package/package.json +1 -1
- package/src/appFiles.ts +12 -3
- package/src/audioEngine.ts +1467 -0
- package/src/chartDom.ts +1 -1
- package/src/deviceFrame.ts +188 -0
- package/src/dragDrop.ts +66 -10
- package/src/files.ts +68 -13
- package/src/hostRequests.ts +53 -3
- package/src/interaction.ts +6 -1
- package/src/languageModel.ts +309 -0
- package/src/layout/engine.ts +128 -48
- package/src/layoutDom.ts +43 -3
- package/src/main.ts +12 -1
- package/src/page.css +65 -0
- package/src/pinnedViews.ts +38 -1
- package/src/protocol.ts +70 -5
- package/src/renderer.ts +299 -53
- package/src/scrollTransition.ts +2 -1
- package/src/styles.css +374 -42
- package/src/styles.ts +13 -15
- package/src/symbols.ts +6 -0
- package/src/viewRegistry.ts +58 -0
package/src/styles.css
CHANGED
|
@@ -67,6 +67,9 @@
|
|
|
67
67
|
--sb-color-tertiary-system-background: #ffffff;
|
|
68
68
|
--sb-color-tertiary-system-grouped-background: #f2f2f7;
|
|
69
69
|
--sb-color-opaque-separator: #c6c6c8;
|
|
70
|
+
/* The stepper's capsule and the line between its halves. */
|
|
71
|
+
--sb-stepper-fill: var(--sb-color-tertiary-fill);
|
|
72
|
+
--sb-stepper-divider: var(--sb-color-opaque-separator);
|
|
70
73
|
--sb-color-system-gray2: #aeaeb2;
|
|
71
74
|
--sb-color-system-gray3: #c7c7cc;
|
|
72
75
|
--sb-color-system-gray4: #d1d1d6;
|
|
@@ -115,7 +118,7 @@
|
|
|
115
118
|
--sb-color-segment-selected: #ffffff;
|
|
116
119
|
/* Menus, floating sheets and alerts are Liquid Glass too: translucent
|
|
117
120
|
over a heavier backdrop blur, with the glass rim. */
|
|
118
|
-
--sb-color-menu-background: rgba(250, 250, 250, 0.
|
|
121
|
+
--sb-color-menu-background: rgba(250, 250, 250, 0.97);
|
|
119
122
|
--sb-sheet-glass: rgba(255, 255, 255, 0.55);
|
|
120
123
|
}
|
|
121
124
|
|
|
@@ -141,6 +144,11 @@
|
|
|
141
144
|
--sb-color-tertiary-system-background: #2c2c2e;
|
|
142
145
|
--sb-color-tertiary-system-grouped-background: #2c2c2e;
|
|
143
146
|
--sb-color-opaque-separator: #38383a;
|
|
147
|
+
/* iOS 26 in dark mode, measured: a fainter capsule than the tertiary fill
|
|
148
|
+
(rgba(120,120,128,.15) reads (18,18,19) over black) and a divider in the
|
|
149
|
+
tertiary label color ((83,83,86) over it). */
|
|
150
|
+
--sb-stepper-fill: rgba(120, 120, 128, 0.15);
|
|
151
|
+
--sb-stepper-divider: rgba(235, 235, 245, 0.3);
|
|
144
152
|
--sb-color-system-gray2: #636366;
|
|
145
153
|
--sb-color-system-gray3: #48484a;
|
|
146
154
|
--sb-color-system-gray4: #3a3a3c;
|
|
@@ -177,7 +185,7 @@
|
|
|
177
185
|
--sb-color-tabbar-background: rgba(29, 29, 29, 0.94);
|
|
178
186
|
--sb-color-segment-track: rgba(118, 118, 128, 0.24);
|
|
179
187
|
--sb-color-segment-selected: #5a5a5f;
|
|
180
|
-
--sb-color-menu-background: rgba(
|
|
188
|
+
--sb-color-menu-background: rgba(44, 44, 46, 0.97);
|
|
181
189
|
/* The iPhone's dark floating sheet reads 42 over black: dark gray glass. */
|
|
182
190
|
--sb-sheet-glass: rgba(60, 60, 60, 0.7);
|
|
183
191
|
}
|
|
@@ -318,12 +326,6 @@
|
|
|
318
326
|
fill: currentColor;
|
|
319
327
|
}
|
|
320
328
|
|
|
321
|
-
.glyph-wifi .wifi-band {
|
|
322
|
-
fill: none;
|
|
323
|
-
stroke: currentColor;
|
|
324
|
-
stroke-linecap: round;
|
|
325
|
-
}
|
|
326
|
-
|
|
327
329
|
.glyph-battery .battery-shell {
|
|
328
330
|
fill: none;
|
|
329
331
|
stroke: currentColor;
|
|
@@ -378,6 +380,86 @@
|
|
|
378
380
|
}
|
|
379
381
|
}
|
|
380
382
|
|
|
383
|
+
/* ---------------------------------------------------------------------------
|
|
384
|
+
The preview's device (html[data-sb-frame], src/deviceFrame.ts), frame mode
|
|
385
|
+
only: the iPhone above is the default; an iPad in landscape (an 11-inch
|
|
386
|
+
iPad's 1180 x 820 points, its status bar the time and date at the leading
|
|
387
|
+
edge, no island); or no device, the app filling the page under the toolbar
|
|
388
|
+
(page.css sizes the stage).
|
|
389
|
+
------------------------------------------------------------------------- */
|
|
390
|
+
|
|
391
|
+
html[data-sb-frame="ipad"]:not([data-sb-mode="device"]) {
|
|
392
|
+
--device-screen-width: 1180px;
|
|
393
|
+
--device-screen-height: 820px;
|
|
394
|
+
--device-bezel: 20px;
|
|
395
|
+
--device-radius: 38px;
|
|
396
|
+
--device-safe-top: 24px;
|
|
397
|
+
--device-safe-bottom: 20px;
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
html[data-sb-frame="ipad"]:not([data-sb-mode="device"]) .dynamic-island,
|
|
401
|
+
html[data-sb-frame="ipad"]:not([data-sb-mode="device"]) .device::before,
|
|
402
|
+
html[data-sb-frame="ipad"]:not([data-sb-mode="device"]) .device::after {
|
|
403
|
+
display: none;
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
html[data-sb-frame="ipad"]:not([data-sb-mode="device"]) .status-bar {
|
|
407
|
+
padding: 0 18px 0 22px;
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
html[data-sb-frame="ipad"]:not([data-sb-mode="device"]) .status-time {
|
|
411
|
+
min-width: 0;
|
|
412
|
+
top: 0;
|
|
413
|
+
font-size: 13px;
|
|
414
|
+
letter-spacing: 0;
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
.status-date {
|
|
418
|
+
display: none;
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
html[data-sb-frame="ipad"]:not([data-sb-mode="device"]) .status-date {
|
|
422
|
+
display: inline;
|
|
423
|
+
margin-left: 6px;
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
html[data-sb-frame="ipad"]:not([data-sb-mode="device"]) .status-glyphs {
|
|
427
|
+
gap: 6px;
|
|
428
|
+
transform: scale(0.82);
|
|
429
|
+
transform-origin: right center;
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
html[data-sb-frame="ipad"]:not([data-sb-mode="device"]) .home-indicator {
|
|
433
|
+
bottom: 7px;
|
|
434
|
+
width: 315px;
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
html[data-sb-frame="none"]:not([data-sb-mode="device"]) .device:not(.sb-island) {
|
|
438
|
+
flex: 1 1 auto;
|
|
439
|
+
align-self: stretch;
|
|
440
|
+
width: auto;
|
|
441
|
+
height: auto;
|
|
442
|
+
min-height: 0;
|
|
443
|
+
padding: 0;
|
|
444
|
+
border-radius: 0;
|
|
445
|
+
background: var(--sb-color-system-background);
|
|
446
|
+
box-shadow: none;
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
html[data-sb-frame="none"]:not([data-sb-mode="device"]) .device-screen {
|
|
450
|
+
width: 100%;
|
|
451
|
+
height: 100%;
|
|
452
|
+
border-radius: 0;
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
html[data-sb-frame="none"]:not([data-sb-mode="device"]) .device::before,
|
|
456
|
+
html[data-sb-frame="none"]:not([data-sb-mode="device"]) .device::after,
|
|
457
|
+
html[data-sb-frame="none"]:not([data-sb-mode="device"]) .dynamic-island,
|
|
458
|
+
html[data-sb-frame="none"]:not([data-sb-mode="device"]) .status-bar,
|
|
459
|
+
html[data-sb-frame="none"]:not([data-sb-mode="device"]) .home-indicator {
|
|
460
|
+
display: none;
|
|
461
|
+
}
|
|
462
|
+
|
|
381
463
|
/* ---------------------------------------------------------------------------
|
|
382
464
|
Device mode (html[data-sb-mode="device"], see src/deviceMode.ts)
|
|
383
465
|
|
|
@@ -417,6 +499,16 @@ html[data-sb-mode="device"] .device {
|
|
|
417
499
|
}
|
|
418
500
|
|
|
419
501
|
html[data-sb-mode="device"] .device::before,
|
|
502
|
+
/* `.statusBarHidden` on the screen showing: the drawn status bar fades, as
|
|
503
|
+
iOS fades the real one (the safe area stays, as it does under a notch). */
|
|
504
|
+
.status-bar {
|
|
505
|
+
transition: opacity 0.25s ease;
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
html[data-sb-status-hidden] .status-bar {
|
|
509
|
+
opacity: 0;
|
|
510
|
+
}
|
|
511
|
+
|
|
420
512
|
html[data-sb-mode="device"] .device::after,
|
|
421
513
|
html[data-sb-mode="device"] .dynamic-island,
|
|
422
514
|
html[data-sb-mode="device"] .status-bar,
|
|
@@ -585,6 +677,21 @@ html[data-sb-mode="device"] .device-screen {
|
|
|
585
677
|
user-select: none;
|
|
586
678
|
}
|
|
587
679
|
|
|
680
|
+
/* Text is not selectable, as on iOS, unless `.textSelection(.enabled)` is
|
|
681
|
+
on it or above it (the renderer marks that box). Fields and editors are
|
|
682
|
+
inputs, selectable as ever. */
|
|
683
|
+
.sb-text {
|
|
684
|
+
user-select: none;
|
|
685
|
+
-webkit-user-select: none;
|
|
686
|
+
}
|
|
687
|
+
|
|
688
|
+
[data-sb-text-selection] .sb-text,
|
|
689
|
+
.sb-text[data-sb-text-selection] {
|
|
690
|
+
user-select: text;
|
|
691
|
+
-webkit-user-select: text;
|
|
692
|
+
-webkit-touch-callout: default;
|
|
693
|
+
}
|
|
694
|
+
|
|
588
695
|
/* Image(systemName:): a 1em glyph centered in its line-height square; the
|
|
589
696
|
renderer sets font-size from the resolved font. */
|
|
590
697
|
.sb-image {
|
|
@@ -728,6 +835,24 @@ svg[data-sb-symbol="progress.indicator"] > line:nth-child(8) { animation-delay:
|
|
|
728
835
|
display: none;
|
|
729
836
|
}
|
|
730
837
|
|
|
838
|
+
/* Pinned section headers (src/pinnedViews.ts): each scroll view names its
|
|
839
|
+
scroll timelines, and a header moves along its nearest one, from where it
|
|
840
|
+
starts to pin (its `animation-range`) by as far as its section lets it
|
|
841
|
+
(`--sb-pinned-to`), in step with the scroll. */
|
|
842
|
+
.sb-scrollview,
|
|
843
|
+
.sb-list {
|
|
844
|
+
scroll-timeline: --sb-pin-y block, --sb-pin-x inline;
|
|
845
|
+
}
|
|
846
|
+
|
|
847
|
+
@keyframes sb-pinned {
|
|
848
|
+
from {
|
|
849
|
+
translate: 0 0;
|
|
850
|
+
}
|
|
851
|
+
to {
|
|
852
|
+
translate: var(--sb-pinned-to);
|
|
853
|
+
}
|
|
854
|
+
}
|
|
855
|
+
|
|
731
856
|
.sb-scrollview[data-sb-axes="vertical"] {
|
|
732
857
|
overflow-y: auto;
|
|
733
858
|
}
|
|
@@ -1010,6 +1135,31 @@ svg[data-sb-symbol="progress.indicator"] > line:nth-child(8) { animation-delay:
|
|
|
1010
1135
|
background: transparent;
|
|
1011
1136
|
}
|
|
1012
1137
|
|
|
1138
|
+
/* A card of clear rows alone paints nothing, so it clips nothing either: a
|
|
1139
|
+
search field drawn in such a row (Notes', Reminders', Files') keeps its own
|
|
1140
|
+
corners instead of the card's larger ones cutting them. */
|
|
1141
|
+
.sb-section-rows:not(:has(> [data-sb-id]:not([data-sb-row-background='clear']))) {
|
|
1142
|
+
overflow: visible;
|
|
1143
|
+
}
|
|
1144
|
+
|
|
1145
|
+
/* A section outside a list (a lazy stack's, its header pinned) is a plain
|
|
1146
|
+
container, as in SwiftUI: its rows draw in no card, so nothing clips them,
|
|
1147
|
+
paints under them or draws lines between them (src/layoutDom.ts leaves the
|
|
1148
|
+
slot unsized there). */
|
|
1149
|
+
:not(.sb-list-content) > .sb-section > .sb-section-rows {
|
|
1150
|
+
overflow: visible;
|
|
1151
|
+
background: none;
|
|
1152
|
+
border-radius: 0;
|
|
1153
|
+
}
|
|
1154
|
+
|
|
1155
|
+
:not(.sb-list-content) > .sb-section > .sb-section-rows > :is([data-sb-id], [data-sb-ghost]) {
|
|
1156
|
+
background: none;
|
|
1157
|
+
}
|
|
1158
|
+
|
|
1159
|
+
:not(.sb-list-content) > .sb-section > .sb-section-rows > *::after {
|
|
1160
|
+
content: none;
|
|
1161
|
+
}
|
|
1162
|
+
|
|
1013
1163
|
.sb-list-content > :not(.sb-section):first-child,
|
|
1014
1164
|
.sb-list-content > .sb-section + :not(.sb-section) {
|
|
1015
1165
|
border-top-left-radius: 26px;
|
|
@@ -1127,6 +1277,12 @@ svg[data-sb-symbol="progress.indicator"] > line:nth-child(8) { animation-delay:
|
|
|
1127
1277
|
background: var(--sb-color-grouped-background);
|
|
1128
1278
|
}
|
|
1129
1279
|
|
|
1280
|
+
/* So does a tab with a list as its content: the list is inset from the
|
|
1281
|
+
horizontal safe area (a phone on its side), the tab fills it. */
|
|
1282
|
+
.sb-tab:has(> .sb-list:not([data-sb-background="hidden"])) {
|
|
1283
|
+
background: var(--sb-color-grouped-background);
|
|
1284
|
+
}
|
|
1285
|
+
|
|
1130
1286
|
/* Screens below the top one slide partially left and are hidden once the
|
|
1131
1287
|
transition has finished. */
|
|
1132
1288
|
.sb-navscreen[data-sb-nav-position="below"] {
|
|
@@ -1220,6 +1376,19 @@ svg[data-sb-symbol="progress.indicator"] > line:nth-child(8) { animation-delay:
|
|
|
1220
1376
|
pointer-events: auto;
|
|
1221
1377
|
}
|
|
1222
1378
|
|
|
1379
|
+
/* `.toolbar(.hidden, for: .navigationBar)`: the bar fades where it was (its
|
|
1380
|
+
items are gone from the layout; the capsules play their exit). */
|
|
1381
|
+
.sb-navbar {
|
|
1382
|
+
transition:
|
|
1383
|
+
opacity 0.25s ease,
|
|
1384
|
+
visibility 0.25s;
|
|
1385
|
+
}
|
|
1386
|
+
|
|
1387
|
+
.sb-navscreen[data-sb-bar-hidden] > .sb-navbar {
|
|
1388
|
+
opacity: 0;
|
|
1389
|
+
visibility: hidden;
|
|
1390
|
+
}
|
|
1391
|
+
|
|
1223
1392
|
.sb-navbar-trailing:empty {
|
|
1224
1393
|
pointer-events: none;
|
|
1225
1394
|
}
|
|
@@ -1810,24 +1979,26 @@ svg[data-sb-symbol="progress.indicator"] > line:nth-child(8) { animation-delay:
|
|
|
1810
1979
|
background: var(--sb-color-system-background);
|
|
1811
1980
|
}
|
|
1812
1981
|
|
|
1813
|
-
/* `sidebar` (iPadOS; a NavigationSplitView's sidebar list)
|
|
1814
|
-
|
|
1815
|
-
|
|
1816
|
-
`insetGrouped` with
|
|
1817
|
-
.
|
|
1818
|
-
|
|
1819
|
-
.sb-list[data-sb-list-
|
|
1820
|
-
.sb-list[data-sb-list-
|
|
1982
|
+
/* `sidebar` (iPadOS; a NavigationSplitView's sidebar list) in a regular
|
|
1983
|
+
width (layoutDom's `data-sb-list-look`): the rows sit on the list's
|
|
1984
|
+
background, without cards or hairlines, and only a selected row is filled
|
|
1985
|
+
(its rounded fill, below). The engine lays it out like `insetGrouped` with
|
|
1986
|
+
44pt rows. In a compact width the list looks like `insetGrouped`; its
|
|
1987
|
+
sections still fold (above). */
|
|
1988
|
+
.sb-list[data-sb-list-look="sidebar"] .sb-section-rows,
|
|
1989
|
+
.sb-list[data-sb-list-look="sidebar"] .sb-section-rows > [data-sb-id],
|
|
1990
|
+
.sb-list[data-sb-list-look="sidebar"] .sb-section-rows > [data-sb-ghost],
|
|
1991
|
+
.sb-list[data-sb-list-look="sidebar"] > .sb-list-content > :not(.sb-section) {
|
|
1821
1992
|
background: transparent;
|
|
1822
1993
|
border-radius: 0;
|
|
1823
1994
|
}
|
|
1824
1995
|
|
|
1825
|
-
.sb-list[data-sb-list-
|
|
1826
|
-
.sb-list[data-sb-list-
|
|
1996
|
+
.sb-list[data-sb-list-look="sidebar"] .sb-section-rows > *::after,
|
|
1997
|
+
.sb-list[data-sb-list-look="sidebar"] > .sb-list-content > :not(.sb-section)::after {
|
|
1827
1998
|
display: none;
|
|
1828
1999
|
}
|
|
1829
2000
|
|
|
1830
|
-
.sb-list[data-sb-list-
|
|
2001
|
+
.sb-list[data-sb-list-look="sidebar"] :is(.sb-section-rows, .sb-list-content) > :is(input, textarea) {
|
|
1831
2002
|
background-image: none;
|
|
1832
2003
|
}
|
|
1833
2004
|
|
|
@@ -2118,13 +2289,17 @@ svg[data-sb-symbol="progress.indicator"] > line:nth-child(8) { animation-delay:
|
|
|
2118
2289
|
stroke-width: 2.5;
|
|
2119
2290
|
}
|
|
2120
2291
|
|
|
2121
|
-
/* The option menu
|
|
2292
|
+
/* The option menu, as iOS 26 draws one: an inset panel (10px above and
|
|
2293
|
+
below the rows, 12px beside them) with large rounded corners, anchored
|
|
2294
|
+
under (or above) the control by the renderer, over a toolbar button.
|
|
2122
2295
|
Closed, it fades out where it was (`.sb-picker-menu-exit`) and leaves. */
|
|
2123
2296
|
.sb-picker-menu,
|
|
2124
2297
|
.sb-picker-menu-exit {
|
|
2125
2298
|
position: absolute;
|
|
2126
2299
|
z-index: 6;
|
|
2127
|
-
|
|
2300
|
+
box-sizing: border-box;
|
|
2301
|
+
padding: 10px 12px;
|
|
2302
|
+
border-radius: 26px;
|
|
2128
2303
|
background: var(--sb-color-menu-background);
|
|
2129
2304
|
backdrop-filter: blur(40px) saturate(180%);
|
|
2130
2305
|
-webkit-backdrop-filter: blur(40px) saturate(180%);
|
|
@@ -2154,12 +2329,13 @@ svg[data-sb-symbol="progress.indicator"] > line:nth-child(8) { animation-delay:
|
|
|
2154
2329
|
appearance: none;
|
|
2155
2330
|
display: flex;
|
|
2156
2331
|
align-items: center;
|
|
2157
|
-
gap:
|
|
2332
|
+
gap: 13px;
|
|
2158
2333
|
width: 100%;
|
|
2159
2334
|
height: 44px;
|
|
2160
2335
|
margin: 0;
|
|
2161
2336
|
padding: 0 16px;
|
|
2162
2337
|
border: 0;
|
|
2338
|
+
border-radius: 14px;
|
|
2163
2339
|
background: none;
|
|
2164
2340
|
color: var(--sb-color-primary);
|
|
2165
2341
|
font: 400 var(--sb-font-size-body) / 1.3 var(--sb-font-family);
|
|
@@ -2169,19 +2345,19 @@ svg[data-sb-symbol="progress.indicator"] > line:nth-child(8) { animation-delay:
|
|
|
2169
2345
|
-webkit-tap-highlight-color: transparent;
|
|
2170
2346
|
}
|
|
2171
2347
|
|
|
2172
|
-
:is(.sb-picker-menu-item, .sb-picker-menu-static) + :is(.sb-picker-menu-item, .sb-picker-menu-static) {
|
|
2173
|
-
box-shadow: inset 0 1px 0 var(--sb-color-separator);
|
|
2174
|
-
}
|
|
2175
|
-
|
|
2176
2348
|
.sb-picker-menu-item:active {
|
|
2177
2349
|
background: var(--sb-color-fill);
|
|
2178
2350
|
}
|
|
2179
2351
|
|
|
2352
|
+
/* The checkmark's column (iOS 26): in a menu with checkable rows every item
|
|
2353
|
+
keeps it, empty unless checked, so titles and symbols line up; it sits
|
|
2354
|
+
closer to what follows than a symbol does. */
|
|
2180
2355
|
.sb-picker-menu-check {
|
|
2181
2356
|
display: inline-flex;
|
|
2182
2357
|
align-items: center;
|
|
2183
2358
|
justify-content: center;
|
|
2184
|
-
width:
|
|
2359
|
+
width: 16px;
|
|
2360
|
+
margin-right: -4px;
|
|
2185
2361
|
flex-shrink: 0;
|
|
2186
2362
|
visibility: hidden;
|
|
2187
2363
|
}
|
|
@@ -2191,8 +2367,8 @@ svg[data-sb-symbol="progress.indicator"] > line:nth-child(8) { animation-delay:
|
|
|
2191
2367
|
}
|
|
2192
2368
|
|
|
2193
2369
|
.sb-picker-menu-check > svg {
|
|
2194
|
-
width:
|
|
2195
|
-
height:
|
|
2370
|
+
width: 0.95em;
|
|
2371
|
+
height: 0.95em;
|
|
2196
2372
|
stroke: currentColor;
|
|
2197
2373
|
stroke-width: 2.5;
|
|
2198
2374
|
}
|
|
@@ -2204,6 +2380,47 @@ svg[data-sb-symbol="progress.indicator"] > line:nth-child(8) { animation-delay:
|
|
|
2204
2380
|
white-space: nowrap;
|
|
2205
2381
|
}
|
|
2206
2382
|
|
|
2383
|
+
/* A menu with a check column (iOS 26): the check sits 24px in from the
|
|
2384
|
+
panel's edge, plain titles and section titles start together after it, and
|
|
2385
|
+
a symbol's column starts a little past them, its title further in. */
|
|
2386
|
+
.sb-picker-menu[data-sb-checks] .sb-picker-menu-item {
|
|
2387
|
+
padding-left: 12px;
|
|
2388
|
+
}
|
|
2389
|
+
|
|
2390
|
+
.sb-picker-menu[data-sb-checks] .sb-picker-menu-check {
|
|
2391
|
+
margin-right: -5px;
|
|
2392
|
+
}
|
|
2393
|
+
|
|
2394
|
+
.sb-picker-menu[data-sb-checks] .sb-picker-menu-symbol {
|
|
2395
|
+
width: 28px;
|
|
2396
|
+
margin: 0 4px 0 2px;
|
|
2397
|
+
}
|
|
2398
|
+
|
|
2399
|
+
.sb-picker-menu[data-sb-checks] .sb-picker-menu-caption {
|
|
2400
|
+
padding-left: 36px;
|
|
2401
|
+
}
|
|
2402
|
+
|
|
2403
|
+
/* A title with a subtitle under it, in a taller row. */
|
|
2404
|
+
.sb-picker-menu-item[data-sb-subtitled] {
|
|
2405
|
+
height: 58px;
|
|
2406
|
+
}
|
|
2407
|
+
|
|
2408
|
+
.sb-picker-menu-titles {
|
|
2409
|
+
display: flex;
|
|
2410
|
+
flex: 1 1 auto;
|
|
2411
|
+
flex-direction: column;
|
|
2412
|
+
min-width: 0;
|
|
2413
|
+
}
|
|
2414
|
+
|
|
2415
|
+
.sb-picker-menu-subtitle {
|
|
2416
|
+
overflow: hidden;
|
|
2417
|
+
color: var(--sb-color-secondary);
|
|
2418
|
+
font-size: 15px;
|
|
2419
|
+
line-height: 20px;
|
|
2420
|
+
text-overflow: ellipsis;
|
|
2421
|
+
white-space: nowrap;
|
|
2422
|
+
}
|
|
2423
|
+
|
|
2207
2424
|
@keyframes sb-menu-in {
|
|
2208
2425
|
from {
|
|
2209
2426
|
opacity: 0;
|
|
@@ -2342,15 +2559,15 @@ svg[data-sb-symbol="progress.indicator"] > line:nth-child(8) { animation-delay:
|
|
|
2342
2559
|
border-radius: 6px;
|
|
2343
2560
|
}
|
|
2344
2561
|
|
|
2345
|
-
/* Popover rows for menu items: a tap row
|
|
2346
|
-
destructive role is red; a disabled item dims; section headers are
|
|
2347
|
-
captions; `text` items are static 44px rows; groups are split by
|
|
2562
|
+
/* Popover rows for menu items (iOS 26): a tap row's symbol leads its title;
|
|
2563
|
+
a destructive role is red; a disabled item dims; section headers are 30px
|
|
2564
|
+
captions; `text` items are static 44px rows; groups are split by a hairline
|
|
2565
|
+
with 9px above and below, inset from the panel's edges. No lines between items. */
|
|
2348
2566
|
.sb-picker-menu-symbol {
|
|
2349
2567
|
display: inline-flex;
|
|
2350
2568
|
align-items: center;
|
|
2351
2569
|
justify-content: center;
|
|
2352
|
-
width:
|
|
2353
|
-
margin-left: auto;
|
|
2570
|
+
width: 24px;
|
|
2354
2571
|
flex-shrink: 0;
|
|
2355
2572
|
}
|
|
2356
2573
|
|
|
@@ -2372,10 +2589,10 @@ svg[data-sb-symbol="progress.indicator"] > line:nth-child(8) { animation-delay:
|
|
|
2372
2589
|
.sb-picker-menu-caption {
|
|
2373
2590
|
display: flex;
|
|
2374
2591
|
align-items: center;
|
|
2375
|
-
height:
|
|
2592
|
+
height: 30px;
|
|
2376
2593
|
padding: 0 16px;
|
|
2377
2594
|
color: var(--sb-color-secondary);
|
|
2378
|
-
font: 400
|
|
2595
|
+
font: 400 15px/20px var(--sb-font-family);
|
|
2379
2596
|
white-space: nowrap;
|
|
2380
2597
|
overflow: hidden;
|
|
2381
2598
|
text-overflow: ellipsis;
|
|
@@ -2394,8 +2611,9 @@ svg[data-sb-symbol="progress.indicator"] > line:nth-child(8) { animation-delay:
|
|
|
2394
2611
|
}
|
|
2395
2612
|
|
|
2396
2613
|
.sb-picker-menu-separator {
|
|
2397
|
-
height:
|
|
2398
|
-
|
|
2614
|
+
height: 1px;
|
|
2615
|
+
margin: 9px 12px;
|
|
2616
|
+
background: var(--sb-color-separator);
|
|
2399
2617
|
}
|
|
2400
2618
|
|
|
2401
2619
|
/* Inline picker: a dim label row (when the label is non-empty) and 44px
|
|
@@ -3405,9 +3623,16 @@ svg[data-sb-symbol="progress.indicator"] > line:nth-child(8) { animation-delay:
|
|
|
3405
3623
|
opacity: 0.4;
|
|
3406
3624
|
}
|
|
3407
3625
|
|
|
3626
|
+
/* The copy lets the pointer through to the destination under it, its own
|
|
3627
|
+
boxes too: a background or a content shape makes a box a target
|
|
3628
|
+
(pointer-events: auto) wherever it is, and the copy keeps those. */
|
|
3629
|
+
.sb-drag-preview,
|
|
3630
|
+
.sb-drag-preview * {
|
|
3631
|
+
pointer-events: none !important;
|
|
3632
|
+
}
|
|
3633
|
+
|
|
3408
3634
|
.sb-drag-preview {
|
|
3409
3635
|
z-index: 60;
|
|
3410
|
-
pointer-events: none;
|
|
3411
3636
|
opacity: 0.9;
|
|
3412
3637
|
filter: drop-shadow(0 8px 20px rgba(0, 0, 0, 0.25));
|
|
3413
3638
|
will-change: transform;
|
|
@@ -3656,7 +3881,8 @@ svg[data-sb-symbol="progress.indicator"] > line:nth-child(8) { animation-delay:
|
|
|
3656
3881
|
.sb-alert-layer > [hidden],
|
|
3657
3882
|
.sb-datepicker > [hidden],
|
|
3658
3883
|
.sb-stepper > [hidden],
|
|
3659
|
-
.sb-slider > [hidden]
|
|
3884
|
+
.sb-slider > [hidden],
|
|
3885
|
+
.sb-slider-labels > [hidden] {
|
|
3660
3886
|
display: none;
|
|
3661
3887
|
}
|
|
3662
3888
|
|
|
@@ -3897,7 +4123,7 @@ svg[data-sb-symbol="progress.indicator"] > line:nth-child(8) { animation-delay:
|
|
|
3897
4123
|
display: flex;
|
|
3898
4124
|
box-sizing: border-box;
|
|
3899
4125
|
border-radius: 16px;
|
|
3900
|
-
background: var(--sb-
|
|
4126
|
+
background: var(--sb-stepper-fill);
|
|
3901
4127
|
overflow: hidden;
|
|
3902
4128
|
}
|
|
3903
4129
|
|
|
@@ -3930,7 +4156,7 @@ svg[data-sb-symbol="progress.indicator"] > line:nth-child(8) { animation-delay:
|
|
|
3930
4156
|
top: 7px;
|
|
3931
4157
|
bottom: 7px;
|
|
3932
4158
|
width: 1px;
|
|
3933
|
-
background: var(--sb-
|
|
4159
|
+
background: var(--sb-stepper-divider);
|
|
3934
4160
|
}
|
|
3935
4161
|
|
|
3936
4162
|
.sb-stepper-button > svg {
|
|
@@ -4213,3 +4439,109 @@ svg.sb-path {
|
|
|
4213
4439
|
.sb-styled[data-sb-hover] {
|
|
4214
4440
|
pointer-events: auto;
|
|
4215
4441
|
}
|
|
4442
|
+
|
|
4443
|
+
/* ---------------------------------------------------------------------------
|
|
4444
|
+
`disclosuregroup`: the label (the first child) with a chevron at its
|
|
4445
|
+
trailing edge that turns down as the content opens below it; a button
|
|
4446
|
+
over the label takes the tap (layoutDom places both from the label's
|
|
4447
|
+
frame). `section` headers of `Section(isExpanded:)` get the chevron too,
|
|
4448
|
+
in a sidebar-style list, where the section collapses as on iOS.
|
|
4449
|
+
--------------------------------------------------------------------------- */
|
|
4450
|
+
.sb-disclosure-toggle {
|
|
4451
|
+
position: absolute;
|
|
4452
|
+
margin: 0;
|
|
4453
|
+
padding: 0;
|
|
4454
|
+
border: 0;
|
|
4455
|
+
background: transparent;
|
|
4456
|
+
cursor: pointer;
|
|
4457
|
+
-webkit-tap-highlight-color: transparent;
|
|
4458
|
+
}
|
|
4459
|
+
|
|
4460
|
+
.sb-disclosure-chevron {
|
|
4461
|
+
position: absolute;
|
|
4462
|
+
stroke-width: 2.5;
|
|
4463
|
+
color: var(--sb-color-accent);
|
|
4464
|
+
pointer-events: none;
|
|
4465
|
+
transition: transform 0.25s ease-in-out;
|
|
4466
|
+
}
|
|
4467
|
+
|
|
4468
|
+
.sb-disclosuregroup[data-sb-expanded] > .sb-disclosure-chevron {
|
|
4469
|
+
transform: rotate(90deg);
|
|
4470
|
+
}
|
|
4471
|
+
|
|
4472
|
+
/* Collapsed: the content (a disclosure group's, a sidebar section's rows) is hidden by the engine, without a frame. */
|
|
4473
|
+
.sb-disclosuregroup > .sb-slot > [data-sb-id][hidden],
|
|
4474
|
+
.sb-disclosuregroup > .sb-slot > [data-sb-ghost][hidden],
|
|
4475
|
+
.sb-section[data-sb-collapsible] .sb-section-rows > [data-sb-id][hidden],
|
|
4476
|
+
.sb-section[data-sb-collapsible] .sb-section-rows > [data-sb-ghost][hidden] {
|
|
4477
|
+
display: none;
|
|
4478
|
+
}
|
|
4479
|
+
|
|
4480
|
+
.sb-list[data-sb-list-style='sidebar'] .sb-section[data-sb-collapsible] > .sb-section-header,
|
|
4481
|
+
.sb-list[data-sb-list-style='sidebar'] .sb-section[data-sb-collapsible] .sb-sectionheader {
|
|
4482
|
+
cursor: pointer;
|
|
4483
|
+
}
|
|
4484
|
+
|
|
4485
|
+
.sb-list[data-sb-list-style='sidebar'] .sb-section[data-sb-collapsible] > .sb-section-header::after,
|
|
4486
|
+
.sb-list[data-sb-list-style='sidebar'] .sb-section[data-sb-collapsible] .sb-sectionheader::after {
|
|
4487
|
+
/* Centered 10 in from the content's trailing edge, over the rows' own chevrons (the engine's 20pt chevron box). */
|
|
4488
|
+
content: '';
|
|
4489
|
+
position: absolute;
|
|
4490
|
+
right: 6.5px;
|
|
4491
|
+
top: 50%;
|
|
4492
|
+
width: 7px;
|
|
4493
|
+
height: 7px;
|
|
4494
|
+
margin-top: -5px;
|
|
4495
|
+
border-right: 2px solid var(--sb-color-accent);
|
|
4496
|
+
border-bottom: 2px solid var(--sb-color-accent);
|
|
4497
|
+
transform: rotate(-45deg);
|
|
4498
|
+
transition: transform 0.25s ease-in-out;
|
|
4499
|
+
}
|
|
4500
|
+
|
|
4501
|
+
.sb-list[data-sb-list-style='sidebar'] .sb-section[data-sb-collapsible][data-sb-expanded] > .sb-section-header::after,
|
|
4502
|
+
.sb-list[data-sb-list-style='sidebar'] .sb-section[data-sb-collapsible][data-sb-expanded] .sb-sectionheader::after {
|
|
4503
|
+
transform: rotate(45deg);
|
|
4504
|
+
}
|
|
4505
|
+
|
|
4506
|
+
/* A registered view (src/viewRegistry.ts): the package's own element fills
|
|
4507
|
+
the box; the element's children are a native view's to place. */
|
|
4508
|
+
.sb-registered {
|
|
4509
|
+
overflow: hidden;
|
|
4510
|
+
}
|
|
4511
|
+
.sb-registered > .sb-registered-content {
|
|
4512
|
+
display: block;
|
|
4513
|
+
width: 100%;
|
|
4514
|
+
height: 100%;
|
|
4515
|
+
}
|
|
4516
|
+
.sb-registered > :not(.sb-registered-content) {
|
|
4517
|
+
display: none;
|
|
4518
|
+
}
|
|
4519
|
+
.sb-registered-missing {
|
|
4520
|
+
box-sizing: border-box;
|
|
4521
|
+
border: 1px dashed currentColor;
|
|
4522
|
+
font: 11px/1.2 ui-monospace, monospace;
|
|
4523
|
+
opacity: 0.6;
|
|
4524
|
+
padding: 2px;
|
|
4525
|
+
}
|
|
4526
|
+
|
|
4527
|
+
/* SwiftUI's `.shadow` on a container falls on each view it draws, not on the
|
|
4528
|
+
container as a whole (renderer.ts castShadows): a view that draws itself
|
|
4529
|
+
casts the shadows of the boxes around it, set on them as
|
|
4530
|
+
`--sb-shadow-filter`, and hands none to what is inside it; nor does a
|
|
4531
|
+
`.compositingGroup()`, which casts them itself, as one. */
|
|
4532
|
+
:is([data-sb-kind="text"], [data-sb-kind="image"], [data-sb-kind="divider"], [data-sb-kind="color"], [data-sb-kind="shape"],
|
|
4533
|
+
[data-sb-kind="textfield"], [data-sb-kind="toggle"], [data-sb-kind="picker"], [data-sb-kind="gauge"],
|
|
4534
|
+
[data-sb-kind="searchfield"], [data-sb-kind="registered"], [data-sb-kind="chart"], [data-sb-kind="stepper"],
|
|
4535
|
+
[data-sb-kind="slider"], [data-sb-kind="datepicker"], [data-sb-kind="texteditor"], [data-sb-kind="video"],
|
|
4536
|
+
[data-sb-kind="canvas"]) {
|
|
4537
|
+
filter: var(--sb-shadow-filter, none);
|
|
4538
|
+
}
|
|
4539
|
+
|
|
4540
|
+
:is([data-sb-kind="text"], [data-sb-kind="image"], [data-sb-kind="divider"], [data-sb-kind="color"], [data-sb-kind="shape"],
|
|
4541
|
+
[data-sb-kind="textfield"], [data-sb-kind="toggle"], [data-sb-kind="picker"], [data-sb-kind="gauge"],
|
|
4542
|
+
[data-sb-kind="searchfield"], [data-sb-kind="registered"], [data-sb-kind="chart"], [data-sb-kind="stepper"],
|
|
4543
|
+
[data-sb-kind="slider"], [data-sb-kind="datepicker"], [data-sb-kind="texteditor"], [data-sb-kind="video"],
|
|
4544
|
+
[data-sb-kind="canvas"]) *,
|
|
4545
|
+
[data-sb-composite] > * {
|
|
4546
|
+
--sb-shadow-filter: initial;
|
|
4547
|
+
}
|
package/src/styles.ts
CHANGED
|
@@ -147,13 +147,6 @@ const HIERARCHICAL_TEXT: Record<1 | 2 | 3 | 4, string> = {
|
|
|
147
147
|
* colors' ratio has it).
|
|
148
148
|
*/
|
|
149
149
|
export const HIERARCHY_OPACITY: Record<1 | 2 | 3 | 4, number> = { 1: 1, 2: 0.5, 3: 0.25, 4: 0.18 };
|
|
150
|
-
const HIERARCHICAL_PAINT: Record<1 | 2 | 3 | 4, string> = {
|
|
151
|
-
1: 'var(--sb-color-fill)',
|
|
152
|
-
2: 'var(--sb-color-secondary-fill)',
|
|
153
|
-
3: 'var(--sb-color-tertiary-fill)',
|
|
154
|
-
4: 'var(--sb-color-quaternary-fill)',
|
|
155
|
-
};
|
|
156
|
-
|
|
157
150
|
/** Opacity of the system background each material shows over the blurred backdrop. */
|
|
158
151
|
export const MATERIAL_ALPHA: Record<Exclude<MaterialName, 'glass' | 'glassClear'>, number> = {
|
|
159
152
|
ultraThin: 0.55,
|
|
@@ -178,7 +171,7 @@ export function materialAlpha(name: MaterialName): number {
|
|
|
178
171
|
* stand out. A non-color style's top-level `opacity` multiplies the result
|
|
179
172
|
* (each stop of a gradient, the material's alpha) like a semantic color's `a`.
|
|
180
173
|
*/
|
|
181
|
-
export function styleToCSS(style: ShapeStyle,
|
|
174
|
+
export function styleToCSS(style: ShapeStyle, _role: PaintRole): PaintCSS {
|
|
182
175
|
if (isColor(style)) {
|
|
183
176
|
const value = colorToCSS(style);
|
|
184
177
|
return { kind: 'color', value, solid: value };
|
|
@@ -186,7 +179,9 @@ export function styleToCSS(style: ShapeStyle, role: PaintRole): PaintCSS {
|
|
|
186
179
|
const opacity = style.opacity;
|
|
187
180
|
if (isHierarchicalStyle(style)) {
|
|
188
181
|
const level = hierarchicalLevel(style.hierarchical);
|
|
189
|
-
|
|
182
|
+
// The levels of the foreground, as SwiftUI paints them in any role: a
|
|
183
|
+
// shape filled or stroked with `.primary` is the label color, not a fill gray.
|
|
184
|
+
const value = withOpacity(HIERARCHICAL_TEXT[level], opacity);
|
|
190
185
|
return { kind: 'color', value, solid: value };
|
|
191
186
|
}
|
|
192
187
|
if (isLinearGradientStyle(style)) {
|
|
@@ -357,7 +352,7 @@ export const PERSPECTIVE_BASE_PX = 500;
|
|
|
357
352
|
* `500px / perspective` (a smaller SwiftUI perspective is a flatter
|
|
358
353
|
* projection, as `perspective: 0.5` is in SwiftUI).
|
|
359
354
|
*/
|
|
360
|
-
export function effectsCSS(style: Style): EffectsCSS {
|
|
355
|
+
export function effectsCSS(style: Style, options: { shadow?: boolean } = {}): EffectsCSS {
|
|
361
356
|
const out: EffectsCSS = {};
|
|
362
357
|
const transforms: string[] = [];
|
|
363
358
|
if (style.offset) {
|
|
@@ -413,11 +408,7 @@ export function effectsCSS(style: Style): EffectsCSS {
|
|
|
413
408
|
filters.push(`hue-rotate(${round(style.hueRotation)}deg)`);
|
|
414
409
|
}
|
|
415
410
|
if (style.colorInvert === true) filters.push('invert(1)');
|
|
416
|
-
if (style.shadow) {
|
|
417
|
-
const { color, radius, x, y } = style.shadow;
|
|
418
|
-
const css = color ? colorToCSS(color) : 'rgba(0, 0, 0, 0.33)';
|
|
419
|
-
filters.push(`drop-shadow(${finite(x)}px ${finite(y)}px ${Math.max(0, finite(radius))}px ${css})`);
|
|
420
|
-
}
|
|
411
|
+
if (style.shadow && options.shadow !== false) filters.push(`drop-shadow(${shadowCSS(style.shadow)})`);
|
|
421
412
|
if (filters.length) out.filter = filters.join(' ');
|
|
422
413
|
// Phase 7.
|
|
423
414
|
if (style.hidden === true) out.hidden = true;
|
|
@@ -425,6 +416,13 @@ export function effectsCSS(style: Style): EffectsCSS {
|
|
|
425
416
|
return out;
|
|
426
417
|
}
|
|
427
418
|
|
|
419
|
+
/** A shadow's `x y radius color`, as `drop-shadow()` and `box-shadow` both take it. */
|
|
420
|
+
export function shadowCSS(shadow: NonNullable<Style['shadow']>): string {
|
|
421
|
+
const { color, radius, x, y } = shadow;
|
|
422
|
+
const css = color ? colorToCSS(color) : 'rgba(0, 0, 0, 0.33)';
|
|
423
|
+
return `${finite(x)}px ${finite(y)}px ${Math.max(0, finite(radius))}px ${css}`;
|
|
424
|
+
}
|
|
425
|
+
|
|
428
426
|
/**
|
|
429
427
|
* Phase 7 `.mask { }` with a ShapeStyle: a CSS `mask-image` whose alpha is
|
|
430
428
|
* the style's. A `linearGradient` / `radialGradient` becomes the matching CSS
|