@tellegen/svelte 0.1.0 → 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.
Files changed (39) hide show
  1. package/dist/TellegenMap.svelte +155 -19
  2. package/dist/TellegenMap.svelte.d.ts +1 -0
  3. package/dist/TellegenMap.svelte.d.ts.map +1 -1
  4. package/dist/TellegenShell.svelte +29 -3
  5. package/dist/TellegenShell.svelte.d.ts.map +1 -1
  6. package/dist/components/AppFooter.svelte +26 -1
  7. package/dist/components/AppFooter.svelte.d.ts +5 -17
  8. package/dist/components/AppFooter.svelte.d.ts.map +1 -1
  9. package/dist/components/AppHeader.svelte +59 -1
  10. package/dist/components/AppHeader.svelte.d.ts.map +1 -1
  11. package/dist/components/BindingLines.svelte +13 -0
  12. package/dist/components/BusPicker.svelte +40 -0
  13. package/dist/components/BusPicker.svelte.d.ts +4 -1
  14. package/dist/components/BusPicker.svelte.d.ts.map +1 -1
  15. package/dist/components/ControlPanel.svelte +359 -72
  16. package/dist/components/ControlPanel.svelte.d.ts +2 -17
  17. package/dist/components/ControlPanel.svelte.d.ts.map +1 -1
  18. package/dist/components/FormulationSelector.svelte +17 -0
  19. package/dist/components/MulticonductorDetails.svelte +58 -5
  20. package/dist/components/MulticonductorDetails.svelte.d.ts.map +1 -1
  21. package/dist/components/SensitivityReadout.svelte +10 -1
  22. package/dist/components/SolveCard.svelte +2 -1
  23. package/dist/controller.svelte.d.ts +3 -0
  24. package/dist/controller.svelte.d.ts.map +1 -1
  25. package/dist/controller.svelte.js +22 -4
  26. package/dist/multiconductor.d.ts +13 -0
  27. package/dist/multiconductor.d.ts.map +1 -1
  28. package/dist/multiconductor.js +39 -9
  29. package/dist/state.svelte.d.ts +18 -0
  30. package/dist/state.svelte.d.ts.map +1 -1
  31. package/dist/state.svelte.js +22 -0
  32. package/dist/styles.css +16 -2
  33. package/dist/synthetic-layout.d.ts +3 -1
  34. package/dist/synthetic-layout.d.ts.map +1 -1
  35. package/dist/synthetic-layout.js +130 -11
  36. package/dist/transformer-icon.d.ts +16 -0
  37. package/dist/transformer-icon.d.ts.map +1 -0
  38. package/dist/transformer-icon.js +35 -0
  39. package/package.json +1 -1
