diff --git a/css/functions/checkbox.scss b/css/functions/checkbox.scss index 3ddc8db..30b4c62 100644 --- a/css/functions/checkbox.scss +++ b/css/functions/checkbox.scss @@ -8,7 +8,6 @@ $boxDisabledColor: #d9d9d9; .check-box-inner { position: relative; display: inline-block; - margin-top: 2px; padding: 0; width: 14px; height: 14px; diff --git a/css/ui.min.css b/css/ui.min.css index 7ec07a4..d59d5ab 100644 --- a/css/ui.min.css +++ b/css/ui.min.css @@ -1 +1 @@ -.checkbox-image>input[type=checkbox]{display:none}.checkbox-image>input[type=checkbox]:checked~.checked{display:inline}.checkbox-image>input[type=checkbox]:checked~.unchecked{display:none}.checkbox-image>.checked{display:none}.checkbox-image>.unchecked{display:inline}.checkbox-wrapper{display:flex;align-items:center;padding:0 8px;height:36px}.checkbox-wrapper .check-box-inner{position:relative;display:inline-block;margin-top:2px;padding:0;width:14px;height:14px;background-color:#fff;border:1px solid #999898;-moz-user-select:none;user-select:none;-webkit-user-select:none;border-radius:2px;transition:all .2s;cursor:pointer}.checkbox-wrapper .check-box-inner>svg{position:absolute;top:0;left:0;width:100%;height:100%;fill:#fff;transform:scale(0);opacity:0;transition:all .08s cubic-bezier(0.78, 0.14, 0.15, 0.86)}.checkbox-wrapper>input[type=checkbox]{display:none}.checkbox-wrapper>input[type=checkbox]:checked+.check-box-inner{border-color:#1890ff;background-color:#1890ff}.checkbox-wrapper>input[type=checkbox]:checked+.check-box-inner>svg{transform:scale(1);opacity:1}.checkbox-wrapper>input[type=checkbox]:disabled+.check-box-inner{border-color:#d9d9d9;cursor:default}.checkbox-wrapper>input[type=checkbox]:disabled:checked+.check-box-inner{border-color:#d9d9d9;background-color:#d9d9d9}.checkbox-wrapper>input[type=checkbox]:disabled~span{color:#d9d9d9;cursor:default}.checkbox-wrapper .check-box-inner{flex:0 0 auto}.checkbox-wrapper>span{flex:1 1 auto;font-weight:400;font-size:.875rem;padding-left:8px;padding-right:6px;align-self:center;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;cursor:pointer;color:#201f1e}.tooltip-color{background-color:#fff;color:#323130;border-color:rgba(204,204,204,.8);outline:none}.tooltip-wrapper{position:fixed;word-wrap:break-word;height:auto;text-align:left;z-index:250;min-width:75px;max-width:480px;min-height:32px;border-radius:2px;box-shadow:0 3.2px 7.2px 0 rgba(0,0,0,.13),0 .6px 1.8px 0 rgba(0,0,0,.11);transition:visibility 0s linear 120ms,opacity 120ms ease}.tooltip-wrapper>.tooltip-pointer{box-sizing:border-box;box-shadow:0 5px 15px 2px rgba(0,0,0,.3);border:1px solid #fff;z-index:-1;width:16px;height:16px;position:absolute;left:calc(50% - 8px);bottom:-8px;transform:rotate(-45deg);transform-origin:center}.tooltip-wrapper>.tooltip-curtain{position:absolute;width:100%;height:100%;z-index:-1}.tooltip-wrapper>.tooltip-content{font-size:.8125rem;line-height:1rem;white-space:normal;overflow:auto;margin:8px;height:calc(100% - 16px);-moz-user-select:none;user-select:none;-webkit-user-select:none} \ No newline at end of file +.checkbox-image>input[type=checkbox]{display:none}.checkbox-image>input[type=checkbox]:checked~.checked{display:inline}.checkbox-image>input[type=checkbox]:checked~.unchecked{display:none}.checkbox-image>.checked{display:none}.checkbox-image>.unchecked{display:inline}.checkbox-wrapper{display:flex;align-items:center;padding:0 8px;height:36px}.checkbox-wrapper .check-box-inner{position:relative;display:inline-block;padding:0;width:14px;height:14px;background-color:#fff;border:1px solid #999898;-moz-user-select:none;user-select:none;-webkit-user-select:none;border-radius:2px;transition:all .2s;cursor:pointer}.checkbox-wrapper .check-box-inner>svg{position:absolute;top:0;left:0;width:100%;height:100%;fill:#fff;transform:scale(0);opacity:0;transition:all .08s cubic-bezier(0.78, 0.14, 0.15, 0.86)}.checkbox-wrapper>input[type=checkbox]{display:none}.checkbox-wrapper>input[type=checkbox]:checked+.check-box-inner{border-color:#1890ff;background-color:#1890ff}.checkbox-wrapper>input[type=checkbox]:checked+.check-box-inner>svg{transform:scale(1);opacity:1}.checkbox-wrapper>input[type=checkbox]:disabled+.check-box-inner{border-color:#d9d9d9;cursor:default}.checkbox-wrapper>input[type=checkbox]:disabled:checked+.check-box-inner{border-color:#d9d9d9;background-color:#d9d9d9}.checkbox-wrapper>input[type=checkbox]:disabled~span{color:#d9d9d9;cursor:default}.checkbox-wrapper .check-box-inner{flex:0 0 auto}.checkbox-wrapper>span{flex:1 1 auto;font-weight:400;font-size:.875rem;padding-left:8px;padding-right:6px;align-self:center;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;cursor:pointer;color:#201f1e}.tooltip-color{background-color:#fff;color:#323130;border-color:rgba(204,204,204,.8);outline:none}.tooltip-wrapper{position:fixed;word-wrap:break-word;height:auto;text-align:left;z-index:250;min-width:75px;max-width:480px;min-height:32px;border-radius:2px;box-shadow:0 3.2px 7.2px 0 rgba(0,0,0,.13),0 .6px 1.8px 0 rgba(0,0,0,.11);transition:visibility 0s linear 120ms,opacity 120ms ease}.tooltip-wrapper>.tooltip-pointer{box-sizing:border-box;box-shadow:0 5px 15px 2px rgba(0,0,0,.3);border:1px solid #fff;z-index:-1;width:16px;height:16px;position:absolute;left:calc(50% - 8px);bottom:-8px;transform:rotate(-45deg);transform-origin:center}.tooltip-wrapper>.tooltip-curtain{position:absolute;width:100%;height:100%;z-index:-1}.tooltip-wrapper>.tooltip-content{font-size:.8125rem;line-height:1rem;white-space:normal;overflow:auto;margin:8px;height:calc(100% - 16px);-moz-user-select:none;user-select:none;-webkit-user-select:none} \ No newline at end of file diff --git a/index.html b/index.html index 905cf90..716817f 100644 --- a/index.html +++ b/index.html @@ -1,42 +1,38 @@ - - - - - UI Lib - - - -
-
- - - -
-
-
- -
-
-
- -
-
- - -
-
-
-
- - + + + + + + UI Lib + + + + + +
+ +
+
+ + + \ No newline at end of file diff --git a/lib/ui.js b/lib/ui.js index f0444cb..2c5547d 100644 --- a/lib/ui.js +++ b/lib/ui.js @@ -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 } diff --git a/lib/ui/checkbox.d.ts b/lib/ui/checkbox.d.ts index 8b5d202..7915163 100644 --- a/lib/ui/checkbox.d.ts +++ b/lib/ui/checkbox.d.ts @@ -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 \ No newline at end of file diff --git a/lib/ui/checkbox.html b/lib/ui/checkbox.html new file mode 100644 index 0000000..01de2c1 --- /dev/null +++ b/lib/ui/checkbox.html @@ -0,0 +1,93 @@ +
+

