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 3 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"
}
}
66 changes: 22 additions & 44 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.target.getAttribute('target') === '_blank'
sergey-shpak marked this conversation as resolved.
Show resolved Hide resolved
)){
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.