@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.
Files changed (195) hide show
  1. package/README.md +8 -0
  2. package/package.json +57 -0
  3. package/src/lib/components/Accordion.svelte +3 -0
  4. package/src/lib/components/AccordionItem.svelte +40 -0
  5. package/src/lib/components/ActionMenu.svelte +96 -0
  6. package/src/lib/components/ActivityFeed.svelte +60 -0
  7. package/src/lib/components/Alert.svelte +19 -0
  8. package/src/lib/components/AnnotatedTimeSeries.svelte +90 -0
  9. package/src/lib/components/ArcDiagram.svelte +74 -0
  10. package/src/lib/components/AreaChart.svelte +64 -0
  11. package/src/lib/components/Avatar.svelte +30 -0
  12. package/src/lib/components/AvatarGroup.svelte +35 -0
  13. package/src/lib/components/Badge.svelte +25 -0
  14. package/src/lib/components/Banner.svelte +37 -0
  15. package/src/lib/components/BarChart.svelte +52 -0
  16. package/src/lib/components/BeeswarmChart.svelte +88 -0
  17. package/src/lib/components/BoxPlot.svelte +66 -0
  18. package/src/lib/components/Breadcrumb.svelte +31 -0
  19. package/src/lib/components/BubbleChart.svelte +69 -0
  20. package/src/lib/components/BulletChart.svelte +70 -0
  21. package/src/lib/components/BumpChart.svelte +109 -0
  22. package/src/lib/components/Button.svelte +25 -0
  23. package/src/lib/components/CIDRInput.svelte +142 -0
  24. package/src/lib/components/CalendarHeatmap.svelte +86 -0
  25. package/src/lib/components/Callout.svelte +38 -0
  26. package/src/lib/components/CandlestickChart.svelte +66 -0
  27. package/src/lib/components/Card.svelte +11 -0
  28. package/src/lib/components/Carousel.svelte +64 -0
  29. package/src/lib/components/Checkbox.svelte +45 -0
  30. package/src/lib/components/ChordDiagram.svelte +90 -0
  31. package/src/lib/components/CirclePacking.svelte +119 -0
  32. package/src/lib/components/CodeBlock.svelte +49 -0
  33. package/src/lib/components/ColorPicker.svelte +91 -0
  34. package/src/lib/components/ColumnToggle.svelte +96 -0
  35. package/src/lib/components/Combobox.svelte +138 -0
  36. package/src/lib/components/CommandPalette.svelte +152 -0
  37. package/src/lib/components/ConfirmDialog.svelte +84 -0
  38. package/src/lib/components/ContextMenu.svelte +107 -0
  39. package/src/lib/components/ContextualHelp.svelte +73 -0
  40. package/src/lib/components/ContourPlot.svelte +164 -0
  41. package/src/lib/components/CopyButton.svelte +33 -0
  42. package/src/lib/components/CorrelationMatrix.svelte +90 -0
  43. package/src/lib/components/CountUp.svelte +49 -0
  44. package/src/lib/components/CumulativeDistribution.svelte +67 -0
  45. package/src/lib/components/DatePicker.svelte +201 -0
  46. package/src/lib/components/DateRangePicker.svelte +249 -0
  47. package/src/lib/components/Dendrogram.svelte +128 -0
  48. package/src/lib/components/Donut.svelte +49 -0
  49. package/src/lib/components/DotPlot.svelte +121 -0
  50. package/src/lib/components/Drawer.svelte +60 -0
  51. package/src/lib/components/DualAxisChart.svelte +80 -0
  52. package/src/lib/components/DumbbellChart.svelte +56 -0
  53. package/src/lib/components/DurationInput.svelte +118 -0
  54. package/src/lib/components/EmptyState.svelte +21 -0
  55. package/src/lib/components/ErrorBandChart.svelte +69 -0
  56. package/src/lib/components/ExpandableRow.svelte +42 -0
  57. package/src/lib/components/Field.svelte +40 -0
  58. package/src/lib/components/FileUpload.svelte +96 -0
  59. package/src/lib/components/FilterBuilder.svelte +160 -0
  60. package/src/lib/components/FlameGraph.svelte +88 -0
  61. package/src/lib/components/ForceGraph.svelte +104 -0
  62. package/src/lib/components/FunnelChart.svelte +50 -0
  63. package/src/lib/components/GanttChart.svelte +49 -0
  64. package/src/lib/components/Gauge.svelte +53 -0
  65. package/src/lib/components/GroupedList.svelte +87 -0
  66. package/src/lib/components/HealthBar.svelte +19 -0
  67. package/src/lib/components/HeatMap.svelte +64 -0
  68. package/src/lib/components/HexbinChart.svelte +100 -0
  69. package/src/lib/components/Histogram.svelte +93 -0
  70. package/src/lib/components/HorizonChart.svelte +71 -0
  71. package/src/lib/components/HoverCard.svelte +45 -0
  72. package/src/lib/components/IPInput.svelte +103 -0
  73. package/src/lib/components/IcicleChart.svelte +87 -0
  74. package/src/lib/components/IconButton.svelte +19 -0
  75. package/src/lib/components/InlineEdit.svelte +72 -0
  76. package/src/lib/components/Input.svelte +18 -0
  77. package/src/lib/components/InputGroup.svelte +14 -0
  78. package/src/lib/components/JsonViewer.svelte +15 -0
  79. package/src/lib/components/JsonViewerNode.svelte +59 -0
  80. package/src/lib/components/KVTable.svelte +18 -0
  81. package/src/lib/components/KanbanBoard.svelte +141 -0
  82. package/src/lib/components/Kbd.svelte +16 -0
  83. package/src/lib/components/LineChart.svelte +67 -0
  84. package/src/lib/components/LogViewer.svelte +84 -0
  85. package/src/lib/components/LollipopChart.svelte +79 -0
  86. package/src/lib/components/MACInput.svelte +98 -0
  87. package/src/lib/components/MarimekkoChart.svelte +108 -0
  88. package/src/lib/components/MatrixChart.svelte +55 -0
  89. package/src/lib/components/Menubar.svelte +131 -0
  90. package/src/lib/components/MirroredBarChart.svelte +120 -0
  91. package/src/lib/components/Modal.svelte +90 -0
  92. package/src/lib/components/MultiSelect.svelte +165 -0
  93. package/src/lib/components/NightingaleChart.svelte +63 -0
  94. package/src/lib/components/NotificationPanel.svelte +170 -0
  95. package/src/lib/components/NumberInput.svelte +93 -0
  96. package/src/lib/components/OTPInput.svelte +94 -0
  97. package/src/lib/components/Pagination.svelte +58 -0
  98. package/src/lib/components/ParallelCoordinates.svelte +98 -0
  99. package/src/lib/components/ParetoChart.svelte +88 -0
  100. package/src/lib/components/PasswordInput.svelte +62 -0
  101. package/src/lib/components/PieChart.svelte +91 -0
  102. package/src/lib/components/Pill.svelte +12 -0
  103. package/src/lib/components/PolarHeatmap.svelte +85 -0
  104. package/src/lib/components/Popover.svelte +89 -0
  105. package/src/lib/components/ProgressBar.svelte +29 -0
  106. package/src/lib/components/PunchCard.svelte +73 -0
  107. package/src/lib/components/QuadrantChart.svelte +122 -0
  108. package/src/lib/components/RadarChart.svelte +71 -0
  109. package/src/lib/components/RadialBarChart.svelte +76 -0
  110. package/src/lib/components/RadioGroup.svelte +48 -0
  111. package/src/lib/components/RangeSlider.svelte +68 -0
  112. package/src/lib/components/RankedList.svelte +49 -0
  113. package/src/lib/components/ResizablePanel.svelte +77 -0
  114. package/src/lib/components/RidgelinePlot.svelte +103 -0
  115. package/src/lib/components/RowToggle.svelte +19 -0
  116. package/src/lib/components/SankeyDiagram.svelte +100 -0
  117. package/src/lib/components/ScatterPlot.svelte +54 -0
  118. package/src/lib/components/SearchBox.svelte +11 -0
  119. package/src/lib/components/SegmentedControl.svelte +51 -0
  120. package/src/lib/components/Select.svelte +110 -0
  121. package/src/lib/components/SelectionToolbar.svelte +42 -0
  122. package/src/lib/components/Sidebar.svelte +44 -0
  123. package/src/lib/components/Signal.svelte +12 -0
  124. package/src/lib/components/Skeleton.svelte +23 -0
  125. package/src/lib/components/SkipLink.svelte +9 -0
  126. package/src/lib/components/Slider.svelte +48 -0
  127. package/src/lib/components/SlopeChart.svelte +91 -0
  128. package/src/lib/components/SortHeader.svelte +19 -0
  129. package/src/lib/components/SortableList.svelte +87 -0
  130. package/src/lib/components/Sparkline.svelte +32 -0
  131. package/src/lib/components/SparklineMatrix.svelte +84 -0
  132. package/src/lib/components/Spinner.svelte +6 -0
  133. package/src/lib/components/SplitButton.svelte +102 -0
  134. package/src/lib/components/Spoiler.svelte +41 -0
  135. package/src/lib/components/StackedBarChart.svelte +83 -0
  136. package/src/lib/components/StackedProgress.svelte +32 -0
  137. package/src/lib/components/StarRating.svelte +64 -0
  138. package/src/lib/components/Stat.svelte +24 -0
  139. package/src/lib/components/StatusIndicator.svelte +10 -0
  140. package/src/lib/components/StepChart.svelte +74 -0
  141. package/src/lib/components/Stepper.svelte +40 -0
  142. package/src/lib/components/StreamGraph.svelte +93 -0
  143. package/src/lib/components/StripeChart.svelte +52 -0
  144. package/src/lib/components/SunburstChart.svelte +80 -0
  145. package/src/lib/components/SwitchPortGrid.svelte +45 -0
  146. package/src/lib/components/TabPanel.svelte +17 -0
  147. package/src/lib/components/Tabs.svelte +45 -0
  148. package/src/lib/components/Tag.svelte +19 -0
  149. package/src/lib/components/TagInput.svelte +92 -0
  150. package/src/lib/components/Textarea.svelte +31 -0
  151. package/src/lib/components/ThresholdAreaChart.svelte +72 -0
  152. package/src/lib/components/TimePicker.svelte +116 -0
  153. package/src/lib/components/TimeRange.svelte +104 -0
  154. package/src/lib/components/Timeline.svelte +32 -0
  155. package/src/lib/components/Toast.svelte +52 -0
  156. package/src/lib/components/Toggle.svelte +23 -0
  157. package/src/lib/components/ToggleGroup.svelte +32 -0
  158. package/src/lib/components/Tooltip.svelte +16 -0
  159. package/src/lib/components/Topbar.svelte +63 -0
  160. package/src/lib/components/TransferList.svelte +184 -0
  161. package/src/lib/components/TreeItem.svelte +61 -0
  162. package/src/lib/components/TreeMap.svelte +90 -0
  163. package/src/lib/components/TreeView.svelte +130 -0
  164. package/src/lib/components/UptimeTimeline.svelte +86 -0
  165. package/src/lib/components/VennDiagram.svelte +91 -0
  166. package/src/lib/components/ViolinPlot.svelte +114 -0
  167. package/src/lib/components/VirtualList.svelte +38 -0
  168. package/src/lib/components/WaffleChart.svelte +68 -0
  169. package/src/lib/components/WaterfallChart.svelte +96 -0
  170. package/src/lib/components/WordCloud.svelte +103 -0
  171. package/src/lib/icons/AlarmIcon.svelte +5 -0
  172. package/src/lib/icons/BellIcon.svelte +6 -0
  173. package/src/lib/icons/CaretIcon.svelte +8 -0
  174. package/src/lib/icons/ClientsIcon.svelte +5 -0
  175. package/src/lib/icons/CloseIcon.svelte +6 -0
  176. package/src/lib/icons/DashboardIcon.svelte +7 -0
  177. package/src/lib/icons/DevicesIcon.svelte +5 -0
  178. package/src/lib/icons/DownloadIcon.svelte +6 -0
  179. package/src/lib/icons/HelpIcon.svelte +7 -0
  180. package/src/lib/icons/InfraIcon.svelte +7 -0
  181. package/src/lib/icons/IntegrationsIcon.svelte +5 -0
  182. package/src/lib/icons/LogsIcon.svelte +6 -0
  183. package/src/lib/icons/PlusIcon.svelte +6 -0
  184. package/src/lib/icons/PortsIcon.svelte +6 -0
  185. package/src/lib/icons/SearchIcon.svelte +10 -0
  186. package/src/lib/icons/SecurityIcon.svelte +4 -0
  187. package/src/lib/icons/SettingsIcon.svelte +5 -0
  188. package/src/lib/icons/TopologyIcon.svelte +7 -0
  189. package/src/lib/icons/UpdatesIcon.svelte +6 -0
  190. package/src/lib/icons/VpnIcon.svelte +5 -0
  191. package/src/lib/icons/WifiIcon.svelte +5 -0
  192. package/src/lib/icons/WirelessIcon.svelte +4 -0
  193. package/src/lib/icons.ts +22 -0
  194. package/src/lib/index.ts +275 -0
  195. package/src/lib/styles.css +2 -0
