Skip to content

Instantly share code, notes, and snippets.

@wojtekmaj
Last active April 26, 2021 10:32
Show Gist options
  • Save wojtekmaj/b9a4144592f59567380de81d154553b1 to your computer and use it in GitHub Desktop.
Save wojtekmaj/b9a4144592f59567380de81d154553b1 to your computer and use it in GitHub Desktop.
Shit I need to put up with to support Safari
function Button({ buttonLabel, onClick }) {
return (
<button
/**
* Apparently iOS Safari sometimes "forgets" to update button innerHTML. So to cope with
* that, we're forcing iOS Safari to re-render the button completely on label change. Why
* all this hassle with UA? Removing a button and rendering it again causes it to lose
* focus.
*/
key={buttonLabel}
onClick={onClick}
type="button"
>
{buttonLabel}
</button>
);
}
const invalidOnChangeKey = '__RECENT_SELECT_NAME__';
/**
* Apparently iOS Safari dispatches an extra onChange when the user jumps between inputs on the
* input the user jumped to, causing the app to select nth value in BOTH inputs! So to cope with
* that, we're creating a temporary variable that prevents other select elements from being
* changed within 100ms from the original change.
*/
function checkOnChangeValidity(event) {
return !event || !window[invalidOnChangeKey] || window[invalidOnChangeKey] === event.target.name;
}
function temporarilySetFlag(event) {
window[invalidOnChangeKey] = event.target;
setTimeout(() => {
delete window[invalidOnChangeKey];
}, 100);
}
function onChange(event) {
// …
temporarilySetFlag(event);
// …
}
function focusWithAnimatedScroll(element) {
const currentScrollY = window.scrollY;
const elementTopEdge = element.offsetTop - element.parentElement.offsetTop;
element.focus({ preventScroll: true });
// Setting focus current step scrolls the page on browsers not supporting preventScroll: true
// (*ahem* Safari), so we scroll back down to saved value…
window.scrollTo({ top: currentScrollY });
setImmediate(() => {
// Because Safari tends to delay focus for some reason, we correct the scroll position
// once again and only then we start animation
window.scrollTo({ top: currentScrollY });
// …and from there, we animate scroll to top.
window.scrollTo({
top: elementTopEdge,
behavior: reduceMotion ? 'auto' : 'smooth',
});
});
}
/**
* Apparently iOS Safari cannot reliably trigger onChange event on <select> elements. So to
* cope with that, from the moment the input is focused up until the moment it's blurred, we're
* monitoring its value, and if it changed but store value did not, we're manually triggering
* onChange event.
*
* Read more: https://stackoverflow.com/questions/8004227/ios-select-onchange-not-firing
*/
const checkForMissedOnChange = useCallback(() => {
if (!select.current) {
return;
}
const nextValue = select.current.value;
if (!nextValue && !value) {
return;
}
const originalValue = findOriginalValue(options, nextValue);
if (originalValue !== value) {
// eslint-disable-next-line no-console
console.warn('Browser failed to dispatch onChange event. Dispatching onChange manually.');
// Dispatch onChange manually
const changeEvent = createEvent('change');
select.current.dispatchEvent(changeEvent);
}
}, [options, select, value]);
useEffect(() => {
if (!isFocused) {
return;
}
const interval = setInterval(checkForMissedOnChange, 100);
return () => {
clearInterval(interval);
};
}, [checkForMissedOnChange, isFocused]);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment