@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,170 @@
1
+ <script lang="ts">
2
+ import { onDestroy } from 'svelte';
3
+
4
+ export type NotifType = 'alarm' | 'system' | 'update';
5
+ export type NotifSeverity = 'danger' | 'warn' | 'info' | 'success';
6
+
7
+ export interface Notification {
8
+ id: string;
9
+ type: NotifType;
10
+ severity?: NotifSeverity;
11
+ title: string;
12
+ description?: string;
13
+ time: string;
14
+ read: boolean;
15
+ }
16
+
17
+ export let open: boolean = false;
18
+ export let notifications: Notification[] = [];
19
+ export let onClose: () => void = () => {};
20
+ export let onMarkRead: ((id: string) => void) | undefined = undefined;
21
+ export let onMarkAllRead: (() => void) | undefined = undefined;
22
+
23
+ let filter: 'all' | NotifType = 'all';
24
+ let panelEl: HTMLDivElement;
25
+ let prevFocus: HTMLElement | null = null;
26
+
27
+ const SEV_COLOR: Record<string, string> = {
28
+ danger: '#F03A3A',
29
+ warn: '#F5A623',
30
+ info: '#006FFF',
31
+ success: '#00B070',
32
+ };
33
+
34
+ const TYPE_LABEL: Record<string, string> = {
35
+ all: 'All',
36
+ alarm: 'Alarm',
37
+ system: 'System',
38
+ update: 'Update',
39
+ };
40
+
41
+ function setFilter(f: string) { filter = f as 'all' | NotifType; }
42
+
43
+ $: unread = notifications.filter((n) => !n.read).length;
44
+ $: filtered = filter === 'all' ? notifications : notifications.filter((n) => n.type === filter);
45
+
46
+ const FOCUSABLE =
47
+ 'a[href],button:not([disabled]),input:not([disabled]),select:not([disabled]),textarea:not([disabled]),[tabindex]:not([tabindex="-1"])';
48
+
49
+ function trapFocus(e: KeyboardEvent) {
50
+ if (!panelEl) return;
51
+ if (e.key === 'Escape') { onClose(); return; }
52
+ if (e.key !== 'Tab') return;
53
+ const items = Array.from(panelEl.querySelectorAll<HTMLElement>(FOCUSABLE));
54
+ if (!items.length) { e.preventDefault(); return; }
55
+ const f = items[0]!, l = items[items.length - 1]!;
56
+ const active = document.activeElement;
57
+ if (e.shiftKey && active === f) { e.preventDefault(); l.focus(); }
58
+ else if (!e.shiftKey && active === l) { e.preventDefault(); f.focus(); }
59
+ }
60
+
61
+ $: if (open) {
62
+ prevFocus = document.activeElement as HTMLElement | null;
63
+ setTimeout(() => {
64
+ if (!panelEl) return;
65
+ const first = panelEl.querySelector<HTMLElement>(FOCUSABLE);
66
+ (first ?? panelEl)?.focus();
67
+ }, 0);
68
+ window.addEventListener('keydown', trapFocus);
69
+ } else {
70
+ window.removeEventListener('keydown', trapFocus);
71
+ prevFocus?.focus?.();
72
+ }
73
+
74
+ onDestroy(() => {
75
+ window.removeEventListener('keydown', trapFocus);
76
+ });
77
+
78
+ let titleId = 'np-title-' + Math.random().toString(36).slice(2);
79
+ </script>
80
+
81
+ <!-- svelte-ignore a11y-click-events-have-key-events a11y-no-static-element-interactions -->
82
+ <div
83
+ class="np-overlay{open ? ' np-overlay--show' : ''}"
84
+ on:click={onClose}
85
+ aria-hidden="true"
86
+ />
87
+ <div
88
+ bind:this={panelEl}
89
+ class="np-panel{open ? ' np-panel--show' : ''}"
90
+ role="dialog"
91
+ aria-modal="true"
92
+ aria-labelledby={titleId}
93
+ tabindex="-1"
94
+ >
95
+ <div class="np-header">
96
+ <div class="np-title-row">
97
+ <h2 id={titleId} class="np-title">
98
+ Notifications
99
+ {#if unread > 0}
100
+ <span class="np-badge" aria-label="{unread} unread">{unread}</span>
101
+ {/if}
102
+ </h2>
103
+ <div class="np-header-actions">
104
+ {#if unread > 0 && onMarkAllRead}
105
+ <button type="button" class="np-mark-all" on:click={onMarkAllRead}>
106
+ Mark all read
107
+ </button>
108
+ {/if}
109
+ <button type="button" class="icon-btn" on:click={onClose} aria-label="Close notifications">
110
+ <svg width="16" height="16" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true">
111
+ <path d="M3.72 3.72a.75.75 0 0 1 1.06 0L8 6.94l3.22-3.22a.75.75 0 1 1 1.06 1.06L9.06 8l3.22 3.22a.75.75 0 1 1-1.06 1.06L8 9.06l-3.22 3.22a.75.75 0 0 1-1.06-1.06L6.94 8 3.72 4.78a.75.75 0 0 1 0-1.06z"/>
112
+ </svg>
113
+ </button>
114
+ </div>
115
+ </div>
116
+ <div class="np-filters" role="tablist" aria-label="Filter by type">
117
+ {#each ['all', 'alarm', 'system', 'update'] as f}
118
+ <button
119
+ type="button"
120
+ role="tab"
121
+ aria-selected={filter === f}
122
+ class="np-filter{filter === f ? ' np-filter--active' : ''}"
123
+ on:click={() => setFilter(f)}
124
+ >
125
+ {TYPE_LABEL[f] ?? f}
126
+ </button>
127
+ {/each}
128
+ </div>
129
+ </div>
130
+ <div
131
+ class="np-list"
132
+ role="log"
133
+ aria-live="polite"
134
+ aria-label="Notifications"
135
+ aria-relevant="additions"
136
+ >
137
+ {#if filtered.length === 0}
138
+ <div class="np-empty">No notifications</div>
139
+ {:else}
140
+ {#each filtered as n (n.id)}
141
+ <div class="np-item{n.read ? ' np-item--read' : ''}">
142
+ <span
143
+ class="np-dot"
144
+ style="background:{SEV_COLOR[n.severity ?? 'info']};"
145
+ aria-hidden="true"
146
+ ></span>
147
+ <div class="np-item-body">
148
+ <div class="np-item-top">
149
+ <span class="np-item-title">{n.title}</span>
150
+ <span class="np-item-time">{n.time}</span>
151
+ </div>
152
+ {#if n.description}
153
+ <p class="np-item-desc">{n.description}</p>
154
+ {/if}
155
+ <span class="np-type-badge">{TYPE_LABEL[n.type]}</span>
156
+ </div>
157
+ {#if !n.read && onMarkRead}
158
+ <button
159
+ type="button"
160
+ class="np-read-btn"
161
+ on:click={() => onMarkRead?.(n.id)}
162
+ aria-label="Mark as read: {n.title}"
163
+ title="Mark as read"
164
+ >&#10003;</button>
165
+ {/if}
166
+ </div>
167
+ {/each}
168
+ {/if}
169
+ </div>
170
+ </div>
@@ -0,0 +1,93 @@
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 min: number | undefined = undefined;
11
+ export let max: number | undefined = undefined;
12
+ export let step: number = 1;
13
+ export let suffix: string | undefined = undefined;
14
+ export let disabled: boolean = false;
15
+ export let id: string | undefined = undefined;
16
+ let className = '';
17
+ export { className as class };
18
+
19
+ const uid = `dash-ui-ni-${++counter}`;
20
+ $: inputId = id ?? uid;
21
+
22
+ const dispatch = createEventDispatcher<{ change: number }>();
23
+
24
+ function clamp(n: number): number {
25
+ let v = n;
26
+ if (min !== undefined) v = Math.max(min, v);
27
+ if (max !== undefined) v = Math.min(max, v);
28
+ return v;
29
+ }
30
+
31
+ function commit(next: number) {
32
+ value = clamp(next);
33
+ dispatch('change', value);
34
+ }
35
+
36
+ function handleKey(e: KeyboardEvent) {
37
+ if (e.key === 'ArrowUp') { e.preventDefault(); commit(value + step); }
38
+ else if (e.key === 'ArrowDown') { e.preventDefault(); commit(value - step); }
39
+ else if (e.key === 'PageUp') { e.preventDefault(); commit(value + step * 10); }
40
+ else if (e.key === 'PageDown') { e.preventDefault(); commit(value - step * 10); }
41
+ else if (e.key === 'Home' && min !== undefined) { e.preventDefault(); commit(min); }
42
+ else if (e.key === 'End' && max !== undefined) { e.preventDefault(); commit(max); }
43
+ }
44
+
45
+ function handleChange(e: Event) {
46
+ const n = Number((e.target as HTMLInputElement).value);
47
+ if (!isNaN(n)) commit(n);
48
+ }
49
+
50
+ $: canDecrement = !disabled && (min === undefined || value > min);
51
+ $: canIncrement = !disabled && (max === undefined || value < max);
52
+ </script>
53
+
54
+ <div class="number-input {className}">
55
+ {#if label}
56
+ <label for={inputId} class="number-input__label">{label}</label>
57
+ {/if}
58
+ <div class="number-input__control">
59
+ <button
60
+ type="button"
61
+ class="number-input__btn"
62
+ aria-label="Decrement"
63
+ tabindex="-1"
64
+ disabled={!canDecrement}
65
+ on:click={() => commit(value - step)}
66
+ >−</button>
67
+ <input
68
+ id={inputId}
69
+ type="number"
70
+ role="spinbutton"
71
+ aria-valuenow={value}
72
+ aria-valuemin={min}
73
+ aria-valuemax={max}
74
+ aria-valuetext={suffix ? `${value} ${suffix}` : undefined}
75
+ {value}
76
+ {disabled}
77
+ class="number-input__field"
78
+ on:change={handleChange}
79
+ on:keydown={handleKey}
80
+ />
81
+ {#if suffix}
82
+ <span class="number-input__suffix" aria-hidden="true">{suffix}</span>
83
+ {/if}
84
+ <button
85
+ type="button"
86
+ class="number-input__btn"
87
+ aria-label="Increment"
88
+ tabindex="-1"
89
+ disabled={!canIncrement}
90
+ on:click={() => commit(value + step)}
91
+ >+</button>
92
+ </div>
93
+ </div>
@@ -0,0 +1,94 @@
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 length: number = 6;
10
+ export let value: string = '';
11
+ export let disabled: boolean = false;
12
+ let className = '';
13
+ export { className as class };
14
+
15
+ const dispatch = createEventDispatcher<{ change: string }>();
16
+ const uid = `dash-ui-otp-${++counter}`;
17
+
18
+ let digits: string[] = Array.from({ length }, (_, i) => value[i] ?? '');
19
+ let inputs: (HTMLInputElement | null)[] = Array(length).fill(null);
20
+
21
+ const half = Math.floor(length / 2);
22
+
23
+ function focusAt(i: number) {
24
+ const el = inputs[i];
25
+ if (el) { el.focus(); el.select(); }
26
+ }
27
+
28
+ function commit(next: string[]) {
29
+ digits = next;
30
+ value = next.join('');
31
+ dispatch('change', value);
32
+ }
33
+
34
+ function handleInput(i: number, raw: string) {
35
+ const char = raw.replace(/\D/g, '').slice(-1);
36
+ const next = [...digits];
37
+ next[i] = char;
38
+ commit(next);
39
+ if (char && i < length - 1) setTimeout(() => focusAt(i + 1), 0);
40
+ }
41
+
42
+ function handleKey(i: number, e: KeyboardEvent) {
43
+ if (e.key === 'Backspace') {
44
+ if (digits[i]) {
45
+ const next = [...digits]; next[i] = ''; commit(next);
46
+ } else if (i > 0) {
47
+ e.preventDefault(); focusAt(i - 1);
48
+ }
49
+ } else if (e.key === 'ArrowLeft' && i > 0) {
50
+ e.preventDefault(); focusAt(i - 1);
51
+ } else if (e.key === 'ArrowRight' && i < length - 1) {
52
+ e.preventDefault(); focusAt(i + 1);
53
+ }
54
+ }
55
+
56
+ function handlePaste(e: ClipboardEvent) {
57
+ const text = (e.clipboardData?.getData('text') ?? '').replace(/\D/g, '').slice(0, length);
58
+ if (!text.length) return;
59
+ e.preventDefault();
60
+ const next = Array.from({ length }, (_, i) => text[i] ?? digits[i] ?? '');
61
+ commit(next);
62
+ setTimeout(() => focusAt(Math.min(text.length, length - 1)), 0);
63
+ }
64
+ </script>
65
+
66
+ <div class="otp-input-wrapper {className}">
67
+ {#if label}
68
+ <label for="{uid}-0" class="otp-input__label">{label}</label>
69
+ {/if}
70
+ <div class="otp-input{disabled ? ' otp-input--disabled' : ''}" role="group" aria-label={label ?? 'One-time password'}>
71
+ {#each digits as d, i}
72
+ {#if i === half}
73
+ <span class="otp-input__separator" aria-hidden="true">&ndash;</span>
74
+ {/if}
75
+ <input
76
+ bind:this={inputs[i]}
77
+ id={i === 0 ? `${uid}-0` : undefined}
78
+ type="text"
79
+ inputmode="numeric"
80
+ pattern="[0-9]*"
81
+ maxlength={1}
82
+ aria-label="Digit {i + 1} of {length}"
83
+ value={d}
84
+ {disabled}
85
+ class="otp-input__digit"
86
+ autocomplete={i === 0 ? 'one-time-code' : 'off'}
87
+ on:input={(e) => handleInput(i, e.currentTarget.value)}
88
+ on:keydown={(e) => handleKey(i, e)}
89
+ on:paste={handlePaste}
90
+ on:focus={(e) => e.currentTarget.select()}
91
+ />
92
+ {/each}
93
+ </div>
94
+ </div>
@@ -0,0 +1,58 @@
1
+ <script lang="ts">
2
+ import { createEventDispatcher } from 'svelte';
3
+
4
+ export let page: number = 1;
5
+ export let pageSize: number = 10;
6
+ export let total: number = 0;
7
+
8
+ const dispatch = createEventDispatcher<{ change: number }>();
9
+
10
+ $: totalPages = Math.max(1, Math.ceil(total / pageSize));
11
+ $: pages = getPageNumbers(page, totalPages);
12
+
13
+ function getPageNumbers(p: number, tp: number): (number | '...')[] {
14
+ if (tp <= 7) return Array.from({ length: tp }, (_, i) => i + 1);
15
+ const items: (number | '...')[] = [1];
16
+ const lo = Math.max(2, p - 1);
17
+ const hi = Math.min(tp - 1, p + 1);
18
+ if (lo > 2) items.push('...');
19
+ for (let i = lo; i <= hi; i++) items.push(i);
20
+ if (hi < tp - 1) items.push('...');
21
+ items.push(tp);
22
+ return items;
23
+ }
24
+
25
+ function go(p: number) { dispatch('change', p); }
26
+ </script>
27
+
28
+ {#if totalPages > 1}
29
+ <nav class="pagination" aria-label="Pagination">
30
+ <button
31
+ type="button"
32
+ class="pagination-btn"
33
+ aria-label="Previous page"
34
+ disabled={page <= 1}
35
+ on:click={() => go(page - 1)}
36
+ >&#x2039;</button>
37
+ {#each pages as p, i (typeof p === 'number' ? p : `e${i}`)}
38
+ {#if p === '...'}
39
+ <span class="pagination-ellipsis" aria-hidden="true">&#x2026;</span>
40
+ {:else}
41
+ <button
42
+ type="button"
43
+ class="pagination-btn{p === page ? ' active' : ''}"
44
+ aria-label="Page {p}"
45
+ aria-current={p === page ? 'page' : undefined}
46
+ on:click={() => go(p)}
47
+ >{p}</button>
48
+ {/if}
49
+ {/each}
50
+ <button
51
+ type="button"
52
+ class="pagination-btn"
53
+ aria-label="Next page"
54
+ disabled={page >= totalPages}
55
+ on:click={() => go(page + 1)}
56
+ >&#x203A;</button>
57
+ </nav>
58
+ {/if}
@@ -0,0 +1,98 @@
1
+ <script lang="ts">
2
+ const COLORS = ['#006FFF', '#00C8C8', '#F5A623', '#7FB6FF', '#A878F5', '#F56342'];
3
+
4
+ export interface ParallelAxis {
5
+ label: string;
6
+ min: number;
7
+ max: number;
8
+ unit?: string;
9
+ invert?: boolean;
10
+ }
11
+
12
+ export interface ParallelSeries {
13
+ label: string;
14
+ color?: string;
15
+ values: number[];
16
+ }
17
+
18
+ export let axes: ParallelAxis[] = [];
19
+ export let series: ParallelSeries[] = [];
20
+ export let height: number = 200;
21
+ export let ariaLabel: string = 'Parallel coordinates chart';
22
+
23
+ const VW = 380;
24
+ $: VH = height;
25
+ const PAD_L = 28;
26
+ const PAD_R = 28;
27
+ const PAD_T = 32;
28
+ const PAD_B = 20;
29
+
30
+ $: TRACK_W = VW - PAD_L - PAD_R;
31
+ $: TRACK_H = VH - PAD_T - PAD_B;
32
+ $: nAxes = axes.length;
33
+
34
+ function axisX(i: number, tw: number, n: number): number {
35
+ return PAD_L + (n > 1 ? i * (tw / (n - 1)) : tw / 2);
36
+ }
37
+
38
+ function valueY(axis: ParallelAxis, v: number, th: number): number {
39
+ const t = Math.max(0, Math.min(1, (v - axis.min) / (axis.max - axis.min)));
40
+ return axis.invert ? PAD_T + t * th : PAD_T + (1 - t) * th;
41
+ }
42
+
43
+ function seriesPath(s: ParallelSeries, tw: number, th: number, n: number): string {
44
+ return axes
45
+ .map((axis, i) => {
46
+ const x = axisX(i, tw, n).toFixed(1);
47
+ const y = valueY(axis, s.values[i] ?? axis.min, th).toFixed(1);
48
+ return i === 0 ? `M${x},${y}` : `L${x},${y}`;
49
+ })
50
+ .join(' ');
51
+ }
52
+
53
+ const TICKS = [0, 0.5, 1];
54
+
55
+ $: paths = series.map((s, si) => ({
56
+ d: seriesPath(s, TRACK_W, TRACK_H, nAxes),
57
+ stroke: s.color ?? COLORS[si % COLORS.length],
58
+ }));
59
+
60
+ $: axisData = axes.map((axis, i) => {
61
+ const x = axisX(i, TRACK_W, nAxes);
62
+ const ticks = TICKS.map(t => {
63
+ const v = axis.invert
64
+ ? axis.min + t * (axis.max - axis.min)
65
+ : axis.max - t * (axis.max - axis.min);
66
+ const y = PAD_T + t * TRACK_H;
67
+ return { t, v, y };
68
+ });
69
+ return { axis, x, ticks };
70
+ });
71
+ </script>
72
+
73
+ <div role="img" aria-label={ariaLabel} style="width:100%;">
74
+ <svg
75
+ viewBox="0 0 {VW} {VH}"
76
+ style="width:100%;height:auto;display:block;"
77
+ aria-hidden="true"
78
+ focusable="false"
79
+ >
80
+ {#each axisData as { axis, x, ticks } (axis.label)}
81
+ <line x1={x} y1={PAD_T} x2={x} y2={VH - PAD_B} stroke="rgba(255,255,255,0.2)" stroke-width="1" />
82
+ <text x={x} y={PAD_T - 8} fill="#A4A7B5" font-size="8" text-anchor="middle" font-family="inherit">{axis.label}</text>
83
+ {#if axis.unit}
84
+ <text x={x} y={PAD_T - 1} fill="#6E7079" font-size="7" text-anchor="middle" font-family="inherit">{axis.unit}</text>
85
+ {/if}
86
+ {#each ticks as tick (tick.t)}
87
+ <line x1={x - 3} y1={tick.y} x2={x + 3} y2={tick.y} stroke="rgba(255,255,255,0.25)" stroke-width="1" />
88
+ <text x={x - 5} y={tick.y + 3} fill="#6E7079" font-size="7" text-anchor="end" font-family="inherit">
89
+ {tick.v % 1 === 0 ? tick.v.toFixed(0) : tick.v.toFixed(1)}
90
+ </text>
91
+ {/each}
92
+ {/each}
93
+
94
+ {#each paths as p, si (si)}
95
+ <path d={p.d} fill="none" stroke={p.stroke} stroke-width="1.5" opacity="0.75" />
96
+ {/each}
97
+ </svg>
98
+ </div>
@@ -0,0 +1,88 @@
1
+ <script lang="ts">
2
+ export interface ParetoItem {
3
+ label: string;
4
+ value: number;
5
+ color?: string;
6
+ }
7
+
8
+ export let items: ParetoItem[] = [];
9
+ export let height: number = 160;
10
+ export let ariaLabel: string = 'Pareto chart';
11
+ export let barColor: string = '#006FFF';
12
+ export let lineColor: string = '#F5A623';
13
+ export let threshold: number = 0.8;
14
+
15
+ const VW = 400;
16
+ const PAD = { t: 12, r: 40, b: 28, l: 8 };
17
+
18
+ $: sorted = [...items].sort((a, b) => b.value - a.value);
19
+ $: total = sorted.reduce((s, it) => s + it.value, 0) || 1;
20
+ $: chartW = VW - PAD.l - PAD.r;
21
+ $: chartH = height - PAD.t - PAD.b;
22
+ $: bot = PAD.t + chartH;
23
+ $: maxV = sorted[0]?.value || 1;
24
+ $: barW = sorted.length > 0 ? chartW / sorted.length : chartW;
25
+ $: gap = barW * 0.15;
26
+ $: bw = Math.max(barW - gap * 2, 1);
27
+
28
+ $: gridLines = [0.25, 0.5, 0.75, 1].map((f) => PAD.t + (1 - f) * chartH);
29
+
30
+ $: bars = sorted.map((item, i) => {
31
+ const bh = (item.value / maxV) * chartH;
32
+ const x = PAD.l + i * barW + gap;
33
+ return {
34
+ x: x.toFixed(1),
35
+ y: (bot - bh).toFixed(1),
36
+ w: bw.toFixed(1),
37
+ h: bh.toFixed(1),
38
+ label: item.label,
39
+ color: item.color ?? barColor,
40
+ key: `b${i}`,
41
+ };
42
+ });
43
+
44
+ $: linePoints = (() => {
45
+ let cumSum = 0;
46
+ const pts: string[] = [`${PAD.l},${bot}`];
47
+ sorted.forEach((item, i) => {
48
+ cumSum += item.value;
49
+ const pct = cumSum / total;
50
+ const x = PAD.l + (i + 1) * barW;
51
+ const y = PAD.t + (1 - pct) * chartH;
52
+ pts.push(`${x.toFixed(1)},${y.toFixed(1)}`);
53
+ });
54
+ return pts.join(' ');
55
+ })();
56
+
57
+ $: thresholdY = threshold > 0 ? (PAD.t + (1 - threshold) * chartH).toFixed(1) : null;
58
+
59
+ $: pctLabels = [0, 50, 100].map((pct) => ({
60
+ label: `${pct}%`,
61
+ y: (PAD.t + (1 - pct / 100) * chartH + 3).toFixed(1),
62
+ }));
63
+ </script>
64
+
65
+ <div role="img" aria-label={ariaLabel} style="width:100%;">
66
+ <svg viewBox="0 0 {VW} {height}" style="width:100%;height:auto;display:block;" aria-hidden="true" focusable="false">
67
+ {#each gridLines as y}
68
+ <line x1={PAD.l} y1={y} x2={VW - PAD.r} y2={y} stroke="rgba(255,255,255,0.06)" stroke-width="1" />
69
+ {/each}
70
+ {#each bars as b (b.key)}
71
+ <rect x={b.x} y={b.y} width={b.w} height={b.h} fill={b.color} fill-opacity="0.85" rx="2" />
72
+ {/each}
73
+ {#if thresholdY}
74
+ <line x1={PAD.l} y1={thresholdY} x2={VW - PAD.r} y2={thresholdY}
75
+ stroke="rgba(255,255,255,0.3)" stroke-width="1" stroke-dasharray="4 3" />
76
+ {/if}
77
+ <polyline points={linePoints} fill="none" stroke={lineColor} stroke-width="1.5" stroke-linejoin="round" />
78
+ {#each pctLabels as pl}
79
+ <text x={VW - PAD.r + 4} y={pl.y} fill="#6E7079" font-size="8">{pl.label}</text>
80
+ {/each}
81
+ {#each bars as b, i (b.key)}
82
+ {@const cx = (parseFloat(b.x) + parseFloat(b.w) / 2).toFixed(1)}
83
+ <text x={cx} y={height - 4} text-anchor="middle" fill="#6E7079" font-size="9">
84
+ {b.label.length > 8 ? `${b.label.slice(0, 7)}…` : b.label}
85
+ </text>
86
+ {/each}
87
+ </svg>
88
+ </div>
@@ -0,0 +1,62 @@
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: string = '';
10
+ export let placeholder: string | undefined = undefined;
11
+ export let disabled: boolean = false;
12
+ export let autocomplete: 'current-password' | 'new-password' | 'off' = 'current-password';
13
+ let className = '';
14
+ export { className as class };
15
+
16
+ const dispatch = createEventDispatcher<{ change: string }>();
17
+ const uid = `dash-ui-pwd-${++counter}`;
18
+
19
+ let shown = false;
20
+
21
+ function handleInput(e: Event) {
22
+ value = (e.target as HTMLInputElement).value;
23
+ dispatch('change', value);
24
+ }
25
+ </script>
26
+
27
+ <div class="pwd-input-wrapper {className}">
28
+ {#if label}
29
+ <label for={uid} class="pwd-input__label">{label}</label>
30
+ {/if}
31
+ <div class="pwd-input{disabled ? ' pwd-input--disabled' : ''}">
32
+ <input
33
+ id={uid}
34
+ type={shown ? 'text' : 'password'}
35
+ class="pwd-input__field"
36
+ {value}
37
+ {placeholder}
38
+ {disabled}
39
+ {autocomplete}
40
+ on:input={handleInput}
41
+ />
42
+ <button
43
+ type="button"
44
+ class="pwd-input__toggle"
45
+ aria-pressed={shown}
46
+ aria-label={shown ? 'Hide password' : 'Show password'}
47
+ {disabled}
48
+ on:click={() => (shown = !shown)}
49
+ >
50
+ {#if shown}
51
+ <svg width="16" height="16" viewBox="0 0 16 16" fill="none" aria-hidden="true" focusable="false">
52
+ <path d="M2 2l12 12M6.5 6.6A2 2 0 0 0 9.4 9.5M4.2 4.3C2.7 5.3 1.5 7 1.5 8s2.2 4.5 6.5 4.5c1.3 0 2.4-.3 3.3-.8M7 3.6C7.3 3.5 7.7 3.5 8 3.5c4.3 0 6.5 3.5 6.5 4.5 0 .6-.6 1.7-1.7 2.7" stroke="currentColor" stroke-width="1.25" stroke-linecap="round" stroke-linejoin="round"/>
53
+ </svg>
54
+ {:else}
55
+ <svg width="16" height="16" viewBox="0 0 16 16" fill="none" aria-hidden="true" focusable="false">
56
+ <path d="M1 8s2.5-5 7-5 7 5 7 5-2.5 5-7 5-7-5-7-5z" stroke="currentColor" stroke-width="1.25" stroke-linejoin="round"/>
57
+ <circle cx="8" cy="8" r="2" stroke="currentColor" stroke-width="1.25"/>
58
+ </svg>
59
+ {/if}
60
+ </button>
61
+ </div>
62
+ </div>