@docentjs/dom 0.5.2 → 0.6.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 +174 -3
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +34 -5
- package/dist/index.d.ts +34 -5
- package/dist/index.js +173 -4
- package/dist/index.js.map +1 -1
- package/dist/validate.cjs +9 -0
- package/dist/validate.d.cts +1 -0
- package/dist/validate.d.ts +1 -0
- package/dist/validate.js +1 -0
- package/package.json +12 -2
package/dist/index.cjs
CHANGED
|
@@ -79,6 +79,45 @@ function renderMedia(doc, media) {
|
|
|
79
79
|
return video;
|
|
80
80
|
}
|
|
81
81
|
//#endregion
|
|
82
|
+
//#region src/dev.ts
|
|
83
|
+
const checked = /* @__PURE__ */ new WeakSet();
|
|
84
|
+
/**
|
|
85
|
+
* Written exactly like this on purpose: bundlers replace
|
|
86
|
+
* `process.env.NODE_ENV` literally, so a production build turns this into
|
|
87
|
+
* `false`, and the check plus its import are dropped. Without a bundler
|
|
88
|
+
* `process` is simply not defined, and the catch treats that as development.
|
|
89
|
+
*/
|
|
90
|
+
function isProduction() {
|
|
91
|
+
try {
|
|
92
|
+
return process.env.NODE_ENV === "production";
|
|
93
|
+
} catch {
|
|
94
|
+
return false;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Check every tour the manager knows, as they load. Tours that never start
|
|
99
|
+
* still get checked, which is where a broken condition usually hides.
|
|
100
|
+
*/
|
|
101
|
+
function warnAboutTours(docent) {
|
|
102
|
+
if (isProduction()) return;
|
|
103
|
+
const check = () => {
|
|
104
|
+
for (const tour of docent.getTours()) warnIfInvalid(tour);
|
|
105
|
+
};
|
|
106
|
+
docent.subscribe(check);
|
|
107
|
+
check();
|
|
108
|
+
}
|
|
109
|
+
/** Warn about anything wrong with this tour, once per tour, in development. */
|
|
110
|
+
function warnIfInvalid(tour) {
|
|
111
|
+
if (!tour || isProduction() || checked.has(tour)) return;
|
|
112
|
+
checked.add(tour);
|
|
113
|
+
import("@docentjs/core/validate").then(({ formatIssues, validateTour }) => {
|
|
114
|
+
const issues = validateTour(tour);
|
|
115
|
+
if (issues.length === 0) return;
|
|
116
|
+
const label = issues.filter((issue) => issue.level === "error").length > 0 ? "error" : "warning";
|
|
117
|
+
console.warn(`[docent] Tour "${tour.id}" has ${issues.length} ${label}${issues.length === 1 ? "" : "s"}:\n${formatIssues(issues)}\nThis check runs in development only. See https://docentjs.dev/reference/schema/`);
|
|
118
|
+
}).catch(() => {});
|
|
119
|
+
}
|
|
120
|
+
//#endregion
|
|
82
121
|
//#region src/occlusion.ts
|
|
83
122
|
function isPinned(el) {
|
|
84
123
|
const view = el.ownerDocument.defaultView;
|
|
@@ -678,18 +717,82 @@ const THEME_VARS = {
|
|
|
678
717
|
connector: "connector",
|
|
679
718
|
ring: "ring"
|
|
680
719
|
};
|
|
720
|
+
/** Tokens that take a unit when given as a number. */
|
|
721
|
+
const UNITS = {
|
|
722
|
+
radius: "px",
|
|
723
|
+
width: "px",
|
|
724
|
+
duration: "ms"
|
|
725
|
+
};
|
|
726
|
+
/** `12` becomes `12px`, `220` becomes `220ms`, strings are passed through. */
|
|
727
|
+
function cssValue(key, value) {
|
|
728
|
+
return typeof value === "number" ? `${value}${UNITS[key] ?? ""}` : value;
|
|
729
|
+
}
|
|
681
730
|
/** Write theme tokens as inline custom properties on an element. Clears unset ones. */
|
|
682
731
|
function applyTheme(el, theme) {
|
|
683
732
|
for (const key of Object.keys(THEME_VARS)) {
|
|
684
733
|
const value = theme?.[key];
|
|
685
734
|
const prop = `--docent-${THEME_VARS[key]}`;
|
|
686
735
|
if (value === void 0) el.style.removeProperty(prop);
|
|
687
|
-
else el.style.setProperty(prop, value);
|
|
736
|
+
else el.style.setProperty(prop, cssValue(key, value));
|
|
737
|
+
}
|
|
738
|
+
if (theme?.accent !== void 0 && theme.accentForeground === void 0) {
|
|
739
|
+
const light = isLightColor(el, cssValue("accent", theme.accent));
|
|
740
|
+
if (light !== void 0) el.style.setProperty("--docent-accent-fg", light ? "var(--docent-fg)" : "var(--docent-bg)");
|
|
688
741
|
}
|
|
689
742
|
}
|
|
743
|
+
/**
|
|
744
|
+
* Is this colour light enough to need dark text on it? Resolves the colour
|
|
745
|
+
* through the browser, so any CSS colour works. Undefined when it cannot tell.
|
|
746
|
+
*/
|
|
747
|
+
function isLightColor(el, color) {
|
|
748
|
+
const view = el.ownerDocument.defaultView;
|
|
749
|
+
if (!view) return void 0;
|
|
750
|
+
const previous = el.style.color;
|
|
751
|
+
el.style.color = color;
|
|
752
|
+
const computed = view.getComputedStyle(el).color;
|
|
753
|
+
el.style.color = previous;
|
|
754
|
+
const rgb = computed.match(/^(?:rgba?|color\(srgb)[( ]([^)]+)\)?/);
|
|
755
|
+
if (rgb?.[1]) {
|
|
756
|
+
const [r = 0, g = 0, b = 0] = rgb[1].split(/[\s,/]+/).slice(0, 3).map((n) => n.endsWith("%") ? Number.parseFloat(n) / 100 * 255 : Number.parseFloat(n));
|
|
757
|
+
const scale = computed.startsWith("color(") ? 255 : 1;
|
|
758
|
+
return (.2126 * r * scale + .7152 * g * scale + .0722 * b * scale) / 255 > .55;
|
|
759
|
+
}
|
|
760
|
+
const ok = computed.match(/^oklch\(\s*([\d.]+)(%?)/);
|
|
761
|
+
if (ok?.[1]) return Number.parseFloat(ok[1]) / (ok[2] === "%" ? 100 : 1) > .62;
|
|
762
|
+
}
|
|
690
763
|
function mergeThemes(...themes) {
|
|
691
764
|
return Object.assign({}, ...themes.filter(Boolean));
|
|
692
765
|
}
|
|
766
|
+
/**
|
|
767
|
+
* Layer one theme over another when either may be a preset name. Used by the
|
|
768
|
+
* framework adapters to merge a provider's defaults with a local theme.
|
|
769
|
+
*/
|
|
770
|
+
function mergeThemeSpecs(base, override) {
|
|
771
|
+
if (base === void 0) return override;
|
|
772
|
+
if (override === void 0) return base;
|
|
773
|
+
const first = typeof base === "string" ? { preset: base } : base;
|
|
774
|
+
const second = typeof override === "string" ? { preset: override } : override;
|
|
775
|
+
return {
|
|
776
|
+
...first,
|
|
777
|
+
...second
|
|
778
|
+
};
|
|
779
|
+
}
|
|
780
|
+
/** `{ theme }` when there is one, or nothing, for spreading into options. */
|
|
781
|
+
function themeOption(theme) {
|
|
782
|
+
return theme === void 0 ? {} : { theme };
|
|
783
|
+
}
|
|
784
|
+
/** True when this theme cannot be resolved without the built-in presets. */
|
|
785
|
+
function needsPresets(spec) {
|
|
786
|
+
return typeof spec === "string" || !!spec && typeof spec === "object" && "preset" in spec;
|
|
787
|
+
}
|
|
788
|
+
/** A preset name, tokens, or a preset with tokens on top, flattened to tokens. */
|
|
789
|
+
function resolveTheme(spec, presets) {
|
|
790
|
+
if (spec === void 0) return void 0;
|
|
791
|
+
if (typeof spec === "string") return presets?.[spec];
|
|
792
|
+
const { preset, ...tokens } = spec;
|
|
793
|
+
if (preset === void 0) return tokens;
|
|
794
|
+
return mergeThemes(presets?.[preset], tokens);
|
|
795
|
+
}
|
|
693
796
|
//#endregion
|
|
694
797
|
//#region src/renderer.ts
|
|
695
798
|
const DEFAULT_LOOK = {
|
|
@@ -721,6 +824,11 @@ var DomRenderer = class {
|
|
|
721
824
|
connectorLoading;
|
|
722
825
|
/** Arrow, spotlight and overlay settings for the current step. */
|
|
723
826
|
look = DEFAULT_LOOK;
|
|
827
|
+
/** Preset tokens, once loaded. */
|
|
828
|
+
presets;
|
|
829
|
+
presetLoad;
|
|
830
|
+
/** Set while `appearance: 'auto'` is following the system setting. */
|
|
831
|
+
schemeQuery;
|
|
724
832
|
/** Until then the step's own transition runs; scroll updates may animate. */
|
|
725
833
|
settleUntil = 0;
|
|
726
834
|
/** Play the connector draw-in on its next render. */
|
|
@@ -744,6 +852,12 @@ var DomRenderer = class {
|
|
|
744
852
|
return `${pathname}${search}`;
|
|
745
853
|
}
|
|
746
854
|
show(ctx) {
|
|
855
|
+
if (this.presets === void 0 && this.usesPresets(ctx)) return this.loadPresets().then(() => {
|
|
856
|
+
this.showNow(ctx);
|
|
857
|
+
});
|
|
858
|
+
this.showNow(ctx);
|
|
859
|
+
}
|
|
860
|
+
showNow(ctx) {
|
|
747
861
|
const firstStep = !this.host;
|
|
748
862
|
const host = this.mount();
|
|
749
863
|
const from = this.popover?.style.transform || null;
|
|
@@ -751,7 +865,8 @@ var DomRenderer = class {
|
|
|
751
865
|
this.ctx = ctx;
|
|
752
866
|
this.target = ctx.step.target === void 0 ? null : resolveTarget(ctx.step.target, this.doc);
|
|
753
867
|
const template = this.template(ctx);
|
|
754
|
-
|
|
868
|
+
this.watchAppearance(ctx);
|
|
869
|
+
applyTheme(host, this.themeFor(ctx, template));
|
|
755
870
|
this.setTemplateCss(template?.css);
|
|
756
871
|
this.applyLook(host, this.resolveLook(ctx, template));
|
|
757
872
|
this.settleUntil = performance.now() + this.duration(host) * 1.5;
|
|
@@ -781,6 +896,8 @@ var DomRenderer = class {
|
|
|
781
896
|
}
|
|
782
897
|
hide() {
|
|
783
898
|
this.teardownStep();
|
|
899
|
+
this.schemeQuery?.removeEventListener("change", this.onSchemeChange);
|
|
900
|
+
this.schemeQuery = void 0;
|
|
784
901
|
if (this.host) {
|
|
785
902
|
this.host.remove();
|
|
786
903
|
this.host = void 0;
|
|
@@ -965,6 +1082,55 @@ var DomRenderer = class {
|
|
|
965
1082
|
shadow.insertBefore(style, before);
|
|
966
1083
|
shadow.insertBefore(this.connector.el, before);
|
|
967
1084
|
}
|
|
1085
|
+
/** Does anything here need the built-in presets? */
|
|
1086
|
+
usesPresets(ctx) {
|
|
1087
|
+
return this.appearance(ctx) !== "light" || needsPresets(this.options.theme) || needsPresets(ctx.tour.options?.theme) || needsPresets(this.template(ctx)?.theme);
|
|
1088
|
+
}
|
|
1089
|
+
loadPresets() {
|
|
1090
|
+
this.presetLoad ??= Promise.resolve().then(() => require("./themes.cjs")).then((mod) => {
|
|
1091
|
+
this.presets = {
|
|
1092
|
+
light: mod.light,
|
|
1093
|
+
dark: mod.dark,
|
|
1094
|
+
minimal: mod.minimal,
|
|
1095
|
+
contrast: mod.contrast
|
|
1096
|
+
};
|
|
1097
|
+
});
|
|
1098
|
+
return this.presetLoad;
|
|
1099
|
+
}
|
|
1100
|
+
appearance(ctx) {
|
|
1101
|
+
return ctx.tour.options?.appearance ?? this.options.appearance ?? "light";
|
|
1102
|
+
}
|
|
1103
|
+
/** The surface tokens for the current appearance: dark, or nothing for light. */
|
|
1104
|
+
appearanceTheme(ctx) {
|
|
1105
|
+
const appearance = this.appearance(ctx);
|
|
1106
|
+
if (appearance === "dark") return this.presets?.dark;
|
|
1107
|
+
if (appearance !== "auto") return void 0;
|
|
1108
|
+
return this.prefersDark() ? this.presets?.dark : this.presets?.light;
|
|
1109
|
+
}
|
|
1110
|
+
prefersDark() {
|
|
1111
|
+
return this.doc.defaultView?.matchMedia?.("(prefers-color-scheme: dark)").matches ?? false;
|
|
1112
|
+
}
|
|
1113
|
+
/** Renderer, then the appearance surface, then template, then tour. */
|
|
1114
|
+
themeFor(ctx, template) {
|
|
1115
|
+
const presets = this.presets;
|
|
1116
|
+
return mergeThemes(resolveTheme(this.options.theme, presets), this.appearanceTheme(ctx), resolveTheme(template?.theme, presets), resolveTheme(ctx.tour.options?.theme, presets));
|
|
1117
|
+
}
|
|
1118
|
+
/** With `appearance: 'auto'`, follow the system setting while the tour runs. */
|
|
1119
|
+
watchAppearance(ctx) {
|
|
1120
|
+
const wanted = this.appearance(ctx) === "auto";
|
|
1121
|
+
if (wanted === (this.schemeQuery !== void 0)) return;
|
|
1122
|
+
if (!wanted) {
|
|
1123
|
+
this.schemeQuery?.removeEventListener("change", this.onSchemeChange);
|
|
1124
|
+
this.schemeQuery = void 0;
|
|
1125
|
+
return;
|
|
1126
|
+
}
|
|
1127
|
+
this.schemeQuery = this.doc.defaultView?.matchMedia?.("(prefers-color-scheme: dark)");
|
|
1128
|
+
this.schemeQuery?.addEventListener("change", this.onSchemeChange);
|
|
1129
|
+
}
|
|
1130
|
+
onSchemeChange = () => {
|
|
1131
|
+
const ctx = this.ctx;
|
|
1132
|
+
if (ctx && this.host) applyTheme(this.host, this.themeFor(ctx, this.template(ctx)));
|
|
1133
|
+
};
|
|
968
1134
|
resolveLook(ctx, template) {
|
|
969
1135
|
const tour = ctx.tour.options ?? {};
|
|
970
1136
|
const step = ctx.step;
|
|
@@ -1365,6 +1531,7 @@ var DomTourController = class extends _docentjs_core.TourController {
|
|
|
1365
1531
|
followRoutes;
|
|
1366
1532
|
constructor(tour, options = {}) {
|
|
1367
1533
|
const { renderer: rendererOptions, followRoutes, ...rest } = options;
|
|
1534
|
+
warnIfInvalid(tour);
|
|
1368
1535
|
const renderer = new DomRenderer(rendererOptions);
|
|
1369
1536
|
super({
|
|
1370
1537
|
...rest,
|
|
@@ -1470,7 +1637,7 @@ function createDocent(options = {}) {
|
|
|
1470
1637
|
...renderer,
|
|
1471
1638
|
document: doc
|
|
1472
1639
|
} : { ...renderer };
|
|
1473
|
-
|
|
1640
|
+
const docent = new _docentjs_core.Docent({
|
|
1474
1641
|
...rest,
|
|
1475
1642
|
storage: rest.storage ?? createLocalStorage(),
|
|
1476
1643
|
environment: createDomEnvironment(doc),
|
|
@@ -1479,6 +1646,8 @@ function createDocent(options = {}) {
|
|
|
1479
1646
|
renderer: rendererOptions
|
|
1480
1647
|
})
|
|
1481
1648
|
});
|
|
1649
|
+
warnAboutTours(docent);
|
|
1650
|
+
return docent;
|
|
1482
1651
|
}
|
|
1483
1652
|
//#endregion
|
|
1484
1653
|
exports.CONNECTOR_STYLES = require_arrows.CONNECTOR_STYLES;
|
|
@@ -1512,6 +1681,7 @@ exports.holePath = holePath;
|
|
|
1512
1681
|
exports.inflate = inflate;
|
|
1513
1682
|
exports.isConnector = require_arrows.isConnector;
|
|
1514
1683
|
exports.isSafeUrl = isSafeUrl;
|
|
1684
|
+
exports.mergeThemeSpecs = mergeThemeSpecs;
|
|
1515
1685
|
exports.mergeThemes = mergeThemes;
|
|
1516
1686
|
exports.parsePlacement = parsePlacement;
|
|
1517
1687
|
exports.queryAllDeep = queryAllDeep;
|
|
@@ -1519,6 +1689,7 @@ exports.renderBody = renderBody;
|
|
|
1519
1689
|
exports.renderMedia = renderMedia;
|
|
1520
1690
|
exports.resolveSlots = resolveSlots;
|
|
1521
1691
|
exports.resolveTarget = resolveTarget;
|
|
1692
|
+
exports.themeOption = themeOption;
|
|
1522
1693
|
exports.toSpec = toSpec;
|
|
1523
1694
|
exports.uncover = uncover;
|
|
1524
1695
|
exports.waitForTarget = waitForTarget;
|