flipfolio 0.1.0 → 0.3.0

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/vue.cjs CHANGED
@@ -1,22 +1,7 @@
1
- "use strict";
2
1
  var __defProp = Object.defineProperty;
3
2
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
3
  var __getOwnPropNames = Object.getOwnPropertyNames;
5
- var __getOwnPropSymbols = Object.getOwnPropertySymbols;
6
4
  var __hasOwnProp = Object.prototype.hasOwnProperty;
7
- var __propIsEnum = Object.prototype.propertyIsEnumerable;
8
- var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
9
- var __spreadValues = (a, b) => {
10
- for (var prop in b || (b = {}))
11
- if (__hasOwnProp.call(b, prop))
12
- __defNormalProp(a, prop, b[prop]);
13
- if (__getOwnPropSymbols)
14
- for (var prop of __getOwnPropSymbols(b)) {
15
- if (__propIsEnum.call(b, prop))
16
- __defNormalProp(a, prop, b[prop]);
17
- }
18
- return a;
19
- };
20
5
  var __export = (target, all) => {
21
6
  for (var name in all)
22
7
  __defProp(target, name, { get: all[name], enumerable: true });
@@ -53,19 +38,27 @@ var DEFAULTS = {
53
38
  // 'auto' | 'off' | 'force'
54
39
  peek: "hover",
55
40
  // 'hover' | 'always' | 'off'
41
+ drag: "fling",
42
+ // 'fling' | 'off' - grab the active folder and throw it (stack mode)
56
43
  defaultActiveIndex: 0,
57
44
  label: "Folder gallery"
58
45
  };
59
46
  var PEEK_MODES = /* @__PURE__ */ new Set(["hover", "always", "off"]);
47
+ var DRAG_MODES = /* @__PURE__ */ new Set(["fling", "off"]);
48
+ var INSTANCES = /* @__PURE__ */ new WeakMap();
60
49
  var MODE_WIDTHS = { stack: 480, grid: 720, carousel: 720 };
61
50
  var SCROLL_THRESHOLD = 30;
62
51
  var SCROLL_COOLDOWN = { stack: 450, carousel: 300 };
63
52
  function hexToRgb(hex) {
64
- const h2 = String(hex).replace("#", "");
53
+ let h2 = String(hex).trim().replace(/^#/, "");
54
+ if (h2.length === 3) h2 = h2.replace(/./g, (c) => c + c);
55
+ if (!/^[0-9a-f]{6}$/i.test(h2)) return null;
65
56
  return [parseInt(h2.slice(0, 2), 16), parseInt(h2.slice(2, 4), 16), parseInt(h2.slice(4, 6), 16)];
66
57
  }
67
58
  function folderColors(hex) {
68
- const [r, g, b] = hexToRgb(hex);
59
+ const rgb = hexToRgb(hex);
60
+ if (!rgb) return null;
61
+ const [r, g, b] = rgb;
69
62
  const dn = (v) => Math.max(v - 30, 0);
70
63
  const up = (v, o) => Math.min(v + o, 255);
71
64
  const fr = up(r, 35);
@@ -79,6 +72,34 @@ function folderColors(hex) {
79
72
  label: yiq >= 128 ? "#1c1c1a" : "#ffffff"
80
73
  };
81
74
  }
75
+ function paintFolder(card, hex) {
76
+ const c = folderColors(hex);
77
+ if (!c) return;
78
+ card.style.setProperty("--fg-folder-bg", hex);
79
+ card.style.setProperty("--fg-front", c.front);
80
+ card.style.setProperty("--fg-front-solid", c.frontSolid);
81
+ card.style.setProperty("--fg-label-on-color", c.label);
82
+ const path = card.querySelector(".fg-folder path");
83
+ if (path) path.setAttribute("fill", c.back);
84
+ }
85
+ function paintGradient(card, gradient) {
86
+ const front = card.querySelector(".fg-front");
87
+ if (!front) return;
88
+ let layer = front.querySelector(".fg-gradient");
89
+ if (!gradient) {
90
+ if (layer) layer.remove();
91
+ card.classList.remove("fg-card--gradient");
92
+ return;
93
+ }
94
+ if (!layer) {
95
+ layer = document.createElement("div");
96
+ layer.className = "fg-gradient";
97
+ layer.setAttribute("aria-hidden", "true");
98
+ front.insertBefore(layer, front.firstChild);
99
+ }
100
+ layer.style.background = gradient;
101
+ card.classList.add("fg-card--gradient");
102
+ }
82
103
  function defaultContentRenderer(card, item) {
83
104
  const wrap = document.createElement("div");
84
105
  wrap.className = "fg-content";
@@ -98,12 +119,17 @@ function defaultContentRenderer(card, item) {
98
119
  card.appendChild(wrap);
99
120
  }
100
121
  function createFolderGallery(root, options = {}) {
101
- if (!root || !root.nodeType) throw new Error("createFolderGallery: a root element is required");
102
- const opts = __spreadValues(__spreadValues({}, DEFAULTS), options);
122
+ if (!root || root.nodeType !== 1) throw new Error("createFolderGallery: a root element is required");
123
+ const prior = INSTANCES.get(root);
124
+ if (prior) prior();
125
+ const provided = {};
126
+ for (const k in options) if (options[k] !== void 0) provided[k] = options[k];
127
+ const opts = { ...DEFAULTS, ...provided };
103
128
  const items = Array.isArray(opts.items) ? opts.items : [];
104
129
  const n = items.length;
105
130
  const renderContent = typeof opts.contentRenderer === "function" ? opts.contentRenderer : defaultContentRenderer;
106
- const reduced = opts.reducedMotion === "force" || opts.reducedMotion !== "off" && typeof matchMedia === "function" && matchMedia("(prefers-reduced-motion: reduce)").matches;
131
+ const reducedQuery = opts.reducedMotion === "auto" && typeof matchMedia === "function" ? matchMedia("(prefers-reduced-motion: reduce)") : null;
132
+ let reduced = opts.reducedMotion === "force" || (reducedQuery ? reducedQuery.matches : false);
107
133
  let mode = MODE_WIDTHS[opts.mode] ? opts.mode : "stack";
108
134
  let active = Math.min(Math.max(opts.defaultActiveIndex | 0, 0), Math.max(n - 1, 0));
109
135
  let scrollCooldown = false;
@@ -114,9 +140,29 @@ function createFolderGallery(root, options = {}) {
114
140
  cleanups.push(() => el.removeEventListener(ev, fn, o));
115
141
  };
116
142
  const emit = (name, detail) => root.dispatchEvent(new CustomEvent(name, { detail, bubbles: true }));
143
+ let destroyed = false;
144
+ const timers = /* @__PURE__ */ new Set();
145
+ const rafs = /* @__PURE__ */ new Set();
146
+ const later = (fn, ms) => {
147
+ const id = setTimeout(() => {
148
+ timers.delete(id);
149
+ fn();
150
+ }, ms);
151
+ timers.add(id);
152
+ return id;
153
+ };
154
+ const frame = (fn) => {
155
+ const id = requestAnimationFrame(() => {
156
+ rafs.delete(id);
157
+ fn();
158
+ });
159
+ rafs.add(id);
160
+ return id;
161
+ };
117
162
  root.classList.add("fg-root");
118
163
  root.setAttribute("data-fg-mode", mode);
119
164
  root.setAttribute("data-fg-peek", PEEK_MODES.has(opts.peek) ? opts.peek : "hover");
165
+ root.setAttribute("data-fg-drag", DRAG_MODES.has(opts.drag) ? opts.drag : "fling");
120
166
  const scene = document.createElement("div");
121
167
  scene.className = "fg-scene";
122
168
  scene.setAttribute("role", "listbox");
@@ -131,26 +177,48 @@ function createFolderGallery(root, options = {}) {
131
177
  live.setAttribute("aria-atomic", "true");
132
178
  root.append(scene, dotsEl, live);
133
179
  function teardown() {
180
+ if (destroyed) return;
181
+ destroyed = true;
182
+ timers.forEach(clearTimeout);
183
+ timers.clear();
184
+ rafs.forEach(cancelAnimationFrame);
185
+ rafs.clear();
134
186
  cleanups.forEach((fn) => fn());
135
187
  cleanups.length = 0;
136
188
  root.replaceChildren();
137
189
  root.classList.remove("fg-root");
138
190
  root.removeAttribute("data-fg-mode");
139
191
  root.removeAttribute("data-fg-peek");
192
+ root.removeAttribute("data-fg-drag");
193
+ INSTANCES.delete(root);
140
194
  }
141
195
  if (n === 0) {
142
- return { next() {
143
- }, prev() {
144
- }, goTo() {
145
- }, setMode() {
146
- }, setPeek() {
147
- }, getActiveIndex: () => -1, getMode: () => mode, destroy: teardown };
196
+ const noop = () => {
197
+ };
198
+ INSTANCES.set(root, teardown);
199
+ return {
200
+ next: noop,
201
+ prev: noop,
202
+ goTo: noop,
203
+ setMode: noop,
204
+ setPeek: noop,
205
+ setColor: noop,
206
+ setGradient: noop,
207
+ getActiveIndex: () => -1,
208
+ getMode: () => mode,
209
+ getPeek: () => root.getAttribute("data-fg-peek"),
210
+ getColor: () => void 0,
211
+ getGradient: () => void 0,
212
+ getItems: () => [],
213
+ destroy: teardown
214
+ };
148
215
  }
149
216
  function setCardTransform(card, { x, y, z, rx, ry, s, zIndex, opacity, isActive }) {
150
- card.style.transform = `translate3d(${x}px,${y}px,${z}px) rotateX(${rx}deg) rotateY(${ry}deg) scale(${s})`;
151
- card.style.transformOrigin = "center bottom";
217
+ if (!card.classList.contains("fg-card--flung")) {
218
+ card.style.transform = `translate3d(${x}px,${y}px,${z}px) rotateX(${rx}deg) rotateY(${ry}deg) scale(${s})`;
219
+ card.style.opacity = String(opacity);
220
+ }
152
221
  card.style.zIndex = String(zIndex);
153
- card.style.opacity = String(opacity);
154
222
  card.setAttribute("aria-selected", isActive ? "true" : "false");
155
223
  card.classList.toggle("is-active", isActive);
156
224
  }
@@ -160,13 +228,9 @@ function createFolderGallery(root, options = {}) {
160
228
  card.tabIndex = i === active ? 0 : -1;
161
229
  card.setAttribute("role", "option");
162
230
  card.setAttribute("aria-label", item.label || `Item ${i + 1}`);
163
- if (item.color) {
164
- const c = folderColors(item.color);
165
- card.style.setProperty("--fg-folder-bg", item.color);
166
- card.style.setProperty("--fg-front", c.front);
167
- card.style.setProperty("--fg-front-solid", c.frontSolid);
168
- card.style.setProperty("--fg-label-on-color", c.label);
169
- }
231
+ card.setAttribute("aria-setsize", String(n));
232
+ card.setAttribute("aria-posinset", String(i + 1));
233
+ card.setAttribute("data-fg-index", String(i));
170
234
  const svg = document.createElementNS(SVG_NS, "svg");
171
235
  svg.setAttribute("class", "fg-folder");
172
236
  svg.setAttribute("viewBox", "0 0 480 342");
@@ -174,7 +238,7 @@ function createFolderGallery(root, options = {}) {
174
238
  svg.setAttribute("aria-hidden", "true");
175
239
  const path = document.createElementNS(SVG_NS, "path");
176
240
  path.setAttribute("d", opts.folderPath);
177
- path.setAttribute("fill", item.color ? folderColors(item.color).back : "var(--fg-folder-bg)");
241
+ path.setAttribute("fill", "var(--fg-folder-bg)");
178
242
  svg.appendChild(path);
179
243
  card.appendChild(svg);
180
244
  renderContent(card, item, i);
@@ -197,6 +261,8 @@ function createFolderGallery(root, options = {}) {
197
261
  front.appendChild(label);
198
262
  }
199
263
  card.appendChild(front);
264
+ if (item.color) paintFolder(card, item.color);
265
+ if (item.gradient) paintGradient(card, item.gradient);
200
266
  scene.appendChild(card);
201
267
  return card;
202
268
  });
@@ -210,9 +276,13 @@ function createFolderGallery(root, options = {}) {
210
276
  dotsEl.appendChild(dot);
211
277
  return dot;
212
278
  });
279
+ const clearGridWidth = (card) => {
280
+ if (card.style.width) card.style.width = "";
281
+ };
213
282
  function layoutStack() {
214
283
  scene.style.height = "";
215
284
  cardEls.forEach((card, i) => {
285
+ clearGridWidth(card);
216
286
  const behind = (i - active + n) % n;
217
287
  if (behind === 0) {
218
288
  setCardTransform(card, { x: 0, y: 100, z: 20, rx: 0, ry: 0, s: 1, zIndex: n + 1, opacity: 1, isActive: true });
@@ -228,33 +298,31 @@ function createFolderGallery(root, options = {}) {
228
298
  }
229
299
  function layoutGrid() {
230
300
  const sceneW = scene.clientWidth || MODE_WIDTHS.grid;
231
- const cols = sceneW < 520 ? 2 : n <= 4 ? 2 : 3;
232
- const gap = 16;
233
- const cardW = sceneW;
234
- const cardH = sceneW * (2 / 3);
235
- const sc = (sceneW - (cols - 1) * gap) / (cols * cardW);
236
- const scaledW = cardW * sc;
237
- const scaledH = cardH * sc;
238
- const tabScaled = 22 * sc;
301
+ const gap = Number.isFinite(opts.grid && opts.grid.gap) ? opts.grid.gap : 16;
302
+ const cols = Math.max(1, Math.round(
303
+ opts.grid && opts.grid.columns ? opts.grid.columns : sceneW < 520 ? 2 : n <= 4 ? 2 : 3
304
+ ));
305
+ const cellW = (sceneW - (cols - 1) * gap) / cols;
306
+ const cellH = cellW * (2 / 3);
239
307
  const rows = Math.ceil(n / cols);
240
308
  cardEls.forEach((card, i) => {
241
309
  const col = i % cols;
242
310
  const row = Math.floor(i / cols);
243
311
  const itemsInRow = Math.min(cols, n - row * cols);
244
- const rowW = itemsInRow * scaledW + (itemsInRow - 1) * gap;
312
+ const rowW = itemsInRow * cellW + (itemsInRow - 1) * gap;
245
313
  const rowStartX = (sceneW - rowW) / 2;
246
- const cellLeft = rowStartX + col * (scaledW + gap);
247
- const cellTop = row * (scaledH + tabScaled + gap) + tabScaled;
248
- const tx = cellLeft + scaledW / 2 - cardW / 2;
249
- const ty = cellTop + scaledH - cardH;
250
- setCardTransform(card, { x: tx, y: ty, z: 0, rx: 0, ry: 0, s: sc, zIndex: i === active ? 2 : 1, opacity: 1, isActive: i === active });
314
+ const x = rowStartX + col * (cellW + gap);
315
+ const y = row * (cellH + gap);
316
+ card.style.width = cellW + "px";
317
+ setCardTransform(card, { x, y, z: 0, rx: 0, ry: 0, s: 1, zIndex: i === active ? 2 : 1, opacity: 1, isActive: i === active });
251
318
  });
252
- scene.style.height = rows * (scaledH + tabScaled + gap) + 20 + "px";
319
+ scene.style.height = rows * cellH + (rows - 1) * gap + "px";
253
320
  }
254
321
  function layoutCarousel() {
255
322
  const sceneW = scene.clientWidth || MODE_WIDTHS.carousel;
256
323
  const cardW = sceneW;
257
324
  const cardH = cardW * (2 / 3);
325
+ cardEls.forEach(clearGridWidth);
258
326
  const activeSc = 0.62;
259
327
  const activeW = cardW * activeSc;
260
328
  const sceneH = Math.max(cardH * activeSc + 40, 180);
@@ -289,29 +357,39 @@ function createFolderGallery(root, options = {}) {
289
357
  live.textContent = cur && cur.label ? `${active + 1} of ${n}: ${cur.label}` : `${active + 1} of ${n}`;
290
358
  }
291
359
  function goTo(i) {
292
- const next = opts.loop ? (i % n + n) % n : Math.min(Math.max(i, 0), n - 1);
360
+ if (destroyed) return;
361
+ const t = Number.isFinite(+i) ? Math.trunc(+i) : active;
362
+ const next = opts.loop ? (t % n + n) % n : Math.min(Math.max(t, 0), n - 1);
293
363
  if (next === active) return;
294
364
  active = next;
295
365
  applyLayout();
296
366
  emit("fg-activechange", { index: active, item: items[active] });
297
367
  }
298
368
  function select(i) {
369
+ if (destroyed) return;
299
370
  emit("fg-select", { index: i, item: items[i] });
300
371
  if (typeof opts.onSelect === "function") opts.onSelect(items[i], i);
301
372
  }
302
373
  function setMode(m) {
303
- if (m === mode || !MODE_WIDTHS[m]) return;
374
+ if (destroyed || m === mode || !MODE_WIDTHS[m]) return;
304
375
  mode = m;
305
376
  root.setAttribute("data-fg-mode", mode);
306
377
  applyLayout();
307
378
  emit("fg-modechange", { mode });
308
379
  }
309
380
  function setPeek(p) {
310
- if (!PEEK_MODES.has(p)) return;
381
+ if (destroyed || !PEEK_MODES.has(p) || p === root.getAttribute("data-fg-peek")) return;
311
382
  root.setAttribute("data-fg-peek", p);
383
+ emit("fg-peekchange", { peek: p });
312
384
  }
385
+ const dragEnabled = opts.drag !== "off";
386
+ let suppressClick = false;
313
387
  cardEls.forEach((card, i) => {
314
388
  on(card, "click", () => {
389
+ if (suppressClick) {
390
+ suppressClick = false;
391
+ return;
392
+ }
315
393
  i === active ? select(i) : goTo(i);
316
394
  });
317
395
  on(card, "keydown", (e) => {
@@ -377,7 +455,7 @@ function createFolderGallery(root, options = {}) {
377
455
  scrollAccum = 0;
378
456
  scrollCooldown = true;
379
457
  goTo(active + dir);
380
- setTimeout(() => {
458
+ later(() => {
381
459
  scrollCooldown = false;
382
460
  scrollAccum = 0;
383
461
  }, SCROLL_COOLDOWN[mode] || 300);
@@ -391,6 +469,7 @@ function createFolderGallery(root, options = {}) {
391
469
  touchStartY = e.touches[0].clientY;
392
470
  }, { passive: true });
393
471
  on(scene, "touchend", (e) => {
472
+ if (dragEnabled && mode === "stack") return;
394
473
  const dx = touchStartX - e.changedTouches[0].clientX;
395
474
  const dy = touchStartY - e.changedTouches[0].clientY;
396
475
  const ax = Math.abs(dx);
@@ -399,22 +478,152 @@ function createFolderGallery(root, options = {}) {
399
478
  const dir = ax > ay ? dx > 0 ? 1 : -1 : dy > 0 ? 1 : -1;
400
479
  goTo(active + dir);
401
480
  }, { passive: true });
481
+ if (dragEnabled) {
482
+ const DRAG_ROT = 0.04;
483
+ const FLING_DIST = 90;
484
+ const FLING_VEL = 0.5;
485
+ const TAP_SLOP = 6;
486
+ let dragCard = null;
487
+ let dragBase = "";
488
+ let startPX = 0, startPY = 0, dragDX = 0, dragDY = 0;
489
+ let lastX = 0, lastY = 0, lastT = 0, velX = 0, velY = 0;
490
+ on(scene, "pointerdown", (e) => {
491
+ if (mode !== "stack") return;
492
+ if (e.button !== void 0 && e.button !== 0) return;
493
+ const card = e.target && e.target.closest ? e.target.closest(".fg-card") : null;
494
+ if (!card || !card.classList.contains("is-active")) return;
495
+ dragCard = card;
496
+ dragBase = card.style.transform;
497
+ startPX = e.clientX;
498
+ startPY = e.clientY;
499
+ dragDX = 0;
500
+ dragDY = 0;
501
+ lastX = e.clientX;
502
+ lastY = e.clientY;
503
+ lastT = typeof performance !== "undefined" ? performance.now() : Date.now();
504
+ velX = 0;
505
+ velY = 0;
506
+ card.classList.add("fg-card--dragging");
507
+ if (card.setPointerCapture && e.pointerId !== void 0) {
508
+ try {
509
+ card.setPointerCapture(e.pointerId);
510
+ } catch (_) {
511
+ }
512
+ }
513
+ });
514
+ on(window, "pointermove", (e) => {
515
+ if (!dragCard) return;
516
+ dragDX = e.clientX - startPX;
517
+ dragDY = e.clientY - startPY;
518
+ const now = typeof performance !== "undefined" ? performance.now() : Date.now();
519
+ const dt = Math.max(now - lastT, 1);
520
+ velX = (e.clientX - lastX) / dt;
521
+ velY = (e.clientY - lastY) / dt;
522
+ lastX = e.clientX;
523
+ lastY = e.clientY;
524
+ lastT = now;
525
+ dragCard.style.transform = `${dragBase} translate3d(${dragDX}px, ${dragDY}px, 40px) rotate(${(dragDX * DRAG_ROT).toFixed(2)}deg)`;
526
+ });
527
+ const endDrag = () => {
528
+ if (!dragCard) return;
529
+ const card = dragCard;
530
+ dragCard = null;
531
+ card.classList.remove("fg-card--dragging");
532
+ const dist = Math.hypot(dragDX, dragDY);
533
+ if (dist < TAP_SLOP) {
534
+ card.style.transform = dragBase;
535
+ return;
536
+ }
537
+ suppressClick = true;
538
+ const horizontal = Math.abs(dragDX) >= Math.abs(dragDY);
539
+ const flung = dist > FLING_DIST || Math.hypot(velX, velY) > FLING_VEL;
540
+ const dir = horizontal ? dragDX > 0 ? -1 : 1 : dragDY > 0 ? -1 : 1;
541
+ const target = active + dir;
542
+ const blocked = !opts.loop && (target < 0 || target > n - 1);
543
+ if (!flung || blocked) {
544
+ card.style.transform = dragBase;
545
+ return;
546
+ }
547
+ const semantic = dir > 0 ? "next" : "prev";
548
+ emit("fg-flingstart", { index: active, direction: semantic });
549
+ if (reduced) {
550
+ goTo(target);
551
+ emit("fg-flingend", { index: target, direction: semantic });
552
+ return;
553
+ }
554
+ const offX = horizontal ? Math.sign(dragDX) * Math.max(window.innerWidth * 0.7, 480) : dragDX * 3;
555
+ const offY = horizontal ? dragDY * 3 : Math.sign(dragDY) * Math.max(window.innerHeight * 0.7, 480);
556
+ card.classList.add("fg-card--flung");
557
+ card.style.transform = `${dragBase} translate3d(${offX}px, ${offY}px, 40px) rotate(${Math.sign(dragDX || 1) * 18}deg)`;
558
+ card.style.opacity = "0";
559
+ goTo(target);
560
+ later(() => {
561
+ card.classList.add("fg-card--snap");
562
+ card.classList.remove("fg-card--flung");
563
+ applyLayout();
564
+ const slot = card.style.transform;
565
+ const slotOpacity = card.style.opacity;
566
+ card.style.transform = `${slot} translate3d(0, -90px, 0)`;
567
+ card.style.opacity = "0";
568
+ frame(() => frame(() => {
569
+ card.classList.remove("fg-card--snap");
570
+ card.classList.add("fg-card--entering");
571
+ card.style.transform = slot;
572
+ card.style.opacity = slotOpacity;
573
+ later(() => {
574
+ card.classList.remove("fg-card--entering");
575
+ emit("fg-flingend", { index: target, direction: semantic });
576
+ }, 700);
577
+ }));
578
+ }, 400);
579
+ };
580
+ on(window, "pointerup", endDrag);
581
+ on(window, "pointercancel", endDrag);
582
+ }
402
583
  let resizeTimer;
403
584
  on(window, "resize", () => {
404
585
  clearTimeout(resizeTimer);
405
- resizeTimer = setTimeout(applyLayout, 150);
586
+ resizeTimer = later(applyLayout, 150);
406
587
  });
588
+ if (reducedQuery && reducedQuery.addEventListener) {
589
+ on(reducedQuery, "change", (e) => {
590
+ if (!destroyed) {
591
+ reduced = e.matches;
592
+ applyLayout();
593
+ }
594
+ });
595
+ }
407
596
  applyLayout();
408
- return {
597
+ const handle = {
409
598
  next: () => goTo(active + 1),
410
599
  prev: () => goTo(active - 1),
411
600
  goTo,
412
601
  setMode,
413
602
  setPeek,
603
+ setColor: (i, hex) => {
604
+ const card = cardEls[i];
605
+ if (card && !destroyed) paintFolder(card, hex);
606
+ },
607
+ setGradient: (i, gradient) => {
608
+ const card = cardEls[i];
609
+ if (card && !destroyed) paintGradient(card, gradient);
610
+ },
414
611
  getActiveIndex: () => active,
415
612
  getMode: () => mode,
613
+ getPeek: () => root.getAttribute("data-fg-peek"),
614
+ getColor: (i) => {
615
+ const card = cardEls[i];
616
+ return card ? card.style.getPropertyValue("--fg-folder-bg") || void 0 : void 0;
617
+ },
618
+ getGradient: (i) => {
619
+ const layer = cardEls[i] && cardEls[i].querySelector(".fg-gradient");
620
+ return layer ? layer.style.background || void 0 : void 0;
621
+ },
622
+ getItems: () => items.slice(),
416
623
  destroy: teardown
417
624
  };
625
+ INSTANCES.set(root, teardown);
626
+ return handle;
418
627
  }
419
628
 
420
629
  // src/folder-gallery-vue.js
@@ -424,17 +633,22 @@ var FolderGallery = (0, import_vue.defineComponent)({
424
633
  items: { type: Array, default: () => [] },
425
634
  mode: { type: String, default: "stack" },
426
635
  peek: { type: String, default: "hover" },
636
+ drag: { type: String, default: "fling" },
427
637
  contentRenderer: { type: Function, default: void 0 },
428
638
  folderPath: { type: String, default: void 0 },
429
- loop: { type: Boolean, default: void 0 },
430
- scrollNav: { type: Boolean, default: void 0 },
639
+ // No `type: Boolean` on purpose: a Boolean prop that is absent casts to
640
+ // false, which would silently flip the core's loop/scrollNav defaults
641
+ // (both true) to off. Left untyped, an unpassed prop stays undefined and
642
+ // the core keeps its default; `:loop="false"` still turns it off.
643
+ loop: { default: void 0 },
644
+ scrollNav: { default: void 0 },
431
645
  reducedMotion: { type: String, default: void 0 },
432
646
  defaultActiveIndex: { type: Number, default: void 0 },
433
647
  label: { type: String, default: void 0 }
434
648
  },
435
649
  // emit('select', ...) dispatches to the current @select handler, so there is
436
650
  // no stale-closure problem the React wrapper has to work around with refs.
437
- emits: ["select", "active-change", "mode-change"],
651
+ emits: ["select", "active-change", "mode-change", "peek-change", "fling-start", "fling-end"],
438
652
  setup(props, { emit, expose }) {
439
653
  const rootRef = (0, import_vue.ref)(null);
440
654
  let handle = null;
@@ -444,6 +658,15 @@ var FolderGallery = (0, import_vue.defineComponent)({
444
658
  function onModeEv(e) {
445
659
  emit("mode-change", e.detail.mode);
446
660
  }
661
+ function onPeekEv(e) {
662
+ emit("peek-change", e.detail.peek);
663
+ }
664
+ function onFlingStartEv(e) {
665
+ emit("fling-start", e.detail.index, e.detail.direction);
666
+ }
667
+ function onFlingEndEv(e) {
668
+ emit("fling-end", e.detail.index, e.detail.direction);
669
+ }
447
670
  function build() {
448
671
  const root = rootRef.value;
449
672
  if (!root) return;
@@ -451,6 +674,7 @@ var FolderGallery = (0, import_vue.defineComponent)({
451
674
  items: props.items,
452
675
  mode: props.mode,
453
676
  peek: props.peek,
677
+ drag: props.drag,
454
678
  folderPath: props.folderPath,
455
679
  loop: props.loop,
456
680
  scrollNav: props.scrollNav,
@@ -464,12 +688,18 @@ var FolderGallery = (0, import_vue.defineComponent)({
464
688
  });
465
689
  root.addEventListener("fg-activechange", onActive);
466
690
  root.addEventListener("fg-modechange", onModeEv);
691
+ root.addEventListener("fg-peekchange", onPeekEv);
692
+ root.addEventListener("fg-flingstart", onFlingStartEv);
693
+ root.addEventListener("fg-flingend", onFlingEndEv);
467
694
  }
468
695
  function teardown() {
469
696
  const root = rootRef.value;
470
697
  if (root) {
471
698
  root.removeEventListener("fg-activechange", onActive);
472
699
  root.removeEventListener("fg-modechange", onModeEv);
700
+ root.removeEventListener("fg-peekchange", onPeekEv);
701
+ root.removeEventListener("fg-flingstart", onFlingStartEv);
702
+ root.removeEventListener("fg-flingend", onFlingEndEv);
473
703
  }
474
704
  if (handle) {
475
705
  handle.destroy();
@@ -481,6 +711,7 @@ var FolderGallery = (0, import_vue.defineComponent)({
481
711
  (0, import_vue.watch)(
482
712
  [
483
713
  () => props.items,
714
+ () => props.drag,
484
715
  () => props.folderPath,
485
716
  () => props.loop,
486
717
  () => props.scrollNav,
@@ -505,8 +736,14 @@ var FolderGallery = (0, import_vue.defineComponent)({
505
736
  goTo: (i) => handle && handle.goTo(i),
506
737
  setMode: (m) => handle && handle.setMode(m),
507
738
  setPeek: (p) => handle && handle.setPeek(p),
739
+ setColor: (i, hex) => handle && handle.setColor(i, hex),
740
+ setGradient: (i, gradient) => handle && handle.setGradient(i, gradient),
508
741
  getActiveIndex: () => handle ? handle.getActiveIndex() : -1,
509
- getMode: () => handle ? handle.getMode() : props.mode
742
+ getMode: () => handle ? handle.getMode() : props.mode,
743
+ getPeek: () => handle ? handle.getPeek() : props.peek,
744
+ getColor: (i) => handle ? handle.getColor(i) : void 0,
745
+ getGradient: (i) => handle ? handle.getGradient(i) : void 0,
746
+ getItems: () => handle ? handle.getItems() : []
510
747
  });
511
748
  return () => (0, import_vue.h)("div", { ref: rootRef });
512
749
  }