From 1a67bca39ddd7fc9c0b28996777629ad45ccfe9b Mon Sep 17 00:00:00 2001 From: Swarnim Doegar Date: Fri, 16 Jan 2026 16:39:14 +0530 Subject: [PATCH 01/10] Support negative values in position x and y for image and text layer --- packages/imagekit-editor-dev/src/schema/index.ts | 16 ++++++++-------- .../src/schema/transformation.ts | 16 ++++++---------- 2 files changed, 14 insertions(+), 18 deletions(-) diff --git a/packages/imagekit-editor-dev/src/schema/index.ts b/packages/imagekit-editor-dev/src/schema/index.ts index 94fec2b..3bbe634 100644 --- a/packages/imagekit-editor-dev/src/schema/index.ts +++ b/packages/imagekit-editor-dev/src/schema/index.ts @@ -2199,7 +2199,7 @@ export const transformationSchema: TransformationSchema[] = [ transformationKey: "x", transformationGroup: "textLayer", helpText: "Specify horizontal offset for the text.", - examples: ["10", "bw_div_2"], + examples: ["10", "-20", "N30", "bw_div_2"], }, { label: "Position Y", @@ -2209,7 +2209,7 @@ export const transformationSchema: TransformationSchema[] = [ transformationKey: "y", transformationGroup: "textLayer", helpText: "Specify vertical offset for the text.", - examples: ["10", "bh_div_2"], + examples: ["10", "-20", "N30", "bh_div_2"], }, { label: "Font Size", @@ -2517,7 +2517,7 @@ export const transformationSchema: TransformationSchema[] = [ transformationKey: "x", transformationGroup: "imageLayer", helpText: "Specify the horizontal offset for the overlay image.", - examples: ["10"], + examples: ["10", "-20", "N30", "bw_div_2"], }, { label: "Position Y", @@ -2527,7 +2527,7 @@ export const transformationSchema: TransformationSchema[] = [ transformationKey: "y", transformationGroup: "imageLayer", helpText: "Specify the vertical offset for the overlay image.", - examples: ["10"], + examples: ["10", "-20", "N30", "bh_div_2"], }, { label: "Opacity", @@ -2926,13 +2926,13 @@ export const transformationFormatters: Record< typeof values.positionX === "number" || typeof values.positionX === "string" ) { - position.x = values.positionX + position.x = values.positionX.toString().replace(/^-/,"N") } if ( typeof values.positionY === "number" || typeof values.positionY === "string" ) { - position.y = values.positionY + position.y = values.positionY.toString().replace(/^-/,"N") } if (Object.keys(position).length > 0) { overlay.position = position @@ -3032,10 +3032,10 @@ export const transformationFormatters: Record< // Positioning via x/y or focus anchor const position: Record = {} if (values.positionX) { - position.x = values.positionX + position.x = values.positionX.toString().replace(/^-/,"N") } if (values.positionY) { - position.y = values.positionY + position.y = values.positionY.toString().replace(/^-/,"N") } if (Object.keys(position).length > 0) { diff --git a/packages/imagekit-editor-dev/src/schema/transformation.ts b/packages/imagekit-editor-dev/src/schema/transformation.ts index 78eb69c..8a798d4 100644 --- a/packages/imagekit-editor-dev/src/schema/transformation.ts +++ b/packages/imagekit-editor-dev/src/schema/transformation.ts @@ -78,10 +78,8 @@ export const aspectRatioValidator = z.any().superRefine((val, ctx) => { }) const layerXNumber = z.coerce - .number({ invalid_type_error: "Should be a number." }) - .min(0, { - message: "Layer X must be a positive number.", - }) + .string() + .regex(/^[N-]?\d+(\.\d{1,2})?$/) const layerXExpr = z .string() @@ -97,15 +95,13 @@ export const layerXValidator = z.any().superRefine((val, ctx) => { } ctx.addIssue({ code: z.ZodIssueCode.custom, - message: "Layer X must be a positive number or a valid expression string.", + message: "Layer X must be a number or a valid expression string.", }) }) const layerYNumber = z.coerce - .number({ invalid_type_error: "Should be a number." }) - .min(0, { - message: "Layer Y must be a positive number.", - }) + .string() + .regex(/^[N-]?\d+(\.\d{1,2})?$/) const layerYExpr = z .string() @@ -121,6 +117,6 @@ export const layerYValidator = z.any().superRefine((val, ctx) => { } ctx.addIssue({ code: z.ZodIssueCode.custom, - message: "Layer Y must be a positive number or a valid expression string.", + message: "Layer Y must be a number or a valid expression string.", }) }) From 560cdbaf98e34c0c000c395e97bd3d0eedb3b6fa Mon Sep 17 00:00:00 2001 From: Swarnim Doegar Date: Mon, 19 Jan 2026 14:00:09 +0530 Subject: [PATCH 02/10] Add support for duplicate and rename in L1 sidebar --- .../src/components/common/Hover.tsx | 38 +++++++- .../sidebar/sortable-transformation-item.tsx | 91 ++++++++++++++++++- .../sidebar/transformation-config-sidebar.tsx | 8 +- 3 files changed, 131 insertions(+), 6 deletions(-) diff --git a/packages/imagekit-editor-dev/src/components/common/Hover.tsx b/packages/imagekit-editor-dev/src/components/common/Hover.tsx index f3cfd1d..d97adec 100644 --- a/packages/imagekit-editor-dev/src/components/common/Hover.tsx +++ b/packages/imagekit-editor-dev/src/components/common/Hover.tsx @@ -1,5 +1,5 @@ import { Box, type BoxProps, Flex, type FlexProps } from "@chakra-ui/react" -import { useState } from "react" +import { useState, useEffect, useRef, useCallback } from "react" interface FlexHoverProps extends FlexProps { children(isHover: boolean): JSX.Element @@ -15,6 +15,41 @@ const Hover = ({ }: BoxHoverProps | FlexHoverProps): JSX.Element => { const [isHover, setIsHover] = useState(false) + const hoverAreaRef = useRef(null) + const debounceTimerRef = useRef(null) + + const handleClickOutside = useCallback((event: MouseEvent): void => { + console.log('handleClickOutside called') + const hoverArea = hoverAreaRef.current + if ( + hoverArea && + !hoverArea.contains(event.target as Node) + ) { + setIsHover(false) + } + }, []) + + const debouncedHandleClickOutside = useCallback((event: MouseEvent): void => { + if (debounceTimerRef.current) { + clearTimeout(debounceTimerRef.current) + } + debounceTimerRef.current = setTimeout(() => { + handleClickOutside(event) + }, 100) + }, [handleClickOutside]) + + useEffect(() => { + document.addEventListener('mousedown', handleClickOutside) + document.addEventListener('mouseover', debouncedHandleClickOutside) + return () => { + document.removeEventListener('mousedown', handleClickOutside) + document.removeEventListener('mouseover', debouncedHandleClickOutside) + if (debounceTimerRef.current) { + clearTimeout(debounceTimerRef.current) + } + } + }, [handleClickOutside, debouncedHandleClickOutside]) + if (props.display === "flex") { return ( { setIsHover(false) }} + ref={hoverAreaRef} > {children(isHover)} diff --git a/packages/imagekit-editor-dev/src/components/sidebar/sortable-transformation-item.tsx b/packages/imagekit-editor-dev/src/components/sidebar/sortable-transformation-item.tsx index 6bca676..ba4122f 100644 --- a/packages/imagekit-editor-dev/src/components/sidebar/sortable-transformation-item.tsx +++ b/packages/imagekit-editor-dev/src/components/sidebar/sortable-transformation-item.tsx @@ -8,7 +8,10 @@ import { MenuList, Text, Tooltip, + Input, + Tag } from "@chakra-ui/react" +import { useState, useEffect, useRef } from "react" import { useSortable } from "@dnd-kit/sortable" import { CSS } from "@dnd-kit/utilities" import { PiArrowDown } from "@react-icons/all-files/pi/PiArrowDown" @@ -21,6 +24,8 @@ import { PiPencilSimple } from "@react-icons/all-files/pi/PiPencilSimple" import { PiPlus } from "@react-icons/all-files/pi/PiPlus" import { PiTrash } from "@react-icons/all-files/pi/PiTrash" import { RxTransform } from "@react-icons/all-files/rx/RxTransform" +import { PiCopy } from "@react-icons/all-files/pi/PiCopy" +import { PiCursorText } from "@react-icons/all-files/pi/PiCursorText" import { type Transformation, useEditorStore } from "../../store" import Hover from "../common/Hover" @@ -54,6 +59,8 @@ export const SortableTransformationItem = ({ _setSelectedTransformationKey, _setTransformationToEdit, _internalState, + addTransformation, + updateTransformation, } = useEditorStore() const style = transform @@ -70,6 +77,27 @@ export const SortableTransformationItem = ({ _internalState.transformationToEdit?.position === "inplace" && _internalState.transformationToEdit?.transformationId === transformation.id + const [isRenaming, setIsRenaming] = useState(false); + + const renamingBoxRef = useRef(null) + + useEffect(() => { + const handleClickOutside = (event: MouseEvent): void => { + const renamingBox = renamingBoxRef.current + if ( + renamingBox && + !renamingBox.contains(event.target as Node) + ) { + setIsRenaming(false) + } + } + + document.addEventListener('mousedown', handleClickOutside) + return () => { + document.removeEventListener('mousedown', handleClickOutside) + } + }, []) + return ( {(isHover) => ( @@ -87,7 +115,13 @@ export const SortableTransformationItem = ({ minH="8" alignItems="center" style={style} - onClick={() => { + onClick={(e) => { + // Triple click to rename + if (e.detail === 3) { + e.stopPropagation() + setIsRenaming(true); + return + } _setSidebarState("config") _setSelectedTransformationKey(transformation.key) _setTransformationToEdit(transformation.id, "inplace") @@ -116,9 +150,34 @@ export const SortableTransformationItem = ({ )} + {isRenaming ? ( + + { + if (e.key === "Enter") { + const newName = e.target.value.trim() + if (newName.length > 0) { + updateTransformation(transformation.id, { ...transformation, name: newName }); + } + setIsRenaming(false) + } else if (e.key === "Escape") { + setIsRenaming(false) + } + }} + /> + + Press { + navigator.platform.toLowerCase().includes('mac') ? 'Return' : 'Enter' + } to save, Esc to cancel + + + ) : ( {transformation.name} - + )} {isHover && ( @@ -181,6 +240,22 @@ export const SortableTransformationItem = ({ > Add transformation after + } + onClick={(e) => { + e.stopPropagation() + const currentIndex = transformations.findIndex( + (t) => t.id === transformation.id, + ) + const transformationId = addTransformation({ + ...transformation, + }, currentIndex + 1); + _setSidebarState("config") + _setTransformationToEdit(transformationId, "inplace") + }} + > + Duplicate + } onClick={(e) => { @@ -192,6 +267,18 @@ export const SortableTransformationItem = ({ > Edit transformation + } + onClick={(e) => { + e.stopPropagation() + setIsRenaming(true); + _setSidebarState("config") + _setSelectedTransformationKey(transformation.key) + _setTransformationToEdit(transformation.id, "inplace") + }} + > + Rename + } onClick={(e) => { diff --git a/packages/imagekit-editor-dev/src/components/sidebar/transformation-config-sidebar.tsx b/packages/imagekit-editor-dev/src/components/sidebar/transformation-config-sidebar.tsx index 24a8bb6..af58c02 100644 --- a/packages/imagekit-editor-dev/src/components/sidebar/transformation-config-sidebar.tsx +++ b/packages/imagekit-editor-dev/src/components/sidebar/transformation-config-sidebar.tsx @@ -83,14 +83,16 @@ export const TransformationConfigSidebar: React.FC = () => { const transformationToEdit = _internalState.transformationToEdit - const editedTransformationValue = useMemo(() => { + const editedTransformation = useMemo(() => { if (!transformationToEdit) return undefined return transformations.find( (transformation) => transformation.id === transformationToEdit.transformationId, - )?.value as Record | undefined + ) }, [transformations, transformationToEdit]) + const editedTransformationValue = editedTransformation?.value as Record | undefined + const defaultValues = useMemo(() => { if ( transformationToEdit && @@ -166,7 +168,7 @@ export const TransformationConfigSidebar: React.FC = () => { if (transformationToEdit && transformationToEdit.position === "inplace") { updateTransformation(transformationToEdit.transformationId, { type: "transformation", - name: selectedTransformation.name, + name: editedTransformation?.name ?? selectedTransformation.name, key: selectedTransformation.key, value: data, }) From 645fd1d8231142f5788f4c0da29a766d771818ee Mon Sep 17 00:00:00 2001 From: Swarnim Doegar Date: Tue, 20 Jan 2026 18:03:58 +0530 Subject: [PATCH 03/10] Add advanced padding input support --- .../src/components/common/PaddingInput.tsx | 267 ++++++++++++++++++ .../sidebar/transformation-config-sidebar.tsx | 15 +- .../imagekit-editor-dev/src/schema/index.ts | 51 +++- 3 files changed, 327 insertions(+), 6 deletions(-) create mode 100644 packages/imagekit-editor-dev/src/components/common/PaddingInput.tsx diff --git a/packages/imagekit-editor-dev/src/components/common/PaddingInput.tsx b/packages/imagekit-editor-dev/src/components/common/PaddingInput.tsx new file mode 100644 index 0000000..1f347cb --- /dev/null +++ b/packages/imagekit-editor-dev/src/components/common/PaddingInput.tsx @@ -0,0 +1,267 @@ +import { + Box, + Flex, + HStack, + Icon, + Text, + Input, + InputGroup, + InputLeftElement, + IconButton, + FormErrorMessage, + useColorModeValue, +} from "@chakra-ui/react" +import { set } from "lodash" +import type * as React from "react" +import { useState, useEffect } from "react" +import { LuArrowLeftToLine } from "@react-icons/all-files/lu/LuArrowLeftToLine" +import { LuArrowRightToLine } from "@react-icons/all-files/lu/LuArrowRightToLine" +import { LuArrowUpToLine } from "@react-icons/all-files/lu/LuArrowUpToLine" +import { LuArrowDownToLine } from "@react-icons/all-files/lu/LuArrowDownToLine" +import { TbBoxPadding } from "@react-icons/all-files/tb/TbBoxPadding" +import { MdOutlinePadding } from "@react-icons/all-files/md/MdOutlinePadding" +import { FieldErrors } from "react-hook-form" + + +type PaddingInputFieldProps = { + id?: string + onChange: (value: number | PaddingObject | string) => void + errors?: FieldErrors> + name: string +} + +type PaddingObject = { + top: number | null + right: number | null + bottom: number | null + left: number | null +} + +function getUpdatedPaddingValue( + current: number | PaddingObject | null | string, + side: "top" | "right" | "bottom" | "left" | "all", + value: string, + mode: "uniform" | "individual" +): number | PaddingObject | null | string { + let inputValue: number | PaddingObject | null | string + try { + inputValue = JSON.parse(value) + } catch { + inputValue = value + } + if (mode === "uniform") { + if (typeof inputValue === "number") { + return inputValue + } else if (inputValue === null) { + return null + } else if (typeof inputValue === "string") { + return inputValue + } else { + const { top, right, bottom, left } = inputValue + if (top === right && top === bottom && top === left) { + return top + } else { + return null + } + } + } else { + let commonValue: number | null = null + if (typeof inputValue === "number") { + commonValue = inputValue + } + const updatedPadding = current && typeof current === "object" + ? { ...current } + : { top: commonValue, right: commonValue, bottom: commonValue, left: commonValue } + if (side !== "all") { + set(updatedPadding, side, inputValue) + } + return updatedPadding + } +} + +export const PaddingInputField: React.FC = ({ + id, + onChange, + errors, + name: propertyName, +}) => { + const [paddingMode, setPaddingMode] = useState<"uniform" | "individual">("uniform") + const [paddingValue, setPaddingValue] = useState("") + const errorRed = useColorModeValue("red.500", "red.300") + + useEffect(() => { + const formatPaddingValue = (value: number | PaddingObject | null | string): string | PaddingObject => { + if (value === null) return "" + if (typeof value === "number") { + return value.toString() + } else if (typeof value === "string") { + return value + } else { + return value; + } + } + const formattedValue = formatPaddingValue(paddingValue) + onChange(formattedValue) + }, [paddingValue]) + + + return ( + + + { paddingMode === "uniform" ? ( + + { + const val = e.target.value + setPaddingValue(getUpdatedPaddingValue( + paddingValue, + "all", + val, + paddingMode + )) + }} + value={["number", "string"].includes(typeof paddingValue) ? paddingValue : ""} + placeholder="Uniform Padding" + isInvalid={!!errors?.[propertyName]} + /> + {errors?.[propertyName]?.message} + + ) : ( + <> + + + + + + { + const val = e.target.value + setPaddingValue(getUpdatedPaddingValue( + paddingValue, + "top", + val, + paddingMode + )) + }} + value={typeof paddingValue === "object" ? paddingValue?.top ?? "" : ""} + placeholder="Top" + isInvalid={!!errors?.[propertyName]?.top} + /> + + {errors?.[propertyName]?.top?.message} + + + + + + + + { + const val = e.target.value + setPaddingValue(getUpdatedPaddingValue( + paddingValue, + "right", + val, + paddingMode + )) + }} + value={typeof paddingValue === "object" ? paddingValue?.right ?? "" : ""} + placeholder="Right" + isInvalid={!!errors?.[propertyName]?.right} + /> + + {errors?.[propertyName]?.right?.message} + + + + + + + + { + const val = e.target.value + setPaddingValue(getUpdatedPaddingValue( + paddingValue, + "bottom", + val, + paddingMode + )) + }} + value={typeof paddingValue === "object" ? paddingValue?.bottom ?? "" : ""} + placeholder="Bottom" + isInvalid={!!errors?.[propertyName]?.bottom} + /> + + {errors?.[propertyName]?.bottom?.message} + + + + + + + + { + const val = e.target.value + setPaddingValue(getUpdatedPaddingValue( + paddingValue, + "left", + val, + paddingMode + )) + }} + value={typeof paddingValue === "object" ? paddingValue?.left ?? "" : ""} + placeholder="Left" + isInvalid={!!errors?.[propertyName]?.left} + /> + + {errors?.[propertyName]?.left?.message} + + + + ) } + + + : } + onClick={() => { + const newPaddingMode = paddingMode === "uniform" ? "individual" : "uniform" + setPaddingValue(getUpdatedPaddingValue( + paddingValue, + "all", + JSON.stringify(paddingValue), + newPaddingMode + )) + setPaddingMode(newPaddingMode) + }} + mb={2} + variant="ghost" + /> + + + ) +} + +export default PaddingInputField diff --git a/packages/imagekit-editor-dev/src/components/sidebar/transformation-config-sidebar.tsx b/packages/imagekit-editor-dev/src/components/sidebar/transformation-config-sidebar.tsx index af58c02..68577f5 100644 --- a/packages/imagekit-editor-dev/src/components/sidebar/transformation-config-sidebar.tsx +++ b/packages/imagekit-editor-dev/src/components/sidebar/transformation-config-sidebar.tsx @@ -55,6 +55,7 @@ import { SidebarBody } from "./sidebar-body" import { SidebarFooter } from "./sidebar-footer" import { SidebarHeader } from "./sidebar-header" import { SidebarRoot } from "./sidebar-root" +import PaddingInputField from "../common/PaddingInput" export const TransformationConfigSidebar: React.FC = () => { const { @@ -133,6 +134,7 @@ export const TransformationConfigSidebar: React.FC = () => { watch, setValue, control, + trigger, } = useForm>({ resolver: zodResolver(selectedTransformation?.schema ?? z.object({})), defaultValues: defaultValues, @@ -292,7 +294,7 @@ export const TransformationConfigSidebar: React.FC = () => { return true }) .map((field: TransformationField) => ( - + {field.label} @@ -560,6 +562,17 @@ export const TransformationConfigSidebar: React.FC = () => { {...field.fieldProps} /> ) : null} + {field.fieldType === "padding-input" ? ( + { + setValue(field.name, value) + trigger(field.name) + }} + errors={errors} + name={field.name} + {...field.fieldProps} + /> + ) : null} {String( errors[field.name as keyof typeof errors]?.message ?? "", diff --git a/packages/imagekit-editor-dev/src/schema/index.ts b/packages/imagekit-editor-dev/src/schema/index.ts index 3bbe634..df35cbd 100644 --- a/packages/imagekit-editor-dev/src/schema/index.ts +++ b/packages/imagekit-editor-dev/src/schema/index.ts @@ -2122,11 +2122,35 @@ export const transformationSchema: TransformationSchema[] = [ innerAlignment: z .enum(["left", "right", "center"]) .default("center"), - padding: z.coerce - .number({ + padding: z.union([ + z.coerce.number({ invalid_type_error: "Should be a number.", - }) - .optional(), + }).min(0, { + message: "Negative values are not allowed.", + }), + z.object({ + top: z.coerce.number({ + invalid_type_error: "Should be a number.", + }).min(0, { + message: "Negative values are not allowed.", + }), + right: z.coerce.number({ + invalid_type_error: "Should be a number.", + }).min(0, { + message: "Negative values are not allowed.", + }), + bottom: z.coerce.number({ + invalid_type_error: "Should be a number.", + }).min(0, { + message: "Negative values are not allowed.", + }), + left: z.coerce.number({ + invalid_type_error: "Should be a number.", + }).min(0, { + message: "Negative values are not allowed.", + }), + }), + ]).optional(), opacity: z .union([ z.coerce @@ -2317,7 +2341,7 @@ export const transformationSchema: TransformationSchema[] = [ { label: "Padding", name: "padding", - fieldType: "input", + fieldType: "padding-input", isTransformation: true, transformationKey: "padding", transformationGroup: "textLayer", @@ -2861,8 +2885,25 @@ export const transformationFormatters: Record< typeof values.padding === "string" ) { overlayTransform.padding = values.padding + } else if (typeof values.padding === "object" && values.padding !== null) { + const { top, right, bottom, left } = values.padding as { + top: number + right: number + bottom: number + left: number + } + let paddingString: string; + if (top === right && top === bottom && top === left) { + paddingString = String(top) + } else if (top === bottom && right === left) { + paddingString = `${top}_${right}` + } else { + paddingString = `${top}_${right}_${bottom}_${left}` + } + overlayTransform.padding = paddingString } + if (Array.isArray(values.flip) && values.flip.length > 0) { const flip = [] if (values.flip.includes("horizontal")) { From 117c4a79fdc6d809eaccc344f919bd9d03936233 Mon Sep 17 00:00:00 2001 From: Swarnim Doegar Date: Tue, 20 Jan 2026 18:04:06 +0530 Subject: [PATCH 04/10] Remove unwanted console logs --- packages/imagekit-editor-dev/src/components/common/Hover.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/imagekit-editor-dev/src/components/common/Hover.tsx b/packages/imagekit-editor-dev/src/components/common/Hover.tsx index d97adec..09f7929 100644 --- a/packages/imagekit-editor-dev/src/components/common/Hover.tsx +++ b/packages/imagekit-editor-dev/src/components/common/Hover.tsx @@ -19,7 +19,6 @@ const Hover = ({ const debounceTimerRef = useRef(null) const handleClickOutside = useCallback((event: MouseEvent): void => { - console.log('handleClickOutside called') const hoverArea = hoverAreaRef.current if ( hoverArea && From 5a13741b7b80c928b341ebc7ca03016bbb70b0ae Mon Sep 17 00:00:00 2001 From: Swarnim Doegar Date: Wed, 21 Jan 2026 16:47:50 +0530 Subject: [PATCH 05/10] Fix Icon Button in padding input --- .../src/components/common/PaddingInput.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/imagekit-editor-dev/src/components/common/PaddingInput.tsx b/packages/imagekit-editor-dev/src/components/common/PaddingInput.tsx index 1f347cb..f6e5ea1 100644 --- a/packages/imagekit-editor-dev/src/components/common/PaddingInput.tsx +++ b/packages/imagekit-editor-dev/src/components/common/PaddingInput.tsx @@ -245,7 +245,7 @@ export const PaddingInputField: React.FC = ({ : } + icon={paddingMode === "uniform" ? : } onClick={() => { const newPaddingMode = paddingMode === "uniform" ? "individual" : "uniform" setPaddingValue(getUpdatedPaddingValue( @@ -257,7 +257,7 @@ export const PaddingInputField: React.FC = ({ setPaddingMode(newPaddingMode) }} mb={2} - variant="ghost" + variant="outline" /> From 7663b96035032432404e0918b952d072b9bc100d Mon Sep 17 00:00:00 2001 From: Swarnim Doegar Date: Wed, 21 Jan 2026 19:25:07 +0530 Subject: [PATCH 06/10] Add focus support in image overlay --- .../src/components/common/AnchorField.tsx | 7 +- .../imagekit-editor-dev/src/schema/index.ts | 217 ++++++++++++++++-- 2 files changed, 206 insertions(+), 18 deletions(-) diff --git a/packages/imagekit-editor-dev/src/components/common/AnchorField.tsx b/packages/imagekit-editor-dev/src/components/common/AnchorField.tsx index 885f919..792814b 100644 --- a/packages/imagekit-editor-dev/src/components/common/AnchorField.tsx +++ b/packages/imagekit-editor-dev/src/components/common/AnchorField.tsx @@ -78,7 +78,12 @@ const AnchorField: React.FC = ({ minWidth="0" p="0" isDisabled={!positions.includes(position.value)} - onClick={() => onChange(position.value)} + onClick={() => { + if (value === position.value) { + return onChange("") + } + onChange(position.value) + }} borderRadius="md" border={ value === position.value diff --git a/packages/imagekit-editor-dev/src/schema/index.ts b/packages/imagekit-editor-dev/src/schema/index.ts index df35cbd..d2ae7bb 100644 --- a/packages/imagekit-editor-dev/src/schema/index.ts +++ b/packages/imagekit-editor-dev/src/schema/index.ts @@ -839,17 +839,6 @@ export const transformationSchema: TransformationSchema[] = [ }) } if (val.focus === "coordinates") { - const hasXY = val.x || val.y - const hasXCYC = val.xc || val.yc - - if (hasXY && hasXCYC) { - ctx.addIssue({ - code: z.ZodIssueCode.custom, - message: "Choose either x/y or xc/yc, not both", - path: [], - }) - } - if (val.coordinateMethod === "topleft") { if (!val.x && !val.y) { ctx.addIssue({ @@ -2470,6 +2459,14 @@ export const transformationSchema: TransformationSchema[] = [ invalid_type_error: "Should be a number.", }) .optional(), + focus: z.string().optional(), + focusAnchor: z.string().optional(), + focusObject: z.string().optional(), + coordinateMethod: z.string().optional(), + x: z.string().optional(), + y: z.string().optional(), + xc: z.string().optional(), + yc: z.string().optional(), }) .refine( (val) => { @@ -2481,7 +2478,42 @@ export const transformationSchema: TransformationSchema[] = [ message: "At least one value is required", path: [], }, - ), + ) + .superRefine((val, ctx) => { + if (val.focus === "object" && !val.focusObject) { + ctx.addIssue({ + code: z.ZodIssueCode.custom, + message: "Focus object is required", + path: ["focusObject"], + }) + } + if (val.focus === "anchor" && !val.focusAnchor) { + ctx.addIssue({ + code: z.ZodIssueCode.custom, + message: "Focus anchor is required", + path: ["focusAnchor"], + }) + } + if (val.focus === "coordinates") { + if (val.coordinateMethod === "topleft") { + if (!val.x && !val.y) { + ctx.addIssue({ + code: z.ZodIssueCode.custom, + message: "At least one coordinate (x or y) is required", + path: [], + }) + } + } else if (val.coordinateMethod === "center") { + if (!val.xc && !val.yc) { + ctx.addIssue({ + code: z.ZodIssueCode.custom, + message: "At least one coordinate (xc or yc) is required", + path: [], + }) + } + } + } + }), transformations: [ { label: "Image URL", @@ -2533,6 +2565,132 @@ export const transformationSchema: TransformationSchema[] = [ defaultValue: "", }, }, + { + label: "Focus", + name: "focus", + fieldType: "select", + isTransformation: true, + transformationGroup: "imageLayer", + fieldProps: { + options: [ + { label: "Select one", value: "" }, + { label: "Auto", value: "auto" }, + { label: "Anchor", value: "anchor" }, + { label: "Face", value: "face" }, + { label: "Object", value: "object" }, + { label: "Custom", value: "custom" }, + { label: "Coordinates", value: "coordinates" }, + ], + }, + helpText: + "Choose how to position the extracted region in overlay image. Custom uses a saved focus area from Media Library.", + isVisible: ({ crop }) => crop === "cm-extract", + }, + // Only for extract crop mode + { + label: "Focus Anchor", + name: "focusAnchor", + fieldType: "anchor", + isTransformation: true, + transformationGroup: "imageLayer", + fieldProps: { + positions: [ + "center", "top", "bottom", "left", "right", "top_left", "top_right", "bottom_left", "bottom_right", + ], + }, + isVisible: ({ focus, crop }) => focus === "anchor" && crop === "cm-extract", + }, + // Only for pad_resize crop mode + { + label: "Focus", + name: "focusAnchor", + fieldType: "anchor", + isTransformation: true, + transformationGroup: "imageLayer", + fieldProps: { + positions: [ + "center", "top", "bottom", "left", "right", + ], + }, + isVisible: ({ crop }) => crop === "cm-pad_resize", + }, + { + label: "Focus Object", + name: "focusObject", + fieldType: "select", + isTransformation: true, + transformationGroup: "imageLayer", + fieldProps: { + isCreatable: false, + }, + helpText: + "Select an object to focus on in the overlay image during extraction. The crop will center on this object.", + isVisible: ({ focus }) => focus === "object", + }, + { + label: "Coordinate Method", + name: "coordinateMethod", + fieldType: "radio-card", + isTransformation: false, + transformationGroup: "imageLayer", + fieldProps: { + options: [ + { label: "Top-left (x, y)", value: "topleft" }, + { label: "Center (xc, yc)", value: "center" }, + ], + defaultValue: "topleft", + }, + helpText: + "Choose whether coordinates are relative to the top-left corner or the center of the overlay image.", + isVisible: ({ focus }) => focus === "coordinates", + }, + { + label: "X (Horizontal)", + name: "x", + fieldType: "input", + isTransformation: true, + transformationGroup: "imageLayer", + helpText: + "Horizontal position from the top-left of the overlay image. Use an integer or expression.", + examples: ["100", "iw_mul_0.4"], + isVisible: ({ focus, coordinateMethod }) => + focus === "coordinates" && coordinateMethod === "topleft", + }, + { + label: "Y (Vertical)", + name: "y", + fieldType: "input", + isTransformation: true, + transformationGroup: "imageLayer", + helpText: + "Vertical position from the top-left of the overlay image. Use an integer or expression.", + examples: ["100", "ih_mul_0.4"], + isVisible: ({ focus, coordinateMethod }) => + focus === "coordinates" && coordinateMethod === "topleft", + }, + { + label: "XC (Horizontal Center)", + name: "xc", + fieldType: "input", + isTransformation: true, + transformationGroup: "imageLayer", + helpText: + "Horizontal center position of the overlay image. Use an integer or expression.", + examples: ["200", "iw_mul_0.5"], + isVisible: ({ focus, coordinateMethod }) => + focus === "coordinates" && coordinateMethod === "center", + }, + { + label: "YC (Vertical Center)", + name: "yc", + fieldType: "input", + isTransformation: true, + transformationGroup: "imageLayer", + helpText: "Vertical center position of the overlay image. Use an integer or expression.", + examples: ["200", "ih_mul_0.5"], + isVisible: ({ focus, coordinateMethod }) => + focus === "coordinates" && coordinateMethod === "center", + }, { label: "Position X", name: "positionX", @@ -2770,7 +2928,7 @@ export const transformationFormatters: Record< } }, focus: (values, transforms) => { - const { focus, focusAnchor, focusObject, x, y, xc, yc } = values + const { focus, focusAnchor, focusObject, x, y, xc, yc, coordinateMethod } = values if (focus === "auto" || focus === "face") { transforms.focus = focus @@ -2783,10 +2941,13 @@ export const transformationFormatters: Record< } else if (focus === "coordinates") { // Handle coordinate-based focus // x/y are top-left coordinates, xc/yc are center coordinates - if (x) transforms.x = x - if (y) transforms.y = y - if (xc) transforms.xc = xc - if (yc) transforms.yc = yc + if (coordinateMethod === "topleft") { + if (x) transforms.x = x + if (y) transforms.y = y + } else if (coordinateMethod === "center") { + if (xc) transforms.xc = xc + if (yc) transforms.yc = yc + } } }, shadow: (values, transforms) => { @@ -3066,6 +3227,28 @@ export const transformationFormatters: Record< overlayTransform.blur = values.blur } + const { focus, crop, focusAnchor, focusObject, x, y, xc, yc, coordinateMethod } = values + + if (focus === "auto" || focus === "face") { + overlayTransform.focus = focus + } else if (focus === "anchor" || crop === "cm-pad_resize") { + overlayTransform.focus = focusAnchor + } else if (focus === "object") { + overlayTransform.focus = focusObject + } else if (focus === "custom") { + overlayTransform.focus = "custom" + } else if (focus === "coordinates") { + // Handle coordinate-based focus + // x/y are top-left coordinates, xc/yc are center coordinates + if (coordinateMethod === "topleft") { + if (x) overlayTransform.x = x + if (y) overlayTransform.y = y + } else if (coordinateMethod === "center") { + if (xc) overlayTransform.xc = xc + if (yc) overlayTransform.yc = yc + } + } + if (Object.keys(overlayTransform).length > 0) { overlay.transformation = [overlayTransform] } From 937b21d30601acdb6a7ce5a85478e5c4b55dac9d Mon Sep 17 00:00:00 2001 From: Swarnim Doegar Date: Thu, 22 Jan 2026 20:21:13 +0530 Subject: [PATCH 07/10] Add tooltip and better UX in padding input --- .../src/components/common/PaddingInput.tsx | 31 ++++++++++++++----- 1 file changed, 24 insertions(+), 7 deletions(-) diff --git a/packages/imagekit-editor-dev/src/components/common/PaddingInput.tsx b/packages/imagekit-editor-dev/src/components/common/PaddingInput.tsx index f6e5ea1..0244dac 100644 --- a/packages/imagekit-editor-dev/src/components/common/PaddingInput.tsx +++ b/packages/imagekit-editor-dev/src/components/common/PaddingInput.tsx @@ -8,12 +8,13 @@ import { InputGroup, InputLeftElement, IconButton, - FormErrorMessage, + IconButtonProps, useColorModeValue, + Tooltip, } from "@chakra-ui/react" import { set } from "lodash" import type * as React from "react" -import { useState, useEffect } from "react" +import { useState, useEffect, forwardRef } from "react" import { LuArrowLeftToLine } from "@react-icons/all-files/lu/LuArrowLeftToLine" import { LuArrowRightToLine } from "@react-icons/all-files/lu/LuArrowRightToLine" import { LuArrowUpToLine } from "@react-icons/all-files/lu/LuArrowUpToLine" @@ -22,7 +23,6 @@ import { TbBoxPadding } from "@react-icons/all-files/tb/TbBoxPadding" import { MdOutlinePadding } from "@react-icons/all-files/md/MdOutlinePadding" import { FieldErrors } from "react-hook-form" - type PaddingInputFieldProps = { id?: string onChange: (value: number | PaddingObject | string) => void @@ -88,6 +88,8 @@ export const PaddingInputField: React.FC = ({ const [paddingMode, setPaddingMode] = useState<"uniform" | "individual">("uniform") const [paddingValue, setPaddingValue] = useState("") const errorRed = useColorModeValue("red.500", "red.300") + const activeColor = useColorModeValue("blue.500", "blue.600") + const inactiveColor = useColorModeValue("gray.600", "gray.400") useEffect(() => { const formatPaddingValue = (value: number | PaddingObject | null | string): string | PaddingObject => { @@ -242,10 +244,25 @@ export const PaddingInputField: React.FC = ({ ) } - + : } + aria-pressed={paddingMode === "individual"} + icon={} onClick={() => { const newPaddingMode = paddingMode === "uniform" ? "individual" : "uniform" setPaddingValue(getUpdatedPaddingValue( @@ -256,10 +273,10 @@ export const PaddingInputField: React.FC = ({ )) setPaddingMode(newPaddingMode) }} - mb={2} variant="outline" + color={paddingMode === "individual" ? activeColor : inactiveColor} /> - + ) } From c36c4dd44b04e4df49f78b11aa97212d51205335 Mon Sep 17 00:00:00 2001 From: Swarnim Doegar Date: Thu, 22 Jan 2026 20:25:12 +0530 Subject: [PATCH 08/10] Add zoom support with fo face and object in base image and image overlay --- .../src/components/common/ZoomInput.tsx | 141 ++++++++++++++++++ .../sidebar/transformation-config-sidebar.tsx | 9 ++ .../imagekit-editor-dev/src/schema/index.ts | 67 ++++++++- 3 files changed, 215 insertions(+), 2 deletions(-) create mode 100644 packages/imagekit-editor-dev/src/components/common/ZoomInput.tsx diff --git a/packages/imagekit-editor-dev/src/components/common/ZoomInput.tsx b/packages/imagekit-editor-dev/src/components/common/ZoomInput.tsx new file mode 100644 index 0000000..7dd7023 --- /dev/null +++ b/packages/imagekit-editor-dev/src/components/common/ZoomInput.tsx @@ -0,0 +1,141 @@ +import { + HStack, + Input, + InputGroup, + InputRightElement, + IconButton, + ButtonGroup, + Text, + useColorModeValue, +} from "@chakra-ui/react" +import type * as React from "react" +import { useState, useEffect } from "react" +import { AiOutlinePlus } from "@react-icons/all-files/ai/AiOutlinePlus" +import { AiOutlineMinus } from "@react-icons/all-files/ai/AiOutlineMinus" + +type ZoomInputFieldProps = { + id?: string + onChange: (value: number) => void + defaultValue?: number +} + +/** + * Calculate the step size based on the current zoom value + * If zoom >= 100: step = 50 + * If zoom < 100: step = 10 + */ +function getStepSize(value: number, zoomMode: "in" | "out"): number { + if (zoomMode === "in") { + return value >= 100 ? 50 : 10 + } else { + return value > 100 ? 50 : 10 + } +} + +/** + * Calculate the next zoom value when zooming in + * Rounds up to the next step value + */ +function calculateZoomIn(currentValue: number): number { + const step = getStepSize(currentValue, "in") + return (Math.floor(currentValue / step) * step) + step +} + +/** + * Calculate the next zoom value when zooming out + * Rounds down to the previous step value + */ +function calculateZoomOut(currentValue: number): number { + const step = getStepSize(currentValue, "out") + return (Math.ceil(currentValue / step) * step) - step +} + +export const ZoomInputField: React.FC = ({ + id, + onChange, + defaultValue = 100, +}) => { + const [zoomValue, setZoomValue] = useState(defaultValue) + const [inputValue, setInputValue] = useState(defaultValue.toString()) + + useEffect(() => { + onChange(zoomValue) + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [zoomValue]) + + const handleInputChange = (e: React.ChangeEvent) => { + const value = e.target.value + setInputValue(value) + + const numValue = Number(value) + if (!isNaN(numValue) && numValue >= 0) { + setZoomValue(numValue) + } + } + + const handleInputBlur = () => { + // Sync input value with zoom value on blur + setInputValue(zoomValue.toString()) + } + + const handleZoomIn = () => { + const newValue = calculateZoomIn(zoomValue) + setZoomValue(newValue) + setInputValue(newValue.toString()) + } + + const handleZoomOut = () => { + const newValue = calculateZoomOut(zoomValue) + // Prevent going below 0 + if (newValue >= 0) { + setZoomValue(newValue) + setInputValue(newValue.toString()) + } else { + setZoomValue(0) + setInputValue("0") + } + } + + return ( + + + + + + % + + + + + + } + onClick={handleZoomOut} + /> + } + onClick={handleZoomIn} + /> + + + + ) +} + +export default ZoomInputField diff --git a/packages/imagekit-editor-dev/src/components/sidebar/transformation-config-sidebar.tsx b/packages/imagekit-editor-dev/src/components/sidebar/transformation-config-sidebar.tsx index 68577f5..747d7b2 100644 --- a/packages/imagekit-editor-dev/src/components/sidebar/transformation-config-sidebar.tsx +++ b/packages/imagekit-editor-dev/src/components/sidebar/transformation-config-sidebar.tsx @@ -56,6 +56,7 @@ import { SidebarFooter } from "./sidebar-footer" import { SidebarHeader } from "./sidebar-header" import { SidebarRoot } from "./sidebar-root" import PaddingInputField from "../common/PaddingInput" +import ZoomInputField from "../common/ZoomInput" export const TransformationConfigSidebar: React.FC = () => { const { @@ -573,6 +574,14 @@ export const TransformationConfigSidebar: React.FC = () => { {...field.fieldProps} /> ) : null} + {field.fieldType === "zoom" ? ( + setValue(field.name, value)} + defaultValue={field.fieldProps?.defaultValue as number ?? 0} + {...field.fieldProps} + /> + ) : null} {String( errors[field.name as keyof typeof errors]?.message ?? "", diff --git a/packages/imagekit-editor-dev/src/schema/index.ts b/packages/imagekit-editor-dev/src/schema/index.ts index d2ae7bb..319afae 100644 --- a/packages/imagekit-editor-dev/src/schema/index.ts +++ b/packages/imagekit-editor-dev/src/schema/index.ts @@ -371,6 +371,7 @@ export const transformationSchema: TransformationSchema[] = [ focus: z.string().optional(), focusAnchor: z.string().optional(), focusObject: z.string().optional(), + zoom: z.coerce.number().optional(), }) .refine( (val) => { @@ -499,6 +500,19 @@ export const transformationSchema: TransformationSchema[] = [ "Select an object to focus on. The crop will center on this object.", isVisible: ({ focus }) => focus === "object", }, + { + label: "Zoom", + name: "zoom", + fieldType: "zoom", + isTransformation: true, + transformationGroup: "focus", + fieldProps: { + isCreatable: false, + }, + helpText: + "Select the zoom level for the focus area. Higher zoom levels crop closer to the focus point.", + isVisible: ({ focus }) => focus === "object" || focus === "face", + }, ], }, { @@ -519,6 +533,7 @@ export const transformationSchema: TransformationSchema[] = [ focus: z.string().optional(), focusAnchor: z.string().optional(), focusObject: z.string().optional(), + zoom: z.coerce.number().optional(), }) .refine( (val) => { @@ -606,6 +621,19 @@ export const transformationSchema: TransformationSchema[] = [ "Select an object to focus on. The crop will center on this object.", isVisible: ({ focus }) => focus === "object", }, + { + label: "Zoom", + name: "zoom", + fieldType: "zoom", + isTransformation: true, + transformationGroup: "focus", + fieldProps: { + isCreatable: false, + }, + helpText: + "Select the zoom level for the focus area. Higher zoom levels crop closer to the focus point.", + isVisible: ({ focus }) => focus === "object", + }, ], }, { @@ -806,6 +834,7 @@ export const transformationSchema: TransformationSchema[] = [ y: z.string().optional(), xc: z.string().optional(), yc: z.string().optional(), + zoom: z.coerce.number().optional(), }) .refine( (val) => { @@ -996,6 +1025,19 @@ export const transformationSchema: TransformationSchema[] = [ isVisible: ({ focus, coordinateMethod }) => focus === "coordinates" && coordinateMethod === "center", }, + { + label: "Zoom", + name: "zoom", + fieldType: "zoom", + isTransformation: true, + transformationGroup: "focus", + fieldProps: { + isCreatable: false, + }, + helpText: + "Select the zoom level for the focus area. Higher zoom levels crop closer to the focus point.", + isVisible: ({ focus }) => focus === "object" || focus === "face", + }, ], }, { @@ -2467,6 +2509,7 @@ export const transformationSchema: TransformationSchema[] = [ y: z.string().optional(), xc: z.string().optional(), yc: z.string().optional(), + zoom: z.coerce.number().optional(), }) .refine( (val) => { @@ -2691,6 +2734,19 @@ export const transformationSchema: TransformationSchema[] = [ isVisible: ({ focus, coordinateMethod }) => focus === "coordinates" && coordinateMethod === "center", }, + { + label: "Zoom", + name: "zoom", + fieldType: "zoom", + isTransformation: true, + transformationGroup: "imageLayer", + fieldProps: { + isCreatable: false, + }, + helpText: + "Select the zoom level for the focus area. Higher zoom levels crop closer to the focus point.", + isVisible: ({ focus }) => focus === "object" || focus === "face", + }, { label: "Position X", name: "positionX", @@ -2928,7 +2984,7 @@ export const transformationFormatters: Record< } }, focus: (values, transforms) => { - const { focus, focusAnchor, focusObject, x, y, xc, yc, coordinateMethod } = values + const { focus, focusAnchor, focusObject, x, y, xc, yc, coordinateMethod, zoom } = values if (focus === "auto" || focus === "face") { transforms.focus = focus @@ -2949,6 +3005,9 @@ export const transformationFormatters: Record< if (yc) transforms.yc = yc } } + if (zoom !== undefined && zoom !== null && !isNaN(Number(zoom)) && zoom !== 0) { + transforms.zoom = (zoom as number) / 100 + } }, shadow: (values, transforms) => { const { @@ -3227,7 +3286,7 @@ export const transformationFormatters: Record< overlayTransform.blur = values.blur } - const { focus, crop, focusAnchor, focusObject, x, y, xc, yc, coordinateMethod } = values + const { focus, crop, focusAnchor, focusObject, x, y, xc, yc, coordinateMethod, zoom } = values if (focus === "auto" || focus === "face") { overlayTransform.focus = focus @@ -3249,6 +3308,10 @@ export const transformationFormatters: Record< } } + if (zoom !== undefined && zoom !== null && !isNaN(Number(zoom)) && zoom !== 0) { + overlayTransform.zoom = (zoom as number) / 100 + } + if (Object.keys(overlayTransform).length > 0) { overlay.transformation = [overlayTransform] } From 56834fed4d899e40b722ec99057ee610eea71c01 Mon Sep 17 00:00:00 2001 From: Swarnim Doegar Date: Fri, 23 Jan 2026 14:59:32 +0530 Subject: [PATCH 09/10] Fix zoom input step size and default value --- .../src/components/common/ZoomInput.tsx | 24 +++++-------------- .../sidebar/transformation-config-sidebar.tsx | 6 ++--- 2 files changed, 9 insertions(+), 21 deletions(-) diff --git a/packages/imagekit-editor-dev/src/components/common/ZoomInput.tsx b/packages/imagekit-editor-dev/src/components/common/ZoomInput.tsx index 7dd7023..843036d 100644 --- a/packages/imagekit-editor-dev/src/components/common/ZoomInput.tsx +++ b/packages/imagekit-editor-dev/src/components/common/ZoomInput.tsx @@ -19,26 +19,15 @@ type ZoomInputFieldProps = { defaultValue?: number } -/** - * Calculate the step size based on the current zoom value - * If zoom >= 100: step = 50 - * If zoom < 100: step = 10 - */ -function getStepSize(value: number, zoomMode: "in" | "out"): number { - if (zoomMode === "in") { - return value >= 100 ? 50 : 10 - } else { - return value > 100 ? 50 : 10 - } -} +const STEP_SIZE = 10 + /** * Calculate the next zoom value when zooming in * Rounds up to the next step value */ function calculateZoomIn(currentValue: number): number { - const step = getStepSize(currentValue, "in") - return (Math.floor(currentValue / step) * step) + step + return (Math.floor(currentValue / STEP_SIZE) * STEP_SIZE) + STEP_SIZE } /** @@ -46,11 +35,10 @@ function calculateZoomIn(currentValue: number): number { * Rounds down to the previous step value */ function calculateZoomOut(currentValue: number): number { - const step = getStepSize(currentValue, "out") - return (Math.ceil(currentValue / step) * step) - step + return (Math.ceil(currentValue / STEP_SIZE) * STEP_SIZE) - STEP_SIZE } -export const ZoomInputField: React.FC = ({ +export const ZoomInput: React.FC = ({ id, onChange, defaultValue = 100, @@ -138,4 +126,4 @@ export const ZoomInputField: React.FC = ({ ) } -export default ZoomInputField +export default ZoomInput diff --git a/packages/imagekit-editor-dev/src/components/sidebar/transformation-config-sidebar.tsx b/packages/imagekit-editor-dev/src/components/sidebar/transformation-config-sidebar.tsx index 747d7b2..6845ee4 100644 --- a/packages/imagekit-editor-dev/src/components/sidebar/transformation-config-sidebar.tsx +++ b/packages/imagekit-editor-dev/src/components/sidebar/transformation-config-sidebar.tsx @@ -56,7 +56,7 @@ import { SidebarFooter } from "./sidebar-footer" import { SidebarHeader } from "./sidebar-header" import { SidebarRoot } from "./sidebar-root" import PaddingInputField from "../common/PaddingInput" -import ZoomInputField from "../common/ZoomInput" +import ZoomInput from "../common/ZoomInput" export const TransformationConfigSidebar: React.FC = () => { const { @@ -575,10 +575,10 @@ export const TransformationConfigSidebar: React.FC = () => { /> ) : null} {field.fieldType === "zoom" ? ( - setValue(field.name, value)} - defaultValue={field.fieldProps?.defaultValue as number ?? 0} + defaultValue={field.fieldProps?.defaultValue as number ?? 100} {...field.fieldProps} /> ) : null} From 90c2b1bda135bfe99a23635cebd272e726245ba1 Mon Sep 17 00:00:00 2001 From: Swarnim Doegar Date: Fri, 23 Jan 2026 15:10:56 +0530 Subject: [PATCH 10/10] Fix padding toggle button style --- .../imagekit-editor-dev/src/components/common/PaddingInput.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/imagekit-editor-dev/src/components/common/PaddingInput.tsx b/packages/imagekit-editor-dev/src/components/common/PaddingInput.tsx index 0244dac..fa33481 100644 --- a/packages/imagekit-editor-dev/src/components/common/PaddingInput.tsx +++ b/packages/imagekit-editor-dev/src/components/common/PaddingInput.tsx @@ -263,6 +263,7 @@ export const PaddingInputField: React.FC = ({ aria-label={paddingMode === "uniform" ? "Switch to individual padding" : "Switch to uniform padding"} aria-pressed={paddingMode === "individual"} icon={} + padding="0.05em" onClick={() => { const newPaddingMode = paddingMode === "uniform" ? "individual" : "uniform" setPaddingValue(getUpdatedPaddingValue(