UNPKG

chayns-components

Version:

A set of beautiful React components for developing chayns® applications.

386 lines (380 loc) 14.7 kB
"use strict"; var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault"); exports.__esModule = true; exports.default = void 0; var _clsx = _interopRequireDefault(require("clsx")); var _propTypes = _interopRequireDefault(require("prop-types")); var _react = _interopRequireWildcard(require("react")); var _loadOptionalDependency = _interopRequireDefault(require("../../utils/loadOptionalDependency")); function _getRequireWildcardCache(nodeInterop) { if (typeof WeakMap !== "function") return null; var cacheBabelInterop = new WeakMap(); var cacheNodeInterop = new WeakMap(); return (_getRequireWildcardCache = function (nodeInterop) { return nodeInterop ? cacheNodeInterop : cacheBabelInterop; })(nodeInterop); } function _interopRequireWildcard(obj, nodeInterop) { if (!nodeInterop && obj && obj.__esModule) { return obj; } if (obj === null || typeof obj !== "object" && typeof obj !== "function") { return { default: obj }; } var cache = _getRequireWildcardCache(nodeInterop); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (key !== "default" && Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj.default = obj; if (cache) { cache.set(obj, newObj); } return newObj; } /** * @component */ function requireEmojione(returnPromise) { return (0, _loadOptionalDependency.default)('emojione', 'emojione', ['https://cdn.jsdelivr.net/npm/emojione@3.1.7/lib/js/emojione.min.js'], ['https://cdn.jsdelivr.net/npm/emojione@3.1.7/extras/css/emojione.min.css'], returnPromise); } /** * A text input that allows emojis to be put in. */ class EmojiInput extends _react.Component { constructor(props) { super(props); this.lastKeyPressed = null; this.firstRender = true; this.activeNode = 0; this.cursorPos = 0; this.getActiveChildNode = () => { const inputDiv = this.input; const selection = window.getSelection(); const { anchorNode } = selection; const { childNodes } = inputDiv; let activeChildNode = -1; if (anchorNode && anchorNode !== inputDiv) { for (let i = 0; i < childNodes.length; i += 1) { let curNode = childNodes[i]; if (chayns.env.isIOS && curNode.nodeName.toUpperCase() === 'I') { // eslint-disable-next-line prefer-destructuring curNode = curNode.childNodes[0]; } if (curNode === anchorNode || curNode.wholeText === anchorNode.wholeText && curNode.nextElementSibling === anchorNode.nextElementSibling && curNode.previousElementSibling === anchorNode.previousElementSibling) { activeChildNode = curNode.nodeType === 1 ? i + 1 : i; break; } } } else { activeChildNode = this.activeNode; } return activeChildNode; }; this.setCursorPos = () => { const inputDiv = this.input; const { activeNode, cursorPos } = this; if (cursorPos > -1) { inputDiv.focus(); const inputChildNodes = inputDiv.childNodes; const range = document.createRange(); if (activeNode > -1) { const cursorNode = inputChildNodes[activeNode]; if (cursorNode) { if (cursorNode.nodeType === 1 || cursorNode.length < cursorPos) { const nextSibling = cursorNode.nextSibling ? cursorNode.nextSibling.nextSibling : undefined; if (nextSibling && nextSibling.nodeType === 3) { range.setStart(nextSibling, 0); range.setEnd(nextSibling, 0); } else { const newTextNode = document.createTextNode(''); inputDiv.insertBefore(newTextNode, nextSibling); range.setStart(newTextNode, 0); range.setEnd(newTextNode, 0); } } else { range.setStart(cursorNode, cursorPos); range.setEnd(cursorNode, cursorPos); } } } else { const newTextNode = document.createTextNode(''); inputDiv.appendChild(newTextNode); range.setStart(newTextNode, 0); range.setEnd(newTextNode, 0); } const sel = window.getSelection(); sel.removeAllRanges(); sel.addRange(range); } }; this.getPureInnerText = elem => { const emojione = requireEmojione(false); const textLines = ['']; let lineIndex = 0; let curChild = elem.firstChild; const isInDavid = navigator.userAgent.toLowerCase().indexOf('david client') >= 0; while (curChild !== null) { if (curChild.nodeType === 1) { switch (curChild.tagName) { case 'IMG': textLines[lineIndex] += curChild.getAttribute('alt'); break; case 'DIV': textLines.push(''); lineIndex += 1; break; case 'BR': if (chayns.env.browser.name.toLowerCase() !== 'chrome' || isInDavid) { textLines[lineIndex] += '\n'; } break; default: break; } } else if (curChild.nodeType === 3) { textLines[lineIndex] += curChild.nodeValue; } if (curChild.hasChildNodes()) { curChild = curChild.firstChild; } else { while (curChild.nextSibling === null) { curChild = curChild.parentNode; if (curChild === elem) { return textLines.join('\n'); } } curChild = curChild.nextSibling; } } if (!emojione) { return emojione.shortnameToUnicode(textLines.join('\n')); } return textLines.join('\n'); }; this.handleInput = event => { const { onInput } = this.props; // eslint-disable-next-line no-param-reassign event.target.pureInnerText = this.getPureInnerText(event.target); onInput(event); }; this.handleKeyDown = event => { const { onKeyDown } = this.props; this.lastKeyPressed = event.keyCode; if (onKeyDown) { onKeyDown(event); } }; this.handleKeyUp = event => { if (chayns.env.browser.name.toLowerCase() === 'ie' && event.keyCode !== 16) { this.handleInput(event); } if (chayns.env.browser.name.toLowerCase() === 'edge' && event.keyCode === 13 && event.shiftKey) { this.handleInput(event); } }; this.handleFocus = event => { const { onFocus } = this.props; if (onFocus) { onFocus(event); } }; this.handleBlur = event => { const { onBlur } = this.props; if (onBlur) { onBlur(event); } }; this.scrollToCursor = (scrollTop, scrollHeight) => { const inputDiv = this.input; const elemScrollHeight = inputDiv.scrollHeight; const elemClientHeight = inputDiv.clientHeight; if (!(elemScrollHeight <= elemClientHeight)) { const diff = elemScrollHeight - scrollHeight; this.input.scrollTop = (scrollTop || 0) + diff; } }; this.formatText = text => { const emojione = requireEmojione(false); let result = ''; let newText = text.replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/™/g, '&#153;').replace(/©/g, '&copy;').replace(/®/g, '&reg;').replace(/\(y\)/g, '👍').replace(/\(n\)/g, '👎'); if (emojione) { newText = emojione.toImage(newText); } newText = newText.replace(/(<img[^<]*)\/>/g, '$1>').replace(/&#153;/g, '™').replace(/&copy;/g, '©').replace(/&reg;/g, '®'); const lines = newText.split('\n'); const isInDavid = navigator.userAgent.toLowerCase().indexOf('david client') >= 0; if (chayns.env.browser.name.toLowerCase() === 'chrome' && !isInDavid) { if (lines[lines.length - 1] === '' && this.lastKeyPressed === 8 && lines.length > 1) { lines[lines.length - 1] = '<br>'; } result = lines.join('\n'); } else { if (lines.length === 1 && chayns.env.browser.name.toLowerCase() === 'edge' && lines[0] === '') { lines[0] = '<br>'; } result = lines.join('<br>'); } return result.replace(String.fromCharCode(160), String.fromCharCode(32)).replace(/&nbsp;/gm, String.fromCharCode(32)).replace(/&amp;/gm, String.fromCharCode(38)); }; this.updateDOM = newProps => { const inputDiv = this.input; const newHtml = this.formatText(newProps.value); const oldHtml = inputDiv.innerHTML.replace(/&nbsp;/gm, String.fromCharCode(32)).replace(/&amp;/gm, String.fromCharCode(38)).replace(String.fromCharCode(160), String.fromCharCode(32)); if (newHtml !== oldHtml) { this.activeNode = this.getActiveChildNode(); const activeElem = inputDiv.childNodes[this.activeNode]; if (activeElem) { this.cursorPos = EmojiInput.getCaretCharacterOffsetWithin(activeElem); const { scrollTop, scrollHeight } = inputDiv.scrollTop; inputDiv.innerHTML = newHtml; this.scrollToCursor(scrollTop, scrollHeight); this.setCursorPos(); } else { inputDiv.innerHTML = newHtml; } } }; requireEmojione().then(emojione => { /* eslint-disable no-param-reassign */ emojione.ascii = true; emojione.imageTitleTag = false; emojione.blacklistChars = '*,#'; emojione.imagePathPNG = 'https://sub54.tobit.com/frontend/assets/emojione/3.1/png/64/'; /* eslint-enable no-param-reassign */ }); } static shouldComponentUpdate() { return false; } componentDidUpdate(prevProps) { const { placeholder, disabled, value } = this.props; if (value.trim() === '') { this.placeholder.classList.remove('emoji-input__placeholder--hidden'); } else { this.placeholder.classList.add('emoji-input__placeholder--hidden'); } if (prevProps.placeholder !== placeholder) { this.placeholder.innerText = placeholder; } if (prevProps.disabled !== disabled) { this.input.contentEditable = !disabled; } if (prevProps.value !== value || this.firstRender) { this.updateDOM(this.props); this.firstRender = false; } } static getCaretCharacterOffsetWithin(element) { let caretOffset = -1; if (typeof window.getSelection !== 'undefined') { const sel = window.getSelection(); if (sel.anchorNode && (sel.anchorNode.nodeType === 3 || !sel.anchorNode.classList.contains('icon-smile-o'))) { const range = sel.getRangeAt(0); const preCaretRange = range.cloneRange(); preCaretRange.selectNodeContents(element); preCaretRange.setEnd(range.endContainer, range.endOffset); caretOffset = preCaretRange.toString().length; } } else if (typeof document.selection !== 'undefined' && document.selection.type !== 'Control') { const sel = document.selection; if (sel.anchorNode && (sel.anchorNode.nodeType === 3 || !sel.anchorNode.classList.contains('icon-smile-o'))) { const textRange = document.selection.createRange(); const preCaretTextRange = document.body.createTextRange(); preCaretTextRange.moveToElementText(element); preCaretTextRange.setEndPoint('EndToEnd', textRange); caretOffset = preCaretTextRange.text.length; } } return caretOffset; } render() { const { hideBorder, disabled, style, id } = this.props; const messageInputClasses = (0, _clsx.default)('emoji-input__message-input', disabled ? 'emoji-input__message-input--disabled' : "input", hideBorder && 'emoji-input__message-input--hide-border'); return /*#__PURE__*/_react.default.createElement("div", { className: "emoji-input notranslate" }, /*#__PURE__*/_react.default.createElement("div", { // eslint-disable-next-line react/no-danger dangerouslySetInnerHTML: { __html: '<br />' }, ref: ref => { this.input = ref; }, className: messageInputClasses, onKeyDown: this.handleKeyDown, contentEditable: !disabled, onKeyUp: this.handleKeyUp, onInput: this.handleInput, onFocus: this.handleFocus, onBlur: this.handleBlur, style: style, dir: "auto", id: id }), /*#__PURE__*/_react.default.createElement("div", { className: "emoji-input__placeholder", ref: ref => { this.placeholder = ref; } })); } } exports.default = EmojiInput; EmojiInput.propTypes = { /** * Text that will be shown as a placeholder when the input is empty. */ placeholder: _propTypes.default.string.isRequired, /** * This is called when the text changes. There is an additional key on the * `event.target` property called `pureInnerText` which contains the full * text without any of the emoji elements. This is the text you should store * in your local state and pass to this input as the `value`-prop. */ onInput: _propTypes.default.func.isRequired, /** * The value of the input. */ value: _propTypes.default.string.isRequired, /** * The HTML id to give to the input element. */ id: _propTypes.default.string.isRequired, /** * Hides the bottom border of the input. */ hideBorder: _propTypes.default.bool, /** * This will be called on the `keydown`-event of the input element. */ onKeyDown: _propTypes.default.func, /** * Disables any interaction with the input and changes to a disabled style. */ disabled: _propTypes.default.bool, /** * A React style object that will be passed to the input element. */ style: _propTypes.default.objectOf(_propTypes.default.oneOfType([_propTypes.default.string, _propTypes.default.number])), /** * This function will be called when the input element receives focus. */ onFocus: _propTypes.default.func, /** * This function will be called when the input element loses focus. */ onBlur: _propTypes.default.func }; EmojiInput.defaultProps = { hideBorder: false, onKeyDown: null, disabled: false, onFocus: null, onBlur: null, style: null }; EmojiInput.displayName = 'EmojiInput'; //# sourceMappingURL=EmojiInput.js.map