@w5-ui/svelte 0.1.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.
- package/README.md +8 -0
- package/package.json +57 -0
- package/src/lib/components/Accordion.svelte +3 -0
- package/src/lib/components/AccordionItem.svelte +40 -0
- package/src/lib/components/ActionMenu.svelte +96 -0
- package/src/lib/components/ActivityFeed.svelte +60 -0
- package/src/lib/components/Alert.svelte +19 -0
- package/src/lib/components/AnnotatedTimeSeries.svelte +90 -0
- package/src/lib/components/ArcDiagram.svelte +74 -0
- package/src/lib/components/AreaChart.svelte +64 -0
- package/src/lib/components/Avatar.svelte +30 -0
- package/src/lib/components/AvatarGroup.svelte +35 -0
- package/src/lib/components/Badge.svelte +25 -0
- package/src/lib/components/Banner.svelte +37 -0
- package/src/lib/components/BarChart.svelte +52 -0
- package/src/lib/components/BeeswarmChart.svelte +88 -0
- package/src/lib/components/BoxPlot.svelte +66 -0
- package/src/lib/components/Breadcrumb.svelte +31 -0
- package/src/lib/components/BubbleChart.svelte +69 -0
- package/src/lib/components/BulletChart.svelte +70 -0
- package/src/lib/components/BumpChart.svelte +109 -0
- package/src/lib/components/Button.svelte +25 -0
- package/src/lib/components/CIDRInput.svelte +142 -0
- package/src/lib/components/CalendarHeatmap.svelte +86 -0
- package/src/lib/components/Callout.svelte +38 -0
- package/src/lib/components/CandlestickChart.svelte +66 -0
- package/src/lib/components/Card.svelte +11 -0
- package/src/lib/components/Carousel.svelte +64 -0
- package/src/lib/components/Checkbox.svelte +45 -0
- package/src/lib/components/ChordDiagram.svelte +90 -0
- package/src/lib/components/CirclePacking.svelte +119 -0
- package/src/lib/components/CodeBlock.svelte +49 -0
- package/src/lib/components/ColorPicker.svelte +91 -0
- package/src/lib/components/ColumnToggle.svelte +96 -0
- package/src/lib/components/Combobox.svelte +138 -0
- package/src/lib/components/CommandPalette.svelte +152 -0
- package/src/lib/components/ConfirmDialog.svelte +84 -0
- package/src/lib/components/ContextMenu.svelte +107 -0
- package/src/lib/components/ContextualHelp.svelte +73 -0
- package/src/lib/components/ContourPlot.svelte +164 -0
- package/src/lib/components/CopyButton.svelte +33 -0
- package/src/lib/components/CorrelationMatrix.svelte +90 -0
- package/src/lib/components/CountUp.svelte +49 -0
- package/src/lib/components/CumulativeDistribution.svelte +67 -0
- package/src/lib/components/DatePicker.svelte +201 -0
- package/src/lib/components/DateRangePicker.svelte +249 -0
- package/src/lib/components/Dendrogram.svelte +128 -0
- package/src/lib/components/Donut.svelte +49 -0
- package/src/lib/components/DotPlot.svelte +121 -0
- package/src/lib/components/Drawer.svelte +60 -0
- package/src/lib/components/DualAxisChart.svelte +80 -0
- package/src/lib/components/DumbbellChart.svelte +56 -0
- package/src/lib/components/DurationInput.svelte +118 -0
- package/src/lib/components/EmptyState.svelte +21 -0
- package/src/lib/components/ErrorBandChart.svelte +69 -0
- package/src/lib/components/ExpandableRow.svelte +42 -0
- package/src/lib/components/Field.svelte +40 -0
- package/src/lib/components/FileUpload.svelte +96 -0
- package/src/lib/components/FilterBuilder.svelte +160 -0
- package/src/lib/components/FlameGraph.svelte +88 -0
- package/src/lib/components/ForceGraph.svelte +104 -0
- package/src/lib/components/FunnelChart.svelte +50 -0
- package/src/lib/components/GanttChart.svelte +49 -0
- package/src/lib/components/Gauge.svelte +53 -0
- package/src/lib/components/GroupedList.svelte +87 -0
- package/src/lib/components/HealthBar.svelte +19 -0
- package/src/lib/components/HeatMap.svelte +64 -0
- package/src/lib/components/HexbinChart.svelte +100 -0
- package/src/lib/components/Histogram.svelte +93 -0
- package/src/lib/components/HorizonChart.svelte +71 -0
- package/src/lib/components/HoverCard.svelte +45 -0
- package/src/lib/components/IPInput.svelte +103 -0
- package/src/lib/components/IcicleChart.svelte +87 -0
- package/src/lib/components/IconButton.svelte +19 -0
- package/src/lib/components/InlineEdit.svelte +72 -0
- package/src/lib/components/Input.svelte +18 -0
- package/src/lib/components/InputGroup.svelte +14 -0
- package/src/lib/components/JsonViewer.svelte +15 -0
- package/src/lib/components/JsonViewerNode.svelte +59 -0
- package/src/lib/components/KVTable.svelte +18 -0
- package/src/lib/components/KanbanBoard.svelte +141 -0
- package/src/lib/components/Kbd.svelte +16 -0
- package/src/lib/components/LineChart.svelte +67 -0
- package/src/lib/components/LogViewer.svelte +84 -0
- package/src/lib/components/LollipopChart.svelte +79 -0
- package/src/lib/components/MACInput.svelte +98 -0
- package/src/lib/components/MarimekkoChart.svelte +108 -0
- package/src/lib/components/MatrixChart.svelte +55 -0
- package/src/lib/components/Menubar.svelte +131 -0
- package/src/lib/components/MirroredBarChart.svelte +120 -0
- package/src/lib/components/Modal.svelte +90 -0
- package/src/lib/components/MultiSelect.svelte +165 -0
- package/src/lib/components/NightingaleChart.svelte +63 -0
- package/src/lib/components/NotificationPanel.svelte +170 -0
- package/src/lib/components/NumberInput.svelte +93 -0
- package/src/lib/components/OTPInput.svelte +94 -0
- package/src/lib/components/Pagination.svelte +58 -0
- package/src/lib/components/ParallelCoordinates.svelte +98 -0
- package/src/lib/components/ParetoChart.svelte +88 -0
- package/src/lib/components/PasswordInput.svelte +62 -0
- package/src/lib/components/PieChart.svelte +91 -0
- package/src/lib/components/Pill.svelte +12 -0
- package/src/lib/components/PolarHeatmap.svelte +85 -0
- package/src/lib/components/Popover.svelte +89 -0
- package/src/lib/components/ProgressBar.svelte +29 -0
- package/src/lib/components/PunchCard.svelte +73 -0
- package/src/lib/components/QuadrantChart.svelte +122 -0
- package/src/lib/components/RadarChart.svelte +71 -0
- package/src/lib/components/RadialBarChart.svelte +76 -0
- package/src/lib/components/RadioGroup.svelte +48 -0
- package/src/lib/components/RangeSlider.svelte +68 -0
- package/src/lib/components/RankedList.svelte +49 -0
- package/src/lib/components/ResizablePanel.svelte +77 -0
- package/src/lib/components/RidgelinePlot.svelte +103 -0
- package/src/lib/components/RowToggle.svelte +19 -0
- package/src/lib/components/SankeyDiagram.svelte +100 -0
- package/src/lib/components/ScatterPlot.svelte +54 -0
- package/src/lib/components/SearchBox.svelte +11 -0
- package/src/lib/components/SegmentedControl.svelte +51 -0
- package/src/lib/components/Select.svelte +110 -0
- package/src/lib/components/SelectionToolbar.svelte +42 -0
- package/src/lib/components/Sidebar.svelte +44 -0
- package/src/lib/components/Signal.svelte +12 -0
- package/src/lib/components/Skeleton.svelte +23 -0
- package/src/lib/components/SkipLink.svelte +9 -0
- package/src/lib/components/Slider.svelte +48 -0
- package/src/lib/components/SlopeChart.svelte +91 -0
- package/src/lib/components/SortHeader.svelte +19 -0
- package/src/lib/components/SortableList.svelte +87 -0
- package/src/lib/components/Sparkline.svelte +32 -0
- package/src/lib/components/SparklineMatrix.svelte +84 -0
- package/src/lib/components/Spinner.svelte +6 -0
- package/src/lib/components/SplitButton.svelte +102 -0
- package/src/lib/components/Spoiler.svelte +41 -0
- package/src/lib/components/StackedBarChart.svelte +83 -0
- package/src/lib/components/StackedProgress.svelte +32 -0
- package/src/lib/components/StarRating.svelte +64 -0
- package/src/lib/components/Stat.svelte +24 -0
- package/src/lib/components/StatusIndicator.svelte +10 -0
- package/src/lib/components/StepChart.svelte +74 -0
- package/src/lib/components/Stepper.svelte +40 -0
- package/src/lib/components/StreamGraph.svelte +93 -0
- package/src/lib/components/StripeChart.svelte +52 -0
- package/src/lib/components/SunburstChart.svelte +80 -0
- package/src/lib/components/SwitchPortGrid.svelte +45 -0
- package/src/lib/components/TabPanel.svelte +17 -0
- package/src/lib/components/Tabs.svelte +45 -0
- package/src/lib/components/Tag.svelte +19 -0
- package/src/lib/components/TagInput.svelte +92 -0
- package/src/lib/components/Textarea.svelte +31 -0
- package/src/lib/components/ThresholdAreaChart.svelte +72 -0
- package/src/lib/components/TimePicker.svelte +116 -0
- package/src/lib/components/TimeRange.svelte +104 -0
- package/src/lib/components/Timeline.svelte +32 -0
- package/src/lib/components/Toast.svelte +52 -0
- package/src/lib/components/Toggle.svelte +23 -0
- package/src/lib/components/ToggleGroup.svelte +32 -0
- package/src/lib/components/Tooltip.svelte +16 -0
- package/src/lib/components/Topbar.svelte +63 -0
- package/src/lib/components/TransferList.svelte +184 -0
- package/src/lib/components/TreeItem.svelte +61 -0
- package/src/lib/components/TreeMap.svelte +90 -0
- package/src/lib/components/TreeView.svelte +130 -0
- package/src/lib/components/UptimeTimeline.svelte +86 -0
- package/src/lib/components/VennDiagram.svelte +91 -0
- package/src/lib/components/ViolinPlot.svelte +114 -0
- package/src/lib/components/VirtualList.svelte +38 -0
- package/src/lib/components/WaffleChart.svelte +68 -0
- package/src/lib/components/WaterfallChart.svelte +96 -0
- package/src/lib/components/WordCloud.svelte +103 -0
- package/src/lib/icons/AlarmIcon.svelte +5 -0
- package/src/lib/icons/BellIcon.svelte +6 -0
- package/src/lib/icons/CaretIcon.svelte +8 -0
- package/src/lib/icons/ClientsIcon.svelte +5 -0
- package/src/lib/icons/CloseIcon.svelte +6 -0
- package/src/lib/icons/DashboardIcon.svelte +7 -0
- package/src/lib/icons/DevicesIcon.svelte +5 -0
- package/src/lib/icons/DownloadIcon.svelte +6 -0
- package/src/lib/icons/HelpIcon.svelte +7 -0
- package/src/lib/icons/InfraIcon.svelte +7 -0
- package/src/lib/icons/IntegrationsIcon.svelte +5 -0
- package/src/lib/icons/LogsIcon.svelte +6 -0
- package/src/lib/icons/PlusIcon.svelte +6 -0
- package/src/lib/icons/PortsIcon.svelte +6 -0
- package/src/lib/icons/SearchIcon.svelte +10 -0
- package/src/lib/icons/SecurityIcon.svelte +4 -0
- package/src/lib/icons/SettingsIcon.svelte +5 -0
- package/src/lib/icons/TopologyIcon.svelte +7 -0
- package/src/lib/icons/UpdatesIcon.svelte +6 -0
- package/src/lib/icons/VpnIcon.svelte +5 -0
- package/src/lib/icons/WifiIcon.svelte +5 -0
- package/src/lib/icons/WirelessIcon.svelte +4 -0
- package/src/lib/icons.ts +22 -0
- package/src/lib/index.ts +275 -0
- package/src/lib/styles.css +2 -0
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
export interface DumbbellItem {
|
|
3
|
+
label: string;
|
|
4
|
+
start: number;
|
|
5
|
+
end: number;
|
|
6
|
+
color?: string;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export let items: DumbbellItem[] = [];
|
|
10
|
+
export let unit: string = '';
|
|
11
|
+
export let color: string = '#006FFF';
|
|
12
|
+
export let ariaLabel: string = 'Dumbbell chart';
|
|
13
|
+
|
|
14
|
+
const VW = 340;
|
|
15
|
+
const LABEL_W = 110;
|
|
16
|
+
const PAD_R = 8;
|
|
17
|
+
const TRACK_W = VW - LABEL_W - PAD_R;
|
|
18
|
+
const DOT_R = 5;
|
|
19
|
+
const ROW_H = 26;
|
|
20
|
+
const PAD_T = 6;
|
|
21
|
+
const AXIS_H = 18;
|
|
22
|
+
const TICKS = 4;
|
|
23
|
+
|
|
24
|
+
$: allValues = items.flatMap((it) => [it.start, it.end]);
|
|
25
|
+
$: minVal = allValues.length ? Math.min(...allValues) : 0;
|
|
26
|
+
$: maxVal = allValues.length ? Math.max(...allValues) : 1;
|
|
27
|
+
$: range = maxVal - minVal || 1;
|
|
28
|
+
$: svgH = PAD_T + items.length * ROW_H + AXIS_H;
|
|
29
|
+
|
|
30
|
+
function tx(v: number): number {
|
|
31
|
+
return LABEL_W + ((v - minVal) / range) * TRACK_W;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
$: ticks = Array.from({ length: TICKS + 1 }, (_, i) =>
|
|
35
|
+
Math.round(minVal + (i / TICKS) * range),
|
|
36
|
+
);
|
|
37
|
+
</script>
|
|
38
|
+
|
|
39
|
+
<div role="img" aria-label={ariaLabel} style="width:100%;">
|
|
40
|
+
<svg viewBox="0 0 {VW} {svgH}" style="width:100%;height:auto;display:block;" aria-hidden="true" focusable="false">
|
|
41
|
+
{#each ticks as t, i}
|
|
42
|
+
<line x1={tx(t)} y1={PAD_T} x2={tx(t)} y2={PAD_T + items.length * ROW_H} stroke="rgba(255,255,255,0.06)" stroke-width="1" />
|
|
43
|
+
<text x={tx(t)} y={PAD_T + items.length * ROW_H + 13} fill="#6E7079" font-size="9" text-anchor="middle" font-family="inherit">{t}{unit}</text>
|
|
44
|
+
{/each}
|
|
45
|
+
{#each items as item, i}
|
|
46
|
+
{@const cy = PAD_T + i * ROW_H + ROW_H / 2}
|
|
47
|
+
{@const cx1 = tx(item.start)}
|
|
48
|
+
{@const cx2 = tx(item.end)}
|
|
49
|
+
{@const c = item.color ?? color}
|
|
50
|
+
<text x={LABEL_W - 6} y={cy + 4} fill="#A4A7B5" font-size="10" text-anchor="end" font-family="inherit">{item.label}</text>
|
|
51
|
+
<line x1={cx1} y1={cy} x2={cx2} y2={cy} stroke={c} stroke-width="2" opacity="0.4" />
|
|
52
|
+
<circle cx={cx1} cy={cy} r={DOT_R} fill="#13131A" stroke={c} stroke-width="1.5" />
|
|
53
|
+
<circle cx={cx2} cy={cy} r={DOT_R} fill={c} />
|
|
54
|
+
{/each}
|
|
55
|
+
</svg>
|
|
56
|
+
</div>
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
<script context="module" lang="ts">
|
|
2
|
+
let counter = 0;
|
|
3
|
+
</script>
|
|
4
|
+
|
|
5
|
+
<script lang="ts">
|
|
6
|
+
import { createEventDispatcher } from 'svelte';
|
|
7
|
+
|
|
8
|
+
export let label: string | undefined = undefined;
|
|
9
|
+
export let value: number = 0;
|
|
10
|
+
export let maxHours: number = 99;
|
|
11
|
+
export let disabled: boolean = false;
|
|
12
|
+
let className = '';
|
|
13
|
+
export { className as class };
|
|
14
|
+
|
|
15
|
+
const dispatch = createEventDispatcher<{ change: number }>();
|
|
16
|
+
const uid = `dash-ui-dur-${++counter}`;
|
|
17
|
+
|
|
18
|
+
function toHMS(secs: number): [number, number, number] {
|
|
19
|
+
const t = Math.max(0, secs);
|
|
20
|
+
return [Math.floor(t / 3600), Math.floor((t % 3600) / 60), t % 60];
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function fromHMS(h: number, m: number, s: number): number {
|
|
24
|
+
return h * 3600 + m * 60 + s;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function pad(n: number): string {
|
|
28
|
+
return String(n).padStart(2, '0');
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
$: [hours, minutes, seconds] = toHMS(value);
|
|
32
|
+
|
|
33
|
+
let inputs: [HTMLInputElement | null, HTMLInputElement | null, HTMLInputElement | null] = [null, null, null];
|
|
34
|
+
|
|
35
|
+
function focusAt(i: number) {
|
|
36
|
+
const el = inputs[i];
|
|
37
|
+
if (el) { el.focus(); el.select(); }
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function commit(h: number, m: number, s: number) {
|
|
41
|
+
value = fromHMS(Math.min(maxHours, h), m, s);
|
|
42
|
+
dispatch('change', value);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function handleKey(field: number, e: KeyboardEvent) {
|
|
46
|
+
const vals: [number, number, number] = [hours, minutes, seconds];
|
|
47
|
+
const maxes = [maxHours, 59, 59];
|
|
48
|
+
if (e.key === 'ArrowUp') {
|
|
49
|
+
e.preventDefault();
|
|
50
|
+
vals[field] = vals[field] >= maxes[field] ? 0 : vals[field] + 1;
|
|
51
|
+
commit(...vals);
|
|
52
|
+
} else if (e.key === 'ArrowDown') {
|
|
53
|
+
e.preventDefault();
|
|
54
|
+
vals[field] = vals[field] <= 0 ? maxes[field] : vals[field] - 1;
|
|
55
|
+
commit(...vals);
|
|
56
|
+
} else if ((e.key === ':' || e.key === 'ArrowRight') && field < 2) {
|
|
57
|
+
e.preventDefault();
|
|
58
|
+
focusAt(field + 1);
|
|
59
|
+
} else if (e.key === 'ArrowLeft' && field > 0) {
|
|
60
|
+
e.preventDefault();
|
|
61
|
+
focusAt(field - 1);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function handleInput(field: number, e: Event) {
|
|
66
|
+
const raw = (e.currentTarget as HTMLInputElement).value;
|
|
67
|
+
if (!/^\d{0,2}$/.test(raw)) return;
|
|
68
|
+
const n = parseInt(raw, 10);
|
|
69
|
+
if (isNaN(n)) return;
|
|
70
|
+
const maxes = [maxHours, 59, 59];
|
|
71
|
+
const updated: [number, number, number] = [hours, minutes, seconds];
|
|
72
|
+
updated[field] = Math.min(maxes[field], n);
|
|
73
|
+
commit(...updated);
|
|
74
|
+
if (raw.length === 2 && field < 2) setTimeout(() => focusAt(field + 1), 0);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const FIELDS = [
|
|
78
|
+
{ idx: 0, ariaLabel: 'Hours' },
|
|
79
|
+
{ idx: 1, ariaLabel: 'Minutes' },
|
|
80
|
+
{ idx: 2, ariaLabel: 'Seconds' },
|
|
81
|
+
];
|
|
82
|
+
|
|
83
|
+
$: fieldVals = [hours, minutes, seconds];
|
|
84
|
+
$: fieldMaxes = [maxHours, 59, 59];
|
|
85
|
+
</script>
|
|
86
|
+
|
|
87
|
+
<div class="dur-input-wrapper {className}">
|
|
88
|
+
{#if label}
|
|
89
|
+
<label for="{uid}-h" class="dur-input__label">{label}</label>
|
|
90
|
+
{/if}
|
|
91
|
+
<div class="dur-input{disabled ? ' dur-input--disabled' : ''}" role="group" aria-label={label ?? 'Duration'}>
|
|
92
|
+
{#each FIELDS as { idx, ariaLabel }, i}
|
|
93
|
+
<input
|
|
94
|
+
bind:this={inputs[idx]}
|
|
95
|
+
id={idx === 0 ? `${uid}-h` : undefined}
|
|
96
|
+
type="text"
|
|
97
|
+
inputmode="numeric"
|
|
98
|
+
pattern="[0-9]*"
|
|
99
|
+
role="spinbutton"
|
|
100
|
+
aria-label={ariaLabel}
|
|
101
|
+
aria-valuenow={fieldVals[idx]}
|
|
102
|
+
aria-valuemin={0}
|
|
103
|
+
aria-valuemax={fieldMaxes[idx]}
|
|
104
|
+
aria-valuetext={pad(fieldVals[idx])}
|
|
105
|
+
value={pad(fieldVals[idx])}
|
|
106
|
+
{disabled}
|
|
107
|
+
maxlength={2}
|
|
108
|
+
class="dur-input__field"
|
|
109
|
+
on:input={(e) => handleInput(idx, e)}
|
|
110
|
+
on:keydown={(e) => handleKey(idx, e)}
|
|
111
|
+
on:focus={(e) => e.currentTarget.select()}
|
|
112
|
+
/>
|
|
113
|
+
{#if i < 2}
|
|
114
|
+
<span class="dur-input__sep" aria-hidden="true">:</span>
|
|
115
|
+
{/if}
|
|
116
|
+
{/each}
|
|
117
|
+
</div>
|
|
118
|
+
</div>
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
export let title: string;
|
|
3
|
+
export let description: string | undefined = undefined;
|
|
4
|
+
let className = '';
|
|
5
|
+
export { className as class };
|
|
6
|
+
</script>
|
|
7
|
+
|
|
8
|
+
<div role="status" aria-live="polite" class="empty-state {className}">
|
|
9
|
+
<svg class="empty-state__icon" viewBox="0 0 48 48" fill="none" aria-hidden="true" focusable="false">
|
|
10
|
+
<rect x="6" y="11" width="36" height="28" rx="4" stroke="currentColor" stroke-width="1.5"/>
|
|
11
|
+
<path d="M6 30h10l3 5h10l3-5h10" stroke="currentColor" stroke-width="1.5" stroke-linejoin="round"/>
|
|
12
|
+
<path d="M15 20h18M15 25h12" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" opacity=".4"/>
|
|
13
|
+
</svg>
|
|
14
|
+
<p class="empty-state__title">{title}</p>
|
|
15
|
+
{#if description}
|
|
16
|
+
<p class="empty-state__desc">{description}</p>
|
|
17
|
+
{/if}
|
|
18
|
+
{#if $$slots.action}
|
|
19
|
+
<div class="empty-state__action"><slot name="action" /></div>
|
|
20
|
+
{/if}
|
|
21
|
+
</div>
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
export interface ErrorBandSeries {
|
|
3
|
+
label: string;
|
|
4
|
+
color: string;
|
|
5
|
+
mean: number[];
|
|
6
|
+
lower: number[];
|
|
7
|
+
upper: number[];
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export let series: ErrorBandSeries[] = [];
|
|
11
|
+
export let xLabels: string[] = [];
|
|
12
|
+
export let yRange: [number, number] | undefined = undefined;
|
|
13
|
+
export let height = 180;
|
|
14
|
+
export let unit = '';
|
|
15
|
+
export let ariaLabel = 'Error band chart';
|
|
16
|
+
|
|
17
|
+
const VW = 340;
|
|
18
|
+
const PAD_L = 28;
|
|
19
|
+
const PAD_R = 8;
|
|
20
|
+
const PAD_T = 8;
|
|
21
|
+
const PAD_B = 20;
|
|
22
|
+
const PLOT_W = VW - PAD_L - PAD_R;
|
|
23
|
+
|
|
24
|
+
$: n = series.length ? series[0].mean.length : 0;
|
|
25
|
+
$: PLOT_H = height - PAD_T - PAD_B;
|
|
26
|
+
$: allVals = series.flatMap((s) => [...s.lower, ...s.upper]);
|
|
27
|
+
$: minV = yRange ? yRange[0] : allVals.length ? Math.min(...allVals) : 0;
|
|
28
|
+
$: maxV = yRange ? yRange[1] : allVals.length ? Math.max(...allVals) : 1;
|
|
29
|
+
$: vRange = maxV - minV || 1;
|
|
30
|
+
$: yTicks = [minV, (minV + maxV) / 2, maxV];
|
|
31
|
+
|
|
32
|
+
function tx(i: number): number {
|
|
33
|
+
return PAD_L + (i / (n - 1 || 1)) * PLOT_W;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function ty(v: number): number {
|
|
37
|
+
return PAD_T + (1 - (v - minV) / vRange) * PLOT_H;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function bandPath(lower: number[], upper: number[]): string {
|
|
41
|
+
const top = upper.map((v, i) => `${i === 0 ? 'M' : 'L'}${tx(i).toFixed(1)},${ty(v).toFixed(1)}`).join(' ');
|
|
42
|
+
const bot = [...lower].reverse().map((v, i) => `L${tx(n - 1 - i).toFixed(1)},${ty(v).toFixed(1)}`).join(' ');
|
|
43
|
+
return `${top} ${bot} Z`;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function linePath(mean: number[]): string {
|
|
47
|
+
return mean.map((v, i) => `${i === 0 ? 'M' : 'L'}${tx(i).toFixed(1)},${ty(v).toFixed(1)}`).join(' ');
|
|
48
|
+
}
|
|
49
|
+
</script>
|
|
50
|
+
|
|
51
|
+
{#if series.length && n > 0}
|
|
52
|
+
<div role="img" aria-label={ariaLabel} style="width:100%;">
|
|
53
|
+
<svg viewBox="0 0 {VW} {height}" style="width:100%;height:auto;display:block;" aria-hidden="true" focusable="false">
|
|
54
|
+
{#each yTicks as v, i (i)}
|
|
55
|
+
<line x1={PAD_L} y1={ty(v)} x2={PAD_L + PLOT_W} y2={ty(v)} stroke="rgba(255,255,255,0.06)" stroke-width="1" />
|
|
56
|
+
<text x={PAD_L - 4} y={ty(v) + 4} fill="#6E7079" font-size="8" text-anchor="end" font-family="inherit">{Math.round(v)}{unit}</text>
|
|
57
|
+
{/each}
|
|
58
|
+
|
|
59
|
+
{#each series as s, si (si)}
|
|
60
|
+
<path d={bandPath(s.lower, s.upper)} fill={s.color} opacity="0.12" />
|
|
61
|
+
<path d={linePath(s.mean)} fill="none" stroke={s.color} stroke-width="1.5" stroke-linejoin="round" stroke-linecap="round" />
|
|
62
|
+
{/each}
|
|
63
|
+
|
|
64
|
+
{#each xLabels as lbl, i (i)}
|
|
65
|
+
<text x={PAD_L + (i / (xLabels.length - 1 || 1)) * PLOT_W} y={height - 4} fill="#6E7079" font-size="9" text-anchor="middle" font-family="inherit">{lbl}</text>
|
|
66
|
+
{/each}
|
|
67
|
+
</svg>
|
|
68
|
+
</div>
|
|
69
|
+
{/if}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
export let colSpan: number;
|
|
3
|
+
export let defaultExpanded: boolean = false;
|
|
4
|
+
|
|
5
|
+
let open = defaultExpanded;
|
|
6
|
+
const detailId = `exp-${Math.random().toString(36).slice(2, 9)}`;
|
|
7
|
+
</script>
|
|
8
|
+
|
|
9
|
+
<tbody class="exp-tbody">
|
|
10
|
+
<tr class="exp-row">
|
|
11
|
+
<td class="exp-toggle-cell">
|
|
12
|
+
<button
|
|
13
|
+
type="button"
|
|
14
|
+
class="exp-toggle"
|
|
15
|
+
aria-expanded={open}
|
|
16
|
+
aria-controls={detailId}
|
|
17
|
+
on:click={() => (open = !open)}
|
|
18
|
+
>
|
|
19
|
+
<svg
|
|
20
|
+
class="exp-chevron"
|
|
21
|
+
class:exp-chevron--open={open}
|
|
22
|
+
viewBox="0 0 10 6"
|
|
23
|
+
fill="none"
|
|
24
|
+
aria-hidden="true"
|
|
25
|
+
>
|
|
26
|
+
<path d="M1 1l4 4 4-4" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" />
|
|
27
|
+
</svg>
|
|
28
|
+
</button>
|
|
29
|
+
</td>
|
|
30
|
+
<slot name="row" />
|
|
31
|
+
</tr>
|
|
32
|
+
<tr
|
|
33
|
+
id={detailId}
|
|
34
|
+
class="exp-detail"
|
|
35
|
+
class:exp-detail--open={open}
|
|
36
|
+
aria-hidden={!open}
|
|
37
|
+
>
|
|
38
|
+
<td colspan={colSpan + 1} class="exp-detail-cell">
|
|
39
|
+
<slot name="detail" />
|
|
40
|
+
</td>
|
|
41
|
+
</tr>
|
|
42
|
+
</tbody>
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
<script context="module" lang="ts">
|
|
2
|
+
let counter = 0;
|
|
3
|
+
</script>
|
|
4
|
+
|
|
5
|
+
<script lang="ts">
|
|
6
|
+
export let label: string;
|
|
7
|
+
export let value: string = '';
|
|
8
|
+
export let id: string | undefined = undefined;
|
|
9
|
+
export let error: string | undefined = undefined;
|
|
10
|
+
export let required: boolean = false;
|
|
11
|
+
export let hint: string | undefined = undefined;
|
|
12
|
+
const inputId = id ?? `dash-ui-field-${++counter}`;
|
|
13
|
+
$: errorId = error ? `${inputId}-error` : undefined;
|
|
14
|
+
$: hintId = hint ? `${inputId}-hint` : undefined;
|
|
15
|
+
$: describedBy = [hintId, errorId].filter(Boolean).join(' ') || undefined;
|
|
16
|
+
</script>
|
|
17
|
+
|
|
18
|
+
<div class="field">
|
|
19
|
+
<label for={inputId}>
|
|
20
|
+
{label}{#if required}<span class="req" aria-hidden="true"> *</span>{/if}
|
|
21
|
+
</label>
|
|
22
|
+
{#if hint}
|
|
23
|
+
<div class="field-hint" id={hintId}>{hint}</div>
|
|
24
|
+
{/if}
|
|
25
|
+
{#if $$slots.default}
|
|
26
|
+
<slot />
|
|
27
|
+
{:else}
|
|
28
|
+
<input
|
|
29
|
+
class="input"
|
|
30
|
+
id={inputId}
|
|
31
|
+
bind:value
|
|
32
|
+
{required}
|
|
33
|
+
aria-invalid={error ? true : undefined}
|
|
34
|
+
aria-describedby={describedBy}
|
|
35
|
+
/>
|
|
36
|
+
{/if}
|
|
37
|
+
{#if error}
|
|
38
|
+
<div class="field-error" id={errorId} role="alert">{error}</div>
|
|
39
|
+
{/if}
|
|
40
|
+
</div>
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { createEventDispatcher } from 'svelte';
|
|
3
|
+
|
|
4
|
+
export let label = 'Upload file';
|
|
5
|
+
export let hint: string | undefined = undefined;
|
|
6
|
+
export let accept: string | undefined = undefined;
|
|
7
|
+
export let multiple = false;
|
|
8
|
+
export let disabled = false;
|
|
9
|
+
|
|
10
|
+
const dispatch = createEventDispatcher<{ files: File[] }>();
|
|
11
|
+
|
|
12
|
+
let drag = false;
|
|
13
|
+
let done: string | null = null;
|
|
14
|
+
let inputEl: HTMLInputElement;
|
|
15
|
+
|
|
16
|
+
function handleFiles(files: FileList | null) {
|
|
17
|
+
if (!files || files.length === 0) return;
|
|
18
|
+
const arr = Array.from(files);
|
|
19
|
+
done = arr.length === 1 ? arr[0].name : `${arr.length} files selected`;
|
|
20
|
+
dispatch('files', arr);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function onChange(e: Event) {
|
|
24
|
+
handleFiles((e.target as HTMLInputElement).files);
|
|
25
|
+
(e.target as HTMLInputElement).value = '';
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function onDragOver(e: DragEvent) {
|
|
29
|
+
e.preventDefault();
|
|
30
|
+
if (!disabled) drag = true;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function onDragLeave(e: DragEvent) {
|
|
34
|
+
if (!(e.currentTarget as HTMLElement).contains(e.relatedTarget as Node)) {
|
|
35
|
+
drag = false;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function onDrop(e: DragEvent) {
|
|
40
|
+
e.preventDefault();
|
|
41
|
+
drag = false;
|
|
42
|
+
if (!disabled) handleFiles(e.dataTransfer?.files ?? null);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function onClick() {
|
|
46
|
+
if (!disabled) inputEl.click();
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function onKeyDown(e: KeyboardEvent) {
|
|
50
|
+
if (e.key === 'Enter' || e.key === ' ') {
|
|
51
|
+
e.preventDefault();
|
|
52
|
+
if (!disabled) inputEl.click();
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
</script>
|
|
56
|
+
|
|
57
|
+
<div
|
|
58
|
+
role="button"
|
|
59
|
+
class="file-upload"
|
|
60
|
+
class:file-upload--drag={drag}
|
|
61
|
+
class:file-upload--done={done}
|
|
62
|
+
class:file-upload--disabled={disabled}
|
|
63
|
+
on:dragover={onDragOver}
|
|
64
|
+
on:dragleave={onDragLeave}
|
|
65
|
+
on:drop={onDrop}
|
|
66
|
+
on:click={onClick}
|
|
67
|
+
on:keydown={onKeyDown}
|
|
68
|
+
tabindex={disabled ? -1 : 0}
|
|
69
|
+
aria-label={done ?? label}
|
|
70
|
+
>
|
|
71
|
+
<input
|
|
72
|
+
bind:this={inputEl}
|
|
73
|
+
type="file"
|
|
74
|
+
{accept}
|
|
75
|
+
{multiple}
|
|
76
|
+
{disabled}
|
|
77
|
+
class="sr-only"
|
|
78
|
+
aria-label={label}
|
|
79
|
+
tabindex="-1"
|
|
80
|
+
on:change={onChange}
|
|
81
|
+
/>
|
|
82
|
+
<svg class="file-upload__icon" viewBox="0 0 24 24" aria-hidden="true" focusable="false">
|
|
83
|
+
{#if done}
|
|
84
|
+
<path d="M5 13l4 4L19 7" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" fill="none" />
|
|
85
|
+
{:else}
|
|
86
|
+
<path d="M12 15V3m0 0L8 7m4-4 4 4" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" fill="none" />
|
|
87
|
+
<path d="M3 15v4a2 2 0 002 2h14a2 2 0 002-2v-4" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" fill="none" />
|
|
88
|
+
{/if}
|
|
89
|
+
</svg>
|
|
90
|
+
<div>
|
|
91
|
+
<span class="file-upload__label">{done ?? label}</span>
|
|
92
|
+
{#if !done && hint}
|
|
93
|
+
<p class="file-upload__hint">{hint}</p>
|
|
94
|
+
{/if}
|
|
95
|
+
</div>
|
|
96
|
+
</div>
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
<script context="module" lang="ts">
|
|
2
|
+
let counter = 0;
|
|
3
|
+
export interface FilterField {
|
|
4
|
+
key: string;
|
|
5
|
+
label: string;
|
|
6
|
+
type?: 'text' | 'number' | 'select';
|
|
7
|
+
options?: string[];
|
|
8
|
+
}
|
|
9
|
+
export interface FilterRule {
|
|
10
|
+
id: string;
|
|
11
|
+
field: string;
|
|
12
|
+
op: string;
|
|
13
|
+
value: string;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const TEXT_OPS = ['contains', 'not contains', 'equals', 'not equals', 'starts with'];
|
|
17
|
+
const NUM_OPS = ['equals', 'not equals', 'greater than', 'less than', 'at least', 'at most'];
|
|
18
|
+
const SEL_OPS = ['is', 'is not'];
|
|
19
|
+
|
|
20
|
+
function opsFor(type?: string) {
|
|
21
|
+
if (type === 'number') return NUM_OPS;
|
|
22
|
+
if (type === 'select') return SEL_OPS;
|
|
23
|
+
return TEXT_OPS;
|
|
24
|
+
}
|
|
25
|
+
function defaultOp(type?: string) { return opsFor(type)[0]; }
|
|
26
|
+
</script>
|
|
27
|
+
|
|
28
|
+
<script lang="ts">
|
|
29
|
+
import { createEventDispatcher } from 'svelte';
|
|
30
|
+
|
|
31
|
+
export let fields: FilterField[] = [];
|
|
32
|
+
export let value: FilterRule[] = [];
|
|
33
|
+
export let conjunction: 'and' | 'or' = 'and';
|
|
34
|
+
export let ariaLabel = 'Filter builder';
|
|
35
|
+
|
|
36
|
+
const uid = `dash-ui-fb-${++counter}`;
|
|
37
|
+
const dispatch = createEventDispatcher<{ change: { rules: FilterRule[]; conjunction: 'and' | 'or' } }>();
|
|
38
|
+
|
|
39
|
+
function emit(rules: FilterRule[], conj: 'and' | 'or' = conjunction) {
|
|
40
|
+
dispatch('change', { rules, conjunction: conj });
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function addRule() {
|
|
44
|
+
const f = fields[0];
|
|
45
|
+
emit([...value, { id: String(Date.now()), field: f?.key ?? '', op: defaultOp(f?.type), value: '' }]);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function removeRule(id: string) {
|
|
49
|
+
emit(value.filter((r) => r.id !== id));
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function setField(id: string, key: string) {
|
|
53
|
+
const f = fields.find((x) => x.key === key);
|
|
54
|
+
emit(value.map((r) => r.id === id ? { ...r, field: key, op: defaultOp(f?.type), value: '' } : r));
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function setOp(id: string, op: string) {
|
|
58
|
+
emit(value.map((r) => r.id === id ? { ...r, op } : r));
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function setValue(id: string, v: string) {
|
|
62
|
+
emit(value.map((r) => r.id === id ? { ...r, value: v } : r));
|
|
63
|
+
}
|
|
64
|
+
</script>
|
|
65
|
+
|
|
66
|
+
<div class="fb" role="group" aria-label={ariaLabel}>
|
|
67
|
+
<div class="fb-header">
|
|
68
|
+
<span class="fb-label">Filters</span>
|
|
69
|
+
{#if value.length > 0}
|
|
70
|
+
<span class="fb-conj-label">Match</span>
|
|
71
|
+
<div class="fb-conj" role="group" aria-label="Conjunction">
|
|
72
|
+
{#each ['and', 'or'] as c}
|
|
73
|
+
<button
|
|
74
|
+
type="button"
|
|
75
|
+
class="fb-conj-btn{conjunction === c ? ' is-active' : ''}"
|
|
76
|
+
aria-pressed={conjunction === c}
|
|
77
|
+
on:click={() => emit(value, c)}
|
|
78
|
+
>{c.toUpperCase()}</button>
|
|
79
|
+
{/each}
|
|
80
|
+
</div>
|
|
81
|
+
<span class="fb-conj-label">conditions</span>
|
|
82
|
+
{/if}
|
|
83
|
+
<button type="button" class="fb-add" on:click={addRule} aria-label="Add filter rule">+ Add rule</button>
|
|
84
|
+
{#if value.length > 0}
|
|
85
|
+
<button type="button" class="fb-clear" on:click={() => emit([])} aria-label="Clear all filters">Clear</button>
|
|
86
|
+
{/if}
|
|
87
|
+
</div>
|
|
88
|
+
{#if value.length > 0}
|
|
89
|
+
<ul class="fb-rules" aria-label="Filter rules">
|
|
90
|
+
{#each value as rule, i}
|
|
91
|
+
{@const f = fields.find((x) => x.key === rule.field)}
|
|
92
|
+
{@const ops = opsFor(f?.type)}
|
|
93
|
+
{@const fieldId = `${uid}-f${i}`}
|
|
94
|
+
{@const opId = `${uid}-op${i}`}
|
|
95
|
+
{@const valId = `${uid}-v${i}`}
|
|
96
|
+
<li class="fb-rule">
|
|
97
|
+
<label class="sr-only" for={fieldId}>Field {i + 1}</label>
|
|
98
|
+
<select
|
|
99
|
+
id={fieldId}
|
|
100
|
+
class="fb-select"
|
|
101
|
+
value={rule.field}
|
|
102
|
+
on:change={(e) => setField(rule.id, e.currentTarget.value)}
|
|
103
|
+
aria-label="Rule {i + 1} field"
|
|
104
|
+
>
|
|
105
|
+
{#each fields as x}
|
|
106
|
+
<option value={x.key}>{x.label}</option>
|
|
107
|
+
{/each}
|
|
108
|
+
</select>
|
|
109
|
+
<label class="sr-only" for={opId}>Operator {i + 1}</label>
|
|
110
|
+
<select
|
|
111
|
+
id={opId}
|
|
112
|
+
class="fb-select fb-select--op"
|
|
113
|
+
value={rule.op}
|
|
114
|
+
on:change={(e) => setOp(rule.id, e.currentTarget.value)}
|
|
115
|
+
aria-label="Rule {i + 1} operator"
|
|
116
|
+
>
|
|
117
|
+
{#each ops as op}
|
|
118
|
+
<option value={op}>{op}</option>
|
|
119
|
+
{/each}
|
|
120
|
+
</select>
|
|
121
|
+
<label class="sr-only" for={valId}>Value {i + 1}</label>
|
|
122
|
+
{#if f?.type === 'select'}
|
|
123
|
+
<select
|
|
124
|
+
id={valId}
|
|
125
|
+
class="fb-select fb-select--val"
|
|
126
|
+
value={rule.value}
|
|
127
|
+
on:change={(e) => setValue(rule.id, e.currentTarget.value)}
|
|
128
|
+
aria-label="Rule {i + 1} value"
|
|
129
|
+
>
|
|
130
|
+
<option value="">Any</option>
|
|
131
|
+
{#each f.options ?? [] as o}
|
|
132
|
+
<option value={o}>{o}</option>
|
|
133
|
+
{/each}
|
|
134
|
+
</select>
|
|
135
|
+
{:else}
|
|
136
|
+
<input
|
|
137
|
+
id={valId}
|
|
138
|
+
class="fb-input"
|
|
139
|
+
type={f?.type === 'number' ? 'number' : 'text'}
|
|
140
|
+
value={rule.value}
|
|
141
|
+
placeholder="Value…"
|
|
142
|
+
on:input={(e) => setValue(rule.id, e.currentTarget.value)}
|
|
143
|
+
aria-label="Rule {i + 1} value"
|
|
144
|
+
/>
|
|
145
|
+
{/if}
|
|
146
|
+
<button
|
|
147
|
+
type="button"
|
|
148
|
+
class="fb-remove icon-btn"
|
|
149
|
+
on:click={() => removeRule(rule.id)}
|
|
150
|
+
aria-label="Remove rule {i + 1}"
|
|
151
|
+
>
|
|
152
|
+
<svg viewBox="0 0 12 12" width="10" height="10" aria-hidden="true" fill="currentColor">
|
|
153
|
+
<path d="M1 1l10 10M11 1L1 11" stroke="currentColor" stroke-width="1.8" stroke-linecap="round"/>
|
|
154
|
+
</svg>
|
|
155
|
+
</button>
|
|
156
|
+
</li>
|
|
157
|
+
{/each}
|
|
158
|
+
</ul>
|
|
159
|
+
{/if}
|
|
160
|
+
</div>
|