add document, fix tooltip position issue

This commit is contained in:
2023-03-30 17:26:59 +08:00
parent f5bc42fa20
commit 5406eea20e
15 changed files with 432 additions and 117 deletions

View File

@ -3,10 +3,13 @@ import { createCheckbox, resolveCheckbox } from "./ui/checkbox";
import { setTooltip, resolveTooltip } from "./ui/tooltip";
export {
// icon
createIcon,
resolveIcon,
// checkbox
createCheckbox,
resolveCheckbox,
// tooltip
setTooltip,
resolveTooltip
}

13
lib/ui/checkbox.d.ts vendored
View File

@ -1,2 +1,13 @@
export function createCheckbox(opts: any): HTMLElement
interface CheckboxOptions {
type?: string;
label?: string;
checked?: boolean;
isImage?: boolean;
imageHeight?: Number;
checkedNode?: HTMLElement;
uncheckedNode?: HTMLElement;
onchange?: (this: HTMLInputElement, ev: Event) => any;
}
export function createCheckbox(opts?: CheckboxOptions): HTMLElement
export function resolveCheckbox(container: HTMLElement): HTMLElement

93
lib/ui/checkbox.html Normal file
View File

@ -0,0 +1,93 @@
<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 {
type?: string;
label?: string;
checked?: boolean;
isImage?: boolean;
imageHeight?: Number;
checkedNode?: HTMLElement;
uncheckedNode?: HTMLElement;
onchange?: (this: HTMLInputElement, ev: Event) => any;
}</pre>
</p>
<h3>type?: string</h3>
<p>
复选框图标的样式,可选值目前有 <code>fa-regular</code><code>fa-light</code><code>fa-solid</code>
</p>
<h3>label?: string</h3>
<p>
复选框的标签文本
</p>
<h3>checked?: boolean</h3>
<p>
初始是否选中
</p>
<h3>isImage?: boolean</h3>
<p>
是否为图片复选框
</p>
<h3>imageHeight?: Number</h3>
<p>
为图片复选框时的图片限制高度
</p>
<h3>checkedNode?: HTMLElement</h3>
<p>
为图片复选框时的选中时显示的元素
</p>
<h3>uncheckedNode?: HTMLElement</h3>
<p>
为图片复选框时的未选中时显示的元素
</p>
<h3>onchange?: (this: HTMLInputElement, ev: Event) => any</h3>
<p>
复选框改变时触发的事件
</p>
<h2>resolveCheckbox</h2>
<code>function resolveCheckbox(container: HTMLElement): HTMLElement</code>
<h3>container: HTMLElement</h3>
<p>
将把此 HTML 元素下的所有 <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>
<h2>示例</h2>
<pre>
&lt;div id="checkbox-sample"&gt;
&lt;label data-checkbox data-type="fa-light" data-label="Checkbox Light"&gt;&lt;/label&gt;
&lt;label data-checkbox data-checked data-label="Checkbox Regular"&gt;&lt;/label&gt;
&lt;label data-checkbox data-type="fa-solid" data-label="Checkbox Solid"&gt;&lt;/label&gt;
&lt;label data-checkbox&gt;
&lt;code class="checked"&gt;Checked&lt;/code&gt;
&lt;code class="unchecked"&gt;Unchecked&lt;/code&gt;
&lt;/label&gt;
&lt;/div&gt;
&lt;script type="text/javascript"&gt;
window["lib-ui"].resolveCheckbox(document.querySelector("#checkbox-sample"));
&lt;/script&gt;</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>
</div>
<script type="text/javascript">
window["lib-ui"].resolveCheckbox(document.querySelector("#checkbox-sample"));
</script>
</div>

View File

@ -18,6 +18,9 @@ function createCheckbox(opts) {
container.className = 'checkbox-wrapper';
const input = document.createElement('input');
input.setAttribute('type', 'checkbox');
if (opts.checked === true) {
input.checked = true;
}
if (typeof opts.onchange === 'function') {
input.addEventListener('change', opts.onchange);
}
@ -64,6 +67,13 @@ function resolveCheckbox(container) {
box.removeAttribute('data-label');
}
const input = document.createElement('input');
const id = box.getAttribute('data-id');
if (id != null && id.length > 0) {
input.id = id;
}
if (box.getAttribute('data-checked') != null) {
input.checked = true;
}
input.setAttribute('type', 'checkbox');
box.insertBefore(input, box.firstChild);
}

46
lib/ui/icon.html Normal file
View File

