Skip to content

Conversation

@GaoNeng-wWw
Copy link
Collaborator

@GaoNeng-wWw GaoNeng-wWw commented Sep 24, 2025

PR

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: #2515

What is the new behavior?

linear-gradient supported.

1

Does this PR introduce a breaking change?

  • Yes
  • No

Other information

  • I/O (input and output)
  • alpha
  • predefine
  • history

Summary by CodeRabbit

  • New Features

    • Color Select Panel: linear-gradient mode — draggable color stops, angle control, alpha support, history and predefined-color quick-picks, updated gradient preview and persistence; new demo components (Options & Composition API).
  • Documentation

    • Demo registry updated; new demo entries and API prop documentation for colorMode.
  • Style

    • Added UI styles for gradient bar, thumbs, heart markers and angle control.
  • Tests

    • E2E tests validating gradient interactions, history and predefined-color flows.
  • Chores

    • Added gradient parsing dependency and color-name map.

- [x] I/O

- [ ] alpha

- [ ] predefine

- [ ] history
@coderabbitai
Copy link

coderabbitai bot commented Sep 24, 2025

Walkthrough

Adds linear-gradient support to color-select-panel: new renderless linear-gradient module, color-points utilities and context, gradient parsing and types, refactors hue/SV/alpha to use shared context, Vue components and styles for gradient UI, demos, tests, and package dependency updates.

Changes

Cohort / File(s) Summary
Demos & Tests
examples/sites/demos/pc/app/color-select-panel/linear-gradient.vue, examples/sites/demos/pc/app/color-select-panel/linear-gradient-composition-api.vue, examples/sites/demos/pc/app/color-select-panel/linear-gradient.spec.ts, examples/sites/demos/pc/app/color-select-panel/webdoc/color-select-panel.js
Add linear-gradient demo SFCs, composition-demo, Playwright tests, and register demo metadata in webdoc.
Renderless core & API
packages/renderless/src/color-select-panel/index.ts, packages/renderless/src/color-select-panel/vue.ts, packages/renderless/types/color-select-panel.type.ts
Add gradient-aware context, colorPoints, new public helpers (panelRgb, updateModelValue, trigger*), new initApi/initState signatures, expose ctx/isLinearGradient/linearGradient, and add gradient-related types and parse type.
Linear-gradient renderless module
packages/renderless/src/color-select-panel/linear-gradient/index.ts, packages/renderless/src/color-select-panel/linear-gradient/vue.ts
New renderless linear-gradient module (useLinearGradient) and wrapper exposing onClickBar, onThumbMouseDown, toString, and state for gradient bar.
Color-points & utils
packages/renderless/src/color-select-panel/utils/color-points.ts, packages/renderless/src/color-select-panel/utils/color-map.ts, packages/renderless/src/color-select-panel/utils/color.ts, packages/renderless/src/color-select-panel/utils/context.ts
Add ColorPoint class and useColorPoints hook, CSS color-name → hex map, Color class implements IColor and parsing tweak, and create/useContext utilities for panel context.
Selectors refactor (context integration)
packages/renderless/src/color-select-panel/hue-select/index.ts, packages/renderless/src/color-select-panel/hue-select/vue.ts, packages/renderless/src/color-select-panel/sv-select/index.ts, packages/renderless/src/color-select-panel/sv-select/vue.ts, packages/renderless/src/color-select-panel/alpha-select/index.ts, packages/renderless/src/color-select-panel/alpha-select/vue.ts
Refactor hue/SV/alpha to consume ColorPanelContext (ctx.activeColor, ctx.colorPoints), add getLeft/onClickBar helpers, update signatures to accept/use ctx, and route color mutations through ctx.
Vue components & package changes
packages/vue/src/color-select-panel/src/pc.vue, packages/vue/src/color-select-panel/src/components/hue-select.vue, packages/vue/src/color-select-panel/src/components/linear-gradient.vue, packages/vue/src/color-select-panel/src/components/alpha-select.vue, packages/vue/src/color-select-panel/package.json
Add colorMode prop and gradient-parser integration in pc.vue, register new linear-gradient component in hue-select, update component bindings to use state.ctx.activeColor, add linear-gradient SFC, and add gradient-parser dependency and typings.
Theme & styles
packages/theme/src/color-select-panel/index.less, packages/theme/src/color-select-panel/vars.less, packages/theme/src/dark-theme-index.less
Add styles for linear-gradient bar, thumbs, heart, and deg control; minor newline adjustments.
API docs
examples/sites/demos/apis/color-select-panel.js
Add colorMode public prop documentation for color-select-panel API with pc demo linkage.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  actor User
  participant PC as pc.vue
  participant RL as Renderless(index.ts)
  participant CTX as ColorPanelContext
  participant CP as ColorPoints
  participant LG as LinearGradient
  participant H as Hue
  participant SV as SV
  participant A as Alpha

  User->>PC: open panel (colorMode)
  PC->>RL: initState(props, hooks, utils, ext)
  RL->>CTX: createContext(ctx)
  alt colorMode == linear-gradient
    RL->>CP: parse modelValue -> create colorPoints
    CP-->>CTX: ctx.colorPoints + ctx.activeColor + ctx.deg
    PC->>LG: mount linear-gradient
  else
    RL->>CTX: set ctx.activeColor from modelValue
  end

  User->>H: drag/click hue
  H->>CTX: set hue on ctx.activeColor
  User->>SV: drag SV
  SV->>CTX: set sat/value on ctx.activeColor
  User->>A: drag alpha
  A->>CTX: set alpha on ctx.activeColor

  User->>LG: click gradient bar
  LG->>CP: getRelativePos -> addPoint()
  CP->>CTX: setActivePoint(newPoint)
  User->>LG: drag thumb
  LG->>CP: onDrag -> update cursorLeft
  CP->>CTX: points updated -> toString(points, deg)
  CTX-->>RL: watchers react
  RL->>PC: emit update:modelValue / colorUpdate

  User->>PC: confirm
  PC->>RL: submitValue()
  RL->>PC: emit update:modelValue + confirm
Loading
sequenceDiagram
  autonumber
  actor User
  participant LG as LinearGradient
  participant CP as ColorPoints
  participant CTX as Context

  User->>LG: click bar at X
  LG->>CP: getRelativePos(X) -> create ColorPoint(color, pos)
  CP->>CTX: addPoint & setActivePoint
  User->>LG: drag thumb
  LG->>CP: onDrag -> clamp pos -> update cursorLeft
  CP->>CTX: update points
  CP->>LG: toString(points, deg)
  LG->>CTX: update linearGradientValue
Loading

Estimated code review effort

🎯 5 (Critical) | ⏱️ ~120 minutes

Poem

I nibble at the gradient line,
Add a point, then drag to shine,
Hearts and thumbs in rainbow play,
Context hops and shows the way,
I confirm — a colorful bouquet. 🐇🎨

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 succinctly follows the conventional format and clearly indicates that this PR adds linear-gradient support to the color-select-panel, directly reflecting the primary enhancement in the changeset. It avoids extraneous details and noise, making it immediately clear to reviewers what main feature is being introduced.
Docstring Coverage ✅ Passed No functions found in the changes. Docstring coverage check skipped.
✨ Finishing touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment

📜 Recent review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between b4b57f3 and dfd6e0b.

📒 Files selected for processing (1)
  • examples/sites/demos/apis/color-select-panel.js (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • examples/sites/demos/apis/color-select-panel.js
⏰ 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)

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
🧪 Early access (Sonnet 4.5): enabled

We are currently testing the Sonnet 4.5 model, which is expected to improve code review quality. However, this model may lead to increased noise levels in the review comments. Please disable the early access features if the noise level causes any inconvenience.

Note:

  • Public repositories are always opted into early access features.
  • You can enable or disable early access features from the CodeRabbit UI or by updating the CodeRabbit configuration file.

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

@GaoNeng-wWw GaoNeng-wWw marked this pull request as draft September 24, 2025 03:06
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: 26

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (3)
packages/renderless/src/color-select-panel/utils/color.ts (2)

204-211: Interface compliance: relax compare signature to IColor (current "this" is too narrow)

Using this makes the method signature stricter than IColor.compare(color: IColor). Switch to IColor and read via the public get(...).

