-
Notifications
You must be signed in to change notification settings - Fork 336
docs(tag-input): add example #3962
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: dev
Are you sure you want to change the base?
Conversation
WalkthroughThis PR adds a complete TagInput component implementation to the Tiny Vue library, including renderless logic, Vue templates for PC and mobile-first modes, comprehensive type definitions, theme styling, and demonstration examples with documentation. Changes
Sequence DiagramsequenceDiagram
actor User
participant Input as TagInput Input
participant RenderlessLogic as Renderless Handlers
participant State as Reactive State
participant UI as Component UI<br/>(TinyTag, TinyTooltip)
User->>Input: Type text + press Enter
Input->>RenderlessLogic: addTag()
RenderlessLogic->>State: Validate, split by separator, check max
State->>State: Append to tagList
State->>UI: Emit update:modelValue
UI->>UI: Re-render showTagList & collapsedTagList
UI->>User: Display new tag
User->>UI: Click tag close icon
UI->>RenderlessLogic: removeTag(index)
RenderlessLogic->>State: Remove from tagList
State->>UI: Emit update:modelValue
UI->>User: Remove tag from display
User->>Input: Drag tag to new position
Input->>RenderlessLogic: handleDragStart(index)<br/>handleDragEnter(targetIndex)<br/>handleDrop(index)
RenderlessLogic->>State: Reorder tagList[draggingIndex]<br/>↔ tagList[targetIndex]
State->>UI: Emit update:modelValue
UI->>User: Re-render tags in new order
Estimated code review effort🎯 4 (Complex) | ⏱️ ~50 minutes Possibly related PRs
Suggested labels
Suggested reviewers
Poem
Pre-merge checks and finishing touches✅ Passed checks (3 passed)
✨ Finishing touches
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. Comment |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 19
Fix all issues with AI Agents 🤖
In @examples/sites/demos/apis/tag-input.js:
- Line 14: Remove the hidden U+200C zero-width non-joiner from the English
description string so it reads exactly 'Binding Value' (no hidden characters).
Locate the mapping entry with the key 'en-US' and the value currently containing
'Binding Value' and replace that value with a clean literal "Binding Value" (or
programmatically strip U+200C, e.g., .replace(/\u200C/g, '') if needed).
- Around line 17-18: The demo references for the TagInput API are wrong: update
the pcDemo and mfDemo values that currently point to 'tag-group-size' to the
TagInput-specific demo slug (e.g. replace 'tag-group-size' with 'basic-usage' or
the TagInput 'model-value' demo ID) so the TagInput API page links to the
correct demo examples.
In @examples/sites/demos/pc/app/tag-input/basic-usage.vue:
- Line 21: Remove the unused maxTagCount variable or bind it to a component:
either delete the maxTagCount declaration if it's not needed, or update one of
the tiny-tag-input usages to pass the value via the :max prop (e.g.,
<tiny-tag-input :max="maxTagCount" ...>) so the variable is actually used.
In @examples/sites/demos/pc/app/tag-input/disabled-readonly-composition-api.vue:
- Around line 3-4: The template uses the placeholder variable for the
tiny-tag-input components but placeholder is not defined in the script setup;
add a placeholder definition (e.g., const placeholder = ref('Enter tag') or
const placeholder = 'Enter tag') inside the <script setup> so placeholder is in
scope for the template (ensure it’s exported from the script setup context
alongside data and data1 used by v-model).
In @examples/sites/demos/pc/app/tag-input/separator-tag-composition-api.vue:
- Line 11: The array `data` is created with reactive([...]) but should use
ref([...]) because v-model may replace the array reference and reactive proxies
can lose reactivity; change the declaration of `data` to use ref([...]) (ensure
`ref` is imported) and update any places that read or mutate the array to use
`data.value` (or keep template v-model usage which works with a ref) so
reactivity is preserved when the component replaces the array reference.
In @examples/sites/demos/pc/app/tag-input/webdoc/tag-input.cn.md:
- Around line 1-7: The TagInput documentation only contains a title and one-line
description; expand tag-input.cn.md to include comprehensive docs: add a "Basic
usage" section with a minimal code example showing <TagInput> usage, an
"API/Props" table listing props (e.g., value, defaultValue, onChange,
placeholder, maxTags, allowDuplicates), an "Events & Methods" section describing
callbacks like onChange and methods (if any), "Examples" showing controlled vs
uncontrolled usage, custom rendering and keyboard interactions, "Common
patterns" (e.g., async suggestions, validation), and an "Accessibility & Notes"
section; ensure examples reference the TagInput component name and include brief
notes on types and expected prop shapes.
In @examples/sites/demos/pc/app/tag-input/webdoc/tag-input.js:
- Around line 59-63: The demo definition for demoId 'clearable-tag' has the
English title incorrectly set to 'basic usage'; update the name object's 'en-US'
value to 'Clearable Tags' so it matches the Chinese 'zh-CN' label and accurately
describes the demo for English users (locate the name property in the demo entry
for demoId 'clearable-tag' and replace the current 'en-US' string).
- Around line 35-39: The demo entry with demoId 'max-tag' has an incorrect
English label; update the name object's 'en-US' value from 'basic usage' to 'Max
Tags' so it matches the Chinese '最大标签数' and the demoId; modify the name property
for the demo with demoId 'max-tag' accordingly.
- Around line 83-87: Update the demo metadata for the demoId 'prefix-suffix' by
changing the English entry in the name object from 'basic usage' to 'Custom
Prefix/Suffix' so the 'en-US' label matches the Chinese label and accurately
describes the demo; locate the name object next to demoId 'prefix-suffix' and
replace the 'en-US' string accordingly.
- Around line 95-99: The English demo name is incorrect: in the demo config
object for demoId 'draggable-tag' (the name property mapping), change the
'en-US' value from 'basic usage' to 'Draggable Tags' so it matches the Chinese
'zh-CN' label and accurately describes the demo.
- Around line 47-51: The English label for the demo entry with demoId
'collapsed-tag' is incorrect: in the name object change the 'en-US' value from
'basic usage' to 'Collapsed Tags' so it matches the Chinese label; update the
name->{'en-US'} string in the demo definition to "Collapsed Tags".
- Around line 71-75: The demo's English title is incorrect: in the object with
demoId 'separator-tag' update the name mapping so name['en-US'] is changed from
'basic usage' to 'Separator Input' (i.e., replace the 'en-US' string value to
match the Chinese label).
- Around line 23-27: Update the demo metadata for demoId 'disabled-readonly' so
the English name under name['en-US'] is corrected from 'basic usage' to
'Disabled and Readonly'; locate the name object next to demoId
'disabled-readonly' in tag-input.js and replace the 'en-US' value accordingly to
match the Chinese label and avoid duplicate names.
In @packages/renderless/src/tag-input/index.ts:
- Around line 99-110: handleDrop can access state.draggingIndex (and
dragTargetIndex) when they are null causing undefined behavior; add a guard at
the start of handleDrop to check that state.draggingIndex and
state.dragTargetIndex are non-null and within bounds of state.tagList (and
return early if not), then proceed with creating newTags, splicing and emitting;
reference the handleDrop function and fields state.draggingIndex,
state.dragTargetIndex, and state.tagList when making the change.
- Around line 80-83: The drag handler is incorrectly calling
event.dataTransfer!.setData with event.target (an EventTarget|null) and using a
non-null assertion on dataTransfer; change this to safely guard dataTransfer and
pass a string: check for event.dataTransfer first, obtain a string identifier
from the element (e.g., cast event.target or event.currentTarget to HTMLElement
and use its id or dataset value), then call
event.dataTransfer.setData('text/plain', identifier) and set
event.dataTransfer.effectAllowed = 'move' without using the non-null assertion;
ensure you handle the case where there is no id/dataset by generating or
skipping the setData call.
- Around line 11-22: The batch-add logic can exceed props.max because newTags
(from value.split when props.separator is set) are pushed wholesale; modify the
code around newTags/tags so you compute available = props.max - tags.length,
then slice newTags to newTags.slice(0, available) before pushing and ensure
state.currentValue is cleared and return if available <= 0; reference symbols:
state.tagList, props.max, props.separator, newTags, tags.push to locate and
update the logic.
In @packages/renderless/src/tag-input/vue.ts:
- Around line 38-41: state.tagList is initialized from props.modelValue once and
won't reflect later parent changes; fix by syncing the prop into the reactive
state: either replace tagList with a computed getter/setter that proxies
props.modelValue (so mutations emit update:modelValue) or add a watch on
props.modelValue that updates state.tagList = newVal || [] (use immediate: true)
and keep emitting update:modelValue in your add/remove handlers; update any
handlers that mutate state.tagList to use the computed setter if you choose the
computed option.
In @packages/renderless/types/tag-input.type.ts:
- Around line 19-31: The modelValue prop is currently declared as a generic
Array but the component and ITagInputState.tagList treat it as an array of
strings; update the prop declaration in the tag-input component
(packages/vue/src/tag-input/src/index.ts) to use a typed PropType<string[]>
(e.g. type: Array as PropType<string[]>) so modelValue, addTag (which calls
value.split(props.separator)), and all tag-list mutations are statically
consistent with string[].
In @packages/theme/src/tag-input/index.less:
- Line 9: Replace the hardcoded border color in the TagInput styles with the
theme CSS variable: locate the rule that sets "border: 1px solid #ccc" (in the
tag input component styles, e.g., the TagInput root selector) and change it to
use a variable like "var(--tv-TagInput-border-color)" with a sensible fallback
(e.g., var(--tv-TagInput-border-color, #ccc)) so theming is respected while
preserving default behavior.
🧹 Nitpick comments (15)
examples/sites/demos/pc/app/tag-input/clearable-tag-composition-api.vue (1)
13-13: Simplify placeholder to a const string.The
placeholderis never mutated, so wrapping it inref()adds unnecessary reactivity overhead. For consistency with other demos (e.g.,max-composition-api.vueusesconst placeholder = 'please enter a tag'), use a simple const string instead.🔎 Proposed fix
-const placeholder = ref('please enter a tag') +const placeholder = 'please enter a tag'examples/sites/demos/pc/app/tag-input/draggable-tag-composition-api.vue (1)
1-12: Consider adding placeholder and styling for consistency.This demo lacks both a
placeholderprop and the scoped styling (margin-bottom: 20px) that other tag-input demos include. While this might be intentionally minimal, adding these would maintain consistency across the demo suite and provide a more complete example.🔎 Proposed additions
<template> <div class="tiny-tag-input-demo"> - <tiny-tag-input v-model="data" draggable></tiny-tag-input> + <tiny-tag-input v-model="data" :placeholder="placeholder" draggable></tiny-tag-input> </div> </template> <script setup> import { TinyTagInput } from '@opentiny/vue' import { reactive } from 'vue' const data = reactive(['tag1', 'tag2', 'tag3', 'tag4', 'tag5', 'tag6']) +const placeholder = 'please enter a tag' </script> + +<style scoped> +.tiny-tag-input-demo .tiny-tag-input { + margin-bottom: 20px; +} +</style>packages/vue/src/tag-input/package.json (1)
24-24: Clarify the commented postversion script.The
"//postversion"key uses a JSON comment workaround to disable the postversion script. While this pattern works, it may be confusing to developers unfamiliar with it. Consider either removing the line entirely if the script isn't needed, or adding a comment in nearby documentation explaining why it's disabled.examples/sites/demos/pc/app/tag-input/prefix-suffix-composition-api.vue (1)
19-19: Simplify placeholder to a const string.The
placeholderis never mutated, so wrapping it inref()adds unnecessary reactivity overhead. For consistency with other demos (e.g.,max-composition-api.vueusesconst placeholder = 'please enter a tag'), use a simple const string instead.🔎 Proposed fix
-const placeholder = ref('please enter a tag') +const placeholder = 'please enter a tag'examples/sites/demos/pc/app/tag-input/webdoc/tag-input.en.md (1)
7-7: Consider expanding the documentation.The description "Used to enter the label" is brief and could be more informative. Consider adding:
- A more detailed explanation of the component's purpose
- Key features or use cases
- A simple usage example
examples/sites/demos/pc/app/tag-input/draggable-tag.vue (1)
1-20: Consider adding styling and placeholder for consistency.This demo is missing elements present in other TagInput demos:
- The scoped
<style>section with.tiny-tag-input { margin-bottom: 20px; }(see collapsed-tag.vue, clearable-tag.vue)- The
placeholderprop (see other demos)Adding these would improve visual consistency across the demo gallery.
🔎 Suggested additions for consistency
Add a placeholder to the template:
<template> <div class="tiny-tag-input-demo"> - <tiny-tag-input v-model="data" draggable></tiny-tag-input> + <tiny-tag-input v-model="data" :placeholder="placeholder" draggable></tiny-tag-input> </div> </template>Update the data to include placeholder:
data() { return { - data: ['tag1', 'tag2', 'tag3', 'tag4', 'tag5', 'tag6'] + data: ['tag1', 'tag2', 'tag3', 'tag4', 'tag5', 'tag6'], + placeholder: 'please enter a tag' } }Add the scoped style section:
} } </script> + +<style scoped> +.tiny-tag-input-demo .tiny-tag-input { + margin-bottom: 20px; +} +</style>examples/sites/demos/pc/app/tag-input/collapsed-tag-composition-api.vue (1)
11-13: Consider simplifying static values for consistency.The
placeholderis wrapped inref()but appears to be a static string that never changes. For static demo values, consider using a plainconstinstead to reduce overhead and improve clarity, similar to how it's handled in the Options API variant.🔎 Optional simplification
const data = reactive(['tag1', 'tag2', 'tag3', 'tag4', 'tag5', 'tag6']) const minCollapsedNum = ref(5) -const placeholder = ref('please enter a tag') +const placeholder = 'please enter a tag'packages/theme/src/tag-input/index.less (2)
38-48: Consider using CSS variables for fixed dimensions.The hardcoded values for height (32px), line-height (32px), and font-size (14px) reduce flexibility and theming consistency. While these may be intentional defaults, using CSS variables would allow better customization.
🔎 Proposed refactor
.@{tag-input-prefix-cls}-inner { border: none; outline: none; - height: 32px; - line-height: 32px; + height: var(--tv-TagInput-inner-height, 32px); + line-height: var(--tv-TagInput-inner-height, 32px); color: var(--tv-TagInput-text-color); background: var(--tv-TagInput-bg-color); padding: 0 var(--tv-TagInput-padding); - font-size: 14px; + font-size: var(--tv-TagInput-font-size, 14px); width: auto; }
50-52: Avoid!importantif possible.Using
!importantcan make styles harder to override and maintain. Consider whether the specificity can be increased through selector refinement instead.packages/vue/src/tag-input/src/index.ts (1)
56-59: Remove redundant default value.Setting
default: undefinedis redundant since undefined is already the default value for unspecified properties.🔎 Proposed fix
separator: { - type: String, - default: undefined + type: String },examples/sites/demos/apis/tag-input.js (1)
155-156: Missing event documentation.The component emits
update:modelValueon various actions (add tag, remove tag, clear, drag-drop reorder). Consider documenting these events for API completeness, such as:
update:modelValue- emitted when the tag list changes- Potentially dedicated events for
tag-add,tag-remove,clearpackages/vue/src/tag-input/src/pc.vue (2)
18-23: Clarify event handler naming for drag-and-drop.The
@dragendevent callshandleDrop, which may cause confusion. Typicallydragendfires on the element being dragged when dragging ends, whiledropfires on the drop target. UsinghandleDropfordragendcould mislead maintainers.Consider renaming to
handleDragEndfor clarity, or verify this is intentional behavior.
25-41: Consider keyboard accessibility for collapsed tags tooltip.The collapsed tags counter (
+N) is shown via a tooltip that is activated on hover. Users relying on keyboard navigation may not be able to access or interact with the collapsed tags to remove them.packages/renderless/src/tag-input/vue.ts (1)
35-36: Unused destructured variables.
refandparentare destructured but never used in this function. Consider removing them to reduce noise.🔎 Proposed fix
export const renderless = ( props, - { reactive, computed, ref }: ISharedRenderlessParamHooks, - { emit, parent }: ISharedRenderlessParamUtils<never> + { reactive, computed }: ISharedRenderlessParamHooks, + { emit }: ISharedRenderlessParamUtils<never> ): ITagInputApi => {packages/renderless/src/tag-input/index.ts (1)
28-33: Unusedpropsparameter.The
propsparameter is destructured but never used inremoveTag. Same applies tohandleBackspaceandhandleClear.🔎 Proposed fix
export const removeTag = - ({ emit, props, state }: Pick<ITagInputRenderlessParams, 'emit' | 'props' | 'state'>) => + ({ emit, state }: Pick<ITagInputRenderlessParams, 'emit' | 'state'>) => (index: number) => {Apply similar changes to
handleBackspace(line 36) andhandleClear(line 45).
📜 Review details
Configuration used: Repository UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (36)
examples/sites/demos/apis/tag-input.jsexamples/sites/demos/pc/app/tag-input/basic-usage-composition-api.vueexamples/sites/demos/pc/app/tag-input/basic-usage.vueexamples/sites/demos/pc/app/tag-input/clearable-tag-composition-api.vueexamples/sites/demos/pc/app/tag-input/clearable-tag.vueexamples/sites/demos/pc/app/tag-input/collapsed-tag-composition-api.vueexamples/sites/demos/pc/app/tag-input/collapsed-tag.vueexamples/sites/demos/pc/app/tag-input/disabled-readonly-composition-api.vueexamples/sites/demos/pc/app/tag-input/disabled-readonly.vueexamples/sites/demos/pc/app/tag-input/draggable-tag-composition-api.vueexamples/sites/demos/pc/app/tag-input/draggable-tag.spec.tsexamples/sites/demos/pc/app/tag-input/draggable-tag.vueexamples/sites/demos/pc/app/tag-input/max-composition-api.vueexamples/sites/demos/pc/app/tag-input/max.vueexamples/sites/demos/pc/app/tag-input/prefix-suffix-composition-api.vueexamples/sites/demos/pc/app/tag-input/prefix-suffix.vueexamples/sites/demos/pc/app/tag-input/separator-tag-composition-api.vueexamples/sites/demos/pc/app/tag-input/separator-tag.vueexamples/sites/demos/pc/app/tag-input/webdoc/tag-input.cn.mdexamples/sites/demos/pc/app/tag-input/webdoc/tag-input.en.mdexamples/sites/demos/pc/app/tag-input/webdoc/tag-input.jsexamples/sites/demos/pc/menus.jspackages/modules.jsonpackages/renderless/src/tag-input/index.tspackages/renderless/src/tag-input/vue.tspackages/renderless/types/index.tspackages/renderless/types/tag-input.type.tspackages/theme/src/index.lesspackages/theme/src/tag-input/index.lesspackages/theme/src/tag-input/vars.lesspackages/vue/index.tspackages/vue/package.jsonpackages/vue/src/tag-input/index.tspackages/vue/src/tag-input/package.jsonpackages/vue/src/tag-input/src/index.tspackages/vue/src/tag-input/src/pc.vue
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2024-11-04T09:35:13.159Z
Learnt from: zzcr
Repo: opentiny/tiny-vue PR: 2481
File: packages/theme/src/time-range/vars.less:27-28
Timestamp: 2024-11-04T09:35:13.159Z
Learning: 在 `packages/theme/src/time-range/vars.less` 文件中,应使用 `var(--tv-TimeRange-header-height)` 作为 `--tv-TimeRange-header-line-height` 的值,以保持一致性。
Applied to files:
packages/theme/src/tag-input/vars.less
🧬 Code graph analysis (4)
packages/vue/src/tag-input/src/index.ts (1)
packages/vue-common/src/index.ts (4)
$props(45-53)$prefix(43-43)props(55-73)$setup(199-233)
packages/vue/src/tag-input/index.ts (1)
packages/vue/index.ts (3)
TagInput(754-754)TagInput(755-755)version(421-421)
packages/renderless/types/tag-input.type.ts (2)
packages/vue/src/tag-input/src/index.ts (1)
tagInputProps(15-68)packages/vue-hooks/types/shared.type.ts (1)
ISharedRenderlessFunctionParams(1-15)
packages/renderless/src/tag-input/index.ts (2)
packages/renderless/types/tag-input.type.ts (1)
ITagInputRenderlessParams(51-55)packages/vue-common/src/index.ts (1)
props(55-73)
🔇 Additional comments (20)
examples/sites/demos/pc/app/tag-input/max-composition-api.vue (1)
1-20: LGTM!The demo correctly demonstrates the max tag limit functionality. The data array intentionally contains 6 tags while
maxis set to 5, effectively showcasing the limiting behavior. The use of const for static values (maxandplaceholder) is appropriate.packages/vue/src/tag-input/index.ts (1)
1-30: LGTM! Standard Vue plugin implementation.The plugin wrapper follows the library's established patterns for component registration, versioning, and auto-installation. The code is clean and correctly structured.
packages/renderless/types/index.ts (1)
183-183: LGTM! Export correctly placed.The new export statement for tag-input types is correctly positioned in alphabetical order and follows the established pattern.
packages/theme/src/index.less (1)
137-137: LGTM! Import correctly placed.The tag-input theme import is correctly positioned in alphabetical order and follows the established pattern for component theme imports.
examples/sites/demos/pc/app/tag-input/collapsed-tag.vue (1)
1-28: LGTM! Well-structured demo component.The collapsed tag demo correctly demonstrates the
minCollapsedNumprop with clear, simple code. The structure follows Vue best practices for Options API components.examples/sites/demos/pc/app/tag-input/clearable-tag.vue (1)
1-27: LGTM! Clear demonstration of the clearable feature.The clearable tag demo is well-implemented and consistent with the other demo files in structure and style.
examples/sites/demos/pc/app/tag-input/separator-tag-composition-api.vue (1)
1-10: LGTM for template, imports, and primitive refs.The template structure, imports, and use of
ref()for primitive values (placeholder, separator) are correct. The scoped styling is appropriate for the demo.Also applies to: 12-20
examples/sites/demos/pc/menus.js (1)
178-178: LGTM! TagInput menu entry properly configured.The menu entry is correctly positioned in the Form components section with appropriate metadata marking it as experimental in version 3.29.0. The structure is consistent with other component entries.
examples/sites/demos/pc/app/tag-input/prefix-suffix.vue (1)
1-34: LGTM! Clean demo of prefix and suffix slots.The demo effectively demonstrates the slot functionality of the TagInput component with clear, straightforward code. The template structure, component registration, and styling all follow Vue best practices.
examples/sites/demos/pc/app/tag-input/separator-tag.vue (1)
1-28: LGTM! Clear demonstration of the separator prop.The demo concisely shows how to configure a custom separator for tag input. The implementation is clean and follows the established demo patterns in the project.
examples/sites/demos/pc/app/tag-input/basic-usage-composition-api.vue (1)
1-23: LGTM! Excellent composition API demonstration.The demo effectively showcases multiple TagInput configurations using Vue 3's composition API. The use of
reactivefor arrays andreffor the placeholder is correct, and the three variants (default, info/light, success/plain) provide good visual examples for users.packages/vue/package.json (1)
200-200: LGTM! Dependency correctly added.The workspace dependency for
@opentiny/vue-tag-inputis properly added in alphabetical order and follows the established pattern for component dependencies in the monorepo.packages/theme/src/tag-input/vars.less (1)
13-41: LGTM! Well-structured theme variables.The CSS custom properties are well-organized with clear sectioning, consistent naming conventions, and appropriate fallback values. The usage of
var()with fallbacks aligns with the codebase's theming patterns.examples/sites/demos/pc/app/tag-input/disabled-readonly.vue (1)
8-23: LGTM! Clean Options API implementation.The component correctly defines all referenced properties and follows Vue Options API best practices. The structure is clear and the data initialization is appropriate for demonstrating disabled and readonly states.
examples/sites/demos/pc/app/tag-input/max.vue (1)
16-17: This is intentional behavior. Themaxprop controls whether users can add new tags, not the initial data size. Both the Options API and Composition API demo variants (max.vueandmax-composition-api.vue) deliberately initialize with 6 tags while settingmax: 5, demonstrating that the component accepts initial data exceeding the limit and only prevents new additions once the max is reached.examples/sites/demos/pc/app/tag-input/basic-usage.vue (1)
1-32: LGTM!The demo effectively showcases three different TagInput configurations with varying tag types and effects. The component usage and styling are appropriate.
packages/vue/index.ts (1)
166-166: LGTM!The TagInput component integration follows the established pattern consistently across all export locations (import, components array, named exports, and default export). The alphabetical ordering is maintained correctly.
Also applies to: 368-368, 754-755, 1155-1156
packages/vue/src/tag-input/src/index.ts (1)
15-76: LGTM!The component definition follows the standard TinyVue pattern correctly. The prop definitions include appropriate types, defaults, and validators. The setup function properly delegates to
$setupwith the required parameters.packages/modules.json (1)
2197-2215: LGTM!The TagInput module entries follow the established patterns for other components in this file. The component entry correctly defines both
mobile-firstandpcmodes, and the template entries reference the appropriate Vue files.packages/vue/src/tag-input/src/pc.vue (1)
63-96: LGTM!The component setup follows the established renderless pattern used in this codebase. Props are properly declared and the component integrates correctly with the theme and sub-components.
| defaultValue: '[]', | ||
| desc: { | ||
| 'zh-CN': '绑定值', | ||
| 'en-US': 'Binding Value' |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hidden zero-width character in English description.
The English description contains a zero-width non-joiner character (U+200C) before "Binding Value". This invisible character may cause display or search issues.
🔎 Proposed fix
- 'en-US': 'Binding Value'
+ 'en-US': 'Binding Value'📝 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.
| 'en-US': 'Binding Value' | |
| 'en-US': 'Binding Value' |
🤖 Prompt for AI Agents
In @examples/sites/demos/apis/tag-input.js around line 14, Remove the hidden
U+200C zero-width non-joiner from the English description string so it reads
exactly 'Binding Value' (no hidden characters). Locate the mapping entry with
the key 'en-US' and the value currently containing 'Binding Value' and replace
that value with a clean literal "Binding Value" (or programmatically strip
U+200C, e.g., .replace(/\u200C/g, '') if needed).
| pcDemo: 'tag-group-size', | ||
| mfDemo: 'tag-group-size' |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Incorrect demo references.
The demo references tag-group-size which appears to be from a different component (TagGroup). For the TagInput component, the model-value prop should reference TagInput-specific demos like basic-usage.
🔎 Proposed fix
- pcDemo: 'tag-group-size',
- mfDemo: 'tag-group-size'
+ pcDemo: 'basic-usage',
+ mfDemo: 'basic-usage'📝 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.
| pcDemo: 'tag-group-size', | |
| mfDemo: 'tag-group-size' | |
| pcDemo: 'basic-usage', | |
| mfDemo: 'basic-usage' |
🤖 Prompt for AI Agents
In @examples/sites/demos/apis/tag-input.js around lines 17-18, The demo
references for the TagInput API are wrong: update the pcDemo and mfDemo values
that currently point to 'tag-group-size' to the TagInput-specific demo slug
(e.g. replace 'tag-group-size' with 'basic-usage' or the TagInput 'model-value'
demo ID) so the TagInput API page links to the correct demo examples.
| data: ['tag1', 'tag2', 'tag3', 'tag4', 'tag5', 'tag6'], | ||
| data1: ['tag1', 'tag2', 'tag3', 'tag4', 'tag5', 'tag6'], | ||
| data2: ['tag1', 'tag2', 'tag3', 'tag4', 'tag5', 'tag6'], | ||
| maxTagCount: 3, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion | 🟠 Major
Remove unused variable.
The maxTagCount variable is declared but never used in the template. Consider removing it or binding it to one of the tiny-tag-input components with the :max prop.
🔎 Proposed fix
data: ['tag1', 'tag2', 'tag3', 'tag4', 'tag5', 'tag6'],
data1: ['tag1', 'tag2', 'tag3', 'tag4', 'tag5', 'tag6'],
data2: ['tag1', 'tag2', 'tag3', 'tag4', 'tag5', 'tag6'],
- maxTagCount: 3,
placeholder: 'please enter a tag'Or if you intend to demonstrate the max prop, bind it to one of the inputs:
- <tiny-tag-input v-model="data" :placeholder="placeholder"></tiny-tag-input>
+ <tiny-tag-input v-model="data" :placeholder="placeholder" :max="maxTagCount"></tiny-tag-input>📝 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.
| maxTagCount: 3, | |
| data: ['tag1', 'tag2', 'tag3', 'tag4', 'tag5', 'tag6'], | |
| data1: ['tag1', 'tag2', 'tag3', 'tag4', 'tag5', 'tag6'], | |
| data2: ['tag1', 'tag2', 'tag3', 'tag4', 'tag5', 'tag6'], | |
| placeholder: 'please enter a tag' |
🤖 Prompt for AI Agents
In @examples/sites/demos/pc/app/tag-input/basic-usage.vue around line 21, Remove
the unused maxTagCount variable or bind it to a component: either delete the
maxTagCount declaration if it's not needed, or update one of the tiny-tag-input
usages to pass the value via the :max prop (e.g., <tiny-tag-input
:max="maxTagCount" ...>) so the variable is actually used.
| <span>disabled: </span><tiny-tag-input v-model="data" :placeholder="placeholder" disabled></tiny-tag-input> | ||
| <span>readonly:</span><tiny-tag-input v-model="data1" :placeholder="placeholder" readonly></tiny-tag-input> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Missing placeholder definition causes runtime error.
The template references placeholder on lines 3-4, but it's not defined in the script setup section. This will cause a ReferenceError at runtime.
🔎 Proposed fix
const data = reactive(['tag1', 'tag2', 'tag3', 'tag4', 'tag5', 'tag6'])
const data1 = reactive(['tag1', 'tag2', 'tag3', 'tag4', 'tag5', 'tag6'])
+const placeholder = 'please enter a tag'📝 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.
| <span>disabled: </span><tiny-tag-input v-model="data" :placeholder="placeholder" disabled></tiny-tag-input> | |
| <span>readonly:</span><tiny-tag-input v-model="data1" :placeholder="placeholder" readonly></tiny-tag-input> | |
| const data = reactive(['tag1', 'tag2', 'tag3', 'tag4', 'tag5', 'tag6']) | |
| const data1 = reactive(['tag1', 'tag2', 'tag3', 'tag4', 'tag5', 'tag6']) | |
| const placeholder = 'please enter a tag' |
🤖 Prompt for AI Agents
In @examples/sites/demos/pc/app/tag-input/disabled-readonly-composition-api.vue
around lines 3-4, The template uses the placeholder variable for the
tiny-tag-input components but placeholder is not defined in the script setup;
add a placeholder definition (e.g., const placeholder = ref('Enter tag') or
const placeholder = 'Enter tag') inside the <script setup> so placeholder is in
scope for the template (ensure it’s exported from the script setup context
alongside data and data1 used by v-model).
| import { TinyTagInput } from '@opentiny/vue' | ||
| import { reactive, ref } from 'vue' | ||
|
|
||
| const data = reactive(['tag1', 'tag2', 'tag3', 'tag4', 'tag5', 'tag6']) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Use ref() instead of reactive() for arrays bound to v-model.
When an array is bound to v-model, the component may replace the entire array reference. Using reactive() creates a proxy that can lose reactivity when the reference is replaced. The Vue 3 best practice is to use ref() for arrays in Composition API, especially with v-model bindings.
🔎 Recommended fix
-const data = reactive(['tag1', 'tag2', 'tag3', 'tag4', 'tag5', 'tag6'])
+const data = ref(['tag1', 'tag2', 'tag3', 'tag4', 'tag5', 'tag6'])📝 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.
| const data = reactive(['tag1', 'tag2', 'tag3', 'tag4', 'tag5', 'tag6']) | |
| const data = ref(['tag1', 'tag2', 'tag3', 'tag4', 'tag5', 'tag6']) |
🤖 Prompt for AI Agents
In @examples/sites/demos/pc/app/tag-input/separator-tag-composition-api.vue
around line 11, The array `data` is created with reactive([...]) but should use
ref([...]) because v-model may replace the array reference and reactive proxies
can lose reactivity; change the declaration of `data` to use ref([...]) (ensure
`ref` is imported) and update any places that read or mutate the array to use
`data.value` (or keep template v-model usage which works with a ref) so
reactivity is preserved when the component replaces the array reference.
| if (event.target) { | ||
| event.dataTransfer?.setData('text/plain', event.target) | ||
| event.dataTransfer!.effectAllowed = 'move' | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Incorrect usage of setData and potential null reference.
setData expects a string as the second argument, but event.target is an EventTarget | null. Additionally, the non-null assertion on dataTransfer could throw if dataTransfer is undefined (e.g., in non-drag contexts).
🔎 Proposed fix
(index: number, event: DragEvent) => {
state.draggingIndex = index
- if (event.target) {
- event.dataTransfer?.setData('text/plain', event.target)
- event.dataTransfer!.effectAllowed = 'move'
+ if (event.dataTransfer) {
+ event.dataTransfer.setData('text/plain', String(index))
+ event.dataTransfer.effectAllowed = 'move'
}
}📝 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.
| if (event.target) { | |
| event.dataTransfer?.setData('text/plain', event.target) | |
| event.dataTransfer!.effectAllowed = 'move' | |
| } | |
| if (event.dataTransfer) { | |
| event.dataTransfer.setData('text/plain', String(index)) | |
| event.dataTransfer.effectAllowed = 'move' | |
| } |
🤖 Prompt for AI Agents
In @packages/renderless/src/tag-input/index.ts around lines 80-83, The drag
handler is incorrectly calling event.dataTransfer!.setData with event.target (an
EventTarget|null) and using a non-null assertion on dataTransfer; change this to
safely guard dataTransfer and pass a string: check for event.dataTransfer first,
obtain a string identifier from the element (e.g., cast event.target or
event.currentTarget to HTMLElement and use its id or dataset value), then call
event.dataTransfer.setData('text/plain', identifier) and set
event.dataTransfer.effectAllowed = 'move' without using the non-null assertion;
ensure you handle the case where there is no id/dataset by generating or
skipping the setData call.
| export const handleDrop = | ||
| ({ emit, props, state }: Pick<ITagInputRenderlessParams, 'emit' | 'props' | 'state'>) => | ||
| (index: number, event: DragEvent) => { | ||
| event.preventDefault() | ||
| const newTags = [...(state.tagList ?? [])] | ||
| const draggingTag = newTags[state.draggingIndex] | ||
| newTags.splice(state.draggingIndex, 1) | ||
| newTags.splice(state.dragTargetIndex, 0, draggingTag) | ||
| state.draggingIndex = null | ||
| state.dragTargetIndex = null | ||
| state.tagList = newTags | ||
| emit('update:modelValue', newTags) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Potential null reference when accessing draggingIndex.
state.draggingIndex is typed as null initially and could still be null when handleDrop executes. Accessing newTags[state.draggingIndex] where draggingIndex is null will result in undefined, and the subsequent splice operations will produce incorrect behavior.
🔎 Proposed fix
export const handleDrop =
({ emit, props, state }: Pick<ITagInputRenderlessParams, 'emit' | 'props' | 'state'>) =>
(index: number, event: DragEvent) => {
event.preventDefault()
+ if (state.draggingIndex === null || state.dragTargetIndex === null) {
+ return
+ }
const newTags = [...(state.tagList ?? [])]
const draggingTag = newTags[state.draggingIndex]
newTags.splice(state.draggingIndex, 1)
newTags.splice(state.dragTargetIndex, 0, draggingTag)
state.draggingIndex = null
state.dragTargetIndex = null
state.tagList = newTags
emit('update:modelValue', newTags)
}📝 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.
| export const handleDrop = | |
| ({ emit, props, state }: Pick<ITagInputRenderlessParams, 'emit' | 'props' | 'state'>) => | |
| (index: number, event: DragEvent) => { | |
| event.preventDefault() | |
| const newTags = [...(state.tagList ?? [])] | |
| const draggingTag = newTags[state.draggingIndex] | |
| newTags.splice(state.draggingIndex, 1) | |
| newTags.splice(state.dragTargetIndex, 0, draggingTag) | |
| state.draggingIndex = null | |
| state.dragTargetIndex = null | |
| state.tagList = newTags | |
| emit('update:modelValue', newTags) | |
| export const handleDrop = | |
| ({ emit, props, state }: Pick<ITagInputRenderlessParams, 'emit' | 'props' | 'state'>) => | |
| (index: number, event: DragEvent) => { | |
| event.preventDefault() | |
| if (state.draggingIndex === null || state.dragTargetIndex === null) { | |
| return | |
| } | |
| const newTags = [...(state.tagList ?? [])] | |
| const draggingTag = newTags[state.draggingIndex] | |
| newTags.splice(state.draggingIndex, 1) | |
| newTags.splice(state.dragTargetIndex, 0, draggingTag) | |
| state.draggingIndex = null | |
| state.dragTargetIndex = null | |
| state.tagList = newTags | |
| emit('update:modelValue', newTags) | |
| } |
🤖 Prompt for AI Agents
In @packages/renderless/src/tag-input/index.ts around lines 99-110, handleDrop
can access state.draggingIndex (and dragTargetIndex) when they are null causing
undefined behavior; add a guard at the start of handleDrop to check that
state.draggingIndex and state.dragTargetIndex are non-null and within bounds of
state.tagList (and return early if not), then proceed with creating newTags,
splicing and emitting; reference the handleDrop function and fields
state.draggingIndex, state.dragTargetIndex, and state.tagList when making the
change.
| const state: ITagInputState = reactive({ | ||
| currentValue: '', | ||
| tagList: props.modelValue || [], | ||
| disabled: computed(() => props.disabled), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Critical: tagList is not reactive to prop changes.
tagList is initialized from props.modelValue once during setup, but it won't update if the parent component changes modelValue externally. This breaks the expected v-model behavior where parent-driven updates should reflect in the component.
The component mutates state.tagList directly and emits update:modelValue, but if the parent changes modelValue independently (e.g., programmatic reset), the internal state will be stale.
🔎 Proposed fix - use computed or watch
Option 1: Make tagList a computed with getter/setter:
const state: ITagInputState = reactive({
currentValue: '',
- tagList: props.modelValue || [],
+ tagList: computed({
+ get: () => props.modelValue || [],
+ set: (val) => emit('update:modelValue', val)
+ }),Option 2: Add a watch to sync prop changes (simpler but maintains current pattern):
watch(() => props.modelValue, (newVal) => {
state.tagList = newVal || []
}, { immediate: true })Note: If using Option 1, handler functions would need adjustment since they currently mutate state.tagList directly.
🤖 Prompt for AI Agents
In @packages/renderless/src/tag-input/vue.ts around lines 38-41, state.tagList
is initialized from props.modelValue once and won't reflect later parent
changes; fix by syncing the prop into the reactive state: either replace tagList
with a computed getter/setter that proxies props.modelValue (so mutations emit
update:modelValue) or add a watch on props.modelValue that updates state.tagList
= newVal || [] (use immediate: true) and keep emitting update:modelValue in your
add/remove handlers; update any handlers that mutate state.tagList to use the
computed setter if you choose the computed option.
| export interface ITagInputState { | ||
| currentValue: string | ||
| tagList: string[] | ||
| disabled: boolean | ||
| closeable: boolean | ||
| showClearIcon: boolean | ||
| showTagList: string[] | ||
| collapsedTagList: string[] | ||
| isHovering: boolean | ||
| isFocused: boolean | ||
| draggingIndex: null | number | ||
| dragTargetIndex: null | number | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Find modelValue prop definition and tagList usage
rg -n --type=ts "modelValue|tagList" packages/renderless/src/tag-input/ packages/vue/src/tag-input/ -A3 -B3 | head -200Repository: opentiny/tiny-vue
Length of output: 6491
🏁 Script executed:
#!/bin/bash
# Check the props interface/definition for modelValue type
fd . packages/renderless/types/ packages/vue/src/tag-input/ --name "*.ts" --type f | xargs rg -l "modelValue"Repository: opentiny/tiny-vue
Length of output: 22789
🏁 Script executed:
#!/bin/bash
# Search for TagInput component definition and props
rg -n "interface.*TagInput|type.*TagInput" packages/renderless/types/ packages/vue/src/tag-input/ -A10 | head -150Repository: opentiny/tiny-vue
Length of output: 2429
🏁 Script executed:
#!/bin/bash
# Search for tag-input tests and documentation
find . -path "*/tag-input*test*" -o -path "*/tag-input*spec*" 2>/dev/null | head -10Repository: opentiny/tiny-vue
Length of output: 122
🏁 Script executed:
#!/bin/bash
# Look for any type definitions or documentation about tag-input props
rg -n "tagInputProps|ITagInputProps" packages/renderless/types/ -A20 | head -100Repository: opentiny/tiny-vue
Length of output: 2057
🏁 Script executed:
#!/bin/bash
# Check if there's a prop type definition file
find packages -name "*prop*" -path "*tag-input*" -type fRepository: opentiny/tiny-vue
Length of output: 43
🏁 Script executed:
#!/bin/bash
# Find and examine the tagInputProps definition
cat packages/vue/src/tag-input/src/index.tsRepository: opentiny/tiny-vue
Length of output: 1823
🏁 Script executed:
#!/bin/bash
# Check if there are any validation or type guard mechanisms for the modelValue
rg -n "modelValue|Array<|string\[\]" packages/renderless/src/tag-input/index.ts -A2 -B2 | head -50Repository: opentiny/tiny-vue
Length of output: 709
Update modelValue prop type to be explicit about string elements.
The modelValue prop is typed as generic Array but the implementation treats it exclusively as an array of strings. The addTag function uses value.split(props.separator), and all state mutations directly manipulate string arrays. Update the prop definition in packages/vue/src/tag-input/src/index.ts to type: Array as PropType<string[]> to ensure type consistency with the ITagInputState.tagList: string[] interface and accurately reflect the component's constraint.
🤖 Prompt for AI Agents
In @packages/renderless/types/tag-input.type.ts around lines 19-31, The
modelValue prop is currently declared as a generic Array but the component and
ITagInputState.tagList treat it as an array of strings; update the prop
declaration in the tag-input component (packages/vue/src/tag-input/src/index.ts)
to use a typed PropType<string[]> (e.g. type: Array as PropType<string[]>) so
modelValue, addTag (which calls value.split(props.separator)), and all tag-list
mutations are statically consistent with string[].
|
|
||
| .@{tag-input-prefix-cls} { | ||
| .inject-TagInput-vars(); | ||
| border: 1px solid #ccc; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion | 🟠 Major
Use CSS variable for border color.
The hardcoded border color #ccc breaks theming consistency. Consider using a CSS variable like var(--tv-TagInput-border-color) to match the theming pattern used elsewhere in this file.
🔎 Proposed fix
- border: 1px solid #ccc;
+ border: 1px solid var(--tv-TagInput-border-color);📝 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.
| border: 1px solid #ccc; | |
| border: 1px solid var(--tv-TagInput-border-color); |
🤖 Prompt for AI Agents
In @packages/theme/src/tag-input/index.less around line 9, Replace the hardcoded
border color in the TagInput styles with the theme CSS variable: locate the rule
that sets "border: 1px solid #ccc" (in the tag input component styles, e.g., the
TagInput root selector) and change it to use a variable like
"var(--tv-TagInput-border-color)" with a sensible fallback (e.g.,
var(--tv-TagInput-border-color, #ccc)) so theming is respected while preserving
default behavior.
PR
PR Checklist
Please check if your PR fulfills the following requirements:
PR Type
What kind of change does this PR introduce?
What is the current behavior?
Issue Number: N/A
What is the new behavior?
Does this PR introduce a breaking change?
Other information
Summary by CodeRabbit
New Features
Documentation
✏️ Tip: You can customize this high-level summary in your review settings.