@tellegen/svelte 0.1.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.
- package/LICENSE +19 -0
- package/README.md +70 -0
- package/dist/Sparkline.svelte +100 -0
- package/dist/Sparkline.svelte.d.ts +8 -0
- package/dist/Sparkline.svelte.d.ts.map +1 -0
- package/dist/TellegenMap.svelte +1430 -0
- package/dist/TellegenMap.svelte.d.ts +14 -0
- package/dist/TellegenMap.svelte.d.ts.map +1 -0
- package/dist/TellegenProvider.svelte +38 -0
- package/dist/TellegenProvider.svelte.d.ts +9 -0
- package/dist/TellegenProvider.svelte.d.ts.map +1 -0
- package/dist/TellegenShell.svelte +126 -0
- package/dist/TellegenShell.svelte.d.ts +4 -0
- package/dist/TellegenShell.svelte.d.ts.map +1 -0
- package/dist/TellegenViewer.svelte +19 -0
- package/dist/TellegenViewer.svelte.d.ts +4 -0
- package/dist/TellegenViewer.svelte.d.ts.map +1 -0
- package/dist/api.d.ts +44 -0
- package/dist/api.d.ts.map +1 -0
- package/dist/api.js +137 -0
- package/dist/colors.d.ts +51 -0
- package/dist/colors.d.ts.map +1 -0
- package/dist/colors.js +140 -0
- package/dist/components/AppFooter.svelte +73 -0
- package/dist/components/AppFooter.svelte.d.ts +19 -0
- package/dist/components/AppFooter.svelte.d.ts.map +1 -0
- package/dist/components/AppHeader.svelte +405 -0
- package/dist/components/AppHeader.svelte.d.ts +4 -0
- package/dist/components/AppHeader.svelte.d.ts.map +1 -0
- package/dist/components/BindingLines.svelte +101 -0
- package/dist/components/BindingLines.svelte.d.ts +4 -0
- package/dist/components/BindingLines.svelte.d.ts.map +1 -0
- package/dist/components/BusPicker.svelte +227 -0
- package/dist/components/BusPicker.svelte.d.ts +4 -0
- package/dist/components/BusPicker.svelte.d.ts.map +1 -0
- package/dist/components/ControlPanel.svelte +154 -0
- package/dist/components/ControlPanel.svelte.d.ts +19 -0
- package/dist/components/ControlPanel.svelte.d.ts.map +1 -0
- package/dist/components/DemandSlider.svelte +271 -0
- package/dist/components/DemandSlider.svelte.d.ts +4 -0
- package/dist/components/DemandSlider.svelte.d.ts.map +1 -0
- package/dist/components/DisplayControls.svelte +81 -0
- package/dist/components/DisplayControls.svelte.d.ts +19 -0
- package/dist/components/DisplayControls.svelte.d.ts.map +1 -0
- package/dist/components/DropZone.svelte +46 -0
- package/dist/components/DropZone.svelte.d.ts +19 -0
- package/dist/components/DropZone.svelte.d.ts.map +1 -0
- package/dist/components/FormulationSelector.svelte +82 -0
- package/dist/components/FormulationSelector.svelte.d.ts +19 -0
- package/dist/components/FormulationSelector.svelte.d.ts.map +1 -0
- package/dist/components/LocalCaseDetails.svelte +253 -0
- package/dist/components/LocalCaseDetails.svelte.d.ts +4 -0
- package/dist/components/LocalCaseDetails.svelte.d.ts.map +1 -0
- package/dist/components/MulticonductorDetails.svelte +280 -0
- package/dist/components/MulticonductorDetails.svelte.d.ts +19 -0
- package/dist/components/MulticonductorDetails.svelte.d.ts.map +1 -0
- package/dist/components/NetworkStats.svelte +75 -0
- package/dist/components/NetworkStats.svelte.d.ts +19 -0
- package/dist/components/NetworkStats.svelte.d.ts.map +1 -0
- package/dist/components/PlacementCue.svelte +35 -0
- package/dist/components/PlacementCue.svelte.d.ts +19 -0
- package/dist/components/PlacementCue.svelte.d.ts.map +1 -0
- package/dist/components/RatingSlider.svelte +273 -0
- package/dist/components/RatingSlider.svelte.d.ts +4 -0
- package/dist/components/RatingSlider.svelte.d.ts.map +1 -0
- package/dist/components/RestoreDefaultsButton.svelte +34 -0
- package/dist/components/RestoreDefaultsButton.svelte.d.ts +19 -0
- package/dist/components/RestoreDefaultsButton.svelte.d.ts.map +1 -0
- package/dist/components/SensitivityReadout.svelte +126 -0
- package/dist/components/SensitivityReadout.svelte.d.ts +4 -0
- package/dist/components/SensitivityReadout.svelte.d.ts.map +1 -0
- package/dist/components/SizeLegend.svelte +55 -0
- package/dist/components/SizeLegend.svelte.d.ts +19 -0
- package/dist/components/SizeLegend.svelte.d.ts.map +1 -0
- package/dist/components/SolveCard.svelte +84 -0
- package/dist/components/SolveCard.svelte.d.ts +4 -0
- package/dist/components/SolveCard.svelte.d.ts.map +1 -0
- package/dist/components/TopMovers.svelte +68 -0
- package/dist/components/TopMovers.svelte.d.ts +19 -0
- package/dist/components/TopMovers.svelte.d.ts.map +1 -0
- package/dist/components.d.ts +19 -0
- package/dist/components.d.ts.map +1 -0
- package/dist/components.js +18 -0
- package/dist/context.svelte.d.ts +20 -0
- package/dist/context.svelte.d.ts.map +1 -0
- package/dist/context.svelte.js +43 -0
- package/dist/controller.svelte.d.ts +334 -0
- package/dist/controller.svelte.d.ts.map +1 -0
- package/dist/controller.svelte.js +2026 -0
- package/dist/display.d.ts +31 -0
- package/dist/display.d.ts.map +1 -0
- package/dist/display.js +63 -0
- package/dist/drop-classify.d.ts +48 -0
- package/dist/drop-classify.d.ts.map +1 -0
- package/dist/drop-classify.js +92 -0
- package/dist/format.d.ts +30 -0
- package/dist/format.d.ts.map +1 -0
- package/dist/format.js +49 -0
- package/dist/index.d.ts +21 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +12 -0
- package/dist/map.d.ts +9 -0
- package/dist/map.d.ts.map +1 -0
- package/dist/map.js +4 -0
- package/dist/multiconductor.d.ts +63 -0
- package/dist/multiconductor.d.ts.map +1 -0
- package/dist/multiconductor.js +255 -0
- package/dist/state.svelte.d.ts +258 -0
- package/dist/state.svelte.d.ts.map +1 -0
- package/dist/state.svelte.js +323 -0
- package/dist/study-package.d.ts +9 -0
- package/dist/study-package.d.ts.map +1 -0
- package/dist/study-package.js +17 -0
- package/dist/styles.css +393 -0
- package/dist/synthetic-layout.d.ts +12 -0
- package/dist/synthetic-layout.d.ts.map +1 -0
- package/dist/synthetic-layout.js +271 -0
- package/dist/types.d.ts +6 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +1 -0
- package/package.json +67 -0
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { onMount } from 'svelte';
|
|
3
|
+
import { getAppState, getController } from '../context.svelte.js';
|
|
4
|
+
|
|
5
|
+
const app = getAppState();
|
|
6
|
+
const ctrl = getController();
|
|
7
|
+
|
|
8
|
+
const buses = $derived(app.active?.network?.buses ?? app.activeLocal?.view?.buses ?? []);
|
|
9
|
+
|
|
10
|
+
let query = $state('');
|
|
11
|
+
let open = $state(false);
|
|
12
|
+
let highlighted = $state(0);
|
|
13
|
+
let inputEl = $state.raw<HTMLInputElement | undefined>(undefined);
|
|
14
|
+
|
|
15
|
+
const results = $derived(
|
|
16
|
+
query.trim() === ''
|
|
17
|
+
? []
|
|
18
|
+
: buses.filter((b) => String(b.id).startsWith(query.trim())).slice(0, 30)
|
|
19
|
+
);
|
|
20
|
+
const activeIndex = $derived(Math.max(0, Math.min(highlighted, results.length - 1)));
|
|
21
|
+
|
|
22
|
+
function commit(id: number) {
|
|
23
|
+
const caseId = app.activeCaseId;
|
|
24
|
+
const localId = app.activeLocalId;
|
|
25
|
+
if (caseId && app.active) ctrl.selectBus(caseId, id);
|
|
26
|
+
else if (localId && app.activeLocal) ctrl.selectLocalBus(localId, id);
|
|
27
|
+
query = '';
|
|
28
|
+
open = false;
|
|
29
|
+
highlighted = 0;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function onKeydown(e: KeyboardEvent) {
|
|
33
|
+
if (!open || results.length === 0) return;
|
|
34
|
+
if (e.key === 'ArrowDown') {
|
|
35
|
+
e.preventDefault();
|
|
36
|
+
highlighted = (activeIndex + 1) % results.length;
|
|
37
|
+
} else if (e.key === 'ArrowUp') {
|
|
38
|
+
e.preventDefault();
|
|
39
|
+
highlighted = (activeIndex - 1 + results.length) % results.length;
|
|
40
|
+
} else if (e.key === 'Enter') {
|
|
41
|
+
e.preventDefault();
|
|
42
|
+
commit(results[activeIndex].id);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
onMount(() => {
|
|
47
|
+
function onWindowKeydown(e: KeyboardEvent) {
|
|
48
|
+
if (e.defaultPrevented || e.metaKey || e.ctrlKey || e.altKey || e.key !== '/') return;
|
|
49
|
+
const target = e.target;
|
|
50
|
+
if (
|
|
51
|
+
target instanceof HTMLElement &&
|
|
52
|
+
(target.isContentEditable || target.closest('input, textarea, select'))
|
|
53
|
+
) {
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
if (!inputEl || buses.length === 0) return;
|
|
57
|
+
e.preventDefault();
|
|
58
|
+
open = true;
|
|
59
|
+
highlighted = 0;
|
|
60
|
+
inputEl.focus();
|
|
61
|
+
inputEl.select();
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
window.addEventListener('keydown', onWindowKeydown);
|
|
65
|
+
return () => window.removeEventListener('keydown', onWindowKeydown);
|
|
66
|
+
});
|
|
67
|
+
</script>
|
|
68
|
+
|
|
69
|
+
{#if buses.length > 0}
|
|
70
|
+
<div
|
|
71
|
+
class="bus-lookup"
|
|
72
|
+
title="Press / to focus bus lookup"
|
|
73
|
+
onfocusout={(e) => {
|
|
74
|
+
if (!e.currentTarget.contains(e.relatedTarget as Node | null)) open = false;
|
|
75
|
+
}}
|
|
76
|
+
>
|
|
77
|
+
<label class="mono dim" for="bus-lookup-input">bus lookup</label>
|
|
78
|
+
<input
|
|
79
|
+
id="bus-lookup-input"
|
|
80
|
+
class="mono"
|
|
81
|
+
type="text"
|
|
82
|
+
inputmode="numeric"
|
|
83
|
+
autocomplete="off"
|
|
84
|
+
placeholder="bus id"
|
|
85
|
+
role="combobox"
|
|
86
|
+
aria-keyshortcuts="/"
|
|
87
|
+
aria-expanded={open}
|
|
88
|
+
aria-controls="bus-lookup-listbox"
|
|
89
|
+
aria-activedescendant={open && results.length > 0
|
|
90
|
+
? `bus-lookup-opt-${results[activeIndex].id}`
|
|
91
|
+
: undefined}
|
|
92
|
+
bind:this={inputEl}
|
|
93
|
+
bind:value={query}
|
|
94
|
+
onfocus={() => (open = true)}
|
|
95
|
+
oninput={() => {
|
|
96
|
+
open = true;
|
|
97
|
+
highlighted = 0;
|
|
98
|
+
}}
|
|
99
|
+
onkeydown={onKeydown}
|
|
100
|
+
/>
|
|
101
|
+
{#if open && results.length > 0}
|
|
102
|
+
<ul class="mono" id="bus-lookup-listbox" role="listbox" aria-label="matching buses">
|
|
103
|
+
{#each results as bus, i (bus.id)}
|
|
104
|
+
<li
|
|
105
|
+
id="bus-lookup-opt-{bus.id}"
|
|
106
|
+
role="option"
|
|
107
|
+
aria-selected={i === activeIndex}
|
|
108
|
+
class:active={i === activeIndex}
|
|
109
|
+
onmousedown={(e) => {
|
|
110
|
+
e.preventDefault();
|
|
111
|
+
commit(bus.id);
|
|
112
|
+
}}
|
|
113
|
+
>
|
|
114
|
+
bus {bus.id}
|
|
115
|
+
</li>
|
|
116
|
+
{/each}
|
|
117
|
+
</ul>
|
|
118
|
+
{:else if open && query.trim() !== ''}
|
|
119
|
+
<p class="mono dim empty">no bus matches "{query.trim()}"</p>
|
|
120
|
+
{/if}
|
|
121
|
+
</div>
|
|
122
|
+
{/if}
|
|
123
|
+
|
|
124
|
+
<style>
|
|
125
|
+
.bus-lookup {
|
|
126
|
+
position: absolute;
|
|
127
|
+
right: 72px;
|
|
128
|
+
bottom: 18px;
|
|
129
|
+
z-index: var(--z-chrome);
|
|
130
|
+
width: 246px;
|
|
131
|
+
box-sizing: border-box;
|
|
132
|
+
padding: 7px 10px;
|
|
133
|
+
background: var(--panel);
|
|
134
|
+
border: 1px solid var(--line);
|
|
135
|
+
border-radius: var(--radius-sm, 3px);
|
|
136
|
+
backdrop-filter: blur(6px);
|
|
137
|
+
box-shadow: var(--elev-2);
|
|
138
|
+
animation: rise var(--dur-med) var(--ease-out) both;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
label {
|
|
142
|
+
display: block;
|
|
143
|
+
font-size: 10px;
|
|
144
|
+
text-transform: uppercase;
|
|
145
|
+
margin-bottom: 3px;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
input {
|
|
149
|
+
width: 100%;
|
|
150
|
+
padding: 4px 8px;
|
|
151
|
+
font-size: 12.5px;
|
|
152
|
+
background: var(--surface-control);
|
|
153
|
+
border: 1px solid var(--line);
|
|
154
|
+
border-radius: var(--radius-xs, 2px);
|
|
155
|
+
color: var(--ink);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
input:focus-visible {
|
|
159
|
+
outline: 2px solid var(--focus-ring);
|
|
160
|
+
outline-offset: 1px;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
ul {
|
|
164
|
+
position: absolute;
|
|
165
|
+
z-index: 20;
|
|
166
|
+
top: calc(100% + 2px);
|
|
167
|
+
left: 0;
|
|
168
|
+
right: 0;
|
|
169
|
+
max-height: 180px;
|
|
170
|
+
overflow-y: auto;
|
|
171
|
+
margin: 0;
|
|
172
|
+
padding: 4px 0;
|
|
173
|
+
list-style: none;
|
|
174
|
+
background: var(--panel);
|
|
175
|
+
border: 1px solid var(--line);
|
|
176
|
+
border-radius: var(--radius-xs, 2px);
|
|
177
|
+
box-shadow: 0 4px 18px rgba(32, 36, 43, 0.14);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
li {
|
|
181
|
+
padding: 5px 10px;
|
|
182
|
+
font-size: 12px;
|
|
183
|
+
cursor: pointer;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
li.active {
|
|
187
|
+
background: var(--accent-soft);
|
|
188
|
+
color: var(--text-accent);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
.empty {
|
|
192
|
+
position: absolute;
|
|
193
|
+
top: calc(100% + 2px);
|
|
194
|
+
left: 0;
|
|
195
|
+
right: 0;
|
|
196
|
+
padding: 6px 8px;
|
|
197
|
+
background: var(--panel);
|
|
198
|
+
border: 1px solid var(--line);
|
|
199
|
+
border-radius: var(--radius-xs, 2px);
|
|
200
|
+
box-shadow: var(--elev-1);
|
|
201
|
+
font-size: 10.5px;
|
|
202
|
+
margin: 4px 0 0;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
@media (max-width: 760px) {
|
|
206
|
+
.bus-lookup {
|
|
207
|
+
top: 106px;
|
|
208
|
+
left: 10px;
|
|
209
|
+
right: 10px;
|
|
210
|
+
bottom: auto;
|
|
211
|
+
width: auto;
|
|
212
|
+
padding: 8px 10px 10px;
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
@media (max-width: 420px) {
|
|
217
|
+
.bus-lookup {
|
|
218
|
+
top: 106px;
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
@media (prefers-reduced-motion: reduce) {
|
|
223
|
+
.bus-lookup {
|
|
224
|
+
animation: none;
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
</style>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"BusPicker.svelte.d.ts","sourceRoot":"","sources":["../../src/lib/components/BusPicker.svelte.ts"],"names":[],"mappings":"AA0GA,QAAA,MAAM,SAAS,2DAAwC,CAAC;AACxD,KAAK,SAAS,GAAG,UAAU,CAAC,OAAO,SAAS,CAAC,CAAC;AAC9C,eAAe,SAAS,CAAC"}
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { getAppState, getController, getUiConfig } from '../context.svelte.js';
|
|
3
|
+
import BindingLines from './BindingLines.svelte';
|
|
4
|
+
import DemandSlider from './DemandSlider.svelte';
|
|
5
|
+
import DisplayControls from './DisplayControls.svelte';
|
|
6
|
+
import FormulationSelector from './FormulationSelector.svelte';
|
|
7
|
+
import LocalCaseDetails from './LocalCaseDetails.svelte';
|
|
8
|
+
import MulticonductorDetails from './MulticonductorDetails.svelte';
|
|
9
|
+
import NetworkStats from './NetworkStats.svelte';
|
|
10
|
+
import RatingSlider from './RatingSlider.svelte';
|
|
11
|
+
import SensitivityReadout from './SensitivityReadout.svelte';
|
|
12
|
+
import SizeLegend from './SizeLegend.svelte';
|
|
13
|
+
import TopMovers from './TopMovers.svelte';
|
|
14
|
+
|
|
15
|
+
const app = getAppState();
|
|
16
|
+
const ctrl = getController();
|
|
17
|
+
const config = getUiConfig();
|
|
18
|
+
</script>
|
|
19
|
+
|
|
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…</p>
|
|
30
|
+
{/if}
|
|
31
|
+
{#if app.activeLocal}
|
|
32
|
+
<LocalCaseDetails />
|
|
33
|
+
{/if}
|
|
34
|
+
{#if app.activeMulti}
|
|
35
|
+
<MulticonductorDetails />
|
|
36
|
+
{/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
|
+
>
|
|
47
|
+
{/if}
|
|
48
|
+
{:else if ctrl.loadingBackendCase}
|
|
49
|
+
<p class="dim mono blink">loading selected case…</p>
|
|
50
|
+
{:else}
|
|
51
|
+
<p class="dim mono blink">loading cases…</p>
|
|
52
|
+
{/if}
|
|
53
|
+
{/if}
|
|
54
|
+
{:else}
|
|
55
|
+
{#if !app.activeLocal}
|
|
56
|
+
<NetworkStats />
|
|
57
|
+
{/if}
|
|
58
|
+
|
|
59
|
+
{#if ctrl.activeSolvable}
|
|
60
|
+
<FormulationSelector />
|
|
61
|
+
{/if}
|
|
62
|
+
|
|
63
|
+
{#if !app.placingLocalId}
|
|
64
|
+
<BindingLines />
|
|
65
|
+
{/if}
|
|
66
|
+
|
|
67
|
+
<hr />
|
|
68
|
+
|
|
69
|
+
{#if app.selectedBus !== null && (ctrl.selectedSensitivity || app.sensitivityLoading)}
|
|
70
|
+
<SensitivityReadout />
|
|
71
|
+
|
|
72
|
+
<DemandSlider />
|
|
73
|
+
|
|
74
|
+
{#if ctrl.showMoverSlot}
|
|
75
|
+
<TopMovers />
|
|
76
|
+
{/if}
|
|
77
|
+
{:else if app.selectedBranch !== null && (ctrl.selectedSensitivity || app.sensitivityLoading)}
|
|
78
|
+
<SensitivityReadout />
|
|
79
|
+
|
|
80
|
+
<RatingSlider />
|
|
81
|
+
|
|
82
|
+
{#if ctrl.showMoverSlot}
|
|
83
|
+
<TopMovers />
|
|
84
|
+
{/if}
|
|
85
|
+
{:else}
|
|
86
|
+
<DisplayControls />
|
|
87
|
+
{/if}
|
|
88
|
+
|
|
89
|
+
<hr />
|
|
90
|
+
|
|
91
|
+
<SizeLegend />
|
|
92
|
+
{/if}
|
|
93
|
+
</aside>
|
|
94
|
+
|
|
95
|
+
<style>
|
|
96
|
+
.panel {
|
|
97
|
+
position: absolute;
|
|
98
|
+
top: 76px;
|
|
99
|
+
left: 20px;
|
|
100
|
+
z-index: 10;
|
|
101
|
+
width: 312px;
|
|
102
|
+
max-height: calc(100% - 122px);
|
|
103
|
+
overflow-y: auto;
|
|
104
|
+
padding: 16px 18px;
|
|
105
|
+
background: var(--panel);
|
|
106
|
+
border: 1px solid var(--line);
|
|
107
|
+
border-radius: 3px;
|
|
108
|
+
backdrop-filter: blur(6px);
|
|
109
|
+
box-shadow: 0 4px 24px rgba(32, 36, 43, 0.08);
|
|
110
|
+
animation: rise 0.5s 0.12s ease-out both;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
hr {
|
|
114
|
+
border: 0;
|
|
115
|
+
border-top: 1px solid var(--line);
|
|
116
|
+
margin: 12px 0;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
.error {
|
|
120
|
+
color: var(--red);
|
|
121
|
+
font-size: 12px;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
.error-actions {
|
|
125
|
+
display: flex;
|
|
126
|
+
gap: 6px;
|
|
127
|
+
}
|
|
128
|
+
|
|
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
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
@media (max-width: 420px) {
|
|
143
|
+
.panel {
|
|
144
|
+
bottom: 34px;
|
|
145
|
+
max-height: 46dvh;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
@media (prefers-reduced-motion: reduce) {
|
|
150
|
+
.panel {
|
|
151
|
+
animation: none;
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
</style>
|
|
@@ -0,0 +1,19 @@
|
|
|
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>;
|
|
18
|
+
export default ControlPanel;
|
|
19
|
+
//# sourceMappingURL=ControlPanel.svelte.d.ts.map
|
|
@@ -0,0 +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"}
|
|
@@ -0,0 +1,271 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { getAppState, getController } from '../context.svelte.js';
|
|
3
|
+
import { fmt, signed } from '../format.js';
|
|
4
|
+
|
|
5
|
+
const app = getAppState();
|
|
6
|
+
const ctrl = getController();
|
|
7
|
+
|
|
8
|
+
// Fill from the committed demand to the live thumb. The local number line stays
|
|
9
|
+
// stable after release; only the committed tick moves.
|
|
10
|
+
const thumbSizePx = 14;
|
|
11
|
+
const sliderSpan = $derived(Math.max(ctrl.sliderMax - ctrl.sliderMin, 1e-9));
|
|
12
|
+
const valFrac = $derived(
|
|
13
|
+
Math.max(0, Math.min(1, (ctrl.sliderValue - ctrl.sliderMin) / sliderSpan))
|
|
14
|
+
);
|
|
15
|
+
const neutralFrac = $derived(
|
|
16
|
+
Math.max(0, Math.min(1, (ctrl.committedDelta - ctrl.sliderMin) / sliderSpan))
|
|
17
|
+
);
|
|
18
|
+
const thumbAlignedPos = (frac: number) =>
|
|
19
|
+
`calc(${frac * 100}% + ${(0.5 - frac) * thumbSizePx}px)`;
|
|
20
|
+
const valPos = $derived(thumbAlignedPos(valFrac));
|
|
21
|
+
const neutralPos = $derived(thumbAlignedPos(neutralFrac));
|
|
22
|
+
const fillLo = $derived(valFrac < neutralFrac ? valPos : neutralPos);
|
|
23
|
+
const fillHi = $derived(valFrac < neutralFrac ? neutralPos : valPos);
|
|
24
|
+
const sliderTip =
|
|
25
|
+
'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.';
|
|
26
|
+
const scoreTip =
|
|
27
|
+
'Gradient is the estimate of total cost change versus base 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.';
|
|
28
|
+
</script>
|
|
29
|
+
|
|
30
|
+
{#if ctrl.activeSolvable}
|
|
31
|
+
{@const c = ctrl.activeSolvable}
|
|
32
|
+
<div class="slider-block">
|
|
33
|
+
<div class="slider-head mono">
|
|
34
|
+
<span>Δ demand</span>
|
|
35
|
+
<span class="val">{signed(ctrl.sliderValue)} MW</span>
|
|
36
|
+
</div>
|
|
37
|
+
<div class="range-mode">
|
|
38
|
+
<div class="segment mono" aria-label="demand range">
|
|
39
|
+
<button
|
|
40
|
+
type="button"
|
|
41
|
+
class:active={app.demandRangeMode === 'local'}
|
|
42
|
+
aria-pressed={app.demandRangeMode === 'local'}
|
|
43
|
+
aria-label="nearby demand range"
|
|
44
|
+
title="range near the selected demand setting"
|
|
45
|
+
onclick={() => ctrl.setDemandRangeMode('local')}>nearby</button
|
|
46
|
+
>
|
|
47
|
+
<button
|
|
48
|
+
type="button"
|
|
49
|
+
class:active={app.demandRangeMode === 'full'}
|
|
50
|
+
aria-pressed={app.demandRangeMode === 'full'}
|
|
51
|
+
aria-label="full demand range"
|
|
52
|
+
title="range from zero load to the local physical limit"
|
|
53
|
+
onclick={() => ctrl.setDemandRangeMode('full')}>full range</button
|
|
54
|
+
>
|
|
55
|
+
</div>
|
|
56
|
+
<span class="mono dim">{fmt.format(ctrl.sliderMin)} to {fmt.format(ctrl.sliderMax)} MW</span>
|
|
57
|
+
</div>
|
|
58
|
+
<div
|
|
59
|
+
class="slider-track"
|
|
60
|
+
style="--fill-lo:{fillLo}; --fill-hi:{fillHi}; --neutral-pos:{neutralPos}"
|
|
61
|
+
title={sliderTip}
|
|
62
|
+
>
|
|
63
|
+
<input
|
|
64
|
+
type="range"
|
|
65
|
+
min={ctrl.sliderMin}
|
|
66
|
+
max={ctrl.sliderMax}
|
|
67
|
+
step="any"
|
|
68
|
+
bind:value={ctrl.sliderCurrent, ctrl.setSliderPreview}
|
|
69
|
+
aria-label="demand delta at selected bus"
|
|
70
|
+
onpointerdown={() => ctrl.setSliderPreview(ctrl.sliderValue)}
|
|
71
|
+
onkeydown={() => ctrl.setSliderPreview(ctrl.sliderValue)}
|
|
72
|
+
onpointerup={(e) => ctrl.finishDemandInput(Number(e.currentTarget.value))}
|
|
73
|
+
onkeyup={(e) => ctrl.finishDemandInput(Number(e.currentTarget.value))}
|
|
74
|
+
onchange={(e) => ctrl.finishDemandInput(Number(e.currentTarget.value))}
|
|
75
|
+
/>
|
|
76
|
+
</div>
|
|
77
|
+
<div class="demand-feedback" class:idle={!ctrl.previewing && !ctrl.isPerturbed(c)}>
|
|
78
|
+
<p class="pred mono dim" aria-hidden={!(ctrl.predictedDeltaObj !== null && ctrl.previewing)}>
|
|
79
|
+
{#if ctrl.predictedDeltaObj !== null && ctrl.previewing}
|
|
80
|
+
predicted Δcost {signed(ctrl.predictedDeltaObj)} $/h
|
|
81
|
+
{:else}
|
|
82
|
+
|
|
83
|
+
{/if}
|
|
84
|
+
</p>
|
|
85
|
+
<p class="score mono" aria-hidden={!(ctrl.gradientScore && ctrl.isPerturbed(c))}>
|
|
86
|
+
{#if ctrl.gradientScore && ctrl.isPerturbed(c)}
|
|
87
|
+
<span title={scoreTip}>
|
|
88
|
+
gradient {signed(ctrl.gradientScore.pred)} · exact {signed(
|
|
89
|
+
ctrl.gradientScore.exact
|
|
90
|
+
)}
|
|
91
|
+
$/h
|
|
92
|
+
</span>
|
|
93
|
+
{:else}
|
|
94
|
+
|
|
95
|
+
{/if}
|
|
96
|
+
</p>
|
|
97
|
+
<div class="reset-row">
|
|
98
|
+
{#if ctrl.isPerturbed(c)}
|
|
99
|
+
<button class="reset mono" onclick={() => ctrl.resetCase(c)}>reset demand</button>
|
|
100
|
+
{/if}
|
|
101
|
+
</div>
|
|
102
|
+
</div>
|
|
103
|
+
</div>
|
|
104
|
+
{/if}
|
|
105
|
+
|
|
106
|
+
<style>
|
|
107
|
+
.slider-block {
|
|
108
|
+
margin-top: 14px;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
.slider-head {
|
|
112
|
+
display: flex;
|
|
113
|
+
justify-content: space-between;
|
|
114
|
+
font-size: 11.5px;
|
|
115
|
+
color: var(--text-secondary);
|
|
116
|
+
margin-bottom: 4px;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
.slider-head .val {
|
|
120
|
+
color: var(--ink);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
.range-mode {
|
|
124
|
+
display: flex;
|
|
125
|
+
align-items: center;
|
|
126
|
+
justify-content: space-between;
|
|
127
|
+
gap: 8px;
|
|
128
|
+
margin: 6px 0 7px;
|
|
129
|
+
font-size: 10.5px;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
.slider-track {
|
|
133
|
+
position: relative;
|
|
134
|
+
margin: 6px 0;
|
|
135
|
+
isolation: isolate;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
.slider-track::before {
|
|
139
|
+
content: '';
|
|
140
|
+
position: absolute;
|
|
141
|
+
top: 50%;
|
|
142
|
+
left: var(--neutral-pos, 50%);
|
|
143
|
+
z-index: 0;
|
|
144
|
+
width: 2px;
|
|
145
|
+
height: 16px;
|
|
146
|
+
background: var(--ink);
|
|
147
|
+
border-radius: 1px;
|
|
148
|
+
box-shadow: 0 0 0 1px rgb(var(--paper-rgb) / 0.72);
|
|
149
|
+
transform: translate(-50%, -50%);
|
|
150
|
+
pointer-events: none;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
input[type='range'] {
|
|
154
|
+
-webkit-appearance: none;
|
|
155
|
+
appearance: none;
|
|
156
|
+
position: relative;
|
|
157
|
+
z-index: 1;
|
|
158
|
+
width: 100%;
|
|
159
|
+
height: 4px;
|
|
160
|
+
padding: 7px 0;
|
|
161
|
+
box-sizing: content-box;
|
|
162
|
+
background:
|
|
163
|
+
linear-gradient(
|
|
164
|
+
90deg,
|
|
165
|
+
transparent 0 var(--fill-lo, 0%),
|
|
166
|
+
rgb(var(--accent-rgb) / 0.32) var(--fill-lo, 0%) var(--fill-hi, 0%),
|
|
167
|
+
transparent var(--fill-hi, 0%) 100%
|
|
168
|
+
),
|
|
169
|
+
var(--line);
|
|
170
|
+
background-clip: content-box;
|
|
171
|
+
border-radius: var(--radius-xs);
|
|
172
|
+
outline-offset: 4px;
|
|
173
|
+
margin: 0;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
input[type='range']::-webkit-slider-thumb {
|
|
177
|
+
-webkit-appearance: none;
|
|
178
|
+
appearance: none;
|
|
179
|
+
width: 14px;
|
|
180
|
+
height: 14px;
|
|
181
|
+
border-radius: 50%;
|
|
182
|
+
background: var(--accent);
|
|
183
|
+
border: 2px solid var(--paper);
|
|
184
|
+
box-shadow: var(--shadow-thumb);
|
|
185
|
+
cursor: ew-resize;
|
|
186
|
+
transition:
|
|
187
|
+
transform var(--dur-fast) var(--ease-out),
|
|
188
|
+
box-shadow var(--dur-fast) var(--ease-out);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
input[type='range']::-moz-range-thumb {
|
|
192
|
+
width: 14px;
|
|
193
|
+
height: 14px;
|
|
194
|
+
border-radius: 50%;
|
|
195
|
+
background: var(--accent);
|
|
196
|
+
border: 2px solid var(--paper);
|
|
197
|
+
box-shadow: var(--shadow-thumb);
|
|
198
|
+
cursor: ew-resize;
|
|
199
|
+
transition:
|
|
200
|
+
transform var(--dur-fast) var(--ease-out),
|
|
201
|
+
box-shadow var(--dur-fast) var(--ease-out);
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
input[type='range']:active::-webkit-slider-thumb,
|
|
205
|
+
input[type='range']:focus-visible::-webkit-slider-thumb {
|
|
206
|
+
transform: scale(1.15);
|
|
207
|
+
box-shadow: 0 2px 7px rgb(var(--ink-rgb) / 0.35);
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
input[type='range']:active::-moz-range-thumb,
|
|
211
|
+
input[type='range']:focus-visible::-moz-range-thumb {
|
|
212
|
+
transform: scale(1.15);
|
|
213
|
+
box-shadow: 0 2px 7px rgb(var(--ink-rgb) / 0.35);
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
/* Coarse pointers: a larger track and thumb for a comfortable touch target.
|
|
217
|
+
The input's own border box grows to a 44px tap target (padding is symmetric,
|
|
218
|
+
so the thumb -- centered on the input's box by default -- stays centered on
|
|
219
|
+
the thinner painted line); background-clip keeps the visible line itself thin. */
|
|
220
|
+
@media (hover: none), (pointer: coarse) {
|
|
221
|
+
input[type='range'] {
|
|
222
|
+
height: 6px;
|
|
223
|
+
padding: 19px 0;
|
|
224
|
+
box-sizing: content-box;
|
|
225
|
+
background-clip: content-box;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
input[type='range']::-webkit-slider-thumb {
|
|
229
|
+
width: 20px;
|
|
230
|
+
height: 20px;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
input[type='range']::-moz-range-thumb {
|
|
234
|
+
width: 20px;
|
|
235
|
+
height: 20px;
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
.pred {
|
|
240
|
+
margin: 2px 0 0;
|
|
241
|
+
font-size: 11px;
|
|
242
|
+
min-height: 16px;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
.score {
|
|
246
|
+
margin: 8px 0 0;
|
|
247
|
+
font-size: 11px;
|
|
248
|
+
color: var(--ink);
|
|
249
|
+
min-height: 16px;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
.demand-feedback {
|
|
253
|
+
min-height: 78px;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
/* On a fresh selection (no preview, no perturbation) the predicted/gradient/
|
|
257
|
+
reset rows are empty; collapse the reserved block so the panel isn't padded
|
|
258
|
+
with whitespace. The reservation returns during interaction to avoid jumps. */
|
|
259
|
+
.demand-feedback.idle {
|
|
260
|
+
min-height: 0;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
.demand-feedback.idle .pred,
|
|
264
|
+
.demand-feedback.idle .score {
|
|
265
|
+
display: none;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
.reset-row {
|
|
269
|
+
min-height: 28px;
|
|
270
|
+
}
|
|
271
|
+
</style>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"DemandSlider.svelte.d.ts","sourceRoot":"","sources":["../../src/lib/components/DemandSlider.svelte.ts"],"names":[],"mappings":"AA+EA,QAAA,MAAM,YAAY,2DAAwC,CAAC;AAC3D,KAAK,YAAY,GAAG,UAAU,CAAC,OAAO,YAAY,CAAC,CAAC;AACpD,eAAe,YAAY,CAAC"}
|