@syntrologie/adapt-content 2.7.0 → 2.8.0-canary.2

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.
@@ -24,7 +24,11 @@ export interface AnchorPickerProps {
24
24
  isActive: boolean;
25
25
  onPick: (picked: PickedElement) => void;
26
26
  onCancel: () => void;
27
+ /** When true, clicks pass through to the page (no preventDefault/stopPropagation).
28
+ * The consumer is responsible for capturing the pick via EventBus or other means.
29
+ * Hover highlighting still works for visual feedback. */
30
+ passthroughClicks?: boolean;
27
31
  excludeSelector?: string;
28
32
  }
29
- export declare function AnchorPicker({ isActive, onPick, onCancel, excludeSelector, }: AnchorPickerProps): import("react").ReactPortal | null;
33
+ export declare function AnchorPicker({ isActive, onPick, onCancel, passthroughClicks, excludeSelector, }: AnchorPickerProps): import("react").ReactPortal | null;
30
34
  //# sourceMappingURL=AnchorPicker.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"AnchorPicker.d.ts","sourceRoot":"","sources":["../../src/components/AnchorPicker.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAWH,MAAM,WAAW,aAAa;IAC5B,OAAO,EAAE,OAAO,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,iBAAiB;IAChC,QAAQ,EAAE,OAAO,CAAC;IAClB,MAAM,EAAE,CAAC,MAAM,EAAE,aAAa,KAAK,IAAI,CAAC;IACxC,QAAQ,EAAE,MAAM,IAAI,CAAC;IACrB,eAAe,CAAC,EAAE,MAAM,CAAC;CAC1B;AAKD,wBAAgB,YAAY,CAAC,EAC3B,QAAQ,EACR,MAAM,EACN,QAAQ,EACR,eAAuJ,GACxJ,EAAE,iBAAiB,sCAgMnB"}
