kempo 3.2.1 → 4.2.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 (39) hide show
  1. package/dist/components/Component.js +1 -1
  2. package/dist/components/Dialog.js +1 -1
  3. package/dist/components/LazyComponent.js +1 -1
  4. package/dist/components/Split.js +1 -1
  5. package/dist/components/Toast.js +1 -0
  6. package/dist/utils/Server.js +1 -0
  7. package/dist/utils/drag.js +1 -1
  8. package/dist/utils/fs-utils.js +1 -0
  9. package/dist/utils/minify.js +1 -0
  10. package/dist/utils/watchWindowSize.js +1 -0
  11. package/docs/components/split.html +19 -0
  12. package/docs/components/toast.html +330 -0
  13. package/docs/icons/error.svg +1 -0
  14. package/docs/icons/warning.svg +1 -0
  15. package/docs/kempo/components/Component.js +1 -1
  16. package/docs/kempo/components/Dialog.js +1 -1
  17. package/docs/kempo/components/LazyComponent.js +1 -1
  18. package/docs/kempo/components/Split.js +1 -1
  19. package/docs/kempo/components/Toast.js +1 -0
  20. package/docs/kempo/utils/Server.js +1 -0
  21. package/docs/kempo/utils/drag.js +1 -1
  22. package/docs/kempo/utils/fs-utils.js +1 -0
  23. package/docs/kempo/utils/minify.js +1 -0
  24. package/docs/kempo/utils/watchWindowSize.js +1 -0
  25. package/docs/nav.inc.html +2 -1
  26. package/docs/utils/Server.html +119 -0
  27. package/icons/error.svg +1 -0
  28. package/icons/warning.svg +1 -0
  29. package/package.json +2 -4
  30. package/scripts/build.js +8 -8
  31. package/scripts/docs.js +20 -20
  32. package/src/components/Dialog.js +11 -1
  33. package/src/components/Split.js +38 -13
  34. package/src/components/Toast.js +424 -0
  35. package/src/utils/Server.js +189 -0
  36. package/src/utils/fs-utils.js +41 -0
  37. package/src/utils/watchWindowSize.js +15 -0
  38. package/test/index.js +27 -16
  39. package/scripts/test.js +0 -31