-  compare(color: this) {
-    return (
-      Math.abs(color._hue - this._hue) < 2 &&
-      Math.abs(color._sat - this._sat) < 1 &&
-      Math.abs(color._value - this._value) < 1 &&
-      Math.abs(color._alpha - this._alpha) < 1
-    )
-  }
+  compare(color: IColor) {
+    return (
+      Math.abs(color.get('hue') - this._hue) < 2 &&
+      Math.abs(color.get('sat') - this._sat) < 1 &&
+      Math.abs(color.get('value') - this._value) < 1 &&
+      Math.abs(color.get('alpha') - this._alpha) < 1
+    )
+  }

285-307: onHsl parsing has two bugs (regex flags and "parent" typo) — breaks HS(L/A) inputs

  • The regex has literal “gm” in the pattern instead of flags.
  • if (parent.length >= 3) references an undefined identifier.

Fix both.

-  onHsl(value: string) {
-    const parts = value
-      .replace(/hsla|hsl\(|\)gm/, '')
-      .split(/\s|,/g)
+  onHsl(value: string) {
+    const parts = value
+      .replace(/hsla?|\(|\)/gi, '')
+      .split(/\s|,/g)
       .filter((val) => val)
       .map((val, idx) => {
         return idx > 2 ? Number.parseFloat(val) : Number.parseInt(val, 10)
       })
 
     if (parts.length === 4) {
       this._alpha = Number.parseFloat(String(parts[3])) * 100
     } else {
       this._alpha = 100
     }
-    if (parent.length >= 3) {
+    if (parts.length >= 3) {
       const { h, s, v } = hsl2hsv({
         hue: parts[0],
         sat: parts[1],
         light: parts[2]
       })
       this.fromHSV({ h, s, v })
     }
   }
packages/renderless/src/color-select-panel/hue-select/index.ts (1)

66-75: Fix hue-to-thumb mapping formula (left position)

The current formula is not the inverse of onDrag; thumb misaligns.

Apply:

   const getThumbTop = () => {
     if (!thumb.value) {
       return 0
     }
-    const hue = ctx.activeColor.value.color.get('hue')
-    if (!bar.value) {
-      return 0
-    }
-    return (hue * (bar.value.offsetWidth - thumb.value.offsetWidth / 2)) / 360
+    const hue = ctx.activeColor.value.color.get('hue')
+    const barEl = bar.value
+    const thumbEl = thumb.value
+    if (!barEl) return 0
+    return (hue / 360) * (barEl.offsetWidth - thumbEl.offsetWidth) + thumbEl.offsetWidth / 2
   }
🧹 Nitpick comments (16)
packages/renderless/src/color-select-panel/utils/color.ts (1)

179-185: Harden get(...) against unknown props

this[\_${prop}`]` can yield undefined for invalid keys, violating the declared return type. Add a fallback.

   get(prop: string) {
     if (prop === 'alpha') {
       return Math.floor(this._alpha)
     }
-    return this[`_${prop}`]
+    const v = (this as any)[`_${prop}`]
+    return typeof v === 'number' ? v : 0
   }
packages/renderless/src/color-select-panel/utils/color-map.ts (1)

1-150: Make the map immutable (and typed) with "as const"

Prevents accidental mutation and preserves literal types.

-export default {
+export default {
   'black': '#000000',
   'silver': '#c0c0c0',
   ...
-  'yellowgreen': '#9acd32'
-}
+  'yellowgreen': '#9acd32'
+} as const
packages/renderless/src/color-select-panel/linear-gradient/vue.ts (2)

73-84: Prefer currentTarget for the thumb element

event.target may be a nested child. Use currentTarget.

-    const el = event.target as HTMLElement
+    const el = (event as any).currentTarget as HTMLElement || (event.target as HTMLElement)

98-109: Rename local toString to avoid shadowing and lint error; also recompute on angle changes

Address Biome’s warning and ensure updates when deg changes.

-  const toString = () => {
+  const linearGradientToString = () => {
     const colors = context.colorPoints.value
@@
-    return `linear-gradient(${context.deg.value}deg, ${colors})`
+    return `linear-gradient(${context.deg.value}deg, ${colors})`
   }
   watch(
     context.colorPoints,
     () => {
-      context.linearGardientValue.value = toString()
+      context.linearGardientValue.value = linearGradientToString()
     },
     { deep: true, immediate: true }
   )
+  watch(context.deg, () => {
+    context.linearGardientValue.value = linearGradientToString()
+  })

Also applies to: 110-116

packages/renderless/types/color-select-panel.type.ts (2)

22-26: Doc correction: HSL sat/light range

Comments say 0–1 but the implementation takes 0–100 (then divides by 100). Align the comment to avoid confusion.

 export interface HSLColor {
   hue: number // 色调 (0-360)
-  sat: number // 饱和度 (0-1)
-  light: number // 亮度 (0-1)
+  sat: number // 饱和度 (0-100)
+  light: number // 亮度 (0-100)
 }

109-113: Naming typo: IColorSelectPanelAlphProps → IColorSelectPanelAlphaProps

Public type name; fix if still unused externally.

-export interface IColorSelectPanelAlphProps<C> {
+export interface IColorSelectPanelAlphaProps<C> {
   color: C
 }
packages/theme/src/color-select-panel/index.less (1)

178-205: Don’t hardcode red; use a themable or neutral fallback

Hardcoding background: red will clash with themes and momentarily show a red bar before JS sets the actual gradient.

Use a themed var with a safe fallback, or remove the background to rely on runtime style:

   &__linear-gradient {
     position: relative;
     width: 85%;
     height: 12px;
     border-radius: 12px;
-    background: red;
+    background: var(--tv-ColorSelectPanel-linear-gradient-bg, transparent);

Consider adding this var to vars.less if you want a design-approved fallback.

packages/vue/src/color-select-panel/src/components/linear-gradient.vue (1)

9-20: Add touch support for mobile interactions

Thumbs only bind mousedown; add touchstart for parity.

-      @mousedown="(event) => onThumbMouseDown(event, point)"
+      @mousedown="(event) => onThumbMouseDown(event, point)"
+      @touchstart.stop="(event) => onThumbMouseDown(event, point)"

Optionally add @touchstart to the bar as well, delegating to onClickBar or a dedicated handler.

examples/sites/demos/pc/app/color-select-panel/linear-gradient.vue (1)

27-27: Consider using a more realistic gradient for the demo.

The initial gradient value appears to be a placeholder. Consider using a more visually appealing gradient that better demonstrates the feature's capabilities.

-      color: 'linear-gradient(120deg, #66ccffff 50%, #f48fa2 100%)',
+      color: 'linear-gradient(90deg, #667eea 0%, #764ba2 100%)',
packages/renderless/src/color-select-panel/alpha-select/index.ts (1)

60-63: Simplify the conditional check.

The nested conditional check can be simplified for better readability.

-    if (ctx.activeColor && ctx.activeColor.value) {
+    if (ctx.activeColor?.value) {
packages/renderless/src/color-select-panel/utils/color-points.ts (2)

63-65: Address TODO comments or create tracking issues.

The TODO comments indicate incomplete functionality for gradient value updates and SV creation. These should be addressed before merging.

Would you like me to help implement the missing functionality for updating the linear gradient value and creating the linear-gradient SV component, or create tracking issues for these tasks?


53-57: Add null check for wrapper element.

The function assumes wrapper.value is not null on line 54 with the ! assertion, but should handle the null case gracefully.

  const getRelativePos = (wrapper: IColorSelectPanelRef<HTMLElement | undefined>, point: IColorPoint) => {
-    const wrapperEl = wrapper.value!
+    const wrapperEl = wrapper.value
+    if (!wrapperEl) {
+      return '0'
+    }
    const rect = wrapperEl.getBoundingClientRect()
    return ((point.cursorLeft / rect.width) * 100).toFixed(0)
  }
packages/renderless/src/color-select-panel/sv-select/index.ts (1)

62-66: Avoid non‑null assertion on wrapper; add null guard

Prevents runtime errors if the ref isn’t ready.

Apply:

-  return (event: MouseEvent | TouchEvent) => {
-    const el = wrapper.value!
-    const rect = el.getBoundingClientRect()
+  return (event: MouseEvent | TouchEvent) => {
+    const el = wrapper.value
+    if (!el) return
+    const rect = el.getBoundingClientRect()
packages/renderless/src/color-select-panel/hue-select/index.ts (1)

39-46: Use ctx for hueValue to keep sources consistent

Current code mixes props.color with ctx.activeColor; align with ctx.

Apply:

-  const hueValue = computed(() => props.color.get('hue'))
+  const hueValue = computed(() => ctx.activeColor.value.color.get('hue'))
packages/renderless/src/color-select-panel/index.ts (2)

337-350: Guarded gradient init is fine; but consider renaming isGrandient → isGradient

Typo in helper name; readable API matters. Update usages accordingly.

Apply:

-  if (isGrandient(props.modelValue)) {
+  if (isGradient(props.modelValue)) {

And rename definition and other call sites in this file:

-const isGrandient = (val: unknown) => {
+const isGradient = (val: unknown) => {
-  if (!isGrandient(val)) {
+  if (!isGradient(val)) {

192-197: Nit: function name typo (isGrandient)

Spelling nit; see separate comment for optional rename.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ec678a3 and c734480.

📒 Files selected for processing (22)
  • examples/sites/demos/pc/app/color-select-panel/linear-gradient.vue (1 hunks)
  • examples/sites/demos/pc/app/color-select-panel/webdoc/color-select-panel.js (1 hunks)
  • packages/renderless/src/color-select-panel/alpha-select/index.ts (5 hunks)
  • packages/renderless/src/color-select-panel/alpha-select/vue.ts (3 hunks)
  • packages/renderless/src/color-select-panel/hue-select/index.ts (3 hunks)
  • packages/renderless/src/color-select-panel/hue-select/vue.ts (2 hunks)
  • packages/renderless/src/color-select-panel/index.ts (7 hunks)
  • packages/renderless/src/color-select-panel/linear-gradient/vue.ts (1 hunks)
  • packages/renderless/src/color-select-panel/sv-select/index.ts (3 hunks)
  • packages/renderless/src/color-select-panel/sv-select/vue.ts (1 hunks)
  • packages/renderless/src/color-select-panel/utils/color-map.ts (1 hunks)
  • packages/renderless/src/color-select-panel/utils/color-points.ts (1 hunks)
  • packages/renderless/src/color-select-panel/utils/color.ts (2 hunks)
  • packages/renderless/src/color-select-panel/utils/context.ts (1 hunks)
  • packages/renderless/src/color-select-panel/vue.ts (3 hunks)
  • packages/renderless/types/color-select-panel.type.ts (3 hunks)
  • packages/theme/src/color-select-panel/index.less (2 hunks)
  • packages/theme/src/dark-theme-index.less (1 hunks)
  • packages/vue/src/color-select-panel/package.json (1 hunks)
  • packages/vue/src/color-select-panel/src/components/hue-select.vue (2 hunks)
  • packages/vue/src/color-select-panel/src/components/linear-gradient.vue (1 hunks)
  • packages/vue/src/color-select-panel/src/pc.vue (4 hunks)
🧰 Additional context used
🧬 Code graph analysis (13)
packages/renderless/src/color-select-panel/utils/color.ts (1)
packages/renderless/types/color-select-panel.type.ts (1)
  • IColor (29-69)
packages/renderless/src/color-select-panel/alpha-select/vue.ts (2)
packages/renderless/src/color-select-panel/utils/context.ts (1)
  • useContext (13-15)
packages/renderless/src/color-select-panel/alpha-select/index.ts (3)
  • initState (12-17)
  • useEvent (19-71)
  • initWatch (73-89)
packages/renderless/src/color-select-panel/hue-select/vue.ts (3)
packages/renderless/src/color-select-panel/hue-select/index.ts (4)
  • initDom (48-54)
  • initState (39-46)
  • useEvent (56-111)
  • useOnClickBar (20-37)
packages/renderless/src/color-select-panel/utils/context.ts (1)
  • useContext (13-15)
packages/renderless/src/color-select-panel/utils/color-points.ts (1)
  • useColorPoints (17-92)
packages/renderless/src/color-select-panel/utils/context.ts (1)
packages/renderless/types/color-select-panel.type.ts (2)
  • IColorSelectPanelMaybeRef (93-93)
  • ColorPanelContext (85-92)
packages/renderless/src/color-select-panel/vue.ts (2)
packages/renderless/types/color-select-panel.type.ts (1)
  • ColorSelectPanelExtends (135-137)
packages/renderless/src/color-select-panel/index.ts (2)
  • initState (307-396)
  • initApi (50-158)
packages/renderless/src/color-select-panel/alpha-select/index.ts (1)
packages/renderless/src/color-select-panel/utils/color.ts (1)
  • Color (157-418)
packages/renderless/src/color-select-panel/linear-gradient/vue.ts (5)
packages/renderless/src/color-select-panel/utils/context.ts (1)
  • useContext (13-15)
packages/renderless/src/color-select-panel/utils/color-points.ts (1)
  • ColorPoint (10-15)
packages/renderless/src/color-select-panel/utils/getClientXY.ts (1)
  • getClientXY (41-58)
packages/renderless/src/color-select-panel/utils/color.ts (1)
  • Color (157-418)
packages/renderless/types/color-select-panel.type.ts (1)
  • IColorPoint (80-83)
packages/renderless/src/color-select-panel/hue-select/index.ts (7)
packages/renderless/types/color-select-panel.type.ts (3)
  • UseColorPointsRet (125-133)
  • IColorSelectPanelHueProps (117-119)
  • ColorPanelContext (85-92)
packages/renderless/src/color-select-panel/utils/color.ts (1)
  • Color (157-418)
packages/renderless/src/color-select-panel/utils/color-points.ts (1)
  • ColorPoint (10-15)
packages/renderless/src/color-select-panel/index.ts (1)
  • initState (307-396)
packages/renderless/src/color-select-panel/sv-select/index.ts (1)
  • initState (14-32)
packages/renderless/src/color-select-panel/utils/context.ts (1)
  • useContext (13-15)
packages/renderless/src/color-select-panel/utils/getClientXY.ts (1)
  • getClientXY (41-58)
packages/renderless/src/color-select-panel/sv-select/vue.ts (2)
packages/renderless/src/color-select-panel/utils/context.ts (1)
  • useContext (13-15)
packages/renderless/src/color-select-panel/sv-select/index.ts (3)
  • initState (14-32)
  • useUpdate (34-53)
  • useDrag (55-87)
packages/renderless/types/color-select-panel.type.ts (2)
packages/renderless/src/color-select-panel/utils/color.ts (1)
  • ColorOptions (151-155)
packages/renderless/src/color-picker/utils/color.ts (1)
  • ColorOptions (149-153)
packages/renderless/src/color-select-panel/utils/color-points.ts (2)
packages/renderless/types/color-select-panel.type.ts (4)
  • IColorPoint (80-83)
  • IColorSelectPanelRef (94-96)
  • IColor (29-69)
  • ColorPanelContext (85-92)
packages/renderless/src/color-select-panel/utils/getClientXY.ts (1)
  • getClientXY (41-58)
packages/renderless/src/color-select-panel/sv-select/index.ts (4)
packages/renderless/src/color-select-panel/index.ts (1)
  • initState (307-396)
packages/renderless/types/color-select-panel.type.ts (3)
  • IColorSelectPanelSVProps (113-115)
  • IColorSelectPanelRef (94-96)
  • ColorPanelContext (85-92)
packages/renderless/src/color-select-panel/utils/color.ts (1)
  • Color (157-418)
packages/renderless/src/color-select-panel/utils/context.ts (1)
  • useContext (13-15)
packages/renderless/src/color-select-panel/index.ts (4)
packages/renderless/src/color-picker/index.ts (1)
  • updateModelValue (4-6)
packages/renderless/types/color-select-panel.type.ts (16)
  • ColorSelectPanelExtends (135-137)
  • DirectionalNode (165-180)
  • AngularNode (182-185)
  • HexNode (193-197)
  • RgbNode (199-203)
  • RgbaNode (205-209)
  • HslNode (211-215)
  • HslaNode (217-221)
  • LiteralNode (187-191)
  • CalcNode (275-278)
  • PercentNode (270-273)
  • EmNode (265-268)
  • PxNode (260-263)
  • ColorStop (280-280)
  • IColorSelectPanelProps (97-107)
  • ColorPanelContext (85-92)
packages/renderless/src/color-select-panel/utils/color-points.ts (1)
  • ColorPoint (10-15)
packages/renderless/src/color-select-panel/utils/context.ts (1)
  • createContext (5-11)
🪛 Biome (2.1.2)
packages/renderless/src/color-select-panel/linear-gradient/vue.ts

[error] 98-98: Do not shadow the global "toString" property.

Consider renaming this variable. It's easy to confuse the origin of variables when they're named after a known global.

(lint/suspicious/noShadowRestrictedNames)

packages/renderless/src/color-select-panel/utils/color-points.ts

[error] 62-62: Do not shadow the global "toString" property.

Consider renaming this variable. It's easy to confuse the origin of variables when they're named after a known global.

(lint/suspicious/noShadowRestrictedNames)

⏰ 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 (20)
packages/theme/src/dark-theme-index.less (1)

1-1: LGTM — confirm gradient tokens/mixins are covered by dark theme

dark-theme-index.less imports only ./base/dark-theme.less, but gradient mixins/vars are defined in packages/theme/src/mixins/common.less and component vars (e.g. packages/theme/src/tag/vars.less, packages/theme/src/skeleton-item/vars.less, packages/theme/src/grid/, packages/theme/src/color-select-panel/index.less); base/dark-theme.less has no gradient definitions — add dark variants or import those files into the dark-theme index so gradient styles are included.

examples/sites/demos/pc/app/color-select-panel/webdoc/color-select-panel.js (1)

19-30: Demo entry addition — LGTM (file exists)

Confirmed examples/sites/demos/pc/app/color-select-panel/linear-gradient.vue is present.

packages/renderless/src/color-select-panel/alpha-select/vue.ts (1)

19-22: LGTM: ctx integration into event/watch flow

Passing ctx into useEvent/initWatch aligns with the new context model.

Please confirm that a provider (createContext) is established in the parent so useContext(hooks) resolves properly at runtime.

Also applies to: 41-41

packages/vue/src/color-select-panel/src/pc.vue (1)

85-112: Parse injection looks correct

Importing parse and passing it via extendOptions is a clean way to wire the parser without polluting component API.

packages/vue/src/color-select-panel/src/components/hue-select.vue (1)

16-22: Context-driven hue UI and conditional linear-gradient block look good

  • Using state.ctx.activeColor.color.value for the thumb is consistent with the shared context.
  • Conditional render of tied to state.ctx.colorMode is appropriate.

Also applies to: 29-42

packages/renderless/src/color-select-panel/sv-select/vue.ts (1)

18-27: Threading ctx OK — initWatch signature and context provider confirmed

initWatch in packages/renderless/src/color-select-panel/sv-select/index.ts is (state, update, { watch }) — keep initWatch(state, update, hooks). createContext(ctx, hooks) is present in packages/renderless/src/color-select-panel/index.ts, so useContext(hooks) will be provided.

packages/renderless/src/color-select-panel/vue.ts (3)

1-6: LGTM! Clean type import additions.

The import statement correctly adds ColorSelectPanelExtends alongside existing types, maintaining proper organization.


32-32: LGTM! Consistent parameter propagation.

The ext and utils parameters are correctly passed to initState, and hooks is appropriately added to initApi. This ensures proper context sharing across initialization functions.

Also applies to: 47-47


26-31: Confirm callers updated to pass ext (has parse)?

I inspected the repo: packages/renderless/src/color-select-panel/vue.ts now accepts ext: ColorSelectPanelExtends. The only component importing this renderless is packages/vue/src/color-select-panel/src/pc.vue — open that file and confirm it calls renderless with an sdk/extensions object that contains a parse implementation; otherwise callers must be updated to pass one. I couldn't find other callers of this specific renderless export.

packages/renderless/src/color-select-panel/hue-select/vue.ts (2)

4-6: LGTM! Proper context integration.

The imports are correctly added to support context-based color management and color points functionality.


58-64: LGTM! Proper reactive synchronization.

The watch implementation correctly synchronizes thumbLeft with the active color's hue value through the context.

packages/renderless/src/color-select-panel/alpha-select/index.ts (2)

1-6: LGTM! Type imports properly updated.

The addition of ColorPanelContext to the imports is correct and follows the established pattern.


40-40: LGTM! Consistent context-based color management.

The replacement of props.color with ctx.activeColor.value.color is implemented consistently throughout the module, maintaining proper separation of concerns.

Also applies to: 56-56

packages/renderless/src/color-select-panel/utils/color-points.ts (1)

10-15: LGTM! Clean ColorPoint implementation.

The ColorPoint class is properly implemented with public properties following TypeScript best practices.

packages/renderless/src/color-select-panel/index.ts (6)

198-205: Directional degree map: looks good

Covers all CSS named corners/edges.


34-49: Event API helpers look good

Clear separation of updateModelValue/triggerConfirm/Cancel/ColorUpdate.


50-58: initApi signature change is reasonable

Separating hooks/utils is consistent with other modules.


398-405: Color update watch emits object; verify downstream expectations

If consumers expect string, adjust. Otherwise OK.

Would you confirm listeners of 'color-update' expect a Color instance, not a string?


329-335: Context initialization LGTM

Context fields and defaults match types.


56-58: Public API wiring looks consistent

open/close/reset/on* handlers are coherent with previous design.

Also applies to: 144-158

@GaoNeng-wWw GaoNeng-wWw changed the title WIP feat(color-select-panel): linear-gradient feat(color-select-panel): linear-gradient Sep 24, 2025
@github-actions github-actions bot added the enhancement New feature or request (功能增强) label Sep 24, 2025
@GaoNeng-wWw GaoNeng-wWw marked this pull request as ready for review September 24, 2025 14:05
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: 5

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (4)
packages/renderless/src/color-select-panel/utils/color.ts (1)

285-299: Fix HSL parsing regex (currently broken flags placement)

The regex places “gm” inside the pattern, so it won’t strip tokens correctly. Use flags properly.

-    const parts = value
-      .replace(/hsla|hsl\(|\)gm/, '')
+    const parts = value
+      .replace(/hsla|hsl|\(|\)/gm, '')
       .split(/\s|,/g)
packages/renderless/src/color-select-panel/alpha-select/index.ts (1)

30-44: Guard alphaWrapper before using getBoundingClientRect

alphaWrapper is not checked; calling getBoundingClientRect on null will throw.

   const onDrag = (event: MouseEvent | TouchEvent) => {
     if (!slider.value || !alphaThumb.value) {
       return
     }
-    const el = alphaWrapper.value!
+    const el = alphaWrapper.value
+    if (!el) {
+      return
+    }
     const rect = el.getBoundingClientRect()
packages/renderless/src/color-select-panel/index.ts (2)

469-485: Fix watcher to use ref values (and parse with correct args)

The watcher operates on Ref objects; use .value for correctness.

-  watch(
-    () => [state.currentColor, state.linearGradient],
+  watch(
+    () => [state.currentColor.value, state.linearGradient.value],
     () => {
-      if (state.isLinearGradient) {
-        state.input = state.linearGradient
+      if (state.isLinearGradient.value) {
+        state.input = state.linearGradient.value
         return
       } else {
-        state.input = state.currentColor
-        const result = parseCustomRGBA(state.currentColor, state.currentFormat)
+        state.input = state.currentColor.value
+        const result = parseCustomRGBA(state.currentColor.value, state.currentFormat.value)
         state.hexInput4 = Math.ceil(Number(result[0]))
         state.hexInput5 = result[1]
         state.hexInput6 = result[2]
         state.hexInput7 = `${(Number(result[3]) || 1) * 100}%`
       }
       triggerColorUpdate(state.input, emit)
     },

340-426: Fix incorrect use of ctx refs (missing .value) — breaks comparisons/assignments

Multiple places use state.ctx. (or ctx.) directly while those props are refs/computed; change reads/writes to use .value and assign to .value when updating.

  • packages/renderless/src/color-select-panel/index.ts:225–227 — if (state.ctx.colorMode === 'linear-gradient') → state.ctx.colorMode.value === 'linear-gradient'; pass state.ctx.linearGardientValue.value to updateModelValue/triggerConfirm.
  • packages/renderless/src/color-select-panel/index.ts:292–306 — use state.ctx.colorMode.value, state.ctx.activeColor.value.color.fromString(historyColor); assign state.ctx.colorPoints.value = colorPoints.colorPoints, state.ctx.activeColor.value = lastPoint, state.ctx.deg.value = colorPoints.angular.
  • packages/renderless/src/color-select-panel/index.ts:308–322 — same fixes for onPredefineColorClick (use .value for colorMode, colorPoints, activeColor, deg).

Search for other state.ctx.* usages and update any remaining missing .value accesses.

🧹 Nitpick comments (7)
packages/theme/src/color-select-panel/index.less (2)

48-52: Use flex-end and apply the theme variable for deg text size

“justify-content: end” may not be consistently supported; prefer “flex-end”. Also consume the new font-size var so the knob respects theming.

 &-deg {
   display: flex;
-  justify-content: end;
+  justify-content: flex-end;
+  font-size: var(--tv-ColorSelectPanel-deg-text-size);
 }

183-209: Deduplicate thumb styles via a shared mixin

The thumb block duplicates the hue/alpha thumb styles. Extract to a LESS mixin to reduce drift.

Example (outside this block):

.thumb-base() {
  position: absolute;
  top: -2px;
  width: 16px;
  height: 16px;
  background: #fff;
  box-shadow: 0 0 8px rgba(0, 0, 0, 0.2);
  border-radius: 16px;
  &-heart {
    position: absolute;
    top: 50%;
    left: 50%;
    width: 12px;
    height: 12px;
    border-radius: 50%;
    transform: translate(-50%, -50%);
  }
}

Then:

&__thumb { .thumb-base(); }
packages/vue/src/color-select-panel/src/pc.vue (1)

29-31: Constrain deg input to valid angle range

Clamp to 0–360 and step by 1 to avoid out-of-range values via scroll.

-            <tiny-numeric v-model="state.ctx.deg" unit="deg" mouse-wheel />
+            <tiny-numeric v-model="state.ctx.deg" unit="deg" mouse-wheel :min="0" :max="360" :step="1" />
packages/renderless/types/color-select-panel.type.ts (1)

292-294: Rename exported type alias parseParseFn

Lowercase type alias shadows a value name; update the exported type and the interface property in packages/renderless/types/color-select-panel.type.ts.

Change:

-export type parse = (value: string) => GradientNode[]
+export type ParseFn = (value: string) => GradientNode[]

And:

-export interface ColorSelectPanelExtends {
-  parse: parse
-}
+export interface ColorSelectPanelExtends {
+  parse: ParseFn
+}
examples/sites/demos/pc/app/color-select-panel/linear-gradient.spec.ts (2)

3-10: Add at least one outcome assertion in the first test

The first test performs interactions but doesn’t assert the resulting value/state beyond thumb count. Add a final expectation to verify behavior (e.g., input value updated).

 test('线性渐变', async ({ page }) => {
   await page.goto('color-select-panel#linear-gradient')
   await page.locator('#linear-gradient').getByRole('button', { name: 'Show Color select panel' }).click()
   expect(await page.locator('.tiny-color-select-panel__linear-gradient__thumb').count()).toBe(2)
   await page.locator('.tiny-color-select-panel__linear-gradient__thumb').first().click()
   await page.locator('.tiny-color-select-panel__linear-gradient').click()
   await page.locator('.tiny-color-select-panel__linear-gradient > div:nth-child(2)').click()
+  await expect(page.getByLabel('示例', { exact: true }).getByRole('textbox'))
+    .toContainText('linear-gradient(')
 })

6-10: Avoid brittle nth-child and deep CSS selectors

These are likely to flake with structure changes. Prefer role, label, or data-testid.

-  await page.locator('.tiny-color-select-panel__linear-gradient > div:nth-child(2)').click()
+  await page.locator('[data-testid="linear-gradient-bar"]').click()
@@
-  await page.locator('.tiny-color-select-panel__history__color-block').first().click()
+  await page.locator('[data-testid="history-color"]').first().click()
@@
-  await page.locator('.tiny-color-select-panel__history > div:nth-child(2)').click()
+  await page.locator('[data-testid="history-color"]').nth(1).click()

Follow-up: add these data-testid attributes in the demo/component markup.

Also applies to: 28-31, 60-63

packages/renderless/src/color-select-panel/alpha-select/index.ts (1)

22-29: Props parameter is unused

Either remove it from the signature or prefix with underscore to signal intentional unused.

-export const useEvent = (
+export const useEvent = (
   state: State,
   slider: IColorSelectPanelRef<HTMLElement | undefined>,
   alphaWrapper: IColorSelectPanelRef<HTMLElement | undefined>,
   alphaThumb: IColorSelectPanelRef<HTMLElement | undefined>,
-  props: IColorSelectPanelAlphProps<Color>,
+  _props: IColorSelectPanelAlphProps<Color>,
   ctx: ColorPanelContext
 ) => {
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between c734480 and 8e08202.

📒 Files selected for processing (17)
  • examples/sites/demos/pc/app/color-select-panel/linear-gradient-composition-api.vue (1 hunks)
  • examples/sites/demos/pc/app/color-select-panel/linear-gradient.spec.ts (1 hunks)
  • examples/sites/demos/pc/app/color-select-panel/linear-gradient.vue (1 hunks)
  • packages/renderless/src/color-select-panel/alpha-select/index.ts (5 hunks)
  • packages/renderless/src/color-select-panel/hue-select/index.ts (4 hunks)
  • packages/renderless/src/color-select-panel/index.ts (8 hunks)
  • packages/renderless/src/color-select-panel/linear-gradient/index.ts (1 hunks)
  • packages/renderless/src/color-select-panel/linear-gradient/vue.ts (1 hunks)
  • packages/renderless/src/color-select-panel/utils/color.ts (3 hunks)
  • packages/renderless/src/color-select-panel/vue.ts (3 hunks)
  • packages/renderless/types/color-select-panel.type.ts (3 hunks)
  • packages/theme/src/color-select-panel/index.less (3 hunks)
  • packages/theme/src/color-select-panel/vars.less (1 hunks)
  • packages/vue/src/color-select-panel/package.json (1 hunks)
  • packages/vue/src/color-select-panel/src/components/alpha-select.vue (1 hunks)
  • packages/vue/src/color-select-panel/src/components/linear-gradient.vue (1 hunks)
  • packages/vue/src/color-select-panel/src/pc.vue (5 hunks)
✅ Files skipped from review due to trivial changes (1)
  • packages/vue/src/color-select-panel/src/components/alpha-select.vue
🚧 Files skipped from review as they are similar to previous changes (3)
  • packages/vue/src/color-select-panel/package.json
  • examples/sites/demos/pc/app/color-select-panel/linear-gradient.vue
  • packages/vue/src/color-select-panel/src/components/linear-gradient.vue
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2024-11-04T09:35:13.159Z
Learnt from: zzcr
PR: opentiny/tiny-vue#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/color-select-panel/vars.less
🧬 Code graph analysis (8)
packages/renderless/src/color-select-panel/linear-gradient/index.ts (3)
packages/renderless/types/color-select-panel.type.ts (3)
  • LinearGradientState (3-5)
  • ColorPanelContext (89-96)
  • IColorPoint (84-87)
packages/renderless/src/color-select-panel/utils/color-points.ts (1)
  • ColorPoint (10-15)
packages/renderless/src/color-select-panel/utils/getClientXY.ts (1)
  • getClientXY (41-58)
packages/renderless/src/color-select-panel/alpha-select/index.ts (5)
packages/renderless/src/color-select-panel/hue-select/index.ts (1)
  • initState (39-46)
packages/renderless/src/color-select-panel/index.ts (1)
  • initState (340-428)
packages/renderless/src/color-select-panel/sv-select/index.ts (1)
  • initState (14-32)
packages/renderless/src/color-select-panel/utils/context.ts (1)
  • useContext (13-15)
packages/renderless/types/color-select-panel.type.ts (2)
  • IColorSelectPanelAlphProps (113-115)
  • ColorPanelContext (89-96)
packages/renderless/src/color-select-panel/vue.ts (2)
packages/renderless/types/color-select-panel.type.ts (1)
  • ColorSelectPanelExtends (139-141)
packages/renderless/src/color-select-panel/index.ts (2)
  • initState (340-428)
  • initApi (202-338)
packages/renderless/src/color-select-panel/linear-gradient/vue.ts (2)
packages/renderless/src/color-select-panel/utils/context.ts (1)
  • useContext (13-15)
packages/renderless/src/color-select-panel/linear-gradient/index.ts (2)
  • initState (138-143)
  • useLinearGradient (17-136)
packages/renderless/src/color-select-panel/index.ts (7)
packages/renderless/types/color-select-panel.type.ts (16)
  • IColorSelectPanelProps (101-111)
  • ColorSelectPanelExtends (139-141)
  • DirectionalNode (169-184)
  • AngularNode (186-189)
  • HexNode (197-201)
  • RgbNode (203-207)
  • RgbaNode (209-213)
  • HslNode (215-219)
  • HslaNode (221-225)
  • LiteralNode (191-195)
  • CalcNode (279-282)
  • PercentNode (274-277)
  • EmNode (269-272)
  • PxNode (264-267)
  • ColorStop (284-284)
  • ColorPanelContext (89-96)
packages/renderless/src/color-select-panel/utils/color-points.ts (1)
  • ColorPoint (10-15)
packages/renderless/src/color-picker/utils/color.ts (1)
  • Color (155-425)
packages/renderless/src/color-select-panel/hue-select/index.ts (1)
  • initState (39-46)
packages/renderless/src/color-select-panel/alpha-select/index.ts (1)
  • initState (13-20)
packages/renderless/src/color-select-panel/sv-select/index.ts (1)
  • initState (14-32)
packages/renderless/src/color-select-panel/utils/context.ts (1)
  • createContext (5-11)
packages/renderless/src/color-select-panel/utils/color.ts (2)
packages/renderless/src/color-picker/utils/color.ts (1)
  • Color (155-425)
packages/renderless/types/color-select-panel.type.ts (1)
  • IColor (33-73)
packages/renderless/src/color-select-panel/hue-select/index.ts (6)
packages/renderless/types/color-select-panel.type.ts (3)
  • UseColorPointsRet (129-137)
  • IColorSelectPanelHueProps (121-123)
  • ColorPanelContext (89-96)
packages/renderless/src/color-select-panel/utils/color.ts (1)
  • Color (157-418)
packages/renderless/src/color-select-panel/utils/color-points.ts (1)
  • ColorPoint (10-15)
packages/renderless/src/color-select-panel/index.ts (1)
  • initState (340-428)
packages/renderless/src/color-select-panel/utils/context.ts (1)
  • useContext (13-15)
packages/renderless/src/color-select-panel/utils/getClientXY.ts (1)
  • getClientXY (41-58)
packages/renderless/types/color-select-panel.type.ts (2)
packages/renderless/src/color-select-panel/utils/color.ts (1)
  • ColorOptions (151-155)
packages/renderless/src/color-picker/utils/color.ts (1)
  • ColorOptions (149-153)
🪛 Biome (2.1.2)
packages/renderless/src/color-select-panel/linear-gradient/index.ts

[error] 99-99: Do not shadow the global "toString" property.

Consider renaming this variable. It's easy to confuse the origin of variables when they're named after a known global.

(lint/suspicious/noShadowRestrictedNames)

⏰ 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 (16)
examples/sites/demos/pc/app/color-select-panel/linear-gradient-composition-api.vue (1)

24-35: LGTM: Good demo coverage for linear-gradient mode

Props and events are wired correctly; example is clear.

packages/renderless/src/color-select-panel/utils/color.ts (1)

1-1: Ensure TS path alias for "@/types" resolves across all builds

If "@/types" isn’t mapped in all tsconfig variants, builds will break. Either add a paths mapping or switch to a relative import.

#!/bin/bash
# Check path alias config and usages
rg -nC2 '"baseUrl"|"paths"' --type=json tsconfig*.json
rg -nP "from\\s+['\"]@/types['\"]" --type=ts --type=tsx --type=vue
packages/vue/src/color-select-panel/src/pc.vue (2)

9-10: Background binding is now correct for gradient vs solid

Switching between gradient and solid works as expected.


90-90: Verify gradient-parser import interop for packages/vue/src/color-select-panel

Confirmed: packages/vue/src/color-select-panel/package.json declares "gradient-parser" and packages/vue/src/color-select-panel/src/pc.vue imports parse as a named export (import { parse } from 'gradient-parser').

Ensure your build supports CJS→ESM named-export interop; if not, switch to a default import and use gp.parse (e.g. import gp from 'gradient-parser'; extendOptions: { parse: gp.parse }).

packages/renderless/types/color-select-panel.type.ts (2)

89-96: Typo in public API: linearGardientValue → linearGradientValue

Fix before shipping; update all usages to avoid API debt.

 export interface ColorPanelContext {
   colorMode: ComputedRef<IColorSelectPanelProps['colorMode']>
   activeColor: Ref<IColorPoint>
   bar: Ref<HTMLElement | null>
   colorPoints: Ref<IColorPoint[]>
-  linearGardientValue: Ref<string>
+  linearGradientValue: Ref<string>
   deg: Ref<number>
 }

129-137: Typo in method name: getActviePoint → getActivePoint

Rename and update all call sites.

 export interface UseColorPointsRet {
   onClick: (element: Element, point: IColorPoint) => void
   linearGradientValue: Readonly<Ref<string>>
   updateDeg: (_deg: number) => void
   removePoint: (point: IColorPoint) => void
   addPoint: (point: IColorPoint) => void
   setActivePoint: (point: IColorPoint) => void
-  getActviePoint: () => Ref<IColorPoint>
+  getActivePoint: () => Ref<IColorPoint>
 }
packages/renderless/src/color-select-panel/vue.ts (1)

26-33: Renderless signature change: confirm all call sites pass ext

renderless now expects (props, hooks, utils, ext). Ensure every consumer (pc/mobile/2.x/3.x variants) passes extendOptions with parse to avoid runtime errors.

packages/renderless/src/color-select-panel/linear-gradient/vue.ts (1)

5-20: API surface is consistent — LGTM

Exposed keys match the returned object. No mismatches detected.

packages/renderless/src/color-select-panel/linear-gradient/index.ts (3)

29-37: Guard ref before getBoundingClientRect (vm.$refs[LINEAR_GRADIENT_BAR])

Prevents runtime error when ref isn’t ready.

   const getPos = (event: MouseEvent | TouchEvent) => {
     if (!vm) {
       return 0
     }
-    const el = vm.$refs[LINEAR_GRADIENT_BAR] as HTMLElement
+    const el = vm.$refs[LINEAR_GRADIENT_BAR] as HTMLElement | undefined
+    if (!el) {
+      return 0
+    }
     const rect = el.getBoundingClientRect()

61-74: Handle THUMB ref as single element or array; also guard vm

Current code assumes array and will throw if a single ref is returned.

-    nextTick(() => {
-      const lastColorPointElement = (vm.$refs[THUMB] as HTMLElement[]).at(-1)
+    nextTick(() => {
+      if (!vm) return
+      const refEl = vm.$refs[THUMB] as HTMLElement | HTMLElement[] | undefined
+      const lastColorPointElement = Array.isArray(refEl) ? refEl.at(-1) : refEl
       if (!lastColorPointElement) {
         return
       }
       draggable(lastColorPointElement, {

118-134: Normalize THUMB refs on mount and set bar ref safely

Supports both array and single refs; avoids assigning undefined.

   hooks.onMounted(() => {
-    const elements = vm.$refs[THUMB] as HTMLElement[]
-    if (!elements || !elements.length) {
-      return
-    }
-    elements.forEach((el) => {
+    const refEl = vm?.$refs[THUMB] as HTMLElement | HTMLElement[] | undefined
+    const elements = Array.isArray(refEl) ? refEl : refEl ? [refEl] : []
+    elements.forEach((el) => {
       draggable(el, {
         drag(event) {
           onDrag(event)
         },
         end(event) {
           onDrag(event)
         }
       })
     })
-    context.bar.value = vm.$refs[LINEAR_GRADIENT_BAR]
+    context.bar.value = (vm?.$refs[LINEAR_GRADIENT_BAR] as HTMLElement | undefined) ?? null
   })
packages/renderless/src/color-select-panel/hue-select/index.ts (1)

20-37: Broaden click handler to support touch; guard bar before use

Current typing blocks touch and uses non-null assertion on bar.

 export const useOnClickBar = (
   { addPoint, setActivePoint, getActviePoint }: UseColorPointsRet,
   { bar }: DomInit,
-  getLeft: (barEl: HTMLElement, event: MouseEvent) => number
+  getLeft: (barEl: HTMLElement, event: MouseEvent | TouchEvent) => number
 ) => {
-  return (event: MouseEvent) => {
+  return (event: MouseEvent | TouchEvent) => {
     const activePoint = getActviePoint().value
     const color = new Color({
       enableAlpha: activePoint.color.enableAlpha,
       format: activePoint.color.format,
       value: activePoint.color.value
     })
-    const left = getLeft(bar.value!, event)
+    const barEl = bar.value
+    if (!barEl) return
+    const left = getLeft(barEl, event)
     const colorPoint = new ColorPoint(color, left)
     addPoint(colorPoint)
     setActivePoint(colorPoint)
   }
 }
packages/renderless/src/color-select-panel/index.ts (4)

225-230: Fix Ref misuse in submitValue (use .value)

Comparing/using refs directly breaks logic and emits wrong payloads.

   const submitValue = () => {
-    if (state.ctx.colorMode === 'linear-gradient') {
-      updateModelValue(state.ctx.linearGardientValue, emit)
-      triggerConfirm(state.ctx.linearGardientValue, emit)
+    if (state.ctx.colorMode.value === 'linear-gradient') {
+      updateModelValue(state.ctx.linearGardientValue.value, emit)
+      triggerConfirm(state.ctx.linearGardientValue.value, emit)
       setShowPicker(false)
       return
     }

150-153: Fix var() fallback: avoid double ‘#’

hex parser expects a value without leading '#'.

     'var': (node: any) => {
       hooks.warn('unsupported var ref.')
-      return parseBehavior.hex({ type: 'hex', value: '#000', length: node.length })
+      return parseBehavior.hex({ type: 'hex', value: '000', length: node.length })
     }

164-174: Fix px conversion: don’t divide by width

Returning parseInt(numberValue / width) collapses px to 0/1.

     if (node.type === '%') {
       return Number.parseInt(`${(numberValue / 100) * width}`)
     }
     if (node.type === 'px') {
-      return Number.parseInt(`${numberValue / width}`)
+      return Math.round(numberValue)
     }

130-144: RGBA/HSLA parsing: use supported format and preserve alpha

Use format 'rgb' (not 'rgba') with enableAlpha=true; keep HSLA already correct.

-    rgba: (node: RgbaNode) => {
-      const color = new Color({ enableAlpha: props.alpha, format: 'rgba', value: `rgba(${node.value.join(',')})` })
-      return new ColorPoint(color, 0)
-    },
+    rgba: (node: RgbaNode) => {
+      const color = new Color({ enableAlpha: props.alpha, format: 'rgb', value: `rgba(${node.value.join(',')})` })
+      return new ColorPoint(color, 0)
+    },
@@
-    hsla: (node: HslaNode) => {
-      const color = new Color({ enableAlpha: props.alpha, format: 'hsl', value: `hsl(${node.value.join(' ')})` })
+    hsla: (node: HslaNode) => {
+      const color = new Color({ enableAlpha: props.alpha, format: 'hsl', value: `hsla(${node.value.join(' ')})` })
       return new ColorPoint(color, 0)
     },

Comment on lines +292 to 306
if (state.ctx.colorMode === 'monochrome') {
state.ctx.activeColor.color.fromString(historyColor)
return
}
const colorString = isGrandient(historyColor)
? historyColor
: `linear-gradient(90deg, #fff 0%, ${historyColor} 100%)`
const colorPoints = createColorPoints(colorString, props, hooks, ext, state.ctx.bar)
state.ctx.colorPoints = colorPoints.colorPoints
const lastPoint = colorPoints.colorPoints.at(-1)
if (lastPoint) {
state.ctx.activeColor = lastPoint
}
state.ctx.deg = colorPoints.angular
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Fix multiple Ref dereferences in onHistoryClick

All ctx fields here are Refs; also pass bar.value to parser.

-    if (state.ctx.colorMode === 'monochrome') {
-      state.ctx.activeColor.color.fromString(historyColor)
+    if (state.ctx.colorMode.value === 'monochrome') {
+      state.ctx.activeColor.value.color.fromString(historyColor)
       return
     }
     const colorString = isGrandient(historyColor)
       ? historyColor
       : `linear-gradient(90deg, #fff 0%, ${historyColor} 100%)`
-    const colorPoints = createColorPoints(colorString, props, hooks, ext, state.ctx.bar)
-    state.ctx.colorPoints = colorPoints.colorPoints
+    const colorPoints = createColorPoints(colorString, props, hooks, ext, state.ctx.bar.value)
+    state.ctx.colorPoints.value = colorPoints.colorPoints
     const lastPoint = colorPoints.colorPoints.at(-1)
     if (lastPoint) {
-      state.ctx.activeColor = lastPoint
+      state.ctx.activeColor.value = lastPoint
     }
-    state.ctx.deg = colorPoints.angular
+    state.ctx.deg.value = colorPoints.angular
📝 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
if (state.ctx.colorMode === 'monochrome') {
state.ctx.activeColor.color.fromString(historyColor)
return
}
const colorString = isGrandient(historyColor)
? historyColor
: `linear-gradient(90deg, #fff 0%, ${historyColor} 100%)`
const colorPoints = createColorPoints(colorString, props, hooks, ext, state.ctx.bar)
state.ctx.colorPoints = colorPoints.colorPoints
const lastPoint = colorPoints.colorPoints.at(-1)
if (lastPoint) {
state.ctx.activeColor = lastPoint
}
state.ctx.deg = colorPoints.angular
}
if (state.ctx.colorMode.value === 'monochrome') {
state.ctx.activeColor.value.color.fromString(historyColor)
return
}
const colorString = isGrandient(historyColor)
? historyColor
: `linear-gradient(90deg, #fff 0%, ${historyColor} 100%)`
const colorPoints = createColorPoints(colorString, props, hooks, ext, state.ctx.bar.value)
state.ctx.colorPoints.value = colorPoints.colorPoints
const lastPoint = colorPoints.colorPoints.at(-1)
if (lastPoint) {
state.ctx.activeColor.value = lastPoint
}
state.ctx.deg.value = colorPoints.angular
}
🤖 Prompt for AI Agents
In packages/renderless/src/color-select-panel/index.ts around lines 292–306, the
onHistoryClick block is directly dereferencing ctx fields that are Vue Refs;
change accesses to use .value (e.g., state.ctx.colorMode.value,
state.ctx.activeColor.value.color.fromString(...), state.ctx.colorPoints.value =
..., state.ctx.deg.value = ..., and when passing the bar to the parser use
state.ctx.bar.value). Also when assigning the active color use the lastPoint (or
its value) correctly via refs (e.g., state.ctx.activeColor.value = lastPoint),
and ensure createColorPoints receives state.ctx.bar.value instead of
state.ctx.bar.

Comment on lines +308 to 322
if (state.ctx.colorMode === 'monochrome') {
state.color.fromString(predefineColor)
return
}
const colorString = isGrandient(predefineColor)
? predefineColor
: `linear-gradient(180deg, #fff 0%, ${predefineColor} 100%)`
const colorPoints = createColorPoints(colorString, props, hooks, ext, state.ctx.bar)
state.ctx.colorPoints = colorPoints.colorPoints
const lastPoint = colorPoints.colorPoints.at(-1)
if (lastPoint) {
state.ctx.activeColor = lastPoint
}
state.ctx.deg = colorPoints.angular
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Fix multiple Ref dereferences in onPredefineColorClick

Same issues as above.

-    if (state.ctx.colorMode === 'monochrome') {
-      state.color.fromString(predefineColor)
+    if (state.ctx.colorMode.value === 'monochrome') {
+      state.color.fromString(predefineColor)
       return
     }
     const colorString = isGrandient(predefineColor)
       ? predefineColor
       : `linear-gradient(180deg, #fff 0%, ${predefineColor} 100%)`
-    const colorPoints = createColorPoints(colorString, props, hooks, ext, state.ctx.bar)
-    state.ctx.colorPoints = colorPoints.colorPoints
+    const colorPoints = createColorPoints(colorString, props, hooks, ext, state.ctx.bar.value)
+    state.ctx.colorPoints.value = colorPoints.colorPoints
     const lastPoint = colorPoints.colorPoints.at(-1)
     if (lastPoint) {
-      state.ctx.activeColor = lastPoint
+      state.ctx.activeColor.value = lastPoint
     }
-    state.ctx.deg = colorPoints.angular
+    state.ctx.deg.value = colorPoints.angular
📝 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
if (state.ctx.colorMode === 'monochrome') {
state.color.fromString(predefineColor)
return
}
const colorString = isGrandient(predefineColor)
? predefineColor
: `linear-gradient(180deg, #fff 0%, ${predefineColor} 100%)`
const colorPoints = createColorPoints(colorString, props, hooks, ext, state.ctx.bar)
state.ctx.colorPoints = colorPoints.colorPoints
const lastPoint = colorPoints.colorPoints.at(-1)
if (lastPoint) {
state.ctx.activeColor = lastPoint
}
state.ctx.deg = colorPoints.angular
}
if (state.ctx.colorMode.value === 'monochrome') {
state.color.fromString(predefineColor)
return
}
const colorString = isGrandient(predefineColor)
? predefineColor
: `linear-gradient(180deg, #fff 0%, ${predefineColor} 100%)`
const colorPoints = createColorPoints(
colorString,
props,
hooks,
ext,
state.ctx.bar.value
)
state.ctx.colorPoints.value = colorPoints.colorPoints
const lastPoint = colorPoints.colorPoints.at(-1)
if (lastPoint) {
state.ctx.activeColor.value = lastPoint
}
state.ctx.deg.value = colorPoints.angular
}
🤖 Prompt for AI Agents
In packages/renderless/src/color-select-panel/index.ts around lines 308 to 322,
the onPredefineColorClick handler repeatedly dereferences reactive refs (e.g.,
state.ctx, state.ctx.colorPoints, props, hooks, ext) causing multiple .value
accesses and potential null/undefined reads; cache required refs/values into
local constants once at the top of the function, perform null checks on those
cached values, then use the locals for branching and assignments (e.g., read
colorMode, predefineColor, and bar into locals, compute colorString and
colorPoints using those locals, set state.ctx.* only once using the cached ctx
reference) to avoid repeated ref dereferences and reduce risk of runtime errors.

Comment on lines +91 to +98
const getRelativePos = (points: IColorPoint) => {
const bar = vm.$refs[LINEAR_GRADIENT_BAR] as HTMLElement
if (!bar) {
return 0
}
const rect = bar.getBoundingClientRect()
return Number.parseInt(((points.cursorLeft / rect.width) * 100).toFixed(0))
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Guard vm before refs in getRelativePos

Accessing vm.$refs when vm is falsy will throw.

   const getRelativePos = (points: IColorPoint) => {
-    const bar = vm.$refs[LINEAR_GRADIENT_BAR] as HTMLElement
-    if (!bar) {
+    if (!vm) {
+      return 0
+    }
+    const bar = vm.$refs[LINEAR_GRADIENT_BAR] as HTMLElement | undefined
+    if (!bar) {
       return 0
     }
📝 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
const getRelativePos = (points: IColorPoint) => {
const bar = vm.$refs[LINEAR_GRADIENT_BAR] as HTMLElement
if (!bar) {
return 0
}
const rect = bar.getBoundingClientRect()
return Number.parseInt(((points.cursorLeft / rect.width) * 100).toFixed(0))
}
const getRelativePos = (points: IColorPoint) => {
if (!vm) {
return 0
}
const bar = vm.$refs[LINEAR_GRADIENT_BAR] as HTMLElement | undefined
if (!bar) {
return 0
}
const rect = bar.getBoundingClientRect()
return Number.parseInt(((points.cursorLeft / rect.width) * 100).toFixed(0))
}
🤖 Prompt for AI Agents
In packages/renderless/src/color-select-panel/linear-gradient/index.ts around
lines 91 to 98, getRelativePos() accesses vm.$refs without guarding vm; add a
defensive check at the start to ensure vm and vm.$refs exist (e.g., if (!vm ||
!vm.$refs) return 0) before referencing LINEAR_GRADIENT_BAR, then proceed to get
the ref and bounding rect as before; you can also use optional chaining when
reading the ref and fall back to 0 if missing.

Comment on lines +99 to +117
const toString = () => {
const colors = context.colorPoints.value
.map((point) => {
return [point.color.value, getRelativePos(point)] as const
})
.sort((a, b) => a[1] - b[1])
.map(([colorValue, pos]) => {
return [colorValue, `${pos}%`].join(' ')
})
.join(',')
return `linear-gradient(${context.deg.value}deg, ${colors})`
}
hooks.watchEffect(() => {
if (isNullOrEmpty(context.deg.value)) {
return
}
context.linearGardientValue.value = toString()
state.linearGradientBarBackground = toString().replace(`${context.deg.value}deg`, '90deg')
})
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Rename local toString and compute once to satisfy lint and avoid shadowing

Avoids shadowing restricted name and duplicate work inside watchEffect.

-  const toString = () => {
+  const gradientToString = () => {
     const colors = context.colorPoints.value
       .map((point) => {
         return [point.color.value, getRelativePos(point)] as const
       })
       .sort((a, b) => a[1] - b[1])
       .map(([colorValue, pos]) => {
         return [colorValue, `${pos}%`].join(' ')
       })
       .join(',')
     return `linear-gradient(${context.deg.value}deg, ${colors})`
   }
   hooks.watchEffect(() => {
     if (isNullOrEmpty(context.deg.value)) {
       return
     }
-    context.linearGardientValue.value = toString()
-    state.linearGradientBarBackground = toString().replace(`${context.deg.value}deg`, '90deg')
+    const s = gradientToString()
+    context.linearGardientValue.value = s
+    state.linearGradientBarBackground = s.replace(`${context.deg.value}deg`, '90deg')
   })
📝 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
const toString = () => {
const colors = context.colorPoints.value
.map((point) => {
return [point.color.value, getRelativePos(point)] as const
})
.sort((a, b) => a[1] - b[1])
.map(([colorValue, pos]) => {
return [colorValue, `${pos}%`].join(' ')
})
.join(',')
return `linear-gradient(${context.deg.value}deg, ${colors})`
}
hooks.watchEffect(() => {
if (isNullOrEmpty(context.deg.value)) {
return
}
context.linearGardientValue.value = toString()
state.linearGradientBarBackground = toString().replace(`${context.deg.value}deg`, '90deg')
})
const gradientToString = () => {
const colors = context.colorPoints.value
.map((point) => {
return [point.color.value, getRelativePos(point)] as const
})
.sort((a, b) => a[1] - b[1])
.map(([colorValue, pos]) => {
return [colorValue, `${pos}%`].join(' ')
})
.join(',')
return `linear-gradient(${context.deg.value}deg, ${colors})`
}
hooks.watchEffect(() => {
if (isNullOrEmpty(context.deg.value)) {
return
}
const s = gradientToString()
context.linearGardientValue.value = s
state.linearGradientBarBackground = s.replace(`${context.deg.value}deg`, '90deg')
})
🧰 Tools
🪛 Biome (2.1.2)

[error] 99-99: Do not shadow the global "toString" property.

Consider renaming this variable. It's easy to confuse the origin of variables when they're named after a known global.

(lint/suspicious/noShadowRestrictedNames)

🤖 Prompt for AI Agents
In packages/renderless/src/color-select-panel/linear-gradient/index.ts around
lines 99 to 117, the local function is named toString (shadowing a restricted
name) and is invoked twice inside the watchEffect causing duplicate computation;
rename the function to a non-shadowing name (e.g., buildGradientString or
gradientToString), invoke it once inside the watchEffect, store the result in a
local const, then assign context.linearGardientValue.value = result and
state.linearGradientBarBackground = result.replace(`${context.deg.value}deg`,
'90deg') so the gradient is computed only once and the restricted name is not
used.

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: 0

🧹 Nitpick comments (2)
examples/sites/demos/apis/color-select-panel.js (2)

95-95: Prefer kebab-case for prop name in docs

Multi-word props in this file use kebab-case (e.g., enable-predefine-color). Align this entry for consistency and HTML attribute usage.

-          name: 'colorMode',
+          name: 'color-mode',

101-102: Fix English description spacing and punctuation

Insert spaces around code tags and split into sentences for clarity.

-              'Determine the color mode of the color selection panel. If it is<code>linear-gradient</code>, it means it is a linear gradient If it is<code>monochrome</code>, it means monochrome selection'
+              'Determine the color mode of the color selection panel. If it is <code>linear-gradient</code>, it is a linear gradient. If it is <code>monochrome</code>, it is a monochrome selection.'
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 8e08202 and b4b57f3.

📒 Files selected for processing (3)
  • examples/sites/demos/apis/color-select-panel.js (1 hunks)
  • examples/sites/demos/pc/app/color-select-panel/webdoc/color-select-panel.js (1 hunks)
  • packages/theme/src/color-select-panel/vars.less (1 hunks)
✅ Files skipped from review due to trivial changes (1)
  • packages/theme/src/color-select-panel/vars.less
🚧 Files skipped from review as they are similar to previous changes (1)
  • examples/sites/demos/pc/app/color-select-panel/webdoc/color-select-panel.js
⏰ 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 (2)
examples/sites/demos/apis/color-select-panel.js (2)

94-106: LGTM: New prop surface looks coherent

The addition of color mode with sensible default and demo hook is good.


104-105: Verified — demo, docs registry, Vue prop wiring, and TS typings for colorMode are present.

linear-gradient.vue exists; webdoc registry lists demoId 'linear-gradient'; renderless types and chart-core expose colorMode; Vue pc component declares 'colorMode' and hue-select uses ctx.colorMode.

@GaoNeng-wWw GaoNeng-wWw requested a review from kagol September 25, 2025 01:31
@zzcr zzcr merged commit bb1c5e2 into opentiny:dev Sep 30, 2025
10 checks passed
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.

2 participants