diff --git a/docs/.layui/detail/demo.md b/docs/.layui/detail/demo.md
new file mode 100644
index 00000000..5c3a8d97
--- /dev/null
+++ b/docs/.layui/detail/demo.md
@@ -0,0 +1,21 @@
+
+
+
+
+示例1
+
+
+
+
+
+示例2
+
+
+
+
diff --git a/docs/.layui/examples/demo.md b/docs/.layui/examples/demo.md
index f6eebb5e..30e2a9fd 100644
--- a/docs/.layui/examples/demo.md
+++ b/docs/.layui/examples/demo.md
@@ -1,5 +1,3 @@
-
-
-
-
-示例标题
-
-
-
-
\ No newline at end of file
diff --git a/docs/.layui/index.md b/docs/.layui/index.md
index 10045b78..ff751287 100644
--- a/docs/.layui/index.md
+++ b/docs/.layui/index.md
@@ -2,10 +2,10 @@
title: 某某组件 MOD_NAME
toc: true
---
-
+
# 某某组件
-> 某某组件 `MOD_NAME`
+> 某某组件 `MOD_NAME`
示例
@@ -27,8 +27,8 @@ toc: true
- 参数 `options` : 基础属性配置项。[#详见属性](#options)
-属性
+属性
{{- d.include("/MOD_NAME/detail/options.md") }}
-
\ No newline at end of file
+
diff --git a/docs/.layui/prompt.txt b/docs/.layui/prompt.txt
new file mode 100644
index 00000000..7ff69634
--- /dev/null
+++ b/docs/.layui/prompt.txt
@@ -0,0 +1,34 @@
+> 自动生成组件文档提示词
+
+# 角色
+你是一位顶级的前端开发专家,能够高效、准确地为 JavaScript 组件代码生成对应的接口文档,并且严格遵循给定的模板规则。
+
+## 组件
+本次生成的组件名称为: input (统一简称为 MOD_NAME)
+
+## 任务
+按照 `/.layui` 目录给定的模板,为 `/src/modules/MOD_NAME.js` 生成完整的文档,并保存在 `/docs/MOD_NAME/` 目录。文档要采用 `HTML+Markdown+laytpl` 混合编写,其中 laytpl 为视图引擎(类似于 ejs,可为文档引入子模版,如:`{{- d.include("/MOD_NAME/detail/demo.md") }}`)。
+
+### 文档模板介绍
+- `index.md`: 组件文档主文件,包含示例、API、属性等完整内容。
+- `detail/`: 目录存放文档子模板。一般在 index.md 内容过大时,可将内容碎片放置在该目录中,index.md 只需引用即可。
+ - `detail/demo.md`: 组件示例主模板,在 `index.md` 中引入。
+ - `detail/options.md`: 组件 `render()` 方法接受的参数配置项。
+- `examples/`: 目录存放组件示例文件,在 `detail/demo.md` 中引入。
+
+### 文档内容要求
+- `index.md` 主文档内容主要包含以下层级:
+```markdown
+## 示例
+## API
+### 渲染
+### 属性
+## 事件(如果有的话)
+```
+其中,API 中列举的所有方法进行介绍,如果方法传入的参数(如 opts)是一个选项,需按照表格的方式展示,如:
+```markdown
+| opts | 描述 | 类型 | 默认值 |
+| --- | --- | --- | --- |
+| 内容 | 内容 | 内容 | 内容 |
+```
+- `detail/options.md` 需按照当前已有的 HTML + Markdown 模板混合编写。
diff --git a/docs/component/detail/options.md b/docs/component/detail/options.md
new file mode 100644
index 00000000..6045945f
--- /dev/null
+++ b/docs/component/detail/options.md
@@ -0,0 +1,197 @@
+
+
+
+
+
+
+
+
+
+ | 属性名 |
+ 描述 |
+ 类型 |
+ 默认值 |
+
+
+
+
+| name |
+
+
+组件名称。如 `name:'tabs'`,在使用组件时,即可通过 `layui.tabs` 获得该组件。注:*组件名必须唯一*。
+
+ |
+string |
+- |
+
+
+| config |
+
+
+定义组件渲染时的默认配置项。
+
+ |
+object |
+- |
+
+
+| CONST |
+
+
+通用常量集,一般存放固定字符,如类名等。如:
+
+```
+CONST: {
+ ELEM: 'layui-tabs',
+}
+```
+
+上述常量可通过 `component.CONST.ELEM` 获得。
+
+ |
+object |
+- |
+
+
+| isRenderWithoutElem |
+
+
+渲染是否无需指定目标元素。即无需设置 `elem` 选项,一般用于渲染即显示的组件类型。
+
+ |
+boolean |
+
+
+`false`
+
+ |
+
+
+| isRenderOnEvent |
+
+
+渲染是否由事件触发。如 `dropdown` 这类通过点击触发的组件,那么应该设置为 `true`;而诸如 `tabs` 这类初始即展示的组件,则应该设置为 `false`。*推荐根据组件类型始终显式设置对应值*。
+
+ |
+boolean |
+
+
+`true`
+
+ |
+
+
+| isDeepReload |
+
+
+组件重载时是否允许深度重载,即对重载时选项进行深度合并。
+
+ |
+boolean |
+
+
+`false`
+
+ |
+
+
+|
+
+
+
+[回调函数](#options.callback)
+
+
+
+ |
+
+
+| render |
+
+
+组件渲染的逻辑。
+
+```js
+render: function() {
+ // 组件的容器构建、插入等
+ // …
+}
+```
+
+也可以通过原型 `component.Class.prototype.render` 进行定义。
+
+ |
+
+
+| beforeInit |
+
+
+组件初始化之前的回调函数。
+
+```js
+beforeInit: function(options) {
+ console.log(options); // 获得组件初始化前的配置项
+}
+```
+
+ |
+
+
+| beforeRender |
+
+
+渲染之前的回调函数。
+
+```js
+beforeRender: function(options) {
+ console.log(options); // 获得组件渲染前的配置项
+}
+```
+
+ |
+
+
+| extendsInstance |
+
+
+扩展组件渲染的实例对象的回调函数。如:
+
+```js
+extendsInstance: function(that) {
+ return {
+ // 关闭组件
+ close: function() {
+ that.remove(); // 调用组件原型中的 remove 方法
+ }
+ }
+}
+```
+
+当组件渲染时,即可通过它返回的对象调用实例方法:
+
+```js
+var inst = xxx.render(); // 某组件渲染
+inst.close(); // 关闭某组件
+```
+
+ |
+
+
+| events |
+
+
+定义组件各内部事件。
+
+```js
+events: function() {
+ // 亦可包含针对组件的 window, document 等全局事件
+ // …
+}
+```
+
+也可以通过原型 `component.Class.prototype.events` 进行定义。
+
+ |
+
+
+
diff --git a/docs/component/index.md b/docs/component/index.md
new file mode 100644
index 00000000..11fdb927
--- /dev/null
+++ b/docs/component/index.md
@@ -0,0 +1,289 @@
+---
+title: 组件构建器 component
+toc: true
+---
+
+# 组件构建器 2.10+
+
+> 组件构建器 `component` 是 2.10 版本新增的重要模块,旨在为 Layui 2 系列版本逐步构建统一 API 规范的组件。
+
+API
+
+| API | 描述 |
+| --- | --- |
+| [layui.component(options)](#create) | 创建组件 |
+
+创建组件
+
+`layui.component(options);`
+
+- 参数 `options` : 基础属性配置项。[#详见属性](#options)
+
+该方法返回一个对象,包含用于组件对外的基础接口,如:组件渲染、重载、事件操作,及构造函数等等。用法示例:
+
+```js
+/**
+ * tabs
+ * 标签页组件
+ */
+layui.define('component', function(exports) {
+ // 创建组件
+ var component = layui.component({
+ name: 'tabs', // 组件名称
+ config: { // 组件默认配置项
+ // …
+ },
+ render: function() { // 组件渲染逻辑
+ // …
+ },
+ // 其他选项
+ });
+
+ // 将创建组件时返回的 `component` 对象作为组件的接口输出
+ // 组件将继承通用的基础接口,如 render, reload, set 等方法
+ exports(component.CONST.MOD_NAME, component);
+});
+```
+
+属性配置
+
+
+{{- d.include("/component/detail/options.md") }}
+
+
+基础接口 🔥
+
+通过 `component` 模块创建的组件,均会继承内部定义的「基础对外接口」和「组件渲染的通用选项」。
+
+| 接口 | 描述 |
+| --- | --- |
+| [component.render(options)](#render) | 组件渲染 |
+| [component.reload(id, options)](#reload) | 组件重载 |
+| [component.set(options)](#set) | 设置组件渲染时的全局配置项 |
+| [component.on(\'event(filter)\', callback)](#on) | 组件的自定义事件 |
+| [component.getThis(id)](#getThis) | 获取指定组件的实例对象 |
+| component.index | 获得组件的自增索引 |
+| component.config | 获得组件渲染时的全局配置项。一般通过 `set` 方法设置 |
+| component.cache | 获得组件的缓存数据集。如组件实例 ID 集 |
+| [component.CONST](#CONST) | 获得组件的通用常量集。如 `MOD_NAME` 等 |
+| [component.Class](#Class) | 获得组件的构造函数。一般用于扩展原型方法 |
+
+> 😊 注:上表中的 `component` 为组件的基础对象,实际使用时,请根据实组件名称进行替换。如 `tabs` 组件,对应的接口则为:`tabs.render(options)` `tabs.on('event(filter)', callback)` 等。
+
+组件渲染
+
+`component.render(options)`
+
+- 参数 `options` : 组件渲染的配置项。可继承的通用选项见下表:
+
+| 选项 | 描述 |
+| --- | --- |
+| elem | 件渲染指定的目标元素选择器或 DOM 对象 |
+| id | 组件渲染的唯一实例 ID |
+| show | 是否初始即渲染组件。通常结合创建组件设定的 `isRenderOnEvent` 选项决定是否启用 |
+
+更多渲染时的选项则由各组件内部单独定义,具体可查阅各组件对应的文档。
+
+```js
+// 以 tabs 组件为例
+// 渲染
+tabs.render({
+ elem: '#id',
+ // …
+});
+```
+
+组件重载
+
+`component.reload(id, options)`
+
+- 参数 `id` : 组件实例 ID。
+- 参数 `options` : 组件重载的配置项。
+
+该方法可实现对组件的完整重载。部分组件通常还提供了「仅数据重载」,具体可参考各组件对应的文档。
+
+```js
+// 以 tabs 组件为例
+// 重载 tabs 组件实例
+tabs.reload('id', {
+ // …
+})
+```
+
+全局设置
+
+`component.set(options)`
+
+- 参数 `options` : 组件渲染的配置项。
+
+该方法用于全局设置组件渲染时的默认配置项,需在组件渲染之前执行。
+
+```js
+// 以 tabs 组件为例
+// 全局设置。后续所有渲染均会生效,除非对选项进行覆盖
+tabs.set({
+ trigger: 'mouseenter' // 默认的触发事件
+ // …
+});
+
+// 渲染实例 1
+tabs.render({ id: 'id1'}); // 继承全局设置
+
+// 渲染实例 2
+tabs.render({
+ id: 'id2',
+ trigger: 'click' // 覆盖全局的触发事件
+});
+```
+
+事件定义
+
+`component.on('event(id)', callback)`
+
+- 参数 `event(id)` : 是事件的特定结构。 `event` 为事件名,支持的事件见各组件列表。`id` 为组件的实例 ID。
+- 参数 `callback` : 事件回调函数。返回的参数由各组件内部单独定义。
+
+具体事件一般由组件内部单独定义,具体可查看各组件对应的文档。
+
+```js
+// 以 tabs 组件为例:
+// 组件渲染成功后的事件
+tabs.on('afterRender(id)', function(data) {
+ console.log(obj);
+});
+```
+
+获取实例
+
+`component.getThis(id)`
+
+- 参数 `id` : 组件的实例 ID。
+
+该方法可获取组件渲染时对应的实例,以调用组件内部的原型方法,一般用于在外部对组件进行扩展或重构。
+
+```js
+// 以 tabs 组件为例
+var tabInstance = tabs.getThis('id');
+// 调用内部的标签滚动方法
+tabInstance.roll();
+```
+
+基础常量
+
+`component.CONST`
+
+获取组件的通用常量集,一般存放固定字符,如类名等。
+
+```js
+// 基础常量如下
+component.CONST.MOD_NAME; // 组件名称
+component.CONST.MOD_INDEX; // 组件自增索引
+component.CONST.CLASS_THIS; // layui-this
+component.CONST.CLASS_SHOW; // layui-show
+component.CONST.CLASS_HIDE; // layui-hide
+component.CONST.CLASS_HIDEV; // layui-hide-v
+component.CONST.CLASS_DISABLED; // layui-disabled
+component.CONST.CLASS_NONE; // layui-none
+
+// 更多常量一般在各组件内部单独定义,以 tabs 组件为例
+tabs.CONST.ELEM; // layui-tabs
+```
+
+扩展接口
+
+除上述「基础接口」外,你也可以对接口进行任意扩展,如:
+
+```js
+/**
+ * 定义组件
+ */
+layui.define('component', function(exports) {
+ // 创建组件
+ var component = layui.component({
+ name: 'test',
+ // …
+ });
+ // 扩展组件接口
+ layui.$.extend(component, {
+ // 以扩展一个关闭组件面板的接口为例
+ close: function(id) {
+ var that = component.getThis(id);
+ if(!that) return this;
+ that.remove(obj); // 调用原型中的 remove 方法
+ }
+ });
+ // 输出组件接口
+ exports(component.CONST.MOD_NAME, component);
+});
+```
+
+```js
+/**
+ * 使用组件(以上述定义的 test 组件为例)
+ */
+layui.use('test', function() {
+ var test = layui.test;
+ // 渲染组件
+ test.render({
+ elem: '#id',
+ id: 'test-1'
+ });
+ // 关闭组件面板(通常在某个事件中使用)
+ test.close('test-1');
+});
+```
+
+扩展原型
+
+`component.Class`
+
+当通过 `layui.component()` 方法创建一个新的组件时,通常需借助 `Class` 构造函数扩展组件原型,以灵活实现组件的个性化定制。但一般不推荐重写 `component.js` 原型中已经定义的基础方法,如:`init, reload, cache`
+
+```
+/**
+ * 定义组件
+ */
+layui.define('component', function(exports) {
+ // 创建组件
+ var component = layui.component({
+ name: '', // 组件名称
+ // …
+ });
+
+ // 获取构造器
+ var Class = component.Class;
+
+ // 扩展原型
+ Class.prototype.xxx = function() {
+ // …
+ };
+ Class.prototype.aaa = function() {
+ // …
+ };
+
+ // 输出组件接口
+ exports(component.CONST.MOD_NAME, component);
+});
+```
+
+通过 `Class` 构造函数也可以对某个组件的原型进行重构,但一般不推荐,因为这可能破坏组件的基础功能。
+
+```
+// 以 tabs 组件为例
+var tabs = layui.tabs;
+
+// 获得 tabs 组件构造函数
+var Class = tabs.Class;
+
+// 重构 tabs 组件内部的 xxx 方法(不推荐)
+Class.prototype.xxx = function() {
+ // …
+};
+```
+
+您也可以直接参考 tabs 组件源码中关于扩展原型的具体实践。
+
+## 💖 心语
+
+Layui 由于早前欠缺统筹性思维,很多组件自成一体,使得无法对组件进行很好的统一管理。随着版本的迭代,我们也一直在努力尝试改善这一问题,但很多时候,为了向下兼容而不得不保留许多旧有的特性。`component` 模块的初衷正是为了确保组件的一致性,如核心逻辑和 API 设计等,其目的也是为了让 2.x 系列版本尽可能地减少些遗憾,让 Layui 在既定的范式中保持前行。
+
diff --git a/docs/index.md b/docs/index.md
index 2b44ada8..2b0c421b 100644
--- a/docs/index.md
+++ b/docs/index.md
@@ -18,7 +18,7 @@ toc: true
用途:用于更简单快速地构建网页界面 |
- | 环境:全部主流 Web 浏览器(IE8 以下除外) |
+ 环境:详见不同版本的浏览器兼容规划 N |
特性:原生态开发 / 轻量级模块化 / 外简内丰 / 开箱即用 |
diff --git a/docs/tab/index.md b/docs/tab/index.md
index 3f5d6a4d..d67c8ab0 100644
--- a/docs/tab/index.md
+++ b/docs/tab/index.md
@@ -2,8 +2,10 @@
title: 选项卡组件 tab
toc: true
---
-
-# 选项卡组件
+
+# ~~选项卡组件~~
+
+> 📣 升级提示:我们在 2.10 版本中新增了全新的 tabs 标签页组件,用于替代原 `element` 模块中的 `tab` 组件,建议过渡到全新的 tabs 组件,旧的 ~~tab~~ 组件将在后续合适的版本中移除。 [前往全新 tabs 组件](../tabs/)
> 选项卡组件 `tab` 是指可进行标签页切换的一段容器,常广泛应用于 Web 页面。由于为了向下兼容等诸多历史原因,在 2.x 版本中,`tab` 组件属于 `element` 模块的子集。
@@ -159,7 +161,7 @@ tab 组件会在元素加载完毕后,自动对 tab 元素完成一次渲染
```
-
+
diff --git a/docs/tabs/examples/beforeClose.md b/docs/tabs/examples/beforeClose.md
new file mode 100644
index 00000000..eecc5749
--- /dev/null
+++ b/docs/tabs/examples/beforeClose.md
@@ -0,0 +1,44 @@
+
+
+
+
Tab Content-1
+
Tab Content-2
+
Tab Content-3
+
Tab Content-4
+
Tab Content-5
+
Tab Content-6
+
+
+
+本示例演示:删除标签之前,弹出确认提示。
+
+
+
diff --git a/docs/tabs/examples/card.md b/docs/tabs/examples/card.md
new file mode 100644
index 00000000..a538c9a7
--- /dev/null
+++ b/docs/tabs/examples/card.md
@@ -0,0 +1,54 @@
+#### 普通卡片
+
+
+
+
+
内容-1
+
内容-2
+
内容-3
+
内容-4
+
内容-5
+
内容-6
+
+
+
+#### 边框卡片
+
+
+
+
+
+
+
+
+
+
2
+
3
+
4
+
5
+
6
+
+
+
+
diff --git a/docs/tabs/examples/custom.md b/docs/tabs/examples/custom.md
new file mode 100644
index 00000000..f3ace3cf
--- /dev/null
+++ b/docs/tabs/examples/custom.md
@@ -0,0 +1,30 @@
+
+
+
+
+
内容 111
+
内容 222
+
内容 333
+
+
+
+
+
diff --git a/docs/tabs/examples/demo.md b/docs/tabs/examples/demo.md
new file mode 100644
index 00000000..ebcd7daa
--- /dev/null
+++ b/docs/tabs/examples/demo.md
@@ -0,0 +1,109 @@
+
+
+
+
Tab Content-1
+
Tab Content-2
+
Tab Content-3
+
Tab Content-4
+
Tab Content-5
+
Tab Content-6
+
+
+
+🔔 操作提示:在「标签头部」点击鼠标右键,可开启标签操作的更多实用演示
+
+
+
+
+
+
+
+
+
+
+
diff --git a/docs/tabs/examples/method.md b/docs/tabs/examples/method.md
new file mode 100644
index 00000000..8743753b
--- /dev/null
+++ b/docs/tabs/examples/method.md
@@ -0,0 +1,26 @@
+
+
+
+
diff --git a/docs/tabs/examples/nest.md b/docs/tabs/examples/nest.md
new file mode 100644
index 00000000..95f501a6
--- /dev/null
+++ b/docs/tabs/examples/nest.md
@@ -0,0 +1,40 @@
+
+
+
diff --git a/docs/tabs/examples/trigger.md b/docs/tabs/examples/trigger.md
new file mode 100644
index 00000000..9523fa9d
--- /dev/null
+++ b/docs/tabs/examples/trigger.md
@@ -0,0 +1,43 @@
+#### mouseenter 触发
+
+
+
+
+
内容-1
+
内容-2
+
内容-3
+
内容-4
+
内容-5
+
内容-6
+
+
+
+#### mousedown 触发
+
+
+
+
+
内容-1
+
内容-2
+
内容-3
+
内容-4
+
内容-5
+
内容-6
+
+
+
+
diff --git a/docs/tabs/index.md b/docs/tabs/index.md
new file mode 100644
index 00000000..37821874
--- /dev/null
+++ b/docs/tabs/index.md
@@ -0,0 +1,350 @@
+---
+title: 标签页组件 tabs
+toc: true
+---
+
+# 标签页组件 2.10+
+
+> `tabs` 是 2.10 版本新增的加强型组件,可替代原 `element` 模块中的 `tab` 组件。tabs 广泛应用于 Web 页面。
+
+示例
+
+
+
+
+{{- d.include("/tabs/detail/demo.md") }}
+
+
+API
+
+| API | 描述 |
+| --- | --- |
+| var tabs = layui.tabs | 获得 `tabs` 模块。|
+| [基础接口](../component/#export) | 该组件由 `component` 构建,因此继承其提供的基础接口。|
+| [tabs.render(options)](#render) | tabs 组件渲染,核心方法。|
+| [tabs.add(id, opts)](#add) | 新增一个标签项。|
+| [tabs.close(id, index, force)](#close) | 关闭指定的标签项。|
+| [tabs.closeMult(id, mode, index)](#closeMult) | 批量关闭标签项。|
+| [tabs.change(id, index, force)](#change) | 切换到指定的标签项。|
+| [tabs.data(id)](#data) | 获取当前标签页相关数据。|
+| [tabs.getHeaderItem(id, index)](#getHeaderItem) | 获取指定的标签头部项。|
+| [tabs.getBodyItem(id, index)](#getBodyItem) | 获取指定的标签内容项。|
+| [tabs.refresh(id)](#refresh) | 刷新标签视图。 |
+
+渲染
+
+`tabs.render(options)`
+
+- 参数 `options` : 基础属性配置项。[#详见属性](#options)
+
+组件支持以下三种渲染方式:
+
+#### 1. 自动渲染
+
+tabs 组件会在元素加载完毕后,自动对 `class="layui-tabs"` 目标元素完成一次渲染,若无法找到默认的目标元素(如:动态插入的标签元素的场景),则可通过该方法完成对标签页的初始化渲染。
+
+```js
+// 对 class="layui-tabs" 所在标签进行初始化渲染
+tabs.render();
+```
+
+#### 2. 方法渲染
+
+通过方法动态渲染一个 tabs 组件,无需在 HTML 中书写标签页的 HTML 结构。
+
+```js
+
+
+
+```
+
+#### 3. 为任意元素渲染 tabs 功能
+
+当 `header` 和 `body` 参数传入元素选择器时,可为任意元素绑定标签切换功能。
+
+```js
+// 给任意元素绑定 Tab 功能
+tabs.render({
+ elem: '#demoTabs3', // 目标主容器选择器
+ header: ['#demoTabsHeader', '>button'], // 标签头部主元素选择器、标签头部列表选择器
+ body: ['#demoTabsBody', '>.test-item'] // 标签内容主元素选择器、标签内容列表选择器
+});
+```
+
+具体用法可直接参考上述示例:[给任意元素绑定 tabs 切换功能](#demo-custom)
+
+
+属性
+
+
+{{- d.include("/tabs/detail/options.md") }}
+
+
+新增标签
+
+`tabs.add(id, opts)`
+
+- 参数 `id` : 组件的实例 ID
+- 参数 `opts` : 标签配置项。可选项详见下表
+
+| opts | 描述 | 类型 | 默认值 |
+| --- | --- | --- | --- |
+| title | 标签标题。必填项 | string | - |
+| content | 标签内容。必填项 | string | - |
+| id | 标签的 `lay-id` 属性值 | string | - |
+| index | 活动标签的索引或 `lay-id` 属性值,默认取当前选中标签的索引 | number | - |
+| mode | 标签的插入方式。支持以下可选值:- `append` 插入标签到最后
- `prepend` 插入标签到最前
- `before` 在活动标签前插入
- `after` 在活动标签后插入
| string | `append` |
+| closable | 标签是否可关闭。初始值取决于 `options.closable` | boolean | `false` |
+| headerItem | 自定义标签头部元素,如 `headerItem: ''` | string | - |
+| bodyItem | 自定义标签内容元素,如 `bodyItem: ''` | string | - |
+| done | 标签添加成功后执行的回调函数 | Function | - |
+
+该方法用于给对应的 tabs 实例新增一个标签
+
+```js
+tabs.add('test', {
+ title: 'New Tab 1',
+ content: 'New Tab Content 1',
+});
+```
+
+关闭标签
+
+`tabs.close(id, index, force)`
+
+- 参数 `id` : 组件的实例 ID
+- 参数 `index` : 标签索引或标签的 `lay-id` 属性值
+- 参数 `force` : 是否强制关闭。若设置 `true` 将忽略 `beforeClose` 事件行为。默认 `false`
+
+该方法用于关闭指定的标签项。
+
+```js
+tabs.close('test', 3); // 关闭索引为 3 的标签
+tabs.close('test', 3, true); // 强制关闭索引为 3 的标签
+tabs.close('test', 'abc'); // 关闭 lay-id="abc" 的标签
+```
+
+批量关闭标签
+
+`tabs.closeMult(id, mode, index)`
+
+- 参数 `id` : 组件的实例 ID
+- 参数 `mode` : 关闭方式。支持以下可选值:
+
+| mode | 描述 |
+| --- | --- |
+| other | 关闭除当前标签外的所有标签 |
+| right | 关闭当前标签及右侧标签 |
+| all | 关闭所有标签 |
+
+- 参数 `index` : 活动标签的索引或 `lay-id` 属性值,默认取当前选中标签的索引。一般用于标签右键事件。
+
+该方法用于批量关闭标签。
+
+```js
+tabs.closeMult(id, 'other'); // 关闭除当前标签外的所有标签
+tabs.closeMult(id, 'other', 3); // 关闭除索引为 3 的标签外的所有标签
+tabs.closeMult(id, 'right'); // 关闭当前标签及右侧标签
+tabs.closeMult(id, 'right', 3); // 关闭索引为 3 的标签的右侧所有标签
+tabs.closeMult(id, 'all'); // 关闭所有标签
+```
+
+切换标签
+
+`tabs.change(id, index, force)`
+
+- 参数 `id` : 组件的实例 ID
+- 参数 `index` : 标签索引或标签的 `lay-id` 属性值
+- 参数 `force` : 是否强制切换。若设置 `true` 将忽略 `beforeChange` 事件行为。默认 false
+
+该方法用于切换到指定的标签项。
+
+```js
+tabs.change('test', 3); // 切换到索引为 3 的标签
+tabs.change('test', 3, true); // 强制切换到索引为 3 的标签
+tabs.change('test', 'abc'); // 切换到 lay-id="abc" 的标签
+tabs.change('test', 'abc', true); // 强制切换到 lay-id="abc" 的标签
+```
+
+获取标签相关数据
+
+`tabs.data(id)`
+
+- 参数 `id` : 组件的实例 ID
+
+该方法用于获取标签相关数据。
+
+```js
+var data = tabs.data('test');
+console.log(data);
+```
+
+返回的 `data` 包含以下字段:
+
+```js
+{
+ options, // 标签配置信息
+ container, // 标签容器的相关元素
+ thisHeaderItem, // 当前标签头部项
+ thisBodyItem, // 当前标签内容项
+ index, // 当前标签索引
+ length, // 当前标签数
+}
+```
+
+
+
+`tabs.getHeaderItem(id, index)`
+
+- 参数 `id` : 组件的实例 ID
+- 参数 `index` : 标签索引或标签的 `lay-id` 属性值
+
+该方法用于获取标签头部项元素。
+
+```js
+var headerItem = tabs.getHeaderItem('test', 3); // 获取索引为 3 的标签头部项元素
+```
+
+获取标签内容项
+
+`tabs.getBodyItem(id, index)`
+
+- 参数 `id` : 组件的实例 ID
+- 参数 `index` : 标签索引或标签的 `lay-id` 属性值
+
+该方法用于获取标签内容项元素。
+
+```js
+var bodyItem = tabs.getBodyItem('test', 3); // 获取索引为 3 的标签内容项元素
+```
+
+刷新标签视图
+
+`tabs.refresh(id)`
+
+- 参数 `id` : 组件的实例 ID
+
+该方法用于刷新标签视图,如标签头部的滚动结构等,一般通过非 API 方式对标签进行修改的场景中使用。
+
+```js
+tabs.refresh('test'); // 刷新标签视图
+```
+
+
+事件
+
+`tabs.on('event(id)', callback)`
+
+- 参数介绍详见 `component` 组件的[事件定义](../component/#on)。以下是组件提供的 `event` 事件列表
+
+| event | 描述 |
+| --- | --- |
+| [afterRender](#on-afterRender) | 标签渲染后的事件 |
+| [beforeChange](#on-beforeChange) | 标签切换前的事件 |
+| [afterChange](#on-afterChange) | 标签切换后的事件 |
+| [beforeClose](#on-beforeClose) | 标签关闭前的事件 |
+| [afterClose](#on-afterClose) | 标签关闭后的事件 |
+
+
+标签渲染后的事件
+
+`tabs.on('afterRender(id)', callback)`
+
+标签渲染成功后触发。
+
+```js
+tabs.on('afterRender(testID)', function(data){
+ console.log(data); // 标签相关数据
+});
+```
+
+标签切换前的事件
+
+`tabs.on('beforeChange(id)', callback)`
+
+标签在切换前触发,通过在事件中 `return false` 可阻止默认标签切换行为。通常和 `tabs.change()` 方法搭配使用。
+
+```js
+// tabs 切换前的事件
+tabs.on(`beforeChange(testID)`, function(data) {
+ console.log(data); // 标签相关数据
+ console.log(data.from.index); // 切换前的选中标签索引
+ console.log(data.from.headerItem); // 切换前的选中标签头部项
+ console.log(data.to.index); // 切换后的选中标签索引
+ console.log(data.to.headerItem); // 切换后的选中标签头部项
+
+ // 阻止标签默认关闭
+ return false;
+});
+```
+
+示例演示:
+
+
+
+
+
+标签切换后的事件
+
+`tabs.on('afterChange(id)', callback)`
+
+标签成功切换后触发。
+
+```js
+// tabs 切换后的事件
+tabs.on('afterChange(testID)', function(data) {
+ console.log(data);
+});
+```
+
+标签关闭前的事件
+
+`tabs.on('beforeClose(id)', callback)`
+
+标签在切换前触发,通过在事件中 `return false` 可阻止默认标签切换行为。通常和 `tabs.close()` 方法搭配使用。
+
+
+
+
+
+标签关闭后的事件
+
+`tabs.on('afterClose(id)', callback)`
+
+标签被成功关闭后触发。
+
+```js
+// tabs 关闭后的事件
+tabs.on('afterClose(testID)', function(data) {
+ console.log(data);
+});
+```
+
+## 💖 心语
+
+tabs 是通过 component 重构的首个组件,它来自于最早试图发布的 Layui 3.0(后因为 3.0 技术路线的变化,而整理放至 2.10+ 版本中),目的是将 element 模块中的 tab 组件进行解耦,增强其可扩展性。为了给开发者必要的时间缓冲,我们会将旧 tab 组件仍然保留在后续的若干版本中,但会在合适的时机对旧 tab 组件进行剔除,建议开发者尽量提前过渡到当前新的 tabs 组件。
+
diff --git a/examples/tabs.html b/examples/tabs.html
new file mode 100644
index 00000000..5a6eff73
--- /dev/null
+++ b/examples/tabs.html
@@ -0,0 +1,289 @@
+
+
+
+
+
+ 标签页组件 - Layui
+
+
+
+
+
动态操作
+
+
+
+
Tab Content-1
+
Tab Content-2
+
Tab Content-3
+
Tab Content-4
+
Tab Content-5
+
Tab Content-6
+
+
+
+
+
+
+
+
+
+
+
+
方法渲染
+
+
+
卡片风格
+
+
+
+
内容-1
+
内容-2
+
内容-3
+
内容-4
+
内容-5
+
内容-6
+
+
+
+
卡片 + 边框
+
+
+
+
+
+
+
+
+
2
+
3
+
4
+
5
+
6
+
+
+
+
标签 HASH 定位
+
+
+
+
+
标签嵌套
+
+
+
给任意元素绑定 tabs 切换功能
+
+
+
+
+
内容 111
+
内容 222
+
内容 333
+
+
+
+
+
+
+
+
+
diff --git a/src/css/layui.css b/src/css/layui.css
index a30e0299..69beff90 100644
--- a/src/css/layui.css
+++ b/src/css/layui.css
@@ -20,24 +20,23 @@ pre{white-space: pre-wrap; white-space: -moz-pre-wrap; white-space: -pre-wrap; w
/** 初始化全局标签 **/
body{line-height: 1.6; color: #333; color: rgba(0,0,0,.85); font: 14px Helvetica Neue,Helvetica,PingFang SC,Tahoma,Arial,sans-serif;}
hr{height: 0; line-height: 0; margin: 10px 0; padding: 0; border: none; border-bottom: 1px solid #eee; clear: both; overflow: hidden; background: none;}
-a{color: #333; text-decoration:none;}
-a:hover{color: #777;}
-a cite{font-style: normal; *cursor:pointer;}
+a{color: #333; text-decoration: none;}
+a cite{font-style: normal;}
/** 基础通用 **/
.layui-border-box, .layui-border-box *{box-sizing: border-box;}
/* 消除第三方ui可能造成的冲突 */.layui-box, .layui-box *{box-sizing: content-box;}
-.layui-clear{clear: both; *zoom: 1;}
-.layui-clear:after{content:'\20'; clear:both; *zoom:1; display:block; height:0;}
+.layui-clear{clear: both;}
+.layui-clear:after{content:'\20'; clear:both; display:block; height:0;}
.layui-clear-space{word-spacing: -5px;}
-.layui-inline{position: relative; display: inline-block; *display:inline; *zoom:1; vertical-align: middle;}
+.layui-inline{position: relative; display: inline-block; vertical-align: middle;}
/* 三角形 */.layui-edge{position: relative; display: inline-block; vertical-align: middle; width: 0; height: 0; border-width: 6px; border-style: dashed; border-color: transparent; overflow: hidden;}
.layui-edge-top{top: -4px; border-bottom-color: #999; border-bottom-style: solid;}
.layui-edge-right{border-left-color: #999; border-left-style: solid;}
.layui-edge-bottom{top: 2px; border-top-color: #999; border-top-style: solid;}
.layui-edge-left{border-right-color: #999; border-right-style: solid;}
/* 单行溢出省略 */.layui-elip{text-overflow: ellipsis; overflow: hidden; white-space: nowrap;}
-/* 屏蔽选中 */.layui-unselect,.layui-icon, .layui-disabled{-moz-user-select: none; -webkit-user-select: none; -ms-user-select: none;}
+/* 屏蔽选中 */.layui-unselect,.layui-icon, .layui-disabled{user-select: none;}
/* 禁用 */.layui-disabled,.layui-disabled:hover{color: #d2d2d2 !important; cursor: not-allowed !important;}
/* 纯圆角 */.layui-circle{border-radius: 100%;}
.layui-show{display: block !important;}
@@ -515,14 +514,18 @@ a cite{font-style: normal; *cursor:pointer;}
.layui-col-space32>*{padding: 16px;}
-/* 内边距 */
+/*
+ * 内边距
+ */
.layui-padding-1{padding: 4px !important;}
.layui-padding-2{padding: 8px !important;}
.layui-padding-3{padding: 16px !important;}
.layui-padding-4{padding: 32px !important;}
.layui-padding-5{padding: 48px !important;}
-/* 外边距 */
+/*
+ * 外边距
+ */
.layui-margin-1{margin: 4px !important;}
.layui-margin-2{margin: 8px !important;}
.layui-margin-3{margin: 16px !important;}
@@ -538,7 +541,7 @@ a cite{font-style: normal; *cursor:pointer;}
.layui-input,
.layui-select,
.layui-textarea,
-.layui-upload-button{outline: none; -webkit-appearance: none; transition: all .3s; -webkit-transition: all .3s; box-sizing: border-box;}
+.layui-upload-button{outline: none; appearance: none; -webkit-appearance: none; transition: all .3s; -webkit-transition: all .3s; box-sizing: border-box;}
/* 引用 */
.layui-elem-quote{margin-bottom: 10px; padding: 15px; line-height: 1.8; border-left: 5px solid #16b777; border-radius: 0 2px 2px 0; background-color: #fafafa;}
@@ -670,8 +673,8 @@ hr.layui-border-black{border-width: 0 0 1px;}
.layui-text ol ul > li{list-style-type: disc;}
.layui-text ul li > p:first-child,
.layui-text ol li > p:first-child{margin-top: 0; margin-bottom: 0;}
-.layui-text a:not(.layui-btn){color: #01AAED;}
-.layui-text a:not(.layui-btn):hover{text-decoration: underline;}
+.layui-text :where(a:not(.layui-btn)){color: #01AAED;}
+.layui-text :where(a:not(.layui-btn):hover){text-decoration: underline;}
.layui-text blockquote:not(.layui-elem-quote){margin: 15px 0; padding: 5px 15px; border-left: 5px solid #eee;}
.layui-text pre > code:not(.layui-code){display: block; padding: 15px; font-family: "Courier New",Consolas,"Lucida Console", monospace;}
@@ -709,7 +712,7 @@ hr.layui-border-black{border-width: 0 0 1px;}
* 按钮
*/
-.layui-btn{display: inline-block; vertical-align: middle; height: 38px; line-height: 38px; border: 1px solid transparent; padding: 0 18px; background-color: #16baaa; color: #fff; white-space: nowrap; text-align: center; font-size: 14px; border-radius: 2px; cursor: pointer; -moz-user-select: none; -webkit-user-select: none; -ms-user-select: none;}
+.layui-btn{display: inline-block; vertical-align: middle; height: 38px; line-height: 38px; border: 1px solid transparent; padding: 0 18px; background-color: #16baaa; color: #fff; white-space: nowrap; text-align: center; font-size: 14px; border-radius: 2px; cursor: pointer; user-select: none;}
.layui-btn:hover{opacity: 0.8; filter:alpha(opacity=80); color: #fff;}
.layui-btn:active{opacity: 1; filter:alpha(opacity=100);}
.layui-btn+.layui-btn{margin-left: 10px;}
@@ -763,8 +766,8 @@ hr.layui-border-black{border-width: 0 0 1px;}
.layui-form input[type=radio]{display: none;}
.layui-form *[lay-ignore]{display: initial;}
-.layui-form-item{position: relative; margin-bottom: 15px; clear: both; *zoom: 1;}
-.layui-form-item:after{content:'\20'; clear: both; *zoom: 1; display: block; height:0;}
+.layui-form-item{position: relative; margin-bottom: 15px; clear: both;}
+.layui-form-item:after{content:'\20'; clear: both; display: block; height:0;}
.layui-form-label{position: relative; float: left; display: block; padding: 9px 15px; width: 80px; font-weight: 400; line-height: 20px; text-align: right;}
.layui-form-label-col{display: block; float: none; padding: 9px 0; line-height: 20px; text-align: left;}
.layui-form-item .layui-inline{margin-bottom: 5px; margin-right: 10px;}
@@ -837,7 +840,7 @@ hr.layui-border-black{border-width: 0 0 1px;}
.layui-input-wrap .layui-input-number .layui-icon:hover{font-weight: 700;}
.layui-input-wrap .layui-input[type="number"]::-webkit-outer-spin-button,
.layui-input-wrap .layui-input[type="number"]::-webkit-inner-spin-button{-webkit-appearance: none !important;}
-.layui-input-wrap .layui-input[type="number"]{-moz-appearance: textfield;}
+.layui-input-wrap .layui-input[type="number"]{-moz-appearance: textfield; -webkit-appearance: textfield; appearance: textfield;}
.layui-input-wrap .layui-input.layui-input-number-out-of-range,
.layui-input-wrap .layui-input.layui-input-number-invalid{color:#ff5722;}
@@ -872,7 +875,6 @@ hr.layui-border-black{border-width: 0 0 1px;}
/* 复选框 */
.layui-form-checkbox{position: relative; display: inline-block; vertical-align: middle; height: 30px; line-height: 30px; margin-right: 10px; padding-right: 30px; background-color: #fff; cursor: pointer; font-size: 0; -webkit-transition: .1s linear; transition: .1s linear; box-sizing: border-box;}
-.layui-form-checkbox:hover{}
.layui-form-checkbox > *{display: inline-block; vertical-align: middle;}
.layui-form-checkbox > div{padding: 0 11px; font-size: 14px; border-radius: 2px 0 0 2px; background-color: #d2d2d2; color: #fff; overflow: hidden; white-space: nowrap; text-overflow: ellipsis;}
.layui-form-checkbox > div > .layui-icon{line-height: normal}
@@ -965,7 +967,7 @@ hr.layui-border-black{border-width: 0 0 1px;}
}
/** 分页 **/
-.layui-laypage{display: inline-block; *display: inline; *zoom: 1; vertical-align: middle; margin: 10px 0; font-size: 0;}
+.layui-laypage{display: inline-block; vertical-align: middle; margin: 10px 0; font-size: 0;}
.layui-laypage>a:first-child,
.layui-laypage>a:first-child em{border-radius: 2px 0 0 2px;}
.layui-laypage>a:last-child,
@@ -978,7 +980,7 @@ hr.layui-border-black{border-width: 0 0 1px;}
.layui-laypage button,
.layui-laypage select{border: 1px solid #eee;}
.layui-laypage a,
-.layui-laypage span{display: inline-block; *display: inline; *zoom: 1; vertical-align: middle; padding: 0 15px; height: 28px; line-height: 28px; margin: 0 -1px 5px 0; background-color: #fff; color: #333; font-size: 12px;}
+.layui-laypage span{display: inline-block; vertical-align: middle; padding: 0 15px; height: 28px; line-height: 28px; margin: 0 -1px 5px 0; background-color: #fff; color: #333; font-size: 12px;}
.layui-laypage a[data-page]{color: #333;}
.layui-laypage a{text-decoration: none !important; cursor: pointer;}
.layui-laypage a:hover{color: #16baaa;}
@@ -1019,12 +1021,6 @@ hr.layui-border-black{border-width: 0 0 1px;}
.layui-table tr{transition: all .3s; -webkit-transition: all .3s;}
.layui-table th{text-align: left; font-weight: 600;}
-.layui-table thead tr,
-.layui-table-header,
-.layui-table-tool,
-.layui-table-total,
-.layui-table-total tr,
-.layui-table-patch{}
.layui-table-mend{background-color: #fff;}
.layui-table-hover,
.layui-table-click,
@@ -1292,18 +1288,55 @@ body .layui-table-tips .layui-layer-content{background: none; padding: 0; box-sh
/* 下拉菜单 */
.layui-dropdown{position: absolute; left: -999999px; top: -999999px; z-index: 77777777; margin: 5px 0; min-width: 100px;}
.layui-dropdown:before{content:""; position: absolute; width: 100%; height: 6px; left: 0; top: -6px;}
-.layui-dropdown-shade{top: 0; left: 0; width: 100%; height: 100%; _height: expression(document.body.offsetHeight+"px"); position: fixed; _position: absolute; pointer-events: auto;}
+.layui-dropdown-shade{top: 0; left: 0; width: 100%; height: 100%; position: fixed; pointer-events: auto;}
+
+
+/* Tabs 标签页 */
+.layui-tabs{position: relative;}
+.layui-tabs *{box-sizing: border-box;}
+.layui-tabs.layui-hide-v{overflow: hidden;}
+.layui-tabs-header{position: relative; left: 0; height: 40px; padding: 0 !important; white-space: nowrap; font-size: 0; transition: all .16s; -webkit-transition: all .16s;}
+.layui-tabs-header:after,
+.layui-tabs-scroll:after{content: ""; position: absolute; left: 0; bottom: 0; z-index: 0; width: 100%; border-bottom: 1px solid #eee;}
+.layui-tabs-header li{position: relative; display: inline-block; vertical-align: middle; line-height: 40px; margin: 0 !important; padding: 0 16px; text-align: center; cursor: pointer; font-size: 14px; transition: all .16s; -webkit-transition: all .16s;}
+.layui-tabs-header li:first-child{margin-left: 0;}
+.layui-tabs-header li a{display: block; padding: 0 16px; margin: 0 -16px; color: inherit;}
+.layui-tabs-header li a:hover{text-decoration: none;}
+.layui-tabs-header li:hover,
+.layui-tabs-header .layui-this{color: #16baaa;}
+.layui-tabs-header .layui-this:after{content: ""; position: absolute; left:0; top: 0; z-index: 1; width: 100%; height: 100%; border-bottom: 3px solid #16baaa; box-sizing: border-box; pointer-events: none;}
+.layui-tabs-header .layui-badge,
+.layui-tabs-header .layui-badge-dot{left: 5px; top: -1px;}
+
+.layui-tabs-scroll{position: relative; overflow: hidden; padding: 0 40px;}
+.layui-tabs-scroll .layui-tabs-header:after{display: none; content: none; border: 0;}
+.layui-tabs-bar .layui-icon{position: absolute; left: 0; top: 0; z-index: 3; width: 40px; height: 100%; line-height: 40px; border: 1px solid #eee; text-align: center; cursor: pointer; box-sizing: border-box; background-color: #fff; box-shadow: 2px 0 5px 0 rgb(0 0 0 / 6%);}
+.layui-tabs-bar .layui-icon-next{left: auto; right: 0; box-shadow: -2px 0 5px 0 rgb(0 0 0 / 6%);}
+
+.layui-tabs-header li .layui-tabs-close{position: relative; display: inline-block; width: 16px; height: 16px; line-height: 18px; margin-left: 8px; top: 0px; text-align: center; font-size: 12px; color: #959595; border-radius: 50%; font-weight: 700; transition: all .16s; -webkit-transition: all .16s;}
+.layui-tabs-header li .layui-tabs-close:hover{ background-color: #ff5722; color: #fff;}
+
+.layui-tabs-body{padding: 16px 0;}
+.layui-tabs-item{display: none;}
+
+/* tabs 卡片风格 */
+.layui-tabs-card>.layui-tabs-header .layui-this{background-color: #fff;}
+.layui-tabs-card>.layui-tabs-header .layui-this:after{border: 1px solid #eee; border-bottom-color: #fff; border-radius: 2px 2px 0 0;}
+.layui-tabs-card>.layui-tabs-header li:first-child.layui-this:after{margin-left: -1px;}
+.layui-tabs-card>.layui-tabs-header li:last-child.layui-this:after{margin-right: -1px;}
+.layui-tabs-card.layui-panel>.layui-tabs-header .layui-this:after{border-top: 0; border-radius: 0;}
+.layui-tabs-card.layui-panel>.layui-tabs-body{padding: 16px;}
+
/** 导航菜单 **/
.layui-nav{position: relative; padding: 0 15px; background-color: #2f363c; color: #fff; border-radius: 2px; font-size: 0; box-sizing: border-box;}
.layui-nav *{font-size: 14px;}
-.layui-nav .layui-nav-item{position: relative; display: inline-block; *display: inline; *zoom: 1; margin-top: 0; list-style: none; vertical-align: middle; line-height: 60px;}
+.layui-nav .layui-nav-item{position: relative; display: inline-block; margin-top: 0; list-style: none; vertical-align: middle; line-height: 60px;}
.layui-nav .layui-nav-item a{display: block; padding: 0 20px; color: #fff; color: rgba(255,255,255,.7); transition: all .3s; -webkit-transition: all .3s;}
.layui-nav-bar,
.layui-nav .layui-this:after{content: ""; position: absolute; left: 0; top: 0; width: 0; height: 3px; background-color: #16b777; transition: all .2s; -webkit-transition: all .2s; pointer-events: none;}
.layui-nav-bar{z-index: 1000;}
.layui-nav[lay-bar="disabled"] .layui-nav-bar{display: none;}
-.layui-nav[lay-bar="disabled"].layui-this:after{}
.layui-nav .layui-this a,
.layui-nav .layui-nav-item a:hover{color: #fff; text-decoration: none;}
.layui-nav .layui-this:after{top: auto; bottom: 0; width: 100%;}
@@ -1381,7 +1414,7 @@ body .layui-table-tips .layui-layer-content{background: none; padding: 0; box-sh
.layui-tab[overflow]>.layui-tab-title{overflow: hidden;}
.layui-tab .layui-tab-title{position: relative; left: 0; height: 40px; white-space: nowrap; font-size: 0; transition: all .2s; -webkit-transition: all .2s;}
.layui-tab .layui-tab-title:after{content: ""; border-bottom-color: #eee; border-bottom-width: 1px; border-style: none none solid; bottom: 0; left: 0; right: auto; top: auto; pointer-events: none; position: absolute; width: 100%; z-index: 8;}
-.layui-tab .layui-tab-title li{display: inline-block; *display: inline; *zoom: 1; vertical-align: middle; font-size: 14px; transition: all .2s; -webkit-transition: all .2s;}
+.layui-tab .layui-tab-title li{display: inline-block; vertical-align: middle; font-size: 14px; transition: all .2s; -webkit-transition: all .2s;}
.layui-tab .layui-tab-title li{position: relative; line-height: 40px; min-width: 65px; margin: 0; padding: 0 15px; text-align: center; cursor: pointer;}
.layui-tab .layui-tab-title li a{display: block; padding: 0 15px; margin: 0 -15px;}
.layui-tab-title .layui-this{color: #000;}
diff --git a/src/layui.js b/src/layui.js
index 567e7e99..a880b5c0 100644
--- a/src/layui.js
+++ b/src/layui.js
@@ -62,6 +62,7 @@
tree: 'tree', // 树结构
table: 'table', // 表格
treeTable: 'treeTable', // 树表
+ tabs: 'tabs', // 标签页
element: 'element', // 常用元素操作
rate: 'rate', // 评分组件
colorpicker: 'colorpicker', // 颜色选择器
@@ -71,6 +72,7 @@
util: 'util', // 工具块
code: 'code', // 代码修饰器
jquery: 'jquery', // DOM 库(第三方)
+ component: 'component', // 组件构建器
all: 'all',
'layui.all': 'layui.all' // 聚合标识(功能性的,非真实模块)
@@ -401,13 +403,14 @@
var data = {
path: [],
search: {},
- hash: (hash.match(/[^#](#.*$)/) || [])[1] || ''
+ hash: (hash.match(/[^#](#.*$)/) || [])[1] || '',
+ href: ''
};
- if(!/^#\//.test(hash)) return data; // 禁止非路由规范
+ if (!/^#/.test(hash)) return data; // 禁止非路由规范
- hash = hash.replace(/^#\//, '');
- data.href = '/' + hash;
+ hash = hash.replace(/^#/, '');
+ data.href = hash;
hash = hash.replace(/([^#])(#.*$)/, '$1').split('/') || [];
// 提取 Hash 结构
diff --git a/src/modules/code.js b/src/modules/code.js
index dc4a795a..744e5ca1 100644
--- a/src/modules/code.js
+++ b/src/modules/code.js
@@ -3,12 +3,13 @@
* Code 预览组件
*/
-layui.define(['lay', 'util', 'element', 'form'], function(exports){
+layui.define(['lay', 'util', 'element', 'tabs', 'form'], function(exports){
"use strict";
var $ = layui.$;
var util = layui.util;
var element = layui.element;
+ var tabs = layui.tabs;
var form = layui.form;
var layer = layui.layer;
var hint = layui.hint();
@@ -403,6 +404,9 @@ layui.define(['lay', 'util', 'element', 'form'], function(exports){
render: function(){
form.render(thisItemBody.find('.layui-form'));
element.render();
+ tabs.render({
+ elem: ['.'+ CONST.ELEM_PREVIEW, '.layui-tabs'].join(' ')
+ });
}
});
},3);
diff --git a/src/modules/component.js b/src/modules/component.js
new file mode 100644
index 00000000..e84f026c
--- /dev/null
+++ b/src/modules/component.js
@@ -0,0 +1,238 @@
+/**
+ * component
+ * Layui 2 组件构建器
+ */
+
+layui.define(['jquery', 'lay'], function(exports) {
+ "use strict";
+
+ var $ = layui.$;
+ var lay = layui.lay;
+
+ // export
+ exports('component', function(settings) {
+ // 默认设置
+ settings = $.extend(true, {
+ isRenderWithoutElem: false, // 渲染是否无需指定目标元素
+ isRenderOnEvent: true, // 渲染是否仅由事件触发。--- 推荐根据组件类型始终显式设置对应值
+ isDeepReload: false // 是否默认为深度重载
+ }, settings);
+
+ // 组件名
+ var MOD_NAME = settings.name;
+ var MOD_INDEX = 'layui_'+ MOD_NAME +'_index'; // 组件索引名
+ var MOD_ID = 'lay-' + MOD_NAME + '-id'; // 用于记录组件实例 id 的属性名
+
+ // 组件基础对外接口
+ var component = {
+ config: {}, // 全局配置项,一般通过 component.set() 设置
+ index: layui[MOD_NAME] ? (layui[MOD_NAME].index + 10000) : 0, // 组件索引
+
+ // 通用常量集,一般存放固定字符,如类名等
+ CONST: $.extend(true, {
+ MOD_NAME: MOD_NAME,
+ MOD_INDEX: MOD_INDEX,
+
+ CLASS_THIS: 'layui-this',
+ CLASS_SHOW: 'layui-show',
+ CLASS_HIDE: 'layui-hide',
+ CLASS_HIDEV: 'layui-hide-v',
+ CLASS_DISABLED: 'layui-disabled',
+ CLASS_NONE: 'layui-none'
+ }, settings.CONST),
+
+ // 设置全局项
+ set: function(options) {
+ var that = this;
+ $.extend(true, that.config, options);
+ return that;
+ },
+
+ // 事件
+ on: function(events, callback) {
+ return layui.onevent.call(this, MOD_NAME, events, callback);
+ }
+ };
+
+ // 操作当前实例
+ var instance = function() {
+ var that = this;
+ var options = that.config;
+ var id = options.id;
+
+ // 实例对象
+ var inst = {
+ config: options,
+ id: id,
+
+ // 重置实例
+ reload: function(options) {
+ that.reload.call(that, options);
+ }
+ };
+
+ // 扩展实例对象的回调
+ if (typeof settings.extendsInstance === 'function') {
+ $.extend(true, inst, settings.extendsInstance.call(that));
+ }
+
+ // 返回实例对象
+ return inst;
+ };
+
+ // 构造器
+ var Class = function(options) {
+ var that = this;
+ that.index = ++component.index; // 每创建一个实例,下标自增
+
+ // 扩展配置项:传入选项 -> 全局选项 -> 默认选项 = 当前选项
+ that.config = $.extend(true, {}, that.config, component.config, options);
+
+ // 初始化之前的回调
+ if (typeof settings.beforeInit === 'function') {
+ settings.beforeInit.call(that, that.config);
+ }
+
+ // 初始化
+ that.init();
+ };
+
+ // 默认配置
+ Class.prototype.config = settings.config;
+
+ // 重载实例
+ Class.prototype.reload = function(options, type) {
+ var that = this;
+ $.extend(settings.isDeepReload, that.config, options);
+ that.init(true, type);
+ };
+
+ // 初始化准备(若由事件触发渲染,则必经此步)
+ Class.prototype.init = function(rerender, type){
+ var that = this;
+ var options = that.config;
+ var elem = $(options.elem);
+
+ // 若 elem 非唯一,则拆分为多个实例
+ if (elem.length > 1) {
+ layui.each(elem, function() {
+ component.render($.extend({}, options, {
+ elem: this
+ }));
+ });
+ return that;
+ }
+
+ // 合并 lay-options 属性上的配置信息
+ $.extend(true, options, lay.options(elem[0]));
+
+ // 若重复执行 render,则视为 reload 处理
+ if (!rerender && elem.attr(MOD_ID)) {
+ var newThat = instance.getThis(elem.attr(MOD_ID));
+ if (!newThat) return;
+ return newThat.reload(options, type);
+ }
+
+ options.elem = $(options.elem);
+
+ // 初始化 id 属性 - 优先取 options.id > 元素 id > 自增索引
+ options.id = lay.hasOwn(options, 'id') ? options.id : (
+ elem.attr('id') || that.index
+ );
+
+ // 记录当前实例对象
+ instance.that[options.id] = that;
+
+ // 渲染之前的回调
+ if (typeof settings.beforeRender === 'function') {
+ settings.beforeRender.call(that, options);
+ }
+
+ // 执行渲染
+ var render = function() {
+ component.cache.id[options.id] = null; // 记录所有实例 id,用于批量操作(如 resize)
+ elem.attr(MOD_ID, options.id); // 目标元素已渲染过的标记
+ that.render(rerender); // 渲染核心
+ };
+
+ // 若绑定元素不存在
+ if (!elem[0]) {
+ return settings.isRenderWithoutElem ? render() : null; // 渲染是否无需指定目标元素
+ };
+
+ // 执行渲染 - 是否初始即渲染组件
+ if((settings.isRenderOnEvent && options.show) || !settings.isRenderOnEvent) {
+ render();
+ }
+
+ // 事件
+ typeof settings.events === 'function' && that.events();
+ };
+
+ // 组件必传项
+ Class.prototype.render = settings.render; // 渲染
+ Class.prototype.events = settings.events; // 事件
+
+ // 元素操作缓存
+ Class.prototype.cache = function(key, value) {
+ var that = this;
+ var options = that.config;
+ var elem = options.elem;
+
+ if (!elem) return;
+
+ var CACHE_NAME = 'lay_'+ MOD_NAME + '_cache';
+ var cache = elem.data(CACHE_NAME) || {};
+
+ if (value === undefined) return cache[key];
+
+ cache[key] = value;
+ elem.data(CACHE_NAME, cache);
+ };
+
+ // 缓存所有实例对象
+ instance.that = {};
+
+ // 获取当前实例对象
+ instance.getThis = component.getThis = function(id) {
+ if (id === undefined) {
+ throw new Error('ID argument required');
+ }
+ return instance.that[id];
+ };
+
+ // 组件缓存
+ component.cache = {
+ id: {}
+ };
+
+ // 用于扩展原型
+ component.Class = Class;
+
+ /**
+ * 组件完整重载
+ * @param {string} id - 实例 id
+ * @param {Object} options - 配置项
+ * @returns
+ */
+ component.reload = function(id, options) {
+ var that = instance.getThis(id);
+ if (!that) return;
+
+ that.reload(options);
+ return instance.call(that);
+ };
+
+ /**
+ * 组件渲染
+ * @param {Object} options - 配置项
+ * @returns
+ */
+ component.render = function(options) {
+ var inst = new Class(options);
+ return instance.call(inst);
+ };
+
+ return component;
+ });
+});
diff --git a/src/modules/jquery.js b/src/modules/jquery.js
index bc821582..73076bfb 100644
--- a/src/modules/jquery.js
+++ b/src/modules/jquery.js
@@ -1,27 +1,26 @@
/*!
- * jQuery JavaScript Library v1.12.4
- * http://jquery.com/
+ * jQuery JavaScript Library v3.7.1
+ * https://jquery.com/
*
- * Includes Sizzle.js
- * http://sizzlejs.com/
- *
- * Copyright jQuery Foundation and other contributors
+ * Copyright OpenJS Foundation and other contributors
* Released under the MIT license
- * http://jquery.org/license
+ * https://jquery.org/license
*
- * Date: 2016-05-20T17:17Z
+ * Date: 2023-08-28T13:37Z
*/
+( function( global, factory ) {
-(function( global, factory ) {
+ "use strict";
if ( typeof module === "object" && typeof module.exports === "object" ) {
+
// For CommonJS and CommonJS-like environments where a proper `window`
// is present, execute the factory and get jQuery.
// For environments that do not have a `window` with a `document`
// (such as Node.js), expose a factory as module.exports.
// This accentuates the need for the creation of a real `window`.
// e.g. var jQuery = require("jquery")(window);
- // See ticket #14549 for more info.
+ // See ticket trac-14549 for more info.
module.exports = global.document ?
factory( global, true ) :
function( w ) {
@@ -35,24 +34,30 @@
}
// Pass this if window is not defined yet
-}(typeof window !== "undefined" ? window : this, function( window, noGlobal ) {
+} )( typeof window !== "undefined" ? window : this, function( window, noGlobal ) {
-// Support: Firefox 18+
-// Can't be in strict mode, several libs including ASP.NET trace
-// the stack via arguments.caller.callee and Firefox dies if
-// you try to trace through "use strict" call chains. (#13335)
-//"use strict";
-var deletedIds = [];
+// Edge <= 12 - 13+, Firefox <=18 - 45+, IE 10 - 11, Safari 5.1 - 9+, iOS 6 - 9.1
+// throw exceptions when non-strict code (e.g., ASP.NET 4.5) accesses strict mode
+// arguments.callee.caller (trac-13335). But as of jQuery 3.0 (2016), strict mode should be common
+// enough that all such attempts are guarded in a try block.
+"use strict";
-var document = window.document;
+var arr = [];
-var slice = deletedIds.slice;
+var getProto = Object.getPrototypeOf;
-var concat = deletedIds.concat;
+var slice = arr.slice;
-var push = deletedIds.push;
+var flat = arr.flat ? function( array ) {
+ return arr.flat.call( array );
+} : function( array ) {
+ return arr.concat.apply( [], array );
+};
-var indexOf = deletedIds.indexOf;
+
+var push = arr.push;
+
+var indexOf = arr.indexOf;
var class2type = {};
@@ -60,12 +65,91 @@ var toString = class2type.toString;
var hasOwn = class2type.hasOwnProperty;
+var fnToString = hasOwn.toString;
+
+var ObjectFunctionString = fnToString.call( Object );
+
var support = {};
+var isFunction = function isFunction( obj ) {
+
+ // Support: Chrome <=57, Firefox <=52
+ // In some browsers, typeof returns "function" for HTML