1
+ {"version":3,"file":"AnchorPicker.d.ts","sourceRoot":"","sources":["../../src/components/AnchorPicker.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAWH,MAAM,WAAW,aAAa;IAC5B,OAAO,EAAE,OAAO,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,iBAAiB;IAChC,QAAQ,EAAE,OAAO,CAAC;IAClB,MAAM,EAAE,CAAC,MAAM,EAAE,aAAa,KAAK,IAAI,CAAC;IACxC,QAAQ,EAAE,MAAM,IAAI,CAAC;IACrB;;8DAE0D;IAC1D,iBAAiB,CAAC,EAAE,OAAO,CAAC;IAC5B,eAAe,CAAC,EAAE,MAAM,CAAC;CAC1B;AAKD,wBAAgB,YAAY,CAAC,EAC3B,QAAQ,EACR,MAAM,EACN,QAAQ,EACR,iBAAyB,EACzB,eAAuJ,GACxJ,EAAE,iBAAiB,sCAwMnB"}
@@ -21,7 +21,7 @@ import { createPortal } from 'react-dom';
21
21
  import { generateSelector, getElementDescription, validateSelector, } from '../utils/selectorGenerator';
22
22
  const HIGHLIGHT_COLOR = '#3b82f6';
23
23
  const HIGHLIGHT_BG = 'rgba(59, 130, 246, 0.1)';
24
- export function AnchorPicker({ isActive, onPick, onCancel, excludeSelector = '[data-syntro-editor-panel], [data-shadow-canvas-id], .syntro-tooltip, .syntro-modal, .syntro-highlight, [data-syntro-anchor-picker]', }) {
24
+ export function AnchorPicker({ isActive, onPick, onCancel, passthroughClicks = false, excludeSelector = '[data-syntro-editor-panel], [data-shadow-canvas-id], .syntro-tooltip, .syntro-modal, .syntro-highlight, [data-syntro-anchor-picker]', }) {
25
25
  const [hoveredElement, setHoveredElement] = useState(null);
26
26
  const [hoveredSelector, setHoveredSelector] = useState('');
27
27
  const overlayRef = useRef(null);
@@ -54,6 +54,8 @@ export function AnchorPicker({ isActive, onPick, onCancel, excludeSelector = '[d
54
54
  setHoveredSelector(selector);
55
55
  }, [excludeSelector]);
56
56
  const handleClick = useCallback((e) => {
57
+ if (passthroughClicks)
58
+ return; // Let click propagate to PostHog
57
59
  e.preventDefault();
58
60
  e.stopPropagation();
59
61
  if (hoveredElement && hoveredSelector) {
@@ -73,7 +75,7 @@ export function AnchorPicker({ isActive, onPick, onCancel, excludeSelector = '[d
73
75
  });
74
76
  }
75
77
  }
76
- }, [hoveredElement, hoveredSelector, onPick]);
78
+ }, [passthroughClicks, hoveredElement, hoveredSelector, onPick]);
77
79
  const handleKeyDown = useCallback((e) => {
78
80
  if (e.key === 'Escape') {
79
81
  e.preventDefault();
@@ -84,14 +86,19 @@ export function AnchorPicker({ isActive, onPick, onCancel, excludeSelector = '[d
84
86
  if (!isActive)
85
87
  return;
86
88
  document.addEventListener('mousemove', handleMouseMove, true);
87
- document.addEventListener('click', handleClick, true);
89
+ // In passthrough mode, don't register click handler — let clicks reach PostHog
90
+ if (!passthroughClicks) {
91
+ document.addEventListener('click', handleClick, true);
92
+ }
88
93
  document.addEventListener('keydown', handleKeyDown, true);
89
94
  return () => {
90
95
  document.removeEventListener('mousemove', handleMouseMove, true);
91
- document.removeEventListener('click', handleClick, true);
96
+ if (!passthroughClicks) {
97
+ document.removeEventListener('click', handleClick, true);
98
+ }
92
99
  document.removeEventListener('keydown', handleKeyDown, true);
93
100
  };
94
- }, [isActive, handleMouseMove, handleClick, handleKeyDown]);
101
+ }, [isActive, passthroughClicks, handleMouseMove, handleClick, handleKeyDown]);
95
102
  if (!isActive)
96
103
  return null;
97
104
  const rect = hoveredElement?.getBoundingClientRect();
@@ -100,6 +107,7 @@ export function AnchorPicker({ isActive, onPick, onCancel, excludeSelector = '[d
100
107
  inset: 0,
101
108
  cursor: 'crosshair',
102
109
  zIndex: 2147483644,
110
+ pointerEvents: passthroughClicks ? 'none' : undefined,
103
111
  }, children: [_jsx("div", { style: {
104
112
  position: 'absolute',
105
113
  inset: 0,
@@ -28,6 +28,7 @@ export { formatConditionLabel } from './formatConditionLabel';
28
28
  export { useElementRect } from './hooks/useElementRect';
29
29
  export type { TriggerWhenItem } from './hooks/useTriggerWhenStatus';
30
30
  export { useTriggerWhenStatus } from './hooks/useTriggerWhenStatus';
31
+ export { type ActionStep, buildActionStepFromElement, findRecommendedIndex, type PostHogElement, } from './utils/elementChainRecommender';
31
32
  export type { SelectorOptions } from './utils/selectorGenerator';
32
33
  export { generateSelector, getElementDescription, validateSelector, } from './utils/selectorGenerator';
33
34
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,EAAE,EAAE,MAAM,MAAM,CAAC;AAC1B,YAAY,EAAE,iBAAiB,EAAE,aAAa,EAAE,MAAM,2BAA2B,CAAC;AAClF,OAAO,EAAE,YAAY,EAAE,MAAM,2BAA2B,CAAC;AACzD,OAAO,EAAE,iBAAiB,EAAE,MAAM,gCAAgC,CAAC;AACnE,YAAY,EAAE,eAAe,EAAE,iBAAiB,EAAE,MAAM,kCAAkC,CAAC;AAC3F,OAAO,EAAE,mBAAmB,EAAE,MAAM,kCAAkC,CAAC;AACvE,OAAO,EAAE,cAAc,EAAE,MAAM,6BAA6B,CAAC;AAC7D,OAAO,EAAE,gBAAgB,EAAE,MAAM,+BAA+B,CAAC;AACjE,OAAO,EAAE,cAAc,EAAE,MAAM,6BAA6B,CAAC;AAC7D,OAAO,EAAE,UAAU,EAAE,MAAM,yBAAyB,CAAC;AACrD,OAAO,EAAE,UAAU,EAAE,MAAM,yBAAyB,CAAC;AACrD,OAAO,EAAE,YAAY,EAAE,MAAM,2BAA2B,CAAC;AACzD,OAAO,EAAE,YAAY,EAAE,MAAM,2BAA2B,CAAC;AACzD,OAAO,EAAE,WAAW,EAAE,MAAM,0BAA0B,CAAC;AACvD,OAAO,EAAE,YAAY,EAAE,MAAM,2BAA2B,CAAC;AACzD,YAAY,EAAE,qBAAqB,EAAE,MAAM,+BAA+B,CAAC;AAC3E,OAAO,EAAE,gBAAgB,EAAE,MAAM,+BAA+B,CAAC;AACjE,OAAO,EAAE,YAAY,EAAE,MAAM,2BAA2B,CAAC;AACzD,OAAO,EAAE,cAAc,EAAE,MAAM,6BAA6B,CAAC;AAC7D,YAAY,EAAE,qBAAqB,EAAE,MAAM,+BAA+B,CAAC;AAC3E,OAAO,EAAE,gBAAgB,EAAE,MAAM,+BAA+B,CAAC;AACjE,OAAO,EAAE,UAAU,EAAE,MAAM,yBAAyB,CAAC;AACrD,OAAO,EAAE,WAAW,EAAE,MAAM,0BAA0B,CAAC;AACvD,YAAY,EAAE,mBAAmB,EAAE,MAAM,6BAA6B,CAAC;AACvE,OAAO,EAAE,cAAc,EAAE,MAAM,6BAA6B,CAAC;AAC7D,YAAY,EAAE,iBAAiB,EAAE,kBAAkB,EAAE,MAAM,wBAAwB,CAAC;AACpF,OAAO,EAAE,oBAAoB,EAAE,MAAM,wBAAwB,CAAC;AAC9D,OAAO,EAAE,cAAc,EAAE,MAAM,wBAAwB,CAAC;AACxD,YAAY,EAAE,eAAe,EAAE,MAAM,8BAA8B,CAAC;AACpE,OAAO,EAAE,oBAAoB,EAAE,MAAM,8BAA8B,CAAC;AACpE,YAAY,EAAE,eAAe,EAAE,MAAM,2BAA2B,CAAC;AACjE,OAAO,EACL,gBAAgB,EAChB,qBAAqB,EACrB,gBAAgB,GACjB,MAAM,2BAA2B,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,EAAE,EAAE,MAAM,MAAM,CAAC;AAC1B,YAAY,EAAE,iBAAiB,EAAE,aAAa,EAAE,MAAM,2BAA2B,CAAC;AAClF,OAAO,EAAE,YAAY,EAAE,MAAM,2BAA2B,CAAC;AACzD,OAAO,EAAE,iBAAiB,EAAE,MAAM,gCAAgC,CAAC;AACnE,YAAY,EAAE,eAAe,EAAE,iBAAiB,EAAE,MAAM,kCAAkC,CAAC;AAC3F,OAAO,EAAE,mBAAmB,EAAE,MAAM,kCAAkC,CAAC;AACvE,OAAO,EAAE,cAAc,EAAE,MAAM,6BAA6B,CAAC;AAC7D,OAAO,EAAE,gBAAgB,EAAE,MAAM,+BAA+B,CAAC;AACjE,OAAO,EAAE,cAAc,EAAE,MAAM,6BAA6B,CAAC;AAC7D,OAAO,EAAE,UAAU,EAAE,MAAM,yBAAyB,CAAC;AACrD,OAAO,EAAE,UAAU,EAAE,MAAM,yBAAyB,CAAC;AACrD,OAAO,EAAE,YAAY,EAAE,MAAM,2BAA2B,CAAC;AACzD,OAAO,EAAE,YAAY,EAAE,MAAM,2BAA2B,CAAC;AACzD,OAAO,EAAE,WAAW,EAAE,MAAM,0BAA0B,CAAC;AACvD,OAAO,EAAE,YAAY,EAAE,MAAM,2BAA2B,CAAC;AACzD,YAAY,EAAE,qBAAqB,EAAE,MAAM,+BAA+B,CAAC;AAC3E,OAAO,EAAE,gBAAgB,EAAE,MAAM,+BAA+B,CAAC;AACjE,OAAO,EAAE,YAAY,EAAE,MAAM,2BAA2B,CAAC;AACzD,OAAO,EAAE,cAAc,EAAE,MAAM,6BAA6B,CAAC;AAC7D,YAAY,EAAE,qBAAqB,EAAE,MAAM,+BAA+B,CAAC;AAC3E,OAAO,EAAE,gBAAgB,EAAE,MAAM,+BAA+B,CAAC;AACjE,OAAO,EAAE,UAAU,EAAE,MAAM,yBAAyB,CAAC;AACrD,OAAO,EAAE,WAAW,EAAE,MAAM,0BAA0B,CAAC;AACvD,YAAY,EAAE,mBAAmB,EAAE,MAAM,6BAA6B,CAAC;AACvE,OAAO,EAAE,cAAc,EAAE,MAAM,6BAA6B,CAAC;AAC7D,YAAY,EAAE,iBAAiB,EAAE,kBAAkB,EAAE,MAAM,wBAAwB,CAAC;AACpF,OAAO,EAAE,oBAAoB,EAAE,MAAM,wBAAwB,CAAC;AAC9D,OAAO,EAAE,cAAc,EAAE,MAAM,wBAAwB,CAAC;AACxD,YAAY,EAAE,eAAe,EAAE,MAAM,8BAA8B,CAAC;AACpE,OAAO,EAAE,oBAAoB,EAAE,MAAM,8BAA8B,CAAC;AACpE,OAAO,EACL,KAAK,UAAU,EACf,0BAA0B,EAC1B,oBAAoB,EACpB,KAAK,cAAc,GACpB,MAAM,iCAAiC,CAAC;AACzC,YAAY,EAAE,eAAe,EAAE,MAAM,2BAA2B,CAAC;AACjE,OAAO,EACL,gBAAgB,EAChB,qBAAqB,EACrB,gBAAgB,GACjB,MAAM,2BAA2B,CAAC"}
@@ -21,4 +21,5 @@ export { TriggerJourney } from './components/TriggerJourney';
21
21
  export { formatConditionLabel } from './formatConditionLabel';
22
22
  export { useElementRect } from './hooks/useElementRect';
23
23
  export { useTriggerWhenStatus } from './hooks/useTriggerWhenStatus';
24
+ export { buildActionStepFromElement, findRecommendedIndex, } from './utils/elementChainRecommender';
24
25
  export { generateSelector, getElementDescription, validateSelector, } from './utils/selectorGenerator';
@@ -0,0 +1,37 @@
1
+ /**
2
+ * Element Chain Recommender
3
+ *
4
+ * Given PostHog's $elements array, recommends which element the user
5
+ * most likely intended to track for a KPI, and builds a PostHog action
6
+ * step from that element.
7
+ *
8
+ * The interactive_tags and interactive_roles are loaded from the shared
9
+ * field mapping fixture so the recommendation heuristic stays in sync
10
+ * with the warehouse and runtime predicate matching.
11
+ */
12
+ export interface PostHogElement {
13
+ tag_name: string;
14
+ $el_text?: string;
15
+ [key: string]: unknown;
16
+ }
17
+ export interface ActionStep {
18
+ event: string;
19
+ tag_name?: string;
20
+ text?: string;
21
+ url?: string;
22
+ }
23
+ /**
24
+ * Find the index of the recommended element in the $elements chain.
25
+ * Returns the first interactive element, or 0 if none found.
26
+ */
27
+ export declare function findRecommendedIndex(elements: PostHogElement[]): number;
28
+ export interface BuildActionStepOptions {
29
+ /** If true, infer $pageview for <a> elements with internal href */
30
+ inferPageview?: boolean;
31
+ }
32
+ /**
33
+ * Build a PostHog action step from a selected element in the chain.
34
+ * Does NOT include `selector` — it's not supported in Athena.
35
+ */
36
+ export declare function buildActionStepFromElement(element: PostHogElement, pageUrl: string, options?: BuildActionStepOptions): ActionStep;
37
+ //# sourceMappingURL=elementChainRecommender.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"elementChainRecommender.d.ts","sourceRoot":"","sources":["../../src/utils/elementChainRecommender.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAMH,MAAM,WAAW,cAAc;IAC7B,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACxB;AAED,MAAM,WAAW,UAAU;IACzB,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,GAAG,CAAC,EAAE,MAAM,CAAC;CACd;AAKD;;;GAGG;AACH,wBAAgB,oBAAoB,CAAC,QAAQ,EAAE,cAAc,EAAE,GAAG,MAAM,CAYvE;AAED,MAAM,WAAW,sBAAsB;IACrC,mEAAmE;IACnE,aAAa,CAAC,EAAE,OAAO,CAAC;CACzB;AAED;;;GAGG;AACH,wBAAgB,0BAA0B,CACxC,OAAO,EAAE,cAAc,EACvB,OAAO,EAAE,MAAM,EACf,OAAO,GAAE,sBAA2B,GACnC,UAAU,CAmBZ"}
@@ -0,0 +1,52 @@
1
+ /**
2
+ * Element Chain Recommender
3
+ *
4
+ * Given PostHog's $elements array, recommends which element the user
5
+ * most likely intended to track for a KPI, and builds a PostHog action
6
+ * step from that element.
7
+ *
8
+ * The interactive_tags and interactive_roles are loaded from the shared
9
+ * field mapping fixture so the recommendation heuristic stays in sync
10
+ * with the warehouse and runtime predicate matching.
11
+ */
12
+ // Loaded from the shared fixture at build time.
13
+ // The fixture is the single source of truth — do NOT hardcode these lists.
14
+ import fieldMapping from '../../../../tests/fixtures/action-step-field-mapping.json';
15
+ const INTERACTIVE_TAGS = new Set(fieldMapping.interactive_tags);
16
+ const INTERACTIVE_ROLES = new Set(fieldMapping.interactive_roles);
17
+ /**
18
+ * Find the index of the recommended element in the $elements chain.
19
+ * Returns the first interactive element, or 0 if none found.
20
+ */
21
+ export function findRecommendedIndex(elements) {
22
+ if (elements.length === 0)
23
+ return 0;
24
+ for (let i = 0; i < elements.length; i++) {
25
+ const el = elements[i];
26
+ if (INTERACTIVE_TAGS.has(el.tag_name))
27
+ return i;
28
+ const role = (el.attr__role ?? el.role);
29
+ if (role && INTERACTIVE_ROLES.has(role))
30
+ return i;
31
+ }
32
+ return 0;
33
+ }
34
+ /**
35
+ * Build a PostHog action step from a selected element in the chain.
36
+ * Does NOT include `selector` — it's not supported in Athena.
37
+ */
38
+ export function buildActionStepFromElement(element, pageUrl, options = {}) {
39
+ const { inferPageview = false } = options;
40
+ const href = element.attr__href;
41
+ const isInternalLink = element.tag_name === 'a' && href && !href.startsWith('http') && !href.startsWith('//');
42
+ const step = {
43
+ event: inferPageview && isInternalLink ? '$pageview' : '$autocapture',
44
+ tag_name: element.tag_name,
45
+ url: pageUrl,
46
+ };
47
+ const text = (element.$el_text || '').trim();
48
+ if (text) {
49
+ step.text = text.slice(0, 100); // Truncate long text
50
+ }
51
+ return step;
52
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@syntrologie/adapt-content",
3
- "version": "2.7.0",
3
+ "version": "2.8.0-canary.2",
4
4
  "description": "Adaptive Content app - DOM manipulation actions for text, attributes, and styles",
5
5
  "license": "Proprietary",
6
6
  "private": false,