UNPKG

@kontent-ai/smart-link

Version:

Kontent.ai Smart Link SDK allowing to automatically inject [smart links](https://docs.kontent.ai/tutorials/develop-apps/build-strong-foundation/set-up-editing-from-preview#a-using-smart-links) to Kontent.ai according to manually specified [HTML data attri

114 lines 5.45 kB
import { groupElementsByRenderingRoot } from '../utils/domElement'; import { KSLContainerElement } from '../web-components/KSLContainerElement'; import { shouldElementHaveHighlight, shouldElementHaveAddButton } from '../utils/customElements'; export class SmartLinkRenderer { configuration; defaultContainer; containerByRenderingRoot; highlightByElement; addButtonByElement; constructor(configuration) { this.configuration = configuration; this.containerByRenderingRoot = new Map(); this.highlightByElement = new Map(); this.addButtonByElement = new Map(); this.defaultContainer = createAndMountDefaultContainer(); } render = (visibleElements, observedElements) => { if (observedElements.size === 0) { this.clear(); return; } const { newAddButtonByElement, newHighlightByElement } = this.processVisibleElements(visibleElements, this.addButtonByElement, this.highlightByElement); // Remove highlights that are not observed anymore as they might be removed from the DOM. for (const [element, highlight] of newHighlightByElement) { if (!observedElements.has(element)) { highlight.remove(); newHighlightByElement.delete(element); } } // Remove add buttons that are not observed anymore as they might be removed from the DOM. for (const [element, addButton] of newAddButtonByElement) { if (!observedElements.has(element)) { addButton.remove(); newAddButtonByElement.delete(element); } } // All containers that have no children can be removed because they are not used by any highlight, or a add button. for (const [parent, container] of this.containerByRenderingRoot.entries()) { if (container.children.length === 0) { container.remove(); this.containerByRenderingRoot.delete(parent); } } this.highlightByElement = newHighlightByElement; this.addButtonByElement = newAddButtonByElement; }; destroy = () => { this.clear(); this.defaultContainer.remove(); }; clear = () => { for (const [, addButton] of this.addButtonByElement.entries()) { addButton.remove(); } for (const [, highlight] of this.highlightByElement.entries()) { highlight.remove(); } for (const [, container] of this.containerByRenderingRoot.entries()) { container.remove(); } this.highlightByElement = new Map(); this.containerByRenderingRoot = new Map(); this.addButtonByElement = new Map(); this.defaultContainer.innerHTML = ''; }; processVisibleElements = (visibleElements, addButtonByElement, highlightByElement) => { // Group elements by their rendering roots to avoid unnecessary re-calculations (e.g. reposition container only once // instead of repositioning it for every child, calculating bounding client rects, etc.). const elementsByRenderingRoot = groupElementsByRenderingRoot(visibleElements); return Array.from(elementsByRenderingRoot.entries()).reduce((acc, [root, elements]) => { const container = this.createContainerIfNotExist(root); container.adjustPosition(); for (const element of elements) { // This check is needed to prevent highlight rendering for the "flat" elements (height or/and width === 0), // because those elements are basically invisible and cannot be clicked. const isFlat = element.offsetHeight === 0 || element.offsetWidth === 0; if (!isFlat && shouldElementHaveHighlight(element, this.configuration)) { const highlight = acc.newHighlightByElement.get(element) ?? container.createHighlightForElement(element); highlight.adjustPosition(); acc.newHighlightByElement.set(element, highlight); } if (shouldElementHaveAddButton(element, this.configuration)) { const addButton = acc.newAddButtonByElement.get(element) ?? container.createAddButtonForElement(element); addButton.adjustPosition(); acc.newAddButtonByElement.set(element, addButton); } } return acc; }, { newAddButtonByElement: new Map(addButtonByElement), newHighlightByElement: new Map(highlightByElement), }); }; createContainerIfNotExist = (root) => { // if root is not specified or root is body if (!root || root === document.body) { return this.defaultContainer; } const container = this.containerByRenderingRoot.get(root); if (container) { return container; } const newContainer = document.createElement(KSLContainerElement.is); root.appendChild(newContainer); this.containerByRenderingRoot.set(root, newContainer); return newContainer; }; } const createAndMountDefaultContainer = () => { const container = document.createElement(KSLContainerElement.is); window.document.body.appendChild(container); return container; }; //# sourceMappingURL=SmartLinkRenderer.js.map