@smartnet360/svelte-components 0.0.43 → 0.0.45
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/dist/apps/site-check/SiteCheck.svelte +127 -27
- package/dist/apps/site-check/SiteCheck.svelte.d.ts +2 -0
- package/dist/core/Charts/ChartCard.svelte +15 -4
- package/dist/core/Charts/ChartCard.svelte.d.ts +1 -0
- package/dist/core/Charts/ChartComponent.svelte +18 -57
- package/dist/core/Charts/GlobalControls.svelte +391 -112
- package/dist/core/Charts/GlobalControls.svelte.d.ts +2 -0
- package/dist/core/Charts/charts.model.d.ts +3 -0
- package/package.json +1 -1
|
@@ -20,9 +20,33 @@
|
|
|
20
20
|
cellStyling?: CellStylingConfig; // Optional cell styling config (defaults to defaultCellStyling)
|
|
21
21
|
initialGrouping?: TreeGroupingConfig; // Optional initial tree grouping (defaults to Site → Azimuth → Cell)
|
|
22
22
|
showGroupingSelector?: boolean; // Show/hide the grouping dropdown (default: true)
|
|
23
|
+
onSearch?: (searchTerm: string) => void; // Optional: Search callback (if provided, shows search box)
|
|
24
|
+
searchPlaceholder?: string; // Optional: Search box placeholder text (default: "Search...")
|
|
23
25
|
}
|
|
24
26
|
|
|
25
|
-
let { rawData, multiCellLayout, singleLteLayout, singleNrLayout, baseMetrics, mode = "scrollspy", markers = [], cellStyling = defaultCellStyling, initialGrouping = defaultTreeGrouping, showGroupingSelector = true }: Props = $props();
|
|
27
|
+
let { rawData, multiCellLayout, singleLteLayout, singleNrLayout, baseMetrics, mode = "scrollspy", markers = [], cellStyling = defaultCellStyling, initialGrouping = defaultTreeGrouping, showGroupingSelector = true, onSearch, searchPlaceholder = "Search..." }: Props = $props();
|
|
28
|
+
|
|
29
|
+
// Search state
|
|
30
|
+
let searchTerm = $state('');
|
|
31
|
+
|
|
32
|
+
// Controls visibility state (starts expanded)
|
|
33
|
+
let controlsExpanded = $state(true);
|
|
34
|
+
|
|
35
|
+
// Handlers
|
|
36
|
+
function handleSearch() {
|
|
37
|
+
if (onSearch) {
|
|
38
|
+
onSearch(searchTerm);
|
|
39
|
+
log('🔍 Search triggered:', searchTerm);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function handleClearSearch() {
|
|
44
|
+
searchTerm = '';
|
|
45
|
+
if (onSearch) {
|
|
46
|
+
onSearch('');
|
|
47
|
+
log('🧹 Search cleared');
|
|
48
|
+
}
|
|
49
|
+
}
|
|
26
50
|
|
|
27
51
|
// Check feature health
|
|
28
52
|
let isHealthy = $state(checkHealth('sitecheck'));
|
|
@@ -63,8 +87,7 @@
|
|
|
63
87
|
firstNode: treeNodes[0],
|
|
64
88
|
grouping: treeGrouping
|
|
65
89
|
});
|
|
66
|
-
if(isHealthy
|
|
67
|
-
console.log('Configuration Required');
|
|
90
|
+
if(!isHealthy) {
|
|
68
91
|
return;
|
|
69
92
|
}
|
|
70
93
|
// Create tree store
|
|
@@ -191,30 +214,90 @@
|
|
|
191
214
|
<div class="row flex-grow-1" style="min-height: 0;">
|
|
192
215
|
<!-- Left: Tree View -->
|
|
193
216
|
<div class="col-lg-3 col-md-4 border-end bg-white d-flex flex-column" style="min-height: 0; height: 100%;">
|
|
194
|
-
<!--
|
|
195
|
-
{#if showGroupingSelector}
|
|
196
|
-
<
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
>
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
217
|
+
<!-- Collapsible Controls Toggle -->
|
|
218
|
+
{#if onSearch || showGroupingSelector}
|
|
219
|
+
<button
|
|
220
|
+
class="controls-toggle w-100 text-start p-2 bg-light border-bottom d-flex align-items-center flex-shrink-0"
|
|
221
|
+
onclick={() => controlsExpanded = !controlsExpanded}
|
|
222
|
+
aria-expanded={controlsExpanded}
|
|
223
|
+
aria-label="Toggle controls"
|
|
224
|
+
>
|
|
225
|
+
<i class="bi bi-sliders me-2"></i>
|
|
226
|
+
<span class="fw-semibold small">Controls</span>
|
|
227
|
+
<i class="bi ms-auto"
|
|
228
|
+
class:bi-chevron-down={controlsExpanded}
|
|
229
|
+
class:bi-chevron-right={!controlsExpanded}>
|
|
230
|
+
</i>
|
|
231
|
+
</button>
|
|
232
|
+
{/if}
|
|
233
|
+
|
|
234
|
+
<!-- Collapsible Controls Content -->
|
|
235
|
+
{#if controlsExpanded}
|
|
236
|
+
<!-- Search Box -->
|
|
237
|
+
{#if onSearch}
|
|
238
|
+
<div class="p-3 border-bottom flex-shrink-0">
|
|
239
|
+
<label for="searchInput" class="form-label small fw-semibold mb-2">
|
|
240
|
+
Search
|
|
241
|
+
</label>
|
|
242
|
+
<div class="input-group input-group-sm">
|
|
243
|
+
<input
|
|
244
|
+
type="text"
|
|
245
|
+
id="searchInput"
|
|
246
|
+
class="form-control"
|
|
247
|
+
placeholder={searchPlaceholder}
|
|
248
|
+
bind:value={searchTerm}
|
|
249
|
+
onkeydown={(e) => {
|
|
250
|
+
if (e.key === 'Enter') {
|
|
251
|
+
handleSearch();
|
|
252
|
+
}
|
|
253
|
+
}}
|
|
254
|
+
/>
|
|
255
|
+
{#if searchTerm}
|
|
256
|
+
<button
|
|
257
|
+
class="btn btn-outline-secondary"
|
|
258
|
+
type="button"
|
|
259
|
+
onclick={handleClearSearch}
|
|
260
|
+
title="Clear search"
|
|
261
|
+
aria-label="Clear search"
|
|
262
|
+
>
|
|
263
|
+
<i class="bi bi-x-lg"></i>
|
|
264
|
+
</button>
|
|
265
|
+
{/if}
|
|
266
|
+
<button
|
|
267
|
+
class="btn btn-primary"
|
|
268
|
+
type="button"
|
|
269
|
+
onclick={handleSearch}
|
|
270
|
+
title="Search"
|
|
271
|
+
aria-label="Search"
|
|
272
|
+
>
|
|
273
|
+
<i class="bi bi-search"></i>
|
|
274
|
+
</button>
|
|
275
|
+
</div>
|
|
276
|
+
</div>
|
|
277
|
+
{/if}
|
|
278
|
+
|
|
279
|
+
<!-- Grouping Selector -->
|
|
280
|
+
{#if showGroupingSelector}
|
|
281
|
+
<div class="p-3 border-bottom flex-shrink-0">
|
|
282
|
+
<label for="groupingSelect" class="form-label small fw-semibold mb-2">
|
|
283
|
+
Tree Grouping
|
|
284
|
+
</label>
|
|
285
|
+
<select
|
|
286
|
+
id="groupingSelect"
|
|
287
|
+
class="form-select form-select-sm"
|
|
288
|
+
onchange={(e) => {
|
|
289
|
+
const index = parseInt(e.currentTarget.value);
|
|
290
|
+
treeGrouping = groupingPresets[index].value;
|
|
291
|
+
}}
|
|
292
|
+
>
|
|
293
|
+
{#each groupingPresets as preset, i}
|
|
294
|
+
<option value={i} selected={JSON.stringify(preset.value) === JSON.stringify(treeGrouping)}>
|
|
295
|
+
{preset.label}
|
|
296
|
+
</option>
|
|
297
|
+
{/each}
|
|
298
|
+
</select>
|
|
299
|
+
</div>
|
|
300
|
+
{/if}
|
|
218
301
|
{/if}
|
|
219
302
|
|
|
220
303
|
<!-- Tree View -->
|
|
@@ -247,3 +330,20 @@
|
|
|
247
330
|
</div>
|
|
248
331
|
</div>
|
|
249
332
|
|
|
333
|
+
<style>
|
|
334
|
+
.controls-toggle {
|
|
335
|
+
cursor: pointer;
|
|
336
|
+
border: none;
|
|
337
|
+
transition: background-color 0.2s;
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
.controls-toggle:hover {
|
|
341
|
+
background-color: #e9ecef !important;
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
.controls-toggle:focus {
|
|
345
|
+
outline: 2px solid #0d6efd;
|
|
346
|
+
outline-offset: -2px;
|
|
347
|
+
}
|
|
348
|
+
</style>
|
|
349
|
+
|
|
@@ -12,6 +12,8 @@ interface Props {
|
|
|
12
12
|
cellStyling?: CellStylingConfig;
|
|
13
13
|
initialGrouping?: TreeGroupingConfig;
|
|
14
14
|
showGroupingSelector?: boolean;
|
|
15
|
+
onSearch?: (searchTerm: string) => void;
|
|
16
|
+
searchPlaceholder?: string;
|
|
15
17
|
}
|
|
16
18
|
declare const SiteCheck: import("svelte").Component<Props, {}, "">;
|
|
17
19
|
type SiteCheck = ReturnType<typeof SiteCheck>;
|
|
@@ -24,6 +24,7 @@
|
|
|
24
24
|
runtimeShowOriginal?: boolean; // Runtime control for showing original lines
|
|
25
25
|
runtimeShowMarkers?: boolean; // Runtime control for showing markers (default: true)
|
|
26
26
|
runtimeShowLegend?: boolean; // Runtime control for showing legend (default: true)
|
|
27
|
+
runtimeHoverMode?: HoverMode; // Runtime override for hover mode from global controls
|
|
27
28
|
onchartcontextmenu?: (detail: {
|
|
28
29
|
chart: ChartModel;
|
|
29
30
|
sectionId?: string;
|
|
@@ -32,7 +33,7 @@
|
|
|
32
33
|
}) => void;
|
|
33
34
|
}
|
|
34
35
|
|
|
35
|
-
let { chart, processedData, markers, plotlyLayout, enableAdaptation = true, sectionId, sectionMovingAverage, layoutMovingAverage, layoutHoverMode, layoutColoredHover = true, runtimeMAOverride, runtimeShowOriginal, runtimeShowMarkers = true, runtimeShowLegend = true, onchartcontextmenu }: Props = $props();
|
|
36
|
+
let { chart, processedData, markers, plotlyLayout, enableAdaptation = true, sectionId, sectionMovingAverage, layoutMovingAverage, layoutHoverMode, layoutColoredHover = true, runtimeMAOverride, runtimeShowOriginal, runtimeShowMarkers = true, runtimeShowLegend = true, runtimeHoverMode, onchartcontextmenu }: Props = $props();
|
|
36
37
|
|
|
37
38
|
// Chart container div and state
|
|
38
39
|
let chartDiv: HTMLElement;
|
|
@@ -174,8 +175,11 @@
|
|
|
174
175
|
colorIndex++;
|
|
175
176
|
});
|
|
176
177
|
|
|
178
|
+
// Determine effective hover mode: runtime override takes precedence over layout config
|
|
179
|
+
const effectiveHoverMode = runtimeHoverMode !== undefined ? runtimeHoverMode : layoutHoverMode;
|
|
180
|
+
|
|
177
181
|
// Create default modern layout using the centralized function
|
|
178
|
-
const defaultLayout: any = createDefaultPlotlyLayout(chart.title,
|
|
182
|
+
const defaultLayout: any = createDefaultPlotlyLayout(chart.title, effectiveHoverMode, layoutColoredHover);
|
|
179
183
|
|
|
180
184
|
// Override specific properties for this chart
|
|
181
185
|
defaultLayout.yaxis.title = {
|
|
@@ -235,7 +239,11 @@
|
|
|
235
239
|
// Use Plotly.react() for updates (preserves zoom/pan) or newPlot for initial render
|
|
236
240
|
if (chartInitialized) {
|
|
237
241
|
// Update existing chart - much faster, preserves user interactions
|
|
238
|
-
log('🔄 Updating chart with Plotly.react', {
|
|
242
|
+
log('🔄 Updating chart with Plotly.react', {
|
|
243
|
+
chartTitle: chart.title,
|
|
244
|
+
hoverMode: effectiveHoverMode,
|
|
245
|
+
layoutHoverMode: finalLayout.hovermode
|
|
246
|
+
});
|
|
239
247
|
Plotly.react(chartDiv, traces, finalLayout, config);
|
|
240
248
|
} else {
|
|
241
249
|
// Initial chart creation
|
|
@@ -243,7 +251,9 @@
|
|
|
243
251
|
chartTitle: chart.title,
|
|
244
252
|
traces: traces.length,
|
|
245
253
|
leftKPIs: chart.yLeft.length,
|
|
246
|
-
rightKPIs: chart.yRight.length
|
|
254
|
+
rightKPIs: chart.yRight.length,
|
|
255
|
+
hoverMode: effectiveHoverMode,
|
|
256
|
+
layoutHoverMode: finalLayout.hovermode
|
|
247
257
|
});
|
|
248
258
|
Plotly.newPlot(chartDiv, traces, finalLayout, config);
|
|
249
259
|
chartInitialized = true;
|
|
@@ -340,6 +350,7 @@
|
|
|
340
350
|
const currentShowOriginal = runtimeShowOriginal;
|
|
341
351
|
const currentShowMarkers = runtimeShowMarkers;
|
|
342
352
|
const currentShowLegend = runtimeShowLegend;
|
|
353
|
+
const currentHoverMode = runtimeHoverMode;
|
|
343
354
|
|
|
344
355
|
// Only re-render if chartDiv is already initialized
|
|
345
356
|
if (chartDiv && chartDiv.children.length > 0) {
|
|
@@ -85,6 +85,9 @@
|
|
|
85
85
|
},
|
|
86
86
|
legend: {
|
|
87
87
|
enabled: true // Default to showing legend
|
|
88
|
+
},
|
|
89
|
+
hoverMode: {
|
|
90
|
+
mode: layout.hoverMode ?? 'x' // Default to 'x' if not specified
|
|
88
91
|
}
|
|
89
92
|
});
|
|
90
93
|
|
|
@@ -94,7 +97,8 @@
|
|
|
94
97
|
movingAverageEnabled: updatedControls.movingAverage?.enabled,
|
|
95
98
|
windowOverride: updatedControls.movingAverage?.windowOverride,
|
|
96
99
|
markersEnabled: updatedControls.markers?.enabled,
|
|
97
|
-
legendEnabled: updatedControls.legend?.enabled
|
|
100
|
+
legendEnabled: updatedControls.legend?.enabled,
|
|
101
|
+
hoverMode: updatedControls.hoverMode?.mode
|
|
98
102
|
});
|
|
99
103
|
globalControls = updatedControls;
|
|
100
104
|
}
|
|
@@ -333,6 +337,7 @@
|
|
|
333
337
|
runtimeShowOriginal={globalControls.movingAverage?.showOriginal}
|
|
334
338
|
runtimeShowMarkers={globalControls.markers?.enabled}
|
|
335
339
|
runtimeShowLegend={globalControls.legend?.enabled}
|
|
340
|
+
runtimeHoverMode={globalControls.hoverMode?.mode}
|
|
336
341
|
onchartcontextmenu={(detail) => handleChartContextMenu(detail, section)}
|
|
337
342
|
/>
|
|
338
343
|
</div>
|
|
@@ -341,16 +346,21 @@
|
|
|
341
346
|
{/snippet}
|
|
342
347
|
|
|
343
348
|
<div class="chart-component" bind:this={componentElement}>
|
|
344
|
-
<!-- Global Controls
|
|
345
|
-
{#if showGlobalControls
|
|
346
|
-
<GlobalControls
|
|
349
|
+
<!-- Floating Global Controls (renders as fixed position overlay) -->
|
|
350
|
+
{#if showGlobalControls}
|
|
351
|
+
<GlobalControls
|
|
352
|
+
controls={globalControls}
|
|
353
|
+
onUpdate={handleControlsUpdate}
|
|
354
|
+
isExpanded={showControlsPanel}
|
|
355
|
+
onToggle={() => showControlsPanel = !showControlsPanel}
|
|
356
|
+
/>
|
|
347
357
|
{/if}
|
|
348
358
|
|
|
349
359
|
<!-- Always render the main content (tabs or scrollspy) -->
|
|
350
360
|
{#if mode === 'tabs'}
|
|
351
361
|
<!-- Tab Mode with Navigation -->
|
|
352
362
|
<div class="tabs-container">
|
|
353
|
-
<!-- Tab Navigation
|
|
363
|
+
<!-- Tab Navigation -->
|
|
354
364
|
<div class="nav-tabs-wrapper">
|
|
355
365
|
<ul class="nav nav-tabs" role="tablist">
|
|
356
366
|
{#each layout.sections as section, index}
|
|
@@ -369,21 +379,6 @@
|
|
|
369
379
|
</li>
|
|
370
380
|
{/each}
|
|
371
381
|
</ul>
|
|
372
|
-
|
|
373
|
-
<!-- Controls Toggle Button -->
|
|
374
|
-
{#if showGlobalControls}
|
|
375
|
-
<button
|
|
376
|
-
class="btn btn-sm btn-outline-secondary controls-toggle"
|
|
377
|
-
onclick={() => showControlsPanel = !showControlsPanel}
|
|
378
|
-
title={showControlsPanel ? "Hide Controls" : "Show Controls"}
|
|
379
|
-
type="button"
|
|
380
|
-
>
|
|
381
|
-
<svg width="16" height="16" fill="currentColor" viewBox="0 0 16 16">
|
|
382
|
-
<path d="M9.405 1.05c-.413-1.4-2.397-1.4-2.81 0l-.1.34a1.464 1.464 0 0 1-2.105.872l-.31-.17c-1.283-.698-2.686.705-1.987 1.987l.169.311c.446.82.023 1.841-.872 2.105l-.34.1c-1.4.413-1.4 2.397 0 2.81l.34.1a1.464 1.464 0 0 1 .872 2.105l-.17.31c-.698 1.283.705 2.686 1.987 1.987l.311-.169a1.464 1.464 0 0 1 2.105.872l.1.34c.413 1.4 2.397 1.4 2.81 0l.1-.34a1.464 1.464 0 0 1 2.105-.872l.31.17c1.283.698 2.686-.705 1.987-1.987l-.169-.311a1.464 1.464 0 0 1 .872-2.105l.34-.1c1.4-.413 1.4-2.397 0-2.81l-.34-.1a1.464 1.464 0 0 1-.872-2.105l.17-.31c.698-1.283-.705-2.686-1.987-1.987l-.311.169a1.464 1.464 0 0 1-2.105-.872l-.1-.34zM8 10.93a2.929 2.929 0 1 1 0-5.86 2.929 2.929 0 0 1 0 5.858z"/>
|
|
383
|
-
</svg>
|
|
384
|
-
<span class="ms-1">{showControlsPanel ? 'Hide' : 'Show'} Controls</span>
|
|
385
|
-
</button>
|
|
386
|
-
{/if}
|
|
387
382
|
</div>
|
|
388
383
|
|
|
389
384
|
<!-- Tab Content -->
|
|
@@ -402,7 +397,7 @@
|
|
|
402
397
|
{:else if mode === 'scrollspy'}
|
|
403
398
|
<!-- ScrollSpy Mode with Navigation -->
|
|
404
399
|
<div class="scrollspy-container">
|
|
405
|
-
<!-- ScrollSpy Navigation
|
|
400
|
+
<!-- ScrollSpy Navigation -->
|
|
406
401
|
<nav class="scrollspy-nav">
|
|
407
402
|
<div class="nav-wrapper">
|
|
408
403
|
<ul class="nav nav-pills">
|
|
@@ -418,21 +413,6 @@
|
|
|
418
413
|
</li>
|
|
419
414
|
{/each}
|
|
420
415
|
</ul>
|
|
421
|
-
|
|
422
|
-
<!-- Controls Toggle Button -->
|
|
423
|
-
{#if showGlobalControls}
|
|
424
|
-
<button
|
|
425
|
-
class="btn btn-sm btn-outline-secondary controls-toggle"
|
|
426
|
-
onclick={() => showControlsPanel = !showControlsPanel}
|
|
427
|
-
title={showControlsPanel ? "Hide Controls" : "Show Controls"}
|
|
428
|
-
type="button"
|
|
429
|
-
>
|
|
430
|
-
<svg width="16" height="16" fill="currentColor" viewBox="0 0 16 16">
|
|
431
|
-
<path d="M9.405 1.05c-.413-1.4-2.397-1.4-2.81 0l-.1.34a1.464 1.464 0 0 1-2.105.872l-.31-.17c-1.283-.698-2.686.705-1.987 1.987l.169.311c.446.82.023 1.841-.872 2.105l-.34.1c-1.4.413-1.4 2.397 0 2.81l.34.1a1.464 1.464 0 0 1 .872 2.105l-.17.31c-.698 1.283.705 2.686 1.987 1.987l.311-.169a1.464 1.464 0 0 1 2.105.872l.1.34c.413 1.4 2.397 1.4 2.81 0l.1-.34a1.464 1.464 0 0 1 2.105-.872l.31.17c1.283.698 2.686-.705 1.987-1.987l-.169-.311a1.464 1.464 0 0 1 .872-2.105l.34-.1c1.4-.413 1.4-2.397 0-2.81l-.34-.1a1.464 1.464 0 0 1-.872-2.105l.17-.31c.698-1.283-.705-2.686-1.987-1.987l-.311.169a1.464 1.464 0 0 1-2.105-.872l-.1-.34zM8 10.93a2.929 2.929 0 1 1 0-5.86 2.929 2.929 0 0 1 0 5.858z"/>
|
|
432
|
-
</svg>
|
|
433
|
-
<!-- <span class="ms-1">{showControlsPanel ? 'Hide' : 'Show'} Controls</span> -->
|
|
434
|
-
</button>
|
|
435
|
-
{/if}
|
|
436
416
|
</div>
|
|
437
417
|
</nav>
|
|
438
418
|
|
|
@@ -722,11 +702,10 @@
|
|
|
722
702
|
max-height: 100%; /* Constrain to slot */
|
|
723
703
|
}
|
|
724
704
|
|
|
725
|
-
/* Tab navigation wrapper
|
|
705
|
+
/* Tab navigation wrapper */
|
|
726
706
|
.nav-tabs-wrapper {
|
|
727
707
|
display: flex;
|
|
728
708
|
align-items: center;
|
|
729
|
-
gap: 1rem;
|
|
730
709
|
border-bottom: 1px solid #dee2e6;
|
|
731
710
|
}
|
|
732
711
|
|
|
@@ -736,25 +715,7 @@
|
|
|
736
715
|
margin-bottom: 0;
|
|
737
716
|
}
|
|
738
717
|
|
|
739
|
-
|
|
740
|
-
display: flex;
|
|
741
|
-
align-items: center;
|
|
742
|
-
gap: 0.25rem;
|
|
743
|
-
white-space: nowrap;
|
|
744
|
-
border-radius: 0.25rem;
|
|
745
|
-
transition: all 0.2s ease;
|
|
746
|
-
}
|
|
747
|
-
|
|
748
|
-
.controls-toggle:hover {
|
|
749
|
-
background-color: #f8f9fa;
|
|
750
|
-
border-color: #6c757d;
|
|
751
|
-
}
|
|
752
|
-
|
|
753
|
-
.controls-toggle svg {
|
|
754
|
-
flex-shrink: 0;
|
|
755
|
-
}
|
|
756
|
-
|
|
757
|
-
/* ScrollSpy navigation wrapper with toggle button */
|
|
718
|
+
/* ScrollSpy navigation wrapper */
|
|
758
719
|
.scrollspy-nav .nav-wrapper {
|
|
759
720
|
display: flex;
|
|
760
721
|
align-items: center;
|
|
@@ -1,14 +1,16 @@
|
|
|
1
1
|
<svelte:options runes={true} />
|
|
2
2
|
|
|
3
3
|
<script lang="ts">
|
|
4
|
-
import type { GlobalChartControls } from './charts.model.js';
|
|
4
|
+
import type { GlobalChartControls, HoverMode } from './charts.model.js';
|
|
5
5
|
|
|
6
6
|
interface Props {
|
|
7
7
|
controls: GlobalChartControls;
|
|
8
8
|
onUpdate: (controls: GlobalChartControls) => void;
|
|
9
|
+
isExpanded?: boolean;
|
|
10
|
+
onToggle?: () => void;
|
|
9
11
|
}
|
|
10
12
|
|
|
11
|
-
let { controls, onUpdate }: Props = $props();
|
|
13
|
+
let { controls, onUpdate, isExpanded = false, onToggle }: Props = $props();
|
|
12
14
|
|
|
13
15
|
function updateControls(updates: Partial<GlobalChartControls>) {
|
|
14
16
|
onUpdate({
|
|
@@ -46,180 +48,457 @@
|
|
|
46
48
|
}
|
|
47
49
|
});
|
|
48
50
|
}
|
|
51
|
+
|
|
52
|
+
function updateHoverMode(updates: Partial<NonNullable<GlobalChartControls['hoverMode']>>) {
|
|
53
|
+
onUpdate({
|
|
54
|
+
...controls,
|
|
55
|
+
hoverMode: {
|
|
56
|
+
...controls.hoverMode!,
|
|
57
|
+
...updates
|
|
58
|
+
}
|
|
59
|
+
});
|
|
60
|
+
}
|
|
49
61
|
</script>
|
|
50
62
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
{
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
63
|
+
<!-- Floating Controls Container -->
|
|
64
|
+
<div class="floating-controls-wrapper">
|
|
65
|
+
<!-- Toggle Button (always visible) -->
|
|
66
|
+
<button
|
|
67
|
+
class="floating-toggle-btn"
|
|
68
|
+
onclick={onToggle}
|
|
69
|
+
title={isExpanded ? "Hide Controls" : "Show Controls"}
|
|
70
|
+
type="button"
|
|
71
|
+
aria-expanded={isExpanded}
|
|
72
|
+
aria-label="Toggle chart controls"
|
|
73
|
+
>
|
|
74
|
+
<svg width="18" height="18" fill="currentColor" viewBox="0 0 16 16">
|
|
75
|
+
<path d="M9.405 1.05c-.413-1.4-2.397-1.4-2.81 0l-.1.34a1.464 1.464 0 0 1-2.105.872l-.31-.17c-1.283-.698-2.686.705-1.987 1.987l.169.311c.446.82.023 1.841-.872 2.105l-.34.1c-1.4.413-1.4 2.397 0 2.81l.34.1a1.464 1.464 0 0 1 .872 2.105l-.17.31c-.698 1.283.705 2.686 1.987 1.987l.311-.169a1.464 1.464 0 0 1 2.105.872l.1.34c.413 1.4 2.397 1.4 2.81 0l.1-.34a1.464 1.464 0 0 1 2.105-.872l.31.17c1.283.698 2.686-.705 1.987-1.987l-.169-.311a1.464 1.464 0 0 1 .872-2.105l.34-.1c1.4-.413 1.4-2.397 0-2.81l-.34-.1a1.464 1.464 0 0 1-.872-2.105l.17-.31c.698-1.283-.705-2.686-1.987-1.987l-.311.169a1.464 1.464 0 0 1-2.105-.872l-.1-.34zM8 10.93a2.929 2.929 0 1 1 0-5.86 2.929 2.929 0 0 1 0 5.858z"/>
|
|
76
|
+
</svg>
|
|
77
|
+
<svg
|
|
78
|
+
class="chevron"
|
|
79
|
+
class:expanded={isExpanded}
|
|
80
|
+
width="12"
|
|
81
|
+
height="12"
|
|
82
|
+
fill="currentColor"
|
|
83
|
+
viewBox="0 0 16 16"
|
|
84
|
+
>
|
|
85
|
+
<path fill-rule="evenodd" d="M1.646 4.646a.5.5 0 0 1 .708 0L8 10.293l5.646-5.647a.5.5 0 0 1 .708.708l-6 6a.5.5 0 0 1-.708 0l-6-6a.5.5 0 0 1 0-.708z"/>
|
|
86
|
+
</svg>
|
|
87
|
+
</button>
|
|
88
|
+
|
|
89
|
+
<!-- Controls Panel (expandable) -->
|
|
90
|
+
{#if isExpanded}
|
|
91
|
+
<div class="floating-controls-panel">
|
|
92
|
+
<div class="controls-section">
|
|
93
|
+
<!-- Top Row: Markers and Legend side by side -->
|
|
94
|
+
<div class="controls-row">
|
|
95
|
+
<!-- Markers Toggle -->
|
|
96
|
+
{#if controls.markers}
|
|
97
|
+
<div class="control-group-inline">
|
|
74
98
|
<input
|
|
75
|
-
type="
|
|
99
|
+
type="checkbox"
|
|
100
|
+
class="btn-check"
|
|
101
|
+
id="markersToggle"
|
|
102
|
+
checked={controls.markers.enabled}
|
|
103
|
+
onchange={() => updateMarkers({ enabled: !controls.markers!.enabled })}
|
|
104
|
+
/>
|
|
105
|
+
<label class="btn btn-outline-primary btn-sm" for="markersToggle">
|
|
106
|
+
Markers
|
|
107
|
+
</label>
|
|
108
|
+
</div>
|
|
109
|
+
{/if}
|
|
110
|
+
|
|
111
|
+
<!-- Legend Toggle -->
|
|
112
|
+
{#if controls.legend}
|
|
113
|
+
<div class="control-group-inline">
|
|
114
|
+
<input
|
|
115
|
+
type="checkbox"
|
|
76
116
|
class="btn-check"
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
onchange={() => updateMovingAverage({ windowOverride: undefined })}
|
|
117
|
+
id="legendToggle"
|
|
118
|
+
checked={controls.legend.enabled}
|
|
119
|
+
onchange={() => updateLegend({ enabled: !controls.legend!.enabled })}
|
|
81
120
|
/>
|
|
82
|
-
<label class="btn btn-outline-primary" for="
|
|
121
|
+
<label class="btn btn-outline-primary btn-sm" for="legendToggle">
|
|
122
|
+
Legend
|
|
123
|
+
</label>
|
|
124
|
+
</div>
|
|
125
|
+
{/if}
|
|
126
|
+
</div>
|
|
83
127
|
|
|
128
|
+
<!-- Hover Mode Controls -->
|
|
129
|
+
{#if controls.hoverMode}
|
|
130
|
+
<div class="control-group">
|
|
131
|
+
<div class="control-label">Hover Mode</div>
|
|
132
|
+
<div class="btn-group btn-group-sm" role="group" aria-label="Hover Mode">
|
|
84
133
|
<input
|
|
85
134
|
type="radio"
|
|
86
135
|
class="btn-check"
|
|
87
|
-
name="
|
|
88
|
-
id="
|
|
89
|
-
checked={controls.
|
|
90
|
-
onchange={() =>
|
|
136
|
+
name="hoverMode"
|
|
137
|
+
id="hoverModeX"
|
|
138
|
+
checked={controls.hoverMode.mode === 'x'}
|
|
139
|
+
onchange={() => updateHoverMode({ mode: 'x' })}
|
|
140
|
+
/>
|
|
141
|
+
<label class="btn btn-outline-primary" for="hoverModeX">X-Axis</label>
|
|
142
|
+
|
|
143
|
+
<!-- <input
|
|
144
|
+
type="radio"
|
|
145
|
+
class="btn-check"
|
|
146
|
+
name="hoverMode"
|
|
147
|
+
id="hoverModeY"
|
|
148
|
+
checked={controls.hoverMode.mode === 'y'}
|
|
149
|
+
onchange={() => updateHoverMode({ mode: 'y' })}
|
|
91
150
|
/>
|
|
92
|
-
<label class="btn btn-outline-primary" for="
|
|
151
|
+
<label class="btn btn-outline-primary" for="hoverModeY">Y-Axis</label> -->
|
|
93
152
|
|
|
94
153
|
<input
|
|
95
154
|
type="radio"
|
|
96
155
|
class="btn-check"
|
|
97
|
-
name="
|
|
98
|
-
id="
|
|
99
|
-
checked={controls.
|
|
100
|
-
onchange={() =>
|
|
156
|
+
name="hoverMode"
|
|
157
|
+
id="hoverModeClosest"
|
|
158
|
+
checked={controls.hoverMode.mode === 'closest'}
|
|
159
|
+
onchange={() => updateHoverMode({ mode: 'closest' })}
|
|
101
160
|
/>
|
|
102
|
-
<label class="btn btn-outline-primary" for="
|
|
161
|
+
<label class="btn btn-outline-primary" for="hoverModeClosest">Closest</label>
|
|
103
162
|
|
|
104
163
|
<input
|
|
105
164
|
type="radio"
|
|
106
165
|
class="btn-check"
|
|
107
|
-
name="
|
|
108
|
-
id="
|
|
109
|
-
checked={controls.
|
|
110
|
-
onchange={() =>
|
|
166
|
+
name="hoverMode"
|
|
167
|
+
id="hoverModeXUnified"
|
|
168
|
+
checked={controls.hoverMode.mode === 'x unified'}
|
|
169
|
+
onchange={() => updateHoverMode({ mode: 'x unified' })}
|
|
170
|
+
/>
|
|
171
|
+
<label class="btn btn-outline-primary" for="hoverModeXUnified">X-Unified</label>
|
|
172
|
+
|
|
173
|
+
<!-- <input
|
|
174
|
+
type="radio"
|
|
175
|
+
class="btn-check"
|
|
176
|
+
name="hoverMode"
|
|
177
|
+
id="hoverModeYUnified"
|
|
178
|
+
checked={controls.hoverMode.mode === 'y unified'}
|
|
179
|
+
onchange={() => updateHoverMode({ mode: 'y unified' })}
|
|
111
180
|
/>
|
|
112
|
-
<label class="btn btn-outline-primary" for="
|
|
181
|
+
<label class="btn btn-outline-primary" for="hoverModeYUnified">Y-Unified</label> -->
|
|
113
182
|
|
|
114
183
|
<input
|
|
115
184
|
type="radio"
|
|
116
185
|
class="btn-check"
|
|
117
|
-
name="
|
|
118
|
-
id="
|
|
119
|
-
checked={controls.
|
|
120
|
-
onchange={() =>
|
|
186
|
+
name="hoverMode"
|
|
187
|
+
id="hoverModeFalse"
|
|
188
|
+
checked={controls.hoverMode.mode === false}
|
|
189
|
+
onchange={() => updateHoverMode({ mode: false })}
|
|
121
190
|
/>
|
|
122
|
-
<label class="btn btn-outline-primary" for="
|
|
191
|
+
<label class="btn btn-outline-primary" for="hoverModeFalse">Off</label>
|
|
123
192
|
</div>
|
|
193
|
+
</div>
|
|
194
|
+
{/if}
|
|
124
195
|
|
|
125
|
-
|
|
196
|
+
<!-- Bottom: Moving Average Controls -->
|
|
197
|
+
{#if controls.movingAverage}
|
|
198
|
+
<div class="control-group">
|
|
199
|
+
<!-- MA Enable Toggle Button -->
|
|
126
200
|
<input
|
|
127
201
|
type="checkbox"
|
|
128
202
|
class="btn-check"
|
|
129
|
-
id="
|
|
130
|
-
checked={controls.movingAverage.
|
|
131
|
-
onchange={() => updateMovingAverage({
|
|
203
|
+
id="maToggle"
|
|
204
|
+
checked={controls.movingAverage.enabled}
|
|
205
|
+
onchange={() => updateMovingAverage({ enabled: !controls.movingAverage!.enabled })}
|
|
132
206
|
/>
|
|
133
|
-
<label class="btn btn-outline-primary btn-sm
|
|
134
|
-
|
|
207
|
+
<label class="btn btn-outline-primary btn-sm" for="maToggle">
|
|
208
|
+
Moving Average
|
|
135
209
|
</label>
|
|
210
|
+
|
|
211
|
+
{#if controls.movingAverage.enabled}
|
|
212
|
+
<div class="control-subgroup">
|
|
213
|
+
<!-- MA Window Size -->
|
|
214
|
+
<div class="btn-group btn-group-sm" role="group" aria-label="MA Window">
|
|
215
|
+
<input
|
|
216
|
+
type="radio"
|
|
217
|
+
class="btn-check"
|
|
218
|
+
name="maWindow"
|
|
219
|
+
id="maWindowAuto"
|
|
220
|
+
checked={controls.movingAverage.windowOverride === undefined}
|
|
221
|
+
onchange={() => updateMovingAverage({ windowOverride: undefined })}
|
|
222
|
+
/>
|
|
223
|
+
<label class="btn btn-outline-primary" for="maWindowAuto">Auto</label>
|
|
224
|
+
|
|
225
|
+
<input
|
|
226
|
+
type="radio"
|
|
227
|
+
class="btn-check"
|
|
228
|
+
name="maWindow"
|
|
229
|
+
id="maWindow7"
|
|
230
|
+
checked={controls.movingAverage.windowOverride === 7}
|
|
231
|
+
onchange={() => updateMovingAverage({ windowOverride: 7 })}
|
|
232
|
+
/>
|
|
233
|
+
<label class="btn btn-outline-primary" for="maWindow7">7</label>
|
|
234
|
+
|
|
235
|
+
<input
|
|
236
|
+
type="radio"
|
|
237
|
+
class="btn-check"
|
|
238
|
+
name="maWindow"
|
|
239
|
+
id="maWindow14"
|
|
240
|
+
checked={controls.movingAverage.windowOverride === 14}
|
|
241
|
+
onchange={() => updateMovingAverage({ windowOverride: 14 })}
|
|
242
|
+
/>
|
|
243
|
+
<label class="btn btn-outline-primary" for="maWindow14">14</label>
|
|
244
|
+
|
|
245
|
+
<input
|
|
246
|
+
type="radio"
|
|
247
|
+
class="btn-check"
|
|
248
|
+
name="maWindow"
|
|
249
|
+
id="maWindow24"
|
|
250
|
+
checked={controls.movingAverage.windowOverride === 24}
|
|
251
|
+
onchange={() => updateMovingAverage({ windowOverride: 24 })}
|
|
252
|
+
/>
|
|
253
|
+
<label class="btn btn-outline-primary" for="maWindow24">24</label>
|
|
254
|
+
|
|
255
|
+
<input
|
|
256
|
+
type="radio"
|
|
257
|
+
class="btn-check"
|
|
258
|
+
name="maWindow"
|
|
259
|
+
id="maWindow30"
|
|
260
|
+
checked={controls.movingAverage.windowOverride === 30}
|
|
261
|
+
onchange={() => updateMovingAverage({ windowOverride: 30 })}
|
|
262
|
+
/>
|
|
263
|
+
<label class="btn btn-outline-primary" for="maWindow30">30</label>
|
|
264
|
+
</div>
|
|
265
|
+
|
|
266
|
+
<!-- Show Original Toggle Button -->
|
|
267
|
+
<input
|
|
268
|
+
type="checkbox"
|
|
269
|
+
class="btn-check"
|
|
270
|
+
id="showOriginal"
|
|
271
|
+
checked={controls.movingAverage.showOriginal}
|
|
272
|
+
onchange={() => updateMovingAverage({ showOriginal: !controls.movingAverage!.showOriginal })}
|
|
273
|
+
/>
|
|
274
|
+
<label class="btn btn-outline-primary btn-sm ms-2" for="showOriginal">
|
|
275
|
+
Show Original
|
|
276
|
+
</label>
|
|
277
|
+
</div>
|
|
278
|
+
{/if}
|
|
136
279
|
</div>
|
|
137
280
|
{/if}
|
|
138
281
|
</div>
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
<!-- Markers Toggle -->
|
|
142
|
-
{#if controls.markers}
|
|
143
|
-
<div class="control-group">
|
|
144
|
-
<input
|
|
145
|
-
type="checkbox"
|
|
146
|
-
class="btn-check"
|
|
147
|
-
id="markersToggle"
|
|
148
|
-
checked={controls.markers.enabled}
|
|
149
|
-
onchange={() => updateMarkers({ enabled: !controls.markers!.enabled })}
|
|
150
|
-
/>
|
|
151
|
-
<label class="btn btn-outline-secondary btn-sm" for="markersToggle">
|
|
152
|
-
Markers
|
|
153
|
-
</label>
|
|
154
|
-
</div>
|
|
155
|
-
{/if}
|
|
156
|
-
|
|
157
|
-
<!-- Legend Toggle -->
|
|
158
|
-
{#if controls.legend}
|
|
159
|
-
<div class="control-group">
|
|
160
|
-
<input
|
|
161
|
-
type="checkbox"
|
|
162
|
-
class="btn-check"
|
|
163
|
-
id="legendToggle"
|
|
164
|
-
checked={controls.legend.enabled}
|
|
165
|
-
onchange={() => updateLegend({ enabled: !controls.legend!.enabled })}
|
|
166
|
-
/>
|
|
167
|
-
<label class="btn btn-outline-secondary btn-sm" for="legendToggle">
|
|
168
|
-
Legend
|
|
169
|
-
</label>
|
|
170
|
-
</div>
|
|
171
|
-
{/if}
|
|
172
|
-
|
|
173
|
-
</div>
|
|
282
|
+
</div>
|
|
283
|
+
{/if}
|
|
174
284
|
</div>
|
|
175
285
|
|
|
176
286
|
<style>
|
|
177
|
-
/*
|
|
178
|
-
.
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
287
|
+
/* Floating Controls Wrapper - positioned in top-right */
|
|
288
|
+
.floating-controls-wrapper {
|
|
289
|
+
position: fixed;
|
|
290
|
+
top: 0.3rem;
|
|
291
|
+
right: 0.3rem;
|
|
292
|
+
z-index: 1000;
|
|
293
|
+
display: flex;
|
|
294
|
+
flex-direction: column;
|
|
295
|
+
align-items: flex-end;
|
|
296
|
+
gap: 0.5rem;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
/* Toggle Button - Always visible */
|
|
300
|
+
.floating-toggle-btn {
|
|
301
|
+
display: flex;
|
|
302
|
+
align-items: center;
|
|
303
|
+
gap: 0.375rem;
|
|
304
|
+
padding: 0.5rem 0.75rem;
|
|
305
|
+
background: rgba(255, 255, 255, 0.95);
|
|
306
|
+
backdrop-filter: blur(10px);
|
|
307
|
+
-webkit-backdrop-filter: blur(10px);
|
|
308
|
+
border: 1px solid rgba(0, 0, 0, 0.08);
|
|
309
|
+
border-radius: 8px;
|
|
310
|
+
box-shadow:
|
|
311
|
+
0 2px 8px rgba(0, 0, 0, 0.08),
|
|
312
|
+
0 4px 16px rgba(0, 0, 0, 0.04);
|
|
313
|
+
cursor: pointer;
|
|
314
|
+
transition: all 200ms cubic-bezier(0.4, 0, 0.2, 1);
|
|
183
315
|
font-size: 0.875rem;
|
|
316
|
+
font-weight: 500;
|
|
317
|
+
color: #495057;
|
|
318
|
+
opacity: 0.5;
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
.floating-toggle-btn:hover {
|
|
322
|
+
background: rgba(255, 255, 255, 1);
|
|
323
|
+
border-color: rgba(0, 0, 0, 0.12);
|
|
324
|
+
box-shadow:
|
|
325
|
+
0 4px 12px rgba(0, 0, 0, 0.12),
|
|
326
|
+
0 8px 24px rgba(0, 0, 0, 0.08);
|
|
327
|
+
transform: translateY(-1px);
|
|
328
|
+
opacity: 1;
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
.floating-toggle-btn:active {
|
|
332
|
+
transform: translateY(0);
|
|
333
|
+
box-shadow:
|
|
334
|
+
0 1px 4px rgba(0, 0, 0, 0.08),
|
|
335
|
+
0 2px 8px rgba(0, 0, 0, 0.04);
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
.floating-toggle-btn .chevron {
|
|
339
|
+
transition: transform 200ms cubic-bezier(0.4, 0, 0.2, 1);
|
|
340
|
+
opacity: 0.6;
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
.floating-toggle-btn .chevron.expanded {
|
|
344
|
+
transform: rotate(180deg);
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
/* Floating Controls Panel - Glass-morphism style */
|
|
348
|
+
.floating-controls-panel {
|
|
349
|
+
background: rgba(255, 255, 255, 0.95);
|
|
350
|
+
backdrop-filter: blur(12px);
|
|
351
|
+
-webkit-backdrop-filter: blur(12px);
|
|
352
|
+
border: 1px solid rgba(0, 0, 0, 0.08);
|
|
353
|
+
border-radius: 12px;
|
|
354
|
+
box-shadow:
|
|
355
|
+
0 4px 16px rgba(0, 0, 0, 0.1),
|
|
356
|
+
0 8px 32px rgba(0, 0, 0, 0.08);
|
|
357
|
+
padding: 1rem;
|
|
358
|
+
min-width: 320px;
|
|
359
|
+
max-width: 480px;
|
|
360
|
+
opacity: 1;
|
|
361
|
+
animation: slideIn 200ms cubic-bezier(0.4, 0, 0.2, 1);
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
.floating-controls-panel:hover {
|
|
365
|
+
opacity: 1;
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
@keyframes slideIn {
|
|
369
|
+
from {
|
|
370
|
+
opacity: 0;
|
|
371
|
+
transform: translateY(-8px);
|
|
372
|
+
}
|
|
373
|
+
to {
|
|
374
|
+
opacity: 1;
|
|
375
|
+
transform: translateY(0);
|
|
376
|
+
}
|
|
184
377
|
}
|
|
185
378
|
|
|
186
379
|
.controls-section {
|
|
380
|
+
display: flex;
|
|
381
|
+
flex-direction: column;
|
|
382
|
+
gap: 1rem;
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
/* Horizontal row for Markers and Legend at top */
|
|
386
|
+
.controls-row {
|
|
187
387
|
display: flex;
|
|
188
388
|
align-items: center;
|
|
189
|
-
gap:
|
|
190
|
-
|
|
389
|
+
gap: 0.75rem;
|
|
390
|
+
padding-bottom: 0.75rem;
|
|
391
|
+
border-bottom: 1px solid rgba(0, 0, 0, 0.08);
|
|
191
392
|
}
|
|
192
393
|
|
|
193
|
-
|
|
394
|
+
/* Inline control group for horizontal layout */
|
|
395
|
+
.control-group-inline {
|
|
194
396
|
display: flex;
|
|
195
397
|
align-items: center;
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
/* Vertical control group for Moving Average */
|
|
401
|
+
.control-group {
|
|
402
|
+
display: flex;
|
|
403
|
+
flex-direction: column;
|
|
196
404
|
gap: 0.75rem;
|
|
197
405
|
}
|
|
198
406
|
|
|
407
|
+
.control-group > label:first-of-type {
|
|
408
|
+
font-weight: 500;
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
.control-label {
|
|
412
|
+
font-size: 0.8125rem;
|
|
413
|
+
font-weight: 600;
|
|
414
|
+
color: #495057;
|
|
415
|
+
margin-bottom: 0.25rem;
|
|
416
|
+
}
|
|
417
|
+
|
|
199
418
|
.control-subgroup {
|
|
200
419
|
display: flex;
|
|
420
|
+
flex-wrap: wrap;
|
|
201
421
|
align-items: center;
|
|
202
422
|
gap: 0.5rem;
|
|
203
|
-
padding-left: 0.
|
|
204
|
-
|
|
423
|
+
padding-left: 0.75rem;
|
|
424
|
+
padding-top: 0.5rem;
|
|
425
|
+
border-left: 2px solid rgba(0, 0, 0, 0.08);
|
|
205
426
|
}
|
|
206
427
|
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
428
|
+
/* Bootstrap button overrides for better integration */
|
|
429
|
+
.floating-controls-panel :global(.btn-sm) {
|
|
430
|
+
padding: 0.375rem 0.75rem;
|
|
431
|
+
font-size: 0.8125rem;
|
|
210
432
|
font-weight: 500;
|
|
433
|
+
border-radius: 6px;
|
|
434
|
+
transition: all 150ms ease;
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
.floating-controls-panel :global(.btn-outline-primary) {
|
|
438
|
+
border-color: rgba(13, 110, 253, 0.3);
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
.floating-controls-panel :global(.btn-outline-primary:hover) {
|
|
442
|
+
background-color: rgba(13, 110, 253, 0.1);
|
|
443
|
+
border-color: rgba(13, 110, 253, 0.5);
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
.floating-controls-panel :global(.btn-check:checked + .btn-outline-primary) {
|
|
447
|
+
background-color: #0d6efd;
|
|
448
|
+
border-color: #0d6efd;
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
.floating-controls-panel :global(.btn-group) {
|
|
452
|
+
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.06);
|
|
453
|
+
border-radius: 6px;
|
|
454
|
+
overflow: hidden;
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
.floating-controls-panel :global(.form-label) {
|
|
458
|
+
font-size: 0.8125rem;
|
|
459
|
+
font-weight: 600;
|
|
460
|
+
color: #495057;
|
|
461
|
+
margin-bottom: 0.5rem;
|
|
211
462
|
}
|
|
212
463
|
|
|
213
464
|
/* Responsive adjustments */
|
|
214
465
|
@media (max-width: 768px) {
|
|
215
|
-
.controls-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
466
|
+
.floating-controls-wrapper {
|
|
467
|
+
top: 0.5rem;
|
|
468
|
+
right: 0.5rem;
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
.floating-controls-panel {
|
|
472
|
+
min-width: 280px;
|
|
473
|
+
max-width: calc(100vw - 2rem);
|
|
474
|
+
padding: 0.875rem;
|
|
219
475
|
}
|
|
220
|
-
|
|
476
|
+
|
|
477
|
+
.control-subgroup {
|
|
478
|
+
padding-left: 0.5rem;
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
/* Dark mode support (optional - activate if needed) */
|
|
483
|
+
@media (prefers-color-scheme: dark) {
|
|
484
|
+
.floating-toggle-btn {
|
|
485
|
+
background: rgba(33, 37, 41, 0.95);
|
|
486
|
+
border-color: rgba(255, 255, 255, 0.1);
|
|
487
|
+
color: #f8f9fa;
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
.floating-toggle-btn:hover {
|
|
491
|
+
background: rgba(33, 37, 41, 1);
|
|
492
|
+
border-color: rgba(255, 255, 255, 0.15);
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
.floating-controls-panel {
|
|
496
|
+
background: rgba(33, 37, 41, 0.95);
|
|
497
|
+
border-color: rgba(255, 255, 255, 0.1);
|
|
498
|
+
}
|
|
499
|
+
|
|
221
500
|
.control-subgroup {
|
|
222
|
-
|
|
501
|
+
border-left-color: rgba(255, 255, 255, 0.1);
|
|
223
502
|
}
|
|
224
503
|
}
|
|
225
504
|
</style>
|
|
@@ -2,6 +2,8 @@ import type { GlobalChartControls } from './charts.model.js';
|
|
|
2
2
|
interface Props {
|
|
3
3
|
controls: GlobalChartControls;
|
|
4
4
|
onUpdate: (controls: GlobalChartControls) => void;
|
|
5
|
+
isExpanded?: boolean;
|
|
6
|
+
onToggle?: () => void;
|
|
5
7
|
}
|
|
6
8
|
declare const GlobalControls: import("svelte").Component<Props, {}, "">;
|
|
7
9
|
type GlobalControls = ReturnType<typeof GlobalControls>;
|