@tellegen/svelte 0.4.3 → 0.4.4

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 (105) hide show
  1. package/README.md +20 -0
  2. package/dist/TellegenMap.svelte +63 -22
  3. package/dist/TellegenProvider.svelte +7 -4
  4. package/dist/TellegenShell.svelte +10 -1
  5. package/dist/TellegenViewer.svelte +2 -0
  6. package/dist/basemap.d.ts +14 -0
  7. package/dist/basemap.js +27 -0
  8. package/dist/colors.d.ts +6 -1
  9. package/dist/colors.js +11 -4
  10. package/dist/components/FormulationSelector.svelte +1 -1
  11. package/dist/components/LocalCaseDetails.svelte +18 -3
  12. package/dist/components/McResults.svelte +18 -3
  13. package/dist/components/McWorkbench.svelte +688 -0
  14. package/dist/components/McWorkbench.svelte.d.ts +38 -0
  15. package/dist/components/MultiWire.svelte +136 -0
  16. package/dist/components/MultiWire.svelte.d.ts +7 -0
  17. package/dist/components/MulticonductorDetails.svelte +7 -0
  18. package/dist/components/OmniSearch.svelte +163 -0
  19. package/dist/components/OmniSearch.svelte.d.ts +14 -0
  20. package/dist/components/ResultsTable.svelte +259 -0
  21. package/dist/components/ResultsTable.svelte.d.ts +13 -0
  22. package/dist/components/SchematicCanvas.svelte +1896 -0
  23. package/dist/components/SchematicCanvas.svelte.d.ts +13 -0
  24. package/dist/components/SolveCard.svelte +6 -0
  25. package/dist/components/schematic/PickCard.svelte +126 -0
  26. package/dist/components/schematic/PickCard.svelte.d.ts +56 -0
  27. package/dist/components/schematic/export.d.ts +21 -0
  28. package/dist/components/schematic/export.js +110 -0
  29. package/dist/components/schematic/layers.d.ts +161 -0
  30. package/dist/components/schematic/layers.js +756 -0
  31. package/dist/components/schematic/paint.d.ts +28 -0
  32. package/dist/components/schematic/paint.js +69 -0
  33. package/dist/components/schematic/palette.d.ts +34 -0
  34. package/dist/components/schematic/palette.js +54 -0
  35. package/dist/components/schematic/text.d.ts +83 -0
  36. package/dist/components/schematic/text.js +190 -0
  37. package/dist/components/schematic/view-prefs.svelte.d.ts +30 -0
  38. package/dist/components/schematic/view-prefs.svelte.js +77 -0
  39. package/dist/components.d.ts +1 -0
  40. package/dist/components.js +1 -0
  41. package/dist/context.svelte.d.ts +2 -0
  42. package/dist/context.svelte.js +2 -0
  43. package/dist/controller.svelte.d.ts +33 -5
  44. package/dist/controller.svelte.js +173 -20
  45. package/dist/diagram/basemap.d.ts +28 -0
  46. package/dist/diagram/basemap.js +39 -0
  47. package/dist/diagram/bus-stacks.d.ts +19 -0
  48. package/dist/diagram/bus-stacks.js +99 -0
  49. package/dist/diagram/colour-modes.d.ts +18 -0
  50. package/dist/diagram/colour-modes.js +33 -0
  51. package/dist/diagram/connections.d.ts +30 -0
  52. package/dist/diagram/connections.js +129 -0
  53. package/dist/diagram/display-tree.d.ts +21 -0
  54. package/dist/diagram/display-tree.js +65 -0
  55. package/dist/diagram/energisation.d.ts +10 -0
  56. package/dist/diagram/energisation.js +31 -0
  57. package/dist/diagram/feeders.d.ts +30 -0
  58. package/dist/diagram/feeders.js +57 -0
  59. package/dist/diagram/galvanic-zones.d.ts +60 -0
  60. package/dist/diagram/galvanic-zones.js +210 -0
  61. package/dist/diagram/graph-inputs.d.ts +18 -0
  62. package/dist/diagram/graph-inputs.js +103 -0
  63. package/dist/diagram/index.d.ts +28 -0
  64. package/dist/diagram/index.js +28 -0
  65. package/dist/diagram/landmarks.d.ts +50 -0
  66. package/dist/diagram/landmarks.js +153 -0
  67. package/dist/diagram/layout-morph.d.ts +15 -0
  68. package/dist/diagram/layout-morph.js +32 -0
  69. package/dist/diagram/multi-wire.d.ts +44 -0
  70. package/dist/diagram/multi-wire.js +65 -0
  71. package/dist/diagram/phase-bars.d.ts +35 -0
  72. package/dist/diagram/phase-bars.js +44 -0
  73. package/dist/diagram/results-table.d.ts +47 -0
  74. package/dist/diagram/results-table.js +173 -0
  75. package/dist/diagram/schematic-equipment.d.ts +133 -0
  76. package/dist/diagram/schematic-equipment.js +645 -0
  77. package/dist/diagram/schematic-layout.d.ts +51 -0
  78. package/dist/diagram/schematic-layout.js +240 -0
  79. package/dist/diagram/search.d.ts +36 -0
  80. package/dist/diagram/search.js +48 -0
  81. package/dist/diagram/shared-routes.d.ts +21 -0
  82. package/dist/diagram/shared-routes.js +40 -0
  83. package/dist/diagram/short-names.d.ts +11 -0
  84. package/dist/diagram/short-names.js +54 -0
  85. package/dist/diagram/snapshot.d.ts +115 -0
  86. package/dist/diagram/snapshot.js +242 -0
  87. package/dist/diagram/summary.d.ts +65 -0
  88. package/dist/diagram/summary.js +144 -0
  89. package/dist/diagram/svg-export.d.ts +80 -0
  90. package/dist/diagram/svg-export.js +108 -0
  91. package/dist/diagram/tree-walk.d.ts +13 -0
  92. package/dist/diagram/tree-walk.js +83 -0
  93. package/dist/diagram/value-labels.d.ts +24 -0
  94. package/dist/diagram/value-labels.js +69 -0
  95. package/dist/diagram/view-state.d.ts +12 -0
  96. package/dist/diagram/view-state.js +22 -0
  97. package/dist/diagram/voltage-levels.d.ts +14 -0
  98. package/dist/diagram/voltage-levels.js +20 -0
  99. package/dist/index.d.ts +1 -0
  100. package/dist/index.js +1 -0
  101. package/dist/state.svelte.d.ts +37 -1
  102. package/dist/state.svelte.js +35 -0
  103. package/dist/transformer-icon.d.ts +22 -13
  104. package/dist/transformer-icon.js +23 -13
  105. package/package.json +2 -2
