Skip to content

Instantly share code, notes, and snippets.

@cowboy
Last active March 25, 2021 03:57
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save cowboy/89e90aa5aad9bdac039aed1030f86c6e to your computer and use it in GitHub Desktop.
Save cowboy/89e90aa5aad9bdac039aed1030f86c6e to your computer and use it in GitHub Desktop.
esnextbin sketch
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>ESNextbin Sketch</title>
<style>
#parent { border: 1px solid #77f; padding: 1em; margin: 1em; }
#child {}
.box { display: inline-block; padding: 10px; margin-right: 10px; border: 1px solid #0a0; }
.box:last-child { margin-right: 0; }
.box:empty { margin-bottom: -3px; background: #afa; }
.uppercase { text-transform: uppercase; }
</style>
</head>
<body>
<div id=root></div>
</body>
</html>
// Note to self, see:
// https://github.com/Jador/react-hyperscript-helpers
import React from 'react';
import ReactDOM from 'react-dom';
/**
* selectorOrComponent - String or Component class
* props - optional object with properties
* children - String, React Element, or Array of any number of Strings or React Elements
*/
function h(selectorOrComponent, ...children) {
// Get props object if specified.
let props = !isChild(children[0]) ? children.shift() : {};
// Children can be passed as an array or individual arguments. Handle the array case.
if (Array.isArray(children[0])) {
[children] = children;
}
// If selector string, parse id and classNames from it.
if (typeof selectorOrComponent === 'string') {
[selectorOrComponent, props] = parseTagAndPropsFromSelector(selectorOrComponent, props);
}
// Seems smart, I saw it in https://github.com/mlmorg/react-hyperscript/blob/master/index.js
props = promoteNamespacedProps(props, 'dataset', 'data');
// Convert camelCased ariaFoo attributes into aria-foo.
props = adjustPrefixedProps(props, 'aria');
// Return actual React Element.
return React.createElement(selectorOrComponent, props, ...children);
}
// Is the given argument a valid react child?
const isChild = c => {
return typeof c === 'string' || typeof c === 'number' || Array.isArray(c) || React.isValidElement(c);
};
// Should parse selector string:
// a#b => tag = 'a', props = {id: 'b'}
// a.c => tag = 'a', props = {className: 'c'}
// a.c.d => tag = 'a', props = {className: 'c d'}
// a#b.c.d => tag = 'a', props = {id: 'b', className: 'c d'}
// a.c.d#b => tag = 'a', props = {id: 'b', className: 'c d'}
// #b => tag = 'div', props = {id: 'b'}
// .c.d => tag = 'div', props = {className: 'c d'}
const parseTagAndPropsFromSelector = (sel, props = {}) => {
props = Object.assign({}, props);
// console.log(`SEL <${sel}>`)
let tag = 'div';
let classNames = [];
let id = null;
const re = /([.#])?([^.#]+)/g
let match;
while (match = re.exec(sel)) {
const [, mode, value] = match;
if (mode === '.') {
classNames.push(value);
}
else if (mode === '#') {
id = value;
}
else {
tag = value;
}
}
if (classNames.length > 0) {
props.className = classNames.join(' ');
}
if (id) {
props.id = id;
}
return [tag, props];
};
// Should hyphenize a camelCased name.
// foo -> foo
// fooBar -> foo-bar
// fooBarBaz -> foo-bar-baz
const hyphenize = s => s.replace(/([a-z])([A-Z])/g, ([a, b]) => `${a}-${b.toLowerCase()}`);
// Should promote namespaced props, delete the namespace obj, return new obj.
// (dataset, data) {dataset, x} -> {dataset, x}
// (dataset, data) {dataset:{foo, bar}, x} -> {data-foo, data-bar, x}
// (dataset, data) {dataset:{fooBar}, x} -> {data-foo-bar, x}
// (dataset, data) {dataset:{fooBarBaz}, x} -> {data-foo-bar-baz, x}
const promoteNamespacedProps = (props, namespace, prefix) => {
const obj = props[namespace];
if (typeof obj !== 'object') {
return props;
}
const result = Object.assign({}, props);
delete result[namespace];
Object.keys(obj).forEach(key => {
result[`${prefix}-${hyphenize(key)}`] = obj[key];
});
return result;
};
// Should adjust prefixed props and return a new object.
// (aria) ariaLabel -> aria-label
// (aria) htmlFor -> htmlFor
const adjustPrefixedProps = (props, prefix) => {
const re = new RegExp(`^${prefix}([A-Z])`);
return Object.keys(props).reduce((memo, key) => {
memo[re.test(key) ? hyphenize(key) : key] = props[key];
return memo;
}, {});
};
// From https://github.com/Jador/react-hyperscript-helpers/blob/master/src/tag-names.js
const TAG_NAMES = [
'a', 'abbr', 'address', 'area', 'article', 'aside', 'audio', 'b', 'base', 'bdi', 'bdo',
'big', 'blockquote', 'body', 'br', 'button', 'canvas', 'caption', 'cite', 'code', 'col',
'colgroup', 'data', 'datalist', 'dd', 'del', 'details', 'dfn', 'dialog', 'div', 'dl', 'dt',
'em', 'embed', 'fieldset', 'figcaption', 'figure', 'footer', 'form', 'h1', 'h2', 'h3', 'h4',
'h5', 'h6', 'head', 'header', 'hgroup', 'hr', 'html', 'i', 'iframe', 'img', 'input', 'ins',
'kbd', 'keygen', 'label', 'legend', 'li', 'link', 'main', 'map', 'mark', 'menu', 'menuitem',
'meta', 'meter', 'nav', 'noscript', 'object', 'ol', 'optgroup', 'option', 'output', 'p', 'param',
'picture', 'pre', 'progress', 'q', 'rp', 'rt', 'ruby', 's', 'samp', 'script', 'section', 'select',
'small', 'source', 'span', 'strong', 'style', 'sub', 'summary', 'sup', 'table', 'tbody', 'td',
'textarea', 'tfoot', 'th', 'thead', 'time', 'title', 'tr', 'track', 'u', 'ul', 'video',
'wbr', 'circle', 'clipPath', 'defs', 'ellipse', 'g', 'image', 'line', 'linearGradient', 'mask',
'path', 'pattern', 'polygon', 'polyline', 'radialGradient', 'rect', 'stop', 'svg', 'text', 'tspan'
];
// A selector is a string comprised of only .aaa (class) and #bbb (id) parts.
const isSelector = s => typeof s === 'string' && /^([.#][^.#\s]+)+$/.test(s);
// getTagWrapperFn(h, 'h1')('#foo') should call h('h1#foo')
// getTagWrapperFn(h, 'h1')('.bar') should call h('h1.bar')
// getTagWrapperFn(h, 'h1')('baz!') should call h('h1', 'baz!')
const getTagWrapperFn = (fn, tag) => (sel, ...args) => {
// todo: better validate that sel is a class-and-id-selector-string
if (isSelector(sel)) {
return fn(`${tag}${sel}`, ...args);
}
return fn(tag, sel, ...args);
};
// todo: set props on module.exports so you can do either of the following:
// import {div, h1, h2, h3, p, a} from 'this-library';
// import * as $ from 'this-library';
TAG_NAMES.forEach(tag => {
window[tag] = getTagWrapperFn(h, tag);
});
// Actual app code would look like this:
ReactDOM.render((
div('#parent', [
h1(a({href: 'http://bocoup.com/'}, span('h1 header'))),
h2('.uppercase', 'h2 sub header'),
h3('#child.uppercase', {ariaLabel: "hello", style: {color: 'red'}}, 'h3 sub sub header'),
span('.box'),
span({className: 'box'}),
span('.box', span('.box', span('.box', span('.box')))),
span('.box', span('.box'), span('.box'), span('.box')),
p([
'p before anchor ',
a({href: 'http://bocoup.com/', className: 'uppercase'}, 'Bocoup!'),
' after anchor',
]),
])
), document.getElementById('root'));
{
"name": "esnextbin-sketch",
"version": "0.0.0",
"dependencies": {
"react": "15.3.1",
"react-dom": "15.3.1",
"babel-runtime": "6.11.6"
}
}
'use strict';
var _keys = require('babel-runtime/core-js/object/keys');
var _keys2 = _interopRequireDefault(_keys);
var _typeof2 = require('babel-runtime/helpers/typeof');
var _typeof3 = _interopRequireDefault(_typeof2);
var _extends2 = require('babel-runtime/helpers/extends');
var _extends3 = _interopRequireDefault(_extends2);
var _toConsumableArray2 = require('babel-runtime/helpers/toConsumableArray');
var _toConsumableArray3 = _interopRequireDefault(_toConsumableArray2);
var _slicedToArray2 = require('babel-runtime/helpers/slicedToArray');
var _slicedToArray3 = _interopRequireDefault(_slicedToArray2);
var _react = require('react');
var _react2 = _interopRequireDefault(_react);
var _reactDom = require('react-dom');
var _reactDom2 = _interopRequireDefault(_reactDom);
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
/**
* selectorOrComponent - String or Component class
* props - optional object with properties
* children - String, React Element, or Array of any number of Strings or React Elements
*/
// Note to self, see:
// https://github.com/Jador/react-hyperscript-helpers
function h(selectorOrComponent) {
for (var _len = arguments.length, children = Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) {
children[_key - 1] = arguments[_key];
}
// Get props object if specified.
var props = !isChild(children[0]) ? children.shift() : {};
// Children can be passed as an array or individual arguments. Handle the array case.
if (Array.isArray(children[0])) {
var _children = children;
var _children2 = (0, _slicedToArray3.default)(_children, 1);
children = _children2[0];
}
// If selector string, parse id and classNames from it.
if (typeof selectorOrComponent === 'string') {
var _parseTagAndPropsFrom = parseTagAndPropsFromSelector(selectorOrComponent, props);
var _parseTagAndPropsFrom2 = (0, _slicedToArray3.default)(_parseTagAndPropsFrom, 2);
selectorOrComponent = _parseTagAndPropsFrom2[0];
props = _parseTagAndPropsFrom2[1];
}
// Seems smart, I saw it in https://github.com/mlmorg/react-hyperscript/blob/master/index.js
props = promoteNamespacedProps(props, 'dataset', 'data');
// Convert camelCased ariaFoo attributes into aria-foo.
props = adjustPrefixedProps(props, 'aria');
// Return actual React Element.
return _react2.default.createElement.apply(_react2.default, [selectorOrComponent, props].concat((0, _toConsumableArray3.default)(children)));
}
// Is the given argument a valid react child?
var isChild = function isChild(c) {
return typeof c === 'string' || typeof c === 'number' || Array.isArray(c) || _react2.default.isValidElement(c);
};
// Should parse selector string:
// a#b => tag = 'a', props = {id: 'b'}
// a.c => tag = 'a', props = {className: 'c'}
// a.c.d => tag = 'a', props = {className: 'c d'}
// a#b.c.d => tag = 'a', props = {id: 'b', className: 'c d'}
// a.c.d#b => tag = 'a', props = {id: 'b', className: 'c d'}
// #b => tag = 'div', props = {id: 'b'}
// .c.d => tag = 'div', props = {className: 'c d'}
var parseTagAndPropsFromSelector = function parseTagAndPropsFromSelector(sel) {
var props = arguments.length <= 1 || arguments[1] === undefined ? {} : arguments[1];
props = (0, _extends3.default)({}, props);
// console.log(`SEL <${sel}>`)
var tag = 'div';
var classNames = [];
var id = null;
var re = /([.#])?([^.#]+)/g;
var match = void 0;
while (match = re.exec(sel)) {
var _match = match;
var _match2 = (0, _slicedToArray3.default)(_match, 3);
var mode = _match2[1];
var value = _match2[2];
if (mode === '.') {
classNames.push(value);
} else if (mode === '#') {
id = value;
} else {
tag = value;
}
}
if (classNames.length > 0) {
props.className = classNames.join(' ');
}
if (id) {
props.id = id;
}
return [tag, props];
};
// Should hyphenize a camelCased name.
// foo -> foo
// fooBar -> foo-bar
// fooBarBaz -> foo-bar-baz
var hyphenize = function hyphenize(s) {
return s.replace(/([a-z])([A-Z])/g, function (_ref) {
var _ref2 = (0, _slicedToArray3.default)(_ref, 2);
var a = _ref2[0];
var b = _ref2[1];
return a + '-' + b.toLowerCase();
});
};
// Should promote namespaced props, delete the namespace obj, return new obj.
// (dataset, data) {dataset, x} -> {dataset, x}
// (dataset, data) {dataset:{foo, bar}, x} -> {data-foo, data-bar, x}
// (dataset, data) {dataset:{fooBar}, x} -> {data-foo-bar, x}
// (dataset, data) {dataset:{fooBarBaz}, x} -> {data-foo-bar-baz, x}
var promoteNamespacedProps = function promoteNamespacedProps(props, namespace, prefix) {
var obj = props[namespace];
if ((typeof obj === 'undefined' ? 'undefined' : (0, _typeof3.default)(obj)) !== 'object') {
return props;
}
var result = (0, _extends3.default)({}, props);
delete result[namespace];
(0, _keys2.default)(obj).forEach(function (key) {
result[prefix + '-' + hyphenize(key)] = obj[key];
});
return result;
};
// Should adjust prefixed props and return a new object.
// (aria) ariaLabel -> aria-label
// (aria) htmlFor -> htmlFor
var adjustPrefixedProps = function adjustPrefixedProps(props, prefix) {
var re = new RegExp('^' + prefix + '([A-Z])');
return (0, _keys2.default)(props).reduce(function (memo, key) {
memo[re.test(key) ? hyphenize(key) : key] = props[key];
return memo;
}, {});
};
// From https://github.com/Jador/react-hyperscript-helpers/blob/master/src/tag-names.js
var TAG_NAMES = ['a', 'abbr', 'address', 'area', 'article', 'aside', 'audio', 'b', 'base', 'bdi', 'bdo', 'big', 'blockquote', 'body', 'br', 'button', 'canvas', 'caption', 'cite', 'code', 'col', 'colgroup', 'data', 'datalist', 'dd', 'del', 'details', 'dfn', 'dialog', 'div', 'dl', 'dt', 'em', 'embed', 'fieldset', 'figcaption', 'figure', 'footer', 'form', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'head', 'header', 'hgroup', 'hr', 'html', 'i', 'iframe', 'img', 'input', 'ins', 'kbd', 'keygen', 'label', 'legend', 'li', 'link', 'main', 'map', 'mark', 'menu', 'menuitem', 'meta', 'meter', 'nav', 'noscript', 'object', 'ol', 'optgroup', 'option', 'output', 'p', 'param', 'picture', 'pre', 'progress', 'q', 'rp', 'rt', 'ruby', 's', 'samp', 'script', 'section', 'select', 'small', 'source', 'span', 'strong', 'style', 'sub', 'summary', 'sup', 'table', 'tbody', 'td', 'textarea', 'tfoot', 'th', 'thead', 'time', 'title', 'tr', 'track', 'u', 'ul', 'video', 'wbr', 'circle', 'clipPath', 'defs', 'ellipse', 'g', 'image', 'line', 'linearGradient', 'mask', 'path', 'pattern', 'polygon', 'polyline', 'radialGradient', 'rect', 'stop', 'svg', 'text', 'tspan'];
// A selector is a string comprised of only .aaa (class) and #bbb (id) parts.
var isSelector = function isSelector(s) {
return typeof s === 'string' && /^([.#][^.#\s]+)+$/.test(s);
};
// getTagWrapperFn(h, 'h1')('#foo') should call h('h1#foo')
// getTagWrapperFn(h, 'h1')('.bar') should call h('h1.bar')
// getTagWrapperFn(h, 'h1')('baz!') should call h('h1', 'baz!')
var getTagWrapperFn = function getTagWrapperFn(fn, tag) {
return function (sel) {
for (var _len2 = arguments.length, args = Array(_len2 > 1 ? _len2 - 1 : 0), _key2 = 1; _key2 < _len2; _key2++) {
args[_key2 - 1] = arguments[_key2];
}
// todo: better validate that sel is a class-and-id-selector-string
if (isSelector(sel)) {
return fn.apply(undefined, ['' + tag + sel].concat(args));
}
return fn.apply(undefined, [tag, sel].concat(args));
};
};
// todo: set props on module.exports so you can do either of the following:
// import {div, h1, h2, h3, p, a} from 'this-library';
// import * as $ from 'this-library';
TAG_NAMES.forEach(function (tag) {
window[tag] = getTagWrapperFn(h, tag);
});
// Actual app code would look like this:
_reactDom2.default.render(div('#parent', [h1(a({ href: 'http://bocoup.com/' }, span('h1 header'))), h2('.uppercase', 'h2 sub header'), h3('#child.uppercase', { ariaLabel: "hello", style: { color: 'red' } }, 'h3 sub sub header'), span('.box'), span({ className: 'box' }), span('.box', span('.box', span('.box', span('.box')))), span('.box', span('.box'), span('.box'), span('.box')), p(['p before anchor ', a({ href: 'http://bocoup.com/', className: 'uppercase' }, 'Bocoup!'), ' after anchor'])]), document.getElementById('root'));
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment