@tellegen/svelte 0.3.0 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (67) hide show
  1. package/README.md +233 -0
  2. package/dist/Sparkline.svelte +2 -2
  3. package/dist/TellegenMap.svelte +201 -47
  4. package/dist/TellegenMap.svelte.d.ts +45 -0
  5. package/dist/TellegenProvider.svelte +36 -4
  6. package/dist/TellegenShell.svelte +14 -42
  7. package/dist/TellegenViewer.svelte +12 -2
  8. package/dist/api.d.ts +10 -2
  9. package/dist/api.js +2 -0
  10. package/dist/basemap.d.ts +192 -0
  11. package/dist/basemap.js +259 -0
  12. package/dist/colors.d.ts +18 -0
  13. package/dist/colors.js +26 -0
  14. package/dist/components/AppFooter.svelte +3 -2
  15. package/dist/components/AppHeader.svelte +21 -8
  16. package/dist/components/BindingLines.svelte +1 -1
  17. package/dist/components/BusPicker.svelte +3 -3
  18. package/dist/components/DemandSlider.svelte +16 -7
  19. package/dist/components/DiagramCanvas.svelte +1 -1
  20. package/dist/components/DisplayControls.svelte +5 -1
  21. package/dist/components/DropZone.svelte +1 -1
  22. package/dist/components/FormulationSelector.svelte +1 -1
  23. package/dist/components/LocalCaseDetails.svelte +6 -3
  24. package/dist/components/McResults.svelte +98 -35
  25. package/dist/components/McResults.svelte.d.ts +8 -2
  26. package/dist/components/ModelDetails.svelte +5 -5
  27. package/dist/components/MulticonductorDetails.svelte +9 -9
  28. package/dist/components/NotificationCenter.svelte +4 -2
  29. package/dist/components/PanelCard.svelte +72 -7
  30. package/dist/components/PanelHost.svelte +59 -34
  31. package/dist/components/PanelHost.svelte.d.ts +4 -1
  32. package/dist/components/PlacementCue.svelte +1 -1
  33. package/dist/components/PowerFlowDetails.svelte +1 -1
  34. package/dist/components/RatingSlider.svelte +14 -5
  35. package/dist/components/RestoreDefaultsButton.svelte +1 -1
  36. package/dist/components/SavedNetworkDetails.svelte +3 -3
  37. package/dist/components/SensitivityReadout.svelte +2 -2
  38. package/dist/components/ShellEnvironment.svelte +51 -0
  39. package/dist/components/ShellEnvironment.svelte.d.ts +18 -0
  40. package/dist/components/SizeLegend.svelte +2 -2
  41. package/dist/components/SolveCard.svelte +3 -3
  42. package/dist/components/TopMovers.svelte +1 -1
  43. package/dist/components.d.ts +3 -0
  44. package/dist/components.js +3 -0
  45. package/dist/context.svelte.d.ts +8 -1
  46. package/dist/context.svelte.js +8 -3
  47. package/dist/controller.svelte.d.ts +65 -5
  48. package/dist/controller.svelte.js +262 -76
  49. package/dist/format.d.ts +9 -0
  50. package/dist/format.js +12 -0
  51. package/dist/index.d.ts +5 -1
  52. package/dist/index.js +2 -0
  53. package/dist/map.d.ts +4 -2
  54. package/dist/map.js +2 -1
  55. package/dist/maplibre-worker.d.ts +5 -0
  56. package/dist/maplibre-worker.dev.d.ts +11 -0
  57. package/dist/maplibre-worker.dev.js +15 -0
  58. package/dist/maplibre-worker.js +8 -0
  59. package/dist/notices.svelte.js +5 -1
  60. package/dist/panels.svelte.d.ts +6 -0
  61. package/dist/panels.svelte.js +31 -1
  62. package/dist/slider.d.ts +12 -0
  63. package/dist/slider.js +31 -0
  64. package/dist/state.svelte.d.ts +19 -2
  65. package/dist/state.svelte.js +12 -0
  66. package/dist/styles.css +59 -6
  67. package/package.json +13 -8
