From efbc2ffe6a1ee5ec38a4396305561d6515d0943e Mon Sep 17 00:00:00 2001 From: littleblack111 Date: Fri, 30 Aug 2024 17:21:18 +0800 Subject: node fixes, updates --- node_modules/swup/src/Swup.ts | 371 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 371 insertions(+) create mode 100644 node_modules/swup/src/Swup.ts (limited to 'node_modules/swup/src/Swup.ts') diff --git a/node_modules/swup/src/Swup.ts b/node_modules/swup/src/Swup.ts new file mode 100644 index 00000000..8d5d769e --- /dev/null +++ b/node_modules/swup/src/Swup.ts @@ -0,0 +1,371 @@ +import { type DelegateEvent } from 'delegate-it'; + +import version from './config/version.js'; + +import { delegateEvent, getCurrentUrl, Location, updateHistoryRecord } from './helpers.js'; +import { type DelegateEventUnsubscribe } from './helpers/delegateEvent.js'; + +import { Cache } from './modules/Cache.js'; +import { Classes } from './modules/Classes.js'; +import { type Visit, createVisit } from './modules/Visit.js'; +import { Hooks } from './modules/Hooks.js'; +import { getAnchorElement } from './modules/getAnchorElement.js'; +import { awaitAnimations } from './modules/awaitAnimations.js'; +import { navigate, performNavigation, type NavigationToSelfAction } from './modules/navigate.js'; +import { fetchPage } from './modules/fetchPage.js'; +import { animatePageOut } from './modules/animatePageOut.js'; +import { replaceContent } from './modules/replaceContent.js'; +import { scrollToContent } from './modules/scrollToContent.js'; +import { animatePageIn } from './modules/animatePageIn.js'; +import { renderPage } from './modules/renderPage.js'; +import { use, unuse, findPlugin, type Plugin } from './modules/plugins.js'; +import { isSameResolvedUrl, resolveUrl } from './modules/resolveUrl.js'; +import { nextTick } from './utils.js'; +import { type HistoryState } from './helpers/history.js'; + +/** Options for customizing swup's behavior. */ +export type Options = { + /** Whether history visits are animated. Default: `false` */ + animateHistoryBrowsing: boolean; + /** Selector for detecting animation timing. Default: `[class*="transition-"]` */ + animationSelector: string | false; + /** Elements on which to add animation classes. Default: `html` element */ + animationScope: 'html' | 'containers'; + /** Enable in-memory page cache. Default: `true` */ + cache: boolean; + /** Content containers to be replaced on page visits. Default: `['#swup']` */ + containers: string[]; + /** Callback for ignoring visits. Receives the element and event that triggered the visit. */ + ignoreVisit: (url: string, { el, event }: { el?: Element; event?: Event }) => boolean; + /** Selector for links that trigger visits. Default: `'a[href]'` */ + linkSelector: string; + /** How swup handles links to the same page. Default: `scroll` */ + linkToSelf: NavigationToSelfAction; + /** Enable native animations using the View Transitions API. */ + native: boolean; + /** Plugins to register on startup. */ + plugins: Plugin[]; + /** Custom headers sent along with fetch requests. */ + requestHeaders: Record; + /** Rewrite URLs before loading them. */ + resolveUrl: (url: string) => string; + /** Callback for telling swup to ignore certain popstate events. */ + skipPopStateHandling: (event: PopStateEvent) => boolean; + /** Request timeout in milliseconds. */ + timeout: number; +}; + +const defaults: Options = { + animateHistoryBrowsing: false, + animationSelector: '[class*="transition-"]', + animationScope: 'html', + cache: true, + containers: ['#swup'], + ignoreVisit: (url, { el } = {}) => !!el?.closest('[data-no-swup]'), + linkSelector: 'a[href]', + linkToSelf: 'scroll', + native: false, + plugins: [], + resolveUrl: (url) => url, + requestHeaders: { + 'X-Requested-With': 'swup', + 'Accept': 'text/html, application/xhtml+xml' + }, + skipPopStateHandling: (event) => (event.state as HistoryState)?.source !== 'swup', + timeout: 0 +}; + +/** Swup page transition library. */ +export default class Swup { + /** Library version */ + readonly version: string = version; + /** Options passed into the instance */ + options: Options; + /** Default options before merging user options */ + readonly defaults: Options = defaults; + /** Registered plugin instances */ + plugins: Plugin[] = []; + /** Data about the current visit */ + visit: Visit; + /** Cache instance */ + readonly cache: Cache; + /** Hook registry */ + readonly hooks: Hooks; + /** Animation class manager */ + readonly classes: Classes; + /** Location of the currently visible page */ + location: Location = Location.fromUrl(window.location.href); + /** URL of the currently visible page @deprecated Use swup.location.url instead */ + get currentPageUrl(): string { + return this.location.url; + } + /** Index of the current history entry */ + protected currentHistoryIndex: number; + /** Delegated event subscription handle */ + protected clickDelegate?: DelegateEventUnsubscribe; + /** Navigation status */ + protected navigating: boolean = false; + /** Run anytime a visit ends */ + protected onVisitEnd?: () => Promise; + + /** Install a plugin */ + use = use; + /** Uninstall a plugin */ + unuse = unuse; + /** Find a plugin by name or instance */ + findPlugin = findPlugin; + + /** Log a message. Has no effect unless debug plugin is installed */ + log: (message: string, context?: unknown) => void = () => {}; + + /** Navigate to a new URL */ + navigate = navigate; + /** Actually perform a navigation */ + protected performNavigation = performNavigation; + /** Create a new context for this visit */ + protected createVisit = createVisit; + /** Register a delegated event listener */ + delegateEvent = delegateEvent; + /** Fetch a page from the server */ + fetchPage = fetchPage; + /** Resolve when animations on the page finish */ + awaitAnimations = awaitAnimations; + protected renderPage = renderPage; + /** Replace the content after page load */ + replaceContent = replaceContent; + protected animatePageIn = animatePageIn; + protected animatePageOut = animatePageOut; + protected scrollToContent = scrollToContent; + /** Find the anchor element for a given hash */ + getAnchorElement = getAnchorElement; + + /** Get the current page URL */ + getCurrentUrl = getCurrentUrl; + /** Resolve a URL to its final location */ + resolveUrl = resolveUrl; + /** Check if two URLs resolve to the same location */ + protected isSameResolvedUrl = isSameResolvedUrl; + + constructor(options: Partial = {}) { + // Merge defaults and options + this.options = { ...this.defaults, ...options }; + + this.handleLinkClick = this.handleLinkClick.bind(this); + this.handlePopState = this.handlePopState.bind(this); + + this.cache = new Cache(this); + this.classes = new Classes(this); + this.hooks = new Hooks(this); + this.visit = this.createVisit({ to: '' }); + + this.currentHistoryIndex = (window.history.state as HistoryState)?.index ?? 1; + + this.enable(); + } + + /** Enable this instance, adding listeners and classnames. */ + async enable() { + // Add event listener + const { linkSelector } = this.options; + this.clickDelegate = this.delegateEvent(linkSelector, 'click', this.handleLinkClick); + + window.addEventListener('popstate', this.handlePopState); + + // Set scroll restoration to manual if animating history visits + if (this.options.animateHistoryBrowsing) { + window.history.scrollRestoration = 'manual'; + } + + // Initial save to cache + if (this.options.cache) { + // Disabled to avoid caching modified dom state: logic moved to preload plugin + // https://github.com/swup/swup/issues/475 + } + + // Sanitize/check native option + this.options.native = this.options.native && !!document.startViewTransition; + + // Mount plugins + this.options.plugins.forEach((plugin) => this.use(plugin)); + + // Create initial history record + if ((window.history.state as HistoryState)?.source !== 'swup') { + updateHistoryRecord(null, { index: this.currentHistoryIndex }); + } + + // Give consumers a chance to hook into enable + await nextTick(); + + // Trigger enable hook + await this.hooks.call('enable', undefined, undefined, () => { + const html = document.documentElement; + html.classList.add('swup-enabled'); + html.classList.toggle('swup-native', this.options.native); + }); + } + + /** Disable this instance, removing listeners and classnames. */ + async destroy() { + // remove delegated listener + this.clickDelegate!.destroy(); + + // remove popstate listener + window.removeEventListener('popstate', this.handlePopState); + + // empty cache + this.cache.clear(); + + // unmount plugins + this.options.plugins.forEach((plugin) => this.unuse(plugin)); + + // trigger disable hook + await this.hooks.call('disable', undefined, undefined, () => { + const html = document.documentElement; + html.classList.remove('swup-enabled'); + html.classList.remove('swup-native'); + }); + + // remove handlers + this.hooks.clear(); + } + + /** Determine if a visit should be ignored by swup, based on URL or trigger element. */ + shouldIgnoreVisit(href: string, { el, event }: { el?: Element; event?: Event } = {}) { + const { origin, url, hash } = Location.fromUrl(href); + + // Ignore if the new origin doesn't match the current one + if (origin !== window.location.origin) { + return true; + } + + // Ignore if the link/form would open a new window (or none at all) + if (el && this.triggerWillOpenNewWindow(el)) { + return true; + } + + // Ignore if the visit should be ignored as per user options + if (this.options.ignoreVisit(url + hash, { el, event })) { + return true; + } + + // Finally, allow the visit + return false; + } + + protected handleLinkClick(event: DelegateEvent) { + const el = event.delegateTarget as HTMLAnchorElement; + const { href, url, hash } = Location.fromElement(el); + + // Exit early if the link should be ignored + if (this.shouldIgnoreVisit(href, { el, event })) { + return; + } + + // Ignore if swup is currently navigating towards the link's URL + if (this.navigating && url === this.visit.to.url) { + event.preventDefault(); + return; + } + + const visit = this.createVisit({ to: url, hash, el, event }); + + // Exit early if control key pressed + if (event.metaKey || event.ctrlKey || event.shiftKey || event.altKey) { + this.hooks.callSync('link:newtab', visit, { href }); + return; + } + + // Exit early if other than left mouse button + if (event.button !== 0) { + return; + } + + this.hooks.callSync('link:click', visit, { el, event }, () => { + const from = visit.from.url ?? ''; + + event.preventDefault(); + + // Handle links to the same page + if (!url || url === from) { + if (hash) { + // With hash: scroll to anchor + this.hooks.callSync('link:anchor', visit, { hash }, () => { + updateHistoryRecord(url + hash); + this.scrollToContent(visit); + }); + } else { + // Without hash: scroll to top or load/reload page + this.hooks.callSync('link:self', visit, undefined, () => { + if (this.options.linkToSelf === 'navigate') { + this.performNavigation(visit); + } else { + updateHistoryRecord(url); + this.scrollToContent(visit); + } + }); + } + return; + } + + // Exit early if the resolved path hasn't changed + if (this.isSameResolvedUrl(url, from)) { + return; + } + + // Finally, proceed with loading the page + this.performNavigation(visit); + }); + } + + protected handlePopState(event: PopStateEvent) { + const href: string = (event.state as HistoryState)?.url ?? window.location.href; + + // Exit early if this event should be ignored + if (this.options.skipPopStateHandling(event)) { + return; + } + + // Exit early if the resolved path hasn't changed + if (this.isSameResolvedUrl(getCurrentUrl(), this.location.url)) { + return; + } + + const { url, hash } = Location.fromUrl(href); + + const visit = this.createVisit({ to: url, hash, event }); + + // Mark as history visit + visit.history.popstate = true; + + // Determine direction of history visit + const index = (event.state as HistoryState)?.index ?? 0; + if (index && index !== this.currentHistoryIndex) { + const direction = index - this.currentHistoryIndex > 0 ? 'forwards' : 'backwards'; + visit.history.direction = direction; + this.currentHistoryIndex = index; + } + + // Disable animation & scrolling for history visits + visit.animation.animate = false; + visit.scroll.reset = false; + visit.scroll.target = false; + + // Animated history visit: re-enable animation & scroll reset + if (this.options.animateHistoryBrowsing) { + visit.animation.animate = true; + visit.scroll.reset = true; + } + + this.hooks.callSync('history:popstate', visit, { event }, () => { + this.performNavigation(visit); + }); + } + + /** Determine whether an element will open a new tab when clicking/activating. */ + protected triggerWillOpenNewWindow(triggerEl: Element) { + if (triggerEl.matches('[download], [target="_blank"]')) { + return true; + } + return false; + } +} -- cgit v1.2.3