@tellegen/svelte 0.2.0 → 0.3.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 (66) hide show
  1. package/README.md +36 -5
  2. package/dist/TellegenMap.svelte +325 -163
  3. package/dist/TellegenProvider.svelte +4 -0
  4. package/dist/TellegenShell.svelte +19 -13
  5. package/dist/api.d.ts +8 -1
  6. package/dist/api.js +6 -2
  7. package/dist/colors.d.ts +7 -7
  8. package/dist/colors.js +13 -13
  9. package/dist/components/AppHeader.svelte +23 -11
  10. package/dist/components/BindingLines.svelte +3 -6
  11. package/dist/components/BusPicker.svelte +8 -2
  12. package/dist/components/ControlPanel.svelte +106 -343
  13. package/dist/components/DemandSlider.svelte +21 -23
  14. package/dist/components/DiagramCanvas.svelte +486 -0
  15. package/dist/components/DiagramCanvas.svelte.d.ts +13 -0
  16. package/dist/components/DisplayControls.svelte +4 -2
  17. package/dist/components/FormulationSelector.svelte +8 -3
  18. package/dist/components/LocalCaseDetails.svelte +55 -45
  19. package/dist/components/McResults.svelte +365 -0
  20. package/dist/components/McResults.svelte.d.ts +11 -0
  21. package/dist/components/ModelDetails.svelte +230 -0
  22. package/dist/components/ModelDetails.svelte.d.ts +7 -0
  23. package/dist/components/MulticonductorDetails.svelte +227 -78
  24. package/dist/components/NetworkStats.svelte +20 -18
  25. package/dist/components/NotificationCenter.svelte +243 -0
  26. package/dist/components/NotificationCenter.svelte.d.ts +3 -0
  27. package/dist/components/PanelCard.svelte +319 -0
  28. package/dist/components/PanelCard.svelte.d.ts +9 -0
  29. package/dist/components/PanelFrame.svelte +46 -0
  30. package/dist/components/PanelFrame.svelte.d.ts +16 -0
  31. package/dist/components/PanelHost.svelte +212 -0
  32. package/dist/components/PanelHost.svelte.d.ts +3 -0
  33. package/dist/components/PowerFlowDetails.svelte +99 -0
  34. package/dist/components/PowerFlowDetails.svelte.d.ts +3 -0
  35. package/dist/components/RatingSlider.svelte +5 -9
  36. package/dist/components/SavedNetworkDetails.svelte +214 -0
  37. package/dist/components/SavedNetworkDetails.svelte.d.ts +3 -0
  38. package/dist/components/SensitivityReadout.svelte +24 -16
  39. package/dist/components/SolveCard.svelte +46 -49
  40. package/dist/components/TopMovers.svelte +8 -3
  41. package/dist/components/TopMovers.svelte.d.ts +2 -17
  42. package/dist/components.d.ts +2 -0
  43. package/dist/components.js +2 -0
  44. package/dist/controller.svelte.d.ts +54 -33
  45. package/dist/controller.svelte.js +927 -233
  46. package/dist/diagram-labels.d.ts +9 -0
  47. package/dist/diagram-labels.js +40 -0
  48. package/dist/display.d.ts +3 -7
  49. package/dist/display.js +31 -27
  50. package/dist/format.js +5 -4
  51. package/dist/index.d.ts +10 -4
  52. package/dist/index.js +7 -2
  53. package/dist/map.d.ts +1 -1
  54. package/dist/map.js +1 -1
  55. package/dist/multiconductor.d.ts +11 -2
  56. package/dist/multiconductor.js +107 -14
  57. package/dist/notices.svelte.d.ts +29 -0
  58. package/dist/notices.svelte.js +82 -0
  59. package/dist/panels.svelte.d.ts +65 -0
  60. package/dist/panels.svelte.js +215 -0
  61. package/dist/state.svelte.d.ts +90 -23
  62. package/dist/state.svelte.js +94 -20
  63. package/dist/synthetic-layout.js +1 -0
  64. package/package.json +22 -9
  65. package/dist/study-package.d.ts +0 -8
  66. package/dist/study-package.js +0 -17