@@ -0,0 +1,91 @@
1
+ <script lang="ts">
2
+ export interface PieSlice {
3
+ label: string;
4
+ value: number;
5
+ color?: string;
6
+ }
7
+
8
+ export let slices: PieSlice[] = [];
9
+ export let size: number = 140;
10
+ export let ariaLabel: string = 'Pie chart';
11
+
12
+ const COLORS = ['#006FFF', '#00C8C8', '#F5A623', '#A878F5', '#FF6B6B', '#00C875', '#7FB6FF', '#6E7079'];
13
+ const TAU = 2 * Math.PI;
14
+ const START = -Math.PI / 2;
15
+
16
+ function arcPath(cx: number, cy: number, r: number, a0: number, a1: number): string {
17
+ const x0 = cx + r * Math.cos(a0);
18
+ const y0 = cy + r * Math.sin(a0);
19
+ const x1 = cx + r * Math.cos(a1);
20
+ const y1 = cy + r * Math.sin(a1);
21
+ const large = a1 - a0 > Math.PI ? 1 : 0;
22
+ return `M ${cx} ${cy} L ${x0.toFixed(2)} ${y0.toFixed(2)} A ${r} ${r} 0 ${large} 1 ${x1.toFixed(2)} ${y1.toFixed(2)} Z`;
23
+ }
24
+
25
+ $: total = slices.reduce((s, sl) => s + sl.value, 0) || 1;
26
+ $: cx = size / 2;
27
+ $: cy = size / 2;
28
+ $: r = (size / 2) * 0.82;
29
+ $: LEGEND_ROW = 18;
30
+ $: legendH = Math.ceil(slices.length / 2) * LEGEND_ROW + 6;
31
+ $: VH = size + legendH;
32
+
33
+ $: segments = (() => {
34
+ const result: { path: string; color: string; mid: number }[] = [];
35
+ let angle = START;
36
+ for (let i = 0; i < slices.length; i++) {
37
+ const sl = slices[i];
38
+ const span = (sl.value / total) * TAU;
39
+ const color = sl.color ?? COLORS[i % COLORS.length];
40
+ result.push({ path: arcPath(cx, cy, r, angle, angle + span), color, mid: angle + span / 2 });
41
+ angle += span;
42
+ }
43
+ return result;
44
+ })();
45
+ </script>
46
+
47
+ <div role="img" aria-label={ariaLabel} style="width:100%;">
48
+ <svg
49
+ viewBox="0 0 {size} {VH}"
50
+ style="width:100%;height:auto;display:block;"
51
+ aria-hidden="true"
52
+ focusable="false"
53
+ >
54
+ {#each segments as seg, i (i)}
55
+ <path d={seg.path} fill={seg.color} stroke="#1a1d2b" stroke-width="1.5" />
56
+ {/each}
57
+
58
+ {#each slices as sl, i (sl.label)}
59
+ {@const pct = Math.round((sl.value / total) * 100)}
60
+ {#if pct >= 5}
61
+ {@const mid = segments[i].mid}
62
+ {@const lr = r * 0.6}
63
+ <text
64
+ x={(cx + lr * Math.cos(mid)).toFixed(1)}
65
+ y={(cy + lr * Math.sin(mid)).toFixed(1)}
66
+ fill="#fff"
67
+ font-size="9"
68
+ text-anchor="middle"
69
+ dominant-baseline="middle"
70
+ font-family="inherit"
71
+ font-weight="600"
72
+ >{pct}%</text>
73
+ {/if}
74
+ {/each}
75
+
76
+ {#each slices as sl, i (sl.label + 'leg')}
77
+ {@const cols = Math.ceil(slices.length / 2)}
78
+ {@const col = Math.floor(i / cols)}
79
+ {@const row = i % cols}
80
+ {@const colW = size / 2}
81
+ {@const lx = col * colW + 10}
82
+ {@const ly = size + 6 + row * LEGEND_ROW}
83
+ {@const c = sl.color ?? COLORS[i % COLORS.length]}
84
+ <g>
85
+ <rect x={lx} y={ly + 2} width="8" height="8" rx="2" fill={c} />
86
+ <text x={lx + 12} y={ly + 9} fill="#A4A7B5" font-size="9" font-family="inherit"
87
+ >{sl.label} ({sl.value})</text>
88
+ </g>
89
+ {/each}
90
+ </svg>
91
+ </div>
@@ -0,0 +1,12 @@
1
+ <script lang="ts">
2
+ export let variant: 'success' | 'warn' | 'danger' | 'info' | 'neutral' = 'neutral';
3
+ export let showDot: boolean = true;
4
+ let className = '';
5
+ export { className as class };
6
+ export let style: string = '';
7
+ </script>
8
+
9
+ <span class="pill pill-{variant} {className}" {style}>
10
+ {#if showDot}<span class="dot" aria-hidden="true"></span>{/if}
11
+ <slot />
12
+ </span>
@@ -0,0 +1,85 @@
1
+ <script lang="ts">
2
+ export interface PolarCell {
3
+ row: number;
4
+ col: number;
5
+ value: number;
6
+ }
7
+
8
+ export let data: PolarCell[] = [];
9
+ export let rows: number = 7;
10
+ export let cols: number = 24;
11
+ export let colLabels: string[] = [];
12
+ export let color: string = '#006FFF';
13
+ export let ariaLabel: string = 'Polar heatmap';
14
+
15
+ const SIZE = 280;
16
+ const CX = SIZE / 2;
17
+ const CY = SIZE / 2;
18
+ const MIN_R = 24;
19
+ const MAX_R = 110;
20
+ const BASE = -Math.PI / 2;
21
+
22
+ function hexToRgb(hex: string): [number, number, number] {
23
+ const h = hex.replace('#', '');
24
+ return [parseInt(h.slice(0, 2), 16), parseInt(h.slice(2, 4), 16), parseInt(h.slice(4, 6), 16)];
25
+ }
26
+
27
+ function annularSector(ir: number, or: number, sa: number, ea: number): string {
28
+ const { cos, sin } = Math;
29
+ const f = (v: number) => v.toFixed(2);
30
+ return [
31
+ `M ${f(CX + ir * cos(sa))} ${f(CY + ir * sin(sa))}`,
32
+ `A ${f(ir)} ${f(ir)} 0 0 1 ${f(CX + ir * cos(ea))} ${f(CY + ir * sin(ea))}`,
33
+ `L ${f(CX + or * cos(ea))} ${f(CY + or * sin(ea))}`,
34
+ `A ${f(or)} ${f(or)} 0 0 0 ${f(CX + or * cos(sa))} ${f(CY + or * sin(sa))}`,
35
+ 'Z',
36
+ ].join(' ');
37
+ }
38
+
39
+ $: maxVal = data.length ? Math.max(...data.map((d) => d.value), 1) : 1;
40
+ $: ringH = (MAX_R - MIN_R) / rows;
41
+ $: segA = (2 * Math.PI) / cols;
42
+ $: [rC, gC, bC] = hexToRgb(color);
43
+ $: LABEL_R = MAX_R + 14;
44
+ $: labelEvery = cols <= 12 ? 1 : cols <= 24 ? 3 : Math.round(cols / 8);
45
+ $: rings = Array.from({ length: rows + 1 }, (_, i) => +(MIN_R + i * ringH).toFixed(2));
46
+ $: cells = data.map((cell) => {
47
+ const ir = MIN_R + cell.row * ringH;
48
+ const or = ir + ringH - 0.5;
49
+ const sa = BASE + cell.col * segA + 0.01;
50
+ const ea = BASE + (cell.col + 1) * segA - 0.01;
51
+ const norm = cell.value / maxVal;
52
+ return {
53
+ d: annularSector(ir, or, sa, ea),
54
+ fill: `rgba(${rC},${gC},${bC},${(0.08 + 0.92 * norm).toFixed(3)})`,
55
+ };
56
+ });
57
+ $: visibleColLabels = colLabels
58
+ .map((label, i) => {
59
+ if (i % labelEvery !== 0) return null;
60
+ const a = BASE + (i + 0.5) * segA;
61
+ return {
62
+ x: (CX + LABEL_R * Math.cos(a)).toFixed(1),
63
+ y: (CY + LABEL_R * Math.sin(a)).toFixed(1),
64
+ label,
65
+ };
66
+ })
67
+ .filter(Boolean) as { x: string; y: string; label: string }[];
68
+ </script>
69
+
70
+ {#if data.length}
71
+ <div role="img" aria-label={ariaLabel} style="width:100%;">
72
+ <svg viewBox="0 0 {SIZE} {SIZE}" style="width:100%;height:auto;display:block;" aria-hidden="true" focusable="false">
73
+ {#each rings as r, i (i)}
74
+ <circle cx={CX} cy={CY} r={r} fill="none" stroke="rgba(255,255,255,0.05)" stroke-width="0.5" />
75
+ {/each}
76
+ {#each cells as cell, i (i)}
77
+ <path d={cell.d} fill={cell.fill} />
78
+ {/each}
79
+ {#each visibleColLabels as lbl, i (i)}
80
+ <text x={lbl.x} y={lbl.y} fill="#6E7079" font-size="7" text-anchor="middle" dominant-baseline="middle" font-family="inherit">{lbl.label}</text>
81
+ {/each}
82
+ <circle cx={CX} cy={CY} r={MIN_R} fill="#13131A" stroke="rgba(255,255,255,0.08)" stroke-width="0.5" />
83
+ </svg>
84
+ </div>
85
+ {/if}
@@ -0,0 +1,89 @@
1
+ <script context="module" lang="ts">
2
+ let counter = 0;
3
+ </script>
4
+
5
+ <script lang="ts">
6
+ import { onMount, onDestroy, tick } from 'svelte';
7
+
8
+ export let label: string;
9
+ export let variant: 'ghost' | 'primary' = 'ghost';
10
+ export let title: string | undefined = undefined;
11
+ export let placement: 'bottom-start' | 'bottom-end' | 'bottom' = 'bottom-start';
12
+
13
+ const uid = `dash-ui-pop-${++counter}`;
14
+ const titleId = `${uid}-title`;
15
+
16
+ const FOCUSABLE =
17
+ 'a[href],button:not([disabled]),input:not([disabled]),select:not([disabled]),textarea:not([disabled]),[tabindex]:not([tabindex="-1"])';
18
+
19
+ let open = false;
20
+ let rootEl: HTMLDivElement;
21
+ let triggerEl: HTMLButtonElement;
22
+ let panelEl: HTMLDivElement;
23
+
24
+ async function toggle() {
25
+ if (!open) {
26
+ open = true;
27
+ await tick();
28
+ const first = panelEl?.querySelector<HTMLElement>(FOCUSABLE);
29
+ (first ?? panelEl)?.focus();
30
+ } else {
31
+ open = false;
32
+ }
33
+ }
34
+
35
+ function onKey(e: KeyboardEvent) {
36
+ if (!open) return;
37
+ if (e.key === 'Escape') {
38
+ e.preventDefault();
39
+ open = false;
40
+ triggerEl?.focus();
41
+ return;
42
+ }
43
+ if (e.key === 'Tab' && panelEl) {
44
+ const items = Array.from(panelEl.querySelectorAll<HTMLElement>(FOCUSABLE));
45
+ if (!items.length) { e.preventDefault(); return; }
46
+ const first = items[0]!;
47
+ const last = items[items.length - 1]!;
48
+ const active = document.activeElement;
49
+ if (e.shiftKey && active === first) { e.preventDefault(); last.focus(); }
50
+ else if (!e.shiftKey && active === last) { e.preventDefault(); first.focus(); }
51
+ }
52
+ }
53
+
54
+ function onPointer(e: PointerEvent) {
55
+ if (open && !rootEl?.contains(e.target as Node)) open = false;
56
+ }
57
+
58
+ onMount(() => {
59
+ window.addEventListener('keydown', onKey);
60
+ document.addEventListener('pointerdown', onPointer);
61
+ });
62
+ onDestroy(() => {
63
+ window.removeEventListener('keydown', onKey);
64
+ document.removeEventListener('pointerdown', onPointer);
65
+ });
66
+ </script>
67
+
68
+ <div class="popover-root" bind:this={rootEl}>
69
+ <button
70
+ bind:this={triggerEl}
71
+ type="button"
72
+ class="btn btn-{variant}"
73
+ aria-haspopup="dialog"
74
+ aria-expanded={open}
75
+ on:click={toggle}
76
+ >{label}</button>
77
+ {#if open}
78
+ <div
79
+ bind:this={panelEl}
80
+ role="dialog"
81
+ aria-labelledby={title ? titleId : undefined}
82
+ class="popover popover-{placement}"
83
+ tabindex="-1"
84
+ >
85
+ {#if title}<div id={titleId} class="popover-h">{title}</div>{/if}
86
+ <div class="popover-b"><slot /></div>
87
+ </div>
88
+ {/if}
89
+ </div>
@@ -0,0 +1,29 @@
1
+ <script lang="ts">
2
+ export let value: number;
3
+ export let label: string | undefined = undefined;
4
+ export let valueText: string | undefined = undefined;
5
+ export let color: string = '#006FFF';
6
+ $: clamped = Math.max(0, Math.min(100, value));
7
+ $: displayed = valueText ?? `${Math.round(clamped)}%`;
8
+ $: hasHeader = label !== undefined || valueText !== undefined;
9
+ </script>
10
+
11
+ <div class="pb">
12
+ {#if hasHeader}
13
+ <div class="pb-header">
14
+ {#if label}<span class="pb-label">{label}</span>{/if}
15
+ <span class="pb-value">{displayed}</span>
16
+ </div>
17
+ {/if}
18
+ <div
19
+ class="pb-track"
20
+ role="progressbar"
21
+ aria-valuenow={Math.round(clamped)}
22
+ aria-valuemin={0}
23
+ aria-valuemax={100}
24
+ aria-label={label}
25
+ aria-valuetext={displayed}
26
+ >
27
+ <div class="pb-fill" style="width:{clamped}%;background:{color};" />
28
+ </div>
29
+ </div>
@@ -0,0 +1,73 @@
1
+ <script context="module">
2
+ let _n = 0;
3
+ </script>
4
+ <script lang="ts">
5
+ const _uid = `pc${_n++}`;
6
+
7
+ export let data: number[][];
8
+ export let rowLabels: string[] | undefined = undefined;
9
+ export let colLabels: string[] | undefined = undefined;
10
+ export let color: string = '#006FFF';
11
+ export let height: number = 180;
12
+ export let ariaLabel: string = 'Punch card chart';
13
+
14
+ const VW = 400;
15
+ const PAD = { t: 16, r: 8, b: 28, l: 28 };
16
+ const DEFAULT_ROWS = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'];
17
+
18
+ $: rows = data.length;
19
+ $: cols = data[0]?.length ?? 0;
20
+ $: chartW = VW - PAD.l - PAD.r;
21
+ $: chartH = height - PAD.t - PAD.b;
22
+ $: cellW = chartW / (cols || 1);
23
+ $: cellH = chartH / (rows || 1);
24
+ $: maxR = Math.min(cellW, cellH) / 2 * 0.85;
25
+ $: maxVal = Math.max(...data.flat(), 1);
26
+ $: rLabels = rowLabels ?? DEFAULT_ROWS.slice(0, rows);
27
+ $: cLabels = colLabels ?? Array.from({ length: cols }, (_, i) =>
28
+ i % 6 === 0 ? String(i).padStart(2, '0') : ''
29
+ );
30
+ $: dots = data.flatMap((row, ri) =>
31
+ row.map((val, ci) => ({
32
+ ri, ci,
33
+ r: Math.sqrt(val / maxVal) * maxR,
34
+ cx: PAD.l + (ci + 0.5) * cellW,
35
+ cy: PAD.t + (ri + 0.5) * cellH,
36
+ }))
37
+ ).filter(d => d.r >= 0.4);
38
+ </script>
39
+
40
+ <div role="img" aria-label={ariaLabel} style="width:100%">
41
+ <svg
42
+ viewBox="0 0 {VW} {height}"
43
+ style="width:100%;height:auto;display:block"
44
+ aria-hidden="true"
45
+ focusable="false"
46
+ >
47
+ {#each rLabels as lbl, ri (ri)}
48
+ <text
49
+ x={PAD.l - 4}
50
+ y={PAD.t + (ri + 0.5) * cellH + 3}
51
+ font-size="8"
52
+ fill="#6E7079"
53
+ text-anchor="end"
54
+ font-family="inherit"
55
+ >{lbl}</text>
56
+ {/each}
57
+ {#each cLabels as lbl, ci (ci)}
58
+ {#if lbl}
59
+ <text
60
+ x={PAD.l + (ci + 0.5) * cellW}
61
+ y={height - 6}
62
+ font-size="7"
63
+ fill="#6E7079"
64
+ text-anchor="middle"
65
+ font-family="inherit"
66
+ >{lbl}</text>
67
+ {/if}
68
+ {/each}
69
+ {#each dots as { cx, cy, r, ri, ci } (`${ri}-${ci}`)}
70
+ <circle {cx} {cy} {r} fill={color} fill-opacity="0.72" />
71
+ {/each}
72
+ </svg>
73
+ </div>
@@ -0,0 +1,122 @@
1
+ <script lang="ts">
2
+ export interface QuadrantPoint {
3
+ x: number;
4
+ y: number;
5
+ label?: string;
6
+ color?: string;
7
+ }
8
+
9
+ export let points: QuadrantPoint[] = [];
10
+ export let xThreshold: number;
11
+ export let yThreshold: number;
12
+ export let xRange: [number, number] | undefined = undefined;
13
+ export let yRange: [number, number] | undefined = undefined;
14
+ /** Labels: [top-left, top-right, bottom-left, bottom-right] */
15
+ export let quadrantLabels: [string, string, string, string] = ['', '', '', ''];
16
+ export let quadrantColors: [string, string, string, string] = [
17
+ 'rgba(0,200,117,0.06)',
18
+ 'rgba(245,166,35,0.07)',
19
+ 'rgba(0,111,255,0.06)',
20
+ 'rgba(255,123,123,0.09)',
21
+ ];
22
+ export let xLabel: string | undefined = undefined;
23
+ export let yLabel: string | undefined = undefined;
24
+ export let height = 200;
25
+ export let ariaLabel = 'Quadrant chart';
26
+
27
+ const VW = 360;
28
+ const PAD = { t: 8, r: 8, b: 28, l: 36 };
29
+
30
+ $: xs = points.map((p) => p.x);
31
+ $: ys = points.map((p) => p.y);
32
+ $: x0 = xRange ? xRange[0] : Math.min(...xs, xThreshold - 1);
33
+ $: x1 = xRange ? xRange[1] : Math.max(...xs, xThreshold + 1);
34
+ $: y0 = yRange ? yRange[0] : Math.min(...ys, yThreshold - 1);
35
+ $: y1 = yRange ? yRange[1] : Math.max(...ys, yThreshold + 1);
36
+ $: xSpan = x1 - x0 || 1;
37
+ $: ySpan = y1 - y0 || 1;
38
+ $: cw = VW - PAD.l - PAD.r;
39
+ $: ch = height - PAD.t - PAD.b;
40
+
41
+ function toX(x: number) { return PAD.l + ((x - x0) / xSpan) * cw; }
42
+ function toY(y: number) { return PAD.t + ch - ((y - y0) / ySpan) * ch; }
43
+
44
+ $: tx = toX(xThreshold);
45
+ $: ty = toY(yThreshold);
46
+ $: xTicks = [x0, xThreshold, x1];
47
+ $: yTicks = [y0, yThreshold, y1];
48
+
49
+ $: dots = points.map((p) => ({
50
+ cx: toX(p.x),
51
+ cy: toY(p.y),
52
+ color: p.color ?? '#006FFF',
53
+ label: p.label,
54
+ }));
55
+ </script>
56
+
57
+ {#if points.length > 0}
58
+ <div role="img" aria-label={ariaLabel} style="width:100%">
59
+ <svg viewBox="0 0 {VW} {height}" style="width:100%;height:auto;display:block" aria-hidden="true" focusable="false">
60
+ <!-- Quadrant zones -->
61
+ <rect x={PAD.l} y={PAD.t} width={tx - PAD.l} height={ty - PAD.t} fill={quadrantColors[0]} />
62
+ <rect x={tx} y={PAD.t} width={PAD.l + cw - tx} height={ty - PAD.t} fill={quadrantColors[1]} />
63
+ <rect x={PAD.l} y={ty} width={tx - PAD.l} height={PAD.t + ch - ty} fill={quadrantColors[2]} />
64
+ <rect x={tx} y={ty} width={PAD.l + cw - tx} height={PAD.t + ch - ty} fill={quadrantColors[3]} />
65
+
66
+ <!-- Grid border -->
67
+ <rect x={PAD.l} y={PAD.t} width={cw} height={ch} fill="none" stroke="rgba(255,255,255,0.08)" stroke-width="1" />
68
+
69
+ <!-- Threshold lines -->
70
+ <line x1={tx} y1={PAD.t} x2={tx} y2={PAD.t + ch} stroke="rgba(255,255,255,0.22)" stroke-width="1" stroke-dasharray="4 3" />
71
+ <line x1={PAD.l} y1={ty} x2={PAD.l + cw} y2={ty} stroke="rgba(255,255,255,0.22)" stroke-width="1" stroke-dasharray="4 3" />
72
+
73
+ <!-- Quadrant labels -->
74
+ {#if quadrantLabels[0]}
75
+ <text x={PAD.l + 5} y={PAD.t + 11} fill="rgba(255,255,255,0.22)" font-size="9" font-family="inherit">{quadrantLabels[0]}</text>
76
+ {/if}
77
+ {#if quadrantLabels[1]}
78
+ <text x={PAD.l + cw - 5} y={PAD.t + 11} fill="rgba(255,255,255,0.22)" font-size="9" font-family="inherit" text-anchor="end">{quadrantLabels[1]}</text>
79
+ {/if}
80
+ {#if quadrantLabels[2]}
81
+ <text x={PAD.l + 5} y={PAD.t + ch - 5} fill="rgba(255,255,255,0.22)" font-size="9" font-family="inherit">{quadrantLabels[2]}</text>
82
+ {/if}
83
+ {#if quadrantLabels[3]}
84
+ <text x={PAD.l + cw - 5} y={PAD.t + ch - 5} fill="rgba(255,255,255,0.22)" font-size="9" font-family="inherit" text-anchor="end">{quadrantLabels[3]}</text>
85
+ {/if}
86
+
87
+ <!-- Y-axis ticks -->
88
+ {#each yTicks as v, i (i)}
89
+ <text x={PAD.l - 4} y={toY(v) + 3} fill="#6E7079" font-size="8" text-anchor="end" font-family="inherit">{Math.round(v)}</text>
90
+ {/each}
91
+
92
+ <!-- X-axis ticks -->
93
+ {#each xTicks as v, i (i)}
94
+ <text x={toX(v)} y={height - 4} fill="#6E7079" font-size="8" text-anchor="middle" font-family="inherit">{Math.round(v)}</text>
95
+ {/each}
96
+
97
+ <!-- Axis labels -->
98
+ {#if xLabel}
99
+ <text x={PAD.l + cw / 2} y={height - 2} fill="#6E7079" font-size="8" text-anchor="middle" font-family="inherit">{xLabel}</text>
100
+ {/if}
101
+ {#if yLabel}
102
+ <text
103
+ x={8}
104
+ y={PAD.t + ch / 2}
105
+ fill="#6E7079"
106
+ font-size="8"
107
+ text-anchor="middle"
108
+ font-family="inherit"
109
+ transform="rotate(-90, 8, {PAD.t + ch / 2})"
110
+ >{yLabel}</text>
111
+ {/if}
112
+
113
+ <!-- Points -->
114
+ {#each dots as { cx, cy, color, label }, i (i)}
115
+ <circle {cx} {cy} r="5" fill={color} fill-opacity="0.85" />
116
+ {#if label}
117
+ <text x={cx + 7} y={cy + 3} fill="#CDD0DB" font-size="8" font-family="inherit">{label}</text>
118
+ {/if}
119
+ {/each}
120
+ </svg>
121
+ </div>
122
+ {/if}
@@ -0,0 +1,71 @@
1
+ <script lang="ts">
2
+ export interface RadarSeries {
3
+ label: string;
4
+ color: string;
5
+ values: number[];
6
+ }
7
+
8
+ export let series: RadarSeries[] = [];
9
+ export let axes: string[] = [];
10
+ export let height: number = 200;
11
+ export let ariaLabel: string = 'Radar chart';
12
+
13
+ const VW = 400;
14
+ const PAD = 36;
15
+
16
+ function pt(cx: number, cy: number, r: number, angle: number): [number, number] {
17
+ return [cx + r * Math.cos(angle), cy + r * Math.sin(angle)];
18
+ }
19
+
20
+ function makePts(coords: [number, number][]): string {
21
+ return coords.map(([x, y]) => `${x.toFixed(1)},${y.toFixed(1)}`).join(' ');
22
+ }
23
+
24
+ $: n = axes.length;
25
+ $: cx = VW / 2;
26
+ $: cy = height / 2;
27
+ $: R = Math.min(cx, cy) - PAD;
28
+ $: angles = axes.map((_, i) => (2 * Math.PI * i) / n - Math.PI / 2);
29
+
30
+ $: rings = n >= 3
31
+ ? [0.25, 0.5, 0.75, 1.0].map((f) => makePts(angles.map((a) => pt(cx, cy, R * f, a))))
32
+ : [];
33
+
34
+ $: axisLines = n >= 3
35
+ ? angles.map((a) => { const [x, y] = pt(cx, cy, R, a); return { x: x.toFixed(1), y: y.toFixed(1) }; })
36
+ : [];
37
+
38
+ $: axisLabels = n >= 3
39
+ ? axes.map((label, i) => {
40
+ const [x, y] = pt(cx, cy, R + 16, angles[i]);
41
+ const cos = Math.cos(angles[i]);
42
+ const anchor = Math.abs(cos) < 0.3 ? 'middle' : cos < 0 ? 'end' : 'start';
43
+ return { x: x.toFixed(1), y: y.toFixed(1), label, anchor };
44
+ })
45
+ : [];
46
+
47
+ $: polygons = n >= 3
48
+ ? series.map((s) => ({
49
+ pts: makePts(angles.map((a, i) => pt(cx, cy, R * Math.max(0, Math.min(1, s.values[i] ?? 0)), a))),
50
+ color: s.color,
51
+ label: s.label,
52
+ }))
53
+ : [];
54
+ </script>
55
+
56
+ <div role="img" aria-label={ariaLabel} style="width:100%;">
57
+ <svg viewBox="0 0 {VW} {height}" style="width:100%;height:auto;display:block;" aria-hidden="true" focusable="false">
58
+ {#each rings as p}
59
+ <polygon points={p} fill="none" stroke="rgba(255,255,255,0.06)" stroke-width="1" />
60
+ {/each}
61
+ {#each axisLines as end}
62
+ <line x1={cx} y1={cy} x2={end.x} y2={end.y} stroke="rgba(255,255,255,0.08)" stroke-width="1" />
63
+ {/each}
64
+ {#each polygons as p (p.label)}
65
+ <polygon points={p.pts} fill={p.color} fill-opacity="0.12" stroke={p.color} stroke-width="1.5" stroke-linejoin="round" />
66
+ {/each}
67
+ {#each axisLabels as l (l.label)}
68
+ <text x={l.x} y={l.y} text-anchor={l.anchor} fill="#6E7079" font-size="9" font-family="inherit" dominant-baseline="middle">{l.label}</text>
69
+ {/each}
70
+ </svg>
71
+ </div>
@@ -0,0 +1,76 @@
1
+ <script lang="ts">
2
+ export interface RadialBarItem {
3
+ label: string;
4
+ value: number;
5
+ max: number;
6
+ color?: string;
7
+ unit?: string;
8
+ }
9
+
10
+ export let items: RadialBarItem[] = [];
11
+ export let ariaLabel = 'Radial bar chart';
12
+
13
+ const W = 280;
14
+ const H = 160;
15
+ const CX = 80;
16
+ const CY = 80;
17
+ const RING_W = 9;
18
+ const RING_GAP = 5;
19
+ const MIN_R = 20;
20
+ const LABEL_X = 170;
21
+ const ITEM_H = 22;
22
+
23
+ function arcPath(r: number, frac: number): string {
24
+ if (frac <= 0) return '';
25
+ const start = -Math.PI / 2;
26
+ const clamped = Math.min(frac, 1);
27
+ const x1 = (CX + r * Math.cos(start)).toFixed(2);
28
+ const y1 = (CY + r * Math.sin(start)).toFixed(2);
29
+ if (clamped >= 1) {
30
+ const xm = (CX + r * Math.cos(start + Math.PI)).toFixed(2);
31
+ const ym = (CY + r * Math.sin(start + Math.PI)).toFixed(2);
32
+ return `M${x1},${y1} A${r},${r} 0 1 1 ${xm},${ym} A${r},${r} 0 1 1 ${x1},${y1}`;
33
+ }
34
+ const sweep = clamped * 2 * Math.PI;
35
+ const end = start + sweep;
36
+ const x2 = (CX + r * Math.cos(end)).toFixed(2);
37
+ const y2 = (CY + r * Math.sin(end)).toFixed(2);
38
+ return `M${x1},${y1} A${r},${r} 0 ${sweep > Math.PI ? 1 : 0} 1 ${x2},${y2}`;
39
+ }
40
+
41
+ $: n = items.length;
42
+ $: yStart = CY - ((n - 1) * ITEM_H) / 2;
43
+ $: rings = items.map((item, i) => {
44
+ const r = MIN_R + (n - 1 - i) * (RING_W + RING_GAP);
45
+ const frac = Math.min(item.value / (item.max || 1), 1);
46
+ const color = item.color ?? '#006FFF';
47
+ const ly = yStart + i * ITEM_H;
48
+ return {
49
+ r,
50
+ frac,
51
+ color,
52
+ ly,
53
+ d: arcPath(r, frac),
54
+ pct: Math.round(frac * 100),
55
+ label: item.label,
56
+ value: item.value,
57
+ unit: item.unit ?? '',
58
+ };
59
+ });
60
+ </script>
61
+
62
+ {#if items.length}
63
+ <div role="img" aria-label={ariaLabel} style="width:100%;">
64
+ <svg viewBox="0 0 {W} {H}" style="width:100%;height:auto;display:block;" aria-hidden="true" focusable="false">
65
+ {#each rings as ring, i (i)}
66
+ <circle cx={CX} cy={CY} r={ring.r} fill="none" stroke="rgba(255,255,255,0.06)" stroke-width={RING_W} />
67
+ {#if ring.d}
68
+ <path d={ring.d} fill="none" stroke={ring.color} stroke-width={RING_W} stroke-linecap="round" />
69
+ {/if}
70
+ <circle cx={LABEL_X} cy={ring.ly} r="3.5" fill={ring.color} />
71
+ <text x={LABEL_X + 10} y={ring.ly - 2} fill="#C8C9D0" font-size="9" font-family="inherit" dominant-baseline="auto">{ring.label}</text>
72
+ <text x={LABEL_X + 10} y={ring.ly + 9} fill="#6E7079" font-size="8" font-family="inherit" dominant-baseline="auto">{ring.value}{ring.unit} · {ring.pct}%</text>
73
+ {/each}
74
+ </svg>
75
+ </div>
76
+ {/if}