@gallerist/plugin-pins 0.0.0-stage → 0.3.1
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/LICENSE +21 -0
- package/README.md +203 -2
- package/dist/index.cjs +491 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +130 -0
- package/dist/index.d.ts +130 -0
- package/dist/index.js +487 -0
- package/dist/index.js.map +1 -0
- package/package.json +70 -3
package/dist/index.js
ADDED
|
@@ -0,0 +1,487 @@
|
|
|
1
|
+
import { safeUrl, reportHandlerError, ICONS } from '@gallerist/core/internal';
|
|
2
|
+
|
|
3
|
+
// src/index.ts
|
|
4
|
+
var DEFAULT_PINS_STRINGS = {
|
|
5
|
+
toggle: "Show pins (P)",
|
|
6
|
+
add: "Add a comment (C)",
|
|
7
|
+
placing: "Click the image to place the comment. Esc cancels.",
|
|
8
|
+
pin: (n, label) => label ? `${n}. ${label}` : `Pin ${n}`,
|
|
9
|
+
close: "Close",
|
|
10
|
+
open: "View",
|
|
11
|
+
resolved: "Resolved"
|
|
12
|
+
};
|
|
13
|
+
var make = (tag, cls) => {
|
|
14
|
+
const node = document.createElement(tag);
|
|
15
|
+
if (cls) node.className = cls;
|
|
16
|
+
return node;
|
|
17
|
+
};
|
|
18
|
+
function defaultPinCard(pin, opts = {}) {
|
|
19
|
+
const t = { ...DEFAULT_PINS_STRINGS, ...opts.strings };
|
|
20
|
+
const frag = document.createDocumentFragment();
|
|
21
|
+
const add = (cls, text, tag = "div", to = frag) => {
|
|
22
|
+
const node = make(tag, cls);
|
|
23
|
+
if (text) node.textContent = text;
|
|
24
|
+
return to.appendChild(node);
|
|
25
|
+
};
|
|
26
|
+
const line = (cls, text, tag, to) => text && add(cls, text, tag, to);
|
|
27
|
+
const head = (to, who, when, badge) => {
|
|
28
|
+
const h = add("gallerist-pin-head", "", "div", to);
|
|
29
|
+
line("gallerist-pin-title", who, "span", h);
|
|
30
|
+
line("gallerist-pin-meta", when, "span", h);
|
|
31
|
+
line("gallerist-pin-badge", badge, "span", h);
|
|
32
|
+
};
|
|
33
|
+
if (opts.presentation === "comment") {
|
|
34
|
+
head(frag, pin.label, pin.meta, pin.resolved && t.resolved);
|
|
35
|
+
line("gallerist-pin-body", pin.body, "p");
|
|
36
|
+
if (pin.replies?.length) {
|
|
37
|
+
const list = add("gallerist-pin-replies", "", "ol");
|
|
38
|
+
for (const reply of pin.replies) {
|
|
39
|
+
const li = add("", "", "li", list);
|
|
40
|
+
head(li, reply.author, reply.meta);
|
|
41
|
+
add("gallerist-pin-body", reply.body, "p", li);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
} else {
|
|
45
|
+
line("gallerist-pin-title", pin.label);
|
|
46
|
+
line("gallerist-pin-meta", pin.meta);
|
|
47
|
+
line("gallerist-pin-body", pin.body, "p");
|
|
48
|
+
}
|
|
49
|
+
const href = pin.href && safeLink(pin.href);
|
|
50
|
+
if (href)
|
|
51
|
+
add("gallerist-pin-link", pin.linkLabel ?? t.open, "a").href = href;
|
|
52
|
+
return frag;
|
|
53
|
+
}
|
|
54
|
+
var button = (cls, label, icon = "") => {
|
|
55
|
+
const b = make("button", cls);
|
|
56
|
+
b.type = "button";
|
|
57
|
+
b.setAttribute("aria-label", label);
|
|
58
|
+
b.innerHTML = icon;
|
|
59
|
+
return b;
|
|
60
|
+
};
|
|
61
|
+
var LINK_SCHEMES = ["http:", "https:", "mailto:", "tel:"];
|
|
62
|
+
var safeLink = (url) => safeUrl(url, LINK_SCHEMES);
|
|
63
|
+
var defaultPins = (item) => item.meta?.pins;
|
|
64
|
+
var uid = 0;
|
|
65
|
+
function pins(opts = {}) {
|
|
66
|
+
const presentation = opts.presentation ?? "tag";
|
|
67
|
+
const read = opts.pins ?? defaultPins;
|
|
68
|
+
const t = { ...DEFAULT_PINS_STRINGS, ...opts.strings };
|
|
69
|
+
let visible = opts.visible ?? true;
|
|
70
|
+
let bound;
|
|
71
|
+
let root = null;
|
|
72
|
+
let toggleBtn = null;
|
|
73
|
+
let addBtn = null;
|
|
74
|
+
let rendered = [];
|
|
75
|
+
let openId = null;
|
|
76
|
+
let placing = false;
|
|
77
|
+
let cleanup;
|
|
78
|
+
const instance = ++uid;
|
|
79
|
+
const live = () => {
|
|
80
|
+
rendered = rendered.filter((r) => r.el.closest(".gallerist"));
|
|
81
|
+
return rendered;
|
|
82
|
+
};
|
|
83
|
+
const viewerRoot = () => {
|
|
84
|
+
if (!root) root = live()[0]?.el.closest(".gallerist") ?? null;
|
|
85
|
+
return root;
|
|
86
|
+
};
|
|
87
|
+
const placeCard = (r) => {
|
|
88
|
+
const box = (sel) => viewerRoot()?.querySelector(sel)?.getBoundingClientRect();
|
|
89
|
+
const s = box(".gallerist-stage");
|
|
90
|
+
if (!s?.width) return;
|
|
91
|
+
const c = box(".gallerist-controls");
|
|
92
|
+
const k = c?.width ? c : s;
|
|
93
|
+
const n = box(".gallerist > .gallerist-itemnav");
|
|
94
|
+
const m = r.marker.getBoundingClientRect();
|
|
95
|
+
const at = r.el.getBoundingClientRect();
|
|
96
|
+
const top = Math.max(s.top, k.top) + 8;
|
|
97
|
+
let bottom = Math.min(s.bottom, k.bottom) - 8;
|
|
98
|
+
const left = Math.max(s.left, k.left) + 8;
|
|
99
|
+
const right = Math.min(s.right, k.right) - 8;
|
|
100
|
+
const toLeft = m.left - left > right - m.right;
|
|
101
|
+
const card = r.card;
|
|
102
|
+
const st = card.style;
|
|
103
|
+
st.maxWidth = `${Math.max(0, Math.min(280, right - left, Math.max(120, (toLeft ? m.left - left : right - m.right) - 4)))}px`;
|
|
104
|
+
const w = card.offsetWidth;
|
|
105
|
+
const x = Math.max(left, Math.min(toLeft ? m.left - 4 - w : m.right + 4, right - w));
|
|
106
|
+
st.left = `${x - at.left}px`;
|
|
107
|
+
if (n?.width && n.top < bottom && n.bottom > top && x < n.right && x + w > n.left)
|
|
108
|
+
bottom = n.top - 8;
|
|
109
|
+
st.maxHeight = `${Math.max(60, bottom - top)}px`;
|
|
110
|
+
st.top = `${Math.max(top, Math.min(m.top, bottom - card.offsetHeight)) - at.top}px`;
|
|
111
|
+
card.tabIndex = card.scrollHeight - 12 * (1 + card.tabIndex) > card.clientHeight + 1 ? 0 : -1;
|
|
112
|
+
};
|
|
113
|
+
let frame = 0;
|
|
114
|
+
const replace = () => {
|
|
115
|
+
cancelAnimationFrame(frame);
|
|
116
|
+
frame = requestAnimationFrame(() => shown && placeCard(shown));
|
|
117
|
+
};
|
|
118
|
+
const fit = typeof ResizeObserver === "function" ? new ResizeObserver(replace) : null;
|
|
119
|
+
const onOpenKey = (e) => {
|
|
120
|
+
if (e.key !== "Escape" || !openId) return;
|
|
121
|
+
if (shown && e.target instanceof Node && shown.card.contains(e.target)) return;
|
|
122
|
+
e.stopPropagation();
|
|
123
|
+
setOpen(null, true);
|
|
124
|
+
};
|
|
125
|
+
let keyRoot = null;
|
|
126
|
+
const calls = [];
|
|
127
|
+
let depth = 0;
|
|
128
|
+
const leaving = /* @__PURE__ */ new Set();
|
|
129
|
+
const fill = (r) => {
|
|
130
|
+
if (r.content !== void 0) return;
|
|
131
|
+
let node = null;
|
|
132
|
+
try {
|
|
133
|
+
node = opts.renderCard?.(r.pin, r.ctx) ?? null;
|
|
134
|
+
} catch (e) {
|
|
135
|
+
calls.push(() => {
|
|
136
|
+
throw e;
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
r.content = node;
|
|
140
|
+
r.card.append(node ?? defaultPinCard(r.pin, { presentation, strings: t }));
|
|
141
|
+
};
|
|
142
|
+
const empty = (r) => {
|
|
143
|
+
const node = r.content;
|
|
144
|
+
if (node === void 0) return;
|
|
145
|
+
r.content = void 0;
|
|
146
|
+
r.card.replaceChildren(r.x);
|
|
147
|
+
if (node && !leaving.has(node)) {
|
|
148
|
+
leaving.add(node);
|
|
149
|
+
calls.push(() => {
|
|
150
|
+
leaving.delete(node);
|
|
151
|
+
if (shown?.content !== node) opts.onCardRemove?.(r.pin, node);
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
};
|
|
155
|
+
const rescue = (id, from) => {
|
|
156
|
+
const a = document.activeElement;
|
|
157
|
+
if (a && a !== document.body && !from?.contains(a)) return;
|
|
158
|
+
const item = bound && currentCtx(bound)?.item;
|
|
159
|
+
for (const to of [
|
|
160
|
+
...live().filter((r) => r.pin.id === id && r.ctx.item === item).map((r) => r.marker),
|
|
161
|
+
addBtn,
|
|
162
|
+
viewerRoot()
|
|
163
|
+
]) {
|
|
164
|
+
to?.focus({ preventScroll: true, focusVisible: kbd });
|
|
165
|
+
if (to && document.activeElement === to) return;
|
|
166
|
+
}
|
|
167
|
+
};
|
|
168
|
+
const refocus = (id, card, force) => {
|
|
169
|
+
rescue(id, force ? document : card);
|
|
170
|
+
queueMicrotask(() => rescue(id, card));
|
|
171
|
+
};
|
|
172
|
+
let kbd = false;
|
|
173
|
+
const modality = (e) => {
|
|
174
|
+
const k = e;
|
|
175
|
+
kbd = e.type === "keydown" ? kbd || !(k.metaKey || k.ctrlKey || k.altKey) : e.type === "click" && !k.detail;
|
|
176
|
+
};
|
|
177
|
+
const inputs = ["keydown", "pointerdown", "click"];
|
|
178
|
+
let shown = null;
|
|
179
|
+
let want = null;
|
|
180
|
+
let focusedCard = null;
|
|
181
|
+
const setOpen = (id, focus = false) => {
|
|
182
|
+
depth++;
|
|
183
|
+
try {
|
|
184
|
+
change(id, focus);
|
|
185
|
+
} finally {
|
|
186
|
+
if (!--depth)
|
|
187
|
+
for (let f = calls.shift(); f; f = calls.shift()) {
|
|
188
|
+
depth++;
|
|
189
|
+
try {
|
|
190
|
+
f();
|
|
191
|
+
} catch (e) {
|
|
192
|
+
reportHandlerError(e);
|
|
193
|
+
}
|
|
194
|
+
depth--;
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
};
|
|
198
|
+
const change = (asked, focus) => {
|
|
199
|
+
const id = bound !== null ? asked : null;
|
|
200
|
+
if (id !== openId) want = null;
|
|
201
|
+
if (focus && id) want = { from: document.activeElement };
|
|
202
|
+
openId = id;
|
|
203
|
+
const prev = shown;
|
|
204
|
+
const a = document.activeElement;
|
|
205
|
+
const v = viewerRoot();
|
|
206
|
+
const item = bound && currentCtx(bound)?.item;
|
|
207
|
+
const had = !!prev && (prev.card.contains(a) || (!a || a === document.body) && (prev.ctx.item !== item || !prev.el.closest(".gallerist")) && (focusedCard === prev.card || !!v?.isConnected && v.hasAttribute("aria-modal")));
|
|
208
|
+
let opened = null;
|
|
209
|
+
for (const r of live()) {
|
|
210
|
+
const isOpen = !opened && r.pin.id === id && r.ctx.item === item;
|
|
211
|
+
if (isOpen) opened = r;
|
|
212
|
+
else empty(r);
|
|
213
|
+
r.card.hidden = !isOpen;
|
|
214
|
+
r.marker.setAttribute("aria-expanded", String(isOpen));
|
|
215
|
+
r.el.toggleAttribute("data-open", isOpen);
|
|
216
|
+
const host = r.el.parentElement;
|
|
217
|
+
if (host) host.style.zIndex = isOpen ? "30" : "20";
|
|
218
|
+
}
|
|
219
|
+
if (prev !== opened) focusedCard = null;
|
|
220
|
+
if (prev && prev !== opened) empty(prev);
|
|
221
|
+
fit?.disconnect();
|
|
222
|
+
if (opened) {
|
|
223
|
+
fill(opened);
|
|
224
|
+
placeCard(opened);
|
|
225
|
+
fit?.observe(opened.card);
|
|
226
|
+
if (opened.content instanceof Element) fit?.observe(opened.content);
|
|
227
|
+
}
|
|
228
|
+
if (opened && want) {
|
|
229
|
+
const now = document.activeElement;
|
|
230
|
+
if (now === want.from || !now || now === document.body || !want.from?.isConnected)
|
|
231
|
+
opened.card.focus({ preventScroll: true, focusVisible: kbd });
|
|
232
|
+
want = null;
|
|
233
|
+
} else if (bound && prev && prev !== opened && (had || focus && !id)) {
|
|
234
|
+
refocus(prev.pin.id, prev.card, focus);
|
|
235
|
+
}
|
|
236
|
+
shown = opened;
|
|
237
|
+
keyRoot?.removeEventListener("keydown", onOpenKey, true);
|
|
238
|
+
keyRoot = null;
|
|
239
|
+
if (openId) {
|
|
240
|
+
document.addEventListener("pointerdown", onOutside, true);
|
|
241
|
+
keyRoot = v;
|
|
242
|
+
keyRoot?.addEventListener("keydown", onOpenKey, true);
|
|
243
|
+
} else {
|
|
244
|
+
document.removeEventListener("pointerdown", onOutside, true);
|
|
245
|
+
}
|
|
246
|
+
const ctx = (opened ?? prev)?.ctx;
|
|
247
|
+
if (ctx && opened?.pin.id !== prev?.pin.id) {
|
|
248
|
+
const pin = opened ? opened.pin : null;
|
|
249
|
+
const previous = prev?.pin ?? null;
|
|
250
|
+
calls.push(() => opts.onOpenChange?.(pin, { ...ctx, previous }));
|
|
251
|
+
}
|
|
252
|
+
};
|
|
253
|
+
const onOutside = (e) => {
|
|
254
|
+
const target = e.target;
|
|
255
|
+
if (!shown?.el.isConnected || target && live().some((r) => r.el.contains(target))) return;
|
|
256
|
+
setOpen(null);
|
|
257
|
+
};
|
|
258
|
+
const setVisible = (v) => {
|
|
259
|
+
visible = v;
|
|
260
|
+
if (!v) {
|
|
261
|
+
setOpen(null);
|
|
262
|
+
const active = document.activeElement;
|
|
263
|
+
if (active && live().some((r) => r.el.contains(active))) {
|
|
264
|
+
const to = toggleBtn?.isConnected ? toggleBtn : viewerRoot();
|
|
265
|
+
to?.focus({ preventScroll: true });
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
for (const r of live()) r.el.hidden = !v;
|
|
269
|
+
toggleBtn?.setAttribute("aria-pressed", String(v));
|
|
270
|
+
};
|
|
271
|
+
const stopPlacing = () => {
|
|
272
|
+
placing = false;
|
|
273
|
+
addBtn?.setAttribute("aria-pressed", "false");
|
|
274
|
+
root?.removeAttribute("data-gallerist-placing");
|
|
275
|
+
root?.removeEventListener("click", onPlaceClick, true);
|
|
276
|
+
root?.removeEventListener("keydown", onPlaceKey, true);
|
|
277
|
+
root?.removeEventListener("pointerdown", onPlaceDown, true);
|
|
278
|
+
};
|
|
279
|
+
let downAt = null;
|
|
280
|
+
const onPlaceDown = (e) => {
|
|
281
|
+
downAt = { x: e.clientX, y: e.clientY };
|
|
282
|
+
};
|
|
283
|
+
const startPlacing = () => {
|
|
284
|
+
if (!root || !opts.onPlace) return;
|
|
285
|
+
placing = true;
|
|
286
|
+
setOpen(null);
|
|
287
|
+
addBtn?.setAttribute("aria-pressed", "true");
|
|
288
|
+
root.setAttribute("data-gallerist-placing", "");
|
|
289
|
+
root.addEventListener("pointerdown", onPlaceDown, true);
|
|
290
|
+
root.addEventListener("click", onPlaceClick, true);
|
|
291
|
+
root.addEventListener("keydown", onPlaceKey, true);
|
|
292
|
+
announce(t.placing);
|
|
293
|
+
};
|
|
294
|
+
const place = () => placing ? stopPlacing() : startPlacing();
|
|
295
|
+
const flip = () => setVisible(!visible);
|
|
296
|
+
const onPlaceKey = (e) => {
|
|
297
|
+
if (e.key !== "Escape") return;
|
|
298
|
+
e.stopPropagation();
|
|
299
|
+
stopPlacing();
|
|
300
|
+
};
|
|
301
|
+
const onPlaceClick = (e) => {
|
|
302
|
+
const target = e.target;
|
|
303
|
+
const dragged = downAt && Math.hypot(e.clientX - downAt.x, e.clientY - downAt.y) > 5;
|
|
304
|
+
downAt = null;
|
|
305
|
+
if (dragged) return;
|
|
306
|
+
if (!target?.closest(".gallerist-content[data-gallerist-current]")) {
|
|
307
|
+
if (!target?.closest(".gallerist-stage, .gallerist-pin-add")) stopPlacing();
|
|
308
|
+
return;
|
|
309
|
+
}
|
|
310
|
+
const at = pointOnImage(e);
|
|
311
|
+
stopPlacing();
|
|
312
|
+
if (!at || !bound) return;
|
|
313
|
+
e.stopPropagation();
|
|
314
|
+
const ctx = currentCtx(bound);
|
|
315
|
+
if (ctx) opts.onPlace?.(at, ctx);
|
|
316
|
+
};
|
|
317
|
+
const pointOnImage = (e) => {
|
|
318
|
+
const media = root?.querySelector(
|
|
319
|
+
".gallerist-content[data-gallerist-current] .gallerist-media"
|
|
320
|
+
);
|
|
321
|
+
if (!media || !bound) return null;
|
|
322
|
+
const r = media.getBoundingClientRect();
|
|
323
|
+
const item = currentCtx(bound)?.item;
|
|
324
|
+
const img = media;
|
|
325
|
+
const nat = item?.naturalSize ?? (img.naturalWidth ? { w: img.naturalWidth, h: img.naturalHeight } : null) ?? {
|
|
326
|
+
w: r.width,
|
|
327
|
+
h: r.height
|
|
328
|
+
};
|
|
329
|
+
const s = Math.min(r.width / nat.w, r.height / nat.h);
|
|
330
|
+
const w = nat.w * s;
|
|
331
|
+
const h = nat.h * s;
|
|
332
|
+
const x = (e.clientX - (r.left + (r.width - w) / 2)) / w;
|
|
333
|
+
const y = (e.clientY - (r.top + (r.height - h) / 2)) / h;
|
|
334
|
+
return x >= 0 && x <= 1 && y >= 0 && y <= 1 ? { x, y } : null;
|
|
335
|
+
};
|
|
336
|
+
const announce = (text) => {
|
|
337
|
+
const region = root?.querySelector(".gallerist-live");
|
|
338
|
+
if (region) region.textContent = text;
|
|
339
|
+
};
|
|
340
|
+
const currentCtx = (viewer) => {
|
|
341
|
+
const { g, i } = viewer.getState().cursor;
|
|
342
|
+
const group = viewer.getCollection().groups[g];
|
|
343
|
+
const item = group?.items[i];
|
|
344
|
+
return group && item ? { viewer, group, item } : null;
|
|
345
|
+
};
|
|
346
|
+
const renderPin = (pin, n, ctx) => {
|
|
347
|
+
const el = make("div", "gallerist-pin");
|
|
348
|
+
el.dataset.presentation = presentation;
|
|
349
|
+
el.dataset.pin = pin.id;
|
|
350
|
+
if (pin.resolved) el.setAttribute("data-resolved", "");
|
|
351
|
+
el.hidden = !visible;
|
|
352
|
+
const cardId = `gallerist-pin-${instance}-${pin.id}`;
|
|
353
|
+
const marker = button("gallerist-pin-marker", t.pin(n, pin.label));
|
|
354
|
+
marker.setAttribute("aria-expanded", "false");
|
|
355
|
+
marker.setAttribute("aria-controls", cardId);
|
|
356
|
+
const dot = make("span", "gallerist-pin-dot");
|
|
357
|
+
dot.setAttribute("aria-hidden", "true");
|
|
358
|
+
if (presentation !== "tag") dot.textContent = String(n);
|
|
359
|
+
marker.appendChild(dot);
|
|
360
|
+
const card = make("div", "gallerist-pin-card");
|
|
361
|
+
card.id = cardId;
|
|
362
|
+
card.hidden = true;
|
|
363
|
+
card.setAttribute("role", "group");
|
|
364
|
+
card.setAttribute("aria-label", t.pin(n, pin.label));
|
|
365
|
+
card.tabIndex = -1;
|
|
366
|
+
const close = button("gallerist-pin-close", t.close, ICONS.close);
|
|
367
|
+
close.addEventListener("click", () => setOpen(null, true));
|
|
368
|
+
card.appendChild(close);
|
|
369
|
+
marker.addEventListener("click", () => setOpen(openId === pin.id ? null : pin.id));
|
|
370
|
+
card.addEventListener("pointerdown", (e) => e.stopPropagation());
|
|
371
|
+
card.addEventListener("wheel", (e) => {
|
|
372
|
+
if (!e.ctrlKey)
|
|
373
|
+
for (const el2 of e.composedPath()) {
|
|
374
|
+
if (e.deltaY && el2.scrollHeight > el2.clientHeight + 1 || e.deltaX && el2.scrollWidth > el2.clientWidth + 1)
|
|
375
|
+
return e.stopPropagation();
|
|
376
|
+
if (el2 === card) return;
|
|
377
|
+
}
|
|
378
|
+
});
|
|
379
|
+
card.addEventListener("focusin", () => {
|
|
380
|
+
focusedCard = card;
|
|
381
|
+
});
|
|
382
|
+
card.addEventListener("focusout", (e) => {
|
|
383
|
+
if (e.relatedTarget) focusedCard = null;
|
|
384
|
+
});
|
|
385
|
+
card.addEventListener("keydown", (e) => {
|
|
386
|
+
if (e.target === card && !card.tabIndex && /^(Arrow(Up|Down)|Home|End|Page(Up|Down))$/.test(e.key))
|
|
387
|
+
e.stopPropagation();
|
|
388
|
+
if (e.key !== "Escape" || e.defaultPrevented || openId !== pin.id) return;
|
|
389
|
+
e.preventDefault();
|
|
390
|
+
e.stopPropagation();
|
|
391
|
+
setOpen(null, true);
|
|
392
|
+
});
|
|
393
|
+
el.append(marker, card);
|
|
394
|
+
rendered.push({ pin, el, marker, card, ctx, x: close });
|
|
395
|
+
return el;
|
|
396
|
+
};
|
|
397
|
+
const toolbar = () => {
|
|
398
|
+
const host = make("div", "gallerist-pins-tools");
|
|
399
|
+
if (opts.toggle !== false) {
|
|
400
|
+
const btn = button("gallerist-pins-toggle", t.toggle, ICONS.pin);
|
|
401
|
+
btn.setAttribute("aria-pressed", String(visible));
|
|
402
|
+
btn.addEventListener("click", flip);
|
|
403
|
+
toggleBtn = btn;
|
|
404
|
+
host.appendChild(btn);
|
|
405
|
+
}
|
|
406
|
+
if (opts.onPlace) {
|
|
407
|
+
const add = button("gallerist-pin-add", t.add, ICONS.comment);
|
|
408
|
+
add.setAttribute("aria-pressed", "false");
|
|
409
|
+
addBtn = add;
|
|
410
|
+
add.addEventListener("click", place);
|
|
411
|
+
host.appendChild(add);
|
|
412
|
+
}
|
|
413
|
+
queueMicrotask(() => {
|
|
414
|
+
root = host.closest(".gallerist") ?? root;
|
|
415
|
+
});
|
|
416
|
+
return host;
|
|
417
|
+
};
|
|
418
|
+
const factory = (viewer) => {
|
|
419
|
+
bound = viewer;
|
|
420
|
+
const overlays = ({ group, item }) => {
|
|
421
|
+
const list = read(item, group) ?? [];
|
|
422
|
+
const ctx = { viewer, group, item };
|
|
423
|
+
const a = document.activeElement;
|
|
424
|
+
const held = item === currentCtx(viewer)?.item && live().find((r) => r.el.contains(a));
|
|
425
|
+
if (held) queueMicrotask(() => rescue(held.pin.id, null));
|
|
426
|
+
if (openId && item === currentCtx(viewer)?.item && !list.some((p) => p.id === openId))
|
|
427
|
+
setOpen(null);
|
|
428
|
+
return list.map((pin, k) => ({
|
|
429
|
+
id: `pin:${pin.id}`,
|
|
430
|
+
anchor: { kind: "point", x: pin.x, y: pin.y, space: pin.space ?? "normalized" },
|
|
431
|
+
interactive: true,
|
|
432
|
+
layer: 20,
|
|
433
|
+
class: "gallerist-pin-overlay",
|
|
434
|
+
render: () => {
|
|
435
|
+
const el = renderPin(pin, k + 1, ctx);
|
|
436
|
+
if (openId === pin.id)
|
|
437
|
+
queueMicrotask(() => {
|
|
438
|
+
if (bound === viewer && openId === pin.id) setOpen(pin.id);
|
|
439
|
+
});
|
|
440
|
+
return el;
|
|
441
|
+
}
|
|
442
|
+
}));
|
|
443
|
+
};
|
|
444
|
+
return {
|
|
445
|
+
name: "pins",
|
|
446
|
+
init() {
|
|
447
|
+
const offs = [
|
|
448
|
+
viewer.on("afterItem", () => setOpen(null)),
|
|
449
|
+
viewer.on("afterSlide", () => setOpen(null)),
|
|
450
|
+
viewer.on("zoom", () => shown && replace())
|
|
451
|
+
];
|
|
452
|
+
for (const type of inputs) document.addEventListener(type, modality, true);
|
|
453
|
+
cleanup = () => {
|
|
454
|
+
for (const off of offs) off();
|
|
455
|
+
for (const type of inputs) document.removeEventListener(type, modality, true);
|
|
456
|
+
cancelAnimationFrame(frame);
|
|
457
|
+
};
|
|
458
|
+
},
|
|
459
|
+
destroy() {
|
|
460
|
+
cleanup?.();
|
|
461
|
+
stopPlacing();
|
|
462
|
+
bound = null;
|
|
463
|
+
setOpen(null);
|
|
464
|
+
rendered = [];
|
|
465
|
+
root = null;
|
|
466
|
+
},
|
|
467
|
+
overlays,
|
|
468
|
+
keymap: {
|
|
469
|
+
p: flip,
|
|
470
|
+
P: flip,
|
|
471
|
+
...opts.onPlace ? { c: place, C: place } : {}
|
|
472
|
+
},
|
|
473
|
+
regions: opts.toggle !== false || opts.onPlace ? {
|
|
474
|
+
toolbar
|
|
475
|
+
} : void 0
|
|
476
|
+
};
|
|
477
|
+
};
|
|
478
|
+
return Object.assign(factory, {
|
|
479
|
+
refresh: () => bound?.refresh(),
|
|
480
|
+
setVisible,
|
|
481
|
+
open: (id, o) => setOpen(id, !!o?.focus)
|
|
482
|
+
});
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
export { DEFAULT_PINS_STRINGS, defaultPinCard, pins };
|
|
486
|
+
//# sourceMappingURL=index.js.map
|
|
487
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/index.ts"],"names":["el"],"mappings":"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","file":"index.js","sourcesContent":["// @gallerist/plugin-pins — pins anchored to the image (plan P2-I). Each pin is a point on the item\n// that tracks pan and zoom at a constant screen size and opens a card. One plugin, three\n// presentations: `comment` (numbered pins with a thread, for review), `tag` (a hotspot with a\n// product card) and `feature` (numbered notes, for tours). Pins are read per item, so they work\n// with any data source; the theme styles them from the skin's tokens.\n\nimport type { Group, Item, OverlaySpec, Plugin, ViewerApi } from '@gallerist/core'\nimport { ICONS, reportHandlerError, safeUrl } from '@gallerist/core/internal'\n\nexport type PinPresentation = 'comment' | 'tag' | 'feature'\n\nexport interface PinReply {\n author?: string\n /** a secondary line, e.g. a timestamp */\n meta?: string\n body: string\n}\n\nexport interface Pin {\n id: string\n /** position on the image: 0..1 of its width/height (`normalized`, default) or image pixels */\n x: number\n y: number\n space?: 'normalized' | 'natural'\n /** short name: the product, the feature, or the comment's author line; also the pin's label */\n label?: string\n /** the card's text: a comment, a description */\n body?: string\n /** a secondary line: a price, a timestamp, a dimension */\n meta?: string\n /** a link from the card (a product page) */\n href?: string\n /** the link's text; defaults to `strings.open` */\n linkLabel?: string\n /** comment threads: the replies under the first comment */\n replies?: PinReply[]\n /** comment threads: drawn muted and marked as resolved */\n resolved?: boolean\n}\n\nexport interface PinContext {\n viewer: ViewerApi\n group: Group\n item: Item\n}\n\n/** `onOpenChange`'s context: the pin's item, and the pin whose card was open before this change. */\nexport interface PinOpenChange extends PinContext {\n /** the pin whose card was open until now (the one that closed), or `null` if none was */\n previous: Pin | null\n}\n\n/** Options for `open()`. */\nexport interface PinOpenOptions {\n /** move focus: into the card when it opens (once it is painted), to the pin when it closes */\n focus?: boolean\n}\n\nexport interface PinsStrings {\n /** the toolbar toggle's label */\n toggle: string\n /** the \"place a comment\" button's label */\n add: string\n /** shown while placing a comment */\n placing: string\n /** a pin's accessible name: its number (1-based) and label */\n pin: (n: number, label?: string) => string\n /** the card's close button */\n close: string\n /** default link text */\n open: string\n /** a resolved thread's badge */\n resolved: string\n}\n\nexport const DEFAULT_PINS_STRINGS: PinsStrings = {\n toggle: 'Show pins (P)',\n add: 'Add a comment (C)',\n placing: 'Click the image to place the comment. Esc cancels.',\n pin: (n, label) => (label ? `${n}. ${label}` : `Pin ${n}`),\n close: 'Close',\n open: 'View',\n resolved: 'Resolved',\n}\n\nexport interface PinsOptions {\n /** how pins look and what their card holds. Default `tag`. */\n presentation?: PinPresentation\n /** the pins of an item. Default: `item.meta.pins`. */\n pins?: (item: Item, group: Group) => readonly Pin[] | undefined\n /** whether pins start shown (default true); the toolbar toggle and the `p` key flip it */\n visible?: boolean\n /** add the show/hide toggle to the toolbar (default true) */\n toggle?: boolean\n /**\n * Replace the card's contents for a pin; `null` (or `undefined`) keeps the default card\n * (`defaultPinCard`) for that pin. Called each time the card opens, and again when an open card\n * is redrawn (`refresh()`, new data); the card is empty while closed. Returning the same node\n * again (one cached per pin) is supported. If it throws, the pin gets the default card and the\n * error is reported (`reportError`, else `console.error`). Don't call `open()` from here.\n */\n renderCard?: (pin: Pin, ctx: PinContext) => Node | null | undefined\n /**\n * A node `renderCard` returned was dropped: its card closed (by the user, `open()`, moving to\n * another item, the pin leaving the data), was redrawn, or the plugin or viewer was destroyed.\n * Called once per returned node, after it left the card, so a host can unmount what it rendered\n * there (a React root or portal). Not called for the default card, nor for a node `renderCard`\n * returned again for the redrawn card (it hasn't left; it is handed back when it does). Runs once\n * the change has settled, before `onOpenChange`; calling `open()` from it is fine.\n */\n onCardRemove?: (pin: Pin, node: Node) => void\n /**\n * A pin's card opened (`pin`) or closed (`null`). `ctx.previous` is the pin whose card was open\n * until now: the one that closed, also when another pin's card replaced it. When a callback\n * changes the card again (`open()`), an earlier call may describe a state already replaced: the\n * last one is current.\n */\n onOpenChange?: (pin: Pin | null, ctx: PinOpenChange) => void\n /**\n * Turns on placing: an \"Add a comment\" toolbar button (and the `c` key) arms a one-shot click on\n * the image, and this is called with the point in normalized image coordinates. Add the pin to\n * your data and call `refresh()` (or update the collection) to show it.\n */\n onPlace?: (at: { x: number; y: number }, ctx: PinContext) => void\n strings?: Partial<PinsStrings>\n}\n\n/** The plugin, plus controls for the host. */\nexport interface PinsPlugin extends Plugin {\n /** re-read every pin (after the host changed pin data in place) */\n refresh(): void\n /** show or hide all pins */\n setVisible(visible: boolean): void\n /** open a pin's card on the current item by id; `null` closes it. `{ focus: true }` moves focus\n * into the card (or, closing, to its pin). */\n open(id: string | null, opts?: PinOpenOptions): void\n}\n\n/** What `defaultPinCard` draws: the presentation and the strings it uses (`open`, `resolved`). */\nexport interface PinCardOptions {\n /** default `tag` */\n presentation?: PinPresentation\n strings?: Partial<PinsStrings>\n}\n\n// an element with a class\nconst make = <K extends keyof HTMLElementTagNameMap>(tag: K, cls: string) => {\n const node = document.createElement(tag)\n if (cls) node.className = cls\n return node\n}\n\n/**\n * The card contents `pins()` draws for a pin by default, for a `renderCard` that wraps or extends\n * them (text is set as text, never parsed as HTML).\n */\nexport function defaultPinCard(pin: Pin, opts: PinCardOptions = {}): DocumentFragment {\n const t: PinsStrings = { ...DEFAULT_PINS_STRINGS, ...opts.strings }\n const frag = document.createDocumentFragment()\n // an element of class `cls` in `to`, holding `text` (set as text)\n const add = (\n cls: string,\n text?: string,\n tag: 'div' | 'span' | 'p' | 'ol' | 'li' | 'a' = 'div',\n to: Node = frag,\n ) => {\n const node = make(tag, cls)\n if (text) node.textContent = text\n return to.appendChild(node)\n }\n // ... none without text\n const line = (cls: string, text?: string | false, tag?: 'span' | 'p', to?: Node) =>\n text && add(cls, text, tag, to)\n // a comment's head: who, when, and a resolved badge\n const head = (to: Node, who?: string, when?: string, badge?: string | false) => {\n const h = add('gallerist-pin-head', '', 'div', to)\n line('gallerist-pin-title', who, 'span', h)\n line('gallerist-pin-meta', when, 'span', h)\n line('gallerist-pin-badge', badge, 'span', h)\n }\n if (opts.presentation === 'comment') {\n head(frag, pin.label, pin.meta, pin.resolved && t.resolved)\n line('gallerist-pin-body', pin.body, 'p')\n if (pin.replies?.length) {\n const list = add('gallerist-pin-replies', '', 'ol')\n for (const reply of pin.replies) {\n const li = add('', '', 'li', list)\n head(li, reply.author, reply.meta)\n add('gallerist-pin-body', reply.body, 'p', li)\n }\n }\n } else {\n line('gallerist-pin-title', pin.label)\n line('gallerist-pin-meta', pin.meta)\n line('gallerist-pin-body', pin.body, 'p')\n }\n const href = pin.href && safeLink(pin.href)\n if (href)\n (add('gallerist-pin-link', pin.linkLabel ?? t.open, 'a') as HTMLAnchorElement).href = href\n return frag\n}\n\n// a toolbar or pin button: no form submit, its class and accessible name, an icon\nconst button = (cls: string, label: string, icon = '') => {\n const b = make('button', cls)\n b.type = 'button'\n b.setAttribute('aria-label', label)\n b.innerHTML = icon\n return b\n}\n\n// Security: pin data can come from users (comments) or a CMS, so the card links only to http(s),\n// mailto, tel and relative URLs; a `javascript:`, `data:` or other scheme drops the link rather than\n// becoming something to click. plugin-save's safeHref uses the same check with its own list.\nconst LINK_SCHEMES = ['http:', 'https:', 'mailto:', 'tel:']\nconst safeLink = (url: string): string | null => safeUrl(url, LINK_SCHEMES)\n\ninterface Rendered {\n pin: Pin\n el: HTMLElement\n marker: HTMLButtonElement\n card: HTMLElement\n ctx: PinContext\n /** the card's close button, its first child (contents follow it) */\n x: HTMLButtonElement\n /** what fills the card while it is open: the node `renderCard` returned, `null` for the default\n * card; `undefined` while empty */\n content?: Node | null\n}\n\nconst defaultPins = (item: Item): readonly Pin[] | undefined =>\n (item.meta as { pins?: readonly Pin[] } | undefined)?.pins\n\nlet uid = 0\n\nexport function pins(opts: PinsOptions = {}): PinsPlugin {\n const presentation = opts.presentation ?? 'tag'\n const read = opts.pins ?? defaultPins\n const t: PinsStrings = { ...DEFAULT_PINS_STRINGS, ...opts.strings }\n let visible = opts.visible ?? true\n // the viewer the plugin is in; `undefined` until it first joins one, `null` once it has left\n let bound: ViewerApi | null | undefined\n let root: HTMLElement | null = null\n let toggleBtn: HTMLButtonElement | null = null\n let addBtn: HTMLButtonElement | null = null\n let rendered: Rendered[] = []\n let openId: string | null = null\n let placing = false\n let cleanup: (() => void) | undefined\n const instance = ++uid\n\n // pins still in a viewer (a closed overlay viewer is out of the document, its pins still count)\n const live = () => {\n rendered = rendered.filter((r) => r.el.closest('.gallerist'))\n return rendered\n }\n // the viewer root: captured from the toolbar, or from any painted pin (no toolbar)\n const viewerRoot = (): HTMLElement | null => {\n if (!root) root = live()[0]?.el.closest<HTMLElement>('.gallerist') ?? null\n return root\n }\n\n // The card stays inside the free stage (the stage less the bands immersive floats over it, by\n // which the controls box is inset, 8px in from its edges) on both axes: beside its pin toward the\n // side with more room, level with it, each slid along when it doesn't fit; at most as wide and as\n // tall as the room (it scrolls past that, and joins the tab order so the keys scroll it); never\n // under the docked item navigator (a root grid item: immersive floats it over the stage's corner).\n // Decided from where the pin is on screen (zoom and pan included) when the card opens, and again\n // after a zoom or pan, and as its contents resize (a host renders late).\n const placeCard = (r: Rendered) => {\n const box = (sel: string) => viewerRoot()?.querySelector(sel)?.getBoundingClientRect()\n const s = box('.gallerist-stage')\n if (!s?.width) return\n const c = box('.gallerist-controls')\n const k = c?.width ? c : s\n const n = box('.gallerist > .gallerist-itemnav')\n const m = r.marker.getBoundingClientRect()\n const at = r.el.getBoundingClientRect()\n const top = Math.max(s.top, k.top) + 8\n let bottom = Math.min(s.bottom, k.bottom) - 8\n const left = Math.max(s.left, k.left) + 8\n const right = Math.min(s.right, k.right) - 8\n const toLeft = m.left - left > right - m.right\n const card = r.card\n const st = card.style\n st.maxWidth = `${Math.max(0, Math.min(280, right - left, Math.max(120, (toLeft ? m.left - left : right - m.right) - 4)))}px`\n const w = card.offsetWidth\n const x = Math.max(left, Math.min(toLeft ? m.left - 4 - w : m.right + 4, right - w))\n st.left = `${x - at.left}px`\n if (n?.width && n.top < bottom && n.bottom > top && x < n.right && x + w > n.left)\n bottom = n.top - 8\n st.maxHeight = `${Math.max(60, bottom - top)}px`\n st.top = `${Math.max(top, Math.min(m.top, bottom - card.offsetHeight)) - at.top}px`\n // it scrolls: in the tab order. While it does, the fade (pins.css) adds 12px below its contents,\n // which don't count: a card that shrinks to fit stops scrolling over nothing (tabIndex 0 or -1)\n card.tabIndex = card.scrollHeight - 12 * (1 + card.tabIndex) > card.clientHeight + 1 ? 0 : -1\n }\n // placed again a frame after a zoom or pan (the pin has moved, its card offset hasn't) or a resize\n // of the card or its contents: not from inside the observer's delivery, where placeCard's own\n // writes would resize what it observes again (a ResizeObserver loop error)\n let frame = 0\n const replace = () => {\n cancelAnimationFrame(frame)\n frame = requestAnimationFrame(() => shown && placeCard(shown))\n }\n const fit = typeof ResizeObserver === 'function' ? new ResizeObserver(replace) : null\n\n // While a card is open, Escape closes it (not the viewer), wherever focus is in the viewer: some\n // browsers don't focus a clicked button, and `open(id)` leaves focus where it was. An Escape from\n // inside the card is left to the card's own (bubble) listener, so a host form in the card sees it\n // first and can keep it with preventDefault().\n const onOpenKey = (e: KeyboardEvent) => {\n if (e.key !== 'Escape' || !openId) return\n if (shown && e.target instanceof Node && shown.card.contains(e.target)) return\n e.stopPropagation()\n setOpen(null, true)\n }\n let keyRoot: HTMLElement | null = null\n\n // Host callbacks (onCardRemove, onOpenChange, a renderCard error) wait in `calls` until the\n // outermost setOpen has settled, then run in order, so a call back into the plugin (open() from\n // onCardRemove) sees the settled state and its own events come after the ones that led to it.\n // A throw is reported (reportError, else console.error: core's emitter's reporter) and the rest run.\n const calls: Array<() => void> = []\n let depth = 0\n // nodes handed to onCardRemove's queue and not delivered yet (each is delivered once)\n const leaving = new Set<Node>()\n\n // A card holds its contents only while open: drawn when it opens (or is redrawn), dropped when it\n // closes, so a host's node is handed back exactly once (onCardRemove, once setOpen has settled).\n // A renderCard that throws gets the default card for that pin; the error is reported after.\n const fill = (r: Rendered) => {\n if (r.content !== undefined) return\n let node: Node | null = null\n try {\n node = opts.renderCard?.(r.pin, r.ctx) ?? null\n } catch (e) {\n calls.push(() => {\n throw e\n })\n }\n r.content = node\n r.card.append(node ?? defaultPinCard(r.pin, { presentation, strings: t })) // after ×\n }\n const empty = (r: Rendered) => {\n const node = r.content\n if (node === undefined) return\n r.content = undefined\n r.card.replaceChildren(r.x) // keep the close button\n if (node && !leaving.has(node)) {\n leaving.add(node)\n calls.push(() => {\n leaving.delete(node)\n // a host may return the same node again (one cached per pin): if it is back in the open\n // card, it hasn't left; it is handed back when it does\n if (shown?.content !== node) opts.onCardRemove?.(r.pin, node)\n })\n }\n }\n\n // Focus leaving a card that closed, or whose nodes were dropped: to the pin's marker on the current\n // item, else the add-pin button, else the viewer, never <body> (the next Escape would close the\n // viewer). Moved if it is on <body> or inside `from` (`document`: from anywhere, as Escape or the\n // close button do). It shows the focus ring if the input that led here was a key, and not if it\n // was a pointer (`focusVisible`; a browser without the option decides by its own heuristic). Not\n // inferred from the element focus leaves (a text field always matches :focus-visible).\n const rescue = (id: string, from: Node | null) => {\n const a = document.activeElement\n if (a && a !== document.body && !from?.contains(a)) return\n const item = bound && currentCtx(bound)?.item\n for (const to of [\n ...live()\n .filter((r) => r.pin.id === id && r.ctx.item === item)\n .map((r) => r.marker),\n addBtn,\n viewerRoot(),\n ]) {\n to?.focus({ preventScroll: true, focusVisible: kbd } as FocusOptions)\n if (to && document.activeElement === to) return\n }\n }\n // again after the current task, as a repaint may drop the marker it went to (the pin is gone)\n const refocus = (id: string, card: HTMLElement, force: boolean) => {\n rescue(id, force ? document : card)\n queueMicrotask(() => rescue(id, card))\n }\n // the last input was a key, not a pointer, seen on the document in capture while the plugin is in\n // a viewer: a keydown without meta, ctrl or alt, or a click without a pointer (`detail` 0: a key on\n // a button, or assistive tech activating it); a pointerdown or a pointer's click is not. A keydown\n // with one of them (a shortcut, ctrl held for a pinch) leaves it as it was: a host's Ctrl+K that\n // calls open(id, { focus: true }) after a key rings, after a pointer doesn't\n let kbd = false\n const modality = (e: Event) => {\n const k = e as KeyboardEvent\n kbd =\n e.type === 'keydown'\n ? kbd || !(k.metaKey || k.ctrlKey || k.altKey)\n : e.type === 'click' && !k.detail\n }\n const inputs = ['keydown', 'pointerdown', 'click']\n\n // `openId` is the pin asked for; `shown` the one whose card is on screen. They differ while a pin\n // asked for by `open(id)` hasn't been painted yet (it opens when it is).\n let shown: Rendered | null = null\n // open(id, { focus: true }): where focus was when asked; the card takes it (now, or once its pin is\n // painted) unless the user moved it meanwhile: it is still there, fell to <body>, or what held it\n // was repainted away (the renderer hands focus to the new pin)\n let want: { from: Element | null } | null = null\n // the card focus last went into (cleared when focus moves to another element, or by setOpen)\n let focusedCard: HTMLElement | null = null\n const setOpen = (id: string | null, focus = false) => {\n depth++\n try {\n change(id, focus)\n } finally {\n if (!--depth)\n for (let f = calls.shift(); f; f = calls.shift()) {\n depth++\n try {\n f()\n } catch (e) {\n reportHandlerError(e)\n }\n depth--\n }\n }\n }\n const change = (asked: string | null, focus: boolean) => {\n // nothing opens once the plugin has left its viewer (open() from a callback run by destroy): no\n // listeners left behind, and no card a later viewer would open on its first paint. Before it\n // first joins one, the id waits: the card opens when its pin is first painted\n const id = bound !== null ? asked : null\n if (id !== openId) want = null\n if (focus && id) want = { from: document.activeElement }\n openId = id\n const prev = shown\n const a = document.activeElement\n const v = viewerRoot()\n // the pin on the current item (its neighbours' slides may carry the same id)\n const item = bound && currentCtx(bound)?.item\n // Focus is in the card on its way out, or fell to <body> as its nodes were dropped or its slide\n // stopped being the current one: from inside the card, or anywhere in an open overlay viewer\n // (Escape on <body> would close it). Not from a host control outside an inline viewer.\n const had =\n !!prev &&\n (prev.card.contains(a) ||\n ((!a || a === document.body) &&\n (prev.ctx.item !== item || !prev.el.closest('.gallerist')) &&\n (focusedCard === prev.card || (!!v?.isConnected && v.hasAttribute('aria-modal')))))\n let opened: Rendered | null = null\n for (const r of live()) {\n const isOpen = !opened && r.pin.id === id && r.ctx.item === item\n if (isOpen) opened = r\n else empty(r)\n r.card.hidden = !isOpen\n r.marker.setAttribute('aria-expanded', String(isOpen))\n r.el.toggleAttribute('data-open', isOpen)\n // the open pin's overlay sits above its neighbours so its card isn't covered\n const host = r.el.parentElement\n if (host) host.style.zIndex = isOpen ? '30' : '20'\n }\n if (prev !== opened) focusedCard = null // a new card: focus hasn't been in it yet\n if (prev && prev !== opened) empty(prev) // also when a repaint took it off the page\n fit?.disconnect()\n if (opened) {\n fill(opened)\n placeCard(opened)\n fit?.observe(opened.card)\n if (opened.content instanceof Element) fit?.observe(opened.content)\n }\n if (opened && want) {\n const now = document.activeElement\n if (now === want.from || !now || now === document.body || !want.from?.isConnected)\n opened.card.focus({ preventScroll: true, focusVisible: kbd } as FocusOptions)\n want = null\n } else if (bound && prev && prev !== opened && (had || (focus && !id))) {\n refocus(prev.pin.id, prev.card, focus)\n }\n shown = opened\n keyRoot?.removeEventListener('keydown', onOpenKey, true)\n keyRoot = null\n if (openId) {\n document.addEventListener('pointerdown', onOutside, true)\n keyRoot = v\n keyRoot?.addEventListener('keydown', onOpenKey, true)\n } else {\n document.removeEventListener('pointerdown', onOutside, true)\n }\n // the host hears about it last (queued after the onCardRemove calls), once state has settled\n const ctx = (opened ?? prev)?.ctx\n if (ctx && opened?.pin.id !== prev?.pin.id) {\n const pin = opened ? opened.pin : null\n const previous = prev?.pin ?? null\n calls.push(() => opts.onOpenChange?.(pin, { ...ctx, previous }))\n }\n }\n\n // no card on screen (open(id) waiting for its pin's first paint, or for a closed overlay viewer\n // to open again): nothing to click outside of, so the click that opens the viewer keeps it\n const onOutside = (e: Event) => {\n const target = e.target as Node | null\n if (!shown?.el.isConnected || (target && live().some((r) => r.el.contains(target)))) return\n setOpen(null)\n }\n\n const setVisible = (v: boolean) => {\n visible = v\n if (!v) {\n setOpen(null)\n // hiding the focused pin would drop focus to <body>: hand it to the toggle (or the viewer)\n const active = document.activeElement\n if (active && live().some((r) => r.el.contains(active))) {\n const to = toggleBtn?.isConnected ? toggleBtn : viewerRoot()\n to?.focus({ preventScroll: true })\n }\n }\n for (const r of live()) r.el.hidden = !v\n toggleBtn?.setAttribute('aria-pressed', String(v))\n }\n\n // ── placing a comment: a one-shot click on the image ───────────────────────────\n const stopPlacing = () => {\n placing = false\n addBtn?.setAttribute('aria-pressed', 'false')\n root?.removeAttribute('data-gallerist-placing')\n root?.removeEventListener('click', onPlaceClick, true)\n root?.removeEventListener('keydown', onPlaceKey, true)\n root?.removeEventListener('pointerdown', onPlaceDown, true)\n }\n // a click that ends a drag (a pan) neither places nor cancels\n let downAt: { x: number; y: number } | null = null\n const onPlaceDown = (e: PointerEvent) => {\n downAt = { x: e.clientX, y: e.clientY }\n }\n const startPlacing = () => {\n if (!root || !opts.onPlace) return\n placing = true\n setOpen(null)\n addBtn?.setAttribute('aria-pressed', 'true')\n root.setAttribute('data-gallerist-placing', '')\n root.addEventListener('pointerdown', onPlaceDown, true)\n root.addEventListener('click', onPlaceClick, true)\n root.addEventListener('keydown', onPlaceKey, true)\n announce(t.placing)\n }\n const place = () => (placing ? stopPlacing() : startPlacing())\n const flip = () => setVisible(!visible)\n const onPlaceKey = (e: KeyboardEvent) => {\n if (e.key !== 'Escape') return\n e.stopPropagation() // cancel placing, don't close the viewer\n stopPlacing()\n }\n const onPlaceClick = (e: MouseEvent) => {\n const target = e.target as Element | null\n const dragged = downAt && Math.hypot(e.clientX - downAt.x, e.clientY - downAt.y) > 5\n downAt = null\n if (dragged) return\n if (!target?.closest('.gallerist-content[data-gallerist-current]')) {\n // a click outside the stage cancels; inside it (beside the image) placing stays armed\n if (!target?.closest('.gallerist-stage, .gallerist-pin-add')) stopPlacing()\n return\n }\n const at = pointOnImage(e)\n stopPlacing()\n if (!at || !bound) return\n e.stopPropagation()\n const ctx = currentCtx(bound)\n if (ctx) opts.onPlace?.(at, ctx)\n }\n // The painted image box (object-fit: contain inside the transformed content layer), in screen px.\n const pointOnImage = (e: MouseEvent): { x: number; y: number } | null => {\n const media = root?.querySelector<HTMLElement>(\n '.gallerist-content[data-gallerist-current] .gallerist-media',\n )\n if (!media || !bound) return null\n const r = media.getBoundingClientRect()\n const item = currentCtx(bound)?.item\n const img = media as HTMLImageElement\n const nat = item?.naturalSize ??\n (img.naturalWidth ? { w: img.naturalWidth, h: img.naturalHeight } : null) ?? {\n w: r.width,\n h: r.height,\n }\n const s = Math.min(r.width / nat.w, r.height / nat.h)\n const w = nat.w * s\n const h = nat.h * s\n const x = (e.clientX - (r.left + (r.width - w) / 2)) / w\n const y = (e.clientY - (r.top + (r.height - h) / 2)) / h\n return x >= 0 && x <= 1 && y >= 0 && y <= 1 ? { x, y } : null\n }\n const announce = (text: string) => {\n const region = root?.querySelector('.gallerist-live')\n if (region) region.textContent = text\n }\n\n const currentCtx = (viewer: ViewerApi): PinContext | null => {\n const { g, i } = viewer.getState().cursor\n const group = viewer.getCollection().groups[g]\n const item = group?.items[i]\n return group && item ? { viewer, group, item } : null\n }\n\n // ── one pin: a marker button and its card ─────────────────────────────────────\n const renderPin = (pin: Pin, n: number, ctx: PinContext): HTMLElement => {\n const el = make('div', 'gallerist-pin')\n el.dataset.presentation = presentation\n el.dataset.pin = pin.id\n if (pin.resolved) el.setAttribute('data-resolved', '')\n el.hidden = !visible\n\n const cardId = `gallerist-pin-${instance}-${pin.id}`\n const marker = button('gallerist-pin-marker', t.pin(n, pin.label))\n marker.setAttribute('aria-expanded', 'false')\n marker.setAttribute('aria-controls', cardId)\n const dot = make('span', 'gallerist-pin-dot')\n dot.setAttribute('aria-hidden', 'true')\n if (presentation !== 'tag') dot.textContent = String(n)\n marker.appendChild(dot)\n\n const card = make('div', 'gallerist-pin-card')\n card.id = cardId\n card.hidden = true\n card.setAttribute('role', 'group')\n card.setAttribute('aria-label', t.pin(n, pin.label))\n card.tabIndex = -1 // open(id, { focus: true }) lands here; the contents are drawn on open\n const close = button('gallerist-pin-close', t.close, ICONS.close)\n close.addEventListener('click', () => setOpen(null, true))\n card.appendChild(close) // first: in reading and tab order before the contents, as it is drawn\n\n marker.addEventListener('click', () => setOpen(openId === pin.id ? null : pin.id))\n // a click inside a card must not reach the stage (no swipe, no close-on-backdrop); nor a wheel\n // over something in it that scrolls on the wheel's axis (the card, or a host's textarea or list\n // in it), which scrolls that rather than zooming the image. A pinch (ctrl+wheel), or a wheel over\n // nothing that scrolls, zooms the image as anywhere on the stage, whose handler keeps it from\n // zooming the page.\n card.addEventListener('pointerdown', (e) => e.stopPropagation())\n // The walk follows the event's composed path, so a scroller inside a web component's (open)\n // shadow tree counts; a ShadowRoot on the path has no scroll sizes, and never does.\n card.addEventListener('wheel', (e) => {\n if (!e.ctrlKey)\n for (const el of e.composedPath() as Element[]) {\n if (\n (e.deltaY && el.scrollHeight > el.clientHeight + 1) ||\n (e.deltaX && el.scrollWidth > el.clientWidth + 1)\n )\n return e.stopPropagation()\n if (el === card) return\n }\n })\n card.addEventListener('focusin', () => {\n focusedCard = card\n })\n card.addEventListener('focusout', (e) => {\n if (e.relatedTarget) focusedCard = null\n })\n // Escape from inside the card closes the card, unless a host handler in it kept the key\n // (preventDefault); then it goes no further, so the viewer stays open\n card.addEventListener('keydown', (e) => {\n // a scrolling card (focused itself) scrolls with these keys rather than navigating\n if (\n e.target === card &&\n !card.tabIndex &&\n /^(Arrow(Up|Down)|Home|End|Page(Up|Down))$/.test(e.key)\n )\n e.stopPropagation()\n if (e.key !== 'Escape' || e.defaultPrevented || openId !== pin.id) return\n e.preventDefault()\n e.stopPropagation()\n setOpen(null, true)\n })\n el.append(marker, card)\n rendered.push({ pin, el, marker, card, ctx, x: close })\n return el\n }\n\n // ── toolbar ────────────────────────────────────────────────────────────────\n const toolbar = () => {\n const host = make('div', 'gallerist-pins-tools')\n if (opts.toggle !== false) {\n const btn = button('gallerist-pins-toggle', t.toggle, ICONS.pin)\n btn.setAttribute('aria-pressed', String(visible))\n btn.addEventListener('click', flip)\n toggleBtn = btn\n host.appendChild(btn)\n }\n if (opts.onPlace) {\n const add = button('gallerist-pin-add', t.add, ICONS.comment)\n add.setAttribute('aria-pressed', 'false')\n addBtn = add\n add.addEventListener('click', place)\n host.appendChild(add)\n }\n // capture the viewer root once mounted (placing listens on it; announcements go to its live region)\n queueMicrotask(() => {\n root = host.closest<HTMLElement>('.gallerist') ?? root\n })\n return host\n }\n\n const factory: Plugin = (viewer) => {\n bound = viewer\n const overlays = ({ group, item }: { group: Group; item: Item }): OverlaySpec[] => {\n const list = read(item, group) ?? []\n const ctx: PinContext = { viewer, group, item }\n // a repaint dropping the pin that holds focus (the host removed it, say a draft left empty)\n // leaves focus on <body>: once it is done, focus goes where a closing card's would\n const a = document.activeElement\n const held = item === currentCtx(viewer)?.item && live().find((r) => r.el.contains(a))\n if (held) queueMicrotask(() => rescue(held.pin.id, null))\n // its pin is gone from the current item (a neighbour's slide has its own pins)\n if (openId && item === currentCtx(viewer)?.item && !list.some((p) => p.id === openId))\n setOpen(null)\n return list.map((pin, k) => ({\n id: `pin:${pin.id}`,\n anchor: { kind: 'point', x: pin.x, y: pin.y, space: pin.space ?? 'normalized' },\n interactive: true,\n layer: 20,\n class: 'gallerist-pin-overlay',\n render: () => {\n const el = renderPin(pin, k + 1, ctx)\n // a card left open stays open across a repaint (zoom bounds, refresh), unless it was\n // closed or the plugin destroyed before the microtask runs\n if (openId === pin.id)\n queueMicrotask(() => {\n if (bound === viewer && openId === pin.id) setOpen(pin.id)\n })\n return el\n },\n }))\n }\n return {\n name: 'pins',\n init() {\n // close any open card when the item changes; place an open one again after a zoom or pan;\n // follow the last input (a key or a pointer) for the focus ring\n const offs = [\n viewer.on('afterItem', () => setOpen(null)),\n viewer.on('afterSlide', () => setOpen(null)),\n viewer.on('zoom', () => shown && replace()),\n ]\n for (const type of inputs) document.addEventListener(type, modality, true)\n cleanup = () => {\n for (const off of offs) off()\n for (const type of inputs) document.removeEventListener(type, modality, true)\n cancelAnimationFrame(frame)\n }\n },\n destroy() {\n cleanup?.()\n stopPlacing()\n bound = null // no refocusing into a viewer on its way out\n setOpen(null) // drops the open card's contents (onCardRemove)\n rendered = []\n root = null // a later viewer captures its own\n },\n overlays,\n keymap: {\n p: flip,\n P: flip,\n ...(opts.onPlace ? { c: place, C: place } : {}),\n },\n regions:\n opts.toggle !== false || opts.onPlace\n ? {\n toolbar,\n }\n : undefined,\n }\n }\n\n return Object.assign(factory, {\n refresh: () => bound?.refresh(),\n setVisible,\n open: (id: string | null, o?: PinOpenOptions) => setOpen(id, !!o?.focus),\n })\n}\n"]}
|