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.
- package/dist/components/Component.js +1 -1
- package/dist/components/Dialog.js +1 -1
- package/dist/components/LazyComponent.js +1 -1
- package/dist/components/Split.js +1 -1
- package/dist/components/Toast.js +1 -0
- package/dist/utils/Server.js +1 -0
- package/dist/utils/drag.js +1 -1
- package/dist/utils/fs-utils.js +1 -0
- package/dist/utils/minify.js +1 -0
- package/dist/utils/watchWindowSize.js +1 -0
- package/docs/components/split.html +19 -0
- package/docs/components/toast.html +330 -0
- package/docs/icons/error.svg +1 -0
- package/docs/icons/warning.svg +1 -0
- package/docs/kempo/components/Component.js +1 -1
- package/docs/kempo/components/Dialog.js +1 -1
- package/docs/kempo/components/LazyComponent.js +1 -1
- package/docs/kempo/components/Split.js +1 -1
- package/docs/kempo/components/Toast.js +1 -0
- package/docs/kempo/utils/Server.js +1 -0
- package/docs/kempo/utils/drag.js +1 -1
- package/docs/kempo/utils/fs-utils.js +1 -0
- package/docs/kempo/utils/minify.js +1 -0
- package/docs/kempo/utils/watchWindowSize.js +1 -0
- package/docs/nav.inc.html +2 -1
- package/docs/utils/Server.html +119 -0
- package/icons/error.svg +1 -0
- package/icons/warning.svg +1 -0
- package/package.json +2 -4
- package/scripts/build.js +8 -8
- package/scripts/docs.js +20 -20
- package/src/components/Dialog.js +11 -1
- package/src/components/Split.js +38 -13
- package/src/components/Toast.js +424 -0
- package/src/utils/Server.js +189 -0
- package/src/utils/fs-utils.js +41 -0
- package/src/utils/watchWindowSize.js +15 -0
- package/test/index.js +27 -16
- 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||
|
|
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
|
|
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)}}}
|
package/docs/kempo/utils/drag.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export default(e={})=>{let t,o,n;const{element:s=(
|
|
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<void></code></h5>
|
|
101
|
+
<p>Serves a single file with appropriate MIME type and optional headers.</p>
|
|
102
|
+
|
|
103
|
+
<h5><code>listen(port?: number): Promise<http.Server></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<string, string></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>
|
package/icons/error.svg
ADDED
|
@@ -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
|
+
"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 ./
|
|
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
|
|
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
|
|
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
|
|
143
|
-
await
|
|
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
|
|
150
|
-
await
|
|
149
|
+
await ensureDir(iconsDestDir);
|
|
150
|
+
await emptyDir(iconsDestDir);
|
|
151
151
|
console.log('Copying all files from icons to docs/icons');
|
|
152
|
-
await
|
|
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
|
-
|
|
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
|
|
25
|
+
const server = new Server();
|
|
29
26
|
|
|
30
|
-
if(src){
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
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
|
-
|
|
39
|
-
|
|
40
|
-
|
|
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
|
-
|
|
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
|
+
}
|
package/src/components/Dialog.js
CHANGED
|
@@ -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){
|
package/src/components/Split.js
CHANGED
|
@@ -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(
|
|
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.
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
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
|
}
|