sync working code, and import type-doc
This commit is contained in:
34
lib/ui/checkbox.d.ts
vendored
34
lib/ui/checkbox.d.ts
vendored
@ -1,18 +1,50 @@
|
||||
/**
|
||||
* 复选框选项
|
||||
*/
|
||||
interface CheckboxOptions {
|
||||
/** 样式类名 */
|
||||
className?: string;
|
||||
/** 是否可用 */
|
||||
enabled?: boolean;
|
||||
/** html 名称 */
|
||||
name?: string;
|
||||
/** 焦点索引 */
|
||||
tabIndex?: Number;
|
||||
/** 样式分类,可以是 ['`fa-light`', '`fa-regular`', '`fa-solid`'] 其中之一 */
|
||||
type?: string;
|
||||
/** 标签 */
|
||||
label?: string | HTMLElement;
|
||||
/** 是否已选中 */
|
||||
checked?: boolean;
|
||||
/** 图片高度 */
|
||||
imageHeight?: Number;
|
||||
/** 选中时显示的元素 */
|
||||
checkedNode?: HTMLElement;
|
||||
/** 未选中时显示的元素 */
|
||||
uncheckedNode?: HTMLElement;
|
||||
customerAttributes?: { [key: string]: string };
|
||||
/** 自定义 html 属性 */
|
||||
customAttributes?: { [key: string]: string };
|
||||
/**
|
||||
* 复选框选择状态改变时触发
|
||||
* @param this 当前复选框对应的 HtmlInput 元素
|
||||
* @param ev 选择事件对象
|
||||
*/
|
||||
onchange?: (this: HTMLInputElement, ev: Event) => any;
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建一个单选框
|
||||
* @param opts 单选框参数
|
||||
*/
|
||||
export function createRadiobox(opts?: CheckboxOptions): HTMLElement
|
||||
/**
|
||||
* 创建一个复选框
|
||||
* @param opts 复选框参数
|
||||
*/
|
||||
export function createCheckbox(opts?: CheckboxOptions): HTMLElement
|
||||
/**
|
||||
* 解析容器元素内符合条件的子元素为复选框元素
|
||||
* @param container 容器元素
|
||||
* @param legacy 是否使用传统模式
|
||||
*/
|
||||
export function resolveCheckbox(container?: HTMLElement, legacy?: boolean): HTMLElement
|
@ -1,138 +0,0 @@
|
||||
<div>
|
||||
<h1>checkbox</h1>
|
||||
<hr />
|
||||
<p>
|
||||
创建一个统一样式的复选框元素,或者解析转换页面上特定类型的 label
|
||||
标签为复选框元素。
|
||||
</p>
|
||||
<h2>createCheckbox</h2>
|
||||
<code>function createCheckbox(opts?: CheckboxOptions): HTMLElement</code>
|
||||
<h3>opts?: CheckboxOptions</h3>
|
||||
<p>
|
||||
复选框初始参数,结构为
|
||||
<pre>interface CheckboxOptions {
|
||||
className?: string;
|
||||
enabled?: boolean;
|
||||
name?: string;
|
||||
tabIndex?: Number;
|
||||
type?: string;
|
||||
label?: string;
|
||||
checked?: boolean;
|
||||
imageHeight?: Number;
|
||||
checkedNode?: HTMLElement;
|
||||
uncheckedNode?: HTMLElement;
|
||||
customerAttributes?: { [key: string]: string };
|
||||
onchange?: (this: HTMLInputElement, ev: Event) => any;
|
||||
}</pre>
|
||||
</p>
|
||||
<h4>className?: string</h4>
|
||||
<p>
|
||||
复选框的自定义 className
|
||||
</p>
|
||||
<h4>enabled?: boolean</h4>
|
||||
<p>
|
||||
复选框默认是否可用
|
||||
</p>
|
||||
<h4>name?: string</h4>
|
||||
<p>
|
||||
复选框或单选框的 name
|
||||
</p>
|
||||
<h4>tabIndex?: Number</h4>
|
||||
<p>
|
||||
复选框的 tabindex
|
||||
</p>
|
||||
<h4>type?: string</h4>
|
||||
<p>
|
||||
复选框图标的样式,可选值目前有 <code>fa-regular</code>、<code>fa-light</code>、<code>fa-solid</code>
|
||||
</p>
|
||||
<h4>label?: string | HTMLElement</h4>
|
||||
<p>
|
||||
复选框的标签文本,或者想要呈现的元素
|
||||
</p>
|
||||
<h4>checked?: boolean</h4>
|
||||
<p>
|
||||
初始是否选中
|
||||
</p>
|
||||
<h4>imageHeight?: Number</h4>
|
||||
<p>
|
||||
为图片复选框时的图片限制高度
|
||||
</p>
|
||||
<h4>checkedNode?: HTMLElement</h4>
|
||||
<p>
|
||||
为图片复选框时的选中时显示的元素
|
||||
</p>
|
||||
<h4>uncheckedNode?: HTMLElement</h4>
|
||||
<p>
|
||||
为图片复选框时的未选中时显示的元素
|
||||
</p>
|
||||
<h4>customerAttributes?: { [key: string]: string }</h4>
|
||||
<p>
|
||||
自定义属性,例如
|
||||
<pre>{
|
||||
'data-id': 'xxxxxx',
|
||||
'disabled': ''
|
||||
}</pre>
|
||||
</p>
|
||||
<h4>onchange?: (this: HTMLInputElement, ev: Event) => any</h4>
|
||||
<p>
|
||||
复选框改变时触发的事件
|
||||
</p>
|
||||
<h2>createRadiobox</h2>
|
||||
<code>function createRadiobox(opts?: CheckboxOptions): HTMLElement</code>
|
||||
<h3>opts?: CheckboxOptions</h3>
|
||||
<p>
|
||||
单选框初始参数,结构如上
|
||||
</p>
|
||||
<h2>resolveCheckbox</h2>
|
||||
<code>function resolveCheckbox(container?: HTMLElement, legacy?: boolean): HTMLElement</code>
|
||||
<h3>container?: HTMLElement</h3>
|
||||
<p>
|
||||
将把此 HTML 元素,为 null 则把 document.body 下的所有 <code>label[data-checkbox]</code> 元素解析为复选框,<code>[data-id]</code> 为复选框元素的
|
||||
id,包含
|
||||
<code>[data-checked]</code> 时复选框默认选中。
|
||||
</p>
|
||||
<p>当该元素无子元素时,<code>[data-type]</code> 同上述参数中的 <code>type?: string</code>,<code>[data-label]</code> 同上述参数中的
|
||||
<code>label?: string</code>。
|
||||
</p>
|
||||
<p>当该元素有子元素时,解析为图片复选框,class 为 <code>checked</code>、<code>unchecked</code> 的子元素将分别在选中与未选中时显示。</p>
|
||||
<h3>legacy?: boolean</h3>
|
||||
<p>
|
||||
是否开启兼容模式,启用兼容模式时将试图匹配 <code>input[type="checkbox"]</code> 标签,与其周围的 label,将其转换为统一样式的复选框。
|
||||
</p>
|
||||
<hr />
|
||||
<h2>示例</h2>
|
||||
<pre><div id="checkbox-sample">
|
||||
<!-- 1 -->
|
||||
<label data-checkbox data-type="fa-light" data-label="Checkbox Light"></label>
|
||||
<!-- 2 -->
|
||||
<label data-checkbox data-checked data-label="Checkbox Regular"></label>
|
||||
<!-- 3 -->
|
||||
<label data-checkbox data-type="fa-solid" data-label="Checkbox Solid"></label>
|
||||
<!-- 4 -->
|
||||
<label data-checkbox>
|
||||
<code class="checked">Checked</code>
|
||||
<code class="unchecked">Unchecked</code>
|
||||
</label>
|
||||
<!-- 5 -->
|
||||
<input id="check-status" type="checkbox"/>
|
||||
<label for="check-status">Label for Status</label>
|
||||
</div>
|
||||
|
||||
<script type="text/javascript">
|
||||
window["lib-ui"].resolveCheckbox(document.querySelector("#checkbox-sample"), true);
|
||||
</script></pre>
|
||||
<div id="checkbox-sample">
|
||||
<label data-checkbox data-type="fa-light" data-label="Checkbox Light"></label>
|
||||
<label data-checkbox data-checked data-label="Checkbox Regular"></label>
|
||||
<label data-checkbox data-type="fa-solid" data-label="Checkbox Solid"></label>
|
||||
<label data-checkbox>
|
||||
<code class="checked">Checked</code>
|
||||
<code class="unchecked">Unchecked</code>
|
||||
</label>
|
||||
<input id="check-status" type="checkbox" />
|
||||
<label for="check-status">Label for Status</label>
|
||||
</div>
|
||||
<script type="text/javascript">
|
||||
window["lib-ui"].resolveCheckbox(document.querySelector("#checkbox-sample"), true);
|
||||
</script>
|
||||
</div>
|
@ -40,8 +40,8 @@ function createRadiobox(opts = {}) {
|
||||
if (opts.enabled === false) {
|
||||
input.disabled = true;
|
||||
}
|
||||
if (opts.customerAttributes != null) {
|
||||
for (let entry of Object.entries(opts.customerAttributes)) {
|
||||
if (opts.customAttributes != null) {
|
||||
for (let entry of Object.entries(opts.customAttributes)) {
|
||||
input.setAttribute(entry[0], entry[1]);
|
||||
}
|
||||
}
|
||||
@ -66,8 +66,8 @@ function createCheckbox(opts = {}) {
|
||||
if (opts.enabled === false) {
|
||||
input.disabled = true;
|
||||
}
|
||||
if (opts.customerAttributes != null) {
|
||||
for (let entry of Object.entries(opts.customerAttributes)) {
|
||||
if (opts.customAttributes != null) {
|
||||
for (let entry of Object.entries(opts.customAttributes)) {
|
||||
input.setAttribute(entry[0], entry[1]);
|
||||
}
|
||||
}
|
||||
|
60
lib/ui/css/media.scss
Normal file
60
lib/ui/css/media.scss
Normal file
@ -0,0 +1,60 @@
|
||||
.ui-media-picture>img {
|
||||
max-width: 100px;
|
||||
max-height: 100px;
|
||||
}
|
||||
|
||||
.ui-media-file {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
|
||||
>svg {
|
||||
width: 26px;
|
||||
height: 26px;
|
||||
fill: var(--link-color);
|
||||
}
|
||||
|
||||
>a {
|
||||
margin-left: 6px;
|
||||
color: var(--link-color);
|
||||
font-size: var(--font-size);
|
||||
}
|
||||
}
|
||||
|
||||
.ui-media-audio {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
background-color: #eee;
|
||||
border-radius: 20px;
|
||||
overflow: hidden;
|
||||
padding-right: 16px;
|
||||
|
||||
>button {
|
||||
background-color: transparent;
|
||||
border: none;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
padding: 14px;
|
||||
display: flex;
|
||||
outline: none;
|
||||
|
||||
&:hover {
|
||||
border: none;
|
||||
}
|
||||
|
||||
>svg {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
>.ui-media-timestamp {
|
||||
color: var(--secondary-color);
|
||||
font-size: var(--font-size);
|
||||
user-select: none;
|
||||
}
|
||||
}
|
||||
|
||||
.ui-media-video {
|
||||
max-width: 200px;
|
||||
max-height: 200px;
|
||||
}
|
@ -13,6 +13,7 @@
|
||||
/*color-scheme: light dark;*/
|
||||
|
||||
--color: #201f1e;
|
||||
--secondary-color: #777;
|
||||
--bg-color: #fff;
|
||||
--border-color: #b9b9b9;
|
||||
--focus-border-color: #666;
|
||||
|
15
lib/ui/dropdown.d.ts
vendored
15
lib/ui/dropdown.d.ts
vendored
@ -23,7 +23,11 @@ export interface DropdownOptions {
|
||||
parent?: HTMLElement;
|
||||
}
|
||||
|
||||
interface Dropdown {
|
||||
export class Dropdown {
|
||||
static resolve(dom?: HTMLElement): HTMLElement;
|
||||
|
||||
constructor(options?: DropdownOptions);
|
||||
|
||||
sourceFilter: () => Array<DropdownItem | any>;
|
||||
onselected: (item: DropdownItem | any) => void;
|
||||
onselectedlist: (list: Array<DropdownItem | any>) => void;
|
||||
@ -40,12 +44,3 @@ interface Dropdown {
|
||||
select(selected: string, silence?: boolean): void;
|
||||
selectlist(selectedlist: Array<string>, silence?: boolean): void;
|
||||
}
|
||||
|
||||
declare var Dropdown: {
|
||||
prototype: Dropdown;
|
||||
new(options?: DropdownOptions): Dropdown;
|
||||
|
||||
resolve(dom?: HTMLElement): HTMLElement;
|
||||
}
|
||||
|
||||
export default Dropdown;
|
@ -1,304 +0,0 @@
|
||||
<div>
|
||||
<h1>dropdown</h1>
|
||||
<hr />
|
||||
<p>
|
||||
创建一个统一样式的下拉输入、选择框元素,或者解析转换页面上的 select 标签为该元素。
|
||||
</p>
|
||||
<h2>constructor</h2>
|
||||
<code>new(options?: DropdownOptions): Dropdown</code>
|
||||
<h3>options?: DropdownOptions</h3>
|
||||
<p>
|
||||
下拉输入、选择框的初始参数,结构为
|
||||
<pre>interface DropdownOptions {
|
||||
textkey?: string;
|
||||
valuekey?: string;
|
||||
htmlkey?: string;
|
||||
maxlength?: Number;
|
||||
multiselect?: boolean;
|
||||
selected?: string;
|
||||
selectedlist?: Array<DropdownItem | any>;
|
||||
disabled?: boolean;
|
||||
input?: boolean;
|
||||
search?: boolean;
|
||||
searchkeys?: Array<string>;
|
||||
searchplaceholder?: string;
|
||||
tabindex?: Number;
|
||||
placeholder?: string;
|
||||
slidefixed?: boolean;
|
||||
parent?: HTMLElement;
|
||||
}</pre>
|
||||
</p>
|
||||
<h4>textkey?: string</h4>
|
||||
<p>
|
||||
数据源中用以显示的属性,默认为 <code>text</code>
|
||||
</p>
|
||||
<h4>valuekey?: string</h4>
|
||||
<p>
|
||||
数据源中作为值的属性,默认为 <code>value</code>
|
||||
</p>
|
||||
<h4>htmlkey?: string</h4>
|
||||
<p>
|
||||
数据源中用来以 html 方式呈现的属性,默认为 <code>html</code>
|
||||
</p>
|
||||
<h4>maxlength?: Number</h4>
|
||||
<p>
|
||||
作为输入类型时的最大允许字符数,默认为 500
|
||||
</p>
|
||||
<h4>multiselect?: boolean</h4>
|
||||
<p>
|
||||
是否允许多选,仅在选择类型下有效
|
||||
</p>
|
||||
<h4>selected?: string</h4>
|
||||
<p>
|
||||
默认选中的项目的值
|
||||
</p>
|
||||
<h4>selectedlist?: Array<DropdownItem | any></h4>
|
||||
<p>
|
||||
多选时默认选中的项目的值的列表
|
||||
</p>
|
||||
<h4>disabled?: boolean</h4>
|
||||
<p>
|
||||
初始化时下拉框是否禁用
|
||||
</p>
|
||||
<h4>input?: boolean</h4>
|
||||
<p>
|
||||
是否为输入类型
|
||||
</p>
|
||||
<h4>search?: boolean</h4>
|
||||
<p>
|
||||
是否允许搜索
|
||||
</p>
|
||||
<h4>searchkeys?: Array<string></h4>
|
||||
<p>
|
||||
搜索时搜索的数据源属性的列表,默认为 <code>[valuekey]</code>
|
||||
</p>
|
||||
<h4>searchplaceholder?: string</h4>
|
||||
<p>
|
||||
搜索输入框的占位字符串
|
||||
</p>
|
||||
<h4>tabindex?: Number</h4>
|
||||
<p>
|
||||
下拉框的焦点顺序
|
||||
</p>
|
||||
<h4>placeholder?: string</h4>
|
||||
<p>
|
||||
作为输入类型时,输入框的占位字符串
|
||||
</p>
|
||||
<h4>slidefixed?: boolean</h4>
|
||||
<p>
|
||||
下拉方向是否固定为下
|
||||
</p>
|
||||
<h4>parent?: HTMLElement</h4>
|
||||
<p>
|
||||
下拉列表呈现的父容器,默认为 <code>document.body</code>
|
||||
</p>
|
||||
<h2>Dropdown.resolve</h2>
|
||||
<code>static resolve(dom?: HTMLElement): HTMLElement</code>
|
||||
<h3>dom?: HTMLElement</h3>
|
||||
<p>
|
||||
将把此 HTML 元素,为 null 则把 document.body 下的所有 <code>select</code> 元素解析为统一样式的下拉框
|
||||
</p>
|
||||
<hr />
|
||||
<h2>sourceFilter</h2>
|
||||
<code>sourceFilter: () => Array<DropdownItem | any></code>
|
||||
<p>
|
||||
数据源代理,返回用以呈现在下拉列表中的数据源
|
||||
</p>
|
||||
<h2>onselected</h2>
|
||||
<code>onselected: (item: DropdownItem | any) => void</code>
|
||||
<h3>item: DropdownItem | any</h3>
|
||||
<p>
|
||||
选中项发生改变时触发该事件,参数为选中的项
|
||||
</p>
|
||||
<h2>onselectedlist</h2>
|
||||
<code>onselectedlist: (list: Array<DropdownItem | any>) => void</code>
|
||||
<h3>list: Array<DropdownItem | any></h3>
|
||||
<p>
|
||||
多选时选中列表发生改变时触发该事件,参数为选中的列表
|
||||
</p>
|
||||
<h2>onexpanded</h2>
|
||||
<code>onexpanded: () => void</code>
|
||||
<p>
|
||||
下拉列表展开时触发该事件,一般用来异步获取服务器数据,填充至数据源
|
||||
</p>
|
||||
<hr />
|
||||
<h2>disabled</h2>
|
||||
<code>property disabled(): boolean</code>
|
||||
<p>
|
||||
获取或设置下拉框是否禁用
|
||||
</p>
|
||||
<h2>source</h2>
|
||||
<code>property source(): Array<DropdownItem | any></code>
|
||||
<p>
|
||||
获取或设置下拉框数据源
|
||||
</p>
|
||||
<h2>multiselect</h2>
|
||||
<code>readonly multiselect: boolean</code>
|
||||
<p>
|
||||
获取下拉框是否支持多选
|
||||
</p>
|
||||
<h2>selected</h2>
|
||||
<code>readonly selected: DropdownItem | any</code>
|
||||
<p>
|
||||
获取下拉框当前选中项
|
||||
</p>
|
||||
<h2>selectedlist</h2>
|
||||
<code>readonly selectedlist: Array<DropdownItem | any></code>
|
||||
<p>
|
||||
获取下拉框当前选中的列表
|
||||
</p>
|
||||
<hr />
|
||||
<h2>create</h2>
|
||||
<code>create(): HTMLElement</code>
|
||||
<p>
|
||||
创建下拉列表,返回一个 HTML 元素
|
||||
</p>
|
||||
<h2>select</h2>
|
||||
<code>select(selected: string, silence?: boolean): void</code>
|
||||
<h3>selected: string</h3>
|
||||
<p>
|
||||
修改下拉框的选中项为参数值对应的项
|
||||
</p>
|
||||
<h3>silence?: boolean</h3>
|
||||
<p>
|
||||
是否静默修改,为 true 时不触发 <code>onselected</code> 事件
|
||||
</p>
|
||||
<h2>selectlist</h2>
|
||||
<code>selectlist(selectedlist: Array<string>, silence?: boolean): void</code>
|
||||
<h3>selectedlist: Array<string></h3>
|
||||
<p>
|
||||
修改下拉框的选中列表为参数值列表对应的项的列表
|
||||
</p>
|
||||
<h3>silence?: boolean</h3>
|
||||
<p>
|
||||
是否静默修改,为 true 时不触发 <code>onselectedlist</code> 事件
|
||||
</p>
|
||||
<hr />
|
||||
<h2>示例</h2>
|
||||
<pre><div id="dropdown-sample">
|
||||
<select>
|
||||
<option value="cs1">Case 1</option>
|
||||
<option value="cs2" selected>Case 2</option>
|
||||
<option value="cs3">Case 3</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<script type="text/javascript">
|
||||
const Dropdown = window["lib-ui"].Dropdown;
|
||||
const sample = document.querySelector('#dropdown-sample');
|
||||
|
||||
// 解析 select 元素
|
||||
Dropdown.resolve(sample);
|
||||
|
||||
// 创建简易输入类型下拉框
|
||||
let drop = new Dropdown({
|
||||
input: true,
|
||||
// selected: 'standby',
|
||||
placeholder: 'asset status',
|
||||
slidefixed: true
|
||||
});
|
||||
drop.source = ['off', 'running', 'standby', 'broken']
|
||||
.map(it => { return { value: it, text: it } });
|
||||
sample.appendChild(drop.create());
|
||||
|
||||
// 创建自定义显示元素的下拉框
|
||||
drop = new Dropdown({
|
||||
selected: '#ff0',
|
||||
search: true,
|
||||
// multiselect: true
|
||||
});
|
||||
drop.source = [
|
||||
{ value: '#fff', text: 'White' },
|
||||
{ value: '#f00', text: 'Red' },
|
||||
{ value: '#0f0', text: 'Green' },
|
||||
{ value: '#00f', text: 'Blue' },
|
||||
{ value: '#ff0', text: 'Yellow' },
|
||||
{ value: '#0ff', text: 'Cyan' },
|
||||
{ value: '#f0f', text: 'Magenta' },
|
||||
];
|
||||
drop.source.forEach(it => {
|
||||
const span = document.createElement('span');
|
||||
span.className = 'ui-drop-item';
|
||||
span.style.setProperty('--color', it.value);
|
||||
span.innerText = it.text;
|
||||
it.html = span;
|
||||
});
|
||||
sample.appendChild(drop.create());
|
||||
</script></pre>
|
||||
<div id="dropdown-sample">
|
||||
<select>
|
||||
<option value="cs1">Case 1</option>
|
||||
<option value="cs2" selected>Case 2</option>
|
||||
<option value="cs3">Case 3</option>
|
||||
</select>
|
||||
</div>
|
||||
<div style="height: 80px"></div>
|
||||
<script type="text/javascript">
|
||||
!function () {
|
||||
const Dropdown = window["lib-ui"].Dropdown;
|
||||
const sample = document.querySelector('#dropdown-sample');
|
||||
|
||||
Dropdown.resolve(sample);
|
||||
|
||||
let drop = new Dropdown({
|
||||
input: true,
|
||||
// selected: 'standby',
|
||||
placeholder: 'asset status',
|
||||
slidefixed: true
|
||||
});
|
||||
drop.source = ['off', 'running', 'standby', 'broken']
|
||||
.map(it => { return { value: it, text: it } });
|
||||
sample.appendChild(drop.create());
|
||||
|
||||
drop = new Dropdown({
|
||||
selected: '#ff0',
|
||||
search: true,
|
||||
// multiselect: true
|
||||
});
|
||||
drop.source = [
|
||||
{ value: '#fff', text: 'White' },
|
||||
{ value: '#f00', text: 'Red' },
|
||||
{ value: '#0f0', text: 'Green' },
|
||||
{ value: '#00f', text: 'Blue' },
|
||||
{ value: '#ff0', text: 'Yellow' },
|
||||
{ value: '#0ff', text: 'Cyan' },
|
||||
{ value: '#f0f', text: 'Magenta' },
|
||||
];
|
||||
drop.source.forEach(it => {
|
||||
const span = document.createElement('span');
|
||||
span.className = 'ui-drop-item';
|
||||
span.style.setProperty('--color', it.value);
|
||||
span.innerText = it.text;
|
||||
it.html = span;
|
||||
});
|
||||
sample.appendChild(drop.create());
|
||||
}();
|
||||
</script>
|
||||
<style type="text/css">
|
||||
#dropdown-sample {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
#dropdown-sample>.ui-drop-wrapper {
|
||||
width: 200px;
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
.ui-drop-item {
|
||||
font-size: .75rem !important;
|
||||
padding: 0 0 0 22px !important;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.ui-drop-item::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
border-radius: 6px;
|
||||
top: calc(50% - 6px);
|
||||
left: 6px;
|
||||
background-color: var(--color);
|
||||
}
|
||||
</style>
|
||||
</div>
|
@ -81,7 +81,7 @@ function filterSource(searchkeys, textkey, key, source) {
|
||||
return source;
|
||||
}
|
||||
|
||||
class Dropdown {
|
||||
export class Dropdown {
|
||||
#options;
|
||||
|
||||
#wrapper;
|
||||
@ -412,7 +412,7 @@ class Dropdown {
|
||||
createCheckbox({
|
||||
label: r('allItem', '( All )'),
|
||||
checked: allchecked,
|
||||
customerAttributes: { 'isall': '1' },
|
||||
customAttributes: { 'isall': '1' },
|
||||
onchange: e => this.#triggerselect(e.target)
|
||||
})
|
||||
)
|
||||
@ -444,7 +444,7 @@ class Dropdown {
|
||||
const box = createCheckbox({
|
||||
label,
|
||||
checked: allchecked || selected,
|
||||
customerAttributes: {
|
||||
customAttributes: {
|
||||
'class': 'dataitem',
|
||||
'data-value': val
|
||||
},
|
||||
@ -527,6 +527,4 @@ class Dropdown {
|
||||
}
|
||||
return dom;
|
||||
}
|
||||
}
|
||||
|
||||
export default Dropdown;
|
||||
}
|
61
lib/ui/grid/column.d.ts
vendored
Normal file
61
lib/ui/grid/column.d.ts
vendored
Normal file
@ -0,0 +1,61 @@
|
||||
import { Grid, GridItem, GridSourceItem } from "./grid";
|
||||
import { DropdownOptions } from "../dropdown";
|
||||
|
||||
interface GridColumnType {
|
||||
0: "Common";
|
||||
1: "Input";
|
||||
2: "Dropdown";
|
||||
3: "Checkbox";
|
||||
4: "Icon";
|
||||
5: "Text";
|
||||
}
|
||||
|
||||
export interface GridColumnDefinition {
|
||||
key?: string;
|
||||
type?: keyof GridColumnType | typeof GridColumn;
|
||||
caption?: string;
|
||||
width?: Number;
|
||||
align?: "left" | "center" | "right";
|
||||
enabled?: boolean | string | ((item: GridItem | any) => boolean);
|
||||
css?: { [key: string]: string };
|
||||
styleFilter?: (item: GridItem | any) => { [key: string]: string };
|
||||
textStyle?: { [key: string]: string };
|
||||
visible?: boolean;
|
||||
resizable?: boolean;
|
||||
sortable?: boolean;
|
||||
orderable?: boolean;
|
||||
allcheck?: boolean;
|
||||
events?: { [event: string]: any };
|
||||
attrs?: { [key: string]: string } | ((item: GridItem | any) => { [key: string]: string });
|
||||
allowFilter?: boolean;
|
||||
filter?: (item: GridItem | any) => any;
|
||||
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> | Promise<Array<GridSourceItem>>);
|
||||
iconType?: string;
|
||||
className?: string | ((item: GridItem | any) => string);
|
||||
text?: string;
|
||||
tooltip?: string;
|
||||
onallchecked?: (this: Grid, col: GridColumnDefinition, flag: boolean) => void;
|
||||
onchanged?: (this: Grid, item: GridItem | any, value: boolean | any) => void;
|
||||
}
|
||||
|
||||
export class GridColumn {
|
||||
create(): HTMLElement;
|
||||
createEdit(trigger: (e: any) => void, col: GridColumnDefinition, body: HTMLElement): HTMLElement;
|
||||
setValue(element: HTMLElement, val: any, item: GridItem | any, col: GridColumnDefinition): void;
|
||||
getValue(e: any): any;
|
||||
setStyle(element: HTMLElement, style: { [key: string]: string }): void;
|
||||
setEnabled(element: HTMLElement, enabled?: boolean): void;
|
||||
}
|
||||
|
||||
export class GridInputColumn extends GridColumn { }
|
||||
|
||||
export class GridTextColumn extends GridInputColumn { }
|
||||
|
||||
export class GridDropdownColumn extends GridColumn { }
|
||||
|
||||
export class GridCheckboxColumn extends GridColumn { }
|
||||
|
||||
export class GridIconColumn extends GridColumn { }
|
@ -4,9 +4,9 @@ import { createElement } from "../../functions";
|
||||
import { createIcon } from "../icon";
|
||||
import { createCheckbox } from "../checkbox";
|
||||
// import { setTooltip } from "../tooltip";
|
||||
import Dropdown from "../dropdown";
|
||||
import { Dropdown } from "../dropdown";
|
||||
|
||||
class GridColumn {
|
||||
export class GridColumn {
|
||||
static create() {
|
||||
return createElement('span');
|
||||
}
|
||||
@ -29,7 +29,7 @@ class GridColumn {
|
||||
}
|
||||
}
|
||||
|
||||
class GridInputColumn extends GridColumn {
|
||||
export class GridInputColumn extends GridColumn {
|
||||
static get editing() { return true };
|
||||
|
||||
static createEdit(trigger, col, _parent, vals) {
|
||||
@ -66,7 +66,7 @@ class GridInputColumn extends GridColumn {
|
||||
}
|
||||
}
|
||||
|
||||
class GridTextColumn extends GridInputColumn {
|
||||
export class GridTextColumn extends GridInputColumn {
|
||||
static createEdit(trigger, col, _parent, vals) {
|
||||
const input = createElement('textarea');
|
||||
if (typeof trigger === 'function') {
|
||||
@ -100,7 +100,7 @@ class GridTextColumn extends GridInputColumn {
|
||||
|
||||
const SymbolDropdown = Symbol.for('ui-dropdown');
|
||||
|
||||
class GridDropdownColumn extends GridColumn {
|
||||
export class GridDropdownColumn extends GridColumn {
|
||||
static createEdit(trigger, col, parent) {
|
||||
const drop = new Dropdown({ ...col.dropOptions, parent });
|
||||
drop.onselected = trigger;
|
||||
@ -179,7 +179,7 @@ class GridDropdownColumn extends GridColumn {
|
||||
}
|
||||
}
|
||||
|
||||
class GridCheckboxColumn extends GridColumn {
|
||||
export class GridCheckboxColumn extends GridColumn {
|
||||
static createEdit(trigger) {
|
||||
const check = createCheckbox({
|
||||
onchange: typeof trigger === 'function' ? trigger : null
|
||||
@ -199,7 +199,7 @@ class GridCheckboxColumn extends GridColumn {
|
||||
}
|
||||
}
|
||||
|
||||
class GridIconColumn extends GridColumn {
|
||||
export class GridIconColumn extends GridColumn {
|
||||
static create() { return createElement('span', 'col-icon') }
|
||||
|
||||
static setValue(element, val, item, col, grid) {
|
||||
@ -235,13 +235,4 @@ class GridIconColumn extends GridColumn {
|
||||
element.classList.remove('disabled');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export {
|
||||
GridColumn,
|
||||
GridInputColumn,
|
||||
GridTextColumn,
|
||||
GridDropdownColumn,
|
||||
GridCheckboxColumn,
|
||||
GridIconColumn
|
||||
}
|
87
lib/ui/grid/grid.d.ts
vendored
87
lib/ui/grid/grid.d.ts
vendored
@ -1,64 +1,15 @@
|
||||
import { DropdownOptions } from "../dropdown";
|
||||
import { GridColumnDefinition } from "./column"
|
||||
|
||||
interface GridItem {
|
||||
export interface GridItem {
|
||||
value: any;
|
||||
displayValue: string;
|
||||
}
|
||||
|
||||
interface GridSourceItem {
|
||||
export interface GridSourceItem {
|
||||
value: string;
|
||||
text: string;
|
||||
}
|
||||
|
||||
declare var GridColumn: {
|
||||
create(): HTMLElement;
|
||||
createEdit(trigger: (e: any) => void, col: GridColumnDefinition, body: HTMLElement): HTMLElement;
|
||||
setValue(element: HTMLElement, val: any, item: GridItem | any, col: GridColumnDefinition): void;
|
||||
getValue(e: any): any;
|
||||
setStyle(element: HTMLElement, style: { [key: string]: string }): void;
|
||||
setEnabled(element: HTMLElement, enabled?: boolean): void;
|
||||
}
|
||||
|
||||
interface GridColumnType {
|
||||
0: "Common";
|
||||
1: "Input";
|
||||
2: "Dropdown";
|
||||
3: "Checkbox";
|
||||
4: "Icon";
|
||||
5: "Text";
|
||||
}
|
||||
|
||||
interface GridColumnDefinition {
|
||||
key?: string;
|
||||
type?: keyof GridColumnType | typeof GridColumn;
|
||||
caption?: string;
|
||||
width?: Number;
|
||||
align?: "left" | "center" | "right";
|
||||
enabled?: boolean | string | ((item: GridItem | any) => boolean);
|
||||
css?: { [key: string]: string };
|
||||
styleFilter?: (item: GridItem | any) => { [key: string]: string };
|
||||
textStyle?: { [key: string]: string };
|
||||
visible?: boolean;
|
||||
resizable?: boolean;
|
||||
sortable?: boolean;
|
||||
orderable?: boolean;
|
||||
allcheck?: boolean;
|
||||
events?: { [event: string]: any };
|
||||
attrs?: { [key: string]: string } | ((item: GridItem | any) => { [key: string]: string });
|
||||
allowFilter?: boolean;
|
||||
filter?: (item: GridItem | any) => any;
|
||||
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> | Promise<Array<GridSourceItem>>);
|
||||
iconType?: string;
|
||||
className?: string | ((item: GridItem | any) => string);
|
||||
text?: string;
|
||||
tooltip?: string;
|
||||
onallchecked?: (this: Grid, col: GridColumnDefinition, flag: boolean) => void;
|
||||
onchanged?: (this: Grid, item: GridItem | any, value: boolean | any) => void;
|
||||
}
|
||||
|
||||
interface GridColumnDirection {
|
||||
[-1]: Number,
|
||||
1: Number
|
||||
@ -70,7 +21,17 @@ interface GridColumnColumnEventMap {
|
||||
"sort": string
|
||||
}
|
||||
|
||||
interface Grid {
|
||||
export class Grid {
|
||||
static ColumnTypes: {
|
||||
Common: 0,
|
||||
Input: 1,
|
||||
Dropdown: 2,
|
||||
Checkbox: 3,
|
||||
Icon: 4,
|
||||
Text: 5,
|
||||
isCheckbox(type: Number): boolean;
|
||||
};
|
||||
|
||||
columns: Array<GridColumnDefinition>;
|
||||
langs?: { all: string, ok: string, reset: string };
|
||||
virtualCount?: Number;
|
||||
@ -88,6 +49,8 @@ interface Grid {
|
||||
sortIndex?: Number;
|
||||
sortDirection?: keyof GridColumnDirection;
|
||||
|
||||
constructor(container: HTMLElement);
|
||||
|
||||
willSelect?: (index: Number, colIndex: Number) => boolean;
|
||||
selectedRowChanged?: (index?: Number) => void;
|
||||
cellDblClicked?: (index: Number, colIndex: Number) => void;
|
||||
@ -115,20 +78,4 @@ interface Grid {
|
||||
refresh(): void;
|
||||
resetChange(): void;
|
||||
sortColumn(reload?: boolean): void;
|
||||
}
|
||||
|
||||
declare var Grid: {
|
||||
ColumnTypes: {
|
||||
Common: 0,
|
||||
Input: 1,
|
||||
Dropdown: 2,
|
||||
Checkbox: 3,
|
||||
Icon: 4,
|
||||
Text: 5,
|
||||
isCheckbox(type: Number): boolean;
|
||||
};
|
||||
GridColumn: typeof GridColumn;
|
||||
new(container: HTMLElement): Grid;
|
||||
}
|
||||
|
||||
export default Grid;
|
||||
}
|
@ -1,277 +0,0 @@
|
||||
<div>
|
||||
<h1>grid</h1>
|
||||
<hr />
|
||||
<p>
|
||||
创建一个统一样式的滚动列表元素。
|
||||
</p>
|
||||
<h2>constructor</h2>
|
||||
<code>new(container: HTMLElement): Grid</code>
|
||||
<h3>container: HTMLElement</h3>
|
||||
<p>
|
||||
父容器元素,Grid 将创建在此元素之内
|
||||
</p>
|
||||
<h2>init</h2>
|
||||
<code>init(container?: HTMLElement): void</code>
|
||||
<h3>container?: HTMLElement</h3>
|
||||
<p>
|
||||
父容器元素,默认使用构造函数中传递的值
|
||||
</p>
|
||||
<h2>scrollToIndex</h2>
|
||||
<code>scrollToIndex(index: Number): void</code>
|
||||
<h3>index: Number</h3>
|
||||
<p>
|
||||
将滚动至此行
|
||||
</p>
|
||||
<h2>resize</h2>
|
||||
<code>resize(force?: boolean): void</code>
|
||||
<h3>force?: boolean</h3>
|
||||
<p>
|
||||
重新计算大小,参数表示是否强制重载
|
||||
</p>
|
||||
<h2>reload</h2>
|
||||
<code>reload(): void</code>
|
||||
<p>
|
||||
重载表格元素
|
||||
</p>
|
||||
<h2>refresh</h2>
|
||||
<code>refresh(): void</code>
|
||||
<p>
|
||||
刷新表格单元格值
|
||||
</p>
|
||||
<h2>resetChange</h2>
|
||||
<code>resetChange(): void</code>
|
||||
<p>
|
||||
还原表格修改状态,置为未修改
|
||||
</p>
|
||||
<h2>sortColumn</h2>
|
||||
<code>sortColumn(reload?: boolean): void</code>
|
||||
<h3>reload?: boolean</h3>
|
||||
<p>
|
||||
根据当前设定的排序列排序,参数表示是否重载表格
|
||||
</p>
|
||||
<hr />
|
||||
<h2>sortIndex</h2>
|
||||
<code>sortIndex?: Number</code>
|
||||
<p>
|
||||
排序的列序号
|
||||
</p>
|
||||
<h2>sortDirection</h2>
|
||||
<code>sortDirection?: keyof GridColumnDirection</code>
|
||||
<p>
|
||||
排序的方向,可选值为 <code>-1: desc</code>、<code>1: asc</code>
|
||||
</p>
|
||||
<h2>columns</h2>
|
||||
<code>columns: Array<GridColumnDefinition></code>
|
||||
<p>
|
||||
表格列的定义,结构为
|
||||
<pre>interface GridColumnDefinition {
|
||||
key?: string;
|
||||
type?: keyof GridColumnType | typeof GridColumn;
|
||||
caption?: string;
|
||||
width?: Number;
|
||||
align?: "left" | "center" | "right";
|
||||
enabled?: boolean | string;
|
||||
css?: { [key: string]: string };
|
||||
styleFilter?: (item: GridItem | any) => { [key: string]: string };
|
||||
textStyle?: { [key: string]: string };
|
||||
visible?: boolean;
|
||||
resizable?: boolean;
|
||||
sortable?: boolean;
|
||||
orderable?: boolean;
|
||||
allcheck?: boolean;
|
||||
events?: { [event: string]: any };
|
||||
attrs?: { [key: string]: string } | ((item: GridItem | any) => { [key: string]: string });
|
||||
filter?: (item: GridItem | any) => any;
|
||||
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> | Promise<Array<GridSourceItem>>);
|
||||
iconType?: string;
|
||||
text?: string;
|
||||
tooltip?: string;
|
||||
onallchecked?: (this: Grid, col: GridColumnDefinition, flag: boolean) => void;
|
||||
onchanged?: (this: Grid, item: GridItem | any, value: boolean | any) => void;
|
||||
}</pre>
|
||||
</p>
|
||||
<h3>key</h3>
|
||||
<code>key?: string</code>
|
||||
<p>关键字</p>
|
||||
<h3>type</h3>
|
||||
<code>type?: keyof GridColumnType | typeof GridColumn</code>
|
||||
<p>列类型,可以为 <code>Grid.ColumnTypes</code> 枚举值表示内置的通用、输入、下拉、复选框、图标等类型
|
||||
<pre>declare var ColumnTypes: {
|
||||
Common: 0,
|
||||
Input: 1,
|
||||
Dropdown: 2,
|
||||
Checkbox: 3,
|
||||
Icon: 4,
|
||||
isCheckbox(type: Number): boolean;
|
||||
}</pre>也可以为符合 <code>GridColumn</code> 接口的类或对象表示自定义类型,接口定义如下
|
||||
<pre>interface GridColumn {
|
||||
static create(): HTMLElement;
|
||||
static createEdit(trigger: (e: any) => void, col: GridColumnDefinition, body: HTMLElement): HTMLElement;
|
||||
static setValue(element: HTMLElement, val: any): void;
|
||||
static getValue(e: any): any;
|
||||
static setStyle(element: HTMLElement, style: { [key: string]: string }): void;
|
||||
static setEnabled(element: HTMLElement, enabled?: boolean): void;
|
||||
}</pre>
|
||||
</p>
|
||||
<h4>create</h4>
|
||||
<code>static create(): HTMLElement</code>
|
||||
<p>创建只读状态的单元格元素</p>
|
||||
<h4>createEdit</h4>
|
||||
<code>static createEdit(trigger: (e: any) => void, col: GridColumnDefinition, body: HTMLElement): HTMLElement</code>
|
||||
<p>创建编辑状态的单元格元素</p>
|
||||
<samp>trigger: (e: any) => void</samp>
|
||||
<p>用以触发 Grid 的列修改事件 <code>columnChanged</code> 的函数代理</p>
|
||||
<samp>col: GridColumnDefinition</samp>
|
||||
<p>当前列的定义对象</p>
|
||||
<samp>body: HTMLElement</samp>
|
||||
<p>Grid 正文表格元素</p>
|
||||
<h4>setValue</h4>
|
||||
<code>static setValue(element: HTMLElement, val: any, item: GridItem | any, col: GridColumnDefinition): void</code>
|
||||
<p>设置单元格值时触发的函数</p>
|
||||
<samp>element: HTMLElement</samp>
|
||||
<p>单元格元素</p>
|
||||
<samp>val: any</samp>
|
||||
<p>单元格的值</p>
|
||||
<samp>item: GridItem | any</samp>
|
||||
<p>单元格所在行的数据项</p>
|
||||
<samp>col: GridColumnDefinition</samp>
|
||||
<p>单元格所在列的定义对象</p>
|
||||
<hr />
|
||||
<h2>示例</h2>
|
||||
<pre></pre>
|
||||
<div id="grid-sample"></div>
|
||||
<!-- <div style="height: 80px"></div> -->
|
||||
<script type="text/javascript">
|
||||
!function () {
|
||||
const Grid = window["lib-ui"].Grid;
|
||||
|
||||
const grid = new Grid(document.querySelector('#grid-sample'));
|
||||
|
||||
const statusCol = {
|
||||
...Grid.GridColumn,
|
||||
|
||||
create() {
|
||||
const container = document.createElement('div');
|
||||
return container;
|
||||
},
|
||||
// createEdit() { },
|
||||
setValue(element, val) {
|
||||
element.innerHTML = val;
|
||||
},
|
||||
getValue(element) {
|
||||
return element.innerHTML;
|
||||
},
|
||||
setEnabled(element, enabled) {
|
||||
element.className = enabled === false ? 'disabled' : '';
|
||||
}
|
||||
};
|
||||
|
||||
const iconCol = {
|
||||
create() {
|
||||
const a = document.createElement('a');
|
||||
a.target = '_blank';
|
||||
a.className = 'icon-col';
|
||||
return a;
|
||||
},
|
||||
setValue(element, val, _item, col) {
|
||||
if (col.tooltip) {
|
||||
element.setAttribute('title', col.tooltip);
|
||||
}
|
||||
element.innerText = val;
|
||||
},
|
||||
setEnabled(element, enabled) {
|
||||
element.style.display = enabled === false ? 'none' : '';
|
||||
},
|
||||
setStyle: Grid.GridColumn.setStyle
|
||||
};
|
||||
|
||||
// grid.readonly = true;
|
||||
grid.allowHtml = true;
|
||||
grid.height = 0;
|
||||
grid.columns = [
|
||||
{ key: 'c1', caption: 'column 122222222311111111111111111' },
|
||||
{ key: 'c2', caption: '选择', allcheck: true, type: Grid.ColumnTypes.Checkbox, enabled: 'enabled' },
|
||||
{
|
||||
key: 'c2a',
|
||||
caption: '下拉',
|
||||
type: Grid.ColumnTypes.Dropdown,
|
||||
allowFilter: true,
|
||||
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)
|
||||
},
|
||||
{
|
||||
key: 'c2b',
|
||||
caption: '自定义',
|
||||
type: statusCol,
|
||||
enabled: 'enabled'
|
||||
},
|
||||
{ key: 'c2c', caption: '多行编辑', type: Grid.ColumnTypes.Text, enabled: 'enabled' },
|
||||
{ key: 'c3', caption: 'column 3', width: 90, align: 'right' },
|
||||
{ key: 'c4', caption: 'Note', type: Grid.ColumnTypes.Input },
|
||||
{
|
||||
key: 'c5',
|
||||
type: Grid.ColumnTypes.Icon,
|
||||
enabled: 'enabled',
|
||||
text: 'times',
|
||||
tooltip: 'Delete',
|
||||
events: {
|
||||
onclick() { console.log('deleted', this) }
|
||||
}
|
||||
},
|
||||
{
|
||||
key: 'c6',
|
||||
type: iconCol,
|
||||
enabled: 'enabled',
|
||||
text: '\uf044',
|
||||
tooltip: 'Edit',
|
||||
events: {
|
||||
onclick() { alert(`Edit, ${this.c1}`) }
|
||||
}
|
||||
}
|
||||
];
|
||||
grid.columnChanged = (type, index, value) => console.log(`column (${index}), ${type}, ${value}`);
|
||||
grid.cellClicked = (rId, cId) => console.log(`row (${rId}), column (${cId}) clicked.`);
|
||||
grid.rowDblClicked = (rId) => console.log(`row (${rId}) double clicked.`);
|
||||
grid.cellDblClicked = (rId, cId) => console.log(`row (${rId}), column (${cId}) double clicked.`);
|
||||
grid.init();
|
||||
grid.source = [
|
||||
{ c1: 'abc', c2: true, c2a: 'off', c2b: '<font style="color: red; margin-left: 8px">red</font>', c2c: 'multiple lines\nline2\nline3\n\nline5', c3: 12345, c4: 'another note' },
|
||||
{ c1: 'abc2bbbbaaaaa', c2: false, c2a: 'pending', c2b: '<b style="margin-left: 8px">bold</b>', c3: 1225, c4: 'Note note this is note' },
|
||||
{ c1: 'type', c2: false, c2a: 'broken', c2c: 'multiple lines\nline2\nline3\n\nline5', c3: 121111 },
|
||||
{ c1: 'diff', c2: true, c2a: 'running', c3: 124445555555555555 },
|
||||
{ c1: 'diff', c2: true, c2a: 'running', c3: 12499, enabled: false },
|
||||
{ c1: 'diff', c2: true, c2a: 'off', c3: 1244445 }
|
||||
];
|
||||
|
||||
window.grid = grid;
|
||||
}();
|
||||
</script>
|
||||
<style type="text/css">
|
||||
#grid-sample {
|
||||
height: 500px;
|
||||
}
|
||||
|
||||
#grid-sample>.ui-grid {
|
||||
height: 100%;
|
||||
}
|
||||
</style>
|
||||
</div>
|
@ -51,7 +51,7 @@ const ColumnTypes = {
|
||||
5: GridTextColumn
|
||||
};
|
||||
|
||||
class Grid {
|
||||
export class Grid {
|
||||
#source;
|
||||
#currentSource;
|
||||
#parent;
|
||||
@ -110,8 +110,6 @@ class Grid {
|
||||
isCheckbox(type) { return type === 3 }
|
||||
};
|
||||
|
||||
static GridColumn = GridColumn;
|
||||
|
||||
constructor(container) {
|
||||
this.#parent = container;
|
||||
}
|
||||
@ -412,6 +410,11 @@ class Grid {
|
||||
}
|
||||
}
|
||||
|
||||
clearHeaderCheckbox() {
|
||||
const boxes = this.#refs.header.querySelectorAll('.ui-check-wrapper>input');
|
||||
boxes.forEach(box => box.checked = false);
|
||||
}
|
||||
|
||||
#createHeader() {
|
||||
const thead = createElement('table', 'ui-grid-header');
|
||||
if (this.headerVisible === false) {
|
||||
@ -1552,6 +1555,4 @@ class Grid {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default Grid;
|
||||
}
|
22
lib/ui/icon.d.ts
vendored
22
lib/ui/icon.d.ts
vendored
@ -1,3 +1,25 @@
|
||||
/**
|
||||
* 创建矢量图标元素
|
||||
* @param type 样式分类,可以是 ['`fa-light`', '`fa-regular`', '`fa-solid`'] 其中之一
|
||||
* @param id 图标 id
|
||||
* @param style 样式表对象
|
||||
*/
|
||||
export function createIcon(type: string, id: string, style?: { [key: string]: string }): SVGSVGElement
|
||||
/**
|
||||
* 修改矢量图标
|
||||
* @param svg 矢量图标元素
|
||||
* @param type 样式分类,见 { @link createIcon } 第一个参数
|
||||
* @param id 图标 id,见 { @link createIcon } 第二个参数
|
||||
*/
|
||||
export function changeIcon(svg: SVGSVGElement, type: string, id: string): SVGSVGElement
|
||||
/**
|
||||
* 解析容器元素内符合条件的子元素为矢量图标元素
|
||||
* @param container 容器元素
|
||||
* @example
|
||||
* ```
|
||||
* <svg data-id="user" data-type="fa-solid"></svg>
|
||||
* ```
|
||||
* - `data-id`: 见 { @link createIcon } 第二个参数
|
||||
* - `data-type`: 见 { @link createIcon } 第一个参数
|
||||
*/
|
||||
export function resolveIcon(container: HTMLElement): HTMLElement
|
@ -1,50 +0,0 @@
|
||||
<div>
|
||||
<h1>icon</h1>
|
||||
<hr />
|
||||
<p>
|
||||
创建一个 svg 矢量图标元素,或者解析转换页面上特定类型的 svg
|
||||
标签到指定的图标元素。
|
||||
</p>
|
||||
<h2>createIcon</h2>
|
||||
<code>function createIcon(type: string, id: string, style?: { [key: string]: string }): SVGElement</code>
|
||||
<h3>type: string</h3>
|
||||
<p>
|
||||
图标类型,可选值目前有 <code>fa-regular</code>、<code>fa-light</code>、<code>fa-solid</code>
|
||||
</p>
|
||||
<h3>id: string</h3>
|
||||
<p>
|
||||
图形 id,例如
|
||||
<code>user-edit</code>、<code>address-card</code>、<code>frog</code>……
|
||||
</p>
|
||||
<h3>style?: { [key: string]: string }</h3>
|
||||
<p>
|
||||
自定义样式的对象
|
||||
</p>
|
||||
<h2>resolveIcon</h2>
|
||||
<code>function resolveIcon(container: HTMLElement): HTMLElement</code>
|
||||
<h3>container: HTMLElement</h3>
|
||||
<p>
|
||||
将把此 HTML 元素下的所有 <code>svg[data-id]</code> 元素解析为图标,<code>[data-id]</code>
|
||||
同上述 <code>id: string</code>,<code>[data-type]</code> 同上述
|
||||
<code>type: string</code>
|
||||
</p>
|
||||
<hr />
|
||||
<h2>示例</h2>
|
||||
<pre><div id="icon-sample">
|
||||
<svg data-id="address-card" data-type="fa-regular"></svg>
|
||||
<svg data-id="user-edit" data-type="fa-light"></svg>
|
||||
<svg data-id="frog" data-type="fa-solid"></svg>
|
||||
</div>
|
||||
|
||||
<script type="text/javascript">
|
||||
window["lib-ui"].resolveIcon(document.querySelector("#icon-sample"));
|
||||
</script></pre>
|
||||
<div id="icon-sample">
|
||||
<svg data-id="address-card" data-type="fa-regular"></svg>
|
||||
<svg data-id="user-edit" data-type="fa-light"></svg>
|
||||
<svg data-id="frog" data-type="fa-solid"></svg>
|
||||
</div>
|
||||
<script type="text/javascript">
|
||||
window["lib-ui"].resolveIcon(document.querySelector("#icon-sample"));
|
||||
</script>
|
||||
</div>
|
49
lib/ui/media.d.ts
vendored
Normal file
49
lib/ui/media.d.ts
vendored
Normal file
@ -0,0 +1,49 @@
|
||||
/**
|
||||
* 创建媒体图片元素
|
||||
* @param url 图片链接地址
|
||||
* @returns 返回一个 A 标签元素
|
||||
*/
|
||||
export function createPicture(url: string): HTMLAnchorElement
|
||||
|
||||
/**
|
||||
* 创建一个音频播放元素
|
||||
* @param mime 音频类型,如 `audio/amr`、`audio/ogg`
|
||||
* @param url 音频 url
|
||||
*/
|
||||
export function createAudio(mime: string, url: string): HTMLAudioElement | HTMLDivElement
|
||||
|
||||
/**
|
||||
* 创建一个视频播放元素
|
||||
* @param url 视频 url
|
||||
*/
|
||||
export function createVideo(url: string): HTMLVideoElement
|
||||
|
||||
/**
|
||||
* 创建一个 pdf 文件元素
|
||||
* @param url pdf 文件 url
|
||||
*/
|
||||
export function createPdf(url: string): HTMLDivElement
|
||||
|
||||
/**
|
||||
* 创建一个 smil 文件元素
|
||||
* @param url smil 文件 url
|
||||
*/
|
||||
export function createSmilefile(url: string): HTMLDivElement
|
||||
|
||||
/**
|
||||
* 创建一个 vcard 文件元素
|
||||
* @param url vcard 文件 url
|
||||
*/
|
||||
export function createVcard(url: string): HTMLDivElement
|
||||
|
||||
/**
|
||||
* 创建一个不支持的视频文件元素
|
||||
* @param url 视频 url
|
||||
*/
|
||||
export function createVideofile(url: string): HTMLDivElement
|
||||
|
||||
/**
|
||||
* 创建一个通用文件元素
|
||||
* @param url 文件 url
|
||||
*/
|
||||
export function createFile(url: string): HTMLDivElement
|
160
lib/ui/media.js
Normal file
160
lib/ui/media.js
Normal file
@ -0,0 +1,160 @@
|
||||
import "./css/media.scss";
|
||||
import { createElement } from "../functions";
|
||||
import { createIcon } from "./icon";
|
||||
import { get } from "../utility";
|
||||
|
||||
export function createPicture(url) {
|
||||
return createElement('a', a => {
|
||||
a.className = 'ui-media-picture';
|
||||
a.target = '_blank';
|
||||
a.href = url;
|
||||
},
|
||||
createElement('img', img => {
|
||||
img.src = url;
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
function readBlob(blob) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const reader = new FileReader();
|
||||
reader.onload = e => {
|
||||
const data = new Uint8Array(e.target.result);
|
||||
resolve(data);
|
||||
};
|
||||
reader.onerror = reject;
|
||||
reader.readAsArrayBuffer(blob);
|
||||
});
|
||||
}
|
||||
|
||||
function playAmrArray(array) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const samples = AMR.decode(array);
|
||||
if (samples != null) {
|
||||
resolve(samples);
|
||||
} else {
|
||||
reject();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function playPcm(samples, ended) {
|
||||
return new Promise(resolve => {
|
||||
const ctx = new AudioContext();
|
||||
ctx.addEventListener('statechange', () => resolve(ctx));
|
||||
const source = ctx.createBufferSource();
|
||||
if (typeof ended === 'function') {
|
||||
source.addEventListener('ended', () => ended(ctx));
|
||||
}
|
||||
const buffer = ctx.createBuffer(1, samples.length, 8000);
|
||||
if (typeof buffer.copyToChannel === 'function') {
|
||||
buffer.copyToChannel(samples, 0, 0);
|
||||
} else {
|
||||
const channelBuffer = buffer.getChannelData(0);
|
||||
channelBuffer.set(samples);
|
||||
}
|
||||
source.buffer = buffer;
|
||||
source.connect(ctx.destination);
|
||||
ctx.duration = buffer.duration;
|
||||
source.start();
|
||||
// resolve(ctx);
|
||||
});
|
||||
}
|
||||
|
||||
function getTimeLabel(time) {
|
||||
time = Math.round(time);
|
||||
return String(Math.floor(time / 60)).padStart(2, '0') + ':' + String(time % 60).padStart(2, '0');
|
||||
}
|
||||
|
||||
export function createAudio(mime, url) {
|
||||
if (mime === 'audio/amr' && typeof AMR !== 'undefined') {
|
||||
const timestamp = createElement('span', 'ui-media-timestamp');
|
||||
timestamp.textContent = '00:00 / 00:00';
|
||||
let context;
|
||||
let timer;
|
||||
return createElement('div', 'ui-media-audio',
|
||||
createElement('button', button => {
|
||||
button.addEventListener('click', () => {
|
||||
if (context != null) {
|
||||
clearInterval(timer);
|
||||
context.close();
|
||||
context = null;
|
||||
timestamp.textContent = '00:00 / 00:00';
|
||||
button.replaceChildren(createIcon('fa-solid', 'play'));
|
||||
return;
|
||||
}
|
||||
get(url, {
|
||||
accept: mime
|
||||
})
|
||||
.then(r => r.blob())
|
||||
.then(r => readBlob(r))
|
||||
.then(r => playAmrArray(r))
|
||||
.then(r => playPcm(r, ctx => {
|
||||
context = null;
|
||||
clearInterval(timer);
|
||||
timestamp.textContent = '00:00 / ' + getTimeLabel(ctx.duration);
|
||||
button.replaceChildren(createIcon('fa-solid', 'play'));
|
||||
}))
|
||||
.then(ctx => {
|
||||
context = ctx;
|
||||
button.replaceChildren(createIcon('fa-solid', 'stop'));
|
||||
const total = getTimeLabel(ctx.duration);
|
||||
const refresh = () => timestamp.textContent = getTimeLabel(ctx.currentTime) + ' / ' + total;
|
||||
refresh();
|
||||
timer = setInterval(refresh, 500);
|
||||
})
|
||||
.catch(e => {
|
||||
clearInterval(timer);
|
||||
console.error(e);
|
||||
});
|
||||
});
|
||||
},
|
||||
createIcon('fa-solid', 'play')
|
||||
),
|
||||
timestamp
|
||||
);
|
||||
}
|
||||
return createElement('audio', audio => {
|
||||
audio.src = url;
|
||||
audio.controls = true;
|
||||
});
|
||||
}
|
||||
|
||||
export function createVideo(url) {
|
||||
return createElement('video', video => {
|
||||
video.className = 'ui-media-video';
|
||||
video.src = url;
|
||||
video.controls = true;
|
||||
});
|
||||
}
|
||||
|
||||
function createFileElement(url, icon) {
|
||||
return createElement('div', 'ui-media-file',
|
||||
createIcon('fa-solid', icon),
|
||||
createElement('a', a => {
|
||||
a.target = '_blank';
|
||||
a.href = url;
|
||||
a.innerText = 'Click here to view the file';
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
export function createPdf(url) {
|
||||
return createFileElement(url, 'file-pdf');
|
||||
}
|
||||
|
||||
export function createSmilefile(url) {
|
||||
return createFileElement(url, 'smile');
|
||||
}
|
||||
|
||||
export function createVcard(url) {
|
||||
return createFileElement(url, 'id-card');
|
||||
}
|
||||
|
||||
export function createVideofile(url) {
|
||||
return createFileElement(url, 'photo-video');
|
||||
}
|
||||
|
||||
export function createFile(url) {
|
||||
return createFileElement(url, 'file-alt');
|
||||
}
|
94
lib/ui/popup.d.ts
vendored
Normal file
94
lib/ui/popup.d.ts
vendored
Normal file
@ -0,0 +1,94 @@
|
||||
/**
|
||||
* 弹出框选项
|
||||
*/
|
||||
interface PopupOptions {
|
||||
/** 弹出框内容,可以是文本或者 html 元素 */
|
||||
content: string | HTMLElement;
|
||||
/** 弹出框标题,可以是文本或者 html 元素 */
|
||||
title: string | HTMLElement;
|
||||
|
||||
/** 是否包含遮罩层,默认为 `true` */
|
||||
mask?: boolean;
|
||||
/** 遮罩层 z-index */
|
||||
zIndex?: number;
|
||||
/** 是否在获取焦点时修改 z-index */
|
||||
changeZIndex?: boolean;
|
||||
/** 是否允许移动 */
|
||||
movable?: boolean;
|
||||
/** 是否允许修改大小 */
|
||||
resizable?: boolean;
|
||||
/** 最小宽度,默认 200 */
|
||||
minWidth?: number;
|
||||
/** 最小高度,默认 200 */
|
||||
minHeight?: number;
|
||||
/** 是否允许弹出框收折 */
|
||||
collapsable?: boolean;
|
||||
/** 弹出框的按钮定义集合 */
|
||||
buttons?: PopupButton[];
|
||||
|
||||
/**
|
||||
* 遮罩层显示或者隐藏时的回调函数
|
||||
* @param this 当前 popup 对象
|
||||
* @param masking 显示或隐藏遮罩层
|
||||
*/
|
||||
onMasking?: (this: Popup, masking: boolean) => void;
|
||||
/**
|
||||
* 移动结束时的回调函数
|
||||
* @param this 当前 popup 对象
|
||||
*/
|
||||
onMoveEnded?: (this: Popup) => void;
|
||||
/**
|
||||
* 修改大小开始时的回调函数
|
||||
* @param this 当前 popup 对象
|
||||
*/
|
||||
onResizeStarted?: (this: Popup) => void;
|
||||
/**
|
||||
* 修改大小中的回调函数
|
||||
* @param this 当前 popup 对象
|
||||
* @param x 坐标 x
|
||||
* @param y 坐标 y
|
||||
* @param width 当前宽度
|
||||
* @param height 当前高度
|
||||
*/
|
||||
onResizing?: (this: Popup, x: number, y: number, width: number, height: number) => void;
|
||||
/**
|
||||
* 修改大小结束时的回调函数
|
||||
* @param this 当前 popup 对象
|
||||
*/
|
||||
onResizeEnded?: (this: Popup) => void;
|
||||
/**
|
||||
* 弹出框关闭时的回调函数
|
||||
*/
|
||||
resolve?: () => void;
|
||||
}
|
||||
|
||||
export class Popup {
|
||||
constructor(opts?: PopupOptions);
|
||||
}
|
||||
|
||||
interface PopupButton {
|
||||
tabIndex: number;
|
||||
key: string;
|
||||
text: string;
|
||||
trigger: (this: Popup) => boolean | Promise<boolean>;
|
||||
}
|
||||
|
||||
interface PopupIconTypes {
|
||||
'info': 'info-circle';
|
||||
'information': 'info-circle';
|
||||
'warn': 'exclamation-triangle';
|
||||
'warning': 'exclamation-triangle';
|
||||
'question': 'question-circle';
|
||||
'error': 'times-circle';
|
||||
}
|
||||
|
||||
interface PopupButtonResult {
|
||||
key: string;
|
||||
popup: Popup;
|
||||
}
|
||||
|
||||
export function createPopup(title: string | HTMLElement, content: string | HTMLElement, ...buttons: PopupButton[]): 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>
|
@ -1,51 +0,0 @@
|
||||
<div>
|
||||
<h1>popup</h1>
|
||||
<hr />
|
||||
<p>
|
||||
创建弹出窗口。
|
||||
</p>
|
||||
<hr />
|
||||
<h2>示例</h2>
|
||||
<div id="popup-sample">
|
||||
<button id="button-popup">Popup</button>
|
||||
</div>
|
||||
<script type="text/javascript">
|
||||
const ui = window['lib-ui'];
|
||||
|
||||
document.querySelector('#button-popup').addEventListener('click', () => {
|
||||
const popup = new ui.Popup({
|
||||
title: 'title long title, looooooong title, looooooooooooooooooooooooong ~',
|
||||
content: ui.createElement('div', 'class-content',
|
||||
ui.createElement('span', span => span.innerText = 'Text content.')
|
||||
),
|
||||
mask: false,
|
||||
// movable: false,
|
||||
resizable: true,
|
||||
collapsable: true,
|
||||
minWidth: 210,
|
||||
minHeight: 200,
|
||||
buttons: [
|
||||
{
|
||||
text: 'Loading', trigger: p => {
|
||||
p.loading = true;
|
||||
setTimeout(() => p.loading = false, 1000);
|
||||
return false;
|
||||
}
|
||||
},
|
||||
{ text: 'OK' }
|
||||
],
|
||||
onMoveEnded: () => console.log('move ended.', popup.rect),
|
||||
onResizeEnded: () => console.log('resize ended.', popup.rect)
|
||||
});
|
||||
popup.show();
|
||||
window.popup = popup;
|
||||
});
|
||||
</script>
|
||||
<style type="text/css">
|
||||
.ui-popup-mask .ui-popup-container {
|
||||
min-width: 210px;
|
||||
min-height: 200px;
|
||||
max-width: unset;
|
||||
}
|
||||
</style>
|
||||
</div>
|
@ -38,7 +38,7 @@ function trimPx(px) {
|
||||
return px;
|
||||
}
|
||||
|
||||
class Popup {
|
||||
export class Popup {
|
||||
#mask;
|
||||
#option;
|
||||
#bounds;
|
||||
@ -116,6 +116,9 @@ class Popup {
|
||||
if (typeof this.#option.onMasking === 'function') {
|
||||
this.#option.onMasking.call(this, false);
|
||||
}
|
||||
if (typeof this.#option.resolve === 'function') {
|
||||
this.#option.resolve();
|
||||
}
|
||||
}
|
||||
|
||||
create() {
|
||||
@ -124,7 +127,7 @@ class Popup {
|
||||
if (option.mask === false) {
|
||||
mask.classList.add('ui-popup-transparent');
|
||||
} else if (typeof option.onMasking === 'function') {
|
||||
this.#option.onMasking.call(this, true);
|
||||
option.onMasking.call(this, true);
|
||||
}
|
||||
if (!isNaN(option.zIndex)) {
|
||||
mask.style.zIndex = String(option.zIndex);
|
||||
@ -478,8 +481,6 @@ class Popup {
|
||||
}
|
||||
}
|
||||
|
||||
export default Popup;
|
||||
|
||||
export function createPopup(title, content, ...buttons) {
|
||||
const popup = new Popup({
|
||||
title,
|
||||
@ -506,6 +507,7 @@ export function showAlert(title, message, iconType = 'info', parent = document.b
|
||||
createIcon('fa-solid', iconTypes[iconType] ?? 'info-circle'),
|
||||
createElement('span', span => span.innerText = message)
|
||||
),
|
||||
resolve,
|
||||
buttons: [
|
||||
{ text: r('ok', 'OK'), trigger: resolve }
|
||||
]
|
||||
@ -529,6 +531,7 @@ export function showConfirm(title, content, buttons, iconType = 'question', pare
|
||||
const popup = new Popup({
|
||||
title,
|
||||
content: wrapper,
|
||||
resolve,
|
||||
buttons: buttons?.map(b => {
|
||||
return {
|
||||
text: b.text,
|
||||
|
16
lib/ui/tooltip.d.ts
vendored
16
lib/ui/tooltip.d.ts
vendored
@ -1,2 +1,18 @@
|
||||
/**
|
||||
* 为元素设置一个 tooltip
|
||||
* @param container 要设置 tooltip 的容器元素
|
||||
* @param content 提示内容,可以是字符串也可以是 html 元素
|
||||
* @param flag 是否仅在元素内容没有呈现完全时显示 tooltip
|
||||
* @param parent 呈现在哪个父元素上,默认添加在 `container` 末端
|
||||
*/
|
||||
export function setTooltip(container: HTMLElement, content: string | HTMLElement, flag?: boolean, parent?: HTMLElement): HTMLElement
|
||||
|
||||
/**
|
||||
* 解析容器元素内符合条件的子元素,为其添加 tooltip
|
||||
* @param container 容器元素
|
||||
* @example
|
||||
* ```
|
||||
* <span title="tooltip text"></span>
|
||||
* ```
|
||||
*/
|
||||
export function resolveTooltip(container?: HTMLElement): HTMLElement
|
@ -1,54 +0,0 @@
|
||||
<div>
|
||||
<h1>tooltip</h1>
|
||||
<hr />
|
||||
<p>
|
||||
给某个元素或者页面上含有 title 属性的元素设置一个统一样式的 tooltip。
|
||||
</p>
|
||||
<h2>setTooltip</h2>
|
||||
<code>function setTooltip(container: HTMLElement, content: string | HTMLElement, flag?: boolean, parent?: HTMLElement): void</code>
|
||||
<h3>container: HTMLElement</h3>
|
||||
<p>
|
||||
要设置 tooltip 的元素
|
||||
</p>
|
||||
<h3>content: string | HTMLElement</h3>
|
||||
<p>
|
||||
要设置的 tooltip 内容,允许为字符串或者 HTML 元素
|
||||
</p>
|
||||
<h3>flag?: boolean</h3>
|
||||
<p>
|
||||
是否启用严格模式,只有显示不完整时才显示 tooltip
|
||||
</p>
|
||||
<h3>parent?: HTMLElement</h3>
|
||||
<p>
|
||||
创建在哪个元素内,默认创建在目标元素之内
|
||||
</p>
|
||||
<h2>resolveTooltip</h2>
|
||||
<code>function resolveTooltip(container?: HTMLElement): HTMLElement</code>
|
||||
<h3>container?: HTMLElement</h3>
|
||||
<p>
|
||||
给此元素,为 null 则把 document.body 下的所有含有 title 属性的子元素设置成统一样式的 tooltip
|
||||
</p>
|
||||
<hr />
|
||||
<h2>示例</h2>
|
||||
<pre><div id="tooltip-sample">
|
||||
<!-- 1 -->
|
||||
<blockquote title="From MDN Website">To send an HTTP request, create an XMLHttpRequest object, open a URL, and
|
||||
send the request. After the transaction completes, the object will contain useful information such as the
|
||||
response body and the HTTP status of the result.</blockquote>
|
||||
<!-- 2 -->
|
||||
<button title="Test to send request through XMLHttpRequest.">Test</button>
|
||||
</div>
|
||||
|
||||
<script type="text/javascript">
|
||||
window["lib-ui"].resolveCheckbox(document.querySelector("#checkbox-sample"));
|
||||
</script></pre>
|
||||
<div id="tooltip-sample">
|
||||
<blockquote title="From MDN Website">To send an HTTP request, create an XMLHttpRequest object, open a URL, and
|
||||
send the request. After the transaction completes, the object will contain useful information such as the
|
||||
response body and the HTTP status of the result.</blockquote>
|
||||
<button title="Test to send request through XMLHttpRequest.">Test</button>
|
||||
</div>
|
||||
<script type="text/javascript">
|
||||
window["lib-ui"].resolveTooltip(document.querySelector("#tooltip-sample"));
|
||||
</script>
|
||||
</div>
|
@ -2,7 +2,7 @@ import './css/tooltip.scss';
|
||||
import { createElement } from "../functions";
|
||||
// import { global } from "../utility";
|
||||
|
||||
function setTooltip(container, content, flag = false, parent = null) {
|
||||
export function setTooltip(container, content, flag = false, parent = null) {
|
||||
const isParent = parent instanceof HTMLElement;
|
||||
if (isParent) {
|
||||
const tipid = container.dataset.tipId;
|
||||
@ -73,64 +73,77 @@ function setTooltip(container, content, flag = false, parent = null) {
|
||||
top -= p.scrollTop;
|
||||
p = p.parentElement;
|
||||
}
|
||||
// check overflow
|
||||
let t = c.offsetTop;
|
||||
let l = c.offsetLeft;
|
||||
p = c.offsetParent;
|
||||
let lastWidth = p.clientWidth;
|
||||
let lastHeight = p.clientHeight;
|
||||
while (p != null) {
|
||||
const overflow = window.getComputedStyle(p).overflow;
|
||||
if (overflow !== 'visible') {
|
||||
t -= p.scrollTop;
|
||||
l -= p.scrollLeft;
|
||||
break;
|
||||
}
|
||||
t += p.offsetTop;
|
||||
l += p.offsetLeft;
|
||||
const w = p.clientWidth;
|
||||
if (w < lastWidth) {
|
||||
lastWidth += l;
|
||||
} else {
|
||||
lastWidth = p.clientWidth;
|
||||
}
|
||||
const h = p.clientHeight;
|
||||
if (h < lastHeight) {
|
||||
lastHeight += t;
|
||||
} else {
|
||||
lastHeight = p.clientHeight;
|
||||
}
|
||||
const parent = p.offsetParent;
|
||||
while (p != null && p != parent) {
|
||||
t -= p.scrollTop;
|
||||
l -= p.scrollLeft;
|
||||
p = p.parentElement;
|
||||
}
|
||||
}
|
||||
wrapper.style.display = '';
|
||||
const offsetHeight = wrapper.offsetHeight;
|
||||
if (t - offsetHeight - 14 < 0) {
|
||||
const parentOffsetHeight = c.parentElement.offsetHeight;
|
||||
if (t + parentOffsetHeight + offsetHeight + 10 > lastHeight) {
|
||||
top = t + (parentOffsetHeight - offsetHeight) / 2;
|
||||
if (top + offsetHeight + 1 > lastHeight) {
|
||||
top = lastHeight - offsetHeight - 1;
|
||||
}
|
||||
wrapper.classList.add('ui-tooltip-no');
|
||||
} else {
|
||||
top += parentOffsetHeight + 10;
|
||||
const offsetWidth = wrapper.offsetWidth;
|
||||
if (isParent) {
|
||||
top -= offsetHeight + 14;
|
||||
if (top < -offsetHeight) {
|
||||
top += c.offsetHeight + offsetHeight + 14;
|
||||
wrapper.classList.add('ui-tooltip-down');
|
||||
}
|
||||
left += (c.offsetWidth - offsetWidth) / 2;
|
||||
if (left < 1) {
|
||||
left = 1;
|
||||
}
|
||||
} else {
|
||||
top -= offsetHeight + 14;
|
||||
wrapper.classList.remove('ui-tooltip-down');
|
||||
}
|
||||
const offsetWidth = wrapper.offsetWidth;
|
||||
left += (c.offsetWidth - offsetWidth) / 2;
|
||||
if (l - offsetWidth < 0) {
|
||||
left = 1;
|
||||
} else if (left + offsetWidth + 1 > lastWidth) {
|
||||
left = lastWidth - offsetWidth - 1;
|
||||
// check overflow
|
||||
let t = c.offsetTop;
|
||||
let l = c.offsetLeft;
|
||||
p = c.offsetParent;
|
||||
let lastWidth = p.clientWidth;
|
||||
let lastHeight = p.clientHeight;
|
||||
while (p != null) {
|
||||
const overflow = window.getComputedStyle(p).overflow;
|
||||
if (overflow !== 'visible') {
|
||||
break;
|
||||
}
|
||||
t += p.offsetTop;
|
||||
l += p.offsetLeft;
|
||||
const parent = p.offsetParent;
|
||||
while (p != null) {
|
||||
const w = p.clientWidth;
|
||||
if (w < lastWidth) {
|
||||
lastWidth += l;
|
||||
} else {
|
||||
lastWidth = p.clientWidth;
|
||||
}
|
||||
const h = p.clientHeight;
|
||||
if (h < lastHeight) {
|
||||
lastHeight += t;
|
||||
} else {
|
||||
lastHeight = p.clientHeight;
|
||||
}
|
||||
t -= p.scrollTop;
|
||||
l -= p.scrollLeft;
|
||||
if (p === parent) {
|
||||
break;
|
||||
}
|
||||
p = p.parentElement;
|
||||
}
|
||||
}
|
||||
if (t - offsetHeight - 14 < 0) {
|
||||
const containerOffsetHeight = c.offsetHeight;
|
||||
if (t + containerOffsetHeight + offsetHeight + 14 > lastHeight) {
|
||||
top = t + (containerOffsetHeight - offsetHeight) / 2;
|
||||
if (top + offsetHeight + 1 > lastHeight) {
|
||||
top = lastHeight - offsetHeight - 1;
|
||||
}
|
||||
wrapper.classList.add('ui-tooltip-no');
|
||||
} else {
|
||||
top += containerOffsetHeight + 14;
|
||||
wrapper.classList.add('ui-tooltip-down');
|
||||
}
|
||||
} else {
|
||||
top -= offsetHeight + 14;
|
||||
wrapper.classList.remove('ui-tooltip-down');
|
||||
}
|
||||
left += (c.offsetWidth - offsetWidth) / 2;
|
||||
if (l - offsetWidth < 0) {
|
||||
left = 1;
|
||||
} else if (left + offsetWidth + 1 > lastWidth) {
|
||||
left = lastWidth - offsetWidth - 1;
|
||||
}
|
||||
}
|
||||
// wrapper.style.left = `${left}px`;
|
||||
// wrapper.style.top = `${top}px`;
|
||||
@ -151,7 +164,7 @@ function setTooltip(container, content, flag = false, parent = null) {
|
||||
return container;
|
||||
}
|
||||
|
||||
function resolveTooltip(container = document.body) {
|
||||
export function resolveTooltip(container = document.body) {
|
||||
const tips = container.querySelectorAll('[title]');
|
||||
for (let tip of tips) {
|
||||
const title = tip.getAttribute('title');
|
||||
@ -161,9 +174,4 @@ function resolveTooltip(container = document.body) {
|
||||
}
|
||||
}
|
||||
return container;
|
||||
}
|
||||
|
||||
export {
|
||||
setTooltip,
|
||||
resolveTooltip
|
||||
}
|
Reference in New Issue
Block a user