@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 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,7 @@
1
+ import { defineNuxtPlugin } from '#app';
2
+
3
+ export default defineNuxtPlugin(() => {
4
+ if (process.client) {
5
+ import('@uibit/hotspot');
6
+ }
7
+ });
@@ -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,10 @@
1
+ import { component$, Slot } from '@builder.io/qwik';
2
+ import '@uibit/hotspot';
3
+
4
+ export const Hotspot = component$<any>((props) => {
5
+ return (
6
+ <uibit-hotspot {...props}>
7
+ <Slot />
8
+ </uibit-hotspot>
9
+ );
10
+ });
@@ -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"}
@@ -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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"}
@@ -0,0 +1,3 @@
1
+ export { default, Hotspot } from './hotspot';
2
+ export type { HotspotItem, HotspotConfig } from './types';
3
+ //# sourceMappingURL=index.d.ts.map
@@ -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,2 @@
1
+ export { default, Hotspot } from './hotspot';
2
+ //# sourceMappingURL=index.js.map
@@ -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"}
@@ -0,0 +1,2 @@
1
+ export declare const styles: import("lit").CSSResult;
2
+ //# sourceMappingURL=styles.d.ts.map
@@ -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"}
@@ -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,2 @@
1
+ export {};
2
+ //# sourceMappingURL=types.js.map
@@ -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
+ }