@@ -0,0 +1,83 @@
1
+ import { ATTACHMENT_NAMES, ATTACHMENT_ORDER, neutralEarths } from './schematic-equipment.js';
2
+ export function treeWalk(tree, positions, stride = 3) {
3
+ const n = tree.parent.length;
4
+ const y = (i) => positions[i * stride + 1];
5
+ const x = (i) => positions[i * stride];
6
+ const order = (a, b) => y(a) - y(b) || x(a) - x(b) || a - b;
7
+ const children = Array.from({ length: n }, () => []);
8
+ for (let i = 0; i < n; i++)
9
+ if (tree.parent[i] !== i)
10
+ children[tree.parent[i]].push(i);
11
+ for (const list of children)
12
+ list.sort(order);
13
+ const roots = [...tree.roots].sort(order);
14
+ const siblings = (i) => (tree.parent[i] === i ? roots : children[tree.parent[i]]);
15
+ return {
16
+ children,
17
+ step(from, key) {
18
+ if (from < 0 || from >= n)
19
+ return roots[0] ?? -1;
20
+ switch (key) {
21
+ case 'parent':
22
+ return tree.parent[from];
23
+ case 'child': {
24
+ // Straight ahead: the child drawn nearest this bus's row.
25
+ const kids = children[from];
26
+ if (!kids.length)
27
+ return from;
28
+ return kids.reduce((best, k) => Math.abs(y(k) - y(from)) < Math.abs(y(best) - y(from)) ? k : best);
29
+ }
30
+ case 'previous':
31
+ case 'next': {
32
+ const list = siblings(from);
33
+ const at = list.indexOf(from);
34
+ const to = at + (key === 'next' ? 1 : -1);
35
+ return to >= 0 && to < list.length ? list[to] : from;
36
+ }
37
+ case 'root': {
38
+ let i = from;
39
+ while (tree.parent[i] !== i)
40
+ i = tree.parent[i];
41
+ return i;
42
+ }
43
+ }
44
+ }
45
+ };
46
+ }
47
+ /** A spoken description of bus `i` for the keyboard cursor: where it sits in the tree, what hangs
48
+ * off it, and an optional result note (for example the worst voltage against its band). */
49
+ export function describeBus(graph, tree, walk, i, note) {
50
+ const bus = graph.buses[i];
51
+ let root = i;
52
+ while (tree.parent[root] !== root)
53
+ root = tree.parent[root];
54
+ const depth = tree.depth[i];
55
+ const where = depth === 0
56
+ ? bus.has_source
57
+ ? 'source bus'
58
+ : 'not reached from a source'
59
+ : `${depth} step${depth === 1 ? '' : 's'} from ${graph.buses[root].id}`;
60
+ const via = tree.parentEdge[i] >= 0
61
+ ? `, fed by ${graph.edges[tree.parentEdge[i]].kind} ${graph.edges[tree.parentEdge[i]].id}`
62
+ : '';
63
+ const onward = walk.children[i].length;
64
+ const counts = new Map();
65
+ const earths = neutralEarths(bus);
66
+ for (const list of Object.values(bus.terminal_attachments ?? {}))
67
+ for (const a of list) {
68
+ const kind = a.kind === 'shunt' && earths.has(a.id) ? 'earth' : a.kind;
69
+ counts.set(kind, (counts.get(kind) ?? new Set()).add(a.id));
70
+ }
71
+ if (bus.grounded?.length)
72
+ counts.set('ground', new Set(bus.grounded));
73
+ const attached = ATTACHMENT_ORDER.filter((k) => counts.has(k)).map((k) => {
74
+ const c = counts.get(k).size;
75
+ return `${c} ${ATTACHMENT_NAMES[k]}${c === 1 ? '' : 's'}`;
76
+ });
77
+ return [
78
+ `${bus.id}: ${where}${via}`,
79
+ `${onward} branch${onward === 1 ? '' : 'es'} onward`,
80
+ ...(attached.length ? [attached.join(', ')] : []),
81
+ ...(note ? [note] : [])
82
+ ].join('. ');
83
+ }
@@ -0,0 +1,24 @@
1
+ import type { ResultSnapshot } from './snapshot.js';
2
+ export declare const VALUE_LABEL_BUDGET = 60;
3
+ export declare const MIN_LABEL_ZOOM = -0.2;
4
+ export type LabelState = 'violation' | 'warning' | 'normal';
5
+ export interface LabelCandidate {
6
+ text: string;
7
+ state: LabelState;
8
+ priority: number;
9
+ /** Screen position in CSS pixels. */
10
+ screen: [number, number];
11
+ selected?: boolean;
12
+ }
13
+ /** Greedy placement by (selected, severity, priority) on a coarse collision grid. The selection is
14
+ * always placed; others must fit inside the canvas margins and the budget. */
15
+ export declare function placeLabels<T extends LabelCandidate>(candidates: T[], { width, height, budget, box, margin }: {
16
+ width: number;
17
+ height: number;
18
+ budget?: number;
19
+ box?: [number, number];
20
+ margin?: [number, number, number, number];
21
+ }): T[];
22
+ /** The pair nearest a limit, shown in volts. Buses without declared limits rank last. */
23
+ export declare function busLabel(result: ResultSnapshot, bus: number): Omit<LabelCandidate, 'screen'> | null;
24
+ export declare function branchLabel(result: ResultSnapshot, branch: number, family: 'loading' | 'power'): Omit<LabelCandidate, 'screen'> | null;
@@ -0,0 +1,69 @@
1
+ import { bandPosition } from './colour-modes.js';
2
+ import { loadingState } from './summary.js';
3
+ export const VALUE_LABEL_BUDGET = 60;
4
+ export const MIN_LABEL_ZOOM = -0.2;
5
+ const SEVERITY = { violation: 2, warning: 1, normal: 0 };
6
+ /** Greedy placement by (selected, severity, priority) on a coarse collision grid. The selection is
7
+ * always placed; others must fit inside the canvas margins and the budget. */
8
+ export function placeLabels(candidates, { width, height, budget = VALUE_LABEL_BUDGET, box = [64, 16], margin = [0, 40, 0, 24] }) {
9
+ const sorted = [...candidates].sort((a, b) => Number(!!b.selected) - Number(!!a.selected) ||
10
+ SEVERITY[b.state] - SEVERITY[a.state] ||
11
+ b.priority - a.priority);
12
+ const [cw, ch] = box;
13
+ const occupied = new Set();
14
+ const placed = [];
15
+ for (const c of sorted) {
16
+ if (placed.length >= budget)
17
+ break;
18
+ const [sx, sy] = c.screen;
19
+ const outside = sx < margin[3] ||
20
+ sx + cw > width - margin[1] ||
21
+ sy - ch < margin[0] ||
22
+ sy > height - margin[2];
23
+ if (!c.selected && outside)
24
+ continue;
25
+ const keys = [];
26
+ for (let gy = Math.floor((sy - ch) / ch); gy <= Math.floor(sy / ch); gy++)
27
+ for (let gx = Math.floor(sx / cw); gx <= Math.floor((sx + cw) / cw); gx++)
28
+ keys.push(`${gx},${gy}`);
29
+ if (!c.selected && keys.some((k) => occupied.has(k)))
30
+ continue;
31
+ for (const k of keys)
32
+ occupied.add(k);
33
+ placed.push(c);
34
+ }
35
+ return placed;
36
+ }
37
+ const short = (x) => Number(x.toPrecision(Math.abs(x) >= 1000 ? 5 : 4)).toLocaleString();
38
+ /** The pair nearest a limit, shown in volts. Buses without declared limits rank last. */
39
+ export function busLabel(result, bus) {
40
+ const pairs = result.buses[bus].pairs;
41
+ if (!pairs.length)
42
+ return null;
43
+ // Distance from mid-band; −1 without declared limits, so those pairs and buses rank last.
44
+ const rank = (p) => {
45
+ const band = bandPosition(p);
46
+ return band === null ? -1 : Math.abs(band);
47
+ };
48
+ let pick = pairs[0];
49
+ let t = rank(pick);
50
+ for (const p of pairs) {
51
+ const pt = rank(p);
52
+ if (pt > t)
53
+ [pick, t] = [p, pt];
54
+ }
55
+ return { text: `${short(pick.volts)} V`, state: t > 1 ? 'violation' : 'normal', priority: t };
56
+ }
57
+ export function branchLabel(result, branch, family) {
58
+ const row = result.branches[branch];
59
+ if (family === 'loading') {
60
+ const state = loadingState(row.loading);
61
+ return state === 'missing'
62
+ ? null
63
+ : { text: `${row.loading.toFixed(1)} %`, state, priority: row.loading };
64
+ }
65
+ const end = row.ends[0] ?? row.ends[1];
66
+ return end
67
+ ? { text: `${short(Math.abs(end.p))} kW`, state: 'normal', priority: Math.abs(end.p) }
68
+ : null;
69
+ }
@@ -0,0 +1,12 @@
1
+ /** Shareable view state. Selections travel as entity keys (source identities), never array offsets. */
2
+ export interface CameraState {
3
+ target: [number, number, number];
4
+ zoom: number;
5
+ }
6
+ export declare function encodeCamera(c: {
7
+ target: ArrayLike<number>;
8
+ zoom: number;
9
+ }): string;
10
+ export declare function decodeCamera(text: string | null | undefined): CameraState | null;
11
+ /** Merge state into a URL without dropping unrelated parameters; `null` or `''` removes a key. */
12
+ export declare function withView(href: string | URL, state: Record<string, string | number | null | undefined>): URL;
@@ -0,0 +1,22 @@
1
+ export function encodeCamera(c) {
2
+ return [c.target[0].toFixed(1), c.target[1].toFixed(1), c.zoom.toFixed(2)].join(',');
3
+ }
4
+ export function decodeCamera(text) {
5
+ const parts = String(text ?? '')
6
+ .split(',')
7
+ .map(Number);
8
+ return parts.length === 3 && parts.every(Number.isFinite) && parts[2] >= -24 && parts[2] <= 5
9
+ ? { target: [parts[0], parts[1], 0], zoom: parts[2] }
10
+ : null;
11
+ }
12
+ /** Merge state into a URL without dropping unrelated parameters; `null` or `''` removes a key. */
13
+ export function withView(href, state) {
14
+ const url = new URL(href);
15
+ for (const [k, v] of Object.entries(state)) {
16
+ if (v === null || v === undefined || v === '')
17
+ url.searchParams.delete(k);
18
+ else
19
+ url.searchParams.set(k, String(v));
20
+ }
21
+ return url;
22
+ }
@@ -0,0 +1,14 @@
1
+ import type { GalvanicZone } from './galvanic-zones.js';
2
+ /** Voltage classes for drawing, by a zone's level in volts. Wide bands, so a single-phase winding's
3
+ * own rating (7.2 kV of a 12.47 kV system, 19.1 kV SWER) lands where its system does. */
4
+ export type VoltageClass = 'hv' | 'sub' | 'mv' | 'lv';
5
+ export declare const VOLTAGE_CLASSES: {
6
+ cls: VoltageClass;
7
+ name: string;
8
+ from: number;
9
+ }[];
10
+ /** The class of a zone: by its level when known; otherwise the zone holding a source counts as MV
11
+ * and the others as LV, as for line weights. */
12
+ export declare function voltageClass(zone: GalvanicZone, volts: number | null): VoltageClass;
13
+ /** A level as engineers name it: "11 kV", "400 V", "19.1 kV". */
14
+ export declare function levelLabel(volts: number): string;
@@ -0,0 +1,20 @@
1
+ export const VOLTAGE_CLASSES = [
2
+ { cls: 'hv', name: 'transmission', from: 100_000 },
3
+ { cls: 'sub', name: 'subtransmission', from: 25_000 },
4
+ { cls: 'mv', name: 'MV', from: 1_000 },
5
+ { cls: 'lv', name: 'LV', from: 0 }
6
+ ];
7
+ /** The class of a zone: by its level when known; otherwise the zone holding a source counts as MV
8
+ * and the others as LV, as for line weights. */
9
+ export function voltageClass(zone, volts) {
10
+ if (volts === null)
11
+ return zone.hasSource ? 'mv' : 'lv';
12
+ return VOLTAGE_CLASSES.find((c) => volts >= c.from).cls;
13
+ }
14
+ /** A level as engineers name it: "11 kV", "400 V", "19.1 kV". */
15
+ export function levelLabel(volts) {
16
+ const kv = volts / 1000;
17
+ if (kv < 1)
18
+ return `${Math.round(volts)} V`;
19
+ return `${Number(kv.toPrecision(4))} kV`;
20
+ }
package/dist/index.d.ts CHANGED
@@ -27,3 +27,4 @@ export type { BusSizing, CameraSnapshot, DiagramCameraSnapshot, DemandRangeMode,
27
27
  export type { MultiView, PlacedMultiBus, PlacedMultiEdge } from './multiconductor.js';
28
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';
29
29
  export { default as McResults } from './components/McResults.svelte';
30
+ export { default as McWorkbench } from './components/McWorkbench.svelte';
package/dist/index.js CHANGED
@@ -17,3 +17,4 @@ export { distExtensionFormat } from './drop-classify.js';
17
17
  export { caseDeltas, caseRatings, displayMetaFor, displaySeriesFor } from './display.js';
18
18
  export { DEFAULT_FORMULATION, FORMULATIONS, BrowserStudy, Study, capabilities, createStudy, errorText, formatOf, classifyJson, ingestJsonDrop, ingestCase, isDisplayFile, isPermanentEngineFailure, parseDisplay, solveModule } from '@tellegen/engine';
19
19
  export { default as McResults } from './components/McResults.svelte';
20
+ export { default as McWorkbench } from './components/McWorkbench.svelte';
@@ -1,7 +1,10 @@
1
1
  import type { CaseSummary, DemandDeltas, Network, NetworkBranch, NetworkBus, SensitivityColumn, Solution, SolveIteration } from './api.js';
2
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
- export type SolveBackend = 'clarabel-wasm' | 'clarabel-wasm-server-sensitivity' | 'rust-server';
4
+ import { type GraphInputs } from './diagram/graph-inputs.js';
5
+ import { type SearchIndex } from './diagram/search.js';
6
+ import { type ResultSnapshot } from './diagram/snapshot.js';
7
+ export type SolveBackend = 'clarabel-wasm' | 'clarabel-wasm-server-sensitivity' | 'pounce-wasi' | 'rust-server';
5
8
  /** A map framing request: a case id, 'all', or one branch to center. */
6
9
  export type FrameTarget = string | 'all' | {
7
10
  caseId: string;
@@ -117,6 +120,8 @@ export declare class LocalCase {
117
120
  solving: boolean;
118
121
  solveMs: number | null;
119
122
  solveBackend: SolveBackend | null;
123
+ /** Short solver-specific diagnostic shown beside the elapsed time. */
124
+ solveDetail: string | null;
120
125
  solveFallbackReason: string | null;
121
126
  /** Monotone token: only the latest solve may write this case. */
122
127
  solveSeq: number;
@@ -155,6 +160,8 @@ export declare class CaseState {
155
160
  solving: boolean;
156
161
  solveMs: number | null;
157
162
  solveBackend: SolveBackend | null;
163
+ /** Short solver-specific diagnostic shown beside the elapsed time. */
164
+ solveDetail: string | null;
158
165
  solveFallbackReason: string | null;
159
166
  /** Monotone token: only the latest solve may write this case. */
160
167
  solveSeq: number;
@@ -231,6 +238,32 @@ export declare class MulticonductorCase {
231
238
  /** The selected edge id, whose conductor pairing expands in the panel;
232
239
  * mutually exclusive with selectedBusId. */
233
240
  selectedEdgeId: string | null;
241
+ /** What the pointer is over in any linked view (schematic, profile, tables); highlighted in
242
+ * the others. Transient: never saved or put in links. */
243
+ hovered: {
244
+ kind: "bus" | "branch";
245
+ id: string;
246
+ } | null;
247
+ /** Bumped to ask the views to bring the current selection into view again (an issue clicked
248
+ * that is already selected). */
249
+ revealSeq: number;
250
+ /** The full operating point read for whole-network views (overview, schematic); null until read,
251
+ * and cleared when a large case's operating point changes. */
252
+ overviewResult: McPfResult | null;
253
+ overviewLoading: boolean;
254
+ /** The graph's buses and branches as the overview and the diagram read them: rebuilt when the
255
+ * graph is replaced, never after a solve. Both views read this one copy. */
256
+ overviewInputs: GraphInputs | null;
257
+ /** Search over the graph's buses and branches, rebuilt with `overviewInputs`. */
258
+ overviewSearch: SearchIndex | null;
259
+ /** `overviewResult` projected onto the graph, built once per operating point and shared by the
260
+ * overview and the diagram; null while there is none. */
261
+ overviewSnapshot: ResultSnapshot | null;
262
+ /** Show this case as a schematic instead of the map. */
263
+ schematic: boolean;
264
+ /** The diagram's last geographic view as a map camera, for the map to continue from when the
265
+ * diagram closes. Plain, not reactive: it is read once, on close. */
266
+ lastGeoView: CameraSnapshot | null;
234
267
  constructor(init: {
235
268
  id: string;
236
269
  label: string;
@@ -264,6 +297,9 @@ export interface StudyDisplaySnapshot {
264
297
  export declare class AppState {
265
298
  #private;
266
299
  camera: CameraSnapshot | null;
300
+ /** Set when a view hands its camera to the map it returns to (the diagram closing from
301
+ * geographic positions): the re-created map starts at `camera` instead of framing the case. */
302
+ cameraHandoff: boolean;
267
303
  cameraRequest: CameraSnapshot | null;
268
304
  cameraSeq: number;
269
305
  diagramCamera: DiagramCameraSnapshot | null;
@@ -1,4 +1,7 @@
1
1
  import { DEFAULT_FORMULATION } from '@tellegen/engine';
2
+ import { graphInputs } from './diagram/graph-inputs.js';
3
+ import { buildSearchIndex } from './diagram/search.js';
4
+ import { snapshotFromMcPf } from './diagram/snapshot.js';
2
5
  /** A case is perturbed when any committed demand or rating delta is nonzero. Shared
3
6
  * by both solvable case classes so the "perturbed" rule stays single-sourced. */
4
7
  const hasPerturbation = (edits) => Object.values(edits).some((mw) => mw !== 0);
@@ -40,6 +43,8 @@ export class LocalCase {
40
43
  solving = $state(false);
41
44
  solveMs = $state(null);
42
45
  solveBackend = $state(null);
46
+ /** Short solver-specific diagnostic shown beside the elapsed time. */
47
+ solveDetail = $state(null);
43
48
  solveFallbackReason = $state(null);
44
49
  /** Monotone token: only the latest solve may write this case. */
45
50
  solveSeq = 0;
@@ -92,6 +97,8 @@ export class CaseState {
92
97
  solving = $state(false);
93
98
  solveMs = $state(null);
94
99
  solveBackend = $state(null);
100
+ /** Short solver-specific diagnostic shown beside the elapsed time. */
101
+ solveDetail = $state(null);
95
102
  solveFallbackReason = $state(null);
96
103
  /** Monotone token: only the latest solve may write this case. */
97
104
  solveSeq = 0;
@@ -158,6 +165,31 @@ export class MulticonductorCase {
158
165
  /** The selected edge id, whose conductor pairing expands in the panel;
159
166
  * mutually exclusive with selectedBusId. */
160
167
  selectedEdgeId = $state(null);
168
+ /** What the pointer is over in any linked view (schematic, profile, tables); highlighted in
169
+ * the others. Transient: never saved or put in links. */
170
+ hovered = $state(null);
171
+ /** Bumped to ask the views to bring the current selection into view again (an issue clicked
172
+ * that is already selected). */
173
+ revealSeq = $state(0);
174
+ /** The full operating point read for whole-network views (overview, schematic); null until read,
175
+ * and cleared when a large case's operating point changes. */
176
+ overviewResult = $state.raw(null);
177
+ overviewLoading = $state(false);
178
+ /** The graph's buses and branches as the overview and the diagram read them: rebuilt when the
179
+ * graph is replaced, never after a solve. Both views read this one copy. */
180
+ overviewInputs = $derived(this.graph ? graphInputs(this.graph) : null);
181
+ /** Search over the graph's buses and branches, rebuilt with `overviewInputs`. */
182
+ overviewSearch = $derived(this.overviewInputs ? buildSearchIndex(this.overviewInputs.entities) : null);
183
+ /** `overviewResult` projected onto the graph, built once per operating point and shared by the
184
+ * overview and the diagram; null while there is none. */
185
+ overviewSnapshot = $derived(this.overviewResult && this.overviewInputs
186
+ ? snapshotFromMcPf(this.overviewResult, this.overviewInputs.buses, this.overviewInputs.branches)
187
+ : null);
188
+ /** Show this case as a schematic instead of the map. */
189
+ schematic = $state(false);
190
+ /** The diagram's last geographic view as a map camera, for the map to continue from when the
191
+ * diagram closes. Plain, not reactive: it is read once, on close. */
192
+ lastGeoView = null;
161
193
  constructor(init) {
162
194
  this.id = init.id;
163
195
  this.label = init.label;
@@ -200,6 +232,9 @@ export class MulticonductorCase {
200
232
  }
201
233
  export class AppState {
202
234
  camera = $state.raw(null);
235
+ /** Set when a view hands its camera to the map it returns to (the diagram closing from
236
+ * geographic positions): the re-created map starts at `camera` instead of framing the case. */
237
+ cameraHandoff = false;
203
238
  cameraRequest = $state.raw(null);
204
239
  cameraSeq = $state(0);
205
240
  diagramCamera = $state.raw(null);
@@ -1,15 +1,24 @@
1
- /** The IEC two-winding transformer symbol as a deck.gl icon: two overlapping
2
- * circles, rasterized once to a data URL. Drawn white on transparent with
3
- * `mask: true`, so the layer's getColor tints it. */
4
- export interface TransformerIcon {
5
- url: string;
6
- width: number;
7
- height: number;
8
- anchorX: number;
9
- anchorY: number;
10
- mask: true;
11
- }
1
+ /** The IEC two-winding transformer symbol as a prepacked deck.gl icon atlas:
2
+ * two overlapping circles on a canvas that deck.gl uploads as a texture.
3
+ * Nothing is fetched, so the symbol needs no `data:` in a CSP's connect-src.
4
+ * Drawn white on transparent with `mask: true`, so the layer's getColor tints
5
+ * it. */
6
+ /** The icon name for the layer's getIcon. */
7
+ export declare const TRANSFORMER_ICON = "transformer";
8
+ /** The atlas holds the one symbol, anchored at its center. */
9
+ export declare const transformerIconMapping: {
10
+ transformer: {
11
+ x: number;
12
+ y: number;
13
+ width: number;
14
+ height: number;
15
+ anchorX: number;
16
+ anchorY: number;
17
+ mask: boolean;
18
+ };
19
+ };
12
20
  /** Rasterize the glyph lazily; layers only build client-side, after the map
13
21
  * modules load, so document is always available here. The 128 px backing keeps
14
- * a ~16 px on-screen symbol crisp on 2x/3x displays. */
15
- export declare function transformerIcon(): TransformerIcon;
22
+ * a ~16 px on-screen symbol crisp on 2x/3x displays. Every call returns the
23
+ * same canvas, so deck.gl's prop diff keeps the texture it made from it. */
24
+ export declare function transformerIconAtlas(): HTMLCanvasElement;
@@ -1,15 +1,32 @@
1
- /** The IEC two-winding transformer symbol as a deck.gl icon: two overlapping
2
- * circles, rasterized once to a data URL. Drawn white on transparent with
3
- * `mask: true`, so the layer's getColor tints it. */
1
+ /** The IEC two-winding transformer symbol as a prepacked deck.gl icon atlas:
2
+ * two overlapping circles on a canvas that deck.gl uploads as a texture.
3
+ * Nothing is fetched, so the symbol needs no `data:` in a CSP's connect-src.
4
+ * Drawn white on transparent with `mask: true`, so the layer's getColor tints
5
+ * it. */
4
6
  const SIZE = 128;
5
7
  const RADIUS = 34;
6
8
  const OFFSET = 17;
7
9
  const STROKE = 9;
10
+ /** The icon name for the layer's getIcon. */
11
+ export const TRANSFORMER_ICON = 'transformer';
12
+ /** The atlas holds the one symbol, anchored at its center. */
13
+ export const transformerIconMapping = {
14
+ [TRANSFORMER_ICON]: {
15
+ x: 0,
16
+ y: 0,
17
+ width: SIZE,
18
+ height: SIZE,
19
+ anchorX: SIZE / 2,
20
+ anchorY: SIZE / 2,
21
+ mask: true
22
+ }
23
+ };
8
24
  let cached = null;
9
25
  /** Rasterize the glyph lazily; layers only build client-side, after the map
10
26
  * modules load, so document is always available here. The 128 px backing keeps
11
- * a ~16 px on-screen symbol crisp on 2x/3x displays. */
12
- export function transformerIcon() {
27
+ * a ~16 px on-screen symbol crisp on 2x/3x displays. Every call returns the
28
+ * same canvas, so deck.gl's prop diff keeps the texture it made from it. */
29
+ export function transformerIconAtlas() {
13
30
  if (cached)
14
31
  return cached;
15
32
  const canvas = document.createElement('canvas');
@@ -23,13 +40,6 @@ export function transformerIcon() {
23
40
  ctx.arc(SIZE / 2 + dx, SIZE / 2, RADIUS, 0, 2 * Math.PI);
24
41
  ctx.stroke();
25
42
  }
26
- cached = {
27
- url: canvas.toDataURL(),
28
- width: SIZE,
29
- height: SIZE,
30
- anchorX: SIZE / 2,
31
- anchorY: SIZE / 2,
32
- mask: true
33
- };
43
+ cached = canvas;
34
44
  return cached;
35
45
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tellegen/svelte",
3
- "version": "0.4.3",
3
+ "version": "0.4.4",
4
4
  "description": "Svelte components for tellegen power system maps, local case files, and browser solves.",
5
5
  "keywords": [
6
6
  "svelte",
@@ -67,7 +67,7 @@
67
67
  "pack:dry-run": "npm pack --dry-run --cache ../../node_modules/.cache/npm-pack"
68
68
  },
69
69
  "dependencies": {
70
- "@tellegen/engine": "^0.4.1"
70
+ "@tellegen/engine": "^0.4.3"
71
71
  },
72
72
  "peerDependencies": {
73
73
  "@deck.gl/core": "^9.4.0",