├── .travis.yml ├── .gitignore ├── generate-ts-def.js ├── test ├── svg-test.js ├── react-test.js └── test.js ├── LICENSE ├── package.json ├── src ├── svg.js └── index.js ├── dist ├── svg.js ├── index.js └── index.d.ts ├── .eslintrc └── README.md /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - '4.1.0' 4 | script: npm test 5 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | 5 | # Runtime data 6 | pids 7 | *.pid 8 | *.seed 9 | 10 | # Directory for instrumented libs generated by jscoverage/JSCover 11 | lib-cov 12 | 13 | # Coverage directory used by tools like istanbul 14 | coverage 15 | 16 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 17 | .grunt 18 | 19 | # node-waf configuration 20 | .lock-wscript 21 | 22 | # Compiled binary addons (http://nodejs.org/api/addons.html) 23 | build/Release 24 | 25 | # Dependency directory 26 | # https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git 27 | node_modules 28 | -------------------------------------------------------------------------------- /generate-ts-def.js: -------------------------------------------------------------------------------- 1 | import fs from 'fs'; 2 | import tagNames from 'html-tag-names'; 3 | import hh from './src/index'; 4 | 5 | const start = 6 | `declare interface HyperScriptHelperFn { 7 | (selector?: any, properties?: any, children?: any): any; 8 | } 9 | 10 | declare type HyperScriptHelpers = {`; 11 | 12 | const middle = tagNames.reduce((accum, tag) => { 13 | return accum + ` 14 | ${tag}: HyperScriptHelperFn;`; 15 | }, ``); 16 | 17 | const end = ` 18 | } 19 | 20 | export default function hh(h: Function): HyperScriptHelpers; 21 | `; 22 | 23 | fs.writeFile('./dist/index.d.ts', start + middle + end, (err) => { 24 | if (err) { 25 | throw err; 26 | } 27 | }); 28 | -------------------------------------------------------------------------------- /test/svg-test.js: -------------------------------------------------------------------------------- 1 | const assert = require('assert'); 2 | const h = require('hyperscript'); 3 | const helpers = require('../dist/index')(h); 4 | const svgHelpers = require('../dist/svg')(h); 5 | const svg = helpers.svg; 6 | const rect = svgHelpers.rect; 7 | const jsc = require('jsverify'); 8 | const _ = require('lodash'); 9 | const tagNames = require('svg-tag-names'); 10 | 11 | describe('svg', function(){ 12 | jsc.property('svg() ≡ h("svg")', function(){ 13 | return _.isEqual(h('svg').nodeName, svg().nodeName); 14 | }); 15 | 16 | jsc.property('rect() ≡ h("rect")', function(){ 17 | return _.isEqual(h('rect').nodeName, rect().nodeName); 18 | }); 19 | }); 20 | 21 | var tagArb = jsc.elements(tagNames); 22 | 23 | describe('arbitrary svg tag', function(){ 24 | jsc.property('tag() ≡ h("tag")', tagArb, function(tag){ 25 | return _.isEqual(h(tag).nodeName, svgHelpers[tag]().nodeName); 26 | }); 27 | }); 28 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Ossi Hanhinen 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "hyperscript-helpers", 3 | "version": "3.0.3", 4 | "description": "Terse syntax for hyperscript", 5 | "main": "dist/index.js", 6 | "typings": "dist/index.d.ts", 7 | "config": { 8 | "commitizen": { 9 | "path": "node_modules/rb-conventional-changelog/" 10 | }, 11 | "ghooks": { 12 | "commit-msg": "node ./node_modules/validate-commit-msg/index.js" 13 | } 14 | }, 15 | "dependencies": {}, 16 | "devDependencies": { 17 | "babel": "^5.6.14", 18 | "babel-eslint": "^4.1.4", 19 | "commitizen": "^2.5.0", 20 | "eslint": "^1.9.0", 21 | "html-tag-names": "^1.0.0", 22 | "hyperscript": "^1.4.6", 23 | "jsverify": "0.6.0", 24 | "lodash": "^3.10.0", 25 | "mocha": "^2.2.5", 26 | "rb-conventional-changelog": "^1.1.9", 27 | "react": "^0.14.2", 28 | "svg-tag-names": "^1.1.1", 29 | "validate-commit-msg": "^1.1.0" 30 | }, 31 | "scripts": { 32 | "lint": "./node_modules/.bin/eslint src/", 33 | "test": "npm run lint && mocha", 34 | "start": "./node_modules/.bin/babel src --out-dir=dist", 35 | "ts-def": "./node_modules/.bin/babel-node ./generate-ts-def.js", 36 | "prestart": "npm install && npm run ts-def", 37 | "commit": "./node_modules/.bin/git-cz" 38 | }, 39 | "repository": { 40 | "type": "git", 41 | "url": "git+https://github.com/ohanhi/hyperscript-helpers.git" 42 | }, 43 | "keywords": [ 44 | "hyperscript", 45 | "virtual-hyperscript", 46 | "virtual-dom" 47 | ], 48 | "author": "ohanhi", 49 | "license": "MIT", 50 | "bugs": { 51 | "url": "https://github.com/ohanhi/hyperscript-helpers/issues" 52 | }, 53 | "homepage": "https://github.com/ohanhi/hyperscript-helpers#readme" 54 | } 55 | -------------------------------------------------------------------------------- /src/svg.js: -------------------------------------------------------------------------------- 1 | 2 | import hh from './index'; 3 | 4 | // https://www.w3.org/TR/SVG/eltindex.html 5 | // The tag names are verified against svg-tag-names in the tests 6 | // See https://github.com/ohanhi/hyperscript-helpers/issues/34 for the reason 7 | // why the tags aren't simply required from svg-tag-names 8 | const TAG_NAMES = [ 9 | 'a', 'altGlyph', 'altGlyphDef', 'altGlyphItem', 'animate', 'animateColor', 10 | 'animateMotion', 'animateTransform', 'animation', 'audio', 'canvas', 11 | 'circle', 'clipPath', 'color-profile', 'cursor', 'defs', 'desc', 'discard', 12 | 'ellipse', 'feBlend', 'feColorMatrix', 'feComponentTransfer', 'feComposite', 13 | 'feConvolveMatrix', 'feDiffuseLighting', 'feDisplacementMap', 14 | 'feDistantLight', 'feDropShadow', 'feFlood', 'feFuncA', 'feFuncB', 'feFuncG', 15 | 'feFuncR', 'feGaussianBlur', 'feImage', 'feMerge', 'feMergeNode', 16 | 'feMorphology', 'feOffset', 'fePointLight', 'feSpecularLighting', 17 | 'feSpotLight', 'feTile', 'feTurbulence', 'filter', 'font', 'font-face', 18 | 'font-face-format', 'font-face-name', 'font-face-src', 'font-face-uri', 19 | 'foreignObject', 'g', 'glyph', 'glyphRef', 'handler', 'hatch', 'hatchpath', 20 | 'hkern', 'iframe', 'image', 'line', 'linearGradient', 'listener', 'marker', 21 | 'mask', 'mesh', 'meshgradient', 'meshpatch', 'meshrow', 'metadata', 22 | 'missing-glyph', 'mpath', 'path', 'pattern', 'polygon', 'polyline', 23 | 'prefetch', 'radialGradient', 'rect', 'script', 'set', 'solidColor', 24 | 'solidcolor', 'stop', 'style', 'svg', 'switch', 'symbol', 'tbreak', 'text', 25 | 'textArea', 'textPath', 'title', 'tref', 'tspan', 'unknown', 'use', 'video', 26 | 'view', 'vkern' 27 | ]; 28 | 29 | export default h => { 30 | const createTag = hh(h).createTag; 31 | const exported = { TAG_NAMES, createTag }; 32 | TAG_NAMES.forEach(n => { 33 | exported[n] = createTag(n); 34 | }); 35 | return exported; 36 | }; 37 | -------------------------------------------------------------------------------- /dist/svg.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | Object.defineProperty(exports, '__esModule', { 4 | value: true 5 | }); 6 | 7 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; } 8 | 9 | var _index = require('./index'); 10 | 11 | var _index2 = _interopRequireDefault(_index); 12 | 13 | // https://www.w3.org/TR/SVG/eltindex.html 14 | // The tag names are verified against svg-tag-names in the tests 15 | // See https://github.com/ohanhi/hyperscript-helpers/issues/34 for the reason 16 | // why the tags aren't simply required from svg-tag-names 17 | var TAG_NAMES = ['a', 'altGlyph', 'altGlyphDef', 'altGlyphItem', 'animate', 'animateColor', 'animateMotion', 'animateTransform', 'animation', 'audio', 'canvas', 'circle', 'clipPath', 'color-profile', 'cursor', 'defs', 'desc', 'discard', 'ellipse', 'feBlend', 'feColorMatrix', 'feComponentTransfer', 'feComposite', 'feConvolveMatrix', 'feDiffuseLighting', 'feDisplacementMap', 'feDistantLight', 'feDropShadow', 'feFlood', 'feFuncA', 'feFuncB', 'feFuncG', 'feFuncR', 'feGaussianBlur', 'feImage', 'feMerge', 'feMergeNode', 'feMorphology', 'feOffset', 'fePointLight', 'feSpecularLighting', 'feSpotLight', 'feTile', 'feTurbulence', 'filter', 'font', 'font-face', 'font-face-format', 'font-face-name', 'font-face-src', 'font-face-uri', 'foreignObject', 'g', 'glyph', 'glyphRef', 'handler', 'hatch', 'hatchpath', 'hkern', 'iframe', 'image', 'line', 'linearGradient', 'listener', 'marker', 'mask', 'mesh', 'meshgradient', 'meshpatch', 'meshrow', 'metadata', 'missing-glyph', 'mpath', 'path', 'pattern', 'polygon', 'polyline', 'prefetch', 'radialGradient', 'rect', 'script', 'set', 'solidColor', 'solidcolor', 'stop', 'style', 'svg', 'switch', 'symbol', 'tbreak', 'text', 'textArea', 'textPath', 'title', 'tref', 'tspan', 'unknown', 'use', 'video', 'view', 'vkern']; 18 | 19 | exports['default'] = function (h) { 20 | var createTag = (0, _index2['default'])(h).createTag; 21 | var exported = { TAG_NAMES: TAG_NAMES, createTag: createTag }; 22 | TAG_NAMES.forEach(function (n) { 23 | exported[n] = createTag(n); 24 | }); 25 | return exported; 26 | }; 27 | 28 | module.exports = exports['default']; -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | const isValidString = param => typeof param === 'string' && param.length > 0; 2 | 3 | const startsWith = (string, start) => string[0] === start; 4 | 5 | const isSelector = param => 6 | isValidString(param) && (startsWith(param, '.') || startsWith(param, '#')); 7 | 8 | const node = h => tagName => (first, ...rest) => { 9 | if (isSelector(first)) { 10 | return h(tagName + first, ...rest); 11 | } else if (typeof first === 'undefined') { 12 | return h(tagName); 13 | } else { 14 | return h(tagName, first, ...rest); 15 | } 16 | }; 17 | 18 | // The tag names are verified against html-tag-names in the tests 19 | // See https://github.com/ohanhi/hyperscript-helpers/issues/34 for the reason 20 | // why the tags aren't simply required from html-tag-names 21 | const TAG_NAMES = [ 22 | 'a', 'abbr', 'acronym', 'address', 'applet', 'area', 'article', 'aside', 23 | 'audio', 'b', 'base', 'basefont', 'bdi', 'bdo', 'bgsound', 'big', 'blink', 24 | 'blockquote', 'body', 'br', 'button', 'canvas', 'caption', 'center', 'cite', 25 | 'code', 'col', 'colgroup', 'command', 'content', 'data', 'datalist', 'dd', 26 | 'del', 'details', 'dfn', 'dialog', 'dir', 'div', 'dl', 'dt', 'element', 'em', 27 | 'embed', 'fieldset', 'figcaption', 'figure', 'font', 'footer', 'form', 28 | 'frame', 'frameset', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'head', 'header', 29 | 'hgroup', 'hr', 'html', 'i', 'iframe', 'image', 'img', 'input', 'ins', 30 | 'isindex', 'kbd', 'keygen', 'label', 'legend', 'li', 'link', 'listing', 31 | 'main', 'map', 'mark', 'marquee', 'math', 'menu', 'menuitem', 'meta', 32 | 'meter', 'multicol', 'nav', 'nextid', 'nobr', 'noembed', 'noframes', 33 | 'noscript', 'object', 'ol', 'optgroup', 'option', 'output', 'p', 'param', 34 | 'picture', 'plaintext', 'pre', 'progress', 'q', 'rb', 'rbc', 'rp', 'rt', 35 | 'rtc', 'ruby', 's', 'samp', 'script', 'section', 'select', 'shadow', 'slot', 36 | 'small', 'source', 'spacer', 'span', 'strike', 'strong', 'style', 'sub', 37 | 'summary', 'sup', 'svg', 'table', 'tbody', 'td', 'template', 'textarea', 38 | 'tfoot', 'th', 'thead', 'time', 'title', 'tr', 'track', 'tt', 'u', 'ul', 39 | 'var', 'video', 'wbr', 'xmp' 40 | ]; 41 | 42 | export default h => { 43 | const createTag = node(h); 44 | const exported = { TAG_NAMES, isSelector, createTag }; 45 | TAG_NAMES.forEach(n => { 46 | // Also support a first-letter-uppercase spelling to help avoid conflicts 47 | // with other variables or Javascript reserved keywords such as 'var' 48 | exported[n] = exported[n.charAt(0).toUpperCase() + n.slice(1)] = 49 | createTag(n); 50 | }); 51 | return exported; 52 | }; 53 | -------------------------------------------------------------------------------- /dist/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | Object.defineProperty(exports, '__esModule', { 4 | value: true 5 | }); 6 | var isValidString = function isValidString(param) { 7 | return typeof param === 'string' && param.length > 0; 8 | }; 9 | 10 | var startsWith = function startsWith(string, start) { 11 | return string[0] === start; 12 | }; 13 | 14 | var isSelector = function isSelector(param) { 15 | return isValidString(param) && (startsWith(param, '.') || startsWith(param, '#')); 16 | }; 17 | 18 | var node = function node(h) { 19 | return function (tagName) { 20 | return function (first) { 21 | for (var _len = arguments.length, rest = Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) { 22 | rest[_key - 1] = arguments[_key]; 23 | } 24 | 25 | if (isSelector(first)) { 26 | return h.apply(undefined, [tagName + first].concat(rest)); 27 | } else if (typeof first === 'undefined') { 28 | return h(tagName); 29 | } else { 30 | return h.apply(undefined, [tagName, first].concat(rest)); 31 | } 32 | }; 33 | }; 34 | }; 35 | 36 | // The tag names are verified against html-tag-names in the tests 37 | // See https://github.com/ohanhi/hyperscript-helpers/issues/34 for the reason 38 | // why the tags aren't simply required from html-tag-names 39 | var TAG_NAMES = ['a', 'abbr', 'acronym', 'address', 'applet', 'area', 'article', 'aside', 'audio', 'b', 'base', 'basefont', 'bdi', 'bdo', 'bgsound', 'big', 'blink', 'blockquote', 'body', 'br', 'button', 'canvas', 'caption', 'center', 'cite', 'code', 'col', 'colgroup', 'command', 'content', 'data', 'datalist', 'dd', 'del', 'details', 'dfn', 'dialog', 'dir', 'div', 'dl', 'dt', 'element', 'em', 'embed', 'fieldset', 'figcaption', 'figure', 'font', 'footer', 'form', 'frame', 'frameset', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'head', 'header', 'hgroup', 'hr', 'html', 'i', 'iframe', 'image', 'img', 'input', 'ins', 'isindex', 'kbd', 'keygen', 'label', 'legend', 'li', 'link', 'listing', 'main', 'map', 'mark', 'marquee', 'math', 'menu', 'menuitem', 'meta', 'meter', 'multicol', 'nav', 'nextid', 'nobr', 'noembed', 'noframes', 'noscript', 'object', 'ol', 'optgroup', 'option', 'output', 'p', 'param', 'picture', 'plaintext', 'pre', 'progress', 'q', 'rb', 'rbc', 'rp', 'rt', 'rtc', 'ruby', 's', 'samp', 'script', 'section', 'select', 'shadow', 'slot', 'small', 'source', 'spacer', 'span', 'strike', 'strong', 'style', 'sub', 'summary', 'sup', 'svg', 'table', 'tbody', 'td', 'template', 'textarea', 'tfoot', 'th', 'thead', 'time', 'title', 'tr', 'track', 'tt', 'u', 'ul', 'var', 'video', 'wbr', 'xmp']; 40 | 41 | exports['default'] = function (h) { 42 | var createTag = node(h); 43 | var exported = { TAG_NAMES: TAG_NAMES, isSelector: isSelector, createTag: createTag }; 44 | TAG_NAMES.forEach(function (n) { 45 | // Also support a first-letter-uppercase spelling to help avoid conflicts 46 | // with other variables or Javascript reserved keywords such as 'var' 47 | exported[n] = exported[n.charAt(0).toUpperCase() + n.slice(1)] = createTag(n); 48 | }); 49 | return exported; 50 | }; 51 | 52 | module.exports = exports['default']; -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "rules": { 3 | "indent": [ 4 | 2, 5 | 2 6 | ], 7 | "quotes": [ 8 | 2, 9 | "single" 10 | ], 11 | "linebreak-style": [ 12 | 2, 13 | "unix" 14 | ], 15 | "semi": [ 16 | 2, 17 | "always" 18 | ], 19 | "no-console": 0, 20 | "max-len": [ 21 | 1, 22 | 90 23 | ], 24 | "comma-dangle": [ 25 | 2, 26 | "never" 27 | ], 28 | "no-cond-assign": [2, "always"], 29 | "no-ex-assign": 2, 30 | "curly": 2, 31 | "max-depth": [2, 5], 32 | "complexity": [1, 8], 33 | "prefer-const": 1, 34 | "indent": [2, 2], 35 | "no-trailing-spaces": [2, {"skipBlankLines": false}], 36 | "one-var": [2, "never"], 37 | "func-names": 2, 38 | "key-spacing": [2, { 39 | "beforeColon": false, 40 | "afterColon": true 41 | }], 42 | "max-nested-callbacks": [2, 2], 43 | "new-cap": 0, 44 | "new-parens": 2, 45 | "no-mixed-spaces-and-tabs": 2, 46 | "no-multiple-empty-lines": [1, {"max": 1}], 47 | "no-nested-ternary": 2, 48 | "no-new-object": 2, 49 | "no-spaced-func": 2, 50 | "arrow-spacing": [2, {"before": true, "after": true}], 51 | "operator-assignment": [2, "always"], 52 | "operator-linebreak": [2, "after"], 53 | "padded-blocks": [2, "never"], 54 | "space-after-keywords": [2, "always"], 55 | "space-before-blocks": [2, "always"], 56 | "space-before-function-paren": [2, "never"], 57 | "object-curly-spacing": [2, "always"], 58 | "array-bracket-spacing": [2, "always"], 59 | "computed-property-spacing": [2, "never"], 60 | "space-in-parens": [2, "never"], 61 | "space-infix-ops": [2, {"int32Hint": true}], 62 | "space-return-throw-case": 2, 63 | "space-unary-ops": [2, { "words": true, "nonwords": false }], 64 | "no-delete-var": 2, 65 | "no-underscore-dangle": 0, 66 | "no-shadow": 2, 67 | "no-shadow-restricted-names": 2, 68 | "no-undef-init": 2, 69 | "no-undef": 2, 70 | "no-undefined": 2, 71 | "no-unused-vars": [2, { 72 | "vars": "all", 73 | "args": "after-used", 74 | "varsIgnorePattern": "hJSX" 75 | }], 76 | "no-use-before-define": 2, 77 | "yoda": [2, "never"], 78 | "consistent-return": 2, 79 | "spaced-line-comment": 0, 80 | "strict": [2, "never"], 81 | "eqeqeq": 2, 82 | "guard-for-in": 2, 83 | "no-alert": 2, 84 | "no-caller": 2, 85 | "no-labels": 2, 86 | "no-eval": 2, 87 | "no-fallthrough": 2, 88 | "default-case": 2, 89 | "no-iterator": 2, 90 | "no-loop-func": 2, 91 | "no-multi-spaces": 2, 92 | "no-multi-str": 2, 93 | "no-new": 2, 94 | "no-param-reassign": 2, 95 | "no-proto": 2, 96 | "no-redeclare": 2, 97 | "no-return-assign": 2, 98 | "no-self-compare": 2, 99 | "no-sequences": 2, 100 | "no-throw-literal": 2, 101 | "no-unused-expressions": 2, 102 | "no-with": 2, 103 | "vars-on-top": 0, 104 | "wrap-iife": [2, "outside"], 105 | "valid-typeof": 2, 106 | "max-statements": [1, 30], 107 | "max-params": [1, 6], 108 | "no-var": 2, 109 | "no-unexpected-multiline": 2, 110 | "dot-location": [2, "property"], 111 | "no-unreachable": 2, 112 | "no-negated-in-lhs": 2, 113 | "no-irregular-whitespace": 2, 114 | "no-invalid-regexp": 2, 115 | "no-func-assign": 2, 116 | "no-extra-semi": 2, 117 | "no-extra-boolean-cast": 2, 118 | "no-empty": 2, 119 | "no-duplicate-case": 2, 120 | "no-dupe-keys": 2, 121 | "no-dupe-args": 2, 122 | "no-constant-condition": 2, 123 | "no-cond-assign": 2, 124 | "comma-style": [2, "last"], 125 | "eol-last": 2, 126 | "no-lonely-if": 2 127 | }, 128 | "env": { 129 | "es6": true, 130 | "browser": true 131 | }, 132 | "extends": "eslint:recommended", 133 | "ecmaFeatures": { 134 | "experimentalObjectRestSpread": true 135 | }, 136 | "parser": "babel-eslint" 137 | } -------------------------------------------------------------------------------- /test/react-test.js: -------------------------------------------------------------------------------- 1 | const assert = require('assert'); 2 | const React = require('react'); 3 | const helpers = require('../dist/index')(React.createElement); 4 | const div = helpers.div; 5 | const jsc = require('jsverify'); 6 | const _ = require('lodash'); 7 | const tagNames = require('html-tag-names'); 8 | 9 | describe('div', function(){ 10 | jsc.property('div() ≡ React.createElement("div")', function(){ 11 | return _.isEqual(React.createElement('div').nodeName, div().nodeName); 12 | }); 13 | 14 | jsc.property('div(attrs) ≡ React.createElement("div", attrs)', "dict string", function(attrs){ 15 | const hr = React.createElement('div', attrs); 16 | const divr = div(attrs); 17 | return jsc.utils.isApproxEqual(hr, divr); 18 | }); 19 | 20 | jsc.property('div(children) ≡ React.createElement("div", children)', "array string", function (children) { 21 | const hr = React.createElement('div', children); 22 | const divr = div(children); 23 | return jsc.utils.isApproxEqual(hr, divr); 24 | }); 25 | 26 | jsc.property('div(attrs, children) ≡ React.createElement("div", attrs, children)', "dict string", "array string", function(attrs, children) { 27 | const hr = React.createElement('div', attrs, children); 28 | const divr = div(attrs, children); 29 | return jsc.utils.isApproxEqual(hr, divr); 30 | }); 31 | }); 32 | 33 | var tagArb = jsc.elements(tagNames); 34 | 35 | describe('arbitrary tag', function(){ 36 | jsc.property('tag() ≡ React.createElement("tag")', tagArb, function(tag){ 37 | return _.isEqual(React.createElement(tag).nodeName, helpers[tag]().nodeName); 38 | }); 39 | 40 | jsc.property('div(attrs) ≡ React.createElement("div", attrs)', tagArb, "dict string", function(tag, attrs){ 41 | const hr = React.createElement(tag, attrs); 42 | const divr = helpers[tag](attrs); 43 | return jsc.utils.isApproxEqual(hr, divr); 44 | }); 45 | 46 | jsc.property('div(children) ≡ React.createElement("div", children)', tagArb, "array string", function (tag, children) { 47 | const hr = React.createElement(tag, children); 48 | const divr = helpers[tag](children); 49 | return jsc.utils.isApproxEqual(hr, divr); 50 | }); 51 | 52 | jsc.property('div(attrs, children) ≡ React.createElement("div", attrs, children)', tagArb, "dict string", "array string", function(tag, attrs, children) { 53 | const hr = React.createElement(tag, attrs, children); 54 | const divr = helpers[tag](attrs, children); 55 | return jsc.utils.isApproxEqual(hr, divr); 56 | }); 57 | }); 58 | 59 | describe('isSelector', function() { 60 | jsc.property('isSelector(".") ≡ true', "string", function(string) { 61 | return helpers.isSelector('.' + string); 62 | }); 63 | 64 | jsc.property('isSelector("#") ≡ true', "string", function(string) { 65 | return helpers.isSelector('#' + string); 66 | }); 67 | 68 | jsc.property('isSelector("^[.#]") ≡ false', "nestring", function(string) { 69 | const startingWith = 70 | string.indexOf('.') === 0 71 | || string.indexOf('#') === 0; 72 | return startingWith || !helpers.isSelector(string); 73 | }); 74 | }); 75 | 76 | var selChars = jsc.elements(['.', '#']); 77 | 78 | describe('arbitrary selector', function() { 79 | jsc.property('div(".class") ≡ React.createElement("div.class")', "nestring", function(className) { 80 | const selector = '.' + className; 81 | const hr = React.createElement('div' + selector); 82 | const divr = div(selector); 83 | return jsc.utils.isApproxEqual(hr, divr); 84 | }); 85 | 86 | jsc.property('div("#id") ≡ React.createElement("div#id")', "nestring", function(id) { 87 | const selector = '#' + id; 88 | const hr = React.createElement('div' + selector); 89 | const divr = div(selector); 90 | return jsc.utils.isApproxEqual(hr, divr); 91 | }); 92 | 93 | jsc.property('div("[.#]foo", children) ≡ React.createElement("div[.#]id", children)', selChars, "nestring", "array string", function(selChar, id, children) { 94 | const selector = selChar + id; 95 | const hr = React.createElement('div' + selector, children); 96 | const divr = div(selector, children); 97 | return jsc.utils.isApproxEqual(hr, divr); 98 | }); 99 | 100 | jsc.property('div("[.#]foo", attrs, children) ≡ React.createElement("div[.#]id", attrs, children)', selChars, "nestring", "dict string", "array string", function(selChar, id, attrs, children) { 101 | const selector = selChar + id; 102 | const hr = React.createElement('div' + selector, attrs, children); 103 | const divr = div(selector, attrs, children); 104 | return jsc.utils.isApproxEqual(hr, divr); 105 | }); 106 | }); 107 | 108 | // TODO: in jsverify 0.7.x there will be helpers to do recursive definitions (easily) 109 | -------------------------------------------------------------------------------- /dist/index.d.ts: -------------------------------------------------------------------------------- 1 | declare interface HyperScriptHelperFn { 2 | (selector?: any, properties?: any, children?: any): any; 3 | } 4 | 5 | declare type HyperScriptHelpers = { 6 | a: HyperScriptHelperFn; 7 | abbr: HyperScriptHelperFn; 8 | acronym: HyperScriptHelperFn; 9 | address: HyperScriptHelperFn; 10 | applet: HyperScriptHelperFn; 11 | area: HyperScriptHelperFn; 12 | article: HyperScriptHelperFn; 13 | aside: HyperScriptHelperFn; 14 | audio: HyperScriptHelperFn; 15 | b: HyperScriptHelperFn; 16 | base: HyperScriptHelperFn; 17 | basefont: HyperScriptHelperFn; 18 | bdi: HyperScriptHelperFn; 19 | bdo: HyperScriptHelperFn; 20 | bgsound: HyperScriptHelperFn; 21 | big: HyperScriptHelperFn; 22 | blink: HyperScriptHelperFn; 23 | blockquote: HyperScriptHelperFn; 24 | body: HyperScriptHelperFn; 25 | br: HyperScriptHelperFn; 26 | button: HyperScriptHelperFn; 27 | canvas: HyperScriptHelperFn; 28 | caption: HyperScriptHelperFn; 29 | center: HyperScriptHelperFn; 30 | cite: HyperScriptHelperFn; 31 | code: HyperScriptHelperFn; 32 | col: HyperScriptHelperFn; 33 | colgroup: HyperScriptHelperFn; 34 | command: HyperScriptHelperFn; 35 | content: HyperScriptHelperFn; 36 | data: HyperScriptHelperFn; 37 | datalist: HyperScriptHelperFn; 38 | dd: HyperScriptHelperFn; 39 | del: HyperScriptHelperFn; 40 | details: HyperScriptHelperFn; 41 | dfn: HyperScriptHelperFn; 42 | dialog: HyperScriptHelperFn; 43 | dir: HyperScriptHelperFn; 44 | div: HyperScriptHelperFn; 45 | dl: HyperScriptHelperFn; 46 | dt: HyperScriptHelperFn; 47 | element: HyperScriptHelperFn; 48 | em: HyperScriptHelperFn; 49 | embed: HyperScriptHelperFn; 50 | fieldset: HyperScriptHelperFn; 51 | figcaption: HyperScriptHelperFn; 52 | figure: HyperScriptHelperFn; 53 | font: HyperScriptHelperFn; 54 | footer: HyperScriptHelperFn; 55 | form: HyperScriptHelperFn; 56 | frame: HyperScriptHelperFn; 57 | frameset: HyperScriptHelperFn; 58 | h1: HyperScriptHelperFn; 59 | h2: HyperScriptHelperFn; 60 | h3: HyperScriptHelperFn; 61 | h4: HyperScriptHelperFn; 62 | h5: HyperScriptHelperFn; 63 | h6: HyperScriptHelperFn; 64 | head: HyperScriptHelperFn; 65 | header: HyperScriptHelperFn; 66 | hgroup: HyperScriptHelperFn; 67 | hr: HyperScriptHelperFn; 68 | html: HyperScriptHelperFn; 69 | i: HyperScriptHelperFn; 70 | iframe: HyperScriptHelperFn; 71 | image: HyperScriptHelperFn; 72 | img: HyperScriptHelperFn; 73 | input: HyperScriptHelperFn; 74 | ins: HyperScriptHelperFn; 75 | isindex: HyperScriptHelperFn; 76 | kbd: HyperScriptHelperFn; 77 | keygen: HyperScriptHelperFn; 78 | label: HyperScriptHelperFn; 79 | legend: HyperScriptHelperFn; 80 | li: HyperScriptHelperFn; 81 | link: HyperScriptHelperFn; 82 | listing: HyperScriptHelperFn; 83 | main: HyperScriptHelperFn; 84 | map: HyperScriptHelperFn; 85 | mark: HyperScriptHelperFn; 86 | marquee: HyperScriptHelperFn; 87 | math: HyperScriptHelperFn; 88 | menu: HyperScriptHelperFn; 89 | menuitem: HyperScriptHelperFn; 90 | meta: HyperScriptHelperFn; 91 | meter: HyperScriptHelperFn; 92 | multicol: HyperScriptHelperFn; 93 | nav: HyperScriptHelperFn; 94 | nextid: HyperScriptHelperFn; 95 | nobr: HyperScriptHelperFn; 96 | noembed: HyperScriptHelperFn; 97 | noframes: HyperScriptHelperFn; 98 | noscript: HyperScriptHelperFn; 99 | object: HyperScriptHelperFn; 100 | ol: HyperScriptHelperFn; 101 | optgroup: HyperScriptHelperFn; 102 | option: HyperScriptHelperFn; 103 | output: HyperScriptHelperFn; 104 | p: HyperScriptHelperFn; 105 | param: HyperScriptHelperFn; 106 | picture: HyperScriptHelperFn; 107 | plaintext: HyperScriptHelperFn; 108 | pre: HyperScriptHelperFn; 109 | progress: HyperScriptHelperFn; 110 | q: HyperScriptHelperFn; 111 | rb: HyperScriptHelperFn; 112 | rbc: HyperScriptHelperFn; 113 | rp: HyperScriptHelperFn; 114 | rt: HyperScriptHelperFn; 115 | rtc: HyperScriptHelperFn; 116 | ruby: HyperScriptHelperFn; 117 | s: HyperScriptHelperFn; 118 | samp: HyperScriptHelperFn; 119 | script: HyperScriptHelperFn; 120 | section: HyperScriptHelperFn; 121 | select: HyperScriptHelperFn; 122 | shadow: HyperScriptHelperFn; 123 | slot: HyperScriptHelperFn; 124 | small: HyperScriptHelperFn; 125 | source: HyperScriptHelperFn; 126 | spacer: HyperScriptHelperFn; 127 | span: HyperScriptHelperFn; 128 | strike: HyperScriptHelperFn; 129 | strong: HyperScriptHelperFn; 130 | style: HyperScriptHelperFn; 131 | sub: HyperScriptHelperFn; 132 | summary: HyperScriptHelperFn; 133 | sup: HyperScriptHelperFn; 134 | svg: HyperScriptHelperFn; 135 | table: HyperScriptHelperFn; 136 | tbody: HyperScriptHelperFn; 137 | td: HyperScriptHelperFn; 138 | template: HyperScriptHelperFn; 139 | textarea: HyperScriptHelperFn; 140 | tfoot: HyperScriptHelperFn; 141 | th: HyperScriptHelperFn; 142 | thead: HyperScriptHelperFn; 143 | time: HyperScriptHelperFn; 144 | title: HyperScriptHelperFn; 145 | tr: HyperScriptHelperFn; 146 | track: HyperScriptHelperFn; 147 | tt: HyperScriptHelperFn; 148 | u: HyperScriptHelperFn; 149 | ul: HyperScriptHelperFn; 150 | var: HyperScriptHelperFn; 151 | video: HyperScriptHelperFn; 152 | wbr: HyperScriptHelperFn; 153 | xmp: HyperScriptHelperFn; 154 | } 155 | 156 | export default function hh(h: Function): HyperScriptHelpers; 157 | -------------------------------------------------------------------------------- /test/test.js: -------------------------------------------------------------------------------- 1 | const assert = require('assert'); 2 | const h = require('hyperscript'); 3 | const helpers = require('../dist/index')(h); 4 | const div = helpers.div; 5 | const jsc = require('jsverify'); 6 | const _ = require('lodash'); 7 | const tagNames = require('html-tag-names'); 8 | 9 | describe('div', function(){ 10 | jsc.property('div() ≡ h("div")', function(){ 11 | return _.isEqual(h('div').nodeName, div().nodeName); 12 | }); 13 | 14 | jsc.property('Div() ≡ div()', function(){ 15 | return _.isEqual(div().nodeName, helpers.Div().nodeName); 16 | }); 17 | 18 | jsc.property('div(attrs) ≡ h("div", attrs)', "dict string", function(attrs){ 19 | const hr = h('div', attrs); 20 | const divr = div(attrs); 21 | return jsc.utils.isApproxEqual(hr, divr); 22 | }); 23 | 24 | jsc.property('div(children) ≡ h("div", children)', "array string", function (children) { 25 | const hr = h('div', children); 26 | const divr = div(children); 27 | return jsc.utils.isApproxEqual(hr, divr); 28 | }); 29 | 30 | jsc.property('div(attrs, children) ≡ h("div", attrs, children)', "dict string", "array string", function(attrs, children) { 31 | const hr = h('div', attrs, children); 32 | const divr = div(attrs, children); 33 | return jsc.utils.isApproxEqual(hr, divr); 34 | }); 35 | }); 36 | 37 | var tagArb = jsc.elements(tagNames); 38 | 39 | describe('arbitrary tag', function(){ 40 | jsc.property('tag() ≡ h("tag")', tagArb, function(tag){ 41 | return _.isEqual(h(tag).nodeName, helpers[tag]().nodeName); 42 | }); 43 | 44 | jsc.property('Tag() ≡ tag()', tagArb, function(tag){ 45 | const Tag = tag.charAt(0).toUpperCase() + tag.slice(1) 46 | return _.isEqual(helpers[tag]().nodeName, helpers[Tag]().nodeName); 47 | }); 48 | 49 | jsc.property('div(attrs) ≡ h("div", attrs)', tagArb, "dict string", function(tag, attrs){ 50 | const hr = h(tag, attrs); 51 | const divr = helpers[tag](attrs); 52 | return jsc.utils.isApproxEqual(hr, divr); 53 | }); 54 | 55 | jsc.property('div(children) ≡ h("div", children)', tagArb, "array string", function (tag, children) { 56 | const hr = h(tag, children); 57 | const divr = helpers[tag](children); 58 | return jsc.utils.isApproxEqual(hr, divr); 59 | }); 60 | 61 | jsc.property('div(attrs, children) ≡ h("div", attrs, children)', tagArb, "dict string", "array string", function(tag, attrs, children) { 62 | const hr = h(tag, attrs, children); 63 | const divr = helpers[tag](attrs, children); 64 | return jsc.utils.isApproxEqual(hr, divr); 65 | }); 66 | }); 67 | 68 | var strRandom = function(size) { 69 | return _.times(size, () => 'abcdefghijklmnopqrstuvwxyz'[_.random(25)]).join(''); 70 | }; 71 | 72 | var tagCustom = jsc.number(1, 20).smap(function(size) { 73 | return strRandom(size) + '-' + strRandom(size); 74 | }); 75 | 76 | describe('custom tag', function(){ 77 | jsc.property('tag() ≡ h("tag")', tagCustom, function(tagName){ 78 | return _.isEqual(h(tagName).nodeName, helpers.createTag(tagName)().nodeName); 79 | }); 80 | 81 | jsc.property('div(attrs) ≡ h("div", attrs)', tagCustom, "dict string", function(tagName, attrs){ 82 | const hr = h(tagName, attrs); 83 | const divr = helpers.createTag(tagName)(attrs); 84 | return jsc.utils.isApproxEqual(hr, divr); 85 | }); 86 | 87 | jsc.property('div(children) ≡ h("div", children)', tagCustom, "array string", function (tagName, children){ 88 | const hr = h(tagName, children); 89 | const divr = helpers.createTag(tagName)(children); 90 | return jsc.utils.isApproxEqual(hr, divr); 91 | }); 92 | 93 | jsc.property('div(attrs, children) ≡ h("div", attrs, children)', tagCustom, "dict string", "array string", function(tagName, attrs, children) { 94 | const hr = h(tagName, attrs, children); 95 | const divr = helpers.createTag(tagName)(attrs, children); 96 | return jsc.utils.isApproxEqual(hr, divr); 97 | }); 98 | }); 99 | 100 | 101 | describe('isSelector', function() { 102 | jsc.property('isSelector(".") ≡ true', "string", function(string) { 103 | return helpers.isSelector('.' + string); 104 | }); 105 | 106 | jsc.property('isSelector("#") ≡ true', "string", function(string) { 107 | return helpers.isSelector('#' + string); 108 | }); 109 | 110 | jsc.property('isSelector("^[.#]") ≡ false', "nestring", function(string) { 111 | const startingWith = 112 | string.indexOf('.') === 0 113 | || string.indexOf('#') === 0; 114 | return startingWith || !helpers.isSelector(string); 115 | }); 116 | }); 117 | 118 | var selChars = jsc.elements(['.', '#']); 119 | 120 | describe('arbitrary selector', function() { 121 | jsc.property('div(".class") ≡ h("div.class")', "nestring", function(className) { 122 | const selector = '.' + className; 123 | const hr = h('div' + selector); 124 | const divr = div(selector); 125 | return jsc.utils.isApproxEqual(hr, divr); 126 | }); 127 | 128 | jsc.property('div("#id") ≡ h("div#id")', "nestring", function(id) { 129 | const selector = '#' + id; 130 | const hr = h('div' + selector); 131 | const divr = div(selector); 132 | return jsc.utils.isApproxEqual(hr, divr); 133 | }); 134 | 135 | jsc.property('div("[.#]foo", children) ≡ h("div[.#]id", children)', selChars, "nestring", "array string", function(selChar, id, children) { 136 | const selector = selChar + id; 137 | const hr = h('div' + selector, children); 138 | const divr = div(selector, children); 139 | return jsc.utils.isApproxEqual(hr, divr); 140 | }); 141 | 142 | jsc.property('div("[.#]foo", attrs, children) ≡ h("div[.#]id", attrs, children)', selChars, "nestring", "dict string", "array string", function(selChar, id, attrs, children) { 143 | const selector = selChar + id; 144 | const hr = h('div' + selector, attrs, children); 145 | const divr = div(selector, attrs, children); 146 | return jsc.utils.isApproxEqual(hr, divr); 147 | }); 148 | }); 149 | 150 | // TODO: in jsverify 0.7.x there will be helpers to do recursive definitions (easily) 151 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # hyperscript-helpers 2 | 3 | ![](https://travis-ci.org/ohanhi/hyperscript-helpers.svg) 4 | 5 | Terse syntax for hyperscript. 6 | 7 | > Less than 50 lines of code, taking your hyperscripting to the next level. 8 | 9 | ## What is it 10 | 11 | **hyperscript-helpers** [elm-html](https://github.com/elm-lang/html/) inspired helpers for writing 12 | [hyperscript](https://github.com/dominictarr/hyperscript) or [virtual-hyperscript](https://github.com/Matt-Esch/virtual-dom/tree/master/virtual-hyperscript). 13 | 14 | They work with `React.createElement`, but there is also a feature-rich hyperscript library for React: 15 | [react-hyperscript](https://github.com/mlmorg/react-hyperscript). 16 | 17 | ```javascript 18 | // instead of writing 19 | h('div') 20 | 21 | // write 22 | div() 23 | 24 | // instead of writing 25 | h('section#main', mainContents) 26 | 27 | // write 28 | section('#main', mainContents) 29 | ``` 30 | 31 | ## hyperscript-helpers vs templates (including JSX) 32 | 33 | With **hyperscript-helpers**: 34 | 35 | * It's nice to use functional utilities like lodash, because it's just functions 36 | * You get errors if you misspell a tag name, because they are function names 37 | * You have a consistent syntax at all times, because markup is just functions 38 | * Also, it's just functions 39 | 40 | This is super helpful, especially when using **hyperscript-helpers** with [Cycle.js](http://cycle.js.org/)! 41 | 42 | See the supported `TAG_NAMES` here: [src/index.js](src/index.js). 43 | 44 | #### Example 45 | 46 | Suppose we have a list of menu items of: 47 | 48 | `{ title: String, id: Number }` 49 | 50 | and a function that returns attributes given an id: 51 | 52 | ```javascript 53 | function attrs(id) { 54 | return { draggable: "true", "data-id": id }; 55 | } 56 | ``` 57 | 58 | How would we render these in plain hyperscript, JSX or with the helpers? 59 | 60 | ```javascript 61 | // plain hyperscript 62 | h('ul#bestest-menu', items.map( item => 63 | h('li#item-'+item.id, attrs(item.id), item.title)) 64 | ); 65 | 66 | // JSX 67 |
    68 | {items.map( item => 69 |
  • {item.title}
  • 70 | )} 71 |
