fluxel
Version:
An ultra-lightweight, high-performance library for efficient DOM building and dynamic web UIs
249 lines (248 loc) • 9.33 kB
JavaScript
function _define_property(obj, key, value) {
if (key in obj) {
Object.defineProperty(obj, key, {
value: value,
enumerable: true,
configurable: true,
writable: true
});
} else {
obj[key] = value;
}
return obj;
}
function _object_spread(target) {
for(var i = 1; i < arguments.length; i++){
var source = arguments[i] != null ? arguments[i] : {};
var ownKeys = Object.keys(source);
if (typeof Object.getOwnPropertySymbols === "function") {
ownKeys = ownKeys.concat(Object.getOwnPropertySymbols(source).filter(function(sym) {
return Object.getOwnPropertyDescriptor(source, sym).enumerable;
}));
}
ownKeys.forEach(function(key) {
_define_property(target, key, source[key]);
});
}
return target;
}
function ownKeys(object, enumerableOnly) {
var keys = Object.keys(object);
if (Object.getOwnPropertySymbols) {
var symbols = Object.getOwnPropertySymbols(object);
if (enumerableOnly) {
symbols = symbols.filter(function(sym) {
return Object.getOwnPropertyDescriptor(object, sym).enumerable;
});
}
keys.push.apply(keys, symbols);
}
return keys;
}
function _object_spread_props(target, source) {
source = source != null ? source : {};
if (Object.getOwnPropertyDescriptors) {
Object.defineProperties(target, Object.getOwnPropertyDescriptors(source));
} else {
ownKeys(Object(source)).forEach(function(key) {
Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key));
});
}
return target;
}
import BaseFluxel from "./index.js";
import { targetListenerMap } from "./internalStore.js";
import ReactiveDependency from "./reactiveDependency.js";
const Fluxel = BaseFluxel;
export const pureStateReservedKeys = [
"render",
"use",
"useWithMemo",
"listenTarget"
];
Fluxel.reactive = function(initialState, renderer) {
const stateListenerMap = Object.create(null);
const pureState = _object_spread_props(_object_spread({}, initialState), {
render: (targetProperty, skipEventDispatch = false)=>{
if (!skipEventDispatch && targetProperty) {
pureState.listenTarget.dispatchEvent(new CustomEvent(targetProperty, {
detail: {
newValue: pureState[targetProperty]
}
}));
}
if (targetProperty) {
var _stateListenerMap_targetProperty;
(_stateListenerMap_targetProperty = stateListenerMap[targetProperty]) === null || _stateListenerMap_targetProperty === void 0 ? void 0 : _stateListenerMap_targetProperty.listeners.forEach((listener)=>listener.call(null));
} else {
for(const key in stateListenerMap){
stateListenerMap[key].listeners.forEach((listener)=>listener.call(null));
}
}
},
use: (key, deriveFn)=>{
let memoIndex = -1;
let usedMemoCount = -1;
const memos = [];
let usedPureMemos = new Map();
let pureMemos = new Map();
const memoize = (factory, deps, pure = false)=>{
if (pure) {
const key = JSON.stringify(deps);
if (pureMemos.has(key)) {
const usedValue = pureMemos.get(key);
usedPureMemos.set(key, usedValue);
return usedValue;
}
const value = factory();
usedPureMemos.set(key, value);
return value;
}
if (memos[memoIndex]) {
if (memos[memoIndex].deps.every((d, i)=>d === deps[i])) {
return memos[memoIndex++].memoValue;
}
memos[memoIndex].deps = deps;
const newValue = memos[memoIndex].memoValue = factory();
memoIndex++;
return newValue;
} else {
memos.push({
deps,
memoValue: factory()
});
return memos[memoIndex++].memoValue;
}
};
const memorableDeriveFn = deriveFn ? (v)=>{
if (memoIndex === -1 && usedMemoCount !== -1) {
usedMemoCount = memoIndex;
}
memoIndex = 0;
usedPureMemos = new Map();
const derived = deriveFn(v, memoize);
if (usedMemoCount !== -1 && usedMemoCount !== memoIndex) {
throw new TypeError("Memoization count mismatch");
}
pureMemos = usedPureMemos;
return derived;
} : undefined;
if (Array.isArray(key)) {
const entries = key.map((k)=>[
k,
pureState.use(k)
]);
return new ReactiveDependency({
get: ()=>Object.fromEntries(entries.map((entry)=>[
entry[0],
entry[1].value
])),
set: (_)=>{
throw new TypeError("Invalid");
}
}, (targetObj, dep)=>{
const throttledDep = createThrottledDep(dep);
entries.forEach((entry)=>entry[1].addDependency(targetObj, throttledDep));
}).derive(memorableDeriveFn);
} else {
let dep;
if (stateListenerMap[key]) {
dep = stateListenerMap[key].dep;
} else if (pureState[key] instanceof ReactiveDependency) {
dep = pureState[key];
} else {
dep = new ReactiveDependency({
get: ()=>pureState[key],
set: (val)=>{
pureState[key] = val;
}
}, function addDependency(targetObj, dep) {
if (!stateListenerMap[key].listeners.includes(dep)) {
stateListenerMap[key].listeners.push(dep);
const cleanupDep = ()=>{
stateListenerMap[key].listeners = stateListenerMap[key].listeners.filter((listener)=>listener !== dep);
};
if (targetListenerMap.has(targetObj)) {
targetListenerMap.get(targetObj).add(cleanupDep);
} else {
targetListenerMap.set(targetObj, new Set([
cleanupDep
]));
}
}
});
stateListenerMap[key] = {
dep,
listeners: []
};
}
if (memorableDeriveFn) {
return dep.derive(memorableDeriveFn);
}
return dep;
}
},
useWithMemo: (key)=>{
let memo = null;
const dep = pureState.use(key, (v, _memo)=>{
memo = _memo;
return v;
});
dep.value;
return [
dep,
memo
];
},
listenTarget: new EventTarget()
});
const state = new Proxy(pureState, {
set (target, prop, value) {
if (pureStateReservedKeys.includes(prop)) {
throw new TypeError(`Cannot set the specified property: ${String(prop)}`);
}
const oldValue = target[prop];
if (oldValue === value) {
return true;
}
target[prop] = value;
target.listenTarget.dispatchEvent(new CustomEvent(prop, {
detail: {
oldValue,
newValue: value
}
}));
target.render(prop);
return true;
}
});
return renderer(state);
};
Fluxel.schedule = function(fn) {
if (typeof window !== "undefined") {
setTimeout(fn, 0);
}
};
Fluxel.mergeDependencies = function(...deps) {
return new ReactiveDependency({
get: ()=>deps.map((dep)=>dep.value),
set: (val)=>{
throw new TypeError("Invalid operation");
}
}, (targetObj, dep)=>{
const throttledDep = createThrottledDep(dep);
deps.forEach((d)=>d.addDependency(targetObj, throttledDep));
});
};
function createThrottledDep(dep) {
let depCallPending = false;
return ()=>{
if (depCallPending) return;
depCallPending = true;
window.setTimeout(()=>{
depCallPending = false;
dep();
}, 0);
};
}
export default Fluxel;