@tellegen/svelte 0.1.2 → 0.3.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 (140) hide show
  1. package/README.md +46 -0
  2. package/dist/Sparkline.svelte.d.ts +0 -1
  3. package/dist/TellegenMap.svelte +328 -200
  4. package/dist/TellegenMap.svelte.d.ts +0 -1
  5. package/dist/TellegenProvider.svelte +4 -0
  6. package/dist/TellegenProvider.svelte.d.ts +0 -1
  7. package/dist/TellegenShell.svelte +19 -13
  8. package/dist/TellegenShell.svelte.d.ts +0 -1
  9. package/dist/TellegenViewer.svelte.d.ts +0 -1
  10. package/dist/api.d.ts +8 -2
  11. package/dist/api.js +6 -2
  12. package/dist/colors.d.ts +7 -8
  13. package/dist/colors.js +13 -13
  14. package/dist/components/AppFooter.svelte.d.ts +0 -1
  15. package/dist/components/AppHeader.svelte +27 -13
  16. package/dist/components/AppHeader.svelte.d.ts +0 -1
  17. package/dist/components/BindingLines.svelte +3 -6
  18. package/dist/components/BindingLines.svelte.d.ts +0 -1
  19. package/dist/components/BusPicker.svelte +8 -2
  20. package/dist/components/BusPicker.svelte.d.ts +0 -1
  21. package/dist/components/ControlPanel.svelte +106 -343
  22. package/dist/components/ControlPanel.svelte.d.ts +0 -1
  23. package/dist/components/DemandSlider.svelte +26 -8
  24. package/dist/components/DemandSlider.svelte.d.ts +0 -1
  25. package/dist/components/DiagramCanvas.svelte +486 -0
  26. package/dist/components/DiagramCanvas.svelte.d.ts +13 -0
  27. package/dist/components/DisplayControls.svelte +4 -2
  28. package/dist/components/DisplayControls.svelte.d.ts +0 -1
  29. package/dist/components/DropZone.svelte.d.ts +0 -1
  30. package/dist/components/FormulationSelector.svelte +8 -3
  31. package/dist/components/FormulationSelector.svelte.d.ts +0 -1
  32. package/dist/components/LocalCaseDetails.svelte +57 -44
  33. package/dist/components/LocalCaseDetails.svelte.d.ts +0 -1
  34. package/dist/components/McResults.svelte +365 -0
  35. package/dist/components/McResults.svelte.d.ts +11 -0
  36. package/dist/components/ModelDetails.svelte +230 -0
  37. package/dist/components/ModelDetails.svelte.d.ts +7 -0
  38. package/dist/components/MulticonductorDetails.svelte +227 -78
  39. package/dist/components/MulticonductorDetails.svelte.d.ts +0 -1
  40. package/dist/components/NetworkStats.svelte +20 -18
  41. package/dist/components/NetworkStats.svelte.d.ts +0 -1
  42. package/dist/components/NotificationCenter.svelte +243 -0
  43. package/dist/components/NotificationCenter.svelte.d.ts +3 -0
  44. package/dist/components/PanelCard.svelte +319 -0
  45. package/dist/components/PanelCard.svelte.d.ts +9 -0
  46. package/dist/components/PanelFrame.svelte +46 -0
  47. package/dist/components/PanelFrame.svelte.d.ts +16 -0
  48. package/dist/components/PanelHost.svelte +212 -0
  49. package/dist/components/PanelHost.svelte.d.ts +3 -0
  50. package/dist/components/PlacementCue.svelte.d.ts +0 -1
  51. package/dist/components/PowerFlowDetails.svelte +99 -0
  52. package/dist/components/PowerFlowDetails.svelte.d.ts +3 -0
  53. package/dist/components/RatingSlider.svelte +5 -9
  54. package/dist/components/RatingSlider.svelte.d.ts +0 -1
  55. package/dist/components/RestoreDefaultsButton.svelte.d.ts +0 -1
  56. package/dist/components/SavedNetworkDetails.svelte +214 -0
  57. package/dist/components/SavedNetworkDetails.svelte.d.ts +3 -0
  58. package/dist/components/SensitivityReadout.svelte +24 -16
  59. package/dist/components/SensitivityReadout.svelte.d.ts +0 -1
  60. package/dist/components/SizeLegend.svelte.d.ts +0 -1
  61. package/dist/components/SolveCard.svelte +46 -49
  62. package/dist/components/SolveCard.svelte.d.ts +0 -1
  63. package/dist/components/TopMovers.svelte +8 -3
  64. package/dist/components/TopMovers.svelte.d.ts +2 -18
  65. package/dist/components.d.ts +2 -1
  66. package/dist/components.js +2 -0
  67. package/dist/context.svelte.d.ts +0 -1
  68. package/dist/controller.svelte.d.ts +74 -41
  69. package/dist/controller.svelte.js +1095 -284
  70. package/dist/diagram-labels.d.ts +9 -0
  71. package/dist/diagram-labels.js +40 -0
  72. package/dist/display.d.ts +3 -8
  73. package/dist/display.js +31 -27
  74. package/dist/drop-classify.d.ts +5 -42
  75. package/dist/drop-classify.js +5 -78
  76. package/dist/drop-limits.d.ts +27 -0
  77. package/dist/drop-limits.js +83 -0
  78. package/dist/format.d.ts +0 -1
  79. package/dist/format.js +5 -4
  80. package/dist/index.d.ts +11 -7
  81. package/dist/index.js +8 -3
  82. package/dist/map-bounds.d.ts +5 -0
  83. package/dist/map-bounds.js +41 -0
  84. package/dist/map.d.ts +1 -2
  85. package/dist/map.js +1 -1
  86. package/dist/multiconductor.d.ts +11 -3
  87. package/dist/multiconductor.js +107 -14
  88. package/dist/notices.svelte.d.ts +29 -0
  89. package/dist/notices.svelte.js +82 -0
  90. package/dist/panels.svelte.d.ts +65 -0
  91. package/dist/panels.svelte.js +215 -0
  92. package/dist/state.svelte.d.ts +90 -24
  93. package/dist/state.svelte.js +94 -20
  94. package/dist/synthetic-layout.d.ts +0 -1
  95. package/dist/synthetic-layout.js +17 -2
  96. package/dist/transformer-icon.d.ts +0 -1
  97. package/dist/types.d.ts +0 -1
  98. package/package.json +28 -12
  99. package/dist/Sparkline.svelte.d.ts.map +0 -1
  100. package/dist/TellegenMap.svelte.d.ts.map +0 -1
  101. package/dist/TellegenProvider.svelte.d.ts.map +0 -1
  102. package/dist/TellegenShell.svelte.d.ts.map +0 -1
  103. package/dist/TellegenViewer.svelte.d.ts.map +0 -1
  104. package/dist/api.d.ts.map +0 -1
  105. package/dist/colors.d.ts.map +0 -1
  106. package/dist/components/AppFooter.svelte.d.ts.map +0 -1
  107. package/dist/components/AppHeader.svelte.d.ts.map +0 -1
  108. package/dist/components/BindingLines.svelte.d.ts.map +0 -1
  109. package/dist/components/BusPicker.svelte.d.ts.map +0 -1
  110. package/dist/components/ControlPanel.svelte.d.ts.map +0 -1
  111. package/dist/components/DemandSlider.svelte.d.ts.map +0 -1
  112. package/dist/components/DisplayControls.svelte.d.ts.map +0 -1
  113. package/dist/components/DropZone.svelte.d.ts.map +0 -1
  114. package/dist/components/FormulationSelector.svelte.d.ts.map +0 -1
  115. package/dist/components/LocalCaseDetails.svelte.d.ts.map +0 -1
  116. package/dist/components/MulticonductorDetails.svelte.d.ts.map +0 -1
  117. package/dist/components/NetworkStats.svelte.d.ts.map +0 -1
  118. package/dist/components/PlacementCue.svelte.d.ts.map +0 -1
  119. package/dist/components/RatingSlider.svelte.d.ts.map +0 -1
  120. package/dist/components/RestoreDefaultsButton.svelte.d.ts.map +0 -1
  121. package/dist/components/SensitivityReadout.svelte.d.ts.map +0 -1
  122. package/dist/components/SizeLegend.svelte.d.ts.map +0 -1
  123. package/dist/components/SolveCard.svelte.d.ts.map +0 -1
  124. package/dist/components/TopMovers.svelte.d.ts.map +0 -1
  125. package/dist/components.d.ts.map +0 -1
  126. package/dist/context.svelte.d.ts.map +0 -1
  127. package/dist/controller.svelte.d.ts.map +0 -1
  128. package/dist/display.d.ts.map +0 -1
  129. package/dist/drop-classify.d.ts.map +0 -1
  130. package/dist/format.d.ts.map +0 -1
  131. package/dist/index.d.ts.map +0 -1
  132. package/dist/map.d.ts.map +0 -1
  133. package/dist/multiconductor.d.ts.map +0 -1
  134. package/dist/state.svelte.d.ts.map +0 -1
  135. package/dist/study-package.d.ts +0 -9
  136. package/dist/study-package.d.ts.map +0 -1
  137. package/dist/study-package.js +0 -17
  138. package/dist/synthetic-layout.d.ts.map +0 -1
  139. package/dist/transformer-icon.d.ts.map +0 -1
  140. package/dist/types.d.ts.map +0 -1
