@web-atoms/web-controls 2.4.41 → 2.4.43

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,270 @@
1
+ import Bind from "@web-atoms/core/dist/core/Bind";
2
+ import { BindableProperty } from "@web-atoms/core/dist/core/BindableProperty";
3
+ import XNode, { isTemplateSymbol } from "@web-atoms/core/dist/core/XNode";
4
+ import { AtomControl } from "@web-atoms/core/dist/web/controls/AtomControl";
5
+
6
+ import "./styles/zoom-view-style";
7
+
8
+ const center = (ev: TouchEvent) => {
9
+ const touch = ev.touches[0];
10
+ if (touch) {
11
+ return {
12
+ x: touch.clientX,
13
+ y: touch.clientY
14
+ }
15
+ }
16
+ return {
17
+ x: 0,
18
+ y: 0
19
+ };
20
+ }
21
+
22
+ const distance = (first: Touch, second: Touch) => {
23
+ return Math.hypot(first.pageX - second.pageX, first.pageY - second.pageY);
24
+ };
25
+
26
+ export interface IZoom {
27
+ anchorX: number;
28
+ anchorY: number;
29
+ x: number;
30
+ y: number;
31
+ scale: number;
32
+ }
33
+
34
+ export default class ZoomView extends AtomControl {
35
+
36
+ @BindableProperty
37
+ public zoom: IZoom;
38
+
39
+ @BindableProperty
40
+ public source: string;
41
+
42
+ @BindableProperty
43
+ private loading: boolean;
44
+
45
+ private image: HTMLImageElement;
46
+
47
+ private scrollDiv: HTMLDivElement;
48
+
49
+ private imageContainer: HTMLDivElement;
50
+
51
+ protected preCreate() {
52
+
53
+ this.element.title = "Use mouse wheel to zoom";
54
+
55
+ this.loading = false;
56
+ this.zoom = {
57
+ scale: 0,
58
+ anchorX: 0,
59
+ anchorY: 0,
60
+ x: 0,
61
+ y: 0
62
+ };
63
+
64
+ this.element.setAttribute("data-zoom-view", "zoom-view");
65
+
66
+ this.element.draggable = false;
67
+
68
+ this.render(<div>
69
+ <div class="scroll">
70
+ <img
71
+ class="image-container"
72
+ src={Bind.oneWay(() => this.getSource(this.source))}
73
+ style-opacity={Bind.oneWay(() => this.loading ? "0.3" : "1")}
74
+ event-load={() => {
75
+ this.loading = false;
76
+ this.updateZoom(this.zoom);
77
+ }}
78
+ />
79
+ </div>
80
+ <i class={Bind.oneWay(() => this.loading ? "spinner fa-duotone fa-spinner fa-spin" : "hide")}/>
81
+ <i
82
+ event-click={() => this.updateZoom()}
83
+ class={Bind.oneWay(() => this.zoom.scale ? "scale" : "hide")}
84
+ title="Display entire image"/>
85
+ </div>);
86
+ this.scrollDiv = this.element.firstElementChild as HTMLDivElement;
87
+ this.imageContainer = this.scrollDiv.firstElementChild as HTMLDivElement;
88
+ this.image = this.imageContainer as HTMLImageElement;
89
+
90
+ const scrollView = this.element;
91
+
92
+ let previous: {x: number, y: number};
93
+
94
+ let touchMoveDisposable;
95
+ let touchEndDisposable;
96
+
97
+ this.bindEvent(scrollView, "touchstart", (evs: TouchEvent) => {
98
+
99
+ previous = center(evs);
100
+ let previousDistance = undefined;
101
+
102
+
103
+ touchMoveDisposable ??= this.bindEvent(scrollView, "touchmove", (ev: TouchEvent) => {
104
+
105
+
106
+ let { x, y, anchorX, anchorY, scale } = this.zoom;
107
+
108
+ if (ev.touches.length === 2) {
109
+
110
+ ev.preventDefault();
111
+ ev.stopImmediatePropagation();
112
+
113
+ const rect = this.element.getBoundingClientRect();
114
+ const first = ev.touches[0];
115
+ const second = ev.touches[1];
116
+ anchorX = ((first.clientX + second.clientX) / 2) - rect.left;
117
+ anchorY = ((first.clientY + second.clientY) / 2) - rect.top;
118
+ const newScale = distance(first, second);
119
+ if (previousDistance === void 0) {
120
+ previousDistance = newScale;
121
+ return;
122
+ }
123
+ if (previousDistance === newScale) {
124
+ return;
125
+ }
126
+
127
+ scale += newScale - previousDistance;
128
+ previousDistance = newScale;
129
+ this.updateZoom({
130
+ anchorX,
131
+ anchorY,
132
+ x,
133
+ y,
134
+ scale
135
+ });
136
+
137
+ return;
138
+ }
139
+
140
+ });
141
+
142
+ touchEndDisposable ??= this.bindEvent(scrollView, "touchend", (ev: TouchEvent) => {
143
+ // ev.preventDefault();
144
+ // ev.stopImmediatePropagation?.();
145
+
146
+ touchMoveDisposable?.dispose();
147
+ touchEndDisposable?.dispose();
148
+ touchMoveDisposable = undefined;
149
+ touchEndDisposable = undefined;
150
+ previousDistance = undefined;
151
+ });
152
+ });
153
+
154
+ let mouseMoveDisposable;
155
+ let mouseUpDisposable;
156
+
157
+ this.bindEvent(scrollView, "dragstart", (ev: DragEvent) => {
158
+ ev.preventDefault();
159
+ ev.stopImmediatePropagation();
160
+ });
161
+
162
+ this.bindEvent(scrollView, "mousedown", (ev: MouseEvent) => {
163
+ this.element.dataset.state = "grabbing";
164
+ previous = {
165
+ x: ev.clientX,
166
+ y: ev.clientY
167
+ };
168
+
169
+ mouseMoveDisposable ??= this.bindEvent(scrollView, "mousemove", (e: MouseEvent) => {
170
+ e.preventDefault();
171
+ e.stopImmediatePropagation?.();
172
+ const cp = { x: e.clientX, y: e.clientY };
173
+ const diffX = previous.x - cp.x;
174
+ const diffY = previous.y - cp.y;
175
+ previous = cp;
176
+ this.scrollDiv.scrollBy(diffX, diffY);
177
+ });
178
+ mouseUpDisposable ??= this.bindEvent(scrollView, "mouseup", (e: MouseEvent) => {
179
+ e.preventDefault();
180
+ e.stopImmediatePropagation?.();
181
+
182
+ this.element.dataset.state = "";
183
+ previous = null;
184
+ mouseMoveDisposable.dispose();
185
+ mouseUpDisposable.dispose();
186
+ mouseMoveDisposable = undefined;
187
+ mouseUpDisposable = undefined;
188
+ });
189
+
190
+ });
191
+
192
+ this.bindEvent(scrollView, "wheel", (ev: WheelEvent) => {
193
+
194
+ ev.preventDefault();
195
+ ev.stopImmediatePropagation?.();
196
+
197
+ const newScale = this.zoom.scale - (ev.deltaY < 0 ? -50 : 50);
198
+
199
+ const anchorX = ev.offsetX;
200
+ const anchorY = ev.offsetY;
201
+ const { x, y } = this.zoom;
202
+ this.updateZoom({
203
+ anchorX,
204
+ anchorY,
205
+ x,
206
+ y,
207
+ scale: newScale < 0 ? 0 : newScale
208
+ });
209
+
210
+ }, undefined, {
211
+ passive: false
212
+ });
213
+ }
214
+
215
+ private getSource(text: string) {
216
+ if (text) {
217
+ this.loading = true;
218
+ }
219
+ return text;
220
+ }
221
+
222
+ private updateZoom(zoom: IZoom = {
223
+ x: 0,
224
+ y: 0,
225
+ anchorX: 0,
226
+ anchorY: 0,
227
+ scale: 0
228
+ }) {
229
+ const { anchorX, anchorY, x, y } = zoom;
230
+ let { scale } = zoom;
231
+ // console.log(zoom);
232
+ this.zoom = zoom;
233
+ const image = this.image;
234
+ if (!image.naturalHeight) {
235
+ return;
236
+ }
237
+ const maxHeight = this.element.clientWidth > this.element.clientHeight;
238
+ const s = maxHeight
239
+ ? this.element.clientWidth / image.naturalWidth
240
+ : this.element.clientHeight / image.naturalHeight ;
241
+
242
+ if (scale <= 0) {
243
+ scale = 0;
244
+ }
245
+
246
+ const { clientWidth, clientHeight } = this.element;
247
+
248
+
249
+ if (scale <= 0) {
250
+ this.element.scrollTo(0,0);
251
+ this.imageContainer.style.transform = ``;
252
+ return;
253
+ }
254
+
255
+ const scaleFactor = (clientWidth + scale) / clientWidth;
256
+
257
+ this.imageContainer.style.transformOrigin = `${anchorX}px ${anchorY}px`;
258
+ this.imageContainer.style.transform = `scale(${scaleFactor})`;
259
+
260
+
261
+ const e = this.imageContainer.getBoundingClientRect();
262
+ // console.log([e.left, e.top]);
263
+ const left = -e.left;
264
+ const top = -e.top;
265
+ this.imageContainer.style.transformOrigin = "0 0";
266
+ const de = this.imageContainer.getBoundingClientRect();
267
+ // console.log([de.left, de.top]);
268
+ this.scrollDiv.scrollTo({ left: left + de.left, top: top + de.top , behavior: "instant" });
269
+ }
270
+ }
@@ -1,4 +1,8 @@
1
- import styled from "@web-atoms/core/dist/style/styled";
1
+
2
+ import styled, { svgAsCssDataUrl } from "@web-atoms/core/dist/style/styled";
3
+
4
+ const svg = svgAsCssDataUrl(`<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><!--!Font Awesome Pro 6.6.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2024 Fonticons, Inc.--><path d="M502.6 54.6c12.5-12.5 12.5-32.8 0-45.3s-32.8-12.5-45.3 0L336 130.7 336 80c0-17.7-14.3-32-32-32s-32 14.3-32 32l0 128c0 17.7 14.3 32 32 32l128 0c17.7 0 32-14.3 32-32s-14.3-32-32-32l-50.7 0L502.6 54.6zM80 272c-17.7 0-32 14.3-32 32s14.3 32 32 32l50.7 0L9.4 457.4c-12.5 12.5-12.5 32.8 0 45.3s32.8 12.5 45.3 0L176 381.3l0 50.7c0 17.7 14.3 32 32 32s32-14.3 32-32l0-128c0-17.7-14.3-32-32-32L80 272z"/></svg>`);
5
+
2
6
 
