Skip to content
This repository has been archived by the owner on Apr 29, 2023. It is now read-only.

Hyperapp#2 support #100

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,5 @@ coverage

# Dist
*.gz
.cache
dist/
28 changes: 14 additions & 14 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,41 +21,41 @@
],
"scripts": {
"test": "jest --coverage --no-cache&& tsc -p test/ts",
"build": "npm run bundle && npm run minify",
"bundle": "rollup -i src/index.js -o dist/router.js -f umd -mn hyperappRouter -g hyperapp:hyperapp",
"minify": "uglifyjs dist/router.js -o dist/router.js -mc pure_funcs=['Object.defineProperty'] --source-map includeSources,url=router.js.map",
"build": "parcel build ./src/index.js --out-dir dist --out-file router.js --global hyperappRouter --no-autoinstall",
"prepublish": "npm run build",
"prepare": "npm run build",
"format": "prettier --semi false --write '{src,test}/**/*.js'",
"release": "npm run build && npm test && git commit -am $npm_package_version && git tag $npm_package_version && git push && git push --tags && npm publish"
},
"babel": {
"presets": [
"env"
],
"plugins": [
[
"transform-react-jsx",
"@babel/preset-env",
{
"pragma": "h"
"forceAllTransforms": true
}
]
],
"plugins": [
"@babel/plugin-transform-runtime"
]
},
"jest": {
"testURL": "http://localhost"
},
"devDependencies": {
"@babel/core": "^7.2.2",
"@babel/plugin-transform-runtime": "^7.2.0",
"@babel/preset-env": "^7.2.3",
"babel-jest": "^22.4.3",
"babel-plugin-transform-react-jsx": "^6.24.1",
"babel-preset-env": "^1.6.1",
"hyperapp": "^1.2.5",
"hyperapp": "github:jorgebucaran/hyperapp#V2",
"jest": "^22.4.3",
"parcel-bundler": "^1.11.0",
"path-to-regexp": "^2.4.0",
"prettier": "^1.11.1",
"rollup": "^0.57.1",
"uglify-js": "^3.3.16",
"typescript": "2.8.1"
},
"peerDependencies": {
"hyperapp": "1.2.5"
"hyperapp": "2.0.0"
}
}
64 changes: 21 additions & 43 deletions src/Link.js
Original file line number Diff line number Diff line change
@@ -1,47 +1,25 @@
import { h } from "hyperapp"
import { h } from 'hyperapp'
import { location } from './index'

