@uibit/hotspot 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +27 -0
- package/custom-elements.json +390 -0
- package/dist/frameworks/angular/index.ts +39 -0
- package/dist/frameworks/astro/index.d.ts +10 -0
- package/dist/frameworks/nuxt/index.ts +7 -0
- package/dist/frameworks/preact/index.d.ts +16 -0
- package/dist/frameworks/qwik/index.tsx +10 -0
- package/dist/frameworks/react/index.d.ts +21 -0
- package/dist/frameworks/solid/index.d.ts +17 -0
- package/dist/frameworks/stencil/index.d.ts +15 -0
- package/dist/frameworks/svelte/index.svelte +47 -0
- package/dist/frameworks/vue/index.ts +27 -0
- package/dist/hotspot.d.ts +58 -0
- package/dist/hotspot.d.ts.map +1 -0
- package/dist/hotspot.js +209 -0
- package/dist/hotspot.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -0
- package/dist/styles.d.ts +2 -0
- package/dist/styles.d.ts.map +1 -0
- package/dist/styles.js +208 -0
- package/dist/styles.js.map +1 -0
- package/dist/types.d.ts +21 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/package.json +90 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 UIBit Contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# Hotspot
|
|
2
|
+
|
|
3
|
+
[Interactive Demonstration](https://rawlings.github.io/uibit/components/hotspot)
|
|
4
|
+
|
|
5
|
+
Hotspot enables interactive image exploration by overlaying contextual annotations on top of specific regions of an image. It transforms static diagrams, product mockups, and photographs into interactive guides that reveal rich details on demand, reducing clutter.
|
|
6
|
+
|
|
7
|
+
## Value Delivery
|
|
8
|
+
|
|
9
|
+
- **Contextual Annotation** – Deliver additional information or media exactly where it applies on the image.
|
|
10
|
+
- **Improved Information Hierarchy** – Keeps interface layouts clean by hiding supplementary text until requested.
|
|
11
|
+
- **Responsive Alignment** – Ensures annotations remain accurately positioned on key regions regardless of screen size or scaling.
|
|
12
|
+
- **Intuitive Discovery** – Uses subtle visual indicators to invite user interaction and exploration.
|
|
13
|
+
|
|
14
|
+
## Ideal Applications
|
|
15
|
+
|
|
16
|
+
- **Product Feature Showcases** – Highlighting materials, technologies, or specifications directly on product images.
|
|
17
|
+
- **Interactive Diagrams** – Explaining technical parts, flowcharts, or system architectures.
|
|
18
|
+
- **Virtual Tours** – Adding details to architectural layouts or real estate listings.
|
|
19
|
+
|
|
20
|
+
## Further Information
|
|
21
|
+
|
|
22
|
+
Detailed design guidelines, customizable attributes, and integration examples are available on our documentation site.
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
## Changelog
|
|
26
|
+
|
|
27
|
+
Please see the [Changelog](CHANGELOG.md) for version history.
|
|
@@ -0,0 +1,390 @@
|
|
|
1
|
+
{
|
|
2
|
+
"schemaVersion": "1.0.0",
|
|
3
|
+
"readme": "",
|
|
4
|
+
"modules": [
|
|
5
|
+
{
|
|
6
|
+
"kind": "javascript-module",
|
|
7
|
+
"path": "src/hotspot.ts",
|
|
8
|
+
"declarations": [
|
|
9
|
+
{
|
|
10
|
+
"kind": "class",
|
|
11
|
+
"description": "Interactive hotspot annotations for images. Display contextual tooltip\ncards on click or hover with custom content overlays and animations.",
|
|
12
|
+
"name": "Hotspot",
|
|
13
|
+
"cssProperties": [
|
|
14
|
+
{
|
|
15
|
+
"description": "Width and height of the trigger button",
|
|
16
|
+
"name": "--uibit-hotspot-trigger-size",
|
|
17
|
+
"default": "2rem"
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
"description": "Background color of the trigger button",
|
|
21
|
+
"name": "--uibit-hotspot-trigger-bg",
|
|
22
|
+
"default": "rgba(0,0,0,0.65)"
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
"description": "Background color of the trigger on hover/active",
|
|
26
|
+
"name": "--uibit-hotspot-trigger-bg-hover",
|
|
27
|
+
"default": "rgba(0,0,0,0.9)"
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
"description": "Border of the trigger button",
|
|
31
|
+
"name": "--uibit-hotspot-trigger-border",
|
|
32
|
+
"default": "0.125rem solid #ffffff"
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
"description": "Icon color inside the trigger button",
|
|
36
|
+
"name": "--uibit-hotspot-trigger-color",
|
|
37
|
+
"default": "#ffffff"
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
"description": "Background of the popover card",
|
|
41
|
+
"name": "--uibit-hotspot-popover-bg",
|
|
42
|
+
"default": "rgba(255,255,255,0.96)"
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
"description": "Primary text color inside the popover",
|
|
46
|
+
"name": "--uibit-hotspot-popover-color",
|
|
47
|
+
"default": "#111827"
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
"description": "Secondary content text color inside the popover",
|
|
51
|
+
"name": "--uibit-hotspot-popover-content-color",
|
|
52
|
+
"default": "#4b5563"
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
"description": "Border radius of the popover card",
|
|
56
|
+
"name": "--uibit-hotspot-popover-border-radius",
|
|
57
|
+
"default": "0.75rem"
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
"description": "Box shadow of the popover card",
|
|
61
|
+
"name": "--uibit-hotspot-popover-shadow",
|
|
62
|
+
"default": "0 0.625rem 1.5rem rgba(0,0,0,0.1)"
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
"description": "Border of the popover card",
|
|
66
|
+
"name": "--uibit-hotspot-popover-border",
|
|
67
|
+
"default": "0.0625rem solid rgba(0,0,0,0.08)"
|
|
68
|
+
}
|
|
69
|
+
],
|
|
70
|
+
"cssParts": [
|
|
71
|
+
{
|
|
72
|
+
"description": "The root container wrapping image and all hotspots",
|
|
73
|
+
"name": "container"
|
|
74
|
+
},
|
|
75
|
+
{
|
|
76
|
+
"description": "The positioning wrapper for each hotspot",
|
|
77
|
+
"name": "wrapper"
|
|
78
|
+
},
|
|
79
|
+
{
|
|
80
|
+
"description": "The circular trigger button",
|
|
81
|
+
"name": "trigger"
|
|
82
|
+
},
|
|
83
|
+
{
|
|
84
|
+
"description": "Added to the trigger when the popover is open",
|
|
85
|
+
"name": "trigger-active"
|
|
86
|
+
},
|
|
87
|
+
{
|
|
88
|
+
"description": "The popover card",
|
|
89
|
+
"name": "popover"
|
|
90
|
+
},
|
|
91
|
+
{
|
|
92
|
+
"description": "The content area inside the popover",
|
|
93
|
+
"name": "popover-content"
|
|
94
|
+
},
|
|
95
|
+
{
|
|
96
|
+
"description": "The close button inside the popover",
|
|
97
|
+
"name": "popover-close"
|
|
98
|
+
}
|
|
99
|
+
],
|
|
100
|
+
"slots": [
|
|
101
|
+
{
|
|
102
|
+
"description": "Default slot for the background image or content",
|
|
103
|
+
"name": ""
|
|
104
|
+
},
|
|
105
|
+
{
|
|
106
|
+
"description": "Custom content for the popover of a specific hotspot (by id)",
|
|
107
|
+
"name": "popover-{id}"
|
|
108
|
+
}
|
|
109
|
+
],
|
|
110
|
+
"members": [
|
|
111
|
+
{
|
|
112
|
+
"kind": "field",
|
|
113
|
+
"name": "hotspots",
|
|
114
|
+
"type": {
|
|
115
|
+
"text": "HotspotItem[]"
|
|
116
|
+
},
|
|
117
|
+
"default": "[]",
|
|
118
|
+
"description": "Array of hotspot items. Each item requires `id`, `x` (%), `y` (%), `label`, and optionally `title` and `content`.",
|
|
119
|
+
"attribute": "hotspots"
|
|
120
|
+
},
|
|
121
|
+
{
|
|
122
|
+
"kind": "field",
|
|
123
|
+
"name": "interactive",
|
|
124
|
+
"type": {
|
|
125
|
+
"text": "boolean"
|
|
126
|
+
},
|
|
127
|
+
"default": "true",
|
|
128
|
+
"description": "When `false`, hotspots are rendered but not clickable or hoverable.",
|
|
129
|
+
"attribute": "interactive"
|
|
130
|
+
},
|
|
131
|
+
{
|
|
132
|
+
"kind": "field",
|
|
133
|
+
"name": "trigger",
|
|
134
|
+
"type": {
|
|
135
|
+
"text": "'click' | 'hover'"
|
|
136
|
+
},
|
|
137
|
+
"default": "'click'",
|
|
138
|
+
"description": "Interaction mode for showing the popover.",
|
|
139
|
+
"attribute": "trigger"
|
|
140
|
+
},
|
|
141
|
+
{
|
|
142
|
+
"kind": "field",
|
|
143
|
+
"name": "activeHotspotId",
|
|
144
|
+
"type": {
|
|
145
|
+
"text": "string | null"
|
|
146
|
+
},
|
|
147
|
+
"privacy": "private",
|
|
148
|
+
"default": "null"
|
|
149
|
+
},
|
|
150
|
+
{
|
|
151
|
+
"kind": "field",
|
|
152
|
+
"name": "hoverTimeout",
|
|
153
|
+
"type": {
|
|
154
|
+
"text": "number | undefined"
|
|
155
|
+
},
|
|
156
|
+
"privacy": "private"
|
|
157
|
+
},
|
|
158
|
+
{
|
|
159
|
+
"kind": "field",
|
|
160
|
+
"name": "handleDocumentKeyDown",
|
|
161
|
+
"privacy": "private"
|
|
162
|
+
},
|
|
163
|
+
{
|
|
164
|
+
"kind": "field",
|
|
165
|
+
"name": "handleDocumentClick",
|
|
166
|
+
"privacy": "private"
|
|
167
|
+
},
|
|
168
|
+
{
|
|
169
|
+
"kind": "method",
|
|
170
|
+
"name": "handleTriggerClick",
|
|
171
|
+
"privacy": "private",
|
|
172
|
+
"parameters": [
|
|
173
|
+
{
|
|
174
|
+
"name": "spot",
|
|
175
|
+
"type": {
|
|
176
|
+
"text": "HotspotItem"
|
|
177
|
+
}
|
|
178
|
+
},
|
|
179
|
+
{
|
|
180
|
+
"name": "spotId",
|
|
181
|
+
"type": {
|
|
182
|
+
"text": "string"
|
|
183
|
+
}
|
|
184
|
+
},
|
|
185
|
+
{
|
|
186
|
+
"name": "e",
|
|
187
|
+
"type": {
|
|
188
|
+
"text": "Event"
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
]
|
|
192
|
+
},
|
|
193
|
+
{
|
|
194
|
+
"kind": "method",
|
|
195
|
+
"name": "handleTriggerMouseEnter",
|
|
196
|
+
"privacy": "private",
|
|
197
|
+
"parameters": [
|
|
198
|
+
{
|
|
199
|
+
"name": "spotId",
|
|
200
|
+
"type": {
|
|
201
|
+
"text": "string"
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
]
|
|
205
|
+
},
|
|
206
|
+
{
|
|
207
|
+
"kind": "method",
|
|
208
|
+
"name": "handleTriggerMouseLeave",
|
|
209
|
+
"privacy": "private"
|
|
210
|
+
},
|
|
211
|
+
{
|
|
212
|
+
"kind": "method",
|
|
213
|
+
"name": "handlePopoverMouseEnter",
|
|
214
|
+
"privacy": "private"
|
|
215
|
+
},
|
|
216
|
+
{
|
|
217
|
+
"kind": "method",
|
|
218
|
+
"name": "handlePopoverMouseLeave",
|
|
219
|
+
"privacy": "private"
|
|
220
|
+
},
|
|
221
|
+
{
|
|
222
|
+
"kind": "method",
|
|
223
|
+
"name": "scheduleClose",
|
|
224
|
+
"privacy": "private"
|
|
225
|
+
},
|
|
226
|
+
{
|
|
227
|
+
"kind": "method",
|
|
228
|
+
"name": "clearHoverTimeout",
|
|
229
|
+
"privacy": "private"
|
|
230
|
+
},
|
|
231
|
+
{
|
|
232
|
+
"kind": "method",
|
|
233
|
+
"name": "closePopover",
|
|
234
|
+
"privacy": "private"
|
|
235
|
+
},
|
|
236
|
+
{
|
|
237
|
+
"kind": "method",
|
|
238
|
+
"name": "onHotspotClick",
|
|
239
|
+
"privacy": "private",
|
|
240
|
+
"parameters": [
|
|
241
|
+
{
|
|
242
|
+
"name": "spot",
|
|
243
|
+
"type": {
|
|
244
|
+
"text": "HotspotItem"
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
]
|
|
248
|
+
}
|
|
249
|
+
],
|
|
250
|
+
"events": [
|
|
251
|
+
{
|
|
252
|
+
"type": {
|
|
253
|
+
"text": "HotspotItem"
|
|
254
|
+
},
|
|
255
|
+
"description": "Fired when a hotspot trigger is activated",
|
|
256
|
+
"name": "hotspot-click"
|
|
257
|
+
}
|
|
258
|
+
],
|
|
259
|
+
"attributes": [
|
|
260
|
+
{
|
|
261
|
+
"name": "hotspots",
|
|
262
|
+
"type": {
|
|
263
|
+
"text": "HotspotItem[]"
|
|
264
|
+
},
|
|
265
|
+
"default": "[]",
|
|
266
|
+
"description": "Array of hotspot items. Each item requires `id`, `x` (%), `y` (%), `label`, and optionally `title` and `content`.",
|
|
267
|
+
"fieldName": "hotspots"
|
|
268
|
+
},
|
|
269
|
+
{
|
|
270
|
+
"name": "interactive",
|
|
271
|
+
"type": {
|
|
272
|
+
"text": "boolean"
|
|
273
|
+
},
|
|
274
|
+
"default": "true",
|
|
275
|
+
"description": "When `false`, hotspots are rendered but not clickable or hoverable.",
|
|
276
|
+
"fieldName": "interactive"
|
|
277
|
+
},
|
|
278
|
+
{
|
|
279
|
+
"name": "trigger",
|
|
280
|
+
"type": {
|
|
281
|
+
"text": "'click' | 'hover'"
|
|
282
|
+
},
|
|
283
|
+
"default": "'click'",
|
|
284
|
+
"description": "Interaction mode for showing the popover.",
|
|
285
|
+
"fieldName": "trigger"
|
|
286
|
+
}
|
|
287
|
+
],
|
|
288
|
+
"superclass": {
|
|
289
|
+
"name": "UIBitElement",
|
|
290
|
+
"package": "@uibit/core"
|
|
291
|
+
},
|
|
292
|
+
"tagName": "uibit-hotspot",
|
|
293
|
+
"customElement": true
|
|
294
|
+
}
|
|
295
|
+
],
|
|
296
|
+
"exports": [
|
|
297
|
+
{
|
|
298
|
+
"kind": "js",
|
|
299
|
+
"name": "Hotspot",
|
|
300
|
+
"declaration": {
|
|
301
|
+
"name": "Hotspot",
|
|
302
|
+
"module": "src/hotspot.ts"
|
|
303
|
+
}
|
|
304
|
+
},
|
|
305
|
+
{
|
|
306
|
+
"kind": "custom-element-definition",
|
|
307
|
+
"name": "uibit-hotspot",
|
|
308
|
+
"declaration": {
|
|
309
|
+
"name": "Hotspot",
|
|
310
|
+
"module": "src/hotspot.ts"
|
|
311
|
+
}
|
|
312
|
+
},
|
|
313
|
+
{
|
|
314
|
+
"kind": "js",
|
|
315
|
+
"name": "default",
|
|
316
|
+
"declaration": {
|
|
317
|
+
"name": "Hotspot",
|
|
318
|
+
"module": "src/hotspot.ts"
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
]
|
|
322
|
+
},
|
|
323
|
+
{
|
|
324
|
+
"kind": "javascript-module",
|
|
325
|
+
"path": "src/index.ts",
|
|
326
|
+
"declarations": [],
|
|
327
|
+
"exports": [
|
|
328
|
+
{
|
|
329
|
+
"kind": "js",
|
|
330
|
+
"name": "default",
|
|
331
|
+
"declaration": {
|
|
332
|
+
"name": "default",
|
|
333
|
+
"module": "./hotspot"
|
|
334
|
+
}
|
|
335
|
+
},
|
|
336
|
+
{
|
|
337
|
+
"kind": "js",
|
|
338
|
+
"name": "Hotspot",
|
|
339
|
+
"declaration": {
|
|
340
|
+
"name": "Hotspot",
|
|
341
|
+
"module": "./hotspot"
|
|
342
|
+
}
|
|
343
|
+
},
|
|
344
|
+
{
|
|
345
|
+
"kind": "js",
|
|
346
|
+
"name": "HotspotItem",
|
|
347
|
+
"declaration": {
|
|
348
|
+
"name": "HotspotItem",
|
|
349
|
+
"module": "./types"
|
|
350
|
+
}
|
|
351
|
+
},
|
|
352
|
+
{
|
|
353
|
+
"kind": "js",
|
|
354
|
+
"name": "HotspotConfig",
|
|
355
|
+
"declaration": {
|
|
356
|
+
"name": "HotspotConfig",
|
|
357
|
+
"module": "./types"
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
]
|
|
361
|
+
},
|
|
362
|
+
{
|
|
363
|
+
"kind": "javascript-module",
|
|
364
|
+
"path": "src/styles.ts",
|
|
365
|
+
"declarations": [
|
|
366
|
+
{
|
|
367
|
+
"kind": "variable",
|
|
368
|
+
"name": "styles",
|
|
369
|
+
"default": "css` :host { display: block; position: relative; width: 100%; --uibit-hotspot-trigger-size: 1.75rem; --uibit-hotspot-trigger-bg: #ffffff; --uibit-hotspot-trigger-bg-hover: #f3f4f6; --uibit-hotspot-trigger-border: 0.0625rem solid #e5e7eb; --uibit-hotspot-trigger-color: #111827; --uibit-hotspot-popover-bg: rgba(255, 255, 255, 0.96); --uibit-hotspot-popover-color: #111827; --uibit-hotspot-popover-content-color: #4b5563; --uibit-hotspot-popover-border-radius: 0.5rem; --uibit-hotspot-popover-border: 0.0625rem solid #e5e7eb; --uibit-hotspot-popover-shadow: 0 0.25rem 1rem rgba(0, 0, 0, 0.04); } .hotspot-container { position: relative; width: 100%; } .hotspot-wrapper { position: absolute; transform: translate(-50%, -50%); z-index: 10; } .hotspot-trigger { width: var(--uibit-hotspot-trigger-size, 1.75rem); height: var(--uibit-hotspot-trigger-size, 1.75rem); background: var(--uibit-hotspot-trigger-bg, #ffffff); border: var(--uibit-hotspot-trigger-border, 0.0625rem solid #e5e7eb); border-radius: 9999rem; cursor: pointer; display: flex; align-items: center; justify-content: center; box-shadow: none; position: relative; transition: transform 150ms ease, background-color 150ms ease, border-color 150ms ease, color 150ms ease; outline: none; padding: 0; } .hotspot-trigger::before { content: ''; position: absolute; inset: -0.0625rem; border-radius: 9999rem; border: 0.0625rem solid var(--uibit-hotspot-trigger-border-color-actual, #e5e7eb); background: transparent; animation: pulse 2.5s ease-out infinite; z-index: -1; opacity: 0; } @keyframes pulse { 0% { transform: scale(1); opacity: 0.8; } 70% { transform: scale(1.6); opacity: 0; } 100% { transform: scale(1.6); opacity: 0; } } .hotspot-trigger:hover, .hotspot-trigger.active { background: var(--uibit-hotspot-trigger-bg-hover, #f3f4f6); border-color: #d1d5db; transform: scale(1.06); } .hotspot-trigger:active { transform: scale(0.95); } .hotspot-trigger:focus-visible { outline: 0.125rem solid #000000; outline-offset: 0.125rem; } .hotspot-trigger svg { width: 0.875rem; height: 0.875rem; color: var(--uibit-hotspot-trigger-color, #111827); pointer-events: none; } .hotspot-popover { position: absolute; width: 15rem; background: var(--uibit-hotspot-popover-bg, rgba(255, 255, 255, 0.96)); backdrop-filter: blur(0.75rem); -webkit-backdrop-filter: blur(0.75rem); color: var(--uibit-hotspot-popover-color, #111827); padding: 0.875rem 1rem; border-radius: var(--uibit-hotspot-popover-border-radius, 0.75rem); box-shadow: var( --uibit-hotspot-popover-shadow, 0 0.625rem 1.5rem rgba(0, 0, 0, 0.1), 0 0.25rem 0.5rem rgba(0, 0, 0, 0.06) ); border: var(--uibit-hotspot-popover-border, 0.0625rem solid rgba(0, 0, 0, 0.08)); z-index: 20; display: flex; flex-direction: column; animation: fadeIn 200ms ease; } @keyframes fadeIn { from { opacity: 0; transform: translate(-50%, 0.5rem); } to { opacity: 1; transform: translate(-50%, 0); } } .hotspot-popover.position-above { bottom: 1.5rem; left: 50%; transform: translateX(-50%); } .hotspot-popover.position-below { top: 1.5rem; left: 50%; transform: translateX(-50%); } .hotspot-popover::after { content: ''; position: absolute; border-style: solid; left: 50%; transform: translateX(-50%); } .hotspot-popover.position-above::after { top: 100%; border-width: 0.375rem 0.375rem 0 0.375rem; border-color: var(--uibit-hotspot-popover-bg, rgba(255, 255, 255, 0.96)) transparent transparent transparent; } .hotspot-popover.position-below::after { bottom: 100%; border-width: 0 0.375rem 0.375rem 0.375rem; border-color: transparent transparent var(--uibit-hotspot-popover-bg, rgba(255, 255, 255, 0.96)) transparent; } .popover-content h3 { margin: 0 0 0.375rem 0; font-size: var(--uibit-font-size-base, 0.9375rem); font-weight: var(--uibit-font-weight-semibold, 600); letter-spacing: var(--uibit-letter-spacing-tight, -0.01em); color: var(--uibit-hotspot-popover-color, #111827); } .popover-content p { margin: 0; font-size: var(--uibit-font-size-sm, 0.8125rem); line-height: var(--uibit-line-height-relaxed, 1.625); color: var(--uibit-hotspot-popover-content-color, #4b5563); } .popover-close { position: absolute; top: 0.5rem; right: 0.5rem; background: transparent; border: none; color: #9ca3af; cursor: pointer; width: 1.5rem; height: 1.5rem; display: flex; align-items: center; justify-content: center; border-radius: 9999rem; transition: background-color 150ms ease, color 150ms ease, transform 150ms ease; } .popover-close:hover { background-color: #f3f4f6; color: #111827; } .popover-close:active { transform: scale(0.92); } .popover-close:focus-visible { outline: 0.125rem solid #000000; outline-offset: 0.125rem; } `"
|
|
370
|
+
}
|
|
371
|
+
],
|
|
372
|
+
"exports": [
|
|
373
|
+
{
|
|
374
|
+
"kind": "js",
|
|
375
|
+
"name": "styles",
|
|
376
|
+
"declaration": {
|
|
377
|
+
"name": "styles",
|
|
378
|
+
"module": "src/styles.ts"
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
]
|
|
382
|
+
},
|
|
383
|
+
{
|
|
384
|
+
"kind": "javascript-module",
|
|
385
|
+
"path": "src/types.ts",
|
|
386
|
+
"declarations": [],
|
|
387
|
+
"exports": []
|
|
388
|
+
}
|
|
389
|
+
]
|
|
390
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { Component, ChangeDetectionStrategy, ElementRef, input, effect, output, booleanAttribute } from '@angular/core';
|
|
2
|
+
import '@uibit/hotspot';
|
|
3
|
+
import type { Hotspot as HTMLElementClass } from '@uibit/hotspot';
|
|
4
|
+
import type { HotspotItem } from '@uibit/hotspot';
|
|
5
|
+
|
|
6
|
+
@Component({
|
|
7
|
+
selector: 'uibit-hotspot',
|
|
8
|
+
template: '<ng-content></ng-content>',
|
|
9
|
+
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
10
|
+
standalone: true,
|
|
11
|
+
host: {
|
|
12
|
+
'(hotspot-click)': 'hotspotClick.emit($event)'
|
|
13
|
+
}
|
|
14
|
+
})
|
|
15
|
+
export class NgxHotspot {
|
|
16
|
+
constructor(private el: ElementRef<HTMLElementClass>) {
|
|
17
|
+
effect(() => {
|
|
18
|
+
if (this.el.nativeElement) {
|
|
19
|
+
this.el.nativeElement.hotspots = this.hotspots();
|
|
20
|
+
}
|
|
21
|
+
});
|
|
22
|
+
effect(() => {
|
|
23
|
+
if (this.el.nativeElement) {
|
|
24
|
+
this.el.nativeElement.interactive = this.interactive();
|
|
25
|
+
}
|
|
26
|
+
});
|
|
27
|
+
effect(() => {
|
|
28
|
+
if (this.el.nativeElement) {
|
|
29
|
+
this.el.nativeElement.trigger = this.trigger();
|
|
30
|
+
}
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
readonly hotspots = input<HotspotItem[], any>([]);
|
|
35
|
+
readonly interactive = input<boolean, any>(true, { transform: booleanAttribute });
|
|
36
|
+
readonly trigger = input<'click' | 'hover', any>('click');
|
|
37
|
+
|
|
38
|
+
readonly hotspotClick = output<CustomEvent<HotspotItem>>();
|
|
39
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { Hotspot as HTMLElementClass } from '@uibit/hotspot';
|
|
2
|
+
import '@uibit/hotspot';
|
|
3
|
+
|
|
4
|
+
declare global {
|
|
5
|
+
namespace astroHTML.JSX {
|
|
6
|
+
interface IntrinsicElements {
|
|
7
|
+
'uibit-hotspot': Partial<HTMLElementClass> & astroHTML.JSX.HTMLAttributes;
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { JSX } from 'preact';
|
|
2
|
+
import type { Hotspot as HTMLElementClass } from '@uibit/hotspot';
|
|
3
|
+
import '@uibit/hotspot';
|
|
4
|
+
import type { HotspotItem } from '@uibit/hotspot';
|
|
5
|
+
|
|
6
|
+
declare module 'preact' {
|
|
7
|
+
namespace JSX {
|
|
8
|
+
interface IntrinsicElements {
|
|
9
|
+
'uibit-hotspot': JSX.HTMLAttributes<HTMLElementClass> & {
|
|
10
|
+
hotspots?: HotspotItem[];
|
|
11
|
+
interactive?: boolean;
|
|
12
|
+
trigger?: 'click' | 'hover';
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { HTMLAttributes, ClassAttributes } from 'react';
|
|
2
|
+
import type { Hotspot as HTMLElementClass } from '@uibit/hotspot';
|
|
3
|
+
import '@uibit/hotspot';
|
|
4
|
+
import type { HotspotItem } from '@uibit/hotspot';
|
|
5
|
+
|
|
6
|
+
declare global {
|
|
7
|
+
namespace React {
|
|
8
|
+
namespace JSX {
|
|
9
|
+
interface IntrinsicElements {
|
|
10
|
+
'uibit-hotspot': ClassAttributes<HTMLElementClass> & HTMLAttributes<HTMLElementClass> & {
|
|
11
|
+
children?: React.ReactNode;
|
|
12
|
+
class?: string;
|
|
13
|
+
hotspots?: HotspotItem[];
|
|
14
|
+
interactive?: boolean;
|
|
15
|
+
trigger?: 'click' | 'hover';
|
|
16
|
+
onHotspotClick?: (event: any) => void;
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { JSX } from 'solid-js';
|
|
2
|
+
import type { Hotspot as HTMLElementClass } from '@uibit/hotspot';
|
|
3
|
+
import '@uibit/hotspot';
|
|
4
|
+
import type { HotspotItem } from '@uibit/hotspot';
|
|
5
|
+
|
|
6
|
+
declare module 'solid-js' {
|
|
7
|
+
namespace JSX {
|
|
8
|
+
interface IntrinsicElements {
|
|
9
|
+
'uibit-hotspot': Partial<HTMLElementClass> & JSX.HTMLAttributes<HTMLElementClass> & {
|
|
10
|
+
hotspots?: HotspotItem[];
|
|
11
|
+
interactive?: boolean;
|
|
12
|
+
trigger?: 'click' | 'hover';
|
|
13
|
+
"on:hotspot-click"?: (event: CustomEvent) => void;
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { Hotspot as HTMLElementClass } from '@uibit/hotspot';
|
|
2
|
+
import '@uibit/hotspot';
|
|
3
|
+
import type { HotspotItem } from '@uibit/hotspot';
|
|
4
|
+
|
|
5
|
+
declare module '@stencil/core' {
|
|
6
|
+
export namespace JSX {
|
|
7
|
+
interface IntrinsicElements {
|
|
8
|
+
'uibit-hotspot': HTMLElementClass & {
|
|
9
|
+
hotspots?: HotspotItem[];
|
|
10
|
+
interactive?: boolean;
|
|
11
|
+
trigger?: 'click' | 'hover';
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import '@uibit/hotspot';
|
|
3
|
+
import type { Hotspot as HTMLElementClass } from '@uibit/hotspot';
|
|
4
|
+
import type { HotspotItem } from '@uibit/hotspot';
|
|
5
|
+
|
|
6
|
+
let {
|
|
7
|
+
hotspots = undefined,
|
|
8
|
+
interactive = undefined,
|
|
9
|
+
trigger = undefined,
|
|
10
|
+
popover-{id} = undefined,
|
|
11
|
+
children
|
|
12
|
+
} = $props<{
|
|
13
|
+
children?: any;
|
|
14
|
+
hotspots?: HotspotItem[];
|
|
15
|
+
interactive?: boolean;
|
|
16
|
+
trigger?: 'click' | 'hover';
|
|
17
|
+
popover-{id}?: import('svelte').Snippet;
|
|
18
|
+
}>();
|
|
19
|
+
|
|
20
|
+
let elementRef: HTMLElementClass | null = $state(null);
|
|
21
|
+
|
|
22
|
+
$effect(() => {
|
|
23
|
+
if (elementRef && hotspots !== undefined) {
|
|
24
|
+
elementRef.hotspots = hotspots;
|
|
25
|
+
}
|
|
26
|
+
if (elementRef && interactive !== undefined) {
|
|
27
|
+
elementRef.interactive = interactive;
|
|
28
|
+
}
|
|
29
|
+
if (elementRef && trigger !== undefined) {
|
|
30
|
+
elementRef.trigger = trigger;
|
|
31
|
+
}
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
</script>
|
|
35
|
+
|
|
36
|
+
<uibit-hotspot bind:this={elementRef} {...$$restProps}>
|
|
37
|
+
{#if popover-{id}}
|
|
38
|
+
<div slot="popover-{id}">
|
|
39
|
+
{@render popover-{id}()}
|
|
40
|
+
</div>
|
|
41
|
+
{/if}
|
|
42
|
+
{#if children}
|
|
43
|
+
{@render children()}
|
|
44
|
+
{:else}
|
|
45
|
+
<slot />
|
|
46
|
+
{/if}
|
|
47
|
+
</uibit-hotspot>
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { defineComponent, h } from 'vue';
|
|
2
|
+
import type { Hotspot as HTMLElementClass } from '@uibit/hotspot';
|
|
3
|
+
import '@uibit/hotspot';
|
|
4
|
+
import type { HotspotItem } from '@uibit/hotspot';
|
|
5
|
+
|
|
6
|
+
export const Hotspot = defineComponent({
|
|
7
|
+
name: 'Hotspot',
|
|
8
|
+
props: {
|
|
9
|
+
hotspots: { type: [String, Number, Boolean, Array, Object] as any },
|
|
10
|
+
interactive: { type: [String, Number, Boolean, Array, Object] as any },
|
|
11
|
+
trigger: { type: [String, Number, Boolean, Array, Object] as any }
|
|
12
|
+
},
|
|
13
|
+
emits: ['hotspot-click'],
|
|
14
|
+
setup(props, { slots, emit }) {
|
|
15
|
+
return () => {
|
|
16
|
+
const eventListeners: Record<string, any> = {};
|
|
17
|
+
eventListeners['onHotspot-click'] = (event: Event) => {
|
|
18
|
+
emit('hotspot-click', event);
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
return h('uibit-hotspot', {
|
|
22
|
+
...props,
|
|
23
|
+
...eventListeners
|
|
24
|
+
}, slots.default?.());
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
});
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { UIBitElement } from '@uibit/core';
|
|
2
|
+
import type { HotspotItem } from './types';
|
|
3
|
+
/**
|
|
4
|
+
* Interactive hotspot annotations for images. Display contextual tooltip
|
|
5
|
+
* cards on click or hover with custom content overlays and animations.
|
|
6
|
+
*
|
|
7
|
+
* @fires {HotspotItem} hotspot-click - Fired when a hotspot trigger is activated
|
|
8
|
+
*
|
|
9
|
+
* @slot - Default slot for the background image or content
|
|
10
|
+
* @slot popover-{id} - Custom content for the popover of a specific hotspot (by id)
|
|
11
|
+
*
|
|
12
|
+
* @cssprop [--uibit-hotspot-trigger-size=2rem] - Width and height of the trigger button
|
|
13
|
+
* @cssprop [--uibit-hotspot-trigger-bg=rgba(0,0,0,0.65)] - Background color of the trigger button
|
|
14
|
+
* @cssprop [--uibit-hotspot-trigger-bg-hover=rgba(0,0,0,0.9)] - Background color of the trigger on hover/active
|
|
15
|
+
* @cssprop [--uibit-hotspot-trigger-border=0.125rem solid #ffffff] - Border of the trigger button
|
|
16
|
+
* @cssprop [--uibit-hotspot-trigger-color=#ffffff] - Icon color inside the trigger button
|
|
17
|
+
* @cssprop [--uibit-hotspot-popover-bg=rgba(255,255,255,0.96)] - Background of the popover card
|
|
18
|
+
* @cssprop [--uibit-hotspot-popover-color=#111827] - Primary text color inside the popover
|
|
19
|
+
* @cssprop [--uibit-hotspot-popover-content-color=#4b5563] - Secondary content text color inside the popover
|
|
20
|
+
* @cssprop [--uibit-hotspot-popover-border-radius=0.75rem] - Border radius of the popover card
|
|
21
|
+
* @cssprop [--uibit-hotspot-popover-shadow=0 0.625rem 1.5rem rgba(0,0,0,0.1)] - Box shadow of the popover card
|
|
22
|
+
* @cssprop [--uibit-hotspot-popover-border=0.0625rem solid rgba(0,0,0,0.08)] - Border of the popover card
|
|
23
|
+
*
|
|
24
|
+
* @csspart container - The root container wrapping image and all hotspots
|
|
25
|
+
* @csspart wrapper - The positioning wrapper for each hotspot
|
|
26
|
+
* @csspart trigger - The circular trigger button
|
|
27
|
+
* @csspart trigger-active - Added to the trigger when the popover is open
|
|
28
|
+
* @csspart popover - The popover card
|
|
29
|
+
* @csspart popover-content - The content area inside the popover
|
|
30
|
+
* @csspart popover-close - The close button inside the popover
|
|
31
|
+
*/
|
|
32
|
+
export declare class Hotspot extends UIBitElement {
|
|
33
|
+
static styles: import("lit").CSSResult;
|
|
34
|
+
/** Array of hotspot items. Each item requires `id`, `x` (%), `y` (%), `label`, and optionally `title` and `content`. */
|
|
35
|
+
hotspots: HotspotItem[];
|
|
36
|
+
/** When `false`, hotspots are rendered but not clickable or hoverable. */
|
|
37
|
+
interactive: boolean;
|
|
38
|
+
/** Interaction mode for showing the popover. */
|
|
39
|
+
trigger: 'click' | 'hover';
|
|
40
|
+
private activeHotspotId;
|
|
41
|
+
private hoverTimeout?;
|
|
42
|
+
connectedCallback(): void;
|
|
43
|
+
disconnectedCallback(): void;
|
|
44
|
+
private handleDocumentKeyDown;
|
|
45
|
+
private handleDocumentClick;
|
|
46
|
+
private handleTriggerClick;
|
|
47
|
+
private handleTriggerMouseEnter;
|
|
48
|
+
private handleTriggerMouseLeave;
|
|
49
|
+
private handlePopoverMouseEnter;
|
|
50
|
+
private handlePopoverMouseLeave;
|
|
51
|
+
private scheduleClose;
|
|
52
|
+
private clearHoverTimeout;
|
|
53
|
+
private closePopover;
|
|
54
|
+
private onHotspotClick;
|
|
55
|
+
render(): import("lit").TemplateResult<1>;
|
|
56
|
+
}
|
|
57
|
+
export default Hotspot;
|
|
58
|
+
//# sourceMappingURL=hotspot.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hotspot.d.ts","sourceRoot":"","sources":["../src/hotspot.ts"],"names":[],"mappings":"AACA,OAAO,EAA2C,YAAY,EAAE,MAAM,aAAa,CAAC;AAGpF,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,SAAS,CAAC;AAG3C;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH,qBACa,OAAQ,SAAQ,YAAY;IACvC,MAAM,CAAC,MAAM,0BAAU;IAEvB,wHAAwH;IAC7F,QAAQ,EAAE,WAAW,EAAE,CAAM;IACxD,0EAA0E;IAC7C,WAAW,UAAQ;IAChD,gDAAgD;IACpB,OAAO,EAAE,OAAO,GAAG,OAAO,CAAW;IAExD,OAAO,CAAC,eAAe,CAAuB;IAEvD,OAAO,CAAC,YAAY,CAAC,CAAS;IAE9B,iBAAiB,SAIhB;IAED,oBAAoB,SAKnB;IAED,OAAO,CAAC,qBAAqB,CAI3B;IAEF,OAAO,CAAC,mBAAmB,CAMzB;IAEF,OAAO,CAAC,kBAAkB;IAe1B,OAAO,CAAC,uBAAuB;IAM/B,OAAO,CAAC,uBAAuB;IAK/B,OAAO,CAAC,uBAAuB;IAK/B,OAAO,CAAC,uBAAuB;IAK/B,OAAO,CAAC,aAAa;IAOrB,OAAO,CAAC,iBAAiB;IAOzB,OAAO,CAAC,YAAY;IAIpB,OAAO,CAAC,cAAc;IAItB,MAAM,oCA+DL;CACF;eAEc,OAAO"}
|
package/dist/hotspot.js
ADDED
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
2
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
3
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
4
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
5
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
6
|
+
};
|
|
7
|
+
import { html } from 'lit';
|
|
8
|
+
import { customElement, fromLucide, getIcon, msg, UIBitElement } from '@uibit/core';
|
|
9
|
+
import { Plus, X } from 'lucide';
|
|
10
|
+
import { property, state } from 'lit/decorators.js';
|
|
11
|
+
import { styles } from './styles';
|
|
12
|
+
/**
|
|
13
|
+
* Interactive hotspot annotations for images. Display contextual tooltip
|
|
14
|
+
* cards on click or hover with custom content overlays and animations.
|
|
15
|
+
*
|
|
16
|
+
* @fires {HotspotItem} hotspot-click - Fired when a hotspot trigger is activated
|
|
17
|
+
*
|
|
18
|
+
* @slot - Default slot for the background image or content
|
|
19
|
+
* @slot popover-{id} - Custom content for the popover of a specific hotspot (by id)
|
|
20
|
+
*
|
|
21
|
+
* @cssprop [--uibit-hotspot-trigger-size=2rem] - Width and height of the trigger button
|
|
22
|
+
* @cssprop [--uibit-hotspot-trigger-bg=rgba(0,0,0,0.65)] - Background color of the trigger button
|
|
23
|
+
* @cssprop [--uibit-hotspot-trigger-bg-hover=rgba(0,0,0,0.9)] - Background color of the trigger on hover/active
|
|
24
|
+
* @cssprop [--uibit-hotspot-trigger-border=0.125rem solid #ffffff] - Border of the trigger button
|
|
25
|
+
* @cssprop [--uibit-hotspot-trigger-color=#ffffff] - Icon color inside the trigger button
|
|
26
|
+
* @cssprop [--uibit-hotspot-popover-bg=rgba(255,255,255,0.96)] - Background of the popover card
|
|
27
|
+
* @cssprop [--uibit-hotspot-popover-color=#111827] - Primary text color inside the popover
|
|
28
|
+
* @cssprop [--uibit-hotspot-popover-content-color=#4b5563] - Secondary content text color inside the popover
|
|
29
|
+
* @cssprop [--uibit-hotspot-popover-border-radius=0.75rem] - Border radius of the popover card
|
|
30
|
+
* @cssprop [--uibit-hotspot-popover-shadow=0 0.625rem 1.5rem rgba(0,0,0,0.1)] - Box shadow of the popover card
|
|
31
|
+
* @cssprop [--uibit-hotspot-popover-border=0.0625rem solid rgba(0,0,0,0.08)] - Border of the popover card
|
|
32
|
+
*
|
|
33
|
+
* @csspart container - The root container wrapping image and all hotspots
|
|
34
|
+
* @csspart wrapper - The positioning wrapper for each hotspot
|
|
35
|
+
* @csspart trigger - The circular trigger button
|
|
36
|
+
* @csspart trigger-active - Added to the trigger when the popover is open
|
|
37
|
+
* @csspart popover - The popover card
|
|
38
|
+
* @csspart popover-content - The content area inside the popover
|
|
39
|
+
* @csspart popover-close - The close button inside the popover
|
|
40
|
+
*/
|
|
41
|
+
let Hotspot = class Hotspot extends UIBitElement {
|
|
42
|
+
constructor() {
|
|
43
|
+
super(...arguments);
|
|
44
|
+
/** Array of hotspot items. Each item requires `id`, `x` (%), `y` (%), `label`, and optionally `title` and `content`. */
|
|
45
|
+
this.hotspots = [];
|
|
46
|
+
/** When `false`, hotspots are rendered but not clickable or hoverable. */
|
|
47
|
+
this.interactive = true;
|
|
48
|
+
/** Interaction mode for showing the popover. */
|
|
49
|
+
this.trigger = 'click';
|
|
50
|
+
this.activeHotspotId = null;
|
|
51
|
+
this.handleDocumentKeyDown = (e) => {
|
|
52
|
+
if (e.key === 'Escape') {
|
|
53
|
+
this.closePopover();
|
|
54
|
+
}
|
|
55
|
+
};
|
|
56
|
+
this.handleDocumentClick = (e) => {
|
|
57
|
+
if (this.trigger !== 'click')
|
|
58
|
+
return;
|
|
59
|
+
const path = e.composedPath();
|
|
60
|
+
if (!path.includes(this)) {
|
|
61
|
+
this.closePopover();
|
|
62
|
+
}
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
static { this.styles = styles; }
|
|
66
|
+
connectedCallback() {
|
|
67
|
+
super.connectedCallback();
|
|
68
|
+
this.listen(document, 'keydown', this.handleDocumentKeyDown);
|
|
69
|
+
this.listen(document, 'click', this.handleDocumentClick);
|
|
70
|
+
}
|
|
71
|
+
disconnectedCallback() {
|
|
72
|
+
super.disconnectedCallback();
|
|
73
|
+
if (this.hoverTimeout) {
|
|
74
|
+
clearTimeout(this.hoverTimeout);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
handleTriggerClick(spot, spotId, e) {
|
|
78
|
+
e.stopPropagation();
|
|
79
|
+
if (!this.interactive)
|
|
80
|
+
return;
|
|
81
|
+
if (this.trigger === 'click') {
|
|
82
|
+
if (this.activeHotspotId === spotId) {
|
|
83
|
+
this.closePopover();
|
|
84
|
+
}
|
|
85
|
+
else {
|
|
86
|
+
this.activeHotspotId = spotId;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
this.onHotspotClick(spot);
|
|
90
|
+
}
|
|
91
|
+
handleTriggerMouseEnter(spotId) {
|
|
92
|
+
if (!this.interactive || this.trigger !== 'hover')
|
|
93
|
+
return;
|
|
94
|
+
this.clearHoverTimeout();
|
|
95
|
+
this.activeHotspotId = spotId;
|
|
96
|
+
}
|
|
97
|
+
handleTriggerMouseLeave() {
|
|
98
|
+
if (!this.interactive || this.trigger !== 'hover')
|
|
99
|
+
return;
|
|
100
|
+
this.scheduleClose();
|
|
101
|
+
}
|
|
102
|
+
handlePopoverMouseEnter() {
|
|
103
|
+
if (!this.interactive || this.trigger !== 'hover')
|
|
104
|
+
return;
|
|
105
|
+
this.clearHoverTimeout();
|
|
106
|
+
}
|
|
107
|
+
handlePopoverMouseLeave() {
|
|
108
|
+
if (!this.interactive || this.trigger !== 'hover')
|
|
109
|
+
return;
|
|
110
|
+
this.scheduleClose();
|
|
111
|
+
}
|
|
112
|
+
scheduleClose() {
|
|
113
|
+
this.clearHoverTimeout();
|
|
114
|
+
this.hoverTimeout = window.setTimeout(() => {
|
|
115
|
+
this.closePopover();
|
|
116
|
+
}, 200);
|
|
117
|
+
}
|
|
118
|
+
clearHoverTimeout() {
|
|
119
|
+
if (this.hoverTimeout) {
|
|
120
|
+
clearTimeout(this.hoverTimeout);
|
|
121
|
+
this.hoverTimeout = undefined;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
closePopover() {
|
|
125
|
+
this.activeHotspotId = null;
|
|
126
|
+
}
|
|
127
|
+
onHotspotClick(spot) {
|
|
128
|
+
this.dispatchCustomEvent('hotspot-click', spot);
|
|
129
|
+
}
|
|
130
|
+
render() {
|
|
131
|
+
return html `
|
|
132
|
+
<div part="container" class="hotspot-container">
|
|
133
|
+
<slot></slot>
|
|
134
|
+
${this.hotspots.map((spot, index) => {
|
|
135
|
+
const spotId = spot.id || String(index);
|
|
136
|
+
const isOpened = this.activeHotspotId === spotId;
|
|
137
|
+
const yVal = typeof spot.y === 'number' ? spot.y : parseFloat(String(spot.y || '0'));
|
|
138
|
+
const positionClass = yVal < 30 ? 'position-below' : 'position-above';
|
|
139
|
+
const leftStyle = typeof spot.x === 'number' ? `${spot.x}%` : (String(spot.x).endsWith('%') ? spot.x : `${spot.x}%`);
|
|
140
|
+
const topStyle = typeof spot.y === 'number' ? `${spot.y}%` : (String(spot.y).endsWith('%') ? spot.y : `${spot.y}%`);
|
|
141
|
+
return html `
|
|
142
|
+
<div part="wrapper" class="hotspot-wrapper" style="left: ${leftStyle}; top: ${topStyle}">
|
|
143
|
+
<button
|
|
144
|
+
part="trigger ${isOpened ? 'trigger-active' : ''}"
|
|
145
|
+
class="hotspot-trigger ${isOpened ? 'active' : ''}"
|
|
146
|
+
aria-label="${spot.label || spot.title || msg('Hotspot')}"
|
|
147
|
+
aria-expanded="${isOpened}"
|
|
148
|
+
aria-controls="popover-${spotId}"
|
|
149
|
+
?disabled=${!this.interactive}
|
|
150
|
+
@click=${(e) => this.handleTriggerClick(spot, spotId, e)}
|
|
151
|
+
@mouseenter=${() => this.handleTriggerMouseEnter(spotId)}
|
|
152
|
+
@mouseleave=${() => this.handleTriggerMouseLeave()}
|
|
153
|
+
>${isOpened ? getIcon('x', 14, fromLucide(X)) : getIcon('plus', 14, fromLucide(Plus))}</button>
|
|
154
|
+
|
|
155
|
+
${isOpened
|
|
156
|
+
? html `
|
|
157
|
+
<div
|
|
158
|
+
id="popover-${spotId}"
|
|
159
|
+
part="popover"
|
|
160
|
+
class="hotspot-popover ${positionClass}"
|
|
161
|
+
role="dialog"
|
|
162
|
+
aria-modal="false"
|
|
163
|
+
aria-label="${spot.title || msg('Hotspot details')}"
|
|
164
|
+
@mouseenter=${() => this.handlePopoverMouseEnter()}
|
|
165
|
+
@mouseleave=${() => this.handlePopoverMouseLeave()}
|
|
166
|
+
>
|
|
167
|
+
<div part="popover-content" class="popover-content">
|
|
168
|
+
<slot name="popover-${spotId}">
|
|
169
|
+
${spot.title ? html `<h3>${spot.title}</h3>` : ''}
|
|
170
|
+
${spot.content ? html `<p>${spot.content}</p>` : ''}
|
|
171
|
+
</slot>
|
|
172
|
+
</div>
|
|
173
|
+
<button
|
|
174
|
+
part="popover-close"
|
|
175
|
+
class="popover-close"
|
|
176
|
+
aria-label=${msg('Close details')}
|
|
177
|
+
@click=${(e) => {
|
|
178
|
+
e.stopPropagation();
|
|
179
|
+
this.closePopover();
|
|
180
|
+
}}
|
|
181
|
+
>${getIcon('x', 14, fromLucide(X))}</button>
|
|
182
|
+
</div>
|
|
183
|
+
`
|
|
184
|
+
: ''}
|
|
185
|
+
</div>
|
|
186
|
+
`;
|
|
187
|
+
})}
|
|
188
|
+
</div>
|
|
189
|
+
`;
|
|
190
|
+
}
|
|
191
|
+
};
|
|
192
|
+
__decorate([
|
|
193
|
+
property({ type: Array })
|
|
194
|
+
], Hotspot.prototype, "hotspots", void 0);
|
|
195
|
+
__decorate([
|
|
196
|
+
property({ type: Boolean })
|
|
197
|
+
], Hotspot.prototype, "interactive", void 0);
|
|
198
|
+
__decorate([
|
|
199
|
+
property({ type: String })
|
|
200
|
+
], Hotspot.prototype, "trigger", void 0);
|
|
201
|
+
__decorate([
|
|
202
|
+
state()
|
|
203
|
+
], Hotspot.prototype, "activeHotspotId", void 0);
|
|
204
|
+
Hotspot = __decorate([
|
|
205
|
+
customElement('uibit-hotspot')
|
|
206
|
+
], Hotspot);
|
|
207
|
+
export { Hotspot };
|
|
208
|
+
export default Hotspot;
|
|
209
|
+
//# sourceMappingURL=hotspot.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hotspot.js","sourceRoot":"","sources":["../src/hotspot.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAC7C,YAAY,EAAE,WAAW,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC"}
|
package/dist/styles.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"styles.d.ts","sourceRoot":"","sources":["../src/styles.ts"],"names":[],"mappings":"AAEA,eAAO,MAAM,MAAM,yBA6MlB,CAAC"}
|
package/dist/styles.js
ADDED
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
import { css } from 'lit';
|
|
2
|
+
export const styles = css `
|
|
3
|
+
:host {
|
|
4
|
+
display: block;
|
|
5
|
+
position: relative;
|
|
6
|
+
width: 100%;
|
|
7
|
+
--uibit-hotspot-trigger-size: 1.75rem;
|
|
8
|
+
--uibit-hotspot-trigger-bg: #ffffff;
|
|
9
|
+
--uibit-hotspot-trigger-bg-hover: #f3f4f6;
|
|
10
|
+
--uibit-hotspot-trigger-border: 0.0625rem solid #e5e7eb;
|
|
11
|
+
--uibit-hotspot-trigger-color: #111827;
|
|
12
|
+
--uibit-hotspot-popover-bg: rgba(255, 255, 255, 0.96);
|
|
13
|
+
--uibit-hotspot-popover-color: #111827;
|
|
14
|
+
--uibit-hotspot-popover-content-color: #4b5563;
|
|
15
|
+
--uibit-hotspot-popover-border-radius: 0.5rem;
|
|
16
|
+
--uibit-hotspot-popover-border: 0.0625rem solid #e5e7eb;
|
|
17
|
+
--uibit-hotspot-popover-shadow: 0 0.25rem 1rem rgba(0, 0, 0, 0.04);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
.hotspot-container {
|
|
21
|
+
position: relative;
|
|
22
|
+
width: 100%;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
.hotspot-wrapper {
|
|
26
|
+
position: absolute;
|
|
27
|
+
transform: translate(-50%, -50%);
|
|
28
|
+
z-index: 10;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
.hotspot-trigger {
|
|
32
|
+
width: var(--uibit-hotspot-trigger-size, 1.75rem);
|
|
33
|
+
height: var(--uibit-hotspot-trigger-size, 1.75rem);
|
|
34
|
+
background: var(--uibit-hotspot-trigger-bg, #ffffff);
|
|
35
|
+
border: var(--uibit-hotspot-trigger-border, 0.0625rem solid #e5e7eb);
|
|
36
|
+
border-radius: 9999rem;
|
|
37
|
+
cursor: pointer;
|
|
38
|
+
display: flex;
|
|
39
|
+
align-items: center;
|
|
40
|
+
justify-content: center;
|
|
41
|
+
box-shadow: none;
|
|
42
|
+
position: relative;
|
|
43
|
+
transition: transform 150ms ease, background-color 150ms ease, border-color 150ms ease, color 150ms ease;
|
|
44
|
+
outline: none;
|
|
45
|
+
padding: 0;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
.hotspot-trigger::before {
|
|
49
|
+
content: '';
|
|
50
|
+
position: absolute;
|
|
51
|
+
inset: -0.0625rem;
|
|
52
|
+
border-radius: 9999rem;
|
|
53
|
+
border: 0.0625rem solid var(--uibit-hotspot-trigger-border-color-actual, #e5e7eb);
|
|
54
|
+
background: transparent;
|
|
55
|
+
animation: pulse 2.5s ease-out infinite;
|
|
56
|
+
z-index: -1;
|
|
57
|
+
opacity: 0;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
@keyframes pulse {
|
|
61
|
+
0% {
|
|
62
|
+
transform: scale(1);
|
|
63
|
+
opacity: 0.8;
|
|
64
|
+
}
|
|
65
|
+
70% {
|
|
66
|
+
transform: scale(1.6);
|
|
67
|
+
opacity: 0;
|
|
68
|
+
}
|
|
69
|
+
100% {
|
|
70
|
+
transform: scale(1.6);
|
|
71
|
+
opacity: 0;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
.hotspot-trigger:hover,
|
|
76
|
+
.hotspot-trigger.active {
|
|
77
|
+
background: var(--uibit-hotspot-trigger-bg-hover, #f3f4f6);
|
|
78
|
+
border-color: #d1d5db;
|
|
79
|
+
transform: scale(1.06);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
.hotspot-trigger:active {
|
|
83
|
+
transform: scale(0.95);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
.hotspot-trigger:focus-visible {
|
|
87
|
+
outline: 0.125rem solid #000000;
|
|
88
|
+
outline-offset: 0.125rem;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
.hotspot-trigger svg {
|
|
92
|
+
width: 0.875rem;
|
|
93
|
+
height: 0.875rem;
|
|
94
|
+
color: var(--uibit-hotspot-trigger-color, #111827);
|
|
95
|
+
pointer-events: none;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
.hotspot-popover {
|
|
99
|
+
position: absolute;
|
|
100
|
+
width: 15rem;
|
|
101
|
+
background: var(--uibit-hotspot-popover-bg, rgba(255, 255, 255, 0.96));
|
|
102
|
+
backdrop-filter: blur(0.75rem);
|
|
103
|
+
-webkit-backdrop-filter: blur(0.75rem);
|
|
104
|
+
color: var(--uibit-hotspot-popover-color, #111827);
|
|
105
|
+
padding: 0.875rem 1rem;
|
|
106
|
+
border-radius: var(--uibit-hotspot-popover-border-radius, 0.75rem);
|
|
107
|
+
box-shadow: var(
|
|
108
|
+
--uibit-hotspot-popover-shadow,
|
|
109
|
+
0 0.625rem 1.5rem rgba(0, 0, 0, 0.1),
|
|
110
|
+
0 0.25rem 0.5rem rgba(0, 0, 0, 0.06)
|
|
111
|
+
);
|
|
112
|
+
border: var(--uibit-hotspot-popover-border, 0.0625rem solid rgba(0, 0, 0, 0.08));
|
|
113
|
+
z-index: 20;
|
|
114
|
+
display: flex;
|
|
115
|
+
flex-direction: column;
|
|
116
|
+
animation: fadeIn 200ms ease;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
@keyframes fadeIn {
|
|
120
|
+
from {
|
|
121
|
+
opacity: 0;
|
|
122
|
+
transform: translate(-50%, 0.5rem);
|
|
123
|
+
}
|
|
124
|
+
to {
|
|
125
|
+
opacity: 1;
|
|
126
|
+
transform: translate(-50%, 0);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
.hotspot-popover.position-above {
|
|
131
|
+
bottom: 1.5rem;
|
|
132
|
+
left: 50%;
|
|
133
|
+
transform: translateX(-50%);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
.hotspot-popover.position-below {
|
|
137
|
+
top: 1.5rem;
|
|
138
|
+
left: 50%;
|
|
139
|
+
transform: translateX(-50%);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
.hotspot-popover::after {
|
|
143
|
+
content: '';
|
|
144
|
+
position: absolute;
|
|
145
|
+
border-style: solid;
|
|
146
|
+
left: 50%;
|
|
147
|
+
transform: translateX(-50%);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
.hotspot-popover.position-above::after {
|
|
151
|
+
top: 100%;
|
|
152
|
+
border-width: 0.375rem 0.375rem 0 0.375rem;
|
|
153
|
+
border-color: var(--uibit-hotspot-popover-bg, rgba(255, 255, 255, 0.96)) transparent transparent transparent;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
.hotspot-popover.position-below::after {
|
|
157
|
+
bottom: 100%;
|
|
158
|
+
border-width: 0 0.375rem 0.375rem 0.375rem;
|
|
159
|
+
border-color: transparent transparent var(--uibit-hotspot-popover-bg, rgba(255, 255, 255, 0.96)) transparent;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
.popover-content h3 {
|
|
163
|
+
margin: 0 0 0.375rem 0;
|
|
164
|
+
font-size: var(--uibit-font-size-base, 0.9375rem);
|
|
165
|
+
font-weight: var(--uibit-font-weight-semibold, 600);
|
|
166
|
+
letter-spacing: var(--uibit-letter-spacing-tight, -0.01em);
|
|
167
|
+
color: var(--uibit-hotspot-popover-color, #111827);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
.popover-content p {
|
|
171
|
+
margin: 0;
|
|
172
|
+
font-size: var(--uibit-font-size-sm, 0.8125rem);
|
|
173
|
+
line-height: var(--uibit-line-height-relaxed, 1.625);
|
|
174
|
+
color: var(--uibit-hotspot-popover-content-color, #4b5563);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
.popover-close {
|
|
178
|
+
position: absolute;
|
|
179
|
+
top: 0.5rem;
|
|
180
|
+
right: 0.5rem;
|
|
181
|
+
background: transparent;
|
|
182
|
+
border: none;
|
|
183
|
+
color: #9ca3af;
|
|
184
|
+
cursor: pointer;
|
|
185
|
+
width: 1.5rem;
|
|
186
|
+
height: 1.5rem;
|
|
187
|
+
display: flex;
|
|
188
|
+
align-items: center;
|
|
189
|
+
justify-content: center;
|
|
190
|
+
border-radius: 9999rem;
|
|
191
|
+
transition: background-color 150ms ease, color 150ms ease, transform 150ms ease;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
.popover-close:hover {
|
|
195
|
+
background-color: #f3f4f6;
|
|
196
|
+
color: #111827;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
.popover-close:active {
|
|
200
|
+
transform: scale(0.92);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
.popover-close:focus-visible {
|
|
204
|
+
outline: 0.125rem solid #000000;
|
|
205
|
+
outline-offset: 0.125rem;
|
|
206
|
+
}
|
|
207
|
+
`;
|
|
208
|
+
//# sourceMappingURL=styles.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"styles.js","sourceRoot":"","sources":["../src/styles.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,GAAG,EAAE,MAAM,KAAK,CAAC;AAE1B,MAAM,CAAC,MAAM,MAAM,GAAG,GAAG,CAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA6MxB,CAAC"}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type Hotspot from './hotspot';
|
|
2
|
+
export interface HotspotItem {
|
|
3
|
+
id: string;
|
|
4
|
+
x: string | number;
|
|
5
|
+
y: string | number;
|
|
6
|
+
label: string;
|
|
7
|
+
title?: string;
|
|
8
|
+
content?: string;
|
|
9
|
+
link?: string;
|
|
10
|
+
}
|
|
11
|
+
export interface HotspotConfig {
|
|
12
|
+
hotspots?: HotspotItem[];
|
|
13
|
+
interactive?: boolean;
|
|
14
|
+
showLabels?: boolean;
|
|
15
|
+
}
|
|
16
|
+
declare global {
|
|
17
|
+
interface HTMLElementTagNameMap {
|
|
18
|
+
'uibit-hotspot': Hotspot;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,OAAO,MAAM,WAAW,CAAC;AAErC,MAAM,WAAW,WAAW;IAC1B,EAAE,EAAE,MAAM,CAAC;IACX,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IACnB,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IACnB,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,EAAE,WAAW,EAAE,CAAC;IACzB,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,UAAU,CAAC,EAAE,OAAO,CAAC;CACtB;AAED,OAAO,CAAC,MAAM,CAAC;IACf,UAAU,qBAAqB;QAC3B,eAAe,EAAE,OAAO,CAAC;KAC5B;CACA"}
|
package/dist/types.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":""}
|
package/package.json
ADDED
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@uibit/hotspot",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Interactive hotspot annotations for images. Display contextual tooltip cards on click or hover with custom content overlays and animations.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/index.js",
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./dist/index.d.ts",
|
|
11
|
+
"import": "./dist/index.js"
|
|
12
|
+
},
|
|
13
|
+
"./custom-elements.json": "./custom-elements.json",
|
|
14
|
+
"./react": "./dist/frameworks/react/index.d.ts",
|
|
15
|
+
"./vue": "./dist/frameworks/vue/index.ts",
|
|
16
|
+
"./svelte": "./dist/frameworks/svelte/index.svelte",
|
|
17
|
+
"./angular": "./dist/frameworks/angular/index.ts",
|
|
18
|
+
"./solid": "./dist/frameworks/solid/index.d.ts",
|
|
19
|
+
"./astro": "./dist/frameworks/astro/index.astro",
|
|
20
|
+
"./qwik": "./dist/frameworks/qwik/index.tsx",
|
|
21
|
+
"./nuxt": "./dist/frameworks/nuxt/index.ts",
|
|
22
|
+
"./preact": "./dist/frameworks/preact/index.d.ts",
|
|
23
|
+
"./stencil": "./dist/frameworks/stencil/index.d.ts"
|
|
24
|
+
},
|
|
25
|
+
"files": [
|
|
26
|
+
"dist",
|
|
27
|
+
"package.json",
|
|
28
|
+
"README.md",
|
|
29
|
+
"LICENSE",
|
|
30
|
+
"custom-elements.json"
|
|
31
|
+
],
|
|
32
|
+
"keywords": [
|
|
33
|
+
"hotspot",
|
|
34
|
+
"interactive",
|
|
35
|
+
"image-annotation",
|
|
36
|
+
"web-component",
|
|
37
|
+
"lit"
|
|
38
|
+
],
|
|
39
|
+
"author": "Jonathan Rawlings",
|
|
40
|
+
"license": "MIT",
|
|
41
|
+
"repository": {
|
|
42
|
+
"type": "git",
|
|
43
|
+
"url": "https://github.com/Rawlings/uibit",
|
|
44
|
+
"directory": "packages/components/hotspot"
|
|
45
|
+
},
|
|
46
|
+
"dependencies": {
|
|
47
|
+
"lucide": "^1.24.0",
|
|
48
|
+
"@uibit/core": "0.1.0"
|
|
49
|
+
},
|
|
50
|
+
"devDependencies": {
|
|
51
|
+
"@types/node": "^20.19.43",
|
|
52
|
+
"lit": "^3.3.3",
|
|
53
|
+
"typescript": "7.0.2",
|
|
54
|
+
"@uibit/codegen": "0.1.0"
|
|
55
|
+
},
|
|
56
|
+
"peerDependencies": {
|
|
57
|
+
"lit": "^3.0.0",
|
|
58
|
+
"react": ">=18",
|
|
59
|
+
"vue": ">=3",
|
|
60
|
+
"svelte": ">=4 || ^5",
|
|
61
|
+
"@angular/core": ">=14"
|
|
62
|
+
},
|
|
63
|
+
"customElements": "custom-elements.json",
|
|
64
|
+
"peerDependenciesMeta": {
|
|
65
|
+
"react": {
|
|
66
|
+
"optional": true
|
|
67
|
+
},
|
|
68
|
+
"vue": {
|
|
69
|
+
"optional": true
|
|
70
|
+
},
|
|
71
|
+
"svelte": {
|
|
72
|
+
"optional": true
|
|
73
|
+
},
|
|
74
|
+
"@angular/core": {
|
|
75
|
+
"optional": true
|
|
76
|
+
}
|
|
77
|
+
},
|
|
78
|
+
"uibit": {
|
|
79
|
+
"id": "hotspot",
|
|
80
|
+
"title": "Hotspot",
|
|
81
|
+
"category": "Media",
|
|
82
|
+
"tagName": "uibit-hotspot"
|
|
83
|
+
},
|
|
84
|
+
"scripts": {
|
|
85
|
+
"build": "cem analyze --globs 'src/**/*.ts' --litelement && uibit-codegen --package . && tsc",
|
|
86
|
+
"dev": "concurrently \"cem analyze --globs 'src/**/*.ts' --litelement --watch\" \"tsc --watch\"",
|
|
87
|
+
"analyze": "cem analyze --globs 'src/**/*.ts' --litelement",
|
|
88
|
+
"typecheck": "tsc --noEmit"
|
|
89
|
+
}
|
|
90
|
+
}
|