Compare commits

...

18 Commits

Author SHA1 Message Date
Last
531d4fde46 减少多余 2025-03-25 01:59:38 +08:00
409dc6d032 1.1.8 2024-12-08 13:07:45 +08:00
62f8bfda2d 更新字体 2024-12-08 13:07:23 +08:00
3ad79bc529 简写合并代码 2024-09-06 01:35:45 +08:00
4b82c1afde 模态窗 2024-09-06 01:08:14 +08:00
a229600fc4 精简 2024-09-05 22:46:21 +08:00
9eb9ddb953 修正一位class 2024-09-05 22:30:01 +08:00
b60339796f 清理日志 2024-09-05 22:05:20 +08:00
96e2f9b9f2 修正m 2024-09-05 21:59:31 +08:00
8a87885921 消除 log 2024-09-05 12:49:28 +08:00
c204f8a3d6 修正tagName 2024-09-05 12:33:32 +08:00
cc6099fb7a DEBUG 2024-09-05 10:26:23 +08:00
7d1a12c5b0 修正路径命名 2024-09-05 10:23:29 +08:00
b094b3f914 快捷定位 2024-08-26 01:00:32 +08:00
573e40f4e2 组件拆分 2024-08-24 15:41:55 +08:00
6428d81818 宽高与内外边距的快捷方法 2024-06-27 14:44:29 +08:00
ddfee6403f 增加font 2024-05-17 00:31:40 +08:00
efa23f8d6a 增加font 2024-05-17 00:30:42 +08:00
8 changed files with 401 additions and 335 deletions

134
.gitignore vendored
View File

@@ -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.*

View File