@@ -1,6 +1,11 @@
1
1
  <script lang="ts">
2
2
  import { tick } from 'svelte';
3
- import { getPanelLayout, type PanelRegistration, type PanelSide } from '../panels.svelte.js';
3
+ import {
4
+ focusFirst,
5
+ getPanelLayout,
6
+ type PanelRegistration,
7
+ type PanelSide
8
+ } from '../panels.svelte.js';
4
9
  let {
5
10
  panel,
6
11
  floating = false,
@@ -12,6 +17,7 @@
12
17
  } = $props();
13
18
  const layout = getPanelLayout();
14
19
  let menu = $state(false);
20
+ let menuAnchor: HTMLDivElement | undefined = $state();
15
21
  let card: HTMLElement;
16
22
  const bounds = $derived(layout.bounds(panel));
17
23
  const preferred = $derived(!floating && !mobile && layout.preferred(panel));
@@ -86,11 +92,40 @@
86
92
  }
87
93
  async function close() {
88
94
  const title = panel.title;
95
+ const position = layout.position(panel);
96
+ const dock = position === 'floating' ? panel.side : position;
97
+ const opener = layout.opener(panel);
89
98
  layout.close(panel);
90
99
  await tick();
91
- document
92
- .querySelector<HTMLElement>(`.panel-toolbar button[aria-label="${CSS.escape(title)}"]`)
93
- ?.focus();
100
+ // The toolbar button for the panel; a host without the toolbar gets the
101
+ // control that opened it, else the panel's dock (the drawer when narrow).
102
+ focusFirst([
103
+ document.querySelector<HTMLElement>(
104
+ `.panel-toolbar button[aria-label="${CSS.escape(title)}"]`
105
+ ),
106
+ opener,
107
+ document.querySelector<HTMLElement>(`[data-panel-dock="${dock}"], [data-panel-drawer]`)
108
+ ]);
109
+ }
110
+ // Keyboard users can scroll an overflowing panel only through focus, so the
111
+ // content becomes a labelled, focusable region exactly while it overflows.
112
+ let overflowing = $state(false);
113
+ function trackOverflow(content: HTMLElement) {
114
+ const sync = () => (overflowing = content.scrollHeight > content.clientHeight + 1);
115
+ const resize = new ResizeObserver(sync);
116
+ const observe = () => {
117
+ resize.disconnect();
118
+ resize.observe(content);
119
+ for (const child of content.children) resize.observe(child);
120
+ sync();
121
+ };
122
+ const children = new MutationObserver(observe);
123
+ children.observe(content, { childList: true });
124
+ observe();
125
+ return () => {
126
+ resize.disconnect();
127
+ children.disconnect();
128
+ };
94
129
  }
95
130
  </script>
96
131
 
@@ -98,6 +133,9 @@
98
133
  onkeydown={(event) => {
99
134
  if (event.key === 'Escape') menu = false;
100
135
  }}
136
+ onpointerdown={(event) => {
137
+ if (menu && !menuAnchor?.contains(event.target as Node)) menu = false;
138
+ }}
101
139
  />
102
140
  <aside
103
141
  bind:this={card}
@@ -127,7 +165,7 @@
127
165
  >
128
166
  {/if}
129
167
  <div class="header-actions">{@render panel.headerActions?.()}</div>
