@moonitoring/nidamjs 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.
@@ -0,0 +1 @@
1
+ (function(f,y){typeof exports=="object"&&typeof module<"u"?y(exports):typeof define=="function"&&define.amd?define(["exports"],y):(f=typeof globalThis<"u"?globalThis:f||self,y(f.Nidam={}))})(this,(function(f){"use strict";class y{static#t=new WeakSet;static initialize(t,e=document,s=null,i=[]){!Array.isArray(i)||i.length===0||i.forEach(({selector:o,init:n,name:a})=>{if(!o||typeof n!="function")return;e.querySelectorAll(o).forEach(l=>{if(!this.#t.has(l))try{const c=n(l,t,s);this.#t.add(l),a&&c&&s&&typeof s.set=="function"&&s.set(a,c)}catch(c){console.warn(`Failed to initialize ${o}:`,c)}})})}}class b{#t=new Map;#e;#s=new Map;constructor(t=document){this.#e=t,this.#i()}#i(){["click","input","change","focusin","focusout","keydown","mousedown","desktop:toggle-matrix","desktop:theme-changed"].forEach(e=>{const s=i=>this.#o(e,i);this.#e.addEventListener(e,s),this.#s.set(e,s)})}#o(t,e){const s=this.#t.get(t);if(!(!s||s.length===0))for(const{selector:i,handler:o}of s){if(!i){o.call(this.#e,e,this.#e);continue}const n=e.target.closest(i);n&&o.call(n,e,n)}}on(t,e,s,i={}){const o=i.group;this.#t.has(t)||this.#t.set(t,[]);const n={selector:e,handler:s,group:o};return this.#t.get(t).push(n),n}off(t,e){const s=this.#t.get(t);if(!s)return;const i=s.indexOf(e);i!==-1&&s.splice(i,1),s.length||this.#t.delete(t)}destroy(){for(const[t,e]of this.#s)this.#e.removeEventListener(t,e);this.#s.clear(),this.#t.clear()}}const R={moveCursorToEnd(r){const t=document.createRange();t.selectNodeContents(r),t.collapse(!1);const e=window.getSelection();e.removeAllRanges(),e.addRange(t)},onEscape(r){const t=e=>{e.key==="Escape"&&(r(),document.removeEventListener("keydown",t))};return document.addEventListener("keydown",t),t},hide(...r){r.forEach(t=>t?.classList.add("hidden"))},show(...r){r.forEach(t=>t?.classList.remove("hidden"))},toggle(...r){r.forEach(t=>t?.classList.toggle("hidden"))}};class W{_root;_elements={};_state={};_utils=R;_delegator=null;constructor(t,e){if(this._root=typeof t=="string"?document.querySelector(t):t,this._delegator=e,!this._root)throw new Error(`Root element not found: ${t}`);this._initialize()}_initialize(){this._elements=this._cacheElements(),this._bindEvents()}_cacheElements(){return{}}_bindEvents(){}_query(t){return this._root.querySelector(t)}_queryAll(t){return this._root.querySelectorAll(t)}_on(t,e,s){this._delegator.on(t,e,(i,o)=>{this._root.contains(o)&&s.call(this,i,o)})}}const T=new WeakMap;function p(r){return!r||r==="auto"||r==="normal"?"":r}function S(r){return Number.isFinite(r)?`${Math.round(r)}px`:""}function C(r,t={}){const e=t.includePosition===!0;let s=null;const i=()=>(s||(s=window.getComputedStyle(r)),s),o=(r.offsetWidth>0?S(r.offsetWidth):"")||p(r.style.width)||p(i().width),n=(r.offsetHeight>0?S(r.offsetHeight):"")||p(r.style.height)||p(i().height);let a="",d="";return e&&(a=S(r.offsetLeft)||p(r.style.left)||p(i().left),d=S(r.offsetTop)||p(r.style.top)||p(i().top)),{width:o||"",height:n||"",left:a,top:d}}function w(r){let t=T.get(r);return t||(t=new Map,T.set(r,t)),t}function D(r,t="prevState",e={}){const s=C(r,e),i=JSON.stringify(s);return r.dataset[t]=i,w(r).set(t,{raw:i,parsed:s}),s}function v(r,t="prevState"){const e=r.dataset[t];if(!e)return null;const s=w(r).get(t);if(s&&s.raw===e)return s.parsed;try{const i=JSON.parse(e);if(!i||typeof i!="object")return w(r).set(t,{raw:e,parsed:null}),null;const o={width:p(i.width),height:p(i.height),left:p(i.left),top:p(i.top)};return w(r).set(t,{raw:e,parsed:o}),o}catch{return w(r).set(t,{raw:e,parsed:null}),null}}function L(r,t,e={}){return!t||typeof t!="object"?!1:(t.width&&r.style.width!==t.width&&(r.style.width=t.width),t.height&&r.style.height!==t.height&&(r.style.height=t.height),e.includePosition&&(t.left&&r.style.left!==t.left&&(r.style.left=t.left),t.top&&r.style.top!==t.top&&(r.style.top=t.top)),!0)}class I extends W{_config={zIndexBase:40,layoutStabilizationMs:450,cascadeOffset:30,cooldownMs:500,maxWindows:10,snapGap:6,taskbarHeight:64,snapThreshold:30,dragThreshold:10,resizeDebounceMs:6,animationDurationMs:400,defaultWidth:800,defaultHeight:600,minMargin:10,edgeDetectionRatio:.4,scrollRestoreTimeoutMs:2e3};_windows=new Map;_zIndexCounter=this._config.zIndexBase;_getModules=null;_notify=null;_fetchWindowContent=null;_initializeContent=null;_resolveEndpoint=null;_lastOpenTimestamps=new Map;_pendingRequests=new Map;_snapIndicator=null;constructor(t,e,s={}){super(t,e);const{getModules:i=null,config:o=null,notify:n=null,fetchWindowContent:a=null,initializeContent:d=null,resolveEndpoint:l=null}=s||{};this._getModules=i,this._notify=n||this._defaultNotify.bind(this),this._fetchWindowContent=a||this._defaultFetchWindowContent.bind(this),this._initializeContent=d||(()=>{}),this._resolveEndpoint=l||this._defaultResolveEndpoint,o&&typeof o=="object"&&(this._config={...this._config,...o}),this._zIndexCounter=this._config.zIndexBase,this._initSnapIndicator()}_initSnapIndicator(){this._snapIndicator=document.createElement("div"),this._snapIndicator.className="snap-indicator",document.body.appendChild(this._snapIndicator)}_cacheElements(){return{}}_bindEvents(){this._delegator.on("click","[data-modal]",this._handleModalTrigger.bind(this)),this._delegator.on("click","[data-maximize]",this._handleMaximizeTrigger.bind(this)),this._delegator.on("click","[data-close]",this._handleCloseTrigger.bind(this)),this._delegator.on("mousedown",".window",this._handleWindowFocus.bind(this)),this._delegator.on("mousedown","[data-bar]",this._handleWindowDragStart.bind(this)),document.addEventListener("keydown",this._handleGlobalKeydown.bind(this));let t;window.addEventListener("resize",()=>{clearTimeout(t),t=setTimeout(()=>this._handleResize(),this._config.resizeDebounceMs)})}_handleModalTrigger(t,e){t.preventDefault(),this.open(e.dataset.modal).catch(s=>{console.debug("Modal trigger failed:",s)})}_handleCloseTrigger(t,e){t.preventDefault();const s=e.closest(".window");s&&this.close(s)}_handleWindowFocus(t,e){if(t.target.closest("[data-close]")||t.target.closest("[data-modal]"))return;const s=e.closest(".window");s&&this._focusWindow(s)}_handleMaximizeTrigger(t,e){t.preventDefault();const s=e.closest(".window");s&&this.toggleMaximize(s)}_handleWindowDragStart(t,e){if(t.target.closest("[data-close]")||t.target.closest("[data-maximize]"))return;t.preventDefault();const s=e.closest(".window");s&&(this._focusWindow(s),this.drag(t,s))}_handleGlobalKeydown(t){t.key==="Escape"&&!t.repeat&&this._closeTopmostWindow()}toggleMaximize(t){const e=t.classList.contains("maximized"),s=t.classList.contains("tiled")&&typeof t.dataset.snapType=="string"&&t.dataset.snapType.length>0;t.classList.add("window-toggling"),!e&&!t.classList.contains("tiled")&&this._ensureRestoreState(t);const i=t.classList.toggle("maximized");let o=!1;if(this._updateMaximizeIcon(t,i),!i)if(s){const n=this._getSnapLayout(t.dataset.snapType,window.innerWidth,window.innerHeight-this._config.taskbarHeight);Object.assign(t.style,n)}else{const n=v(t);L(t,n);const a=this._parseCssPixelValue(n?.width)||this._parseCssPixelValue(t.style.width)||t.offsetWidth,d=this._parseCssPixelValue(n?.height)||this._parseCssPixelValue(t.style.height)||t.offsetHeight;this._repositionWindowFromRatios(t,window.innerWidth,window.innerHeight,{widthPx:a,heightPx:d}),o=!0}setTimeout(()=>{t.classList.remove("window-toggling"),o&&this._savePositionRatios(t)},this._config.animationDurationMs)}async open(t,e=!1,s=null,i=!0){if(this._windows.size>=this._config.maxWindows&&!this._windows.has(t)){const a=document.body.dataset.errorMaxWindows||`Maximum of ${this._config.maxWindows} windows allowed.`;return this._notify("error",a.replace("%s",String(this._config.maxWindows))),Promise.reject(new Error("Max windows reached"))}if(this._windows.has(t)&&!e){const a=this._windows.get(t);return i&&this._focusWindow(a),Promise.resolve(a)}if(this._pendingRequests.has(t))return this._pendingRequests.get(t);const o=Date.now();if(!e&&o-(this._lastOpenTimestamps.get(t)||0)<this._config.cooldownMs)return Promise.resolve();this._lastOpenTimestamps.set(t,o);const n=(async()=>{try{const a=await this._fetchWindowContent(t,{force:e,focusSelector:s,activate:i,manager:this});if(typeof a!="string")throw new TypeError("fetchWindowContent must return an HTML string");if(this._windows.has(t)&&e){const l=this._windows.get(t);return!i&&this._isWindowBusy(l)||(this._refreshWindowContent(l,a),i&&this._focusWindow(l),s&&this._handleFocusSelector(l,s)),l}const d=this._createWindowElement(a,t);if(!d){console.warn(`No .window element found for ${t}`);return}return this._setupNewWindow(d,t,s,i),d}catch(a){console.error("Error opening window:",a);const d=document.body.dataset.errorOpenFailed||"Failed to open window.";throw this._notify("error",d),a}finally{this._pendingRequests.delete(t)}})();return this._pendingRequests.set(t,n),n}close(t){const e=t.dataset.endpoint;this._windows.get(e)===t&&this._windows.delete(e),t.classList.add("animate-disappearance"),t.classList.remove("animate-appearance"),t.addEventListener("animationend",()=>{t.isConnected&&t.remove()},{once:!0})}_focusWindow(t){this._zIndexCounter++,t.style.zIndex=this._zIndexCounter,t.classList.add("focused"),this._windows.forEach(e=>{e!==t&&e.classList.remove("focused")})}_isWindowBusy(t){return t.dataset.isBusy==="true"?!0:t.querySelector('[data-is-busy="true"]')!==null}_refreshWindowContent(t,e){const o=new DOMParser().parseFromString(e,"text/html").querySelector(".window");if(!o)return;const n=t.dataset.snapType,a=t.dataset.prevState,d=t.dataset.xRatio,l=t.dataset.yRatio,c=t.classList.contains("focused"),h=t.classList.contains("maximized"),g=t.classList.contains("tiled"),u=this._captureScrollState(t);t.innerHTML=o.innerHTML,t.className=o.className,n&&(t.dataset.snapType=n),a&&(t.dataset.prevState=a),d&&(t.dataset.xRatio=d),l&&(t.dataset.yRatio=l),c&&t.classList.add("focused"),g&&t.classList.add("tiled"),h&&(t.classList.add("maximized"),this._updateMaximizeIcon(t,!0)),!g&&!h&&(o.style.width&&(t.style.width=o.style.width),o.style.height&&(t.style.height=o.style.height)),t.style.margin="0",t.style.transform="none",this._restoreScrollState(t,u),this._initializeModalContent(t)}_updateMaximizeIcon(t,e){const s=t.querySelector("[data-maximize] i");s&&(s.classList.toggle("fa-expand",!e),s.classList.toggle("fa-compress",e))}_createWindowElement(t,e){const o=new DOMParser().parseFromString(t,"text/html").querySelector(".window");return o&&(o.dataset.endpoint=e),o}_setupNewWindow(t,e,s,i=!0){Object.assign(t.style,{position:"absolute",pointerEvents:"auto",margin:"0",transform:"none",visibility:"hidden"}),this._root.appendChild(t);const o=this._windows.size,n=t.dataset.defaultSnap;if(n){const a=window.innerWidth,d=window.innerHeight-this._config.taskbarHeight;this._snapWindow(t,n,a,d)}else this._positionWindow(t,o),this._ensureRestoreState(t);this._windows.set(e,t),this._initializeModalContent(t),i&&this._focusWindow(t),t.style.visibility="",n||this._stabilizeInitialPlacement(t,o),s&&this._handleFocusSelector(t,s)}_positionWindow(t,e=null){const s=t.offsetWidth||parseInt(t.style.width)||this._config.defaultWidth,i=t.offsetHeight||parseInt(t.style.height)||this._config.defaultHeight,o=window.innerWidth,n=window.innerHeight,a=Number.isFinite(e)&&e>=0?e:this._windows.size,d=a*this._config.cascadeOffset,l=a*this._config.cascadeOffset;let c=(o-s)/2+d,h=(n-i)/2+l;const g=this._config.minMargin;c+s>o&&(c=Math.max(g,o-s-g)),h+i>n&&(h=Math.max(g,n-i-g)),t.style.left=`${Math.round(c)}px`,t.style.top=`${Math.round(h)}px`,this._savePositionRatios(t)}_stabilizeInitialPlacement(t,e){if(!t?.isConnected)return;const s=Number.isFinite(this._config.layoutStabilizationMs)&&this._config.layoutStabilizationMs>0?this._config.layoutStabilizationMs:450,i=typeof performance<"u"?()=>performance.now():()=>Date.now(),o=i();let n=!0,a=null,d=t.offsetWidth,l=t.offsetHeight;const c=()=>{n&&(n=!1,a&&(a.disconnect(),a=null))},h=()=>{if(!n||!t.isConnected){c();return}if(t.classList.contains("tiled")||t.classList.contains("maximized"))return;const u=t.offsetWidth,m=t.offsetHeight;(u!==d||m!==l)&&(d=u,l=m,this._positionWindow(t,e))},g=()=>{if(n){if(h(),i()-o<s){requestAnimationFrame(g);return}c()}};requestAnimationFrame(g),typeof ResizeObserver=="function"&&(a=new ResizeObserver(()=>h()),a.observe(t)),setTimeout(()=>c(),s)}_savePositionRatios(t){if(t.classList.contains("tiled")||t.classList.contains("maximized"))return;const e=t.offsetLeft+t.offsetWidth/2,s=t.offsetTop+t.offsetHeight/2;t.dataset.xRatio=String(e/window.innerWidth),t.dataset.yRatio=String(s/window.innerHeight)}_ensureRestoreState(t){const e=v(t);return e?.width&&e?.height?e:D(t,"prevState",{includePosition:!1})}_parseCssPixelValue(t){if(!t)return null;const e=parseFloat(t);return Number.isFinite(e)?e:null}_repositionWindowFromRatios(t,e,s,i=null){const o=parseFloat(t.dataset.xRatio),n=parseFloat(t.dataset.yRatio);if(isNaN(o)||isNaN(n))return!1;const a=(i&&Number.isFinite(i.widthPx)&&i.widthPx>0?i.widthPx:null)||t.offsetWidth,d=(i&&Number.isFinite(i.heightPx)&&i.heightPx>0?i.heightPx:null)||t.offsetHeight,l=o*e,c=n*s;return t.style.left=`${Math.round(l-a/2)}px`,t.style.top=`${Math.round(c-d/2)}px`,!0}_handleFocusSelector(t,e){const s=t.querySelector(e);s&&((s.type==="radio"||s.type==="checkbox")&&(s.checked=!0),s.focus())}_closeTopmostWindow(){let t=null,e=0;this._windows.forEach(s=>{if(s.classList.contains("animate-disappearance"))return;const i=parseInt(s.style.zIndex||0,10);i>e&&(e=i,t=s)}),t&&this.close(t)}drag(t,e){this._dragState?.active||(this._dragState={active:!0,winElement:e,startX:t.clientX,startY:t.clientY,currentX:t.clientX,currentY:t.clientY,startWinLeft:e.offsetLeft,startWinTop:e.offsetTop,isRestored:!1,restoreXRatio:null,initialState:{tiled:e.classList.contains("tiled"),maximized:e.classList.contains("maximized")},view:{w:window.innerWidth,h:window.innerHeight-this._config.taskbarHeight},snap:null,inhibitSnap:!1,isDragging:!1},this._dragHandlers={move:s=>{this._dragState.currentX=s.clientX,this._dragState.currentY=s.clientY},stop:()=>this._handleDragStop()},document.addEventListener("mousemove",this._dragHandlers.move,{passive:!0}),document.addEventListener("mouseup",this._dragHandlers.stop),requestAnimationFrame(()=>this._dragLoop()))}_dragLoop(){this._dragState?.active&&(this._updateDragPosition(),requestAnimationFrame(()=>this._dragLoop()))}_updateDragPosition(){const t=this._dragState,{winElement:e,currentX:s,currentY:i,startX:o,startY:n}=t,a=s-o,d=i-n;if(!t.isDragging&&(Math.abs(a)>this._config.dragThreshold||Math.abs(d)>this._config.dragThreshold)&&(t.isDragging=!0),!t.isDragging)return;(t.initialState.tiled||t.initialState.maximized)&&!t.isRestored&&t.isDragging&&(t.initialState.maximized?(t.restoreXRatio=o/window.innerWidth,this._restoreWindowInternal(e,t.restoreXRatio),t.startWinTop=0):(t.restoreXRatio=(o-e.offsetLeft)/e.offsetWidth,this._restoreWindowInternal(e,null),t.startWinTop=e.offsetTop),t.startX=s,t.startY=i,t.isRestored=!0);let l,c;if(t.isRestored&&t.restoreXRatio!==null){const h=e.offsetWidth;l=s-t.restoreXRatio*h,c=Math.max(0,t.startWinTop+(i-n))}else l=t.startWinLeft+(t.isRestored?s-t.startX:a),c=Math.max(0,t.startWinTop+(t.isRestored?i-t.startY:d));e.style.left=`${l}px`,e.style.top=`${c}px`,(t.isRestored||!t.initialState.tiled&&!t.initialState.maximized)&&(e.classList.contains("tiled")&&e.classList.remove("tiled"),e.classList.contains("maximized")&&(e.classList.remove("maximized"),this._updateMaximizeIcon(e,!1))),t.isDragging&&this._detectSnapZone(s,i)}_detectSnapZone(t,e){const{view:s}=this._dragState,i=this._config.snapThreshold,o=s.w*this._config.edgeDetectionRatio,n=s.h*this._config.edgeDetectionRatio;let a=null;e<i?t<o?a="tl":t>s.w-o?a="tr":a="maximize":t<i?e<n?a="tl":e>s.h-n?a="bl":a="left":t>s.w-i?e<n?a="tr":e>s.h-n?a="br":a="right":e>s.h-i&&(a=t<s.w/2?"bl":"br"),this._dragState.snap!==a&&(this._dragState.snap=a,this._updateSnapIndicator(a,s.w,s.h))}_handleDragStop(){if(!this._dragState?.active)return;const{winElement:t,snap:e,view:s}=this._dragState;document.removeEventListener("mousemove",this._dragHandlers.move),document.removeEventListener("mouseup",this._dragHandlers.stop),this._snapIndicator.classList.remove("visible"),e?e==="maximize"?this.toggleMaximize(t):this._snapWindow(t,e,s.w,s.h):this._savePositionRatios(t),this._dragState.active=!1,this._dragState=null,this._dragHandlers=null}_restoreWindowInternal(t,e){let s,i;const o=v(t);e===null?o&&(s=o.width,i=o.height):(s=o?.width||t.style.width,i=o?.height||t.style.height),(!s||s==="100%")&&(s=this._config.defaultWidth+"px"),(!i||i==="100%")&&(i=this._config.defaultHeight+"px"),t.classList.remove("maximized","tiled"),this._updateMaximizeIcon(t,!1),t.classList.add("window-toggling","dragging-restore"),L(t,{width:s,height:i}),setTimeout(()=>{t.classList.remove("window-toggling","dragging-restore"),this._savePositionRatios(t)},this._config.animationDurationMs)}_updateSnapIndicator(t,e,s){if(!t){this._snapIndicator.classList.remove("visible");return}let i;t==="maximize"?i={top:"0px",left:"0px",width:`${e}px`,height:`${s}px`}:i=this._getSnapLayout(t,e,s),Object.assign(this._snapIndicator.style,i),this._snapIndicator.classList.add("visible")}_snapWindow(t,e,s,i){t.classList.contains("tiled")||this._ensureRestoreState(t),t.classList.add("window-toggling","tiled"),t.dataset.snapType=e;const o=this._getSnapLayout(e,s,i);Object.assign(t.style,o),setTimeout(()=>t.classList.remove("window-toggling"),this._config.animationDurationMs)}_getSnapLayout(t,e,s){const i=this._config.snapGap,o=(e-i*3)/2,n=(s-i*3)/2,a=s-i*2,d=i,l=o+i*2,c=i,h=n+i*2,u={tl:{top:c,left:d,width:o,height:n},tr:{top:c,left:l,width:o,height:n},bl:{top:h,left:d,width:o,height:n},br:{top:h,left:l,width:o,height:n},left:{top:c,left:d,width:o,height:a},right:{top:c,left:l,width:o,height:a}}[t];return{width:`${u.width}px`,height:`${u.height}px`,top:`${u.top}px`,left:`${u.left}px`}}_handleResize(){const t=window.innerWidth,e=window.innerHeight,s=e-this._config.taskbarHeight;this._windows.forEach(i=>{if(i.classList.contains("tiled")&&i.dataset.snapType){const o=i.dataset.snapType,n=this._getSnapLayout(o,t,s);Object.assign(i.style,n)}else i.classList.contains("maximized")||this._repositionWindowFromRatios(i,t,e)})}_captureScrollState(t){const e=new Map;return(t.scrollTop>0||t.scrollLeft>0)&&e.set("root",{top:t.scrollTop,left:t.scrollLeft}),t.querySelectorAll("*").forEach(s=>{(s.scrollTop>0||s.scrollLeft>0)&&e.set(this._getElementPath(t,s),{top:s.scrollTop,left:s.scrollLeft})}),e}_restoreScrollState(t,e){const s=()=>{e.forEach((n,a)=>{let d;if(a==="root")d=t;else try{d=t.querySelector(`:scope > ${a}`)}catch{d=t.querySelector(a)}d&&(d.scrollTop=n.top,d.scrollLeft=n.left)})};s();const i=t.querySelector(".window-content-scrollable > div")||t,o=new ResizeObserver(()=>s());o.observe(i),setTimeout(()=>o.disconnect(),this._config.scrollRestoreTimeoutMs)}_getElementPath(t,e){let s=[],i=e;for(;i&&i!==t;){let o=Array.prototype.indexOf.call(i.parentNode.children,i);s.unshift(`${i.tagName}:nth-child(${o+1})`),i=i.parentNode}return s.join(" > ")}async _defaultFetchWindowContent(t){return(await fetch(this._resolveEndpoint(t),{headers:{"X-Modal-Request":"1"},cache:"no-cache"})).text()}_defaultResolveEndpoint(t){return`/${String(t||"").replace(/^\/+/,"")}`}_defaultNotify(t,e){(t==="error"?console.error:console.log)(`[nidamjs:${t}]`,e)}_initializeModalContent(t){const e=this._getModules?this._getModules():null;this._initializeContent(t,{delegator:this._delegator,modules:e,manager:this})}}class H{#t;#e;#s=200;constructor(t,{refreshMap:e=null,refreshTimeout:s=200}={}){this.#t=t,this.#e=e||window.window_refresh_map||{},this.#s=s}setRefreshMap(t={}){this.#e=t||{}}handleEvent(t,e){const s=this.#e[t],[i,o]=t.split(":"),n=o==="deleted",a=e?.id||null;this.#t&&Array.from(this.#t._windows.entries()).forEach(([d,l])=>{const c=d.startsWith("/")?d.slice(1):d;if(n&&a&&l.dataset.dependsOn&&l.dataset.dependsOn.split("|").some(u=>{const[m,x]=u.split(":");return i===m&&String(x)===String(a)})){setTimeout(()=>{this.#t.close(l)},this.#s);return}s&&s.forEach(h=>{this.#i(h,c,n?null:a)&&setTimeout(()=>{this.#t.open(d,!0,null,!1)},this.#s)})})}#i(t,e,s=null){if(t===e)return!0;const i=t.split("/"),o=e.split("/");return!(i[i.length-1]==="*")&&i.length!==o.length?!1:i.every((a,d)=>a==="*"?!0:a.startsWith("{")&&a.endsWith("}")?s!==null?String(o[d])===String(s):!0:a===o[d])}}const $=(r,t)=>{(r==="error"?console.error:console.log)(`[nidamjs:${r}]`,t)};class P{#t;#e=new Map;#s=null;constructor(t={}){this.#t={root:document,modalContainer:"#target",pendingModalDatasetKey:"pendingModal",registry:[],refreshMap:null,refreshTimeout:200,notify:$,windowManager:{},...t}}initialize(){return this.#i(),this.#o(),this.#r(),this}getModule(t){return this.#e.get(t)}getModules(){return this.#e}#i(){this.#s=new b(this.#t.root),this.#e.set("delegator",this.#s)}#o(){const t=this.#t.root.querySelector(this.#t.modalContainer);if(!t)return;const e=new I(t,this.#s,{getModules:()=>this.#e,initializeContent:(i,o)=>y.initialize(o.delegator,i,o.modules,this.#t.registry),notify:this.#t.notify,...this.#t.windowManager||{}});this.#e.set("window",e),this.#a(t,e);const s=new H(e,{refreshMap:this.#t.refreshMap,refreshTimeout:this.#t.refreshTimeout});this.#e.set("refresher",s)}#a(t,e){const s=this.#t.pendingModalDatasetKey,i=(t?.dataset?.[s]||"").trim();if(!i)return;const o=i.startsWith("/")?i.slice(1):i;e.open(o)}#r(){y.initialize(this.#s,this.#t.root,this.#e,this.#t.registry)}}const N=(r={})=>new P(r),M={set(r,t){try{const e=JSON.stringify(t);localStorage.setItem(r,e)}catch(e){console.error(`Error saving to localStorage (key: ${r}):`,e)}},get(r,t=null){try{const e=localStorage.getItem(r);return e===null?t:JSON.parse(e)}catch(e){return console.error(`Error reading from localStorage (key: ${r}):`,e),t}},remove(r){try{localStorage.removeItem(r)}catch(t){console.error(`Error removing from localStorage (key: ${r}):`,t)}},clear(){try{localStorage.clear()}catch(r){console.error("Error clearing localStorage:",r)}},has(r){try{return localStorage.getItem(r)!==null}catch{return!1}}};class A extends W{_dragState=null;_storageKey="desktop_grid_layout";_storageNamespace="";_storage=M;constructor(t,e,s={}){super(t,e),this._storageKey=s.storageKey||this._storageKey,this._storageNamespace=s.storageNamespace||"",this._storage=s.storage||M,this._loadLayout()}_getStorageKey(){return this._storageNamespace?`${this._storageNamespace}_${this._storageKey}`:this._storageKey}_loadLayout(){try{const t=this._getStorageKey(),e=this._storage.get(t,[]);if(!e||!Array.isArray(e))return;this._root.querySelectorAll(".desktop-icon").forEach(i=>{const o=i.dataset.modal,n=e.find(a=>a.id===o);if(o&&n&&Array.isArray(n.classes)){const a=Array.from(i.classList).filter(d=>d.startsWith("col-start-")||d.startsWith("row-start-")||d.startsWith("col-end-"));i.classList.remove(...a),i.classList.add(...n.classes)}})}catch(t){console.error("Failed to load desktop layout",t)}}_bindEvents(){this._delegator.on("mousedown",".desktop-icon",this._handleDragStart.bind(this))}_handleDragStart(t,e){if(t.button!==0)return;t.preventDefault();const s=e.closest(".desktop-icon"),i=s.getBoundingClientRect(),o=this._root.getBoundingClientRect(),n=Array.from(s.classList).filter(a=>a.startsWith("col-start-")||a.startsWith("row-start-")||a.startsWith("col-end-"));this._dragState={element:s,startX:t.clientX,startY:t.clientY,initialLeft:i.left-o.left,initialTop:i.top-o.top,containerRect:o,originalClasses:n},s.style.left=`${this._dragState.initialLeft}px`,s.style.top=`${this._dragState.initialTop}px`,s.style.width=`${i.width}px`,s.style.position="absolute",s.classList.remove(...n),s.classList.add("dragging"),this._dragHandlers={move:this._handleDragMove.bind(this),stop:this._handleDragStop.bind(this)},document.addEventListener("mousemove",this._dragHandlers.move),document.addEventListener("mouseup",this._dragHandlers.stop)}_handleDragMove(t){if(!this._dragState)return;const{element:e,startX:s,startY:i,initialLeft:o,initialTop:n,containerRect:a}=this._dragState,d=t.clientX-s,l=t.clientY-i;let c=o+d,h=n+l;const g=a.width-e.offsetWidth,u=a.height-e.offsetHeight;c=Math.max(0,Math.min(c,g)),h=Math.max(0,Math.min(h,u)),e.style.left=`${c}px`,e.style.top=`${h}px`}_handleDragStop(t){if(!this._dragState)return;const{element:e,startX:s,startY:i,originalClasses:o}=this._dragState,n=Math.abs(t.clientX-s),a=Math.abs(t.clientY-i),d=n>5||a>5;if(e.classList.remove("dragging"),e.style.zIndex="",d){const l=c=>{c.preventDefault(),c.stopPropagation()};e.addEventListener("click",l,{capture:!0,once:!0}),this._snapToGrid(e)}else e.style.position="",e.style.left="",e.style.top="",e.style.width="",e.classList.add(...o);this._saveLayout&&this._saveLayout(),document.removeEventListener("mousemove",this._dragHandlers.move),document.removeEventListener("mouseup",this._dragHandlers.stop),this._dragState=null}_snapToGrid(t){const e=this._root.getBoundingClientRect(),s=t.getBoundingClientRect();let i=2;window.innerWidth>=1280?i=10:window.innerWidth>=1024?i=8:window.innerWidth>=768&&(i=6);const o=3,n=e.width/i,a=e.height/o,d=s.left-e.left+s.width/2,l=s.top-e.top+s.height/2;let c=Math.floor(d/n)+1,h=Math.floor(l/a)+1;c=Math.max(1,Math.min(c,i)),h=Math.max(1,Math.min(h,o));const g=`col-start-${c}`,u=`row-start-${h}`;if(Array.from(this._root.querySelectorAll(".desktop-icon")).find(_=>_!==t&&_.classList.contains(g)&&_.classList.contains(u))){if(t.style.position="",t.style.left="",t.style.top="",t.style.width="",this._dragState&&this._dragState.originalClasses){const _=Array.from(t.classList).filter(z=>z.startsWith("col-start-")||z.startsWith("row-start-")||z.startsWith("col-end-"));t.classList.remove(..._),t.classList.add(...this._dragState.originalClasses)}return}t.style.position="",t.style.left="",t.style.top="",t.style.width="";const x=Array.from(t.classList).filter(_=>_.startsWith("col-start-")||_.startsWith("row-start-")||_.startsWith("col-end-"));t.classList.remove(...x),t.classList.add(g,u)}_saveLayout(){const t=[];this._root.querySelectorAll(".desktop-icon").forEach(i=>{const o=i.dataset.modal;if(o){const n=Array.from(i.classList).filter(a=>a.startsWith("col-start-")||a.startsWith("row-start-"));t.push({id:o,classes:n})}});const s=this._getStorageKey();this._storage.set(s,t)}}function X(r,t){if(!r||!t||t.type!=="success"||!t.emit)return;let e=t.emit,s=t.id||null;const i=e.split(":");i.length>=3&&(s=i.pop(),e=i.join(":")),r.handleEvent(e,{...t,emit:e,id:s})}N().initialize(),f.BaseManager=W,f.ContentInitializer=y,f.DOMUtils=R,f.DesktopIconManager=A,f.EventDelegator=b,f.NidamApp=P,f.WindowManager=I,f.WindowRefresher=H,f.applyWindowState=L,f.captureWindowState=C,f.createNidamApp=N,f.handleRefreshEvent=X,f.readWindowState=v,f.saveWindowState=D,f.storageUtil=M,Object.defineProperty(f,Symbol.toStringTag,{value:"Module"})}));
package/package.json ADDED
@@ -0,0 +1,46 @@
1
+ {
2
+ "name": "@moonitoring/nidamjs",
3
+ "version": "1.0.0",
4
+ "type": "module",
5
+ "main": "./dist/nidam.cjs",
6
+ "module": "./dist/nidam.es.js",
7
+ "style": "./dist/nidam.css",
8
+ "exports": {
9
+ ".": {
10
+ "import": "./dist/nidam.es.js",
11
+ "require": "./dist/nidam.cjs"
12
+ },
13
+ "./cjs": "./dist/nidam.cjs",
14
+ "./umd": "./dist/nidam.umd.js",
15
+ "./nidam.css": "./dist/nidam.css"
16
+ },
17
+ "files": [
18
+ "dist",
19
+ "readme.md"
20
+ ],
21
+ "publishConfig": {
22
+ "access": "public",
23
+ "tag": "beta"
24
+ },
25
+ "scripts": {
26
+ "imports": "bun -e \"await import('./src/index.js')\"",
27
+ "test": "vitest run --coverage",
28
+ "test:watch": "vitest --coverage",
29
+ "lint": "tsc --noEmit",
30
+ "format": "prettier -w .",
31
+ "quality": "bun run imports && bun run lint && bun run test && bun run format",
32
+ "build": "vite build --mode lib && vite build --mode css",
33
+ "csr": "bun run build && vite --open /examples/csr/index.html",
34
+ "ssr": "bun run build && bun examples/ssr/main.js"
35
+ },
36
+ "devDependencies": {
37
+ "@types/node": "^24.10.13",
38
+ "@vitest/coverage-v8": "3.2.4",
39
+ "express": "^5.2.1",
40
+ "jsdom": "^27.4.0",
41
+ "prettier": "^3.8.1",
42
+ "typescript": "^5.9.3",
43
+ "vite": "^7.3.1",
44
+ "vitest": "^3.2.4"
45
+ }
46
+ }
package/readme.md ADDED
@@ -0,0 +1,175 @@
1
+ # NidamJS
2
+
3
+ NidamJS is a framework-agnostic JavaScript library for desktop-like window components in web applications.
4
+
5
+ ## Official API
6
+
7
+ Use the package root export for manual initialization:
8
+
9
+ ```js
10
+ import { createNidamApp, WindowManager, WindowRefresher } from "nidamjs";
11
+ ```
12
+
13
+ Internal paths (`src/*`) are implementation details and not public API.
14
+
15
+ ## Bundle Strategy
16
+
17
+ - `nidamjs` (`dist/nidam.es.js`, `dist/nidam.umd.js`): core bundle.
18
+ - Initialization is explicit: call `createNidamApp(...).initialize()`.
19
+
20
+ ## Installation
21
+
22
+ ```bash
23
+ bun install
24
+ ```
25
+
26
+ ## Quick Start
27
+
28
+ ```js
29
+ import { createNidamApp } from "nidamjs";
30
+
31
+ const app = createNidamApp({
32
+ modalContainer: "#target",
33
+ registry: [],
34
+ windowManager: {
35
+ config: {
36
+ layoutStabilizationMs: 450,
37
+ },
38
+ notify: (level, message) => console.log(level, message),
39
+ },
40
+ });
41
+
42
+ app.initialize();
43
+ ```
44
+
45
+ `layoutStabilizationMs` controls how long the first window can auto-recenter if late CSS changes its rendered size after
46
+ open.
47
+
48
+ ## Documentation
49
+
50
+ - Porting plan and Code Arena differences: [porting_plan.md](porting_plan.md)
51
+ - Additional docs index: [docs/readme.md](docs/readme.md)
52
+ - CSR example: [examples/csr/readme.md](examples/csr/readme.md)
53
+ - SSR example: [examples/ssr/readme.md](examples/ssr/readme.md)
54
+
55
+ ## Naming Convention
56
+
57
+ - All documentation filenames use lowercase.
58
+ - Standard file name: `readme.md` (not `README.md`).
59
+
60
+ ## Files
61
+
62
+ - Porting plan: [porting_plan.md](porting_plan.md)
63
+ - Project root documentation: [readme.md](readme.md)
64
+
65
+ ## Scripts (Bun)
66
+
67
+ - `bun run imports`: verifies the public entrypoint can be imported.
68
+ - `bun run test`: runs Vitest with coverage enabled.
69
+ - `bun run test:watch`: watch mode with coverage enabled.
70
+ - `bun run lint`: type-lints JS with TypeScript (`checkJs`).
71
+ - `bun run format`: rewrites formatting with Prettier.
72
+ - `bun run quality`: aggregate quality command.
73
+ - `bun run csr`: builds bundles then opens the CSR demo.
74
+ - `bun run ssr`: builds bundles then starts the SSR demo server.
75
+
76
+ ## Quality Stack
77
+
78
+ - Test framework: `Vitest` + `jsdom`
79
+ - Coverage: `@vitest/coverage-v8` (V8 provider)
80
+ - Type linter: `TypeScript` in `checkJs` mode
81
+ - Formatter: `Prettier`
82
+
83
+ ## Coverage Report
84
+
85
+ - Run `bun run test`.
86
+ - Terminal summary is printed after tests.
87
+ - HTML report: `coverage/index.html`.
88
+ - LCOV report: `coverage/lcov.info`.
89
+
90
+ ## Runtime DOM Contract
91
+
92
+ The window engine expects these selectors/attributes in your modal HTML:
93
+
94
+ - `.window`
95
+ - `[data-modal]`
96
+ - `[data-close]`
97
+ - `[data-maximize]`
98
+ - `[data-bar]`
99
+ - `.window-content-scrollable` (optional but recommended)
100
+
101
+ ## Project Tree
102
+
103
+ ```text
104
+ .
105
+ ├── .gitignore
106
+ ├── porting_plan.md
107
+ ├── readme.md
108
+ ├── bun.lock
109
+ ├── docs
110
+ │ └── readme.md
111
+ ├── examples
112
+ │ ├── csr
113
+ │ │ ├── index.html
114
+ │ │ ├── main.js
115
+ │ │ └── readme.md
116
+ │ ├── ssr
117
+ │ │ ├── main.js
118
+ │ │ ├── public
119
+ │ │ │ └── client.js
120
+ │ │ ├── readme.md
121
+ │ │ └── server
122
+ │ │ ├── routes.js
123
+ │ │ └── templates
124
+ │ │ └── layout.js
125
+ │ ├── shared
126
+ │ ├── demo.css
127
+ │ ├── page-one.html
128
+ │ └── page-two.html
129
+ ├── package.json
130
+ ├── src
131
+ │ ├── bootstrap
132
+ │ │ └── NidamApp.js
133
+ │ ├── core
134
+ │ │ ├── BaseManager.js
135
+ │ │ ├── ContentInitializer.js
136
+ │ │ └── EventDelegator.js
137
+ │ ├── features
138
+ │ │ ├── desktop
139
+ │ │ │ └── DesktopIconManager.js
140
+ │ │ └── window
141
+ │ │ ├── WindowManager.js
142
+ │ │ └── WindowRefresher.js
143
+ │ ├── index.js
144
+ │ └── utils
145
+ │ ├── dom.js
146
+ │ └── eventUtils.js
147
+ ├── tests
148
+ │ ├── readme.md
149
+ │ └── unit
150
+ │ ├── contentInitializer.test.js
151
+ │ ├── windowManager.test.js
152
+ │ └── windowRefresher.test.js
153
+ ├── tsconfig.json
154
+ └── vitest.config.js
155
+ ```
156
+
157
+ ## Tree Explanation
158
+
159
+ - `src/index.js`: side-effect-free public API entrypoint.
160
+ - `src/bootstrap/`: app bootstrap composition (`NidamApp`).
161
+ - `src/core/`: generic infrastructure primitives (base manager, event delegation, dynamic init).
162
+ - `src/features/window/`: core window system (open/close/focus/drag/snap/refresh).
163
+ - `src/features/desktop/`: desktop icon drag-and-drop behavior.
164
+ - `src/utils/`: shared utility helpers.
165
+ - `examples/csr/`: ES module client-side example.
166
+ - `examples/ssr/`: Express-based server-side rendering example.
167
+ - `examples/shared/`: shared pages and demo styling reused by CSR/SSR.
168
+ - `tests/unit/`: focused unit tests for core and window features.
169
+ - `docs/porting_plan.md`: migration decisions and boundaries.
170
+ - `tsconfig.json`: type-lint config for JS (`checkJs`).
171
+ - `vitest.config.js`: test runner config.
172
+
173
+ ## Notes
174
+
175
+ Detailed migration strategy and design rationale are documented in [docs/porting_plan.md](porting_plan.md).