Compare commits
33 Commits
6419d6780f
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
531d4fde46 | ||
| 409dc6d032 | |||
| 62f8bfda2d | |||
| 3ad79bc529 | |||
| 4b82c1afde | |||
| a229600fc4 | |||
| 9eb9ddb953 | |||
| b60339796f | |||
| 96e2f9b9f2 | |||
| 8a87885921 | |||
| c204f8a3d6 | |||
| cc6099fb7a | |||
| 7d1a12c5b0 | |||
| b094b3f914 | |||
| 573e40f4e2 | |||
| 6428d81818 | |||
| ddfee6403f | |||
| efa23f8d6a | |||
| 2e41b3d18c | |||
| b6df98b35d | |||
| c12ca95c42 | |||
| 8b12e63fbb | |||
| 5cbc817840 | |||
| 3f6ddb0674 | |||
| 26ee741741 | |||
| fcd9b76ace | |||
| c5ee9687c9 | |||
| 6967078a43 | |||
| 4c8aeb57cd | |||
| d575735bb4 | |||
| 3fb9f57e28 | |||
| d8166e1b36 | |||
| 678ab6bf84 |
134
.gitignore
vendored
134
.gitignore
vendored
@@ -1,134 +1,2 @@
|
||||
node_modules
|
||||
package-lock.json
|
||||
|
||||
# ---> Node
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
lerna-debug.log*
|
||||
.pnpm-debug.log*
|
||||
|
||||
# Diagnostic reports (https://nodejs.org/api/report.html)
|
||||
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
|
||||
|
||||
# Runtime data
|
||||
pids
|
||||
*.pid
|
||||
*.seed
|
||||
*.pid.lock
|
||||
|
||||
# Directory for instrumented libs generated by jscoverage/JSCover
|
||||
lib-cov
|
||||
|
||||
# Coverage directory used by tools like istanbul
|
||||
coverage
|
||||
*.lcov
|
||||
|
||||
# nyc test coverage
|
||||
.nyc_output
|
||||
|
||||
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
|
||||
.grunt
|
||||
|
||||
# Bower dependency directory (https://bower.io/)
|
||||
bower_components
|
||||
|
||||
# node-waf configuration
|
||||
.lock-wscript
|
||||
|
||||
# Compiled binary addons (https://nodejs.org/api/addons.html)
|
||||
build/Release
|
||||
|
||||
# Dependency directories
|
||||
node_modules/
|
||||
jspm_packages/
|
||||
|
||||
# Snowpack dependency directory (https://snowpack.dev/)
|
||||
web_modules/
|
||||
|
||||
# TypeScript cache
|
||||
*.tsbuildinfo
|
||||
|
||||
# Optional npm cache directory
|
||||
.npm
|
||||
|
||||
# Optional eslint cache
|
||||
.eslintcache
|
||||
|
||||
# Optional stylelint cache
|
||||
.stylelintcache
|
||||
|
||||
# Microbundle cache
|
||||
.rpt2_cache/
|
||||
.rts2_cache_cjs/
|
||||
.rts2_cache_es/
|
||||
.rts2_cache_umd/
|
||||
|
||||
# Optional REPL history
|
||||
.node_repl_history
|
||||
|
||||
# Output of 'npm pack'
|
||||
*.tgz
|
||||
|
||||
# Yarn Integrity file
|
||||
.yarn-integrity
|
||||
|
||||
# dotenv environment variable files
|
||||
.env
|
||||
.env.development.local
|
||||
.env.test.local
|
||||
.env.production.local
|
||||
.env.local
|
||||
|
||||
# parcel-bundler cache (https://parceljs.org/)
|
||||
.cache
|
||||
.parcel-cache
|
||||
|
||||
# Next.js build output
|
||||
.next
|
||||
out
|
||||
|
||||
# Nuxt.js build / generate output
|
||||
.nuxt
|
||||
dist
|
||||
|
||||
# Gatsby files
|
||||
.cache/
|
||||
# Comment in the public line in if your project uses Gatsby and not Next.js
|
||||
# https://nextjs.org/blog/next-9-1#public-directory-support
|
||||
# public
|
||||
|
||||
# vuepress build output
|
||||
.vuepress/dist
|
||||
|
||||
# vuepress v2.x temp and cache directory
|
||||
.temp
|
||||
.cache
|
||||
|
||||
# Docusaurus cache and generated files
|
||||
.docusaurus
|
||||
|
||||
# Serverless directories
|
||||
.serverless/
|
||||
|
||||
# FuseBox cache
|
||||
.fusebox/
|
||||
|
||||
# DynamoDB Local files
|
||||
.dynamodb/
|
||||
|
||||
# TernJS port file
|
||||
.tern-port
|
||||
|
||||
# Stores VSCode versions used for testing VSCode extensions
|
||||
.vscode-test
|
||||
|
||||
# yarn v2
|
||||
.yarn/cache
|
||||
.yarn/unplugged
|
||||
.yarn/build-state.yml
|
||||
.yarn/install-state.gz
|
||||
.pnp.*
|
||||
|
||||
|
||||
104
README.md
104
README.md
@@ -1,8 +1,22 @@
|
||||
# widgets
|
||||
|
||||
1. 减少多余操作
|
||||
2. 提高信息密度
|
||||
3. 补充需求
|
||||
|
||||
像 flutter 风格用纯粹 js 构建页面
|
||||
{ createElement List ListItem Span Button Img Input TextArea Avatar Dialog }
|
||||
|
||||
(本质上只是给创建 html 的js方法套了一层使其能够链式调用)
|
||||
|
||||
```bash
|
||||
npm version patch # 修复问题
|
||||
npm version minor # 添加功能
|
||||
npm version major # 破坏性更改
|
||||
|
||||
npm publish
|
||||
```
|
||||
|
||||
|
||||
```bash
|
||||
npm i @laniakeasupercluster/widgets
|
||||
@@ -33,3 +47,93 @@ git clone git@git.satori.love:LaniakeaSupercluster/widgets.git
|
||||
npm i
|
||||
npm run dev
|
||||
```
|
||||
|
||||
### HTML 基本概念
|
||||
对于UI书写划分为两类, 一是外观样式, 一是结构关系
|
||||
一个元素的基本构成就是其外观样式, 因此它书写在声明它的同一列
|
||||
```javascript
|
||||
div.w(128).h(64)
|
||||
```
|
||||
|
||||
嵌套关系则应该换行并缩进, 保持与 HTML 一致的风格
|
||||
```javascript
|
||||
document.body.appendChild(div.w(128).h(64).children([
|
||||
div.text('Hello world!'),
|
||||
div.text('Hello world!'),
|
||||
div.text('Hello world!')
|
||||
]))
|
||||
```
|
||||
|
||||
有时期望复用样式
|
||||
```javascript
|
||||
const option = { style: { width: '128px', height: '64px' } }
|
||||
div(option).children([
|
||||
div.text('Hello world!'),
|
||||
div.text('Hello world!'),
|
||||
div.text('Hello world!')
|
||||
])
|
||||
```
|
||||
|
||||
由于返回值是真实的 DOM 对象, 它具有原生 DOM 对象的所有方法, 因而可以这样使用它
|
||||
```javascript
|
||||
const demo = div.w(128).h(64).children([
|
||||
div.text('Hello world!'),
|
||||
div.text('Hello world!'),
|
||||
div.text('Hello world!')
|
||||
])
|
||||
demo.textContent = 'hello world!'
|
||||
document.body.appendChild(demo)
|
||||
```
|
||||
|
||||
然而 dom 元素的 textContent 并不是一个函数方法而是一个变量值, 自然无法使用链式调用 `demo.textContent('hello world!')`
|
||||
为了减少代码量也为了避免破坏dom基本结构, 所有额外提供的方法都使用缩写,
|
||||
像是这样 `demo.text('hello world!')`
|
||||
|
||||
|
||||
这样做的期望自然是将样式便捷地与数据混合处理, 像这样
|
||||
```javascript
|
||||
import { div, span } from '@laniakeasupercluster/widgets'
|
||||
|
||||
const data = ['hello', 'world', 'and', 'you']
|
||||
document.body.appendChild(div.w(20).h(20).children([
|
||||
...data.filter(item => item.length >= 3).map(item => span.text(item))
|
||||
]))
|
||||
|
||||
// 也许想要这样做
|
||||
const data = ['hello', 'world', 'and', 'you'].filter(item => item.length >= 3)
|
||||
const items = data.map(item => span.text(item)).map((item, index) => {
|
||||
if (index < 1) item.style.color = '#cc1414'
|
||||
return item
|
||||
})
|
||||
|
||||
document.body.appendChild(div.w(20).h(20).children(items))
|
||||
```
|
||||
|
||||
也许想要这样做
|
||||
```javascript
|
||||
import { div, span } from '@laniakeasupercluster/widgets'
|
||||
|
||||
const data = ['hello', 'world', 'and', 'you'].filter(item => item.length >= 3)
|
||||
const items = data.map(item => span.text(item)).map((item, index) => {
|
||||
if (index < 1) item.style.color = '#cc1414'
|
||||
return item
|
||||
})
|
||||
|
||||
document.body.appendChild(div.w(20).h(20).children(items))
|
||||
```
|
||||
|
||||
|
||||
虽然这与 pug 中 `div.w-8.h-12` 赋予元素 class 名的概念基本相同, 但实际并不会给元素赋予 class 名, 它只是一个方法去设置元素样式, 因为在 js 的实现中过多引入 css 概念是没有意义的(至少目前我这么认为)
|
||||
|
||||
CSS 的便捷性还有两点, 一是伪元素和伪类(:hover ::after), 一是选择器(div.text#ctx.mix > .cc { width: 12px }), 在js中目前没有很好的实现方法, 此事只能再议
|
||||
|
||||
|
||||
三种情境:
|
||||
div({})
|
||||
div.text('')
|
||||
new div({})
|
||||
|
||||
符合直觉的标准
|
||||
1. 元素在初始化前是 class 而不是 function, 这在编辑器中渲染的样式也将不同
|
||||
2. 直接导出 html5 语义标签
|
||||
3. 额外添加的快捷方法都是缩写, 将避免与默认属性或方法冲突
|
||||
|
||||
63
index.js
63
index.js
@@ -1,12 +1,59 @@
|
||||
import { div, pre } from './main.js'
|
||||
import { div, span, pre, h1, h2, p, ul, li, button } from './main.js'
|
||||
import { Dialog } from './widgets/dialog.js'
|
||||
|
||||
const demo = div.w(128).h(64) //.onclick.stop('click')
|
||||
demo.textContent = 'Hello world'
|
||||
document.body.style.fontFamily = 'Arial, sans-serif'
|
||||
document.body.style.fontSize = '14px'
|
||||
|
||||
console.log('demo', demo)
|
||||
document.body.appendChild(demo)
|
||||
document.body.appendChild(div.w(128).h(64).text('Hello world'))
|
||||
//document.body.appendChild(div('px-2 w-12 bg- ').childs([
|
||||
// div.text('会话/账户')
|
||||
//]))
|
||||
|
||||
fetch('./index.js').then(res => res.text()).then(text => {
|
||||
document.body.appendChild(pre.text(text))
|
||||
document.body.appendChild(div.absolute.t_2.r_2.bg_red_500.childs([
|
||||
div.text('会话/账户')
|
||||
]))
|
||||
|
||||
document.body.appendChild(div.cursor_pointer.bg_red_500.overflow_clip.w_1000.childs([
|
||||
h1.m_2rem.pt_2rem.text('Widgets!'),
|
||||
p.m('1rem').p({ top: '1rem' }).text('是什么, 为什么, 怎么做?'),
|
||||
ul.m('1rem').p({ top: '1rem' }).childs([
|
||||
li.text('让事情变简单直观, 仅使用js代码, 而不必在 js/html/css 文件之间来回翻找'),
|
||||
li.text('大幅提高代码信息密度, 灵感来自 pug/winicss/flutter, 减少布局与样式冗余'),
|
||||
li.text('为dom对象添加链式操作方法, 使其返回值都是dom自身')
|
||||
]),
|
||||
new button({
|
||||
textContent: '模态窗口 dialog',
|
||||
onclick: event => {
|
||||
document.body.appendChild(Dialog({
|
||||
style: { width: '32rem', padding: '24px' },
|
||||
children: [
|
||||
div.childs([
|
||||
h2.m_0.font_semibold.text('Are you absolutely sure?'),
|
||||
p.mt_8.color('#71717a').text('This action cannot be undone. This will permanently delete your account and remove your data from our servers.')
|
||||
]),
|
||||
div.flex.justify_end.gap_8.childs([
|
||||
button.text('Cancel'),
|
||||
button.text('Delete'),
|
||||
]),
|
||||
]
|
||||
}))
|
||||
}
|
||||
}),
|
||||
|
||||
// 三类参数
|
||||
p.text(`
|
||||
span 是一个类(class), 直接实例化 (new span()) 将创建一个 span 元素(DOM对象). 实例化时可以传递参数, 它对应DOM对象的属性.
|
||||
span.m('2rem').p({ top: '2rem' }).text('Hello world!') 是一个链式操作, 每个链式操作都是DOM对象的方法缩写. 不同的是它返回的是DOM对象自身.
|
||||
span.cursor_pointer.color_ppp.w(128).h(64).childs([span.text('Hello world!')]) 是预定义的链式操作, 在执行函数方法前的静态属性调用并没有实例化对象, 只是缓存了属性值. 当对象被实例化时, 静态属性会被应用到实例对象上. 而静态属性的语法对应 tailwindcss 的类名.
|
||||
`)
|
||||
]))
|
||||
|
||||
// 使用 js 文件作为组件管理代码
|
||||
document.body.appendChild(div.cursor_pointer.color_ppp.w(128).h(64).childs([
|
||||
span.m('2rem').p({ top: '2rem' }).text('Hello world!'),
|
||||
new span({ textContent: 'Hello world!' }),
|
||||
]))
|
||||
|
||||
fetch('/index.js').then(res => res.text()).then(text => {
|
||||
text = text.replace(/from "\/main.js"/g, "from '@laniakeasupercluster/widgets'")
|
||||
document.body.appendChild(pre.overflow_auto.p('2rem').bg('#ececec').text(text.substring(0, text.indexOf('fetch'))))
|
||||
})
|
||||
|
||||
766
main.js
766
main.js
@@ -1,4 +1,4 @@
|
||||
export function createElement({ innerText, innerHTML, textContent, readOnly, children = [], dataset, style, classList = [], ...attributes }, tagName = 'div') {
|
||||
export function createElement({ innerText, innerHTML, textContent, readOnly, children = [], dataset, style, classList = [], ...attributes }, tagName = 'div', names = []) {
|
||||
const element = document.createElement(tagName)
|
||||
for (const key in attributes) {
|
||||
if (key.startsWith('on')) element[key] = attributes[key] // 如果是事件则直接赋值
|
||||
@@ -12,103 +12,701 @@ export function createElement({ innerText, innerHTML, textContent, readOnly, chi
|
||||
if (innerHTML) element.innerHTML = innerHTML
|
||||
if (readOnly) element.readOnly = readOnly
|
||||
if (children) children.forEach(child => element.appendChild(child))
|
||||
return element
|
||||
}
|
||||
|
||||
export function List(options) {
|
||||
return createElement(options, 'ul')
|
||||
}
|
||||
// 分解类名生成样式, 直接将样式赋予元素
|
||||
names.map(name => name.split('_')).forEach(item => {
|
||||
|
||||
export function ListItem(options) {
|
||||
return createElement(options, 'li')
|
||||
}
|
||||
if (item.length === 1) {
|
||||
const key = item[0]
|
||||
|
||||
export function Span(options) {
|
||||
return createElement(options, 'span')
|
||||
}
|
||||
// 支持快捷定位 position
|
||||
if (['static', 'fixed', 'absolute', 'relative', 'sticky'].includes(key)) {
|
||||
element.style.position = key
|
||||
return
|
||||
}
|
||||
|
||||
export function Button(options) {
|
||||
return createElement({
|
||||
...options,
|
||||
style: {
|
||||
cursor: 'pointer',
|
||||
...options.style
|
||||
// 支持快捷布局 display
|
||||
if (['block', 'inline-block', 'flex', 'grid'].includes(key)) {
|
||||
element.style.display = key
|
||||
return
|
||||
}
|
||||
}
|
||||
}, 'button')
|
||||
}
|
||||
|
||||
export function Img(options) {
|
||||
return createElement(options, 'img')
|
||||
}
|
||||
// 两个参数的情况下, 第一个参数是样式名, 第二个参数是样式值
|
||||
if (item.length === 2) {
|
||||
const [key, value] = item
|
||||
|
||||
export function Input(options) {
|
||||
return createElement(options, 'input')
|
||||
}
|
||||
if (key === 'cursor') {
|
||||
element.style.cursor = value
|
||||
return
|
||||
}
|
||||
if (key === 'overflow') {
|
||||
element.style.overflow = value
|
||||
return
|
||||
}
|
||||
if (key === 'bg') {
|
||||
element.style.backgroundColor = value
|
||||
return
|
||||
}
|
||||
|
||||
export function TextArea(options) {
|
||||
return createElement(options, 'textarea')
|
||||
}
|
||||
const styleMap = {
|
||||
'w': ['width'],
|
||||
'h': ['height'],
|
||||
'p': ['padding'],
|
||||
'm': ['margin'],
|
||||
'pt': ['paddingTop'],
|
||||
'pl': ['paddingLeft'],
|
||||
'pr': ['paddingRight'],
|
||||
'pb': ['paddingBottom'],
|
||||
'mt': ['marginTop'],
|
||||
'ml': ['marginLeft'],
|
||||
'mr': ['marginRight'],
|
||||
'mb': ['marginBottom'],
|
||||
'px': ['paddingLeft', 'paddingRight'],
|
||||
'py': ['paddingTop', 'paddingBottom'],
|
||||
'mx': ['marginLeft', 'marginRight'],
|
||||
'my': ['marginTop', 'marginBottom'],
|
||||
't': ['top'],
|
||||
'b': ['bottom'],
|
||||
'l': ['left'],
|
||||
'r': ['right'],
|
||||
'x': ['left', 'right'],
|
||||
'y': ['top', 'bottom'],
|
||||
'gap': ['gap'],
|
||||
}
|
||||
|
||||
export function Avatar(options) {
|
||||
const element = createElement(options, 'img')
|
||||
element.onerror = () => element.src = '/favicon.ico'
|
||||
return element
|
||||
}
|
||||
if (styleMap.hasOwnProperty(key)) {
|
||||
styleMap[key].forEach(style => {
|
||||
element.style[style] = isNaN(value) ? value : `${value}px`
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
export function Dialog(options) {
|
||||
const element = createElement({
|
||||
tabIndex: 0,
|
||||
style: {
|
||||
position: 'fixed',
|
||||
top: 0,
|
||||
left: 0,
|
||||
zIndex: 1000,
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
backdropFilter: 'blur(5px)',
|
||||
duration: '0.5s',
|
||||
transition: 'all 0.5s'
|
||||
},
|
||||
onclick: async event => {
|
||||
if (event.target !== event.currentTarget) return
|
||||
await event.target.animate([{ opacity: 1 }, { opacity: 0 }], { duration: 100 }).finished
|
||||
await event.target.remove()
|
||||
},
|
||||
onkeydown: async event => {
|
||||
if (event.key !== 'Escape') return
|
||||
await event.target.animate([{ opacity: 1 }, { opacity: 0 }], { duration: 100 }).finished
|
||||
await event.target.remove()
|
||||
},
|
||||
children: [
|
||||
createElement({
|
||||
...options,
|
||||
style: {
|
||||
position: 'fixed',
|
||||
top: '50%',
|
||||
left: '50%',
|
||||
transform: 'translate(-50%, -50%)',
|
||||
backgroundColor: '#fff',
|
||||
borderRadius: '150px',
|
||||
boxShadow: '0 0 1em #ccc',
|
||||
overflow: 'hidden',
|
||||
display: 'flex',
|
||||
justifyContent: 'center',
|
||||
...options.style,
|
||||
// TODO 支持颜色输入
|
||||
if (key === 'color') {
|
||||
element.style.color = value
|
||||
return
|
||||
}
|
||||
|
||||
// TODO 支持各角和单边圆角
|
||||
if (key === 'radius' || key === 'rounded') {
|
||||
element.style.borderRadius = value
|
||||
return
|
||||
}
|
||||
|
||||
if (key === 'justify') {
|
||||
if (['start', 'end', 'center', 'between', 'around'].includes(value)) {
|
||||
element.style.justifyContent = `flex-${value}`
|
||||
return
|
||||
}
|
||||
})
|
||||
]
|
||||
})
|
||||
const observer = new MutationObserver(mutationsList => {
|
||||
for (const mutation of mutationsList) {
|
||||
if (mutation.type === 'childList' && mutation.addedNodes.length > 0) {
|
||||
element.focus()
|
||||
element.animate([{ opacity: 0 }, { opacity: 1 }], { duration: 100 }).finished
|
||||
return observer.disconnect()
|
||||
return
|
||||
}
|
||||
|
||||
if (key === 'font') {
|
||||
if (['medium', 'bold', 'semibold'].includes(value)) {
|
||||
const fontWeightMap = { 'medium':500, 'semibold': 600, 'bold': 700 }
|
||||
element.style.fontWeight = fontWeightMap[value]
|
||||
return
|
||||
}
|
||||
|
||||
element.style.font = value
|
||||
return
|
||||
}
|
||||
|
||||
if (key === 'text') {
|
||||
|
||||
// 字体常用尺寸
|
||||
if (value === 'xs') {
|
||||
element.style.fontSize = '0.75rem'
|
||||
element.style.lineHeight = '1rem'
|
||||
return
|
||||
}
|
||||
if (value === 'sm') {
|
||||
element.style.fontSize = '0.875rem'
|
||||
element.style.lineHeight = '1.25rem'
|
||||
return
|
||||
}
|
||||
if (value === 'base') {
|
||||
element.style.fontSize = '1rem'
|
||||
element.style.lineHeight = '1.5rem'
|
||||
return
|
||||
}
|
||||
if (value === 'lg') {
|
||||
element.style.fontSize = '1.125rem'
|
||||
element.style.lineHeight = '1.75rem'
|
||||
return
|
||||
}
|
||||
if (value === 'xl') {
|
||||
element.style.fontSize = '1.25rem'
|
||||
element.style.lineHeight = '1.75rem'
|
||||
return
|
||||
}
|
||||
if (value === '2xl') {
|
||||
element.style.fontSize = '1.5rem'
|
||||
element.style.lineHeight = '2rem'
|
||||
return
|
||||
}
|
||||
if (value === '3xl') {
|
||||
element.style.fontSize = '1.875rem'
|
||||
element.style.lineHeight = '2.25rem'
|
||||
return
|
||||
}
|
||||
if (value === '4xl') {
|
||||
element.style.fontSize = '2.25rem'
|
||||
element.style.lineHeight = '2.5rem'
|
||||
return
|
||||
}
|
||||
|
||||
// 字体对齐方式
|
||||
if (value === 'center') {
|
||||
element.style.textAlign = value
|
||||
return
|
||||
}
|
||||
|
||||
element.textContent = value
|
||||
return
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// 三个参数的情况下, 第一个参数是样式名, 第二个参数是样式, 第三个参数是数值
|
||||
if (item.length === 3) {
|
||||
if (item[0] === 'color') {
|
||||
element.style.color = item[1]
|
||||
return
|
||||
}
|
||||
}
|
||||
})
|
||||
observer.observe(document.body, { childList: true, subtree: true })
|
||||
|
||||
// 元素 增加链式操作方法
|
||||
element.w = (width) => { element.style.width = width; return element }
|
||||
element.h = (height) => { element.style.height = height; return element }
|
||||
element.x = (left) => { element.style.left = left; return element } // 以居中线对齐(flex)
|
||||
element.y = (top) => { element.style.top = top; return element } // 以居中线对齐(flex)
|
||||
element.z = (zIndex) => { element.style.zIndex = zIndex; return element }
|
||||
element.cursor = (cursor) => { element.style.cursor = cursor; return element }
|
||||
element.text = (text) => { element.textContent = text; return element }
|
||||
element.html = (html) => { element.innerHTML = html; return element }
|
||||
element.childs = (childs) => { childs.forEach(child => element.appendChild(child)); return element }
|
||||
element.bg = (color) => { element.style.backgroundColor = color; return element }
|
||||
element.grid = (options) => { element.style.display = 'grid'; Object.assign(element.style, options); return element }
|
||||
element.flex = (options) => { element.style.display = 'flex'; Object.assign(element.style, options); return element }
|
||||
element.radius = (radius) => { element.style.borderRadius = radius; return element }
|
||||
|
||||
element.m = (margin) => applyStyle(element, 'margin', margin)
|
||||
element.mx = (value) => { element.style.marginLeft = value; element.style.marginRight = value; return element }
|
||||
element.my = (value) => { element.style.marginTop = value; element.style.marginBottom = value; return element }
|
||||
element.mt = (value) => { element.style.marginTop = value; return element }
|
||||
element.ml = (value) => { element.style.marginLeft = value; return element }
|
||||
element.mr = (value) => { element.style.marginRight = value; return element }
|
||||
element.mb = (value) => { element.style.marginBottom = value; return element }
|
||||
|
||||
element.p = (padding) => applyStyle(element, 'padding', padding)
|
||||
element.px = (value) => { element.style.paddingLeft = value; element.style.paddingRight = value; return element }
|
||||
element.py = (value) => { element.style.paddingTop = value; element.style.paddingBottom = value; return element }
|
||||
element.pt = (value) => { element.style.paddingTop = value; return element }
|
||||
element.pl = (value) => { element.style.paddingLeft = value; return element }
|
||||
element.pr = (value) => { element.style.paddingRight = value; return element }
|
||||
element.pb = (value) => { element.style.paddingBottom = value; return element }
|
||||
|
||||
element.font = (font) => { element.style.font = font; return element }
|
||||
element.color = (color) => { element.style.color = color; return element }
|
||||
return element
|
||||
}
|
||||
|
||||
export default { createElement, List, ListItem, Span, Button, Img, Input, TextArea, Avatar, Dialog }
|
||||
function applyStyle(element, styleName, value) {
|
||||
if (typeof value === 'string') {
|
||||
element.style[styleName] = value;
|
||||
} else if (typeof value === 'object') {
|
||||
const { left = 0, right = 0, top = 0, bottom = 0 } = value;
|
||||
element.style[`${styleName}Left`] = left;
|
||||
element.style[`${styleName}Right`] = right;
|
||||
element.style[`${styleName}Top`] = top;
|
||||
element.style[`${styleName}Bottom`] = bottom;
|
||||
}
|
||||
return element;
|
||||
}
|
||||
|
||||
/* HTML5 语义标签 */
|
||||
export class BaseElement {
|
||||
constructor(options) {
|
||||
return createElement(options, this.constructor.name)
|
||||
}
|
||||
static text(text, tagName, names) {
|
||||
return createElement({ textContent: text }, tagName, names)
|
||||
}
|
||||
static html(html, tagName, names) {
|
||||
return createElement({ innerHTML: html }, tagName, names)
|
||||
}
|
||||
static childs(childs, tagName, names) {
|
||||
return createElement({ children: childs }, tagName, names)
|
||||
}
|
||||
static w(width, tagName, names) {
|
||||
if (typeof width === 'number') width = width + 'px'
|
||||
return createElement({ style: { width } }, tagName, names)
|
||||
}
|
||||
static h(height, tagName, names) {
|
||||
if (typeof height === 'number') height = height + 'px'
|
||||
return createElement({ style: { height } }, tagName, names)
|
||||
}
|
||||
static bg(color, tagName, names) {
|
||||
return createElement({ style: { backgroundColor: color } }, tagName, names)
|
||||
}
|
||||
static p(args, tagName, names) {
|
||||
if (typeof args === 'string') {
|
||||
return createElement({ style: { padding: args } }, tagName, names)
|
||||
}
|
||||
if (typeof args === 'object') {
|
||||
const { left = 0, right = 0, top = 0, bottom = 0 } = args
|
||||
const style = { paddingLeft: left, paddingRight: right, paddingTop: top, paddingBottom: bottom }
|
||||
return createElement({ style }, tagName, names)
|
||||
}
|
||||
if (Array.isArray(args)) {
|
||||
const [top, right, bottom, left] = args
|
||||
const style = { paddingTop: top, paddingRight: right, paddingBottom: bottom, paddingLeft: left }
|
||||
return createElement({ style }, tagName, names)
|
||||
}
|
||||
}
|
||||
static m(args, tagName, names) {
|
||||
if (typeof args === 'string') {
|
||||
return createElement({ style: { margin: args } }, tagName, names)
|
||||
}
|
||||
if (typeof args === 'object') {
|
||||
const { left = 0, right = 0, top = 0, bottom = 0 } = args
|
||||
const style = { marginLeft: left, marginRight: right, marginTop: top, marginBottom: bottom }
|
||||
return createElement({ style }, tagName, names)
|
||||
}
|
||||
if (Array.isArray(args)) {
|
||||
const [top, right, bottom, left] = args
|
||||
const style = { marginTop: top, marginRight: right, marginBottom: bottom, marginLeft: left }
|
||||
return createElement({ style }, tagName, names)
|
||||
}
|
||||
}
|
||||
static mx(value, tagName, names) {
|
||||
if (typeof value === 'number') value = value + 'px'
|
||||
return createElement({ style: { marginLeft: value, marginRight: value } }, tagName, names)
|
||||
}
|
||||
static my(value, tagName, names) {
|
||||
if (typeof value === 'number') value = value + 'px'
|
||||
return createElement({ style: { marginTop: value, marginBottom: value } }, tagName, names)
|
||||
}
|
||||
static px(value, tagName, names) {
|
||||
if (typeof value === 'number') value = value + 'px'
|
||||
return createElement({ style: { paddingLeft: value, paddingRight: value } }, tagName, names)
|
||||
}
|
||||
static py(value, tagName, names) {
|
||||
if (typeof value === 'number') value = value + 'px'
|
||||
return createElement({ style: { paddingTop: value, paddingBottom: value } }, tagName, names)
|
||||
}
|
||||
static radius(radius, tagName, names) {
|
||||
return createElement({ style: { borderRadius: radius } }, tagName, names)
|
||||
}
|
||||
static font(font, tagName, names) {
|
||||
return createElement({ style: { font } }, tagName, names)
|
||||
}
|
||||
static color(color, tagName, names) {
|
||||
return createElement({ style: { color } }, tagName, names)
|
||||
}
|
||||
}
|
||||
|
||||
function createProxy(tagName) {
|
||||
return new Proxy(BaseElement, {
|
||||
get(target, property) {
|
||||
if (property in target) {
|
||||
const __name = this.__name ?? tagName
|
||||
if (typeof target[property] === 'function') {
|
||||
return function (...args) {
|
||||
const [tagname, ...names] = __name.split('.')
|
||||
return target[property](...args, tagName, names)
|
||||
}
|
||||
}
|
||||
return target[property]
|
||||
} else {
|
||||
if (!this.__name) this.__name = tagName
|
||||
const __name = this.__name ? this.__name + '.' + property : property
|
||||
return new Proxy(target, { __name, get: this.get })
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
export const div = createProxy('div')
|
||||
export const pre = createProxy('pre')
|
||||
|
||||
|
||||
export class span extends BaseElement {
|
||||
constructor(options) {
|
||||
super(options)
|
||||
}
|
||||
}
|
||||
|
||||
export class button extends BaseElement {
|
||||
constructor(options) {
|
||||
super({ ...options, style: { cursor: 'pointer', ...options.style } })
|
||||
}
|
||||
}
|
||||
|
||||
export class img extends BaseElement {
|
||||
constructor(options) {
|
||||
super(options)
|
||||
}
|
||||
}
|
||||
|
||||
export class input extends BaseElement {
|
||||
constructor(options) {
|
||||
super(options)
|
||||
}
|
||||
}
|
||||
|
||||
export class textarea extends BaseElement {
|
||||
constructor(options) {
|
||||
super(options)
|
||||
}
|
||||
}
|
||||
|
||||
export class select extends BaseElement {
|
||||
constructor(options) {
|
||||
super(options)
|
||||
}
|
||||
}
|
||||
|
||||
export class option extends BaseElement {
|
||||
constructor(options) {
|
||||
super(options)
|
||||
}
|
||||
}
|
||||
|
||||
export class nav extends BaseElement {
|
||||
constructor(options) {
|
||||
super(options)
|
||||
}
|
||||
}
|
||||
|
||||
export class main extends BaseElement {
|
||||
constructor(options) {
|
||||
super(options)
|
||||
}
|
||||
}
|
||||
|
||||
export class article extends BaseElement {
|
||||
constructor(options) {
|
||||
super(options)
|
||||
}
|
||||
}
|
||||
|
||||
export class section extends BaseElement {
|
||||
constructor(options) {
|
||||
super(options)
|
||||
}
|
||||
}
|
||||
|
||||
export class aside extends BaseElement {
|
||||
constructor(options) {
|
||||
super(options)
|
||||
}
|
||||
}
|
||||
|
||||
export class header extends BaseElement {
|
||||
constructor(options) {
|
||||
super(options)
|
||||
}
|
||||
}
|
||||
|
||||
export class footer extends BaseElement {
|
||||
constructor(options) {
|
||||
super(options)
|
||||
}
|
||||
}
|
||||
|
||||
export const a = createProxy('a')
|
||||
export const p = createProxy('p')
|
||||
|
||||
export const h1 = createProxy('h1')
|
||||
export const h2 = createProxy('h2')
|
||||
export const h3 = createProxy('h3')
|
||||
export const h4 = createProxy('h4')
|
||||
export const h5 = createProxy('h5')
|
||||
export const h6 = createProxy('h6')
|
||||
|
||||
export const ul = createProxy('ul')
|
||||
export const ol = createProxy('ol')
|
||||
export const dl = createProxy('dl')
|
||||
export const li = createProxy('li')
|
||||
export const dt = createProxy('dt')
|
||||
export const dd = createProxy('dd')
|
||||
|
||||
export class table extends BaseElement {
|
||||
constructor(options) {
|
||||
super(options)
|
||||
}
|
||||
}
|
||||
|
||||
export class caption extends BaseElement {
|
||||
constructor(options) {
|
||||
super(options)
|
||||
}
|
||||
}
|
||||
|
||||
export class thead extends BaseElement {
|
||||
constructor(options) {
|
||||
super(options)
|
||||
}
|
||||
}
|
||||
|
||||
export class tbody extends BaseElement {
|
||||
constructor(options) {
|
||||
super(options)
|
||||
}
|
||||
}
|
||||
|
||||
export class tfoot extends BaseElement {
|
||||
constructor(options) {
|
||||
super(options)
|
||||
}
|
||||
}
|
||||
|
||||
export class tr extends BaseElement {
|
||||
constructor(options) {
|
||||
super(options)
|
||||
}
|
||||
}
|
||||
|
||||
export class th extends BaseElement {
|
||||
constructor(options) {
|
||||
super(options)
|
||||
}
|
||||
}
|
||||
|
||||
export class td extends BaseElement {
|
||||
constructor(options) {
|
||||
super(options)
|
||||
}
|
||||
}
|
||||
|
||||
export class colgroup extends BaseElement {
|
||||
constructor(options) {
|
||||
super(options)
|
||||
}
|
||||
}
|
||||
|
||||
export class col extends BaseElement {
|
||||
constructor(options) {
|
||||
super(options)
|
||||
}
|
||||
}
|
||||
|
||||
export class form extends BaseElement {
|
||||
constructor(options) {
|
||||
super(options)
|
||||
}
|
||||
}
|
||||
|
||||
export class fieldset extends BaseElement {
|
||||
constructor(options) {
|
||||
super(options)
|
||||
}
|
||||
}
|
||||
|
||||
export class legend extends BaseElement {
|
||||
constructor(options) {
|
||||
super(options)
|
||||
}
|
||||
}
|
||||
|
||||
export class label extends BaseElement {
|
||||
constructor(options) {
|
||||
super(options)
|
||||
}
|
||||
}
|
||||
|
||||
export class strong extends BaseElement {
|
||||
constructor(options) {
|
||||
super(options)
|
||||
}
|
||||
}
|
||||
|
||||
export class small extends BaseElement {
|
||||
constructor(options) {
|
||||
super(options)
|
||||
}
|
||||
}
|
||||
|
||||
export class em extends BaseElement {
|
||||
constructor(options) {
|
||||
super(options)
|
||||
}
|
||||
}
|
||||
|
||||
export class mark extends BaseElement {
|
||||
constructor(options) {
|
||||
super(options)
|
||||
}
|
||||
}
|
||||
|
||||
export class del extends BaseElement {
|
||||
constructor(options) {
|
||||
super(options)
|
||||
}
|
||||
}
|
||||
|
||||
export class ins extends BaseElement {
|
||||
constructor(options) {
|
||||
super(options)
|
||||
}
|
||||
}
|
||||
|
||||
export class code extends BaseElement {
|
||||
constructor(options) {
|
||||
super(options)
|
||||
}
|
||||
}
|
||||
|
||||
export class sub extends BaseElement {
|
||||
constructor(options) {
|
||||
super(options)
|
||||
}
|
||||
}
|
||||
|
||||
export class sup extends BaseElement {
|
||||
constructor(options) {
|
||||
super(options)
|
||||
}
|
||||
}
|
||||
|
||||
export class i extends BaseElement {
|
||||
constructor(options) {
|
||||
super(options)
|
||||
}
|
||||
}
|
||||
|
||||
export class b extends BaseElement {
|
||||
constructor(options) {
|
||||
super(options)
|
||||
}
|
||||
}
|
||||
|
||||
export class u extends BaseElement {
|
||||
constructor(options) {
|
||||
super(options)
|
||||
}
|
||||
}
|
||||
|
||||
export class s extends BaseElement {
|
||||
constructor(options) {
|
||||
super(options)
|
||||
}
|
||||
}
|
||||
|
||||
export class cite extends BaseElement {
|
||||
constructor(options) {
|
||||
super(options)
|
||||
}
|
||||
}
|
||||
|
||||
export class q extends BaseElement {
|
||||
constructor(options) {
|
||||
super(options)
|
||||
}
|
||||
}
|
||||
|
||||
export class blockquote extends BaseElement {
|
||||
constructor(options) {
|
||||
super(options)
|
||||
}
|
||||
}
|
||||
|
||||
export class address extends BaseElement {
|
||||
constructor(options) {
|
||||
super(options)
|
||||
}
|
||||
}
|
||||
|
||||
export class time extends BaseElement {
|
||||
constructor(options) {
|
||||
super(options)
|
||||
}
|
||||
}
|
||||
|
||||
export class dfn extends BaseElement {
|
||||
constructor(options) {
|
||||
super(options)
|
||||
}
|
||||
}
|
||||
|
||||
export class abbr extends BaseElement {
|
||||
constructor(options) {
|
||||
super(options)
|
||||
}
|
||||
}
|
||||
|
||||
export class ruby extends BaseElement {
|
||||
constructor(options) {
|
||||
super(options)
|
||||
}
|
||||
}
|
||||
|
||||
export class rt extends BaseElement {
|
||||
constructor(options) {
|
||||
super(options)
|
||||
}
|
||||
}
|
||||
|
||||
export class rp extends BaseElement {
|
||||
constructor(options) {
|
||||
super(options)
|
||||
}
|
||||
}
|
||||
|
||||
export class bdi extends BaseElement {
|
||||
constructor(options) {
|
||||
super(options)
|
||||
}
|
||||
}
|
||||
|
||||
export class bdo extends BaseElement {
|
||||
constructor(options) {
|
||||
super(options)
|
||||
}
|
||||
}
|
||||
|
||||
export class hr extends BaseElement {
|
||||
constructor(options) {
|
||||
super(options)
|
||||
}
|
||||
}
|
||||
|
||||
export class br extends BaseElement {
|
||||
constructor(options) {
|
||||
super(options)
|
||||
}
|
||||
}
|
||||
|
||||
export class figure extends BaseElement {
|
||||
constructor(options) {
|
||||
super(options)
|
||||
}
|
||||
}
|
||||
|
||||
export class figcaption extends BaseElement {
|
||||
constructor(options) {
|
||||
super(options)
|
||||
}
|
||||
}
|
||||
|
||||
export class video extends BaseElement {
|
||||
constructor(options) {
|
||||
super(options)
|
||||
}
|
||||
}
|
||||
|
||||
export class audio extends BaseElement {
|
||||
constructor(options) {
|
||||
super(options)
|
||||
}
|
||||
}
|
||||
|
||||
export class canvas extends BaseElement {
|
||||
constructor(options) {
|
||||
super(options)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@laniakeasupercluster/widgets",
|
||||
"description": "A simple widgets tracker",
|
||||
"version": "1.0.1",
|
||||
"version": "1.1.8",
|
||||
"type": "module",
|
||||
"main": "main.js",
|
||||
"author": "Laniakea Supercluster <huan0016@gmail.com>",
|
||||
|
||||
7
widgets/avatar.js
Normal file
7
widgets/avatar.js
Normal file
@@ -0,0 +1,7 @@
|
||||
import { createElement } from '../main.js'
|
||||
|
||||
export function Avatar(options) {
|
||||
const element = createElement(options, 'img')
|
||||
element.onerror = () => element.src = '/favicon.ico'
|
||||
return element
|
||||
}
|
||||
56
widgets/dialog.js
Normal file
56
widgets/dialog.js
Normal file
@@ -0,0 +1,56 @@
|
||||
import { createElement } from '../main.js'
|
||||
|
||||
export function Dialog(options) {
|
||||
const element = createElement({
|
||||
tabIndex: 0,
|
||||
style: {
|
||||
position: 'fixed',
|
||||
top: 0,
|
||||
left: 0,
|
||||
zIndex: 1000,
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
backdropFilter: 'blur(5px)',
|
||||
duration: '0.2s',
|
||||
transition: 'all 0.2s'
|
||||
},
|
||||
onclick: async event => {
|
||||
if (event.target !== event.currentTarget) return
|
||||
await event.target.animate([{ opacity: 1 }, { opacity: 0 }], { duration: 100 }).finished
|
||||
await event.target.remove()
|
||||
},
|
||||
onkeydown: async event => {
|
||||
if (event.key !== 'Escape') return
|
||||
await event.target.animate([{ opacity: 1 }, { opacity: 0 }], { duration: 100 }).finished
|
||||
await event.target.remove()
|
||||
},
|
||||
children: [
|
||||
createElement({
|
||||
...options,
|
||||
style: {
|
||||
position: 'fixed',
|
||||
top: '50%',
|
||||
left: '50%',
|
||||
transform: 'translate(-50%, -50%)',
|
||||
backgroundColor: '#fff',
|
||||
borderRadius: '0.5rem',
|
||||
boxShadow: '0 0 1em #ccc',
|
||||
overflow: 'hidden',
|
||||
justifyContent: 'center',
|
||||
...options.style,
|
||||
}
|
||||
})
|
||||
]
|
||||
})
|
||||
const observer = new MutationObserver(mutationsList => {
|
||||
for (const mutation of mutationsList) {
|
||||
if (mutation.type === 'childList' && mutation.addedNodes.length > 0) {
|
||||
element.focus()
|
||||
element.animate([{ opacity: 0 }, { opacity: 1 }], { duration: 100 }).finished
|
||||
return observer.disconnect()
|
||||
}
|
||||
}
|
||||
})
|
||||
observer.observe(document.body, { childList: true, subtree: true })
|
||||
return element
|
||||
}
|
||||
7
widgets/navbar.js
Normal file
7
widgets/navbar.js
Normal file
@@ -0,0 +1,7 @@
|
||||
import { nav } from '../main.js'
|
||||
|
||||
export default function navbar(options) {
|
||||
return nav.bg_gray_500.w_full.h_12.childs([
|
||||
new span({ textContent: 'Hello world!' }),
|
||||
])
|
||||
}
|
||||
Reference in New Issue
Block a user