feature: tooltip in the checkbox

feature: add GridColumn.getElement
feature: column property of `contentWrap` and `maxLines`
change: line height from 24px to 18px
fix: adapt enabled after cell changed
fix: wrap issue of filter panel
This commit is contained in:
2024-03-07 12:54:00 +08:00
parent 6fb7c3c769
commit 168cae3ce1
8 changed files with 343 additions and 126 deletions

View File

@ -13,7 +13,7 @@ import { GridColumn, GridInputColumn, GridTextColumn, GridDropdownColumn, GridCh
/**
* @author Tsanie Lily <tsorgy@gmail.com>
* @license MIT
* @version 1.0.1
* @version 1.0.2
*/
const ColumnChangedType = {
@ -234,7 +234,9 @@ let r = lang;
* @property {boolean} [orderable=true] - 列是否允许重排顺序
* @property {boolean} [allcheck=false] - 列为复选框类型时是否在列头增加全选复选框
* @property {boolean} [shrink=false] - 列为收缩列,禁用自动调整大小
* @property {string} [class] - 单元格元素的额外样式类型字符串(仅在重建行元素时读取
* @property {string} [class] - 单元格元素的额外样式类型字符串(仅在重建行元素时设置
* @property {boolean} [contentWrap=false] - 单元格文本是否换行(仅在重建行元素时设置)
* @property {number} [maxLines=0] - 大于 0 时限制显示最大行数
* @property {any} [css] - 单元格css样式对象仅在重建行元素时读取
* @property {any} [totalCss] - 合计行样式(仅在重建合计行元素时读取)
* @property {(any | GridItemObjectCallback)} [style] - 单元格样式(填充行列数据时读取),支持直接返回样式对象或调用函数返回(若赋值则忽略 [styleFilter]{@linkcode GridColumnDefinition#styleFilter}
@ -588,6 +590,12 @@ export class Grid {
* @private
*/
currentSource: null,
/**
* 列可用性的关联字典
* @type {KeyMap<string | boolean>}
* @private
*/
enabledDict: {},
/**
* 合计行数据
* @type {GridRowItem}
@ -825,10 +833,10 @@ export class Grid {
/**
* 文本行高(多行文本列计算高度时使用)
* @type {number}
* @default 24
* @default 18
* @ignore
*/
lineHeight = 24;
lineHeight = 18;
/**
* 列头未过滤时的图标
* @type {string}
@ -1046,7 +1054,7 @@ export class Grid {
* @property {number} [virtualCount=100] - 行数大于等于该值则启用虚模式
* @property {boolean} [autoResize=true] - 未设置宽度的列自动调整列宽
* @property {number} [rowHeight=36] - 表格行高
* @property {number} [lineHeight=24] - 文本行高(多行文本列计算高度时使用)
* @property {number} [lineHeight=18] - 文本行高(多行文本列计算高度时使用)
* @property {string} [filterIcon=ellipsis-h] - 列头未过滤时的图标
* @property {string} [filteredIcon=filter] - 列头已过滤时的图标
* @property {number} [extraRows=0] - 列表底部留出额外的空白行
@ -2062,6 +2070,78 @@ export class Grid {
return array;
}
/**
* 导出已压缩的数据源,结构为
* ```
* {
* columns: [],
* source: [],
* rowHeight: number,
* sortDirection: number,
* sortKey?: string,
* sortArray?: Array<{
* column: string,
* order: "asc" | "desc"
* }>
* }
* ```
* @returns {Promise<Uint8Array>} 返回 `Uint8Array` 数据对象
* @since 1.0.2
*/
export() {
const js = new Blob(['function h(e,t,o){if(null==e)return"";let r;const l={},h={};let s="",n="",p="",a=2,f=3,c=2;const u=[];let i=0,d=0;for(let w=0;w<e.length;w+=1)if(s=e.charAt(w),Object.prototype.hasOwnProperty.call(l,s)||(l[s]=f++,h[s]=!0),n=p+s,Object.prototype.hasOwnProperty.call(l,n))p=n;else{if(Object.prototype.hasOwnProperty.call(h,p)){if(p.charCodeAt(0)<256){for(let e=0;e<c;e++)i<<=1,d==t-1?(d=0,u.push(o(i)),i=0):d++;r=p.charCodeAt(0);for(let e=0;e<8;e++)i=i<<1|1&r,d==t-1?(d=0,u.push(o(i)),i=0):d++,r>>=1}else{r=1;for(let e=0;e<c;e++)i=i<<1|r,d==t-1?(d=0,u.push(o(i)),i=0):d++,r=0;r=p.charCodeAt(0);for(let e=0;e<16;e++)i=i<<1|1&r,d==t-1?(d=0,u.push(o(i)),i=0):d++,r>>=1}0==--a&&(a=Math.pow(2,c),c++),delete h[p]}else{r=l[p];for(let e=0;e<c;e++)i=i<<1|1&r,d==t-1?(d=0,u.push(o(i)),i=0):d++,r>>=1}0==--a&&(a=Math.pow(2,c),c++),l[n]=f++,p=String(s)}if(""!==p){if(Object.prototype.hasOwnProperty.call(h,p)){if(p.charCodeAt(0)<256){for(let e=0;e<c;e++)i<<=1,d==t-1?(d=0,u.push(o(i)),i=0):d++;r=p.charCodeAt(0);for(let e=0;e<8;e++)i=i<<1|1&r,d==t-1?(d=0,u.push(o(i)),i=0):d++,r>>=1}else{r=1;for(let e=0;e<c;e++)i=i<<1|r,d==t-1?(d=0,u.push(o(i)),i=0):d++,r=0;r=p.charCodeAt(0);for(let e=0;e<16;e++)i=i<<1|1&r,d==t-1?(d=0,u.push(o(i)),i=0):d++,r>>=1}0==--a&&(a=Math.pow(2,c),c++),delete h[p]}else{r=l[p];for(let e=0;e<c;e++)i=i<<1|1&r,d==t-1?(d=0,u.push(o(i)),i=0):d++,r>>=1}0==--a&&(a=Math.pow(2,c),c++)}r=2;for(let e=0;e<c;e++)i=i<<1|1&r,d==t-1?(d=0,u.push(o(i)),i=0):d++,r>>=1;let w=!0;do{i<<=1,d==t-1?(u.push(o(i)),w=!1):d++}while(w);return u.join("")}function s(e){return null==e?"":h(e,16,e=>String.fromCharCode(e))}function i(e){const t=s(e),o=new Uint8Array(2*t.length);for(let e=0,r=t.length;e<r;e++){const r=t.charCodeAt(e);o[2*e]=r>>>8,o[2*e+1]=r%256}return o}self.addEventListener("message",function(e){this.self.postMessage(i(e.data))},!1);']);
return new Promise((resolve, reject) => {
let working;
const url = URL.createObjectURL(js);
const worker = new Worker(url);
/**
* @private
* @param {Function} next
* @param {any} data
*/
const terminate = (next, data) => {
working = false;
worker.terminate();
URL.revokeObjectURL(url);
next(data);
}
// 超过 10 秒则拒绝
const timer = setTimeout(() => {
if (working) {
terminate(reject, { message: 'timeout' });
}
}, 10000);
worker.addEventListener('message', e => {
if (working) {
clearTimeout(timer);
terminate(resolve, e.data);
}
})
worker.addEventListener('error', e => {
if (working) {
clearTimeout(timer);
terminate(reject, e);
}
})
working = true;
worker.postMessage(JSON.stringify({
columns: this.columns.map(c => ({
key: c.key,
type: c.type?.toString(),
caption: c.caption,
width: c.width,
align: c.align,
visible: c.visible
})),
source: this.source,
rowHeight: this.rowHeight,
sortDirection: this.sortDirection,
sortKey: this.sortKey,
sortArray: this.sortArray
}));
});
}
/**
* @private
* @callback PrivateGridComparerCallback
@ -2387,10 +2467,13 @@ export class Grid {
if (style !== '') {
cell.style.cssText = style;
}
const element = GridColumn.create(col)
const element = GridColumn.create(col);
if (typeof col.class === 'string') {
GridColumn.setClass(element, col.class);
}
if (col.contentWrap) {
element.classList.add('wrap');
}
cell.appendChild(element);
} else {
cell.style.display = 'none';
@ -2474,6 +2557,9 @@ export class Grid {
if (typeof col.class === 'string') {
type.setClass(element, col.class);
}
if (col.contentWrap) {
element.classList.add('wrap');
}
}
cell.appendChild(element);
if (col.events != null) {
@ -2649,6 +2735,9 @@ export class Grid {
if (typeof col.class === 'string') {
type.setClass(element, col.class);
}
if (col.contentWrap) {
element.classList.add('wrap');
}
cell.replaceChildren(element);
if (col.events != null) {
for (let ev of Object.entries(col.events)) {
@ -2666,6 +2755,7 @@ export class Grid {
delete virtualCell.attrs;
delete virtualCell.style;
delete virtualCell.value;
delete virtualCell.enabled;
}
if (typeof type.setEditing === 'function') {
type.setEditing(element, virtualRow.editing);
@ -2675,15 +2765,17 @@ export class Grid {
virtualCell.value = val;
type.setValue(element, val, vals, col, this);
}
if (virtualRow.editing && typeof type.setEnabled === 'function') {
if (typeof type.setEnabled === 'function') {
let enabled;
if (readonly) {
enabled = false;
} else {
enabled = col.enabled;
if (typeof enabled === 'function') {
this._var.enabledDict[col.key] = true;
enabled = enabled.call(col, item);
} else if (typeof enabled === 'string') {
this._var.enabledDict[col.key] = enabled;
enabled = item[enabled];
}
}
@ -2720,6 +2812,14 @@ export class Grid {
} else if (typeof col.styleFilter === 'function') {
style = col.styleFilter(item);
}
if (col.maxLines > 0) {
const maxHeight = `${col.maxLines * this.lineHeight}px`;
if (style == null) {
style = { 'max-height': maxHeight };
} else {
style['max-height'] = maxHeight;
}
}
const styleText = style != null ? convertCssStyle(style) : '';
if (styleText !== virtualCell.style) {
virtualCell.style = styleText;
@ -3420,9 +3520,11 @@ export class Grid {
_doFillFilterList(content, array, all) {
for (let item of array) {
const div = createElement('div', 'filter-item');
const display = Object.prototype.hasOwnProperty.call(item, 'DisplayValue') ? item.DisplayValue : item;
div.appendChild(createCheckbox({
checked: item.__checked,
label: Object.prototype.hasOwnProperty.call(item, 'DisplayValue') ? item.DisplayValue : item,
label: display && String(display).replace(/(\r\n|\n|<br[ \t]*\/?>)/g, '\u00a0'),
title: display,
onchange: e => {
item.__checked = e.target.checked;
all.querySelector('input').checked = ![...content.querySelectorAll('input')].some(i => !i.checked);
@ -3708,7 +3810,25 @@ export class Grid {
// sub ui-grid
return;
}
const element = target.children[0];
const row = target.dataset.row;
if (this._var.virtualRows[row]?.editing) {
delete holder.dataset.row;
delete holder.dataset.col;
if (holder.classList.contains('active')) {
holder.classList.remove('active');
}
return;
}
const col = target.dataset.col;
if (holder.dataset.row === row &&
holder.dataset.col === col) {
return;
}
const type = this._var.colTypes[this.columns[col]?.key];
let element = target.children[0];
if (type != null && typeof type.getElement === 'function') {
element = type.getElement(element);
}
if (element?.tagName !== 'SPAN') {
if (holder.classList.contains('active')) {
delete holder.dataset.row;
@ -3717,13 +3837,8 @@ export class Grid {
}
return;
}
const row = target.dataset.row;
const col = target.dataset.col;
if (holder.dataset.row === row &&
holder.dataset.col === col) {
return;
}
if (element.scrollWidth > element.offsetWidth) {
if (element.scrollWidth > element.offsetWidth ||
element.scrollHeight > element.offsetHeight) {
holder.dataset.row = row;
holder.dataset.col = col;
holder.innerText = element.innerText;
@ -3738,7 +3853,7 @@ export class Grid {
left = maxleft;
}
const height = target.offsetHeight;
holder.style.cssText = `top: ${top}px; left: ${left}px; max-width: ${this._var.wrapClientWidth}px; height: ${height - 2}px`;
holder.style.cssText = `top: ${top}px; left: ${left}px; max-width: ${this._var.wrapClientWidth}px; min-height: ${height - 2}px`;
holder.classList.add('active');
} else if (holder.classList.contains('active')) {
delete holder.dataset.row;
@ -3852,9 +3967,9 @@ export class Grid {
if (this._var.currentSource == null) {
return;
}
const row = this._var.currentSource[this._var.startIndex + index];
delete row.source;
const item = row.values;
const vals = this._var.currentSource[this._var.startIndex + index];
delete vals.source;
const item = vals.values;
if (item == null) {
return;
}
@ -3873,7 +3988,8 @@ export class Grid {
oldValue ??= val;
item[col.key] = value;
}
const virtualCell = this._var.virtualRows[index]?.cells[col.key];
const virtualRow = this._var.virtualRows[index];
const virtualCell = virtualRow.cells[col.key];
if (virtualCell != null) {
virtualCell.value = value;
}
@ -3886,7 +4002,36 @@ export class Grid {
} else {
setTooltip(cell.children[0], tip, false, this.element);
}
row.__changed = true;
// 调整其他列的可用性
const row = this._tableRows[index];
const offset = this.expandable ? 1 : 0;
this.columns.forEach((c, j) => {
const cache = this._var.enabledDict[c.key];
if (cache !== true && cache !== col.key) {
return;
}
const cell = row.children[j + offset];
if (cell == null) {
return;
}
const type = GridColumnTypeEnum.isCheckbox(c.type) ?
GridCheckboxColumn : this._var.colTypes[c.key] ?? GridColumn;
if (typeof type.setEnabled === 'function') {
if (typeof c.enabled === 'function') {
enabled = c.enabled(item);
} else if (typeof c.enabled === 'string') {
enabled = item[c.enabled];
} else {
return;
}
const vCell = virtualRow.cells[c.key ?? j];
if (enabled !== vCell.enabled) {
vCell.enabled = enabled;
type.setEnabled(cell.children[0], enabled);
}
}
});
vals.__changed = true;
if (typeof col.onChanged === 'function') {
col.onChanged.call(this, item, value, oldValue, e);
}