feat: align with base project's No ESM design and register global Kanban component (by AI)

This commit is contained in:
AI Engineer 2026-06-12 02:03:09 +08:00
parent 66f24fdbe0
commit f6983f7224
15 changed files with 245 additions and 155 deletions

5
.gitignore vendored Normal file
View File

@ -0,0 +1,5 @@
node_modules/
dist/
test-results/
playwright-report/
.DS_Store

1
.npmrc Normal file
View File

@ -0,0 +1 @@
//registry.npmjs.org/:_authToken=${NODE_AUTH_TOKEN}

View File

@ -1,4 +1,9 @@
# @web/kanban 更新日志
# @apigo.cc/kanban 更新日志
## v1.0.2 (2026-06-12)
- **架构对齐**: 遵循 `base` 包新设计,彻底消灭用户侧 ESM 强依赖。
- **组件注册**: 支持 `<Kanban>` 全局组件声明式用法(兼容 `$state` 框架)。
- **构建优化**: 产物调整为纯 UMD 模式,通过 `globalThis.Kanban` 暴露 API。
## v1.0.0 (2026-05-29)
- **Feat**: 初始化看板引擎,支持多列渲染。

View File

@ -1,78 +1,53 @@
# @web/kanban
# @apigo.cc/kanban API 手册
原生 ESM 驱动的极简看板引擎,专注于无状态渲染与高性能拖拽
原生 Web Component 编写的轻量级看板组件。支持响应式数据绑定与自动拖拽排序
## 核心特性
- **零依赖**: 仅使用原生 Web Components 与 Drag & Drop API。
- **双轨分发**: 提供源码版 `kanban.js` 与压缩版 `kanban.min.js`
- **高性能**: 支持数百个看板项的丝滑交互。
---
## 快速上手
## 1. 引入方式 (UMD 优先)
### 1. 引入模块
使用 `loader.js` 或直接通过 `importmap` 引入:
在 HTML 中引入脚本即刻注册 `<web-kanban>` 自定义标签。
```html
<script type="importmap">
{
"imports": {
"@web/kanban": "./dist/kanban.js"
}
}
</script>
<script type="module">
import '@web/kanban';
<script src="https://cdn.jsdelivr.net/npm/@apigo.cc/kanban@1.0.1/dist/kanban.min.js"></script>
<script>
// 直接操作全局 Kanban 类或 DOM 元素
console.log(Kanban);
</script>
```
### 2. 使用组件
---
## 2. 核心用法
### 组件化用法 (推荐)
直接在 HTML 中使用 `<Kanban>` 标签,通过 `state` 进行数据绑定。
```html
<web-kanban id="my-kanban"></web-kanban>
<script type="module">
const kanban = document.querySelector('#my-kanban');
// 注入数据
kanban.data = [
{
id: 'todo',
title: '待处理',
items: [
{ id: '1', content: '任务一' }
<div $data="{
myBoard: [
{ id: 'todo', title: '待办', items: [{ id: '1', content: '任务 1' }] }
]
},
{
id: 'done',
title: '已完成',
items: []
}
];
// 监听更新
kanban.addEventListener('@update', (e) => {
console.log('最新数据:', e.detail);
});
</script>
}">
<Kanban $.state.data="myBoard"></Kanban>
</div>
```
## API 参考
### JS 直接调用
...
---
### 属性 (Properties)
- `data`: `Array<Column>` - 获取或设置看板数据。
## 3. API 参考
### 数据结构 (Types)
```typescript
interface Column {
id: string;
title: string;
items: Item[];
}
### 属性
- **`.data`**: 设置或获取看板数组。
interface Item {
id: string;
content: string;
}
```
### 事件
- **`@update`**: 拖拽结束且位置变动时触发,`detail` 为全量新数据。
### 事件 (Events)
- `@update`: 当看板项位置发生变化时触发。`event.detail` 包含完整的 `Column[]` 数据。
---
## 开发者提示 (AI 必读)
1. **样式隔离**: 组件使用 Shadow DOM外部 CSS 无法影响内部。
2. **全局变量**: UMD 模式下,`Kanban` 类自动挂载到 `window`

View File

