Socket
Socket
Sign inDemoInstall

haunted

Package Overview
Dependencies
Maintainers
1
Versions
41
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

haunted - npm Package Compare versions

Comparing version 4.6.0-beta.0 to 4.6.0

lib/component.d.ts

925

haunted.js
import { directive, render } from 'https://unpkg.com/lit-html@^1.0.0/lit-html.js';
export { html, render } from 'https://unpkg.com/lit-html@^1.0.0/lit-html.js';
const symbolFor = typeof Symbol === 'function' ? Symbol.for : str => str;
const phaseSymbol = symbolFor('haunted.phase');
const hookSymbol = symbolFor('haunted.hook');
const updateSymbol = symbolFor('haunted.update');
const commitSymbol = symbolFor('haunted.commit');
const effectsSymbol = symbolFor('haunted.effects');
const contextSymbol = symbolFor('haunted.context');
const contextEvent = 'haunted.context';
let current;
let currentId = 0;
function setCurrent(element) {
current = element;
function setCurrent(state) {
current = state;
}
function clear() {
current = null;
currentId = 0;
current = null;
currentId = 0;
}
function notify() {
let id = currentId;
currentId++;
return id;
return currentId++;
}
//import { render, html } from './lit.js';
const phaseSymbol = Symbol('haunted.phase');
const hookSymbol = Symbol('haunted.hook');
const updateSymbol = Symbol('haunted.update');
const commitSymbol = Symbol('haunted.commit');
const effectsSymbol = Symbol('haunted.effects');
const layoutEffectsSymbol = Symbol('haunted.layoutEffects');
const contextEvent = 'haunted.context';
const defer = Promise.resolve().then.bind(Promise.resolve());
function scheduler() {
let tasks = [];
let id;
function runTasks() {
id = null;
let t = tasks;
tasks = [];
for(var i = 0, len = t.length; i < len; i++) {
t[i]();
class State {
constructor(update, host) {
this.update = update;
this.host = host;
this[hookSymbol] = new Map();
this[effectsSymbol] = [];
this[layoutEffectsSymbol] = [];
}
}
return function(task) {
tasks.push(task);
if(id == null) {
id = defer(runTasks);
run(cb) {
setCurrent(this);
let res = cb();
clear();
return res;
}
};
_runEffects(phase) {
let effects = this[phase];
setCurrent(this);
for (let effect of effects) {
effect.call(this);
}
clear();
}
runEffects() {
this._runEffects(effectsSymbol);
}
runLayoutEffects() {
this._runEffects(layoutEffectsSymbol);
}
teardown() {
let hooks = this[hookSymbol];
hooks.forEach(hook => {
if (typeof hook.teardown === 'function') {
hook.teardown();
}
});
}
}
function makeContainer(render$$1) {
const read = scheduler();
const write = scheduler();
class Container {
constructor(renderer, frag, host) {
this.renderer = renderer;
this.frag = frag;
this.host = host || frag;
this[hookSymbol] = new Map();
this[phaseSymbol] = null;
this._updateQueued = false;
const defer = Promise.resolve().then.bind(Promise.resolve());
function runner() {
let tasks = [];
let id;
function runTasks() {
id = null;
let t = tasks;
tasks = [];
for (var i = 0, len = t.length; i < len; i++) {
t[i]();
}
}
return function (task) {
tasks.push(task);
if (id == null) {
id = defer(runTasks);
}
};
}
const read = runner();
const write = runner();
class BaseScheduler {
constructor(renderer, host) {
this.renderer = renderer;
this.host = host;
this.state = new State(this.update.bind(this), host);
this[phaseSymbol] = null;
this._updateQueued = false;
}
update() {
if(this._updateQueued) return;
read(() => {
let result = this.handlePhase(updateSymbol);
write(() => {
this.handlePhase(commitSymbol, result);
if(this[effectsSymbol]) {
if (this._updateQueued)
return;
read(() => {
let result = this.handlePhase(updateSymbol);
write(() => {
this.handlePhase(effectsSymbol);
this.handlePhase(commitSymbol, result);
write(() => {
this.handlePhase(effectsSymbol);
});
});
}
this._updateQueued = false;
});
this._updateQueued = false;
});
this._updateQueued = true;
this._updateQueued = true;
}
handlePhase(phase, arg) {
this[phaseSymbol] = phase;
switch(phase) {
case commitSymbol: return this.commit(arg);
case updateSymbol: return this.render();
case effectsSymbol: return this.runEffects(effectsSymbol);
}
this[phaseSymbol] = null;
this[phaseSymbol] = phase;
switch (phase) {
case commitSymbol:
this.commit(arg);
this.runEffects(layoutEffectsSymbol);
return;
case updateSymbol: return this.render();
case effectsSymbol: return this.runEffects(effectsSymbol);
}
this[phaseSymbol] = null;
}
commit(result) {
render$$1(result, this.frag);
this.runEffects(commitSymbol);
}
render() {
setCurrent(this);
let result = this.args ?
this.renderer.apply(this.host, this.args) :
this.renderer.call(this.host, this.host);
clear();
return result;
return this.state.run(() => this.renderer.call(this.host, this.host));
}
runEffects(symbol) {
let effects = this[symbol];
if(effects) {
setCurrent(this);
for(let effect of effects) {
effect.call(this);
}
clear();
}
runEffects(phase) {
this.state._runEffects(phase);
}
teardown() {
let hooks = this[hookSymbol];
hooks.forEach((hook) => {
if (typeof hook.teardown === 'function') {
hook.teardown();
}
});
this.state.teardown();
}
}
return Container;
}
function toCamelCase(val = '') {
return val.indexOf('-') === -1 ? val.toLowerCase() : val.toLowerCase().split('-').reduce((out, part) => {
return out ? out + part.charAt(0).toUpperCase() + part.slice(1) : part;
},'')
}
function makeComponent(Container) {
function component(renderer, baseElementOrOptions, options) {
const BaseElement = (options || baseElementOrOptions || {}).baseElement || HTMLElement;
const {observedAttributes = [], useShadowDOM = true, shadowRootInit = {}} = options || baseElementOrOptions || {};
class Element extends BaseElement {
static get observedAttributes() {
return renderer.observedAttributes || observedAttributes || [];
}
constructor() {
super();
if (useShadowDOM === false) {
this._container = new Container(renderer, this);
} else {
this.attachShadow({ mode: "open", ...shadowRootInit});
this._container = new Container(renderer, this.shadowRoot, this);
const toCamelCase = (val = '') => val.replace(/-+([a-z])?/g, (_, char) => char ? char.toUpperCase() : '');
function makeComponent(render) {
class Scheduler extends BaseScheduler {
constructor(renderer, frag, host) {
super(renderer, host || frag);
this.frag = frag;
}
}
connectedCallback() {
this._container.update();
}
disconnectedCallback() {
this._container.teardown();
}
attributeChangedCallback(name, _, newValue) {
let val = newValue === '' ? true : newValue;
Reflect.set(this, toCamelCase(name), val);
}
}
function reflectiveProp(initialValue) {
let value = initialValue;
return Object.freeze({
enumerable: true,
configurable: true,
get() {
return value;
},
set(newValue) {
value = newValue;
this._container.update();
commit(result) {
render(result, this.frag);
}
})
}
const proto = new Proxy(BaseElement.prototype, {
set(target, key, value, receiver) {
if(key in target) {
Reflect.set(target, key, value);
function component(renderer, baseElementOrOptions, options) {
const BaseElement = (options || baseElementOrOptions || {}).baseElement || HTMLElement;
const { observedAttributes = [], useShadowDOM = true, shadowRootInit = {} } = options || baseElementOrOptions || {};
class Element extends BaseElement {
constructor() {
super();
if (useShadowDOM === false) {
this._scheduler = new Scheduler(renderer, this);
}
else {
this.attachShadow({ mode: 'open', ...shadowRootInit });
this._scheduler = new Scheduler(renderer, this.shadowRoot, this);
}
}
static get observedAttributes() {
return renderer.observedAttributes || observedAttributes || [];
}
connectedCallback() {
this._scheduler.update();
}
disconnectedCallback() {
this._scheduler.teardown();
}
attributeChangedCallback(name, _, newValue) {
let val = newValue === '' ? true : newValue;
Reflect.set(this, toCamelCase(name), val);
}
}
let desc;
if(typeof key === 'symbol' || key[0] === '_') {
desc = {
enumerable: true,
configurable: true,
writable: true,
value
};
} else {
desc = reflectiveProp(value);
function reflectiveProp(initialValue) {
let value = initialValue;
return Object.freeze({
enumerable: true,
configurable: true,
get() {
return value;
},
set(newValue) {
value = newValue;
this._scheduler.update();
}
});
}
Object.defineProperty(receiver, key, desc);
if(desc.set) {
desc.set.call(receiver, value);
}
return true;
}
});
Object.setPrototypeOf(Element.prototype, proto);
return Element;
}
return component;
const proto = new Proxy(BaseElement.prototype, {
getPrototypeOf(target) {
return target;
},
set(target, key, value, receiver) {
if (key in target) {
Reflect.set(target, key, value);
}
let desc;
if (typeof key === 'symbol' || key[0] === '_') {
desc = {
enumerable: true,
configurable: true,
writable: true,
value
};
}
else {
desc = reflectiveProp(value);
}
Object.defineProperty(receiver, key, desc);
if (desc.set) {
desc.set.call(receiver, value);
}
return true;
}
});
Object.setPrototypeOf(Element.prototype, proto);
return Element;
}
return component;
}
class Hook {
constructor(id, el) {
this.id = id;
this.el = el;
}
constructor(id, state) {
this.id = id;
this.state = state;
}
}
function use(Hook, ...args) {
let id = notify();
let hooks = current[hookSymbol];
let hook = hooks.get(id);
if(!hook) {
hook = new Hook(id, current, ...args);
hooks.set(id, hook);
}
return hook.update(...args);
let id = notify();
let hooks = current[hookSymbol];
let hook = hooks.get(id);
if (!hook) {
hook = new Hook(id, current, ...args);
hooks.set(id, hook);
}
return hook.update(...args);
}
function hook(Hook) {
return use.bind(null, Hook);
return use.bind(null, Hook);
}
function setEffects(el, cb) {
if(!(effectsSymbol in el)) {
el[effectsSymbol] = [];
}
el[effectsSymbol].push(cb);
function createEffect(setEffects) {
return hook(class extends Hook {
constructor(id, state, ignored1, ignored2) {
super(id, state);
setEffects(state, this);
}
update(callback, values) {
this.callback = callback;
this.lastValues = this.values;
this.values = values;
}
call() {
if (!this.values || this.hasChanged()) {
this.run();
}
}
run() {
this.teardown();
this._teardown = this.callback.call(this.state);
}
teardown() {
if (this._teardown) {
this._teardown();
}
}
hasChanged() {
return !this.lastValues || this.values.some((value, i) => this.lastValues[i] !== value);
}
});
}
const useEffect = hook(class extends Hook {
constructor(id, el) {
super(id, el);
this.values = false;
setEffects(el, this);
}
update(callback, values) {
this.callback = callback;
this.lastValues = this.values;
this.values = values;
}
call() {
if(this.values) {
if(this.hasChanged()) {
this.run();
}
} else {
this.run();
}
}
run() {
this.teardown();
this._teardown = this.callback.call(this.el);
}
teardown() {
if(this._teardown) {
this._teardown();
}
}
hasChanged() {
return this.lastValues === false || this.values.some((value, i) => this.lastValues[i] !== value);
}
});
function setContexts(el, consumer) {
if(!(contextSymbol in el)) {
el[contextSymbol] = [];
}
el[contextSymbol].push(consumer);
function setEffects(state, cb) {
state[effectsSymbol].push(cb);
}
const useEffect = createEffect(setEffects);
const useContext = hook(class extends Hook {
constructor(id, el) {
super(id, el);
setContexts(el, this);
this._updater = this._updater.bind(this);
this._ranEffect = false;
this._unsubscribe = null;
setEffects(el, this);
}
update(Context) {
if (this.el.virtual) {
throw new Error('can\'t be used with virtual components');
constructor(id, state, _) {
super(id, state);
this._updater = this._updater.bind(this);
this._ranEffect = false;
this._unsubscribe = null;
setEffects(state, this);
}
if (this.Context !== Context) {
this._subscribe(Context);
this.Context = Context;
update(Context) {
if (this.state.virtual) {
throw new Error('can\'t be used with virtual components');
}
if (this.Context !== Context) {
this._subscribe(Context);
this.Context = Context;
}
return this.value;
}
return this.value;
}
call() {
if(!this._ranEffect) {
this._ranEffect = true;
if(this._unsubscribe) this._unsubscribe();
this._subscribe(this.Context);
this.el.update();
call() {
if (!this._ranEffect) {
this._ranEffect = true;
if (this._unsubscribe)
this._unsubscribe();
this._subscribe(this.Context);
this.state.update();
}
}
}
_updater(value) {
this.value = value;
this.el.update();
}
_subscribe(Context) {
const detail = { Context, callback: this._updater };
this.el.host.dispatchEvent(new CustomEvent(contextEvent, {
detail, // carrier
bubbles: true, // to bubble up in tree
cancelable: true, // to be able to cancel
composed: true, // to pass ShadowDOM boundaries
}));
const { unsubscribe, value } = detail;
this.value = unsubscribe ? value : Context.defaultValue;
this._unsubscribe = unsubscribe;
}
teardown() {
if (this._unsubscribe) {
this._unsubscribe();
_updater(value) {
this.value = value;
this.state.update();
}
}
_subscribe(Context) {
const detail = { Context, callback: this._updater };
this.state.host.dispatchEvent(new CustomEvent(contextEvent, {
detail,
bubbles: true,
cancelable: true,
composed: true,
}));
const { unsubscribe, value } = detail;
this.value = unsubscribe ? value : Context.defaultValue;
this._unsubscribe = unsubscribe;
}
teardown() {
if (this._unsubscribe) {
this._unsubscribe();
}
}
});
function makeContext(component) {
return (defaultValue) => {
const Context = {
Provider: class extends HTMLElement {
constructor() {
super();
this.listeners = new Set();
this.addEventListener(contextEvent, this);
}
disconnectedCallback() {
this.removeEventListener(contextEvent, this);
}
handleEvent(event) {
const { detail } = event;
if (detail.Context === Context) {
detail.value = this.value;
detail.unsubscribe = this.unsubscribe.bind(this, detail.callback);
this.listeners.add(detail.callback);
event.stopPropagation();
}
}
unsubscribe(callback) {
if(this.listeners.has(callback)) {
this.listeners.delete(callback);
}
}
set value(value) {
this._value = value;
for(let callback of this.listeners) {
callback(value);
}
}
get value() {
return this._value;
}
},
Consumer: component(function ({ render: render$$1 }) {
const context = useContext(Context);
return render$$1(context);
}),
defaultValue
return (defaultValue) => {
const Context = {
Provider: class extends HTMLElement {
constructor() {
super();
this.listeners = new Set();
this.addEventListener(contextEvent, this);
}
disconnectedCallback() {
this.removeEventListener(contextEvent, this);
}
handleEvent(event) {
const { detail } = event;
if (detail.Context === Context) {
detail.value = this.value;
detail.unsubscribe = this.unsubscribe.bind(this, detail.callback);
this.listeners.add(detail.callback);
event.stopPropagation();
}
}
unsubscribe(callback) {
this.listeners.delete(callback);
}
set value(value) {
this._value = value;
for (let callback of this.listeners) {
callback(value);
}
}
get value() {
return this._value;
}
},
Consumer: component(function ({ render }) {
const context = useContext(Context);
return render(context);
}),
defaultValue,
};
return Context;
};
return Context;
};
}
const useMemo = hook(class extends Hook {
constructor(id, el, fn, values) {
super(id, el);
this.value = fn();
this.values = values;
}
update(fn, values) {
if(this.hasChanged(values)) {
this.values = values;
this.value = fn();
constructor(id, state, fn, values) {
super(id, state);
this.value = fn();
this.values = values;
}
return this.value;
}
hasChanged(values) {
return values.some((value, i) => this.values[i] !== value);
}
update(fn, values) {
if (this.hasChanged(values)) {
this.values = values;
this.value = fn();
}
return this.value;
}
hasChanged(values) {
return values.some((value, i) => this.values[i] !== value);
}
});

@@ -447,145 +386,127 @@

function setLayoutEffects(state, cb) {
state[layoutEffectsSymbol].push(cb);
}
const useLayoutEffect = createEffect(setLayoutEffects);
const useState = hook(class extends Hook {
constructor(id, el, initialValue) {
super(id, el);
this.updater = this.updater.bind(this);
if(typeof initialValue === 'function') {
initialValue = initialValue();
constructor(id, state, initialValue) {
super(id, state);
this.updater = this.updater.bind(this);
if (typeof initialValue === 'function') {
initialValue = initialValue();
}
this.makeArgs(initialValue);
}
this.makeArgs(initialValue);
}
update() {
return this.args;
}
updater(value) {
if (typeof value === "function") {
const updaterFn = value;
const [previousValue] = this.args;
value = updaterFn(previousValue);
update() {
return this.args;
}
this.makeArgs(value);
this.el.update();
}
makeArgs(value) {
this.args = Object.freeze([value, this.updater]);
}
updater(value) {
if (typeof value === 'function') {
const updaterFn = value;
const [previousValue] = this.args;
value = updaterFn(previousValue);
}
this.makeArgs(value);
this.state.update();
}
makeArgs(value) {
this.args = Object.freeze([value, this.updater]);
}
});
const useReducer = hook(class extends Hook {
constructor(id, el, _, initialState) {
super(id, el);
this.dispatch = this.dispatch.bind(this);
this.state = initialState;
}
update(reducer) {
this.reducer = reducer;
return [this.state, this.dispatch];
}
dispatch(action) {
this.state = this.reducer(this.state, action);
this.el.update();
}
constructor(id, state, _, initialState) {
super(id, state);
this.dispatch = this.dispatch.bind(this);
this.currentState = initialState;
}
update(reducer) {
this.reducer = reducer;
return [this.currentState, this.dispatch];
}
dispatch(action) {
this.currentState = this.reducer(this.currentState, action);
this.state.update();
}
});
const useRef = (initialValue) => {
return useMemo(() => {
return {
current: initialValue
};
}, []);
};
const useRef = (initialValue) => useMemo(() => ({
current: initialValue
}), []);
function haunted({ render: render$$1 }) {
const Container = makeContainer(render$$1);
const component = makeComponent(Container);
const createContext = makeContext(component);
return { Container, component, createContext };
function haunted({ render }) {
const component = makeComponent(render);
const createContext = makeContext(component);
return { component, createContext };
}
const includes = Array.prototype.includes;
function makeVirtual(Container) {
const partToContainer = new WeakMap();
const containerToPart = new WeakMap();
class DirectiveContainer extends Container {
constructor(renderer, part) {
super(renderer, part);
this.virtual = true;
function makeVirtual() {
const partToScheduler = new WeakMap();
const schedulerToPart = new WeakMap();
class Scheduler extends BaseScheduler {
constructor(renderer, part) {
super(renderer, part);
this.state.virtual = true;
}
render() {
return this.state.run(() => this.renderer.apply(this.host, this.args));
}
commit(result) {
this.host.setValue(result);
this.host.commit();
}
teardown() {
super.teardown();
let part = schedulerToPart.get(this);
partToScheduler.delete(part);
}
}
commit(result) {
this.host.setValue(result);
this.host.commit();
}
teardown() {
super.teardown();
let part = containerToPart.get(this);
partToContainer.delete(part);
}
}
function virtual(renderer) {
function factory(...args) {
return part => {
let cont = partToContainer.get(part);
if(!cont) {
cont = new DirectiveContainer(renderer, part);
partToContainer.set(part, cont);
containerToPart.set(cont, part);
teardownOnRemove(cont, part);
function virtual(renderer) {
function factory(...args) {
return (part) => {
let cont = partToScheduler.get(part);
if (!cont) {
cont = new Scheduler(renderer, part);
partToScheduler.set(part, cont);
schedulerToPart.set(cont, part);
teardownOnRemove(cont, part);
}
cont.args = args;
cont.update();
};
}
cont.args = args;
cont.update();
};
return directive(factory);
}
return directive(factory);
}
return virtual;
return virtual;
}
function teardownOnRemove(cont, part, node = part.startNode) {
let frag = node.parentNode;
let mo = new MutationObserver(mutations => {
for(let mutation of mutations) {
if(includes.call(mutation.removedNodes, node)) {
mo.disconnect();
if(node.parentNode instanceof ShadowRoot) {
teardownOnRemove(cont, part);
} else {
cont.teardown();
let frag = node.parentNode;
let mo = new MutationObserver(mutations => {
for (let mutation of mutations) {
if (includes.call(mutation.removedNodes, node)) {
mo.disconnect();
if (node.parentNode instanceof ShadowRoot) {
teardownOnRemove(cont, part);
}
else {
cont.teardown();
}
break;
}
else if (includes.call(mutation.addedNodes, node.nextSibling)) {
mo.disconnect();
teardownOnRemove(cont, part, node.nextSibling || undefined);
break;
}
}
break;
} else if(includes.call(mutation.addedNodes, node.nextSibling)) {
mo.disconnect();
teardownOnRemove(cont, part, node.nextSibling);
break;
}
}
});
mo.observe(frag, { childList: true });
});
mo.observe(frag, { childList: true });
}
const { Container, component, createContext } = haunted({
render(what, where) {
render(what, where);
}
});
const { component, createContext } = haunted({ render });
const virtual = makeVirtual();
const virtual = makeVirtual(Container);
export default haunted;
export { component, createContext, virtual, useCallback, useEffect, useState, useReducer, useMemo, useContext, useRef, hook, Hook };
export { BaseScheduler, Hook, State, component, createContext, hook, useCallback, useContext, useEffect, useLayoutEffect, useMemo, useReducer, useRef, useState, virtual };

@@ -1,91 +0,88 @@

function toCamelCase(val = '') {
return val.indexOf('-') === -1 ? val.toLowerCase() : val.toLowerCase().split('-').reduce((out, part) => {
return out ? out + part.charAt(0).toUpperCase() + part.slice(1) : part;
},'')
}
function makeComponent(Scheduler) {
function component(renderer, baseElementOrOptions, options) {
const BaseElement = (options || baseElementOrOptions || {}).baseElement || HTMLElement;
const {observedAttributes = [], useShadowDOM = true, shadowRootInit = {}} = options || baseElementOrOptions || {};
class Element extends BaseElement {
static get observedAttributes() {
return renderer.observedAttributes || observedAttributes || [];
}
constructor() {
super();
if (useShadowDOM === false) {
this._scheduler = new Scheduler(renderer, this);
} else {
this.attachShadow({ mode: "open", ...shadowRootInit});
this._scheduler = new Scheduler(renderer, this.shadowRoot, this);
import { BaseScheduler } from './scheduler.js';
const toCamelCase = (val = '') => val.replace(/-+([a-z])?/g, (_, char) => char ? char.toUpperCase() : '');
function makeComponent(render) {
class Scheduler extends BaseScheduler {
constructor(renderer, frag, host) {
super(renderer, host || frag);
this.frag = frag;
}
}
connectedCallback() {
this._scheduler.update();
}
disconnectedCallback() {
this._scheduler.teardown();
}
attributeChangedCallback(name, _, newValue) {
let val = newValue === '' ? true : newValue;
Reflect.set(this, toCamelCase(name), val);
}
};
function reflectiveProp(initialValue) {
let value = initialValue;
return Object.freeze({
enumerable: true,
configurable: true,
get() {
return value;
},
set(newValue) {
value = newValue;
this._scheduler.update();
commit(result) {
render(result, this.frag);
}
})
}
const proto = new Proxy(BaseElement.prototype, {
set(target, key, value, receiver) {
if(key in target) {
Reflect.set(target, key, value);
function component(renderer, baseElementOrOptions, options) {
const BaseElement = (options || baseElementOrOptions || {}).baseElement || HTMLElement;
const { observedAttributes = [], useShadowDOM = true, shadowRootInit = {} } = options || baseElementOrOptions || {};
class Element extends BaseElement {
constructor() {
super();
if (useShadowDOM === false) {
this._scheduler = new Scheduler(renderer, this);
}
else {
this.attachShadow({ mode: 'open', ...shadowRootInit });
this._scheduler = new Scheduler(renderer, this.shadowRoot, this);
}
}
static get observedAttributes() {
return renderer.observedAttributes || observedAttributes || [];
}
connectedCallback() {
this._scheduler.update();
}
disconnectedCallback() {
this._scheduler.teardown();
}
attributeChangedCallback(name, _, newValue) {
let val = newValue === '' ? true : newValue;
Reflect.set(this, toCamelCase(name), val);
}
}
let desc;
if(typeof key === 'symbol' || key[0] === '_') {
desc = {
enumerable: true,
configurable: true,
writable: true,
value
};
} else {
desc = reflectiveProp(value);
;
function reflectiveProp(initialValue) {
let value = initialValue;
return Object.freeze({
enumerable: true,
configurable: true,
get() {
return value;
},
set(newValue) {
value = newValue;
this._scheduler.update();
}
});
}
Object.defineProperty(receiver, key, desc);
if(desc.set) {
desc.set.call(receiver, value);
}
return true;
}
});
Object.setPrototypeOf(Element.prototype, proto);
return Element;
}
return component;
const proto = new Proxy(BaseElement.prototype, {
getPrototypeOf(target) {
return target;
},
set(target, key, value, receiver) {
if (key in target) {
Reflect.set(target, key, value);
}
let desc;
if (typeof key === 'symbol' || key[0] === '_') {
desc = {
enumerable: true,
configurable: true,
writable: true,
value
};
}
else {
desc = reflectiveProp(value);
}
Object.defineProperty(receiver, key, desc);
if (desc.set) {
desc.set.call(receiver, value);
}
return true;
}
});
Object.setPrototypeOf(Element.prototype, proto);
return Element;
}
return component;
}
export { makeComponent };

@@ -1,16 +0,12 @@

import { makeScheduler } from './scheduler.js';
import { makeComponent } from './component.js';
import { makeContext } from './create-context.js';
function haunted({ render }) {
const Scheduler = makeScheduler(render);
const component = makeComponent(Scheduler);
const createContext = makeContext(component);
return { Scheduler, component, createContext };
const component = makeComponent(render);
const createContext = makeContext(component);
return { component, createContext };
}
export { haunted as default };
export { useCallback } from "./use-callback.js";
export { useCallback } from './use-callback.js';
export { useEffect } from './use-effect.js';
export { useLayoutEffect } from './use-layout-effect.js';
export { useState } from './use-state.js';

@@ -22,2 +18,3 @@ export { useReducer } from './use-reducer.js';

export { hook, Hook } from './hook.js';
export { BaseScheduler } from './scheduler.js';
export { State } from './state.js';
import { contextEvent } from './symbols.js';
import { useContext } from './use-context.js';
function makeContext(component) {
return (defaultValue) => {
const Context = {
Provider: class extends HTMLElement {
constructor() {
super();
this.listeners = new Set();
this.addEventListener(contextEvent, this);
}
disconnectedCallback() {
this.removeEventListener(contextEvent, this);
}
handleEvent(event) {
const { detail } = event;
if (detail.Context === Context) {
detail.value = this.value;
detail.unsubscribe = this.unsubscribe.bind(this, detail.callback);
this.listeners.add(detail.callback);
event.stopPropagation();
}
}
unsubscribe(callback) {
if(this.listeners.has(callback)) {
this.listeners.delete(callback);
}
}
set value(value) {
this._value = value;
for(let callback of this.listeners) {
callback(value);
}
}
get value() {
return this._value;
}
},
Consumer: component(function ({ render }) {
const context = useContext(Context);
return render(context);
}),
defaultValue
return (defaultValue) => {
const Context = {
Provider: class extends HTMLElement {
constructor() {
super();
this.listeners = new Set();
this.addEventListener(contextEvent, this);
}
disconnectedCallback() {
this.removeEventListener(contextEvent, this);
}
handleEvent(event) {
const { detail } = event;
if (detail.Context === Context) {
detail.value = this.value;
detail.unsubscribe = this.unsubscribe.bind(this, detail.callback);
this.listeners.add(detail.callback);
event.stopPropagation();
}
}
unsubscribe(callback) {
this.listeners.delete(callback);
}
set value(value) {
this._value = value;
for (let callback of this.listeners) {
callback(value);
}
}
get value() {
return this._value;
}
},
Consumer: component(function ({ render }) {
const context = useContext(Context);
return render(context);
}),
defaultValue,
};
return Context;
};
return Context;
};
}
export { makeContext };
export { makeContext };

@@ -1,60 +0,3 @@

import { html, render, TemplateResult } from 'lit-html';
import { DirectiveFactory } from "lit-html/lib/directive";
export { html, render, TemplateResult, DirectiveFactory }
export type ComponentLike = HTMLElement | ShadowRoot;
export type ComponentType<P, T extends ComponentLike = HTMLElement> = new(...args: any[]) => T & P;
type Options = {
useShadowDOM: boolean,
shadowRootInit?: {
mode?: string
delegatesFocus?: boolean,
}
}
export function component<P, T extends ComponentLike = HTMLElement>(
renderer: (this: T, el: P & T) => TemplateResult | void,
BaseElement?: new(...args: any[]) => T,
options?: Options): ComponentType<P, T>;
export function component<P, T extends ComponentLike = HTMLElement>(
renderer: (this: T, el: P & T) => TemplateResult | void,
options?: Options & { baseElement: new(...args: any[]) => T} ): ComponentType<P, T>;
export function useCallback<T extends Function>(fn: T, inputs: any[]): T;
export function useEffect(fn: () => void | VoidFunction, inputs?: any[]): void;
export type StateUpdater<T> = (value: T | ((state?: T) => T)) => void;
export function useState<T>(intialValue?: T): [T, StateUpdater<T>];
export function useReducer<S = any, A = any>(reducer: (state: S, action: A) => S, initialState: S): [S, (action: A) => void];
export function useMemo<T>(fn: () => T, values: any[]): T;
export function useRef<T>(initialValue: T): { current: T};
export function virtual<P, T extends ComponentLike = HTMLElement>(renderer: (this: T, el: P) => TemplateResult | void): () => DirectiveFactory;
export interface Context<T> {
Provider: ComponentType<T>;
Consumer: ComponentType<T>;
defaultValue: T;
}
export function createContext<T = any>(defaultValue: T): Context<T>
export function useContext<T>(Context: Context<T>): T
export class Hook<T extends ComponentLike = HTMLElement> {
id: number;
el: T;
constructor(id: number, el: T);
}
interface HookWithLifecycle<T extends ComponentLike = HTMLElement, P extends any[] = null, R = void> extends Hook<T> {
update?(...args: P): R;
teardown?(): void;
}
export function hook<T extends ComponentLike = HTMLElement>(Hook: new(id: number, el: T) => Hook<T>): () => void;
export function hook<T extends ComponentLike = HTMLElement, P extends any[] = void[], R = void>(Hook: new (id: number, el: T, ...args: P) => HookWithLifecycle<T, P, R>): (...args: P) => R;
export { html, render, component, createContext, virtual } from './lit-haunted';
export * from './core';
export { default } from './core';
import { current, notify } from './interface.js';
import { hookSymbol } from './symbols.js';
class Hook {
constructor(id, state) {
this.id = id;
this.el = this.state = state;
}
constructor(id, state) {
this.id = id;
this.state = state;
}
}
function use(Hook, ...args) {
let id = notify();
let hooks = current[hookSymbol];
let hook = hooks.get(id);
if(!hook) {
hook = new Hook(id, current, ...args);
hooks.set(id, hook);
}
return hook.update(...args);
let id = notify();
let hooks = current[hookSymbol];
let hook = hooks.get(id);
if (!hook) {
hook = new Hook(id, current, ...args);
hooks.set(id, hook);
}
return hook.update(...args);
}
function hook(Hook) {
return use.bind(null, Hook);
return use.bind(null, Hook);
}
export { hook, Hook };
export { hook, Hook };
let current;
let currentId = 0;
function setCurrent(element) {
current = element;
function setCurrent(state) {
current = state;
}
function clear() {
current = null;
currentId = 0;
current = null;
currentId = 0;
}
function notify() {
let id = currentId;
currentId++;
return id;
return currentId++;
}
export { clear, current, setCurrent, notify }
export { clear, current, setCurrent, notify };
import { html, render } from 'lit-html';
import haunted from './core.js';
import { makeVirtual } from './virtual.js';
const { Scheduler, component, createContext } = haunted({
render(what, where) {
render(what, where);
}
});
const virtual = makeVirtual(Scheduler);
export {
component,
createContext,
virtual,
html,
render
};
const { component, createContext } = haunted({ render });
const virtual = makeVirtual();
export { component, createContext, virtual, html, render };

@@ -1,93 +0,69 @@

import { commitSymbol, phaseSymbol, updateSymbol, effectsSymbol } from './symbols.js';
import { State } from './state.js';
import { commitSymbol, phaseSymbol, updateSymbol, effectsSymbol, layoutEffectsSymbol } from './symbols.js';
const defer = Promise.resolve().then.bind(Promise.resolve());
function runner() {
let tasks = [];
let id;
function runTasks() {
id = null;
let t = tasks;
tasks = [];
for(var i = 0, len = t.length; i < len; i++) {
t[i]();
let tasks = [];
let id;
function runTasks() {
id = null;
let t = tasks;
tasks = [];
for (var i = 0, len = t.length; i < len; i++) {
t[i]();
}
}
}
return function(task) {
tasks.push(task);
if(id == null) {
id = defer(runTasks);
}
};
return function (task) {
tasks.push(task);
if (id == null) {
id = defer(runTasks);
}
};
}
function makeScheduler(render) {
const read = runner();
const write = runner();
class Scheduler {
constructor(renderer, frag, host) {
this.renderer = renderer;
this.frag = frag;
this.host = host || frag;
this[phaseSymbol] = null;
this._updateQueued = false;
this.state = new State(this.update.bind(this), host);
const read = runner();
const write = runner();
class BaseScheduler {
constructor(renderer, host) {
this.renderer = renderer;
this.host = host;
this.state = new State(this.update.bind(this), host);
this[phaseSymbol] = null;
this._updateQueued = false;
}
update() {
if(this._updateQueued) return;
read(() => {
let result = this.handlePhase(updateSymbol);
write(() => {
this.handlePhase(commitSymbol, result);
if(this.state[effectsSymbol]) {
if (this._updateQueued)
return;
read(() => {
let result = this.handlePhase(updateSymbol);
write(() => {
this.handlePhase(effectsSymbol);
this.handlePhase(commitSymbol, result);
write(() => {
this.handlePhase(effectsSymbol);
});
});
}
this._updateQueued = false;
});
this._updateQueued = false;
});
this._updateQueued = true;
this._updateQueued = true;
}
handlePhase(phase, arg) {
this[phaseSymbol] = phase;
switch(phase) {
case commitSymbol: return this.commit(arg);
case updateSymbol: return this.render();
case effectsSymbol: return this.runEffects(effectsSymbol);
}
this[phaseSymbol] = null;
this[phaseSymbol] = phase;
switch (phase) {
case commitSymbol:
this.commit(arg);
this.runEffects(layoutEffectsSymbol);
return;
case updateSymbol: return this.render();
case effectsSymbol: return this.runEffects(effectsSymbol);
}
this[phaseSymbol] = null;
}
render() {
return this.state.run(() => {
return this.args ?
this.renderer.apply(this.host, this.args) :
this.renderer.call(this.host, this.host);
});
return this.state.run(() => this.renderer.call(this.host, this.host));
}
runEffects() {
this.state.runEffects();
runEffects(phase) {
this.state._runEffects(phase);
}
commit(result) {
render(result, this.frag);
}
teardown() {
this.state.teardown();
this.state.teardown();
}
}
return Scheduler;
}
export { makeScheduler };
export { BaseScheduler };

@@ -1,40 +0,40 @@

import { hookSymbol, effectsSymbol } from './symbols.js';
import { setCurrent, clear } from './interface.js';
import { hookSymbol, effectsSymbol, layoutEffectsSymbol } from './symbols.js';
class State {
constructor(update, host) {
this.update = update;
this.host = host;
this[hookSymbol] = new Map();
this[effectsSymbol] = [];
}
run(cb) {
setCurrent(this);
let res = cb();
clear();
return res;
}
runEffects() {
let effects = this[effectsSymbol];
if(effects) {
setCurrent(this);
for(let effect of effects) {
effect.call(this);
}
clear();
constructor(update, host) {
this.update = update;
this.host = host;
this[hookSymbol] = new Map();
this[effectsSymbol] = [];
this[layoutEffectsSymbol] = [];
}
}
teardown() {
let hooks = this[hookSymbol];
hooks.forEach((hook) => {
if (typeof hook.teardown === 'function') {
hook.teardown();
}
})
}
run(cb) {
setCurrent(this);
let res = cb();
clear();
return res;
}
_runEffects(phase) {
let effects = this[phase];
setCurrent(this);
for (let effect of effects) {
effect.call(this);
}
clear();
}
runEffects() {
this._runEffects(effectsSymbol);
}
runLayoutEffects() {
this._runEffects(layoutEffectsSymbol);
}
teardown() {
let hooks = this[hookSymbol];
hooks.forEach(hook => {
if (typeof hook.teardown === 'function') {
hook.teardown();
}
});
}
}
export { State };
export { State };

@@ -1,10 +0,8 @@

const symbolFor = typeof Symbol === 'function' ? Symbol.for : str => str;
export const phaseSymbol = symbolFor('haunted.phase');
export const hookSymbol = symbolFor('haunted.hook');
export const updateSymbol = symbolFor('haunted.update');
export const commitSymbol = symbolFor('haunted.commit');
export const effectsSymbol = symbolFor('haunted.effects');
export const contextEvent = 'haunted.context';
const phaseSymbol = Symbol('haunted.phase');
const hookSymbol = Symbol('haunted.hook');
const updateSymbol = Symbol('haunted.update');
const commitSymbol = Symbol('haunted.commit');
const effectsSymbol = Symbol('haunted.effects');
const layoutEffectsSymbol = Symbol('haunted.layoutEffects');
const contextEvent = 'haunted.context';
export { phaseSymbol, hookSymbol, updateSymbol, commitSymbol, effectsSymbol, layoutEffectsSymbol, contextEvent, };

@@ -1,5 +0,3 @@

import { useMemo } from "./use-memo.js";
import { useMemo } from './use-memo.js';
const useCallback = (fn, inputs) => useMemo(() => fn, inputs);
export { useCallback };

@@ -0,65 +1,53 @@

import { hook, Hook } from './hook.js';
import { contextEvent } from './symbols.js';
import { hook, Hook } from './hook.js';
import { setEffects } from './use-effect.js';
const useContext = hook(class extends Hook {
constructor(id, state) {
super(id, state);
this._updater = this._updater.bind(this);
this._ranEffect = false;
this._unsubscribe = null;
setEffects(state, this);
}
update(Context) {
if (this.state.virtual) {
throw new Error('can\'t be used with virtual components');
constructor(id, state, _) {
super(id, state);
this._updater = this._updater.bind(this);
this._ranEffect = false;
this._unsubscribe = null;
setEffects(state, this);
}
if (this.Context !== Context) {
this._subscribe(Context);
this.Context = Context;
update(Context) {
if (this.state.virtual) {
throw new Error('can\'t be used with virtual components');
}
if (this.Context !== Context) {
this._subscribe(Context);
this.Context = Context;
}
return this.value;
}
return this.value;
}
call() {
if(!this._ranEffect) {
this._ranEffect = true;
if(this._unsubscribe) this._unsubscribe();
this._subscribe(this.Context);
this.state.update();
call() {
if (!this._ranEffect) {
this._ranEffect = true;
if (this._unsubscribe)
this._unsubscribe();
this._subscribe(this.Context);
this.state.update();
}
}
}
_updater(value) {
this.value = value;
this.state.update();
}
_subscribe(Context) {
const detail = { Context, callback: this._updater };
this.state.host.dispatchEvent(new CustomEvent(contextEvent, {
detail, // carrier
bubbles: true, // to bubble up in tree
cancelable: true, // to be able to cancel
composed: true, // to pass ShadowDOM boundaries
}));
const { unsubscribe, value } = detail;
this.value = unsubscribe ? value : Context.defaultValue;
this._unsubscribe = unsubscribe;
}
teardown() {
if (this._unsubscribe) {
this._unsubscribe();
_updater(value) {
this.value = value;
this.state.update();
}
}
_subscribe(Context) {
const detail = { Context, callback: this._updater };
this.state.host.dispatchEvent(new CustomEvent(contextEvent, {
detail,
bubbles: true,
cancelable: true,
composed: true,
}));
const { unsubscribe, value } = detail;
this.value = unsubscribe ? value : Context.defaultValue;
this._unsubscribe = unsubscribe;
}
teardown() {
if (this._unsubscribe) {
this._unsubscribe();
}
}
});
export { useContext };
import { effectsSymbol } from './symbols.js';
import { hook, Hook } from './hook.js';
import { createEffect } from './create-effect.js';
function setEffects(state, cb) {
if(!(effectsSymbol in state)) {
state[effectsSymbol] = [];
}
state[effectsSymbol].push(cb);
state[effectsSymbol].push(cb);
}
const useEffect = hook(class extends Hook {
constructor(id, state) {
super(id, state);
this.values = false;
setEffects(state, this);
}
update(callback, values) {
this.callback = callback;
this.lastValues = this.values;
this.values = values;
}
call() {
if(this.values) {
if(this.hasChanged()) {
this.run();
}
} else {
this.run();
}
}
run() {
this.teardown();
this._teardown = this.callback.call(this.state);
}
teardown() {
if(this._teardown) {
this._teardown();
}
}
hasChanged() {
return this.lastValues === false || this.values.some((value, i) => this.lastValues[i] !== value);
}
});
export { setEffects, useEffect };
const useEffect = createEffect(setEffects);
export { setEffects, useEffect };
import { hook, Hook } from './hook.js';
const useMemo = hook(class extends Hook {
constructor(id, state, fn, values) {
super(id, state);
this.value = fn();
this.values = values;
}
update(fn, values) {
if(this.hasChanged(values)) {
this.values = values;
this.value = fn();
constructor(id, state, fn, values) {
super(id, state);
this.value = fn();
this.values = values;
}
return this.value;
}
hasChanged(values) {
return values.some((value, i) => this.values[i] !== value);
}
update(fn, values) {
if (this.hasChanged(values)) {
this.values = values;
this.value = fn();
}
return this.value;
}
hasChanged(values) {
return values.some((value, i) => this.values[i] !== value);
}
});
export { useMemo };
export { useMemo };
import { hook, Hook } from './hook.js';
const useReducer = hook(class extends Hook {
constructor(id, state, _, initialState) {
super(id, state);
this.dispatch = this.dispatch.bind(this);
this.state = initialState;
}
update(reducer) {
this.reducer = reducer;
return [this.state, this.dispatch];
}
dispatch(action) {
this.state = this.reducer(this.state, action);
this.state.update();
}
constructor(id, state, _, initialState) {
super(id, state);
this.dispatch = this.dispatch.bind(this);
this.currentState = initialState;
}
update(reducer) {
this.reducer = reducer;
return [this.currentState, this.dispatch];
}
dispatch(action) {
this.currentState = this.reducer(this.currentState, action);
this.state.update();
}
});
export { useReducer };
export { useReducer };

@@ -1,11 +0,5 @@

import { useMemo } from "./use-memo.js";
const useRef = (initialValue) => {
return useMemo(() => {
return {
current: initialValue
};
}, []);
}
export { useRef }
import { useMemo } from './use-memo.js';
const useRef = (initialValue) => useMemo(() => ({
current: initialValue
}), []);
export { useRef };
import { hook, Hook } from './hook.js';
const useState = hook(class extends Hook {
constructor(id, state, initialValue) {
super(id, state);
this.updater = this.updater.bind(this);
if(typeof initialValue === 'function') {
initialValue = initialValue();
constructor(id, state, initialValue) {
super(id, state);
this.updater = this.updater.bind(this);
if (typeof initialValue === 'function') {
initialValue = initialValue();
}
this.makeArgs(initialValue);
}
this.makeArgs(initialValue);
}
update() {
return this.args;
}
updater(value) {
if (typeof value === "function") {
const updaterFn = value;
const [previousValue] = this.args;
value = updaterFn(previousValue);
update() {
return this.args;
}
this.makeArgs(value);
this.state.update();
}
makeArgs(value) {
this.args = Object.freeze([value, this.updater]);
}
updater(value) {
if (typeof value === 'function') {
const updaterFn = value;
const [previousValue] = this.args;
value = updaterFn(previousValue);
}
this.makeArgs(value);
this.state.update();
}
makeArgs(value) {
this.args = Object.freeze([value, this.updater]);
}
});
export { useState };
import { directive } from 'lit-html';
import { BaseScheduler } from './scheduler.js';
const includes = Array.prototype.includes;
function makeVirtual(Scheduler) {
const partToContainer = new WeakMap();
const containerToPart = new WeakMap();
class DirectiveContainer extends Scheduler {
constructor(renderer, part) {
super(renderer, part);
this.virtual = true;
function makeVirtual() {
const partToScheduler = new WeakMap();
const schedulerToPart = new WeakMap();
class Scheduler extends BaseScheduler {
constructor(renderer, part) {
super(renderer, part);
this.state.virtual = true;
}
render() {
return this.state.run(() => this.renderer.apply(this.host, this.args));
}
commit(result) {
this.host.setValue(result);
this.host.commit();
}
teardown() {
super.teardown();
let part = schedulerToPart.get(this);
partToScheduler.delete(part);
}
}
commit(result) {
this.host.setValue(result);
this.host.commit();
}
teardown() {
super.teardown();
let part = containerToPart.get(this);
partToContainer.delete(part);
}
}
function virtual(renderer) {
function factory(...args) {
return part => {
let cont = partToContainer.get(part);
if(!cont) {
cont = new DirectiveContainer(renderer, part);
partToContainer.set(part, cont);
containerToPart.set(cont, part);
teardownOnRemove(cont, part);
function virtual(renderer) {
function factory(...args) {
return (part) => {
let cont = partToScheduler.get(part);
if (!cont) {
cont = new Scheduler(renderer, part);
partToScheduler.set(part, cont);
schedulerToPart.set(cont, part);
teardownOnRemove(cont, part);
}
cont.args = args;
cont.update();
};
}
cont.args = args;
cont.update();
};
return directive(factory);
}
return directive(factory);
}
return virtual;
return virtual;
}
function teardownOnRemove(cont, part, node = part.startNode) {
let frag = node.parentNode;
let mo = new MutationObserver(mutations => {
for(let mutation of mutations) {
if(includes.call(mutation.removedNodes, node)) {
mo.disconnect();
if(node.parentNode instanceof ShadowRoot) {
teardownOnRemove(cont, part);
} else {
cont.teardown();
let frag = node.parentNode;
let mo = new MutationObserver(mutations => {
for (let mutation of mutations) {
if (includes.call(mutation.removedNodes, node)) {
mo.disconnect();
if (node.parentNode instanceof ShadowRoot) {
teardownOnRemove(cont, part);
}
else {
cont.teardown();
}
break;
}
else if (includes.call(mutation.addedNodes, node.nextSibling)) {
mo.disconnect();
teardownOnRemove(cont, part, node.nextSibling || undefined);
break;
}
}
break;
} else if(includes.call(mutation.addedNodes, node.nextSibling)) {
mo.disconnect();
teardownOnRemove(cont, part, node.nextSibling);
break;
}
}
});
mo.observe(frag, { childList: true });
});
mo.observe(frag, { childList: true });
}
export { makeVirtual };
export { makeVirtual };
{
"name": "haunted",
"version": "4.6.0-beta.0",
"version": "4.6.0",
"description": "Hooks for web components",

@@ -31,7 +31,8 @@ "main": "lib/haunted.js",

"homepage": "https://github.com/matthewp/haunted#readme",
"typings": " lib/haunted.d.ts",
"typings": "lib/haunted.d.ts",
"devDependencies": {
"@matthewp/compile": "^2.4.3",
"http-server": "^0.11.1",
"mocha-headless-chrome": "^2.0.3"
"mocha-headless-chrome": "^2.0.3",
"typescript": "^3.6.2"
},

@@ -38,0 +39,0 @@ "dependencies": {

# Haunted 🦇 🎃
[![npm](https://img.shields.io/npm/dt/haunted)](https://npm.im/haunted)
[![npm](https://img.shields.io/npm/v/haunted)](https://npm.im/haunted)
React's Hooks API but for standard web components and [lit-html](https://lit-html.polymer-project.org/) or [hyperHTML](https://codepen.io/WebReflection/pen/pxXrdy?editors=0010).

@@ -297,2 +300,8 @@

#### useLayoutEffect
The function signature is the same as `useEffect`, but the callback is being called synchronously after rendering. Updates scheduled inside `useLayoutEffect` will therefore be flushed synchronously, before the browser has a chance to paint.
Most of time, it is preferable to use `useEffect` to avoid blocking visual updates.
#### useReducer

@@ -299,0 +308,0 @@

import { directive, render } from '../lit-html/lit-html.js';
export { html, render } from '../lit-html/lit-html.js';
const symbolFor = typeof Symbol === 'function' ? Symbol.for : str => str;
const phaseSymbol = symbolFor('haunted.phase');
const hookSymbol = symbolFor('haunted.hook');
const updateSymbol = symbolFor('haunted.update');
const commitSymbol = symbolFor('haunted.commit');
const effectsSymbol = symbolFor('haunted.effects');
const contextSymbol = symbolFor('haunted.context');
const contextEvent = 'haunted.context';
let current;
let currentId = 0;
function setCurrent(element) {
current = element;
function setCurrent(state) {
current = state;
}
function clear() {
current = null;
currentId = 0;
current = null;
currentId = 0;
}
function notify() {
let id = currentId;
currentId++;
return id;
return currentId++;
}
//import { render, html } from './lit.js';
const phaseSymbol = Symbol('haunted.phase');
const hookSymbol = Symbol('haunted.hook');
const updateSymbol = Symbol('haunted.update');
const commitSymbol = Symbol('haunted.commit');
const effectsSymbol = Symbol('haunted.effects');
const layoutEffectsSymbol = Symbol('haunted.layoutEffects');
const contextEvent = 'haunted.context';
const defer = Promise.resolve().then.bind(Promise.resolve());
function scheduler() {
let tasks = [];
let id;
function runTasks() {
id = null;
let t = tasks;
tasks = [];
for(var i = 0, len = t.length; i < len; i++) {
t[i]();
class State {
constructor(update, host) {
this.update = update;
this.host = host;
this[hookSymbol] = new Map();
this[effectsSymbol] = [];
this[layoutEffectsSymbol] = [];
}
}
return function(task) {
tasks.push(task);
if(id == null) {
id = defer(runTasks);
run(cb) {
setCurrent(this);
let res = cb();
clear();
return res;
}
};
_runEffects(phase) {
let effects = this[phase];
setCurrent(this);
for (let effect of effects) {
effect.call(this);
}
clear();
}
runEffects() {
this._runEffects(effectsSymbol);
}
runLayoutEffects() {
this._runEffects(layoutEffectsSymbol);
}
teardown() {
let hooks = this[hookSymbol];
hooks.forEach(hook => {
if (typeof hook.teardown === 'function') {
hook.teardown();
}
});
}
}
function makeContainer(render$$1) {
const read = scheduler();
const write = scheduler();
class Container {
constructor(renderer, frag, host) {
this.renderer = renderer;
this.frag = frag;
this.host = host || frag;
this[hookSymbol] = new Map();
this[phaseSymbol] = null;
this._updateQueued = false;
const defer = Promise.resolve().then.bind(Promise.resolve());
function runner() {
let tasks = [];
let id;
function runTasks() {
id = null;
let t = tasks;
tasks = [];
for (var i = 0, len = t.length; i < len; i++) {
t[i]();
}
}
return function (task) {
tasks.push(task);
if (id == null) {
id = defer(runTasks);
}
};
}
const read = runner();
const write = runner();
class BaseScheduler {
constructor(renderer, host) {
this.renderer = renderer;
this.host = host;
this.state = new State(this.update.bind(this), host);
this[phaseSymbol] = null;
this._updateQueued = false;
}
update() {
if(this._updateQueued) return;
read(() => {
let result = this.handlePhase(updateSymbol);
write(() => {
this.handlePhase(commitSymbol, result);
if(this[effectsSymbol]) {
if (this._updateQueued)
return;
read(() => {
let result = this.handlePhase(updateSymbol);
write(() => {
this.handlePhase(effectsSymbol);
this.handlePhase(commitSymbol, result);
write(() => {
this.handlePhase(effectsSymbol);
});
});
}
this._updateQueued = false;
});
this._updateQueued = false;
});
this._updateQueued = true;
this._updateQueued = true;
}
handlePhase(phase, arg) {
this[phaseSymbol] = phase;
switch(phase) {
case commitSymbol: return this.commit(arg);
case updateSymbol: return this.render();
case effectsSymbol: return this.runEffects(effectsSymbol);
}
this[phaseSymbol] = null;
this[phaseSymbol] = phase;
switch (phase) {
case commitSymbol:
this.commit(arg);
this.runEffects(layoutEffectsSymbol);
return;
case updateSymbol: return this.render();
case effectsSymbol: return this.runEffects(effectsSymbol);
}
this[phaseSymbol] = null;
}
commit(result) {
render$$1(result, this.frag);
this.runEffects(commitSymbol);
}
render() {
setCurrent(this);
let result = this.args ?
this.renderer.apply(this.host, this.args) :
this.renderer.call(this.host, this.host);
clear();
return result;
return this.state.run(() => this.renderer.call(this.host, this.host));
}
runEffects(symbol) {
let effects = this[symbol];
if(effects) {
setCurrent(this);
for(let effect of effects) {
effect.call(this);
}
clear();
}
runEffects(phase) {
this.state._runEffects(phase);
}
teardown() {
let hooks = this[hookSymbol];
hooks.forEach((hook) => {
if (typeof hook.teardown === 'function') {
hook.teardown();
}
});
this.state.teardown();
}
}
return Container;
}
function toCamelCase(val = '') {
return val.indexOf('-') === -1 ? val.toLowerCase() : val.toLowerCase().split('-').reduce((out, part) => {
return out ? out + part.charAt(0).toUpperCase() + part.slice(1) : part;
},'')
}
function makeComponent(Container) {
function component(renderer, baseElementOrOptions, options) {
const BaseElement = (options || baseElementOrOptions || {}).baseElement || HTMLElement;
const {observedAttributes = [], useShadowDOM = true, shadowRootInit = {}} = options || baseElementOrOptions || {};
class Element extends BaseElement {
static get observedAttributes() {
return renderer.observedAttributes || observedAttributes || [];
}
constructor() {
super();
if (useShadowDOM === false) {
this._container = new Container(renderer, this);
} else {
this.attachShadow({ mode: "open", ...shadowRootInit});
this._container = new Container(renderer, this.shadowRoot, this);
const toCamelCase = (val = '') => val.replace(/-+([a-z])?/g, (_, char) => char ? char.toUpperCase() : '');
function makeComponent(render) {
class Scheduler extends BaseScheduler {
constructor(renderer, frag, host) {
super(renderer, host || frag);
this.frag = frag;
}
}
connectedCallback() {
this._container.update();
}
disconnectedCallback() {
this._container.teardown();
}
attributeChangedCallback(name, _, newValue) {
let val = newValue === '' ? true : newValue;
Reflect.set(this, toCamelCase(name), val);
}
}
function reflectiveProp(initialValue) {
let value = initialValue;
return Object.freeze({
enumerable: true,
configurable: true,
get() {
return value;
},
set(newValue) {
value = newValue;
this._container.update();
commit(result) {
render(result, this.frag);
}
})
}
const proto = new Proxy(BaseElement.prototype, {
set(target, key, value, receiver) {
if(key in target) {
Reflect.set(target, key, value);
function component(renderer, baseElementOrOptions, options) {
const BaseElement = (options || baseElementOrOptions || {}).baseElement || HTMLElement;
const { observedAttributes = [], useShadowDOM = true, shadowRootInit = {} } = options || baseElementOrOptions || {};
class Element extends BaseElement {
constructor() {
super();
if (useShadowDOM === false) {
this._scheduler = new Scheduler(renderer, this);
}
else {
this.attachShadow({ mode: 'open', ...shadowRootInit });
this._scheduler = new Scheduler(renderer, this.shadowRoot, this);
}
}
static get observedAttributes() {
return renderer.observedAttributes || observedAttributes || [];
}
connectedCallback() {
this._scheduler.update();
}
disconnectedCallback() {
this._scheduler.teardown();
}
attributeChangedCallback(name, _, newValue) {
let val = newValue === '' ? true : newValue;
Reflect.set(this, toCamelCase(name), val);
}
}
let desc;
if(typeof key === 'symbol' || key[0] === '_') {
desc = {
enumerable: true,
configurable: true,
writable: true,
value
};
} else {
desc = reflectiveProp(value);
function reflectiveProp(initialValue) {
let value = initialValue;
return Object.freeze({
enumerable: true,
configurable: true,
get() {
return value;
},
set(newValue) {
value = newValue;
this._scheduler.update();
}
});
}
Object.defineProperty(receiver, key, desc);
if(desc.set) {
desc.set.call(receiver, value);
}
return true;
}
});
Object.setPrototypeOf(Element.prototype, proto);
return Element;
}
return component;
const proto = new Proxy(BaseElement.prototype, {
getPrototypeOf(target) {
return target;
},
set(target, key, value, receiver) {
if (key in target) {
Reflect.set(target, key, value);
}
let desc;
if (typeof key === 'symbol' || key[0] === '_') {
desc = {
enumerable: true,
configurable: true,
writable: true,
value
};
}
else {
desc = reflectiveProp(value);
}
Object.defineProperty(receiver, key, desc);
if (desc.set) {
desc.set.call(receiver, value);
}
return true;
}
});
Object.setPrototypeOf(Element.prototype, proto);
return Element;
}
return component;
}
class Hook {
constructor(id, el) {
this.id = id;
this.el = el;
}
constructor(id, state) {
this.id = id;
this.state = state;
}
}
function use(Hook, ...args) {
let id = notify();
let hooks = current[hookSymbol];
let hook = hooks.get(id);
if(!hook) {
hook = new Hook(id, current, ...args);
hooks.set(id, hook);
}
return hook.update(...args);
let id = notify();
let hooks = current[hookSymbol];
let hook = hooks.get(id);
if (!hook) {
hook = new Hook(id, current, ...args);
hooks.set(id, hook);
}
return hook.update(...args);
}
function hook(Hook) {
return use.bind(null, Hook);
return use.bind(null, Hook);
}
function setEffects(el, cb) {
if(!(effectsSymbol in el)) {
el[effectsSymbol] = [];
}
el[effectsSymbol].push(cb);
function createEffect(setEffects) {
return hook(class extends Hook {
constructor(id, state, ignored1, ignored2) {
super(id, state);
setEffects(state, this);
}
update(callback, values) {
this.callback = callback;
this.lastValues = this.values;
this.values = values;
}
call() {
if (!this.values || this.hasChanged()) {
this.run();
}
}
run() {
this.teardown();
this._teardown = this.callback.call(this.state);
}
teardown() {
if (this._teardown) {
this._teardown();
}
}
hasChanged() {
return !this.lastValues || this.values.some((value, i) => this.lastValues[i] !== value);
}
});
}
const useEffect = hook(class extends Hook {
constructor(id, el) {
super(id, el);
this.values = false;
setEffects(el, this);
}
update(callback, values) {
this.callback = callback;
this.lastValues = this.values;
this.values = values;
}
call() {
if(this.values) {
if(this.hasChanged()) {
this.run();
}
} else {
this.run();
}
}
run() {
this.teardown();
this._teardown = this.callback.call(this.el);
}
teardown() {
if(this._teardown) {
this._teardown();
}
}
hasChanged() {
return this.lastValues === false || this.values.some((value, i) => this.lastValues[i] !== value);
}
});
function setContexts(el, consumer) {
if(!(contextSymbol in el)) {
el[contextSymbol] = [];
}
el[contextSymbol].push(consumer);
function setEffects(state, cb) {
state[effectsSymbol].push(cb);
}
const useEffect = createEffect(setEffects);
const useContext = hook(class extends Hook {
constructor(id, el) {
super(id, el);
setContexts(el, this);
this._updater = this._updater.bind(this);
this._ranEffect = false;
this._unsubscribe = null;
setEffects(el, this);
}
update(Context) {
if (this.el.virtual) {
throw new Error('can\'t be used with virtual components');
constructor(id, state, _) {
super(id, state);
this._updater = this._updater.bind(this);
this._ranEffect = false;
this._unsubscribe = null;
setEffects(state, this);
}
if (this.Context !== Context) {
this._subscribe(Context);
this.Context = Context;
update(Context) {
if (this.state.virtual) {
throw new Error('can\'t be used with virtual components');
}
if (this.Context !== Context) {
this._subscribe(Context);
this.Context = Context;
}
return this.value;
}
return this.value;
}
call() {
if(!this._ranEffect) {
this._ranEffect = true;
if(this._unsubscribe) this._unsubscribe();
this._subscribe(this.Context);
this.el.update();
call() {
if (!this._ranEffect) {
this._ranEffect = true;
if (this._unsubscribe)
this._unsubscribe();
this._subscribe(this.Context);
this.state.update();
}
}
}
_updater(value) {
this.value = value;
this.el.update();
}
_subscribe(Context) {
const detail = { Context, callback: this._updater };
this.el.host.dispatchEvent(new CustomEvent(contextEvent, {
detail, // carrier
bubbles: true, // to bubble up in tree
cancelable: true, // to be able to cancel
composed: true, // to pass ShadowDOM boundaries
}));
const { unsubscribe, value } = detail;
this.value = unsubscribe ? value : Context.defaultValue;
this._unsubscribe = unsubscribe;
}
teardown() {
if (this._unsubscribe) {
this._unsubscribe();
_updater(value) {
this.value = value;
this.state.update();
}
}
_subscribe(Context) {
const detail = { Context, callback: this._updater };
this.state.host.dispatchEvent(new CustomEvent(contextEvent, {
detail,
bubbles: true,
cancelable: true,
composed: true,
}));
const { unsubscribe, value } = detail;
this.value = unsubscribe ? value : Context.defaultValue;
this._unsubscribe = unsubscribe;
}
teardown() {
if (this._unsubscribe) {
this._unsubscribe();
}
}
});
function makeContext(component) {
return (defaultValue) => {
const Context = {
Provider: class extends HTMLElement {
constructor() {
super();
this.listeners = new Set();
this.addEventListener(contextEvent, this);
}
disconnectedCallback() {
this.removeEventListener(contextEvent, this);
}
handleEvent(event) {
const { detail } = event;
if (detail.Context === Context) {
detail.value = this.value;
detail.unsubscribe = this.unsubscribe.bind(this, detail.callback);
this.listeners.add(detail.callback);
event.stopPropagation();
}
}
unsubscribe(callback) {
if(this.listeners.has(callback)) {
this.listeners.delete(callback);
}
}
set value(value) {
this._value = value;
for(let callback of this.listeners) {
callback(value);
}
}
get value() {
return this._value;
}
},
Consumer: component(function ({ render: render$$1 }) {
const context = useContext(Context);
return render$$1(context);
}),
defaultValue
return (defaultValue) => {
const Context = {
Provider: class extends HTMLElement {
constructor() {
super();
this.listeners = new Set();
this.addEventListener(contextEvent, this);
}
disconnectedCallback() {
this.removeEventListener(contextEvent, this);
}
handleEvent(event) {
const { detail } = event;
if (detail.Context === Context) {
detail.value = this.value;
detail.unsubscribe = this.unsubscribe.bind(this, detail.callback);
this.listeners.add(detail.callback);
event.stopPropagation();
}
}
unsubscribe(callback) {
this.listeners.delete(callback);
}
set value(value) {
this._value = value;
for (let callback of this.listeners) {
callback(value);
}
}
get value() {
return this._value;
}
},
Consumer: component(function ({ render }) {
const context = useContext(Context);
return render(context);
}),
defaultValue,
};
return Context;
};
return Context;
};
}
const useMemo = hook(class extends Hook {
constructor(id, el, fn, values) {
super(id, el);
this.value = fn();
this.values = values;
}
update(fn, values) {
if(this.hasChanged(values)) {
this.values = values;
this.value = fn();
constructor(id, state, fn, values) {
super(id, state);
this.value = fn();
this.values = values;
}
return this.value;
}
hasChanged(values) {
return values.some((value, i) => this.values[i] !== value);
}
update(fn, values) {
if (this.hasChanged(values)) {
this.values = values;
this.value = fn();
}
return this.value;
}
hasChanged(values) {
return values.some((value, i) => this.values[i] !== value);
}
});

@@ -447,145 +386,127 @@

function setLayoutEffects(state, cb) {
state[layoutEffectsSymbol].push(cb);
}
const useLayoutEffect = createEffect(setLayoutEffects);
const useState = hook(class extends Hook {
constructor(id, el, initialValue) {
super(id, el);
this.updater = this.updater.bind(this);
if(typeof initialValue === 'function') {
initialValue = initialValue();
constructor(id, state, initialValue) {
super(id, state);
this.updater = this.updater.bind(this);
if (typeof initialValue === 'function') {
initialValue = initialValue();
}
this.makeArgs(initialValue);
}
this.makeArgs(initialValue);
}
update() {
return this.args;
}
updater(value) {
if (typeof value === "function") {
const updaterFn = value;
const [previousValue] = this.args;
value = updaterFn(previousValue);
update() {
return this.args;
}
this.makeArgs(value);
this.el.update();
}
makeArgs(value) {
this.args = Object.freeze([value, this.updater]);
}
updater(value) {
if (typeof value === 'function') {
const updaterFn = value;
const [previousValue] = this.args;
value = updaterFn(previousValue);
}
this.makeArgs(value);
this.state.update();
}
makeArgs(value) {
this.args = Object.freeze([value, this.updater]);
}
});
const useReducer = hook(class extends Hook {
constructor(id, el, _, initialState) {
super(id, el);
this.dispatch = this.dispatch.bind(this);
this.state = initialState;
}
update(reducer) {
this.reducer = reducer;
return [this.state, this.dispatch];
}
dispatch(action) {
this.state = this.reducer(this.state, action);
this.el.update();
}
constructor(id, state, _, initialState) {
super(id, state);
this.dispatch = this.dispatch.bind(this);
this.currentState = initialState;
}
update(reducer) {
this.reducer = reducer;
return [this.currentState, this.dispatch];
}
dispatch(action) {
this.currentState = this.reducer(this.currentState, action);
this.state.update();
}
});
const useRef = (initialValue) => {
return useMemo(() => {
return {
current: initialValue
};
}, []);
};
const useRef = (initialValue) => useMemo(() => ({
current: initialValue
}), []);
function haunted({ render: render$$1 }) {
const Container = makeContainer(render$$1);
const component = makeComponent(Container);
const createContext = makeContext(component);
return { Container, component, createContext };
function haunted({ render }) {
const component = makeComponent(render);
const createContext = makeContext(component);
return { component, createContext };
}
const includes = Array.prototype.includes;
function makeVirtual(Container) {
const partToContainer = new WeakMap();
const containerToPart = new WeakMap();
class DirectiveContainer extends Container {
constructor(renderer, part) {
super(renderer, part);
this.virtual = true;
function makeVirtual() {
const partToScheduler = new WeakMap();
const schedulerToPart = new WeakMap();
class Scheduler extends BaseScheduler {
constructor(renderer, part) {
super(renderer, part);
this.state.virtual = true;
}
render() {
return this.state.run(() => this.renderer.apply(this.host, this.args));
}
commit(result) {
this.host.setValue(result);
this.host.commit();
}
teardown() {
super.teardown();
let part = schedulerToPart.get(this);
partToScheduler.delete(part);
}
}
commit(result) {
this.host.setValue(result);
this.host.commit();
}
teardown() {
super.teardown();
let part = containerToPart.get(this);
partToContainer.delete(part);
}
}
function virtual(renderer) {
function factory(...args) {
return part => {
let cont = partToContainer.get(part);
if(!cont) {
cont = new DirectiveContainer(renderer, part);
partToContainer.set(part, cont);
containerToPart.set(cont, part);
teardownOnRemove(cont, part);
function virtual(renderer) {
function factory(...args) {
return (part) => {
let cont = partToScheduler.get(part);
if (!cont) {
cont = new Scheduler(renderer, part);
partToScheduler.set(part, cont);
schedulerToPart.set(cont, part);
teardownOnRemove(cont, part);
}
cont.args = args;
cont.update();
};
}
cont.args = args;
cont.update();
};
return directive(factory);
}
return directive(factory);
}
return virtual;
return virtual;
}
function teardownOnRemove(cont, part, node = part.startNode) {
let frag = node.parentNode;
let mo = new MutationObserver(mutations => {
for(let mutation of mutations) {
if(includes.call(mutation.removedNodes, node)) {
mo.disconnect();
if(node.parentNode instanceof ShadowRoot) {
teardownOnRemove(cont, part);
} else {
cont.teardown();
let frag = node.parentNode;
let mo = new MutationObserver(mutations => {
for (let mutation of mutations) {
if (includes.call(mutation.removedNodes, node)) {
mo.disconnect();
if (node.parentNode instanceof ShadowRoot) {
teardownOnRemove(cont, part);
}
else {
cont.teardown();
}
break;
}
else if (includes.call(mutation.addedNodes, node.nextSibling)) {
mo.disconnect();
teardownOnRemove(cont, part, node.nextSibling || undefined);
break;
}
}
break;
} else if(includes.call(mutation.addedNodes, node.nextSibling)) {
mo.disconnect();
teardownOnRemove(cont, part, node.nextSibling);
break;
}
}
});
mo.observe(frag, { childList: true });
});
mo.observe(frag, { childList: true });
}
const { Container, component, createContext } = haunted({
render(what, where) {
render(what, where);
}
});
const { component, createContext } = haunted({ render });
const virtual = makeVirtual();
const virtual = makeVirtual(Container);
export default haunted;
export { component, createContext, virtual, useCallback, useEffect, useState, useReducer, useMemo, useContext, useRef, hook, Hook };
export { BaseScheduler, Hook, State, component, createContext, hook, useCallback, useContext, useEffect, useLayoutEffect, useMemo, useReducer, useRef, useState, virtual };
SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc