@player-ui/auto-scroll-manager-plugin-react 0.8.0--canary.307.9645 → 0.8.0--canary.410.15865
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 +201 -0
- package/dist/cjs/index.cjs.map +1 -0
- package/dist/{index.esm.js → index.legacy-esm.js} +58 -43
- package/dist/index.mjs +160 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +31 -58
- package/src/__tests__/plugin.test.tsx +411 -0
- package/src/hooks.tsx +5 -5
- package/src/index.tsx +2 -2
- package/src/plugin.tsx +18 -15
- package/src/scrollIntoViewWithOffset.ts +3 -3
- package/types/hooks.d.ts +27 -0
- package/types/index.d.ts +3 -0
- package/types/plugin.d.ts +42 -0
- package/types/scrollIntoViewWithOffset.d.ts +9 -0
- package/dist/index.cjs.js +0 -156
- package/dist/index.d.ts +0 -69
package/package.json
CHANGED
|
@@ -1,66 +1,39 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@player-ui/auto-scroll-manager-plugin-react",
|
|
3
|
-
"version": "0.8.0--canary.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
"
|
|
3
|
+
"version": "0.8.0--canary.410.15865",
|
|
4
|
+
"main": "dist/cjs/index.cjs",
|
|
5
|
+
"devDependencies": {
|
|
6
|
+
"@player-ui/types": "workspace:*",
|
|
7
|
+
"@player-ui/make-flow": "workspace:*",
|
|
8
|
+
"@player-ui/asset-transform-plugin": "workspace:*",
|
|
9
|
+
"@player-ui/common-types-plugin": "workspace:*",
|
|
10
|
+
"@player-ui/reference-assets-plugin-react": "workspace:*",
|
|
11
|
+
"@player-ui/reference-assets-plugin": "workspace:*"
|
|
7
12
|
},
|
|
8
13
|
"peerDependencies": {
|
|
9
|
-
"@player-ui/react": "0.8.0--canary.
|
|
14
|
+
"@player-ui/react": "0.8.0--canary.410.15865",
|
|
15
|
+
"react": "^18.2.0",
|
|
16
|
+
"@types/react": "^18.2.39"
|
|
10
17
|
},
|
|
11
|
-
"
|
|
12
|
-
|
|
13
|
-
"@babel/runtime": "7.15.4"
|
|
14
|
-
},
|
|
15
|
-
"main": "dist/index.cjs.js",
|
|
16
|
-
"module": "dist/index.esm.js",
|
|
17
|
-
"typings": "dist/index.d.ts",
|
|
18
|
+
"module": "dist/index.legacy-esm.js",
|
|
19
|
+
"types": "types/index.d.ts",
|
|
18
20
|
"sideEffects": false,
|
|
19
|
-
"
|
|
20
|
-
|
|
21
|
-
"
|
|
22
|
-
"
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
},
|
|
27
|
-
"homepage": "https://player-ui.github.io",
|
|
28
|
-
"contributors": [
|
|
29
|
-
{
|
|
30
|
-
"name": "Adam Dierkens",
|
|
31
|
-
"url": "https://github.com/adierkens"
|
|
32
|
-
},
|
|
33
|
-
{
|
|
34
|
-
"name": "Spencer Hamm",
|
|
35
|
-
"url": "https://github.com/spentacular"
|
|
36
|
-
},
|
|
37
|
-
{
|
|
38
|
-
"name": "Harris Borawski",
|
|
39
|
-
"url": "https://github.com/hborawski"
|
|
40
|
-
},
|
|
41
|
-
{
|
|
42
|
-
"name": "Jeremiah Zucker",
|
|
43
|
-
"url": "https://github.com/sugarmanz"
|
|
44
|
-
},
|
|
45
|
-
{
|
|
46
|
-
"name": "Ketan Reddy",
|
|
47
|
-
"url": "https://github.com/KetanReddy"
|
|
48
|
-
},
|
|
49
|
-
{
|
|
50
|
-
"name": "Brocollie08",
|
|
51
|
-
"url": "https://github.com/brocollie08"
|
|
52
|
-
},
|
|
53
|
-
{
|
|
54
|
-
"name": "Kelly Harrop",
|
|
55
|
-
"url": "https://github.com/kharrop"
|
|
56
|
-
},
|
|
57
|
-
{
|
|
58
|
-
"name": "Alejandro Fimbres",
|
|
59
|
-
"url": "https://github.com/lexfm"
|
|
60
|
-
},
|
|
61
|
-
{
|
|
62
|
-
"name": "Rafael Campos",
|
|
63
|
-
"url": "https://github.com/rafbcampos"
|
|
21
|
+
"exports": {
|
|
22
|
+
"./package.json": "./package.json",
|
|
23
|
+
"./dist/index.css": "./dist/index.css",
|
|
24
|
+
".": {
|
|
25
|
+
"types": "./types/index.d.ts",
|
|
26
|
+
"import": "./dist/index.mjs",
|
|
27
|
+
"default": "./dist/cjs/index.cjs"
|
|
64
28
|
}
|
|
65
|
-
|
|
29
|
+
},
|
|
30
|
+
"files": [
|
|
31
|
+
"dist",
|
|
32
|
+
"src",
|
|
33
|
+
"types"
|
|
34
|
+
],
|
|
35
|
+
"dependencies": {
|
|
36
|
+
"seamless-scroll-polyfill": "^2.3.4",
|
|
37
|
+
"tslib": "^2.6.2"
|
|
38
|
+
}
|
|
66
39
|
}
|
|
@@ -0,0 +1,411 @@
|
|
|
1
|
+
import { test, expect, vitest, describe, beforeEach } from "vitest";
|
|
2
|
+
import type { ComponentType } from "react";
|
|
3
|
+
import React, { useLayoutEffect } from "react";
|
|
4
|
+
import type { InProgressState } from "@player-ui/react";
|
|
5
|
+
import { ReactPlayer } from "@player-ui/react";
|
|
6
|
+
|
|
7
|
+
import { findByRole, render, waitFor, act } from "@testing-library/react";
|
|
8
|
+
import { makeFlow } from "@player-ui/make-flow";
|
|
9
|
+
|
|
10
|
+
import {
|
|
11
|
+
actionTransform,
|
|
12
|
+
inputTransform,
|
|
13
|
+
infoTransform,
|
|
14
|
+
} from "@player-ui/reference-assets-plugin";
|
|
15
|
+
import { Info, Action, Input } from "@player-ui/reference-assets-plugin-react";
|
|
16
|
+
import { CommonTypesPlugin } from "@player-ui/common-types-plugin";
|
|
17
|
+
import { AssetTransformPlugin } from "@player-ui/asset-transform-plugin";
|
|
18
|
+
|
|
19
|
+
import scrollIntoViewWithOffset from "../scrollIntoViewWithOffset";
|
|
20
|
+
|
|
21
|
+
import {
|
|
22
|
+
AutoScrollManagerPlugin,
|
|
23
|
+
ScrollType,
|
|
24
|
+
useRegisterAsScrollable,
|
|
25
|
+
} from "..";
|
|
26
|
+
|
|
27
|
+
vitest.mock("../scrollIntoViewWithOffset");
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* HOC to enable scrollable behavior for a given component
|
|
31
|
+
*
|
|
32
|
+
* - @param Component
|
|
33
|
+
*/
|
|
34
|
+
const withScrollable = (Component: ComponentType<any>) => {
|
|
35
|
+
/**
|
|
36
|
+
*
|
|
37
|
+
*/
|
|
38
|
+
const ScrollableComponent = (props: any) => {
|
|
39
|
+
const registerFunction = useRegisterAsScrollable();
|
|
40
|
+
|
|
41
|
+
useLayoutEffect(() => {
|
|
42
|
+
registerFunction({ type: ScrollType.ValidationError, ref: props.id });
|
|
43
|
+
}, [props.validation]);
|
|
44
|
+
|
|
45
|
+
return <Component {...props} />;
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
return ScrollableComponent;
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
describe("auto-scroll plugin", () => {
|
|
52
|
+
beforeEach(() => {
|
|
53
|
+
vitest.clearAllMocks();
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
const flow = makeFlow({
|
|
57
|
+
id: "view-1",
|
|
58
|
+
type: "info",
|
|
59
|
+
primaryInfo: {
|
|
60
|
+
asset: {
|
|
61
|
+
id: "asset",
|
|
62
|
+
type: "input",
|
|
63
|
+
binding: "person.name",
|
|
64
|
+
},
|
|
65
|
+
},
|
|
66
|
+
actions: [
|
|
67
|
+
{
|
|
68
|
+
asset: {
|
|
69
|
+
id: "action-auto-scroll",
|
|
70
|
+
type: "action",
|
|
71
|
+
value: "Next",
|
|
72
|
+
},
|
|
73
|
+
},
|
|
74
|
+
],
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
flow.schema = {
|
|
78
|
+
ROOT: {
|
|
79
|
+
person: {
|
|
80
|
+
type: "PersonType",
|
|
81
|
+
},
|
|
82
|
+
},
|
|
83
|
+
PersonType: {
|
|
84
|
+
name: {
|
|
85
|
+
type: "StringType",
|
|
86
|
+
validation: [
|
|
87
|
+
{
|
|
88
|
+
type: "required",
|
|
89
|
+
message: "Required",
|
|
90
|
+
},
|
|
91
|
+
],
|
|
92
|
+
},
|
|
93
|
+
},
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
document.getElementById = (id: any) => {
|
|
97
|
+
if (id === "asset") {
|
|
98
|
+
return {
|
|
99
|
+
getAttribute: () => "true",
|
|
100
|
+
getBoundingClientRect: () => ({ top: 50 }),
|
|
101
|
+
} as any;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
return undefined;
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
test("scrolls successfully test", async () => {
|
|
108
|
+
const wp = new ReactPlayer({
|
|
109
|
+
plugins: [
|
|
110
|
+
new AssetTransformPlugin([
|
|
111
|
+
[{ type: "action" }, actionTransform],
|
|
112
|
+
[{ type: "input" }, inputTransform],
|
|
113
|
+
[{ type: "info" }, infoTransform],
|
|
114
|
+
]),
|
|
115
|
+
new CommonTypesPlugin(),
|
|
116
|
+
new AutoScrollManagerPlugin({
|
|
117
|
+
autoFocusOnErrorField: true,
|
|
118
|
+
}),
|
|
119
|
+
],
|
|
120
|
+
});
|
|
121
|
+
wp.assetRegistry.set({ type: "info" }, Info);
|
|
122
|
+
wp.assetRegistry.set({ type: "action" }, Action);
|
|
123
|
+
wp.assetRegistry.set({ type: "input" }, withScrollable(Input));
|
|
124
|
+
|
|
125
|
+
wp.start(flow as any);
|
|
126
|
+
|
|
127
|
+
const { container } = render(
|
|
128
|
+
<div>
|
|
129
|
+
<React.Suspense fallback="loading...">
|
|
130
|
+
<wp.Component />
|
|
131
|
+
</React.Suspense>
|
|
132
|
+
</div>,
|
|
133
|
+
);
|
|
134
|
+
|
|
135
|
+
await waitFor(async () => {
|
|
136
|
+
const action = await findByRole(container, "button");
|
|
137
|
+
action.click();
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
expect(scrollIntoViewWithOffset).toBeCalledTimes(1);
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
test("works with custom base element and offset", async () => {
|
|
144
|
+
const getBaseElementMock = vitest.fn();
|
|
145
|
+
|
|
146
|
+
const wp = new ReactPlayer({
|
|
147
|
+
plugins: [
|
|
148
|
+
new AssetTransformPlugin([
|
|
149
|
+
[{ type: "action" }, actionTransform],
|
|
150
|
+
[{ type: "input" }, inputTransform],
|
|
151
|
+
[{ type: "info" }, infoTransform],
|
|
152
|
+
]),
|
|
153
|
+
new CommonTypesPlugin(),
|
|
154
|
+
new AutoScrollManagerPlugin({
|
|
155
|
+
autoFocusOnErrorField: true,
|
|
156
|
+
getBaseElement: getBaseElementMock,
|
|
157
|
+
offset: 40,
|
|
158
|
+
}),
|
|
159
|
+
],
|
|
160
|
+
});
|
|
161
|
+
wp.assetRegistry.set({ type: "info" }, Info);
|
|
162
|
+
wp.assetRegistry.set({ type: "action" }, Action);
|
|
163
|
+
wp.assetRegistry.set({ type: "input" }, withScrollable(Input));
|
|
164
|
+
|
|
165
|
+
wp.start(flow as any);
|
|
166
|
+
|
|
167
|
+
const { container } = render(
|
|
168
|
+
<div>
|
|
169
|
+
<React.Suspense fallback="loading...">
|
|
170
|
+
<wp.Component />
|
|
171
|
+
</React.Suspense>
|
|
172
|
+
</div>,
|
|
173
|
+
);
|
|
174
|
+
await act(() => waitFor(() => {}));
|
|
175
|
+
|
|
176
|
+
getBaseElementMock.mockReturnValue({ id: "view" });
|
|
177
|
+
|
|
178
|
+
const action = await findByRole(container, "button");
|
|
179
|
+
act(() => action.click());
|
|
180
|
+
await act(() => waitFor(() => {}));
|
|
181
|
+
|
|
182
|
+
expect(scrollIntoViewWithOffset).toBeCalledWith(
|
|
183
|
+
expect.anything(),
|
|
184
|
+
expect.objectContaining({ id: "view" }),
|
|
185
|
+
40,
|
|
186
|
+
);
|
|
187
|
+
|
|
188
|
+
// Mock the case where the base element can't be found, so document.body is used as a fallback
|
|
189
|
+
getBaseElementMock.mockReturnValue(null);
|
|
190
|
+
|
|
191
|
+
act(() => action.click());
|
|
192
|
+
await act(() => waitFor(() => {}));
|
|
193
|
+
|
|
194
|
+
expect(scrollIntoViewWithOffset).toHaveBeenLastCalledWith(
|
|
195
|
+
expect.anything(),
|
|
196
|
+
document.body,
|
|
197
|
+
40,
|
|
198
|
+
);
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
test("works without custom base element and offset provided", async () => {
|
|
202
|
+
const getBaseElementMock = vitest.fn();
|
|
203
|
+
|
|
204
|
+
const wp = new ReactPlayer({
|
|
205
|
+
plugins: [
|
|
206
|
+
new AssetTransformPlugin([
|
|
207
|
+
[{ type: "action" }, actionTransform],
|
|
208
|
+
[{ type: "input" }, inputTransform],
|
|
209
|
+
[{ type: "info" }, infoTransform],
|
|
210
|
+
]),
|
|
211
|
+
new CommonTypesPlugin(),
|
|
212
|
+
new AutoScrollManagerPlugin({
|
|
213
|
+
autoFocusOnErrorField: true,
|
|
214
|
+
}),
|
|
215
|
+
],
|
|
216
|
+
});
|
|
217
|
+
wp.assetRegistry.set({ type: "info" }, Info);
|
|
218
|
+
wp.assetRegistry.set({ type: "action" }, Action);
|
|
219
|
+
wp.assetRegistry.set({ type: "input" }, withScrollable(Input));
|
|
220
|
+
|
|
221
|
+
wp.start(flow as any);
|
|
222
|
+
|
|
223
|
+
const { container } = render(
|
|
224
|
+
<div>
|
|
225
|
+
<React.Suspense fallback="loading...">
|
|
226
|
+
<wp.Component />
|
|
227
|
+
</React.Suspense>
|
|
228
|
+
</div>,
|
|
229
|
+
);
|
|
230
|
+
await act(() => waitFor(() => {}));
|
|
231
|
+
|
|
232
|
+
getBaseElementMock.mockReturnValue({ id: "view" });
|
|
233
|
+
|
|
234
|
+
const action = await findByRole(container, "button");
|
|
235
|
+
act(() => action.click());
|
|
236
|
+
await act(() => waitFor(() => {}));
|
|
237
|
+
|
|
238
|
+
expect(scrollIntoViewWithOffset).toBeCalledWith(
|
|
239
|
+
expect.anything(),
|
|
240
|
+
document.body,
|
|
241
|
+
0,
|
|
242
|
+
);
|
|
243
|
+
});
|
|
244
|
+
|
|
245
|
+
test("no error no scroll test", async () => {
|
|
246
|
+
const wp = new ReactPlayer({
|
|
247
|
+
plugins: [
|
|
248
|
+
new AssetTransformPlugin([
|
|
249
|
+
[{ type: "action" }, actionTransform],
|
|
250
|
+
[{ type: "input" }, inputTransform],
|
|
251
|
+
]),
|
|
252
|
+
new CommonTypesPlugin(),
|
|
253
|
+
new AutoScrollManagerPlugin({
|
|
254
|
+
autoFocusOnErrorField: true,
|
|
255
|
+
}),
|
|
256
|
+
],
|
|
257
|
+
});
|
|
258
|
+
wp.assetRegistry.set({ type: "info" }, Info);
|
|
259
|
+
wp.assetRegistry.set({ type: "action" }, Action);
|
|
260
|
+
wp.assetRegistry.set({ type: "input" }, withScrollable(Input));
|
|
261
|
+
|
|
262
|
+
wp.start(flow as any);
|
|
263
|
+
|
|
264
|
+
const { container } = render(
|
|
265
|
+
<div>
|
|
266
|
+
<React.Suspense fallback="loading...">
|
|
267
|
+
<wp.Component />
|
|
268
|
+
</React.Suspense>
|
|
269
|
+
</div>,
|
|
270
|
+
);
|
|
271
|
+
|
|
272
|
+
waitFor(async () => {
|
|
273
|
+
const state = wp.player.getState() as InProgressState;
|
|
274
|
+
state.controllers.data.set([["person.name", "sam"]]);
|
|
275
|
+
const action = await findByRole(container, "button");
|
|
276
|
+
action.click();
|
|
277
|
+
});
|
|
278
|
+
|
|
279
|
+
expect(scrollIntoViewWithOffset).not.toBeCalled();
|
|
280
|
+
});
|
|
281
|
+
});
|
|
282
|
+
|
|
283
|
+
describe("getFirstScrollableElement unit tests", () => {
|
|
284
|
+
const defineGetElementId = (bcrValues: any[]) => {
|
|
285
|
+
return (idIn: string): HTMLElement | null => {
|
|
286
|
+
switch (idIn) {
|
|
287
|
+
case "Element1":
|
|
288
|
+
return {
|
|
289
|
+
getAttribute: (attrIn) =>
|
|
290
|
+
attrIn === "aria-invalid" ? "true" : "false",
|
|
291
|
+
getBoundingClientRect: () => bcrValues[0],
|
|
292
|
+
} as HTMLElement;
|
|
293
|
+
case "Element2":
|
|
294
|
+
return {
|
|
295
|
+
getAttribute: (attrIn) =>
|
|
296
|
+
attrIn === "aria-invalid" ? "true" : "false",
|
|
297
|
+
getBoundingClientRect: () => bcrValues[1],
|
|
298
|
+
} as HTMLElement;
|
|
299
|
+
case "Element3":
|
|
300
|
+
return {
|
|
301
|
+
getAttribute: (attrIn) =>
|
|
302
|
+
attrIn === "aria-invalid" ? "true" : "false",
|
|
303
|
+
getBoundingClientRect: () => bcrValues[2],
|
|
304
|
+
} as HTMLElement;
|
|
305
|
+
default:
|
|
306
|
+
return null;
|
|
307
|
+
}
|
|
308
|
+
};
|
|
309
|
+
};
|
|
310
|
+
|
|
311
|
+
let autoScroll: AutoScrollManagerPlugin;
|
|
312
|
+
let idList: Set<string>;
|
|
313
|
+
|
|
314
|
+
beforeEach(() => {
|
|
315
|
+
autoScroll = new AutoScrollManagerPlugin({});
|
|
316
|
+
idList = new Set<string>();
|
|
317
|
+
idList.add("Element1");
|
|
318
|
+
idList.add("Element2");
|
|
319
|
+
idList.add("Element3");
|
|
320
|
+
});
|
|
321
|
+
|
|
322
|
+
test("scrolled past all elements", () => {
|
|
323
|
+
document.getElementById = defineGetElementId([
|
|
324
|
+
{
|
|
325
|
+
top: -30,
|
|
326
|
+
},
|
|
327
|
+
{
|
|
328
|
+
top: -20,
|
|
329
|
+
},
|
|
330
|
+
{
|
|
331
|
+
top: -10,
|
|
332
|
+
},
|
|
333
|
+
]);
|
|
334
|
+
|
|
335
|
+
expect(
|
|
336
|
+
autoScroll.getFirstScrollableElement(idList, ScrollType.ValidationError),
|
|
337
|
+
).toBe("Element1");
|
|
338
|
+
});
|
|
339
|
+
|
|
340
|
+
test("scrolled past some of elements", () => {
|
|
341
|
+
document.getElementById = defineGetElementId([
|
|
342
|
+
{
|
|
343
|
+
top: -20,
|
|
344
|
+
},
|
|
345
|
+
{
|
|
346
|
+
top: -10,
|
|
347
|
+
},
|
|
348
|
+
{
|
|
349
|
+
top: 0,
|
|
350
|
+
},
|
|
351
|
+
]);
|
|
352
|
+
|
|
353
|
+
expect(
|
|
354
|
+
autoScroll.getFirstScrollableElement(idList, ScrollType.ValidationError),
|
|
355
|
+
).toBe("Element1");
|
|
356
|
+
});
|
|
357
|
+
|
|
358
|
+
test("all elements in view", () => {
|
|
359
|
+
document.getElementById = defineGetElementId([
|
|
360
|
+
{
|
|
361
|
+
top: 10,
|
|
362
|
+
},
|
|
363
|
+
{
|
|
364
|
+
top: 20,
|
|
365
|
+
},
|
|
366
|
+
{
|
|
367
|
+
top: 30,
|
|
368
|
+
},
|
|
369
|
+
]);
|
|
370
|
+
|
|
371
|
+
expect(
|
|
372
|
+
autoScroll.getFirstScrollableElement(idList, ScrollType.ValidationError),
|
|
373
|
+
).toBe("Element1");
|
|
374
|
+
});
|
|
375
|
+
|
|
376
|
+
test("all elements in view and target is 0", () => {
|
|
377
|
+
document.getElementById = defineGetElementId([
|
|
378
|
+
{
|
|
379
|
+
top: 0,
|
|
380
|
+
},
|
|
381
|
+
{
|
|
382
|
+
top: 10,
|
|
383
|
+
},
|
|
384
|
+
{
|
|
385
|
+
top: 20,
|
|
386
|
+
},
|
|
387
|
+
]);
|
|
388
|
+
|
|
389
|
+
expect(
|
|
390
|
+
autoScroll.getFirstScrollableElement(idList, ScrollType.ValidationError),
|
|
391
|
+
).toBe("Element1");
|
|
392
|
+
});
|
|
393
|
+
|
|
394
|
+
test("all elements in view but there is a tie", () => {
|
|
395
|
+
document.getElementById = defineGetElementId([
|
|
396
|
+
{
|
|
397
|
+
top: 10,
|
|
398
|
+
},
|
|
399
|
+
{
|
|
400
|
+
top: 10,
|
|
401
|
+
},
|
|
402
|
+
{
|
|
403
|
+
top: 20,
|
|
404
|
+
},
|
|
405
|
+
]);
|
|
406
|
+
|
|
407
|
+
expect(
|
|
408
|
+
autoScroll.getFirstScrollableElement(idList, ScrollType.ValidationError),
|
|
409
|
+
).toBe("Element1");
|
|
410
|
+
});
|
|
411
|
+
});
|
package/src/hooks.tsx
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
import type { PropsWithChildren } from
|
|
2
|
-
import React, { useEffect, useState } from
|
|
3
|
-
import scrollIntoViewWithOffset from
|
|
4
|
-
import type { ScrollType } from
|
|
1
|
+
import type { PropsWithChildren } from "react";
|
|
2
|
+
import React, { useEffect, useState } from "react";
|
|
3
|
+
import scrollIntoViewWithOffset from "./scrollIntoViewWithOffset";
|
|
4
|
+
import type { ScrollType } from "./index";
|
|
5
5
|
|
|
6
6
|
export interface AutoScrollProviderProps {
|
|
7
7
|
/** Return the element to scroll to based on the registered types */
|
|
8
8
|
getElementToScrollTo: (
|
|
9
|
-
scrollableElements: Map<ScrollType, Set<string
|
|
9
|
+
scrollableElements: Map<ScrollType, Set<string>>,
|
|
10
10
|
) => string;
|
|
11
11
|
/** Optional function to get container element, which is used for calculating offset (default: document.body) */
|
|
12
12
|
getBaseElement: () => HTMLElement | undefined | null;
|
package/src/index.tsx
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export * from
|
|
2
|
-
export * from
|
|
1
|
+
export * from "./hooks";
|
|
2
|
+
export * from "./plugin";
|
package/src/plugin.tsx
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import type { ReactPlayer, ReactPlayerPlugin } from
|
|
2
|
-
import type { Player } from
|
|
3
|
-
import React from
|
|
4
|
-
import { AutoScrollProvider } from
|
|
1
|
+
import type { ReactPlayer, ReactPlayerPlugin } from "@player-ui/react";
|
|
2
|
+
import type { Player } from "@player-ui/react";
|
|
3
|
+
import React from "react";
|
|
4
|
+
import { AutoScrollProvider } from "./hooks";
|
|
5
5
|
|
|
6
6
|
export enum ScrollType {
|
|
7
7
|
ValidationError,
|
|
@@ -22,7 +22,7 @@ export interface AutoScrollManagerConfig {
|
|
|
22
22
|
|
|
23
23
|
/** A plugin to manage scrolling behavior */
|
|
24
24
|
export class AutoScrollManagerPlugin implements ReactPlayerPlugin {
|
|
25
|
-
name =
|
|
25
|
+
name = "auto-scroll-manager";
|
|
26
26
|
|
|
27
27
|
/** Toggles if we should auto scroll to to the first failed validation on page load */
|
|
28
28
|
private autoScrollOnLoad: boolean;
|
|
@@ -45,7 +45,7 @@ export class AutoScrollManagerPlugin implements ReactPlayerPlugin {
|
|
|
45
45
|
/** map of scroll type to set of ids that are registered under that type */
|
|
46
46
|
private alreadyScrolledTo: Array<string>;
|
|
47
47
|
private scrollFn: (
|
|
48
|
-
scrollableElements: Map<ScrollType, Set<string
|
|
48
|
+
scrollableElements: Map<ScrollType, Set<string>>,
|
|
49
49
|
) => string;
|
|
50
50
|
|
|
51
51
|
constructor(config: AutoScrollManagerConfig) {
|
|
@@ -61,7 +61,7 @@ export class AutoScrollManagerPlugin implements ReactPlayerPlugin {
|
|
|
61
61
|
|
|
62
62
|
getFirstScrollableElement(idList: Set<string>, type: ScrollType) {
|
|
63
63
|
const highestElement = {
|
|
64
|
-
id:
|
|
64
|
+
id: "",
|
|
65
65
|
ypos: 0,
|
|
66
66
|
};
|
|
67
67
|
const ypos = window.scrollY;
|
|
@@ -71,7 +71,7 @@ export class AutoScrollManagerPlugin implements ReactPlayerPlugin {
|
|
|
71
71
|
// if we are looking at validation errors, make sure the element is invalid
|
|
72
72
|
if (
|
|
73
73
|
type === ScrollType.ValidationError &&
|
|
74
|
-
element?.getAttribute(
|
|
74
|
+
element?.getAttribute("aria-invalid") === "false"
|
|
75
75
|
) {
|
|
76
76
|
return;
|
|
77
77
|
}
|
|
@@ -89,7 +89,7 @@ export class AutoScrollManagerPlugin implements ReactPlayerPlugin {
|
|
|
89
89
|
const epos = element?.getBoundingClientRect().top;
|
|
90
90
|
if (
|
|
91
91
|
epos !== undefined &&
|
|
92
|
-
(epos + ypos < highestElement.ypos || highestElement.id ===
|
|
92
|
+
(epos + ypos < highestElement.ypos || highestElement.id === "")
|
|
93
93
|
) {
|
|
94
94
|
highestElement.id = id;
|
|
95
95
|
highestElement.ypos = ypos + epos;
|
|
@@ -118,12 +118,12 @@ export class AutoScrollManagerPlugin implements ReactPlayerPlugin {
|
|
|
118
118
|
if (elementList) {
|
|
119
119
|
const element = this.getFirstScrollableElement(
|
|
120
120
|
elementList,
|
|
121
|
-
currentScroll
|
|
121
|
+
currentScroll,
|
|
122
122
|
);
|
|
123
|
-
return element ??
|
|
123
|
+
return element ?? "";
|
|
124
124
|
}
|
|
125
125
|
|
|
126
|
-
return
|
|
126
|
+
return "";
|
|
127
127
|
}
|
|
128
128
|
|
|
129
129
|
// Hooks into player flow to determine what scroll targets need to be evaluated at specific lifecycle points
|
|
@@ -149,8 +149,9 @@ export class AutoScrollManagerPlugin implements ReactPlayerPlugin {
|
|
|
149
149
|
|
|
150
150
|
applyReact(reactPlayer: ReactPlayer) {
|
|
151
151
|
reactPlayer.hooks.webComponent.tap(this.name, (Comp) => {
|
|
152
|
-
|
|
153
|
-
|
|
152
|
+
const { scrollFn, getBaseElement, offset } = this;
|
|
153
|
+
|
|
154
|
+
function AutoScrollManagerComponent() {
|
|
154
155
|
return (
|
|
155
156
|
<AutoScrollProvider
|
|
156
157
|
getElementToScrollTo={scrollFn}
|
|
@@ -160,7 +161,9 @@ export class AutoScrollManagerPlugin implements ReactPlayerPlugin {
|
|
|
160
161
|
<Comp />
|
|
161
162
|
</AutoScrollProvider>
|
|
162
163
|
);
|
|
163
|
-
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
return AutoScrollManagerComponent;
|
|
164
167
|
});
|
|
165
168
|
}
|
|
166
169
|
}
|
|
@@ -5,15 +5,15 @@
|
|
|
5
5
|
-* @param offset Additional offset
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
-
import { scrollTo } from
|
|
8
|
+
import { scrollTo } from "seamless-scroll-polyfill";
|
|
9
9
|
|
|
10
10
|
export default (
|
|
11
11
|
node: HTMLElement,
|
|
12
12
|
baseElement: HTMLElement,
|
|
13
|
-
offset: number
|
|
13
|
+
offset: number,
|
|
14
14
|
) => {
|
|
15
15
|
scrollTo(window, {
|
|
16
|
-
behavior:
|
|
16
|
+
behavior: "smooth",
|
|
17
17
|
top:
|
|
18
18
|
node.getBoundingClientRect().top -
|
|
19
19
|
baseElement.getBoundingClientRect().top -
|
package/types/hooks.d.ts
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { PropsWithChildren } from "react";
|
|
2
|
+
import React from "react";
|
|
3
|
+
import type { ScrollType } from "./index";
|
|
4
|
+
export interface AutoScrollProviderProps {
|
|
5
|
+
/** Return the element to scroll to based on the registered types */
|
|
6
|
+
getElementToScrollTo: (scrollableElements: Map<ScrollType, Set<string>>) => string;
|
|
7
|
+
/** Optional function to get container element, which is used for calculating offset (default: document.body) */
|
|
8
|
+
getBaseElement: () => HTMLElement | undefined | null;
|
|
9
|
+
/** Additional offset to be used (default: 0) */
|
|
10
|
+
offset: number;
|
|
11
|
+
}
|
|
12
|
+
export interface RegisterData {
|
|
13
|
+
/** when to scroll to the target */
|
|
14
|
+
type: ScrollType;
|
|
15
|
+
/** the html id to scroll to */
|
|
16
|
+
ref: string;
|
|
17
|
+
}
|
|
18
|
+
export type ScrollFunction = (registerData: RegisterData) => void;
|
|
19
|
+
export declare const AutoScrollManagerContext: React.Context<{
|
|
20
|
+
/** function to register a scroll target */
|
|
21
|
+
register: ScrollFunction;
|
|
22
|
+
}>;
|
|
23
|
+
/** hook to register as a scroll target */
|
|
24
|
+
export declare const useRegisterAsScrollable: () => ScrollFunction;
|
|
25
|
+
/** Component to handle scrolling */
|
|
26
|
+
export declare const AutoScrollProvider: ({ getElementToScrollTo, getBaseElement, offset, children, }: PropsWithChildren<AutoScrollProviderProps>) => React.JSX.Element;
|
|
27
|
+
//# sourceMappingURL=hooks.d.ts.map
|
package/types/index.d.ts
ADDED