Skip to content

Commit 34c6a27

Browse files
authored
Fix button should hide when TAB to focus (#46)
1 parent f19b14d commit 34c6a27

File tree

3 files changed

+113
-60
lines changed

3 files changed

+113
-60
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
3535

3636
### Fixed
3737
- Fix `atStart` was not reporting correctly, in PR [#31](https://github.com/compulim/react-scroll-to-bottom/pull/31)
38+
- Chrome: Fix scroll to bottom button should hide when using <kbd>TAB</kbd> to scroll the bottommost button into view, in PR [#46](https://github.com/compulim/react-scroll-to-bottom/pull/46)
3839

3940
## [1.3.2] - 2019-06-20
4041
### Changed

packages/component/src/ScrollToBottom/Composer.js

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -207,6 +207,35 @@ const Composer = ({ checkInterval, children, debounce, mode }) => {
207207
[scrollTo, scrollToBottom, scrollToEnd, scrollToStart, scrollToTop]
208208
);
209209

210+
useEffect(() => {
211+
// We need to update the "scrollHeight" value to latest when the user do a focus inside the box.
212+
//
213+
// This is because:
214+
// - In our code that mitigate Chrome synthetic scrolling, that code will look at whether "scrollHeight" value is latest or not.
215+
// - That code only run on "scroll" event.
216+
// - That means, on every "scroll" event, if the "scrollHeight" value is not latest, we will skip modifying the stickiness.
217+
// - That means, if the user "focus" to an element that cause the scroll view to scroll to the bottom, the user agent will fire "scroll" event.
218+
// Since the "scrollHeight" is not latest value, this "scroll" event will be ignored and stickiness will not be modified.
219+
// - That means, if the user "focus" to a newly added element that is at the end of the scroll view, the "scroll to bottom" button will continue to show.
220+
//
221+
// Repro in Chrome:
222+
// 1. Fill up a scroll view
223+
// 2. Scroll up, the "scroll to bottom" button should show up
224+
// 3. Click "Add a button"
225+
// 4. Click on the scroll view (to pseudo-focus on it)
226+
// 5. Press TAB, the scroll view will be at the bottom
227+
//
228+
// Expect:
229+
// - The "scroll to bottom" button should be gone.
230+
if (target) {
231+
const handleFocus = () => setScrollHeight(target.scrollHeight);
232+
233+
target.addEventListener('focus', handleFocus, { capture: true, passive: true });
234+
235+
return () => target.removeEventListener('focus', handleFocus);
236+
}
237+
}, [target]);
238+
210239
return (
211240
<InternalContext.Provider value={internalContext}>
212241
<FunctionContext.Provider value={functionContext}>

packages/playground/src/App.js

Lines changed: 83 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import ScrollToEnd, { StateContext } from 'react-scroll-to-bottom';
88
import CommandBar from './CommandBar';
99

1010
const FADE_IN_ANIMATION = css.keyframes({
11-
'0%': { opacity: .2 },
11+
'0%': { opacity: 0.2 },
1212
'100%': { opacity: 1 }
1313
});
1414

@@ -62,7 +62,7 @@ const SCROLL_VIEW_PADDING_CSS = css({
6262
},
6363

6464
'& > p': {
65-
animation: `${ FADE_IN_ANIMATION } 500ms`
65+
animation: `${FADE_IN_ANIMATION} 500ms`
6666
}
6767
});
6868

@@ -78,44 +78,69 @@ const App = () => {
7878
const [paragraphs, setParagraphs] = useState(createParagraphs(10));
7979
const [commandBarVisible, setCommandBarVisible] = useState(false);
8080

81-
const handleAdd = useCallback(count => setParagraphs([...paragraphs, ...createParagraphs(count)]), [paragraphs, setParagraphs]);
81+
const handleAdd = useCallback(count => setParagraphs([...paragraphs, ...createParagraphs(count)]), [
82+
paragraphs,
83+
setParagraphs
84+
]);
8285
const handleAdd1 = useCallback(() => handleAdd(1), [handleAdd]);
8386
const handleAdd10 = useCallback(() => handleAdd(10), [handleAdd]);
87+
const handleAddButton = useCallback(
88+
() => setParagraphs([...paragraphs, 'Button: ' + loremIpsum({ units: 'words' })]),
89+
[paragraphs, setParagraphs]
90+
);
8491
const handleClear = useCallback(() => setParagraphs([]), [setParagraphs]);
85-
const handleCommandBarVisibleChange = useCallback(({ target: { checked } }) => setCommandBarVisible(checked), [setCommandBarVisible]);
92+
const handleCommandBarVisibleChange = useCallback(({ target: { checked } }) => setCommandBarVisible(checked), [
93+
setCommandBarVisible
94+
]);
8695
const handleContainerSizeLarge = useCallback(() => setContainerSize('large'), [setContainerSize]);
8796
const handleContainerSizeNormal = useCallback(() => setContainerSize(''), [setContainerSize]);
8897
const handleContainerSizeSmall = useCallback(() => setContainerSize('small'), [setContainerSize]);
89-
const handleIntervalEnabledChange = useCallback(({ target: { checked: intervalEnabled } }) => setIntervalEnabled(intervalEnabled), []);
90-
const containerClassName = useMemo(() => classNames(
91-
CONTAINER_CSS + '',
92-
containerSize === 'small' ?
93-
SMALL_CONTAINER_CSS + ''
94-
: containerSize === 'large' ?
95-
LARGE_CONTAINER_CSS + ''
96-
:
97-
''
98-
), [containerSize]);
99-
100-
const handleKeyDown = useCallback(({ keyCode }) => {
101-
switch (keyCode) {
102-
case 49: return handleAdd1();
103-
case 50: return handleAdd10();
104-
case 51: return handleClear();
105-
case 52: return handleContainerSizeSmall();
106-
case 53: return handleContainerSizeNormal();
107-
case 54: return handleContainerSizeLarge();
108-
case 82: return window.location.reload(); // Press R key
109-
default: break;
110-
}
111-
}, [
112-
handleAdd1,
113-
handleAdd10,
114-
handleClear,
115-
handleContainerSizeLarge,
116-
handleContainerSizeNormal,
117-
handleContainerSizeSmall
118-
]);
98+
const handleIntervalEnabledChange = useCallback(
99+
({ target: { checked: intervalEnabled } }) => setIntervalEnabled(intervalEnabled),
100+
[]
101+
);
102+
const containerClassName = useMemo(
103+
() =>
104+
classNames(
105+
CONTAINER_CSS + '',
106+
containerSize === 'small' ? SMALL_CONTAINER_CSS + '' : containerSize === 'large' ? LARGE_CONTAINER_CSS + '' : ''
107+
),
108+
[containerSize]
109+
);
110+
111+
const handleKeyDown = useCallback(
112+
({ keyCode }) => {
113+
switch (keyCode) {
114+
case 49:
115+
return handleAdd1();
116+
case 50:
117+
return handleAdd10();
118+
case 51:
119+
return handleClear();
120+
case 52:
121+
return handleContainerSizeSmall();
122+
case 53:
123+
return handleContainerSizeNormal();
124+
case 54:
125+
return handleContainerSizeLarge();
126+
case 55:
127+
return handleAddButton();
128+
case 82:
129+
return window.location.reload(); // Press R key
130+
default:
131+
break;
132+
}
133+
},
134+
[
135+
handleAdd1,
136+
handleAdd10,
137+
handleAddButton,
138+
handleClear,
139+
handleContainerSizeLarge,
140+
handleContainerSizeNormal,
141+
handleContainerSizeSmall
142+
]
143+
);
119144

120145
useEffect(() => {
121146
window.addEventListener('keydown', handleKeyDown);
@@ -124,7 +149,7 @@ const App = () => {
124149
}, [handleKeyDown]);
125150

126151
return (
127-
<div className={ROOT_CSS + ""}>
152+
<div className={ROOT_CSS + ''}>
128153
<ul className="button-bar">
129154
<li>
130155
<button onClick={handleAdd1}>Add new paragraph</button>
@@ -144,40 +169,36 @@ const App = () => {
144169
<li>
145170
<button onClick={handleContainerSizeLarge}>Large</button>
146171
</li>
172+
<li>
173+
<button onClick={handleAddButton}>Add a button</button>
174+
</li>
147175
<li>
148176
<label>
149-
<input
150-
checked={intervalEnabled}
151-
onChange={handleIntervalEnabledChange}
152-
type="checkbox"
153-
/>
177+
<input checked={intervalEnabled} onChange={handleIntervalEnabledChange} type="checkbox" />
154178
Add one every second
155179
</label>
156180
</li>
157181
<li>
158182
<label>
159-
<input
160-
checked={commandBarVisible}
161-
onChange={handleCommandBarVisibleChange}
162-
type="checkbox"
163-
/>
183+
<input checked={commandBarVisible} onChange={handleCommandBarVisibleChange} type="checkbox" />
164184
Show command bar
165185
</label>
166186
</li>
167187
</ul>
168188
<div className="panes">
169-
<ScrollToEnd
170-
className={containerClassName}
171-
scrollViewClassName={SCROLL_VIEW_CSS + ""}
172-
>
189+
<ScrollToEnd className={containerClassName} scrollViewClassName={SCROLL_VIEW_CSS + ''}>
173190
{commandBarVisible && <CommandBar />}
174191
<StateContext.Consumer>
175192
{({ sticky }) => (
176-
<div
177-
className={classNames(SCROLL_VIEW_PADDING_CSS + "", { sticky })}
178-
>
193+
<div className={classNames(SCROLL_VIEW_PADDING_CSS + '', { sticky })}>
179194
{paragraphs.map(paragraph => (
180-
<p key={paragraph}>{paragraph}</p>
195+
<p key={paragraph}>
196+
{paragraph.startsWith('Button: ') ? (
197+
<button type="button">{paragraph.substr(8)}</button>
198+
) : (
199+
paragraph
200+
)}
201+
</p>
181202
))}
182203
</div>
183204
)}
@@ -188,21 +209,23 @@ const App = () => {
188209
{commandBarVisible && <CommandBar />}
189210
<StateContext.Consumer>
190211
{({ sticky }) => (
191-
<div
192-
className={classNames(SCROLL_VIEW_PADDING_CSS + "", { sticky })}
193-
>
212+
<div className={classNames(SCROLL_VIEW_PADDING_CSS + '', { sticky })}>
194213
{[...paragraphs].reverse().map(paragraph => (
195-
<p key={paragraph}>{paragraph}</p>
214+
<p key={paragraph}>
215+
{paragraph.startsWith('Button: ') ? (
216+
<button type="button">{paragraph.substr(8)}</button>
217+
) : (
218+
paragraph
219+
)}
220+
</p>
196221
))}
197222
</div>
198223
)}
199224
</StateContext.Consumer>
200225
{commandBarVisible && <CommandBar />}
201226
</ScrollToEnd>
202227
</div>
203-
{intervalEnabled && (
204-
<Interval callback={handleAdd1} enabled={true} timeout={1000} />
205-
)}
228+
{intervalEnabled && <Interval callback={handleAdd1} enabled={true} timeout={1000} />}
206229
</div>
207230
);
208231
};

0 commit comments

Comments
 (0)