@@ -2,40 +2,49 @@
2
2
  import { getAppState, getController } from '../context.svelte.js';
3
3
  import { fmt } from '../format.js';
4
4
  import type { LocalCase } from '../state.svelte.js';
5
+ import { formatPowerIoDiagnostic, type PowerIoDiagnostic } from '@tellegen/engine';
5
6
 
6
7
  const app = getAppState();
7
8
  const ctrl = getController();
8
9
 
9
- // Export tokens the committed state writes to; labels carry their extensions.
10
- // `model-json` is powerio's own model document, not a case format.
10
+ // Grid exchange formats the committed state can export.
11
11
  const EXPORT_FORMATS = [
12
12
  { token: 'matpower', label: 'MATPOWER (.m)' },
13
13
  { token: 'psse', label: 'PSS/E (.raw)' },
14
14
  { token: 'powerworld', label: 'PowerWorld (.aux)' },
15
15
  { token: 'pslf', label: 'PSLF (.epc)' },
16
16
  { token: 'powermodels-json', label: 'PowerModels (.json)' },
17
- { token: 'pandapower-json', label: 'pandapower (.json)' },
18
- { token: 'model-json', label: 'PowerIO model (.json)' }
17
+ { token: 'pandapower-json', label: 'pandapower (.json)' }
19
18
  ];
20
19
 
21
20
  let busy = $state(false);
22
21
  let exportOpen = $state(false);
23
- let exportWarnings = $state<string[]>([]);
22
+ let exportDiagnostics = $state<PowerIoDiagnostic[]>([]);
24
23
 
