native-document 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (47) hide show
  1. package/dist/native-document.dev.js +2346 -0
  2. package/dist/native-document.min.js +1 -0
  3. package/elements.js +2 -0
  4. package/index.js +11 -0
  5. package/package.json +16 -0
  6. package/readme.md +495 -0
  7. package/rollup.config.js +29 -0
  8. package/router.js +9 -0
  9. package/src/data/MemoryManager.js +60 -0
  10. package/src/data/Observable.js +162 -0
  11. package/src/data/ObservableChecker.js +24 -0
  12. package/src/data/ObservableItem.js +101 -0
  13. package/src/data/Store.js +74 -0
  14. package/src/elements/content-formatter.js +32 -0
  15. package/src/elements/control/for-each.js +110 -0
  16. package/src/elements/control/show-if.js +86 -0
  17. package/src/elements/control/switch.js +88 -0
  18. package/src/elements/description-list.js +5 -0
  19. package/src/elements/form.js +71 -0
  20. package/src/elements/html5-semantics.js +12 -0
  21. package/src/elements/img.js +45 -0
  22. package/src/elements/index.js +21 -0
  23. package/src/elements/interactive.js +7 -0
  24. package/src/elements/list.js +6 -0
  25. package/src/elements/medias.js +8 -0
  26. package/src/elements/meta-data.js +9 -0
  27. package/src/elements/table.js +14 -0
  28. package/src/errors/ArgTypesError.js +7 -0
  29. package/src/errors/NativeDocumentError.js +8 -0
  30. package/src/errors/RouterError.js +9 -0
  31. package/src/router/Route.js +102 -0
  32. package/src/router/RouteGroupHelper.js +52 -0
  33. package/src/router/Router.js +232 -0
  34. package/src/router/RouterComponent.js +37 -0
  35. package/src/router/link.js +27 -0
  36. package/src/router/modes/HashRouter.js +83 -0
  37. package/src/router/modes/HistoryRouter.js +66 -0
  38. package/src/router/modes/MemoryRouter.js +71 -0
  39. package/src/utils/args-types.js +100 -0
  40. package/src/utils/debug-manager.js +34 -0
  41. package/src/utils/helpers.js +37 -0
  42. package/src/utils/prototypes.js +16 -0
  43. package/src/utils/validator.js +96 -0
  44. package/src/wrappers/AttributesWrapper.js +94 -0
  45. package/src/wrappers/DocumentObserver.js +51 -0
  46. package/src/wrappers/HtmlElementEventsWrapper.js +77 -0
  47. package/src/wrappers/HtmlElementWrapper.js +174 -0
