@glowhop/core-tour 1.0.0 → 1.0.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.
- package/CHANGELOG.md +27 -0
- package/adapter.js +2 -1
- package/elements/idle-presentation.d.ts +12 -1
- package/elements/overlay.d.ts +50 -0
- package/index.js +138 -28
- package/package.json +1 -1
- package/utils/utils.d.ts +34 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,32 @@
|
|
|
1
1
|
# @glowhop/core-tour
|
|
2
2
|
|
|
3
|
+
## 1.0.2
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- 5aa8cdf: Fix two mobile overlay defects.
|
|
8
|
+
|
|
9
|
+
The backdrop no longer stops short of the bottom of the screen. Its `viewBox` is
|
|
10
|
+
now measured from the element's own box instead of `documentElement.clientHeight`,
|
|
11
|
+
which a retracting mobile URL bar moves out of step with the box a fixed element
|
|
12
|
+
is sized against; `preserveAspectRatio="xMinYMin slice"` makes any residual mismatch
|
|
13
|
+
overdraw rather than letterbox, and the element is sized to `100lvh` where that
|
|
14
|
+
unit exists so it always spans the visible area.
|
|
15
|
+
|
|
16
|
+
The cutout now animates on WebKit. Safari — and therefore every browser on iOS —
|
|
17
|
+
cannot animate `d` through the Web Animations API, so the rectangle is
|
|
18
|
+
interpolated frame by frame and the path regenerated from it, clocked by the
|
|
19
|
+
eased progress of the animation already running on the same element, instead of
|
|
20
|
+
snapping to each target.
|
|
21
|
+
|
|
22
|
+
## 1.0.1
|
|
23
|
+
|
|
24
|
+
### Patch Changes
|
|
25
|
+
|
|
26
|
+
- 358c4b1: Draw the overlay backdrop on WebKit. The cutout geometry was only written to the CSS `d` property, which Safari does not implement — so on macOS Safari and on every iOS browser (Chrome and Firefox included, both WebKit) the backdrop never drew and tours ran with a popover but no dimming. The `d` attribute is now the source of truth, with the CSS property mirrored where it exists so the cutout still morphs between steps.
|
|
27
|
+
|
|
28
|
+
Measure the viewport as the layout viewport (`documentElement.clientWidth/clientHeight`) instead of `innerWidth`/`innerHeight`. The latter reports the visual viewport, which shrinks with a mobile URL bar and includes a classic desktop scrollbar; the resulting mismatch with the overlay's own `100%`-sized box scaled and centred the backdrop, leaving undimmed bands and a cutout offset from its target. The same measurement backs popover clamping and the pointer's in-viewport checks.
|
|
29
|
+
|
|
3
30
|
## 1.0.0
|
|
4
31
|
|
|
5
32
|
### Major Changes
|
package/adapter.js
CHANGED
|
@@ -15,7 +15,8 @@ var OVERLAY_IDLE_STYLE = {
|
|
|
15
15
|
var OVERLAY_IDLE_ATTRIBUTES = {
|
|
16
16
|
"aria-hidden": "true",
|
|
17
17
|
"data-glow-tour-allow-interaction": "false",
|
|
18
|
-
inert: "true"
|
|
18
|
+
inert: "true",
|
|
19
|
+
preserveAspectRatio: "xMinYMin slice"
|
|
19
20
|
};
|
|
20
21
|
var OVERLAY_PATH_IDLE_ATTRIBUTES = {
|
|
21
22
|
cursor: "auto",
|
|
@@ -17,11 +17,22 @@
|
|
|
17
17
|
export type CssStyleRecord = Readonly<Record<string, string>>;
|
|
18
18
|
/** Idle inline style for the overlay `<svg>` element. */
|
|
19
19
|
export declare const OVERLAY_IDLE_STYLE: CssStyleRecord;
|
|
20
|
-
/**
|
|
20
|
+
/**
|
|
21
|
+
* Idle attributes for the overlay `<svg>` element.
|
|
22
|
+
*
|
|
23
|
+
* `preserveAspectRatio` is the load-bearing one: the default (`xMidYMid meet`)
|
|
24
|
+
* letterboxes the backdrop the moment the `viewBox` and the element's own box
|
|
25
|
+
* disagree, which is exactly what a mobile URL bar causes as it retracts —
|
|
26
|
+
* undimmed bands above and below the overlay. `slice` overdraws instead, so a
|
|
27
|
+
* `viewBox` that is momentarily stale costs a few scaled pixels rather than a
|
|
28
|
+
* hole in the backdrop, and `xMinYMin` anchors that overdraw at the origin the
|
|
29
|
+
* cutout coordinates are measured from.
|
|
30
|
+
*/
|
|
21
31
|
export declare const OVERLAY_IDLE_ATTRIBUTES: {
|
|
22
32
|
readonly "aria-hidden": "true";
|
|
23
33
|
readonly "data-glow-tour-allow-interaction": "false";
|
|
24
34
|
readonly inert: "true";
|
|
35
|
+
readonly preserveAspectRatio: "xMinYMin slice";
|
|
25
36
|
};
|
|
26
37
|
/**
|
|
27
38
|
* Idle attributes for the overlay's cutout `<path>`. Genuinely load-bearing,
|
package/elements/overlay.d.ts
CHANGED
|
@@ -1,16 +1,66 @@
|
|
|
1
1
|
import GlowTourElement, { type TourElementStep } from "./base";
|
|
2
2
|
export default class OverlayElement extends GlowTourElement {
|
|
3
3
|
private currentTransition;
|
|
4
|
+
private geometryTween;
|
|
5
|
+
/** Where the cutout is now — the tween's starting point on the next move. */
|
|
6
|
+
private cutout;
|
|
4
7
|
private visualState;
|
|
8
|
+
private cssPathDSupported;
|
|
5
9
|
setInteractionAllowed(allowed: boolean): void;
|
|
6
10
|
moveToTarget(nextPosition: DOMRect, step: TourElementStep): Promise<void>;
|
|
7
11
|
animateTo(position: DOMRect, step: TourElementStep): Promise<void>;
|
|
8
12
|
_getNextStyles(position: DOMRect, step: TourElementStep): Keyframe;
|
|
13
|
+
/** The backdrop with a hole punched around `position`, as a CSS `d` value. */
|
|
14
|
+
private _cutoutPath;
|
|
9
15
|
initializeProps(): void;
|
|
10
16
|
private _getPathElement;
|
|
11
17
|
protected _release(): void;
|
|
12
18
|
updatePosition(nextPosition: DOMRect, step: TourElementStep, animateChanges?: boolean, onTransition?: (transition: Promise<void>) => void): void;
|
|
19
|
+
/**
|
|
20
|
+
* Pins the `viewBox` to the element's own box, so one SVG unit is one CSS
|
|
21
|
+
* pixel and the cutout lands where `getBoundingClientRect()` said it should.
|
|
22
|
+
*/
|
|
23
|
+
private syncViewBox;
|
|
13
24
|
cancelAnimations(): void;
|
|
25
|
+
/**
|
|
26
|
+
* Starts an animation whose keyframes carry the cutout geometry.
|
|
27
|
+
*
|
|
28
|
+
* WebKit ignores `d` both as a CSS property and as an animatable value, so
|
|
29
|
+
* there the geometry is stripped from the keyframes — the engine animates
|
|
30
|
+
* fill and opacity — and {@link _startGeometryTween} moves the shape instead.
|
|
31
|
+
* When it cannot, the target shape is committed up front, which is the old
|
|
32
|
+
* snap-and-be-correct behaviour.
|
|
33
|
+
*/
|
|
34
|
+
private _startPathAnimation;
|
|
35
|
+
/**
|
|
36
|
+
* Morphs the cutout by hand, one frame at a time, the way driver.js moves its
|
|
37
|
+
* stage: the rectangle is interpolated and the path regenerated from it.
|
|
38
|
+
*
|
|
39
|
+
* The clock is the animation that is already running on the same element, so
|
|
40
|
+
* the shape, the backdrop and anything else the engine is easing stay in step
|
|
41
|
+
* without this having to re-implement a timing function. Its progress reads
|
|
42
|
+
* `null` once it is over, which is the tween's cue to land and stop.
|
|
43
|
+
*
|
|
44
|
+
* @returns Whether the tween took ownership of the geometry. `false` leaves
|
|
45
|
+
* the caller to commit the final shape.
|
|
46
|
+
*/
|
|
47
|
+
private _startGeometryTween;
|
|
48
|
+
/** Drops the running cutout tween, leaving the shape wherever it got to. */
|
|
49
|
+
private _stopGeometryTween;
|
|
50
|
+
/**
|
|
51
|
+
* Whether this document implements the CSS `d` property. Safari/WebKit — and
|
|
52
|
+
* therefore every browser on iOS, Chrome included — does not: an inline
|
|
53
|
+
* `d: path(...)` is dropped on the floor and the backdrop never draws.
|
|
54
|
+
*/
|
|
55
|
+
private supportsCssPathD;
|
|
56
|
+
/**
|
|
57
|
+
* Writes the cutout geometry. The `d` attribute is the source of truth since
|
|
58
|
+
* every engine honours it; the CSS property is mirrored where it exists,
|
|
59
|
+
* because that is what makes the shape animatable and what wins the cascade.
|
|
60
|
+
*/
|
|
61
|
+
private writePathD;
|
|
62
|
+
/** The current geometry, as the `path("...")` CSS value the keyframes use. */
|
|
63
|
+
private readPathD;
|
|
14
64
|
private applyStyles;
|
|
15
65
|
private commitAndCancelCurrentTransition;
|
|
16
66
|
private getCurrentRenderedStyles;
|
package/index.js
CHANGED
|
@@ -424,12 +424,24 @@ function isNode(value, context) {
|
|
|
424
424
|
return typeof Node === "function" && value instanceof Node;
|
|
425
425
|
}
|
|
426
426
|
function viewportDimensions(context) {
|
|
427
|
+
const root = ownerDocument(context)?.documentElement;
|
|
428
|
+
const width = root?.clientWidth;
|
|
429
|
+
const height = root?.clientHeight;
|
|
430
|
+
if (typeof width === "number" && width > 0 && typeof height === "number" && height > 0) {
|
|
431
|
+
return { width, height };
|
|
432
|
+
}
|
|
427
433
|
const currentWindow = ownerWindow(context);
|
|
428
434
|
return {
|
|
429
435
|
width: currentWindow?.innerWidth ?? 1024,
|
|
430
436
|
height: currentWindow?.innerHeight ?? 768
|
|
431
437
|
};
|
|
432
438
|
}
|
|
439
|
+
function paintedBoxDimensions(element) {
|
|
440
|
+
const rect = element?.getBoundingClientRect?.();
|
|
441
|
+
if (rect && rect.width > 0 && rect.height > 0)
|
|
442
|
+
return { height: rect.height, width: rect.width };
|
|
443
|
+
return viewportDimensions(element);
|
|
444
|
+
}
|
|
433
445
|
function isInViewport(rect, context) {
|
|
434
446
|
const viewport = viewportDimensions(context);
|
|
435
447
|
return rect.left >= 0 && rect.top >= 0 && rect.right <= viewport.width && rect.bottom <= viewport.height;
|
|
@@ -600,7 +612,8 @@ var OVERLAY_IDLE_STYLE = {
|
|
|
600
612
|
var OVERLAY_IDLE_ATTRIBUTES = {
|
|
601
613
|
"aria-hidden": "true",
|
|
602
614
|
"data-glow-tour-allow-interaction": "false",
|
|
603
|
-
inert: "true"
|
|
615
|
+
inert: "true",
|
|
616
|
+
preserveAspectRatio: "xMinYMin slice"
|
|
604
617
|
};
|
|
605
618
|
var POINTER_IDLE_STYLE = {
|
|
606
619
|
left: "0px",
|
|
@@ -634,7 +647,10 @@ var DEFAULT_OVERLAY_RADIUS = 8;
|
|
|
634
647
|
|
|
635
648
|
class OverlayElement extends GlowTourElement {
|
|
636
649
|
currentTransition = null;
|
|
650
|
+
geometryTween = null;
|
|
651
|
+
cutout = null;
|
|
637
652
|
visualState = null;
|
|
653
|
+
cssPathDSupported = null;
|
|
638
654
|
setInteractionAllowed(allowed) {
|
|
639
655
|
this.element.style.setProperty("pointer-events", allowed ? "none" : "auto");
|
|
640
656
|
this.element.setAttribute("data-glow-tour-allow-interaction", String(allowed));
|
|
@@ -648,12 +664,11 @@ class OverlayElement extends GlowTourElement {
|
|
|
648
664
|
}
|
|
649
665
|
const keyframe = this.getRenderedTargetStyles(path, this._getNextStyles(nextPosition, step));
|
|
650
666
|
this.visualState = nextVisualState;
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
}
|
|
667
|
+
const previousCutout = this.cutout;
|
|
668
|
+
this.cutout = nextPosition;
|
|
669
|
+
if (!this.readPathD(path)) {
|
|
670
|
+
const { opacity: _initialOpacity, ...geometry } = keyframe;
|
|
671
|
+
this.applyStyles(path, geometry);
|
|
657
672
|
const opacity = keyframe.opacity == null ? "0.7" : String(keyframe.opacity);
|
|
658
673
|
path.style.setProperty("opacity", "0");
|
|
659
674
|
const animation2 = this._startAnimation([{ opacity: "0" }, { opacity }], {
|
|
@@ -665,14 +680,14 @@ class OverlayElement extends GlowTourElement {
|
|
|
665
680
|
return;
|
|
666
681
|
}
|
|
667
682
|
const baseStyle = {
|
|
668
|
-
d:
|
|
683
|
+
d: this.readPathD(path),
|
|
669
684
|
fill: path.style.getPropertyValue("fill") ?? "",
|
|
670
685
|
opacity: path.style.getPropertyValue("opacity") ?? "0"
|
|
671
686
|
};
|
|
672
|
-
const animation = this.
|
|
687
|
+
const animation = this._startPathAnimation([baseStyle, keyframe], {
|
|
673
688
|
...this._getAnimationOptions(),
|
|
674
689
|
fill: "none"
|
|
675
|
-
}, path);
|
|
690
|
+
}, path, previousCutout, nextPosition, step);
|
|
676
691
|
if (!animation || await this._waitForAnimation(animation))
|
|
677
692
|
this.applyStyles(path, keyframe);
|
|
678
693
|
}
|
|
@@ -681,12 +696,14 @@ class OverlayElement extends GlowTourElement {
|
|
|
681
696
|
if (!path)
|
|
682
697
|
return;
|
|
683
698
|
this.commitAndCancelCurrentTransition(path);
|
|
699
|
+
const previousCutout = this.cutout;
|
|
700
|
+
this.cutout = position;
|
|
684
701
|
const from = this.getCurrentRenderedStyles(path);
|
|
685
702
|
const finalStyles = this.getRenderedTargetStyles(path, this._getNextStyles(position, step));
|
|
686
|
-
const animation = this.
|
|
703
|
+
const animation = this._startPathAnimation([from, finalStyles], {
|
|
687
704
|
...this._getAnimationOptions(),
|
|
688
705
|
fill: "none"
|
|
689
|
-
}, path);
|
|
706
|
+
}, path, previousCutout, position, step);
|
|
690
707
|
if (!animation) {
|
|
691
708
|
this.applyStyles(path, finalStyles);
|
|
692
709
|
return;
|
|
@@ -703,39 +720,43 @@ class OverlayElement extends GlowTourElement {
|
|
|
703
720
|
}
|
|
704
721
|
}
|
|
705
722
|
_getNextStyles(position, step) {
|
|
706
|
-
const { padding, radius, color, opacity } = step.overlay || {};
|
|
707
|
-
const path = roundedRectPath(position, viewportDimensions(this.element), {
|
|
708
|
-
padding: padding ?? DEFAULT_OVERLAY_PADDING,
|
|
709
|
-
radius: radius ?? DEFAULT_OVERLAY_RADIUS
|
|
710
|
-
}, this.element);
|
|
711
723
|
return {
|
|
712
|
-
d:
|
|
713
|
-
fill: color,
|
|
714
|
-
opacity: opacity != null ? String(opacity) : 0.7
|
|
724
|
+
d: this._cutoutPath(position, step),
|
|
725
|
+
fill: step.overlay?.color,
|
|
726
|
+
opacity: step.overlay?.opacity != null ? String(step.overlay.opacity) : 0.7
|
|
715
727
|
};
|
|
716
728
|
}
|
|
729
|
+
_cutoutPath(position, step) {
|
|
730
|
+
return `path("${roundedRectPath(position, paintedBoxDimensions(this.element), {
|
|
731
|
+
padding: step.overlay?.padding ?? DEFAULT_OVERLAY_PADDING,
|
|
732
|
+
radius: step.overlay?.radius ?? DEFAULT_OVERLAY_RADIUS
|
|
733
|
+
}, this.element)}")`;
|
|
734
|
+
}
|
|
717
735
|
initializeProps() {
|
|
718
736
|
const el = this.getElement();
|
|
719
737
|
if (!el) {
|
|
720
738
|
return;
|
|
721
739
|
}
|
|
722
|
-
const viewport = viewportDimensions(el);
|
|
723
740
|
for (const [property, value] of Object.entries(OVERLAY_IDLE_STYLE)) {
|
|
724
741
|
el.style.setProperty(property, value);
|
|
725
742
|
}
|
|
726
743
|
for (const [name, value] of Object.entries(OVERLAY_IDLE_ATTRIBUTES)) {
|
|
727
744
|
el.setAttribute(name, value);
|
|
728
745
|
}
|
|
729
|
-
el.
|
|
746
|
+
el.style.setProperty("height", "100lvh");
|
|
747
|
+
this.syncViewBox();
|
|
730
748
|
}
|
|
731
749
|
_getPathElement() {
|
|
732
750
|
return this.element.querySelector("path");
|
|
733
751
|
}
|
|
734
752
|
_release() {
|
|
735
753
|
this.currentTransition = null;
|
|
754
|
+
this._stopGeometryTween();
|
|
736
755
|
this.visualState = null;
|
|
756
|
+
this.cutout = null;
|
|
737
757
|
const path = this._getPathElement();
|
|
738
|
-
path
|
|
758
|
+
if (path)
|
|
759
|
+
this.writePathD(path, null);
|
|
739
760
|
path?.style.removeProperty("fill");
|
|
740
761
|
path?.style.setProperty("opacity", "0");
|
|
741
762
|
this.element.style.setProperty("pointer-events", "none");
|
|
@@ -753,10 +774,15 @@ class OverlayElement extends GlowTourElement {
|
|
|
753
774
|
onTransition(transition);
|
|
754
775
|
else
|
|
755
776
|
transition.catch(() => {});
|
|
756
|
-
} else
|
|
777
|
+
} else {
|
|
757
778
|
this.applyStyles(path, this.getRenderedTargetStyles(path, this._getNextStyles(nextPosition, step)));
|
|
779
|
+
this.cutout = nextPosition;
|
|
780
|
+
}
|
|
758
781
|
this.visualState = nextVisualState;
|
|
759
|
-
|
|
782
|
+
this.syncViewBox();
|
|
783
|
+
}
|
|
784
|
+
syncViewBox() {
|
|
785
|
+
const viewport = paintedBoxDimensions(this.element);
|
|
760
786
|
const viewBox = `0 0 ${viewport.width} ${viewport.height}`;
|
|
761
787
|
if (this.element.getAttribute("viewBox") !== viewBox) {
|
|
762
788
|
this.element.setAttribute("viewBox", viewBox);
|
|
@@ -764,11 +790,80 @@ class OverlayElement extends GlowTourElement {
|
|
|
764
790
|
}
|
|
765
791
|
cancelAnimations() {
|
|
766
792
|
this.currentTransition = null;
|
|
793
|
+
this._stopGeometryTween();
|
|
767
794
|
super.cancelAnimations();
|
|
768
795
|
}
|
|
796
|
+
_startPathAnimation(keyframes, options, path, from, position, step) {
|
|
797
|
+
this._stopGeometryTween();
|
|
798
|
+
if (this.supportsCssPathD())
|
|
799
|
+
return this._startAnimation(keyframes, options, path);
|
|
800
|
+
const animation = this._startAnimation(keyframes.map(({ d: _geometry, ...rest }) => rest), options, path);
|
|
801
|
+
const geometry = keyframes[keyframes.length - 1]?.d;
|
|
802
|
+
if (geometry != null && !(animation && from && this._startGeometryTween(path, from, position, step, animation)))
|
|
803
|
+
this.writePathD(path, String(geometry));
|
|
804
|
+
return animation;
|
|
805
|
+
}
|
|
806
|
+
_startGeometryTween(path, from, to, step, animation) {
|
|
807
|
+
const request = ownerWindow(this.element)?.requestAnimationFrame;
|
|
808
|
+
const effect = animation.effect;
|
|
809
|
+
if (typeof request !== "function" || typeof effect?.getComputedTiming !== "function")
|
|
810
|
+
return false;
|
|
811
|
+
const tween = {};
|
|
812
|
+
const draw = () => {
|
|
813
|
+
if (this.geometryTween !== tween)
|
|
814
|
+
return;
|
|
815
|
+
const progress = effect.getComputedTiming().progress;
|
|
816
|
+
const done = typeof progress !== "number";
|
|
817
|
+
this.cutout = betweenRects(from, to, done ? 1 : progress);
|
|
818
|
+
this.writePathD(path, this._cutoutPath(this.cutout, step));
|
|
819
|
+
if (done)
|
|
820
|
+
this.geometryTween = null;
|
|
821
|
+
else
|
|
822
|
+
request(draw);
|
|
823
|
+
};
|
|
824
|
+
this.geometryTween = tween;
|
|
825
|
+
request(draw);
|
|
826
|
+
return true;
|
|
827
|
+
}
|
|
828
|
+
_stopGeometryTween() {
|
|
829
|
+
this.geometryTween = null;
|
|
830
|
+
}
|
|
831
|
+
supportsCssPathD() {
|
|
832
|
+
if (this.cssPathDSupported === null) {
|
|
833
|
+
const currentWindow = ownerWindow(this.element);
|
|
834
|
+
try {
|
|
835
|
+
this.cssPathDSupported = currentWindow?.CSS?.supports?.("d", 'path("M0 0")') === true;
|
|
836
|
+
} catch {
|
|
837
|
+
this.cssPathDSupported = false;
|
|
838
|
+
}
|
|
839
|
+
}
|
|
840
|
+
return this.cssPathDSupported;
|
|
841
|
+
}
|
|
842
|
+
writePathD(path, value) {
|
|
843
|
+
if (value == null || value === "") {
|
|
844
|
+
path.style.removeProperty("d");
|
|
845
|
+
path.removeAttribute("d");
|
|
846
|
+
return;
|
|
847
|
+
}
|
|
848
|
+
path.style.setProperty("d", value);
|
|
849
|
+
path.setAttribute("d", pathDataFromCssValue(value));
|
|
850
|
+
}
|
|
851
|
+
readPathD(path) {
|
|
852
|
+
const inline = path.style.getPropertyValue("d");
|
|
853
|
+
if (inline)
|
|
854
|
+
return inline;
|
|
855
|
+
const attribute = path.getAttribute("d");
|
|
856
|
+
return attribute ? `path("${attribute}")` : "";
|
|
857
|
+
}
|
|
769
858
|
applyStyles(path, styles) {
|
|
770
859
|
for (const [property, value] of Object.entries(styles)) {
|
|
771
|
-
if (
|
|
860
|
+
if (property === "d") {
|
|
861
|
+
const next = value == null ? null : String(value);
|
|
862
|
+
if (this.readPathD(path) !== (next ?? "")) {
|
|
863
|
+
this._stopGeometryTween();
|
|
864
|
+
this.writePathD(path, next);
|
|
865
|
+
}
|
|
866
|
+
} else if (value == null) {
|
|
772
867
|
if (path.style.getPropertyValue(property))
|
|
773
868
|
path.style.removeProperty(property);
|
|
774
869
|
} else if (path.style.getPropertyValue(property) !== String(value)) {
|
|
@@ -780,6 +875,7 @@ class OverlayElement extends GlowTourElement {
|
|
|
780
875
|
const animation = this.currentTransition;
|
|
781
876
|
if (!animation)
|
|
782
877
|
return;
|
|
878
|
+
this._stopGeometryTween();
|
|
783
879
|
this.applyStyles(path, this.getCurrentRenderedStyles(path));
|
|
784
880
|
this.currentTransition = null;
|
|
785
881
|
this._cancelAnimation(animation);
|
|
@@ -787,7 +883,7 @@ class OverlayElement extends GlowTourElement {
|
|
|
787
883
|
getCurrentRenderedStyles(path) {
|
|
788
884
|
const computed = computedStyle(path);
|
|
789
885
|
return {
|
|
790
|
-
d: computed?.getPropertyValue("d") ||
|
|
886
|
+
d: computed?.getPropertyValue("d") || this.readPathD(path),
|
|
791
887
|
fill: computed?.getPropertyValue("fill") || path.style.getPropertyValue("fill"),
|
|
792
888
|
opacity: computed?.getPropertyValue("opacity") || path.style.getPropertyValue("opacity")
|
|
793
889
|
};
|
|
@@ -822,6 +918,7 @@ class OverlayElement extends GlowTourElement {
|
|
|
822
918
|
return Promise.resolve();
|
|
823
919
|
}
|
|
824
920
|
const finalStyles = this.getRenderedTargetStyles(path, this._getNextStyles(position, step));
|
|
921
|
+
this.cutout = position;
|
|
825
922
|
const opacity = String(finalStyles.opacity ?? "0.7");
|
|
826
923
|
this.applyStyles(path, { ...finalStyles, opacity: "0" });
|
|
827
924
|
const animation = this._startAnimation([{ opacity: "0" }, { opacity }], {
|
|
@@ -836,17 +933,30 @@ class OverlayElement extends GlowTourElement {
|
|
|
836
933
|
if (!path) {
|
|
837
934
|
return Promise.resolve();
|
|
838
935
|
}
|
|
936
|
+
this._stopGeometryTween();
|
|
839
937
|
const animation = this._startAnimation({
|
|
840
938
|
opacity: "0"
|
|
841
939
|
}, { ...this._getAnimationOptions(), fill: "none" }, path);
|
|
842
940
|
if (animation && !await this._waitForAnimation(animation))
|
|
843
941
|
return;
|
|
844
|
-
|
|
942
|
+
this.writePathD(path, null);
|
|
943
|
+
this.cutout = null;
|
|
845
944
|
path.style.removeProperty("fill");
|
|
846
945
|
path.style.setProperty("opacity", "0");
|
|
847
946
|
this.element.style.setProperty("pointer-events", "none");
|
|
848
947
|
}
|
|
849
948
|
}
|
|
949
|
+
function betweenRects(from, to, progress) {
|
|
950
|
+
return {
|
|
951
|
+
height: from.height + (to.height - from.height) * progress,
|
|
952
|
+
left: from.left + (to.left - from.left) * progress,
|
|
953
|
+
top: from.top + (to.top - from.top) * progress,
|
|
954
|
+
width: from.width + (to.width - from.width) * progress
|
|
955
|
+
};
|
|
956
|
+
}
|
|
957
|
+
function pathDataFromCssValue(value) {
|
|
958
|
+
return value.replace(/^path\(\s*(["'])([\s\S]*)\1\s*\)$/, "$2");
|
|
959
|
+
}
|
|
850
960
|
function computedStyle(element) {
|
|
851
961
|
const document2 = element.ownerDocument;
|
|
852
962
|
const currentWindow = document2 && "defaultView" in document2 ? document2.defaultView : ownerWindow(element);
|
package/package.json
CHANGED
package/utils/utils.d.ts
CHANGED
|
@@ -4,10 +4,41 @@ export declare function ownerDocument(element?: Node | null): Document | null;
|
|
|
4
4
|
export declare function isHTMLElement(value: unknown, context?: Node | null): value is HTMLElement;
|
|
5
5
|
export declare function isElement(value: unknown, context?: Node | null): value is Element;
|
|
6
6
|
export declare function isNode(value: unknown, context?: Node | null): value is Node;
|
|
7
|
+
/**
|
|
8
|
+
* The layout viewport, in CSS pixels — the box every `position: fixed` tour
|
|
9
|
+
* element is sized and positioned against, and the frame that
|
|
10
|
+
* `getBoundingClientRect()` reports coordinates in.
|
|
11
|
+
*
|
|
12
|
+
* Deliberately not `innerWidth`/`innerHeight`: those measure the *visual*
|
|
13
|
+
* viewport, which on mobile shrinks and grows with the browser's URL bar and on
|
|
14
|
+
* desktop includes the classic scrollbar. Either gap skews the overlay's
|
|
15
|
+
* `viewBox` against its own `100%`-sized box, and the default
|
|
16
|
+
* `preserveAspectRatio` then scales and centres the backdrop — leaving undimmed
|
|
17
|
+
* bands and a cutout that no longer lines up with its target.
|
|
18
|
+
*/
|
|
7
19
|
export declare function viewportDimensions(context?: Node | null): {
|
|
8
20
|
width: number;
|
|
9
21
|
height: number;
|
|
10
22
|
};
|
|
23
|
+
/**
|
|
24
|
+
* The box the overlay `<svg>` is actually painted into, in CSS pixels.
|
|
25
|
+
*
|
|
26
|
+
* The overlay is `position: fixed` and sized to `100%`, and its `viewBox` has
|
|
27
|
+
* to match that box exactly. Any mismatch makes the SVG scale its contents, and
|
|
28
|
+
* `preserveAspectRatio` then either letterboxes the backdrop — the undimmed
|
|
29
|
+
* bands mobile browsers show once a retracting URL bar moves the layout
|
|
30
|
+
* viewport out of step with the initial containing block, which is what sizes
|
|
31
|
+
* the element — or slices the cutout away from its target.
|
|
32
|
+
*
|
|
33
|
+
* Measuring the element sidesteps the question of which of the two each engine
|
|
34
|
+
* resizes: whatever box it gave the overlay is the box the overlay draws in.
|
|
35
|
+
* {@link viewportDimensions} stays the fallback for an element that has no box
|
|
36
|
+
* yet — detached nodes, server-rendered markup, test doubles.
|
|
37
|
+
*/
|
|
38
|
+
export declare function paintedBoxDimensions(element?: Element | null): {
|
|
39
|
+
width: number;
|
|
40
|
+
height: number;
|
|
41
|
+
};
|
|
11
42
|
export declare function isInViewport(rect: {
|
|
12
43
|
left: number;
|
|
13
44
|
top: number;
|
|
@@ -15,7 +46,9 @@ export declare function isInViewport(rect: {
|
|
|
15
46
|
bottom: number;
|
|
16
47
|
}, context?: Node | null): boolean;
|
|
17
48
|
export declare function roundByDPR(value: number, context?: Node | null): number;
|
|
18
|
-
|
|
49
|
+
/** The only part of a `DOMRect` the cutout geometry reads. */
|
|
50
|
+
export type RectGeometry = Pick<DOMRect, "height" | "left" | "top" | "width">;
|
|
51
|
+
export declare function roundedRectPath(rect: RectGeometry, viewport: {
|
|
19
52
|
width: number;
|
|
20
53
|
height: number;
|
|
21
54
|
}, options: {
|