Skip to content
Open
Show file tree
Hide file tree
Changes from 3 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
14 changes: 14 additions & 0 deletions examples/sites/demos/apis/color-picker.js
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,20 @@ export default {
meta: {
stable: '3.27.0'
}
},
{
name: 'placement',
type: 'top | bottom',
defaultValue: 'bottom',
desc: {
'zh-CN': '定义颜色选择器的弹出位置',
'en-US': 'Define the pop-up position of the color selector'
},
mode: ['pc'],
pcDemo: 'placement',
meta: {
stable: '3.27.0'
Comment thread
ourcx marked this conversation as resolved.
Outdated
}
}
],
events: [
Expand Down
11 changes: 11 additions & 0 deletions examples/sites/demos/pc/app/color-picker/placement.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { test, expect } from '@playwright/test'


test('placement', async ({ page }) => {
// 测试
page.on('pageerror', (exception) => expect(exception).toBeNull())
await page.locator('#placement').selectOption('top')
await expect(page.locator('.t-color-picker__panel')).toHaveCSS('top', '0px')
await page.locator('#placement').selectOption('bottom')
await expect(page.locator('.t-color-picker__panel')).toHaveCSS('top', 'auto')
Comment on lines +4 to +10

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check how other color-picker e2e tests are structured (page.goto, selectors)
find examples/sites/demos/pc/app/color-picker -name "*.spec.ts" -type f | head -10

Repository: opentiny/tiny-vue

Length of output: 655


🏁 Script executed:

#!/bin/bash
# Look at a few other spec files for structure
fd 'spec.ts' examples/sites/demos/pc/app/color-picker --exec head -15 {}

Repository: opentiny/tiny-vue

Length of output: 5827


🏁 Script executed:

#!/bin/bash
# Check what CSS classes the color-picker actually uses
rg 't-color-picker__panel' packages/ --type ts --type vue

Repository: opentiny/tiny-vue

Length of output: 88


🏁 Script executed:

#!/bin/bash
# Check for tiny-color-picker__dropdown
rg 'tiny-color-picker__dropdown' packages/ --type ts --type vue

Repository: opentiny/tiny-vue

Length of output: 88


🏁 Script executed:

#!/bin/bash
# Also search more broadly for color-picker panel classes
rg 'color-picker.*panel|__panel' packages/vue/src/color-picker/ -t ts -t vue

Repository: opentiny/tiny-vue

Length of output: 88


🏁 Script executed:

#!/bin/bash
# Search for color-picker panel class definitions in source files
rg 'color-picker.*panel' packages/vue/src/color-picker/ -A 2 -B 2

Repository: opentiny/tiny-vue

Length of output: 43


🏁 Script executed:

#!/bin/bash
# Look for all CSS classes in the color-picker component files
find packages/vue/src/color-picker -type f \( -name "*.ts" -o -name "*.vue" \) | xargs grep -h "class=" | head -20

Repository: opentiny/tiny-vue

Length of output: 218


🏁 Script executed:

#!/bin/bash
# Check the placement.vue demo file to see what it actually contains
cat examples/sites/demos/pc/app/color-picker/placement.vue 2>/dev/null || echo "File not found"

Repository: opentiny/tiny-vue

Length of output: 444


🏁 Script executed:

#!/bin/bash
# Look for any reference to placement-related elements
rg '#placement' examples/sites/demos/pc/app/color-picker/

Repository: opentiny/tiny-vue

Length of output: 295


Test is missing page navigation and uses incorrect selectors—it will fail immediately.

  1. Missing page.goto(): The test never navigates to the demo page. All other color-picker spec files include await page.goto('color-picker#placement'), but this test omits it entirely. Without navigation, the test runs against a blank page.

  2. Wrong CSS class: The test asserts on .t-color-picker__panel, which doesn't exist in the codebase. The actual color-picker uses .tiny-color-picker__dropdown with placement modifiers (e.g., .tiny-color-picker__dropdown--top, .tiny-color-picker__dropdown--bottom).

  3. Missing #placement select element: The placement.vue demo contains no <select id="placement"> element. It only has two hardcoded <tiny-color-picker> components. The selectOption() calls on lines 7 and 9 will fail because the element doesn't exist.

Add await page.goto('color-picker#placement') at the start and update selectors to match the actual component structure.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@examples/sites/demos/pc/app/color-picker/placement.spec.ts` around lines 4 -
10, The test "placement" fails because it never navigates to the demo and uses
non-existent selectors and controls; fix it by adding await
page.goto('color-picker#placement') at the start of the test, remove any
interaction with the non-existent '#placement' select, and instead open the
relevant tiny-color-picker instances (e.g., use
page.locator('tiny-color-picker').nth(0) / .nth(1) and click their toggle to
show the dropdown) then assert on the real dropdown selector
'.tiny-color-picker__dropdown' and its placement modifier classes (check for
'.tiny-color-picker__dropdown--top' for the first picker and
'.tiny-color-picker__dropdown--bottom' for the second) rather than expecting
'.t-color-picker__panel' or calling selectOption on '#placement'.

})
27 changes: 27 additions & 0 deletions examples/sites/demos/pc/app/color-picker/placement.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
<template>
<div>
<tiny-color-picker v-model="color" placement="top" />
<tiny-color-picker v-model="color"/>
</div>
</template>
Comment on lines +1 to +6

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

# Search for placement-related files in the color-picker demo directory
fd 'placement' examples/sites/demos/pc/app/color-picker/

Repository: opentiny/tiny-vue

Length of output: 174


🏁 Script executed:

# Find the placement test file
fd 'placement.spec' examples/

Repository: opentiny/tiny-vue

Length of output: 288


🏁 Script executed:

# Check the structure of the color-picker demo directory
ls -la examples/sites/demos/pc/app/color-picker/ 2>/dev/null | head -20

Repository: opentiny/tiny-vue

Length of output: 1408


🏁 Script executed:

# Read the placement test file to see what it expects
cat -n examples/sites/demos/pc/app/color-picker/placement.spec.ts

Repository: opentiny/tiny-vue

Length of output: 591


🏁 Script executed:

# Read the placement demo file
cat -n examples/sites/demos/pc/app/color-picker/placement.vue

Repository: opentiny/tiny-vue

Length of output: 633


Demo is missing required <select> element for E2E test.

The test placement.spec.ts attempts to interact with a #placement select element at lines 7 and 9 to toggle placement values, but the demo provides no such element. The demo statically sets placement="top" on one picker and uses the default on the other, with no mechanism for dynamic toggling.

Add a <select id="placement"> with options for dynamic placement binding, or update the test to match the static demo implementation.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@examples/sites/demos/pc/app/color-picker/placement.vue` around lines 1 - 6,
The demo is missing the dynamic <select id="placement"> the E2E test expects;
add a select bound to a placement data property and use that property for the
tiny-color-picker placement prop instead of hard-coding "top". Specifically, in
the component that currently renders <tiny-color-picker v-model="color"
placement="top" /> and <tiny-color-picker v-model="color" />, replace the static
placement with a reactive placement variable (e.g., placement) and add a <select
id="placement"> with options like "top", "bottom", "left", "right" bound via
v-model="placement"; ensure the component's setup/data defines and exports the
placement variable so the pickers use :placement="placement" and the test can
toggle the select.


<script>
import { TinyColorPicker } from '@opentiny/vue'

export default {
components: {
TinyColorPicker
},
data() {
return {
color: '#66ccff'
}
}
}
</script>

<style scoped>
.demo-margin {
margin: 8px 0;
}
</style>
13 changes: 13 additions & 0 deletions examples/sites/demos/pc/app/color-picker/webdoc/color-picker.js
Original file line number Diff line number Diff line change
Expand Up @@ -241,6 +241,19 @@ export default {
},
apis: ['color-mode'],
demos: ['color-mode']
},
{
id: 'placement',
name: '显示位置',
support: {
value: true
},
description: '通过 placement 属性来定义颜色选择器的显示位置',
cloud: {
value: false
},
apis: ['placement'],
demos: ['placement']
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.
]
}
8 changes: 7 additions & 1 deletion packages/renderless/src/color-picker/vue.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,11 +63,17 @@ export const renderless = (props, ctx: ISharedRenderlessParamHooks, { emit }: IS
)
const changeVisible = toggleVisible(isShow)
const { onConfirm, onCancel } = useEvent(state, emit, changeVisible, color)

// 根据 placement 决定动画方向
const transitionName = () => {
return props.placement === 'top' ? 'tiny-zoom-in-bottom' : 'tiny-zoom-in-top'
}
const api = {
state,
changeVisible,
onConfirm,
onCancel
onCancel,
transitionName
Comment on lines +68 to +76

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== color-picker renderless module =="
sed -n '1,120p' packages/renderless/src/color-picker/vue.ts

echo
echo "== color-picker component wiring =="
sed -n '1,220p' packages/vue/src/color-picker/src/pc.vue

echo
echo "== usages of transitionName in the color-picker surface =="
rg -n -C2 '\btransitionName\b' \
  packages/renderless/src/color-picker/vue.ts \
  packages/vue/src/color-picker/src/pc.vue \
  packages/vue/src/color-picker/src/index.ts

Repository: opentiny/tiny-vue

Length of output: 5557


🏁 Script executed:

# Search for other renderless modules to understand the api array pattern
fd -t f 'vue\.ts$' packages/renderless/src --exec head -20 {} \; 2>/dev/null | head -100

# Look for setup function usage patterns across renderless modules
rg -A5 'setup\(\{.*renderless.*api' packages/vue/src --type vue --max-count 5

Repository: opentiny/tiny-vue

Length of output: 2814


🏁 Script executed:

# Check if there are other renderless modules with api arrays and returned methods
rg 'export const api = \[' packages/renderless/src

Repository: opentiny/tiny-vue

Length of output: 19504


🏁 Script executed:

# Look for how setup function is defined or documented
fd -t f '(setup|common)' packages/ -name '*.ts' -name '*.vue' | head -20

Repository: opentiny/tiny-vue

Length of output: 291


🏁 Script executed:

# Find the setup function implementation
fd -type f -name '*.ts' -o -name '*.js' packages/vue-common | head -20

Repository: opentiny/tiny-vue

Length of output: 231


🏁 Script executed:

# Search for setup function definition
rg -l 'export.*setup' packages/vue-common --type ts --type js 2>/dev/null | head -5

Repository: opentiny/tiny-vue

Length of output: 93


🏁 Script executed:

# Look in the common package
find packages -type d -name '*common*' 2>/dev/null

Repository: opentiny/tiny-vue

Length of output: 211


🏁 Script executed:

# Find setup function definition in vue-common
rg -n 'export.*setup' packages/vue-common/src --max-count 10

Repository: opentiny/tiny-vue

Length of output: 395


🏁 Script executed:

# Read the vue-common index to see what's exported
cat packages/vue-common/src/index.ts

Repository: opentiny/tiny-vue

Length of output: 14503


Add transitionName to the exported renderless API list.

transitionName is defined and returned from the renderless function but is not included in the exported api array at Line 6. The setup function filters renderless properties based on this array, so transitionName will not be exposed to the component instance and the :name="transitionName" binding in the template will be undefined.

Suggested fix
-export const api = ['state', 'changeVisible', 'onConfirm', 'onCancel', 'onHueUpdate', 'onSVUpdate', 'onColorUpdate']
+export const api = [
+  'state',
+  'changeVisible',
+  'onConfirm',
+  'onCancel',
+  'onHueUpdate',
+  'onSVUpdate',
+  'onColorUpdate',
+  'transitionName'
+]
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/renderless/src/color-picker/vue.ts` around lines 68 - 76, The
renderless function defines transitionName but it isn't exported in the
renderless API list, so add "transitionName" to the exported api array/object
(the same structure that contains state, changeVisible, onConfirm, onCancel) so
the setup/filter logic will expose transitionName to the component instance and
the template :name binding will receive it.

}
return api
}
16 changes: 16 additions & 0 deletions packages/theme/src/color-picker/index.less
Original file line number Diff line number Diff line change
Expand Up @@ -66,4 +66,20 @@
&--mini &__inner {
border-radius: 2px;
}

&__dropdown {
position: absolute;
}

&__dropdown--bottom {
top: 100%;
left: 0;
margin-top: 8px;
}

&__dropdown--top {
bottom: 100%;
left: 0;
margin-bottom: 8px;
}
}
7 changes: 7 additions & 0 deletions packages/vue/src/color-picker/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,13 @@ export default defineComponent({
validator(val: string) {
return ['monochrome', 'linear-gradient'].includes(val)
}
},
placement:{
type: String,
default: 'bottom',
validator(val: string) {
return ['top', 'bottom'].includes(val)
}
}
},
setup(props, context) {
Expand Down
60 changes: 33 additions & 27 deletions packages/vue/src/color-picker/src/pc.vue
Original file line number Diff line number Diff line change
@@ -1,50 +1,53 @@
<template>
<div
:class="[
{
'tiny-color-picker': true
},
{ 'tiny-color-picker': true },
state.size ? 'tiny-color-picker--' + state.size : ''
]"
@click="() => changeVisible(!state.isShow)"
>
<div
class="tiny-color-picker__inner"
:style="{
background: state.hex ?? ''
}"
:style="{ background: state.hex ?? '' }"
>
<IconChevronDown />
</div>
<Transition name="tiny-zoom-in-top">
<color-select
@confirm="onConfirm"
@cancel="onCancel"
v-model="state.hex"
:visible="state.isShow"
:alpha="alpha"
:predefine="state.predefineStack"
:history="state.stack"
:format="format"
:style="{
'min-width': '330px'
}"
:color-mode="$props.colorMode"
:enable-history="state.enableHistory"
:enable-predefine-color="state.enablePredefineColor"
/>

<!-- 根据 placement 设置弹出位置 -->
<Transition :name="transitionName">

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

# First, let's look at the specific file and context around line 17
head -30 packages/vue/src/color-picker/src/pc.vue

Repository: opentiny/tiny-vue

Length of output: 865


🏁 Script executed:

# Find the renderless color-picker implementation
find . -path "*/renderless/src/color-picker*" -name "*.ts" | head -5

Repository: opentiny/tiny-vue

Length of output: 267


🏁 Script executed:

# Search for transitionName definition in renderless
rg -n "transitionName\s*=" packages/renderless/src/ -B2 -A2

Repository: opentiny/tiny-vue

Length of output: 447


🏁 Script executed:

# Check what `@opentiny/vue-common` setup returns
rg -n "export.*setup|return.*{" packages/vue-common/src/ -A3 -B1 | head -50

Repository: opentiny/tiny-vue

Length of output: 170


🏁 Script executed:

# See how other color-picker related files use Transition
rg -n "Transition.*:name" packages/vue/src/color-picker/ -B1 -A1

Repository: opentiny/tiny-vue

Length of output: 276


🏁 Script executed:

# Let's look at the full renderless setup function to see how transitionName is returned
cat -n packages/renderless/src/color-picker/vue.ts | sed -n '1,120p'

Repository: opentiny/tiny-vue

Length of output: 2968


🏁 Script executed:

# Check the full pc.vue file to see the setup integration
cat -n packages/vue/src/color-picker/src/pc.vue

Repository: opentiny/tiny-vue

Length of output: 2919


🏁 Script executed:

# Check how `@opentiny/vue-common` handles setup
rg -n "setup\s*\(" packages/vue-common/src/ -A 10 | head -80

Repository: opentiny/tiny-vue

Length of output: 1241


🏁 Script executed:

# Let's check if there are any computed or unwrap mechanisms for functions in the renderless
rg -n "computed\|unref\|isRef" packages/renderless/src/color-picker/vue.ts

Repository: opentiny/tiny-vue

Length of output: 43


🏁 Script executed:

# Let's look at the `@opentiny/vue-common` setup function implementation
find . -path "*/vue-common/src*" -name "*.ts" -o -name "*.js" | grep -v node_modules | head -10

Repository: opentiny/tiny-vue

Length of output: 431


🏁 Script executed:

# Search for the setup function that's imported in pc.vue
rg -n "export.*setup\|function setup" packages/vue-common/src/ -B2 -A15 | head -100

Repository: opentiny/tiny-vue

Length of output: 43


🏁 Script executed:

# Let's check if there's any special handling in how renderless properties are exposed
rg -n "transitionName\|computed" packages/vue-common/src/ -B2 -A2 | head -80

Repository: opentiny/tiny-vue

Length of output: 43


🏁 Script executed:

# Let's test by looking at how similar components handle functions from renderless
rg -n "Transition.*:name" packages/vue/src/ -l | head -5

Repository: opentiny/tiny-vue

Length of output: 101


🏁 Script executed:

# Check the actual vue-common setup function more carefully
cat -n packages/vue-common/src/index.ts | head -100

Repository: opentiny/tiny-vue

Length of output: 3655


🏁 Script executed:

# Continue reading the setup function
cat -n packages/vue-common/src/index.ts | sed -n '100,200p'

Repository: opentiny/tiny-vue

Length of output: 3713


🏁 Script executed:

# Let's look for the setup function specifically
rg -n "^export.*setup|^const setup|^function setup" packages/vue-common/src/index.ts -A 30

Repository: opentiny/tiny-vue

Length of output: 3122


🏁 Script executed:

# Let's search in all files within vue-common
find packages/vue-common/src -name "*.ts" -exec grep -l "export.*setup" {} \;

Repository: opentiny/tiny-vue

Length of output: 93


🏁 Script executed:

# Let's check if there's a setup function that wraps renderless
rg -rn "export.*setup\s*=" packages/vue-common/src/ -A 20 | head -100

Repository: opentiny/tiny-vue

Length of output: 2855


transitionName is a function, but it's bound as a value — the transition name will not resolve to a string.

In the renderless layer, transitionName is defined as () => { return props.placement === 'top' ? 'tiny-zoom-in-bottom' : 'tiny-zoom-in-top' }. Binding it as :name="transitionName" passes the function reference itself to <Transition>, not its return value. Vue's <Transition> expects a string for name, not a function.

Either invoke it in the template or convert it to a computed property in the renderless layer.

Option A: Invoke in template
-    <Transition :name="transitionName">
+    <Transition :name="transitionName()">
Option B: Convert to computed in renderless (preferred)

In packages/renderless/src/color-picker/vue.ts:

-  const transitionName = () => {
-    return props.placement === 'top' ? 'tiny-zoom-in-bottom' : 'tiny-zoom-in-top'
-  }
+  const transitionName = ctx.computed(() => {
+    return props.placement === 'top' ? 'tiny-zoom-in-bottom' : 'tiny-zoom-in-top'
+  })
📝 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
<Transition :name="transitionName">
<Transition :name="transitionName()">
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/vue/src/color-picker/src/pc.vue` at line 17, transitionName is
currently a function but is being passed as a function reference to
<Transition>, causing the name prop to be a function instead of a string; fix by
returning a string instead of a function: either invoke transitionName in the
template (change :name="transitionName" to :name="transitionName()" in pc.vue)
or (preferred) change the renderless implementation in
renderless/src/color-picker/vue.ts to expose transitionName as a
computed/string-valued property (e.g., make transitionName a computed that
returns props.placement === 'top' ? 'tiny-zoom-in-bottom' : 'tiny-zoom-in-top')
so the existing :name="transitionName" receives a string.

<div
class="tiny-color-picker__dropdown"
:class="`tiny-color-picker__dropdown--${placement}`"
v-show="state.isShow"
>
<color-select
@confirm="onConfirm"
@cancel="onCancel"
v-model="state.hex"
:visible="state.isShow"
:alpha="alpha"
:predefine="state.predefineStack"
:history="state.stack"
:format="format"
:style="{ 'min-width': '330px' }"
:color-mode="$props.colorMode"
:enable-history="state.enableHistory"
:enable-predefine-color="state.enablePredefineColor"
/>
</div>
</Transition>
</div>
</template>

<script>
import { renderless, api } from '@opentiny/vue-renderless/color-picker/vue'
import { props, setup, defineComponent } from '@opentiny/vue-common'
import { IconChevronDown } from '@opentiny/vue-icon'
import colorSelect from '@opentiny/vue-color-select-panel'
import '@opentiny/vue-theme/color-picker/index.less'

export default defineComponent({
name: 'TinyColorPicker',
emits: ['update:modelValue', 'confirm', 'cancel'],
props: [
...props,
Expand All @@ -57,14 +60,17 @@ export default defineComponent({
'format',
'enableHistory',
'enablePredefineColor',
'colorMode'
'colorMode',
'placement'
],
components: {
IconChevronDown: IconChevronDown(),
ColorSelect: colorSelect
},
setup(props, context) {
return setup({ props, context, renderless, api })
const state = setup({ props, context, renderless, api })
return { ...state }
Comment thread
coderabbitai[bot] marked this conversation as resolved.
}
})
</script>

Loading