@@ -0,0 +1 @@
1
+ var NativeDocument=function(t){"use strict";function n(e,t,n){return e.addEventListener(t,n),e}const r="production"===process.env.NODE_ENV,o={enabled:!r,enable(){this.enabled=!0,console.log("🔍 NativeDocument Debug Mode enabled")},disable(){this.enabled=!1},log:r?()=>{}:function(e,t,n){this.enabled&&(console.group(`🔍 [${e}] ${t}`),n&&console.log(n),console.trace(),console.groupEnd())},warn:r?()=>{}:function(e,t,n){this.enabled&&console.warn(`⚠️ [${e}] ${t}`,n)},error:r?()=>{}:function(e,t,n){console.error(`❌ [${e}] ${t}`,n)}},s=function(){let e=0;const t=new Map,n=new FinalizationRegistry(e=>{o.log("MemoryManager","🧹 Auto-cleanup observable:",e),e.listeners.splice(0)});return{register(r,o){const s=++e,i={id:s,listeners:o};return n.register(r,i),t.set(s,new WeakRef(r)),s},cleanup(){for(const[e,n]of t){const e=n.deref();e&&e.cleanup()}t.clear()},cleanObservables(e){if(t.size<e)return;let n=0;for(const[e,r]of t)r.deref()||(t.delete(e),n++);n>0&&o.log("Memory Auto Clean",`🧹 Cleaned ${n} orphaned observables`)}}}();class i extends Error{constructor(e,t={}){super(e),this.name="NativeDocumentError",this.context=t,this.timestamp=(new Date).toISOString()}}function a(e,t){this.observable=e,this.checker=t,this.subscribe=function(n){return e.subscribe(e=>{n&&n(t(e))})},this.val=function(){return t&&t(e.val())},this.cleanup=function(){return e.cleanup()}}function u(e){if(void 0===e)throw new i("ObservableItem requires an initial value");if(e instanceof u)throw new i("ObservableItem cannot be an Observable");const t="object"==typeof e?JSON.parse(JSON.stringify(e)):e;let n=e,r=e,c=!1;const l=[];s.register(this,l),this.trigger=()=>{l.forEach(e=>{try{e(r,n)}catch(t){o.error("Listener Undefined","Error in observable listener:",t),this.unsubscribe(e)}})},this.originalValue=()=>t,this.set=e=>{const t="function"==typeof e?e(r):e;r!==t&&(n=r,r=t,this.trigger())},this.val=()=>r,this.cleanup=function(){l.splice(0),c=!0},this.subscribe=e=>{if(c)return o.warn("Observable subscription","⚠️ Attempted to subscribe to a cleaned up observable."),()=>{};if("function"!=typeof e)throw new i("Callback must be a function");return l.push(e),()=>this.unsubscribe(e)},this.unsubscribe=e=>{const t=l.indexOf(e);t>-1&&l.splice(t,1)},this.check=function(e){return new a(this,e)}}const c={isObservable:e=>e instanceof u||e instanceof a,isProxy:e=>e?.__isProxy__,isObservableChecker:e=>e instanceof a,isArray:e=>Array.isArray(e),isString:e=>"string"==typeof e,isNumber:e=>"number"==typeof e,isBoolean:e=>"boolean"==typeof e,isFunction:e=>"function"==typeof e,isObject:e=>"object"==typeof e,isJson:e=>"object"==typeof e&&null!==e&&!Array.isArray(e),isElement:e=>e instanceof HTMLElement||e instanceof DocumentFragment||e instanceof Text,isFragment:e=>e instanceof DocumentFragment,isStringOrObservable(e){return this.isString(e)||this.isObservable(e)},isValidChild(e){return null===e||this.isElement(e)||this.isObservable(e)||["string","number","boolean"].includes(typeof e)},isValidChildren(e){Array.isArray(e)||(e=[e]);return 0===e.filter(e=>!this.isValidChild(e)).length},validateChildren(e){Array.isArray(e)||(e=[e]);const t=e.filter(e=>!this.isValidChild(e));if(t.length>0)throw new i(`Invalid children detected: ${t.map(e=>typeof e).join(", ")}`);return e},validateAttributes(e){if(!e||"object"!=typeof e)return e;const t=[],n=Object.keys(e).filter(e=>t.includes(e));return n.length>0&&o.warn("Validator",`Reserved attributes found: ${n.join(", ")}`),e},validateEventCallback(e){if("function"!=typeof e)throw new i("Event callback must be a function")}},l=function(e,t,n){n?e.classList.add(t):e.classList.remove(t)};function d(e,t){for(let n in t){const r=t[n];c.isObservable(r)?(l(e,n,r.val()),r.subscribe(t=>l(e,n,t))):l(e,n,r)}}function h(e,t){for(let n in t){const r=t[n];c.isObservable(r)?(e.style[n]=r.val(),r.subscribe(t=>{e.style[n]=t})):e.style[n]=r}}const p=function(e,t,n={}){let r=null,o=0;const{leading:s=!0,trailing:i=!0,debounce:a=!1}=n;return function(...n){const u=Date.now();if(a)return clearTimeout(r),void(r=setTimeout(()=>e.apply(this,n),t));s&&u-o>=t&&(e.apply(this,n),o=u),i&&!r&&(r=setTimeout(()=>{e.apply(this,n),o=Date.now(),r=null},t-(u-o)))}},f=function(e,t){return e.replace(new RegExp(`^[${t}]+|[${t}]+$`,"g"),"")},m={elements:new Map,observer:null,checkMutation:p(function(){for(const[e,t]of m.elements.entries()){const n=document.body.contains(e);n&&!t.inDom?(t.inDom=!0,t.mounted.forEach(t=>t(e))):!n&&t.inDom&&(t.inDom=!1,t.unmounted.forEach(t=>t(e)))}},10,{debounce:!0}),watch:function(e){let t={};if(m.elements.has(e))t=m.elements.get(e);else{const n=document.body.contains(e);t={inDom:n,mounted:new Set,unmounted:new Set},m.elements.set(e,t)}return{watch:()=>m.elements.set(e,t),disconnect:()=>m.elements.delete(e),mounted:e=>t.mounted.add(e),unmounted:e=>t.unmounted.add(e)}}};m.observer=new MutationObserver(m.checkMutation),m.observer.observe(document.body,{childList:!0,subtree:!0});const b=function(e,t){const n=document.createTextNode("");return t.subscribe(e=>n.textContent=String(e)),n.textContent=t.val(),e&&e.appendChild(n),n},v=function(e,t){const n=document.createTextNode("");return n.textContent=String(t),e&&e.appendChild(n),n},y=function(e){return c.isObservable(e)?b(null,e):v(null,e)},g={createElement:e=>e?document.createElement(e):document.createDocumentFragment(),processChildren(e,t){if(null===e)return;(Array.isArray(e)?e:[e]).forEach(e=>{null!==e&&(c.isElement(e)?t.appendChild(e):c.isObservable(e)?b(t,e):e&&v(t,e))})},processAttributes(e,t){c.isFragment(e)||t&&function(e,t){if(c.validateAttributes(t),!c.isObject(t))throw console.log(t),new i("Attributes must be an object");for(let n in t){const r=t[n];c.isObservable(r)?(r.subscribe(t=>e.setAttribute(n,t)),e.setAttribute(n,r.val()),"value"===n&&(["checkbox","radio"].includes(e.type)?e.addEventListener("input",()=>r.set(e.checked)):e.addEventListener("input",()=>r.set(e.value)))):"class"===n&&c.isJson(r)?d(e,r):"style"===n&&c.isJson(r)?h(e,r):e.setAttribute(n,r)}}(e,t)},setup(e,t,r){e.nd={},function(e){e.nd||(e.nd={}),e.nd.on=function(t){for(const r in t){const o=t[r];n(e,r,o)}return e},e.nd.on.prevent=function(t){for(const r in t){const o=t[r];n(e,r,t=>(t.preventDefault(),o&&o(t),e))}return e};const t={click:t=>n(e,"click",t),focus:t=>n(e,"focus",t),blur:t=>n(e,"blur",t),input:t=>n(e,"input",t),change:t=>n(e,"change",t),keyup:t=>n(e,"keyup",t),keydown:t=>n(e,"keydown",t),beforeInput:t=>n(e,"beforeinput",t),mouseOver:t=>n(e,"mouseover",t),mouseOut:t=>n(e,"mouseout",t),mouseDown:t=>n(e,"mousedown",t),mouseUp:t=>n(e,"mouseup",t),mouseMove:t=>n(e,"mousemove",t),hover:(t,n)=>{e.addEventListener("mouseover",t),e.addEventListener("mouseout",n)},dropped:t=>n(e,"drop",t),submit:t=>n(e,"submit",t),dragEnd:t=>n(e,"dragend",t),dragStart:t=>n(e,"dragstart",t),drop:t=>n(e,"drop",t),dragOver:t=>n(e,"dragover",t),dragEnter:t=>n(e,"dragenter",t),dragLeave:t=>n(e,"dragleave",t)};for(let r in t)e.nd.on[r]=t[r],e.nd.on.prevent[r]=function(t){return n(e,r.toLowerCase(),e=>{e.preventDefault(),t&&t(e)}),e}}(e);const o="function"==typeof r?r(e):e;return function(e){e.nd.wrap=t=>{if(!c.isFunction(t))throw new i("Callback must be a function");return t&&t(e),e},e.nd.ref=(t,n)=>(t[n]=e,e);let t=null;e.nd.lifecycle=function(n){return t=t||m.watch(e),n.mounted&&t.mounted(n.mounted),n.unmounted&&t.unmounted(n.unmounted),e},e.nd.mounted=n=>(t=t||m.watch(e),t.mounted(n),e),e.nd.unmounted=n=>(t=t||m.watch(e),t.unmounted(n),e)}(o),o}};function w(e,t){const n=e.toLowerCase().trim(),r=function(e,r=null){try{if(c.isValidChildren(e)){const t=r;r=e,e=t}const o=g.createElement(n);return g.processAttributes(o,e),g.processChildren(r,o),g.setup(o,e,t)}catch(e){o.error("ElementCreation",`Error creating ${n}`,e)}};return r.hold=(e,t)=>()=>r(e,t),r}class C extends Error{constructor(e,t){super(`${e}\n\n${t.join("\n")}\n\n`)}}const E={string:e=>({name:e,type:"string",validate:e=>c.isString(e)}),number:e=>({name:e,type:"number",validate:e=>c.isNumber(e)}),boolean:e=>({name:e,type:"boolean",validate:e=>c.isBoolean(e)}),observable:e=>({name:e,type:"observable",validate:e=>c.isObservable(e)}),element:e=>({name:e,type:"element",validate:e=>c.isElement(e)}),function:e=>({name:e,type:"function",validate:e=>c.isFunction(e)}),object:e=>({name:e,type:"object",validate:e=>c.isObject(e)}),objectNotNull:e=>({name:e,type:"object",validate:e=>c.isObject(e)&&null!==e}),children:e=>({name:e,type:"children",validate:e=>c.validateChildren(e)}),attributes:e=>({name:e,type:"attributes",validate:e=>c.validateAttributes(e)}),optional:e=>({...e,optional:!0}),oneOf:(e,...t)=>({name:e,type:"oneOf",types:t,validate:e=>t.some(t=>t.validate(e))})},O=(e,t,n="Function")=>{if(!c.isArray(t))throw new i("withValidation : argSchema must be an array");return function(...r){return((e,t,n="Function")=>{if(!t)return;const r=[],o=t.filter(e=>!e.optional).length;if(e.length<o&&r.push(`${n}: Expected at least ${o} arguments, got ${e.length}`),t.forEach((t,o)=>{const s=o+1,i=e[o];if(void 0!==i){if(!t.validate(i)){const e=i?.constructor?.name||typeof i;r.push(`${n}: Invalid argument '${t.name}' at position ${s}, expected ${t.type}, got ${e}`)}}else t.optional||r.push(`${n}: Missing required argument '${t.name}' at position ${s}`)}),r.length>0)throw new C("Argument validation failed",r)})(r,t,e.name||n),e.apply(this,r)}};function S(e){return new u(e)}Function.prototype.args=function(...e){return O(this,e)},Function.prototype.errorBoundary=function(e){return(...t)=>{try{return this.apply(this,t)}catch(t){return e(t)}}},S.computed=function(e,t=[]){const n=new u(e()),r=p(()=>n.set(e()),10,{debounce:!0});return t.forEach(e=>e.subscribe(r)),n},S.cleanup=function(e){e.cleanup()},S.value=function(e){if(c.isObservable(e))return e.val();if(c.isProxy(e))return e.$val();if(c.isArray(e)){const t=[];return e.forEach(e=>{t.push(S.value(e))}),t}return e},S.init=function(e){const t={};for(const n in e){const r=e[n];c.isJson(r)?(console.log(r),t[n]=S.init(r)):t[n]=S(r)}const n=function(){const e={};for(const n in t){const r=t[n];c.isObservable(r)?e[n]=r.val():c.isProxy(r)?e[n]=r.$val():e[n]=r}return e};return new Proxy(t,{get:(e,t)=>"__isProxy__"===t||("$val"===t?n:void 0!==e[t]?e[t]:void 0),set(e,t,n){void 0!==e[t]&&e[t].set(n)}})},S.object=S.init,S.array=function(e){if(!Array.isArray(e))throw new i("Observable.array : target must be an array");const t=S(e);["push","pop","shift","unshift","reverse","sort","splice"].forEach(e=>{t[e]=function(...n){const r=t.val(),o=r[e].apply(r,arguments);return t.trigger(),o}});return["map","filter","reduce","some","every","find"].forEach(e=>{t[e]=function(n){return t.val()[e](n)}}),t},S.autoCleanup=function(e=!1,t={}){if(!e)return;const{interval:n=6e4,threshold:r=100}=t;window.addEventListener("beforeunload",()=>{s.cleanup()}),setInterval(()=>s.cleanObservables(r),n)};const F=function(){const e=new Map;return{use(t){const{observer:n,followers:r}=e.get(t),o=S(n.val()),s=n.subscribe(e=>o.set(e)),i=o.subscribe(e=>n.set(e));return o.destroy=()=>{s(),i(),o.cleanup()},r.add(o),o},follow(e){return this.use(e)},create(t,n){const r=S(n);return e.set(t,{observer:r,followers:new Set}),r},get(t){const n=e.get(t);return n?n.observer:null},getWithFollowers:t=>e.get(t),delete(t){const n=e.get(t);n&&(n.observer.cleanup(),n.followers.forEach(e=>e.destroy()),n.observer.clear())}}}();const k=function(e,t,n){if(!c.isObservable(e))return o.warn("ShowIf","ShowIf : condition must be an Observable / "+n,e);const r=document.createDocumentFragment(),s=document.createComment("Show if : "+(n||"")),i=document.createComment("Show if : "+(n||""));r.appendChild(s),r.appendChild(i);let a=null;const u=()=>a||(a="function"==typeof t?t():t,c.isStringOrObservable(a)&&(a=y(a)),a);return e.val()&&r.appendChild(u()),e.subscribe(e=>{const t=i.parentNode;e&&t?t.insertBefore(u(),i):c.isElement(a)&&a.remove()}),r},A=function(e,t,n){if(!c.isObservable(e))throw new i("Toggle : condition must be an Observable");const r=document.createComment("Toggle Start"),o=document.createComment("Toggle End"),s=document.createDocumentFragment();s.appendChild(r),s.appendChild(o);const a={onTrueNode:c.isFunction(t)?null:t,onFalseNode:c.isFunction(n)?null:n};c.isStringOrObservable(a.onTrueNode)&&(a.onTrueNode=y(a.onTrueNode)),c.isStringOrObservable(a.onFalseNode)&&(a.onFalseNode=y(a.onFalseNode));const u=e=>{const r=o.parentNode;r&&(e?(!a.onTrueNode&&c.isFunction(t)&&(a.onTrueNode=t()),a.onFalseNode&&a.onFalseNode.remove(),r.insertBefore(a.onTrueNode,o)):(!a.onFalseNode&&c.isFunction(n)&&(a.onFalseNode=n()),a.onTrueNode&&a.onTrueNode.remove(),r.insertBefore(a.onFalseNode,o)))};return e.subscribe(u),u(e.val()),s},$=w("div"),T=w("span"),I=w("span"),q=w("p"),N=q,x=w("strong"),D=w("h1"),R=w("h2"),L=w("h3"),j=w("h4"),M=w("h5"),H=w("h6"),_=w("br"),B=w("a"),P=w("pre"),V=w("code"),U=w("blockquote"),W=w("hr"),J=w("em"),z=w("small"),K=w("mark"),Q=w("del"),G=w("ins"),X=w("sub"),Y=w("sup"),Z=w("abbr"),ee=w("cite"),te=w("q"),ne=w("dl"),re=w("dt"),oe=w("dd"),se=w("form",function(e){return e.submit=function(t){return"function"==typeof t?(e.on.submit(e=>{e.preventDefault(),t(e)}),e):(this.setAttribute("action",t),e)},e.multipartFormData=function(){return this.setAttribute("enctype","multipart/form-data"),e},e.post=function(t){return this.setAttribute("method","post"),this.setAttribute("action",t),e},e.get=function(e){this.setAttribute("method","get"),this.setAttribute("action",e)},e}),ie=w("input"),ae=w("textarea"),ue=ae,ce=w("select"),le=w("fieldset"),de=w("option"),he=w("legend"),pe=w("datalist"),fe=w("output"),me=w("progress"),be=w("meter"),ve=w("button"),ye=w("main"),ge=w("section"),we=w("article"),Ce=w("aside"),Ee=w("nav"),Oe=w("figure"),Se=w("figcaption"),Fe=w("header"),ke=w("footer"),Ae=w("img"),$e=function(e,t){return Ae({src:e,...t})},Te=w("details"),Ie=w("summary"),qe=w("dialog"),Ne=w("menu"),xe=w("ol"),De=w("ul"),Re=w("li"),Le=w("audio"),je=w("video"),Me=w("source"),He=w("track"),_e=w("canvas"),Be=w("svg"),Pe=w("time"),Ve=w("data"),Ue=w("address"),We=w("kbd"),Je=w("samp"),ze=w("var"),Ke=w("wbr"),Qe=w("caption"),Ge=w("table"),Xe=w("thead"),Ye=w("tfoot"),Ze=w("tbody"),et=w("tr"),tt=et,nt=w("th"),rt=nt,ot=nt,st=w("td"),it=st,at=w("");var ut=Object.freeze({__proto__:null,Abbr:Z,Address:Ue,Article:we,Aside:Ce,AsyncImg:function(e,t,n,r){const o=$e(t||e,n),s=new Image;return s.onload=()=>{c.isFunction(r)&&r(null,o),o.src=e},s.onerror=()=>{c.isFunction(r)&&r(new i("Image not found"))},c.isObservable(e)&&e.subscribe(e=>{s.src=e}),s.src=e,o},Audio:Le,BaseImage:Ae,Blockquote:U,Br:_,Button:ve,Canvas:_e,Caption:Qe,Checkbox:e=>ie({type:"checkbox",...e}),Cite:ee,Code:V,ColorInput:e=>ie({type:"color",...e}),Data:Ve,Datalist:pe,DateInput:e=>ie({type:"date",...e}),DateTimeInput:e=>ie({type:"datetime-local",...e}),Dd:oe,Del:Q,Details:Te,Dialog:qe,Div:$,Dl:ne,Dt:re,Em:J,EmailInput:e=>ie({type:"email",...e}),FieldSet:le,FigCaption:Se,Figure:Oe,FileInput:e=>ie({type:"file",...e}),Footer:ke,ForEach:function(e,t,n){const r=document.createDocumentFragment(),o=document.createComment("Foreach start"),s=document.createComment("Foreach end");r.appendChild(o),r.appendChild(s);let i=new Map;const a=(e,r)=>{const o=((e,t,n)=>{if(c.isFunction(n))return n(e,t);if(c.isObservable(e)){const r=e.val();return r&&n?r[n]:t}return e[n]??t})(e,r,n);if(i.has(o))i.get(o).indexObserver.set(r);else{const n=S(r);let s=t(e,n);c.isStringOrObservable(s)&&(s=y(s)),i.set(o,{child:s,indexObserver:n})}return o},u=new Set,l=()=>{const t=c.isObservable(e)?e.val():e,n=s.parentNode;if(!n)return;if(u.clear(),Array.isArray(t))t.forEach((e,t)=>u.add(a(e,t)));else for(const e in t)u.add(a(t[e],e));((e,t)=>{for(const[n,{child:r}]of e.entries())t.has(n)||r.remove()})(i,u);let r=s;for(const e of[...u].reverse()){const{child:t}=i.get(e);if(t){if(r&&r.previousSibling===t){r=t;continue}n.insertBefore(t,r),r=t}}};return l(),c.isObservable(e)&&e.subscribe(p((e,t)=>{l()},50,{debounce:!0})),r},Form:se,Fragment:at,H1:D,H2:R,H3:L,H4:j,H5:M,H6:H,Header:Fe,HiddenInput:e=>ie({type:"hidden",...e}),HideIf:function(e,t,n){const r=S(!e.val());return e.subscribe(e=>r.set(!e)),k(r,t,n)},HideIfNot:function(e,t,n){return k(e,t,n)},Hr:W,Img:$e,Input:ie,Ins:G,Kbd:We,Label:I,LazyImg:function(e,t){return $e(e,{...t,loading:"lazy"})},Legend:he,Link:B,ListItem:Re,Main:ye,Mark:K,Menu:Ne,Meter:be,MonthInput:e=>ie({type:"month",...e}),Nav:Ee,NumberInput:e=>ie({type:"number",...e}),Option:de,OrderedList:xe,Output:fe,P:q,Paragraph:N,PasswordInput:e=>ie({type:"password",...e}),Pre:P,Progress:me,Quote:te,Radio:e=>ie({type:"radio",...e}),RangeInput:e=>ie({type:"range",...e}),ReadonlyInput:e=>ie({readonly:!0,...e}),Samp:Je,SearchInput:e=>ie({type:"search",...e}),Section:ge,Select:ce,ShowIf:k,SimpleButton:(e,t)=>ve(e,{type:"button",...t}),Small:z,Source:Me,Span:T,Strong:x,Sub:X,SubmitButton:(e,t)=>ve(e,{type:"submit",...t}),Summary:Ie,Sup:Y,Svg:Be,Switch:A,TBody:Ze,TBodyCell:it,TFoot:Ye,TFootCell:ot,THead:Xe,THeadCell:rt,TRow:tt,Table:Ge,Td:st,TelInput:e=>ie({type:"tel",...e}),TextArea:ae,TextInput:ue,Th:nt,Time:Pe,TimeInput:e=>ie({type:"time",...e}),Tr:et,Track:He,UnorderedList:De,UrlInput:e=>ie({type:"url",...e}),Var:ze,Video:je,Wbr:Ke,WeekInput:e=>ie({type:"week",...e}),When:function(e){let t=null,n=null;return{show(e){if(!c.isElement(e)&&!c.isFunction(e))throw new i("When : onTrue must be a valid Element");return t=e,this},otherwise(r){if(!c.isElement(r)&&!c.isFunction(r))throw new i("When : onFalse must be a valid Element");return n=r,A(e,t,n)}}}});const ct={};function lt(e,t,n={}){e="/"+f(e,"/");let r=null,o=n.name||null;const s=n.middlewares||[],i=n.shouldRebuild||!1,a=n.with||{},u={},c=[],l=e=>{if(!e)return null;const[t,n]=e.split(":");let r=a[t];return!r&&n&&(r=ct[n]),r||(r="[^/]+"),r=r.replace("(","(?:"),{name:t,pattern:`(${r})`}},d=()=>{if(r)return r;const t=e.replace(/\{(.*?)}/gi,(e,t)=>{const n=l(t);return n&&n.pattern?(u[n.name]=n.pattern,c.push(n.name),n.pattern):e});return r=new RegExp("^"+t+"$"),r};this.name=()=>o,this.component=()=>t,this.middlewares=()=>s,this.shouldRebuild=()=>i,this.path=()=>e,this.match=function(e){e="/"+f(e,"/");if(!d().exec(e))return!1;const t={};return d().exec(e).forEach((e,n)=>{if(n<1)return;const r=c[n-1];t[r]=e}),t},this.url=function(t){const n=e.replace(/\{(.*?)}/gi,(e,n)=>{const r=l(n);if(t.params&&t.params[r.name])return t.params[r.name];throw new Error(`Missing parameter '${r.name}'`)}),r="object"==typeof t.query?new URLSearchParams(t.query).toString():null;return(t.basePath?t.basePath:"")+(r?`${n}?${r}`:n)}}class dt extends Error{constructor(e,t){super(e),this.context=t}}const ht=(e,t)=>{const n=[];return e.forEach(e=>{n.push(f(e.suffix,"/"))}),n.push(f(t,"/")),n.join("/")},pt=(e,t)=>{const n=[];return e.forEach(e=>{e.options.middlewares&&n.push(...e.options.middlewares)}),t&&n.push(...t),n},ft=(e,t)=>{const n=[];return e.forEach(e=>{e.options?.name&&n.push(e.options.name)}),t&&n.push(t),n.join(".")};function mt(){const e=[];let t=0;const n=n=>{const o=t+n;if(!e[o])return;t=o;const{route:s,params:i,query:a,path:u}=e[o];r(u)},r=e=>{window.location.replace(`${window.location.pathname}${window.location.search}#${e}`)},o=()=>window.location.hash.slice(1);this.push=function(n){const{route:s,params:i,query:a,path:u}=this.resolve(n);u!==o()&&(e.splice(t+1),e.push({route:s,params:i,query:a,path:u}),t++,r(u))},this.replace=function(n){const{route:r,params:s,query:i,path:a}=this.resolve(n);a!==o()&&(e[t]={route:r,params:s,query:i,path:a})},this.forward=function(){return t<e.length-1&&n(1)},this.back=function(){return t>0&&n(-1)},this.init=function(n){window.addEventListener("hashchange",()=>{const{route:e,params:t,query:n,path:r}=this.resolve(o());this.handleRouteChange(e,t,n,r)});const{route:r,params:s,query:i,path:a}=this.resolve(n||o());e.push({route:r,params:s,query:i,path:a}),t=0,this.handleRouteChange(r,s,i,a)}}function bt(){this.push=function(e){try{const{route:t,path:n,params:r,query:o}=this.resolve(e);if(window.history.state&&window.history.state.path===n)return;window.history.pushState({name:t.name(),params:r,path:n},t.name()||n,n),this.handleRouteChange(t,r,o,n)}catch(e){o.error("HistoryRouter","Error in pushState",e)}},this.replace=function(e){const{route:t,path:n,params:r}=this.resolve(e);try{window.history.replaceState({name:t.name(),params:r,path:n},t.name()||n,n),this.handleRouteChange(t,r,{},n)}catch(e){o.error("HistoryRouter","Error in replaceState",e)}},this.forward=function(){window.history.forward()},this.back=function(){window.history.back()},this.init=function(e){window.addEventListener("popstate",e=>{try{if(!e.state||!e.state.path)return;const t=e.state.path,{route:n,params:r,query:o,path:s}=this.resolve(t);if(!n)return;this.handleRouteChange(n,r,o,s)}catch(e){o.error("HistoryRouter","Error in popstate event",e)}});const{route:t,params:n,query:r,path:s}=this.resolve(e||window.location.pathname+window.location.search);this.handleRouteChange(t,n,r,s)}}function vt(){const e=[];let t=0;const n=n=>{const r=t+n;if(!e[r])return;t=r;const{route:o,params:s,query:i,path:a}=e[r];this.handleRouteChange(o,s,i,a)};this.push=function(n){const{route:r,params:o,query:s,path:i}=this.resolve(n);e[t]&&e[t].path===i||(e.splice(t+1),e.push({route:r,params:o,query:s,path:i}),t++,this.handleRouteChange(r,o,s,i))},this.replace=function(n){const{route:r,params:o,query:s,path:i}=this.resolve(n);e[t]={route:r,params:o,query:s,path:i},this.handleRouteChange(r,o,s,i)},this.forward=function(){return t<e.length-1&&n(1)},this.back=function(){return t>0&&n(-1)},this.init=function(n){const r=n||window.location.pathname+window.location.search,{route:o,params:s,query:i,path:a}=this.resolve(r);e.push({route:o,params:s,query:i,path:a}),t=0,this.handleRouteChange(o,s,i,a)}}const yt="default";function gt(e={}){const t=[],n={},r=[],s=[],i={route:null,params:null,query:null,path:null,hash:null};if("hash"===e.mode)mt.apply(this,[]);else if("history"===e.mode)bt.apply(this,[]);else{if("memory"!==e.mode)throw new dt("Invalid router mode "+e.mode);vt.apply(this,[])}const a=function(e,t){for(const n of s)try{n(e),t&&t(e)}catch(e){o.warn("Route Listener","Error in listener:",e)}};this.routes=()=>[...t],this.currentState=()=>({...i}),this.add=function(e,o,s){const i=new lt(ht(r,e),o,{...s,middlewares:pt(r,s?.middlewares||[]),name:s?.name?ft(r,s.name):null});return t.push(i),i.name()&&(n[i.name()]=i),this},this.group=function(e,t,n){if(!c.isFunction(n))throw new dt("Callback must be a function");return r.push({suffix:e,options:t}),n(),r.pop(),this},this.generateUrl=function(e,t={},r={}){const o=n[e];if(!o)throw new dt(`Route not found for name: ${e}`);return o.url({params:t,query:r})},this.resolve=function(e){if(c.isJson(e)){const t=n[e.name];if(!t)throw new dt(`Route not found for name: ${e.name}`);return{route:t,params:e.params,query:e.query,path:t.url({...e})}}const[r,o]=e.split("?"),s="/"+f(r,"/");let i,a=null;for(const e of t)if(i=e.match(s),i){a=e;break}if(!a)throw new dt(`Route not found for url: ${r}`);const u={};if(o){const e=new URLSearchParams(o).entries();for(const[t,n]of e)u[t]=n}return{route:a,params:i,query:u,path:e}},this.subscribe=function(e){if(!c.isFunction(e))throw new dt("Listener must be a function");return s.push(e),()=>{s.splice(s.indexOf(e),1)}},this.handleRouteChange=function(e,t,n,r){i.route=e,i.params=t,i.query=n,i.path=r,console.log(i.query);const o=[...e.middlewares(),a];let s=0;const u={...i},c=e=>{if(s++,!(s>=o.length))return o[s](e||u,c)};return o[s](u,c)}}function wt(e,t){const n=e.to||e.href;if(c.isString(n)){const r=gt.get();return B({...e,href:n},t).nd.on.prevent.click(()=>{r.push(n)})}const r=gt.get(n.router);if(!r)throw new dt('Router not found "'+n.router+'" for link "'+n.name+'"');const o=r.generateUrl(n.name,n.params,n.query);return B({...e,href:o},t).nd.on.prevent.click(()=>{r.push(o)})}gt.routers={},gt.create=function(t,n){if(!c.isFunction(n))throw o.error("Router","Callback must be a function",e),new dt("Callback must be a function");const r=new gt(t);return gt.routers[t.name||yt]=r,n(r),r.init(t.entry),{mount:e=>{if(c.isString(e)){const t=document.querySelector(e);if(!t)throw new dt(`Container not found for selector: ${e}`);e=t}else if(!c.isElement(e))throw new dt("Container must be a string or an Element");!function(e,t){const n=new Map,r=function(e){t.innerHTML="",t.appendChild(e)},o=function(e){if(!e.route)return;const{route:t,params:o,query:s,path:i}=e;if(n.has(i)){const e=n.get(i);return console.log(e),void r(e)}const a=t.component();console.log({params:o,query:s});const u=a({params:o,query:s});n.set(i,u),r(u)};e.subscribe(o),o(e.currentState())}(r,e)}}},gt.get=function(e){return gt.routers[e||yt]},wt.blank=function(e,t){return B({...e,target:"_blank"},t)};var Ct=Object.freeze({__proto__:null,Link:wt,Router:gt});return t.ArgTypes=E,t.ElementCreator=g,t.HtmlElementWrapper=w,t.Observable=S,t.Store=F,t.elements=ut,t.router=Ct,t.withValidation=O,t}({});
package/elements.js ADDED
@@ -0,0 +1,2 @@
1
+
2
+ export * from './src/elements/index';
package/index.js ADDED
@@ -0,0 +1,11 @@
1
+ export { default as HtmlElementWrapper, ElementCreator } from './src/wrappers/HtmlElementWrapper'
2
+
3
+ import './src/utils/prototypes.js';
4
+
5
+ export * from './src/utils/args-types';
6
+ export * from './src/data/Observable';
7
+ export * from './src/data/Store';
8
+ import * as elements from './elements';
9
+ import * as router from './router';
10
+
11
+ export { elements, router};
package/package.json ADDED
@@ -0,0 +1,16 @@
1
+ {
2
+ "name": "native-document",
3
+ "version": "1.0.0",
4
+ "main": "index.js",
5
+ "type": "module",
6
+ "scripts": {
7
+ "build": "rollup --config rollup.config.js --watch"
8
+ },
9
+ "keywords": [],
10
+ "author": "",
11
+ "license": "ISC",
12
+ "description": "",
13
+ "devDependencies": {
14
+ "@rollup/plugin-terser": "^0.4.4"
15
+ }
16
+ }
package/readme.md ADDED
@@ -0,0 +1,495 @@
1
+ # 🚀 NativeDocument
2
+
3
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
4
+ [![JavaScript](https://img.shields.io/badge/JavaScript-ES6+-yellow.svg)](https://developer.mozilla.org/en-US/docs/Web/JavaScript)
5
+ [![Size](https://img.shields.io/badge/Size-~15KB-green.svg)](https://github.com/yourusername/nativedocument)
6
+
7
+ > A lightweight, reactive JavaScript framework for building modern web applications with zero dependencies.
8
+
9
+ NativeDocument provides a reactive programming model similar to React but using vanilla JavaScript and modern web APIs. It features automatic memory management, a complete set of HTML elements, routing, state management, and real-time DOM updates.
10
+
11
+ ## ✨ Features
12
+
13
+ - 🔄 **Reactive Observables** - Automatic DOM updates when data changes
14
+ - 🧠 **Smart Memory Management** - Automatic cleanup with FinalizationRegistry
15
+ - 🎯 **Zero Dependencies** - Pure vanilla JavaScript
16
+ - 🛣️ **Built-in Router** - Hash, History, and Memory modes
17
+ - 🏪 **Global State Management** - Share state across components
18
+ - 📦 **Complete HTML Elements** - All HTML elements with extended functionality
19
+ - 🎨 **Conditional Rendering** - ShowIf, ToggleView, ForEach components
20
+ - ✅ **Runtime Validation** - Type checking and argument validation
21
+
22
+
23
+ ## 🚀 Quick Start
24
+
25
+ ### Installation
26
+
27
+ ```bash
28
+ # Download the minified version
29
+ curl -o native-document.min.js https://raw.githubusercontent.com/afrocodeur/native-document/refs/heads/main/dist/native-document.min.js
30
+
31
+ # Or include via CDN
32
+ <script src="https://cdn.jsdelivr.net/gh/afrocodeur/native-document@latest/dist/native-document.min.js"></script>
33
+ ```
34
+
35
+
36
+ ### Basic Example
37
+
38
+ ```javascript
39
+ const { Observable } = NativeDocument;
40
+ const { H1, P, Button } = NativeDocument.elements;
41
+ // Create reactive state
42
+ const count = Observable(0);
43
+ const message = Observable("Hello World!");
44
+
45
+ // Create reactive UI
46
+ const app = Div([
47
+ H1(message),
48
+ P(['Count: ',count]),
49
+ Button("Increment").nd.on.click(() => count.set(count.val() + 1)),
50
+ Button("Reset").nd.on.click(() => count.set(0))
51
+ ]);
52
+
53
+ // Mount to DOM
54
+ document.body.appendChild(app);
55
+ ```
56
+
57
+ ## 📚 Core Concepts
58
+
59
+ ### Observables
60
+
61
+ Observables are the heart of NativeDocument's reactivity system:
62
+
63
+ ```javascript
64
+ const { Observable } = NativeDocument;
65
+ // Create observable
66
+ const name = Observable("John");
67
+
68
+ // Get value
69
+ console.log(name.val()); // "John"
70
+
71
+ // Subscribe to changes
72
+ const unsubscribe = name.subscribe((newValue, oldValue) => {
73
+ console.log(`Changed from ${oldValue} to ${newValue}`);
74
+ });
75
+
76
+ // Set value
77
+ name.set("Jane");
78
+
79
+ // Cleanup
80
+ unsubscribe();
81
+ ```
82
+
83
+
84
+ ### 📦 Elements
85
+
86
+ All HTML elements are available as functions:
87
+
88
+ ```javascript
89
+ const { Observable } = NativeDocument;
90
+ const { Div, H1, H2, P, Link, Strong, Input, Button, Br } = NativeDocument.elements;
91
+ // Basic elements
92
+ const header = H1("My App");
93
+ const paragraph = P("Welcome to NativeDocument!");
94
+
95
+ // With attributes
96
+ const link = Link({href: "https://example.com", target: "_blank" }, "Click me");
97
+
98
+ const name = Observable('');
99
+
100
+ // With reactive attributes
101
+ const input = Input({
102
+ type: "text",
103
+ value: name, // Observable binding
104
+ placeholder: "Enter name"
105
+ });
106
+
107
+ // Nested elements
108
+ const card = Div({ class: "card" }, [
109
+ H2("Card Title"),
110
+ Strong(name),
111
+ P("Card content here..."),
112
+ Button("Action")
113
+ ]);
114
+
115
+ document.body.appendChild(Div([
116
+ input,
117
+ card,
118
+ Br(),
119
+ link
120
+ ]));
121
+ ```
122
+
123
+
124
+ ### Conditional Rendering
125
+
126
+ ```javascript
127
+ const { Observable } = NativeDocument;
128
+ const { ShowIf, Switch, When, Div, P, Button, Br, H2 } = NativeDocument.elements;
129
+
130
+
131
+ const isVisible = Observable(true);
132
+ const user = Observable({ name: "John", age: 15 });
133
+
134
+ // Show/hide elements
135
+ const conditionalContent = Div([
136
+ ShowIf(isVisible, P("This content is visible!")),
137
+ Button('Toggle').nd.on.click(() => isVisible.set((currentValue) => !currentValue))
138
+ ]);
139
+
140
+ const whenContent = Div([
141
+ H2('When'),
142
+ When(user.check(u => u.age >= 18))
143
+ .show(() => P("Adult content"))
144
+ .otherwise(() => P("Minor content")),
145
+ ]);
146
+
147
+ // Toggle between two states
148
+ const toggleContent = Div([
149
+ H2('Switch'),
150
+ Switch(
151
+ user.check(u => u.age >= 18),
152
+ () => P("Adult content"), // use function to create element only if requested
153
+ () => P("Minor content")
154
+ ),
155
+ Button('Toggle user age').nd.on.click(() => user.set((currentValue) => ({ ...currentValue, age: currentValue.age === 25 ? 15 :25 })))
156
+ ]);
157
+
158
+ document.body.appendChild(Div([
159
+ conditionalContent,
160
+ Br(),
161
+ whenContent,
162
+ Br(),
163
+ toggleContent
164
+ ]));
165
+ ```
166
+
167
+ ### Lists and Iteration
168
+
169
+ ```javascript
170
+ const { Observable } = NativeDocument;
171
+ const { ForEach, Div, P, Button, Br, H4 } = NativeDocument.elements;
172
+
173
+ const items = Observable([
174
+ { id: 1, name: "Apple", price: 1.20 },
175
+ { id: 2, name: "Banana", price: 0.80 },
176
+ { id: 3, name: "Orange", price: 1.50 }
177
+ ]);
178
+
179
+ const itemList = ForEach(items, (item, index) =>
180
+ Div({ class: "item" }, [
181
+ H4(item.name),
182
+ P(`$${item.price}`),
183
+ Button("Remove").nd.on.click(() => {
184
+ items.set((currentItems) => currentItems.filter(i => i.id !== item.id))
185
+ })
186
+ ]), 'id');
187
+
188
+ document.body.appendChild(itemList);
189
+ ```
190
+
191
+ ### Forms
192
+
193
+ ```javascript
194
+
195
+ const { EmailInput, Form, Label, Strong, SubmitButton, Input, Div } = NativeDocument.elements;
196
+ const { Observable } = NativeDocument;
197
+
198
+ const formData = Observable.object({ name: "", email: "d.mamadou@miridoo.net" });
199
+
200
+ const FormItem = (title, input) => {
201
+ return Div([
202
+ Label(Strong(title)),
203
+ Div(input)
204
+ ]);
205
+ }
206
+
207
+ const form = Form([
208
+ FormItem("Name", Input({ value: formData.name })),
209
+ FormItem("Email", Input({ value: formData.email })),
210
+ SubmitButton("Submit")
211
+ ]).nd.on.prevent.submit((event) => {
212
+ console.log(Observable.value(formData));
213
+ });
214
+
215
+ document.body.appendChild(form);
216
+ ```
217
+ ## 🛣️ Routing
218
+
219
+ NativeDocument includes a powerful routing system:
220
+
221
+ ```javascript
222
+
223
+ const { Div, Button, Main } = NativeDocument.elements;
224
+ const { Router, Link } = NativeDocument.router;
225
+
226
+ const CustomMiddleware = (request, next) => {
227
+ console.log('check custom middleware', request);
228
+ // request.params.customValue = true;
229
+ return next(request);
230
+ };
231
+ const AuthMiddleware = (request, next) => {
232
+ console.log('check if user is authenticated');
233
+ return next(request);
234
+ };
235
+
236
+
237
+ const DefaultLayout = (children) => {
238
+ return Div([
239
+ Main({ class: 'main-container', style: 'padding: 1rem 0' }, children),
240
+ Div({ class: 'navigation-container'}, Div([
241
+ Link({ to: '/' }, 'Home'), // Link from router
242
+ Link({ to: { name: 'profile' } }, 'Profile'),
243
+ Link({ to: { name: 'user.show', params: {id: 1} } }, 'Show User'),
244
+ Link({ to: { name: 'admin.dashboard' } }, 'Show Admin Dashboard'),
245
+ Link({ to: { name: 'admin.users' } }, 'Show Admin User'),
246
+ Button('Product Page').nd.on.click(() => {
247
+ const router = Router.get();
248
+ // Navigate programmatically
249
+ router.push('/product/123?name=ProductName');
250
+ }),
251
+ ]))
252
+ ]);
253
+ };
254
+
255
+ const HomePage = () => {
256
+ return DefaultLayout(Div('Home page'));
257
+ };
258
+ const UserPage = ({ params, query }) => {
259
+ return DefaultLayout(Div('User page for '+params.id));
260
+ };
261
+ const ProductPage = ({ params, query }) => {
262
+ return DefaultLayout(Div('Product page '+params.id+' with product name '+query.name));
263
+ };
264
+ const AdminDashboard = () => {
265
+ return DefaultLayout(Div('Admin dashboard'));
266
+ };
267
+ const AdminUsers = () => {
268
+ return DefaultLayout(Div('Admin users'));
269
+ };
270
+ const ProfilePage = () => {
271
+ return DefaultLayout(Div('Profile page'));
272
+ }
273
+
274
+
275
+ const router = Router.create({ mode: "history" }, (router) => {
276
+ // Basic route
277
+ router.add("/", HomePage, { name: 'home' });
278
+
279
+ // Route with parameters
280
+ router.add("/user/{id}", UserPage, { name: "user.show" });
281
+
282
+ // Route with constraints
283
+ router.add("/product/{id:number}", ProductPage);
284
+
285
+ // Grouped routes with middleware
286
+ router.group("/admin", { middlewares: [AuthMiddleware], name: 'admin' }, () => {
287
+ router.add("/dashboard", AdminDashboard, { name: 'dashboard' }); // name = admin.dashboard
288
+ router.add("/users", AdminUsers, { name: 'users' }); // name = admin.users
289
+ });
290
+
291
+ // Named routes
292
+ router.add("/profile", ProfilePage, { name: "profile" });
293
+
294
+ router.add(".*", () => {
295
+ return DefaultLayout(
296
+ Div([
297
+ '404',
298
+ Div('Route not found')
299
+ ])
300
+ );
301
+ })
302
+ }).mount(document.body);
303
+
304
+ ```
305
+
306
+ ## 🏪 State Management
307
+
308
+ ### Global Store
309
+
310
+ ```javascript
311
+ const { Div, P, Button, ShowIf, When } = NativeDocument.elements;
312
+ const { Store, Observable } = NativeDocument;
313
+
314
+ const $ = Observable.computed;
315
+
316
+ // Create global store
317
+ Store.create("user", {
318
+ name: "Anonymous",
319
+ loginAt: '...',
320
+ isLoggedIn: false
321
+ });
322
+
323
+ const Footer = () => {
324
+ const user = Store.use("user");
325
+ const dynamicLoginAtText = $(() => 'Login at '+user.val().loginAt, [user]);
326
+
327
+ return Div({ class: 'footer-container', style: { padding: '1rem 0' }}, [
328
+ ShowIf(user.check(u => u.isLoggedIn), P(dynamicLoginAtText))
329
+ ]);
330
+ };
331
+
332
+ // Use in components
333
+ const Header = () => {
334
+ const user = Store.use("user");
335
+
336
+ return Div([
337
+ When(user.check(u => u.isLoggedIn))
338
+ .show(() => {
339
+ const dynamicHelloText = $(() => 'Hello '+user.val().name, [user]);
340
+
341
+ return Div([
342
+ P(dynamicHelloText),
343
+ Button("Logout").nd.on.click(() => {
344
+ user.set({ name: null, isLoggedIn: false })
345
+ })
346
+ ]);
347
+ })
348
+ .otherwise(() => {
349
+ return Button('Login').nd.on.click(() => {
350
+ user.set({ name: "John", loginAt: (new Date()).toLocaleString(), isLoggedIn: true })
351
+ });
352
+ })
353
+ ]);
354
+ };
355
+
356
+ const App = () => {
357
+ return Div([
358
+ Header(),
359
+ Footer()
360
+ ]);
361
+ }
362
+
363
+ document.body.appendChild(App());
364
+ ```
365
+
366
+
367
+
368
+ ### Local State
369
+
370
+ ```javascript
371
+ const { Div, H2, Button } = NativeDocument.elements;
372
+ const { Observable } = NativeDocument;
373
+
374
+ const Counter = () => {
375
+ const count = Observable(0);
376
+
377
+ const Increment = () => count.set(count.val() + 1);
378
+ const Decrement = () => count.set(currentValue => --currentValue)
379
+
380
+ return Div({}, [
381
+ H2(['Count: ', count]),
382
+ Button("-").nd.on.click(Decrement),
383
+ Button("+").nd.on.click(Increment),
384
+ ]);
385
+ };
386
+
387
+ document.body.appendChild(Counter());
388
+ ```
389
+
390
+
391
+ ## 🔧 Advanced Features
392
+
393
+ ### Lifecycle Hooks
394
+
395
+ ```javascript
396
+ const { Div, Button, ShowIf } = NativeDocument.elements;
397
+ const { Observable } = NativeDocument;
398
+
399
+ const MyComponent = () => {
400
+ const element = Div('Hello my component');
401
+
402
+ return element
403
+ .nd.mounted(() => {
404
+ console.log("Component mounted");
405
+ })
406
+ .nd.unmounted(() => {
407
+ console.log("Component unmounted");
408
+ });
409
+ };
410
+
411
+ const App = () => {
412
+ const isActive = Observable(true);
413
+
414
+ return Div([
415
+ ShowIf(isActive, () => MyComponent()),
416
+ Button('Toggle').nd.on.click(() => isActive.set(!isActive.val()))
417
+ ]);
418
+ }
419
+
420
+ document.body.appendChild(App());
421
+ ```
422
+
423
+
424
+ ### Custom Validation
425
+
426
+ ```javascript
427
+ const { withValidation, ArgTypes } = NativeDocument;
428
+
429
+ const createUser = (name, age, email) => {
430
+ // Function implementation
431
+ return { name, age, email };
432
+ };
433
+
434
+ const createUserWithArgsValidation = withValidation(createUser, [
435
+ ArgTypes.string("name"),
436
+ ArgTypes.number("age"),
437
+ ArgTypes.string("email")
438
+ ]);
439
+
440
+ // Usage
441
+ const user = createUserWithArgsValidation("John", 25, "john@example.com");
442
+ ```
443
+
444
+ ### Custom Validation with Function prototype (args)
445
+
446
+ ```javascript
447
+
448
+ const { withValidation, ArgTypes, Observable } = NativeDocument;
449
+
450
+ const createUser = ((name, age, email) => {
451
+ // Function implementation
452
+ console.log(name, age, email);
453
+ return { name, age, email };
454
+ }).args(ArgTypes.string('name'), ArgTypes.number('age'), ArgTypes.string('email'));
455
+
456
+
457
+ // Usage
458
+ const user = createUser('John', 25, "john@example.com");
459
+
460
+ ```
461
+
462
+
463
+ ## 🎨 Styling
464
+
465
+ NativeDocument works great with CSS:
466
+
467
+ ```javascript
468
+ const { Div, H2, Button, ShowIf } = NativeDocument.elements;
469
+ const { withValidation, ArgTypes, Observable } = NativeDocument;
470
+
471
+ const StyledComponent = () => {
472
+ const isActive = Observable(false);
473
+
474
+ return Div({
475
+ class: {
476
+ "component": true,
477
+ "active": isActive,
478
+ "inactive": isActive.check(active => !active)
479
+ },
480
+ style: {
481
+ color: isActive.check(active => active ? "white" : "black"),
482
+ backgroundColor: isActive.check(active => active ? "#2980b9" : "#7f8c8d"),
483
+ padding: "20px",
484
+ borderRadius: "4px"
485
+ }
486
+ }, [
487
+ H2("Styled Component"),
488
+ ShowIf(isActive, Div({ style: 'color: white; padding: 1rem 0'}, 'Styled Component is active')),
489
+ Button("Toggle").nd.on.click(() => isActive.set((val) => !val))
490
+ ]);
491
+ };
492
+
493
+ // Usage
494
+ document.body.appendChild(StyledComponent());
495
+ ```
@@ -0,0 +1,29 @@
1
+ import terser from '@rollup/plugin-terser';
2
+
3
+ export default [
4
+ {
5
+ input: {
6
+ main: 'index.js'
7
+ },
8
+ output: {
9
+ dir: 'dist',
10
+ entryFileNames: 'native-document.dev.js',
11
+ format: 'iife',
12
+ name: 'NativeDocument'
13
+ },
14
+ },
15
+ {
16
+ input: {
17
+ main: 'index.js'
18
+ },
19
+ output: {
20
+ dir: 'dist',
21
+ entryFileNames: 'native-document.min.js',
22
+ format: 'iife',
23
+ name: 'NativeDocument'
24
+ },
25
+ plugins: [
26
+ terser()
27
+ ]
28
+ }
29
+ ];
package/router.js ADDED
@@ -0,0 +1,9 @@
1
+
2
+ import Router from "./src/router/Router.js";
3
+ import { Link } from './src/router/link.js';
4
+
5
+
6
+ export {
7
+ Router,
8
+ Link
9
+ }
@@ -0,0 +1,60 @@
1
+ import DebugManager from "../utils/debug-manager";
2
+
3
+
4
+ const MemoryManager = (function() {
5
+
6
+ let $nexObserverId = 0;
7
+ const $observables = new Map();
8
+ const $registry = new FinalizationRegistry((heldValue) => {
9
+ DebugManager.log('MemoryManager', '🧹 Auto-cleanup observable:', heldValue);
10
+ heldValue.listeners.splice(0);
11
+ })
12
+
13
+ return {
14
+ /**
15
+ * Register an observable and return an id.
16
+ *
17
+ * @param {ObservableItem} observable
18
+ * @param {Function[]} listeners
19
+ * @returns {number}
20
+ */
21
+ register(observable, listeners) {
22
+ const id = ++$nexObserverId;
23
+ const heldValue = {
24
+ id: id,
25
+ listeners
26
+ };
27
+ $registry.register(observable, heldValue);
28
+ $observables.set(id, new WeakRef(observable));
29
+ return id;
30
+ },
31
+ cleanup() {
32
+ for (const [_, weakObservableRef] of $observables) {
33
+ const observable = weakObservableRef.deref();
34
+ if (observable) {
35
+ observable.cleanup();
36
+ }
37
+ }
38
+ $observables.clear();
39
+ },
40
+ /**
41
+ * Clean observables that are not referenced anymore.
42
+ * @param {number} threshold
43
+ */
44
+ cleanObservables(threshold) {
45
+ if($observables.size < threshold) return;
46
+ let cleanedCount = 0;
47
+ for (const [id, weakObservableRef] of $observables) {
48
+ if (!weakObservableRef.deref()) {
49
+ $observables.delete(id);
50
+ cleanedCount++;
51
+ }
52
+ }
53
+ if (cleanedCount > 0) {
54
+ DebugManager.log('Memory Auto Clean', `🧹 Cleaned ${cleanedCount} orphaned observables`);
55
+ }
56
+ }
57
+ };
58
+ }());
59
+
60
+ export default MemoryManager;