UNPKG

@qwik.dev/core

Version:

An open source framework for building instant loading web apps at any scale, without the extra effort.

453 lines (452 loc) 14.8 kB
const doc = document; const win = window; const windowPrefix = "w"; const passiveWindowPrefix = "wp"; const documentPrefix = "d"; const passiveDocumentPrefix = "dp"; const elementPrefix = "e"; const passiveElementPrefix = "ep"; const capturePrefix = "capture:"; const readyStateChange = "readystatechange"; const containerReady = "qready"; const events = /* @__PURE__ */ new Set(); const roots = /* @__PURE__ */ new Set([doc]); const symbols = /* @__PURE__ */ new Map(); let observer; let hasInitialized; let queuedTasks; const nativeQuerySelectorAll = (root, selector) => Array.from(root.querySelectorAll(selector)); const querySelectorAll = (query) => { const elements = []; roots.forEach((root) => elements.push(...nativeQuerySelectorAll(root, query))); return elements; }; const addEventListener = (el, eventName, handler, capture = false, passive = false) => el.addEventListener(eventName, handler, { capture, passive }); const findShadowRoots = (fragment) => { addEventOrRoot(fragment); const shadowRoots = nativeQuerySelectorAll(fragment, "[q\\:shadowroot]"); for (let i = 0; i < shadowRoots.length; i++) { const parent = shadowRoots[i]; const shadowRoot = parent.shadowRoot; shadowRoot && findShadowRoots(shadowRoot); } }; const isPromise = (promise) => promise && typeof promise.then === "function"; const runTasks = async (tasks) => { for (let i = 0; i < tasks.length; i++) { await tasks[i](); } }; const queueTasks = (tasks) => { if (tasks.length) { const run = () => runTasks(tasks); queuedTasks = queuedTasks ? queuedTasks.then(run, run) : run(); } }; const resolveContainer = (containerEl) => { if (containerEl._qwikjson_ === void 0) { const parentJSON = containerEl === doc.documentElement ? doc.body : containerEl; let script = parentJSON.lastElementChild; while (script) { if (script.tagName === "SCRIPT" && script.getAttribute("type") === "qwik/json") { containerEl._qwikjson_ = JSON.parse( script.textContent.replace(/\\x3C(\/?script)/gi, "<$1") ); break; } script = script.previousElementSibling; } } }; const waitForContainerReady = (container) => { const hash = container.getAttribute("q:instance"); return container.getAttribute("q:container") === "paused" && doc.readyState === "loading" && !doc[containerReady]?.[hash] && new Promise((resolve) => { const ready = (ev) => { if (ev.detail === hash) { doc.removeEventListener(containerReady, ready); resolve(); } }; addEventListener(doc, readyStateChange, resolve); addEventListener(doc, containerReady, ready); }); }; const createEvent = (eventName, detail) => new CustomEvent(eventName, { detail }); const emitEvent = (eventName, detail) => { doc.dispatchEvent(createEvent(eventName, detail)); }; const camelToKebab = (str) => str.replace(/([A-Z-])/g, (a) => "-" + a.toLowerCase()); const kebabToCamel = (eventName) => eventName.replace(/-./g, (a) => a[1].toUpperCase()); const parseKebabEvent = (event) => { const separatorIndex = event.indexOf(":"); const scope = event.slice(0, separatorIndex); return { scope, eventName: kebabToCamel(event.slice(separatorIndex + 1)) }; }; const isPassiveScope = (scope) => scope.length === 2; const getRootScope = (scope) => scope.charAt(0); const isElementNode = (node) => !!node && node.nodeType === 1; const isCaptureHandlerElement = (element, scopedKebabName, captureAttribute) => element.hasAttribute(captureAttribute) && (!!element._qDispatch?.[scopedKebabName] || element.hasAttribute("q-" + scopedKebabName)); const resolveHandler = (container, element, qBase, base, chunk, symbol, reqTime, reportSyncError = true) => { const eventData = { qBase, symbol, element, reqTime }; if (!chunk) { const handler2 = (doc["qFuncs_" + container.getAttribute("q:instance")] || [])[+symbol]; if (!handler2 && reportSyncError) { const error = new Error("sym:" + symbol); emitEvent("qerror", { importError: "sync", error, ...eventData }); console.error(error); } return handler2; } const key = `${symbol}|${qBase}|${chunk}`; const handler = symbols.get(key); if (handler) { return handler; } const href = new URL(chunk, base).href; const module = import( /* @vite-ignore */ href ); resolveContainer(container); return module.then( (module2) => { const handler2 = module2[symbol]; if (!handler2) { const error = new Error(`${symbol} not in ${href}`); emitEvent("qerror", { importError: "no-symbol", error, ...eventData }); console.error(error); } else { symbols.set(key, handler2); emitEvent("qsymbol", eventData); } return handler2; }, (error) => { emitEvent("qerror", { importError: "async", error, ...eventData }); console.error(error); return void 0; } ); }; const dispatch = (element, ev, scopedKebabName, tasks, kebabName, allowPreventDefault = true) => { let defer = false; if (kebabName) { if (allowPreventDefault && element.hasAttribute("preventdefault:" + kebabName)) { ev.preventDefault(); } if (element.hasAttribute("stoppropagation:" + kebabName)) { ev.stopPropagation(); } } const handlers = element._qDispatch?.[scopedKebabName]; if (handlers) { if (typeof handlers === "function") { const run = () => handlers(ev, element); if (defer) { tasks.push(async () => { const result = run(); if (isPromise(result)) { await result; } }); } else { const result = run(); if (isPromise(result)) { defer = true; tasks.push(() => result); } } } else if (handlers.length) { for (let i = 0; i < handlers.length; i++) { const handler = handlers[i]; if (handler) { const run = () => handler(ev, element); if (defer) { tasks.push(async () => { const result = run(); if (isPromise(result)) { await result; } }); } else { const result = run(); if (isPromise(result)) { defer = true; tasks.push(() => result); } } } } } return; } const attrValue = element.getAttribute("q-" + scopedKebabName); if (attrValue) { const container = element.closest( "[q\\:container]:not([q\\:container=html]):not([q\\:container=text])" ); const qBase = container.getAttribute("q:base"); const base = new URL(qBase, doc.baseURI); const qrls = attrValue.split("|"); const waitForReady = waitForContainerReady(container); for (let i = 0; i < qrls.length; i++) { const qrl = qrls[i]; const reqTime = performance.now(); const [chunk, symbol, capturedIds] = qrl.split("#"); const run = (handler2) => { if (handler2 && element.isConnected) { const onError = (error) => { const retry = waitForContainerReady(container); if (retry) { return retry.then(() => run(handler2)); } emitEvent("qerror", { error, qBase, symbol, element, reqTime }); }; try { const result = handler2.call(capturedIds, ev, element); if (isPromise(result)) { return result.catch(onError); } } catch (error) { return onError(error); } } }; const resolve = (reportSyncError = true) => resolveHandler(container, element, qBase, base, chunk, symbol, reqTime, reportSyncError); const handler = waitForReady && !chunk ? resolve(false) : resolve(); if (isPromise(handler)) { defer = true; tasks.push(() => handler.then(run)); } else if (defer || waitForReady && !chunk && !handler) { defer = true; tasks.push(async () => { let retryHandler = handler; if (!retryHandler && waitForReady) { await waitForReady; retryHandler = resolve(false); } await run(retryHandler || await resolve()); }); } else { const result = run(handler); if (isPromise(result)) { defer = true; tasks.push(() => result); } } } } }; const processElementEvent = (ev, scope = elementPrefix, allowPreventDefault = true) => { const kebabName = camelToKebab(ev.type); const scopedKebabName = scope + ":" + kebabName; const captureAttribute = capturePrefix + kebabName; const elements = []; const captureHandlers = []; const tasks = []; let current = ev.target; while (current) { if (isElementNode(current)) { elements.push(current); captureHandlers.push(isCaptureHandlerElement(current, scopedKebabName, captureAttribute)); current = current.parentElement; } else { current = current.parentElement; } } for (let i = elements.length - 1; i >= 0; i--) { if (captureHandlers[i]) { dispatch(elements[i], ev, scopedKebabName, tasks, kebabName, allowPreventDefault); if (ev.cancelBubble) { queueTasks(tasks); return; } } } for (let i = 0; i < elements.length; i++) { if (!captureHandlers[i]) { dispatch(elements[i], ev, scopedKebabName, tasks, kebabName, allowPreventDefault); if (!ev.bubbles || ev.cancelBubble) { queueTasks(tasks); return; } } } queueTasks(tasks); }; const processPassiveElementEvent = (ev) => processElementEvent(ev, passiveElementPrefix, false); const broadcast = (scope, ev, allowPreventDefault = true) => { const kebabName = camelToKebab(ev.type); const scopedKebabName = scope + ":" + kebabName; const elements = querySelectorAll("[q-" + scope + "\\:" + kebabName + "]"); const tasks = []; for (let i = 0; i < elements.length; i++) { const el = elements[i]; dispatch(el, ev, scopedKebabName, tasks, kebabName, allowPreventDefault); } queueTasks(tasks); }; const processDocumentEvent = (ev) => { broadcast(documentPrefix, ev); }; const processPassiveDocumentEvent = (ev) => { broadcast(passiveDocumentPrefix, ev, false); }; const processWindowEvent = (ev) => { broadcast(windowPrefix, ev); }; const processPassiveWindowEvent = (ev) => { broadcast(passiveWindowPrefix, ev, false); }; const processReadyStateChange = () => { const readyState = doc.readyState; if (readyState == "interactive" || readyState == "complete") { hasInitialized = 1; roots.forEach(findShadowRoots); if (events.has("d:qinit")) { events.delete("d:qinit"); const ev = createEvent("qinit"); const elements = querySelectorAll("[q-d\\:qinit]"); const tasks = []; for (let i = 0; i < elements.length; i++) { const el = elements[i]; dispatch(el, ev, "d:qinit", tasks); el.removeAttribute("q-d:qinit"); } queueTasks(tasks); } if (events.has("d:qidle")) { events.delete("d:qidle"); const riC = win.requestIdleCallback ?? win.setTimeout; riC.bind(win)(() => { const ev = createEvent("qidle"); const elements = querySelectorAll("[q-d\\:qidle]"); const tasks = []; for (let i = 0; i < elements.length; i++) { const el = elements[i]; dispatch(el, ev, "d:qidle", tasks); el.removeAttribute("q-d:qidle"); } queueTasks(tasks); }); } if (events.has("e:qvisible")) { observer || (observer = new IntersectionObserver((entries) => { const tasks = []; for (let i = 0; i < entries.length; i++) { const entry = entries[i]; if (entry.isIntersecting) { observer.unobserve(entry.target); dispatch( entry.target, createEvent("qvisible", entry), "e:qvisible", tasks ); } } queueTasks(tasks); })); const elements = querySelectorAll("[q-e\\:qvisible]:not([q\\:observed])"); for (let i = 0; i < elements.length; i++) { const el = elements[i]; observer.observe(el); el.setAttribute("q:observed", "true"); } } } }; const addEventOrRoot = (...eventNames) => { for (let i = 0; i < eventNames.length; i++) { const eventNameOrRoot = eventNames[i]; if (typeof eventNameOrRoot === "string") { if (!events.has(eventNameOrRoot)) { events.add(eventNameOrRoot); const { scope, eventName } = parseKebabEvent(eventNameOrRoot); const passive = isPassiveScope(scope); const rootScope = getRootScope(scope); if (rootScope === windowPrefix) { addEventListener( win, eventName, passive ? processPassiveWindowEvent : processWindowEvent, true, passive ); } else { roots.forEach( (root) => addEventListener( root, eventName, rootScope === documentPrefix ? passive ? processPassiveDocumentEvent : processDocumentEvent : passive ? processPassiveElementEvent : processElementEvent, true, passive ) ); } if (hasInitialized === 1 && (eventNameOrRoot === "e:qvisible" || eventNameOrRoot === "d:qinit" || eventNameOrRoot === "d:qidle")) { processReadyStateChange(); } } } else { if (!roots.has(eventNameOrRoot)) { events.forEach((kebabEventName) => { const { scope, eventName } = parseKebabEvent(kebabEventName); const passive = isPassiveScope(scope); const rootScope = getRootScope(scope); if (rootScope !== windowPrefix) { addEventListener( eventNameOrRoot, eventName, rootScope === documentPrefix ? passive ? processPassiveDocumentEvent : processDocumentEvent : passive ? processPassiveElementEvent : processElementEvent, true, passive ); } }); roots.add(eventNameOrRoot); } } } }; const _qwikEv = win._qwikEv; if (!_qwikEv?.roots) { if (Array.isArray(_qwikEv)) { addEventOrRoot(..._qwikEv); } else { addEventOrRoot("e:click", "e:input"); } win._qwikEv = { events, roots, push: addEventOrRoot }; addEventListener(doc, readyStateChange, processReadyStateChange); processReadyStateChange(); }