@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,594 @@
|
|
|
1
|
+
import {
|
|
2
|
+
type CSSProperties,
|
|
3
|
+
type KeyboardEvent as ReactKeyboardEvent,
|
|
4
|
+
type ReactNode,
|
|
5
|
+
useEffect,
|
|
6
|
+
useInsertionEffect,
|
|
7
|
+
useRef,
|
|
8
|
+
useState,
|
|
9
|
+
} from 'react';
|
|
10
|
+
import {clamp} from '../../shared/geometry/box.util.js';
|
|
11
|
+
import type {Box} from '../../shared/geometry/geometry.type.js';
|
|
12
|
+
import {useIsomorphicLayoutEffect} from '../../shared/react/use-isomorphic-layout-effect.hook.js';
|
|
13
|
+
import {motion} from '../../shared/renderer/motion.js';
|
|
14
|
+
import type {
|
|
15
|
+
ArrowProps,
|
|
16
|
+
BackdropProps,
|
|
17
|
+
ButtonProps,
|
|
18
|
+
CardParts,
|
|
19
|
+
CardProps,
|
|
20
|
+
CloseButtonProps,
|
|
21
|
+
ContainerProps,
|
|
22
|
+
CounterProps,
|
|
23
|
+
DotProps,
|
|
24
|
+
DotsProps,
|
|
25
|
+
LayerProps,
|
|
26
|
+
MeasureFrameProps,
|
|
27
|
+
SpotlightProps,
|
|
28
|
+
TextPartProps,
|
|
29
|
+
TooltipFrameProps,
|
|
30
|
+
TourRenderer,
|
|
31
|
+
} from '../../shared/renderer/renderer.type.js';
|
|
32
|
+
import {handlesKey, pageDirection} from '../dom/elements.util.js';
|
|
33
|
+
import {lockScroll} from '../dom/scroll-lock.util.js';
|
|
34
|
+
import {injectTourStyles} from './styles.js';
|
|
35
|
+
|
|
36
|
+
/** Custom properties (`--rtk-*`) in inline styles. */
|
|
37
|
+
type StyleWithVariables = CSSProperties & Record<`--rtk-${string}`, string>;
|
|
38
|
+
|
|
39
|
+
const [x1, y1, x2, y2] = motion.easing;
|
|
40
|
+
const standardEasing = `cubic-bezier(${x1}, ${y1}, ${x2}, ${y2})`;
|
|
41
|
+
|
|
42
|
+
// ---------------------------------------------------------------------------------------------
|
|
43
|
+
// Layer: a modal <dialog> in the top layer
|
|
44
|
+
// ---------------------------------------------------------------------------------------------
|
|
45
|
+
|
|
46
|
+
/** Elements made inert by the fallback below, so they can be restored. */
|
|
47
|
+
const inertedByTour = new WeakMap<HTMLDialogElement, Element[]>();
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Opens the dialog as a modal: top layer, rest of the page inert, focus restored on close.
|
|
51
|
+
* Without `showModal` (very old browsers, jsdom) the dialog is shown with a high z-index
|
|
52
|
+
* (`--rtk-z-index`) and the rest of the page is made inert manually.
|
|
53
|
+
*/
|
|
54
|
+
function openModal(dialog: HTMLDialogElement): void {
|
|
55
|
+
if (dialog.open || dialog.hasAttribute('open')) {
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
if (typeof dialog.showModal === 'function') {
|
|
60
|
+
try {
|
|
61
|
+
dialog.showModal();
|
|
62
|
+
return;
|
|
63
|
+
} catch {
|
|
64
|
+
// Not connected or not allowed: fall through to the fallback.
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
dialog.setAttribute('open', '');
|
|
69
|
+
const others = [...document.body.children].filter(
|
|
70
|
+
(element) => element !== dialog && !element.contains(dialog) && !element.hasAttribute('inert'),
|
|
71
|
+
);
|
|
72
|
+
for (const element of others) {
|
|
73
|
+
element.setAttribute('inert', '');
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
inertedByTour.set(dialog, others);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function closeModal(dialog: HTMLDialogElement): void {
|
|
80
|
+
if (typeof dialog.close === 'function' && dialog.open) {
|
|
81
|
+
dialog.close();
|
|
82
|
+
} else {
|
|
83
|
+
dialog.removeAttribute('open');
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
for (const element of inertedByTour.get(dialog) ?? []) {
|
|
87
|
+
element.removeAttribute('inert');
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
inertedByTour.delete(dialog);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function safeMatches(element: Element, selector: string): boolean {
|
|
94
|
+
try {
|
|
95
|
+
return element.matches(selector);
|
|
96
|
+
} catch {
|
|
97
|
+
return false;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/** The tooltip on screen, to put focus on. */
|
|
102
|
+
function shownTooltip(dialog: HTMLDialogElement): HTMLElement | null {
|
|
103
|
+
return dialog.querySelector<HTMLElement>('[data-rtk-tooltip]:not([data-hidden])');
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Returns focus to where it was when the tour opened. Browsers do this when a modal dialog closes,
|
|
108
|
+
* but forget where it was once the dialog has been re-opened over one of the app's own.
|
|
109
|
+
*/
|
|
110
|
+
function restoreFocus(trigger: Element | null, dialog: HTMLDialogElement): void {
|
|
111
|
+
if (!(trigger instanceof HTMLElement) || trigger === document.body || !trigger.isConnected) {
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
const active = document.activeElement;
|
|
116
|
+
if (!active || active === document.body || dialog.contains(active)) {
|
|
117
|
+
trigger.focus({preventScroll: true});
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* A modal `<dialog>` in the top layer: above any z-index, with the page inert, focus restored on
|
|
123
|
+
* close and the page kept from scrolling. Keys and the dialog's own close requests become intents.
|
|
124
|
+
*/
|
|
125
|
+
function Layer({
|
|
126
|
+
children,
|
|
127
|
+
state,
|
|
128
|
+
isVisible,
|
|
129
|
+
fadeMs,
|
|
130
|
+
isInteractive,
|
|
131
|
+
theme,
|
|
132
|
+
titleId,
|
|
133
|
+
descriptionId,
|
|
134
|
+
fallbackLabel,
|
|
135
|
+
announcement,
|
|
136
|
+
stepKey,
|
|
137
|
+
onIntent,
|
|
138
|
+
}: LayerProps): ReactNode {
|
|
139
|
+
const layerRef = useRef<HTMLDialogElement | null>(null);
|
|
140
|
+
const liveRef = useRef<HTMLDivElement | null>(null);
|
|
141
|
+
const reopening = useRef(false);
|
|
142
|
+
const [direction] = useState(pageDirection);
|
|
143
|
+
|
|
144
|
+
useInsertionEffect(() => {
|
|
145
|
+
injectTourStyles(document);
|
|
146
|
+
}, []);
|
|
147
|
+
|
|
148
|
+
useIsomorphicLayoutEffect(() => {
|
|
149
|
+
const dialog = layerRef.current;
|
|
150
|
+
if (!dialog) {
|
|
151
|
+
return undefined;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
const trigger = document.activeElement;
|
|
155
|
+
openModal(dialog);
|
|
156
|
+
const unlock = lockScroll(dialog);
|
|
157
|
+
return (): void => {
|
|
158
|
+
unlock();
|
|
159
|
+
reopening.current = true;
|
|
160
|
+
closeModal(dialog);
|
|
161
|
+
restoreFocus(trigger, dialog);
|
|
162
|
+
};
|
|
163
|
+
}, []);
|
|
164
|
+
|
|
165
|
+
// If the app opened its own modal (in onBeforeStep), move the tour back on top of it.
|
|
166
|
+
useEffect(() => {
|
|
167
|
+
const dialog = layerRef.current;
|
|
168
|
+
if (!dialog || stepKey === undefined) {
|
|
169
|
+
return;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
const other = [...document.querySelectorAll('dialog')].find(
|
|
173
|
+
(element) => element !== dialog && element.open && safeMatches(element, ':modal'),
|
|
174
|
+
);
|
|
175
|
+
if (other && dialog.open && safeMatches(dialog, ':modal')) {
|
|
176
|
+
const focused = document.activeElement;
|
|
177
|
+
// `close` fires asynchronously; the handler consumes this flag.
|
|
178
|
+
reopening.current = true;
|
|
179
|
+
closeModal(dialog);
|
|
180
|
+
openModal(dialog);
|
|
181
|
+
// Opening focuses the first button: keep focus where it was in the tour.
|
|
182
|
+
const refocus = focused instanceof HTMLElement && dialog.contains(focused) ? focused : shownTooltip(dialog);
|
|
183
|
+
refocus?.focus({preventScroll: true});
|
|
184
|
+
}
|
|
185
|
+
}, [stepKey]);
|
|
186
|
+
|
|
187
|
+
// A custom tooltip may not render an element with the title id: then label the dialog directly.
|
|
188
|
+
useEffect(() => {
|
|
189
|
+
const dialog = layerRef.current;
|
|
190
|
+
if (!dialog || titleId === undefined) {
|
|
191
|
+
return;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
// `getElementById`: ids from useId() contain characters that would need escaping in selectors.
|
|
195
|
+
// eslint-disable-next-line unicorn/prefer-query-selector
|
|
196
|
+
if (document.getElementById(titleId)) {
|
|
197
|
+
dialog.removeAttribute('aria-label');
|
|
198
|
+
} else {
|
|
199
|
+
dialog.setAttribute('aria-label', fallbackLabel);
|
|
200
|
+
}
|
|
201
|
+
}, [stepKey, titleId, fallbackLabel]);
|
|
202
|
+
|
|
203
|
+
useEffect(() => {
|
|
204
|
+
if (announcement && liveRef.current) {
|
|
205
|
+
liveRef.current.textContent = announcement;
|
|
206
|
+
}
|
|
207
|
+
}, [announcement, stepKey]);
|
|
208
|
+
|
|
209
|
+
const style: StyleWithVariables = {
|
|
210
|
+
'--rtk-primary': theme.primaryColor,
|
|
211
|
+
'--rtk-on-primary': theme.primaryTextColor,
|
|
212
|
+
'--rtk-bg': theme.tooltipBackground,
|
|
213
|
+
'--rtk-title': theme.titleColor,
|
|
214
|
+
'--rtk-text': theme.contentColor,
|
|
215
|
+
'--rtk-overlay': theme.overlayColor,
|
|
216
|
+
'--rtk-fade-duration': `${fadeMs}ms`,
|
|
217
|
+
};
|
|
218
|
+
|
|
219
|
+
const onKeyDown = (event: ReactKeyboardEvent<HTMLDialogElement>): void => {
|
|
220
|
+
const {nativeEvent, key} = event;
|
|
221
|
+
if (
|
|
222
|
+
!isInteractive ||
|
|
223
|
+
event.defaultPrevented ||
|
|
224
|
+
nativeEvent.isComposing ||
|
|
225
|
+
event.altKey ||
|
|
226
|
+
event.ctrlKey ||
|
|
227
|
+
event.metaKey
|
|
228
|
+
) {
|
|
229
|
+
return;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
if (key === 'Escape') {
|
|
233
|
+
event.preventDefault();
|
|
234
|
+
onIntent('escape');
|
|
235
|
+
return;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
if ((key === 'ArrowRight' || key === 'ArrowLeft') && !handlesKey(event.target, key)) {
|
|
239
|
+
event.preventDefault();
|
|
240
|
+
// In right-to-left pages, the arrow pointing to the start goes forward.
|
|
241
|
+
const isForward = (key === 'ArrowRight') !== (direction === 'rtl');
|
|
242
|
+
onIntent(isForward ? 'next' : 'previous');
|
|
243
|
+
}
|
|
244
|
+
};
|
|
245
|
+
|
|
246
|
+
return (
|
|
247
|
+
<dialog
|
|
248
|
+
ref={layerRef}
|
|
249
|
+
className="rtk-root"
|
|
250
|
+
data-rtk-root=""
|
|
251
|
+
data-state={state}
|
|
252
|
+
data-visible={isVisible ? '' : undefined}
|
|
253
|
+
dir={direction === 'rtl' ? 'rtl' : undefined}
|
|
254
|
+
aria-labelledby={titleId}
|
|
255
|
+
aria-describedby={descriptionId}
|
|
256
|
+
style={style}
|
|
257
|
+
onKeyDown={onKeyDown}
|
|
258
|
+
onCancel={(event): void => {
|
|
259
|
+
// Escape (and the Android back gesture) fire `cancel`; the tour decides how to close.
|
|
260
|
+
event.preventDefault();
|
|
261
|
+
onIntent('escape');
|
|
262
|
+
}}
|
|
263
|
+
onClose={(): void => {
|
|
264
|
+
if (reopening.current) {
|
|
265
|
+
reopening.current = false;
|
|
266
|
+
return;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
// The browser closed the dialog by force (for example a second Escape press).
|
|
270
|
+
onIntent('escape');
|
|
271
|
+
}}
|
|
272
|
+
>
|
|
273
|
+
{children}
|
|
274
|
+
<div ref={liveRef} className="rtk-sr-only" aria-live="polite" aria-atomic="true" />
|
|
275
|
+
</dialog>
|
|
276
|
+
);
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
function Backdrop({isDismissible, onPress}: BackdropProps): ReactNode {
|
|
280
|
+
return (
|
|
281
|
+
<div
|
|
282
|
+
className="rtk-backdrop"
|
|
283
|
+
data-rtk-backdrop=""
|
|
284
|
+
data-closable={isDismissible ? '' : undefined}
|
|
285
|
+
onClick={(event): void => {
|
|
286
|
+
onPress({x: event.clientX, y: event.clientY});
|
|
287
|
+
}}
|
|
288
|
+
/>
|
|
289
|
+
);
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
const spotlightProperties = ['left', 'top', 'width', 'height', 'border-radius'];
|
|
293
|
+
|
|
294
|
+
/** How far off screen a hole may be: past the ring around it. */
|
|
295
|
+
const offScreenMargin = 16;
|
|
296
|
+
|
|
297
|
+
/**
|
|
298
|
+
* Keeps a hole that is far off screen (a target still to be scrolled to) just past the screen's
|
|
299
|
+
* edge: the dim is the hole's shadow, which only reaches so far.
|
|
300
|
+
*/
|
|
301
|
+
function nearScreen(hole: Box): Box {
|
|
302
|
+
const width = document.documentElement.clientWidth || globalThis.innerWidth;
|
|
303
|
+
const height = document.documentElement.clientHeight || globalThis.innerHeight;
|
|
304
|
+
return {
|
|
305
|
+
...hole,
|
|
306
|
+
x: clamp(hole.x, -offScreenMargin - hole.width, width + offScreenMargin),
|
|
307
|
+
y: clamp(hole.y, -offScreenMargin - hole.height, height + offScreenMargin),
|
|
308
|
+
};
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
/** The dimmed overlay with a rounded hole, drawn with a single box-shadow. */
|
|
312
|
+
function Spotlight({hole: requested, transition}: SpotlightProps): ReactNode {
|
|
313
|
+
const hole = requested ? nearScreen(requested) : undefined;
|
|
314
|
+
const style: CSSProperties = hole
|
|
315
|
+
? {
|
|
316
|
+
left: hole.x,
|
|
317
|
+
top: hole.y,
|
|
318
|
+
width: hole.width,
|
|
319
|
+
height: hole.height,
|
|
320
|
+
borderRadius: `min(var(--rtk-spotlight-radius), ${hole.width / 2}px, ${hole.height / 2}px)`,
|
|
321
|
+
}
|
|
322
|
+
: {left: '50%', top: '50%', width: 0, height: 0, borderRadius: 0};
|
|
323
|
+
if (transition) {
|
|
324
|
+
const easing = transition.easing === 'linear' ? 'linear' : standardEasing;
|
|
325
|
+
style.transition = spotlightProperties
|
|
326
|
+
.map((property) => `${property} ${transition.durationMs}ms ${easing}`)
|
|
327
|
+
.join(', ');
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
return (
|
|
331
|
+
<div
|
|
332
|
+
aria-hidden="true"
|
|
333
|
+
className="rtk-spotlight"
|
|
334
|
+
data-rtk-spotlight=""
|
|
335
|
+
data-collapsed={hole ? undefined : ''}
|
|
336
|
+
style={style}
|
|
337
|
+
/>
|
|
338
|
+
);
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
function isVerticalWritingMode(element: Element): boolean {
|
|
342
|
+
return getComputedStyle(element).writingMode.startsWith('vertical');
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
/**
|
|
346
|
+
* True when the tooltip's content is bigger than its frame (it reached the size limits), so the
|
|
347
|
+
* frame has to scroll. Decorations sticking out of the content (the arrow) do not count.
|
|
348
|
+
*/
|
|
349
|
+
function overflowsFrame(frame: HTMLElement): boolean {
|
|
350
|
+
const children = [...frame.children].filter((child) => child instanceof HTMLElement);
|
|
351
|
+
if (children.length === 0 || children.length < frame.children.length) {
|
|
352
|
+
return frame.scrollHeight > frame.clientHeight + 1 || frame.scrollWidth > frame.clientWidth + 1;
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
let left = 0;
|
|
356
|
+
let top = 0;
|
|
357
|
+
let right = 0;
|
|
358
|
+
let bottom = 0;
|
|
359
|
+
for (const child of children) {
|
|
360
|
+
left = Math.min(left, child.offsetLeft);
|
|
361
|
+
top = Math.min(top, child.offsetTop);
|
|
362
|
+
right = Math.max(right, child.offsetLeft + child.offsetWidth);
|
|
363
|
+
bottom = Math.max(bottom, child.offsetTop + child.offsetHeight);
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
return left < -1 || top < -1 || right > frame.clientWidth + 1 || bottom > frame.clientHeight + 1;
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
/** Positions the tooltip (with `translate`, so the entrance's `transform` never moves it), reports its size and takes focus. */
|
|
370
|
+
function TooltipFrame({children, layout, entrance, isBusy, isHidden, onSize}: TooltipFrameProps): ReactNode {
|
|
371
|
+
const ref = useRef<HTMLDivElement | null>(null);
|
|
372
|
+
const [isEntering, setIsEntering] = useState(true);
|
|
373
|
+
const [isOverflowing, setIsOverflowing] = useState(false);
|
|
374
|
+
|
|
375
|
+
// Report size changes (late-loading fonts or images, content changes); keep scrolling available
|
|
376
|
+
// when the content does not fit. Sizes come from ResizeObserver's border box: unlike
|
|
377
|
+
// getBoundingClientRect it ignores the entrance animation's scale. The content is observed too:
|
|
378
|
+
// it can keep growing once the frame has reached its size limits.
|
|
379
|
+
useIsomorphicLayoutEffect(() => {
|
|
380
|
+
const element = ref.current;
|
|
381
|
+
if (!element) {
|
|
382
|
+
return undefined;
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
const report = (box: ResizeObserverSize | undefined): void => {
|
|
386
|
+
const isVertical = isVerticalWritingMode(element);
|
|
387
|
+
const width = box ? (isVertical ? box.blockSize : box.inlineSize) : element.offsetWidth;
|
|
388
|
+
const height = box ? (isVertical ? box.inlineSize : box.blockSize) : element.offsetHeight;
|
|
389
|
+
onSize({width, height});
|
|
390
|
+
};
|
|
391
|
+
|
|
392
|
+
if (typeof ResizeObserver !== 'function') {
|
|
393
|
+
// No ResizeObserver (jsdom): report the size once.
|
|
394
|
+
report(undefined);
|
|
395
|
+
return undefined;
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
const observer = new ResizeObserver((entries) => {
|
|
399
|
+
setIsOverflowing(overflowsFrame(element));
|
|
400
|
+
const own = entries.find((entry) => entry.target === element);
|
|
401
|
+
if (own) {
|
|
402
|
+
report(own.borderBoxSize?.[0]);
|
|
403
|
+
}
|
|
404
|
+
});
|
|
405
|
+
observer.observe(element);
|
|
406
|
+
for (const child of element.children) {
|
|
407
|
+
observer.observe(child);
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
return (): void => {
|
|
411
|
+
observer.disconnect();
|
|
412
|
+
};
|
|
413
|
+
}, [onSize]);
|
|
414
|
+
|
|
415
|
+
// Move focus into the tooltip so screen readers read it and keyboard users can act on it.
|
|
416
|
+
useEffect(() => {
|
|
417
|
+
ref.current?.focus({preventScroll: true});
|
|
418
|
+
}, []);
|
|
419
|
+
|
|
420
|
+
const style: StyleWithVariables = {
|
|
421
|
+
translate: `${layout.x}px ${layout.y}px`,
|
|
422
|
+
maxWidth: layout.maxWidth,
|
|
423
|
+
maxHeight: layout.maxHeight,
|
|
424
|
+
'--rtk-max-height': `${layout.maxHeight}px`,
|
|
425
|
+
'--rtk-enter-x': `${entrance.dx}px`,
|
|
426
|
+
'--rtk-enter-y': `${entrance.dy}px`,
|
|
427
|
+
'--rtk-enter-scale': String(entrance.scale),
|
|
428
|
+
'--rtk-enter-duration': `${entrance.durationMs}ms`,
|
|
429
|
+
'--rtk-enter-delay': `${entrance.delayMs}ms`,
|
|
430
|
+
};
|
|
431
|
+
|
|
432
|
+
return (
|
|
433
|
+
<div
|
|
434
|
+
ref={ref}
|
|
435
|
+
className="rtk-tooltip"
|
|
436
|
+
data-rtk-tooltip=""
|
|
437
|
+
data-placement={layout.placement}
|
|
438
|
+
data-entering={isEntering ? '' : undefined}
|
|
439
|
+
data-hidden={isHidden ? '' : undefined}
|
|
440
|
+
data-overflowing={isOverflowing ? '' : undefined}
|
|
441
|
+
aria-busy={isBusy ? 'true' : undefined}
|
|
442
|
+
tabIndex={-1}
|
|
443
|
+
style={style}
|
|
444
|
+
onAnimationEnd={(event): void => {
|
|
445
|
+
// Animations inside a custom tooltip bubble up here too.
|
|
446
|
+
if (event.target === event.currentTarget) {
|
|
447
|
+
setIsEntering(false);
|
|
448
|
+
}
|
|
449
|
+
}}
|
|
450
|
+
>
|
|
451
|
+
{children}
|
|
452
|
+
</div>
|
|
453
|
+
);
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
function MeasureFrame({children, maxWidth, maxHeight, onSize}: MeasureFrameProps): ReactNode {
|
|
457
|
+
const ref = useRef<HTMLDivElement | null>(null);
|
|
458
|
+
useIsomorphicLayoutEffect(() => {
|
|
459
|
+
const element = ref.current;
|
|
460
|
+
if (element) {
|
|
461
|
+
const rect = element.getBoundingClientRect();
|
|
462
|
+
onSize({width: rect.width, height: rect.height});
|
|
463
|
+
}
|
|
464
|
+
}, [onSize]);
|
|
465
|
+
|
|
466
|
+
const style: StyleWithVariables = {maxWidth, maxHeight, '--rtk-max-height': `${maxHeight}px`};
|
|
467
|
+
return (
|
|
468
|
+
<div ref={ref} aria-hidden="true" className="rtk-measure" style={style}>
|
|
469
|
+
{children}
|
|
470
|
+
</div>
|
|
471
|
+
);
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
// ---------------------------------------------------------------------------------------------
|
|
475
|
+
// Card parts
|
|
476
|
+
// ---------------------------------------------------------------------------------------------
|
|
477
|
+
|
|
478
|
+
function CloseIcon(): ReactNode {
|
|
479
|
+
return (
|
|
480
|
+
<svg aria-hidden="true" width="16" height="16" viewBox="0 0 16 16" fill="none" focusable="false">
|
|
481
|
+
<path d="M4 4l8 8M12 4l-8 8" stroke="currentColor" strokeWidth="1.75" strokeLinecap="round" />
|
|
482
|
+
</svg>
|
|
483
|
+
);
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
function Card({placement, children}: CardProps): ReactNode {
|
|
487
|
+
return (
|
|
488
|
+
<div className="rtk-card" data-placement={placement}>
|
|
489
|
+
{children}
|
|
490
|
+
</div>
|
|
491
|
+
);
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
function Arrow({side, offset}: ArrowProps): ReactNode {
|
|
495
|
+
const style: StyleWithVariables = {'--rtk-arrow-offset': `${offset}px`};
|
|
496
|
+
return <div aria-hidden="true" className="rtk-arrow" data-side={side} style={style} />;
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
function Header({children}: ContainerProps): ReactNode {
|
|
500
|
+
return <div className="rtk-header">{children}</div>;
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
function Counter({text}: CounterProps): ReactNode {
|
|
504
|
+
return <span className="rtk-counter">{text}</span>;
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
function CloseButton({label, onPress}: CloseButtonProps): ReactNode {
|
|
508
|
+
return (
|
|
509
|
+
<button type="button" className="rtk-close" aria-label={label} onClick={onPress}>
|
|
510
|
+
<CloseIcon />
|
|
511
|
+
</button>
|
|
512
|
+
);
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
function Body({children}: ContainerProps): ReactNode {
|
|
516
|
+
return <div className="rtk-body">{children}</div>;
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
/** A heading by role rather than an `<h2>`: global heading styles (resets, typography) do not reach it. */
|
|
520
|
+
function Title({id, children}: TextPartProps): ReactNode {
|
|
521
|
+
return (
|
|
522
|
+
<div id={id} role="heading" aria-level={2} className="rtk-title">
|
|
523
|
+
{children}
|
|
524
|
+
</div>
|
|
525
|
+
);
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
function Content({id, children}: TextPartProps): ReactNode {
|
|
529
|
+
return (
|
|
530
|
+
<div id={id} className="rtk-content">
|
|
531
|
+
{children}
|
|
532
|
+
</div>
|
|
533
|
+
);
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
function Footer({children}: ContainerProps): ReactNode {
|
|
537
|
+
return <div className="rtk-footer">{children}</div>;
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
function Button({kind, label, isDisabled, onPress}: ButtonProps): ReactNode {
|
|
541
|
+
return (
|
|
542
|
+
<button
|
|
543
|
+
type="button"
|
|
544
|
+
className={kind === 'next' ? 'rtk-button rtk-button--next' : 'rtk-button rtk-button--prev'}
|
|
545
|
+
disabled={isDisabled}
|
|
546
|
+
onClick={onPress}
|
|
547
|
+
>
|
|
548
|
+
{label}
|
|
549
|
+
</button>
|
|
550
|
+
);
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
function Dots({label, children}: DotsProps): ReactNode {
|
|
554
|
+
return (
|
|
555
|
+
<div className="rtk-dots" role="group" aria-label={label}>
|
|
556
|
+
{children}
|
|
557
|
+
</div>
|
|
558
|
+
);
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
function Dot({label, isCurrent, onPress}: DotProps): ReactNode {
|
|
562
|
+
return (
|
|
563
|
+
<button
|
|
564
|
+
type="button"
|
|
565
|
+
className="rtk-dot"
|
|
566
|
+
aria-label={label}
|
|
567
|
+
aria-current={isCurrent ? 'step' : undefined}
|
|
568
|
+
onClick={onPress}
|
|
569
|
+
/>
|
|
570
|
+
);
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
function Ellipsis(): ReactNode {
|
|
574
|
+
return <span aria-hidden="true" className="rtk-ellipsis" />;
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
const card: CardParts = {
|
|
578
|
+
Card,
|
|
579
|
+
Arrow,
|
|
580
|
+
Header,
|
|
581
|
+
Counter,
|
|
582
|
+
CloseButton,
|
|
583
|
+
Body,
|
|
584
|
+
Title,
|
|
585
|
+
Content,
|
|
586
|
+
Footer,
|
|
587
|
+
Button,
|
|
588
|
+
Dots,
|
|
589
|
+
Dot,
|
|
590
|
+
Ellipsis,
|
|
591
|
+
};
|
|
592
|
+
|
|
593
|
+
/** How the web draws the tour. */
|
|
594
|
+
export const webRenderer: TourRenderer = {Layer, Backdrop, Spotlight, TooltipFrame, MeasureFrame, card};
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import {type ReactNode, useEffect, useState} from 'react';
|
|
2
|
+
import {createPortal} from 'react-dom';
|
|
3
|
+
import {TourRoot} from '../shared/react/tour-root.js';
|
|
4
|
+
import {type TourProviderBaseProps, useTourCore} from '../shared/react/tour-provider-core.js';
|
|
5
|
+
import {useIsomorphicLayoutEffect} from '../shared/react/use-isomorphic-layout-effect.hook.js';
|
|
6
|
+
import {TourOverlay, type TourOverlayProps} from '../shared/renderer/tour-overlay.js';
|
|
7
|
+
import {acquireSafeAreaProbe} from './dom/viewport.util.js';
|
|
8
|
+
import {createWebPlatform, type WebPlatform} from './dom/web-platform.js';
|
|
9
|
+
import {webRenderer} from './layer/web-renderer.js';
|
|
10
|
+
|
|
11
|
+
export type TourProviderProps = TourProviderBaseProps;
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Provides `useTour()` to its children and renders the tour. Place it around your app, inside
|
|
15
|
+
* your router if steps navigate between pages.
|
|
16
|
+
*/
|
|
17
|
+
export function TourProvider(props: TourProviderProps): ReactNode {
|
|
18
|
+
const [platform] = useState(createWebPlatform);
|
|
19
|
+
const {engine, overlay} = useTourCore(props, platform, webRenderer);
|
|
20
|
+
useEffect(() => acquireSafeAreaProbe(), []);
|
|
21
|
+
return (
|
|
22
|
+
<TourRoot engine={engine}>
|
|
23
|
+
{props.children}
|
|
24
|
+
<WebMount platform={platform} overlay={overlay} />
|
|
25
|
+
</TourRoot>
|
|
26
|
+
);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/** Draws the tour into its own element at the end of `<body>`, on the client only (after hydration). */
|
|
30
|
+
function WebMount({
|
|
31
|
+
platform,
|
|
32
|
+
overlay,
|
|
33
|
+
}: {
|
|
34
|
+
readonly platform: WebPlatform;
|
|
35
|
+
readonly overlay: TourOverlayProps;
|
|
36
|
+
}): ReactNode {
|
|
37
|
+
const [container, setContainer] = useState<HTMLElement | undefined>(undefined);
|
|
38
|
+
useIsomorphicLayoutEffect(() => {
|
|
39
|
+
const host = platform.mountHost();
|
|
40
|
+
setContainer(host.container);
|
|
41
|
+
return host.unmount;
|
|
42
|
+
}, [platform]);
|
|
43
|
+
return container ? createPortal(<TourOverlay {...overlay} />, container) : null;
|
|
44
|
+
}
|
package/src/react.ts
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
// Web entry point: import from '@robertlinde/react-tour-kit/react'.
|
|
4
|
+
|
|
5
|
+
export {TourProvider, type TourProviderProps} from './react-platform/tour-provider.js';
|
|
6
|
+
export {useTour} from './shared/react/use-tour.hook.js';
|
|
7
|
+
export {getTruncatedDots, type DotItem} from './shared/utils/get-truncated-dots.util.js';
|
|
8
|
+
export type {TourContextType} from './shared/types/tour-context.type.js';
|
|
9
|
+
export type {StartTourOptions, TourEndCallback, TourEndInfo, TourEndReason} from './shared/types/tour-end-info.type.js';
|
|
10
|
+
export type {TourI18n} from './shared/types/tour-i18n.type.js';
|
|
11
|
+
export type {TourPlacement, TourStep, TourStepContext, TourTarget} from './shared/types/tour-step.type.js';
|
|
12
|
+
export type {TourTheme} from './shared/types/tour-theme.type.js';
|
|
13
|
+
export type {TourTooltipProps} from './shared/types/tour-tooltip-props.type.js';
|