Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
109 changes: 109 additions & 0 deletions packages/design/aurora/src/select-wrapper/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
import { iconChevronDown } from '@opentiny/vue-icon'

export default {
// 虚拟滚动的默认options不一致
baseOpts: { optionHeight: 34, limit: 20 },
icons: {
dropdownIcon: iconChevronDown()
},
state: {
sizeMap: {
default: 30,
mini: 24,
small: 36,
medium: 42
},
spacingHeight: 2,
initialInputHeight: 30,
// 显示清除等图标时,不隐藏下拉箭头时
autoHideDownIcon: false,
delayBlur: true
},
props: {
tagType: 'info',
stopPropagation: true
},
renderless: (props, hooks, { emit }, api) => {
const state = api.state

return {
// 兼容不同主题输入框尺寸对应标签尺寸不一致
computedCollapseTagSize: () => {
let size = 'small'

if (~['small', 'mini'].indexOf(state.selectSize)) {
size = state.selectSize
} else if (~['medium', 'default'].indexOf(state.selectSize)) {
size = 'small'
}

return size
},
// 兼容显示清除图标时,是否同时显示下拉图标
computedShowDropdownIcon: () => {
return !(props.remote && props.filterable && !props.remoteConfig.showIcon)
},

// aui 的勾选未处理disabled的选项,故此放这里。
toggleCheckAll: (filtered) => {
const getEnabledValues = (options) => {
let values = []

for (let i = 0; i < options.length; i++) {
if (!options[i].state.disabled && !options[i].state.groupDisabled && options[i].state.visible) {
values.push(options[i].value)
}
}

return values
}

let value
const enabledValues = getEnabledValues(state.options)

if (filtered) {
if (state.filteredSelectCls === 'check' || state.filteredSelectCls === 'halfselect') {
value = Array.from(new Set([...state.modelValue, ...enabledValues]))
} else {
value = state.modelValue.filter((val) => !enabledValues.includes(val))
}
} else {
if (state.selectCls === 'check') {
value = enabledValues
} else if (state.selectCls === 'halfselect') {
const unchecked = state.options.filter((item) => !item.state.disabled && item.state.selectCls === 'check')

unchecked.length ? (value = enabledValues) : (value = [])
} else if (state.selectCls === 'checked-sur') {
value = []
}
}

const requiredValue = []
if (props.multiple) {
state.options.forEach((opt) => {
if (opt.required) requiredValue.push(opt.value)
})
}

if (Array.isArray(value)) {
value = requiredValue.concat(value.filter((val) => !requiredValue.find((requireVal) => requireVal === val)))
}

api.setSoftFocus()

state.isSilentBlur = true
api.updateModelValue(value)
api.directEmitChange(value)
},
// aurora 禁用和只展示的时候都是tagText,默认主题是 isDisplayOnly 才显示tagText
computedShowTagText: () => {
return state.isDisabled || state.isDisplayOnly
},
// aurora 禁用已选项无效果,必选不显示关闭图标
isTagClosable: (item) => {
return !item.required
}
}
}
}
3 changes: 3 additions & 0 deletions packages/design/saas/src/select-wrapper/icon-loading.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
106 changes: 106 additions & 0 deletions packages/design/saas/src/select-wrapper/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
import { iconChevronDown, iconPlus } from '@opentiny/vue-icon'
import loadingIcon from './icon-loading.svg'

