qiling-web-table 0.0.0-stage → 0.1.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 炼器师
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,33 @@
1
- # Temporary Holding Version
1
+ # qiling-web-table
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ QiLing Web 数据表格拓展包。客户端排序、分页、行选择、展开行与通用拖拽排序。
4
+
5
+ ## 安装
6
+
7
+ ```bash
8
+ npm install qiling-web qiling-web-table
9
+ # 或
10
+ bun add qiling-web qiling-web-table
11
+ ```
12
+
13
+ > `qiling-web-table` 通过 peer dependency 使用 `qiling-web`,请同时安装核心包。
14
+
15
+ ## 使用
16
+
17
+ ```js
18
+ import 'qiling-web'
19
+ import 'qiling-web-table'
20
+
21
+ // 导入即注册:ql-data-table / ql-sortable
22
+ ```
23
+
24
+ 组件标签:`ql-data-table / ql-sortable`。
25
+
26
+ ## 文档
27
+
28
+ - [QiLing Web 文档](https://gitee.com/i-o-i/qiling-web)
29
+ - [源码仓库](https://gitee.com/i-o-i/qiling-web)
30
+
31
+ ## License
32
+
33
+ MIT
package/dist/index.js ADDED
@@ -0,0 +1,72 @@
1
+ import{Registry as T}from"qiling-web/internal";import{QlElement as x}from"qiling-web";class y{static sync(e,t,a){let o=e.querySelector("table");if(!o)return;if(!t){for(let s of e.querySelectorAll("[data-ql-select]"))s.remove();return}let n=o.querySelector("thead tr");if(n&&!n.querySelector("[data-ql-select]")){let s=document.createElement("th");s.dataset.qlSelect="",s.className="ql-table-select";let l=document.createElement("input");l.type="checkbox",l.setAttribute("aria-label","全选"),l.addEventListener("change",()=>this.toggleAll(e,l.checked,a)),s.append(l),n.prepend(s)}[...o.querySelectorAll("tbody tr")].forEach((s,l)=>{if(s.querySelector("[data-ql-select]"))return;let r=document.createElement("td");r.dataset.qlSelect="",r.className="ql-table-select";let i=document.createElement("input");i.type="checkbox",i.setAttribute("aria-label",`选择第 ${l+1} 行`),i.addEventListener("change",()=>this.changed(e,a)),r.append(i),s.prepend(r)}),this.syncAll(e)}static toggleAll(e,t,a){for(let o of e.querySelectorAll("tbody [data-ql-select] input"))o.checked=t;this.changed(e,a)}static selectedKeys(e){return[...e.querySelectorAll("tbody tr")].filter((t)=>t.querySelector("[data-ql-select] input")?.checked).map((t,a)=>t.dataset.key??t.dataset.id??String(a))}static changed(e,t){this.syncAll(e),t(this.selectedKeys(e))}static syncAll(e){let t=[...e.querySelectorAll("tbody [data-ql-select] input")],a=t.filter((n)=>n.checked).length,o=e.querySelector("thead [data-ql-select] input");if(!o)return;o.checked=t.length>0&&a===t.length,o.indeterminate=a>0&&a<t.length}}class u{static sync(e,t,a,o){let n=e.querySelector("table");if(!n)return;if(!t){this.clear(e);return}let s=n.querySelector("thead tr");if(!s)return;if(!s.querySelector("[data-ql-expand]")){let l=document.createElement("th");l.dataset.qlExpand="",l.className="ql-table-expand",l.textContent="";let r=s.querySelector("[data-ql-select]");if(r)r.after(l);else s.prepend(l)}[...n.querySelectorAll("tbody tr")].forEach((l,r)=>{if(l.hasAttribute("data-ql-expanded")||l.querySelector("[data-ql-expand]"))return;let i=document.createElement("td");i.dataset.qlExpand="",i.className="ql-table-expand";let d=document.createElement("button");d.type="button",d.className="ql-table-expand-button",d.setAttribute("aria-label","展开行"),d.setAttribute("aria-expanded","false"),d.textContent="›",d.addEventListener("click",()=>{let c=l.nextElementSibling;if(!c||!c.hasAttribute("data-ql-expanded")){c=document.createElement("tr"),c.dataset.qlExpanded="",c.className="ql-table-expanded",c.hidden=!0;let f=document.createElement("td");f.colSpan=Math.max(1,s.children.length);let v=a?.(l,r);if(v instanceof Node)f.append(v);else f.textContent=v??"";c.append(f),l.after(c)}let b=Boolean(c.hidden);c.hidden=!b,d.setAttribute("aria-expanded",String(b)),d.textContent=b?"⌄":"›",o(l,b)}),i.append(d);let p=l.querySelector("[data-ql-select]");if(p)p.after(i);else l.prepend(i)})}static clear(e){for(let t of e.querySelectorAll("[data-ql-expand]"))t.remove();for(let t of e.querySelectorAll("[data-ql-expanded]"))t.remove()}static detach(e){let t=new Map;for(let a of e.querySelectorAll("[data-ql-expanded]")){let o=a.previousElementSibling;if(!o)continue;t.set(o,a),a.remove()}return t}static restore(e){for(let[t,a]of e)t.after(a)}static closeAll(e){for(let t of e.querySelectorAll("[data-ql-expanded]"))t.hidden=!0;for(let t of e.querySelectorAll(".ql-table-expand-button"))t.setAttribute("aria-expanded","false"),t.textContent="›"}}class m{static cellText(e,t,a=0){return(e.querySelectorAll("td, th")[t+a]?.textContent??"").trim()}static isNumeric(e){return e!==""&&!Number.isNaN(Number(e))}}class E extends x{constructor(){super(...arguments);this.#t=null;this.#e=1;this.#a=null;this.layout={children:[{type:"div",props:{class:"wrap"},children:[{type:"slot",ref:"slot"},{type:"div",ref:"overlay",props:{class:"overlay",hidden:!0}}]},{type:"div",props:{class:"foot"},children:[{type:"span",ref:"info",props:{class:"info"}},{type:"span",ref:"pager",props:{class:"pager"}}]}]};this.actions={refresh:function(){this.actions.syncSelection(),this.actions.syncExpand()},syncOverlay(){let e=this.$(".overlay");if(!e)return;let t=this.getProp("loading"),a=this.getProp("empty");if(this.toggleAttribute("data-loading",t),this.toggleAttribute("data-empty",a),e.hidden=!t&&!a,e.textContent="",t){let o=document.createElement("span");o.className="spinner",o.setAttribute("aria-hidden","true"),e.append(o)}if(t||a){let o=document.createElement("span");o.textContent=t?"加载中…":this.getProp("emptyText"),e.append(o)}},sync(){this.actions.syncSelection(),this.actions.syncExpand(),this.actions.bindHeaders(),this.actions.applyPage()},syncSelection(){y.sync(this,this.getProp("selectable"),(e)=>{this.emit("ql-select",e)})},selectedKeys(){return y.selectedKeys(this)},syncExpand(){u.sync(this,this.getProp("expandable"),this.#a,(e,t)=>this.emit("ql-expand",{key:e.dataset.key??e.dataset.id??"",expanded:t}))},utilityOffset(){return(this.getProp("selectable")?1:0)+(this.getProp("expandable")?1:0)},table(){return this.querySelector("table")},headCells(){let e=this.actions.table()?.querySelector("thead tr");return e?Array.from(e.querySelectorAll("th, td")):[]},body(){return this.actions.table()?.querySelector("tbody")??null},rows(){let e=this.actions.body();return e?Array.from(e.querySelectorAll("tr:not([data-ql-expanded])")):[]},size(){let e=this.getProp("pageSize");return typeof e==="number"&&e>0?Math.floor(e):10},bindHeaders(){let e=this.getProp("sortable"),t=this.actions.utilityOffset();this.actions.headCells().forEach((a,o)=>{if(a.classList.contains("ql-table-select")||a.classList.contains("ql-table-expand"))return;let n=o-t;if(!a.dataset.qlBound)a.dataset.qlBound="1",a.addEventListener("click",()=>this.actions.sortBy(n));let s=this.#t,l=s&&s.index===n?s.direction:null;a.setAttribute("aria-sort",!e||!l?"none":l==="asc"?"ascending":"descending")})},sortBy(e){if(!this.getProp("sortable"))return;let t=this.actions.body();if(!t)return;let a=this.#t,o=a?.index===e&&a.direction==="asc"?"desc":"asc",n=this.actions.utilityOffset(),s=this.actions.rows(),l=s.length>0&&s.every((i)=>m.isNumeric(m.cellText(i,e,n)));s.sort((i,d)=>{let p=m.cellText(i,e,n),c=m.cellText(d,e,n),b=l?Number(p)-Number(c):p.localeCompare(c,"zh-Hans-CN");return o==="asc"?b:-b});let r=u.detach(this);for(let i of s)t.append(i);u.restore(r),this.#t={index:e,direction:o},this.#e=1,this.actions.bindHeaders(),this.actions.applyPage(),this.emit("ql-sort",{column:e,direction:o})},applyPage(){let e=this.actions.rows(),t=e.length,a=this.actions.size(),o=Math.max(1,Math.ceil(t/a));if(this.#e>o)this.#e=o;if(this.getProp("paginated")){let n=(this.#e-1)*a;e.forEach((s,l)=>{s.hidden=l<n||l>=n+a})}else for(let n of e)n.hidden=!1;this.actions.syncFoot(t,o)},syncFoot(e,t){let a=this.$(".foot"),o=this.$(".info"),n=this.$(".pager");if(!a||!o||!n)return;let s=this.getProp("paginated")&&e>0;if(a.hidden=!s,!s)return;o.textContent=`共 ${e} 条 · 第 ${this.#e} / ${t} 页`,n.textContent="";let l=(r,i,d)=>{let p=document.createElement("button");p.type="button",p.className="page",p.textContent=r,p.disabled=d,p.addEventListener("click",()=>{this.#e=i,this.actions.applyPage(),this.emit("ql-page",i)}),n.append(p)};l("‹",this.#e-1,this.#e<=1),l("›",this.#e+1,this.#e>=t)}}}static tag="ql-data-table";static props={sortable:{type:"boolean",default:!1,description:"点击表头排序(客户端)"},paginated:{type:"boolean",default:!1,description:"分页(客户端)"},pageSize:{type:"number",default:10,description:"每页条数"},stickyHeader:{type:"boolean",default:!1,description:"固定表头(配合 maxHeight 使用)"},stickyColumn:{type:"boolean",default:!1,description:"固定第一列"},maxHeight:{type:"length",default:"",description:"最大高度(固定表头时建议设置)"},striped:{type:"boolean",default:!1,description:"斑马纹(偶数行浅底)"},hoverable:{type:"boolean",default:!1,description:"行悬停高亮"},compact:{type:"boolean",default:!1,description:"紧凑行高"},loading:{type:"boolean",default:!1,description:"显示加载遮罩"},empty:{type:"boolean",default:!1,description:"显示空状态并隐藏表格"},emptyText:{type:"string",default:"暂无数据",description:"空状态文案"},selectable:{type:"boolean",default:!1,description:"首列显示行选择框"},expandable:{type:"boolean",default:!1,description:"首列显示展开按钮"}};static emits={"ql-sort":{desc:"排序时触发,detail 为 { column, direction }"},"ql-page":{desc:"翻页时触发,detail 为页码"},"ql-select":{desc:"勾选行变化时触发,detail 为选中行的 key 数组"},"ql-expand":{desc:"展开 / 收起行时触发,detail 为 { key, expanded }"}};static styles=`
2
+ :host { display: block; }
3
+ .foot {
4
+ display: flex;
5
+ align-items: center;
6
+ justify-content: space-between;
7
+ gap: var(--ql-space-3);
8
+ padding: var(--ql-space-2) var(--ql-space-3);
9
+ font-size: var(--ql-font-size-sm);
10
+ color: var(--ql-color-fg-muted);
11
+ border-top: 1px solid var(--ql-color-border);
12
+ }
13
+ .foot[hidden] { display: none; }
14
+ .pager { display: flex; gap: var(--ql-space-1); }
15
+ .page {
16
+ min-width: 28px;
17
+ height: 28px;
18
+ padding: 0 var(--ql-space-2);
19
+ font: inherit;
20
+ font-size: var(--ql-font-size-sm);
21
+ color: var(--ql-color-fg);
22
+ background: var(--ql-color-bg);
23
+ border: 1px solid var(--ql-color-border);
24
+ border-radius: var(--ql-radius-sm);
25
+ cursor: pointer;
26
+ transition: border-color .12s ease, color .12s ease;
27
+ }
28
+ .page:hover:not(:disabled) { color: var(--ql-color-primary); border-color: var(--ql-color-primary); }
29
+ .page:disabled { opacity: .5; cursor: not-allowed; }
30
+ .wrap { position: relative; }
31
+ .overlay {
32
+ position: absolute;
33
+ inset: 0;
34
+ z-index: 2;
35
+ display: flex;
36
+ align-items: center;
37
+ justify-content: center;
38
+ gap: var(--ql-space-2);
39
+ min-height: 100px;
40
+ font-size: var(--ql-font-size-sm);
41
+ color: var(--ql-color-fg-muted);
42
+ background: color-mix(in srgb, var(--ql-color-bg) 82%, transparent);
43
+ }
44
+ .overlay[hidden] { display: none; }
45
+ :host([data-empty]) ::slotted(table) { display: none; }
46
+ .spinner {
47
+ width: 18px;
48
+ height: 18px;
49
+ box-sizing: border-box;
50
+ border: 2px solid var(--ql-color-border-strong);
51
+ border-top-color: var(--ql-color-primary);
52
+ border-radius: 50%;
53
+ animation: ql-data-table-spin .8s linear infinite;
54
+ }
55
+ @keyframes ql-data-table-spin { to { transform: rotate(360deg); } }
56
+ `;#t;#e;#a;set renderExpanded(e){this.#a=e,u.clear(this),this.actions.syncExpand()}get renderExpanded(){return this.#a}onMounted(){this.ref("slot")?.addEventListener("slotchange",()=>this.actions.sync()),this.actions.sync(),this.actions.syncOverlay(),this.actions.syncSelection()}static cssVars={maxHeight:"--ql-data-table-max-height"};static effects={sortable(){this.actions.sync()},paginated(){this.actions.sync()},pageSize(){this.actions.sync()},loading(){this.actions.syncOverlay()},empty(){this.actions.syncOverlay()},emptyText(){this.actions.syncOverlay()},selectable(){this.actions.syncSelection()},expandable(){this.actions.syncExpand()}}}import{Ql as g}from"qiling-web";import{QlElement as q}from"qiling-web";class h extends q{constructor(){super(...arguments);this.layout={type:"slot",ref:"slot"};this.#t=null;this.#e=null;this.actions={syncA11y(){let e=String(this.getProp("label")??"");if(e)this.setAttribute("aria-label",e);else this.removeAttribute("aria-label");if(this.getProp("disabled"))this.setAttribute("aria-disabled","true");else this.removeAttribute("aria-disabled")},items(){return Array.from(this.children).filter((e)=>!e.hasAttribute(h.PLACEHOLDER_FLAG))},itemOf(e){let t=e instanceof HTMLElement?e:null;while(t&&t.parentElement!==this)t=t.parentElement;if(!t||t.hasAttribute(h.PLACEHOLDER_FLAG))return null;return t},keyOf(e,t){return e.dataset.key||e.id||String(t)},keys(){return this.actions.items().map((e,t)=>this.actions.keyOf(e,t))},syncTabIndex(){for(let e of this.actions.items())if(!e.hasAttribute("tabindex"))e.setAttribute("tabindex","0")},startDrag(e,t){let a=this.#t;if(!a)return;let o=a.el.getBoundingClientRect();a.offsetX=e-o.left,a.offsetY=t-o.top;let n=document.createElement("div");n.setAttribute(h.PLACEHOLDER_FLAG,""),n.setAttribute("aria-hidden","true"),n.style.height=`${o.height}px`,a.el.parentElement?.insertBefore(n,a.el),this.#e=n,a.el.setAttribute(h.DRAGGING_FLAG,""),a.el.style.position="fixed",a.el.style.left=`${o.left}px`,a.el.style.top=`${o.top}px`,a.el.style.width=`${o.width}px`,a.el.style.height=`${o.height}px`,a.el.style.margin="0",a.el.style.zIndex="10",a.el.style.pointerEvents="none",a.started=!0},moveDrag(e,t){let a=this.#t;if(!a||!a.started)return;a.el.style.left=`${e-a.offsetX}px`,a.el.style.top=`${t-a.offsetY}px`,this.actions.movePlaceholder(t)},movePlaceholder(e){let t=this.#e;if(!t)return;let a=this.actions.items().find((o)=>{let n=o.getBoundingClientRect();return e<n.top+n.height/2});if(a)this.insertBefore(t,a);else this.append(t)},endDrag(e){let t=this.#t,a=this.#e;if(this.#t=null,this.#e=null,!t)return;if(t.started){if(e)if(t.anchor&&t.anchor.parentNode===this)this.insertBefore(t.el,t.anchor);else this.append(t.el);else if(a?.parentElement)this.insertBefore(t.el,a);if(a?.remove(),t.el.removeAttribute(h.DRAGGING_FLAG),t.originalStyle===null)t.el.removeAttribute("style");else t.el.setAttribute("style",t.originalStyle)}if(e)return;let o=this.actions.keys();if(t.started&&o.join("\x00")!==t.keys.join("\x00"))this.emit("ql-sort",o)},cancelDrag(){if(!this.#t)return;this.actions.endDrag(!0)},swap(e,t){let a=this.actions.items(),o=a.indexOf(e);if(o<0)return!1;let n=o+t;if(n<0||n>=a.length)return!1;if(t<0)this.insertBefore(e,a[n]);else this.insertBefore(e,a[n].nextElementSibling);return!0}};this.events={pointerDown:g.desc("按下某项,记录拖拽起点",function(e){if(this.getProp("disabled"))return;let t=this.actions.itemOf(e.target);if(!t)return;let a=t.getBoundingClientRect();this.#t={el:t,pointerId:e.pointerId,startX:e.clientX,startY:e.clientY,offsetX:e.clientX-a.left,offsetY:e.clientY-a.top,started:!1,keys:this.actions.keys(),anchor:t.nextSibling,originalStyle:t.getAttribute("style")}}),pointerMoved:g.desc("拖拽移动:超过阈值后进入拖拽态并跟随指针",function(e){let t=this.#t;if(!t||e.pointerId!==t.pointerId)return;if(!t.started){if(Math.hypot(e.clientX-t.startX,e.clientY-t.startY)<h.THRESHOLD)return;if(e.preventDefault(),typeof this.setPointerCapture==="function")try{this.setPointerCapture(e.pointerId)}catch{}this.actions.startDrag(e.clientX,e.clientY)}this.actions.moveDrag(e.clientX,e.clientY)}),pointerUp:g.desc("松开指针:落位并派发排序",function(e){let t=this.#t;if(!t||e.pointerId!==t.pointerId)return;if(typeof this.releasePointerCapture==="function")try{this.releasePointerCapture(e.pointerId)}catch{}this.actions.endDrag(!1)}),keyPressed:g.desc("Alt + ↑ / ↓ 与相邻项交换位置",function(e){if(this.getProp("disabled"))return;if(!e.altKey||e.key!=="ArrowUp"&&e.key!=="ArrowDown")return;let t=this.actions.itemOf(e.target);if(!t)return;if(!this.actions.swap(t,e.key==="ArrowUp"?-1:1))return;e.preventDefault(),t.focus(),this.emit("ql-sort",this.actions.keys())})}}static PLACEHOLDER_FLAG="data-ql-sortable-placeholder";static DRAGGING_FLAG="data-ql-sortable-dragging";static THRESHOLD=4;static tag="ql-sortable";static props={disabled:{type:"boolean",default:!1,description:"是否禁用排序"},label:{type:"string",default:"",description:"无障碍名称(为空则不设 aria-label)"}};static emits={"ql-sort":{desc:"排序变化时触发,detail 为排序后的 key 数组"}};static styles=`
57
+ :host { display: block; }
58
+ /* 项与占位都是 light DOM 子元素,经 ::slotted 命中 */
59
+ ::slotted(*) { cursor: grab; }
60
+ ::slotted([${h.DRAGGING_FLAG}]) {
61
+ cursor: grabbing;
62
+ opacity: .9;
63
+ box-shadow: var(--ql-shadow-lg);
64
+ }
65
+ ::slotted([${h.PLACEHOLDER_FLAG}]) {
66
+ box-sizing: border-box;
67
+ background: var(--ql-color-bg-subtle);
68
+ border: 1px dashed var(--ql-color-border-strong);
69
+ border-radius: var(--ql-radius-sm);
70
+ }
71
+ :host([disabled]) ::slotted(*) { cursor: default; }
72
+ `;#t;#e;static effects={disabled(){this.actions.syncA11y()},label(){this.actions.syncA11y()}};onMounted(){this.addEventListener("pointerdown",(e)=>this.events.pointerDown(e)),this.addEventListener("pointermove",(e)=>this.events.pointerMoved(e)),this.addEventListener("pointerup",(e)=>this.events.pointerUp(e)),this.addEventListener("pointercancel",()=>this.actions.cancelDrag()),this.addEventListener("keydown",(e)=>this.events.keyPressed(e)),this.ref("slot")?.addEventListener("slotchange",()=>this.actions.syncTabIndex()),this.actions.syncA11y(),this.actions.syncTabIndex()}onUnmounted(){this.actions.cancelDrag()}}T.define(E);T.define(h);export{E as QlDataTable,h as QlSortable};
@@ -0,0 +1,11 @@
1
+ /** 数据表格展开行:插入展开按钮列、生成详情行并切换显隐。 */
2
+ export declare class DataTableExpand {
3
+ static sync(host: HTMLElement, expandable: boolean, render: ((row: HTMLTableRowElement, index: number) => Node | string | null) | null, notify: (row: HTMLTableRowElement, expanded: boolean) => void): void;
4
+ /** 清空展开列与详情行(渲染函数变化时重建) */
5
+ static clear(host: HTMLElement): void;
6
+ /** 排序前摘下展开行,避免排序移动数据行时丢失对应关系 */
7
+ static detach(host: HTMLElement): Map<HTMLTableRowElement, HTMLTableRowElement>;
8
+ /** 排序后把展开行放回数据行后面 */
9
+ static restore(map: Map<HTMLTableRowElement, HTMLTableRowElement>): void;
10
+ static closeAll(host: HTMLElement): void;
11
+ }
@@ -0,0 +1,11 @@
1
+ /** 数据表格的行选择:插入选择列、同步全选状态、读取选中 key。 */
2
+ export declare class DataTableSelection {
3
+ /** 按 selectable 增删选择列;变化时回调选中 key */
4
+ static sync(host: HTMLElement, selectable: boolean, notify: (keys: string[]) => void): void;
5
+ /** 选中全部 / 全不选 */
6
+ static toggleAll(host: HTMLElement, checked: boolean, notify: (keys: string[]) => void): void;
7
+ /** 当前选中行的 key(data-key → data-id → 序号) */
8
+ static selectedKeys(host: HTMLElement): string[];
9
+ private static changed;
10
+ private static syncAll;
11
+ }
@@ -0,0 +1,5 @@
1
+ /** 数据表格排序的单元格读取与数值判断。 */
2
+ export declare class DataTableSort {
3
+ static cellText(row: HTMLTableRowElement, index: number, offset?: number): string;
4
+ static isNumeric(value: string): boolean;
5
+ }
@@ -0,0 +1,4 @@
1
+ import { QlDataTable } from './ql-data-table';
2
+ import { QlSortable } from './ql-sortable';
3
+ export { QlDataTable, QlSortable };
4
+ export type { DragState, SortState } from './types';
@@ -0,0 +1,67 @@
1
+ import type { EffectMap, GroupMethod } from 'qiling-web';
2
+ import { QlElement } from 'qiling-web';
3
+ import type { LayoutNode, CssVarMap, PropSchema } from 'qiling-web';
4
+ /**
5
+ * 数据表格:在原生 `<table>` 之上提供**客户端**排序与分页。
6
+ *
7
+ * 内容放原生 `<table>`(默认插槽);表格样式走主题全局表(与 `ql-table` 同款)。
8
+ *
9
+ * ⚠️ 排序与分页都在浏览器内完成。数据量大或需要服务端分页时,请改用**受控方式**:
10
+ * 监听 `ql-sort` / `ql-page` 后自行取数、重渲染表格。
11
+ *
12
+ * 事件:`ql-sort`(detail 为 `{ column, direction }`)、`ql-page`(detail 为页码)。
13
+ */
14
+ export declare class QlDataTable extends QlElement {
15
+ #private;
16
+ static tag: string;
17
+ static props: PropSchema;
18
+ sortable: boolean;
19
+ paginated: boolean;
20
+ pageSize: number;
21
+ stickyHeader: boolean;
22
+ stickyColumn: boolean;
23
+ maxHeight: string | number;
24
+ striped: boolean;
25
+ hoverable: boolean;
26
+ compact: boolean;
27
+ loading: boolean;
28
+ empty: boolean;
29
+ emptyText: string;
30
+ selectable: boolean;
31
+ expandable: boolean;
32
+ /** 事件自述(与 props 同源:文档站与 AI 据此生成事件表) */
33
+ static emits: {
34
+ 'ql-sort': {
35
+ desc: string;
36
+ };
37
+ 'ql-page': {
38
+ desc: string;
39
+ };
40
+ 'ql-select': {
41
+ desc: string;
42
+ };
43
+ 'ql-expand': {
44
+ desc: string;
45
+ };
46
+ };
47
+ static styles: string;
48
+ set renderExpanded(value: ((row: HTMLTableRowElement, index: number) => Node | string | null) | null);
49
+ get renderExpanded(): ((row: HTMLTableRowElement, index: number) => Node | string | null) | null;
50
+ /** 内部结构:内容区(原生 table 由使用方给)+ 底部信息与分页 */
51
+ layout: LayoutNode;
52
+ protected onMounted(): void;
53
+ /** `maxHeight` 以 CSS 变量下发 —— 由基类按 `cssVars` 自动绑定 */
54
+ static cssVars: CssVarMap;
55
+ /**
56
+ * 取 light DOM 里的 table。
57
+ * 后续一律用通用选择器访问 thead / tbody / 单元格,**不用 `tHead` / `rows` / `cells`
58
+ * 这些便捷 API** —— 它们并非所有 DOM 实现都提供(happy-dom 即缺失)。
59
+ */
60
+ /** 绑定表头点击与排序指示 */
61
+ /** 按列排序(升序 ↔ 降序交替) */
62
+ /** 应用分页:用 hidden 控制行显示 */
63
+ /** 属性变化 → 重算(挂载时也会执行一次,故动作幂等) */
64
+ static effects: EffectMap;
65
+ /** 3. 操作:业务动作。**不挂 `Ql.desc()` 即内部方法**,不对外暴露 */
66
+ actions: Record<string, GroupMethod>;
67
+ }
@@ -0,0 +1,55 @@
1
+ import type { EffectMap } from 'qiling-web';
2
+ import type { GroupMethod } from 'qiling-web';
3
+ import { QlElement } from 'qiling-web';
4
+ import type { PropSchema } from 'qiling-web';
5
+ /**
6
+ * 拖拽排序:把直接子元素当作可排序项(指针拖拽,或 `Alt` + `↑` / `↓` 键盘移动)。
7
+ *
8
+ * ```html
9
+ * <ql-sortable>
10
+ * <ql-card data-key="a">第一项</ql-card>
11
+ * <ql-card data-key="b">第二项</ql-card>
12
+ * </ql-sortable>
13
+ * ```
14
+ *
15
+ * 排序后的 key 数组由 `ql-sort` 事件回传(`detail`);key 取 `data-key`,
16
+ * 缺失时退化为元素的 `id`,再退化为初始下标。
17
+ *
18
+ * 交互约定:
19
+ * - 按住某项拖动:拖动中该项脱离文档流跟随指针,原位显示**占位**指示落点
20
+ * - 松手:把项落到占位处,顺序变化时派发 `ql-sort`
21
+ * - 键盘:焦点在项上时 `Alt` + `↑` / `↓` 与相邻项交换,焦点跟随,同样派发 `ql-sort`
22
+ *
23
+ * 键盘可达按规范 §4.4⑤「可排序列表项」:与导航列表同族(每项各自一个 Tab 停留点),
24
+ * 方向键不参与,改顺序用 `Alt` + `↑` / `↓`。
25
+ */
26
+ export declare class QlSortable extends QlElement {
27
+ #private;
28
+ private static readonly PLACEHOLDER_FLAG;
29
+ private static readonly DRAGGING_FLAG;
30
+ private static readonly THRESHOLD;
31
+ static tag: string;
32
+ static props: PropSchema;
33
+ disabled: boolean;
34
+ label: string;
35
+ /** 事件自述(与 props 同源:文档站与 AI 据此生成事件表) */
36
+ static emits: {
37
+ 'ql-sort': {
38
+ desc: string;
39
+ };
40
+ };
41
+ static styles: string;
42
+ /** 内部结构:只有一个默认插槽(项由使用方给) */
43
+ layout: {
44
+ type: string;
45
+ ref: string;
46
+ };
47
+ /** 属性变化 → 同步无障碍名称与状态 */
48
+ static effects: EffectMap;
49
+ /** 3. 操作:**不挂 `Ql.desc()` 即内部方法**,不对外暴露 */
50
+ actions: Record<string, GroupMethod>;
51
+ /** 4. 事件:用户交互的处理方法 */
52
+ events: Record<string, GroupMethod>;
53
+ protected onMounted(): void;
54
+ protected onUnmounted(): void;
55
+ }
@@ -0,0 +1,18 @@
1
+ /** 表格排序状态 */
2
+ export interface SortState {
3
+ index: number;
4
+ direction: 'asc' | 'desc';
5
+ }
6
+ /** 拖拽中的一次操作 */
7
+ export interface DragState {
8
+ el: HTMLElement;
9
+ pointerId: number;
10
+ startX: number;
11
+ startY: number;
12
+ offsetX: number;
13
+ offsetY: number;
14
+ started: boolean;
15
+ keys: string[];
16
+ anchor: Node | null;
17
+ originalStyle: string | null;
18
+ }
package/package.json CHANGED
@@ -1,6 +1,57 @@
1
1
  {
2
2
  "name": "qiling-web-table",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "0.1.1",
4
+ "description": "QiLing UI 表格:数据表格(排序 / 分页)与通用拖拽排序",
5
+ "type": "module",
6
+ "license": "MIT",
7
+ "exports": {
8
+ ".": {
9
+ "types": "./dist/types/index.d.ts",
10
+ "default": "./dist/index.js"
11
+ }
12
+ },
13
+ "files": [
14
+ "dist",
15
+ "LICENSE",
16
+ "README.md"
17
+ ],
18
+ "sideEffects": [
19
+ "./dist/index.js"
20
+ ],
21
+ "peerDependencies": {
22
+ "qiling-web": "^0.1.1"
23
+ },
24
+ "devDependencies": {
25
+ "qiling-web": "workspace:*"
26
+ },
27
+ "scripts": {
28
+ "clean": "rm -rf dist",
29
+ "build": "bun run clean && bun run build:ui && bun run build:types",
30
+ "build:ui": "bun build src/index.ts --outdir=dist --target=browser --format=esm --minify --splitting --entry-naming=index.js --chunk-naming=chunk-[hash].js --min-chunk-size=16384 --external qiling-web --external qiling-web/internal",
31
+ "build:types": "tsc -p tsconfig.build.json",
32
+ "typecheck": "tsc --noEmit -p tsconfig.json",
33
+ "prepublishOnly": "bun run build && bun run typecheck"
34
+ },
35
+ "repository": {
36
+ "type": "git",
37
+ "url": "git+https://gitee.com/i-o-i/qiling-web.git",
38
+ "directory": "packages/qiling-web-table"
39
+ },
40
+ "homepage": "https://gitee.com/i-o-i/qiling-web",
41
+ "bugs": {
42
+ "url": "https://gitee.com/i-o-i/qiling-web/issues"
43
+ },
44
+ "keywords": [
45
+ "qiling",
46
+ "qiling-web",
47
+ "web-components",
48
+ "ui",
49
+ "framework"
50
+ ],
51
+ "author": {
52
+ "name": "炼器师"
53
+ },
54
+ "publishConfig": {
55
+ "access": "public"
56
+ }
57
+ }