Skip to content

Conversation

@discreted66
Copy link
Collaborator

@discreted66 discreted66 commented Dec 5, 2025

PR

feat:select-wrapper多端开发

PR Checklist

Please check if your PR fulfills the following requirements:

  • The commit message follows our Commit Message Guidelines
  • Tests for the changes have been added (for bug fixes / features)
  • Docs have been added / updated (for bug fixes / features)

PR Type

What kind of change does this PR introduce?

  • Bugfix
  • Feature
  • Code style update (formatting, local variables)
  • Refactoring (no functional changes, no api changes)
  • Build related changes
  • CI related changes
  • Documentation content changes
  • Other... Please describe:

What is the current behavior?

Issue Number: N/A

What is the new behavior?

Does this PR introduce a breaking change?

  • Yes
  • No

Other information

Summary by CodeRabbit

  • New Features

    • Added mobile-first select wrapper component with enhanced configuration.
    • Input components now support underline styling via new style option.
  • Improvements

    • Extended select component with new initialization and display control options.
    • Enhanced tag sizing and visibility logic for better UI consistency.
    • Optimized class merging and dynamic configuration resolution.

✏️ Tip: You can customize this high-level summary in your review settings.

@github-actions github-actions bot added the enhancement New feature or request (功能增强) label Dec 5, 2025
@coderabbitai
Copy link

coderabbitai bot commented Dec 5, 2025

Walkthrough

This PR introduces a new select-wrapper module infrastructure across design systems (Aurora and Saas), adds mobile-first component implementations for SelectWrapper with extensive configuration options, and extends existing components with new props (initLabel, inputBoxType, showEmptyValue) and updated label initialization logic. Multiple prop renames standardize naming conventions across components.

Changes

Cohort / File(s) Summary
Design System Select-Wrapper Modules
packages/design/aurora/src/select-wrapper/index.ts, packages/design/saas/src/select-wrapper/index.ts
New modules exporting default configuration objects with baseOpts (optionHeight, limit), icons (chevron-down, add, loading), state properties (sizeMap, spacingHeight, initialInputHeight, autoHideDownIcon, delayBlur), and a renderless function exposing computedCollapseTagSize, computedShowDropdownIcon, toggleCheckAll, computedShowTagText, and isTagClosable methods.
Vue SelectWrapper Component Updates
packages/vue/src/select-wrapper/src/index.ts, packages/vue/src/select-wrapper/src/pc.vue
Template import resolution extended to include mobile-first variant; prop InputBoxType renamed to inputBoxType with default changed from 'input' to 'normal' in index.ts, and renamed to inputBoxType in pc.vue.
Vue SelectWrapper Mobile-First Component
packages/vue/src/select-wrapper/src/mobile-first.vue
New mobile-first component with extensive props interface covering remote handling, grid/tree options, display flags, and styling. Registers TinyBaseSelect, TinyTreeSelect, TinyGridSelect subcomponents; wires renderless logic and dynamic component resolution.
Vue Base-Select & Input Components
packages/vue/src/base-select/src/mobile-first.vue, packages/vue/src/input/src/mobile-first.vue
Added props: initLabel, inputBoxType, showEmptyValue to base-select; inputBoxType prop and corresponding emit event to input with conditional underline border styling.
Vue GridSelect & Renderless Updates
packages/vue/src/grid-select/src/mobile-first.vue, packages/renderless/src/base-select/index.ts
GridSelect config bindings replaced with builder function calls (buildSelectConfig, buildRadioConfig); postOperOfToVisible extended to set selectedLabel from initLabel even when state.selected is absent.
Renderless Select-Wrapper Vue
packages/renderless/src/select-wrapper/vue.ts
Class array deduplication via Set; new dataTag property ('tiny-select') added to merged props.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~22 minutes

  • The postOperOfToVisible logic change in base-select requires verification that initLabel assignment correctly handles edge cases when state.selected is absent
  • The new mobile-first select-wrapper component's extensive prop surface and component registration logic should be reviewed for completeness and consistency
  • Prop renaming from InputBoxType/InputBoxType to inputBoxType must be verified across all affected files for consistency
  • The toggleCheckAll and related renderless functions in Aurora/Saas modules require careful review of selection state computation, including disabled/groupDisabled/visibility handling
  • Builder function calls in grid-select (buildSelectConfig, buildRadioConfig) need confirmation they produce equivalent configurations at render time

Poem

🐰 A wrapper wove, with mobile flair,
SelectConfigs float through the air,
Props renamed, fresh and bright,
From aurora's glow to saas's light,
The renderless dances—what a sight! ✨

