@autono/pinbox-toolbar 0.11.0 → 0.13.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 +1 -1
- package/dist/react.js +1 -1
- package/dist/{src-ChTPrRo5.js → src-C3zvwQrr.js} +156 -23
- package/dist/svelte.js +1 -1
- package/dist/toolbar.iife.js +156 -23
- package/dist/vue.js +1 -1
- package/package.json +2 -2
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-
|
|
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-C3zvwQrr.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-
|
|
1
|
+
import { n as defineToolbarElement, r as PinboxToolbarElement } from "./src-C3zvwQrr.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
|
-
|
|
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
|
|
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
|
}
|
|
@@ -772,6 +837,39 @@ async function firstFrame(win, stream) {
|
|
|
772
837
|
});
|
|
773
838
|
return video;
|
|
774
839
|
}
|
|
840
|
+
/**
|
|
841
|
+
* The one live capture stream, reused across pins. getDisplayMedia MUST
|
|
842
|
+
* prompt on every call (spec — no persistent grant exists), so the only way
|
|
843
|
+
* to stop asking per pin is to never re-call it: prompt once, keep the track,
|
|
844
|
+
* and read a fresh frame from the still-playing video for every capture.
|
|
845
|
+
* The browser's "sharing this tab" indicator stays on while the track lives —
|
|
846
|
+
* honest, and the user ending it from there simply re-prompts on the next pin.
|
|
847
|
+
*/
|
|
848
|
+
let liveCapture = null;
|
|
849
|
+
async function captureVideo(win, media) {
|
|
850
|
+
const track = liveCapture?.stream.getVideoTracks()[0];
|
|
851
|
+
if (liveCapture && track?.readyState === "live") return liveCapture.video;
|
|
852
|
+
releaseCapture();
|
|
853
|
+
const stream = await media.getDisplayMedia({
|
|
854
|
+
video: true,
|
|
855
|
+
audio: false,
|
|
856
|
+
preferCurrentTab: true
|
|
857
|
+
});
|
|
858
|
+
const video = await firstFrame(win, stream);
|
|
859
|
+
stream.getVideoTracks()[0]?.addEventListener("ended", releaseCapture, { once: true });
|
|
860
|
+
liveCapture = {
|
|
861
|
+
stream,
|
|
862
|
+
video
|
|
863
|
+
};
|
|
864
|
+
return video;
|
|
865
|
+
}
|
|
866
|
+
/** Stop the cached stream (toolbar disconnect; also the track-ended handler). */
|
|
867
|
+
function releaseCapture() {
|
|
868
|
+
if (liveCapture === null) return;
|
|
869
|
+
for (const t of liveCapture.stream.getTracks()) t.stop();
|
|
870
|
+
liveCapture.video.srcObject = null;
|
|
871
|
+
liveCapture = null;
|
|
872
|
+
}
|
|
775
873
|
/** webp-encode a bitmap; also emit the ≤32px placeholder data URL. */
|
|
776
874
|
async function encode(bmp) {
|
|
777
875
|
const canvas = new OffscreenCanvas(bmp.width, bmp.height);
|
|
@@ -811,21 +909,14 @@ async function captureElement(el) {
|
|
|
811
909
|
const crop = visibleCropRect(el);
|
|
812
910
|
if (source === null || crop === null) return null;
|
|
813
911
|
const { win, media } = source;
|
|
814
|
-
let stream = null;
|
|
815
912
|
try {
|
|
816
|
-
|
|
817
|
-
video: true,
|
|
818
|
-
audio: false,
|
|
819
|
-
preferCurrentTab: true
|
|
820
|
-
});
|
|
821
|
-
const video = await firstFrame(win, stream);
|
|
913
|
+
const video = await captureVideo(win, media);
|
|
822
914
|
const sx = video.videoWidth / win.innerWidth;
|
|
823
915
|
const sy = video.videoHeight / win.innerHeight;
|
|
824
916
|
return await encode(await createImageBitmap(video, Math.round(crop.x * sx), Math.round(crop.y * sy), Math.max(1, Math.round(crop.width * sx)), Math.max(1, Math.round(crop.height * sy))));
|
|
825
917
|
} catch {
|
|
918
|
+
releaseCapture();
|
|
826
919
|
return null;
|
|
827
|
-
} finally {
|
|
828
|
-
if (stream) for (const track of stream.getTracks()) track.stop();
|
|
829
920
|
}
|
|
830
921
|
}
|
|
831
922
|
/**
|
|
@@ -2077,12 +2168,25 @@ function renderPins(layer, state) {
|
|
|
2077
2168
|
//#region src/ui/puck.ts
|
|
2078
2169
|
/** The bar's ident mark, sized up for the 48px puck face. */
|
|
2079
2170
|
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>";
|
|
2171
|
+
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>";
|
|
2172
|
+
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>";
|
|
2173
|
+
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>";
|
|
2174
|
+
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>";
|
|
2175
|
+
function fanItem(act, index, icon, label, key) {
|
|
2176
|
+
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>`;
|
|
2177
|
+
}
|
|
2080
2178
|
function createMinimizeUi(doc) {
|
|
2081
2179
|
const puck = doc.createElement("button");
|
|
2082
2180
|
puck.type = "button";
|
|
2083
2181
|
puck.className = "pb-puck pb-ghost";
|
|
2084
|
-
puck.setAttribute("aria-label", "
|
|
2182
|
+
puck.setAttribute("aria-label", "Pinbox menu");
|
|
2183
|
+
puck.setAttribute("aria-haspopup", "menu");
|
|
2085
2184
|
puck.innerHTML = `<span class="in">${PUCK_ICON}</span><span class="badge" data-ref="count" hidden>0</span><span class="cdot"></span>`;
|
|
2185
|
+
const fan = doc.createElement("div");
|
|
2186
|
+
fan.className = "pb-fan";
|
|
2187
|
+
fan.hidden = true;
|
|
2188
|
+
fan.setAttribute("role", "menu");
|
|
2189
|
+
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
2190
|
const morphWrap = doc.createElement("div");
|
|
2087
2191
|
morphWrap.className = "pb-morph-wrap";
|
|
2088
2192
|
morphWrap.hidden = true;
|
|
@@ -2093,9 +2197,14 @@ function createMinimizeUi(doc) {
|
|
|
2093
2197
|
carrier.innerHTML = `${PUCK_ICON}<span class="badge" data-ref="count" hidden>0</span>`;
|
|
2094
2198
|
morphWrap.appendChild(surface);
|
|
2095
2199
|
morphWrap.appendChild(carrier);
|
|
2096
|
-
const badges = [
|
|
2200
|
+
const badges = [
|
|
2201
|
+
puck.querySelector("[data-ref=\"count\"]"),
|
|
2202
|
+
carrier.querySelector("[data-ref=\"count\"]"),
|
|
2203
|
+
fan.querySelector("[data-ref=\"count\"]")
|
|
2204
|
+
];
|
|
2097
2205
|
return {
|
|
2098
2206
|
puck,
|
|
2207
|
+
fan,
|
|
2099
2208
|
morphWrap,
|
|
2100
2209
|
surface,
|
|
2101
2210
|
carrier,
|
|
@@ -2107,6 +2216,7 @@ function createMinimizeUi(doc) {
|
|
|
2107
2216
|
}
|
|
2108
2217
|
const degraded = state.connection === "offline" || state.connection === "incompatible";
|
|
2109
2218
|
puck.classList.toggle("degraded", degraded);
|
|
2219
|
+
puck.classList.toggle("armed", state.mode === "placing");
|
|
2110
2220
|
}
|
|
2111
2221
|
};
|
|
2112
2222
|
}
|
|
@@ -2393,10 +2503,28 @@ button { font: inherit; color: inherit; background: none; border: 0; cursor: poi
|
|
|
2393
2503
|
.pb-puck:active { cursor: grabbing; }
|
|
2394
2504
|
.pb-puck .in { display: flex; transition: transform 160ms var(--pb-ease); }
|
|
2395
2505
|
.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; }
|
|
2506
|
+
.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
2507
|
/* The bar says "· OFFLINE" in words; the puck's amber dot is the same signal. */
|
|
2398
2508
|
.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
2509
|
.pb-puck.degraded .cdot { display: block; }
|
|
2510
|
+
/* Placing armed from the fan: the bar's armed-ring is hidden with the bar. */
|
|
2511
|
+
.pb-puck.armed { border-color: var(--pb-amber); box-shadow: 0 0 0 4px var(--pb-amber-soft), var(--pb-shadow); }
|
|
2512
|
+
|
|
2513
|
+
/* puck fan menu (ui/puck.ts, minimize.ts) — tap the puck and a vertical
|
|
2514
|
+
quick-menu fans out of it; EXPAND is how the bar comes back. Items stagger
|
|
2515
|
+
from the puck; hovering slides a label + key chip toward screen center. */
|
|
2516
|
+
.pb-fan { position: fixed; z-index: 96; display: flex; flex-direction: column; gap: 6px; align-items: center; }
|
|
2517
|
+
.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); }
|
|
2518
|
+
.pb-fan.up { --fan-from: translateY(16px); }
|
|
2519
|
+
.pb-fan.down { --fan-from: translateY(-16px); }
|
|
2520
|
+
.pb-fan.on .pb-fan-item { opacity: 1; transform: none; }
|
|
2521
|
+
.pb-fan-item:hover { color: var(--pb-amber); border-color: var(--pb-amber); }
|
|
2522
|
+
.pb-fan-item .badge { pointer-events: none; }
|
|
2523
|
+
.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); }
|
|
2524
|
+
.pb-fan.labels-right .fl { left: calc(100% + 10px); transform: translateY(-50%) translateX(-6px); }
|
|
2525
|
+
.pb-fan.labels-left .fl { right: calc(100% + 10px); transform: translateY(-50%) translateX(6px); }
|
|
2526
|
+
.pb-fan-item:hover .fl { opacity: 1; transform: translateY(-50%) translateX(0); }
|
|
2527
|
+
.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
2528
|
|
|
2401
2529
|
/* shortcuts modal (ui/shortcuts.ts) — prototype lines 226–232 */
|
|
2402
2530
|
.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; }
|
|
@@ -2543,6 +2671,7 @@ var PinboxToolbarElement = class extends BaseElement {
|
|
|
2543
2671
|
this.#aim = null;
|
|
2544
2672
|
this.#min?.destroy();
|
|
2545
2673
|
this.#min = null;
|
|
2674
|
+
releaseCapture();
|
|
2546
2675
|
this.#unsubscribe?.();
|
|
2547
2676
|
this.#unsubscribe = null;
|
|
2548
2677
|
this.#pageStyle?.remove();
|
|
@@ -2603,6 +2732,7 @@ var PinboxToolbarElement = class extends BaseElement {
|
|
|
2603
2732
|
this.#minUi = createMinimizeUi(document);
|
|
2604
2733
|
shadow.appendChild(this.#minUi.morphWrap);
|
|
2605
2734
|
shadow.appendChild(this.#minUi.puck);
|
|
2735
|
+
shadow.appendChild(this.#minUi.fan);
|
|
2606
2736
|
this.#drawer = createDrawer(document, {
|
|
2607
2737
|
onActivate: (pinId) => this.#activateFromInbox(pinId),
|
|
2608
2738
|
onClose: () => this.store.update({ inboxOpen: false })
|
|
@@ -2627,7 +2757,12 @@ var PinboxToolbarElement = class extends BaseElement {
|
|
|
2627
2757
|
storagePrefix: `pinbox:${this.config?.endpoint ?? ""}`,
|
|
2628
2758
|
reduced: window.matchMedia("(prefers-reduced-motion: reduce)").matches,
|
|
2629
2759
|
initialMinimized: this.config?.minimized === true,
|
|
2630
|
-
onSettled: (minimized, keyboard) => this.#onMinimizeSettled(minimized, keyboard)
|
|
2760
|
+
onSettled: (minimized, keyboard) => this.#onMinimizeSettled(minimized, keyboard),
|
|
2761
|
+
onFanAction: (action) => {
|
|
2762
|
+
if (action === "pin") this.#togglePlacing();
|
|
2763
|
+
else if (action === "inbox") this.#toggleInbox();
|
|
2764
|
+
else this.#toggleTheme();
|
|
2765
|
+
}
|
|
2631
2766
|
});
|
|
2632
2767
|
this.#min.applyInitial();
|
|
2633
2768
|
}
|
|
@@ -2654,11 +2789,6 @@ var PinboxToolbarElement = class extends BaseElement {
|
|
|
2654
2789
|
restore(keyboard = false) {
|
|
2655
2790
|
this.#min?.restore(keyboard);
|
|
2656
2791
|
}
|
|
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
2792
|
/**
|
|
2663
2793
|
* Task 8 wiring: WS events mutate the store, connection state renders in the
|
|
2664
2794
|
* bar, card actions hit the hub. The mirror seeds pins so an offline reload
|
|
@@ -2884,9 +3014,12 @@ var PinboxToolbarElement = class extends BaseElement {
|
|
|
2884
3014
|
};
|
|
2885
3015
|
/** Prototype keyboard map (v2-command-bar.html lines 701–712) + M (v3 minimize). */
|
|
2886
3016
|
#shortcuts = {
|
|
2887
|
-
escape: () =>
|
|
2888
|
-
|
|
2889
|
-
|
|
3017
|
+
escape: () => {
|
|
3018
|
+
if (this.#min?.closeFan() === true) return;
|
|
3019
|
+
this.#dismiss();
|
|
3020
|
+
},
|
|
3021
|
+
p: () => this.#togglePlacing(),
|
|
3022
|
+
i: () => this.#toggleInbox(),
|
|
2890
3023
|
d: () => this.#toggleTheme(),
|
|
2891
3024
|
r: () => this.#resolveActive(),
|
|
2892
3025
|
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-
|
|
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-C3zvwQrr.js";
|
|
2
2
|
//#region src/svelte.ts
|
|
3
3
|
/**
|
|
4
4
|
* `<div use:pinbox={{ endpoint }} />` — creates + configures the element BEFORE insertion
|
package/dist/toolbar.iife.js
CHANGED
|
@@ -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
|
-
|
|
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
|
|
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
|
}
|
|
@@ -774,6 +839,39 @@ var Pinbox = (function(exports) {
|
|
|
774
839
|
});
|
|
775
840
|
return video;
|
|
776
841
|
}
|
|
842
|
+
/**
|
|
843
|
+
* The one live capture stream, reused across pins. getDisplayMedia MUST
|
|
844
|
+
* prompt on every call (spec — no persistent grant exists), so the only way
|
|
845
|
+
* to stop asking per pin is to never re-call it: prompt once, keep the track,
|
|
846
|
+
* and read a fresh frame from the still-playing video for every capture.
|
|
847
|
+
* The browser's "sharing this tab" indicator stays on while the track lives —
|
|
848
|
+
* honest, and the user ending it from there simply re-prompts on the next pin.
|
|
849
|
+
*/
|
|
850
|
+
let liveCapture = null;
|
|
851
|
+
async function captureVideo(win, media) {
|
|
852
|
+
const track = liveCapture?.stream.getVideoTracks()[0];
|
|
853
|
+
if (liveCapture && track?.readyState === "live") return liveCapture.video;
|
|
854
|
+
releaseCapture();
|
|
855
|
+
const stream = await media.getDisplayMedia({
|
|
856
|
+
video: true,
|
|
857
|
+
audio: false,
|
|
858
|
+
preferCurrentTab: true
|
|
859
|
+
});
|
|
860
|
+
const video = await firstFrame(win, stream);
|
|
861
|
+
stream.getVideoTracks()[0]?.addEventListener("ended", releaseCapture, { once: true });
|
|
862
|
+
liveCapture = {
|
|
863
|
+
stream,
|
|
864
|
+
video
|
|
865
|
+
};
|
|
866
|
+
return video;
|
|
867
|
+
}
|
|
868
|
+
/** Stop the cached stream (toolbar disconnect; also the track-ended handler). */
|
|
869
|
+
function releaseCapture() {
|
|
870
|
+
if (liveCapture === null) return;
|
|
871
|
+
for (const t of liveCapture.stream.getTracks()) t.stop();
|
|
872
|
+
liveCapture.video.srcObject = null;
|
|
873
|
+
liveCapture = null;
|
|
874
|
+
}
|
|
777
875
|
/** webp-encode a bitmap; also emit the ≤32px placeholder data URL. */
|
|
778
876
|
async function encode(bmp) {
|
|
779
877
|
const canvas = new OffscreenCanvas(bmp.width, bmp.height);
|
|
@@ -813,21 +911,14 @@ var Pinbox = (function(exports) {
|
|
|
813
911
|
const crop = visibleCropRect(el);
|
|
814
912
|
if (source === null || crop === null) return null;
|
|
815
913
|
const { win, media } = source;
|
|
816
|
-
let stream = null;
|
|
817
914
|
try {
|
|
818
|
-
|
|
819
|
-
video: true,
|
|
820
|
-
audio: false,
|
|
821
|
-
preferCurrentTab: true
|
|
822
|
-
});
|
|
823
|
-
const video = await firstFrame(win, stream);
|
|
915
|
+
const video = await captureVideo(win, media);
|
|
824
916
|
const sx = video.videoWidth / win.innerWidth;
|
|
825
917
|
const sy = video.videoHeight / win.innerHeight;
|
|
826
918
|
return await encode(await createImageBitmap(video, Math.round(crop.x * sx), Math.round(crop.y * sy), Math.max(1, Math.round(crop.width * sx)), Math.max(1, Math.round(crop.height * sy))));
|
|
827
919
|
} catch {
|
|
920
|
+
releaseCapture();
|
|
828
921
|
return null;
|
|
829
|
-
} finally {
|
|
830
|
-
if (stream) for (const track of stream.getTracks()) track.stop();
|
|
831
922
|
}
|
|
832
923
|
}
|
|
833
924
|
/**
|
|
@@ -2079,12 +2170,25 @@ var Pinbox = (function(exports) {
|
|
|
2079
2170
|
//#region src/ui/puck.ts
|
|
2080
2171
|
/** The bar's ident mark, sized up for the 48px puck face. */
|
|
2081
2172
|
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>";
|
|
2173
|
+
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>";
|
|
2174
|
+
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>";
|
|
2175
|
+
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>";
|
|
2176
|
+
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>";
|
|
2177
|
+
function fanItem(act, index, icon, label, key) {
|
|
2178
|
+
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>`;
|
|
2179
|
+
}
|
|
2082
2180
|
function createMinimizeUi(doc) {
|
|
2083
2181
|
const puck = doc.createElement("button");
|
|
2084
2182
|
puck.type = "button";
|
|
2085
2183
|
puck.className = "pb-puck pb-ghost";
|
|
2086
|
-
puck.setAttribute("aria-label", "
|
|
2184
|
+
puck.setAttribute("aria-label", "Pinbox menu");
|
|
2185
|
+
puck.setAttribute("aria-haspopup", "menu");
|
|
2087
2186
|
puck.innerHTML = `<span class="in">${PUCK_ICON}</span><span class="badge" data-ref="count" hidden>0</span><span class="cdot"></span>`;
|
|
2187
|
+
const fan = doc.createElement("div");
|
|
2188
|
+
fan.className = "pb-fan";
|
|
2189
|
+
fan.hidden = true;
|
|
2190
|
+
fan.setAttribute("role", "menu");
|
|
2191
|
+
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
2192
|
const morphWrap = doc.createElement("div");
|
|
2089
2193
|
morphWrap.className = "pb-morph-wrap";
|
|
2090
2194
|
morphWrap.hidden = true;
|
|
@@ -2095,9 +2199,14 @@ var Pinbox = (function(exports) {
|
|
|
2095
2199
|
carrier.innerHTML = `${PUCK_ICON}<span class="badge" data-ref="count" hidden>0</span>`;
|
|
2096
2200
|
morphWrap.appendChild(surface);
|
|
2097
2201
|
morphWrap.appendChild(carrier);
|
|
2098
|
-
const badges = [
|
|
2202
|
+
const badges = [
|
|
2203
|
+
puck.querySelector("[data-ref=\"count\"]"),
|
|
2204
|
+
carrier.querySelector("[data-ref=\"count\"]"),
|
|
2205
|
+
fan.querySelector("[data-ref=\"count\"]")
|
|
2206
|
+
];
|
|
2099
2207
|
return {
|
|
2100
2208
|
puck,
|
|
2209
|
+
fan,
|
|
2101
2210
|
morphWrap,
|
|
2102
2211
|
surface,
|
|
2103
2212
|
carrier,
|
|
@@ -2109,6 +2218,7 @@ var Pinbox = (function(exports) {
|
|
|
2109
2218
|
}
|
|
2110
2219
|
const degraded = state.connection === "offline" || state.connection === "incompatible";
|
|
2111
2220
|
puck.classList.toggle("degraded", degraded);
|
|
2221
|
+
puck.classList.toggle("armed", state.mode === "placing");
|
|
2112
2222
|
}
|
|
2113
2223
|
};
|
|
2114
2224
|
}
|
|
@@ -2395,10 +2505,28 @@ button { font: inherit; color: inherit; background: none; border: 0; cursor: poi
|
|
|
2395
2505
|
.pb-puck:active { cursor: grabbing; }
|
|
2396
2506
|
.pb-puck .in { display: flex; transition: transform 160ms var(--pb-ease); }
|
|
2397
2507
|
.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; }
|
|
2508
|
+
.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
2509
|
/* The bar says "· OFFLINE" in words; the puck's amber dot is the same signal. */
|
|
2400
2510
|
.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
2511
|
.pb-puck.degraded .cdot { display: block; }
|
|
2512
|
+
/* Placing armed from the fan: the bar's armed-ring is hidden with the bar. */
|
|
2513
|
+
.pb-puck.armed { border-color: var(--pb-amber); box-shadow: 0 0 0 4px var(--pb-amber-soft), var(--pb-shadow); }
|
|
2514
|
+
|
|
2515
|
+
/* puck fan menu (ui/puck.ts, minimize.ts) — tap the puck and a vertical
|
|
2516
|
+
quick-menu fans out of it; EXPAND is how the bar comes back. Items stagger
|
|
2517
|
+
from the puck; hovering slides a label + key chip toward screen center. */
|
|
2518
|
+
.pb-fan { position: fixed; z-index: 96; display: flex; flex-direction: column; gap: 6px; align-items: center; }
|
|
2519
|
+
.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); }
|
|
2520
|
+
.pb-fan.up { --fan-from: translateY(16px); }
|
|
2521
|
+
.pb-fan.down { --fan-from: translateY(-16px); }
|
|
2522
|
+
.pb-fan.on .pb-fan-item { opacity: 1; transform: none; }
|
|
2523
|
+
.pb-fan-item:hover { color: var(--pb-amber); border-color: var(--pb-amber); }
|
|
2524
|
+
.pb-fan-item .badge { pointer-events: none; }
|
|
2525
|
+
.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); }
|
|
2526
|
+
.pb-fan.labels-right .fl { left: calc(100% + 10px); transform: translateY(-50%) translateX(-6px); }
|
|
2527
|
+
.pb-fan.labels-left .fl { right: calc(100% + 10px); transform: translateY(-50%) translateX(6px); }
|
|
2528
|
+
.pb-fan-item:hover .fl { opacity: 1; transform: translateY(-50%) translateX(0); }
|
|
2529
|
+
.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
2530
|
|
|
2403
2531
|
/* shortcuts modal (ui/shortcuts.ts) — prototype lines 226–232 */
|
|
2404
2532
|
.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; }
|
|
@@ -2545,6 +2673,7 @@ button { font: inherit; color: inherit; background: none; border: 0; cursor: poi
|
|
|
2545
2673
|
this.#aim = null;
|
|
2546
2674
|
this.#min?.destroy();
|
|
2547
2675
|
this.#min = null;
|
|
2676
|
+
releaseCapture();
|
|
2548
2677
|
this.#unsubscribe?.();
|
|
2549
2678
|
this.#unsubscribe = null;
|
|
2550
2679
|
this.#pageStyle?.remove();
|
|
@@ -2605,6 +2734,7 @@ button { font: inherit; color: inherit; background: none; border: 0; cursor: poi
|
|
|
2605
2734
|
this.#minUi = createMinimizeUi(document);
|
|
2606
2735
|
shadow.appendChild(this.#minUi.morphWrap);
|
|
2607
2736
|
shadow.appendChild(this.#minUi.puck);
|
|
2737
|
+
shadow.appendChild(this.#minUi.fan);
|
|
2608
2738
|
this.#drawer = createDrawer(document, {
|
|
2609
2739
|
onActivate: (pinId) => this.#activateFromInbox(pinId),
|
|
2610
2740
|
onClose: () => this.store.update({ inboxOpen: false })
|
|
@@ -2629,7 +2759,12 @@ button { font: inherit; color: inherit; background: none; border: 0; cursor: poi
|
|
|
2629
2759
|
storagePrefix: `pinbox:${this.config?.endpoint ?? ""}`,
|
|
2630
2760
|
reduced: window.matchMedia("(prefers-reduced-motion: reduce)").matches,
|
|
2631
2761
|
initialMinimized: this.config?.minimized === true,
|
|
2632
|
-
onSettled: (minimized, keyboard) => this.#onMinimizeSettled(minimized, keyboard)
|
|
2762
|
+
onSettled: (minimized, keyboard) => this.#onMinimizeSettled(minimized, keyboard),
|
|
2763
|
+
onFanAction: (action) => {
|
|
2764
|
+
if (action === "pin") this.#togglePlacing();
|
|
2765
|
+
else if (action === "inbox") this.#toggleInbox();
|
|
2766
|
+
else this.#toggleTheme();
|
|
2767
|
+
}
|
|
2633
2768
|
});
|
|
2634
2769
|
this.#min.applyInitial();
|
|
2635
2770
|
}
|
|
@@ -2656,11 +2791,6 @@ button { font: inherit; color: inherit; background: none; border: 0; cursor: poi
|
|
|
2656
2791
|
restore(keyboard = false) {
|
|
2657
2792
|
this.#min?.restore(keyboard);
|
|
2658
2793
|
}
|
|
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
2794
|
/**
|
|
2665
2795
|
* Task 8 wiring: WS events mutate the store, connection state renders in the
|
|
2666
2796
|
* bar, card actions hit the hub. The mirror seeds pins so an offline reload
|
|
@@ -2886,9 +3016,12 @@ button { font: inherit; color: inherit; background: none; border: 0; cursor: poi
|
|
|
2886
3016
|
};
|
|
2887
3017
|
/** Prototype keyboard map (v2-command-bar.html lines 701–712) + M (v3 minimize). */
|
|
2888
3018
|
#shortcuts = {
|
|
2889
|
-
escape: () =>
|
|
2890
|
-
|
|
2891
|
-
|
|
3019
|
+
escape: () => {
|
|
3020
|
+
if (this.#min?.closeFan() === true) return;
|
|
3021
|
+
this.#dismiss();
|
|
3022
|
+
},
|
|
3023
|
+
p: () => this.#togglePlacing(),
|
|
3024
|
+
i: () => this.#toggleInbox(),
|
|
2892
3025
|
d: () => this.#toggleTheme(),
|
|
2893
3026
|
r: () => this.#resolveActive(),
|
|
2894
3027
|
c: () => this.#copyOpenPins(),
|
package/dist/vue.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@autono/pinbox-toolbar",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.13.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.
|
|
74
|
+
"@autono/pinbox-core": "0.13.0",
|
|
75
75
|
"@types/react": "^19.2.0",
|
|
76
76
|
"typescript": "^7.0.0",
|
|
77
77
|
"tsdown": "^0.22.0",
|