@annotorious/annotorious 3.3.6 → 3.4.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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@annotorious/annotorious",
3
- "version": "3.3.6",
3
+ "version": "3.4.1",
4
4
  "description": "Add image annotation functionality to any web page with a few lines of JavaScript",
5
5
  "author": "Rainer Simon",
6
6
  "license": "BSD-3-Clause",
@@ -41,15 +41,15 @@
41
41
  "@tsconfig/svelte": "^5.0.4",
42
42
  "@types/rbush": "^4.0.0",
43
43
  "jsdom": "^26.1.0",
44
- "svelte": "^4.2.19",
44
+ "svelte": "^4.2.20",
45
45
  "svelte-preprocess": "^6.0.3",
46
46
  "typescript": "5.8.3",
47
- "vite": "^5.4.18",
48
- "vite-plugin-dts": "^4.5.3",
49
- "vitest": "^3.1.2"
47
+ "vite": "^5.4.19",
48
+ "vite-plugin-dts": "^4.5.4",
49
+ "vitest": "^3.1.4"
50
50
  },
51
51
  "dependencies": {
52
- "@annotorious/core": "3.3.6",
52
+ "@annotorious/core": "3.4.1",
53
53
  "rbush": "^4.0.1",
54
54
  "svg-pathdata": "^7.2.0",
55
55
  "uuid": "^11.1.0"
@@ -11,7 +11,7 @@
11
11
  top: 0;
12
12
  touch-action: none;
13
13
  width: 100%;
14
-
14
+ -webkit-tap-highlight-color: transparent;
15
15
  -webkit-user-select: none;
16
16
  -moz-user-select: none;
17
17
  -ms-user-select: none;
@@ -34,6 +34,7 @@
34
34
  fill: transparent;
35
35
  shape-rendering: geometricPrecision;
36
36
  vector-effect: non-scaling-stroke;
37
+ -webkit-tap-highlight-color: transparent;
37
38
  }
38
39
 
39
40
  .a9s-edge-handle {
@@ -50,6 +51,10 @@
50
51
  cursor: move;
51
52
  }
52
53
 
54
+ .a9s-handle.a9s-corner-handle {
55
+ cursor: crosshair;
56
+ }
57
+
53
58
  .a9s-edge-handle-top {
54
59
  cursor: n-resize;
55
60
  }
@@ -143,16 +143,19 @@
143
143
  const getEditor = (shape: Shape): typeof SvelteComponent => _getEditor(shape)!;
144
144
  </script>
145
145
 
146
+ <!-- svelte-ignore a11y-no-noninteractive-tabindex -->
146
147
  <svg
147
148
  bind:this={svgEl}
149
+ role="application"
150
+ tabindex={0}
148
151
  class="a9s-annotationlayer"
149
152
  class:drawing={tool}
153
+ class:editing={editableAnnotations}
150
154
  class:hidden={!visible}
151
155
  class:hover={$hover}
152
156
  on:pointerup={onPointerUp}
153
157
  on:pointerdown={onPointerDown}
154
158
  on:pointermove={onPointerMove}>
155
-
156
159
  <g>