export default {
// 虚拟滚动的默认options不一致
baseOpts: { optionHeight: 34, limit: 20 },
icons: {
dropdownIcon: iconChevronDown(),
addIcon: iconPlus(),
loadingIcon
},
state: {
sizeMap: {
default: 28,
mini: 24,
small: 28,
medium: 32
},
spacingHeight: 4,
initialInputHeight: 28,
// 显示清除等图标时,不隐藏下拉箭头时
autoHideDownIcon: false,
delayBlur: true
},
props: {
tagType: 'info',
stopPropagation: true
},
renderless: (props, hooks, { emit }, api) => {
const state = api.state

return {
computedCollapseTagSize: () => {
let size = 'small'

if (~['small', 'mini'].indexOf(state.selectSize)) {
size = state.selectSize
} else if (~['medium', 'default'].indexOf(state.selectSize)) {
size = 'small'
}

return size
},
// aui 的勾选未处理disabled的选项,故此放这里。
toggleCheckAll: (filtered) => {
const getEnabledValues = (options) => {
let values = []

for (let i = 0; i < options.length; i++) {
if (!options[i].state.disabled && !options[i].state.groupDisabled && options[i].state.visible) {
values.push(options[i].value)
}
}

return values
}

let value
const enabledValues = getEnabledValues(state.options)

if (filtered) {
if (state.filteredSelectCls === 'check' || state.filteredSelectCls === 'halfselect') {
value = Array.from(new Set([...state.modelValue, ...enabledValues]))
} else {
value = state.modelValue.filter((val) => !enabledValues.includes(val))
}
} else {
if (state.selectCls === 'check') {
value = enabledValues
} else if (state.selectCls === 'halfselect') {
const unchecked = state.options.filter((item) => !item.state.disabled && item.state.selectCls === 'check')

unchecked.length ? (value = enabledValues) : (value = [])
} else if (state.selectCls === 'checked-sur') {
value = []
}
}

const requiredValue = []
if (props.multiple) {
state.options.forEach((opt) => {
if (opt.required) requiredValue.push(opt.value)
})
}

if (Array.isArray(value)) {
value = requiredValue.concat(value.filter((val) => !requiredValue.find((requireVal) => requireVal === val)))
}

api.setSoftFocus()

state.isSilentBlur = true
api.updateModelValue(value)
api.directEmitChange(value)
},
// aurora 禁用和只展示的时候都是tagText,默认主题是 isDisplayOnly 才显示tagText
computedShowTagText: () => {
return state.isDisabled || state.isDisplayOnly
},
// aurora 禁用已选项无效果,必选不显示关闭图标
isTagClosable: (item) => {
return !item.required
}
}
}
}
Comment on lines +1 to +106
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion | 🟠 Major

Code duplication detected between Aurora and Saas implementations.

This file is nearly identical to packages/design/aurora/src/select-wrapper/index.ts, with only minor differences in state.sizeMap values and additional icons. The toggleCheckAll function (lines 45-95) is duplicated verbatim.

Consider refactoring to reduce duplication:

  1. Extract common logic into a shared base module
  2. Use theme-specific overrides for differences (sizeMap, icons)
  3. Move the toggleCheckAll implementation to the shared renderless layer

Example structure:

// packages/design/common/select-wrapper-base.ts
export const createSelectWrapperConfig = (themeConfig) => ({
  baseOpts: themeConfig.baseOpts,
  icons: themeConfig.icons,
  state: themeConfig.state,
  props: themeConfig.props,
  renderless: createRenderless(themeConfig)
})

Then each theme file would only define its specific configuration values.

3 changes: 3 additions & 0 deletions packages/renderless/src/base-select/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1249,6 +1249,9 @@ const postOperOfToVisible = ({ props, state, constants, vm }) => {
if (props.modelValue && props.initLabel && !state.selectedLabel) {
state.selectedLabel = props.initLabel
}
} else if (props.modelValue && props.initLabel) {
// 如果 state.selected 不存在,但有 modelValue 和 initLabel,则使用 initLabel
state.selectedLabel = props.initLabel
}
}

Expand Down
4 changes: 2 additions & 2 deletions packages/renderless/src/select-wrapper/vue.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,11 @@ export const renderless = (props, { reactive, computed, useAttrs }, { constants,
: ['tiny-select']

const { class: _omitClass, ...restAttrs } = attrs

return {
...props,
...restAttrs,
class: classArray
class: Array.from(new Set(classArray)),
dataTag: 'tiny-select'
}
})

