Revive packages, let me explain!

Really the lack of a declarative Task API (think pure,
lazy promises) was the motivation behind deprecating
packages in the first place. Without tasks, @/dom,
@/random, and @/http are unable to easily chain
effects (sync or async) without actions.

This is a limitation of Hyperapp, not an issue with any
particular package. Some packages don't even need tasks
@/html, @/svg, @/events, and confiscating everything
has done more harm than good, so I've decided to
bring them back.

Maybe we'll figure out tasks someday.
This commit is contained in:
Jorge Bucaran 2021-09-22 14:25:21 +09:00
parent dc10f52bb2
commit 8594d20a74
No known key found for this signature in database
GPG Key ID: E54BA3C0E646DB30
17 changed files with 568 additions and 4 deletions

View File

@ -56,9 +56,24 @@ npm install hyperapp
## Documentation
Ready to dive in? Learn the basics in the [Tutorial](docs/tutorial.md), check out the [Examples](https://codepen.io/collection/nLLvrz?grid_type=grid), or visit the [Reference](docs/reference.md) for more detail.
Learn the basics in the [Tutorial](docs/tutorial.md), check out the [Examples](https://codepen.io/collection/nLLvrz?grid_type=grid), or visit the [Reference](docs/reference.md).
To access [Web Platform APIs](https://platform.html5.org) (like `fetch` or `addEventListener`) in a way that makes sense for Hyperapp, learn [how to create your own effects and subscriptions](/docs/architecture/subscriptions.md). For everything else, from third-party packages to real-world examples, browse the [Hyperawesome](https://github.com/jorgebucaran/hyperawesome) collection.
## Packages
Official packages provide access to [Web Platform](https://platform.html5.org) APIs in a way that makes sense for Hyperapp. For third-party packages and real-world examples, browse the [Hyperawesome](https://github.com/jorgebucaran/hyperawesome) collection.
| Package | Status | About |
| ---------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------- |
| [`@hyperapp/dom`](/packages/dom) | [![npm](https://img.shields.io/npm/v/@hyperapp/dom.svg?style=for-the-badge&color=0366d6&label=)](https://www.npmjs.com/package/@hyperapp/dom) | Inspect the DOM, focus and blur. |
| [`@hyperapp/svg`](/packages/svg) | [![npm](https://img.shields.io/npm/v/@hyperapp/svg.svg?style=for-the-badge&color=0366d6&label=)](https://www.npmjs.com/package/@hyperapp/svg) | Draw SVG with plain functions. |
| [`@hyperapp/html`](/packages/html) | [![npm](https://img.shields.io/npm/v/@hyperapp/html.svg?style=for-the-badge&color=0366d6&label=)](https://www.npmjs.com/package/@hyperapp/html) | Write HTML with plain functions. |
| [`@hyperapp/time`](/packages/time) | [![npm](https://img.shields.io/npm/v/@hyperapp/time.svg?style=for-the-badge&color=0366d6&label=)](https://www.npmjs.com/package/@hyperapp/time) | Subscribe to intervals, get the time now. |
| [`@hyperapp/events`](/packages/events) | [![npm](https://img.shields.io/npm/v/@hyperapp/events.svg?style=for-the-badge&color=0366d6&label=)](https://www.npmjs.com/package/@hyperapp/events) | Subscribe to mouse, keyboard, window, and frame events. |
| [`@hyperapp/http`](/packages/http) | [![npm](https://img.shields.io/badge/-planned-6a737d?style=for-the-badge&label=)](https://www.npmjs.com/package/@hyperapp/http) | Talk to servers, make HTTP requests ([#1027](https://github.com/jorgebucaran/hyperapp/discussions/1027)). |
| [`@hyperapp/random`](/packages/random) | [![npm](https://img.shields.io/badge/-planned-6a737d?style=for-the-badge&label=)](https://www.npmjs.com/package/@hyperapp/random) | Declarative random numbers and values. |
| [`@hyperapp/navigation`](/packages/navigation) | [![npm](https://img.shields.io/badge/-planned-6a737d?style=for-the-badge&label=)](https://www.npmjs.com/package/@hyperapp/navigation) | Subscribe and manage the browser URL history. |
> Need to create your own effects and subscriptions? [You can do that too](docs/reference.md).
## Help, I'm stuck!
@ -66,7 +81,7 @@ If you've hit a stumbling block, hop on our [Discord](https://discord.gg/eFvZXzX
## Contributing
Hyperapp is free and open-source software. If you love Hyperapp, becoming a contributor or [sponsoring](https://github.com/sponsors/jorgebucaran) is the best way to give back. Thank you to everyone who already contributed to Hyperapp!
Hyperapp is free and open-source software. If you want to support Hyperapp, becoming a contributor or [sponsoring](https://github.com/sponsors/jorgebucaran) is the best way to give back. Thank you to everyone who already contributed to Hyperapp! <3
[![](https://opencollective.com/hyperapp/contributors.svg?width=1024&button=false)](https://github.com/jorgebucaran/hyperapp/graphs/contributors)

View File

@ -23,8 +23,9 @@
],
"scripts": {
"test": "c8 twist tests/*.js",
"info": "node --print \"('$pkg' ? '@$npm_package_name/$pkg@' : '') + require('./${pkg:+packages/$pkg/}package').version\"",
"deploy": "npm test && git commit --all --message $tag && git tag --sign $tag --message $tag && git push && git push --tags",
"release": "tag=$npm_package_version npm run deploy && npm publish --access public"
"release": "tag=$(npm run --silent info) npm run deploy && cd ./${pkg:+packages/$pkg} && npm publish --access public"
},
"devDependencies": {
"twist": "*",

25
packages/dom/README.md Normal file
View File

@ -0,0 +1,25 @@
# @hyperapp/dom
> Inspect the DOM, focus and blur.
## Installation
```console
npm install @hyperapp/dom
```
```js
import { focus, blur } from "@hyperapp/dom"
```
Or without a build step—import it right in your browser.
```html
<script type="module">
import { focus, blur } from "https://unpkg.com/@hyperapp/dom"
</script>
```
## License
[MIT](../../LICENSE.md)

10
packages/dom/index.js Normal file
View File

@ -0,0 +1,10 @@
const justFocus = (_, { id, ...props }) =>
requestAnimationFrame(() => document.getElementById(id).focus(props))
const justBlur = (_, id) => document.getElementById(id).blur()
export const focus = (id, { preventScroll } = {}) => [
justFocus,
{ id, preventScroll },
]
export const blur = (id) => [justBlur, id]

15
packages/dom/package.json Normal file
View File

@ -0,0 +1,15 @@
{
"name": "@hyperapp/dom",
"version": "0.1.2",
"type": "module",
"main": "index.js",
"description": "Inspect the DOM, focus and blur",
"repository": "https://github.com/jorgebucaran/hyperapp/tree/main/packages/dom",
"license": "MIT",
"keywords": [
"hyperapp",
"focus",
"blur",
"dom"
]
}

13
packages/events/README.md Normal file
View File

@ -0,0 +1,13 @@
# @hyperapp/events
> Subscribe to mouse, keyboard, window, and frame events.
## Installation
```console
npm install @hyperapp/events
```
## License
[MIT](../../LICENSE.md)

45
packages/events/index.js Normal file
View File

@ -0,0 +1,45 @@
const listeners = {}
const fx = (subscriber) => (action) => [subscriber, action]
const globalListener = (event) => {
for (const [dispatch, actions] of listeners[event.type])
for (const action of actions) dispatch(action, event)
}
const on = (type) =>
fx((dispatch, action) => {
if (!listeners[type]) {
listeners[type] = new Map()
addEventListener(type, globalListener)
}
listeners[type].set(
dispatch,
(listeners[type].get(dispatch) || []).concat(action)
)
return () => {
const actions = listeners[type].get(dispatch).filter((a) => a !== action)
listeners[type].set(dispatch, actions)
if (
actions.length === 0 &&
listeners[type].delete(dispatch) &&
listeners[type].size === 0
) {
delete listeners[type]
removeEventListener(type, globalListener)
}
}
})
export const onMouseMove = on("mousemove")
export const onMouseDown = on("mousedown")
export const onMouseUp = on("mouseup")
export const onKeyDown = on("keydown")
export const onKeyUp = on("keyup")
export const onClick = on("click")
export const onFocus = on("focus")
export const onBlur = on("blur")

View File

@ -0,0 +1,16 @@
{
"name": "@hyperapp/events",
"version": "0.1.0",
"type": "module",
"main": "index.js",
"description": "Subscribe to mouse, keyboard, window, and frame events",
"repository": "https://github.com/jorgebucaran/hyperapp/tree/main/packages/events",
"license": "MIT",
"keywords": [
"hyperapp",
"events",
"keyboard",
"window",
"mouse"
]
}

67
packages/html/README.md Normal file
View File

@ -0,0 +1,67 @@
# @hyperapp/html
> Write HTML with plain functions.
Hyperapp's built-in `h()` function is intentionally primitive to give you the freedom to write views any way you like it. If you prefer a functional approach over templating solutions like JSX or template literals, here is a collection of functions—one for [each HTML tag](https://developer.mozilla.org/en-US/docs/Web/HTML/Element)—to make your views faster to write and easier to read.
Here's the first example to get you started. [Try it in your browser](https://codepen.io/jorgebucaran/pen/MrBgMy?editors=1000).
```html
<!DOCTYPE html>
<html lang="en">
<head>
<script type="module">
import { app } from "https://unpkg.com/hyperapp"
import {
main,
h1,
button,
text,
} from "https://unpkg.com/@hyperapp/html?module"
const Subtract = (state) => ({ ...state, count: state.count - 1 })
const Add = (state) => ({ ...state, count: state.count + 1 })
app({
init: (count = 0) => ({ count }),
view: (state) =>
main([
h1(text(state.count)),
button({ onclick: Subtract }, text("-")),
button({ onclick: Add }, text("+")),
]),
node: document.getElementById("app"),
})
</script>
</head>
<body>
<main id="app"></main>
</body>
</html>
```
> Looking for [@hyperapp/svg](../svg) instead?
## Installation
```console
npm install @hyperapp/html
```
Then with a module bundler like [Rollup](https://rollupjs.org) or [Webpack](https://webpack.js.org) import it in your application and get right down to business.
```js
import { a, form, input } from "@hyperapp/html"
```
Don't want to set up a build step? Import it in a `<script>` tag as a module.
```html
<script type="module">
import { a, form, input } from "https://unpkg.com/@hyperapp/html?module"
</script>
```
## License
[MIT](../../LICENSE.md)

109
packages/html/index.js Normal file
View File

@ -0,0 +1,109 @@
import { h } from "hyperapp"
const EMPTY_ARR = []
const EMPTY_OBJ = {}
const tag = (tag) => (
props = EMPTY_OBJ,
children = props.tag != null || Array.isArray(props) ? props : EMPTY_ARR
) => h(tag, props === children ? EMPTY_OBJ : props, children)
export const a = tag("a")
export const b = tag("b")
export const i = tag("i")
export const p = tag("p")
export const q = tag("q")
export const s = tag("s")
export const br = tag("br")
export const dd = tag("dd")
export const dl = tag("dl")
export const dt = tag("dt")
export const em = tag("em")
export const h1 = tag("h1")
export const h2 = tag("h2")
export const h3 = tag("h3")
export const h4 = tag("h4")
export const h5 = tag("h5")
export const h6 = tag("h6")
export const hr = tag("hr")
export const li = tag("li")
export const ol = tag("ol")
export const rp = tag("rp")
export const rt = tag("rt")
export const td = tag("td")
export const th = tag("th")
export const tr = tag("tr")
export const ul = tag("ul")
export const bdi = tag("bdi")
export const bdo = tag("bdo")
export const col = tag("col")
export const del = tag("del")
export const dfn = tag("dfn")
export const div = tag("div")
export const img = tag("img")
export const ins = tag("ins")
export const kbd = tag("kbd")
export const map = tag("map")
export const nav = tag("nav")
export const pre = tag("pre")
export const rtc = tag("rtc")
export const sub = tag("sub")
export const sup = tag("sup")
export const wbr = tag("wbr")
export const abbr = tag("abbr")
export const area = tag("area")
export const cite = tag("cite")
export const code = tag("code")
export const data = tag("data")
export const form = tag("form")
export const main = tag("main")
export const mark = tag("mark")
export const ruby = tag("ruby")
export const samp = tag("samp")
export const span = tag("span")
export const time = tag("time")
export const aside = tag("aside")
export const audio = tag("audio")
export const input = tag("input")
export const label = tag("label")
export const meter = tag("meter")
export const param = tag("param")
export const small = tag("small")
export const table = tag("table")
export const tbody = tag("tbody")
export const tfoot = tag("tfoot")
export const thead = tag("thead")
export const track = tag("track")
export const video = tag("video")
export const button = tag("button")
export const canvas = tag("canvas")
export const dialog = tag("dialog")
export const figure = tag("figure")
export const footer = tag("footer")
export const header = tag("header")
export const iframe = tag("iframe")
export const legend = tag("legend")
export const object = tag("object")
export const option = tag("option")
export const output = tag("output")
export const select = tag("select")
export const source = tag("source")
export const strong = tag("strong")
export const address = tag("address")
export const article = tag("article")
export const caption = tag("caption")
export const details = tag("details")
export const section = tag("section")
export const summary = tag("summary")
export const picture = tag("picture")
export const colgroup = tag("colgroup")
export const datalist = tag("datalist")
export const fieldset = tag("fieldset")
export const menuitem = tag("menuitem")
export const optgroup = tag("optgroup")
export const progress = tag("progress")
export const textarea = tag("textarea")
export const blockquote = tag("blockquote")
export const figcaption = tag("figcaption")
export { text } from "hyperapp"

View File

@ -0,0 +1,16 @@
{
"name": "@hyperapp/html",
"version": "1.3.1",
"type": "module",
"main": "index.js",
"description": "Write HTML with plain functions",
"repository": "https://github.com/jorgebucaran/hyperapp/tree/main/packages/html",
"license": "MIT",
"keywords": [
"hyperapp",
"html"
],
"peerDependencies": {
"hyperapp": "^2.0.0"
}
}

60
packages/svg/README.md Normal file
View File

@ -0,0 +1,60 @@
# @hyperapp/svg
> Draw SVG with plain functions.
HTML's evil twin. Here's a collection of functions—one for [each SVG tag](https://developer.mozilla.org/en-US/docs/Web/SVG)—to help you get started with SVG in Hyperapp.
Want to draw some circles? [Try this example in your browser](https://codepen.io/jorgebucaran/pen/preYMW?editors=1000).
```html
<!DOCTYPE html>
<html lang="en">
<head>
<script type="module">
import { app } from "https://unpkg.com/hyperapp"
import { main } from "https://unpkg.com/@hyperapp/html?module"
import { svg, use, circle } from "https://unpkg.com/@hyperapp/svg?module"
app({
init: {},
view: () =>
main([
svg({ viewBox: "0 0 30 10" }, [
circle({ id: "symbol", cx: 5, cy: 5, r: 4, stroke: "#0366d6" }),
use({ href: "#symbol", x: 10, fill: "#0366d6" }),
use({ href: "#symbol", x: 20, fill: "white" }),
]),
]),
node: document.getElementById("app"),
})
</script>
</head>
<body>
<main id="app"></main>
</body>
</html>
```
## Installation
```console
npm install @hyperapp/html
```
Then with a module bundler like [Rollup](https://rollupjs.org) or [Webpack](https://webpack.js.org) import it in your application and get right down to business.
```js
import { svg, use, circle } from "@hyperapp/svg"
```
Don't want to set up a build step? Import it in a `<script>` tag as a module.
```html
<script type="module">
import { svg, use, circle } from "https://unpkg.com/@hyperapp/svg?module"
</script>
```
## License
[MIT](../../LICENSE.md)

81
packages/svg/index.js Normal file
View File

@ -0,0 +1,81 @@
import { h } from "hyperapp"
const EMPTY_ARR = []
const EMPTY_OBJ = {}
const tag = (tag) => (
props,
children = props.tag != null || Array.isArray(props) ? props : EMPTY_ARR
) => h(tag, props === children ? EMPTY_OBJ : props, children)
export const a = tag("a")
export const g = tag("g")
export const svg = tag("svg")
export const use = tag("use")
export const set = tag("set")
export const line = tag("line")
export const path = tag("path")
export const rect = tag("rect")
export const desc = tag("desc")
export const defs = tag("defs")
export const mask = tag("mask")
export const tref = tag("tref")
export const font = tag("font")
export const stop = tag("stop")
export const view = tag("view")
export const text_ = tag("text")
export const image = tag("image")
export const mpath = tag("mpath")
export const title = tag("title")
export const glyph = tag("glyph")
export const tspan = tag("tspan")
export const style = tag("style")
export const circle = tag("circle")
export const marker = tag("marker")
export const symbol = tag("symbol")
export const feTile = tag("feTile")
export const cursor = tag("cursor")
export const filter = tag("filter")
export const switch_ = tag("switch")
export const ellipse = tag("ellipse")
export const polygon = tag("polygon")
export const animate = tag("animate")
export const pattern = tag("pattern")
export const feBlend = tag("feBlend")
export const feFlood = tag("feFlood")
export const feFuncA = tag("feFuncA")
export const feFuncB = tag("feFuncB")
export const feFuncG = tag("feFuncG")
export const feFuncR = tag("feFuncR")
export const feImage = tag("feImage")
export const feMerge = tag("feMerge")
export const polyline = tag("polyline")
export const metadata = tag("metadata")
export const altGlyph = tag("altGlyph")
export const glyphRef = tag("glyphRef")
export const textPath = tag("textPath")
export const feOffset = tag("feOffset")
export const clipPath = tag("clipPath")
export const altGlyphDef = tag("altGlyphDef")
export const feComposite = tag("feComposite")
export const feMergeNode = tag("feMergeNode")
export const feSpotLight = tag("feSpotLight")
export const animateColor = tag("animateColor")
export const altGlyphItem = tag("altGlyphItem")
export const feMorphology = tag("feMorphology")
export const feTurbulence = tag("feTurbulence")
export const fePointLight = tag("fePointLight")
export const colorProfile = tag("colorProfile")
export const foreignObject = tag("foreignObject")
export const animateMotion = tag("animateMotion")
export const feColorMatrix = tag("feColorMatrix")
export const linearGradient = tag("linearGradient")
export const radialGradient = tag("radialGradient")
export const feGaussianBlur = tag("feGaussianBlur")
export const feDistantLight = tag("feDistantLight")
export const animateTransform = tag("animateTransform")
export const feConvolveMatrix = tag("feConvolveMatrix")
export const feDiffuseLighting = tag("feDiffuseLighting")
export const feDisplacementMap = tag("feDisplacementMap")
export const feSpecularLighting = tag("feSpecularLighting")
export const feComponentTransfer = tag("feComponentTransfer")

16
packages/svg/package.json Normal file
View File

@ -0,0 +1,16 @@
{
"name": "@hyperapp/svg",
"version": "1.0.1",
"type": "module",
"main": "index.js",
"description": "Draw SVG with plain functions",
"repository": "https://github.com/jorgebucaran/hyperapp/tree/main/packages/svg",
"license": "MIT",
"keywords": [
"hyperapp",
"svg"
],
"peerDependencies": {
"hyperapp": "^2.0.0"
}
}

25
packages/time/README.md Normal file
View File

@ -0,0 +1,25 @@
# @hyperapp/time
> Subscribe to intervals, get the time now.
## Installation
```console
npm install @hyperapp/time
```
```js
import { every, delay, now } from "@hyperapp/time"
```
Or without a build step—import it right in your browser.
```html
<script type="module">
import { every, delay, now } from "https://unpkg.com/@hyperapp/time"
</script>
```
## License
[MIT](../../LICENSE.md)

34
packages/time/index.js Normal file
View File

@ -0,0 +1,34 @@
const timeout = (dispatch, props) =>
setTimeout(() => dispatch(props.action), props.delay)
const interval = (dispatch, props) => {
const id = setInterval(() => {
dispatch(props.action, Date.now())
}, props.delay)
return () => clearInterval(id)
}
const getTime = (dispatch, props) => dispatch(props.action, Date.now())
/**
* @example
* app({
* subscriptions: (state) => [
* // Dispatch RequestResource every delayInMilliseconds
* every(state.delayInMilliseconds, RequestResource),
* ],
* })
*/
export const every = (delay, action) => [interval, { delay, action }]
/**
* @example
* const SlowClap = (state, ms = 1200) => [state, delay(ms, Clap)]
*/
export const delay = (delay, action) => [timeout, { delay, action }]
/**
* @example
* now(NewTime)
*/
export const now = (action) => [getTime, { action }]

View File

@ -0,0 +1,16 @@
{
"name": "@hyperapp/time",
"version": "0.2.0",
"type": "module",
"main": "index.js",
"description": "Subscribe to intervals, get the time now",
"repository": "https://github.com/jorgebucaran/hyperapp/tree/main/packages/time",
"license": "MIT",
"keywords": [
"hyperapp",
"interval",
"delay",
"time",
"now"
]
}