@syntrologie/adapt-overlays 2.10.0 → 2.12.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/dist/WorkflowWidget.js +3 -3
- package/dist/cdn.d.ts +2 -2
- package/dist/celebrations/__tests__/reduced-motion.test.d.ts +2 -0
- package/dist/celebrations/__tests__/reduced-motion.test.d.ts.map +1 -0
- package/dist/celebrations/__tests__/reduced-motion.test.js +97 -0
- package/dist/celebrations/engine.d.ts.map +1 -1
- package/dist/celebrations/engine.js +4 -0
- package/dist/editor.d.ts +6 -6
- package/dist/editor.d.ts.map +1 -1
- package/dist/editor.js +6 -412
- package/dist/overlay-editor-state.d.ts +41 -0
- package/dist/overlay-editor-state.d.ts.map +1 -0
- package/dist/overlay-editor-state.js +131 -0
- package/dist/overlay-editor-ui.d.ts +9 -0
- package/dist/overlay-editor-ui.d.ts.map +1 -0
- package/dist/overlay-editor-ui.js +306 -0
- package/dist/runtime.d.ts +2 -2
- package/dist/runtime.js +1 -1
- package/dist/tour-types.d.ts +1 -1
- package/dist/tour-types.d.ts.map +1 -1
- package/node_modules/@syntro/design-system/dist/tailwind-preset.d.ts.map +1 -1
- package/node_modules/@syntro/design-system/dist/tailwind-preset.js +4 -2
- package/node_modules/@syntro/design-system/dist/tokens/colors.css +1 -1
- package/node_modules/@syntro/design-system/dist/tokens/colors.d.ts +2 -2
- package/node_modules/@syntro/design-system/dist/tokens/colors.js +1 -1
- package/node_modules/@syntro/design-system/dist/tokens/effects.d.ts +54 -0
- package/node_modules/@syntro/design-system/dist/tokens/effects.d.ts.map +1 -1
- package/node_modules/@syntro/design-system/dist/tokens/effects.js +44 -0
- package/node_modules/@syntro/design-system/package.json +2 -2
- package/node_modules/@syntrologie/shared-editor-ui/dist/components/AnchorPicker.js +2 -4
- package/node_modules/@syntrologie/shared-editor-ui/dist/components/ElementHighlight.js +2 -4
- package/node_modules/@syntrologie/shared-editor-ui/dist/hooks/useTriggerWhenStatus.d.ts.map +1 -1
- package/node_modules/@syntrologie/shared-editor-ui/dist/hooks/useTriggerWhenStatus.js +0 -1
- package/node_modules/@syntrologie/shared-editor-ui/package.json +11 -9
- package/package.json +12 -12
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Adaptive Overlays - Editor State & Helpers
|
|
3
|
+
*
|
|
4
|
+
* Pure logic: anchor resolution, types, section configuration,
|
|
5
|
+
* flatten/filter helpers, tour step utilities, and dismissal logic.
|
|
6
|
+
* No React or JSX — consumed by the UI module.
|
|
7
|
+
*/
|
|
8
|
+
import type { OverlaysConfig } from './schema';
|
|
9
|
+
import type { TourStep } from './types';
|
|
10
|
+
/** Extract the CSS selector string from an anchorId object. */
|
|
11
|
+
export declare function resolveAnchorSelector(anchorId: unknown): string;
|
|
12
|
+
/** Extract the target route from an AnchorId object, ignoring wildcard '**'. */
|
|
13
|
+
export declare function resolveAnchorRoute(anchorId: unknown): string | null;
|
|
14
|
+
/** Save a pending highlight selector to sessionStorage (inlined to avoid cross-package import). */
|
|
15
|
+
export declare function savePendingHighlight(selector: string): void;
|
|
16
|
+
export type OverlaySection = 'tooltips' | 'highlights' | 'badges' | 'pulses' | 'modals';
|
|
17
|
+
export type SectionKey = OverlaySection | 'tours';
|
|
18
|
+
export declare function itemKey(section: SectionKey, index: number): string;
|
|
19
|
+
export declare const OVERLAY_SECTIONS: OverlaySection[];
|
|
20
|
+
export interface FlatItem {
|
|
21
|
+
key: string;
|
|
22
|
+
section: SectionKey;
|
|
23
|
+
index: number;
|
|
24
|
+
summary: string;
|
|
25
|
+
anchorId: string;
|
|
26
|
+
rawAnchorId: unknown;
|
|
27
|
+
isTour: boolean;
|
|
28
|
+
}
|
|
29
|
+
export declare function flattenItems(config: OverlaysConfig): FlatItem[];
|
|
30
|
+
export declare function filterConfig(config: OverlaysConfig, dismissedKeys: Set<string>): OverlaysConfig;
|
|
31
|
+
export declare function getStepIcon(step: TourStep): string;
|
|
32
|
+
export declare function getStepLabel(step: TourStep): string;
|
|
33
|
+
export interface DetectionEntry {
|
|
34
|
+
found: boolean;
|
|
35
|
+
element: HTMLElement | null;
|
|
36
|
+
}
|
|
37
|
+
export declare function parseOverlayItemKey(key: string): {
|
|
38
|
+
section: OverlaySection;
|
|
39
|
+
index: number;
|
|
40
|
+
};
|
|
41
|
+
//# sourceMappingURL=overlay-editor-state.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"overlay-editor-state.d.ts","sourceRoot":"","sources":["../src/overlay-editor-state.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,UAAU,CAAC;AAE/C,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AAMxC,+DAA+D;AAC/D,wBAAgB,qBAAqB,CAAC,QAAQ,EAAE,OAAO,GAAG,MAAM,CAK/D;AAED,gFAAgF;AAChF,wBAAgB,kBAAkB,CAAC,QAAQ,EAAE,OAAO,GAAG,MAAM,GAAG,IAAI,CASnE;AAED,mGAAmG;AACnG,wBAAgB,oBAAoB,CAAC,QAAQ,EAAE,MAAM,QAMpD;AAMD,MAAM,MAAM,cAAc,GAAG,UAAU,GAAG,YAAY,GAAG,QAAQ,GAAG,QAAQ,GAAG,QAAQ,CAAC;AACxF,MAAM,MAAM,UAAU,GAAG,cAAc,GAAG,OAAO,CAAC;AAElD,wBAAgB,OAAO,CAAC,OAAO,EAAE,UAAU,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,CAElE;AAMD,eAAO,MAAM,gBAAgB,EAAE,cAAc,EAM5C,CAAC;AAMF,MAAM,WAAW,QAAQ;IACvB,GAAG,EAAE,MAAM,CAAC;IACZ,OAAO,EAAE,UAAU,CAAC;IACpB,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,EAAE,OAAO,CAAC;IACrB,MAAM,EAAE,OAAO,CAAC;CACjB;AAED,wBAAgB,YAAY,CAAC,MAAM,EAAE,cAAc,GAAG,QAAQ,EAAE,CAkC/D;AAED,wBAAgB,YAAY,CAAC,MAAM,EAAE,cAAc,EAAE,aAAa,EAAE,GAAG,CAAC,MAAM,CAAC,GAAG,cAAc,CAW/F;AAED,wBAAgB,WAAW,CAAC,IAAI,EAAE,QAAQ,GAAG,MAAM,CASlD;AAED,wBAAgB,YAAY,CAAC,IAAI,EAAE,QAAQ,GAAG,MAAM,CAQnD;AAMD,MAAM,WAAW,cAAc;IAC7B,KAAK,EAAE,OAAO,CAAC;IACf,OAAO,EAAE,WAAW,GAAG,IAAI,CAAC;CAC7B;AAED,wBAAgB,mBAAmB,CAAC,GAAG,EAAE,MAAM,GAAG;IAAE,OAAO,EAAE,cAAc,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAG3F"}
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Adaptive Overlays - Editor State & Helpers
|
|
3
|
+
*
|
|
4
|
+
* Pure logic: anchor resolution, types, section configuration,
|
|
5
|
+
* flatten/filter helpers, tour step utilities, and dismissal logic.
|
|
6
|
+
* No React or JSX — consumed by the UI module.
|
|
7
|
+
*/
|
|
8
|
+
import { summarizeOverlayItem } from './summarize';
|
|
9
|
+
// ============================================================================
|
|
10
|
+
// Anchor Helpers
|
|
11
|
+
// ============================================================================
|
|
12
|
+
/** Extract the CSS selector string from an anchorId object. */
|
|
13
|
+
export function resolveAnchorSelector(anchorId) {
|
|
14
|
+
if (!anchorId)
|
|
15
|
+
return '';
|
|
16
|
+
if (typeof anchorId === 'string')
|
|
17
|
+
return anchorId;
|
|
18
|
+
if (typeof anchorId === 'object')
|
|
19
|
+
return anchorId.selector ?? '';
|
|
20
|
+
return '';
|
|
21
|
+
}
|
|
22
|
+
/** Extract the target route from an AnchorId object, ignoring wildcard '**'. */
|
|
23
|
+
export function resolveAnchorRoute(anchorId) {
|
|
24
|
+
if (!anchorId || typeof anchorId !== 'object')
|
|
25
|
+
return null;
|
|
26
|
+
const route = anchorId.route;
|
|
27
|
+
if (typeof route === 'string' && route !== '**')
|
|
28
|
+
return route;
|
|
29
|
+
if (Array.isArray(route)) {
|
|
30
|
+
const first = route.find((r) => typeof r === 'string' && r !== '**');
|
|
31
|
+
return first ?? null;
|
|
32
|
+
}
|
|
33
|
+
return null;
|
|
34
|
+
}
|
|
35
|
+
/** Save a pending highlight selector to sessionStorage (inlined to avoid cross-package import). */
|
|
36
|
+
export function savePendingHighlight(selector) {
|
|
37
|
+
try {
|
|
38
|
+
sessionStorage.setItem('syntro:editor:pending-highlight', selector);
|
|
39
|
+
}
|
|
40
|
+
catch {
|
|
41
|
+
// Silently ignore
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
export function itemKey(section, index) {
|
|
45
|
+
return `${section}:${index}`;
|
|
46
|
+
}
|
|
47
|
+
// ============================================================================
|
|
48
|
+
// Section Config
|
|
49
|
+
// ============================================================================
|
|
50
|
+
export const OVERLAY_SECTIONS = [
|
|
51
|
+
'tooltips',
|
|
52
|
+
'highlights',
|
|
53
|
+
'badges',
|
|
54
|
+
'pulses',
|
|
55
|
+
'modals',
|
|
56
|
+
];
|
|
57
|
+
export function flattenItems(config) {
|
|
58
|
+
const items = [];
|
|
59
|
+
for (const section of OVERLAY_SECTIONS) {
|
|
60
|
+
const arr = config[section] || [];
|
|
61
|
+
arr.forEach((item, i) => {
|
|
62
|
+
const rec = item;
|
|
63
|
+
items.push({
|
|
64
|
+
key: itemKey(section, i),
|
|
65
|
+
section,
|
|
66
|
+
index: i,
|
|
67
|
+
summary: summarizeOverlayItem(section, rec),
|
|
68
|
+
anchorId: resolveAnchorSelector(rec.anchorId),
|
|
69
|
+
rawAnchorId: rec.anchorId,
|
|
70
|
+
isTour: false,
|
|
71
|
+
});
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
// Tours
|
|
75
|
+
const tours = config.tours || [];
|
|
76
|
+
tours.forEach((tour, i) => {
|
|
77
|
+
items.push({
|
|
78
|
+
key: itemKey('tours', i),
|
|
79
|
+
section: 'tours',
|
|
80
|
+
index: i,
|
|
81
|
+
summary: summarizeOverlayItem('tours', tour),
|
|
82
|
+
anchorId: '',
|
|
83
|
+
rawAnchorId: undefined,
|
|
84
|
+
isTour: true,
|
|
85
|
+
});
|
|
86
|
+
});
|
|
87
|
+
return items;
|
|
88
|
+
}
|
|
89
|
+
export function filterConfig(config, dismissedKeys) {
|
|
90
|
+
const result = { ...config };
|
|
91
|
+
const allSections = [...OVERLAY_SECTIONS, 'tours'];
|
|
92
|
+
for (const section of allSections) {
|
|
93
|
+
const arr = config[section] || [];
|
|
94
|
+
const filtered = arr.filter((_, i) => !dismissedKeys.has(itemKey(section, i)));
|
|
95
|
+
if (filtered.length > 0 || config[section] !== undefined) {
|
|
96
|
+
result[section] = filtered;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
return result;
|
|
100
|
+
}
|
|
101
|
+
export function getStepIcon(step) {
|
|
102
|
+
const action = step.action;
|
|
103
|
+
const kind = action.kind || '';
|
|
104
|
+
if (kind.includes('tooltip'))
|
|
105
|
+
return '\u{1f4ac}';
|
|
106
|
+
if (kind.includes('highlight'))
|
|
107
|
+
return '\u{2728}';
|
|
108
|
+
if (kind.includes('modal'))
|
|
109
|
+
return '\u{1f4e6}';
|
|
110
|
+
if (kind.includes('badge'))
|
|
111
|
+
return '\u{1f3f7}\u{fe0f}';
|
|
112
|
+
if (kind.includes('pulse'))
|
|
113
|
+
return '\u{1f4ab}';
|
|
114
|
+
return '\u{25cf}';
|
|
115
|
+
}
|
|
116
|
+
export function getStepLabel(step) {
|
|
117
|
+
const action = step.action;
|
|
118
|
+
const anchor = resolveAnchorSelector(action.anchorId);
|
|
119
|
+
if (anchor)
|
|
120
|
+
return anchor;
|
|
121
|
+
const content = action.content;
|
|
122
|
+
if (content?.title)
|
|
123
|
+
return content.title;
|
|
124
|
+
if (content?.body)
|
|
125
|
+
return content.body.slice(0, 30);
|
|
126
|
+
return step.id;
|
|
127
|
+
}
|
|
128
|
+
export function parseOverlayItemKey(key) {
|
|
129
|
+
const [section, indexStr] = key.split(':');
|
|
130
|
+
return { section: section, index: Number(indexStr) };
|
|
131
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Adaptive Overlays - Editor UI Component
|
|
3
|
+
*
|
|
4
|
+
* Main editor component, tabs, forms, tour drill-in, item editing, save/publish.
|
|
5
|
+
* All React/JSX lives here; pure logic is imported from overlay-editor-state.
|
|
6
|
+
*/
|
|
7
|
+
import type { EditorPanelProps } from './types';
|
|
8
|
+
export declare function OverlaysEditor({ config, onChange, editor }: EditorPanelProps): import("react/jsx-runtime").JSX.Element;
|
|
9
|
+
//# sourceMappingURL=overlay-editor-ui.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"overlay-editor-ui.d.ts","sourceRoot":"","sources":["../src/overlay-editor-ui.tsx"],"names":[],"mappings":"AAAA;;;;;GAKG;AAiCH,OAAO,KAAK,EAAE,gBAAgB,EAAY,MAAM,SAAS,CAAC;AAkF1D,wBAAgB,cAAc,CAAC,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,EAAE,gBAAgB,2CAif5E"}
|
|
@@ -0,0 +1,306 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
+
/**
|
|
3
|
+
* Adaptive Overlays - Editor UI Component
|
|
4
|
+
*
|
|
5
|
+
* Main editor component, tabs, forms, tour drill-in, item editing, save/publish.
|
|
6
|
+
* All React/JSX lives here; pure logic is imported from overlay-editor-state.
|
|
7
|
+
*/
|
|
8
|
+
import { DetectionBadge, DismissedSection, EditorBody, EditorCard, EditorFooter, EditorHeader, EditorInput, EditorLayout, EditorTextarea, EmptyState, GroupHeader, } from '@syntrologie/shared-editor-ui';
|
|
9
|
+
import { MessageSquare, Route, Sparkles, Square, Tag, Zap } from 'lucide-react';
|
|
10
|
+
import { useCallback, useEffect, useRef, useState } from 'react';
|
|
11
|
+
import { filterConfig, flattenItems, getStepIcon, getStepLabel, parseOverlayItemKey, resolveAnchorRoute, resolveAnchorSelector, savePendingHighlight, } from './overlay-editor-state';
|
|
12
|
+
// ============================================================================
|
|
13
|
+
// Section Icons
|
|
14
|
+
// ============================================================================
|
|
15
|
+
const SECTION_ICON_MAP = {
|
|
16
|
+
tooltips: MessageSquare,
|
|
17
|
+
highlights: Sparkles,
|
|
18
|
+
badges: Tag,
|
|
19
|
+
pulses: Zap,
|
|
20
|
+
modals: Square,
|
|
21
|
+
tours: Route,
|
|
22
|
+
};
|
|
23
|
+
/** Renders the appropriate Lucide icon for a section type */
|
|
24
|
+
function SectionIcon({ section, className }) {
|
|
25
|
+
const IconComponent = SECTION_ICON_MAP[section];
|
|
26
|
+
return _jsx(IconComponent, { size: 16, className: className });
|
|
27
|
+
}
|
|
28
|
+
// ============================================================================
|
|
29
|
+
// Anchor Detection Hook
|
|
30
|
+
// ============================================================================
|
|
31
|
+
function useAnchorDetection(items, config) {
|
|
32
|
+
const [detectionMap, setDetectionMap] = useState(new Map());
|
|
33
|
+
const itemsRef = useRef(items);
|
|
34
|
+
const configRef = useRef(config);
|
|
35
|
+
itemsRef.current = items;
|
|
36
|
+
configRef.current = config;
|
|
37
|
+
useEffect(() => {
|
|
38
|
+
const runDetection = () => {
|
|
39
|
+
const map = new Map();
|
|
40
|
+
for (const item of itemsRef.current) {
|
|
41
|
+
let selectorToCheck = item.anchorId;
|
|
42
|
+
// For tours, detect the first step's anchor
|
|
43
|
+
if (item.isTour && !selectorToCheck) {
|
|
44
|
+
const tours = configRef.current.tours || [];
|
|
45
|
+
const tour = tours[item.index];
|
|
46
|
+
if (tour && tour.steps.length > 0) {
|
|
47
|
+
const firstStep = tour.steps[0];
|
|
48
|
+
const action = firstStep.action;
|
|
49
|
+
selectorToCheck = resolveAnchorSelector(action?.anchorId);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
if (!selectorToCheck) {
|
|
53
|
+
map.set(item.key, { found: false, element: null });
|
|
54
|
+
continue;
|
|
55
|
+
}
|
|
56
|
+
try {
|
|
57
|
+
const el = document.querySelector(selectorToCheck);
|
|
58
|
+
map.set(item.key, { found: el !== null, element: el });
|
|
59
|
+
}
|
|
60
|
+
catch {
|
|
61
|
+
map.set(item.key, { found: false, element: null });
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
setDetectionMap(map);
|
|
65
|
+
};
|
|
66
|
+
runDetection();
|
|
67
|
+
const interval = setInterval(runDetection, 2000);
|
|
68
|
+
return () => clearInterval(interval);
|
|
69
|
+
}, []);
|
|
70
|
+
return detectionMap;
|
|
71
|
+
}
|
|
72
|
+
// ============================================================================
|
|
73
|
+
// OverlaysEditor Component
|
|
74
|
+
// ============================================================================
|
|
75
|
+
export function OverlaysEditor({ config, onChange, editor }) {
|
|
76
|
+
const typedConfig = config;
|
|
77
|
+
const [dismissedKeys, setDismissedKeys] = useState(() => editor.getDismissedKeys?.() ?? new Set());
|
|
78
|
+
const [expandedTour, setExpandedTour] = useState(null);
|
|
79
|
+
const [editingKey, setEditingKey] = useState(null);
|
|
80
|
+
const [_previewMode, setPreviewMode] = useState('after');
|
|
81
|
+
// Sync dismissed keys back to navigation context on every change
|
|
82
|
+
useEffect(() => {
|
|
83
|
+
editor.setDismissedKeys?.(dismissedKeys);
|
|
84
|
+
}, [dismissedKeys, editor]);
|
|
85
|
+
// React to global before/after toggle from the panel
|
|
86
|
+
// biome-ignore lint/correctness/useExhaustiveDependencies: intentionally omitted — adding config/typedConfig/previewConfig would cause infinite re-renders since previewConfig triggers state updates
|
|
87
|
+
useEffect(() => {
|
|
88
|
+
const mode = editor.previewMode;
|
|
89
|
+
if (!mode)
|
|
90
|
+
return;
|
|
91
|
+
if (mode === 'before') {
|
|
92
|
+
// Remove all overlay changes — push a config with every item filtered out
|
|
93
|
+
const allKeys = new Set(flattenItems(typedConfig).map((item) => item.key));
|
|
94
|
+
const empty = filterConfig(typedConfig, allKeys);
|
|
95
|
+
editor.previewConfig(empty);
|
|
96
|
+
}
|
|
97
|
+
else {
|
|
98
|
+
// Restore the full config
|
|
99
|
+
editor.previewConfig(config);
|
|
100
|
+
}
|
|
101
|
+
}, [editor.previewMode]);
|
|
102
|
+
// Consume initialEditKey from accordion navigation on mount
|
|
103
|
+
const initialConsumed = useRef(false);
|
|
104
|
+
useEffect(() => {
|
|
105
|
+
if (editor.initialEditKey != null && !initialConsumed.current) {
|
|
106
|
+
initialConsumed.current = true;
|
|
107
|
+
const allFlat = flattenItems(typedConfig);
|
|
108
|
+
const targetIdx = Number(editor.initialEditKey);
|
|
109
|
+
if (targetIdx >= 0 && targetIdx < allFlat.length) {
|
|
110
|
+
const target = allFlat[targetIdx];
|
|
111
|
+
if (target.isTour) {
|
|
112
|
+
setExpandedTour(target.key);
|
|
113
|
+
}
|
|
114
|
+
else {
|
|
115
|
+
setEditingKey(target.key);
|
|
116
|
+
if (target.anchorId) {
|
|
117
|
+
editor.highlightElement(target.anchorId);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
editor.clearInitialState?.();
|
|
122
|
+
}
|
|
123
|
+
else if (editor.initialCreate && !initialConsumed.current) {
|
|
124
|
+
initialConsumed.current = true;
|
|
125
|
+
editor.clearInitialState?.();
|
|
126
|
+
}
|
|
127
|
+
}, [editor, typedConfig]);
|
|
128
|
+
const allItems = flattenItems(typedConfig);
|
|
129
|
+
const activeItems = allItems.filter((item) => !dismissedKeys.has(item.key));
|
|
130
|
+
const dismissedItems = allItems.filter((item) => dismissedKeys.has(item.key));
|
|
131
|
+
const overlayItems = activeItems.filter((item) => !item.isTour);
|
|
132
|
+
const tourItems = activeItems.filter((item) => item.isTour);
|
|
133
|
+
const totalItems = activeItems.length;
|
|
134
|
+
const [_hoveredKey, setHoveredKey] = useState(null);
|
|
135
|
+
const detectionMap = useAnchorDetection(allItems, typedConfig);
|
|
136
|
+
const foundCount = activeItems.filter((item) => detectionMap.get(item.key)?.found).length;
|
|
137
|
+
const handleDismiss = useCallback((key) => {
|
|
138
|
+
setDismissedKeys((prev) => {
|
|
139
|
+
const next = new Set(prev);
|
|
140
|
+
next.add(key);
|
|
141
|
+
return next;
|
|
142
|
+
});
|
|
143
|
+
if (expandedTour === key)
|
|
144
|
+
setExpandedTour(null);
|
|
145
|
+
if (editingKey === key)
|
|
146
|
+
setEditingKey(null);
|
|
147
|
+
}, [expandedTour, editingKey]);
|
|
148
|
+
const handleRestore = useCallback((key) => {
|
|
149
|
+
setDismissedKeys((prev) => {
|
|
150
|
+
const next = new Set(prev);
|
|
151
|
+
next.delete(key);
|
|
152
|
+
return next;
|
|
153
|
+
});
|
|
154
|
+
}, []);
|
|
155
|
+
const handleCardClick = useCallback((item) => {
|
|
156
|
+
if (item.isTour) {
|
|
157
|
+
setExpandedTour((prev) => (prev === item.key ? null : item.key));
|
|
158
|
+
}
|
|
159
|
+
else {
|
|
160
|
+
if (item.anchorId) {
|
|
161
|
+
editor.highlightElement(item.anchorId);
|
|
162
|
+
}
|
|
163
|
+
setEditingKey(item.key);
|
|
164
|
+
}
|
|
165
|
+
}, [editor]);
|
|
166
|
+
const handleBackToList = useCallback(() => {
|
|
167
|
+
setEditingKey(null);
|
|
168
|
+
setPreviewMode('after');
|
|
169
|
+
editor.previewConfig(config);
|
|
170
|
+
editor.clearHighlight();
|
|
171
|
+
}, [editor, config]);
|
|
172
|
+
// Register back handler in panel header when editing
|
|
173
|
+
useEffect(() => {
|
|
174
|
+
editor.setBackHandler?.(editingKey !== null ? handleBackToList : null);
|
|
175
|
+
return () => editor.setBackHandler?.(null);
|
|
176
|
+
}, [editingKey, handleBackToList, editor]);
|
|
177
|
+
const _handleBeforeAfter = useCallback((mode) => {
|
|
178
|
+
setPreviewMode(mode);
|
|
179
|
+
if (mode === 'before') {
|
|
180
|
+
const filtered = filterConfig(typedConfig, new Set([editingKey]));
|
|
181
|
+
editor.previewConfig(filtered);
|
|
182
|
+
}
|
|
183
|
+
else {
|
|
184
|
+
editor.previewConfig(config);
|
|
185
|
+
}
|
|
186
|
+
}, [typedConfig, editingKey, editor, config]);
|
|
187
|
+
const handleFieldChange = useCallback((section, index, updater) => {
|
|
188
|
+
const arr = (typedConfig[section] || []).slice();
|
|
189
|
+
const item = { ...arr[index] };
|
|
190
|
+
arr[index] = updater(item);
|
|
191
|
+
const updated = { ...typedConfig, [section]: arr };
|
|
192
|
+
onChange(updated);
|
|
193
|
+
editor.setDirty(true);
|
|
194
|
+
}, [typedConfig, onChange, editor]);
|
|
195
|
+
const handlePublish = useCallback(() => {
|
|
196
|
+
if (dismissedKeys.size > 0) {
|
|
197
|
+
const filtered = filterConfig(typedConfig, dismissedKeys);
|
|
198
|
+
onChange(filtered);
|
|
199
|
+
}
|
|
200
|
+
editor.publish();
|
|
201
|
+
}, [dismissedKeys, typedConfig, onChange, editor]);
|
|
202
|
+
const handleBadgeClick = useCallback(async (item) => {
|
|
203
|
+
const detection = detectionMap.get(item.key);
|
|
204
|
+
if (detection?.found && item.anchorId) {
|
|
205
|
+
editor.highlightElement(item.anchorId);
|
|
206
|
+
}
|
|
207
|
+
else {
|
|
208
|
+
const route = resolveAnchorRoute(item.rawAnchorId);
|
|
209
|
+
if (route) {
|
|
210
|
+
if (item.anchorId)
|
|
211
|
+
savePendingHighlight(item.anchorId);
|
|
212
|
+
await editor.navigateTo(route);
|
|
213
|
+
if (item.anchorId)
|
|
214
|
+
editor.highlightElement(item.anchorId);
|
|
215
|
+
}
|
|
216
|
+
else if (item.anchorId) {
|
|
217
|
+
editor.highlightElement(item.anchorId);
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
}, [editor, detectionMap]);
|
|
221
|
+
const handleCardHover = useCallback((item) => {
|
|
222
|
+
setHoveredKey(item.key);
|
|
223
|
+
if (item.anchorId) {
|
|
224
|
+
editor.highlightElement(item.anchorId);
|
|
225
|
+
}
|
|
226
|
+
}, [editor]);
|
|
227
|
+
const handleCardLeave = useCallback(() => {
|
|
228
|
+
setHoveredKey(null);
|
|
229
|
+
editor.clearHighlight();
|
|
230
|
+
}, [editor]);
|
|
231
|
+
// ---- Edit form renderers per overlay type ----
|
|
232
|
+
const renderEditFields = (section, index) => {
|
|
233
|
+
const arr = typedConfig[section] || [];
|
|
234
|
+
const item = arr[index];
|
|
235
|
+
if (!item)
|
|
236
|
+
return null;
|
|
237
|
+
const anchorId = resolveAnchorSelector(item.anchorId);
|
|
238
|
+
switch (section) {
|
|
239
|
+
case 'tooltips': {
|
|
240
|
+
const content = item.content || {};
|
|
241
|
+
return (_jsxs("div", { className: "se-py-1", children: [_jsx("div", { className: "se-text-[11px] se-font-mono se-text-slate-grey-8 se-py-1 se-px-2 se-bg-white/[0.04] se-rounded se-mb-3", children: anchorId }), _jsx(EditorInput, { label: "Title", value: content.title || '', onChange: (e) => handleFieldChange(section, index, (it) => ({
|
|
242
|
+
...it,
|
|
243
|
+
content: { ...it.content, title: e.target.value },
|
|
244
|
+
})) }), _jsx(EditorTextarea, { label: "Body", value: content.body || '', onChange: (e) => handleFieldChange(section, index, (it) => ({
|
|
245
|
+
...it,
|
|
246
|
+
content: { ...it.content, body: e.target.value },
|
|
247
|
+
})) })] }));
|
|
248
|
+
}
|
|
249
|
+
case 'highlights':
|
|
250
|
+
return (_jsxs("div", { className: "se-py-1", children: [_jsx("div", { className: "se-text-[11px] se-font-mono se-text-slate-grey-8 se-py-1 se-px-2 se-bg-white/[0.04] se-rounded se-mb-3", children: anchorId }), _jsx(EditorInput, { label: "Color", value: item.style?.color || '', onChange: (e) => handleFieldChange(section, index, (it) => ({
|
|
251
|
+
...it,
|
|
252
|
+
style: {
|
|
253
|
+
...(it.style || {}),
|
|
254
|
+
color: e.target.value,
|
|
255
|
+
},
|
|
256
|
+
})) })] }));
|
|
257
|
+
case 'badges':
|
|
258
|
+
return (_jsxs("div", { className: "se-py-1", children: [_jsx("div", { className: "se-text-[11px] se-font-mono se-text-slate-grey-8 se-py-1 se-px-2 se-bg-white/[0.04] se-rounded se-mb-3", children: anchorId }), _jsx(EditorInput, { label: "Content", value: item.content || '', onChange: (e) => handleFieldChange(section, index, (it) => ({
|
|
259
|
+
...it,
|
|
260
|
+
content: e.target.value,
|
|
261
|
+
})) })] }));
|
|
262
|
+
case 'pulses':
|
|
263
|
+
return (_jsxs("div", { className: "se-py-1", children: [_jsx("div", { className: "se-text-[11px] se-font-mono se-text-slate-grey-8 se-py-1 se-px-2 se-bg-white/[0.04] se-rounded se-mb-3", children: anchorId }), _jsx(EditorInput, { label: "Duration (ms)", type: "number", value: item.duration || '', onChange: (e) => handleFieldChange(section, index, (it) => ({
|
|
264
|
+
...it,
|
|
265
|
+
duration: Number(e.target.value) || undefined,
|
|
266
|
+
})) })] }));
|
|
267
|
+
case 'modals': {
|
|
268
|
+
const content = item.content || {};
|
|
269
|
+
return (_jsxs("div", { className: "se-py-1", children: [_jsx(EditorInput, { label: "Title", value: content.title || '', onChange: (e) => handleFieldChange(section, index, (it) => ({
|
|
270
|
+
...it,
|
|
271
|
+
content: { ...it.content, title: e.target.value },
|
|
272
|
+
})) }), _jsx(EditorTextarea, { label: "Body", value: content.body || '', onChange: (e) => handleFieldChange(section, index, (it) => ({
|
|
273
|
+
...it,
|
|
274
|
+
content: { ...it.content, body: e.target.value },
|
|
275
|
+
})) })] }));
|
|
276
|
+
}
|
|
277
|
+
default:
|
|
278
|
+
return null;
|
|
279
|
+
}
|
|
280
|
+
};
|
|
281
|
+
const renderTourDrillIn = (tourIdx) => {
|
|
282
|
+
const tours = typedConfig.tours || [];
|
|
283
|
+
const tour = tours[tourIdx];
|
|
284
|
+
if (!tour)
|
|
285
|
+
return null;
|
|
286
|
+
return (_jsxs("div", { className: "se-p-3 se-rounded-lg se-border se-border-white/[0.08] se-bg-white/[0.02] se-mt-1 se-mb-2", children: [_jsxs("div", { className: "se-text-[13px] se-font-semibold se-text-slate-grey-10 se-mb-2", children: ['\u{1f3af}', " Tour: ", tour.tourId] }), _jsxs("label", { className: "se-flex se-items-center se-gap-2 se-text-xs se-text-[#d1d5db] se-mb-2", children: [_jsx("input", { type: "checkbox", checked: tour.autoStart || false, readOnly: true }), "Auto-start tour"] }), tour.steps.map((step, stepIdx) => (_jsxs("div", { className: "se-flex se-items-center se-gap-2 se-py-1.5 se-px-2 se-rounded se-border se-border-white/[0.04] se-mb-1 se-text-xs se-text-[#d1d5db]", children: [_jsxs("span", { className: "se-text-[11px] se-font-bold se-text-slate-grey-7 se-min-w-[18px]", children: [stepIdx + 1, "."] }), _jsx("span", { children: getStepIcon(step) }), _jsxs("div", { className: "se-flex-1 se-overflow-hidden", children: [_jsx("div", { children: getStepLabel(step) }), step.route && (_jsx("div", { className: "se-text-[10px] se-text-slate-grey-7 se-font-mono", children: step.route }))] })] }, step.id || stepIdx))), tour.steps.length === 0 && (_jsx("div", { className: "se-text-xs se-text-slate-grey-7 se-py-2", children: "No steps in this tour." })), _jsx("button", { type: "button", className: "se-py-1 se-px-2.5 se-rounded se-border se-border-white/10 se-bg-transparent se-text-slate-grey-8 se-text-[11px] se-cursor-pointer se-mt-2", onClick: () => setExpandedTour(null), children: "\u2190 Back to list" })] }));
|
|
287
|
+
};
|
|
288
|
+
const renderCard = (item) => {
|
|
289
|
+
const detection = detectionMap.get(item.key);
|
|
290
|
+
return (_jsxs("div", { children: [_jsxs(EditorCard, { itemKey: item.key, onClick: () => handleCardClick(item), className: "se-flex se-items-center se-gap-2", onMouseEnter: () => handleCardHover(item), onMouseLeave: handleCardLeave, children: [_jsx(DetectionBadge, { found: detection?.found ?? false, onClick: () => handleBadgeClick(item) }), _jsx("span", { className: "se-shrink-0 se-flex se-items-center -se-ml-1", onClick: (e) => {
|
|
291
|
+
e.stopPropagation();
|
|
292
|
+
handleCardClick(item);
|
|
293
|
+
}, children: _jsx(SectionIcon, { section: item.section }) }), _jsx("span", { className: "se-flex-1 se-overflow-hidden se-text-ellipsis se-whitespace-nowrap", onClick: () => handleCardClick(item), children: item.summary }), _jsx("button", { type: "button", className: "se-py-0.5 se-px-1.5 se-rounded se-border-none se-bg-transparent se-text-slate-grey-7 se-text-sm se-cursor-pointer se-shrink-0 se-leading-none", onClick: (e) => {
|
|
294
|
+
e.stopPropagation();
|
|
295
|
+
handleDismiss(item.key);
|
|
296
|
+
}, title: "Dismiss", children: "\u00D7" })] }), item.isTour && expandedTour === item.key && renderTourDrillIn(item.index)] }, item.key));
|
|
297
|
+
};
|
|
298
|
+
return (_jsxs(EditorLayout, { children: [_jsx(EditorHeader, { title: "Review Changes", subtitle: `${totalItems} item${totalItems !== 1 ? 's' : ''}${totalItems > 0 ? ` (${foundCount} found on this page)` : ''}`, onBack: () => editor.navigateHome() }), _jsx(EditorBody, { children: editingKey !== null ? ((() => {
|
|
299
|
+
const ref = parseOverlayItemKey(editingKey);
|
|
300
|
+
const editItem = allItems.find((it) => it.key === editingKey);
|
|
301
|
+
return (_jsxs(_Fragment, { children: [_jsxs("div", { className: "se-flex se-items-center se-gap-2 se-mb-3 se-text-[13px] se-font-semibold se-text-slate-grey-10", children: [_jsx("span", { children: editItem && _jsx(SectionIcon, { section: editItem.section }) }), _jsx("span", { children: editItem?.summary })] }), renderEditFields(ref.section, ref.index)] }));
|
|
302
|
+
})()) : (_jsxs(_Fragment, { children: [allItems.length === 0 && _jsx(EmptyState, { message: "No overlays configured." }), overlayItems.length > 0 && (_jsxs(_Fragment, { children: [_jsx(GroupHeader, { label: "OVERLAYS", count: overlayItems.length }), overlayItems.map(renderCard)] })), tourItems.length > 0 && (_jsxs(_Fragment, { children: [_jsx(GroupHeader, { label: "TOURS", count: tourItems.length, className: overlayItems.length > 0 ? 'se-mt-4' : '' }), tourItems.map(renderCard)] })), dismissedItems.length > 0 && (_jsx(DismissedSection, { count: dismissedItems.length, children: dismissedItems.map((item) => (_jsxs("div", { className: "se-flex se-items-center se-gap-2 se-py-1.5 se-px-2.5 se-rounded-md se-border se-border-white/[0.03] se-bg-transparent se-mb-0.5 se-cursor-pointer se-text-xs se-text-slate-grey-6 se-opacity-60", children: [_jsx("span", { className: "se-shrink-0 se-flex se-items-center -se-ml-1", children: _jsx(SectionIcon, { section: item.section }) }), _jsx("span", { className: "se-flex-1 se-overflow-hidden se-text-ellipsis se-whitespace-nowrap se-line-through", children: item.summary }), _jsx("button", { type: "button", className: "se-py-0.5 se-px-1.5 se-rounded se-border-none se-bg-transparent se-text-blue-5 se-text-[11px] se-cursor-pointer se-shrink-0 se-leading-none", onClick: (e) => {
|
|
303
|
+
e.stopPropagation();
|
|
304
|
+
handleRestore(item.key);
|
|
305
|
+
}, children: "Restore" })] }, item.key))) }))] })) }), _jsx(EditorFooter, { onSave: () => editor.save(), onPublish: handlePublish })] }));
|
|
306
|
+
}
|
package/dist/runtime.d.ts
CHANGED
|
@@ -44,7 +44,7 @@ export declare const executors: readonly [{
|
|
|
44
44
|
readonly kind: "overlays:modal";
|
|
45
45
|
readonly executor: ActionExecutor<import("./types").ModalAction>;
|
|
46
46
|
}, {
|
|
47
|
-
readonly kind: "
|
|
47
|
+
readonly kind: "overlays:tour";
|
|
48
48
|
readonly executor: ActionExecutor<import("./tour-types").TourAction>;
|
|
49
49
|
}, {
|
|
50
50
|
readonly kind: "overlays:celebrate";
|
|
@@ -74,7 +74,7 @@ export declare const runtime: {
|
|
|
74
74
|
readonly kind: "overlays:modal";
|
|
75
75
|
readonly executor: ActionExecutor<import("./types").ModalAction>;
|
|
76
76
|
}, {
|
|
77
|
-
readonly kind: "
|
|
77
|
+
readonly kind: "overlays:tour";
|
|
78
78
|
readonly executor: ActionExecutor<import("./tour-types").TourAction>;
|
|
79
79
|
}, {
|
|
80
80
|
readonly kind: "overlays:celebrate";
|
package/dist/runtime.js
CHANGED
|
@@ -375,7 +375,7 @@ export const executors = [
|
|
|
375
375
|
{ kind: 'overlays:badge', executor: executeBadge },
|
|
376
376
|
{ kind: 'overlays:tooltip', executor: executeTooltip },
|
|
377
377
|
{ kind: 'overlays:modal', executor: executeModal },
|
|
378
|
-
{ kind: '
|
|
378
|
+
{ kind: 'overlays:tour', executor: executeTour },
|
|
379
379
|
{ kind: 'overlays:celebrate', executor: executeCelebrate },
|
|
380
380
|
];
|
|
381
381
|
/**
|
package/dist/tour-types.d.ts
CHANGED
package/dist/tour-types.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tour-types.d.ts","sourceRoot":"","sources":["../src/tour-types.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAMH,4CAA4C;AAC5C,MAAM,WAAW,QAAQ;IACvB,EAAE,EAAE,MAAM,CAAC;IACX,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAChC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CACnC;AAED,yDAAyD;AACzD,MAAM,WAAW,UAAU;IACzB,IAAI,EAAE,
|
|
1
|
+
{"version":3,"file":"tour-types.d.ts","sourceRoot":"","sources":["../src/tour-types.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAMH,4CAA4C;AAC5C,MAAM,WAAW,QAAQ;IACvB,EAAE,EAAE,MAAM,CAAC;IACX,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAChC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CACnC;AAED,yDAAyD;AACzD,MAAM,WAAW,UAAU;IACzB,IAAI,EAAE,eAAe,CAAC;IACtB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,QAAQ,EAAE,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,OAAO,CAAC;CACrB;AAMD,2EAA2E;AAC3E,MAAM,WAAW,YAAY;IAC3B,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACpC,YAAY,EAAE;QACZ,KAAK,EAAE,MAAM,CAAC;QACd,IAAI,CAAC,EAAE,MAAM,CAAC;KACf,CAAC;CACH"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tailwind-preset.d.ts","sourceRoot":"","sources":["../src/tailwind-preset.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"tailwind-preset.d.ts","sourceRoot":"","sources":["../src/tailwind-preset.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAW1C,eAAO,MAAM,YAAY,EAAE,OAAO,CAAC,MAAM,CAkdxC,CAAC;AAEF,eAAe,YAAY,CAAC"}
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
* ```
|
|
15
15
|
*/
|
|
16
16
|
import { colors } from './tokens/colors';
|
|
17
|
-
import { backdropBlur, focusRings, overlay, shadows } from './tokens/effects';
|
|
17
|
+
import { backdropBlur, elevationShadows, focusRings, glowShadows, overlay, shadows, } from './tokens/effects';
|
|
18
18
|
export const syntroPreset = {
|
|
19
19
|
darkMode: ['class'],
|
|
20
20
|
theme: {
|
|
@@ -390,9 +390,11 @@ export const syntroPreset = {
|
|
|
390
390
|
'3xl': '1.5rem',
|
|
391
391
|
full: '9999px',
|
|
392
392
|
},
|
|
393
|
-
// Box shadow — Figma shadow scale
|
|
393
|
+
// Box shadow — Figma shadow scale + product glows + elevation
|
|
394
394
|
boxShadow: {
|
|
395
395
|
...shadows,
|
|
396
|
+
...glowShadows,
|
|
397
|
+
...elevationShadows,
|
|
396
398
|
DEFAULT: shadows.sm,
|
|
397
399
|
// Focus rings (4px spread + xs shadow)
|
|
398
400
|
'focus-primary': focusRings.primary,
|
|
@@ -316,7 +316,7 @@
|
|
|
316
316
|
/* ========================================================================== */
|
|
317
317
|
--syntro-tag-content: #cbd0d7;
|
|
318
318
|
--syntro-tag-border: #2b333f;
|
|
319
|
-
--syntro-tag-bg: #
|
|
319
|
+
--syntro-tag-bg: #1c222a;
|
|
320
320
|
|
|
321
321
|
/* ========================================================================== */
|
|
322
322
|
/* COMPONENT TOKENS - MENU */
|
|
@@ -310,7 +310,7 @@ export declare const alert: {
|
|
|
310
310
|
export declare const tag: {
|
|
311
311
|
readonly content: "#cbd0d7";
|
|
312
312
|
readonly border: "#2b333f";
|
|
313
|
-
readonly background: "#
|
|
313
|
+
readonly background: "#1c222a";
|
|
314
314
|
};
|
|
315
315
|
export declare const menu: {
|
|
316
316
|
readonly backgroundDefault: "#0e1114";
|
|
@@ -742,7 +742,7 @@ export declare const colors: {
|
|
|
742
742
|
readonly tag: {
|
|
743
743
|
readonly content: "#cbd0d7";
|
|
744
744
|
readonly border: "#2b333f";
|
|
745
|
-
readonly background: "#
|
|
745
|
+
readonly background: "#1c222a";
|
|
746
746
|
};
|
|
747
747
|
readonly menu: {
|
|
748
748
|
readonly backgroundDefault: "#0e1114";
|
|
@@ -349,7 +349,7 @@ export const alert = {
|
|
|
349
349
|
export const tag = {
|
|
350
350
|
content: slateGrey[10],
|
|
351
351
|
border: slateGrey[4],
|
|
352
|
-
background:
|
|
352
|
+
background: slateGrey[3],
|
|
353
353
|
};
|
|
354
354
|
// ============================================================================
|
|
355
355
|
// MENU TOKENS
|