@player-ui/auto-scroll-manager-plugin-react 0.15.2-next.1 → 0.15.2-next.3
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/dist/cjs/index.cjs +1 -0
- package/dist/cjs/index.cjs.map +1 -1
- package/dist/index.legacy-esm.js +1 -0
- package/dist/index.mjs +1 -0
- package/dist/index.mjs.map +1 -1
- package/package.json +8 -8
- package/src/__tests__/plugin.test.tsx +57 -0
- package/src/plugin.tsx +2 -1
package/dist/cjs/index.cjs
CHANGED
|
@@ -169,6 +169,7 @@ var AutoScrollManagerPlugin = class {
|
|
|
169
169
|
this.failedNavigation = false;
|
|
170
170
|
this.alreadyScrolledTo = [];
|
|
171
171
|
this.clearScrollableMap();
|
|
172
|
+
window.dispatchEvent(new WheelEvent("wheel", { bubbles: true }));
|
|
172
173
|
window.scroll(0, 0);
|
|
173
174
|
});
|
|
174
175
|
flow.hooks.skipTransition.intercept({
|
package/dist/cjs/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../../../../../../../../../execroot/_main/bazel-out/k8-fastbuild/bin/plugins/auto-scroll/react/src/index.tsx","../../../../../../../../../../../../../execroot/_main/bazel-out/k8-fastbuild/bin/plugins/auto-scroll/react/src/hooks.tsx","../../../../../../../../../../../../../execroot/_main/bazel-out/k8-fastbuild/bin/plugins/auto-scroll/react/src/scrollIntoViewWithOffset.ts","../../../../../../../../../../../../../execroot/_main/bazel-out/k8-fastbuild/bin/plugins/auto-scroll/react/src/plugin.tsx"],"sourcesContent":["export * from \"./hooks\";\nexport * from \"./plugin\";\n","import type { PropsWithChildren } from \"react\";\nimport React, { useEffect, useState } from \"react\";\nimport { scrollIntoViewWithOffset } from \"./scrollIntoViewWithOffset\";\nimport type { ScrollType } from \"./index\";\n\nexport interface AutoScrollProviderProps {\n /** Return the element to scroll to based on the registered types */\n getElementToScrollTo: (\n scrollableElements: Map<ScrollType, Set<string>>,\n ) => string;\n /** Optional function to get container element, which is used for calculating offset (default: document.body) */\n getBaseElement: () => HTMLElement | undefined | null;\n /** Additional offset to be used (default: 0) */\n offset: number;\n /** Called on mount with a function that clears all registered scroll targets */\n onMount?: (clearScrollableMap: () => void) => void;\n}\n\nexport interface RegisterData {\n /** when to scroll to the target */\n type: ScrollType;\n\n /** the html id to scroll to */\n ref: string;\n}\n\nexport type ScrollFunction = (registerData: RegisterData) => void;\n\nexport const AutoScrollManagerContext = React.createContext<{\n /** function to register a scroll target */\n register: ScrollFunction;\n}>({ register: () => {} });\n\n/** hook to register as a scroll target */\nexport const useRegisterAsScrollable = (): ScrollFunction => {\n const { register } = React.useContext(AutoScrollManagerContext);\n\n return register;\n};\n\n/** Component to handle scrolling */\nexport const AutoScrollProvider = ({\n getElementToScrollTo,\n getBaseElement,\n offset,\n onMount,\n children,\n}: PropsWithChildren<AutoScrollProviderProps>) => {\n // Tracker for what elements are registered to be scroll targets\n // Key is the type (initial, validation, appear)\n // Value is a set of target ids\n const [scrollableMap, setScrollableMap] = useState<\n Map<ScrollType, Set<string>>\n >(new Map());\n\n useEffect(() => {\n onMount?.(() => setScrollableMap(new Map()));\n }, []);\n\n /** Add a new entry as a scroll target */\n const updateScrollableMap = (key: ScrollType, value: string) => {\n setScrollableMap((prev) => {\n const nm = new Map(prev);\n\n if (!nm.get(key)) {\n nm.set(key, new Set());\n }\n\n nm.get(key)?.add(value);\n\n return nm;\n });\n };\n\n /** register a new scroll target */\n const register: ScrollFunction = (data) => {\n updateScrollableMap(data.type, data.ref);\n };\n\n useEffect(() => {\n const node = document.getElementById(getElementToScrollTo(scrollableMap));\n\n if (node) {\n scrollIntoViewWithOffset(node, getBaseElement() || document.body, offset);\n }\n }, [scrollableMap]);\n\n return (\n <AutoScrollManagerContext.Provider value={{ register }}>\n {children}\n </AutoScrollManagerContext.Provider>\n );\n};\n","import { scrollTo } from \"seamless-scroll-polyfill\";\n\n/**\n * Scroll to the given element with an offset\n * @param node Element to scroll to\n * @param baseElement Container element used to calculate offset\n * @param offset Additional offset\n */\nexport function scrollIntoViewWithOffset(\n node: HTMLElement,\n baseElement: HTMLElement,\n offset: number,\n) {\n scrollTo(window, {\n behavior: \"smooth\",\n top:\n node.getBoundingClientRect().top -\n baseElement.getBoundingClientRect().top -\n offset,\n });\n}\n","import type { ReactPlayer, ReactPlayerPlugin } from \"@player-ui/react\";\nimport type { Player } from \"@player-ui/react\";\nimport React from \"react\";\nimport { AutoScrollProvider } from \"./hooks\";\n\nexport enum ScrollType {\n ValidationError,\n FirstAppearance,\n Unknown,\n}\n\nexport interface AutoScrollManagerConfig {\n /** Config to auto-scroll on load */\n autoScrollOnLoad?: boolean;\n /** Config to auto-focus on an error */\n autoFocusOnErrorField?: boolean;\n /** Optional function to get container element, which is used for calculating offset (default: document.body) */\n getBaseElement?: () => HTMLElement | undefined | null;\n /** Additional offset to be used (default: 0) */\n offset?: number;\n}\n\n/** A plugin to manage scrolling behavior */\nexport class AutoScrollManagerPlugin implements ReactPlayerPlugin {\n name = \"auto-scroll-manager\";\n\n /** Toggles if we should auto scroll to to the first failed validation on page load */\n private autoScrollOnLoad: boolean;\n\n /** Toggles if we should auto scroll to the first failed validation on navigation failure */\n private autoFocusOnErrorField: boolean;\n\n /** tracks if its the initial page render */\n private initialRender: boolean;\n\n /** tracks if the navigation failed */\n private failedNavigation: boolean;\n\n /** function to return the base of the scrollable area */\n private getBaseElement: () => HTMLElement | undefined | null;\n\n /** static offset */\n private offset: number;\n\n /** clears scrollableMap in AutoScrollProvider — set when the provider mounts */\n private clearScrollableMap: () => void;\n\n /** map of scroll type to set of ids that are registered under that type */\n private alreadyScrolledTo: Array<string>;\n private scrollFn: (\n scrollableElements: Map<ScrollType, Set<string>>,\n ) => string;\n\n constructor(config: AutoScrollManagerConfig) {\n this.autoScrollOnLoad = config.autoScrollOnLoad ?? false;\n this.autoFocusOnErrorField = config.autoFocusOnErrorField ?? false;\n this.getBaseElement = config.getBaseElement ?? (() => null);\n this.offset = config.offset ?? 0;\n this.initialRender = false;\n this.failedNavigation = false;\n this.alreadyScrolledTo = [];\n this.clearScrollableMap = () => {};\n this.scrollFn = this.calculateScroll.bind(this);\n }\n\n getFirstScrollableElement(idList: Set<string>, type: ScrollType): string {\n const highestElement = {\n id: \"\",\n ypos: 0,\n };\n const ypos = window.scrollY;\n idList.forEach((id) => {\n const element = document.getElementById(id);\n\n // if we are looking at validation errors, make sure the element is invalid\n if (\n type === ScrollType.ValidationError &&\n element?.getAttribute(\"aria-invalid\") === \"false\"\n ) {\n return;\n }\n\n // if we are just looking at elements that just appeared, make sure we haven't\n // scrolled to them before\n if (type === ScrollType.FirstAppearance) {\n if (this.alreadyScrolledTo.indexOf(id) !== -1) {\n return;\n }\n\n this.alreadyScrolledTo.push(id);\n }\n\n const epos = element?.getBoundingClientRect().top;\n if (\n epos !== undefined &&\n (epos + ypos < highestElement.ypos || highestElement.id === \"\")\n ) {\n highestElement.id = id;\n highestElement.ypos = ypos + epos;\n }\n });\n return highestElement.id;\n }\n\n calculateScroll(scrollableElements: Map<ScrollType, Set<string>>): string {\n let currentScroll = ScrollType.FirstAppearance;\n if (this.initialRender) {\n if (this.autoScrollOnLoad) {\n currentScroll = ScrollType.ValidationError;\n }\n\n this.initialRender = false;\n } else if (this.failedNavigation) {\n if (this.autoFocusOnErrorField) {\n currentScroll = ScrollType.ValidationError;\n }\n\n this.failedNavigation = false;\n }\n\n const elementList = scrollableElements.get(currentScroll);\n if (elementList) {\n const element = this.getFirstScrollableElement(\n elementList,\n currentScroll,\n );\n return element ?? \"\";\n }\n\n return \"\";\n }\n\n // Hooks into player flow to determine what scroll targets need to be evaluated at specific lifecycle points\n apply(player: Player): void {\n player.hooks.flowController.tap(this.name, (fc) => {\n fc.hooks.flow.tap(this.name, (flow) => {\n flow.hooks.transition.tap(this.name, () => {\n // Reset Everything\n this.initialRender = true;\n this.failedNavigation = false;\n this.alreadyScrolledTo = [];\n this.clearScrollableMap();\n // Reset scroll position for new view\n window.scroll(0, 0);\n });\n flow.hooks.skipTransition.intercept({\n call: () => {\n this.failedNavigation = true;\n },\n });\n });\n });\n }\n\n applyReact(reactPlayer: ReactPlayer): void {\n reactPlayer.hooks.webComponent.tap(this.name, (Comp) => {\n const { scrollFn, getBaseElement, offset } = this;\n const setScrollableMapClearer = (clear: () => void) => {\n this.clearScrollableMap = clear;\n };\n\n function AutoScrollManagerComponent() {\n return (\n <AutoScrollProvider\n getElementToScrollTo={scrollFn}\n getBaseElement={getBaseElement}\n offset={offset}\n onMount={(clear) => {\n setScrollableMapClearer(clear);\n }}\n >\n <Comp />\n </AutoScrollProvider>\n );\n }\n\n return AutoScrollManagerComponent;\n });\n }\n}\n"],"mappings":"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","names":["React","import_react","ScrollType","React"]}
|
|
1
|
+
{"version":3,"sources":["../../../../../../../../../../../../../execroot/_main/bazel-out/k8-fastbuild/bin/plugins/auto-scroll/react/src/index.tsx","../../../../../../../../../../../../../execroot/_main/bazel-out/k8-fastbuild/bin/plugins/auto-scroll/react/src/hooks.tsx","../../../../../../../../../../../../../execroot/_main/bazel-out/k8-fastbuild/bin/plugins/auto-scroll/react/src/scrollIntoViewWithOffset.ts","../../../../../../../../../../../../../execroot/_main/bazel-out/k8-fastbuild/bin/plugins/auto-scroll/react/src/plugin.tsx"],"sourcesContent":["export * from \"./hooks\";\nexport * from \"./plugin\";\n","import type { PropsWithChildren } from \"react\";\nimport React, { useEffect, useState } from \"react\";\nimport { scrollIntoViewWithOffset } from \"./scrollIntoViewWithOffset\";\nimport type { ScrollType } from \"./index\";\n\nexport interface AutoScrollProviderProps {\n /** Return the element to scroll to based on the registered types */\n getElementToScrollTo: (\n scrollableElements: Map<ScrollType, Set<string>>,\n ) => string;\n /** Optional function to get container element, which is used for calculating offset (default: document.body) */\n getBaseElement: () => HTMLElement | undefined | null;\n /** Additional offset to be used (default: 0) */\n offset: number;\n /** Called on mount with a function that clears all registered scroll targets */\n onMount?: (clearScrollableMap: () => void) => void;\n}\n\nexport interface RegisterData {\n /** when to scroll to the target */\n type: ScrollType;\n\n /** the html id to scroll to */\n ref: string;\n}\n\nexport type ScrollFunction = (registerData: RegisterData) => void;\n\nexport const AutoScrollManagerContext = React.createContext<{\n /** function to register a scroll target */\n register: ScrollFunction;\n}>({ register: () => {} });\n\n/** hook to register as a scroll target */\nexport const useRegisterAsScrollable = (): ScrollFunction => {\n const { register } = React.useContext(AutoScrollManagerContext);\n\n return register;\n};\n\n/** Component to handle scrolling */\nexport const AutoScrollProvider = ({\n getElementToScrollTo,\n getBaseElement,\n offset,\n onMount,\n children,\n}: PropsWithChildren<AutoScrollProviderProps>) => {\n // Tracker for what elements are registered to be scroll targets\n // Key is the type (initial, validation, appear)\n // Value is a set of target ids\n const [scrollableMap, setScrollableMap] = useState<\n Map<ScrollType, Set<string>>\n >(new Map());\n\n useEffect(() => {\n onMount?.(() => setScrollableMap(new Map()));\n }, []);\n\n /** Add a new entry as a scroll target */\n const updateScrollableMap = (key: ScrollType, value: string) => {\n setScrollableMap((prev) => {\n const nm = new Map(prev);\n\n if (!nm.get(key)) {\n nm.set(key, new Set());\n }\n\n nm.get(key)?.add(value);\n\n return nm;\n });\n };\n\n /** register a new scroll target */\n const register: ScrollFunction = (data) => {\n updateScrollableMap(data.type, data.ref);\n };\n\n useEffect(() => {\n const node = document.getElementById(getElementToScrollTo(scrollableMap));\n\n if (node) {\n scrollIntoViewWithOffset(node, getBaseElement() || document.body, offset);\n }\n }, [scrollableMap]);\n\n return (\n <AutoScrollManagerContext.Provider value={{ register }}>\n {children}\n </AutoScrollManagerContext.Provider>\n );\n};\n","import { scrollTo } from \"seamless-scroll-polyfill\";\n\n/**\n * Scroll to the given element with an offset\n * @param node Element to scroll to\n * @param baseElement Container element used to calculate offset\n * @param offset Additional offset\n */\nexport function scrollIntoViewWithOffset(\n node: HTMLElement,\n baseElement: HTMLElement,\n offset: number,\n) {\n scrollTo(window, {\n behavior: \"smooth\",\n top:\n node.getBoundingClientRect().top -\n baseElement.getBoundingClientRect().top -\n offset,\n });\n}\n","import type { ReactPlayer, ReactPlayerPlugin } from \"@player-ui/react\";\nimport type { Player } from \"@player-ui/react\";\nimport React from \"react\";\nimport { AutoScrollProvider } from \"./hooks\";\n\nexport enum ScrollType {\n ValidationError,\n FirstAppearance,\n Unknown,\n}\n\nexport interface AutoScrollManagerConfig {\n /** Config to auto-scroll on load */\n autoScrollOnLoad?: boolean;\n /** Config to auto-focus on an error */\n autoFocusOnErrorField?: boolean;\n /** Optional function to get container element, which is used for calculating offset (default: document.body) */\n getBaseElement?: () => HTMLElement | undefined | null;\n /** Additional offset to be used (default: 0) */\n offset?: number;\n}\n\n/** A plugin to manage scrolling behavior */\nexport class AutoScrollManagerPlugin implements ReactPlayerPlugin {\n name = \"auto-scroll-manager\";\n\n /** Toggles if we should auto scroll to to the first failed validation on page load */\n private autoScrollOnLoad: boolean;\n\n /** Toggles if we should auto scroll to the first failed validation on navigation failure */\n private autoFocusOnErrorField: boolean;\n\n /** tracks if its the initial page render */\n private initialRender: boolean;\n\n /** tracks if the navigation failed */\n private failedNavigation: boolean;\n\n /** function to return the base of the scrollable area */\n private getBaseElement: () => HTMLElement | undefined | null;\n\n /** static offset */\n private offset: number;\n\n /** clears scrollableMap in AutoScrollProvider — set when the provider mounts */\n private clearScrollableMap: () => void;\n\n /** map of scroll type to set of ids that are registered under that type */\n private alreadyScrolledTo: Array<string>;\n private scrollFn: (\n scrollableElements: Map<ScrollType, Set<string>>,\n ) => string;\n\n constructor(config: AutoScrollManagerConfig) {\n this.autoScrollOnLoad = config.autoScrollOnLoad ?? false;\n this.autoFocusOnErrorField = config.autoFocusOnErrorField ?? false;\n this.getBaseElement = config.getBaseElement ?? (() => null);\n this.offset = config.offset ?? 0;\n this.initialRender = false;\n this.failedNavigation = false;\n this.alreadyScrolledTo = [];\n this.clearScrollableMap = () => {};\n this.scrollFn = this.calculateScroll.bind(this);\n }\n\n getFirstScrollableElement(idList: Set<string>, type: ScrollType): string {\n const highestElement = {\n id: \"\",\n ypos: 0,\n };\n const ypos = window.scrollY;\n idList.forEach((id) => {\n const element = document.getElementById(id);\n\n // if we are looking at validation errors, make sure the element is invalid\n if (\n type === ScrollType.ValidationError &&\n element?.getAttribute(\"aria-invalid\") === \"false\"\n ) {\n return;\n }\n\n // if we are just looking at elements that just appeared, make sure we haven't\n // scrolled to them before\n if (type === ScrollType.FirstAppearance) {\n if (this.alreadyScrolledTo.indexOf(id) !== -1) {\n return;\n }\n\n this.alreadyScrolledTo.push(id);\n }\n\n const epos = element?.getBoundingClientRect().top;\n if (\n epos !== undefined &&\n (epos + ypos < highestElement.ypos || highestElement.id === \"\")\n ) {\n highestElement.id = id;\n highestElement.ypos = ypos + epos;\n }\n });\n return highestElement.id;\n }\n\n calculateScroll(scrollableElements: Map<ScrollType, Set<string>>): string {\n let currentScroll = ScrollType.FirstAppearance;\n if (this.initialRender) {\n if (this.autoScrollOnLoad) {\n currentScroll = ScrollType.ValidationError;\n }\n\n this.initialRender = false;\n } else if (this.failedNavigation) {\n if (this.autoFocusOnErrorField) {\n currentScroll = ScrollType.ValidationError;\n }\n\n this.failedNavigation = false;\n }\n\n const elementList = scrollableElements.get(currentScroll);\n if (elementList) {\n const element = this.getFirstScrollableElement(\n elementList,\n currentScroll,\n );\n return element ?? \"\";\n }\n\n return \"\";\n }\n\n // Hooks into player flow to determine what scroll targets need to be evaluated at specific lifecycle points\n apply(player: Player): void {\n player.hooks.flowController.tap(this.name, (fc) => {\n fc.hooks.flow.tap(this.name, (flow) => {\n flow.hooks.transition.tap(this.name, () => {\n // Reset Everything\n this.initialRender = true;\n this.failedNavigation = false;\n this.alreadyScrolledTo = [];\n this.clearScrollableMap();\n // Cancel any in-flight polyfill smooth-scroll animation before resetting.\n window.dispatchEvent(new WheelEvent(\"wheel\", { bubbles: true }));\n window.scroll(0, 0);\n });\n flow.hooks.skipTransition.intercept({\n call: () => {\n this.failedNavigation = true;\n },\n });\n });\n });\n }\n\n applyReact(reactPlayer: ReactPlayer): void {\n reactPlayer.hooks.webComponent.tap(this.name, (Comp) => {\n const { scrollFn, getBaseElement, offset } = this;\n const setScrollableMapClearer = (clear: () => void) => {\n this.clearScrollableMap = clear;\n };\n\n function AutoScrollManagerComponent() {\n return (\n <AutoScrollProvider\n getElementToScrollTo={scrollFn}\n getBaseElement={getBaseElement}\n offset={offset}\n onMount={(clear) => {\n setScrollableMapClearer(clear);\n }}\n >\n <Comp />\n </AutoScrollProvider>\n );\n }\n\n return AutoScrollManagerComponent;\n });\n }\n}\n"],"mappings":"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","names":["React","import_react","ScrollType","React"]}
|
package/dist/index.legacy-esm.js
CHANGED
|
@@ -129,6 +129,7 @@ var AutoScrollManagerPlugin = class {
|
|
|
129
129
|
this.failedNavigation = false;
|
|
130
130
|
this.alreadyScrolledTo = [];
|
|
131
131
|
this.clearScrollableMap();
|
|
132
|
+
window.dispatchEvent(new WheelEvent("wheel", { bubbles: true }));
|
|
132
133
|
window.scroll(0, 0);
|
|
133
134
|
});
|
|
134
135
|
flow.hooks.skipTransition.intercept({
|
package/dist/index.mjs
CHANGED
|
@@ -129,6 +129,7 @@ var AutoScrollManagerPlugin = class {
|
|
|
129
129
|
this.failedNavigation = false;
|
|
130
130
|
this.alreadyScrolledTo = [];
|
|
131
131
|
this.clearScrollableMap();
|
|
132
|
+
window.dispatchEvent(new WheelEvent("wheel", { bubbles: true }));
|
|
132
133
|
window.scroll(0, 0);
|
|
133
134
|
});
|
|
134
135
|
flow.hooks.skipTransition.intercept({
|
package/dist/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../../../../../../../../execroot/_main/bazel-out/k8-fastbuild/bin/plugins/auto-scroll/react/src/hooks.tsx","../../../../../../../../../../../../execroot/_main/bazel-out/k8-fastbuild/bin/plugins/auto-scroll/react/src/scrollIntoViewWithOffset.ts","../../../../../../../../../../../../execroot/_main/bazel-out/k8-fastbuild/bin/plugins/auto-scroll/react/src/plugin.tsx"],"sourcesContent":["import type { PropsWithChildren } from \"react\";\nimport React, { useEffect, useState } from \"react\";\nimport { scrollIntoViewWithOffset } from \"./scrollIntoViewWithOffset\";\nimport type { ScrollType } from \"./index\";\n\nexport interface AutoScrollProviderProps {\n /** Return the element to scroll to based on the registered types */\n getElementToScrollTo: (\n scrollableElements: Map<ScrollType, Set<string>>,\n ) => string;\n /** Optional function to get container element, which is used for calculating offset (default: document.body) */\n getBaseElement: () => HTMLElement | undefined | null;\n /** Additional offset to be used (default: 0) */\n offset: number;\n /** Called on mount with a function that clears all registered scroll targets */\n onMount?: (clearScrollableMap: () => void) => void;\n}\n\nexport interface RegisterData {\n /** when to scroll to the target */\n type: ScrollType;\n\n /** the html id to scroll to */\n ref: string;\n}\n\nexport type ScrollFunction = (registerData: RegisterData) => void;\n\nexport const AutoScrollManagerContext = React.createContext<{\n /** function to register a scroll target */\n register: ScrollFunction;\n}>({ register: () => {} });\n\n/** hook to register as a scroll target */\nexport const useRegisterAsScrollable = (): ScrollFunction => {\n const { register } = React.useContext(AutoScrollManagerContext);\n\n return register;\n};\n\n/** Component to handle scrolling */\nexport const AutoScrollProvider = ({\n getElementToScrollTo,\n getBaseElement,\n offset,\n onMount,\n children,\n}: PropsWithChildren<AutoScrollProviderProps>) => {\n // Tracker for what elements are registered to be scroll targets\n // Key is the type (initial, validation, appear)\n // Value is a set of target ids\n const [scrollableMap, setScrollableMap] = useState<\n Map<ScrollType, Set<string>>\n >(new Map());\n\n useEffect(() => {\n onMount?.(() => setScrollableMap(new Map()));\n }, []);\n\n /** Add a new entry as a scroll target */\n const updateScrollableMap = (key: ScrollType, value: string) => {\n setScrollableMap((prev) => {\n const nm = new Map(prev);\n\n if (!nm.get(key)) {\n nm.set(key, new Set());\n }\n\n nm.get(key)?.add(value);\n\n return nm;\n });\n };\n\n /** register a new scroll target */\n const register: ScrollFunction = (data) => {\n updateScrollableMap(data.type, data.ref);\n };\n\n useEffect(() => {\n const node = document.getElementById(getElementToScrollTo(scrollableMap));\n\n if (node) {\n scrollIntoViewWithOffset(node, getBaseElement() || document.body, offset);\n }\n }, [scrollableMap]);\n\n return (\n <AutoScrollManagerContext.Provider value={{ register }}>\n {children}\n </AutoScrollManagerContext.Provider>\n );\n};\n","import { scrollTo } from \"seamless-scroll-polyfill\";\n\n/**\n * Scroll to the given element with an offset\n * @param node Element to scroll to\n * @param baseElement Container element used to calculate offset\n * @param offset Additional offset\n */\nexport function scrollIntoViewWithOffset(\n node: HTMLElement,\n baseElement: HTMLElement,\n offset: number,\n) {\n scrollTo(window, {\n behavior: \"smooth\",\n top:\n node.getBoundingClientRect().top -\n baseElement.getBoundingClientRect().top -\n offset,\n });\n}\n","import type { ReactPlayer, ReactPlayerPlugin } from \"@player-ui/react\";\nimport type { Player } from \"@player-ui/react\";\nimport React from \"react\";\nimport { AutoScrollProvider } from \"./hooks\";\n\nexport enum ScrollType {\n ValidationError,\n FirstAppearance,\n Unknown,\n}\n\nexport interface AutoScrollManagerConfig {\n /** Config to auto-scroll on load */\n autoScrollOnLoad?: boolean;\n /** Config to auto-focus on an error */\n autoFocusOnErrorField?: boolean;\n /** Optional function to get container element, which is used for calculating offset (default: document.body) */\n getBaseElement?: () => HTMLElement | undefined | null;\n /** Additional offset to be used (default: 0) */\n offset?: number;\n}\n\n/** A plugin to manage scrolling behavior */\nexport class AutoScrollManagerPlugin implements ReactPlayerPlugin {\n name = \"auto-scroll-manager\";\n\n /** Toggles if we should auto scroll to to the first failed validation on page load */\n private autoScrollOnLoad: boolean;\n\n /** Toggles if we should auto scroll to the first failed validation on navigation failure */\n private autoFocusOnErrorField: boolean;\n\n /** tracks if its the initial page render */\n private initialRender: boolean;\n\n /** tracks if the navigation failed */\n private failedNavigation: boolean;\n\n /** function to return the base of the scrollable area */\n private getBaseElement: () => HTMLElement | undefined | null;\n\n /** static offset */\n private offset: number;\n\n /** clears scrollableMap in AutoScrollProvider — set when the provider mounts */\n private clearScrollableMap: () => void;\n\n /** map of scroll type to set of ids that are registered under that type */\n private alreadyScrolledTo: Array<string>;\n private scrollFn: (\n scrollableElements: Map<ScrollType, Set<string>>,\n ) => string;\n\n constructor(config: AutoScrollManagerConfig) {\n this.autoScrollOnLoad = config.autoScrollOnLoad ?? false;\n this.autoFocusOnErrorField = config.autoFocusOnErrorField ?? false;\n this.getBaseElement = config.getBaseElement ?? (() => null);\n this.offset = config.offset ?? 0;\n this.initialRender = false;\n this.failedNavigation = false;\n this.alreadyScrolledTo = [];\n this.clearScrollableMap = () => {};\n this.scrollFn = this.calculateScroll.bind(this);\n }\n\n getFirstScrollableElement(idList: Set<string>, type: ScrollType): string {\n const highestElement = {\n id: \"\",\n ypos: 0,\n };\n const ypos = window.scrollY;\n idList.forEach((id) => {\n const element = document.getElementById(id);\n\n // if we are looking at validation errors, make sure the element is invalid\n if (\n type === ScrollType.ValidationError &&\n element?.getAttribute(\"aria-invalid\") === \"false\"\n ) {\n return;\n }\n\n // if we are just looking at elements that just appeared, make sure we haven't\n // scrolled to them before\n if (type === ScrollType.FirstAppearance) {\n if (this.alreadyScrolledTo.indexOf(id) !== -1) {\n return;\n }\n\n this.alreadyScrolledTo.push(id);\n }\n\n const epos = element?.getBoundingClientRect().top;\n if (\n epos !== undefined &&\n (epos + ypos < highestElement.ypos || highestElement.id === \"\")\n ) {\n highestElement.id = id;\n highestElement.ypos = ypos + epos;\n }\n });\n return highestElement.id;\n }\n\n calculateScroll(scrollableElements: Map<ScrollType, Set<string>>): string {\n let currentScroll = ScrollType.FirstAppearance;\n if (this.initialRender) {\n if (this.autoScrollOnLoad) {\n currentScroll = ScrollType.ValidationError;\n }\n\n this.initialRender = false;\n } else if (this.failedNavigation) {\n if (this.autoFocusOnErrorField) {\n currentScroll = ScrollType.ValidationError;\n }\n\n this.failedNavigation = false;\n }\n\n const elementList = scrollableElements.get(currentScroll);\n if (elementList) {\n const element = this.getFirstScrollableElement(\n elementList,\n currentScroll,\n );\n return element ?? \"\";\n }\n\n return \"\";\n }\n\n // Hooks into player flow to determine what scroll targets need to be evaluated at specific lifecycle points\n apply(player: Player): void {\n player.hooks.flowController.tap(this.name, (fc) => {\n fc.hooks.flow.tap(this.name, (flow) => {\n flow.hooks.transition.tap(this.name, () => {\n // Reset Everything\n this.initialRender = true;\n this.failedNavigation = false;\n this.alreadyScrolledTo = [];\n this.clearScrollableMap();\n // Reset scroll position for new view\n window.scroll(0, 0);\n });\n flow.hooks.skipTransition.intercept({\n call: () => {\n this.failedNavigation = true;\n },\n });\n });\n });\n }\n\n applyReact(reactPlayer: ReactPlayer): void {\n reactPlayer.hooks.webComponent.tap(this.name, (Comp) => {\n const { scrollFn, getBaseElement, offset } = this;\n const setScrollableMapClearer = (clear: () => void) => {\n this.clearScrollableMap = clear;\n };\n\n function AutoScrollManagerComponent() {\n return (\n <AutoScrollProvider\n getElementToScrollTo={scrollFn}\n getBaseElement={getBaseElement}\n offset={offset}\n onMount={(clear) => {\n setScrollableMapClearer(clear);\n }}\n >\n <Comp />\n </AutoScrollProvider>\n );\n }\n\n return AutoScrollManagerComponent;\n });\n }\n}\n"],"mappings":";AACA,OAAO,SAAS,WAAW,gBAAgB;;;ACD3C,SAAS,gBAAgB;AAQlB,SAAS,yBACd,MACA,aACA,QACA;AACA,WAAS,QAAQ;AAAA,IACf,UAAU;AAAA,IACV,KACE,KAAK,sBAAsB,EAAE,MAC7B,YAAY,sBAAsB,EAAE,MACpC;AAAA,EACJ,CAAC;AACH;;;ADQO,IAAM,2BAA2B,MAAM,cAG3C,EAAE,UAAU,MAAM;AAAC,EAAE,CAAC;AAGlB,IAAM,0BAA0B,MAAsB;AAC3D,QAAM,EAAE,SAAS,IAAI,MAAM,WAAW,wBAAwB;AAE9D,SAAO;AACT;AAGO,IAAM,qBAAqB,CAAC;AAAA,EACjC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,MAAkD;AAIhD,QAAM,CAAC,eAAe,gBAAgB,IAAI,SAExC,oBAAI,IAAI,CAAC;AAEX,YAAU,MAAM;AACd,cAAU,MAAM,iBAAiB,oBAAI,IAAI,CAAC,CAAC;AAAA,EAC7C,GAAG,CAAC,CAAC;AAGL,QAAM,sBAAsB,CAAC,KAAiB,UAAkB;AAC9D,qBAAiB,CAAC,SAAS;AACzB,YAAM,KAAK,IAAI,IAAI,IAAI;AAEvB,UAAI,CAAC,GAAG,IAAI,GAAG,GAAG;AAChB,WAAG,IAAI,KAAK,oBAAI,IAAI,CAAC;AAAA,MACvB;AAEA,SAAG,IAAI,GAAG,GAAG,IAAI,KAAK;AAEtB,aAAO;AAAA,IACT,CAAC;AAAA,EACH;AAGA,QAAM,WAA2B,CAAC,SAAS;AACzC,wBAAoB,KAAK,MAAM,KAAK,GAAG;AAAA,EACzC;AAEA,YAAU,MAAM;AACd,UAAM,OAAO,SAAS,eAAe,qBAAqB,aAAa,CAAC;AAExE,QAAI,MAAM;AACR,+BAAyB,MAAM,eAAe,KAAK,SAAS,MAAM,MAAM;AAAA,IAC1E;AAAA,EACF,GAAG,CAAC,aAAa,CAAC;AAElB,SACE,oCAAC,yBAAyB,UAAzB,EAAkC,OAAO,EAAE,SAAS,KAClD,QACH;AAEJ;;;AE1FA,OAAOA,YAAW;AAGX,IAAK,aAAL,kBAAKC,gBAAL;AACL,EAAAA,wBAAA;AACA,EAAAA,wBAAA;AACA,EAAAA,wBAAA;AAHU,SAAAA;AAAA,GAAA;AAkBL,IAAM,0BAAN,MAA2D;AAAA,EA8BhE,YAAY,QAAiC;AA7B7C,gBAAO;AA8BL,SAAK,mBAAmB,OAAO,oBAAoB;AACnD,SAAK,wBAAwB,OAAO,yBAAyB;AAC7D,SAAK,iBAAiB,OAAO,mBAAmB,MAAM;AACtD,SAAK,SAAS,OAAO,UAAU;AAC/B,SAAK,gBAAgB;AACrB,SAAK,mBAAmB;AACxB,SAAK,oBAAoB,CAAC;AAC1B,SAAK,qBAAqB,MAAM;AAAA,IAAC;AACjC,SAAK,WAAW,KAAK,gBAAgB,KAAK,IAAI;AAAA,EAChD;AAAA,EAEA,0BAA0B,QAAqB,MAA0B;AACvE,UAAM,iBAAiB;AAAA,MACrB,IAAI;AAAA,MACJ,MAAM;AAAA,IACR;AACA,UAAM,OAAO,OAAO;AACpB,WAAO,QAAQ,CAAC,OAAO;AACrB,YAAM,UAAU,SAAS,eAAe,EAAE;AAG1C,UACE,SAAS,2BACT,SAAS,aAAa,cAAc,MAAM,SAC1C;AACA;AAAA,MACF;AAIA,UAAI,SAAS,yBAA4B;AACvC,YAAI,KAAK,kBAAkB,QAAQ,EAAE,MAAM,IAAI;AAC7C;AAAA,QACF;AAEA,aAAK,kBAAkB,KAAK,EAAE;AAAA,MAChC;AAEA,YAAM,OAAO,SAAS,sBAAsB,EAAE;AAC9C,UACE,SAAS,WACR,OAAO,OAAO,eAAe,QAAQ,eAAe,OAAO,KAC5D;AACA,uBAAe,KAAK;AACpB,uBAAe,OAAO,OAAO;AAAA,MAC/B;AAAA,IACF,CAAC;AACD,WAAO,eAAe;AAAA,EACxB;AAAA,EAEA,gBAAgB,oBAA0D;AACxE,QAAI,gBAAgB;AACpB,QAAI,KAAK,eAAe;AACtB,UAAI,KAAK,kBAAkB;AACzB,wBAAgB;AAAA,MAClB;AAEA,WAAK,gBAAgB;AAAA,IACvB,WAAW,KAAK,kBAAkB;AAChC,UAAI,KAAK,uBAAuB;AAC9B,wBAAgB;AAAA,MAClB;AAEA,WAAK,mBAAmB;AAAA,IAC1B;AAEA,UAAM,cAAc,mBAAmB,IAAI,aAAa;AACxD,QAAI,aAAa;AACf,YAAM,UAAU,KAAK;AAAA,QACnB;AAAA,QACA;AAAA,MACF;AACA,aAAO,WAAW;AAAA,IACpB;AAEA,WAAO;AAAA,EACT;AAAA;AAAA,EAGA,MAAM,QAAsB;AAC1B,WAAO,MAAM,eAAe,IAAI,KAAK,MAAM,CAAC,OAAO;AACjD,SAAG,MAAM,KAAK,IAAI,KAAK,MAAM,CAAC,SAAS;AACrC,aAAK,MAAM,WAAW,IAAI,KAAK,MAAM,MAAM;AAEzC,eAAK,gBAAgB;AACrB,eAAK,mBAAmB;AACxB,eAAK,oBAAoB,CAAC;AAC1B,eAAK,mBAAmB;AAExB,iBAAO,OAAO,GAAG,CAAC;AAAA,QACpB,CAAC;AACD,aAAK,MAAM,eAAe,UAAU;AAAA,UAClC,MAAM,MAAM;AACV,iBAAK,mBAAmB;AAAA,UAC1B;AAAA,QACF,CAAC;AAAA,MACH,CAAC;AAAA,IACH,CAAC;AAAA,EACH;AAAA,EAEA,WAAW,aAAgC;AACzC,gBAAY,MAAM,aAAa,IAAI,KAAK,MAAM,CAAC,SAAS;AACtD,YAAM,EAAE,UAAU,gBAAgB,OAAO,IAAI;AAC7C,YAAM,0BAA0B,CAAC,UAAsB;AACrD,aAAK,qBAAqB;AAAA,MAC5B;AAEA,eAAS,6BAA6B;AACpC,eACE,gBAAAC,OAAA;AAAA,UAAC;AAAA;AAAA,YACC,sBAAsB;AAAA,YACtB;AAAA,YACA;AAAA,YACA,SAAS,CAAC,UAAU;AAClB,sCAAwB,KAAK;AAAA,YAC/B;AAAA;AAAA,UAEA,gBAAAA,OAAA,cAAC,UAAK;AAAA,QACR;AAAA,MAEJ;AAEA,aAAO;AAAA,IACT,CAAC;AAAA,EACH;AACF;","names":["React","ScrollType","React"]}
|
|
1
|
+
{"version":3,"sources":["../../../../../../../../../../../../execroot/_main/bazel-out/k8-fastbuild/bin/plugins/auto-scroll/react/src/hooks.tsx","../../../../../../../../../../../../execroot/_main/bazel-out/k8-fastbuild/bin/plugins/auto-scroll/react/src/scrollIntoViewWithOffset.ts","../../../../../../../../../../../../execroot/_main/bazel-out/k8-fastbuild/bin/plugins/auto-scroll/react/src/plugin.tsx"],"sourcesContent":["import type { PropsWithChildren } from \"react\";\nimport React, { useEffect, useState } from \"react\";\nimport { scrollIntoViewWithOffset } from \"./scrollIntoViewWithOffset\";\nimport type { ScrollType } from \"./index\";\n\nexport interface AutoScrollProviderProps {\n /** Return the element to scroll to based on the registered types */\n getElementToScrollTo: (\n scrollableElements: Map<ScrollType, Set<string>>,\n ) => string;\n /** Optional function to get container element, which is used for calculating offset (default: document.body) */\n getBaseElement: () => HTMLElement | undefined | null;\n /** Additional offset to be used (default: 0) */\n offset: number;\n /** Called on mount with a function that clears all registered scroll targets */\n onMount?: (clearScrollableMap: () => void) => void;\n}\n\nexport interface RegisterData {\n /** when to scroll to the target */\n type: ScrollType;\n\n /** the html id to scroll to */\n ref: string;\n}\n\nexport type ScrollFunction = (registerData: RegisterData) => void;\n\nexport const AutoScrollManagerContext = React.createContext<{\n /** function to register a scroll target */\n register: ScrollFunction;\n}>({ register: () => {} });\n\n/** hook to register as a scroll target */\nexport const useRegisterAsScrollable = (): ScrollFunction => {\n const { register } = React.useContext(AutoScrollManagerContext);\n\n return register;\n};\n\n/** Component to handle scrolling */\nexport const AutoScrollProvider = ({\n getElementToScrollTo,\n getBaseElement,\n offset,\n onMount,\n children,\n}: PropsWithChildren<AutoScrollProviderProps>) => {\n // Tracker for what elements are registered to be scroll targets\n // Key is the type (initial, validation, appear)\n // Value is a set of target ids\n const [scrollableMap, setScrollableMap] = useState<\n Map<ScrollType, Set<string>>\n >(new Map());\n\n useEffect(() => {\n onMount?.(() => setScrollableMap(new Map()));\n }, []);\n\n /** Add a new entry as a scroll target */\n const updateScrollableMap = (key: ScrollType, value: string) => {\n setScrollableMap((prev) => {\n const nm = new Map(prev);\n\n if (!nm.get(key)) {\n nm.set(key, new Set());\n }\n\n nm.get(key)?.add(value);\n\n return nm;\n });\n };\n\n /** register a new scroll target */\n const register: ScrollFunction = (data) => {\n updateScrollableMap(data.type, data.ref);\n };\n\n useEffect(() => {\n const node = document.getElementById(getElementToScrollTo(scrollableMap));\n\n if (node) {\n scrollIntoViewWithOffset(node, getBaseElement() || document.body, offset);\n }\n }, [scrollableMap]);\n\n return (\n <AutoScrollManagerContext.Provider value={{ register }}>\n {children}\n </AutoScrollManagerContext.Provider>\n );\n};\n","import { scrollTo } from \"seamless-scroll-polyfill\";\n\n/**\n * Scroll to the given element with an offset\n * @param node Element to scroll to\n * @param baseElement Container element used to calculate offset\n * @param offset Additional offset\n */\nexport function scrollIntoViewWithOffset(\n node: HTMLElement,\n baseElement: HTMLElement,\n offset: number,\n) {\n scrollTo(window, {\n behavior: \"smooth\",\n top:\n node.getBoundingClientRect().top -\n baseElement.getBoundingClientRect().top -\n offset,\n });\n}\n","import type { ReactPlayer, ReactPlayerPlugin } from \"@player-ui/react\";\nimport type { Player } from \"@player-ui/react\";\nimport React from \"react\";\nimport { AutoScrollProvider } from \"./hooks\";\n\nexport enum ScrollType {\n ValidationError,\n FirstAppearance,\n Unknown,\n}\n\nexport interface AutoScrollManagerConfig {\n /** Config to auto-scroll on load */\n autoScrollOnLoad?: boolean;\n /** Config to auto-focus on an error */\n autoFocusOnErrorField?: boolean;\n /** Optional function to get container element, which is used for calculating offset (default: document.body) */\n getBaseElement?: () => HTMLElement | undefined | null;\n /** Additional offset to be used (default: 0) */\n offset?: number;\n}\n\n/** A plugin to manage scrolling behavior */\nexport class AutoScrollManagerPlugin implements ReactPlayerPlugin {\n name = \"auto-scroll-manager\";\n\n /** Toggles if we should auto scroll to to the first failed validation on page load */\n private autoScrollOnLoad: boolean;\n\n /** Toggles if we should auto scroll to the first failed validation on navigation failure */\n private autoFocusOnErrorField: boolean;\n\n /** tracks if its the initial page render */\n private initialRender: boolean;\n\n /** tracks if the navigation failed */\n private failedNavigation: boolean;\n\n /** function to return the base of the scrollable area */\n private getBaseElement: () => HTMLElement | undefined | null;\n\n /** static offset */\n private offset: number;\n\n /** clears scrollableMap in AutoScrollProvider — set when the provider mounts */\n private clearScrollableMap: () => void;\n\n /** map of scroll type to set of ids that are registered under that type */\n private alreadyScrolledTo: Array<string>;\n private scrollFn: (\n scrollableElements: Map<ScrollType, Set<string>>,\n ) => string;\n\n constructor(config: AutoScrollManagerConfig) {\n this.autoScrollOnLoad = config.autoScrollOnLoad ?? false;\n this.autoFocusOnErrorField = config.autoFocusOnErrorField ?? false;\n this.getBaseElement = config.getBaseElement ?? (() => null);\n this.offset = config.offset ?? 0;\n this.initialRender = false;\n this.failedNavigation = false;\n this.alreadyScrolledTo = [];\n this.clearScrollableMap = () => {};\n this.scrollFn = this.calculateScroll.bind(this);\n }\n\n getFirstScrollableElement(idList: Set<string>, type: ScrollType): string {\n const highestElement = {\n id: \"\",\n ypos: 0,\n };\n const ypos = window.scrollY;\n idList.forEach((id) => {\n const element = document.getElementById(id);\n\n // if we are looking at validation errors, make sure the element is invalid\n if (\n type === ScrollType.ValidationError &&\n element?.getAttribute(\"aria-invalid\") === \"false\"\n ) {\n return;\n }\n\n // if we are just looking at elements that just appeared, make sure we haven't\n // scrolled to them before\n if (type === ScrollType.FirstAppearance) {\n if (this.alreadyScrolledTo.indexOf(id) !== -1) {\n return;\n }\n\n this.alreadyScrolledTo.push(id);\n }\n\n const epos = element?.getBoundingClientRect().top;\n if (\n epos !== undefined &&\n (epos + ypos < highestElement.ypos || highestElement.id === \"\")\n ) {\n highestElement.id = id;\n highestElement.ypos = ypos + epos;\n }\n });\n return highestElement.id;\n }\n\n calculateScroll(scrollableElements: Map<ScrollType, Set<string>>): string {\n let currentScroll = ScrollType.FirstAppearance;\n if (this.initialRender) {\n if (this.autoScrollOnLoad) {\n currentScroll = ScrollType.ValidationError;\n }\n\n this.initialRender = false;\n } else if (this.failedNavigation) {\n if (this.autoFocusOnErrorField) {\n currentScroll = ScrollType.ValidationError;\n }\n\n this.failedNavigation = false;\n }\n\n const elementList = scrollableElements.get(currentScroll);\n if (elementList) {\n const element = this.getFirstScrollableElement(\n elementList,\n currentScroll,\n );\n return element ?? \"\";\n }\n\n return \"\";\n }\n\n // Hooks into player flow to determine what scroll targets need to be evaluated at specific lifecycle points\n apply(player: Player): void {\n player.hooks.flowController.tap(this.name, (fc) => {\n fc.hooks.flow.tap(this.name, (flow) => {\n flow.hooks.transition.tap(this.name, () => {\n // Reset Everything\n this.initialRender = true;\n this.failedNavigation = false;\n this.alreadyScrolledTo = [];\n this.clearScrollableMap();\n // Cancel any in-flight polyfill smooth-scroll animation before resetting.\n window.dispatchEvent(new WheelEvent(\"wheel\", { bubbles: true }));\n window.scroll(0, 0);\n });\n flow.hooks.skipTransition.intercept({\n call: () => {\n this.failedNavigation = true;\n },\n });\n });\n });\n }\n\n applyReact(reactPlayer: ReactPlayer): void {\n reactPlayer.hooks.webComponent.tap(this.name, (Comp) => {\n const { scrollFn, getBaseElement, offset } = this;\n const setScrollableMapClearer = (clear: () => void) => {\n this.clearScrollableMap = clear;\n };\n\n function AutoScrollManagerComponent() {\n return (\n <AutoScrollProvider\n getElementToScrollTo={scrollFn}\n getBaseElement={getBaseElement}\n offset={offset}\n onMount={(clear) => {\n setScrollableMapClearer(clear);\n }}\n >\n <Comp />\n </AutoScrollProvider>\n );\n }\n\n return AutoScrollManagerComponent;\n });\n }\n}\n"],"mappings":"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","names":["React","ScrollType","React"]}
|
package/package.json
CHANGED
|
@@ -6,18 +6,18 @@
|
|
|
6
6
|
"types"
|
|
7
7
|
],
|
|
8
8
|
"name": "@player-ui/auto-scroll-manager-plugin-react",
|
|
9
|
-
"version": "0.15.2-next.
|
|
9
|
+
"version": "0.15.2-next.3",
|
|
10
10
|
"main": "dist/cjs/index.cjs",
|
|
11
11
|
"devDependencies": {
|
|
12
|
-
"@player-ui/types": "workspace
|
|
13
|
-
"@player-ui/make-flow": "workspace
|
|
14
|
-
"@player-ui/asset-transform-plugin": "workspace
|
|
15
|
-
"@player-ui/common-types-plugin": "workspace
|
|
16
|
-
"@player-ui/reference-assets-plugin-react": "workspace
|
|
17
|
-
"@player-ui/reference-assets-plugin": "workspace
|
|
12
|
+
"@player-ui/types": "workspace:^",
|
|
13
|
+
"@player-ui/make-flow": "workspace:^",
|
|
14
|
+
"@player-ui/asset-transform-plugin": "workspace:^",
|
|
15
|
+
"@player-ui/common-types-plugin": "workspace:^",
|
|
16
|
+
"@player-ui/reference-assets-plugin-react": "workspace:^",
|
|
17
|
+
"@player-ui/reference-assets-plugin": "workspace:^"
|
|
18
18
|
},
|
|
19
19
|
"peerDependencies": {
|
|
20
|
-
"@player-ui/react": "0.15.2-next.
|
|
20
|
+
"@player-ui/react": "0.15.2-next.3",
|
|
21
21
|
"react": "^18.2.0",
|
|
22
22
|
"@types/react": "^18.2.39"
|
|
23
23
|
},
|
|
@@ -387,6 +387,63 @@ describe("scroll reset on view transition", () => {
|
|
|
387
387
|
});
|
|
388
388
|
});
|
|
389
389
|
|
|
390
|
+
describe("scroll reset on view transition cancels in-flight polyfill animation", () => {
|
|
391
|
+
test("dispatches a wheel event and calls scroll(0,0) on transition", async () => {
|
|
392
|
+
vitest.clearAllMocks();
|
|
393
|
+
|
|
394
|
+
const dispatchSpy = vitest
|
|
395
|
+
.spyOn(window, "dispatchEvent")
|
|
396
|
+
.mockImplementation(() => true);
|
|
397
|
+
const scrollSpy = vitest
|
|
398
|
+
.spyOn(window, "scroll")
|
|
399
|
+
.mockImplementation(() => {});
|
|
400
|
+
|
|
401
|
+
vitest.spyOn(document, "getElementById").mockReturnValue(null);
|
|
402
|
+
|
|
403
|
+
const wp = new ReactPlayer({
|
|
404
|
+
plugins: [
|
|
405
|
+
new AssetTransformPlugin([
|
|
406
|
+
[{ type: "action" }, actionTransform],
|
|
407
|
+
[{ type: "input" }, inputTransform],
|
|
408
|
+
[{ type: "info" }, infoTransform],
|
|
409
|
+
]),
|
|
410
|
+
new CommonTypesPlugin(),
|
|
411
|
+
new AutoScrollManagerPlugin({}),
|
|
412
|
+
],
|
|
413
|
+
});
|
|
414
|
+
wp.assetRegistry.set({ type: "info" }, Info);
|
|
415
|
+
wp.assetRegistry.set({ type: "action" }, Action);
|
|
416
|
+
wp.assetRegistry.set({ type: "input" }, Input);
|
|
417
|
+
|
|
418
|
+
wp.start(twoViewFlow as any);
|
|
419
|
+
|
|
420
|
+
const { container } = render(
|
|
421
|
+
<div>
|
|
422
|
+
<React.Suspense fallback="loading...">
|
|
423
|
+
<wp.Component />
|
|
424
|
+
</React.Suspense>
|
|
425
|
+
</div>,
|
|
426
|
+
);
|
|
427
|
+
|
|
428
|
+
await act(() => waitFor(() => {}));
|
|
429
|
+
|
|
430
|
+
// navigate to page 2 — triggers the transition hook
|
|
431
|
+
const action = await findByRole(container, "button");
|
|
432
|
+
act(() => action.click());
|
|
433
|
+
await act(() => waitFor(() => {}));
|
|
434
|
+
|
|
435
|
+
// wheel event must have been dispatched to cancel the polyfill's RAF
|
|
436
|
+
expect(dispatchSpy).toHaveBeenCalledWith(
|
|
437
|
+
expect.objectContaining({ type: "wheel" }),
|
|
438
|
+
);
|
|
439
|
+
|
|
440
|
+
// scroll must be reset to top
|
|
441
|
+
expect(scrollSpy).toHaveBeenCalledWith(0, 0);
|
|
442
|
+
|
|
443
|
+
vitest.restoreAllMocks();
|
|
444
|
+
});
|
|
445
|
+
});
|
|
446
|
+
|
|
390
447
|
describe("getFirstScrollableElement unit tests", () => {
|
|
391
448
|
const defineGetElementId = (bcrValues: any[]) => {
|
|
392
449
|
return (idIn: string): HTMLElement | null => {
|
package/src/plugin.tsx
CHANGED
|
@@ -140,7 +140,8 @@ export class AutoScrollManagerPlugin implements ReactPlayerPlugin {
|
|
|
140
140
|
this.failedNavigation = false;
|
|
141
141
|
this.alreadyScrolledTo = [];
|
|
142
142
|
this.clearScrollableMap();
|
|
143
|
-
//
|
|
143
|
+
// Cancel any in-flight polyfill smooth-scroll animation before resetting.
|
|
144
|
+
window.dispatchEvent(new WheelEvent("wheel", { bubbles: true }));
|
|
144
145
|
window.scroll(0, 0);
|
|
145
146
|
});
|
|
146
147
|
flow.hooks.skipTransition.intercept({
|