@@ -1,10 +1,23 @@
# 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
```

View File

@@ -1,10 +1,59 @@
import { div, span, pre } from './main.js'
import { div, span, pre, h1, h2, p, ul, li, button } from './main.js'
import { Dialog } from './widgets/dialog.js'
document.body.appendChild(div.w(128).h(64).childs([
document.body.style.fontFamily = 'Arial, sans-serif'
document.body.style.fontSize = '14px'
//document.body.appendChild(div('px-2 w-12 bg- ').childs([
// div.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 => {
document.body.appendChild(pre.p('2rem').bg('#ececec').text(text.substring(0, text.indexOf('fetch'))))
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'))))
})

462
main.js
View File

@@ -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] // 如果是事件则直接赋值
@@ -13,9 +13,178 @@ export function createElement({ innerText, innerHTML, textContent, readOnly, chi
if (readOnly) element.readOnly = readOnly
if (children) children.forEach(child => element.appendChild(child))
// 为元素添加链式操作方法
// 分解类名生成样式, 直接将样式赋予元素
names.map(name => name.split('_')).forEach(item => {
if (item.length === 1) {
const key = item[0]
// 支持快捷定位 position
if (['static', 'fixed', 'absolute', 'relative', 'sticky'].includes(key)) {
element.style.position = key
return
}
// 支持快捷布局 display
if (['block', 'inline-block', 'flex', 'grid'].includes(key)) {
element.style.display = key
return
}
}
// 两个参数的情况下, 第一个参数是样式名, 第二个参数是样式值
if (item.length === 2) {
const [key, value] = item
if (key === 'cursor') {
element.style.cursor = value
return
}
if (key === 'overflow') {
element.style.overflow = value
return
}
if (key === 'bg') {
element.style.backgroundColor = value
return
}
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'],
}
if (styleMap.hasOwnProperty(key)) {
styleMap[key].forEach(style => {
element.style[style] = isNaN(value) ? value : `${value}px`
})
return
}
// 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
}
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
}
}
})
// 元素 增加链式操作方法
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 }
@@ -23,12 +192,25 @@ export function createElement({ innerText, innerHTML, textContent, readOnly, chi
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.p = (padding) => applyStyle(element, 'padding', padding)
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
}
@@ -50,89 +232,108 @@ export class BaseElement {
constructor(options) {
return createElement(options, this.constructor.name)
}
static text(text) {
return createElement({ textContent: text }, this.name)
static text(text, tagName, names) {
return createElement({ textContent: text }, tagName, names)
}
static html(html) {
return createElement({ innerHTML: html }, this.name)
static html(html, tagName, names) {
return createElement({ innerHTML: html }, tagName, names)
}
static childs(childs) {
return createElement({ children: childs }, this.name)
static childs(childs, tagName, names) {
return createElement({ children: childs }, tagName, names)
}
static w(width) {
static w(width, tagName, names) {
if (typeof width === 'number') width = width + 'px'
return createElement({ style: { width } }, this.name)
return createElement({ style: { width } }, tagName, names)
}
static h(height) {
static h(height, tagName, names) {
if (typeof height === 'number') height = height + 'px'
return createElement({ style: { height } }, this.name)
return createElement({ style: { height } }, tagName, names)
}
static bg(color) {
return createElement({ style: { backgroundColor: color } }, this.name)
static bg(color, tagName, names) {
return createElement({ style: { backgroundColor: color } }, tagName, names)
}
static p(...args) {
if (args.length === 1 && typeof args[0] === 'string') {
return createElement({ style: { padding: args[0] } }, this.name)
static p(args, tagName, names) {
if (typeof args === 'string') {
return createElement({ style: { padding: args } }, tagName, names)
}
if (args.length === 1 && typeof args[0] === 'object') {
const { left = 0, right = 0, top = 0, bottom = 0 } = args[0]
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 }, this.name)
return createElement({ style }, tagName, names)
}
if (args.length === 4) {
if (Array.isArray(args)) {
const [top, right, bottom, left] = args
const style = { paddingTop: top, paddingRight: right, paddingBottom: bottom, paddingLeft: left }
return createElement({ style }, this.name)
return createElement({ style }, tagName, names)
}
}
static m(...args) {
if (args.length === 1 && typeof args[0] === 'string') {
return createElement({ style: { margin: args[0] } }, this.name)
static m(args, tagName, names) {
if (typeof args === 'string') {
return createElement({ style: { margin: args } }, tagName, names)
}
if (args.length === 1 && typeof args[0] === 'object') {
const { left = 0, right = 0, top = 0, bottom = 0 } = args[0]
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 }, this.name)
return createElement({ style }, tagName, names)
}
if (args.length === 4) {
if (Array.isArray(args)) {
const [top, right, bottom, left] = args
const style = { marginTop: top, marginRight: right, marginBottom: bottom, marginLeft: left }
return createElement({ style }, this.name)
return createElement({ style }, tagName, names)
}
}
static mx(value) {
static mx(value, tagName, names) {
if (typeof value === 'number') value = value + 'px'
return createElement({ style: { marginLeft: value, marginRight: value } }, this.name)
return createElement({ style: { marginLeft: value, marginRight: value } }, tagName, names)
}
static my(value) {
static my(value, tagName, names) {
if (typeof value === 'number') value = value + 'px'
return createElement({ style: { marginTop: value, marginBottom: value } }, this.name)
return createElement({ style: { marginTop: value, marginBottom: value } }, tagName, names)
}
static px(value) {
static px(value, tagName, names) {
if (typeof value === 'number') value = value + 'px'
return createElement({ style: { paddingLeft: value, paddingRight: value } }, this.name)
return createElement({ style: { paddingLeft: value, paddingRight: value } }, tagName, names)
}
static py(value) {
static py(value, tagName, names) {
if (typeof value === 'number') value = value + 'px'
return createElement({ style: { paddingTop: value, paddingBottom: value } }, this.name)
return createElement({ style: { paddingTop: value, paddingBottom: value } }, tagName, names)
}
static grid(options) {
return createElement({ style: { display: 'grid', ...options } }, this.name)
static radius(radius, tagName, names) {
return createElement({ style: { borderRadius: radius } }, tagName, names)
}
static flex(options) {
return createElement({ style: { display: 'flex', ...options } }, this.name)
static font(font, tagName, names) {
return createElement({ style: { font } }, tagName, names)
}
static radius(radius) {
return createElement({ style: { borderRadius: radius } }, this.name)
static color(color, tagName, names) {
return createElement({ style: { color } }, tagName, names)
}
}
export class div extends BaseElement {
constructor(options) {
super(options)
}
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)
@@ -151,12 +352,6 @@ export class img extends BaseElement {
}
}
export class pre extends BaseElement {
constructor(options) {
super(options)
}
}
export class input extends BaseElement {
constructor(options) {
super(options)
@@ -223,89 +418,22 @@ export class footer extends BaseElement {
}
}
export class h1 extends BaseElement {
constructor(options) {
super(options)
}
}
export const a = createProxy('a')
export const p = createProxy('p')
export class h2 extends BaseElement {
constructor(options) {
super(options)
}
}
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 class h3 extends BaseElement {
constructor(options) {
super(options)
}
}
export class h4 extends BaseElement {
constructor(options) {
super(options)
}
}
export class h5 extends BaseElement {
constructor(options) {
super(options)
}
}
export class h6 extends BaseElement {
constructor(options) {
super(options)
}
}
export class p extends BaseElement {
constructor(options) {
super(options)
}
}
export class a extends BaseElement {
constructor(options) {
super(options)
}
}
export class ul extends BaseElement {
constructor(options) {
super(options)
}
}
export class ol extends BaseElement {
constructor(options) {
super(options)
}
}
export class li extends BaseElement {
constructor(options) {
super(options)
}
}
export class dl extends BaseElement {
constructor(options) {
super(options)
}
}
export class dt extends BaseElement {
constructor(options) {
super(options)
}
}
export class dd extends BaseElement {
constructor(options) {
super(options)
}
}
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) {
@@ -582,65 +710,3 @@ export class canvas extends BaseElement {
super(options)
}
}
export function Avatar(options) {
const element = createElement(options, 'img')
element.onerror = () => element.src = '/favicon.ico'
return element
}
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,
}
})
]
})
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
}

View File

@@ -1,7 +1,7 @@
{
"name": "@laniakeasupercluster/widgets",
"description": "A simple widgets tracker",
"version": "1.1.3",
"version": "1.1.8",
"type": "module",
"main": "main.js",
"author": "Laniakea Supercluster <huan0016@gmail.com>",

7
widgets/avatar.js Normal file
View 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
View 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
View 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!' }),
])
}