add: getText
compatibility.
add: `AssetSelector` and `TemplateSelector`. add: `popup-selector` style class. add: `ui.resolvePopup` function. add: `switch` in checkbox. add: `GridColumn.filterTemplate` supports. add: add `action` callback in `createIcon`. change: replace `setTimeout(..., 0)` with `requestAnimationFrame`. change: Popup result structure adjustment ({ result: any, popup: Popup }). change: complete add work order flow. change: reduce Popup title height. fix: Grid column sort in number.
This commit is contained in:
2
lib/ui/checkbox.d.ts
vendored
2
lib/ui/checkbox.d.ts
vendored
@ -10,6 +10,8 @@ interface CheckboxOptions {
|
||||
name?: string;
|
||||
/** 焦点索引 */
|
||||
tabIndex?: Number;
|
||||
/** 是否为 switch 样式 */
|
||||
switch?: boolean;
|
||||
/** 样式分类,可以是 ['`fa-light`', '`fa-regular`', '`fa-solid`'] 其中之一 */
|
||||
type?: string;
|
||||
/** 标签 */
|
||||
|
@ -62,7 +62,7 @@ export function createRadiobox(opts = {}) {
|
||||
}
|
||||
|
||||
export function createCheckbox(opts = {}) {
|
||||
const container = createElement('label', 'ui-check-wrapper',
|
||||
const container = createElement('label', opts.switch ? 'ui-switch' : 'ui-check-wrapper',
|
||||
createElement('input', input => {
|
||||
input.setAttribute('type', 'checkbox');
|
||||
if (opts.checked === true) {
|
||||
@ -86,7 +86,23 @@ export function createCheckbox(opts = {}) {
|
||||
if (opts.enabled === false) {
|
||||
container.classList.add('disabled');
|
||||
}
|
||||
if (opts.checkedNode != null && opts.uncheckedNode != null) {
|
||||
if (opts.switch) {
|
||||
const label = opts.label;
|
||||
if (label instanceof Element) {
|
||||
container.appendChild(label);
|
||||
} else {
|
||||
container.appendChild(
|
||||
createElement('span', span => {
|
||||
if (label != null && String(label).length > 0) {
|
||||
span.innerText = label;
|
||||
}
|
||||
if (opts.title != null) {
|
||||
span.title = opts.title;
|
||||
}
|
||||
})
|
||||
);
|
||||
}
|
||||
} else if (opts.checkedNode != null && opts.uncheckedNode != null) {
|
||||
container.classList.add('ui-check-image-wrapper');
|
||||
let height = opts.imageHeight;
|
||||
if (isNaN(height) || height <= 0) {
|
||||
|
@ -55,6 +55,10 @@
|
||||
user-select: none;
|
||||
cursor: pointer;
|
||||
|
||||
&.disabled {
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
>span:first-of-type {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
@ -101,9 +105,10 @@
|
||||
}
|
||||
|
||||
&:disabled+span:first-of-type {
|
||||
cursor: default;
|
||||
|
||||
&::before {
|
||||
opacity: .5;
|
||||
opacity: .3;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -7,7 +7,8 @@
|
||||
|
||||
@include outborder();
|
||||
|
||||
&.validation-error {
|
||||
&.validation-error,
|
||||
&:invalid {
|
||||
border-color: var(--red-color);
|
||||
|
||||
&:focus,
|
||||
|
@ -30,7 +30,8 @@ $listMaxHeight: 210px;
|
||||
flex: 1 1 auto;
|
||||
cursor: pointer;
|
||||
font-size: var(--font-size);
|
||||
// line-height: $headerHeight;
|
||||
line-height: $headerHeight;
|
||||
min-height: $headerHeight;
|
||||
padding: 0 6px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
|
@ -45,6 +45,7 @@
|
||||
--header-filter-padding: 4px 26px 4px 8px;
|
||||
--spacing-s: 4px;
|
||||
--spacing-cell: 9px 4px 9px 8px;
|
||||
--spacing-drop-cell: 5px 4px 5px 8px;
|
||||
--filter-line-height: 30px;
|
||||
--filter-item-padding: 0 4px;
|
||||
}
|
||||
@ -122,8 +123,10 @@
|
||||
}
|
||||
}
|
||||
|
||||
>.ui-check-wrapper {
|
||||
>.ui-check-wrapper,
|
||||
>.ui-switch {
|
||||
height: 20px;
|
||||
padding: 0 4px 0 0;
|
||||
}
|
||||
|
||||
>svg {
|
||||
@ -398,10 +401,12 @@
|
||||
@include scrollbar();
|
||||
}
|
||||
|
||||
.ui-check-wrapper {
|
||||
.ui-check-wrapper,
|
||||
.ui-switch {
|
||||
display: inline-flex;
|
||||
justify-content: center;
|
||||
height: var(--row-height);
|
||||
padding: 0 8px;
|
||||
|
||||
.ui-check-inner {
|
||||
|
||||
@ -410,6 +415,14 @@
|
||||
transition: none;
|
||||
}
|
||||
}
|
||||
|
||||
>span:first-of-type {
|
||||
|
||||
&:before,
|
||||
&:after {
|
||||
transition: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.ui-drop-span {
|
||||
@ -439,7 +452,7 @@
|
||||
height: 100%;
|
||||
|
||||
>.ui-drop-text {
|
||||
padding: var(--spacing-cell);
|
||||
padding: var(--spacing-drop-cell);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -708,6 +721,7 @@
|
||||
.ui-popup-mask .ui-popup-container .ui-popup-footer {
|
||||
>.ui-sort-layout {
|
||||
flex: 1 1 auto;
|
||||
margin-left: 8px;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -62,7 +62,7 @@ $buttonHeight: 28px;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
padding: 10px 0 6px 12px;
|
||||
padding: 5px 0 3px 12px;
|
||||
}
|
||||
|
||||
>.ui-popup-header-title,
|
||||
@ -76,7 +76,7 @@ $buttonHeight: 28px;
|
||||
|
||||
>.ui-popup-header-icons {
|
||||
flex: 0 0 auto;
|
||||
padding: 10px 12px 6px 0;
|
||||
padding: 5px 12px 3px 0;
|
||||
display: flex;
|
||||
|
||||
>svg {
|
||||
@ -145,8 +145,9 @@ $buttonHeight: 28px;
|
||||
margin: 10px;
|
||||
|
||||
>svg {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
fill: unset;
|
||||
|
||||
+span {
|
||||
padding-left: 16px;
|
||||
@ -204,6 +205,10 @@ $buttonHeight: 28px;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
padding: 4px 10px 16px 2px;
|
||||
}
|
||||
|
||||
.ui-popup-body,
|
||||
.ui-popup-footer {
|
||||
|
||||
.ui-popup-button {
|
||||
margin-left: 12px;
|
||||
@ -212,7 +217,7 @@ $buttonHeight: 28px;
|
||||
line-height: $buttonHeight;
|
||||
color: var(--title-color);
|
||||
border-radius: var(--corner-radius);
|
||||
padding: 4px 16px;
|
||||
padding: 1px 16px;
|
||||
box-sizing: border-box;
|
||||
min-width: 70px;
|
||||
text-align: center;
|
||||
@ -227,6 +232,11 @@ $buttonHeight: 28px;
|
||||
}
|
||||
|
||||
@include outline();
|
||||
|
||||
&:disabled {
|
||||
opacity: .4;
|
||||
cursor: default;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -38,7 +38,7 @@
|
||||
--border-radius: 2px;
|
||||
--text-indent: 4px;
|
||||
--line-height: 18px;
|
||||
--settings-line-height: 28px;
|
||||
--settings-line-height: 32px;
|
||||
|
||||
--font-size: .8125rem; // 13px
|
||||
--font-smaller-size: .75rem; // 12px
|
||||
|
4
lib/ui/date.d.ts
vendored
4
lib/ui/date.d.ts
vendored
@ -87,6 +87,10 @@ export class DateSelector {
|
||||
maxDate?: string,
|
||||
/** 是否启用 */
|
||||
enabled?: boolean,
|
||||
/** 焦点索引 */
|
||||
tabIndex?: number,
|
||||
/** 类名 */
|
||||
className?: string,
|
||||
/**
|
||||
* 自定义格式化函数,用于获取日期值时调用
|
||||
* @param date 日期值
|
||||
|
@ -1,4 +1,5 @@
|
||||
import { createElement } from "../functions";
|
||||
import { isPositive, nullOrEmpty } from "../utility";
|
||||
|
||||
/**
|
||||
* 创建或转换日期选择框
|
||||
@ -344,6 +345,16 @@ export class DateSelector {
|
||||
* @private
|
||||
*/
|
||||
maxDate: null,
|
||||
/**
|
||||
* @type {number?}
|
||||
* @private
|
||||
*/
|
||||
tabIndex: null,
|
||||
/**
|
||||
* @type {string?}
|
||||
* @private
|
||||
*/
|
||||
className: null,
|
||||
/**
|
||||
* @type {DateFormatterCallback?}
|
||||
* @private
|
||||
@ -399,6 +410,10 @@ export class DateSelector {
|
||||
create(element) {
|
||||
const opts = this._var.options;
|
||||
const el = createDateInput(opts.minDate, opts.maxDate, element);
|
||||
isPositive(opts.tabIndex) && el.setAttribute('tabindex', opts.tabIndex);
|
||||
if (!nullOrEmpty(opts.className)) {
|
||||
el.classList.add(opts.className);
|
||||
}
|
||||
if (element == null) {
|
||||
el.disabled = opts.enabled === false;
|
||||
}
|
||||
|
2
lib/ui/dropdown.d.ts
vendored
2
lib/ui/dropdown.d.ts
vendored
@ -16,6 +16,8 @@ export interface DropdownOptions {
|
||||
valueKey?: string;
|
||||
/** 源码显示的关键字,默认值 `html` */
|
||||
htmlKey?: string;
|
||||
/** 源码显示的模板函数 */
|
||||
htmlTemplate?: (item: DropdownItem) => HTMLElement;
|
||||
/** 最大输入长度,默认值 `500` */
|
||||
maxLength?: number;
|
||||
/** 是否允许多选 */
|
||||
|
@ -130,7 +130,6 @@ export class Dropdown {
|
||||
onCollapsed;
|
||||
|
||||
constructor(options = {}) {
|
||||
options.searchPlaceholder ??= r('searchHolder', 'Search...');
|
||||
options.textKey ??= 'text';
|
||||
options.valueKey ??= 'value';
|
||||
options.htmlKey ??= 'html';
|
||||
@ -139,7 +138,10 @@ export class Dropdown {
|
||||
const getText = options.getText;
|
||||
if (typeof getText === 'function') {
|
||||
r = getText;
|
||||
} else if (typeof GetTextByKey === 'function') {
|
||||
r = GetTextByKey;
|
||||
}
|
||||
options.searchPlaceholder ??= r('searchHolder', 'Search...');
|
||||
}
|
||||
|
||||
create() {
|
||||
|
@ -44,6 +44,14 @@ export class GridColumn {
|
||||
* @type {boolean}
|
||||
*/
|
||||
|
||||
/**
|
||||
* 标记该类型是否支持列头批量操作
|
||||
* @member
|
||||
* @name GridColumn.headerEditing
|
||||
* @readonly
|
||||
* @type {boolean}
|
||||
*/
|
||||
|
||||
/**
|
||||
* 创建显示单元格时调用的方法
|
||||
* @param {GridColumnDefinition} col - 列定义对象
|
||||
@ -579,10 +587,12 @@ export class GridCheckboxColumn extends GridColumn {
|
||||
/**
|
||||
* @ignore
|
||||
* @param {Function} trigger
|
||||
* @param {GridColumnDefinition} col
|
||||
* @returns {HTMLElement}
|
||||
*/
|
||||
static createEdit(trigger) {
|
||||
static createEdit(trigger, col) {
|
||||
const check = createCheckbox({
|
||||
switch: col.switch,
|
||||
onchange: trigger
|
||||
});
|
||||
return check;
|
||||
|
@ -9,11 +9,12 @@ import { setTooltip } from "../tooltip";
|
||||
import { Popup, showAlert, showConfirm } from "../popup";
|
||||
import { convertCssStyle } from "../extension";
|
||||
import { GridColumn, GridInputColumn, GridTextColumn, GridDropdownColumn, GridCheckboxColumn, GridRadioboxColumn, GridIconColumn, GridDateColumn } from "./column";
|
||||
import { requestAnimationFrame } from '../../ui';
|
||||
|
||||
/**
|
||||
* @author Tsanie Lily <tsorgy@gmail.com>
|
||||
* @license MIT
|
||||
* @version 1.0.3
|
||||
* @version 1.0.6
|
||||
*/
|
||||
|
||||
const ScriptPath = (self.document == null ? self.location.href : self.document.currentScript?.src ?? '').replace(/ui\.min\.js\?.+$/, '');
|
||||
@ -147,6 +148,14 @@ let r = lang;
|
||||
* @this GridColumnDefinition
|
||||
*/
|
||||
|
||||
/**
|
||||
* 行数据过滤项模板回调函数
|
||||
* @callback GridItemHtmlCallback
|
||||
* @param {ValueItem} item - 行数据对象
|
||||
* @returns {HTMLElement} 返回过滤项元素对象
|
||||
* @this GridColumnDefinition
|
||||
*/
|
||||
|
||||
/**
|
||||
* 行数据字符串回调函数
|
||||
* @callback GridItemStringCallback
|
||||
@ -250,13 +259,15 @@ let r = lang;
|
||||
* @property {GridItemObjectCallback} [styleFilter] - **已过时**<br/>_根据返回值填充单元格样式(填充行列数据时读取)_
|
||||
* @property {(string | GridItemStringCallback)} [background] - 设置单元格背景色(填充行列数据时读取),支持直接设置颜色字符串或调用函数返回(若赋值则忽略 [bgFilter]{@linkcode GridColumnDefinition#bgFilter})
|
||||
* @property {GridItemStringCallback} [bgFilter] - **已过时**<br/>_根据返回值设置单元格背景色_
|
||||
* @property {KeyMap<Function>} [events] - 给单元格元素附加事件(事件函数上下文为数据行对象)
|
||||
* @property {boolean} [switch=false] - 复选框为 `ui-switch` 样式 `@since 1.0.6`
|
||||
* @property {(any | GridItemObjectCallback)} [attrs] - 根据返回值设置单元格元素的附加属性,允许直接设置对象也支持调用函数返回对象
|
||||
* @property {KeyMap<Function>} [events] - 给单元格元素附加事件(事件函数上下文为数据行对象)
|
||||
* @property {boolean} [allowFilter=false] - 是否允许进行列头过滤
|
||||
* @property {any[]} [filterValues] - 过滤值的数组
|
||||
* @property {boolean} [filterAllowNull=false] - 是否区分 `null` 与空字符串
|
||||
* @property {(ValueItem[] | GridColumnFilterSourceCallback)} [filterSource] - 自定义列过滤器的数据源,支持调用函数返回数据源
|
||||
* @property {boolean} [filterAsValue=false] - 列头过滤强制使用 `Value` 字段
|
||||
* @property {GridItemHtmlCallback} [filterTemplate] - 列头过滤项的模板函数
|
||||
* @property {GridItemSortCallback} [sortFilter] - 自定义列排序函数
|
||||
* @property {boolean} [sortAsText=false] - 按照 `DisplayValue` 排序
|
||||
* @property {DropdownOptions} [dropOptions] - 列为下拉列表类型时以该值设置下拉框的参数
|
||||
@ -403,7 +414,7 @@ let r = lang;
|
||||
/**
|
||||
* 判断列是否始终编辑的回调函数
|
||||
* @callback ColumnTypesEnumIsAlwaysEditing
|
||||
* @param {number} type - 列类型
|
||||
* @param {number | GridColumn} type - 列类型
|
||||
* @returns {boolean} 返回是否始终编辑
|
||||
*/
|
||||
|
||||
@ -432,7 +443,9 @@ const GridColumnTypeEnum = {
|
||||
* 判断列是否为复选框列
|
||||
* @type {ColumnTypesEnumIsAlwaysEditing}
|
||||
*/
|
||||
isAlwaysEditing(type) { return type === 3 || type === 7 }
|
||||
isAlwaysEditing(type) {
|
||||
return type?.headerEditing || type === 3 || type === 7;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
@ -1111,7 +1124,7 @@ export class Grid {
|
||||
* @property {GridLanguages} [langs] - 多语言资源对象
|
||||
* @property {number} [virtualCount=100] - 行数大于等于该值则启用虚模式
|
||||
* @property {boolean} [autoResize=true] - 未设置宽度的列自动调整列宽
|
||||
* @property {number} [rowHeight=36] - 表格行高
|
||||
* @property {number} [rowHeight=36] - 表格行高,修改后同时需要在 `.ui-grid` 所在父容器重写 `--line-height` 的值以配合显示
|
||||
* @property {number} [lineHeight=18] - 文本行高(多行文本列计算高度时使用)
|
||||
* @property {string} [filterIcon=ellipsis-h] - 列头未过滤时的图标
|
||||
* @property {string} [filteredIcon=filter] - 列头已过滤时的图标
|
||||
@ -2030,13 +2043,15 @@ export class Grid {
|
||||
}
|
||||
}
|
||||
source ??= [{ column: '', order: 'asc' }];
|
||||
pop.create();
|
||||
pop.rect = { width: 600, height: 460 };
|
||||
pop.show(this._var.el).then(() => {
|
||||
pop.container.style.cssText += 'width: 600px; height: 460px';
|
||||
if (layout) {
|
||||
const footer = pop.container.querySelector('.ui-popup-footer');
|
||||
footer.insertBefore(createCheckbox({
|
||||
label: this.langs.updateLayout,
|
||||
className: 'ui-sort-layout',
|
||||
switch: true,
|
||||
enabled: false
|
||||
}), footer.children[0]);
|
||||
}
|
||||
@ -2462,9 +2477,9 @@ export class Grid {
|
||||
direction = 1;
|
||||
}
|
||||
const editing = col.sortAsText !== true;
|
||||
return (a, b) => {
|
||||
a = this._getItemSortProp(a.values, editing, col);
|
||||
b = this._getItemSortProp(b.values, editing, col);
|
||||
const comparer = (a, b) => {
|
||||
a = this._getItemSortProp(a, editing, col);
|
||||
b = this._getItemSortProp(b, editing, col);
|
||||
if (editing) {
|
||||
if (typeof a === 'boolean') {
|
||||
a = a ? 2 : 1;
|
||||
@ -2473,26 +2488,30 @@ export class Grid {
|
||||
b = b ? 2 : 1;
|
||||
}
|
||||
if (a == null && typeof b === 'number') {
|
||||
a = 0;
|
||||
return (b >= 0 ? -1 : 1);
|
||||
} else if (typeof a === 'number' && b == null) {
|
||||
b = 0;
|
||||
return (a >= 0 ? 1 : -1);
|
||||
} else if (a == null && b != null) {
|
||||
return -1;
|
||||
} else if (a != null && b == null) {
|
||||
return direction;
|
||||
} else {
|
||||
if (Array.isArray(a)) {
|
||||
a = a.join(', ');
|
||||
}
|
||||
if (Array.isArray(b)) {
|
||||
b = b.join(', ');
|
||||
}
|
||||
if (typeof a === 'string' && typeof b === 'string') {
|
||||
a = a.toLowerCase();
|
||||
b = b.toLowerCase();
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
if (Array.isArray(a)) {
|
||||
a = a.join(', ');
|
||||
}
|
||||
if (Array.isArray(b)) {
|
||||
b = b.join(', ');
|
||||
}
|
||||
if (typeof a === 'string' && typeof b === 'string') {
|
||||
a = a.toLowerCase();
|
||||
b = b.toLowerCase();
|
||||
}
|
||||
} else {
|
||||
if (a == null && b != null) {
|
||||
return -1;
|
||||
}
|
||||
if (a != null && b == null) {
|
||||
return direction;
|
||||
return 1;
|
||||
}
|
||||
if (Array.isArray(a)) {
|
||||
a = a.join(', ');
|
||||
@ -2505,8 +2524,9 @@ export class Grid {
|
||||
b = b.toLowerCase();
|
||||
}
|
||||
}
|
||||
return a === b ? 0 : (a > b ? 1 : -1) * direction;
|
||||
return a === b ? 0 : (a > b ? 1 : -1);
|
||||
};
|
||||
return (a, b) => comparer(a.values, b.values) * direction;
|
||||
}
|
||||
return (a, b) => col.sortFilter(a.values, b.values) * direction;
|
||||
}
|
||||
@ -2670,6 +2690,7 @@ export class Grid {
|
||||
th.appendChild(wrapper);
|
||||
if (!readonly && col.enabled !== false && col.allcheck && alwaysEditing) {
|
||||
const check = createCheckbox({
|
||||
switch: col.switch,
|
||||
onchange: e => this._onColumnAllChecked(col, e.target.checked)
|
||||
});
|
||||
wrapper.appendChild(check);
|
||||
@ -3625,11 +3646,14 @@ export class Grid {
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @param {string} tagName
|
||||
* @param {HTMLElement} e
|
||||
* @returns {boolean}
|
||||
*/
|
||||
_notHeader(tagName) {
|
||||
return /^(input|label|layer|svg|use)$/i.test(tagName);
|
||||
_notHeader(e) {
|
||||
if (e.parentElement.classList.contains('ui-switch')) {
|
||||
return true;
|
||||
}
|
||||
return /^(input|label|layer|svg|use)$/i.test(e.tagName);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -3642,7 +3666,7 @@ export class Grid {
|
||||
if (!force && (this._get(col.key, 'resizing') || this._get(col.key, 'dragging'))) {
|
||||
return;
|
||||
}
|
||||
if (!this._notHeader(e.target.tagName)) {
|
||||
if (!this._notHeader(e.target)) {
|
||||
if (Array.isArray(this.sortArray) && this.sortArray.length > 0) {
|
||||
showConfirm(this.langs.sort, this.langs.sortArrayExists, [
|
||||
{
|
||||
@ -3652,8 +3676,8 @@ export class Grid {
|
||||
{
|
||||
text: this.langs.cancel
|
||||
}
|
||||
]).then(result => {
|
||||
if (result?.key === 'yes') {
|
||||
]).then(r => {
|
||||
if (r.result === 'yes') {
|
||||
const sortCol = this.sortArray.find(c => c.column === col.key);
|
||||
this.sortDirection = sortCol?.order === 'asc' ? -1 : 1;
|
||||
this._onDoHeaderSort(col);
|
||||
@ -3790,22 +3814,27 @@ export class Grid {
|
||||
if (!Object.hasOwnProperty.call(dict, display)) {
|
||||
dict[display] = {
|
||||
Value: vals[i],
|
||||
DisplayValue: display
|
||||
DisplayValue: display,
|
||||
RowItem: item.values
|
||||
};
|
||||
}
|
||||
});
|
||||
} else if (!Object.hasOwnProperty.call(dict, displayValue)) {
|
||||
dict[displayValue] = {
|
||||
Value: this._getItemProp(item.values, true, col),
|
||||
DisplayValue: displayValue
|
||||
DisplayValue: displayValue,
|
||||
RowItem: item.values
|
||||
};
|
||||
}
|
||||
}
|
||||
const filterAsValue = col.filterAsValue;
|
||||
const type = this._var.colTypes[col.key];
|
||||
const isDateColumn = type === GridDateColumn || type instanceof GridDateColumn;
|
||||
array = Object.values(dict)
|
||||
.sort((itemA, itemB) => {
|
||||
array = Object.values(dict);
|
||||
if (typeof col.sortFilter === 'function') {
|
||||
array.sort((a, b) => col.sortFilter(a.RowItem, b.RowItem));
|
||||
} else {
|
||||
const type = this._var.colTypes[col.key];
|
||||
const isDateColumn = type === GridDateColumn || type instanceof GridDateColumn;
|
||||
const filterAsValue = col.filterAsValue;
|
||||
array.sort((itemA, itemB) => {
|
||||
let a = itemA.Value;
|
||||
let b = itemB.Value;
|
||||
if (a instanceof Date || b instanceof Date) {
|
||||
@ -3828,6 +3857,7 @@ export class Grid {
|
||||
}
|
||||
return a > b ? 1 : (a < b ? -1 : 0);
|
||||
});
|
||||
}
|
||||
}
|
||||
array = array.map(i => {
|
||||
if (Object.prototype.hasOwnProperty.call(i, 'Value') &&
|
||||
@ -3907,7 +3937,7 @@ export class Grid {
|
||||
panel.appendChild(functions);
|
||||
|
||||
this._var.el.appendChild(panel);
|
||||
setTimeout(() => panel.classList.add('active'), 0);
|
||||
requestAnimationFrame(() => panel.classList.add('active'));
|
||||
this._var.colAttrs.__filtering = filter;
|
||||
filter.classList.add('hover');
|
||||
}
|
||||
@ -3943,24 +3973,32 @@ export class Grid {
|
||||
if (array.length > 12) {
|
||||
array = array.slice(0, 12);
|
||||
}
|
||||
this._doFillFilterList(content, array, all);
|
||||
this._doFillFilterList(col, content, array, all);
|
||||
list.append(holder, content);
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @param {GridColumnDefinition} col
|
||||
* @param {HTMLDivElement} content
|
||||
* @param {ValueItem[]} array
|
||||
* @param {HTMLDivElement} all
|
||||
*/
|
||||
_doFillFilterList(content, array, all) {
|
||||
_doFillFilterList(col, 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;
|
||||
const title = Object.prototype.hasOwnProperty.call(item, 'DisplayValue') ? item.DisplayValue : item;
|
||||
let display;
|
||||
if (typeof col.filterTemplate === 'function') {
|
||||
display = col.filterTemplate(item);
|
||||
}
|
||||
if (display == null) {
|
||||
display = title && String(title).replace(/( |\r\n|\n|<br[ \t]*\/?>)/g, '\u00a0');
|
||||
}
|
||||
div.appendChild(createCheckbox({
|
||||
checked: item.__checked,
|
||||
label: display && String(display).replace(/( |\r\n|\n|<br[ \t]*\/?>)/g, '\u00a0'),
|
||||
title: display,
|
||||
label: display,
|
||||
title,
|
||||
onchange: e => {
|
||||
item.__checked = e.target.checked;
|
||||
all.querySelector('input').checked = ![...content.querySelectorAll('input')].some(i => !i.checked);
|
||||
@ -4001,7 +4039,7 @@ export class Grid {
|
||||
}
|
||||
const content = list.querySelector('.filter-content');
|
||||
content.replaceChildren();
|
||||
this._doFillFilterList(content, array, list.querySelector('.filter-all'));
|
||||
this._doFillFilterList(col, content, array, list.querySelector('.filter-all'));
|
||||
content.style.top = `${top + rowHeight}px`;
|
||||
}
|
||||
}
|
||||
@ -4012,7 +4050,7 @@ export class Grid {
|
||||
* @param {GridColumnDefinition} col
|
||||
*/
|
||||
_onDragStart(e, col) {
|
||||
if (this._notHeader(e.target.tagName)) {
|
||||
if (this._notHeader(e.target)) {
|
||||
return;
|
||||
}
|
||||
if (e.currentTarget.classList.contains('sticky')) {
|
||||
@ -4066,7 +4104,7 @@ export class Grid {
|
||||
if (attr.offset == null) {
|
||||
delete attr.dragging;
|
||||
} else {
|
||||
setTimeout(() => {
|
||||
requestAnimationFrame(() => {
|
||||
delete attr.dragging;
|
||||
delete attr.offset;
|
||||
});
|
||||
@ -4116,7 +4154,7 @@ export class Grid {
|
||||
clearEvents(attr);
|
||||
const width = attr.resizing;
|
||||
if (width != null) {
|
||||
setTimeout(() => delete attr.resizing);
|
||||
requestAnimationFrame(() => delete attr.resizing);
|
||||
if (attr.sizing) {
|
||||
delete attr.sizing;
|
||||
delete attr.autoResize;
|
||||
|
3
lib/ui/icon.d.ts
vendored
3
lib/ui/icon.d.ts
vendored
@ -3,8 +3,9 @@
|
||||
* @param type 样式分类,可以是 ['`fa-light`', '`fa-regular`', '`fa-solid`'] 其中之一
|
||||
* @param id 图标 id
|
||||
* @param style 样式表对象
|
||||
* @param action 点击回调事件
|
||||
*/
|
||||
export function createIcon(type: string, id: string, style?: { [key: string]: string }): SVGSVGElement
|
||||
export function createIcon(type: string, id: string, style?: { [key: string]: string } | ((icon: SVGSVGElement) => { [key: string]: string }), action?: (this: SVGSVGElement, ev: MouseEvent) => any): SVGSVGElement
|
||||
/**
|
||||
* 修改矢量图标
|
||||
* @param svg 矢量图标元素
|
||||
|
@ -24,15 +24,20 @@ export function changeIcon(svg, type, id) {
|
||||
return svg;
|
||||
}
|
||||
|
||||
export function createIcon(type, id, style) {
|
||||
export function createIcon(type, id, style, action) {
|
||||
const svg = document.createElementNS(svgns, 'svg');
|
||||
svg.classList.add('ui-icon');
|
||||
svg.appendChild(createUse(type, id));
|
||||
if (style != null) {
|
||||
if (typeof style === 'function') {
|
||||
style(svg);
|
||||
} else if (style != null) {
|
||||
for (let css of Object.entries(style)) {
|
||||
svg.style.setProperty(css[0], css[1]);
|
||||
}
|
||||
}
|
||||
if (typeof action === 'function') {
|
||||
svg.addEventListener('click', action);
|
||||
}
|
||||
return svg;
|
||||
}
|
||||
|
||||
|
21
lib/ui/popup.d.ts
vendored
21
lib/ui/popup.d.ts
vendored
@ -7,6 +7,8 @@ interface PopupOptions {
|
||||
/** 弹出框标题,可以是文本或者 html 元素 */
|
||||
title: string | HTMLElement;
|
||||
|
||||
/** 是否持久化显示 */
|
||||
persistent?: boolean;
|
||||
/** 是否包含遮罩层,默认为 `true` */
|
||||
mask?: boolean;
|
||||
/** 遮罩层 z-index */
|
||||
@ -66,6 +68,21 @@ interface PopupOptions {
|
||||
|
||||
export class Popup {
|
||||
constructor(opts?: PopupOptions);
|
||||
|
||||
get container(): HTMLElement;
|
||||
|
||||
get title(): string;
|
||||
set title(title: string);
|
||||
|
||||
get loading(): boolean;
|
||||
set loading(flag: boolean);
|
||||
|
||||
get rect(): { collapsed: boolean, left: number, top: number, width: number, height: number };
|
||||
set rect(r: { collapsed?: boolean, left?: number, top?: number, width?: number, height?: number });
|
||||
|
||||
close(result?: any, animation?: boolean): void;
|
||||
create(): HTMLDivElement;
|
||||
show(parent?: HTMLElement, hidden?: boolean): Promise<HTMLElement> | undefined;
|
||||
}
|
||||
|
||||
interface PopupButton {
|
||||
@ -86,12 +103,14 @@ interface PopupIconTypes {
|
||||
}
|
||||
|
||||
interface PopupButtonResult {
|
||||
key: string;
|
||||
result: string;
|
||||
popup: Popup;
|
||||
}
|
||||
|
||||
export function createPopup(title: string | HTMLElement, content: string | HTMLElement, ...buttons: PopupButton[]): Popup
|
||||
|
||||
export function resolvePopup(wrapper: string | HTMLElement, callback?: Function, removable?: boolean, zIndex?: number): Popup
|
||||
|
||||
export function showAlert(title: string | HTMLElement, message: string, iconType?: keyof PopupIconTypes, parent?: HTMLElement): Promise<void>
|
||||
|
||||
export function showConfirm(title: string | HTMLElement, content: string | HTMLElement, buttons: PopupButton[], iconType?: keyof PopupIconTypes, parent?: HTMLElement): Promise<PopupButtonResult>
|
183
lib/ui/popup.js
183
lib/ui/popup.js
@ -1,9 +1,10 @@
|
||||
import "./css/popup.scss";
|
||||
import { r } from "../utility/lgres";
|
||||
import { r as lang } from "../utility/lgres";
|
||||
import { nullOrEmpty } from "../utility/strings";
|
||||
import { global } from "../utility";
|
||||
import { createElement } from "../functions";
|
||||
import { createIcon, changeIcon } from "./icon";
|
||||
import { requestAnimationFrame } from "../ui";
|
||||
|
||||
const ResizeMods = {
|
||||
right: 1,
|
||||
@ -51,6 +52,30 @@ export class Popup {
|
||||
|
||||
get container() { return this._var.mask.querySelector('.ui-popup-container') }
|
||||
|
||||
get title() { return this._var.option.title }
|
||||
set title(title) {
|
||||
const element = this._var.mask?.querySelector('.ui-popup-container .ui-popup-header .ui-popup-header-title');
|
||||
if (element != null) {
|
||||
element.innerText = title;
|
||||
}
|
||||
this._var.option.title = title;
|
||||
}
|
||||
|
||||
get loading() { return this._var.mask?.querySelector('.ui-popup-body>.ui-popup-loading')?.style?.visibility === 'visible' }
|
||||
set loading(flag) {
|
||||
let loading = this._var.mask?.querySelector('.ui-popup-body>.ui-popup-loading');
|
||||
if (loading == null) {
|
||||
return;
|
||||
}
|
||||
if (flag === false) {
|
||||
loading.style.visibility = 'hidden';
|
||||
loading.style.opacity = 0;
|
||||
} else {
|
||||
loading.style.visibility = 'visible';
|
||||
loading.style.opacity = 1;
|
||||
}
|
||||
}
|
||||
|
||||
get rect() {
|
||||
const container = this.container;
|
||||
if (container == null) {
|
||||
@ -105,25 +130,41 @@ export class Popup {
|
||||
}
|
||||
}
|
||||
|
||||
close(animation = true) {
|
||||
close(result = null, animation = true) {
|
||||
const option = this._var.option;
|
||||
const mask = this._var.mask;
|
||||
const doClose = () => {
|
||||
if (option.persistent) {
|
||||
mask.style.display = 'none';
|
||||
} else {
|
||||
mask.remove();
|
||||
this._var.mask = null;
|
||||
}
|
||||
}
|
||||
if (animation) {
|
||||
mask.classList.add('ui-popup-active');
|
||||
mask.style.opacity = 0;
|
||||
setTimeout(() => { mask.remove(); }, 120);
|
||||
setTimeout(() => { doClose(); }, 120);
|
||||
} else {
|
||||
mask.remove();
|
||||
doClose();
|
||||
}
|
||||
if (typeof this._var.option.onMasking === 'function') {
|
||||
this._var.option.onMasking.call(this, false);
|
||||
if (typeof option.onMasking === 'function') {
|
||||
option.onMasking.call(this, false);
|
||||
}
|
||||
if (typeof this._var.option.resolve === 'function') {
|
||||
this._var.option.resolve();
|
||||
if (typeof option.resolve === 'function') {
|
||||
option.resolve.call(this, {
|
||||
result,
|
||||
popup: this
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建 Popup 面板
|
||||
* @returns {HTMLDivElement} 返回遮罩元素(顶层元素)
|
||||
*/
|
||||
create() {
|
||||
const mask = createElement('div', 'ui-popup-mask');
|
||||
const mask = createElement('div', 'ui-popup-mask ui-popup-active');
|
||||
const option = this._var.option;
|
||||
if (option.mask === false) {
|
||||
mask.classList.add('ui-popup-transparent');
|
||||
@ -286,14 +327,14 @@ export class Popup {
|
||||
if (typeof result?.then === 'function') {
|
||||
result.then(r => {
|
||||
if (r !== false) {
|
||||
this.close();
|
||||
this.close(r);
|
||||
}
|
||||
}).catch(reason => console.warn(reason));
|
||||
} else if (result !== false) {
|
||||
this.close();
|
||||
this.close(result);
|
||||
}
|
||||
} else {
|
||||
this.close();
|
||||
this.close(b.key ?? i);
|
||||
}
|
||||
});
|
||||
return button;
|
||||
@ -357,24 +398,33 @@ export class Popup {
|
||||
return mask;
|
||||
}
|
||||
|
||||
show(parent = document.body) {
|
||||
show(parent = document.body, hidden = false) {
|
||||
if (parent == null) {
|
||||
return;
|
||||
}
|
||||
let mask = this._var.mask ?? this.create();
|
||||
// const exists = [...parent.children].filter(e => e.classList.contains('ui-popup-mask'));
|
||||
const exists = parent.querySelectorAll('.ui-popup-mask');
|
||||
let zindex = 0;
|
||||
for (let ex of exists) {
|
||||
let z = parseInt(ex.style.zIndex);
|
||||
if (!isNaN(z) && z > zindex) {
|
||||
zindex = z;
|
||||
let mask = this._var.mask;
|
||||
if (mask == null) {
|
||||
mask = this._var.mask = this.create();
|
||||
}
|
||||
if (mask.parentElement == null) {
|
||||
// const exists = [...parent.children].filter(e => e.classList.contains('ui-popup-mask'));
|
||||
const exists = parent.querySelectorAll('.ui-popup-mask');
|
||||
let zindex = 0;
|
||||
for (let ex of exists) {
|
||||
let z = parseInt(global.getComputedStyle(ex).zIndex);
|
||||
if (!isNaN(z) && z > zindex) {
|
||||
zindex = z;
|
||||
}
|
||||
}
|
||||
if (zindex > 0) {
|
||||
mask.style.zIndex = String(zindex + 1);
|
||||
}
|
||||
parent.appendChild(mask);
|
||||
if (hidden === true) {
|
||||
mask.style.display = 'none';
|
||||
return Promise.resolve(mask);
|
||||
}
|
||||
}
|
||||
if (zindex > 0) {
|
||||
mask.style.zIndex = String(zindex + 1);
|
||||
}
|
||||
parent.appendChild(mask);
|
||||
if (this._var.option.mask === false) {
|
||||
// calculator position
|
||||
const container = this.container;
|
||||
@ -382,29 +432,16 @@ export class Popup {
|
||||
container.style.top = String((parent.offsetHeight - container.offsetHeight) / 2) + 'px';
|
||||
}
|
||||
return new Promise(resolve => {
|
||||
setTimeout(() => {
|
||||
mask.style.display = '';
|
||||
requestAnimationFrame(() => {
|
||||
mask.classList.remove('ui-popup-active');
|
||||
mask.style.opacity = 1;
|
||||
this.container.focus();
|
||||
resolve(mask);
|
||||
}, 0);
|
||||
setTimeout(() => resolve(mask), 120);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
get loading() { return this._var.mask?.querySelector('.ui-popup-body>.ui-popup-loading')?.style?.visibility === 'visible' }
|
||||
set loading(flag) {
|
||||
let loading = this._var.mask?.querySelector('.ui-popup-body>.ui-popup-loading');
|
||||
if (loading == null) {
|
||||
return;
|
||||
}
|
||||
if (flag === false) {
|
||||
loading.style.visibility = 'hidden';
|
||||
loading.style.opacity = 0;
|
||||
} else {
|
||||
loading.style.visibility = 'visible';
|
||||
loading.style.opacity = 1;
|
||||
}
|
||||
}
|
||||
|
||||
_resize(mod, e) {
|
||||
if (e.buttons !== 1) {
|
||||
return;
|
||||
@ -500,6 +537,43 @@ export function createPopup(title, content, ...buttons) {
|
||||
return popup;
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析对话框元素
|
||||
* @param {HTMLElement | string} wrapper - 解析该 `.dialog` 元素
|
||||
* @param {Function} [callback] - 关闭对话框时的回调
|
||||
* @param {boolean} [removable] - 是否可移除
|
||||
* @param {number} [zIndex] - 对话框默认 `z-index`
|
||||
* @returns {Popup} 返回弹出框字典
|
||||
*/
|
||||
export function resolvePopup(wrapper, callback, removable, zIndex) {
|
||||
if (typeof wrapper === 'string') {
|
||||
wrapper = document.querySelector(wrapper);
|
||||
}
|
||||
if (wrapper == null) {
|
||||
return null;
|
||||
}
|
||||
if (!wrapper.classList.contains('dialog')) {
|
||||
return null;
|
||||
}
|
||||
const title = wrapper.querySelector('.dialog-title>.title')?.innerText;
|
||||
const content = wrapper.querySelector('.dialog-title+div');
|
||||
const buttons = [...wrapper.querySelectorAll('.dialog-func>input[type="button"]')].reverse().map(b => ({
|
||||
tabIndex: b.tabIndex,
|
||||
text: b.value,
|
||||
trigger: b.onclick == null ? null : (popup => (b.onclick.call(popup), false))
|
||||
}));
|
||||
const popup = new Popup({
|
||||
title,
|
||||
content,
|
||||
persistent: !removable,
|
||||
resolve: typeof callback === 'function' ? (result => callback(result)) : null,
|
||||
zIndex: wrapper.zIndex ?? zIndex,
|
||||
buttons
|
||||
});
|
||||
popup.show(document.body, true);
|
||||
return popup;
|
||||
}
|
||||
|
||||
const iconTypes = {
|
||||
'info': 'info-circle',
|
||||
'information': 'info-circle',
|
||||
@ -510,6 +584,7 @@ const iconTypes = {
|
||||
}
|
||||
|
||||
export function showAlert(title, message, iconType = 'info', parent = document.body) {
|
||||
const r = typeof GetTextByKey === 'function' ? GetTextByKey : lang;
|
||||
return new Promise(resolve => {
|
||||
const popup = new Popup({
|
||||
title,
|
||||
@ -519,7 +594,7 @@ export function showAlert(title, message, iconType = 'info', parent = document.b
|
||||
),
|
||||
resolve,
|
||||
buttons: [
|
||||
{ text: r('ok', 'OK'), trigger: resolve }
|
||||
{ text: r('ok', 'OK') }
|
||||
]
|
||||
});
|
||||
popup.show(parent).then(mask => {
|
||||
@ -530,6 +605,7 @@ export function showAlert(title, message, iconType = 'info', parent = document.b
|
||||
}
|
||||
|
||||
export function showConfirm(title, content, buttons, iconType = 'question', parent = document.body) {
|
||||
const r = typeof GetTextByKey === 'function' ? GetTextByKey : lang;
|
||||
return new Promise(resolve => {
|
||||
const wrapper = createElement('div', 'message-wrapper');
|
||||
if (!nullOrEmpty(iconType)) {
|
||||
@ -542,34 +618,23 @@ export function showConfirm(title, content, buttons, iconType = 'question', pare
|
||||
title,
|
||||
content: wrapper,
|
||||
resolve,
|
||||
buttons: buttons?.map(b => {
|
||||
buttons: buttons?.map((b, i) => {
|
||||
return {
|
||||
text: b.text,
|
||||
trigger: p => {
|
||||
let result;
|
||||
if (typeof b.trigger === 'function') {
|
||||
result = b.trigger(p, b);
|
||||
if (typeof result?.then === 'function') {
|
||||
return result.then(r => {
|
||||
r !== false && resolve(r);
|
||||
return r;
|
||||
});
|
||||
}
|
||||
result !== false && resolve(result);
|
||||
} else {
|
||||
result = {
|
||||
key: b.key,
|
||||
popup: p
|
||||
};
|
||||
resolve(result);
|
||||
result = b.key ?? i;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
};
|
||||
}) ??
|
||||
[
|
||||
{ text: r('yes', 'Yes'), trigger: p => resolve({ key: 'yes', popup: p }) },
|
||||
{ text: r('no', 'No'), trigger: p => resolve({ key: 'no', popup: p }) }
|
||||
{ key: 'yes', text: r('yes', 'Yes') },
|
||||
{ key: 'no', text: r('no', 'No') }
|
||||
]
|
||||
});
|
||||
popup.show(parent).then(mask => {
|
||||
|
Reference in New Issue
Block a user