@tellegen/svelte 0.1.1 → 0.2.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.
Files changed (105) hide show
  1. package/README.md +15 -0
  2. package/dist/Sparkline.svelte.d.ts +0 -1
  3. package/dist/TellegenMap.svelte +109 -47
  4. package/dist/TellegenMap.svelte.d.ts +0 -1
  5. package/dist/TellegenProvider.svelte.d.ts +0 -1
  6. package/dist/TellegenShell.svelte +28 -3
  7. package/dist/TellegenShell.svelte.d.ts +0 -1
  8. package/dist/TellegenViewer.svelte.d.ts +0 -1
  9. package/dist/api.d.ts +0 -1
  10. package/dist/colors.d.ts +0 -1
  11. package/dist/components/AppFooter.svelte +26 -1
  12. package/dist/components/AppFooter.svelte.d.ts +5 -18
  13. package/dist/components/AppHeader.svelte +63 -3
  14. package/dist/components/AppHeader.svelte.d.ts +0 -1
  15. package/dist/components/BindingLines.svelte +13 -0
  16. package/dist/components/BindingLines.svelte.d.ts +0 -1
  17. package/dist/components/BusPicker.svelte +40 -0
  18. package/dist/components/BusPicker.svelte.d.ts +4 -2
  19. package/dist/components/ControlPanel.svelte +359 -72
  20. package/dist/components/ControlPanel.svelte.d.ts +2 -18
  21. package/dist/components/DemandSlider.svelte +28 -8
  22. package/dist/components/DemandSlider.svelte.d.ts +0 -1
  23. package/dist/components/DisplayControls.svelte.d.ts +0 -1
  24. package/dist/components/DropZone.svelte.d.ts +0 -1
  25. package/dist/components/FormulationSelector.svelte +17 -0
  26. package/dist/components/FormulationSelector.svelte.d.ts +0 -1
  27. package/dist/components/LocalCaseDetails.svelte +5 -2
  28. package/dist/components/LocalCaseDetails.svelte.d.ts +0 -1
  29. package/dist/components/MulticonductorDetails.svelte +3 -3
  30. package/dist/components/MulticonductorDetails.svelte.d.ts +0 -1
  31. package/dist/components/NetworkStats.svelte.d.ts +0 -1
  32. package/dist/components/PlacementCue.svelte.d.ts +0 -1
  33. package/dist/components/RatingSlider.svelte.d.ts +0 -1
  34. package/dist/components/RestoreDefaultsButton.svelte.d.ts +0 -1
  35. package/dist/components/SensitivityReadout.svelte +10 -1
  36. package/dist/components/SensitivityReadout.svelte.d.ts +0 -1
  37. package/dist/components/SizeLegend.svelte.d.ts +0 -1
  38. package/dist/components/SolveCard.svelte +2 -1
  39. package/dist/components/SolveCard.svelte.d.ts +0 -1
  40. package/dist/components/TopMovers.svelte.d.ts +0 -1
  41. package/dist/components.d.ts +0 -1
  42. package/dist/context.svelte.d.ts +0 -1
  43. package/dist/controller.svelte.d.ts +24 -12
  44. package/dist/controller.svelte.js +218 -101
  45. package/dist/display.d.ts +0 -1
  46. package/dist/drop-classify.d.ts +5 -42
  47. package/dist/drop-classify.js +5 -78
  48. package/dist/drop-limits.d.ts +27 -0
  49. package/dist/drop-limits.js +83 -0
  50. package/dist/format.d.ts +0 -1
  51. package/dist/index.d.ts +3 -5
  52. package/dist/index.js +2 -2
  53. package/dist/map-bounds.d.ts +5 -0
  54. package/dist/map-bounds.js +41 -0
  55. package/dist/map.d.ts +0 -1
  56. package/dist/multiconductor.d.ts +0 -1
  57. package/dist/state.svelte.d.ts +13 -1
  58. package/dist/state.svelte.js +13 -0
  59. package/dist/study-package.d.ts +0 -1
  60. package/dist/styles.css +16 -2
  61. package/dist/synthetic-layout.d.ts +0 -1
  62. package/dist/synthetic-layout.js +16 -2
  63. package/dist/transformer-icon.d.ts +0 -1
  64. package/dist/types.d.ts +0 -1
  65. package/package.json +14 -11
  66. package/dist/Sparkline.svelte.d.ts.map +0 -1
  67. package/dist/TellegenMap.svelte.d.ts.map +0 -1
  68. package/dist/TellegenProvider.svelte.d.ts.map +0 -1
  69. package/dist/TellegenShell.svelte.d.ts.map +0 -1
  70. package/dist/TellegenViewer.svelte.d.ts.map +0 -1
  71. package/dist/api.d.ts.map +0 -1
  72. package/dist/colors.d.ts.map +0 -1
  73. package/dist/components/AppFooter.svelte.d.ts.map +0 -1
  74. package/dist/components/AppHeader.svelte.d.ts.map +0 -1
  75. package/dist/components/BindingLines.svelte.d.ts.map +0 -1
  76. package/dist/components/BusPicker.svelte.d.ts.map +0 -1
  77. package/dist/components/ControlPanel.svelte.d.ts.map +0 -1
  78. package/dist/components/DemandSlider.svelte.d.ts.map +0 -1
  79. package/dist/components/DisplayControls.svelte.d.ts.map +0 -1
  80. package/dist/components/DropZone.svelte.d.ts.map +0 -1
  81. package/dist/components/FormulationSelector.svelte.d.ts.map +0 -1
  82. package/dist/components/LocalCaseDetails.svelte.d.ts.map +0 -1
  83. package/dist/components/MulticonductorDetails.svelte.d.ts.map +0 -1
  84. package/dist/components/NetworkStats.svelte.d.ts.map +0 -1
  85. package/dist/components/PlacementCue.svelte.d.ts.map +0 -1
  86. package/dist/components/RatingSlider.svelte.d.ts.map +0 -1
  87. package/dist/components/RestoreDefaultsButton.svelte.d.ts.map +0 -1
  88. package/dist/components/SensitivityReadout.svelte.d.ts.map +0 -1
  89. package/dist/components/SizeLegend.svelte.d.ts.map +0 -1
  90. package/dist/components/SolveCard.svelte.d.ts.map +0 -1
  91. package/dist/components/TopMovers.svelte.d.ts.map +0 -1
  92. package/dist/components.d.ts.map +0 -1
  93. package/dist/context.svelte.d.ts.map +0 -1
  94. package/dist/controller.svelte.d.ts.map +0 -1
  95. package/dist/display.d.ts.map +0 -1
  96. package/dist/drop-classify.d.ts.map +0 -1
  97. package/dist/format.d.ts.map +0 -1
  98. package/dist/index.d.ts.map +0 -1
  99. package/dist/map.d.ts.map +0 -1
  100. package/dist/multiconductor.d.ts.map +0 -1
  101. package/dist/state.svelte.d.ts.map +0 -1
  102. package/dist/study-package.d.ts.map +0 -1
  103. package/dist/synthetic-layout.d.ts.map +0 -1
  104. package/dist/transformer-icon.d.ts.map +0 -1
  105. package/dist/types.d.ts.map +0 -1