function getOrigin(loc) {
return loc.protocol + "//" + loc.hostname + (loc.port ? ":" + loc.port : "")
const onClick = (state, link, e) => {
const { pathname } = state.location
if(!(
e.defaultPrevented ||
e.button !== 0 ||
e.altKey ||
e.metaKey ||
e.ctrlKey ||
e.shiftKey ||
e.currentTarget.getAttribute('target') === '_blank'
)){
e.preventDefault()
return link.to !== pathname
? [location, link.to] : state
} else return state
}

function isExternal(anchorElement) {
// Location.origin and HTMLAnchorElement.origin are not
// supported by IE and Safari.
return getOrigin(location) !== getOrigin(anchorElement)
}

export function Link(props, children) {
return function(state, actions) {
var to = props.to
var location = state.location
var onclick = props.onclick
delete props.to
delete props.location

props.href = to
props.onclick = function(e) {
if (onclick) {
onclick(e)
}
if (
e.defaultPrevented ||
e.button !== 0 ||
e.altKey ||
e.metaKey ||
e.ctrlKey ||
e.shiftKey ||
props.target === "_blank" ||
isExternal(e.currentTarget)
) {
} else {
e.preventDefault()

if (to !== location.pathname) {
history.pushState(location.pathname, "", to)
}
}
}

return h("a", props, children)
}
export const Link = (props, children) => {
props.onClick = props.onClick || [ onClick, props ]
props.href = props.to
return h('a', props, children)
}
18 changes: 12 additions & 6 deletions src/Redirect.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
export function Redirect(props) {
return function(state, actions) {
var location = state.location
history.replaceState(props.from || location.pathname, "", props.to)
}
}
import { location, Route } from './index'

const fake = () => () => {}
export const Redirect = (props) => {
const { from, to } = props
const render = typeof to === 'function' ? to : () => to
const match = Route({...props, path: from, render })
return { effect: match ? (props, dispatch) => {
dispatch([location, match])
return () => {}
} : fake }
}
48 changes: 32 additions & 16 deletions src/Route.js
Original file line number Diff line number Diff line change
@@ -1,18 +1,34 @@
import { parseRoute } from "./parseRoute"
import pathToRegexp from 'path-to-regexp'

export function Route(props) {
return function(state, actions) {
var location = state.location
var match = parseRoute(props.path, location.pathname, {
exact: !props.parent
})

return (
match &&
props.render({
match: match,
location: location
})
)
}
const cache = {}
const compile = (path, { exact: end, strict }, keys = []) => {
const id = `${path}/${end}/${strict}`
return cache[id] ? cache[id] : (cache[id] = {
regexp: pathToRegexp(path, keys, { end, strict }), keys
})
}

export const Route = (context, child) => {
const {
path = '',
exact = false,
strict = false,
render = () => child,
location = window.location
} = context

const compiled = compile(path, { exact, strict })
const match = compiled.regexp.exec(location.pathname)
const [ url, ...values ] = match || []
return match ? render({ route: {
params: compiled.keys.reduce((params, key, index) =>
Object.assign(params, {[ key.name ]: values[index]}), {}),
context,
path,
url
}}, (props, ...args) => Route.call(undefined, {
...context, ...props,
render: props.render || undefined,
path: path + (props.path || ''),
}, ...args)) : null
}
12 changes: 0 additions & 12 deletions src/Switch.js

This file was deleted.

25 changes: 25 additions & 0 deletions src/history.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { location } from './index'

const effect = (_, dispatch) => {
const handleLocationChange = () =>
dispatch([ location, window.location.pathname ])
addEventListener('popstate', handleLocationChange)

return ['pushState', 'replaceState'].reduce((next, key) => {
const fn = history[key]
history[key] = (data, tittle, url) => {
!data.ignore && dispatch([location, url])
return fn.call(history, data, tittle, url)
}
return () => {
history[key] = fn
next()
}
}, () => removeEventListener("popstate", handleLocationChange))
}

export default location => {
if(location && window.location.pathname !== location.pathname)
history.pushState({ location, ignore: true }, '', location.pathname)
return { effect }
}
10 changes: 5 additions & 5 deletions src/index.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
export { Link } from "./Link"
export { Route } from "./Route"
export { Switch } from "./Switch"
export { Redirect } from "./Redirect"
export { location } from "./location"
export { location } from './location'
export { default as history } from './history'
export { Redirect } from './Redirect'
export { Route } from './Route'
export { Link } from './Link'
66 changes: 17 additions & 49 deletions src/location.js
Original file line number Diff line number Diff line change
@@ -1,51 +1,19 @@
function wrapHistory(keys) {
return keys.reduce(function(next, key) {
var fn = history[key]

history[key] = function(data, title, url) {
fn.call(this, data, title, url)
dispatchEvent(new CustomEvent("pushstate", { detail: data }))
}

return function() {
history[key] = fn
next && next()
}
}, null)
}

export var location = {
state: {
pathname: window.location.pathname,
previous: window.location.pathname
},
actions: {
go: function(pathname) {
history.pushState(null, "", pathname)
},
set: function(data) {
return data
}
},
subscribe: function(actions) {
function handleLocationChange(e) {
actions.set({
pathname: window.location.pathname,
previous: e.detail
? (window.location.previous = e.detail)
: window.location.previous
})
}

var unwrap = wrapHistory(["pushState", "replaceState"])
export const location = (state, path) => ({
...state,
location: {
pathname: path,
previous: state && state.location ? state.location : null
}
})

addEventListener("pushstate", handleLocationChange)
addEventListener("popstate", handleLocationChange)
location.back = (state, n = 1) => ({
...state,
location: new Array(n).fill(n).reduce(location =>
location.previous || location, state.location)
})

return function() {
removeEventListener("pushstate", handleLocationChange)
removeEventListener("popstate", handleLocationChange)
unwrap()
}
}
}
location.go = url => (state, event) => {
event.preventDefault()
event.stopPropagation()
return [location, url]
}
46 changes: 0 additions & 46 deletions src/parseRoute.js

This file was deleted.