@@ -0,0 +1,243 @@
1
+ <script lang="ts">
2
+ import { onDestroy, untrack } from 'svelte';
3
+ import { formatPowerIoDiagnostic } from '@tellegen/engine';
4
+ import { getAppState, getController } from '../context.svelte.js';
5
+ import { getNoticeCenter, errorNoticeTitle } from '../notices.svelte.js';
6
+ const app = getAppState();
7
+ const ctrl = getController();
8
+ const notices = getNoticeCenter();
9
+ let root = $state.raw<HTMLElement>();
10
+ let focused = $state(false);
11
+ let hovered = $state(false);
12
+ const observed = new WeakSet<object>();
13
+ $effect(() => {
14
+ const message = app.error;
15
+ const revision = app.errorRevision;
16
+ const retry = app.errorRetry;
17
+ if (message)
18
+ untrack(() =>
19
+ notices.push({
20
+ kind: 'error',
21
+ title: errorNoticeTitle(message),
22
+ details: message,
23
+ retry: () => ctrl.retryError(),
24
+ canRetry: () =>
25
+ app.errorRevision === revision && app.error === message && app.errorRetry === retry
26
+ })
27
+ );
28
+ });
29
+ $effect(() => {
30
+ const local = app.activeLocal;
31
+ const multi = app.activeMulti;
32
+ const summary = local?.summary ?? multi?.summary;
33
+ const geo = local?.geoWarnings ?? multi?.geoWarnings;
34
+ for (const [identity, title, details] of [
35
+ [
36
+ summary,
37
+ 'Case import notes',
38
+ summary
39
+ ? [
40
+ ...summary.diagnostics.map(formatPowerIoDiagnostic),
41
+ ...('warnings' in summary ? summary.warnings : [])
42
+ ].join('\n\n')
43
+ : ''
44
+ ],
45
+ [geo, 'Coordinate import notes', geo?.join('\n\n') ?? '']
46
+ ] as Array<[object | undefined, string, string]>) {
47
+ if (identity && !observed.has(identity)) {
48
+ observed.add(identity);
49
+ if (details) untrack(() => notices.push({ kind: 'warning', title, details }));
50
+ }
51
+ }
52
+ });
53
+ $effect(() => {
54
+ notices.pause(focused || hovered || notices.expanded);
55
+ });
56
+ onDestroy(() => notices.dispose());
57
+ </script>
58
+
59
+ <div class="announcement" role="status" aria-live="polite" aria-atomic="true">
60
+ {notices.announcement}
61
+ </div>
62
+ {#if notices.entries.length}
63
+ <section
64
+ class="notifications"
65
+ aria-label="Notifications"
66
+ bind:this={root}
67
+ onfocusin={() => (focused = true)}
68
+ onfocusout={(event) => {
69
+ focused = root?.contains(event.relatedTarget as Node | null) ?? false;
70
+ }}
71
+ onpointerenter={() => (hovered = true)}
72
+ onpointerleave={() => (hovered = false)}
73
+ >
74
+ {#if notices.expanded}
75
+ <div class="recent" id="recent-notifications">
76
+ <header>
77
+ <strong>Recent messages</strong><button
78
+ onclick={() => {
79
+ focused = false;
80
+ hovered = false;
81
+ notices.clear();
82
+ }}>Clear</button
83
+ >
84
+ </header>
85
+ <ol>
86
+ {#each notices.entries as entry (entry.id)}
87
+ <li>
88
+ <details>
89
+ <summary
90
+ >{entry.title}{#if entry.count > 1}<span class="count">{entry.count}</span
91
+ >{/if}</summary
92
+ >
93
+ <pre data-testid="notification-details">{entry.details}</pre>
94
+ </details>
95
+ {#if entry.retry && entry.canRetry?.()}<button onclick={entry.retry}>Retry</button
96
+ >{/if}
97
+ </li>
98
+ {/each}
99
+ </ol>
100
+ </div>
101
+ {/if}
102
+ <div
103
+ class="notice"
104
+ data-testid="notification-toast"
105
+ data-kind={notices.active?.kind}
106
+ class:quiet={!notices.active}
107
+ >
108
+ {#if notices.active}
109
+ <span class="message">{notices.active.title}</span>
110
+ {#if notices.active.count > 1}<span
111
+ class="count"
112
+ aria-label={`${notices.active.count} occurrences`}>{notices.active.count}</span
113
+ >{/if}
114
+ {#if notices.active.retry && notices.active.canRetry?.()}<button
115
+ onclick={notices.active.retry}>Retry</button
116
+ >{/if}
117
+ {/if}
118
+ <button
119
+ class="history"
120
+ aria-expanded={notices.expanded}
121
+ aria-controls="recent-notifications"
122
+ onclick={() => (notices.expanded = !notices.expanded)}
123
+ >{notices.active ? 'Details' : 'Messages'}{#if !notices.active}<span class="count"
124
+ >{notices.entries.length}</span
125
+ >{/if}</button
126
+ >
127
+ {#if notices.active}<button
128
+ aria-label="Dismiss notification"
129
+ onclick={() => notices.dismiss()}>Close</button
130
+ >{/if}
131
+ </div>
132
+ </section>
133
+ {/if}
134
+
135
+ <style>
136
+ .announcement {
137
+ position: absolute;
138
+ width: 1px;
139
+ height: 1px;
140
+ overflow: hidden;
141
+ clip-path: inset(50%);
142
+ }
143
+ .notifications {
144
+ position: fixed;
145
+ z-index: 65;
146
+ bottom: 40px;
147
+ left: 50%;
148
+ transform: translateX(-50%);
149
+ width: min(540px, calc(100vw - 200px));
150
+ font: 12px/1.4 var(--font-display);
151
+ pointer-events: none;
152
+ }
153
+ .notice,
154
+ .recent {
155
+ pointer-events: auto;
156
+ background: var(--panel);
157
+ border: 1px solid var(--line);
158
+ border-radius: 5px;
159
+ box-shadow: var(--elev-2);
160
+ }
161
+ .notice {
162
+ display: flex;
163
+ align-items: center;
164
+ gap: 8px;
165
+ min-height: 38px;
166
+ padding: 3px 8px 3px 12px;
167
+ box-sizing: border-box;
168
+ }
169
+ .message {
170
+ min-width: 0;
171
+ flex: 1;
172
+ overflow: hidden;
173
+ text-overflow: ellipsis;
174
+ white-space: nowrap;
175
+ }
176
+ .quiet {
177
+ width: fit-content;
178
+ margin: auto;
179
+ padding-left: 5px;
180
+ background: var(--paper);
181
+ }
182
+ button {
183
+ font: inherit;
184
+ color: var(--ink);
185
+ border: 0;
186
+ background: transparent;
187
+ padding: 6px;
188
+ border-radius: 3px;
189
+ cursor: pointer;
190
+ flex-shrink: 0;
191
+ }
192
+ button:hover {
193
+ background: var(--accent-soft);
194
+ }
195
+ button:focus-visible,
196
+ summary:focus-visible {
197
+ outline: 2px solid var(--focus-ring);
198
+ outline-offset: 2px;
199
+ }
200
+ .count {
201
+ margin-left: 5px;
202
+ font: 10px var(--font-mono);
203
+ color: var(--text-secondary);
204
+ }
205
+ .recent {
206
+ margin-bottom: 6px;
207
+ max-height: min(320px, 40dvh);
208
+ overflow-y: auto;
209
+ }
210
+ header {
211
+ display: flex;
212
+ align-items: center;
213
+ justify-content: space-between;
214
+ padding: 8px 12px;
215
+ }
216
+ ol {
217
+ list-style: none;
218
+ padding: 0;
219
+ margin: 0;
220
+ }
221
+ li {
222
+ padding: 10px 12px;
223
+ border-top: 1px solid var(--line);
224
+ }
225
+ summary {
226
+ cursor: pointer;
227
+ }
228
+ pre {
229
+ font: 11px/1.5 var(--font-mono);
230
+ white-space: pre-wrap;
231
+ overflow-wrap: anywhere;
232
+ margin: 10px 0 0;
233
+ }
234
+ @media (max-width: 960px) {
235
+ .notifications {
236
+ width: calc(100vw - 24px);
237
+ bottom: 60px;
238
+ }
239
+ button {
240
+ min-height: 36px;
241
+ }
242
+ }
243
+ </style>
@@ -0,0 +1,3 @@
1
+ declare const NotificationCenter: import("svelte").Component<Record<string, never>, {}, "">;
2
+ type NotificationCenter = ReturnType<typeof NotificationCenter>;
3
+ export default NotificationCenter;
@@ -0,0 +1,319 @@
1
+ <script lang="ts">
2
+ import { tick } from 'svelte';
3
+ import { getPanelLayout, type PanelRegistration, type PanelSide } from '../panels.svelte.js';
4
+ let {
5
+ panel,
6
+ floating = false,
7
+ mobile = false
8
+ }: {
9
+ panel: PanelRegistration;
10
+ floating?: boolean;
11
+ mobile?: boolean;
12
+ } = $props();
13
+ const layout = getPanelLayout();
14
+ let menu = $state(false);
15
+ let card: HTMLElement;
16
+ const bounds = $derived(layout.bounds(panel));
17
+ const preferred = $derived(!floating && !mobile && layout.preferred(panel));
18
+ const dockReserve = $derived.by(() => {
19
+ const side = layout.position(panel);
20
+ return side === 'floating'
21
+ ? 0
22
+ : Math.max(0, layout.ordered(side).filter((candidate) => candidate.open).length - 1) * 142;
23
+ });
24
+ function start(event: PointerEvent, kind: 'move' | 'resize') {
25
+ if (mobile || !event.isPrimary || event.button !== 0) return;
26
+ const rect = card.getBoundingClientRect();
27
+ layout.beginGesture(panel, kind, event.clientX, event.clientY, {
28
+ x: rect.x,
29
+ y: rect.y,
30
+ width: rect.width,
31
+ height: rect.height
32
+ });
33
+ (event.currentTarget as HTMLElement).setPointerCapture(event.pointerId);
34
+ event.preventDefault();
35
+ }
36
+ async function key(event: KeyboardEvent) {
37
+ if (mobile) return;
38
+ const panelId = panel.id;
39
+ const step = event.shiftKey ? 40 : 10;
40
+ const directions: Record<string, [number, number]> = {
41
+ ArrowLeft: [-step, 0],
42
+ ArrowRight: [step, 0],
43
+ ArrowUp: [0, -step],
44
+ ArrowDown: [0, step]
45
+ };
46
+ if (directions[event.key]) {
47
+ event.preventDefault();
48
+ const [x, y] = directions[event.key];
49
+ const rect = card.getBoundingClientRect();
50
+ layout.float(panel, {
51
+ x: rect.x + x,
52
+ y: rect.y + y,
53
+ width: rect.width,
54
+ height: rect.height
55
+ });
56
+ layout.save();
57
+ await tick();
58
+ document.querySelector<HTMLElement>(`[data-panel="${CSS.escape(panelId)}"] .title`)?.focus();
59
+ } else if (event.key === 'Escape') {
60
+ menu = false;
61
+ }
62
+ }
63
+ function resizeKey(event: KeyboardEvent) {
64
+ const step = event.shiftKey ? 40 : 10;
65
+ if (!['ArrowLeft', 'ArrowRight', 'ArrowUp', 'ArrowDown'].includes(event.key)) return;
66
+ event.preventDefault();
67
+ layout.float(panel, {
68
+ ...bounds,
69
+ width:
70
+ bounds.width + (event.key === 'ArrowRight' ? step : event.key === 'ArrowLeft' ? -step : 0),
71
+ height:
72
+ bounds.height + (event.key === 'ArrowDown' ? step : event.key === 'ArrowUp' ? -step : 0)
73
+ });
74
+ layout.save();
75
+ }
76
+ async function position(side?: PanelSide) {
77
+ const panelId = panel.id;
78
+ menu = false;
79
+ if (side) layout.dock(panel, side);
80
+ else {
81
+ layout.float(panel);
82
+ layout.save();
83
+ }
84
+ await tick();
85
+ document.querySelector<HTMLElement>(`[data-panel="${CSS.escape(panelId)}"] .title`)?.focus();
86
+ }
87
+ async function close() {
88
+ const title = panel.title;
89
+ layout.close(panel);
90
+ await tick();
91
+ document
92
+ .querySelector<HTMLElement>(`.panel-toolbar button[aria-label="${CSS.escape(title)}"]`)
93
+ ?.focus();
94
+ }
95
+ </script>
96
+
97
+ <svelte:window
98
+ onkeydown={(event) => {
99
+ if (event.key === 'Escape') menu = false;
100
+ }}
101
+ />
102
+ <aside
103
+ bind:this={card}
104
+ class="panel-frame"
105
+ class:floating
106
+ class:mobile
107
+ class:preferred
108
+ data-panel={panel.id}
109
+ aria-label={panel.title}
110
+ style:left={floating ? `${bounds.x}px` : undefined}
111
+ style:top={floating ? `${bounds.y}px` : undefined}
112
+ style:width={floating ? `${bounds.width}px` : undefined}
113
+ style:height={floating ? `${bounds.height}px` : undefined}
114
+ style:max-height={preferred ? `calc(100% - ${dockReserve}px)` : undefined}
115
+ >
116
+ <header>
117
+ {#if mobile}
118
+ <h2 class="title">{panel.title}</h2>
119
+ {:else}
120
+ <button
121
+ class="title"
122
+ aria-label={`Move ${panel.title} panel`}
123
+ title="Click to expand. Drag or use arrow keys to move. Open panel options to dock."
124
+ onpointerdown={(event) => start(event, 'move')}
125
+ onkeydown={key}
126
+ onclick={() => layout.activate(panel)}>{panel.title}</button
127
+ >
128
+ {/if}
129
+ <div class="header-actions">{@render panel.headerActions?.()}</div>
130
+ {#if !mobile}<div class="menu-anchor">
131
+ <button
132
+ class="icon"
133
+ aria-label={`${panel.title} panel options`}
134
+ aria-expanded={menu}
135
+ onclick={() => (menu = !menu)}
136
+ title="Panel options"
137
+ >
138
+ <svg width="16" height="16" viewBox="0 0 16 16" aria-hidden="true"
139
+ ><circle cx="3" cy="8" r="1" /><circle cx="8" cy="8" r="1" /><circle
140
+ cx="13"
141
+ cy="8"
142
+ r="1"
143
+ /></svg
144
+ >
145
+ </button>
146
+ {#if menu}<div class="panel-menu">
147
+ <button onclick={() => position('left')}>Dock left</button>
148
+ <button onclick={() => position('right')}>Dock right</button>
149
+ <button onclick={() => position()}>Detach</button>
150
+ </div>{/if}
151
+ </div>{/if}
152
+ <button
153
+ class="icon"
154
+ aria-label={`Close ${panel.title.toLowerCase()} panel`}
155
+ title="Close panel"
156
+ onclick={close}
157
+ >
158
+ <svg width="14" height="14" viewBox="0 0 16 16" aria-hidden="true"
159
+ ><path d="m4 4 8 8m0-8-8 8" stroke="currentColor" stroke-width="1.5" fill="none" /></svg
160
+ >
161
+ </button>
162
+ </header>
163
+ <div class="panel-content">{@render panel.children()}</div>
164
+ {#if floating}<button
165
+ class="resize"
166
+ aria-label={`Resize ${panel.title} panel`}
167
+ title="Drag to resize, or use arrow keys"
168
+ onpointerdown={(event) => start(event, 'resize')}
169
+ onkeydown={resizeKey}
170
+ >
171
+ <svg width="12" height="12" viewBox="0 0 12 12" aria-hidden="true"
172
+ ><path d="m3 10 7-7m-3 7 3-3" stroke="currentColor" /></svg
173
+ >
174
+ </button>{/if}
175
+ </aside>
176
+
177
+ <style>
178
+ .panel-frame {
179
+ display: flex;
180
+ flex-direction: column;
181
+ min-height: 130px;
182
+ max-height: 100%;
183
+ flex: 0 1 auto;
184
+ pointer-events: auto;
185
+ background: var(--paper);
186
+ color: var(--ink);
187
+ border: 1px solid var(--line);
188
+ border-radius: 5px;
189
+ box-shadow: var(--elev-1);
190
+ font: 13px/1.5 var(--font-display);
191
+ position: relative;
192
+ }
193
+ header {
194
+ display: flex;
195
+ align-items: center;
196
+ gap: 5px;
197
+ padding: 8px 10px 8px 14px;
198
+ flex: none;
199
+ border-bottom: 1px solid var(--line);
200
+ }
201
+ button {
202
+ color: inherit;
203
+ font: inherit;
204
+ cursor: pointer;
205
+ border: 0;
206
+ background: transparent;
207
+ border-radius: 3px;
208
+ }
209
+ button:hover {
210
+ background: var(--accent-soft);
211
+ }
212
+ button:focus-visible {
213
+ outline: 2px solid var(--focus-ring);
214
+ outline-offset: 2px;
215
+ }
216
+ .title {
217
+ flex: 1;
218
+ min-width: 0;
219
+ text-align: left;
220
+ font-size: inherit;
221
+ font-weight: 600;
222
+ margin: 0;
223
+ padding: 3px 0;
224
+ cursor: grab;
225
+ touch-action: none;
226
+ }
227
+ .title:active {
228
+ cursor: grabbing;
229
+ }
230
+ .icon {
231
+ display: grid;
232
+ place-items: center;
233
+ width: 26px;
234
+ height: 28px;
235
+ padding: 0;
236
+ flex: none;
237
+ }
238
+ .icon svg {
239
+ fill: currentColor;
240
+ }
241
+ .header-actions {
242
+ display: flex;
243
+ align-items: center;
244
+ gap: 8px;
245
+ }
246
+ .header-actions :global(button) {
247
+ font: 11px/1.3 var(--font-display);
248
+ padding: 4px;
249
+ border: 0;
250
+ background: none;
251
+ color: var(--text-secondary);
252
+ }
253
+ .panel-content {
254
+ min-height: 0;
255
+ overflow: auto;
256
+ overscroll-behavior: contain;
257
+ display: flex;
258
+ flex-direction: column;
259
+ border-radius: 0 0 5px 5px;
260
+ }
261
+ .menu-anchor {
262
+ position: relative;
263
+ }
264
+ .panel-menu {
265
+ position: absolute;
266
+ z-index: 3;
267
+ top: 100%;
268
+ right: 0;
269
+ min-width: 130px;
270
+ padding: 5px;
271
+ background: var(--paper);
272
+ border: 1px solid var(--line);
273
+ border-radius: 4px;
274
+ box-shadow: var(--elev-2);
275
+ }
276
+ .panel-menu button {
277
+ display: block;
278
+ padding: 7px 10px;
279
+ width: 100%;
280
+ text-align: left;
281
+ white-space: nowrap;
282
+ font-size: 12px;
283
+ }
284
+ .preferred {
285
+ flex-shrink: 0;
286
+ }
287
+ .floating {
288
+ position: fixed;
289
+ min-height: 0;
290
+ z-index: 40;
291
+ max-height: none;
292
+ box-shadow: var(--elev-2);
293
+ }
294
+ .floating .panel-content {
295
+ flex: 1;
296
+ padding-bottom: 12px;
297
+ }
298
+ .resize {
299
+ position: absolute;
300
+ bottom: 0;
301
+ right: 0;
302
+ width: 20px;
303
+ height: 20px;
304
+ cursor: nwse-resize;
305
+ touch-action: none;
306
+ color: var(--text-secondary);
307
+ }
308
+ .mobile {
309
+ min-height: 0;
310
+ height: 100%;
311
+ border-radius: 10px 10px 0 0;
312
+ }
313
+ .mobile .panel-content {
314
+ flex: 1;
315
+ }
316
+ .mobile .title {
317
+ cursor: default;
318
+ }
319
+ </style>
@@ -0,0 +1,9 @@
1
+ import { type PanelRegistration } from '../panels.svelte.js';
2
+ type $$ComponentProps = {
3
+ panel: PanelRegistration;
4
+ floating?: boolean;
5
+ mobile?: boolean;
6
+ };
7
+ declare const PanelCard: import("svelte").Component<$$ComponentProps, {}, "">;
8
+ type PanelCard = ReturnType<typeof PanelCard>;
9
+ export default PanelCard;
@@ -0,0 +1,46 @@
1
+ <script lang="ts">
2
+ import { onMount, untrack, type Snippet } from 'svelte';
3
+ import { getPanelLayout, type PanelSide } from '../panels.svelte.js';
4
+ let {
5
+ id,
6
+ title,
7
+ side,
8
+ order = 0,
9
+ width = 340,
10
+ open = $bindable(false),
11
+ children,
12
+ headerActions,
13
+ onopen
14
+ }: {
15
+ id: string;
16
+ title: string;
17
+ side: PanelSide;
18
+ order?: number;
19
+ width?: number;
20
+ open?: boolean;
21
+ children: Snippet;
22
+ headerActions?: Snippet;
23
+ onopen?: () => void;
24
+ } = $props();
25
+ const layout = getPanelLayout();
26
+ onMount(() =>
27
+ layout.register({
28
+ id,
29
+ title,
30
+ side,
31
+ order,
32
+ width,
33
+ open,
34
+ children,
35
+ headerActions,
36
+ onopen,
37
+ setOpen: (value) => {
38
+ open = value;
39
+ }
40
+ })
41
+ );
42
+ $effect(() => {
43
+ const values = { id, open, title, width };
44
+ untrack(() => layout.update(values.id, values.open, values.title, values.width));
45
+ });
46
+ </script>
@@ -0,0 +1,16 @@
1
+ import { type Snippet } from 'svelte';
2
+ import { type PanelSide } from '../panels.svelte.js';
3
+ type $$ComponentProps = {
4
+ id: string;
5
+ title: string;
6
+ side: PanelSide;
7
+ order?: number;
8
+ width?: number;
9
+ open?: boolean;
10
+ children: Snippet;
11
+ headerActions?: Snippet;
12
+ onopen?: () => void;
13
+ };
14
+ declare const PanelFrame: import("svelte").Component<$$ComponentProps, {}, "open">;
15
+ type PanelFrame = ReturnType<typeof PanelFrame>;
16
+ export default PanelFrame;