|
|
import * as React from 'react'; |
|
|
import { create } from 'zustand'; |
|
|
import { subscribeWithSelector } from 'zustand/middleware'; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const context = React.createContext(null); |
|
|
function KeyboardControls({ |
|
|
map, |
|
|
children, |
|
|
onChange, |
|
|
domElement |
|
|
}) { |
|
|
const key = map.map(item => item.name + item.keys).join('-'); |
|
|
const useControls = React.useMemo(() => { |
|
|
return create(subscribeWithSelector(() => map.reduce((prev, cur) => ({ |
|
|
...prev, |
|
|
[cur.name]: false |
|
|
}), {}))); |
|
|
}, [key]); |
|
|
const api = React.useMemo(() => [useControls.subscribe, useControls.getState, useControls], [key]); |
|
|
const set = useControls.setState; |
|
|
React.useEffect(() => { |
|
|
const config = map.map(({ |
|
|
name, |
|
|
keys, |
|
|
up |
|
|
}) => ({ |
|
|
keys, |
|
|
up, |
|
|
fn: value => { |
|
|
|
|
|
set({ |
|
|
[name]: value |
|
|
}); |
|
|
|
|
|
if (onChange) onChange(name, value, api[1]()); |
|
|
} |
|
|
})); |
|
|
const keyMap = config.reduce((out, { |
|
|
keys, |
|
|
fn, |
|
|
up = true |
|
|
}) => { |
|
|
keys.forEach(key => out[key] = { |
|
|
fn, |
|
|
pressed: false, |
|
|
up |
|
|
}); |
|
|
return out; |
|
|
}, {}); |
|
|
const downHandler = ({ |
|
|
key, |
|
|
code |
|
|
}) => { |
|
|
const obj = keyMap[key] || keyMap[code]; |
|
|
if (!obj) return; |
|
|
const { |
|
|
fn, |
|
|
pressed, |
|
|
up |
|
|
} = obj; |
|
|
obj.pressed = true; |
|
|
if (up || !pressed) fn(true); |
|
|
}; |
|
|
const upHandler = ({ |
|
|
key, |
|
|
code |
|
|
}) => { |
|
|
const obj = keyMap[key] || keyMap[code]; |
|
|
if (!obj) return; |
|
|
const { |
|
|
fn, |
|
|
up |
|
|
} = obj; |
|
|
obj.pressed = false; |
|
|
if (up) fn(false); |
|
|
}; |
|
|
const source = domElement || window; |
|
|
source.addEventListener('keydown', downHandler, { |
|
|
passive: true |
|
|
}); |
|
|
source.addEventListener('keyup', upHandler, { |
|
|
passive: true |
|
|
}); |
|
|
return () => { |
|
|
source.removeEventListener('keydown', downHandler); |
|
|
source.removeEventListener('keyup', upHandler); |
|
|
}; |
|
|
}, [domElement, key]); |
|
|
return React.createElement(context.Provider, { |
|
|
value: api, |
|
|
children: children |
|
|
}); |
|
|
} |
|
|
function useKeyboardControls(sel) { |
|
|
const [sub, get, store] = React.useContext(context); |
|
|
if (sel) return store(sel);else return [sub, get]; |
|
|
} |
|
|
|
|
|
export { KeyboardControls, useKeyboardControls }; |
|
|
|