@@ -1 +1 @@
1
- import Component from"./Component.js";import{isInView}from"../utils/element.js";import raf from"../utils/raf.js";const inViewIntervalId=Symbol();export default class LazyComponent extends Component{constructor(e){super(e),this[inViewIntervalId]=null,new IntersectionObserver((([e])=>{e.intersectionRatio>0&&!this.rendered?this.inViewCallback():this.outOfViewCallback()})).observe(this),this.registerAttributes({unrender:!1,inViewInterval:0})}connectedCallback(){this.renderSkelton(),raf((()=>{isInView(this)?this.inViewCallback():this.outOfViewCallback()}),2)}async inViewCallback(){return clearInterval(this[inViewIntervalId]),await this.render()}async outOfViewCallback(){return clearInterval(this[inViewIntervalId]),this[inViewIntervalId]=setInterval((async()=>{await isInView(this)&&this.inViewCallback()}),this.inViewInterval||this.constructor.inViewIntervalTimeout),!!this.unrender&&(await this.renderSkelton(),!0)}async renderSkelton(){return this.shadowRoot.innerHTML=`<link rel="stylesheet" href="${Component.pathToKempo}/kempo-styles.css" />${this.skeletonTemplate}<style>${this.shadowStyles}</style>`,this.rendered=!1,!0}get skeletonTemplate(){return'<div style="height: var(--skeleton_height, 1rem)"></div>'}static inViewIntervalTimeout=1e3}
1
+ import Component from"./Component.js";import{isInView}from"../utils/element.js";import raf from"../utils/raf.js";const inViewIntervalId=Symbol();export default class LazyComponent extends Component{constructor(e){super(e),this[inViewIntervalId]=null,new IntersectionObserver((([e])=>{e.intersectionRatio>0&&!this.rendered?this.inViewCallback():this.outOfViewCallback()})).observe(this),this.registerAttributes({unrender:!1,inViewInterval:0})}connectedCallback(){this.renderSkelton(),raf((()=>{isInView(this)?this.inViewCallback():this.outOfViewCallback()}),2)}async inViewCallback(){return clearInterval(this[inViewIntervalId]),await this.render()}async outOfViewCallback(){return clearInterval(this[inViewIntervalId]),this[inViewIntervalId]=setInterval((async()=>{await isInView(this)&&this.inViewCallback()}),this.inViewInterval||LazyComponent.inViewIntervalTimeout),!!this.unrender&&(await this.renderSkelton(),!0)}async renderSkelton(){return this.shadowRoot.innerHTML=`<link rel="stylesheet" href="${Component.pathToKempo}/kempo-styles.css" />${this.skeletonTemplate}<style>${this.shadowStyles}</style>`,this.rendered=!1,!0}get skeletonTemplate(){return'<div style="height: var(--skeleton_height, 1rem)"></div>'}static inViewIntervalTimeout=1e3}
@@ -1 +1 @@
1
- import Component from"./Component.js";import drag from"../utils/drag.js";import{dispatchEvent}from"../utils/element.js";const dragStartWidth=Symbol(),dragStartCallback=Symbol(),dragEndCallback=Symbol(),dragCallback=Symbol(),dragCleanup=Symbol();export default class Split extends Component{constructor(t,e){super(),this[dragStartWidth]=0,this[dragStartCallback]=()=>{this.resizing=!0,this[dragStartWidth]=Math.round(this.shadowRoot.getElementById("left").getBoundingClientRect().width),dispatchEvent(this,"resizestart",{startSize:this[dragStartWidth]})},this[dragCallback]=({x:t})=>{const e=`${this[dragStartWidth]+t}px`;this.setSize(e),dispatchEvent(this,"resize",{size:e})},this[dragEndCallback]=({x:t})=>{this.resizing=!1;const e=`${this[dragStartWidth]+t}px`;this.setSize(e),dispatchEvent(this,"resizeend",{size:e})},this[dragCleanup]=()=>{},this.registerAttribute("resizing",!1)}async connectedCallback(){super.connectedCallback(),this[dragCleanup]=drag({element:this.shadowRoot.getElementById("divider-handle"),callback:this[dragCallback],startCallback:this[dragStartCallback],endCallback:this[dragEndCallback]})}disconnectedCallback(){super.disconnectedCallback(),this[dragCleanup]()}setSize(t){this.style.setProperty("--left_width",t)}get shadowTemplate(){return`\n <div\n id="left"\n class="pane"\n >\n ${super.shadowTemplate}\n </div>\n <div id="divider-handle">\n <div id="divider-border"></div>\n </div>\n <div\n id="right"\n class="pane"\n >\n <slot name="right"></slot>\n </div>\n `}get shadowStyles(){return`\n ${super.shadowStyles}\n :host {\n --left_width: calc( (100% - var(--handle_width)) / 2);\n --handle_width: 0.5rem;\n --min_pane_width: 6rem;\n\n height: 100%;\n display: flex;\n align-items: stretch;\n flex: 1 1 auto;\n overflow: hidden;\n }\n .pane, #divider-handle {\n display: inline-block;\n }\n .pane {\n min-width: var(--min_pane_width);\n max-width: calc(100% - var(--min_pane_width));\n max-height: 100%;\n overflow: hidden;\n }\n #left {\n flex: 0 0 var(--left_width);\n }\n #divider-handle {\n display: flex;\n justify-content: center;\n width: var(--handle_width);\n cursor: ew-resize;\n }\n :host([resizing]) #divider-handle {\n background-color: var(--tc_primary);\n }\n :host([resizing]) .pane {\n pointer-events: none;\n user-select: none;\n }\n #divider-border {\n width: 1px;\n height: 100%;\n border-left: 1px solid var(--c_border);\n }\n #right {\n flex: 1 1;\n }\n \n `}}window.customElements.define("k-split",Split);
1
+ import Component from"./Component.js";import drag from"../utils/drag.js";import{dispatchEvent}from"../utils/element.js";import{watchWindowSize,unwatchWindowSize}from"../utils/watchWindowSize.js";const dragStartWidth=Symbol(),dragStartCallback=Symbol(),dragEndCallback=Symbol(),dragCallback=Symbol(),dragCleanup=Symbol(),windowResizeHandler=Symbol();export default class Split extends Component{constructor({stackWidth:t=0}){super(),this[dragStartWidth]=0,this[dragStartCallback]=()=>{this.resizing=!0,this[dragStartWidth]=Math.round(this.shadowRoot.getElementById("left").getBoundingClientRect().width),dispatchEvent(this,"resizestart",{startSize:this[dragStartWidth]})},this[dragCallback]=({x:t})=>{const e=`${this[dragStartWidth]+t}px`;this.setSize(e),dispatchEvent(this,"resize",{size:e})},this[dragEndCallback]=({x:t})=>{this.resizing=!1;const e=`${this[dragStartWidth]+t}px`;this.setSize(e),dispatchEvent(this,"resizeend",{size:e})},this[dragCleanup]=()=>{},this[windowResizeHandler]=t=>{this.stacked=t<=this.stackedWith},this.registerAttributes({resizing:!1,stacked:!1,stackWidth:t})}disconnectedCallback(){super.disconnectedCallback(),this[dragCleanup]()}async render(t){return!!await super.render(t)&&(this[dragCleanup]=drag({element:this.shadowRoot.getElementById("divider-handle"),callback:this[dragCallback],startCallback:this[dragStartCallback],endCallback:this[dragEndCallback]}),this.stacked=watchWindowSize(this[windowResizeHandler])<=this.stackWidth,!0)}setSize(t){this.style.setProperty("--left_width",t)}get shadowTemplate(){return`\n <div\n id="left"\n class="pane"\n >\n ${super.shadowTemplate}\n </div>\n <div id="divider-handle">\n <div id="divider-border"></div>\n </div>\n <div\n id="right"\n class="pane"\n >\n <slot name="right"></slot>\n </div>\n `}get shadowStyles(){return`\n ${super.shadowStyles}\n :host {\n --left_width: calc( (100% - var(--handle_width)) / 2);\n --handle_width: 0.5rem;\n --min_pane_width: 6rem;\n\n height: 100%;\n display: flex;\n align-items: stretch;\n flex: 1 1 auto;\n overflow: hidden;\n }\n .pane, #divider-handle {\n display: inline-block;\n }\n .pane {\n min-width: var(--min_pane_width);\n max-width: calc(100% - var(--min_pane_width));\n max-height: 100%;\n overflow: hidden;\n }\n #left {\n flex: 0 0 var(--left_width);\n }\n #divider-handle {\n display: flex;\n justify-content: center;\n width: var(--handle_width);\n cursor: ew-resize;\n }\n :host([resizing]) #divider-handle {\n background-color: var(--tc_primary);\n }\n :host([resizing]) .pane {\n pointer-events: none;\n user-select: none;\n }\n #divider-border {\n width: 1px;\n height: 100%;\n border-left: 1px solid var(--c_border);\n }\n #right {\n flex: 1 1;\n }\n \n `}}window.customElements.define("k-split",Split);
@@ -0,0 +1 @@
1
+ import Component from"./Component.js";import{onEvent,offEvent,dispatchEvent}from"../utils/element.js";const _position=Symbol();export class ToastContainer extends Component{constructor(t="bottom center"){super(),this[_position]=t.toLowerCase(),this.setAttribute("position",this[_position])}get position(){return this[_position]}set position(t){t!==this[_position]&&(this[_position]=t.toLowerCase(),this.setAttribute("position",this[_position]))}get shadowStyles(){return`\n ${super.shadowStyles}\n :host {\n position: fixed;\n display: flex;\n flex-direction: column;\n gap: 8px;\n z-index: 1000;\n pointer-events: none;\n padding: 16px;\n box-sizing: border-box;\n max-width: 100%;\n max-height: 100%;\n overflow: hidden;\n }\n\n :host([position*="top"]) {\n top: 0;\n }\n\n :host([position*="bottom"]) {\n bottom: 0;\n }\n\n :host([position*="left"]) {\n left: 0;\n align-items: flex-start;\n }\n\n :host([position*="right"]) {\n right: 0;\n align-items: flex-end;\n }\n\n :host([position*="center"]:not([position*="top"]):not([position*="bottom"])) {\n top: 50%;\n transform: translateY(-50%);\n }\n \n :host([position="center"]),\n :host([position*="center"][position*="top"]),\n :host([position*="center"][position*="bottom"]) {\n left: 50%;\n transform: translateX(-50%);\n }\n\n :host([position*="center"]:not([position="center"])) {\n align-items: center;\n }\n \n /* Combined transforms for center vertically and horizontally */\n :host([position*="center"]:not([position*="top"]):not([position*="bottom"]):not([position="center"])) {\n transform: translate(-50%, -50%);\n }\n\n ::slotted(*) {\n pointer-events: auto;\n }\n `}}window.customElements.define("k-toast-container",ToastContainer);const closeHandler=Symbol(),actionHandler=Symbol(),timeoutId=Symbol(),animationEndHandler=Symbol(),_toastPosition=Symbol(),_closing=Symbol();export default class Toast extends Component{constructor({actionCallback:t=()=>{},closeCallback:n=()=>{},timeout:o=0,position:i="bottom center"}={}){super(),this[timeoutId]=-1,this[_toastPosition]=i.toLowerCase(),this[_closing]=!1,this[closeHandler]=()=>{this.close()},this[actionHandler]=()=>{!1!==this.actionCallback()&&this.close()},this[animationEndHandler]=t=>{t.animationName.includes("toast-hide")?(this.removeAttribute("animating"),this.opened=!1,dispatchEvent(this,"close openchange"),this.closeCallback(),this[_closing]=!1):t.animationName.includes("toast-show")&&this.removeAttribute("animating")},this.registerProps({actionCallback:t,closeCallback:n}),this.registerAttributes({hasAction:!1,hasClose:!1,hasIcon:!1,timeout:o,opened:!1,position:this[_toastPosition]})}async render(t){return!!await super.render(t)&&(onEvent(this.shadowRoot.getElementById("action"),"click",this[actionHandler]),onEvent(this.shadowRoot.getElementById("close"),"click",this[closeHandler]),onEvent(this,"animationend",this[animationEndHandler]),!0)}disconnectedCallback(){super.disconnectedCallback(),clearTimeout(this[timeoutId]),offEvent(this.shadowRoot.getElementById("action"),"click",this[actionHandler]),offEvent(this.shadowRoot.getElementById("close"),"click",this[closeHandler]),offEvent(this,"animationend",this[animationEndHandler])}attributeChangedCallback(t,n,o){super.attributeChangedCallback(t,n,o),"opened"===t&&n!==o&&o&&(this.hasAction=!!this.querySelector('[slot="action"]'),this.hasClose=!!this.querySelector('[slot="close"]'),this.hasIcon=!!this.querySelector('[slot="icon"]'))}open(){this[_closing]=!1,this.setAttribute("animating","in"),this.opened=!0,this.timeout&&(clearTimeout(this[timeoutId]),this[timeoutId]=setTimeout((()=>{this.close()}),this.timeout)),dispatchEvent(this,"open openchange")}close(){clearTimeout(this[timeoutId]),this.opened&&!this[_closing]&&(this[_closing]=!0,this.setAttribute("animating","out"))}get shadowTemplate(){return`\n <div id="icon">\n <slot name="icon"></slot>\n </div>\n <div id="message">\n ${super.shadowTemplate}\n </div>\n <button id="action" class="no-style">\n <slot name="action"></slot>\n </button>\n <button id="close" class="no-style">\n <slot name="close"></slot>\n </button>\n `}get shadowStyles(){return`\n ${super.shadowStyles}\n @keyframes toast-show-bottom {\n from { opacity: 0; transform: translateY(20px); }\n to { opacity: 1; transform: translateY(0); }\n }\n @keyframes toast-hide-bottom {\n from { opacity: 1; transform: translateY(0); }\n to { opacity: 0; transform: translateY(20px); }\n }\n @keyframes toast-show-top {\n from { opacity: 0; transform: translateY(-20px); }\n to { opacity: 1; transform: translateY(0); }\n }\n @keyframes toast-hide-top {\n from { opacity: 1; transform: translateY(0); }\n to { opacity: 0; transform: translateY(-20px); }\n }\n @keyframes toast-show-left {\n from { opacity: 0; transform: translateX(-20px); }\n to { opacity: 1; transform: translateX(0); }\n }\n @keyframes toast-hide-left {\n from { opacity: 1; transform: translateX(0); }\n to { opacity: 0; transform: translateX(-20px); }\n }\n @keyframes toast-show-right {\n from { opacity: 0; transform: translateX(20px); }\n to { opacity: 1; transform: translateX(0); }\n }\n @keyframes toast-hide-right {\n from { opacity: 1; transform: translateX(0); }\n to { opacity: 0; transform: translateX(20px); }\n }\n :host {\n display: none;\n min-width: 16rem;\n max-width: calc(100% - ( 2 * var(--spacer) ));\n background-color: var(--c_bg);\n border-radius: var(--radius);\n box-shadow: var(--drop_shadow);\n }\n :host([opened="true"]) {\n display: flex;\n }\n :host([animating="in"][position*="bottom"]) {\n animation: toast-show-bottom var(--animation_ms, 300ms) ease forwards;\n }\n :host([animating="out"][position*="bottom"]) {\n animation: toast-hide-bottom var(--animation_ms, 300ms) ease forwards;\n }\n :host([animating="in"][position*="top"]) {\n animation: toast-show-top var(--animation_ms, 300ms) ease forwards;\n }\n :host([animating="out"][position*="top"]) {\n animation: toast-hide-top var(--animation_ms, 300ms) ease forwards;\n }\n :host([animating="in"][position*="left"]:not([position*="top"]):not([position*="bottom"])) {\n animation: toast-show-left var(--animation_ms, 300ms) ease forwards;\n }\n :host([animating="out"][position*="left"]:not([position*="top"]):not([position*="bottom"])) {\n animation: toast-hide-left var(--animation_ms, 300ms) ease forwards;\n }\n :host([animating="in"][position*="right"]:not([position*="top"]):not([position*="bottom"])) {\n animation: toast-show-right var(--animation_ms, 300ms) ease forwards;\n }\n :host([animating="out"][position*="right"]:not([position*="top"]):not([position*="bottom"])) {\n animation: toast-hide-right var(--animation_ms, 300ms) ease forwards;\n }\n #icon {\n padding: var(--spacer);\n padding-right: 0;\n }\n #message {\n padding: var(--spacer);\n flex: 1 1 auto;\n }\n :host(:not([has-close])) #close,\n :host(:not([has-action])) #action,\n :host(:not([has-icon])) #icon {\n display: none;\n }\n #action {\n background: transparent;\n border: none;\n color: var(--tc_primary, blue);\n cursor: pointer;\n padding: var(--spacer);\n }\n #action:hover {\n color: var(--tc_primary__hover, lightblue);\n }\n #close {\n background: transparent;\n border: none;\n cursor: pointer;\n padding: var(--spacer);\n opacity: 0.7;\n transition: opacity var(--animation_ms);\n }\n #close:hover {\n opacity: 1;\n }\n `}static observedAttributes=[...super.observedAttributes,"opened"];static create(t,n={}){let{position:o="auto",removeOnClose:i=!0,closeCallback:s=()=>{},action:e=!1,close:a=!1,icon:r=!1}=n;"auto"===o&&(o=window.innerWidth<=768?"bottom center":"top right");let c=document.querySelector(`k-toast-container[position="${o}"]`);c||(c=new ToastContainer(o),document.body.appendChild(c));const l=new Toast({position:o,timeout:5e3,...n,closeCallback:(...t)=>{i&&(l.remove(),0===c.children.length&&c.remove()),s(...t)}});if(l.innerHTML=t,r)if(r instanceof HTMLElement)r.slot="icon",l.appencChild(r);else if("string"==typeof r){const t=document.createElement("span");t.slot="icon",t.innerHTML=r,l.appendChild(t)}if(e)if(e instanceof HTMLElement)e.slot="action",l.appencChild(e);else if("string"==typeof e){const t=document.createElement("span");t.slot="action",t.innerHTML=e,l.appendChild(t)}if(a)if(a instanceof HTMLElement)a.slot="close",l.appencChild(a);else if("string"==typeof a){const t=document.createElement("span");t.slot="close",t.innerHTML=a,l.appendChild(t)}return l.open(),c.appendChild(l),l}static success(t,n={}){const o=Toast.create(t,{icon:'<k-icon name="check"></k-icon>',...n});return o.classList.add("bg-success"),o}static warning(t,n={}){const o=Toast.create(t,{icon:'<k-icon name="warning"></k-icon>',...n});return o.classList.add("bg-warning"),o}static error(t,n={}){const o=Toast.create(t,{icon:'<k-icon name="error"></k-icon>',...n});return o.classList.add("bg-danger"),o}}window.customElements.define("k-toast",Toast);
@@ -0,0 +1 @@
1
+ import http from"http";import{readFile}from"fs/promises";import{join,extname,normalize}from"path";export class Server{constructor(t={}){this.routes=[]}route(t,e,r){const n=r?r.map((t=>t.toUpperCase())):["ANY"];return this.routes.push({pattern:t,handler:e,methods:n}),this}get(t,e){return this.route(t,e,["GET"])}post(t,e){return this.route(t,e,["POST"])}put(t,e){return this.route(t,e,["PUT"])}delete(t,e){return this.route(t,e,["DELETE"])}patch(t,e){return this.route(t,e,["PATCH"])}async handleRequest(t,e){try{const[r,n=""]=t.url.split("?"),s=n.split("&").filter((t=>""!=t)).map((t=>{const[e,r]=t.split("=");return{[e]:decodeURIComponent(r)}})),i=this.routes.find((({methods:e,pattern:n})=>{if(!e.includes(t.method||"GET")&&!e.includes("ANY"))return!1;const s=r.split("/").filter((t=>""!=t)),i=n.split("/").filter((t=>""!=t));if(0==s.length&&0==i.length)return!0;const o=i.length>0&&"*"===i[i.length-1];if(!o&&s.length!==i.length)return!1;if(o&&s.length<i.length-1)return!1;const a=o?i.length-1:i.length;for(let t=0;t<a;t++)if(!i[t].startsWith(":")&&i[t]!==s[t])return!1;return!0}));if(!i)return e.writeHead(404,{"Content-Type":"text/plain"}),void e.end("Not Found");const{pattern:o,handler:a}=i;if("string"==typeof a){const t=o.split("/").filter((t=>""!=t)),n=r.split("/").filter((t=>""!=t)),s="*"===t[t.length-1]?n.slice(t.length-1).join("/"):"",i=a.endsWith("*")?a.slice(0,-1):a,l=i.startsWith("/")||/^[A-Z]:/i.test(i)?normalize(join(i,s)):join(process.cwd(),normalize(join(i,s)));l.endsWith("/")||l.endsWith("\\")?await Server.serveFile(join(l,"index.html"),e):await Server.serveFile(l,e)}else if("function"==typeof a){const n={};o.includes(":")&&o.split("/").filter((t=>""!=t)).forEach(((t,e)=>{t.startsWith(":")&&(n[t.slice(1)]=r.split("/").filter((t=>""!=t))[e])})),await a(t,e,n,s)}else console.error("Invalid Route Handler"),e.writeHead(500,{"Content-Type":"text/plain"}),e.end("Internal server error")}catch(t){console.error("Request error:",t),e.writeHead(500,{"Content-Type":"text/plain"}),e.end("Internal server error")}}listen(t=8080){return new Promise(((e,r)=>{try{const n=http.createServer(((t,e)=>this.handleRequest(t,e)));n.on("error",(e=>{"EADDRINUSE"===e.code?r(new Error(`Port ${t} is already in use`)):r(e)})),n.listen(t,(()=>{e(n)}))}catch(t){r(t)}}))}static mimeTypes={".html":"text/html; charset=utf-8",".js":"text/javascript; charset=utf-8",".css":"text/css; charset=utf-8",".json":"application/json; charset=utf-8",".png":"image/png",".jpg":"image/jpeg",".svg":"image/svg+xml",".ico":"image/x-icon",".txt":"text/plain; charset=utf-8",".md":"text/plain; charset=utf-8",".map":"application/json; charset=utf-8"};static textExtensions=[".txt",".md",".js",".css",".html",".json",".map"];static getMimeType(t){const e=extname(t).toLowerCase();return this.mimeTypes[e]?this.mimeTypes[e]:this.textExtensions.includes(e)?"text/plain; charset=utf-8":"application/octet-stream"}static async serveFile(t,e,r={}){try{const n=await readFile(t);e.writeHead(200,{"Content-Type":Server.getMimeType(t),...r}),e.end(n)}catch(t){const r="ENOENT"===t.code?404:500,n=404===r?"Not found":"Internal server error";e.writeHead(r,{"Content-Type":"text/plain"}),e.end(n)}}}
@@ -1 +1 @@
1
- export default(e={})=>{let t,o,n;const{element:s=(()=>{}),callback:a=(()=>{}),startCallback:v=(()=>{}),moveCallback:i=(()=>{}),endCallback:r=(()=>{}),preventScroll:d=!1}=e,u=s=>{d&&s.preventDefault(),clearTimeout(n);const v=s.pageX||s.touches[0].pageX,r=s.pageY||s.touches[0].pageY,u={x:v-t,y:r-o,...e};a(u),i(u)},c=({pageX:s,pageY:v})=>{clearTimeout(n);const i={x:s-t,y:v-o,...e};a(i),r(i),m()},p=s=>{clearTimeout(n),t=s.pageX||s.touches[0].pageX,o=s.pageY||s.touches[0].pageY;const a={x:0,y:0,...e};v(a),window.addEventListener("mousemove",u,{passive:!d}),window.addEventListener("mouseup",c,{passive:!d}),window.addEventListener("touchmove",u,{passive:!d}),window.addEventListener("touchend",c,{passive:!d})},m=()=>{window.removeEventListener("mousemove",u),window.removeEventListener("mouseup",c),window.removeEventListener("touchmove",u),window.removeEventListener("touchend",c)};return s.addEventListener("mousedown",p,{passive:!d}),s.addEventListener("touchstart",p,{passive:!d}),()=>{s.removeEventListener("mousedown",p),m()}};
1
+ export default(e={})=>{let t,o,n;const{element:s=()=>{},callback:a=()=>{},startCallback:v=()=>{},moveCallback:i=()=>{},endCallback:r=()=>{},preventScroll:d=!1}=e,u=s=>{d&&s.preventDefault(),clearTimeout(n);const v=s.pageX||s.touches[0].pageX,r=s.pageY||s.touches[0].pageY,u={x:v-t,y:r-o,...e};a(u),i(u)},c=({pageX:s,pageY:v})=>{clearTimeout(n);const i={x:s-t,y:v-o,...e};a(i),r(i),m()},p=s=>{clearTimeout(n),t=s.pageX||s.touches[0].pageX,o=s.pageY||s.touches[0].pageY;const a={x:0,y:0,...e};v(a),window.addEventListener("mousemove",u,{passive:!d}),window.addEventListener("mouseup",c,{passive:!d}),window.addEventListener("touchmove",u,{passive:!d}),window.addEventListener("touchend",c,{passive:!d})},m=()=>{window.removeEventListener("mousemove",u),window.removeEventListener("mouseup",c),window.removeEventListener("touchmove",u),window.removeEventListener("touchend",c)};return s.addEventListener("mousedown",p,{passive:!d}),s.addEventListener("touchstart",p,{passive:!d}),()=>{s.removeEventListener("mousedown",p),m()}};
@@ -0,0 +1 @@
1
+ import fs from"fs/promises";import path from"path";export async function ensureDir(i){try{await fs.mkdir(i,{recursive:!0})}catch(i){if("EEXIST"!==i.code)throw i}}export async function copyDir(i,r){await ensureDir(r);const t=await fs.readdir(i,{withFileTypes:!0});for(const a of t){const t=path.join(i,a.name),o=path.join(r,a.name);a.isDirectory()?await copyDir(t,o):await fs.copyFile(t,o)}}export async function emptyDir(i){try{const r=await fs.readdir(i);await Promise.all(r.map((r=>fs.rm(path.join(i,r),{recursive:!0,force:!0}))))}catch(i){if("ENOENT"!==i.code)throw i}}
@@ -0,0 +1 @@
1
+ export function minifyCss(e){return e.replace(/\s*\{\s*/g,"{").replace(/\s*\}\s*/g,"}").replace(/\n/g,"").replace(/\r/g,"").replace(/\s+/g," ").replace(/\s*\:\s*/g,":").replace(/\s*\;\s*/g,";").replace(/;\}/g,"}")}
@@ -0,0 +1 @@
1
+ import debounce from"./debounce.js";const handlers=new Set;window.addEventListener("resize",debounce((()=>{const e=window.innerWidth;handlers.forEach((n=>n(e)))})));export const watchWindowSize=e=>(handlers.add(e),window.innerWidth);export const unwatchWindowSize=e=>{handlers.remove(e)};
package/docs/nav.inc.html CHANGED
@@ -55,6 +55,7 @@
55
55
  <a href="%%root%%components/table.html">Table</a>
