@docentjs/core 0.2.1 → 0.3.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/index.cjs +72 -18
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +32 -4
- package/dist/index.d.ts +32 -4
- package/dist/index.js +72 -18
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.d.cts
CHANGED
|
@@ -610,6 +610,12 @@ interface DocentOptions {
|
|
|
610
610
|
/** Builds a platform controller for a tour. The DOM package supplies this. */
|
|
611
611
|
createController: (tour: Tour, options: SharedControllerOptions) => TourController;
|
|
612
612
|
now?: () => number;
|
|
613
|
+
/**
|
|
614
|
+
* Start watching triggers right away. Default true. Framework bindings pass
|
|
615
|
+
* `false` and call `connect()` / `disconnect()` from their mount lifecycle,
|
|
616
|
+
* which keeps construction free of side effects (React StrictMode).
|
|
617
|
+
*/
|
|
618
|
+
connect?: boolean;
|
|
613
619
|
}
|
|
614
620
|
interface DocentState {
|
|
615
621
|
/** Id of the tour currently running or paused, if any. */
|
|
@@ -623,8 +629,6 @@ interface StartOptions {
|
|
|
623
629
|
at?: number | string;
|
|
624
630
|
}
|
|
625
631
|
export declare class Docent {
|
|
626
|
-
/** Resolves once tours and progress are loaded and triggers are armed. */
|
|
627
|
-
readonly ready: Promise<void>;
|
|
628
632
|
private readonly options;
|
|
629
633
|
private readonly env;
|
|
630
634
|
private readonly baseStorage;
|
|
@@ -642,8 +646,30 @@ export declare class Docent {
|
|
|
642
646
|
private readonly cleanups;
|
|
643
647
|
private readonly timers;
|
|
644
648
|
private readonly listeners;
|
|
649
|
+
private readonly eventListeners;
|
|
645
650
|
private destroyed;
|
|
651
|
+
private loading;
|
|
652
|
+
/** Wanted by the owner (between connect and disconnect). */
|
|
653
|
+
private connected;
|
|
654
|
+
/** Listening to routes, the source and triggers. */
|
|
655
|
+
private attached;
|
|
646
656
|
constructor(options: DocentOptions);
|
|
657
|
+
/** Resolves once tours and progress are loaded. Loading starts on first use. */
|
|
658
|
+
get ready(): Promise<void>;
|
|
659
|
+
/** Start watching routes, the tour source and triggers. Idempotent. */
|
|
660
|
+
connect(): void;
|
|
661
|
+
/**
|
|
662
|
+
* Stop watching and remove any running tour without recording an outcome.
|
|
663
|
+
* `connect()` resumes. Unlike `destroy()`, the manager stays usable.
|
|
664
|
+
*/
|
|
665
|
+
disconnect(): Promise<void>;
|
|
666
|
+
/** The tours currently managed. */
|
|
667
|
+
getTours(): Tour[];
|
|
668
|
+
/**
|
|
669
|
+
* Observe every lifecycle event from every tour, in addition to the `sink`
|
|
670
|
+
* option. Returns an unsubscribe function. Used by devtools.
|
|
671
|
+
*/
|
|
672
|
+
onEvent(listener: (event: DocentEvent) => void): () => void;
|
|
647
673
|
getState(): DocentState;
|
|
648
674
|
subscribe(listener: DocentListener): () => void;
|
|
649
675
|
/** The controller of the running tour, for fine-grained control. */
|
|
@@ -677,7 +703,8 @@ export declare class Docent {
|
|
|
677
703
|
/** Stop the running tour (recorded as skipped). */
|
|
678
704
|
stop(): Promise<void>;
|
|
679
705
|
destroy(): Promise<void>;
|
|
680
|
-
private
|
|
706
|
+
private load;
|
|
707
|
+
private attach;
|
|
681
708
|
private setTours;
|
|
682
709
|
private loadRecords;
|
|
683
710
|
private disarmTriggers;
|
|
@@ -693,7 +720,8 @@ export declare class Docent {
|
|
|
693
720
|
private fire;
|
|
694
721
|
/** Route triggers are only valid while the user is still on a matching route. */
|
|
695
722
|
private triggerStillHolds;
|
|
696
|
-
|
|
723
|
+
/** What conditions are evaluated against right now. Used by `isEligible` and devtools. */
|
|
724
|
+
getConditionEnv(): ConditionEnv;
|
|
697
725
|
private sharedOptions;
|
|
698
726
|
private run;
|
|
699
727
|
/**
|
package/dist/index.d.ts
CHANGED
|
@@ -610,6 +610,12 @@ interface DocentOptions {
|
|
|
610
610
|
/** Builds a platform controller for a tour. The DOM package supplies this. */
|
|
611
611
|
createController: (tour: Tour, options: SharedControllerOptions) => TourController;
|
|
612
612
|
now?: () => number;
|
|
613
|
+
/**
|
|
614
|
+
* Start watching triggers right away. Default true. Framework bindings pass
|
|
615
|
+
* `false` and call `connect()` / `disconnect()` from their mount lifecycle,
|
|
616
|
+
* which keeps construction free of side effects (React StrictMode).
|
|
617
|
+
*/
|
|
618
|
+
connect?: boolean;
|
|
613
619
|
}
|
|
614
620
|
interface DocentState {
|
|
615
621
|
/** Id of the tour currently running or paused, if any. */
|
|
@@ -623,8 +629,6 @@ interface StartOptions {
|
|
|
623
629
|
at?: number | string;
|
|
624
630
|
}
|
|
625
631
|
export declare class Docent {
|
|
626
|
-
/** Resolves once tours and progress are loaded and triggers are armed. */
|
|
627
|
-
readonly ready: Promise<void>;
|
|
628
632
|
private readonly options;
|
|
629
633
|
private readonly env;
|
|
630
634
|
private readonly baseStorage;
|
|
@@ -642,8 +646,30 @@ export declare class Docent {
|
|
|
642
646
|
private readonly cleanups;
|
|
643
647
|
private readonly timers;
|
|
644
648
|
private readonly listeners;
|
|
649
|
+
private readonly eventListeners;
|
|
645
650
|
private destroyed;
|
|
651
|
+
private loading;
|
|
652
|
+
/** Wanted by the owner (between connect and disconnect). */
|
|
653
|
+
private connected;
|
|
654
|
+
/** Listening to routes, the source and triggers. */
|
|
655
|
+
private attached;
|
|
646
656
|
constructor(options: DocentOptions);
|
|
657
|
+
/** Resolves once tours and progress are loaded. Loading starts on first use. */
|
|
658
|
+
get ready(): Promise<void>;
|
|
659
|
+
/** Start watching routes, the tour source and triggers. Idempotent. */
|
|
660
|
+
connect(): void;
|
|
661
|
+
/**
|
|
662
|
+
* Stop watching and remove any running tour without recording an outcome.
|
|
663
|
+
* `connect()` resumes. Unlike `destroy()`, the manager stays usable.
|
|
664
|
+
*/
|
|
665
|
+
disconnect(): Promise<void>;
|
|
666
|
+
/** The tours currently managed. */
|
|
667
|
+
getTours(): Tour[];
|
|
668
|
+
/**
|
|
669
|
+
* Observe every lifecycle event from every tour, in addition to the `sink`
|
|
670
|
+
* option. Returns an unsubscribe function. Used by devtools.
|
|
671
|
+
*/
|
|
672
|
+
onEvent(listener: (event: DocentEvent) => void): () => void;
|
|
647
673
|
getState(): DocentState;
|
|
648
674
|
subscribe(listener: DocentListener): () => void;
|
|
649
675
|
/** The controller of the running tour, for fine-grained control. */
|
|
@@ -677,7 +703,8 @@ export declare class Docent {
|
|
|
677
703
|
/** Stop the running tour (recorded as skipped). */
|
|
678
704
|
stop(): Promise<void>;
|
|
679
705
|
destroy(): Promise<void>;
|
|
680
|
-
private
|
|
706
|
+
private load;
|
|
707
|
+
private attach;
|
|
681
708
|
private setTours;
|
|
682
709
|
private loadRecords;
|
|
683
710
|
private disarmTriggers;
|
|
@@ -693,7 +720,8 @@ export declare class Docent {
|
|
|
693
720
|
private fire;
|
|
694
721
|
/** Route triggers are only valid while the user is still on a matching route. */
|
|
695
722
|
private triggerStillHolds;
|
|
696
|
-
|
|
723
|
+
/** What conditions are evaluated against right now. Used by `isEligible` and devtools. */
|
|
724
|
+
getConditionEnv(): ConditionEnv;
|
|
697
725
|
private sharedOptions;
|
|
698
726
|
private run;
|
|
699
727
|
/**
|
package/dist/index.js
CHANGED
|
@@ -467,8 +467,9 @@ var TourController = class {
|
|
|
467
467
|
async destroy() {
|
|
468
468
|
this.cancelPending();
|
|
469
469
|
this.listeners.clear();
|
|
470
|
-
|
|
470
|
+
const hidden = this.renderer.hide();
|
|
471
471
|
this.state = IDLE_STATE;
|
|
472
|
+
await hidden;
|
|
472
473
|
}
|
|
473
474
|
isActive() {
|
|
474
475
|
return this.state.status === "running" || this.state.status === "paused";
|
|
@@ -691,8 +692,6 @@ function isTourSource(value) {
|
|
|
691
692
|
return !!value && !Array.isArray(value) && typeof value.load === "function";
|
|
692
693
|
}
|
|
693
694
|
var Docent = class {
|
|
694
|
-
/** Resolves once tours and progress are loaded and triggers are armed. */
|
|
695
|
-
ready;
|
|
696
695
|
options;
|
|
697
696
|
env;
|
|
698
697
|
baseStorage;
|
|
@@ -710,14 +709,61 @@ var Docent = class {
|
|
|
710
709
|
cleanups = [];
|
|
711
710
|
timers = /* @__PURE__ */ new Set();
|
|
712
711
|
listeners = /* @__PURE__ */ new Set();
|
|
712
|
+
eventListeners = /* @__PURE__ */ new Set();
|
|
713
713
|
destroyed = false;
|
|
714
|
+
loading;
|
|
715
|
+
/** Wanted by the owner (between connect and disconnect). */
|
|
716
|
+
connected = false;
|
|
717
|
+
/** Listening to routes, the source and triggers. */
|
|
718
|
+
attached = false;
|
|
714
719
|
constructor(options) {
|
|
715
720
|
this.options = options;
|
|
716
721
|
this.env = options.environment;
|
|
717
722
|
this.identity = options.identity ?? ANONYMOUS_IDENTITY;
|
|
718
723
|
this.baseStorage = options.storage ?? createMemoryStorage();
|
|
719
724
|
this.store = new ProgressStore(scopeStorage(this.baseStorage, this.identity.id));
|
|
720
|
-
|
|
725
|
+
if (options.connect !== false) this.connect();
|
|
726
|
+
}
|
|
727
|
+
/** Resolves once tours and progress are loaded. Loading starts on first use. */
|
|
728
|
+
get ready() {
|
|
729
|
+
this.loading ??= this.load();
|
|
730
|
+
return this.loading;
|
|
731
|
+
}
|
|
732
|
+
/** Start watching routes, the tour source and triggers. Idempotent. */
|
|
733
|
+
connect() {
|
|
734
|
+
if (this.destroyed || this.connected) return;
|
|
735
|
+
this.connected = true;
|
|
736
|
+
this.ready.then(() => {
|
|
737
|
+
if (this.connected && !this.attached && !this.destroyed) this.attach();
|
|
738
|
+
});
|
|
739
|
+
}
|
|
740
|
+
/**
|
|
741
|
+
* Stop watching and remove any running tour without recording an outcome.
|
|
742
|
+
* `connect()` resumes. Unlike `destroy()`, the manager stays usable.
|
|
743
|
+
*/
|
|
744
|
+
async disconnect() {
|
|
745
|
+
this.connected = false;
|
|
746
|
+
if (this.attached) {
|
|
747
|
+
this.attached = false;
|
|
748
|
+
this.disarmTriggers();
|
|
749
|
+
for (const c of this.cleanups) c();
|
|
750
|
+
this.cleanups.length = 0;
|
|
751
|
+
}
|
|
752
|
+
this.queue = [];
|
|
753
|
+
await this.stopActive();
|
|
754
|
+
this.emitState();
|
|
755
|
+
}
|
|
756
|
+
/** The tours currently managed. */
|
|
757
|
+
getTours() {
|
|
758
|
+
return [...this.tours.values()];
|
|
759
|
+
}
|
|
760
|
+
/**
|
|
761
|
+
* Observe every lifecycle event from every tour, in addition to the `sink`
|
|
762
|
+
* option. Returns an unsubscribe function. Used by devtools.
|
|
763
|
+
*/
|
|
764
|
+
onEvent(listener) {
|
|
765
|
+
this.eventListeners.add(listener);
|
|
766
|
+
return () => this.eventListeners.delete(listener);
|
|
721
767
|
}
|
|
722
768
|
getState() {
|
|
723
769
|
return {
|
|
@@ -778,7 +824,7 @@ var Docent = class {
|
|
|
778
824
|
isEligible(tourId) {
|
|
779
825
|
const tour = this.tours.get(tourId);
|
|
780
826
|
if (!tour) return false;
|
|
781
|
-
return shouldShow(tour, this.records.get(tourId) ?? null) && evaluateAll(tour.conditions, this.
|
|
827
|
+
return shouldShow(tour, this.records.get(tourId) ?? null) && evaluateAll(tour.conditions, this.getConditionEnv());
|
|
782
828
|
}
|
|
783
829
|
/** Progress state of a tour for the current user. */
|
|
784
830
|
tourState(tourId) {
|
|
@@ -813,22 +859,20 @@ var Docent = class {
|
|
|
813
859
|
await this.controller?.skip();
|
|
814
860
|
}
|
|
815
861
|
async destroy() {
|
|
862
|
+
await this.disconnect();
|
|
816
863
|
this.destroyed = true;
|
|
817
|
-
this.disarmTriggers();
|
|
818
|
-
for (const c of this.cleanups) c();
|
|
819
|
-
this.cleanups.length = 0;
|
|
820
|
-
const controller = this.controller;
|
|
821
|
-
this.controller = void 0;
|
|
822
|
-
this.activeId = null;
|
|
823
|
-
await controller?.destroy();
|
|
824
864
|
this.listeners.clear();
|
|
865
|
+
this.eventListeners.clear();
|
|
825
866
|
}
|
|
826
|
-
async
|
|
867
|
+
async load() {
|
|
827
868
|
const source = this.options.tours;
|
|
828
869
|
const initial = isTourSource(source) ? await source.load() : source ?? [];
|
|
829
870
|
this.setTours(initial);
|
|
830
871
|
await this.loadRecords();
|
|
831
|
-
|
|
872
|
+
}
|
|
873
|
+
attach() {
|
|
874
|
+
this.attached = true;
|
|
875
|
+
const source = this.options.tours;
|
|
832
876
|
if (isTourSource(source) && source.subscribe) this.cleanups.push(source.subscribe((tours) => {
|
|
833
877
|
this.setTours(tours);
|
|
834
878
|
this.loadRecords().then(() => this.armTriggers());
|
|
@@ -857,7 +901,7 @@ var Docent = class {
|
|
|
857
901
|
* `except` skips one tour, used for the tour that just finished.
|
|
858
902
|
*/
|
|
859
903
|
armTriggers(except) {
|
|
860
|
-
if (this.destroyed) return;
|
|
904
|
+
if (this.destroyed || !this.attached) return;
|
|
861
905
|
this.disarmTriggers();
|
|
862
906
|
for (const tour of this.tours.values()) {
|
|
863
907
|
const trigger = tour.trigger;
|
|
@@ -899,7 +943,7 @@ var Docent = class {
|
|
|
899
943
|
}
|
|
900
944
|
/** A trigger fired: start the tour if eligible, or queue it behind the running one. */
|
|
901
945
|
fire(tourId) {
|
|
902
|
-
if (this.destroyed || tourId === this.activeId) return;
|
|
946
|
+
if (this.destroyed || !this.attached || tourId === this.activeId) return;
|
|
903
947
|
if (!this.isEligible(tourId) || !this.triggerStillHolds(tourId)) return;
|
|
904
948
|
if (this.tours.get(tourId)?.trigger?.type === "auto") this.autoFired.add(tourId);
|
|
905
949
|
if (this.activeId) {
|
|
@@ -916,7 +960,8 @@ var Docent = class {
|
|
|
916
960
|
const route = this.env.currentRoute?.();
|
|
917
961
|
return route !== void 0 && matchRoute(trigger.pattern, route);
|
|
918
962
|
}
|
|
919
|
-
|
|
963
|
+
/** What conditions are evaluated against right now. Used by `isEligible` and devtools. */
|
|
964
|
+
getConditionEnv() {
|
|
920
965
|
const env = {
|
|
921
966
|
identity: this.identity,
|
|
922
967
|
elementExists: (t) => this.env.hasTarget(t),
|
|
@@ -933,7 +978,10 @@ var Docent = class {
|
|
|
933
978
|
storage: scopeStorage(this.baseStorage, this.identity.id),
|
|
934
979
|
tourState: (id) => this.tourState(id)
|
|
935
980
|
};
|
|
936
|
-
|
|
981
|
+
shared.sink = { emit: (event) => {
|
|
982
|
+
this.options.sink?.emit(event);
|
|
983
|
+
for (const l of this.eventListeners) l(event);
|
|
984
|
+
} };
|
|
937
985
|
const hooks = this.options.hooks?.[tour.id];
|
|
938
986
|
if (hooks) shared.hooks = hooks;
|
|
939
987
|
if (this.options.custom) shared.custom = this.options.custom;
|
|
@@ -945,6 +993,12 @@ var Docent = class {
|
|
|
945
993
|
const controller = this.options.createController(tour, this.sharedOptions(tour));
|
|
946
994
|
this.controller = controller;
|
|
947
995
|
this.activeId = tour.id;
|
|
996
|
+
this.records.set(tour.id, {
|
|
997
|
+
tourId: tour.id,
|
|
998
|
+
version: tourVersion(tour),
|
|
999
|
+
state: "in-progress",
|
|
1000
|
+
updatedAt: (this.options.now ?? Date.now)()
|
|
1001
|
+
});
|
|
948
1002
|
this.emitState();
|
|
949
1003
|
const off = controller.subscribe((state) => {
|
|
950
1004
|
if (state.status === "completed" || state.status === "skipped" || state.status === "aborted") {
|