@guided-tour-s4marth/core 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 +21 -0
- package/dist/conditions.d.ts +12 -0
- package/dist/conditions.d.ts.map +1 -0
- package/dist/conditions.js +34 -0
- package/dist/conditions.js.map +1 -0
- package/dist/index.d.ts +15 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +8 -0
- package/dist/index.js.map +1 -0
- package/dist/locator.d.ts +37 -0
- package/dist/locator.d.ts.map +1 -0
- package/dist/locator.js +188 -0
- package/dist/locator.js.map +1 -0
- package/dist/persistence.d.ts +44 -0
- package/dist/persistence.d.ts.map +1 -0
- package/dist/persistence.js +57 -0
- package/dist/persistence.js.map +1 -0
- package/dist/player.d.ts +18 -0
- package/dist/player.d.ts.map +1 -0
- package/dist/player.js +270 -0
- package/dist/player.js.map +1 -0
- package/dist/resolver.d.ts +20 -0
- package/dist/resolver.d.ts.map +1 -0
- package/dist/resolver.js +81 -0
- package/dist/resolver.js.map +1 -0
- package/dist/schema.d.ts +525 -0
- package/dist/schema.d.ts.map +1 -0
- package/dist/schema.js +78 -0
- package/dist/schema.js.map +1 -0
- package/dist/telemetry.d.ts +46 -0
- package/dist/telemetry.d.ts.map +1 -0
- package/dist/telemetry.js +16 -0
- package/dist/telemetry.js.map +1 -0
- package/package.json +51 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 DataGenie
|
|
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.
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { Condition, Tour } from './schema.js';
|
|
2
|
+
export interface RuntimeContext {
|
|
3
|
+
route: string;
|
|
4
|
+
role?: string;
|
|
5
|
+
appVersion: string;
|
|
6
|
+
flags: Record<string, boolean>;
|
|
7
|
+
seenTours: Set<string>;
|
|
8
|
+
customPredicates?: Record<string, () => boolean>;
|
|
9
|
+
}
|
|
10
|
+
export declare function evaluateCondition(condition: Condition, ctx: RuntimeContext): boolean;
|
|
11
|
+
export declare function isTourEligible(tour: Tour, ctx: RuntimeContext): boolean;
|
|
12
|
+
//# sourceMappingURL=conditions.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"conditions.d.ts","sourceRoot":"","sources":["../src/conditions.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,IAAI,EAAE,MAAM,aAAa,CAAC;AAEnD,MAAM,WAAW,cAAc;IAC7B,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,UAAU,EAAE,MAAM,CAAC;IACnB,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC/B,SAAS,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;IACvB,gBAAgB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,OAAO,CAAC,CAAC;CAClD;AAED,wBAAgB,iBAAiB,CAAC,SAAS,EAAE,SAAS,EAAE,GAAG,EAAE,cAAc,GAAG,OAAO,CAyBpF;AAED,wBAAgB,cAAc,CAAC,IAAI,EAAE,IAAI,EAAE,GAAG,EAAE,cAAc,GAAG,OAAO,CAGvE"}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { satisfies as semverSatisfies, valid as semverValid } from 'semver';
|
|
2
|
+
export function evaluateCondition(condition, ctx) {
|
|
3
|
+
switch (condition.kind) {
|
|
4
|
+
case 'route': {
|
|
5
|
+
try {
|
|
6
|
+
return new RegExp(condition.match).test(ctx.route);
|
|
7
|
+
}
|
|
8
|
+
catch {
|
|
9
|
+
return ctx.route.includes(condition.match);
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
case 'role':
|
|
13
|
+
return ctx.role != null && condition.in.includes(ctx.role);
|
|
14
|
+
case 'version': {
|
|
15
|
+
if (!semverValid(ctx.appVersion)) {
|
|
16
|
+
console.warn(`[tour] invalid semver for appVersion: "${ctx.appVersion}"`);
|
|
17
|
+
return false;
|
|
18
|
+
}
|
|
19
|
+
return semverSatisfies(ctx.appVersion, condition.range);
|
|
20
|
+
}
|
|
21
|
+
case 'flag':
|
|
22
|
+
return (ctx.flags[condition.key] ?? false) === condition.on;
|
|
23
|
+
case 'seen':
|
|
24
|
+
return ctx.seenTours.has(condition.tourId) === condition.value;
|
|
25
|
+
case 'custom':
|
|
26
|
+
return ctx.customPredicates?.[condition.predicateId]?.() ?? false;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
export function isTourEligible(tour, ctx) {
|
|
30
|
+
if (tour.status !== 'active')
|
|
31
|
+
return false;
|
|
32
|
+
return tour.conditions.every(c => evaluateCondition(c, ctx));
|
|
33
|
+
}
|
|
34
|
+
//# sourceMappingURL=conditions.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"conditions.js","sourceRoot":"","sources":["../src/conditions.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,IAAI,eAAe,EAAE,KAAK,IAAI,WAAW,EAAE,MAAM,QAAQ,CAAC;AAY5E,MAAM,UAAU,iBAAiB,CAAC,SAAoB,EAAE,GAAmB;IACzE,QAAQ,SAAS,CAAC,IAAI,EAAE,CAAC;QACvB,KAAK,OAAO,CAAC,CAAC,CAAC;YACb,IAAI,CAAC;gBACH,OAAO,IAAI,MAAM,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;YACrD,CAAC;YAAC,MAAM,CAAC;gBACP,OAAO,GAAG,CAAC,KAAK,CAAC,QAAQ,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC;YAC7C,CAAC;QACH,CAAC;QACD,KAAK,MAAM;YACT,OAAO,GAAG,CAAC,IAAI,IAAI,IAAI,IAAI,SAAS,CAAC,EAAE,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;QAC7D,KAAK,SAAS,CAAC,CAAC,CAAC;YACf,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,UAAU,CAAC,EAAE,CAAC;gBACjC,OAAO,CAAC,IAAI,CAAC,0CAA0C,GAAG,CAAC,UAAU,GAAG,CAAC,CAAC;gBAC1E,OAAO,KAAK,CAAC;YACf,CAAC;YACD,OAAO,eAAe,CAAC,GAAG,CAAC,UAAU,EAAE,SAAS,CAAC,KAAK,CAAC,CAAC;QAC1D,CAAC;QACD,KAAK,MAAM;YACT,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,SAAS,CAAC,GAAG,CAAC,IAAI,KAAK,CAAC,KAAK,SAAS,CAAC,EAAE,CAAC;QAC9D,KAAK,MAAM;YACT,OAAO,GAAG,CAAC,SAAS,CAAC,GAAG,CAAC,SAAS,CAAC,MAAM,CAAC,KAAK,SAAS,CAAC,KAAK,CAAC;QACjE,KAAK,QAAQ;YACX,OAAO,GAAG,CAAC,gBAAgB,EAAE,CAAC,SAAS,CAAC,WAAW,CAAC,EAAE,EAAE,IAAI,KAAK,CAAC;IACtE,CAAC;AACH,CAAC;AAED,MAAM,UAAU,cAAc,CAAC,IAAU,EAAE,GAAmB;IAC5D,IAAI,IAAI,CAAC,MAAM,KAAK,QAAQ;QAAE,OAAO,KAAK,CAAC;IAC3C,OAAO,IAAI,CAAC,UAAU,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,CAAC,iBAAiB,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC,CAAC;AAC/D,CAAC"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export { parseTour, parseTours, Tour, Step, Condition, ThemeOverrides, InteractionAction } from './schema.js';
|
|
2
|
+
export type { Tour as TourType, Step as StepType, Condition as ConditionType, ThemeOverrides as ThemeOverridesType, InteractionAction as InteractionActionType } from './schema.js';
|
|
3
|
+
export { evaluateCondition, isTourEligible } from './conditions.js';
|
|
4
|
+
export type { RuntimeContext } from './conditions.js';
|
|
5
|
+
export { resolveAnchor, waitForAnchor } from './resolver.js';
|
|
6
|
+
export type { AnchorMeta, AnchorMetaMap } from './resolver.js';
|
|
7
|
+
export { encodeLocator, decodeLocator, resolveLocator, resolveXPath, waitForLocator, signatureMatches, buildLocator, getXPath, } from './locator.js';
|
|
8
|
+
export type { TourLocator, TourSignature, LocatorStatus } from './locator.js';
|
|
9
|
+
export { playTour } from './player.js';
|
|
10
|
+
export type { PlayerOptions } from './player.js';
|
|
11
|
+
export { localSeenStore, createBackendSeenStore } from './persistence.js';
|
|
12
|
+
export type { SeenStore } from './persistence.js';
|
|
13
|
+
export { setTelemetryHandler, emit, PREVIEW_TOUR_ID } from './telemetry.js';
|
|
14
|
+
export type { TourEvent, TelemetryHandler } from './telemetry.js';
|
|
15
|
+
//# 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,SAAS,EAAE,UAAU,EAAE,IAAI,EAAE,IAAI,EAAE,SAAS,EAAE,cAAc,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAC9G,YAAY,EAAE,IAAI,IAAI,QAAQ,EAAE,IAAI,IAAI,QAAQ,EAAE,SAAS,IAAI,aAAa,EAAE,cAAc,IAAI,kBAAkB,EAAE,iBAAiB,IAAI,qBAAqB,EAAE,MAAM,aAAa,CAAC;AACpL,OAAO,EAAE,iBAAiB,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AACpE,YAAY,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AACtD,OAAO,EAAE,aAAa,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AAC7D,YAAY,EAAE,UAAU,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AAC/D,OAAO,EACL,aAAa,EACb,aAAa,EACb,cAAc,EACd,YAAY,EACZ,cAAc,EACd,gBAAgB,EAChB,YAAY,EACZ,QAAQ,GACT,MAAM,cAAc,CAAC;AACtB,YAAY,EAAE,WAAW,EAAE,aAAa,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAC9E,OAAO,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AACvC,YAAY,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AACjD,OAAO,EAAE,cAAc,EAAE,sBAAsB,EAAE,MAAM,kBAAkB,CAAC;AAC1E,YAAY,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAClD,OAAO,EAAE,mBAAmB,EAAE,IAAI,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAC;AAC5E,YAAY,EAAE,SAAS,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export { parseTour, parseTours, Tour, Step, Condition, ThemeOverrides, InteractionAction } from './schema.js';
|
|
2
|
+
export { evaluateCondition, isTourEligible } from './conditions.js';
|
|
3
|
+
export { resolveAnchor, waitForAnchor } from './resolver.js';
|
|
4
|
+
export { encodeLocator, decodeLocator, resolveLocator, resolveXPath, waitForLocator, signatureMatches, buildLocator, getXPath, } from './locator.js';
|
|
5
|
+
export { playTour } from './player.js';
|
|
6
|
+
export { localSeenStore, createBackendSeenStore } from './persistence.js';
|
|
7
|
+
export { setTelemetryHandler, emit, PREVIEW_TOUR_ID } from './telemetry.js';
|
|
8
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,UAAU,EAAE,IAAI,EAAE,IAAI,EAAE,SAAS,EAAE,cAAc,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAE9G,OAAO,EAAE,iBAAiB,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAEpE,OAAO,EAAE,aAAa,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AAE7D,OAAO,EACL,aAAa,EACb,aAAa,EACb,cAAc,EACd,YAAY,EACZ,cAAc,EACd,gBAAgB,EAChB,YAAY,EACZ,QAAQ,GACT,MAAM,cAAc,CAAC;AAEtB,OAAO,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AAEvC,OAAO,EAAE,cAAc,EAAE,sBAAsB,EAAE,MAAM,kBAAkB,CAAC;AAE1E,OAAO,EAAE,mBAAmB,EAAE,IAAI,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAC"}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
export interface TourSignature {
|
|
2
|
+
tag: string;
|
|
3
|
+
role?: string;
|
|
4
|
+
name?: string;
|
|
5
|
+
text?: string;
|
|
6
|
+
}
|
|
7
|
+
export interface TourLocator {
|
|
8
|
+
testid?: string;
|
|
9
|
+
domId?: string;
|
|
10
|
+
role?: string;
|
|
11
|
+
name?: string;
|
|
12
|
+
text?: string;
|
|
13
|
+
xpath?: string;
|
|
14
|
+
signature: TourSignature;
|
|
15
|
+
route?: string;
|
|
16
|
+
}
|
|
17
|
+
export declare function encodeLocator(loc: TourLocator): string;
|
|
18
|
+
export declare function decodeLocator(anchorId: string | undefined | null): TourLocator | null;
|
|
19
|
+
export declare function resolveXPath(xpath: string): Element | null;
|
|
20
|
+
export declare function getXPath(el: Element): string;
|
|
21
|
+
export declare function buildLocator(el: Element): TourLocator;
|
|
22
|
+
/** True if the element plausibly *is* the signature's target. Tag must match;
|
|
23
|
+
* any provided name/text must be contained. Lenient on role (often implicit). */
|
|
24
|
+
export declare function signatureMatches(el: Element, sig: TourSignature): boolean;
|
|
25
|
+
export type LocatorStatus = 'ok' | 'healed' | 'mismatch' | 'broken';
|
|
26
|
+
/** Resolve a locator against the live DOM.
|
|
27
|
+
* - ok: a signal resolved to a unique element matching the signature
|
|
28
|
+
* - healed: signals failed, but a unique element matches the signature
|
|
29
|
+
* - mismatch: a signal resolved a unique element, but its signature differs (rot)
|
|
30
|
+
* - broken: nothing usable found */
|
|
31
|
+
export declare function resolveLocator(loc: TourLocator): {
|
|
32
|
+
el: Element | null;
|
|
33
|
+
status: LocatorStatus;
|
|
34
|
+
};
|
|
35
|
+
/** Wait for a locator to resolve (post-navigation / async render). */
|
|
36
|
+
export declare function waitForLocator(loc: TourLocator, timeoutMs?: number): Promise<Element | null>;
|
|
37
|
+
//# sourceMappingURL=locator.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"locator.d.ts","sourceRoot":"","sources":["../src/locator.ts"],"names":[],"mappings":"AAaA,MAAM,WAAW,aAAa;IAC5B,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,WAAW;IAC1B,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,aAAa,CAAC;IAIzB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAID,wBAAgB,aAAa,CAAC,GAAG,EAAE,WAAW,GAAG,MAAM,CAEtD;AAED,wBAAgB,aAAa,CAAC,QAAQ,EAAE,MAAM,GAAG,SAAS,GAAG,IAAI,GAAG,WAAW,GAAG,IAAI,CAQrF;AAED,wBAAgB,YAAY,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,GAAG,IAAI,CAO1D;AAWD,wBAAgB,QAAQ,CAAC,EAAE,EAAE,OAAO,GAAG,MAAM,CAiB5C;AAQD,wBAAgB,YAAY,CAAC,EAAE,EAAE,OAAO,GAAG,WAAW,CAiCrD;AAMD;kFACkF;AAClF,wBAAgB,gBAAgB,CAAC,EAAE,EAAE,OAAO,EAAE,GAAG,EAAE,aAAa,GAAG,OAAO,CASzE;AAMD,MAAM,MAAM,aAAa,GAAG,IAAI,GAAG,QAAQ,GAAG,UAAU,GAAG,QAAQ,CAAC;AAEpE;;;;uCAIuC;AACvC,wBAAgB,cAAc,CAAC,GAAG,EAAE,WAAW,GAAG;IAAE,EAAE,EAAE,OAAO,GAAG,IAAI,CAAC;IAAC,MAAM,EAAE,aAAa,CAAA;CAAE,CAiC9F;AAED,sEAAsE;AACtE,wBAAgB,cAAc,CAAC,GAAG,EAAE,WAAW,EAAE,SAAS,SAAO,GAAG,OAAO,CAAC,OAAO,GAAG,IAAI,CAAC,CAqB1F"}
|
package/dist/locator.js
ADDED
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
// ─── Locator + signature ────────────────────────────────────────────────────
|
|
2
|
+
// A tour step targets an element by a multi-signal locator (no build-time
|
|
3
|
+
// data-tour anchor needed). The locator is encoded into the Step.anchorId string
|
|
4
|
+
// (reusing that field — no schema change): `loc:<json>`. A plain string anchorId
|
|
5
|
+
// is treated as a legacy data-tour / CSS selector.
|
|
6
|
+
//
|
|
7
|
+
// The `signature` captures *what* the element is (tag, role, accessible name,
|
|
8
|
+
// text). It serves two jobs:
|
|
9
|
+
// 1. detect rot — if a signal resolves to an element whose signature no longer
|
|
10
|
+
// matches, that's "wrong element" rot (not silently highlighted).
|
|
11
|
+
// 2. self-heal — if the signals miss, find a unique element matching the
|
|
12
|
+
// signature and re-bind to it (recovers from many UI refactors, no deploy).
|
|
13
|
+
const PREFIX = 'loc:';
|
|
14
|
+
export function encodeLocator(loc) {
|
|
15
|
+
return PREFIX + JSON.stringify(loc);
|
|
16
|
+
}
|
|
17
|
+
export function decodeLocator(anchorId) {
|
|
18
|
+
if (!anchorId || !anchorId.startsWith(PREFIX))
|
|
19
|
+
return null;
|
|
20
|
+
try {
|
|
21
|
+
const parsed = JSON.parse(anchorId.slice(PREFIX.length));
|
|
22
|
+
return parsed && parsed.signature ? parsed : null;
|
|
23
|
+
}
|
|
24
|
+
catch {
|
|
25
|
+
return null;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
export function resolveXPath(xpath) {
|
|
29
|
+
try {
|
|
30
|
+
const r = document.evaluate(xpath, document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null);
|
|
31
|
+
return r.singleNodeValue ?? null;
|
|
32
|
+
}
|
|
33
|
+
catch {
|
|
34
|
+
return null;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
// ── Pseudo-XPath construction ─────────────────────────────────────────────────
|
|
38
|
+
// Anchors on the nearest ancestor `id` for stability across re-renders;
|
|
39
|
+
// otherwise uses tag:nth-of-type segments.
|
|
40
|
+
function xpathLiteral(s) {
|
|
41
|
+
if (!s.includes('"'))
|
|
42
|
+
return `"${s}"`;
|
|
43
|
+
if (!s.includes("'"))
|
|
44
|
+
return `'${s}'`;
|
|
45
|
+
return 'concat("' + s.split('"').join('",\'"\',"') + '")';
|
|
46
|
+
}
|
|
47
|
+
export function getXPath(el) {
|
|
48
|
+
const segs = [];
|
|
49
|
+
let node = el;
|
|
50
|
+
while (node && node.nodeType === 1) {
|
|
51
|
+
if (node.id) {
|
|
52
|
+
segs.unshift(`*[@id=${xpathLiteral(node.id)}]`);
|
|
53
|
+
return '//' + segs.join('/');
|
|
54
|
+
}
|
|
55
|
+
const tag = node.tagName.toLowerCase();
|
|
56
|
+
let i = 1;
|
|
57
|
+
for (let sib = node.previousElementSibling; sib; sib = sib.previousElementSibling) {
|
|
58
|
+
if (sib.tagName === node.tagName)
|
|
59
|
+
i++;
|
|
60
|
+
}
|
|
61
|
+
segs.unshift(`${tag}[${i}]`);
|
|
62
|
+
node = node.parentElement;
|
|
63
|
+
}
|
|
64
|
+
return '/' + segs.join('/');
|
|
65
|
+
}
|
|
66
|
+
// ── Build a multi-signal locator + signature for an element ────────────────────
|
|
67
|
+
// Signals (testid → id → xpath) are each verified UNIQUE before being recorded,
|
|
68
|
+
// so resolveLocator can trust them. The signature (tag/role/name/text) captures
|
|
69
|
+
// what the element *is* — used for rot detection and self-heal. Single source of
|
|
70
|
+
// locator construction, shared by the recorder (capture) and the health auditor
|
|
71
|
+
// (re-point suggestion).
|
|
72
|
+
export function buildLocator(el) {
|
|
73
|
+
const tag = el.tagName.toLowerCase();
|
|
74
|
+
const role = el.getAttribute('role') ?? undefined;
|
|
75
|
+
const ariaLabel = el.getAttribute('aria-label')?.trim() || undefined;
|
|
76
|
+
const text = (el.textContent ?? '').trim().replace(/\s+/g, ' ').slice(0, 80) || undefined;
|
|
77
|
+
const signature = { tag };
|
|
78
|
+
if (role)
|
|
79
|
+
signature.role = role;
|
|
80
|
+
if (ariaLabel)
|
|
81
|
+
signature.name = ariaLabel;
|
|
82
|
+
if (text)
|
|
83
|
+
signature.text = text;
|
|
84
|
+
const loc = { signature };
|
|
85
|
+
// testid — only if it's on the element itself AND unique in the document.
|
|
86
|
+
const testid = el.getAttribute('data-testid') ?? undefined;
|
|
87
|
+
if (testid && document.querySelectorAll(`[data-testid="${CSS.escape(testid)}"]`).length === 1) {
|
|
88
|
+
loc.testid = testid;
|
|
89
|
+
}
|
|
90
|
+
// dom id — only if it actually resolves back to this element.
|
|
91
|
+
if (el.id && document.getElementById(el.id) === el)
|
|
92
|
+
loc.domId = el.id;
|
|
93
|
+
// xpath — always recorded as the precise last-resort signal.
|
|
94
|
+
loc.xpath = getXPath(el);
|
|
95
|
+
if (role)
|
|
96
|
+
loc.role = role;
|
|
97
|
+
if (ariaLabel)
|
|
98
|
+
loc.name = ariaLabel;
|
|
99
|
+
if (text)
|
|
100
|
+
loc.text = text;
|
|
101
|
+
// Route the element was captured on — metadata for the offline auditor (which
|
|
102
|
+
// screen to load). Ignored by runtime resolution.
|
|
103
|
+
loc.route = location.pathname + location.search;
|
|
104
|
+
return loc;
|
|
105
|
+
}
|
|
106
|
+
function accessibleName(el) {
|
|
107
|
+
return (el.getAttribute('aria-label') ?? el.textContent ?? '').trim();
|
|
108
|
+
}
|
|
109
|
+
/** True if the element plausibly *is* the signature's target. Tag must match;
|
|
110
|
+
* any provided name/text must be contained. Lenient on role (often implicit). */
|
|
111
|
+
export function signatureMatches(el, sig) {
|
|
112
|
+
if (sig.tag && el.tagName.toLowerCase() !== sig.tag.toLowerCase())
|
|
113
|
+
return false;
|
|
114
|
+
const text = (el.textContent ?? '').trim().toLowerCase();
|
|
115
|
+
if (sig.text && !text.includes(sig.text.toLowerCase()))
|
|
116
|
+
return false;
|
|
117
|
+
if (sig.name) {
|
|
118
|
+
const name = accessibleName(el).toLowerCase();
|
|
119
|
+
if (!name.includes(sig.name.toLowerCase()))
|
|
120
|
+
return false;
|
|
121
|
+
}
|
|
122
|
+
return true;
|
|
123
|
+
}
|
|
124
|
+
function unique(nodes) {
|
|
125
|
+
return nodes.length === 1 ? nodes[0] : null;
|
|
126
|
+
}
|
|
127
|
+
/** Resolve a locator against the live DOM.
|
|
128
|
+
* - ok: a signal resolved to a unique element matching the signature
|
|
129
|
+
* - healed: signals failed, but a unique element matches the signature
|
|
130
|
+
* - mismatch: a signal resolved a unique element, but its signature differs (rot)
|
|
131
|
+
* - broken: nothing usable found */
|
|
132
|
+
export function resolveLocator(loc) {
|
|
133
|
+
let sawCandidate = false;
|
|
134
|
+
const tryEl = (el) => {
|
|
135
|
+
if (!el)
|
|
136
|
+
return null;
|
|
137
|
+
sawCandidate = true;
|
|
138
|
+
return signatureMatches(el, loc.signature) ? { el, status: 'ok' } : null;
|
|
139
|
+
};
|
|
140
|
+
// 1–4: encoded signals, each must resolve to a UNIQUE element + match signature.
|
|
141
|
+
if (loc.testid) {
|
|
142
|
+
const r = tryEl(unique(document.querySelectorAll(`[data-testid="${CSS.escape(loc.testid)}"]`)));
|
|
143
|
+
if (r)
|
|
144
|
+
return r;
|
|
145
|
+
}
|
|
146
|
+
if (loc.domId) {
|
|
147
|
+
const r = tryEl(document.getElementById(loc.domId));
|
|
148
|
+
if (r)
|
|
149
|
+
return r;
|
|
150
|
+
}
|
|
151
|
+
if (loc.xpath) {
|
|
152
|
+
const r = tryEl(resolveXPath(loc.xpath));
|
|
153
|
+
if (r)
|
|
154
|
+
return r;
|
|
155
|
+
}
|
|
156
|
+
// 5: self-heal — a single element anywhere matching the signature.
|
|
157
|
+
if (loc.signature.tag) {
|
|
158
|
+
const healed = Array.from(document.getElementsByTagName(loc.signature.tag)).filter(e => signatureMatches(e, loc.signature));
|
|
159
|
+
if (healed.length === 1)
|
|
160
|
+
return { el: healed[0], status: 'healed' };
|
|
161
|
+
}
|
|
162
|
+
// A signal hit a unique element but the signature didn't match → wrong element.
|
|
163
|
+
return { el: null, status: sawCandidate ? 'mismatch' : 'broken' };
|
|
164
|
+
}
|
|
165
|
+
/** Wait for a locator to resolve (post-navigation / async render). */
|
|
166
|
+
export function waitForLocator(loc, timeoutMs = 5000) {
|
|
167
|
+
return new Promise(resolve => {
|
|
168
|
+
const now = resolveLocator(loc);
|
|
169
|
+
if (now.el) {
|
|
170
|
+
resolve(now.el);
|
|
171
|
+
return;
|
|
172
|
+
}
|
|
173
|
+
const observer = new MutationObserver(() => {
|
|
174
|
+
const r = resolveLocator(loc);
|
|
175
|
+
if (r.el) {
|
|
176
|
+
observer.disconnect();
|
|
177
|
+
clearTimeout(timer);
|
|
178
|
+
resolve(r.el);
|
|
179
|
+
}
|
|
180
|
+
});
|
|
181
|
+
observer.observe(document.body, { childList: true, subtree: true, attributes: true });
|
|
182
|
+
const timer = setTimeout(() => {
|
|
183
|
+
observer.disconnect();
|
|
184
|
+
resolve(null);
|
|
185
|
+
}, timeoutMs);
|
|
186
|
+
});
|
|
187
|
+
}
|
|
188
|
+
//# sourceMappingURL=locator.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"locator.js","sourceRoot":"","sources":["../src/locator.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
export interface SeenStore {
|
|
2
|
+
hasSeen(userId: string, tourId: string): Promise<boolean>;
|
|
3
|
+
markSeen(userId: string, tourId: string): Promise<void>;
|
|
4
|
+
getAll(userId: string): Promise<Set<string>>;
|
|
5
|
+
}
|
|
6
|
+
export declare const localSeenStore: SeenStore;
|
|
7
|
+
export interface BackendSeenStoreOpts<TProfile = unknown> {
|
|
8
|
+
/**
|
|
9
|
+
* URL to fetch the user profile (or any object containing the seen-tours list).
|
|
10
|
+
* e.g. (userId) => `/api/users/${userId}`
|
|
11
|
+
*/
|
|
12
|
+
getUrl: (userId: string) => string;
|
|
13
|
+
/**
|
|
14
|
+
* Extract the seen-tour IDs from whatever the getUrl endpoint returns.
|
|
15
|
+
* Defaults to treating the response as a bare string[].
|
|
16
|
+
*
|
|
17
|
+
* Example for a user-profile response:
|
|
18
|
+
* extractSeenTours: (profile) => profile.preferences?.seenTours ?? []
|
|
19
|
+
*/
|
|
20
|
+
extractSeenTours?: (data: TProfile) => string[];
|
|
21
|
+
/**
|
|
22
|
+
* URL to PATCH / POST when marking a tour seen.
|
|
23
|
+
* e.g. `/api/users/me/preferences`
|
|
24
|
+
*/
|
|
25
|
+
updateUrl: (userId: string) => string;
|
|
26
|
+
/**
|
|
27
|
+
* HTTP method for the update request. Defaults to 'PATCH'.
|
|
28
|
+
*/
|
|
29
|
+
updateMethod?: 'PATCH' | 'POST' | 'PUT';
|
|
30
|
+
/**
|
|
31
|
+
* Build the request body for the update call.
|
|
32
|
+
* Receives the full updated seen-tours set so you can embed it however
|
|
33
|
+
* your endpoint expects.
|
|
34
|
+
*
|
|
35
|
+
* Defaults to: (seenTours) => ({ seenTours: Array.from(seenTours) })
|
|
36
|
+
*
|
|
37
|
+
* Example for a nested preferences object:
|
|
38
|
+
* buildBody: (seenTours) => ({ preferences: { seenTours: Array.from(seenTours) } })
|
|
39
|
+
*/
|
|
40
|
+
buildBody?: (seenTours: Set<string>) => unknown;
|
|
41
|
+
headers?: Record<string, string>;
|
|
42
|
+
}
|
|
43
|
+
export declare function createBackendSeenStore<TProfile = unknown>(opts: BackendSeenStoreOpts<TProfile>): SeenStore;
|
|
44
|
+
//# sourceMappingURL=persistence.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"persistence.d.ts","sourceRoot":"","sources":["../src/persistence.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,SAAS;IACxB,OAAO,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IAC1D,QAAQ,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACxD,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC;CAC9C;AAMD,eAAO,MAAM,cAAc,EAAE,SAgB5B,CAAC;AAEF,MAAM,WAAW,oBAAoB,CAAC,QAAQ,GAAG,OAAO;IACtD;;;OAGG;IACH,MAAM,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,MAAM,CAAC;IAEnC;;;;;;OAMG;IACH,gBAAgB,CAAC,EAAE,CAAC,IAAI,EAAE,QAAQ,KAAK,MAAM,EAAE,CAAC;IAEhD;;;OAGG;IACH,SAAS,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,MAAM,CAAC;IAEtC;;OAEG;IACH,YAAY,CAAC,EAAE,OAAO,GAAG,MAAM,GAAG,KAAK,CAAC;IAExC;;;;;;;;;OASG;IACH,SAAS,CAAC,EAAE,CAAC,SAAS,EAAE,GAAG,CAAC,MAAM,CAAC,KAAK,OAAO,CAAC;IAEhD,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAClC;AAED,wBAAgB,sBAAsB,CAAC,QAAQ,GAAG,OAAO,EACvD,IAAI,EAAE,oBAAoB,CAAC,QAAQ,CAAC,GACnC,SAAS,CA6CX"}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
function storageKey(userId, tourId) {
|
|
2
|
+
return `tour.seen.${userId}.${tourId}`;
|
|
3
|
+
}
|
|
4
|
+
export const localSeenStore = {
|
|
5
|
+
async hasSeen(userId, tourId) {
|
|
6
|
+
return localStorage.getItem(storageKey(userId, tourId)) === '1';
|
|
7
|
+
},
|
|
8
|
+
async markSeen(userId, tourId) {
|
|
9
|
+
localStorage.setItem(storageKey(userId, tourId), '1');
|
|
10
|
+
},
|
|
11
|
+
async getAll(userId) {
|
|
12
|
+
const prefix = `tour.seen.${userId}.`;
|
|
13
|
+
const seen = new Set();
|
|
14
|
+
for (let i = 0; i < localStorage.length; i++) {
|
|
15
|
+
const k = localStorage.key(i);
|
|
16
|
+
if (k?.startsWith(prefix))
|
|
17
|
+
seen.add(k.slice(prefix.length));
|
|
18
|
+
}
|
|
19
|
+
return seen;
|
|
20
|
+
},
|
|
21
|
+
};
|
|
22
|
+
export function createBackendSeenStore(opts) {
|
|
23
|
+
const { getUrl, extractSeenTours = (data) => data, updateUrl, updateMethod = 'PATCH', buildBody = (seen) => ({ seenTours: Array.from(seen) }), headers = {}, } = opts;
|
|
24
|
+
const cache = new Map();
|
|
25
|
+
const jsonHeaders = { 'Content-Type': 'application/json', ...headers };
|
|
26
|
+
return {
|
|
27
|
+
async hasSeen(userId, tourId) {
|
|
28
|
+
const all = await this.getAll(userId);
|
|
29
|
+
return all.has(tourId);
|
|
30
|
+
},
|
|
31
|
+
async markSeen(userId, tourId) {
|
|
32
|
+
const all = await this.getAll(userId);
|
|
33
|
+
if (all.has(tourId))
|
|
34
|
+
return; // already marked, skip the request
|
|
35
|
+
all.add(tourId);
|
|
36
|
+
cache.set(userId, all);
|
|
37
|
+
await fetch(updateUrl(userId), {
|
|
38
|
+
method: updateMethod,
|
|
39
|
+
headers: jsonHeaders,
|
|
40
|
+
body: JSON.stringify(buildBody(all)),
|
|
41
|
+
});
|
|
42
|
+
},
|
|
43
|
+
async getAll(userId) {
|
|
44
|
+
if (!cache.has(userId)) {
|
|
45
|
+
const res = await fetch(getUrl(userId), { headers });
|
|
46
|
+
if (!res.ok) {
|
|
47
|
+
cache.set(userId, new Set());
|
|
48
|
+
return cache.get(userId);
|
|
49
|
+
}
|
|
50
|
+
const data = (await res.json());
|
|
51
|
+
cache.set(userId, new Set(extractSeenTours(data)));
|
|
52
|
+
}
|
|
53
|
+
return cache.get(userId);
|
|
54
|
+
},
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
//# sourceMappingURL=persistence.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"persistence.js","sourceRoot":"","sources":["../src/persistence.ts"],"names":[],"mappings":"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"}
|
package/dist/player.d.ts
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { Tour, ThemeOverrides } from './schema.js';
|
|
2
|
+
import { type AnchorMetaMap } from './resolver.js';
|
|
3
|
+
export interface PlayerOptions {
|
|
4
|
+
tour: Tour;
|
|
5
|
+
anchorMeta?: AnchorMetaMap;
|
|
6
|
+
theme?: ThemeOverrides;
|
|
7
|
+
onComplete?: () => void;
|
|
8
|
+
onSkip?: (stepIndex: number) => void;
|
|
9
|
+
/** Fired with the 0-based index of each step as it's shown. */
|
|
10
|
+
onStepChange?: (stepIndex: number) => void;
|
|
11
|
+
/** No step could be shown (all anchored targets missing). The caller should
|
|
12
|
+
* NOT mark the tour seen so it can show once the UI is fixed. */
|
|
13
|
+
onUnavailable?: (missingAnchors: string[]) => void;
|
|
14
|
+
navigate?: (route: string) => void | Promise<void>;
|
|
15
|
+
waitForElement?: (selector: string, timeoutMs?: number) => Promise<Element | null>;
|
|
16
|
+
}
|
|
17
|
+
export declare function playTour(opts: PlayerOptions): Promise<void>;
|
|
18
|
+
//# sourceMappingURL=player.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"player.d.ts","sourceRoot":"","sources":["../src/player.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,IAAI,EAAQ,cAAc,EAAE,MAAM,aAAa,CAAC;AAC9D,OAAO,EAAgC,KAAK,aAAa,EAAE,MAAM,eAAe,CAAC;AAIjF,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,IAAI,CAAC;IACX,UAAU,CAAC,EAAE,aAAa,CAAC;IAC3B,KAAK,CAAC,EAAE,cAAc,CAAC;IACvB,UAAU,CAAC,EAAE,MAAM,IAAI,CAAC;IACxB,MAAM,CAAC,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,IAAI,CAAC;IACrC,+DAA+D;IAC/D,YAAY,CAAC,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,IAAI,CAAC;IAC3C;sEACkE;IAClE,aAAa,CAAC,EAAE,CAAC,cAAc,EAAE,MAAM,EAAE,KAAK,IAAI,CAAC;IACnD,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACnD,cAAc,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE,MAAM,KAAK,OAAO,CAAC,OAAO,GAAG,IAAI,CAAC,CAAC;CACpF;AAyJD,wBAAsB,QAAQ,CAAC,IAAI,EAAE,aAAa,GAAG,OAAO,CAAC,IAAI,CAAC,CA+IjE"}
|