56
56
  <a href="%%root%%components/tabs.html">Tabs</a>
57
57
  <a href="%%root%%components/tags.html">Tags</a>
58
+ <a href="%%root%%components/toast.html">Toast</a>
58
59
  <a href="%%root%%components/theme-switcher.html">Theme Switcher</a>
59
60
  <a href="%%root%%components/timestamp.html">Timestamp</a>
60
61
  <a href="%%root%%components/toggle.html">Toggle</a>
@@ -62,7 +63,7 @@
62
63
 
63
64
  <h5 class="mb0">Utils</h5>
64
65
  <div class="pl mb">
65
-
66
+ <a href="%%root%%utils/Server.html">Server</a>
66
67
  </div>
67
68
  </menu>
68
69
  </k-side-menu>
@@ -0,0 +1,119 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Template - Utils - Kempo Docs - A Web Components Solution</title>
7
+ <link rel="stylesheet" href="../kempo/kempo-vars.css" /><link rel="stylesheet" href="../kempo/kempo-styles.css" />
8
+ <link rel="stylesheet" href="../kempo/kempo-hljs.css" />
9
+ <script type="module">
10
+ import Import from '../kempo/components/Import.js';
11
+ import Icon from '../kempo/components/Icon.js';
12
+ import Search from '../kempo/components/Search.js';
13
+ Import.addReplacement('root', '../');
14
+ Icon.pathToIcons = ['../icons'];
15
+ Search.searchDataUrl = '../search.json';
16
+ </script>
17
+ </head>
18
+ <body>
19
+ <k-import src="../nav.inc.html"></k-import>
20
+ <h1 class="ta-center">Server</h1>
21
+ <main>
22
+ <details class="b r mb">
23
+ <summary class="p">Table of Contents</summary>
24
+ <div class="m mt0 pl">
25
+ <h6>Examples</h6>
26
+ <a href="#basicUsage">Basic Usage</a><br />
27
+ <a href="#staticServing">Static File Serving</a><br />
28
+ <a href="#routePatterns">Route Patterns</a><br />
29
+
30
+ <h6 class="mt">JavaScript Reference</h6>
31
+ <a href="#constructor">Constructor</a><br />
32
+ <a href="#requirements">Requirements</a><br />
33
+ <a href="#methods">Methods</a><br />
34
+ <a href="#staticMembers">Static Members</a><br />
35
+ </div>
36
+ </details>
37
+
38
+ <h3>Description</h3>
39
+ <p>A lightweight HTTP server utility for serving static files and handling route patterns with wildcard support.</p>
40
+
41
+ <h3 id="basicUsage"><a href="#basicUsage" class="no-link">Basic Usage</a></h3>
42
+ <pre><code class="language-javascript">import { Server } from '../utils/Server.js';
43
+
44
+ const server = new Server();
45
+ server
46
+ .static('/assets', './public')
47
+ .route('/api/*.json', async (req, res) => {
48
+ // Handle JSON requests
49
+ });
50
+
51
+ await server.listen(8080);</code></pre>
52
+
53
+ <h3 id="staticServing"><a href="#staticServing" class="no-link">Static File Serving</a></h3>
54
+ <pre><code class="language-javascript">// Serve entire directories
55
+ server.static('/assets', './public');
56
+ server.static('/docs', './documentation');
57
+
58
+ // Serve single files with custom headers
59
+ server.route('/style.css', async (req, res) => {
60
+ await server.serveFile('./styles/main.css', res, {
61
+ 'Cache-Control': 'no-cache'
62
+ });
63
+ });</code></pre>
64
+
65
+ <h3 id="routePatterns"><a href="#routePatterns" class="no-link">Route Patterns</a></h3>
66
+ <p>Routes support wildcard patterns for flexible path matching:</p>
67
+ <ul>
68
+ <li><code>*</li>
69
+ <li><code>**</code> - Matches any characters including /</li>
70
+ </ul>
71
+ <pre><code class="language-javascript">// Match specific file types
72
+ server.route('/api/*.json', handler);
73
+
74
+ // Match files in subdirectories
75
+ server.route('/docs/**/*.html', handler);
76
+
77
+ // Match everything under a path
78
+ server.route('/assets/**/*', handler);</code></pre>
79
+
80
+ <h2 id="jsRef">JavaScript Reference</h2>
81
+
82
+ <h3 id="constructor"><a href="#constructor" class="no-link">Constructor</a></h3>
83
+ <h5><code>new Server()</code></h5>
84
+ <p>Creates a new server instance.</p>
85
+
86
+ <h3 id="requirements"><a href="#requirements" class="no-link">Requirements</a></h3>
87
+ <ul>
88
+ <li>Node.js built-in modules only (http, fs/promises, path)</li>
89
+ <li>No external dependencies</li>
90
+ </ul>
91
+
92
+ <h3 id="methods"><a href="#methods" class="no-link">Methods</a></h3>
93
+
94
+ <h5><code>route(path: string, handler: Function, methods?: string[]): Server</code></h5>
95
+ <p>Registers a route handler with optional HTTP method restrictions. Returns this for chaining.</p>
96
+
97
+ <h5><code>static(urlPath: string, dirPath: string): Server</code></h5>
98
+ <p>Serves an entire directory of static files. Returns this for chaining.</p>
99
+
100
+ <h5><code>serveFile(filePath: string, res: Response, headers?: object): Promise&lt;void&gt;</code></h5>
101
+ <p>Serves a single file with appropriate MIME type and optional headers.</p>
102
+
103
+ <h5><code>listen(port?: number): Promise&lt;http.Server&gt;</code></h5>
104
+ <p>Starts the server on the specified port. Returns a Promise that resolves to the server instance.</p>
105
+
106
+ <h3 id="staticMembers"><a href="#staticMembers" class="no-link">Static Members</a></h3>
107
+
108
+ <h5><code>static mimeTypes: Record&lt;string, string&gt;</code></h5>
109
+ <p>MIME type mappings for file extensions.</p>
110
+
111
+ <h5><code>static textExtensions: string[]</code></h5>
112
+ <p>List of file extensions that should be served as text.</p>
113
+
114
+ <h5><code>static getMimeType(filePath: string): string</code></h5>
115
+ <p>Returns the appropriate MIME type for a file based on its extension.</p>
116
+ </main>
117
+ <script type="module" src="../kempo/components/Import.js"></script>
118
+ </body>
119
+ </html>
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 -960 960 960"><path fill="error" d="M480-280q17 0 28.5-11.5T520-320q0-17-11.5-28.5T480-360q-17 0-28.5 11.5T440-320q0 17 11.5 28.5T480-280Zm-40-160h80v-240h-80v240ZM330-120 120-330v-300l210-210h300l210 210v300L630-120H330Zm34-80h232l164-164v-232L596-760H364L200-596v232l164 164Zm116-280Z"/></svg>
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 -960 960 960"><path fill="warning" d="m40-120 440-760 440 760H40Zm138-80h604L480-720 178-200Zm302-40q17 0 28.5-11.5T520-280q0-17-11.5-28.5T480-320q-17 0-28.5 11.5T440-280q0 17 11.5 28.5T480-240Zm-40-120h80v-200h-80v200Zm40-100Z"/></svg>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "kempo",
3
- "version": "3.2.1",
3
+ "version": "4.2.0",
4
4
  "type": "module",
