@autono/pinbox-toolbar 0.8.0 → 0.10.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.
@@ -287,6 +287,448 @@ function pinsToMarkdown(pins, threads) {
287
287
  return `${open.map((p) => block(p, threads.get(p.id) ?? [])).join("\n\n")}\n`;
288
288
  }
289
289
  //#endregion
290
+ //#region src/motion/spring.ts
291
+ /** Bar ⇄ puck morphs and the post-drag settle. */
292
+ const MORPH_SPRING = {
293
+ k: 300,
294
+ c: 30
295
+ };
296
+ /** The morph surface chasing the pointer mid-drag. */
297
+ const FOLLOW_SPRING = {
298
+ k: 600,
299
+ c: 38
300
+ };
301
+ /** Integration substep — small enough that MORPH/FOLLOW stay stable at any dt. */
302
+ const SUBSTEP = 1 / 240;
303
+ /** A property this close to target, moving this slowly, counts as settled. */
304
+ const SETTLE = .5;
305
+ function mkSpring(init) {
306
+ const cur = { ...init };
307
+ const tgt = { ...init };
308
+ const keys = Object.keys(init);
309
+ const vel = {};
310
+ for (const key of keys) vel[key] = 0;
311
+ let cfg = { ...MORPH_SPRING };
312
+ const curN = cur;
313
+ const tgtN = tgt;
314
+ function integrate(h) {
315
+ for (const key of keys) {
316
+ const x = curN[key] ?? 0;
317
+ const v = vel[key] ?? 0;
318
+ const nextV = v + (-cfg.k * (x - (tgtN[key] ?? 0)) - cfg.c * v) * h;
319
+ vel[key] = nextV;
320
+ curN[key] = x + nextV * h;
321
+ }
322
+ }
323
+ return {
324
+ cur,
325
+ tgt,
326
+ snap(values) {
327
+ Object.assign(cur, values);
328
+ Object.assign(tgt, values);
329
+ for (const key of Object.keys(vel)) vel[key] = 0;
330
+ },
331
+ to(values, nextCfg) {
332
+ Object.assign(tgt, values);
333
+ if (nextCfg) cfg = { ...nextCfg };
334
+ },
335
+ step(dt) {
336
+ let remaining = dt;
337
+ while (remaining > 1e-6) {
338
+ const h = Math.min(SUBSTEP, remaining);
339
+ remaining -= h;
340
+ integrate(h);
341
+ }
342
+ for (const key of keys) {
343
+ const v = vel[key] ?? 0;
344
+ const gap = (curN[key] ?? 0) - (tgtN[key] ?? 0);
345
+ if (Math.abs(v) > SETTLE || Math.abs(gap) > SETTLE) return false;
346
+ }
347
+ this.snap({ ...tgt });
348
+ return true;
349
+ }
350
+ };
351
+ }
352
+ //#endregion
353
+ //#region src/minimize.ts
354
+ const PUCK = 48;
355
+ const MARGIN = 16;
356
+ /** Movement that begins a drag… */
357
+ const DRAG_START = 8;
358
+ /** …but a release under this much TOTAL travel is still a tap. */
359
+ const TAP_MAX = 12;
360
+ /** Spring hold while the surface swap crossfades (ms). */
361
+ const HOLD_MINIMIZE = 90;
362
+ const HOLD_RESTORE = 70;
363
+ /** Arrival: real element fades in first, morph layer fades this much later. */
364
+ const SWAP_FADE = 100;
365
+ /** …and is display:none'd once its own fade has finished. */
366
+ const LAYER_HIDE = 140;
367
+ const BAR_RADIUS = 4;
368
+ /** The carrier icon rides the surface only while it is puck-like (< this width). */
369
+ const CARRIER_MAX_W = 260;
370
+ function createMinimize(host) {
371
+ const { win, bar, ui } = host;
372
+ const main = mkSpring({
373
+ x: 0,
374
+ y: 0,
375
+ w: 0,
376
+ h: 0,
377
+ r: 0
378
+ });
379
+ let mode = "bar";
380
+ let puckPos = null;
381
+ let dock = loadDock();
382
+ let pdown = null;
383
+ let keyboardToggle = false;
384
+ let raf = 0;
385
+ let last = 0;
386
+ let holdTimer = 0;
387
+ let fadeTimer = 0;
388
+ let hideTimer = 0;
389
+ function loadDock() {
390
+ try {
391
+ const raw = host.storage?.getItem(`${host.storagePrefix}:dock`);
392
+ if (raw == null) return null;
393
+ const parsed = JSON.parse(raw);
394
+ if (typeof parsed.x !== "number" || typeof parsed.y !== "number") return null;
395
+ return {
396
+ x: parsed.x,
397
+ y: parsed.y
398
+ };
399
+ } catch {
400
+ return null;
401
+ }
402
+ }
403
+ function persist() {
404
+ try {
405
+ if (dock) host.storage?.setItem(`${host.storagePrefix}:dock`, JSON.stringify(dock));
406
+ host.storage?.setItem(`${host.storagePrefix}:minimized`, mode === "bar" ? "0" : "1");
407
+ } catch {}
408
+ }
409
+ function loadMinimized() {
410
+ try {
411
+ const raw = host.storage?.getItem(`${host.storagePrefix}:minimized`);
412
+ return raw == null ? host.initialMinimized : raw === "1";
413
+ } catch {
414
+ return host.initialMinimized;
415
+ }
416
+ }
417
+ const clamp = (v, lo, hi) => Math.min(Math.max(v, lo), hi);
418
+ function clampPos(p) {
419
+ return {
420
+ x: clamp(p.x, MARGIN, win.innerWidth - MARGIN - PUCK),
421
+ y: clamp(p.y, MARGIN, win.innerHeight - MARGIN - PUCK)
422
+ };
423
+ }
424
+ function defaultDock() {
425
+ const r = bar.getBoundingClientRect();
426
+ return {
427
+ x: r.left + r.width / 2 - PUCK / 2,
428
+ y: r.top + (r.height - PUCK) / 2
429
+ };
430
+ }
431
+ function placePuck(x, y) {
432
+ puckPos = {
433
+ x,
434
+ y
435
+ };
436
+ ui.puck.style.transform = `translate(${x}px, ${y}px)`;
437
+ }
438
+ function render() {
439
+ const m = main.cur;
440
+ ui.surface.style.width = `${m.w}px`;
441
+ ui.surface.style.height = `${m.h}px`;
442
+ ui.surface.style.borderRadius = `${m.r}px`;
443
+ ui.surface.style.transform = `translate(${m.x}px, ${m.y}px)`;
444
+ ui.carrier.style.transform = `translate(${m.x + m.w / 2 - PUCK / 2}px, ${m.y + m.h / 2 - PUCK / 2}px)`;
445
+ const iconOn = mode === "drag" || mode === "settle" || (mode === "toPuck" || mode === "toBar") && m.w < CARRIER_MAX_W;
446
+ ui.carrier.classList.toggle("show", iconOn);
447
+ }
448
+ function showMorph() {
449
+ win.clearTimeout(hideTimer);
450
+ win.clearTimeout(fadeTimer);
451
+ ui.morphWrap.hidden = false;
452
+ ui.morphWrap.offsetWidth;
453
+ ui.morphWrap.classList.add("on");
454
+ }
455
+ function hideMorph() {
456
+ ui.morphWrap.classList.remove("on");
457
+ ui.carrier.classList.remove("show");
458
+ win.clearTimeout(hideTimer);
459
+ hideTimer = win.setTimeout(() => {
460
+ ui.morphWrap.hidden = true;
461
+ }, LAYER_HIDE);
462
+ }
463
+ function tick(now) {
464
+ const dt = Math.min((now - last) / 1e3, 1 / 30);
465
+ last = now;
466
+ const done = main.step(dt);
467
+ render();
468
+ if (done && mode !== "drag") {
469
+ raf = 0;
470
+ if (mode === "toPuck" || mode === "settle") finishPuck();
471
+ else if (mode === "toBar") finishBar();
472
+ } else raf = win.requestAnimationFrame(tick);
473
+ }
474
+ function ensureLoop() {
475
+ if (raf === 0) {
476
+ last = win.performance.now();
477
+ raf = win.requestAnimationFrame(tick);
478
+ }
479
+ }
480
+ function finishPuck() {
481
+ placePuck(main.cur.x, main.cur.y);
482
+ ui.puck.classList.remove("pb-ghost");
483
+ win.clearTimeout(fadeTimer);
484
+ fadeTimer = win.setTimeout(hideMorph, SWAP_FADE);
485
+ mode = "puck";
486
+ persist();
487
+ host.onSettled(true, keyboardToggle);
488
+ }
489
+ function finishBar() {
490
+ bar.classList.remove("pb-ghost");
491
+ win.clearTimeout(fadeTimer);
492
+ fadeTimer = win.setTimeout(hideMorph, SWAP_FADE);
493
+ mode = "bar";
494
+ persist();
495
+ host.onSettled(false, keyboardToggle);
496
+ }
497
+ function minimize(keyboard = false) {
498
+ if (mode !== "bar") return;
499
+ pdown = null;
500
+ keyboardToggle = keyboard;
501
+ const r = bar.getBoundingClientRect();
502
+ const d = clampPos(dock ?? defaultDock());
503
+ bar.classList.add("pb-ghost");
504
+ if (host.reduced) {
505
+ placePuck(d.x, d.y);
506
+ ui.puck.classList.remove("pb-ghost");
507
+ mode = "puck";
508
+ persist();
509
+ host.onSettled(true, keyboard);
510
+ return;
511
+ }
512
+ main.snap({
513
+ x: r.left,
514
+ y: r.top,
515
+ w: r.width,
516
+ h: r.height,
517
+ r: BAR_RADIUS
518
+ });
519
+ mode = "toPuck";
520
+ render();
521
+ showMorph();
522
+ win.clearTimeout(holdTimer);
523
+ holdTimer = win.setTimeout(() => {
524
+ main.to({
525
+ x: d.x,
526
+ y: d.y,
527
+ w: PUCK,
528
+ h: PUCK,
529
+ r: PUCK / 2
530
+ }, MORPH_SPRING);
531
+ ensureLoop();
532
+ }, HOLD_MINIMIZE);
533
+ }
534
+ function restore(keyboard = false) {
535
+ if (mode !== "puck" || puckPos === null) return;
536
+ pdown = null;
537
+ keyboardToggle = keyboard;
538
+ dock = { ...puckPos };
539
+ ui.puck.classList.add("pb-ghost");
540
+ if (host.reduced) {
541
+ bar.classList.remove("pb-ghost");
542
+ mode = "bar";
543
+ persist();
544
+ host.onSettled(false, keyboard);
545
+ return;
546
+ }
547
+ const r = bar.getBoundingClientRect();
548
+ main.snap({
549
+ x: puckPos.x,
550
+ y: puckPos.y,
551
+ w: PUCK,
552
+ h: PUCK,
553
+ r: PUCK / 2
554
+ });
555
+ mode = "toBar";
556
+ render();
557
+ showMorph();
558
+ win.clearTimeout(holdTimer);
559
+ holdTimer = win.setTimeout(() => {
560
+ main.to({
561
+ x: r.left,
562
+ y: r.top,
563
+ w: r.width,
564
+ h: r.height,
565
+ r: BAR_RADIUS
566
+ }, MORPH_SPRING);
567
+ ensureLoop();
568
+ }, HOLD_RESTORE);
569
+ }
570
+ function onPointerDown(e) {
571
+ if (mode !== "puck" || e.button !== 0) return;
572
+ try {
573
+ ui.puck.setPointerCapture(e.pointerId);
574
+ } catch {}
575
+ if (puckPos === null) return;
576
+ pdown = {
577
+ px: e.clientX,
578
+ py: e.clientY,
579
+ ox: puckPos.x,
580
+ oy: puckPos.y,
581
+ lx: e.clientX,
582
+ ly: e.clientY,
583
+ moved: false
584
+ };
585
+ }
586
+ function onPointerMove(e) {
587
+ if (pdown === null) return;
588
+ pdown.lx = e.clientX;
589
+ pdown.ly = e.clientY;
590
+ const dx = e.clientX - pdown.px;
591
+ const dy = e.clientY - pdown.py;
592
+ if (!pdown.moved) {
593
+ if (mode !== "puck") {
594
+ pdown = null;
595
+ return;
596
+ }
597
+ if (Math.hypot(dx, dy) < DRAG_START) return;
598
+ pdown.moved = true;
599
+ mode = "drag";
600
+ if (!host.reduced) {
601
+ ui.puck.classList.add("pb-ghost");
602
+ main.snap({
603
+ x: pdown.ox,
604
+ y: pdown.oy,
605
+ w: PUCK,
606
+ h: PUCK,
607
+ r: PUCK / 2
608
+ });
609
+ render();
610
+ showMorph();
611
+ }
612
+ }
613
+ const next = {
614
+ x: pdown.ox + dx,
615
+ y: pdown.oy + dy
616
+ };
617
+ if (host.reduced) {
618
+ const p = clampPos(next);
619
+ placePuck(p.x, p.y);
620
+ } else {
621
+ main.to({
622
+ ...next,
623
+ w: PUCK,
624
+ h: PUCK,
625
+ r: PUCK / 2
626
+ }, FOLLOW_SPRING);
627
+ ensureLoop();
628
+ }
629
+ }
630
+ function onPointerUp() {
631
+ if (pdown === null) return;
632
+ const total = Math.hypot(pdown.lx - pdown.px, pdown.ly - pdown.py);
633
+ const wasDrag = pdown.moved && total >= TAP_MAX;
634
+ const origin = {
635
+ x: pdown.ox,
636
+ y: pdown.oy
637
+ };
638
+ const startedDrag = pdown.moved;
639
+ pdown = null;
640
+ if (!wasDrag) {
641
+ if (startedDrag && mode === "drag") {
642
+ main.snap({
643
+ x: origin.x,
644
+ y: origin.y,
645
+ w: PUCK,
646
+ h: PUCK,
647
+ r: PUCK / 2
648
+ });
649
+ placePuck(origin.x, origin.y);
650
+ ui.puck.classList.remove("pb-ghost");
651
+ hideMorph();
652
+ mode = "puck";
653
+ }
654
+ restore(false);
655
+ return;
656
+ }
657
+ if (host.reduced) {
658
+ if (puckPos) {
659
+ const p = clampPos(puckPos);
660
+ placePuck(p.x, p.y);
661
+ dock = { ...p };
662
+ }
663
+ mode = "puck";
664
+ persist();
665
+ return;
666
+ }
667
+ const p = clampPos({
668
+ x: main.tgt.x,
669
+ y: main.tgt.y
670
+ });
671
+ main.to({
672
+ ...p,
673
+ w: PUCK,
674
+ h: PUCK,
675
+ r: PUCK / 2
676
+ }, MORPH_SPRING);
677
+ mode = "settle";
678
+ ensureLoop();
679
+ }
680
+ /** Keyboard/AT activation is a synthesized click (detail 0) with no pointer events. */
681
+ function onClick(e) {
682
+ if (e.detail === 0) restore(true);
683
+ }
684
+ function onResize() {
685
+ if (mode === "puck" && puckPos !== null) {
686
+ const p = clampPos(puckPos);
687
+ placePuck(p.x, p.y);
688
+ }
689
+ if (dock !== null) dock = clampPos(dock);
690
+ }
691
+ ui.puck.addEventListener("pointerdown", onPointerDown);
692
+ ui.puck.addEventListener("pointermove", onPointerMove);
693
+ ui.puck.addEventListener("pointerup", onPointerUp);
694
+ ui.puck.addEventListener("pointercancel", onPointerUp);
695
+ ui.puck.addEventListener("click", onClick);
696
+ win.addEventListener("resize", onResize);
697
+ return {
698
+ minimized: () => mode !== "bar",
699
+ mode: () => mode,
700
+ minimize,
701
+ restore,
702
+ applyInitial() {
703
+ if (!loadMinimized()) return;
704
+ const apply = () => {
705
+ if (mode !== "bar") return;
706
+ const d = clampPos(dock ?? defaultDock());
707
+ bar.classList.add("pb-ghost");
708
+ placePuck(d.x, d.y);
709
+ ui.puck.classList.remove("pb-ghost");
710
+ mode = "puck";
711
+ host.onSettled(true, false);
712
+ };
713
+ if (host.reduced) apply();
714
+ else win.requestAnimationFrame(apply);
715
+ },
716
+ destroy() {
717
+ ui.puck.removeEventListener("pointerdown", onPointerDown);
718
+ ui.puck.removeEventListener("pointermove", onPointerMove);
719
+ ui.puck.removeEventListener("pointerup", onPointerUp);
720
+ ui.puck.removeEventListener("pointercancel", onPointerUp);
721
+ ui.puck.removeEventListener("click", onClick);
722
+ win.removeEventListener("resize", onResize);
723
+ if (raf !== 0) win.cancelAnimationFrame(raf);
724
+ raf = 0;
725
+ win.clearTimeout(holdTimer);
726
+ win.clearTimeout(fadeTimer);
727
+ win.clearTimeout(hideTimer);
728
+ }
729
+ };
730
+ }
731
+ //#endregion
290
732
  //#region src/screenshot.ts
