@tellegen/svelte 0.4.1 → 0.4.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 (35) hide show
  1. package/README.md +38 -10
  2. package/dist/Sparkline.svelte +4 -4
  3. package/dist/TellegenMap.svelte +41 -46
  4. package/dist/TellegenProvider.svelte +29 -13
  5. package/dist/components/AppFooter.svelte +4 -4
  6. package/dist/components/AppHeader.svelte +15 -19
  7. package/dist/components/BindingLines.svelte +10 -9
  8. package/dist/components/BusPicker.svelte +5 -6
  9. package/dist/components/ControlPanel.svelte +12 -9
  10. package/dist/components/ControlPanel.svelte.d.ts +6 -1
  11. package/dist/components/DemandSlider.svelte +26 -29
  12. package/dist/components/DisplayControls.svelte +5 -24
  13. package/dist/components/DropZone.svelte +7 -3
  14. package/dist/components/DropZone.svelte.d.ts +6 -17
  15. package/dist/components/FormulationSelector.svelte +2 -7
  16. package/dist/components/LocalCaseDetails.svelte +126 -126
  17. package/dist/components/LocalCaseDetails.svelte.d.ts +6 -1
  18. package/dist/components/MulticonductorDetails.svelte +48 -38
  19. package/dist/components/NetworkStats.svelte +14 -16
  20. package/dist/components/PanelCard.svelte +1 -1
  21. package/dist/components/PlacementCue.svelte +1 -1
  22. package/dist/components/RatingSlider.svelte +22 -23
  23. package/dist/components/RestoreDefaultsButton.svelte +2 -3
  24. package/dist/components/SensitivityReadout.svelte +9 -27
  25. package/dist/components/SolveCard.svelte +69 -35
  26. package/dist/components/TopMovers.svelte +5 -5
  27. package/dist/context.svelte.d.ts +6 -2
  28. package/dist/context.svelte.js +4 -2
  29. package/dist/controller.svelte.d.ts +7 -0
  30. package/dist/controller.svelte.js +33 -6
  31. package/dist/display.js +8 -9
  32. package/dist/format.d.ts +1 -9
  33. package/dist/format.js +0 -13
  34. package/dist/styles.css +3 -0
  35. package/package.json +10 -6
@@ -23,48 +23,45 @@
23
23
  const neutralPos = $derived(thumbAlignedPos(neutralFrac));
24
24
  const fillLo = $derived(valFrac < neutralFrac ? valPos : neutralPos);
25
25
  const fillHi = $derived(valFrac < neutralFrac ? neutralPos : valPos);
26
- const sliderTip =
27
- 'The black tick marks the last committed demand. Drag the knob to preview a new demand; release to solve and move the tick to that point.';
28
- const disabledTip = 'this analysis bus is display-only; demand is not adjustable';
29
- const scoreTip =
30
- 'Gradient estimates the declared objective change before the solve finishes. It uses the selected bus LMP times the demand step, plus local curvature when the preview engine is unavailable. Exact is the resolved OPF objective change.';
26
+ const sliderTip = 'Drag to preview, release to solve. The tick marks the committed demand.';
27
+ const disabledTip = 'Display-only bus; demand is fixed';
28
+ const scoreTip = 'Gradient: first-order estimate from the LMP. Exact: solved objective change.';
31
29
  </script>
32
30
 
