@zoompinch/elements 0.0.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.
@@ -0,0 +1,418 @@
1
+ var x = Object.defineProperty;
2
+ var y = (r, t, e) => t in r ? x(r, t, { enumerable: !0, configurable: !0, writable: !0, value: e }) : r[t] = e;
3
+ var b = (r, t, e) => y(r, typeof t != "symbol" ? t + "" : t, e);
4
+ var E = Object.defineProperty, A = (r, t, e) => t in r ? E(r, t, { enumerable: !0, configurable: !0, writable: !0, value: e }) : r[t] = e, p = (r, t, e) => A(r, typeof t != "symbol" ? t + "" : t, e);
5
+ function z(r) {
6
+ return r * Math.PI / 180;
7
+ }
8
+ function T(r, t, e) {
9
+ return Math.min(Math.max(r, t), e);
10
+ }
11
+ function v(r, t, e) {
12
+ const [s, n] = r, [a, h] = t, o = Math.cos(e) * (s - a) - Math.sin(e) * (n - h) + a, c = Math.sin(e) * (s - a) + Math.cos(e) * (n - h) + h;
13
+ return [o, c];
14
+ }
15
+ function S(r, t) {
16
+ const e = Math.pow(10, t);
17
+ return Math.round(r * e) / e;
18
+ }
19
+ function I(r) {
20
+ var t = !1;
21
+ return r.wheelDeltaY ? r.wheelDeltaY === r.deltaY * -3 && (t = !0) : r.deltaMode === 0 && (t = !0), t;
22
+ }
23
+ function C(r, t) {
24
+ const e = t.find((s) => r % s === 0);
25
+ return e ? r / e : 1;
26
+ }
27
+ function W(r, t, e, s, n) {
28
+ let a = r.left - t, h = r.top - e;
29
+ const o = Math.cos(-n), c = Math.sin(-n);
30
+ let l = a * o - h * c, u = a * c + h * o;
31
+ const d = r.width / s, i = r.height / s;
32
+ return l /= s, u /= s, { x: S(l, 4), y: S(u, 4), width: S(d, 4), height: S(i, 4) };
33
+ }
34
+ class L extends EventTarget {
35
+ constructor(t, e, s, n, a, h, o = 0.1, c = 10) {
36
+ super(), p(this, "wrapperBounds"), p(this, "canvasBounds"), p(this, "gestureStartRadians", 0), p(this, "dragStart", null), p(this, "dragStartFrozenX", null), p(this, "dragStartFrozenY", null), p(this, "touchStarts", null), p(this, "touchStartTranslateX", 0), p(this, "touchStartTranslateY", 0), p(this, "centeredTransform", {
37
+ translateX: 0,
38
+ translateY: 0,
39
+ scale: 1,
40
+ radians: 0
41
+ }), this.element = t, this.offset = e, this.translateX = s, this.translateY = n, this.scale = a, this.rotate = h, this.minScale = o, this.maxScale = c;
42
+ const l = new ResizeObserver(() => {
43
+ const { x: d, y: i, width: g, height: f } = this.element.getBoundingClientRect();
44
+ this.wrapperBounds = { x: d, y: i, width: g, height: f }, this.update();
45
+ }), u = new ResizeObserver(() => {
46
+ const { x: d, y: i, width: g, height: f } = W(
47
+ this.canvasElement.getBoundingClientRect(),
48
+ this.renderingTranslateX,
49
+ this.renderingTranslateY,
50
+ this.renderinScale,
51
+ this.renderingRotate
52
+ );
53
+ this.canvasBounds = { x: d, y: i, width: g, height: f }, this.update();
54
+ });
55
+ requestAnimationFrame(() => {
56
+ this.wrapperBounds = this.element.getBoundingClientRect(), this.canvasBounds = this.canvasElement.getBoundingClientRect(), this.update();
57
+ }), u.observe(this.canvasElement), l.observe(this.element);
58
+ }
59
+ get canvasElement() {
60
+ return this.element.querySelector(".canvas");
61
+ }
62
+ get wrapperInnerX() {
63
+ return this.wrapperBounds.x + this.offset.left;
64
+ }
65
+ get wrapperInnerY() {
66
+ return this.wrapperBounds.y + this.offset.top;
67
+ }
68
+ get wrapperInnerWidth() {
69
+ return this.wrapperBounds.width - this.offset.left - this.offset.right;
70
+ }
71
+ get wrapperInnerHeight() {
72
+ return this.wrapperBounds.height - this.offset.top - this.offset.bottom;
73
+ }
74
+ // Calculate the inner ratio
75
+ get wrapperInnerRatio() {
76
+ return this.wrapperInnerWidth / this.wrapperInnerHeight;
77
+ }
78
+ get canvasNaturalRatio() {
79
+ return this.canvasBounds.width / this.canvasBounds.height;
80
+ }
81
+ // Get natural scale
82
+ get naturalScale() {
83
+ return this.canvasNaturalRatio >= this.wrapperInnerRatio ? this.wrapperInnerWidth / this.canvasBounds.width : this.wrapperInnerHeight / this.canvasBounds.height;
84
+ }
85
+ handleGesturestart(t) {
86
+ this.gestureStartRadians = this.rotate;
87
+ }
88
+ handleGesturechange(t) {
89
+ const { clientX: e, clientY: s } = t, n = t.rotation;
90
+ if (n === 0)
91
+ return;
92
+ const a = this.normalizeMatrixCoordinates(e, s);
93
+ this.rotateCanvas(a[0], a[1], this.gestureStartRadians + z(n));
94
+ }
95
+ handleGestureend(t) {
96
+ }
97
+ handleMousedown(t) {
98
+ t.preventDefault(), this.dragStart = [t.clientX, t.clientY], this.dragStartFrozenX = this.translateX, this.dragStartFrozenY = this.translateY;
99
+ }
100
+ handleMouseup(t) {
101
+ t.preventDefault(), this.dragStart = null, this.dragStartFrozenX = null, this.dragStartFrozenY = null;
102
+ }
103
+ handleMousemove(t) {
104
+ if (t.preventDefault(), this.dragStart && this.dragStartFrozenX !== null && this.dragStartFrozenY !== null) {
105
+ const e = t.clientX - this.dragStart[0], s = t.clientY - this.dragStart[1], n = this.dragStartFrozenX - -e, a = this.dragStartFrozenY - -s;
106
+ this.translateX = n, this.translateY = a, this.update();
107
+ }
108
+ }
109
+ handleWheel(t) {
110
+ let { deltaX: e, deltaY: s, ctrlKey: n } = t;
111
+ const a = [120, 100], h = 2;
112
+ I(t) || ((Math.abs(e) === 120 || Math.abs(e) === 200) && (e = e / (100 / h * C(e, a)) * Math.sign(e)), (Math.abs(s) === 120 || Math.abs(s) === 200) && (s = s / (100 / h * C(s, a)) * Math.sign(s)));
113
+ const o = this.scale;
114
+ if (n) {
115
+ const c = -s / 100 * o, l = T(o + c, this.minScale, this.maxScale), u = this.relativeWrapperCoordinatesFromClientCoords(t.clientX, t.clientY), [d, i] = this.calcProjectionTranslate(
116
+ l,
117
+ u,
118
+ this.normalizeMatrixCoordinates(t.clientX, t.clientY)
119
+ );
120
+ this.translateX = d, this.translateY = i, this.scale = l;
121
+ } else
122
+ this.translateX = this.translateX - e, this.translateY = this.translateY - s;
123
+ this.update(), t.preventDefault();
124
+ }
125
+ freezeTouches(t) {
126
+ return Array.from(t).map((e) => {
127
+ const s = this.clientCoordsToWrapperCoords(e.clientX, e.clientY);
128
+ return {
129
+ client: [e.clientX, e.clientY],
130
+ canvasRel: this.getCanvasCoordsRel(s[0], s[1])
131
+ };
132
+ });
133
+ }
134
+ handleTouchstart(t) {
135
+ this.touchStarts = this.freezeTouches(t.touches), this.touchStartTranslateX = this.translateX, this.touchStartTranslateY = this.translateY, t.preventDefault();
136
+ }
137
+ handleTouchmove(t) {
138
+ t.preventDefault();
139
+ const e = Array.from(t.touches).map(
140
+ (s) => this.clientCoordsToWrapperCoords(s.clientX, s.clientY)
141
+ );
142
+ if (this.touchStarts) {
143
+ if (e.length >= 2 && this.touchStarts.length >= 2) {
144
+ const s = [
145
+ this.touchStarts[0].canvasRel[0] * this.canvasBounds.width,
146
+ this.touchStarts[0].canvasRel[1] * this.canvasBounds.height
147
+ ], n = [
148
+ this.touchStarts[1].canvasRel[0] * this.canvasBounds.width,
149
+ this.touchStarts[1].canvasRel[1] * this.canvasBounds.height
150
+ ], a = Math.sqrt(
151
+ Math.pow(s[0] - n[0], 2) + Math.pow(s[1] - n[1], 2)
152
+ ), h = Math.sqrt(
153
+ Math.pow(e[0][0] - e[1][0], 2) + Math.pow(e[0][1] - e[1][1], 2)
154
+ ) / this.naturalScale, o = T(h / a, this.minScale, this.maxScale), c = [
155
+ e[0][0] / this.wrapperInnerWidth,
156
+ e[0][1] / this.wrapperInnerHeight
157
+ ], l = this.touchStarts[0].canvasRel, [u, d] = this.calcProjectionTranslate(o, c, l, 0);
158
+ let i = 0, g = 0, f = 0;
159
+ const m = Math.atan2(
160
+ n[1] - s[1],
161
+ n[0] - s[0]
162
+ );
163
+ f = Math.atan2(e[1][1] - e[0][1], e[1][0] - e[0][0]) - m;
164
+ const Y = (R, N) => [
165
+ this.offset.left + this.canvasBounds.width * R * this.naturalScale * o + u,
166
+ this.offset.top + this.canvasBounds.height * N * this.naturalScale * o + d
167
+ ], w = Y(0, 0), M = Y(
168
+ this.touchStarts[0].canvasRel[0],
169
+ this.touchStarts[0].canvasRel[1]
170
+ ), X = v(
171
+ w,
172
+ M,
173
+ f
174
+ );
175
+ i = X[0] - w[0], g = X[1] - w[1], this.scale = o, this.rotate = f, this.translateX = u + i, this.translateY = d + g;
176
+ } else {
177
+ const s = t.touches[0].clientX - this.touchStarts[0].client[0], n = t.touches[0].clientY - this.touchStarts[0].client[1], a = this.touchStartTranslateX + s, h = this.touchStartTranslateY + n;
178
+ this.translateX = a, this.translateY = h;
179
+ }
180
+ this.update();
181
+ }
182
+ }
183
+ handleTouchend(t) {
184
+ t.touches.length === 0 ? this.touchStarts = null : (this.touchStarts = this.freezeTouches(t.touches), this.touchStartTranslateX = this.translateX, this.touchStartTranslateY = this.translateY);
185
+ }
186
+ calcProjectionTranslate(t, e, s, n) {
187
+ const a = this.canvasBounds.width * this.naturalScale, h = this.canvasBounds.height * this.naturalScale, o = s[0] * a * t, c = s[1] * h * t, l = v([o, c], [0, 0], n ?? this.rotate), u = e[0] * this.wrapperInnerWidth, d = e[1] * this.wrapperInnerHeight, i = u - l[0], g = d - l[1];
188
+ return [i, g];
189
+ }
190
+ applyTransform(t, e, s) {
191
+ const n = this.calcProjectionTranslate(t, e, s, 0);
192
+ this.scale = t, this.translateX = n[0], this.translateY = n[1], this.update();
193
+ }
194
+ composeRelPoint(t, e, s, n, a, h) {
195
+ s = s ?? this.scale, n = n ?? this.translateX, a = a ?? this.translateY, h = h ?? this.rotate;
196
+ const o = [this.offset.left, this.offset.top], c = [
197
+ this.offset.left + this.canvasBounds.width * (s * this.naturalScale) * t,
198
+ this.offset.top + this.canvasBounds.height * (s * this.naturalScale) * e
199
+ ], l = v(c, o, h);
200
+ return [l[0] + n, l[1] + a];
201
+ }
202
+ composePoint(t, e) {
203
+ const s = t / this.canvasBounds.width, n = e / this.canvasBounds.height;
204
+ return this.composeRelPoint(s, n);
205
+ }
206
+ getAnchorOffset(t, e, s, n, a = [0.5, 0.5]) {
207
+ const h = this.calcProjectionTranslate(t, a, a, 0), o = [
208
+ this.offset.left + h[0] + this.canvasBounds.width * (t * this.naturalScale) * a[0],
209
+ this.offset.top + h[1] + this.canvasBounds.height * (t * this.naturalScale) * a[1]
210
+ ], c = this.composeRelPoint(a[0], a[1], t, e, s, n), l = c[0] - o[0], u = c[1] - o[1];
211
+ return [l, u];
212
+ }
213
+ // Converts absolute inner wrapper coordinates to relative canvas coordinates (0-1, 0-1)
214
+ getCanvasCoordsRel(t, e) {
215
+ const s = [0, 0], n = [t - this.translateX, e - this.translateY], a = v(n, s, -this.rotate), h = [a[0] / this.renderinScale, a[1] / this.renderinScale];
216
+ return [h[0] / this.canvasBounds.width, h[1] / this.canvasBounds.height];
217
+ }
218
+ // Converts absolute client to coordinates to absolute inner-wrapper coorinates
219
+ clientCoordsToWrapperCoords(t, e) {
220
+ return [t - this.wrapperInnerX, e - this.wrapperInnerY];
221
+ }
222
+ // Converts absolute client coordinates to relative wrapper coordinates (0-1, 0-1)
223
+ relativeWrapperCoordinatesFromClientCoords(t, e) {
224
+ const [s, n] = this.clientCoordsToWrapperCoords(t, e);
225
+ return [s / this.wrapperInnerWidth, n / this.wrapperInnerHeight];
226
+ }
227
+ // Converts client coordinates to relative canvas coordinates (0-1, 0-1)
228
+ normalizeMatrixCoordinates(t, e) {
229
+ const s = this.clientCoordsToWrapperCoords(t, e);
230
+ return this.getCanvasCoordsRel(s[0], s[1]);
231
+ }
232
+ // Converts client coordinates to absolute canvas coordinates
233
+ normalizeClientCoords(t, e) {
234
+ const [s, n] = this.normalizeMatrixCoordinates(t, e);
235
+ return [s * this.canvasBounds.width, n * this.canvasBounds.height];
236
+ }
237
+ rotateCanvas(t, e, s) {
238
+ const n = this.composeRelPoint(t, e, this.scale, 0, 0, s), a = this.composeRelPoint(t, e);
239
+ this.translateX = a[0] - n[0], this.translateY = a[1] - n[1], this.rotate = s, this.update();
240
+ }
241
+ get renderinScale() {
242
+ return this.naturalScale * this.scale;
243
+ }
244
+ get renderingTranslateX() {
245
+ return this.offset.left + this.translateX;
246
+ }
247
+ get renderingTranslateY() {
248
+ return this.offset.top + this.translateY;
249
+ }
250
+ get renderingRotate() {
251
+ return this.rotate;
252
+ }
253
+ update() {
254
+ this.canvasElement.style.transformOrigin = "top left", this.canvasElement.style.transform = `translateX(${this.renderingTranslateX}px) translateY(${this.renderingTranslateY}px) scale(${this.renderinScale}) rotate(${this.renderingRotate}rad)`, this.dispatchEvent(new Event("update"));
255
+ }
256
+ destroy() {
257
+ }
258
+ }
259
+ class B extends HTMLElement {
260
+ constructor() {
261
+ super();
262
+ b(this, "engine");
263
+ this.attachShadow({ mode: "open" });
264
+ }
265
+ get contentEl() {
266
+ return this.shadowRoot.querySelector(".content");
267
+ }
268
+ get canvasElement() {
269
+ return this.shadowRoot.querySelector(".canvas");
270
+ }
271
+ connectedCallback() {
272
+ this.shadowRoot.innerHTML = `
273
+ <style>
274
+ :host {
275
+ display: block;
276
+ touch-action: none;
277
+ }
278
+ .content {
279
+ display: inline-block;
280
+ will-change: transform;
281
+ width: 100%;
282
+ height: 100%;
283
+ overflow: hidden;
284
+ position: relative;
285
+ }
286
+ .canvas {
287
+ display: inline-block;
288
+ }
289
+ .matrix {
290
+ position: absolute;
291
+ top: 0;
292
+ left: 0;
293
+ pointer-events: none;
294
+ width: 100%;
295
+ height: 100%;
296
+ }
297
+ </style>
298
+ <div class="content" style="touch-action: none;">
299
+ <div class="canvas">
300
+ <slot></slot>
301
+ </div>
302
+ <div class="matrix">
303
+ <slot name="matrix"></slot>
304
+ </div>
305
+ </div>
306
+ `;
307
+ const e = Number(this.getAttribute("translate-x") || "0"), s = Number(this.getAttribute("translate-y") || "0"), n = Number(this.getAttribute("scale") || "1"), a = Number(this.getAttribute("rotate") || "0"), h = Number(this.getAttribute("min-scale")), o = Number(this.getAttribute("max-scale")), c = Number(this.getAttribute("offset-top")), l = Number(this.getAttribute("offset-right")), u = Number(this.getAttribute("offset-bottom")), d = Number(this.getAttribute("offset-left"));
308
+ this.engine = new L(
309
+ this.contentEl,
310
+ {
311
+ top: isNaN(c) ? 100 : c,
312
+ left: isNaN(d) ? 0 : d,
313
+ right: isNaN(l) ? 0 : l,
314
+ bottom: isNaN(u) ? 0 : u
315
+ },
316
+ e,
317
+ s,
318
+ n,
319
+ a,
320
+ isNaN(h) ? void 0 : h,
321
+ isNaN(o) ? void 0 : o
322
+ ), this.contentEl.addEventListener("wheel", (i) => this.engine.handleWheel(i)), this.contentEl.addEventListener(
323
+ "gesturestart",
324
+ (i) => this.engine.handleGesturestart(i)
325
+ ), window.addEventListener(
326
+ "gesturechange",
327
+ (i) => this.engine.handleGesturechange(i)
328
+ ), window.addEventListener(
329
+ "gestureend",
330
+ (i) => this.engine.handleGestureend(i)
331
+ ), this.contentEl.addEventListener(
332
+ "mousedown",
333
+ (i) => this.engine.handleMousedown(i)
334
+ ), window.addEventListener("mousemove", (i) => this.engine.handleMousemove(i)), window.addEventListener("mouseup", (i) => this.engine.handleMouseup(i)), this.contentEl.addEventListener(
335
+ "touchstart",
336
+ (i) => this.engine.handleTouchstart(i)
337
+ ), window.addEventListener("touchmove", (i) => this.engine.handleTouchmove(i)), window.addEventListener("touchend", (i) => this.engine.handleTouchend(i)), this.engine.addEventListener("update", () => {
338
+ const i = this.engine.translateX.toString(), g = this.engine.translateY.toString(), f = this.engine.scale.toString(), m = this.engine.rotate.toString();
339
+ this.getAttribute("translate-x") !== i && this.setAttribute("translate-x", i), this.getAttribute("translate-y") !== g && this.setAttribute("translate-y", g), this.getAttribute("scale") !== f && this.setAttribute("scale", f), this.getAttribute("rotate") !== m && this.setAttribute("rotate", m), this.dispatchEvent(new Event("update"));
340
+ });
341
+ }
342
+ disconnectedCallback() {
343
+ this.engine.destroy();
344
+ }
345
+ attributeChangedCallback(e, s, n) {
346
+ if (this.engine)
347
+ switch (e) {
348
+ case "translate-x":
349
+ const a = Number(n);
350
+ this.engine.translateX !== a && (this.engine.translateX = a, this.engine.update());
351
+ break;
352
+ case "translate-y":
353
+ const h = Number(n);
354
+ this.engine.translateY !== h && (this.engine.translateY = h, this.engine.update());
355
+ break;
356
+ case "scale":
357
+ const o = Number(n);
358
+ this.engine.scale !== o && (this.engine.scale = o, this.engine.update());
359
+ break;
360
+ case "rotate":
361
+ const c = Number(n);
362
+ this.engine.rotate !== c && (this.engine.rotate = c, this.engine.update());
363
+ break;
364
+ case "min-scale":
365
+ const l = Number(n);
366
+ !isNaN(l) && this.engine.minScale !== l && (this.engine.minScale = l, this.engine.update());
367
+ break;
368
+ case "max-scale":
369
+ const u = Number(n);
370
+ !isNaN(u) && this.engine.maxScale !== u && (this.engine.maxScale = u, this.engine.update());
371
+ break;
372
+ case "offset-top":
373
+ case "offset-right":
374
+ case "offset-bottom":
375
+ case "offset-left":
376
+ const d = Number(this.getAttribute("offset-top") || "0"), i = Number(this.getAttribute("offset-right") || "0"), g = Number(this.getAttribute("offset-bottom") || "0"), f = Number(this.getAttribute("offset-left") || "0");
377
+ this.engine.offset = {
378
+ top: d,
379
+ right: i,
380
+ bottom: g,
381
+ left: f
382
+ }, this.engine.update();
383
+ break;
384
+ }
385
+ }
386
+ get canvasWidth() {
387
+ return this.engine.canvasBounds.width;
388
+ }
389
+ get canvasHeight() {
390
+ return this.engine.canvasBounds.height;
391
+ }
392
+ applyTransform(e, s, n) {
393
+ this.engine.applyTransform(
394
+ e,
395
+ s,
396
+ n
397
+ );
398
+ }
399
+ normalizeClientCoords(e, s) {
400
+ return this.engine.normalizeClientCoords(e, s);
401
+ }
402
+ composePoint(e, s) {
403
+ return this.engine.composePoint(e, s);
404
+ }
405
+ }
406
+ b(B, "observedAttributes", [
407
+ "translate-x",
408
+ "translate-y",
409
+ "scale",
410
+ "rotate",
411
+ "min-scale",
412
+ "max-scale",
413
+ "offset-top",
414
+ "offset-right",
415
+ "offset-bottom",
416
+ "offset-left"
417
+ ]);
418
+ customElements.get("zoom-pinch") || customElements.define("zoom-pinch", B);
@@ -0,0 +1,35 @@
1
+ (function(m){typeof define=="function"&&define.amd?define(m):m()})((function(){"use strict";var W=Object.defineProperty;var L=(m,v,g)=>v in m?W(m,v,{enumerable:!0,configurable:!0,writable:!0,value:g}):m[v]=g;var X=(m,v,g)=>L(m,typeof v!="symbol"?v+"":v,g);var m=Object.defineProperty,v=(h,t,e)=>t in h?m(h,t,{enumerable:!0,configurable:!0,writable:!0,value:e}):h[t]=e,g=(h,t,e)=>v(h,typeof t!="symbol"?t+"":t,e);function N(h){return h*Math.PI/180}function T(h,t,e){return Math.min(Math.max(h,t),e)}function S(h,t,e){const[s,n]=h,[a,r]=t,o=Math.cos(e)*(s-a)-Math.sin(e)*(n-r)+a,c=Math.sin(e)*(s-a)+Math.cos(e)*(n-r)+r;return[o,c]}function w(h,t){const e=Math.pow(10,t);return Math.round(h*e)/e}function x(h){var t=!1;return h.wheelDeltaY?h.wheelDeltaY===h.deltaY*-3&&(t=!0):h.deltaMode===0&&(t=!0),t}function C(h,t){const e=t.find(s=>h%s===0);return e?h/e:1}function y(h,t,e,s,n){let a=h.left-t,r=h.top-e;const o=Math.cos(-n),c=Math.sin(-n);let l=a*o-r*c,u=a*c+r*o;const d=h.width/s,i=h.height/s;return l/=s,u/=s,{x:w(l,4),y:w(u,4),width:w(d,4),height:w(i,4)}}class E extends EventTarget{constructor(t,e,s,n,a,r,o=.1,c=10){super(),g(this,"wrapperBounds"),g(this,"canvasBounds"),g(this,"gestureStartRadians",0),g(this,"dragStart",null),g(this,"dragStartFrozenX",null),g(this,"dragStartFrozenY",null),g(this,"touchStarts",null),g(this,"touchStartTranslateX",0),g(this,"touchStartTranslateY",0),g(this,"centeredTransform",{translateX:0,translateY:0,scale:1,radians:0}),this.element=t,this.offset=e,this.translateX=s,this.translateY=n,this.scale=a,this.rotate=r,this.minScale=o,this.maxScale=c;const l=new ResizeObserver(()=>{const{x:d,y:i,width:f,height:p}=this.element.getBoundingClientRect();this.wrapperBounds={x:d,y:i,width:f,height:p},this.update()}),u=new ResizeObserver(()=>{const{x:d,y:i,width:f,height:p}=y(this.canvasElement.getBoundingClientRect(),this.renderingTranslateX,this.renderingTranslateY,this.renderinScale,this.renderingRotate);this.canvasBounds={x:d,y:i,width:f,height:p},this.update()});requestAnimationFrame(()=>{this.wrapperBounds=this.element.getBoundingClientRect(),this.canvasBounds=this.canvasElement.getBoundingClientRect(),this.update()}),u.observe(this.canvasElement),l.observe(this.element)}get canvasElement(){return this.element.querySelector(".canvas")}get wrapperInnerX(){return this.wrapperBounds.x+this.offset.left}get wrapperInnerY(){return this.wrapperBounds.y+this.offset.top}get wrapperInnerWidth(){return this.wrapperBounds.width-this.offset.left-this.offset.right}get wrapperInnerHeight(){return this.wrapperBounds.height-this.offset.top-this.offset.bottom}get wrapperInnerRatio(){return this.wrapperInnerWidth/this.wrapperInnerHeight}get canvasNaturalRatio(){return this.canvasBounds.width/this.canvasBounds.height}get naturalScale(){return this.canvasNaturalRatio>=this.wrapperInnerRatio?this.wrapperInnerWidth/this.canvasBounds.width:this.wrapperInnerHeight/this.canvasBounds.height}handleGesturestart(t){this.gestureStartRadians=this.rotate}handleGesturechange(t){const{clientX:e,clientY:s}=t,n=t.rotation;if(n===0)return;const a=this.normalizeMatrixCoordinates(e,s);this.rotateCanvas(a[0],a[1],this.gestureStartRadians+N(n))}handleGestureend(t){}handleMousedown(t){t.preventDefault(),this.dragStart=[t.clientX,t.clientY],this.dragStartFrozenX=this.translateX,this.dragStartFrozenY=this.translateY}handleMouseup(t){t.preventDefault(),this.dragStart=null,this.dragStartFrozenX=null,this.dragStartFrozenY=null}handleMousemove(t){if(t.preventDefault(),this.dragStart&&this.dragStartFrozenX!==null&&this.dragStartFrozenY!==null){const e=t.clientX-this.dragStart[0],s=t.clientY-this.dragStart[1],n=this.dragStartFrozenX- -e,a=this.dragStartFrozenY- -s;this.translateX=n,this.translateY=a,this.update()}}handleWheel(t){let{deltaX:e,deltaY:s,ctrlKey:n}=t;const a=[120,100],r=2;x(t)||((Math.abs(e)===120||Math.abs(e)===200)&&(e=e/(100/r*C(e,a))*Math.sign(e)),(Math.abs(s)===120||Math.abs(s)===200)&&(s=s/(100/r*C(s,a))*Math.sign(s)));const o=this.scale;if(n){const c=-s/100*o,l=T(o+c,this.minScale,this.maxScale),u=this.relativeWrapperCoordinatesFromClientCoords(t.clientX,t.clientY),[d,i]=this.calcProjectionTranslate(l,u,this.normalizeMatrixCoordinates(t.clientX,t.clientY));this.translateX=d,this.translateY=i,this.scale=l}else this.translateX=this.translateX-e,this.translateY=this.translateY-s;this.update(),t.preventDefault()}freezeTouches(t){return Array.from(t).map(e=>{const s=this.clientCoordsToWrapperCoords(e.clientX,e.clientY);return{client:[e.clientX,e.clientY],canvasRel:this.getCanvasCoordsRel(s[0],s[1])}})}handleTouchstart(t){this.touchStarts=this.freezeTouches(t.touches),this.touchStartTranslateX=this.translateX,this.touchStartTranslateY=this.translateY,t.preventDefault()}handleTouchmove(t){t.preventDefault();const e=Array.from(t.touches).map(s=>this.clientCoordsToWrapperCoords(s.clientX,s.clientY));if(this.touchStarts){if(e.length>=2&&this.touchStarts.length>=2){const s=[this.touchStarts[0].canvasRel[0]*this.canvasBounds.width,this.touchStarts[0].canvasRel[1]*this.canvasBounds.height],n=[this.touchStarts[1].canvasRel[0]*this.canvasBounds.width,this.touchStarts[1].canvasRel[1]*this.canvasBounds.height],a=Math.sqrt(Math.pow(s[0]-n[0],2)+Math.pow(s[1]-n[1],2)),r=Math.sqrt(Math.pow(e[0][0]-e[1][0],2)+Math.pow(e[0][1]-e[1][1],2))/this.naturalScale,o=T(r/a,this.minScale,this.maxScale),c=[e[0][0]/this.wrapperInnerWidth,e[0][1]/this.wrapperInnerHeight],l=this.touchStarts[0].canvasRel,[u,d]=this.calcProjectionTranslate(o,c,l,0);let i=0,f=0,p=0;const b=Math.atan2(n[1]-s[1],n[0]-s[0]);p=Math.atan2(e[1][1]-e[0][1],e[1][0]-e[0][0])-b;const M=(z,I)=>[this.offset.left+this.canvasBounds.width*z*this.naturalScale*o+u,this.offset.top+this.canvasBounds.height*I*this.naturalScale*o+d],Y=M(0,0),A=M(this.touchStarts[0].canvasRel[0],this.touchStarts[0].canvasRel[1]),R=S(Y,A,p);i=R[0]-Y[0],f=R[1]-Y[1],this.scale=o,this.rotate=p,this.translateX=u+i,this.translateY=d+f}else{const s=t.touches[0].clientX-this.touchStarts[0].client[0],n=t.touches[0].clientY-this.touchStarts[0].client[1],a=this.touchStartTranslateX+s,r=this.touchStartTranslateY+n;this.translateX=a,this.translateY=r}this.update()}}handleTouchend(t){t.touches.length===0?this.touchStarts=null:(this.touchStarts=this.freezeTouches(t.touches),this.touchStartTranslateX=this.translateX,this.touchStartTranslateY=this.translateY)}calcProjectionTranslate(t,e,s,n){const a=this.canvasBounds.width*this.naturalScale,r=this.canvasBounds.height*this.naturalScale,o=s[0]*a*t,c=s[1]*r*t,l=S([o,c],[0,0],n??this.rotate),u=e[0]*this.wrapperInnerWidth,d=e[1]*this.wrapperInnerHeight,i=u-l[0],f=d-l[1];return[i,f]}applyTransform(t,e,s){const n=this.calcProjectionTranslate(t,e,s,0);this.scale=t,this.translateX=n[0],this.translateY=n[1],this.update()}composeRelPoint(t,e,s,n,a,r){s=s??this.scale,n=n??this.translateX,a=a??this.translateY,r=r??this.rotate;const o=[this.offset.left,this.offset.top],c=[this.offset.left+this.canvasBounds.width*(s*this.naturalScale)*t,this.offset.top+this.canvasBounds.height*(s*this.naturalScale)*e],l=S(c,o,r);return[l[0]+n,l[1]+a]}composePoint(t,e){const s=t/this.canvasBounds.width,n=e/this.canvasBounds.height;return this.composeRelPoint(s,n)}getAnchorOffset(t,e,s,n,a=[.5,.5]){const r=this.calcProjectionTranslate(t,a,a,0),o=[this.offset.left+r[0]+this.canvasBounds.width*(t*this.naturalScale)*a[0],this.offset.top+r[1]+this.canvasBounds.height*(t*this.naturalScale)*a[1]],c=this.composeRelPoint(a[0],a[1],t,e,s,n),l=c[0]-o[0],u=c[1]-o[1];return[l,u]}getCanvasCoordsRel(t,e){const s=[0,0],n=[t-this.translateX,e-this.translateY],a=S(n,s,-this.rotate),r=[a[0]/this.renderinScale,a[1]/this.renderinScale];return[r[0]/this.canvasBounds.width,r[1]/this.canvasBounds.height]}clientCoordsToWrapperCoords(t,e){return[t-this.wrapperInnerX,e-this.wrapperInnerY]}relativeWrapperCoordinatesFromClientCoords(t,e){const[s,n]=this.clientCoordsToWrapperCoords(t,e);return[s/this.wrapperInnerWidth,n/this.wrapperInnerHeight]}normalizeMatrixCoordinates(t,e){const s=this.clientCoordsToWrapperCoords(t,e);return this.getCanvasCoordsRel(s[0],s[1])}normalizeClientCoords(t,e){const[s,n]=this.normalizeMatrixCoordinates(t,e);return[s*this.canvasBounds.width,n*this.canvasBounds.height]}rotateCanvas(t,e,s){const n=this.composeRelPoint(t,e,this.scale,0,0,s),a=this.composeRelPoint(t,e);this.translateX=a[0]-n[0],this.translateY=a[1]-n[1],this.rotate=s,this.update()}get renderinScale(){return this.naturalScale*this.scale}get renderingTranslateX(){return this.offset.left+this.translateX}get renderingTranslateY(){return this.offset.top+this.translateY}get renderingRotate(){return this.rotate}update(){this.canvasElement.style.transformOrigin="top left",this.canvasElement.style.transform=`translateX(${this.renderingTranslateX}px) translateY(${this.renderingTranslateY}px) scale(${this.renderinScale}) rotate(${this.renderingRotate}rad)`,this.dispatchEvent(new Event("update"))}destroy(){}}class B extends HTMLElement{constructor(){super();X(this,"engine");this.attachShadow({mode:"open"})}get contentEl(){return this.shadowRoot.querySelector(".content")}get canvasElement(){return this.shadowRoot.querySelector(".canvas")}connectedCallback(){this.shadowRoot.innerHTML=`
2
+ <style>
3
+ :host {
4
+ display: block;
5
+ touch-action: none;
6
+ }
7
+ .content {
8
+ display: inline-block;
9
+ will-change: transform;
10
+ width: 100%;
11
+ height: 100%;
12
+ overflow: hidden;
13
+ position: relative;
14
+ }
15
+ .canvas {
16
+ display: inline-block;
17
+ }
18
+ .matrix {
19
+ position: absolute;
20
+ top: 0;
21
+ left: 0;
22
+ pointer-events: none;
23
+ width: 100%;
24
+ height: 100%;
25
+ }
26
+ </style>
27
+ <div class="content" style="touch-action: none;">
28
+ <div class="canvas">
29
+ <slot></slot>
30
+ </div>
31
+ <div class="matrix">
32
+ <slot name="matrix"></slot>
33
+ </div>
34
+ </div>
35
+ `;const e=Number(this.getAttribute("translate-x")||"0"),s=Number(this.getAttribute("translate-y")||"0"),n=Number(this.getAttribute("scale")||"1"),a=Number(this.getAttribute("rotate")||"0"),r=Number(this.getAttribute("min-scale")),o=Number(this.getAttribute("max-scale")),c=Number(this.getAttribute("offset-top")),l=Number(this.getAttribute("offset-right")),u=Number(this.getAttribute("offset-bottom")),d=Number(this.getAttribute("offset-left"));this.engine=new E(this.contentEl,{top:isNaN(c)?100:c,left:isNaN(d)?0:d,right:isNaN(l)?0:l,bottom:isNaN(u)?0:u},e,s,n,a,isNaN(r)?void 0:r,isNaN(o)?void 0:o),this.contentEl.addEventListener("wheel",i=>this.engine.handleWheel(i)),this.contentEl.addEventListener("gesturestart",i=>this.engine.handleGesturestart(i)),window.addEventListener("gesturechange",i=>this.engine.handleGesturechange(i)),window.addEventListener("gestureend",i=>this.engine.handleGestureend(i)),this.contentEl.addEventListener("mousedown",i=>this.engine.handleMousedown(i)),window.addEventListener("mousemove",i=>this.engine.handleMousemove(i)),window.addEventListener("mouseup",i=>this.engine.handleMouseup(i)),this.contentEl.addEventListener("touchstart",i=>this.engine.handleTouchstart(i)),window.addEventListener("touchmove",i=>this.engine.handleTouchmove(i)),window.addEventListener("touchend",i=>this.engine.handleTouchend(i)),this.engine.addEventListener("update",()=>{const i=this.engine.translateX.toString(),f=this.engine.translateY.toString(),p=this.engine.scale.toString(),b=this.engine.rotate.toString();this.getAttribute("translate-x")!==i&&this.setAttribute("translate-x",i),this.getAttribute("translate-y")!==f&&this.setAttribute("translate-y",f),this.getAttribute("scale")!==p&&this.setAttribute("scale",p),this.getAttribute("rotate")!==b&&this.setAttribute("rotate",b),this.dispatchEvent(new Event("update"))})}disconnectedCallback(){this.engine.destroy()}attributeChangedCallback(e,s,n){if(this.engine)switch(e){case"translate-x":const a=Number(n);this.engine.translateX!==a&&(this.engine.translateX=a,this.engine.update());break;case"translate-y":const r=Number(n);this.engine.translateY!==r&&(this.engine.translateY=r,this.engine.update());break;case"scale":const o=Number(n);this.engine.scale!==o&&(this.engine.scale=o,this.engine.update());break;case"rotate":const c=Number(n);this.engine.rotate!==c&&(this.engine.rotate=c,this.engine.update());break;case"min-scale":const l=Number(n);!isNaN(l)&&this.engine.minScale!==l&&(this.engine.minScale=l,this.engine.update());break;case"max-scale":const u=Number(n);!isNaN(u)&&this.engine.maxScale!==u&&(this.engine.maxScale=u,this.engine.update());break;case"offset-top":case"offset-right":case"offset-bottom":case"offset-left":const d=Number(this.getAttribute("offset-top")||"0"),i=Number(this.getAttribute("offset-right")||"0"),f=Number(this.getAttribute("offset-bottom")||"0"),p=Number(this.getAttribute("offset-left")||"0");this.engine.offset={top:d,right:i,bottom:f,left:p},this.engine.update();break}}get canvasWidth(){return this.engine.canvasBounds.width}get canvasHeight(){return this.engine.canvasBounds.height}applyTransform(e,s,n){this.engine.applyTransform(e,s,n)}normalizeClientCoords(e,s){return this.engine.normalizeClientCoords(e,s)}composePoint(e,s){return this.engine.composePoint(e,s)}}X(B,"observedAttributes",["translate-x","translate-y","scale","rotate","min-scale","max-scale","offset-top","offset-right","offset-bottom","offset-left"]),customElements.get("zoom-pinch")||customElements.define("zoom-pinch",B)}));
package/package.json ADDED
@@ -0,0 +1,30 @@
1
+ {
2
+ "name": "@zoompinch/elements",
3
+ "description": "Pinch-and-zoom experience that's feels native and communicates the transform reactively and lets you project any layer on top of the transformed canvas",
4
+ "version": "0.0.1",
5
+ "type": "module",
6
+ "main": "./dist/zoompinch-elements.umd.js",
7
+ "module": "./dist/zoompinch-elements.es.js",
8
+ "types": "./dist/index.d.ts",
9
+ "exports": {
10
+ ".": {
11
+ "import": "./dist/zoompinch-elements.es.js",
12
+ "require": "./dist/zoompinch-elements.umd.js",
13
+ "types": "./dist/index.d.ts"
14
+ }
15
+ },
16
+ "files": [
17
+ "dist"
18
+ ],
19
+ "scripts": {
20
+ "build": "vite build",
21
+ "dev": "vite"
22
+ },
23
+ "devDependencies": {
24
+ "typescript": "^5.9.3",
25
+ "vite": "^6.4.1"
26
+ },
27
+ "dependencies": {
28
+ "@zoompinch/core": "^0.0.3"
29
+ }
30
+ }