annotate-image 2.0.0-beta.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json ADDED
@@ -0,0 +1,117 @@
1
+ {
2
+ "name": "annotate-image",
3
+ "version": "2.0.0-beta.1",
4
+ "description": "Create Flickr-like comment annotations on images — draw rectangles, add notes, save via AJAX or static data",
5
+ "license": "GPL-2.0",
6
+ "repository": {
7
+ "type": "git",
8
+ "url": "https://github.com/flipbit/jquery-image-annotate.git"
9
+ },
10
+ "files": [
11
+ "dist/",
12
+ "LICENSE",
13
+ "README.md"
14
+ ],
15
+ "main": "dist/core.js",
16
+ "types": "dist/types/index.d.ts",
17
+ "exports": {
18
+ ".": {
19
+ "types": "./dist/types/index.d.ts",
20
+ "import": "./dist/core.js",
21
+ "default": "./dist/core.min.js"
22
+ },
23
+ "./jquery": {
24
+ "types": "./dist/types/jquery.annotate.d.ts",
25
+ "import": "./dist/jquery.js",
26
+ "default": "./dist/jquery.min.js"
27
+ },
28
+ "./react": {
29
+ "types": "./dist/types/react.d.ts",
30
+ "import": "./dist/react.js"
31
+ },
32
+ "./vue": {
33
+ "types": "./dist/types/vue.d.ts",
34
+ "import": "./dist/vue.js"
35
+ },
36
+ "./css": "./dist/css/annotate.min.css"
37
+ },
38
+ "scripts": {
39
+ "clean": "rm -rf dist",
40
+ "build:dirs": "mkdir -p dist/css dist/types",
41
+ "build:check": "tsc --noEmit",
42
+ "build:types": "tsc --emitDeclarationOnly --noEmit false --declaration --declarationDir dist/types --outDir dist/types",
43
+ "build:core": "esbuild src/index.ts --bundle --format=esm --outfile=dist/core.js",
44
+ "build:core:iife": "esbuild src/index.ts --bundle --minify --format=iife --global-name=AnnotateImage --outfile=dist/core.min.js",
45
+ "build:jquery": "esbuild src/jquery.annotate.ts --bundle --format=esm --external:jquery --outfile=dist/jquery.js",
46
+ "build:jquery:iife": "esbuild src/jquery.annotate.ts --bundle --minify --external:jquery --outfile=dist/jquery.min.js",
47
+ "build:react": "esbuild src/react.tsx --bundle --format=esm --external:react --external:react-dom --outfile=dist/react.js",
48
+ "build:vue": "esbuild src/vue.ts --bundle --format=esm --external:vue --outfile=dist/vue.js",
49
+ "build:css": "esbuild src/annotation.css --minify --outfile=dist/css/annotate.min.css",
50
+ "build": "npm run clean && npm run build:dirs && npm run build:check && npm run build:types && npm run build:core && npm run build:core:iife && npm run build:jquery && npm run build:jquery:iife && npm run build:react && npm run build:vue && npm run build:css",
51
+ "test": "vitest run",
52
+ "test:watch": "vitest",
53
+ "test:jquery4": "npm install jquery@4 --no-save && vitest run; STATUS=$?; npm install --no-save; exit $STATUS",
54
+ "test:e2e": "npx playwright test --config e2e/playwright.config.ts",
55
+ "lint": "eslint src/ test/",
56
+ "lint:fix": "eslint --fix src/ test/",
57
+ "format": "prettier --write 'src/**/*.{ts,css}' 'test/**/*.ts' '*.{js,ts,json}'",
58
+ "format:check": "prettier --check 'src/**/*.{ts,css}' 'test/**/*.ts' '*.{js,ts,json}'",
59
+ "demo": "npm run build && concurrently -k \"npm:demo:watch:*\" \"npm:demo:serve\"",
60
+ "demo:watch:core": "esbuild src/index.ts --bundle --format=esm --outfile=dist/core.js --watch",
61
+ "demo:watch:core-min": "esbuild src/index.ts --bundle --minify --format=iife --global-name=AnnotateImage --outfile=dist/core.min.js --watch",
62
+ "demo:watch:jquery": "esbuild src/jquery.annotate.ts --bundle --format=esm --external:jquery --outfile=dist/jquery.js --watch",
63
+ "demo:watch:jquery-min": "esbuild src/jquery.annotate.ts --bundle --minify --external:jquery --outfile=dist/jquery.min.js --watch",
64
+ "demo:watch:react": "esbuild src/react.tsx --bundle --format=esm --external:react --external:react-dom --outfile=dist/react.js --watch",
65
+ "demo:watch:vue": "esbuild src/vue.ts --bundle --format=esm --external:vue --outfile=dist/vue.js --watch",
66
+ "demo:watch:css": "esbuild src/annotation.css --minify --outfile=dist/css/annotate.min.css --watch",
67
+ "demo:serve": "live-server --port=8080 --open=demo/index.html --watch=dist,demo",
68
+ "demo:ci": "npm run build && concurrently -k \"npm:demo:watch:*\" \"npm:demo:serve:ci\"",
69
+ "demo:serve:ci": "live-server --port=8080 --no-browser --watch=dist,demo"
70
+ },
71
+ "devDependencies": {
72
+ "@eslint/js": "^9.39.2",
73
+ "@playwright/test": "^1.58.2",
74
+ "@testing-library/dom": "^10.4.1",
75
+ "@testing-library/react": "^16.3.2",
76
+ "@types/jquery": "^3.5.33",
77
+ "@types/react": "^18.3.28",
78
+ "@types/react-dom": "^18.3.7",
79
+ "@vue/test-utils": "^2.4.6",
80
+ "concurrently": "^9.2.1",
81
+ "esbuild": "^0.27.3",
82
+ "eslint": "^9.39.2",
83
+ "eslint-config-prettier": "^10.1.8",
84
+ "eslint-plugin-react-hooks": "^7.0.1",
85
+ "eslint-plugin-vue": "^10.8.0",
86
+ "jquery": "^3.7.1",
87
+ "jsdom": "^28.0.0",
88
+ "live-server": "^1.2.2",
89
+ "prettier": "^3.8.1",
90
+ "react": "^18.3.1",
91
+ "react-dom": "^18.3.1",
92
+ "typescript": "^5.9.3",
93
+ "typescript-eslint": "^8.55.0",
94
+ "vitest": "^4.0.18",
95
+ "vue": "^3.5.28"
96
+ },
97
+ "peerDependencies": {
98
+ "jquery": ">=3.7.0",
99
+ "react": ">=18.0.0",
100
+ "react-dom": ">=18.0.0",
101
+ "vue": ">=3.3.0"
102
+ },
103
+ "peerDependenciesMeta": {
104
+ "jquery": {
105
+ "optional": true
106
+ },
107
+ "react": {
108
+ "optional": true
109
+ },
110
+ "react-dom": {
111
+ "optional": true
112
+ },
113
+ "vue": {
114
+ "optional": true
115
+ }
116
+ }
117
+ }
package/readme.md ADDED
@@ -0,0 +1,389 @@
1
+ # Annotate Image
2
+
3
+ A JavaScript image annotation plugin that creates Flickr-like comment annotations on images. Users can draw rectangular regions on images, add text notes, and persist annotations via callbacks or AJAX.
4
+
5
+ Works standalone (vanilla JS), or with jQuery, React, or Vue. Framework adapters are tree-shakeable — only the one you import gets bundled.
6
+
7
+ ## Installation
8
+
9
+ ```sh
10
+ npm install annotate-image
11
+ ```
12
+
13
+ jQuery, React, and Vue are optional peer dependencies. Install only what you use:
14
+
15
+ ```sh
16
+ # For jQuery projects
17
+ npm install jquery
18
+
19
+ # For React projects
20
+ npm install react react-dom
21
+
22
+ # For Vue projects
23
+ npm install vue
24
+ ```
25
+
26
+ ## Usage
27
+
28
+ ### Vanilla JS
29
+
30
+ ```html
31
+ <link rel="stylesheet" href="dist/css/annotate.min.css">
32
+ <script src="dist/core.min.js"></script>
33
+ ```
34
+
35
+ ```js
36
+ const instance = AnnotateImage.annotate(document.getElementById('myImage'), {
37
+ editable: true,
38
+ notes: [
39
+ { top: 286, left: 161, width: 52, height: 37,
40
+ text: 'Small people on the steps',
41
+ id: 'note-1', editable: false },
42
+ { top: 134, left: 179, width: 68, height: 74,
43
+ text: 'National Gallery Dome',
44
+ id: 'note-2', editable: true },
45
+ ],
46
+ onChange(notes) { console.log('Notes changed:', notes); },
47
+ });
48
+
49
+ // Programmatic control
50
+ instance.add(); // Enter add-note mode
51
+ instance.getNotes(); // Get current annotations
52
+ instance.clear(); // Remove all annotations
53
+ instance.destroy(); // Tear down completely
54
+ ```
55
+
56
+ ### ES Modules
57
+
58
+ ```js
59
+ import { annotate } from 'annotate-image';
60
+ import 'annotate-image/css';
61
+
62
+ const instance = annotate(document.getElementById('myImage'), {
63
+ editable: true,
64
+ notes: [],
65
+ });
66
+ ```
67
+
68
+ ### jQuery
69
+
70
+ ```html
71
+ <link rel="stylesheet" href="dist/css/annotate.min.css">
72
+ <script src="https://code.jquery.com/jquery-3.7.1.min.js"></script>
73
+ <script src="dist/jquery.min.js"></script>
74
+ ```
75
+
76
+ ```js
77
+ $(function() {
78
+ $('#myImage').annotateImage({
79
+ editable: true,
80
+ notes: [/* ... */],
81
+ });
82
+ });
83
+
84
+ // Destroy
85
+ $('#myImage').annotateImage('destroy');
86
+ ```
87
+
88
+ Or as an ES module:
89
+
90
+ ```js
91
+ import 'annotate-image/jquery';
92
+ import 'annotate-image/css';
93
+ ```
94
+
95
+ ### React
96
+
97
+ ```tsx
98
+ import { useRef } from 'react';
99
+ import { AnnotateImage } from 'annotate-image/react';
100
+ import type { AnnotateImageRef } from 'annotate-image/react';
101
+ import 'annotate-image/css';
102
+
103
+ function App() {
104
+ const ref = useRef<AnnotateImageRef>(null);
105
+
106
+ return (
107
+ <>
108
+ <AnnotateImage
109
+ ref={ref}
110
+ src="/photo.jpg"
111
+ width={800}
112
+ height={600}
113
+ editable
114
+ notes={[]}
115
+ onChange={(notes) => console.log(notes)}
116
+ onSave={(note) => console.log('Saved:', note)}
117
+ onDelete={(note) => console.log('Deleted:', note)}
118
+ />
119
+ <button onClick={() => ref.current?.add()}>Add Note</button>
120
+ <button onClick={() => console.log(ref.current?.getNotes())}>
121
+ Get Notes
122
+ </button>
123
+ </>
124
+ );
125
+ }
126
+ ```
127
+
128
+ ### Vue
129
+
130
+ ```vue
131
+ <script setup lang="ts">
132
+ import { ref } from 'vue';
133
+ import { AnnotateImage } from 'annotate-image/vue';
134
+ import 'annotate-image/css';
135
+
136
+ const annotator = ref();
137
+ </script>
138
+
139
+ <template>
140
+ <AnnotateImage
141
+ ref="annotator"
142
+ src="/photo.jpg"
143
+ :width="800"
144
+ :height="600"
145
+ editable
146
+ :notes="[]"
147
+ @change="(notes) => console.log(notes)"
148
+ @save="(note) => console.log('Saved:', note)"
149
+ @delete="(note) => console.log('Deleted:', note)"
150
+ />
151
+ <button @click="annotator?.add()">Add Note</button>
152
+ <button @click="console.log(annotator?.getNotes())">Get Notes</button>
153
+ </template>
154
+ ```
155
+
156
+ ## API Reference
157
+
158
+ ### Core: `annotate(img, options?)`
159
+
160
+ Creates an annotation layer on an image element. Returns an `AnnotateImage` instance.
161
+
162
+ #### Options
163
+
164
+ | Option | Type | Default | Description |
165
+ |--------|------|---------|-------------|
166
+ | `editable` | `boolean` | `true` | Enable annotation editing |
167
+ | `notes` | `AnnotationNote[]` | `[]` | Initial annotations |
168
+ | `api` | `AnnotateApi` | — | Server persistence (see below) |
169
+ | `onChange` | `(notes: NoteData[]) => void` | — | Called after any notes mutation |
170
+ | `onSave` | `(note: NoteData) => void` | — | Called after a note is saved |
171
+ | `onDelete` | `(note: NoteData) => void` | — | Called after a note is deleted |
172
+ | `onLoad` | `(notes: NoteData[]) => void` | — | Called after notes are loaded |
173
+ | `onError` | `(ctx: AnnotateErrorContext) => void` | — | Called on API errors (defaults to `console.error`) |
174
+
175
+ #### `AnnotateApi`
176
+
177
+ Each field accepts a URL string (default fetch behavior) or a function for full control. Omit `api` entirely for static-only mode.
178
+
179
+ ```typescript
180
+ {
181
+ load?: string | (() => Promise<AnnotationNote[]>),
182
+ save?: string | ((note: NoteData) => Promise<SaveResult>),
183
+ delete?: string | ((note: NoteData) => Promise<void>),
184
+ }
185
+ ```
186
+
187
+ #### Instance Methods
188
+
189
+ | Method | Returns | Description |
190
+ |--------|---------|-------------|
191
+ | `add()` | `boolean` | Enter add-note mode. Returns false if already editing. |
192
+ | `clear()` | `void` | Remove all annotations. |
193
+ | `destroy()` | `void` | Tear down completely. Idempotent. |
194
+ | `getNotes()` | `NoteData[]` | Current annotations (internal fields stripped). |
195
+ | `load()` | `void` | Rebuild views from the current notes array. |
196
+
197
+ #### Data Types
198
+
199
+ ```typescript
200
+ // Full annotation (includes internal fields)
201
+ interface AnnotationNote {
202
+ id: string;
203
+ top: number;
204
+ left: number;
205
+ width: number;
206
+ height: number;
207
+ text: string;
208
+ editable: boolean;
209
+ }
210
+
211
+ // Annotation data passed to callbacks (no internal fields)
212
+ type NoteData = Omit<AnnotationNote, 'view' | 'editable'>;
213
+ ```
214
+
215
+ ### React: `<AnnotateImage>`
216
+
217
+ #### Props (`AnnotateImageProps`)
218
+
219
+ | Prop | Type | Default | Description |
220
+ |------|------|---------|-------------|
221
+ | `src` | `string` | — | Image URL (required) |
222
+ | `width` | `number` | — | Image width in pixels |
223
+ | `height` | `number` | — | Image height in pixels |
224
+ | `notes` | `AnnotationNote[]` | `[]` | Initial annotations |
225
+ | `editable` | `boolean` | `true` | Enable editing |
226
+ | `onChange` | `(notes: NoteData[]) => void` | — | Notes changed |
227
+ | `onSave` | `(note: NoteData) => void` | — | Note saved |
228
+ | `onDelete` | `(note: NoteData) => void` | — | Note deleted |
229
+ | `onLoad` | `(notes: NoteData[]) => void` | — | Notes loaded |
230
+ | `onError` | `(ctx: AnnotateErrorContext) => void` | — | Error occurred |
231
+
232
+ #### Ref Methods (`AnnotateImageRef`)
233
+
234
+ | Method | Returns | Description |
235
+ |--------|---------|-------------|
236
+ | `add()` | `void` | Enter add-note mode |
237
+ | `clear()` | `void` | Remove all annotations |
238
+ | `destroy()` | `void` | Tear down the instance |
239
+ | `getNotes()` | `NoteData[]` | Get current annotations |
240
+
241
+ ### Vue: `<AnnotateImage>`
242
+
243
+ #### Props
244
+
245
+ | Prop | Type | Default | Description |
246
+ |------|------|---------|-------------|
247
+ | `src` | `String` | — | Image URL (required) |
248
+ | `width` | `Number` | — | Image width in pixels |
249
+ | `height` | `Number` | — | Image height in pixels |
250
+ | `notes` | `AnnotationNote[]` | — | Initial annotations |
251
+ | `editable` | `Boolean` | `true` | Enable editing |
252
+
253
+ #### Emits
254
+
255
+ | Event | Payload | Description |
256
+ |-------|---------|-------------|
257
+ | `change` | `NoteData[]` | Notes changed |
258
+ | `save` | `NoteData` | Note saved |
259
+ | `delete` | `NoteData` | Note deleted |
260
+ | `load` | `NoteData[]` | Notes loaded |
261
+ | `error` | `AnnotateErrorContext` | Error occurred |
262
+
263
+ #### Exposed Methods (via template ref)
264
+
265
+ | Method | Returns | Description |
266
+ |--------|---------|-------------|
267
+ | `add()` | `void` | Enter add-note mode |
268
+ | `clear()` | `void` | Remove all annotations |
269
+ | `destroy()` | `void` | Tear down the instance |
270
+ | `getNotes()` | `NoteData[]` | Get current annotations |
271
+ | `notes` | `Ref<NoteData[]>` | Reactive current notes |
272
+
273
+ ## Tree Shaking
274
+
275
+ Each entry point (`annotate-image`, `annotate-image/jquery`, `annotate-image/react`, `annotate-image/vue`) is a separate bundle. Importing one does not pull in the others. Unused framework adapters are excluded automatically by bundlers that support package `exports`.
276
+
277
+ ## Error Handling
278
+
279
+ The `onError` callback receives a context object:
280
+
281
+ ```typescript
282
+ interface AnnotateErrorContext {
283
+ type: 'load' | 'save' | 'delete';
284
+ error: Error;
285
+ note?: AnnotationNote;
286
+ }
287
+ ```
288
+
289
+ If no `onError` callback is provided, errors are logged to the console.
290
+
291
+ ## Accessibility
292
+
293
+ The plugin supports keyboard navigation:
294
+
295
+ - **Tab** to navigate between annotation areas and controls
296
+ - **Enter/Space** to activate buttons and open editable annotations
297
+ - **Escape** to cancel editing
298
+ - Annotation areas and buttons have `role="button"` and `tabindex` attributes
299
+ - Focus is managed automatically when entering/exiting edit mode
300
+ - Visible focus styles on all interactive elements
301
+
302
+ ## Demos
303
+
304
+ Run the demo server locally:
305
+
306
+ ```sh
307
+ npm run demo
308
+ ```
309
+
310
+ This opens a browser at `http://localhost:8080/demo/index.html` with links to demos including static annotations, AJAX loading, vanilla JS, React, Vue, and multiple instances.
311
+
312
+ ## Build
313
+
314
+ ```sh
315
+ npm install
316
+ npm run build # Type-check, bundle, minify
317
+ npm run build:check # Type-check only
318
+ npm test # Run tests
319
+ npm run test:jquery4 # Run tests against jQuery 4
320
+ ```
321
+
322
+ ### Build Output
323
+
324
+ `dist/` contains the built artifacts:
325
+
326
+ - `dist/core.js` — Core library (ESM, no dependencies)
327
+ - `dist/core.min.js` — Core library (IIFE, minified, `AnnotateImage` global)
328
+ - `dist/jquery.js` — jQuery adapter (ESM, jQuery external)
329
+ - `dist/jquery.min.js` — jQuery adapter (IIFE, minified, jQuery external)
330
+ - `dist/react.js` — React component (ESM, React external)
331
+ - `dist/vue.js` — Vue component (ESM, Vue external)
332
+ - `dist/css/annotate.min.css` — Minified styles
333
+ - `dist/types/` — TypeScript declaration files
334
+
335
+ ## Browser Compatibility
336
+
337
+ All modern browsers (Chrome, Firefox, Safari, Edge). The plugin uses pointer events and `fetch`, so IE is not supported.
338
+
339
+ ## History
340
+
341
+ ### Version 2.0
342
+
343
+ * Package renamed from `jquery-image-annotate` to `annotate-image`
344
+ * Rewritten in TypeScript with vanilla DOM internals
345
+ * Removed jQuery UI dependency — drag/resize uses vanilla pointer events
346
+ * Added core vanilla JS API (`annotate()` factory) that works without jQuery
347
+ * jQuery adapter is now a thin wrapper registering `$.fn.annotateImage`
348
+ * Added React 18+ and Vue 3+ framework components
349
+ * Lifecycle callbacks: `onChange`, `onSave`, `onDelete`, `onLoad`
350
+ * `getNotes()` method for reading current annotations
351
+ * Replaced `$.ajax`/`$.getJSON` with `fetch`
352
+ * Build system replaced: Bower/Grunt removed in favor of npm/esbuild
353
+ * Added ESM and IIFE bundle outputs with TypeScript declarations
354
+ * Supports jQuery 3.x and 4.x
355
+ * Keyboard accessibility for all interactive elements
356
+ * Full test suite using Vitest
357
+
358
+ ### Version 1.4 — 19th January, 2011
359
+ * Upgraded jQuery to version 1.7.1
360
+
361
+ ### Version 1.3 — 22nd June, 2009
362
+ * Fixed a bug when creating a new annotation via AJAX.
363
+ * The Id of the annotation is expected to be returned as a JSON object from the response of the save call, e.g.
364
+
365
+ `{ "annotation_id": "000001" }`
366
+
367
+ ### Version 1.2 — 24th April, 2009
368
+ * Fixed jQuery UI 1.3.2 compatibility.
369
+ * Forked source for jQuery 1.2.x and 1.3.x
370
+ * Notes now fade in/out.
371
+ * Tidied up CSS/positioning.
372
+
373
+ ### Version 1.1 — 2nd April, 2009
374
+ * Fixed bug when annotating an image with no previous annotations.
375
+
376
+ ### Version 1.0 — 11th March, 2009
377
+ * Initial release
378
+
379
+ ## Credits
380
+
381
+ Based on the Drupal extension:
382
+
383
+ Image Annotations by Ronan Berder
384
+ hunvreus@gmail.com
385
+ http://drupal.org/project/image_annotate
386
+
387
+ ## Licence
388
+
389
+ Released under the [GNU GPL v2](LICENSE) license.