Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,7 @@ export default ImageEditor;
| `signer` | `Signer` | ❌ | Function to generate signed URLs for private images |
| `onAddImage` | `() => void` | ❌ | Callback function for adding new images |
| `exportOptions` | `ExportOptions` | ❌ | Configuration for export functionality |
| `focusObjects` | `string[]` | ❌ | Custom list of selectable focus objects for object-based focus |

### ImageKitEditor Ref Methods

Expand Down Expand Up @@ -181,6 +182,19 @@ exportOptions={{
}}
```

### Focus Objects

You can override the list of selectable focus objects used when a transformation's focus is set to "Object" (e.g., Maintain Aspect Ratio, Forced Crop, Extract). If not provided, the editor defaults to ImageKit's supported objects (e.g., person, bicycle, car, dog, etc.).

See the supported object list in the ImageKit docs: https://imagekit.io/docs/image-resize-and-crop#supported-object-list

```tsx
<ImageKitEditor
focusObjects={["person", "cat", "car", "customObject"]}
// ... other props
/>
```

### File Element Interface

For advanced use cases with metadata and signed URLs:
Expand Down
2 changes: 1 addition & 1 deletion examples/react-example/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"version": "0.1.0",
"private": true,
"dependencies": {
"@imagekit/editor": "1.0.0",
"@imagekit/editor": "1.1.0",
"@types/node": "^20.11.24",
"@types/react": "^17.0.2",
"@types/react-dom": "^17.0.2",
Expand Down
6 changes: 6 additions & 0 deletions examples/react-example/src/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,12 @@ function App() {
requireSignedUrl: false,
},
},
{
url: "https://ik.imagekit.io/v3sxk1svj/brown%20bear%20plush%20toy%20on%20whi....jpg?updatedAt=1760432666859",
metadata: {
requireSignedUrl: false,
},
},
// ...Array.from({ length: 10000 }).map((_, i) => ({
// url: `https://ik.imagekit.io/v3sxk1svj/placeholder.jpg?updatedAt=${Date.now()}&v=${i}`,
// metadata: {
Expand Down
9 changes: 6 additions & 3 deletions packages/imagekit-editor-dev/src/ImageKitEditor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,10 @@ import merge from "lodash/merge"
import React, { forwardRef, useImperativeHandle } from "react"
import { EditorLayout, EditorWrapper } from "./components/editor"
import type { HeaderProps } from "./components/header"
import type { DEFAULT_FOCUS_OBJECTS } from "./schema"
import {
type FileElement,
type FocusObjects,
type RequiredMetadata,
type Signer,
useEditorStore,
Expand All @@ -24,15 +26,15 @@ interface EditorProps<Metadata extends RequiredMetadata = RequiredMetadata> {
signer?: Signer<Metadata>
onAddImage?: () => void
exportOptions?: HeaderProps["exportOptions"]

focusObjects?: ReadonlyArray<FocusObjects>
onClose: (args: { dirty: boolean; destroy: () => void }) => void
}

function ImageKitEditorImpl<M extends RequiredMetadata>(
props: EditorProps<M>,
ref: React.Ref<ImageKitEditorRef>,
) {
const { theme, initialImages, signer } = props
const { theme, initialImages, signer, focusObjects } = props
const {
addImage,
addImages,
Expand Down Expand Up @@ -62,8 +64,9 @@ function ImageKitEditorImpl<M extends RequiredMetadata>(
initialize({
imageList: initialImages,
signer,
focusObjects,
})
}, [initialImages, signer, initialize])
}, [initialImages, signer, focusObjects, initialize])

useImperativeHandle(
ref,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,13 +37,14 @@ import { PiArrowLeft } from "@react-icons/all-files/pi/PiArrowLeft"
import { PiCaretDown } from "@react-icons/all-files/pi/PiCaretDown"
import { PiInfo } from "@react-icons/all-files/pi/PiInfo"
import { PiX } from "@react-icons/all-files/pi/PiX"
import startCase from "lodash/startCase"
import { useEffect, useMemo } from "react"
import { Controller, type SubmitHandler, useForm } from "react-hook-form"
import Select from "react-select"
import CreateableSelect from "react-select/creatable"
import { z } from "zod/v3"
import type { TransformationField } from "../../schema"
import { transformationSchema } from "../../schema"
import { DEFAULT_FOCUS_OBJECTS, transformationSchema } from "../../schema"
import { useEditorStore } from "../../store"
import { isStepAligned } from "../../utils"
import AnchorField from "../common/AnchorField"
Expand All @@ -61,6 +62,7 @@ export const TransformationConfigSidebar: React.FC = () => {
addTransformation,
updateTransformation,
imageList,
focusObjects,
_setSidebarState,
_internalState,
_setTransformationToEdit,
Expand Down Expand Up @@ -296,40 +298,74 @@ export const TransformationConfigSidebar: React.FC = () => {
<Controller
name={field.name}
control={control}
render={({ field: controllerField }) => (
<Select
id={field.name}
placeholder="Select"
menuPlacement="auto"
options={field.fieldProps?.options?.map((option) => ({
value: option.value,
label: option.label,
}))}
value={field.fieldProps?.options?.find(
(option) => option.value === controllerField.value,
)}
onChange={(selectedOption) =>
controllerField.onChange(selectedOption?.value)
}
onBlur={controllerField.onBlur}
styles={{
control: (base) => ({
...base,
fontSize: "12px",
minHeight: "32px",
borderColor: "#E2E8F0",
}),
menu: (base) => ({
...base,
zIndex: 10,
}),
option: (base) => ({
...base,
fontSize: "12px",
}),
}}
/>
)}
render={({ field: controllerField }) => {
// For focusObject field, use focusObjects from store or default list
const selectOptions =
field.name === "focusObject"
? (focusObjects || DEFAULT_FOCUS_OBJECTS).map(
(obj) => ({
value: obj,
label: startCase(obj),
}),
)
: field.fieldProps?.options?.map((option) => ({
value: option.value,
label: option.label,
}))

const isCreatable = field.fieldProps?.isCreatable === true
const SelectComponent = isCreatable
? CreateableSelect
: Select

// For creatable selects, find the value in options or create a custom one
const selectedValue = isCreatable
? selectOptions?.find(
(option) => option.value === controllerField.value,
) ||
(controllerField.value
? {
value: controllerField.value as string,
label: startCase(controllerField.value as string),
}
: null)
: selectOptions?.find(
(option) => option.value === controllerField.value,
)

return (
<SelectComponent
id={field.name}
formatCreateLabel={(inputValue) =>
`Use "${inputValue}"`
}
placeholder="Select"
menuPlacement="auto"
options={selectOptions}
value={selectedValue}
onChange={(selectedOption) =>
controllerField.onChange(selectedOption?.value)
}
onBlur={controllerField.onBlur}
styles={{
control: (base) => ({
...base,
fontSize: "12px",
minHeight: "32px",
borderColor: "#E2E8F0",
}),
menu: (base) => ({
...base,
zIndex: 10,
}),
option: (base) => ({
...base,
fontSize: "12px",
}),
}}
/>
)
}}
/>
) : null}
{field.fieldType === "select-creatable" ? (
Expand Down
1 change: 1 addition & 0 deletions packages/imagekit-editor-dev/src/index.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
export type { ImageKitEditorProps, ImageKitEditorRef } from "./ImageKitEditor"
export { ImageKitEditor } from "./ImageKitEditor"
export { DEFAULT_FOCUS_OBJECTS } from "./schema"
export type { FileElement, Signer } from "./store"
Loading