Expand Down
7 changes: 6 additions & 1 deletion packages/vue/src/base-select/src/mobile-first.vue
Original file line number Diff line number Diff line change
Expand Up @@ -302,6 +302,8 @@
:display-only-content="state.displayOnlyContent"
:unselectable="state.readonly ? 'on' : 'off'"
:validate-event="false"
:show-empty-value="showEmptyValue"
:input-box-type="inputBoxType"
:tabindex="multiple && filterable ? '-1' : tabindex"
@focus="handleFocus"
@blur="handleBlur"
Expand Down Expand Up @@ -758,7 +760,10 @@ export default defineComponent({
'showAllTextTag',
'hoverExpand',
'clickExpand',
'maxVisibleRows'
'maxVisibleRows',
'initLabel',
'inputBoxType',
'showEmptyValue'
],
setup(props, context) {
return setup({ props, context, renderless, api, classes })
Expand Down
4 changes: 2 additions & 2 deletions packages/vue/src/grid-select/src/mobile-first.vue
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,8 @@
ref="gridRef"
auto-resize
:row-id="valueField"
:select-config="state.selectConfig"
:radio-config="state.radioConfig"
:select-config="buildSelectConfig()"
:radio-config="buildRadioConfig()"
:highlight-current-row="true"
:columns="gridOp?.columns || []"
:data="Array.isArray(state.gridData) ? state.gridData : state.gridData?.data || state.gridData || []"
Expand Down
19 changes: 11 additions & 8 deletions packages/vue/src/input/src/mobile-first.vue
Original file line number Diff line number Diff line change
Expand Up @@ -94,13 +94,15 @@
? 'h-6 leading-6 text-xs placeholder:text-xs'
: 'h-7 leading-7',
slots.prepend || slots.append ? 'align-middle table-cell' : 'inline-block',
slots.prepend && slots.append
? 'rounded-none'
: slots.prepend
? 'rounded-tl-none rounded-bl-none rounded-tr rounded-br'
: slots.append
? 'rounded-tl rounded-bl rounded-tr-none rounded-br-none'
: 'rounded',
inputBoxType === 'underline'
? 'rounded-none border-t-0 border-l-0 border-r-0 border-b sm:border-b'
: slots.prepend && slots.append
? 'rounded-none'
: slots.prepend
? 'rounded-tl-none rounded-bl-none rounded-tr rounded-br'
: slots.append
? 'rounded-tl rounded-bl rounded-tr-none rounded-br-none'
: 'rounded',
readonly ? ' text-ellipsis overflow-hidden whitespace-nowrap' : 'sm:border',
(slots.prefix || prefixIcon) && (slots.suffix || suffixIcon || clearable || showPassword)
? 'px-6 sm:px-6'
Expand Down Expand Up @@ -427,7 +429,8 @@ export default defineComponent({
'popupMore',
'showTooltip',
'frontClearIcon',
'hoverExpand'
'hoverExpand',
'inputBoxType'
],
setup(props, context): any {
return setup({ props, context, renderless, api })
Expand Down
6 changes: 3 additions & 3 deletions packages/vue/src/select-wrapper/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@

import { $props, $prefix, $setup, defineComponent } from '@opentiny/vue-common'
import { t } from '@opentiny/vue-locale'
import template from 'virtual-template?pc'
import template from 'virtual-template?pc|mobile-first'

const $constants = {
CLASS: {
Expand Down Expand Up @@ -326,9 +326,9 @@ export default defineComponent({
type: Boolean,
default: false
},
InputBoxType: {
inputBoxType: {
type: String,
default: 'input',
default: 'normal',
validator: (value: string) => ['input', 'underline'].includes(value)
},
Comment on lines +329 to 333
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Critical: Default value fails prop validation.

The inputBoxType prop has a default value of 'normal', but the validator only allows ['input', 'underline']. This will cause a prop validation error in development mode and potentially unexpected behavior in production.

Apply this diff to fix the validation:

 inputBoxType: {
   type: String,
   default: 'normal',
-  validator: (value: string) => ['input', 'underline'].includes(value)
+  validator: (value: string) => ['normal', 'input', 'underline'].includes(value)
 },

Alternatively, if 'normal' should not be a valid value, change the default back to 'input' to maintain backward compatibility.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
inputBoxType: {
type: String,
default: 'input',
default: 'normal',
validator: (value: string) => ['input', 'underline'].includes(value)
},
inputBoxType: {
type: String,
default: 'normal',
validator: (value: string) => ['normal', 'input', 'underline'].includes(value)
},
🤖 Prompt for AI Agents
In packages/vue/src/select-wrapper/src/index.ts around lines 329 to 333, the
prop inputBoxType currently defaults to 'normal' while its validator only allows
['input','underline'], causing validation errors; fix by setting the default to
'input' to match the validator (or, if 'normal' is intended, add 'normal' to the
validator array) and ensure the default value and validator remain consistent.

tagType: {
Expand Down
Loading
Loading