UNPKG

bang.html

Version:

BANG! Web Component custom elements with async templating, smooth syntax and custom void tags!

255 lines (217 loc) 7.08 kB
// notes // this doesn't really work because either we have to reference every value via the full path // missing our goal of reducing typing by introducing proxies // or we have to add a .value() or () to the end of every value, which is weird... // either way we can't use normal JS syntax and ops on values of we use a proxy like this // we can only have normality if we do things the way we do them, with the cloneState and setState // methods const RecursiveStateProxy = (function () { const Target = () => void 0; // We have two handlers, one for state[key], and one for the rest // this is so 'get' will work, because otherwise we don't know when a get path begins // this way it always begins at state class StateHandler { apply(targ, thisArg, args) { throw new TypeError(`State is not a function.`); } get(targ, prop, recv) { if ( prop === isProxy ) { return true; } else { guard('get', this, targ, recv); return new Proxy(Target, new Handler(prop)); } return recv; } set(targ, prop, value, recv) { guard('set', this, targ, recv, value); const key = prop; const state = cloneState(key); setState(key, value); return true; } // not possible construct() { throw new TypeError(`Not constructible.`); } defineProperty() { throw new TypeError(`Not possible.`); } deleteProperty() { throw new TypeError(`Not possible.`); } getOwnPropertyDescriptor() { throw new TypeError(`Not possible.`); } getPrototypeOf() { throw new TypeError(`Not possible.`); } has() { throw new TypeError(`Not possible.`); } isExtensible() { throw new TypeError(`Not possible.`); } ownKeys() { throw new TypeError(`Not possible.`); } preventExtensions() { throw new TypeError(`Not possible.`); } setPrototypeOf() { throw new TypeError(`Not possible.`); } } class Handler { // private #path = []; constructor(firstStop) { this.#path.push(firstStop); } apply(targ, thisArg, args) { guard('apply', this, targ); try { args = JSON.stringify(args); } catch(e) { DEBUG && console.warn(`apply.JSON.stringify error`, e); throw new TypeError(`Arguments need to be able to be serialized by JSON.stringify`); } const path = Array.from(this.#path); this.#path.length = 0; console.log(path, args); const key = path.shift(); const state = cloneState(key); const reboundFunction = resolvePathToFunction(state, path); // note: result of function call is *not* proxied return reboundFunction(...args); } get(targ, prop, recv) { if ( prop === isProxy ) { return true; } else { guard('get', this, targ, recv); this.#path.push(prop); } return recv; } set(targ, prop, value, recv) { guard('set', this, targ, recv, value); const path = Array.from(this.#path); console.log(path, value); this.#path.length = 0; const key = path.shift(); const state = cloneState(key); const setter = resolvePenultimate(state, path); setter[path.pop()] = value; setState(key, state); return true; } // not possible construct() { throw new TypeError(`Not constructible.`); } defineProperty() { throw new TypeError(`Not possible.`); } deleteProperty() { throw new TypeError(`Not possible.`); } getOwnPropertyDescriptor() { throw new TypeError(`Not possible.`); } getPrototypeOf() { throw new TypeError(`Not possible.`); } has() { throw new TypeError(`Not possible.`); } isExtensible() { throw new TypeError(`Not possible.`); } ownKeys() { throw new TypeError(`Not possible.`); } preventExtensions() { throw new TypeError(`Not possible.`); } setPrototypeOf() { throw new TypeError(`Not possible.`); } } const HandlerInstance = new StateHandler; class RecursiveStateProxy { constructor() { const {proxy: StateProxy, revoke} = Proxy.revocable(Target, HandlerInstance); this.detachProxy = revoke; this.receiver = StateProxy; } } return RecursiveStateProxy; /* eslint-disable no-inner-declarations */ // helpers function guard(source, handler, targ, recv, value = null) { if ( !(handler instanceof StateHandler) && !(handler instanceof Handler) ) { throw new TypeError(`${source}: this needs to be the Handler instance`); } if ( targ !== Target ) { throw new TypeError(`${source}: targ needs to be the Target`); } if ( source !== 'apply' && !recv[isProxy] ) { throw new TypeError(`${source}: recv needs to be the State Proxy`); } if ( source === 'set' ) { } } function resolvePenultimate(root, steps) { let link = root; let lastLink; let index = 0; let nextStep = steps[index]; while(link[nextStep] !== undefined) { lastLink = link; link = link[nextStep]; index+=1; nextStep = steps[index]; } if ( index < steps.length ) { console.info(`Path ended before last step reached`, {lastLink, link, nextStep, steps, root}); throw new TypeError(`API method ${steps.join('.')} was not found. Reason: Path was undefined (at ${ steps.slice(0,index+1).join('.') }) before reaching end of: ${ steps.join('.') }` ); } return lastLink; } function resolvePathToFunction(root, steps) { let link = root; let lastLink; let index = 0; let nextStep = steps[index]; while(link[nextStep] !== undefined) { lastLink = link; link = link[nextStep]; index+=1; nextStep = steps[index]; } if ( index < steps.length ) { console.info(`Path ended before last step reached`, {lastLink, link, nextStep, steps, root}); throw new TypeError(`API method ${steps.join('.')} was not found. Reason: Path was undefined (at ${ steps.slice(0,index+1).join('.') }) before reaching end of: ${ steps.join('.') }` ); } if ( typeof link !== "function" ) { console.info(`Path ended at non-function`, {lastLink, nonFunction: link, nextStep, steps, root}); throw new TypeError(`Path needs to end at a function for API call. But ended at: ${link}`); } // bind link's this value to lastLink // as if it was called via <lastLink>.<link>( const reboundFunction = link.bind(lastLink); return reboundFunction; } /* eslint-enable no-inner-declarations */ }());