Pre-merge checks and finishing touches

✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly identifies the main focus as select-wrapper mobile-first development with related components, directly matching the file changes and PR objectives.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (2)
packages/vue/src/select-wrapper/src/mobile-first.vue (2)

2-9: Consider adding a :key on the v-for template for slots

<template v-for="slotName in slotNames" ...> currently has no :key. Vue will still work, but adding a stable key avoids potential warnings and makes diffing clearer if slotNames changes.

-    <template v-for="slotName in slotNames" v-slot:[slotName]="slotProps">
+    <template v-for="slotName in slotNames" v-slot:[slotName]="slotProps" :key="slotName">
       <slot :name="slotName" v-bind="slotProps"></slot>
     </template>

306-314: Reduce ambiguity by renaming the imported setup helper

Using setup both as the imported helper and as the component’s setup method works (the method name doesn’t create a local binding), but it’s easy for readers to think the inner setup(...) call is recursive.

You could rename the import to clarify intent:

-import { props, $prefix, defineComponent, setup } from '@opentiny/vue-common'
+import { props, $prefix, defineComponent, setup as useSetup } from '@opentiny/vue-common'

 // ...

-  setup(props, context) {
+  setup(props, context) {
     const components = {
       TinyBaseSelect,
       TinyTreeSelect,
       TinyGridSelect
     }

-    return setup({ props, context, renderless, api, extendOptions: { components } }) as any
+    return useSetup({ props, context, renderless, api, extendOptions: { components } }) as any
   }
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between c29f2bd and 5857637.

⛔ Files ignored due to path filters (1)
  • packages/design/saas/src/select-wrapper/icon-loading.svg is excluded by !**/*.svg
📒 Files selected for processing (10)
  • packages/design/aurora/src/select-wrapper/index.ts (1 hunks)
  • packages/design/saas/src/select-wrapper/index.ts (1 hunks)
  • packages/renderless/src/base-select/index.ts (1 hunks)
  • packages/renderless/src/select-wrapper/vue.ts (1 hunks)
  • packages/vue/src/base-select/src/mobile-first.vue (2 hunks)
  • packages/vue/src/grid-select/src/mobile-first.vue (1 hunks)
  • packages/vue/src/input/src/mobile-first.vue (2 hunks)
  • packages/vue/src/select-wrapper/src/index.ts (2 hunks)
  • packages/vue/src/select-wrapper/src/mobile-first.vue (1 hunks)
  • packages/vue/src/select-wrapper/src/pc.vue (1 hunks)
🧰 Additional context used
🧠 Learnings (4)
📚 Learning: 2024-11-25T03:43:05.285Z
Learnt from: Davont
Repo: opentiny/tiny-vue PR: 2513
File: packages/vue/src/huicharts/huicharts-histogram/src/chart-histogram.vue:33-36
Timestamp: 2024-11-25T03:43:05.285Z
Learning: 在 Tiny Vue 代码库中,使用 `chart-core` 中的 `huiChartOption` 的组件,不应在其 `data` 中定义 `huiChartOption` 或 `option`,而是应该依赖 `chart-core` 提供的 `huiChartOption`。

Applied to files:

  • packages/design/aurora/src/select-wrapper/index.ts
  • packages/design/saas/src/select-wrapper/index.ts
📚 Learning: 2024-11-25T03:24:05.740Z
Learnt from: Davont
Repo: opentiny/tiny-vue PR: 2513
File: packages/vue/src/huicharts/huicharts-sunburst/src/chart-sunburst.vue:30-32
Timestamp: 2024-11-25T03:24:05.740Z
Learning: 在位于`packages/vue/src/huicharts/huicharts-sunburst/src/chart-sunburst.vue`的组件中,当使用`chart-core`时,应删除错误的`option`定义,使用`chart-core`中的`huiChartOption`。

Applied to files:

  • packages/design/aurora/src/select-wrapper/index.ts
  • packages/design/saas/src/select-wrapper/index.ts
📚 Learning: 2024-11-25T03:43:19.322Z
Learnt from: Davont
Repo: opentiny/tiny-vue PR: 2513
File: packages/vue/src/huicharts/huicharts-heatmap/src/chart-heatmap.vue:38-40
Timestamp: 2024-11-25T03:43:19.322Z
Learning: 在 Vue.js 组件(如 `chart-heatmap.vue`)中,使用来自 `chart-core` 的 `huiChartOption` 来管理图表选项,不要在 `data()` 中声明 `option`。

Applied to files:

  • packages/design/aurora/src/select-wrapper/index.ts
  • packages/design/saas/src/select-wrapper/index.ts
📚 Learning: 2024-11-25T03:24:33.808Z
Learnt from: Davont
Repo: opentiny/tiny-vue PR: 2513
File: packages/vue/src/huicharts/huicharts-sunburst/src/chart-sunburst.vue:30-32
Timestamp: 2024-11-25T03:24:33.808Z
Learning: 对于使用了来自`opentiny/vue-huicharts-core`中`Core` mixin的组件,`huiChartOption`数据属性已由`chart-core`提供,因此无需在组件的`data()`中声明。

Applied to files:

  • packages/design/saas/src/select-wrapper/index.ts
🧬 Code graph analysis (1)
packages/design/saas/src/select-wrapper/index.ts (1)
packages/renderless/src/select-wrapper/vue.ts (1)
  • api (1-10)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: PR E2E Test (pnpm test:e2e3)
🔇 Additional comments (9)
packages/renderless/src/select-wrapper/vue.ts (1)

17-33: Class deduplication and dataTag addition look correct

Using Array.from(new Set(classArray)) here cleanly removes duplicate class entries while preserving order and keeps existing behavior for string/array class bindings. Adding dataTag: 'tiny-select' after spreading props and restAttrs enforces a consistent identifier for downstream consumers, even if a conflicting dataTag was provided upstream. No changes needed from my side.

packages/renderless/src/base-select/index.ts (1)

1252-1255: LGTM! Fallback label initialization added.

The additional branch correctly handles the case where state.selected is not present but modelValue and initLabel exist. This ensures that the selectedLabel is properly initialized from initLabel in scenarios where selection state hasn't been fully established yet.

packages/vue/src/input/src/mobile-first.vue (1)

97-105: LGTM! Conditional styling for inputBoxType implemented correctly.

The conditional rendering logic properly applies different border styles based on the inputBoxType prop:

  • 'underline': removes side and top borders, keeping only bottom border
  • Other values: applies rounding based on the presence of prepend/append slots
packages/vue/src/base-select/src/mobile-first.vue (1)

763-766: LGTM! New props properly integrated.

The three new props (initLabel, inputBoxType, showEmptyValue) are correctly declared and passed through to the TinyInput component (lines 305-306). These extend the component's API surface to support:

  • External label initialization via initLabel
  • Input box styling control via inputBoxType
  • Empty value display handling via showEmptyValue
packages/vue/src/select-wrapper/src/pc.vue (1)

266-270: LGTM! Prop naming convention improved.

The prop has been correctly renamed from InputBoxType to inputBoxType, following JavaScript/Vue camelCase naming conventions. The type, default value, and validator remain unchanged.

packages/vue/src/grid-select/src/mobile-first.vue (1)

40-41: Verify that buildSelectConfig() and buildRadioConfig() functions are properly implemented and exported from the renderless API.

The grid configuration bindings have been changed from static state properties to dynamic builder function calls. Ensure these functions are defined in the renderless module (@opentiny/vue-renderless/grid-select/vue) and properly accessible within the component context, as missing or incorrectly scoped implementations would cause runtime errors.

packages/design/aurora/src/select-wrapper/index.ts (1)

48-98: Verify API method availability and compare Aurora/Saas implementations for duplication.

The toggleCheckAll function calls API methods (setSoftFocus, updateModelValue, directEmitChange) that need verification. Additionally, check if this implementation duplicates logic in the Saas theme's select-wrapper module. If duplication exists, extract the common logic into a shared utility function.

packages/vue/src/select-wrapper/src/index.ts (1)

15-15: Verify mobile-first template configuration.

The template import uses a virtual-template loader with ?pc|mobile-first query parameters. Confirm that the mobile-first variant is properly configured in the template loader configuration and that the corresponding template variant is available.

packages/vue/src/select-wrapper/src/mobile-first.vue (1)

254-257: Verify that stopPropagation is intentionally tri‑state (Boolean + undefined)

Defining a Boolean prop with default: undefined gives you three states (true / false / “not set”). That’s useful if the renderless logic distinguishes “unset” from an explicit false, but it also means any boolean operations must guard against undefined.

Please double‑check the renderless/select‑wrapper logic to confirm it:

  • Explicitly handles undefined as a distinct case, and
  • Never assumes stopPropagation is always a strict boolean without normalization (e.g., using !!props.stopPropagation where needed).

Comment on lines +1 to +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
}
}
}
}
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.

Comment on lines +329 to 333
inputBoxType: {
type: String,
default: 'input',
default: 'normal',
validator: (value: string) => ['input', 'underline'].includes(value)
},
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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request (功能增强)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant