在使用本组件库之前,你需要了解自定义元素的相关知识,简易入门可参考下一节,更多相关知识可参考教程、MDN或Vue,只需知道如何使用即可,无需了解如何创建
快速开始
你可在 Stackblitz 中快速开始尝试:
安装
目前只发布了 alpha 版本,提供以下库:
@lun-web/utils
:js 工具函数库@lun-web/core
:提供组件功能的钩子函数库@lun-web/plugins
: 为 JSX 或 Vue template 提供自定义指令@lun-web/components
:组件库,其依赖于上面三个@lun-web/theme
:主题库,其依赖于组件库@lun-web/react
:为 React 封装的组件库,详细见下文React 中使用
根据需要并安装对应的库
npm i vue # 虽然vue从3.2开始支持自定义元素,但最好使用最新版,中间修复了很多相关问题
npm i @lun-web/components
npm i @lun-web/theme # 如果需要主题,则安装此库
npm i @lun-web/react # React 19可选此库,之前的版本则必须使用此库导出的组件
React 中使用
React 是目前流行 web 框架中最晚支持customElement
的,其他框架早已支持,详情见Custom Elements Everywhere
。在 React 18 及之前版本中使用自定义元素,只会将属性设置为 attribute,不会自动设置为元素的 property,也无法使用onXXX
监听自定义元素的事件
React 19 已经支持customElement
,本文档使用的是 React 19,在文档的 React 代码中可正常使用自定义元素。
对于 React 19 之前的版本,我们需要手动封装一层。@lun-web/react
将@lun-web/components
中的每个组件都封装成了 React 组件,在 useLayoutEffect 中将属性和事件绑定到元素上,使之能够正常工作。使用这些组件无需在应用入口手动调用 define 函数,组件内部会自行调用
import { LInput } from '@lun-web/react';
export default function () {
return <LInput onUpdate={() => {}} />;
}
React 19 中你仍可以使用这个包。在安装后其会检测当前安装的 React 版本,如果是 19 则使用另一个入口,避免使用了包装的版本,属性和事件的设置将完全由 React 处理。
全量引入
import { GlobalStaticConfig, defineAllComponents } from '@lun-web/components';
import {
importCommonTheme
importAllColors,
importAllP3Colors,
importAllThemes
} from '@lun-web/theme';
// 如果使用了日期组件,则必须引入日期处理预设。内部提供了dayjs实现,但需要用户手动引入
import '@lun-web/core/date-dayjs';
// 如果你想要使用其他的日期处理库,可参考左侧“数字和日期处理预设”一栏
// 定义组件前设置想要更改的全局静态配置
GlobalStaticConfig.xx = xx;
// 引入所有的预设主题色
importAllColors(); // 如果需要自定义主题色请参考顶部导航栏中的“调色”一栏
// 如果需要P3广色域支持则额外调用如下函数,当支持的时候会使用display-p3
// importAllP3Colors();
// 引入所有预设主题
importAllThemes();
// 定义全部组件
defineAllComponents();
- 全局静态配置需要在组件被使用前修改,但最好在定义组件前就统一修改,因为
namespace
在定义时就会使用 - 全量引入的组件会使用全局静态配置中的
namespace
加上组件本身的名字作为命名,例如namespace
默认为l
,那么button
组件的默认名字便是l-button
。定义组件后你便可以在任何地方使用它们 - 在 SSR 的情况下,为保证视觉效果,你需要自行向页面添加类似于下面的样式,在组件未定义时隐藏它们,以避免它们的子元素被渲染出来而造成闪烁
:not(:defined) {
visibility: hidden;
}
/** or */
:not(:defined) {
opacity: 0;
}
动态引入
import { autoDefine } from '@lun-web/components';
import { autoImportTheme } from '@lun-web/theme';
autoImportTheme();
autoDefine();
动态引入会自动检测页面上的元素并自动加载引入,动态引入无法 Tree Shaking,但会动态加载需要的脚本
自定义引入
import { defineButton } from '@lun-web/components';
import '@lun-web/components/define/theme-provider'; // 也可以直接通过import副作用来调用
// 第二个参数用于给该组件依赖的组件自定义命名
defineButton('my-button', {
spin: 'my-spin',
});
// 此后你便可以直接使用<my-button></my-button>, <my-spin></my-spin>以及<l-theme-provider></l-theme-provider>
每个组件都导出了单独的 define 函数,用于单独引入该组件,没有使用的组件最终不会被打包。
第一个参数用于指定该组件命名,第二个参数用于指定其依赖的组件命名,不传参则是使用默认命名(全局配置)
每个组件的主题提供了单独的 import 函数
import { importButtonBasicTheme, importButtonSurfaceTheme } from '@lun-web/theme';
importButtonBasicTheme();
importButtonSurfaceTheme();
主题的引入除了组件维度的单独引入,还可以根据类型直接全部引入
import {
importCommonTheme, // 所有组件的公共样式
importBasicTheme, // 所有组件的基础主题
importSurfaceTheme,
importOutlineTheme,
importSoftTheme,
importSolidTheme,
} from '@lun-web/theme';
注
自定义引入需要注意组件的引入顺序,这在 SSR 场景下尤为重要,最先使用的组件需要最先定义,例如你可能需要在其他组件定义前先调用defineThemeProvider
和defineTeleportHolder()
,有明显父子关系的组件也需要注意,例如form
和form-item
需要这么做的原因是,大部分组件都有继承关系,部分状态由父组件提供。在 SSR 场景下,页面上的元素已经存在,如果子组件先被定义,此时它无法探测到父组件(组件未被定义时是无效组件),等父组件再被定义时子组件也不会被更新,便会出现问题
CDN 引入
<script src="https://cdn.jsdelivr.net/npm/vue@3/dist/vue.global.js"></script>
<script src="https://cdn.jsdelivr.net/npm/@lun-web/utils/dist/lun-web-utils.iife.js"></script>
<script src="https://cdn.jsdelivr.net/npm/@lun-web/core/dist/lun-web-core.iife.js"></script>
<script src="https://cdn.jsdelivr.net/npm/@lun-web/plugins/dist/lun-web-plugins-vue.iife.js"></script>
<script src="https://cdn.jsdelivr.net/npm/@lun-web/components/dist/lun-web-components.iife.js"></script>
<script src="https://cdn.jsdelivr.net/npm/@lun-web/theme/dist/lun-web-theme.iife.js"></script>
CDN 直接引入只提供开发打包版本,不建议在生产环境使用,引入后在全局即可访问
<script>
LunWebTheme.importAllColors();
LunWebTheme.importAllP3Colors();
LunWebTheme.importAllThemes();
LunWebComponents.defineAllComponents();
</script>
你可以在CodePen快速尝试
若因网络问题国内无法访问 jsdelivr,可尝试将cdn.jsdelivr.net
更换为jsd.onmicrosoft.cn
TS 支持
组件库的组件本身具有完整的类型支持,针对不同的框架,目前提供了以下类型定义:
@lun-web/components/elements-types-vue
: Vue template 以及 JSX 的元素类型@lun-web/components/elements-types-react
: React JSX 元素类型@lun-web/components/elements-types-html
: document.createElement 等的元素类型支持
你需要在 TS 配置文件中的types
字段中对应引入它们
需要注意的是,提供的类型文件是针对默认namespace
,也就是l-button
, l-input
等以l
开头的组件,如果你自定义了命名空间,可以仿造以下示例编写
// Vue
import * as Vue from 'vue';
import * as LunComps from '@lun-web/components';
// Vue template
declare module 'vue' {
interface GlobalComponents {
LButton: LunComps.DefineVueCustomElement<LunComps.iButton, LunComps.ButtonEventMap, keyof LunComps.ButtonSetupProps>;
}
}
// Vue JSX
declare module 'vue/jsx-runtime' {
namespace JSX {
interface IntrinsicElements {
'l-button': Vue.HTMLAttributes & Vue.PublicProps & LunComps.ButtonProps;
}
}
}
// React
import * as React from 'react';
import * as LunComps from '@lun-web/components';
declare module 'react/jsx-runtime' {
namespace JSX {
interface IntrinsicElements {
'l-button': React.HTMLAttributes<HTMLElement> &
React.RefAttributes<LunComps.iButton> &
LunComps.ButtonProps;
}
}
}
每个组件都有一个 class 和两个类型,注意区分
import { Button, tButton, iButton } from '@lun-web/components';
Button; // 组件的class,这是值,不是类型,一般用不到
tButton; // 组件class的类型,相当于typeof Button
iButton; // 组件实例的类型,相当于InstanceType<tButton>,这是实际DOM元素的类型
当需要组件实例类型时,你可以像下面这样使用:
import { iButton } from '@lun-web/components';
const button = document.querySelector('l-button') as iButton;
button.asyncHandler = () => console.log('');
const buttonRef = ref<iButton>();
const render = () => <l-button ref={buttonRef}></l-button>;
兼容性
至少需要兼容customElement
, 考虑到以下特性版本要求不高, 若不支持你需要自行 polyfill 或不使用某些特性,CSS 不兼容可考虑使用全局配置stylePreprocessor
处理或自行编写样式
- customElement
- 54
- 79
- 63
- 10.3
- BigInt
- 67
- 79
- 68
- 14
- flatMap
- 69
- 79
- 62
- 12
- fromEntries
- 73
- 79
- 63
- 12.1
- Named capture group
- 64
- 79
- 78
- 11.1
- IntersectionObserver
- 58
- 16
- 55
- 12.1
- ResizeObserver
- 64
- 79
- 69
- 13.1
- CSS gap in flex
- 84
- 84
- 63
- 14.1
- CSS :where :is
- 88
- 88
- 78
- 14
- CSS Logical Properties
- 89
- 89
- 66
- 15
当前用户代理信息
某些特性需要的版本较高, 但它们在内部有做兼容处理或替代方案, 渐进式增强能给用户带来更好的体验,详细信息如下
注
Javascript
Promise.try - 128
- 128
- 135
- 18.2
Promise.withResolvers - 119
- 119
- 121
- 17.4
Set.intersection - 122
- 122
- 127
- 17
Web API
adoptedStyleSheets - 73
- 79
- 101
- 16.4
CustomStateSet - 90
- 90
- 126
- 17.4
dialog
: Dialog- 37
- 79
- 98
- 15.4
HTMLSlotElement.assign - 86
- 86
- 92
- 16.4
Input cancel Event - 113
- 113
- 91
- 16.4
popover
: Popover API- 114
- 114
- 125
- 17
showOpenFilePicker - 86
- 86
- x
- x
mentions
: getComposedRanges- x
- x
- 134
- 17
CSS
CSS anchor positioning - 125
- 125
- x
- x
CSS auto height transition - 129
- 129
- x
- x
CSS color() - 111
- 111
- 113
- 15
CSS content-visibility - 85
- 85
- 125
- 18
CSS layer - 99
- 99
- 97
- 15.4
CSS subgrid - 117
- 117
- 71
- 16
某些特性无法或不好做兼容,但它们影响不大,不使用那些功能即可
doc-pip
: DocumentPictureInPicture- 116
- 116
- x
- x
mentions
: CSS highlight- 105
- 105
- 132
- 17.2
input
: CSS overflow-clip-margin- 90
- 90
- 102
- x