clean-react-directives
Version:
Vue-like syntax for ReactJS
263 lines (243 loc) • 9.35 kB
JavaScript
/**
* MIT License
*
* Copyright (c) 2019 Dejan Sandic - Deyo
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
import React, { createElement, Fragment, Children } from 'react';
/*! *****************************************************************************
Copyright (c) Microsoft Corporation. All rights reserved.
Licensed under the Apache License, Version 2.0 (the "License"); you may not use
this file except in compliance with the License. You may obtain a copy of the
License at http://www.apache.org/licenses/LICENSE-2.0
THIS CODE IS PROVIDED ON AN *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED
WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE,
MERCHANTABLITY OR NON-INFRINGEMENT.
See the Apache Version 2.0 License for specific language governing permissions
and limitations under the License.
***************************************************************************** */
var __assign = function() {
__assign = Object.assign || function __assign(t) {
for (var s, i = 1, n = arguments.length; i < n; i++) {
s = arguments[i];
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) t[p] = s[p];
}
return t;
};
return __assign.apply(this, arguments);
};
function __read(o, n) {
var m = typeof Symbol === "function" && o[Symbol.iterator];
if (!m) return o;
var i = m.call(o), r, ar = [], e;
try {
while ((n === void 0 || n-- > 0) && !(r = i.next()).done) ar.push(r.value);
}
catch (error) { e = { error: error }; }
finally {
try {
if (r && !r.done && (m = i["return"])) m.call(i);
}
finally { if (e) throw e.error; }
}
return ar;
}
function __spread() {
for (var ar = [], i = 0; i < arguments.length; i++)
ar = ar.concat(__read(arguments[i]));
return ar;
}
function createCommonjsModule(fn, module) {
return module = { exports: {} }, fn(module, module.exports), module.exports;
}
var classnames = createCommonjsModule(function (module) {
/*!
Copyright (c) 2017 Jed Watson.
Licensed under the MIT License (MIT), see
http://jedwatson.github.io/classnames
*/
/* global define */
(function () {
var hasOwn = {}.hasOwnProperty;
function classNames () {
var classes = [];
for (var i = 0; i < arguments.length; i++) {
var arg = arguments[i];
if (!arg) continue;
var argType = typeof arg;
if (argType === 'string' || argType === 'number') {
classes.push(arg);
} else if (Array.isArray(arg) && arg.length) {
var inner = classNames.apply(null, arg);
if (inner) {
classes.push(inner);
}
} else if (argType === 'object') {
for (var key in arg) {
if (hasOwn.call(arg, key) && arg[key]) {
classes.push(key);
}
}
}
}
return classes.join(' ');
}
if (module.exports) {
classNames.default = classNames;
module.exports = classNames;
} else {
window.classNames = classNames;
}
}());
});
function CleanReact(_a) {
var children = _a.children, deep = _a.deep;
return createElement(Fragment, {}, applyDirectives(children, deep));
}
/**
* Main function
*/
function applyDirectives(children, deep) {
var last = false;
var lastCon = '';
return Children.map(children, function (child) {
if (!React.isValidElement(child))
return child;
/**
* When checking children, we want to stop when we run into CleanReact
* component because it does its directive checking
*/
if (child.type === CleanReact)
return child;
var props = __assign({}, child.props);
var children = props.children;
var cloningRequired = false;
/**
* If deep flag is true check all children of the component
*/
if (deep && children) {
children = applyDirectives(children, deep);
cloningRequired = true;
}
/**
* Handle r-html directive
*
*/
if ('r-html' in props) {
var __html = props['r-html'];
if (typeof __html !== 'string')
throw new Error('r-html expects a string as its value.');
props = __assign({}, props, { dangerouslySetInnerHTML: { __html: __html } });
cloningRequired = true;
}
/**
* Handle r-class directive
*
*/
if ('r-class' in props) {
var rclass = props['r-class'];
var className = props.className ? classnames(props.className, rclass) : classnames(rclass);
props = __assign({}, props, { className: className });
cloningRequired = true;
}
/**
* Handle r-show directive
*
*/
if ('r-show' in props && !props['r-show']) {
var style = child.props.style || {};
props = __assign({}, props, { style: __assign({}, style, { display: 'none' }) });
cloningRequired = true;
}
/**
* Handle r-if r-else-if and r-else directives
*
*/
var rif = 'r-if' in props && 'r-if';
var relseif = 'r-else-if' in props && 'r-else-if';
var relse = 'r-else' in props && 'r-else';
if ((rif && relseif) || (rif && relse) || (relseif && relse)) {
throw new Error('You cannot combine r-if, r-else-if and r-else on the same component');
}
if (rif) {
lastCon = rif;
last = props[rif];
if (!last)
return null;
}
if (relseif || relse) {
if (lastCon !== 'r-if' && lastCon !== 'r-else-if') {
lastCon = relseif || relse;
throw new Error(lastCon + " can only be placed after r-if or r-else-if");
}
if ((!relse && !props[relseif]) || last)
return null;
last = true;
}
/**
* If cloningRequired is set to true, meaning the child component has been modified,
* strip down library-related props and clone the child using createElement function
*
* We use createElement and not cloneElement because cloneElement keeps the original
* props of the child component
*/
if (cloningRequired) {
libProps.forEach(function (prop) { return delete props[prop]; });
child = createElement(child.type, __assign({}, props), children);
}
return child;
});
}
/**
* Library related props
*/
var libProps = ['r-if', 'r-else-if', 'r-else', 'r-show', 'r-class', 'r-html'];
/**
* In the create-react-app in DEVELOPMENT mode, when passing a prop in the format `prop-name`
* the react component, React expects the value of that prop to be a string
*
* Since we are passing non-boolean values to the directives r-if, r-else-if, r-else,
* and r-show, React will throw an error ( Received `true` for a non-boolean attribute `r-if` )
*
* For this reason, we need to ignore the thrown error message if it is related to our library.
* This is not ass clean as we would like it to be, but unfortunately, it is
* the only solution we have at this point
*/
if (process && process.env && !process.env.rif && process.env.NODE_ENV !== 'production') {
var error_1 = console.error;
console.error = function () {
var args = [];
for (var _i = 0; _i < arguments.length; _i++) {
args[_i] = arguments[_i];
}
function includesString(text, string) {
if (typeof text !== 'string')
return false;
return text.includes(string);
}
var nonBoolean = args.some(function (arg) { return includesString(arg, 'non-boolean'); });
var libRelated = args.some(function (arg) { return libProps.some(function (prop) { return includesString(arg, prop); }); });
if (!nonBoolean || !libRelated)
error_1.apply(void 0, __spread(args));
};
process.env.rif = 'true';
}
export default CleanReact;