@autono/pinbox-toolbar 0.10.0 → 0.12.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/index.js CHANGED
@@ -1,2 +1,2 @@
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";
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-C-gslo8y.js";
2
2
  export { HubError, HubTransport, Pinbox, PinboxToolbarElement, appendThreadMessage, applyHubEvent, createStore, defineToolbarElement, deriveUiStatus, upsertPin };
package/dist/react.js CHANGED
@@ -1,4 +1,4 @@
1
- import { n as defineToolbarElement, r as PinboxToolbarElement } from "./src-ChTPrRo5.js";
1
+ import { n as defineToolbarElement, r as PinboxToolbarElement } from "./src-C-gslo8y.js";
2
2
  import { createElement, useEffect, useRef } from "react";
3
3
  //#region src/react.ts
4
4
  /**
@@ -367,6 +367,8 @@ const LAYER_HIDE = 140;
367
367
  const BAR_RADIUS = 4;
368
368
  /** The carrier icon rides the surface only while it is puck-like (< this width). */
369
369
  const CARRIER_MAX_W = 260;
370
+ /** Fan close transition length before display:none. */
371
+ const FAN_HIDE = 300;
370
372
  function createMinimize(host) {
371
373
  const { win, bar, ui } = host;
372
374
  const main = mkSpring({
@@ -386,6 +388,8 @@ function createMinimize(host) {
386
388
  let holdTimer = 0;
387
389
  let fadeTimer = 0;
388
390
  let hideTimer = 0;
391
+ let fanOpen = false;
392
+ let fanTimer = 0;
389
393
  function loadDock() {
390
394
  try {
391
395
  const raw = host.storage?.getItem(`${host.storagePrefix}:dock`);
@@ -533,6 +537,7 @@ function createMinimize(host) {
533
537
  }
534
538
  function restore(keyboard = false) {
535
539
  if (mode !== "puck" || puckPos === null) return;
540
+ closeFan();
536
541
  pdown = null;
537
542
  keyboardToggle = keyboard;
538
543
  dock = { ...puckPos };
@@ -567,6 +572,54 @@ function createMinimize(host) {
567
572
  ensureLoop();
568
573
  }, HOLD_RESTORE);
569
574
  }
575
+ /** Fan out of the puck: direction away from the nearer vertical edge,
576
+ * labels sliding toward screen center. */
577
+ function openFan() {
578
+ if (mode !== "puck" || puckPos === null) return;
579
+ win.clearTimeout(fanTimer);
580
+ ui.fan.hidden = false;
581
+ const upward = puckPos.y + PUCK / 2 > win.innerHeight / 2;
582
+ ui.fan.classList.toggle("up", upward);
583
+ ui.fan.classList.toggle("down", !upward);
584
+ ui.fan.classList.toggle("labels-right", puckPos.x + PUCK / 2 < win.innerWidth / 2);
585
+ ui.fan.classList.toggle("labels-left", puckPos.x + PUCK / 2 >= win.innerWidth / 2);
586
+ ui.fan.style.left = `${puckPos.x + PUCK / 2 - 20}px`;
587
+ ui.fan.offsetHeight;
588
+ const h = ui.fan.offsetHeight;
589
+ ui.fan.style.top = upward ? `${puckPos.y - h - 10}px` : `${puckPos.y + PUCK + 10}px`;
590
+ ui.fan.classList.add("on");
591
+ ui.puck.setAttribute("aria-expanded", "true");
592
+ fanOpen = true;
593
+ }
594
+ function closeFan() {
595
+ if (!fanOpen) return false;
596
+ ui.fan.classList.remove("on");
597
+ ui.puck.setAttribute("aria-expanded", "false");
598
+ fanOpen = false;
599
+ win.clearTimeout(fanTimer);
600
+ fanTimer = win.setTimeout(() => {
601
+ ui.fan.hidden = true;
602
+ }, FAN_HIDE);
603
+ return true;
604
+ }
605
+ function onFanClick(e) {
606
+ const act = (e.target.closest?.("[data-act]"))?.getAttribute("data-act");
607
+ if (act == null) return;
608
+ if (act === "expand") {
609
+ closeFan();
610
+ restore(e.detail === 0);
611
+ return;
612
+ }
613
+ host.onFanAction(act);
614
+ }
615
+ /** Click-away, at the document level: shadow events retarget, so membership
616
+ * is checked via composedPath, not target. */
617
+ function onDocPointerDown(e) {
618
+ if (!fanOpen) return;
619
+ const path = e.composedPath();
620
+ if (path.includes(ui.fan) || path.includes(ui.puck)) return;
621
+ closeFan();
622
+ }
570
623
  function onPointerDown(e) {
571
624
  if (mode !== "puck" || e.button !== 0) return;
572
625
  try {
@@ -596,6 +649,7 @@ function createMinimize(host) {
596
649
  }
597
650
  if (Math.hypot(dx, dy) < DRAG_START) return;
598
651
  pdown.moved = true;
652
+ closeFan();
599
653
  mode = "drag";
600
654
  if (!host.reduced) {
601
655
  ui.puck.classList.add("pb-ghost");
@@ -651,7 +705,7 @@ function createMinimize(host) {
651
705
  hideMorph();
652
706
  mode = "puck";
653
707
  }
654
- restore(false);
708
+ if (!closeFan()) openFan();
655
709
  return;
656
710
  }
657
711
  if (host.reduced) {
@@ -679,9 +733,11 @@ function createMinimize(host) {
679
733
  }
680
734
  /** Keyboard/AT activation is a synthesized click (detail 0) with no pointer events. */
681
735
  function onClick(e) {
682
- if (e.detail === 0) restore(true);
736
+ if (e.detail !== 0) return;
737
+ if (!closeFan()) openFan();
683
738
  }
684
739
  function onResize() {
740
+ closeFan();
685
741
  if (mode === "puck" && puckPos !== null) {
686
742
  const p = clampPos(puckPos);
687
743
  placePuck(p.x, p.y);
@@ -693,12 +749,15 @@ function createMinimize(host) {
693
749
  ui.puck.addEventListener("pointerup", onPointerUp);
694
750
  ui.puck.addEventListener("pointercancel", onPointerUp);
695
751
  ui.puck.addEventListener("click", onClick);
752
+ ui.fan.addEventListener("click", onFanClick);
753
+ win.document.addEventListener("pointerdown", onDocPointerDown);
696
754
  win.addEventListener("resize", onResize);
697
755
  return {
698
756
  minimized: () => mode !== "bar",
699
757
  mode: () => mode,
700
758
  minimize,
701
759
  restore,
760
+ closeFan,
702
761
  applyInitial() {
703
762
  if (!loadMinimized()) return;
704
763
  const apply = () => {
@@ -719,12 +778,18 @@ function createMinimize(host) {
719
778
  ui.puck.removeEventListener("pointerup", onPointerUp);
720
779
  ui.puck.removeEventListener("pointercancel", onPointerUp);
721
780
  ui.puck.removeEventListener("click", onClick);
781
+ ui.fan.removeEventListener("click", onFanClick);
782
+ win.document.removeEventListener("pointerdown", onDocPointerDown);
722
783
  win.removeEventListener("resize", onResize);
723
784
  if (raf !== 0) win.cancelAnimationFrame(raf);
724
785
  raf = 0;
725
786
  win.clearTimeout(holdTimer);
726
787
  win.clearTimeout(fadeTimer);
727
788
  win.clearTimeout(hideTimer);
789
+ win.clearTimeout(fanTimer);
790
+ ui.fan.hidden = true;
791
+ ui.fan.classList.remove("on");
792
+ fanOpen = false;
728
793
  }
729
794
  };
730
795
  }
@@ -2077,12 +2142,25 @@ function renderPins(layer, state) {
2077
2142
  //#region src/ui/puck.ts
2078
2143
  /** The bar's ident mark, sized up for the 48px puck face. */
2079
2144
  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>";
2145
+ const FAN_PIN = "<svg width=\"15\" height=\"15\" viewBox=\"0 0 16 16\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.4\"><rect x=\"3\" y=\"1.5\" width=\"10\" height=\"6.5\" rx=\"1\"/><path d=\"M8 8v6.5\"/></svg>";
2146
+ const FAN_INBOX = "<svg width=\"15\" height=\"15\" viewBox=\"0 0 16 16\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.4\"><path d=\"M1.8 8.5h3.4l1 2h3.6l1-2h3.4\"/><path d=\"M2.6 3.2h10.8l1.2 5.3v4a1 1 0 01-1 1H2.4a1 1 0 01-1-1v-4z\"/></svg>";
2147
+ const FAN_THEME = "<svg width=\"15\" height=\"15\" 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>";
2148
+ const FAN_EXPAND = "<svg width=\"15\" height=\"15\" viewBox=\"0 0 16 16\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.4\"><path d=\"M9.5 6.5v-4h4\"/><path d=\"M6.5 9.5v4h-4\"/></svg>";
2149
+ function fanItem(act, index, icon, label, key) {
2150
+ return `<button type="button" class="pb-fan-item" data-act="${act}" style="--i:${index}" aria-label="${label}">${icon}${act === "inbox" ? "<span class=\"badge\" data-ref=\"count\" hidden>0</span>" : ""}<span class="fl">${label.toUpperCase()}<i>${key}</i></span></button>`;
2151
+ }
2080
2152
  function createMinimizeUi(doc) {
2081
2153
  const puck = doc.createElement("button");
2082
2154
  puck.type = "button";
2083
2155
  puck.className = "pb-puck pb-ghost";
2084
- puck.setAttribute("aria-label", "Restore Pinbox toolbar");
2156
+ puck.setAttribute("aria-label", "Pinbox menu");
2157
+ puck.setAttribute("aria-haspopup", "menu");
2085
2158
  puck.innerHTML = `<span class="in">${PUCK_ICON}</span><span class="badge" data-ref="count" hidden>0</span><span class="cdot"></span>`;
2159
+ const fan = doc.createElement("div");
2160
+ fan.className = "pb-fan";
2161
+ fan.hidden = true;
2162
+ fan.setAttribute("role", "menu");
2163
+ fan.innerHTML = fanItem("pin", 0, FAN_PIN, "Drop a pin", "P") + fanItem("inbox", 1, FAN_INBOX, "Inbox", "I") + fanItem("theme", 2, FAN_THEME, "Theme", "D") + fanItem("expand", 3, FAN_EXPAND, "Expand", "M");
2086
2164
  const morphWrap = doc.createElement("div");
2087
2165
  morphWrap.className = "pb-morph-wrap";
2088
2166
  morphWrap.hidden = true;
@@ -2093,9 +2171,14 @@ function createMinimizeUi(doc) {
2093
2171
  carrier.innerHTML = `${PUCK_ICON}<span class="badge" data-ref="count" hidden>0</span>`;
2094
2172
  morphWrap.appendChild(surface);
2095
2173
  morphWrap.appendChild(carrier);
2096
- const badges = [puck.querySelector("[data-ref=\"count\"]"), carrier.querySelector("[data-ref=\"count\"]")];
2174
+ const badges = [
2175
+ puck.querySelector("[data-ref=\"count\"]"),
2176
+ carrier.querySelector("[data-ref=\"count\"]"),
2177
+ fan.querySelector("[data-ref=\"count\"]")
2178
+ ];
2097
2179
  return {
2098
2180
  puck,
2181
+ fan,
2099
2182
  morphWrap,
2100
2183
  surface,
2101
2184
  carrier,
@@ -2107,6 +2190,7 @@ function createMinimizeUi(doc) {
2107
2190
  }
2108
2191
  const degraded = state.connection === "offline" || state.connection === "incompatible";
2109
2192
  puck.classList.toggle("degraded", degraded);
2193
+ puck.classList.toggle("armed", state.mode === "placing");
2110
2194
  }
2111
2195
  };
2112
2196
  }
@@ -2393,10 +2477,28 @@ button { font: inherit; color: inherit; background: none; border: 0; cursor: poi
2393
2477
  .pb-puck:active { cursor: grabbing; }
2394
2478
  .pb-puck .in { display: flex; transition: transform 160ms var(--pb-ease); }
2395
2479
  .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; }
2480
+ .pb-puck .badge, .pb-carrier .badge, .pb-fan-item .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
2481
  /* The bar says "· OFFLINE" in words; the puck's amber dot is the same signal. */
2398
2482
  .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
2483
  .pb-puck.degraded .cdot { display: block; }
2484
+ /* Placing armed from the fan: the bar's armed-ring is hidden with the bar. */
2485
+ .pb-puck.armed { border-color: var(--pb-amber); box-shadow: 0 0 0 4px var(--pb-amber-soft), var(--pb-shadow); }
2486
+
2487
+ /* puck fan menu (ui/puck.ts, minimize.ts) — tap the puck and a vertical
2488
+ quick-menu fans out of it; EXPAND is how the bar comes back. Items stagger
2489
+ from the puck; hovering slides a label + key chip toward screen center. */
2490
+ .pb-fan { position: fixed; z-index: 96; display: flex; flex-direction: column; gap: 6px; align-items: center; }
2491
+ .pb-fan-item { position: relative; width: 40px; height: 40px; 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); color: var(--pb-fg2); display: flex; align-items: center; justify-content: center; opacity: 0; transform: var(--fan-from) scale(0.5); transition: transform 300ms var(--pb-ease), opacity 180ms linear, color 140ms linear, border-color 140ms linear; transition-delay: calc(var(--i) * 35ms); }
2492
+ .pb-fan.up { --fan-from: translateY(16px); }
2493
+ .pb-fan.down { --fan-from: translateY(-16px); }
2494
+ .pb-fan.on .pb-fan-item { opacity: 1; transform: none; }
2495
+ .pb-fan-item:hover { color: var(--pb-amber); border-color: var(--pb-amber); }
2496
+ .pb-fan-item .badge { pointer-events: none; }
2497
+ .pb-fan-item .fl { position: absolute; top: 50%; display: flex; align-items: center; gap: 6px; padding: 4px 8px; background: var(--pb-elev); border: 1px solid var(--pb-line-2); border-radius: 2px; box-shadow: var(--pb-shadow); font-family: var(--pb-font-mono); font-size: 9px; letter-spacing: .14em; color: var(--pb-fg1); white-space: nowrap; opacity: 0; pointer-events: none; transition: opacity 140ms linear, transform 200ms var(--pb-ease); }
2498
+ .pb-fan.labels-right .fl { left: calc(100% + 10px); transform: translateY(-50%) translateX(-6px); }
2499
+ .pb-fan.labels-left .fl { right: calc(100% + 10px); transform: translateY(-50%) translateX(6px); }
2500
+ .pb-fan-item:hover .fl { opacity: 1; transform: translateY(-50%) translateX(0); }
2501
+ .pb-fan-item .fl i { font-style: normal; padding: 1px 5px; border: 1px solid var(--pb-line-2); border-radius: 2px; background: var(--pb-sunken); color: var(--pb-fg3); }
2400
2502
 
2401
2503
  /* shortcuts modal (ui/shortcuts.ts) — prototype lines 226–232 */
2402
2504
  .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; }
@@ -2603,6 +2705,7 @@ var PinboxToolbarElement = class extends BaseElement {
2603
2705
  this.#minUi = createMinimizeUi(document);
2604
2706
  shadow.appendChild(this.#minUi.morphWrap);
2605
2707
  shadow.appendChild(this.#minUi.puck);
2708
+ shadow.appendChild(this.#minUi.fan);
2606
2709
  this.#drawer = createDrawer(document, {
2607
2710
  onActivate: (pinId) => this.#activateFromInbox(pinId),
2608
2711
  onClose: () => this.store.update({ inboxOpen: false })
@@ -2627,7 +2730,12 @@ var PinboxToolbarElement = class extends BaseElement {
2627
2730
  storagePrefix: `pinbox:${this.config?.endpoint ?? ""}`,
2628
2731
  reduced: window.matchMedia("(prefers-reduced-motion: reduce)").matches,
2629
2732
  initialMinimized: this.config?.minimized === true,
2630
- onSettled: (minimized, keyboard) => this.#onMinimizeSettled(minimized, keyboard)
2733
+ onSettled: (minimized, keyboard) => this.#onMinimizeSettled(minimized, keyboard),
2734
+ onFanAction: (action) => {
2735
+ if (action === "pin") this.#togglePlacing();
2736
+ else if (action === "inbox") this.#toggleInbox();
2737
+ else this.#toggleTheme();
2738
+ }
2631
2739
  });
2632
2740
  this.#min.applyInitial();
2633
2741
  }
@@ -2654,11 +2762,6 @@ var PinboxToolbarElement = class extends BaseElement {
2654
2762
  restore(keyboard = false) {
2655
2763
  this.#min?.restore(keyboard);
2656
2764
  }
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
- }
2662
2765
  /**
2663
2766
  * Task 8 wiring: WS events mutate the store, connection state renders in the
2664
2767
  * bar, card actions hit the hub. The mirror seeds pins so an offline reload
@@ -2884,9 +2987,12 @@ var PinboxToolbarElement = class extends BaseElement {
2884
2987
  };
2885
2988
  /** Prototype keyboard map (v2-command-bar.html lines 701–712) + M (v3 minimize). */
2886
2989
  #shortcuts = {
2887
- escape: () => this.#dismiss(),
2888
- p: () => this.#surfaced(() => this.#togglePlacing()),
2889
- i: () => this.#surfaced(() => this.#toggleInbox()),
2990
+ escape: () => {
2991
+ if (this.#min?.closeFan() === true) return;
2992
+ this.#dismiss();
2993
+ },
2994
+ p: () => this.#togglePlacing(),
2995
+ i: () => this.#toggleInbox(),
2890
2996
  d: () => this.#toggleTheme(),
2891
2997
  r: () => this.#resolveActive(),
2892
2998
  c: () => this.#copyOpenPins(),
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-ChTPrRo5.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-C-gslo8y.js";
2
2
  //#region src/svelte.ts
3
3
  /**
4
4
  * `<div use:pinbox={{ endpoint }} />` — creates + configures the element BEFORE insertion
@@ -369,6 +369,8 @@ var Pinbox = (function(exports) {
369
369
  const BAR_RADIUS = 4;
370
370
  /** The carrier icon rides the surface only while it is puck-like (< this width). */
371
371
  const CARRIER_MAX_W = 260;
372
+ /** Fan close transition length before display:none. */
373
+ const FAN_HIDE = 300;
372
374
  function createMinimize(host) {
373
375
  const { win, bar, ui } = host;
374
376
  const main = mkSpring({
@@ -388,6 +390,8 @@ var Pinbox = (function(exports) {
388
390
  let holdTimer = 0;
389
391
  let fadeTimer = 0;
390
392
  let hideTimer = 0;
393
+ let fanOpen = false;
394
+ let fanTimer = 0;
391
395
  function loadDock() {
392
396
  try {
393
397
  const raw = host.storage?.getItem(`${host.storagePrefix}:dock`);
@@ -535,6 +539,7 @@ var Pinbox = (function(exports) {
535
539
  }
536
540
  function restore(keyboard = false) {
537
541
  if (mode !== "puck" || puckPos === null) return;
542
+ closeFan();
538
543
  pdown = null;
539
544
  keyboardToggle = keyboard;
540
545
  dock = { ...puckPos };
@@ -569,6 +574,54 @@ var Pinbox = (function(exports) {
569
574
  ensureLoop();
570
575
  }, HOLD_RESTORE);
571
576
  }
577
+ /** Fan out of the puck: direction away from the nearer vertical edge,
578
+ * labels sliding toward screen center. */
579
+ function openFan() {
580
+ if (mode !== "puck" || puckPos === null) return;
581
+ win.clearTimeout(fanTimer);
582
+ ui.fan.hidden = false;
583
+ const upward = puckPos.y + PUCK / 2 > win.innerHeight / 2;
584
+ ui.fan.classList.toggle("up", upward);
585
+ ui.fan.classList.toggle("down", !upward);
586
+ ui.fan.classList.toggle("labels-right", puckPos.x + PUCK / 2 < win.innerWidth / 2);
587
+ ui.fan.classList.toggle("labels-left", puckPos.x + PUCK / 2 >= win.innerWidth / 2);
588
+ ui.fan.style.left = `${puckPos.x + PUCK / 2 - 20}px`;
589
+ ui.fan.offsetHeight;
590
+ const h = ui.fan.offsetHeight;
591
+ ui.fan.style.top = upward ? `${puckPos.y - h - 10}px` : `${puckPos.y + PUCK + 10}px`;
592
+ ui.fan.classList.add("on");
593
+ ui.puck.setAttribute("aria-expanded", "true");
594
+ fanOpen = true;
595
+ }
596
+ function closeFan() {
597
+ if (!fanOpen) return false;
598
+ ui.fan.classList.remove("on");
599
+ ui.puck.setAttribute("aria-expanded", "false");
600
+ fanOpen = false;
601
+ win.clearTimeout(fanTimer);
602
+ fanTimer = win.setTimeout(() => {
603
+ ui.fan.hidden = true;
604
+ }, FAN_HIDE);
605
+ return true;
606
+ }
607
+ function onFanClick(e) {
608
+ const act = (e.target.closest?.("[data-act]"))?.getAttribute("data-act");
609
+ if (act == null) return;
610
+ if (act === "expand") {
611
+ closeFan();
612
+ restore(e.detail === 0);
613
+ return;
614
+ }
615
+ host.onFanAction(act);
616
+ }
617
+ /** Click-away, at the document level: shadow events retarget, so membership
618
+ * is checked via composedPath, not target. */
619
+ function onDocPointerDown(e) {
620
+ if (!fanOpen) return;
621
+ const path = e.composedPath();
622
+ if (path.includes(ui.fan) || path.includes(ui.puck)) return;
623
+ closeFan();
624
+ }
572
625
  function onPointerDown(e) {
573
626
  if (mode !== "puck" || e.button !== 0) return;
574
627
  try {
@@ -598,6 +651,7 @@ var Pinbox = (function(exports) {
598
651
  }
599
652
  if (Math.hypot(dx, dy) < DRAG_START) return;
600
653
  pdown.moved = true;
654
+ closeFan();
601
655
  mode = "drag";
602
656
  if (!host.reduced) {
603
657
  ui.puck.classList.add("pb-ghost");
@@ -653,7 +707,7 @@ var Pinbox = (function(exports) {
653
707
  hideMorph();
654
708
  mode = "puck";
655
709
  }
656
- restore(false);
710
+ if (!closeFan()) openFan();
657
711
  return;
658
712
  }
659
713
  if (host.reduced) {
@@ -681,9 +735,11 @@ var Pinbox = (function(exports) {
681
735
  }
682
736
  /** Keyboard/AT activation is a synthesized click (detail 0) with no pointer events. */
683
737
  function onClick(e) {
684
- if (e.detail === 0) restore(true);
738
+ if (e.detail !== 0) return;
739
+ if (!closeFan()) openFan();
685
740
  }
686
741
  function onResize() {
742
+ closeFan();
687
743
  if (mode === "puck" && puckPos !== null) {
688
744
  const p = clampPos(puckPos);
689
745
  placePuck(p.x, p.y);
@@ -695,12 +751,15 @@ var Pinbox = (function(exports) {
695
751
  ui.puck.addEventListener("pointerup", onPointerUp);
696
752
  ui.puck.addEventListener("pointercancel", onPointerUp);
697
753
  ui.puck.addEventListener("click", onClick);
754
+ ui.fan.addEventListener("click", onFanClick);
755
+ win.document.addEventListener("pointerdown", onDocPointerDown);
698
756
  win.addEventListener("resize", onResize);
699
757
  return {
700
758
  minimized: () => mode !== "bar",
701
759
  mode: () => mode,
702
760
  minimize,
703
761
  restore,
762
+ closeFan,
704
763
  applyInitial() {
705
764
  if (!loadMinimized()) return;
706
765
  const apply = () => {
@@ -721,12 +780,18 @@ var Pinbox = (function(exports) {
721
780
  ui.puck.removeEventListener("pointerup", onPointerUp);
722
781
  ui.puck.removeEventListener("pointercancel", onPointerUp);
723
782
  ui.puck.removeEventListener("click", onClick);
783
+ ui.fan.removeEventListener("click", onFanClick);
784
+ win.document.removeEventListener("pointerdown", onDocPointerDown);
724
785
  win.removeEventListener("resize", onResize);
725
786
  if (raf !== 0) win.cancelAnimationFrame(raf);
726
787
  raf = 0;
727
788
  win.clearTimeout(holdTimer);
728
789
  win.clearTimeout(fadeTimer);
729
790
  win.clearTimeout(hideTimer);
791
+ win.clearTimeout(fanTimer);
792
+ ui.fan.hidden = true;
793
+ ui.fan.classList.remove("on");
794
+ fanOpen = false;
730
795
  }
731
796
  };
732
797
  }
@@ -2079,12 +2144,25 @@ var Pinbox = (function(exports) {
2079
2144
  //#region src/ui/puck.ts
2080
2145
  /** The bar's ident mark, sized up for the 48px puck face. */
2081
2146
  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>";
2147
+ const FAN_PIN = "<svg width=\"15\" height=\"15\" viewBox=\"0 0 16 16\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.4\"><rect x=\"3\" y=\"1.5\" width=\"10\" height=\"6.5\" rx=\"1\"/><path d=\"M8 8v6.5\"/></svg>";
2148
+ const FAN_INBOX = "<svg width=\"15\" height=\"15\" viewBox=\"0 0 16 16\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.4\"><path d=\"M1.8 8.5h3.4l1 2h3.6l1-2h3.4\"/><path d=\"M2.6 3.2h10.8l1.2 5.3v4a1 1 0 01-1 1H2.4a1 1 0 01-1-1v-4z\"/></svg>";
2149
+ const FAN_THEME = "<svg width=\"15\" height=\"15\" 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>";
2150
+ const FAN_EXPAND = "<svg width=\"15\" height=\"15\" viewBox=\"0 0 16 16\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.4\"><path d=\"M9.5 6.5v-4h4\"/><path d=\"M6.5 9.5v4h-4\"/></svg>";
2151
+ function fanItem(act, index, icon, label, key) {
2152
+ return `<button type="button" class="pb-fan-item" data-act="${act}" style="--i:${index}" aria-label="${label}">${icon}${act === "inbox" ? "<span class=\"badge\" data-ref=\"count\" hidden>0</span>" : ""}<span class="fl">${label.toUpperCase()}<i>${key}</i></span></button>`;
2153
+ }
2082
2154
  function createMinimizeUi(doc) {
2083
2155
  const puck = doc.createElement("button");
2084
2156
  puck.type = "button";
2085
2157
  puck.className = "pb-puck pb-ghost";
2086
- puck.setAttribute("aria-label", "Restore Pinbox toolbar");
2158
+ puck.setAttribute("aria-label", "Pinbox menu");
2159
+ puck.setAttribute("aria-haspopup", "menu");
2087
2160
  puck.innerHTML = `<span class="in">${PUCK_ICON}</span><span class="badge" data-ref="count" hidden>0</span><span class="cdot"></span>`;
2161
+ const fan = doc.createElement("div");
2162
+ fan.className = "pb-fan";
2163
+ fan.hidden = true;
2164
+ fan.setAttribute("role", "menu");
2165
+ fan.innerHTML = fanItem("pin", 0, FAN_PIN, "Drop a pin", "P") + fanItem("inbox", 1, FAN_INBOX, "Inbox", "I") + fanItem("theme", 2, FAN_THEME, "Theme", "D") + fanItem("expand", 3, FAN_EXPAND, "Expand", "M");
2088
2166
  const morphWrap = doc.createElement("div");
2089
2167
  morphWrap.className = "pb-morph-wrap";
2090
2168
  morphWrap.hidden = true;
@@ -2095,9 +2173,14 @@ var Pinbox = (function(exports) {
2095
2173
  carrier.innerHTML = `${PUCK_ICON}<span class="badge" data-ref="count" hidden>0</span>`;
2096
2174
  morphWrap.appendChild(surface);
2097
2175
  morphWrap.appendChild(carrier);
2098
- const badges = [puck.querySelector("[data-ref=\"count\"]"), carrier.querySelector("[data-ref=\"count\"]")];
2176
+ const badges = [
2177
+ puck.querySelector("[data-ref=\"count\"]"),
2178
+ carrier.querySelector("[data-ref=\"count\"]"),
2179
+ fan.querySelector("[data-ref=\"count\"]")
2180
+ ];
2099
2181
  return {
2100
2182
  puck,
2183
+ fan,
2101
2184
  morphWrap,
2102
2185
  surface,
2103
2186
  carrier,
@@ -2109,6 +2192,7 @@ var Pinbox = (function(exports) {
2109
2192
  }
2110
2193
  const degraded = state.connection === "offline" || state.connection === "incompatible";
2111
2194
  puck.classList.toggle("degraded", degraded);
2195
+ puck.classList.toggle("armed", state.mode === "placing");
2112
2196
  }
2113
2197
  };
2114
2198
  }
@@ -2395,10 +2479,28 @@ button { font: inherit; color: inherit; background: none; border: 0; cursor: poi
2395
2479
  .pb-puck:active { cursor: grabbing; }
2396
2480
  .pb-puck .in { display: flex; transition: transform 160ms var(--pb-ease); }
2397
2481
  .pb-puck:hover .in { transform: scale(1.12); }
2398
- .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; }
2482
+ .pb-puck .badge, .pb-carrier .badge, .pb-fan-item .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; }
2399
2483
  /* The bar says "· OFFLINE" in words; the puck's amber dot is the same signal. */
2400
2484
  .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; }
2401
2485
  .pb-puck.degraded .cdot { display: block; }
2486
+ /* Placing armed from the fan: the bar's armed-ring is hidden with the bar. */
2487
+ .pb-puck.armed { border-color: var(--pb-amber); box-shadow: 0 0 0 4px var(--pb-amber-soft), var(--pb-shadow); }
2488
+
2489
+ /* puck fan menu (ui/puck.ts, minimize.ts) — tap the puck and a vertical
2490
+ quick-menu fans out of it; EXPAND is how the bar comes back. Items stagger
2491
+ from the puck; hovering slides a label + key chip toward screen center. */
2492
+ .pb-fan { position: fixed; z-index: 96; display: flex; flex-direction: column; gap: 6px; align-items: center; }
2493
+ .pb-fan-item { position: relative; width: 40px; height: 40px; 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); color: var(--pb-fg2); display: flex; align-items: center; justify-content: center; opacity: 0; transform: var(--fan-from) scale(0.5); transition: transform 300ms var(--pb-ease), opacity 180ms linear, color 140ms linear, border-color 140ms linear; transition-delay: calc(var(--i) * 35ms); }
2494
+ .pb-fan.up { --fan-from: translateY(16px); }
2495
+ .pb-fan.down { --fan-from: translateY(-16px); }
2496
+ .pb-fan.on .pb-fan-item { opacity: 1; transform: none; }
2497
+ .pb-fan-item:hover { color: var(--pb-amber); border-color: var(--pb-amber); }
2498
+ .pb-fan-item .badge { pointer-events: none; }
2499
+ .pb-fan-item .fl { position: absolute; top: 50%; display: flex; align-items: center; gap: 6px; padding: 4px 8px; background: var(--pb-elev); border: 1px solid var(--pb-line-2); border-radius: 2px; box-shadow: var(--pb-shadow); font-family: var(--pb-font-mono); font-size: 9px; letter-spacing: .14em; color: var(--pb-fg1); white-space: nowrap; opacity: 0; pointer-events: none; transition: opacity 140ms linear, transform 200ms var(--pb-ease); }
2500
+ .pb-fan.labels-right .fl { left: calc(100% + 10px); transform: translateY(-50%) translateX(-6px); }
2501
+ .pb-fan.labels-left .fl { right: calc(100% + 10px); transform: translateY(-50%) translateX(6px); }
2502
+ .pb-fan-item:hover .fl { opacity: 1; transform: translateY(-50%) translateX(0); }
2503
+ .pb-fan-item .fl i { font-style: normal; padding: 1px 5px; border: 1px solid var(--pb-line-2); border-radius: 2px; background: var(--pb-sunken); color: var(--pb-fg3); }
2402
2504
 
2403
2505
  /* shortcuts modal (ui/shortcuts.ts) — prototype lines 226–232 */
2404
2506
  .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; }
@@ -2605,6 +2707,7 @@ button { font: inherit; color: inherit; background: none; border: 0; cursor: poi
2605
2707
  this.#minUi = createMinimizeUi(document);
2606
2708
  shadow.appendChild(this.#minUi.morphWrap);
2607
2709
  shadow.appendChild(this.#minUi.puck);
2710
+ shadow.appendChild(this.#minUi.fan);
2608
2711
  this.#drawer = createDrawer(document, {
2609
2712
  onActivate: (pinId) => this.#activateFromInbox(pinId),
2610
2713
  onClose: () => this.store.update({ inboxOpen: false })
@@ -2629,7 +2732,12 @@ button { font: inherit; color: inherit; background: none; border: 0; cursor: poi
2629
2732
  storagePrefix: `pinbox:${this.config?.endpoint ?? ""}`,
2630
2733
  reduced: window.matchMedia("(prefers-reduced-motion: reduce)").matches,
2631
2734
  initialMinimized: this.config?.minimized === true,
2632
- onSettled: (minimized, keyboard) => this.#onMinimizeSettled(minimized, keyboard)
2735
+ onSettled: (minimized, keyboard) => this.#onMinimizeSettled(minimized, keyboard),
2736
+ onFanAction: (action) => {
2737
+ if (action === "pin") this.#togglePlacing();
2738
+ else if (action === "inbox") this.#toggleInbox();
2739
+ else this.#toggleTheme();
2740
+ }
2633
2741
  });
2634
2742
  this.#min.applyInitial();
2635
2743
  }
@@ -2656,11 +2764,6 @@ button { font: inherit; color: inherit; background: none; border: 0; cursor: poi
2656
2764
  restore(keyboard = false) {
2657
2765
  this.#min?.restore(keyboard);
2658
2766
  }
2659
- /** Bar-surface shortcuts pressed while minimized restore the bar, then run. */
2660
- #surfaced(run) {
2661
- if (this.#min?.minimized() === true) this.restore(false);
2662
- run();
2663
- }
2664
2767
  /**
2665
2768
  * Task 8 wiring: WS events mutate the store, connection state renders in the
2666
2769
  * bar, card actions hit the hub. The mirror seeds pins so an offline reload
@@ -2886,9 +2989,12 @@ button { font: inherit; color: inherit; background: none; border: 0; cursor: poi
2886
2989
  };
2887
2990
  /** Prototype keyboard map (v2-command-bar.html lines 701–712) + M (v3 minimize). */
2888
2991
  #shortcuts = {
2889
- escape: () => this.#dismiss(),
2890
- p: () => this.#surfaced(() => this.#togglePlacing()),
2891
- i: () => this.#surfaced(() => this.#toggleInbox()),
2992
+ escape: () => {
2993
+ if (this.#min?.closeFan() === true) return;
2994
+ this.#dismiss();
2995
+ },
2996
+ p: () => this.#togglePlacing(),
2997
+ i: () => this.#toggleInbox(),
2892
2998
  d: () => this.#toggleTheme(),
2893
2999
  r: () => this.#resolveActive(),
2894
3000
  c: () => this.#copyOpenPins(),
package/dist/vue.js CHANGED
@@ -1,4 +1,4 @@
1
- import { n as defineToolbarElement, r as PinboxToolbarElement } from "./src-ChTPrRo5.js";
1
+ import { n as defineToolbarElement, r as PinboxToolbarElement } from "./src-C-gslo8y.js";
2
2
  import { h } from "vue";
3
3
  //#region src/vue.ts
4
4
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@autono/pinbox-toolbar",
3
- "version": "0.10.0",
3
+ "version": "0.12.0",
4
4
  "type": "module",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -71,7 +71,7 @@
71
71
  }
72
72
  },
73
73
  "devDependencies": {
74
- "@autono/pinbox-core": "0.10.0",
74
+ "@autono/pinbox-core": "0.12.0",
75
75
  "@types/react": "^19.2.0",
76
76
  "typescript": "^7.0.0",
77
77
  "tsdown": "^0.22.0",