marquee-selection 0.0.11
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/README.html +442 -0
- package/README.md +204 -0
- package/dist/index.d.ts +215 -0
- package/dist/infinite-canvas.es.js +475 -0
- package/dist/infinite-canvas.es.js.map +1 -0
- package/dist/infinite-canvas.umd.js +2 -0
- package/dist/infinite-canvas.umd.js.map +1 -0
- package/dist/marquee-selection.es.js +672 -0
- package/dist/marquee-selection.es.js.map +1 -0
- package/dist/marquee-selection.umd.js +2 -0
- package/dist/marquee-selection.umd.js.map +1 -0
- package/index.html +740 -0
- package/package.json +49 -0
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* InfiniteCanvas
|
|
3
|
+
*
|
|
4
|
+
* 在指定 HTML 容器内启用双指缩放与双指平移(触摸手势)。
|
|
5
|
+
* 会将容器现有子节点搬运到一个内部 content 容器中,并对该 content 施加 CSS 变换。
|
|
6
|
+
*/
|
|
7
|
+
type InfiniteCanvasOptions = {
|
|
8
|
+
container: HTMLElement;
|
|
9
|
+
initialScale?: number;
|
|
10
|
+
minScale?: number;
|
|
11
|
+
maxScale?: number;
|
|
12
|
+
initialTranslate?: {
|
|
13
|
+
x: number;
|
|
14
|
+
y: number;
|
|
15
|
+
};
|
|
16
|
+
clampOverflowHidden?: boolean;
|
|
17
|
+
contentClassName?: string;
|
|
18
|
+
autoFit?: boolean | {
|
|
19
|
+
mode?: "contain" | "width" | "height" | "cover";
|
|
20
|
+
alignX?: "left" | "center" | "right";
|
|
21
|
+
alignY?: "top" | "center" | "bottom";
|
|
22
|
+
padding?: number;
|
|
23
|
+
};
|
|
24
|
+
autoRefitOnResize?: boolean;
|
|
25
|
+
onReady?: (content: HTMLDivElement) => void;
|
|
26
|
+
onTransform?: (state: {
|
|
27
|
+
scale: number;
|
|
28
|
+
x: number;
|
|
29
|
+
y: number;
|
|
30
|
+
content: HTMLDivElement;
|
|
31
|
+
}) => void;
|
|
32
|
+
onTransformStart?: (state: {
|
|
33
|
+
scale: number;
|
|
34
|
+
x: number;
|
|
35
|
+
y: number;
|
|
36
|
+
content: HTMLDivElement;
|
|
37
|
+
}) => void;
|
|
38
|
+
onTransformEnd?: (state: {
|
|
39
|
+
scale: number;
|
|
40
|
+
x: number;
|
|
41
|
+
y: number;
|
|
42
|
+
content: HTMLDivElement;
|
|
43
|
+
}) => void;
|
|
44
|
+
};
|
|
45
|
+
declare class InfiniteCanvas {
|
|
46
|
+
private container;
|
|
47
|
+
private content;
|
|
48
|
+
private scale;
|
|
49
|
+
private tx;
|
|
50
|
+
private ty;
|
|
51
|
+
private minScale;
|
|
52
|
+
private maxScale;
|
|
53
|
+
private destroyed;
|
|
54
|
+
private onTransformCb?;
|
|
55
|
+
private onTransformStartCb?;
|
|
56
|
+
private onTransformEndCb?;
|
|
57
|
+
private transforming;
|
|
58
|
+
private transformEndTimer;
|
|
59
|
+
private refitTimer;
|
|
60
|
+
private autoFitPreset;
|
|
61
|
+
private resizeObserver?;
|
|
62
|
+
private resizeHandler?;
|
|
63
|
+
private hitLayer;
|
|
64
|
+
private gesture;
|
|
65
|
+
private inContainerGesture;
|
|
66
|
+
private onWindowTouchStart?;
|
|
67
|
+
private onWindowTouchMove?;
|
|
68
|
+
private onWindowTouchEnd?;
|
|
69
|
+
private onWindowWheel?;
|
|
70
|
+
constructor(options: InfiniteCanvasOptions);
|
|
71
|
+
destroy(): void;
|
|
72
|
+
getTransform(): {
|
|
73
|
+
scale: number;
|
|
74
|
+
x: number;
|
|
75
|
+
y: number;
|
|
76
|
+
};
|
|
77
|
+
setTransform(t: {
|
|
78
|
+
scale?: number;
|
|
79
|
+
x?: number;
|
|
80
|
+
y?: number;
|
|
81
|
+
}): void;
|
|
82
|
+
reset(): void;
|
|
83
|
+
private clampScale;
|
|
84
|
+
private normalizeFitParams;
|
|
85
|
+
computeFitTransform(params?: {
|
|
86
|
+
mode?: "contain" | "width" | "height" | "cover";
|
|
87
|
+
alignX?: "left" | "center" | "right";
|
|
88
|
+
alignY?: "top" | "center" | "bottom";
|
|
89
|
+
padding?: number;
|
|
90
|
+
}): {
|
|
91
|
+
scale: number;
|
|
92
|
+
x: number;
|
|
93
|
+
y: number;
|
|
94
|
+
} | null;
|
|
95
|
+
fitToView(params?: {
|
|
96
|
+
mode?: "contain" | "width" | "height" | "cover";
|
|
97
|
+
alignX?: "left" | "center" | "right";
|
|
98
|
+
alignY?: "top" | "center" | "bottom";
|
|
99
|
+
padding?: number;
|
|
100
|
+
}): void;
|
|
101
|
+
private _noop;
|
|
102
|
+
private _isPointInContainer;
|
|
103
|
+
private _windowTouchStart;
|
|
104
|
+
private _windowTouchMove;
|
|
105
|
+
private _windowTouchEnd;
|
|
106
|
+
private _windowWheel;
|
|
107
|
+
private applyTransform;
|
|
108
|
+
private getTouches;
|
|
109
|
+
private midpoint;
|
|
110
|
+
private distance;
|
|
111
|
+
private onTouchStart;
|
|
112
|
+
private onTouchMove;
|
|
113
|
+
private onTouchEnd;
|
|
114
|
+
private onWheel;
|
|
115
|
+
private onGesture;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
type MarqueeSelectionOptions = {
|
|
119
|
+
container: HTMLElement;
|
|
120
|
+
selectable?: string;
|
|
121
|
+
exclude?: string | string[];
|
|
122
|
+
selectionMode?: "intersects" | "contains" | "center";
|
|
123
|
+
minOverlapRatio?: number;
|
|
124
|
+
overlapMetric?: "element" | "iou";
|
|
125
|
+
selectedClass?: string;
|
|
126
|
+
onChange?: (payload: {
|
|
127
|
+
type: "single";
|
|
128
|
+
selected: Element[];
|
|
129
|
+
} | {
|
|
130
|
+
type: "groups";
|
|
131
|
+
groups: Element[][];
|
|
132
|
+
flat: Element[];
|
|
133
|
+
}) => void;
|
|
134
|
+
preventAncestorSelection?: boolean;
|
|
135
|
+
conflictStrategy?: "none" | "leaf" | "best";
|
|
136
|
+
multi?: boolean;
|
|
137
|
+
combineMode?: "replace" | "add" | "subtract" | "toggle" | "auto";
|
|
138
|
+
groupMode?: boolean;
|
|
139
|
+
groupOverlayClass?: string;
|
|
140
|
+
groupColor?: string;
|
|
141
|
+
groupRandomColor?: boolean;
|
|
142
|
+
groupColorPalette?: string[];
|
|
143
|
+
hoverHighlight?: boolean;
|
|
144
|
+
hoverClass?: string;
|
|
145
|
+
onSelectionEnd?: (snapshot: MarqueeSelectionSnapshot) => void;
|
|
146
|
+
quickGroupOnDblClick?: boolean;
|
|
147
|
+
quickGroupSelector?: string;
|
|
148
|
+
allowIntersectionSelection?: boolean;
|
|
149
|
+
allowContainmentSelection?: boolean;
|
|
150
|
+
allowUnionSelection?: boolean;
|
|
151
|
+
toolbarButtons?: Array<{
|
|
152
|
+
label: string;
|
|
153
|
+
title?: string;
|
|
154
|
+
className?: string;
|
|
155
|
+
onClick?: (ctx: {
|
|
156
|
+
index: number;
|
|
157
|
+
group: Element[];
|
|
158
|
+
controller: MarqueeSelectionController;
|
|
159
|
+
getSnapshot: () => MarqueeSelectionSnapshot;
|
|
160
|
+
refresh: () => void;
|
|
161
|
+
mouseX?: number;
|
|
162
|
+
mouseY?: number;
|
|
163
|
+
anchorRect?: DOMRect;
|
|
164
|
+
anchorEl?: HTMLElement;
|
|
165
|
+
overlayEl?: HTMLElement;
|
|
166
|
+
}) => void;
|
|
167
|
+
}>;
|
|
168
|
+
};
|
|
169
|
+
type MarqueeSelectionSnapshot = {
|
|
170
|
+
type: "single";
|
|
171
|
+
selected: Element[];
|
|
172
|
+
} | {
|
|
173
|
+
type: "groups";
|
|
174
|
+
groups: Element[][];
|
|
175
|
+
flat: Element[];
|
|
176
|
+
hidden: boolean[];
|
|
177
|
+
groupRects: ({
|
|
178
|
+
left: number;
|
|
179
|
+
top: number;
|
|
180
|
+
width: number;
|
|
181
|
+
height: number;
|
|
182
|
+
} | null)[];
|
|
183
|
+
groupNesting: {
|
|
184
|
+
parents: (number | null)[];
|
|
185
|
+
children: number[][];
|
|
186
|
+
roots: number[];
|
|
187
|
+
};
|
|
188
|
+
};
|
|
189
|
+
type MarqueeSelectionController = {
|
|
190
|
+
destroy: () => void;
|
|
191
|
+
getGroups: () => Element[][];
|
|
192
|
+
clearGroups: () => void;
|
|
193
|
+
removeGroup: (index: number) => boolean;
|
|
194
|
+
addGroup: (elements: Element[]) => boolean;
|
|
195
|
+
setGroupVisibility: (index: number, hidden: boolean) => boolean;
|
|
196
|
+
toggleGroupVisibility: (index: number) => boolean;
|
|
197
|
+
getSelectionResult: () => MarqueeSelectionSnapshot;
|
|
198
|
+
refresh: () => void;
|
|
199
|
+
hideOverlays: () => void;
|
|
200
|
+
showAndRefreshOverlays: () => void;
|
|
201
|
+
onSelectionEnd: (handler: (snapshot: MarqueeSelectionSnapshot) => void) => () => void;
|
|
202
|
+
waitForSelectionEnd: () => Promise<MarqueeSelectionSnapshot>;
|
|
203
|
+
getGroupNesting: () => {
|
|
204
|
+
parents: (number | null)[];
|
|
205
|
+
children: number[][];
|
|
206
|
+
roots: number[];
|
|
207
|
+
} | null;
|
|
208
|
+
};
|
|
209
|
+
/**
|
|
210
|
+
* 启用容器内矩形拖拽圈选
|
|
211
|
+
*/
|
|
212
|
+
declare function marqueeSelection(options: MarqueeSelectionOptions): MarqueeSelectionController;
|
|
213
|
+
|
|
214
|
+
export { InfiniteCanvas, marqueeSelection };
|
|
215
|
+
export type { InfiniteCanvasOptions, MarqueeSelectionOptions };
|
|
@@ -0,0 +1,475 @@
|
|
|
1
|
+
class G {
|
|
2
|
+
constructor(t) {
|
|
3
|
+
this.scale = 1, this.tx = 0, this.ty = 0, this.destroyed = !1, this.transforming = !1, this.transformEndTimer = null, this.refitTimer = null, this.autoFitPreset = null, this.gesture = null, this.inContainerGesture = !1;
|
|
4
|
+
const {
|
|
5
|
+
container: e,
|
|
6
|
+
initialScale: i = 1,
|
|
7
|
+
minScale: n = 0.2,
|
|
8
|
+
maxScale: s = 6,
|
|
9
|
+
initialTranslate: r = { x: 0, y: 0 },
|
|
10
|
+
clampOverflowHidden: h = !0,
|
|
11
|
+
contentClassName: o,
|
|
12
|
+
autoFit: a,
|
|
13
|
+
autoRefitOnResize: f = !1,
|
|
14
|
+
onReady: y,
|
|
15
|
+
onTransform: p,
|
|
16
|
+
onTransformStart: w,
|
|
17
|
+
onTransformEnd: x
|
|
18
|
+
} = t;
|
|
19
|
+
if (!e) throw new Error("InfiniteCanvas: container is required");
|
|
20
|
+
this.container = e, this.scale = i, this.tx = r.x, this.ty = r.y, this.minScale = Math.min(n, s), this.maxScale = Math.max(n, s), this.onTransformCb = p, this.onTransformStartCb = w, this.onTransformEndCb = x;
|
|
21
|
+
const c = document.createElement("div");
|
|
22
|
+
c.style.position = "relative", c.style.transformOrigin = "0 0", c.style.willChange = "transform", o && (c.className = o);
|
|
23
|
+
const T = [];
|
|
24
|
+
for (; e.firstChild; )
|
|
25
|
+
T.push(e.firstChild), e.removeChild(e.firstChild);
|
|
26
|
+
if (T.forEach((d) => c.appendChild(d)), e.appendChild(c), this.content = c, e.style.touchAction = "none", e.style.overscrollBehavior = e.style.overscrollBehavior || "none", h && (e.style.overflow || (e.style.overflow = "hidden")), a) {
|
|
27
|
+
const d = this.normalizeFitParams(a), l = this.computeFitTransform(d);
|
|
28
|
+
l && (this.scale = this.clampScale(l.scale), this.tx = l.x, this.ty = l.y, this.autoFitPreset = d);
|
|
29
|
+
}
|
|
30
|
+
if (this.applyTransform(), this.autoFitPreset && f) {
|
|
31
|
+
const d = () => {
|
|
32
|
+
this.refitTimer && clearTimeout(this.refitTimer), this.refitTimer = setTimeout(() => {
|
|
33
|
+
this.fitToView(this.autoFitPreset || void 0);
|
|
34
|
+
}, 100);
|
|
35
|
+
};
|
|
36
|
+
try {
|
|
37
|
+
this.resizeObserver = new ResizeObserver(() => d()), this.resizeObserver.observe(this.container);
|
|
38
|
+
} catch {
|
|
39
|
+
}
|
|
40
|
+
this.resizeHandler = d, window.addEventListener("resize", this.resizeHandler);
|
|
41
|
+
}
|
|
42
|
+
this.onTouchStart = this.onTouchStart.bind(this), this.onTouchMove = this.onTouchMove.bind(this), this.onTouchEnd = this.onTouchEnd.bind(this), this.onWheel = this.onWheel.bind(this), this.onGesture = this.onGesture.bind(this), this.onWindowTouchStart = this._windowTouchStart.bind(this), this.onWindowTouchMove = this._windowTouchMove.bind(this), this.onWindowTouchEnd = this._windowTouchEnd.bind(this), this.onWindowWheel = this._windowWheel.bind(this);
|
|
43
|
+
try {
|
|
44
|
+
getComputedStyle(e).position === "static" && (e.style.position = "relative");
|
|
45
|
+
} catch {
|
|
46
|
+
}
|
|
47
|
+
if (window.addEventListener(
|
|
48
|
+
"touchstart",
|
|
49
|
+
this.onWindowTouchStart,
|
|
50
|
+
{
|
|
51
|
+
passive: !1,
|
|
52
|
+
capture: !0
|
|
53
|
+
}
|
|
54
|
+
), window.addEventListener(
|
|
55
|
+
"touchmove",
|
|
56
|
+
this.onWindowTouchMove,
|
|
57
|
+
{
|
|
58
|
+
passive: !1,
|
|
59
|
+
capture: !0
|
|
60
|
+
}
|
|
61
|
+
), window.addEventListener(
|
|
62
|
+
"touchend",
|
|
63
|
+
this.onWindowTouchEnd,
|
|
64
|
+
{
|
|
65
|
+
passive: !1,
|
|
66
|
+
capture: !0
|
|
67
|
+
}
|
|
68
|
+
), window.addEventListener(
|
|
69
|
+
"touchcancel",
|
|
70
|
+
this.onWindowTouchEnd,
|
|
71
|
+
{
|
|
72
|
+
passive: !1,
|
|
73
|
+
capture: !0
|
|
74
|
+
}
|
|
75
|
+
), window.addEventListener(
|
|
76
|
+
"wheel",
|
|
77
|
+
this.onWindowWheel,
|
|
78
|
+
{
|
|
79
|
+
passive: !1,
|
|
80
|
+
capture: !0
|
|
81
|
+
}
|
|
82
|
+
), window.addEventListener(
|
|
83
|
+
"gesturestart",
|
|
84
|
+
this.onGesture,
|
|
85
|
+
{ passive: !1, capture: !0 }
|
|
86
|
+
), window.addEventListener(
|
|
87
|
+
"gesturechange",
|
|
88
|
+
this.onGesture,
|
|
89
|
+
{ passive: !1, capture: !0 }
|
|
90
|
+
), window.addEventListener(
|
|
91
|
+
"gestureend",
|
|
92
|
+
this.onGesture,
|
|
93
|
+
{ passive: !1, capture: !0 }
|
|
94
|
+
), typeof y == "function")
|
|
95
|
+
try {
|
|
96
|
+
y(this.content);
|
|
97
|
+
} catch {
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
// 对外 API
|
|
101
|
+
destroy() {
|
|
102
|
+
if (!this.destroyed) {
|
|
103
|
+
if (this.destroyed = !0, this.container, window.removeEventListener(
|
|
104
|
+
"touchstart",
|
|
105
|
+
this.onWindowTouchStart,
|
|
106
|
+
!0
|
|
107
|
+
), window.removeEventListener(
|
|
108
|
+
"touchmove",
|
|
109
|
+
this.onWindowTouchMove,
|
|
110
|
+
!0
|
|
111
|
+
), window.removeEventListener(
|
|
112
|
+
"touchend",
|
|
113
|
+
this.onWindowTouchEnd,
|
|
114
|
+
!0
|
|
115
|
+
), window.removeEventListener(
|
|
116
|
+
"touchcancel",
|
|
117
|
+
this.onWindowTouchEnd,
|
|
118
|
+
!0
|
|
119
|
+
), window.removeEventListener(
|
|
120
|
+
"wheel",
|
|
121
|
+
this.onWindowWheel,
|
|
122
|
+
!0
|
|
123
|
+
), window.removeEventListener(
|
|
124
|
+
"gesturestart",
|
|
125
|
+
this.onGesture,
|
|
126
|
+
!0
|
|
127
|
+
), window.removeEventListener(
|
|
128
|
+
"gesturechange",
|
|
129
|
+
this.onGesture,
|
|
130
|
+
!0
|
|
131
|
+
), window.removeEventListener(
|
|
132
|
+
"gestureend",
|
|
133
|
+
this.onGesture,
|
|
134
|
+
!0
|
|
135
|
+
), this.resizeObserver) {
|
|
136
|
+
try {
|
|
137
|
+
this.resizeObserver.disconnect();
|
|
138
|
+
} catch {
|
|
139
|
+
}
|
|
140
|
+
this.resizeObserver = void 0;
|
|
141
|
+
}
|
|
142
|
+
this.resizeHandler && (window.removeEventListener("resize", this.resizeHandler), this.resizeHandler = void 0);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
getTransform() {
|
|
146
|
+
return { scale: this.scale, x: this.tx, y: this.ty };
|
|
147
|
+
}
|
|
148
|
+
setTransform(t) {
|
|
149
|
+
typeof t.scale == "number" && (this.scale = this.clampScale(t.scale)), typeof t.x == "number" && (this.tx = t.x), typeof t.y == "number" && (this.ty = t.y), this.applyTransform();
|
|
150
|
+
}
|
|
151
|
+
reset() {
|
|
152
|
+
this.scale = 1, this.tx = 0, this.ty = 0, this.applyTransform();
|
|
153
|
+
}
|
|
154
|
+
// 内部工具
|
|
155
|
+
clampScale(t) {
|
|
156
|
+
return Math.min(this.maxScale, Math.max(this.minScale, t));
|
|
157
|
+
}
|
|
158
|
+
normalizeFitParams(t) {
|
|
159
|
+
const e = typeof t == "object" ? t : {
|
|
160
|
+
mode: "contain",
|
|
161
|
+
alignX: "center",
|
|
162
|
+
alignY: "center",
|
|
163
|
+
padding: 0
|
|
164
|
+
};
|
|
165
|
+
return {
|
|
166
|
+
mode: e.mode || "contain",
|
|
167
|
+
alignX: e.alignX || "center",
|
|
168
|
+
alignY: e.alignY || "center",
|
|
169
|
+
padding: Math.max(0, e.padding ?? 0)
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
// 计算使内容按给定策略适配到容器可见区域的变换(不应用)
|
|
173
|
+
computeFitTransform(t) {
|
|
174
|
+
const e = this.container, i = this.content, n = Math.max(0, e.clientWidth), s = Math.max(0, e.clientHeight);
|
|
175
|
+
if (n === 0 || s === 0) return null;
|
|
176
|
+
const r = i.getBoundingClientRect(), h = this.scale || 1;
|
|
177
|
+
let o = Number.POSITIVE_INFINITY, a = Number.POSITIVE_INFINITY, f = Number.NEGATIVE_INFINITY, y = Number.NEGATIVE_INFINITY;
|
|
178
|
+
const p = Array.from(i.querySelectorAll("*"));
|
|
179
|
+
for (const g of p) {
|
|
180
|
+
const m = g.getBoundingClientRect();
|
|
181
|
+
if (m.width <= 0 || m.height <= 0) continue;
|
|
182
|
+
const W = (m.left - r.left) / h, I = (m.top - r.top) / h, Y = (m.right - r.left) / h, F = (m.bottom - r.top) / h;
|
|
183
|
+
W < o && (o = W), I < a && (a = I), Y > f && (f = Y), F > y && (y = F);
|
|
184
|
+
}
|
|
185
|
+
if (!isFinite(o) || !isFinite(a) || !isFinite(f) || !isFinite(y)) {
|
|
186
|
+
const g = Math.max(
|
|
187
|
+
i.scrollWidth,
|
|
188
|
+
i.offsetWidth,
|
|
189
|
+
i.clientWidth
|
|
190
|
+
), m = Math.max(
|
|
191
|
+
i.scrollHeight,
|
|
192
|
+
i.offsetHeight,
|
|
193
|
+
i.clientHeight
|
|
194
|
+
);
|
|
195
|
+
if (g === 0 || m === 0) return null;
|
|
196
|
+
o = 0, a = 0, f = g, y = m;
|
|
197
|
+
}
|
|
198
|
+
const w = Math.max(0, f - o), x = Math.max(0, y - a), c = (t == null ? void 0 : t.mode) ?? "contain", T = (t == null ? void 0 : t.alignX) ?? "center", d = (t == null ? void 0 : t.alignY) ?? "center", l = Math.max(0, (t == null ? void 0 : t.padding) ?? 0), L = Math.max(0, n - l * 2), X = Math.max(0, s - l * 2), v = L / w, E = X / x;
|
|
199
|
+
let u = 1;
|
|
200
|
+
c === "width" ? u = v : c === "height" ? u = E : c === "cover" ? u = Math.max(v, E) : u = Math.min(v, E), u = this.clampScale(u);
|
|
201
|
+
const S = w * u, M = x * u;
|
|
202
|
+
let b = l, C = l;
|
|
203
|
+
T === "center" ? b = Math.round((n - S) / 2) : T === "right" && (b = Math.round(n - S - l)), d === "center" ? C = Math.round((s - M) / 2) : d === "bottom" && (C = Math.round(s - M - l));
|
|
204
|
+
const _ = b - Math.round(o * u), z = C - Math.round(a * u);
|
|
205
|
+
return { scale: u, x: _, y: z };
|
|
206
|
+
}
|
|
207
|
+
// 应用适配(基于 computeFitTransform 计算结果)
|
|
208
|
+
fitToView(t) {
|
|
209
|
+
const e = this.normalizeFitParams(t || { mode: "contain" }), i = this.computeFitTransform(e);
|
|
210
|
+
i && (this.scale = this.clampScale(i.scale), this.tx = i.x, this.ty = i.y, this.autoFitPreset = e, this.applyTransform());
|
|
211
|
+
}
|
|
212
|
+
// 仅用于移除 window.resize 监听占位(便于彻底清理)
|
|
213
|
+
_noop() {
|
|
214
|
+
}
|
|
215
|
+
// 命中检测:点是否在容器可见区域内
|
|
216
|
+
_isPointInContainer(t, e) {
|
|
217
|
+
const i = this.container.getBoundingClientRect();
|
|
218
|
+
return t >= i.left && t <= i.right && e >= i.top && e <= i.bottom;
|
|
219
|
+
}
|
|
220
|
+
// 窗口级触摸/滚轮捕获:仅当命中容器时转发
|
|
221
|
+
_windowTouchStart(t) {
|
|
222
|
+
if (t.touches.length === 0) return;
|
|
223
|
+
let e = !1;
|
|
224
|
+
for (let i = 0; i < t.touches.length; i++) {
|
|
225
|
+
const n = t.touches.item ? t.touches.item(i) : t.touches[i];
|
|
226
|
+
if (n && this._isPointInContainer(n.clientX, n.clientY)) {
|
|
227
|
+
e = !0;
|
|
228
|
+
break;
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
e && this.onTouchStart(t);
|
|
232
|
+
}
|
|
233
|
+
_windowTouchMove(t) {
|
|
234
|
+
if (t.touches.length === 0) return;
|
|
235
|
+
let e = !1;
|
|
236
|
+
for (let i = 0; i < t.touches.length; i++) {
|
|
237
|
+
const n = t.touches.item ? t.touches.item(i) : t.touches[i];
|
|
238
|
+
if (n && this._isPointInContainer(n.clientX, n.clientY)) {
|
|
239
|
+
e = !0;
|
|
240
|
+
break;
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
e && this.onTouchMove(t);
|
|
244
|
+
}
|
|
245
|
+
_windowTouchEnd(t) {
|
|
246
|
+
const e = t.changedTouches;
|
|
247
|
+
if (e && e.length > 0) {
|
|
248
|
+
const i = e.item ? e.item(0) : e[0];
|
|
249
|
+
if (i && !this._isPointInContainer(i.clientX, i.clientY)) return;
|
|
250
|
+
}
|
|
251
|
+
this.onTouchEnd(t);
|
|
252
|
+
}
|
|
253
|
+
_windowWheel(t) {
|
|
254
|
+
this._isPointInContainer(t.clientX, t.clientY) && this.onWheel(t);
|
|
255
|
+
}
|
|
256
|
+
applyTransform() {
|
|
257
|
+
if (this.content.style.transform = `translate(${this.tx}px, ${this.ty}px) scale(${this.scale})`, this.onTransformCb)
|
|
258
|
+
try {
|
|
259
|
+
this.onTransformCb({
|
|
260
|
+
scale: this.scale,
|
|
261
|
+
x: this.tx,
|
|
262
|
+
y: this.ty,
|
|
263
|
+
content: this.content
|
|
264
|
+
});
|
|
265
|
+
} catch {
|
|
266
|
+
}
|
|
267
|
+
try {
|
|
268
|
+
const t = new CustomEvent("infiniteCanvas:transform", {
|
|
269
|
+
detail: {
|
|
270
|
+
scale: this.scale,
|
|
271
|
+
x: this.tx,
|
|
272
|
+
y: this.ty,
|
|
273
|
+
content: this.content
|
|
274
|
+
},
|
|
275
|
+
bubbles: !0
|
|
276
|
+
});
|
|
277
|
+
this.container.dispatchEvent(t);
|
|
278
|
+
} catch {
|
|
279
|
+
}
|
|
280
|
+
this.transforming && (this.transformEndTimer && clearTimeout(this.transformEndTimer), this.transformEndTimer = setTimeout(() => {
|
|
281
|
+
this.transforming = !1;
|
|
282
|
+
try {
|
|
283
|
+
const t = new CustomEvent("infiniteCanvas:transformEnd", {
|
|
284
|
+
detail: {
|
|
285
|
+
scale: this.scale,
|
|
286
|
+
x: this.tx,
|
|
287
|
+
y: this.ty,
|
|
288
|
+
content: this.content
|
|
289
|
+
},
|
|
290
|
+
bubbles: !0
|
|
291
|
+
});
|
|
292
|
+
this.container.dispatchEvent(t);
|
|
293
|
+
} catch {
|
|
294
|
+
}
|
|
295
|
+
if (this.onTransformEndCb)
|
|
296
|
+
try {
|
|
297
|
+
this.onTransformEndCb({
|
|
298
|
+
scale: this.scale,
|
|
299
|
+
x: this.tx,
|
|
300
|
+
y: this.ty,
|
|
301
|
+
content: this.content
|
|
302
|
+
});
|
|
303
|
+
} catch {
|
|
304
|
+
}
|
|
305
|
+
}, 120));
|
|
306
|
+
}
|
|
307
|
+
getTouches(t) {
|
|
308
|
+
const e = t.touches, i = [];
|
|
309
|
+
for (let n = 0; n < e.length; n++) {
|
|
310
|
+
const s = e.item ? e.item(n) : e[n];
|
|
311
|
+
s && i.push({ x: s.clientX, y: s.clientY });
|
|
312
|
+
}
|
|
313
|
+
return i;
|
|
314
|
+
}
|
|
315
|
+
midpoint(t, e) {
|
|
316
|
+
return { x: (t.x + e.x) / 2, y: (t.y + e.y) / 2 };
|
|
317
|
+
}
|
|
318
|
+
distance(t, e) {
|
|
319
|
+
const i = t.x - e.x, n = t.y - e.y;
|
|
320
|
+
return Math.hypot(i, n);
|
|
321
|
+
}
|
|
322
|
+
onTouchStart(t) {
|
|
323
|
+
if (t.touches.length >= 2) {
|
|
324
|
+
if (t.preventDefault(), this.inContainerGesture = !0, !this.transforming) {
|
|
325
|
+
this.transforming = !0;
|
|
326
|
+
try {
|
|
327
|
+
const a = new CustomEvent("infiniteCanvas:transformStart", {
|
|
328
|
+
detail: {
|
|
329
|
+
scale: this.scale,
|
|
330
|
+
x: this.tx,
|
|
331
|
+
y: this.ty,
|
|
332
|
+
content: this.content
|
|
333
|
+
},
|
|
334
|
+
bubbles: !0
|
|
335
|
+
});
|
|
336
|
+
this.container.dispatchEvent(a);
|
|
337
|
+
} catch {
|
|
338
|
+
}
|
|
339
|
+
if (this.onTransformStartCb)
|
|
340
|
+
try {
|
|
341
|
+
this.onTransformStartCb({
|
|
342
|
+
scale: this.scale,
|
|
343
|
+
x: this.tx,
|
|
344
|
+
y: this.ty,
|
|
345
|
+
content: this.content
|
|
346
|
+
});
|
|
347
|
+
} catch {
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
const e = this.getTouches(t);
|
|
351
|
+
if (e.length < 2) return;
|
|
352
|
+
const i = e[0], n = e[1], s = this.midpoint(i, n), r = this.distance(i, n), h = (s.x - this.tx) / this.scale, o = (s.y - this.ty) / this.scale;
|
|
353
|
+
this.gesture = {
|
|
354
|
+
startMid: s,
|
|
355
|
+
startDist: Math.max(1, r),
|
|
356
|
+
startScale: this.scale,
|
|
357
|
+
startTx: this.tx,
|
|
358
|
+
startTy: this.ty,
|
|
359
|
+
worldAtMid: { x: h, y: o }
|
|
360
|
+
};
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
onTouchMove(t) {
|
|
364
|
+
if (!this.gesture) {
|
|
365
|
+
t.touches.length >= 2 && this.onTouchStart(t);
|
|
366
|
+
return;
|
|
367
|
+
}
|
|
368
|
+
if (t.touches.length < 2) return;
|
|
369
|
+
t.preventDefault();
|
|
370
|
+
const e = this.getTouches(t);
|
|
371
|
+
if (e.length < 2) return;
|
|
372
|
+
const i = e[0], n = e[1], s = this.midpoint(i, n), r = this.distance(i, n), h = this.gesture, o = this.clampScale(h.startScale * (r / h.startDist)), a = s.x - o * h.worldAtMid.x, f = s.y - o * h.worldAtMid.y;
|
|
373
|
+
this.scale = o, this.tx = a, this.ty = f, this.applyTransform();
|
|
374
|
+
}
|
|
375
|
+
onTouchEnd(t) {
|
|
376
|
+
if (t.touches.length < 2 && (this.gesture = null, this.inContainerGesture = !1, this.transforming)) {
|
|
377
|
+
this.transforming = !1, this.transformEndTimer && (clearTimeout(this.transformEndTimer), this.transformEndTimer = null);
|
|
378
|
+
try {
|
|
379
|
+
const e = new CustomEvent("infiniteCanvas:transformEnd", {
|
|
380
|
+
detail: {
|
|
381
|
+
scale: this.scale,
|
|
382
|
+
x: this.tx,
|
|
383
|
+
y: this.ty,
|
|
384
|
+
content: this.content
|
|
385
|
+
},
|
|
386
|
+
bubbles: !0
|
|
387
|
+
});
|
|
388
|
+
this.container.dispatchEvent(e);
|
|
389
|
+
} catch {
|
|
390
|
+
}
|
|
391
|
+
if (this.onTransformEndCb)
|
|
392
|
+
try {
|
|
393
|
+
this.onTransformEndCb({
|
|
394
|
+
scale: this.scale,
|
|
395
|
+
x: this.tx,
|
|
396
|
+
y: this.ty,
|
|
397
|
+
content: this.content
|
|
398
|
+
});
|
|
399
|
+
} catch {
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
// 轨迹板/鼠标:两指滚动 => 平移;Ctrl/⌘ + 滚轮 => 缩放(Chrome/Edge)
|
|
404
|
+
onWheel(t) {
|
|
405
|
+
if (this.container.contains(t.target)) {
|
|
406
|
+
if (t.ctrlKey) {
|
|
407
|
+
if (t.preventDefault(), !this.transforming) {
|
|
408
|
+
this.transforming = !0;
|
|
409
|
+
try {
|
|
410
|
+
const r = new CustomEvent("infiniteCanvas:transformStart", {
|
|
411
|
+
detail: {
|
|
412
|
+
scale: this.scale,
|
|
413
|
+
x: this.tx,
|
|
414
|
+
y: this.ty,
|
|
415
|
+
content: this.content
|
|
416
|
+
},
|
|
417
|
+
bubbles: !0
|
|
418
|
+
});
|
|
419
|
+
this.container.dispatchEvent(r);
|
|
420
|
+
} catch {
|
|
421
|
+
}
|
|
422
|
+
if (this.onTransformStartCb)
|
|
423
|
+
try {
|
|
424
|
+
this.onTransformStartCb({
|
|
425
|
+
scale: this.scale,
|
|
426
|
+
x: this.tx,
|
|
427
|
+
y: this.ty,
|
|
428
|
+
content: this.content
|
|
429
|
+
});
|
|
430
|
+
} catch {
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
const e = (t.clientX - this.tx) / this.scale, i = (t.clientY - this.ty) / this.scale, n = Math.exp(-t.deltaY * 2e-3), s = this.clampScale(this.scale * n);
|
|
434
|
+
this.tx = t.clientX - s * e, this.ty = t.clientY - s * i, this.scale = s, this.applyTransform();
|
|
435
|
+
return;
|
|
436
|
+
}
|
|
437
|
+
if (t.preventDefault(), !this.transforming) {
|
|
438
|
+
this.transforming = !0;
|
|
439
|
+
try {
|
|
440
|
+
const e = new CustomEvent("infiniteCanvas:transformStart", {
|
|
441
|
+
detail: {
|
|
442
|
+
scale: this.scale,
|
|
443
|
+
x: this.tx,
|
|
444
|
+
y: this.ty,
|
|
445
|
+
content: this.content
|
|
446
|
+
},
|
|
447
|
+
bubbles: !0
|
|
448
|
+
});
|
|
449
|
+
this.container.dispatchEvent(e);
|
|
450
|
+
} catch {
|
|
451
|
+
}
|
|
452
|
+
if (this.onTransformStartCb)
|
|
453
|
+
try {
|
|
454
|
+
this.onTransformStartCb({
|
|
455
|
+
scale: this.scale,
|
|
456
|
+
x: this.tx,
|
|
457
|
+
y: this.ty,
|
|
458
|
+
content: this.content
|
|
459
|
+
});
|
|
460
|
+
} catch {
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
this.tx -= t.deltaX, this.ty -= t.deltaY, this.applyTransform();
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
// iOS Safari 的非标准手势事件:阻止页面级缩放
|
|
467
|
+
onGesture(t) {
|
|
468
|
+
const e = t.target;
|
|
469
|
+
(!!(e && this.container.contains(e)) || this.inContainerGesture) && typeof t.preventDefault == "function" && t.preventDefault();
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
export {
|
|
473
|
+
G as InfiniteCanvas
|
|
474
|
+
};
|
|
475
|
+
//# sourceMappingURL=infinite-canvas.es.js.map
|