@ -1,4 +1,4 @@
# @web/kanban 测试报告
# @apigo.cc/kanban 测试报告
## 基准性能 (Benchmark)
- **环境**: Desktop Chrome (Playwright)

28
dist/kanban.js vendored
View File

@ -1,4 +1,7 @@
var __typeError = (msg) => {
(function(global, factory) {
typeof exports === "object" && typeof module !== "undefined" ? factory(exports) : typeof define === "function" && define.amd ? define(["exports"], factory) : (global = typeof globalThis !== "undefined" ? globalThis : global || self, factory(global.ApigoKanban = {}));
})(this, function(exports2) {
"use strict";var __typeError = (msg) => {
throw TypeError(msg);
};
var __accessCheck = (obj, member, msg) => member.has(obj) || __typeError("Cannot " + msg);
@ -6,6 +9,7 @@ var __privateGet = (obj, member, getter) => (__accessCheck(obj, member, "read fr
var __privateAdd = (obj, member, value) => member.has(obj) ? __typeError("Cannot add the same private member more than once") : member instanceof WeakSet ? member.add(obj) : member.set(obj, value);
var __privateSet = (obj, member, value, setter) => (__accessCheck(obj, member, "write to private field"), setter ? setter.call(obj, value) : member.set(obj, value), value);
var __privateMethod = (obj, member, method) => (__accessCheck(obj, member, "access private method"), method);
var _data, _Kanban_instances, render_fn, initDragAndDrop_fn, moveItem_fn;
class Kanban extends HTMLElement {
constructor() {
@ -140,6 +144,22 @@ moveItem_fn = function(itemId, fromColumnId, toColumnId) {
}));
};
customElements.define("web-kanban", Kanban);
export {
Kanban
};
if (typeof globalThis !== "undefined" && globalThis.Component) {
globalThis.Component.register("Kanban", (container) => {
const kanban = document.createElement("web-kanban");
container.appendChild(kanban);
container.kanbanInstance = kanban;
container.state.__watch("data", (val) => kanban.data = val);
if (container.state.data) kanban.data = container.state.data;
kanban.addEventListener("@update", (e) => {
container.state.data = e.detail;
container.dispatchEvent(new CustomEvent("update", { detail: e.detail }));
});
});
}
if (typeof globalThis !== "undefined") {
globalThis.Kanban = Kanban;
}
exports2.Kanban = Kanban;
Object.defineProperty(exports2, Symbol.toStringTag, { value: "Module" });
});

2
dist/kanban.min.js vendored
View File

@ -1 +1 @@
var n,t,e,a,i,s=n=>{throw TypeError(n)},d=(n,t,e)=>t.has(n)||s("Cannot "+e),r=(n,t,e)=>(d(n,t,"read from private field"),e?e.call(n):t.get(n)),o=(n,t,e)=>t.has(n)?s("Cannot add the same private member more than once"):t instanceof WeakSet?t.add(n):t.set(n,e),l=(n,t,e)=>(d(n,t,"access private method"),e);class c extends HTMLElement{constructor(){super(),o(this,t),o(this,n,[]),this.attachShadow({mode:"open"})}connectedCallback(){l(this,t,e).call(this)}get data(){return r(this,n)}set data(a){((n,t,e,a)=>{d(n,t,"write to private field"),a?a.call(n,e):t.set(n,e)})(this,n,a),l(this,t,e).call(this)}}n=new WeakMap,t=new WeakSet,e=function(){this.shadowRoot.innerHTML=`\n <style>\n :host {\n display: flex;\n gap: 16px;\n padding: 16px;\n overflow-x: auto;\n font-family: system-ui, -apple-system, sans-serif;\n }\n .column {\n background: #f1f2f4;\n border-radius: 8px;\n width: 280px;\n min-width: 280px;\n display: flex;\n flex-direction: column;\n max-height: 100%;\n }\n .column-header {\n padding: 12px;\n font-weight: bold;\n display: flex;\n justify-content: space-between;\n align-items: center;\n }\n .items-container {\n flex: 1;\n padding: 8px;\n min-height: 50px;\n }\n .item {\n background: white;\n border-radius: 4px;\n padding: 12px;\n margin-bottom: 8px;\n box-shadow: 0 1px 3px rgba(0,0,0,0.1);\n cursor: grab;\n }\n .item:active {\n cursor: grabbing;\n }\n .item.dragging {\n opacity: 0.5;\n }\n .items-container.drag-over {\n background: rgba(0,0,0,0.05);\n }\n </style>\n ${r(this,n).map(n=>`\n <div class="column" data-id="${n.id}">\n <div class="column-header">\n <span>${n.title}</span>\n </div>\n <div class="items-container" data-column-id="${n.id}">\n ${(n.items||[]).map(t=>`\n <div class="item" draggable="true" data-id="${t.id}" data-column-id="${n.id}">\n ${t.content}\n </div>\n `).join("")}\n </div>\n </div>\n `).join("")}\n `,l(this,t,a).call(this)},a=function(){const n=this.shadowRoot.querySelectorAll(".item"),e=this.shadowRoot.querySelectorAll(".items-container");n.forEach(n=>{n.addEventListener("dragstart",t=>{n.classList.add("dragging"),t.dataTransfer.setData("text/plain",JSON.stringify({itemId:n.dataset.id,fromColumnId:n.dataset.columnId}))}),n.addEventListener("dragend",()=>{n.classList.remove("dragging")})}),e.forEach(n=>{n.addEventListener("dragover",t=>{t.preventDefault(),n.classList.add("drag-over")}),n.addEventListener("dragleave",()=>{n.classList.remove("drag-over")}),n.addEventListener("drop",e=>{e.preventDefault(),n.classList.remove("drag-over");const a=JSON.parse(e.dataTransfer.getData("text/plain"));l(this,t,i).call(this,a.itemId,a.fromColumnId,n.dataset.columnId)})})},i=function(a,i,s){if(i===s)return;const d=r(this,n).find(n=>n.id===i),o=r(this,n).find(n=>n.id===s),c=d.items.findIndex(n=>n.id===a),[m]=d.items.splice(c,1);o.items.push(m),l(this,t,e).call(this),this.dispatchEvent(new CustomEvent("@update",{detail:r(this,n),bubbles:!0,composed:!0}))},customElements.define("web-kanban",c);export{c as Kanban};
!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?t(exports):"function"==typeof define&&define.amd?define(["exports"],t):t((e="undefined"!=typeof globalThis?globalThis:e||self).ApigoKanban={})}(this,function(e){"use strict";var t,n,a,i,d,s=e=>{throw TypeError(e)},o=(e,t,n)=>t.has(e)||s("Cannot "+n),r=(e,t,n)=>(o(e,t,"read from private field"),n?n.call(e):t.get(e)),l=(e,t,n)=>t.has(e)?s("Cannot add the same private member more than once"):t instanceof WeakSet?t.add(e):t.set(e,n),c=(e,t,n)=>(o(e,t,"access private method"),n);class p extends HTMLElement{constructor(){super(),l(this,n),l(this,t,[]),this.attachShadow({mode:"open"})}connectedCallback(){c(this,n,a).call(this)}get data(){return r(this,t)}set data(e){((e,t,n,a)=>{o(e,t,"write to private field"),a?a.call(e,n):t.set(e,n)})(this,t,e),c(this,n,a).call(this)}}t=new WeakMap,n=new WeakSet,a=function(){this.shadowRoot.innerHTML=`\n <style>\n :host {\n display: flex;\n gap: 16px;\n padding: 16px;\n overflow-x: auto;\n font-family: system-ui, -apple-system, sans-serif;\n }\n .column {\n background: #f1f2f4;\n border-radius: 8px;\n width: 280px;\n min-width: 280px;\n display: flex;\n flex-direction: column;\n max-height: 100%;\n }\n .column-header {\n padding: 12px;\n font-weight: bold;\n display: flex;\n justify-content: space-between;\n align-items: center;\n }\n .items-container {\n flex: 1;\n padding: 8px;\n min-height: 50px;\n }\n .item {\n background: white;\n border-radius: 4px;\n padding: 12px;\n margin-bottom: 8px;\n box-shadow: 0 1px 3px rgba(0,0,0,0.1);\n cursor: grab;\n }\n .item:active {\n cursor: grabbing;\n }\n .item.dragging {\n opacity: 0.5;\n }\n .items-container.drag-over {\n background: rgba(0,0,0,0.05);\n }\n </style>\n ${r(this,t).map(e=>`\n <div class="column" data-id="${e.id}">\n <div class="column-header">\n <span>${e.title}</span>\n </div>\n <div class="items-container" data-column-id="${e.id}">\n ${(e.items||[]).map(t=>`\n <div class="item" draggable="true" data-id="${t.id}" data-column-id="${e.id}">\n ${t.content}\n </div>\n `).join("")}\n </div>\n </div>\n `).join("")}\n `,c(this,n,i).call(this)},i=function(){const e=this.shadowRoot.querySelectorAll(".item"),t=this.shadowRoot.querySelectorAll(".items-container");e.forEach(e=>{e.addEventListener("dragstart",t=>{e.classList.add("dragging"),t.dataTransfer.setData("text/plain",JSON.stringify({itemId:e.dataset.id,fromColumnId:e.dataset.columnId}))}),e.addEventListener("dragend",()=>{e.classList.remove("dragging")})}),t.forEach(e=>{e.addEventListener("dragover",t=>{t.preventDefault(),e.classList.add("drag-over")}),e.addEventListener("dragleave",()=>{e.classList.remove("drag-over")}),e.addEventListener("drop",t=>{t.preventDefault(),e.classList.remove("drag-over");const a=JSON.parse(t.dataTransfer.getData("text/plain"));c(this,n,d).call(this,a.itemId,a.fromColumnId,e.dataset.columnId)})})},d=function(e,i,d){if(i===d)return;const s=r(this,t).find(e=>e.id===i),o=r(this,t).find(e=>e.id===d),l=s.items.findIndex(t=>t.id===e),[p]=s.items.splice(l,1);o.items.push(p),c(this,n,a).call(this),this.dispatchEvent(new CustomEvent("@update",{detail:r(this,t),bubbles:!0,composed:!0}))},customElements.define("web-kanban",p),"undefined"!=typeof globalThis&&globalThis.Component&&globalThis.Component.register("Kanban",e=>{const t=document.createElement("web-kanban");e.appendChild(t),e.kanbanInstance=t,e.state.__watch("data",e=>t.data=e),e.state.data&&(t.data=e.state.data),t.addEventListener("@update",t=>{e.state.data=t.detail,e.dispatchEvent(new CustomEvent("update",{detail:t.detail}))})}),"undefined"!=typeof globalThis&&(globalThis.Kanban=p),e.Kanban=p,Object.defineProperty(e,Symbol.toStringTag,{value:"Module"})});

15
kanbanTODO.md Normal file
View File

@ -0,0 +1,15 @@
# kanban TODO
- [x] 架构对齐Align with `base` project's "No ESM" design.
- [ ] 构建配置Modify `vite.config.js` to output only UMD (`kanban.js` and `kanban.min.js`).
- [ ] 源码重构:
- [ ] 注册 `Kanban` 为全局组件 (`globalThis.Component.register`) 以对齐状态框架。
- [ ] 确保 `Kanban` 类在 `globalThis` 上可用。
- [ ] 测试验证:运行 `npm test` 确保功能无损。
- [ ] 文档更新:
- [ ] 更新 `package.json` 版本号。
- [ ] 更新 `README.md` 以反映组件化用法。
- [ ] 更新 `CHANGELOG.md`
## 当前状态 (v1.0.1)
- 首次渲染耗时: 9.40ms (from TEST.md)

4
package-lock.json generated
View File

@ -1,11 +1,11 @@
{
"name": "@web/kanban",
"name": "@apigo.cc/kanban",
"version": "1.0.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "@web/kanban",
"name": "@apigo.cc/kanban",
"version": "1.0.0",
"devDependencies": {
"@playwright/test": "^1.40.0",

View File

@ -1,9 +1,8 @@
{
"name": "@apigo.cc/kanban",
"version": "1.0.1",
"version": "1.0.2",
"type": "module",
"main": "dist/kanban.js",
"module": "dist/kanban.js",
"files": [
"dist"
],

View File

@ -87,4 +87,4 @@ Error generating stack: `+l.message+`
<div id='root'></div>
</body>
</html>
<template id="playwrightReportBase64">data:application/zip;base64,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</template>
<template id="playwrightReportBase64">data:application/zip;base64,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</template>

48
scripts/publish.js Normal file
View File

@ -0,0 +1,48 @@
import { execSync } from 'child_process';
import fs from 'fs';
import path from 'path';
import { fileURLToPath } from 'url';
const __dirname = path.dirname(fileURLToPath(import.meta.url));
try {
// 1. 获取最新 tag
let tag;
try {
tag = execSync('git describe --tags --abbrev=0', { encoding: 'utf8' }).trim();
} catch (err) {
throw new Error('Failed to find git tags. Please make sure the repository has tags (e.g., v1.0.0) before publishing.');
}
// 去掉 v 前缀
const version = tag.startsWith('v') ? tag.slice(1) : tag;
console.log(`Latest git tag: ${tag}, Version to publish: ${version}`);
// 2. 读取并更新 package.json
const pkgPath = path.join(__dirname, '../package.json');
const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8'));
// 保持原有名称(如果已经带有 @apigo.cc/ 前缀)或替换前缀
if (!pkg.name.startsWith('@apigo.cc/')) {
const baseName = pkg.name.includes('/') ? pkg.name.split('/')[1] : pkg.name;
pkg.name = `@apigo.cc/${baseName}`;
}
pkg.version = version;
fs.writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + '\n');
console.log(`Updated package.json: name=${pkg.name}, version=${pkg.version}`);
// 3. 构建
console.log('Running build...');
execSync('npm run build', { stdio: 'inherit', cwd: path.join(__dirname, '..') });
// 4. 发布
console.log('Publishing to npm...');
const args = process.argv.slice(2).join(' ');
execSync(`npm publish --access public ${args}`, { stdio: 'inherit', cwd: path.join(__dirname, '..') });
console.log('Publish successful!');
} catch (error) {
console.error('Publish failed:', error.message);
process.exit(1);
}

View File

@ -1,5 +1,5 @@
/**
* @web/kanban
* @apigo.cc/kanban
* 原生 ESM 看板引擎
*/
@ -152,3 +152,25 @@ export class Kanban extends HTMLElement {
}
customElements.define('web-kanban', Kanban);
// 注册为全局组件以对齐状态框架
if (typeof globalThis !== 'undefined' && globalThis.Component) {
globalThis.Component.register('Kanban', container => {
const kanban = document.createElement('web-kanban');
container.appendChild(kanban);
container.kanbanInstance = kanban;
container.state.__watch('data', val => kanban.data = val);
if (container.state.data) kanban.data = container.state.data;
kanban.addEventListener('@update', e => {
container.state.data = e.detail;
container.dispatchEvent(new CustomEvent('update', { detail: e.detail }));
});
});
}
// 挂载到全局
if (typeof globalThis !== 'undefined') {
globalThis.Kanban = Kanban;
}

View File

@ -10,7 +10,7 @@
<script type="importmap">
{
"imports": {
"@web/kanban": "../src/index.js"
"@apigo.cc/kanban": "../src/index.js"
}
}
</script>
@ -20,7 +20,7 @@
<web-kanban id="kanban"></web-kanban>
</div>
<script type="module">
import '@web/kanban';
import '@apigo.cc/kanban';
const kanban = document.getElementById('kanban');
kanban.data = [

View File

@ -5,30 +5,30 @@ import { resolve } from 'path';
export default defineConfig({
resolve: {
alias: {
'@web/kanban': resolve(__dirname, 'src/index.js'),
'@apigo.cc/kanban': resolve(__dirname, 'src/index.js'),
},
},
build: {
lib: {
entry: 'src/index.js',
name: 'Kanban',
fileName: (format) => `kanban${format === 'es' ? '' : '.min'}.js`,
formats: ['es'],
name: 'ApigoKanban',
formats: ['umd'],
},
rollupOptions: {
output: [
{
format: 'es',
format: 'umd',
name: 'ApigoKanban',
entryFileNames: 'kanban.js',
plugins: [], // 不压缩
},
{
format: 'es',
format: 'umd',
name: 'ApigoKanban',
entryFileNames: 'kanban.min.js',
plugins: [terser()], // 压缩
},
plugins: [terser()],
}
],
},
minify: false, // 由 rollupOptions 控制
minify: false,
},
});