33
31
  {#if ctrl.activeSolvable}
34
32
  {@const c = ctrl.activeSolvable}
35
33
  <div class="slider-block">
36
- <div class="slider-head mono">
37
- <span>&Delta; demand</span>
38
- <span class="val">{signed(ctrl.sliderValue)} MW</span>
34
+ <div class="slider-head">
35
+ <span>Demand change</span>
36
+ <span class="val mono">{signed(ctrl.sliderValue)} MW</span>
39
37
  </div>
40
38
  <div class="range-mode">
41
- <div class="segment mono" role="group" aria-label="demand range">
39
+ <div class="segment" role="group" aria-label="Demand range">
42
40
  <button
43
41
  type="button"
44
42
  class:active={app.demandRangeMode === 'local'}
45
43
  aria-pressed={app.demandRangeMode === 'local'}
46
- aria-label="nearby demand range"
44
+ aria-label="Nearby demand range"
47
45
  {disabled}
48
- title="range near the selected demand setting"
49
- onclick={() => ctrl.setDemandRangeMode('local')}>nearby</button
46
+ title="Range near the committed demand"
47
+ onclick={() => ctrl.setDemandRangeMode('local')}>Nearby</button
50
48
  >
51
49
  <button
52
50
  type="button"
53
51
  class:active={app.demandRangeMode === 'full'}
54
52
  aria-pressed={app.demandRangeMode === 'full'}
55
- aria-label="full range of demand"
53
+ aria-label="Full demand range"
56
54
  {disabled}
57
- title="range from zero load to the local physical limit"
58
- onclick={() => ctrl.setDemandRangeMode('full')}>full range</button
55
+ title="Zero load to the local physical limit"
56
+ onclick={() => ctrl.setDemandRangeMode('full')}>Full range</button
59
57
  >
60
58
  </div>
61
- <span class="mono dim">
62
- {#if disabled}
63
- not adjustable
64
- {:else}
65
- {fmt.format(ctrl.sliderMin)} to {fmt.format(ctrl.sliderMax)} MW
66
- {/if}
67
- </span>
59
+ {#if disabled}
60
+ <span class="dim">Not adjustable</span>
61
+ {:else}
62
+ <span class="mono dim">{fmt.format(ctrl.sliderMin)} to {fmt.format(ctrl.sliderMax)} MW</span
63
+ >
64
+ {/if}
68
65
  </div>
69
66
  <div
70
67
  class="slider-track"
@@ -78,7 +75,7 @@
78
75
  step="any"
79
76
  {disabled}
80
77
  bind:value={ctrl.sliderCurrent, ctrl.setSliderPreview}
81
- aria-label="demand delta at selected bus"
78
+ aria-label="Demand delta at selected bus"
82
79
  onpointerdown={() => ctrl.setSliderPreview(ctrl.sliderValue)}
83
80
  onkeydown={(e) => {
84
81
  const next = sliderKeyValue(e.key, ctrl.sliderValue, ctrl.sliderMin, ctrl.sliderMax);
@@ -95,18 +92,18 @@
95
92
  />
96
93
  </div>
97
94
  <div class="demand-feedback" class:idle={!ctrl.previewing && !ctrl.isPerturbed(c)}>
98
- <p class="pred mono dim" aria-hidden={!(ctrl.predictedDeltaObj !== null && ctrl.previewing)}>
95
+ <p class="pred dim" aria-hidden={!(ctrl.predictedDeltaObj !== null && ctrl.previewing)}>
99
96
  {#if ctrl.predictedDeltaObj !== null && ctrl.previewing}
100
- predicted &Delta;objective {signed(ctrl.predictedDeltaObj)}
97
+ Predicted objective change <span class="mono">{signed(ctrl.predictedDeltaObj)}</span>
101
98
  {:else}
102
99
  &nbsp;
103
100
  {/if}
104
101
  </p>
105
- <p class="score mono" aria-hidden={!(ctrl.gradientScore && ctrl.isPerturbed(c))}>
102
+ <p class="score" aria-hidden={!(ctrl.gradientScore && ctrl.isPerturbed(c))}>
106
103
  {#if ctrl.gradientScore && ctrl.isPerturbed(c)}
107
104
  <span title={scoreTip}>
108
- gradient {signed(ctrl.gradientScore.pred)}, exact {signed(ctrl.gradientScore.exact)} objective
109
- units
105
+ Gradient <span class="mono">{signed(ctrl.gradientScore.pred)}</span>, exact
106
+ <span class="mono">{signed(ctrl.gradientScore.exact)}</span> objective units
110
107
  </span>
111
108
  {:else}
112
109
  &nbsp;
@@ -114,7 +111,7 @@
114
111
  </p>
115
112
  <div class="reset-row">
116
113
  {#if ctrl.isPerturbed(c)}
117
- <button class="reset mono" onclick={() => ctrl.resetCase(c)}>reset demand</button>
114
+ <button class="reset" onclick={() => ctrl.resetCase(c)}>Reset demand</button>
118
115
  {/if}
119
116
  </div>
120
117
  </div>
@@ -7,7 +7,7 @@
7
7
  </script>
8
8
 
9
9
  <div class="mode display-mode">
10
- <div class="segment mono" aria-label="bus color variable">
10
+ <div class="segment" aria-label="Bus color variable">
11
11
  {#each ctrl.displayOptions as option (option.mode)}
12
12
  <button
13
13
  type="button"
@@ -19,27 +19,16 @@
19
19
  </div>
20
20
  <span class="mono dim">{ctrl.activeDisplay?.unit ?? ''}</span>
21
21
  {#if app.sensitivityLoading}
22
- <span class="mono dim blink">&part; loading&hellip;</span>
22
+ <span class="dim blink">Loading&hellip;</span>
23
23
  {/if}
24
24
  </div>
25
25
  {#if ctrl.activeDisplay && ctrl.displayStats}
26
- {#if ctrl.activeDisplay.mode === 'price'}
27
- <div class="legend-heading">
28
- <span class="legend-title">Locational marginal price</span>
29
- <span
30
- class="hint-dot mono"
31
- title={ctrl.activeDisplay.copy}
32
- aria-label={ctrl.activeDisplay.copy}>i</span
33
- >
34
- </div>
35
- {:else}
36
- <p class="dim small">{ctrl.activeDisplay.copy}</p>
37
- {/if}
26
+ <p class="legend-title">{ctrl.activeDisplay.copy}</p>
38
27
  <div class="legend" style:background={ctrl.activeDisplay.gradient}></div>
39
28
  <div class="legend-labels mono">
40
29
  {#if ctrl.displayStats.uniform !== null}
41
30
  <span>
42
- uniform {displayFmt(ctrl.activeDisplay.mode, ctrl.displayStats.uniform)}
31
+ Uniform {displayFmt(ctrl.activeDisplay.mode, ctrl.displayStats.uniform)}
43
32
  {ctrl.activeDisplay.unit}
44
33
  </span>
45
34
  {:else}
@@ -70,18 +59,10 @@
70
59
  {/if}
71
60
 
72
61
  <style>
73
- .legend-heading {
74
- display: flex;
75
- align-items: center;
76
- gap: 6px;
77
- margin-top: 10px;
78
- }
79
-
80
62
  .legend-title {
63
+ margin: 10px 0 0;
81
64
  font-size: 12px;
82
65
  font-weight: 600;
83
66
  color: var(--ink);
84
67
  }
85
-
86
- /* .hint-dot lives in the shared stylesheet; SensitivityReadout uses it too. */
87
68
  </style>
@@ -1,14 +1,18 @@
1
1
  <script lang="ts">
2
+ import type { Snippet } from 'svelte';
2
3
  import { getAppState } from '../context.svelte.js';
3
4
 
5
+ /** Optional host content rendered under the drop prompt. */
6
+ let { children }: { children?: Snippet } = $props();
7
+
4
8
  const app = getAppState();
5
9
  </script>
6
10
 
7
11
  {#if app.dragOver}
8
12
  <div class="dropzone" aria-hidden="true">
9
13
  <div class="dropframe">
10
- <p class="mono">drop to parse: case files or geographic files</p>
11
- <p class="mono hint">parsed in your browser; the file never uploads</p>
14
+ <p>Drop case or geographic files</p>
15
+ {#if children}<div class="note">{@render children()}</div>{/if}
12
16
  </div>
13
17
  </div>
14
18
  {/if}
@@ -39,7 +43,7 @@
39
43
  font-size: 13px;
40
44
  }
41
45
 
42
- .dropframe .hint {
46
+ .note {
43
47
  font-size: 11px;
44
48
  color: var(--text-secondary);
45
49
  }
@@ -1,18 +1,7 @@
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 DropZone: $$__sveltets_2_IsomorphicComponent<Record<string, never>, {
15
- [evt: string]: CustomEvent<any>;
16
- }, {}, {}, string>;
17
- type DropZone = InstanceType<typeof DropZone>;
1
+ import type { Snippet } from 'svelte';
2
+ type $$ComponentProps = {
3
+ children?: Snippet;
4
+ };
5
+ declare const DropZone: import("svelte").Component<$$ComponentProps, {}, "">;
6
+ type DropZone = ReturnType<typeof DropZone>;
18
7
  export default DropZone;
@@ -9,15 +9,10 @@
9
9
  {#if ctrl.activeSolvable}
10
10
  {@const c = ctrl.activeSolvable}
11
11
  <div class="formulation">
12
- <label
13
- class="formulation-row mono"
14
- for="formulation-select"
15
- title={formulationHint(c.formulation)}
16
- >
12
+ <label class="formulation-row" for="formulation-select" title={formulationHint(c.formulation)}>
17
13
  <span>Calculation</span>
18
14
  <select
19
15
  id="formulation-select"
20
- class="mono"
21
16
  disabled={c.solving}
22
17
  value={c.formulation}
23
18
  onchange={(e) => ctrl.changeFormulation(c, e.currentTarget.value as Formulation)}
@@ -52,7 +47,7 @@
52
47
  }
53
48
 
54
49
  .formulation-row select {
55
- font-family: var(--font-mono);
50
+ font-family: inherit;
56
51
  font-size: 13px;
57
52
  padding: 3px 22px 3px 8px;
58
53
  border: 1px solid var(--line);
@@ -1,9 +1,13 @@
1
1
  <script lang="ts">
2
+ import type { Snippet } from 'svelte';
2
3
  import { getAppState, getController } from '../context.svelte.js';
3
4
  import { fmt } from '../format.js';
4
5
  import type { LocalCase } from '../state.svelte.js';
5
6
  import { formatPowerIoDiagnostic, type PowerIoDiagnostic } from '@tellegen/engine';
6
7
 
8
+ /** Optional host content rendered under the case stats. */
9
+ let { note }: { note?: Snippet<[LocalCase]> } = $props();
10
+
7
11
  const app = getAppState();
8
12
  const ctrl = getController();
9
13
 
@@ -22,6 +26,23 @@
22
26
  let exportOpen = $state(false);
23
27
  let exportDiagnostics = $state<PowerIoDiagnostic[]>([]);
24
28
 
29
+ /** Where the map coordinates came from. */
30
+ function coordinateSource(lc: LocalCase): string {
31
+ if (!lc.view) return 'None';
32
+ switch (lc.coordsKind) {
33
+ case 'file':
34
+ return 'From file';
35
+ case 'geofile':
36
+ return lc.geoSource ? `From ${lc.geoSource}` : 'From geographic file';
37
+ case 'manual':
38
+ return 'Manual layout';
39
+ case 'synthetic':
40
+ return lc.summary?.coords_method === 'generated' ? 'Generated' : 'Synthetic layout';
41
+ default:
42
+ return 'None';
43
+ }
44
+ }
45
+
25
46
  async function saveCase(lc: LocalCase) {
26
47
  busy = true;
27
48
  exportDiagnostics = [];
@@ -64,137 +85,128 @@
64
85
 
65
86
  {#if app.activeLocal}
66
87
  {@const lc = app.activeLocal}
67
- <h2>{lc.label} <span class="region mono">via {lc.fileName}</span></h2>
88
+ <h2>{lc.label}</h2>
68
89
  {#if lc.substations}
69
- <dl class="mono">
90
+ <dl>
91
+ <div>
92
+ <dt>Substations</dt>
93
+ <dd class="mono">{lc.substations.points.length}</dd>
94
+ </div>
95
+ <div>
96
+ <dt>Positions</dt>
97
+ <dd>Inferred from diagram</dd>
98
+ </div>
70
99
  <div>
71
- <dt>substations</dt>
72
- <dd>{lc.substations.points.length}</dd>
100
+ <dt>File</dt>
101
+ <dd class="mono">{lc.fileName}</dd>
73
102
  </div>
74
103
  </dl>
75
- <p class="footnote mono">
76
- display only: positions inferred from the PowerWorld diagram, not surveyed latitude and
77
- longitude
78
- </p>
79
- <p class="footnote mono">decoded in your browser by powerio (wasm); never uploaded</p>
80
104
  {:else if lc.summary}
81
- <dl class="mono">
105
+ <dl>
82
106
  <div>
83
- <dt>buses</dt>
84
- <dd>{lc.summary.n_bus}</dd>
107
+ <dt>Buses</dt>
108
+ <dd class="mono">{lc.summary.n_bus}</dd>
85
109
  </div>
86
110
  <div>
87
- <dt>branches</dt>
88
- <dd>{lc.summary.n_branch}</dd>
111
+ <dt>Branches</dt>
112
+ <dd class="mono">{lc.summary.n_branch}</dd>
89
113
  </div>
90
114
  <div>
91
- <dt>generators</dt>
92
- <dd>{lc.summary.n_gen}</dd>
115
+ <dt>Generators</dt>
116
+ <dd class="mono">{lc.summary.n_gen}</dd>
93
117
  </div>
94
118
  <div>
95
- <dt>load</dt>
96
- <dd>{fmt.format(lc.summary.load_mw)} MW</dd>
119
+ <dt>Load</dt>
120
+ <dd class="mono">{fmt.format(lc.summary.load_mw)} MW</dd>
97
121
  </div>
98
122
  <div>
99
- <dt>gen capacity</dt>
100
- <dd>{fmt.format(lc.summary.gen_mw)} MW</dd>
123
+ <dt>Generation capacity</dt>
124
+ <dd class="mono">{fmt.format(lc.summary.gen_mw)} MW</dd>
101
125
  </div>
102
126
  <div>
103
- <dt>base MVA</dt>
104
- <dd>{fmt.format(lc.summary.base_mva)}</dd>
127
+ <dt>Base MVA</dt>
128
+ <dd class="mono">{fmt.format(lc.summary.base_mva)}</dd>
105
129
  </div>
106
- </dl>
107
- {#if lc.displayMode === 'diagram' && lc.diagram}
108
- <p class="footnote mono">Drawing: {lc.diagram.name}</p>
109
- {#if lc.diagram.warnings.length}
110
- <details>
111
- <summary>Drawing details</summary>
112
- <ul class="warnings mono">
113
- {#each lc.diagram.warnings as warning, i (i)}<li>{warning}</li>{/each}
114
- </ul>
115
- </details>
116
- {/if}
117
- {:else if !lc.view}
118
- <p class="footnote mono">
119
- no coordinates in this file: click the map or drop a geographic file
120
- </p>
121
- {:else if lc.coordsKind === 'synthetic' && lc.summary.coords_method === 'generated'}
122
- <p class="footnote mono">coordinates: synthetic grid sited on real geography</p>
123
- {:else if lc.coordsKind === 'synthetic' || lc.coordsKind === 'manual'}
124
- <p class="footnote mono">
125
- coordinates: {lc.coordsKind} topology layout centered where it was placed
126
- </p>
127
- {:else if lc.coordsKind === 'geofile'}
128
- <p class="footnote mono">
129
- coordinates: geographic file data from {lc.geoSource}
130
- </p>
131
- {/if}
132
- <p class="footnote mono">parsed in your browser by powerio (wasm); never uploaded</p>
133
- {#if lc.studyInputJson}
134
- <div class="case-actions">
135
- <button class="reset mono" disabled={busy} onclick={() => saveCase(lc)}>
136
- save PowerIO module (.json)
137
- </button>
138
- {#if lc.formulation === 'dcopf' && lc.solution}
139
- <button class="reset mono" disabled={busy} onclick={() => saveSolution(lc)}>
140
- save exact solution (.json)
141
- </button>
142
- {/if}
143
- <div class="export">
144
- <button
145
- class="reset mono"
146
- disabled={busy}
147
- aria-expanded={exportOpen}
148
- onclick={() => (exportOpen = !exportOpen)}
149
- >
150
- export committed state…
151
- </button>
152
- {#if exportOpen}
153
- <ul class="export-menu mono">
154
- {#each EXPORT_FORMATS as f (f.token)}
155
- <li>
156
- <button
157
- class="reset mono"
158
- disabled={busy}
159
- onclick={() => exportCase(lc, f.token)}
160
- >
161
- {f.label}
162
- </button>
163
- </li>
164
- {/each}
165
- </ul>
166
- {/if}
130
+ {#if lc.displayMode === 'diagram' && lc.diagram}
131
+ <div>
132
+ <dt>Drawing</dt>
133
+ <dd>{lc.diagram.name}</dd>
167
134
  </div>
168
- {#if lc.view || lc.diagram}
169
- <button class="reset mono" disabled={busy} onclick={() => downloadLayout(lc)}>
170
- {lc.displayMode === 'diagram'
171
- ? 'Download drawing (.geo.json)'
172
- : 'Download geography (.geo.json)'}
173
- </button>
174
- {/if}
135
+ {:else}
136
+ <div>
137
+ <dt>Coordinates</dt>
138
+ <dd>{coordinateSource(lc)}</dd>
139
+ </div>
140
+ {/if}
141
+ <div>
142
+ <dt>File</dt>
143
+ <dd class="mono">{lc.fileName}</dd>
175
144
  </div>
176
- {#if exportDiagnostics.length > 0}
177
- <ul class="warnings mono">
178
- {#each exportDiagnostics.slice(0, 4) as diagnostic, i (i)}
179
- <li>{formatPowerIoDiagnostic(diagnostic)}</li>
180
- {/each}
181
- {#if exportDiagnostics.length > 4}
182
- <li>+{exportDiagnostics.length - 4} more</li>
183
- {/if}
145
+ </dl>
146
+ {#if lc.displayMode === 'diagram' && lc.diagram?.warnings.length}
147
+ <details>
148
+ <summary>Drawing details</summary>
149
+ <ul class="warnings">
150
+ {#each lc.diagram.warnings as warning, i (i)}<li>{warning}</li>{/each}
184
151
  </ul>
152
+ </details>
153
+ {/if}
154
+ {/if}
155
+ {@render note?.(lc)}
156
+ {#if lc.summary && lc.studyInputJson}
157
+ <div class="case-actions">
158
+ <button class="reset" disabled={busy} onclick={() => saveCase(lc)}>Save case</button>
159
+ {#if lc.formulation === 'dcopf' && lc.solution}
160
+ <button class="reset" disabled={busy} onclick={() => saveSolution(lc)}>Save solution</button
161
+ >
162
+ {/if}
163
+ <button
164
+ class="reset"
165
+ disabled={busy}
166
+ aria-expanded={exportOpen}
167
+ aria-controls="case-export-formats"
168
+ onclick={() => (exportOpen = !exportOpen)}
169
+ >
170
+ Export as
171
+ </button>
172
+ {#if lc.view || lc.diagram}
173
+ <button class="reset" disabled={busy} onclick={() => downloadLayout(lc)}>
174
+ {lc.displayMode === 'diagram' ? 'Save drawing' : 'Save geography'}
175
+ </button>
185
176
  {/if}
177
+ </div>
178
+ {#if exportOpen}
179
+ <ul class="case-actions" id="case-export-formats" aria-label="Export formats">
180
+ {#each EXPORT_FORMATS as f (f.token)}
181
+ <li>
182
+ <button class="reset" disabled={busy} onclick={() => exportCase(lc, f.token)}>
183
+ {f.label}
184
+ </button>
185
+ </li>
186
+ {/each}
187
+ </ul>
188
+ {/if}
189
+ {#if exportDiagnostics.length > 0}
190
+ <ul class="warnings">
191
+ {#each exportDiagnostics.slice(0, 4) as diagnostic, i (i)}
192
+ <li>{formatPowerIoDiagnostic(diagnostic)}</li>
193
+ {/each}
194
+ {#if exportDiagnostics.length > 4}
195
+ <li>+{exportDiagnostics.length - 4} more</li>
196
+ {/if}
197
+ </ul>
186
198
  {/if}
187
199
  {/if}
188
200
  {#if lc.displayMode !== 'diagram' && lc.topology && lc.coordsKind !== 'file'}
189
- <button class="reset mono" onclick={() => ctrl.moveLocalCase(lc)}>
201
+ <button class="reset" onclick={() => ctrl.moveLocalCase(lc)}>
190
202
  {lc.coordsKind === 'synthetic_pending'
191
- ? 'place on map'
203
+ ? 'Place on map'
192
204
  : lc.coordsKind === 'geofile'
193
- ? 'place manually'
194
- : 'move layout'}
205
+ ? 'Place manually'
206
+ : 'Move layout'}
195
207
  </button>
196
208
  {/if}
197
- <button class="reset mono" onclick={() => ctrl.removeLocalCase(lc)}>remove</button>
209
+ <button class="reset" onclick={() => ctrl.removeLocalCase(lc)}>Remove</button>
198
210
  {/if}
199
211
 
200
212
  <style>
@@ -212,22 +224,25 @@
212
224
  dl div {
213
225
  display: flex;
214
226
  justify-content: space-between;
227
+ gap: 12px;
215
228
  padding: 3px 0;
216
229
  }
217
230
 
218
231
  dt {
232
+ flex: none;
219
233
  color: var(--text-secondary);
220
234
  }
221
235
 
222
236
  dd {
237
+ min-width: 0;
223
238
  margin: 0;
239
+ text-align: right;
240
+ overflow-wrap: anywhere;
224
241
  }
225
242
 
226
- .footnote {
227
- margin: 8px 0 0;
243
+ details {
244
+ margin-top: 8px;
228
245
  font-size: 12px;
229
- color: var(--text-tertiary);
230
- letter-spacing: 0;
231
246
  }
232
247
 
233
248
  .warnings {
@@ -243,27 +258,12 @@
243
258
  display: flex;
244
259
  flex-wrap: wrap;
245
260
  gap: 8px;
246
- margin-top: 10px;
247
- }
248
-
249
- .export {
250
- position: relative;
251
- }
252
-
253
- .export-menu {
254
- position: absolute;
255
- z-index: 1;
256
- margin: 4px 0 0;
257
- padding: 4px;
261
+ margin: 10px 0 0;
262
+ padding: 0;
258
263
  list-style: none;
259
- min-width: 100%;
260
- background: var(--surface, var(--surface-menu));
261
- border: 1px solid var(--border, rgba(0, 0, 0, 0.15));
262
- border-radius: 6px;
263
- box-shadow: 0 6px 20px rgba(0, 0, 0, 0.18);
264
264
  }
265
265
 
266
- .export-menu li {
267
- white-space: nowrap;
266
+ .case-actions .reset {
267
+ margin-top: 0;
268
268
  }
269
269
  </style>
@@ -1,3 +1,8 @@
1
- declare const LocalCaseDetails: import("svelte").Component<Record<string, never>, {}, "">;
1
+ import type { Snippet } from 'svelte';
2
+ import type { LocalCase } from '../state.svelte.js';
3
+ type $$ComponentProps = {
4
+ note?: Snippet<[LocalCase]>;
5
+ };
6
+ declare const LocalCaseDetails: import("svelte").Component<$$ComponentProps, {}, "">;
2
7
  type LocalCaseDetails = ReturnType<typeof LocalCaseDetails>;
3
8
  export default LocalCaseDetails;