@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,220 @@
1
+ /**
2
+ * Tooltip Overlay
3
+ *
4
+ * Creates a positioned tooltip near an element using Floating UI.
5
+ */
6
+ import { computePosition, autoUpdate, offset, flip, shift, hide, arrow as arrowMiddleware, } from '@floating-ui/dom';
7
+ import { sanitizeHtml } from './sanitizer';
8
+ /**
9
+ * For large elements (larger than viewport), create a virtual anchor
10
+ * at the center of the visible portion.
11
+ */
12
+ function getAnchorReference(anchorEl) {
13
+ const rect = anchorEl.getBoundingClientRect();
14
+ const viewportWidth = window.innerWidth;
15
+ const viewportHeight = window.innerHeight;
16
+ const isLargeElement = rect.width > viewportWidth * 0.8 || rect.height > viewportHeight * 0.8;
17
+ if (!isLargeElement) {
18
+ return anchorEl;
19
+ }
20
+ const visibleLeft = Math.max(rect.left, 0);
21
+ const visibleTop = Math.max(rect.top, 0);
22
+ const visibleRight = Math.min(rect.right, viewportWidth);
23
+ const visibleBottom = Math.min(rect.bottom, viewportHeight);
24
+ const centerX = (visibleLeft + visibleRight) / 2;
25
+ const centerY = (visibleTop + visibleBottom) / 2;
26
+ return {
27
+ getBoundingClientRect() {
28
+ return {
29
+ width: 0,
30
+ height: 0,
31
+ x: centerX,
32
+ y: centerY,
33
+ top: centerY,
34
+ left: centerX,
35
+ right: centerX,
36
+ bottom: centerY,
37
+ };
38
+ },
39
+ };
40
+ }
41
+ export function showTooltip(anchorEl, overlayRoot, opts) {
42
+ // Scroll anchor into view for non-large elements
43
+ const rect = anchorEl.getBoundingClientRect();
44
+ const isLargeElement = rect.width > window.innerWidth * 0.8 || rect.height > window.innerHeight * 0.8;
45
+ if (!isLargeElement) {
46
+ anchorEl.scrollIntoView({ behavior: 'smooth', block: 'center', inline: 'center' });
47
+ }
48
+ const div = document.createElement('div');
49
+ div.className = 'syntro-tooltip';
50
+ div.setAttribute('role', 'tooltip');
51
+ div.innerHTML = sanitizeHtml(opts.html);
52
+ // Handle action button clicks
53
+ const actionBtns = div.querySelectorAll('[data-syntro-action]');
54
+ const actionHandler = (e) => {
55
+ const btn = e.currentTarget;
56
+ const actionId = btn.getAttribute('data-syntro-action');
57
+ if (actionId && opts.onAction) {
58
+ opts.onAction(actionId);
59
+ }
60
+ };
61
+ actionBtns.forEach((btn) => btn.addEventListener('click', actionHandler));
62
+ // Create arrow element
63
+ const arrowEl = document.createElement('div');
64
+ arrowEl.className = 'syntro-tooltip-arrow';
65
+ div.appendChild(arrowEl);
66
+ overlayRoot.appendChild(div);
67
+ const middleware = [
68
+ offset(opts.offsetPx ?? 8),
69
+ flip(),
70
+ shift({ padding: 8 }),
71
+ hide(),
72
+ arrowMiddleware({ element: arrowEl }),
73
+ ];
74
+ const placement = opts.placement && opts.placement !== 'auto' ? opts.placement : 'top';
75
+ const cleanup = autoUpdate(anchorEl, div, async () => {
76
+ const currentAnchorRef = getAnchorReference(anchorEl);
77
+ const result = await computePosition(currentAnchorRef, div, {
78
+ placement,
79
+ middleware,
80
+ });
81
+ const { x, y, strategy, middlewareData, placement: finalPlacement } = result;
82
+ Object.assign(div.style, {
83
+ left: `${x}px`,
84
+ top: `${y}px`,
85
+ position: strategy,
86
+ });
87
+ // Position arrow
88
+ if (middlewareData.arrow) {
89
+ const { x: arrowX, y: arrowY } = middlewareData.arrow;
90
+ const side = finalPlacement.split('-')[0];
91
+ const staticSide = {
92
+ top: 'bottom',
93
+ right: 'left',
94
+ bottom: 'top',
95
+ left: 'right',
96
+ };
97
+ Object.assign(arrowEl.style, {
98
+ left: arrowX != null ? `${arrowX}px` : '',
99
+ top: arrowY != null ? `${arrowY}px` : '',
100
+ right: '',
101
+ bottom: '',
102
+ [staticSide[side]]: '-4px',
103
+ });
104
+ const rotation = {
105
+ top: '0deg',
106
+ right: '90deg',
107
+ bottom: '180deg',
108
+ left: '270deg',
109
+ };
110
+ arrowEl.style.transform = `rotate(${rotation[side] || '0deg'})`;
111
+ }
112
+ });
113
+ const onKey = (e) => {
114
+ if (e.key === 'Escape')
115
+ handle.destroy();
116
+ };
117
+ window.addEventListener('keydown', onKey);
118
+ // Handle blocking mode
119
+ const originalInert = [];
120
+ if (opts.blocking) {
121
+ Array.from(document.body.children).forEach((el) => {
122
+ if (el !== overlayRoot && el.getAttribute('inert') === null) {
123
+ el.setAttribute('inert', '');
124
+ originalInert.push(el.id || el.tagName);
125
+ }
126
+ });
127
+ const focusableEls = Array.from(div.querySelectorAll('button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'));
128
+ if (focusableEls.length > 0) {
129
+ const firstFocusable = focusableEls[0];
130
+ const lastFocusable = focusableEls[focusableEls.length - 1];
131
+ const trapFocus = (e) => {
132
+ if (e.key !== 'Tab')
133
+ return;
134
+ if (e.shiftKey) {
135
+ if (document.activeElement === firstFocusable) {
136
+ lastFocusable.focus();
137
+ e.preventDefault();
138
+ }
139
+ }
140
+ else {
141
+ if (document.activeElement === lastFocusable) {
142
+ firstFocusable.focus();
143
+ e.preventDefault();
144
+ }
145
+ }
146
+ };
147
+ div.addEventListener('keydown', trapFocus);
148
+ requestAnimationFrame(() => firstFocusable.focus());
149
+ }
150
+ }
151
+ const attachTrigger = () => {
152
+ if (opts.trigger === 'hover') {
153
+ const enter = () => {
154
+ div.style.visibility = 'visible';
155
+ div.style.opacity = '1';
156
+ };
157
+ const leave = () => {
158
+ div.style.visibility = 'hidden';
159
+ div.style.opacity = '0';
160
+ };
161
+ div.style.visibility = 'hidden';
162
+ div.style.opacity = '0';
163
+ div.style.transition = 'opacity 200ms ease, visibility 200ms';
164
+ anchorEl.addEventListener('mouseenter', enter);
165
+ anchorEl.addEventListener('mouseleave', leave);
166
+ anchorEl.addEventListener('focus', enter);
167
+ anchorEl.addEventListener('blur', leave);
168
+ return () => {
169
+ anchorEl.removeEventListener('mouseenter', enter);
170
+ anchorEl.removeEventListener('mouseleave', leave);
171
+ anchorEl.removeEventListener('focus', enter);
172
+ anchorEl.removeEventListener('blur', leave);
173
+ };
174
+ }
175
+ if (opts.trigger === 'click') {
176
+ const toggle = () => {
177
+ const isVisible = div.style.visibility === 'visible';
178
+ if (isVisible) {
179
+ handle.destroy();
180
+ }
181
+ else {
182
+ div.style.visibility = 'visible';
183
+ div.style.opacity = '1';
184
+ }
185
+ };
186
+ div.style.visibility = 'hidden';
187
+ div.style.opacity = '0';
188
+ div.style.transition = 'opacity 200ms ease, visibility 200ms';
189
+ anchorEl.addEventListener('click', toggle);
190
+ return () => anchorEl.removeEventListener('click', toggle);
191
+ }
192
+ // Immediate - fade in on mount
193
+ div.style.opacity = '0';
194
+ div.style.transition = 'opacity 200ms ease';
195
+ requestAnimationFrame(() => {
196
+ div.style.opacity = '1';
197
+ });
198
+ return () => { };
199
+ };
200
+ const removeTrigger = attachTrigger();
201
+ const handle = {
202
+ el: div,
203
+ destroy() {
204
+ cleanup();
205
+ removeTrigger();
206
+ window.removeEventListener('keydown', onKey);
207
+ actionBtns.forEach((btn) => btn.removeEventListener('click', actionHandler));
208
+ if (opts.blocking) {
209
+ Array.from(document.body.children).forEach((el) => {
210
+ if (el !== overlayRoot) {
211
+ el.removeAttribute('inert');
212
+ }
213
+ });
214
+ }
215
+ div.style.opacity = '0';
216
+ setTimeout(() => div.remove(), 200);
217
+ },
218
+ };
219
+ return handle;
220
+ }
@@ -0,0 +1,113 @@
1
+ /**
2
+ * Types from @syntrologie/runtime-sdk
3
+ *
4
+ * Minimal type definitions for building this app independently.
5
+ * These match the types exported from @syntrologie/runtime-sdk/types.
6
+ */
7
+ import type { Placement } from '@floating-ui/dom';
8
+ export interface EditorPanelProps {
9
+ config: Record<string, unknown>;
10
+ onChange: (config: Record<string, unknown>) => void;
11
+ editor: {
12
+ setDirty: (dirty: boolean) => void;
13
+ navigateHome: () => Promise<boolean>;
14
+ save: () => Promise<void>;
15
+ publish: (captureScreenshot?: boolean) => Promise<void>;
16
+ };
17
+ platformClient?: unknown;
18
+ }
19
+ export interface HighlightStyle {
20
+ color?: string;
21
+ scrimOpacity?: number;
22
+ paddingPx?: number;
23
+ radiusPx?: number;
24
+ }
25
+ export type BadgePosition = 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right';
26
+ /** CTA button configuration for tooltips/modals */
27
+ export interface CtaButton {
28
+ label: string;
29
+ actionId: string;
30
+ primary?: boolean;
31
+ }
32
+ /** Modal content configuration */
33
+ export interface ModalContent {
34
+ title?: string;
35
+ body: string;
36
+ }
37
+ export interface TooltipContent {
38
+ title?: string;
39
+ body: string;
40
+ cta?: {
41
+ label: string;
42
+ action: unknown;
43
+ };
44
+ ctaButtons?: CtaButton[];
45
+ }
46
+ export type TooltipTrigger = 'immediate' | 'hover' | 'click';
47
+ interface BaseAction {
48
+ label?: string;
49
+ }
50
+ export interface HighlightAction extends BaseAction {
51
+ kind: 'overlays:highlight';
52
+ anchorId: string;
53
+ style?: HighlightStyle;
54
+ /** Duration in ms before auto-cleanup (for use in sequences) */
55
+ duration?: number;
56
+ }
57
+ export interface PulseAction extends BaseAction {
58
+ kind: 'overlays:pulse';
59
+ anchorId: string;
60
+ duration?: number;
61
+ }
62
+ export interface BadgeAction extends BaseAction {
63
+ kind: 'overlays:badge';
64
+ anchorId: string;
65
+ content: string;
66
+ position?: BadgePosition;
67
+ }
68
+ /** Tooltip lifecycle - when the action completes (for sequences) */
69
+ export type TooltipWaitFor = 'dismissed' | 'cta-click' | `timeout:${number}`;
70
+ export interface TooltipAction extends BaseAction {
71
+ kind: 'overlays:tooltip';
72
+ anchorId: string;
73
+ content: TooltipContent;
74
+ trigger?: TooltipTrigger;
75
+ placement?: Placement;
76
+ /** When the action completes (for sequences). Publishes event to EventBus. */
77
+ waitFor?: TooltipWaitFor;
78
+ }
79
+ /** Modal lifecycle - when the action completes (for sequences) */
80
+ export type ModalWaitFor = 'dismissed' | 'cta-click' | `timeout:${number}`;
81
+ export interface ModalAction extends BaseAction {
82
+ kind: 'overlays:modal';
83
+ content: ModalContent;
84
+ size?: 'sm' | 'md' | 'lg';
85
+ blocking?: boolean;
86
+ scrim?: {
87
+ opacity?: number;
88
+ };
89
+ dismiss?: {
90
+ onEsc?: boolean;
91
+ closeButton?: boolean;
92
+ timeoutMs?: number;
93
+ };
94
+ ctaButtons?: CtaButton[];
95
+ /** When the action completes (for sequences). Publishes event to EventBus. */
96
+ waitFor?: ModalWaitFor;
97
+ }
98
+ export type ExecutorCleanup = () => void | Promise<void>;
99
+ export type ExecutorUpdate = (changes: Record<string, unknown>) => void | Promise<void>;
100
+ export interface ExecutorResult {
101
+ cleanup: ExecutorCleanup;
102
+ updateFn?: ExecutorUpdate;
103
+ }
104
+ export interface ExecutorContext {
105
+ overlayRoot: HTMLElement;
106
+ resolveAnchor: (anchorId: string) => HTMLElement | null;
107
+ generateId: () => string;
108
+ publishEvent: (name: string, props?: Record<string, unknown>) => void;
109
+ adaptiveId?: string;
110
+ }
111
+ export type ActionExecutor<T> = (action: T, context: ExecutorContext) => Promise<ExecutorResult>;
112
+ export {};
113
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
package/dist/types.js ADDED
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Types from @syntrologie/runtime-sdk
3
+ *
4
+ * Minimal type definitions for building this app independently.
5
+ * These match the types exported from @syntrologie/runtime-sdk/types.
6
+ */
7
+ export {};
package/package.json ADDED
@@ -0,0 +1,52 @@
1
+ {
2
+ "name": "@syntrologie/adapt-overlays",
3
+ "version": "0.0.0-semantically-released",
4
+ "description": "Adaptive Overlays app - Visual overlays for tooltips, highlights, badges, and pulses",
5
+ "license": "Proprietary",
6
+ "private": false,
7
+ "author": "Syntrologie <eng@syntrologie.com>",
8
+ "repository": {
9
+ "type": "git",
10
+ "url": "https://github.com/SyntropyForge/amazing-demos.git",
11
+ "directory": "tech-core/sdks/adaptives/adaptive-overlays"
12
+ },
13
+ "publishConfig": {
14
+ "access": "public"
15
+ },
16
+ "type": "module",
17
+ "exports": {
18
+ "./runtime": {
19
+ "types": "./dist/runtime.d.ts",
20
+ "import": "./dist/runtime.js"
21
+ },
22
+ "./schema": {
23
+ "types": "./dist/schema.d.ts",
24
+ "import": "./dist/schema.js"
25
+ },
26
+ "./editor": {
27
+ "types": "./dist/editor.d.ts",
28
+ "import": "./dist/editor.js"
29
+ }
30
+ },
31
+ "files": [
32
+ "dist"
33
+ ],
34
+ "scripts": {
35
+ "build": "tsc",
36
+ "clean": "rm -rf dist"
37
+ },
38
+ "dependencies": {
39
+ "@floating-ui/dom": "^1.6.0"
40
+ },
41
+ "peerDependencies": {
42
+ "@syntrologie/runtime-sdk": "^2.0.0",
43
+ "react": ">=18.0.0",
44
+ "react-dom": ">=18.0.0",
45
+ "zod": "^3.0.0"
46
+ },
47
+ "devDependencies": {
48
+ "@types/react": "^18.0.0",
49
+ "typescript": "^5.0.0",
50
+ "zod": "^3.25.0"
51
+ }
52
+ }