291
733
  const WEBP_QUALITY = .7;
292
734
  const PLACEHOLDER_MAX = 32;
@@ -418,7 +860,8 @@ function initialState() {
418
860
  activePinId: null,
419
861
  inboxOpen: false,
420
862
  connection: "connecting",
421
- queuedIds: /* @__PURE__ */ new Set()
863
+ queuedIds: /* @__PURE__ */ new Set(),
864
+ minimized: false
422
865
  };
423
866
  }
424
867
  function createStore() {
@@ -1113,6 +1556,7 @@ const INBOX_ICON = "<svg width=\"14\" height=\"14\" viewBox=\"0 0 16 16\" fill=\
1113
1556
  const THEME_ICON = "<svg width=\"14\" height=\"14\" viewBox=\"0 0 16 16\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.4\"><path d=\"M8 1.6a6.4 6.4 0 100 12.8A5 5 0 018 1.6z\"/></svg>";
1114
1557
  const COPY_ICON = "<svg width=\"14\" height=\"14\" viewBox=\"0 0 16 16\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.4\"><rect x=\"5.5\" y=\"5.5\" width=\"8\" height=\"8\" rx=\"1\"/><path d=\"M10.5 3.5v-1a1 1 0 00-1-1h-6a1 1 0 00-1 1v6a1 1 0 001 1h1\"/></svg>";
1115
1558
  const IDENT_ICON = "<svg width=\"15\" height=\"15\" viewBox=\"0 0 16 16\" fill=\"none\" stroke=\"var(--pb-amber)\" stroke-width=\"1.4\"><rect x=\"2.5\" y=\"1.5\" width=\"11\" height=\"7\" rx=\"1\"/><path d=\"M8 8.5v6\"/><circle cx=\"8\" cy=\"14.6\" r=\".9\" fill=\"var(--pb-amber)\" stroke=\"none\"/></svg>";
1559
+ const MIN_ICON = "<svg width=\"14\" height=\"14\" viewBox=\"0 0 16 16\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.4\"><path d=\"M6.5 2.5v4h-4\"/><path d=\"M9.5 13.5v-4h4\"/></svg>";
1116
1560
  const CONNECTION_LABEL = {
1117
1561
  connecting: "PINBOX",
1118
1562
  live: "PINBOX",
@@ -1122,7 +1566,7 @@ const CONNECTION_LABEL = {
1122
1566
  function createBar(doc, on) {
1123
1567
  const root = doc.createElement("div");
1124
1568
  root.className = "pb-bar";
1125
- root.innerHTML = `<div class="armed-ring"></div><div class="ident">${IDENT_ICON}<span class="bl" data-ref="label">PINBOX</span></div><div class="div"></div><button type="button" class="pb-tb" data-ref="pin" title="Pin (P)">${PIN_ICON}PIN</button><button type="button" class="pb-tb" data-ref="inbox" title="Inbox (I)">${INBOX_ICON}<span data-ref="count">0</span></button><div class="div" style="margin:0 3px"></div><button type="button" class="pb-tb sq" data-ref="copy" title="Copy open pins (C)">${COPY_ICON}</button><button type="button" class="pb-tb sq" data-ref="theme" title="Theme (D)">${THEME_ICON}</button><button type="button" class="pb-tb sq" data-ref="help" title="Shortcuts (?)">?</button>`;
1569
+ root.innerHTML = `<div class="armed-ring"></div><div class="ident">${IDENT_ICON}<span class="bl" data-ref="label">PINBOX</span></div><div class="div"></div><button type="button" class="pb-tb" data-ref="pin" title="Pin (P)">${PIN_ICON}PIN</button><button type="button" class="pb-tb" data-ref="inbox" title="Inbox (I)">${INBOX_ICON}<span data-ref="count">0</span></button><div class="div" style="margin:0 3px"></div><button type="button" class="pb-tb sq" data-ref="copy" title="Copy open pins (C)">${COPY_ICON}</button><button type="button" class="pb-tb sq" data-ref="theme" title="Theme (D)">${THEME_ICON}</button><button type="button" class="pb-tb sq" data-ref="help" title="Shortcuts (?)">?</button><button type="button" class="pb-tb sq" data-ref="min" title="Minimize (M)" aria-label="Minimize toolbar">${MIN_ICON}</button>`;
1126
1570
  const ref = (name) => root.querySelector(`[data-ref="${name}"]`);
1127
1571
  const label = ref("label");
1128
1572
  const pinBtn = ref("pin");
@@ -1133,6 +1577,7 @@ function createBar(doc, on) {
1133
1577
  ref("copy").addEventListener("click", on.onCopy);
1134
1578
  ref("theme").addEventListener("click", on.onTheme);
1135
1579
  ref("help").addEventListener("click", on.onHelp);
1580
+ ref("min").addEventListener("click", (e) => on.onMinimize(e.detail === 0));
1136
1581
  return {
1137
1582
  root,
1138
1583
  update(state) {
@@ -1629,6 +2074,43 @@ function renderPins(layer, state) {
1629
2074
  if (state.draft) patchNode(ensureNode(layer, "draft", true), state.draft.placedAt, true, chipInner(visible.length + 1, null));
1630
2075
  }
1631
2076
  //#endregion
2077
+ //#region src/ui/puck.ts
2078
+ /** The bar's ident mark, sized up for the 48px puck face. */
2079
+ const PUCK_ICON = "<svg width=\"17\" height=\"17\" viewBox=\"0 0 16 16\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.4\"><rect x=\"2.5\" y=\"1.5\" width=\"11\" height=\"7\" rx=\"1\"/><path d=\"M8 8.5v6\"/><circle cx=\"8\" cy=\"14.6\" r=\".9\" fill=\"currentColor\" stroke=\"none\"/></svg>";
2080
+ function createMinimizeUi(doc) {
2081
+ const puck = doc.createElement("button");
2082
+ puck.type = "button";
2083
+ puck.className = "pb-puck pb-ghost";
2084
+ puck.setAttribute("aria-label", "Restore Pinbox toolbar");
2085
+ puck.innerHTML = `<span class="in">${PUCK_ICON}</span><span class="badge" data-ref="count" hidden>0</span><span class="cdot"></span>`;
2086
+ const morphWrap = doc.createElement("div");
2087
+ morphWrap.className = "pb-morph-wrap";
2088
+ morphWrap.hidden = true;
2089
+ const surface = doc.createElement("div");
2090
+ surface.className = "pb-morph";
2091
+ const carrier = doc.createElement("div");
2092
+ carrier.className = "pb-carrier";
2093
+ carrier.innerHTML = `${PUCK_ICON}<span class="badge" data-ref="count" hidden>0</span>`;
2094
+ morphWrap.appendChild(surface);
2095
+ morphWrap.appendChild(carrier);
2096
+ const badges = [puck.querySelector("[data-ref=\"count\"]"), carrier.querySelector("[data-ref=\"count\"]")];
2097
+ return {
2098
+ puck,
2099
+ morphWrap,
2100
+ surface,
2101
+ carrier,
2102
+ update(state) {
2103
+ const open = String(state.pins.filter((p) => p.status !== "resolved").length);
2104
+ for (const badge of badges) {
2105
+ if (badge.textContent !== open) badge.textContent = open;
2106
+ badge.hidden = open === "0";
2107
+ }
2108
+ const degraded = state.connection === "offline" || state.connection === "incompatible";
2109
+ puck.classList.toggle("degraded", degraded);
2110
+ }
2111
+ };
2112
+ }
2113
+ //#endregion
1632
2114
  //#region src/ui/reticle.ts
1633
2115
  function createReticle(doc) {
1634
2116
  const crosshair = doc.createElement("div");
@@ -1684,6 +2166,7 @@ const ROWS = [
1684
2166
  ["Send comment", "⌘ ↵"],
1685
2167
  ["Mark pin resolved", "R"],
1686
2168
  ["Copy open pins", "C"],
2169
+ ["Minimize toolbar", "M"],
1687
2170
  ["Cancel", "ESC"]
1688
2171
  ];
1689
2172
  function createShortcutsModal(doc, onClose) {
@@ -1894,6 +2377,27 @@ button { font: inherit; color: inherit; background: none; border: 0; cursor: poi
1894
2377
  .pb-item .mm .sdot { width: 5px; height: 5px; border-radius: 999px; }
1895
2378
  .pb-empty { padding: 26px 16px; font-size: 12.5px; color: var(--pb-fg3); }
1896
2379
 
2380
+ /* minimize: floating puck + morph layer (ui/puck.ts, minimize.ts) — v3 design.
2381
+ The morph surface is styled identically to the bar so endpoint handoffs are
2382
+ pixel-invisible. Ghosting keeps layout (getBoundingClientRect still measures
2383
+ morph targets) while dropping the element from paint and the tab order. */
2384
+ .pb-ghost { opacity: 0 !important; pointer-events: none !important; visibility: hidden; transition: opacity 90ms linear, visibility 0s 90ms; }
2385
+ .pb-bar { transition: opacity 90ms linear; }
2386
+ .pb-morph-wrap { position: fixed; inset: 0; z-index: 89; pointer-events: none; opacity: 0; transition: opacity 90ms linear; }
2387
+ .pb-morph-wrap.on { opacity: 1; }
2388
+ .pb-morph { position: absolute; left: 0; top: 0; background: var(--pb-bar); backdrop-filter: blur(16px); -webkit-backdrop-filter: blur(16px); border: 1px solid var(--pb-line-2); box-shadow: var(--pb-shadow); will-change: transform, width, height; }
2389
+ .pb-carrier { position: absolute; left: 0; top: 0; width: 48px; height: 48px; display: flex; align-items: center; justify-content: center; color: var(--pb-amber); opacity: 0; transition: opacity 140ms linear; will-change: transform; }
2390
+ .pb-carrier.show { opacity: 1; }
2391
+ /* Above the bar (90) and drawer (85), below the aim layer (100) and modal (120). */
2392
+ .pb-puck { position: fixed; left: 0; top: 0; width: 48px; height: 48px; z-index: 95; border-radius: 999px; background: var(--pb-bar); backdrop-filter: blur(16px); -webkit-backdrop-filter: blur(16px); border: 1px solid var(--pb-line-2); box-shadow: var(--pb-shadow); display: flex; align-items: center; justify-content: center; color: var(--pb-amber); cursor: grab; touch-action: none; transition: opacity 90ms linear; }
2393
+ .pb-puck:active { cursor: grabbing; }
2394
+ .pb-puck .in { display: flex; transition: transform 160ms var(--pb-ease); }
2395
+ .pb-puck:hover .in { transform: scale(1.12); }
2396
+ .pb-puck .badge, .pb-carrier .badge { position: absolute; top: -5px; right: -5px; min-width: 16px; height: 16px; padding: 0 4px; border-radius: 999px; background: var(--pb-amber); color: var(--pb-amber-ink); font-family: var(--pb-font-mono); font-size: 9px; font-weight: 500; display: flex; align-items: center; justify-content: center; }
2397
+ /* The bar says "· OFFLINE" in words; the puck's amber dot is the same signal. */
2398
+ .pb-puck .cdot { position: absolute; bottom: -1px; right: -1px; width: 9px; height: 9px; border-radius: 999px; background: var(--pb-amber); border: 2px solid var(--pb-canvas); display: none; }
2399
+ .pb-puck.degraded .cdot { display: block; }
2400
+
1897
2401
  /* shortcuts modal (ui/shortcuts.ts) — prototype lines 226–232 */
1898
2402
  .pb-modal { position: fixed; inset: 0; z-index: 120; display: flex; align-items: center; justify-content: center; background: var(--pb-scrim); backdrop-filter: blur(12px); -webkit-backdrop-filter: blur(12px); animation: pb-fade 200ms ease-out both; }
1899
2403
  .pb-modal .mx { width: 430px; background: var(--pb-elev); border: 1px solid var(--pb-line-2); border-radius: 4px; box-shadow: var(--pb-shadow); animation: pb-in 280ms var(--pb-ease) both; }
@@ -1949,6 +2453,8 @@ var PinboxToolbarElement = class extends BaseElement {
1949
2453
  /** Pending viewport-refresh frame, 0 when none is queued. */
1950
2454
  #viewportFrame = 0;
1951
2455
  #modal = null;
2456
+ #minUi = null;
2457
+ #min = null;
1952
2458
  #helpOpen = false;
1953
2459
  #pageStyle = null;
1954
2460
  #unsubscribe = null;
@@ -1989,6 +2495,7 @@ var PinboxToolbarElement = class extends BaseElement {
1989
2495
  document.head.appendChild(style);
1990
2496
  this.#pageStyle = style;
1991
2497
  if (this.#aim === null) this.#mountAim();
2498
+ if (this.#min === null) this.#mountMinimize();
1992
2499
  document.addEventListener("mousemove", this.#onMouseMove);
1993
2500
  window.addEventListener("scroll", this.#onViewportChange, { passive: true });
1994
2501
  window.addEventListener("resize", this.#onViewportChange);
@@ -2034,6 +2541,8 @@ var PinboxToolbarElement = class extends BaseElement {
2034
2541
  this.#viewportFrame = 0;
2035
2542
  this.#aim?.destroy();
2036
2543
  this.#aim = null;
2544
+ this.#min?.destroy();
2545
+ this.#min = null;
2037
2546
  this.#unsubscribe?.();
2038
2547
  this.#unsubscribe = null;
2039
2548
  this.#pageStyle?.remove();
@@ -2087,9 +2596,13 @@ var PinboxToolbarElement = class extends BaseElement {
2087
2596
  onInbox: () => this.store.update({ inboxOpen: !this.store.get().inboxOpen }),
2088
2597
  onTheme: () => this.#toggleTheme(),
2089
2598
  onHelp: () => this.#toggleHelp(),
2090
- onCopy: () => this.#copyOpenPins()
2599
+ onCopy: () => this.#copyOpenPins(),
2600
+ onMinimize: (keyboard) => this.minimize(keyboard)
2091
2601
  });
2092
2602
  shadow.appendChild(this.#bar.root);
2603
+ this.#minUi = createMinimizeUi(document);
2604
+ shadow.appendChild(this.#minUi.morphWrap);
2605
+ shadow.appendChild(this.#minUi.puck);
2093
2606
  this.#drawer = createDrawer(document, {
2094
2607
  onActivate: (pinId) => this.#activateFromInbox(pinId),
2095
2608
  onClose: () => this.store.update({ inboxOpen: false })
@@ -2100,6 +2613,52 @@ var PinboxToolbarElement = class extends BaseElement {
2100
2613
  shadow.appendChild(this.#modal.root);
2101
2614
  this.#pinsLayer.addEventListener("click", (e) => this.#onChipClick(e));
2102
2615
  }
2616
+ /** Same lifetime split as #mountAim: the controller's window listeners and timers die on
2617
+ * disconnect, so it is (re)built on connect and re-applies the persisted resting state. */
2618
+ #mountMinimize() {
2619
+ const ui = this.#minUi;
2620
+ const bar = this.#bar;
2621
+ if (ui === null || bar === null) return;
2622
+ this.#min = createMinimize({
2623
+ win: window,
2624
+ bar: bar.root,
2625
+ ui,
2626
+ storage: globalThis.localStorage ?? null,
2627
+ storagePrefix: `pinbox:${this.config?.endpoint ?? ""}`,
2628
+ reduced: window.matchMedia("(prefers-reduced-motion: reduce)").matches,
2629
+ initialMinimized: this.config?.minimized === true,
2630
+ onSettled: (minimized, keyboard) => this.#onMinimizeSettled(minimized, keyboard)
2631
+ });
2632
+ this.#min.applyInitial();
2633
+ }
2634
+ #onMinimizeSettled(minimized, keyboard) {
2635
+ if (this.store.get().minimized !== minimized) this.store.update({ minimized });
2636
+ this.dispatchEvent(new CustomEvent(minimized ? "pinbox:minimize" : "pinbox:restore", {
2637
+ bubbles: true,
2638
+ composed: true
2639
+ }));
2640
+ if (!keyboard) return;
2641
+ if (minimized) this.#minUi?.puck.focus();
2642
+ else this.#bar?.root.querySelector("[data-ref=\"min\"]")?.focus();
2643
+ }
2644
+ /** Public: collapse the bar to the floating puck. Bar surfaces close first —
2645
+ * the morph must never sweep over an open card, drawer, or armed reticle. */
2646
+ minimize(keyboard = false) {
2647
+ const state = this.store.get();
2648
+ if (state.mode === "placing" || state.activePinId !== null || state.draft !== null) this.#dismiss();
2649
+ if (state.inboxOpen) this.store.update({ inboxOpen: false });
2650
+ this.#setHelp(false);
2651
+ this.#min?.minimize(keyboard);
2652
+ }
2653
+ /** Public: bring the bar back from the puck. */
2654
+ restore(keyboard = false) {
2655
+ this.#min?.restore(keyboard);
2656
+ }
2657
+ /** Bar-surface shortcuts pressed while minimized restore the bar, then run. */
2658
+ #surfaced(run) {
2659
+ if (this.#min?.minimized() === true) this.restore(false);
2660
+ run();
2661
+ }
2103
2662
  /**
2104
2663
  * Task 8 wiring: WS events mutate the store, connection state renders in the
2105
2664
  * bar, card actions hit the hub. The mirror seeds pins so an offline reload
@@ -2323,14 +2882,15 @@ var PinboxToolbarElement = class extends BaseElement {
2323
2882
  if (state.inboxOpen) this.store.update({ inboxOpen: false });
2324
2883
  if (state.activePinId || state.draft) this.#dismiss();
2325
2884
  };
2326
- /** Prototype keyboard map (v2-command-bar.html lines 701–712). */
2885
+ /** Prototype keyboard map (v2-command-bar.html lines 701–712) + M (v3 minimize). */
2327
2886
  #shortcuts = {
2328
2887
  escape: () => this.#dismiss(),
2329
- p: () => this.#togglePlacing(),
2330
- i: () => this.#toggleInbox(),
2888
+ p: () => this.#surfaced(() => this.#togglePlacing()),
2889
+ i: () => this.#surfaced(() => this.#toggleInbox()),
2331
2890
  d: () => this.#toggleTheme(),
2332
2891
  r: () => this.#resolveActive(),
2333
2892
  c: () => this.#copyOpenPins(),
2893
+ m: () => this.#min?.minimized() === true ? this.restore(true) : this.minimize(true),
2334
2894
  "?": () => this.#toggleHelp()
2335
2895
  };
2336
2896
  #onKeyDown = (e) => {
@@ -2368,6 +2928,7 @@ var PinboxToolbarElement = class extends BaseElement {
2368
2928
  if (this.shadowRoot) renderCard(this.shadowRoot, state, this.#cardActions);
2369
2929
  this.#drawer?.update(state);
2370
2930
  this.#bar?.update(state);
2931
+ this.#minUi?.update(state);
2371
2932
  }
2372
2933
  };
2373
2934
  //#endregion
package/dist/svelte.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { _ as applyHubEvent, a as HubEvent, b as upsertPin, c as WebSocketLike, d as PinboxToolbarElement, f as Draft, g as appendThreadMessage, h as UiStatus, i as ConnectionState, l as HubError, m as ToolbarState, n as PinboxConfig, o as HubTransport, p as Store, r as defineToolbarElement, s as TransportOptions, t as Pinbox, u as StorageLike, v as createStore, x as CaptureResult, y as deriveUiStatus } from "./index-BAoi0bRT.js";
1
+ import { _ as applyHubEvent, a as HubEvent, b as upsertPin, c as WebSocketLike, d as PinboxToolbarElement, f as Draft, g as appendThreadMessage, h as UiStatus, i as ConnectionState, l as HubError, m as ToolbarState, n as PinboxConfig, o as HubTransport, p as Store, r as defineToolbarElement, s as TransportOptions, t as Pinbox, u as StorageLike, v as createStore, x as CaptureResult, y as deriveUiStatus } from "./index-DK_kVd2Z.js";
2
2
  import { Action } from "svelte/action";
3
3
  //#region src/svelte.d.ts
4
4
  /**
package/dist/svelte.js CHANGED
@@ -1,4 +1,4 @@
1
- import { a as HubError, c as createStore, i as HubTransport, l as deriveUiStatus, n as defineToolbarElement, o as appendThreadMessage, r as PinboxToolbarElement, s as applyHubEvent, t as Pinbox, u as upsertPin } from "./src-D1qn1iet.js";
1
+ import { a as HubError, c as createStore, i as HubTransport, l as deriveUiStatus, n as defineToolbarElement, o as appendThreadMessage, r as PinboxToolbarElement, s as applyHubEvent, t as Pinbox, u as upsertPin } from "./src-ChTPrRo5.js";
2
2
  //#region src/svelte.ts
3
3
  /**
4
4
  * `<div use:pinbox={{ endpoint }} />` — creates + configures the element BEFORE insertion