package/README.md CHANGED
@@ -28,6 +28,10 @@ Use local files only by disabling bundled case loading:
28
28
  <TellegenViewer loadDefaultCases={false} showFooter={false} />
29
29
  ```
30
30
 
31
+ The viewer accepts at most 32 files in one ingestion batch. Each file must be
32
+ at most 128 MiB, and the aggregate batch must also be at most 128 MiB. Files
33
+ remain in the browser on this path.
34
+
31
35
  `TellegenViewer` accepts:
32
36
 
33
37
  - `apiBase`, default `/api`
@@ -50,6 +54,17 @@ Use `TellegenProvider` and `TellegenShell` when state should survive route chang
50
54
  </TellegenProvider>
51
55
  ```
52
56
 
57
+ ## Engine Reexports
58
+
59
+ The package reexports the browser engine helpers, including `ingestCase`,
60
+ `classifyJson`, and `ingestJsonDrop`, for custom drop surfaces. In 0.2 these
61
+ APIs use byte input: pass `Uint8Array` to `ingestCase`, await
62
+ `classifyJson(bytes)` and read its `{ kind, format }` result, and use
63
+ `ingestJsonDrop(bytes)` to classify and parse in one call. `isStudyPackageText`
64
+ was removed; test for `kind === "balanced-package"` instead. `JsonDropKind` now
65
+ includes `transmission`, `distribution`, `ambiguous`, and `unknown`; BMOPF and
66
+ PMD are distribution `format` values rather than separate kinds.
67
+
53
68
  ## Release
