@reshape-biotech/design-system 1.2.6 → 2.0.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 +5 -1
- package/dist/app.css +97 -97
- package/dist/components/activity/Activity.stories.svelte +104 -104
- package/dist/components/activity/Activity.svelte +112 -112
- package/dist/components/avatar/Avatar.stories.svelte +23 -23
- package/dist/components/avatar/Avatar.svelte +54 -54
- package/dist/components/banner/Banner.stories.svelte +105 -105
- package/dist/components/banner/Banner.svelte +42 -42
- package/dist/components/button/Button.stories.svelte +61 -61
- package/dist/components/button/Button.svelte +95 -95
- package/dist/components/card/Card.stories.svelte +112 -112
- package/dist/components/card/Card.svelte +18 -18
- package/dist/components/checkbox/Checkbox.stories.svelte +8 -8
- package/dist/components/checkbox/Checkbox.svelte +17 -17
- package/dist/components/collapsible/Collapsible.stories.svelte +26 -26
- package/dist/components/collapsible/components/collapsible-content.svelte +20 -20
- package/dist/components/collapsible/components/collapsible-trigger.svelte +12 -12
- package/dist/components/collapsible/index.d.ts +1 -1
- package/dist/components/combobox/Combobox.stories.svelte +412 -412
- package/dist/components/combobox/components/combobox-add.svelte +8 -8
- package/dist/components/combobox/components/combobox-content.svelte +39 -39
- package/dist/components/combobox/components/combobox-indicator.svelte +1 -1
- package/dist/components/combobox/index.d.ts +1 -1
- package/dist/components/datepicker/DatePicker.stories.svelte +196 -196
- package/dist/components/datepicker/DatePicker.svelte +173 -173
- package/dist/components/divider/Divider.stories.svelte +7 -7
- package/dist/components/divider/Divider.svelte +9 -9
- package/dist/components/drawer/Drawer.stories.svelte +51 -51
- package/dist/components/drawer/Drawer.svelte +33 -33
- package/dist/components/drawer/DrawerLabel.svelte +10 -10
- package/dist/components/dropdown/Dropdown.stories.svelte +210 -210
- package/dist/components/dropdown/Dropdown.svelte +57 -57
- package/dist/components/dropdown/components/DropdownContent.svelte +16 -16
- package/dist/components/dropdown/components/DropdownMenu.svelte +10 -10
- package/dist/components/dropdown/components/DropdownTrigger.svelte +37 -37
- package/dist/components/dropdown/components/OutlinedButton.svelte +9 -9
- package/dist/components/empty-content/EmptyContent.stories.svelte +38 -38
- package/dist/components/empty-content/EmptyContent.svelte +12 -12
- package/dist/components/graphs/bar-chart/BarChart.stories.svelte +91 -91
- package/dist/components/graphs/bar-chart/BarChart.svelte +147 -147
- package/dist/components/graphs/bar-chart/StackedBarChart.stories.svelte +57 -57
- package/dist/components/graphs/bar-chart/StackedBarChart.svelte +198 -199
- package/dist/components/graphs/chart/Chart.stories.svelte +96 -96
- package/dist/components/graphs/chart/Chart.svelte +207 -207
- package/dist/components/graphs/line/LineChart.stories.svelte +138 -138
- package/dist/components/graphs/line/LineChart.svelte +140 -142
- package/dist/components/graphs/matrix/Matrix.stories.svelte +117 -117
- package/dist/components/graphs/matrix/Matrix.svelte +141 -141
- package/dist/components/graphs/multiline/MultiLineChart.stories.svelte +168 -168
- package/dist/components/graphs/multiline/MultiLineChart.svelte +236 -236
- package/dist/components/graphs/scatterplot/Scatterplot.stories.svelte +84 -84
- package/dist/components/graphs/scatterplot/Scatterplot.svelte +302 -302
- package/dist/components/graphs/utils/duration.d.ts +1 -0
- package/dist/components/graphs/utils/duration.js +33 -0
- package/dist/components/graphs/utils/tooltipFormatter.js +1 -1
- package/dist/components/icon-button/IconButton.stories.svelte +64 -64
- package/dist/components/icon-button/IconButton.svelte +88 -88
- package/dist/components/icons/AnalysisIcon.stories.svelte +18 -18
- package/dist/components/icons/AnalysisIcon.svelte +96 -96
- package/dist/components/icons/Icon.stories.svelte +111 -111
- package/dist/components/icons/Icon.svelte +17 -17
- package/dist/components/icons/PrincipalIcon.svelte +59 -59
- package/dist/components/icons/custom/Halo.svelte +31 -31
- package/dist/components/icons/custom/Well.svelte +27 -27
- package/dist/components/icons/index.js +1 -1
- package/dist/components/image/Image.svelte +42 -42
- package/dist/components/input/Input.stories.svelte +55 -55
- package/dist/components/input/Input.svelte +121 -121
- package/dist/components/label/Label.stories.svelte +18 -18
- package/dist/components/label/Label.svelte +11 -11
- package/dist/components/list/List.stories.svelte +84 -84
- package/dist/components/list/List.svelte +20 -20
- package/dist/components/logo/Logo.stories.svelte +15 -15
- package/dist/components/logo/Logo.svelte +30 -30
- package/dist/components/manual-cfu-counter/ManualCFUCounter.stories.svelte +102 -102
- package/dist/components/manual-cfu-counter/ManualCFUCounter.svelte +557 -557
- package/dist/components/manual-cfu-counter/test/ManualCFUCounterTestWrapper.svelte +11 -11
- package/dist/components/markdown/Markdown.stories.svelte +10 -10
- package/dist/components/markdown/Markdown.svelte +6 -6
- package/dist/components/modal/Modal.stories.svelte +29 -29
- package/dist/components/modal/Modal.svelte +71 -71
- package/dist/components/multi-cfu-counter/MultiCFUCounter.stories.svelte +201 -201
- package/dist/components/multi-cfu-counter/MultiCFUCounter.svelte +606 -606
- package/dist/components/multi-cfu-counter/test/MultiCFUCounterTestWrapper.svelte +17 -17
- package/dist/components/notification-popup/NotificationPopup.stories.svelte +18 -18
- package/dist/components/notification-popup/NotificationPopup.svelte +26 -26
- package/dist/components/notifications/Notifications.stories.svelte +101 -101
- package/dist/components/notifications/Notifications.svelte +9 -9
- package/dist/components/pill/Pill.stories.svelte +8 -8
- package/dist/components/pill/Pill.svelte +27 -27
- package/dist/components/progress-circle/ProgressCircle.stories.svelte +8 -8
- package/dist/components/progress-circle/ProgressCircle.svelte +54 -54
- package/dist/components/required-status-indicator/RequiredStatusIndicator.stories.svelte +18 -18
- package/dist/components/required-status-indicator/RequiredStatusIndicator.svelte +14 -14
- package/dist/components/segmented-control-buttons/ControlButton.svelte +36 -36
- package/dist/components/segmented-control-buttons/SegmentedControlButtons.stories.svelte +35 -35
- package/dist/components/segmented-control-buttons/SegmentedControlButtons.svelte +13 -13
- package/dist/components/select/Select.stories.svelte +200 -94
- package/dist/components/select/Select.stories.svelte.d.ts +1 -1
- package/dist/components/select/components/Group.svelte +24 -0
- package/dist/components/select/components/MultiSelectTrigger.svelte +66 -0
- package/dist/components/select/components/SelectContent.svelte +33 -0
- package/dist/components/select/components/SelectGroupHeading.svelte +19 -0
- package/dist/components/select/components/SelectItem.svelte +39 -0
- package/dist/components/select/components/SelectTrigger.svelte +48 -0
- package/dist/components/select/index.d.ts +10 -7
- package/dist/components/select/index.js +12 -1
- package/dist/components/sjsf-wrappers/SjsfNumberInputWrapper.svelte +102 -87
- package/dist/components/sjsf-wrappers/SjsfNumberInputWrapper.svelte.d.ts +1 -1
- package/dist/components/sjsf-wrappers/SjsfTextInputWrapper.svelte +53 -53
- package/dist/components/sjsf-wrappers/SjsfTextInputWrapper.svelte.d.ts +1 -1
- package/dist/components/sjsf-wrappers/sjsfCustomTheme.js +1 -1
- package/dist/components/skeleton-loader/SkeletonLoader.stories.svelte +32 -32
- package/dist/components/skeleton-loader/SkeletonLoader.svelte +10 -10
- package/dist/components/skeleton-loader/StatcardSkeleton.svelte +9 -9
- package/dist/components/skeleton-loader/components/Skeleton.svelte +7 -7
- package/dist/components/skeleton-loader/components/SkeletonImage.svelte +12 -12
- package/dist/components/slider/Slider.stories.svelte +23 -23
- package/dist/components/slider/Slider.svelte +107 -107
- package/dist/components/spinner/Spinner.stories.svelte +8 -8
- package/dist/components/spinner/Spinner.svelte +18 -18
- package/dist/components/stat-card/StatCard.stories.svelte +26 -26
- package/dist/components/stat-card/StatCard.svelte +128 -128
- package/dist/components/status-badge/StatusBadge.stories.svelte +365 -365
- package/dist/components/status-badge/StatusBadge.svelte +54 -54
- package/dist/components/stepper/Stepper.stories.svelte +219 -219
- package/dist/components/stepper/components/stepper-root.svelte +12 -12
- package/dist/components/stepper/components/stepper-step.svelte +83 -83
- package/dist/components/table/Table.stories.svelte +87 -87
- package/dist/components/table/Table.svelte +32 -32
- package/dist/components/table/components/TBody.svelte +7 -7
- package/dist/components/table/components/THead.svelte +7 -7
- package/dist/components/table/components/Td.svelte +8 -8
- package/dist/components/table/components/Th.svelte +8 -8
- package/dist/components/table/components/Tr.svelte +11 -11
- package/dist/components/tabs/Tabs.stories.svelte +20 -20
- package/dist/components/tabs/Tabs.svelte +8 -8
- package/dist/components/tabs/components/Content.svelte +8 -8
- package/dist/components/tabs/components/Tab.svelte +14 -14
- package/dist/components/tabs/components/Tabs.svelte +7 -7
- package/dist/components/tag/Tag.stories.svelte +57 -57
- package/dist/components/tag/Tag.svelte +95 -95
- package/dist/components/textarea/Textarea.stories.svelte +70 -70
- package/dist/components/textarea/Textarea.svelte +76 -76
- package/dist/components/toast/Toast.stories.svelte +204 -204
- package/dist/components/toast/Toast.svelte +53 -53
- package/dist/components/toggle/Toggle.stories.svelte +9 -9
- package/dist/components/toggle/Toggle.svelte +53 -53
- package/dist/components/toggle-icon-button/ToggleIconButton.stories.svelte +152 -152
- package/dist/components/toggle-icon-button/ToggleIconButton.svelte +99 -99
- package/dist/components/tooltip/Tooltip.stories.svelte +85 -111
- package/dist/components/tooltip/Tooltip.svelte +57 -46
- package/dist/components/tooltip/Tooltip.svelte.d.ts +1 -1
- package/dist/components/tooltip/TooltipTest.svelte +31 -0
- package/dist/components/tooltip/TooltipTest.svelte.d.ts +11 -0
- package/dist/fonts/index.js +1 -1
- package/dist/index.d.ts +0 -1
- package/dist/index.js +0 -1
- package/dist/notifications.d.ts +1 -4
- package/dist/notifications.js +1 -1
- package/dist/tailwind-safelist.js +406 -406
- package/dist/tailwind.preset.js +10 -10
- package/dist/tokens/colors.js +18 -18
- package/dist/tokens/typography.js +6 -6
- package/dist/tokens.js +19 -19
- package/dist/types/fonts.d.ts +2 -2
- package/package.json +199 -204
- package/dist/components/select/Select.svelte +0 -139
- package/dist/components/select/Select.svelte.d.ts +0 -60
- package/dist/components/select-new/Select.stories.svelte +0 -219
- package/dist/components/select-new/Select.stories.svelte.d.ts +0 -19
- package/dist/components/select-new/components/Group.svelte +0 -24
- package/dist/components/select-new/components/MultiSelectTrigger.svelte +0 -66
- package/dist/components/select-new/components/SelectContent.svelte +0 -33
- package/dist/components/select-new/components/SelectGroupHeading.svelte +0 -19
- package/dist/components/select-new/components/SelectItem.svelte +0 -39
- package/dist/components/select-new/components/SelectTrigger.svelte +0 -48
- package/dist/components/select-new/index.d.ts +0 -10
- package/dist/components/select-new/index.js +0 -12
- /package/dist/components/{select-new → select}/components/Group.svelte.d.ts +0 -0
- /package/dist/components/{select-new → select}/components/MultiSelectTrigger.svelte.d.ts +0 -0
- /package/dist/components/{select-new → select}/components/SelectContent.svelte.d.ts +0 -0
- /package/dist/components/{select-new → select}/components/SelectGroupHeading.svelte.d.ts +0 -0
- /package/dist/components/{select-new → select}/components/SelectItem.svelte.d.ts +0 -0
- /package/dist/components/{select-new → select}/components/SelectTrigger.svelte.d.ts +0 -0
- /package/dist/components/{select-new → select}/types.d.ts +0 -0
- /package/dist/components/{select-new → select}/types.js +0 -0
|
@@ -1,573 +1,573 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
2
|
+
import { onMount } from 'svelte';
|
|
3
|
+
import { Button } from '../button';
|
|
4
|
+
import { textColor, borderColor } from '../../tokens';
|
|
5
|
+
import { Icon } from '../icons';
|
|
6
|
+
import IconButton from '../icon-button/IconButton.svelte';
|
|
7
|
+
import Divider from '../divider/Divider.svelte';
|
|
8
|
+
|
|
9
|
+
// Constants for the component
|
|
10
|
+
const BASE_IMAGE_SIZE = 464; // Base size, will be overriden by container dimensions
|
|
11
|
+
const BASE_MARKER_SIZE = 8; // Base size at zoom level 1
|
|
12
|
+
const BASE_MARKER_FONT_SIZE = 8; // Base font size at zoom level 1
|
|
13
|
+
const MAX_ZOOM = 10;
|
|
14
|
+
const MIN_ZOOM = 1;
|
|
15
|
+
const ZOOM_STEP = 0.001;
|
|
16
|
+
const MARKER_COLOR = textColor['icon-blue'] || '#2563eb';
|
|
17
|
+
const MARKER_BORDER_COLOR = borderColor['interactive'] || '#12192A1A';
|
|
18
|
+
const TEXT_COLOR = textColor['primary-inverse'];
|
|
19
|
+
const DRAG_THRESHOLD = 5;
|
|
20
|
+
|
|
21
|
+
interface Props {
|
|
22
|
+
imageUrl: string;
|
|
23
|
+
marks?: Array<{ x: number; y: number }>;
|
|
24
|
+
onclick?: (event: MouseEvent, marks: Array<{ x: number; y: number }>) => void;
|
|
25
|
+
disabled?: boolean;
|
|
26
|
+
hideMarkers?: boolean;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
let {
|
|
30
|
+
imageUrl,
|
|
31
|
+
onclick,
|
|
32
|
+
disabled = false,
|
|
33
|
+
hideMarkers = false,
|
|
34
|
+
marks = $bindable([]),
|
|
35
|
+
}: Props = $props();
|
|
36
|
+
|
|
37
|
+
let svgElement: SVGSVGElement;
|
|
38
|
+
let viewport: SVGGraphicsElement;
|
|
39
|
+
let dotsGroup: SVGElement;
|
|
40
|
+
let container: HTMLDivElement;
|
|
41
|
+
|
|
42
|
+
let containerW = BASE_IMAGE_SIZE;
|
|
43
|
+
let containerH = BASE_IMAGE_SIZE;
|
|
44
|
+
|
|
45
|
+
let transform = $state({ x: 0, y: 0, scale: 1 });
|
|
46
|
+
|
|
47
|
+
// Panning state: null = not panning, 'ready' = mouse button down, 'active' = dragging
|
|
48
|
+
let panningState = $state<null | 'ready' | 'active'>(null);
|
|
49
|
+
let startPoint = $state({ x: 0, y: 0 });
|
|
50
|
+
let isShiftPressed = $state(false);
|
|
51
|
+
let resizeObserver: ResizeObserver;
|
|
52
|
+
|
|
53
|
+
let imageAspectRatio = $state(1); // default 1:1 ratio
|
|
54
|
+
let imageDisplayWidth = $state(0);
|
|
55
|
+
let imageDisplayHeight = $state(0);
|
|
56
|
+
|
|
57
|
+
function handleResize(entries: ResizeObserverEntry[]) {
|
|
58
|
+
const entry = entries[0];
|
|
59
|
+
if (entry) {
|
|
60
|
+
const oldWidth = imageDisplayWidth || containerW;
|
|
61
|
+
const oldHeight = imageDisplayHeight || containerH;
|
|
62
|
+
|
|
63
|
+
containerW = entry.contentRect.width;
|
|
64
|
+
containerH = entry.contentRect.height;
|
|
65
|
+
|
|
66
|
+
// If we already had dimensions and marks, update their positions
|
|
67
|
+
if (oldWidth > 0 && oldHeight > 0 && marks.length > 0) {
|
|
68
|
+
// Adjust marks positions based on new container dimensions
|
|
69
|
+
marks = marks.map((mark) => ({
|
|
70
|
+
x: (mark.x / oldWidth) * containerW,
|
|
71
|
+
y: (mark.y / oldHeight) * containerH,
|
|
72
|
+
}));
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
updateImageDimensions();
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function onImageLoad() {
|
|
80
|
+
if (container) {
|
|
81
|
+
containerW = container.clientWidth;
|
|
82
|
+
containerH = container.clientHeight;
|
|
83
|
+
|
|
84
|
+
// get original image aspect ratio
|
|
85
|
+
const img = new Image();
|
|
86
|
+
img.src = imageUrl;
|
|
87
|
+
img.onload = () => {
|
|
88
|
+
imageAspectRatio = img.naturalWidth / img.naturalHeight;
|
|
89
|
+
updateImageDimensions();
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
if (img.complete && img.naturalWidth) {
|
|
93
|
+
imageAspectRatio = img.naturalWidth / img.naturalHeight;
|
|
94
|
+
updateImageDimensions();
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function updateImageDimensions() {
|
|
100
|
+
// calculate dimensions that maintain aspect ratio within container
|
|
101
|
+
const containerRatio = containerW / containerH;
|
|
102
|
+
|
|
103
|
+
if (containerRatio > imageAspectRatio) {
|
|
104
|
+
// container is wider than image - fit to height
|
|
105
|
+
imageDisplayHeight = containerH;
|
|
106
|
+
imageDisplayWidth = containerH * imageAspectRatio;
|
|
107
|
+
} else {
|
|
108
|
+
// container is taller than image - fit to width
|
|
109
|
+
imageDisplayWidth = containerW;
|
|
110
|
+
imageDisplayHeight = containerW / imageAspectRatio;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
updateTransform();
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// Properly constrain the transform to prevent moving outside container
|
|
117
|
+
function clampTransform() {
|
|
118
|
+
// Calculate maximum translation offsets based on scale > 1
|
|
119
|
+
const maxX = 0;
|
|
120
|
+
const maxY = 0;
|
|
121
|
+
// Prevent division by zero or unexpected behavior if container dimensions aren't ready
|
|
122
|
+
const safeContainerW = containerW || 1;
|
|
123
|
+
const safeContainerH = containerH || 1;
|
|
124
|
+
const minX = safeContainerW * (1 - transform.scale);
|
|
125
|
+
const minY = safeContainerH * (1 - transform.scale);
|
|
126
|
+
|
|
127
|
+
if (transform.scale <= 1) {
|
|
128
|
+
// If scale is 1 or less, center the view
|
|
129
|
+
// Check if scale is exactly 1 before modifying to avoid floating point issues if needed
|
|
130
|
+
// Or simply reset if it goes below
|
|
131
|
+
transform.scale = 1;
|
|
132
|
+
transform.x = 0;
|
|
133
|
+
transform.y = 0;
|
|
134
|
+
} else {
|
|
135
|
+
// Apply clamping for scale > 1
|
|
136
|
+
transform.x = Math.max(minX, Math.min(maxX, transform.x));
|
|
137
|
+
transform.y = Math.max(minY, Math.min(maxY, transform.y));
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
function updateTransform() {
|
|
142
|
+
if (!viewport) return;
|
|
143
|
+
|
|
144
|
+
// update viewport transform
|
|
145
|
+
viewport.setAttribute(
|
|
146
|
+
'transform',
|
|
147
|
+
`translate(${transform.x}, ${transform.y}) scale(${transform.scale})`
|
|
148
|
+
);
|
|
149
|
+
|
|
150
|
+
// set image dimensions based on calculated size that maintains aspect ratio
|
|
151
|
+
const imageElement = viewport.querySelector('image');
|
|
152
|
+
if (imageElement) {
|
|
153
|
+
imageElement.setAttribute('width', String(imageDisplayWidth));
|
|
154
|
+
imageElement.setAttribute('height', String(imageDisplayHeight));
|
|
155
|
+
|
|
156
|
+
// center the image in the container
|
|
157
|
+
const offsetX = (containerW - imageDisplayWidth) / 2;
|
|
158
|
+
const offsetY = (containerH - imageDisplayHeight) / 2;
|
|
159
|
+
|
|
160
|
+
imageElement.setAttribute('x', String(offsetX));
|
|
161
|
+
imageElement.setAttribute('y', String(offsetY));
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// update markers to maintain consistent size
|
|
165
|
+
renderMarkers();
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
function getSvgPoint(event: MouseEvent) {
|
|
169
|
+
if (!svgElement || !container) return { x: 0, y: 0 };
|
|
170
|
+
|
|
171
|
+
// get container bounding rect
|
|
172
|
+
const rect = container.getBoundingClientRect();
|
|
173
|
+
|
|
174
|
+
// calculate position relative to container
|
|
175
|
+
const relativeX = event.clientX - rect.left;
|
|
176
|
+
const relativeY = event.clientY - rect.top;
|
|
177
|
+
|
|
178
|
+
// apply inverse of current transform to get SVG coordinates
|
|
179
|
+
const svgX = (relativeX - transform.x) / transform.scale;
|
|
180
|
+
const svgY = (relativeY - transform.y) / transform.scale;
|
|
181
|
+
|
|
182
|
+
// adjust for image position within container (letterboxing)
|
|
183
|
+
const offsetX = (containerW - imageDisplayWidth) / 2;
|
|
184
|
+
const offsetY = (containerH - imageDisplayHeight) / 2;
|
|
185
|
+
|
|
186
|
+
// return coordinates relative to the image, not the container
|
|
187
|
+
return {
|
|
188
|
+
x: svgX - offsetX,
|
|
189
|
+
y: svgY - offsetY,
|
|
190
|
+
};
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
// Render SVG markers from the marks array
|
|
194
|
+
function renderMarkers() {
|
|
195
|
+
if (!dotsGroup) return;
|
|
196
|
+
|
|
197
|
+
while (dotsGroup.firstChild) {
|
|
198
|
+
dotsGroup.removeChild(dotsGroup.firstChild);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
const offsetX = (containerW - imageDisplayWidth) / 2;
|
|
202
|
+
const offsetY = (containerH - imageDisplayHeight) / 2;
|
|
203
|
+
|
|
204
|
+
const svgns = 'http://www.w3.org/2000/svg';
|
|
205
|
+
const adjustedMarkerSize = BASE_MARKER_SIZE / transform.scale;
|
|
206
|
+
const adjustedFontSize = BASE_MARKER_FONT_SIZE / transform.scale;
|
|
207
|
+
|
|
208
|
+
marks.forEach((mark, index) => {
|
|
209
|
+
// Create a marker group
|
|
210
|
+
const group = document.createElementNS(svgns, 'g');
|
|
211
|
+
group.setAttribute('data-testid', `marker-${index + 1}`);
|
|
212
|
+
|
|
213
|
+
const markX = mark.x + offsetX;
|
|
214
|
+
const markY = mark.y + offsetY;
|
|
215
|
+
|
|
216
|
+
const circle = document.createElementNS(svgns, 'circle');
|
|
217
|
+
circle.setAttribute('cx', String(markX));
|
|
218
|
+
circle.setAttribute('cy', String(markY));
|
|
219
|
+
circle.setAttribute('r', String(adjustedMarkerSize));
|
|
220
|
+
circle.setAttribute('fill', MARKER_COLOR);
|
|
221
|
+
circle.setAttribute('class', 'drop-shadow-sm');
|
|
222
|
+
circle.setAttribute('opacity', '0.75');
|
|
223
|
+
circle.setAttribute('stroke', MARKER_BORDER_COLOR);
|
|
224
|
+
circle.setAttribute('stroke-width', String(1 / transform.scale));
|
|
225
|
+
group.appendChild(circle);
|
|
226
|
+
|
|
227
|
+
const text = document.createElementNS(svgns, 'text');
|
|
228
|
+
text.setAttribute('x', String(markX));
|
|
229
|
+
text.setAttribute('y', String(markY));
|
|
230
|
+
text.setAttribute('text-anchor', 'middle');
|
|
231
|
+
text.setAttribute('dominant-baseline', 'central');
|
|
232
|
+
text.setAttribute('fill', TEXT_COLOR);
|
|
233
|
+
text.setAttribute('font-size', String(adjustedFontSize));
|
|
234
|
+
text.setAttribute('font-weight', 'bold');
|
|
235
|
+
text.textContent = String(index + 1);
|
|
236
|
+
group.appendChild(text);
|
|
237
|
+
|
|
238
|
+
dotsGroup.appendChild(group);
|
|
239
|
+
});
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
function handleClick(event: MouseEvent) {
|
|
243
|
+
if (disabled || hideMarkers || !dotsGroup || isShiftPressed || panningState !== null) {
|
|
244
|
+
panningState = null;
|
|
245
|
+
return;
|
|
246
|
+
}
|
|
247
|
+
const pt = getSvgPoint(event);
|
|
248
|
+
marks.push({ x: pt.x, y: pt.y });
|
|
249
|
+
|
|
250
|
+
renderMarkers();
|
|
251
|
+
if (onclick) {
|
|
252
|
+
onclick(event, marks);
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
function handleKeyDown(event: KeyboardEvent) {
|
|
257
|
+
if (event.shiftKey) {
|
|
258
|
+
isShiftPressed = true;
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
function handleKeyUp(event: KeyboardEvent) {
|
|
263
|
+
let shouldResetReadyState = false;
|
|
264
|
+
|
|
265
|
+
if (!event.shiftKey && isShiftPressed) {
|
|
266
|
+
isShiftPressed = false;
|
|
267
|
+
shouldResetReadyState = true;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
// If panning was in 'ready' state and the relevant key was released, reset state
|
|
271
|
+
if (shouldResetReadyState && panningState === 'ready') {
|
|
272
|
+
panningState = null;
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
function handleMouseDown(event: MouseEvent) {
|
|
277
|
+
// Only start panning on right click, shift+left click
|
|
278
|
+
const isPanningTrigger = event.button === 2 || (event.button === 0 && isShiftPressed);
|
|
279
|
+
if (!isPanningTrigger) return;
|
|
280
|
+
|
|
281
|
+
event.preventDefault(); // Prevent default context menu or text selection
|
|
282
|
+
panningState = 'ready';
|
|
283
|
+
startPoint = { x: event.clientX, y: event.clientY };
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
// Handle panning on mouse move
|
|
287
|
+
function handleMouseMove(event: MouseEvent) {
|
|
288
|
+
if (panningState === null) return;
|
|
289
|
+
|
|
290
|
+
const dx = event.clientX - startPoint.x;
|
|
291
|
+
const dy = event.clientY - startPoint.y;
|
|
292
|
+
|
|
293
|
+
// Detect if we're dragging (moved beyond threshold)
|
|
294
|
+
if (panningState === 'ready' && Math.sqrt(dx * dx + dy * dy) > DRAG_THRESHOLD) {
|
|
295
|
+
panningState = 'active';
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
// Only update position if we're actually panning
|
|
299
|
+
if (panningState === 'active') {
|
|
300
|
+
startPoint = { x: event.clientX, y: event.clientY };
|
|
301
|
+
|
|
302
|
+
transform.x += dx;
|
|
303
|
+
transform.y += dy;
|
|
304
|
+
|
|
305
|
+
// Ensure pan stays within bounds
|
|
306
|
+
clampTransform();
|
|
307
|
+
updateTransform();
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
function handleMouseUp() {
|
|
312
|
+
panningState = null;
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
function handleMouseLeave() {
|
|
316
|
+
panningState = null;
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
function handleContextMenu(event: MouseEvent) {
|
|
320
|
+
event.preventDefault();
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
function handleWheel(event: WheelEvent) {
|
|
324
|
+
event.preventDefault();
|
|
325
|
+
|
|
326
|
+
if (!svgElement || !viewport || !container) return;
|
|
327
|
+
|
|
328
|
+
// Update container dimensions just in case they changed
|
|
329
|
+
containerW = container.clientWidth;
|
|
330
|
+
containerH = container.clientHeight;
|
|
331
|
+
|
|
332
|
+
const zoomIntensity = ZOOM_STEP;
|
|
333
|
+
const delta = event.deltaY;
|
|
334
|
+
const zoomFactor = 1 - delta * zoomIntensity;
|
|
335
|
+
const currentScale = transform.scale; // Store current scale before modification
|
|
336
|
+
|
|
337
|
+
let potentialNewScale = currentScale * zoomFactor;
|
|
338
|
+
|
|
339
|
+
// Clamp the scale
|
|
340
|
+
let finalScale = Math.max(MIN_ZOOM, Math.min(MAX_ZOOM, potentialNewScale));
|
|
341
|
+
|
|
342
|
+
if (finalScale <= MIN_ZOOM) {
|
|
343
|
+
// Reset view if zooming out below minimum or exactly at minimum
|
|
344
|
+
transform.scale = MIN_ZOOM;
|
|
345
|
+
transform.x = 0;
|
|
346
|
+
transform.y = 0;
|
|
347
|
+
} else {
|
|
348
|
+
// Calculate transform based on finalScale (which might be MAX_ZOOM)
|
|
349
|
+
const rect = container.getBoundingClientRect();
|
|
350
|
+
const mouseX = event.clientX - rect.left;
|
|
351
|
+
const mouseY = event.clientY - rect.top;
|
|
352
|
+
|
|
353
|
+
// Calculate distance from origin at current scale
|
|
354
|
+
const distX = mouseX - transform.x;
|
|
355
|
+
const distY = mouseY - transform.y;
|
|
356
|
+
|
|
357
|
+
// Calculate what the distance should be at the final scale
|
|
358
|
+
// Use currentScale from *before* this zoom event for the ratio
|
|
359
|
+
const scaleRatio = finalScale / currentScale;
|
|
360
|
+
const newDistX = distX * scaleRatio;
|
|
361
|
+
const newDistY = distY * scaleRatio;
|
|
362
|
+
|
|
363
|
+
// Update the transform to maintain the mouse position relative to the final scale
|
|
364
|
+
transform.x = mouseX - newDistX;
|
|
365
|
+
transform.y = mouseY - newDistY;
|
|
366
|
+
transform.scale = finalScale;
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
clampTransform();
|
|
370
|
+
updateTransform();
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
// Simplified undo function - just pop the last mark
|
|
374
|
+
function undo() {
|
|
375
|
+
if (marks.length === 0) return;
|
|
376
|
+
|
|
377
|
+
marks.pop();
|
|
378
|
+
marks = marks;
|
|
379
|
+
renderMarkers();
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
function reset() {
|
|
383
|
+
if (marks.length === 0) return;
|
|
384
|
+
|
|
385
|
+
marks = [];
|
|
386
|
+
renderMarkers();
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
// --- Zoom Functions ---
|
|
390
|
+
function applyZoom(zoomFactor: number) {
|
|
391
|
+
if (!container) return;
|
|
392
|
+
|
|
393
|
+
const currentScale = transform.scale;
|
|
394
|
+
const potentialNewScale = currentScale * zoomFactor;
|
|
395
|
+
const finalScale = Math.max(MIN_ZOOM, Math.min(MAX_ZOOM, potentialNewScale));
|
|
396
|
+
|
|
397
|
+
// If scale didn't change (already at min/max), do nothing
|
|
398
|
+
if (finalScale === currentScale) return;
|
|
399
|
+
|
|
400
|
+
// Get center of the container view
|
|
401
|
+
const centerX = containerW / 2;
|
|
402
|
+
const centerY = containerH / 2;
|
|
403
|
+
|
|
404
|
+
// Calculate distance from origin at current scale
|
|
405
|
+
const distX = centerX - transform.x;
|
|
406
|
+
const distY = centerY - transform.y;
|
|
407
|
+
|
|
408
|
+
// Calculate what the distance should be at the final scale
|
|
409
|
+
const scaleRatio = finalScale / currentScale;
|
|
410
|
+
const newDistX = distX * scaleRatio;
|
|
411
|
+
const newDistY = distY * scaleRatio;
|
|
412
|
+
|
|
413
|
+
// Update the transform to maintain the center position relative to the final scale
|
|
414
|
+
transform.x = centerX - newDistX;
|
|
415
|
+
transform.y = centerY - newDistY;
|
|
416
|
+
transform.scale = finalScale;
|
|
417
|
+
|
|
418
|
+
clampTransform();
|
|
419
|
+
updateTransform();
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
function zoomIn() {
|
|
423
|
+
applyZoom(1.2); // Zoom in by 20%
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
function zoomOut() {
|
|
427
|
+
applyZoom(1 / 1.2); // Zoom out by 20%
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
onMount(() => {
|
|
431
|
+
if (container) {
|
|
432
|
+
containerW = container.clientWidth;
|
|
433
|
+
containerH = container.clientHeight;
|
|
434
|
+
|
|
435
|
+
// setup resize observer
|
|
436
|
+
resizeObserver = new ResizeObserver(handleResize);
|
|
437
|
+
resizeObserver.observe(container);
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
window.addEventListener('keydown', handleKeyDown);
|
|
441
|
+
window.addEventListener('keyup', handleKeyUp);
|
|
442
|
+
|
|
443
|
+
updateTransform();
|
|
444
|
+
renderMarkers();
|
|
445
|
+
|
|
446
|
+
const img = new Image();
|
|
447
|
+
img.onload = onImageLoad;
|
|
448
|
+
img.src = imageUrl;
|
|
449
|
+
|
|
450
|
+
if (img.complete) {
|
|
451
|
+
onImageLoad();
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
return () => {
|
|
455
|
+
window.removeEventListener('keydown', handleKeyDown);
|
|
456
|
+
window.removeEventListener('keyup', handleKeyUp);
|
|
457
|
+
// cleanup resize observer
|
|
458
|
+
if (resizeObserver) {
|
|
459
|
+
resizeObserver.disconnect();
|
|
460
|
+
}
|
|
461
|
+
};
|
|
462
|
+
});
|
|
463
|
+
|
|
464
|
+
// Effect to render when marks prop changes from parent or internally
|
|
465
|
+
$effect(() => {
|
|
466
|
+
renderMarkers();
|
|
467
|
+
});
|
|
468
468
|
</script>
|
|
469
469
|
|
|
470
470
|
{#snippet TopLeftActions()}
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
471
|
+
{#if marks.length > 0}
|
|
472
|
+
<div
|
|
473
|
+
class="absolute left-2 top-2 z-20 flex items-center gap-1 rounded-lg border border-interactive-inverse bg-base-inverse p-1 opacity-0 transition-opacity duration-200 group-hover:opacity-100"
|
|
474
|
+
>
|
|
475
|
+
<IconButton
|
|
476
|
+
variant="transparent-inverse"
|
|
477
|
+
rounded={false}
|
|
478
|
+
onclick={undo}
|
|
479
|
+
disabled={marks.length === 0 || disabled}
|
|
480
|
+
aria-label="Undo last mark"
|
|
481
|
+
>
|
|
482
|
+
<Icon iconName={'ArrowUUpLeft'} />
|
|
483
|
+
</IconButton>
|
|
484
|
+
<Divider vertical inverse class="!h-5" />
|
|
485
|
+
<Button
|
|
486
|
+
variant="transparent-inverse"
|
|
487
|
+
size="sm"
|
|
488
|
+
onClick={reset}
|
|
489
|
+
disabled={marks.length === 0 || disabled}
|
|
490
|
+
accessibilityLabel="Reset all marks"
|
|
491
|
+
class="!text-primary-inverse"
|
|
492
|
+
>
|
|
493
|
+
Clear all
|
|
494
|
+
</Button>
|
|
495
|
+
</div>
|
|
496
|
+
{/if}
|
|
497
497
|
{/snippet}
|
|
498
498
|
|
|
499
499
|
{#snippet ZoomControls()}
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
500
|
+
<div
|
|
501
|
+
class="absolute bottom-2 right-2 z-20 flex flex-col items-center gap-1 rounded-md border border-interactive-inverse bg-base-inverse p-1 opacity-0 transition-opacity duration-200 group-hover:opacity-100"
|
|
502
|
+
>
|
|
503
|
+
<IconButton
|
|
504
|
+
variant="transparent-inverse"
|
|
505
|
+
rounded={false}
|
|
506
|
+
onclick={zoomIn}
|
|
507
|
+
aria-label="Zoom In"
|
|
508
|
+
disabled={transform.scale >= MAX_ZOOM || disabled}
|
|
509
|
+
>
|
|
510
|
+
<Icon iconName={'Plus'} />
|
|
511
|
+
</IconButton>
|
|
512
|
+
<IconButton
|
|
513
|
+
variant="transparent-inverse"
|
|
514
|
+
rounded={false}
|
|
515
|
+
onclick={zoomOut}
|
|
516
|
+
aria-label="Zoom Out"
|
|
517
|
+
disabled={transform.scale <= MIN_ZOOM || disabled}
|
|
518
|
+
>
|
|
519
|
+
<Icon iconName={'Minus'} />
|
|
520
|
+
</IconButton>
|
|
521
|
+
</div>
|
|
522
522
|
{/snippet}
|
|
523
523
|
|
|
524
524
|
<div bind:this={container} class="group relative h-full w-full overflow-hidden rounded-lg border">
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
525
|
+
{#if !hideMarkers && !disabled}
|
|
526
|
+
{@render TopLeftActions()}
|
|
527
|
+
{@render ZoomControls()}
|
|
528
|
+
{/if}
|
|
529
529
|
|
|
530
|
-
|
|
530
|
+
<!--
|
|
531
531
|
We need to use SVG for this interactive component.
|
|
532
532
|
The SVG element is treated as a canvas for clicking, panning, and zooming.
|
|
533
533
|
We add accessibility attributes to make it more accessible despite the interactive nature.
|
|
534
534
|
-->
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
535
|
+
<!-- svelte-ignore a11y_click_events_have_key_events -->
|
|
536
|
+
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
|
537
|
+
<!-- svelte-ignore a11y_no_noninteractive_element_interactions -->
|
|
538
|
+
<!-- svelte-ignore a11y_no_noninteractive_tabindex -->
|
|
539
|
+
<svg
|
|
540
|
+
bind:this={svgElement}
|
|
541
|
+
onclick={handleClick}
|
|
542
|
+
onmousedown={handleMouseDown}
|
|
543
|
+
onmousemove={handleMouseMove}
|
|
544
|
+
onmouseup={handleMouseUp}
|
|
545
|
+
onmouseleave={handleMouseLeave}
|
|
546
|
+
oncontextmenu={handleContextMenu}
|
|
547
|
+
onwheel={handleWheel}
|
|
548
|
+
class:cursor-grabbing={panningState === 'active'}
|
|
549
|
+
class:cursor-grab={!disabled && (panningState === 'ready' || (isShiftPressed && !panningState))}
|
|
550
|
+
class:cursor-not-allowed={disabled}
|
|
551
|
+
class:cursor-crosshair={!disabled && !panningState && !isShiftPressed}
|
|
552
|
+
class="h-full w-full"
|
|
553
|
+
role="application"
|
|
554
|
+
aria-label={disabled
|
|
555
|
+
? 'CFU Counter (disabled)'
|
|
556
|
+
: 'CFU Counter - Click to add markers, right click or shift+click to pan'}
|
|
557
|
+
tabindex="0"
|
|
558
|
+
>
|
|
559
|
+
<g bind:this={viewport} id="viewport" class="h-full w-full">
|
|
560
|
+
<image href={imageUrl} x="0" y="0" width="100%" />
|
|
561
|
+
<g
|
|
562
|
+
bind:this={dotsGroup}
|
|
563
|
+
id="dots"
|
|
564
|
+
class="pointer-events-none"
|
|
565
|
+
aria-hidden={hideMarkers}
|
|
566
|
+
class:hidden={hideMarkers}
|
|
567
|
+
/>
|
|
568
|
+
</g>
|
|
569
|
+
</svg>
|
|
570
|
+
|
|
571
|
+
<!-- Debug info for marks count - useful for testing -->
|
|
572
|
+
<span class="sr-only" data-testid="marks-count">{marks.length}</span>
|
|
573
573
|
</div>
|