@tellegen/svelte 0.1.1 → 0.1.2
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/dist/TellegenMap.svelte +106 -10
- package/dist/TellegenMap.svelte.d.ts.map +1 -1
- package/dist/TellegenShell.svelte +28 -3
- package/dist/TellegenShell.svelte.d.ts.map +1 -1
- package/dist/components/AppFooter.svelte +26 -1
- package/dist/components/AppFooter.svelte.d.ts +5 -17
- package/dist/components/AppFooter.svelte.d.ts.map +1 -1
- package/dist/components/AppHeader.svelte +59 -1
- package/dist/components/AppHeader.svelte.d.ts.map +1 -1
- package/dist/components/BindingLines.svelte +13 -0
- package/dist/components/BusPicker.svelte +40 -0
- package/dist/components/BusPicker.svelte.d.ts +4 -1
- package/dist/components/BusPicker.svelte.d.ts.map +1 -1
- package/dist/components/ControlPanel.svelte +359 -72
- package/dist/components/ControlPanel.svelte.d.ts +2 -17
- package/dist/components/ControlPanel.svelte.d.ts.map +1 -1
- package/dist/components/FormulationSelector.svelte +17 -0
- package/dist/components/SensitivityReadout.svelte +10 -1
- package/dist/components/SolveCard.svelte +2 -1
- package/dist/state.svelte.d.ts +13 -0
- package/dist/state.svelte.d.ts.map +1 -1
- package/dist/state.svelte.js +13 -0
- package/dist/styles.css +16 -2
- package/package.json +1 -1
package/dist/TellegenMap.svelte
CHANGED
|
@@ -495,11 +495,10 @@
|
|
|
495
495
|
* The branch variant also clears the rating readout on the panel's lower edge. */
|
|
496
496
|
function framePadding(width: number, height: number, kind: 'case' | 'branch') {
|
|
497
497
|
if (width <= 760) {
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
return { top: 130, left: 24, right: 24, bottom };
|
|
498
|
+
// bottom follows the sheet; capped so a fully open sheet still leaves a band to fit into
|
|
499
|
+
const clearance = kind === 'branch' ? 16 : 28;
|
|
500
|
+
const bottom = Math.min(app.sheetInset + clearance, Math.round(height * 0.55));
|
|
501
|
+
return { top: app.headerInset + 8, left: 24, right: 24, bottom };
|
|
503
502
|
}
|
|
504
503
|
return kind === 'branch'
|
|
505
504
|
? { top: 106, left: 388, right: 76, bottom: 80 }
|
|
@@ -552,6 +551,25 @@
|
|
|
552
551
|
);
|
|
553
552
|
}
|
|
554
553
|
|
|
554
|
+
function coarsePointer(): boolean {
|
|
555
|
+
return typeof window !== 'undefined' && window.matchMedia('(pointer: coarse)').matches;
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
// Compact chrome anchoring. maplibre's bottom-right controls sit over the map,
|
|
559
|
+
// so the sheet would bury them; they ride above it instead. Under
|
|
560
|
+
// CHROME_MIN_BAND_PX of map left they drop out rather than climb into the
|
|
561
|
+
// header — at that point the sheet covers what they annotate.
|
|
562
|
+
const CHROME_MIN_BAND_PX = 132;
|
|
563
|
+
|
|
564
|
+
/** How far the sheet can push the chrome up before it would reach the header. */
|
|
565
|
+
const chromeLiftCap = $derived(
|
|
566
|
+
Math.max(0, app.viewportHeight - app.headerInset - CHROME_MIN_BAND_PX)
|
|
567
|
+
);
|
|
568
|
+
const chromeInset = $derived(
|
|
569
|
+
app.compactLayout ? Math.min(app.sheetInset, chromeLiftCap) : 0
|
|
570
|
+
);
|
|
571
|
+
const chromeVisible = $derived(!app.compactLayout || app.sheetInset <= chromeLiftCap);
|
|
572
|
+
|
|
555
573
|
/** Fly to the selected branch; true when a camera move started. */
|
|
556
574
|
function focusSelectedBranch(): boolean {
|
|
557
575
|
if (!map || !selectedBranchCue) return false;
|
|
@@ -699,6 +717,7 @@
|
|
|
699
717
|
// deck layers also inherit maplibre's 4096 canvas clamp. The flat positron
|
|
700
718
|
// basemap writes no depth, so the overlay always draws on top.
|
|
701
719
|
function buildOverlay(MapboxOverlay: MapModules['MapboxOverlay']): MapboxOverlay {
|
|
720
|
+
const touch = coarsePointer();
|
|
702
721
|
return new MapboxOverlay({
|
|
703
722
|
interleaved: true,
|
|
704
723
|
layers: [],
|
|
@@ -706,10 +725,13 @@
|
|
|
706
725
|
depthWriteEnabled: false,
|
|
707
726
|
depthCompare: 'always'
|
|
708
727
|
},
|
|
728
|
+
// a small bus draws ~9px across, well under a fingertip
|
|
729
|
+
pickingRadius: touch ? 12 : 0,
|
|
709
730
|
onClick: (info: PickingInfo) => {
|
|
710
731
|
if (!app.placingId && !isSelectableLayer(info.layer?.id)) onmapclick();
|
|
711
732
|
},
|
|
712
|
-
|
|
733
|
+
// a touch tooltip opens under the finger and stays until the next tap
|
|
734
|
+
getTooltip: touch ? undefined : tooltip,
|
|
713
735
|
getCursor: ({ isHovering, isDragging }: CursorState) =>
|
|
714
736
|
app.placingId ? 'crosshair' : isDragging ? 'grabbing' : isHovering ? 'pointer' : 'grab'
|
|
715
737
|
});
|
|
@@ -1121,6 +1143,25 @@
|
|
|
1121
1143
|
void tick().then(scheduleCueSync);
|
|
1122
1144
|
});
|
|
1123
1145
|
|
|
1146
|
+
// Pan the selected bus into the band the sheet leaves. The delay outlasts the
|
|
1147
|
+
// sheet's snap transition; sheetInset is read inside the timeout so the effect
|
|
1148
|
+
// does not re-run (and re-pan) on every frame of a sheet drag.
|
|
1149
|
+
$effect(() => {
|
|
1150
|
+
const cue = selectedBusCue;
|
|
1151
|
+
if (!map || !cue || !app.compactLayout) return;
|
|
1152
|
+
void cue.key;
|
|
1153
|
+
const m = map;
|
|
1154
|
+
const timer = setTimeout(() => {
|
|
1155
|
+
const { clientHeight } = m.getContainer();
|
|
1156
|
+
const y = m.project([cue.lon, cue.lat]).y;
|
|
1157
|
+
const floor = clientHeight - app.sheetInset - CUE_MARGIN_PX;
|
|
1158
|
+
const ceiling = 104;
|
|
1159
|
+
const dy = y > floor ? y - floor : y < ceiling ? y - ceiling : 0;
|
|
1160
|
+
if (dy !== 0) m.panBy([0, dy], { duration: prefersReducedMotion() ? 0 : 320 });
|
|
1161
|
+
}, 340);
|
|
1162
|
+
return () => clearTimeout(timer);
|
|
1163
|
+
});
|
|
1164
|
+
|
|
1124
1165
|
function boundsFor(target: string): LngLatBoundsLike | null {
|
|
1125
1166
|
const points: [number, number][] = [];
|
|
1126
1167
|
const fold = (pts: { lon: number; lat: number }[]) => {
|
|
@@ -1176,7 +1217,11 @@
|
|
|
1176
1217
|
</script>
|
|
1177
1218
|
|
|
1178
1219
|
{#key mapGen}
|
|
1179
|
-
<div
|
|
1220
|
+
<div
|
|
1221
|
+
class="map-stage"
|
|
1222
|
+
class:chrome-clear={!chromeVisible}
|
|
1223
|
+
style="--chrome-inset: {chromeInset}px"
|
|
1224
|
+
>
|
|
1180
1225
|
<div class="map" {@attach initMap}></div>
|
|
1181
1226
|
{#if selectedBusCue}
|
|
1182
1227
|
{#key selectedBusCue.key}
|
|
@@ -1203,6 +1248,7 @@
|
|
|
1203
1248
|
<button
|
|
1204
1249
|
type="button"
|
|
1205
1250
|
class="place-at-center mono"
|
|
1251
|
+
style="--chrome-inset: {chromeInset}px"
|
|
1206
1252
|
onclick={() => {
|
|
1207
1253
|
if (!map) return;
|
|
1208
1254
|
const center = map.getCenter();
|
|
@@ -1386,7 +1432,7 @@
|
|
|
1386
1432
|
.place-at-center {
|
|
1387
1433
|
position: absolute;
|
|
1388
1434
|
left: 50%;
|
|
1389
|
-
bottom: 92px;
|
|
1435
|
+
bottom: calc(92px + var(--chrome-inset, 0px));
|
|
1390
1436
|
z-index: 15;
|
|
1391
1437
|
transform: translateX(-50%);
|
|
1392
1438
|
padding: 7px 14px;
|
|
@@ -1401,17 +1447,27 @@
|
|
|
1401
1447
|
|
|
1402
1448
|
@media (max-width: 420px) {
|
|
1403
1449
|
.place-at-center {
|
|
1404
|
-
bottom: 78px;
|
|
1450
|
+
bottom: calc(78px + var(--chrome-inset, 0px));
|
|
1405
1451
|
}
|
|
1406
1452
|
}
|
|
1407
1453
|
|
|
1408
1454
|
.map :global(.maplibregl-ctrl-bottom-right) {
|
|
1409
1455
|
right: 20px;
|
|
1410
|
-
bottom
|
|
1456
|
+
/* rides above the bottom sheet so attribution stays legible */
|
|
1457
|
+
bottom: calc(18px + var(--chrome-inset, 0px));
|
|
1411
1458
|
display: flex;
|
|
1412
1459
|
flex-direction: column;
|
|
1413
1460
|
align-items: flex-end;
|
|
1414
1461
|
gap: 6px;
|
|
1462
|
+
transition:
|
|
1463
|
+
bottom var(--dur-med) var(--ease-out),
|
|
1464
|
+
opacity var(--dur-fast) var(--ease-out);
|
|
1465
|
+
}
|
|
1466
|
+
|
|
1467
|
+
/* The sheet has taken the map; nothing left down there to attribute. */
|
|
1468
|
+
.map-stage.chrome-clear :global(.maplibregl-ctrl-bottom-right) {
|
|
1469
|
+
opacity: 0;
|
|
1470
|
+
pointer-events: none;
|
|
1415
1471
|
}
|
|
1416
1472
|
|
|
1417
1473
|
.map :global(.maplibregl-ctrl-bottom-right .maplibregl-ctrl) {
|
|
@@ -1439,6 +1495,26 @@
|
|
|
1439
1495
|
color: var(--text-secondary);
|
|
1440
1496
|
}
|
|
1441
1497
|
|
|
1498
|
+
@media (hover: none), (pointer: coarse) {
|
|
1499
|
+
/* A long press on the canvas must not raise the iOS callout or magnifier.
|
|
1500
|
+
Scoped to touch so a mouse can still select the attribution text. */
|
|
1501
|
+
.map {
|
|
1502
|
+
-webkit-touch-callout: none;
|
|
1503
|
+
-webkit-tap-highlight-color: transparent;
|
|
1504
|
+
user-select: none;
|
|
1505
|
+
}
|
|
1506
|
+
|
|
1507
|
+
/* maplibre ships 29px zoom buttons; a fingertip wants 44. */
|
|
1508
|
+
.map :global(.maplibregl-ctrl-group button) {
|
|
1509
|
+
width: 44px;
|
|
1510
|
+
height: 44px;
|
|
1511
|
+
}
|
|
1512
|
+
|
|
1513
|
+
.map :global(.maplibregl-ctrl-group button .maplibregl-ctrl-icon) {
|
|
1514
|
+
background-size: 22px 22px;
|
|
1515
|
+
}
|
|
1516
|
+
}
|
|
1517
|
+
|
|
1442
1518
|
.map :global(.deck-tooltip) {
|
|
1443
1519
|
background: var(--panel) !important;
|
|
1444
1520
|
border: 1px solid var(--line);
|
|
@@ -1453,11 +1529,31 @@
|
|
|
1453
1529
|
|
|
1454
1530
|
@media (max-width: 760px) {
|
|
1455
1531
|
.map :global(.maplibregl-ctrl-attrib) {
|
|
1532
|
+
/* last in the column so it rests on the sheet's edge instead of
|
|
1533
|
+
floating over the network a zoom control's height above it */
|
|
1534
|
+
order: 1;
|
|
1456
1535
|
width: auto;
|
|
1457
1536
|
min-width: 0;
|
|
1458
1537
|
right: 0;
|
|
1459
1538
|
white-space: normal;
|
|
1460
1539
|
}
|
|
1540
|
+
|
|
1541
|
+
/* The band of map a phone has left between the header and the sheet is
|
|
1542
|
+
about the height of the solve card, which covers the buttons wherever
|
|
1543
|
+
they land. Pinch and double tap zoom; the attribution stays. */
|
|
1544
|
+
.map :global(.maplibregl-ctrl-bottom-right .maplibregl-ctrl-group) {
|
|
1545
|
+
display: none;
|
|
1546
|
+
}
|
|
1547
|
+
}
|
|
1548
|
+
|
|
1549
|
+
/* One line on a small phone: wrapped attribution turns into a wide block
|
|
1550
|
+
sitting over the network. */
|
|
1551
|
+
@media (max-width: 460px) {
|
|
1552
|
+
.map :global(.maplibregl-ctrl-attrib) {
|
|
1553
|
+
max-width: calc(100vw - 20px);
|
|
1554
|
+
font-size: 9px;
|
|
1555
|
+
white-space: nowrap;
|
|
1556
|
+
}
|
|
1461
1557
|
}
|
|
1462
1558
|
|
|
1463
1559
|
@media (prefers-reduced-motion: reduce) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"TellegenMap.svelte.d.ts","sourceRoot":"","sources":["../src/lib/TellegenMap.svelte.ts"],"names":[],"mappings":"AAGA,OAAO,kCAAkC,CAAC;AAoEzC,KAAK,gBAAgB,GAAI;IACxB,UAAU,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IACpD,eAAe,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IACzD,aAAa,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,KAAK,IAAI,CAAC;IAC3D,kBAAkB,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,KAAK,IAAI,CAAC;IAChE,eAAe,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IACzD,gBAAgB,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,KAAK,IAAI,CAAC;IAC5D,WAAW,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;IAChD,UAAU,EAAE,MAAM,IAAI,CAAC;CACvB,CAAC;
|
|
1
|
+
{"version":3,"file":"TellegenMap.svelte.d.ts","sourceRoot":"","sources":["../src/lib/TellegenMap.svelte.ts"],"names":[],"mappings":"AAGA,OAAO,kCAAkC,CAAC;AAoEzC,KAAK,gBAAgB,GAAI;IACxB,UAAU,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IACpD,eAAe,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IACzD,aAAa,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,KAAK,IAAI,CAAC;IAC3D,kBAAkB,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,KAAK,IAAI,CAAC;IAChE,eAAe,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IACzD,gBAAgB,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,KAAK,IAAI,CAAC;IAC5D,WAAW,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;IAChD,UAAU,EAAE,MAAM,IAAI,CAAC;CACvB,CAAC;AA+qCH,QAAA,MAAM,WAAW,sDAAwC,CAAC;AAC1D,KAAK,WAAW,GAAG,UAAU,CAAC,OAAO,WAAW,CAAC,CAAC;AAClD,eAAe,WAAW,CAAC"}
|
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
import TellegenMap from './TellegenMap.svelte';
|
|
13
13
|
|
|
14
14
|
const FILE_DROP_QUERY = '(hover: hover) and (pointer: fine) and (min-width: 761px)';
|
|
15
|
+
const COMPACT_QUERY = '(max-width: 760px)';
|
|
15
16
|
|
|
16
17
|
const app = getAppState();
|
|
17
18
|
const ctrl = getController();
|
|
@@ -50,7 +51,28 @@
|
|
|
50
51
|
};
|
|
51
52
|
syncFileDropUi();
|
|
52
53
|
query.addEventListener('change', syncFileDropUi);
|
|
53
|
-
|
|
54
|
+
|
|
55
|
+
const compact = window.matchMedia(COMPACT_QUERY);
|
|
56
|
+
const syncCompact = () => {
|
|
57
|
+
app.compactLayout = compact.matches;
|
|
58
|
+
if (!compact.matches) app.sheetInset = 0;
|
|
59
|
+
};
|
|
60
|
+
syncCompact();
|
|
61
|
+
compact.addEventListener('change', syncCompact);
|
|
62
|
+
|
|
63
|
+
// The sheet sizes its snaps against the viewport and the map caps how far
|
|
64
|
+
// it lifts the basemap chrome, so both read one measurement taken here.
|
|
65
|
+
const syncViewport = () => (app.viewportHeight = window.innerHeight);
|
|
66
|
+
syncViewport();
|
|
67
|
+
window.addEventListener('resize', syncViewport);
|
|
68
|
+
window.addEventListener('orientationchange', syncViewport);
|
|
69
|
+
|
|
70
|
+
return () => {
|
|
71
|
+
query.removeEventListener('change', syncFileDropUi);
|
|
72
|
+
compact.removeEventListener('change', syncCompact);
|
|
73
|
+
window.removeEventListener('resize', syncViewport);
|
|
74
|
+
window.removeEventListener('orientationchange', syncViewport);
|
|
75
|
+
};
|
|
54
76
|
});
|
|
55
77
|
|
|
56
78
|
function dragHasFiles(e: DragEvent): boolean {
|
|
@@ -109,11 +131,14 @@
|
|
|
109
131
|
<AppHeader />
|
|
110
132
|
<ControlPanel />
|
|
111
133
|
<SolveCard />
|
|
112
|
-
|
|
134
|
+
<!-- ControlPanel mounts the lookup and the footer inline when compact. -->
|
|
135
|
+
{#if !app.compactLayout}
|
|
136
|
+
<BusPicker />
|
|
137
|
+
{/if}
|
|
113
138
|
<DropZone />
|
|
114
139
|
<PlacementCue />
|
|
115
140
|
<RestoreDefaultsButton />
|
|
116
|
-
{#if config.showFooter}
|
|
141
|
+
{#if config.showFooter && !app.compactLayout}
|
|
117
142
|
<AppFooter />
|
|
118
143
|
{/if}
|
|
119
144
|
</main>
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"TellegenShell.svelte.d.ts","sourceRoot":"","sources":["../src/lib/TellegenShell.svelte.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"TellegenShell.svelte.d.ts","sourceRoot":"","sources":["../src/lib/TellegenShell.svelte.ts"],"names":[],"mappings":"AAqJA,QAAA,MAAM,aAAa,2DAAwC,CAAC;AAC5D,KAAK,aAAa,GAAG,UAAU,CAAC,OAAO,aAAa,CAAC,CAAC;AACtD,eAAe,aAAa,CAAC"}
|
|
@@ -2,9 +2,12 @@
|
|
|
2
2
|
import { getController } from '../context.svelte.js';
|
|
3
3
|
|
|
4
4
|
const ctrl = getController();
|
|
5
|
+
|
|
6
|
+
/** Render in the parent's flow instead of pinned to the bottom of the map. */
|
|
7
|
+
let { inline = false }: { inline?: boolean } = $props();
|
|
5
8
|
</script>
|
|
6
9
|
|
|
7
|
-
<footer class="mono">
|
|
10
|
+
<footer class="mono" class:inline>
|
|
8
11
|
<a href="/credits/">credits</a>
|
|
9
12
|
<i class="sep"></i>
|
|
10
13
|
<a href="/privacy/">privacy</a>
|
|
@@ -50,6 +53,28 @@
|
|
|
50
53
|
color: var(--text-accent);
|
|
51
54
|
}
|
|
52
55
|
|
|
56
|
+
footer.inline {
|
|
57
|
+
position: static;
|
|
58
|
+
margin-top: 14px;
|
|
59
|
+
padding: 0;
|
|
60
|
+
background: none;
|
|
61
|
+
animation: none;
|
|
62
|
+
pointer-events: auto;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
@media (hover: none), (pointer: coarse) {
|
|
66
|
+
footer a {
|
|
67
|
+
display: inline-flex;
|
|
68
|
+
align-items: center;
|
|
69
|
+
min-height: 44px;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
footer.inline {
|
|
73
|
+
margin-top: 4px;
|
|
74
|
+
font-size: 11px;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
53
78
|
@media (max-width: 760px) {
|
|
54
79
|
footer {
|
|
55
80
|
padding: 7px 10px;
|
|
@@ -1,19 +1,7 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
$$events?: Events;
|
|
7
|
-
$$slots?: Slots;
|
|
8
|
-
}): Exports & {
|
|
9
|
-
$set?: any;
|
|
10
|
-
$on?: any;
|
|
11
|
-
};
|
|
12
|
-
z_$$bindings?: Bindings;
|
|
13
|
-
}
|
|
14
|
-
declare const AppFooter: $$__sveltets_2_IsomorphicComponent<Record<string, never>, {
|
|
15
|
-
[evt: string]: CustomEvent<any>;
|
|
16
|
-
}, {}, {}, string>;
|
|
17
|
-
type AppFooter = InstanceType<typeof AppFooter>;
|
|
1
|
+
type $$ComponentProps = {
|
|
2
|
+
inline?: boolean;
|
|
3
|
+
};
|
|
4
|
+
declare const AppFooter: import("svelte").Component<$$ComponentProps, {}, "">;
|
|
5
|
+
type AppFooter = ReturnType<typeof AppFooter>;
|
|
18
6
|
export default AppFooter;
|
|
19
7
|
//# sourceMappingURL=AppFooter.svelte.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AppFooter.svelte.d.ts","sourceRoot":"","sources":["../../src/lib/components/AppFooter.svelte.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"AppFooter.svelte.d.ts","sourceRoot":"","sources":["../../src/lib/components/AppFooter.svelte.ts"],"names":[],"mappings":"AAKC,KAAK,gBAAgB,GAAI;IAAE,MAAM,CAAC,EAAE,OAAO,CAAA;CAAE,CAAC;AAwB/C,QAAA,MAAM,SAAS,sDAAwC,CAAC;AACxD,KAAK,SAAS,GAAG,UAAU,CAAC,OAAO,SAAS,CAAC,CAAC;AAC9C,eAAe,SAAS,CAAC"}
|
|
@@ -7,9 +7,23 @@
|
|
|
7
7
|
const config = getUiConfig();
|
|
8
8
|
|
|
9
9
|
let fileInput = $state.raw<HTMLInputElement | undefined>(undefined);
|
|
10
|
+
let headerEl = $state.raw<HTMLElement | undefined>(undefined);
|
|
11
|
+
|
|
12
|
+
// The case tabs wrap onto their own row on a narrow viewport and unwrap on a
|
|
13
|
+
// short one, so the height moves with both the breakpoint and the case list.
|
|
14
|
+
// Publish it for the map chrome and the sheet, which both anchor below it.
|
|
15
|
+
$effect(() => {
|
|
16
|
+
const el = headerEl;
|
|
17
|
+
if (!el) return;
|
|
18
|
+
const measure = () => (app.headerInset = Math.round(el.getBoundingClientRect().height));
|
|
19
|
+
measure();
|
|
20
|
+
const observer = new ResizeObserver(measure);
|
|
21
|
+
observer.observe(el);
|
|
22
|
+
return () => observer.disconnect();
|
|
23
|
+
});
|
|
10
24
|
</script>
|
|
11
25
|
|
|
12
|
-
<header>
|
|
26
|
+
<header bind:this={headerEl}>
|
|
13
27
|
<div class="brand">
|
|
14
28
|
<svg viewBox="0 0 24 24" width="20" height="20" aria-hidden="true">
|
|
15
29
|
<path d="M4 18 L12 6 L20 18" stroke="#b25e00" stroke-width="1.6" fill="none" />
|
|
@@ -397,6 +411,50 @@
|
|
|
397
411
|
}
|
|
398
412
|
}
|
|
399
413
|
|
|
414
|
+
/* Landscape phone: height is the scarce axis. Keep the case tabs on the
|
|
415
|
+
brand's row instead of giving them one of their own, which halves the
|
|
416
|
+
header and leaves the map a band worth looking at. */
|
|
417
|
+
@media (max-width: 760px) and (max-height: 520px) {
|
|
418
|
+
header {
|
|
419
|
+
flex-wrap: nowrap;
|
|
420
|
+
align-items: center;
|
|
421
|
+
padding: 6px 10px;
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
h1 {
|
|
425
|
+
font-size: 17px;
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
.cases {
|
|
429
|
+
order: 0;
|
|
430
|
+
width: auto;
|
|
431
|
+
justify-content: flex-start;
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
.kicker {
|
|
435
|
+
display: none;
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
/* Touch sizing keys off the pointer, not the breakpoint: a tablet gets the
|
|
440
|
+
wide layout and still needs a fingertip's worth of chip. */
|
|
441
|
+
@media (hover: none), (pointer: coarse) {
|
|
442
|
+
.cases > button,
|
|
443
|
+
.case-chip {
|
|
444
|
+
min-height: 44px;
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
.case-activate {
|
|
448
|
+
min-height: 44px;
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
.kicker a {
|
|
452
|
+
display: inline-flex;
|
|
453
|
+
align-items: center;
|
|
454
|
+
min-height: 44px;
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
|
|
400
458
|
@media (prefers-reduced-motion: reduce) {
|
|
401
459
|
header {
|
|
402
460
|
animation: none;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AppHeader.svelte.d.ts","sourceRoot":"","sources":["../../src/lib/components/AppHeader.svelte.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"AppHeader.svelte.d.ts","sourceRoot":"","sources":["../../src/lib/components/AppHeader.svelte.ts"],"names":[],"mappings":"AA6FA,QAAA,MAAM,SAAS,2DAAwC,CAAC;AACxD,KAAK,SAAS,GAAG,UAAU,CAAC,OAAO,SAAS,CAAC,CAAC;AAC9C,eAAe,SAAS,CAAC"}
|
|
@@ -98,4 +98,17 @@
|
|
|
98
98
|
box-shadow: inset 3px 0 0 var(--accent);
|
|
99
99
|
font-weight: 600;
|
|
100
100
|
}
|
|
101
|
+
|
|
102
|
+
/* Each row is a tap target that reframes the map, so give it a finger's worth
|
|
103
|
+
of height and show a couple more rows before the list scrolls. */
|
|
104
|
+
@media (hover: none), (pointer: coarse) {
|
|
105
|
+
ul {
|
|
106
|
+
max-height: 176px;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
li button {
|
|
110
|
+
min-height: 44px;
|
|
111
|
+
padding-block: 6px;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
101
114
|
</style>
|
|
@@ -5,6 +5,9 @@
|
|
|
5
5
|
const app = getAppState();
|
|
6
6
|
const ctrl = getController();
|
|
7
7
|
|
|
8
|
+
/** Render in the parent's flow instead of as a card floating over the map. */
|
|
9
|
+
let { inline = false }: { inline?: boolean } = $props();
|
|
10
|
+
|
|
8
11
|
const buses = $derived(app.active?.network?.buses ?? app.activeLocal?.view?.buses ?? []);
|
|
9
12
|
|
|
10
13
|
let query = $state('');
|
|
@@ -27,6 +30,11 @@
|
|
|
27
30
|
query = '';
|
|
28
31
|
open = false;
|
|
29
32
|
highlighted = 0;
|
|
33
|
+
// Drop the on-screen keyboard, which otherwise covers the readout the
|
|
34
|
+
// selection just produced. A keyboard user keeps focus for the next lookup.
|
|
35
|
+
if (typeof window !== 'undefined' && window.matchMedia('(pointer: coarse)').matches) {
|
|
36
|
+
inputEl?.blur();
|
|
37
|
+
}
|
|
30
38
|
}
|
|
31
39
|
|
|
32
40
|
function onKeydown(e: KeyboardEvent) {
|
|
@@ -69,6 +77,7 @@
|
|
|
69
77
|
{#if buses.length > 0}
|
|
70
78
|
<div
|
|
71
79
|
class="bus-lookup"
|
|
80
|
+
class:inline
|
|
72
81
|
title="Press / to focus bus lookup"
|
|
73
82
|
onfocusout={(e) => {
|
|
74
83
|
if (!e.currentTarget.contains(e.relatedTarget as Node | null)) open = false;
|
|
@@ -160,6 +169,37 @@
|
|
|
160
169
|
outline-offset: 1px;
|
|
161
170
|
}
|
|
162
171
|
|
|
172
|
+
/* Safari zooms the layout viewport when a focused input is under 16px. */
|
|
173
|
+
@media (hover: none), (pointer: coarse) {
|
|
174
|
+
input {
|
|
175
|
+
min-height: 44px;
|
|
176
|
+
font-size: 16px;
|
|
177
|
+
padding: 7px 10px;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
li {
|
|
181
|
+
display: flex;
|
|
182
|
+
align-items: center;
|
|
183
|
+
min-height: 44px;
|
|
184
|
+
font-size: 14px;
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
.bus-lookup.inline {
|
|
189
|
+
/* relative, not static: the results list is absolute against this block */
|
|
190
|
+
position: relative;
|
|
191
|
+
inset: auto;
|
|
192
|
+
width: auto;
|
|
193
|
+
margin-top: 12px;
|
|
194
|
+
padding: 0;
|
|
195
|
+
background: none;
|
|
196
|
+
border: 0;
|
|
197
|
+
border-radius: 0;
|
|
198
|
+
backdrop-filter: none;
|
|
199
|
+
box-shadow: none;
|
|
200
|
+
animation: none;
|
|
201
|
+
}
|
|
202
|
+
|
|
163
203
|
ul {
|
|
164
204
|
position: absolute;
|
|
165
205
|
z-index: 20;
|
|
@@ -1,4 +1,7 @@
|
|
|
1
|
-
|
|
1
|
+
type $$ComponentProps = {
|
|
2
|
+
inline?: boolean;
|
|
3
|
+
};
|
|
4
|
+
declare const BusPicker: import("svelte").Component<$$ComponentProps, {}, "">;
|
|
2
5
|
type BusPicker = ReturnType<typeof BusPicker>;
|
|
3
6
|
export default BusPicker;
|
|
4
7
|
//# sourceMappingURL=BusPicker.svelte.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"BusPicker.svelte.d.ts","sourceRoot":"","sources":["../../src/lib/components/BusPicker.svelte.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"BusPicker.svelte.d.ts","sourceRoot":"","sources":["../../src/lib/components/BusPicker.svelte.ts"],"names":[],"mappings":"AAMC,KAAK,gBAAgB,GAAI;IAAE,MAAM,CAAC,EAAE,OAAO,CAAA;CAAE,CAAC;AA6G/C,QAAA,MAAM,SAAS,sDAAwC,CAAC;AACxD,KAAK,SAAS,GAAG,UAAU,CAAC,OAAO,SAAS,CAAC,CAAC;AAC9C,eAAe,SAAS,CAAC"}
|
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
+
import { untrack } from 'svelte';
|
|
2
3
|
import { getAppState, getController, getUiConfig } from '../context.svelte.js';
|
|
4
|
+
import { splitName } from '../format.js';
|
|
5
|
+
import AppFooter from './AppFooter.svelte';
|
|
3
6
|
import BindingLines from './BindingLines.svelte';
|
|
7
|
+
import BusPicker from './BusPicker.svelte';
|
|
4
8
|
import DemandSlider from './DemandSlider.svelte';
|
|
5
9
|
import DisplayControls from './DisplayControls.svelte';
|
|
6
10
|
import FormulationSelector from './FormulationSelector.svelte';
|
|
@@ -15,81 +19,297 @@
|
|
|
15
19
|
const app = getAppState();
|
|
16
20
|
const ctrl = getController();
|
|
17
21
|
const config = getUiConfig();
|
|
22
|
+
|
|
23
|
+
// Bottom sheet heights, compact layout only. peek is measured from the grab
|
|
24
|
+
// bar; half and full are fractions of the viewport.
|
|
25
|
+
type Snap = 'peek' | 'half' | 'full';
|
|
26
|
+
const SNAPS: Snap[] = ['peek', 'half', 'full'];
|
|
27
|
+
const SNAP_FRACTION: Record<Snap, number> = { peek: 0, half: 0.46, full: 0.92 };
|
|
28
|
+
/** Below this the viewport is a landscape phone and half has to give up room. */
|
|
29
|
+
const SHORT_VIEWPORT_PX = 560;
|
|
30
|
+
/** px/ms above which a release snaps in the drag direction instead of to the nearest. */
|
|
31
|
+
const FLICK_SPEED = 0.35;
|
|
32
|
+
/** Map left visible above a full sheet, on top of the header it also clears. */
|
|
33
|
+
const FULL_SNAP_MAP_BAND = 14;
|
|
34
|
+
|
|
35
|
+
let snap = $state<Snap>('half');
|
|
36
|
+
let headEl = $state.raw<HTMLElement | undefined>(undefined);
|
|
37
|
+
let bodyEl = $state.raw<HTMLElement | undefined>(undefined);
|
|
38
|
+
let headHeight = $state(52);
|
|
39
|
+
/** Height under an active drag; null when resting on a snap. */
|
|
40
|
+
let dragHeight = $state<number | null>(null);
|
|
41
|
+
|
|
42
|
+
const fractionFor = (s: Snap) =>
|
|
43
|
+
s === 'half' && app.viewportHeight < SHORT_VIEWPORT_PX ? 0.38 : SNAP_FRACTION[s];
|
|
44
|
+
|
|
45
|
+
const snapHeight = (s: Snap) =>
|
|
46
|
+
s === 'peek'
|
|
47
|
+
? headHeight
|
|
48
|
+
: Math.min(
|
|
49
|
+
Math.round(app.viewportHeight * fractionFor(s)),
|
|
50
|
+
app.viewportHeight - app.headerInset - FULL_SNAP_MAP_BAND
|
|
51
|
+
);
|
|
52
|
+
const sheetHeight = $derived(dragHeight ?? snapHeight(snap));
|
|
53
|
+
|
|
54
|
+
const caseLabel = $derived(
|
|
55
|
+
app.active
|
|
56
|
+
? splitName(app.active.name)[0]
|
|
57
|
+
: (app.activeLocal?.label ?? app.activeMulti?.label ?? '')
|
|
58
|
+
);
|
|
59
|
+
|
|
60
|
+
/** Single line shown on the grab bar while collapsed. */
|
|
61
|
+
const summary = $derived.by(() => {
|
|
62
|
+
if (app.error) return 'error — open for details';
|
|
63
|
+
if (app.selectedBus !== null) return `bus ${app.selectedBus} · ∂LMP/∂d`;
|
|
64
|
+
if (app.selectedBranch !== null) return `line ${app.selectedBranch} · ∂LMP/∂rating`;
|
|
65
|
+
const stats = ctrl.networkStats;
|
|
66
|
+
if (!stats) return app.parsingFile ? 'parsing…' : 'loading…';
|
|
67
|
+
return `${caseLabel} · ${stats.buses} buses · ${stats.branches} lines`;
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
const expandLabel = $derived(
|
|
71
|
+
snap === 'full' ? 'collapse the control panel' : 'expand the control panel'
|
|
72
|
+
);
|
|
73
|
+
|
|
74
|
+
/** A selection whose readout has resolved (or is resolving). */
|
|
75
|
+
const busReadout = $derived(
|
|
76
|
+
app.selectedBus !== null && (ctrl.selectedSensitivity || app.sensitivityLoading)
|
|
77
|
+
);
|
|
78
|
+
const branchReadout = $derived(
|
|
79
|
+
app.selectedBranch !== null && (ctrl.selectedSensitivity || app.sensitivityLoading)
|
|
80
|
+
);
|
|
81
|
+
/** Compact only: a resolved selection outranks the case stats it was read
|
|
82
|
+
* against. Gated on networkStats because that is what renders the readout. */
|
|
83
|
+
const selectionLeads = $derived(
|
|
84
|
+
app.compactLayout && !!ctrl.networkStats && (busReadout || branchReadout)
|
|
85
|
+
);
|
|
86
|
+
|
|
87
|
+
$effect(() => {
|
|
88
|
+
app.sheetInset = app.compactLayout ? sheetHeight : 0;
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
// peek height tracks the grab bar, which resizes when the summary line changes.
|
|
92
|
+
$effect(() => {
|
|
93
|
+
const el = headEl;
|
|
94
|
+
if (!el) return;
|
|
95
|
+
const measure = () => (headHeight = Math.round(el.getBoundingClientRect().height));
|
|
96
|
+
measure();
|
|
97
|
+
const observer = new ResizeObserver(measure);
|
|
98
|
+
observer.observe(el);
|
|
99
|
+
return () => observer.disconnect();
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
// A map selection renders the sensitivity readout and the demand control, both
|
|
103
|
+
// of which are below the fold at peek. The body also has to go back to the top:
|
|
104
|
+
// reaching the bus lookup scrolls it down, and the readout that replaces it as
|
|
105
|
+
// the first block would otherwise open out of view.
|
|
106
|
+
$effect(() => {
|
|
107
|
+
const selected = app.selectedBus ?? app.selectedBranch;
|
|
108
|
+
if (selected === null) return;
|
|
109
|
+
untrack(() => {
|
|
110
|
+
if (!app.compactLayout) return;
|
|
111
|
+
if (snap === 'peek') snap = 'half';
|
|
112
|
+
if (bodyEl) bodyEl.scrollTop = 0;
|
|
113
|
+
});
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
function cycle() {
|
|
117
|
+
snap = snap === 'peek' ? 'half' : snap === 'half' ? 'full' : 'peek';
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function nearestSnap(height: number): Snap {
|
|
121
|
+
return SNAPS.reduce((best, s) =>
|
|
122
|
+
Math.abs(snapHeight(s) - height) < Math.abs(snapHeight(best) - height) ? s : best
|
|
123
|
+
);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
let dragStartY = 0;
|
|
127
|
+
let dragStartHeight = 0;
|
|
128
|
+
let dragMoved = false;
|
|
129
|
+
let lastY = 0;
|
|
130
|
+
let lastT = 0;
|
|
131
|
+
let speed = 0;
|
|
132
|
+
|
|
133
|
+
function onPointerDown(e: PointerEvent) {
|
|
134
|
+
if (!app.compactLayout || !e.isPrimary) return;
|
|
135
|
+
if (e.currentTarget instanceof HTMLElement) e.currentTarget.setPointerCapture(e.pointerId);
|
|
136
|
+
dragStartY = e.clientY;
|
|
137
|
+
dragStartHeight = sheetHeight;
|
|
138
|
+
dragHeight = dragStartHeight;
|
|
139
|
+
dragMoved = false;
|
|
140
|
+
speed = 0;
|
|
141
|
+
lastY = e.clientY;
|
|
142
|
+
lastT = e.timeStamp;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
function onPointerMove(e: PointerEvent) {
|
|
146
|
+
if (dragHeight === null) return;
|
|
147
|
+
const dy = dragStartY - e.clientY;
|
|
148
|
+
if (Math.abs(dy) > 4) dragMoved = true;
|
|
149
|
+
const dt = e.timeStamp - lastT;
|
|
150
|
+
if (dt > 0) speed = (lastY - e.clientY) / dt;
|
|
151
|
+
lastY = e.clientY;
|
|
152
|
+
lastT = e.timeStamp;
|
|
153
|
+
dragHeight = Math.max(
|
|
154
|
+
snapHeight('peek'),
|
|
155
|
+
Math.min(snapHeight('full'), dragStartHeight + dy)
|
|
156
|
+
);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
function onPointerUp() {
|
|
160
|
+
if (dragHeight === null) return;
|
|
161
|
+
const height = dragHeight;
|
|
162
|
+
dragHeight = null;
|
|
163
|
+
if (!dragMoved) return; // a tap; the click handler cycles
|
|
164
|
+
const landed = nearestSnap(height);
|
|
165
|
+
if (Math.abs(speed) > FLICK_SPEED) {
|
|
166
|
+
const next = SNAPS.indexOf(landed) + (speed > 0 ? 1 : -1);
|
|
167
|
+
snap = SNAPS[Math.max(0, Math.min(SNAPS.length - 1, next))];
|
|
168
|
+
} else {
|
|
169
|
+
snap = landed;
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
function onHeadKeydown(e: KeyboardEvent) {
|
|
174
|
+
const step = e.key === 'ArrowUp' ? 1 : e.key === 'ArrowDown' ? -1 : 0;
|
|
175
|
+
if (step === 0) return;
|
|
176
|
+
e.preventDefault();
|
|
177
|
+
const next = SNAPS.indexOf(snap) + step;
|
|
178
|
+
snap = SNAPS[Math.max(0, Math.min(SNAPS.length - 1, next))];
|
|
179
|
+
}
|
|
18
180
|
</script>
|
|
19
181
|
|
|
20
|
-
<aside
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
182
|
+
<aside
|
|
183
|
+
class="panel"
|
|
184
|
+
class:sheet={app.compactLayout}
|
|
185
|
+
class:dragging={dragHeight !== null}
|
|
186
|
+
style:height={app.compactLayout ? `${sheetHeight}px` : null}
|
|
187
|
+
>
|
|
188
|
+
{#if app.compactLayout}
|
|
189
|
+
<button
|
|
190
|
+
type="button"
|
|
191
|
+
class="sheet-head"
|
|
192
|
+
bind:this={headEl}
|
|
193
|
+
aria-expanded={snap !== 'peek'}
|
|
194
|
+
aria-controls="control-panel-body"
|
|
195
|
+
aria-label={expandLabel}
|
|
196
|
+
onpointerdown={onPointerDown}
|
|
197
|
+
onpointermove={onPointerMove}
|
|
198
|
+
onpointerup={onPointerUp}
|
|
199
|
+
onpointercancel={onPointerUp}
|
|
200
|
+
onkeydown={onHeadKeydown}
|
|
201
|
+
onclick={() => {
|
|
202
|
+
// click fires after pointerup; a keyboard activation leaves dragMoved
|
|
203
|
+
// unset, so clear it here rather than in the pointer handlers
|
|
204
|
+
const moved = dragMoved;
|
|
205
|
+
dragMoved = false;
|
|
206
|
+
if (!moved) cycle();
|
|
207
|
+
}}
|
|
208
|
+
>
|
|
209
|
+
<span class="grabber" aria-hidden="true"></span>
|
|
210
|
+
{#if snap === 'peek'}
|
|
211
|
+
<span class="sheet-summary mono">{summary}</span>
|
|
212
|
+
{/if}
|
|
213
|
+
</button>
|
|
36
214
|
{/if}
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
215
|
+
|
|
216
|
+
<div class="panel-body" id="control-panel-body" bind:this={bodyEl}>
|
|
217
|
+
{#snippet selectionBlock()}
|
|
218
|
+
{#if busReadout}
|
|
219
|
+
<SensitivityReadout />
|
|
220
|
+
|
|
221
|
+
<DemandSlider />
|
|
222
|
+
|
|
223
|
+
{#if ctrl.showMoverSlot}
|
|
224
|
+
<TopMovers />
|
|
225
|
+
{/if}
|
|
226
|
+
{:else if branchReadout}
|
|
227
|
+
<SensitivityReadout />
|
|
228
|
+
|
|
229
|
+
<RatingSlider />
|
|
230
|
+
|
|
231
|
+
{#if ctrl.showMoverSlot}
|
|
232
|
+
<TopMovers />
|
|
47
233
|
{/if}
|
|
48
|
-
{:else if ctrl.loadingBackendCase}
|
|
49
|
-
<p class="dim mono blink">loading selected case…</p>
|
|
50
234
|
{:else}
|
|
51
|
-
<
|
|
235
|
+
<DisplayControls />
|
|
52
236
|
{/if}
|
|
237
|
+
{/snippet}
|
|
238
|
+
|
|
239
|
+
{#if app.error}
|
|
240
|
+
<p class="error mono">{app.error}</p>
|
|
241
|
+
<div class="error-actions">
|
|
242
|
+
<button class="reset mono" onclick={ctrl.retryError}>retry</button>
|
|
243
|
+
<button class="reset mono" onclick={() => (app.error = null)}>dismiss</button>
|
|
244
|
+
</div>
|
|
53
245
|
{/if}
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
<NetworkStats />
|
|
246
|
+
{#if app.parsingFile}
|
|
247
|
+
<p class="dim mono blink">parsing…</p>
|
|
57
248
|
{/if}
|
|
58
249
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
{
|
|
250
|
+
<!-- The sheet shows a few hundred px at most, so what the last tap produced
|
|
251
|
+
leads; the case provenance and stats it was read against follow. -->
|
|
252
|
+
{#if selectionLeads}
|
|
253
|
+
{@render selectionBlock()}
|
|
62
254
|
|
|
63
|
-
|
|
64
|
-
<BindingLines />
|
|
255
|
+
<hr />
|
|
65
256
|
{/if}
|
|
66
257
|
|
|
67
|
-
|
|
258
|
+
{#if app.activeLocal}
|
|
259
|
+
<LocalCaseDetails />
|
|
260
|
+
{/if}
|
|
261
|
+
{#if app.activeMulti}
|
|
262
|
+
<MulticonductorDetails />
|
|
263
|
+
{/if}
|
|
264
|
+
{#if !ctrl.networkStats}
|
|
265
|
+
{#if !app.error && !app.activeLocal && !app.activeMulti}
|
|
266
|
+
{#if ctrl.casesLoaded && app.cases.length === 0}
|
|
267
|
+
<p class="dim mono">
|
|
268
|
+
{config.loadDefaultCases ? 'no default cases loaded' : 'drop a case file to begin'}
|
|
269
|
+
</p>
|
|
270
|
+
{#if config.loadDefaultCases}
|
|
271
|
+
<button class="reset mono" onclick={ctrl.restoreDefaultCases}
|
|
272
|
+
>restore default cases</button
|
|
273
|
+
>
|
|
274
|
+
{/if}
|
|
275
|
+
{:else if ctrl.loadingBackendCase}
|
|
276
|
+
<p class="dim mono blink">loading selected case…</p>
|
|
277
|
+
{:else}
|
|
278
|
+
<p class="dim mono blink">loading cases…</p>
|
|
279
|
+
{/if}
|
|
280
|
+
{/if}
|
|
281
|
+
{:else}
|
|
282
|
+
{#if !app.activeLocal}
|
|
283
|
+
<NetworkStats />
|
|
284
|
+
{/if}
|
|
68
285
|
|
|
69
|
-
|
|
70
|
-
|
|
286
|
+
{#if ctrl.activeSolvable}
|
|
287
|
+
<FormulationSelector />
|
|
288
|
+
{/if}
|
|
71
289
|
|
|
72
|
-
|
|
290
|
+
{#if app.compactLayout}
|
|
291
|
+
<BusPicker inline />
|
|
292
|
+
{/if}
|
|
73
293
|
|
|
74
|
-
{#if
|
|
75
|
-
<
|
|
294
|
+
{#if !app.placingLocalId}
|
|
295
|
+
<BindingLines />
|
|
76
296
|
{/if}
|
|
77
|
-
{:else if app.selectedBranch !== null && (ctrl.selectedSensitivity || app.sensitivityLoading)}
|
|
78
|
-
<SensitivityReadout />
|
|
79
297
|
|
|
80
|
-
|
|
298
|
+
{#if !selectionLeads}
|
|
299
|
+
<hr />
|
|
81
300
|
|
|
82
|
-
|
|
83
|
-
<TopMovers />
|
|
301
|
+
{@render selectionBlock()}
|
|
84
302
|
{/if}
|
|
85
|
-
{:else}
|
|
86
|
-
<DisplayControls />
|
|
87
|
-
{/if}
|
|
88
303
|
|
|
89
|
-
|
|
304
|
+
<hr />
|
|
90
305
|
|
|
91
|
-
|
|
92
|
-
|
|
306
|
+
<SizeLegend />
|
|
307
|
+
{/if}
|
|
308
|
+
|
|
309
|
+
{#if app.compactLayout && config.showFooter}
|
|
310
|
+
<AppFooter inline />
|
|
311
|
+
{/if}
|
|
312
|
+
</div>
|
|
93
313
|
</aside>
|
|
94
314
|
|
|
95
315
|
<style>
|
|
@@ -98,10 +318,11 @@
|
|
|
98
318
|
top: 76px;
|
|
99
319
|
left: 20px;
|
|
100
320
|
z-index: 10;
|
|
321
|
+
display: flex;
|
|
322
|
+
flex-direction: column;
|
|
101
323
|
width: 312px;
|
|
102
324
|
max-height: calc(100% - 122px);
|
|
103
|
-
overflow
|
|
104
|
-
padding: 16px 18px;
|
|
325
|
+
overflow: hidden;
|
|
105
326
|
background: var(--panel);
|
|
106
327
|
border: 1px solid var(--line);
|
|
107
328
|
border-radius: 3px;
|
|
@@ -110,6 +331,16 @@
|
|
|
110
331
|
animation: rise 0.5s 0.12s ease-out both;
|
|
111
332
|
}
|
|
112
333
|
|
|
334
|
+
.panel-body {
|
|
335
|
+
flex: 1 1 auto;
|
|
336
|
+
min-height: 0;
|
|
337
|
+
overflow-y: auto;
|
|
338
|
+
/* A flick that runs past the end of the panel must not scroll the page or
|
|
339
|
+
pan the map underneath. */
|
|
340
|
+
overscroll-behavior: contain;
|
|
341
|
+
padding: 16px 18px;
|
|
342
|
+
}
|
|
343
|
+
|
|
113
344
|
hr {
|
|
114
345
|
border: 0;
|
|
115
346
|
border-top: 1px solid var(--line);
|
|
@@ -126,24 +357,80 @@
|
|
|
126
357
|
gap: 6px;
|
|
127
358
|
}
|
|
128
359
|
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
360
|
+
/* The grab bar: a 44px strip that drags the sheet and cycles it on tap. */
|
|
361
|
+
.sheet-head {
|
|
362
|
+
flex: 0 0 auto;
|
|
363
|
+
display: flex;
|
|
364
|
+
flex-direction: column;
|
|
365
|
+
align-items: center;
|
|
366
|
+
justify-content: center;
|
|
367
|
+
gap: 7px;
|
|
368
|
+
width: 100%;
|
|
369
|
+
min-height: 44px;
|
|
370
|
+
padding: 9px 16px 11px;
|
|
371
|
+
background: none;
|
|
372
|
+
border: 0;
|
|
373
|
+
border-bottom: 1px solid transparent;
|
|
374
|
+
color: inherit;
|
|
375
|
+
font: inherit;
|
|
376
|
+
cursor: grab;
|
|
377
|
+
/* the browser must not claim the vertical gesture */
|
|
378
|
+
touch-action: none;
|
|
140
379
|
}
|
|
141
380
|
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
381
|
+
.sheet-head:active {
|
|
382
|
+
cursor: grabbing;
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
.grabber {
|
|
386
|
+
width: 40px;
|
|
387
|
+
height: 4px;
|
|
388
|
+
border-radius: 2px;
|
|
389
|
+
background: var(--line);
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
.sheet-head:hover .grabber,
|
|
393
|
+
.sheet-head:focus-visible .grabber {
|
|
394
|
+
background: var(--accent);
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
.sheet-summary {
|
|
398
|
+
max-width: 100%;
|
|
399
|
+
overflow: hidden;
|
|
400
|
+
font-size: 11.5px;
|
|
401
|
+
color: var(--text-secondary);
|
|
402
|
+
text-overflow: ellipsis;
|
|
403
|
+
white-space: nowrap;
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
.sheet {
|
|
407
|
+
top: auto;
|
|
408
|
+
right: 0;
|
|
409
|
+
bottom: 0;
|
|
410
|
+
left: 0;
|
|
411
|
+
width: auto;
|
|
412
|
+
max-height: none;
|
|
413
|
+
background: var(--paper);
|
|
414
|
+
border-width: 1px 0 0;
|
|
415
|
+
border-radius: 12px 12px 0 0;
|
|
416
|
+
box-shadow: 0 -6px 28px rgba(32, 36, 43, 0.14);
|
|
417
|
+
transition: height var(--dur-med) var(--ease-out);
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
.sheet .panel-body {
|
|
421
|
+
padding: 0 16px 14px;
|
|
422
|
+
/* clear the home indicator on a phone with no bezel */
|
|
423
|
+
padding-bottom: max(14px, env(safe-area-inset-bottom));
|
|
424
|
+
-webkit-overflow-scrolling: touch;
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
.sheet .sheet-head {
|
|
428
|
+
border-bottom-color: var(--line);
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
/* A dragged sheet tracks the finger; the snap animation is for releases. */
|
|
432
|
+
.sheet.dragging {
|
|
433
|
+
transition: none;
|
|
147
434
|
}
|
|
148
435
|
|
|
149
436
|
@media (prefers-reduced-motion: reduce) {
|
|
@@ -1,19 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
$$bindings?: Bindings;
|
|
4
|
-
} & Exports;
|
|
5
|
-
(internal: unknown, props: {
|
|
6
|
-
$$events?: Events;
|
|
7
|
-
$$slots?: Slots;
|
|
8
|
-
}): Exports & {
|
|
9
|
-
$set?: any;
|
|
10
|
-
$on?: any;
|
|
11
|
-
};
|
|
12
|
-
z_$$bindings?: Bindings;
|
|
13
|
-
}
|
|
14
|
-
declare const ControlPanel: $$__sveltets_2_IsomorphicComponent<Record<string, never>, {
|
|
15
|
-
[evt: string]: CustomEvent<any>;
|
|
16
|
-
}, {}, {}, string>;
|
|
17
|
-
type ControlPanel = InstanceType<typeof ControlPanel>;
|
|
1
|
+
declare const ControlPanel: import("svelte").Component<Record<string, never>, {}, "">;
|
|
2
|
+
type ControlPanel = ReturnType<typeof ControlPanel>;
|
|
18
3
|
export default ControlPanel;
|
|
19
4
|
//# sourceMappingURL=ControlPanel.svelte.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ControlPanel.svelte.d.ts","sourceRoot":"","sources":["../../src/lib/components/ControlPanel.svelte.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"ControlPanel.svelte.d.ts","sourceRoot":"","sources":["../../src/lib/components/ControlPanel.svelte.ts"],"names":[],"mappings":"AA6TA,QAAA,MAAM,YAAY,2DAAwC,CAAC;AAC3D,KAAK,YAAY,GAAG,UAAU,CAAC,OAAO,YAAY,CAAC,CAAC;AACpD,eAAe,YAAY,CAAC"}
|
|
@@ -79,4 +79,21 @@
|
|
|
79
79
|
opacity: 0.55;
|
|
80
80
|
cursor: progress;
|
|
81
81
|
}
|
|
82
|
+
|
|
83
|
+
@media (hover: none), (pointer: coarse) {
|
|
84
|
+
/* The select is sized to its longest option, which runs off a 320px screen.
|
|
85
|
+
Let it shrink around the current value rather than truncate the label.
|
|
86
|
+
16px also keeps Safari from zooming the layout viewport on focus. */
|
|
87
|
+
.formulation-row select {
|
|
88
|
+
flex: 0 1 auto;
|
|
89
|
+
min-width: 0;
|
|
90
|
+
min-height: 44px;
|
|
91
|
+
font-size: 16px;
|
|
92
|
+
padding-block: 6px;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
.formulation-row > span {
|
|
96
|
+
flex: 0 0 auto;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
82
99
|
</style>
|
|
@@ -40,7 +40,9 @@
|
|
|
40
40
|
<span class="mono dim">
|
|
41
41
|
{#if branchMode}line {branchLabel}{:else}bus {app.selectedBus}{/if}
|
|
42
42
|
</span>
|
|
43
|
-
<button class="mono" onclick={ctrl.clearSelection}
|
|
43
|
+
<button class="mono" onclick={ctrl.clearSelection}
|
|
44
|
+
><span class="key-hint">esc </span>clear</button
|
|
45
|
+
>
|
|
44
46
|
</div>
|
|
45
47
|
<div class="sensitivity-readout" aria-live="polite">
|
|
46
48
|
{#if ctrl.previewing}
|
|
@@ -123,4 +125,11 @@
|
|
|
123
125
|
line-height: 1.35;
|
|
124
126
|
white-space: nowrap;
|
|
125
127
|
}
|
|
128
|
+
|
|
129
|
+
/* No Esc key to name on a touch device. */
|
|
130
|
+
@media (hover: none), (pointer: coarse) {
|
|
131
|
+
.key-hint {
|
|
132
|
+
display: none;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
126
135
|
</style>
|
package/dist/state.svelte.d.ts
CHANGED
|
@@ -234,6 +234,19 @@ export declare class AppState {
|
|
|
234
234
|
placingMultiId: string | null;
|
|
235
235
|
dragOver: boolean;
|
|
236
236
|
parsingFile: boolean;
|
|
237
|
+
/** True under `(max-width: 760px)`; the panel renders as a bottom sheet. Set by
|
|
238
|
+
* the shell from one media query so panel and map read the same value. */
|
|
239
|
+
compactLayout: boolean;
|
|
240
|
+
/** Height in px the bottom sheet covers; 0 when `compactLayout` is false.
|
|
241
|
+
* Read by the map's camera padding and by chrome anchored above the sheet. */
|
|
242
|
+
sheetInset: number;
|
|
243
|
+
/** Height in px the header covers at the top of the map, measured by
|
|
244
|
+
* `AppHeader`. Wrapping the case tabs onto their own row roughly doubles it,
|
|
245
|
+
* so callers that need to clear the header read this rather than a constant. */
|
|
246
|
+
headerInset: number;
|
|
247
|
+
/** `window.innerHeight`, tracked by the shell. The sheet's snaps and the map's
|
|
248
|
+
* chrome lift are both fractions of it; the fallback is a desktop window. */
|
|
249
|
+
viewportHeight: number;
|
|
237
250
|
/** Map framing request: bump seq so repeat targets still fly. `requestFrame`
|
|
238
251
|
* returns a promise the map resolves when the camera lands (or immediately
|
|
239
252
|
* when it cannot fly), so a caller can defer heavy work until the animation
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"state.svelte.d.ts","sourceRoot":"","sources":["../src/lib/state.svelte.ts"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"state.svelte.d.ts","sourceRoot":"","sources":["../src/lib/state.svelte.ts"],"names":[],"mappings":"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"}
|
package/dist/state.svelte.js
CHANGED
|
@@ -200,6 +200,19 @@ export class AppState {
|
|
|
200
200
|
placingMultiId = $state(null);
|
|
201
201
|
dragOver = $state(false);
|
|
202
202
|
parsingFile = $state(false);
|
|
203
|
+
/** True under `(max-width: 760px)`; the panel renders as a bottom sheet. Set by
|
|
204
|
+
* the shell from one media query so panel and map read the same value. */
|
|
205
|
+
compactLayout = $state(false);
|
|
206
|
+
/** Height in px the bottom sheet covers; 0 when `compactLayout` is false.
|
|
207
|
+
* Read by the map's camera padding and by chrome anchored above the sheet. */
|
|
208
|
+
sheetInset = $state(0);
|
|
209
|
+
/** Height in px the header covers at the top of the map, measured by
|
|
210
|
+
* `AppHeader`. Wrapping the case tabs onto their own row roughly doubles it,
|
|
211
|
+
* so callers that need to clear the header read this rather than a constant. */
|
|
212
|
+
headerInset = $state(64);
|
|
213
|
+
/** `window.innerHeight`, tracked by the shell. The sheet's snaps and the map's
|
|
214
|
+
* chrome lift are both fractions of it; the fallback is a desktop window. */
|
|
215
|
+
viewportHeight = $state(800);
|
|
203
216
|
/** Map framing request: bump seq so repeat targets still fly. `requestFrame`
|
|
204
217
|
* returns a promise the map resolves when the camera lands (or immediately
|
|
205
218
|
* when it cannot fly), so a caller can defer heavy work until the animation
|
package/dist/styles.css
CHANGED
|
@@ -372,11 +372,25 @@ a {
|
|
|
372
372
|
/* Coarse pointers: comfortable hit targets for the toggle and reset controls. */
|
|
373
373
|
@media (hover: none), (pointer: coarse) {
|
|
374
374
|
.segment button {
|
|
375
|
-
min-height:
|
|
375
|
+
min-height: 44px;
|
|
376
376
|
}
|
|
377
377
|
|
|
378
378
|
.reset {
|
|
379
|
-
min-height:
|
|
379
|
+
min-height: 44px;
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
/* Keep the chip visually small next to its short siblings; the ::before pads
|
|
383
|
+
the tap target out to 44px, as .case-remove does in AppHeader. */
|
|
384
|
+
.mode > button {
|
|
385
|
+
position: relative;
|
|
386
|
+
min-height: 28px;
|
|
387
|
+
padding-inline: 10px;
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
.mode > button::before {
|
|
391
|
+
content: '';
|
|
392
|
+
position: absolute;
|
|
393
|
+
inset: -8px -4px;
|
|
380
394
|
}
|
|
381
395
|
}
|
|
382
396
|
|