54
69
 
55
70
  Build and inspect the package from the repository root:
@@ -5,4 +5,3 @@ type $$ComponentProps = {
5
5
  declare const Sparkline: import("svelte").Component<$$ComponentProps, {}, "">;
6
6
  type Sparkline = ReturnType<typeof Sparkline>;
7
7
  export default Sparkline;
8
- //# sourceMappingURL=Sparkline.svelte.d.ts.map
@@ -32,6 +32,7 @@
32
32
  type TransformerMark
33
33
  } from './multiconductor.js';
34
34
  import { transformerIcon } from './transformer-icon.js';
35
+ import { foldMapBounds } from './map-bounds.js';
35
36
  import { getAppState, getController } from './context.svelte.js';
36
37
  import { displayFmt, fmt, rgbaCss } from './format.js';
37
38
  import type { RGBA } from './colors.js';
@@ -495,54 +496,18 @@
495
496
  * The branch variant also clears the rating readout on the panel's lower edge. */
496
497
  function framePadding(width: number, height: number, kind: 'case' | 'branch') {
497
498
  if (width <= 760) {
498
- const bottom =
499
- kind === 'branch'
500
- ? Math.min(Math.round(height * 0.42), 300)
501
- : Math.min(Math.round(height * 0.5), 330);
502
- return { top: 130, left: 24, right: 24, bottom };
499
+ // bottom follows the sheet; capped so a fully open sheet still leaves a band to fit into
500
+ const clearance = kind === 'branch' ? 16 : 28;
501
+ const bottom = Math.min(app.sheetInset + clearance, Math.round(height * 0.55));
502
+ return { top: app.headerInset + 8, left: 24, right: 24, bottom };
503
503
  }
504
504
  return kind === 'branch'
505
505
  ? { top: 106, left: 388, right: 76, bottom: 80 }
506
506
  : { top: 96, left: 380, right: 60, bottom: 64 };
507
507
  }
508
508
 
509
- /** Fold lon/lat pairs into LngLat bounds; null when no point is finite. */
510
- function foldBounds(
511
- points: Iterable<[number, number]>
512
- ): [[number, number], [number, number]] | null {
513
- let minLon = Infinity;
514
- let minLat = Infinity;
515
- let maxLon = -Infinity;
516
- let maxLat = -Infinity;
517
- for (const [lon, lat] of points) {
518
- minLon = Math.min(minLon, lon);
519
- minLat = Math.min(minLat, lat);
520
- maxLon = Math.max(maxLon, lon);
521
- maxLat = Math.max(maxLat, lat);
522
- }
523
- if (!Number.isFinite(minLon) || !Number.isFinite(minLat)) return null;
524
- return [
525
- [minLon, minLat],
526
- [maxLon, maxLat]
527
- ];
528
- }
529
-
530
509
  function branchBounds(path: [number, number][]): LngLatBoundsLike | null {
531
- const bounds = foldBounds(path);
532
- if (!bounds) return null;
533
- let [[minLon, minLat], [maxLon, maxLat]] = bounds;
534
- if (minLon === maxLon) {
535
- minLon -= 0.005;
536
- maxLon += 0.005;
537
- }
538
- if (minLat === maxLat) {
539
- minLat -= 0.005;
540
- maxLat += 0.005;
541
- }
542
- return [
543
- [minLon, minLat],
544
- [maxLon, maxLat]
545
- ];
510
+ return foldMapBounds(path);
546
511
  }
