feat: add CurveEditor component#8860
Conversation
🎨 Storybook: ✅ Built — View Storybook |
🎭 Playwright: ✅ 555 passed, 0 failed · 1 flaky📊 Browser Reports
|
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
📝 WalkthroughWalkthroughAdds an interactive SVG curve editor: new CurveEditor component and useCurveEditor composable, interpolation/histogram/LUT utilities with tests, Vue widget wrapper and registry integration, LiteGraph types/widget plumbing, and schema support for curve inputs. Changes
Sequence Diagram(s)sequenceDiagram
participant User as User
participant CurveComp as CurveEditor.vue
participant Composable as useCurveEditor
participant Model as Parent Model (v-model)
rect rgba(200,200,255,0.5)
User->>CurveComp: pointerdown / pointermove / pointerup
end
CurveComp->>Composable: handleSvgPointerDown / startDrag
Composable->>Composable: compute svgCoords, update dragIndex, modify points (immutable)
Composable->>Model: emit updated modelValue (new array)
Model-->>CurveComp: reactive modelValue update
CurveComp->>CurveComp: recompute curvePath and rerender SVG
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
📦 Bundle: 4.39 MB gzip 🔴 +2.92 kBDetailsSummary
Category Glance App Entry Points — 17.9 kB (baseline 17.9 kB) • ⚪ 0 BMain entry bundles and manifests
Status: 1 added / 1 removed Graph Workspace — 968 kB (baseline 968 kB) • ⚪ 0 BGraph editor runtime, canvas, workflow orchestration
Status: 1 added / 1 removed Views & Navigation — 68.8 kB (baseline 68.8 kB) • ⚪ 0 BTop-level views, pages, and routed surfaces
Status: 11 added / 11 removed Panels & Settings — 436 kB (baseline 436 kB) • ⚪ 0 BConfiguration panels, inspectors, and settings screens
Status: 22 added / 22 removed User & Accounts — 16 kB (baseline 16 kB) • ⚪ 0 BAuthentication, profile, and account management bundles
Status: 7 added / 7 removed Editors & Dialogs — 736 B (baseline 736 B) • ⚪ 0 BModals, dialogs, drawers, and in-app editors
Status: 1 added / 1 removed UI Components — 46.9 kB (baseline 46.9 kB) • ⚪ 0 BReusable component library chunks
Status: 10 added / 10 removed Data & Services — 2.54 MB (baseline 2.54 MB) • 🔴 +1.17 kBStores, services, APIs, and repositories
Status: 14 added / 14 removed Utilities & Hooks — 58.3 kB (baseline 58.3 kB) • ⚪ 0 BHelpers, composables, and utility bundles
Status: 13 added / 13 removed Vendor & Third-Party — 8.84 MB (baseline 8.84 MB) • ⚪ 0 BExternal libraries and shared vendor chunks
Status: 1 added / 1 removed Other — 7.63 MB (baseline 7.62 MB) • 🔴 +9.35 kBBundles that do not match a named category
Status: 109 added / 108 removed |
|
i don't think the failing tests caused by my changes, another two have the same failure tests |
fb96da6 to
2dd0e73
Compare
2dd0e73 to
9d2b4f5
Compare
There was a problem hiding this comment.
Actionable comments posted: 3
🧹 Nitpick comments (4)
src/components/curve/CurveEditor.test.ts (2)
18-18: Use the imported component reference indescribe()instead of a string literal
CurveEditoris already imported at line 6, so the describe suite can reference it directly.♻️ Proposed fix
-describe('CurveEditor', () => { +describe(CurveEditor, () => {Based on learnings: "follow the
vitest/prefer-describe-function-titlerule by usingdescribe(ComponentOrFunction, ...)instead of a string literal description when naming test suites."🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/components/curve/CurveEditor.test.ts` at line 18, The test suite currently uses a string literal in describe; update the suite to pass the imported component reference instead by replacing the string with the CurveEditor symbol (i.e., use describe(CurveEditor, ...) so the describe call references the imported CurveEditor component), ensuring imports at top (CurveEditor) remain unchanged and tests inside the suite keep their existing names and behavior.
8-16:extraPropsparameter lacks type safety — usePartial<ComponentProps<typeof CurveEditor>>The implicit
{}type onextraPropsaccepts anything, losing the benefit of TypeScript validation for prop names and types. Per the repo convention, test mount helpers should type extra props against the component's actual prop types.♻️ Proposed fix
+import type { ComponentProps } from 'vue-component-type-helpers' + -function mountEditor(points: CurvePoint[], extraProps = {}) { +function mountEditor( + points: CurvePoint[], + extraProps: Partial<ComponentProps<typeof CurveEditor>> = {} +) { return mount(CurveEditor, { props: { modelValue: points, ...extraProps } }) } -function getCurvePath(wrapper: ReturnType<typeof mount>) { +function getCurvePath(wrapper: ReturnType<typeof mountEditor>) {Based on learnings: "for test helpers like
mountComponent, type the props parameter asPartial<ComponentProps<typeof Component>>."🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/components/curve/CurveEditor.test.ts` around lines 8 - 16, The mountEditor helper currently types extraProps as an untyped {}, losing TS prop checks; update the signature of mountEditor to type extraProps as Partial<ComponentProps<typeof CurveEditor>> and add the necessary import for ComponentProps from 'vue' (or the appropriate typings source) so passing props is type-checked against CurveEditor; keep the rest of the logic (props: { modelValue: points, ...extraProps }) unchanged and leave getCurvePath as-is.src/components/curve/CurveEditor.vue (1)
62-72: Use a stable key instead of array index for deletable control points
:key="i"means Vue identifies each circle by its position in the array. When a middle point is deleted, all subsequent circles get recycled with shifted indices, which can cause incorrect pointer-capture state or stale composable state instartDrag. A point's[x, y]tuple uniquely identifies it in practice.♻️ Proposed fix
- :key="i" + :key="`${point[0]},${point[1]}`"🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/components/curve/CurveEditor.vue` around lines 62 - 72, The circles currently use the array index as the key which causes recycled DOM elements and stale pointer state when points are removed; change the key to a stable identifier derived from the point value (e.g. a string based on the point tuple) so Vue tracks each control point by its coordinates instead of position in modelValue; update the v-for key expression where the circle is rendered and ensure handlers like startDrag(i, $event) still receive the correct index (you can compute the index from modelValue when handling events if you switch the key to a point-based string).src/components/curve/curveUtils.test.ts (1)
11-11: Use function references instead of string literals indescribe()Per the
vitest/prefer-describe-function-titlerule enforced in this repo, test suites should be named by passing the tested function/component directly rather than a string.♻️ Proposed fix
-describe('createMonotoneInterpolator', () => { +describe(createMonotoneInterpolator, () => {-describe('curvesToLUT', () => { +describe(curvesToLUT, () => {-describe('histogramToPath', () => { +describe(histogramToPath, () => {Based on learnings: "In test files under
src/**/*.test.ts, follow thevitest/prefer-describe-function-titlerule by usingdescribe(ComponentOrFunction, ...)instead of a string literal description when naming test suites."Also applies to: 76-76, 109-109
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/components/curve/curveUtils.test.ts` at line 11, Replace string literal titles in the test suites with direct function references: change describe('createMonotoneInterpolator', ...) to describe(createMonotoneInterpolator, ...) in src/components/curve/curveUtils.test.ts and do the same for the other describe blocks in this file that currently use string literals (the ones flagged by the rule). Ensure the imported/defined symbol createMonotoneInterpolator is in scope for the describe call and update any other test-suite names in this file to pass the function/ component reference instead of a string.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@src/components/curve/CurveEditor.vue`:
- Line 68: The control point circles currently use a hardcoded stroke="white"
which breaks theming; change the circle stroke to use the component's theming or
prop (e.g., use stroke="currentColor" or bind to the existing curveColor prop
with a fallback to a semantic CSS variable) so the stroke follows light/dark
themes and the component's color conventions (update the circle element in
CurveEditor.vue to reference curveColor or currentColor instead of the literal
"white").
In `@src/components/curve/curveUtils.ts`:
- Around line 95-111: The histogramToPath function assumes a 256-bin histogram
causing NaNs for other lengths; update it to validate and enforce the 256-bin
contract (or handle variable-length histograms consistently): inside
histogramToPath check histogram.length and either return '' or throw if
histogram.length !== 256 (or, if supporting variable lengths, compute n =
histogram.length, use percentileIndex = Math.floor((n - 1) * 0.995) to find max
from sorted, guard against undefined max, set step = 1 / (n - 1) and loop for
(let i = 0; i < n; i++) using histogram[i] safely); ensure all references to
255/256 are replaced by n-1/n as needed and preserve the early-return when max
is 0 to avoid NaNs.
- Around line 87-91: The docblock describing "Generate a 256-entry lookup table
from curve control points" is placed above histogramToPath instead of above
curvesToLUT; move that comment block so it directly precedes the curvesToLUT
function declaration (and remove or replace any incorrect docblock above
histogramToPath), ensuring curvesToLUT has the proper description and
histogramToPath has its own appropriate docblock if needed; reference the
functions curvesToLUT and histogramToPath to locate and reattach the docblock.
---
Nitpick comments:
In `@src/components/curve/CurveEditor.test.ts`:
- Line 18: The test suite currently uses a string literal in describe; update
the suite to pass the imported component reference instead by replacing the
string with the CurveEditor symbol (i.e., use describe(CurveEditor, ...) so the
describe call references the imported CurveEditor component), ensuring imports
at top (CurveEditor) remain unchanged and tests inside the suite keep their
existing names and behavior.
- Around line 8-16: The mountEditor helper currently types extraProps as an
untyped {}, losing TS prop checks; update the signature of mountEditor to type
extraProps as Partial<ComponentProps<typeof CurveEditor>> and add the necessary
import for ComponentProps from 'vue' (or the appropriate typings source) so
passing props is type-checked against CurveEditor; keep the rest of the logic
(props: { modelValue: points, ...extraProps }) unchanged and leave getCurvePath
as-is.
In `@src/components/curve/CurveEditor.vue`:
- Around line 62-72: The circles currently use the array index as the key which
causes recycled DOM elements and stale pointer state when points are removed;
change the key to a stable identifier derived from the point value (e.g. a
string based on the point tuple) so Vue tracks each control point by its
coordinates instead of position in modelValue; update the v-for key expression
where the circle is rendered and ensure handlers like startDrag(i, $event) still
receive the correct index (you can compute the index from modelValue when
handling events if you switch the key to a point-based string).
In `@src/components/curve/curveUtils.test.ts`:
- Line 11: Replace string literal titles in the test suites with direct function
references: change describe('createMonotoneInterpolator', ...) to
describe(createMonotoneInterpolator, ...) in
src/components/curve/curveUtils.test.ts and do the same for the other describe
blocks in this file that currently use string literals (the ones flagged by the
rule). Ensure the imported/defined symbol createMonotoneInterpolator is in scope
for the describe call and update any other test-suite names in this file to pass
the function/ component reference instead of a string.
There was a problem hiding this comment.
Actionable comments posted: 5
🧹 Nitpick comments (4)
src/schemas/nodeDef/nodeDefSchemaV2.ts (1)
129-136: Consider exportingCurveInputSpectype andisCurveInputSpecguard for consistency.Every other concrete input spec in this file has both an exported TypeScript type (
ColorInputSpec,BoundingBoxInputSpec,ChartInputSpec, etc.) and for some an exportedis*InputSpecguard.zCurveInputSpecis the only one added without them, which breaks the pattern and leaves callers unable to narrow anInputSpectoCurveInputSpecwithout re-declaring the shape.💡 Suggested additions (at the bottom of the file)
+export type CurveInputSpec = z.infer<typeof zCurveInputSpec> + +export const isCurveInputSpec = ( + inputSpec: InputSpec +): inputSpec is CurveInputSpec => { + return inputSpec.type === 'CURVE' +}🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/schemas/nodeDef/nodeDefSchemaV2.ts` around lines 129 - 136, Export a TypeScript type alias and a type guard for the curve spec to match the file's pattern: add an exported type named CurveInputSpec that infers the shape from zCurveInputSpec and export a boolean type guard isCurveInputSpec (or similar) that narrows InputSpec to CurveInputSpec using zCurveInputSpec.safeParse or instance checks. Reference zCurveInputSpec when creating the type alias and when implementing the guard so callers can import CurveInputSpec and call isCurveInputSpec(...) to narrow an InputSpec to the CURVE variant.src/components/curve/CurveEditor.test.ts (1)
67-76:expect(d).toContain('L')is tightly coupled to the path command format.This assertion passes today because the curve generator samples points and emits
L(lineto) commands. If the rendering is ever changed to use d3-shape's smooth-curve generators (which emitCcubic-bezier commands), this assertion—and thesplit(/[ML]/)x-range parsing on lines 84–89—will silently produce wrong results or fail. Consider documenting the assumption, or testing the rendered shape semantically (e.g., checking that the curve path exists and is non-empty) rather than encoding the SVG command syntax.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/components/curve/CurveEditor.test.ts` around lines 67 - 76, The test "generates path starting with M and containing L segments" is tightly coupled to SVG command letters — instead of asserting expect(d).toContain('L'), use a semantic check: locate the path via getCurvePath(wrapper) and assert it's present and non-empty (d is a non-empty string and not just "M"), or better call the DOM SVG path API (e.g., element.getTotalLength()) and assert the length is > 0 to prove a rendered curve exists; update the test around mountEditor / getCurvePath to remove the 'L' assertion and replace it with the non-empty/totalLength check.src/composables/useCurveEditor.ts (1)
126-130: ConsideruseEventListenerfrom VueUse for drag listener cleanup.The three listeners added in
startDragare manually removed inendDragand guarded viacleanupDrag+onBeforeUnmount. VueUse'suseEventListenerwith a reactive/conditional target can consolidate cleanup automatically and aligns with the VueUse-first composable guideline.Based on learnings: "Applies to
**/composables/**/*.ts: Leverage VueUse functions for performance-enhancing composables."🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/composables/useCurveEditor.ts` around lines 126 - 130, Replace the manual add/remove listener pattern in startDrag/endDrag by using VueUse's useEventListener for the svg target so cleanup is automatic: locate startDrag, endDrag and the cleanupDrag handling in useCurveEditor.ts and swap svg.addEventListener('pointermove', onMove)/('pointerup', endDrag)/('lostpointercapture', endDrag) and the cleanupDrag assignment with useEventListener calls (reactive/conditional target bound to svg) for 'pointermove', 'pointerup' and 'lostpointercapture'; remove the manual removeEventListener calls and the cleanupDrag/onBeforeUnmount guard so VueUse handles teardown.src/renderer/extensions/vueNodes/widgets/composables/useCurveWidget.ts (1)
9-9: Use a function declaration instead of a function expression.♻️ Proposed fix
-export const useCurveWidget = (): ComfyWidgetConstructorV2 => { - return (node: LGraphNode, inputSpec: InputSpecV2): ICurveWidget => { +export function useCurveWidget(): ComfyWidgetConstructorV2 { + return function (node: LGraphNode, inputSpec: InputSpecV2): ICurveWidget {As per coding guidelines: "Do not use function expressions if it's possible to use function declarations instead."
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/renderer/extensions/vueNodes/widgets/composables/useCurveWidget.ts` at line 9, The exported function is currently declared as a function expression named useCurveWidget; change it to a function declaration to follow the guideline—replace "export const useCurveWidget = (): ComfyWidgetConstructorV2 => { ... }" with "export function useCurveWidget(): ComfyWidgetConstructorV2 { ... }", keeping the same return type and body and preserving the exported symbol so callers of useCurveWidget are unaffected.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@src/components/curve/CurveEditor.test.ts`:
- Line 18: The test suite is using a string literal; change
describe('CurveEditor', ...) to use the actual component reference
describe(CurveEditor, ...) so it satisfies
vitest/prefer-describe-function-title; ensure the CurveEditor symbol is imported
or referenced in the test file (e.g., the existing CurveEditor import or export
used by tests) so the identifier is in scope before updating the describe call.
In `@src/components/curve/CurveEditor.vue`:
- Around line 84-87: The default prop curveColor in CurveEditor.vue is set to
'white' which disappears on light backgrounds; update the defineProps default
for curveColor from 'white' to 'currentColor' (or a semantic CSS variable like
'--foreground') so the histogram fill, curve stroke and control circle fills
inherit the host foreground; ensure the components and bindings that reference
curveColor (the histogram, the curve path, and control circle rendering logic)
keep using that prop so they automatically pick up the inherited color.
In `@src/components/curve/WidgetCurve.vue`:
- Around line 2-4: The div wrapping CurveEditor uses a non-existent class
"widget-expands"; update the wrapper in WidgetCurve.vue (the div containing
<CurveEditor v-model="modelValue" />) to use Tailwind utility classes instead —
for parity with WidgetImageCrop.vue replace "widget-expands" with the equivalent
utilities such as "relative flex h-full w-full flex-col" (add "gap-1" if spacing
is needed) and remove any custom class references so only Tailwind classes are
used.
In `@src/composables/useCurveEditor.ts`:
- Around line 70-86: handleSvgPointerDown currently adds a new point then calls
startDrag(newIndex, e), but if e.ctrlKey is true startDrag will immediately
delete that new point; to avoid the spurious add/remove, add an early-exit guard
in handleSvgPointerDown to return if e.ctrlKey is set (after checking e.button
=== 0) so you only proceed to create a new point when ctrl is not pressed;
update the logic in handleSvgPointerDown (which uses svgCoords,
findNearestPoint, modelValue and startDrag) to check e.ctrlKey before creating
and assigning newPoints.
In `@src/renderer/extensions/vueNodes/widgets/composables/useCurveWidget.ts`:
- Around line 12-16: The current defaultValue assignment unsafely asserts
inputSpec as { default?: CurvePoint[] } — replace that with proper type
narrowing: use the provided isCurveInputSpec(inputSpec) type guard to narrow
InputSpec to the CURVE case before reading inputSpec.default, or if you prefer a
local assertion, read (inputSpec.default as CurvePoint[] | undefined) and
fallback to [[0,0],[1,1]]; update the defaultValue initialization in
useCurveWidget.ts (the defaultValue constant) to perform the narrowing via
isCurveInputSpec or the narrower assertion so you no longer cast the whole
inputSpec object.
---
Duplicate comments:
In `@src/components/curve/CurveEditor.vue`:
- Around line 61-72: The circle elements in CurveEditor.vue hardcode
stroke="white", breaking theming; change the SVG circles to use a themed stroke
value instead (e.g., bind :stroke to a reactive/prop/computed like strokeColor
or to a CSS variable) and provide that value via the existing theming (use
curveColor fallback or a new prop/computed). Update the <circle> that references
modelValue and startDrag to use :stroke="strokeColor" (or
:stroke="getStrokeColor()") and add the corresponding prop/computed (and
default) so the component respects app/theme colors rather than the literal
"white".
---
Nitpick comments:
In `@src/components/curve/CurveEditor.test.ts`:
- Around line 67-76: The test "generates path starting with M and containing L
segments" is tightly coupled to SVG command letters — instead of asserting
expect(d).toContain('L'), use a semantic check: locate the path via
getCurvePath(wrapper) and assert it's present and non-empty (d is a non-empty
string and not just "M"), or better call the DOM SVG path API (e.g.,
element.getTotalLength()) and assert the length is > 0 to prove a rendered curve
exists; update the test around mountEditor / getCurvePath to remove the 'L'
assertion and replace it with the non-empty/totalLength check.
In `@src/composables/useCurveEditor.ts`:
- Around line 126-130: Replace the manual add/remove listener pattern in
startDrag/endDrag by using VueUse's useEventListener for the svg target so
cleanup is automatic: locate startDrag, endDrag and the cleanupDrag handling in
useCurveEditor.ts and swap svg.addEventListener('pointermove',
onMove)/('pointerup', endDrag)/('lostpointercapture', endDrag) and the
cleanupDrag assignment with useEventListener calls (reactive/conditional target
bound to svg) for 'pointermove', 'pointerup' and 'lostpointercapture'; remove
the manual removeEventListener calls and the cleanupDrag/onBeforeUnmount guard
so VueUse handles teardown.
In `@src/renderer/extensions/vueNodes/widgets/composables/useCurveWidget.ts`:
- Line 9: The exported function is currently declared as a function expression
named useCurveWidget; change it to a function declaration to follow the
guideline—replace "export const useCurveWidget = (): ComfyWidgetConstructorV2 =>
{ ... }" with "export function useCurveWidget(): ComfyWidgetConstructorV2 { ...
}", keeping the same return type and body and preserving the exported symbol so
callers of useCurveWidget are unaffected.
In `@src/schemas/nodeDef/nodeDefSchemaV2.ts`:
- Around line 129-136: Export a TypeScript type alias and a type guard for the
curve spec to match the file's pattern: add an exported type named
CurveInputSpec that infers the shape from zCurveInputSpec and export a boolean
type guard isCurveInputSpec (or similar) that narrows InputSpec to
CurveInputSpec using zCurveInputSpec.safeParse or instance checks. Reference
zCurveInputSpec when creating the type alias and when implementing the guard so
callers can import CurveInputSpec and call isCurveInputSpec(...) to narrow an
InputSpec to the CURVE variant.
src/renderer/extensions/vueNodes/widgets/composables/useCurveWidget.ts
Outdated
Show resolved
Hide resolved
There was a problem hiding this comment.
🧹 Nitpick comments (1)
src/schemas/nodeDef/nodeDefSchemaV2.ts (1)
203-203: Consider adding anisCurveInputSpectype guard for consistency.The file provides narrowing guards for several other exported specs (e.g.
isChartInputSpec). TheCurveWidgetdownstream will likely need to narrowInputSpec → CurveInputSpec; a guard here would make that ergonomic and consistent with the existing pattern.✨ Suggested addition
export type CurveInputSpec = z.infer<typeof zCurveInputSpec>Append after the existing
isChartInputSpecguard:+export function isCurveInputSpec( + inputSpec: InputSpec +): inputSpec is CurveInputSpec { + return inputSpec.type === 'CURVE' +}🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/schemas/nodeDef/nodeDefSchemaV2.ts` at line 203, Add an exported type guard named isCurveInputSpec to mirror the existing pattern (e.g., isChartInputSpec): implement isCurveInputSpec(spec: unknown): spec is CurveInputSpec by using zCurveInputSpec.safeParse(spec).success (or equivalent zod validation) to narrow the InputSpec to CurveInputSpec, and export it so downstream consumers like CurveWidget can reliably narrow InputSpec → CurveInputSpec.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Nitpick comments:
In `@src/schemas/nodeDef/nodeDefSchemaV2.ts`:
- Line 203: Add an exported type guard named isCurveInputSpec to mirror the
existing pattern (e.g., isChartInputSpec): implement isCurveInputSpec(spec:
unknown): spec is CurveInputSpec by using
zCurveInputSpec.safeParse(spec).success (or equivalent zod validation) to narrow
the InputSpec to CurveInputSpec, and export it so downstream consumers like
CurveWidget can reliably narrow InputSpec → CurveInputSpec.
src/renderer/extensions/vueNodes/widgets/composables/useCurveWidget.ts
Outdated
Show resolved
Hide resolved
bb51b65 to
2e554d3
Compare
Summary
Prerequisite for upcoming native color correction nodes (ColorCurves).
Reusable curve editor with monotone cubic Hermite interpolation, drag-to-add/move/delete control points, and SVG-based rendering.
Includes CurvePoint type, LUT generation utility, and useCurveEditor composable for interaction logic.
Screenshots (if applicable)
2026-02-13.20-26-52.mp4
┆Issue is synchronized with this Notion page by Unito