checkbox

+
+

+ 创建一个统一样式的复选框元素,或者解析转换页面上特定类型的 label + 标签为复选框元素。 +

+

createCheckbox

+ function createCheckbox(opts?: CheckboxOptions): HTMLElement +

opts?: CheckboxOptions

+

+ 复选框初始参数,结构为 +

interface CheckboxOptions {
+    type?: string;
+    label?: string;
+    checked?: boolean;
+    isImage?: boolean;
+    imageHeight?: Number;
+    checkedNode?: HTMLElement;
+    uncheckedNode?: HTMLElement;
+    onchange?: (this: HTMLInputElement, ev: Event) => any;
+}
+

+

type?: string

+

+ 复选框图标的样式,可选值目前有 fa-regularfa-lightfa-solid +

+

label?: string

+

+ 复选框的标签文本 +

+

checked?: boolean

+

+ 初始是否选中 +

+

isImage?: boolean

+

+ 是否为图片复选框 +

+

imageHeight?: Number

+

+ 为图片复选框时的图片限制高度 +

+

checkedNode?: HTMLElement

+

+ 为图片复选框时的选中时显示的元素 +

+

uncheckedNode?: HTMLElement

+

+ 为图片复选框时的未选中时显示的元素 +

+

onchange?: (this: HTMLInputElement, ev: Event) => any

+

+ 复选框改变时触发的事件 +

