@docusaurus/plugin-ideal-image 3.7.0-canary-6264 → 3.7.0-canary-6265
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/lib/theme/IdealImageLegacy/components/IdealImage/index.js +1 -2
- package/lib/theme/IdealImageLegacy/components/IdealImage/waypoint.d.ts +10 -0
- package/lib/theme/IdealImageLegacy/components/IdealImage/waypoint.js +174 -0
- package/package.json +8 -9
- package/src/theme/IdealImageLegacy/components/IdealImage/index.js +1 -2
- package/src/theme/IdealImageLegacy/components/IdealImage/waypoint.tsx +239 -0
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import React, {Component} from 'react';
|
|
2
|
-
|
|
3
|
-
import {Waypoint} from 'react-waypoint';
|
|
2
|
+
import {Waypoint} from './waypoint';
|
|
4
3
|
import Media from '../Media';
|
|
5
4
|
import {icons, loadStates} from '../constants';
|
|
6
5
|
import {xhrLoader, imageLoader, timeout, combineCancel} from '../loaders';
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import React, { ReactNode } from 'react';
|
|
2
|
+
type Props = {
|
|
3
|
+
topOffset: number;
|
|
4
|
+
bottomOffset: number;
|
|
5
|
+
onEnter: () => void;
|
|
6
|
+
onLeave: () => void;
|
|
7
|
+
children: ReactNode;
|
|
8
|
+
};
|
|
9
|
+
export declare function Waypoint(props: Props): string | number | boolean | Iterable<React.ReactNode> | React.JSX.Element | null | undefined;
|
|
10
|
+
export {};
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
/*
|
|
2
|
+
This is a slimmed down copy of https://github.com/civiccc/react-waypoint
|
|
3
|
+
The MIT License (MIT)
|
|
4
|
+
Copyright (c) 2015 Brigade
|
|
5
|
+
*/
|
|
6
|
+
import React, {createRef} from 'react';
|
|
7
|
+
function addEventListener(element, type, listener, options) {
|
|
8
|
+
element.addEventListener(type, listener, options);
|
|
9
|
+
return () => element.removeEventListener(type, listener, options);
|
|
10
|
+
}
|
|
11
|
+
export function Waypoint(props) {
|
|
12
|
+
return typeof window !== 'undefined' ? (
|
|
13
|
+
<WaypointClient {...props}>{props.children}</WaypointClient>
|
|
14
|
+
) : (
|
|
15
|
+
props.children
|
|
16
|
+
);
|
|
17
|
+
}
|
|
18
|
+
// TODO maybe replace this with IntersectionObserver later?
|
|
19
|
+
// IntersectionObserver doesn't support the "fast scroll" thing
|
|
20
|
+
// but it's probably not a big deal
|
|
21
|
+
class WaypointClient extends React.Component {
|
|
22
|
+
static defaultProps = {
|
|
23
|
+
topOffset: 0,
|
|
24
|
+
bottomOffset: 0,
|
|
25
|
+
onEnter() {},
|
|
26
|
+
onLeave() {},
|
|
27
|
+
};
|
|
28
|
+
scrollableAncestor;
|
|
29
|
+
previousPosition = null;
|
|
30
|
+
unsubscribe;
|
|
31
|
+
innerRef = createRef();
|
|
32
|
+
componentDidMount() {
|
|
33
|
+
this.scrollableAncestor = findScrollableAncestor(this.innerRef.current);
|
|
34
|
+
const unsubscribeScroll = addEventListener(
|
|
35
|
+
this.scrollableAncestor,
|
|
36
|
+
'scroll',
|
|
37
|
+
this._handleScroll,
|
|
38
|
+
{passive: true},
|
|
39
|
+
);
|
|
40
|
+
const unsubscribeResize = addEventListener(
|
|
41
|
+
window,
|
|
42
|
+
'resize',
|
|
43
|
+
this._handleScroll,
|
|
44
|
+
{passive: true},
|
|
45
|
+
);
|
|
46
|
+
this.unsubscribe = () => {
|
|
47
|
+
unsubscribeScroll();
|
|
48
|
+
unsubscribeResize();
|
|
49
|
+
};
|
|
50
|
+
this._handleScroll();
|
|
51
|
+
}
|
|
52
|
+
componentDidUpdate() {
|
|
53
|
+
this._handleScroll();
|
|
54
|
+
}
|
|
55
|
+
componentWillUnmount() {
|
|
56
|
+
this.unsubscribe?.();
|
|
57
|
+
}
|
|
58
|
+
_handleScroll = () => {
|
|
59
|
+
const node = this.innerRef.current;
|
|
60
|
+
const {topOffset, bottomOffset, onEnter, onLeave} = this.props;
|
|
61
|
+
const bounds = getBounds({
|
|
62
|
+
node: node,
|
|
63
|
+
scrollableAncestor: this.scrollableAncestor,
|
|
64
|
+
topOffset,
|
|
65
|
+
bottomOffset,
|
|
66
|
+
});
|
|
67
|
+
const currentPosition = getCurrentPosition(bounds);
|
|
68
|
+
const previousPosition = this.previousPosition;
|
|
69
|
+
this.previousPosition = currentPosition;
|
|
70
|
+
if (previousPosition === currentPosition) {
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
if (currentPosition === 'inside') {
|
|
74
|
+
onEnter();
|
|
75
|
+
} else if (previousPosition === 'inside') {
|
|
76
|
+
onLeave();
|
|
77
|
+
}
|
|
78
|
+
const isRapidScrollDown =
|
|
79
|
+
previousPosition === 'below' && currentPosition === 'above';
|
|
80
|
+
const isRapidScrollUp =
|
|
81
|
+
previousPosition === 'above' && currentPosition === 'below';
|
|
82
|
+
if (isRapidScrollDown || isRapidScrollUp) {
|
|
83
|
+
onEnter();
|
|
84
|
+
onLeave();
|
|
85
|
+
}
|
|
86
|
+
};
|
|
87
|
+
render() {
|
|
88
|
+
// @ts-expect-error: fix this implicit API
|
|
89
|
+
return React.cloneElement(this.props.children, {innerRef: this.innerRef});
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Traverses up the DOM to find an ancestor container which has an overflow
|
|
94
|
+
* style that allows for scrolling.
|
|
95
|
+
*
|
|
96
|
+
* @return {Object} the closest ancestor element with an overflow style that
|
|
97
|
+
* allows for scrolling. If none is found, the `window` object is returned
|
|
98
|
+
* as a fallback.
|
|
99
|
+
*/
|
|
100
|
+
function findScrollableAncestor(inputNode) {
|
|
101
|
+
let node = inputNode;
|
|
102
|
+
while (node.parentNode) {
|
|
103
|
+
// @ts-expect-error: it's fine
|
|
104
|
+
node = node.parentNode;
|
|
105
|
+
if (node === document.body) {
|
|
106
|
+
// We've reached all the way to the root node.
|
|
107
|
+
return window;
|
|
108
|
+
}
|
|
109
|
+
const style = window.getComputedStyle(node);
|
|
110
|
+
const overflow =
|
|
111
|
+
style.getPropertyValue('overflow-y') ||
|
|
112
|
+
style.getPropertyValue('overflow');
|
|
113
|
+
if (
|
|
114
|
+
overflow === 'auto' ||
|
|
115
|
+
overflow === 'scroll' ||
|
|
116
|
+
overflow === 'overlay'
|
|
117
|
+
) {
|
|
118
|
+
return node;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
// A scrollable ancestor element was not found, which means that we need to
|
|
122
|
+
// do stuff on window.
|
|
123
|
+
return window;
|
|
124
|
+
}
|
|
125
|
+
function getBounds({node, scrollableAncestor, topOffset, bottomOffset}) {
|
|
126
|
+
const {top, bottom} = node.getBoundingClientRect();
|
|
127
|
+
let contextHeight;
|
|
128
|
+
let contextScrollTop;
|
|
129
|
+
if (scrollableAncestor === window) {
|
|
130
|
+
contextHeight = window.innerHeight;
|
|
131
|
+
contextScrollTop = 0;
|
|
132
|
+
} else {
|
|
133
|
+
const ancestorElement = scrollableAncestor;
|
|
134
|
+
contextHeight = ancestorElement.offsetHeight;
|
|
135
|
+
contextScrollTop = ancestorElement.getBoundingClientRect().top;
|
|
136
|
+
}
|
|
137
|
+
const contextBottom = contextScrollTop + contextHeight;
|
|
138
|
+
return {
|
|
139
|
+
top,
|
|
140
|
+
bottom,
|
|
141
|
+
viewportTop: contextScrollTop + topOffset,
|
|
142
|
+
viewportBottom: contextBottom - bottomOffset,
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
function getCurrentPosition(bounds) {
|
|
146
|
+
if (bounds.viewportBottom - bounds.viewportTop === 0) {
|
|
147
|
+
return 'invisible';
|
|
148
|
+
}
|
|
149
|
+
// top is within the viewport
|
|
150
|
+
if (bounds.viewportTop <= bounds.top && bounds.top <= bounds.viewportBottom) {
|
|
151
|
+
return 'inside';
|
|
152
|
+
}
|
|
153
|
+
// bottom is within the viewport
|
|
154
|
+
if (
|
|
155
|
+
bounds.viewportTop <= bounds.bottom &&
|
|
156
|
+
bounds.bottom <= bounds.viewportBottom
|
|
157
|
+
) {
|
|
158
|
+
return 'inside';
|
|
159
|
+
}
|
|
160
|
+
// top is above the viewport and bottom is below the viewport
|
|
161
|
+
if (
|
|
162
|
+
bounds.top <= bounds.viewportTop &&
|
|
163
|
+
bounds.viewportBottom <= bounds.bottom
|
|
164
|
+
) {
|
|
165
|
+
return 'inside';
|
|
166
|
+
}
|
|
167
|
+
if (bounds.viewportBottom < bounds.top) {
|
|
168
|
+
return 'below';
|
|
169
|
+
}
|
|
170
|
+
if (bounds.top < bounds.viewportTop) {
|
|
171
|
+
return 'above';
|
|
172
|
+
}
|
|
173
|
+
return 'invisible';
|
|
174
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@docusaurus/plugin-ideal-image",
|
|
3
|
-
"version": "3.7.0-canary-
|
|
3
|
+
"version": "3.7.0-canary-6265",
|
|
4
4
|
"description": "Docusaurus Plugin to generate an almost ideal image (responsive, lazy-loading, and low quality placeholder).",
|
|
5
5
|
"main": "lib/index.js",
|
|
6
6
|
"types": "src/plugin-ideal-image.d.ts",
|
|
@@ -20,19 +20,18 @@
|
|
|
20
20
|
},
|
|
21
21
|
"license": "MIT",
|
|
22
22
|
"dependencies": {
|
|
23
|
-
"@docusaurus/core": "3.7.0-canary-
|
|
24
|
-
"@docusaurus/lqip-loader": "3.7.0-canary-
|
|
23
|
+
"@docusaurus/core": "3.7.0-canary-6265",
|
|
24
|
+
"@docusaurus/lqip-loader": "3.7.0-canary-6265",
|
|
25
25
|
"@docusaurus/responsive-loader": "^1.7.0",
|
|
26
|
-
"@docusaurus/theme-translations": "3.7.0-canary-
|
|
27
|
-
"@docusaurus/types": "3.7.0-canary-
|
|
28
|
-
"@docusaurus/utils-validation": "3.7.0-canary-
|
|
29
|
-
"react-waypoint": "^10.3.0",
|
|
26
|
+
"@docusaurus/theme-translations": "3.7.0-canary-6265",
|
|
27
|
+
"@docusaurus/types": "3.7.0-canary-6265",
|
|
28
|
+
"@docusaurus/utils-validation": "3.7.0-canary-6265",
|
|
30
29
|
"sharp": "^0.32.3",
|
|
31
30
|
"tslib": "^2.6.0",
|
|
32
31
|
"webpack": "^5.88.1"
|
|
33
32
|
},
|
|
34
33
|
"devDependencies": {
|
|
35
|
-
"@docusaurus/module-type-aliases": "3.7.0-canary-
|
|
34
|
+
"@docusaurus/module-type-aliases": "3.7.0-canary-6265",
|
|
36
35
|
"fs-extra": "^11.1.0"
|
|
37
36
|
},
|
|
38
37
|
"peerDependencies": {
|
|
@@ -48,5 +47,5 @@
|
|
|
48
47
|
"engines": {
|
|
49
48
|
"node": ">=18.0"
|
|
50
49
|
},
|
|
51
|
-
"gitHead": "
|
|
50
|
+
"gitHead": "fca9417fbb5dc82196c1528c0a9341fdd5af770e"
|
|
52
51
|
}
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import React, {Component} from 'react';
|
|
2
|
-
|
|
3
|
-
import {Waypoint} from 'react-waypoint';
|
|
2
|
+
import {Waypoint} from './waypoint';
|
|
4
3
|
import Media from '../Media';
|
|
5
4
|
import {icons, loadStates} from '../constants';
|
|
6
5
|
import {xhrLoader, imageLoader, timeout, combineCancel} from '../loaders';
|
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
/*
|
|
2
|
+
This is a slimmed down copy of https://github.com/civiccc/react-waypoint
|
|
3
|
+
The MIT License (MIT)
|
|
4
|
+
Copyright (c) 2015 Brigade
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import React, {createRef, ReactNode} from 'react';
|
|
8
|
+
|
|
9
|
+
type ScrollContainer = Window | HTMLElement;
|
|
10
|
+
|
|
11
|
+
function addEventListener(
|
|
12
|
+
element: ScrollContainer,
|
|
13
|
+
type: 'scroll' | 'resize',
|
|
14
|
+
listener: () => void,
|
|
15
|
+
options: AddEventListenerOptions,
|
|
16
|
+
) {
|
|
17
|
+
element.addEventListener(type, listener, options);
|
|
18
|
+
return () => element.removeEventListener(type, listener, options);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
type Position = 'above' | 'inside' | 'below' | 'invisible';
|
|
22
|
+
|
|
23
|
+
type Props = {
|
|
24
|
+
topOffset: number;
|
|
25
|
+
bottomOffset: number;
|
|
26
|
+
onEnter: () => void;
|
|
27
|
+
onLeave: () => void;
|
|
28
|
+
children: ReactNode;
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
export function Waypoint(props: Props) {
|
|
32
|
+
return typeof window !== 'undefined' ? (
|
|
33
|
+
<WaypointClient {...props}>{props.children}</WaypointClient>
|
|
34
|
+
) : (
|
|
35
|
+
props.children
|
|
36
|
+
);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// TODO maybe replace this with IntersectionObserver later?
|
|
40
|
+
// IntersectionObserver doesn't support the "fast scroll" thing
|
|
41
|
+
// but it's probably not a big deal
|
|
42
|
+
class WaypointClient extends React.Component<Props> {
|
|
43
|
+
static defaultProps = {
|
|
44
|
+
topOffset: 0,
|
|
45
|
+
bottomOffset: 0,
|
|
46
|
+
onEnter() {},
|
|
47
|
+
onLeave() {},
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
scrollableAncestor?: ScrollContainer;
|
|
51
|
+
previousPosition: Position | null = null;
|
|
52
|
+
unsubscribe?: () => void;
|
|
53
|
+
|
|
54
|
+
innerRef = createRef<HTMLElement>();
|
|
55
|
+
|
|
56
|
+
override componentDidMount() {
|
|
57
|
+
this.scrollableAncestor = findScrollableAncestor(this.innerRef.current!);
|
|
58
|
+
|
|
59
|
+
const unsubscribeScroll = addEventListener(
|
|
60
|
+
this.scrollableAncestor!,
|
|
61
|
+
'scroll',
|
|
62
|
+
this._handleScroll,
|
|
63
|
+
{passive: true},
|
|
64
|
+
);
|
|
65
|
+
|
|
66
|
+
const unsubscribeResize = addEventListener(
|
|
67
|
+
window,
|
|
68
|
+
'resize',
|
|
69
|
+
this._handleScroll,
|
|
70
|
+
{passive: true},
|
|
71
|
+
);
|
|
72
|
+
|
|
73
|
+
this.unsubscribe = () => {
|
|
74
|
+
unsubscribeScroll();
|
|
75
|
+
unsubscribeResize();
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
this._handleScroll();
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
override componentDidUpdate() {
|
|
82
|
+
this._handleScroll();
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
override componentWillUnmount() {
|
|
86
|
+
this.unsubscribe?.();
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
_handleScroll = () => {
|
|
90
|
+
const node = this.innerRef.current;
|
|
91
|
+
const {topOffset, bottomOffset, onEnter, onLeave} = this.props;
|
|
92
|
+
|
|
93
|
+
const bounds = getBounds({
|
|
94
|
+
node: node!,
|
|
95
|
+
scrollableAncestor: this.scrollableAncestor!,
|
|
96
|
+
topOffset,
|
|
97
|
+
bottomOffset,
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
const currentPosition = getCurrentPosition(bounds);
|
|
101
|
+
const previousPosition = this.previousPosition;
|
|
102
|
+
this.previousPosition = currentPosition;
|
|
103
|
+
|
|
104
|
+
if (previousPosition === currentPosition) {
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
if (currentPosition === 'inside') {
|
|
109
|
+
onEnter();
|
|
110
|
+
} else if (previousPosition === 'inside') {
|
|
111
|
+
onLeave();
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
const isRapidScrollDown =
|
|
115
|
+
previousPosition === 'below' && currentPosition === 'above';
|
|
116
|
+
const isRapidScrollUp =
|
|
117
|
+
previousPosition === 'above' && currentPosition === 'below';
|
|
118
|
+
if (isRapidScrollDown || isRapidScrollUp) {
|
|
119
|
+
onEnter();
|
|
120
|
+
onLeave();
|
|
121
|
+
}
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
override render() {
|
|
125
|
+
// @ts-expect-error: fix this implicit API
|
|
126
|
+
return React.cloneElement(this.props.children, {innerRef: this.innerRef});
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Traverses up the DOM to find an ancestor container which has an overflow
|
|
132
|
+
* style that allows for scrolling.
|
|
133
|
+
*
|
|
134
|
+
* @return {Object} the closest ancestor element with an overflow style that
|
|
135
|
+
* allows for scrolling. If none is found, the `window` object is returned
|
|
136
|
+
* as a fallback.
|
|
137
|
+
*/
|
|
138
|
+
function findScrollableAncestor(inputNode: HTMLElement): ScrollContainer {
|
|
139
|
+
let node: HTMLElement = inputNode;
|
|
140
|
+
|
|
141
|
+
while (node.parentNode) {
|
|
142
|
+
// @ts-expect-error: it's fine
|
|
143
|
+
node = node.parentNode!;
|
|
144
|
+
|
|
145
|
+
if (node === document.body) {
|
|
146
|
+
// We've reached all the way to the root node.
|
|
147
|
+
return window;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
const style = window.getComputedStyle(node);
|
|
151
|
+
const overflow =
|
|
152
|
+
style.getPropertyValue('overflow-y') ||
|
|
153
|
+
style.getPropertyValue('overflow');
|
|
154
|
+
|
|
155
|
+
if (
|
|
156
|
+
overflow === 'auto' ||
|
|
157
|
+
overflow === 'scroll' ||
|
|
158
|
+
overflow === 'overlay'
|
|
159
|
+
) {
|
|
160
|
+
return node;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// A scrollable ancestor element was not found, which means that we need to
|
|
165
|
+
// do stuff on window.
|
|
166
|
+
return window;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
type Bounds = {
|
|
170
|
+
top: number;
|
|
171
|
+
bottom: number;
|
|
172
|
+
viewportTop: number;
|
|
173
|
+
viewportBottom: number;
|
|
174
|
+
};
|
|
175
|
+
|
|
176
|
+
function getBounds({
|
|
177
|
+
node,
|
|
178
|
+
scrollableAncestor,
|
|
179
|
+
topOffset,
|
|
180
|
+
bottomOffset,
|
|
181
|
+
}: {
|
|
182
|
+
node: Element;
|
|
183
|
+
scrollableAncestor: ScrollContainer;
|
|
184
|
+
topOffset: number;
|
|
185
|
+
bottomOffset: number;
|
|
186
|
+
}): Bounds {
|
|
187
|
+
const {top, bottom} = node.getBoundingClientRect();
|
|
188
|
+
|
|
189
|
+
let contextHeight;
|
|
190
|
+
let contextScrollTop;
|
|
191
|
+
if (scrollableAncestor === window) {
|
|
192
|
+
contextHeight = window.innerHeight;
|
|
193
|
+
contextScrollTop = 0;
|
|
194
|
+
} else {
|
|
195
|
+
const ancestorElement = scrollableAncestor as HTMLElement;
|
|
196
|
+
contextHeight = ancestorElement.offsetHeight;
|
|
197
|
+
contextScrollTop = ancestorElement.getBoundingClientRect().top;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
const contextBottom = contextScrollTop + contextHeight;
|
|
201
|
+
|
|
202
|
+
return {
|
|
203
|
+
top,
|
|
204
|
+
bottom,
|
|
205
|
+
viewportTop: contextScrollTop + topOffset,
|
|
206
|
+
viewportBottom: contextBottom - bottomOffset,
|
|
207
|
+
};
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
function getCurrentPosition(bounds: Bounds): Position {
|
|
211
|
+
if (bounds.viewportBottom - bounds.viewportTop === 0) {
|
|
212
|
+
return 'invisible';
|
|
213
|
+
}
|
|
214
|
+
// top is within the viewport
|
|
215
|
+
if (bounds.viewportTop <= bounds.top && bounds.top <= bounds.viewportBottom) {
|
|
216
|
+
return 'inside';
|
|
217
|
+
}
|
|
218
|
+
// bottom is within the viewport
|
|
219
|
+
if (
|
|
220
|
+
bounds.viewportTop <= bounds.bottom &&
|
|
221
|
+
bounds.bottom <= bounds.viewportBottom
|
|
222
|
+
) {
|
|
223
|
+
return 'inside';
|
|
224
|
+
}
|
|
225
|
+
// top is above the viewport and bottom is below the viewport
|
|
226
|
+
if (
|
|
227
|
+
bounds.top <= bounds.viewportTop &&
|
|
228
|
+
bounds.viewportBottom <= bounds.bottom
|
|
229
|
+
) {
|
|
230
|
+
return 'inside';
|
|
231
|
+
}
|
|
232
|
+
if (bounds.viewportBottom < bounds.top) {
|
|
233
|
+
return 'below';
|
|
234
|
+
}
|
|
235
|
+
if (bounds.top < bounds.viewportTop) {
|
|
236
|
+
return 'above';
|
|
237
|
+
}
|
|
238
|
+
return 'invisible';
|
|
239
|
+
}
|