157
160
  {#each $store.filter(a => isImageAnnotation(a)) as annotation}
158
161
  {#if isImageAnnotation(annotation) && !isEditable(annotation)}
@@ -9,6 +9,7 @@
9
9
  export let shape: Shape;
10
10
  export let editor: (shape: Shape, handle: string, delta: [number, number]) => Shape;
11
11
  export let transform: Transform;
12
+ export let svgEl: SVGSVGElement;
12
13
 
13
14
  let grabbedHandle: string | undefined;
14
15
 
@@ -18,7 +19,22 @@
18
19
 
19
20
  const onGrab = (handle: string) => (evt: PointerEvent) => {
20
21
  grabbedHandle = handle;
21
- origin = transform.elementToImage(evt.offsetX, evt.offsetY);
22
+
23
+ // For legacy compatibility: offsetX and offsetY are not
24
+ // available on synthetic events, currently used by the
25
+ // new PolygonEditor. Old versions of Annotorious, however,
26
+ // won't yet forward the svgEl prop!
27
+ if (svgEl) {
28
+ const { left, top } = svgEl.getBoundingClientRect();
29
+ const offsetX = evt.clientX - left;
30
+ const offsetY = evt.clientY - top;
31
+
32
+ origin = transform.elementToImage(offsetX, offsetY);
33
+ } else {
34
+ const { offsetX, offsetY } = evt;
35
+ origin = transform.elementToImage(offsetX, offsetY);
36
+ }
37
+
22
38
  initialShape = shape;
23
39
 
24
40
  const target = evt.target as Element;
@@ -27,7 +27,13 @@
27
27
  onMount(() => {
28
28
  editorComponent = new editor({
29
29
  target,
30
- props: { shape: annotation.target.selector, computedStyle, transform, viewportScale }
30
+ props: {
31
+ shape: annotation.target.selector,
32
+ computedStyle,
33
+ transform,
34
+ viewportScale,
35
+ svgEl: target.closest('svg')
36
+ }
31
37
  });
32
38
 
33
39
  editorComponent.$on('change', event => {
@@ -5,19 +5,19 @@
5
5
  export let x: number;
6
6
  export let y: number;
7
7
  export let scale: number;
8
- export let radius: number = 30;
8
+ export let selected: Boolean | undefined = undefined;
9
9
 
10
10
  let touched = false;
11
11
 
12
- const onPointerDown = (event: PointerEvent) => {
13
- if (event.pointerType === 'touch')
12
+ const onPointerDown = (evt: PointerEvent) => {
13
+ if (evt.pointerType === 'touch')
14
14
  touched = true;
15
15
  }
16
16
 
17
17
  const onPointerUp = () =>
18
18
  touched = false;
19
19
 
20
- $: handleSize = 10 / scale;
20
+ $: handleRadius = 4 / scale;
21
21
  </script>
22
22
 
23
23
  {#if isTouch}
@@ -25,42 +25,101 @@
25
25
  <circle
26
26
  cx={x}
27
27
  cy={y}
28
- r={radius / scale}
28
+ r={handleRadius * 10}
29
29
  class="a9s-touch-halo"
30
- class:touched={touched}
30
+ class:touched={touched} />
31
+
32
+ <circle
33
+ cx={x}
34
+ cy={y}
35
+ r={handleRadius + 10 / scale}
36
+ class="a9s-handle-buffer"
31
37
  on:pointerdown
38
+ on:pointerup
32
39
  on:pointerdown={onPointerDown}
33
- on:pointerup={onPointerUp} />
40
+ on:pointerup={onPointerUp} />
34
41
 
35
- <rect
36
- class={`a9s-handle ${$$props.class || ''}`.trim()}
37
- x={x - handleSize / 2}
38
- y={y - handleSize / 2}
39
- width={handleSize}
40
- height={handleSize}
41
- on:pointerdown
42
- on:pointerdown={onPointerDown}
43
- on:pointerup={onPointerUp} />
42
+ <circle
43
+ class="a9s-handle-dot"
44
+ cx={x}
45
+ cy={y}
46
+ r={handleRadius + 2 / scale} />
44
47
  </g>
45
48
  {:else}
46
- <rect
47
- class={`a9s-handle ${$$props.class || ''}`.trim()}
48
- x={x - handleSize / 2}
49
- y={y - handleSize / 2}
50
- width={handleSize}
51
- height={handleSize}
52
- on:pointerdown />
49
+ <g class={`a9s-handle ${$$props.class || ''}`.trim()}>
50
+ <circle
51
+ class="a9s-handle-buffer"
52
+ cx={x}
53
+ cy={y}
54
+ r={handleRadius + (6 / scale)}
55
+ on:pointerenter
56
+ on:pointerleave
57
+ on:pointerdown
58
+ on:pointerdown={onPointerDown}
59
+ on:pointerup
60
+ on:pointerup={onPointerUp} />
61
+
62
+ {#if selected}
63
+ <circle
64
+ class="a9s-handle-selected"
65
+ cx={x}
66
+ cy={y}
67
+ r={handleRadius + (8 / scale)} />
68
+ {/if}
69
+
70
+ <circle
71
+ class={`a9s-handle-dot${selected ? ' selected': ''}`}
72
+ cx={x}
73
+ cy={y}
74
+ r={handleRadius} />
75
+ </g>
53
76
  {/if}
54
77
 
55
78
  <style>
56
79
  .a9s-touch-halo {
57
80
  fill: transparent;
81
+ pointer-events: none;
58
82
  stroke-width: 0;
83
+ transition: fill 150ms;
59
84
  }
60
85
 
61
86
  .a9s-touch-halo.touched {
62
- fill: rgba(255, 255, 255, 0.25);
87
+ fill: rgba(255, 255, 255, 0.4);
88
+ }
89
+
90
+ .a9s-handle-buffer {
91
+ fill: transparent;
92
+ }
93
+
94
+ .a9s-handle-dot {
95
+ fill: #fff;
96
+ pointer-events: none;
97
+ stroke: rgba(0, 0, 0, 0.35);
98
+ stroke-width: 1px;
99
+ vector-effect: non-scaling-stroke;
100
+ }
101
+
102
+ .a9s-handle-dot.selected {
103
+ fill: #1a1a1a;
104
+ stroke: none;
63
105
  }
64
106
 
107
+ .a9s-handle-selected {
108
+ animation: dash-rotate 350ms linear infinite reverse;
109
+ fill: rgba(255, 255, 255, 0.25);
110
+ stroke: rgba(0, 0, 0, 0.9);
111
+ stroke-dasharray: 2 2;
112
+ stroke-width: 1px;
113
+ pointer-events: none;
114
+ vector-effect: non-scaling-stroke;
115
+ }
65
116
 
66
- </style>
117
+ @keyframes dash-rotate {
118
+ 0% {
119
+ stroke-dashoffset: 0;
120
+ }
121
+ 100% {
122
+ stroke-dashoffset: 4; /* Sum of dash + gap */
123
+ }
124
+ }
125
+ </style>
@@ -11,6 +11,7 @@
11
11
  multipolygonElementToPath
12
12
  } from '../../../model';
13
13
  import type { Transform } from '../../Transform';
14
+ import { getMaskDimensions } from '../../utils';
14
15
  import { Editor, Handle } from '..';
15
16
 
16
17
  /** Props */
@@ -18,6 +19,7 @@
18
19
  export let computedStyle: string | undefined;
19
20
  export let transform: Transform;
20
21
  export let viewportScale: number = 1;
22
+ export let svgEl: SVGSVGElement;
21
23
 
22
24
  $: geom = shape.geometry;
23
25
 
@@ -76,22 +78,33 @@
76
78
  }
77
79
  } as MultiPolygon;
78
80
  }
81
+
82
+ $: mask = getMaskDimensions(geom.bounds, 2 / viewportScale);
83
+
84
+ const maskId = `multipoly-mask-${Math.random().toString(36).substring(2, 12)}`;
79
85
  </script>
80
86
 
81
87
  <Editor
82
88
  shape={shape}
83
89
  transform={transform}
84
90
  editor={editor}
91
+ svgEl={svgEl}
85
92
  on:change
86
93
  on:grab
87
94
  on:release
88
95
  let:grab={grab}>
89
-
90
96
  {#each geom.polygons as element, elementIdx}
91
97
  <g>
98
+ <defs>
99
+ <mask id={`${maskId}-${elementIdx}`} class="a9s-multipolygon-editor-mask">
100
+ <rect x={mask.x} y={mask.y} width={mask.w} height={mask.h} />
101
+ <path d={multipolygonElementToPath(element)} />
102
+ </mask>
103
+ </defs>
104
+
92
105
  <path
93
106
  class="a9s-outer"
94
- style={computedStyle ? 'display:none;' : undefined}
107
+ mask={`url(#${maskId}-${elementIdx})`}
95
108
  fill-rule="evenodd"
96
109
  on:pointerdown={grab('SHAPE')}
97
110
  d={multipolygonElementToPath(element)} />
@@ -106,6 +119,7 @@
106
119
  {#each element.rings as ring, ringIdx}
107
120
  {#each ring.points as point, pointIdx}
108
121
  <Handle
122
+ class="a9s-corner-handle"
109
123
  on:pointerdown={grab(`HANDLE-${elementIdx}-${ringIdx}-${pointIdx}`)}
110
124
  x={point[0]} y={point[1]}
111
125
  scale={viewportScale} />
@@ -113,4 +127,14 @@
113
127
  {/each}
114
128
  </g>
115
129
  {/each}
116
- </Editor>
130
+ </Editor>
131
+
132
+ <style>
133
+ mask.a9s-multipolygon-editor-mask > rect {
134
+ fill: #fff;
135
+ }
136
+
137
+ mask.a9s-multipolygon-editor-mask > path {
138
+ fill: #000;
139
+ }
140
+ </style>
@@ -0,0 +1,76 @@
1
+ <script lang="ts">
2
+ import { isTouch } from '../../utils';
3
+
4
+ /** props **/
5
+ export let x: number;
6
+ export let y: number;
7
+ export let scale: number;
8
+
9
+ let touched = false;
10
+
11
+ const onPointerDown = (evt: PointerEvent) => {
12
+ if (evt.pointerType === 'touch')
13
+ touched = true;
14
+ }
15
+
16
+ const onPointerUp = () =>
17
+ touched = false;
18
+
19
+ $: handleRadius = 4 / scale;
20
+ </script>
21
+
22
+ {#if isTouch}
23
+ <circle
24
+ cx={x}
25
+ cy={y}
26
+ r={handleRadius} />
27
+ {:else}
28
+ <g class="a9s-polygon-midpoint">
29
+ <circle
30
+ class="a9s-polygon-midpoint-buffer"
31
+ cx={x}
32
+ cy={y}
33
+ r={1.75 * handleRadius}
34
+ on:pointerdown
35
+ on:pointerdown={onPointerDown} />
36
+
37
+ <circle
38
+ class="a9s-polygon-midpoint-outer"
39
+ cx={x}
40
+ cy={y}
41
+ r={handleRadius} />
42
+
43
+ <circle
44
+ class="a9s-polygon-midpoint-inner"
45
+ cx={x}
46
+ cy={y}
47
+ r={handleRadius} />
48
+ </g>
49
+ {/if}
50
+
51
+ <style>
52
+ .a9s-polygon-midpoint {
53
+ cursor: crosshair;
54
+ }
55
+
56
+ .a9s-polygon-midpoint-buffer {
57
+ fill: transparent;
58
+ }
59
+
60
+ .a9s-polygon-midpoint-outer {
61
+ display: none;
62
+ fill: transparent;
63
+ pointer-events: none;
64
+ stroke: rgba(0, 0, 0, 0.35);
65
+ stroke-width: 1.5px;
66
+ vector-effect: non-scaling-stroke;
67
+ }
68
+
69
+ .a9s-polygon-midpoint-inner {
70
+ fill: rgba(0, 0, 0, 0.25);
71
+ pointer-events: none;
72
+ stroke: #fff;
73
+ stroke-width: 1px;
74
+ vector-effect: non-scaling-stroke;
75
+ }
76
+ </style>