@map-gesture-controls/core 0.1.7 → 0.1.9

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/dist/index.js CHANGED
@@ -1,15 +1,15 @@
1
- var w = Object.defineProperty;
2
- var b = (o, t, e) => t in o ? w(o, t, { enumerable: !0, configurable: !0, writable: !0, value: e }) : o[t] = e;
3
- var a = (o, t, e) => b(o, typeof t != "symbol" ? t + "" : t, e);
4
- import { FilesetResolver as E } from "@mediapipe/tasks-vision";
5
- const L = {
1
+ var O = Object.defineProperty;
2
+ var x = (i, t, e) => t in i ? O(i, t, { enumerable: !0, configurable: !0, writable: !0, value: e }) : i[t] = e;
3
+ var l = (i, t, e) => x(i, typeof t != "symbol" ? t + "" : t, e);
4
+ import { FilesetResolver as H } from "@mediapipe/tasks-vision";
5
+ const Y = {
6
6
  enabled: !0,
7
7
  mode: "corner",
8
8
  opacity: 0.85,
9
9
  position: "bottom-right",
10
10
  width: 320,
11
11
  height: 240
12
- }, H = {
12
+ }, Z = {
13
13
  actionDwellMs: 80,
14
14
  releaseGraceMs: 150,
15
15
  panDeadzonePx: 10,
@@ -18,7 +18,7 @@ const L = {
18
18
  minDetectionConfidence: 0.65,
19
19
  minTrackingConfidence: 0.65,
20
20
  minPresenceConfidence: 0.6
21
- }, h = {
21
+ }, r = {
22
22
  WRIST: 0,
23
23
  THUMB_TIP: 4,
24
24
  INDEX_TIP: 8,
@@ -29,75 +29,97 @@ const L = {
29
29
  RING_MCP: 13,
30
30
  PINKY_TIP: 20,
31
31
  PINKY_MCP: 17
32
- }, I = [
33
- h.INDEX_TIP,
34
- h.MIDDLE_TIP,
35
- h.RING_TIP,
36
- h.PINKY_TIP
37
- ], y = [
38
- h.INDEX_MCP,
39
- h.MIDDLE_MCP,
40
- h.RING_MCP,
41
- h.PINKY_MCP
42
- ], T = {
32
+ }, L = 0.25, z = 0.35, E = [
33
+ r.INDEX_TIP,
34
+ r.MIDDLE_TIP,
35
+ r.RING_TIP,
36
+ r.PINKY_TIP
37
+ ], _ = [
38
+ r.INDEX_MCP,
39
+ r.MIDDLE_MCP,
40
+ r.RING_MCP,
41
+ r.PINKY_MCP
42
+ ], D = {
43
43
  idle: "#888888",
44
44
  panning: "#00ccff",
45
45
  zooming: "#00ffcc",
46
+ rotating: "#ff9900",
46
47
  landmark: "rgba(255,255,255,0.6)",
47
48
  connection: "rgba(255,255,255,0.3)",
48
49
  fingertipGlow: "#4488ff"
49
- }, C = "https://cdn.jsdelivr.net/npm/@mediapipe/tasks-vision@0.10.14/wasm";
50
- function g(o, t) {
51
- const e = o.x - t.x, n = o.y - t.y;
50
+ }, G = "https://cdn.jsdelivr.net/npm/@mediapipe/tasks-vision@0.10.14/wasm";
51
+ function T(i, t) {
52
+ const e = i.x - t.x, n = i.y - t.y;
52
53
  return Math.sqrt(e * e + n * n);
53
54
  }
54
- function M(o) {
55
- const t = o[h.WRIST];
56
- for (let i = 0; i < I.length; i++) {
57
- const d = o[I[i]], c = o[y[i]];
58
- if (g(d, t) < g(c, t) * 0.9)
55
+ function N(i) {
56
+ const t = i[r.WRIST];
57
+ for (let s = 0; s < E.length; s++) {
58
+ const u = i[E[s]], c = i[_[s]];
59
+ if (T(u, t) < T(c, t) * 0.9)
59
60
  return !1;
60
61
  }
61
- const e = N(o);
62
+ const e = w(i);
62
63
  if (e === 0) return !1;
63
- const n = I.map((i) => o[i]);
64
- let s = 1 / 0;
65
- for (let i = 0; i < n.length - 1; i++) {
66
- const d = g(n[i], n[i + 1]);
67
- d < s && (s = d);
64
+ const n = E.map((s) => i[s]);
65
+ let o = 1 / 0;
66
+ for (let s = 0; s < n.length - 1; s++) {
67
+ const u = T(n[s], n[s + 1]);
68
+ u < o && (o = u);
68
69
  }
69
- return s >= e * 0.18;
70
+ return o >= e * 0.18;
70
71
  }
71
- function _(o) {
72
- const t = o[h.WRIST];
72
+ function R(i) {
73
+ const t = i[r.WRIST];
73
74
  let e = 0;
74
- for (let n = 0; n < I.length; n++) {
75
- const s = o[I[n]], l = o[y[n]];
76
- g(s, t) < g(l, t) * 1.1 && e++;
75
+ for (let n = 0; n < E.length; n++) {
76
+ const o = i[E[n]], a = i[_[n]];
77
+ T(o, t) < T(a, t) * 1.1 && e++;
77
78
  }
78
79
  return e >= 3;
79
80
  }
80
- function N(o) {
81
- const t = o[h.WRIST], e = o[h.MIDDLE_MCP];
82
- return !t || !e ? 0 : g(t, e);
81
+ function w(i) {
82
+ const t = i[r.WRIST], e = i[r.MIDDLE_MCP];
83
+ return !t || !e ? 0 : T(t, e);
83
84
  }
84
- function S(o, t) {
85
- const e = o[h.INDEX_TIP], n = t[h.INDEX_TIP];
86
- return !e || !n ? 0 : g(e, n);
85
+ function j(i, t) {
86
+ const e = i[r.INDEX_TIP], n = t[r.INDEX_TIP];
87
+ return !e || !n ? 0 : T(e, n);
87
88
  }
88
- function x(o) {
89
- return o.length < 21 ? "none" : _(o) ? "fist" : M(o) ? "openPalm" : "none";
89
+ function F(i) {
90
+ const t = w(i);
91
+ if (t === 0) return !1;
92
+ const e = i[r.THUMB_TIP], n = i[r.INDEX_TIP];
93
+ return T(e, n) < t * L;
90
94
  }
91
- class k {
95
+ function B(i) {
96
+ const t = w(i);
97
+ if (t === 0) return !1;
98
+ const e = i[r.THUMB_TIP], n = i[r.INDEX_TIP];
99
+ return T(e, n) < t * z;
100
+ }
101
+ function k(i) {
102
+ return i.length < 21 ? "none" : R(i) ? "fist" : F(i) ? "pinch" : N(i) ? "openPalm" : "none";
103
+ }
104
+ function C() {
105
+ let i = !1;
106
+ return function(e) {
107
+ return e.length < 21 ? (i = !1, "none") : R(e) ? (i = !1, "fist") : (i ? i = B(e) : i = F(e), i ? "pinch" : N(e) ? "openPalm" : "none");
108
+ };
109
+ }
110
+ class $ {
92
111
  constructor(t, e) {
93
- a(this, "landmarker", null);
94
- a(this, "videoEl", null);
95
- a(this, "stream", null);
96
- a(this, "rafHandle", null);
97
- a(this, "running", !1);
98
- a(this, "onFrame");
99
- a(this, "tuning");
100
- a(this, "lastVideoTime", -1);
112
+ l(this, "landmarker", null);
113
+ l(this, "videoEl", null);
114
+ l(this, "stream", null);
115
+ l(this, "rafHandle", null);
116
+ l(this, "running", !1);
117
+ l(this, "onFrame");
118
+ l(this, "tuning");
119
+ l(this, "lastVideoTime", -1);
120
+ // One stateful classifier per hand label; persists pinch hysteresis across frames.
121
+ l(this, "leftClassifier", C());
122
+ l(this, "rightClassifier", C());
101
123
  this.tuning = t, this.onFrame = e;
102
124
  }
103
125
  /**
@@ -105,7 +127,7 @@ class k {
105
127
  * Returns the video element so the overlay can render it.
106
128
  */
107
129
  async init() {
108
- const t = await E.forVisionTasks(C), { HandLandmarker: e } = await import("@mediapipe/tasks-vision");
130
+ const t = await H.forVisionTasks(G), { HandLandmarker: e } = await import("@mediapipe/tasks-vision");
109
131
  return this.landmarker = await e.createFromOptions(t, {
110
132
  baseOptions: {
111
133
  modelAssetPath: "https://storage.googleapis.com/mediapipe-models/hand_landmarker/hand_landmarker/float16/1/hand_landmarker.task",
@@ -141,30 +163,30 @@ class k {
141
163
  const n = performance.now();
142
164
  if (t.currentTime === this.lastVideoTime) return;
143
165
  this.lastVideoTime = t.currentTime;
144
- let s;
166
+ let o;
145
167
  try {
146
- s = e.detectForVideo(t, n);
168
+ o = e.detectForVideo(t, n);
147
169
  } catch {
148
170
  return;
149
171
  }
150
- const l = this.buildFrame(s, n);
151
- this.onFrame(l);
172
+ const a = this.buildFrame(o, n);
173
+ this.onFrame(a);
152
174
  }
153
175
  buildFrame(t, e) {
154
- const n = t.landmarks.map((i, d) => {
155
- var f, v;
156
- const c = t.handedness[d], u = ((f = c == null ? void 0 : c[0]) == null ? void 0 : f.categoryName) === "Left" ? "Left" : "Right", m = ((v = c == null ? void 0 : c[0]) == null ? void 0 : v.score) ?? 0, p = x(i);
157
- return { handedness: u, score: m, landmarks: i, gesture: p };
158
- }), s = n.find((i) => i.handedness === "Left") ?? null, l = n.find((i) => i.handedness === "Right") ?? null;
159
- return { timestamp: e, hands: n, leftHand: s, rightHand: l };
176
+ const n = t.landmarks.map((s, u) => {
177
+ var m, p;
178
+ const c = t.handedness[u], d = ((m = c == null ? void 0 : c[0]) == null ? void 0 : m.categoryName) === "Left" ? "Left" : "Right", P = ((p = c == null ? void 0 : c[0]) == null ? void 0 : p.score) ?? 0, f = (d === "Left" ? this.leftClassifier : this.rightClassifier)(s);
179
+ return { handedness: d, score: P, landmarks: s, gesture: f };
180
+ }), o = n.find((s) => s.handedness === "Left") ?? null, a = n.find((s) => s.handedness === "Right") ?? null;
181
+ return { timestamp: e, hands: n, leftHand: o, rightHand: a };
160
182
  }
161
183
  getVideoElement() {
162
184
  return this.videoEl;
163
185
  }
164
186
  }
165
- class R {
187
+ class U {
166
188
  constructor(t) {
167
- a(this, "value", null);
189
+ l(this, "value", null);
168
190
  this.alpha = t;
169
191
  }
170
192
  update(t, e) {
@@ -177,9 +199,9 @@ class R {
177
199
  this.value = null;
178
200
  }
179
201
  }
180
- class F {
202
+ class M {
181
203
  constructor(t) {
182
- a(this, "value", null);
204
+ l(this, "value", null);
183
205
  this.alpha = t;
184
206
  }
185
207
  update(t) {
@@ -189,65 +211,94 @@ class F {
189
211
  this.value = null;
190
212
  }
191
213
  }
192
- class B {
214
+ const y = class y {
193
215
  constructor(t) {
194
- a(this, "mode", "idle");
195
- a(this, "actionDwell", null);
196
- a(this, "releaseTimer", null);
197
- a(this, "panSmoother");
198
- a(this, "prevPanPos", null);
199
- a(this, "zoomSmoother");
200
- a(this, "prevZoomDist", null);
201
- this.tuning = t, this.panSmoother = new R(t.smoothingAlpha), this.zoomSmoother = new F(t.smoothingAlpha);
216
+ l(this, "mode", "idle");
217
+ l(this, "actionDwell", null);
218
+ l(this, "releaseTimer", null);
219
+ l(this, "panSmoother");
220
+ l(this, "prevPanPos", null);
221
+ l(this, "zoomSmoother");
222
+ l(this, "prevZoomDist", null);
223
+ l(this, "rotateSmoother");
224
+ l(this, "prevRotateAngle", null);
225
+ // Tracks how many consecutive frames each hand has been active.
226
+ // Used to require the secondary hand to be stable before escalating
227
+ // the mode (e.g. pan → rotate), preventing a single noisy frame from
228
+ // interrupting an ongoing single-hand gesture.
229
+ l(this, "leftActiveFrames", 0);
230
+ l(this, "rightActiveFrames", 0);
231
+ this.tuning = t, this.panSmoother = new U(t.smoothingAlpha), this.zoomSmoother = new M(t.smoothingAlpha), this.rotateSmoother = new M(t.smoothingAlpha);
202
232
  }
203
233
  getMode() {
204
234
  return this.mode;
205
235
  }
206
236
  update(t) {
207
- const e = t.timestamp, { actionDwellMs: n, releaseGraceMs: s } = this.tuning, { leftHand: l, rightHand: i } = t, d = l !== null && i !== null && l.gesture === "openPalm" && i.gesture === "openPalm", c = l !== null && l.gesture === "fist" && i === null || i !== null && i.gesture === "fist" && l === null, r = d ? "zooming" : c ? "panning" : "idle";
237
+ const e = t.timestamp, { actionDwellMs: n, releaseGraceMs: o } = this.tuning, { leftHand: a, rightHand: s } = t, u = (f) => f !== null && (f.gesture === "fist" || f.gesture === "pinch"), c = u(s), h = u(a);
238
+ this.leftActiveFrames = h ? this.leftActiveFrames + 1 : 0, this.rightActiveFrames = c ? this.rightActiveFrames + 1 : 0;
239
+ const d = this.leftActiveFrames >= y.ESCALATION_FRAMES && this.rightActiveFrames >= y.ESCALATION_FRAMES, g = d ? "rotating" : h && c && !d && (this.mode === "panning" || this.mode === "zooming") ? this.mode : c ? "zooming" : h ? "panning" : "idle";
208
240
  if (this.mode === "idle")
209
- return r !== "idle" ? this.actionDwell === null || this.actionDwell.gesture !== r ? this.actionDwell = { gesture: r, startMs: e } : e - this.actionDwell.startMs >= n && this.transitionTo(r) : this.actionDwell = null, this.buildOutput(null, null);
241
+ return g !== "idle" ? this.actionDwell === null || this.actionDwell.gesture !== g ? this.actionDwell = { gesture: g, startMs: e } : e - this.actionDwell.startMs >= n && this.transitionTo(g) : this.actionDwell = null, this.buildOutput(null, null, null);
210
242
  if (this.mode === "panning") {
211
- if (r !== "panning")
212
- return this.releaseTimer === null ? this.releaseTimer = e : e - this.releaseTimer >= s && this.transitionTo("idle"), this.buildOutput(null, null);
243
+ if (d)
244
+ return this.transitionTo("rotating"), this.buildOutput(null, null, null);
245
+ if (g !== "panning")
246
+ return this.releaseTimer === null ? this.releaseTimer = e : e - this.releaseTimer >= o && this.transitionTo("idle"), this.buildOutput(null, null, null);
213
247
  this.releaseTimer = null;
214
- const u = (l == null ? void 0 : l.gesture) === "fist" ? l : i;
215
- if (!u)
216
- return this.transitionTo("idle"), this.buildOutput(null, null);
217
- const m = u.landmarks[0], p = this.panSmoother.update(m.x, m.y);
218
- let f = null;
248
+ const f = u(a) ? a : null;
249
+ if (!f)
250
+ return this.transitionTo("idle"), this.buildOutput(null, null, null);
251
+ const m = f.landmarks[0], p = this.panSmoother.update(m.x, m.y);
252
+ let I = null;
219
253
  if (this.prevPanPos !== null) {
220
- const v = p.x - this.prevPanPos.x, P = p.y - this.prevPanPos.y, D = this.tuning.panDeadzonePx / 640;
221
- (Math.abs(v) > D || Math.abs(P) > D) && (f = { x: v, y: P });
254
+ const b = p.x - this.prevPanPos.x, v = p.y - this.prevPanPos.y, A = this.tuning.panDeadzonePx / 640;
255
+ (Math.abs(b) > A || Math.abs(v) > A) && (I = { x: b, y: v });
222
256
  }
223
- return this.prevPanPos = p, this.buildOutput(f, null);
257
+ return this.prevPanPos = p, this.buildOutput(I, null, null);
224
258
  }
225
259
  if (this.mode === "zooming") {
226
- if (r !== "zooming")
227
- return this.releaseTimer === null ? this.releaseTimer = e : e - this.releaseTimer >= s && this.transitionTo("idle"), this.buildOutput(null, null);
228
- if (this.releaseTimer = null, !l || !i)
229
- return this.transitionTo("idle"), this.buildOutput(null, null);
230
- const u = S(l.landmarks, i.landmarks), m = this.zoomSmoother.update(u);
260
+ if (d)
261
+ return this.transitionTo("rotating"), this.buildOutput(null, null, null);
262
+ if (g !== "zooming")
263
+ return this.releaseTimer === null ? this.releaseTimer = e : e - this.releaseTimer >= o && this.transitionTo("idle"), this.buildOutput(null, null, null);
264
+ if (this.releaseTimer = null, !s)
265
+ return this.transitionTo("idle"), this.buildOutput(null, null, null);
266
+ const f = s.landmarks[0], m = this.zoomSmoother.update(f.y);
231
267
  let p = null;
232
268
  if (this.prevZoomDist !== null) {
233
- const f = m - this.prevZoomDist;
234
- Math.abs(f) > this.tuning.zoomDeadzoneRatio && (p = f);
269
+ const I = m - this.prevZoomDist;
270
+ Math.abs(I) > this.tuning.zoomDeadzoneRatio && (p = -I);
235
271
  }
236
- return this.prevZoomDist = m, this.buildOutput(null, p);
272
+ return this.prevZoomDist = m, this.buildOutput(null, p, null);
237
273
  }
238
- return this.buildOutput(null, null);
274
+ if (this.mode === "rotating") {
275
+ if (g !== "rotating")
276
+ return this.releaseTimer === null ? this.releaseTimer = e : e - this.releaseTimer >= o && this.transitionTo("idle"), this.buildOutput(null, null, null);
277
+ if (this.releaseTimer = null, !a || !s)
278
+ return this.transitionTo("idle"), this.buildOutput(null, null, null);
279
+ const f = a.landmarks[0], m = s.landmarks[0], p = Math.atan2(m.y - f.y, m.x - f.x), I = this.rotateSmoother.update(p);
280
+ let b = null;
281
+ if (this.prevRotateAngle !== null) {
282
+ let v = I - this.prevRotateAngle;
283
+ v > Math.PI && (v -= 2 * Math.PI), v < -Math.PI && (v += 2 * Math.PI), Math.abs(v) > 5e-3 && (b = v);
284
+ }
285
+ return this.prevRotateAngle = I, this.buildOutput(null, null, b);
286
+ }
287
+ return this.buildOutput(null, null, null);
239
288
  }
240
289
  transitionTo(t) {
241
- this.mode = t, this.releaseTimer = null, this.actionDwell = null, t !== "panning" && (this.panSmoother.reset(), this.prevPanPos = null), t !== "zooming" && (this.zoomSmoother.reset(), this.prevZoomDist = null);
290
+ this.mode = t, this.releaseTimer = null, this.actionDwell = null, t !== "panning" && t !== "rotating" && (this.leftActiveFrames = 0), t !== "zooming" && t !== "rotating" && (this.rightActiveFrames = 0), t !== "panning" && (this.panSmoother.reset(), this.prevPanPos = null), t !== "zooming" && (this.zoomSmoother.reset(), this.prevZoomDist = null), t !== "rotating" && (this.rotateSmoother.reset(), this.prevRotateAngle = null);
242
291
  }
243
- buildOutput(t, e) {
244
- return { mode: this.mode, panDelta: t, zoomDelta: e };
292
+ buildOutput(t, e, n) {
293
+ return { mode: this.mode, panDelta: t, zoomDelta: e, rotateDelta: n };
245
294
  }
246
295
  reset() {
247
296
  this.transitionTo("idle");
248
297
  }
249
- }
250
- const O = [
298
+ };
299
+ l(y, "ESCALATION_FRAMES", 3);
300
+ let S = y;
301
+ const X = [
251
302
  [0, 1],
252
303
  [1, 2],
253
304
  [2, 3],
@@ -277,20 +328,20 @@ const O = [
277
328
  [9, 13],
278
329
  [13, 17]
279
330
  // palm cross
280
- ], z = [
281
- h.THUMB_TIP,
282
- h.INDEX_TIP,
283
- h.MIDDLE_TIP,
284
- h.RING_TIP,
285
- h.PINKY_TIP
331
+ ], V = [
332
+ r.THUMB_TIP,
333
+ r.INDEX_TIP,
334
+ r.MIDDLE_TIP,
335
+ r.RING_TIP,
336
+ r.PINKY_TIP
286
337
  ];
287
- class U {
338
+ class q {
288
339
  constructor(t) {
289
- a(this, "container");
290
- a(this, "canvas");
291
- a(this, "ctx");
292
- a(this, "badge");
293
- a(this, "config");
340
+ l(this, "container");
341
+ l(this, "canvas");
342
+ l(this, "ctx");
343
+ l(this, "badge");
344
+ l(this, "config");
294
345
  this.config = t, this.container = document.createElement("div"), this.container.className = "ol-gesture-overlay", this.applyContainerStyles(), this.canvas = document.createElement("canvas"), this.canvas.className = "ol-gesture-canvas", this.canvas.style.cssText = "position:absolute;top:0;left:0;width:100%;height:100%;pointer-events:none;", this.badge = document.createElement("div"), this.badge.className = "ol-gesture-badge ol-gesture-badge--idle", this.badge.textContent = "Idle", this.container.appendChild(this.canvas), this.container.appendChild(this.badge);
295
346
  const e = this.canvas.getContext("2d");
296
347
  if (!e) throw new Error("Cannot get 2D canvas context");
@@ -311,35 +362,36 @@ class U {
311
362
  /** Called each frame with the latest gesture frame and mode. */
312
363
  render(t, e) {
313
364
  this.updateBadge(e);
314
- const n = this.config.width, s = this.config.height;
315
- if (this.canvas.width = n, this.canvas.height = s, this.ctx.clearRect(0, 0, n, s), t !== null)
316
- for (const l of t.hands)
317
- this.drawSkeleton(l.landmarks, e, l.gesture === "fist");
365
+ const n = this.config.width, o = this.config.height;
366
+ if (this.canvas.width = n, this.canvas.height = o, this.ctx.clearRect(0, 0, n, o), t !== null)
367
+ for (const a of t.hands)
368
+ this.drawSkeleton(a.landmarks, e, a.gesture === "fist");
318
369
  }
319
370
  drawSkeleton(t, e, n) {
320
- const { ctx: s } = this, l = this.config.width, i = this.config.height, d = (r) => (1 - r.x) * l, c = (r) => r.y * i;
321
- s.strokeStyle = T.connection, s.lineWidth = 1.5;
322
- for (const [r, u] of O)
323
- !t[r] || !t[u] || (s.beginPath(), s.moveTo(d(t[r]), c(t[r])), s.lineTo(d(t[u]), c(t[u])), s.stroke());
324
- for (let r = 0; r < t.length; r++) {
325
- const u = t[r], m = z.includes(r), p = e !== "idle" && m ? T.fingertipGlow : T.landmark;
326
- s.beginPath(), s.arc(d(u), c(u), m ? 5 : 3, 0, Math.PI * 2), s.fillStyle = p, s.fill(), e !== "idle" && m && (s.shadowBlur = n ? 12 : 6, s.shadowColor = T.fingertipGlow, s.fill(), s.shadowBlur = 0);
371
+ const { ctx: o } = this, a = this.config.width, s = this.config.height, u = (h) => (1 - h.x) * a, c = (h) => h.y * s;
372
+ o.strokeStyle = D.connection, o.lineWidth = 1.5;
373
+ for (const [h, d] of X)
374
+ !t[h] || !t[d] || (o.beginPath(), o.moveTo(u(t[h]), c(t[h])), o.lineTo(u(t[d]), c(t[d])), o.stroke());
375
+ for (let h = 0; h < t.length; h++) {
376
+ const d = t[h], P = V.includes(h), g = e !== "idle" && P ? D.fingertipGlow : D.landmark;
377
+ o.beginPath(), o.arc(u(d), c(d), P ? 5 : 3, 0, Math.PI * 2), o.fillStyle = g, o.fill(), e !== "idle" && P && (o.shadowBlur = n ? 12 : 6, o.shadowColor = D.fingertipGlow, o.fill(), o.shadowBlur = 0);
327
378
  }
328
379
  }
329
380
  updateBadge(t) {
330
381
  const e = {
331
382
  idle: "Idle",
332
383
  panning: "Pan",
333
- zooming: "Zoom"
384
+ zooming: "Zoom",
385
+ rotating: "Rotate"
334
386
  };
335
387
  this.badge.textContent = e[t], this.badge.className = `ol-gesture-badge ol-gesture-badge--${t}`;
336
388
  }
337
389
  applyContainerStyles() {
338
- const { mode: t, position: e, width: n, height: s, opacity: l } = this.config;
339
- if (this.container.style.cssText = "", this.container.style.position = "fixed", this.container.style.zIndex = "9999", this.container.style.overflow = "hidden", this.container.style.borderRadius = "8px", this.container.style.opacity = String(l), this.container.style.display = t === "hidden" ? "none" : "block", t === "corner") {
340
- this.container.style.width = `${n}px`, this.container.style.height = `${s}px`;
341
- const i = "16px";
342
- e === "bottom-right" ? (this.container.style.bottom = i, this.container.style.right = i) : e === "bottom-left" ? (this.container.style.bottom = i, this.container.style.left = i) : e === "top-right" ? (this.container.style.top = i, this.container.style.right = i) : (this.container.style.top = i, this.container.style.left = i);
390
+ const { mode: t, position: e, width: n, height: o, opacity: a } = this.config;
391
+ if (this.container.style.cssText = "", this.container.style.position = "fixed", this.container.style.zIndex = "9999", this.container.style.overflow = "hidden", this.container.style.borderRadius = "8px", this.container.style.opacity = String(a), this.container.style.display = t === "hidden" ? "none" : "block", t === "corner") {
392
+ this.container.style.width = `${n}px`, this.container.style.height = `${o}px`;
393
+ const s = "16px";
394
+ e === "bottom-right" ? (this.container.style.bottom = s, this.container.style.right = s) : e === "bottom-left" ? (this.container.style.bottom = s, this.container.style.left = s) : e === "top-right" ? (this.container.style.top = s, this.container.style.right = s) : (this.container.style.top = s, this.container.style.left = s);
343
395
  } else t === "full" && (this.container.style.top = "0", this.container.style.left = "0", this.container.style.width = "100vw", this.container.style.height = "100vh", this.container.style.borderRadius = "0");
344
396
  }
345
397
  updateConfig(t) {
@@ -347,14 +399,15 @@ class U {
347
399
  }
348
400
  }
349
401
  export {
350
- T as COLORS,
351
- H as DEFAULT_TUNING_CONFIG,
352
- L as DEFAULT_WEBCAM_CONFIG,
353
- k as GestureController,
354
- B as GestureStateMachine,
355
- h as LANDMARKS,
356
- U as WebcamOverlay,
357
- x as classifyGesture,
358
- N as getHandSize,
359
- S as getTwoHandDistance
402
+ D as COLORS,
403
+ Z as DEFAULT_TUNING_CONFIG,
404
+ Y as DEFAULT_WEBCAM_CONFIG,
405
+ $ as GestureController,
406
+ S as GestureStateMachine,
407
+ r as LANDMARKS,
408
+ q as WebcamOverlay,
409
+ k as classifyGesture,
410
+ C as createHandClassifier,
411
+ w as getHandSize,
412
+ j as getTwoHandDistance
360
413
  };
package/dist/types.d.ts CHANGED
@@ -1,6 +1,6 @@
1
- export type GestureMode = 'idle' | 'panning' | 'zooming';
1
+ export type GestureMode = 'idle' | 'panning' | 'zooming' | 'rotating';
2
2
  export type HandednessLabel = 'Left' | 'Right';
3
- export type GestureType = 'openPalm' | 'fist' | 'none';
3
+ export type GestureType = 'openPalm' | 'fist' | 'pinch' | 'none';
4
4
  export interface Point2D {
5
5
  x: number;
6
6
  y: number;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@map-gesture-controls/core",
3
- "version": "0.1.7",
3
+ "version": "0.1.9",
4
4
  "description": "Map-agnostic hand gesture detection and state machine (MediaPipe)",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",