diff --git a/README.md b/README.md index eafdcdb..d0f8bfa 100644 --- a/README.md +++ b/README.md @@ -48,18 +48,20 @@ The component works out of the box in React Server Components environments (e.g. ## Props -| Prop | Type | Description | -| ------------- | ----------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `image` | `string` (required) | Image URL or base64 data URL to edit. | -| `options` | `ImageEditorOptions` | Editor configuration: `projectId`, `user`, `features`, `theme`, `locale`, `translations`, `env`, `offline`, `licenseUrl`, `defaultPrompt`, `autoSubmitPrompt`, `aiAssistantOpenState`. | -| `editorId` | `string` | id for the container div. Cosmetic — the editor mounts by element reference. | -| `minHeight` | `number \| string` | Minimum height of the editor container. Defaults to `500`. | -| `style` | `CSSProperties` | Styles applied to the container div. | -| `onLoad` | `(editor) => void` | Called with the editor instance once it is mounted. | -| `onSave` | `({ dataUrl, blob }) => void` | Called when the user saves the edited image. | -| `onCancel` | `() => void` | Called when the user cancels editing. | -| `onLoadError` | `() => void` | Called when the image fails to load into the canvas (CORS, 404, decode error). | -| `onError` | `(error: Error) => void` | Wrapper-level failures: embed script load, editor creation, or image reset. Falls back to `console.error` when absent. | +| Prop | Type | Description | +| -------------- | ----------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `image` | `string` (required) | Image URL or base64 data URL to edit. | +| `options` | `ImageEditorOptions` | Editor configuration: `projectId`, `user`, `features`, `theme`, `locale`, `translations`, `env`, `offline`, `licenseUrl`, `defaultPrompt`, `autoSubmitPrompt`, `aiAssistantOpenState`. | +| `editorId` | `string` | id for the container div. Cosmetic — the editor mounts by element reference. | +| `minHeight` | `number \| string` | Minimum height of the editor container. Defaults to `500`. | +| `style` | `CSSProperties` | Styles applied to the container div. Overrides the default `flex: 1`. | +| `wrapperStyle` | `CSSProperties` | Styles applied to the outer wrapper div, which owns `minHeight` and the flex layout. Set this to drop the editor into a non-flex layout. | +| `ariaLabel` | `string` | Accessible name for the editor region. Defaults to `'Image editor'`. | +| `onLoad` | `(editor) => void` | Called with the editor instance once it is mounted. | +| `onSave` | `({ dataUrl, blob }) => void` | Called when the user saves the edited image. | +| `onCancel` | `() => void` | Called when the user cancels editing. | +| `onLoadError` | `() => void` | Called when the image fails to load into the canvas (CORS, 404, decode error). | +| `onError` | `(error: Error) => void` | Wrapper-level failures: embed script load, editor creation, or image reset. Falls back to `console.error` when absent. | ## Editor instance (ref) diff --git a/src/ImageEditor.tsx b/src/ImageEditor.tsx index a01c9e8..4d87d30 100644 --- a/src/ImageEditor.tsx +++ b/src/ImageEditor.tsx @@ -13,7 +13,15 @@ function ImageEditorInner( props: ImageEditorProps, ref: React.Ref ) { - const { image, options = {}, scriptUrl, minHeight = 500, style = {} } = props; + const { + image, + options = {}, + scriptUrl, + minHeight = 500, + style = {}, + wrapperStyle = {}, + ariaLabel = 'Image editor', + } = props; const [editor, setEditor] = useState(null); const containerRef = useRef(null); @@ -196,9 +204,20 @@ function ImageEditorInner( flex: 1, display: 'flex', minHeight: minHeight, + ...wrapperStyle, }} > -
+
); } diff --git a/src/types.ts b/src/types.ts index 7e7b4f4..fb1b831 100644 --- a/src/types.ts +++ b/src/types.ts @@ -101,8 +101,18 @@ export interface ImageEditorProps { editorId?: string; /** Minimum height of the editor container. Defaults to 500. */ minHeight?: number | string; - /** Styles applied to the container div. */ + /** Styles applied to the container div. Overrides the default `flex: 1`. */ style?: CSSProperties; + /** + * Styles applied to the outer wrapper div, which owns `minHeight` and the + * flex layout. Set this to drop the editor into a non-flex layout. + */ + wrapperStyle?: CSSProperties; + /** + * Accessible name for the editor region. The embed renders no landmark + * and no name of its own. Defaults to 'Image editor'. + */ + ariaLabel?: string; /** * Override the embed script URL (e.g. to pin an environment). One embed * per page: the first loader to run installs window.ImageEditor and wins diff --git a/test/index.test.tsx b/test/index.test.tsx index 5c08aa2..909c577 100644 --- a/test/index.test.tsx +++ b/test/index.test.tsx @@ -361,6 +361,45 @@ it('applies minHeight and style to the container', async () => { ); }); +it('lets style override the container default and wrapperStyle reach the wrapper', async () => { + render( + + ); + await flush(); + + const container = document.querySelector('#styled') as HTMLElement; + const wrapper = container.parentElement as HTMLElement; + + // flex is a default now, not a hardcoded override. ('none' is stored in + // expanded longhand form; the default `flex: 1` would be '1 1 0%'.) + expect(container.style.flex).toBe('0 0 auto'); + expect(container.style.width).toBe('640px'); + // The outer wrapper is reachable, so the component can be dropped into a + // non-flex layout. + expect(wrapper.style.display).toBe('block'); + expect(wrapper.style.minHeight).toBe('0px'); +}); + +it('gives the editor region an accessible name, overridable via ariaLabel', async () => { + const { rerender } = render(); + await flush(); + + const container = document.querySelector('#a11y') as HTMLElement; + expect(container.getAttribute('role')).toBe('region'); + expect(container.getAttribute('aria-label')).toBe('Image editor'); + + rerender( + + ); + expect(container.getAttribute('aria-label')).toBe('Avatar cropper'); +}); + it('passes onCancel and onLoadError through to the editor', async () => { const onCancel = vi.fn(); const onLoadError = vi.fn();