@player-ui/auto-scroll-manager-plugin-react 0.15.1-next.2 → 0.15.1-next.4

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.
@@ -61,9 +61,13 @@ var AutoScrollProvider = ({
61
61
  getElementToScrollTo,
62
62
  getBaseElement,
63
63
  offset,
64
+ onMount,
64
65
  children
65
66
  }) => {
66
67
  const [scrollableMap, setScrollableMap] = (0, import_react.useState)(/* @__PURE__ */ new Map());
68
+ (0, import_react.useEffect)(() => {
69
+ onMount?.(() => setScrollableMap(/* @__PURE__ */ new Map()));
70
+ }, []);
67
71
  const updateScrollableMap = (key, value) => {
68
72
  setScrollableMap((prev) => {
69
73
  const nm = new Map(prev);
@@ -82,7 +86,7 @@ var AutoScrollProvider = ({
82
86
  if (node) {
83
87
  scrollIntoViewWithOffset(node, getBaseElement() || document.body, offset);
84
88
  }
85
- });
89
+ }, [scrollableMap]);
86
90
  return /* @__PURE__ */ import_react.default.createElement(AutoScrollManagerContext.Provider, { value: { register } }, children);
87
91
  };
88
92
 
@@ -104,6 +108,8 @@ var AutoScrollManagerPlugin = class {
104
108
  this.initialRender = false;
105
109
  this.failedNavigation = false;
106
110
  this.alreadyScrolledTo = [];
111
+ this.clearScrollableMap = () => {
112
+ };
107
113
  this.scrollFn = this.calculateScroll.bind(this);
108
114
  }
109
115
  getFirstScrollableElement(idList, type) {
@@ -162,6 +168,7 @@ var AutoScrollManagerPlugin = class {
162
168
  this.initialRender = true;
163
169
  this.failedNavigation = false;
164
170
  this.alreadyScrolledTo = [];
171
+ this.clearScrollableMap();
165
172
  window.scroll(0, 0);
166
173
  });
167
174
  flow.hooks.skipTransition.intercept({
@@ -175,13 +182,19 @@ var AutoScrollManagerPlugin = class {
175
182
  applyReact(reactPlayer) {
176
183
  reactPlayer.hooks.webComponent.tap(this.name, (Comp) => {
177
184
  const { scrollFn, getBaseElement, offset } = this;
185
+ const setScrollableMapClearer = (clear) => {
186
+ this.clearScrollableMap = clear;
187
+ };
178
188
  function AutoScrollManagerComponent() {
179
189
  return /* @__PURE__ */ import_react2.default.createElement(
180
190
  AutoScrollProvider,
181
191
  {
182
192
  getElementToScrollTo: scrollFn,
183
193
  getBaseElement,
184
- offset
194
+ offset,
195
+ onMount: (clear) => {
196
+ setScrollableMapClearer(clear);
197
+ }
185
198
  },
186
199
  /* @__PURE__ */ import_react2.default.createElement(Comp, null)
187
200
  );
@@ -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}\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 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 /** 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 });\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 /** 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.scrollFn = this.calculateScroll.bind(this);\n }\n\n getFirstScrollableElement(idList: Set<string>, type: ScrollType) {\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>>) {\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) {\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 // 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) {\n reactPlayer.hooks.webComponent.tap(this.name, (Comp) => {\n const { scrollFn, getBaseElement, offset } = this;\n\n function AutoScrollManagerComponent() {\n return (\n <AutoScrollProvider\n getElementToScrollTo={scrollFn}\n getBaseElement={getBaseElement}\n offset={offset}\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 // 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"]}
@@ -21,9 +21,13 @@ var AutoScrollProvider = ({
21
21
  getElementToScrollTo,
22
22
  getBaseElement,
23
23
  offset,
24
+ onMount,
24
25
  children
25
26
  }) => {
26
27
  const [scrollableMap, setScrollableMap] = useState(/* @__PURE__ */ new Map());
28
+ useEffect(() => {
29
+ onMount?.(() => setScrollableMap(/* @__PURE__ */ new Map()));
30
+ }, []);
27
31
  const updateScrollableMap = (key, value) => {
28
32
  setScrollableMap((prev) => {
29
33
  const nm = new Map(prev);
@@ -42,7 +46,7 @@ var AutoScrollProvider = ({
42
46
  if (node) {
43
47
  scrollIntoViewWithOffset(node, getBaseElement() || document.body, offset);
44
48
  }
45
- });
49
+ }, [scrollableMap]);
46
50
  return /* @__PURE__ */ React.createElement(AutoScrollManagerContext.Provider, { value: { register } }, children);
47
51
  };
48
52
 
@@ -64,6 +68,8 @@ var AutoScrollManagerPlugin = class {
64
68
  this.initialRender = false;
65
69
  this.failedNavigation = false;
66
70
  this.alreadyScrolledTo = [];
71
+ this.clearScrollableMap = () => {
72
+ };
67
73
  this.scrollFn = this.calculateScroll.bind(this);
68
74
  }
69
75
  getFirstScrollableElement(idList, type) {
@@ -122,6 +128,7 @@ var AutoScrollManagerPlugin = class {
122
128
  this.initialRender = true;
123
129
  this.failedNavigation = false;
124
130
  this.alreadyScrolledTo = [];
131
+ this.clearScrollableMap();
125
132
  window.scroll(0, 0);
126
133
  });
127
134
  flow.hooks.skipTransition.intercept({
@@ -135,13 +142,19 @@ var AutoScrollManagerPlugin = class {
135
142
  applyReact(reactPlayer) {
136
143
  reactPlayer.hooks.webComponent.tap(this.name, (Comp) => {
137
144
  const { scrollFn, getBaseElement, offset } = this;
145
+ const setScrollableMapClearer = (clear) => {
146
+ this.clearScrollableMap = clear;
147
+ };
138
148
  function AutoScrollManagerComponent() {
139
149
  return /* @__PURE__ */ React2.createElement(
140
150
  AutoScrollProvider,
141
151
  {
142
152
  getElementToScrollTo: scrollFn,
143
153
  getBaseElement,
144
- offset
154
+ offset,
155
+ onMount: (clear) => {
156
+ setScrollableMapClearer(clear);
157
+ }
145
158
  },
146
159
  /* @__PURE__ */ React2.createElement(Comp, null)
147
160
  );
package/dist/index.mjs CHANGED
@@ -21,9 +21,13 @@ var AutoScrollProvider = ({
21
21
  getElementToScrollTo,
22
22
  getBaseElement,
23
23
  offset,
24
+ onMount,
24
25
  children
25
26
  }) => {
26
27
  const [scrollableMap, setScrollableMap] = useState(/* @__PURE__ */ new Map());
28
+ useEffect(() => {
29
+ onMount?.(() => setScrollableMap(/* @__PURE__ */ new Map()));
30
+ }, []);
27
31
  const updateScrollableMap = (key, value) => {
28
32
  setScrollableMap((prev) => {
29
33
  const nm = new Map(prev);
@@ -42,7 +46,7 @@ var AutoScrollProvider = ({
42
46
  if (node) {
43
47
  scrollIntoViewWithOffset(node, getBaseElement() || document.body, offset);
44
48
  }
45
- });
49
+ }, [scrollableMap]);
46
50
  return /* @__PURE__ */ React.createElement(AutoScrollManagerContext.Provider, { value: { register } }, children);
47
51
  };
48
52
 
@@ -64,6 +68,8 @@ var AutoScrollManagerPlugin = class {
64
68
  this.initialRender = false;
65
69
  this.failedNavigation = false;
66
70
  this.alreadyScrolledTo = [];
71
+ this.clearScrollableMap = () => {
72
+ };
67
73
  this.scrollFn = this.calculateScroll.bind(this);
68
74
  }
69
75
  getFirstScrollableElement(idList, type) {
@@ -122,6 +128,7 @@ var AutoScrollManagerPlugin = class {
122
128
  this.initialRender = true;
123
129
  this.failedNavigation = false;
124
130
  this.alreadyScrolledTo = [];
131
+ this.clearScrollableMap();
125
132
  window.scroll(0, 0);
126
133
  });
127
134
  flow.hooks.skipTransition.intercept({
@@ -135,13 +142,19 @@ var AutoScrollManagerPlugin = class {
135
142
  applyReact(reactPlayer) {
136
143
  reactPlayer.hooks.webComponent.tap(this.name, (Comp) => {
137
144
  const { scrollFn, getBaseElement, offset } = this;
145
+ const setScrollableMapClearer = (clear) => {
146
+ this.clearScrollableMap = clear;
147
+ };
138
148
  function AutoScrollManagerComponent() {
139
149
  return /* @__PURE__ */ React2.createElement(
140
150
  AutoScrollProvider,
141
151
  {
142
152
  getElementToScrollTo: scrollFn,
143
153
  getBaseElement,
144
- offset
154
+ offset,
155
+ onMount: (clear) => {
156
+ setScrollableMapClearer(clear);
157
+ }
145
158
  },
146
159
  /* @__PURE__ */ React2.createElement(Comp, null)
147
160
  );
@@ -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}\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 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 /** 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 });\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 /** 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.scrollFn = this.calculateScroll.bind(this);\n }\n\n getFirstScrollableElement(idList: Set<string>, type: ScrollType) {\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>>) {\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) {\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 // 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) {\n reactPlayer.hooks.webComponent.tap(this.name, (Comp) => {\n const { scrollFn, getBaseElement, offset } = this;\n\n function AutoScrollManagerComponent() {\n return (\n <AutoScrollProvider\n getElementToScrollTo={scrollFn}\n getBaseElement={getBaseElement}\n offset={offset}\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;;;ADMO,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;AACF,MAAkD;AAIhD,QAAM,CAAC,eAAe,gBAAgB,IAAI,SAExC,oBAAI,IAAI,CAAC;AAGX,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,CAAC;AAED,SACE,oCAAC,yBAAyB,UAAzB,EAAkC,OAAO,EAAE,SAAS,KAClD,QACH;AAEJ;;;AEnFA,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,EA2BhE,YAAY,QAAiC;AA1B7C,gBAAO;AA2BL,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,WAAW,KAAK,gBAAgB,KAAK,IAAI;AAAA,EAChD;AAAA,EAEA,0BAA0B,QAAqB,MAAkB;AAC/D,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,oBAAkD;AAChE,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,QAAgB;AACpB,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;AAE1B,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,aAA0B;AACnC,gBAAY,MAAM,aAAa,IAAI,KAAK,MAAM,CAAC,SAAS;AACtD,YAAM,EAAE,UAAU,gBAAgB,OAAO,IAAI;AAE7C,eAAS,6BAA6B;AACpC,eACE,gBAAAC,OAAA;AAAA,UAAC;AAAA;AAAA,YACC,sBAAsB;AAAA,YACtB;AAAA,YACA;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 // 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","ScrollType","React"]}
package/package.json CHANGED
@@ -6,7 +6,7 @@
6
6
  "types"
7
7
  ],
8
8
  "name": "@player-ui/auto-scroll-manager-plugin-react",
9
- "version": "0.15.1-next.2",
9
+ "version": "0.15.1-next.4",
10
10
  "main": "dist/cjs/index.cjs",
11
11
  "devDependencies": {
12
12
  "@player-ui/types": "workspace:*",
@@ -17,7 +17,7 @@
17
17
  "@player-ui/reference-assets-plugin": "workspace:*"
18
18
  },
19
19
  "peerDependencies": {
20
- "@player-ui/react": "0.15.1-next.2",
20
+ "@player-ui/react": "0.15.1-next.4",
21
21
  "react": "^18.2.0",
22
22
  "@types/react": "^18.2.39"
23
23
  },
@@ -177,24 +177,24 @@ describe("auto-scroll plugin", () => {
177
177
 
178
178
  const action = await findByRole(container, "button");
179
179
  act(() => action.click());
180
- await act(() => waitFor(() => {}));
181
-
182
- expect(scrollIntoViewWithOffset).toBeCalledWith(
183
- expect.anything(),
184
- expect.objectContaining({ id: "view" }),
185
- 40,
180
+ await waitFor(() =>
181
+ expect(scrollIntoViewWithOffset).toBeCalledWith(
182
+ expect.anything(),
183
+ expect.objectContaining({ id: "view" }),
184
+ 40,
185
+ ),
186
186
  );
187
187
 
188
188
  // Mock the case where the base element can't be found, so document.body is used as a fallback
189
189
  getBaseElementMock.mockReturnValue(null);
190
190
 
191
191
  act(() => action.click());
192
- await act(() => waitFor(() => {}));
193
-
194
- expect(scrollIntoViewWithOffset).toHaveBeenLastCalledWith(
195
- expect.anything(),
196
- document.body,
197
- 40,
192
+ await waitFor(() =>
193
+ expect(scrollIntoViewWithOffset).toHaveBeenLastCalledWith(
194
+ expect.anything(),
195
+ document.body,
196
+ 40,
197
+ ),
198
198
  );
199
199
  });
200
200
 
@@ -233,12 +233,12 @@ describe("auto-scroll plugin", () => {
233
233
 
234
234
  const action = await findByRole(container, "button");
235
235
  act(() => action.click());
236
- await act(() => waitFor(() => {}));
237
-
238
- expect(scrollIntoViewWithOffset).toBeCalledWith(
239
- expect.anything(),
240
- document.body,
241
- 0,
236
+ await waitFor(() =>
237
+ expect(scrollIntoViewWithOffset).toBeCalledWith(
238
+ expect.anything(),
239
+ document.body,
240
+ 0,
241
+ ),
242
242
  );
243
243
  });
244
244
 
@@ -280,6 +280,113 @@ describe("auto-scroll plugin", () => {
280
280
  });
281
281
  });
282
282
 
283
+ const twoViewFlow = {
284
+ id: "two-view-flow",
285
+ views: [
286
+ {
287
+ id: "view-1",
288
+ type: "info",
289
+ primaryInfo: {
290
+ asset: { id: "info-1", type: "input", binding: "person.name" },
291
+ },
292
+ actions: [
293
+ { asset: { id: "next-action", type: "action", value: "Next" } },
294
+ ],
295
+ },
296
+ {
297
+ id: "view-2",
298
+ type: "info",
299
+ primaryInfo: {
300
+ asset: { id: "info-2", type: "input", binding: "person.age" },
301
+ },
302
+ actions: [],
303
+ },
304
+ ],
305
+ schema: {},
306
+ data: { person: { name: "sam", age: "30" } },
307
+ navigation: {
308
+ BEGIN: "FLOW_Start",
309
+ FLOW_Start: {
310
+ startState: "VIEW_1",
311
+ VIEW_1: {
312
+ state_type: "VIEW",
313
+ ref: "view-1",
314
+ transitions: { "*": "VIEW_2" },
315
+ },
316
+ VIEW_2: {
317
+ state_type: "VIEW",
318
+ ref: "view-2",
319
+ transitions: { "*": "END_Done" },
320
+ },
321
+ END_Done: { state_type: "END", outcome: "done" },
322
+ },
323
+ },
324
+ };
325
+
326
+ describe("scroll reset on view transition", () => {
327
+ test("does not scroll to page 1 elements after view transition (stale scrollableMap)", async () => {
328
+ vitest.clearAllMocks();
329
+
330
+ const withFirstAppearance = (Component: ComponentType<any>) => {
331
+ const Scrollable = (props: any) => {
332
+ const register = useRegisterAsScrollable();
333
+ useLayoutEffect(() => {
334
+ register({ type: ScrollType.FirstAppearance, ref: props.id });
335
+ }, []);
336
+ return <Component {...props} />;
337
+ };
338
+ return Scrollable;
339
+ };
340
+
341
+ // only "info-1" (page 1) is findable. if the map isn't cleared on transition,
342
+ // "info-1" remains a scroll target and scrollIntoViewWithOffset gets called.
343
+ vitest.spyOn(document, "getElementById").mockImplementation((id) => {
344
+ if (id === "info-1") {
345
+ return { getBoundingClientRect: () => ({ top: 50 }) } as any;
346
+ }
347
+ return null;
348
+ });
349
+
350
+ const wp = new ReactPlayer({
351
+ plugins: [
352
+ new AssetTransformPlugin([
353
+ [{ type: "action" }, actionTransform],
354
+ [{ type: "input" }, inputTransform],
355
+ [{ type: "info" }, infoTransform],
356
+ ]),
357
+ new CommonTypesPlugin(),
358
+ new AutoScrollManagerPlugin({}),
359
+ ],
360
+ });
361
+ wp.assetRegistry.set({ type: "info" }, Info);
362
+ wp.assetRegistry.set({ type: "action" }, Action);
363
+ wp.assetRegistry.set({ type: "input" }, withFirstAppearance(Input));
364
+
365
+ wp.start(twoViewFlow as any);
366
+
367
+ const { container } = render(
368
+ <div>
369
+ <React.Suspense fallback="loading...">
370
+ <wp.Component />
371
+ </React.Suspense>
372
+ </div>,
373
+ );
374
+
375
+ // wait for page 1 to settle, then reset scroll call history so we only assert on page 2
376
+ await act(() => waitFor(() => {}));
377
+ (scrollIntoViewWithOffset as ReturnType<typeof vitest.fn>).mockClear();
378
+
379
+ // navigate to page 2
380
+ const action = await findByRole(container, "button");
381
+ act(() => action.click());
382
+ await act(() => waitFor(() => {}));
383
+
384
+ // map was cleared on transition — "info-1" is no longer a scroll target
385
+ expect(scrollIntoViewWithOffset).not.toHaveBeenCalled();
386
+ vitest.restoreAllMocks();
387
+ });
388
+ });
389
+
283
390
  describe("getFirstScrollableElement unit tests", () => {
284
391
  const defineGetElementId = (bcrValues: any[]) => {
285
392
  return (idIn: string): HTMLElement | null => {
package/src/hooks.tsx CHANGED
@@ -12,6 +12,8 @@ export interface AutoScrollProviderProps {
12
12
  getBaseElement: () => HTMLElement | undefined | null;
13
13
  /** Additional offset to be used (default: 0) */
14
14
  offset: number;
15
+ /** Called on mount with a function that clears all registered scroll targets */
16
+ onMount?: (clearScrollableMap: () => void) => void;
15
17
  }
16
18
 
17
19
  export interface RegisterData {
@@ -41,6 +43,7 @@ export const AutoScrollProvider = ({
41
43
  getElementToScrollTo,
42
44
  getBaseElement,
43
45
  offset,
46
+ onMount,
44
47
  children,
45
48
  }: PropsWithChildren<AutoScrollProviderProps>) => {
46
49
  // Tracker for what elements are registered to be scroll targets
@@ -50,6 +53,10 @@ export const AutoScrollProvider = ({
50
53
  Map<ScrollType, Set<string>>
51
54
  >(new Map());
52
55
 
56
+ useEffect(() => {
57
+ onMount?.(() => setScrollableMap(new Map()));
58
+ }, []);
59
+
53
60
  /** Add a new entry as a scroll target */
54
61
  const updateScrollableMap = (key: ScrollType, value: string) => {
55
62
  setScrollableMap((prev) => {
@@ -76,7 +83,7 @@ export const AutoScrollProvider = ({
76
83
  if (node) {
77
84
  scrollIntoViewWithOffset(node, getBaseElement() || document.body, offset);
78
85
  }
79
- });
86
+ }, [scrollableMap]);
80
87
 
81
88
  return (
82
89
  <AutoScrollManagerContext.Provider value={{ register }}>
package/src/plugin.tsx CHANGED
@@ -42,6 +42,9 @@ export class AutoScrollManagerPlugin implements ReactPlayerPlugin {
42
42
  /** static offset */
43
43
  private offset: number;
44
44
 
45
+ /** clears scrollableMap in AutoScrollProvider — set when the provider mounts */
46
+ private clearScrollableMap: () => void;
47
+
45
48
  /** map of scroll type to set of ids that are registered under that type */
46
49
  private alreadyScrolledTo: Array<string>;
47
50
  private scrollFn: (
@@ -56,10 +59,11 @@ export class AutoScrollManagerPlugin implements ReactPlayerPlugin {
56
59
  this.initialRender = false;
57
60
  this.failedNavigation = false;
58
61
  this.alreadyScrolledTo = [];
62
+ this.clearScrollableMap = () => {};
59
63
  this.scrollFn = this.calculateScroll.bind(this);
60
64
  }
61
65
 
62
- getFirstScrollableElement(idList: Set<string>, type: ScrollType) {
66
+ getFirstScrollableElement(idList: Set<string>, type: ScrollType): string {
63
67
  const highestElement = {
64
68
  id: "",
65
69
  ypos: 0,
@@ -98,7 +102,7 @@ export class AutoScrollManagerPlugin implements ReactPlayerPlugin {
98
102
  return highestElement.id;
99
103
  }
100
104
 
101
- calculateScroll(scrollableElements: Map<ScrollType, Set<string>>) {
105
+ calculateScroll(scrollableElements: Map<ScrollType, Set<string>>): string {
102
106
  let currentScroll = ScrollType.FirstAppearance;
103
107
  if (this.initialRender) {
104
108
  if (this.autoScrollOnLoad) {
@@ -127,7 +131,7 @@ export class AutoScrollManagerPlugin implements ReactPlayerPlugin {
127
131
  }
128
132
 
129
133
  // Hooks into player flow to determine what scroll targets need to be evaluated at specific lifecycle points
130
- apply(player: Player) {
134
+ apply(player: Player): void {
131
135
  player.hooks.flowController.tap(this.name, (fc) => {
132
136
  fc.hooks.flow.tap(this.name, (flow) => {
133
137
  flow.hooks.transition.tap(this.name, () => {
@@ -135,6 +139,7 @@ export class AutoScrollManagerPlugin implements ReactPlayerPlugin {
135
139
  this.initialRender = true;
136
140
  this.failedNavigation = false;
137
141
  this.alreadyScrolledTo = [];
142
+ this.clearScrollableMap();
138
143
  // Reset scroll position for new view
139
144
  window.scroll(0, 0);
140
145
  });
@@ -147,9 +152,12 @@ export class AutoScrollManagerPlugin implements ReactPlayerPlugin {
147
152
  });
148
153
  }
149
154
 
150
- applyReact(reactPlayer: ReactPlayer) {
155
+ applyReact(reactPlayer: ReactPlayer): void {
151
156
  reactPlayer.hooks.webComponent.tap(this.name, (Comp) => {
152
157
  const { scrollFn, getBaseElement, offset } = this;
158
+ const setScrollableMapClearer = (clear: () => void) => {
159
+ this.clearScrollableMap = clear;
160
+ };
153
161
 
154
162
  function AutoScrollManagerComponent() {
155
163
  return (
@@ -157,6 +165,9 @@ export class AutoScrollManagerPlugin implements ReactPlayerPlugin {
157
165
  getElementToScrollTo={scrollFn}
158
166
  getBaseElement={getBaseElement}
159
167
  offset={offset}
168
+ onMount={(clear) => {
169
+ setScrollableMapClearer(clear);
170
+ }}
160
171
  >
161
172
  <Comp />
162
173
  </AutoScrollProvider>
package/types/hooks.d.ts CHANGED
@@ -8,6 +8,8 @@ export interface AutoScrollProviderProps {
8
8
  getBaseElement: () => HTMLElement | undefined | null;
9
9
  /** Additional offset to be used (default: 0) */
10
10
  offset: number;
11
+ /** Called on mount with a function that clears all registered scroll targets */
12
+ onMount?: (clearScrollableMap: () => void) => void;
11
13
  }
12
14
  export interface RegisterData {
13
15
  /** when to scroll to the target */
@@ -23,5 +25,5 @@ export declare const AutoScrollManagerContext: React.Context<{
23
25
  /** hook to register as a scroll target */
24
26
  export declare const useRegisterAsScrollable: () => ScrollFunction;
25
27
  /** Component to handle scrolling */
26
- export declare const AutoScrollProvider: ({ getElementToScrollTo, getBaseElement, offset, children, }: PropsWithChildren<AutoScrollProviderProps>) => React.JSX.Element;
28
+ export declare const AutoScrollProvider: ({ getElementToScrollTo, getBaseElement, offset, onMount, children, }: PropsWithChildren<AutoScrollProviderProps>) => React.JSX.Element;
27
29
  //# sourceMappingURL=hooks.d.ts.map
package/types/plugin.d.ts CHANGED
@@ -30,6 +30,8 @@ export declare class AutoScrollManagerPlugin implements ReactPlayerPlugin {
30
30
  private getBaseElement;
31
31
  /** static offset */
32
32
  private offset;
33
+ /** clears scrollableMap in AutoScrollProvider — set when the provider mounts */
34
+ private clearScrollableMap;
33
35
  /** map of scroll type to set of ids that are registered under that type */
34
36
  private alreadyScrolledTo;
35
37
  private scrollFn;