@@ -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 class="panel">
21
- {#if app.error}
22
- <p class="error mono">{app.error}</p>
23
- <div class="error-actions">
24
- <button class="reset mono" onclick={ctrl.retryError}>retry</button>
25
- <button class="reset mono" onclick={() => (app.error = null)}>dismiss</button>
26
- </div>
27
- {/if}
28
- {#if app.parsingFile}
29
- <p class="dim mono blink">parsing&hellip;</p>
30
- {/if}
31
- {#if app.activeLocal}
32
- <LocalCaseDetails />
33
- {/if}
34
- {#if app.activeMulti}
35
- <MulticonductorDetails />
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
- {#if !ctrl.networkStats}
38
- {#if !app.error && !app.activeLocal && !app.activeMulti}
39
- {#if ctrl.casesLoaded && app.cases.length === 0}
40
- <p class="dim mono">
41
- {config.loadDefaultCases ? 'no default cases loaded' : 'drop a case file to begin'}
42
- </p>
43
- {#if config.loadDefaultCases}
44
- <button class="reset mono" onclick={ctrl.restoreDefaultCases}
45
- >restore default cases</button
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&hellip;</p>
50
234
  {:else}
51
- <p class="dim mono blink">loading cases&hellip;</p>
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
- {:else}
55
- {#if !app.activeLocal}
56
- <NetworkStats />
246
+ {#if app.parsingFile}
247
+ <p class="dim mono blink">parsing&hellip;</p>
57
248
  {/if}
58
249
 
59
- {#if ctrl.activeSolvable}
60
- <FormulationSelector />
61
- {/if}
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
- {#if !app.placingLocalId}
64
- <BindingLines />
255
+ <hr />
65
256
  {/if}
66
257
 
67
- <hr />
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&hellip;</p>
277
+ {:else}
278
+ <p class="dim mono blink">loading cases&hellip;</p>
279
+ {/if}
280
+ {/if}
281
+ {:else}
282
+ {#if !app.activeLocal}
283
+ <NetworkStats />
284
+ {/if}
68
285
 
69
- {#if app.selectedBus !== null && (ctrl.selectedSensitivity || app.sensitivityLoading)}
70
- <SensitivityReadout />
286
+ {#if ctrl.activeSolvable}
287
+ <FormulationSelector />
288
+ {/if}
71
289
 
72
- <DemandSlider />
290
+ {#if app.compactLayout}
291
+ <BusPicker inline />
292
+ {/if}
73
293
 
74
- {#if ctrl.showMoverSlot}
75
- <TopMovers />
294
+ {#if !app.placingLocalId}
295
+ <BindingLines />
76
296
  {/if}
77
- {:else if app.selectedBranch !== null && (ctrl.selectedSensitivity || app.sensitivityLoading)}
78
- <SensitivityReadout />
79
297
 
80
- <RatingSlider />
298
+ {#if !selectionLeads}
299
+ <hr />
81
300
 
82
- {#if ctrl.showMoverSlot}
83
- <TopMovers />
301
+ {@render selectionBlock()}
84
302
  {/if}
85
- {:else}
86
- <DisplayControls />
87
- {/if}
88
303
 
89
- <hr />
304
+ <hr />
90
305
 
91
- <SizeLegend />
92
- {/if}
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-y: auto;
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
- @media (max-width: 760px) {
130
- .panel {
131
- top: auto;
132
- left: 10px;
133
- right: 10px;
134
- bottom: 40px;
135
- width: auto;
136
- max-height: 44dvh;
137
- padding: 14px 16px;
138
- background: var(--paper);
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
- @media (max-width: 420px) {
143
- .panel {
144
- bottom: 34px;
145
- max-height: 46dvh;
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
- 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 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":"AAgHA,UAAU,kCAAkC,CAAC,KAAK,SAAS,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,GAAG,EAAE,MAAM,SAAS,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,GAAG,EAAE,KAAK,SAAS,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,GAAG,EAAE,OAAO,GAAG,EAAE,EAAE,QAAQ,GAAG,MAAM;IACpM,KAAK,OAAO,EAAE,OAAO,QAAQ,EAAE,2BAA2B,CAAC,KAAK,CAAC,GAAG,OAAO,QAAQ,EAAE,eAAe,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,CAAC,GAAG;QAAE,UAAU,CAAC,EAAE,QAAQ,CAAA;KAAE,GAAG,OAAO,CAAC;IACjK,CAAC,QAAQ,EAAE,OAAO,EAAE,KAAK,EAAE;QAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;QAAC,OAAO,CAAC,EAAE,KAAK,CAAA;KAAC,GAAG,OAAO,GAAG;QAAE,IAAI,CAAC,EAAE,GAAG,CAAC;QAAC,GAAG,CAAC,EAAE,GAAG,CAAA;KAAE,CAAC;IACtG,YAAY,CAAC,EAAE,QAAQ,CAAC;CAC3B;AAKD,QAAA,MAAM,YAAY;;kBAA+E,CAAC;AAChF,KAAK,YAAY,GAAG,YAAY,CAAC,OAAO,YAAY,CAAC,CAAC;AACxD,eAAe,YAAY,CAAC"}
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>
@@ -4,6 +4,7 @@
4
4
  import {
5
5
  attachmentColor,
6
6
  attachmentGlyph,
7
+ edgeColor,
7
8
  isPhaseTerminal,
8
9
  phaseColor
9
10
  } from '../multiconductor.js';
@@ -114,11 +115,39 @@
114
115
  {:else}
115
116
  <p class="footnote mono">no attachments on this bus</p>
116
117
  {/if}
118
+ {:else if mc.selectedEdge}
119
+ {@const e = mc.selectedEdge}
120
+ <hr />
121
+ <h3 class="mono">
122
+ <i class="swatch" style={`--sc:${rgbaCss([...edgeColor(e.kind, e.closed)])}`}></i>
123
+ {e.kind} {e.from}&#8201;&ndash;&#8201;{e.to}
124
+ </h3>
125
+ <dl class="mono">
126
+ <div>
127
+ <dt>phases</dt>
128
+ <dd>{e.n_phases}&#966; &#8901; {e.conductors.length} conductors</dd>
129
+ </div>
130
+ {#if e.kind === 'switch'}
131
+ <div>
132
+ <dt>state</dt>
133
+ <dd>{e.closed ? 'closed' : 'open'}</dd>
134
+ </div>
135
+ {/if}
136
+ </dl>
137
+ <div class="pairs">
138
+ {#each e.conductors as [ft, tt], i (i)}
139
+ <span class="pair">
140
+ <span class="term" style={`--tc:${terminalColor(ft)}`}>{ft}</span>
141
+ <span class="pair-dash mono">&ndash;</span>
142
+ <span class="term" style={`--tc:${terminalColor(tt)}`}>{tt}</span>
143
+ </span>
144
+ {/each}
145
+ </div>
117
146
  {:else if mc.placed}
118
- <p class="footnote mono">select a bus to expand its terminals and conductors</p>
147
+ <p class="footnote mono">select a bus or a line to expand its detail</p>
119
148
  {/if}
120
149
 
121
- <div class="legend mono">
150
+ <div class="mc-legend mono">
122
151
  <span class="legend-row">
123
152
  <i class="swatch" style={`--sc:${rgbaCss([...phaseColor('1')])}`}></i>a
124
153
  <i class="swatch" style={`--sc:${rgbaCss([...phaseColor('2')])}`}></i>b
@@ -134,8 +163,6 @@
134
163
  </span>
135
164
  </div>
136
165
 
137
- <p class="footnote mono">parsed in your browser by powerio (wasm); never uploaded</p>
138
-
139
166
  {#if !mc.placed}
140
167
  <button class="reset mono" onclick={() => ctrl.moveMultiCase(mc)}>place on map</button>
141
168
  {:else}
@@ -184,6 +211,8 @@
184
211
  dd {
185
212
  margin: 0;
186
213
  text-align: right;
214
+ /* Wrap the label instead of splitting a value sequence like 126 / 0 / 9. */
215
+ white-space: nowrap;
187
216
  }
188
217
 
189
218
  .terminals {
@@ -232,7 +261,9 @@
232
261
  letter-spacing: 0;
233
262
  }
234
263
 
235
- .legend {
264
+ /* The global stylesheet's .legend is the 6px color-ramp strip; its height
265
+ * leaks into a scoped .legend, so this container carries its own name. */
266
+ .mc-legend {
236
267
  display: flex;
237
268
  flex-direction: column;
238
269
  gap: 4px;
@@ -241,6 +272,28 @@
241
272
  color: var(--text-secondary);
242
273
  }
243
274
 
275
+ .pairs {
276
+ display: flex;
277
+ flex-wrap: wrap;
278
+ gap: 8px;
279
+ margin-top: 8px;
280
+ }
281
+
282
+ .pair {
283
+ display: inline-flex;
284
+ align-items: center;
285
+ gap: 3px;
286
+ }
287
+
288
+ .pair-dash {
289
+ color: var(--text-secondary);
290
+ }
291
+
292
+ h3 .swatch {
293
+ margin-right: 4px;
294
+ vertical-align: -1px;
295
+ }
296
+
244
297
  .legend-row {
245
298
  display: flex;
246
299
  align-items: center;
@@ -1 +1 @@
1
- {"version":3,"file":"MulticonductorDetails.svelte.d.ts","sourceRoot":"","sources":["../../src/lib/components/MulticonductorDetails.svelte.ts"],"names":[],"mappings":"AAyJA,UAAU,kCAAkC,CAAC,KAAK,SAAS,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,GAAG,EAAE,MAAM,SAAS,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,GAAG,EAAE,KAAK,SAAS,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,GAAG,EAAE,OAAO,GAAG,EAAE,EAAE,QAAQ,GAAG,MAAM;IACpM,KAAK,OAAO,EAAE,OAAO,QAAQ,EAAE,2BAA2B,CAAC,KAAK,CAAC,GAAG,OAAO,QAAQ,EAAE,eAAe,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,CAAC,GAAG;QAAE,UAAU,CAAC,EAAE,QAAQ,CAAA;KAAE,GAAG,OAAO,CAAC;IACjK,CAAC,QAAQ,EAAE,OAAO,EAAE,KAAK,EAAE;QAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;QAAC,OAAO,CAAC,EAAE,KAAK,CAAA;KAAC,GAAG,OAAO,GAAG;QAAE,IAAI,CAAC,EAAE,GAAG,CAAC;QAAC,GAAG,CAAC,EAAE,GAAG,CAAA;KAAE,CAAC;IACtG,YAAY,CAAC,EAAE,QAAQ,CAAC;CAC3B;AAKD,QAAA,MAAM,qBAAqB;;kBAA+E,CAAC;AACzF,KAAK,qBAAqB,GAAG,YAAY,CAAC,OAAO,qBAAqB,CAAC,CAAC;AAC1E,eAAe,qBAAqB,CAAC"}
1
+ {"version":3,"file":"MulticonductorDetails.svelte.d.ts","sourceRoot":"","sources":["../../src/lib/components/MulticonductorDetails.svelte.ts"],"names":[],"mappings":"AAoLA,UAAU,kCAAkC,CAAC,KAAK,SAAS,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,GAAG,EAAE,MAAM,SAAS,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,GAAG,EAAE,KAAK,SAAS,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,GAAG,EAAE,OAAO,GAAG,EAAE,EAAE,QAAQ,GAAG,MAAM;IACpM,KAAK,OAAO,EAAE,OAAO,QAAQ,EAAE,2BAA2B,CAAC,KAAK,CAAC,GAAG,OAAO,QAAQ,EAAE,eAAe,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,CAAC,GAAG;QAAE,UAAU,CAAC,EAAE,QAAQ,CAAA;KAAE,GAAG,OAAO,CAAC;IACjK,CAAC,QAAQ,EAAE,OAAO,EAAE,KAAK,EAAE;QAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;QAAC,OAAO,CAAC,EAAE,KAAK,CAAA;KAAC,GAAG,OAAO,GAAG;QAAE,IAAI,CAAC,EAAE,GAAG,CAAC;QAAC,GAAG,CAAC,EAAE,GAAG,CAAA;KAAE,CAAC;IACtG,YAAY,CAAC,EAAE,QAAQ,CAAC;CAC3B;AAKD,QAAA,MAAM,qBAAqB;;kBAA+E,CAAC;AACzF,KAAK,qBAAqB,GAAG,YAAY,CAAC,OAAO,qBAAqB,CAAC,CAAC;AAC1E,eAAe,qBAAqB,CAAC"}
@@ -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}>esc&nbsp;clear</button>
43
+ <button class="mono" onclick={ctrl.clearSelection}
44
+ ><span class="key-hint">esc&nbsp;</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>
@@ -75,7 +75,8 @@
75
75
 
76
76
  @media (max-width: 760px) {
77
77
  .solvecard {
78
- top: 184px;
78
+ /* just clear of the header's case tabs */
79
+ top: 108px;
79
80
  left: auto;
80
81
  right: 10px;
81
82
  width: min(300px, calc(100% - 20px));