@syntrologie/adapt-overlays 0.0.0-semantically-released

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.
@@ -0,0 +1,267 @@
1
+ /**
2
+ * Adaptive Overlays - Runtime Module
3
+ *
4
+ * Visual overlay actions: highlight, pulse, badge, tooltip.
5
+ */
6
+ import { showHighlight } from './highlight';
7
+ import { showTooltip } from './tooltip';
8
+ import { sanitizeHtml } from './sanitizer';
9
+ import { executeModal } from './modal';
10
+ // Re-export executeModal for use by other packages
11
+ export { executeModal };
12
+ // ============================================================================
13
+ // Executors
14
+ // ============================================================================
15
+ /**
16
+ * Execute a highlight action
17
+ */
18
+ export const executeHighlight = async (action, context) => {
19
+ const anchorEl = context.resolveAnchor(action.anchorId);
20
+ if (!anchorEl) {
21
+ throw new Error(`Anchor not found: ${action.anchorId}`);
22
+ }
23
+ const handle = showHighlight(anchorEl, context.overlayRoot, {
24
+ paddingPx: action.style?.paddingPx ?? 12,
25
+ radiusPx: action.style?.radiusPx ?? 12,
26
+ scrimOpacity: action.style?.scrimOpacity ?? 0.55,
27
+ ringColor: action.style?.color,
28
+ blocking: false,
29
+ onClickOutside: false,
30
+ onEsc: false,
31
+ });
32
+ context.publishEvent('action.applied', {
33
+ id: context.generateId(),
34
+ kind: 'overlays:highlight',
35
+ anchorId: action.anchorId,
36
+ });
37
+ return {
38
+ cleanup: () => {
39
+ handle.destroy();
40
+ },
41
+ };
42
+ };
43
+ /**
44
+ * Execute a pulse action
45
+ */
46
+ export const executePulse = async (action, context) => {
47
+ const anchorEl = context.resolveAnchor(action.anchorId);
48
+ if (!anchorEl) {
49
+ throw new Error(`Anchor not found: ${action.anchorId}`);
50
+ }
51
+ const duration = action.duration ?? 2000;
52
+ const className = 'syntro-pulse-effect';
53
+ // Inject pulse animation styles if not already present
54
+ if (!document.querySelector('[data-syntro-pulse-styles]')) {
55
+ const style = document.createElement('style');
56
+ style.setAttribute('data-syntro-pulse-styles', '');
57
+ style.textContent = `
58
+ @keyframes syntro-pulse {
59
+ 0%, 100% {
60
+ box-shadow: 0 0 0 0 rgba(79, 70, 229, 0.4);
61
+ }
62
+ 50% {
63
+ box-shadow: 0 0 0 8px rgba(79, 70, 229, 0);
64
+ }
65
+ }
66
+ .${className} {
67
+ animation: syntro-pulse 1s cubic-bezier(0.4, 0, 0.6, 1) infinite;
68
+ }
69
+ `;
70
+ document.head.appendChild(style);
71
+ }
72
+ // Add pulse class
73
+ anchorEl.classList.add(className);
74
+ // Set up auto-remove timeout
75
+ const timeoutId = setTimeout(() => {
76
+ anchorEl.classList.remove(className);
77
+ }, duration);
78
+ context.publishEvent('action.applied', {
79
+ id: context.generateId(),
80
+ kind: 'overlays:pulse',
81
+ anchorId: action.anchorId,
82
+ duration,
83
+ });
84
+ return {
85
+ cleanup: () => {
86
+ clearTimeout(timeoutId);
87
+ anchorEl.classList.remove(className);
88
+ },
89
+ };
90
+ };
91
+ /**
92
+ * Execute a badge action
93
+ */
94
+ export const executeBadge = async (action, context) => {
95
+ const anchorEl = context.resolveAnchor(action.anchorId);
96
+ if (!anchorEl) {
97
+ throw new Error(`Anchor not found: ${action.anchorId}`);
98
+ }
99
+ // Create badge element
100
+ const badge = document.createElement('div');
101
+ badge.className = 'syntro-badge';
102
+ badge.textContent = action.content;
103
+ badge.setAttribute('data-syntro-badge', action.anchorId);
104
+ // Inject badge styles if not present
105
+ if (!document.querySelector('[data-syntro-badge-styles]')) {
106
+ const style = document.createElement('style');
107
+ style.setAttribute('data-syntro-badge-styles', '');
108
+ style.textContent = `
109
+ .syntro-badge {
110
+ position: absolute;
111
+ padding: 2px 6px;
112
+ font-size: 12px;
113
+ font-weight: 600;
114
+ line-height: 1;
115
+ color: white;
116
+ background: var(--syntro-accent, #4f46e5);
117
+ border-radius: 9999px;
118
+ pointer-events: none;
119
+ z-index: 2147483646;
120
+ white-space: nowrap;
121
+ }
122
+ `;
123
+ document.head.appendChild(style);
124
+ }
125
+ // Position badge relative to anchor
126
+ const position = action.position ?? 'top-right';
127
+ // Ensure anchor has relative positioning for badge
128
+ const originalPosition = anchorEl.style.position;
129
+ if (getComputedStyle(anchorEl).position === 'static') {
130
+ anchorEl.style.position = 'relative';
131
+ }
132
+ // Append to anchor for relative positioning
133
+ anchorEl.appendChild(badge);
134
+ // Position based on config
135
+ switch (position) {
136
+ case 'top-left':
137
+ Object.assign(badge.style, { top: '-8px', left: '-8px' });
138
+ break;
139
+ case 'top-right':
140
+ Object.assign(badge.style, { top: '-8px', right: '-8px' });
141
+ break;
142
+ case 'bottom-left':
143
+ Object.assign(badge.style, { bottom: '-8px', left: '-8px' });
144
+ break;
145
+ case 'bottom-right':
146
+ Object.assign(badge.style, { bottom: '-8px', right: '-8px' });
147
+ break;
148
+ }
149
+ context.publishEvent('action.applied', {
150
+ id: context.generateId(),
151
+ kind: 'overlays:badge',
152
+ anchorId: action.anchorId,
153
+ content: action.content,
154
+ position,
155
+ });
156
+ return {
157
+ cleanup: () => {
158
+ badge.remove();
159
+ if (originalPosition !== undefined) {
160
+ anchorEl.style.position = originalPosition;
161
+ }
162
+ },
163
+ updateFn: (changes) => {
164
+ if ('content' in changes && typeof changes.content === 'string') {
165
+ badge.textContent = changes.content;
166
+ }
167
+ },
168
+ };
169
+ };
170
+ /**
171
+ * Execute a tooltip action
172
+ */
173
+ export const executeTooltip = async (action, context) => {
174
+ const anchorEl = context.resolveAnchor(action.anchorId);
175
+ if (!anchorEl) {
176
+ throw new Error(`Anchor not found: ${action.anchorId}`);
177
+ }
178
+ // Build tooltip HTML
179
+ const content = action.content;
180
+ let html = '';
181
+ if (content.title) {
182
+ html += `<div class="syntro-tt-title">${sanitizeHtml(content.title)}</div>`;
183
+ }
184
+ html += `<div class="syntro-tt-body">${sanitizeHtml(content.body)}</div>`;
185
+ // Add CTA buttons if provided (new multi-button support)
186
+ if (content.ctaButtons && content.ctaButtons.length > 0) {
187
+ html += `<div class="syntro-tt-actions">`;
188
+ for (const btn of content.ctaButtons) {
189
+ const isPrimary = btn.primary ?? false;
190
+ html += `
191
+ <button
192
+ class="syntro-tt-btn ${isPrimary ? 'syntro-tt-btn-primary' : ''}"
193
+ data-syntro-action="${sanitizeHtml(btn.actionId)}"
194
+ >
195
+ ${sanitizeHtml(btn.label)}
196
+ </button>
197
+ `;
198
+ }
199
+ html += `</div>`;
200
+ }
201
+ else if (content.cta) {
202
+ // Legacy single CTA support
203
+ html += `<div class="syntro-tt-actions">
204
+ <button class="syntro-tt-btn syntro-tt-btn-primary" data-syntro-action="cta">
205
+ ${sanitizeHtml(content.cta.label)}
206
+ </button>
207
+ </div>`;
208
+ }
209
+ const handle = showTooltip(anchorEl, context.overlayRoot, {
210
+ html,
211
+ placement: action.placement ?? 'top',
212
+ trigger: action.trigger ?? 'immediate',
213
+ onAction: (actionId) => {
214
+ if (actionId === 'cta' && content.cta) {
215
+ context.publishEvent('action.cta_clicked', {
216
+ anchorId: action.anchorId,
217
+ ctaLabel: content.cta.label,
218
+ });
219
+ }
220
+ else if (content.ctaButtons) {
221
+ const clickedBtn = content.ctaButtons.find((b) => b.actionId === actionId);
222
+ if (clickedBtn) {
223
+ context.publishEvent('action.tooltip_cta_clicked', {
224
+ anchorId: action.anchorId,
225
+ actionId,
226
+ label: clickedBtn.label,
227
+ });
228
+ }
229
+ }
230
+ },
231
+ });
232
+ context.publishEvent('action.applied', {
233
+ id: context.generateId(),
234
+ kind: 'overlays:tooltip',
235
+ anchorId: action.anchorId,
236
+ trigger: action.trigger ?? 'immediate',
237
+ });
238
+ return {
239
+ cleanup: () => {
240
+ handle.destroy();
241
+ },
242
+ };
243
+ };
244
+ // ============================================================================
245
+ // Executor Definitions for Registration
246
+ // ============================================================================
247
+ /**
248
+ * All executors provided by this app.
249
+ * These are registered with the runtime's ExecutorRegistry.
250
+ */
251
+ export const executors = [
252
+ { kind: 'overlays:highlight', executor: executeHighlight },
253
+ { kind: 'overlays:pulse', executor: executePulse },
254
+ { kind: 'overlays:badge', executor: executeBadge },
255
+ { kind: 'overlays:tooltip', executor: executeTooltip },
256
+ { kind: 'overlays:modal', executor: executeModal },
257
+ ];
258
+ /**
259
+ * App runtime manifest.
260
+ */
261
+ export const runtime = {
262
+ id: 'adaptive-overlays',
263
+ version: '1.0.0',
264
+ name: 'Overlays',
265
+ description: 'Tooltips, highlights, badges, modals, and visual overlays',
266
+ executors,
267
+ };
@@ -0,0 +1,8 @@
1
+ /**
2
+ * HTML Sanitizer
3
+ *
4
+ * Sanitizes HTML to prevent XSS attacks.
5
+ * Uses native Sanitizer API when available, falls back to whitelist approach.
6
+ */
7
+ export declare function sanitizeHtml(html: string): string;
8
+ //# sourceMappingURL=sanitizer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sanitizer.d.ts","sourceRoot":"","sources":["../src/sanitizer.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAwBH,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAqDjD"}
@@ -0,0 +1,75 @@
1
+ /**
2
+ * HTML Sanitizer
3
+ *
4
+ * Sanitizes HTML to prevent XSS attacks.
5
+ * Uses native Sanitizer API when available, falls back to whitelist approach.
6
+ */
7
+ const ALLOWED_TAGS = new Set([
8
+ 'b',
9
+ 'strong',
10
+ 'i',
11
+ 'em',
12
+ 'u',
13
+ 'span',
14
+ 'div',
15
+ 'p',
16
+ 'br',
17
+ 'ul',
18
+ 'ol',
19
+ 'li',
20
+ 'code',
21
+ 'pre',
22
+ 'small',
23
+ 'sup',
24
+ 'sub',
25
+ 'a',
26
+ 'button',
27
+ ]);
28
+ export function sanitizeHtml(html) {
29
+ // Try native Sanitizer API first
30
+ const hasNative = typeof window.Sanitizer === 'function';
31
+ if (hasNative) {
32
+ try {
33
+ const s = new window.Sanitizer({});
34
+ const frag = s.sanitizeToFragment(html);
35
+ const div = document.createElement('div');
36
+ div.append(frag);
37
+ return div.innerHTML;
38
+ }
39
+ catch {
40
+ // Fall through to manual sanitizer
41
+ }
42
+ }
43
+ // Conservative fallback sanitizer
44
+ const tpl = document.createElement('template');
45
+ tpl.innerHTML = html;
46
+ const root = tpl.content;
47
+ const walker = document.createTreeWalker(root, NodeFilter.SHOW_ELEMENT, null);
48
+ const toRemove = [];
49
+ while (walker.nextNode()) {
50
+ const el = walker.currentNode;
51
+ const tag = el.tagName.toLowerCase();
52
+ if (!ALLOWED_TAGS.has(tag)) {
53
+ toRemove.push(el);
54
+ continue;
55
+ }
56
+ // Remove dangerous attributes
57
+ for (const attr of Array.from(el.attributes)) {
58
+ const name = attr.name.toLowerCase();
59
+ const value = attr.value.trim().toLowerCase();
60
+ const isEvent = name.startsWith('on');
61
+ const isJsUrl = (name === 'href' || name === 'src') && value.startsWith('javascript:');
62
+ if (isEvent || isJsUrl) {
63
+ el.removeAttribute(attr.name);
64
+ }
65
+ }
66
+ }
67
+ // Remove disallowed elements but keep their children
68
+ for (const el of toRemove) {
69
+ while (el.firstChild) {
70
+ el.parentNode?.insertBefore(el.firstChild, el);
71
+ }
72
+ el.remove();
73
+ }
74
+ return tpl.innerHTML;
75
+ }
@@ -0,0 +1,197 @@
1
+ /**
2
+ * Adaptive Overlays - Config Schema
3
+ *
4
+ * Zod schema for validating overlays app configuration.
5
+ */
6
+ import { z } from 'zod';
7
+ /**
8
+ * Overlays app config schema.
9
+ * Defines the structure for overlay configurations in a canvas config.
10
+ */
11
+ export declare const configSchema: z.ZodObject<{
12
+ /** Highlight configurations */
13
+ highlights: z.ZodOptional<z.ZodArray<z.ZodObject<{
14
+ anchorId: z.ZodString;
15
+ style: z.ZodOptional<z.ZodObject<{
16
+ color: z.ZodOptional<z.ZodString>;
17
+ paddingPx: z.ZodOptional<z.ZodNumber>;
18
+ radiusPx: z.ZodOptional<z.ZodNumber>;
19
+ scrimOpacity: z.ZodOptional<z.ZodNumber>;
20
+ }, "strip", z.ZodTypeAny, {
21
+ color?: string | undefined;
22
+ paddingPx?: number | undefined;
23
+ radiusPx?: number | undefined;
24
+ scrimOpacity?: number | undefined;
25
+ }, {
26
+ color?: string | undefined;
27
+ paddingPx?: number | undefined;
28
+ radiusPx?: number | undefined;
29
+ scrimOpacity?: number | undefined;
30
+ }>>;
31
+ }, "strip", z.ZodTypeAny, {
32
+ anchorId: string;
33
+ style?: {
34
+ color?: string | undefined;
35
+ paddingPx?: number | undefined;
36
+ radiusPx?: number | undefined;
37
+ scrimOpacity?: number | undefined;
38
+ } | undefined;
39
+ }, {
40
+ anchorId: string;
41
+ style?: {
42
+ color?: string | undefined;
43
+ paddingPx?: number | undefined;
44
+ radiusPx?: number | undefined;
45
+ scrimOpacity?: number | undefined;
46
+ } | undefined;
47
+ }>, "many">>;
48
+ /** Tooltip configurations */
49
+ tooltips: z.ZodOptional<z.ZodArray<z.ZodObject<{
50
+ anchorId: z.ZodString;
51
+ content: z.ZodObject<{
52
+ title: z.ZodOptional<z.ZodString>;
53
+ body: z.ZodString;
54
+ cta: z.ZodOptional<z.ZodObject<{
55
+ label: z.ZodString;
56
+ action: z.ZodUnknown;
57
+ }, "strip", z.ZodTypeAny, {
58
+ label: string;
59
+ action?: unknown;
60
+ }, {
61
+ label: string;
62
+ action?: unknown;
63
+ }>>;
64
+ }, "strip", z.ZodTypeAny, {
65
+ body: string;
66
+ title?: string | undefined;
67
+ cta?: {
68
+ label: string;
69
+ action?: unknown;
70
+ } | undefined;
71
+ }, {
72
+ body: string;
73
+ title?: string | undefined;
74
+ cta?: {
75
+ label: string;
76
+ action?: unknown;
77
+ } | undefined;
78
+ }>;
79
+ trigger: z.ZodOptional<z.ZodEnum<["immediate", "hover", "click"]>>;
80
+ placement: z.ZodOptional<z.ZodEnum<["top", "top-start", "top-end", "bottom", "bottom-start", "bottom-end", "left", "left-start", "left-end", "right", "right-start", "right-end"]>>;
81
+ }, "strip", z.ZodTypeAny, {
82
+ anchorId: string;
83
+ content: {
84
+ body: string;
85
+ title?: string | undefined;
86
+ cta?: {
87
+ label: string;
88
+ action?: unknown;
89
+ } | undefined;
90
+ };
91
+ trigger?: "immediate" | "hover" | "click" | undefined;
92
+ placement?: "top" | "right" | "bottom" | "left" | "top-start" | "top-end" | "right-start" | "right-end" | "bottom-start" | "bottom-end" | "left-start" | "left-end" | undefined;
93
+ }, {
94
+ anchorId: string;
95
+ content: {
96
+ body: string;
97
+ title?: string | undefined;
98
+ cta?: {
99
+ label: string;
100
+ action?: unknown;
101
+ } | undefined;
102
+ };
103
+ trigger?: "immediate" | "hover" | "click" | undefined;
104
+ placement?: "top" | "right" | "bottom" | "left" | "top-start" | "top-end" | "right-start" | "right-end" | "bottom-start" | "bottom-end" | "left-start" | "left-end" | undefined;
105
+ }>, "many">>;
106
+ /** Badge configurations */
107
+ badges: z.ZodOptional<z.ZodArray<z.ZodObject<{
108
+ anchorId: z.ZodString;
109
+ content: z.ZodString;
110
+ position: z.ZodOptional<z.ZodEnum<["top-left", "top-right", "bottom-left", "bottom-right"]>>;
111
+ }, "strip", z.ZodTypeAny, {
112
+ anchorId: string;
113
+ content: string;
114
+ position?: "top-left" | "top-right" | "bottom-left" | "bottom-right" | undefined;
115
+ }, {
116
+ anchorId: string;
117
+ content: string;
118
+ position?: "top-left" | "top-right" | "bottom-left" | "bottom-right" | undefined;
119
+ }>, "many">>;
120
+ /** Pulse configurations */
121
+ pulses: z.ZodOptional<z.ZodArray<z.ZodObject<{
122
+ anchorId: z.ZodString;
123
+ duration: z.ZodOptional<z.ZodNumber>;
124
+ }, "strip", z.ZodTypeAny, {
125
+ anchorId: string;
126
+ duration?: number | undefined;
127
+ }, {
128
+ anchorId: string;
129
+ duration?: number | undefined;
130
+ }>, "many">>;
131
+ }, "strip", z.ZodTypeAny, {
132
+ highlights?: {
133
+ anchorId: string;
134
+ style?: {
135
+ color?: string | undefined;
136
+ paddingPx?: number | undefined;
137
+ radiusPx?: number | undefined;
138
+ scrimOpacity?: number | undefined;
139
+ } | undefined;
140
+ }[] | undefined;
141
+ tooltips?: {
142
+ anchorId: string;
143
+ content: {
144
+ body: string;
145
+ title?: string | undefined;
146
+ cta?: {
147
+ label: string;
148
+ action?: unknown;
149
+ } | undefined;
150
+ };
151
+ trigger?: "immediate" | "hover" | "click" | undefined;
152
+ placement?: "top" | "right" | "bottom" | "left" | "top-start" | "top-end" | "right-start" | "right-end" | "bottom-start" | "bottom-end" | "left-start" | "left-end" | undefined;
153
+ }[] | undefined;
154
+ badges?: {
155
+ anchorId: string;
156
+ content: string;
157
+ position?: "top-left" | "top-right" | "bottom-left" | "bottom-right" | undefined;
158
+ }[] | undefined;
159
+ pulses?: {
160
+ anchorId: string;
161
+ duration?: number | undefined;
162
+ }[] | undefined;
163
+ }, {
164
+ highlights?: {
165
+ anchorId: string;
166
+ style?: {
167
+ color?: string | undefined;
168
+ paddingPx?: number | undefined;
169
+ radiusPx?: number | undefined;
170
+ scrimOpacity?: number | undefined;
171
+ } | undefined;
172
+ }[] | undefined;
173
+ tooltips?: {
174
+ anchorId: string;
175
+ content: {
176
+ body: string;
177
+ title?: string | undefined;
178
+ cta?: {
179
+ label: string;
180
+ action?: unknown;
181
+ } | undefined;
182
+ };
183
+ trigger?: "immediate" | "hover" | "click" | undefined;
184
+ placement?: "top" | "right" | "bottom" | "left" | "top-start" | "top-end" | "right-start" | "right-end" | "bottom-start" | "bottom-end" | "left-start" | "left-end" | undefined;
185
+ }[] | undefined;
186
+ badges?: {
187
+ anchorId: string;
188
+ content: string;
189
+ position?: "top-left" | "top-right" | "bottom-left" | "bottom-right" | undefined;
190
+ }[] | undefined;
191
+ pulses?: {
192
+ anchorId: string;
193
+ duration?: number | undefined;
194
+ }[] | undefined;
195
+ }>;
196
+ export type OverlaysConfig = z.infer<typeof configSchema>;
197
+ //# sourceMappingURL=schema.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"schema.d.ts","sourceRoot":"","sources":["../src/schema.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB;;;GAGG;AACH,eAAO,MAAM,YAAY;IACvB,+BAA+B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;IAiB/B,6BAA6B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;IAoC7B,2BAA2B;;;;;;;;;;;;;;IAW3B,2BAA2B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAS3B,CAAC;AAEH,MAAM,MAAM,cAAc,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,YAAY,CAAC,CAAC"}
package/dist/schema.js ADDED
@@ -0,0 +1,74 @@
1
+ /**
2
+ * Adaptive Overlays - Config Schema
3
+ *
4
+ * Zod schema for validating overlays app configuration.
5
+ */
6
+ import { z } from 'zod';
7
+ /**
8
+ * Overlays app config schema.
9
+ * Defines the structure for overlay configurations in a canvas config.
10
+ */
11
+ export const configSchema = z.object({
12
+ /** Highlight configurations */
13
+ highlights: z
14
+ .array(z.object({
15
+ anchorId: z.string(),
16
+ style: z
17
+ .object({
18
+ color: z.string().optional(),
19
+ paddingPx: z.number().optional(),
20
+ radiusPx: z.number().optional(),
21
+ scrimOpacity: z.number().optional(),
22
+ })
23
+ .optional(),
24
+ }))
25
+ .optional(),
26
+ /** Tooltip configurations */
27
+ tooltips: z
28
+ .array(z.object({
29
+ anchorId: z.string(),
30
+ content: z.object({
31
+ title: z.string().optional(),
32
+ body: z.string(),
33
+ cta: z
34
+ .object({
35
+ label: z.string(),
36
+ action: z.unknown(), // ActionStep - validated separately
37
+ })
38
+ .optional(),
39
+ }),
40
+ trigger: z.enum(['immediate', 'hover', 'click']).optional(),
41
+ placement: z
42
+ .enum([
43
+ 'top',
44
+ 'top-start',
45
+ 'top-end',
46
+ 'bottom',
47
+ 'bottom-start',
48
+ 'bottom-end',
49
+ 'left',
50
+ 'left-start',
51
+ 'left-end',
52
+ 'right',
53
+ 'right-start',
54
+ 'right-end',
55
+ ])
56
+ .optional(),
57
+ }))
58
+ .optional(),
59
+ /** Badge configurations */
60
+ badges: z
61
+ .array(z.object({
62
+ anchorId: z.string(),
63
+ content: z.string(),
64
+ position: z.enum(['top-left', 'top-right', 'bottom-left', 'bottom-right']).optional(),
65
+ }))
66
+ .optional(),
67
+ /** Pulse configurations */
68
+ pulses: z
69
+ .array(z.object({
70
+ anchorId: z.string(),
71
+ duration: z.number().optional(),
72
+ }))
73
+ .optional(),
74
+ });
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Tooltip Overlay
3
+ *
4
+ * Creates a positioned tooltip near an element using Floating UI.
5
+ */
6
+ import { type Placement } from '@floating-ui/dom';
7
+ export type TooltipHandle = {
8
+ destroy(): void;
9
+ el: HTMLElement;
10
+ };
11
+ export interface TooltipOptions {
12
+ html: string;
13
+ placement?: Placement | 'auto';
14
+ offsetPx?: number;
15
+ blocking?: boolean;
16
+ trigger?: 'immediate' | 'hover' | 'click';
17
+ onAction?: (actionId: string) => void;
18
+ }
19
+ export declare function showTooltip(anchorEl: HTMLElement, overlayRoot: HTMLElement, opts: TooltipOptions): TooltipHandle;
20
+ //# sourceMappingURL=tooltip.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tooltip.d.ts","sourceRoot":"","sources":["../src/tooltip.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EASL,KAAK,SAAS,EAEf,MAAM,kBAAkB,CAAC;AAG1B,MAAM,MAAM,aAAa,GAAG;IAAE,OAAO,IAAI,IAAI,CAAC;IAAC,EAAE,EAAE,WAAW,CAAA;CAAE,CAAC;AAEjE,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,CAAC,EAAE,SAAS,GAAG,MAAM,CAAC;IAC/B,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,OAAO,CAAC,EAAE,WAAW,GAAG,OAAO,GAAG,OAAO,CAAC;IAC1C,QAAQ,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,IAAI,CAAC;CACvC;AAyCD,wBAAgB,WAAW,CACzB,QAAQ,EAAE,WAAW,EACrB,WAAW,EAAE,WAAW,EACxB,IAAI,EAAE,cAAc,GACnB,aAAa,CAkNf"}