packing-bingo 0.3.0 → 0.4.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/README.md +57 -25
- package/dist/{chunk-Z34GMJW2.js → chunk-BAA2CFUA.js} +174 -41
- package/dist/core.cjs +174 -41
- package/dist/core.d.cts +36 -3
- package/dist/core.d.ts +36 -3
- package/dist/core.js +1 -1
- package/dist/index.cjs +181 -43
- package/dist/index.d.cts +3 -1
- package/dist/index.d.ts +3 -1
- package/dist/index.js +8 -3
- package/dist/packing-bingo.global.js +26 -20
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -126,7 +126,7 @@ function bingoLines(size, free = false) {
|
|
|
126
126
|
// src/messages.ts
|
|
127
127
|
var ja = {
|
|
128
128
|
title: "\u6301\u3061\u3082\u306E\u30D3\u30F3\u30B4",
|
|
129
|
-
tag: "
|
|
129
|
+
tag: "",
|
|
130
130
|
subtitle: "",
|
|
131
131
|
packed: "\u305D\u308D\u3063\u305F",
|
|
132
132
|
bingo: "BINGO",
|
|
@@ -134,6 +134,7 @@ var ja = {
|
|
|
134
134
|
combo: (n) => `${n} COMBO`,
|
|
135
135
|
comboPop: (n) => `${n} COMBO!`,
|
|
136
136
|
reset: "\u30EA\u30BB\u30C3\u30C8",
|
|
137
|
+
shuffle: "\u{1F500} \u30B7\u30E3\u30C3\u30D5\u30EB",
|
|
137
138
|
soundOn: "\u{1F50A} \u97F3\u3042\u308A",
|
|
138
139
|
soundOff: "\u{1F507} \u97F3\u306A\u3057",
|
|
139
140
|
stamp: "OK",
|
|
@@ -147,7 +148,7 @@ var ja = {
|
|
|
147
148
|
bannerMulti: (n) => `\u4E00\u6C17\u306B ${n} \u30E9\u30A4\u30F3\uFF01`,
|
|
148
149
|
statusStart: () => "",
|
|
149
150
|
statusLines: (lines, left) => `\u30D3\u30F3\u30B4 ${lines} \u30E9\u30A4\u30F3\uFF01 \u306E\u3053\u308A ${left} \u3053`,
|
|
150
|
-
statusReach: (
|
|
151
|
+
statusReach: () => "",
|
|
151
152
|
statusDone: "\u{1F389} \u30D1\u30FC\u30D5\u30A7\u30AF\u30C8\uFF01\u5FD8\u308C\u7269\u30BC\u30ED\u3067\u51FA\u767A\u3067\u304D\u307E\u3059",
|
|
152
153
|
perfectTag: "\u{1F3C6} PERFECT",
|
|
153
154
|
perfectStamp: "\u6E96\u5099\n\u5B8C\u4E86",
|
|
@@ -166,7 +167,7 @@ var ja = {
|
|
|
166
167
|
};
|
|
167
168
|
var en = {
|
|
168
169
|
title: "Packing Bingo",
|
|
169
|
-
tag: "
|
|
170
|
+
tag: "",
|
|
170
171
|
subtitle: "",
|
|
171
172
|
packed: "PACKED",
|
|
172
173
|
bingo: "BINGO",
|
|
@@ -174,6 +175,7 @@ var en = {
|
|
|
174
175
|
combo: (n) => `${n} COMBO`,
|
|
175
176
|
comboPop: (n) => `${n} COMBO!`,
|
|
176
177
|
reset: "Reset",
|
|
178
|
+
shuffle: "\u{1F500} Shuffle",
|
|
177
179
|
soundOn: "\u{1F50A} Sound on",
|
|
178
180
|
soundOff: "\u{1F507} Muted",
|
|
179
181
|
stamp: "OK",
|
|
@@ -187,7 +189,7 @@ var en = {
|
|
|
187
189
|
bannerMulti: (n) => `${n} lines at once!`,
|
|
188
190
|
statusStart: () => "",
|
|
189
191
|
statusLines: (lines, left) => `${lines} bingo line${lines === 1 ? "" : "s"}! ${left} to go`,
|
|
190
|
-
statusReach: (
|
|
192
|
+
statusReach: () => "",
|
|
191
193
|
statusDone: "\u{1F389} Perfect! Nothing left behind.",
|
|
192
194
|
perfectTag: "\u{1F3C6} PERFECT",
|
|
193
195
|
perfectStamp: "ALL\nSET",
|
|
@@ -293,6 +295,11 @@ function createSound(isMuted) {
|
|
|
293
295
|
},
|
|
294
296
|
tick: () => tone(1760, 0, 0.03, "square", 0.03),
|
|
295
297
|
note: (k, at = 0) => tone(step(k), at, 0.12, "square", 0.05),
|
|
298
|
+
shuffle() {
|
|
299
|
+
for (let i = 0; i < 7; i++) noise(i * 0.042, 0.05, 0.16, 1600 + i * 260);
|
|
300
|
+
tone(hz(7), 0.34, 0.12, "triangle", 0.14);
|
|
301
|
+
tone(hz(12), 0.42, 0.2, "triangle", 0.14);
|
|
302
|
+
},
|
|
296
303
|
close() {
|
|
297
304
|
if (ac) void ac.close().catch(() => {
|
|
298
305
|
});
|
|
@@ -320,7 +327,6 @@ function itemsKey(items) {
|
|
|
320
327
|
}
|
|
321
328
|
|
|
322
329
|
// src/styles.ts
|
|
323
|
-
var FONT_URL = "https://fonts.googleapis.com/css2?family=Dela+Gothic+One&family=M+PLUS+Rounded+1c:wght@500;800&display=swap";
|
|
324
330
|
var CSS = `
|
|
325
331
|
.pkb{--pk-sun:var(--pkb-sun,#FFD54F);--pk-ink:var(--pkb-ink,#2A1F14);--pk-paper:var(--pkb-paper,#FFFDF6);--pk-red:var(--pkb-red,#E8473B);--pk-mint:var(--pkb-mint,#BDEBD2);--pk-display:"Dela Gothic One",var(--pkb-font-fallback,sans-serif);--pk-body:"M PLUS Rounded 1c","Hiragino Maru Gothic ProN",var(--pkb-font-fallback,sans-serif);--pk-n:4;--pk-s:1;--pk-d:1;container-type:inline-size;isolation:isolate;width:100%}
|
|
326
332
|
.pkb,.pkb *{box-sizing:border-box;margin:0;padding:0}
|
|
@@ -332,6 +338,7 @@ var CSS = `
|
|
|
332
338
|
.pkb-head{display:flex;flex-wrap:wrap;justify-content:space-between;align-items:center;gap:12px 16px;margin-bottom:18px}
|
|
333
339
|
.pkb-tags{display:flex;gap:8px;align-items:center}
|
|
334
340
|
.pkb-kicker{display:inline-block;background:var(--pk-ink);color:var(--pk-sun);font-weight:800;font-size:11px;letter-spacing:.14em;padding:4px 10px;border-radius:999px}
|
|
341
|
+
.pkb-kicker:empty{display:none}
|
|
335
342
|
.pkb-kicker.pkb-gold{color:#fff;background:linear-gradient(90deg,#E8473B,#FF9F1C,#1FA971,#2F6FED,#E8473B);background-size:200% 100%;animation:pkb-rb 2s linear infinite}
|
|
336
343
|
@keyframes pkb-rb{to{background-position:-200% 0}}
|
|
337
344
|
.pkb button{font-family:var(--pk-body)}
|
|
@@ -388,8 +395,13 @@ var CSS = `
|
|
|
388
395
|
.pkb-lines .pkb-o{stroke:var(--pk-ink);stroke-width:calc(6px*var(--pk-d))}
|
|
389
396
|
.pkb-lines .pkb-draw line{animation:pkb-draw .45s ease-out .3s both}
|
|
390
397
|
@keyframes pkb-draw{from{stroke-dashoffset:1}}
|
|
391
|
-
.pkb-foot{display:flex;justify-content:space-between;align-items:center;gap:10px;margin-top:18px;font-weight:800;font-size:13px;word-break:auto-phrase}
|
|
392
|
-
.pkb-
|
|
398
|
+
.pkb-foot{display:flex;flex-wrap:wrap;justify-content:space-between;align-items:center;gap:8px 10px;margin-top:18px;font-weight:800;font-size:13px;word-break:auto-phrase}
|
|
399
|
+
.pkb-foot>span{flex:1 1 14em}
|
|
400
|
+
.pkb-foot>span:empty{flex-basis:0}
|
|
401
|
+
.pkb-acts{display:flex;gap:8px;flex:none;margin-left:auto}
|
|
402
|
+
.pkb-reset,.pkb-shuf{appearance:none;flex:none;border:2px solid var(--pk-ink);background:transparent;border-radius:999px;padding:5px 12px;font-weight:800;font-size:12px;line-height:1.4;color:var(--pk-ink);cursor:pointer}
|
|
403
|
+
.pkb-shuf{background:var(--pk-paper);box-shadow:2px 2px 0 var(--pk-ink)}
|
|
404
|
+
.pkb-shuf:active{transform:translate(2px,2px);box-shadow:none}
|
|
393
405
|
.pkb-flash{position:absolute;inset:0;z-index:7;background:#fff;opacity:0;pointer-events:none}
|
|
394
406
|
.pkb-flash.pkb-go{animation:pkb-flash .4s ease-out}
|
|
395
407
|
@keyframes pkb-flash{0%{opacity:.8}100%{opacity:0}}
|
|
@@ -437,6 +449,7 @@ var CONFETTI = ["#E8473B", "#2F6FED", "#1FA971", "#FFFDF6", "#2A1F14", "#FF9F1C"
|
|
|
437
449
|
var COMBO_COLORS = ["#FF9F1C", "#1FA971", "#2F6FED", "#9B5DE5", "#E8473B"];
|
|
438
450
|
var COMBO_MS = 2500;
|
|
439
451
|
var MUTE_KEY = "packing-bingo-muted";
|
|
452
|
+
var ORDER_PREFIX = "packing-bingo-order:";
|
|
440
453
|
var esc = (s) => s.replace(/[&<>"']/g, (c) => ({ "&": "&", "<": "<", ">": ">", '"': """, "'": "'" })[c]);
|
|
441
454
|
function readStore(key) {
|
|
442
455
|
try {
|
|
@@ -451,29 +464,25 @@ function writeStore(key, value) {
|
|
|
451
464
|
} catch {
|
|
452
465
|
}
|
|
453
466
|
}
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
l.id = "pkb-fonts";
|
|
464
|
-
l.rel = "stylesheet";
|
|
465
|
-
l.href = FONT_URL;
|
|
466
|
-
document.head.appendChild(l);
|
|
467
|
-
}
|
|
467
|
+
var glintDelay = (c, n) => `${+((Math.floor(c / n) + c % n) * 0.36 / Math.max(1, n - 1)).toFixed(3)}s`;
|
|
468
|
+
var lineKey = (l) => l.slice().sort((a, b) => a - b).join(",");
|
|
469
|
+
var FLY_MS = 560;
|
|
470
|
+
function ensureStyles() {
|
|
471
|
+
if (document.getElementById("pkb-styles")) return;
|
|
472
|
+
const s = document.createElement("style");
|
|
473
|
+
s.id = "pkb-styles";
|
|
474
|
+
s.textContent = CSS;
|
|
475
|
+
document.head.appendChild(s);
|
|
468
476
|
}
|
|
469
|
-
function template(L, M, demo) {
|
|
477
|
+
function template(L, M, { demo, shuffle, vibrate }) {
|
|
470
478
|
const n = L.size;
|
|
479
|
+
const input = vibrate ? `<input type="checkbox" switch role="checkbox">` : `<input type="checkbox">`;
|
|
471
480
|
const tiles = Array.from({ length: n * n }, (_, c) => {
|
|
472
|
-
const gd = `--pkb-gd:${
|
|
481
|
+
const gd = `--pkb-gd:${glintDelay(c, n)}`;
|
|
473
482
|
if (c === L.center)
|
|
474
483
|
return `<div class="pkb-tile pkb-free pkb-on" style="${gd}"><span class="pkb-stamp">${esc(M.free)}</span></div>`;
|
|
475
484
|
const it = L.items[L.itemCell.indexOf(c)];
|
|
476
|
-
return `<label class="pkb-tile" data-reach="${esc(M.reachBadge)}" style="${gd}"
|
|
485
|
+
return `<label class="pkb-tile" data-reach="${esc(M.reachBadge)}" style="${gd}">${input}<span class="pkb-emo" aria-hidden="true">${esc(it.emoji)}</span><span class="pkb-lbl">${esc(it.label).replace(/・/g, "\u30FB<wbr>")}</span><span class="pkb-stamp" aria-hidden="true">${esc(M.stamp)}</span></label>`;
|
|
477
486
|
}).join("");
|
|
478
487
|
const scale = `--pk-n:${n};--pk-s:${+Math.min(1.35, 4 / n).toFixed(3)};--pk-d:${+Math.min(1, 4 / n).toFixed(3)}`;
|
|
479
488
|
const demoBar = demo ? `<div class="pkb-demo"><span>${esc(M.demoLabel)}</span><button type="button" data-r="dB">${esc(M.demoBingo)}</button><button type="button" data-r="dP">${esc(M.demoPerfect)}</button><button type="button" data-r="dR">${esc(M.demoRestore)}</button></div>` : "";
|
|
@@ -488,7 +497,7 @@ function template(L, M, demo) {
|
|
|
488
497
|
<div class="pkb-combo" data-r="combo"><b data-r="cmbN"></b><span class="pkb-bar"><i data-r="cbar"></i></span></div></div></div>
|
|
489
498
|
<div class="pkb-gw"><div class="pkb-grid">${tiles}</div><svg class="pkb-lines" aria-hidden="true"></svg>
|
|
490
499
|
<div class="pkb-banner" data-r="banner" aria-hidden="true"><b data-r="bT"></b><span data-r="bS"></span></div></div>
|
|
491
|
-
<div class="pkb-foot"><span data-r="msg" aria-live="polite"></span><button type="button" class="pkb-reset" data-r="reset">${esc(M.reset)}</button></div>
|
|
500
|
+
<div class="pkb-foot"><span data-r="msg" aria-live="polite"></span><div class="pkb-acts">${shuffle ? `<button type="button" class="pkb-shuf" data-r="shuf">${esc(M.shuffle)}</button>` : ""}<button type="button" class="pkb-reset" data-r="reset">${esc(M.reset)}</button></div></div>
|
|
492
501
|
<div class="pkb-flash" data-r="flash"></div>
|
|
493
502
|
<div class="pkb-perfect" data-r="perfect"><div class="pkb-card" role="dialog" aria-label="${esc(M.perfectTitle)}">
|
|
494
503
|
<div class="pkb-medal">${esc(M.perfectStamp)}</div><h3 class="pkb-ptitle">${esc(M.perfectTitle)}</h3><p class="pkb-pbody" data-r="pbody">${esc(perfectText(M, L.items.length))}</p>
|
|
@@ -499,12 +508,22 @@ function template(L, M, demo) {
|
|
|
499
508
|
var perfectText = (M, total) => typeof M.perfectBody === "function" ? M.perfectBody(total) : M.perfectBody;
|
|
500
509
|
function createPackingBingo(container, options = {}) {
|
|
501
510
|
const L = resolveLayout(options);
|
|
502
|
-
const { items,
|
|
511
|
+
const { items, cellLines } = L;
|
|
503
512
|
const n = L.size, N = items.length;
|
|
504
513
|
const normalize = (v) => Array.from({ length: N }, (_, i) => !!v?.[i]);
|
|
514
|
+
const identity = () => Array.from({ length: N }, (_, s) => s);
|
|
515
|
+
const isIdentity = (o) => o.every((v, s) => v === s);
|
|
516
|
+
const validOrder = (o) => Array.isArray(o) && o.length === N && o.every((v) => Number.isInteger(v) && v >= 0 && v < N) && new Set(o).size === N;
|
|
517
|
+
let order = identity();
|
|
518
|
+
const slotOf = identity();
|
|
519
|
+
let lines = L.lines;
|
|
505
520
|
const demoCells = [...cellLines[0], ...cellLines[2 * n].slice(1, -1)];
|
|
506
|
-
const
|
|
507
|
-
const demoStart = () =>
|
|
521
|
+
const demoSlots = L.itemCell.flatMap((c, s) => demoCells.includes(c) ? [s] : []);
|
|
522
|
+
const demoStart = () => {
|
|
523
|
+
const on = normalize(void 0);
|
|
524
|
+
demoSlots.forEach((s) => on[order[s]] = true);
|
|
525
|
+
return on;
|
|
526
|
+
};
|
|
508
527
|
const far = (c) => Math.floor(c / n) + c % n;
|
|
509
528
|
const dScale = Math.min(1, 4 / n);
|
|
510
529
|
const M = { ...ja, ...options.messages };
|
|
@@ -513,7 +532,9 @@ function createPackingBingo(container, options = {}) {
|
|
|
513
532
|
if (options.subtitle != null) M.subtitle = options.subtitle;
|
|
514
533
|
const controlled = !!options.controlled;
|
|
515
534
|
const storageKey = resolveStorageKey(options);
|
|
535
|
+
const orderKey = storageKey && ORDER_PREFIX + storageKey;
|
|
516
536
|
const demo = !!options.demo;
|
|
537
|
+
const vibrate = options.vibrate !== false;
|
|
517
538
|
const calm = typeof matchMedia === "function" && matchMedia("(prefers-reduced-motion: reduce)").matches;
|
|
518
539
|
let dead = false;
|
|
519
540
|
const timers = /* @__PURE__ */ new Set();
|
|
@@ -526,8 +547,8 @@ function createPackingBingo(container, options = {}) {
|
|
|
526
547
|
};
|
|
527
548
|
const wait = (ms) => new Promise((r) => window.setTimeout(r, ms));
|
|
528
549
|
let gen = 0;
|
|
529
|
-
|
|
530
|
-
container.innerHTML = template(L, M, demo);
|
|
550
|
+
ensureStyles();
|
|
551
|
+
container.innerHTML = template(L, M, { demo, shuffle: options.shuffle !== false, vibrate });
|
|
531
552
|
const $ = (r) => {
|
|
532
553
|
const el = container.querySelector(`[data-r="${r}"]`);
|
|
533
554
|
if (!el) throw new Error(`[packing-bingo] internal: missing element "${r}"`);
|
|
@@ -704,6 +725,7 @@ function createPackingBingo(container, options = {}) {
|
|
|
704
725
|
r.addEventListener("animationend", () => r.remove());
|
|
705
726
|
};
|
|
706
727
|
const buzz = (pattern) => {
|
|
728
|
+
if (!vibrate) return;
|
|
707
729
|
const ua = navigator.userActivation;
|
|
708
730
|
if (ua && !ua.hasBeenActive) return;
|
|
709
731
|
try {
|
|
@@ -762,7 +784,12 @@ function createPackingBingo(container, options = {}) {
|
|
|
762
784
|
const ctr = (t) => {
|
|
763
785
|
return { x: t.offsetLeft + t.offsetWidth / 2, y: t.offsetTop + t.offsetHeight / 2 };
|
|
764
786
|
};
|
|
787
|
+
let landing = 0;
|
|
765
788
|
function drawLines(fresh = []) {
|
|
789
|
+
if (landing) {
|
|
790
|
+
svg.innerHTML = "";
|
|
791
|
+
return;
|
|
792
|
+
}
|
|
766
793
|
svg.setAttribute("viewBox", `0 0 ${gw.clientWidth} ${gw.clientHeight}`);
|
|
767
794
|
svg.innerHTML = cur.map((k) => {
|
|
768
795
|
const a = ctr(cellTiles[cellLines[k][0]]), b = ctr(cellTiles[cellLines[k][n - 1]]);
|
|
@@ -777,6 +804,7 @@ function createPackingBingo(container, options = {}) {
|
|
|
777
804
|
combo = 0;
|
|
778
805
|
$("combo").classList.remove("pkb-on");
|
|
779
806
|
sfx.off();
|
|
807
|
+
buzz(8);
|
|
780
808
|
return;
|
|
781
809
|
}
|
|
782
810
|
const now = performance.now();
|
|
@@ -799,7 +827,7 @@ function createPackingBingo(container, options = {}) {
|
|
|
799
827
|
$("cmbN").textContent = M.combo(combo);
|
|
800
828
|
replay($("combo"), "pkb-on");
|
|
801
829
|
}
|
|
802
|
-
buzz(
|
|
830
|
+
buzz(18);
|
|
803
831
|
}
|
|
804
832
|
$("cbar").addEventListener("animationend", () => {
|
|
805
833
|
$("combo").classList.remove("pkb-on");
|
|
@@ -893,10 +921,11 @@ function createPackingBingo(container, options = {}) {
|
|
|
893
921
|
$("combo").classList.remove("pkb-on");
|
|
894
922
|
perfectEl.classList.remove("pkb-show");
|
|
895
923
|
}
|
|
924
|
+
const completeLines = () => lines.flatMap((l, k) => l.every((i) => checked[i]) ? [k] : []);
|
|
896
925
|
function render(src, quiet) {
|
|
897
926
|
const events = [];
|
|
898
927
|
const on = checked, c = on.filter(Boolean).length;
|
|
899
|
-
cur =
|
|
928
|
+
cur = completeLines();
|
|
900
929
|
const fresh = cur.filter((k) => !prevLines.has(k));
|
|
901
930
|
if ([...prevLines].some((k) => !cur.includes(k))) {
|
|
902
931
|
cancelFx();
|
|
@@ -910,7 +939,7 @@ function createPackingBingo(container, options = {}) {
|
|
|
910
939
|
}
|
|
911
940
|
tiles.forEach((t, i) => {
|
|
912
941
|
t.classList.toggle("pkb-on", on[i]);
|
|
913
|
-
t.classList.toggle("pkb-line", hit.has(L.itemCell[i]));
|
|
942
|
+
t.classList.toggle("pkb-line", hit.has(L.itemCell[slotOf[i]]));
|
|
914
943
|
t.classList.toggle("pkb-reach", reach.has(i));
|
|
915
944
|
});
|
|
916
945
|
freeTile?.classList.toggle("pkb-line", hit.has(L.center));
|
|
@@ -928,8 +957,8 @@ function createPackingBingo(container, options = {}) {
|
|
|
928
957
|
lastN = cur.length;
|
|
929
958
|
drawLines(fresh);
|
|
930
959
|
if (src !== null) tapFx(src, on[src]);
|
|
931
|
-
const newL = fresh.
|
|
932
|
-
newL.forEach((
|
|
960
|
+
const newL = fresh.map((k) => lineKey(lines[k])).filter((key) => !gotL.has(key));
|
|
961
|
+
newL.forEach((key) => gotL.add(key));
|
|
933
962
|
const linePts = 100 * newL.length * newL.length;
|
|
934
963
|
score += linePts;
|
|
935
964
|
if (quiet) {
|
|
@@ -974,8 +1003,9 @@ function createPackingBingo(container, options = {}) {
|
|
|
974
1003
|
got.clear();
|
|
975
1004
|
gotL.clear();
|
|
976
1005
|
checked.forEach((v, i) => v && got.add(i));
|
|
977
|
-
|
|
978
|
-
|
|
1006
|
+
const done = completeLines();
|
|
1007
|
+
done.forEach((k) => gotL.add(lineKey(lines[k])));
|
|
1008
|
+
prevLines = new Set(done);
|
|
979
1009
|
prevReach = /* @__PURE__ */ new Set();
|
|
980
1010
|
prevAll = checked.every(Boolean);
|
|
981
1011
|
gotP = prevAll;
|
|
@@ -998,6 +1028,77 @@ function createPackingBingo(container, options = {}) {
|
|
|
998
1028
|
options.onChange?.(checked.slice());
|
|
999
1029
|
flush(events);
|
|
1000
1030
|
}
|
|
1031
|
+
function place(next) {
|
|
1032
|
+
order = next;
|
|
1033
|
+
order.forEach((i, s) => {
|
|
1034
|
+
slotOf[i] = s;
|
|
1035
|
+
cellTiles[L.itemCell[s]] = tiles[i];
|
|
1036
|
+
});
|
|
1037
|
+
lines = L.lines.map((l) => l.map((s) => order[s]));
|
|
1038
|
+
const focused = document.activeElement;
|
|
1039
|
+
cellTiles.forEach((t, c) => {
|
|
1040
|
+
t.style.setProperty("--pkb-gd", glintDelay(c, n));
|
|
1041
|
+
grid.appendChild(t);
|
|
1042
|
+
});
|
|
1043
|
+
if (focused instanceof HTMLElement && grid.contains(focused) && document.activeElement !== focused)
|
|
1044
|
+
focused.focus({ preventScroll: true });
|
|
1045
|
+
}
|
|
1046
|
+
const saveOrder = () => {
|
|
1047
|
+
if (!orderKey) return;
|
|
1048
|
+
writeStore(orderKey, JSON.stringify(order));
|
|
1049
|
+
};
|
|
1050
|
+
let flights = [];
|
|
1051
|
+
function arrange(next) {
|
|
1052
|
+
if (!checked.every(Boolean)) cancelFx();
|
|
1053
|
+
const from = calm ? null : tiles.map((t) => t.getBoundingClientRect());
|
|
1054
|
+
flights.forEach((a) => a.cancel());
|
|
1055
|
+
flights = [];
|
|
1056
|
+
place(next);
|
|
1057
|
+
lines.forEach((l) => {
|
|
1058
|
+
if (l.every((i) => got.has(i))) gotL.add(lineKey(l));
|
|
1059
|
+
});
|
|
1060
|
+
const done = completeLines();
|
|
1061
|
+
done.forEach((k) => gotL.add(lineKey(lines[k])));
|
|
1062
|
+
prevLines = new Set(done);
|
|
1063
|
+
render(null, true);
|
|
1064
|
+
saveOrder();
|
|
1065
|
+
if (!from || typeof Element.prototype.animate !== "function") return;
|
|
1066
|
+
tiles.forEach((t, i) => {
|
|
1067
|
+
const a = from[i], b = t.getBoundingClientRect();
|
|
1068
|
+
const dx = a.left - b.left, dy = a.top - b.top;
|
|
1069
|
+
if (Math.abs(dx) + Math.abs(dy) < 1) return;
|
|
1070
|
+
flights.push(
|
|
1071
|
+
t.animate([{ transform: `translate(${dx}px,${dy}px)` }, { transform: "none" }], {
|
|
1072
|
+
duration: FLY_MS - 140,
|
|
1073
|
+
delay: Math.random() * 140,
|
|
1074
|
+
easing: "cubic-bezier(.3,1.25,.5,1)",
|
|
1075
|
+
fill: "backwards"
|
|
1076
|
+
})
|
|
1077
|
+
);
|
|
1078
|
+
});
|
|
1079
|
+
clearTimeout(landing);
|
|
1080
|
+
landing = window.setTimeout(() => {
|
|
1081
|
+
landing = 0;
|
|
1082
|
+
flights = [];
|
|
1083
|
+
if (!dead) drawLines(cur);
|
|
1084
|
+
}, FLY_MS);
|
|
1085
|
+
svg.innerHTML = "";
|
|
1086
|
+
}
|
|
1087
|
+
function shuffleCard() {
|
|
1088
|
+
let next = order.slice();
|
|
1089
|
+
do {
|
|
1090
|
+
for (let s = N - 1; s > 0; s--) {
|
|
1091
|
+
const r = Math.floor(Math.random() * (s + 1));
|
|
1092
|
+
[next[s], next[r]] = [next[r], next[s]];
|
|
1093
|
+
}
|
|
1094
|
+
} while (next.every((v, s) => v === order[s]));
|
|
1095
|
+
arrange(next);
|
|
1096
|
+
sfx.shuffle();
|
|
1097
|
+
buzz([10, 40, 10, 40, 10, 40, 24]);
|
|
1098
|
+
}
|
|
1099
|
+
const unshuffle = () => {
|
|
1100
|
+
if (!isIdentity(order)) arrange(identity());
|
|
1101
|
+
};
|
|
1001
1102
|
grid.addEventListener("change", (e) => {
|
|
1002
1103
|
const i = ins.indexOf(e.target);
|
|
1003
1104
|
if (i < 0) return;
|
|
@@ -1005,7 +1106,11 @@ function createPackingBingo(container, options = {}) {
|
|
|
1005
1106
|
next[i] = !next[i];
|
|
1006
1107
|
request(next, i);
|
|
1007
1108
|
});
|
|
1008
|
-
|
|
1109
|
+
container.querySelector('[data-r="shuf"]')?.addEventListener("click", shuffleCard);
|
|
1110
|
+
$("reset").addEventListener("click", () => {
|
|
1111
|
+
unshuffle();
|
|
1112
|
+
request(normalize(void 0), null, { reset: true });
|
|
1113
|
+
});
|
|
1009
1114
|
$("close").addEventListener("click", () => perfectEl.classList.remove("pkb-show"));
|
|
1010
1115
|
perfectEl.addEventListener("click", (e) => {
|
|
1011
1116
|
if (e.target === perfectEl) perfectEl.classList.remove("pkb-show");
|
|
@@ -1044,9 +1149,21 @@ function createPackingBingo(container, options = {}) {
|
|
|
1044
1149
|
void run(miss.map((i, k) => k < miss.length - 1 ? { i, delay: 170, quiet: true } : { i, delay: 450 }));
|
|
1045
1150
|
});
|
|
1046
1151
|
$("dR").addEventListener("click", () => {
|
|
1047
|
-
if (
|
|
1152
|
+
if (busy) return;
|
|
1153
|
+
unshuffle();
|
|
1154
|
+
request(demoStart(), null, { reset: true });
|
|
1048
1155
|
});
|
|
1049
1156
|
}
|
|
1157
|
+
let startOrder = null;
|
|
1158
|
+
if (orderKey) {
|
|
1159
|
+
try {
|
|
1160
|
+
const saved = JSON.parse(readStore(orderKey) ?? "null");
|
|
1161
|
+
if (validOrder(saved)) startOrder = saved;
|
|
1162
|
+
} catch {
|
|
1163
|
+
}
|
|
1164
|
+
}
|
|
1165
|
+
if (!startOrder && validOrder(options.defaultOrder)) startOrder = options.defaultOrder.slice();
|
|
1166
|
+
if (startOrder && !isIdentity(startOrder)) place(startOrder);
|
|
1050
1167
|
let initial = null;
|
|
1051
1168
|
if (storageKey) {
|
|
1052
1169
|
try {
|
|
@@ -1078,19 +1195,35 @@ function createPackingBingo(container, options = {}) {
|
|
|
1078
1195
|
flush(apply(n2, changed[0], false));
|
|
1079
1196
|
},
|
|
1080
1197
|
reset() {
|
|
1081
|
-
if (
|
|
1198
|
+
if (dead) return;
|
|
1199
|
+
unshuffle();
|
|
1200
|
+
request(normalize(void 0), null, { reset: true });
|
|
1201
|
+
},
|
|
1202
|
+
shuffle() {
|
|
1203
|
+
if (!dead) shuffleCard();
|
|
1082
1204
|
},
|
|
1083
1205
|
refresh() {
|
|
1084
1206
|
if (!dead) render(null, true);
|
|
1085
1207
|
},
|
|
1086
1208
|
getState() {
|
|
1087
|
-
return {
|
|
1209
|
+
return {
|
|
1210
|
+
checked: checked.slice(),
|
|
1211
|
+
lines: cur.slice(),
|
|
1212
|
+
score,
|
|
1213
|
+
maxCombo,
|
|
1214
|
+
complete: checked.every(Boolean),
|
|
1215
|
+
size: n,
|
|
1216
|
+
free: L.free,
|
|
1217
|
+
order: order.slice()
|
|
1218
|
+
};
|
|
1088
1219
|
},
|
|
1089
1220
|
destroy() {
|
|
1090
1221
|
if (dead) return;
|
|
1091
1222
|
dead = true;
|
|
1092
1223
|
timers.forEach((id) => clearTimeout(id));
|
|
1093
1224
|
timers.clear();
|
|
1225
|
+
clearTimeout(landing);
|
|
1226
|
+
flights.forEach((a) => a.cancel());
|
|
1094
1227
|
cancelAnimationFrame(raf);
|
|
1095
1228
|
cancelAnimationFrame(tw);
|
|
1096
1229
|
ro?.disconnect();
|
|
@@ -1103,7 +1236,7 @@ function createPackingBingo(container, options = {}) {
|
|
|
1103
1236
|
// src/react.tsx
|
|
1104
1237
|
var import_jsx_runtime = require("react/jsx-runtime");
|
|
1105
1238
|
var PackingBingo = (0, import_react.forwardRef)(function PackingBingo2(props, ref) {
|
|
1106
|
-
const { checked, defaultChecked, className, style, onChange, onBingo, onPerfect, messages, ...config } = props;
|
|
1239
|
+
const { checked, defaultChecked, defaultOrder, className, style, onChange, onBingo, onPerfect, messages, fonts: _fonts, ...config } = props;
|
|
1107
1240
|
const host = (0, import_react.useRef)(null);
|
|
1108
1241
|
const inst = (0, import_react.useRef)(null);
|
|
1109
1242
|
const callbacks = (0, import_react.useRef)({ onChange, onBingo, onPerfect });
|
|
@@ -1115,6 +1248,7 @@ var PackingBingo = (0, import_react.forwardRef)(function PackingBingo2(props, re
|
|
|
1115
1248
|
const storageKey = resolveStorageKey({ ...config, controlled });
|
|
1116
1249
|
const latestKey = (0, import_react.useRef)(storageKey);
|
|
1117
1250
|
if (controlled) latest.current = checked;
|
|
1251
|
+
const latestOrder = (0, import_react.useRef)(defaultOrder);
|
|
1118
1252
|
const messageShape = messages ? Object.fromEntries(Object.entries(messages).map(([k, v]) => [k, typeof v === "function" ? "\u0192" : v])) : null;
|
|
1119
1253
|
const key = JSON.stringify({ ...config, messageShape, controlled });
|
|
1120
1254
|
(0, import_react.useEffect)(() => {
|
|
@@ -1130,6 +1264,7 @@ var PackingBingo = (0, import_react.forwardRef)(function PackingBingo2(props, re
|
|
|
1130
1264
|
messages: liveMessages,
|
|
1131
1265
|
controlled,
|
|
1132
1266
|
defaultChecked: controlled || latestKey.current === storageKey ? latest.current : defaultChecked,
|
|
1267
|
+
defaultOrder: latestKey.current === storageKey ? latestOrder.current : defaultOrder,
|
|
1133
1268
|
onChange: (next) => {
|
|
1134
1269
|
if (!controlled) latest.current = next;
|
|
1135
1270
|
callbacks.current.onChange?.(next);
|
|
@@ -1139,7 +1274,9 @@ var PackingBingo = (0, import_react.forwardRef)(function PackingBingo2(props, re
|
|
|
1139
1274
|
});
|
|
1140
1275
|
inst.current = instance;
|
|
1141
1276
|
return () => {
|
|
1142
|
-
|
|
1277
|
+
const state = instance.getState();
|
|
1278
|
+
if (!controlled) latest.current = state.checked;
|
|
1279
|
+
latestOrder.current = state.order;
|
|
1143
1280
|
latestKey.current = storageKey;
|
|
1144
1281
|
instance.destroy();
|
|
1145
1282
|
if (inst.current === instance) inst.current = null;
|
|
@@ -1155,6 +1292,7 @@ var PackingBingo = (0, import_react.forwardRef)(function PackingBingo2(props, re
|
|
|
1155
1292
|
ref,
|
|
1156
1293
|
() => ({
|
|
1157
1294
|
reset: () => inst.current?.reset(),
|
|
1295
|
+
shuffle: () => inst.current?.shuffle(),
|
|
1158
1296
|
setChecked: (next) => inst.current?.setChecked(next, { animate: true }),
|
|
1159
1297
|
getState: () => inst.current?.getState() ?? null
|
|
1160
1298
|
}),
|
package/dist/index.d.cts
CHANGED
|
@@ -12,8 +12,10 @@ interface PackingBingoProps extends Omit<PackingBingoOptions, "controlled" | "de
|
|
|
12
12
|
style?: CSSProperties;
|
|
13
13
|
}
|
|
14
14
|
interface PackingBingoHandle {
|
|
15
|
-
/** Clear the card and score (calls onChange). */
|
|
15
|
+
/** Clear the card and score (calls onChange), and put the items back in their own squares. */
|
|
16
16
|
reset(): void;
|
|
17
|
+
/** Move the items to random squares, as the 🔀 button does. Checks stay with their items. */
|
|
18
|
+
shuffle(): void;
|
|
17
19
|
/** Set the card. A one-item change plays like a tap; bigger jumps apply quietly. */
|
|
18
20
|
setChecked(checked: readonly boolean[]): void;
|
|
19
21
|
getState(): PackingBingoState | null;
|
package/dist/index.d.ts
CHANGED
|
@@ -12,8 +12,10 @@ interface PackingBingoProps extends Omit<PackingBingoOptions, "controlled" | "de
|
|
|
12
12
|
style?: CSSProperties;
|
|
13
13
|
}
|
|
14
14
|
interface PackingBingoHandle {
|
|
15
|
-
/** Clear the card and score (calls onChange). */
|
|
15
|
+
/** Clear the card and score (calls onChange), and put the items back in their own squares. */
|
|
16
16
|
reset(): void;
|
|
17
|
+
/** Move the items to random squares, as the 🔀 button does. Checks stay with their items. */
|
|
18
|
+
shuffle(): void;
|
|
17
19
|
/** Set the card. A one-item change plays like a tap; bigger jumps apply quietly. */
|
|
18
20
|
setChecked(checked: readonly boolean[]): void;
|
|
19
21
|
getState(): PackingBingoState | null;
|
package/dist/index.js
CHANGED
|
@@ -7,13 +7,13 @@ import {
|
|
|
7
7
|
en,
|
|
8
8
|
ja,
|
|
9
9
|
resolveStorageKey
|
|
10
|
-
} from "./chunk-
|
|
10
|
+
} from "./chunk-BAA2CFUA.js";
|
|
11
11
|
|
|
12
12
|
// src/react.tsx
|
|
13
13
|
import { forwardRef, useEffect, useImperativeHandle, useRef } from "react";
|
|
14
14
|
import { jsx } from "react/jsx-runtime";
|
|
15
15
|
var PackingBingo = forwardRef(function PackingBingo2(props, ref) {
|
|
16
|
-
const { checked, defaultChecked, className, style, onChange, onBingo, onPerfect, messages, ...config } = props;
|
|
16
|
+
const { checked, defaultChecked, defaultOrder, className, style, onChange, onBingo, onPerfect, messages, fonts: _fonts, ...config } = props;
|
|
17
17
|
const host = useRef(null);
|
|
18
18
|
const inst = useRef(null);
|
|
19
19
|
const callbacks = useRef({ onChange, onBingo, onPerfect });
|
|
@@ -25,6 +25,7 @@ var PackingBingo = forwardRef(function PackingBingo2(props, ref) {
|
|
|
25
25
|
const storageKey = resolveStorageKey({ ...config, controlled });
|
|
26
26
|
const latestKey = useRef(storageKey);
|
|
27
27
|
if (controlled) latest.current = checked;
|
|
28
|
+
const latestOrder = useRef(defaultOrder);
|
|
28
29
|
const messageShape = messages ? Object.fromEntries(Object.entries(messages).map(([k, v]) => [k, typeof v === "function" ? "\u0192" : v])) : null;
|
|
29
30
|
const key = JSON.stringify({ ...config, messageShape, controlled });
|
|
30
31
|
useEffect(() => {
|
|
@@ -40,6 +41,7 @@ var PackingBingo = forwardRef(function PackingBingo2(props, ref) {
|
|
|
40
41
|
messages: liveMessages,
|
|
41
42
|
controlled,
|
|
42
43
|
defaultChecked: controlled || latestKey.current === storageKey ? latest.current : defaultChecked,
|
|
44
|
+
defaultOrder: latestKey.current === storageKey ? latestOrder.current : defaultOrder,
|
|
43
45
|
onChange: (next) => {
|
|
44
46
|
if (!controlled) latest.current = next;
|
|
45
47
|
callbacks.current.onChange?.(next);
|
|
@@ -49,7 +51,9 @@ var PackingBingo = forwardRef(function PackingBingo2(props, ref) {
|
|
|
49
51
|
});
|
|
50
52
|
inst.current = instance;
|
|
51
53
|
return () => {
|
|
52
|
-
|
|
54
|
+
const state = instance.getState();
|
|
55
|
+
if (!controlled) latest.current = state.checked;
|
|
56
|
+
latestOrder.current = state.order;
|
|
53
57
|
latestKey.current = storageKey;
|
|
54
58
|
instance.destroy();
|
|
55
59
|
if (inst.current === instance) inst.current = null;
|
|
@@ -65,6 +69,7 @@ var PackingBingo = forwardRef(function PackingBingo2(props, ref) {
|
|
|
65
69
|
ref,
|
|
66
70
|
() => ({
|
|
67
71
|
reset: () => inst.current?.reset(),
|
|
72
|
+
shuffle: () => inst.current?.shuffle(),
|
|
68
73
|
setChecked: (next) => inst.current?.setChecked(next, { animate: true }),
|
|
69
74
|
getState: () => inst.current?.getState() ?? null
|
|
70
75
|
}),
|