@k3000/ce 0.1.0 → 0.2.1
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/README.md +12 -6
- package/app/comp/comp-test.mjs +1 -1
- package/app/comp/nav-bar.mjs +1 -1
- package/app/comp/page-container.mjs +1 -1
- package/app/comp/tab-bar.mjs +1 -1
- package/app/index.html +1 -1
- package/bin.mjs +48 -0
- package/ce.mjs +1 -0
- package/comm/router-view.mjs +1 -1
- package/console/components/common-action-button.mjs +1 -1
- package/console/components/common-header.mjs +1 -1
- package/console/components/common-input.mjs +1 -1
- package/console/components/common-modal.mjs +1 -1
- package/console/components/common-pagination.mjs +1 -1
- package/console/components/common-toolbar.mjs +1 -1
- package/console/components/div-test.mjs +11 -0
- package/console/components/g-button.mjs +79 -0
- package/console/components/g-cell.mjs +129 -0
- package/console/components/g-col.mjs +38 -0
- package/console/components/g-field.mjs +162 -0
- package/console/components/g-form.mjs +11 -0
- package/console/components/g-icon.mjs +82 -0
- package/console/components/g-image.mjs +127 -0
- package/console/components/g-popup.mjs +130 -0
- package/console/components/g-row.mjs +120 -0
- package/console/components/g-space.mjs +21 -0
- package/console/components/g-toast.mjs +116 -0
- package/console/components/layout-header.mjs +0 -179
- package/console/components/layout-menu.mjs +99 -2
- package/console/index.html +2 -1
- package/console/index2.html +46 -0
- package/console/pages/demo-button.mjs +116 -0
- package/console/pages/demo-cell.mjs +59 -0
- package/console/pages/demo-field.mjs +67 -0
- package/console/pages/demo-icon.mjs +200 -0
- package/console/pages/demo-image.mjs +113 -0
- package/console/pages/demo-layout.mjs +141 -0
- package/console/pages/demo-popup.mjs +158 -0
- package/console/pages/demo-space.mjs +51 -0
- package/console/pages/demo-toast.mjs +66 -0
- package/index.mjs +8 -1
- package/package.json +8 -2
- package/test.html +15 -0
- package/console/components/console-header.mjs +0 -26
package/README.md
CHANGED
|
@@ -16,12 +16,20 @@
|
|
|
16
16
|
|
|
17
17
|
## 🚀 快速上手
|
|
18
18
|
|
|
19
|
-
### 1.
|
|
19
|
+
### 1. 全局安装
|
|
20
|
+
```
|
|
21
|
+
.\> npm i @k3000/ce -g #全局安装使用ce命令
|
|
22
|
+
.\> cd demo #进入需要的目录
|
|
23
|
+
.\demo> ce copy #复制核心框架文件可以带参数,ce copy dist.js,将ce.mjs复制到当前目录,并改名为:dist.js
|
|
24
|
+
.\demo> ce demo #复制使用示例一共三个目录和一个数据文件:/app、/comm、/console、/info.json
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
### 2. 项目引入
|
|
20
28
|
在 `index.html` 中通过 Script 标签引入核心框架:
|
|
21
29
|
|
|
22
30
|
```html
|
|
23
31
|
<script type="module">
|
|
24
|
-
import { config } from '../
|
|
32
|
+
import { config } from '../ce.mjs'
|
|
25
33
|
|
|
26
34
|
config({
|
|
27
35
|
dir: './console/components',
|
|
@@ -30,15 +38,13 @@
|
|
|
30
38
|
</script>
|
|
31
39
|
```
|
|
32
40
|
|
|
33
|
-
###
|
|
41
|
+
### 3. 定义组件
|
|
34
42
|
每个组件为一个独立的 `.mjs` 文件。
|
|
35
43
|
|
|
36
44
|
**示例: `my-counter.mjs`**
|
|
37
45
|
|
|
38
|
-
**更多示例参照: `/app`,`/console`**
|
|
39
|
-
|
|
40
46
|
```javascript
|
|
41
|
-
import { useWatch } from "../../
|
|
47
|
+
import { useWatch } from "../../ce.mjs";
|
|
42
48
|
|
|
43
49
|
// 1. 定义 HTML 结构 (支持插值 {{ }})
|
|
44
50
|
export const innerHTML = `
|
package/app/comp/comp-test.mjs
CHANGED
package/app/comp/nav-bar.mjs
CHANGED
package/app/comp/tab-bar.mjs
CHANGED
package/app/index.html
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0, viewport-fit=cover"/>
|
|
7
7
|
<title>demo</title>
|
|
8
8
|
<script type="module">
|
|
9
|
-
import { config } from '../
|
|
9
|
+
import { config } from '../ce.mjs'
|
|
10
10
|
|
|
11
11
|
config({
|
|
12
12
|
dir: './app/comp',
|
package/bin.mjs
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {cpSync, existsSync, mkdirSync} from "node:fs";
|
|
3
|
+
import {resolve, dirname} from "node:path";
|
|
4
|
+
|
|
5
|
+
const [, , key, value] = process.argv
|
|
6
|
+
|
|
7
|
+
const path = import.meta.url.substring(process.platform === 'win32' ? 8 : 7)
|
|
8
|
+
|
|
9
|
+
const getDir = path => decodeURI(dirname(path));
|
|
10
|
+
/**
|
|
11
|
+
* 将本目录下的文件或者文件夹复制到项目运行目录
|
|
12
|
+
* @param {string} source 自动加上'/'
|
|
13
|
+
* @param {string} destination
|
|
14
|
+
* @param {boolean} force
|
|
15
|
+
* @example
|
|
16
|
+
* myCopyFn('source', 'destination')
|
|
17
|
+
*/
|
|
18
|
+
const myCopyFn = (source, destination = source, force = false) => {
|
|
19
|
+
|
|
20
|
+
return cpSync( `${getDir(path)}/${source}`, destination, {recursive: true, force})
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// console.log(`key: ${key}, value: ${value}, path: ${path}, getDir: ${getDir(path)}`);
|
|
24
|
+
|
|
25
|
+
switch (key) {
|
|
26
|
+
|
|
27
|
+
case 'copy':
|
|
28
|
+
|
|
29
|
+
let target = value || 'ce.mjs'
|
|
30
|
+
|
|
31
|
+
if (!target.endsWith('.mjs') && !target.endsWith('.js')) {
|
|
32
|
+
|
|
33
|
+
target += '.js'
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
myCopyFn('index.mjs', target, true)
|
|
37
|
+
|
|
38
|
+
break
|
|
39
|
+
|
|
40
|
+
case 'demo':
|
|
41
|
+
|
|
42
|
+
myCopyFn('app', 'app', !!value)
|
|
43
|
+
myCopyFn('comm', 'comm', !!value)
|
|
44
|
+
myCopyFn('console', 'console', !!value)
|
|
45
|
+
myCopyFn('info.json', 'info.json', !!value)
|
|
46
|
+
|
|
47
|
+
break
|
|
48
|
+
}
|
package/ce.mjs
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
let dir="./comp",ext="mjs",getVariables=e=>{const t=e.match(/\{\{.+?}}/g);if(!t)return new Set;const o=new Set,n=new Set(["true","false","null","undefined","this","new","typeof","in","instanceof"]);return t.forEach((e=>{const t=e.slice(2,-2).match(/\b[a-zA-Z_$][\w$]*(?:\s*\.\s*[a-zA-Z_$][\w$]*)*(?!\s*\()/g);t&&t.forEach((e=>{n.has(e)||o.add(e)}))})),o},createTmpNode=(e="")=>document.createTextNode(""),literalsCode=e=>`with (scope) {\n\treturn \`${e.replace(/{{(.*?)}}/g,((e,t)=>`\${${t}}`))}\` \n}`,functionCode=e=>`with (scope) {\n\treturn ${e} \n}`,handleArray=(e,t)=>{const o=e.ownerElement;if(e.nodeMap||(e.nodeMap=[],e.mark=createTmpNode("数组结束标记"),o.parentNode.replaceChild(e.mark,o),e.ownerElement=o.cloneNode(!0)),!Array.isArray(t))return e;const n=e.mark.parentNode;let r=0;for(;r<t.length;r++)if(void 0!==e.nodeMap[r]){if(e.nodeMap[r][0]!==t[r]){const s=o.cloneNode(!0);try{t[r]._=e.scope}catch(e){}bindNode(t[r],s,!0),n.replaceChild(s,e.nodeMap[r][1]),e.nodeMap[r]=[t[r],s]}}else{const s=o.cloneNode(!0);try{t[r]._=e.scope}catch(e){}bindNode(t[r],s,!0),n.insertBefore(s,e.mark),e.nodeMap[r]=[t[r],s]}const s=r;for(;r<e.nodeMap.length;r++)n.removeChild(e.nodeMap[r][1]);return e.nodeMap.splice(s,e.nodeMap.length-s),e};export const config=e=>{e.dir&&(dir=e.dir),e.ext&&(ext=e.ext),"function"==typeof e.getVariables&&(getVariables=e.getVariables),"function"==typeof e.createTmpNode&&(createTmpNode=e.createTmpNode),"function"==typeof e.literalsCode&&(literalsCode=e.literalsCode),"function"==typeof e.functionCode&&(functionCode=e.functionCode),"function"==typeof e.handleArray&&(handleArray=e.handleArray)};const getCEName=e=>e.tagName.includes("-")?e.tagName.toLowerCase():e.hasAttribute("is")?e.getAttribute("is"):void 0;export const throttle=(e,t=41)=>{let o;return(...n)=>{clearTimeout(o),o=setTimeout((()=>e(...n)),t)}};const objectPool=new WeakMap,importPool=new WeakSet,addListener=(e,t,o)=>{getVariables(t).forEach((t=>{let n="",r=e,s=0,a=t.split(".");for([s,n]of a.entries()){if(s===a.length-1)break;if(null===r||"object"!=typeof r||!Reflect.has(r,n))return;if(Array.isArray(r[n])&&s===a.length-2)break;r=r[n]}if(null!==r&&"object"==typeof r&&(!Object.getOwnPropertyDescriptor(r,n)||Object.getOwnPropertyDescriptor(r,n).configurable))if(objectPool.has(r)){const e=objectPool.get(r);if(e.has(n))e.get(n).add(o);else{const t=new Set([o]);e.set(n,t);let s=r[n];Object.defineProperty(r,n,{get:()=>s,set:e=>{s!==e&&(s=e,t.forEach((e=>e())))}})}}else{const e=new Set([o]);objectPool.set(r,new Map([[n,e]]));let t=r[n];Object.defineProperty(r,n,{get:()=>t,set:o=>{t!==o&&(t=o,e.forEach((e=>e())))}})}}))},ceMap=Object.create(null),disposeCE=(e,t)=>{if(e.querySelectorAll("*:not(:defined)").forEach((e=>importCE(e))),ceMap[t].shadowRoot&&(e.attachShadow({mode:"open"}),e.shadowRoot.appendChild(ceMap[t].shadowRoot.create(e))),ceMap[t].innerHTML||ceMap[t].outerHTML){const o=(ceMap[t].innerHTML||ceMap[t].outerHTML).create(e);if(!ceMap[t].shadowRoot){const t=Array.from(o.querySelectorAll("slot")),n=t.reduce(((e,t)=>(e[t.getAttribute("name")||""]=t,e)),{}),r=Array.from(e.childNodes);for(const e of r){const t="function"==typeof e.getAttribute&&e.getAttribute("slot")||"";n[t]&&n[t].parentNode.insertBefore(e,n[t])}for(const e of t)e.parentNode.removeChild(e)}if(ceMap[t].outerHTML){for(;o.firstChild;)e.parentElement.insertBefore(o.firstChild,e);e.parentElement.removeChild(e)}else e.appendChild(o)}"function"==typeof e.ready&&e.ready()};let tmp=null;const importCE=(e,t=void 0)=>{if(t=t??getCEName(e),importPool.has(e))return;if(importPool.add(e),void 0!==customElements.get(t))return void disposeCE(e,t);const o=e.nodeName.includes("-");let n=dir;if(e.attributes.dir&&e.attributes.dir.nodeValue){const t=e.attributes.dir.nodeValue;e.removeAttribute("dir"),n=t.startsWith("/")||t.startsWith("./")?t:`${n}/${t}`}import(`${n}/${t}.${ext}`).then((n=>{void 0===customElements.get(t)&&(ceMap[t]=Object.create(null),"string"==typeof n.innerHTML&&(ceMap[t].innerHTML=bind(n.innerHTML)),"string"==typeof n.outerHTML&&(ceMap[t].outerHTML=bind(n.outerHTML)),"string"==typeof n.shadowRoot&&(ceMap[t].shadowRoot=bind(n.shadowRoot)),o?customElements.define(t,n.default):customElements.define(t,n.default,{extends:e.nodeName.toLowerCase()})),disposeCE(e,t)})).catch((e=>{console.error(e)}))},disposeNode=(e,t)=>{const o=t.nodeValue;if(null===o.match(/{{(.*?)}}/))return;const n=new Function("scope",literalsCode(o));addListener(e,o,throttle((()=>t.nodeValue=n.call(t,e)))),t.nodeValue=n.call(t,e)},disposeAttrValue=(e,t,o,n)=>{if(!n.startsWith("{{")||!n.endsWith("}}")||n.slice(2).includes("{{")){const r=new Function("scope",literalsCode(n));return()=>o.nodeValue=r.call(t,e)}const r=new Function("scope",functionCode(n.slice(2,-2)));return()=>{const n=r.call(t,e);switch(n){case!0:o.nodeValue="",t.attributes.setNamedItem(o);break;case!1:t.removeAttribute(o.nodeName);break;default:o.nodeValue=n,t.attributes.setNamedItem(o)}}},disposeSpecialAttr=(e,t,o,n)=>{const r=t.ownerElement;let s=t.nodeName;if(s.startsWith("on")){if(r.removeAttribute(s),!o.includes("(")){const t=o.split(".");let n="",a=e,i=0;for([i,n]of t.entries()){if(i===t.length-1)break;if(null===a||"object"!=typeof a||!Reflect.has(a,n)){a={};break}a=a[n]}if("function"==typeof a[n])return r[s]=e=>a[n](e),!0}const t=new Function("scope","event",functionCode(o));return r[s]=o=>t.call(r,e,o),!0}if("connect"===s){r.removeAttribute(s);const n=new Function("scope",functionCode(o)),a=createTmpNode("节点断开重连的标记"),i=()=>{n(e)?a.parentNode?.replaceChild(r,a):r.parentNode?.replaceChild(a,r)};return addListener(e,t.nodeValue,throttle((()=>i()))),i(),!0}if("each"===s){r.removeAttribute(s);let n={ownerElement:r,scope:e};if(Reflect.has(e,o)){const r=()=>n=handleArray(n,e[o]);return addListener(e,t.nodeValue,throttle((()=>r()))),r(),!0}const a=new Function("scope",functionCode(o));return handleArray(n,a(e)),!0}if(n){r.removeAttribute(s);const n=new Function("scope",functionCode(o));s.includes("-")&&(s=s.split("-").map(((e,t)=>e&&t?e[0].toUpperCase()+e.slice(1):e)).join(""));const a=()=>{const t=n(e);r[s]="function"==typeof t?t.bind(e):t};return addListener(e,t.nodeValue,throttle((()=>a()))),a(),!0}return!1},disposeAttr=(e,t,o)=>{const n=t.nodeValue;if(null===n.match(/{{(.*?)}}/))return;if(n.startsWith("{{")&&n.endsWith("}}")&&disposeSpecialAttr(e,t,n.slice(2,-2),o))return;const r=disposeAttrValue(e,t.ownerElement,t,n);addListener(e,n,throttle((()=>r()))),r()};export const bindNode=(e,t,o)=>{const n=o?[t]:Array.from(t.childNodes);for(const o of n){if(o.attributes)for(const n of Array.from(o.attributes))if(disposeAttr(e,n,getCEName(o)),"each"===n.nodeName&&n.nodeValue.startsWith("{{")&&n.nodeValue.endsWith("}}"))return t;if(o.childNodes&&bindNode(e,o),3===o.nodeType&&null!==o.nodeValue&&disposeNode(e,o),1===o.nodeType){const e=getCEName(o);e&&importCE(o,e)}}return t};export const bind=e=>{const t=document.createElement("template");return t.innerHTML=e,{create:e=>bindNode(e,document.importNode(t.content,!0))}};const style=document.createElement("style");style.innerHTML="\n*:not(:defined) {\n display: none\n}\n",document.head.appendChild(style),addEventListener("DOMContentLoaded",(()=>document.querySelectorAll("*:not(:defined)").forEach((e=>importCE(e)))));class EventBus{value=void 0;targets=new Map;add(e,t=Object.create(null)){this.targets.set(e,t),t.preValue&&void 0!==this.value&&e(...this.value)}remove(e){this.targets.delete(e)}dispatch(...e){return this.value=e,Promise.allSettled(this.targets.entries().map((([t,{once:o}])=>(o&&this.targets.delete(t),t(...e)))))}}const eventBusObj=Object.create(null),eventBus=Object.defineProperties(Object.create(null),{add:{value:(e,t,o)=>{Reflect.has(eventBusObj,e)||(eventBusObj[e]=new EventBus),eventBusObj[e].add(t,o)}},remove:{value:(e,t)=>{Reflect.has(eventBusObj,e)&&eventBusObj[e].remove(t)}},dispatch:{value:(e,...t)=>(Reflect.has(eventBusObj,e)||(eventBusObj[e]=new EventBus),eventBusObj[e].dispatch(...t))}});export const useEvent=()=>eventBus;export const useRef=e=>{const t=Object.create(null);for(const o of e.querySelectorAll("[ref]"))t[o.attributes.ref.nodeValue]=o;return t};export const useAttr=e=>{const t=Object.create(null);for(const o of e.attributes)o.nodeName.includes("-")&&(t[o.nodeName.split("-").map(((e,t)=>e&&t?e[0].toUpperCase()+e.slice(1):e)).join("")]=o.nodeValue),t[o.nodeName]=o.nodeValue;return t};export const useWatch=e=>(t,o=!1)=>{for(const[n,r]of Object.entries(t)){if("function"!=typeof r)continue;let t=e[n];Object.defineProperty(e,n,{get:()=>t,set:e=>{if(!1!==r(e,t))return t=e}}),o&&r(t,t)}};
|
package/comm/router-view.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {throttle, useWatch} from "../../
|
|
1
|
+
import {throttle, useWatch} from "../../ce.mjs";
|
|
2
2
|
|
|
3
3
|
export const innerHTML = `
|
|
4
4
|
<div class="px-6 py-4 border-t border-white/20 dark:border-white/5 bg-white/30 dark:bg-white/5 backdrop-blur-md flex items-center justify-between">
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { useAttr, bind, useRef } from "../../ce.mjs";
|
|
2
|
+
|
|
3
|
+
export const innerHTML = `
|
|
4
|
+
<button disabled="{{!!(isDisabled || loading)}}" class="cursor-pointer relative inline-flex items-center justify-center {{sizeClass}} rounded-full backdrop-blur-2xl border shadow-[inset_0_1px_1px_rgba(255,255,255,0.8),0_4px_12px_rgba(0,0,0,0.05)] dark:shadow-[inset_0_1px_1px_rgba(255,255,255,0.1),0_4px_12px_rgba(0,0,0,0.2)] font-medium transition-all {{(isDisabled || loading) ? 'opacity-50 cursor-not-allowed' : 'hover:-translate-y-0.5 active:translate-y-0 active:shadow-inner group'}} overflow-hidden {{typeClass}} {{classStr}}">
|
|
5
|
+
<!-- Highlight overlay for glass effect -->
|
|
6
|
+
<div class="absolute inset-0 rounded-full bg-gradient-to-b from-white/40 to-transparent dark:from-white/5 opacity-0 {{(isDisabled || loading) ? '' : 'group-hover:opacity-100'}} transition-opacity pointer-events-none"></div>
|
|
7
|
+
<div class="relative z-10 flex items-center justify-center gap-2">
|
|
8
|
+
<svg class="animate-spin h-4 w-4 text-current {{loading ? '' : 'hidden'}}" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
|
|
9
|
+
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
|
|
10
|
+
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
|
|
11
|
+
</svg>
|
|
12
|
+
<slot name="prefix"></slot>
|
|
13
|
+
<slot name="icon"></slot>
|
|
14
|
+
<div connect="{{loading && loadingText}}" style="display: contents">{{loadingText}}</div>
|
|
15
|
+
<div style="display: {{loading && loadingText ? 'none' : 'contents'}}">
|
|
16
|
+
<g-icon connect="{{icon && !isIconUrl}}" name="{{icon}}" class="w-4 h-4"></g-icon>
|
|
17
|
+
<img connect="{{isIconUrl}}" src="{{isIconUrl ? icon : false}}" class="w-4 h-4" />
|
|
18
|
+
<slot></slot>
|
|
19
|
+
</div>
|
|
20
|
+
<slot name="suffix"></slot>
|
|
21
|
+
</div>
|
|
22
|
+
</button>
|
|
23
|
+
`
|
|
24
|
+
|
|
25
|
+
const typeStyles = {
|
|
26
|
+
default: 'bg-white/40 dark:bg-gray-800/40 border-white/60 dark:border-white/10 text-gray-800 dark:text-gray-100 hover:bg-white/60 dark:hover:bg-gray-700/60',
|
|
27
|
+
primary: 'bg-blue-500/10 dark:bg-blue-500/20 border-blue-500/20 dark:border-blue-500/10 text-blue-600 dark:text-blue-400 hover:bg-blue-500/20 dark:hover:bg-blue-500/30',
|
|
28
|
+
success: 'bg-green-500/10 dark:bg-green-500/20 border-green-500/20 dark:border-green-500/10 text-green-600 dark:text-green-400 hover:bg-green-500/20 dark:hover:bg-green-500/30',
|
|
29
|
+
warning: 'bg-orange-500/10 dark:bg-orange-500/20 border-orange-500/20 dark:border-orange-500/10 text-orange-600 dark:text-orange-400 hover:bg-orange-500/20 dark:hover:bg-orange-500/30',
|
|
30
|
+
danger: 'bg-red-500/10 dark:bg-red-500/20 border-red-500/20 dark:border-red-500/10 text-red-600 dark:text-red-400 hover:bg-red-500/20 dark:hover:bg-red-500/30'
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const plainTypeStyles = {
|
|
34
|
+
default: 'bg-transparent border-white/60 dark:border-white/10 text-gray-800 dark:text-gray-100 hover:bg-white/10 dark:hover:bg-gray-700/20',
|
|
35
|
+
primary: 'bg-transparent border-blue-500/20 dark:border-blue-500/10 text-blue-600 dark:text-blue-400 hover:bg-blue-500/10 dark:hover:bg-blue-500/20',
|
|
36
|
+
success: 'bg-transparent border-green-500/20 dark:border-green-500/10 text-green-600 dark:text-green-400 hover:bg-green-500/10 dark:hover:bg-green-500/20',
|
|
37
|
+
warning: 'bg-transparent border-orange-500/20 dark:border-orange-500/10 text-orange-600 dark:text-orange-400 hover:bg-orange-500/10 dark:hover:bg-orange-500/20',
|
|
38
|
+
danger: 'bg-transparent border-red-500/20 dark:border-red-500/10 text-red-600 dark:text-red-400 hover:bg-red-500/10 dark:hover:bg-red-500/20'
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const sizeStyles = {
|
|
42
|
+
large: 'px-6 py-2.5 text-base',
|
|
43
|
+
normal: 'px-4 py-2 text-sm',
|
|
44
|
+
small: 'px-3 py-1.5 text-xs',
|
|
45
|
+
mini: 'px-2.5 py-1 text-[11px]'
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export default class extends HTMLElement {
|
|
49
|
+
text = ''
|
|
50
|
+
icon = ''
|
|
51
|
+
isIconUrl = false
|
|
52
|
+
classStr = ''
|
|
53
|
+
typeClass = ''
|
|
54
|
+
sizeClass = ''
|
|
55
|
+
isDisabled = false
|
|
56
|
+
loadingText = ''
|
|
57
|
+
|
|
58
|
+
constructor() {
|
|
59
|
+
super()
|
|
60
|
+
this.style.display = 'inline-block'
|
|
61
|
+
const attr = useAttr(this)
|
|
62
|
+
this.text = attr.text || ''
|
|
63
|
+
this.icon = attr.icon || ''
|
|
64
|
+
this.isIconUrl = this.icon.startsWith('http') || this.icon.startsWith('/') || this.icon.startsWith('data:')
|
|
65
|
+
this.classStr = attr.class || ''
|
|
66
|
+
this.isDisabled = attr.disabled !== undefined && attr.disabled !== 'false'
|
|
67
|
+
this.loadingText = attr.loadingText || ''
|
|
68
|
+
|
|
69
|
+
const type = attr.type || 'default'
|
|
70
|
+
const isPlain = attr.plain !== undefined && attr.plain !== 'false'
|
|
71
|
+
const size = attr.size || 'normal'
|
|
72
|
+
|
|
73
|
+
this.typeClass = isPlain
|
|
74
|
+
? (plainTypeStyles[type] || plainTypeStyles.default)
|
|
75
|
+
: (typeStyles[type] || typeStyles.default)
|
|
76
|
+
|
|
77
|
+
this.sizeClass = sizeStyles[size] || sizeStyles.normal
|
|
78
|
+
}
|
|
79
|
+
}
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import { useAttr, bind, useRef } from "../../ce.mjs";
|
|
2
|
+
|
|
3
|
+
export const innerHTML = `
|
|
4
|
+
<!-- Main Cell Container -->
|
|
5
|
+
<div class="group relative flex items-center justify-between p-4 bg-white/40 dark:bg-gray-800/40 backdrop-blur-2xl border-b border-white/60 dark:border-white/10 shadow-[inset_0_1px_1px_rgba(255,255,255,0.8),0_4px_12px_rgba(0,0,0,0.02)] dark:shadow-[inset_0_1px_1px_rgba(255,255,255,0.1),0_4px_12px_rgba(0,0,0,0.1)] transition-all hover:bg-white/60 dark:hover:bg-gray-700/60 {{clickable ? 'cursor-pointer active:bg-white/30 dark:active:bg-gray-800/80 active:translate-y-[1px]' : ''}} {{classStr}}">
|
|
6
|
+
<!-- Highlight overlay for glass effect on hover -->
|
|
7
|
+
<div class="absolute inset-0 bg-gradient-to-b from-white/40 to-transparent dark:from-white/5 opacity-0 group-hover:opacity-100 transition-opacity pointer-events-none {{isLast ? 'rounded-b-2xl' : ''}} {{isFirst ? 'rounded-t-2xl' : ''}}"></div>
|
|
8
|
+
|
|
9
|
+
<!-- Left Section: Icon + Title + Label -->
|
|
10
|
+
<div class="relative z-10 flex items-center gap-3 flex-1 min-w-0">
|
|
11
|
+
<!-- Icon Slot -->
|
|
12
|
+
<div class="flex-shrink-0 text-gray-500 dark:text-gray-400 {{icon ? '' : 'hidden'}}">
|
|
13
|
+
<span ref="iconMark"></span>
|
|
14
|
+
</div>
|
|
15
|
+
|
|
16
|
+
<!-- Text Content -->
|
|
17
|
+
<div class="flex flex-col flex-1 min-w-0">
|
|
18
|
+
<div class="text-[15px] font-medium text-gray-800 dark:text-gray-100 truncate">{{title}}</div>
|
|
19
|
+
<div class="text-xs text-gray-500 dark:text-gray-400 truncate mt-0.5 {{label ? '' : 'hidden'}}">{{label}}</div>
|
|
20
|
+
</div>
|
|
21
|
+
</div>
|
|
22
|
+
|
|
23
|
+
<!-- Right Section: Value + Arrow Slot -->
|
|
24
|
+
<div class="relative z-10 flex items-center gap-2 pl-4 flex-shrink-0">
|
|
25
|
+
<!-- Value Content -->
|
|
26
|
+
<div class="text-[14px] text-gray-500 dark:text-gray-400 text-right">{{value}}</div>
|
|
27
|
+
|
|
28
|
+
<!-- Arrow / Right Icon Slot -->
|
|
29
|
+
<div class="flex-shrink-0 text-gray-400 dark:text-gray-500 {{showArrow ? '' : 'hidden'}}">
|
|
30
|
+
<span ref="arrowMark"></span>
|
|
31
|
+
</div>
|
|
32
|
+
|
|
33
|
+
<slot name="right-icon"></slot>
|
|
34
|
+
</div>
|
|
35
|
+
</div>
|
|
36
|
+
`
|
|
37
|
+
|
|
38
|
+
export default class extends HTMLElement {
|
|
39
|
+
title = ''
|
|
40
|
+
value = ''
|
|
41
|
+
label = ''
|
|
42
|
+
icon = ''
|
|
43
|
+
isIconUrl = false
|
|
44
|
+
arrowDirection = ''
|
|
45
|
+
showArrow = false
|
|
46
|
+
clickable = false
|
|
47
|
+
isClickableHover = false
|
|
48
|
+
classStr = ''
|
|
49
|
+
|
|
50
|
+
// For grouped rounded corners if we support cell-group later
|
|
51
|
+
isFirst = false
|
|
52
|
+
isLast = false
|
|
53
|
+
|
|
54
|
+
constructor() {
|
|
55
|
+
super()
|
|
56
|
+
this.style.display = 'block'
|
|
57
|
+
const attr = useAttr(this)
|
|
58
|
+
|
|
59
|
+
this.title = attr.title || ''
|
|
60
|
+
this.value = attr.value || ''
|
|
61
|
+
this.label = attr.label || ''
|
|
62
|
+
|
|
63
|
+
this.icon = attr.icon || ''
|
|
64
|
+
this.isIconUrl = this.icon.startsWith('http') || this.icon.startsWith('/') || this.icon.startsWith('data:')
|
|
65
|
+
|
|
66
|
+
// Arrow direction: 'right', 'down', 'up', 'left'. Default is empty (no arrow)
|
|
67
|
+
// If is-link is present, implicitly show right arrow unless specified
|
|
68
|
+
const isLink = attr['is-link'] !== undefined && attr['is-link'] !== 'false'
|
|
69
|
+
this.arrowDirection = attr['arrow-direction'] || (isLink ? 'right' : '')
|
|
70
|
+
this.showArrow = !!this.arrowDirection
|
|
71
|
+
|
|
72
|
+
// Clickable implies hover effect and pointer cursor
|
|
73
|
+
this.clickable = this.showArrow || (attr.clickable !== undefined && attr.clickable !== 'false')
|
|
74
|
+
|
|
75
|
+
this.classStr = attr.class || ''
|
|
76
|
+
|
|
77
|
+
// Helper classes for borderline and border-radius rounding (handled externally via cell-group usually, but allowing override)
|
|
78
|
+
this.isFirst = attr['round-top'] !== undefined && attr['round-top'] !== 'false'
|
|
79
|
+
this.isLast = attr['border'] === 'false' || (attr['round-bottom'] !== undefined && attr['round-bottom'] !== 'false')
|
|
80
|
+
|
|
81
|
+
// Adjust border logic
|
|
82
|
+
if (this.isLast || attr.border === 'false') {
|
|
83
|
+
this.classStr = this.classStr.replace('border-b', '') + ' border-transparent'
|
|
84
|
+
}
|
|
85
|
+
if (this.isFirst) {
|
|
86
|
+
this.classStr += ' rounded-t-2xl'
|
|
87
|
+
}
|
|
88
|
+
if (this.isLast) {
|
|
89
|
+
this.classStr += ' rounded-b-2xl'
|
|
90
|
+
}
|
|
91
|
+
if (!this.isFirst && !this.isLast && attr.round === 'true') {
|
|
92
|
+
this.classStr += ' rounded-2xl border-transparent'
|
|
93
|
+
this.isFirst = true
|
|
94
|
+
this.isLast = true
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
ready() {
|
|
99
|
+
const { iconMark, arrowMark } = useRef(this)
|
|
100
|
+
|
|
101
|
+
// Handle Left Icon
|
|
102
|
+
if (this.icon) {
|
|
103
|
+
const iconHtml = this.isIconUrl
|
|
104
|
+
? `<img src="${this.icon}" class="w-5 h-5" />`
|
|
105
|
+
: `<g-icon name="${this.icon}" class="w-5 h-5"></g-icon>`
|
|
106
|
+
const iconNode = bind(iconHtml).create(this)
|
|
107
|
+
iconMark.parentElement.insertBefore(iconNode, iconMark)
|
|
108
|
+
}
|
|
109
|
+
iconMark.parentElement.removeChild(iconMark)
|
|
110
|
+
|
|
111
|
+
// Handle Right Arrow
|
|
112
|
+
if (this.showArrow) {
|
|
113
|
+
// Map direction to standard icons if we had them or just use svg path directly
|
|
114
|
+
// M9 5l7 7-7 7 (right)
|
|
115
|
+
// M19 9l-7 7-7-7 (down)
|
|
116
|
+
// M5 15l7-7 7 7 (up)
|
|
117
|
+
// M15 19l-7-7 7-7 (left)
|
|
118
|
+
let pathD = 'M9 5l7 7-7 7' // default right
|
|
119
|
+
if (this.arrowDirection === 'down') pathD = 'M19 9l-7 7-7-7'
|
|
120
|
+
else if (this.arrowDirection === 'up') pathD = 'M5 15l7-7 7 7'
|
|
121
|
+
else if (this.arrowDirection === 'left') pathD = 'M15 19l-7-7 7-7'
|
|
122
|
+
|
|
123
|
+
const arrowHtml = `<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="${pathD}"></path></svg>`
|
|
124
|
+
const arrowNode = bind(arrowHtml).create(this)
|
|
125
|
+
arrowMark.parentElement.insertBefore(arrowNode, arrowMark)
|
|
126
|
+
}
|
|
127
|
+
arrowMark.parentElement.removeChild(arrowMark)
|
|
128
|
+
}
|
|
129
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { useAttr } from "../../ce.mjs";
|
|
2
|
+
|
|
3
|
+
export default class extends HTMLElement {
|
|
4
|
+
constructor() {
|
|
5
|
+
super()
|
|
6
|
+
this.style.display = 'block'
|
|
7
|
+
this.style.boxSizing = 'border-box'
|
|
8
|
+
|
|
9
|
+
const attr = useAttr(this)
|
|
10
|
+
|
|
11
|
+
const span = attr.span ? Number(attr.span) : 24
|
|
12
|
+
const offset = attr.offset ? Number(attr.offset) : 0
|
|
13
|
+
const classStr = attr.class || ''
|
|
14
|
+
|
|
15
|
+
// Convert span 1-24 to basis/% widths directly on host element
|
|
16
|
+
if (span) {
|
|
17
|
+
const percentage = (span / 24) * 100
|
|
18
|
+
this.style.width = `${percentage}%`
|
|
19
|
+
this.style.flex = `0 0 ${percentage}%`
|
|
20
|
+
this.style.maxWidth = `${percentage}%`
|
|
21
|
+
} else {
|
|
22
|
+
this.style.width = '0'
|
|
23
|
+
this.style.flex = '0 0 0'
|
|
24
|
+
this.style.maxWidth = '0'
|
|
25
|
+
this.style.overflow = 'hidden'
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// Convert offset 1-24 to margin-left natively
|
|
29
|
+
if (offset) {
|
|
30
|
+
const offsetPerc = (offset / 24) * 100
|
|
31
|
+
this.style.marginLeft = `${offsetPerc}%`
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
if (classStr) {
|
|
35
|
+
this.className = this.className ? this.className + ' ' + classStr : classStr
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
import { useAttr, useWatch } from "../../ce.mjs";
|
|
2
|
+
|
|
3
|
+
export const innerHTML = `
|
|
4
|
+
<div class="flex items-center w-full min-h-[44px] px-3 bg-white/60 dark:bg-gray-800/60 backdrop-blur-2xl rounded-xl border border-gray-200 dark:border-gray-700 transition-all duration-300 {{focusClass}} {{disabledClass}} {{readonlyClass}}">
|
|
5
|
+
<!-- Prefix Icon Option (e.g. search icon) -->
|
|
6
|
+
<div class="mr-2 text-gray-400 dark:text-gray-500 flex items-center justify-center {{leftIcon ? '' : 'hidden'}}">
|
|
7
|
+
<g-icon name="{{leftIcon}}" class="w-4 h-4"></g-icon>
|
|
8
|
+
</div>
|
|
9
|
+
|
|
10
|
+
<!-- Label Option -->
|
|
11
|
+
<div class="mr-4 text-sm whitespace-nowrap text-gray-700 dark:text-gray-300 font-medium select-none {{label ? '' : 'hidden'}}">
|
|
12
|
+
{{label}}
|
|
13
|
+
</div>
|
|
14
|
+
|
|
15
|
+
<!-- Input Area -->
|
|
16
|
+
<div class="flex-1 relative flex items-center h-full">
|
|
17
|
+
<input
|
|
18
|
+
type="{{type}}"
|
|
19
|
+
class="w-full h-full bg-transparent border-none outline-none text-sm text-gray-900 dark:text-gray-100 placeholder-gray-400 dark:placeholder-gray-500 {{disabled || readonly ? 'cursor-not-allowed text-gray-500 dark:text-gray-400' : ''}}"
|
|
20
|
+
placeholder="{{placeholder}}"
|
|
21
|
+
value="{{value}}"
|
|
22
|
+
{{disabled ? 'disabled' : ''}}
|
|
23
|
+
{{readonly ? 'readonly' : ''}}
|
|
24
|
+
oninput="{{handleInput}}"
|
|
25
|
+
onfocus="{{handleFocus}}"
|
|
26
|
+
onblur="{{handleBlur}}"
|
|
27
|
+
onchange="{{handleChange}}"
|
|
28
|
+
onkeydown="{{handleKeyDown}}"
|
|
29
|
+
/>
|
|
30
|
+
|
|
31
|
+
<!-- Clear Icon -->
|
|
32
|
+
<div class="ml-2 text-gray-400 hover:text-gray-600 dark:hover:text-gray-200 cursor-pointer transition-colors flex items-center justify-center {{clearable && value && !disabled && !readonly ? '' : 'hidden'}}" onclick="{{clear}}">
|
|
33
|
+
<g-icon name="close" class="w-4 h-4 bg-gray-200/50 dark:bg-gray-700/50 rounded-full p-0.5 hover:bg-gray-300 dark:hover:bg-gray-600"></g-icon>
|
|
34
|
+
</div>
|
|
35
|
+
|
|
36
|
+
<!-- Right Icon Option -->
|
|
37
|
+
<div class="ml-2 text-gray-400 dark:text-gray-500 flex items-center justify-center cursor-pointer {{rightIcon ? '' : 'hidden'}}" onclick="{{onRightIconClick}}">
|
|
38
|
+
<g-icon name="{{rightIcon}}" class="w-4 h-4"></g-icon>
|
|
39
|
+
</div>
|
|
40
|
+
</div>
|
|
41
|
+
</div>
|
|
42
|
+
`
|
|
43
|
+
|
|
44
|
+
export default class extends HTMLElement {
|
|
45
|
+
value = ''
|
|
46
|
+
type = 'text'
|
|
47
|
+
placeholder = '请输入内容'
|
|
48
|
+
label = ''
|
|
49
|
+
leftIcon = ''
|
|
50
|
+
rightIcon = ''
|
|
51
|
+
|
|
52
|
+
// Using string boolean values from attributes
|
|
53
|
+
disabled = false
|
|
54
|
+
readonly = false
|
|
55
|
+
clearable = false
|
|
56
|
+
|
|
57
|
+
focusClass = 'hover:border-gray-300 dark:hover:border-gray-600'
|
|
58
|
+
disabledClass = ''
|
|
59
|
+
readonlyClass = ''
|
|
60
|
+
form = null
|
|
61
|
+
name = ''
|
|
62
|
+
|
|
63
|
+
constructor() {
|
|
64
|
+
super()
|
|
65
|
+
this.style.display = 'block'
|
|
66
|
+
this.style.width = '100%'
|
|
67
|
+
|
|
68
|
+
const attr = useAttr(this)
|
|
69
|
+
|
|
70
|
+
this.value = attr.value || ''
|
|
71
|
+
this.type = attr.type || 'text'
|
|
72
|
+
this.placeholder = attr.placeholder || '请输入内容'
|
|
73
|
+
this.label = attr.label || ''
|
|
74
|
+
this.leftIcon = attr['left-icon'] || ''
|
|
75
|
+
this.rightIcon = attr['right-icon'] || ''
|
|
76
|
+
this.name = this.name || attr.name || ''
|
|
77
|
+
|
|
78
|
+
this.disabled = attr.disabled === 'true' || attr.disabled === ''
|
|
79
|
+
this.readonly = attr.readonly === 'true' || attr.readonly === ''
|
|
80
|
+
this.clearable = attr.clearable === 'true' || attr.clearable === ''
|
|
81
|
+
|
|
82
|
+
this._updateClasses()
|
|
83
|
+
|
|
84
|
+
const watch = useWatch(this)
|
|
85
|
+
// Ensure changing disabled/readonly state dynamically also acts
|
|
86
|
+
watch({
|
|
87
|
+
disabled: () => this._updateClasses(),
|
|
88
|
+
readonly: () => this._updateClasses()
|
|
89
|
+
})
|
|
90
|
+
|
|
91
|
+
this.form = this.closest('g-form')
|
|
92
|
+
|
|
93
|
+
this.updateFormValue()
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
updateFormValue() {
|
|
97
|
+
|
|
98
|
+
if (this.form && this.name) {
|
|
99
|
+
|
|
100
|
+
this.form.data[this.name] = this.value
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
_updateClasses() {
|
|
105
|
+
if (this.disabled) {
|
|
106
|
+
this.disabledClass = 'opacity-60 bg-gray-50 dark:bg-gray-900/40 border-gray-100 dark:border-gray-800'
|
|
107
|
+
this.focusClass = ''
|
|
108
|
+
} else if (this.readonly) {
|
|
109
|
+
this.readonlyClass = 'bg-gray-50 dark:bg-gray-900/40'
|
|
110
|
+
this.focusClass = ''
|
|
111
|
+
} else {
|
|
112
|
+
this.disabledClass = ''
|
|
113
|
+
this.readonlyClass = ''
|
|
114
|
+
this.focusClass = 'hover:border-gray-300 dark:hover:border-gray-600 focus-within:border-blue-500 focus-within:ring-2 focus-within:ring-blue-500/20 dark:focus-within:border-blue-400'
|
|
115
|
+
}
|
|
116
|
+
if (this.update) this.update()
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
handleInput(e) {
|
|
120
|
+
this.value = e.target.value
|
|
121
|
+
if (this.update) this.update()
|
|
122
|
+
if (this.oninput) this.oninput(e)
|
|
123
|
+
this.updateFormValue()
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
handleChange(e) {
|
|
127
|
+
if (this.onchange) this.onchange(e)
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
handleFocus(e) {
|
|
131
|
+
if (this.onfocus) this.onfocus(e)
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
handleBlur(e) {
|
|
135
|
+
// Small delay so clear button click can register before focus is lost entirely if we base logic on focus
|
|
136
|
+
if (this.onblur) this.onblur(e)
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
handleKeyDown(e) {
|
|
140
|
+
if (e.key === 'Enter' && this.onenter) {
|
|
141
|
+
this.onenter(e)
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
clear() {
|
|
146
|
+
if (this.disabled || this.readonly) return
|
|
147
|
+
this.value = ''
|
|
148
|
+
const input = this.querySelector('input')
|
|
149
|
+
if (input) {
|
|
150
|
+
input.value = ''
|
|
151
|
+
input.focus()
|
|
152
|
+
// Dispatch input event artificially for watchers
|
|
153
|
+
input.dispatchEvent(new Event('input', { bubbles: true }))
|
|
154
|
+
}
|
|
155
|
+
if (this.update) this.update()
|
|
156
|
+
if (this.onclear) this.onclear()
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
onRightIconClick(e) {
|
|
160
|
+
if (this.onrightclick) this.onrightclick(e)
|
|
161
|
+
}
|
|
162
|
+
}
|