@ -0,0 +1,46 @@
<div>
<h1>icon</h1>
<hr />
<p>
创建一个 svg 矢量图标元素,或者解析转换页面上特定类型的 svg
标签到指定的图标元素。
</p>
<h2>createIcon</h2>
<code>function createIcon(type: string, id: 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>
<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>
<h2>示例</h2>
<pre>
&lt;div id="icon-sample"&gt;
&lt;svg data-id="address-card" data-type="fa-regular"&gt;&lt;/svg&gt;
&lt;svg data-id="user-edit" data-type="fa-light"&gt;&lt;/svg&gt;
&lt;svg data-id="frog" data-type="fa-solid"&gt;&lt;/svg&gt;
&lt;/div&gt;
&lt;script type="text/javascript"&gt;
window["lib-ui"].resolveIcon(document.querySelector("#icon-sample"));
&lt;/script&gt;</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>

44
lib/ui/tooltip.html Normal file
View File

@ -0,0 +1,44 @@
<div>
<h1>tooltip</h1>
<hr />
<p>
给某个元素或者页面上含有 title 属性的元素设置一个统一样式的 tooltip。
</p>
<h2>setTooltip</h2>
<code>function setTooltip(container: HTMLElement, content: string | HTMLElement): void</code>
<h3>container: HTMLElement</h3>
<p>
要设置 tooltip 的元素
</p>
<h3>content: string | HTMLElement</h3>
<p>
要设置的 tooltip 内容,允许为字符串或者 HTML 元素
</p>
<h2>resolveTooltip</h2>
<code>function resolveTooltip(container: HTMLElement): HTMLElement</code>
<h3>container: HTMLElement</h3>
<p>
给此元素下的所有含有 title 属性的子元素设置成统一样式的 tooltip
</p>
<h2>示例</h2>
<pre>
&lt;div id="tooltip-sample"&gt;
&lt;blockquote title="From MDN Website"&gt;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.&lt;/blockquote&gt;
&lt;button title="Test to send request through XMLHttpRequest."&gt;Test&lt;/button&gt;
&lt;/div&gt;
&lt;script type="text/javascript"&gt;
window["lib-ui"].resolveCheckbox(document.querySelector("#checkbox-sample"));
&lt;/script&gt;</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>

View File

@ -23,8 +23,16 @@ function setTooltip(container, content) {
container.addEventListener('mouseenter', () => {
tid && clearTimeout(tid);
tid = setTimeout(() => {
const left = container.offsetLeft + container.offsetWidth / 2 - wrapper.offsetWidth / 2;
const top = container.offsetTop - wrapper.offsetHeight - 14;
let left = container.offsetLeft;
let top = container.offsetTop;
let parent = container.parentElement;
while (parent != null) {
left -= parent.scrollLeft;
top -= parent.scrollTop;
parent = parent.parentElement;
}
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';

View File

@ -4,15 +4,19 @@ import { get, post, upload } from "./utility/request";
import { nullOrEmpty, contains, endsWith, padStart } from "./utility/strings";
export {
// cookie
getCookie,
setCookie,
deleteCookie,
// lgres
init,
r,
lang,
// request
get,
post,
upload,
// strings
nullOrEmpty,
contains,
endsWith,

View File

@ -1,4 +1,9 @@
export function init(dom?: HTMLElement, ahead?: | { callback?: (result: any) => void }): Promise<any>
interface LgresOptions {
template?: string,
callback?: (result: any) => void
}
export function init(dom?: HTMLElement, options?: LgresOptions): Promise<any>
export function r(key: string, defaultValue?: any): any
export const lang: {
get current(): string,

View File

@ -45,23 +45,24 @@ function getStorageKey(lgid) {
return `res_${lgid}`;
}
async function doRefreshLgres() {
async function doRefreshLgres(template) {
template ??= '';
const lgid = getCurrentLgId();
const r = await get(`language/${lgid}/res.json`);
const r = await get(`language/${lgid}${template}`);
const dict = await r.json();
localStorage.setItem(getStorageKey(lgid), JSON.stringify(dict));
cache = dict;
return dict;
}
async function refreshLgres(lgres) {
async function refreshLgres(template, lgres) {
if (lgres == null || typeof consts === 'undefined') {
return await doRefreshLgres();
return await doRefreshLgres(template);
}
const ver = Number(consts.resver);
if (isNaN(lgres.ver) || isNaN(ver) || ver > lgres.ver) {
console.log(`found new language res version: ${lgres.ver} => ${ver}`);
return await doRefreshLgres();
return await doRefreshLgres(template);
}
cache = lgres;
return lgres;
@ -97,42 +98,40 @@ function applyLanguage(dom, result) {
}
}
async function init(dom, ahead) {
async function init(dom, options) {
options ??= {};
const lgid = getCurrentLgId();
let lgres = localStorage.getItem(getStorageKey(lgid));
let result;
if (lgres != null) {
try {
lgres = JSON.parse(lgres);
result = await refreshLgres(lgres);
result = await refreshLgres(options.template, lgres);
} catch (e) {
console.error('error while parsing lgres, try refresh ...', e);
result = await refreshLgres();
result = await refreshLgres(options.template);
}
} else {
result = await refreshLgres();
result = await refreshLgres(options.template);
}
try {
if (ahead != null) {
// not in defer mode
if (document.readyState === 'loading') {
return await new Promise((resolve, reject) => {
let tid = setTimeout(() => reject('timeout'), 30000);
document.addEventListener('DOMContentLoaded', () => {
clearTimeout(tid);
tid = void 0;
if (typeof ahead.callback === 'function') {
ahead.callback(result);
}
applyLanguage(dom, result);
resolve(result);
});
if (document.readyState === 'loading') {
return await new Promise((resolve, reject) => {
let tid = setTimeout(() => reject('timeout'), 30000);
document.addEventListener('DOMContentLoaded', () => {
clearTimeout(tid);
tid = void 0;
if (typeof options.callback === 'function') {
options.callback(result);
}
applyLanguage(dom, result);
resolve(result);
});
}
if (typeof ahead.callback === 'function') {
ahead.callback(result);
}
});
}
if (typeof options.callback === 'function') {
options.callback(result);
}
applyLanguage(dom, result);
return result;