72 | 73 | // hyperscript-helpers 74 | ul('#bestest-menu', items.map( item => 75 | li('#item-'+item.id, attrs(item.id), item.title)) 76 | ); 77 | ``` 78 | 79 | ## How to use 80 | 81 | ``` 82 | npm install hyperscript-helpers 83 | ``` 84 | 85 | The **hyperscript-helpers** are hyperscript-agnostic, which means there are no dependencies. 86 | Instead, you need to pass the implementation when you import the helpers. 87 | 88 | Using ES6 :sparkling_heart: 89 | 90 | ```js 91 | const h = require('hyperscript'); // or 'virtual-hyperscript' 92 | const { div, span, h1 } = 93 | require('hyperscript-helpers')(h); // ← Notice the (h) 94 | ``` 95 | 96 | With React 97 | 98 | ```js 99 | // ✅ Preferred 100 | const h = require('react-hyperscript'); 101 | const React = require('react'); 102 | const { div, span, h1 } = 103 | require('hyperscript-helpers')(h); // ← Notice the (h) 104 | 105 | 106 | // Also works, but beware of the createElement API 107 | const React = require('react'); 108 | const { div, span, h1 } = 109 | require('hyperscript-helpers')(React.createElement); // ← Notice the (React.createElement) 110 | ``` 111 | 112 | Using ES5 113 | 114 | ```js 115 | var h = require('hyperscript'); // or 'virtual-hyperscript' 116 | var hh = require('hyperscript-helpers')(h); // ← Notice the (h) 117 | // to use the short syntax, you need to introduce them to the current scope 118 | var div = hh.div, 119 | span = hh.span, 120 | h1 = hh.h1; 121 | ``` 122 | 123 | Once that's done, you can go and use the terse syntax: 124 | 125 | ```js 126 | $ node 127 | ▸ const hh = require('hyperscript-helpers')(require('hyperscript')); 128 | ◂ undefined 129 | 130 | ▸ const { div, span, h1 } = hh; 131 | ◂ undefined 132 | 133 | ▸ span('😍').outerHTML 134 | ◂ '😍' 135 | 136 | ▸ h1({ 'data-id': 'headline-6.1.2' }, 'Structural Weaknesses').outerHTML 137 | ◂ '

