@kodaris/krubble-app-components 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.
- package/LICENSE +14 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +7 -0
- package/dist/index.js.map +1 -0
- package/dist/krubble-app.bundled.js +3572 -0
- package/dist/krubble-app.bundled.js.map +1 -0
- package/dist/krubble-app.bundled.min.js +2005 -0
- package/dist/krubble-app.bundled.min.js.map +1 -0
- package/dist/krubble-app.umd.js +3577 -0
- package/dist/krubble-app.umd.js.map +1 -0
- package/dist/krubble-app.umd.min.js +2005 -0
- package/dist/krubble-app.umd.min.js.map +1 -0
- package/dist/scaffold/nav-item-edit.d.ts +34 -0
- package/dist/scaffold/nav-item-edit.d.ts.map +1 -0
- package/dist/scaffold/nav-item-edit.js +216 -0
- package/dist/scaffold/nav-item-edit.js.map +1 -0
- package/dist/scaffold.d.ts +368 -0
- package/dist/scaffold.d.ts.map +1 -0
- package/dist/scaffold.js +1853 -0
- package/dist/scaffold.js.map +1 -0
- package/dist/screen-detail.d.ts +34 -0
- package/dist/screen-detail.d.ts.map +1 -0
- package/dist/screen-detail.js +104 -0
- package/dist/screen-detail.js.map +1 -0
- package/dist/screen-nav.d.ts +93 -0
- package/dist/screen-nav.d.ts.map +1 -0
- package/dist/screen-nav.js +297 -0
- package/dist/screen-nav.js.map +1 -0
- package/dist/shell.d.ts +104 -0
- package/dist/shell.d.ts.map +1 -0
- package/dist/shell.js +860 -0
- package/dist/shell.js.map +1 -0
- package/dist/subbar.d.ts +34 -0
- package/dist/subbar.d.ts.map +1 -0
- package/dist/subbar.js +133 -0
- package/dist/subbar.js.map +1 -0
- package/package.json +47 -0
|
@@ -0,0 +1,3572 @@
|
|
|
1
|
+
import { KRContextMenu, KRDialog } from '@kodaris/krubble-components';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @license
|
|
5
|
+
* Copyright 2019 Google LLC
|
|
6
|
+
* SPDX-License-Identifier: BSD-3-Clause
|
|
7
|
+
*/
|
|
8
|
+
const t$3=globalThis,e$5=t$3.ShadowRoot&&(void 0===t$3.ShadyCSS||t$3.ShadyCSS.nativeShadow)&&"adoptedStyleSheets"in Document.prototype&&"replace"in CSSStyleSheet.prototype,s$2=Symbol(),o$5=new WeakMap;let n$3 = class n{constructor(t,e,o){if(this._$cssResult$=true,o!==s$2)throw Error("CSSResult is not constructable. Use `unsafeCSS` or `css` instead.");this.cssText=t,this.t=e;}get styleSheet(){let t=this.o;const s=this.t;if(e$5&&void 0===t){const e=void 0!==s&&1===s.length;e&&(t=o$5.get(s)),void 0===t&&((this.o=t=new CSSStyleSheet).replaceSync(this.cssText),e&&o$5.set(s,t));}return t}toString(){return this.cssText}};const r$4=t=>new n$3("string"==typeof t?t:t+"",void 0,s$2),i$4=(t,...e)=>{const o=1===t.length?t[0]:e.reduce(((e,s,o)=>e+(t=>{if(true===t._$cssResult$)return t.cssText;if("number"==typeof t)return t;throw Error("Value passed to 'css' function must be a 'css' function result: "+t+". Use 'unsafeCSS' to pass non-literal values, but take care to ensure page security.")})(s)+t[o+1]),t[0]);return new n$3(o,t,s$2)},S$1=(s,o)=>{if(e$5)s.adoptedStyleSheets=o.map((t=>t instanceof CSSStyleSheet?t:t.styleSheet));else for(const e of o){const o=document.createElement("style"),n=t$3.litNonce;void 0!==n&&o.setAttribute("nonce",n),o.textContent=e.cssText,s.appendChild(o);}},c$2=e$5?t=>t:t=>t instanceof CSSStyleSheet?(t=>{let e="";for(const s of t.cssRules)e+=s.cssText;return r$4(e)})(t):t;
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* @license
|
|
12
|
+
* Copyright 2017 Google LLC
|
|
13
|
+
* SPDX-License-Identifier: BSD-3-Clause
|
|
14
|
+
*/const{is:i$3,defineProperty:e$4,getOwnPropertyDescriptor:h$1,getOwnPropertyNames:r$3,getOwnPropertySymbols:o$4,getPrototypeOf:n$2}=Object,a$1=globalThis,c$1=a$1.trustedTypes,l$1=c$1?c$1.emptyScript:"",p$1=a$1.reactiveElementPolyfillSupport,d$1=(t,s)=>t,u$1={toAttribute(t,s){switch(s){case Boolean:t=t?l$1:null;break;case Object:case Array:t=null==t?t:JSON.stringify(t);}return t},fromAttribute(t,s){let i=t;switch(s){case Boolean:i=null!==t;break;case Number:i=null===t?null:Number(t);break;case Object:case Array:try{i=JSON.parse(t);}catch(t){i=null;}}return i}},f$1=(t,s)=>!i$3(t,s),b={attribute:true,type:String,converter:u$1,reflect:false,useDefault:false,hasChanged:f$1};Symbol.metadata??=Symbol("metadata"),a$1.litPropertyMetadata??=new WeakMap;let y$1 = class y extends HTMLElement{static addInitializer(t){this._$Ei(),(this.l??=[]).push(t);}static get observedAttributes(){return this.finalize(),this._$Eh&&[...this._$Eh.keys()]}static createProperty(t,s=b){if(s.state&&(s.attribute=false),this._$Ei(),this.prototype.hasOwnProperty(t)&&((s=Object.create(s)).wrapped=true),this.elementProperties.set(t,s),!s.noAccessor){const i=Symbol(),h=this.getPropertyDescriptor(t,i,s);void 0!==h&&e$4(this.prototype,t,h);}}static getPropertyDescriptor(t,s,i){const{get:e,set:r}=h$1(this.prototype,t)??{get(){return this[s]},set(t){this[s]=t;}};return {get:e,set(s){const h=e?.call(this);r?.call(this,s),this.requestUpdate(t,h,i);},configurable:true,enumerable:true}}static getPropertyOptions(t){return this.elementProperties.get(t)??b}static _$Ei(){if(this.hasOwnProperty(d$1("elementProperties")))return;const t=n$2(this);t.finalize(),void 0!==t.l&&(this.l=[...t.l]),this.elementProperties=new Map(t.elementProperties);}static finalize(){if(this.hasOwnProperty(d$1("finalized")))return;if(this.finalized=true,this._$Ei(),this.hasOwnProperty(d$1("properties"))){const t=this.properties,s=[...r$3(t),...o$4(t)];for(const i of s)this.createProperty(i,t[i]);}const t=this[Symbol.metadata];if(null!==t){const s=litPropertyMetadata.get(t);if(void 0!==s)for(const[t,i]of s)this.elementProperties.set(t,i);}this._$Eh=new Map;for(const[t,s]of this.elementProperties){const i=this._$Eu(t,s);void 0!==i&&this._$Eh.set(i,t);}this.elementStyles=this.finalizeStyles(this.styles);}static finalizeStyles(s){const i=[];if(Array.isArray(s)){const e=new Set(s.flat(1/0).reverse());for(const s of e)i.unshift(c$2(s));}else void 0!==s&&i.push(c$2(s));return i}static _$Eu(t,s){const i=s.attribute;return false===i?void 0:"string"==typeof i?i:"string"==typeof t?t.toLowerCase():void 0}constructor(){super(),this._$Ep=void 0,this.isUpdatePending=false,this.hasUpdated=false,this._$Em=null,this._$Ev();}_$Ev(){this._$ES=new Promise((t=>this.enableUpdating=t)),this._$AL=new Map,this._$E_(),this.requestUpdate(),this.constructor.l?.forEach((t=>t(this)));}addController(t){(this._$EO??=new Set).add(t),void 0!==this.renderRoot&&this.isConnected&&t.hostConnected?.();}removeController(t){this._$EO?.delete(t);}_$E_(){const t=new Map,s=this.constructor.elementProperties;for(const i of s.keys())this.hasOwnProperty(i)&&(t.set(i,this[i]),delete this[i]);t.size>0&&(this._$Ep=t);}createRenderRoot(){const t=this.shadowRoot??this.attachShadow(this.constructor.shadowRootOptions);return S$1(t,this.constructor.elementStyles),t}connectedCallback(){this.renderRoot??=this.createRenderRoot(),this.enableUpdating(true),this._$EO?.forEach((t=>t.hostConnected?.()));}enableUpdating(t){}disconnectedCallback(){this._$EO?.forEach((t=>t.hostDisconnected?.()));}attributeChangedCallback(t,s,i){this._$AK(t,i);}_$ET(t,s){const i=this.constructor.elementProperties.get(t),e=this.constructor._$Eu(t,i);if(void 0!==e&&true===i.reflect){const h=(void 0!==i.converter?.toAttribute?i.converter:u$1).toAttribute(s,i.type);this._$Em=t,null==h?this.removeAttribute(e):this.setAttribute(e,h),this._$Em=null;}}_$AK(t,s){const i=this.constructor,e=i._$Eh.get(t);if(void 0!==e&&this._$Em!==e){const t=i.getPropertyOptions(e),h="function"==typeof t.converter?{fromAttribute:t.converter}:void 0!==t.converter?.fromAttribute?t.converter:u$1;this._$Em=e;const r=h.fromAttribute(s,t.type);this[e]=r??this._$Ej?.get(e)??r,this._$Em=null;}}requestUpdate(t,s,i){if(void 0!==t){const e=this.constructor,h=this[t];if(i??=e.getPropertyOptions(t),!((i.hasChanged??f$1)(h,s)||i.useDefault&&i.reflect&&h===this._$Ej?.get(t)&&!this.hasAttribute(e._$Eu(t,i))))return;this.C(t,s,i);} false===this.isUpdatePending&&(this._$ES=this._$EP());}C(t,s,{useDefault:i,reflect:e,wrapped:h},r){i&&!(this._$Ej??=new Map).has(t)&&(this._$Ej.set(t,r??s??this[t]),true!==h||void 0!==r)||(this._$AL.has(t)||(this.hasUpdated||i||(s=void 0),this._$AL.set(t,s)),true===e&&this._$Em!==t&&(this._$Eq??=new Set).add(t));}async _$EP(){this.isUpdatePending=true;try{await this._$ES;}catch(t){Promise.reject(t);}const t=this.scheduleUpdate();return null!=t&&await t,!this.isUpdatePending}scheduleUpdate(){return this.performUpdate()}performUpdate(){if(!this.isUpdatePending)return;if(!this.hasUpdated){if(this.renderRoot??=this.createRenderRoot(),this._$Ep){for(const[t,s]of this._$Ep)this[t]=s;this._$Ep=void 0;}const t=this.constructor.elementProperties;if(t.size>0)for(const[s,i]of t){const{wrapped:t}=i,e=this[s];true!==t||this._$AL.has(s)||void 0===e||this.C(s,void 0,i,e);}}let t=false;const s=this._$AL;try{t=this.shouldUpdate(s),t?(this.willUpdate(s),this._$EO?.forEach((t=>t.hostUpdate?.())),this.update(s)):this._$EM();}catch(s){throw t=false,this._$EM(),s}t&&this._$AE(s);}willUpdate(t){}_$AE(t){this._$EO?.forEach((t=>t.hostUpdated?.())),this.hasUpdated||(this.hasUpdated=true,this.firstUpdated(t)),this.updated(t);}_$EM(){this._$AL=new Map,this.isUpdatePending=false;}get updateComplete(){return this.getUpdateComplete()}getUpdateComplete(){return this._$ES}shouldUpdate(t){return true}update(t){this._$Eq&&=this._$Eq.forEach((t=>this._$ET(t,this[t]))),this._$EM();}updated(t){}firstUpdated(t){}};y$1.elementStyles=[],y$1.shadowRootOptions={mode:"open"},y$1[d$1("elementProperties")]=new Map,y$1[d$1("finalized")]=new Map,p$1?.({ReactiveElement:y$1}),(a$1.reactiveElementVersions??=[]).push("2.1.1");
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* @license
|
|
18
|
+
* Copyright 2017 Google LLC
|
|
19
|
+
* SPDX-License-Identifier: BSD-3-Clause
|
|
20
|
+
*/
|
|
21
|
+
const t$2=globalThis,i$2=t$2.trustedTypes,s$1=i$2?i$2.createPolicy("lit-html",{createHTML:t=>t}):void 0,e$3="$lit$",h=`lit$${Math.random().toFixed(9).slice(2)}$`,o$3="?"+h,n$1=`<${o$3}>`,r$2=document,l=()=>r$2.createComment(""),c=t=>null===t||"object"!=typeof t&&"function"!=typeof t,a=Array.isArray,u=t=>a(t)||"function"==typeof t?.[Symbol.iterator],d="[ \t\n\f\r]",f=/<(?:(!--|\/[^a-zA-Z])|(\/?[a-zA-Z][^>\s]*)|(\/?$))/g,v=/-->/g,_=/>/g,m=RegExp(`>|${d}(?:([^\\s"'>=/]+)(${d}*=${d}*(?:[^ \t\n\f\r"'\`<>=]|("|')|))|$)`,"g"),p=/'/g,g=/"/g,$=/^(?:script|style|textarea|title)$/i,y=t=>(i,...s)=>({_$litType$:t,strings:i,values:s}),x=y(1),T=Symbol.for("lit-noChange"),E=Symbol.for("lit-nothing"),A=new WeakMap,C=r$2.createTreeWalker(r$2,129);function P(t,i){if(!a(t)||!t.hasOwnProperty("raw"))throw Error("invalid template strings array");return void 0!==s$1?s$1.createHTML(i):i}const V=(t,i)=>{const s=t.length-1,o=[];let r,l=2===i?"<svg>":3===i?"<math>":"",c=f;for(let i=0;i<s;i++){const s=t[i];let a,u,d=-1,y=0;for(;y<s.length&&(c.lastIndex=y,u=c.exec(s),null!==u);)y=c.lastIndex,c===f?"!--"===u[1]?c=v:void 0!==u[1]?c=_:void 0!==u[2]?($.test(u[2])&&(r=RegExp("</"+u[2],"g")),c=m):void 0!==u[3]&&(c=m):c===m?">"===u[0]?(c=r??f,d=-1):void 0===u[1]?d=-2:(d=c.lastIndex-u[2].length,a=u[1],c=void 0===u[3]?m:'"'===u[3]?g:p):c===g||c===p?c=m:c===v||c===_?c=f:(c=m,r=void 0);const x=c===m&&t[i+1].startsWith("/>")?" ":"";l+=c===f?s+n$1:d>=0?(o.push(a),s.slice(0,d)+e$3+s.slice(d)+h+x):s+h+(-2===d?i:x);}return [P(t,l+(t[s]||"<?>")+(2===i?"</svg>":3===i?"</math>":"")),o]};class N{constructor({strings:t,_$litType$:s},n){let r;this.parts=[];let c=0,a=0;const u=t.length-1,d=this.parts,[f,v]=V(t,s);if(this.el=N.createElement(f,n),C.currentNode=this.el.content,2===s||3===s){const t=this.el.content.firstChild;t.replaceWith(...t.childNodes);}for(;null!==(r=C.nextNode())&&d.length<u;){if(1===r.nodeType){if(r.hasAttributes())for(const t of r.getAttributeNames())if(t.endsWith(e$3)){const i=v[a++],s=r.getAttribute(t).split(h),e=/([.?@])?(.*)/.exec(i);d.push({type:1,index:c,name:e[2],strings:s,ctor:"."===e[1]?H:"?"===e[1]?I:"@"===e[1]?L:k}),r.removeAttribute(t);}else t.startsWith(h)&&(d.push({type:6,index:c}),r.removeAttribute(t));if($.test(r.tagName)){const t=r.textContent.split(h),s=t.length-1;if(s>0){r.textContent=i$2?i$2.emptyScript:"";for(let i=0;i<s;i++)r.append(t[i],l()),C.nextNode(),d.push({type:2,index:++c});r.append(t[s],l());}}}else if(8===r.nodeType)if(r.data===o$3)d.push({type:2,index:c});else {let t=-1;for(;-1!==(t=r.data.indexOf(h,t+1));)d.push({type:7,index:c}),t+=h.length-1;}c++;}}static createElement(t,i){const s=r$2.createElement("template");return s.innerHTML=t,s}}function S(t,i,s=t,e){if(i===T)return i;let h=void 0!==e?s._$Co?.[e]:s._$Cl;const o=c(i)?void 0:i._$litDirective$;return h?.constructor!==o&&(h?._$AO?.(false),void 0===o?h=void 0:(h=new o(t),h._$AT(t,s,e)),void 0!==e?(s._$Co??=[])[e]=h:s._$Cl=h),void 0!==h&&(i=S(t,h._$AS(t,i.values),h,e)),i}class M{constructor(t,i){this._$AV=[],this._$AN=void 0,this._$AD=t,this._$AM=i;}get parentNode(){return this._$AM.parentNode}get _$AU(){return this._$AM._$AU}u(t){const{el:{content:i},parts:s}=this._$AD,e=(t?.creationScope??r$2).importNode(i,true);C.currentNode=e;let h=C.nextNode(),o=0,n=0,l=s[0];for(;void 0!==l;){if(o===l.index){let i;2===l.type?i=new R(h,h.nextSibling,this,t):1===l.type?i=new l.ctor(h,l.name,l.strings,this,t):6===l.type&&(i=new z(h,this,t)),this._$AV.push(i),l=s[++n];}o!==l?.index&&(h=C.nextNode(),o++);}return C.currentNode=r$2,e}p(t){let i=0;for(const s of this._$AV) void 0!==s&&(void 0!==s.strings?(s._$AI(t,s,i),i+=s.strings.length-2):s._$AI(t[i])),i++;}}class R{get _$AU(){return this._$AM?._$AU??this._$Cv}constructor(t,i,s,e){this.type=2,this._$AH=E,this._$AN=void 0,this._$AA=t,this._$AB=i,this._$AM=s,this.options=e,this._$Cv=e?.isConnected??true;}get parentNode(){let t=this._$AA.parentNode;const i=this._$AM;return void 0!==i&&11===t?.nodeType&&(t=i.parentNode),t}get startNode(){return this._$AA}get endNode(){return this._$AB}_$AI(t,i=this){t=S(this,t,i),c(t)?t===E||null==t||""===t?(this._$AH!==E&&this._$AR(),this._$AH=E):t!==this._$AH&&t!==T&&this._(t):void 0!==t._$litType$?this.$(t):void 0!==t.nodeType?this.T(t):u(t)?this.k(t):this._(t);}O(t){return this._$AA.parentNode.insertBefore(t,this._$AB)}T(t){this._$AH!==t&&(this._$AR(),this._$AH=this.O(t));}_(t){this._$AH!==E&&c(this._$AH)?this._$AA.nextSibling.data=t:this.T(r$2.createTextNode(t)),this._$AH=t;}$(t){const{values:i,_$litType$:s}=t,e="number"==typeof s?this._$AC(t):(void 0===s.el&&(s.el=N.createElement(P(s.h,s.h[0]),this.options)),s);if(this._$AH?._$AD===e)this._$AH.p(i);else {const t=new M(e,this),s=t.u(this.options);t.p(i),this.T(s),this._$AH=t;}}_$AC(t){let i=A.get(t.strings);return void 0===i&&A.set(t.strings,i=new N(t)),i}k(t){a(this._$AH)||(this._$AH=[],this._$AR());const i=this._$AH;let s,e=0;for(const h of t)e===i.length?i.push(s=new R(this.O(l()),this.O(l()),this,this.options)):s=i[e],s._$AI(h),e++;e<i.length&&(this._$AR(s&&s._$AB.nextSibling,e),i.length=e);}_$AR(t=this._$AA.nextSibling,i){for(this._$AP?.(false,true,i);t!==this._$AB;){const i=t.nextSibling;t.remove(),t=i;}}setConnected(t){ void 0===this._$AM&&(this._$Cv=t,this._$AP?.(t));}}class k{get tagName(){return this.element.tagName}get _$AU(){return this._$AM._$AU}constructor(t,i,s,e,h){this.type=1,this._$AH=E,this._$AN=void 0,this.element=t,this.name=i,this._$AM=e,this.options=h,s.length>2||""!==s[0]||""!==s[1]?(this._$AH=Array(s.length-1).fill(new String),this.strings=s):this._$AH=E;}_$AI(t,i=this,s,e){const h=this.strings;let o=false;if(void 0===h)t=S(this,t,i,0),o=!c(t)||t!==this._$AH&&t!==T,o&&(this._$AH=t);else {const e=t;let n,r;for(t=h[0],n=0;n<h.length-1;n++)r=S(this,e[s+n],i,n),r===T&&(r=this._$AH[n]),o||=!c(r)||r!==this._$AH[n],r===E?t=E:t!==E&&(t+=(r??"")+h[n+1]),this._$AH[n]=r;}o&&!e&&this.j(t);}j(t){t===E?this.element.removeAttribute(this.name):this.element.setAttribute(this.name,t??"");}}class H extends k{constructor(){super(...arguments),this.type=3;}j(t){this.element[this.name]=t===E?void 0:t;}}class I extends k{constructor(){super(...arguments),this.type=4;}j(t){this.element.toggleAttribute(this.name,!!t&&t!==E);}}class L extends k{constructor(t,i,s,e,h){super(t,i,s,e,h),this.type=5;}_$AI(t,i=this){if((t=S(this,t,i,0)??E)===T)return;const s=this._$AH,e=t===E&&s!==E||t.capture!==s.capture||t.once!==s.once||t.passive!==s.passive,h=t!==E&&(s===E||e);e&&this.element.removeEventListener(this.name,this,s),h&&this.element.addEventListener(this.name,this,t),this._$AH=t;}handleEvent(t){"function"==typeof this._$AH?this._$AH.call(this.options?.host??this.element,t):this._$AH.handleEvent(t);}}class z{constructor(t,i,s){this.element=t,this.type=6,this._$AN=void 0,this._$AM=i,this.options=s;}get _$AU(){return this._$AM._$AU}_$AI(t){S(this,t);}}const j=t$2.litHtmlPolyfillSupport;j?.(N,R),(t$2.litHtmlVersions??=[]).push("3.3.1");const B=(t,i,s)=>{const e=s?.renderBefore??i;let h=e._$litPart$;if(void 0===h){const t=s?.renderBefore??null;e._$litPart$=h=new R(i.insertBefore(l(),t),t,void 0,s??{});}return h._$AI(t),h};
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* @license
|
|
25
|
+
* Copyright 2017 Google LLC
|
|
26
|
+
* SPDX-License-Identifier: BSD-3-Clause
|
|
27
|
+
*/const s=globalThis;let i$1 = class i extends y$1{constructor(){super(...arguments),this.renderOptions={host:this},this._$Do=void 0;}createRenderRoot(){const t=super.createRenderRoot();return this.renderOptions.renderBefore??=t.firstChild,t}update(t){const r=this.render();this.hasUpdated||(this.renderOptions.isConnected=this.isConnected),super.update(t),this._$Do=B(r,this.renderRoot,this.renderOptions);}connectedCallback(){super.connectedCallback(),this._$Do?.setConnected(true);}disconnectedCallback(){super.disconnectedCallback(),this._$Do?.setConnected(false);}render(){return T}};i$1._$litElement$=true,i$1["finalized"]=true,s.litElementHydrateSupport?.({LitElement:i$1});const o$2=s.litElementPolyfillSupport;o$2?.({LitElement:i$1});(s.litElementVersions??=[]).push("4.2.1");
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* @license
|
|
31
|
+
* Copyright 2017 Google LLC
|
|
32
|
+
* SPDX-License-Identifier: BSD-3-Clause
|
|
33
|
+
*/
|
|
34
|
+
const t$1=t=>(e,o)=>{ void 0!==o?o.addInitializer((()=>{customElements.define(t,e);})):customElements.define(t,e);};
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* @license
|
|
38
|
+
* Copyright 2017 Google LLC
|
|
39
|
+
* SPDX-License-Identifier: BSD-3-Clause
|
|
40
|
+
*/const o$1={attribute:true,type:String,converter:u$1,reflect:false,hasChanged:f$1},r$1=(t=o$1,e,r)=>{const{kind:n,metadata:i}=r;let s=globalThis.litPropertyMetadata.get(i);if(void 0===s&&globalThis.litPropertyMetadata.set(i,s=new Map),"setter"===n&&((t=Object.create(t)).wrapped=true),s.set(r.name,t),"accessor"===n){const{name:o}=r;return {set(r){const n=e.get.call(this);e.set.call(this,r),this.requestUpdate(o,n,t);},init(e){return void 0!==e&&this.C(o,void 0,t,e),e}}}if("setter"===n){const{name:o}=r;return function(r){const n=this[o];e.call(this,r),this.requestUpdate(o,n,t);}}throw Error("Unsupported decorator location: "+n)};function n(t){return (e,o)=>"object"==typeof o?r$1(t,e,o):((t,e,o)=>{const r=e.hasOwnProperty(o);return e.constructor.createProperty(o,t),r?Object.getOwnPropertyDescriptor(e,o):void 0})(t,e,o)}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* @license
|
|
44
|
+
* Copyright 2017 Google LLC
|
|
45
|
+
* SPDX-License-Identifier: BSD-3-Clause
|
|
46
|
+
*/function r(r){return n({...r,state:true,attribute:false})}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* @license
|
|
50
|
+
* Copyright 2017 Google LLC
|
|
51
|
+
* SPDX-License-Identifier: BSD-3-Clause
|
|
52
|
+
*/
|
|
53
|
+
const t={ATTRIBUTE:1,CHILD:2},e$2=t=>(...e)=>({_$litDirective$:t,values:e});class i{constructor(t){}get _$AU(){return this._$AM._$AU}_$AT(t,e,i){this._$Ct=t,this._$AM=e,this._$Ci=i;}_$AS(t,e){return this.update(t,e)}update(t,e){return this.render(...e)}}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* @license
|
|
57
|
+
* Copyright 2018 Google LLC
|
|
58
|
+
* SPDX-License-Identifier: BSD-3-Clause
|
|
59
|
+
*/const e$1=e$2(class extends i{constructor(t$1){if(super(t$1),t$1.type!==t.ATTRIBUTE||"class"!==t$1.name||t$1.strings?.length>2)throw Error("`classMap()` can only be used in the `class` attribute and must be the only part in the attribute.")}render(t){return " "+Object.keys(t).filter((s=>t[s])).join(" ")+" "}update(s,[i]){if(void 0===this.st){this.st=new Set,void 0!==s.strings&&(this.nt=new Set(s.strings.join(" ").split(/\s/).filter((t=>""!==t))));for(const t in i)i[t]&&!this.nt?.has(t)&&this.st.add(t);return this.render(i)}const r=s.element.classList;for(const t of this.st)t in i||(r.remove(t),this.st.delete(t));for(const t in i){const s=!!i[t];s===this.st.has(t)||this.nt?.has(t)||(s?(r.add(t),this.st.add(t)):(r.remove(t),this.st.delete(t)));}return T}});
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* @license
|
|
63
|
+
* Copyright 2017 Google LLC
|
|
64
|
+
* SPDX-License-Identifier: BSD-3-Clause
|
|
65
|
+
*/class e extends i{constructor(i){if(super(i),this.it=E,i.type!==t.CHILD)throw Error(this.constructor.directiveName+"() can only be used in child bindings")}render(r){if(r===E||null==r)return this._t=void 0,this.it=r;if(r===T)return r;if("string"!=typeof r)throw Error(this.constructor.directiveName+"() called with a non-string value");if(r===this.it)return this._t;this.it=r;const s=[r];return s.raw=s,this._t={_$litType$:this.constructor.resultType,strings:s,values:[]}}}e.directiveName="unsafeHTML",e.resultType=1;const o=e$2(e);
|
|
66
|
+
|
|
67
|
+
var __decorate$5 = (undefined && undefined.__decorate) || function (decorators, target, key, desc) {
|
|
68
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
69
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
70
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
71
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
72
|
+
};
|
|
73
|
+
/**
|
|
74
|
+
* Global shell component with app bar for app switching.
|
|
75
|
+
*
|
|
76
|
+
* The shell provides a horizontal app bar across the top of the viewport containing:
|
|
77
|
+
* - Logo/home link on the left
|
|
78
|
+
* - App switcher icons in the middle
|
|
79
|
+
* - User avatar/menu on the right
|
|
80
|
+
*
|
|
81
|
+
* This allows individual apps (using kr-scaffold) to utilize the full width
|
|
82
|
+
* of the browser window while maintaining consistent global navigation.
|
|
83
|
+
*
|
|
84
|
+
* @slot - The main content (typically kr-scaffold with the app)
|
|
85
|
+
*
|
|
86
|
+
* @property {string} logo - URL for the logo image
|
|
87
|
+
* @property {KRApp[]} apps - Available applications for the switcher
|
|
88
|
+
* @property {string} activeAppId - ID of the currently active app
|
|
89
|
+
* @property {KRShellUser} user - User profile data
|
|
90
|
+
* @property {KRShellMenuItem[]} menuItems - Custom menu items for user menu
|
|
91
|
+
*
|
|
92
|
+
* @fires app-click - When an app icon is clicked. Detail: { app: KRApp }
|
|
93
|
+
* @fires menu-item-click - When a menu item is clicked. Detail: { item: KRShellMenuItem }
|
|
94
|
+
* @fires logout - When logout is clicked
|
|
95
|
+
*/
|
|
96
|
+
let KRShell = class KRShell extends i$1 {
|
|
97
|
+
constructor() {
|
|
98
|
+
super(...arguments);
|
|
99
|
+
this.logo = 'https://s3.amazonaws.com/kodariscom/kodariscom/content/website/kodaris-logo-icon-light-53.png';
|
|
100
|
+
this.apps = [];
|
|
101
|
+
this.activeAppId = '';
|
|
102
|
+
this.user = null;
|
|
103
|
+
this.menuItems = [];
|
|
104
|
+
this.isUserMenuOpen = false;
|
|
105
|
+
this.isAppsMenuOpen = false;
|
|
106
|
+
this.pendingRequests = 0;
|
|
107
|
+
this.originalFetch = null;
|
|
108
|
+
this.originalXhrOpen = null;
|
|
109
|
+
// Default icons
|
|
110
|
+
this.defaultLogoutIcon = '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor"><path d="M17 7l-1.41 1.41L18.17 11H8v2h10.17l-2.58 2.58L17 17l5-5zM4 5h8V3H4c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h8v-2H4V5z"/></svg>';
|
|
111
|
+
this.appsGridIcon = '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor"><path d="M4 8h4V4H4v4zm6 12h4v-4h-4v4zm-6 0h4v-4H4v4zm0-6h4v-4H4v4zm6 0h4v-4h-4v4zm6-10v4h4V4h-4zm-6 4h4V4h-4v4zm6 6h4v-4h-4v4zm0 6h4v-4h-4v4z"/></svg>';
|
|
112
|
+
this.closeIcon = '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor"><path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z"/></svg>';
|
|
113
|
+
}
|
|
114
|
+
connectedCallback() {
|
|
115
|
+
super.connectedCallback();
|
|
116
|
+
this.installFetchInterceptor();
|
|
117
|
+
}
|
|
118
|
+
disconnectedCallback() {
|
|
119
|
+
super.disconnectedCallback();
|
|
120
|
+
this.uninstallFetchInterceptor();
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* Installs global interceptors for fetch and XMLHttpRequest to track pending HTTP requests.
|
|
124
|
+
* Updates `pendingRequests` state when requests start/complete.
|
|
125
|
+
*/
|
|
126
|
+
installFetchInterceptor() {
|
|
127
|
+
if (this.originalFetch)
|
|
128
|
+
return;
|
|
129
|
+
const shell = this;
|
|
130
|
+
// Intercept fetch
|
|
131
|
+
this.originalFetch = window.fetch.bind(window);
|
|
132
|
+
const originalFetch = this.originalFetch;
|
|
133
|
+
window.fetch = function (...args) {
|
|
134
|
+
shell.pendingRequests++;
|
|
135
|
+
return originalFetch(...args).finally(() => {
|
|
136
|
+
shell.pendingRequests--;
|
|
137
|
+
});
|
|
138
|
+
};
|
|
139
|
+
// Intercept XMLHttpRequest
|
|
140
|
+
this.originalXhrOpen = XMLHttpRequest.prototype.open;
|
|
141
|
+
const originalXhrOpen = this.originalXhrOpen;
|
|
142
|
+
XMLHttpRequest.prototype.open = function (method, url, async = true, username, password) {
|
|
143
|
+
shell.pendingRequests++;
|
|
144
|
+
this.addEventListener('loadend', () => {
|
|
145
|
+
shell.pendingRequests--;
|
|
146
|
+
}, { once: true });
|
|
147
|
+
return originalXhrOpen.call(this, method, url, async, username, password);
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
/**
|
|
151
|
+
* Restores the original fetch and XMLHttpRequest functions.
|
|
152
|
+
*/
|
|
153
|
+
uninstallFetchInterceptor() {
|
|
154
|
+
if (this.originalFetch) {
|
|
155
|
+
window.fetch = this.originalFetch;
|
|
156
|
+
this.originalFetch = null;
|
|
157
|
+
}
|
|
158
|
+
if (this.originalXhrOpen) {
|
|
159
|
+
XMLHttpRequest.prototype.open = this.originalXhrOpen;
|
|
160
|
+
this.originalXhrOpen = null;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
handleLogoClick() {
|
|
164
|
+
this.dispatchEvent(new CustomEvent('logo-click', {
|
|
165
|
+
bubbles: true,
|
|
166
|
+
composed: true,
|
|
167
|
+
}));
|
|
168
|
+
}
|
|
169
|
+
toggleUserMenu() {
|
|
170
|
+
this.isUserMenuOpen = !this.isUserMenuOpen;
|
|
171
|
+
this.isAppsMenuOpen = false;
|
|
172
|
+
}
|
|
173
|
+
closeUserMenu() {
|
|
174
|
+
this.isUserMenuOpen = false;
|
|
175
|
+
}
|
|
176
|
+
toggleAppsMenu() {
|
|
177
|
+
this.isAppsMenuOpen = !this.isAppsMenuOpen;
|
|
178
|
+
this.isUserMenuOpen = false;
|
|
179
|
+
}
|
|
180
|
+
closeAppsMenu() {
|
|
181
|
+
this.isAppsMenuOpen = false;
|
|
182
|
+
}
|
|
183
|
+
handleMenuItemClick(item) {
|
|
184
|
+
this.closeUserMenu();
|
|
185
|
+
this.dispatchEvent(new CustomEvent('menu-item-click', {
|
|
186
|
+
detail: { item },
|
|
187
|
+
bubbles: true,
|
|
188
|
+
composed: true,
|
|
189
|
+
}));
|
|
190
|
+
}
|
|
191
|
+
handleLogout() {
|
|
192
|
+
this.closeUserMenu();
|
|
193
|
+
this.dispatchEvent(new CustomEvent('logout', {
|
|
194
|
+
bubbles: true,
|
|
195
|
+
composed: true,
|
|
196
|
+
}));
|
|
197
|
+
}
|
|
198
|
+
renderAppsMenu() {
|
|
199
|
+
return x `
|
|
200
|
+
<div class="appbar-apps">
|
|
201
|
+
${this.isAppsMenuOpen ? x `
|
|
202
|
+
<div class="menu-backdrop" @click=${this.closeAppsMenu}></div>
|
|
203
|
+
` : E}
|
|
204
|
+
|
|
205
|
+
<button
|
|
206
|
+
class=${e$1({
|
|
207
|
+
'appbar-apps-btn': true,
|
|
208
|
+
'appbar-apps-btn--open': this.isAppsMenuOpen,
|
|
209
|
+
})}
|
|
210
|
+
@click=${this.toggleAppsMenu}
|
|
211
|
+
>
|
|
212
|
+
<span class="appbar-apps-btn__icon">${o(this.appsGridIcon)}</span>
|
|
213
|
+
</button>
|
|
214
|
+
|
|
215
|
+
<div class=${e$1({
|
|
216
|
+
'appbar-apps-menu': true,
|
|
217
|
+
'appbar-apps-menu--open': this.isAppsMenuOpen,
|
|
218
|
+
})}>
|
|
219
|
+
<div class="appbar-apps-menu__header">
|
|
220
|
+
<h2 class="appbar-apps-menu__title">Applications</h2>
|
|
221
|
+
<button class="appbar-apps-menu__close" @click=${this.closeAppsMenu}>
|
|
222
|
+
${o(this.closeIcon)}
|
|
223
|
+
</button>
|
|
224
|
+
</div>
|
|
225
|
+
<div class="appbar-apps-menu__content">
|
|
226
|
+
<div class="appbar-apps-menu__list">
|
|
227
|
+
${this.apps.map((app) => x `
|
|
228
|
+
<a
|
|
229
|
+
class="appbar-apps-menu__item"
|
|
230
|
+
href=${app.url}
|
|
231
|
+
@click=${(e) => {
|
|
232
|
+
this.closeAppsMenu();
|
|
233
|
+
const event = new CustomEvent('app-click', {
|
|
234
|
+
detail: { app },
|
|
235
|
+
bubbles: true,
|
|
236
|
+
composed: true,
|
|
237
|
+
cancelable: true,
|
|
238
|
+
});
|
|
239
|
+
this.dispatchEvent(event);
|
|
240
|
+
if (event.defaultPrevented) {
|
|
241
|
+
e.preventDefault();
|
|
242
|
+
}
|
|
243
|
+
}}
|
|
244
|
+
>
|
|
245
|
+
${app.name}
|
|
246
|
+
</a>
|
|
247
|
+
`)}
|
|
248
|
+
</div>
|
|
249
|
+
</div>
|
|
250
|
+
</div>
|
|
251
|
+
</div>
|
|
252
|
+
`;
|
|
253
|
+
}
|
|
254
|
+
renderUserMenu() {
|
|
255
|
+
if (!this.user)
|
|
256
|
+
return E;
|
|
257
|
+
const initials = this.user.name
|
|
258
|
+
.split(' ')
|
|
259
|
+
.map((part) => part[0])
|
|
260
|
+
.join('')
|
|
261
|
+
.toUpperCase()
|
|
262
|
+
.slice(0, 2);
|
|
263
|
+
return x `
|
|
264
|
+
<div class="appbar-user">
|
|
265
|
+
${this.isUserMenuOpen ? x `
|
|
266
|
+
<div class="menu-backdrop" @click=${this.closeUserMenu}></div>
|
|
267
|
+
` : E}
|
|
268
|
+
|
|
269
|
+
<div
|
|
270
|
+
class="appbar-user__avatar"
|
|
271
|
+
@click=${this.toggleUserMenu}
|
|
272
|
+
>
|
|
273
|
+
${this.user.avatar
|
|
274
|
+
? x `<img src=${this.user.avatar} alt=${this.user.name} />`
|
|
275
|
+
: initials}
|
|
276
|
+
</div>
|
|
277
|
+
|
|
278
|
+
<div class=${e$1({
|
|
279
|
+
'appbar-user__menu': true,
|
|
280
|
+
'appbar-user__menu--open': this.isUserMenuOpen,
|
|
281
|
+
})}>
|
|
282
|
+
<div class="appbar-user__header">
|
|
283
|
+
<p class="appbar-user__name">${this.user.name}</p>
|
|
284
|
+
${this.user.email ? x `
|
|
285
|
+
<p class="appbar-user__email">${this.user.email}</p>
|
|
286
|
+
` : E}
|
|
287
|
+
</div>
|
|
288
|
+
|
|
289
|
+
${this.menuItems.map((item) => item.divider
|
|
290
|
+
? x `<div class="appbar-user__menu-divider"></div>`
|
|
291
|
+
: x `
|
|
292
|
+
<button
|
|
293
|
+
class="appbar-user__menu-item"
|
|
294
|
+
@click=${() => this.handleMenuItemClick(item)}
|
|
295
|
+
>
|
|
296
|
+
${item.icon ? x `
|
|
297
|
+
<span class="appbar-user__menu-item__icon">${o(item.icon)}</span>
|
|
298
|
+
` : E}
|
|
299
|
+
${item.label}
|
|
300
|
+
</button>
|
|
301
|
+
`)}
|
|
302
|
+
|
|
303
|
+
${this.menuItems.length > 0 ? x `
|
|
304
|
+
<div class="appbar-user__menu-divider"></div>
|
|
305
|
+
` : E}
|
|
306
|
+
|
|
307
|
+
<button
|
|
308
|
+
class="appbar-user__menu-item appbar-user__menu-item--danger"
|
|
309
|
+
@click=${this.handleLogout}
|
|
310
|
+
>
|
|
311
|
+
<span class="appbar-user__menu-item__icon">${o(this.defaultLogoutIcon)}</span>
|
|
312
|
+
Sign Out
|
|
313
|
+
</button>
|
|
314
|
+
</div>
|
|
315
|
+
</div>
|
|
316
|
+
`;
|
|
317
|
+
}
|
|
318
|
+
render() {
|
|
319
|
+
return x `
|
|
320
|
+
<div class=${e$1({
|
|
321
|
+
'progress': true,
|
|
322
|
+
'progress--loading': this.pendingRequests > 0,
|
|
323
|
+
})}>
|
|
324
|
+
<div class="progress__track"></div>
|
|
325
|
+
<div class="progress__bar progress__bar--primary">
|
|
326
|
+
<span class="progress__bar-inner"></span>
|
|
327
|
+
</div>
|
|
328
|
+
<div class="progress__bar progress__bar--secondary">
|
|
329
|
+
<span class="progress__bar-inner"></span>
|
|
330
|
+
</div>
|
|
331
|
+
</div>
|
|
332
|
+
|
|
333
|
+
<header class="appbar">
|
|
334
|
+
<!-- Logo -->
|
|
335
|
+
${this.logo ? x `
|
|
336
|
+
<div class="appbar-logo" @click=${this.handleLogoClick}>
|
|
337
|
+
<img src=${this.logo} alt="Home" />
|
|
338
|
+
</div>
|
|
339
|
+
<div class="appbar-divider"></div>
|
|
340
|
+
` : E}
|
|
341
|
+
|
|
342
|
+
<!-- App Switcher -->
|
|
343
|
+
${this.renderAppsMenu()}
|
|
344
|
+
|
|
345
|
+
<div class="appbar-spacer"></div>
|
|
346
|
+
|
|
347
|
+
<!-- User -->
|
|
348
|
+
${this.renderUserMenu()}
|
|
349
|
+
</header>
|
|
350
|
+
|
|
351
|
+
<div class="subbar">
|
|
352
|
+
<nav class="breadcrumbs">
|
|
353
|
+
<a href="#">Home</a>
|
|
354
|
+
<span class="breadcrumbs__separator">/</span>
|
|
355
|
+
<a href="#">Companies</a>
|
|
356
|
+
<span class="breadcrumbs__separator">/</span>
|
|
357
|
+
<span>Acme Corp</span>
|
|
358
|
+
</nav>
|
|
359
|
+
</div>
|
|
360
|
+
|
|
361
|
+
<main class="main">
|
|
362
|
+
<slot></slot>
|
|
363
|
+
</main>
|
|
364
|
+
`;
|
|
365
|
+
}
|
|
366
|
+
};
|
|
367
|
+
KRShell.styles = i$4 `
|
|
368
|
+
:host {
|
|
369
|
+
display: flex;
|
|
370
|
+
flex-direction: column;
|
|
371
|
+
height: 100vh;
|
|
372
|
+
width: 100vw;
|
|
373
|
+
--kr-shell-bar-height: 48px;
|
|
374
|
+
--kr-shell-bar-bg: #10172a; /* matches scaffold nav-bg */
|
|
375
|
+
/* --kr-shell-bar-bg: #0a0f1a; */ /* original darker bg */
|
|
376
|
+
--kr-shell-bar-border: rgba(255, 255, 255, 0.08);
|
|
377
|
+
--kr-shell-icon-size: 36px;
|
|
378
|
+
--kr-shell-icon-color: rgba(255, 255, 255, 0.7);
|
|
379
|
+
--kr-shell-icon-color-hover: rgba(255, 255, 255, 1);
|
|
380
|
+
--kr-shell-icon-color-active: #beea4e;
|
|
381
|
+
--kr-shell-icon-bg-hover: rgba(255, 255, 255, 0.08);
|
|
382
|
+
--kr-shell-icon-bg-active: rgba(190, 234, 78, 0.15);
|
|
383
|
+
--kr-shell-tooltip-bg: #1a2332;
|
|
384
|
+
--kr-shell-tooltip-color: #ffffff;
|
|
385
|
+
--kr-shell-menu-bg: #1a2332;
|
|
386
|
+
--kr-shell-menu-border: rgba(255, 255, 255, 0.1);
|
|
387
|
+
--kr-shell-menu-hover: rgba(255, 255, 255, 0.08);
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
*,
|
|
391
|
+
*::before,
|
|
392
|
+
*::after {
|
|
393
|
+
box-sizing: border-box;
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
/* App Bar */
|
|
397
|
+
.appbar {
|
|
398
|
+
height: var(--kr-shell-bar-height);
|
|
399
|
+
background: var(--kr-shell-bar-bg);
|
|
400
|
+
border-bottom: 1px solid var(--kr-shell-bar-border);
|
|
401
|
+
display: flex;
|
|
402
|
+
align-items: center;
|
|
403
|
+
padding: 0 16px;
|
|
404
|
+
flex-shrink: 0;
|
|
405
|
+
z-index: 100;
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
/* Logo */
|
|
409
|
+
.appbar-logo {
|
|
410
|
+
cursor: pointer;
|
|
411
|
+
margin-right: 8px;
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
.appbar-logo img {
|
|
415
|
+
display: block;
|
|
416
|
+
max-width: 24px;
|
|
417
|
+
max-height: 24px;
|
|
418
|
+
object-fit: contain;
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
/* Divider */
|
|
422
|
+
.appbar-divider {
|
|
423
|
+
width: 1px;
|
|
424
|
+
height: 24px;
|
|
425
|
+
background: #ffffff4f;
|
|
426
|
+
margin: 0 10px;
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
/* Apps Button */
|
|
430
|
+
.appbar-apps {
|
|
431
|
+
position: relative;
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
.appbar-apps-btn {
|
|
435
|
+
width: var(--kr-shell-icon-size);
|
|
436
|
+
height: var(--kr-shell-icon-size);
|
|
437
|
+
display: flex;
|
|
438
|
+
align-items: center;
|
|
439
|
+
justify-content: center;
|
|
440
|
+
border-radius: 6px;
|
|
441
|
+
cursor: pointer;
|
|
442
|
+
color: var(--kr-shell-icon-color);
|
|
443
|
+
background: transparent;
|
|
444
|
+
border: none;
|
|
445
|
+
padding: 0;
|
|
446
|
+
transition: all 0.15s ease;
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
.appbar-apps-btn:hover {
|
|
450
|
+
color: var(--kr-shell-icon-color-hover);
|
|
451
|
+
background: var(--kr-shell-icon-bg-hover);
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
.appbar-apps-btn--open {
|
|
455
|
+
color: var(--kr-shell-icon-color-hover);
|
|
456
|
+
background: var(--kr-shell-icon-bg-hover);
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
.appbar-apps-btn__icon {
|
|
460
|
+
width: 24px;
|
|
461
|
+
height: 24px;
|
|
462
|
+
display: flex;
|
|
463
|
+
align-items: center;
|
|
464
|
+
justify-content: center;
|
|
465
|
+
color: #ffffff;
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
.appbar-apps-btn__icon svg {
|
|
469
|
+
width: 100%;
|
|
470
|
+
height: 100%;
|
|
471
|
+
fill: currentColor;
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
/* Apps Megamenu */
|
|
475
|
+
.appbar-apps-menu {
|
|
476
|
+
position: absolute;
|
|
477
|
+
left: 0;
|
|
478
|
+
top: 100%;
|
|
479
|
+
margin-top: 6px;
|
|
480
|
+
min-width: 320px;
|
|
481
|
+
min-height: 90vh;
|
|
482
|
+
max-height: calc(100vh - var(--kr-shell-bar-height));
|
|
483
|
+
background: #10172a;
|
|
484
|
+
border: 1px solid var(--kr-shell-menu-border);
|
|
485
|
+
border-top: none;
|
|
486
|
+
border-radius: 0 0 8px 8px;
|
|
487
|
+
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.4);
|
|
488
|
+
z-index: 1000;
|
|
489
|
+
opacity: 0;
|
|
490
|
+
visibility: hidden;
|
|
491
|
+
transform: translateY(-10px);
|
|
492
|
+
transition: all 0.15s ease;
|
|
493
|
+
overflow-y: auto;
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
.appbar-apps-menu--open {
|
|
497
|
+
opacity: 1;
|
|
498
|
+
visibility: visible;
|
|
499
|
+
transform: translateY(0);
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
.appbar-apps-menu__header {
|
|
503
|
+
display: flex;
|
|
504
|
+
align-items: center;
|
|
505
|
+
justify-content: space-between;
|
|
506
|
+
padding: 16px 16px 16px 24px;
|
|
507
|
+
border-bottom: 1px solid var(--kr-shell-menu-border);
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
.appbar-apps-menu__title {
|
|
511
|
+
font-size: 16px;
|
|
512
|
+
font-weight: 500;
|
|
513
|
+
color: #ffffff;
|
|
514
|
+
margin: 0;
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
.appbar-apps-menu__close {
|
|
518
|
+
width: 32px;
|
|
519
|
+
height: 32px;
|
|
520
|
+
display: flex;
|
|
521
|
+
align-items: center;
|
|
522
|
+
justify-content: center;
|
|
523
|
+
background: none;
|
|
524
|
+
border: none;
|
|
525
|
+
color: rgba(255, 255, 255, 0.6);
|
|
526
|
+
cursor: pointer;
|
|
527
|
+
border-radius: 6px;
|
|
528
|
+
transition: all 0.15s ease;
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
.appbar-apps-menu__close:hover {
|
|
532
|
+
background: var(--kr-shell-menu-hover);
|
|
533
|
+
color: #ffffff;
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
.appbar-apps-menu__close svg {
|
|
537
|
+
width: 24px;
|
|
538
|
+
height: 24px;
|
|
539
|
+
fill: #ffffff;
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
.appbar-apps-menu__content {
|
|
543
|
+
padding: 16px 8px;
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
.appbar-apps-menu__list {
|
|
547
|
+
display: flex;
|
|
548
|
+
flex-direction: column;
|
|
549
|
+
gap: 2px;
|
|
550
|
+
max-width: 320px;
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
.appbar-apps-menu__item {
|
|
554
|
+
display: block;
|
|
555
|
+
width: 100%;
|
|
556
|
+
padding: 12px 16px;
|
|
557
|
+
background: none;
|
|
558
|
+
border: none;
|
|
559
|
+
color: #ffffff;
|
|
560
|
+
font-size: 14px;
|
|
561
|
+
font-family: inherit;
|
|
562
|
+
text-align: left;
|
|
563
|
+
text-decoration: none;
|
|
564
|
+
cursor: pointer;
|
|
565
|
+
border-radius: 6px;
|
|
566
|
+
transition: all 0.15s ease;
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
.appbar-apps-menu__item:hover {
|
|
570
|
+
background: var(--kr-shell-menu-hover);
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
.appbar-apps-menu__item--active,
|
|
574
|
+
.appbar-apps-menu__item--active:hover {
|
|
575
|
+
color: var(--kr-shell-icon-color-active);
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
/* Spacer to push user to the right */
|
|
579
|
+
.appbar-spacer {
|
|
580
|
+
flex: 1;
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
/* User Section */
|
|
584
|
+
.appbar-user {
|
|
585
|
+
position: relative;
|
|
586
|
+
margin-left: 8px;
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
.appbar-user__avatar {
|
|
590
|
+
width: 32px;
|
|
591
|
+
height: 32px;
|
|
592
|
+
border-radius: 50%;
|
|
593
|
+
background: #beea4e;
|
|
594
|
+
color: #0a0f1a;
|
|
595
|
+
display: flex;
|
|
596
|
+
align-items: center;
|
|
597
|
+
justify-content: center;
|
|
598
|
+
font-size: 12px;
|
|
599
|
+
font-weight: 600;
|
|
600
|
+
cursor: pointer;
|
|
601
|
+
overflow: hidden;
|
|
602
|
+
border: 2px solid transparent;
|
|
603
|
+
transition: border-color 0.15s ease;
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
.appbar-user__avatar:hover {
|
|
607
|
+
border-color: rgba(255, 255, 255, 0.3);
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
.appbar-user__avatar img {
|
|
611
|
+
width: 100%;
|
|
612
|
+
height: 100%;
|
|
613
|
+
object-fit: cover;
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
/* User Menu */
|
|
617
|
+
.appbar-user__menu {
|
|
618
|
+
position: absolute;
|
|
619
|
+
right: 0;
|
|
620
|
+
top: calc(100% + 8px);
|
|
621
|
+
min-width: 200px;
|
|
622
|
+
background: var(--kr-shell-menu-bg);
|
|
623
|
+
border: 1px solid var(--kr-shell-menu-border);
|
|
624
|
+
border-radius: 8px;
|
|
625
|
+
padding: 8px;
|
|
626
|
+
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.4);
|
|
627
|
+
z-index: 1000;
|
|
628
|
+
opacity: 0;
|
|
629
|
+
visibility: hidden;
|
|
630
|
+
transform: translateY(-8px);
|
|
631
|
+
transition: all 0.15s ease;
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
.appbar-user__menu--open {
|
|
635
|
+
opacity: 1;
|
|
636
|
+
visibility: visible;
|
|
637
|
+
transform: translateY(0);
|
|
638
|
+
}
|
|
639
|
+
|
|
640
|
+
.appbar-user__header {
|
|
641
|
+
padding: 8px 12px 12px;
|
|
642
|
+
border-bottom: 1px solid var(--kr-shell-menu-border);
|
|
643
|
+
margin-bottom: 8px;
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
.appbar-user__name {
|
|
647
|
+
font-size: 14px;
|
|
648
|
+
font-weight: 600;
|
|
649
|
+
color: #ffffff;
|
|
650
|
+
margin: 0 0 2px;
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
.appbar-user__email {
|
|
654
|
+
font-size: 12px;
|
|
655
|
+
color: rgba(255, 255, 255, 0.6);
|
|
656
|
+
margin: 0;
|
|
657
|
+
}
|
|
658
|
+
|
|
659
|
+
.appbar-user__menu-item {
|
|
660
|
+
display: flex;
|
|
661
|
+
align-items: center;
|
|
662
|
+
gap: 10px;
|
|
663
|
+
width: 100%;
|
|
664
|
+
padding: 10px 12px;
|
|
665
|
+
background: none;
|
|
666
|
+
border: none;
|
|
667
|
+
color: rgba(255, 255, 255, 0.8);
|
|
668
|
+
font-size: 13px;
|
|
669
|
+
font-family: inherit;
|
|
670
|
+
text-align: left;
|
|
671
|
+
cursor: pointer;
|
|
672
|
+
border-radius: 6px;
|
|
673
|
+
transition: all 0.15s ease;
|
|
674
|
+
}
|
|
675
|
+
|
|
676
|
+
.appbar-user__menu-item:hover {
|
|
677
|
+
background: var(--kr-shell-menu-hover);
|
|
678
|
+
color: #ffffff;
|
|
679
|
+
}
|
|
680
|
+
|
|
681
|
+
.appbar-user__menu-item--danger {
|
|
682
|
+
color: #f87171;
|
|
683
|
+
}
|
|
684
|
+
|
|
685
|
+
.appbar-user__menu-item--danger:hover {
|
|
686
|
+
background: rgba(248, 113, 113, 0.1);
|
|
687
|
+
color: #f87171;
|
|
688
|
+
}
|
|
689
|
+
|
|
690
|
+
.appbar-user__menu-item__icon {
|
|
691
|
+
width: 16px;
|
|
692
|
+
height: 16px;
|
|
693
|
+
display: flex;
|
|
694
|
+
align-items: center;
|
|
695
|
+
justify-content: center;
|
|
696
|
+
flex-shrink: 0;
|
|
697
|
+
}
|
|
698
|
+
|
|
699
|
+
.appbar-user__menu-item__icon svg {
|
|
700
|
+
width: 100%;
|
|
701
|
+
height: 100%;
|
|
702
|
+
fill: currentColor;
|
|
703
|
+
}
|
|
704
|
+
|
|
705
|
+
.appbar-user__menu-divider {
|
|
706
|
+
height: 1px;
|
|
707
|
+
background: var(--kr-shell-menu-border);
|
|
708
|
+
margin: 8px 0;
|
|
709
|
+
}
|
|
710
|
+
|
|
711
|
+
/* Secondary App Bar */
|
|
712
|
+
.subbar {
|
|
713
|
+
height: 30px;
|
|
714
|
+
background: #364365;
|
|
715
|
+
border-bottom: 1px solid rgba(255, 255, 255, 0.08);
|
|
716
|
+
display: flex;
|
|
717
|
+
align-items: center;
|
|
718
|
+
padding: 0 16px;
|
|
719
|
+
flex-shrink: 0;
|
|
720
|
+
}
|
|
721
|
+
|
|
722
|
+
.breadcrumbs {
|
|
723
|
+
display: flex;
|
|
724
|
+
align-items: center;
|
|
725
|
+
gap: 8px;
|
|
726
|
+
font-size: 12px;
|
|
727
|
+
color: #ffffff;
|
|
728
|
+
}
|
|
729
|
+
|
|
730
|
+
.breadcrumbs a {
|
|
731
|
+
color: #ffffff;
|
|
732
|
+
text-decoration: none;
|
|
733
|
+
}
|
|
734
|
+
|
|
735
|
+
.breadcrumbs a:hover {
|
|
736
|
+
text-decoration: underline;
|
|
737
|
+
}
|
|
738
|
+
|
|
739
|
+
.breadcrumbs__separator {
|
|
740
|
+
color: #ffffff;
|
|
741
|
+
}
|
|
742
|
+
|
|
743
|
+
/* Main Content */
|
|
744
|
+
.main {
|
|
745
|
+
flex: 1;
|
|
746
|
+
min-height: 0;
|
|
747
|
+
display: flex;
|
|
748
|
+
flex-direction: column;
|
|
749
|
+
overflow: hidden;
|
|
750
|
+
position: relative;
|
|
751
|
+
}
|
|
752
|
+
|
|
753
|
+
/* Click outside overlay */
|
|
754
|
+
.menu-backdrop {
|
|
755
|
+
position: fixed;
|
|
756
|
+
inset: 0;
|
|
757
|
+
z-index: 999;
|
|
758
|
+
}
|
|
759
|
+
|
|
760
|
+
/* Progress bar */
|
|
761
|
+
.progress {
|
|
762
|
+
position: fixed;
|
|
763
|
+
top: 0;
|
|
764
|
+
left: 0;
|
|
765
|
+
right: 0;
|
|
766
|
+
height: 4px;
|
|
767
|
+
overflow-x: hidden;
|
|
768
|
+
z-index: 1000;
|
|
769
|
+
display: none;
|
|
770
|
+
}
|
|
771
|
+
|
|
772
|
+
.progress--loading {
|
|
773
|
+
display: block;
|
|
774
|
+
}
|
|
775
|
+
|
|
776
|
+
.progress__track {
|
|
777
|
+
position: absolute;
|
|
778
|
+
top: 0;
|
|
779
|
+
bottom: 0;
|
|
780
|
+
width: 100%;
|
|
781
|
+
background: rgb(190 234 78 / 50%); // rgba(190, 234, 78, 0.3);
|
|
782
|
+
}
|
|
783
|
+
|
|
784
|
+
.progress__bar {
|
|
785
|
+
position: absolute;
|
|
786
|
+
top: 0;
|
|
787
|
+
bottom: 0;
|
|
788
|
+
width: 100%;
|
|
789
|
+
transform-origin: left center;
|
|
790
|
+
}
|
|
791
|
+
|
|
792
|
+
.progress__bar-inner {
|
|
793
|
+
display: inline-block;
|
|
794
|
+
position: absolute;
|
|
795
|
+
width: 100%;
|
|
796
|
+
height: 100%;
|
|
797
|
+
background: #beea4e;
|
|
798
|
+
}
|
|
799
|
+
|
|
800
|
+
.progress__bar--primary {
|
|
801
|
+
left: -145.166611%;
|
|
802
|
+
}
|
|
803
|
+
|
|
804
|
+
.progress--loading .progress__bar--primary {
|
|
805
|
+
animation: progress-primary-translate 2s infinite linear;
|
|
806
|
+
}
|
|
807
|
+
|
|
808
|
+
.progress--loading .progress__bar--primary .progress__bar-inner {
|
|
809
|
+
animation: progress-primary-scale 2s infinite linear;
|
|
810
|
+
}
|
|
811
|
+
|
|
812
|
+
.progress__bar--secondary {
|
|
813
|
+
left: -54.888891%;
|
|
814
|
+
}
|
|
815
|
+
|
|
816
|
+
.progress--loading .progress__bar--secondary {
|
|
817
|
+
animation: progress-secondary-translate 2s infinite linear;
|
|
818
|
+
}
|
|
819
|
+
|
|
820
|
+
.progress--loading .progress__bar--secondary .progress__bar-inner {
|
|
821
|
+
animation: progress-secondary-scale 2s infinite linear;
|
|
822
|
+
}
|
|
823
|
+
|
|
824
|
+
@keyframes progress-primary-translate {
|
|
825
|
+
0% {
|
|
826
|
+
transform: translateX(0);
|
|
827
|
+
}
|
|
828
|
+
20% {
|
|
829
|
+
animation-timing-function: cubic-bezier(0.5, 0, 0.701732, 0.495819);
|
|
830
|
+
transform: translateX(0);
|
|
831
|
+
}
|
|
832
|
+
59.15% {
|
|
833
|
+
animation-timing-function: cubic-bezier(0.302435, 0.381352, 0.55, 0.956352);
|
|
834
|
+
transform: translateX(83.67142%);
|
|
835
|
+
}
|
|
836
|
+
100% {
|
|
837
|
+
transform: translateX(200.611057%);
|
|
838
|
+
}
|
|
839
|
+
}
|
|
840
|
+
|
|
841
|
+
@keyframes progress-primary-scale {
|
|
842
|
+
0% {
|
|
843
|
+
transform: scaleX(0.08);
|
|
844
|
+
}
|
|
845
|
+
36.65% {
|
|
846
|
+
animation-timing-function: cubic-bezier(0.334731, 0.12482, 0.785844, 1);
|
|
847
|
+
transform: scaleX(0.08);
|
|
848
|
+
}
|
|
849
|
+
69.15% {
|
|
850
|
+
animation-timing-function: cubic-bezier(0.06, 0.11, 0.6, 1);
|
|
851
|
+
transform: scaleX(0.661479);
|
|
852
|
+
}
|
|
853
|
+
100% {
|
|
854
|
+
transform: scaleX(0.08);
|
|
855
|
+
}
|
|
856
|
+
}
|
|
857
|
+
|
|
858
|
+
@keyframes progress-secondary-translate {
|
|
859
|
+
0% {
|
|
860
|
+
animation-timing-function: cubic-bezier(0.15, 0, 0.515058, 0.409685);
|
|
861
|
+
transform: translateX(0);
|
|
862
|
+
}
|
|
863
|
+
25% {
|
|
864
|
+
animation-timing-function: cubic-bezier(0.31033, 0.284058, 0.8, 0.733712);
|
|
865
|
+
transform: translateX(37.651913%);
|
|
866
|
+
}
|
|
867
|
+
48.35% {
|
|
868
|
+
animation-timing-function: cubic-bezier(0.4, 0.627035, 0.6, 0.902026);
|
|
869
|
+
transform: translateX(84.386165%);
|
|
870
|
+
}
|
|
871
|
+
100% {
|
|
872
|
+
transform: translateX(160.277782%);
|
|
873
|
+
}
|
|
874
|
+
}
|
|
875
|
+
|
|
876
|
+
@keyframes progress-secondary-scale {
|
|
877
|
+
0% {
|
|
878
|
+
animation-timing-function: cubic-bezier(0.205028, 0.057051, 0.57661, 0.453971);
|
|
879
|
+
transform: scaleX(0.08);
|
|
880
|
+
}
|
|
881
|
+
19.15% {
|
|
882
|
+
animation-timing-function: cubic-bezier(0.152313, 0.196432, 0.648374, 1.004315);
|
|
883
|
+
transform: scaleX(0.457104);
|
|
884
|
+
}
|
|
885
|
+
44.15% {
|
|
886
|
+
animation-timing-function: cubic-bezier(0.257759, -0.003163, 0.211762, 1.38179);
|
|
887
|
+
transform: scaleX(0.72796);
|
|
888
|
+
}
|
|
889
|
+
100% {
|
|
890
|
+
transform: scaleX(0.08);
|
|
891
|
+
}
|
|
892
|
+
}
|
|
893
|
+
`;
|
|
894
|
+
__decorate$5([
|
|
895
|
+
n({ type: String })
|
|
896
|
+
], KRShell.prototype, "logo", void 0);
|
|
897
|
+
__decorate$5([
|
|
898
|
+
n({ type: Array })
|
|
899
|
+
], KRShell.prototype, "apps", void 0);
|
|
900
|
+
__decorate$5([
|
|
901
|
+
n({ type: String, attribute: 'active-app-id' })
|
|
902
|
+
], KRShell.prototype, "activeAppId", void 0);
|
|
903
|
+
__decorate$5([
|
|
904
|
+
n({ type: Object })
|
|
905
|
+
], KRShell.prototype, "user", void 0);
|
|
906
|
+
__decorate$5([
|
|
907
|
+
n({ type: Array, attribute: 'menu-items' })
|
|
908
|
+
], KRShell.prototype, "menuItems", void 0);
|
|
909
|
+
__decorate$5([
|
|
910
|
+
r()
|
|
911
|
+
], KRShell.prototype, "isUserMenuOpen", void 0);
|
|
912
|
+
__decorate$5([
|
|
913
|
+
r()
|
|
914
|
+
], KRShell.prototype, "isAppsMenuOpen", void 0);
|
|
915
|
+
__decorate$5([
|
|
916
|
+
r()
|
|
917
|
+
], KRShell.prototype, "pendingRequests", void 0);
|
|
918
|
+
KRShell = __decorate$5([
|
|
919
|
+
t$1('kr-shell')
|
|
920
|
+
], KRShell);
|
|
921
|
+
|
|
922
|
+
class KRClient {
|
|
923
|
+
constructor() {
|
|
924
|
+
this._url = `https://content.kodaris.com`;
|
|
925
|
+
}
|
|
926
|
+
static getInstance() {
|
|
927
|
+
if (!KRClient._instance) {
|
|
928
|
+
KRClient._instance = new KRClient();
|
|
929
|
+
}
|
|
930
|
+
return KRClient._instance;
|
|
931
|
+
}
|
|
932
|
+
fetch(options) {
|
|
933
|
+
let url = options.url;
|
|
934
|
+
// Add on the default base url if user
|
|
935
|
+
// has not provided the full url
|
|
936
|
+
if (url.indexOf('http') !== 0) {
|
|
937
|
+
url = this._url + url;
|
|
938
|
+
}
|
|
939
|
+
if (options.params) {
|
|
940
|
+
url += '?';
|
|
941
|
+
Object.keys(options.params).forEach((key, keyIdx) => {
|
|
942
|
+
if (keyIdx > 0) {
|
|
943
|
+
url += '&';
|
|
944
|
+
}
|
|
945
|
+
url += `${key}=${options.params[key]}`;
|
|
946
|
+
});
|
|
947
|
+
}
|
|
948
|
+
// fixme
|
|
949
|
+
if (!options) {
|
|
950
|
+
options = {};
|
|
951
|
+
}
|
|
952
|
+
if (!options.headers) {
|
|
953
|
+
options.headers = {};
|
|
954
|
+
}
|
|
955
|
+
if (!options.headers['Content-Type']) {
|
|
956
|
+
options.headers['Content-Type'] = 'application/json';
|
|
957
|
+
}
|
|
958
|
+
options.credentials = 'include';
|
|
959
|
+
// User and Customer apis require CSRF tokens on non-GET reequests
|
|
960
|
+
let token;
|
|
961
|
+
if (url.indexOf('/api/user/') > -1 || url.indexOf('/api/customer/') > -1) {
|
|
962
|
+
token = fetch(`${this._url}/api/user/customer/authToken`, { credentials: 'include' });
|
|
963
|
+
}
|
|
964
|
+
else {
|
|
965
|
+
token = Promise.resolve(null);
|
|
966
|
+
}
|
|
967
|
+
let response;
|
|
968
|
+
return token
|
|
969
|
+
.then(res => res?.json())
|
|
970
|
+
.then(res => {
|
|
971
|
+
if (res?.data) {
|
|
972
|
+
options.headers['X-CSRF-TOKEN'] = res.data;
|
|
973
|
+
}
|
|
974
|
+
return fetch(url, options);
|
|
975
|
+
})
|
|
976
|
+
.then(res => {
|
|
977
|
+
response = res;
|
|
978
|
+
return res.json();
|
|
979
|
+
})
|
|
980
|
+
.then(json => {
|
|
981
|
+
if (!response?.ok) {
|
|
982
|
+
return Promise.reject(json);
|
|
983
|
+
}
|
|
984
|
+
return Promise.resolve(json);
|
|
985
|
+
});
|
|
986
|
+
}
|
|
987
|
+
}
|
|
988
|
+
KRClient._instance = null;
|
|
989
|
+
|
|
990
|
+
var __decorate$4 = (undefined && undefined.__decorate) || function (decorators, target, key, desc) {
|
|
991
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
992
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
993
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
994
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
995
|
+
};
|
|
996
|
+
let KRNavItemEdit = class KRNavItemEdit extends i$1 {
|
|
997
|
+
constructor() {
|
|
998
|
+
super(...arguments);
|
|
999
|
+
this.label = '';
|
|
1000
|
+
this.icon = '';
|
|
1001
|
+
this.url = '';
|
|
1002
|
+
this.active = true;
|
|
1003
|
+
}
|
|
1004
|
+
connectedCallback() {
|
|
1005
|
+
super.connectedCallback();
|
|
1006
|
+
if (this.data) {
|
|
1007
|
+
this.label = this.data.label || '';
|
|
1008
|
+
this.icon = this.data.icon || '';
|
|
1009
|
+
this.url = this.data.url || '';
|
|
1010
|
+
this.active = this.data.active !== false;
|
|
1011
|
+
}
|
|
1012
|
+
}
|
|
1013
|
+
handleLabelInput(e) {
|
|
1014
|
+
const textField = e.target;
|
|
1015
|
+
this.label = textField.value;
|
|
1016
|
+
}
|
|
1017
|
+
handleIconInput(e) {
|
|
1018
|
+
const textField = e.target;
|
|
1019
|
+
this.icon = textField.value;
|
|
1020
|
+
}
|
|
1021
|
+
handleUrlInput(e) {
|
|
1022
|
+
const textField = e.target;
|
|
1023
|
+
this.url = textField.value;
|
|
1024
|
+
}
|
|
1025
|
+
handleActiveChange(e) {
|
|
1026
|
+
this.active = e.target.checked;
|
|
1027
|
+
}
|
|
1028
|
+
handleCancel() {
|
|
1029
|
+
this.dialogRef.close(undefined);
|
|
1030
|
+
}
|
|
1031
|
+
handleSave() {
|
|
1032
|
+
this.dialogRef.close({
|
|
1033
|
+
label: this.label,
|
|
1034
|
+
icon: this.icon || undefined,
|
|
1035
|
+
url: this.url || undefined,
|
|
1036
|
+
active: this.active,
|
|
1037
|
+
});
|
|
1038
|
+
}
|
|
1039
|
+
handleKeyDown(e) {
|
|
1040
|
+
if (e.key === 'Enter') {
|
|
1041
|
+
this.handleSave();
|
|
1042
|
+
}
|
|
1043
|
+
}
|
|
1044
|
+
render() {
|
|
1045
|
+
return x `
|
|
1046
|
+
<h2>Edit Navigation Item</h2>
|
|
1047
|
+
|
|
1048
|
+
<div class="form-group">
|
|
1049
|
+
<kr-text-field
|
|
1050
|
+
label="Label"
|
|
1051
|
+
name="label"
|
|
1052
|
+
.value=${this.label}
|
|
1053
|
+
@input=${this.handleLabelInput}
|
|
1054
|
+
@keydown=${this.handleKeyDown}
|
|
1055
|
+
placeholder="Enter label"
|
|
1056
|
+
></kr-text-field>
|
|
1057
|
+
</div>
|
|
1058
|
+
|
|
1059
|
+
<div class="form-group">
|
|
1060
|
+
<kr-text-field
|
|
1061
|
+
label="Icon (emoji or SVG)"
|
|
1062
|
+
name="icon"
|
|
1063
|
+
.value=${this.icon}
|
|
1064
|
+
@input=${this.handleIconInput}
|
|
1065
|
+
@keydown=${this.handleKeyDown}
|
|
1066
|
+
placeholder="e.g. 📊 or <svg>...</svg>"
|
|
1067
|
+
></kr-text-field>
|
|
1068
|
+
</div>
|
|
1069
|
+
|
|
1070
|
+
<div class="form-group">
|
|
1071
|
+
<kr-text-field
|
|
1072
|
+
label="URL"
|
|
1073
|
+
name="url"
|
|
1074
|
+
.value=${this.url}
|
|
1075
|
+
@input=${this.handleUrlInput}
|
|
1076
|
+
@keydown=${this.handleKeyDown}
|
|
1077
|
+
placeholder="e.g. /ci/my-page or https://..."
|
|
1078
|
+
></kr-text-field>
|
|
1079
|
+
</div>
|
|
1080
|
+
|
|
1081
|
+
<div class="form-group">
|
|
1082
|
+
<div class="checkbox-group">
|
|
1083
|
+
<input
|
|
1084
|
+
type="checkbox"
|
|
1085
|
+
id="active"
|
|
1086
|
+
.checked=${this.active}
|
|
1087
|
+
@change=${this.handleActiveChange}
|
|
1088
|
+
/>
|
|
1089
|
+
<label class="checkbox-label" for="active">Visible in navigation</label>
|
|
1090
|
+
</div>
|
|
1091
|
+
</div>
|
|
1092
|
+
|
|
1093
|
+
<div class="actions">
|
|
1094
|
+
<button class="btn-cancel" @click=${this.handleCancel}>Cancel</button>
|
|
1095
|
+
<button class="btn-save" @click=${this.handleSave}>Save</button>
|
|
1096
|
+
</div>
|
|
1097
|
+
`;
|
|
1098
|
+
}
|
|
1099
|
+
};
|
|
1100
|
+
KRNavItemEdit.styles = i$4 `
|
|
1101
|
+
:host {
|
|
1102
|
+
display: block;
|
|
1103
|
+
width: 360px;
|
|
1104
|
+
padding: 24px;
|
|
1105
|
+
color: rgba(255, 255, 255, 0.9);
|
|
1106
|
+
|
|
1107
|
+
/* Dark theme for kr-text-field */
|
|
1108
|
+
--kr-text-field-label-color: rgba(255, 255, 255, 0.7);
|
|
1109
|
+
--kr-text-field-border-color: rgba(255, 255, 255, 0.1);
|
|
1110
|
+
--kr-text-field-bg: rgba(255, 255, 255, 0.05);
|
|
1111
|
+
--kr-text-field-color: #ffffff;
|
|
1112
|
+
--kr-text-field-focus-border-color: #beea4e;
|
|
1113
|
+
--kr-text-field-focus-ring-color: rgba(190, 234, 78, 0.1);
|
|
1114
|
+
--kr-text-field-placeholder-color: rgba(255, 255, 255, 0.3);
|
|
1115
|
+
--kr-text-field-helper-color: rgba(255, 255, 255, 0.5);
|
|
1116
|
+
}
|
|
1117
|
+
|
|
1118
|
+
h2 {
|
|
1119
|
+
margin: 0 0 24px 0;
|
|
1120
|
+
font-size: 18px;
|
|
1121
|
+
font-weight: 600;
|
|
1122
|
+
color: #ffffff;
|
|
1123
|
+
}
|
|
1124
|
+
|
|
1125
|
+
.form-group {
|
|
1126
|
+
margin-bottom: 20px;
|
|
1127
|
+
}
|
|
1128
|
+
|
|
1129
|
+
.checkbox-group {
|
|
1130
|
+
display: flex;
|
|
1131
|
+
align-items: center;
|
|
1132
|
+
gap: 10px;
|
|
1133
|
+
}
|
|
1134
|
+
|
|
1135
|
+
input[type="checkbox"] {
|
|
1136
|
+
width: 18px;
|
|
1137
|
+
height: 18px;
|
|
1138
|
+
accent-color: #beea4e;
|
|
1139
|
+
cursor: pointer;
|
|
1140
|
+
}
|
|
1141
|
+
|
|
1142
|
+
.checkbox-label {
|
|
1143
|
+
font-size: 14px;
|
|
1144
|
+
color: rgba(255, 255, 255, 0.9);
|
|
1145
|
+
cursor: pointer;
|
|
1146
|
+
}
|
|
1147
|
+
|
|
1148
|
+
.actions {
|
|
1149
|
+
display: flex;
|
|
1150
|
+
gap: 12px;
|
|
1151
|
+
justify-content: flex-end;
|
|
1152
|
+
margin-top: 28px;
|
|
1153
|
+
}
|
|
1154
|
+
|
|
1155
|
+
button {
|
|
1156
|
+
padding: 10px 20px;
|
|
1157
|
+
border-radius: 6px;
|
|
1158
|
+
font-size: 14px;
|
|
1159
|
+
font-weight: 500;
|
|
1160
|
+
font-family: inherit;
|
|
1161
|
+
cursor: pointer;
|
|
1162
|
+
transition: all 0.15s ease;
|
|
1163
|
+
}
|
|
1164
|
+
|
|
1165
|
+
.btn-cancel {
|
|
1166
|
+
background: rgba(255, 255, 255, 0.05);
|
|
1167
|
+
border: 1px solid rgba(255, 255, 255, 0.1);
|
|
1168
|
+
color: rgba(255, 255, 255, 0.8);
|
|
1169
|
+
}
|
|
1170
|
+
|
|
1171
|
+
.btn-cancel:hover {
|
|
1172
|
+
background: rgba(255, 255, 255, 0.1);
|
|
1173
|
+
color: #ffffff;
|
|
1174
|
+
}
|
|
1175
|
+
|
|
1176
|
+
.btn-save {
|
|
1177
|
+
background: #beea4e;
|
|
1178
|
+
border: none;
|
|
1179
|
+
color: #10172a;
|
|
1180
|
+
}
|
|
1181
|
+
|
|
1182
|
+
.btn-save:hover {
|
|
1183
|
+
background: #d4f472;
|
|
1184
|
+
}
|
|
1185
|
+
`;
|
|
1186
|
+
__decorate$4([
|
|
1187
|
+
r()
|
|
1188
|
+
], KRNavItemEdit.prototype, "label", void 0);
|
|
1189
|
+
__decorate$4([
|
|
1190
|
+
r()
|
|
1191
|
+
], KRNavItemEdit.prototype, "icon", void 0);
|
|
1192
|
+
__decorate$4([
|
|
1193
|
+
r()
|
|
1194
|
+
], KRNavItemEdit.prototype, "url", void 0);
|
|
1195
|
+
__decorate$4([
|
|
1196
|
+
r()
|
|
1197
|
+
], KRNavItemEdit.prototype, "active", void 0);
|
|
1198
|
+
KRNavItemEdit = __decorate$4([
|
|
1199
|
+
t$1('kr-nav-item-edit')
|
|
1200
|
+
], KRNavItemEdit);
|
|
1201
|
+
|
|
1202
|
+
var __decorate$3 = (undefined && undefined.__decorate) || function (decorators, target, key, desc) {
|
|
1203
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
1204
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
1205
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
1206
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
1207
|
+
};
|
|
1208
|
+
/**
|
|
1209
|
+
* Application shell component with nav drawer and main content area.
|
|
1210
|
+
*
|
|
1211
|
+
* ## Features
|
|
1212
|
+
* - Collapsible navigation drawer with hierarchical items (groups and items)
|
|
1213
|
+
* - User profile display in footer with customization menu
|
|
1214
|
+
* - Edit mode for customizing navigation (rename, reorder, hide items, add custom items)
|
|
1215
|
+
* - Drag and drop reordering in edit mode
|
|
1216
|
+
* - Persistence of customizations via preference API (global/organization level)
|
|
1217
|
+
*
|
|
1218
|
+
* ## Nav Customization
|
|
1219
|
+
* Users can customize the navigation by clicking their profile and selecting
|
|
1220
|
+
* "Customize Navigation". This enters edit mode where users can:
|
|
1221
|
+
* - Right-click items to edit label, icon, or visibility
|
|
1222
|
+
* - Right-click to add new items above/below existing items
|
|
1223
|
+
* - Drag and drop to reorder items or move them between groups
|
|
1224
|
+
*
|
|
1225
|
+
* Customizations are stored globally and apply to all users.
|
|
1226
|
+
*
|
|
1227
|
+
* ## Preference Storage
|
|
1228
|
+
* Preferences are stored as JSON with the following structure:
|
|
1229
|
+
* ```json
|
|
1230
|
+
* {
|
|
1231
|
+
* "nav": {
|
|
1232
|
+
* "itemId": { "label": "Custom Label", "icon": "...", "active": true, "order": 0 },
|
|
1233
|
+
* "custom-123": { "type": "item", "label": "New Item", "url": "/path", ... }
|
|
1234
|
+
* }
|
|
1235
|
+
* }
|
|
1236
|
+
* ```
|
|
1237
|
+
*
|
|
1238
|
+
* The `nav` object is a delta - it only contains overrides for existing items
|
|
1239
|
+
* or definitions for custom items (prefixed with "custom-").
|
|
1240
|
+
*
|
|
1241
|
+
* ## API Endpoints Used
|
|
1242
|
+
* - GET `/api/system/preference/json/scaffold?global=true` - load preferences
|
|
1243
|
+
* - POST `/api/system/preference/json/scaffold?global=true` - create new preference
|
|
1244
|
+
* - PUT `/api/system/preference/json/scaffold/{uuid}?global=true` - update existing preference
|
|
1245
|
+
*
|
|
1246
|
+
* @slot - The main content
|
|
1247
|
+
*
|
|
1248
|
+
* @property {string} logo - URL for the logo image
|
|
1249
|
+
* @property {string} title - Title text to display instead of logo
|
|
1250
|
+
* @property {'light'|'dark'} scheme - Color scheme: 'light' (default) or 'dark'
|
|
1251
|
+
* @property {KRNavItem[]} nav - Navigation items as JSON array
|
|
1252
|
+
* @property {KRUser} user - User profile data
|
|
1253
|
+
*
|
|
1254
|
+
*
|
|
1255
|
+
* TODO
|
|
1256
|
+
* - Don't hardcode breadcrumbs
|
|
1257
|
+
* - Look at renderNormalFooter and renderEditFooter and see if that is what we want to do
|
|
1258
|
+
*/
|
|
1259
|
+
let KRScaffold = class KRScaffold extends i$1 {
|
|
1260
|
+
constructor() {
|
|
1261
|
+
super();
|
|
1262
|
+
/**
|
|
1263
|
+
* HTTP client for API calls
|
|
1264
|
+
*/
|
|
1265
|
+
this.http = KRClient.getInstance();
|
|
1266
|
+
/**
|
|
1267
|
+
* Default icon for nav items without an icon (shown at top level)
|
|
1268
|
+
*/
|
|
1269
|
+
this.defaultNavItemIcon = '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor"><circle cx="12" cy="12" r="10"/></svg>';
|
|
1270
|
+
this.navItemsExpanded = new Set();
|
|
1271
|
+
this.navQuery = '';
|
|
1272
|
+
this.activeNavItemId = null;
|
|
1273
|
+
this.isNavScrolled = false;
|
|
1274
|
+
this.isNavOpened = true;
|
|
1275
|
+
this.isEditing = false;
|
|
1276
|
+
this.isUserMenuOpen = false;
|
|
1277
|
+
this.pref = { nav: {} };
|
|
1278
|
+
this.draggedNavItemId = null;
|
|
1279
|
+
this.navItemDropTargetId = null;
|
|
1280
|
+
this.navItemDropPosition = 'above';
|
|
1281
|
+
this.pendingRequests = 0;
|
|
1282
|
+
this.originalFetch = null;
|
|
1283
|
+
this.originalXhrOpen = null;
|
|
1284
|
+
this.navItemDragPreview = null;
|
|
1285
|
+
this.navItemDragStartY = 0;
|
|
1286
|
+
this.isNavItemDragging = false;
|
|
1287
|
+
this.navItemDragExpandTimeout = null;
|
|
1288
|
+
this.navInitialized = false;
|
|
1289
|
+
this.logo = '';
|
|
1290
|
+
this.title = '';
|
|
1291
|
+
this.scheme = 'light';
|
|
1292
|
+
this.nav = [];
|
|
1293
|
+
this.navIconsDisplayed = false;
|
|
1294
|
+
this.navExpanded = false;
|
|
1295
|
+
this.user = null;
|
|
1296
|
+
/**
|
|
1297
|
+
* Whether to show the subbar with menu toggle
|
|
1298
|
+
*/
|
|
1299
|
+
this.subbar = false;
|
|
1300
|
+
/**
|
|
1301
|
+
* Breadcrumbs to display in the subbar
|
|
1302
|
+
*/
|
|
1303
|
+
this.breadcrumbs = [];
|
|
1304
|
+
this.boundHandleMouseMove = this.handleMouseMove.bind(this);
|
|
1305
|
+
this.boundHandleMouseUp = this.handleMouseUp.bind(this);
|
|
1306
|
+
}
|
|
1307
|
+
connectedCallback() {
|
|
1308
|
+
super.connectedCallback();
|
|
1309
|
+
this.loadPref();
|
|
1310
|
+
this.installFetchInterceptor();
|
|
1311
|
+
}
|
|
1312
|
+
updated(changedProperties) {
|
|
1313
|
+
super.updated(changedProperties);
|
|
1314
|
+
if (changedProperties.has('nav') && this.nav.length > 0) {
|
|
1315
|
+
this.updateActiveNavItem();
|
|
1316
|
+
if (this.navExpanded && !this.navInitialized) {
|
|
1317
|
+
this.navInitialized = true;
|
|
1318
|
+
this.getComputedNav()
|
|
1319
|
+
.filter(item => item.type === 'group')
|
|
1320
|
+
.forEach(group => this.navItemsExpanded.add(group.id));
|
|
1321
|
+
}
|
|
1322
|
+
}
|
|
1323
|
+
}
|
|
1324
|
+
disconnectedCallback() {
|
|
1325
|
+
super.disconnectedCallback();
|
|
1326
|
+
this.uninstallFetchInterceptor();
|
|
1327
|
+
}
|
|
1328
|
+
/**
|
|
1329
|
+
* Installs global interceptors for fetch and XMLHttpRequest to track pending HTTP requests.
|
|
1330
|
+
* Updates `pendingRequests` state when requests start/complete.
|
|
1331
|
+
*/
|
|
1332
|
+
installFetchInterceptor() {
|
|
1333
|
+
if (this.originalFetch)
|
|
1334
|
+
return;
|
|
1335
|
+
const scaffold = this;
|
|
1336
|
+
// Intercept fetch
|
|
1337
|
+
this.originalFetch = window.fetch.bind(window);
|
|
1338
|
+
const originalFetch = this.originalFetch; // Capture in local scope to avoid null reference after uninstall
|
|
1339
|
+
window.fetch = function (...args) {
|
|
1340
|
+
scaffold.pendingRequests++;
|
|
1341
|
+
return originalFetch(...args).finally(() => {
|
|
1342
|
+
scaffold.pendingRequests--;
|
|
1343
|
+
});
|
|
1344
|
+
};
|
|
1345
|
+
// Intercept XMLHttpRequest
|
|
1346
|
+
this.originalXhrOpen = XMLHttpRequest.prototype.open;
|
|
1347
|
+
const originalXhrOpen = this.originalXhrOpen; // Capture in local scope to avoid null reference after uninstall
|
|
1348
|
+
XMLHttpRequest.prototype.open = function (method, url, async = true, username, password) {
|
|
1349
|
+
scaffold.pendingRequests++;
|
|
1350
|
+
this.addEventListener('loadend', () => {
|
|
1351
|
+
scaffold.pendingRequests--;
|
|
1352
|
+
}, { once: true });
|
|
1353
|
+
return originalXhrOpen.call(this, method, url, async, username, password);
|
|
1354
|
+
};
|
|
1355
|
+
}
|
|
1356
|
+
/**
|
|
1357
|
+
* Restores the original fetch and XMLHttpRequest functions.
|
|
1358
|
+
*/
|
|
1359
|
+
uninstallFetchInterceptor() {
|
|
1360
|
+
if (this.originalFetch) {
|
|
1361
|
+
window.fetch = this.originalFetch;
|
|
1362
|
+
this.originalFetch = null;
|
|
1363
|
+
}
|
|
1364
|
+
if (this.originalXhrOpen) {
|
|
1365
|
+
XMLHttpRequest.prototype.open = this.originalXhrOpen;
|
|
1366
|
+
this.originalXhrOpen = null;
|
|
1367
|
+
}
|
|
1368
|
+
}
|
|
1369
|
+
// =========================================================================
|
|
1370
|
+
// Navigation Data
|
|
1371
|
+
// =========================================================================
|
|
1372
|
+
/**
|
|
1373
|
+
* Returns the computed navigation as a flat array with preferences applied.
|
|
1374
|
+
*
|
|
1375
|
+
* Transforms the hierarchical `nav` property into a flat list where each item
|
|
1376
|
+
* has a `parentId` reference instead of nested `children`. Merges user
|
|
1377
|
+
* preference overrides (label, icon, url, active, order, parentId) and
|
|
1378
|
+
* includes any custom items added via the edit dialog.
|
|
1379
|
+
*
|
|
1380
|
+
* @returns Flat array of nav items with preferences applied
|
|
1381
|
+
*/
|
|
1382
|
+
getComputedNav() {
|
|
1383
|
+
const result = [];
|
|
1384
|
+
this.nav.forEach((item, index) => {
|
|
1385
|
+
const override = this.pref.nav[item.id];
|
|
1386
|
+
result.push({
|
|
1387
|
+
...item,
|
|
1388
|
+
...override,
|
|
1389
|
+
order: override?.order ?? item.order ?? index,
|
|
1390
|
+
parentId: override?.parentId ?? null,
|
|
1391
|
+
});
|
|
1392
|
+
// Add children if group
|
|
1393
|
+
if (item.type === 'group' && item.children) {
|
|
1394
|
+
item.children.forEach((child, childIndex) => {
|
|
1395
|
+
const childOverride = this.pref.nav[child.id];
|
|
1396
|
+
result.push({
|
|
1397
|
+
...child,
|
|
1398
|
+
...childOverride,
|
|
1399
|
+
order: childOverride?.order ?? child.order ?? childIndex,
|
|
1400
|
+
parentId: childOverride?.parentId !== undefined ? childOverride.parentId : item.id,
|
|
1401
|
+
});
|
|
1402
|
+
});
|
|
1403
|
+
}
|
|
1404
|
+
});
|
|
1405
|
+
// Add custom items from pref
|
|
1406
|
+
Object.entries(this.pref.nav).forEach(([id, item]) => {
|
|
1407
|
+
if (id.startsWith('custom') && item.type && item.label) {
|
|
1408
|
+
result.push({
|
|
1409
|
+
id,
|
|
1410
|
+
type: item.type,
|
|
1411
|
+
label: item.label,
|
|
1412
|
+
icon: item.icon,
|
|
1413
|
+
url: item.url,
|
|
1414
|
+
active: item.active,
|
|
1415
|
+
order: item.order ?? result.length,
|
|
1416
|
+
parentId: item.parentId ?? null,
|
|
1417
|
+
});
|
|
1418
|
+
}
|
|
1419
|
+
});
|
|
1420
|
+
return result;
|
|
1421
|
+
}
|
|
1422
|
+
/**
|
|
1423
|
+
* Returns nav items for a given parent, filtered and sorted for rendering.
|
|
1424
|
+
*
|
|
1425
|
+
* Filters the computed nav to items matching the parentId, sorts by order,
|
|
1426
|
+
* hides inactive items in normal mode, and applies the search query filter.
|
|
1427
|
+
* Groups are shown if their label matches or if they have matching children.
|
|
1428
|
+
*
|
|
1429
|
+
* @param parentId - The parent ID to filter by (null for top-level items)
|
|
1430
|
+
* @returns Filtered and sorted nav items ready for rendering
|
|
1431
|
+
*/
|
|
1432
|
+
getNavItemChildren(parentId) {
|
|
1433
|
+
let items = this.getComputedNav()
|
|
1434
|
+
.filter(item => item.parentId === parentId)
|
|
1435
|
+
.sort((a, b) => a.order - b.order);
|
|
1436
|
+
// In normal mode, filter out hidden items
|
|
1437
|
+
if (!this.isEditing) {
|
|
1438
|
+
items = items.filter(item => item.active !== false);
|
|
1439
|
+
}
|
|
1440
|
+
// Apply search filter
|
|
1441
|
+
if (this.navQuery) {
|
|
1442
|
+
const query = this.navQuery.toLowerCase();
|
|
1443
|
+
const allItems = this.getComputedNav();
|
|
1444
|
+
items = items.filter(item => {
|
|
1445
|
+
if (item.type === 'group') {
|
|
1446
|
+
// Show group if its label matches OR if it has matching children
|
|
1447
|
+
return item.label.toLowerCase().includes(query) || allItems.some(child => child.parentId === item.id &&
|
|
1448
|
+
child.active !== false &&
|
|
1449
|
+
child.label.toLowerCase().includes(query));
|
|
1450
|
+
}
|
|
1451
|
+
// Show item if its label matches
|
|
1452
|
+
return item.label.toLowerCase().includes(query);
|
|
1453
|
+
});
|
|
1454
|
+
}
|
|
1455
|
+
return items;
|
|
1456
|
+
}
|
|
1457
|
+
// =========================================================================
|
|
1458
|
+
// Navigation Search
|
|
1459
|
+
// =========================================================================
|
|
1460
|
+
/**
|
|
1461
|
+
* Handles search input changes for filtering nav items.
|
|
1462
|
+
*
|
|
1463
|
+
* When a search query is entered, automatically expands any groups that
|
|
1464
|
+
* contain matching children so users can see the results. When the search
|
|
1465
|
+
* is cleared, collapses all groups back to their default state.
|
|
1466
|
+
*
|
|
1467
|
+
* @param e - The input event from the search field
|
|
1468
|
+
*/
|
|
1469
|
+
handleNavQueryChange(e) {
|
|
1470
|
+
this.navQuery = e.target.value;
|
|
1471
|
+
// Expand all groups when searching so matching children are visible
|
|
1472
|
+
// (groups without matching children won't be rendered anyway)
|
|
1473
|
+
if (this.navQuery) {
|
|
1474
|
+
this.getComputedNav().forEach(item => {
|
|
1475
|
+
if (item.type === 'group') {
|
|
1476
|
+
this.navItemsExpanded.add(item.id);
|
|
1477
|
+
}
|
|
1478
|
+
});
|
|
1479
|
+
}
|
|
1480
|
+
else {
|
|
1481
|
+
// Collapse all when search is cleared
|
|
1482
|
+
this.navItemsExpanded.clear();
|
|
1483
|
+
}
|
|
1484
|
+
}
|
|
1485
|
+
/**
|
|
1486
|
+
* Clears the search query.
|
|
1487
|
+
*/
|
|
1488
|
+
handleNavQueryClear() {
|
|
1489
|
+
this.navQuery = '';
|
|
1490
|
+
this.navItemsExpanded.clear();
|
|
1491
|
+
}
|
|
1492
|
+
// =========================================================================
|
|
1493
|
+
// Navigation Interaction
|
|
1494
|
+
// =========================================================================
|
|
1495
|
+
handleMenuClick() {
|
|
1496
|
+
this.isNavOpened = !this.isNavOpened;
|
|
1497
|
+
}
|
|
1498
|
+
/**
|
|
1499
|
+
* Handles scroll on nav content to show/hide header shadow.
|
|
1500
|
+
*/
|
|
1501
|
+
handleNavScroll(e) {
|
|
1502
|
+
this.isNavScrolled = e.target.scrollTop > 0;
|
|
1503
|
+
}
|
|
1504
|
+
/**
|
|
1505
|
+
* Toggles the expanded/collapsed state of a nav group with animation.
|
|
1506
|
+
* Uses the Web Animations API to animate height transitions.
|
|
1507
|
+
* When collapsing, the `nav-group--expanded` class is removed after animation completes.
|
|
1508
|
+
* When expanding, the class is added immediately before animation starts.
|
|
1509
|
+
*
|
|
1510
|
+
* @param itemId - The unique identifier of the nav group to toggle
|
|
1511
|
+
*/
|
|
1512
|
+
toggleNavItem(itemId) {
|
|
1513
|
+
const navItem = this.shadowRoot?.querySelector(`.nav-item[data-id="${itemId}"]`);
|
|
1514
|
+
const groupEl = navItem?.nextElementSibling;
|
|
1515
|
+
if (!groupEl)
|
|
1516
|
+
return;
|
|
1517
|
+
if (this.navItemsExpanded.has(itemId)) {
|
|
1518
|
+
this.navItemsExpanded.delete(itemId);
|
|
1519
|
+
groupEl.animate([
|
|
1520
|
+
{ height: `${groupEl.scrollHeight}px` },
|
|
1521
|
+
{ height: '0px' }
|
|
1522
|
+
], { duration: 300, easing: 'ease-in-out' }).onfinish = () => {
|
|
1523
|
+
groupEl.classList.remove('nav-group--expanded');
|
|
1524
|
+
};
|
|
1525
|
+
}
|
|
1526
|
+
else {
|
|
1527
|
+
this.navItemsExpanded.add(itemId);
|
|
1528
|
+
groupEl.classList.add('nav-group--expanded');
|
|
1529
|
+
groupEl.animate([
|
|
1530
|
+
{ height: '0px' },
|
|
1531
|
+
{ height: `${groupEl.scrollHeight}px` }
|
|
1532
|
+
], { duration: 300, easing: 'ease-in-out' });
|
|
1533
|
+
}
|
|
1534
|
+
this.requestUpdate();
|
|
1535
|
+
}
|
|
1536
|
+
/**
|
|
1537
|
+
* Handles click events on navigation items.
|
|
1538
|
+
*
|
|
1539
|
+
* For groups, toggles the expanded/collapsed state.
|
|
1540
|
+
* For items, dispatches a cancelable `nav-item-click` custom event.
|
|
1541
|
+
*
|
|
1542
|
+
* If a listener calls `preventDefault()` on the custom event, native browser
|
|
1543
|
+
* navigation is prevented. Otherwise, the default `<a href>` navigation
|
|
1544
|
+
* proceeds normally.
|
|
1545
|
+
*
|
|
1546
|
+
* The `@krubble/angular` package provides `KRScaffoldDirective` which
|
|
1547
|
+
* automatically listens for this event and handles navigation using
|
|
1548
|
+
* Angular's router. Import `KrubbleModule` in your Angular app to enable
|
|
1549
|
+
* SPA navigation without page reloads.
|
|
1550
|
+
*
|
|
1551
|
+
* @param e - The original click event
|
|
1552
|
+
* @param item - The nav item that was clicked
|
|
1553
|
+
*/
|
|
1554
|
+
handleNavItemClick(e, item) {
|
|
1555
|
+
if (item.type === 'group') {
|
|
1556
|
+
this.toggleNavItem(item.id);
|
|
1557
|
+
}
|
|
1558
|
+
else {
|
|
1559
|
+
const navEvent = new CustomEvent('nav-item-click', {
|
|
1560
|
+
detail: { item },
|
|
1561
|
+
bubbles: true,
|
|
1562
|
+
composed: true,
|
|
1563
|
+
cancelable: true,
|
|
1564
|
+
});
|
|
1565
|
+
this.dispatchEvent(navEvent);
|
|
1566
|
+
// If a listener called preventDefault(), prevent the native navigation
|
|
1567
|
+
if (navEvent.defaultPrevented) {
|
|
1568
|
+
e.preventDefault();
|
|
1569
|
+
}
|
|
1570
|
+
}
|
|
1571
|
+
}
|
|
1572
|
+
/**
|
|
1573
|
+
* Updates the active nav item highlight based on the current URL.
|
|
1574
|
+
*
|
|
1575
|
+
* Compares the first path segment of `window.location.pathname` with each
|
|
1576
|
+
* nav item's URL to find a match. When found, highlights that item and
|
|
1577
|
+
* expands its parent group if nested.
|
|
1578
|
+
*
|
|
1579
|
+
* Called automatically by `KRScaffoldDirective` on Angular route changes.
|
|
1580
|
+
*/
|
|
1581
|
+
updateActiveNavItem() {
|
|
1582
|
+
const currentPath = window.location.pathname;
|
|
1583
|
+
const allItems = this.getComputedNav().filter(item => item.type === 'item' && item.url);
|
|
1584
|
+
// Find the best match - prefer longer/more specific URL matches
|
|
1585
|
+
let activeItem = null;
|
|
1586
|
+
let longestMatch = 0;
|
|
1587
|
+
for (const item of allItems) {
|
|
1588
|
+
const url = item.url; // We filtered for items with url above
|
|
1589
|
+
if (currentPath.startsWith(url) && url.length > longestMatch) {
|
|
1590
|
+
activeItem = item;
|
|
1591
|
+
longestMatch = url.length;
|
|
1592
|
+
}
|
|
1593
|
+
}
|
|
1594
|
+
if (activeItem) {
|
|
1595
|
+
this.activeNavItemId = activeItem.id;
|
|
1596
|
+
if (activeItem.parentId) {
|
|
1597
|
+
this.navItemsExpanded.add(activeItem.parentId);
|
|
1598
|
+
}
|
|
1599
|
+
}
|
|
1600
|
+
else {
|
|
1601
|
+
this.activeNavItemId = null;
|
|
1602
|
+
}
|
|
1603
|
+
}
|
|
1604
|
+
// =========================================================================
|
|
1605
|
+
// Navigation Customization
|
|
1606
|
+
// =========================================================================
|
|
1607
|
+
startEditing() {
|
|
1608
|
+
this.isEditing = true;
|
|
1609
|
+
}
|
|
1610
|
+
cancelEditing() {
|
|
1611
|
+
this.isEditing = false;
|
|
1612
|
+
// Reload to revert changes
|
|
1613
|
+
this.loadPref();
|
|
1614
|
+
}
|
|
1615
|
+
resetPref() {
|
|
1616
|
+
this.pref.nav = {};
|
|
1617
|
+
}
|
|
1618
|
+
savePref() {
|
|
1619
|
+
const url = this.pref.uuid
|
|
1620
|
+
? `/api/system/preference/json/scaffold/${this.pref.uuid}?global=true`
|
|
1621
|
+
: `/api/system/preference/json/scaffold?global=true`;
|
|
1622
|
+
this.http.fetch({
|
|
1623
|
+
url,
|
|
1624
|
+
method: this.pref.uuid ? 'PUT' : 'POST',
|
|
1625
|
+
body: JSON.stringify({ nav: this.pref.nav }),
|
|
1626
|
+
})
|
|
1627
|
+
.then((response) => {
|
|
1628
|
+
this.pref = response.data;
|
|
1629
|
+
this.isEditing = false;
|
|
1630
|
+
})
|
|
1631
|
+
.catch((error) => {
|
|
1632
|
+
console.error('Failed to save nav customizations:', error);
|
|
1633
|
+
});
|
|
1634
|
+
}
|
|
1635
|
+
loadPref() {
|
|
1636
|
+
this.http.fetch({
|
|
1637
|
+
url: '/api/system/preference/json/scaffold?global=true',
|
|
1638
|
+
method: 'GET',
|
|
1639
|
+
})
|
|
1640
|
+
.then((response) => {
|
|
1641
|
+
const pref = response?.data?.[0];
|
|
1642
|
+
if (pref) {
|
|
1643
|
+
this.pref = pref;
|
|
1644
|
+
}
|
|
1645
|
+
})
|
|
1646
|
+
.catch(() => {
|
|
1647
|
+
// No preferences saved yet
|
|
1648
|
+
});
|
|
1649
|
+
}
|
|
1650
|
+
handleNavItemContextMenu(e, item) {
|
|
1651
|
+
if (!this.isEditing)
|
|
1652
|
+
return;
|
|
1653
|
+
e.preventDefault();
|
|
1654
|
+
KRContextMenu.open({
|
|
1655
|
+
x: e.clientX,
|
|
1656
|
+
y: e.clientY,
|
|
1657
|
+
items: [
|
|
1658
|
+
{ id: 'edit', label: 'Edit Item' },
|
|
1659
|
+
{ id: 'divider-1', label: '', divider: true },
|
|
1660
|
+
{ id: 'add-above', label: 'Add Item Above' },
|
|
1661
|
+
{ id: 'add-below', label: 'Add Item Below' },
|
|
1662
|
+
],
|
|
1663
|
+
}).then((result) => {
|
|
1664
|
+
if (!result)
|
|
1665
|
+
return;
|
|
1666
|
+
switch (result.id) {
|
|
1667
|
+
case 'edit':
|
|
1668
|
+
this.openNavItemEdit(item);
|
|
1669
|
+
break;
|
|
1670
|
+
case 'add-above':
|
|
1671
|
+
this.addNavItem(item, 'above');
|
|
1672
|
+
break;
|
|
1673
|
+
case 'add-below':
|
|
1674
|
+
this.addNavItem(item, 'below');
|
|
1675
|
+
break;
|
|
1676
|
+
}
|
|
1677
|
+
});
|
|
1678
|
+
}
|
|
1679
|
+
/**
|
|
1680
|
+
* Opens the nav item edit dialog for customizing an item's properties.
|
|
1681
|
+
*
|
|
1682
|
+
* Opens `KRNavItemEdit` dialog pre-populated with the item's current values.
|
|
1683
|
+
* When the user saves, merges the result into the nav delta to persist
|
|
1684
|
+
* customizations like label, icon, URL, and visibility.
|
|
1685
|
+
*
|
|
1686
|
+
* @param item - The nav item to edit
|
|
1687
|
+
*/
|
|
1688
|
+
openNavItemEdit(item) {
|
|
1689
|
+
KRDialog.open(KRNavItemEdit, { data: item }).afterClosed().then((res) => {
|
|
1690
|
+
const result = res;
|
|
1691
|
+
if (!result)
|
|
1692
|
+
return;
|
|
1693
|
+
if (!this.pref.nav[item.id]) {
|
|
1694
|
+
this.pref.nav[item.id] = {};
|
|
1695
|
+
}
|
|
1696
|
+
this.pref.nav[item.id] = {
|
|
1697
|
+
...this.pref.nav[item.id],
|
|
1698
|
+
...result
|
|
1699
|
+
};
|
|
1700
|
+
this.requestUpdate();
|
|
1701
|
+
});
|
|
1702
|
+
}
|
|
1703
|
+
/**
|
|
1704
|
+
* Adds a new custom nav item relative to an existing item.
|
|
1705
|
+
*
|
|
1706
|
+
* Creates a placeholder item in the nav delta, shifts existing siblings
|
|
1707
|
+
* to make room, then opens the edit dialog so the user can configure
|
|
1708
|
+
* the new item's label, icon, and URL.
|
|
1709
|
+
*
|
|
1710
|
+
* @param targetItem - The existing item to position relative to
|
|
1711
|
+
* @param position - Whether to insert 'above' or 'below' the target item
|
|
1712
|
+
*/
|
|
1713
|
+
addNavItem(targetItem, position) {
|
|
1714
|
+
const customId = `custom-${Date.now()}`;
|
|
1715
|
+
const siblings = this.getComputedNav()
|
|
1716
|
+
.filter(i => i.parentId === targetItem.parentId)
|
|
1717
|
+
.sort((a, b) => a.order - b.order);
|
|
1718
|
+
const targetIndex = siblings.findIndex(i => i.id === targetItem.id);
|
|
1719
|
+
const newOrder = position === 'above' ? targetIndex : targetIndex + 1;
|
|
1720
|
+
// Shift existing items to make room for the new item
|
|
1721
|
+
siblings.forEach((sibling, index) => {
|
|
1722
|
+
if (index >= newOrder) {
|
|
1723
|
+
if (!this.pref.nav[sibling.id]) {
|
|
1724
|
+
this.pref.nav[sibling.id] = {};
|
|
1725
|
+
}
|
|
1726
|
+
this.pref.nav[sibling.id].order = index + 1;
|
|
1727
|
+
}
|
|
1728
|
+
});
|
|
1729
|
+
// Add the new item and open edit dialog
|
|
1730
|
+
const newItem = {
|
|
1731
|
+
id: customId,
|
|
1732
|
+
type: 'item',
|
|
1733
|
+
label: 'New Item',
|
|
1734
|
+
order: newOrder,
|
|
1735
|
+
parentId: targetItem.parentId,
|
|
1736
|
+
active: true,
|
|
1737
|
+
};
|
|
1738
|
+
this.pref.nav[customId] = newItem;
|
|
1739
|
+
this.requestUpdate();
|
|
1740
|
+
this.openNavItemEdit(newItem);
|
|
1741
|
+
}
|
|
1742
|
+
// =========================================================================
|
|
1743
|
+
// Navigation Drag & Drop
|
|
1744
|
+
// =========================================================================
|
|
1745
|
+
/**
|
|
1746
|
+
* Initiates a potential drag operation when mouse is pressed on a nav item.
|
|
1747
|
+
*
|
|
1748
|
+
* Only active in edit mode. Records the starting position and sets up
|
|
1749
|
+
* document-level listeners for mouse move and mouse up events. The actual
|
|
1750
|
+
* drag doesn't start until the mouse moves more than 5 pixels.
|
|
1751
|
+
*
|
|
1752
|
+
* @param e - The mouse event
|
|
1753
|
+
* @param item - The nav item being pressed
|
|
1754
|
+
*/
|
|
1755
|
+
handleNavItemMouseDown(e, item) {
|
|
1756
|
+
if (!this.isEditing)
|
|
1757
|
+
return;
|
|
1758
|
+
e.preventDefault();
|
|
1759
|
+
this.draggedNavItemId = item.id;
|
|
1760
|
+
this.navItemDragStartY = e.clientY;
|
|
1761
|
+
this.isNavItemDragging = false;
|
|
1762
|
+
document.addEventListener('mousemove', this.boundHandleMouseMove);
|
|
1763
|
+
document.addEventListener('mouseup', this.boundHandleMouseUp);
|
|
1764
|
+
}
|
|
1765
|
+
/**
|
|
1766
|
+
* Handles mouse movement during a nav item drag operation.
|
|
1767
|
+
*
|
|
1768
|
+
* Initiates dragging after the mouse moves more than 5 pixels (to distinguish
|
|
1769
|
+
* from clicks), creates a floating preview element that follows the cursor,
|
|
1770
|
+
* and updates the drop target indicator based on mouse position.
|
|
1771
|
+
*
|
|
1772
|
+
* @param e - The mouse event
|
|
1773
|
+
*/
|
|
1774
|
+
handleMouseMove(e) {
|
|
1775
|
+
if (!this.draggedNavItemId)
|
|
1776
|
+
return;
|
|
1777
|
+
// Start dragging after moving a few pixels (to distinguish from clicks)
|
|
1778
|
+
if (!this.isNavItemDragging && Math.abs(e.clientY - this.navItemDragStartY) > 5) {
|
|
1779
|
+
this.isNavItemDragging = true;
|
|
1780
|
+
this.classList.add('kr-scaffold--dragging');
|
|
1781
|
+
// Create drag preview
|
|
1782
|
+
const draggedItem = this.getComputedNav().find(i => i.id === this.draggedNavItemId);
|
|
1783
|
+
if (draggedItem) {
|
|
1784
|
+
this.navItemDragPreview = document.createElement('div');
|
|
1785
|
+
this.navItemDragPreview.className = 'nav-item-drag-preview';
|
|
1786
|
+
this.navItemDragPreview.textContent = draggedItem.label;
|
|
1787
|
+
this.navItemDragPreview.style.left = `${e.clientX + 10}px`;
|
|
1788
|
+
this.navItemDragPreview.style.top = `${e.clientY - 20}px`;
|
|
1789
|
+
this.shadowRoot?.appendChild(this.navItemDragPreview);
|
|
1790
|
+
}
|
|
1791
|
+
}
|
|
1792
|
+
if (!this.isNavItemDragging)
|
|
1793
|
+
return;
|
|
1794
|
+
// Update preview position
|
|
1795
|
+
if (this.navItemDragPreview) {
|
|
1796
|
+
this.navItemDragPreview.style.left = `${e.clientX + 10}px`;
|
|
1797
|
+
this.navItemDragPreview.style.top = `${e.clientY - 20}px`;
|
|
1798
|
+
}
|
|
1799
|
+
// Find drop target based on mouse position
|
|
1800
|
+
this.updateNavItemDropTarget(e);
|
|
1801
|
+
}
|
|
1802
|
+
/**
|
|
1803
|
+
* Handles the end of a nav item drag operation.
|
|
1804
|
+
*
|
|
1805
|
+
* Removes document-level mouse listeners, executes the drop if there's a valid
|
|
1806
|
+
* target, and cleans up all drag-related state.
|
|
1807
|
+
*/
|
|
1808
|
+
handleMouseUp() {
|
|
1809
|
+
document.removeEventListener('mousemove', this.boundHandleMouseMove);
|
|
1810
|
+
document.removeEventListener('mouseup', this.boundHandleMouseUp);
|
|
1811
|
+
if (this.isNavItemDragging && this.navItemDropTargetId) {
|
|
1812
|
+
this.executeNavItemDrop();
|
|
1813
|
+
}
|
|
1814
|
+
// Reset all drag state
|
|
1815
|
+
this.draggedNavItemId = null;
|
|
1816
|
+
this.navItemDropTargetId = null;
|
|
1817
|
+
this.isNavItemDragging = false;
|
|
1818
|
+
this.classList.remove('kr-scaffold--dragging');
|
|
1819
|
+
if (this.navItemDragPreview) {
|
|
1820
|
+
this.navItemDragPreview.remove();
|
|
1821
|
+
this.navItemDragPreview = null;
|
|
1822
|
+
}
|
|
1823
|
+
this.clearNavItemDragExpandTimeout();
|
|
1824
|
+
this.requestUpdate();
|
|
1825
|
+
}
|
|
1826
|
+
/**
|
|
1827
|
+
* Determines where a dragged nav item would be dropped based on mouse position.
|
|
1828
|
+
*
|
|
1829
|
+
* Loops through all nav items and checks if the mouse is hovering over one.
|
|
1830
|
+
* For groups, the item is divided into thirds (above/center/below) where
|
|
1831
|
+
* "center" means drop into the group. For regular items, it's halves (above/below).
|
|
1832
|
+
*
|
|
1833
|
+
* Also handles auto-expanding collapsed groups when hovering over their center,
|
|
1834
|
+
* and validates drops (e.g., prevents dropping a group into another group).
|
|
1835
|
+
*
|
|
1836
|
+
* Updates `navItemDropTargetId` and `navItemDropPosition` state which are used to render
|
|
1837
|
+
* visual drop indicators in the UI.
|
|
1838
|
+
*
|
|
1839
|
+
* @param e - The mouse event from dragging
|
|
1840
|
+
*/
|
|
1841
|
+
updateNavItemDropTarget(e) {
|
|
1842
|
+
// Get all nav items in shadow DOM
|
|
1843
|
+
const navItems = this.shadowRoot?.querySelectorAll('.nav-item[data-id]');
|
|
1844
|
+
if (!navItems)
|
|
1845
|
+
return;
|
|
1846
|
+
let foundTarget = false;
|
|
1847
|
+
navItems.forEach((el) => {
|
|
1848
|
+
const rect = el.getBoundingClientRect();
|
|
1849
|
+
const itemId = el.getAttribute('data-id');
|
|
1850
|
+
if (!itemId || itemId === this.draggedNavItemId)
|
|
1851
|
+
return;
|
|
1852
|
+
// Skip if mouse is outside this element
|
|
1853
|
+
if (e.clientX < rect.left || e.clientX > rect.right ||
|
|
1854
|
+
e.clientY < rect.top || e.clientY > rect.bottom) {
|
|
1855
|
+
return;
|
|
1856
|
+
}
|
|
1857
|
+
const item = this.getComputedNav().find(i => i.id === itemId);
|
|
1858
|
+
if (!item) {
|
|
1859
|
+
return;
|
|
1860
|
+
}
|
|
1861
|
+
const y = e.clientY - rect.top;
|
|
1862
|
+
let position;
|
|
1863
|
+
// Determine drop position based on mouse Y position within the item.
|
|
1864
|
+
// Groups are divided into thirds: top=above, middle=center (drop into), bottom=below.
|
|
1865
|
+
// Regular items are divided in half: top=above, bottom=below.
|
|
1866
|
+
if (item.type === 'group') {
|
|
1867
|
+
if (y < rect.height / 3) {
|
|
1868
|
+
// Top third - drop above the group
|
|
1869
|
+
position = 'above';
|
|
1870
|
+
this.clearNavItemDragExpandTimeout();
|
|
1871
|
+
}
|
|
1872
|
+
else if (y > (rect.height * 2) / 3) {
|
|
1873
|
+
// Bottom third - drop below the group
|
|
1874
|
+
position = 'below';
|
|
1875
|
+
this.clearNavItemDragExpandTimeout();
|
|
1876
|
+
}
|
|
1877
|
+
else {
|
|
1878
|
+
// Middle third - drop into the group (as a child)
|
|
1879
|
+
position = 'center';
|
|
1880
|
+
// Auto-expand collapsed groups after a delay
|
|
1881
|
+
if (!this.navItemsExpanded.has(item.id) && !this.navItemDragExpandTimeout) {
|
|
1882
|
+
this.expandNavGroupOnDrag(item.id);
|
|
1883
|
+
}
|
|
1884
|
+
}
|
|
1885
|
+
}
|
|
1886
|
+
else {
|
|
1887
|
+
// Regular items only support above/below, not center
|
|
1888
|
+
position = y < rect.height / 2 ? 'above' : 'below';
|
|
1889
|
+
this.clearNavItemDragExpandTimeout();
|
|
1890
|
+
}
|
|
1891
|
+
// Determine what the new parent would be
|
|
1892
|
+
const newParentId = position === 'center' ? item.id : item.parentId;
|
|
1893
|
+
const draggedItem = this.getComputedNav().find(i => i.id === this.draggedNavItemId);
|
|
1894
|
+
// Don't show drop indicator if invalid drop:
|
|
1895
|
+
// 1. Can't drop an item into itself (newParentId === draggedNavItemId)
|
|
1896
|
+
// 2. Groups can only exist at the top level, so prevent dropping a group into another group (newParentId !== null means it would be nested)
|
|
1897
|
+
if (newParentId === this.draggedNavItemId || (draggedItem?.type === 'group' && newParentId !== null)) {
|
|
1898
|
+
this.navItemDropTargetId = null;
|
|
1899
|
+
this.clearNavItemDragExpandTimeout();
|
|
1900
|
+
return;
|
|
1901
|
+
}
|
|
1902
|
+
// Valid drop target found - store the target item and position (above/below/center)
|
|
1903
|
+
// so the UI can render the drop indicator in the correct location
|
|
1904
|
+
this.navItemDropTargetId = item.id;
|
|
1905
|
+
this.navItemDropPosition = position;
|
|
1906
|
+
foundTarget = true;
|
|
1907
|
+
});
|
|
1908
|
+
// Mouse is not over any valid nav item - clear the drop indicator
|
|
1909
|
+
// (e.g., user dragged outside the nav area or over empty space)
|
|
1910
|
+
if (!foundTarget) {
|
|
1911
|
+
this.navItemDropTargetId = null;
|
|
1912
|
+
this.clearNavItemDragExpandTimeout();
|
|
1913
|
+
}
|
|
1914
|
+
this.requestUpdate();
|
|
1915
|
+
}
|
|
1916
|
+
/**
|
|
1917
|
+
* Cancels the pending auto-expand timeout for nav groups during drag.
|
|
1918
|
+
* Called when the user moves away from a group's center or stops dragging.
|
|
1919
|
+
*/
|
|
1920
|
+
clearNavItemDragExpandTimeout() {
|
|
1921
|
+
if (this.navItemDragExpandTimeout) {
|
|
1922
|
+
clearTimeout(this.navItemDragExpandTimeout);
|
|
1923
|
+
this.navItemDragExpandTimeout = null;
|
|
1924
|
+
}
|
|
1925
|
+
}
|
|
1926
|
+
/**
|
|
1927
|
+
* Auto-expands a collapsed nav group after a delay while dragging over it.
|
|
1928
|
+
*
|
|
1929
|
+
* Uses a 500ms timeout to prevent groups from expanding too eagerly when the
|
|
1930
|
+
* user is just passing over them. Only expands if the user is still hovering
|
|
1931
|
+
* over the group's center when the timeout fires.
|
|
1932
|
+
*
|
|
1933
|
+
* @param itemId - The ID of the group to expand
|
|
1934
|
+
*/
|
|
1935
|
+
expandNavGroupOnDrag(itemId) {
|
|
1936
|
+
this.navItemDragExpandTimeout = window.setTimeout(() => {
|
|
1937
|
+
if (this.navItemDropTargetId === itemId && this.navItemDropPosition === 'center') {
|
|
1938
|
+
this.navItemsExpanded.add(itemId);
|
|
1939
|
+
this.requestUpdate();
|
|
1940
|
+
}
|
|
1941
|
+
this.navItemDragExpandTimeout = null;
|
|
1942
|
+
}, 500);
|
|
1943
|
+
}
|
|
1944
|
+
/**
|
|
1945
|
+
* Executes the drop operation after a nav item drag completes.
|
|
1946
|
+
*
|
|
1947
|
+
* Calculates the new position and parent for the dragged item based on
|
|
1948
|
+
* the drop target and position (above/below/center). Updates the navDelta
|
|
1949
|
+
* with new order values for the dragged item and shifts siblings as needed.
|
|
1950
|
+
*/
|
|
1951
|
+
executeNavItemDrop() {
|
|
1952
|
+
if (!this.draggedNavItemId || !this.navItemDropTargetId)
|
|
1953
|
+
return;
|
|
1954
|
+
const allItems = this.getComputedNav();
|
|
1955
|
+
const draggedItem = allItems.find(i => i.id === this.draggedNavItemId);
|
|
1956
|
+
const targetItem = allItems.find(i => i.id === this.navItemDropTargetId);
|
|
1957
|
+
if (!draggedItem || !targetItem)
|
|
1958
|
+
return;
|
|
1959
|
+
// Determine the new parentId for the dragged item
|
|
1960
|
+
let newParentId;
|
|
1961
|
+
if (this.navItemDropPosition === 'center' && targetItem.type === 'group') {
|
|
1962
|
+
if (targetItem.id === this.draggedNavItemId) {
|
|
1963
|
+
return;
|
|
1964
|
+
}
|
|
1965
|
+
newParentId = targetItem.id;
|
|
1966
|
+
}
|
|
1967
|
+
else {
|
|
1968
|
+
newParentId = targetItem.parentId;
|
|
1969
|
+
}
|
|
1970
|
+
if (newParentId === this.draggedNavItemId)
|
|
1971
|
+
return;
|
|
1972
|
+
// Get siblings in the destination parent
|
|
1973
|
+
const siblings = allItems
|
|
1974
|
+
.filter(i => i.parentId === newParentId && i.id !== this.draggedNavItemId)
|
|
1975
|
+
.sort((a, b) => a.order - b.order);
|
|
1976
|
+
const navDelta = this.pref.nav;
|
|
1977
|
+
// Calculate the new order
|
|
1978
|
+
let newOrder;
|
|
1979
|
+
if (this.navItemDropPosition === 'center') {
|
|
1980
|
+
// Dropping into a group - place at the end of its children
|
|
1981
|
+
if (siblings.length > 0) {
|
|
1982
|
+
newOrder = Math.max(...siblings.map(s => s.order)) + 1;
|
|
1983
|
+
}
|
|
1984
|
+
else {
|
|
1985
|
+
newOrder = 0;
|
|
1986
|
+
}
|
|
1987
|
+
}
|
|
1988
|
+
else {
|
|
1989
|
+
// Find where the target item is in the sorted siblings list
|
|
1990
|
+
const targetIndex = siblings.findIndex(i => i.id === targetItem.id);
|
|
1991
|
+
// Determine insert position based on drop position (above/below target)
|
|
1992
|
+
if (targetIndex === -1) {
|
|
1993
|
+
newOrder = 0;
|
|
1994
|
+
}
|
|
1995
|
+
else if (this.navItemDropPosition === 'above') {
|
|
1996
|
+
newOrder = targetIndex;
|
|
1997
|
+
}
|
|
1998
|
+
else {
|
|
1999
|
+
newOrder = targetIndex + 1;
|
|
2000
|
+
}
|
|
2001
|
+
// Shift existing siblings to make room for the dropped item.
|
|
2002
|
+
// Items at or after the insert position get their order bumped up by 1.
|
|
2003
|
+
siblings.forEach((sibling, index) => {
|
|
2004
|
+
if (!navDelta[sibling.id]) {
|
|
2005
|
+
navDelta[sibling.id] = {};
|
|
2006
|
+
}
|
|
2007
|
+
if (index >= newOrder) {
|
|
2008
|
+
navDelta[sibling.id].order = index + 1;
|
|
2009
|
+
}
|
|
2010
|
+
else {
|
|
2011
|
+
navDelta[sibling.id].order = index;
|
|
2012
|
+
}
|
|
2013
|
+
});
|
|
2014
|
+
}
|
|
2015
|
+
// Update the dragged item's position and parent in the delta
|
|
2016
|
+
if (!navDelta[this.draggedNavItemId]) {
|
|
2017
|
+
navDelta[this.draggedNavItemId] = {};
|
|
2018
|
+
}
|
|
2019
|
+
navDelta[this.draggedNavItemId].order = newOrder;
|
|
2020
|
+
navDelta[this.draggedNavItemId].parentId = newParentId;
|
|
2021
|
+
// Persist changes and trigger re-render
|
|
2022
|
+
this.pref.nav = navDelta;
|
|
2023
|
+
this.requestUpdate();
|
|
2024
|
+
}
|
|
2025
|
+
// =========================================================================
|
|
2026
|
+
// User Menu
|
|
2027
|
+
// =========================================================================
|
|
2028
|
+
toggleUserMenu() {
|
|
2029
|
+
this.isUserMenuOpen = !this.isUserMenuOpen;
|
|
2030
|
+
}
|
|
2031
|
+
closeUserMenu() {
|
|
2032
|
+
this.isUserMenuOpen = false;
|
|
2033
|
+
}
|
|
2034
|
+
handleCustomize() {
|
|
2035
|
+
this.closeUserMenu();
|
|
2036
|
+
this.startEditing();
|
|
2037
|
+
}
|
|
2038
|
+
// =========================================================================
|
|
2039
|
+
// Rendering
|
|
2040
|
+
// =========================================================================
|
|
2041
|
+
renderNormalFooter() {
|
|
2042
|
+
if (!this.user)
|
|
2043
|
+
return E;
|
|
2044
|
+
const initials = this.user.name
|
|
2045
|
+
.split(' ')
|
|
2046
|
+
.map((part) => part[0])
|
|
2047
|
+
.join('')
|
|
2048
|
+
.toUpperCase()
|
|
2049
|
+
.slice(0, 2);
|
|
2050
|
+
// todo - use a reusable menu component
|
|
2051
|
+
return x `
|
|
2052
|
+
<div class="user-menu-container">
|
|
2053
|
+
${this.isUserMenuOpen ? x `
|
|
2054
|
+
<div class="user-menu">
|
|
2055
|
+
<button class="user-menu__item" @click=${this.handleCustomize}>
|
|
2056
|
+
Customize Navigation
|
|
2057
|
+
</button>
|
|
2058
|
+
</div>
|
|
2059
|
+
` : E}
|
|
2060
|
+
<div class="user" @click=${this.toggleUserMenu}>
|
|
2061
|
+
<div class="user__avatar">
|
|
2062
|
+
${this.user.avatar
|
|
2063
|
+
? x `<img src=${this.user.avatar} alt=${this.user.name} />`
|
|
2064
|
+
: initials}
|
|
2065
|
+
</div>
|
|
2066
|
+
<div class="user__info">
|
|
2067
|
+
<div class="user__name">${this.user.name}</div>
|
|
2068
|
+
${this.user.email ? x `<div class="user__email">${this.user.email}</div>` : E}
|
|
2069
|
+
</div>
|
|
2070
|
+
</div>
|
|
2071
|
+
</div>
|
|
2072
|
+
`;
|
|
2073
|
+
}
|
|
2074
|
+
// todo - review
|
|
2075
|
+
renderEditFooter() {
|
|
2076
|
+
return x `
|
|
2077
|
+
<div class="edit-actions">
|
|
2078
|
+
<button class="edit-actions__btn edit-actions__btn--primary" @click=${this.savePref}>
|
|
2079
|
+
Save
|
|
2080
|
+
</button>
|
|
2081
|
+
<div class="edit-actions__secondary">
|
|
2082
|
+
<button class="edit-actions__btn" @click=${this.resetPref}>
|
|
2083
|
+
Reset
|
|
2084
|
+
</button>
|
|
2085
|
+
<button class="edit-actions__btn" @click=${this.cancelEditing}>
|
|
2086
|
+
Cancel
|
|
2087
|
+
</button>
|
|
2088
|
+
</div>
|
|
2089
|
+
</div>
|
|
2090
|
+
`;
|
|
2091
|
+
}
|
|
2092
|
+
/**
|
|
2093
|
+
* Renders a navigation item as either a group or a link.
|
|
2094
|
+
*
|
|
2095
|
+
* Groups render as expandable `<button>` elements with a chevron indicator and
|
|
2096
|
+
* nested children. Items render as `<a>` anchor links with href navigation.
|
|
2097
|
+
*
|
|
2098
|
+
* Both types support drag-and-drop reordering in edit mode, context menus for
|
|
2099
|
+
* customization, and visual indicators for active/hidden/drop-target states.
|
|
2100
|
+
*
|
|
2101
|
+
* Icons use SVG strings and fall back to `defaultNavItemIcon` when not provided.
|
|
2102
|
+
* Icons are only rendered for top-level items, not for children nested within groups.
|
|
2103
|
+
*
|
|
2104
|
+
* @param item - The flat nav item data to render
|
|
2105
|
+
* @param isChild - Whether this item is nested within a group (affects icon rendering)
|
|
2106
|
+
* @returns Lit HTML template for the nav item
|
|
2107
|
+
*/
|
|
2108
|
+
renderNavItem(item, isChild = false) {
|
|
2109
|
+
if (item.type === 'section') {
|
|
2110
|
+
return x `
|
|
2111
|
+
<div class="nav-section">${item.label}</div>
|
|
2112
|
+
`;
|
|
2113
|
+
}
|
|
2114
|
+
if (item.type === 'group') {
|
|
2115
|
+
const children = this.getNavItemChildren(item.id);
|
|
2116
|
+
return x `
|
|
2117
|
+
<button
|
|
2118
|
+
class=${e$1({
|
|
2119
|
+
'nav-item': true,
|
|
2120
|
+
'nav-item--expanded': this.navItemsExpanded.has(item.id),
|
|
2121
|
+
'nav-item--hidden': this.isEditing && item.active === false,
|
|
2122
|
+
'nav-item--drop-above': this.navItemDropTargetId === item.id && this.navItemDropPosition === 'above',
|
|
2123
|
+
'nav-item--drop-below': this.navItemDropTargetId === item.id && this.navItemDropPosition === 'below',
|
|
2124
|
+
'nav-item--drop-center': this.navItemDropTargetId === item.id && this.navItemDropPosition === 'center',
|
|
2125
|
+
})}
|
|
2126
|
+
data-id="${item.id}"
|
|
2127
|
+
@mousedown=${(e) => this.handleNavItemMouseDown(e, item)}
|
|
2128
|
+
@click=${(e) => this.handleNavItemClick(e, item)}
|
|
2129
|
+
@contextmenu=${(e) => this.handleNavItemContextMenu(e, item)}
|
|
2130
|
+
>
|
|
2131
|
+
${this.navIconsDisplayed ? x `<span class="nav-item__icon">${o(item.icon || this.defaultNavItemIcon)}</span>` : E}
|
|
2132
|
+
<span class="nav-item__label">${item.label}</span>
|
|
2133
|
+
<svg
|
|
2134
|
+
class="nav-item__chevron"
|
|
2135
|
+
viewBox="0 0 24 24"
|
|
2136
|
+
fill="none"
|
|
2137
|
+
stroke="currentColor"
|
|
2138
|
+
stroke-width="2"
|
|
2139
|
+
>
|
|
2140
|
+
<path d="M6 9l6 6 6-6" stroke-linecap="round" stroke-linejoin="round" />
|
|
2141
|
+
</svg>
|
|
2142
|
+
</button>
|
|
2143
|
+
<div class=${e$1({
|
|
2144
|
+
'nav-group': true,
|
|
2145
|
+
'nav-group--expanded': this.navItemsExpanded.has(item.id),
|
|
2146
|
+
})}>
|
|
2147
|
+
${children.map((child) => this.renderNavItem(child, true))}
|
|
2148
|
+
</div>
|
|
2149
|
+
`;
|
|
2150
|
+
}
|
|
2151
|
+
return x `
|
|
2152
|
+
<a
|
|
2153
|
+
class=${e$1({
|
|
2154
|
+
'nav-item': true,
|
|
2155
|
+
'nav-item--active': this.activeNavItemId === item.id,
|
|
2156
|
+
'nav-item--hidden': this.isEditing && item.active === false,
|
|
2157
|
+
'nav-item--drop-above': this.navItemDropTargetId === item.id && this.navItemDropPosition === 'above',
|
|
2158
|
+
'nav-item--drop-below': this.navItemDropTargetId === item.id && this.navItemDropPosition === 'below',
|
|
2159
|
+
})}
|
|
2160
|
+
data-id="${item.id}"
|
|
2161
|
+
href=${item.url || '#'}
|
|
2162
|
+
@mousedown=${(e) => this.handleNavItemMouseDown(e, item)}
|
|
2163
|
+
@click=${(e) => this.handleNavItemClick(e, item)}
|
|
2164
|
+
@contextmenu=${(e) => this.handleNavItemContextMenu(e, item)}
|
|
2165
|
+
>
|
|
2166
|
+
${this.navIconsDisplayed && !isChild ? x `<span class="nav-item__icon">${o(item.icon || this.defaultNavItemIcon)}</span>` : E}
|
|
2167
|
+
<span class="nav-item__label">${item.label}</span>
|
|
2168
|
+
</a>
|
|
2169
|
+
`;
|
|
2170
|
+
}
|
|
2171
|
+
// todo - don't hardcode breadcrumbs
|
|
2172
|
+
render() {
|
|
2173
|
+
const topLevelItems = this.getNavItemChildren(null);
|
|
2174
|
+
// Use unfiltered count to determine if search should show (so it doesn't disappear while typing)
|
|
2175
|
+
const totalTopLevelItems = this.getComputedNav().filter(item => item.parentId === null).length;
|
|
2176
|
+
return x `
|
|
2177
|
+
<div class=${e$1({
|
|
2178
|
+
'progress': true,
|
|
2179
|
+
'progress--loading': this.pendingRequests > 0,
|
|
2180
|
+
})}>
|
|
2181
|
+
<div class="progress__track"></div>
|
|
2182
|
+
<div class="progress__bar progress__bar--primary">
|
|
2183
|
+
<span class="progress__bar-inner"></span>
|
|
2184
|
+
</div>
|
|
2185
|
+
<div class="progress__bar progress__bar--secondary">
|
|
2186
|
+
<span class="progress__bar-inner"></span>
|
|
2187
|
+
</div>
|
|
2188
|
+
</div>
|
|
2189
|
+
|
|
2190
|
+
${this.subbar ? x `
|
|
2191
|
+
<kr-subbar menu .breadcrumbs=${this.breadcrumbs} @menu-click=${this.handleMenuClick}>
|
|
2192
|
+
<slot name="subbar"></slot>
|
|
2193
|
+
</kr-subbar>
|
|
2194
|
+
` : E}
|
|
2195
|
+
|
|
2196
|
+
<div class="wrapper">
|
|
2197
|
+
<nav class=${e$1({
|
|
2198
|
+
'nav': true,
|
|
2199
|
+
'nav--scrolled': this.isNavScrolled,
|
|
2200
|
+
'nav--opened': !this.subbar || this.isNavOpened,
|
|
2201
|
+
})}>
|
|
2202
|
+
<div class="nav-header">
|
|
2203
|
+
${this.title
|
|
2204
|
+
? x `<span class="nav-title">${this.title}</span>`
|
|
2205
|
+
: this.logo
|
|
2206
|
+
? x `<img class="nav-logo" src=${this.logo} alt="Logo" />`
|
|
2207
|
+
: E}
|
|
2208
|
+
</div>
|
|
2209
|
+
<div class="nav-content" @scroll=${this.handleNavScroll}>
|
|
2210
|
+
${totalTopLevelItems > 20 ? x `
|
|
2211
|
+
<div class="nav-search">
|
|
2212
|
+
<div class="nav-search__wrapper">
|
|
2213
|
+
<span class="nav-search__icon">
|
|
2214
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor">
|
|
2215
|
+
<path d="M15.5 14h-.79l-.28-.27C15.41 12.59 16 11.11 16 9.5 16 5.91 13.09 3 9.5 3S3 5.91 3 9.5 5.91 16 9.5 16c1.61 0 3.09-.59 4.23-1.57l.27.28v.79l5 4.99L20.49 19l-4.99-5zm-6 0C7.01 14 5 11.99 5 9.5S7.01 5 9.5 5 14 7.01 14 9.5 11.99 14 9.5 14z"/>
|
|
2216
|
+
</svg>
|
|
2217
|
+
</span>
|
|
2218
|
+
<input
|
|
2219
|
+
class="nav-search__input"
|
|
2220
|
+
type="text"
|
|
2221
|
+
placeholder="Search..."
|
|
2222
|
+
.value=${this.navQuery}
|
|
2223
|
+
@input=${this.handleNavQueryChange}
|
|
2224
|
+
/>
|
|
2225
|
+
${this.navQuery ? x `
|
|
2226
|
+
<button class="nav-search__clear" @click=${this.handleNavQueryClear}>
|
|
2227
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor">
|
|
2228
|
+
<path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z"/>
|
|
2229
|
+
</svg>
|
|
2230
|
+
</button>
|
|
2231
|
+
` : E}
|
|
2232
|
+
</div>
|
|
2233
|
+
</div>
|
|
2234
|
+
` : E}
|
|
2235
|
+
<div class="nav-items">
|
|
2236
|
+
${topLevelItems.map((item) => this.renderNavItem(item))}
|
|
2237
|
+
</div>
|
|
2238
|
+
</div>
|
|
2239
|
+
<!-- <div class="nav-footer">
|
|
2240
|
+
${this.isEditing ? this.renderEditFooter() : this.renderNormalFooter()}
|
|
2241
|
+
</div> -->
|
|
2242
|
+
</nav>
|
|
2243
|
+
|
|
2244
|
+
<main class="main">
|
|
2245
|
+
<slot></slot>
|
|
2246
|
+
</main>
|
|
2247
|
+
</div>
|
|
2248
|
+
|
|
2249
|
+
`;
|
|
2250
|
+
}
|
|
2251
|
+
};
|
|
2252
|
+
KRScaffold.styles = i$4 `
|
|
2253
|
+
:host {
|
|
2254
|
+
display: flex;
|
|
2255
|
+
flex-direction: column;
|
|
2256
|
+
width: 100%;
|
|
2257
|
+
height: 100%;
|
|
2258
|
+
font-size: 14px;
|
|
2259
|
+
--kr-scaffold-nav-width: 210px;
|
|
2260
|
+
|
|
2261
|
+
/* Default to light scheme */
|
|
2262
|
+
--kr-scaffold-nav-bg: #ffffff;
|
|
2263
|
+
--kr-scaffold-nav-text: #000000;
|
|
2264
|
+
--kr-scaffold-nav-text-active: #000000;
|
|
2265
|
+
--kr-scaffold-nav-hover: #f3f4f6;
|
|
2266
|
+
--kr-scaffold-nav-active-bg: #e5e7eb;
|
|
2267
|
+
--kr-scaffold-nav-border: rgb(229, 229, 228);
|
|
2268
|
+
--kr-scaffold-nav-divider: #e5e7eb;
|
|
2269
|
+
--kr-scaffold-nav-search-bg: #f3f4f6;
|
|
2270
|
+
--kr-scaffold-nav-search-focus-bg: #ffffff;
|
|
2271
|
+
--kr-scaffold-nav-search-focus-border: #d1d5db;
|
|
2272
|
+
}
|
|
2273
|
+
|
|
2274
|
+
/* Dark scheme */
|
|
2275
|
+
:host([scheme="dark"]) {
|
|
2276
|
+
--kr-scaffold-nav-bg: #10172a;
|
|
2277
|
+
--kr-scaffold-nav-text: rgb(255 255 255 / 80%);
|
|
2278
|
+
--kr-scaffold-nav-text-active: #ffffff;
|
|
2279
|
+
--kr-scaffold-nav-hover: rgba(255, 255, 255, 0.05);
|
|
2280
|
+
--kr-scaffold-nav-active-bg: rgb(255 255 255 / 12%);
|
|
2281
|
+
--kr-scaffold-nav-border: transparent;
|
|
2282
|
+
--kr-scaffold-nav-divider: rgba(255, 255, 255, 0.06);
|
|
2283
|
+
--kr-scaffold-nav-search-bg: rgba(255, 255, 255, 0.15);
|
|
2284
|
+
--kr-scaffold-nav-search-focus-bg: rgba(255, 255, 255, 0.2);
|
|
2285
|
+
--kr-scaffold-nav-search-focus-border: rgba(255, 255, 255, 0.3);
|
|
2286
|
+
}
|
|
2287
|
+
|
|
2288
|
+
*,
|
|
2289
|
+
*::before,
|
|
2290
|
+
*::after {
|
|
2291
|
+
box-sizing: border-box;
|
|
2292
|
+
}
|
|
2293
|
+
|
|
2294
|
+
/* Nav */
|
|
2295
|
+
.nav {
|
|
2296
|
+
width: var(--kr-scaffold-nav-width);
|
|
2297
|
+
background: var(--kr-scaffold-nav-bg);
|
|
2298
|
+
color: var(--kr-scaffold-nav-text);
|
|
2299
|
+
display: flex;
|
|
2300
|
+
flex-direction: column;
|
|
2301
|
+
flex-shrink: 0;
|
|
2302
|
+
overflow: hidden;
|
|
2303
|
+
border-right: 1px solid var(--kr-scaffold-nav-border);
|
|
2304
|
+
transition: margin-left 0.3s ease;
|
|
2305
|
+
margin-left: calc(-1 * var(--kr-scaffold-nav-width));
|
|
2306
|
+
}
|
|
2307
|
+
|
|
2308
|
+
.nav--opened {
|
|
2309
|
+
margin-left: 0;
|
|
2310
|
+
}
|
|
2311
|
+
|
|
2312
|
+
.wrapper {
|
|
2313
|
+
display: flex;
|
|
2314
|
+
flex: 1;
|
|
2315
|
+
min-height: 0;
|
|
2316
|
+
}
|
|
2317
|
+
|
|
2318
|
+
.nav-header {
|
|
2319
|
+
height: 64px;
|
|
2320
|
+
padding: 0 20px;
|
|
2321
|
+
display: flex;
|
|
2322
|
+
align-items: center;
|
|
2323
|
+
position: relative;
|
|
2324
|
+
z-index: 1;
|
|
2325
|
+
transition: box-shadow 0.2s ease;
|
|
2326
|
+
}
|
|
2327
|
+
|
|
2328
|
+
.nav--scrolled .nav-header {
|
|
2329
|
+
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
|
|
2330
|
+
}
|
|
2331
|
+
|
|
2332
|
+
.nav-logo {
|
|
2333
|
+
max-width: 100%;
|
|
2334
|
+
height: 32px;
|
|
2335
|
+
}
|
|
2336
|
+
|
|
2337
|
+
.nav-title {
|
|
2338
|
+
font-size: 18px;
|
|
2339
|
+
font-weight: 600;
|
|
2340
|
+
color: var(--kr-scaffold-nav-text-active);
|
|
2341
|
+
letter-spacing: .2px;
|
|
2342
|
+
white-space: nowrap;
|
|
2343
|
+
overflow: hidden;
|
|
2344
|
+
text-overflow: ellipsis;
|
|
2345
|
+
}
|
|
2346
|
+
|
|
2347
|
+
.nav-search {
|
|
2348
|
+
padding: 4px 0 8px;
|
|
2349
|
+
}
|
|
2350
|
+
|
|
2351
|
+
.nav-search__wrapper {
|
|
2352
|
+
display: flex;
|
|
2353
|
+
align-items: center;
|
|
2354
|
+
background: var(--kr-scaffold-nav-search-bg);
|
|
2355
|
+
border-radius: 8px;
|
|
2356
|
+
padding: 0 12px;
|
|
2357
|
+
height: 40px;
|
|
2358
|
+
gap: 8px;
|
|
2359
|
+
border: 1px solid transparent;
|
|
2360
|
+
transition: all 0.15s ease;
|
|
2361
|
+
}
|
|
2362
|
+
|
|
2363
|
+
.nav-search__wrapper:focus-within {
|
|
2364
|
+
background: var(--kr-scaffold-nav-search-focus-bg);
|
|
2365
|
+
border-color: var(--kr-scaffold-nav-search-focus-border);
|
|
2366
|
+
}
|
|
2367
|
+
|
|
2368
|
+
.nav-search__icon {
|
|
2369
|
+
width: 18px;
|
|
2370
|
+
height: 18px;
|
|
2371
|
+
flex-shrink: 0;
|
|
2372
|
+
opacity: 0.6;
|
|
2373
|
+
}
|
|
2374
|
+
|
|
2375
|
+
.nav-search__icon svg {
|
|
2376
|
+
width: 100%;
|
|
2377
|
+
height: 100%;
|
|
2378
|
+
fill: currentColor;
|
|
2379
|
+
}
|
|
2380
|
+
|
|
2381
|
+
.nav-search__input {
|
|
2382
|
+
flex: 1;
|
|
2383
|
+
background: none;
|
|
2384
|
+
border: none;
|
|
2385
|
+
outline: none;
|
|
2386
|
+
color: var(--kr-scaffold-nav-text-active);
|
|
2387
|
+
font-size: 13px;
|
|
2388
|
+
font-family: inherit;
|
|
2389
|
+
min-width: 0;
|
|
2390
|
+
}
|
|
2391
|
+
|
|
2392
|
+
.nav-search__input::placeholder {
|
|
2393
|
+
color: var(--kr-scaffold-nav-text);
|
|
2394
|
+
opacity: 0.7;
|
|
2395
|
+
}
|
|
2396
|
+
|
|
2397
|
+
.nav-search__clear {
|
|
2398
|
+
width: 18px;
|
|
2399
|
+
height: 18px;
|
|
2400
|
+
padding: 0;
|
|
2401
|
+
background: none;
|
|
2402
|
+
border: none;
|
|
2403
|
+
color: var(--kr-scaffold-nav-text);
|
|
2404
|
+
cursor: pointer;
|
|
2405
|
+
display: flex;
|
|
2406
|
+
align-items: center;
|
|
2407
|
+
justify-content: center;
|
|
2408
|
+
opacity: 0.6;
|
|
2409
|
+
transition: opacity 0.15s ease;
|
|
2410
|
+
}
|
|
2411
|
+
|
|
2412
|
+
.nav-search__clear:hover {
|
|
2413
|
+
opacity: 1;
|
|
2414
|
+
}
|
|
2415
|
+
|
|
2416
|
+
.nav-search__clear svg {
|
|
2417
|
+
width: 100%;
|
|
2418
|
+
height: 100%;
|
|
2419
|
+
fill: currentColor;
|
|
2420
|
+
}
|
|
2421
|
+
|
|
2422
|
+
.nav-content {
|
|
2423
|
+
flex: 1;
|
|
2424
|
+
overflow-y: auto;
|
|
2425
|
+
padding: 0 9px 0.75rem 8px;
|
|
2426
|
+
}
|
|
2427
|
+
|
|
2428
|
+
.nav-footer {
|
|
2429
|
+
padding: 12px;
|
|
2430
|
+
border-top: 1px solid var(--kr-scaffold-nav-divider);
|
|
2431
|
+
}
|
|
2432
|
+
|
|
2433
|
+
.user {
|
|
2434
|
+
display: flex;
|
|
2435
|
+
align-items: center;
|
|
2436
|
+
gap: 12px;
|
|
2437
|
+
padding: 8px;
|
|
2438
|
+
border-radius: 8px;
|
|
2439
|
+
cursor: pointer;
|
|
2440
|
+
transition: background 0.15s ease;
|
|
2441
|
+
}
|
|
2442
|
+
|
|
2443
|
+
.user:hover {
|
|
2444
|
+
background: var(--kr-scaffold-nav-hover);
|
|
2445
|
+
}
|
|
2446
|
+
|
|
2447
|
+
.user__avatar {
|
|
2448
|
+
width: 36px;
|
|
2449
|
+
height: 36px;
|
|
2450
|
+
border-radius: 50%;
|
|
2451
|
+
background: var(--kr-scaffold-avatar-bg, #beea4e);
|
|
2452
|
+
color: var(--kr-scaffold-avatar-text, #10172a);
|
|
2453
|
+
display: flex;
|
|
2454
|
+
align-items: center;
|
|
2455
|
+
justify-content: center;
|
|
2456
|
+
font-size: 14px;
|
|
2457
|
+
font-weight: 600;
|
|
2458
|
+
flex-shrink: 0;
|
|
2459
|
+
overflow: hidden;
|
|
2460
|
+
}
|
|
2461
|
+
|
|
2462
|
+
.user__avatar img {
|
|
2463
|
+
width: 100%;
|
|
2464
|
+
height: 100%;
|
|
2465
|
+
object-fit: cover;
|
|
2466
|
+
}
|
|
2467
|
+
|
|
2468
|
+
.user__info {
|
|
2469
|
+
flex: 1;
|
|
2470
|
+
min-width: 0;
|
|
2471
|
+
}
|
|
2472
|
+
|
|
2473
|
+
.user__name {
|
|
2474
|
+
font-size: 13px;
|
|
2475
|
+
font-weight: 500;
|
|
2476
|
+
color: var(--kr-scaffold-nav-text-active);
|
|
2477
|
+
white-space: nowrap;
|
|
2478
|
+
overflow: hidden;
|
|
2479
|
+
text-overflow: ellipsis;
|
|
2480
|
+
}
|
|
2481
|
+
|
|
2482
|
+
.user__email {
|
|
2483
|
+
font-size: 12px;
|
|
2484
|
+
color: var(--kr-scaffold-nav-text);
|
|
2485
|
+
white-space: nowrap;
|
|
2486
|
+
overflow: hidden;
|
|
2487
|
+
text-overflow: ellipsis;
|
|
2488
|
+
}
|
|
2489
|
+
|
|
2490
|
+
.user-menu-container {
|
|
2491
|
+
position: relative;
|
|
2492
|
+
}
|
|
2493
|
+
|
|
2494
|
+
.user-menu {
|
|
2495
|
+
position: absolute;
|
|
2496
|
+
bottom: 100%;
|
|
2497
|
+
left: 0;
|
|
2498
|
+
right: 0;
|
|
2499
|
+
margin-bottom: 8px;
|
|
2500
|
+
background: var(--kr-scaffold-nav-bg);
|
|
2501
|
+
border: 1px solid var(--kr-scaffold-nav-divider);
|
|
2502
|
+
border-radius: 8px;
|
|
2503
|
+
padding: 4px;
|
|
2504
|
+
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.15);
|
|
2505
|
+
}
|
|
2506
|
+
|
|
2507
|
+
.user-menu__item {
|
|
2508
|
+
display: block;
|
|
2509
|
+
width: 100%;
|
|
2510
|
+
padding: 10px 12px;
|
|
2511
|
+
background: none;
|
|
2512
|
+
border: none;
|
|
2513
|
+
color: var(--kr-scaffold-nav-text);
|
|
2514
|
+
font-size: 13px;
|
|
2515
|
+
font-family: inherit;
|
|
2516
|
+
text-align: left;
|
|
2517
|
+
cursor: pointer;
|
|
2518
|
+
border-radius: 6px;
|
|
2519
|
+
transition: background 0.15s ease;
|
|
2520
|
+
}
|
|
2521
|
+
|
|
2522
|
+
.user-menu__item:hover {
|
|
2523
|
+
background: var(--kr-scaffold-nav-hover);
|
|
2524
|
+
color: var(--kr-scaffold-nav-text-active);
|
|
2525
|
+
}
|
|
2526
|
+
|
|
2527
|
+
.user-menu__divider {
|
|
2528
|
+
height: 1px;
|
|
2529
|
+
background: var(--kr-scaffold-nav-divider);
|
|
2530
|
+
margin: 4px 0;
|
|
2531
|
+
}
|
|
2532
|
+
|
|
2533
|
+
/* Main */
|
|
2534
|
+
.main {
|
|
2535
|
+
flex: 1;
|
|
2536
|
+
min-width: 0;
|
|
2537
|
+
overflow-y: auto;
|
|
2538
|
+
background: #ffffff;
|
|
2539
|
+
display: flex;
|
|
2540
|
+
flex-direction: column;
|
|
2541
|
+
}
|
|
2542
|
+
|
|
2543
|
+
.breadcrumbs {
|
|
2544
|
+
height: 32px;
|
|
2545
|
+
padding: 0 1rem;
|
|
2546
|
+
display: flex;
|
|
2547
|
+
align-items: center;
|
|
2548
|
+
gap: 6px;
|
|
2549
|
+
font-size: 12px;
|
|
2550
|
+
color: #6b7280;
|
|
2551
|
+
background: #f9fafb;
|
|
2552
|
+
border-bottom: 1px solid #e5e7eb;
|
|
2553
|
+
flex-shrink: 0;
|
|
2554
|
+
}
|
|
2555
|
+
|
|
2556
|
+
.breadcrumbs a {
|
|
2557
|
+
color: #6b7280;
|
|
2558
|
+
text-decoration: none;
|
|
2559
|
+
}
|
|
2560
|
+
|
|
2561
|
+
.breadcrumbs a:hover {
|
|
2562
|
+
color: #111827;
|
|
2563
|
+
text-decoration: underline;
|
|
2564
|
+
}
|
|
2565
|
+
|
|
2566
|
+
.breadcrumbs__separator {
|
|
2567
|
+
color: #d1d5db;
|
|
2568
|
+
}
|
|
2569
|
+
|
|
2570
|
+
.breadcrumbs__current {
|
|
2571
|
+
color: #111827;
|
|
2572
|
+
font-weight: 500;
|
|
2573
|
+
}
|
|
2574
|
+
|
|
2575
|
+
.main__content {
|
|
2576
|
+
flex: 1;
|
|
2577
|
+
overflow-y: auto;
|
|
2578
|
+
}
|
|
2579
|
+
|
|
2580
|
+
/* Nav Items */
|
|
2581
|
+
.nav-items {
|
|
2582
|
+
display: flex;
|
|
2583
|
+
flex-direction: column;
|
|
2584
|
+
gap: 2px;
|
|
2585
|
+
}
|
|
2586
|
+
|
|
2587
|
+
.nav-item {
|
|
2588
|
+
display: flex;
|
|
2589
|
+
align-items: center;
|
|
2590
|
+
gap: 16px;
|
|
2591
|
+
padding: 0 12px;
|
|
2592
|
+
height: 32px;
|
|
2593
|
+
border-radius: 8px;
|
|
2594
|
+
color: var(--kr-scaffold-nav-text);
|
|
2595
|
+
text-decoration: none;
|
|
2596
|
+
cursor: pointer;
|
|
2597
|
+
border: none;
|
|
2598
|
+
background: none;
|
|
2599
|
+
width: 100%;
|
|
2600
|
+
text-align: left;
|
|
2601
|
+
font-size: 13px;
|
|
2602
|
+
font-weight: 400;
|
|
2603
|
+
font-family: inherit;
|
|
2604
|
+
transition: all 0.15s ease;
|
|
2605
|
+
}
|
|
2606
|
+
|
|
2607
|
+
.nav-item:hover {
|
|
2608
|
+
background: #F3F7FC;
|
|
2609
|
+
color: var(--kr-scaffold-nav-text-active);
|
|
2610
|
+
}
|
|
2611
|
+
|
|
2612
|
+
.nav-item--active,
|
|
2613
|
+
.nav-item--active:hover {
|
|
2614
|
+
background: #e1e9f6;
|
|
2615
|
+
color: #1a2332;
|
|
2616
|
+
font-weight: 500;
|
|
2617
|
+
}
|
|
2618
|
+
|
|
2619
|
+
.nav-item__icon {
|
|
2620
|
+
width: 16px;
|
|
2621
|
+
height: 16px;
|
|
2622
|
+
display: flex;
|
|
2623
|
+
align-items: center;
|
|
2624
|
+
justify-content: center;
|
|
2625
|
+
flex-shrink: 0;
|
|
2626
|
+
}
|
|
2627
|
+
|
|
2628
|
+
.nav-item__icon svg {
|
|
2629
|
+
width: 16px;
|
|
2630
|
+
height: 16px;
|
|
2631
|
+
fill: currentColor;
|
|
2632
|
+
}
|
|
2633
|
+
|
|
2634
|
+
.nav-item__label {
|
|
2635
|
+
flex: 1;
|
|
2636
|
+
white-space: nowrap;
|
|
2637
|
+
overflow: hidden;
|
|
2638
|
+
text-overflow: ellipsis;
|
|
2639
|
+
letter-spacing: 0.01em;
|
|
2640
|
+
}
|
|
2641
|
+
|
|
2642
|
+
.nav-item__chevron {
|
|
2643
|
+
width: 16px;
|
|
2644
|
+
height: 16px;
|
|
2645
|
+
transition: transform 0.2s ease;
|
|
2646
|
+
transform: rotate(-90deg);
|
|
2647
|
+
stroke: currentColor;
|
|
2648
|
+
}
|
|
2649
|
+
|
|
2650
|
+
.nav-item--expanded .nav-item__chevron {
|
|
2651
|
+
transform: rotate(0deg);
|
|
2652
|
+
}
|
|
2653
|
+
|
|
2654
|
+
.nav-group {
|
|
2655
|
+
display: flex;
|
|
2656
|
+
flex-direction: column;
|
|
2657
|
+
overflow: hidden;
|
|
2658
|
+
height: 0;
|
|
2659
|
+
}
|
|
2660
|
+
|
|
2661
|
+
.nav-group--expanded {
|
|
2662
|
+
height: auto;
|
|
2663
|
+
}
|
|
2664
|
+
|
|
2665
|
+
.nav-group .nav-item {
|
|
2666
|
+
padding: 0 12px 0 44px;
|
|
2667
|
+
height: 32px;
|
|
2668
|
+
font-size: 13px;
|
|
2669
|
+
font-weight: 400;
|
|
2670
|
+
margin-top: 2px;
|
|
2671
|
+
flex-shrink: 0;
|
|
2672
|
+
}
|
|
2673
|
+
|
|
2674
|
+
.nav-group .nav-item:first-child {
|
|
2675
|
+
margin-top: 0;
|
|
2676
|
+
}
|
|
2677
|
+
|
|
2678
|
+
/* Nav items in groups when icons are hidden */
|
|
2679
|
+
:host(:not([nav-icons-displayed])) .nav-group .nav-item {
|
|
2680
|
+
padding-left: 24px;
|
|
2681
|
+
}
|
|
2682
|
+
|
|
2683
|
+
/* Sections */
|
|
2684
|
+
.nav-section {
|
|
2685
|
+
padding: 16px 12px 8px;
|
|
2686
|
+
font-size: 11px;
|
|
2687
|
+
font-weight: 600;
|
|
2688
|
+
text-transform: uppercase;
|
|
2689
|
+
letter-spacing: 0.05em;
|
|
2690
|
+
color: var(--kr-scaffold-nav-text);
|
|
2691
|
+
opacity: 0.6;
|
|
2692
|
+
}
|
|
2693
|
+
|
|
2694
|
+
.nav-section:first-child {
|
|
2695
|
+
padding-top: 0;
|
|
2696
|
+
}
|
|
2697
|
+
|
|
2698
|
+
/* Edit mode actions */
|
|
2699
|
+
.edit-actions {
|
|
2700
|
+
display: flex;
|
|
2701
|
+
flex-direction: column;
|
|
2702
|
+
gap: 8px;
|
|
2703
|
+
}
|
|
2704
|
+
|
|
2705
|
+
.edit-actions__btn {
|
|
2706
|
+
display: block;
|
|
2707
|
+
width: 100%;
|
|
2708
|
+
padding: 10px 12px;
|
|
2709
|
+
background: var(--kr-scaffold-nav-hover);
|
|
2710
|
+
border: none;
|
|
2711
|
+
color: var(--kr-scaffold-nav-text);
|
|
2712
|
+
font-size: 13px;
|
|
2713
|
+
font-weight: 500;
|
|
2714
|
+
font-family: inherit;
|
|
2715
|
+
cursor: pointer;
|
|
2716
|
+
border-radius: 6px;
|
|
2717
|
+
transition: background 0.15s ease;
|
|
2718
|
+
}
|
|
2719
|
+
|
|
2720
|
+
.edit-actions__btn:hover {
|
|
2721
|
+
background: var(--kr-scaffold-nav-active-bg);
|
|
2722
|
+
color: var(--kr-scaffold-nav-text-active);
|
|
2723
|
+
}
|
|
2724
|
+
|
|
2725
|
+
.edit-actions__btn--primary {
|
|
2726
|
+
background: #beea4e;
|
|
2727
|
+
color: #10172a;
|
|
2728
|
+
}
|
|
2729
|
+
|
|
2730
|
+
.edit-actions__btn--primary:hover {
|
|
2731
|
+
background: #d4f472;
|
|
2732
|
+
color: #10172a;
|
|
2733
|
+
}
|
|
2734
|
+
|
|
2735
|
+
.edit-actions__secondary {
|
|
2736
|
+
display: flex;
|
|
2737
|
+
gap: 8px;
|
|
2738
|
+
}
|
|
2739
|
+
|
|
2740
|
+
.edit-actions__secondary .edit-actions__btn {
|
|
2741
|
+
flex: 1;
|
|
2742
|
+
}
|
|
2743
|
+
|
|
2744
|
+
/* Hidden/inactive items in edit mode */
|
|
2745
|
+
.nav-item--hidden {
|
|
2746
|
+
opacity: 0.4;
|
|
2747
|
+
font-style: italic;
|
|
2748
|
+
}
|
|
2749
|
+
|
|
2750
|
+
.nav-item--hidden:hover {
|
|
2751
|
+
opacity: 0.6;
|
|
2752
|
+
}
|
|
2753
|
+
|
|
2754
|
+
/* Dragging state */
|
|
2755
|
+
:host(.kr-scaffold--dragging),
|
|
2756
|
+
:host(.kr-scaffold--dragging) * {
|
|
2757
|
+
cursor: grabbing;
|
|
2758
|
+
user-select: none;
|
|
2759
|
+
}
|
|
2760
|
+
|
|
2761
|
+
.nav-item-drag-preview {
|
|
2762
|
+
position: fixed;
|
|
2763
|
+
pointer-events: none;
|
|
2764
|
+
z-index: 1000;
|
|
2765
|
+
opacity: 0.9;
|
|
2766
|
+
background: var(--kr-scaffold-nav-bg);
|
|
2767
|
+
border: 1px solid var(--kr-scaffold-nav-divider);
|
|
2768
|
+
border-radius: 8px;
|
|
2769
|
+
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.2);
|
|
2770
|
+
padding: 0 12px;
|
|
2771
|
+
height: 40px;
|
|
2772
|
+
min-width: 200px;
|
|
2773
|
+
display: flex;
|
|
2774
|
+
align-items: center;
|
|
2775
|
+
gap: 16px;
|
|
2776
|
+
color: var(--kr-scaffold-nav-text-active);
|
|
2777
|
+
font-size: 13px;
|
|
2778
|
+
font-weight: 500;
|
|
2779
|
+
}
|
|
2780
|
+
|
|
2781
|
+
/* Drop indicator */
|
|
2782
|
+
.nav-item--drop-above,
|
|
2783
|
+
.nav-item--drop-below,
|
|
2784
|
+
.nav-item--drop-center {
|
|
2785
|
+
position: relative;
|
|
2786
|
+
}
|
|
2787
|
+
|
|
2788
|
+
.nav-item--drop-above::before,
|
|
2789
|
+
.nav-item--drop-below::after {
|
|
2790
|
+
content: '';
|
|
2791
|
+
position: absolute;
|
|
2792
|
+
left: 0;
|
|
2793
|
+
right: 0;
|
|
2794
|
+
height: 2px;
|
|
2795
|
+
background: #beea4e;
|
|
2796
|
+
}
|
|
2797
|
+
|
|
2798
|
+
.nav-item--drop-above::before {
|
|
2799
|
+
top: 0;
|
|
2800
|
+
}
|
|
2801
|
+
|
|
2802
|
+
.nav-item--drop-below::after {
|
|
2803
|
+
bottom: 0;
|
|
2804
|
+
}
|
|
2805
|
+
|
|
2806
|
+
.nav-item--drop-center {
|
|
2807
|
+
background: var(--kr-scaffold-nav-active-bg);
|
|
2808
|
+
}
|
|
2809
|
+
|
|
2810
|
+
.breadcrumbs {
|
|
2811
|
+
height: 32px;
|
|
2812
|
+
padding: 0 1rem;
|
|
2813
|
+
display: flex;
|
|
2814
|
+
align-items: center;
|
|
2815
|
+
gap: 6px;
|
|
2816
|
+
font-size: 12px;
|
|
2817
|
+
color: #6b7280;
|
|
2818
|
+
//background: #f9fafb;
|
|
2819
|
+
background: #beea4e;
|
|
2820
|
+
border-top: 1px solid #e5e7eb;
|
|
2821
|
+
flex-shrink: 0;
|
|
2822
|
+
}
|
|
2823
|
+
|
|
2824
|
+
.breadcrumbs a {
|
|
2825
|
+
//color: #6b7280;
|
|
2826
|
+
color: #10172a;
|
|
2827
|
+
font-size: 13px;
|
|
2828
|
+
font-weight: 600;
|
|
2829
|
+
text-decoration: none;
|
|
2830
|
+
}
|
|
2831
|
+
|
|
2832
|
+
.breadcrumbs a:hover {
|
|
2833
|
+
color: #111827;
|
|
2834
|
+
text-decoration: underline;
|
|
2835
|
+
}
|
|
2836
|
+
|
|
2837
|
+
.breadcrumbs__separator {
|
|
2838
|
+
color: #d1d5db;
|
|
2839
|
+
}
|
|
2840
|
+
|
|
2841
|
+
.breadcrumbs__current {
|
|
2842
|
+
color: #111827;
|
|
2843
|
+
font-weight: 500;
|
|
2844
|
+
}
|
|
2845
|
+
|
|
2846
|
+
/* Progress bar */
|
|
2847
|
+
.progress {
|
|
2848
|
+
position: fixed;
|
|
2849
|
+
top: 0;
|
|
2850
|
+
left: 0;
|
|
2851
|
+
right: 0;
|
|
2852
|
+
height: 4px;
|
|
2853
|
+
overflow-x: hidden;
|
|
2854
|
+
z-index: 1000;
|
|
2855
|
+
display: none;
|
|
2856
|
+
}
|
|
2857
|
+
|
|
2858
|
+
.progress--loading {
|
|
2859
|
+
display: block;
|
|
2860
|
+
}
|
|
2861
|
+
|
|
2862
|
+
.progress__track {
|
|
2863
|
+
position: absolute;
|
|
2864
|
+
top: 0;
|
|
2865
|
+
bottom: 0;
|
|
2866
|
+
width: 100%;
|
|
2867
|
+
background: rgba(190, 234, 78, 0.3);
|
|
2868
|
+
}
|
|
2869
|
+
|
|
2870
|
+
.progress__bar {
|
|
2871
|
+
position: absolute;
|
|
2872
|
+
top: 0;
|
|
2873
|
+
bottom: 0;
|
|
2874
|
+
width: 100%;
|
|
2875
|
+
transform-origin: left center;
|
|
2876
|
+
}
|
|
2877
|
+
|
|
2878
|
+
.progress__bar-inner {
|
|
2879
|
+
display: inline-block;
|
|
2880
|
+
position: absolute;
|
|
2881
|
+
width: 100%;
|
|
2882
|
+
height: 100%;
|
|
2883
|
+
background: #beea4e;
|
|
2884
|
+
}
|
|
2885
|
+
|
|
2886
|
+
.progress__bar--primary {
|
|
2887
|
+
left: -145.166611%;
|
|
2888
|
+
}
|
|
2889
|
+
|
|
2890
|
+
.progress--loading .progress__bar--primary {
|
|
2891
|
+
animation: progress-primary-translate 2s infinite linear;
|
|
2892
|
+
}
|
|
2893
|
+
|
|
2894
|
+
.progress--loading .progress__bar--primary .progress__bar-inner {
|
|
2895
|
+
animation: progress-primary-scale 2s infinite linear;
|
|
2896
|
+
}
|
|
2897
|
+
|
|
2898
|
+
.progress__bar--secondary {
|
|
2899
|
+
left: -54.888891%;
|
|
2900
|
+
}
|
|
2901
|
+
|
|
2902
|
+
.progress--loading .progress__bar--secondary {
|
|
2903
|
+
animation: progress-secondary-translate 2s infinite linear;
|
|
2904
|
+
}
|
|
2905
|
+
|
|
2906
|
+
.progress--loading .progress__bar--secondary .progress__bar-inner {
|
|
2907
|
+
animation: progress-secondary-scale 2s infinite linear;
|
|
2908
|
+
}
|
|
2909
|
+
|
|
2910
|
+
@keyframes progress-primary-translate {
|
|
2911
|
+
0% {
|
|
2912
|
+
transform: translateX(0);
|
|
2913
|
+
}
|
|
2914
|
+
20% {
|
|
2915
|
+
animation-timing-function: cubic-bezier(0.5, 0, 0.701732, 0.495819);
|
|
2916
|
+
transform: translateX(0);
|
|
2917
|
+
}
|
|
2918
|
+
59.15% {
|
|
2919
|
+
animation-timing-function: cubic-bezier(0.302435, 0.381352, 0.55, 0.956352);
|
|
2920
|
+
transform: translateX(83.67142%);
|
|
2921
|
+
}
|
|
2922
|
+
100% {
|
|
2923
|
+
transform: translateX(200.611057%);
|
|
2924
|
+
}
|
|
2925
|
+
}
|
|
2926
|
+
|
|
2927
|
+
@keyframes progress-primary-scale {
|
|
2928
|
+
0% {
|
|
2929
|
+
transform: scaleX(0.08);
|
|
2930
|
+
}
|
|
2931
|
+
36.65% {
|
|
2932
|
+
animation-timing-function: cubic-bezier(0.334731, 0.12482, 0.785844, 1);
|
|
2933
|
+
transform: scaleX(0.08);
|
|
2934
|
+
}
|
|
2935
|
+
69.15% {
|
|
2936
|
+
animation-timing-function: cubic-bezier(0.06, 0.11, 0.6, 1);
|
|
2937
|
+
transform: scaleX(0.661479);
|
|
2938
|
+
}
|
|
2939
|
+
100% {
|
|
2940
|
+
transform: scaleX(0.08);
|
|
2941
|
+
}
|
|
2942
|
+
}
|
|
2943
|
+
|
|
2944
|
+
@keyframes progress-secondary-translate {
|
|
2945
|
+
0% {
|
|
2946
|
+
animation-timing-function: cubic-bezier(0.15, 0, 0.515058, 0.409685);
|
|
2947
|
+
transform: translateX(0);
|
|
2948
|
+
}
|
|
2949
|
+
25% {
|
|
2950
|
+
animation-timing-function: cubic-bezier(0.31033, 0.284058, 0.8, 0.733712);
|
|
2951
|
+
transform: translateX(37.651913%);
|
|
2952
|
+
}
|
|
2953
|
+
48.35% {
|
|
2954
|
+
animation-timing-function: cubic-bezier(0.4, 0.627035, 0.6, 0.902026);
|
|
2955
|
+
transform: translateX(84.386165%);
|
|
2956
|
+
}
|
|
2957
|
+
100% {
|
|
2958
|
+
transform: translateX(160.277782%);
|
|
2959
|
+
}
|
|
2960
|
+
}
|
|
2961
|
+
|
|
2962
|
+
@keyframes progress-secondary-scale {
|
|
2963
|
+
0% {
|
|
2964
|
+
animation-timing-function: cubic-bezier(0.205028, 0.057051, 0.57661, 0.453971);
|
|
2965
|
+
transform: scaleX(0.08);
|
|
2966
|
+
}
|
|
2967
|
+
19.15% {
|
|
2968
|
+
animation-timing-function: cubic-bezier(0.152313, 0.196432, 0.648374, 1.004315);
|
|
2969
|
+
transform: scaleX(0.457104);
|
|
2970
|
+
}
|
|
2971
|
+
44.15% {
|
|
2972
|
+
animation-timing-function: cubic-bezier(0.257759, -0.003163, 0.211762, 1.38179);
|
|
2973
|
+
transform: scaleX(0.72796);
|
|
2974
|
+
}
|
|
2975
|
+
100% {
|
|
2976
|
+
transform: scaleX(0.08);
|
|
2977
|
+
}
|
|
2978
|
+
}
|
|
2979
|
+
`;
|
|
2980
|
+
__decorate$3([
|
|
2981
|
+
r()
|
|
2982
|
+
], KRScaffold.prototype, "navItemsExpanded", void 0);
|
|
2983
|
+
__decorate$3([
|
|
2984
|
+
r()
|
|
2985
|
+
], KRScaffold.prototype, "navQuery", void 0);
|
|
2986
|
+
__decorate$3([
|
|
2987
|
+
r()
|
|
2988
|
+
], KRScaffold.prototype, "activeNavItemId", void 0);
|
|
2989
|
+
__decorate$3([
|
|
2990
|
+
r()
|
|
2991
|
+
], KRScaffold.prototype, "isNavScrolled", void 0);
|
|
2992
|
+
__decorate$3([
|
|
2993
|
+
r()
|
|
2994
|
+
], KRScaffold.prototype, "isNavOpened", void 0);
|
|
2995
|
+
__decorate$3([
|
|
2996
|
+
r()
|
|
2997
|
+
], KRScaffold.prototype, "isEditing", void 0);
|
|
2998
|
+
__decorate$3([
|
|
2999
|
+
r()
|
|
3000
|
+
], KRScaffold.prototype, "isUserMenuOpen", void 0);
|
|
3001
|
+
__decorate$3([
|
|
3002
|
+
r()
|
|
3003
|
+
], KRScaffold.prototype, "pref", void 0);
|
|
3004
|
+
__decorate$3([
|
|
3005
|
+
r()
|
|
3006
|
+
], KRScaffold.prototype, "draggedNavItemId", void 0);
|
|
3007
|
+
__decorate$3([
|
|
3008
|
+
r()
|
|
3009
|
+
], KRScaffold.prototype, "navItemDropTargetId", void 0);
|
|
3010
|
+
__decorate$3([
|
|
3011
|
+
r()
|
|
3012
|
+
], KRScaffold.prototype, "navItemDropPosition", void 0);
|
|
3013
|
+
__decorate$3([
|
|
3014
|
+
r()
|
|
3015
|
+
], KRScaffold.prototype, "pendingRequests", void 0);
|
|
3016
|
+
__decorate$3([
|
|
3017
|
+
n({ type: String })
|
|
3018
|
+
], KRScaffold.prototype, "logo", void 0);
|
|
3019
|
+
__decorate$3([
|
|
3020
|
+
n({ type: String })
|
|
3021
|
+
], KRScaffold.prototype, "title", void 0);
|
|
3022
|
+
__decorate$3([
|
|
3023
|
+
n({ type: String, reflect: true })
|
|
3024
|
+
], KRScaffold.prototype, "scheme", void 0);
|
|
3025
|
+
__decorate$3([
|
|
3026
|
+
n({ type: Array })
|
|
3027
|
+
], KRScaffold.prototype, "nav", void 0);
|
|
3028
|
+
__decorate$3([
|
|
3029
|
+
n({ type: Boolean, attribute: 'nav-icons-displayed', reflect: true })
|
|
3030
|
+
], KRScaffold.prototype, "navIconsDisplayed", void 0);
|
|
3031
|
+
__decorate$3([
|
|
3032
|
+
n({ type: Boolean, attribute: 'nav-expanded' })
|
|
3033
|
+
], KRScaffold.prototype, "navExpanded", void 0);
|
|
3034
|
+
__decorate$3([
|
|
3035
|
+
n({ type: Object })
|
|
3036
|
+
], KRScaffold.prototype, "user", void 0);
|
|
3037
|
+
__decorate$3([
|
|
3038
|
+
n({ type: Boolean })
|
|
3039
|
+
], KRScaffold.prototype, "subbar", void 0);
|
|
3040
|
+
__decorate$3([
|
|
3041
|
+
n({ type: Array })
|
|
3042
|
+
], KRScaffold.prototype, "breadcrumbs", void 0);
|
|
3043
|
+
KRScaffold = __decorate$3([
|
|
3044
|
+
t$1('kr-scaffold')
|
|
3045
|
+
], KRScaffold);
|
|
3046
|
+
|
|
3047
|
+
var __decorate$2 = (undefined && undefined.__decorate) || function (decorators, target, key, desc) {
|
|
3048
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
3049
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
3050
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
3051
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
3052
|
+
};
|
|
3053
|
+
/**
|
|
3054
|
+
* Secondary navigation component for screens within the scaffold.
|
|
3055
|
+
* Provides a simple nav drawer for sub-navigation within a page/screen.
|
|
3056
|
+
* Typically used when viewing details of a specific entity (e.g., Company, Contact).
|
|
3057
|
+
*
|
|
3058
|
+
* ## Usage
|
|
3059
|
+
* ```html
|
|
3060
|
+
* <kr-screen-nav
|
|
3061
|
+
* title="Acme Corporation"
|
|
3062
|
+
* subtitle="Company"
|
|
3063
|
+
* backUrl="/crm/companies"
|
|
3064
|
+
* .navItems=${[
|
|
3065
|
+
* { id: 'overview', label: 'Overview', url: '/companies/123/overview' },
|
|
3066
|
+
* { id: 'contacts', label: 'Contacts', url: '/companies/123/contacts' },
|
|
3067
|
+
* { id: 'activity', label: 'Activity', url: '/companies/123/activity' },
|
|
3068
|
+
* ]}
|
|
3069
|
+
* activeId="overview"
|
|
3070
|
+
* >
|
|
3071
|
+
* <div>Screen content here</div>
|
|
3072
|
+
* </kr-screen-nav>
|
|
3073
|
+
* ```
|
|
3074
|
+
*
|
|
3075
|
+
* @slot - The main screen content
|
|
3076
|
+
*
|
|
3077
|
+
* @property {string} title - Main title (e.g., entity name like "Acme Corporation")
|
|
3078
|
+
* @property {string} subtitle - Subtitle/label (e.g., entity type like "Company")
|
|
3079
|
+
* @property {string} backUrl - URL to navigate back to (e.g., the table/list view)
|
|
3080
|
+
* @property {KRScreenNavItem[]} navItems - Navigation items as JSON array
|
|
3081
|
+
* @property {string} activeId - Currently active item ID
|
|
3082
|
+
*/
|
|
3083
|
+
let KRScreenNav = class KRScreenNav extends i$1 {
|
|
3084
|
+
constructor() {
|
|
3085
|
+
super(...arguments);
|
|
3086
|
+
/**
|
|
3087
|
+
* Main title (e.g., entity name like "Acme Corporation")
|
|
3088
|
+
*/
|
|
3089
|
+
this.title = '';
|
|
3090
|
+
/**
|
|
3091
|
+
* Subtitle/label (e.g., entity type like "Company")
|
|
3092
|
+
*/
|
|
3093
|
+
this.subtitle = '';
|
|
3094
|
+
/**
|
|
3095
|
+
* URL to navigate back to (e.g., the table/list view)
|
|
3096
|
+
*/
|
|
3097
|
+
this.backUrl = '';
|
|
3098
|
+
/**
|
|
3099
|
+
* Navigation items
|
|
3100
|
+
*/
|
|
3101
|
+
this.navItems = [];
|
|
3102
|
+
/**
|
|
3103
|
+
* Currently active item ID
|
|
3104
|
+
*/
|
|
3105
|
+
this.activeId = '';
|
|
3106
|
+
}
|
|
3107
|
+
/**
|
|
3108
|
+
* Handles click events on navigation items.
|
|
3109
|
+
*
|
|
3110
|
+
* Dispatches a cancelable `nav-item-click` custom event.
|
|
3111
|
+
* If a listener calls `preventDefault()` on the custom event, native browser
|
|
3112
|
+
* navigation is prevented. Otherwise, the default `<a href>` navigation
|
|
3113
|
+
* proceeds normally.
|
|
3114
|
+
*
|
|
3115
|
+
* The `@krubble/angular` package provides `KRScreenNavDirective` which
|
|
3116
|
+
* automatically listens for this event and handles navigation using
|
|
3117
|
+
* Angular's router.
|
|
3118
|
+
*
|
|
3119
|
+
* @param e - The original click event
|
|
3120
|
+
* @param item - The nav item that was clicked
|
|
3121
|
+
*/
|
|
3122
|
+
handleNavItemClick(e, item) {
|
|
3123
|
+
const navEvent = new CustomEvent('nav-item-click', {
|
|
3124
|
+
detail: { item },
|
|
3125
|
+
bubbles: true,
|
|
3126
|
+
composed: true,
|
|
3127
|
+
cancelable: true,
|
|
3128
|
+
});
|
|
3129
|
+
this.dispatchEvent(navEvent);
|
|
3130
|
+
// If a listener called preventDefault(), prevent the native navigation
|
|
3131
|
+
if (navEvent.defaultPrevented) {
|
|
3132
|
+
e.preventDefault();
|
|
3133
|
+
}
|
|
3134
|
+
}
|
|
3135
|
+
/**
|
|
3136
|
+
* Updates the active nav item based on the current URL.
|
|
3137
|
+
* Called automatically by `KRScreenNavDirective` on Angular route changes.
|
|
3138
|
+
*/
|
|
3139
|
+
updateActiveNavItem() {
|
|
3140
|
+
const currentPath = window.location.pathname;
|
|
3141
|
+
const activeItem = this.navItems.find(item => {
|
|
3142
|
+
if (!item.url)
|
|
3143
|
+
return false;
|
|
3144
|
+
// Handle both absolute and relative URLs
|
|
3145
|
+
if (item.url.startsWith('./')) {
|
|
3146
|
+
const segment = item.url.replace(/^\.\//, '');
|
|
3147
|
+
return currentPath.endsWith('/' + segment);
|
|
3148
|
+
}
|
|
3149
|
+
return currentPath === item.url || currentPath.startsWith(item.url + '/');
|
|
3150
|
+
});
|
|
3151
|
+
if (activeItem) {
|
|
3152
|
+
this.activeId = activeItem.id;
|
|
3153
|
+
}
|
|
3154
|
+
}
|
|
3155
|
+
render() {
|
|
3156
|
+
return x `
|
|
3157
|
+
<nav class="nav">
|
|
3158
|
+
${this.title ? x `
|
|
3159
|
+
<div class="nav-header">
|
|
3160
|
+
<div class="nav-header__text">
|
|
3161
|
+
${this.subtitle ? x `<div class="nav-header__subtitle">${this.subtitle}</div>` : E}
|
|
3162
|
+
<div class="nav-header__title">${this.title}</div>
|
|
3163
|
+
</div>
|
|
3164
|
+
</div>
|
|
3165
|
+
` : E}
|
|
3166
|
+
<div class="nav-content">
|
|
3167
|
+
<div class="nav-items">
|
|
3168
|
+
${this.navItems.map((item) => x `
|
|
3169
|
+
<a
|
|
3170
|
+
class="nav-item ${item.id === this.activeId ? 'nav-item--active' : ''}"
|
|
3171
|
+
href=${item.url || '#'}
|
|
3172
|
+
@click=${(e) => this.handleNavItemClick(e, item)}
|
|
3173
|
+
>
|
|
3174
|
+
${item.label}
|
|
3175
|
+
</a>
|
|
3176
|
+
`)}
|
|
3177
|
+
</div>
|
|
3178
|
+
</div>
|
|
3179
|
+
</nav>
|
|
3180
|
+
<main class="content">
|
|
3181
|
+
<div class="content__main">
|
|
3182
|
+
<slot></slot>
|
|
3183
|
+
</div>
|
|
3184
|
+
<!--<div class="breadcrumbs">
|
|
3185
|
+
<a href="/crm">CRM</a>
|
|
3186
|
+
<span class="breadcrumbs__separator">/</span>
|
|
3187
|
+
<a href="/crm/companies">Companies</a>
|
|
3188
|
+
<span class="breadcrumbs__separator">/</span>
|
|
3189
|
+
<span class="breadcrumbs__current">Acme Corporation</span>
|
|
3190
|
+
</div>-->
|
|
3191
|
+
</main>
|
|
3192
|
+
`;
|
|
3193
|
+
}
|
|
3194
|
+
};
|
|
3195
|
+
KRScreenNav.styles = i$4 `
|
|
3196
|
+
:host {
|
|
3197
|
+
display: flex;
|
|
3198
|
+
height: 100%;
|
|
3199
|
+
}
|
|
3200
|
+
|
|
3201
|
+
.nav {
|
|
3202
|
+
width: 200px;
|
|
3203
|
+
background: #F3F7FC;
|
|
3204
|
+
//border-right: 1px solid #000000;
|
|
3205
|
+
display: flex;
|
|
3206
|
+
flex-direction: column;
|
|
3207
|
+
flex-shrink: 0;
|
|
3208
|
+
}
|
|
3209
|
+
|
|
3210
|
+
.nav-header {
|
|
3211
|
+
padding: 20px 16px 16px;
|
|
3212
|
+
}
|
|
3213
|
+
|
|
3214
|
+
.nav-header__text {
|
|
3215
|
+
display: flex;
|
|
3216
|
+
flex-direction: column;
|
|
3217
|
+
gap: 4px;
|
|
3218
|
+
}
|
|
3219
|
+
|
|
3220
|
+
.nav-header__title {
|
|
3221
|
+
font-size: 16px;
|
|
3222
|
+
font-weight: 600;
|
|
3223
|
+
color: #111827;
|
|
3224
|
+
white-space: nowrap;
|
|
3225
|
+
overflow: hidden;
|
|
3226
|
+
text-overflow: ellipsis;
|
|
3227
|
+
line-height: 1.3;
|
|
3228
|
+
}
|
|
3229
|
+
|
|
3230
|
+
.nav-header__subtitle {
|
|
3231
|
+
font-size: 12px;
|
|
3232
|
+
font-weight: 400;
|
|
3233
|
+
color: rgb(0 0 0 / 70%);
|
|
3234
|
+
line-height: 1;
|
|
3235
|
+
}
|
|
3236
|
+
|
|
3237
|
+
.nav-content {
|
|
3238
|
+
flex: 1;
|
|
3239
|
+
overflow-y: auto;
|
|
3240
|
+
padding: 0 0 0.75rem;
|
|
3241
|
+
}
|
|
3242
|
+
|
|
3243
|
+
.nav-items {
|
|
3244
|
+
display: flex;
|
|
3245
|
+
flex-direction: column;
|
|
3246
|
+
padding: 0 8px;
|
|
3247
|
+
}
|
|
3248
|
+
|
|
3249
|
+
.nav-item {
|
|
3250
|
+
display: block;
|
|
3251
|
+
padding: 0 12px;
|
|
3252
|
+
height: 40px;
|
|
3253
|
+
line-height: 40px;
|
|
3254
|
+
letter-spacing: 0.13px;
|
|
3255
|
+
color: rgb(32, 33, 36);
|
|
3256
|
+
text-decoration: none;
|
|
3257
|
+
font-size: 13px;
|
|
3258
|
+
font-weight: 500;
|
|
3259
|
+
border-radius: 8px;
|
|
3260
|
+
transition: all 0.15s ease;
|
|
3261
|
+
}
|
|
3262
|
+
|
|
3263
|
+
.nav-item:hover {
|
|
3264
|
+
background: rgba(0, 0, 0, 0.04);
|
|
3265
|
+
color: #163052;
|
|
3266
|
+
}
|
|
3267
|
+
|
|
3268
|
+
.nav-item--active {
|
|
3269
|
+
background: rgba(22, 48, 82, 0.08);
|
|
3270
|
+
color: #163052;
|
|
3271
|
+
font-weight: 600;
|
|
3272
|
+
}
|
|
3273
|
+
|
|
3274
|
+
.content {
|
|
3275
|
+
flex: 1;
|
|
3276
|
+
min-width: 0;
|
|
3277
|
+
display: flex;
|
|
3278
|
+
flex-direction: column;
|
|
3279
|
+
}
|
|
3280
|
+
|
|
3281
|
+
.breadcrumbs {
|
|
3282
|
+
height: 32px;
|
|
3283
|
+
padding: 0 1rem;
|
|
3284
|
+
display: flex;
|
|
3285
|
+
align-items: center;
|
|
3286
|
+
gap: 6px;
|
|
3287
|
+
font-size: 12px;
|
|
3288
|
+
color: #6b7280;
|
|
3289
|
+
//background: #f9fafb;
|
|
3290
|
+
background: #beea4e;
|
|
3291
|
+
border-top: 1px solid #e5e7eb;
|
|
3292
|
+
flex-shrink: 0;
|
|
3293
|
+
}
|
|
3294
|
+
|
|
3295
|
+
.breadcrumbs a {
|
|
3296
|
+
//color: #6b7280;
|
|
3297
|
+
color: #10172a;
|
|
3298
|
+
font-size: 13px;
|
|
3299
|
+
font-weight: 600;
|
|
3300
|
+
text-decoration: none;
|
|
3301
|
+
}
|
|
3302
|
+
|
|
3303
|
+
.breadcrumbs a:hover {
|
|
3304
|
+
color: #111827;
|
|
3305
|
+
text-decoration: underline;
|
|
3306
|
+
}
|
|
3307
|
+
|
|
3308
|
+
.breadcrumbs__separator {
|
|
3309
|
+
color: #d1d5db;
|
|
3310
|
+
}
|
|
3311
|
+
|
|
3312
|
+
.breadcrumbs__current {
|
|
3313
|
+
color: #111827;
|
|
3314
|
+
font-weight: 500;
|
|
3315
|
+
}
|
|
3316
|
+
|
|
3317
|
+
.content__main {
|
|
3318
|
+
flex: 1;
|
|
3319
|
+
overflow-y: auto;
|
|
3320
|
+
}
|
|
3321
|
+
`;
|
|
3322
|
+
__decorate$2([
|
|
3323
|
+
n({ type: String })
|
|
3324
|
+
], KRScreenNav.prototype, "title", void 0);
|
|
3325
|
+
__decorate$2([
|
|
3326
|
+
n({ type: String })
|
|
3327
|
+
], KRScreenNav.prototype, "subtitle", void 0);
|
|
3328
|
+
__decorate$2([
|
|
3329
|
+
n({ type: String })
|
|
3330
|
+
], KRScreenNav.prototype, "backUrl", void 0);
|
|
3331
|
+
__decorate$2([
|
|
3332
|
+
n({ type: Array })
|
|
3333
|
+
], KRScreenNav.prototype, "navItems", void 0);
|
|
3334
|
+
__decorate$2([
|
|
3335
|
+
n({ type: String })
|
|
3336
|
+
], KRScreenNav.prototype, "activeId", void 0);
|
|
3337
|
+
KRScreenNav = __decorate$2([
|
|
3338
|
+
t$1('kr-screen-nav')
|
|
3339
|
+
], KRScreenNav);
|
|
3340
|
+
|
|
3341
|
+
var __decorate$1 = (undefined && undefined.__decorate) || function (decorators, target, key, desc) {
|
|
3342
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
3343
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
3344
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
3345
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
3346
|
+
};
|
|
3347
|
+
/**
|
|
3348
|
+
* Detail screen component for viewing/editing entity details.
|
|
3349
|
+
* Displays a form-like layout with labeled fields centered in a single column.
|
|
3350
|
+
*
|
|
3351
|
+
* ## Usage
|
|
3352
|
+
* ```html
|
|
3353
|
+
* <kr-screen-detail title="Company Details">
|
|
3354
|
+
* <kr-button slot="actions">Save</kr-button>
|
|
3355
|
+
* <kr-text-field label="Company Name" value="Acme Corporation"></kr-text-field>
|
|
3356
|
+
* <kr-text-field label="Industry" value="Technology"></kr-text-field>
|
|
3357
|
+
* <kr-text-field label="Website" value="https://acme.com"></kr-text-field>
|
|
3358
|
+
* </kr-screen-detail>
|
|
3359
|
+
* ```
|
|
3360
|
+
*
|
|
3361
|
+
* @slot - Form fields and content (centered, max-width 480px)
|
|
3362
|
+
* @slot actions - Action buttons displayed in the header on the right
|
|
3363
|
+
*
|
|
3364
|
+
* @property {string} title - Section title (e.g., "Company Details")
|
|
3365
|
+
*/
|
|
3366
|
+
let KRScreenDetail = class KRScreenDetail extends i$1 {
|
|
3367
|
+
constructor() {
|
|
3368
|
+
super(...arguments);
|
|
3369
|
+
/**
|
|
3370
|
+
* Section title
|
|
3371
|
+
*/
|
|
3372
|
+
this.title = '';
|
|
3373
|
+
}
|
|
3374
|
+
render() {
|
|
3375
|
+
return x `
|
|
3376
|
+
<div class="header">
|
|
3377
|
+
<h2 class="header__title">${this.title}</h2>
|
|
3378
|
+
<div class="header__actions">
|
|
3379
|
+
<slot name="actions"></slot>
|
|
3380
|
+
</div>
|
|
3381
|
+
</div>
|
|
3382
|
+
<div class="content">
|
|
3383
|
+
<div class="content__inner">
|
|
3384
|
+
<slot></slot>
|
|
3385
|
+
</div>
|
|
3386
|
+
</div>
|
|
3387
|
+
`;
|
|
3388
|
+
}
|
|
3389
|
+
};
|
|
3390
|
+
KRScreenDetail.styles = i$4 `
|
|
3391
|
+
:host {
|
|
3392
|
+
display: flex;
|
|
3393
|
+
flex-direction: column;
|
|
3394
|
+
height: 100%;
|
|
3395
|
+
overflow: hidden;
|
|
3396
|
+
}
|
|
3397
|
+
|
|
3398
|
+
.header {
|
|
3399
|
+
height: 64px;
|
|
3400
|
+
display: flex;
|
|
3401
|
+
align-items: center;
|
|
3402
|
+
justify-content: space-between;
|
|
3403
|
+
padding: 0 1.5rem;
|
|
3404
|
+
background: #ffffff;
|
|
3405
|
+
border-bottom: 1px solid #e5e7eb;
|
|
3406
|
+
flex-shrink: 0;
|
|
3407
|
+
}
|
|
3408
|
+
|
|
3409
|
+
.header__title {
|
|
3410
|
+
font-size: 18px;
|
|
3411
|
+
font-weight: 600;
|
|
3412
|
+
color: #111827;
|
|
3413
|
+
margin: 0;
|
|
3414
|
+
}
|
|
3415
|
+
|
|
3416
|
+
.header__actions {
|
|
3417
|
+
display: flex;
|
|
3418
|
+
align-items: center;
|
|
3419
|
+
gap: 8px;
|
|
3420
|
+
}
|
|
3421
|
+
|
|
3422
|
+
.content {
|
|
3423
|
+
flex: 1;
|
|
3424
|
+
overflow-y: auto;
|
|
3425
|
+
padding: 1.5rem;
|
|
3426
|
+
display: flex;
|
|
3427
|
+
justify-content: center;
|
|
3428
|
+
}
|
|
3429
|
+
|
|
3430
|
+
.content__inner {
|
|
3431
|
+
width: 100%;
|
|
3432
|
+
max-width: 700px;
|
|
3433
|
+
}
|
|
3434
|
+
`;
|
|
3435
|
+
__decorate$1([
|
|
3436
|
+
n({ type: String })
|
|
3437
|
+
], KRScreenDetail.prototype, "title", void 0);
|
|
3438
|
+
KRScreenDetail = __decorate$1([
|
|
3439
|
+
t$1('kr-screen-detail')
|
|
3440
|
+
], KRScreenDetail);
|
|
3441
|
+
|
|
3442
|
+
var __decorate = (undefined && undefined.__decorate) || function (decorators, target, key, desc) {
|
|
3443
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
3444
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
3445
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
3446
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
3447
|
+
};
|
|
3448
|
+
/**
|
|
3449
|
+
* Secondary bar component that sits below the main app bar.
|
|
3450
|
+
*
|
|
3451
|
+
* @slot - Content to display in the subbar (right side)
|
|
3452
|
+
*/
|
|
3453
|
+
let KRSubbar = class KRSubbar extends i$1 {
|
|
3454
|
+
constructor() {
|
|
3455
|
+
super(...arguments);
|
|
3456
|
+
/**
|
|
3457
|
+
* Array of breadcrumb items to display
|
|
3458
|
+
*/
|
|
3459
|
+
this.breadcrumbs = [];
|
|
3460
|
+
/**
|
|
3461
|
+
* Whether to show the menu icon
|
|
3462
|
+
*/
|
|
3463
|
+
this.menu = false;
|
|
3464
|
+
}
|
|
3465
|
+
_handleMenuClick() {
|
|
3466
|
+
this.dispatchEvent(new CustomEvent('menu-click', { bubbles: true, composed: true }));
|
|
3467
|
+
}
|
|
3468
|
+
render() {
|
|
3469
|
+
return x `
|
|
3470
|
+
${this.menu ? x `
|
|
3471
|
+
<div class="menu" @click=${this._handleMenuClick}>
|
|
3472
|
+
<svg class="menu__icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
3473
|
+
<line x1="3" y1="6" x2="21" y2="6"></line>
|
|
3474
|
+
<line x1="3" y1="12" x2="21" y2="12"></line>
|
|
3475
|
+
<line x1="3" y1="18" x2="21" y2="18"></line>
|
|
3476
|
+
</svg>
|
|
3477
|
+
</div>
|
|
3478
|
+
` : E}
|
|
3479
|
+
<div class="breadcrumbs">
|
|
3480
|
+
${this.breadcrumbs.map((crumb, index) => x `
|
|
3481
|
+
${index > 0 ? x `<span class="breadcrumb-separator">/</span>` : ''}
|
|
3482
|
+
<a
|
|
3483
|
+
class=${e$1({ 'breadcrumb': true, 'breadcrumb--current': index === this.breadcrumbs.length - 1 })}
|
|
3484
|
+
href=${crumb.url || ''}
|
|
3485
|
+
>${crumb.label}</a>
|
|
3486
|
+
`)}
|
|
3487
|
+
</div>
|
|
3488
|
+
<div class="content">
|
|
3489
|
+
<slot></slot>
|
|
3490
|
+
</div>
|
|
3491
|
+
|
|
3492
|
+
`;
|
|
3493
|
+
}
|
|
3494
|
+
};
|
|
3495
|
+
KRSubbar.styles = i$4 `
|
|
3496
|
+
:host {
|
|
3497
|
+
display: flex;
|
|
3498
|
+
flex-direction: row;
|
|
3499
|
+
flex-wrap: nowrap;
|
|
3500
|
+
height: 40px;
|
|
3501
|
+
background: #ffffff;
|
|
3502
|
+
border-bottom: 1px solid rgb(229, 229, 228);
|
|
3503
|
+
align-items: center;
|
|
3504
|
+
padding: 0 16px;
|
|
3505
|
+
width: 100%;
|
|
3506
|
+
}
|
|
3507
|
+
|
|
3508
|
+
.breadcrumbs {
|
|
3509
|
+
display: flex;
|
|
3510
|
+
align-items: center;
|
|
3511
|
+
gap: 4px;
|
|
3512
|
+
font-size: 13px;
|
|
3513
|
+
flex: 1;
|
|
3514
|
+
}
|
|
3515
|
+
|
|
3516
|
+
.breadcrumb {
|
|
3517
|
+
color: #000000;
|
|
3518
|
+
text-decoration: none;
|
|
3519
|
+
cursor: pointer;
|
|
3520
|
+
font-size: 13px;
|
|
3521
|
+
}
|
|
3522
|
+
|
|
3523
|
+
.breadcrumb:hover {
|
|
3524
|
+
text-decoration: underline;
|
|
3525
|
+
}
|
|
3526
|
+
|
|
3527
|
+
.breadcrumb--current {
|
|
3528
|
+
cursor: default;
|
|
3529
|
+
}
|
|
3530
|
+
|
|
3531
|
+
.breadcrumb--current:hover {
|
|
3532
|
+
text-decoration: none;
|
|
3533
|
+
}
|
|
3534
|
+
|
|
3535
|
+
.breadcrumb-separator {
|
|
3536
|
+
color: #9ca3af;
|
|
3537
|
+
font-size: 12px;
|
|
3538
|
+
}
|
|
3539
|
+
|
|
3540
|
+
.content {
|
|
3541
|
+
display: flex;
|
|
3542
|
+
align-items: center;
|
|
3543
|
+
margin-left: auto;
|
|
3544
|
+
}
|
|
3545
|
+
|
|
3546
|
+
.menu {
|
|
3547
|
+
display: flex;
|
|
3548
|
+
align-items: center;
|
|
3549
|
+
justify-content: center;
|
|
3550
|
+
width: 24px;
|
|
3551
|
+
height: 24px;
|
|
3552
|
+
margin-right: 8px;
|
|
3553
|
+
cursor: pointer;
|
|
3554
|
+
}
|
|
3555
|
+
|
|
3556
|
+
.menu__icon {
|
|
3557
|
+
width: 18px;
|
|
3558
|
+
height: 18px;
|
|
3559
|
+
}
|
|
3560
|
+
`;
|
|
3561
|
+
__decorate([
|
|
3562
|
+
n({ type: Array })
|
|
3563
|
+
], KRSubbar.prototype, "breadcrumbs", void 0);
|
|
3564
|
+
__decorate([
|
|
3565
|
+
n({ type: Boolean })
|
|
3566
|
+
], KRSubbar.prototype, "menu", void 0);
|
|
3567
|
+
KRSubbar = __decorate([
|
|
3568
|
+
t$1('kr-subbar')
|
|
3569
|
+
], KRSubbar);
|
|
3570
|
+
|
|
3571
|
+
export { KRScaffold, KRScreenDetail, KRScreenNav, KRShell, KRSubbar };
|
|
3572
|
+
//# sourceMappingURL=krubble-app.bundled.js.map
|