@tellegen/svelte 0.3.0 → 0.4.1
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/README.md +233 -0
- package/dist/Sparkline.svelte +2 -2
- package/dist/TellegenMap.svelte +239 -66
- package/dist/TellegenMap.svelte.d.ts +45 -0
- package/dist/TellegenProvider.svelte +36 -4
- package/dist/TellegenShell.svelte +14 -42
- package/dist/TellegenViewer.svelte +12 -2
- package/dist/api.d.ts +10 -2
- package/dist/api.js +2 -0
- package/dist/basemap.d.ts +192 -0
- package/dist/basemap.js +259 -0
- package/dist/colors.d.ts +55 -1
- package/dist/colors.js +89 -0
- package/dist/components/AppFooter.svelte +3 -2
- package/dist/components/AppHeader.svelte +21 -8
- package/dist/components/BindingLines.svelte +1 -1
- package/dist/components/BusPicker.svelte +3 -3
- package/dist/components/ControlPanel.svelte +4 -2
- package/dist/components/DemandSlider.svelte +16 -7
- package/dist/components/DiagramCanvas.svelte +17 -5
- package/dist/components/DisplayControls.svelte +5 -1
- package/dist/components/DropZone.svelte +1 -1
- package/dist/components/FormulationSelector.svelte +1 -1
- package/dist/components/LocalCaseDetails.svelte +6 -3
- package/dist/components/McResults.svelte +98 -35
- package/dist/components/McResults.svelte.d.ts +8 -2
- package/dist/components/ModelDetails.svelte +5 -5
- package/dist/components/MulticonductorDetails.svelte +9 -9
- package/dist/components/NotificationCenter.svelte +4 -2
- package/dist/components/PanelCard.svelte +72 -7
- package/dist/components/PanelHost.svelte +59 -34
- package/dist/components/PanelHost.svelte.d.ts +4 -1
- package/dist/components/PlacementCue.svelte +1 -1
- package/dist/components/PowerFlowDetails.svelte +1 -1
- package/dist/components/RatingSlider.svelte +14 -5
- package/dist/components/RestoreDefaultsButton.svelte +1 -1
- package/dist/components/SavedNetworkDetails.svelte +3 -3
- package/dist/components/SensitivityReadout.svelte +2 -2
- package/dist/components/ShellEnvironment.svelte +51 -0
- package/dist/components/ShellEnvironment.svelte.d.ts +18 -0
- package/dist/components/SizeLegend.svelte +2 -2
- package/dist/components/SolveCard.svelte +3 -3
- package/dist/components/TopMovers.svelte +1 -1
- package/dist/components.d.ts +3 -0
- package/dist/components.js +3 -0
- package/dist/context.svelte.d.ts +8 -1
- package/dist/context.svelte.js +8 -3
- package/dist/controller.svelte.d.ts +65 -5
- package/dist/controller.svelte.js +266 -78
- package/dist/display.d.ts +3 -2
- package/dist/display.js +22 -4
- package/dist/format.d.ts +9 -0
- package/dist/format.js +13 -1
- package/dist/index.d.ts +6 -2
- package/dist/index.js +2 -0
- package/dist/map.d.ts +4 -2
- package/dist/map.js +2 -1
- package/dist/maplibre-worker.d.ts +5 -0
- package/dist/maplibre-worker.dev.d.ts +11 -0
- package/dist/maplibre-worker.dev.js +15 -0
- package/dist/maplibre-worker.js +8 -0
- package/dist/notices.svelte.js +5 -1
- package/dist/panels.svelte.d.ts +6 -0
- package/dist/panels.svelte.js +31 -1
- package/dist/slider.d.ts +12 -0
- package/dist/slider.js +31 -0
- package/dist/state.svelte.d.ts +25 -3
- package/dist/state.svelte.js +13 -0
- package/dist/styles.css +59 -6
- package/dist/types.d.ts +1 -1
- package/package.json +13 -8
package/dist/format.js
CHANGED
|
@@ -26,6 +26,18 @@ export function solveMetaLabel(c) {
|
|
|
26
26
|
return 'server price sensitivity';
|
|
27
27
|
return c.solveBackend === 'rust-server' ? 'server solve' : 'browser solve';
|
|
28
28
|
}
|
|
29
|
+
export const INFEASIBLE_DISPATCH = 'No feasible dispatch: demand cannot be served within line and generator limits';
|
|
30
|
+
/** The message for a local case whose browser solve failed, and the
|
|
31
|
+
* technical detail to disclose behind it. An infeasible DC OPF means the
|
|
32
|
+
* network cannot carry the demand, which deserves plain words. The engine
|
|
33
|
+
* spells it `DC OPF solve infeasible`, and an unbounded one as `unbounded`. */
|
|
34
|
+
export function browserSolveFailure(label, reason) {
|
|
35
|
+
const technical = `${label}: browser solve unavailable${reason ? `: ${reason}` : ''}`;
|
|
36
|
+
if (reason && /\binfeasible\b/i.test(reason)) {
|
|
37
|
+
return { message: INFEASIBLE_DISPATCH, detail: technical };
|
|
38
|
+
}
|
|
39
|
+
return { message: technical, detail: null };
|
|
40
|
+
}
|
|
29
41
|
export function rgbaCss([r, g, b, a]) {
|
|
30
42
|
return `rgba(${r}, ${g}, ${b}, ${(a / 255).toFixed(3)})`;
|
|
31
43
|
}
|
|
@@ -47,4 +59,4 @@ export function extent(values) {
|
|
|
47
59
|
export const fmt = new Intl.NumberFormat('en-US', { maximumFractionDigits: 1 });
|
|
48
60
|
export const signed = (v) => `${v < 0 ? '−' : '+'}${fmt.format(Math.abs(v))}`;
|
|
49
61
|
export const signedExp = (v) => `${v < 0 ? '−' : '+'}${Math.abs(v).toExponential(2)}`;
|
|
50
|
-
export const displayFmt = (mode, value) => mode === 'price' ? fmt.format(value) : value.toFixed(3);
|
|
62
|
+
export const displayFmt = (mode, value) => mode === 'price' || mode === 'demand' ? fmt.format(value) : value.toFixed(3);
|
package/dist/index.d.ts
CHANGED
|
@@ -9,6 +9,8 @@ export { default as TellegenViewer } from './TellegenViewer.svelte';
|
|
|
9
9
|
export { default as TellegenMap } from './TellegenMap.svelte';
|
|
10
10
|
export { DEFAULT_TELLEGEN_UI_CONFIG, getAppState, getController, getUiConfig, resolveTellegenUiConfig, setAppState, setController, setUiConfig } from './context.svelte.js';
|
|
11
11
|
export { ApiError, createApiClient } from './api.js';
|
|
12
|
+
export { CARTO_STYLES, OPENFREEMAP_STYLES, attachBasemapFallback, cartoStyleUrl, defaultBasemap, isOpenFreeMapStyle, openFreeMapEdits, resolveBasemap } from './basemap.js';
|
|
13
|
+
export { MAP_PALETTE } from './colors.js';
|
|
12
14
|
export { Controller, createController } from './controller.svelte.js';
|
|
13
15
|
export { AppState, CaseState, LocalCase, MulticonductorCase, createAppState } from './state.svelte.js';
|
|
14
16
|
export { attachmentColor, attachmentGlyph, edgeColor, edgeWidth, phaseColor, neutralTerminal, placeMultiView, buildGeographicView } from './multiconductor.js';
|
|
@@ -17,9 +19,11 @@ export { caseDeltas, caseRatings, displayMetaFor, displaySeriesFor } from './dis
|
|
|
17
19
|
export { DEFAULT_FORMULATION, FORMULATIONS, BrowserStudy, Study, capabilities, createStudy, errorText, formatOf, classifyJson, ingestJsonDrop, ingestCase, isDisplayFile, isPermanentEngineFailure, parseDisplay, solveModule } from '@tellegen/engine';
|
|
18
20
|
export type { BranchRatingDeltas, CaseSummary, DemandDeltas, Network, NetworkBranch, NetworkBus, SensitivityColumn, Solution, SolveIteration, SolveStreamHandlers, TellegenApiClient, TellegenApiClientOptions } from './api.js';
|
|
19
21
|
export type { TellegenUiConfig, TellegenUiOptions } from './context.svelte.js';
|
|
20
|
-
export type { ControllerOptions } from './controller.svelte.js';
|
|
22
|
+
export type { ControllerOptions, OpenBalancedModuleInput } from './controller.svelte.js';
|
|
23
|
+
export type { BasemapFallbackReason, BasemapMapLike, BasemapProvider, BasemapSpec, BasemapStyle, BasemapSwitch, BasemapTheme, DefaultBasemap, MapProjection, MapTheme, ResolvedBasemap, StyleEdit } from './basemap.js';
|
|
24
|
+
export type { MapPalette } from './colors.js';
|
|
21
25
|
export type { DisplayOption } from './display.js';
|
|
22
|
-
export type { CameraSnapshot, DiagramCameraSnapshot, DemandRangeMode, DisplayMode, FallbackTarget, LocalCaseInit, LocalSubstations, MultiCaseSummary, MultiCoordsKind, SolvableCase, StudyDisplaySnapshot, SolveBackend } from './state.svelte.js';
|
|
26
|
+
export type { BusSizing, CameraSnapshot, DiagramCameraSnapshot, DemandRangeMode, DisplayMode, FallbackTarget, LocalCaseInit, LocalSubstations, MultiCaseSummary, MultiCoordsKind, SolvableCase, StudyDisplaySnapshot, SolveBackend } from './state.svelte.js';
|
|
23
27
|
export type { MultiView, PlacedMultiBus, PlacedMultiEdge } from './multiconductor.js';
|
|
24
28
|
export type { CaseFileSummary, DistAttachmentKind, DistEdgeKind, DistGraph, DistGraphAttachment, DistGraphBus, DistGraphEdge, DisplayPreview, Formulation, IngestedCase, IngestedDistCase, IngestedJsonDrop, JsonDropClassification, JsonDropKind, CapacityPlanBusWeightJson, CapacityPlanIterationJson, CapacityPlanOutcomeJson, CapacityPlanRatingChangeJson, CapacityPlanResultSummaryJson, CapacityPlanSpecJson, ImplicitObjectiveJson, SensTarget, Topology, TopologyBranch, TopologyBus } from '@tellegen/engine';
|
|
25
29
|
export { default as McResults } from './components/McResults.svelte';
|
package/dist/index.js
CHANGED
|
@@ -8,6 +8,8 @@ export { default as TellegenViewer } from './TellegenViewer.svelte';
|
|
|
8
8
|
export { default as TellegenMap } from './TellegenMap.svelte';
|
|
9
9
|
export { DEFAULT_TELLEGEN_UI_CONFIG, getAppState, getController, getUiConfig, resolveTellegenUiConfig, setAppState, setController, setUiConfig } from './context.svelte.js';
|
|
10
10
|
export { ApiError, createApiClient } from './api.js';
|
|
11
|
+
export { CARTO_STYLES, OPENFREEMAP_STYLES, attachBasemapFallback, cartoStyleUrl, defaultBasemap, isOpenFreeMapStyle, openFreeMapEdits, resolveBasemap } from './basemap.js';
|
|
12
|
+
export { MAP_PALETTE } from './colors.js';
|
|
11
13
|
export { Controller, createController } from './controller.svelte.js';
|
|
12
14
|
export { AppState, CaseState, LocalCase, MulticonductorCase, createAppState } from './state.svelte.js';
|
|
13
15
|
export { attachmentColor, attachmentGlyph, edgeColor, edgeWidth, phaseColor, neutralTerminal, placeMultiView, buildGeographicView } from './multiconductor.js';
|
package/dist/map.d.ts
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import type { ComponentProps } from 'svelte';
|
|
2
2
|
import TellegenMap from './TellegenMap.svelte';
|
|
3
3
|
export { default as TellegenMap } from './TellegenMap.svelte';
|
|
4
|
-
export {
|
|
4
|
+
export { CARTO_STYLES, OPENFREEMAP_STYLES, attachBasemapFallback, cartoStyleUrl, defaultBasemap, isOpenFreeMapStyle, openFreeMapEdits, resolveBasemap } from './basemap.js';
|
|
5
|
+
export type { BasemapFallbackReason, BasemapMapLike, BasemapProvider, BasemapSpec, BasemapStyle, BasemapSwitch, BasemapTheme, DefaultBasemap, MapProjection, MapTheme, ResolvedBasemap, StyleEdit } from './basemap.js';
|
|
6
|
+
export { MAP_PALETTE, branchColor, branchWidth, busNeutral, busRadius, busMarkerRadius, busRadiusFor, demandColor, demandDomain, demandGradient, priceColor, priceDomain, priceGradient, scalarDomain, sensColor, sensFlatColor, sensGradient, sensNeutral, sensitivityDomain, UNIFORM_BUS_GROUND_RADIUS, UNIFORM_BUS_MIN_RADIUS, UNIFORM_BUS_RADIUS } from './colors.js';
|
|
5
7
|
export { caseDeltas, displayMetaFor, displaySeriesFor } from './display.js';
|
|
6
|
-
export type { RGBA, SensitivityDomain } from './colors.js';
|
|
8
|
+
export type { MapPalette, RGBA, SensitivityDomain } from './colors.js';
|
|
7
9
|
export type { DisplayOption } from './display.js';
|
|
8
10
|
export type TellegenMapProps = ComponentProps<typeof TellegenMap>;
|
package/dist/map.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import TellegenMap from './TellegenMap.svelte';
|
|
2
2
|
export { default as TellegenMap } from './TellegenMap.svelte';
|
|
3
|
-
export {
|
|
3
|
+
export { CARTO_STYLES, OPENFREEMAP_STYLES, attachBasemapFallback, cartoStyleUrl, defaultBasemap, isOpenFreeMapStyle, openFreeMapEdits, resolveBasemap } from './basemap.js';
|
|
4
|
+
export { MAP_PALETTE, branchColor, branchWidth, busNeutral, busRadius, busMarkerRadius, busRadiusFor, demandColor, demandDomain, demandGradient, priceColor, priceDomain, priceGradient, scalarDomain, sensColor, sensFlatColor, sensGradient, sensNeutral, sensitivityDomain, UNIFORM_BUS_GROUND_RADIUS, UNIFORM_BUS_MIN_RADIUS, UNIFORM_BUS_RADIUS } from './colors.js';
|
|
4
5
|
export { caseDeltas, displayMetaFor, displaySeriesFor } from './display.js';
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
/** The URL of MapLibre's worker, which the host's Vite build bundles with its
|
|
2
|
+
* shared chunk. `TellegenMap` hands it to `setWorkerUrl` before it creates a
|
|
3
|
+
* map. Reached through the `@tellegen/svelte/maplibre-worker` export; see
|
|
4
|
+
* `maplibre-worker.dev.ts` for the dev server. */
|
|
5
|
+
export declare function maplibreWorkerUrl(): Promise<string>;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/** The dev server's `@tellegen/svelte/maplibre-worker`, chosen by the
|
|
2
|
+
* `development` export condition.
|
|
3
|
+
*
|
|
4
|
+
* Vite's dev server pre-bundles this package with Rolldown, which cannot load
|
|
5
|
+
* the `?worker&url` import in `maplibre-worker.ts`, so a static import of that
|
|
6
|
+
* module would stop the dev server at startup. Here it is imported at run time
|
|
7
|
+
* by URL instead, outside the pre-bundle, and the dev server resolves the
|
|
8
|
+
* query itself. Vite rewrites the `new URL` in a pre-bundled file to point at
|
|
9
|
+
* the original. A production build takes the default condition and imports
|
|
10
|
+
* `maplibre-worker.ts` directly. */
|
|
11
|
+
export declare function maplibreWorkerUrl(): Promise<string>;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/** The dev server's `@tellegen/svelte/maplibre-worker`, chosen by the
|
|
2
|
+
* `development` export condition.
|
|
3
|
+
*
|
|
4
|
+
* Vite's dev server pre-bundles this package with Rolldown, which cannot load
|
|
5
|
+
* the `?worker&url` import in `maplibre-worker.ts`, so a static import of that
|
|
6
|
+
* module would stop the dev server at startup. Here it is imported at run time
|
|
7
|
+
* by URL instead, outside the pre-bundle, and the dev server resolves the
|
|
8
|
+
* query itself. Vite rewrites the `new URL` in a pre-bundled file to point at
|
|
9
|
+
* the original. A production build takes the default condition and imports
|
|
10
|
+
* `maplibre-worker.ts` directly. */
|
|
11
|
+
export async function maplibreWorkerUrl() {
|
|
12
|
+
const url = new URL('./maplibre-worker.js', import.meta.url).href;
|
|
13
|
+
const module = await import(/* @vite-ignore */ url);
|
|
14
|
+
return module.maplibreWorkerUrl();
|
|
15
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import workerUrl from 'maplibre-gl/dist/maplibre-gl-worker.mjs?worker&url';
|
|
2
|
+
/** The URL of MapLibre's worker, which the host's Vite build bundles with its
|
|
3
|
+
* shared chunk. `TellegenMap` hands it to `setWorkerUrl` before it creates a
|
|
4
|
+
* map. Reached through the `@tellegen/svelte/maplibre-worker` export; see
|
|
5
|
+
* `maplibre-worker.dev.ts` for the dev server. */
|
|
6
|
+
export async function maplibreWorkerUrl() {
|
|
7
|
+
return workerUrl;
|
|
8
|
+
}
|
package/dist/notices.svelte.js
CHANGED
|
@@ -70,11 +70,15 @@ export function getNoticeCenter() {
|
|
|
70
70
|
export function errorNoticeTitle(details) {
|
|
71
71
|
if (/rate limit/i.test(details))
|
|
72
72
|
return 'Too many requests';
|
|
73
|
+
// MapLibre's WebGL failure includes "Read more"; classify our map errors
|
|
74
|
+
// before the generic file/read fallback so it does not blame a case file.
|
|
75
|
+
if (/^map failed to load:|^the map lost its graphics context/i.test(details))
|
|
76
|
+
return 'Map unavailable';
|
|
73
77
|
if (/storage|quota|space|indexeddb/i.test(details))
|
|
74
78
|
return 'Could not save locally';
|
|
75
79
|
if (/sensitivity|derivative|singular/i.test(details))
|
|
76
80
|
return 'Sensitivity unavailable';
|
|
77
|
-
if (/infeasib|converg|solver|solving|calculation|compute|webassembly/i.test(details))
|
|
81
|
+
if (/infeasib|converg|solver|solving|calculation|compute|webassembly|power flow/i.test(details))
|
|
78
82
|
return 'Calculation did not complete';
|
|
79
83
|
if (/parse|read|file|format/i.test(details))
|
|
80
84
|
return 'Could not read this file';
|
package/dist/panels.svelte.d.ts
CHANGED
|
@@ -49,6 +49,9 @@ export declare class PanelLayout {
|
|
|
49
49
|
position(panel: PanelRegistration): PanelPosition;
|
|
50
50
|
ordered(side?: PanelSide): PanelRegistration[];
|
|
51
51
|
toggle(panel: PanelRegistration): void;
|
|
52
|
+
/** The control that had focus when the panel last opened, while it is still
|
|
53
|
+
* in the document: where focus returns when the panel closes. */
|
|
54
|
+
opener(panel: PanelRegistration): HTMLElement | SVGElement | null;
|
|
52
55
|
close(panel: PanelRegistration): void;
|
|
53
56
|
dock(panel: PanelRegistration, side?: PanelSide): void;
|
|
54
57
|
float(panel: PanelRegistration, bounds?: PanelBounds): void;
|
|
@@ -60,6 +63,9 @@ export declare class PanelLayout {
|
|
|
60
63
|
initialize(storage?: Storage): void;
|
|
61
64
|
save(): void;
|
|
62
65
|
}
|
|
66
|
+
/** Focus the first candidate that takes focus. A detached or hidden element
|
|
67
|
+
* does not, so the next one gets its turn. */
|
|
68
|
+
export declare function focusFirst(candidates: Array<HTMLElement | SVGElement | null | undefined>): HTMLElement | SVGElement | null;
|
|
63
69
|
export declare const setPanelLayout: (layout: PanelLayout) => PanelLayout;
|
|
64
70
|
export declare const getPanelLayout: () => PanelLayout;
|
|
65
71
|
export {};
|
package/dist/panels.svelte.js
CHANGED
|
@@ -18,6 +18,7 @@ export class PanelLayout {
|
|
|
18
18
|
bottom = $state(88);
|
|
19
19
|
storageError = $state(null);
|
|
20
20
|
#storage;
|
|
21
|
+
#openers = new Map();
|
|
21
22
|
#gesture = null;
|
|
22
23
|
beginGesture(panel, kind, x, y, bounds) {
|
|
23
24
|
this.#gesture = { panel, kind, x, y, bounds, moved: false };
|
|
@@ -60,6 +61,7 @@ export class PanelLayout {
|
|
|
60
61
|
return;
|
|
61
62
|
this.panels = this.panels.map((p) => (p.id === id ? { ...p, open, title, width } : p));
|
|
62
63
|
if (open && !previous.open) {
|
|
64
|
+
this.#rememberOpener(id);
|
|
63
65
|
this.activate(previous);
|
|
64
66
|
if (this.compact)
|
|
65
67
|
this.drawer = id;
|
|
@@ -91,19 +93,35 @@ export class PanelLayout {
|
|
|
91
93
|
if (this.compact) {
|
|
92
94
|
this.drawer = this.drawer === panel.id ? null : panel.id;
|
|
93
95
|
if (this.drawer === panel.id) {
|
|
96
|
+
this.#rememberOpener(panel.id);
|
|
94
97
|
this.activate(panel);
|
|
95
98
|
panel.setOpen(true);
|
|
96
99
|
panel.onopen?.();
|
|
97
100
|
}
|
|
98
101
|
}
|
|
99
102
|
else {
|
|
100
|
-
if (!panel.open)
|
|
103
|
+
if (!panel.open) {
|
|
104
|
+
this.#rememberOpener(panel.id);
|
|
101
105
|
this.activate(panel);
|
|
106
|
+
}
|
|
102
107
|
panel.setOpen(!panel.open);
|
|
103
108
|
if (!panel.open)
|
|
104
109
|
panel.onopen?.();
|
|
105
110
|
}
|
|
106
111
|
}
|
|
112
|
+
/** The control that had focus when the panel last opened, while it is still
|
|
113
|
+
* in the document: where focus returns when the panel closes. */
|
|
114
|
+
opener(panel) {
|
|
115
|
+
const element = this.#openers.get(panel.id);
|
|
116
|
+
return element?.isConnected ? element : null;
|
|
117
|
+
}
|
|
118
|
+
#rememberOpener(id) {
|
|
119
|
+
const active = typeof document === 'undefined' ? null : document.activeElement;
|
|
120
|
+
if ((active instanceof HTMLElement || active instanceof SVGElement) && active !== document.body)
|
|
121
|
+
this.#openers.set(id, active);
|
|
122
|
+
else
|
|
123
|
+
this.#openers.delete(id);
|
|
124
|
+
}
|
|
107
125
|
close(panel) {
|
|
108
126
|
panel.setOpen(false);
|
|
109
127
|
if (this.drawer === panel.id)
|
|
@@ -206,6 +224,18 @@ export class PanelLayout {
|
|
|
206
224
|
}
|
|
207
225
|
}
|
|
208
226
|
}
|
|
227
|
+
/** Focus the first candidate that takes focus. A detached or hidden element
|
|
228
|
+
* does not, so the next one gets its turn. */
|
|
229
|
+
export function focusFirst(candidates) {
|
|
230
|
+
for (const candidate of candidates) {
|
|
231
|
+
if (!candidate?.isConnected)
|
|
232
|
+
continue;
|
|
233
|
+
candidate.focus();
|
|
234
|
+
if (candidate.ownerDocument.activeElement === candidate)
|
|
235
|
+
return candidate;
|
|
236
|
+
}
|
|
237
|
+
return null;
|
|
238
|
+
}
|
|
209
239
|
export const setPanelLayout = (layout) => setContext(KEY, layout);
|
|
210
240
|
export const getPanelLayout = () => {
|
|
211
241
|
const layout = getContext(KEY);
|
package/dist/slider.d.ts
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/** Smallest demand or rating edit that counts: a slider released closer than
|
|
2
|
+
* this to the committed value commits nothing and starts no solve. */
|
|
3
|
+
export declare const DEAD_ZONE_MW = 0.25;
|
|
4
|
+
/** Arrow-key step of a demand or rating slider spanning `span` MW: about 1%
|
|
5
|
+
* of the span, rounded up to 1, 2 or 5 times a power of ten. It is at least
|
|
6
|
+
* twice the dead zone, so one press always leaves it, rounding error
|
|
7
|
+
* included. The native step of a `step="any"` range is 1% of the span, which
|
|
8
|
+
* on a small bus never leaves the dead zone. */
|
|
9
|
+
export declare function sliderKeyStep(span: number): number;
|
|
10
|
+
/** The value an arrow key moves a slider to, kept within [min, max], or null
|
|
11
|
+
* for any other key. Right and up raise it. */
|
|
12
|
+
export declare function sliderKeyValue(key: string, value: number, min: number, max: number): number | null;
|
package/dist/slider.js
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/** Smallest demand or rating edit that counts: a slider released closer than
|
|
2
|
+
* this to the committed value commits nothing and starts no solve. */
|
|
3
|
+
export const DEAD_ZONE_MW = 0.25;
|
|
4
|
+
const KEY_DIRECTIONS = {
|
|
5
|
+
ArrowRight: 1,
|
|
6
|
+
ArrowUp: 1,
|
|
7
|
+
ArrowLeft: -1,
|
|
8
|
+
ArrowDown: -1
|
|
9
|
+
};
|
|
10
|
+
/** Arrow-key step of a demand or rating slider spanning `span` MW: about 1%
|
|
11
|
+
* of the span, rounded up to 1, 2 or 5 times a power of ten. It is at least
|
|
12
|
+
* twice the dead zone, so one press always leaves it, rounding error
|
|
13
|
+
* included. The native step of a `step="any"` range is 1% of the span, which
|
|
14
|
+
* on a small bus never leaves the dead zone. */
|
|
15
|
+
export function sliderKeyStep(span) {
|
|
16
|
+
const target = Math.max(span / 100, 2 * DEAD_ZONE_MW);
|
|
17
|
+
const power = 10 ** Math.floor(Math.log10(target));
|
|
18
|
+
for (const factor of [1, 2, 5]) {
|
|
19
|
+
if (factor * power >= target)
|
|
20
|
+
return factor * power;
|
|
21
|
+
}
|
|
22
|
+
return 10 * power;
|
|
23
|
+
}
|
|
24
|
+
/** The value an arrow key moves a slider to, kept within [min, max], or null
|
|
25
|
+
* for any other key. Right and up raise it. */
|
|
26
|
+
export function sliderKeyValue(key, value, min, max) {
|
|
27
|
+
const direction = KEY_DIRECTIONS[key];
|
|
28
|
+
if (direction === undefined)
|
|
29
|
+
return null;
|
|
30
|
+
return Math.min(max, Math.max(min, value + direction * sliderKeyStep(max - min)));
|
|
31
|
+
}
|
package/dist/state.svelte.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { CaseSummary, DemandDeltas, Network, NetworkBranch, NetworkBus, SensitivityColumn, Solution, SolveIteration } from './api.js';
|
|
2
|
-
import { type StudyView, type BranchRatingDeltas, type BrowserMcPfSession, type CaseFileSummary, type DistGraph, type Formulation, type IngestedDistCase, type McPfResult, type McLoadBranchState, type McLoadPowerEdit, type McStudySnapshot, type SensTarget, type Topology } from '@tellegen/engine';
|
|
2
|
+
import { type StudyView, type BranchRatingDeltas, type BrowserMcPfSession, type CaseFileSummary, type DistGraph, type Formulation, type IngestedDistCase, type McPfCallTiming, type McPfResult, type McPfSummary, type McLoadBranchState, type McLoadPowerEdit, type McStudySnapshot, type SensTarget, type Topology } from '@tellegen/engine';
|
|
3
3
|
import type { MultiView } from './multiconductor.js';
|
|
4
4
|
export type SolveBackend = 'clarabel-wasm' | 'clarabel-wasm-server-sensitivity' | 'rust-server';
|
|
5
5
|
/** A map framing request: a case id, 'all', or one branch to center. */
|
|
@@ -22,7 +22,11 @@ export interface DiagramCameraSnapshot {
|
|
|
22
22
|
scale: number;
|
|
23
23
|
}
|
|
24
24
|
export type DemandRangeMode = 'local' | 'full';
|
|
25
|
-
export type DisplayMode = 'price' | 'angle' | 'voltage';
|
|
25
|
+
export type DisplayMode = 'price' | 'angle' | 'voltage' | 'demand';
|
|
26
|
+
/** How bus markers are sized on the map: by the larger of demand and generation
|
|
27
|
+
* (`power`), or all at one size (`uniform`) so dense networks keep their lines
|
|
28
|
+
* readable and colour carries the variable. */
|
|
29
|
+
export type BusSizing = 'power' | 'uniform';
|
|
26
30
|
/** The case a removal promoted to active, so the caller can hydrate it: a backend
|
|
27
31
|
* case needs its network/solution loaded, a local needs a browser solve, and
|
|
28
32
|
* `none` means nothing remains (or the removal left the active case untouched). */
|
|
@@ -174,6 +178,12 @@ export type SolvableCase = CaseState | LocalCase;
|
|
|
174
178
|
export type MultiCaseSummary = Omit<IngestedDistCase, 'graph'>;
|
|
175
179
|
/** Geographic coordinates use the map; other positions use the diagram canvas. */
|
|
176
180
|
export type MultiCoordsKind = 'geographic' | 'planar' | 'synthetic';
|
|
181
|
+
/** Where one interactive load edit spent its time, in milliseconds: engine
|
|
182
|
+
* (solve and summary), transfer, and parsing as recorded by the session, and
|
|
183
|
+
* the total from the start of the solve until the result state is updated. */
|
|
184
|
+
export interface McEditTiming extends McPfCallTiming {
|
|
185
|
+
total_ms: number;
|
|
186
|
+
}
|
|
177
187
|
/** A conductor-resolved case with retained electrical inputs and terminal results. */
|
|
178
188
|
export declare class MulticonductorCase {
|
|
179
189
|
readonly id: string;
|
|
@@ -182,7 +192,14 @@ export declare class MulticonductorCase {
|
|
|
182
192
|
moduleJson: string | null;
|
|
183
193
|
geoLayer: string | null;
|
|
184
194
|
geoWarnings: string[];
|
|
185
|
-
|
|
195
|
+
/** Summary of the displayed operating point: from the live session after
|
|
196
|
+
* each solve or load edit, or derived from a stored full result. */
|
|
197
|
+
result: McPfSummary | null;
|
|
198
|
+
/** The complete result when no live session holds the operating point: a
|
|
199
|
+
* saved Study, a solve without sessions, or after coordinates are attached. */
|
|
200
|
+
mcFullResult: McPfResult | null;
|
|
201
|
+
/** Latency breakdown of the latest load-edit solve. */
|
|
202
|
+
mcTiming: McEditTiming | null;
|
|
186
203
|
mcSnapshot: McStudySnapshot | null;
|
|
187
204
|
mcSession: BrowserMcPfSession | null;
|
|
188
205
|
mcLoadBranches: McLoadBranchState[];
|
|
@@ -280,11 +297,16 @@ export declare class AppState {
|
|
|
280
297
|
} | null;
|
|
281
298
|
demandRangeMode: DemandRangeMode;
|
|
282
299
|
displayMode: DisplayMode;
|
|
300
|
+
busSizing: BusSizing;
|
|
283
301
|
sensitivityLoading: boolean;
|
|
284
302
|
errorRevision: number;
|
|
285
303
|
/** Re-runs the operation behind the current `error`, when one applies. Every
|
|
286
304
|
* write to `error` clears it, so a retry op can never outlive its message. */
|
|
287
305
|
errorRetry: (() => void) | null;
|
|
306
|
+
/** Technical detail behind the current `error`, when the message itself is
|
|
307
|
+
* written for people; notices keep it in their disclosure. Every write to
|
|
308
|
+
* `error` clears it, as it clears `errorRetry`. */
|
|
309
|
+
errorDetail: string | null;
|
|
288
310
|
get error(): string | null;
|
|
289
311
|
set error(message: string | null);
|
|
290
312
|
/** Case files parsed in the browser via the powerio wasm module. */
|
package/dist/state.svelte.js
CHANGED
|
@@ -122,7 +122,14 @@ export class MulticonductorCase {
|
|
|
122
122
|
moduleJson = $state.raw(null);
|
|
123
123
|
geoLayer = $state.raw(null);
|
|
124
124
|
geoWarnings = $state.raw([]);
|
|
125
|
+
/** Summary of the displayed operating point: from the live session after
|
|
126
|
+
* each solve or load edit, or derived from a stored full result. */
|
|
125
127
|
result = $state.raw(null);
|
|
128
|
+
/** The complete result when no live session holds the operating point: a
|
|
129
|
+
* saved Study, a solve without sessions, or after coordinates are attached. */
|
|
130
|
+
mcFullResult = $state.raw(null);
|
|
131
|
+
/** Latency breakdown of the latest load-edit solve. */
|
|
132
|
+
mcTiming = $state.raw(null);
|
|
126
133
|
mcSnapshot = $state.raw(null);
|
|
127
134
|
mcSession = $state.raw(null);
|
|
128
135
|
mcLoadBranches = $state.raw([]);
|
|
@@ -245,12 +252,17 @@ export class AppState {
|
|
|
245
252
|
previewPrices = $state.raw(null);
|
|
246
253
|
demandRangeMode = $state('local');
|
|
247
254
|
displayMode = $state('price');
|
|
255
|
+
busSizing = $state('power');
|
|
248
256
|
sensitivityLoading = $state(false);
|
|
249
257
|
#error = $state(null);
|
|
250
258
|
errorRevision = $state(0);
|
|
251
259
|
/** Re-runs the operation behind the current `error`, when one applies. Every
|
|
252
260
|
* write to `error` clears it, so a retry op can never outlive its message. */
|
|
253
261
|
errorRetry = $state.raw(null);
|
|
262
|
+
/** Technical detail behind the current `error`, when the message itself is
|
|
263
|
+
* written for people; notices keep it in their disclosure. Every write to
|
|
264
|
+
* `error` clears it, as it clears `errorRetry`. */
|
|
265
|
+
errorDetail = $state(null);
|
|
254
266
|
get error() {
|
|
255
267
|
return this.#error;
|
|
256
268
|
}
|
|
@@ -259,6 +271,7 @@ export class AppState {
|
|
|
259
271
|
if (message)
|
|
260
272
|
this.errorRevision++;
|
|
261
273
|
this.errorRetry = null;
|
|
274
|
+
this.errorDetail = null;
|
|
262
275
|
}
|
|
263
276
|
/** Case files parsed in the browser via the powerio wasm module. */
|
|
264
277
|
localCases = $state.raw([]);
|
package/dist/styles.css
CHANGED
|
@@ -32,6 +32,7 @@
|
|
|
32
32
|
--surface-panel: var(--panel);
|
|
33
33
|
--surface-chip: rgb(var(--paper-rgb) / 0.65);
|
|
34
34
|
--surface-control: rgb(var(--paper-rgb) / 0.55);
|
|
35
|
+
--surface-menu: #fff;
|
|
35
36
|
--border-default: var(--line);
|
|
36
37
|
--border-strong: var(--accent);
|
|
37
38
|
--text-primary: var(--ink);
|
|
@@ -47,6 +48,10 @@
|
|
|
47
48
|
--data-pos: var(--pos);
|
|
48
49
|
--data-neg: var(--neg);
|
|
49
50
|
--focus-ring: var(--accent);
|
|
51
|
+
/* unselected bus outline in the plain diagram view */
|
|
52
|
+
--diagram-bus-stroke: #756a5b;
|
|
53
|
+
/* ground symbol in the multiconductor bus detail */
|
|
54
|
+
--mc-ground-stroke: #3a3a3a;
|
|
50
55
|
|
|
51
56
|
/* type scale, anchored at the 15px body */
|
|
52
57
|
--fs-display-lg: 33px;
|
|
@@ -81,6 +86,11 @@
|
|
|
81
86
|
--radius-full: 50%;
|
|
82
87
|
--elev-1: 0 2px 10px rgb(var(--ink-rgb) / 0.1);
|
|
83
88
|
--elev-2: 0 4px 24px rgb(var(--ink-rgb) / 0.08);
|
|
89
|
+
/* floating controls that keep their own light shadows; the dark theme maps
|
|
90
|
+
them onto the two steps above */
|
|
91
|
+
--elev-cue: 0 4px 18px rgb(var(--ink-rgb) / 0.1);
|
|
92
|
+
--elev-menu: 0 4px 18px rgb(var(--ink-rgb) / 0.14);
|
|
93
|
+
--elev-tooltip: 0 2px 10px rgb(var(--ink-rgb) / 0.12);
|
|
84
94
|
--shadow-thumb: 0 1px 4px rgb(var(--ink-rgb) / 0.3);
|
|
85
95
|
--inset-active: inset 0 -2px 0 var(--accent);
|
|
86
96
|
|
|
@@ -103,6 +113,49 @@
|
|
|
103
113
|
--z-grain: 9999;
|
|
104
114
|
}
|
|
105
115
|
|
|
116
|
+
/* Dark theme: `TellegenProvider theme="dark"` sets the attribute (a host may
|
|
117
|
+
also put it in its template). Contrast ratios are measured against the panel
|
|
118
|
+
composited over #4a4a4a, the darkest grey a dark basemap paints under it:
|
|
119
|
+
every text token clears 7:1. --border-strong marks a panel's edge over the
|
|
120
|
+
map, so it clears the 3:1 non-text minimum against that panel and against
|
|
121
|
+
bare #4a4a4a. The same markup passes WCAG AA on either ground. */
|
|
122
|
+
:root[data-theme='dark'] {
|
|
123
|
+
--bg: #0b1420;
|
|
124
|
+
--paper: #0f1a27;
|
|
125
|
+
--panel: rgba(10, 20, 31, 0.92);
|
|
126
|
+
--line: #34465a; /* decorative separators only */
|
|
127
|
+
--border-strong: #859cb4; /* 6.30:1 against the panel, 3.13:1 against bare #4a4a4a */
|
|
128
|
+
--ink: #e8eef4; /* 15.3:1 */
|
|
129
|
+
--ink-dim: #b4c1cd; /* 9.7:1 */
|
|
130
|
+
--ink-faint: #95a6b6; /* 7.14:1, and 7.02:1 on the opaque --paper of a panel card */
|
|
131
|
+
--accent: #ffb24d;
|
|
132
|
+
--accent-bright: #ffc46e; /* 11.4:1 */
|
|
133
|
+
--accent-soft: rgba(255, 178, 77, 0.14);
|
|
134
|
+
--red: #ff9f8b; /* 9.0:1 */
|
|
135
|
+
--pos: #c9a7e0;
|
|
136
|
+
--neg: #8fd0a4;
|
|
137
|
+
--bg-rgb: 11 20 32;
|
|
138
|
+
--ink-rgb: 232 238 244;
|
|
139
|
+
--accent-rgb: 255 178 77;
|
|
140
|
+
--paper-rgb: 15 26 39;
|
|
141
|
+
--text-primary: var(--ink);
|
|
142
|
+
--text-secondary: var(--ink-dim);
|
|
143
|
+
--text-tertiary: var(--ink-faint);
|
|
144
|
+
--text-accent: var(--accent-bright);
|
|
145
|
+
--text-danger: var(--red);
|
|
146
|
+
--focus-ring: #8de7f7; /* 12.7:1 */
|
|
147
|
+
--surface-menu: var(--paper);
|
|
148
|
+
--diagram-bus-stroke: var(--ink-faint);
|
|
149
|
+
--mc-ground-stroke: var(--ink-dim);
|
|
150
|
+
--elev-1: 0 2px 10px rgb(0 0 0 / 0.45);
|
|
151
|
+
--elev-2: 0 4px 24px rgb(0 0 0 / 0.4);
|
|
152
|
+
--elev-cue: var(--elev-2);
|
|
153
|
+
--elev-menu: var(--elev-2);
|
|
154
|
+
--elev-tooltip: var(--elev-1);
|
|
155
|
+
/* the light form derives from --ink-rgb, which would glow here */
|
|
156
|
+
--shadow-thumb: 0 1px 4px rgb(0 0 0 / 0.5);
|
|
157
|
+
}
|
|
158
|
+
|
|
106
159
|
* {
|
|
107
160
|
box-sizing: border-box;
|
|
108
161
|
}
|
|
@@ -225,7 +278,7 @@ a {
|
|
|
225
278
|
ControlPanel.svelte; the .mode / .display-mode / @media source order is
|
|
226
279
|
preserved so the cascade resolves exactly as before. */
|
|
227
280
|
.region {
|
|
228
|
-
font-size:
|
|
281
|
+
font-size: 12px;
|
|
229
282
|
font-weight: 400;
|
|
230
283
|
color: var(--text-secondary);
|
|
231
284
|
text-transform: uppercase;
|
|
@@ -245,7 +298,7 @@ a {
|
|
|
245
298
|
before the component split. Scoping this would raise its specificity past that rule. */
|
|
246
299
|
.mode > button {
|
|
247
300
|
margin-left: auto;
|
|
248
|
-
font-size:
|
|
301
|
+
font-size: 12.5px;
|
|
249
302
|
padding: 2px 7px;
|
|
250
303
|
background: none;
|
|
251
304
|
border: 1px solid var(--line);
|
|
@@ -264,7 +317,7 @@ a {
|
|
|
264
317
|
}
|
|
265
318
|
|
|
266
319
|
.display-mode .segment button {
|
|
267
|
-
font-size:
|
|
320
|
+
font-size: 13px;
|
|
268
321
|
padding: 2px 9px;
|
|
269
322
|
}
|
|
270
323
|
|
|
@@ -282,7 +335,7 @@ a {
|
|
|
282
335
|
.legend-labels {
|
|
283
336
|
display: flex;
|
|
284
337
|
justify-content: space-between;
|
|
285
|
-
font-size:
|
|
338
|
+
font-size: 12.5px;
|
|
286
339
|
color: var(--text-tertiary);
|
|
287
340
|
margin-top: 4px;
|
|
288
341
|
}
|
|
@@ -301,7 +354,7 @@ a {
|
|
|
301
354
|
border-radius: 999px;
|
|
302
355
|
color: var(--text-secondary);
|
|
303
356
|
background: var(--surface-control);
|
|
304
|
-
font-size:
|
|
357
|
+
font-size: 12px;
|
|
305
358
|
line-height: 1;
|
|
306
359
|
cursor: help;
|
|
307
360
|
}
|
|
@@ -334,7 +387,7 @@ a {
|
|
|
334
387
|
|
|
335
388
|
.reset {
|
|
336
389
|
margin-top: 10px;
|
|
337
|
-
font-size:
|
|
390
|
+
font-size: 12.5px;
|
|
338
391
|
padding: 3px 10px;
|
|
339
392
|
background: none;
|
|
340
393
|
border: 1px solid var(--line);
|
package/dist/types.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export type { CaseSummary, DemandDeltas, Network, NetworkBranch, NetworkBus, SensitivityColumn, Solution, SolveIteration, SolveStreamHandlers } from './api.js';
|
|
2
2
|
export type { DisplayOption } from './display.js';
|
|
3
3
|
export type { PlacedNetwork, PlacementCenter } from './synthetic-layout.js';
|
|
4
|
-
export type { DemandRangeMode, DisplayMode, FallbackTarget, LocalCaseInit, LocalSubstations, SolvableCase, SolveBackend } from './state.svelte.js';
|
|
4
|
+
export type { BusSizing, DemandRangeMode, DisplayMode, FallbackTarget, LocalCaseInit, LocalSubstations, SolvableCase, SolveBackend } from './state.svelte.js';
|
|
5
5
|
export type { AppliedGeoCase, CaseFileSummary, DisplayPreview, Formulation, GeoApplyReport, IngestedCase, ParsedGeoLayer, StampedLayoutCase, Topology, TopologyBranch, TopologyBus } from '@tellegen/engine';
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tellegen/svelte",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.1",
|
|
4
4
|
"description": "Svelte components for tellegen power system maps, local case files, and browser solves.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"svelte",
|
|
@@ -53,6 +53,11 @@
|
|
|
53
53
|
"svelte": "./dist/map.js",
|
|
54
54
|
"default": "./dist/map.js"
|
|
55
55
|
},
|
|
56
|
+
"./maplibre-worker": {
|
|
57
|
+
"types": "./dist/maplibre-worker.d.ts",
|
|
58
|
+
"development": "./dist/maplibre-worker.dev.js",
|
|
59
|
+
"default": "./dist/maplibre-worker.js"
|
|
60
|
+
},
|
|
56
61
|
"./styles.css": "./dist/styles.css"
|
|
57
62
|
},
|
|
58
63
|
"scripts": {
|
|
@@ -65,19 +70,19 @@
|
|
|
65
70
|
"@deck.gl/core": "^9.4.0",
|
|
66
71
|
"@deck.gl/layers": "^9.4.0",
|
|
67
72
|
"@deck.gl/maplibre": "^9.4.0",
|
|
68
|
-
"@tellegen/engine": "^0.
|
|
69
|
-
"maplibre-gl": "^6.
|
|
73
|
+
"@tellegen/engine": "^0.4.0",
|
|
74
|
+
"maplibre-gl": "^6.15.0"
|
|
70
75
|
},
|
|
71
76
|
"peerDependencies": {
|
|
72
77
|
"svelte": ">=5.30 <6"
|
|
73
78
|
},
|
|
74
79
|
"devDependencies": {
|
|
75
80
|
"@sveltejs/package": "^2.3.0",
|
|
76
|
-
"@sveltejs/vite-plugin-svelte": "^7.3.
|
|
77
|
-
"svelte": "^5.57.
|
|
78
|
-
"svelte-check": "^4.7.
|
|
81
|
+
"@sveltejs/vite-plugin-svelte": "^7.3.1",
|
|
82
|
+
"svelte": "^5.57.2",
|
|
83
|
+
"svelte-check": "^4.7.7",
|
|
79
84
|
"typescript": "^6.0.3",
|
|
80
|
-
"vite": "^8.
|
|
81
|
-
"vitest": "^5.0.
|
|
85
|
+
"vite": "^8.3.1",
|
|
86
|
+
"vitest": "^5.0.2"
|
|
82
87
|
}
|
|
83
88
|
}
|