flipfolio 0.2.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/element.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 });
@@ -60,15 +45,20 @@ var DEFAULTS = {
60
45
  };
61
46
  var PEEK_MODES = /* @__PURE__ */ new Set(["hover", "always", "off"]);
62
47
  var DRAG_MODES = /* @__PURE__ */ new Set(["fling", "off"]);
48
+ var INSTANCES = /* @__PURE__ */ new WeakMap();
63
49
  var MODE_WIDTHS = { stack: 480, grid: 720, carousel: 720 };
64
50
  var SCROLL_THRESHOLD = 30;
65
51
  var SCROLL_COOLDOWN = { stack: 450, carousel: 300 };
66
52
  function hexToRgb(hex) {
67
- const h = String(hex).replace("#", "");
53
+ let h = String(hex).trim().replace(/^#/, "");
54
+ if (h.length === 3) h = h.replace(/./g, (c) => c + c);
55
+ if (!/^[0-9a-f]{6}$/i.test(h)) return null;
68
56
  return [parseInt(h.slice(0, 2), 16), parseInt(h.slice(2, 4), 16), parseInt(h.slice(4, 6), 16)];
69
57
  }
70
58
  function folderColors(hex) {
71
- const [r, g, b] = hexToRgb(hex);
59
+ const rgb = hexToRgb(hex);
60
+ if (!rgb) return null;
61
+ const [r, g, b] = rgb;
72
62
  const dn = (v) => Math.max(v - 30, 0);
73
63
  const up = (v, o) => Math.min(v + o, 255);
74
64
  const fr = up(r, 35);
@@ -82,6 +72,34 @@ function folderColors(hex) {
82
72
  label: yiq >= 128 ? "#1c1c1a" : "#ffffff"
83
73
  };
84
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
+ }
85
103
  function defaultContentRenderer(card, item) {
86
104
  const wrap = document.createElement("div");
87
105
  wrap.className = "fg-content";
@@ -101,12 +119,17 @@ function defaultContentRenderer(card, item) {
101
119
  card.appendChild(wrap);
102
120
  }
103
121
  function createFolderGallery(root, options = {}) {
104
- if (!root || !root.nodeType) throw new Error("createFolderGallery: a root element is required");
105
- 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 };
106
128
  const items = Array.isArray(opts.items) ? opts.items : [];
107
129
  const n = items.length;
108
130
  const renderContent = typeof opts.contentRenderer === "function" ? opts.contentRenderer : defaultContentRenderer;
109
- 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);
110
133
  let mode = MODE_WIDTHS[opts.mode] ? opts.mode : "stack";
111
134
  let active = Math.min(Math.max(opts.defaultActiveIndex | 0, 0), Math.max(n - 1, 0));
112
135
  let scrollCooldown = false;
@@ -117,6 +140,25 @@ function createFolderGallery(root, options = {}) {
117
140
  cleanups.push(() => el.removeEventListener(ev, fn, o));
118
141
  };
119
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
+ };
120
162
  root.classList.add("fg-root");
121
163
  root.setAttribute("data-fg-mode", mode);
122
164
  root.setAttribute("data-fg-peek", PEEK_MODES.has(opts.peek) ? opts.peek : "hover");
