@smartnet360/svelte-components 0.0.105 → 0.0.107
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +75 -0
- package/dist/core/CellTable/CellTable.svelte +87 -15
- package/dist/core/CellTable/CellTable.svelte.d.ts +6 -0
- package/dist/core/CellTable/CellTableDemo.svelte +83 -127
- package/dist/core/CellTable/CellTablePanel.svelte +293 -25
- package/dist/core/CellTable/CellTablePanel.svelte.d.ts +15 -1
- package/dist/core/CellTable/CellTableToolbar.svelte +71 -1
- package/dist/core/CellTable/CellTableToolbar.svelte.d.ts +17 -0
- package/dist/core/CellTable/ColumnPicker.svelte +214 -0
- package/dist/core/CellTable/ColumnPicker.svelte.d.ts +26 -0
- package/dist/core/CellTable/column-config.d.ts +16 -0
- package/dist/core/CellTable/column-config.js +72 -0
- package/dist/core/CellTable/index.d.ts +3 -2
- package/dist/core/CellTable/index.js +6 -3
- package/dist/core/CellTable/types.d.ts +5 -46
- package/dist/core/CoverageMap/data/SiteStore.js +2 -2
- package/dist/map-v3/demo/demo-cells.d.ts +14 -10
- package/dist/map-v3/demo/demo-cells.js +21 -244
- package/dist/map-v3/features/cells/types.d.ts +3 -54
- package/dist/map-v3/features/cells/types.js +2 -1
- package/dist/shared/demo/cell-generator.d.ts +73 -0
- package/dist/shared/demo/cell-generator.js +258 -0
- package/dist/shared/demo/cell-types.d.ts +62 -0
- package/dist/shared/demo/cell-types.js +6 -0
- package/dist/shared/demo/index.d.ts +7 -0
- package/dist/shared/demo/index.js +7 -0
- package/package.json +1 -1
- package/dist/core/CellTable/demo-data.d.ts +0 -5
- package/dist/core/CellTable/demo-data.js +0 -501
package/README.md
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
# RF Tool Components
|
|
2
|
+
|
|
3
|
+
Private SvelteKit component library for RF planning and network visualization tools.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install @smartnet360/svelte-components
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Components
|
|
12
|
+
|
|
13
|
+
### Core Components (`@smartnet360/svelte-components/core`)
|
|
14
|
+
|
|
15
|
+
| Component | Description |
|
|
16
|
+
|-----------|-------------|
|
|
17
|
+
| **Desktop** | Grid orchestrator for multi-panel layouts with component assignment |
|
|
18
|
+
| **Charts** | Plotly-based chart system with tabs/scrollspy modes |
|
|
19
|
+
| **CellTable** | Tabulator-based cell data table with grouping and filtering |
|
|
20
|
+
| **TreeView** | Hierarchical tree component for data navigation |
|
|
21
|
+
| **Settings** | Configuration panels and settings management |
|
|
22
|
+
| **CoverageMap** | Coverage visualization component |
|
|
23
|
+
|
|
24
|
+
### Map Components
|
|
25
|
+
|
|
26
|
+
| Package | Description |
|
|
27
|
+
|---------|-------------|
|
|
28
|
+
| **map-v2** | Legacy map implementation |
|
|
29
|
+
| **map-v3** | Current map implementation with cell/site features |
|
|
30
|
+
|
|
31
|
+
### Shared Utilities (`@smartnet360/svelte-components/shared`)
|
|
32
|
+
|
|
33
|
+
| Module | Description |
|
|
34
|
+
|--------|-------------|
|
|
35
|
+
| **demo** | Cell data generator with configurable presets (small/medium/large/xlarge) |
|
|
36
|
+
| **ResizableSplitPanel** | Draggable split panel component |
|
|
37
|
+
|
|
38
|
+
## Development
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
# Install dependencies
|
|
42
|
+
npm install
|
|
43
|
+
|
|
44
|
+
# Run dev server
|
|
45
|
+
npm run dev
|
|
46
|
+
|
|
47
|
+
# Type checking
|
|
48
|
+
npm run check
|
|
49
|
+
|
|
50
|
+
# Build library
|
|
51
|
+
npm run build
|
|
52
|
+
|
|
53
|
+
# Release (patch/minor/major)
|
|
54
|
+
npm run release:patch
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
## Demo Routes
|
|
58
|
+
|
|
59
|
+
Demo pages available at `/apps/demo/`:
|
|
60
|
+
- `/apps/demo/cell-table` - CellTable component
|
|
61
|
+
- `/apps/demo/charts` - Charts component
|
|
62
|
+
- `/apps/demo/desktop` - Desktop grid system
|
|
63
|
+
|
|
64
|
+
## Tech Stack
|
|
65
|
+
|
|
66
|
+
- **Svelte 5** with runes (`$state`, `$derived`, `$effect`)
|
|
67
|
+
- **SvelteKit** library mode
|
|
68
|
+
- **TypeScript** first
|
|
69
|
+
- **Bootstrap 5** for styling
|
|
70
|
+
- **Plotly.js** for charts
|
|
71
|
+
- **Tabulator** for data tables
|
|
72
|
+
|
|
73
|
+
## License
|
|
74
|
+
|
|
75
|
+
Private - SmartNet360
|
|
@@ -57,8 +57,11 @@
|
|
|
57
57
|
let table: Tabulator | null = null;
|
|
58
58
|
let isInitialized = $state(false);
|
|
59
59
|
|
|
60
|
-
// Reactive column configuration
|
|
61
|
-
let columns = $derived(
|
|
60
|
+
// Reactive column configuration - only changes when preset changes
|
|
61
|
+
let columns = $derived.by(() => {
|
|
62
|
+
// Only depend on columnPreset to avoid unnecessary recalculations
|
|
63
|
+
return getColumnsForPreset(columnPreset, techColors, statusColors, headerFilters);
|
|
64
|
+
});
|
|
62
65
|
|
|
63
66
|
// Build Tabulator options
|
|
64
67
|
function buildOptions(): Options {
|
|
@@ -171,13 +174,7 @@
|
|
|
171
174
|
// Mark as initialized after table is ready
|
|
172
175
|
table.on('tableBuilt', () => {
|
|
173
176
|
isInitialized = true;
|
|
174
|
-
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
// Update table data when cells change
|
|
178
|
-
$effect(() => {
|
|
179
|
-
if (isInitialized && table && cells) {
|
|
180
|
-
table.replaceData(cells);
|
|
177
|
+
// Fire initial data change event
|
|
181
178
|
if (ondatachange) {
|
|
182
179
|
ondatachange({
|
|
183
180
|
type: 'load',
|
|
@@ -185,28 +182,52 @@
|
|
|
185
182
|
filteredCount: cells.length
|
|
186
183
|
});
|
|
187
184
|
}
|
|
185
|
+
});
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
// Track previous values to avoid unnecessary updates
|
|
189
|
+
let prevCellsLength = 0;
|
|
190
|
+
let prevCellsFirstId: string | null = null;
|
|
191
|
+
let prevGroupBy: string | null = null;
|
|
192
|
+
let prevColumnPreset: string | null = null;
|
|
193
|
+
|
|
194
|
+
// Update table data when cells actually change (not just reference)
|
|
195
|
+
$effect(() => {
|
|
196
|
+
const currentLength = cells?.length ?? 0;
|
|
197
|
+
const currentFirstId = cells?.[0]?.id ?? null;
|
|
198
|
+
|
|
199
|
+
// Only update if length or first item changed (rough equality check)
|
|
200
|
+
if (isInitialized && table &&
|
|
201
|
+
(currentLength !== prevCellsLength || currentFirstId !== prevCellsFirstId)) {
|
|
202
|
+
prevCellsLength = currentLength;
|
|
203
|
+
prevCellsFirstId = currentFirstId;
|
|
204
|
+
table.replaceData(cells);
|
|
205
|
+
ondatachange?.({
|
|
206
|
+
type: 'load',
|
|
207
|
+
rowCount: cells.length,
|
|
208
|
+
filteredCount: cells.length
|
|
209
|
+
});
|
|
188
210
|
}
|
|
189
211
|
});
|
|
190
212
|
|
|
191
213
|
// Update grouping when groupBy changes
|
|
192
214
|
$effect(() => {
|
|
193
|
-
if (isInitialized && table) {
|
|
215
|
+
if (isInitialized && table && groupBy !== prevGroupBy) {
|
|
216
|
+
prevGroupBy = groupBy;
|
|
194
217
|
if (groupBy === 'none') {
|
|
195
218
|
table.setGroupBy(false);
|
|
196
219
|
} else {
|
|
197
220
|
table.setGroupBy(groupBy);
|
|
198
221
|
table.setGroupHeader(getGroupHeaderFormatter(groupBy));
|
|
199
222
|
}
|
|
200
|
-
// Force redraw after grouping change
|
|
201
|
-
table.redraw(true);
|
|
202
223
|
}
|
|
203
224
|
});
|
|
204
225
|
|
|
205
226
|
// Update columns when preset changes
|
|
206
227
|
$effect(() => {
|
|
207
|
-
if (isInitialized && table &&
|
|
228
|
+
if (isInitialized && table && columnPreset !== prevColumnPreset) {
|
|
229
|
+
prevColumnPreset = columnPreset;
|
|
208
230
|
table.setColumns(columns);
|
|
209
|
-
table.redraw(true);
|
|
210
231
|
}
|
|
211
232
|
});
|
|
212
233
|
|
|
@@ -256,12 +277,63 @@
|
|
|
256
277
|
}
|
|
257
278
|
|
|
258
279
|
export function clearFilters(): void {
|
|
259
|
-
table
|
|
280
|
+
if (!table) return;
|
|
281
|
+
// Clear programmatic filters
|
|
282
|
+
table.clearFilter();
|
|
283
|
+
// Clear header filter inputs
|
|
284
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
285
|
+
(table as any).clearHeaderFilter();
|
|
260
286
|
}
|
|
261
287
|
|
|
262
288
|
export function redraw(): void {
|
|
263
289
|
table?.redraw(true);
|
|
264
290
|
}
|
|
291
|
+
|
|
292
|
+
export function collapseAll(): void {
|
|
293
|
+
if (!table) return;
|
|
294
|
+
// Use setGroupStartOpen to collapse all groups, then refresh data to apply
|
|
295
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
296
|
+
(table as any).setGroupStartOpen(false);
|
|
297
|
+
table.setData(table.getData());
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
export function expandAll(): void {
|
|
301
|
+
if (!table) return;
|
|
302
|
+
// Use setGroupStartOpen to expand all groups, then refresh data to apply
|
|
303
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
304
|
+
(table as any).setGroupStartOpen(true);
|
|
305
|
+
table.setData(table.getData());
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
export function toggleHeaderFilters(visible: boolean): void {
|
|
309
|
+
if (!table) return;
|
|
310
|
+
const headerFiltersElement = tableContainer.querySelector('.tabulator-header-filter');
|
|
311
|
+
if (headerFiltersElement) {
|
|
312
|
+
// Toggle all header filter rows
|
|
313
|
+
const filterRows = tableContainer.querySelectorAll('.tabulator-col .tabulator-header-filter');
|
|
314
|
+
filterRows.forEach(el => {
|
|
315
|
+
(el as HTMLElement).style.display = visible ? '' : 'none';
|
|
316
|
+
});
|
|
317
|
+
table.redraw();
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
export function showColumn(field: string): void {
|
|
322
|
+
table?.showColumn(field);
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
export function hideColumn(field: string): void {
|
|
326
|
+
table?.hideColumn(field);
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
export function getVisibleColumns(): string[] {
|
|
330
|
+
if (!table) return [];
|
|
331
|
+
const columns = table.getColumns();
|
|
332
|
+
return columns
|
|
333
|
+
.filter(col => col.isVisible())
|
|
334
|
+
.map(col => col.getField())
|
|
335
|
+
.filter((field): field is string => !!field);
|
|
336
|
+
}
|
|
265
337
|
</script>
|
|
266
338
|
|
|
267
339
|
<div class="cell-table-container">
|
|
@@ -22,6 +22,12 @@ declare const CellTable: import("svelte").Component<Props, {
|
|
|
22
22
|
setFilter: (field: string, type: string, value: unknown) => void;
|
|
23
23
|
clearFilters: () => void;
|
|
24
24
|
redraw: () => void;
|
|
25
|
+
collapseAll: () => void;
|
|
26
|
+
expandAll: () => void;
|
|
27
|
+
toggleHeaderFilters: (visible: boolean) => void;
|
|
28
|
+
showColumn: (field: string) => void;
|
|
29
|
+
hideColumn: (field: string) => void;
|
|
30
|
+
getVisibleColumns: () => string[];
|
|
25
31
|
}, "">;
|
|
26
32
|
type CellTable = ReturnType<typeof CellTable>;
|
|
27
33
|
export default CellTable;
|
|
@@ -1,145 +1,101 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import CellTablePanel from './CellTablePanel.svelte';
|
|
3
|
-
import type { CellTableGroupField, ColumnPreset,
|
|
4
|
-
import {
|
|
3
|
+
import type { CellTableGroupField, ColumnPreset, RowSelectionEvent } from './types';
|
|
4
|
+
import { generateCellsFromPreset, getGeneratorInfo, type GeneratorPreset } from '../../shared/demo';
|
|
5
5
|
|
|
6
|
+
let datasetSize: GeneratorPreset = $state('small');
|
|
7
|
+
let demoCells = $state(generateCellsFromPreset('small'));
|
|
8
|
+
let dataInfo = $derived(getGeneratorInfo(demoCells));
|
|
9
|
+
|
|
6
10
|
let groupBy: CellTableGroupField = $state('none');
|
|
7
11
|
let columnPreset: ColumnPreset = $state('default');
|
|
8
|
-
let lastClickedCell: CellData | null = $state(null);
|
|
9
|
-
let selectedIds: string[] = $state([]);
|
|
10
|
-
|
|
11
|
-
function handleRowClick(event: RowClickEvent) {
|
|
12
|
-
lastClickedCell = event.row;
|
|
13
|
-
console.log('Row clicked:', event.row);
|
|
14
|
-
}
|
|
15
12
|
|
|
16
13
|
function handleSelectionChange(event: RowSelectionEvent) {
|
|
17
|
-
selectedIds = event.ids;
|
|
18
14
|
console.log('Selection changed:', event.ids);
|
|
19
15
|
}
|
|
16
|
+
|
|
17
|
+
function regenerateData() {
|
|
18
|
+
demoCells = generateCellsFromPreset(datasetSize);
|
|
19
|
+
}
|
|
20
20
|
</script>
|
|
21
21
|
|
|
22
|
-
<div class="cell-table-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
22
|
+
<div class="cell-table-page vh-100 d-flex flex-column">
|
|
23
|
+
<!-- Simple header for demo controls only -->
|
|
24
|
+
<div class="demo-controls bg-light border-bottom px-3 py-2 d-flex align-items-center gap-3 flex-wrap">
|
|
25
|
+
<div class="input-group input-group-sm" style="width: auto;">
|
|
26
|
+
<span class="input-group-text">Dataset</span>
|
|
27
|
+
<select class="form-select" bind:value={datasetSize} onchange={regenerateData}>
|
|
28
|
+
<option value="small">Small (~300)</option>
|
|
29
|
+
<option value="medium">Medium (~3K)</option>
|
|
30
|
+
<option value="large">Large (~15K)</option>
|
|
31
|
+
<option value="xlarge">XLarge (~60K)</option>
|
|
32
|
+
</select>
|
|
32
33
|
</div>
|
|
34
|
+
<span class="badge bg-info">
|
|
35
|
+
{dataInfo.totalCells.toLocaleString()} cells | {dataInfo.totalSites} sites
|
|
36
|
+
</span>
|
|
37
|
+
<span class="badge bg-secondary d-none d-md-inline">
|
|
38
|
+
{Object.entries(dataInfo.techBreakdown).map(([k, v]) => `${k}: ${v}`).join(' | ')}
|
|
39
|
+
</span>
|
|
33
40
|
</div>
|
|
34
41
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
{
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
<
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
42
|
+
<!-- CellTablePanel with integrated sidebar -->
|
|
43
|
+
<div class="flex-grow-1 overflow-hidden">
|
|
44
|
+
<CellTablePanel
|
|
45
|
+
cells={demoCells}
|
|
46
|
+
bind:groupBy
|
|
47
|
+
bind:columnPreset
|
|
48
|
+
selectable={true}
|
|
49
|
+
multiSelect={true}
|
|
50
|
+
showToolbar={true}
|
|
51
|
+
showExport={true}
|
|
52
|
+
headerFilters={true}
|
|
53
|
+
showDetailsSidebar={true}
|
|
54
|
+
sidebarWidth={320}
|
|
55
|
+
title="Cell Data"
|
|
56
|
+
onselectionchange={handleSelectionChange}
|
|
57
|
+
>
|
|
58
|
+
{#snippet footer({ selectedRows, selectedCount })}
|
|
59
|
+
<div class="d-flex align-items-center justify-content-between">
|
|
60
|
+
<span class="text-muted small">
|
|
61
|
+
{#if selectedCount > 0}
|
|
62
|
+
{selectedCount} cell(s) selected
|
|
63
|
+
{:else}
|
|
64
|
+
Click a row to view details
|
|
65
|
+
{/if}
|
|
66
|
+
</span>
|
|
67
|
+
<div class="btn-group">
|
|
68
|
+
<button
|
|
69
|
+
type="button"
|
|
70
|
+
class="btn btn-sm btn-outline-primary"
|
|
71
|
+
disabled={selectedCount === 0}
|
|
72
|
+
onclick={() => console.log('Process:', selectedRows.map(r => r.id))}
|
|
73
|
+
>
|
|
74
|
+
<i class="bi bi-gear"></i>
|
|
75
|
+
<span class="d-none d-sm-inline ms-1">Process</span>
|
|
76
|
+
</button>
|
|
77
|
+
<button
|
|
78
|
+
type="button"
|
|
79
|
+
class="btn btn-sm btn-outline-secondary"
|
|
80
|
+
disabled={selectedCount === 0}
|
|
81
|
+
onclick={() => console.log('Export:', selectedRows.map(r => r.id))}
|
|
82
|
+
>
|
|
83
|
+
<i class="bi bi-download"></i>
|
|
84
|
+
<span class="d-none d-sm-inline ms-1">Export</span>
|
|
85
|
+
</button>
|
|
77
86
|
</div>
|
|
78
|
-
{/snippet}
|
|
79
|
-
</CellTablePanel>
|
|
80
|
-
</div>
|
|
81
|
-
|
|
82
|
-
<div class="col-12 col-lg-3 h-100 overflow-auto">
|
|
83
|
-
<div class="card h-100">
|
|
84
|
-
<div class="card-header">
|
|
85
|
-
<h6 class="mb-0">
|
|
86
|
-
<i class="bi bi-info-circle"></i> Cell Details
|
|
87
|
-
</h6>
|
|
88
87
|
</div>
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
<dl class="row mb-0 small">
|
|
92
|
-
<dt class="col-5">ID</dt>
|
|
93
|
-
<dd class="col-7"><code>{lastClickedCell.id}</code></dd>
|
|
94
|
-
|
|
95
|
-
<dt class="col-5">Cell Name</dt>
|
|
96
|
-
<dd class="col-7">{lastClickedCell.cellName}</dd>
|
|
97
|
-
|
|
98
|
-
<dt class="col-5">Site</dt>
|
|
99
|
-
<dd class="col-7">{lastClickedCell.siteId}</dd>
|
|
100
|
-
|
|
101
|
-
<dt class="col-5">Technology</dt>
|
|
102
|
-
<dd class="col-7">
|
|
103
|
-
<span class="badge bg-secondary">{lastClickedCell.tech}</span>
|
|
104
|
-
</dd>
|
|
105
|
-
|
|
106
|
-
<dt class="col-5">Band</dt>
|
|
107
|
-
<dd class="col-7">
|
|
108
|
-
<span class="badge bg-info">{lastClickedCell.fband}</span>
|
|
109
|
-
</dd>
|
|
110
|
-
|
|
111
|
-
<dt class="col-5">Status</dt>
|
|
112
|
-
<dd class="col-7">
|
|
113
|
-
<span class="badge bg-success">{lastClickedCell.status.replace(/_/g, ' ')}</span>
|
|
114
|
-
</dd>
|
|
115
|
-
|
|
116
|
-
<dt class="col-5">Azimuth</dt>
|
|
117
|
-
<dd class="col-7">{lastClickedCell.azimuth}°</dd>
|
|
118
|
-
|
|
119
|
-
<dt class="col-5">Height</dt>
|
|
120
|
-
<dd class="col-7">{lastClickedCell.height}m</dd>
|
|
121
|
-
|
|
122
|
-
<dt class="col-5">Antenna</dt>
|
|
123
|
-
<dd class="col-7 text-truncate" title={lastClickedCell.antenna}>
|
|
124
|
-
{lastClickedCell.antenna}
|
|
125
|
-
</dd>
|
|
126
|
-
|
|
127
|
-
<dt class="col-5">Planner</dt>
|
|
128
|
-
<dd class="col-7">{lastClickedCell.planner}</dd>
|
|
129
|
-
|
|
130
|
-
{#if lastClickedCell.comment}
|
|
131
|
-
<dt class="col-5">Comment</dt>
|
|
132
|
-
<dd class="col-7 text-muted">{lastClickedCell.comment}</dd>
|
|
133
|
-
{/if}
|
|
134
|
-
</dl>
|
|
135
|
-
{:else}
|
|
136
|
-
<p class="text-muted text-center">
|
|
137
|
-
<i class="bi bi-hand-index"></i><br>
|
|
138
|
-
Click a row to see details
|
|
139
|
-
</p>
|
|
140
|
-
{/if}
|
|
141
|
-
</div>
|
|
142
|
-
</div>
|
|
143
|
-
</div>
|
|
88
|
+
{/snippet}
|
|
89
|
+
</CellTablePanel>
|
|
144
90
|
</div>
|
|
145
91
|
</div>
|
|
92
|
+
|
|
93
|
+
<style>
|
|
94
|
+
.cell-table-page {
|
|
95
|
+
background: var(--bs-body-bg, #f8f9fa);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
.demo-controls {
|
|
99
|
+
flex-shrink: 0;
|
|
100
|
+
}
|
|
101
|
+
</style>
|