Compare commits

...

11 Commits

Author SHA1 Message Date
Kagol baad8c8420
docs: add breaking changes to changelog (#1562) 2024-04-26 16:01:09 +08:00
Kagol 180ef51c04
feat(unplugin-tiny-vue): add TinyVueResolver (#1585) 2024-04-19 09:11:50 +08:00
ajaxzheng 282ff2ba80
Update package.json 2024-04-18 14:26:12 +08:00
gimmyhehe 171e3b1854
fix(theme-saas): update theme saas dependencies (#1582) 2024-04-18 11:43:52 +08:00
chenxi-20 431c1b04b7
fix(tabs): [tabs] Fixed issue with default slot and template/v-if/v-for usage in vue2 and vue3 (#1558)
* fix(tabs): [tabs] Fix the bug where the name is empty caused by using v-if in tabstem

* fix(tabs): [tabs] update renderless version

* fix(tabs): [tabs] Fix the issue with rendering in the default slot of Vue3

* fix(tabs): [tabs] Fix the issue of inconsistent highlighting when multiple V-ifs are used

* fix(tabs): [tabs] update opentiny/vue-tabs version
2024-04-17 15:18:57 +08:00
申君健 85e8123360
refactor(unplugin): rewrite unplugin for autoimport Vue components (#1553) 2024-04-11 15:21:38 +08:00
gimmyhehe c471a9d0d5
fix(action-menu): [action-menu] fix no divided bug (#1538)
* fix(action-menu): [action-menu] fix no divided bug

* fix(action-menu): [action-menu] fix no divided bug
2024-04-10 15:52:23 +08:00
申君健 d794903050
fix(tree): remove leaf node's padding-left (#1546) 2024-04-10 15:47:36 +08:00
Kagol e7661371e2
docs: add v3.15.0 changelog (#1544) 2024-04-10 15:20:35 +08:00
gimmyhehe 86a36784a5
fix(grid): [grid] fix grid no emit filter-change bug (#1536)
* fix(grid): [grid] fix grid not emit filter-change bug

* fix(grid): [grid] fix grid not emit filter-change bug
2024-04-10 15:14:22 +08:00
gimmyhehe e106631d98
fix(grid): fix right fixed table header gutter style (#1542) 2024-04-10 15:09:16 +08:00
25 changed files with 249 additions and 170 deletions

View File

@ -165,11 +165,11 @@ export default {
type: 'interface',
code: `
interface IItemData {
label: string // 菜单项文本
disabled: boolean // 是否禁用
divided: boolean // 是否显示分割线
children: IItemData[] // 菜单项子集
icon: Component // 菜单项图标
label?: string // 菜单项文本
disabled?: boolean // 是否禁用
divided?: boolean // 是否显示分割线
children?: IItemData[] // 菜单项子集
icon?: Component // 菜单项图标
}
`
},

View File

@ -17,7 +17,8 @@ const options = ref([
label: '关机'
},
{
label: '重启'
label: '重启',
divided: true
},
{
label: '网络设置',

View File

@ -22,7 +22,8 @@ export default {
label: '关机'
},
{
label: '重启'
label: '重启',
divided: true
},
{
label: '网络设置',

View File

@ -1,5 +1,74 @@
# 更新日志
## v2.15.0/v3.15.0
`2024/04/08`
## What's Changed
### Breaking Changes 🛠
- remove CreditCardForm component
- remove DetailPage component
- remove SlideBar component
### Exciting New Features 🎉
- feat(sites): add theme route by @gimmyhehe in https://github.com/opentiny/tiny-vue/pull/1478
- Cgm/add theme route by @gimmyhehe in https://github.com/opentiny/tiny-vue/pull/1479
- fix(transfer ): update transfer xdesign by @James-9696 in https://github.com/opentiny/tiny-vue/pull/1496
- feat(form): [form] add xDesign theme by @gimmyhehe in https://github.com/opentiny/tiny-vue/pull/1507
- feat(action-menu): [action-menu] add XDesign theme by @gimmyhehe in https://github.com/opentiny/tiny-vue/pull/1514
- feat(date-picker): [date-picker] date picker add quarter type by @kagol in https://github.com/opentiny/tiny-vue/pull/1513
- feat(statistic): statistic component by @James-9696 in https://github.com/opentiny/tiny-vue/pull/1491
- feat(select): add show-proportion props of select component by @James-9696 in https://github.com/opentiny/tiny-vue/pull/1503
- feat(rich-text-editor): [rich-text-editor] add image drag adjustment by @HAOUEHF in https://github.com/opentiny/tiny-vue/pull/1504
- feat(grid): [grid] add XDesign theme by @gimmyhehe in https://github.com/opentiny/tiny-vue/pull/1518
- feat: Adapting to the X-design theme by @zzcr in https://github.com/opentiny/tiny-vue/pull/1534
- feat(charts): refactor chart components and replace chart-core with hui-charts by @Davont
- refactored the underlying chart-core library which is used by all chart components
- chart-core uses hui-charts instead of echarts as the underlying logic of the chart
- chart components add a unified entry `option` based on the original API to facilitate unified calls
- improved documentation and demos for all chart components
- add theme switching function to adapt to different business needs
- add chart status function `chartInstance`, providing 5 states: `loading`, `error`, `empty`, `stateEmpty`, `customize`
### Bug Fixes 🐛
- fix(site): anchor offset by @GaoNeng-wWw in https://github.com/opentiny/tiny-vue/pull/1477
- fix(select): fix select/picker bugs by @zzcr in https://github.com/opentiny/tiny-vue/pull/1487
- fix(vue-component): [mind-map] border-radius & border by @GaoNeng-wWw in https://github.com/opentiny/tiny-vue/pull/1510
- fix(quarter-panel): [date-picker] add mono: true by @kagol in https://github.com/opentiny/tiny-vue/pull/1519
- fix: Custom header height of dialog-box by @James-9696 in https://github.com/opentiny/tiny-vue/pull/1530
- fix(search): [search] fixed the maxlength attribute bug in search by @chenxi-20 in https://github.com/opentiny/tiny-vue/pull/1528
- fix(rich-text-editor): fix right package name by @shenjunjian in https://github.com/opentiny/tiny-vue/pull/1535
- fix(docs): fix the issue of the theme switch button not being centered by @chenxi-20 in https://github.com/opentiny/tiny-vue/pull/1533
- fix(AMap): fix the problem of failure to display the AMap case in the document by @Davont
- fix(chart-heatMap): fix tooltip display error in bar-chart by @Davont
- fix(chart-bar): fix the problem of abnormal gap display when the histogram width is too low by @Davont
- fix(chart-histogram): fix the problem of histogram chart displaying blank in special scenarios by @Davont
### Other Changes
- docs(sites): add deep style in scoped by @gimmyhehe in https://github.com/opentiny/tiny-vue/pull/1473
- fix(ip-address): [ip-address] add spaces by @wuyiping0628 in https://github.com/opentiny/tiny-vue/pull/1475
- docs(steps): optimization of steps docs by @Huangyilin19 in https://github.com/opentiny/tiny-vue/pull/1474
- docs: add project name to issue template by @kagol in https://github.com/opentiny/tiny-vue/pull/1486
- docs(grid): [grid] fix tree-grid-insert-delete-update demo by @gimmyhehe in https://github.com/opentiny/tiny-vue/pull/1495
- docs(sites): fix site overview input error by @gimmyhehe in https://github.com/opentiny/tiny-vue/pull/1499
- docs: update changelog by @kagol in https://github.com/opentiny/tiny-vue/pull/1501
- [select] Optimized remote search demo by @Huangyilin19 in https://github.com/opentiny/tiny-vue/pull/1488
- ci(publish): add github action auto publish by @zzcr in https://github.com/opentiny/tiny-vue/pull/1512
- docs(date-picker): [date-picker] fix format docs by @kagol in https://github.com/opentiny/tiny-vue/pull/1522
- docs(Numeric): [examples] add the description of change-compat for ch… by @AcWrong02 in https://github.com/opentiny/tiny-vue/pull/1521
- docs(sites): add aui adapter document by @gimmyhehe in https://github.com/opentiny/tiny-vue/pull/1516
- ci(publish): add dispatch publish action by @zzcr in https://github.com/opentiny/tiny-vue/pull/1517
- fix: update numeric demo style by @James-9696 in https://github.com/opentiny/tiny-vue/pull/1526
## New Contributors
- @HAOUEHF made their first contribution in https://github.com/opentiny/tiny-vue/pull/1504
## v2.14.0/v3.14.0
`2024/03/07`

View File

@ -22,7 +22,7 @@ Vite
import autoImportPlugin from '@opentiny/unplugin-tiny-vue'
export default {
plugins: [autoImportPlugin()]
plugins: [autoImportPlugin('vite')]
}
```
@ -33,13 +33,53 @@ Webpack
const autoImportPlugin = require('@opentiny/unplugin-tiny-vue')
module.exports = {
plugins: [autoImportPlugin()]
}
module.exports = defineConfig({
configureWebpack: {
plugins: [autoImportPlugin('webpack')]
}
})
```
这样你就能直接在项目中使用 TinyVue 的组件,这些组件都是自动按需导入的,无需手动导入,且不用担心项目体积变得太大。
你也可以只使用 TinyVueResolver这样就可以和其他组件库一起使用。
Vite
```ts
// vite.config.ts
import Components from 'unplugin-vue-components/vite'
import autoImportPlugin from '@opentiny/unplugin-tiny-vue'
export default {
plugins: [
Components({
resolvers: [TinyVueResolver]
})
]
}
```
Webpack
```js
// webpack.config.js
const Components = require('unplugin-vue-components/webpack').default
const TinyVueResolver = require('@opentiny/unplugin-tiny-vue').TinyVueResolver
module.exports = defineConfig({
configureWebpack: {
plugins: [
Components({
resolvers: [TinyVueResolver]
})
]
}
})
```
想了解更多自动按需导入的信息,请参考:[unplugin-vue-components](https://github.com/antfu/unplugin-vue-components) 和 [unplugin-auto-import](https://github.com/antfu/unplugin-auto-import)。
### 多组件引入

View File

@ -18,7 +18,7 @@
<app-root></app-root>
<div id="app" class="wp100 hp100 pt60 of-hidden"></div>
<!-- prettier-ignore -->
<script id="tinyui-design-common" src="https://test-static-resource.obs.cn-north-7.ulanqab.huawei.com/tinyui-design-common/1.0.5.20240330164329/tinyui-design-common.min.js"></script>
<script id="tinyui-design-common" src="https://res.hc-cdn.com/tinyui-design-common/1.0.5.20230707170109/tinyui-design-common.min.js"></script>
<script type="module" src="./src/main.js"></script>
</body>
</html>

View File

@ -1,6 +1,6 @@
{
"name": "@opentiny/vue-docs",
"version": "2.2.20",
"version": "2.2.23",
"license": "MIT",
"scripts": {
"start": "vite",

View File

@ -17,7 +17,7 @@ import logoUrl from './assets/opentiny-logo.svg?url'
import GitHub from './icons/Github.vue'
import Share from './icons/Share.vue'
const VERSION = 'tiny-vue-version-3.14'
const VERSION = 'tiny-vue-version-3.15'
const LAYOUT = 'playground-layout'
const LAYOUT_REVERSE = 'playground-layout-reverse'
@ -28,7 +28,7 @@ const isMobileFirst = tinyMode === 'mobile-first'
const isSaas = tinyTheme === 'saas'
const isPreview = searchObj.get('openMode') === 'preview' //
const versions = ['3.14', '3.13', '3.12', '3.11', '3.10', '3.9', '3.8']
const versions = ['3.15', '3.14', '3.13', '3.12', '3.11', '3.10', '3.9', '3.8']
const latestVersion = isPreview ? versions[0] : localStorage.getItem(VERSION) || versions[0]
const cdnHost = localStorage.getItem('setting-cdn')
const getRuntime = (version) => {

View File

@ -1,6 +1,6 @@
{
"name": "@opentiny/unplugin-tiny-vue",
"version": "0.0.1",
"version": "0.0.2",
"description": "A vite auto import plugin for TinyVue",
"main": "dist/index.cjs",
"module": "dist/index.js",
@ -35,7 +35,7 @@
"vite": ">=4"
},
"dependencies": {
"magic-string": "^0.27.0"
"unplugin-vue-components": "^0.26.0"
},
"devDependencies": {
"rimraf": "^5.0.5",

View File

@ -1,62 +1,37 @@
import MagicString from 'magic-string'
import type { Plugin } from 'vite'
import AutoVite from 'unplugin-vue-components/vite'
import AutoWebpack from 'unplugin-vue-components/webpack'
import AutoRollup from 'unplugin-vue-components/rollup'
import AutoEsbuild from 'unplugin-vue-components/esbuild'
import AutoRspack from 'unplugin-vue-components/rspack'
function pascalCase(str: string) {
const camelCaseStr = str.replace(/-(\w)/g, (_, c) => (c ? c.toUpperCase() : ''))
return camelCaseStr.charAt(0).toUpperCase() + camelCaseStr.slice(1)
const supportMap = {
'vite': AutoVite,
'webpack': AutoWebpack,
'rollup': AutoRollup,
'esbuild': AutoEsbuild,
'rspack': AutoRspack
}
const resolveVue = (code: string, s: MagicString) => {
const results: any = []
for (const match of code.matchAll(/_resolveComponent[0-9]*\("(.+?)"\)/g)) {
const matchedName = match[1]
if (match.index != null && matchedName && !matchedName.startsWith('_')) {
const start = match.index
const end = start + match[0].length
results.push({
rawName: matchedName,
replace: (resolved: string) => s.overwrite(start, end, resolved)
})
}
}
return results
}
const findComponent = (rawName: string, name: string, s: MagicString) => {
if (!name.match(/^Tiny[a-zA-Z]/)) {
return
}
s.prepend(`import ${name} from '@opentiny/vue-${rawName.slice(5)}';\n`)
}
const transformCode = (code) => {
const s = new MagicString(code)
const results = resolveVue(code, s)
for (const { rawName, replace } of results) {
const name = pascalCase(rawName)
findComponent(rawName, name, s)
replace(name)
}
const result = s.toString()
return result
}
export default function vitePluginAutoImport(): Plugin {
return {
name: '@opentiny/auto-import',
transform(code, id) {
// 不处理node_modules内的依赖
if (/\.(?:vue)$/.test(id) && !/(node_modules)/.test(id)) {
return {
code: transformCode(code),
map: null
}
}
export const TinyVueResolver = (componentName) => {
if (componentName.startsWith('Tiny') && !componentName.startsWith('TinyIcon')) {
return {
name: componentName.slice(4),
from: '@opentiny/vue'
}
}
}
/** TinyVue Vite,Webpack,Rollup
* Tiny Icon的自动导入
* @example
* import autoImportPlugin from '@opentiny/unplugin-tiny-vue'
* plugins: [autoImportPlugin('vite')]
*/
export default (name) => {
// 兼容webpack/vite的差异
const autoPlugin = supportMap[name].default || supportMap[name]
return autoPlugin({
resolvers: [TinyVueResolver]
})
}

View File

@ -1,7 +1,7 @@
{
"name": "@opentiny/vue-renderless",
"private": true,
"version": "3.15.0",
"version": "3.15.1",
"description": "An enterprise-class UI component library, support both Vue.js 2 and Vue.js 3, as well as PC and mobile.",
"homepage": "https://opentiny.design/tiny-vue",
"keywords": [

View File

@ -11,6 +11,58 @@
*/
import type { ITabsRenderlessParams, ITabsPane, ITabsCustomEvent, ITabsPaneVm } from '@/types'
// 此处与aui区别开将tabNav的方法抽离出来从源头解决pane的排序问题
const getOrderedPanes = (parent, panes) => {
const slotDefault = parent.$slots.default
let orders
if (typeof slotDefault === 'function') {
orders = []
const tabVnodes = slotDefault()
const handler = ({ type, componentOptions, props }) => {
let componentName = type && type.componentName
if (!componentName) componentName = componentOptions && componentOptions.Ctor.extendOptions.componentName
if (componentName === 'TabItem') {
const paneName = (props && props.name) || (componentOptions && componentOptions.propsData.name)
orders.push(paneName)
}
}
tabVnodes.forEach(({ type, componentOptions, props, children }) => {
if (
type &&
(type.toString() === 'Symbol(Fragment)' || // vue@3.3之前的开发模式
type.toString() === 'Symbol(v-fgt)' || // vue@3.3.1 的变更
type.toString() === 'Symbol()') // 构建后
) {
Array.isArray(children) &&
children.forEach(({ type, componentOptions, props }) => handler({ type, componentOptions, props }))
} else {
handler({ type, componentOptions, props })
}
})
}
// 此处不同步auivue3情况下插槽使用v-if生成的slotDefault有差异
if (orders.length > 0) {
let tmpPanes = []
orders.forEach((paneName) => {
let pane = panes.find((pane) => pane.name === paneName)
if (pane) tmpPanes.push(pane)
})
panes = tmpPanes
}
return panes
}
export const calcPaneInstances =
({
constants,
@ -28,10 +80,12 @@ export const calcPaneInstances =
tabItemVNodes().forEach((vnode) => {
if (Array.isArray(vnode.children)) {
vnode.children.forEach((child) => {
orderPanes.push(child.props?.name)
const name = child.props?.name
name && orderPanes.push(name)
})
} else {
orderPanes.push(vnode.props?.name)
const name = vnode.props?.name
name && orderPanes.push(name)
}
})
const currentPanes = [] as ITabsPaneVm[]
@ -42,24 +96,7 @@ export const calcPaneInstances =
index > -1 ? (currentPanes[index] = vm) : currentPanes.push(vm)
}
})
const currentPaneStates = currentPanes.map((pane) => pane.state)
const paneStates = state.panes.map((pane) => pane.state)
let newPanes = [] as ITabsPaneVm[]
for (let i = 0; i < paneStates.length; i++) {
const paneState = paneStates[i]
const index = currentPaneStates.indexOf(paneState)
if (index > -1) {
newPanes.push(state.panes[i])
currentPanes.splice(index, 1)
currentPaneStates.splice(index, 1)
}
}
newPanes = newPanes.concat(currentPanes)
const newPanes = getOrderedPanes(parent, currentPanes) as ITabsPaneVm[]
const panesChanged = !(
newPanes.length === state.panes.length &&

View File

@ -23,14 +23,23 @@ import type {
computedSuffixIcon
} from '../src/action-menu'
export interface IActonMenuOptionsItem {
label?: string
disabled?: boolean
divided?: boolean
children?: IActonMenuOptionsItem[]
icon?: any
[key: string]: any
}
export interface IActionMenuState {
visibleOptions: object
moreOptions: object
visibleOptions: IActonMenuOptionsItem[]
moreOptions: IActonMenuOptionsItem[]
isCardMode: boolean
spacing: string | number
maxShowNum: number
moreText: string
suffixIcon: string | Object
suffixIcon: string | object
}
export type IActionMenuProps = ExtractPropTypes<typeof actionMenuProps>

View File

@ -1,6 +1,6 @@
{
"name": "@opentiny/vue-theme-saas",
"version": "3.15.0",
"version": "3.15.1",
"description": "An enterprise-class UI component library, support both Vue.js 2 and Vue.js 3, as well as PC and mobile.",
"homepage": "https://opentiny.design/tiny-vue",
"main": "index.css",
@ -101,4 +101,4 @@
]
}
}
}
}

View File

@ -50,8 +50,6 @@
@import './config-provider/index.less';
@import './container/index.less';
@import './country/index.less';
@import './credit-card/index.less';
@import './credit-card-form/index.less';
@import './crop/index.less';
@import './currency/index.less';
@import './date-panel/index.less';
@ -59,7 +57,6 @@
@import './date-range/index.less';
@import './date-table/index.less';
@import './dept/index.less';
@import './detail-page/index.less';
@import './dialog-box/index.less';
@import './dialog-select/index.less';
@import './divider/index.less';
@ -102,6 +99,7 @@
@import './logout/index.less';
@import './menubar/index.less';
@import './milestone/index.less';
@import './mind-map/index.less';
@import './modal/index.less';
@import './month-range/index.less';
@import './month-table/index.less';
@ -142,7 +140,6 @@
@import './selector/index.less';
@import './skeleton/index.less';
@import './skeleton-item/index.less';
@import './slide-bar/index.less';
@import './slide-img/index.less';
@import './slider/index.less';
@import './split/index.less';

View File

@ -1,6 +1,6 @@
{
"name": "@opentiny/vue-theme",
"version": "3.15.0",
"version": "3.15.1",
"description": "An enterprise-class UI component library, support both Vue.js 2 and Vue.js 3, as well as PC and mobile.",
"main": "index.css",
"homepage": "https://opentiny.design/tiny-vue",
@ -87,4 +87,4 @@
]
}
}
}
}

View File

@ -1003,6 +1003,9 @@
// 部分场景下浏览器缩放比例导致表头和表体错位问题
th.col__gutter {
width: 0;
position: sticky;
right: 0;
background-color: var(--ti-grid-header-background-color);
}
}

View File

@ -319,12 +319,6 @@
}
}
&.is-leaf:not(.is-root) {
.@{tree-node-prefix-cls}__content {
padding-left: var(--ti-tree-node-icon-font-size);
}
}
&__content {
display: flex;
align-items: center;

View File

@ -1,6 +1,6 @@
{
"name": "@opentiny/vue-action-menu",
"version": "3.15.0",
"version": "3.15.1",
"description": "",
"main": "lib/index.js",
"module": "index.ts",
@ -24,4 +24,4 @@
"@opentiny/vue-icon": "workspace:~"
},
"license": "MIT"
}
}

View File

@ -42,6 +42,7 @@
<tiny-dropdown-item
v-for="(item, index) in state.moreOptions"
:key="index"
:divided="item.divided"
:item-data="item"
:label="item[textField]"
:disabled="item.disabled"

View File

@ -1,6 +1,6 @@
{
"name": "@opentiny/vue-grid",
"version": "3.15.0",
"version": "3.15.1",
"description": "",
"main": "lib/index.js",
"module": "index.ts",
@ -31,4 +31,4 @@
"@opentiny/vue-common": "workspace:~"
},
"license": "MIT"
}
}

View File

@ -267,9 +267,10 @@ export default {
if (this.$grid.pagerConfig) {
this.$grid.pagerConfig.currentPage = 1
}
// 3、抛出filter-change事件在服务端筛选时grid才会注册filter-change事件处理
emitEvent(this, 'filter-change', [{ filters, $table: this }])
}
// 3、抛出filter-change事件在服务端筛选时grid会注册filter-change事件处理然后grid会再次抛出filter-change
// 在本地筛选时table会直接对业务抛出filter-change
emitEvent(this, 'filter-change', [{ filters, $table: this }])
this.updateFooter()
@ -327,7 +328,8 @@ export default {
// 如果清除所有列筛选或者参数传递的清除列存在才发送事件从reload执行过来的不发送事件
if (field === true || column) {
emitEvent(this, 'filter-change', [{ filters: {}, $table: this }])
const filters = columnfilters(this.visibleColumn)
emitEvent(this, 'filter-change', [{ filters, $table: this }])
}
this.clearSelection()

View File

@ -1,6 +1,6 @@
{
"name": "@opentiny/vue-tabs",
"version": "3.15.0",
"version": "3.15.1",
"description": "",
"main": "lib/index.js",
"module": "index.ts",

View File

@ -22,56 +22,6 @@ import type { ITabNavApi } from '@opentiny/vue-renderless/types/tab-nav.type'
import TabBar from './tab-bar.vue'
import { tabNavPcProps } from './index'
const getOrderedPanes = (state, panes) => {
const slotDefault = state.rootTabs.$slots.default
let orders
if (typeof slotDefault === 'function') {
orders = []
const tabVnodes = slotDefault()
const handler = ({ type, componentOptions, props }) => {
let componentName = type && type.componentName
if (!componentName) componentName = componentOptions && componentOptions.Ctor.extendOptions.componentName
if (componentName === 'TabItem') {
const paneName = (props && props.name) || (componentOptions && componentOptions.propsData.name)
orders.push(paneName)
}
}
tabVnodes.forEach(({ type, componentOptions, props, children }) => {
if (
type &&
(type.toString() === 'Symbol(Fragment)' || // vue@3.3
type.toString() === 'Symbol(v-fgt)' || // vue@3.3.1
type.toString() === 'Symbol()') //
) {
Array.isArray(children) &&
children.forEach(({ type, componentOptions, props }) => handler({ type, componentOptions, props }))
} else {
handler({ type, componentOptions, props })
}
})
}
if (orders) {
let tmpPanes = []
orders.forEach((paneName) => {
let pane = panes.find((pane) => pane.name === paneName)
if (pane) tmpPanes.push(pane)
})
panes = tmpPanes
}
return panes
}
export default defineComponent({
name: $prefix + 'TabNav',
components: {
@ -195,7 +145,7 @@ export default defineComponent({
)
}
const tabs = getOrderedPanes(state, panes).map((pane, index) => {
const tabs = panes.map((pane, index) => {
let tabName = pane.name || pane.state.index || index
const withClose = pane.state.isClosable || editable