@@ -135,6 +177,12 @@ function createFolderGallery(root, options = {}) {
135
177
  live.setAttribute("aria-atomic", "true");
136
178
  root.append(scene, dotsEl, live);
137
179
  function teardown() {
180
+ if (destroyed) return;
181
+ destroyed = true;
182
+ timers.forEach(clearTimeout);
183
+ timers.clear();
184
+ rafs.forEach(cancelAnimationFrame);
185
+ rafs.clear();
138
186
  cleanups.forEach((fn) => fn());
139
187
  cleanups.length = 0;
140
188
  root.replaceChildren();
@@ -142,21 +190,34 @@ function createFolderGallery(root, options = {}) {
142
190
  root.removeAttribute("data-fg-mode");
143
191
  root.removeAttribute("data-fg-peek");
144
192
  root.removeAttribute("data-fg-drag");
193
+ INSTANCES.delete(root);
145
194
  }
146
195
  if (n === 0) {
147
- return { next() {
148
- }, prev() {
149
- }, goTo() {
150
- }, setMode() {
151
- }, setPeek() {
152
- }, 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
+ };
153
215
  }
154
216
  function setCardTransform(card, { x, y, z, rx, ry, s, zIndex, opacity, isActive }) {
155
217
  if (!card.classList.contains("fg-card--flung")) {
156
218
  card.style.transform = `translate3d(${x}px,${y}px,${z}px) rotateX(${rx}deg) rotateY(${ry}deg) scale(${s})`;
157
219
  card.style.opacity = String(opacity);
158
220
  }
159
- card.style.transformOrigin = "center bottom";
160
221
  card.style.zIndex = String(zIndex);
161
222
  card.setAttribute("aria-selected", isActive ? "true" : "false");
162
223
  card.classList.toggle("is-active", isActive);
@@ -167,13 +228,9 @@ function createFolderGallery(root, options = {}) {
167
228
  card.tabIndex = i === active ? 0 : -1;
168
229
  card.setAttribute("role", "option");
169
230
  card.setAttribute("aria-label", item.label || `Item ${i + 1}`);
170
- if (item.color) {
171
- const c = folderColors(item.color);
172
- card.style.setProperty("--fg-folder-bg", item.color);
173
- card.style.setProperty("--fg-front", c.front);
174
- card.style.setProperty("--fg-front-solid", c.frontSolid);
175
- card.style.setProperty("--fg-label-on-color", c.label);
176
- }
231
+ card.setAttribute("aria-setsize", String(n));
232
+ card.setAttribute("aria-posinset", String(i + 1));
233
+ card.setAttribute("data-fg-index", String(i));
177
234
  const svg = document.createElementNS(SVG_NS, "svg");
178
235
  svg.setAttribute("class", "fg-folder");
179
236
  svg.setAttribute("viewBox", "0 0 480 342");
@@ -181,7 +238,7 @@ function createFolderGallery(root, options = {}) {
181
238
  svg.setAttribute("aria-hidden", "true");
182
239
  const path = document.createElementNS(SVG_NS, "path");
183
240
  path.setAttribute("d", opts.folderPath);
184
- path.setAttribute("fill", item.color ? folderColors(item.color).back : "var(--fg-folder-bg)");
241
+ path.setAttribute("fill", "var(--fg-folder-bg)");
185
242
  svg.appendChild(path);
186
243
  card.appendChild(svg);
187
244
  renderContent(card, item, i);
@@ -204,6 +261,8 @@ function createFolderGallery(root, options = {}) {
204
261
  front.appendChild(label);
205
262
  }
206
263
  card.appendChild(front);
264
+ if (item.color) paintFolder(card, item.color);
265
+ if (item.gradient) paintGradient(card, item.gradient);
207
266
  scene.appendChild(card);
208
267
  return card;
209
268
  });
@@ -217,9 +276,13 @@ function createFolderGallery(root, options = {}) {
217
276
  dotsEl.appendChild(dot);
218
277
  return dot;
219
278
  });
279
+ const clearGridWidth = (card) => {
280
+ if (card.style.width) card.style.width = "";
281
+ };
220
282
  function layoutStack() {
221
283
  scene.style.height = "";
222
284
  cardEls.forEach((card, i) => {
285
+ clearGridWidth(card);
223
286
  const behind = (i - active + n) % n;
224
287
  if (behind === 0) {
225
288
  setCardTransform(card, { x: 0, y: 100, z: 20, rx: 0, ry: 0, s: 1, zIndex: n + 1, opacity: 1, isActive: true });
@@ -235,33 +298,31 @@ function createFolderGallery(root, options = {}) {
235
298
  }
236
299
  function layoutGrid() {
237
300
  const sceneW = scene.clientWidth || MODE_WIDTHS.grid;
238
- const cols = sceneW < 520 ? 2 : n <= 4 ? 2 : 3;
239
- const gap = 16;
240
- const cardW = sceneW;
241
- const cardH = sceneW * (2 / 3);
242
- const sc = (sceneW - (cols - 1) * gap) / (cols * cardW);
243
- const scaledW = cardW * sc;
244
- const scaledH = cardH * sc;
245
- 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);
246
307
  const rows = Math.ceil(n / cols);
247
308
  cardEls.forEach((card, i) => {
248
309
  const col = i % cols;
249
310
  const row = Math.floor(i / cols);
250
311
  const itemsInRow = Math.min(cols, n - row * cols);
251
- const rowW = itemsInRow * scaledW + (itemsInRow - 1) * gap;
312
+ const rowW = itemsInRow * cellW + (itemsInRow - 1) * gap;
252
313
  const rowStartX = (sceneW - rowW) / 2;
253
- const cellLeft = rowStartX + col * (scaledW + gap);
254
- const cellTop = row * (scaledH + tabScaled + gap) + tabScaled;
255
- const tx = cellLeft + scaledW / 2 - cardW / 2;
256
- const ty = cellTop + scaledH - cardH;
257
- 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 });
258
318
  });
259
- scene.style.height = rows * (scaledH + tabScaled + gap) + 20 + "px";
319
+ scene.style.height = rows * cellH + (rows - 1) * gap + "px";
260
320
  }
261
321
  function layoutCarousel() {
262
322
  const sceneW = scene.clientWidth || MODE_WIDTHS.carousel;
263
323
  const cardW = sceneW;
264
324
  const cardH = cardW * (2 / 3);
325
+ cardEls.forEach(clearGridWidth);
265
326
  const activeSc = 0.62;
266
327
  const activeW = cardW * activeSc;
267
328
  const sceneH = Math.max(cardH * activeSc + 40, 180);
@@ -296,26 +357,30 @@ function createFolderGallery(root, options = {}) {
296
357
  live.textContent = cur && cur.label ? `${active + 1} of ${n}: ${cur.label}` : `${active + 1} of ${n}`;
297
358
  }
298
359
  function goTo(i) {
299
- 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);
300
363
  if (next === active) return;
301
364
  active = next;
302
365
  applyLayout();
303
366
  emit("fg-activechange", { index: active, item: items[active] });
304
367
  }
305
368
  function select(i) {
369
+ if (destroyed) return;
306
370
  emit("fg-select", { index: i, item: items[i] });
307
371
  if (typeof opts.onSelect === "function") opts.onSelect(items[i], i);
308
372
  }
309
373
  function setMode(m) {
310
- if (m === mode || !MODE_WIDTHS[m]) return;
374
+ if (destroyed || m === mode || !MODE_WIDTHS[m]) return;
311
375
  mode = m;
312
376
  root.setAttribute("data-fg-mode", mode);
313
377
  applyLayout();
314
378
  emit("fg-modechange", { mode });
315
379
  }
316
380
  function setPeek(p) {
317
- if (!PEEK_MODES.has(p)) return;
381
+ if (destroyed || !PEEK_MODES.has(p) || p === root.getAttribute("data-fg-peek")) return;
318
382
  root.setAttribute("data-fg-peek", p);
383
+ emit("fg-peekchange", { peek: p });
319
384
  }
320
385
  const dragEnabled = opts.drag !== "off";
321
386
  let suppressClick = false;
@@ -390,7 +455,7 @@ function createFolderGallery(root, options = {}) {
390
455
  scrollAccum = 0;
391
456
  scrollCooldown = true;
392
457
  goTo(active + dir);
393
- setTimeout(() => {
458
+ later(() => {
394
459
  scrollCooldown = false;
395
460
  scrollAccum = 0;
396
461
  }, SCROLL_COOLDOWN[mode] || 300);
@@ -479,8 +544,11 @@ function createFolderGallery(root, options = {}) {
479
544
  card.style.transform = dragBase;
480
545
  return;
481
546
  }
547
+ const semantic = dir > 0 ? "next" : "prev";
548
+ emit("fg-flingstart", { index: active, direction: semantic });
482
549
  if (reduced) {
483
550
  goTo(target);
551
+ emit("fg-flingend", { index: target, direction: semantic });
484
552
  return;
485
553
  }
486
554
  const offX = horizontal ? Math.sign(dragDX) * Math.max(window.innerWidth * 0.7, 480) : dragDX * 3;
@@ -489,7 +557,7 @@ function createFolderGallery(root, options = {}) {
489
557
  card.style.transform = `${dragBase} translate3d(${offX}px, ${offY}px, 40px) rotate(${Math.sign(dragDX || 1) * 18}deg)`;
490
558
  card.style.opacity = "0";
491
559
  goTo(target);
492
- setTimeout(() => {
560
+ later(() => {
493
561
  card.classList.add("fg-card--snap");
494
562
  card.classList.remove("fg-card--flung");
495
563
  applyLayout();
@@ -497,12 +565,15 @@ function createFolderGallery(root, options = {}) {
497
565
  const slotOpacity = card.style.opacity;
498
566
  card.style.transform = `${slot} translate3d(0, -90px, 0)`;
499
567
  card.style.opacity = "0";
500
- requestAnimationFrame(() => requestAnimationFrame(() => {
568
+ frame(() => frame(() => {
501
569
  card.classList.remove("fg-card--snap");
502
570
  card.classList.add("fg-card--entering");
503
571
  card.style.transform = slot;
504
572
  card.style.opacity = slotOpacity;
505
- setTimeout(() => card.classList.remove("fg-card--entering"), 700);
573
+ later(() => {
574
+ card.classList.remove("fg-card--entering");
575
+ emit("fg-flingend", { index: target, direction: semantic });
576
+ }, 700);
506
577
  }));
507
578
  }, 400);
508
579
  };
@@ -512,19 +583,47 @@ function createFolderGallery(root, options = {}) {
512
583
  let resizeTimer;
513
584
  on(window, "resize", () => {
514
585
  clearTimeout(resizeTimer);
515
- resizeTimer = setTimeout(applyLayout, 150);
586
+ resizeTimer = later(applyLayout, 150);
516
587
  });
588
+ if (reducedQuery && reducedQuery.addEventListener) {
589
+ on(reducedQuery, "change", (e) => {
590
+ if (!destroyed) {
591
+ reduced = e.matches;
592
+ applyLayout();
593
+ }
594
+ });
595
+ }
517
596
  applyLayout();
518
- return {
597
+ const handle = {
519
598
  next: () => goTo(active + 1),
520
599
  prev: () => goTo(active - 1),
521
600
  goTo,
522
601
  setMode,
523
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
+ },
524
611
  getActiveIndex: () => active,
525
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(),
526
623
  destroy: teardown
527
624
  };
625
+ INSTANCES.set(root, teardown);
626
+ return handle;
528
627
  }
529
628
 
530
629
  // src/folder-gallery-element.js
@@ -613,12 +712,30 @@ var FolderGalleryElement = class extends ElementBase {
613
712
  setPeek(p) {
614
713
  this.setAttribute("peek", p);
615
714
  }
715
+ setColor(i, hex) {
716
+ this._handle && this._handle.setColor(i, hex);
717
+ }
718
+ setGradient(i, gradient) {
719
+ this._handle && this._handle.setGradient(i, gradient);
720
+ }
616
721
  getActiveIndex() {
617
722
  return this._handle ? this._handle.getActiveIndex() : -1;
618
723
  }
619
724
  getMode() {
620
725
  return this._handle ? this._handle.getMode() : this.getAttribute("mode") || "stack";
621
726
  }
727
+ getPeek() {
728
+ return this._handle ? this._handle.getPeek() : this.getAttribute("peek") || "hover";
729
+ }
730
+ getColor(i) {
731
+ return this._handle ? this._handle.getColor(i) : void 0;
732
+ }
733
+ getGradient(i) {
734
+ return this._handle ? this._handle.getGradient(i) : void 0;
735
+ }
736
+ getItems() {
737
+ return this._handle ? this._handle.getItems() : [];
738
+ }
622
739
  };
623
740
  function defineFolderGallery(tag = "folder-gallery") {
624
741
  if (typeof customElements !== "undefined" && !customElements.get(tag)) {