communications app, popup lib

This commit is contained in:
2023-04-08 11:46:20 +08:00
parent 449196b491
commit f85d4c9903
25 changed files with 746 additions and 279 deletions

View File

@ -1,4 +1,4 @@
import { createElement, createElementInit } from "../functions";
import { createElement } from "../functions";
import { createIcon } from "./icon";
function fillCheckbox(container, type, label) {
@ -9,14 +9,14 @@ function fillCheckbox(container, type, label) {
container.appendChild(label);
} else if (label?.length > 0) {
container.appendChild(
createElementInit('span', span => span.innerText = label)
createElement('span', span => span.innerText = label)
);
}
}
function createCheckbox(opts = {}) {
const container = createElement('label', 'checkbox-wrapper',
createElementInit('input', input => {
createElement('input', input => {
input.setAttribute('type', 'checkbox');
if (opts.checked === true) {
input.checked = true;

View File

@ -279,7 +279,7 @@
display: flex;
}
#dropdown-sample>.dropdown-wrapper {
#dropdown-sample>.drop-wrapper {
width: 200px;
margin-right: 10px;
}

View File

@ -20,7 +20,7 @@ if (dropdownGlobal == null) {
configurable: false,
enumerable: false,
value: function () {
const panel = document.querySelector('.dropdown-wrapper .dropdown-box.active');
const panel = document.querySelector('.drop-wrapper .drop-box.active');
if (panel == null) {
return;
}
@ -40,7 +40,7 @@ if (dropdownGlobal == null) {
document.addEventListener('mousedown', e => {
let parent = e.target;
while (parent != null) {
if (parent.classList.contains('dropdown-box')) {
if (parent.classList.contains('drop-box')) {
e.stopPropagation();
return;
}
@ -111,14 +111,14 @@ class Dropdown {
const options = this.#options;
// wrapper
const wrapper = createElement('div', 'dropdown-wrapper');
const wrapper = createElement('div', 'drop-wrapper');
const dropId = String(Math.random()).substring(2);
wrapper.dataset.dropId = dropId;
dropdownGlobal[dropId] = this;
this.#wrapper = wrapper;
// header
const header = createElement('div', 'dropdown-header');
const header = createElement('div', 'drop-header');
header.addEventListener('click', () => {
if (this.disabled) {
return;
@ -136,7 +136,7 @@ class Dropdown {
// label or input
let label;
if (options.input) {
label = createElement('input', 'dropdown-text');
label = createElement('input', 'drop-text');
label.setAttribute('type', 'text');
options.placeholder && label.setAttribute('placeholder', options.placeholder);
isPositive(options.maxlength) && label.setAttribute('maxlength', options.maxlength);
@ -151,7 +151,7 @@ class Dropdown {
label.addEventListener('mousedown', e => this.#expanded && e.stopPropagation());
} else {
isPositive(options.tabindex) && header.setAttribute('tabindex', options.tabindex);
label = createElement('label', 'dropdown-text');
label = createElement('label', 'drop-text');
}
this.#label = label;
if (options.multiselect) {
@ -164,7 +164,7 @@ class Dropdown {
} else if (options.selected != null) {
this.select(options.selected, true);
}
header.append(label, createElement('label', 'dropdown-caret'));
header.append(label, createElement('label', 'drop-caret'));
wrapper.appendChild(header);
this.disabled = options.disabled || false;
@ -173,16 +173,16 @@ class Dropdown {
get multiselect() { return this.#options.multiselect }
get disabled() { return this.#wrapper == null || this.#wrapper.querySelector('.dropdown-header.disabled') != null }
get disabled() { return this.#wrapper == null || this.#wrapper.querySelector('.drop-header.disabled') != null }
set disabled(flag) {
if (this.#wrapper == null) {
return;
}
if (flag) {
this.#wrapper.querySelector('.dropdown-header').classList.add('disabled');
this.#wrapper.querySelector('.drop-header').classList.add('disabled');
} else {
this.#wrapper.querySelector('.dropdown-header').classList.remove('disabled');
this.#wrapper.querySelector('.drop-header').classList.remove('disabled');
}
}
@ -275,16 +275,16 @@ class Dropdown {
}
}
get #expanded() { return this.#container?.style.visibility === 'visible' }
get #expanded() { return this.#container?.style?.visibility === 'visible' }
#dropdown(flag = true) {
const options = this.#options;
let panel = this.#container;
if (panel == null) {
panel = createElement('div', 'dropdown-box');
panel = createElement('div', 'drop-box');
// search box
if (!options.input && options.search) {
const search = createElement('div', 'dropdown-search');
const search = createElement('div', 'drop-search');
const input = createElement('input');
input.setAttribute('type', 'text');
isPositive(options.tabindex) && input.setAttribute('tabindex', options.tabindex);
@ -298,7 +298,7 @@ class Dropdown {
panel.appendChild(search);
}
// list
const list = createElement('ul', 'dropdown-list');
const list = createElement('ul', 'drop-list');
if (!this.multiselect) {
list.addEventListener('click', e => {
let li = e.target;
@ -321,7 +321,7 @@ class Dropdown {
if (flag) {
let source = this.source;
if (!options.input && options.search) {
const search = panel.querySelector('.dropdown-search > input');
const search = panel.querySelector('.drop-search > input');
if (!nullOrEmpty(search?.value)) {
source = filterSource(options.searchkeys, options.textkey, search.value, source);
}
@ -348,7 +348,7 @@ class Dropdown {
}
#filllist(source) {
const list = this.#container.querySelector('.dropdown-list');
const list = this.#container.querySelector('.drop-list');
list.replaceChildren();
const multiselect = this.multiselect;
const allchecked = this.#allChecked;

7
lib/ui/grid.d.ts vendored
View File

@ -5,6 +5,11 @@ interface GridItem {
displayValue: string;
}
interface GridSourceItem {
value: string;
text: string;
}
declare var GridColumn: {
create(): HTMLElement;
createEdit(trigger: (e: any) => void, col: GridColumnDefinition, body: HTMLElement): HTMLElement;
@ -44,7 +49,7 @@ interface GridColumnDefinition {
sortFilter?: (a: GridItem | any, b: GridItem | any) => -1 | 0 | 1;
bgFilter?: (item: GridItem | any) => string;
dropOptions?: DropdownOptions;
source?: Array<any> | ((item: GridItem | any) => Array<any>);
source?: Array<any> | ((item: GridItem | any) => Array<any> | Promise<Array<GridSourceItem>>);
iconType?: string;
text?: string;
tooltip?: string;

View File

@ -85,7 +85,7 @@
sortFilter?: (a: GridItem | any, b: GridItem | any) => -1 | 0 | 1;
bgFilter?: (item: GridItem | any) => string;
dropOptions?: DropdownOptions;
source?: Array&lt;any&gt; | ((item: GridItem | any) => Array&lt;any&gt;);
source?: Array&lt;any&gt; | ((item: GridItem | any) => Array&lt;any&gt; | Promise&lt;Array&lt;GridSourceItem&gt;&gt;);
iconType?: string;
text?: string;
tooltip?: string;
@ -136,8 +136,8 @@
<samp>val: any</samp>
<p>单元格的值</p>
<samp>item: GridItem | any</samp>
<p>单元格所在行的数据项/p>
<samp>col: GridColumnDefinition</samp>
<p>单元格所在行的数据项</p>
<samp>col: GridColumnDefinition</samp>
<p>单元格所在列的定义对象</p>
<hr />
<h2>示例</h2>
@ -198,12 +198,22 @@
key: 'c2a',
caption: '下拉',
type: Grid.ColumnTypes.Dropdown,
source: [
{ value: 'off', text: 'Off' },
{ value: 'pending', text: 'Pending' },
{ value: 'broken', text: 'Broken' },
{ value: 'running', text: 'Running' }
],
source: item => {
if (item.source == null) {
return new Promise((resolve, reject) => {
setTimeout(() => {
item.source = [
{ value: 'off', text: 'Off' },
{ value: 'pending', text: 'Pending' },
{ value: 'broken', text: 'Broken' },
{ value: 'running', text: 'Running' }
];
resolve(item.source);
}, 2000);
});
}
return item.source;
},
enabled: 'enabled',
onchanged: (item, value) => console.log('dropdown changed', item, value)
},

View File

@ -1,7 +1,7 @@
import { global, isPositive, isMobile, throttle, truncate } from "../utility";
import { nullOrEmpty } from "../utility/strings";
import { r } from "../utility/lgres";
import { createElement, createElementInit } from "../functions";
import { createElement } from "../functions";
import { createIcon } from "./icon";
import { createCheckbox } from "./checkbox";
import { setTooltip } from "./tooltip";
@ -85,7 +85,7 @@ class GridDropdownColumn extends GridColumn {
return drop.create();
}
static getDrop(element) {
static #getDrop(element) {
const dropGlobal = global[SymbolDropdown];
if (dropGlobal == null) {
return null;
@ -98,7 +98,7 @@ class GridDropdownColumn extends GridColumn {
return drop;
}
static getSource(item, col) {
static #getSource(item, col) {
let source = col.source;
if (typeof source === 'function') {
source = source(item);
@ -106,23 +106,37 @@ class GridDropdownColumn extends GridColumn {
return source;
}
static #setValue(source, element, val) {
const data = source?.find(v => v.value === val);
if (data != null) {
val = data.text;
}
super.setValue(element, val);
}
static setValue(element, val, item, col) {
if (element.tagName !== 'DIV') {
let source = this.getSource(item, col);
const data = source?.find(v => v.value === val);
if (data != null) {
val = data.text;
let source = this.#getSource(item, col);
if (source instanceof Promise) {
source.then(s => this.#setValue(s, element, val));
} else {
this.#setValue(source, element, val);
}
super.setValue(element, val);
return;
}
const drop = this.getDrop(element);
const drop = this.#getDrop(element);
if (drop == null) {
return;
}
if (drop.source == null || drop.source.length === 0) {
let source = this.getSource(item, col);
if (source != null) {
let source = this.#getSource(item, col);
if (source instanceof Promise) {
source.then(s => {
drop.source = s;
drop.select(val, true);
})
return;
} else if (source != null) {
drop.source = source;
}
}
@ -134,7 +148,7 @@ class GridDropdownColumn extends GridColumn {
}
static setEnabled(element, enabled) {
const drop = this.getDrop(element);
const drop = this.#getDrop(element);
if (drop == null) {
return;
}
@ -316,7 +330,7 @@ class Grid {
get selectedIndex() { return (this.#selectedIndexes && this.#selectedIndexes[0]) ?? -1 }
get loading() { return this.#refs.loading?.style.visibility === 'visible' }
get loading() { return this.#refs.loading?.style?.visibility === 'visible' }
set loading(flag) {
if (this.#refs.loading == null) {
return;

45
lib/ui/popup.js Normal file
View File

@ -0,0 +1,45 @@
import "../../css/popup.scss";
import { createElement } from "../functions";
import { createIcon } from "./icon";
function createPopup(title, content, ...buttons) {
const mask = createElement('div', 'popup-mask');
const container = createElement('div', 'popup-container');
const close = () => {
mask.classList.add('popup-active');
mask.style.opacity = 0;
setTimeout(() => mask.remove(), 120);
};
container.append(
createElement('div', header => {
header.className = 'popup-header';
if (title instanceof HTMLElement) {
header.appendChild(title);
} else {
header.appendChild(createElement('div', t => t.innerText = title));
}
const cancel = createIcon('fa-regular', 'times');
cancel.addEventListener('click', () => close());
header.appendChild(cancel);
}),
createElement('div', 'popup-body', content),
createElement('div', 'popup-footer', ...buttons.map(b => {
const button = createElement('div', 'popup-button');
button.innerText = b.text;
if (typeof b.trigger === 'function') {
button.addEventListener('click', () => {
if (b.trigger(container) === false) {
return;
}
close();
});
}
}))
);
mask.appendChild(container);
return mask;
}
export {
createPopup
}

2
lib/ui/tooltip.d.ts vendored
View File

@ -1,2 +1,2 @@
export function setTooltip(container: HTMLElement, content: string | HTMLElement): HTMLElement
export function setTooltip(container: HTMLElement, content: string | HTMLElement, flag?: boolean): HTMLElement
export function resolveTooltip(container?: HTMLElement): HTMLElement

View File

@ -5,7 +5,7 @@
给某个元素或者页面上含有 title 属性的元素设置一个统一样式的 tooltip。
</p>
<h2>setTooltip</h2>
<code>function setTooltip(container: HTMLElement, content: string | HTMLElement): void</code>
<code>function setTooltip(container: HTMLElement, content: string | HTMLElement, flag?: boolean): void</code>
<h3>container: HTMLElement</h3>
<p>
要设置 tooltip 的元素
@ -14,6 +14,10 @@
<p>
要设置的 tooltip 内容,允许为字符串或者 HTML 元素
</p>
<h3>flag?: boolean</h3>
<p>
是否启用严格模式,只有显示不完整时才显示 tooltip
</p>
<h2>resolveTooltip</h2>
<code>function resolveTooltip(container?: HTMLElement): HTMLElement</code>
<h3>container?: HTMLElement</h3>

View File

@ -1,18 +1,18 @@
import { createElement, createElementInit } from "../functions";
import { createElement } from "../functions";
function setTooltip(container, content) {
function setTooltip(container, content, flag = false) {
const tip = container.querySelector('.tooltip-wrapper');
if (tip != null) {
tip.remove();
}
const wrapper = createElementInit('div', wrapper => {
const wrapper = createElement('div', wrapper => {
wrapper.className = 'tooltip-wrapper tooltip-color';
wrapper.style.visibility = 'hidden';
wrapper.style.opacity = 0;
},
createElement('div', 'tooltip-pointer tooltip-color'),
createElement('div', 'tooltip-curtain tooltip-color'),
createElementInit('div', cnt => {
createElement('div', cnt => {
cnt.className = 'tooltip-content';
if (content instanceof HTMLElement) {
cnt.appendChild(content);
@ -27,32 +27,35 @@ function setTooltip(container, content) {
let tid;
container.addEventListener('mouseenter', () => {
tid && clearTimeout(tid);
tid = setTimeout(() => {
while (container?.offsetWidth == null) {
container = container.parentElement;
}
if (container == null) {
return;
}
let parent = container;
let left = container.offsetLeft;
let top = container.offsetTop;
while ((parent = parent.offsetParent) != null) {
left += parent.offsetLeft;
top += parent.offsetTop;
}
parent = container;
while ((parent = parent.parentElement) != null) {
left -= parent.scrollLeft;
top -= parent.scrollTop;
}
left -= wrapper.offsetWidth / 2 - container.offsetWidth / 2;
top -= wrapper.offsetHeight + 14;
wrapper.style.left = `${left}px`;
wrapper.style.top = `${top}px`;
wrapper.style.visibility = 'visible';
wrapper.style.opacity = 1;
}, 100);
let c = container;
while (c?.offsetWidth == null) {
c = c.parentElement;
}
if (c == null) {
return;
}
if (!flag || c.scrollWidth > c.offsetWidth) {
tid = setTimeout(() => {
let parent = c;
let left = c.offsetLeft;
let top = c.offsetTop;
while ((parent = parent.offsetParent) != null) {
left += parent.offsetLeft;
top += parent.offsetTop;
}
parent = c;
while ((parent = parent.parentElement) != null) {
left -= parent.scrollLeft;
top -= parent.scrollTop;
}
left -= wrapper.offsetWidth / 2 - c.offsetWidth / 2;
top -= wrapper.offsetHeight + 14;
wrapper.style.left = `${left}px`;
wrapper.style.top = `${top}px`;
wrapper.style.visibility = 'visible';
wrapper.style.opacity = 1;
}, 100);
}
});
container.addEventListener('mouseleave', () => {
tid && clearTimeout(tid);