diff --git a/examples/sites/demos/apis/numeric.js b/examples/sites/demos/apis/numeric.js index d50a399e8..50d844549 100644 --- a/examples/sites/demos/apis/numeric.js +++ b/examples/sites/demos/apis/numeric.js @@ -393,6 +393,18 @@ export default { mode: ['mobile-first'], mfDemo: '' }, + { + name: 'string-mode', + type: 'Boolean', + defaultValue: '', + desc: { + 'zh-CN': '使用字符串模式,精度超过JS限制时使用', + 'en-US': '' + }, + mode: ['pc', 'mobile', 'mobile-first'], + pcDemo: 'string-mode', + mfDemo: '' + }, { name: 'value', type: 'Number', diff --git a/examples/sites/demos/pc/app/button/ghost-composition-api.vue b/examples/sites/demos/pc/app/button/ghost-composition-api.vue index 194e11942..db5cb58a1 100644 --- a/examples/sites/demos/pc/app/button/ghost-composition-api.vue +++ b/examples/sites/demos/pc/app/button/ghost-composition-api.vue @@ -1,11 +1,11 @@ diff --git a/examples/sites/demos/pc/app/button/ghost.spec.ts b/examples/sites/demos/pc/app/button/ghost.spec.ts index fdbc036b3..425f0bc05 100644 --- a/examples/sites/demos/pc/app/button/ghost.spec.ts +++ b/examples/sites/demos/pc/app/button/ghost.spec.ts @@ -8,10 +8,12 @@ test('幽灵按钮', async ({ page }) => { const getGhostBtn = (index: number) => demo.locator('.tiny-button').nth(index) // 默认幽灵按钮 + await page.waitForTimeout(1000) await expect(getGhostBtn(0)).toHaveCSS('color', 'rgb(37, 43, 58)') await expect(getGhostBtn(0)).toHaveCSS('background-color', 'rgba(0, 0, 0, 0)') await expect(getGhostBtn(0)).toHaveCSS('border-bottom-color', 'rgb(173, 176, 184)') - await getGhostBtn(0).hover() + await page.waitForTimeout(100) + await getGhostBtn(0).click() await page.waitForTimeout(100) await expect(getGhostBtn(0)).toHaveCSS('color', 'rgb(94, 124, 224)') await expect(getGhostBtn(0)).toHaveCSS('background-color', 'rgba(0, 0, 0, 0)') @@ -21,7 +23,8 @@ test('幽灵按钮', async ({ page }) => { await expect(getGhostBtn(1)).toHaveCSS('color', 'rgb(94, 124, 224)') await expect(getGhostBtn(1)).toHaveCSS('background-color', 'rgba(0, 0, 0, 0)') await expect(getGhostBtn(1)).toHaveCSS('border-bottom-color', 'rgb(94, 124, 224)') - await getGhostBtn(1).hover() + await page.waitForTimeout(100) + await getGhostBtn(1).click() await page.waitForTimeout(100) await expect(getGhostBtn(1)).toHaveCSS('color', 'rgb(118, 147, 245)') await expect(getGhostBtn(1)).toHaveCSS('background-color', 'rgba(0, 0, 0, 0)') @@ -31,7 +34,8 @@ test('幽灵按钮', async ({ page }) => { await expect(getGhostBtn(2)).toHaveCSS('color', 'rgb(80, 212, 171)') await expect(getGhostBtn(2)).toHaveCSS('background-color', 'rgba(0, 0, 0, 0)') await expect(getGhostBtn(2)).toHaveCSS('border-bottom-color', 'rgb(80, 212, 171)') - await getGhostBtn(2).hover() + await page.waitForTimeout(100) + await getGhostBtn(2).click() await page.waitForTimeout(100) await expect(getGhostBtn(2)).toHaveCSS('color', 'rgb(172, 242, 220)') await expect(getGhostBtn(2)).toHaveCSS('background-color', 'rgba(0, 0, 0, 0)') @@ -41,7 +45,8 @@ test('幽灵按钮', async ({ page }) => { await expect(getGhostBtn(3)).toHaveCSS('color', 'rgb(37, 43, 58)') await expect(getGhostBtn(3)).toHaveCSS('background-color', 'rgba(0, 0, 0, 0)') await expect(getGhostBtn(3)).toHaveCSS('border-bottom-color', 'rgb(37, 43, 58)') - await getGhostBtn(3).hover() + await page.waitForTimeout(100) + await getGhostBtn(3).click() await page.waitForTimeout(100) await expect(getGhostBtn(3)).toHaveCSS('color', 'rgb(92, 97, 115)') await expect(getGhostBtn(3)).toHaveCSS('background-color', 'rgba(0, 0, 0, 0)') @@ -51,7 +56,8 @@ test('幽灵按钮', async ({ page }) => { await expect(getGhostBtn(4)).toHaveCSS('color', 'rgb(250, 152, 65)') await expect(getGhostBtn(4)).toHaveCSS('background-color', 'rgba(0, 0, 0, 0)') await expect(getGhostBtn(4)).toHaveCSS('border-bottom-color', 'rgb(250, 152, 65)') - await getGhostBtn(4).hover() + await page.waitForTimeout(100) + await getGhostBtn(4).click() await page.waitForTimeout(100) await expect(getGhostBtn(4)).toHaveCSS('color', 'rgb(250, 194, 10)') await expect(getGhostBtn(4)).toHaveCSS('background-color', 'rgba(0, 0, 0, 0)') @@ -61,7 +67,8 @@ test('幽灵按钮', async ({ page }) => { await expect(getGhostBtn(5)).toHaveCSS('color', 'rgb(199, 0, 11)') await expect(getGhostBtn(5)).toHaveCSS('background-color', 'rgba(0, 0, 0, 0)') await expect(getGhostBtn(5)).toHaveCSS('border-bottom-color', 'rgb(199, 0, 11)') - await getGhostBtn(5).hover() + await page.waitForTimeout(100) + await getGhostBtn(5).click() await page.waitForTimeout(100) await expect(getGhostBtn(5)).toHaveCSS('color', 'rgb(214, 74, 82)') await expect(getGhostBtn(5)).toHaveCSS('background-color', 'rgba(0, 0, 0, 0)') diff --git a/examples/sites/demos/pc/app/button/ghost.vue b/examples/sites/demos/pc/app/button/ghost.vue index 80998cada..73da3562f 100644 --- a/examples/sites/demos/pc/app/button/ghost.vue +++ b/examples/sites/demos/pc/app/button/ghost.vue @@ -1,11 +1,11 @@ diff --git a/examples/sites/demos/pc/app/numeric/mouse-wheel.spec.ts b/examples/sites/demos/pc/app/numeric/mouse-wheel.spec.ts index d87a1af25..06b322576 100644 --- a/examples/sites/demos/pc/app/numeric/mouse-wheel.spec.ts +++ b/examples/sites/demos/pc/app/numeric/mouse-wheel.spec.ts @@ -7,7 +7,7 @@ test('鼠标滚轮事件', async ({ page }) => { const numeric = page.getByRole('spinbutton') const initVal = Number(await numeric.inputValue()) await numeric.click() - await page.mouse.wheel(0, -100) + await page.mouse.wheel(0, -500) const currentVal = Number(await numeric.inputValue()) - expect(currentVal).toBeGreaterThanOrEqual(initVal) + expect(currentVal).toBeLessThan(initVal) }) diff --git a/examples/sites/demos/pc/app/numeric/string-mode-composition-api.vue b/examples/sites/demos/pc/app/numeric/string-mode-composition-api.vue new file mode 100644 index 000000000..c9b08ae8c --- /dev/null +++ b/examples/sites/demos/pc/app/numeric/string-mode-composition-api.vue @@ -0,0 +1,10 @@ + + + diff --git a/examples/sites/demos/pc/app/numeric/string-mode.spec.ts b/examples/sites/demos/pc/app/numeric/string-mode.spec.ts new file mode 100644 index 000000000..2dc813089 --- /dev/null +++ b/examples/sites/demos/pc/app/numeric/string-mode.spec.ts @@ -0,0 +1,17 @@ +import { test, expect } from '@playwright/test' + +test('高精度', async ({ page }) => { + page.on('pageerror', (exception) => expect(exception).toBeNull()) + await page.goto('numeric#string-mode') + + const input = page.getByRole('spinbutton') + const increaseBtn = page.locator('.tiny-numeric__increase') + const decreaseBtn = page.locator('.tiny-numeric__decrease') + await increaseBtn.click() + const increasedVal = await input.inputValue() + expect(increasedVal).toContain('0.00000000000000000004') + + await decreaseBtn.click() + const decreasedVal = await input.inputValue() + expect(decreasedVal).toContain('0.00000000000000000002') +}) diff --git a/examples/sites/demos/pc/app/numeric/string-mode.vue b/examples/sites/demos/pc/app/numeric/string-mode.vue new file mode 100644 index 000000000..925ad9b90 --- /dev/null +++ b/examples/sites/demos/pc/app/numeric/string-mode.vue @@ -0,0 +1,19 @@ + + + diff --git a/examples/sites/demos/pc/app/numeric/webdoc/numeric.js b/examples/sites/demos/pc/app/numeric/webdoc/numeric.js index 709660ac4..0212aa419 100644 --- a/examples/sites/demos/pc/app/numeric/webdoc/numeric.js +++ b/examples/sites/demos/pc/app/numeric/webdoc/numeric.js @@ -171,6 +171,19 @@ export default { }, codeFiles: ['blur-event.vue'] }, + { + demoId: 'string-mode', + name: { + 'zh-CN': '高精度', + 'en-US': 'Out of Focus Event' + }, + desc: { + 'zh-CN': + '

可通过 string-mode 设置高精度模式,当 JS 默认的 Number 不满足数字的长度与精度需求时。

\n', + 'en-US': '

The@blurevent is triggered when the text box loses focus.

\n' + }, + codeFiles: ['string-mode.vue'] + }, { demoId: 'filter-mode', name: { diff --git a/examples/sites/demos/pc/app/select/disabled-composition-api.vue b/examples/sites/demos/pc/app/select/disabled-composition-api.vue index 22fc976ba..5f8139c62 100644 --- a/examples/sites/demos/pc/app/select/disabled-composition-api.vue +++ b/examples/sites/demos/pc/app/select/disabled-composition-api.vue @@ -82,12 +82,12 @@ const options2 = ref([ { value: '选项1', label: '黄金糕' }, { value: '选项2', label: '双皮奶', disabled: true }, { value: '选项3', label: '蚵仔煎' }, - { value: '选项4', label: '龙须面' }, + { value: '选项4', label: '龙须面', disabled: true }, { value: '选项5', label: '北京烤鸭' } ]) const value1 = ref('') -const value2 = ref([]) +const value2 = ref(['选项2']) const value3 = ref('') const value4 = ref(['选项2', '选项3']) const value5 = ref(['选项1', '选项2', '选项3', '选项4', '选项5']) diff --git a/examples/sites/demos/pc/app/select/disabled.spec.ts b/examples/sites/demos/pc/app/select/disabled.spec.ts index 6d65657ac..f19b35661 100644 --- a/examples/sites/demos/pc/app/select/disabled.spec.ts +++ b/examples/sites/demos/pc/app/select/disabled.spec.ts @@ -19,15 +19,15 @@ test('多选某项禁用', async ({ page }) => { const tag = select.locator('.tiny-tag') const option = dropdown.locator('.tiny-option') - await expect(tag).toHaveCount(0) + await expect(tag).toHaveCount(1) await select.click() await expect(option.filter({ hasText: '双皮奶' })).toHaveClass(/is-disabled/) await option.filter({ hasText: '双皮奶' }).click() - await expect(tag).toHaveCount(0) + await expect(tag).toHaveCount(1) await option.filter({ hasText: '黄金糕' }).click() - await expect(tag).toHaveCount(1) + await expect(tag).toHaveCount(2) await expect(tag.filter({ hasText: '黄金糕' })).toHaveCount(1) }) diff --git a/examples/sites/demos/pc/app/select/disabled.vue b/examples/sites/demos/pc/app/select/disabled.vue index 3822862da..92f23fc35 100644 --- a/examples/sites/demos/pc/app/select/disabled.vue +++ b/examples/sites/demos/pc/app/select/disabled.vue @@ -87,11 +87,11 @@ export default { { value: '选项1', label: '黄金糕' }, { value: '选项2', label: '双皮奶', disabled: true }, { value: '选项3', label: '蚵仔煎' }, - { value: '选项4', label: '龙须面' }, + { value: '选项4', label: '龙须面', disabled: true }, { value: '选项5', label: '北京烤鸭' } ], value1: '', - value2: [], + value2: ['选项2'], value3: '', value4: ['选项2', '选项3'], value5: ['选项1', '选项2', '选项3', '选项4', '选项5'] diff --git a/examples/sites/demos/pc/app/select/events.spec.ts b/examples/sites/demos/pc/app/select/events.spec.ts index cf990224e..9cb67d948 100644 --- a/examples/sites/demos/pc/app/select/events.spec.ts +++ b/examples/sites/demos/pc/app/select/events.spec.ts @@ -26,7 +26,7 @@ test('单选事件', async ({ page }) => { await page.waitForTimeout(200) await input.hover() - await select.locator('.tiny-select__caret').click() + await select.locator('.tiny-select__caret.icon-close').click() await page.waitForTimeout(500) await expect(input).toHaveValue('') await expect(model.filter({ hasText: '触发 clear 事件' })).toHaveCount(1) @@ -42,6 +42,7 @@ test('多选事件', async ({ page }) => { const option = dropdown.locator('.tiny-option') const model = page.locator('.tiny-modal') + await page.waitForTimeout(500) await select.click() await expect(model.filter({ hasText: '触发 focus 事件' })).toHaveCount(1) await expect(model.filter({ hasText: '触发 visible-change 事件' })).toHaveCount(1) @@ -56,7 +57,6 @@ test('多选事件', async ({ page }) => { await page.waitForTimeout(500) await tag.first().locator('.tiny-tag__close').click() - await expect(model.filter({ hasText: '触发 blur 事件' })).toHaveCount(1) await expect(model.filter({ hasText: '触发 change 事件' })).toHaveCount(1) await expect(model.filter({ hasText: '触发 remove-tag 事件' })).toHaveCount(1) await expect(tag).toHaveCount(4) @@ -66,7 +66,7 @@ test('多选事件', async ({ page }) => { await page.waitForTimeout(200) await select.hover() - await select.locator('.tiny-select__caret').click() + await select.locator('.tiny-select__caret.icon-close').click() await expect(tag).toHaveCount(0) await expect(model.filter({ hasText: '触发 change 事件' })).toHaveCount(1) diff --git a/examples/sites/demos/pc/app/select/multiple.vue b/examples/sites/demos/pc/app/select/multiple.vue index c3f14ad17..84d230cfd 100644 --- a/examples/sites/demos/pc/app/select/multiple.vue +++ b/examples/sites/demos/pc/app/select/multiple.vue @@ -3,7 +3,7 @@
场景1:多选

- +
diff --git a/examples/sites/demos/pc/app/tabs/tabs-events-close.spec.ts b/examples/sites/demos/pc/app/tabs/tabs-events-close.spec.ts index f262e04c3..82b1b421c 100644 --- a/examples/sites/demos/pc/app/tabs/tabs-events-close.spec.ts +++ b/examples/sites/demos/pc/app/tabs/tabs-events-close.spec.ts @@ -5,7 +5,7 @@ test('删除事件', async ({ page }) => { await page.goto('tabs#tabs-events-close') const tabs = page.locator('.tiny-tabs') - const tabItem = tabs.getByRole('tab', { name: '表单组件' }) + const tabItem = tabs.getByRole('tab', { name: '其他组件' }) const close = tabItem.locator('.tiny-tabs__icon-close') const modal = page.locator('.tiny-modal').first() diff --git a/internals/cli/src/commands/build/build-entry-app.ts b/internals/cli/src/commands/build/build-entry-app.ts index 1097cd120..0e94e13b0 100644 --- a/internals/cli/src/commands/build/build-entry-app.ts +++ b/internals/cli/src/commands/build/build-entry-app.ts @@ -10,7 +10,7 @@ import { prettierFormat, logGreen } from '../../shared/utils' -import { getComponents } from '../../shared/module-utils' +import { getComponents, excludeComponents } from '../../shared/module-utils' import handlebarsRender from './handlebars.render' const version = getopentinyVersion({ key: 'version' }) @@ -61,7 +61,12 @@ const buildFullRuntime = () => { }) components.forEach((item) => { - if (item.inEntry !== false && !item.path.includes('river') && !item.path.includes('chart-beta')) { + if ( + item.inEntry !== false && + !item.path.includes('river') && + !item.path.includes('chart-beta') && + !excludeComponents.includes(item.name) + ) { const component = capitalizeKebabCase(item.name) componentsTemplate.push(` ${component}`) diff --git a/internals/cli/src/commands/build/build-entry.ts b/internals/cli/src/commands/build/build-entry.ts index d226c39c9..da2fbe368 100644 --- a/internals/cli/src/commands/build/build-entry.ts +++ b/internals/cli/src/commands/build/build-entry.ts @@ -10,7 +10,7 @@ import { prettierFormat, logGreen } from '../../shared/utils' -import { getComponents } from '../../shared/module-utils' +import { getComponents, excludeComponents } from '../../shared/module-utils' import handlebarsRender from './handlebars.render' const version = getopentinyVersion({ key: 'version' }) @@ -79,7 +79,7 @@ const createEntry = (mode) => { const PKGDeps = {} components.forEach((item) => { - if (item.inEntry !== false) { + if (item.inEntry !== false && !excludeComponents.includes(item.name)) { const component = capitalizeKebabCase(item.name) PKGDeps[item.importName] = 'workspace:~' componentsTemplate.push(` ${component}`) diff --git a/internals/cli/src/commands/build/build-ui.ts b/internals/cli/src/commands/build/build-ui.ts index c7dd7ef0c..2df8f0ef3 100644 --- a/internals/cli/src/commands/build/build-ui.ts +++ b/internals/cli/src/commands/build/build-ui.ts @@ -160,6 +160,7 @@ export const getBaseConfig = ({ vueVersion, dtsInclude, dts, buildTarget, isRunt } }, include: [...dtsInclude, 'packages/vue/*.d.ts'], + exclude: ['**/__tests__'], beforeWriteFile: (filePath, content) => { return { // "vue/src/alert/index.d.ts" ==> "alert/index.d.ts" diff --git a/internals/cli/src/commands/build/rollup/inline-chunks.ts b/internals/cli/src/commands/build/rollup/inline-chunks.ts index aa5d02658..3334217d5 100644 --- a/internals/cli/src/commands/build/rollup/inline-chunks.ts +++ b/internals/cli/src/commands/build/rollup/inline-chunks.ts @@ -1,13 +1,22 @@ -import type { Plugin, NormalizedOutputOptions, OutputBundle, OutputChunk } from 'rollup' +import type { Plugin, NormalizedOutputOptions, OutputBundle } from 'rollup' +import path from 'node:path' +import fs from 'node:fs' +import { rollup } from 'rollup' import chalk from 'chalk' +import commonjs from '@rollup/plugin-commonjs' +import { external } from '../../../shared/config' + +const bundlesToMerge = new Set() +const commonChunk = new Set() +let buildFormat +let isDelete export default function ({ deleteInlinedFiles = true }): Plugin { return { name: 'opentiny-vue:inline-chunks', - generateBundle: ({ format }: NormalizedOutputOptions, bundle: OutputBundle) => { - const bundlesToDelete = new Set() - const cache = {} - + generateBundle: async ({ format, dir }: NormalizedOutputOptions, bundle: OutputBundle) => { + buildFormat = format + isDelete = deleteInlinedFiles const jsAssets = Object.keys(bundle).filter((i) => /\.[mc]?js$/.test(i)) for (const jsName of jsAssets) { const jsChunk = bundle[jsName] @@ -16,51 +25,66 @@ export default function ({ deleteInlinedFiles = true }): Plugin { if (!jsChunk.code) continue if (format === 'es') { - jsChunk.code = jsChunk.code.replace( - // import { _ as _export_sfc } from "../../../../_plugin-vue_export-helper-1faf6727.mjs" - /^import\s*.+\s*from\s+"[./]+(.+-[a-f0-9]{8}.+)".*$/gim, - (_, chunkName) => { - if (!cache[chunkName]) { - cache[chunkName] = (bundle[chunkName] as OutputChunk).code - .replace(/export {[\s\S]+$/, '') - .replace(/_extends/g, '_extends_tiny') - .replace(/_createForOfIteratorHelperLoose/g, '_createForOfIteratorHelperLoose_tiny') - .replace(/_unsupportedIterableToArray/g, '_unsupportedIterableToArray_tiny') - .replace(/_arrayLikeToArray/g, '_arrayLikeToArray_tiny') - bundlesToDelete.add(chunkName) - } - - return cache[chunkName] - } - ) + const reg = /^import(\s*.+\s*from)?\s+"[./]+(.+-[a-f0-9]{8}.+)".*$/gim + const matchArr = jsChunk.code.match(reg) + if (matchArr) { + const filePath = path.join(dir, jsName) + bundlesToMerge.add(filePath) + matchArr.forEach((matchImport) => { + const sourceName = matchImport.match(/"(.+)"/)[1] + commonChunk.add(path.join(filePath, '../', sourceName)) + }) + } } if (format === 'cjs') { - jsChunk.code = jsChunk.code.replace( - // var _pluginVue_exportHelper = require("../../../../_plugin-vue_export-helper-65c7de93.js"); - /^var\s+(.+)\s+=\s+require\("[./]+(.+-[a-f0-9]{8}.+)".*$/gim, - (_, localVarName, chunkName) => { - if (!cache[chunkName]) { - cache[chunkName] = - 'var _pluginVue_exportHelper = {};\n' + - (bundle[chunkName] as OutputChunk).code.replace(/exports\./g, `${localVarName}.`) - bundlesToDelete.add(chunkName) - } - - return cache[chunkName] - } - ) + const reg = /require\("[./]+(.+-[a-f0-9]{8}.+)".*$/gim + const matchArr = jsChunk.code.match(reg) + if (matchArr) { + const filePath = path.join(dir, jsName) + bundlesToMerge.add(filePath) + matchArr.forEach((matchRequire) => { + const sourceName = matchRequire.match(/"(.+)"/)[1] + commonChunk.add(path.join(filePath, '../', sourceName)) + }) + } } } - - if (deleteInlinedFiles) { - // 删除 chunks - bundlesToDelete.forEach((name) => { - delete bundle[name] - // eslint-disable-next-line no-console - console.log(`\n${chalk.red(name)} 已经被内联并删除`) - }) - } - } + }, + closeBundle: async () => + new Promise((resolve) => { + // eslint-disable-next-line no-console + console.log(`\n${chalk.green('开始内联公共依赖')}`) + let i = 0 + if (bundlesToMerge.size > 0) { + bundlesToMerge.forEach(async (filePath) => { + if (commonChunk.has(filePath)) { + ++i + } else { + const bundle = await rollup({ + input: filePath, + external: (source) => external(source), + plugins: buildFormat === 'cjs' ? [commonjs()] : [] + }) + await bundle.write({ dir: path.join(filePath, '../'), format: buildFormat }) + await bundle.close() + ++i + } + if (i === bundlesToMerge.size) { + resolve() + } + }) + } else { + resolve() + } + }).then(async () => { + if (isDelete) { + commonChunk.forEach((filePath) => { + fs.unlinkSync(filePath) + // eslint-disable-next-line no-console + console.log(`\n${chalk.red(filePath)} 已经被内联并删除`) + }) + } + }) } } diff --git a/internals/cli/src/commands/release/releaseAurora.ts b/internals/cli/src/commands/release/releaseAurora.ts index 71b4e63bd..66465462a 100644 --- a/internals/cli/src/commands/release/releaseAurora.ts +++ b/internals/cli/src/commands/release/releaseAurora.ts @@ -35,15 +35,14 @@ const findAllpage = (packagesPath) => { // 解决TinyVue和AUI国际化键名不兼容问题 .replace(/zhCN/g, 'zh_CN') .replace(/enUS/g, 'en_US') + .replace(/-openaui/g, '-opentiny') // 解决在linkjs环境z-index无法统一导致下拉框被遮挡问题 .replace(/"(.*?\/popup-manager)"/g, '"@aurora/renderless/common/deps/popup-manager"') // 解决当AUI只有一个mobile-first模板而TinyVue有多个模板,导致Linkjs加载不到多端模板的问题 if ( packagesPath.endsWith('index.js') && - onlyMobileFirstTemplateLists.some( - (item) => packagesPath.includes(`${item}\\`) || packagesPath.includes(`${item}/`) - ) + onlyMobileFirstTemplateLists.some((item) => packagesPath.includes(`${path.sep}${item}${path.sep}`)) ) { result = result.replace(/pc.js/g, 'mobile-first.js') } diff --git a/internals/cli/src/shared/module-utils.ts b/internals/cli/src/shared/module-utils.ts index bac94afb9..297b31e4a 100644 --- a/internals/cli/src/shared/module-utils.ts +++ b/internals/cli/src/shared/module-utils.ts @@ -13,6 +13,9 @@ const moduleMap = require(pathFromWorkspaceRoot('packages/modules.json')) type mode = 'pc' | 'mobile' | 'mobile-first' +// 需要在入口文件中排除的组件,比如:富文本 +export const excludeComponents = ['RichText'] + export interface Module { /** 源码路径,如 vue/src/button/index.ts */ path: string @@ -452,8 +455,7 @@ const createModuleMapping = (componentName, isMobile = false) => { parser: 'json', printWidth: 10 } - }), - + }) ) } diff --git a/packages/design/aurora/src/select/index.ts b/packages/design/aurora/src/select/index.ts index 0aff271da..99f0bea43 100644 --- a/packages/design/aurora/src/select/index.ts +++ b/packages/design/aurora/src/select/index.ts @@ -14,7 +14,9 @@ export default { medium: 42 }, spacingHeight: 2, - initialInputHeight: 30 + initialInputHeight: 30, + // 显示清除等图标时,不隐藏下拉箭头时 + autoHideDownIcon: false }, props: { tagType: 'info' diff --git a/packages/design/saas/src/select/index.ts b/packages/design/saas/src/select/index.ts index ff3366231..322369106 100644 --- a/packages/design/saas/src/select/index.ts +++ b/packages/design/saas/src/select/index.ts @@ -14,7 +14,9 @@ export default { medium: 32 }, spacingHeight: 4, - initialInputHeight: 30 + initialInputHeight: 30, + // 显示清除等图标时,不隐藏下拉箭头时 + autoHideDownIcon: false }, props: { tagType: 'info' diff --git a/packages/design/smb/src/tree-node/index.ts b/packages/design/smb/src/tree-node/index.ts index 1bb8ba7ef..9795ce9c3 100644 --- a/packages/design/smb/src/tree-node/index.ts +++ b/packages/design/smb/src/tree-node/index.ts @@ -2,6 +2,7 @@ import { iconPutAway, iconExpand } from '@opentiny/vue-icon' export default { icons: { + // 在 showLine=true时,才要切换的图标。 并不是设置正常模式下的图标 expanded: iconExpand(), collapse: iconPutAway() } diff --git a/packages/modules.json b/packages/modules.json index a20e599ca..123c42e43 100644 --- a/packages/modules.json +++ b/packages/modules.json @@ -2360,6 +2360,14 @@ "type": "template", "exclude": false }, + "RichText": { + "path": "vue/src/rich-text/index.ts", + "type": "component", + "exclude": false, + "mode": [ + "pc" + ] + }, "RichTextEditor": { "path": "vue/src/rich-text-editor/index.ts", "type": "component", diff --git a/packages/renderless/src/common/bigInt.ts b/packages/renderless/src/common/bigInt.ts index f12676c09..5e4254141 100644 --- a/packages/renderless/src/common/bigInt.ts +++ b/packages/renderless/src/common/bigInt.ts @@ -180,7 +180,7 @@ export class BigIntDecimal { const convertBigInt = (str) => { // 将以多个零开头的整数前置零清空 '0000000000000003e+21' --> '3e+21' ,解决BigInt(0000000000000003e+21)报错问题 const validStr = str.replace(/^0+/, '') || '0' - return f(`return BigInt(${validStr})`)() + return f(`return BigInt('${validStr}')`)() } if (validateNumber(mergedValue)) { const trimRet = trimNumber(mergedValue) @@ -188,7 +188,9 @@ export class BigIntDecimal { const numbers = trimRet.trimStr.split('.') this.integer = !numbers[0].includes('e') ? BigInt(numbers[0]) : numbers[0] const decimalStr = numbers[1] || '0' - this.decimal = convertBigInt(decimalStr) + + // 如果小数点后有科学计数法,需要特殊处理,如果是正常数字则保留之前逻辑 + this.decimal = decimalStr.includes('e') ? convertBigInt(decimalStr) : BigInt(decimalStr) this.decimalLen = decimalStr.length } else { this.nan = true diff --git a/packages/renderless/src/common/deps/date-util.ts b/packages/renderless/src/common/deps/date-util.ts index 77573e32c..e4e92c0f8 100644 --- a/packages/renderless/src/common/deps/date-util.ts +++ b/packages/renderless/src/common/deps/date-util.ts @@ -91,11 +91,13 @@ export const prevDate = (date, amount = 1) => new Date(date.getFullYear(), date. export const nextDate = (date, amount = 1) => new Date(date.getFullYear(), date.getMonth(), date.getDate() + amount) -export const getStartDateOfMonth = (year, month) => { +export const getStartDateOfMonth = (year, month, offsetDay = 0) => { const res = new Date(year, month, 1) const day = res.getDay() + const _day = day === 0 ? 7 : day - return day === 0 ? prevDate(res, 7) : prevDate(res, day) + const offset = _day + offsetDay <= 0 ? 7 + _day : _day + return prevDate(res, offset) } export const getWeekNumber = (src) => { @@ -143,6 +145,7 @@ const setRangeData = (arr, start, end, value) => { } } +// eslint-disable-next-line prefer-spread export const range = (length) => Array.apply(null, { length }).map((_, n) => n) export const getMonthDays = (date) => { diff --git a/packages/renderless/src/date-range/index.ts b/packages/renderless/src/date-range/index.ts index 97b58e47e..e148c02b3 100644 --- a/packages/renderless/src/date-range/index.ts +++ b/packages/renderless/src/date-range/index.ts @@ -207,6 +207,7 @@ export const handleClear = state.leftDate = calcDefaultValue(state.defaultValue)[0] state.rightDate = nextMonth(state.leftDate) state.rangeState.selecting = false + // tiny 新增下面行 state.rangeState.endDate = null emit('pick', null) diff --git a/packages/renderless/src/date-range/vue.ts b/packages/renderless/src/date-range/vue.ts index fa9369fb1..3cce39cec 100644 --- a/packages/renderless/src/date-range/vue.ts +++ b/packages/renderless/src/date-range/vue.ts @@ -127,6 +127,7 @@ const initState = ({ reactive, computed, api, constants, designConfig }) => { dateFormat: computed(() => (state.format ? extractDateFormat(state.format) : 'yyyy-MM-dd')), enableMonthArrow: computed(() => api.getEnableMonthArrow()), enableYearArrow: computed(() => api.computerEnableYearArrow()), + // tiny 新增 confirmButtonProps: { plain: true, type: 'default', diff --git a/packages/renderless/src/date-table/index.ts b/packages/renderless/src/date-table/index.ts index 498a05c7b..ab7bede46 100644 --- a/packages/renderless/src/date-table/index.ts +++ b/packages/renderless/src/date-table/index.ts @@ -21,10 +21,9 @@ import { clearTime } from '../common/deps/date-util' import { DATEPICKER } from '../common' -import type { IDateTableRow } from '@/types' const formatJudg = ({ day, offset, j, i, cell, count, dateCountOfLastMonth }) => { - const nodfpm = day + offset < 0 ? 7 + day + offset : day + offset + const nodfpm = day + offset <= 0 ? 7 + day + offset : day + offset if (j + i * 7 >= nodfpm) { cell.text = count++ @@ -58,22 +57,8 @@ export const getDateTimestamp = (time) => { return NaN } -export const arrayFindIndex = (arr, pred) => { - for (let i = 0, len = arr.length; i !== len; ++i) { - if (pred(arr[i])) { - return i - } - } - - return -1 -} - -export const arrayFind = (arr, pred) => { - const idx = arrayFindIndex(arr, pred) - return ~idx ? arr[idx] : undefined -} - -const getSelected = ({ props, cell, format, t, cellDate, selectedDate }) => { +// tiny 新增: api参数多余,优化掉 +const getSelected = (props, cell, format, t, cellDate, selectedDate) => { let selected = cell.selected if (props.selectionMode === 'dates') { @@ -92,14 +77,7 @@ export const getCell = let cell = row[props.showWeekNumber ? j + 1 : j] if (!cell) { - cell = { - row: i, - column: j, - inRange: false, - start: false, - end: false, - type: DATEPICKER.Normal - } + cell = { row: i, column: j, inRange: false, start: false, end: false, type: DATEPICKER.Normal } } cell.type = DATEPICKER.Normal @@ -119,15 +97,7 @@ export const getCell = const doCount = ({ i, day, offset, j, cell, count, dateCountOfLastMonth, dateCountOfMonth }) => { if (i >= 0 && i <= 1) { - const ret = formatJudg({ - day, - offset, - j, - i, - cell, - count, - dateCountOfLastMonth - }) + const ret = formatJudg({ day, offset, j, i, cell, count, dateCountOfLastMonth }) count = ret.count } else { if (count <= dateCountOfMonth) { @@ -141,8 +111,6 @@ const doCount = ({ i, day, offset, j, cell, count, dateCountOfLastMonth, dateCou return count } -export const coerceTruthyValueToArray = (val) => (Array.isArray(val) ? val : val ? [val] : []) - /** * 获取日期表格二维数组,用于渲染日期表格。 * @@ -160,7 +128,7 @@ export const coerceTruthyValueToArray = (val) => (Array.isArray(val) ? val : val */ export const getRows = ({ api, props, state, t, vm }) => - (): IDateTableRow[][] => { + () => { const date = new Date(state.year, state.month, 1) let day = getFirstDayOfMonth(date) const dateCountOfMonth = getDayCountOfMonth(date.getFullYear(), date.getMonth()) @@ -172,7 +140,7 @@ export const getRows = day = day === 0 ? 7 : day const offset = state.offsetDay - const rows: IDateTableRow[][] = state.tableRows + const rows = state.tableRows const startDate = state.startDate const disabledDate = props.disabledDate const cellClassName = props.cellClassName @@ -181,7 +149,7 @@ export const getRows = const isFunction = props.formatWeeks instanceof Function - const arr: Date[][] = [] + const arr = [] // 日期表格行,从0开始,共6行,[0, 5] for (let i = 0; i < 6; i++) { @@ -207,15 +175,7 @@ export const getRows = count = doCount({ i, day, offset, j, cell, count, dateCountOfLastMonth, dateCountOfMonth }) cell.disabled = typeof disabledDate === 'function' && disabledDate(cellDate) - cell.selected = getSelected({ - props, - cell, - api, - format: DATEPICKER.DateFormats.date, - t, - cellDate, - selectedDate - }) + cell.selected = getSelected(props, cell, DATEPICKER.DateFormats.date, t, cellDate, selectedDate) cell.customClass = typeof cellClassName === 'function' && cellClassName(cellDate) // 更新日期表格的行数据,执行了这行代码,rows 中才会有数据 @@ -250,6 +210,23 @@ export const getRows = return rows } +export const arrayFindIndex = (arr, pred) => { + for (let i = 0, len = arr.length; i !== len; ++i) { + if (pred(arr[i])) { + return i + } + } + + return -1 +} + +export const arrayFind = (arr, pred) => { + const idx = arrayFindIndex(arr, pred) + return ~idx ? arr[idx] : undefined +} + +export const coerceTruthyValueToArray = (val) => (Array.isArray(val) ? val : val ? [val] : []) + export const watchMinDate = ({ api, props }) => (value, oldvalue) => { @@ -398,15 +375,15 @@ export const markRange = const row = rows[i] for (let j = 0, l = row.length; j < l; j++) { - if (!props.showWeekNumber || j !== 0) { - const cell = row[j] - const index = i * 7 + j + (props.showWeekNumber ? -1 : 0) - const time = nextDate(startDate, index - state.offsetDay).getTime() + if (props.showWeekNumber && j === 0) continue - cell.inRange = minDate && time >= minDate && time <= maxDate - cell.start = minDate && time === minDate - cell.end = maxDate && time === maxDate - } + const cell = row[j] + const index = i * 7 + j + (props.showWeekNumber ? -1 : 0) + const time = nextDate(startDate, index - state.offsetDay).getTime() + + cell.inRange = minDate && time >= minDate && time <= maxDate + cell.start = minDate && time === minDate + cell.end = maxDate && time === maxDate } } } @@ -468,12 +445,6 @@ const getTarget = (event) => { return target } -export const removeFromArray = (arr, pred) => { - const idx = typeof pred === 'function' ? arrayFindIndex(arr, pred) : arr.indexOf(pred) - - return idx >= 0 ? [...arr.slice(0, idx), ...arr.slice(idx + 1)] : arr -} - export const handleClick = ({ api, emit, props, state }) => (event) => { @@ -529,8 +500,16 @@ export const handleClick = } } -export const getCssToken = ({ api }) => (cell, prexfix = '') => { - const cssStr = api.getCellClasses(cell) || '' +export const removeFromArray = (arr, pred) => { + const idx = typeof pred === 'function' ? arrayFindIndex(arr, pred) : arr.indexOf(pred) - return cssStr.split(' ').map((className) => prexfix + className) + return idx >= 0 ? [...arr.slice(0, idx), ...arr.slice(idx + 1)] : arr } + +export const getCssToken = + ({ api }) => + (cell, prexfix = '') => { + const cssStr = api.getCellClasses(cell) || '' + + return cssStr.split(' ').map((className) => prexfix + className) + } diff --git a/packages/renderless/src/date-table/vue.ts b/packages/renderless/src/date-table/vue.ts index 4d27f50dc..0b8fff133 100644 --- a/packages/renderless/src/date-table/vue.ts +++ b/packages/renderless/src/date-table/vue.ts @@ -42,7 +42,7 @@ const initState = ({ reactive, computed, api, props }) => { month: computed(() => !Array.isArray(props.date) && props.date.getMonth()), offsetDay: computed(() => api.getOffsetDay()), year: computed(() => !Array.isArray(props.date) && props.date.getFullYear()), - startDate: computed(() => getStartDateOfMonth(state.year, state.month)), + startDate: computed(() => getStartDateOfMonth(state.year, state.month, state.offsetDay)), date: props.value }) diff --git a/packages/renderless/src/numeric/index.ts b/packages/renderless/src/numeric/index.ts index 9e1528bbe..aecc9aea0 100644 --- a/packages/renderless/src/numeric/index.ts +++ b/packages/renderless/src/numeric/index.ts @@ -116,7 +116,10 @@ export const increase = return } - const value = (props.mouseWheel ? state.displayValue : Number(state.userInput)) || 0 + // 处理高精度情况 + const userInput = props.stringMode ? state.userInput : Number(state.userInput) + + const value = (props.mouseWheel ? state.displayValue : userInput) || 0 if (value.toString().includes('e')) { return @@ -142,7 +145,11 @@ export const decrease = if (state.inputDisabled || state.minDisabled) { return } - const value = (props.mouseWheel ? state.displayValue : Number(state.userInput)) || 0 + + // 处理高精度情况 + const userInput = props.stringMode ? state.userInput : Number(state.userInput) + + const value = (props.mouseWheel ? state.displayValue : userInput) || 0 if (value.toString().includes('e')) { return diff --git a/packages/renderless/src/picker/index.ts b/packages/renderless/src/picker/index.ts index f355319fc..dbce4779e 100644 --- a/packages/renderless/src/picker/index.ts +++ b/packages/renderless/src/picker/index.ts @@ -16,7 +16,6 @@ import userPopper from '../common/deps/vue-popper' import { DATEPICKER } from '../common' import { formatDate, parseDate, isDateObject, getWeekNumber, prevDate, nextDate } from '../common/deps/date-util' import { extend } from '../common/object' -import { isFunction } from '../common/type' import globalTimezone from './timezone' const iso8601Reg = /^\d{4}-\d{2}-\d{2}(.)\d{2}:\d{2}:\d{2}(.+)$/ @@ -41,8 +40,18 @@ export const getPanel = return DatePanel } +export const watchMobileVisible = + ({ api, props, state }) => + ([dateMobileVisible, timeMobileVisible]) => { + if (dateMobileVisible || timeMobileVisible) { + state.valueOnOpen = Array.isArray(props.modelValue) ? [...props.modelValue] : props.modelValue + } else { + api.emitChange(props.modelValue) + } + } + export const watchPickerVisible = - ({ api, vm, dispatch, emit, props, state }) => + ({ api, vm, dispatch, emit, props, state, nextTick }) => (value) => { if (props.readonly || state.pickerDisabled || state.isMobileScreen) return @@ -52,15 +61,18 @@ export const watchPickerVisible = state.valueOnOpen = Array.isArray(props.modelValue) ? [...props.modelValue] : props.modelValue } else { api.hidePicker() - api.emitChange(props.modelValue) + // tiny 新增: 解决vue3下,modelValue的值仍是旧值,误认为值不变,不触发change事件了。 + nextTick(() => api.emitChange(props.modelValue)) state.userInput = null if (props.validateEvent) { dispatch('FormItem', 'form.blur') } + if (props.changeOnConfirm && !valueEquals(props.modelValue, state.oldValue)) { emit('update:modelValue', state.oldValue) } + emit('blur', vm) api.blur() } @@ -117,14 +129,8 @@ export const displayValue = const formatObj = { rangeSeparator: props.rangeSeparator } - const formattedValue = api.formatAsFormatAndType( - state.parsedValue, - state.format, - state.type, - props.rangeSeparator, - formatObj - ) + const formattedValue = api.formatAsFormatAndType(state.parsedValue, state.format, state.type, formatObj) if (Array.isArray(state.userInput)) { return [ state.userInput[0] || (formattedValue && formattedValue[0]) || '', @@ -176,12 +182,13 @@ export const parsedValue = if (isServiceTimezone) { if (Array.isArray(date)) { - date = [].concat(date).map((item) => (isDate(item) ? formatDate(item, state.valueFormat, t) : item)) + date = [].concat(date).map((item) => { + return isDate(item) ? formatDate(item, state.valueFormat, t) : item + }) } else { date = formatDate(date, state.valueFormat, t) } } - const result = api.parseAsFormatAndType(date, state.valueFormat, state.type, props.rangeSeparator) if (Array.isArray(result)) { return result.map((date) => getDateWithNewTimezone(date, from, to, timezoneOffset)) @@ -194,7 +201,6 @@ export const parsedValue = const values = [] .concat(props.modelValue) .map((val) => getDateWithNewTimezone(trans(val), from, to, timezoneOffset)) - return values.length > 1 ? values : values[0] } @@ -337,7 +343,6 @@ const getWeekOfTypeValueResolveMap = ({ t, props, api }) => ({ trueDate.setHours(0, 0, 0, 0) trueDate.setDate(trueDate.getDate() + 3 - ((trueDate.getDay() + 6) % 7)) } - let date if (type === 'format' && !/W/.test(format)) { const { start, end } = getWeekRange(value, format, t, props.pickerOptions) @@ -346,6 +351,7 @@ const getWeekOfTypeValueResolveMap = ({ t, props, api }) => ({ date = formatDate(trueDate, format, t) date = /WW/.test(date) ? date.replace(/WW/, week < 10 ? '0' + week : week) : date.replace(/W/, week) } + return date }, parser(text, format) { @@ -505,12 +511,211 @@ export const handleMouseEnter = } } +// 这个是 input 组件的 input 事件,应该只有一个 event 参数,input 组件的具体值从 event.target.value 中获取。 +export const handleInput = + ({ state, props, api }) => + (val, event) => { + // 兼容tiny-input传参不同导致的报错问题 + event = val.target ? val : event + if (props.autoFormat) { + const value = api.formatInputValue({ event, prevValue: state.displayValue }) + state.userInput = value + } else { + const val = event.target.value + state.userInput = val + } + } + +export const formatInputValue = + ({ props, state }) => + ({ event, prevValue = '' }) => { + const val = event.target.value + const inputData = event.data + const format = state.type === 'time-select' ? 'HH:mm' : props.format || DATEPICKER.DateFormats[state.type] + if (inputData && inputData.charCodeAt() >= 48 && inputData.charCodeAt() <= 57) { + return formatText({ event, format, text: prevValue, needSelectionStart: true }) + } else { + return val + } + } + +const getSelectionStart = ({ value, format, regx, event }) => { + const formatMatchArr = format.match(regx) + let selectionStart = getSelectionStartIndex(event) + let I = 0 + + if (value !== '') { + const match = value.match(/[0-9]/g) + I = match === null ? 0 : match.length + + for (let i = 0; i < formatMatchArr.length; i++) { + I -= Math.max(formatMatchArr[i].length, 2) + } + + I = I >= 0 ? 1 : 0 + I === 1 && selectionStart >= value.length && (selectionStart = value.length - 1) + } + + return { selectionStart, I } +} + +const getNum = (value, format, regx) => { + let len = value.length + if (format && regx) { + const formatMatchArr = format.match(regx) + len = Math.max(len, formatMatchArr.join('').length) + } + let num = { str: '', arr: [] } + for (let i = 0; i < len; i++) { + let char = value.charAt(i) ? value.charAt(i) : '00' + + if (/[0-9]/.test(char)) { + num.str += char + } else { + num.arr[i] = 1 + } + } + return num +} + +const getSelectionStartIndex = (event) => { + const inputElem = event.target + return inputElem.selectionStart - (event.data ? event.data.length : 0) +} + +const moveStart = (inputElem, moveStartIndex) => { + if (inputElem.setSelectionRange) { + inputElem.focus() + setTimeout(() => { + inputElem.setSelectionRange(moveStartIndex, moveStartIndex) + }, 0) + } +} + +export const formatText = ({ event, text, format, needSelectionStart = false }) => { + if (!format) return text + let cursorOffset = 0 + let value = '' + let regx = /yyyy|yyy|yy|y|MM|M|dd|d|HH|hh|H|h|mm|m|ss|s|WW|W|w/g + let startIndex = 0 + let { numStr, selectionStart } = getNumAndSelectionStart({ + value: text, + format, + regx, + event, + needSelectionStart + }) + + let matchResult = regx.exec(format) + while (numStr.str !== '' && matchResult !== null) { + let subStr + let newNum + let subLen + const endIndex = matchResult.index + if (startIndex >= 0) { + value += format.substring(startIndex, endIndex) + } + + selectionStart >= startIndex + cursorOffset && + selectionStart <= endIndex + cursorOffset && + (selectionStart = selectionStart + endIndex - startIndex) + + startIndex = regx.lastIndex + subLen = startIndex - endIndex + + subStr = numStr.str.substring(0, subLen) + + const firstMatchChar = matchResult[0].charAt(0) + const firstChar = parseInt(subStr.charAt(0), 10) + + if (numStr.str.length > 1) { + const secondChar = numStr.str.charAt(1) + newNum = 10 * firstChar + parseInt(secondChar, 10) + } else { + newNum = firstChar + } + if ( + numStr.arr[endIndex + 1] || + (firstMatchChar === 'M' && newNum > 12) || + (firstMatchChar === 'd' && newNum > 31) || + (['H', 'h'].includes(firstMatchChar) && newNum > 23) || + ('ms'.includes(firstMatchChar) && newNum > 59) + ) { + subStr = matchResult[0].length === 2 ? '0' + firstChar : firstChar + selectionStart++ + } else { + if (subLen === 1) { + subStr = String(newNum) + subLen++ + cursorOffset++ + } + } + + value += subStr + numStr.str = numStr.str.substring(subLen) + matchResult = regx.exec(format) + } + + const { value: val, selectionStart: cursorPos } = checkFormat({ + value, + format, + startIndex, + selectionStart, + regx, + needSelectionStart + }) + value = val + selectionStart = cursorPos + + needSelectionStart && moveStart(event.target, selectionStart) + + return value +} + +const getNumAndSelectionStart = ({ value, format, regx, event, needSelectionStart }) => { + if (needSelectionStart) { + let { selectionStart, I } = getSelectionStart({ value, format, regx, event }) + let valueStr + + if (event.data) { + valueStr = value.substring(0, selectionStart) + event.data + value.substring(selectionStart + I) + selectionStart++ + } else { + valueStr = value + } + + const numStr = getNum(valueStr) + + return { numStr, selectionStart } + } else { + const numStr = getNum(value, format, regx) + return { numStr } + } +} + +const checkFormat = ({ value, format, startIndex, selectionStart, regx, needSelectionStart }) => { + if ( + (!needSelectionStart && regx.lastIndex === 0) || + (needSelectionStart && regx.lastIndex === 0 && selectionStart >= startIndex) + ) { + const subFormat = `(?<=${format.substring(0, startIndex)})(\\s*\\S*\\s*)+` + const pattern = new RegExp(subFormat, 'g') + + const res = format.match(pattern) + + if (res) { + value += res[0] + selectionStart = value.length + } + } + return { value, selectionStart } +} + export const handleChange = ({ api, state }) => () => { if (state.userInput) { const value = api.parseString(state.displayValue) - if (value) { state.picker.state.value = value @@ -534,6 +739,7 @@ export const handleStartInput = const value = props.autoFormat ? api.formatInputValue({ event, prevValue: state.displayValue[0] }) : event.target.value + if (state.userInput) { state.userInput = [value, state.userInput[1]] } else { @@ -754,15 +960,12 @@ export const handleKeydown = } export const hidePicker = - ({ state, doDestroy }) => + ({ destroyPopper, state }) => () => { if (state.picker) { state.picker.resetView && state.picker.resetView() state.pickerVisible = state.picker.visible = state.picker.state.visible = false - - if (isFunction(doDestroy)) { - doDestroy() - } + destroyPopper() } } @@ -780,7 +983,7 @@ export const showPicker = state.pickerVisible = state.picker.state.visible = true state.picker.state.value = state.parsedValue state.picker.resetView && state.picker.resetView() - // 使用nextTick方法解决time-picker组件的demo"下拉框类名"点击input,时间选择框弹出位置错误的问题, + nextTick(() => { updatePopper(state.picker.$el) state.picker.adjustSpinners && state.picker.adjustSpinners() @@ -791,6 +994,7 @@ export const handlePick = ({ state, api }) => (date = '', visible = false) => { if (!state.picker) return + state.userInput = null state.pickerVisible = state.picker.state.visible = visible @@ -806,25 +1010,24 @@ export const handleSelectRange = (state) => (start, end, pos) => { } const adjust = (value, start, end) => { - if (!value) { - return { start, end } - } - const valueReg = /(\d+):(\d+):(\d+)(\s+.+)?/ + if (value) { + const valueReg = /(\d+):(\d+):(\d+)(\s+.+)?/ - if (valueReg.test(value)) { - const matched = valueReg.exec(value) - const hourLength = matched[1].length - const minuteLength = matched[2].length - const secondLength = matched[3].length + if (valueReg.test(value)) { + const matched = valueReg.exec(value) + const hourLength = matched[1].length + const minuteLength = matched[2].length + const secondLength = matched[3].length - if (start === 0) { - end = hourLength - } else if (start === 3) { - start = hourLength + 1 - end = hourLength + minuteLength + 1 - } else { - start = hourLength + minuteLength + 2 - end = hourLength + minuteLength + secondLength + 2 + if (start === 0) { + end = hourLength + } else if (start === 3) { + start = hourLength + 1 + end = hourLength + minuteLength + 1 + } else { + start = hourLength + minuteLength + 2 + end = hourLength + minuteLength + secondLength + 2 + } } } @@ -965,7 +1168,6 @@ export const emitInput = } const formatted = api.formatToValue(value) || val - if (!valueEquals(props.modelValue, formatted)) { emit('update:modelValue', formatted) } @@ -1051,19 +1253,23 @@ export const computedFormat = export const computedTriggerClass = ({ props, state }) => - () => - props.suffixIcon || - props.prefixIcon || - (state.type.includes(DATEPICKER.Time) ? DATEPICKER.IconTime : DATEPICKER.IconDate) + () => { + return ( + props.suffixIcon || + props.prefixIcon || + (state.type.includes(DATEPICKER.Time) ? DATEPICKER.IconTime : DATEPICKER.IconDate) + ) + } export const computedHaveTrigger = ({ props }) => - () => - typeof props.showTrigger !== 'undefined' ? props.showTrigger : DATEPICKER.TriggerTypes.includes(props.type) + () => { + return typeof props.showTrigger !== 'undefined' ? props.showTrigger : DATEPICKER.TriggerTypes.includes(props.type) + } export const initPopper = ({ props, hooks, vnode }) => { const { reactive, watch, toRefs, onBeforeUnmount, onDeactivated } = hooks - // vnode就是第3参,名字有误导性 + // tiny提示: vnode就是第3参,名字有误导性 const { emit, vm, slots, nextTick } = vnode const placementMap = DATEPICKER.PlacementMap @@ -1073,7 +1279,7 @@ export const initPopper = ({ props, hooks, vnode }) => { emit, props: { ...props, - popperOptions: { boundariesPadding: 0, gpuAcceleration: false }, + popperOptions: Object.assign({ boundariesPadding: 0, gpuAcceleration: false }, props.popperOptions), visibleArrow: true, offset: 0, boundariesPadding: 5, @@ -1163,203 +1369,3 @@ export const setInputPaddingLeft = }) } } - -const getSelectionStart = ({ value, format, regx, event }) => { - const formatMatchArr = format.match(regx) - let selectionStart = getSelectionStartIndex(event) - let I = 0 - - if (value !== '') { - const match = value.match(/[0-9]/g) - I = match === null ? 0 : match.length - - for (let i = 0; i < formatMatchArr.length; i++) { - I -= Math.max(formatMatchArr[i].length, 2) - } - - I = I >= 0 ? 1 : 0 - I === 1 && selectionStart >= value.length && (selectionStart = value.length - 1) - } - - return { selectionStart, I } -} - -const getNum = (value, format, regx) => { - let len = value.length - if (format && regx) { - const formatMatchArr = format.match(regx) - len = Math.max(len, formatMatchArr.join('').length) - } - let num = { str: '', arr: [] } - for (let i = 0; i < len; i++) { - let char = value.charAt(i) ? value.charAt(i) : '00' - - if (/[0-9]/.test(char)) { - num.str += char - } else { - num.arr[i] = 1 - } - } - return num -} - -const getSelectionStartIndex = (event) => { - const inputElem = event.target - return inputElem.selectionStart - (event.data ? event.data.length : 0) -} - -const getNumAndSelectionStart = ({ value, format, regx, event, needSelectionStart }) => { - if (needSelectionStart) { - let { selectionStart, I } = getSelectionStart({ value, format, regx, event }) - let valueStr - - if (event.data) { - valueStr = value.substring(0, selectionStart) + event.data + value.substring(selectionStart + I) - selectionStart++ - } else { - valueStr = value - } - - const numStr = getNum(valueStr) - - return { numStr, selectionStart } - } else { - const numStr = getNum(value, format, regx) - return { numStr } - } -} - -const checkFormat = ({ value, format, startIndex, selectionStart, regx, needSelectionStart }) => { - if ( - (!needSelectionStart && regx.lastIndex === 0) || - (needSelectionStart && regx.lastIndex === 0 && selectionStart >= startIndex) - ) { - const subFormat = `(?<=${format.substring(0, startIndex)})(\\s*\\S*\\s*)+` - const pattern = new RegExp(subFormat, 'g') - - const res = format.match(pattern) - - if (res) { - value += res[0] - selectionStart = value.length - } - } - return { value, selectionStart } -} - -const moveStart = (inputElem, moveStartIndex) => { - if (inputElem.setSelectionRange) { - inputElem.focus() - setTimeout(() => { - inputElem.setSelectionRange(moveStartIndex, moveStartIndex) - }, 0) - } -} - -// 这个是 input 组件的 input 事件,应该只有一个 event 参数,input 组件的具体值从 event.target.value 中获取。 -export const handleInput = - ({ state, props, api }) => - (val, event) => { - // 兼容tiny-input传参不同导致的报错问题 - event = val.target ? val : event - if (props.autoFormat) { - const value = api.formatInputValue({ event, prevValue: state.displayValue }) - state.userInput = value - } else { - const val = event.target.value - state.userInput = val - } - } - -export const formatInputValue = - ({ props, state }) => - ({ event, prevValue = '' }) => { - const val = event.target.value - const inputData = event.data - const format = state.type === 'time-select' ? 'HH:mm' : props.format || DATEPICKER.DateFormats[state.type] - if (inputData && inputData.charCodeAt() >= 48 && inputData.charCodeAt() <= 57) { - return formatText({ event, format, text: prevValue, needSelectionStart: true }) - } else { - return val - } - } - -export const formatText = ({ event, text, format, needSelectionStart = false }) => { - if (!format) return text - let cursorOffset = 0 - let value = '' - let regx = /yyyy|yyy|yy|y|MM|M|dd|d|HH|hh|H|h|mm|m|ss|s|WW|W|w/g - let startIndex = 0 - let { numStr, selectionStart } = getNumAndSelectionStart({ - value: text, - format, - regx, - event, - needSelectionStart - }) - - let matchResult = regx.exec(format) - while (numStr.str !== '' && matchResult !== null) { - let subStr - let newNum - let subLen - const endIndex = matchResult.index - if (startIndex >= 0) { - value += format.substring(startIndex, endIndex) - } - - selectionStart >= startIndex + cursorOffset && - selectionStart <= endIndex + cursorOffset && - (selectionStart = selectionStart + endIndex - startIndex) - - startIndex = regx.lastIndex - subLen = startIndex - endIndex - - subStr = numStr.str.substring(0, subLen) - - const firstMatchChar = matchResult[0].charAt(0) - const firstChar = parseInt(subStr.charAt(0), 10) - - if (numStr.str.length > 1) { - const secondChar = numStr.str.charAt(1) - newNum = 10 * firstChar + parseInt(secondChar, 10) - } else { - newNum = firstChar - } - if ( - numStr.arr[endIndex + 1] || - (firstMatchChar === 'M' && newNum > 12) || - (firstMatchChar === 'd' && newNum > 31) || - (['H', 'h'].includes(firstMatchChar) && newNum > 23) || - ('ms'.includes(firstMatchChar) && newNum > 59) - ) { - subStr = matchResult[0].length === 2 ? '0' + firstChar : firstChar - selectionStart++ - } else { - if (subLen === 1) { - subStr = String(newNum) - subLen++ - cursorOffset++ - } - } - - value += subStr - numStr.str = numStr.str.substring(subLen) - matchResult = regx.exec(format) - } - - const { value: val, selectionStart: cursorPos } = checkFormat({ - value, - format, - startIndex, - selectionStart, - regx, - needSelectionStart - }) - value = val - selectionStart = cursorPos - - needSelectionStart && moveStart(event.target, selectionStart) - - return value -} diff --git a/packages/renderless/src/picker/vue.ts b/packages/renderless/src/picker/vue.ts index 402f8e1a7..ef274e9d2 100644 --- a/packages/renderless/src/picker/vue.ts +++ b/packages/renderless/src/picker/vue.ts @@ -16,6 +16,7 @@ import { watchIsRange, parseAsFormatAndType, watchPickerVisible, + watchMobileVisible, getValueEmpty, getMode, displayValue, @@ -177,7 +178,7 @@ const initState = ({ api, reactive, vm, computed, props, utils, parent, breakpoi const initApi = ({ api, props, hooks, state, vnode, others, utils, parent }) => { const { t, emit, dispatch, nextTick, vm } = vnode const { TimePanel, TimeRangePanel } = others - const { destroyPopper, popperElm, updatePopper, doDestroy } = initPopper({ props, hooks, vnode }) + const { destroyPopper, popperElm, updatePopper } = initPopper({ props, hooks, vnode }) state.popperElm = popperElm state.picker = null @@ -185,7 +186,7 @@ const initApi = ({ api, props, hooks, state, vnode, others, utils, parent }) => Object.assign(api, { destroyPopper, emitDbTime: emitDbTime({ emit, state, t }), - hidePicker: hidePicker({ state, doDestroy }), + hidePicker: hidePicker({ destroyPopper, state }), handleSelectChange: ({ tz, date }) => !state.ranged && emit('select-change', { tz, date }), getPanel: getPanel(others), handleFocus: handleFocus({ emit, vm, state, api }), @@ -211,7 +212,8 @@ const initApi = ({ api, props, hooks, state, vnode, others, utils, parent }) => handleClose: handleClose({ api, props, state }), displayValue: displayValue({ api, props, state }), handlePick: handlePick({ api, state }), - watchPickerVisible: watchPickerVisible({ api, vm, dispatch, emit, props, state }), + watchPickerVisible: watchPickerVisible({ api, vm, dispatch, emit, props, state, nextTick }), + watchMobileVisible: watchMobileVisible({ api, props, state }), formatToString: formatToString({ api, state }), watchIsRange: watchIsRange({ api, state, TimePanel, TimeRangePanel }), mountPicker: mountPicker({ api, vm, props, state, updatePopper }), @@ -226,6 +228,7 @@ const initApi = ({ api, props, hooks, state, vnode, others, utils, parent }) => initApi2({ api, props, state, t, parent }) initMobileApi({ api, props, state, t, parent }) } + const initApi2 = ({ api, props, state, t, parent }) => { Object.assign(api, { t, @@ -268,6 +271,8 @@ const initWatch = ({ api, state, props, watch, markRaw }) => { { immediate: true } ) + watch(() => [state.dateMobileOption.visible, state.timeMobileOption.visible], api.watchMobileVisible) + watch(() => state.pickerVisible, api.watchPickerVisible) watch( diff --git a/packages/renderless/src/popeditor/index.ts b/packages/renderless/src/popeditor/index.ts index dbb6c51d5..5ec78cfe7 100644 --- a/packages/renderless/src/popeditor/index.ts +++ b/packages/renderless/src/popeditor/index.ts @@ -813,7 +813,7 @@ export const doSuggesst = api.sourceGridSelectChange({ checked: false, row, confirm: false }) }) - if (addtions.length) { + if (!state.suggestList.length || addtions.length) { doQuery(query) } } else { @@ -829,7 +829,7 @@ export const closeSuggestPanel = const popper = vm.$refs.popper let keep = !event - if (event.target) { + if (event.target && reference) { keep = reference.$el.contains(event.target) || popper.contains(event.target) } diff --git a/packages/renderless/src/select/index.ts b/packages/renderless/src/select/index.ts index 5b82dcafc..c5b90c4b2 100644 --- a/packages/renderless/src/select/index.ts +++ b/packages/renderless/src/select/index.ts @@ -494,38 +494,14 @@ export const getPluginOption = return items } -// tiny 修改: aui的 toggleCheckAll 在designConfig, 同步时要更新 export const toggleCheckAll = ({ api, state, props }) => (filtered) => { - // tiny 移入内部 - const getEnabledValues = (options) => { - let values = [] - - // tiny 新增 避免全不选时,将disabled的项目勾掉 - for (let i = 0; i < options.length; i++) { - const isEnabled = !options[i].state.disabled && !options[i].state.groupDisabled - const isRequired = options[i].required - const isDisabledAndChecked = !isEnabled && options[i].state.selectCls === 'checked-sur' - - if (state.isSelectAll) { - // 取消选中全部,必填和禁用且选中项不可取消 - if (isRequired || isDisabledAndChecked) { - values.push(options[i].value) - } - } else { - // 选中全部,非禁用项 和 必填项和 禁用且选中项 需选中 - if (isEnabled || isRequired || isDisabledAndChecked) { - values.push(options[i].value) - } - } - } - - return values - } - - let value - const enabledValues = getEnabledValues(state.options) + let value = [] + // 1. 需要控制勾选或去勾选的项 + const enabledValues = state.options + .filter((op) => !op.state.disabled && !op.state.groupDisabled && !op.required && op.state.visible) + .map((op) => op.value) if (filtered) { if (state.filteredSelectCls === 'check' || state.filteredSelectCls === 'halfselect') { @@ -541,10 +517,18 @@ export const toggleCheckAll = unchecked.length ? (value = enabledValues) : (value = []) } else if (state.selectCls === 'checked-sur') { - // tiny 新增 - value = getEnabledValues(state.options) + value = [] } } + // 2. 必选项 + const requiredValue = state.options.filter((op) => op.required).map((op) => op.value) + + // 3. 禁用且已设置为勾选的项 + const disabledSelectedValues = state.options + .filter((op) => (op.state.disabled || op.state.groupDisabled) && op.state.selectCls === 'checked-sur') + .map((op) => op.value) + + value = [...value, ...requiredValue, ...disabledSelectedValues] api.setSoftFocus() @@ -1278,10 +1262,9 @@ export const watchValue = } if (props.filterable && !props.reserveKeyword) { - const isChange = false - const isInput = true - props.renderType !== constants.TYPE.Grid && (state.query = '') - api.handleQueryChange(state.query, isChange, isInput) + // tiny 优化: 多选且props.reserveKeyword为false时, aui此处会多请求一次 + // searchable时,不清空query, 这样才能保持搜索结果 + props.renderType !== constants.TYPE.Grid && !props.searchable && (state.query = '') } } @@ -1549,7 +1532,7 @@ export const queryVisibleOptions = if (props.optimization) { return optmzApis.queryVisibleOptions(vm, isMobileFirstMode) } else { - return Array.from(vm.$refs.scrollbar.$el.querySelectorAll('[data-index]:not([style*="display: none"])')) + return Array.from(vm.$refs.scrollbar?.$el.querySelectorAll('[data-index]:not([style*="display: none"])') || []) } } @@ -1839,11 +1822,16 @@ export const watchHoverIndex = } export const handleDropdownClick = - ({ emit }) => + ({ vm, state, props, emit }) => ($event) => { + if (props.allowCopy && vm.$refs.reference) { + vm.$refs.reference.$el.querySelector('input').selectionEnd = 0 + } + + state.softFocus = false + emit('dropdown-click', $event) } - export const handleEnterTag = ({ state }) => ($event, key) => { diff --git a/packages/renderless/src/select/vue.ts b/packages/renderless/src/select/vue.ts index 58e5c7a51..28237caa4 100644 --- a/packages/renderless/src/select/vue.ts +++ b/packages/renderless/src/select/vue.ts @@ -169,7 +169,7 @@ export const api = [ 'clearSearchText' ] -const initState = ({ reactive, computed, props, api, emitter, parent, constants, useBreakpoint, vm }) => { +const initState = ({ reactive, computed, props, api, emitter, parent, constants, useBreakpoint, vm, designConfig }) => { const stateAdd = initStateAdd({ computed, props, api, parent }) const state = reactive({ ...stateAdd, @@ -226,7 +226,13 @@ const initState = ({ reactive, computed, props, api, emitter, parent, constants, // tiny 新增 getIcon: computed(() => api.computedGetIcon()), getTagType: computed(() => api.computedGetTagType()), - isSelectAll: computed(() => state.selectCls === 'checked-sur') + isSelectAll: computed(() => state.selectCls === 'checked-sur'), + autoHideDownIcon: (() => { + if (designConfig?.state && 'autoHideDownIcon' in designConfig.state) { + return designConfig.state.autoHideDownIcon + } + return true // tiny 默认为true + })() }) return state @@ -431,7 +437,7 @@ const addApi = ({ mounted: mounted({ api, parent, state, props, vm, designConfig }), unMount: unMount({ api, parent, vm, state }), watchOptimizeOpts: watchOptimizeOpts({ props, state }), - handleDropdownClick: handleDropdownClick({ emit }), + handleDropdownClick: handleDropdownClick({ props, vm, state, emit }), handleEnterTag: handleEnterTag({ state }), calcCollapseTags: calcCollapseTags({ state, vm }), initValue: initValue({ state }), @@ -555,8 +561,19 @@ export const renderless = ( { computed, onBeforeUnmount, onMounted, reactive, watch, provide, inject }, { vm, parent, emit, constants, nextTick, dispatch, t, emitter, isMobileFirstMode, useBreakpoint, designConfig } ) => { - const api = {} - const state = initState({ reactive, computed, props, api, emitter, parent, constants, useBreakpoint, vm }) + const api: any = {} + const state = initState({ + reactive, + computed, + props, + api, + emitter, + parent, + constants, + useBreakpoint, + vm, + designConfig + }) const dialog = inject('dialog', null) provide('selectEmitter', state.selectEmitter) @@ -579,9 +596,7 @@ export const renderless = ( isMobileFirstMode, designConfig }) - initWatch({ watch, props, api, state, nextTick }) - onMounted(api.mounted) - onBeforeUnmount(api.unMount) + parent.$on('handle-clear', (event) => { api.handleClearClick(event) }) @@ -599,5 +614,14 @@ export const renderless = ( state.selectEmitter.on(constants.EVENT_NAME.setSelected, api.setSelected) state.selectEmitter.on(constants.EVENT_NAME.initValue, api.initValue) + initWatch({ watch, props, api, state, nextTick }) + + onMounted(api.mounted) + + onBeforeUnmount(() => { + api.unMount() + dialog && dialog.state.emitter.off('handleSelectClose', api.handleClose) + }) + return api } diff --git a/packages/renderless/src/tree-node/index.ts b/packages/renderless/src/tree-node/index.ts index 43c8a47b1..355e602f9 100644 --- a/packages/renderless/src/tree-node/index.ts +++ b/packages/renderless/src/tree-node/index.ts @@ -349,6 +349,7 @@ export const addNode = state.tree.state.emitter.emit('tree-node-add', event, node) } +// tiny 新增 export const computedExpandIcon = ({ designConfig }) => (treeRoot, state) => { @@ -356,6 +357,7 @@ export const computedExpandIcon = return state.tree.icon } + // tiny 新增的判断。 显示线时强制切换图标,仅smb定制了 if (treeRoot.showLine) { const expandIcon = designConfig?.icons?.expanded || 'icon-minus-square' const collapseIcon = designConfig?.icons?.collapse || 'icon-plus-square' @@ -364,7 +366,7 @@ export const computedExpandIcon = return 'icon-chevron-right' } - +// tiny 新增 export const computedIndent = () => ({ node, showLine }, { tree }) => { diff --git a/packages/renderless/src/tree-node/vue.ts b/packages/renderless/src/tree-node/vue.ts index 17d553964..681c39690 100644 --- a/packages/renderless/src/tree-node/vue.ts +++ b/packages/renderless/src/tree-node/vue.ts @@ -36,6 +36,7 @@ import { deleteNode, onSiblingToggleExpand, watchExpandedChange, + // tiny 新增 computedExpandIcon, computedIndent } from './index' diff --git a/packages/renderless/src/user/index.ts b/packages/renderless/src/user/index.ts index 1749bd942..6710e154a 100644 --- a/packages/renderless/src/user/index.ts +++ b/packages/renderless/src/user/index.ts @@ -250,7 +250,7 @@ export const updateOptions = export const autoSelect = ({ props, state, nextTick }) => (usersList) => { - if (!usersList.length) { + if (!usersList.length || (props.multiple && props.multipleLimit && state.user.length >= props.multipleLimit)) { return nextTick() } diff --git a/packages/theme-saas/src/drawer/index.less b/packages/theme-saas/src/drawer/index.less index 01b4d8721..5c9156f1a 100644 --- a/packages/theme-saas/src/drawer/index.less +++ b/packages/theme-saas/src/drawer/index.less @@ -138,11 +138,15 @@ @apply text-base; @apply items-center; - .@{drawer-prefix-cls}__title { + .@{drawer-prefix-cls}__header-left { @apply ~'max-w-[80%]'; - @apply pr-4; - @apply text-left; - @apply truncate; + + // 标题增加帮助提示, 勿覆盖 + .@{drawer-prefix-cls}__title { + @apply pr-4; + @apply text-left; + @apply truncate; + } } .@{drawer-prefix-cls}__header-right { diff --git a/packages/theme-saas/theme/theme.json b/packages/theme-saas/theme/theme.json index 517e447d8..1d4cf1324 100644 --- a/packages/theme-saas/theme/theme.json +++ b/packages/theme-saas/theme/theme.json @@ -1,6 +1,6 @@ { "version": "1.0.0", - "themeName": "华为SaaS设计系统主题", + "themeName": "SaaS设计系统主题", "themeColor": [ { "mode": "light", diff --git a/packages/theme/src/drawer/index.less b/packages/theme/src/drawer/index.less index 01a8018bf..fd3e2c38f 100644 --- a/packages/theme/src/drawer/index.less +++ b/packages/theme/src/drawer/index.less @@ -150,7 +150,6 @@ border-bottom: 1px solid var(--ti-drawer-divider-border-color); .@{drawer-prefix-cls}__title { - max-width: 80%; text-align: left; overflow: hidden; text-overflow: ellipsis; @@ -161,7 +160,7 @@ } .@{drawer-prefix-cls}__header-left { - flex: 1; + max-width: 80%; display: flex; align-items: center; padding-right: 16px; diff --git a/packages/theme/src/tree/index.less b/packages/theme/src/tree/index.less index 2074d239e..f334bf80a 100644 --- a/packages/theme/src/tree/index.less +++ b/packages/theme/src/tree/index.less @@ -425,6 +425,10 @@ transform: rotate(0); } } + + &.is-leaf { + visibility: hidden; + } } &__label { diff --git a/packages/vue/src/button/src/mobile-first.vue b/packages/vue/src/button/src/mobile-first.vue index 1035f7d84..1916688ca 100644 --- a/packages/vue/src/button/src/mobile-first.vue +++ b/packages/vue/src/button/src/mobile-first.vue @@ -23,7 +23,7 @@ ) " :tabindex="tabindex" - v-bind="a($attrs, ['class', 'style'], true)" + v-bind="a($attrs, ['class', 'style', 'id'], true)" > diff --git a/packages/vue/src/checkbox/src/pc.vue b/packages/vue/src/checkbox/src/pc.vue index ab2c2b0fe..a84e38a55 100644 --- a/packages/vue/src/checkbox/src/pc.vue +++ b/packages/vue/src/checkbox/src/pc.vue @@ -100,7 +100,8 @@ import type { ICheckboxApi } from '@opentiny/vue-renderless/types/checkbox.type' import Tooltip from '@opentiny/vue-tooltip' export default defineComponent({ - emits: ['update:modelValue', 'change', 'complete', 'click'], + // tiny 新增。 renderless中,没有emit('click')的地方。 此处勿声明,否则会造成丢失click事件。 + emits: ['update:modelValue', 'change', 'complete'], props: [ ...props, 'modelValue', diff --git a/packages/vue/src/grid/src/cell/src/cell.ts b/packages/vue/src/grid/src/cell/src/cell.ts index 68b96dc53..5d6e77cc6 100644 --- a/packages/vue/src/grid/src/cell/src/cell.ts +++ b/packages/vue/src/grid/src/cell/src/cell.ts @@ -773,7 +773,7 @@ export const Cell = { renderEditHeader(h, params) { let { $table, column } = params let { editConfig, editRules, validOpts } = $table - let { filter, remoteSort, sortable, type } = column + let { filter, remoteSort, sortable, type, own } = column let icon = GLOBAL_CONFIG.icon let isRequired @@ -799,7 +799,7 @@ export const Cell = { let vNodes = [ isRequired && showAsterisk ? h('i', { class: `tiny-icon ${icon.required}` }) : null, - !editConfig || !column.showIcon ? null : h(icon.edit, { class: 'tiny-grid-edit-icon tiny-svg-size' }) + !editConfig || !own.showIcon ? null : h(icon.edit, { class: 'tiny-grid-edit-icon tiny-svg-size' }) ] vNodes = vNodes.concat(Cell.renderHeader(h, params)) diff --git a/packages/vue/src/grid/src/dragger/src/rowDrop.ts b/packages/vue/src/grid/src/dragger/src/rowDrop.ts index 3deacc1e3..3f160528b 100644 --- a/packages/vue/src/grid/src/dragger/src/rowDrop.ts +++ b/packages/vue/src/grid/src/dragger/src/rowDrop.ts @@ -57,7 +57,7 @@ export const createHandlerOnEnd = ({ _vm, refresh }) => { if (insertRecords.length) { return false } - const options = { children: 'children' } + const options = { children: (_vm.treeConfig || {}).children || 'children' } const targetTrElem = event.item const { parentNode: wrapperElem, previousElementSibling: prevTrElem } = targetTrElem // 这里优先使用用户通过props传递过来的表格数据,所以拖拽后会改变原始数据 diff --git a/packages/vue/src/grid/src/footer/src/footer.ts b/packages/vue/src/grid/src/footer/src/footer.ts index d44bef3e5..f168f5ed4 100644 --- a/packages/vue/src/grid/src/footer/src/footer.ts +++ b/packages/vue/src/grid/src/footer/src/footer.ts @@ -176,6 +176,7 @@ export default { name: `${$prefix}GridFooter`, props: { fixedColumn: Array, + fixedType: String, footerData: Array, size: String, tableColumn: Array, @@ -193,7 +194,7 @@ export default { elemStore[`${keyPrefix}x-space`] = $refs.xSpace }, render() { - let { $parent: $table, buildParamFunc, footerData, tableColumn } = this + let { $parent: $table, buildParamFunc, fixedColumn, fixedType, footerData, tableColumn } = this let { align: allAlign, columnKey, @@ -203,7 +204,7 @@ export default { footerSpanMethod, columnStore } = $table - let { overflowX, showOverflow: allColumnOverflow, tableLayout, tableListeners } = $table + let { overflowX, showOverflow: allColumnOverflow, tableLayout, tableListeners, renderFooter } = $table let tableAttrs = { cellspacing: 0, cellpadding: 0, border: 0 } let colgroupVNode = renderColgroup(tableColumn) @@ -219,6 +220,8 @@ export default { } let tfootVNode = renderTfoot(Object.assign(arg1, arg2)) + const renderParams = { $table, columns: tableColumn, footerData, fixedColumns: fixedColumn, fixedType } + return h( 'div', { @@ -227,21 +230,23 @@ export default { }, [ h('div', { class: 'tiny-grid-body__x-space', ref: 'xSpace' }), - h( - 'table', - { - class: 'tiny-grid__footer', - style: { tableLayout }, - attrs: tableAttrs, - ref: 'table' - }, - [ - // 列宽 - colgroupVNode, - // 底部 - tfootVNode - ] - ) + typeof renderFooter === 'function' + ? renderFooter(renderParams, h) + : h( + 'table', + { + class: 'tiny-grid__footer', + style: { tableLayout }, + attrs: tableAttrs, + ref: 'table' + }, + [ + // 列宽 + colgroupVNode, + // 底部 + tfootVNode + ] + ) ] ) }, diff --git a/packages/vue/src/grid/src/resize/src/methods.ts b/packages/vue/src/grid/src/resize/src/methods.ts index f43e646f3..93913e3c1 100644 --- a/packages/vue/src/grid/src/resize/src/methods.ts +++ b/packages/vue/src/grid/src/resize/src/methods.ts @@ -33,7 +33,9 @@ export default { this.recalculate() }, GlobalConfig.resizeInterval) - resizeObserver.observe(this.getParentElem()) + const parentElem = this.getParentElem() + + parentElem && resizeObserver.observe(parentElem) this.$resize = resizeObserver }, unbindResize() { diff --git a/packages/vue/src/grid/src/table/src/strategy.ts b/packages/vue/src/grid/src/table/src/strategy.ts index 62f4c6c4c..0bc1e184b 100644 --- a/packages/vue/src/grid/src/table/src/strategy.ts +++ b/packages/vue/src/grid/src/table/src/strategy.ts @@ -141,7 +141,7 @@ const setTotalRows = (_vm) => { } const getTotalRows = (_vm) => { - const { afterFullData, scrollYLoad, treeConfig } = _vm + const { afterFullData, scrollYLoad, scrollLoad, treeConfig } = _vm let totalRows = afterFullData.length if (scrollYLoad && treeConfig) { @@ -152,6 +152,11 @@ const getTotalRows = (_vm) => { totalRows = TOTALROWS_MAP.get(_vm) } + // 滚动分页场景总行数由(afterFullData.length)调整为(scrollLoad.pageSize),解决最后一页数据不足时滚动条位置改变问题 + if (scrollLoad) { + totalRows = scrollLoad.pageSize || 10 + } + return totalRows } diff --git a/packages/vue/src/rich-text/index.ts b/packages/vue/src/rich-text/index.ts new file mode 100644 index 000000000..75a7b4a5b --- /dev/null +++ b/packages/vue/src/rich-text/index.ts @@ -0,0 +1,23 @@ +import RichText from './src/pc.vue' +import '@opentiny/vue-theme/rich-text/index.css' + +RichText.model = { + prop: 'modelValue', + event: 'update:modelValue' +} + +/* istanbul ignore next */ +RichText.install = function (Vue) { + Vue.component(RichText.name, RichText) +} + +RichText.version = process.env.COMPONENT_VERSION + +/* istanbul ignore next */ +if (process.env.BUILD_TARGET === 'runtime') { + if (typeof window !== 'undefined' && window.Vue) { + RichText.install(window.Vue) + } +} + +export default RichText diff --git a/packages/vue/src/rich-text/package.json b/packages/vue/src/rich-text/package.json new file mode 100644 index 000000000..6bce44639 --- /dev/null +++ b/packages/vue/src/rich-text/package.json @@ -0,0 +1,24 @@ +{ + "name": "@opentiny/vue-rich-text", + "version": "3.14.0", + "description": "", + "main": "lib/index.js", + "module": "index.ts", + "sideEffects": false, + "type": "module", + "devDependencies": { + "@opentiny-internal/vue-test-utils": "workspace:*", + "vitest": "^0.31.0" + }, + "scripts": { + "build": "pnpm -w build:ui $npm_package_name", + "//postversion": "pnpm build" + }, + "dependencies": { + "@opentiny/vue-icon": "workspace:~", + "@opentiny/vue-common": "workspace:~", + "@opentiny/vue-locale": "workspace:~", + "quill": "^1.3.7" + }, + "license": "MIT" +} \ No newline at end of file diff --git a/packages/vue/src/rich-text/src/pc.vue b/packages/vue/src/rich-text/src/pc.vue new file mode 100644 index 000000000..42df9fb20 --- /dev/null +++ b/packages/vue/src/rich-text/src/pc.vue @@ -0,0 +1,87 @@ + + + diff --git a/packages/vue/src/select/src/pc.vue b/packages/vue/src/select/src/pc.vue index 436ac2f3f..a4977e007 100644 --- a/packages/vue/src/select/src/pc.vue +++ b/packages/vue/src/select/src/pc.vue @@ -190,10 +190,10 @@ - + - + 0 && !loading" > -
  • {{ t('ui.base.all') }} --> - + @@ -460,8 +462,7 @@ !state.multipleLimit && state.query && !state.emptyText && - !remote && - !filterable + !remote " class="tiny-option tiny-select-dropdown__item" data-tag="tiny-select-dropdown-item" @@ -477,9 +478,9 @@ > - + diff --git a/packages/vue/src/tabs/src/index.ts b/packages/vue/src/tabs/src/index.ts index b8de98950..423d239cf 100644 --- a/packages/vue/src/tabs/src/index.ts +++ b/packages/vue/src/tabs/src/index.ts @@ -70,6 +70,7 @@ export const tabsProps = { export default defineComponent({ name: $prefix + 'Tabs', + emits: ['tab-nav-update'], props: tabsProps, setup(props, context) { return $setup({ props, context, template }) diff --git a/packages/vue/src/tree/src/tree-node.vue b/packages/vue/src/tree/src/tree-node.vue index 5b7345dfd..6dd07c501 100644 --- a/packages/vue/src/tree/src/tree-node.vue +++ b/packages/vue/src/tree/src/tree-node.vue @@ -67,7 +67,6 @@