547
512
 
548
513
  function prefersReducedMotion(): boolean {
@@ -552,6 +517,25 @@
552
517
  );
553
518
  }
554
519
 
520
+ function coarsePointer(): boolean {
521
+ return typeof window !== 'undefined' && window.matchMedia('(pointer: coarse)').matches;
522
+ }
523
+
524
+ // Compact chrome anchoring. maplibre's bottom-right controls sit over the map,
525
+ // so the sheet would bury them; they ride above it instead. Under
526
+ // CHROME_MIN_BAND_PX of map left they drop out rather than climb into the
527
+ // header — at that point the sheet covers what they annotate.
528
+ const CHROME_MIN_BAND_PX = 132;
529
+
530
+ /** How far the sheet can push the chrome up before it would reach the header. */
531
+ const chromeLiftCap = $derived(
532
+ Math.max(0, app.viewportHeight - app.headerInset - CHROME_MIN_BAND_PX)
533
+ );
534
+ const chromeInset = $derived(
535
+ app.compactLayout ? Math.min(app.sheetInset, chromeLiftCap) : 0
536
+ );
537
+ const chromeVisible = $derived(!app.compactLayout || app.sheetInset <= chromeLiftCap);
538
+
555
539
  /** Fly to the selected branch; true when a camera move started. */
556
540
  function focusSelectedBranch(): boolean {
557
541
  if (!map || !selectedBranchCue) return false;
@@ -699,6 +683,7 @@
699
683
  // deck layers also inherit maplibre's 4096 canvas clamp. The flat positron
700
684
  // basemap writes no depth, so the overlay always draws on top.
701
685
  function buildOverlay(MapboxOverlay: MapModules['MapboxOverlay']): MapboxOverlay {
686
+ const touch = coarsePointer();
702
687
  return new MapboxOverlay({
703
688
  interleaved: true,
704
689
  layers: [],
@@ -706,10 +691,13 @@
706
691
  depthWriteEnabled: false,
707
692
  depthCompare: 'always'
708
693
  },
694
+ // a small bus draws ~9px across, well under a fingertip
695
+ pickingRadius: touch ? 12 : 0,
709
696
  onClick: (info: PickingInfo) => {
710
697
  if (!app.placingId && !isSelectableLayer(info.layer?.id)) onmapclick();
711
698
  },
712
- getTooltip: tooltip,
699
+ // a touch tooltip opens under the finger and stays until the next tap
700
+ getTooltip: touch ? undefined : tooltip,
713
701
  getCursor: ({ isHovering, isDragging }: CursorState) =>
714
702
  app.placingId ? 'crosshair' : isDragging ? 'grabbing' : isHovering ? 'pointer' : 'grab'
715
703
  });
@@ -1121,6 +1109,25 @@
1121
1109
  void tick().then(scheduleCueSync);
1122
1110
  });
1123
1111
 
1112
+ // Pan the selected bus into the band the sheet leaves. The delay outlasts the
1113
+ // sheet's snap transition; sheetInset is read inside the timeout so the effect
1114
+ // does not re-run (and re-pan) on every frame of a sheet drag.
1115
+ $effect(() => {
1116
+ const cue = selectedBusCue;
1117
+ if (!map || !cue || !app.compactLayout) return;
1118
+ void cue.key;
1119
+ const m = map;
1120
+ const timer = setTimeout(() => {
1121
+ const { clientHeight } = m.getContainer();
1122
+ const y = m.project([cue.lon, cue.lat]).y;
1123
+ const floor = clientHeight - app.sheetInset - CUE_MARGIN_PX;
1124
+ const ceiling = 104;
1125
+ const dy = y > floor ? y - floor : y < ceiling ? y - ceiling : 0;
1126
+ if (dy !== 0) m.panBy([0, dy], { duration: prefersReducedMotion() ? 0 : 320 });
1127
+ }, 340);
1128
+ return () => clearTimeout(timer);
1129
+ });
1130
+
1124
1131
  function boundsFor(target: string): LngLatBoundsLike | null {
1125
1132
  const points: [number, number][] = [];
1126
1133
  const fold = (pts: { lon: number; lat: number }[]) => {
@@ -1139,7 +1146,7 @@
1139
1146
  if (target !== 'all' && c.id !== target) continue;
1140
1147
  if (c.view) fold(c.view.buses);
1141
1148
  }
1142
- return foldBounds(points);
1149
+ return foldMapBounds(points);
1143
1150
  }
