mirror of
https://github.com/shoelace-style/shoelace.git
synced 2026-01-12 02:59:13 +00:00
fix ssr for sl-alert and scrollend-polyfill (#2359)
* Fix: Make Alert SSR compatible * Fix: Make sure polyfill is only called on the frontend * Add changelog entries * Removed debug statement * Changelog adjustments * Another console.log statement :( --------- Co-authored-by: Cory LaViska <cory@abeautifulsite.net>
This commit is contained in:
committed by
GitHub
parent
b0399ca74e
commit
372ba1f66d
@@ -16,6 +16,8 @@ New versions of Shoelace are released as-needed and generally occur when a criti
|
||||
|
||||
- Improved performance of `<sl-select>` when using a large number of options [#2318]
|
||||
- Updated the Japanese translation [#2329]
|
||||
- Adjust `<sl-alert>` to create the toast stack when used only, making it usable in SSR environments. [#2359]
|
||||
- Adjust `scrollend-polyfill` so it only runs on the client to make it usable in SSR environments. [#2359]
|
||||
- Fixed a bug with radios in `<sl-dialog>` focus trapping.
|
||||
|
||||
## 2.19.1
|
||||
|
||||
@@ -13,8 +13,6 @@ import SlIconButton from '../icon-button/icon-button.component.js';
|
||||
import styles from './alert.styles.js';
|
||||
import type { CSSResultGroup } from 'lit';
|
||||
|
||||
const toastStack = Object.assign(document.createElement('div'), { className: 'sl-toast-stack' });
|
||||
|
||||
/**
|
||||
* @summary Alerts are used to display important messages inline or as toast notifications.
|
||||
* @documentation https://shoelace.style/components/alert
|
||||
@@ -50,6 +48,17 @@ export default class SlAlert extends ShoelaceElement {
|
||||
private readonly hasSlotController = new HasSlotController(this, 'icon', 'suffix');
|
||||
private readonly localize = new LocalizeController(this);
|
||||
|
||||
private static currentToastStack: HTMLDivElement;
|
||||
|
||||
private static get toastStack() {
|
||||
if (!this.currentToastStack) {
|
||||
this.currentToastStack = Object.assign(document.createElement('div'), {
|
||||
className: 'sl-toast-stack'
|
||||
});
|
||||
}
|
||||
return this.currentToastStack;
|
||||
}
|
||||
|
||||
@query('[part~="base"]') base: HTMLElement;
|
||||
|
||||
@query('.alert__countdown-elapsed') countdownElement: HTMLElement;
|
||||
@@ -195,11 +204,11 @@ export default class SlAlert extends ShoelaceElement {
|
||||
async toast() {
|
||||
return new Promise<void>(resolve => {
|
||||
this.handleCountdownChange();
|
||||
if (toastStack.parentElement === null) {
|
||||
document.body.append(toastStack);
|
||||
if (SlAlert.toastStack.parentElement === null) {
|
||||
document.body.append(SlAlert.toastStack);
|
||||
}
|
||||
|
||||
toastStack.appendChild(this);
|
||||
SlAlert.toastStack.appendChild(this);
|
||||
|
||||
// Wait for the toast stack to render
|
||||
requestAnimationFrame(() => {
|
||||
@@ -211,12 +220,12 @@ export default class SlAlert extends ShoelaceElement {
|
||||
this.addEventListener(
|
||||
'sl-after-hide',
|
||||
() => {
|
||||
toastStack.removeChild(this);
|
||||
SlAlert.toastStack.removeChild(this);
|
||||
resolve();
|
||||
|
||||
// Remove the toast stack from the DOM when there are no more alerts
|
||||
if (toastStack.querySelector('sl-alert') === null) {
|
||||
toastStack.remove();
|
||||
if (SlAlert.toastStack.querySelector('sl-alert') === null) {
|
||||
SlAlert.toastStack.remove();
|
||||
}
|
||||
},
|
||||
{ once: true }
|
||||
|
||||
@@ -26,54 +26,61 @@ const decorate = <T, M extends keyof T>(
|
||||
} as MethodOf<T, M>;
|
||||
};
|
||||
|
||||
const isSupported = 'onscrollend' in window;
|
||||
(() => {
|
||||
// SSR environments should not apply the polyfill
|
||||
if (typeof window === 'undefined') {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!isSupported) {
|
||||
const pointers = new Set();
|
||||
const scrollHandlers = new WeakMap<EventTarget, EventListenerOrEventListenerObject>();
|
||||
const isSupported = 'onscrollend' in window;
|
||||
|
||||
const handlePointerDown = (event: TouchEvent) => {
|
||||
for (const touch of event.changedTouches) {
|
||||
pointers.add(touch.identifier);
|
||||
}
|
||||
};
|
||||
if (!isSupported) {
|
||||
const pointers = new Set();
|
||||
const scrollHandlers = new WeakMap<EventTarget, EventListenerOrEventListenerObject>();
|
||||
|
||||
const handlePointerUp = (event: TouchEvent) => {
|
||||
for (const touch of event.changedTouches) {
|
||||
pointers.delete(touch.identifier);
|
||||
}
|
||||
};
|
||||
|
||||
document.addEventListener('touchstart', handlePointerDown, true);
|
||||
document.addEventListener('touchend', handlePointerUp, true);
|
||||
document.addEventListener('touchcancel', handlePointerUp, true);
|
||||
|
||||
decorate(EventTarget.prototype, 'addEventListener', function (this: EventTarget, addEventListener, type) {
|
||||
if (type !== 'scrollend') return;
|
||||
|
||||
const handleScrollEnd = debounce(() => {
|
||||
if (!pointers.size) {
|
||||
// If no pointer is active in the scroll area then the scroll has ended
|
||||
this.dispatchEvent(new Event('scrollend'));
|
||||
} else {
|
||||
// otherwise let's wait a bit more
|
||||
handleScrollEnd();
|
||||
const handlePointerDown = (event: TouchEvent) => {
|
||||
for (const touch of event.changedTouches) {
|
||||
pointers.add(touch.identifier);
|
||||
}
|
||||
}, 100);
|
||||
};
|
||||
|
||||
addEventListener.call(this, 'scroll', handleScrollEnd, { passive: true });
|
||||
scrollHandlers.set(this, handleScrollEnd);
|
||||
});
|
||||
const handlePointerUp = (event: TouchEvent) => {
|
||||
for (const touch of event.changedTouches) {
|
||||
pointers.delete(touch.identifier);
|
||||
}
|
||||
};
|
||||
|
||||
decorate(EventTarget.prototype, 'removeEventListener', function (this: EventTarget, removeEventListener, type) {
|
||||
if (type !== 'scrollend') return;
|
||||
document.addEventListener('touchstart', handlePointerDown, true);
|
||||
document.addEventListener('touchend', handlePointerUp, true);
|
||||
document.addEventListener('touchcancel', handlePointerUp, true);
|
||||
|
||||
const scrollHandler = scrollHandlers.get(this);
|
||||
if (scrollHandler) {
|
||||
removeEventListener.call(this, 'scroll', scrollHandler, { passive: true } as unknown as EventListenerOptions);
|
||||
}
|
||||
});
|
||||
}
|
||||
decorate(EventTarget.prototype, 'addEventListener', function (this: EventTarget, addEventListener, type) {
|
||||
if (type !== 'scrollend') return;
|
||||
|
||||
const handleScrollEnd = debounce(() => {
|
||||
if (!pointers.size) {
|
||||
// If no pointer is active in the scroll area then the scroll has ended
|
||||
this.dispatchEvent(new Event('scrollend'));
|
||||
} else {
|
||||
// otherwise let's wait a bit more
|
||||
handleScrollEnd();
|
||||
}
|
||||
}, 100);
|
||||
|
||||
addEventListener.call(this, 'scroll', handleScrollEnd, { passive: true });
|
||||
scrollHandlers.set(this, handleScrollEnd);
|
||||
});
|
||||
|
||||
decorate(EventTarget.prototype, 'removeEventListener', function (this: EventTarget, removeEventListener, type) {
|
||||
if (type !== 'scrollend') return;
|
||||
|
||||
const scrollHandler = scrollHandlers.get(this);
|
||||
if (scrollHandler) {
|
||||
removeEventListener.call(this, 'scroll', scrollHandler, { passive: true } as unknown as EventListenerOptions);
|
||||
}
|
||||
});
|
||||
}
|
||||
})();
|
||||
|
||||
// Without an import or export, TypeScript sees vars in this file as global
|
||||
export {};
|
||||
|
||||
Reference in New Issue
Block a user