+

resolveCheckbox

+ function resolveCheckbox(container: HTMLElement): HTMLElement +

container: HTMLElement

+

+ 将把此 HTML 元素下的所有 label[data-checkbox] 元素解析为复选框,[data-id] 为复选框元素的 id,包含 + [data-checked] 时复选框默认选中。

+

当该元素无子元素时,[data-type] 同上述参数中的 type?: string[data-label] 同上述参数中的 + label?: string。 +

+

当该元素有子元素时,解析为图片复选框,class 为 checkedunchecked 的子元素将分别在选中与未选中时显示。

+

示例

+
+<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>
+
+ + + + +
+ +
\ No newline at end of file diff --git a/lib/ui/checkbox.js b/lib/ui/checkbox.js index 76af5ff..98a91db 100644 --- a/lib/ui/checkbox.js +++ b/lib/ui/checkbox.js @@ -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); } diff --git a/lib/ui/icon.html b/lib/ui/icon.html new file mode 100644 index 0000000..f02228e --- /dev/null +++ b/lib/ui/icon.html @@ -0,0 +1,46 @@ +
+

icon

+
+

+ 创建一个 svg 矢量图标元素,或者解析转换页面上特定类型的 svg + 标签到指定的图标元素。 +

+

createIcon

+ function createIcon(type: string, id: string): SVGElement +

type: string

+

+ 图标类型,可选值目前有 fa-regularfa-lightfa-solid +

+

id: string

+

+ 图形 id,例如 + user-editaddress-cardfrog…… +

+

resolveIcon

+ function resolveIcon(container: HTMLElement): HTMLElement +

container: HTMLElement

+

+ 将把此 HTML 元素下的所有 svg[data-id] 元素解析为图标,[data-id] + 同上述 id: string[data-type] 同上述 + type: string +

+

示例

+
+<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>
+
+ + + +
+ +
\ No newline at end of file diff --git a/lib/ui/tooltip.html b/lib/ui/tooltip.html new file mode 100644 index 0000000..a9e6665 --- /dev/null +++ b/lib/ui/tooltip.html @@ -0,0 +1,44 @@ +
+

tooltip

+
+

+ 给某个元素或者页面上含有 title 属性的元素设置一个统一样式的 tooltip。 +

+

setTooltip

+ function setTooltip(container: HTMLElement, content: string | HTMLElement): void +

container: HTMLElement

+

+ 要设置 tooltip 的元素 +

+

content: string | HTMLElement

+

+ 要设置的 tooltip 内容,允许为字符串或者 HTML 元素 +

+

resolveTooltip

+ function resolveTooltip(container: HTMLElement): HTMLElement +

container: HTMLElement

+

+ 给此元素下的所有含有 title 属性的子元素设置成统一样式的 tooltip +

+

示例

