diff --git a/.github/workflows/code-quality.yml b/.github/workflows/code-quality.yml index 2a34c51..0bbc622 100644 --- a/.github/workflows/code-quality.yml +++ b/.github/workflows/code-quality.yml @@ -25,6 +25,8 @@ jobs: run: npm run lint:check - name: Check formatting (Prettier) run: npm run format:check + - name: Check types + run: npm run types:check test: runs-on: ubuntu-latest diff --git a/CHANGELOG.md b/CHANGELOG.md index de05029..4f9c2df 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,12 @@ release steps in [CONTRIBUTING.md](CONTRIBUTING.md). ### Added +- TypeScript declarations, shipped with the package as `src/index.d.ts` and + wired into `exports` so both `bundler` and `node16` resolution find them. + TypeScript consumers previously got an implicit `any`. +- `npm run types:check`, a `tsc --noEmit` pass over a compile-time test of the + public surface, enforced in CI so the declarations cannot drift from the + implementation unnoticed. - Open Graph and Twitter card metadata on the demo site, plus a description and canonical URL. Links to the demo previously rendered as a bare URL with no preview anywhere they were shared, and search engines had no description to diff --git a/README.md b/README.md index a3d0ea4..342e6a2 100644 --- a/README.md +++ b/README.md @@ -136,6 +136,30 @@ const imageTool = new VisualImageTool({ }); ``` +## TypeScript + +Type declarations ship with the package — there is nothing extra to install. + +```typescript +import { + VisualImageTool, + type ChangeData, + type CropZone, + type FocusPoint, +} from "@h4md1/visual-image-tool"; + +const tool = new VisualImageTool({ + imageElement: document.querySelector("#myImage")!, + onChange: (data: ChangeData) => { + const { x, y }: FocusPoint = data.focusPoint; + const crop: CropZone = data.cropZone; + }, +}); +``` + +Exported types: `VisualImageToolOptions`, `ChangeData`, `FocusPoint`, `CropZone`, +`ImageDimensions`, `FocusPointOptions`, `CropZoneOptions`, and the style interfaces. + ## Full API ### Methods diff --git a/package-lock.json b/package-lock.json index 7e00da7..5600f6a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@h4md1/visual-image-tool", - "version": "1.0.0", + "version": "0.2.6", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@h4md1/visual-image-tool", - "version": "1.0.0", + "version": "0.2.6", "license": "MIT", "devDependencies": { "@biomejs/biome": "1.9.4", @@ -16,10 +16,11 @@ "jsdom": "^24.1.3", "prettier": "3.5.3", "rollup": "^4.12.0", + "typescript": "^7.0.2", "vitest": "^1.6.1" }, "engines": { - "node": ">=18.0.0 <23.0.0" + "node": ">=18.0.0" } }, "node_modules/@asamuzakjp/css-color": { @@ -1193,6 +1194,346 @@ "dev": true, "license": "MIT" }, + "node_modules/@typescript/typescript-aix-ppc64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-aix-ppc64/-/typescript-aix-ppc64-7.0.2.tgz", + "integrity": "sha512-MTKKkWB7p/0E9xi1d1tHtZ5PiLkGEMIq88pK2CubZjOsLtYTLqhgIgi6zepFa+9GHZ6h05NMCkQxGKiPXMxXtQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-darwin-arm64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-darwin-arm64/-/typescript-darwin-arm64-7.0.2.tgz", + "integrity": "sha512-gowzar9MwS/aRWp6f3a4KUqzRjAZjOsmGNCM6LcTgXum+dBfgsBVMN+AgvOCCbguXyick6LJhpBszxMebJ8syA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-darwin-x64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-darwin-x64/-/typescript-darwin-x64-7.0.2.tgz", + "integrity": "sha512-SZ9xZInqApNlNGc9s0W1VSsktYSOe9cFqNOIqmN1Gs8SmkjKZYFt017G4VwPxASInODuAdbTW7sXiFUf893RgA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-freebsd-arm64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-freebsd-arm64/-/typescript-freebsd-arm64-7.0.2.tgz", + "integrity": "sha512-W5NH4y/J0plIIS5b2xvTEkU7JFxyqdMAOgf+Ilhl0vHQXKO5dZoxd+C/jEtq56c4F3wk71RB4BMRQ2XdI+bwYQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-freebsd-x64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-freebsd-x64/-/typescript-freebsd-x64-7.0.2.tgz", + "integrity": "sha512-UMGDx5sTpzNw3WiPebH7l90IWfJggEd+egHt/q6p7/Cm3zqoV7VxkGXt+3DxPIw8CcmvAB0j3sVVfbhX+M4Tpw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-linux-arm": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-linux-arm/-/typescript-linux-arm-7.0.2.tgz", + "integrity": "sha512-gffT3xPz9sR7j/YJExkyPntrI0P2EP9XbOyWzth2/Gs0RstK+90RBcO0ncXoXy/beYll1SXw846Nf2zdnEz0QQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-linux-arm64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-linux-arm64/-/typescript-linux-arm64-7.0.2.tgz", + "integrity": "sha512-Qh4eU4/y3yDjnfjjyPYihMj5/ODIlmt+Bzu17OI+fiSRDW57QmU5SiN63exPRNJPKUzcc1INa1NXdrJ+MqHjUQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-linux-loong64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-linux-loong64/-/typescript-linux-loong64-7.0.2.tgz", + "integrity": "sha512-uEHck9i8hoAzXPiYRib1O7miOnz23SxIeVl6F4LXox+qov1K35jHcEW6VHKvZI+pyvl7fZEP4MCU5LYvIq1GuQ==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-linux-mips64el": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-linux-mips64el/-/typescript-linux-mips64el-7.0.2.tgz", + "integrity": "sha512-R4KvAMnE43W5Qeqb0Ly56O3mWMWIAgsMyz36DCaycd5nbg/9kzm0liw3JocfRqyJY0KPmzFjbswozXyW0DnIYA==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-linux-ppc64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-linux-ppc64/-/typescript-linux-ppc64-7.0.2.tgz", + "integrity": "sha512-DORx5b3sd/4S7eayxm4FQv+A7CrkUIGRaHiwI8oiHTAI1fAPWhF4J0vAlkC8biAlHSVVwxMQ3tjZ2/DVbnQiiA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-linux-riscv64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-linux-riscv64/-/typescript-linux-riscv64-7.0.2.tgz", + "integrity": "sha512-wf0jqEDOjrPRnKwYRyyJDRo11KMbvMFrU+q4zqKyChODBzvlkbhNQfKvLxQCcwTpdDaXSHZTVuh0JoCrKCUMHQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-linux-s390x": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-linux-s390x/-/typescript-linux-s390x-7.0.2.tgz", + "integrity": "sha512-IkwJc3L7yhytWd/ewjyxNDfOmswCm9GWMJT/ue/dU4aZNbwZeYAetq42VyLmsmSjvoX7z74X6ZaYCtzAr0EuGw==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-linux-x64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-linux-x64/-/typescript-linux-x64-7.0.2.tgz", + "integrity": "sha512-EYdf2cNg7rgCWJnxCdJ+F3V39O8ihb37eHAu1LK8oAFizgTQbPOK7zHHXbPt8rX24COqODXeI3sIf0fCXG7H/A==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-netbsd-arm64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-netbsd-arm64/-/typescript-netbsd-arm64-7.0.2.tgz", + "integrity": "sha512-+polYF4MF04aPpO5FTkHran9yUQDSXqy5GiSDKpsll5jy3l3+g9QLhpf39T+ePtefhXLOGrLl0QIjkQP6VnelA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-netbsd-x64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-netbsd-x64/-/typescript-netbsd-x64-7.0.2.tgz", + "integrity": "sha512-8YIT0EHM/3dq10ZOVF/A7pc/YSMtbcecct4rWtexrnSCHOPcpC2KTLXfTCR6vDpnSiY12heNb1GiN/wu+T/FyA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-openbsd-arm64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-openbsd-arm64/-/typescript-openbsd-arm64-7.0.2.tgz", + "integrity": "sha512-APT8+ClYnuYm1u9+kgGXoMj2VzWzcymwh2gNSQVySHfkRDGOTVkoWLjCmOQSaO+PoqQ57B0flRp9SA+7GnnkzQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-openbsd-x64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-openbsd-x64/-/typescript-openbsd-x64-7.0.2.tgz", + "integrity": "sha512-yX7s+Q0Dln0Dt9tEzZsAjXXR/+ytBM7AlglaqyeMPxQszJ1JhlJdZ6jLA+IzldHtflX81em7lDao1xXu+aRRkg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-sunos-x64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-sunos-x64/-/typescript-sunos-x64-7.0.2.tgz", + "integrity": "sha512-dLJDGaLZ1D4HPQn62u1n8mBDkJREwMsAkCdkwd4Ieqw+x3TUyTsqY0YiBCtE6H6OzzgGk3iuZ3vFWRS+E8/d1g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-win32-arm64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-win32-arm64/-/typescript-win32-arm64-7.0.2.tgz", + "integrity": "sha512-Gyl1Vy6OsWesLzmq+EP0Fb7b4Nid5232AvcA2SFcdYreldpNtYFFofPjnt62y9hQy7VTaZp65ICJjuAQRaVcIQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-win32-x64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-win32-x64/-/typescript-win32-x64-7.0.2.tgz", + "integrity": "sha512-0BQ3HkAHHlKLSp1qRvf3SUhGpGsDuhB/jgFw75guyqbxJqEaS0Cw/VFO8i2nHglJUzQCRtMMR/IBAKE3ETMC4g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=16.20.0" + } + }, "node_modules/@vitest/expect": { "version": "1.6.1", "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-1.6.1.tgz", @@ -3349,6 +3690,41 @@ "node": ">=4" } }, + "node_modules/typescript": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-7.0.2.tgz", + "integrity": "sha512-8FYau96o3NKOhbjKi/qNvG/W5jhzxkbdm5sj9AbZ/5T5sWqn3hJgLfGx27sRKZWTvyzCP8dLRBTf5tBTSRVUNA==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc" + }, + "engines": { + "node": ">=16.20.0" + }, + "optionalDependencies": { + "@typescript/typescript-aix-ppc64": "7.0.2", + "@typescript/typescript-darwin-arm64": "7.0.2", + "@typescript/typescript-darwin-x64": "7.0.2", + "@typescript/typescript-freebsd-arm64": "7.0.2", + "@typescript/typescript-freebsd-x64": "7.0.2", + "@typescript/typescript-linux-arm": "7.0.2", + "@typescript/typescript-linux-arm64": "7.0.2", + "@typescript/typescript-linux-loong64": "7.0.2", + "@typescript/typescript-linux-mips64el": "7.0.2", + "@typescript/typescript-linux-ppc64": "7.0.2", + "@typescript/typescript-linux-riscv64": "7.0.2", + "@typescript/typescript-linux-s390x": "7.0.2", + "@typescript/typescript-linux-x64": "7.0.2", + "@typescript/typescript-netbsd-arm64": "7.0.2", + "@typescript/typescript-netbsd-x64": "7.0.2", + "@typescript/typescript-openbsd-arm64": "7.0.2", + "@typescript/typescript-openbsd-x64": "7.0.2", + "@typescript/typescript-sunos-x64": "7.0.2", + "@typescript/typescript-win32-arm64": "7.0.2", + "@typescript/typescript-win32-x64": "7.0.2" + } + }, "node_modules/ufo": { "version": "1.6.1", "resolved": "https://registry.npmjs.org/ufo/-/ufo-1.6.1.tgz", diff --git a/package.json b/package.json index 541b98c..565f8d8 100644 --- a/package.json +++ b/package.json @@ -5,8 +5,10 @@ "main": "dist/visual-image-tool.cjs", "module": "dist/visual-image-tool.esm.js", "browser": "dist/visual-image-tool.umd.js", + "types": "./src/index.d.ts", "exports": { ".": { + "types": "./src/index.d.ts", "import": "./dist/visual-image-tool.esm.js", "require": "./dist/visual-image-tool.cjs", "default": "./dist/visual-image-tool.esm.js" @@ -22,6 +24,7 @@ "publish:demo": "gh-pages -d demo --nojekyll --src \"**/!(index-local).*\"", "test": "vitest run", "test:watch": "vitest", + "types:check": "tsc --noEmit", "prepublishOnly": "npm run build", "lint:check": "biome check .", "lint:fix": "biome check --write .", @@ -55,6 +58,7 @@ "jsdom": "^24.1.3", "prettier": "3.5.3", "rollup": "^4.12.0", + "typescript": "^7.0.2", "vitest": "^1.6.1" }, "type": "module", diff --git a/src/index.d.ts b/src/index.d.ts new file mode 100644 index 0000000..eac87db --- /dev/null +++ b/src/index.d.ts @@ -0,0 +1,138 @@ +/** + * Type declarations for @h4md1/visual-image-tool + * + * All coordinates and dimensions are expressed in the image's *original* + * pixels, not the displayed (scaled) pixels, so they stay valid whatever + * size the image is rendered at. + */ + +/** A point in original image pixels. */ +export interface FocusPoint { + x: number; + y: number; +} + +/** A rectangle in original image pixels. */ +export interface CropZone { + x: number; + y: number; + width: number; + height: number; +} + +/** The image's intrinsic size in pixels. */ +export interface ImageDimensions { + width: number; + height: number; +} + +/** The payload handed to `onChange` whenever anything moves. */ +export interface ChangeData { + focusPoint: FocusPoint; + cropZone: CropZone; + focusActive: boolean; + cropActive: boolean; +} + +/** + * Appearance of the focus point marker. + * + * Merged into the defaults, so overriding one property leaves the rest alone. + */ +export interface FocusPointStyle { + width?: string; + height?: string; + border?: string; + boxShadow?: string; + backgroundColor?: string; +} + +/** + * Appearance of the crop overlay. + * + * Merged into the defaults — see {@link FocusPointStyle}. + */ +export interface CropZoneStyle { + border?: string; + backgroundColor?: string; +} + +/** + * Appearance of the eight crop resize handles. + * + * Merged into the defaults — see {@link FocusPointStyle}. + */ +export interface CropHandleStyle { + width?: string; + height?: string; + backgroundColor?: string; + border?: string; + boxShadow?: string; +} + +export interface FocusPointOptions { + /** Set false to disable the focus point entirely. Defaults to true. */ + enabled?: boolean; + style?: FocusPointStyle; +} + +export interface CropZoneOptions { + /** Set false to disable the crop zone entirely. Defaults to true. */ + enabled?: boolean; + style?: CropZoneStyle; + handleStyle?: CropHandleStyle; +} + +export interface VisualImageToolOptions { + /** The `` to attach to, or a CSS selector resolving to one. */ + imageElement: HTMLImageElement | HTMLElement | string; + focusPoint?: FocusPointOptions; + cropZone?: CropZoneOptions; + /** Called whenever the focus point or crop zone changes. */ + onChange?: (data: ChangeData) => void; + /** Logs overlay positioning details to the console. Defaults to false. */ + debug?: boolean; +} + +export declare class VisualImageTool { + /** + * @throws if `imageElement` is missing, or does not resolve to an ``. + */ + constructor(options: VisualImageToolOptions); + + /** + * Shows or hides the focus point. Omit `active` to toggle. + * + * The first time this is enabled, a focus point still sitting at its + * initial `{x: 0, y: 0}` is moved to the centre of the image — so read it + * back with {@link getFocusPoint} afterwards rather than before. + */ + toggleFocusPoint(active?: boolean): this; + + /** Shows or hides the crop zone. Omit `active` to toggle. */ + toggleCropZone(active?: boolean): this; + + /** Moves the focus point. Coordinates are clamped to the image. */ + setFocusPoint(x: number, y: number): this; + + /** + * Moves and resizes the crop zone. Values are clamped to the image, and + * width and height are held to a 10px minimum. + */ + setCropZone(x: number, y: number, width: number, height: number): this; + + /** The current focus point, in original image pixels. */ + getFocusPoint(): FocusPoint; + + /** The current crop zone, in original image pixels. */ + getCropZone(): CropZone; + + /** The image's intrinsic dimensions. */ + getImageDimensions(): ImageDimensions; + + /** + * Removes every element and listener the tool created. The instance is + * unusable afterwards; calling `destroy()` twice is safe. + */ + destroy(): void; +} diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..bfe828e --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,13 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "ESNext", + "moduleResolution": "bundler", + "lib": ["ES2020", "DOM"], + "strict": true, + "noEmit": true, + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true + }, + "include": ["src/index.d.ts", "types-test.ts"] +} diff --git a/types-test.ts b/types-test.ts new file mode 100644 index 0000000..cd6e43c --- /dev/null +++ b/types-test.ts @@ -0,0 +1,68 @@ +/** + * Compile-time exercise of the public type surface. Nothing here runs — the + * point is that `npm run types:check` fails if src/index.d.ts drifts away + * from the API the library actually exposes. + */ +import { + type ChangeData, + type CropZone, + type FocusPoint, + type ImageDimensions, + VisualImageTool, +} from "./src/index.js"; + +declare const img: HTMLImageElement; + +// Minimal construction. +new VisualImageTool({ imageElement: img }); + +// A CSS selector is accepted in place of an element. +new VisualImageTool({ imageElement: "#myImage" }); + +// Every option, with a typed onChange payload. +const tool = new VisualImageTool({ + imageElement: img, + debug: true, + focusPoint: { + enabled: true, + style: { + width: "30px", + height: "30px", + border: "3px solid white", + boxShadow: "0 0 0 2px black", + backgroundColor: "rgba(255, 0, 0, 0.5)", + }, + }, + cropZone: { + enabled: true, + style: { border: "1px dashed #fff", backgroundColor: "rgba(0,0,0,0.4)" }, + handleStyle: { width: "14px", height: "14px", backgroundColor: "white" }, + }, + onChange: (data: ChangeData) => { + const _x: number = data.focusPoint.x; + const _w: number = data.cropZone.width; + const _active: boolean = data.focusActive && data.cropActive; + }, +}); + +// Mutators chain. +tool + .toggleFocusPoint(true) + .toggleCropZone() + .setFocusPoint(120, 240) + .setCropZone(0, 0, 100, 100); + +// Accessors return the documented shapes. +const _focus: FocusPoint = tool.getFocusPoint(); +const _crop: CropZone = tool.getCropZone(); +const _dims: ImageDimensions = tool.getImageDimensions(); +tool.destroy(); + +// @ts-expect-error imageElement is required. +new VisualImageTool({}); + +// @ts-expect-error setFocusPoint takes two numbers. +tool.setFocusPoint("120", 240); + +// @ts-expect-error there is no such option. +new VisualImageTool({ imageElement: img, notAnOption: true });