Structural Weaknesses

' 138 | 139 | ▸ div('#with-proper-id.wrapper', [ h1('Heading'), span('Spanner') ]).outerHTML 140 | ◂ '

Heading

Spanner
' 141 | ``` 142 | 143 | It's also natively supported to spell the helper function names with an uppercase first 144 | letter, for example to avoid conflicts with existing variables or reserved 145 | JavaScript keywords: 146 | 147 | ```js 148 | ▸ const { Span, Var } = hh; 149 | ◂ undefined 150 | 151 | ▸ Span('😍').outerHTML 152 | ◂ '😍' 153 | 154 | ▸ Var('x').outerHTML 155 | ◂ 'x' 156 | ``` 157 | 158 | Creating custom HTML tag names can be done with the `createTag` function: 159 | 160 | ```js 161 | ▸ const someFn = hh.createTag('otherTag'); 162 | ◂ undefined 163 | 164 | ▸ someFn('bla').outerHTML 165 | ◂ 'bla' 166 | ``` 167 | 168 | ## API 169 | 170 | Because **hyperscript-helpers** are hyperscript-agnostic there is no "exact" API. 171 | But, just to give you a direction of what should be possible: 172 | 173 | ```js 174 | tagName(selector) 175 | tagName(attrs) 176 | tagName(children) 177 | tagName(attrs, children) 178 | tagName(selector, children) 179 | tagName(selector, attrs, children) 180 | ``` 181 | 182 | Where 183 | * `tagName` is a helper function named like the HTML element that it creates; **hyperscript-helpers** natively supports spelling the tag name with the first letter lowercase or uppercase. 184 | * `selector` is string, starting with "." or "#". 185 | * `attrs` is an object of attributes. 186 | * `children` is a hyperscript node, an array of hyperscript nodes, a string or an array of strings. 187 | 188 | **hyperscript-helpers** is a collection of wrapper functions, so the syntax of your exact hyperscript library 189 | (like [virtual-hyperscript](https://github.com/Matt-Esch/virtual-dom/tree/master/virtual-hyperscript)) still applies. 190 | 191 | For example, for multiple classes: 192 | 193 | ```js 194 | // ... with Matt-Esch/virtual-dom/.../virtual-hyperscript 195 | button({className: "btn btn-default"}); // ← separated by space! 196 | button(".btn.btn-default"); // ← separated by dot! 197 | ``` 198 | 199 | Other hyperscript libraries may have other syntax conventions. 200 | 201 | 202 | ## Potential issues 203 | 204 | ### Selector shortcut 205 | 206 | The selector shortcut (`div('.my-class')`) may cause unexpected results in some cases. Our suggestion is: 207 | 208 | **Whenever you use `tagName()` syntax and `` may be a string, 209 | starting with `.` (period) or `#` (number sign), wrap the argument in `[]`.** 210 | 211 | ```js 212 | // ✅ GOOD 213 | filenames.map(filename => span([filename])); // README.md.gitignore 214 | 215 | // ❌ BAD 216 | filenames.map(span); // README.md 217 | ``` 218 | 219 | As most hyperscript is written by hand, we decided keep this convenient shortcut despite the [issue](https://github.com/ohanhi/hyperscript-helpers/issues/6#issuecomment-162989208). 220 | 221 | 222 | ### Logic in class names 223 | 224 | If you need to apply logic rules for class generation, 225 | we recommend using libraries like [classnames](https://github.com/JedWatson/classnames) 226 | for making proper `{className: ...}` argument. 227 | 228 | Not recommended: 229 | ```js 230 | span(error ? ".error" : null); // ← may be a trap, because: 231 | span(error ? ".error" : null, {}, []); // ← this one is wrong 232 | ``` 233 | 234 | ## Tools 235 | 236 | [html-to-hyperscript.paqmind.com](http://html-to-hyperscript.paqmind.com) – webservice to convert HTML to hyperscript 237 | 238 | ## Contributing 239 | 240 | To get set up, simply clone the repository, navigate to the directory on your terminal 241 | and do the following: 242 | 243 | ```bash 244 | # install dependencies 245 | npm install 246 | 247 | # build the project 248 | npm start 249 | 250 | # run tests 251 | npm test 252 | 253 | # commit your changes with commitizen 254 | npm run commit 255 | # or "git cz", if you have commitizen in your PATH 256 | ``` 257 | 258 | The source code can be found under the `src` directory, and the built file is under `dist`. 259 | 260 | Tests are written with Mocha, using the awesome [JSVerify](http://jsverify.github.io/) library. 261 | 262 | --- 263 | 264 | **hyperscript-helpers** is brought to you by [@ohanhi](https://twitter.com/ohanhi/). 265 | 266 | License: MIT 267 | --------------------------------------------------------------------------------