5
5
  "description": "A web component library",
6
6
  "exports": {
@@ -11,7 +11,7 @@
11
11
  "./components/*": "./dist/components/*.js"
12
12
  },
13
13
  "scripts": {
14
- "test": "node ./scripts/test.js",
14
+ "test": "node ./test/index.js",
15
15
  "build": "node ./scripts/build.js",
16
16
  "docs": "node ./scripts/docs.js",
17
17
  "docs-src": "node ./scripts/docs.js -s"
@@ -20,8 +20,6 @@
20
20
  "license": "MIT",
21
21
  "dependencies": {},
22
22
  "devDependencies": {
23
- "express": "^4.18.3",
24
- "fs-extra": "^11.2.0",
25
23
  "terser": "^5.29.2"
26
24
  },
27
25
  "directories": {
package/scripts/build.js CHANGED
@@ -1,8 +1,8 @@
1
1
  import fs from 'fs/promises';
2
- import fse from 'fs-extra';
3
2
  import { minify } from 'terser';
4
3
  import path from 'path';
5
4
  import { fileURLToPath } from 'url';
5
+ import { ensureDir, copyDir, emptyDir } from '../src/utils/fs-utils.js';
6
6
 
7
7
  const __filename = fileURLToPath(import.meta.url);
8
8
  const __dirname = path.dirname(__filename);
@@ -57,7 +57,7 @@ process.stdout.write(`0/${components.length} = 0%`);
57
57
  await Promise.all(components.map(async component => {
58
58
  const relativePath = path.relative(componentsDir, component);
59
59
  const destPath = path.join('./dist/components', relativePath);
60
- await fse.ensureDir(path.dirname(destPath));
60
+ await ensureDir(path.dirname(destPath));
61
61
  await fs.writeFile(destPath, minifiedComponents[component], 'utf-8');
62
62
  process.stdout.write("\r");
63
63
  complete++;
@@ -95,7 +95,7 @@ process.stdout.write("\n");
95
95
  complete = 0;
96
96
  console.log('Saving Minified Utils to dist/');
97
97
  process.stdout.write(`0/${utils.length} = 0%`);
98
- await fse.ensureDir('./dist/utils');
98
+ await ensureDir('./dist/utils');
99
99
  await Promise.all(utils.map(async util => {
100
100
  await fs.writeFile(`./dist/utils/${util}.js`, minifiedUtils[util], 'utf-8');
101
101
  process.stdout.write("\r");
@@ -139,16 +139,16 @@ console.log('Saving kempo-hljs.css');
139
139
  await fs.writeFile('./dist/kempo-hljs.css', minifiedKempoHljsCSS, 'utf-8');
140
140
 
141
141
  console.log('Copying dist/ to docs/');
142
- await fse.ensureDir('./docs/'); // will also ensure docs/kempo
143
- await fse.copy('./dist', './docs/kempo')
142
+ await ensureDir('./docs/'); // will also ensure docs/kempo
143
+ await copyDir('./dist', './docs/kempo')
144
144
 
145
145
  const iconsSrcDir = path.join(__dirname, '../icons');
146
146
  const iconsDestDir = path.join(__dirname, '../docs/icons');
147
147
 
148
148
  console.log('Deleting svg icons currently in docs/icons');
149
- await fse.ensureDir(iconsDestDir);
150
- await fse.emptyDir(iconsDestDir);
149
+ await ensureDir(iconsDestDir);
150
+ await emptyDir(iconsDestDir);
151
151
  console.log('Copying all files from icons to docs/icons');
152
- await fse.copy(iconsSrcDir, iconsDestDir);
152
+ await copyDir(iconsSrcDir, iconsDestDir);
153
153
 
154
154
  console.log('Build Complete');
package/scripts/docs.js CHANGED
@@ -1,10 +1,7 @@
1
- import express from 'express';
2
1
  import { dirname, join } from 'path';
3
2
  import { fileURLToPath } from 'url';
4
- import {
5
- getArgs,
6
- runChildNodeProcess
7
- } from '../src/utils/cli.js';
3
+ import { Server } from '../src/utils/Server.js';
4
+ import { getArgs, runChildNodeProcess } from '../src/utils/cli.js';
8
5
 
9
6
  const __filename = fileURLToPath(import.meta.url);
10
7
  const __dirname = dirname(__filename);
@@ -25,23 +22,26 @@ if(build){
25
22
  runChildNodeProcess(join(__dirname, 'build.js'));
26
23
  }
27
24
 
28
- const app = express();
25
+ const server = new Server();
29
26
 
30
- if(src){
31
- app.use('/kempo', express.static('src'));
32
- app.use('/icons', express.static('icons'));
33
- app.get('/kempo/kempo-styles.css', (req, res) => {
34
- res.set('Cache-Control', 'no-cache');
35
- res.sendFile(join(__dirname, '../src/kempo-styles.css'));
36
- })
27
+ if (src) {
28
+ server
29
+ .get('/', join(__dirname, '../docs/'))
30
+ .get('/kempo/kempo-styles.css', join(__dirname, '../src/kempo-styles.css'))
31
+ .get('/kempo/*', join(__dirname, '../src/*'))
32
+ .get('/icons/*', join(__dirname, '../icons/*'))
33
+ .get('/*', join(__dirname, '../docs/*'));
37
34
  } else {
38
- app.get('/kempo/kempo-styles.css', (req, res) => {
39
- res.set('Cache-Control', 'private');
40
- res.sendFile(join(__dirname, '../docs/kempo/kempo-styles.css'));
41
- });
35
+ server
36
+ .get('/', join(__dirname, '../docs/'))
37
+ .get('/kempo/kempo-styles.css', join(__dirname, '../docs/kempo/kempo-styles.css'))
38
+ .get('/*', join(__dirname, '../docs/*'));
42
39
  }
43
- app.use(express.static('docs'));
44
40
 
45
- app.listen(port, () => {
41
+ try {
42
+ await server.listen(port);
46
43
  console.log(`Docs Running on: http://localhost:${port}`);
47
- });
44
+ } catch (err) {
45
+ console.error('Failed to start server:', err.message);
46
+ process.exit(1);
47
+ }
@@ -244,9 +244,19 @@ export default class Dialog extends Component {
244
244
 
245
245
  static create(contents = '', options = {}){
246
246
  if(options.closeExisting!==false)document.querySelectorAll('k-dialog').forEach(d=>d.close());
247
+ const {
248
+ removeOnClose = true,
249
+ closeCallback = () => {}
250
+ } = options;
247
251
  const $dialog = new Dialog({
248
252
  opened: true,
249
- ...options
253
+ ...options,
254
+ closeCallback: (...args) => {
255
+ if(removeOnClose){
256
+ $dialog.remove();
257
+ }
258
+ closeCallback(...args);
259
+ }
250
260
  });
251
261
  onEvent($dialog, 'close', $dialog.remove);
252
262
  if(contents instanceof HTMLElement || contents instanceof DocumentFragment){
@@ -3,14 +3,21 @@ import drag from '../utils/drag.js';
3
3
  import {
4
4
  dispatchEvent
5
5
  } from '../utils/element.js';
6
+ import {
7
+ watchWindowSize,
8
+ unwatchWindowSize
9
+ } from '../utils/watchWindowSize.js'
6
10
 
7
11
  const dragStartWidth = Symbol(),
8
12
  dragStartCallback = Symbol(),
9
13
  dragEndCallback = Symbol(),
10
14
  dragCallback = Symbol(),
11
- dragCleanup = Symbol();
15
+ dragCleanup = Symbol(),
16
+ windowResizeHandler = Symbol();
12
17
  export default class Split extends Component {
13
- constructor(_saveWidth, _loadWidth){
18
+ constructor({
19
+ stackWidth = 0
20
+ } = {}){
14
21
  super();
15
22
 
16
23
  /* Private Members */
@@ -37,24 +44,35 @@ export default class Split extends Component {
37
44
  dispatchEvent(this, 'resizeend', { size } );
38
45
  }
39
46
  this[dragCleanup] = () => {}
47
+ this[windowResizeHandler] = (width) => {
48
+ this.stacked = width <= this.stackWidth;
49
+ }
40
50
 
41
51
  /* Init */
42
- this.registerAttribute('resizing', false);
43
- }
44
-
45
- async connectedCallback(){
46
- super.connectedCallback();
47
- this[dragCleanup] = drag({
48
- element: this.shadowRoot.getElementById('divider-handle'),
49
- callback: this[dragCallback],
50
- startCallback: this[dragStartCallback],
51
- endCallback: this[dragEndCallback]
52
+ this.registerAttributes({
53
+ resizing: false,
54
+ stacked: false,
55
+ stackWidth
52
56
  });
53
57
  }
58
+
54
59
  disconnectedCallback(){
55
60
  super.disconnectedCallback();
56
61
  this[dragCleanup]();
57
62
  }
63
+ async render(force){
64
+ if(await super.render(force)){
65
+ this[dragCleanup] = drag({
66
+ element: this.shadowRoot.getElementById('divider-handle'),
67
+ callback: this[dragCallback],
68
+ startCallback: this[dragStartCallback],
69
+ endCallback: this[dragEndCallback]
70
+ });
71
+ this.stacked = watchWindowSize(this[windowResizeHandler]) <= this.stackWidth;
72
+ return true;
73
+ }
74
+ return false;
75
+ }
58
76
 
59
77
  setSize(size){
60
78
  this.style.setProperty('--left_width', size);
@@ -126,7 +144,14 @@ export default class Split extends Component {
126
144
  #right {
127
145
  flex: 1 1;
128
146
  }
129
-
147
+ :host([stacked="true"]),
148
+ :host([stacked="true"]) #left,
149
+ :host([stacked="true"]) #right {
150
+ display: block;
151
+ }
152
+ :host([stacked="true"]) #divider-handle {
153
+ display: none;
154
+ }
130
155
  `;
131
156
  }
132
157
  }