├── .gitignore ├── .nvmrc ├── LICENSE ├── README.md ├── TODO.md ├── dist ├── _hyperscript.d.ts ├── _hyperscript.js ├── _hyperscript.min.js ├── _hyperscript.min.js.gz ├── bin │ └── node-hyperscript.js ├── deno-hyperscript.js ├── eventsource.d.ts ├── eventsource.js ├── ext │ └── tailwind.js ├── hdb.js ├── hdb.min.js ├── node-hyperscript.js ├── socket.js ├── template.js └── worker.js ├── jsconfig.json ├── package-lock.json ├── package.json ├── scripts └── www.js ├── src ├── _hyperscript.d.ts ├── _hyperscript.js ├── deno-hyperscript.js ├── eventsource.d.ts ├── eventsource.js ├── ext │ └── tailwind.js ├── hdb.js ├── node-hyperscript.js ├── socket.js ├── template.js └── worker.js ├── test ├── commands │ ├── add.js │ ├── append.js │ ├── async.js │ ├── call.js │ ├── default.js │ ├── fetch.js │ ├── fetch │ │ ├── response.txt │ │ ├── scratch.html │ │ └── server.py │ ├── hide.js │ ├── if.js │ ├── increment.js │ ├── js.js │ ├── log.js │ ├── make.js │ ├── measure.js │ ├── pick.js │ ├── pseudoCommand.js │ ├── put.js │ ├── remove.js │ ├── repeat.js │ ├── send.js │ ├── set.js │ ├── settle.js │ ├── show.js │ ├── take.js │ ├── tell.js │ ├── throw.js │ ├── toggle.js │ ├── transition.js │ ├── trigger.js │ ├── unlessModifier.js │ └── wait.js ├── core │ ├── api.js │ ├── bootstrap.js │ ├── parser.js │ ├── regressions.js │ ├── runtime.js │ ├── runtimeErrors.js │ ├── scoping.js │ ├── security.js │ ├── sourceInfo.js │ └── tokenizer.js ├── eventSource │ └── scratch.html ├── expressions │ ├── arrayIndex.js │ ├── arrayLiteral.js │ ├── asExpression.js │ ├── async.js │ ├── attributeRef.js │ ├── beep!.js │ ├── blockLiteral.js │ ├── boolean.js │ ├── classRef.js │ ├── closest.js │ ├── comparisonOperator.js │ ├── cookies.js │ ├── functionCalls.js │ ├── idRef.js │ ├── in.js │ ├── logicalOperator.js │ ├── mathOperator.js │ ├── no.js │ ├── not.js │ ├── null.js │ ├── numbers.js │ ├── objectLiteral.js │ ├── positionalExpression.js │ ├── possessiveExpression.js │ ├── propertyAccess.js │ ├── queryRef.js │ ├── relativePositionalExpression.js │ ├── some.js │ ├── stringPostfix.js │ ├── strings.js │ ├── styleRef.js │ ├── symbol.js │ └── typecheck.js ├── ext │ └── tailwind.js ├── features │ ├── behavior.js │ ├── def.js │ ├── init.js │ ├── js.js │ ├── on.js │ ├── set.js │ ├── socket.js │ └── worker.js ├── index.html ├── playground │ ├── scratch.html │ └── tailwinds.html ├── sockets │ ├── asyncio │ ├── json │ ├── scratch.html │ ├── server.py │ └── websockets ├── templates │ └── templates.js └── util │ └── util.js ├── tsconfig.json └── www ├── .eleventy.js ├── README.md ├── _build └── widgets.js ├── _data └── layout.json ├── _includes ├── commands_table.md ├── js_end.md └── layout.njk ├── a-fun-guide.md ├── clickedMessage.md ├── commands ├── add.md ├── append.md ├── async.md ├── beep.md ├── break.md ├── call.md ├── continue.md ├── decrement.md ├── default.md ├── fetch.md ├── go.md ├── halt.md ├── hide.md ├── if.md ├── increment.md ├── js.md ├── log.md ├── make.md ├── measure.md ├── pick.md ├── pseudo-commands.md ├── put.md ├── remove.md ├── render.md ├── repeat.md ├── return.md ├── send.md ├── set.md ├── settle.md ├── show.md ├── take.md ├── tell.md ├── throw.md ├── toggle.md ├── transition.md ├── trigger.md └── wait.md ├── comparison.md ├── cookbook.md ├── cookbook ├── 10-concat-two-strings.md ├── 20-indeterminate-checkbox.md ├── 30-fade-and-remove.md ├── 40-disable-btn-during-request.md ├── 50-disable-btn-during-request-all.md ├── 60-filter-a-group-of-elements.md ├── 70-drag-n-drop.md ├── 80-event-filtering.md └── 90-filter-table-rows.md ├── css ├── prism-htmx.css └── site.css ├── docs.md ├── expressions.md ├── expressions ├── as.md ├── async.md ├── attribute-ref.md ├── beep.md ├── block-literal.md ├── class-reference.md ├── closest.md ├── comparison-operator.md ├── cookies.md ├── id-reference.md ├── it.md ├── logical-operator.md ├── me.md ├── no.md ├── of.md ├── positional.md ├── possessive.md ├── query-reference.md ├── relative-positional.md ├── string.md ├── time-expression.md └── you.md ├── features ├── behavior.md ├── def.md ├── event-source.md ├── init.md ├── js.md ├── on.md ├── set.md ├── socket.md └── worker.md ├── hdb.md ├── img ├── debugging.gif ├── hdb-code.png ├── hdb-eval.png ├── hdb.png ├── hyperscript-cheatsheet.pdf ├── light_logo.png ├── topo.svg └── transparent_logo.png ├── index.md ├── js ├── _hyperscript.js ├── _hyperscript.min.js ├── _hyperscript_w9y.min.js ├── hdb.min.js ├── lib │ ├── core.js │ ├── hdb.js │ ├── web.js │ └── worker.js ├── prism-hyperscript.js └── worker.js ├── playground.html ├── playground ├── drag.html ├── goto.html ├── hide_remove.html ├── input_mirroring.html ├── modal.html ├── password_visibility.html └── playground.json ├── posts ├── 2021-02-28-hyperscript-0.0.4-is-released.md ├── 2021-03-06-hyperscript-0.0.5-is-released.md ├── 2021-03-16-hyperscript-0.0.6-is-released.md ├── 2021-03-21-hyperscript-0.0.8-is-released.md ├── 2021-04-05-hyperscript-0.0.9-is-released.md ├── 2021-04-06-async-transparency-in-practice.md ├── 2021-06-15-hyperscript-0.8-is-released.md ├── 2021-06-21-hyperscript-0.8.1-is-released.md ├── 2021-10-02-hyperscript-0.8.2-is-released.md ├── 2021-11-02-hyperscript-0.8.4-is-released.md ├── 2021-11-19-hyperscript-0.9-is-released.md ├── 2021-11-29-hyperscript-0.9.1-is-released.md ├── 2021-12-25-hyperscript-0.9.2-is-released.md ├── 2022-02-22-hyperscript-0.9.5-is-released.md ├── 2022-07-12-hyperscript-0.9.6-is-released.md ├── 2022-07-18-hyperscript-0.9.7-is-released.md ├── 2022-1-14-hyperscript-0.9.4-is-released.md ├── 2023-03-02-hyperscript-0.9.8-is-released.md ├── 2023-06-30-hyperscript-0.9.9-is-released.md ├── 2023-10-20-hyperscript-0.9.12-is-released.md ├── 2024-10-20-hyperscript-0.9.13-is-released.md └── 2025-02-01-hyperscript-0.9.14-is-released.md ├── reference.md ├── talk.md └── test ├── 0.9.14 ├── dist │ ├── _hyperscript.d.ts │ ├── _hyperscript.js │ ├── _hyperscript.min.js │ ├── _hyperscript.min.js.gz │ ├── bin │ │ └── node-hyperscript.js │ ├── deno-hyperscript.js │ ├── eventsource.d.ts │ ├── eventsource.js │ ├── ext │ │ └── tailwind.js │ ├── hdb.js │ ├── hdb.min.js │ ├── node-hyperscript.js │ ├── socket.js │ ├── template.js │ └── worker.js ├── node_modules │ ├── chai │ │ └── chai.js │ ├── mocha │ │ ├── mocha.css │ │ └── mocha.js │ └── sinon │ │ └── pkg │ │ └── sinon.js ├── src │ ├── _hyperscript.d.ts │ ├── _hyperscript.js │ ├── deno-hyperscript.js │ ├── eventsource.d.ts │ ├── eventsource.js │ ├── ext │ │ └── tailwind.js │ ├── hdb.js │ ├── node-hyperscript.js │ ├── socket.js │ ├── template.js │ └── worker.js └── test │ ├── commands │ ├── add.js │ ├── append.js │ ├── async.js │ ├── call.js │ ├── default.js │ ├── fetch.js │ ├── fetch │ │ ├── response.txt │ │ ├── scratch.html │ │ └── server.py │ ├── hide.js │ ├── if.js │ ├── increment.js │ ├── js.js │ ├── log.js │ ├── make.js │ ├── measure.js │ ├── pick.js │ ├── pseudoCommand.js │ ├── put.js │ ├── remove.js │ ├── repeat.js │ ├── send.js │ ├── set.js │ ├── settle.js │ ├── show.js │ ├── take.js │ ├── tell.js │ ├── throw.js │ ├── toggle.js │ ├── transition.js │ ├── trigger.js │ ├── unlessModifier.js │ └── wait.js │ ├── core │ ├── api.js │ ├── bootstrap.js │ ├── parser.js │ ├── regressions.js │ ├── runtime.js │ ├── runtimeErrors.js │ ├── scoping.js │ ├── security.js │ ├── sourceInfo.js │ └── tokenizer.js │ ├── eventSource │ └── scratch.html │ ├── expressions │ ├── arrayIndex.js │ ├── arrayLiteral.js │ ├── asExpression.js │ ├── async.js │ ├── attributeRef.js │ ├── beep!.js │ ├── blockLiteral.js │ ├── boolean.js │ ├── classRef.js │ ├── closest.js │ ├── comparisonOperator.js │ ├── cookies.js │ ├── functionCalls.js │ ├── idRef.js │ ├── in.js │ ├── logicalOperator.js │ ├── mathOperator.js │ ├── no.js │ ├── not.js │ ├── null.js │ ├── numbers.js │ ├── objectLiteral.js │ ├── positionalExpression.js │ ├── possessiveExpression.js │ ├── propertyAccess.js │ ├── queryRef.js │ ├── relativePositionalExpression.js │ ├── some.js │ ├── stringPostfix.js │ ├── strings.js │ ├── styleRef.js │ ├── symbol.js │ └── typecheck.js │ ├── ext │ └── tailwind.js │ ├── features │ ├── behavior.js │ ├── def.js │ ├── init.js │ ├── js.js │ ├── on.js │ ├── set.js │ ├── socket.js │ └── worker.js │ ├── index.html │ ├── playground │ ├── scratch.html │ └── tailwinds.html │ ├── sockets │ ├── asyncio │ ├── json │ ├── scratch.html │ ├── server.py │ └── websockets │ ├── templates │ └── templates.js │ └── util │ └── util.js └── index.html /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | /node_modules 3 | _site 4 | /HyperTalk Reference 2.4.pdf 5 | .DS_Store 6 | *~ 7 | *.icloud 8 | -------------------------------------------------------------------------------- /.nvmrc: -------------------------------------------------------------------------------- 1 | v14.20.0 2 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Zero-Clause BSD 2 | ============= 3 | 4 | Permission to use, copy, modify, and/or distribute this software for 5 | any purpose with or without fee is hereby granted. 6 | 7 | THE SOFTWARE IS PROVIDED “AS IS” AND THE AUTHOR DISCLAIMS ALL 8 | WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES 9 | OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE 10 | FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY 11 | DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN 12 | AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT 13 | OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 14 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![//_hyperscript](https://hyperscript.org/img/light_logo.png "the underscore is silent")](https://hyperscript.org) 2 | 3 | *the underscore is silent* 4 | 5 | ## introduction 6 | 7 | `_hyperscript` is a small, open scripting language inspired by [hypertalk](https://hypercard.org/HyperTalk%20Reference%202.4.pdf) 8 | 9 | it is a companion project of 10 | 11 | ## quickstart 12 | 13 | ```html 14 | 15 | 16 | 17 | 18 | 21 | 22 | 23 |
24 |
25 | 26 | 31 | ``` 32 | 33 | ## website & docs 34 | 35 | * 36 | * 37 | 38 | ## contributing 39 | 40 | * please include test cases in [`/test`](https://github.com/bigskysoftware/_hyperscript/tree/dev/test) and docs in [`/www`](https://github.com/bigskysoftware/_hyperscript/tree/dev/www) 41 | * you can run the test suite by viewing `test/index.html` in a browser. 42 | * development pull requests should be against the `dev` branch, docs fixes can be made directly against `master` 43 | * you can build _hyperscript as shown: `npm run dist`. building is not necessary during development to run tests. 44 | -------------------------------------------------------------------------------- /dist/_hyperscript.min.js.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bigskysoftware/_hyperscript/8c4d36a98f14a91540ee5dc2c7b197ca2a7134fd/dist/_hyperscript.min.js.gz -------------------------------------------------------------------------------- /dist/bin/node-hyperscript.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | const _hyperscript = require('../_hyperscript.js') 4 | const fs = require('fs'); 5 | const path = require('path') 6 | 7 | /** 8 | * File extension for _hyperscript files 9 | */ 10 | const hsExt = '._hs'; 11 | 12 | global.require = require; // Allow importing modules from within hyperscript 13 | 14 | /** 15 | * 16 | * @param {String} modulePath 17 | */ 18 | function run(modulePath) { 19 | modulePath = path.resolve(modulePath); 20 | const args = { module: { dir: path.dirname(modulePath), id: modulePath } } 21 | return fs.promises.readFile(modulePath, { encoding: 'utf-8' }) 22 | .then(code => _hyperscript.evaluate(code, {}, args)) 23 | .catch(e => console.error("Cannot execute file: ", e)); 24 | } 25 | 26 | _hyperscript.addFeature('require', (parser, runtime, tokens) => { 27 | if (!tokens.matchToken('require')) return; 28 | /** @type {string} */ 29 | let id = parser.requireElement('nakedString', tokens) 30 | // @ts-ignore 31 | .evaluate({}); 32 | 33 | let name = id; 34 | if (tokens.matchToken('as')) { 35 | name = tokens.requireTokenType('IDENTIFIER').value; 36 | } else { 37 | name = path.basename(id) 38 | .replace(/\.[^\.]*$/, '') // remove extension 39 | } 40 | 41 | return { 42 | install(target, source, args) { 43 | if (id.startsWith('./') || id.startsWith('../')) { 44 | id = path.join(args.module.dir, id); 45 | } 46 | 47 | let mod; 48 | if (id.endsWith(hsExt)) mod = run(id); 49 | if (fs.existsSync(id + hsExt)) mod = run(id + hsExt); 50 | else mod = require(id); 51 | runtime.assignToNamespace(target, [], name, mod); 52 | //console.log(id, name, mod.toString(), target.hyperscriptFeatures); 53 | } 54 | } 55 | }) 56 | 57 | run(process.argv[2]) 58 | -------------------------------------------------------------------------------- /dist/eventsource.d.ts: -------------------------------------------------------------------------------- 1 | // QUESTION: Is it OK to pack additional data into the "Feature" struct that's returned? 2 | // TODO: Add methods for EventSourceFeature.connect() and EventSourceFeature.close() 3 | 4 | interface EventSourceFeature { 5 | name: string 6 | object: EventSourceStub 7 | install: () => void 8 | } 9 | 10 | interface EventSourceStub { 11 | eventSource: EventSource 12 | listeners: EventListenerEntry[] 13 | retryCount: number 14 | open: (url?:string) => void 15 | close: () => void 16 | addEventListener: (type: keyof HTMLElementEventMap, listener:(event: Event) => any, options?: boolean | AddEventListenerOptions) => void 17 | } 18 | 19 | interface EventListenerEntry { 20 | type: string 21 | handler: EventHandlerNonNull 22 | options?: any 23 | } 24 | -------------------------------------------------------------------------------- /dist/ext/tailwind.js: -------------------------------------------------------------------------------- 1 | _hyperscript.config.hideShowStrategies = { 2 | twDisplay: function (op, element, arg) { 3 | if (op === "toggle") { 4 | if (element.classList.contains("hidden")) { 5 | _hyperscript.config.hideShowStrategies.twDisplay("show", element, arg); 6 | } else { 7 | _hyperscript.config.hideShowStrategies.twDisplay("hide", element, arg); 8 | } 9 | } else if (op === "hide") { 10 | element.classList.add('hidden'); 11 | } else { 12 | element.classList.remove('hidden'); 13 | } 14 | }, 15 | 16 | twVisibility: function (op, element, arg) { 17 | if (op === "toggle") { 18 | if (element.classList.contains("invisible")) { 19 | _hyperscript.config.hideShowStrategies.twVisibility("show", element, arg); 20 | } else { 21 | _hyperscript.config.hideShowStrategies.twVisibility("hide", element, arg); 22 | } 23 | } else if (op === "hide") { 24 | element.classList.add('invisible'); 25 | } else { 26 | element.classList.remove('invisible'); 27 | } 28 | }, 29 | 30 | twOpacity: function (op, element, arg) { 31 | if (op === "toggle") { 32 | if (element.classList.contains("opacity-0")) { 33 | _hyperscript.config.hideShowStrategies.twOpacity("show", element, arg); 34 | } else { 35 | _hyperscript.config.hideShowStrategies.twOpacity("hide", element, arg); 36 | } 37 | } else if (op === "hide") { 38 | element.classList.add('opacity-0'); 39 | } else { 40 | element.classList.remove('opacity-0'); 41 | } 42 | } 43 | }; -------------------------------------------------------------------------------- /dist/node-hyperscript.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | const _hyperscript = require('./_hyperscript.js') 4 | const fs = require('fs'); 5 | const path = require('path') 6 | 7 | /** 8 | * File extension for _hyperscript files 9 | */ 10 | const hsExt = '._hs'; 11 | 12 | global.require = require; // Allow importing modules from within hyperscript 13 | 14 | /** 15 | * 16 | * @param {String} modulePath 17 | */ 18 | function run(modulePath) { 19 | modulePath = path.resolve(modulePath); 20 | const args = { module: { dir: path.dirname(modulePath), id: modulePath } } 21 | return fs.promises.readFile(modulePath, { encoding: 'utf-8' }) 22 | .then(code => _hyperscript.evaluate(code, {}, args)) 23 | .catch(e => console.error("Cannot execute file: ", e)); 24 | } 25 | 26 | _hyperscript.addFeature('require', (parser, runtime, tokens) => { 27 | if (!tokens.matchToken('require')) return; 28 | /** @type {string} */ 29 | let id = parser.requireElement('nakedString', tokens) 30 | // @ts-ignore 31 | .evaluate({}); 32 | 33 | let name = id; 34 | if (tokens.matchToken('as')) { 35 | name = tokens.requireTokenType('IDENTIFIER').value; 36 | } else { 37 | name = path.basename(id) 38 | .replace(/\.[^\.]*$/, '') // remove extension 39 | } 40 | 41 | return { 42 | install(target, source, args) { 43 | if (id.startsWith('./') || id.startsWith('../')) { 44 | id = path.join(args.module.dir, id); 45 | } 46 | 47 | let mod; 48 | if (id.endsWith(hsExt)) mod = run(id); 49 | if (fs.existsSync(id + hsExt)) mod = run(id + hsExt); 50 | else mod = require(id); 51 | runtime.assignToNamespace(target, [], name, mod); 52 | //console.log(id, name, mod.toString(), target.hyperscriptFeatures); 53 | } 54 | } 55 | }) 56 | 57 | run(process.argv[2]) 58 | -------------------------------------------------------------------------------- /jsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "strict": false, 4 | "checkJs": true, 5 | "target": "ES2015", 6 | "module": "umd" 7 | }, 8 | "include": ["src/*"], 9 | "exclude": ["node_modules"] 10 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "hyperscript.org", 3 | "description": "a small scripting language for the web", 4 | "keywords": [ 5 | "scripting", 6 | "HTML" 7 | ], 8 | "version": "0.9.14", 9 | "homepage": "https://hyperscript.org/", 10 | "bugs": { 11 | "url": "https://github.com/bigskysoftware/_hyperscript/issues" 12 | }, 13 | "license": "BSD 2-Clause", 14 | "files": [ 15 | "LICENSE", 16 | "README.md", 17 | "src/*", 18 | "dist/*" 19 | ], 20 | "main": "dist/_hyperscript.min.js", 21 | "types": "dist/_hyperscript.d.ts", 22 | "bin": { 23 | "_hyperscript": "src/node-hyperscript.js" 24 | }, 25 | "scripts": { 26 | "test": "mocha-chrome test/index.html", 27 | "dist": "cp -r src/* dist/ && npm run-script terser && npm run-script typings && gzip -k -f dist/_hyperscript.min.js > dist/_hyperscript.min.js.gz && exit", 28 | "typings": "npx tsc --declaration dist/_hyperscript.js --allowJs --emitDeclarationOnly --skipLibCheck", 29 | "www": "node scripts/www.js", 30 | "terser": "terser -m eval dist/_hyperscript.js > dist/_hyperscript.min.js && terser -m eval dist/hdb.js > dist/hdb.min.js" 31 | }, 32 | "repository": { 33 | "type": "git", 34 | "url": "git+https://github.com/bigskysoftware/_hyperscript.git" 35 | }, 36 | "devDependencies": { 37 | "@11ty/eleventy": "^1.0.2", 38 | "chai": "^4.2.0", 39 | "fs-extra": "^9.0.0", 40 | "markdown-it": "^12.3.2", 41 | "markdown-it-anchor": "^8.4.1", 42 | "markdown-it-attrs": "^4.1.3", 43 | "markdown-it-table-of-contents": "^0.6.0", 44 | "mocha": "^7.1.1", 45 | "mocha-chrome": "^2.2.0", 46 | "sinon": "^9.0.2" 47 | }, 48 | "dependencies": { 49 | "markdown-it-deflist": "^2.1.0", 50 | "terser": "^5.14.1" 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /scripts/www.js: -------------------------------------------------------------------------------- 1 | var config = require('../package.json'); 2 | var fs = require('fs-extra'); 3 | 4 | var testRoot = "www/test/"; 5 | var currentReleaseRoot = testRoot + config.version; 6 | fs.ensureDirSync(currentReleaseRoot); 7 | fs.copySync("node_modules/mocha/mocha.js", currentReleaseRoot + "/node_modules/mocha/mocha.js"); 8 | fs.copySync("node_modules/mocha/mocha.css", currentReleaseRoot + "/node_modules/mocha/mocha.css"); 9 | fs.copySync("node_modules/chai/chai.js", currentReleaseRoot + "/node_modules/chai/chai.js"); 10 | fs.copySync("node_modules/sinon/pkg/sinon.js", currentReleaseRoot + "/node_modules/sinon/pkg/sinon.js"); 11 | fs.copySync("test/", currentReleaseRoot + "/test"); 12 | fs.copySync("src/", currentReleaseRoot + "/src"); 13 | fs.copySync("dist/", currentReleaseRoot + "/dist"); 14 | fs.copySync("dist/_hyperscript.min.js", "www/js/_hyperscript.min.js"); 15 | fs.copySync("dist/hdb.min.js", "www/js/hdb.min.js"); 16 | 17 | var testHTML = "

Hyperscript RELEASE TESTS

    \n" 18 | fs.readdirSync(testRoot).reverse().forEach(function(file){ 19 | if (file !== "index.html") { 20 | testHTML += "
  • " + file + "\n"; 21 | } 22 | }); 23 | testHTML += "
" 24 | fs.writeFileSync(testRoot + "/index.html", testHTML); 25 | -------------------------------------------------------------------------------- /src/_hyperscript.d.ts: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /src/eventsource.d.ts: -------------------------------------------------------------------------------- 1 | // QUESTION: Is it OK to pack additional data into the "Feature" struct that's returned? 2 | // TODO: Add methods for EventSourceFeature.connect() and EventSourceFeature.close() 3 | 4 | interface EventSourceFeature { 5 | name: string 6 | object: EventSourceStub 7 | install: () => void 8 | } 9 | 10 | interface EventSourceStub { 11 | eventSource: EventSource 12 | listeners: EventListenerEntry[] 13 | retryCount: number 14 | open: (url?:string) => void 15 | close: () => void 16 | addEventListener: (type: keyof HTMLElementEventMap, listener:(event: Event) => any, options?: boolean | AddEventListenerOptions) => void 17 | } 18 | 19 | interface EventListenerEntry { 20 | type: string 21 | handler: EventHandlerNonNull 22 | options?: any 23 | } 24 | -------------------------------------------------------------------------------- /src/ext/tailwind.js: -------------------------------------------------------------------------------- 1 | _hyperscript.config.hideShowStrategies = { 2 | twDisplay: function (op, element, arg) { 3 | if (op === "toggle") { 4 | if (element.classList.contains("hidden")) { 5 | _hyperscript.config.hideShowStrategies.twDisplay("show", element, arg); 6 | } else { 7 | _hyperscript.config.hideShowStrategies.twDisplay("hide", element, arg); 8 | } 9 | } else if (op === "hide") { 10 | element.classList.add('hidden'); 11 | } else { 12 | element.classList.remove('hidden'); 13 | } 14 | }, 15 | 16 | twVisibility: function (op, element, arg) { 17 | if (op === "toggle") { 18 | if (element.classList.contains("invisible")) { 19 | _hyperscript.config.hideShowStrategies.twVisibility("show", element, arg); 20 | } else { 21 | _hyperscript.config.hideShowStrategies.twVisibility("hide", element, arg); 22 | } 23 | } else if (op === "hide") { 24 | element.classList.add('invisible'); 25 | } else { 26 | element.classList.remove('invisible'); 27 | } 28 | }, 29 | 30 | twOpacity: function (op, element, arg) { 31 | if (op === "toggle") { 32 | if (element.classList.contains("opacity-0")) { 33 | _hyperscript.config.hideShowStrategies.twOpacity("show", element, arg); 34 | } else { 35 | _hyperscript.config.hideShowStrategies.twOpacity("hide", element, arg); 36 | } 37 | } else if (op === "hide") { 38 | element.classList.add('opacity-0'); 39 | } else { 40 | element.classList.remove('opacity-0'); 41 | } 42 | } 43 | }; -------------------------------------------------------------------------------- /src/node-hyperscript.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | const _hyperscript = require('./_hyperscript.js') 4 | const fs = require('fs'); 5 | const path = require('path') 6 | 7 | /** 8 | * File extension for _hyperscript files 9 | */ 10 | const hsExt = '._hs'; 11 | 12 | global.require = require; // Allow importing modules from within hyperscript 13 | 14 | /** 15 | * 16 | * @param {String} modulePath 17 | */ 18 | function run(modulePath) { 19 | modulePath = path.resolve(modulePath); 20 | const args = { module: { dir: path.dirname(modulePath), id: modulePath } } 21 | return fs.promises.readFile(modulePath, { encoding: 'utf-8' }) 22 | .then(code => _hyperscript.evaluate(code, {}, args)) 23 | .catch(e => console.error("Cannot execute file: ", e)); 24 | } 25 | 26 | _hyperscript.addFeature('require', (parser, runtime, tokens) => { 27 | if (!tokens.matchToken('require')) return; 28 | /** @type {string} */ 29 | let id = parser.requireElement('nakedString', tokens) 30 | // @ts-ignore 31 | .evaluate({}); 32 | 33 | let name = id; 34 | if (tokens.matchToken('as')) { 35 | name = tokens.requireTokenType('IDENTIFIER').value; 36 | } else { 37 | name = path.basename(id) 38 | .replace(/\.[^\.]*$/, '') // remove extension 39 | } 40 | 41 | return { 42 | install(target, source, args) { 43 | if (id.startsWith('./') || id.startsWith('../')) { 44 | id = path.join(args.module.dir, id); 45 | } 46 | 47 | let mod; 48 | if (id.endsWith(hsExt)) mod = run(id); 49 | if (fs.existsSync(id + hsExt)) mod = run(id + hsExt); 50 | else mod = require(id); 51 | runtime.assignToNamespace(target, [], name, mod); 52 | //console.log(id, name, mod.toString(), target.hyperscriptFeatures); 53 | } 54 | } 55 | }) 56 | 57 | run(process.argv[2]) 58 | -------------------------------------------------------------------------------- /test/commands/async.js: -------------------------------------------------------------------------------- 1 | describe("the async command", function () { 2 | beforeEach(function () { 3 | clearWorkArea(); 4 | }); 5 | afterEach(function () { 6 | clearWorkArea(); 7 | }); 8 | 9 | it("is async", function (done) { 10 | var div = make("
"); 11 | div.classList.contains("foo").should.equal(false); 12 | div.click(); 13 | div.classList.contains("foo").should.equal(false); 14 | setTimeout(function () { 15 | div.classList.contains("foo").should.equal(true); 16 | done(); 17 | }, 10); 18 | }); 19 | 20 | it("can trigger an event on the original element", function (done) { 21 | var div = make( 22 | "
" 29 | ); 30 | div.classList.contains("bar").should.equal(false); 31 | div.classList.contains("foo").should.equal(false); 32 | div.click(); 33 | div.classList.contains("bar").should.equal(true); 34 | div.classList.contains("foo").should.equal(false); 35 | setTimeout(function () { 36 | div.classList.contains("bar").should.equal(true); 37 | div.classList.contains("foo").should.equal(true); 38 | done(); 39 | }, 30); 40 | }); 41 | }); 42 | -------------------------------------------------------------------------------- /test/commands/default.js: -------------------------------------------------------------------------------- 1 | describe("the default command", function () { 2 | beforeEach(function () { 3 | clearWorkArea(); 4 | }); 5 | afterEach(function () { 6 | clearWorkArea(); 7 | }); 8 | 9 | it("can default variables", function () { 10 | var d1 = make("
"); 11 | d1.click(); 12 | d1.innerHTML.should.equal("foo"); 13 | }); 14 | 15 | it("can default attributes", function () { 16 | var d1 = make("
"); 17 | d1.click(); 18 | d1.innerHTML.should.equal("foo"); 19 | }); 20 | 21 | it("can default properties", function () { 22 | var d1 = make("
"); 23 | d1.click(); 24 | d1.innerHTML.should.equal("foo"); 25 | }); 26 | 27 | it("default variables respect existing values", function () { 28 | var d1 = make("
"); 29 | d1.click(); 30 | d1.innerHTML.should.equal("bar"); 31 | }); 32 | 33 | it("default attributes respect existing values", function () { 34 | var d1 = make("
"); 35 | d1.click(); 36 | d1.innerHTML.should.equal("bar"); 37 | }); 38 | 39 | it("default properties respect existing values", function () { 40 | var d1 = make( 41 | "
" 42 | ); 43 | d1.click(); 44 | d1.innerHTML.should.equal("bar"); 45 | }); 46 | }); 47 | -------------------------------------------------------------------------------- /test/commands/fetch/response.txt: -------------------------------------------------------------------------------- 1 | Fetch Response 2 | -------------------------------------------------------------------------------- /test/commands/fetch/scratch.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 7 | 8 | 9 | 14 | Fetch Timeout Tests 15 |
16 | 22 | 23 | 24 |
25 |

2 Second Wait...

26 | 29 |
30 | 31 |
32 |

2 Second Wait, abort at 100ms

33 | 37 |
38 | 39 |
40 |

5 Second Wait + Abort Button

41 | 50 | 53 |
54 | 55 |
56 |

Playground

57 |
58 | 62 |
63 | 67 |
68 | 69 | 70 | 71 | 72 | -------------------------------------------------------------------------------- /test/commands/fetch/server.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | from flask import Flask, request, make_response 4 | from time import sleep 5 | 6 | app = Flask(__name__) 7 | 8 | @app.route('/respond') 9 | def respond(): 10 | time_to_sleep = int(request.args.get('time')) / 1000 11 | sleep(time_to_sleep) 12 | resp = make_response('Response from Flask') 13 | resp.headers['Access-Control-Allow-Origin'] = '*' 14 | return resp 15 | 16 | @app.route('/request_type') 17 | def request_type(): 18 | resp = make_response('Request Type: ' + request.method) 19 | resp.headers['Access-Control-Allow-Origin'] = '*' 20 | return resp 21 | 22 | @app.route('/json') 23 | def json(): 24 | return {resp:'Hello JSON!'} 25 | 26 | # main driver function 27 | if __name__ == '__main__': 28 | app.run() 29 | -------------------------------------------------------------------------------- /test/commands/js.js: -------------------------------------------------------------------------------- 1 | describe("The (inline) js command", function () { 2 | beforeEach(function () { 3 | clearWorkArea(); 4 | }); 5 | afterEach(function () { 6 | clearWorkArea(); 7 | }); 8 | 9 | it("can run js", function () { 10 | window.testSuccess = false; 11 | var div = make('
'); 12 | div.click(); 13 | assert.equal(window.testSuccess, true); 14 | delete window.testSuccess; 15 | }); 16 | 17 | it("can deal with empty input list", function () { 18 | window.testSuccess = false; 19 | var div = make('
'); 20 | div.click(); 21 | assert.equal(window.testSuccess, true); 22 | delete window.testSuccess; 23 | }); 24 | 25 | it("can access values from _hyperscript", function () { 26 | window.testSuccess = false; 27 | var div = make("
"); 28 | div.click(); 29 | assert.equal(window.testSuccess, true); 30 | delete window.testSuccess; 31 | }); 32 | 33 | it("can return values to _hyperscript", function () { 34 | var div = make( 35 | "
' 36 | ); 37 | div.click(); 38 | div.innerHTML.should.equal("test success"); 39 | }); 40 | 41 | it("can do both of the above", function () { 42 | var div = make( 43 | '
' 46 | ); 47 | div.click(); 48 | div.innerHTML.should.equal("2"); 49 | }); 50 | }); 51 | -------------------------------------------------------------------------------- /test/commands/log.js: -------------------------------------------------------------------------------- 1 | describe("the log command", function () { 2 | beforeEach(function () { 3 | clearWorkArea(); 4 | }); 5 | afterEach(function () { 6 | clearWorkArea(); 7 | }); 8 | 9 | it("can log single item", function () { 10 | var d1 = make("
"); 11 | d1.click(); 12 | }); 13 | 14 | it("can log multiple items", function () { 15 | var d1 = make("
"); 16 | d1.click(); 17 | }); 18 | 19 | it("can log multiple items with debug", function () { 20 | var d1 = make("
"); 21 | d1.click(); 22 | }); 23 | 24 | it("can log multiple items with error", function () { 25 | var d1 = make("
"); 26 | d1.click(); 27 | }); 28 | }); 29 | -------------------------------------------------------------------------------- /test/commands/measure.js: -------------------------------------------------------------------------------- 1 | describe("the measure command", function () { 2 | it("can measure me", function () { 3 | var div = make( 4 | "
" 6 | ); 7 | div.click(); 8 | window.measurement.should.have.property("top"); 9 | Math.round(window.measurement.top).should.equal(89); 10 | delete window.measurement; 11 | }); 12 | 13 | it("can measure another element", function () { 14 | var div = make("
"); 15 | var measure = make("
"); 16 | measure.click(); 17 | window.measurement.should.have.property("top"); 18 | Math.round(window.measurement.top).should.equal(89); 19 | delete window.measurement; 20 | }); 21 | 22 | it("can assign measurements to locals", function () { 23 | var measure = make( 24 | "
" 26 | ); 27 | measure.click(); 28 | window.measurement.should.have.property("top").that.exist; 29 | window.measurement.should.have.property("left").that.exist; 30 | window.measurement.should.have.property("right").that.exist; 31 | window.measurement.should.have.property("bottom").that.exist; 32 | delete window.measurement; 33 | }); 34 | 35 | it("can measure all the supported properties", function () { 36 | var measure = make( 37 | "
" 38 | ); 39 | try { 40 | measure.click(); 41 | } catch (e) { 42 | fail("Should not have thrown"); 43 | } 44 | }); 45 | }); 46 | -------------------------------------------------------------------------------- /test/commands/settle.js: -------------------------------------------------------------------------------- 1 | describe("the settle command", function () { 2 | beforeEach(function () { 3 | clearWorkArea(); 4 | }); 5 | afterEach(function () { 6 | clearWorkArea(); 7 | }); 8 | 9 | it("can settle me no transition", function (done) { 10 | var d1 = make("
"); 11 | d1.click(); 12 | d1.classList.contains("foo").should.equal(false); 13 | setTimeout(function () { 14 | d1.classList.contains("foo").should.equal(true); 15 | done(); 16 | }, 1000); 17 | }); 18 | 19 | it("can settle target no transition", function (done) { 20 | var d1 = make("
"); 21 | var d2 = make("
"); 22 | d2.click(); 23 | d1.classList.contains("foo").should.equal(false); 24 | setTimeout(function () { 25 | d1.classList.contains("foo").should.equal(true); 26 | done(); 27 | }, 1000); 28 | }); 29 | }); 30 | -------------------------------------------------------------------------------- /test/commands/unlessModifier.js: -------------------------------------------------------------------------------- 1 | describe("the unless command modifier", function () { 2 | beforeEach(function () { 3 | clearWorkArea(); 4 | }); 5 | afterEach(function () { 6 | clearWorkArea(); 7 | }); 8 | 9 | it("unless modifier can conditionally execute a command", function () { 10 | var div = make("
"); 11 | 12 | div.classList.contains("foo").should.equal(false); 13 | div.click(); 14 | div.classList.contains("foo").should.equal(true); 15 | div.click(); 16 | div.classList.contains("foo").should.equal(false); 17 | 18 | div.classList.add("bar"); 19 | div.classList.contains("foo").should.equal(false); 20 | div.click(); 21 | div.classList.contains("foo").should.equal(false); 22 | div.click(); 23 | div.classList.contains("foo").should.equal(false); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /test/core/api.js: -------------------------------------------------------------------------------- 1 | describe("_hyperscript API", function () { 2 | beforeEach(function () { 3 | clearWorkArea(); 4 | }); 5 | afterEach(function () { 6 | clearWorkArea(); 7 | }); 8 | 9 | it("processNodes does not reinitialize a node already processed", function () { 10 | window.global_int = 0; 11 | var div = make("
"); 12 | window.global_int.should.equal(0); 13 | div.click(); 14 | window.global_int.should.equal(1); 15 | _hyperscript.processNode(div); 16 | div.click(); 17 | window.global_int.should.equal(2); 18 | delete window.global_int; 19 | }); 20 | }); 21 | -------------------------------------------------------------------------------- /test/core/security.js: -------------------------------------------------------------------------------- 1 | describe("security options", function () { 2 | beforeEach(function () { 3 | clearWorkArea(); 4 | }); 5 | afterEach(function () { 6 | clearWorkArea(); 7 | }); 8 | 9 | it("on a single div", function () { 10 | var div = make("
" + "
" + "
"); 11 | var innerDiv = byId("d1"); 12 | innerDiv.click(); 13 | innerDiv.classList.contains("foo").should.equal(false); 14 | }); 15 | }); 16 | -------------------------------------------------------------------------------- /test/core/sourceInfo.js: -------------------------------------------------------------------------------- 1 | describe("the line info parser", function () { 2 | beforeEach(function () { 3 | clearWorkArea(); 4 | }); 5 | afterEach(function () { 6 | clearWorkArea(); 7 | }); 8 | 9 | it("debug", function () { 10 | var elt = _hyperscript.parse(""); 11 | elt.sourceFor().should.equal(""); 12 | }); 13 | 14 | it("get source works for expressions", function () { 15 | var elt = _hyperscript.parse("1"); 16 | elt.sourceFor().should.equal("1"); 17 | 18 | elt = _hyperscript.parse("a.b"); 19 | elt.sourceFor().should.equal("a.b"); 20 | elt.root.sourceFor().should.equal("a"); 21 | 22 | elt = _hyperscript.parse("a.b()"); 23 | elt.sourceFor().should.equal("a.b()"); 24 | elt.root.sourceFor().should.equal("a.b"); 25 | elt.root.root.sourceFor().should.equal("a"); 26 | 27 | elt = _hyperscript.parse(""); 28 | elt.sourceFor().should.equal(""); 29 | 30 | elt = _hyperscript.parse("x + y"); 31 | elt.sourceFor().should.equal("x + y"); 32 | elt.lhs.sourceFor().should.equal("x"); 33 | elt.rhs.sourceFor().should.equal("y"); 34 | 35 | elt = _hyperscript.parse("'foo'"); 36 | elt.sourceFor().should.equal("'foo'"); 37 | 38 | elt = _hyperscript.parse(".foo"); 39 | elt.sourceFor().should.equal(".foo"); 40 | 41 | elt = _hyperscript.parse("#bar"); 42 | elt.sourceFor().should.equal("#bar"); 43 | }); 44 | 45 | it("get source works for statements", function () { 46 | var elt = _hyperscript.parse("if true log 'it was true'"); 47 | elt.sourceFor().should.equal("if true log 'it was true'"); 48 | 49 | var elt = _hyperscript.parse("for x in [1, 2, 3] log x then log x end"); 50 | elt.sourceFor().should.equal("for x in [1, 2, 3] log x then log x end"); 51 | }); 52 | 53 | it("get line works for statements", function () { 54 | var elt = _hyperscript.parse("if true\n log 'it was true'\n log 'it was true'"); 55 | elt.lineFor().should.equal("if true"); 56 | elt.trueBranch.lineFor().should.equal(" log 'it was true'"); 57 | elt.trueBranch.next.lineFor().should.equal(" log 'it was true'"); 58 | }); 59 | }); 60 | -------------------------------------------------------------------------------- /test/expressions/arrayLiteral.js: -------------------------------------------------------------------------------- 1 | describe("the arrayLiteral expression", function () { 2 | it("empty array literals work", function () { 3 | var result = evalHyperScript("[]"); 4 | result.should.deep.equal([]); 5 | }); 6 | 7 | it("one element array literal works", function () { 8 | var result = evalHyperScript("[true]"); 9 | result.should.deep.equal([true]); 10 | }); 11 | 12 | it("multi element array literal works", function () { 13 | var result = evalHyperScript("[true, false]"); 14 | result.should.deep.equal([true, false]); 15 | }); 16 | }); 17 | -------------------------------------------------------------------------------- /test/expressions/async.js: -------------------------------------------------------------------------------- 1 | describe("the async expression works", function () { 2 | it("simple async expression works", function () { 3 | var result = evalHyperScript("(async 1)"); 4 | result.value.should.deep.equal(1); 5 | }); 6 | 7 | it("async argument works w/ non-async value", function () { 8 | var val = null; 9 | window.func = function (x) { 10 | val = x; 11 | }; 12 | var result = evalHyperScript("func(async 1)"); 13 | val.should.equal(1); 14 | delete window.func; 15 | }); 16 | 17 | it("async argument works w/ async value", function (done) { 18 | var val = null; 19 | window.func = function (x) { 20 | val = x; 21 | }; 22 | var result = evalHyperScript("func(async promiseAnIntIn(10))"); 23 | val.then(function (i) { 24 | i.should.equal(42); 25 | delete window.func; 26 | done(); 27 | }); 28 | }); 29 | }); 30 | -------------------------------------------------------------------------------- /test/expressions/blockLiteral.js: -------------------------------------------------------------------------------- 1 | describe("the blockLiteral expression", function () { 2 | it("basic block literals work", function () { 3 | var result = evalHyperScript("\\-> true"); 4 | result().should.equal(true); 5 | }); 6 | 7 | it("basic identity works", function () { 8 | var result = evalHyperScript("\\ x -> x"); 9 | result(true).should.equal(true); 10 | }); 11 | 12 | it("basic two arg identity works", function () { 13 | var result = evalHyperScript("\\ x, y -> y"); 14 | result(false, true).should.equal(true); 15 | }); 16 | 17 | it("can map an array", function () { 18 | var result = evalHyperScript("['a', 'ab', 'abc'].map(\\ s -> s.length )"); 19 | result.should.deep.equal([1, 2, 3]); 20 | }); 21 | }); 22 | -------------------------------------------------------------------------------- /test/expressions/boolean.js: -------------------------------------------------------------------------------- 1 | describe("the boolean literal expression", function () { 2 | it("true boolean literals work", function () { 3 | var result = evalHyperScript("true"); 4 | result.should.equal(true); 5 | }); 6 | 7 | it("false boolean literals work", function () { 8 | var result = evalHyperScript("false"); 9 | result.should.equal(false); 10 | }); 11 | }); 12 | -------------------------------------------------------------------------------- /test/expressions/cookies.js: -------------------------------------------------------------------------------- 1 | describe("the cookies identifier", function () { 2 | beforeEach(function () { 3 | evalHyperScript("cookies.clearAll()"); 4 | }); 5 | afterEach(function () { 6 | evalHyperScript("cookies.clearAll()"); 7 | }); 8 | 9 | 10 | it("basic set cookie values work", function () { 11 | var result = evalHyperScript("cookies.foo"); 12 | should.equal(result, undefined); 13 | evalHyperScript("set cookies.foo to 'bar'"); 14 | result = evalHyperScript("cookies.foo"); 15 | result.should.equal('bar'); 16 | }); 17 | 18 | it("update cookie values work", function () { 19 | evalHyperScript("set cookies.foo to 'bar'"); 20 | var result = evalHyperScript("cookies.foo"); 21 | result.should.equal('bar'); 22 | evalHyperScript("set cookies.foo to 'doh'"); 23 | var result = evalHyperScript("cookies.foo"); 24 | result.should.equal('doh'); 25 | }); 26 | 27 | it("basic clear cookie values work", function () { 28 | evalHyperScript("set cookies.foo to 'bar'"); 29 | evalHyperScript("cookies.clear('foo')"); 30 | var result = evalHyperScript("cookies.foo"); 31 | should.equal(result, undefined); 32 | }); 33 | 34 | it("iterate cookies values work", function () { 35 | evalHyperScript("set cookies.foo to 'bar'"); 36 | let context = {me:[], you:[]}; // horrifying, but use arrays for me and you to capture values... 37 | evalHyperScript("for x in cookies me.push(x.name) then you.push(x.value) end", context); 38 | context.me.includes('foo').should.equal(true); 39 | context.you.includes('bar').should.equal(true); 40 | }); 41 | 42 | }); 43 | -------------------------------------------------------------------------------- /test/expressions/functionCalls.js: -------------------------------------------------------------------------------- 1 | describe("function call expressions", function () { 2 | it("can invoke global function", function () { 3 | window.identity = function (x) { 4 | return x; 5 | }; 6 | try { 7 | var result = evalHyperScript('identity("foo")'); 8 | result.should.equal("foo"); 9 | } finally { 10 | delete window.identity; 11 | } 12 | }); 13 | 14 | it("can invoke function on object", function () { 15 | window.obj = { 16 | value: "foo", 17 | getValue: function () { 18 | return this.value; 19 | }, 20 | }; 21 | try { 22 | var result = evalHyperScript("obj.getValue()"); 23 | result.should.equal("foo"); 24 | } finally { 25 | delete window.obj; 26 | } 27 | }); 28 | 29 | it("can invoke global function w/ async arg", function (done) { 30 | window.identity = function (x) { 31 | return x; 32 | }; 33 | var result = evalHyperScript("identity(promiseAnIntIn(10))"); 34 | result.then(function (result) { 35 | result.should.equal(42); 36 | delete window.identity; 37 | done(); 38 | }); 39 | }); 40 | 41 | it("can invoke function on object w/ async arg", function (done) { 42 | window.obj = { 43 | identity: function (x) { 44 | return x; 45 | }, 46 | }; 47 | var result = evalHyperScript("obj.identity(promiseAnIntIn(10))"); 48 | result.then(function (result) { 49 | result.should.equal(42); 50 | delete window.obj; 51 | done(); 52 | }); 53 | }); 54 | 55 | it("can invoke function on object w/ async root & arg", function (done) { 56 | window.obj = { 57 | identity: function (x) { 58 | return x; 59 | }, 60 | }; 61 | var result = evalHyperScript("promiseValueBackIn(obj, 20).identity(promiseAnIntIn(10))"); 62 | result.then(function (result) { 63 | result.should.equal(42); 64 | delete window.obj; 65 | done(); 66 | }); 67 | }); 68 | }); 69 | -------------------------------------------------------------------------------- /test/expressions/idRef.js: -------------------------------------------------------------------------------- 1 | describe("the idRef expression", function () { 2 | beforeEach(function () { 3 | clearWorkArea(); 4 | }); 5 | afterEach(function () { 6 | clearWorkArea(); 7 | }); 8 | 9 | it("basic id ref works", function () { 10 | var div = make("
"); 11 | var value = evalHyperScript("#d1"); 12 | value.should.equal(div); 13 | }); 14 | 15 | it("basic id ref works w no match", function () { 16 | var div = make("
"); 17 | var value = evalHyperScript("#d1"); 18 | should.equal(value, null); 19 | }); 20 | 21 | it("template id ref works", function () { 22 | var div = make("
"); 23 | var value = evalHyperScript("#{'d1'}"); 24 | value.should.equal(div); 25 | }); 26 | 27 | it("id ref works from a disconnected element", function () { 28 | var div = make("
"); 29 | var value = evalHyperScript("#d1", { me: document.createElement('div') }); 30 | value.should.equal(div); 31 | }); 32 | }); 33 | -------------------------------------------------------------------------------- /test/expressions/logicalOperator.js: -------------------------------------------------------------------------------- 1 | describe("the logicalOperator expression", function () { 2 | it("and works", function () { 3 | var result = evalHyperScript("true and false"); 4 | result.should.equal(true && false); 5 | }); 6 | 7 | it("or works", function () { 8 | var result = evalHyperScript("true or false"); 9 | result.should.equal(true || false); 10 | }); 11 | 12 | it("and works w/ more than one value", function () { 13 | var result = evalHyperScript("true and true and false"); 14 | result.should.equal(true && true && false); 15 | }); 16 | 17 | it("unparenthesized expressions with multiple operators cause an error", function () { 18 | var result = getParseErrorFor("true and false or true"); 19 | result.indexOf("You must parenthesize logical operations with different operators").should.equal(0); 20 | }); 21 | 22 | it("parenthesized expressions with multiple operators work", function () { 23 | var result = evalHyperScript("true and (false or true)"); 24 | result.should.equal(true && (false || true)); 25 | }); 26 | 27 | it("should short circuit with and expression", function () { 28 | var func1Called = false; 29 | var func1 = () => {func1Called = true; return false;} 30 | var func2Called = false; 31 | var func2 = () => {func2Called = true; return false;} 32 | var result = evalHyperScript("func1() and func2()", {locals: {func1, func2}}); 33 | result.should.equal(false); 34 | func1Called.should.equal(true); 35 | func2Called.should.equal(false); 36 | }); 37 | 38 | it("should short circuit with or expression", function () { 39 | var func1Called = false; 40 | var func1 = () => {func1Called = true; return true;} 41 | var func2Called = false; 42 | var func2 = () => {func2Called = true; return true;} 43 | var result = evalHyperScript("func1() or func2()", {locals: {func1, func2}}); 44 | result.should.equal(true); 45 | func1Called.should.equal(true); 46 | func2Called.should.equal(false); 47 | }); 48 | 49 | 50 | }); 51 | -------------------------------------------------------------------------------- /test/expressions/mathOperator.js: -------------------------------------------------------------------------------- 1 | describe("the mathOperator expression", function () { 2 | it("addition works", function () { 3 | var result = evalHyperScript("1 + 1"); 4 | result.should.equal(1 + 1); 5 | }); 6 | 7 | it("string concat works", function () { 8 | var result = evalHyperScript("'a' + 'b'"); 9 | result.should.equal("ab"); 10 | }); 11 | 12 | it("subtraction works", function () { 13 | var result = evalHyperScript("1 - 1"); 14 | result.should.equal(1 - 1); 15 | }); 16 | 17 | it("multiplication works", function () { 18 | var result = evalHyperScript("1 * 2"); 19 | result.should.equal(1 * 2); 20 | }); 21 | 22 | it("division works", function () { 23 | var result = evalHyperScript("1 / 2"); 24 | result.should.equal(1 / 2); 25 | }); 26 | 27 | it("mod works", function () { 28 | var result = evalHyperScript("3 mod 2"); 29 | result.should.equal(3 % 2); 30 | }); 31 | 32 | it("addition works w/ more than one value", function () { 33 | var result = evalHyperScript("1 + 2 + 3"); 34 | result.should.equal(1 + 2 + 3); 35 | }); 36 | 37 | it("unparenthesized expressions with multiple operators cause an error", function () { 38 | var result = getParseErrorFor("1 + 2 * 3"); 39 | result.indexOf("You must parenthesize math operations with different operators").should.equal(0); 40 | }); 41 | 42 | it("parenthesized expressions with multiple operators work", function () { 43 | var result = evalHyperScript("1 + (2 * 3)"); 44 | result.should.equal(1 + 2 * 3); 45 | }); 46 | 47 | it("can use mixed expressions", function (done) { 48 | var result = evalHyperScript("1 + promiseAnIntIn(10)"); 49 | result.then(function (value) { 50 | value.should.equal(43); 51 | done(); 52 | }); 53 | }); 54 | }); 55 | -------------------------------------------------------------------------------- /test/expressions/no.js: -------------------------------------------------------------------------------- 1 | describe("the no expression", function () { 2 | it("no returns true for null", function () { 3 | var result = evalHyperScript("no null"); 4 | result.should.equal(true); 5 | }); 6 | 7 | it("no returns false for non-null", function () { 8 | var result = evalHyperScript("no 'thing'"); 9 | result.should.equal(false); 10 | }); 11 | 12 | it("no returns true for empty array", function () { 13 | var result = evalHyperScript("no []"); 14 | result.should.equal(true); 15 | }); 16 | 17 | it("no returns true for empty selector", function () { 18 | var result = evalHyperScript("no .aClassThatDoesNotExist"); 19 | result.should.equal(true); 20 | }); 21 | 22 | it("no returns false for non-null", function () { 23 | var result = evalHyperScript("no ['thing']"); 24 | result.should.equal(false); 25 | }); 26 | }); 27 | -------------------------------------------------------------------------------- /test/expressions/not.js: -------------------------------------------------------------------------------- 1 | describe("the not expression", function () { 2 | it("not inverts true", function () { 3 | var result = evalHyperScript("not true"); 4 | result.should.equal(false); 5 | }); 6 | 7 | it("not inverts false", function () { 8 | var result = evalHyperScript("not false"); 9 | result.should.equal(true); 10 | }); 11 | 12 | it("two nots make a true", function () { 13 | var result = evalHyperScript("not not true"); 14 | result.should.equal(true); 15 | }); 16 | }); 17 | -------------------------------------------------------------------------------- /test/expressions/null.js: -------------------------------------------------------------------------------- 1 | describe("the null literal expression", function () { 2 | it("null literal work", function () { 3 | var result = evalHyperScript("null"); 4 | should.equal(result, null); 5 | }); 6 | }); 7 | -------------------------------------------------------------------------------- /test/expressions/numbers.js: -------------------------------------------------------------------------------- 1 | describe("the number expression", function () { 2 | it("handles numbers properly", function () { 3 | var result = evalHyperScript("-1"); 4 | result.should.equal(-1); 5 | 6 | var result = evalHyperScript("1"); 7 | result.should.equal(1); 8 | 9 | var result = evalHyperScript("1.1"); 10 | result.should.equal(1.1); 11 | 12 | var result = evalHyperScript("1e6"); 13 | result.should.equal(1e6); 14 | 15 | var result = evalHyperScript("1e-6"); 16 | result.should.equal(1e-6); 17 | 18 | var result = evalHyperScript("1.1e6"); 19 | result.should.equal(1.1e6); 20 | 21 | var result = evalHyperScript("1.1e-6"); 22 | result.should.equal(1.1e-6); 23 | 24 | var result = evalHyperScript("1234567890.1234567890"); 25 | result.should.equal(1234567890.123456789); 26 | }); 27 | }); 28 | -------------------------------------------------------------------------------- /test/expressions/objectLiteral.js: -------------------------------------------------------------------------------- 1 | describe("the objectLiteral expression", function () { 2 | it("empty object literals work", function () { 3 | var result = evalHyperScript("{}"); 4 | result.should.deep.equal({}); 5 | }); 6 | 7 | it("one field object literal works", function () { 8 | var result = evalHyperScript("{foo:true}"); 9 | result.should.deep.equal({ foo: true }); 10 | }); 11 | 12 | it("multi-field object literal works", function () { 13 | var result = evalHyperScript("{foo:true, bar:false}"); 14 | result.should.deep.equal({ foo: true, bar: false }); 15 | }); 16 | 17 | it("strings work in object literal field names", function () { 18 | var result = evalHyperScript('{"foo":true, "bar":false}'); 19 | result.should.deep.equal({ foo: true, bar: false }); 20 | }); 21 | 22 | it("hyphens work in object literal field names", function () { 23 | var result = evalHyperScript("{-foo:true, bar-baz:false}"); 24 | result.should.deep.equal({ "-foo": true, "bar-baz": false }); 25 | }); 26 | 27 | it("allows trailing commans", function () { 28 | var result = evalHyperScript("{foo:true, bar-baz:false,}"); 29 | result.should.deep.equal({ "foo": true, "bar-baz": false }); 30 | }); 31 | 32 | it("expressions work in object literal field names", function () { 33 | window.foo = "bar"; 34 | window.bar = function () { 35 | return "foo"; 36 | }; 37 | var result = evalHyperScript("{[foo]:true, [bar()]:false}"); 38 | result.should.deep.equal({ bar: true, foo: false }); 39 | delete window.foo; 40 | delete window.bar; 41 | }); 42 | }); 43 | -------------------------------------------------------------------------------- /test/expressions/positionalExpression.js: -------------------------------------------------------------------------------- 1 | describe("the positional expression", function () { 2 | beforeEach(function () { 3 | clearWorkArea(); 4 | }); 5 | afterEach(function () { 6 | //clearWorkArea(); 7 | }); 8 | 9 | it("first works", function () { 10 | var result = evalHyperScript("the first of [1, 2, 3]"); 11 | result.should.equal(1); 12 | }); 13 | 14 | it("last works", function () { 15 | var result = evalHyperScript("the last of [1, 2, 3]"); 16 | result.should.equal(3); 17 | }); 18 | 19 | it("first works w/ array-like", function () { 20 | var div = make( 21 | "
\n" + 22 | "
\n" + 23 | "
\n" 24 | ); 25 | var result = evalHyperScript("the first of .c1"); 26 | result.should.equal(byId("d1")); 27 | }); 28 | 29 | it("last works w/ array-like", function () { 30 | var div = make( 31 | "
" + 32 | "
" + 33 | "
" 34 | ); 35 | var result = evalHyperScript("the last of .c1"); 36 | result.should.equal(byId("d3")); 37 | }); 38 | 39 | it("first works w/ node", function () { 40 | var div = make( 41 | "
" + 42 | "
" + 43 | "
" 44 | ); 45 | var result = evalHyperScript("the first of div", { locals: { div: div } }); 46 | result.should.equal(byId("d1")); 47 | }); 48 | 49 | it("last works w/ node", function () { 50 | var div = make( 51 | "
" + 52 | "
" + 53 | "
" 54 | ); 55 | var result = evalHyperScript("the last of div", { locals: { div: div } }); 56 | result.should.equal(byId("d3")); 57 | }); 58 | 59 | it("is null safe", function () { 60 | var result = evalHyperScript("the first of null"); 61 | should.equal(result, undefined); 62 | }); 63 | 64 | 65 | }); 66 | -------------------------------------------------------------------------------- /test/expressions/propertyAccess.js: -------------------------------------------------------------------------------- 1 | describe("propertyAccess", function () { 2 | it("can access basic properties", function () { 3 | var result = evalHyperScript("foo.foo", { locals: { foo: { foo: "foo" } } }); 4 | result.should.equal("foo"); 5 | }); 6 | 7 | it("is null safe", function () { 8 | var result = evalHyperScript("foo.foo"); 9 | should.equal(result, undefined); 10 | }); 11 | 12 | it("of form works", function () { 13 | var result = evalHyperScript("foo of foo", { locals: { foo: { foo: "foo" } } }); 14 | result.should.equal("foo"); 15 | }); 16 | 17 | it("of form works w/ complex left side", function () { 18 | var result = evalHyperScript("bar.doh of foo", { 19 | locals: { foo: { bar: { doh: "foo" } } } 20 | }); 21 | result.should.equal("foo"); 22 | }); 23 | 24 | it("of form works w/ complex right side", function () { 25 | var result = evalHyperScript("doh of foo.bar", { 26 | locals: { foo: { bar: { doh: "foo" } } } 27 | }); 28 | result.should.equal("foo"); 29 | }); 30 | 31 | it("works properly w/ boolean properties", function () { 32 | make(" "); 33 | let result = evalHyperScript(".cb.checked"); 34 | result.should.deep.equal([true, false]); 35 | }); 36 | 37 | }); 38 | -------------------------------------------------------------------------------- /test/expressions/some.js: -------------------------------------------------------------------------------- 1 | describe("the some expression", function () { 2 | it("some returns false for null", function () { 3 | var result = evalHyperScript("some null"); 4 | result.should.equal(false); 5 | }); 6 | 7 | it("some returns true for non-null", function () { 8 | var result = evalHyperScript("some 'thing'"); 9 | result.should.equal(true); 10 | }); 11 | 12 | it("some returns false for empty array", function () { 13 | var result = evalHyperScript("some []"); 14 | result.should.equal(false); 15 | }); 16 | 17 | it("some returns false for empty selector", function () { 18 | var result = evalHyperScript("some .aClassThatDoesNotExist"); 19 | result.should.equal(false); 20 | }); 21 | 22 | it("some returns true for nonempty selector", function () { 23 | var result = evalHyperScript("some "); 24 | result.should.equal(true); 25 | }); 26 | 27 | it("some returns true for filled array", function () { 28 | var result = evalHyperScript("some ['thing']"); 29 | result.should.equal(true); 30 | }); 31 | }); 32 | -------------------------------------------------------------------------------- /test/expressions/stringPostfix.js: -------------------------------------------------------------------------------- 1 | describe("the string postfix expression", function () { 2 | 3 | it("handles basic postfix strings properly", function () { 4 | 5 | var result = evalHyperScript("1em"); 6 | result.should.equal("1em"); 7 | 8 | var result = evalHyperScript("1px"); 9 | result.should.equal("1px"); 10 | 11 | var result = evalHyperScript("-1px"); 12 | result.should.equal("-1px"); 13 | 14 | var result = evalHyperScript("100%"); 15 | result.should.equal("100%"); 16 | }); 17 | 18 | it("handles basic postfix strings with spaces properly", function () { 19 | 20 | var result = evalHyperScript("1 em"); 21 | result.should.equal("1em"); 22 | 23 | var result = evalHyperScript("1 px"); 24 | result.should.equal("1px"); 25 | 26 | var result = evalHyperScript("100 %"); 27 | result.should.equal("100%"); 28 | }); 29 | 30 | it("handles expression roots properly", function () { 31 | 32 | var result = evalHyperScript("(0 + 1) em"); 33 | result.should.equal("1em"); 34 | 35 | var result = evalHyperScript("(0 + 1) px"); 36 | result.should.equal("1px"); 37 | 38 | var result = evalHyperScript("(100 + 0) %"); 39 | result.should.equal("100%"); 40 | }); 41 | 42 | }); 43 | -------------------------------------------------------------------------------- /test/expressions/symbol.js: -------------------------------------------------------------------------------- 1 | describe("the symbol expression", function () { 2 | it("resolves local context properly", function () { 3 | var result = evalHyperScript("foo", { locals: { foo: 42 } }); 4 | result.should.equal(42); 5 | }); 6 | 7 | it("resolves global context properly", function () { 8 | var result = evalHyperScript("document", { locals : { foo: 42 } }); 9 | result.should.equal(document); 10 | }); 11 | }); 12 | -------------------------------------------------------------------------------- /test/expressions/typecheck.js: -------------------------------------------------------------------------------- 1 | describe("the typecheck expression", function () { 2 | it("can do basic string typecheck", function () { 3 | var result = evalHyperScript("'foo' : String"); 4 | result.should.equal("foo"); 5 | }); 6 | 7 | it("can do null as string typecheck", function () { 8 | var result = evalHyperScript("null : String"); 9 | should.equal(result, null); 10 | }); 11 | 12 | it("can do basic non-string typecheck failure", function () { 13 | try { 14 | var result = evalHyperScript("true : String"); 15 | throw new Error("should not reach"); 16 | } catch (e) { 17 | console.log(e.message); 18 | e.message.indexOf("Typecheck failed!").should.equal(0); 19 | } 20 | }); 21 | 22 | it("can do basic string non-null typecheck", function () { 23 | var result = evalHyperScript("'foo' : String!"); 24 | result.should.equal("foo"); 25 | }); 26 | 27 | it("null causes null safe string check to fail", function () { 28 | try { 29 | var result = evalHyperScript("null : String!"); 30 | throw new Error("should not reach"); 31 | } catch (e) { 32 | console.log(e.message); 33 | e.message.indexOf("Typecheck failed!").should.equal(0); 34 | } 35 | }); 36 | }); 37 | -------------------------------------------------------------------------------- /test/features/init.js: -------------------------------------------------------------------------------- 1 | describe("the init feature", function () { 2 | beforeEach(function () { 3 | clearWorkArea(); 4 | }); 5 | afterEach(function () { 6 | clearWorkArea(); 7 | }); 8 | 9 | it("can define an init block inline", function (done) { 10 | var div = make( 11 | "
" 15 | ); 16 | setTimeout(function () { 17 | div.click(); 18 | div.innerHTML.should.equal("42"); 19 | done(); 20 | }, 10); 21 | }); 22 | 23 | it("can define an init block in a script", function (done) { 24 | var div = make( 25 | "" 26 | ); 27 | setTimeout(function () { 28 | window.foo.should.equal(42); 29 | delete window.foo; 30 | done(); 31 | }, 10); 32 | }); 33 | 34 | it("can initialize immediately", function (done) { 35 | var div = make( 36 | "" 38 | ); 39 | setTimeout(function () { 40 | window.foo.should.equal(10); 41 | should.equal(window.bar, undefined); 42 | delete window.foo; 43 | delete window.bar; 44 | done(); 45 | }, 10); 46 | }); 47 | }); 48 | -------------------------------------------------------------------------------- /test/features/js.js: -------------------------------------------------------------------------------- 1 | describe("The (top-level) js feature", function () { 2 | beforeEach(function () { 3 | clearWorkArea(); 4 | }); 5 | afterEach(function () { 6 | clearWorkArea(); 7 | }); 8 | 9 | it("can run js at the top level", function () { 10 | window.testSuccess = false; 11 | var script = make(" 10 | 11 | 12 | 13 | Web Socket Playground 14 |
15 | 21 | 27 | 28 |
29 | 32 | 33 | 39 | 40 | 43 |
44 | 45 |
46 | 49 |
50 | 51 | 52 | -------------------------------------------------------------------------------- /test/sockets/server.py: -------------------------------------------------------------------------------- 1 | 2 | #!/usr/bin/env python3 3 | 4 | import asyncio 5 | import websockets 6 | import json 7 | 8 | async def server(websocket, path): 9 | while True: 10 | # Get received data from websocket 11 | data = await websocket.recv() 12 | print ("Received: " + data) 13 | json_data = json.loads(data) 14 | if json_data.get('function') == 'echo': 15 | #echo back the first argument 16 | json_data['return'] = json_data['args'][0] 17 | elif json_data.get('function') == 'ask': 18 | #echo back the first argument 19 | json_data['return'] = input(json_data['args'][0]) 20 | elif json_data.get('function') == 'throw': 21 | #throw the first argument 22 | json_data['throw'] = json_data['args'][0] 23 | elif not json_data.get('function') is None: 24 | json_data['throw'] = 'Unknown function : ' + json_data['function'] 25 | 26 | # Send response back to client to acknowledge receiving message 27 | response = json.dumps(json_data) 28 | print ("Responding: " + response) 29 | await websocket.send(response) 30 | 31 | # Create websocket server 32 | start_server = websockets.serve(server, "localhost", 5150) 33 | 34 | # Start and run websocket server forever 35 | asyncio.get_event_loop().run_until_complete(start_server) 36 | print("Starting loop") 37 | asyncio.get_event_loop().run_forever() -------------------------------------------------------------------------------- /test/sockets/websockets: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bigskysoftware/_hyperscript/8c4d36a98f14a91540ee5dc2c7b197ca2a7134fd/test/sockets/websockets -------------------------------------------------------------------------------- /test/templates/templates.js: -------------------------------------------------------------------------------- 1 | describe("Templating", function () { 2 | it("can render", function () { 3 | var tmpl = make(""); 4 | _hyperscript("render tmpl with (x: x) then put it into window.res", { 5 | locals: { 6 | x: ":)", 7 | tmpl: tmpl, 8 | } 9 | }); 10 | window.res.should.equal("render :)"); 11 | delete window.res; 12 | }); 13 | 14 | it("escapes html, with opt-out", function () { 15 | var tmpl = make(""); 16 | _hyperscript("render tmpl with (x: x) then put it into window.res", { 17 | locals: { 18 | x: "
", 19 | tmpl: tmpl, 20 | } 21 | }); 22 | window.res.should.equal("render <br>
"); 23 | delete window.res; 24 | }); 25 | 26 | it("supports repeat", function () { 27 | var tmpl = make( 28 | "" 29 | ); 30 | _hyperscript("render tmpl with (x: x) then put it into window.res", { 31 | locals: { 32 | x: ":)", 33 | tmpl: tmpl, 34 | } 35 | }); 36 | window.res.should.equal("begin\n1\n2\n3\nend\n"); 37 | delete window.res; 38 | }); 39 | 40 | it("supports if", function () { 41 | var tmpl = make( 42 | "" 43 | ); 44 | _hyperscript("render tmpl with (x: x) then put it into window.res", { 45 | locals: { 46 | x: ":)", 47 | tmpl: tmpl, 48 | } 49 | }); 50 | window.res.should.equal("begin\na\nend\n"); 51 | delete window.res; 52 | }); 53 | }); 54 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "strict": false, 4 | "checkJs": true, 5 | "outDir": "dist/lib", 6 | "target":"ES2015" 7 | }, 8 | "include": ["src/lib/*", "src/bin/*", "src/lib/plugin/socket.js", "src/lib/plugin/template.js", "src/lib/plugin/worker.js", "src/lib/plugin/web.js"], 9 | "exclude": ["node_modules"], 10 | } -------------------------------------------------------------------------------- /www/.eleventy.js: -------------------------------------------------------------------------------- 1 | 2 | const markdownItAttrs = require("markdown-it-attrs"); 3 | const markdownItAnchor = require("markdown-it-anchor"); 4 | const markdownItDeflist = require("markdown-it-deflist"); 5 | module.exports = function(config) { 6 | config.addPassthroughCopy("css"); 7 | config.addPassthroughCopy("img"); 8 | config.addPassthroughCopy("js"); 9 | config.addPassthroughCopy("test"); 10 | 11 | config.addCollection('cookbook', coll => coll.getFilteredByGlob('cookbook/*')) 12 | 13 | var md = new (require("markdown-it"))({ html: true }); 14 | md.use(markdownItAttrs); 15 | md.use(markdownItDeflist); 16 | md.use(markdownItAnchor, { 17 | permalink: markdownItAnchor.permalink.ariaHidden({ 18 | symbol: "§", 19 | placement: 'before' 20 | }) 21 | }); 22 | md.use(require("markdown-it-table-of-contents"), { 23 | "includeLevel": [2,3,4,5,6] 24 | }); 25 | config.setLibrary("md", md) 26 | 27 | require("./_build/widgets")(config); 28 | } 29 | -------------------------------------------------------------------------------- /www/README.md: -------------------------------------------------------------------------------- 1 | ## Running The Website Locally 2 | 3 | The hyperscript.org website is built on [eleventy](https://www.11ty.dev/). 4 | To run the site, use node 15 and run: 5 | 6 | ``` 7 | npm install 8 | npx eleventy --serve 9 | ``` 10 | 11 | The site should then be available at 12 | 13 | -------------------------------------------------------------------------------- /www/_build/widgets.js: -------------------------------------------------------------------------------- 1 | 2 | module.exports = function (config) { 3 | config.addPairedShortcode('example', (content, caption) => { 4 | let rv = "
\n\n" 5 | if (caption) rv += `
Example: ${caption}
\n\n` 6 | else rv += `
Example
\n\n` 7 | rv += " ~~~ html" 8 | rv += content 9 | rv += "~~~\n\n" 10 | rv += content 11 | rv += "
" 12 | return rv 13 | }) 14 | 15 | function syntax(syntax) { 16 | syntax = syntax 17 | .replace(/\[\[([\*\+\?])\]\]/g, 18 | "$1" 19 | ) 20 | .replace(/\[\[([a-zA-Z0-9 ]+)\]\]/g, 21 | '$1' 22 | ) 23 | 24 | return `${syntax}` 25 | } 26 | 27 | config.addShortcode("syntax", syntax) 28 | 29 | function syntaxify(line) { 30 | return line 31 | .replace(/`([^`]+)`/g, (match, p1) => syntax(p1)) 32 | } 33 | 34 | config.addPairedShortcode("syntaxes", content => { 35 | const buf = [] 36 | const lines = content.split('\n'); 37 | let dt = false; 38 | for (const line of lines) { 39 | if (isJustWhitespace(line)) { 40 | buf.push(line); 41 | } else if (!indented(line)) { 42 | dt = true; 43 | buf.push('\n
' + syntaxify(line)); 44 | } else { 45 | if (dt) buf.push('
\n'); 46 | dt = false; 47 | buf.push(dedent(line)); 48 | } 49 | } 50 | return `
${buf.join('\n')}
`; 51 | }) 52 | } 53 | 54 | function isJustWhitespace(str) { return /^\s*$/.test(str) } 55 | function dedent(str) { return str.replace(/^(\t| )/, '') } 56 | function indented(str) { return str.startsWith(' ') || str.startsWith('\t') } 57 | -------------------------------------------------------------------------------- /www/_data/layout.json: -------------------------------------------------------------------------------- 1 | "layout.njk" 2 | -------------------------------------------------------------------------------- /www/_includes/js_end.md: -------------------------------------------------------------------------------- 1 | ### Note on `end` 2 | 3 | `end` cannot appear inside JS code as an identifier. However, it **can** appear in string literals (`"end", 'end'`, **not** `` `end` ``). 4 | 5 | Here are workarounds for some cases where you might need `end` in your JavaScript code: 6 | 7 | ```js 8 | // Don't: 9 | var end = getTheEnd(); 10 | // Do: 11 | var theEnd = getTheEnd(); 12 | 13 | // Don't: 14 | getEndable().end(); 15 | // Do: 16 | getEndable()["end"](); 17 | 18 | // Don't: 19 | var template = `this can only end ${good ? "well" : "badly"}`; 20 | // Do: 21 | var template = `this can only ${"end"} ${good ? "well" : "badly"}`; 22 | 23 | // Don't: 24 | var regex = /end (.*)/; 25 | // Do: 26 | var regex = new RegExp("end (.*)"); 27 | ``` 28 | -------------------------------------------------------------------------------- /www/clickedMessage.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: null 3 | --- 4 | You clicked! (this message brought to you by `fetch`) 5 | -------------------------------------------------------------------------------- /www/commands/add.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: add - ///_hyperscript 3 | --- 4 | 5 | ## The `add` Command 6 | 7 | ### Syntax 8 | 9 | ```ebnf 10 | add [to ] [where ] 11 | ``` 12 | 13 | ### Description 14 | 15 | The `add` command allows you to add a class (via a [class ref](/expressions/class-reference)), an attribute 16 | (via an [attribute ref](/expressions/attribute-ref)) or CSS attributes (via an object literal) to either the current element or to another element. 17 | 18 | **Note:** Hyperscript supports hyphens in object property names, so you can write `add { font-size: '2em' }`. However, double hyphens (`--`) mark comments in hyperscript, so if you need to use them for [CSS Custom Properties][], use quotes -- `add { '--big-font-size': '2em' }`. 19 | 20 | The `where` clause allows you filter what elements have the class or property added in the `target`. The expression will be evaluated for 21 | each element in `target` and, if the result is true, the element class or property will be added. If it is false, the class 22 | or property will be removed. The `it` symbol will be set to the current element, allowing you to express conditions against each element 23 | in `target`. Note that this clause only works with classes and properties. 24 | 25 | ### Examples 26 | 27 | ```html 28 |
Click Me!
29 | 30 |
Click Me!
31 | 32 | 33 | 34 | 38 | 39 | 40 | 41 | ``` 42 | -------------------------------------------------------------------------------- /www/commands/append.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: append - ///_hyperscript 3 | --- 4 | 5 | ## The `append` Command 6 | 7 | ### Syntax 8 | 9 | ```ebnf 10 | append [to | | ] 11 | ``` 12 | 13 | ### Description 14 | 15 | The `append` command adds a string value to the end of another string, array, or HTML Element. If no target variable is defined, then the standard `result` variable is used by default. 16 | 17 | ### Examples 18 | 19 | #### Append to a string 20 | 21 | If you target a string variable, then `append` uses `+=` to add the string to the end of the target variable. 22 | 23 | ```hyperscript 24 | set fullName to "John" 25 | append " Connor" to fullName 26 | -- fullName == "John Connnor" 27 | ``` 28 | 29 | #### Append to an array 30 | 31 | If you target an array variable, then `append` uses `Array.push()` to add a new item to the end of the array. 32 | 33 | ```hyperscript 34 | set resultArray to [] 35 | append 1 to resultArray 36 | append 2 to resultArray 37 | append 3 to resultArray 38 | -- resultArray == [1,2,3] 39 | ``` 40 | 41 | #### Append to an HTML Element 42 | 43 | If you target an HTML Element, then the value is appended to the end of the element's `innerHTML` 44 | 45 | ```hyperscript 46 | append "More HTML here" to #myDIV 47 | ``` 48 | 49 | #### Use `append` to collect content 50 | 51 | If no target variable is provided, `append` writes to the standard `result` variable by default. In some cases this can help you to write even more compact code. But, be careful! Many other commands will also write to the `result` (or `it`) variable, which can overwrite your work. 52 | 53 | ```hyperscript 54 | set result to "
" 55 | repeat for person in people 56 | append ` 57 |
58 |
59 |
${person.firstName} ${person.lastName}
60 |
61 | ` 62 | end 63 | append "
" 64 | put it into #people 65 | ``` 66 | -------------------------------------------------------------------------------- /www/commands/async.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: async - ///_hyperscript 3 | --- 4 | 5 | ## The `async` Command 6 | 7 | ### Syntax 8 | 9 | ```ebnf 10 | async 11 | 12 | async do 13 | {command} 14 | end 15 | ``` 16 | 17 | ### Description 18 | 19 | The `async` command you to execute a command or a block of commands asynchronously (they will not block hyperscript from continuing 20 | even if they return a promise) 21 | 22 | ### Examples 23 | 24 | ```html 25 | 29 | 38 | ``` 39 | -------------------------------------------------------------------------------- /www/commands/beep.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: layout.njk 3 | title: beep - ///_hyperscript 4 | --- 5 | 6 | ## The `beep!` Statement 7 | 8 | ### Syntax 9 | 10 | ```ebnf 11 | beep! {, } 12 | ``` 13 | 14 | ### Description 15 | 16 | The `beep!` command allows you to debug an expression (or multiple expressions) by printing 17 | the source of the expression, its result and the type of the result to the console. This is 18 | quick and convenient mechanism for print-debugging in hyperscript. 19 | 20 | Note that the syntax is slightly different than the [beep! expression](/expressions/beep), which binds 21 | to unary expressions rather than general expressions. 22 | 23 | Note that you can also print the value of multiple expressions in a single `beep!` command. 24 | Note that you can also print the value of multiple expressions in a single `beep!` command. 25 | 26 | ### Examples 27 | 28 | ```hyperscript 29 | beep! <.foo/> 30 | beep! <.foo/>, <.foo/> in <.bar/> 31 | ``` 32 | -------------------------------------------------------------------------------- /www/commands/break.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: break - ///_hyperscript 3 | --- 4 | 5 | ## The `break` Command 6 | 7 | ### Syntax 8 | 9 | ```ebnf 10 | break 11 | ``` 12 | 13 | ### Description 14 | 15 | The `break` command works inside any `repeat` block. It exits the loop. 16 | 17 | ### Example 18 | 19 | ```hyperscript 20 | repeat 3 times 21 | wait 2s 22 | if my @value is not empty 23 | break 24 | end 25 | append "Value is still empty...
" to #message 26 | end 27 | ``` 28 | -------------------------------------------------------------------------------- /www/commands/call.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: call - ///_hyperscript 3 | --- 4 | 5 | ## The `call` Command 6 | 7 | ### Syntax 8 | 9 | ```ebnf 10 | call 11 | get 12 | ``` 13 | 14 | ### Description 15 | 16 | The `call` command allows you evaluate an expression. 17 | 18 | The value of this expression will be put into the `it` variable. 19 | 20 | `get` is an alias for `call` and can be used if it more clearly expresses the meaning of the code. 21 | 22 | ### Examples 23 | 24 | ```html 25 |
Click Me!
26 | 27 |
31 | Click Me! 32 |
33 | ``` 34 | -------------------------------------------------------------------------------- /www/commands/continue.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: continue - ///_hyperscript 3 | --- 4 | 5 | ## The `continue` Command 6 | 7 | ### Syntax 8 | 9 | ```ebnf 10 | continue 11 | ``` 12 | 13 | ### Description 14 | 15 | The `continue` command works inside any `repeat` block. It exits the current iteration of the loop and begins at the top of the next iteration. 16 | 17 | ### Example 18 | 19 | ```hyperscript 20 | repeat 3 times 21 | append "works " to #message -- this command will execute 22 | continue 23 | append "skipped " to #message -- this command will be skipped 24 | end 25 | ``` 26 | -------------------------------------------------------------------------------- /www/commands/decrement.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: decrement - ///_hyperscript 3 | --- 4 | 5 | ## The `decrement` Command 6 | 7 | ### Syntax 8 | 9 | ```ebnf 10 | decrement [by ] 11 | ``` 12 | 13 | ### Description 14 | 15 | The `decrement` command subtracts from an existing variable, property, or attribute. It defaults to subtracting the value `1`, but this can be changed using the `by` modifier. If the target variable is null, then it is assumed to be `0`, and then decremented by the specified amount. The `decrement` command is the opposite of the [`increment` command](/commands/increment) command. 16 | 17 | ### Example 18 | 19 | If you target a string variable, then `decrement` uses `+=` to add the string to the end of the target variable. 20 | 21 | ```hyperscript 22 | set counter to 5 23 | decrement counter by 2 -- counter is now 3 24 | 25 | decrement newVariable -- newVariable is defaulted to zero, then decremented to -1 26 | ``` 27 | -------------------------------------------------------------------------------- /www/commands/default.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: default - ///_hyperscript 3 | --- 4 | 5 | ## The `default` Command 6 | 7 | ### Syntax 8 | 9 | ```ebnf 10 | default to 11 | ``` 12 | 13 | ### Description 14 | 15 | The `default` command defaults a variable or property to a given value. 16 | 17 | ### Example 18 | 19 | ```hyperscript 20 | -- default an attribute to a value 21 | default @foo to 'bar' 22 | 23 | -- default a variable to a value 24 | default x to 10 25 | ``` 26 | -------------------------------------------------------------------------------- /www/commands/go.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: go - ///_hyperscript 3 | --- 4 | 5 | ## The `go` Command 6 | 7 | ### Syntax 8 | 9 | ```ebnf 10 | go [to] url [in new window] 11 | go [to] [top|middle|bottom] [left|center|right] [of] [(+|-) [px] ][smoothly|instantly] 12 | go back 13 | ``` 14 | 15 | ### Description 16 | 17 | The `go` command allows you navigate on the page with various forms 18 | 19 | `go to url ` will navigate to the given URL. If the url starts with an anchor `#` it will instead update 20 | the windows href. 21 | 22 | `go to elt` will scroll the element into view on the current page. You can pick the top, bottom, left, etc. 23 | by using modifiers, and you can pick the animation style with a following `smoothly` or `instantly`. 24 | 25 | Additionally you can use a pixel-based offset to pad the scrolling by some amount since, annoyingly, the default behavior of 26 | `scrollIntoView()` is to put the element right on the edge of the viewport. 27 | 28 | Finally, the `go back` form will navigate back in the history stack. 29 | 30 | ### Examples 31 | 32 | ```html 33 | 36 | 37 | 40 | 41 | 44 | ``` 45 | -------------------------------------------------------------------------------- /www/commands/halt.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: halt - ///_hyperscript 3 | --- 4 | 5 | ## The `halt` Command 6 | 7 | ### Syntax 8 | 9 | ```ebnf 10 | halt [the event['s]] (bubbling|default) 11 | exit 12 | ``` 13 | 14 | ### Description 15 | 16 | The `halt` command prevents an event from bubbling and/or from performing its default action. 17 | 18 | The form `halt the event` will halt both the bubbling and default for the event, but continue execution of the 19 | event handler 20 | 21 | The form `halt the event's (bubbling|default)` will halt either the bubbling or the default for the event, but continue 22 | execution of the event handler 23 | 24 | The form `halt` will halt both the bubbling and default for the event and exit the current event handler, acting the same 25 | as the [`exit`](/commands/return) command. 26 | 27 | The form `halt (bubbling|default)` will halt either the bubbling or the default for the event and exit the current event 28 | handler. 29 | 30 | ### Examples 31 | 32 | ```html 33 | 39 | ``` 40 | -------------------------------------------------------------------------------- /www/commands/if.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: if - ///_hyperscript 3 | --- 4 | 5 | ## The `if` Command 6 | 7 | ### Syntax 8 | 9 | ```ebnf 10 | if [then] [(else | otherwise) ] end` 11 | ``` 12 | 13 | ### Description 14 | 15 | The `if` command provides the standard if-statement control flow. 16 | 17 | Note that a leading `if` on a separate line from an `else` statement will be treated as a nested if within the else: 18 | 19 | ```hyperscriptr 20 | ... 21 | else 22 | if false -- does not bind to the else on the previous line as an "else if" 23 | log 'foo' 24 | end 25 | log 'bar' 26 | end 27 | ``` 28 | 29 | ### Examples 30 | 31 | ```html 32 |
36 | Click Me! 37 |
38 | ``` 39 | -------------------------------------------------------------------------------- /www/commands/increment.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: increment - ///_hyperscript 3 | --- 4 | 5 | ## The `increment` Command 6 | 7 | ### Syntax 8 | 9 | ```ebnf 10 | increment [by ] 11 | ``` 12 | 13 | ### Description 14 | 15 | The `increment` command adds to an existing variable, property, or attribute. It defaults to adding the value `1`, but this can be changed using the `by` modifier. If the target variable is null, then it is assumed to be `0`, and then incremented by the specified amount. The `increment` command is the opposite of the [`decrement` command](/commands/decrement) command. 16 | 17 | ### Example 18 | 19 | If you target a string variable, then `increment` uses `+=` to add the string to the end of the target variable. 20 | 21 | ```hyperscript 22 | set counter to 5 23 | increment counter by 2 -- counter is now 7 24 | 25 | increment newVariable -- newVariable is defaulted to zero, then incremented to 1 26 | ``` 27 | -------------------------------------------------------------------------------- /www/commands/js.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: js - ///_hyperscript 3 | --- 4 | 5 | ## The `js` Command (inline) 6 | 7 | **Note:** This page is about the inline JS command. \_hyperscript also supports [JS blocks at the top level](/features/js/). 8 | 9 | ### Syntax 10 | 11 | `js[()] end` 12 | 13 | - `param-list` is a comma separated list of identifiers, which are \_hyperscript variables whose values will be passed to this JavaScript code and become available there under their original name 14 | - `js-body` is some JavaScript code whose return value will be the result of this command (what `it` refers to in the next command). 15 | 16 | ### Description 17 | 18 | The `js` command can be used to embed JavaScript code inline in \_hyperscript, as shown below: 19 | 20 | ```html 21 | 30 | ``` 31 | 32 | `this` inside a `js` block is the global scope (`window`, or `self` in workers). 33 | 34 | If the `js` block returns a promise, the code that comes after it will execute when it resolves. 35 | 36 | If the `js` block needs to use variables from the surrounding \_hyperscript code, these need to be explicitly declared as shown: 37 | 38 | ```html 39 | 51 | ``` 52 | 53 | {% include "js_end.md" %} 54 | -------------------------------------------------------------------------------- /www/commands/log.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: log - ///_hyperscript 3 | --- 4 | 5 | ## The `log` Command 6 | 7 | ### Syntax 8 | 9 | ```ebnf 10 | log {, } [with ] 11 | ``` 12 | 13 | ### Description 14 | 15 | The `log` command logs an expression to `console.log` or whatever value is provided with the `with` clause. 16 | 17 | ### Examples 18 | 19 | ```html 20 |
Click Me!
21 | 22 |
Click Me!
23 | 24 |
Click Me!
25 | ``` 26 | -------------------------------------------------------------------------------- /www/commands/make.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: make - ///_hyperscript 3 | --- 4 | 5 | ## The `make` command 6 | 7 | ### Syntax 8 | 9 | ```ebnf 10 | make (a|an) [from ] [called ] 11 | make (a|an) [called ] 12 | ``` 13 | 14 | ### Description 15 | 16 | The `make` command can be used to create class instances or DOM elements. 17 | 18 | In the first form: 19 | 20 | ```hyperscript 21 | make a URL from "/path/", "https://origin.example.com" 22 | ``` 23 | 24 | is equal to the JavaScript `new URL("/path/", "https://origin.example.com")`. 25 | 26 | In the second form: 27 | 28 | ```hyperscript 29 | make an 30 | ``` 31 | 32 | will create an `` element and add the class `"navlink"` to it. Currently, only 33 | classes and IDs are supported. 34 | 35 | ### Examples 36 | 37 | ```hyperscript 38 | def formatPizzaToppings(toppings) 39 | make an Intl.ListFormat from "en", { type: "conjunction" } 40 | called listFmt 41 | 42 | for part in listFmt.formatToParts(toppings) 43 | if the part's type is "element" 44 | make a 45 | put the part's value into its textContent 46 | append it to #toppings 47 | else 48 | append the part's value to #toppings 49 | end 50 | end 51 | ``` 52 | 53 | Pepperoni, Mushrooms 54 | and Green Peppers 55 | 56 | 59 | -------------------------------------------------------------------------------- /www/commands/measure.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: measure - ///_hyperscript 3 | --- 4 | 5 | ## The `measure` Command 6 | 7 | ### Syntax 8 | 9 | ```ebnf 10 | measure [measurement {, measurement} 11 | ``` 12 | 13 | ### Description 14 | 15 | The `measure` command gets the measurements for a given element using `getBoundingClientRect()` as well as the 16 | `scroll*` properties. It will place the result into the `result` variable. 17 | 18 | You may also specify particular measurements to be saved into local variables, by name. 19 | 20 | The available measurements are: 21 | 22 | - x 23 | - y 24 | - left 25 | - top 26 | - right 27 | - bottom 28 | - width 29 | - height 30 | - bounds 31 | - scrollLeft 32 | - scrollTop 33 | - scrollLeftMax 34 | - scrollTopMax 35 | - scrollWidth 36 | - scrollHeight 37 | - scroll 38 | 39 | ### Examples 40 | 41 | ```html 42 |
Click Me To Measure
43 | 44 |
Click Me To Measure My Top
45 | ``` 46 | -------------------------------------------------------------------------------- /www/commands/put.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: put - ///_hyperscript 3 | --- 4 | 5 | ## The `put` Command 6 | 7 | ### Syntax 8 | 9 | ```ebnf 10 | put (into | before | at [the] start of | at [the] end of | after) ` 11 | ``` 12 | 13 | ### Description 14 | 15 | The `put` command allows you to insert content into a variable, property or the DOM. 16 | 17 | Content that is added to the DOM via the `put` command targeting DOM will have any hyperscript content within it 18 | initialized without needing to call `processNode()`. 19 | 20 | ### Examples 21 | 22 | ```html 23 |
Click Me!
24 | 25 | 26 |
Click Me!
27 | 28 |
34 | Click Me! 35 |
36 | ``` 37 | 38 | ```hyperscript 39 | def fillList(array, ul) 40 | for item in array 41 | -- put `
  • ${item}
  • ` at end of ul 42 | call document.createElement('li') 43 | put the item into its textContent 44 | put it at end of the ul 45 | end 46 | end 47 | ``` 48 | -------------------------------------------------------------------------------- /www/commands/remove.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: remove - ///_hyperscript 3 | --- 4 | 5 | ## The `remove` Command 6 | 7 | ### Syntax 8 | 9 | ```ebnf 10 | remove [from ] 11 | ``` 12 | 13 | ### Description 14 | 15 | The `remove` command allows you to remove an element from the DOM or to remove 16 | a class or property from an element node. 17 | 18 | ### Examples 19 | 20 | ```html 21 |
    Remove Me!
    22 | 23 |
    Remove Class From Me!
    24 | 25 |
    26 | Remove Class From Another Div! 27 |
    28 | 29 |
    30 | Remove Class From Another Div! 31 |
    32 | 33 | 34 | 35 | ``` 36 | -------------------------------------------------------------------------------- /www/commands/render.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: render - ///_hyperscript 3 | --- 4 | 5 | ## The `render` command 6 | 7 | ### Installing 8 | 9 | Note: if you want the template command, you must include the /dist/template.js file in addition to the hyperscript script 10 | ~~~ html 11 | 12 | ~~~ 13 | 14 | ### Syntax 15 | 16 | `render