@robertlinde/react-tour-kit 0.8.9 → 1.0.1
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 +48 -0
- package/MIGRATION.md +205 -0
- package/README.md +401 -850
- package/dist/react-native-platform/hooks/use-tour-scroll-view.hook.d.ts +14 -0
- package/dist/react-native-platform/hooks/use-tour-scroll-view.hook.d.ts.map +1 -0
- package/dist/react-native-platform/hooks/use-tour-scroll-view.hook.js +21 -0
- package/dist/react-native-platform/hooks/use-tour-scroll-view.hook.js.map +1 -0
- package/dist/react-native-platform/hooks/use-tour-target.hook.d.ts +15 -0
- package/dist/react-native-platform/hooks/use-tour-target.hook.d.ts.map +1 -0
- package/dist/react-native-platform/hooks/use-tour-target.hook.js +28 -0
- package/dist/react-native-platform/hooks/use-tour-target.hook.js.map +1 -0
- package/dist/react-native-platform/host-store.d.ts +24 -0
- package/dist/react-native-platform/host-store.d.ts.map +1 -0
- package/dist/react-native-platform/host-store.js +63 -0
- package/dist/react-native-platform/host-store.js.map +1 -0
- package/dist/react-native-platform/insets.util.d.ts +11 -0
- package/dist/react-native-platform/insets.util.d.ts.map +1 -0
- package/dist/react-native-platform/insets.util.js +31 -0
- package/dist/react-native-platform/insets.util.js.map +1 -0
- package/dist/react-native-platform/layer/native-renderer.d.ts +7 -0
- package/dist/react-native-platform/layer/native-renderer.d.ts.map +1 -0
- package/dist/react-native-platform/layer/native-renderer.js +496 -0
- package/dist/react-native-platform/layer/native-renderer.js.map +1 -0
- package/dist/react-native-platform/layer/tour-host.d.ts +13 -0
- package/dist/react-native-platform/layer/tour-host.d.ts.map +1 -0
- package/dist/react-native-platform/layer/tour-host.js +70 -0
- package/dist/react-native-platform/layer/tour-host.js.map +1 -0
- package/dist/react-native-platform/measure.util.d.ts +20 -0
- package/dist/react-native-platform/measure.util.d.ts.map +1 -0
- package/dist/react-native-platform/measure.util.js +100 -0
- package/dist/react-native-platform/measure.util.js.map +1 -0
- package/dist/react-native-platform/native-context.d.ts +12 -0
- package/dist/react-native-platform/native-context.d.ts.map +1 -0
- package/dist/react-native-platform/native-context.js +4 -0
- package/dist/react-native-platform/native-context.js.map +1 -0
- package/dist/react-native-platform/native-environment.d.ts +14 -0
- package/dist/react-native-platform/native-environment.d.ts.map +1 -0
- package/dist/react-native-platform/native-environment.js +20 -0
- package/dist/react-native-platform/native-environment.js.map +1 -0
- package/dist/react-native-platform/native-platform.d.ts +19 -0
- package/dist/react-native-platform/native-platform.d.ts.map +1 -0
- package/dist/react-native-platform/native-platform.js +265 -0
- package/dist/react-native-platform/native-platform.js.map +1 -0
- package/dist/react-native-platform/reduced-motion.d.ts +4 -0
- package/dist/react-native-platform/reduced-motion.d.ts.map +1 -0
- package/dist/react-native-platform/reduced-motion.js +44 -0
- package/dist/react-native-platform/reduced-motion.js.map +1 -0
- package/dist/react-native-platform/registry.d.ts +18 -0
- package/dist/react-native-platform/registry.d.ts.map +1 -0
- package/dist/react-native-platform/registry.js +51 -0
- package/dist/react-native-platform/registry.js.map +1 -0
- package/dist/react-native-platform/tour-provider.d.ts +16 -3
- package/dist/react-native-platform/tour-provider.d.ts.map +1 -0
- package/dist/react-native-platform/tour-provider.js +92 -211
- package/dist/react-native-platform/tour-provider.js.map +1 -1
- package/dist/react-native.d.ts +14 -10
- package/dist/react-native.d.ts.map +1 -0
- package/dist/react-native.js +7 -7
- package/dist/react-native.js.map +1 -1
- package/dist/react-platform/dom/elements.util.d.ts +35 -0
- package/dist/react-platform/dom/elements.util.d.ts.map +1 -0
- package/dist/react-platform/dom/elements.util.js +155 -0
- package/dist/react-platform/dom/elements.util.js.map +1 -0
- package/dist/react-platform/dom/occluders.util.d.ts +13 -0
- package/dist/react-platform/dom/occluders.util.d.ts.map +1 -0
- package/dist/react-platform/dom/occluders.util.js +75 -0
- package/dist/react-platform/dom/occluders.util.js.map +1 -0
- package/dist/react-platform/dom/scroll-lock.util.d.ts +13 -0
- package/dist/react-platform/dom/scroll-lock.util.d.ts.map +1 -0
- package/dist/react-platform/dom/scroll-lock.util.js +219 -0
- package/dist/react-platform/dom/scroll-lock.util.js.map +1 -0
- package/dist/react-platform/dom/scroll.util.d.ts +44 -0
- package/dist/react-platform/dom/scroll.util.d.ts.map +1 -0
- package/dist/react-platform/dom/scroll.util.js +250 -0
- package/dist/react-platform/dom/scroll.util.js.map +1 -0
- package/dist/react-platform/dom/viewport.util.d.ts +16 -0
- package/dist/react-platform/dom/viewport.util.d.ts.map +1 -0
- package/dist/react-platform/dom/viewport.util.js +65 -0
- package/dist/react-platform/dom/viewport.util.js.map +1 -0
- package/dist/react-platform/dom/web-platform.d.ts +15 -0
- package/dist/react-platform/dom/web-platform.d.ts.map +1 -0
- package/dist/react-platform/dom/web-platform.js +169 -0
- package/dist/react-platform/dom/web-platform.js.map +1 -0
- package/dist/react-platform/layer/styles.d.ts +12 -0
- package/dist/react-platform/layer/styles.d.ts.map +1 -0
- package/dist/react-platform/layer/styles.js +285 -0
- package/dist/react-platform/layer/styles.js.map +1 -0
- package/dist/react-platform/layer/web-renderer.d.ts +4 -0
- package/dist/react-platform/layer/web-renderer.d.ts.map +1 -0
- package/dist/react-platform/layer/web-renderer.js +390 -0
- package/dist/react-platform/layer/web-renderer.js.map +1 -0
- package/dist/react-platform/tour-provider.d.ts +9 -3
- package/dist/react-platform/tour-provider.d.ts.map +1 -0
- package/dist/react-platform/tour-provider.js +28 -218
- package/dist/react-platform/tour-provider.js.map +1 -1
- package/dist/react.d.ts +10 -10
- package/dist/react.d.ts.map +1 -0
- package/dist/react.js +5 -7
- package/dist/react.js.map +1 -1
- package/dist/shared/engine/abort.util.d.ts +17 -0
- package/dist/shared/engine/abort.util.d.ts.map +1 -0
- package/dist/shared/engine/abort.util.js +72 -0
- package/dist/shared/engine/abort.util.js.map +1 -0
- package/dist/shared/engine/report.util.d.ts +12 -0
- package/dist/shared/engine/report.util.d.ts.map +1 -0
- package/dist/shared/engine/report.util.js +51 -0
- package/dist/shared/engine/report.util.js.map +1 -0
- package/dist/shared/engine/tour-adapter.type.d.ts +67 -0
- package/dist/shared/engine/tour-adapter.type.d.ts.map +1 -0
- package/dist/shared/engine/tour-adapter.type.js +2 -0
- package/dist/shared/engine/tour-adapter.type.js.map +1 -0
- package/dist/shared/engine/tour-engine.d.ts +101 -0
- package/dist/shared/engine/tour-engine.d.ts.map +1 -0
- package/dist/shared/engine/tour-engine.js +835 -0
- package/dist/shared/engine/tour-engine.js.map +1 -0
- package/dist/shared/geometry/box.util.d.ts +39 -0
- package/dist/shared/geometry/box.util.d.ts.map +1 -0
- package/dist/shared/geometry/box.util.js +146 -0
- package/dist/shared/geometry/box.util.js.map +1 -0
- package/dist/shared/geometry/compute-tooltip-layout.util.d.ts +60 -0
- package/dist/shared/geometry/compute-tooltip-layout.util.d.ts.map +1 -0
- package/dist/shared/geometry/compute-tooltip-layout.util.js +148 -0
- package/dist/shared/geometry/compute-tooltip-layout.util.js.map +1 -0
- package/dist/shared/geometry/geometry.type.d.ts +34 -0
- package/dist/shared/geometry/geometry.type.d.ts.map +1 -0
- package/dist/shared/geometry/geometry.type.js +2 -0
- package/dist/shared/geometry/geometry.type.js.map +1 -0
- package/dist/shared/geometry/reveal.util.d.ts +38 -0
- package/dist/shared/geometry/reveal.util.d.ts.map +1 -0
- package/dist/shared/geometry/reveal.util.js +135 -0
- package/dist/shared/geometry/reveal.util.js.map +1 -0
- package/dist/shared/geometry/target-visibility.util.d.ts +10 -0
- package/dist/shared/geometry/target-visibility.util.d.ts.map +1 -0
- package/dist/shared/geometry/target-visibility.util.js +29 -0
- package/dist/shared/geometry/target-visibility.util.js.map +1 -0
- package/dist/shared/platform/create-tour-adapter.d.ts +9 -0
- package/dist/shared/platform/create-tour-adapter.d.ts.map +1 -0
- package/dist/shared/platform/create-tour-adapter.js +145 -0
- package/dist/shared/platform/create-tour-adapter.js.map +1 -0
- package/dist/shared/platform/platform.type.d.ts +77 -0
- package/dist/shared/platform/platform.type.d.ts.map +1 -0
- package/dist/shared/platform/platform.type.js +2 -0
- package/dist/shared/platform/platform.type.js.map +1 -0
- package/dist/shared/platform/reveal-in-scroll-containers.d.ts +27 -0
- package/dist/shared/platform/reveal-in-scroll-containers.d.ts.map +1 -0
- package/dist/shared/platform/reveal-in-scroll-containers.js +49 -0
- package/dist/shared/platform/reveal-in-scroll-containers.js.map +1 -0
- package/dist/shared/react/tour-context.d.ts +7 -0
- package/dist/shared/react/tour-context.d.ts.map +1 -0
- package/dist/shared/react/tour-context.js +23 -0
- package/dist/shared/react/tour-context.js.map +1 -0
- package/dist/shared/react/tour-provider-core.d.ts +41 -0
- package/dist/shared/react/tour-provider-core.d.ts.map +1 -0
- package/dist/shared/react/tour-provider-core.js +24 -0
- package/dist/shared/react/tour-provider-core.js.map +1 -0
- package/dist/shared/react/tour-root.d.ts +14 -0
- package/dist/shared/react/tour-root.d.ts.map +1 -0
- package/dist/shared/react/tour-root.js +22 -0
- package/dist/shared/react/tour-root.js.map +1 -0
- package/dist/shared/react/use-isomorphic-layout-effect.hook.d.ts +4 -0
- package/dist/shared/react/use-isomorphic-layout-effect.hook.d.ts.map +1 -0
- package/dist/shared/react/use-isomorphic-layout-effect.hook.js +5 -0
- package/dist/shared/react/use-isomorphic-layout-effect.hook.js.map +1 -0
- package/dist/shared/react/use-tour-view.hook.d.ts +5 -0
- package/dist/shared/react/use-tour-view.hook.d.ts.map +1 -0
- package/dist/shared/react/use-tour-view.hook.js +14 -0
- package/dist/shared/react/use-tour-view.hook.js.map +1 -0
- package/dist/shared/react/use-tour.hook.d.ts +7 -0
- package/dist/shared/react/use-tour.hook.d.ts.map +1 -0
- package/dist/shared/react/use-tour.hook.js +10 -0
- package/dist/shared/react/use-tour.hook.js.map +1 -0
- package/dist/shared/renderer/default-tooltip.d.ts +5 -0
- package/dist/shared/renderer/default-tooltip.d.ts.map +1 -0
- package/dist/shared/renderer/default-tooltip.js +13 -0
- package/dist/shared/renderer/default-tooltip.js.map +1 -0
- package/dist/shared/renderer/motion.d.ts +29 -0
- package/dist/shared/renderer/motion.d.ts.map +1 -0
- package/dist/shared/renderer/motion.js +49 -0
- package/dist/shared/renderer/motion.js.map +1 -0
- package/dist/shared/renderer/renderer-context.d.ts +17 -0
- package/dist/shared/renderer/renderer-context.d.ts.map +1 -0
- package/dist/shared/renderer/renderer-context.js +16 -0
- package/dist/shared/renderer/renderer-context.js.map +1 -0
- package/dist/shared/renderer/renderer.type.d.ts +164 -0
- package/dist/shared/renderer/renderer.type.d.ts.map +1 -0
- package/dist/shared/renderer/renderer.type.js +2 -0
- package/dist/shared/renderer/renderer.type.js.map +1 -0
- package/dist/shared/renderer/tooltip-model.d.ts +32 -0
- package/dist/shared/renderer/tooltip-model.d.ts.map +1 -0
- package/dist/shared/renderer/tooltip-model.js +24 -0
- package/dist/shared/renderer/tooltip-model.js.map +1 -0
- package/dist/shared/renderer/tour-overlay.d.ts +24 -0
- package/dist/shared/renderer/tour-overlay.d.ts.map +1 -0
- package/dist/shared/renderer/tour-overlay.js +228 -0
- package/dist/shared/renderer/tour-overlay.js.map +1 -0
- package/dist/shared/types/tour-context.type.d.ts +21 -11
- package/dist/shared/types/tour-context.type.d.ts.map +1 -0
- package/dist/shared/types/tour-end-info.type.d.ts +19 -2
- package/dist/shared/types/tour-end-info.type.d.ts.map +1 -0
- package/dist/shared/types/tour-i18n.type.d.ts +8 -0
- package/dist/shared/types/tour-i18n.type.d.ts.map +1 -0
- package/dist/shared/types/tour-step.type.d.ts +35 -6
- package/dist/shared/types/tour-step.type.d.ts.map +1 -0
- package/dist/shared/types/tour-theme.type.d.ts +22 -2
- package/dist/shared/types/tour-theme.type.d.ts.map +1 -0
- package/dist/shared/types/tour-theme.type.js +1 -16
- package/dist/shared/types/tour-theme.type.js.map +1 -1
- package/dist/shared/types/tour-tooltip-props.type.d.ts +21 -17
- package/dist/shared/types/tour-tooltip-props.type.d.ts.map +1 -0
- package/dist/shared/utils/frame.util.d.ts +6 -0
- package/dist/shared/utils/frame.util.d.ts.map +1 -0
- package/dist/shared/utils/frame.util.js +20 -0
- package/dist/shared/utils/frame.util.js.map +1 -0
- package/dist/shared/utils/get-truncated-dots.util.d.ts +13 -7
- package/dist/shared/utils/get-truncated-dots.util.d.ts.map +1 -0
- package/dist/shared/utils/get-truncated-dots.util.js +30 -30
- package/dist/shared/utils/get-truncated-dots.util.js.map +1 -1
- package/dist/shared/utils/i18n.util.d.ts +7 -0
- package/dist/shared/utils/i18n.util.d.ts.map +1 -0
- package/dist/shared/utils/i18n.util.js +34 -0
- package/dist/shared/utils/i18n.util.js.map +1 -0
- package/dist/shared/utils/theme.util.d.ts +6 -0
- package/dist/shared/utils/theme.util.d.ts.map +1 -0
- package/dist/shared/utils/theme.util.js +31 -0
- package/dist/shared/utils/theme.util.js.map +1 -0
- package/package.json +72 -55
- package/src/react-native-platform/hooks/use-tour-scroll-view.hook.ts +24 -0
- package/src/react-native-platform/hooks/use-tour-target.hook.ts +30 -0
- package/src/react-native-platform/host-store.ts +92 -0
- package/src/react-native-platform/insets.util.ts +39 -0
- package/src/react-native-platform/layer/native-renderer.tsx +776 -0
- package/src/react-native-platform/layer/tour-host.tsx +105 -0
- package/src/react-native-platform/measure.util.ts +140 -0
- package/src/react-native-platform/native-context.ts +15 -0
- package/src/react-native-platform/native-environment.ts +33 -0
- package/src/react-native-platform/native-platform.ts +368 -0
- package/src/react-native-platform/reduced-motion.ts +47 -0
- package/src/react-native-platform/registry.ts +70 -0
- package/src/react-native-platform/tour-provider.tsx +148 -0
- package/src/react-native.ts +15 -0
- package/src/react-platform/dom/elements.util.ts +191 -0
- package/src/react-platform/dom/occluders.util.ts +88 -0
- package/src/react-platform/dom/scroll-lock.util.ts +261 -0
- package/src/react-platform/dom/scroll.util.ts +310 -0
- package/src/react-platform/dom/viewport.util.ts +74 -0
- package/src/react-platform/dom/web-platform.ts +208 -0
- package/src/react-platform/layer/styles.ts +286 -0
- package/src/react-platform/layer/web-renderer.tsx +594 -0
- package/src/react-platform/tour-provider.tsx +44 -0
- package/src/react.ts +13 -0
- package/src/shared/engine/abort.util.ts +84 -0
- package/src/shared/engine/report.util.ts +58 -0
- package/src/shared/engine/tour-adapter.type.ts +73 -0
- package/src/shared/engine/tour-engine.ts +1147 -0
- package/src/shared/geometry/box.util.ts +185 -0
- package/src/shared/geometry/compute-tooltip-layout.util.ts +238 -0
- package/src/shared/geometry/geometry.type.ts +38 -0
- package/src/shared/geometry/reveal.util.ts +190 -0
- package/src/shared/geometry/target-visibility.util.ts +44 -0
- package/src/shared/platform/create-tour-adapter.ts +169 -0
- package/src/shared/platform/platform.type.ts +81 -0
- package/src/shared/platform/reveal-in-scroll-containers.ts +78 -0
- package/src/shared/react/tour-context.ts +28 -0
- package/src/shared/react/tour-provider-core.ts +67 -0
- package/src/shared/react/tour-root.tsx +34 -0
- package/src/shared/react/use-isomorphic-layout-effect.hook.ts +7 -0
- package/src/shared/react/use-tour-view.hook.ts +17 -0
- package/src/shared/react/use-tour.hook.ts +11 -0
- package/src/shared/renderer/default-tooltip.tsx +52 -0
- package/src/shared/renderer/motion.ts +59 -0
- package/src/shared/renderer/renderer-context.ts +33 -0
- package/src/shared/renderer/renderer.type.ts +183 -0
- package/src/shared/renderer/tooltip-model.ts +44 -0
- package/src/shared/renderer/tour-overlay.tsx +453 -0
- package/src/shared/types/tour-context.type.ts +23 -0
- package/src/shared/types/tour-end-info.type.ts +32 -0
- package/src/shared/types/tour-i18n.type.ts +15 -0
- package/src/shared/types/tour-step.type.ts +40 -0
- package/src/shared/types/tour-theme.type.ts +28 -0
- package/src/shared/types/tour-tooltip-props.type.ts +27 -0
- package/src/shared/utils/frame.util.ts +21 -0
- package/src/shared/utils/get-truncated-dots.util.ts +46 -0
- package/src/shared/utils/i18n.util.ts +44 -0
- package/src/shared/utils/theme.util.ts +38 -0
- package/dist/react-native-platform/components/index.d.ts +0 -2
- package/dist/react-native-platform/components/index.js +0 -3
- package/dist/react-native-platform/components/index.js.map +0 -1
- package/dist/react-native-platform/components/tour-overlay.d.ts +0 -3
- package/dist/react-native-platform/components/tour-overlay.js +0 -26
- package/dist/react-native-platform/components/tour-overlay.js.map +0 -1
- package/dist/react-native-platform/components/tour-tooltip.d.ts +0 -3
- package/dist/react-native-platform/components/tour-tooltip.js +0 -167
- package/dist/react-native-platform/components/tour-tooltip.js.map +0 -1
- package/dist/react-native-platform/hooks/index.d.ts +0 -1
- package/dist/react-native-platform/hooks/index.js +0 -2
- package/dist/react-native-platform/hooks/index.js.map +0 -1
- package/dist/react-native-platform/hooks/use-tour-target.d.ts +0 -2
- package/dist/react-native-platform/hooks/use-tour-target.js +0 -13
- package/dist/react-native-platform/hooks/use-tour-target.js.map +0 -1
- package/dist/react-native-platform/index.d.ts +0 -7
- package/dist/react-native-platform/index.js +0 -7
- package/dist/react-native-platform/index.js.map +0 -1
- package/dist/react-native-platform/platform-adapter.d.ts +0 -2
- package/dist/react-native-platform/platform-adapter.js +0 -84
- package/dist/react-native-platform/platform-adapter.js.map +0 -1
- package/dist/react-native-platform/target-registry.d.ts +0 -10
- package/dist/react-native-platform/target-registry.js +0 -17
- package/dist/react-native-platform/target-registry.js.map +0 -1
- package/dist/react-native-platform/types/tour-provider.props.type.d.ts +0 -12
- package/dist/react-native-platform/types/tour-provider.props.type.js +0 -2
- package/dist/react-native-platform/types/tour-provider.props.type.js.map +0 -1
- package/dist/react-platform/components/index.d.ts +0 -2
- package/dist/react-platform/components/index.js +0 -3
- package/dist/react-platform/components/index.js.map +0 -1
- package/dist/react-platform/components/tour-overlay.d.ts +0 -3
- package/dist/react-platform/components/tour-overlay.js +0 -29
- package/dist/react-platform/components/tour-overlay.js.map +0 -1
- package/dist/react-platform/components/tour-tooltip.d.ts +0 -2
- package/dist/react-platform/components/tour-tooltip.js +0 -155
- package/dist/react-platform/components/tour-tooltip.js.map +0 -1
- package/dist/react-platform/index.d.ts +0 -6
- package/dist/react-platform/index.js +0 -6
- package/dist/react-platform/index.js.map +0 -1
- package/dist/react-platform/platform-adapter.d.ts +0 -2
- package/dist/react-platform/platform-adapter.js +0 -101
- package/dist/react-platform/platform-adapter.js.map +0 -1
- package/dist/react-platform/types/tour-provider.props.type.d.ts +0 -12
- package/dist/react-platform/types/tour-provider.props.type.js +0 -2
- package/dist/react-platform/types/tour-provider.props.type.js.map +0 -1
- package/dist/react-platform/utils/find-visible-element.util.d.ts +0 -1
- package/dist/react-platform/utils/find-visible-element.util.js +0 -11
- package/dist/react-platform/utils/find-visible-element.util.js.map +0 -1
- package/dist/react-platform/utils/index.d.ts +0 -1
- package/dist/react-platform/utils/index.js +0 -2
- package/dist/react-platform/utils/index.js.map +0 -1
- package/dist/shared/context/index.d.ts +0 -1
- package/dist/shared/context/index.js +0 -2
- package/dist/shared/context/index.js.map +0 -1
- package/dist/shared/context/tour-context.d.ts +0 -3
- package/dist/shared/context/tour-context.js +0 -14
- package/dist/shared/context/tour-context.js.map +0 -1
- package/dist/shared/hooks/index.d.ts +0 -1
- package/dist/shared/hooks/index.js +0 -2
- package/dist/shared/hooks/index.js.map +0 -1
- package/dist/shared/hooks/use-tour.hook.d.ts +0 -2
- package/dist/shared/hooks/use-tour.hook.js +0 -7
- package/dist/shared/hooks/use-tour.hook.js.map +0 -1
- package/dist/shared/index.d.ts +0 -7
- package/dist/shared/index.js +0 -6
- package/dist/shared/index.js.map +0 -1
- package/dist/shared/types/index.d.ts +0 -10
- package/dist/shared/types/index.js +0 -2
- package/dist/shared/types/index.js.map +0 -1
- package/dist/shared/types/platform-adapter.type.d.ts +0 -24
- package/dist/shared/types/platform-adapter.type.js +0 -2
- package/dist/shared/types/platform-adapter.type.js.map +0 -1
- package/dist/shared/types/rect.type.d.ts +0 -11
- package/dist/shared/types/rect.type.js +0 -2
- package/dist/shared/types/rect.type.js.map +0 -1
- package/dist/shared/types/tour-overlay-props.type.d.ts +0 -8
- package/dist/shared/types/tour-overlay-props.type.js +0 -2
- package/dist/shared/types/tour-overlay-props.type.js.map +0 -1
- package/dist/shared/utils/calculate-tooltip-position.util.d.ts +0 -14
- package/dist/shared/utils/calculate-tooltip-position.util.js +0 -49
- package/dist/shared/utils/calculate-tooltip-position.util.js.map +0 -1
- package/dist/shared/utils/index.d.ts +0 -3
- package/dist/shared/utils/index.js +0 -3
- package/dist/shared/utils/index.js.map +0 -1
|
@@ -0,0 +1,261 @@
|
|
|
1
|
+
import {handlesKey, hasLayout} from './elements.util.js';
|
|
2
|
+
import {readPageOverflow} from './scroll.util.js';
|
|
3
|
+
|
|
4
|
+
function canScroll(element: Element, deltaX: number, deltaY: number): boolean {
|
|
5
|
+
const style = getComputedStyle(element);
|
|
6
|
+
const scrollsY = /auto|scroll|overlay/u.test(style.overflowY) && element.scrollHeight > element.clientHeight;
|
|
7
|
+
const scrollsX = /auto|scroll|overlay/u.test(style.overflowX) && element.scrollWidth > element.clientWidth;
|
|
8
|
+
if (scrollsY && deltaY !== 0) {
|
|
9
|
+
const atTop = element.scrollTop <= 0;
|
|
10
|
+
const atBottom = element.scrollTop + element.clientHeight >= element.scrollHeight - 1;
|
|
11
|
+
if ((deltaY < 0 && !atTop) || (deltaY > 0 && !atBottom)) {
|
|
12
|
+
return true;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
if (scrollsX && deltaX !== 0) {
|
|
17
|
+
const max = element.scrollWidth - element.clientWidth;
|
|
18
|
+
const position = Math.abs(element.scrollLeft);
|
|
19
|
+
if ((deltaX < 0 && position > 0) || (deltaX > 0 && position < max - 1)) {
|
|
20
|
+
return true;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
return false;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/** True when `from` or one of its ancestors (up to `boundary`) can scroll further in the given direction. */
|
|
28
|
+
function canScrollWithin(from: EventTarget | null, boundary: Element, deltaX: number, deltaY: number): boolean {
|
|
29
|
+
for (
|
|
30
|
+
let element = from instanceof Element ? from : null;
|
|
31
|
+
element && element !== boundary;
|
|
32
|
+
element = element.parentElement
|
|
33
|
+
) {
|
|
34
|
+
if (canScroll(element, deltaX, deltaY)) {
|
|
35
|
+
return true;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
return false;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* The scrollable part of the tooltip (the built-in card's body, or a custom tooltip that does not
|
|
44
|
+
* fit) that can scroll in the given direction, when the focus is on the tooltip itself: browsers
|
|
45
|
+
* only scroll the focused element's ancestors with the keyboard, never its descendants.
|
|
46
|
+
*/
|
|
47
|
+
function scrollableInTooltip(from: EventTarget | null, layer: Element, deltaY: number): Element | undefined {
|
|
48
|
+
const frame =
|
|
49
|
+
from === layer
|
|
50
|
+
? layer.querySelector('[data-rtk-tooltip]:not([data-hidden])')
|
|
51
|
+
: from instanceof Element
|
|
52
|
+
? from.closest('[data-rtk-tooltip]')
|
|
53
|
+
: null;
|
|
54
|
+
if (!frame || !(from instanceof Element) || (from !== layer && !frame.contains(from))) {
|
|
55
|
+
return undefined;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
return [frame, ...frame.querySelectorAll('*')].find((element) => canScroll(element, 0, deltaY));
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/** How far each scrolling key scrolls: pixels, or `page` / `end`. */
|
|
62
|
+
function keyScroll(event: KeyboardEvent): {readonly direction: -1 | 1; readonly amount: number | 'page' | 'end'} {
|
|
63
|
+
// Ctrl/Cmd with an arrow goes all the way.
|
|
64
|
+
const arrowAmount = event.ctrlKey || event.metaKey ? 'end' : 40;
|
|
65
|
+
switch (event.key) {
|
|
66
|
+
case 'ArrowUp': {
|
|
67
|
+
return {direction: -1, amount: arrowAmount};
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
case 'ArrowDown': {
|
|
71
|
+
return {direction: 1, amount: arrowAmount};
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
case 'PageUp': {
|
|
75
|
+
return {direction: -1, amount: 'page'};
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
case 'Home': {
|
|
79
|
+
return {direction: -1, amount: 'end'};
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
case 'End': {
|
|
83
|
+
return {direction: 1, amount: 'end'};
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
default: {
|
|
87
|
+
// PageDown, and the space bar (with Shift: up).
|
|
88
|
+
return {direction: event.key === ' ' && event.shiftKey ? -1 : 1, amount: 'page'};
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function prefersReducedMotion(): boolean {
|
|
94
|
+
return (
|
|
95
|
+
typeof globalThis.matchMedia === 'function' && globalThis.matchMedia('(prefers-reduced-motion: reduce)').matches
|
|
96
|
+
);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const scrollKeys = new Set(['PageUp', 'PageDown', 'Home', 'End', 'ArrowUp', 'ArrowDown', ' ']);
|
|
100
|
+
|
|
101
|
+
/** Where smooth keyboard scrolls are heading, so quick presses add up like the browser's own. */
|
|
102
|
+
const keyScrollDestinations = new WeakMap<Element, {readonly top: number; readonly at: number}>();
|
|
103
|
+
const smoothScrollMs = 400;
|
|
104
|
+
|
|
105
|
+
function scrollByKey(element: Element, direction: -1 | 1, amount: number | 'page' | 'end'): void {
|
|
106
|
+
const max = element.scrollHeight - element.clientHeight;
|
|
107
|
+
const pending = keyScrollDestinations.get(element);
|
|
108
|
+
const from = pending && performance.now() - pending.at < smoothScrollMs ? pending.top : element.scrollTop;
|
|
109
|
+
const distance = amount === 'end' ? max : amount === 'page' ? element.clientHeight * 0.875 : amount;
|
|
110
|
+
const top = Math.min(max, Math.max(0, from + direction * distance));
|
|
111
|
+
const isSmooth = !prefersReducedMotion();
|
|
112
|
+
keyScrollDestinations.set(element, {top, at: performance.now()});
|
|
113
|
+
element.scrollTo({top, behavior: isSmooth ? 'smooth' : 'instant'});
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
type PageLock = {readonly overflow: {readonly x: string; readonly y: string}; readonly release: () => void};
|
|
117
|
+
let pageLock: PageLock | undefined;
|
|
118
|
+
let pageLockUsers = 0;
|
|
119
|
+
|
|
120
|
+
/** The page's own overflow, also while the tour has locked it. */
|
|
121
|
+
export function pageOverflow(): {readonly x: string; readonly y: string} {
|
|
122
|
+
return pageLock?.overflow ?? readPageOverflow();
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Hides the page's scrollbar (`overflow: hidden` on `<html>`), so it cannot be dragged while the
|
|
127
|
+
* tour is open; programmatic scrolling still works. Only where that cannot move anything: with
|
|
128
|
+
* overlay scrollbars, which take no space (macOS, mobile). Hiding a classic scrollbar would widen
|
|
129
|
+
* the page (browsers drop the space of styled scrollbars even with `scrollbar-gutter: stable`), and
|
|
130
|
+
* pages cannot block it (the wheel over it scrolls the page whatever its listeners do), so it stays
|
|
131
|
+
* usable; the tour keeps the target in view when it is used. Pages whose `<body>` has an overflow of
|
|
132
|
+
* its own, and pinch-zoomed pages, are left alone too.
|
|
133
|
+
*/
|
|
134
|
+
function lockPage(): PageLock {
|
|
135
|
+
const overflow = readPageOverflow();
|
|
136
|
+
const root = document.documentElement;
|
|
137
|
+
const body = document.body ? getComputedStyle(document.body) : undefined;
|
|
138
|
+
const isRootOverflow =
|
|
139
|
+
getComputedStyle(root).overflowY !== 'visible' ||
|
|
140
|
+
!body ||
|
|
141
|
+
(body.overflowX === 'visible' && body.overflowY === 'visible');
|
|
142
|
+
const hasClassicScrollbar =
|
|
143
|
+
globalThis.innerWidth - root.clientWidth > 0.5 || globalThis.innerHeight - root.clientHeight > 0.5;
|
|
144
|
+
const isZoomed = Math.abs((globalThis.visualViewport?.scale ?? 1) - 1) >= 0.01;
|
|
145
|
+
if (!hasLayout() || !isRootOverflow || hasClassicScrollbar || isZoomed) {
|
|
146
|
+
return {
|
|
147
|
+
overflow,
|
|
148
|
+
release(): void {
|
|
149
|
+
// Nothing was changed.
|
|
150
|
+
},
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
const hadStyle = root.hasAttribute('style');
|
|
155
|
+
const previous = ['overflow-x', 'overflow-y'].map(
|
|
156
|
+
(property) => [property, root.style.getPropertyValue(property), root.style.getPropertyPriority(property)] as const,
|
|
157
|
+
);
|
|
158
|
+
root.style.setProperty('overflow-x', 'hidden');
|
|
159
|
+
root.style.setProperty('overflow-y', 'hidden');
|
|
160
|
+
return {
|
|
161
|
+
overflow,
|
|
162
|
+
release(): void {
|
|
163
|
+
for (const [property, value, priority] of previous) {
|
|
164
|
+
if (value) {
|
|
165
|
+
root.style.setProperty(property, value, priority);
|
|
166
|
+
} else {
|
|
167
|
+
root.style.removeProperty(property);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
if (!hadStyle && !root.getAttribute('style')) {
|
|
172
|
+
root.removeAttribute('style');
|
|
173
|
+
}
|
|
174
|
+
},
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
function acquirePageLock(): () => void {
|
|
179
|
+
pageLockUsers++;
|
|
180
|
+
pageLock ??= lockPage();
|
|
181
|
+
let isReleased = false;
|
|
182
|
+
return () => {
|
|
183
|
+
if (isReleased) {
|
|
184
|
+
return;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
isReleased = true;
|
|
188
|
+
pageLockUsers--;
|
|
189
|
+
if (pageLockUsers === 0) {
|
|
190
|
+
pageLock?.release();
|
|
191
|
+
pageLock = undefined;
|
|
192
|
+
}
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* Keeps the page from scrolling while the tour is open (wheel, touch, keyboard, overlay scrollbars), so
|
|
198
|
+
* the user cannot scroll the highlighted element out of view. Scrolling inside the tooltip still
|
|
199
|
+
* works, with the keyboard too, and so do keys that controls in the tooltip use themselves.
|
|
200
|
+
* Programmatic scrolling (the tour's own, or the app's) is not affected. Returns the cleanup.
|
|
201
|
+
*/
|
|
202
|
+
export function lockScroll(layer: HTMLElement): () => void {
|
|
203
|
+
let touchX = 0;
|
|
204
|
+
let touchY = 0;
|
|
205
|
+
const releasePage = acquirePageLock();
|
|
206
|
+
const onWheel = (event: WheelEvent): void => {
|
|
207
|
+
if (!event.ctrlKey && !canScrollWithin(event.target, layer, event.deltaX, event.deltaY)) {
|
|
208
|
+
event.preventDefault();
|
|
209
|
+
}
|
|
210
|
+
};
|
|
211
|
+
|
|
212
|
+
const onTouchStart = (event: TouchEvent): void => {
|
|
213
|
+
const touch = event.touches[0];
|
|
214
|
+
if (touch) {
|
|
215
|
+
touchX = touch.clientX;
|
|
216
|
+
touchY = touch.clientY;
|
|
217
|
+
}
|
|
218
|
+
};
|
|
219
|
+
|
|
220
|
+
const onTouchMove = (event: TouchEvent): void => {
|
|
221
|
+
const touch = event.touches[0];
|
|
222
|
+
// Two fingers: pinch-zoom stays available.
|
|
223
|
+
if (!touch || event.touches.length > 1) {
|
|
224
|
+
return;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
if (!canScrollWithin(event.target, layer, touchX - touch.clientX, touchY - touch.clientY)) {
|
|
228
|
+
event.preventDefault();
|
|
229
|
+
}
|
|
230
|
+
};
|
|
231
|
+
|
|
232
|
+
const onKeyDown = (event: KeyboardEvent): void => {
|
|
233
|
+
if (!scrollKeys.has(event.key) || event.defaultPrevented || handlesKey(event.target, event.key)) {
|
|
234
|
+
return;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
const {direction, amount} = keyScroll(event);
|
|
238
|
+
if (canScrollWithin(event.target, layer, 0, direction)) {
|
|
239
|
+
// The browser scrolls the focused element's scrollable ancestor.
|
|
240
|
+
return;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
event.preventDefault();
|
|
244
|
+
const scrollable = scrollableInTooltip(event.target, layer, direction);
|
|
245
|
+
if (scrollable) {
|
|
246
|
+
scrollByKey(scrollable, direction, amount);
|
|
247
|
+
}
|
|
248
|
+
};
|
|
249
|
+
|
|
250
|
+
layer.addEventListener('wheel', onWheel, {passive: false});
|
|
251
|
+
layer.addEventListener('touchstart', onTouchStart, {passive: true});
|
|
252
|
+
layer.addEventListener('touchmove', onTouchMove, {passive: false});
|
|
253
|
+
layer.addEventListener('keydown', onKeyDown);
|
|
254
|
+
return () => {
|
|
255
|
+
releasePage();
|
|
256
|
+
layer.removeEventListener('wheel', onWheel);
|
|
257
|
+
layer.removeEventListener('touchstart', onTouchStart);
|
|
258
|
+
layer.removeEventListener('touchmove', onTouchMove);
|
|
259
|
+
layer.removeEventListener('keydown', onKeyDown);
|
|
260
|
+
};
|
|
261
|
+
}
|
|
@@ -0,0 +1,310 @@
|
|
|
1
|
+
import {deflate, narrowClip} from '../../shared/geometry/box.util.js';
|
|
2
|
+
import type {Box, Insets, Point} from '../../shared/geometry/geometry.type.js';
|
|
3
|
+
import type {ScrollContainer, ScrollContainerMetrics, ScrollOptions} from '../../shared/platform/platform.type.js';
|
|
4
|
+
import {requestFrame} from '../../shared/utils/frame.util.js';
|
|
5
|
+
import {pageDirection, parentOf} from './elements.util.js';
|
|
6
|
+
import {occludersOver} from './occluders.util.js';
|
|
7
|
+
import {readViewport} from './viewport.util.js';
|
|
8
|
+
|
|
9
|
+
const clippingOverflow = new Set(['hidden', 'scroll', 'auto', 'overlay', 'clip']);
|
|
10
|
+
/**
|
|
11
|
+
* Overflow the user can scroll. `hidden` only clips: what it shows is up to the app (carousels,
|
|
12
|
+
* sliders, off-canvas menus), so the tour never scrolls it.
|
|
13
|
+
*/
|
|
14
|
+
const userScrollable = new Set(['scroll', 'auto', 'overlay']);
|
|
15
|
+
|
|
16
|
+
/** Which axes of a scroll container the tour may scroll. */
|
|
17
|
+
export type ScrollAxes = {readonly x: boolean; readonly y: boolean};
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* True for elements whose overflow is the page's (CSS overflow propagation): `<html>`, and `<body>`
|
|
21
|
+
* unless `<html>` has an overflow of its own (then `<body>` is a scroll container like any other).
|
|
22
|
+
*/
|
|
23
|
+
function holdsPageOverflow(element: Element): boolean {
|
|
24
|
+
if (element === document.documentElement) {
|
|
25
|
+
return true;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
return element === document.body && getComputedStyle(document.documentElement).overflowY === 'visible';
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/** The page's own overflow per axis (from `<html>`, or from `<body>` when `<html>` has none). */
|
|
32
|
+
export function readPageOverflow(): {readonly x: string; readonly y: string} {
|
|
33
|
+
const root = getComputedStyle(document.documentElement);
|
|
34
|
+
const style = root.overflowY === 'visible' && document.body ? getComputedStyle(document.body) : root;
|
|
35
|
+
return {x: style.overflowX, y: style.overflowY};
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function isSet(value: string): boolean {
|
|
39
|
+
return value !== '' && value !== 'none';
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* True when an element with `style` is the containing block of `position: absolute` (or `fixed`)
|
|
44
|
+
* descendants: their position, clipping and scrolling come from it, not from the elements between.
|
|
45
|
+
*/
|
|
46
|
+
function isContainingBlock(style: CSSStyleDeclaration, of: 'absolute' | 'fixed'): boolean {
|
|
47
|
+
if (of === 'absolute' && style.position !== 'static') {
|
|
48
|
+
return true;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
return (
|
|
52
|
+
['transform', 'translate', 'rotate', 'scale', 'perspective', 'filter', 'backdrop-filter'].some((property) =>
|
|
53
|
+
isSet(style.getPropertyValue(property)),
|
|
54
|
+
) ||
|
|
55
|
+
/layout|paint|strict|content/u.test(style.getPropertyValue('contain')) ||
|
|
56
|
+
/transform|translate|rotate|scale|perspective|filter/u.test(style.getPropertyValue('will-change')) ||
|
|
57
|
+
!['', 'normal'].includes(style.getPropertyValue('container-type')) ||
|
|
58
|
+
style.getPropertyValue('content-visibility') === 'auto'
|
|
59
|
+
);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function escapeOf(position: string): 'absolute' | 'fixed' | undefined {
|
|
63
|
+
return position === 'absolute' || position === 'fixed' ? position : undefined;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function paddingBox(element: Element): Box {
|
|
67
|
+
const rect = element.getBoundingClientRect();
|
|
68
|
+
return {
|
|
69
|
+
x: rect.left + element.clientLeft,
|
|
70
|
+
y: rect.top + element.clientTop,
|
|
71
|
+
width: element.clientWidth,
|
|
72
|
+
height: element.clientHeight,
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/** A computed length (`20px`, `10%`, `calc(10% + 20px)`) in pixels; percentages refer to `basis`. */
|
|
77
|
+
export function resolveLength(value: string, basis: number): number {
|
|
78
|
+
const expression = value.startsWith('calc(') && value.endsWith(')') ? value.slice(5, -1) : value;
|
|
79
|
+
let total = 0;
|
|
80
|
+
let sign = 1;
|
|
81
|
+
for (const token of expression.trim().split(/\s+/u)) {
|
|
82
|
+
if (token === '+' || token === '-') {
|
|
83
|
+
sign = token === '-' ? -1 : 1;
|
|
84
|
+
continue;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const match = /^(-?[\d.]+(?:e[+-]?\d+)?)(px|%)$/iu.exec(token);
|
|
88
|
+
if (!match?.[1]) {
|
|
89
|
+
// `auto`, or an expression this does not understand.
|
|
90
|
+
return Number.parseFloat(value) || 0;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const amount = Number(match[1]);
|
|
94
|
+
total += sign * (match[2] === '%' ? (amount / 100) * basis : amount);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
return Number.isFinite(total) ? total : 0;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function scrollPadding(element: Element, size: {readonly width: number; readonly height: number}): Insets {
|
|
101
|
+
const style = getComputedStyle(element);
|
|
102
|
+
return {
|
|
103
|
+
top: resolveLength(style.scrollPaddingTop, size.height),
|
|
104
|
+
right: resolveLength(style.scrollPaddingRight, size.width),
|
|
105
|
+
bottom: resolveLength(style.scrollPaddingBottom, size.height),
|
|
106
|
+
left: resolveLength(style.scrollPaddingLeft, size.width),
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
type Ancestors = {
|
|
111
|
+
/** Scroll containers that move the target, innermost first (the page itself is not included). */
|
|
112
|
+
readonly scrollers: ReadonlyArray<{readonly element: Element; readonly axes: ScrollAxes}>;
|
|
113
|
+
/** Intersection of every clipping ancestor's padding box, or `undefined` when nothing clips. */
|
|
114
|
+
readonly clip: Box | undefined;
|
|
115
|
+
/** True when the target sits inside a `position: fixed` element, so scrolling the page does not move it. */
|
|
116
|
+
readonly fixed: boolean;
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Walks up from `element`, collecting the scroll containers and clipping that apply to it. An
|
|
121
|
+
* absolutely positioned element escapes the overflow of the ancestors between it and its
|
|
122
|
+
* containing block (a dropdown sticking out of a navigation bar); a fixed one escapes everything
|
|
123
|
+
* up to the page.
|
|
124
|
+
*/
|
|
125
|
+
export function inspectAncestors(element: Element): Ancestors {
|
|
126
|
+
const scrollers: Array<{element: Element; axes: ScrollAxes}> = [];
|
|
127
|
+
let clip: Box | undefined;
|
|
128
|
+
let escaping = escapeOf(getComputedStyle(element).position);
|
|
129
|
+
for (let current = parentOf(element); current && current !== document.documentElement; current = parentOf(current)) {
|
|
130
|
+
if (holdsPageOverflow(current)) {
|
|
131
|
+
continue;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
const style = getComputedStyle(current);
|
|
135
|
+
if (escaping) {
|
|
136
|
+
if (!isContainingBlock(style, escaping)) {
|
|
137
|
+
continue;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
escaping = undefined;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
const clips = clippingOverflow.has(style.overflowX) || clippingOverflow.has(style.overflowY);
|
|
144
|
+
// `display: contents` elements have no box, so their overflow does nothing.
|
|
145
|
+
if (clips && style.display !== 'contents') {
|
|
146
|
+
clip = narrowClip(clip, paddingBox(current));
|
|
147
|
+
const axes = {
|
|
148
|
+
x: userScrollable.has(style.overflowX) && current.scrollWidth > current.clientWidth + 1,
|
|
149
|
+
y: userScrollable.has(style.overflowY) && current.scrollHeight > current.clientHeight + 1,
|
|
150
|
+
};
|
|
151
|
+
if (axes.x || axes.y) {
|
|
152
|
+
scrollers.push({element: current, axes});
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
escaping = escapeOf(style.position);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
return {scrollers, clip, fixed: escaping === 'fixed'};
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
function isRtl(element: Element): boolean {
|
|
163
|
+
return getComputedStyle(element).direction === 'rtl';
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/** Where the tour may scroll on each axis: from 0 to the end where it may, nowhere where it must not. */
|
|
167
|
+
function scrollRange(
|
|
168
|
+
offset: Point,
|
|
169
|
+
max: Point,
|
|
170
|
+
axes: ScrollAxes,
|
|
171
|
+
): Pick<ScrollContainerMetrics, 'minOffset' | 'maxOffset'> {
|
|
172
|
+
return {
|
|
173
|
+
minOffset: {x: axes.x ? 0 : offset.x, y: axes.y ? 0 : offset.y},
|
|
174
|
+
maxOffset: {x: axes.x ? max.x : offset.x, y: axes.y ? max.y : offset.y},
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/** A nested scroll container. Horizontal offsets are normalised to [0, max] in right-to-left containers too. */
|
|
179
|
+
export function elementContainer(element: Element, axes: ScrollAxes, occluders: () => readonly Box[]): ScrollContainer {
|
|
180
|
+
const maxX = (): number => Math.max(0, element.scrollWidth - element.clientWidth);
|
|
181
|
+
const read = (): Point => ({
|
|
182
|
+
x: isRtl(element) ? element.scrollLeft + maxX() : element.scrollLeft,
|
|
183
|
+
y: element.scrollTop,
|
|
184
|
+
});
|
|
185
|
+
return {
|
|
186
|
+
async measure(): Promise<ScrollContainerMetrics | undefined> {
|
|
187
|
+
if (!element.isConnected) {
|
|
188
|
+
return undefined;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
const box = paddingBox(element);
|
|
192
|
+
const area = deflate(box, scrollPadding(element, box));
|
|
193
|
+
const offset = read();
|
|
194
|
+
return {
|
|
195
|
+
area,
|
|
196
|
+
obstacles: occludersOver(area, occluders()),
|
|
197
|
+
offset,
|
|
198
|
+
...scrollRange(offset, {x: maxX(), y: Math.max(0, element.scrollHeight - element.clientHeight)}, axes),
|
|
199
|
+
};
|
|
200
|
+
},
|
|
201
|
+
async scrollTo(offset, options): Promise<void> {
|
|
202
|
+
element.scrollTo({
|
|
203
|
+
left: isRtl(element) ? offset.x - maxX() : offset.x,
|
|
204
|
+
top: offset.y,
|
|
205
|
+
behavior: options.animated ? 'smooth' : 'instant',
|
|
206
|
+
});
|
|
207
|
+
await waitForScrollEnd(element, read, offset, options);
|
|
208
|
+
},
|
|
209
|
+
};
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
/**
|
|
213
|
+
* The page itself; its visible area is the visual viewport minus safe areas and the page's
|
|
214
|
+
* scroll-padding. `overflow` is the page's own (see `readPageOverflow`), not the tour's scroll lock.
|
|
215
|
+
*/
|
|
216
|
+
export function pageContainer(
|
|
217
|
+
occluders: () => readonly Box[],
|
|
218
|
+
overflow: {readonly x: string; readonly y: string},
|
|
219
|
+
): ScrollContainer {
|
|
220
|
+
const root = (): Element => document.scrollingElement ?? document.documentElement;
|
|
221
|
+
const maxX = (): number => Math.max(0, root().scrollWidth - root().clientWidth);
|
|
222
|
+
const read = (): Point => ({
|
|
223
|
+
x: pageDirection() === 'rtl' ? globalThis.scrollX + maxX() : globalThis.scrollX,
|
|
224
|
+
y: globalThis.scrollY,
|
|
225
|
+
});
|
|
226
|
+
// The page scrolls unless the app keeps an axis from scrolling (`overflow-x: hidden` against
|
|
227
|
+
// stray horizontal overflow, off-canvas menus): then the tour leaves that axis alone too.
|
|
228
|
+
const axes = {x: !['hidden', 'clip'].includes(overflow.x), y: !['hidden', 'clip'].includes(overflow.y)};
|
|
229
|
+
return {
|
|
230
|
+
async measure(): Promise<ScrollContainerMetrics | undefined> {
|
|
231
|
+
const viewport = readViewport();
|
|
232
|
+
const visible = deflate(viewport.rect, viewport.insets);
|
|
233
|
+
const size = {width: document.documentElement.clientWidth, height: document.documentElement.clientHeight};
|
|
234
|
+
const offset = read();
|
|
235
|
+
return {
|
|
236
|
+
area: deflate(visible, scrollPadding(document.documentElement, size)),
|
|
237
|
+
// Sticky headers, floating buttons, banners: the target is scrolled clear of them where possible.
|
|
238
|
+
obstacles: occluders(),
|
|
239
|
+
offset,
|
|
240
|
+
...scrollRange(offset, {x: maxX(), y: Math.max(0, root().scrollHeight - root().clientHeight)}, axes),
|
|
241
|
+
};
|
|
242
|
+
},
|
|
243
|
+
async scrollTo(offset, options): Promise<void> {
|
|
244
|
+
globalThis.scrollTo({
|
|
245
|
+
left: pageDirection() === 'rtl' ? offset.x - maxX() : offset.x,
|
|
246
|
+
top: offset.y,
|
|
247
|
+
behavior: options.animated ? 'smooth' : 'instant',
|
|
248
|
+
});
|
|
249
|
+
await waitForScrollEnd(globalThis, read, offset, options);
|
|
250
|
+
},
|
|
251
|
+
};
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
/** Frames without movement after which a smooth scroll counts as finished (it moved, or it never started). */
|
|
255
|
+
const stillFramesAfterMoving = 3;
|
|
256
|
+
const stillFramesWithoutMoving = 8;
|
|
257
|
+
|
|
258
|
+
/**
|
|
259
|
+
* Resolves once a scroll of `scroller` has finished: at `scrollend` where browsers support it, or
|
|
260
|
+
* once the offset has reached `requested`, or has stopped changing (scroll snapping can end the
|
|
261
|
+
* scroll elsewhere). Instant scrolls finish on the next frame.
|
|
262
|
+
*/
|
|
263
|
+
async function waitForScrollEnd(
|
|
264
|
+
scroller: EventTarget,
|
|
265
|
+
read: () => Point,
|
|
266
|
+
requested: Point,
|
|
267
|
+
{animated, signal}: ScrollOptions,
|
|
268
|
+
): Promise<void> {
|
|
269
|
+
if (signal.aborted) {
|
|
270
|
+
return;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
await new Promise<void>((resolve) => {
|
|
274
|
+
let cancelFrame: () => void;
|
|
275
|
+
let previous = read();
|
|
276
|
+
let stillFrames = 0;
|
|
277
|
+
let hasMoved = false;
|
|
278
|
+
const finish = (): void => {
|
|
279
|
+
cancelFrame();
|
|
280
|
+
scroller.removeEventListener('scrollend', finish);
|
|
281
|
+
signal.removeEventListener('abort', finish);
|
|
282
|
+
resolve();
|
|
283
|
+
};
|
|
284
|
+
|
|
285
|
+
const tick = (): void => {
|
|
286
|
+
const current = read();
|
|
287
|
+
const isStill = Math.abs(current.x - previous.x) < 0.5 && Math.abs(current.y - previous.y) < 0.5;
|
|
288
|
+
hasMoved ||= !isStill;
|
|
289
|
+
stillFrames = isStill ? stillFrames + 1 : 0;
|
|
290
|
+
previous = current;
|
|
291
|
+
const hasArrived = Math.abs(current.x - requested.x) <= 1 && Math.abs(current.y - requested.y) <= 1;
|
|
292
|
+
const isDone =
|
|
293
|
+
!animated ||
|
|
294
|
+
(hasArrived && isStill) ||
|
|
295
|
+
stillFrames >= (hasMoved ? stillFramesAfterMoving : stillFramesWithoutMoving);
|
|
296
|
+
if (isDone) {
|
|
297
|
+
finish();
|
|
298
|
+
} else {
|
|
299
|
+
cancelFrame = requestFrame(tick);
|
|
300
|
+
}
|
|
301
|
+
};
|
|
302
|
+
|
|
303
|
+
if (animated) {
|
|
304
|
+
scroller.addEventListener('scrollend', finish);
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
signal.addEventListener('abort', finish);
|
|
308
|
+
cancelFrame = requestFrame(tick);
|
|
309
|
+
});
|
|
310
|
+
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import {zeroInsets} from '../../shared/geometry/box.util.js';
|
|
2
|
+
import type {Box, Insets, Viewport} from '../../shared/geometry/geometry.type.js';
|
|
3
|
+
|
|
4
|
+
let probe: HTMLElement | undefined;
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Safe-area insets (notch, home indicator) via `env(safe-area-inset-*)`. They are only non-zero
|
|
8
|
+
* when the page opts in with `viewport-fit=cover`.
|
|
9
|
+
*/
|
|
10
|
+
export function readSafeAreaInsets(): Insets {
|
|
11
|
+
if (typeof document === 'undefined' || !document.body) {
|
|
12
|
+
return zeroInsets;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
if (!probe?.isConnected) {
|
|
16
|
+
probe = document.createElement('div');
|
|
17
|
+
probe.setAttribute('aria-hidden', 'true');
|
|
18
|
+
probe.dataset.rtkProbe = '';
|
|
19
|
+
probe.style.cssText =
|
|
20
|
+
'position:fixed;top:0;left:0;width:0;height:0;visibility:hidden;pointer-events:none;' +
|
|
21
|
+
'padding:env(safe-area-inset-top,0px) env(safe-area-inset-right,0px) env(safe-area-inset-bottom,0px) env(safe-area-inset-left,0px)';
|
|
22
|
+
document.body.append(probe);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const style = getComputedStyle(probe);
|
|
26
|
+
return {
|
|
27
|
+
top: Number.parseFloat(style.paddingTop) || 0,
|
|
28
|
+
right: Number.parseFloat(style.paddingRight) || 0,
|
|
29
|
+
bottom: Number.parseFloat(style.paddingBottom) || 0,
|
|
30
|
+
left: Number.parseFloat(style.paddingLeft) || 0,
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
let probeUsers = 0;
|
|
35
|
+
|
|
36
|
+
/** Registers a user of the safe-area probe; the returned function releases it and removes the probe when unused. */
|
|
37
|
+
export function acquireSafeAreaProbe(): () => void {
|
|
38
|
+
probeUsers++;
|
|
39
|
+
return () => {
|
|
40
|
+
probeUsers--;
|
|
41
|
+
if (probeUsers === 0) {
|
|
42
|
+
probe?.remove();
|
|
43
|
+
probe = undefined;
|
|
44
|
+
}
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* The part of the page the user sees, in the coordinate space of `position: fixed` elements
|
|
50
|
+
* (the layout viewport): the visual viewport (pinch-zoom, on-screen keyboard), without scrollbars.
|
|
51
|
+
*/
|
|
52
|
+
export function readVisibleRect(): Box {
|
|
53
|
+
const root = document.documentElement;
|
|
54
|
+
const layoutWidth = root.clientWidth || window.innerWidth;
|
|
55
|
+
const layoutHeight = root.clientHeight || window.innerHeight;
|
|
56
|
+
const visual = window.visualViewport;
|
|
57
|
+
if (!visual) {
|
|
58
|
+
return {x: 0, y: 0, width: layoutWidth, height: layoutHeight};
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const x = Math.max(0, visual.offsetLeft);
|
|
62
|
+
const y = Math.max(0, visual.offsetTop);
|
|
63
|
+
return {
|
|
64
|
+
x,
|
|
65
|
+
y,
|
|
66
|
+
width: Math.max(0, Math.min(visual.width, layoutWidth - x)),
|
|
67
|
+
height: Math.max(0, Math.min(visual.height, layoutHeight - y)),
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/** The visible area and its safe-area insets (notch, home indicator). */
|
|
72
|
+
export function readViewport(): Viewport {
|
|
73
|
+
return {rect: readVisibleRect(), insets: readSafeAreaInsets()};
|
|
74
|
+
}
|