UNPKG

ember-source

Version:

A JavaScript framework for creating ambitious web applications

469 lines (455 loc) 17.1 kB
import '../../shared-chunks/capabilities-DHiXCCuB.js'; import '../../shared-chunks/debug-to-string-BsFOvUtQ.js'; import '@embroider/macros'; import '../global-context/index.js'; import '../validator/index.js'; import '../../shared-chunks/reference-B6HMX4y0.js'; import { C as ConcreteBounds, q as COMMENT_NODE, d as CursorImpl, T as TEXT_NODE, r as ELEMENT_NODE, N as NS_SVG } from '../../shared-chunks/dynamic-CuBsUXX8.js'; export { s as CurriedValue, D as DOMChanges, u as DynamicAttribute, v as EMPTY_ARGS, w as EMPTY_NAMED, x as EMPTY_POSITIONAL, m as IDOMChanges, S as SimpleDynamicAttribute, y as TEMPLATE_ONLY_COMPONENT_MANAGER, z as TemplateOnlyComponent, F as TemplateOnlyComponentManager, a as array, j as clear, c as concat, G as createCapturedArgs, H as curry, e as dynamicAttribute, f as fn, g as get, h as hash, i as invokeHelper, I as isWhitespace, K as normalizeProperty, o as on, L as reifyArgs, M as reifyNamed, O as reifyPositional, P as resetDebuggerCallback, Q as setDebuggerCallback, t as templateOnlyComponent } from '../../shared-chunks/dynamic-CuBsUXX8.js'; export { D as DynamicScopeImpl, E as EnvironmentImpl, L as LowLevelVM, S as ScopeImpl, U as UpdatingVM, i as inTransaction, r as renderComponent, a as renderMain, b as renderSync, c as runtimeOptions } from '../../shared-chunks/render-DTOhhssy.js'; export { destroy, isDestroyed, isDestroying, registerDestructor } from '../destroyable/index.js'; import { N as NewTreeBuilder, R as RemoteBlock } from '../../shared-chunks/element-builder-BuVym8EM.js'; export { D as DOMTreeConstruction, a as ResettableBlockImpl, c as clientBuilder } from '../../shared-chunks/element-builder-BuVym8EM.js'; import { a as assert } from '../../shared-chunks/assert-CUCJBR2C.js'; import { e as expect } from '../../shared-chunks/collections-B8me-ZlQ.js'; import { a as castToSimple, c as castToBrowser } from '../../shared-chunks/simple-cast-BXTrayoV.js'; const SERIALIZATION_FIRST_NODE_STRING = '%+b:0%'; function isSerializationFirstNode(node) { return node.nodeValue === SERIALIZATION_FIRST_NODE_STRING; } class RehydratingCursor extends CursorImpl { candidate = null; openBlockDepth; injectedOmittedNode = false; constructor(element, nextSibling, startingBlockDepth) { super(element, nextSibling); this.startingBlockDepth = startingBlockDepth; this.openBlockDepth = startingBlockDepth - 1; } } class RehydrateTree extends NewTreeBuilder { unmatchedAttributes = null; // Hides property on base class blockDepth = 0; startingBlockOffset; constructor(env, parentNode, nextSibling) { super(env, parentNode, nextSibling); if (nextSibling) throw new Error('Rehydration with nextSibling not supported'); // eslint-disable-next-line @typescript-eslint/no-non-null-assertion -- @fixme let node = this.currentCursor.element.firstChild; while (node !== null) { if (isOpenBlock(node)) { break; } node = node.nextSibling; } this.candidate = node; const startingBlockOffset = getBlockDepth(node); if (startingBlockOffset !== 0) { // We are rehydrating from a partial tree and not the root component // We need to add an extra block before the first block to rehydrate correctly // The extra block is needed since the renderComponent API creates a synthetic component invocation which generates the extra block const newBlockDepth = startingBlockOffset - 1; const newCandidate = this.dom.createComment(`%+b:${newBlockDepth}%`); // eslint-disable-next-line @typescript-eslint/no-non-null-assertion -- @fixme node.parentNode.insertBefore(newCandidate, this.candidate); let closingNode = node.nextSibling; while (closingNode !== null) { if (isCloseBlock(closingNode) && getBlockDepth(closingNode) === startingBlockOffset) { break; } closingNode = closingNode.nextSibling; } const newClosingBlock = this.dom.createComment(`%-b:${newBlockDepth}%`); // eslint-disable-next-line @typescript-eslint/no-non-null-assertion -- @fixme node.parentNode.insertBefore(newClosingBlock, closingNode.nextSibling); this.candidate = newCandidate; this.startingBlockOffset = newBlockDepth; } else { this.startingBlockOffset = 0; } } get currentCursor() { return this.cursors.current; } get candidate() { if (this.currentCursor) { // eslint-disable-next-line @typescript-eslint/no-non-null-assertion -- @fixme return this.currentCursor.candidate; } return null; } set candidate(node) { // eslint-disable-next-line @typescript-eslint/no-non-null-assertion -- @fixme const currentCursor = this.currentCursor; currentCursor.candidate = node; } disableRehydration(nextSibling) { // eslint-disable-next-line @typescript-eslint/no-non-null-assertion -- @fixme const currentCursor = this.currentCursor; // rehydration will be disabled until we either: // * hit popElement (and return to using the parent elements cursor) // * hit closeBlock and the next sibling is a close block comment // matching the expected openBlockDepth currentCursor.candidate = null; currentCursor.nextSibling = nextSibling; } enableRehydration(candidate) { // eslint-disable-next-line @typescript-eslint/no-non-null-assertion -- @fixme const currentCursor = this.currentCursor; currentCursor.candidate = candidate; currentCursor.nextSibling = null; } pushElement(element, nextSibling = null) { const cursor = new RehydratingCursor(element, nextSibling, this.blockDepth || 0); /** * <div> <--------------- currentCursor.element * <!--%+b:1%--> <------- would have been removed during openBlock * <div> <--------------- currentCursor.candidate -> cursor.element * <!--%+b:2%--> <----- currentCursor.candidate.firstChild -> cursor.candidate * Foo * <!--%-b:2%--> * </div> * <!--%-b:1%--> <------ becomes currentCursor.candidate */ if (this.candidate !== null) { cursor.candidate = element.firstChild; this.candidate = element.nextSibling; } this.cursors.push(cursor); } // clears until the end of the current container // either the current open block or higher clearMismatch(candidate) { let current = candidate; const currentCursor = this.currentCursor; if (currentCursor !== null) { const openBlockDepth = currentCursor.openBlockDepth; if (openBlockDepth >= currentCursor.startingBlockDepth) { while (current) { if (isCloseBlock(current)) { const closeBlockDepth = getBlockDepthWithOffset(current, this.startingBlockOffset); if (openBlockDepth >= closeBlockDepth) { break; } } current = this.remove(current); } } else { while (current !== null) { current = this.remove(current); } } // current cursor parentNode should be openCandidate if element // or openCandidate.parentNode if comment this.disableRehydration(current); } } __openBlock() { const { currentCursor } = this; if (currentCursor === null) return; const blockDepth = this.blockDepth; this.blockDepth++; const { candidate } = currentCursor; if (candidate === null) return; const { tagName } = currentCursor.element; if (isOpenBlock(candidate) && getBlockDepthWithOffset(candidate, this.startingBlockOffset) === blockDepth) { this.candidate = this.remove(candidate); currentCursor.openBlockDepth = blockDepth; } else if (tagName !== 'TITLE' && tagName !== 'SCRIPT' && tagName !== 'STYLE') { this.clearMismatch(candidate); } } __closeBlock() { const { currentCursor } = this; if (currentCursor === null) return; // openBlock is the last rehydrated open block const openBlockDepth = currentCursor.openBlockDepth; // this currently is the expected next open block depth this.blockDepth--; const { candidate } = currentCursor; let isRehydrating = false; if (candidate !== null) { isRehydrating = true; //assert( // openBlockDepth === this.blockDepth, // 'when rehydrating, openBlockDepth should match this.blockDepth here' //); if (isCloseBlock(candidate) && getBlockDepthWithOffset(candidate, this.startingBlockOffset) === openBlockDepth) { const nextSibling = this.remove(candidate); this.candidate = nextSibling; currentCursor.openBlockDepth--; } else { // close the block and clear mismatch in parent container // we will be either at the end of the element // or at the end of our containing block this.clearMismatch(candidate); isRehydrating = false; } } if (!isRehydrating) { // check if nextSibling matches our expected close block // if so, we remove the close block comment and // restore rehydration after clearMismatch disabled const nextSibling = currentCursor.nextSibling; if (nextSibling !== null && isCloseBlock(nextSibling) && getBlockDepthWithOffset(nextSibling, this.startingBlockOffset) === this.blockDepth) { // restore rehydration state const candidate = this.remove(nextSibling); this.enableRehydration(candidate); currentCursor.openBlockDepth--; } } } __appendNode(node) { const { candidate } = this; // This code path is only used when inserting precisely one node. It needs more // comparison logic, but we can probably lean on the cases where this code path // is actually used. if (candidate) { return candidate; } else { return super.__appendNode(node); } } __appendHTML(html) { const candidateBounds = this.markerBounds(); if (candidateBounds) { const first = candidateBounds.firstNode(); const last = candidateBounds.lastNode(); // eslint-disable-next-line @typescript-eslint/no-non-null-assertion -- @fixme const newBounds = new ConcreteBounds(this.element, first.nextSibling, last.previousSibling); const possibleEmptyMarker = this.remove(first); this.remove(last); if (possibleEmptyMarker !== null && isEmpty(possibleEmptyMarker)) { this.candidate = this.remove(possibleEmptyMarker); if (this.candidate !== null) { this.clearMismatch(this.candidate); } } return newBounds; } else { return super.__appendHTML(html); } } remove(node) { const element = expect(node.parentNode); const next = node.nextSibling; element.removeChild(node); return next; } markerBounds() { const _candidate = this.candidate; if (_candidate && isMarker(_candidate)) { const first = _candidate; let last = expect(first.nextSibling); while (!isMarker(last)) { last = expect(last.nextSibling); } return new ConcreteBounds(this.element, first, last); } else { return null; } } __appendText(string) { const { candidate } = this; if (candidate) { if (isTextNode(candidate)) { if (candidate.nodeValue !== string) { candidate.nodeValue = string; } this.candidate = candidate.nextSibling; return candidate; } else if (isSeparator(candidate)) { this.candidate = this.remove(candidate); return this.__appendText(string); } else if (isEmpty(candidate) && string === '') { this.candidate = this.remove(candidate); return this.__appendText(string); } else { this.clearMismatch(candidate); return super.__appendText(string); } } else { return super.__appendText(string); } } __appendComment(string) { const _candidate = this.candidate; if (_candidate && isComment(_candidate)) { if (_candidate.nodeValue !== string) { _candidate.nodeValue = string; } this.candidate = _candidate.nextSibling; return _candidate; } else if (_candidate) { this.clearMismatch(_candidate); } return super.__appendComment(string); } __openElement(tag) { const _candidate = this.candidate; if (_candidate && isElement(_candidate) && isSameNodeType(_candidate, tag)) { this.unmatchedAttributes = [].slice.call(_candidate.attributes); return _candidate; } else if (_candidate) { if (isElement(_candidate) && _candidate.tagName === 'TBODY') { this.pushElement(_candidate, null); // eslint-disable-next-line @typescript-eslint/no-non-null-assertion -- @fixme this.currentCursor.injectedOmittedNode = true; return this.__openElement(tag); } this.clearMismatch(_candidate); } return super.__openElement(tag); } __setAttribute(name, value, namespace) { const unmatched = this.unmatchedAttributes; if (unmatched) { const attr = findByName(unmatched, name); if (attr) { if (attr.value !== value) { attr.value = value; } unmatched.splice(unmatched.indexOf(attr), 1); return; } } return super.__setAttribute(name, value, namespace); } __setProperty(name, value) { const unmatched = this.unmatchedAttributes; if (unmatched) { const attr = findByName(unmatched, name); if (attr) { if (attr.value !== value) { attr.value = value; } unmatched.splice(unmatched.indexOf(attr), 1); return; } } return super.__setProperty(name, value); } __flushElement(parent, constructing) { const { unmatchedAttributes: unmatched } = this; if (unmatched) { for (const attr of unmatched) { // eslint-disable-next-line @typescript-eslint/no-non-null-assertion -- @fixme this.constructing.removeAttribute(attr.name); } this.unmatchedAttributes = null; } else { super.__flushElement(parent, constructing); } } willCloseElement() { const { candidate, currentCursor } = this; if (candidate !== null) { this.clearMismatch(candidate); } if (currentCursor && currentCursor.injectedOmittedNode) { this.popElement(); } super.willCloseElement(); } getMarker(element, guid) { const marker = element.querySelector(`script[glmr="${guid}"]`); if (marker) { return castToSimple(marker); } return null; } __pushRemoteElement(element, cursorId, insertBefore) { const marker = this.getMarker(castToBrowser(element), cursorId); assert(!marker || marker.parentNode === element); // when insertBefore is not present, we clear the element if (insertBefore === undefined) { while (element.firstChild !== null && element.firstChild !== marker) { this.remove(element.firstChild); } insertBefore = null; } const cursor = new RehydratingCursor(element, null, this.blockDepth); this.cursors.push(cursor); if (marker === null) { this.disableRehydration(insertBefore); } else { this.candidate = this.remove(marker); } const block = new RemoteBlock(element); return this.pushBlock(block, true); } didAppendBounds(bounds) { super.didAppendBounds(bounds); if (this.candidate) { const last = bounds.lastNode(); this.candidate = last.nextSibling; } return bounds; } } function isTextNode(node) { return node.nodeType === TEXT_NODE; } function isComment(node) { return node.nodeType === COMMENT_NODE; } function isOpenBlock(node) { return node.nodeType === COMMENT_NODE && node.nodeValue.lastIndexOf('%+b:', 0) === 0; } function isCloseBlock(node) { return node.nodeType === COMMENT_NODE && node.nodeValue.lastIndexOf('%-b:', 0) === 0; } function getBlockDepth(node) { return parseInt(node.nodeValue.slice(4), 10); } function getBlockDepthWithOffset(node, offset) { return getBlockDepth(node) - offset; } function isElement(node) { return node.nodeType === ELEMENT_NODE; } function isMarker(node) { return node.nodeType === COMMENT_NODE && node.nodeValue === '%glmr%'; } function isSeparator(node) { return node.nodeType === COMMENT_NODE && node.nodeValue === '%|%'; } function isEmpty(node) { return node.nodeType === COMMENT_NODE && node.nodeValue === '% %'; } function isSameNodeType(candidate, tag) { if (candidate.namespaceURI === NS_SVG) { return candidate.tagName === tag; } return candidate.tagName === tag.toUpperCase(); } function findByName(array, name) { for (const attr of array) { if (attr.name === name) return attr; } return undefined; } function rehydrationBuilder(env, cursor) { return RehydrateTree.forInitialRender(env, cursor); } export { ConcreteBounds, CursorImpl, NewTreeBuilder, RehydrateTree, RemoteBlock, SERIALIZATION_FIRST_NODE_STRING, isSerializationFirstNode, rehydrationBuilder };