25
- async function saveStudy(lc: LocalCase) {
24
+ async function saveCase(lc: LocalCase) {
26
25
  busy = true;
27
- exportWarnings = [];
26
+ exportDiagnostics = [];
28
27
  try {
29
- await ctrl.saveStudyPackage(lc);
28
+ await ctrl.saveCaseModule(lc);
30
29
  } finally {
31
30
  busy = false;
32
31
  }
33
32
  }
34
33
 
35
- async function exportStudy(lc: LocalCase, token: string) {
34
+ async function saveSolution(lc: LocalCase) {
36
35
  busy = true;
36
+ exportDiagnostics = [];
37
37
  try {
38
- exportWarnings = await ctrl.exportStudyAs(lc, token);
38
+ await ctrl.saveSolutionModule(lc);
39
+ } finally {
40
+ busy = false;
41
+ }
42
+ }
43
+
44
+ async function exportCase(lc: LocalCase, token: string) {
45
+ busy = true;
46
+ try {
47
+ exportDiagnostics = await ctrl.exportCaseAs(lc, token);
39
48
  exportOpen = false;
40
49
  } finally {
41
50
  busy = false;
@@ -94,17 +103,17 @@
94
103
  <dd>{fmt.format(lc.summary.base_mva)}</dd>
95
104
  </div>
96
105
  </dl>
97
- {#if lc.summary.warnings.length > 0}
98
- <ul class="warnings mono">
99
- {#each lc.summary.warnings.slice(0, 4) as w, i (i)}
100
- <li>{w}</li>
101
- {/each}
102
- {#if lc.summary.warnings.length > 4}
103
- <li>+{lc.summary.warnings.length - 4} more</li>
104
- {/if}
105
- </ul>
106
- {/if}
107
- {#if !lc.view}
106
+ {#if lc.displayMode === 'diagram' && lc.diagram}
107
+ <p class="footnote mono">Drawing: {lc.diagram.name}</p>
108
+ {#if lc.diagram.warnings.length}
109
+ <details>
110
+ <summary>Drawing details</summary>
111
+ <ul class="warnings mono">
112
+ {#each lc.diagram.warnings as warning, i (i)}<li>{warning}</li>{/each}
113
+ </ul>
114
+ </details>
115
+ {/if}
116
+ {:else if !lc.view}
108
117
  <p class="footnote mono">
109
118
  no coordinates in this file: click the map or drop a geographic file
110
119
  </p>
@@ -117,22 +126,17 @@
117
126
  coordinates: geographic file data from {lc.geoSource}
118
127
  </p>
119
128
  {/if}
120
- {#if lc.geoWarnings && lc.geoWarnings.length > 0}
121
- <ul class="warnings mono">
122
- {#each lc.geoWarnings.slice(0, 4) as w, i (i)}
123
- <li>{w}</li>
124
- {/each}
125
- {#if lc.geoWarnings.length > 4}
126
- <li>+{lc.geoWarnings.length - 4} more</li>
127
- {/if}
128
- </ul>
129
- {/if}
130
129
  <p class="footnote mono">parsed in your browser by powerio (wasm); never uploaded</p>
131
- {#if lc.networkJson}
132
- <div class="study">
133
- <button class="reset mono" disabled={busy} onclick={() => saveStudy(lc)}>
134
- save study (.pio.json)
130
+ {#if lc.studyInputJson}
131
+ <div class="case-actions">
132
+ <button class="reset mono" disabled={busy} onclick={() => saveCase(lc)}>
133
+ save PowerIO module (.json)
135
134
  </button>
135
+ {#if lc.formulation === 'dcopf' && lc.solution}
136
+ <button class="reset mono" disabled={busy} onclick={() => saveSolution(lc)}>
137
+ save exact solution (.json)
138
+ </button>
139
+ {/if}
136
140
  <div class="export">
137
141
  <button
138
142
  class="reset mono"
@@ -146,7 +150,11 @@
146
150
  <ul class="export-menu mono">
147
151
  {#each EXPORT_FORMATS as f (f.token)}
148
152
  <li>
149
- <button class="reset mono" disabled={busy} onclick={() => exportStudy(lc, f.token)}>
153
+ <button
154
+ class="reset mono"
155
+ disabled={busy}
156
+ onclick={() => exportCase(lc, f.token)}
157
+ >
150
158
  {f.label}
151
159
  </button>
152
160
  </li>
@@ -154,25 +162,27 @@
154
162
  </ul>
155
163
  {/if}
156
164
  </div>
157
- {#if lc.view}
165
+ {#if lc.view || lc.diagram}
158
166
  <button class="reset mono" disabled={busy} onclick={() => downloadLayout(lc)}>
159
- download layout (.geo.json)
167
+ {lc.displayMode === 'diagram'
168
+ ? 'Download drawing (.geo.json)'
169
+ : 'Download geography (.geo.json)'}
160
170
  </button>
161
171
  {/if}
162
172
  </div>
163
- {#if exportWarnings.length > 0}
173
+ {#if exportDiagnostics.length > 0}
164
174
  <ul class="warnings mono">
165
- {#each exportWarnings.slice(0, 4) as w, i (i)}
166
- <li>{w}</li>
175
+ {#each exportDiagnostics.slice(0, 4) as diagnostic, i (i)}
176
+ <li>{formatPowerIoDiagnostic(diagnostic)}</li>
167
177
  {/each}
168
- {#if exportWarnings.length > 4}
169
- <li>+{exportWarnings.length - 4} more</li>
178
+ {#if exportDiagnostics.length > 4}
179
+ <li>+{exportDiagnostics.length - 4} more</li>
170
180
  {/if}
171
181
  </ul>
172
182
  {/if}
173
183
  {/if}
174
184
  {/if}
175
- {#if lc.topology && lc.coordsKind !== 'file'}
185
+ {#if lc.displayMode !== 'diagram' && lc.topology && lc.coordsKind !== 'file'}
176
186
  <button class="reset mono" onclick={() => ctrl.moveLocalCase(lc)}>
177
187
  {lc.coordsKind === 'synthetic_pending'
178
188
  ? 'place on map'
@@ -226,7 +236,7 @@
226
236
  color: var(--text-accent);
227
237
  }
228
238
 
229
- .study {
239
+ .case-actions {
230
240
  display: flex;
231
241
  flex-wrap: wrap;
232
242
  gap: 8px;
@@ -0,0 +1,365 @@
1
+ <script lang="ts">
2
+ import type { McPfResult, McComplex, DistGraph } from '@tellegen/engine';
3
+ import { neutralTerminal } from '../multiconductor.js';
4
+ let {
5
+ result,
6
+ elapsedMs,
7
+ selectedBus,
8
+ selectedEdge,
9
+ graph
10
+ }: {
11
+ result: McPfResult;
12
+ elapsedMs: number | null;
13
+ selectedBus: string | null;
14
+ selectedEdge: string | null;
15
+ graph: DistGraph | null;
16
+ } = $props();
17
+ let busChoice = $state('');
18
+ let edgeChoice = $state('');
19
+ const busId = $derived(
20
+ selectedBus ??
21
+ (graph?.buses.some((b) => b.id === busChoice) ? busChoice : graph?.buses[0]?.id) ??
22
+ ''
23
+ );
24
+ const bus = $derived(graph?.buses.find((b) => b.id === busId));
25
+ const neutralName = $derived(bus ? neutralTerminal(bus.terminals, bus.neutral_terminal) : null);
26
+ const neutral = $derived(
27
+ result.terminals.find((t) => t.bus === busId && t.terminal === neutralName)?.voltage
28
+ );
29
+ const edgeId = $derived(selectedEdge ?? edgeChoice);
30
+ const sourcePower = $derived(
31
+ result.source_reactions.reduce(
32
+ (sum, port) => ({
33
+ re: sum.re + port.power_into_network.re,
34
+ im: sum.im + port.power_into_network.im
35
+ }),
36
+ { re: 0, im: 0 }
37
+ )
38
+ );
39
+ const passiveLoss = $derived(
40
+ result.element_ports
41
+ .filter((port) => !['load', 'generator', 'ibr'].includes(port.kind))
42
+ .reduce((sum, port) => sum + port.power_into_element.re, 0)
43
+ );
44
+ let search = $state('');
45
+ let page = $state(0);
46
+ const size = 20;
47
+ const terminals = $derived(
48
+ result.terminals.filter(
49
+ (row) =>
50
+ (!busId || row.bus === busId) &&
51
+ `${row.bus} ${row.terminal}`.toLowerCase().includes(search.toLowerCase())
52
+ )
53
+ );
54
+ const pageCount = $derived(Math.max(1, Math.ceil(terminals.length / size)));
55
+ const currentPage = $derived(Math.min(page, pageCount - 1));
56
+ const visible = $derived(terminals.slice(currentPage * size, (currentPage + 1) * size));
57
+ const ports = $derived(result.element_ports.filter((row) => !edgeId || row.element === edgeId));
58
+ let portPage = $state(0);
59
+ const portPages = $derived(Math.max(1, Math.ceil(ports.length / size)));
60
+ const currentPortPage = $derived(Math.min(portPage, portPages - 1));
61
+ const visiblePorts = $derived(ports.slice(currentPortPage * size, (currentPortPage + 1) * size));
62
+ const magnitude = (value: McComplex) => Math.hypot(value.re, value.im);
63
+ const fixed = (value: number) => (Number.isFinite(value) ? value.toFixed(3) : '-');
64
+ const angle = (value: McComplex) =>
65
+ magnitude(value) > 1e-12 ? ((Math.atan2(value.im, value.re) * 180) / Math.PI).toFixed(2) : '-';
66
+ </script>
67
+
68
+ <section aria-label="AC power flow results" class="mc-results">
69
+ <div class="result-heading">
70
+ <strong>AC power flow</strong><span
71
+ >{result.iterations} iterations{elapsedMs === null
72
+ ? ''
73
+ : `, ${Math.round(elapsedMs)} ms`}</span
74
+ >
75
+ </div>
76
+ <dl class="totals">
77
+ <div>
78
+ <dt>Status</dt>
79
+ <dd>{result.converged ? 'Converged' : 'Not converged'}</dd>
80
+ </div>
81
+ <div>
82
+ <dt>Voltage band</dt>
83
+ <dd>{result.voltage_valid ? 'Valid' : `${result.voltage_violations.length} violations`}</dd>
84
+ </div>
85
+ <div>
86
+ <dt>Load voltage range</dt>
87
+ <dd>{result.min_voltage_pu == null || result.max_voltage_pu == null ? 'n/a' : `${fixed(result.min_voltage_pu)}–${fixed(result.max_voltage_pu)} pu`}</dd>
88
+ </div>
89
+ <div>
90
+ <dt>Source P / Q</dt>
91
+ <dd>{fixed(sourcePower.re / 1000)} / {fixed(sourcePower.im / 1000)} kW / kvar</dd>
92
+ </div>
93
+ <div>
94
+ <dt>Passive loss</dt>
95
+ <dd>{fixed(passiveLoss / 1000)} kW</dd>
96
+ </div>
97
+ </dl>
98
+ <label class="search"
99
+ >Bus result<select
100
+ value={busId}
101
+ onchange={(event) => {
102
+ busChoice = event.currentTarget.value;
103
+ selectedBus = null;
104
+ page = 0;
105
+ }}
106
+ >{#each graph?.buses ?? [] as bus (bus.id)}<option value={bus.id}>{bus.id}</option
107
+ >{/each}</select
108
+ ></label
109
+ >
110
+ <label class="search"
111
+ >Terminals<input
112
+ aria-label="Find terminal"
113
+ type="search"
114
+ placeholder="Bus or terminal"
115
+ bind:value={search}
116
+ oninput={() => (page = 0)}
117
+ /></label
118
+ >
119
+ <!-- Scrollable result tables accept keyboard navigation. -->
120
+ <!-- svelte-ignore a11y_no_noninteractive_tabindex -->
121
+ <div class="scroll" role="region" aria-label="Terminal result columns" tabindex="0">
122
+ <table aria-label="Terminal results">
123
+ <thead
124
+ ><tr
125
+ ><th>Terminal</th><th>To ground<br />V</th><th>Angle<br />deg</th><th
126
+ >Net current<br />A</th
127
+ ></tr
128
+ ></thead
129
+ >
130
+ <tbody
131
+ >{#each visible as row (JSON.stringify([row.bus, row.terminal]))}
132
+ <tr
133
+ ><td>{row.terminal}</td><td>{fixed(magnitude(row.voltage))}</td><td
134
+ >{angle(row.voltage)}</td
135
+ ><td>{fixed(magnitude(row.current_into_network))}</td></tr
136
+ >
137
+ {/each}</tbody
138
+ >
139
+ </table>
140
+ </div>
141
+ <div class="pagination">
142
+ <span>{terminals.length} terminals{busId ? ` at bus ${busId}` : ''}</span
143
+ >{#if pageCount > 1}<button
144
+ disabled={currentPage === 0}
145
+ onclick={() => (page = currentPage - 1)}
146
+ aria-label="Previous terminals">Previous</button
147
+ ><span>{currentPage + 1} / {pageCount}</span><button
148
+ disabled={currentPage + 1 >= pageCount}
149
+ onclick={() => (page = currentPage + 1)}
150
+ aria-label="Next terminals">Next</button
151
+ >{/if}
152
+ </div>
153
+ {#if neutral}
154
+ <details>
155
+ <summary>Voltages to neutral</summary>
156
+ <!-- Scrollable result tables accept keyboard navigation. -->
157
+ <!-- svelte-ignore a11y_no_noninteractive_tabindex -->
158
+ <div class="scroll" role="region" aria-label="Neutral voltage columns" tabindex="0">
159
+ <table aria-label="Neutral voltage results">
160
+ <thead><tr><th>Terminal</th><th>To neutral<br />V</th><th>Angle<br />deg</th></tr></thead>
161
+ <tbody
162
+ >{#each visible as row (JSON.stringify([row.bus, row.terminal]))}
163
+ {@const relative = {
164
+ re: row.voltage.re - neutral.re,
165
+ im: row.voltage.im - neutral.im
166
+ }}
167
+ <tr
168
+ ><td>{row.terminal}</td><td>{fixed(magnitude(relative))}</td><td
169
+ >{angle(relative)}</td
170
+ ></tr
171
+ >
172
+ {/each}</tbody
173
+ >
174
+ </table>
175
+ </div>
176
+ </details>
177
+ {/if}
178
+ <details>
179
+ <summary>Equipment currents and power</summary>
180
+ <label class="search"
181
+ >Branch result<select
182
+ value={edgeId}
183
+ onchange={(event) => {
184
+ edgeChoice = event.currentTarget.value;
185
+ selectedEdge = null;
186
+ portPage = 0;
187
+ }}
188
+ ><option value="">All equipment</option>{#each graph?.edges ?? [] as edge (edge.id)}<option
189
+ value={edge.id}>{edge.from} - {edge.to}, {edge.id}</option
190
+ >{/each}</select
191
+ ></label
192
+ >
193
+ <p>Current enters the named equipment at each bus terminal.</p>
194
+ <!-- Scrollable result tables accept keyboard navigation. -->
195
+ <!-- svelte-ignore a11y_no_noninteractive_tabindex -->
196
+ <div class="scroll" role="region" aria-label="Equipment current columns" tabindex="0">
197
+ <table aria-label="Equipment currents">
198
+ <thead
199
+ ><tr
200
+ ><th>Equipment</th><th>Bus</th><th>Terminal</th><th>Current<br />A</th><th>P<br />kW</th
201
+ ><th>Q<br />kvar</th></tr
202
+ ></thead
203
+ ><tbody
204
+ >{#each visiblePorts as row (JSON.stringify( [row.kind, row.element, row.branch, row.bus, row.terminal] ))}<tr
205
+ ><td>{row.kind} {row.element}</td><td>{row.bus}</td><td>{row.terminal}</td><td
206
+ >{fixed(magnitude(row.current_into_element))}</td
207
+ ><td>{fixed(row.power_into_element.re / 1000)}</td><td
208
+ >{fixed(row.power_into_element.im / 1000)}</td
209
+ ></tr
210
+ >{/each}</tbody
211
+ >
212
+ </table>
213
+ </div>
214
+ <div class="pagination">
215
+ <span>{ports.length} connections</span>{#if portPages > 1}<button
216
+ disabled={currentPortPage === 0}
217
+ onclick={() => (portPage = currentPortPage - 1)}
218
+ aria-label="Previous currents">Previous</button
219
+ ><span>{currentPortPage + 1} / {portPages}</span><button
220
+ disabled={currentPortPage + 1 >= portPages}
221
+ onclick={() => (portPage = currentPortPage + 1)}
222
+ aria-label="Next currents">Next</button
223
+ >{/if}
224
+ </div>
225
+ </details>
226
+ <details>
227
+ <summary>Calculation details</summary>
228
+ <dl>
229
+ <div>
230
+ <dt>Maximum KCL residual</dt>
231
+ <dd>{result.physical_kcl_residual.toExponential(3)} A</dd>
232
+ </div>
233
+ <div>
234
+ <dt>Scaled KCL residual</dt>
235
+ <dd>{result.scaled_kcl_residual.toExponential(3)}</dd>
236
+ </div>
237
+ <div>
238
+ <dt>Voltage change</dt>
239
+ <dd>{result.voltage_change.toExponential(3)} V</dd>
240
+ </div>
241
+ <div>
242
+ <dt>Matrix size</dt>
243
+ <dd>{result.matrix_dimension}</dd>
244
+ </div>
245
+ <div>
246
+ <dt>Nonzero entries</dt>
247
+ <dd>{result.matrix_nonzeros}</dd>
248
+ </div>
249
+ <div>
250
+ <dt>Factorizations</dt>
251
+ <dd>{result.factorization_count}</dd>
252
+ </div>
253
+ </dl>
254
+ <p>
255
+ Voltage is measured to ground. Net current is the injection into the network. The calculation
256
+ checks current balance at unconstrained terminals.
257
+ </p>
258
+ </details>
259
+ </section>
260
+
261
+ <style>
262
+ .mc-results {
263
+ margin: 14px 0;
264
+ padding-top: 12px;
265
+ border-top: 1px solid var(--line);
266
+ font-size: 11px;
267
+ }
268
+ .result-heading,
269
+ .pagination,
270
+ dl div {
271
+ display: flex;
272
+ align-items: center;
273
+ justify-content: space-between;
274
+ gap: 8px;
275
+ }
276
+ .result-heading span,
277
+ .pagination,
278
+ dt,
279
+ p {
280
+ color: var(--text-secondary);
281
+ }
282
+ .search {
283
+ display: flex;
284
+ flex-direction: column;
285
+ gap: 5px;
286
+ margin: 12px 0 8px;
287
+ }
288
+ input,
289
+ select,
290
+ button {
291
+ font: inherit;
292
+ color: var(--ink);
293
+ background: var(--paper);
294
+ border: 1px solid var(--line);
295
+ border-radius: 3px;
296
+ padding: 5px 7px;
297
+ }
298
+ button {
299
+ cursor: pointer;
300
+ }
301
+ button:disabled {
302
+ opacity: 0.4;
303
+ cursor: default;
304
+ }
305
+ input,
306
+ select {
307
+ width: 100%;
308
+ box-sizing: border-box;
309
+ }
310
+ .scroll {
311
+ overflow: auto;
312
+ max-width: 100%;
313
+ }
314
+ .scroll:focus-visible {
315
+ outline: 2px solid var(--accent);
316
+ outline-offset: 2px;
317
+ }
318
+ table {
319
+ width: 100%;
320
+ border-collapse: collapse;
321
+ font: 10px var(--font-mono);
322
+ }
323
+ th,
324
+ td {
325
+ padding: 6px 5px;
326
+ text-align: right;
327
+ white-space: nowrap;
328
+ border-bottom: 1px solid var(--line);
329
+ }
330
+ th:first-child,
331
+ td:first-child {
332
+ text-align: left;
333
+ }
334
+ th {
335
+ color: var(--text-secondary);
336
+ font-weight: 500;
337
+ }
338
+ .pagination {
339
+ margin: 8px 0;
340
+ justify-content: flex-end;
341
+ }
342
+ .pagination > span:first-child {
343
+ margin-right: auto;
344
+ }
345
+ details {
346
+ margin-top: 12px;
347
+ }
348
+ summary {
349
+ cursor: pointer;
350
+ font-weight: 500;
351
+ }
352
+ p {
353
+ line-height: 1.5;
354
+ margin: 8px 0;
355
+ }
356
+ dl {
357
+ font: 10px var(--font-mono);
358
+ }
359
+ dl div {
360
+ padding: 4px 0;
361
+ }
362
+ dd {
363
+ margin: 0;
364
+ }
365
+ </style>
@@ -0,0 +1,11 @@
1
+ import type { McPfResult, DistGraph } from '@tellegen/engine';
2
+ type $$ComponentProps = {
3
+ result: McPfResult;
4
+ elapsedMs: number | null;
5
+ selectedBus: string | null;
6
+ selectedEdge: string | null;
7
+ graph: DistGraph | null;
8
+ };
9
+ declare const McResults: import("svelte").Component<$$ComponentProps, {}, "">;
10
+ type McResults = ReturnType<typeof McResults>;
11
+ export default McResults;