@robertlinde/react-tour-kit 0.8.8 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +40 -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 -8
- package/dist/react-native.d.ts.map +1 -0
- package/dist/react-native.js +7 -6
- 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 -8
- package/dist/react.d.ts.map +1 -0
- package/dist/react.js +5 -6
- 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 +60 -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 +92 -0
- package/dist/shared/engine/tour-engine.d.ts.map +1 -0
- package/dist/shared/engine/tour-engine.js +787 -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 +130 -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 +62 -0
- package/src/shared/engine/tour-engine.ts +1068 -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 +151 -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 -5
- package/dist/shared/index.js +0 -5
- 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,183 @@
|
|
|
1
|
+
import type {ComponentType, ReactNode} from 'react';
|
|
2
|
+
import type {TourPhase} from '../engine/tour-engine.js';
|
|
3
|
+
import type {TooltipLayout} from '../geometry/compute-tooltip-layout.util.js';
|
|
4
|
+
import type {Box, Point, Side, Size} from '../geometry/geometry.type.js';
|
|
5
|
+
import type {TourPlacement} from '../types/tour-step.type.js';
|
|
6
|
+
import type {ResolvedTheme} from '../utils/theme.util.js';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* How a platform draws the tour: primitives only. Every decision — what is visible, when and how
|
|
10
|
+
* things animate, which texts are shown, what a press means — is made by the shared overlay
|
|
11
|
+
* (`TourOverlay`) and default tooltip (`DefaultTooltip`), so all platforms behave the same.
|
|
12
|
+
*
|
|
13
|
+
* Every primitive is required: a platform that lacks one does not compile. A new feature either
|
|
14
|
+
* lives in the shared code (and every platform gets it), or needs a new primitive here (and every
|
|
15
|
+
* platform has to draw it).
|
|
16
|
+
*/
|
|
17
|
+
export type TourRenderer = {
|
|
18
|
+
/** The full-screen layer everything else is drawn in. */
|
|
19
|
+
readonly Layer: ComponentType<LayerProps>;
|
|
20
|
+
/** The area behind the spotlight that receives presses on the dimmed part of the screen. */
|
|
21
|
+
readonly Backdrop: ComponentType<BackdropProps>;
|
|
22
|
+
/** The dimmed overlay with a hole over the target. */
|
|
23
|
+
readonly Spotlight: ComponentType<SpotlightProps>;
|
|
24
|
+
/** Places the tooltip, plays its entrance, reports its size and takes focus. */
|
|
25
|
+
readonly TooltipFrame: ComponentType<TooltipFrameProps>;
|
|
26
|
+
/** Renders a tooltip invisibly with given size limits and reports its size. */
|
|
27
|
+
readonly MeasureFrame: ComponentType<MeasureFrameProps>;
|
|
28
|
+
/** The pieces of the built-in tooltip card. */
|
|
29
|
+
readonly card: CardParts;
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
/** What the user asked for through platform input (keys, gestures, system buttons). */
|
|
33
|
+
export type TourIntent =
|
|
34
|
+
/** Go to the next step. */
|
|
35
|
+
| 'next'
|
|
36
|
+
/** Go to the previous step. */
|
|
37
|
+
| 'previous'
|
|
38
|
+
/** Leave the tour like Escape does (ends with `'escape'`). */
|
|
39
|
+
| 'escape'
|
|
40
|
+
/** Leave the tour with a system back action (ends with `'closed'`). */
|
|
41
|
+
| 'back';
|
|
42
|
+
|
|
43
|
+
export type LayerProps = {
|
|
44
|
+
readonly children: ReactNode;
|
|
45
|
+
readonly state: TourPhase | 'closing';
|
|
46
|
+
/** Faded in (`true`) or out, over `fadeMs`. */
|
|
47
|
+
readonly isVisible: boolean;
|
|
48
|
+
readonly fadeMs: number;
|
|
49
|
+
/** False while fading out: input is no longer handled. */
|
|
50
|
+
readonly isInteractive: boolean;
|
|
51
|
+
readonly theme: ResolvedTheme;
|
|
52
|
+
/** Id of the element showing the step's title, `undefined` while no step is shown. */
|
|
53
|
+
readonly titleId: string | undefined;
|
|
54
|
+
/** Id of the element showing the step's content, `undefined` while no step is shown. */
|
|
55
|
+
readonly descriptionId: string | undefined;
|
|
56
|
+
/** Accessible name for when a custom tooltip renders no element with `titleId`. */
|
|
57
|
+
readonly fallbackLabel: string;
|
|
58
|
+
/** Text for screen readers when the step changes; empty when there is nothing to announce. */
|
|
59
|
+
readonly announcement: string;
|
|
60
|
+
/** Changes whenever a step is shown. */
|
|
61
|
+
readonly stepKey: number | undefined;
|
|
62
|
+
readonly onIntent: (intent: TourIntent) => void;
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
export type BackdropProps = {
|
|
66
|
+
/** Whether a press closes the tour (to show it with a pointer cursor, for example). */
|
|
67
|
+
readonly isDismissible: boolean;
|
|
68
|
+
/** A press on the dimmed area, at a point in layer coordinates. */
|
|
69
|
+
readonly onPress: (point: Point) => void;
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
export type SpotlightProps = {
|
|
73
|
+
/** The hole over the target, or `undefined`: no hole, everything dimmed (shrunk to the layer's centre). */
|
|
74
|
+
readonly hole: Box | undefined;
|
|
75
|
+
/** How changes are animated; `undefined`: applied instantly. */
|
|
76
|
+
readonly transition: SpotlightTransition | undefined;
|
|
77
|
+
readonly theme: ResolvedTheme;
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
export type SpotlightTransition = {
|
|
81
|
+
readonly durationMs: number;
|
|
82
|
+
readonly easing: 'standard' | 'linear';
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
export type TooltipFrameProps = {
|
|
86
|
+
readonly children: ReactNode;
|
|
87
|
+
readonly layout: TooltipLayout;
|
|
88
|
+
/** Played once, when the frame mounts. */
|
|
89
|
+
readonly entrance: Entrance;
|
|
90
|
+
/** A step change is being prepared: ignore presses. */
|
|
91
|
+
readonly isBusy: boolean;
|
|
92
|
+
/** The next target is being scrolled into view: hide the tooltip (keep its size). */
|
|
93
|
+
readonly isHidden: boolean;
|
|
94
|
+
readonly onSize: (size: Size) => void;
|
|
95
|
+
readonly onIntent: (intent: TourIntent) => void;
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
/** The tooltip's entrance: from this offset, scale and transparency to its place, after `delayMs`. */
|
|
99
|
+
export type Entrance = {
|
|
100
|
+
readonly dx: number;
|
|
101
|
+
readonly dy: number;
|
|
102
|
+
readonly scale: number;
|
|
103
|
+
readonly durationMs: number;
|
|
104
|
+
readonly delayMs: number;
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
export type MeasureFrameProps = {
|
|
108
|
+
readonly children: ReactNode;
|
|
109
|
+
readonly maxWidth: number;
|
|
110
|
+
readonly maxHeight: number;
|
|
111
|
+
readonly onSize: (size: Size) => void;
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
/** The pieces of the built-in tooltip, put together by the shared `DefaultTooltip`. */
|
|
115
|
+
export type CardParts = {
|
|
116
|
+
readonly Card: ComponentType<CardProps>;
|
|
117
|
+
readonly Arrow: ComponentType<ArrowProps>;
|
|
118
|
+
readonly Header: ComponentType<ContainerProps>;
|
|
119
|
+
readonly Counter: ComponentType<CounterProps>;
|
|
120
|
+
readonly CloseButton: ComponentType<CloseButtonProps>;
|
|
121
|
+
/** Scrolls when the content is taller than the space available. */
|
|
122
|
+
readonly Body: ComponentType<ContainerProps>;
|
|
123
|
+
readonly Title: ComponentType<TextPartProps>;
|
|
124
|
+
readonly Content: ComponentType<TextPartProps>;
|
|
125
|
+
readonly Footer: ComponentType<ContainerProps>;
|
|
126
|
+
readonly Button: ComponentType<ButtonProps>;
|
|
127
|
+
readonly Dots: ComponentType<DotsProps>;
|
|
128
|
+
readonly Dot: ComponentType<DotProps>;
|
|
129
|
+
readonly Ellipsis: ComponentType<EllipsisProps>;
|
|
130
|
+
};
|
|
131
|
+
|
|
132
|
+
export type CardProps = {
|
|
133
|
+
readonly placement: TourPlacement | 'center';
|
|
134
|
+
readonly children: ReactNode;
|
|
135
|
+
};
|
|
136
|
+
|
|
137
|
+
export type ArrowProps = {
|
|
138
|
+
readonly side: Side;
|
|
139
|
+
/** Distance from the card's left edge (top/bottom) or top edge (left/right) to the arrow's tip. */
|
|
140
|
+
readonly offset: number;
|
|
141
|
+
};
|
|
142
|
+
|
|
143
|
+
export type ContainerProps = {
|
|
144
|
+
readonly children: ReactNode;
|
|
145
|
+
};
|
|
146
|
+
|
|
147
|
+
export type CounterProps = {
|
|
148
|
+
readonly text: string;
|
|
149
|
+
};
|
|
150
|
+
|
|
151
|
+
export type CloseButtonProps = {
|
|
152
|
+
readonly label: string;
|
|
153
|
+
readonly onPress: () => void;
|
|
154
|
+
};
|
|
155
|
+
|
|
156
|
+
/** The title or the content: any React content, with the id screen readers find it by. */
|
|
157
|
+
export type TextPartProps = {
|
|
158
|
+
readonly id: string;
|
|
159
|
+
readonly children: ReactNode;
|
|
160
|
+
};
|
|
161
|
+
|
|
162
|
+
export type ButtonProps = {
|
|
163
|
+
readonly kind: 'previous' | 'next';
|
|
164
|
+
readonly label: string;
|
|
165
|
+
readonly isDisabled: boolean;
|
|
166
|
+
readonly onPress: () => void;
|
|
167
|
+
};
|
|
168
|
+
|
|
169
|
+
export type DotsProps = {
|
|
170
|
+
/** Describes the group, for example "2 of 5". */
|
|
171
|
+
readonly label: string;
|
|
172
|
+
readonly children: ReactNode;
|
|
173
|
+
};
|
|
174
|
+
|
|
175
|
+
export type DotProps = {
|
|
176
|
+
readonly label: string;
|
|
177
|
+
readonly isCurrent: boolean;
|
|
178
|
+
readonly onPress: () => void;
|
|
179
|
+
};
|
|
180
|
+
|
|
181
|
+
export type EllipsisProps = {
|
|
182
|
+
readonly children?: never;
|
|
183
|
+
};
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import type {Side} from '../geometry/geometry.type.js';
|
|
2
|
+
import type {TourTooltipProps} from '../types/tour-tooltip-props.type.js';
|
|
3
|
+
import {getTruncatedDots} from '../utils/get-truncated-dots.util.js';
|
|
4
|
+
import {formatTemplate, type ResolvedI18n} from '../utils/i18n.util.js';
|
|
5
|
+
|
|
6
|
+
export type TooltipDot =
|
|
7
|
+
| {readonly kind: 'dot'; readonly index: number; readonly label: string; readonly isCurrent: boolean}
|
|
8
|
+
| {readonly kind: 'ellipsis'; readonly key: string};
|
|
9
|
+
|
|
10
|
+
/** Everything the built-in tooltip shows, worked out once for every platform. */
|
|
11
|
+
export type TooltipModel = {
|
|
12
|
+
readonly counter: string;
|
|
13
|
+
readonly closeLabel: string;
|
|
14
|
+
readonly arrow: {readonly side: Side; readonly offset: number} | undefined;
|
|
15
|
+
readonly previous: {readonly label: string; readonly isDisabled: boolean};
|
|
16
|
+
readonly next: {readonly label: string};
|
|
17
|
+
/** `undefined` for a single step. */
|
|
18
|
+
readonly dots: readonly TooltipDot[] | undefined;
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
export function tooltipModel(props: TourTooltipProps, i18n: ResolvedI18n): TooltipModel {
|
|
22
|
+
const {currentStep, totalSteps, placement, arrowOffset} = props;
|
|
23
|
+
const isLast = currentStep >= totalSteps - 1;
|
|
24
|
+
return {
|
|
25
|
+
counter: formatTemplate(i18n.stepCounterTemplate, {current: currentStep + 1, total: totalSteps}),
|
|
26
|
+
closeLabel: i18n.closeAriaLabel,
|
|
27
|
+
arrow: arrowOffset === undefined || placement === 'center' ? undefined : {side: placement, offset: arrowOffset},
|
|
28
|
+
previous: {label: i18n.prevLabel, isDisabled: currentStep === 0},
|
|
29
|
+
next: {label: isLast ? i18n.finishLabel : i18n.nextLabel},
|
|
30
|
+
dots:
|
|
31
|
+
totalSteps > 1
|
|
32
|
+
? getTruncatedDots(currentStep, totalSteps).map((item): TooltipDot =>
|
|
33
|
+
item.type === 'ellipsis'
|
|
34
|
+
? {kind: 'ellipsis', key: `ellipsis-${item.position}`}
|
|
35
|
+
: {
|
|
36
|
+
kind: 'dot',
|
|
37
|
+
index: item.index,
|
|
38
|
+
label: formatTemplate(i18n.dotAriaLabelTemplate, {step: item.index + 1, total: totalSteps}),
|
|
39
|
+
isCurrent: item.index === currentStep,
|
|
40
|
+
},
|
|
41
|
+
)
|
|
42
|
+
: undefined,
|
|
43
|
+
};
|
|
44
|
+
}
|
|
@@ -0,0 +1,453 @@
|
|
|
1
|
+
import {
|
|
2
|
+
type ComponentType,
|
|
3
|
+
isValidElement,
|
|
4
|
+
type ReactNode,
|
|
5
|
+
useCallback,
|
|
6
|
+
useEffect,
|
|
7
|
+
useId,
|
|
8
|
+
useMemo,
|
|
9
|
+
useRef,
|
|
10
|
+
useState,
|
|
11
|
+
} from 'react';
|
|
12
|
+
import {
|
|
13
|
+
type MeasureRequest,
|
|
14
|
+
type ShownStep,
|
|
15
|
+
type TourEngine,
|
|
16
|
+
tourLayout,
|
|
17
|
+
type TourViewState,
|
|
18
|
+
} from '../engine/tour-engine.js';
|
|
19
|
+
import {containsPoint, inflate} from '../geometry/box.util.js';
|
|
20
|
+
import type {Box, Point, Size} from '../geometry/geometry.type.js';
|
|
21
|
+
import {useTourView} from '../react/use-tour-view.hook.js';
|
|
22
|
+
import type {TourTooltipProps} from '../types/tour-tooltip-props.type.js';
|
|
23
|
+
import {formatTemplate, type ResolvedI18n} from '../utils/i18n.util.js';
|
|
24
|
+
import type {ResolvedTheme} from '../utils/theme.util.js';
|
|
25
|
+
import {entranceFor, fadeDuration, motion} from './motion.js';
|
|
26
|
+
import {RendererContext, type RendererContextValue, TooltipLimitsContext} from './renderer-context.js';
|
|
27
|
+
import type {SpotlightTransition, TourIntent, TourRenderer} from './renderer.type.js';
|
|
28
|
+
|
|
29
|
+
export type TourOverlayProps = {
|
|
30
|
+
readonly engine: TourEngine;
|
|
31
|
+
readonly renderer: TourRenderer;
|
|
32
|
+
readonly theme: ResolvedTheme;
|
|
33
|
+
readonly i18n: ResolvedI18n;
|
|
34
|
+
readonly Tooltip: ComponentType<TourTooltipProps>;
|
|
35
|
+
readonly isOverlayDismissible: boolean;
|
|
36
|
+
readonly isReducedMotion: boolean;
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
function noop(): void {
|
|
40
|
+
// Intentionally empty.
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* The tour on screen, for every platform: decides what is visible, how it animates, what it
|
|
45
|
+
* says and what input means, and draws it with the platform's primitives ({@link TourRenderer}).
|
|
46
|
+
* Keeps the last step on screen while the tour fades out.
|
|
47
|
+
*/
|
|
48
|
+
export function TourOverlay(props: TourOverlayProps): ReactNode {
|
|
49
|
+
const view = useTourView(props.engine);
|
|
50
|
+
// When the tour ends, the last view stays on screen while it fades out. Worked out during render
|
|
51
|
+
// (not in an effect), so the overlay is never unmounted in between and the fade really plays.
|
|
52
|
+
const [tracked, setTracked] = useState<{view: TourViewState; closing: TourViewState | undefined}>(() => ({
|
|
53
|
+
view,
|
|
54
|
+
closing: undefined,
|
|
55
|
+
}));
|
|
56
|
+
let {closing} = tracked;
|
|
57
|
+
if (tracked.view !== view) {
|
|
58
|
+
const before = tracked.view;
|
|
59
|
+
closing = view.phase === 'idle' ? (before.phase === 'idle' ? closing : before) : undefined;
|
|
60
|
+
setTracked({view, closing});
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const onClosed = useCallback((): void => {
|
|
64
|
+
setTracked((current) => ({...current, closing: undefined}));
|
|
65
|
+
}, []);
|
|
66
|
+
const isClosing = view.phase === 'idle' && closing !== undefined;
|
|
67
|
+
const rendered = view.phase === 'idle' ? closing : view;
|
|
68
|
+
return rendered ? <OverlayContent {...props} view={rendered} isClosing={isClosing} onClosed={onClosed} /> : null;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
type OverlayContentProps = TourOverlayProps & {
|
|
72
|
+
readonly view: TourViewState;
|
|
73
|
+
readonly isClosing: boolean;
|
|
74
|
+
readonly onClosed: () => void;
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
function OverlayContent({
|
|
78
|
+
engine,
|
|
79
|
+
renderer,
|
|
80
|
+
theme,
|
|
81
|
+
i18n,
|
|
82
|
+
Tooltip,
|
|
83
|
+
isOverlayDismissible,
|
|
84
|
+
isReducedMotion,
|
|
85
|
+
view,
|
|
86
|
+
isClosing,
|
|
87
|
+
onClosed,
|
|
88
|
+
}: OverlayContentProps): ReactNode {
|
|
89
|
+
const {Layer, Backdrop, Spotlight} = renderer;
|
|
90
|
+
const id = useId();
|
|
91
|
+
const titleId = `${id}title`;
|
|
92
|
+
const descriptionId = `${id}description`;
|
|
93
|
+
const {shown, phase, measure} = view;
|
|
94
|
+
const totalSteps = view.steps.length;
|
|
95
|
+
// The first step can take a while (navigation in onBeforeStep): dim the screen after a moment.
|
|
96
|
+
const isWaitingDimmed = useDelayedFlag(!shown && !isClosing, motion.waitingDimDelayMs);
|
|
97
|
+
// Once on screen, the overlay stays until the tour ends (also when another tour takes over).
|
|
98
|
+
const [hasBeenVisible, setHasBeenVisible] = useState(false);
|
|
99
|
+
const isShowingSomething = shown !== undefined || isWaitingDimmed;
|
|
100
|
+
if (isShowingSomething && !isClosing && !hasBeenVisible) {
|
|
101
|
+
setHasBeenVisible(true);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const isVisible = !isClosing && (isShowingSomething || hasBeenVisible);
|
|
105
|
+
// When the overlay became visible: presses on the dimmed area only count once it has faded in.
|
|
106
|
+
const visibleSince = useRef<number | undefined>(undefined);
|
|
107
|
+
useEffect(() => {
|
|
108
|
+
visibleSince.current = isVisible ? Date.now() : undefined;
|
|
109
|
+
}, [isVisible]);
|
|
110
|
+
const spotlight = useSpotlight(view, isVisible, isReducedMotion);
|
|
111
|
+
const announcement = useAnnouncement(shown, totalSteps, i18n, isClosing);
|
|
112
|
+
|
|
113
|
+
// Fade out, then unmount.
|
|
114
|
+
useEffect(() => {
|
|
115
|
+
if (!isClosing) {
|
|
116
|
+
return undefined;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
const timer = setTimeout(onClosed, fadeDuration(true, isReducedMotion) + 20);
|
|
120
|
+
return (): void => {
|
|
121
|
+
clearTimeout(timer);
|
|
122
|
+
};
|
|
123
|
+
}, [isClosing, isReducedMotion, onClosed]);
|
|
124
|
+
|
|
125
|
+
const onIntent = useCallback(
|
|
126
|
+
(intent: TourIntent): void => {
|
|
127
|
+
if (isClosing) {
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
switch (intent) {
|
|
132
|
+
case 'next': {
|
|
133
|
+
engine.actions.nextStep();
|
|
134
|
+
break;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
case 'previous': {
|
|
138
|
+
engine.actions.prevStep();
|
|
139
|
+
break;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
case 'escape': {
|
|
143
|
+
engine.dismiss('escape');
|
|
144
|
+
break;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
case 'back': {
|
|
148
|
+
engine.dismiss('closed');
|
|
149
|
+
break;
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
},
|
|
153
|
+
[engine, isClosing],
|
|
154
|
+
);
|
|
155
|
+
|
|
156
|
+
const onBackdropPress = (point: Point): void => {
|
|
157
|
+
// Not while the overlay is still appearing (a double click on the button that started the
|
|
158
|
+
// tour), nor while a step change is under way (a second tap on Next falls through the busy tooltip).
|
|
159
|
+
const since = visibleSince.current;
|
|
160
|
+
const hasFadedIn = since !== undefined && Date.now() - since >= motion.fadeInMs;
|
|
161
|
+
const isChangingStep = shown !== undefined && phase !== 'showing';
|
|
162
|
+
if (!isOverlayDismissible || isClosing || !hasFadedIn || isChangingStep) {
|
|
163
|
+
return;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// Presses on the highlighted element go nowhere; presses on the dimmed area close the tour.
|
|
167
|
+
if (spotlight.hole === undefined || !containsPoint(spotlight.hole, point)) {
|
|
168
|
+
engine.dismiss('blur');
|
|
169
|
+
}
|
|
170
|
+
};
|
|
171
|
+
|
|
172
|
+
const context = useMemo<RendererContextValue>(() => ({renderer, theme, i18n}), [renderer, theme, i18n]);
|
|
173
|
+
const counter = shown
|
|
174
|
+
? formatTemplate(i18n.stepCounterTemplate, {current: shown.stepIndex + 1, total: totalSteps})
|
|
175
|
+
: '';
|
|
176
|
+
|
|
177
|
+
return (
|
|
178
|
+
<RendererContext.Provider value={context}>
|
|
179
|
+
<Layer
|
|
180
|
+
state={isClosing ? 'closing' : phase}
|
|
181
|
+
isVisible={isVisible}
|
|
182
|
+
fadeMs={fadeDuration(isClosing, isReducedMotion)}
|
|
183
|
+
isInteractive={!isClosing}
|
|
184
|
+
theme={theme}
|
|
185
|
+
titleId={shown ? titleId : undefined}
|
|
186
|
+
descriptionId={shown ? descriptionId : undefined}
|
|
187
|
+
fallbackLabel={(shown ? textOf(shown.step.title) : '') || counter}
|
|
188
|
+
announcement={announcement}
|
|
189
|
+
stepKey={shown?.transitionId}
|
|
190
|
+
onIntent={onIntent}
|
|
191
|
+
>
|
|
192
|
+
<Backdrop isDismissible={isOverlayDismissible} onPress={onBackdropPress} />
|
|
193
|
+
<Spotlight hole={spotlight.hole} transition={spotlight.transition} theme={theme} />
|
|
194
|
+
{shown ? (
|
|
195
|
+
<ShownTooltip
|
|
196
|
+
key={shown.transitionId}
|
|
197
|
+
engine={engine}
|
|
198
|
+
renderer={renderer}
|
|
199
|
+
Tooltip={Tooltip}
|
|
200
|
+
shown={shown}
|
|
201
|
+
totalSteps={totalSteps}
|
|
202
|
+
isBusy={phase === 'preparing' && !isClosing}
|
|
203
|
+
isHidden={phase === 'revealing'}
|
|
204
|
+
isDelayed={spotlight.isMorphing}
|
|
205
|
+
isReducedMotion={isReducedMotion}
|
|
206
|
+
titleId={titleId}
|
|
207
|
+
descriptionId={descriptionId}
|
|
208
|
+
onIntent={onIntent}
|
|
209
|
+
/>
|
|
210
|
+
) : null}
|
|
211
|
+
{measure && !isClosing ? (
|
|
212
|
+
<MeasuredTooltip
|
|
213
|
+
key={measure.id}
|
|
214
|
+
engine={engine}
|
|
215
|
+
renderer={renderer}
|
|
216
|
+
Tooltip={Tooltip}
|
|
217
|
+
request={measure}
|
|
218
|
+
totalSteps={totalSteps}
|
|
219
|
+
/>
|
|
220
|
+
) : null}
|
|
221
|
+
</Layer>
|
|
222
|
+
</RendererContext.Provider>
|
|
223
|
+
);
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
type ShownTooltipProps = {
|
|
227
|
+
readonly engine: TourEngine;
|
|
228
|
+
readonly renderer: TourRenderer;
|
|
229
|
+
readonly Tooltip: ComponentType<TourTooltipProps>;
|
|
230
|
+
readonly shown: ShownStep;
|
|
231
|
+
readonly totalSteps: number;
|
|
232
|
+
readonly isBusy: boolean;
|
|
233
|
+
readonly isHidden: boolean;
|
|
234
|
+
/** Enters a moment later, while the spotlight is still moving to the target. */
|
|
235
|
+
readonly isDelayed: boolean;
|
|
236
|
+
readonly isReducedMotion: boolean;
|
|
237
|
+
readonly titleId: string;
|
|
238
|
+
readonly descriptionId: string;
|
|
239
|
+
readonly onIntent: (intent: TourIntent) => void;
|
|
240
|
+
};
|
|
241
|
+
|
|
242
|
+
/** The step on screen: its tooltip in the platform's frame. Mounted once per shown step. */
|
|
243
|
+
function ShownTooltip({
|
|
244
|
+
engine,
|
|
245
|
+
renderer,
|
|
246
|
+
Tooltip,
|
|
247
|
+
shown,
|
|
248
|
+
totalSteps,
|
|
249
|
+
isBusy,
|
|
250
|
+
isHidden,
|
|
251
|
+
isDelayed,
|
|
252
|
+
isReducedMotion,
|
|
253
|
+
titleId,
|
|
254
|
+
descriptionId,
|
|
255
|
+
onIntent,
|
|
256
|
+
}: ShownTooltipProps): ReactNode {
|
|
257
|
+
const {TooltipFrame} = renderer;
|
|
258
|
+
const {transitionId, layout} = shown;
|
|
259
|
+
const onSize = useCallback(
|
|
260
|
+
(size: Size): void => {
|
|
261
|
+
engine.reportTooltipSize(transitionId, size);
|
|
262
|
+
},
|
|
263
|
+
[engine, transitionId],
|
|
264
|
+
);
|
|
265
|
+
const onClose = useCallback((): void => {
|
|
266
|
+
engine.dismiss('closed');
|
|
267
|
+
}, [engine]);
|
|
268
|
+
// The entrance is played once, when the step appears.
|
|
269
|
+
const [entrance] = useState(() => entranceFor(layout.placement, {isDelayed, isReducedMotion}));
|
|
270
|
+
return (
|
|
271
|
+
<TooltipFrame
|
|
272
|
+
layout={layout}
|
|
273
|
+
entrance={entrance}
|
|
274
|
+
isBusy={isBusy}
|
|
275
|
+
isHidden={isHidden}
|
|
276
|
+
onSize={onSize}
|
|
277
|
+
onIntent={onIntent}
|
|
278
|
+
>
|
|
279
|
+
<TooltipLimitsContext.Provider value={layout}>
|
|
280
|
+
<Tooltip
|
|
281
|
+
step={shown.step}
|
|
282
|
+
currentStep={shown.stepIndex}
|
|
283
|
+
totalSteps={totalSteps}
|
|
284
|
+
placement={layout.placement}
|
|
285
|
+
arrowOffset={layout.arrowOffset}
|
|
286
|
+
titleId={titleId}
|
|
287
|
+
descriptionId={descriptionId}
|
|
288
|
+
onNext={engine.actions.nextStep}
|
|
289
|
+
onPrev={engine.actions.prevStep}
|
|
290
|
+
onClose={onClose}
|
|
291
|
+
onGoToStep={engine.actions.goToStep}
|
|
292
|
+
/>
|
|
293
|
+
</TooltipLimitsContext.Provider>
|
|
294
|
+
</TooltipFrame>
|
|
295
|
+
);
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
type MeasuredTooltipProps = {
|
|
299
|
+
readonly engine: TourEngine;
|
|
300
|
+
readonly renderer: TourRenderer;
|
|
301
|
+
readonly Tooltip: ComponentType<TourTooltipProps>;
|
|
302
|
+
readonly request: MeasureRequest;
|
|
303
|
+
readonly totalSteps: number;
|
|
304
|
+
};
|
|
305
|
+
|
|
306
|
+
/** The next step's tooltip, rendered invisibly with the same limits to learn its size: same content, no actions. */
|
|
307
|
+
function MeasuredTooltip({engine, renderer, Tooltip, request, totalSteps}: MeasuredTooltipProps): ReactNode {
|
|
308
|
+
const {MeasureFrame} = renderer;
|
|
309
|
+
const id = useId();
|
|
310
|
+
const onSize = useCallback(
|
|
311
|
+
(size: Size): void => {
|
|
312
|
+
engine.reportTooltipSize(request.id, size);
|
|
313
|
+
},
|
|
314
|
+
[engine, request.id],
|
|
315
|
+
);
|
|
316
|
+
return (
|
|
317
|
+
<MeasureFrame maxWidth={request.maxWidth} maxHeight={request.maxHeight} onSize={onSize}>
|
|
318
|
+
<TooltipLimitsContext.Provider value={request}>
|
|
319
|
+
<Tooltip
|
|
320
|
+
step={request.step}
|
|
321
|
+
currentStep={request.stepIndex}
|
|
322
|
+
totalSteps={totalSteps}
|
|
323
|
+
placement={request.step.target === undefined ? 'center' : (request.step.placement ?? 'bottom')}
|
|
324
|
+
arrowOffset={undefined}
|
|
325
|
+
titleId={`${id}title`}
|
|
326
|
+
descriptionId={`${id}description`}
|
|
327
|
+
onNext={noop}
|
|
328
|
+
onPrev={noop}
|
|
329
|
+
onClose={noop}
|
|
330
|
+
onGoToStep={noop}
|
|
331
|
+
/>
|
|
332
|
+
</TooltipLimitsContext.Provider>
|
|
333
|
+
</MeasureFrame>
|
|
334
|
+
);
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
/** True once `isWaiting` has been true for `delayMs`, as long as it stays true. */
|
|
338
|
+
function useDelayedFlag(isWaiting: boolean, delayMs: number): boolean {
|
|
339
|
+
const [isDue, setIsDue] = useState(false);
|
|
340
|
+
const [wasWaiting, setWasWaiting] = useState(isWaiting);
|
|
341
|
+
if (wasWaiting !== isWaiting) {
|
|
342
|
+
setWasWaiting(isWaiting);
|
|
343
|
+
setIsDue(false);
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
useEffect(() => {
|
|
347
|
+
if (!isWaiting) {
|
|
348
|
+
return undefined;
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
const timer = setTimeout(() => {
|
|
352
|
+
setIsDue(true);
|
|
353
|
+
}, delayMs);
|
|
354
|
+
return (): void => {
|
|
355
|
+
clearTimeout(timer);
|
|
356
|
+
};
|
|
357
|
+
}, [isWaiting, delayMs]);
|
|
358
|
+
return isWaiting && isDue;
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
type SpotlightState = {
|
|
362
|
+
readonly hole: Box | undefined;
|
|
363
|
+
readonly transition: SpotlightTransition | undefined;
|
|
364
|
+
/** Moving to a new target right now (the tooltip waits a moment before entering). */
|
|
365
|
+
readonly isMorphing: boolean;
|
|
366
|
+
};
|
|
367
|
+
|
|
368
|
+
/**
|
|
369
|
+
* Where the spotlight's hole is and how it gets there. While the next target is scrolled into
|
|
370
|
+
* view, the hole goes with it (frame by frame, or straight to where it will end up); otherwise it
|
|
371
|
+
* marks the step on screen. Moving to another step is animated; following a target that moves on
|
|
372
|
+
* its own (layout changes, resizing) is instant. Nothing animates before the overlay has appeared.
|
|
373
|
+
*/
|
|
374
|
+
function useSpotlight(view: TourViewState, isVisible: boolean, isReducedMotion: boolean): SpotlightState {
|
|
375
|
+
const {shown, phase, revealHighlight, followsScroll} = view;
|
|
376
|
+
const isRevealing = phase === 'revealing';
|
|
377
|
+
const source = isRevealing ? revealHighlight : shown?.highlight;
|
|
378
|
+
const hole = source ? inflate(source, tourLayout.highlightPadding) : undefined;
|
|
379
|
+
const shownKey = shown?.transitionId ?? 'none';
|
|
380
|
+
const key = isRevealing ? `reveal:${shownKey}` : `${shownKey}:${hole ? 'open' : 'closed'}`;
|
|
381
|
+
|
|
382
|
+
// The key at which the overlay appeared: it appears in place, later changes animate.
|
|
383
|
+
const [appearedAtKey, setAppearedAtKey] = useState<string | undefined>(undefined);
|
|
384
|
+
if (isVisible && appearedAtKey === undefined) {
|
|
385
|
+
setAppearedAtKey(key);
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
// A move to a new key keeps animating until it has had time to finish.
|
|
389
|
+
const [settledKey, setSettledKey] = useState(key);
|
|
390
|
+
useEffect(() => {
|
|
391
|
+
if (key === settledKey) {
|
|
392
|
+
return undefined;
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
const timer = setTimeout(() => {
|
|
396
|
+
setSettledKey(key);
|
|
397
|
+
}, motion.morphMs + 50);
|
|
398
|
+
return (): void => {
|
|
399
|
+
clearTimeout(timer);
|
|
400
|
+
};
|
|
401
|
+
}, [key, settledKey]);
|
|
402
|
+
|
|
403
|
+
const hasAppeared = appearedAtKey !== undefined;
|
|
404
|
+
const isMorphing = hasAppeared && key !== appearedAtKey && key !== settledKey;
|
|
405
|
+
let transition: SpotlightTransition | undefined;
|
|
406
|
+
if (hasAppeared && !isReducedMotion) {
|
|
407
|
+
if (isRevealing && followsScroll) {
|
|
408
|
+
transition = {durationMs: motion.chaseMs, easing: 'linear'};
|
|
409
|
+
} else if (isMorphing) {
|
|
410
|
+
transition = {durationMs: motion.morphMs, easing: 'standard'};
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
return {hole, transition, isMorphing};
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
/** "2 of 5: Title" when the step changes (the first step is announced by the tour appearing). */
|
|
418
|
+
function useAnnouncement(
|
|
419
|
+
shown: ShownStep | undefined,
|
|
420
|
+
totalSteps: number,
|
|
421
|
+
i18n: ResolvedI18n,
|
|
422
|
+
isClosing: boolean,
|
|
423
|
+
): string {
|
|
424
|
+
const [firstKey, setFirstKey] = useState<number | undefined>(undefined);
|
|
425
|
+
if (shown && firstKey === undefined) {
|
|
426
|
+
setFirstKey(shown.transitionId);
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
if (!shown || isClosing || shown.transitionId === firstKey) {
|
|
430
|
+
return '';
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
const counter = formatTemplate(i18n.stepCounterTemplate, {current: shown.stepIndex + 1, total: totalSteps});
|
|
434
|
+
const title = textOf(shown.step.title);
|
|
435
|
+
return title ? `${counter}: ${title}` : counter;
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
/** The plain text of React content (strings, numbers, arrays and the children of elements). */
|
|
439
|
+
export function textOf(node: ReactNode): string {
|
|
440
|
+
if (typeof node === 'string' || typeof node === 'number') {
|
|
441
|
+
return String(node);
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
if (Array.isArray(node)) {
|
|
445
|
+
return node.map((child: ReactNode) => textOf(child)).join('');
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
if (isValidElement<{children?: ReactNode}>(node)) {
|
|
449
|
+
return textOf(node.props.children);
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
return '';
|
|
453
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type {StartTourOptions} from './tour-end-info.type.js';
|
|
2
|
+
import type {TourStep} from './tour-step.type.js';
|
|
3
|
+
|
|
4
|
+
/** What `useTour()` returns. The functions keep their identity for the lifetime of the provider. */
|
|
5
|
+
export type TourContextType = {
|
|
6
|
+
/** `true` from `startTour` until the tour ends. */
|
|
7
|
+
readonly isActive: boolean;
|
|
8
|
+
/** Zero-based index of the step on screen. */
|
|
9
|
+
readonly currentStep: number;
|
|
10
|
+
readonly steps: readonly TourStep[];
|
|
11
|
+
readonly currentTourId: string | undefined;
|
|
12
|
+
/**
|
|
13
|
+
* Starts a tour. Starting the tour that is already running (same id, or same steps array without
|
|
14
|
+
* an id) does nothing; starting a different one ends the current tour with the reason `'closed'`.
|
|
15
|
+
*/
|
|
16
|
+
readonly startTour: (steps: readonly TourStep[], tourId?: string, options?: StartTourOptions) => void;
|
|
17
|
+
/** Ends the tour with the reason `'closed'`. */
|
|
18
|
+
readonly endTour: () => void;
|
|
19
|
+
/** Goes to the next step, or completes the tour on the last step. */
|
|
20
|
+
readonly nextStep: () => void;
|
|
21
|
+
readonly prevStep: () => void;
|
|
22
|
+
readonly goToStep: (index: number) => void;
|
|
23
|
+
};
|