flipfolio 0.1.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/react.js ADDED
@@ -0,0 +1,108 @@
1
+ import {
2
+ __objRest,
3
+ __spreadValues,
4
+ createFolderGallery
5
+ } from "./chunk-DT6YOBGV.js";
6
+
7
+ // src/folder-gallery-react.js
8
+ import { createElement, forwardRef, useEffect, useImperativeHandle, useRef } from "react";
9
+ var FolderGallery = forwardRef(function FolderGallery2(props, ref) {
10
+ const _a = props, {
11
+ items,
12
+ mode = "stack",
13
+ peek = "hover",
14
+ contentRenderer,
15
+ onSelect,
16
+ onActiveChange,
17
+ onModeChange,
18
+ folderPath,
19
+ loop,
20
+ scrollNav,
21
+ reducedMotion,
22
+ defaultActiveIndex,
23
+ label,
24
+ className,
25
+ style
26
+ } = _a, rest = __objRest(_a, [
27
+ "items",
28
+ "mode",
29
+ "peek",
30
+ "contentRenderer",
31
+ "onSelect",
32
+ "onActiveChange",
33
+ "onModeChange",
34
+ "folderPath",
35
+ "loop",
36
+ "scrollNav",
37
+ "reducedMotion",
38
+ "defaultActiveIndex",
39
+ "label",
40
+ "className",
41
+ "style"
42
+ ]);
43
+ const rootRef = useRef(null);
44
+ const handleRef = useRef(null);
45
+ const callbacksRef = useRef({});
46
+ callbacksRef.current = { onSelect, onActiveChange, onModeChange, contentRenderer };
47
+ useEffect(() => {
48
+ const root = rootRef.current;
49
+ if (!root) return void 0;
50
+ const handle = createFolderGallery(root, {
51
+ items,
52
+ mode,
53
+ peek,
54
+ folderPath,
55
+ loop,
56
+ scrollNav,
57
+ reducedMotion,
58
+ defaultActiveIndex,
59
+ label,
60
+ contentRenderer: callbacksRef.current.contentRenderer ? (card, item, i) => callbacksRef.current.contentRenderer(card, item, i) : void 0,
61
+ onSelect: (item, i) => {
62
+ const fn = callbacksRef.current.onSelect;
63
+ if (fn) fn(item, i);
64
+ }
65
+ });
66
+ handleRef.current = handle;
67
+ const onActive = (e) => {
68
+ const fn = callbacksRef.current.onActiveChange;
69
+ if (fn) fn(e.detail.index, e.detail.item);
70
+ };
71
+ const onModeEv = (e) => {
72
+ const fn = callbacksRef.current.onModeChange;
73
+ if (fn) fn(e.detail.mode);
74
+ };
75
+ root.addEventListener("fg-activechange", onActive);
76
+ root.addEventListener("fg-modechange", onModeEv);
77
+ return () => {
78
+ root.removeEventListener("fg-activechange", onActive);
79
+ root.removeEventListener("fg-modechange", onModeEv);
80
+ handle.destroy();
81
+ handleRef.current = null;
82
+ };
83
+ }, [items, folderPath, loop, scrollNav, reducedMotion, defaultActiveIndex, label]);
84
+ useEffect(() => {
85
+ if (handleRef.current && handleRef.current.getMode() !== mode) {
86
+ handleRef.current.setMode(mode);
87
+ }
88
+ }, [mode]);
89
+ useEffect(() => {
90
+ if (handleRef.current) handleRef.current.setPeek(peek);
91
+ }, [peek]);
92
+ useImperativeHandle(ref, () => ({
93
+ next: () => handleRef.current && handleRef.current.next(),
94
+ prev: () => handleRef.current && handleRef.current.prev(),
95
+ goTo: (i) => handleRef.current && handleRef.current.goTo(i),
96
+ setMode: (m) => handleRef.current && handleRef.current.setMode(m),
97
+ setPeek: (p) => handleRef.current && handleRef.current.setPeek(p),
98
+ getActiveIndex: () => handleRef.current ? handleRef.current.getActiveIndex() : -1,
99
+ getMode: () => handleRef.current ? handleRef.current.getMode() : mode
100
+ }), [mode]);
101
+ return createElement("div", __spreadValues({ ref: rootRef, className, style }, rest));
102
+ });
103
+ var folder_gallery_react_default = FolderGallery;
104
+ export {
105
+ FolderGallery,
106
+ folder_gallery_react_default as default
107
+ };
108
+ //# sourceMappingURL=react.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/folder-gallery-react.js"],"sourcesContent":["/* ============================================================================\n * folder-gallery/react - thin React wrapper over the framework-agnostic core.\n *\n * import { FolderGallery } from 'folder-gallery/react';\n * import 'folder-gallery/styles.css';\n *\n * <FolderGallery\n * items={items}\n * mode=\"stack\"\n * onSelect={(item, i) => ...}\n * ref={galleryRef} // -> { next, prev, goTo, setMode, getActiveIndex, getMode }\n * />\n *\n * Written without JSX so the package builds with no React toolchain;\n * React is a peer dependency and only resolved in the consumer's app.\n * ========================================================================== */\n\nimport { createElement, forwardRef, useEffect, useImperativeHandle, useRef } from 'react';\nimport { createFolderGallery } from './folder-gallery.js';\n\nexport const FolderGallery = forwardRef(function FolderGallery(props, ref) {\n const {\n items,\n mode = 'stack',\n peek = 'hover',\n contentRenderer,\n onSelect,\n onActiveChange,\n onModeChange,\n folderPath,\n loop,\n scrollNav,\n reducedMotion,\n defaultActiveIndex,\n label,\n className,\n style,\n ...rest\n } = props;\n\n const rootRef = useRef(null);\n const handleRef = useRef(null);\n\n // Callbacks live in refs so a new inline closure per render doesn't force a\n // full gallery rebuild (and the core never holds a stale reference).\n const callbacksRef = useRef({});\n callbacksRef.current = { onSelect, onActiveChange, onModeChange, contentRenderer };\n\n // (Re)build when the structural inputs change.\n useEffect(() => {\n const root = rootRef.current;\n if (!root) return undefined;\n\n const handle = createFolderGallery(root, {\n items,\n mode,\n peek,\n folderPath,\n loop,\n scrollNav,\n reducedMotion,\n defaultActiveIndex,\n label,\n contentRenderer: callbacksRef.current.contentRenderer\n ? (card, item, i) => callbacksRef.current.contentRenderer(card, item, i)\n : undefined,\n onSelect: (item, i) => {\n const fn = callbacksRef.current.onSelect;\n if (fn) fn(item, i);\n },\n });\n handleRef.current = handle;\n\n const onActive = (e) => {\n const fn = callbacksRef.current.onActiveChange;\n if (fn) fn(e.detail.index, e.detail.item);\n };\n const onModeEv = (e) => {\n const fn = callbacksRef.current.onModeChange;\n if (fn) fn(e.detail.mode);\n };\n root.addEventListener('fg-activechange', onActive);\n root.addEventListener('fg-modechange', onModeEv);\n\n return () => {\n root.removeEventListener('fg-activechange', onActive);\n root.removeEventListener('fg-modechange', onModeEv);\n handle.destroy();\n handleRef.current = null;\n };\n // mode and peek are handled by the effects below without a rebuild.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [items, folderPath, loop, scrollNav, reducedMotion, defaultActiveIndex, label]);\n\n // Mode changes reuse the live instance.\n useEffect(() => {\n if (handleRef.current && handleRef.current.getMode() !== mode) {\n handleRef.current.setMode(mode);\n }\n }, [mode]);\n\n // Peek changes reuse the live instance too.\n useEffect(() => {\n if (handleRef.current) handleRef.current.setPeek(peek);\n }, [peek]);\n\n useImperativeHandle(ref, () => ({\n next: () => handleRef.current && handleRef.current.next(),\n prev: () => handleRef.current && handleRef.current.prev(),\n goTo: (i) => handleRef.current && handleRef.current.goTo(i),\n setMode: (m) => handleRef.current && handleRef.current.setMode(m),\n setPeek: (p) => handleRef.current && handleRef.current.setPeek(p),\n getActiveIndex: () => (handleRef.current ? handleRef.current.getActiveIndex() : -1),\n getMode: () => (handleRef.current ? handleRef.current.getMode() : mode),\n }), [mode]);\n\n return createElement('div', { ref: rootRef, className, style, ...rest });\n});\n\nexport default FolderGallery;\n"],"mappings":"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","names":["FolderGallery"]}
package/dist/vue.cjs ADDED
@@ -0,0 +1,519 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __getOwnPropSymbols = Object.getOwnPropertySymbols;
6
+ 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
+ var __export = (target, all) => {
21
+ for (var name in all)
22
+ __defProp(target, name, { get: all[name], enumerable: true });
23
+ };
24
+ var __copyProps = (to, from, except, desc) => {
25
+ if (from && typeof from === "object" || typeof from === "function") {
26
+ for (let key of __getOwnPropNames(from))
27
+ if (!__hasOwnProp.call(to, key) && key !== except)
28
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
29
+ }
30
+ return to;
31
+ };
32
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
33
+
34
+ // src/folder-gallery-vue.js
35
+ var folder_gallery_vue_exports = {};
36
+ __export(folder_gallery_vue_exports, {
37
+ FolderGallery: () => FolderGallery,
38
+ default: () => folder_gallery_vue_default
39
+ });
40
+ module.exports = __toCommonJS(folder_gallery_vue_exports);
41
+ var import_vue = require("vue");
42
+
43
+ // src/folder-gallery.js
44
+ var SVG_NS = "http://www.w3.org/2000/svg";
45
+ var DEFAULT_FOLDER_PATH = "M 0 22 C 0 10 10 0 22 0 L 155 0 C 168 0 172 6 176 14 C 180 24 188 32 204 32 L 458 32 C 470 32 480 42 480 54 L 480 320 C 480 332 470 342 458 342 L 22 342 C 10 342 0 332 0 320 Z";
46
+ var DEFAULTS = {
47
+ mode: "stack",
48
+ // 'stack' | 'grid' | 'carousel'
49
+ folderPath: DEFAULT_FOLDER_PATH,
50
+ loop: true,
51
+ scrollNav: true,
52
+ reducedMotion: "auto",
53
+ // 'auto' | 'off' | 'force'
54
+ peek: "hover",
55
+ // 'hover' | 'always' | 'off'
56
+ defaultActiveIndex: 0,
57
+ label: "Folder gallery"
58
+ };
59
+ var PEEK_MODES = /* @__PURE__ */ new Set(["hover", "always", "off"]);
60
+ var MODE_WIDTHS = { stack: 480, grid: 720, carousel: 720 };
61
+ var SCROLL_THRESHOLD = 30;
62
+ var SCROLL_COOLDOWN = { stack: 450, carousel: 300 };
63
+ function hexToRgb(hex) {
64
+ const h2 = String(hex).replace("#", "");
65
+ return [parseInt(h2.slice(0, 2), 16), parseInt(h2.slice(2, 4), 16), parseInt(h2.slice(4, 6), 16)];
66
+ }
67
+ function folderColors(hex) {
68
+ const [r, g, b] = hexToRgb(hex);
69
+ const dn = (v) => Math.max(v - 30, 0);
70
+ const up = (v, o) => Math.min(v + o, 255);
71
+ const fr = up(r, 35);
72
+ const fg = up(g, 35);
73
+ const fb = up(b, 35);
74
+ const yiq = (fr * 299 + fg * 587 + fb * 114) / 1e3;
75
+ return {
76
+ back: `rgb(${dn(r)},${dn(g)},${dn(b)})`,
77
+ front: `rgba(${up(r, 40)},${up(g, 40)},${up(b, 40)},0.55)`,
78
+ frontSolid: `rgb(${fr},${fg},${fb})`,
79
+ label: yiq >= 128 ? "#1c1c1a" : "#ffffff"
80
+ };
81
+ }
82
+ function defaultContentRenderer(card, item) {
83
+ const wrap = document.createElement("div");
84
+ wrap.className = "fg-content";
85
+ if (item.content && typeof item.content === "object" && typeof item.content.nodeType === "number") {
86
+ wrap.appendChild(item.content);
87
+ } else if (typeof item.content === "string") {
88
+ wrap.innerHTML = item.content;
89
+ } else if (item.src) {
90
+ const img = document.createElement("img");
91
+ img.className = "fg-image";
92
+ img.src = item.src;
93
+ img.alt = item.label || "";
94
+ img.loading = "lazy";
95
+ img.decoding = "async";
96
+ wrap.appendChild(img);
97
+ }
98
+ card.appendChild(wrap);
99
+ }
100
+ 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);
103
+ const items = Array.isArray(opts.items) ? opts.items : [];
104
+ const n = items.length;
105
+ 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;
107
+ let mode = MODE_WIDTHS[opts.mode] ? opts.mode : "stack";
108
+ let active = Math.min(Math.max(opts.defaultActiveIndex | 0, 0), Math.max(n - 1, 0));
109
+ let scrollCooldown = false;
110
+ let scrollAccum = 0;
111
+ const cleanups = [];
112
+ const on = (el, ev, fn, o) => {
113
+ el.addEventListener(ev, fn, o);
114
+ cleanups.push(() => el.removeEventListener(ev, fn, o));
115
+ };
116
+ const emit = (name, detail) => root.dispatchEvent(new CustomEvent(name, { detail, bubbles: true }));
117
+ root.classList.add("fg-root");
118
+ root.setAttribute("data-fg-mode", mode);
119
+ root.setAttribute("data-fg-peek", PEEK_MODES.has(opts.peek) ? opts.peek : "hover");
120
+ const scene = document.createElement("div");
121
+ scene.className = "fg-scene";
122
+ scene.setAttribute("role", "listbox");
123
+ scene.setAttribute("aria-label", opts.label);
124
+ scene.setAttribute("aria-roledescription", "folder gallery");
125
+ const dotsEl = document.createElement("div");
126
+ dotsEl.className = "fg-dots";
127
+ dotsEl.setAttribute("role", "tablist");
128
+ const live = document.createElement("div");
129
+ live.className = "fg-sr-only";
130
+ live.setAttribute("aria-live", "polite");
131
+ live.setAttribute("aria-atomic", "true");
132
+ root.append(scene, dotsEl, live);
133
+ function teardown() {
134
+ cleanups.forEach((fn) => fn());
135
+ cleanups.length = 0;
136
+ root.replaceChildren();
137
+ root.classList.remove("fg-root");
138
+ root.removeAttribute("data-fg-mode");
139
+ root.removeAttribute("data-fg-peek");
140
+ }
141
+ if (n === 0) {
142
+ return { next() {
143
+ }, prev() {
144
+ }, goTo() {
145
+ }, setMode() {
146
+ }, setPeek() {
147
+ }, getActiveIndex: () => -1, getMode: () => mode, destroy: teardown };
148
+ }
149
+ 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";
152
+ card.style.zIndex = String(zIndex);
153
+ card.style.opacity = String(opacity);
154
+ card.setAttribute("aria-selected", isActive ? "true" : "false");
155
+ card.classList.toggle("is-active", isActive);
156
+ }
157
+ const cardEls = items.map((item, i) => {
158
+ const card = document.createElement("div");
159
+ card.className = "fg-card";
160
+ card.tabIndex = i === active ? 0 : -1;
161
+ card.setAttribute("role", "option");
162
+ 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
+ }
170
+ const svg = document.createElementNS(SVG_NS, "svg");
171
+ svg.setAttribute("class", "fg-folder");
172
+ svg.setAttribute("viewBox", "0 0 480 342");
173
+ svg.setAttribute("preserveAspectRatio", "none");
174
+ svg.setAttribute("aria-hidden", "true");
175
+ const path = document.createElementNS(SVG_NS, "path");
176
+ path.setAttribute("d", opts.folderPath);
177
+ path.setAttribute("fill", item.color ? folderColors(item.color).back : "var(--fg-folder-bg)");
178
+ svg.appendChild(path);
179
+ card.appendChild(svg);
180
+ renderContent(card, item, i);
181
+ const front = document.createElement("div");
182
+ front.className = "fg-front";
183
+ if (item.decal) {
184
+ card.classList.add("fg-card--decal");
185
+ const photo = document.createElement("img");
186
+ photo.className = "fg-decal";
187
+ photo.src = item.decal;
188
+ photo.alt = "";
189
+ photo.loading = "lazy";
190
+ photo.decoding = "async";
191
+ front.appendChild(photo);
192
+ }
193
+ if (item.label) {
194
+ const label = document.createElement("div");
195
+ label.className = "fg-label";
196
+ label.textContent = item.label;
197
+ front.appendChild(label);
198
+ }
199
+ card.appendChild(front);
200
+ scene.appendChild(card);
201
+ return card;
202
+ });
203
+ const dots = cardEls.map((_, i) => {
204
+ const dot = document.createElement("button");
205
+ dot.type = "button";
206
+ dot.className = "fg-dot";
207
+ dot.setAttribute("role", "tab");
208
+ dot.setAttribute("aria-label", items[i].label || `Go to ${i + 1}`);
209
+ on(dot, "click", () => goTo(i));
210
+ dotsEl.appendChild(dot);
211
+ return dot;
212
+ });
213
+ function layoutStack() {
214
+ scene.style.height = "";
215
+ cardEls.forEach((card, i) => {
216
+ const behind = (i - active + n) % n;
217
+ if (behind === 0) {
218
+ setCardTransform(card, { x: 0, y: 100, z: 20, rx: 0, ry: 0, s: 1, zIndex: n + 1, opacity: 1, isActive: true });
219
+ return;
220
+ }
221
+ const safeN = Math.max(n - 1, 1);
222
+ const tz = -behind * 25 - behind * behind * 3;
223
+ const rx = reduced ? 0 : -behind * 3;
224
+ const ty = 100 - behind * 28 - behind * behind * 2;
225
+ const sc = 1 - behind * 0.06 - behind / safeN * (behind / safeN) * 0.04;
226
+ setCardTransform(card, { x: 0, y: ty, z: tz, rx, ry: 0, s: Math.max(sc, 0.5), zIndex: n - behind, opacity: 1 - behind * 0.08, isActive: false });
227
+ });
228
+ }
229
+ function layoutGrid() {
230
+ 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;
239
+ const rows = Math.ceil(n / cols);
240
+ cardEls.forEach((card, i) => {
241
+ const col = i % cols;
242
+ const row = Math.floor(i / cols);
243
+ const itemsInRow = Math.min(cols, n - row * cols);
244
+ const rowW = itemsInRow * scaledW + (itemsInRow - 1) * gap;
245
+ 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 });
251
+ });
252
+ scene.style.height = rows * (scaledH + tabScaled + gap) + 20 + "px";
253
+ }
254
+ function layoutCarousel() {
255
+ const sceneW = scene.clientWidth || MODE_WIDTHS.carousel;
256
+ const cardW = sceneW;
257
+ const cardH = cardW * (2 / 3);
258
+ const activeSc = 0.62;
259
+ const activeW = cardW * activeSc;
260
+ const sceneH = Math.max(cardH * activeSc + 40, 180);
261
+ scene.style.height = sceneH + "px";
262
+ const offsetStep = Math.min(130, (sceneW - activeW) / 4);
263
+ cardEls.forEach((card, i) => {
264
+ let offset = i - active;
265
+ if (n > 2 && offset > Math.floor(n / 2)) offset -= n;
266
+ if (n > 2 && offset < -Math.floor(n / 2)) offset += n;
267
+ const sc = Math.max(activeSc - Math.abs(offset) * 0.06, 0.28);
268
+ const z = -Math.abs(offset) * 50;
269
+ const op = Math.max(1 - Math.abs(offset) * 0.25, 0.15);
270
+ const tx = sceneW / 2 + offset * offsetStep - cardW / 2;
271
+ const ty = sceneH - 20 - cardH;
272
+ setCardTransform(card, { x: tx, y: ty, z, rx: 0, ry: 0, s: sc, zIndex: n - Math.abs(offset), opacity: op, isActive: offset === 0 });
273
+ });
274
+ }
275
+ function sizeScene() {
276
+ scene.style.width = `min(${MODE_WIDTHS[mode]}px, 92vw)`;
277
+ }
278
+ function applyLayout() {
279
+ sizeScene();
280
+ (mode === "grid" ? layoutGrid : mode === "carousel" ? layoutCarousel : layoutStack)();
281
+ dots.forEach((d, i) => {
282
+ d.classList.toggle("is-active", i === active);
283
+ d.setAttribute("aria-selected", i === active ? "true" : "false");
284
+ });
285
+ cardEls.forEach((c, i) => {
286
+ c.tabIndex = i === active ? 0 : -1;
287
+ });
288
+ const cur = items[active];
289
+ live.textContent = cur && cur.label ? `${active + 1} of ${n}: ${cur.label}` : `${active + 1} of ${n}`;
290
+ }
291
+ function goTo(i) {
292
+ const next = opts.loop ? (i % n + n) % n : Math.min(Math.max(i, 0), n - 1);
293
+ if (next === active) return;
294
+ active = next;
295
+ applyLayout();
296
+ emit("fg-activechange", { index: active, item: items[active] });
297
+ }
298
+ function select(i) {
299
+ emit("fg-select", { index: i, item: items[i] });
300
+ if (typeof opts.onSelect === "function") opts.onSelect(items[i], i);
301
+ }
302
+ function setMode(m) {
303
+ if (m === mode || !MODE_WIDTHS[m]) return;
304
+ mode = m;
305
+ root.setAttribute("data-fg-mode", mode);
306
+ applyLayout();
307
+ emit("fg-modechange", { mode });
308
+ }
309
+ function setPeek(p) {
310
+ if (!PEEK_MODES.has(p)) return;
311
+ root.setAttribute("data-fg-peek", p);
312
+ }
313
+ cardEls.forEach((card, i) => {
314
+ on(card, "click", () => {
315
+ i === active ? select(i) : goTo(i);
316
+ });
317
+ on(card, "keydown", (e) => {
318
+ if (e.key === "Enter" || e.key === " ") {
319
+ e.preventDefault();
320
+ select(i);
321
+ }
322
+ });
323
+ });
324
+ function handleArrowKeys(e) {
325
+ if (e.key === "ArrowRight" || e.key === "ArrowDown") {
326
+ e.preventDefault();
327
+ goTo(active + 1);
328
+ return true;
329
+ }
330
+ if (e.key === "ArrowLeft" || e.key === "ArrowUp") {
331
+ e.preventDefault();
332
+ goTo(active - 1);
333
+ return true;
334
+ }
335
+ if (e.key === "Home") {
336
+ e.preventDefault();
337
+ goTo(0);
338
+ return true;
339
+ }
340
+ if (e.key === "End") {
341
+ e.preventDefault();
342
+ goTo(n - 1);
343
+ return true;
344
+ }
345
+ return false;
346
+ }
347
+ on(scene, "keydown", (e) => {
348
+ if (handleArrowKeys(e)) cardEls[active].focus();
349
+ });
350
+ on(dotsEl, "keydown", (e) => {
351
+ if (handleArrowKeys(e)) cardEls[active].focus();
352
+ });
353
+ let hovered = false;
354
+ on(root, "mouseenter", () => {
355
+ hovered = true;
356
+ });
357
+ on(root, "mouseleave", () => {
358
+ hovered = false;
359
+ });
360
+ on(document, "keydown", (e) => {
361
+ if (!hovered) return;
362
+ const ae = document.activeElement;
363
+ if (ae && ae !== document.body && !root.contains(ae)) return;
364
+ handleArrowKeys(e);
365
+ });
366
+ if (opts.scrollNav) {
367
+ on(root, "wheel", (e) => {
368
+ if (mode === "grid") return;
369
+ const atStart = !opts.loop && active === 0 && e.deltaY < 0;
370
+ const atEnd = !opts.loop && active === n - 1 && e.deltaY > 0;
371
+ if (atStart || atEnd) return;
372
+ e.preventDefault();
373
+ if (scrollCooldown) return;
374
+ scrollAccum += e.deltaY;
375
+ if (Math.abs(scrollAccum) >= SCROLL_THRESHOLD) {
376
+ const dir = scrollAccum > 0 ? 1 : -1;
377
+ scrollAccum = 0;
378
+ scrollCooldown = true;
379
+ goTo(active + dir);
380
+ setTimeout(() => {
381
+ scrollCooldown = false;
382
+ scrollAccum = 0;
383
+ }, SCROLL_COOLDOWN[mode] || 300);
384
+ }
385
+ }, { passive: false });
386
+ }
387
+ let touchStartX = 0;
388
+ let touchStartY = 0;
389
+ on(scene, "touchstart", (e) => {
390
+ touchStartX = e.touches[0].clientX;
391
+ touchStartY = e.touches[0].clientY;
392
+ }, { passive: true });
393
+ on(scene, "touchend", (e) => {
394
+ const dx = touchStartX - e.changedTouches[0].clientX;
395
+ const dy = touchStartY - e.changedTouches[0].clientY;
396
+ const ax = Math.abs(dx);
397
+ const ay = Math.abs(dy);
398
+ if (Math.max(ax, ay) < 40) return;
399
+ const dir = ax > ay ? dx > 0 ? 1 : -1 : dy > 0 ? 1 : -1;
400
+ goTo(active + dir);
401
+ }, { passive: true });
402
+ let resizeTimer;
403
+ on(window, "resize", () => {
404
+ clearTimeout(resizeTimer);
405
+ resizeTimer = setTimeout(applyLayout, 150);
406
+ });
407
+ applyLayout();
408
+ return {
409
+ next: () => goTo(active + 1),
410
+ prev: () => goTo(active - 1),
411
+ goTo,
412
+ setMode,
413
+ setPeek,
414
+ getActiveIndex: () => active,
415
+ getMode: () => mode,
416
+ destroy: teardown
417
+ };
418
+ }
419
+
420
+ // src/folder-gallery-vue.js
421
+ var FolderGallery = (0, import_vue.defineComponent)({
422
+ name: "FolderGallery",
423
+ props: {
424
+ items: { type: Array, default: () => [] },
425
+ mode: { type: String, default: "stack" },
426
+ peek: { type: String, default: "hover" },
427
+ contentRenderer: { type: Function, default: void 0 },
428
+ folderPath: { type: String, default: void 0 },
429
+ loop: { type: Boolean, default: void 0 },
430
+ scrollNav: { type: Boolean, default: void 0 },
431
+ reducedMotion: { type: String, default: void 0 },
432
+ defaultActiveIndex: { type: Number, default: void 0 },
433
+ label: { type: String, default: void 0 }
434
+ },
435
+ // emit('select', ...) dispatches to the current @select handler, so there is
436
+ // no stale-closure problem the React wrapper has to work around with refs.
437
+ emits: ["select", "active-change", "mode-change"],
438
+ setup(props, { emit, expose }) {
439
+ const rootRef = (0, import_vue.ref)(null);
440
+ let handle = null;
441
+ function onActive(e) {
442
+ emit("active-change", e.detail.index, e.detail.item);
443
+ }
444
+ function onModeEv(e) {
445
+ emit("mode-change", e.detail.mode);
446
+ }
447
+ function build() {
448
+ const root = rootRef.value;
449
+ if (!root) return;
450
+ handle = createFolderGallery(root, {
451
+ items: props.items,
452
+ mode: props.mode,
453
+ peek: props.peek,
454
+ folderPath: props.folderPath,
455
+ loop: props.loop,
456
+ scrollNav: props.scrollNav,
457
+ reducedMotion: props.reducedMotion,
458
+ defaultActiveIndex: props.defaultActiveIndex,
459
+ label: props.label,
460
+ // Read props.contentRenderer at call time so a swapped renderer is
461
+ // picked up on the next (structural) rebuild without a stale ref.
462
+ contentRenderer: props.contentRenderer ? (card, item, i) => props.contentRenderer(card, item, i) : void 0,
463
+ onSelect: (item, i) => emit("select", item, i)
464
+ });
465
+ root.addEventListener("fg-activechange", onActive);
466
+ root.addEventListener("fg-modechange", onModeEv);
467
+ }
468
+ function teardown() {
469
+ const root = rootRef.value;
470
+ if (root) {
471
+ root.removeEventListener("fg-activechange", onActive);
472
+ root.removeEventListener("fg-modechange", onModeEv);
473
+ }
474
+ if (handle) {
475
+ handle.destroy();
476
+ handle = null;
477
+ }
478
+ }
479
+ (0, import_vue.onMounted)(build);
480
+ (0, import_vue.onBeforeUnmount)(teardown);
481
+ (0, import_vue.watch)(
482
+ [
483
+ () => props.items,
484
+ () => props.folderPath,
485
+ () => props.loop,
486
+ () => props.scrollNav,
487
+ () => props.reducedMotion,
488
+ () => props.defaultActiveIndex,
489
+ () => props.label
490
+ ],
491
+ () => {
492
+ teardown();
493
+ build();
494
+ }
495
+ );
496
+ (0, import_vue.watch)(() => props.mode, (m) => {
497
+ if (handle && handle.getMode() !== m) handle.setMode(m);
498
+ });
499
+ (0, import_vue.watch)(() => props.peek, (p) => {
500
+ if (handle) handle.setPeek(p);
501
+ });
502
+ expose({
503
+ next: () => handle && handle.next(),
504
+ prev: () => handle && handle.prev(),
505
+ goTo: (i) => handle && handle.goTo(i),
506
+ setMode: (m) => handle && handle.setMode(m),
507
+ setPeek: (p) => handle && handle.setPeek(p),
508
+ getActiveIndex: () => handle ? handle.getActiveIndex() : -1,
509
+ getMode: () => handle ? handle.getMode() : props.mode
510
+ });
511
+ return () => (0, import_vue.h)("div", { ref: rootRef });
512
+ }
513
+ });
514
+ var folder_gallery_vue_default = FolderGallery;
515
+ // Annotate the CommonJS export names for ESM import in node:
516
+ 0 && (module.exports = {
517
+ FolderGallery
518
+ });
519
+ //# sourceMappingURL=vue.cjs.map