@8ux-co/eelzap 0.10.0 → 0.10.2

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.
@@ -1,1256 +0,0 @@
1
- import {
2
- OVERLAY_HOST_TAG,
3
- Overlay,
4
- TagIndex,
5
- buildAnchor,
6
- findAnchorElement,
7
- isRecordRef,
8
- normalizeText,
9
- observeTags,
10
- parseRecordRef
11
- } from "./chunk-VW4EH7NY.js";
12
- import {
13
- SUGGEST_MESSAGES,
14
- TOKENS,
15
- el,
16
- icon,
17
- initials,
18
- mountHost
19
- } from "./chunk-2IUU53QA.js";
20
- import {
21
- clearToken,
22
- readToken
23
- } from "./chunk-QN53G5P7.js";
24
-
25
- // src/preview/site/api.ts
26
- var BASE = "/api/public/v1/comments";
27
- async function call(ctx, token, path, init) {
28
- const headers = { Authorization: `Bearer ${token}` };
29
- if (init.body !== void 0) headers["Content-Type"] = "application/json";
30
- if (init.idempotencyKey) headers["Idempotency-Key"] = init.idempotencyKey;
31
- let response;
32
- try {
33
- response = await ctx.win.fetch(`${ctx.zapOrigin}${path}`, {
34
- method: init.method,
35
- headers,
36
- credentials: "omit",
37
- body: init.body === void 0 ? void 0 : JSON.stringify(init.body)
38
- });
39
- } catch {
40
- return { ok: false, kind: "refused" };
41
- }
42
- if (response.status === 401) return { ok: false, kind: "expired" };
43
- if (response.status === 403) return { ok: false, kind: "forbidden" };
44
- if (response.status === 429) {
45
- const seconds = Number.parseInt(response.headers.get("Retry-After") ?? "", 10);
46
- return { ok: false, kind: "rate", retryAfter: seconds > 0 ? Math.min(seconds, 3600) : 30 };
47
- }
48
- if (!response.ok) return { ok: false, kind: "invalid" };
49
- try {
50
- return { ok: true, data: await response.json() };
51
- } catch {
52
- return { ok: false, kind: "invalid" };
53
- }
54
- }
55
- function fetchOnPage(ctx, token, pageUrl, refs) {
56
- const query = new URLSearchParams({ url: pageUrl });
57
- if (refs.length > 0) query.set("refs", refs.slice(0, 20).join(","));
58
- return call(ctx, token, `${BASE}/on-page?${query.toString()}`, { method: "GET" });
59
- }
60
- function createComment(ctx, token, body, idempotencyKey) {
61
- return call(ctx, token, BASE, { method: "POST", body, idempotencyKey });
62
- }
63
- function saveToDraft(ctx, token, body, idempotencyKey) {
64
- return call(ctx, token, `${BASE}/save-to-draft`, { method: "POST", body, idempotencyKey });
65
- }
66
- function newIdempotencyKey(win) {
67
- const native = win.crypto.randomUUID;
68
- if (typeof native === "function") return native.call(win.crypto);
69
- const bytes = win.crypto.getRandomValues(new Uint8Array(16));
70
- bytes[6] = bytes[6] & 15 | 64;
71
- bytes[8] = bytes[8] & 63 | 128;
72
- const hex = Array.from(bytes, (b) => b.toString(16).padStart(2, "0")).join("");
73
- return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;
74
- }
75
-
76
- // src/preview/site/suggestion.ts
77
- function editKindFor(tagged, field) {
78
- if (!tagged || !field || tagged.html) return null;
79
- if (field.type === "TEXT" || field.type === "LONG_TEXT") return "text";
80
- if (field.type === "NUMBER" || field.type === "URL") return "input";
81
- return null;
82
- }
83
- function fieldOf(data, tagged) {
84
- if (!data || !tagged) return null;
85
- const field = data.fields?.[tagged.recordRef]?.[tagged.fieldKey];
86
- return field && typeof field.type === "string" ? field : null;
87
- }
88
- function recordFor(element, index) {
89
- const tagged = index.closest(element);
90
- if (tagged) return tagged.recordRef;
91
- const entry = element.closest("[data-zap-entry]")?.getAttribute("data-zap-entry")?.trim();
92
- if (entry && isRecordRef(entry)) return entry;
93
- const counts = /* @__PURE__ */ new Map();
94
- for (const row of index.summary()) {
95
- counts.set(row.recordRef, (counts.get(row.recordRef) ?? 0) + row.count);
96
- }
97
- let best = null;
98
- let most = 0;
99
- for (const [ref, count] of counts) {
100
- if (count > most) {
101
- best = ref;
102
- most = count;
103
- }
104
- }
105
- return best;
106
- }
107
- function currentPageUrl(win) {
108
- const { origin, pathname, search } = win.location;
109
- return `${origin}${pathname}${search}`.slice(0, 2048);
110
- }
111
- var unit = (value) => Number.isFinite(value) ? Math.min(1, Math.max(0, value)) : 0;
112
- var whole = (value, max) => Number.isFinite(value) ? Math.min(max, Math.max(1, Math.round(value))) : 1;
113
- function toApiAnchor(anchor, recordRef) {
114
- const dom = anchor.dom;
115
- const out = {
116
- dom: {
117
- ...dom.tag && dom.tag.length <= 500 ? { tag: dom.tag } : {},
118
- ...dom.selector ? { selector: dom.selector } : {},
119
- ...dom.textQuote ? {
120
- textQuote: {
121
- exact: dom.textQuote.exact.slice(0, 500),
122
- prefix: dom.textQuote.prefix.slice(-32),
123
- suffix: dom.textQuote.suffix.slice(0, 32)
124
- }
125
- } : {},
126
- rect: { x: unit(dom.rect.x), y: unit(dom.rect.y), w: unit(dom.rect.w), h: unit(dom.rect.h) },
127
- viewport: { w: whole(dom.viewport.w, 1e5), h: whole(dom.viewport.h, 1e6) }
128
- }
129
- };
130
- if (anchor.field && anchor.field.recordRef === recordRef) {
131
- out.field = {
132
- key: anchor.field.key,
133
- ...anchor.field.locale ? { locale: anchor.field.locale } : {}
134
- };
135
- }
136
- return out;
137
- }
138
- function recordKeys(recordRef) {
139
- const parsed = parseRecordRef(recordRef);
140
- if (!parsed) return {};
141
- return parsed.kind === "document" ? { document: parsed.key } : { collection: parsed.collection, slug: parsed.slug };
142
- }
143
- function buildCreateBody(input) {
144
- const proposal = input.isChangeRequest ? input.proposal : null;
145
- const comment = input.comment.trim();
146
- const body = {
147
- ...recordKeys(input.recordRef),
148
- isChangeRequest: input.isChangeRequest,
149
- pageUrl: input.pageUrl,
150
- anchors: input.anchors.slice(0, 20),
151
- body: (comment || (proposal ? String(proposal.value) : "")).slice(0, 1e4)
152
- };
153
- if (proposal) Object.assign(body, proposedPart(proposal));
154
- return body;
155
- }
156
- function buildDraftBody(input) {
157
- return {
158
- ...recordKeys(input.recordRef),
159
- pageUrl: input.pageUrl,
160
- anchors: input.anchors.slice(0, 20),
161
- body: String(input.proposal.value).slice(0, 1e4) || " ",
162
- ...proposedPart(input.proposal)
163
- };
164
- }
165
- function proposedPart(proposal) {
166
- const locale = proposal.locale;
167
- return {
168
- ...locale ? { locale } : {},
169
- proposedValues: [
170
- { fieldKey: proposal.fieldKey, ...locale ? { locale } : {}, value: proposal.value }
171
- ]
172
- };
173
- }
174
- function parseProposed(type, raw) {
175
- if (type === "NUMBER") {
176
- const text = raw.trim().replace(",", ".");
177
- const value = Number(text);
178
- return text !== "" && Number.isFinite(value) ? { ok: true, value } : { ok: false };
179
- }
180
- if (type === "TEXT" || type === "URL") return { ok: true, value: normalizeText(raw) };
181
- return { ok: true, value: raw.replace(/\r\n?/g, "\n").trim() };
182
- }
183
- function spotAnchor(x, y, doc, win) {
184
- const root = doc.documentElement;
185
- const w = Math.max(root.scrollWidth, 1);
186
- const h = Math.max(root.scrollHeight, 1);
187
- return {
188
- spot: { x: unit((x + win.scrollX) / w), y: unit((y + win.scrollY) / h) },
189
- viewport: { w: whole(w, 1e5), h: whole(h, 1e6) }
190
- };
191
- }
192
- function onPage(anchor) {
193
- return !!(anchor?.spot || anchor?.rect || anchor?.tag || anchor?.selector);
194
- }
195
- function pinsFor(threads, index, doc, win) {
196
- const numbers = /* @__PURE__ */ new Map();
197
- const pins = [];
198
- const numbered = threads.filter((thread) => Array.isArray(thread.anchors) && thread.anchors.some(onPage)).sort(
199
- (a, b) => String(a.createdAt).localeCompare(String(b.createdAt)) || a.id.localeCompare(b.id)
200
- );
201
- numbered.forEach((thread, i) => {
202
- const n = i + 1;
203
- numbers.set(thread.id, n);
204
- const spot = thread.anchors.find((anchor) => anchor?.spot)?.spot;
205
- if (spot && Number.isFinite(spot.x) && Number.isFinite(spot.y)) {
206
- pins.push({ id: thread.id, n, spot: { x: unit(spot.x), y: unit(spot.y) } });
207
- return;
208
- }
209
- for (const anchor of thread.anchors) {
210
- if (!onPage(anchor) || anchor.spot) continue;
211
- const element = findAnchorElement(asDomAnchor(anchor), index, doc, win);
212
- if (element && element !== doc.body && element !== doc.documentElement) {
213
- pins.push({ id: thread.id, n, element });
214
- return;
215
- }
216
- }
217
- });
218
- return { pins: pins.slice(0, 50), numbers };
219
- }
220
- var NOWHERE = { x: -1, y: -1, w: 0, h: 0 };
221
- function asDomAnchor(anchor) {
222
- const tag = typeof anchor?.tag === "string" && anchor.tag.length <= 500 ? anchor.tag : null;
223
- const selector = typeof anchor?.selector === "string" && anchor.selector.length <= 1024 ? anchor.selector : null;
224
- return { tag, selector, textQuote: null, rect: NOWHERE, viewport: { w: 1, h: 1 } };
225
- }
226
-
227
- // src/preview/site/suggest.ts
228
- var OVERLAY_MODE = {
229
- navigate: "off",
230
- edit: "inspect",
231
- comment: "select"
232
- };
233
- var PENDING_PIN = "eelzap:pending";
234
- var LARGE_SHARE = 0.5;
235
- var TOAST_MS = 8e3;
236
- var SAVED_MS = 4e3;
237
- function start(ctx, options = {}) {
238
- const { win, doc } = ctx;
239
- const stored = readToken(win, ctx.siteId);
240
- if (!stored) {
241
- void ctx.open("signin", { reason: "expired" });
242
- return { destroy() {
243
- } };
244
- }
245
- const token = stored.token;
246
- const m = SUGGEST_MESSAGES[ctx.locale];
247
- const host = mountHost(doc, CSS);
248
- const layer = el(doc, "div", { class: "layer" });
249
- const marks = el(doc, "div", { class: "marks" });
250
- const bottom = el(doc, "div", { class: "bottom" });
251
- layer.append(marks, bottom);
252
- host.root.append(layer);
253
- let mode = "navigate";
254
- let selection = [];
255
- let data = null;
256
- let refused = false;
257
- let composer = null;
258
- let editing = null;
259
- let list = null;
260
- let toast = null;
261
- let toastTimer = null;
262
- let status = null;
263
- let statusTimer = null;
264
- let frame = null;
265
- let destroyed = false;
266
- let pins = [];
267
- let numbers = /* @__PURE__ */ new Map();
268
- let lastClick = { x: 0, y: 0, additive: false };
269
- const onClickFirst = (event) => {
270
- lastClick = {
271
- x: event.clientX,
272
- y: event.clientY,
273
- additive: event.shiftKey || event.metaKey || event.ctrlKey
274
- };
275
- };
276
- win.addEventListener("click", onClickFirst, true);
277
- const index = new TagIndex(doc);
278
- index.scan();
279
- const overlay = new Overlay({
280
- doc,
281
- win,
282
- index,
283
- pinsWhenOff: true,
284
- ignore: (target) => target === host.host,
285
- onInspect: (tagged) => beginEdit(tagged),
286
- onSelect: (elements) => onSelect(elements),
287
- onPin: ({ id }) => {
288
- if (id === PENDING_PIN) return;
289
- const found = data?.comments.find((comment) => comment.id === id);
290
- if (!found) return;
291
- list = [found];
292
- renderBottom();
293
- }
294
- });
295
- overlay.mount();
296
- const stopObserving = observeTags(
297
- doc.documentElement,
298
- () => {
299
- index.scan();
300
- placePins();
301
- },
302
- {
303
- ignore: (node) => {
304
- const own = overlay.hostElement;
305
- return node === host.host || !!own && (node === own || own.contains(node));
306
- }
307
- }
308
- );
309
- async function load() {
310
- const refs = Array.from(new Set(index.elements.map((tagged) => tagged.recordRef)));
311
- const result = await fetchOnPage(ctx, token, currentPageUrl(win), refs);
312
- if (destroyed) return;
313
- if (!result.ok) {
314
- onFailure(result);
315
- return;
316
- }
317
- const first = data === null;
318
- data = result.data;
319
- const labels = {};
320
- for (const fields of Object.values(data.fields ?? {})) {
321
- for (const [key, field] of Object.entries(fields)) {
322
- if (typeof field?.label === "string") labels[key] = field.label;
323
- }
324
- }
325
- overlay.setLabels(labels);
326
- if (!data.liveEditing && mode === "edit") setMode("navigate");
327
- if (first && options.reason === "renewed") {
328
- const name = data.viewer?.name;
329
- setToast({ title: m.renewed, body: name ? m.renewedAs(name) : m.renewedBody });
330
- }
331
- renderBottom();
332
- placePins();
333
- }
334
- function onFailure(failure, during = "read") {
335
- if (failure.kind === "expired") {
336
- expire();
337
- return;
338
- }
339
- if (failure.kind === "forbidden" && during === "edit") {
340
- if (data) data.liveEditing = false;
341
- setMode("navigate");
342
- setToast({ title: m.editOff, body: m.editOffBody, error: true });
343
- return;
344
- }
345
- if (failure.kind === "refused" || failure.kind === "forbidden") {
346
- refused = true;
347
- closeComposer();
348
- overlay.setMode("off");
349
- overlay.setPins([]);
350
- renderBottom();
351
- return;
352
- }
353
- const message = failure.kind === "rate" ? m.rateLimited(failure.retryAfter) : m.failed;
354
- if (during === "comment" && composer) showError(message);
355
- else
356
- setToast({ title: during === "edit" ? m.saveFailed : m.failed, body: message, error: true });
357
- }
358
- function expire() {
359
- clearToken(win);
360
- void ctx.open("signin", { reason: "expired" });
361
- }
362
- function canEdit() {
363
- return !!data?.liveEditing;
364
- }
365
- function setMode(next) {
366
- if (next === "edit" && !canEdit()) return;
367
- endEdit();
368
- closeComposer();
369
- mode = next;
370
- selection = [];
371
- overlay.clearSelection();
372
- overlay.setMode(OVERLAY_MODE[next]);
373
- renderBottom();
374
- }
375
- function renderBottom() {
376
- if (destroyed) return;
377
- const children = [];
378
- if (toast) children.push(toastCard(toast));
379
- if (refused) {
380
- children.push(refusedCard());
381
- bottom.replaceChildren(...children);
382
- marks.replaceChildren();
383
- return;
384
- }
385
- if (list) children.push(listCard(list));
386
- if (selection.length > 0 && !composer) children.push(trayCard());
387
- if (status) {
388
- children.push(
389
- el(doc, "div", { class: "card status", attrs: { role: "status" } }, [
390
- status === "saved" ? icon(doc, "check", 14) : null,
391
- status === "saved" ? m.saved : m.saving
392
- ])
393
- );
394
- }
395
- children.push(toolbar());
396
- bottom.replaceChildren(...children);
397
- drawMarks();
398
- }
399
- function toolbar() {
400
- const open = data?.comments.length ?? 0;
401
- const name = data?.viewer?.name ?? null;
402
- const tool = (value, label, glyph) => {
403
- const off = value === "edit" && !canEdit();
404
- const why = off && data ? m.editOff : null;
405
- return el(
406
- doc,
407
- "button",
408
- {
409
- class: "seg",
410
- attrs: {
411
- type: "button",
412
- "aria-pressed": String(mode === value),
413
- "aria-disabled": off ? "true" : null,
414
- title: why,
415
- "data-tip": why,
416
- "data-mode": value
417
- },
418
- on: { click: () => setMode(value) }
419
- },
420
- [icon(doc, glyph, 14), label]
421
- );
422
- };
423
- return el(
424
- doc,
425
- "div",
426
- { class: "card toolbar", attrs: { role: "toolbar", "aria-label": m.toolbar } },
427
- [
428
- icon(doc, "zap", 20),
429
- el(doc, "div", { class: "segments", attrs: { role: "group", "aria-label": m.tools } }, [
430
- tool("navigate", m.navigate, "pointer"),
431
- tool("edit", m.edit, "pencil"),
432
- tool("comment", m.comment, "commentPlus")
433
- ]),
434
- el(doc, "span", { class: "divider", attrs: { "aria-hidden": "true" } }),
435
- el(
436
- doc,
437
- "button",
438
- {
439
- class: "tool strong",
440
- attrs: { type: "button", "aria-expanded": String(!!list), "data-action": "open-list" },
441
- on: {
442
- click: () => {
443
- list = list ? null : data?.comments ?? [];
444
- renderBottom();
445
- }
446
- }
447
- },
448
- [
449
- icon(doc, "thread"),
450
- data?.truncated ? m.open(open).replace(String(open), `${open}+`) : m.open(open)
451
- ]
452
- ),
453
- el(doc, "span", { class: "divider", attrs: { "aria-hidden": "true" } }),
454
- name ? el(doc, "span", { class: "avatar", attrs: { title: name }, text: initials(name) }) : null,
455
- el(
456
- doc,
457
- "button",
458
- { class: "tool", attrs: { type: "button", "data-action": "exit" }, on: { click: exit } },
459
- [icon(doc, "exit"), m.exit]
460
- )
461
- ]
462
- );
463
- }
464
- function trayCard() {
465
- return el(doc, "div", { class: "card tray", attrs: { role: "status" } }, [
466
- el(doc, "span", { text: m.selected(selection.length) }),
467
- el(doc, "button", {
468
- class: "btn btn-outline",
469
- text: m.clearSelection,
470
- attrs: { type: "button" },
471
- on: {
472
- click: () => {
473
- selection = [];
474
- overlay.clearSelection();
475
- renderBottom();
476
- }
477
- }
478
- }),
479
- el(doc, "button", {
480
- class: "btn btn-primary",
481
- text: m.comment,
482
- attrs: { type: "button", "data-action": "comment-selection" },
483
- on: {
484
- click: () => {
485
- const elements = selection;
486
- selection = [];
487
- overlay.clearSelection();
488
- openComposer(elements, null);
489
- }
490
- }
491
- })
492
- ]);
493
- }
494
- function listCard(comments) {
495
- return el(
496
- doc,
497
- "div",
498
- { class: "card list sheet", attrs: { role: "dialog", "aria-label": m.openTitle } },
499
- [
500
- el(doc, "div", { class: "head" }, [
501
- el(doc, "h2", { text: m.openTitle }),
502
- closeButton(m.dismiss, () => {
503
- list = null;
504
- renderBottom();
505
- })
506
- ]),
507
- comments.length === 0 ? el(doc, "p", { class: "muted", text: m.noneOpen }) : el(
508
- doc,
509
- "ul",
510
- {},
511
- comments.map((comment) => {
512
- const n = numbers.get(comment.id);
513
- return el(doc, "li", {}, [
514
- el(
515
- doc,
516
- "button",
517
- {
518
- class: "item",
519
- attrs: { type: "button", "data-comment": comment.id },
520
- on: { click: () => reveal(comment.id) }
521
- },
522
- [
523
- n ? el(doc, "span", { class: "num", text: String(n) }) : null,
524
- el(doc, "span", { class: "item-text" }, [
525
- el(doc, "span", { class: "who", text: comment.author?.name || m.someone }),
526
- comment.isChangeRequest ? el(doc, "span", { class: "flag", text: m.changeRequest }) : null,
527
- el(doc, "span", { class: "what", text: comment.excerpt ?? "" })
528
- ])
529
- ]
530
- )
531
- ]);
532
- })
533
- )
534
- ]
535
- );
536
- }
537
- function reveal(id) {
538
- const pin = pins.find((entry) => entry.id === id);
539
- if (pin?.element) pin.element.scrollIntoView?.({ block: "center", behavior: "smooth" });
540
- else if (pin?.spot) {
541
- const root = doc.documentElement;
542
- win.scrollTo?.({
543
- top: Math.max(0, pin.spot.y * root.scrollHeight - win.innerHeight / 2),
544
- behavior: "smooth"
545
- });
546
- }
547
- }
548
- function toastCard(value) {
549
- return el(
550
- doc,
551
- "div",
552
- {
553
- class: value.error ? "card toast toast-error" : "card toast",
554
- attrs: { role: "status", "aria-live": "polite" }
555
- },
556
- [
557
- icon(doc, value.error ? "alert" : "check", 18),
558
- el(doc, "div", { class: "toast-text" }, [
559
- el(doc, "span", { class: "title", text: value.title }),
560
- el(doc, "span", { class: "muted", text: value.body })
561
- ]),
562
- closeButton(m.dismiss, () => setToast(null))
563
- ]
564
- );
565
- }
566
- function refusedCard() {
567
- return el(doc, "div", { class: "card refused", attrs: { role: "alert" } }, [
568
- icon(doc, "alert", 18),
569
- el(doc, "div", { class: "toast-text" }, [
570
- el(doc, "span", { class: "title", text: m.refusedTitle }),
571
- el(doc, "span", { class: "muted", text: m.refusedBody })
572
- ]),
573
- el(
574
- doc,
575
- "button",
576
- { class: "tool", attrs: { type: "button", "data-action": "exit" }, on: { click: exit } },
577
- [icon(doc, "exit"), m.exit]
578
- )
579
- ]);
580
- }
581
- function closeButton(label, onClick2) {
582
- return el(
583
- doc,
584
- "button",
585
- {
586
- class: "iconbtn",
587
- attrs: { type: "button", "aria-label": label, title: label },
588
- on: { click: onClick2 }
589
- },
590
- [icon(doc, "x")]
591
- );
592
- }
593
- function setToast(value) {
594
- toast = value;
595
- if (toastTimer !== null) win.clearTimeout(toastTimer);
596
- toastTimer = value ? win.setTimeout(() => setToast(null), TOAST_MS) : null;
597
- renderBottom();
598
- }
599
- function setStatus(value) {
600
- status = value;
601
- if (statusTimer !== null) win.clearTimeout(statusTimer);
602
- statusTimer = value === "saved" ? win.setTimeout(() => setStatus(null), SAVED_MS) : null;
603
- renderBottom();
604
- }
605
- function exit() {
606
- clearToken(win);
607
- ctx.close();
608
- }
609
- function placePins() {
610
- if (destroyed || refused || !data) return;
611
- const placed = pinsFor(data.comments ?? [], index, doc, win);
612
- pins = placed.pins;
613
- numbers = placed.numbers;
614
- overlay.setPins(composer?.pin ? [...pins, composer.pin] : pins);
615
- }
616
- function drawMarks() {
617
- const targets = editing ? [editing.element] : composer?.elements ?? [];
618
- marks.replaceChildren(
619
- ...targets.filter((element) => element.isConnected).map((element) => {
620
- const rect = element.getBoundingClientRect();
621
- const box = el(doc, "div", { class: "mark" });
622
- box.style.left = `${rect.left - 3}px`;
623
- box.style.top = `${rect.top - 3}px`;
624
- box.style.width = `${rect.width + 6}px`;
625
- box.style.height = `${rect.height + 6}px`;
626
- return box;
627
- })
628
- );
629
- }
630
- function schedule() {
631
- if (frame !== null) return;
632
- const raf = win.requestAnimationFrame?.bind(win) ?? ((fn) => win.setTimeout(fn, 16));
633
- frame = raf(() => {
634
- frame = null;
635
- drawMarks();
636
- if (composer) position(composer.card, anchorRect(composer));
637
- if (editing?.card) position(editing.card, editing.element.getBoundingClientRect());
638
- });
639
- }
640
- function position(card, rect) {
641
- const width = Math.min(420, win.innerWidth - 16);
642
- const height = card.offsetHeight || 260;
643
- const below = rect.bottom + 10;
644
- const top = below + height < win.innerHeight - 90 ? below : Math.max(8, rect.top - height - 10);
645
- const left = Math.min(Math.max(8, rect.left), Math.max(8, win.innerWidth - width - 8));
646
- card.style.width = `${width}px`;
647
- card.style.top = `${top}px`;
648
- card.style.left = `${left}px`;
649
- }
650
- function onSelect(elements) {
651
- if (mode !== "comment" || composer) return;
652
- if (elements.length === 0) {
653
- if (selection.length > 0) {
654
- selection = [];
655
- renderBottom();
656
- }
657
- return;
658
- }
659
- if (lastClick.additive || elements.length > 1) {
660
- selection = elements;
661
- renderBottom();
662
- return;
663
- }
664
- const element = elements[0];
665
- overlay.clearSelection();
666
- if (isLarge(element)) openComposer([], { x: lastClick.x, y: lastClick.y }, element);
667
- else openComposer([element], null);
668
- }
669
- function isLarge(element) {
670
- const rect = element.getBoundingClientRect();
671
- const area = Math.max(1, win.innerWidth * win.innerHeight);
672
- return rect.width * rect.height / area >= LARGE_SHARE;
673
- }
674
- function anchorRect(session) {
675
- const first = session.elements[0];
676
- if (first) return first.getBoundingClientRect();
677
- const point = session.point ?? { x: 0, y: 0 };
678
- return { left: point.x, top: point.y, bottom: point.y };
679
- }
680
- function openComposer(elements, point, owner) {
681
- if (refused || elements.length === 0 && !point) return;
682
- const recordRef = recordFor(elements[0] ?? owner ?? doc.body, index);
683
- if (!recordRef) {
684
- setToast({ title: m.failed, body: m.noRecord, error: true });
685
- return;
686
- }
687
- list = null;
688
- const pageUrl = currentPageUrl(win);
689
- const anchors = point ? [spotAnchor(point.x, point.y, doc, win)] : elements.slice(0, 20).map((element) => toApiAnchor(buildAnchor(element, index, pageUrl, doc, win), recordRef));
690
- const single = elements.length === 1 ? elements[0] : null;
691
- const own = single ? index.get(single) : null;
692
- const tagged = own && own.recordRef === recordRef ? own : null;
693
- const field = fieldOf(data, tagged);
694
- const proposable = editKindFor(tagged, field) !== null;
695
- const target = single;
696
- const before = field?.type === "URL" && target?.localName === "a" ? target.getAttribute("href") ?? "" : (target?.textContent ?? "").replace(/\s+/g, " ").trim();
697
- const spotPin = point ? anchors[0] : null;
698
- const textarea = el(doc, "textarea", {
699
- class: "composer",
700
- attrs: {
701
- "aria-label": m.commentLabel,
702
- rows: "3",
703
- maxlength: "10000",
704
- placeholder: m.commentPlaceholder
705
- }
706
- });
707
- const error = el(doc, "p", { class: "error", attrs: { role: "alert" } });
708
- const sendButton = el(
709
- doc,
710
- "button",
711
- {
712
- class: "btn btn-primary",
713
- attrs: { type: "button", "data-action": "send" },
714
- on: { click: () => void submit() }
715
- },
716
- [icon(doc, "commentPlus", 16), m.comment]
717
- );
718
- const title = tagged ? el(doc, "span", { class: "chip" }, [
719
- icon(doc, "field", 12),
720
- field?.label || tagged.fieldKey
721
- ]) : el(doc, "h2", {
722
- text: elements.length > 1 ? m.selected(elements.length) : m.commentTitle
723
- });
724
- const extra = el(doc, "div", { class: "extra" });
725
- const card = el(
726
- doc,
727
- "div",
728
- { class: "card popover sheet", attrs: { role: "dialog", "aria-label": m.newComment } },
729
- [
730
- el(doc, "div", { class: "head" }, [title, closeButton(m.close, closeComposer)]),
731
- el(doc, "div", { class: "content" }, [
732
- textarea,
733
- extra,
734
- error,
735
- // A dialog footer: a full-bleed rule, Cancelar and the primary (OWNER-PASS 19).
736
- el(doc, "div", { class: "foot" }, [
737
- el(doc, "button", {
738
- class: "btn btn-outline",
739
- text: m.cancel,
740
- attrs: { type: "button" },
741
- on: { click: closeComposer }
742
- }),
743
- sendButton
744
- ])
745
- ])
746
- ]
747
- );
748
- layer.append(card);
749
- composer = {
750
- elements,
751
- point,
752
- pin: spotPin && "spot" in spotPin ? { id: PENDING_PIN, n: (numbers.size || 0) + 1, spot: spotPin.spot } : null,
753
- recordRef,
754
- pageUrl,
755
- anchors,
756
- tagged: proposable ? tagged : null,
757
- field: proposable ? field : null,
758
- before,
759
- card,
760
- textarea,
761
- request: false,
762
- proposing: false,
763
- proposal: null,
764
- error,
765
- sendButton,
766
- busy: false,
767
- idempotency: null
768
- };
769
- renderExtra(extra);
770
- textarea.addEventListener("input", refreshSend);
771
- textarea.addEventListener("keydown", (event) => {
772
- if (event.key !== "Enter" || event.shiftKey || event.isComposing || event.keyCode === 229) {
773
- return;
774
- }
775
- event.preventDefault();
776
- if (!sendButton.disabled) void submit();
777
- });
778
- overlay.setMode("off");
779
- placePins();
780
- refreshSend();
781
- renderBottom();
782
- position(card, anchorRect(composer));
783
- textarea.focus();
784
- }
785
- function renderExtra(extra) {
786
- const session = composer;
787
- if (!session) return;
788
- const toggle = el(
789
- doc,
790
- "button",
791
- {
792
- class: "check",
793
- attrs: {
794
- type: "button",
795
- role: "checkbox",
796
- "aria-checked": String(session.request),
797
- "data-action": "request-change"
798
- },
799
- on: {
800
- click: () => {
801
- session.request = !session.request;
802
- if (!session.request) {
803
- session.proposing = false;
804
- session.proposal = null;
805
- }
806
- renderExtra(extra);
807
- refreshSend();
808
- }
809
- }
810
- },
811
- [el(doc, "span", { class: "box", attrs: { "aria-hidden": "true" } }), m.requestChange]
812
- );
813
- const parts = [toggle];
814
- if (session.request) {
815
- parts.push(el(doc, "p", { class: "muted small", text: m.recipient }));
816
- parts.push(el(doc, "p", { class: "muted small", text: m.requestHelp }));
817
- if (session.tagged && session.field && !session.proposing) {
818
- parts.push(
819
- el(
820
- doc,
821
- "button",
822
- {
823
- class: "btn btn-outline propose",
824
- attrs: { type: "button", "data-action": "propose" },
825
- on: {
826
- click: () => {
827
- session.proposing = true;
828
- renderExtra(extra);
829
- refreshSend();
830
- session.proposal?.focus();
831
- }
832
- }
833
- },
834
- [icon(doc, "pencil", 14), m.propose]
835
- )
836
- );
837
- }
838
- if (session.tagged && session.field && session.proposing) {
839
- const label = session.field.label || session.tagged.fieldKey;
840
- const input = session.field.type === "LONG_TEXT" ? el(doc, "textarea", {
841
- class: "value",
842
- attrs: { "aria-label": m.proposal, rows: "3", maxlength: "10000" }
843
- }) : el(doc, "input", {
844
- class: "value",
845
- attrs: {
846
- "aria-label": m.proposal,
847
- type: "text",
848
- inputmode: session.field.type === "NUMBER" ? "decimal" : null
849
- }
850
- });
851
- input.value = session.proposal?.value ?? session.before;
852
- input.addEventListener("input", refreshSend);
853
- session.proposal = input;
854
- parts.push(
855
- el(doc, "div", { class: "proposal" }, [
856
- el(doc, "div", { class: "head" }, [
857
- el(doc, "span", { class: "small strong-text", text: `${m.proposalFor} ${label}` }),
858
- closeButton(m.removeProposal, () => {
859
- session.proposing = false;
860
- session.proposal = null;
861
- renderExtra(extra);
862
- refreshSend();
863
- })
864
- ]),
865
- session.before ? el(doc, "p", { class: "muted small before" }, [
866
- `${m.now} `,
867
- el(doc, "span", { class: "strong-text", text: session.before })
868
- ]) : null,
869
- input
870
- ])
871
- );
872
- }
873
- }
874
- extra.replaceChildren(...parts.filter((part) => part !== null));
875
- }
876
- function proposalOf(session) {
877
- if (!session.request || !session.proposing || !session.proposal) return null;
878
- if (!session.tagged || !session.field) return null;
879
- const parsed = parseProposed(session.field.type, session.proposal.value);
880
- if (!parsed.ok) return "invalid";
881
- const before = parseProposed(session.field.type, session.before);
882
- if (before.ok && before.value === parsed.value) return null;
883
- return { fieldKey: session.tagged.fieldKey, locale: session.tagged.locale, value: parsed.value };
884
- }
885
- function refreshSend() {
886
- const session = composer;
887
- if (!session) return;
888
- const proposal = proposalOf(session);
889
- const comment = session.textarea.value.trim();
890
- session.sendButton.replaceChildren(
891
- icon(doc, session.request ? "pencil" : "commentPlus", 16),
892
- session.busy ? m.sending : session.request ? m.requestChange : m.comment
893
- );
894
- const proposed = proposal && proposal !== "invalid" && String(proposal.value) !== "";
895
- session.sendButton.disabled = session.busy || proposal === "invalid" || !comment && !proposed;
896
- session.error.textContent = proposal === "invalid" ? m.invalidNumber : session.error.dataset.sticky ?? "";
897
- }
898
- function showError(message) {
899
- if (!composer) return;
900
- composer.error.dataset.sticky = message;
901
- composer.error.textContent = message;
902
- }
903
- async function submit() {
904
- const session = composer;
905
- if (!session || session.busy) return;
906
- const proposal = proposalOf(session);
907
- if (proposal === "invalid") return;
908
- const body = buildCreateBody({
909
- recordRef: session.recordRef,
910
- pageUrl: session.pageUrl,
911
- anchors: session.anchors,
912
- comment: session.textarea.value,
913
- isChangeRequest: session.request,
914
- proposal
915
- });
916
- if (!body.body) return;
917
- const payload = JSON.stringify(body);
918
- if (session.idempotency?.payload !== payload) {
919
- session.idempotency = { payload, key: newIdempotencyKey(win) };
920
- }
921
- session.busy = true;
922
- delete session.error.dataset.sticky;
923
- refreshSend();
924
- const result = await createComment(ctx, token, body, session.idempotency.key);
925
- if (destroyed || composer !== session) return;
926
- session.busy = false;
927
- if (!result.ok) {
928
- refreshSend();
929
- onFailure(result, "comment");
930
- return;
931
- }
932
- closeComposer();
933
- const assignee = result.data?.thread?.assignee?.name;
934
- setToast(
935
- body.isChangeRequest ? {
936
- title: m.sentRequest,
937
- body: m.sentRequestBody(
938
- typeof assignee === "string" && assignee ? assignee : null,
939
- !!body.proposedValues
940
- )
941
- } : { title: m.sentComment, body: m.sentCommentBody }
942
- );
943
- void load();
944
- }
945
- function closeComposer() {
946
- const session = composer;
947
- if (!session) return;
948
- composer = null;
949
- session.card.remove();
950
- if (!refused) {
951
- overlay.setMode(OVERLAY_MODE[mode]);
952
- placePins();
953
- }
954
- renderBottom();
955
- }
956
- function beginEdit(tagged) {
957
- if (mode !== "edit" || refused || !canEdit()) return;
958
- if (editing && !editing.done) {
959
- if (editing.element === tagged.element) return;
960
- commitEdit(editing);
961
- }
962
- const field = fieldOf(data, tagged);
963
- const kind = editKindFor(tagged, field);
964
- if (!kind || !field) {
965
- setToast({ title: m.notEditable, body: m.notEditableBody, error: true });
966
- return;
967
- }
968
- const element = tagged.element;
969
- const pageUrl = currentPageUrl(win);
970
- const anchors = [toApiAnchor(buildAnchor(element, index, pageUrl, doc, win), tagged.recordRef)];
971
- const before = field.type === "URL" && element.localName === "a" ? element.getAttribute("href") ?? "" : (element.textContent ?? "").replace(/\s+/g, " ").trim();
972
- const inline = kind === "text" ? beginInlineEdit(element) : null;
973
- const session = {
974
- element,
975
- tagged,
976
- field,
977
- kind,
978
- recordRef: tagged.recordRef,
979
- pageUrl,
980
- anchors,
981
- before,
982
- revert: inline?.revert ?? (() => {
983
- }),
984
- keep: inline?.keep ?? (() => {
985
- }),
986
- card: null,
987
- input: null,
988
- done: false
989
- };
990
- editing = session;
991
- if (inline) {
992
- element.addEventListener("blur", () => commitEdit(session), { once: true });
993
- element.focus?.();
994
- } else {
995
- openInputCard(session);
996
- }
997
- renderBottom();
998
- }
999
- function openInputCard(session) {
1000
- const input = el(doc, "input", {
1001
- class: "value",
1002
- attrs: {
1003
- "aria-label": session.field.label || session.tagged.fieldKey,
1004
- type: "text",
1005
- inputmode: session.field.type === "NUMBER" ? "decimal" : "url"
1006
- }
1007
- });
1008
- input.value = session.before;
1009
- input.addEventListener("keydown", (event) => {
1010
- if (event.key === "Enter") {
1011
- event.preventDefault();
1012
- commitEdit(session);
1013
- }
1014
- });
1015
- const card = el(doc, "div", { class: "card popover sheet", attrs: { role: "dialog" } }, [
1016
- el(doc, "div", { class: "head" }, [
1017
- el(doc, "span", { class: "chip" }, [
1018
- icon(doc, "field", 12),
1019
- session.field.label || session.tagged.fieldKey
1020
- ]),
1021
- closeButton(m.close, () => cancelEdit(session))
1022
- ]),
1023
- el(doc, "div", { class: "content" }, [
1024
- input,
1025
- el(doc, "div", { class: "actions" }, [
1026
- el(doc, "button", {
1027
- class: "btn btn-outline",
1028
- text: m.cancel,
1029
- attrs: { type: "button" },
1030
- on: { click: () => cancelEdit(session) }
1031
- }),
1032
- el(doc, "button", {
1033
- class: "btn btn-primary",
1034
- text: m.save,
1035
- attrs: { type: "button", "data-action": "save" },
1036
- on: { click: () => commitEdit(session) }
1037
- })
1038
- ])
1039
- ])
1040
- ]);
1041
- session.card = card;
1042
- session.input = input;
1043
- layer.append(card);
1044
- position(card, session.element.getBoundingClientRect());
1045
- input.focus();
1046
- }
1047
- function beginInlineEdit(target) {
1048
- const children = Array.from(target.childNodes);
1049
- const texts = [];
1050
- const walker = doc.createTreeWalker(
1051
- target,
1052
- 4
1053
- /* NodeFilter.SHOW_TEXT */
1054
- );
1055
- for (let node = walker.nextNode(); node; node = walker.nextNode()) {
1056
- texts.push([node, node.data]);
1057
- }
1058
- const hadAttribute = target.getAttribute("contenteditable");
1059
- target.setAttribute("contenteditable", "plaintext-only");
1060
- if (target.contentEditable !== "plaintext-only") target.setAttribute("contenteditable", "true");
1061
- target.setAttribute("spellcheck", "true");
1062
- const keep = () => {
1063
- if (hadAttribute === null) target.removeAttribute("contenteditable");
1064
- else target.setAttribute("contenteditable", hadAttribute);
1065
- target.removeAttribute("spellcheck");
1066
- };
1067
- return {
1068
- keep,
1069
- revert: () => {
1070
- keep();
1071
- target.replaceChildren(...children);
1072
- for (const [node, text] of texts) node.data = text;
1073
- }
1074
- };
1075
- }
1076
- function typedValue(session) {
1077
- if (session.input) return session.input.value;
1078
- const element = session.element;
1079
- return element.innerText ?? element.textContent ?? "";
1080
- }
1081
- function finishEdit(session, keepTyped) {
1082
- session.done = true;
1083
- if (keepTyped) session.keep();
1084
- else session.revert();
1085
- session.card?.remove();
1086
- if (editing === session) editing = null;
1087
- renderBottom();
1088
- }
1089
- function cancelEdit(session) {
1090
- if (session.done) return;
1091
- finishEdit(session, false);
1092
- }
1093
- function commitEdit(session) {
1094
- if (session.done) return;
1095
- const parsed = parseProposed(session.field.type, typedValue(session));
1096
- if (!parsed.ok) {
1097
- finishEdit(session, false);
1098
- setToast({ title: m.saveFailed, body: m.invalidNumber, error: true });
1099
- return;
1100
- }
1101
- const before = parseProposed(session.field.type, session.before);
1102
- if (before.ok && before.value === parsed.value) {
1103
- finishEdit(session, false);
1104
- return;
1105
- }
1106
- finishEdit(session, true);
1107
- void save(session, {
1108
- fieldKey: session.tagged.fieldKey,
1109
- locale: session.tagged.locale,
1110
- value: parsed.value
1111
- });
1112
- }
1113
- async function save(session, proposal) {
1114
- setStatus("saving");
1115
- const body = buildDraftBody({
1116
- recordRef: session.recordRef,
1117
- pageUrl: session.pageUrl,
1118
- anchors: session.anchors,
1119
- proposal
1120
- });
1121
- const result = await saveToDraft(ctx, token, body, newIdempotencyKey(win));
1122
- if (destroyed) return;
1123
- if (!result.ok) {
1124
- session.revert();
1125
- setStatus(null);
1126
- onFailure(result, "edit");
1127
- return;
1128
- }
1129
- setStatus("saved");
1130
- }
1131
- function endEdit() {
1132
- if (editing && !editing.done) commitEdit(editing);
1133
- }
1134
- const onKey = (event) => {
1135
- if (composer && event.key === "Escape") {
1136
- event.preventDefault();
1137
- closeComposer();
1138
- return;
1139
- }
1140
- const session = editing;
1141
- if (!session || session.done) return;
1142
- if (event.key === "Escape") {
1143
- event.preventDefault();
1144
- cancelEdit(session);
1145
- } else if (event.key === "Enter" && session.kind === "text" && event.target === session.element && (session.field.type === "TEXT" || event.metaKey || event.ctrlKey)) {
1146
- event.preventDefault();
1147
- commitEdit(session);
1148
- }
1149
- };
1150
- const onClick = (event) => {
1151
- if (editing?.kind === "text" && !editing.done && editing.element.contains(event.target)) {
1152
- event.preventDefault();
1153
- }
1154
- };
1155
- win.addEventListener("keydown", onKey, true);
1156
- win.addEventListener("click", onClick, true);
1157
- win.addEventListener("scroll", schedule, true);
1158
- win.addEventListener("resize", schedule);
1159
- const expiry = win.setTimeout(expire, Math.max(0, stored.exp - Date.now() - 3e4));
1160
- overlay.setMode(OVERLAY_MODE[mode]);
1161
- renderBottom();
1162
- void load();
1163
- return {
1164
- destroy() {
1165
- endEdit();
1166
- destroyed = true;
1167
- closeComposer();
1168
- win.clearTimeout(expiry);
1169
- if (toastTimer !== null) win.clearTimeout(toastTimer);
1170
- if (statusTimer !== null) win.clearTimeout(statusTimer);
1171
- win.removeEventListener("click", onClickFirst, true);
1172
- win.removeEventListener("keydown", onKey, true);
1173
- win.removeEventListener("click", onClick, true);
1174
- win.removeEventListener("scroll", schedule, true);
1175
- win.removeEventListener("resize", schedule);
1176
- stopObserving();
1177
- overlay.destroy();
1178
- host.destroy();
1179
- }
1180
- };
1181
- }
1182
- var CSS = `
1183
- .marks { position: fixed; inset: 0; pointer-events: none; }
1184
- .mark { position: fixed; border-radius: 4px; outline: 2px solid rgba(207,135,0,.55); }
1185
- .bottom { position: fixed; left: 0; right: 0; bottom: 22px; display: flex; flex-direction: column;
1186
- align-items: center; gap: 10px; pointer-events: none; padding: 0 8px; }
1187
- .bottom > .card { position: relative; }
1188
- .toolbar { display: flex; align-items: center; gap: 8px; padding: 6px 6px 6px 12px; white-space: nowrap;
1189
- max-width: 100%; overflow-x: auto; }
1190
- .segments { display: inline-flex; gap: 2px; padding: 2px; border-radius: 7px; background: ${TOKENS.subtle}; }
1191
- .seg { position: relative; height: 26px; padding: 0 9px; border-radius: 5px; font-size: 12px; color: ${TOKENS.muted}; }
1192
- .seg[aria-pressed="true"] { background: ${TOKENS.bg}; color: ${TOKENS.fg}; box-shadow: 0 1px 2px rgba(15,23,42,.05); }
1193
- .seg[aria-disabled="true"] { opacity: .5; cursor: default; }
1194
- .seg[data-tip]:hover::after { content: attr(data-tip); position: absolute; bottom: 34px; left: 50%;
1195
- transform: translateX(-50%); padding: 6px 10px; border-radius: 9px; background: ${TOKENS.dark}; color: #FFFFFF;
1196
- font-size: 12px; white-space: nowrap; }
1197
- .tool { height: 30px; padding: 0 9px; border-radius: 9px; color: ${TOKENS.muted}; }
1198
- .tool:hover, .seg:not([aria-disabled="true"]):hover { background: ${TOKENS.subtle}; }
1199
- .tool.strong { color: ${TOKENS.fg}; }
1200
- .divider { width: 1px; height: 22px; background: ${TOKENS.border}; }
1201
- .avatar { width: 26px; height: 26px; border-radius: 7px; background: #DBEAFE; color: #1D4ED8;
1202
- display: inline-flex; align-items: center; justify-content: center; font-weight: 600; font-size: 12px; }
1203
- .status { display: inline-flex; align-items: center; gap: 6px; padding: 6px 12px; border-radius: 999px;
1204
- font-size: 12px; font-weight: 500; }
1205
- .status > .icon { color: #16A34A; }
1206
- .tray { display: flex; align-items: center; gap: 10px; padding: 8px 8px 8px 14px; }
1207
- .list { width: min(420px, calc(100vw - 16px)); padding: 13px 10px 10px 15px; max-height: 50vh; overflow: auto; }
1208
- .list ul { list-style: none; margin: 8px 0 0; padding: 0; display: flex; flex-direction: column; gap: 2px; }
1209
- .item { width: 100%; text-align: left; align-items: flex-start; gap: 10px; padding: 8px; border-radius: 9px; }
1210
- .item:hover { background: ${TOKENS.subtle}; }
1211
- .num { flex-shrink: 0; width: 22px; height: 22px; border-radius: 999px 999px 999px 0; background: #CF8700;
1212
- color: #FFFFFF; font-weight: 700; font-size: 11px; display: inline-flex; align-items: center; justify-content: center; }
1213
- .item-text { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
1214
- .who { font-weight: 600; font-size: 12px; }
1215
- .flag { font-size: 11px; color: ${TOKENS.goldText}; }
1216
- .what { font-weight: 400; color: ${TOKENS.muted}; overflow-wrap: anywhere; }
1217
- .toast, .refused { width: min(340px, calc(100vw - 16px)); display: flex; gap: 11px; padding: 14px 12px 14px 16px;
1218
- align-self: flex-end; margin-right: 16px; }
1219
- .refused { align-self: center; margin-right: 0; width: min(420px, calc(100vw - 16px)); align-items: flex-start; }
1220
- .toast > .icon { color: #16A34A; }
1221
- .refused > .icon, .toast-error > .icon { color: ${TOKENS.danger}; }
1222
- .toast-text { display: flex; flex-direction: column; gap: 3px; flex: 1; min-width: 0; }
1223
- .title { font-weight: 600; }
1224
- .head { display: flex; align-items: center; justify-content: space-between; gap: 8px; }
1225
- .popover { border-radius: 13px; }
1226
- .popover > .head { padding: 13px 10px 0 15px; }
1227
- .content { display: flex; flex-direction: column; gap: 11px; padding: 10px 15px 15px; }
1228
- .extra { display: flex; flex-direction: column; gap: 8px; }
1229
- .check { gap: 8px; font-size: 13px; align-self: flex-start; }
1230
- .check .box { width: 16px; height: 16px; border-radius: 4px; border: 1px solid #90A1B9; background: ${TOKENS.bg}; }
1231
- .check[aria-checked="true"] .box { background: ${TOKENS.primary}; border-color: ${TOKENS.primaryBorder};
1232
- box-shadow: inset 0 0 0 3px ${TOKENS.bg}; }
1233
- .propose { align-self: flex-start; height: 30px; }
1234
- .proposal { display: flex; flex-direction: column; gap: 6px; padding: 10px; border-radius: 12px; background: ${TOKENS.goldTint}; }
1235
- .chip { display: inline-flex; align-items: center; gap: 4px; height: 22px; padding: 0 8px 0 6px; border-radius: 7px;
1236
- background: ${TOKENS.goldTint}; color: ${TOKENS.goldText}; font: 400 12px Poppins, ui-sans-serif, system-ui, sans-serif;
1237
- white-space: nowrap; max-width: 220px; overflow: hidden; text-overflow: ellipsis; }
1238
- .before { overflow-wrap: anywhere; }
1239
- .strong-text { color: ${TOKENS.fg}; }
1240
- .small { font-size: 12px; }
1241
- .composer, .value { width: 100%; font: 400 13px/22px Poppins, ui-sans-serif, system-ui, sans-serif; color: ${TOKENS.fg};
1242
- border: 1px solid ${TOKENS.border}; border-radius: 12px; padding: 8px 12px; background: ${TOKENS.bg}; resize: vertical; }
1243
- input.value { height: 36px; border-radius: 9px; }
1244
- .composer:focus, .value:focus { border-color: #90A1B9; box-shadow: 0 0 0 3px ${TOKENS.focus}; outline: none; }
1245
- .error:empty { display: none; }
1246
- .actions { display: flex; justify-content: flex-end; gap: 8px; flex-wrap: wrap; }
1247
- .foot { display: flex; justify-content: flex-end; gap: 8px; flex-wrap: wrap; margin: 2px -15px -15px;
1248
- padding: 11px 15px; border-top: 1px solid ${TOKENS.border}; }
1249
- .foot .btn { height: 30px; gap: 7px; }
1250
- .foot .btn-outline { padding: 0 14px; }
1251
- `;
1252
- export {
1253
- OVERLAY_HOST_TAG,
1254
- start
1255
- };
1256
- //# sourceMappingURL=suggest-5RHURHED.js.map