1144
1151
 
1145
1152
  // Fly to whatever the header, the initial load, or a branch selection asked
@@ -1176,7 +1183,11 @@
1176
1183
  </script>
1177
1184
 
1178
1185
  {#key mapGen}
1179
- <div class="map-stage">
1186
+ <div
1187
+ class="map-stage"
1188
+ class:chrome-clear={!chromeVisible}
1189
+ style="--chrome-inset: {chromeInset}px"
1190
+ >
1180
1191
  <div class="map" {@attach initMap}></div>
1181
1192
  {#if selectedBusCue}
1182
1193
  {#key selectedBusCue.key}
@@ -1203,6 +1214,7 @@
1203
1214
  <button
1204
1215
  type="button"
1205
1216
  class="place-at-center mono"
1217
+ style="--chrome-inset: {chromeInset}px"
1206
1218
  onclick={() => {
1207
1219
  if (!map) return;
1208
1220
  const center = map.getCenter();
@@ -1386,7 +1398,7 @@
1386
1398
  .place-at-center {
1387
1399
  position: absolute;
1388
1400
  left: 50%;
1389
- bottom: 92px;
1401
+ bottom: calc(92px + var(--chrome-inset, 0px));
1390
1402
  z-index: 15;
1391
1403
  transform: translateX(-50%);
1392
1404
  padding: 7px 14px;
@@ -1401,17 +1413,27 @@
1401
1413
 
1402
1414
  @media (max-width: 420px) {
1403
1415
  .place-at-center {
1404
- bottom: 78px;
1416
+ bottom: calc(78px + var(--chrome-inset, 0px));
1405
1417
  }
1406
1418
  }
1407
1419
 
1408
1420
  .map :global(.maplibregl-ctrl-bottom-right) {
1409
1421
  right: 20px;
1410
- bottom: 18px;
1422
+ /* rides above the bottom sheet so attribution stays legible */
1423
+ bottom: calc(18px + var(--chrome-inset, 0px));
1411
1424
  display: flex;
1412
1425
  flex-direction: column;
1413
1426
  align-items: flex-end;
1414
1427
  gap: 6px;
1428
+ transition:
1429
+ bottom var(--dur-med) var(--ease-out),
1430
+ opacity var(--dur-fast) var(--ease-out);
1431
+ }
1432
+
1433
+ /* The sheet has taken the map; nothing left down there to attribute. */
1434
+ .map-stage.chrome-clear :global(.maplibregl-ctrl-bottom-right) {
1435
+ opacity: 0;
1436
+ pointer-events: none;
1415
1437
  }
1416
1438
 
1417
1439
  .map :global(.maplibregl-ctrl-bottom-right .maplibregl-ctrl) {
@@ -1439,6 +1461,26 @@
1439
1461
  color: var(--text-secondary);
1440
1462
  }
1441
1463
 
1464
+ @media (hover: none), (pointer: coarse) {
1465
+ /* A long press on the canvas must not raise the iOS callout or magnifier.
1466
+ Scoped to touch so a mouse can still select the attribution text. */
1467
+ .map {
1468
+ -webkit-touch-callout: none;
1469
+ -webkit-tap-highlight-color: transparent;
1470
+ user-select: none;
1471
+ }
1472
+
1473
+ /* maplibre ships 29px zoom buttons; a fingertip wants 44. */
1474
+ .map :global(.maplibregl-ctrl-group button) {
1475
+ width: 44px;
1476
+ height: 44px;
1477
+ }
1478
+
1479
+ .map :global(.maplibregl-ctrl-group button .maplibregl-ctrl-icon) {
1480
+ background-size: 22px 22px;
1481
+ }
1482
+ }
1483
+
1442
1484
  .map :global(.deck-tooltip) {
1443
1485
  background: var(--panel) !important;
1444
1486
  border: 1px solid var(--line);
@@ -1453,11 +1495,31 @@
1453
1495
 
1454
1496
  @media (max-width: 760px) {
1455
1497
  .map :global(.maplibregl-ctrl-attrib) {
1498
+ /* last in the column so it rests on the sheet's edge instead of
1499
+ floating over the network a zoom control's height above it */
1500
+ order: 1;
1456
1501
  width: auto;
1457
1502
  min-width: 0;
1458
1503
  right: 0;
1459
1504
  white-space: normal;
1460
1505
  }
1506
+
1507
+ /* The band of map a phone has left between the header and the sheet is
1508
+ about the height of the solve card, which covers the buttons wherever
1509
+ they land. Pinch and double tap zoom; the attribution stays. */
1510
+ .map :global(.maplibregl-ctrl-bottom-right .maplibregl-ctrl-group) {
1511
+ display: none;
1512
+ }
1513
+ }
1514
+
1515
+ /* One line on a small phone: wrapped attribution turns into a wide block
1516
+ sitting over the network. */
1517
+ @media (max-width: 460px) {
1518
+ .map :global(.maplibregl-ctrl-attrib) {
1519
+ max-width: calc(100vw - 20px);
1520
+ font-size: 9px;
1521
+ white-space: nowrap;
1522
+ }
1461
1523
  }
1462
1524
 
1463
1525
  @media (prefers-reduced-motion: reduce) {
@@ -12,4 +12,3 @@ type $$ComponentProps = {
12
12
  declare const TellegenMap: import("svelte").Component<$$ComponentProps, {}, "">;
13
13
  type TellegenMap = ReturnType<typeof TellegenMap>;
14
14
  export default TellegenMap;
15
- //# sourceMappingURL=TellegenMap.svelte.d.ts.map
@@ -6,4 +6,3 @@ interface Props extends TellegenUiOptions {
6
6
  declare const TellegenProvider: import("svelte").Component<Props, {}, "">;
7
7
  type TellegenProvider = ReturnType<typeof TellegenProvider>;
8
8
  export default TellegenProvider;
9
- //# sourceMappingURL=TellegenProvider.svelte.d.ts.map
@@ -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
- return () => query.removeEventListener('change', syncFileDropUi);
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
- <BusPicker />
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,4 +1,3 @@
1
1
  declare const TellegenShell: import("svelte").Component<Record<string, never>, {}, "">;
2
2
  type TellegenShell = ReturnType<typeof TellegenShell>;
3
3
  export default TellegenShell;
4
- //# sourceMappingURL=TellegenShell.svelte.d.ts.map
@@ -1,4 +1,3 @@
1
1
  declare const TellegenViewer: import("svelte").Component<Partial<import("./context.svelte.js").TellegenUiConfig>, {}, "">;
2
2
  type TellegenViewer = ReturnType<typeof TellegenViewer>;
3
3
  export default TellegenViewer;
4
- //# sourceMappingURL=TellegenViewer.svelte.d.ts.map
package/dist/api.d.ts CHANGED
@@ -41,4 +41,3 @@ export interface SolveStreamHandlers {
41
41
  ondone?: () => void;
42
42
  }
43
43
  export declare function createApiClient(options?: TellegenApiClientOptions): TellegenApiClient;
44
- //# sourceMappingURL=api.d.ts.map
package/dist/colors.d.ts CHANGED
@@ -48,4 +48,3 @@ export declare function branchWidth(loading: number): number;
48
48
  /** Bus radius in px; area tracks max(load, gen) so big plants and big loads
49
49
  * both read at a glance. Shared by the map layers and the size legend. */
50
50
  export declare function busRadius(maxMw: number): number;
51
- //# sourceMappingURL=colors.d.ts.map
@@ -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,6 @@
1
- interface $$__sveltets_2_IsomorphicComponent<Props extends Record<string, any> = any, Events extends Record<string, any> = any, Slots extends Record<string, any> = any, Exports = {}, Bindings = string> {
2
- new (options: import('svelte').ComponentConstructorOptions<Props>): import('svelte').SvelteComponent<Props, Events, Slots> & {
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 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
- //# sourceMappingURL=AppFooter.svelte.d.ts.map
@@ -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" />
@@ -83,13 +97,15 @@
83
97
  </span>
84
98
  <input
85
99
  type="file"
86
- accept=".m,.raw,.aux,.dss,.pwd,.csv,.json,.geojson"
100
+ accept=".m,.raw,.aux,.epc,.pwb,.dss,.pwd,.csv,.json,.geojson"
87
101
  multiple
88
102
  hidden
89
103
  bind:this={fileInput}
90
104
  onchange={(e) => {
91
105
  const input = e.currentTarget;
92
- if (input.files) ctrl.ingestFiles(Array.from(input.files));
106
+ // Pass the FileList through unchanged: the controller checks its declared
107
+ // count before materializing any entries.
108
+ if (input.files) ctrl.ingestFiles(input.files);
93
109
  input.value = '';
94
110
  }}
95
111
  />
@@ -397,6 +413,50 @@
397
413
  }
398
414
  }
399
415
 
416
+ /* Landscape phone: height is the scarce axis. Keep the case tabs on the
417
+ brand's row instead of giving them one of their own, which halves the
418
+ header and leaves the map a band worth looking at. */
419
+ @media (max-width: 760px) and (max-height: 520px) {
420
+ header {
421
+ flex-wrap: nowrap;
422
+ align-items: center;
423
+ padding: 6px 10px;
424
+ }
425
+
426
+ h1 {
427
+ font-size: 17px;
428
+ }
429
+
430
+ .cases {
431
+ order: 0;
432
+ width: auto;
433
+ justify-content: flex-start;
434
+ }
435
+
436
+ .kicker {
437
+ display: none;
438
+ }
439
+ }
440
+
441
+ /* Touch sizing keys off the pointer, not the breakpoint: a tablet gets the
442
+ wide layout and still needs a fingertip's worth of chip. */
443
+ @media (hover: none), (pointer: coarse) {
444
+ .cases > button,
445
+ .case-chip {
446
+ min-height: 44px;
447
+ }
448
+
449
+ .case-activate {
450
+ min-height: 44px;
451
+ }
452
+
453
+ .kicker a {
454
+ display: inline-flex;
455
+ align-items: center;
456
+ min-height: 44px;
457
+ }
458
+ }
459
+
400
460
  @media (prefers-reduced-motion: reduce) {
401
461
  header {
402
462
  animation: none;
@@ -1,4 +1,3 @@
1
1
  declare const AppHeader: import("svelte").Component<Record<string, never>, {}, "">;
2
2
  type AppHeader = ReturnType<typeof AppHeader>;
3
3
  export default AppHeader;
4
- //# sourceMappingURL=AppHeader.svelte.d.ts.map
@@ -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>
@@ -1,4 +1,3 @@
1
1
  declare const BindingLines: import("svelte").Component<Record<string, never>, {}, "">;
2
2
  type BindingLines = ReturnType<typeof BindingLines>;
3
3
  export default BindingLines;
4
- //# sourceMappingURL=BindingLines.svelte.d.ts.map
@@ -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,6 @@
1
- declare const BusPicker: import("svelte").Component<Record<string, never>, {}, "">;
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
- //# sourceMappingURL=BusPicker.svelte.d.ts.map