@aegisjsproject/state
Version:
A simple state manager library
2 lines (1 loc) • 9.13 kB
JavaScript
const e=new Map,t=new BroadcastChannel("aegis:state_sync"),n=crypto.randomUUID(),a=Symbol("proxy"),r=Symbol("aegis:state:update");let o=!0;const s=new EventTarget,i="aegisStateKey",c="aegisStateAttr",l="aegisStateStyle",f="aegisStateProperty",u="data-aegis-state-key",d="data-aegis-state-attr",y="data-aegis-state-property",m="data-aegis-state-style",g="change",p="beforechange",b=(e,t=null)=>history.state?.[e]??t;function h(e,t=document.documentElement){const n=t.querySelectorAll(e);return t.matches(e)?[t,...n]:Array.from(n)}async function E({state:e=history.state??{}}={}){const t=this.dataset.aegisStateKey,n=e?.[t];if(await(scheduler?.yield()),"string"==typeof this.dataset[c]){const e=this.dataset[c],t=this.getAttribute(e);"string"==typeof t&&t.startsWith("blob:")&&URL.revokeObjectURL(t),"boolean"==typeof n?this.toggleAttribute(e,n):null==n?this.removeAttribute(e):n instanceof Blob?this.setAttribute(e,URL.createObjectURL(n)):this.setAttribute(e,n)}else"string"==typeof this.dataset[f]&&"innerHTML"!==this.dataset[f]?this[this.dataset[f]]=n:"string"==typeof this.dataset[l]?null==n||!1===n?this.style.removeProperty(this.dataset[l]):this.style.setProperty(this.dataset[l],n):this instanceof HTMLInputElement||this instanceof HTMLSelectElement||this instanceof HTMLTextAreaElement?this.value=n:this.textContent=n}const v=new MutationObserver((e=>{e.forEach((e=>{switch(e.type){case"childList":e.addedNodes.forEach((e=>{e.nodeType===Node.ELEMENT_NODE&&h(`[${u}]`,e.target).forEach((e=>{e[r]=E.bind(e),L(e[r],e.dataset[i])}))})),e.removedNodes.forEach((e=>{e.nodeType===Node.ELEMENT_NODE&&h(`[${u}]`,e.target).forEach((e=>{P(e[r]),delete e[r]}))}));break;case"attributes":"string"==typeof e.oldValue?(P(e.target[r]),delete e.target[r]):"string"==typeof e.target.dataset[i]&&(e.target[r]=E.bind(e.target),L(e.target[r],e.target.dataset[i]))}}))}));function S(e,t,a={}){return{sender:n,type:e,state:M(),msgId:crypto.randomUUID(),recipient:t,location:location.href,timestamp:Date.now(),...a}}function w(e=M(),t=location.href){const n=T(e);return 0!==n.length&&(history.replaceState(e,"",t),O(n),!0)}function A(){o&&(t.close(),o=!1)}function T(e,t=M()){if(t!==e){const n=Object.keys(t),a=Object.keys(e),r=a.filter((e=>!n.includes(e))),o=n.filter((e=>!a.includes(e))),s=n.filter((n=>n in e&&n in t&&e[n]!==t[n]));return Object.freeze([...r,...s,...o])}return[]}async function O(t){if(Array.isArray(t)&&0!==t.length){const n=M(),a=Object.fromEntries(t.map((e=>[e,n[e]])));await Promise.allSettled(Array.from(e.entries(),(([e,n])=>{(0===n.length||n.some((e=>t.includes(e))))&&e({diff:t,state:a})})))}}function L(t,...n){return t instanceof Function&&!e.has(t)&&(e.set(t,n),!0)}function k(e,t=null){return new Proxy({toString:()=>b(e,t)?.toString()??"",valueOf(){const n=b(e,t);return n?.valueOf instanceof Function?n.valueOf():n},toJSON:()=>b(e,t),[Symbol.toPrimitive](n){const a=b(e,t);return typeof a===n||"default"===n&&"object"!=typeof a?a:a?.[Symbol.toPrimitive]instanceof Function?a?.[Symbol.toPrimitive]instanceof Function?a[Symbol.toPrimitive](n):a:"number"!==n&&a?.toString instanceof Function?a.toString():"number"===n?parseFloat(a):a},[a]:!0,[Symbol.toStringTag]:"StateValue",[Symbol.iterator]:()=>b(e,t)?.[Symbol.iterator]()},{defineProperty(n,a,r){const o=b(e,t);return!!Reflect.defineProperty(o,a,r)&&(j(e,o),o)},deleteProperty:(n,a)=>Reflect.deleteProperty(b(e,t),a),get(n,a){const r=b(e,t);if(a in n)return n[a];if("object"==typeof r){const e=Reflect.get(r,a,r);return e instanceof Function?e.bind(r):e}return r[a]},getOwnPropertyDescriptor:(n,a)=>Reflect.getOwnPropertyDescriptor(b(e,t),a),getPrototypeOf(){const n=b(e,t);return"object"==typeof n?Reflect.getPrototypeOf(n):Object.getPrototypeOf(n)},has:(n,a)=>Reflect.has(b(e,t),a),isExtensible:()=>Reflect.isExtensible(b(e,t)),ownKeys:()=>Reflect.ownKeys(b(e,t)),preventExtensions:()=>Reflect.preventExtensions(b(e,t)),set(n,a,r){const o=b(e,t);return!!Reflect.set(o,a,r,o)&&(j(e,o),!0)}})}const P=t=>e.delete(t),M=()=>Object.freeze(null===history.state?{}:structuredClone(history.state)),R=e=>e in M();function j(e,t){const n=M();if("function"==typeof t)x(e,t);else if(n[e]!==t){const r={key:e,oldValue:n[e],newValue:t},o=new CustomEvent(p,{cancelable:!0,detail:r});s.dispatchEvent(o),o.defaultPrevented||(U({...M(),[e]:t?.[a]?t.valueOf():t},location.href),s.dispatchEvent(new CustomEvent(g,{detail:r})))}}const x=async(e,t)=>await Promise.try((()=>t(b(e),e))).then((t=>(j(e,t),t)));function N(e,t=null){return[k(e,t),t=>j(e,t)]}function F(e){const t={...M()};delete t[e],U(t,location.href)}const H=(e="aegis:state")=>localStorage.setItem(e,JSON.stringify(M())),D=(e="aegis:state")=>w(JSON.parse(localStorage.getItem(e)),location.href),I=()=>U({},location.href);function U(e=M(),n=location.href){w(e,n)&&o&&t.postMessage(S("update"))}function q({signal:e}={}){t.addEventListener("message",(e=>{if(e.isTrusted&&"string"==typeof e.data.msgId&&"string"==typeof e.data.sender&&e.data.sender!==n&&"object"==typeof e.data.state&&("string"!=typeof e.data.recipient||e.data.recipient===n)){const n=M();if(0!==T(e.data.state,n).length)switch(e.data.type){case"update":w({...n,...e.data.state},location.href);break;case"sync":o&&t.postMessage(S("update",e.data.sender));break;case"clear":w({},location.href);break;default:reportError(new Error(`Unhandled broadcast channel message type: ${e.data.type}`))}}}),{signal:e}),t.postMessage(S("sync")),e instanceof AbortSignal&&e.aborted?A():e instanceof AbortSignal&&e.addEventListener("abort",A,{once:!0})}function C(e=document.documentElement,{signal:t,base:n=document.documentElement}={}){if(t instanceof AbortSignal&&t.aborted)throw t.reason;if("string"==typeof e)C(n.querySelector(e),{signal:t});else{if(!(e instanceof Element||e instanceof ShadowRoot))throw new TypeError("Target must be an element, selector, or shadow root.");v.observe(e,{childList:!0,subtree:!0,attributeFilter:[u],attributeOldValue:!0}),h(`[${u}]`,e).forEach((e=>{e[r]=E.bind(e),L(e[r],e.dataset[i]),e[r]({state:history.state})})),t instanceof AbortSignal&&t.addEventListener("abort",(()=>v.disconnect()),{once:!0})}}function $(e,t,{attr:n,style:a,base:r=document.body}={}){if("string"==typeof e)$(r.querySelector(e),t,{attr:n,style:a});else{if(!(e instanceof Element))throw new TypeError("Target must be an element or selector.");e instanceof HTMLElement?(e.dataset[i]=t,"string"==typeof n?e.dataset[c]=n:"string"==typeof a&&(e.dataset[l]=a),requestAnimationFrame((()=>E.call(e,{state:history.state??{}})))):e instanceof Element&&(e.setAttribute(u,t),"string"==typeof n?e.setAttribute(d,n):"string"==typeof a&&e.setAttribute(m,a),requestAnimationFrame((()=>E.call(e,{state:history.state??{}}))))}}function V(e,t,n,{base:a=document.documentElement,signal:r}={}){if(r instanceof AbortSignal&&r.aborted)throw r.reason;if("string"==typeof e)return V(a.querySelector(e),t,n,{});if(e instanceof Element){if("string"!=typeof t||0===t.length)throw new TypeError("State key must be a non-empty string.");if(n instanceof Function){const a=function({state:e={}}={}){return n.call(this,e[t],this)}.bind(e);return L(a,t),r instanceof AbortSignal&&r.addEventListener("abort",(()=>P(a)),{once:!0}),a}throw new TypeError("Callback must be a function.")}throw new TypeError("Target must be an element or selector.")}function K({target:e,type:t}){if(!(e instanceof HTMLElement))throw new TypeError(`Event ${t} target must be an HTMLElement.`);if(e instanceof HTMLSelectElement)j(e.name,e.multiple?Array.from(e.selectedOptions,(e=>e.value)):e.value);else if(e instanceof HTMLInputElement)switch(e.type){case"checkbox":{const t=Array.from(e.form?.elements??[e]).filter((t=>t.name===e.name&&"checkbox"===t.type));1===t.length?j(e.name,"on"===e.value?e.checked:e.value):j(e.name,Array.from(t).filter((e=>e.checked)).map((e=>e.value)))}break;case"number":case"range":j(e.name,e.valueAsNumber);break;case"date":j(e.name,e.valueAsDate?.toISOString()?.split("T")?.at(0));break;case"file":j(e.name,e.multiple?Array.from(e.files):e.files.item(0));break;case"datetime-local":j(e.name,e.valueAsDate);break;default:j(e.name,e.value)}else if(e instanceof HTMLTextAreaElement)j(e.name,e.value);else{if(!e.constructor.formAssociated)throw new TypeError(`Event ${t} target is not a valid form element.`);j(e.name,e.value)}}function J(e,{signal:t,once:n=!1,passive:a=!1}={}){s.addEventListener(g,e,{signal:t,once:n,passive:a})}function _(e,{signal:t,once:n=!1,passive:a=!1}={}){s.addEventListener(p,e,{signal:t,once:n,passive:a})}export{s as EVENT_TARGET,p as beforeChangeEvent,$ as bindState,g as changeEvent,K as changeHandler,I as clearState,A as closeChannel,V as createStateHandler,F as deleteState,T as diffState,k as getState,M as getStateObj,R as hasState,N as manageState,O as notifyStateChange,C as observeDOMState,L as observeStateChanges,_ as onBeforeStateChange,J as onStateChange,U as replaceState,D as restoreState,H as saveState,j as setState,c as stateAttr,d as stateAttrAttribute,i as stateKey,u as stateKeyAttribute,f as stateProperty,y as statePropertyAttr,l as stateStyle,m as stateStyleAttribute,P as unobserveStateChanges,x as updateState,q as watchState};