130
- {#if !mobile}<div class="menu-anchor">
168
+ {#if !mobile}<div class="menu-anchor" bind:this={menuAnchor}>
131
169
  <button
132
170
  class="icon"
133
171
  aria-label={`${panel.title} panel options`}
@@ -150,7 +188,7 @@
150
188
  </div>{/if}
151
189
  </div>{/if}
152
190
  <button
153
- class="icon"
191
+ class="icon close"
154
192
  aria-label={`Close ${panel.title.toLowerCase()} panel`}
155
193
  title="Close panel"
156
194
  onclick={close}
@@ -160,7 +198,18 @@
160
198
  >
161
199
  </button>
162
200
  </header>
163
- <div class="panel-content">{@render panel.children()}</div>
201
+ <!-- A scroll container must take focus for keyboard scrolling, and this one
202
+ takes it only while it overflows. -->
203
+ <!-- svelte-ignore a11y_no_noninteractive_tabindex -->
204
+ <div
205
+ class="panel-content"
206
+ {@attach trackOverflow}
207
+ tabindex={overflowing ? 0 : undefined}
208
+ role={overflowing ? 'region' : undefined}
209
+ aria-label={overflowing ? `${panel.title} panel content` : undefined}
210
+ >
211
+ {@render panel.children()}
212
+ </div>
164
213
  {#if floating}<button
165
214
  class="resize"
166
215
  aria-label={`Resize ${panel.title} panel`}
@@ -238,6 +287,19 @@
238
287
  .icon svg {
239
288
  fill: currentColor;
240
289
  }
290
+ /* Coarse pointers: a 44px hit area around the 26x28 close button. The
291
+ pseudo-element reaches into the header padding and the gap, so nothing
292
+ moves. */
293
+ @media (pointer: coarse) {
294
+ .close {
295
+ position: relative;
296
+ }
297
+ .close::after {
298
+ content: '';
299
+ position: absolute;
300
+ inset: -8px -10px -8px -8px;
301
+ }
302
+ }
241
303
  .header-actions {
242
304
  display: flex;
243
305
  align-items: center;
@@ -258,6 +320,9 @@
258
320
  flex-direction: column;
259
321
  border-radius: 0 0 5px 5px;
260
322
  }
323
+ .panel-content:focus-visible {
324
+ outline-offset: -2px;
325
+ }
261
326
  .menu-anchor {
262
327
  position: relative;
263
328
  }
@@ -4,6 +4,12 @@
4
4
  import { getPanelLayout } from '../panels.svelte.js';
5
5
  import { getNoticeCenter } from '../notices.svelte.js';
6
6
  import PanelCard from './PanelCard.svelte';
7
+
8
+ // The dock insets come from `--tellegen-dock-left/right/bottom` on an
9
+ // ancestor (defaults 20px, 20px, 100px), so a custom shell with its own
10
+ // rail or status bar can move the docks without restyling them.
11
+ let { showToolbar = true }: { showToolbar?: boolean } = $props();
12
+
7
13
  const app = getAppState();
8
14
  const layout = getPanelLayout();
9
15
  const notices = getNoticeCenter();
@@ -55,43 +61,59 @@
55
61
  onpointerup={() => layout.finishGesture()}
56
62
  onpointercancel={() => layout.finishGesture()}
57
63
  />
58
- <div class="panel-toolbar" style:top="{toolbarTop}px" aria-label="Panels">
59
- <div class="panel-buttons">
60
- {#each layout.ordered().filter((p) => p.side === 'left') as panel (panel.id)}
61
- <button
62
- aria-label={panel.title}
63
- aria-pressed={layout.compact ? layout.drawer === panel.id : panel.open}
64
- onclick={() => layout.toggle(panel)}>{panel.title}</button
65
- >
66
- {/each}
67
- </div>
68
- <div class="panel-buttons right">
69
- {#each layout.ordered().filter((p) => p.side === 'right') as panel (panel.id)}
64
+ {#if showToolbar}
65
+ <div class="panel-toolbar" style:top="{toolbarTop}px" aria-label="Panels">
66
+ <div class="panel-buttons">
67
+ {#each layout.ordered().filter((p) => p.side === 'left') as panel (panel.id)}
68
+ <button
69
+ aria-label={panel.title}
70
+ aria-pressed={layout.compact ? layout.drawer === panel.id : panel.open}
71
+ onclick={() => layout.toggle(panel)}>{panel.title}</button
72
+ >
73
+ {/each}
74
+ </div>
75
+ <div class="panel-buttons right">
76
+ {#each layout.ordered().filter((p) => p.side === 'right') as panel (panel.id)}
77
+ <button
78
+ aria-label={panel.title}
79
+ aria-pressed={layout.compact ? layout.drawer === panel.id : panel.open}
80
+ onclick={() => layout.toggle(panel)}>{panel.title}</button
81
+ >
82
+ {/each}
70
83
  <button
71
- aria-label={panel.title}
72
- aria-pressed={layout.compact ? layout.drawer === panel.id : panel.open}
73
- onclick={() => layout.toggle(panel)}>{panel.title}</button
84
+ class="reset-layout"
85
+ onclick={() => layout.reset()}
86
+ title="Restore the default panel positions">Reset layout</button
74
87
  >
75
- {/each}
76
- <button
77
- class="reset-layout"
78
- onclick={() => layout.reset()}
79
- title="Restore the default panel positions">Reset layout</button
80
- >
88
+ </div>
81
89
  </div>
82
- </div>
90
+ {/if}
83
91
  {#if layout.storageError}<p class="layout-error" role="status" style:top="{panelTop}px">
84
92
  {layout.storageError}
85
93
  </p>{/if}
94
+ <!-- The docks and the drawer take focus (tabindex -1) when a closed panel has
95
+ no toolbar button or opener to hand it back to. The drawer stays rendered,
96
+ at zero height, while no panel is open in it. -->
86
97
  {#if layout.compact}
87
- {#if drawer}<div class="drawer" style:bottom="{bottomReserve}px" style:height="{drawerHeight}px">
88
- <PanelCard panel={drawer} mobile />
89
- </div>{/if}
98
+ <div
99
+ class="drawer"
100
+ data-panel-drawer
101
+ role="region"
102
+ aria-label="Panels"
103
+ tabindex="-1"
104
+ style:bottom="{bottomReserve}px"
105
+ style:height="{drawer ? drawerHeight : 0}px"
106
+ >
107
+ {#if drawer}<PanelCard panel={drawer} mobile />{/if}
108
+ </div>
90
109
  {:else}
91
110
  <div
92
111
  class="dock left"
93
- style:bottom="{Math.max(100, bottomReserve)}px"
112
+ style:bottom="max(var(--tellegen-dock-bottom, 100px), {bottomReserve}px)"
94
113
  data-panel-dock="left"
114
+ role="region"
115
+ aria-label="Left panels"
116
+ tabindex="-1"
95
117
  style:top="{panelTop}px"
96
118
  style:width="{layout.dockWidth('left')}px"
97
119
  >
@@ -99,8 +121,11 @@
99
121
  </div>
100
122
  <div
101
123
  class="dock right"
102
- style:bottom="{Math.max(100, bottomReserve)}px"
124
+ style:bottom="max(var(--tellegen-dock-bottom, 100px), {bottomReserve}px)"
103
125
  data-panel-dock="right"
126
+ role="region"
127
+ aria-label="Right panels"
128
+ tabindex="-1"
104
129
  style:top="{panelTop}px"
105
130
  style:width="{layout.dockWidth('right')}px"
106
131
  >
@@ -113,8 +138,8 @@
113
138
  .panel-toolbar {
114
139
  position: fixed;
115
140
  z-index: 30;
116
- left: 20px;
117
- right: 20px;
141
+ left: var(--tellegen-dock-left, 20px);
142
+ right: var(--tellegen-dock-right, 20px);
118
143
  display: flex;
119
144
  justify-content: space-between;
120
145
  align-items: center;
@@ -159,7 +184,7 @@
159
184
  .dock {
160
185
  position: fixed;
161
186
  z-index: 25;
162
- bottom: 100px;
187
+ bottom: var(--tellegen-dock-bottom, 100px);
163
188
  display: flex;
164
189
  flex-direction: column;
165
190
  align-items: stretch;
@@ -167,10 +192,10 @@
167
192
  pointer-events: none;
168
193
  }
169
194
  .dock.left {
170
- left: 20px;
195
+ left: var(--tellegen-dock-left, 20px);
171
196
  }
172
197
  .dock.right {
173
- right: 20px;
198
+ right: var(--tellegen-dock-right, 20px);
174
199
  }
175
200
  .drawer {
176
201
  position: fixed;
@@ -194,8 +219,8 @@
194
219
  }
195
220
  @media (max-width: 960px) {
196
221
  .panel-toolbar {
197
- left: 12px;
198
- right: 12px;
222
+ left: var(--tellegen-dock-left, 12px);
223
+ right: var(--tellegen-dock-right, 12px);
199
224
  gap: 6px;
200
225
  }
201
226
  .panel-buttons {
@@ -1,3 +1,6 @@
1
- declare const PanelHost: import("svelte").Component<Record<string, never>, {}, "">;
1
+ type $$ComponentProps = {
2
+ showToolbar?: boolean;
3
+ };
4
+ declare const PanelHost: import("svelte").Component<$$ComponentProps, {}, "">;
2
5
  type PanelHost = ReturnType<typeof PanelHost>;
3
6
  export default PanelHost;
@@ -21,7 +21,7 @@
21
21
  border-radius: 3px;
22
22
  color: var(--text-accent);
23
23
  font-size: 11px;
24
- box-shadow: 0 4px 18px rgba(32, 36, 43, 0.1);
24
+ box-shadow: var(--elev-cue);
25
25
  pointer-events: none;
26
26
  }
27
27
 
@@ -73,7 +73,7 @@
73
73
  h3 span {
74
74
  display: block;
75
75
  margin-top: 3px;
76
- font-size: 11px;
76
+ font-size: 13px;
77
77
  font-weight: 400;
78
78
  color: var(--text-secondary);
79
79
  }
@@ -1,6 +1,7 @@
1
1
  <script lang="ts">
2
2
  import { getController } from '../context.svelte.js';
3
3
  import { fmt, signed } from '../format.js';
4
+ import { sliderKeyValue } from '../slider.js';
4
5
 
5
6
  const ctrl = getController();
6
7
 
@@ -64,7 +65,15 @@
64
65
  bind:value={ctrl.ratingSliderCurrent, ctrl.setRatingPreview}
65
66
  aria-label="rating delta at selected line"
66
67
  onpointerdown={() => ctrl.setRatingPreview(ctrl.ratingSliderValue)}
67
- onkeydown={() => ctrl.setRatingPreview(ctrl.ratingSliderValue)}
68
+ onkeydown={(e) => {
69
+ const next = sliderKeyValue(e.key, ctrl.ratingSliderValue, bounds.min, bounds.max);
70
+ if (next === null) {
71
+ ctrl.setRatingPreview(ctrl.ratingSliderValue);
72
+ return;
73
+ }
74
+ e.preventDefault();
75
+ ctrl.setRatingPreview(next);
76
+ }}
68
77
  onpointerup={(e) => ctrl.finishRatingInput(Number(e.currentTarget.value))}
69
78
  onkeyup={(e) => ctrl.finishRatingInput(Number(e.currentTarget.value))}
70
79
  onchange={(e) => ctrl.finishRatingInput(Number(e.currentTarget.value))}
@@ -105,7 +114,7 @@
105
114
  .slider-head {
106
115
  display: flex;
107
116
  justify-content: space-between;
108
- font-size: 11.5px;
117
+ font-size: 13.5px;
109
118
  color: var(--text-secondary);
110
119
  margin-bottom: 4px;
111
120
  }
@@ -120,7 +129,7 @@
120
129
  justify-content: space-between;
121
130
  gap: 8px;
122
131
  margin: 6px 0 7px;
123
- font-size: 10.5px;
132
+ font-size: 12.5px;
124
133
  }
125
134
 
126
135
  .slider-track {
@@ -236,13 +245,13 @@
236
245
 
237
246
  .pred {
238
247
  margin: 2px 0 0;
239
- font-size: 11px;
248
+ font-size: 13px;
240
249
  min-height: 16px;
241
250
  }
242
251
 
243
252
  .score {
244
253
  margin: 8px 0 0;
245
- font-size: 11px;
254
+ font-size: 13px;
246
255
  color: var(--ink);
247
256
  min-height: 16px;
248
257
  }
@@ -24,7 +24,7 @@
24
24
  color: var(--text-secondary);
25
25
  font-size: 11px;
26
26
  cursor: pointer;
27
- box-shadow: 0 2px 10px rgba(32, 36, 43, 0.1);
27
+ box-shadow: var(--elev-1);
28
28
  }
29
29
 
30
30
  .restore-defaults:hover {
@@ -140,7 +140,7 @@
140
140
  .state-label span {
141
141
  flex: none;
142
142
  color: var(--text-secondary);
143
- font-size: 11px;
143
+ font-size: 13px;
144
144
  }
145
145
  dl {
146
146
  margin: 0;
@@ -163,7 +163,7 @@
163
163
  }
164
164
  small {
165
165
  display: block;
166
- font-size: 10px;
166
+ font-size: 12px;
167
167
  color: var(--text-secondary);
168
168
  }
169
169
  .live {
@@ -208,7 +208,7 @@
208
208
  display: block;
209
209
  margin-top: 3px;
210
210
  color: var(--text-secondary);
211
- font-size: 11px;
211
+ font-size: 13px;
212
212
  font-weight: 400;
213
213
  }
214
214
  </style>
@@ -111,7 +111,7 @@
111
111
  <style>
112
112
  .chip {
113
113
  font-family: var(--font-mono);
114
- font-size: 11px;
114
+ font-size: 13px;
115
115
  padding: 2px 8px;
116
116
  border: 1px solid var(--accent);
117
117
  color: var(--text-accent);
@@ -129,7 +129,7 @@
129
129
  }
130
130
 
131
131
  .sensitivity-copy {
132
- font-size: 11.5px;
132
+ font-size: 13.5px;
133
133
  line-height: 1.35;
134
134
  white-space: nowrap;
135
135
  }
@@ -0,0 +1,51 @@
1
+ <script lang="ts">
2
+ import { onMount } from 'svelte';
3
+ import { PANEL_COMPACT_QUERY } from '../panels.svelte.js';
4
+ import { getAppState, getController } from '../context.svelte.js';
5
+
6
+ // Renderless. Keeps the shared app state in step with the environment: the
7
+ // pointer that gates the file drop affordance, the compact-layout media
8
+ // query, and the viewport height panel and map framing both read. The
9
+ // default shell renders it; a custom shell on `PanelHost` must too.
10
+
11
+ // Gates the drag-and-drop affordance only. The upload button in the header
12
+ // is always rendered: a file picker works on every device, and the plain
13
+ // `hover`/`pointer` queries resolve false on a touchscreen laptop that also
14
+ // has a mouse, which hid the whole file flow there (#110). `any-*` match
15
+ // when at least one attached input can hover and point precisely.
16
+ const FILE_DROP_QUERY = '(any-hover: hover) and (any-pointer: fine)';
17
+
18
+ const app = getAppState();
19
+ const ctrl = getController();
20
+
21
+ onMount(() => {
22
+ const query = window.matchMedia(FILE_DROP_QUERY);
23
+ const syncFileDropUi = () => {
24
+ ctrl.showFileDropUi = query.matches;
25
+ if (!ctrl.showFileDropUi) app.dragOver = false;
26
+ };
27
+ syncFileDropUi();
28
+ query.addEventListener('change', syncFileDropUi);
29
+
30
+ const compact = window.matchMedia(PANEL_COMPACT_QUERY);
31
+ const syncCompact = () => {
32
+ app.compactLayout = compact.matches;
33
+ if (!compact.matches) app.sheetInset = 0;
34
+ };
35
+ syncCompact();
36
+ compact.addEventListener('change', syncCompact);
37
+
38
+ // Shared viewport measurements keep panel and map framing dimensions aligned.
39
+ const syncViewport = () => (app.viewportHeight = window.innerHeight);
40
+ syncViewport();
41
+ window.addEventListener('resize', syncViewport);
42
+ window.addEventListener('orientationchange', syncViewport);
43
+
44
+ return () => {
45
+ query.removeEventListener('change', syncFileDropUi);
46
+ compact.removeEventListener('change', syncCompact);
47
+ window.removeEventListener('resize', syncViewport);
48
+ window.removeEventListener('orientationchange', syncViewport);
49
+ };
50
+ });
51
+ </script>
@@ -0,0 +1,18 @@
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 ShellEnvironment: $$__sveltets_2_IsomorphicComponent<Record<string, never>, {
15
+ [evt: string]: CustomEvent<any>;
16
+ }, {}, {}, string>;
17
+ type ShellEnvironment = InstanceType<typeof ShellEnvironment>;
18
+ export default ShellEnvironment;
@@ -19,7 +19,7 @@
19
19
  display: flex;
20
20
  align-items: center;
21
21
  gap: 12px;
22
- font-size: 10px;
22
+ font-size: 12px;
23
23
  color: var(--text-secondary);
24
24
  }
25
25
 
@@ -38,7 +38,7 @@
38
38
 
39
39
  .caption {
40
40
  margin-left: auto;
41
- font-size: 9.5px;
41
+ font-size: 12px;
42
42
  }
43
43
 
44
44
  @media (max-width: 760px) {
@@ -53,7 +53,7 @@
53
53
 
54
54
  <style>
55
55
  .solve-label {
56
- font-size: 11px;
56
+ font-size: 13px;
57
57
  margin-bottom: 8px;
58
58
  }
59
59
  .solvecard {
@@ -65,7 +65,7 @@
65
65
  align-items: center;
66
66
  flex-wrap: wrap;
67
67
  gap: 10px 14px;
68
- font-size: 10px;
68
+ font-size: 12px;
69
69
  margin-top: 6px;
70
70
  }
71
71
 
@@ -75,7 +75,7 @@
75
75
 
76
76
  .fallback-reason {
77
77
  margin: 6px 0 0;
78
- font-size: 10px;
78
+ font-size: 12px;
79
79
  line-height: 1.35;
80
80
  overflow-wrap: anywhere;
81
81
  }
@@ -46,7 +46,7 @@
46
46
 
47
47
  caption {
48
48
  text-align: left;
49
- font-size: 10.5px;
49
+ font-size: 12.5px;
50
50
  margin-bottom: 4px;
51
51
  }
52
52
 
@@ -1,5 +1,8 @@
1
1
  export { default as ModelDetails } from './components/ModelDetails.svelte';
2
2
  export { default as AppFooter } from './components/AppFooter.svelte';
3
+ export { default as NotificationCenter } from './components/NotificationCenter.svelte';
4
+ export { default as PanelHost } from './components/PanelHost.svelte';
5
+ export { default as ShellEnvironment } from './components/ShellEnvironment.svelte';
3
6
  export { default as AppHeader } from './components/AppHeader.svelte';
4
7
  export { default as BusPicker } from './components/BusPicker.svelte';
5
8
  export { default as ControlPanel } from './components/ControlPanel.svelte';
@@ -1,5 +1,8 @@
1
1
  export { default as ModelDetails } from './components/ModelDetails.svelte';
2
2
  export { default as AppFooter } from './components/AppFooter.svelte';
3
+ export { default as NotificationCenter } from './components/NotificationCenter.svelte';
4
+ export { default as PanelHost } from './components/PanelHost.svelte';
5
+ export { default as ShellEnvironment } from './components/ShellEnvironment.svelte';
3
6
  export { default as AppHeader } from './components/AppHeader.svelte';
4
7
  export { default as BusPicker } from './components/BusPicker.svelte';
5
8
  export { default as ControlPanel } from './components/ControlPanel.svelte';
@@ -1,12 +1,19 @@
1
1
  import type { AppState } from './state.svelte.js';
2
2
  import type { Controller } from './controller.svelte.js';
3
3
  export interface TellegenUiConfig {
4
- apiBase: string;
4
+ /** Backend base path; `null` for a host with no backend at all. */
5
+ apiBase: string | null;
5
6
  loadDefaultCases: boolean;
6
7
  docsHref: string;
7
8
  orgHref: string;
8
9
  orgLabel: string;
9
10
  showFooter: boolean;
11
+ /** `'dark'` sets `data-theme="dark"` on the document root while the
12
+ * provider is mounted, which switches the `styles.css` tokens. */
13
+ theme: 'light' | 'dark';
14
+ /** CARTO basemap key for the default shell's map, trimmed; empty for
15
+ * none. See `TellegenMap`'s `cartoApiKey`. */
16
+ cartoApiKey: string;
10
17
  }
11
18
  export type TellegenUiOptions = Partial<TellegenUiConfig>;
12
19
  export declare const DEFAULT_TELLEGEN_UI_CONFIG: TellegenUiConfig;
@@ -1,4 +1,5 @@
1
1
  import { getContext, setContext } from 'svelte';
2
+ import { normalizeCartoApiKey } from './basemap.js';
2
3
  const APP = Symbol('tellegen.app');
3
4
  const CTRL = Symbol('tellegen.controller');
4
5
  const CONFIG = Symbol('tellegen.config');
@@ -8,16 +9,20 @@ export const DEFAULT_TELLEGEN_UI_CONFIG = {
8
9
  docsHref: 'https://eigenergy.github.io/tellegen/',
9
10
  orgHref: 'https://github.com/eigenergy',
10
11
  orgLabel: 'eigenergy group @ michigan ece',
11
- showFooter: true
12
+ showFooter: true,
13
+ theme: 'light',
14
+ cartoApiKey: ''
12
15
  };
13
16
  export function resolveTellegenUiConfig(options = {}) {
14
17
  return {
15
- apiBase: options.apiBase ?? DEFAULT_TELLEGEN_UI_CONFIG.apiBase,
18
+ apiBase: options.apiBase === undefined ? DEFAULT_TELLEGEN_UI_CONFIG.apiBase : options.apiBase,
16
19
  loadDefaultCases: options.loadDefaultCases ?? DEFAULT_TELLEGEN_UI_CONFIG.loadDefaultCases,
17
20
  docsHref: options.docsHref ?? DEFAULT_TELLEGEN_UI_CONFIG.docsHref,
18
21
  orgHref: options.orgHref ?? DEFAULT_TELLEGEN_UI_CONFIG.orgHref,
19
22
  orgLabel: options.orgLabel ?? DEFAULT_TELLEGEN_UI_CONFIG.orgLabel,
20
- showFooter: options.showFooter ?? DEFAULT_TELLEGEN_UI_CONFIG.showFooter
23
+ showFooter: options.showFooter ?? DEFAULT_TELLEGEN_UI_CONFIG.showFooter,
24
+ theme: options.theme ?? DEFAULT_TELLEGEN_UI_CONFIG.theme,
25
+ cartoApiKey: normalizeCartoApiKey(options.cartoApiKey) ?? DEFAULT_TELLEGEN_UI_CONFIG.cartoApiKey
21
26
  };
22
27
  }
23
28
  export const setAppState = (a) => setContext(APP, a);