+
+<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"].resolveCheckbox(document.querySelector("#checkbox-sample"));
+</script>
+
+
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.
+ +
+ +
\ No newline at end of file diff --git a/lib/ui/tooltip.js b/lib/ui/tooltip.js index 9922f30..5b7a344 100644 --- a/lib/ui/tooltip.js +++ b/lib/ui/tooltip.js @@ -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'; diff --git a/lib/utility.js b/lib/utility.js index 763df82..ed07ffd 100644 --- a/lib/utility.js +++ b/lib/utility.js @@ -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, diff --git a/lib/utility/lgres.d.ts b/lib/utility/lgres.d.ts index 7ea3414..ca946af 100644 --- a/lib/utility/lgres.d.ts +++ b/lib/utility/lgres.d.ts @@ -1,4 +1,9 @@ -export function init(dom?: HTMLElement, ahead?: | { callback?: (result: any) => void }): Promise +interface LgresOptions { + template?: string, + callback?: (result: any) => void +} + +export function init(dom?: HTMLElement, options?: LgresOptions): Promise export function r(key: string, defaultValue?: any): any export const lang: { get current(): string, diff --git a/lib/utility/lgres.js b/lib/utility/lgres.js index db49d1f..4daaf19 100644 --- a/lib/utility/lgres.js +++ b/lib/utility/lgres.js @@ -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; diff --git a/main.js b/main.js index faeac76..ed45e60 100644 --- a/main.js +++ b/main.js @@ -1,8 +1,7 @@ import './css/ui.min.css' import './style.css' // import javascriptLogo from './javascript.svg' -import { resolveCheckbox, resolveIcon, resolveTooltip } from './lib/ui' -import { init, r, lang, get, post, upload } from './lib/utility' +import { get } from './lib/utility' // document.querySelector('#js-logo').src = javascriptLogo @@ -11,7 +10,36 @@ window.consts = { resver: 20230329 } +function navigate(page) { + get(page, { + accept: 'text/html' + }) + .then(r => r.text()) + .then(html => { + const range = document.createRange(); + range.selectNode(document.body); + const doc = range.createContextualFragment(html); + document.querySelector('#container').replaceChildren(doc); + }); +} + +document.querySelector('#directory').addEventListener('click', (ev) => { + const page = ev.target.getAttribute('data-page'); + if (typeof page === 'string') { + location.hash = page; + navigate(page); + } +}); + +let page = location.hash; +if (page.length > 1) { + page = page.substring(1); + navigate(page); +} + +/* init(null, { + template: '/res.json', callback: (result) => console.log(result) }).then(() => { // document.querySelector('#create-icon').appendChild(createIcon('fa-solid', 'user-edit')) @@ -35,3 +63,4 @@ init(null, { .then(blob => document.querySelector('#js-logo').src = URL.createObjectURL(blob)); }); }); +*/ diff --git a/style.css b/style.css index ee0d0a3..a72f5fc 100644 --- a/style.css +++ b/style.css @@ -13,6 +13,46 @@ -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; -webkit-text-size-adjust: 100%; + + --border-color: #ccc; + --hover-color: #666; + --mono-font-family: 'FantasqueSansMono NFM', 'Cascadia Code', 'PT Mono', Consolas, 'Courier New', monospace; +} + +code, kbd, pre, samp { + font-family: var(--mono-font-family); + background-color: var(--hover-color); + padding: 0 10px; +} + +code { + display: inline-block; +} + +pre { + font-size: .875em; +} + +h2 + code { + margin-left: 70px; + position: relative; +} + +h2 + code::before { + content: '签名:'; + position: absolute; + margin-left: -70px; +} + +h3 { + font-family: var(--mono-font-family); + font-size: 1em; + margin-left: 10px; + /* font-weight: bold; */ +} + +h3 ~ p { + margin-left: 10px; } a { @@ -20,50 +60,11 @@ a { color: #646cff; text-decoration: inherit; } + a:hover { color: #535bf2; } -body { - margin: 0; - display: flex; - place-items: center; - min-width: 320px; - min-height: 100vh; -} - -h1 { - font-size: 3.2em; - line-height: 1.1; -} - -#app { - max-width: 1280px; - margin: 0 auto; - padding: 2rem; - text-align: center; -} - -.logo { - height: 6em; - padding: 1.5em; - will-change: filter; -} -.logo:hover { - filter: drop-shadow(0 0 2em #646cffaa); -} -.logo.vanilla:hover { - filter: drop-shadow(0 0 2em #f7df1eaa); -} - -.card { - padding: 2em; -} - -.read-the-docs { - color: #888; -} - button { border-radius: 8px; border: 1px solid transparent; @@ -75,14 +76,74 @@ button { cursor: pointer; transition: border-color 0.25s; } + button:hover { border-color: #646cff; } + button:focus, button:focus-visible { outline: 4px auto -webkit-focus-ring-color; } +body { + margin: 0; + display: flex; + height: 100vh; +} + +h1 { + font-size: 3.2em; + line-height: 1.1; + margin: 20px 0; +} + +#directory { + width: 200px; + padding: 2rem; + border-right: 1px solid var(--border-color); + flex: 0 0 auto; +} + +#directory>ul { + padding: 0; + line-height: 1.6em; +} + +#directory>ul>li { + list-style: none; + user-select: none; +} + +#directory>ul>li.title { + margin: 20px 0 6px; + font-weight: bold; + font-size: 1.25em; +} + +#directory ol { + padding-left: 10px; +} + +#directory ol>li { + padding: 0 6px; + list-style-position: inside; + cursor: pointer; +} + +#directory ol>li:hover { + background-color: var(--hover-color); +} + +#container { + flex: 1 1 auto; + overflow: auto; +} + +#container>div { + padding: 20px; +} + .app-module { margin: 8px 0; } @@ -91,6 +152,7 @@ button:focus-visible { display: flex; justify-content: center; } + #create-icon svg { width: 20px; height: 20px; @@ -101,11 +163,13 @@ button:focus-visible { flex-direction: column; align-items: center; } + .checkbox-wrapper .check-box-inner { width: 14px; height: 14px; } -.checkbox-wrapper > span { + +.checkbox-wrapper>span { font-size: 1em; } @@ -113,11 +177,15 @@ button:focus-visible { :root { color: #213547; background-color: #ffffff; + --border-color: #666; + --hover-color: #eee; } + a:hover { color: #747bff; } + button { background-color: #f9f9f9; } -} +} \ No newline at end of file