@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 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
- /** Idle attributes for the overlay `<svg>` element. */
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,
@@ -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
- if (!path.style.getPropertyValue("d")) {
652
- for (const [property, value] of Object.entries(keyframe)) {
653
- if (property !== "opacity" && value != null) {
654
- path.style.setProperty(property, String(value));
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: path.style.getPropertyValue("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._startAnimation([baseStyle, keyframe], {
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._startAnimation([from, finalStyles], {
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: `path("${path}")`,
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.setAttribute("viewBox", `0 0 ${viewport.width} ${viewport.height}`);
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?.style.removeProperty("d");
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
- const viewport = viewportDimensions(this.element);
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 (value == null) {
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") || path.style.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
- path.style.removeProperty("d");
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@glowhop/core-tour",
3
- "version": "1.0.0",
3
+ "version": "1.0.2",
4
4
  "description": "Framework-agnostic tour controller and DOM runtime for GlowTour.js.",
5
5
  "license": "MIT",
6
6
  "homepage": "https://github.com/Glowhop/GlowTour.js#readme",
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
- export declare function roundedRectPath(rect: DOMRect, viewport: {
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: {