3
7
  styled.css `
4
8
  position: relative;
@@ -18,6 +22,12 @@ import styled from "@web-atoms/core/dist/style/styled";
18
22
  background-color: white;
19
23
  padding: 4px;
20
24
  color: black;
25
+ width: 30px;
26
+ height: 30px;
27
+ background-image: ${svg};
28
+ background-position: 2px 2px;
29
+ background-size: 20px 20px;
30
+ background-repeat: no-repeat;
21
31
  }
22
32
 
23
33
  & > .image-container {
@@ -0,0 +1,73 @@
1
+
2
+ import styled, { svgAsCssDataUrl } from "@web-atoms/core/dist/style/styled";
3
+
4
+ const svg = svgAsCssDataUrl(`<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><!--!Font Awesome Pro 6.6.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2024 Fonticons, Inc.--><path d="M502.6 54.6c12.5-12.5 12.5-32.8 0-45.3s-32.8-12.5-45.3 0L336 130.7 336 80c0-17.7-14.3-32-32-32s-32 14.3-32 32l0 128c0 17.7 14.3 32 32 32l128 0c17.7 0 32-14.3 32-32s-14.3-32-32-32l-50.7 0L502.6 54.6zM80 272c-17.7 0-32 14.3-32 32s14.3 32 32 32l50.7 0L9.4 457.4c-12.5 12.5-12.5 32.8 0 45.3s32.8 12.5 45.3 0L176 381.3l0 50.7c0 17.7 14.3 32 32 32s32-14.3 32-32l0-128c0-17.7-14.3-32-32-32L80 272z"/></svg>`);
5
+
6
+
7
+ styled.css `
8
+ position: relative;
9
+ cursor: grab;
10
+ overflow: hidden;
11
+
12
+ &[data-state=grabbing] {
13
+ cursor: grabbing;
14
+ }
15
+
16
+ & > div {
17
+
18
+ position: absolute;
19
+ left: 0;
20
+ top: 0;
21
+ right: 0;
22
+ bottom: 0;
23
+ scrollbar-width: none;
24
+ overflow: auto;
25
+
26
+
27
+ & > .image-container {
28
+ position: absolute;
29
+ left: 0;
30
+ top: 0;
31
+ right: 0;
32
+ bottom: 0;
33
+ overflow: hidden;
34
+ object-fit: contain;
35
+ width: 100%;
36
+ height: 100%;
37
+ }
38
+
39
+ }
40
+
41
+ & > .scale {
42
+ position: absolute;
43
+ right: 10px;
44
+ top: 10px;
45
+ border: solid 2px darkgray;
46
+ border-radius: 4px;
47
+ background-color: white;
48
+ padding: 4px;
49
+ color: black;
50
+ width: 30px;
51
+ height: 30px;
52
+ background-image: ${svg};
53
+ background-position: 2px 2px;
54
+ background-size: 20px 20px;
55
+ background-repeat: no-repeat;
56
+ }
57
+
58
+ & > .spinner {
59
+ position: absolute;
60
+ left: 0;
61
+ top: 0;
62
+ right: 0;
63
+ bottom: 0;
64
+ display: inline-grid;
65
+ align-items: center;
66
+ justify-items: center;
67
+ z-index: 10;
68
+ }
69
+
70
+ & > .hide {
71
+ display: none;
72
+ }
73
+ `.installGlobal("div[data-zoom-view]");