UNPKG

@navinc/base-react-components

Version:
126 lines 5.01 kB
/* eslint-env browser */ // Based on https://github.com/reactjs/react-magic/blob/master/src/htmltojsx.js import React from 'react'; import mapAttribute from './mapAttribute.js'; const TABLE_ELEMENTS = ['table', 'tbody', 'thead', 'tfoot', 'tr']; // map HTML attribute to react props, and optionally DOM prop by using array // if DOM prop is same as attribute name, use single item array const attributePropMap = { for: 'htmlFor', class: 'className', // react prop and DOM prop have different casing allowfullscreen: ['allowFullScreen', 'allowFullscreen'], autocomplete: 'autoComplete', autofocus: ['autoFocus'], contenteditable: 'contentEditable', spellcheck: 'spellCheck', srcdoc: 'srcDoc', srcset: 'srcSet', itemscope: 'itemScope', itemprop: 'itemProp', itemtype: 'itemType', }; // some attribute can't be checked whether it's a boolean attribute // or not by using setAttribute trick const SPECIAL_BOOLEAN_ATTRIBUTES = ['itemScope']; function checkBooleanAttribute(el, prop) { el.setAttribute(prop, ''); // @ts-expect-error -- copied code return el[prop] === true || SPECIAL_BOOLEAN_ATTRIBUTES.indexOf(prop) > -1; } /** * In browser we can get equivalent React props by creating a temporary DOM node, * and iterating over its properties. This means we don't have to ship entire * HTML attributes mapping to React props. * * For other edge cases we can use specialPropsMaps */ function getPropInfo(tagName, attributeName) { const propName = attributePropMap[attributeName]; const el = document.createElement(tagName); // handle edge cases first if (propName) { const reactProp = Array.isArray(propName) ? propName[0] : propName; const domProp = Array.isArray(propName) ? propName[1] || attributeName : propName; return { name: reactProp, isBoolean: checkBooleanAttribute(el, domProp) }; } for (const propName in el) { if (propName.toLowerCase() === attributeName.toLowerCase()) { return { name: propName, isBoolean: checkBooleanAttribute(el, propName) }; } } return { name: attributeName, isBoolean: checkBooleanAttribute(el, attributeName), }; } function reactCreateElement(tag, props, transform, children = null) { const customElement = transform[tag]; const defaultTransform = transform._; return customElement ? React.createElement(customElement, props, children) : defaultTransform ? defaultTransform(tag, props, children) : React.createElement(tag, props, children); } function toReactNode(node, key, options) { const transform = options.transform || {}; const preserveAttributes = options.preserveAttributes || []; const dangerouslySetChildren = options.dangerouslySetChildren || ['style']; const defaultTransform = transform._; if (node.nodeType === 8) { return null; } else if (node.nodeType === 3) { const text = node.textContent; return defaultTransform ? defaultTransform(text) : text; } const attrs = {}; const nodeAttributes = node.attributes; for (let i = 0; i < nodeAttributes.length; i++) { attrs[nodeAttributes[i].name] = nodeAttributes[i].value; } attrs.key = key.toString(); const tag = node.tagName.toLowerCase(); const props = mapAttribute(tag, attrs, preserveAttributes, getPropInfo); const children = Array.from(node.childNodes) .map((childNode, i) => { if (TABLE_ELEMENTS.indexOf(tag) > -1 && childNode.nodeType === 3) { childNode.textContent = childNode.textContent.trim(); if (childNode.textContent === '') { return null; } } return toReactNode(childNode, key + '.' + i, options); }) .filter(Boolean); if (dangerouslySetChildren.indexOf(tag) > -1) { let html = node.innerHTML; // Tag can have empty children if (html) { // we need to preserve quote inside style & script tag if (tag !== 'style' && tag !== 'script') { html = html.replace(/"/g, '&quot;'); } props.dangerouslySetInnerHTML = { __html: html.trim() }; } return reactCreateElement(tag, props, transform); } // self closing tag shouldn't have children const reactChildren = children.length === 0 ? null : children; return reactCreateElement(tag, props, transform, reactChildren); } export function htmr(html, options = {}) { if (typeof html !== 'string') { throw new TypeError('Expected HTML string'); } const container = document.createElement('div'); container.innerHTML = html.trim(); const nodes = Array.from(container.childNodes) .map((childNode, index) => { return toReactNode(childNode, String(index), options); }) .filter(Boolean); return nodes.length === 1 ? nodes[0] : nodes; } //# sourceMappingURL=index.js.map