@8ux-co/eelzap 0.10.1 → 0.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +58 -1
- package/README.md +34 -7
- package/dist/{chunk-7ZPXZZUT.js → chunk-4ET7VBJA.js} +60 -6
- package/dist/chunk-4ET7VBJA.js.map +1 -0
- package/dist/{chunk-7CXQK2PR.js → chunk-72YCJCBI.js} +65 -12
- package/dist/chunk-72YCJCBI.js.map +1 -0
- package/dist/next.d.ts +21 -11
- package/dist/next.js +94 -26
- package/dist/next.js.map +1 -1
- package/dist/preview.d.ts +81 -5
- package/dist/preview.js +129 -6
- package/dist/preview.js.map +1 -1
- package/dist/react.js +2 -2
- package/dist/react.js.map +1 -1
- package/dist/{signin-QT7YANBN.js → signin-XOGBCGBL.js} +4 -8
- package/dist/signin-XOGBCGBL.js.map +1 -0
- package/dist/{suggest-JZ63L3WP.js → suggest-ERMR7PCE.js} +503 -79
- package/dist/suggest-ERMR7PCE.js.map +1 -0
- package/package.json +5 -5
- package/dist/chunk-7CXQK2PR.js.map +0 -1
- package/dist/chunk-7ZPXZZUT.js.map +0 -1
- package/dist/signin-QT7YANBN.js.map +0 -1
- package/dist/suggest-JZ63L3WP.js.map +0 -1
|
@@ -13,16 +13,17 @@ import {
|
|
|
13
13
|
parseRecordRef,
|
|
14
14
|
reveal,
|
|
15
15
|
tagGuard
|
|
16
|
-
} from "./chunk-
|
|
16
|
+
} from "./chunk-72YCJCBI.js";
|
|
17
17
|
import {
|
|
18
18
|
SUGGEST_MESSAGES,
|
|
19
19
|
TOKENS,
|
|
20
20
|
el,
|
|
21
21
|
icon,
|
|
22
22
|
initials,
|
|
23
|
+
kbd,
|
|
23
24
|
mountHost,
|
|
24
25
|
readTokenAnswer
|
|
25
|
-
} from "./chunk-
|
|
26
|
+
} from "./chunk-4ET7VBJA.js";
|
|
26
27
|
import {
|
|
27
28
|
EXPIRY_MARGIN_MS,
|
|
28
29
|
accessLive,
|
|
@@ -55,6 +56,10 @@ async function call(ctx, token, path, init) {
|
|
|
55
56
|
const seconds = Number.parseInt(response.headers.get("Retry-After") ?? "", 10);
|
|
56
57
|
return { ok: false, kind: "rate", retryAfter: seconds > 0 ? Math.min(seconds, 3600) : 30 };
|
|
57
58
|
}
|
|
59
|
+
if (response.status === 409) {
|
|
60
|
+
const body = await response.json().catch(() => null);
|
|
61
|
+
if (body?.error?.code === "NO_CHANGE") return { ok: false, kind: "unchanged" };
|
|
62
|
+
}
|
|
58
63
|
if (!response.ok) return { ok: false, kind: "invalid" };
|
|
59
64
|
try {
|
|
60
65
|
return { ok: true, data: await response.json() };
|
|
@@ -318,6 +323,36 @@ var DRAG_CSS = `
|
|
|
318
323
|
.bottom[data-below] .seg[data-tip]:hover::after { bottom: auto; top: 34px; }
|
|
319
324
|
`;
|
|
320
325
|
|
|
326
|
+
// src/preview/draft-path.ts
|
|
327
|
+
var UNSAFE_PATH_CHARS = /[\u0000-\u001f\u007f\\]/;
|
|
328
|
+
function safeRedirectPath(raw, requestUrl) {
|
|
329
|
+
const value = raw ?? "/";
|
|
330
|
+
if (value.length === 0 || value.length > 2048) return null;
|
|
331
|
+
if (!value.startsWith("/") || value.startsWith("//")) return null;
|
|
332
|
+
if (UNSAFE_PATH_CHARS.test(value)) return null;
|
|
333
|
+
let base;
|
|
334
|
+
let target;
|
|
335
|
+
try {
|
|
336
|
+
base = new URL(requestUrl);
|
|
337
|
+
target = new URL(value, base);
|
|
338
|
+
} catch {
|
|
339
|
+
return null;
|
|
340
|
+
}
|
|
341
|
+
if (target.origin !== base.origin) return null;
|
|
342
|
+
if (target.pathname.startsWith("//")) return null;
|
|
343
|
+
let decoded = value;
|
|
344
|
+
for (let i = 0; i < value.length; i++) {
|
|
345
|
+
if (/zpt_/i.test(decoded)) return null;
|
|
346
|
+
const next = decoded.replace(
|
|
347
|
+
/%([0-9a-f]{2})/gi,
|
|
348
|
+
(_, hex) => String.fromCharCode(Number.parseInt(hex, 16))
|
|
349
|
+
);
|
|
350
|
+
if (next === decoded) break;
|
|
351
|
+
decoded = next;
|
|
352
|
+
}
|
|
353
|
+
return `${target.pathname}${target.search}${target.hash}`;
|
|
354
|
+
}
|
|
355
|
+
|
|
321
356
|
// src/preview/site/draft-session.ts
|
|
322
357
|
var DRAFT_SESSION_PATH = "/api/public/v1/preview/session";
|
|
323
358
|
var EDIT_INTENT_KEY = "eelzap:edit-intent";
|
|
@@ -374,6 +409,9 @@ function inDraftSession(win, now = Date.now()) {
|
|
|
374
409
|
const value = readJson(win, DRAFT_SESSION_KEY);
|
|
375
410
|
return !!value && typeof value.exp === "number" && value.exp > now && value.origin === win.location.origin;
|
|
376
411
|
}
|
|
412
|
+
function rememberEditIntent(win, now = Date.now()) {
|
|
413
|
+
write2(win, EDIT_INTENT_KEY, { path: win.location.pathname, at: now });
|
|
414
|
+
}
|
|
377
415
|
function takeEditIntent(win, now = Date.now()) {
|
|
378
416
|
const value = readJson(win, EDIT_INTENT_KEY);
|
|
379
417
|
remove(win, EDIT_INTENT_KEY);
|
|
@@ -388,9 +426,11 @@ function acceptSessionUrl(raw, win, draftRoute) {
|
|
|
388
426
|
return null;
|
|
389
427
|
}
|
|
390
428
|
if (url.origin !== win.location.origin || url.pathname !== draftRoute) return null;
|
|
391
|
-
if (
|
|
392
|
-
const
|
|
393
|
-
if (
|
|
429
|
+
if (url.username || url.password || url.search) return null;
|
|
430
|
+
const params = new URLSearchParams(url.hash.slice(1));
|
|
431
|
+
if (!/^zpt_[A-Za-z0-9_-]{43}$/.test(params.get("token") ?? "")) return null;
|
|
432
|
+
const path = params.get("path") ?? "";
|
|
433
|
+
if (safeRedirectPath(path, win.location.href) === null) return null;
|
|
394
434
|
return url.toString();
|
|
395
435
|
}
|
|
396
436
|
async function requestDraftSession(ctx, token, now = Date.now) {
|
|
@@ -403,7 +443,7 @@ async function requestDraftSession(ctx, token, now = Date.now) {
|
|
|
403
443
|
method: "POST",
|
|
404
444
|
headers: { Authorization: `Bearer ${token}`, "Content-Type": "application/json" },
|
|
405
445
|
credentials: "omit",
|
|
406
|
-
body: JSON.stringify({ draftRoute, path: currentPath(win) })
|
|
446
|
+
body: JSON.stringify({ draftRoute, path: currentPath(win), tokenTransport: "fragment" })
|
|
407
447
|
});
|
|
408
448
|
} catch {
|
|
409
449
|
return { ok: false, reason: "refused" };
|
|
@@ -465,12 +505,112 @@ async function leaveDraftSession(ctx, token, tagged) {
|
|
|
465
505
|
}
|
|
466
506
|
|
|
467
507
|
// src/preview/site/suggestion.ts
|
|
508
|
+
var SUGGESTIBLE_TYPES = ["TEXT", "LONG_TEXT", "NUMBER", "URL"];
|
|
509
|
+
var INPUT_TYPES = /* @__PURE__ */ new Set(["URL", "NUMBER", "INTEGER", "CURRENCY", "DATE", "DATETIME"]);
|
|
510
|
+
function proposableField(tagged, field) {
|
|
511
|
+
return !!tagged && !!field && !tagged.html && SUGGESTIBLE_TYPES.includes(field.type);
|
|
512
|
+
}
|
|
468
513
|
function editKindFor(tagged, field) {
|
|
469
514
|
if (!tagged || !field || tagged.html) return null;
|
|
470
515
|
if (field.type === "TEXT" || field.type === "LONG_TEXT") return "text";
|
|
471
|
-
if (
|
|
516
|
+
if (INPUT_TYPES.has(field.type)) return "input";
|
|
517
|
+
if (field.type === "ENUM") return field.options?.length ? "choice" : null;
|
|
518
|
+
if (field.type === "BOOLEAN") return "switch";
|
|
472
519
|
return null;
|
|
473
520
|
}
|
|
521
|
+
var ZERO_DIGITS = /* @__PURE__ */ new Set([
|
|
522
|
+
"BIF",
|
|
523
|
+
"CLP",
|
|
524
|
+
"DJF",
|
|
525
|
+
"GNF",
|
|
526
|
+
"ISK",
|
|
527
|
+
"JPY",
|
|
528
|
+
"KMF",
|
|
529
|
+
"KRW",
|
|
530
|
+
"PYG",
|
|
531
|
+
"RWF",
|
|
532
|
+
"UGX",
|
|
533
|
+
"UYI",
|
|
534
|
+
"VND",
|
|
535
|
+
"VUV",
|
|
536
|
+
"XAF",
|
|
537
|
+
"XOF",
|
|
538
|
+
"XPF"
|
|
539
|
+
]);
|
|
540
|
+
var THREE_DIGITS = /* @__PURE__ */ new Set(["BHD", "IQD", "JOD", "KWD", "LYD", "OMR", "TND"]);
|
|
541
|
+
function currencyDigits(code) {
|
|
542
|
+
const upper = (code ?? "").toUpperCase();
|
|
543
|
+
if (ZERO_DIGITS.has(upper)) return 0;
|
|
544
|
+
if (THREE_DIGITS.has(upper)) return 3;
|
|
545
|
+
return 2;
|
|
546
|
+
}
|
|
547
|
+
var pad = (n) => String(n).padStart(2, "0");
|
|
548
|
+
function inputSpec(field, value) {
|
|
549
|
+
const digits = currencyDigits(field.currency);
|
|
550
|
+
const limits = { min: field.min, max: field.max };
|
|
551
|
+
const number = typeof value === "number" && Number.isFinite(value) ? value : null;
|
|
552
|
+
switch (field.type) {
|
|
553
|
+
case "NUMBER":
|
|
554
|
+
return {
|
|
555
|
+
type: "number",
|
|
556
|
+
text: number === null ? "" : String(number),
|
|
557
|
+
...limits,
|
|
558
|
+
step: field.step ?? "any"
|
|
559
|
+
};
|
|
560
|
+
case "INTEGER":
|
|
561
|
+
return { type: "number", text: number === null ? "" : String(number), ...limits, step: 1 };
|
|
562
|
+
case "CURRENCY":
|
|
563
|
+
return {
|
|
564
|
+
type: "number",
|
|
565
|
+
text: number === null ? "" : (number / 10 ** digits).toFixed(digits),
|
|
566
|
+
...limits,
|
|
567
|
+
step: field.step ?? 10 ** -digits
|
|
568
|
+
};
|
|
569
|
+
case "DATE":
|
|
570
|
+
return { type: "date", text: typeof value === "string" ? value.slice(0, 10) : "" };
|
|
571
|
+
case "DATETIME": {
|
|
572
|
+
const at = typeof value === "string" ? new Date(value) : null;
|
|
573
|
+
return {
|
|
574
|
+
type: "datetime-local",
|
|
575
|
+
text: at && Number.isFinite(at.getTime()) ? `${at.getFullYear()}-${pad(at.getMonth() + 1)}-${pad(at.getDate())}T${pad(at.getHours())}:${pad(at.getMinutes())}` : ""
|
|
576
|
+
};
|
|
577
|
+
}
|
|
578
|
+
default:
|
|
579
|
+
return { type: "text", text: typeof value === "string" ? value : "" };
|
|
580
|
+
}
|
|
581
|
+
}
|
|
582
|
+
function parseInput(field, raw) {
|
|
583
|
+
const text = cleanStega(raw).trim();
|
|
584
|
+
if (field.type === "URL") return { ok: true, value: normalizeText(text) };
|
|
585
|
+
if (text === "") return { ok: true, value: null };
|
|
586
|
+
const number = Number(text.replace(",", "."));
|
|
587
|
+
switch (field.type) {
|
|
588
|
+
case "NUMBER":
|
|
589
|
+
return Number.isFinite(number) ? { ok: true, value: number } : { ok: false };
|
|
590
|
+
case "INTEGER":
|
|
591
|
+
return Number.isInteger(number) ? { ok: true, value: number } : { ok: false };
|
|
592
|
+
case "CURRENCY":
|
|
593
|
+
return Number.isFinite(number) ? { ok: true, value: Math.round(number * 10 ** currencyDigits(field.currency)) } : { ok: false };
|
|
594
|
+
case "DATE":
|
|
595
|
+
return /^\d{4}-\d{2}-\d{2}$/.test(text) && Number.isFinite(Date.parse(text)) ? { ok: true, value: text } : { ok: false };
|
|
596
|
+
case "DATETIME": {
|
|
597
|
+
const at = new Date(text);
|
|
598
|
+
return Number.isFinite(at.getTime()) ? { ok: true, value: at.toISOString() } : { ok: false };
|
|
599
|
+
}
|
|
600
|
+
default:
|
|
601
|
+
return { ok: true, value: text };
|
|
602
|
+
}
|
|
603
|
+
}
|
|
604
|
+
function sameRaw(type, a, b) {
|
|
605
|
+
const before = a ?? null;
|
|
606
|
+
if (typeof before === "string" && typeof b === "string") {
|
|
607
|
+
if (type === "DATETIME") {
|
|
608
|
+
return Math.floor(Date.parse(before) / 6e4) === Math.floor(Date.parse(b) / 6e4);
|
|
609
|
+
}
|
|
610
|
+
return normalizeText(before) === normalizeText(b);
|
|
611
|
+
}
|
|
612
|
+
return before === b;
|
|
613
|
+
}
|
|
474
614
|
function fieldOf(data, tagged) {
|
|
475
615
|
if (!data || !tagged) return null;
|
|
476
616
|
const field = data.fields?.[tagged.recordRef]?.[tagged.fieldKey];
|
|
@@ -549,7 +689,7 @@ function buildDraftBody(input) {
|
|
|
549
689
|
...recordKeys(input.recordRef),
|
|
550
690
|
pageUrl: input.pageUrl,
|
|
551
691
|
anchors: input.anchors.slice(0, 20),
|
|
552
|
-
body: String(input.proposal.value).slice(0, 1e4) || " ",
|
|
692
|
+
body: (input.display ?? String(input.proposal.value ?? "")).slice(0, 1e4) || " ",
|
|
553
693
|
...proposedPart(input.proposal)
|
|
554
694
|
};
|
|
555
695
|
}
|
|
@@ -781,6 +921,7 @@ var OVERLAY_MODE = {
|
|
|
781
921
|
var PENDING_PIN = "eelzap:pending";
|
|
782
922
|
var LARGE_SHARE = 0.5;
|
|
783
923
|
var TOAST_MS = 8e3;
|
|
924
|
+
var RELOAD_AFTER_SAVE_MS = 700;
|
|
784
925
|
var SAVED_MS = 4e3;
|
|
785
926
|
function start(ctx, options = {}) {
|
|
786
927
|
const { win, doc } = ctx;
|
|
@@ -795,6 +936,7 @@ function start(ctx, options = {}) {
|
|
|
795
936
|
const m = SUGGEST_MESSAGES[ctx.locale];
|
|
796
937
|
const dm = DRAFT_SESSION_MESSAGES[ctx.locale];
|
|
797
938
|
let reopenEdit = takeEditIntent(win);
|
|
939
|
+
let retryDraft = !reopenEdit;
|
|
798
940
|
const host = mountHost(doc, CSS + DRAG_CSS);
|
|
799
941
|
const layer = el(doc, "div", { class: "layer" });
|
|
800
942
|
const marks = el(doc, "div", { class: "marks" });
|
|
@@ -813,6 +955,7 @@ function start(ctx, options = {}) {
|
|
|
813
955
|
let editing = null;
|
|
814
956
|
let list = null;
|
|
815
957
|
let toast = null;
|
|
958
|
+
let info = null;
|
|
816
959
|
let toastTimer = null;
|
|
817
960
|
let status = null;
|
|
818
961
|
let statusTimer = null;
|
|
@@ -834,7 +977,10 @@ function start(ctx, options = {}) {
|
|
|
834
977
|
};
|
|
835
978
|
win.addEventListener("click", onClickFirst, true);
|
|
836
979
|
const onPressFirst = (event) => {
|
|
837
|
-
if (
|
|
980
|
+
if (event.target === host.host) return;
|
|
981
|
+
if (menuOpen) setMenu(false);
|
|
982
|
+
if (editing?.card && !editing.done) commitEdit(editing);
|
|
983
|
+
if (info) closeInfo();
|
|
838
984
|
};
|
|
839
985
|
win.addEventListener("pointerdown", onPressFirst, true);
|
|
840
986
|
layer.addEventListener("pointerdown", (event) => {
|
|
@@ -969,12 +1115,14 @@ function start(ctx, options = {}) {
|
|
|
969
1115
|
function setMode(next) {
|
|
970
1116
|
if (next === "edit" && !canEdit() || paused && next !== "navigate") return;
|
|
971
1117
|
if (status === "preparing") return;
|
|
972
|
-
if (next === "edit" && !index.elements.length && !inDraftSession(win)) {
|
|
1118
|
+
if (next === "edit" && !index.elements.length && (retryDraft || !inDraftSession(win))) {
|
|
1119
|
+
retryDraft = false;
|
|
973
1120
|
void prepareEdit();
|
|
974
1121
|
return;
|
|
975
1122
|
}
|
|
976
1123
|
endEdit();
|
|
977
1124
|
closeComposer();
|
|
1125
|
+
closeInfo();
|
|
978
1126
|
mode = next;
|
|
979
1127
|
selection = [];
|
|
980
1128
|
overlay.clearSelection();
|
|
@@ -1075,7 +1223,7 @@ function start(ctx, options = {}) {
|
|
|
1075
1223
|
function withTip(target, text, end) {
|
|
1076
1224
|
const show = () => {
|
|
1077
1225
|
tip.replaceChildren(text);
|
|
1078
|
-
if (key) tip.append(
|
|
1226
|
+
if (key) tip.append(kbd(doc, "Shift", key));
|
|
1079
1227
|
tip.classList.add("on");
|
|
1080
1228
|
const rect = target.getBoundingClientRect();
|
|
1081
1229
|
const vw = doc.documentElement.clientWidth || win.innerWidth;
|
|
@@ -1494,6 +1642,13 @@ function start(ctx, options = {}) {
|
|
|
1494
1642
|
box.style.top = `${rect.top - 3}px`;
|
|
1495
1643
|
box.style.width = `${rect.width + 6}px`;
|
|
1496
1644
|
box.style.height = `${rect.height + 6}px`;
|
|
1645
|
+
if (editing?.card)
|
|
1646
|
+
box.append(
|
|
1647
|
+
el(doc, "span", {
|
|
1648
|
+
class: "mark-chip",
|
|
1649
|
+
text: editing.field.label || editing.tagged.fieldKey
|
|
1650
|
+
})
|
|
1651
|
+
);
|
|
1497
1652
|
return box;
|
|
1498
1653
|
})
|
|
1499
1654
|
);
|
|
@@ -1505,18 +1660,42 @@ function start(ctx, options = {}) {
|
|
|
1505
1660
|
frame = null;
|
|
1506
1661
|
drawMarks();
|
|
1507
1662
|
if (composer) position(composer.card, anchorRect(composer));
|
|
1508
|
-
if (editing?.card)
|
|
1663
|
+
if (editing?.card)
|
|
1664
|
+
position(editing.card, editing.element.getBoundingClientRect(), editing.card.offsetWidth);
|
|
1665
|
+
if (info) position(info.card, info.tagged.element.getBoundingClientRect(), 300);
|
|
1509
1666
|
});
|
|
1510
1667
|
}
|
|
1511
|
-
function position(card, rect) {
|
|
1512
|
-
const
|
|
1513
|
-
const
|
|
1514
|
-
const
|
|
1515
|
-
const top = below + height < win.innerHeight - 90 ? below : Math.max(8, rect.top - height - 10);
|
|
1516
|
-
const left = Math.min(Math.max(8, rect.left), Math.max(8, win.innerWidth - width - 8));
|
|
1668
|
+
function position(card, rect, preferred = 420) {
|
|
1669
|
+
const vw = win.innerWidth;
|
|
1670
|
+
const vh = win.innerHeight;
|
|
1671
|
+
const width = Math.min(preferred, vw - 16);
|
|
1517
1672
|
card.style.width = `${width}px`;
|
|
1518
|
-
card.
|
|
1519
|
-
|
|
1673
|
+
const height = card.offsetHeight || 260;
|
|
1674
|
+
const bar = bottom.querySelector(".toolbar, .pill")?.getBoundingClientRect();
|
|
1675
|
+
const clampX = (x) => Math.min(Math.max(8, x), Math.max(8, vw - width - 8));
|
|
1676
|
+
const hits = (x, y) => !!bar && x < bar.right + 8 && x + width > bar.left - 8 && y < bar.bottom + 8 && y + height > bar.top - 8;
|
|
1677
|
+
const fits = (y) => y >= 8 && y + height <= vh - 8;
|
|
1678
|
+
let place = null;
|
|
1679
|
+
for (const y of [rect.bottom + 10, rect.top - height - 10]) {
|
|
1680
|
+
if (!fits(y)) continue;
|
|
1681
|
+
for (const x of [
|
|
1682
|
+
clampX(rect.left),
|
|
1683
|
+
clampX((bar?.right ?? 0) + 8),
|
|
1684
|
+
clampX((bar?.left ?? 0) - 8 - width)
|
|
1685
|
+
]) {
|
|
1686
|
+
if (!hits(x, y)) {
|
|
1687
|
+
place = [x, y];
|
|
1688
|
+
break;
|
|
1689
|
+
}
|
|
1690
|
+
}
|
|
1691
|
+
if (place) break;
|
|
1692
|
+
}
|
|
1693
|
+
if (!place) {
|
|
1694
|
+
const y = fits(rect.bottom + 10) ? rect.bottom + 10 : Math.max(8, rect.top - height - 10);
|
|
1695
|
+
place = [clampX(rect.left), y];
|
|
1696
|
+
}
|
|
1697
|
+
card.style.left = `${place[0]}px`;
|
|
1698
|
+
card.style.top = `${place[1]}px`;
|
|
1520
1699
|
}
|
|
1521
1700
|
function onSelect(elements) {
|
|
1522
1701
|
if (mode !== "comment" || composer) return;
|
|
@@ -1562,7 +1741,7 @@ function start(ctx, options = {}) {
|
|
|
1562
1741
|
const own = single ? index.get(single) : null;
|
|
1563
1742
|
const tagged = own && own.recordRef === recordRef ? own : null;
|
|
1564
1743
|
const field = fieldOf(data, tagged);
|
|
1565
|
-
const proposable =
|
|
1744
|
+
const proposable = proposableField(tagged, field);
|
|
1566
1745
|
const target = single;
|
|
1567
1746
|
const before = field?.type === "URL" && target?.localName === "a" ? target.getAttribute("href") ?? "" : normalizeText(target?.textContent ?? "");
|
|
1568
1747
|
const spotPin = point ? anchors[0] : null;
|
|
@@ -1834,13 +2013,17 @@ function start(ctx, options = {}) {
|
|
|
1834
2013
|
const field = fieldOf(data, tagged);
|
|
1835
2014
|
const kind = editKindFor(tagged, field);
|
|
1836
2015
|
if (!kind || !field) {
|
|
1837
|
-
|
|
2016
|
+
openInfoCard(tagged);
|
|
1838
2017
|
return;
|
|
1839
2018
|
}
|
|
2019
|
+
closeInfo();
|
|
1840
2020
|
const element = tagged.element;
|
|
1841
2021
|
const pageUrl = currentPageUrl(win);
|
|
1842
2022
|
const anchors = [toApiAnchor(buildAnchor(element, index, pageUrl, doc, win), tagged.recordRef)];
|
|
1843
2023
|
const before = field.type === "URL" && element.localName === "a" ? element.getAttribute("href") ?? "" : normalizeText(element.textContent ?? "");
|
|
2024
|
+
const stored2 = data?.values?.[tagged.recordRef]?.[tagged.fieldKey];
|
|
2025
|
+
const legacy = field.type === "NUMBER" && stored2 === void 0 ? parseProposed("NUMBER", before) : null;
|
|
2026
|
+
const raw = legacy?.ok ? legacy.value : stored2;
|
|
1844
2027
|
const inline = kind === "text" ? beginInlineEdit(element) : null;
|
|
1845
2028
|
const session = {
|
|
1846
2029
|
element,
|
|
@@ -1851,6 +2034,9 @@ function start(ctx, options = {}) {
|
|
|
1851
2034
|
pageUrl,
|
|
1852
2035
|
anchors,
|
|
1853
2036
|
before,
|
|
2037
|
+
raw,
|
|
2038
|
+
picked: raw,
|
|
2039
|
+
returnFocus: doc.activeElement,
|
|
1854
2040
|
revert: inline?.revert ?? (() => {
|
|
1855
2041
|
}),
|
|
1856
2042
|
keep: inline?.keep ?? (() => {
|
|
@@ -1863,58 +2049,228 @@ function start(ctx, options = {}) {
|
|
|
1863
2049
|
if (inline) {
|
|
1864
2050
|
element.addEventListener("blur", () => commitEdit(session), { once: true });
|
|
1865
2051
|
element.focus?.();
|
|
2052
|
+
} else if (kind === "choice") {
|
|
2053
|
+
openChoiceCard(session);
|
|
2054
|
+
} else if (kind === "switch") {
|
|
2055
|
+
openSwitchCard(session);
|
|
1866
2056
|
} else {
|
|
1867
2057
|
openInputCard(session);
|
|
1868
2058
|
}
|
|
1869
2059
|
renderBottom();
|
|
1870
2060
|
}
|
|
2061
|
+
function openValueCard(session, body, focus, width) {
|
|
2062
|
+
const label = session.field.label || session.tagged.fieldKey;
|
|
2063
|
+
const card = el(
|
|
2064
|
+
doc,
|
|
2065
|
+
"div",
|
|
2066
|
+
{
|
|
2067
|
+
class: "card editor",
|
|
2068
|
+
attrs: { role: "dialog", "aria-label": label },
|
|
2069
|
+
on: {
|
|
2070
|
+
keydown: (event) => {
|
|
2071
|
+
const e = event;
|
|
2072
|
+
if (e.key !== "Enter" || e.target.getAttribute("role") === "option") return;
|
|
2073
|
+
e.preventDefault();
|
|
2074
|
+
commitEdit(session);
|
|
2075
|
+
}
|
|
2076
|
+
}
|
|
2077
|
+
},
|
|
2078
|
+
[
|
|
2079
|
+
body,
|
|
2080
|
+
el(doc, "div", { class: "keys" }, [
|
|
2081
|
+
kbd(doc, "Enter"),
|
|
2082
|
+
m.enterSaves,
|
|
2083
|
+
kbd(doc, "Esc"),
|
|
2084
|
+
m.escCancels
|
|
2085
|
+
])
|
|
2086
|
+
]
|
|
2087
|
+
);
|
|
2088
|
+
card.style.width = `${width}px`;
|
|
2089
|
+
session.card = card;
|
|
2090
|
+
layer.append(card);
|
|
2091
|
+
position(card, session.element.getBoundingClientRect(), width);
|
|
2092
|
+
focus.focus();
|
|
2093
|
+
}
|
|
1871
2094
|
function openInputCard(session) {
|
|
2095
|
+
const { field } = session;
|
|
2096
|
+
const spec = field.type === "URL" ? { type: "url", text: session.before, min: void 0, max: void 0, step: void 0 } : inputSpec(field, session.raw);
|
|
1872
2097
|
const input = el(doc, "input", {
|
|
1873
2098
|
class: "value",
|
|
1874
2099
|
attrs: {
|
|
1875
|
-
"aria-label":
|
|
1876
|
-
type:
|
|
1877
|
-
|
|
2100
|
+
"aria-label": field.label || session.tagged.fieldKey,
|
|
2101
|
+
type: spec.type,
|
|
2102
|
+
min: spec.min === void 0 ? null : String(spec.min),
|
|
2103
|
+
max: spec.max === void 0 ? null : String(spec.max),
|
|
2104
|
+
step: spec.step === void 0 ? null : String(spec.step)
|
|
1878
2105
|
}
|
|
1879
2106
|
});
|
|
1880
|
-
input.value =
|
|
1881
|
-
input
|
|
1882
|
-
|
|
1883
|
-
|
|
1884
|
-
|
|
1885
|
-
}
|
|
1886
|
-
});
|
|
1887
|
-
const card = el(doc, "div", { class: "card popover sheet", attrs: { role: "dialog" } }, [
|
|
1888
|
-
el(doc, "div", { class: "head" }, [
|
|
1889
|
-
el(doc, "span", { class: "chip" }, [
|
|
1890
|
-
icon(doc, "field", 12),
|
|
1891
|
-
session.field.label || session.tagged.fieldKey
|
|
1892
|
-
]),
|
|
1893
|
-
closeButton(m.close, () => cancelEdit(session))
|
|
1894
|
-
]),
|
|
1895
|
-
el(doc, "div", { class: "content" }, [
|
|
2107
|
+
input.value = spec.text;
|
|
2108
|
+
session.input = input;
|
|
2109
|
+
const unit2 = field.type === "CURRENCY" ? field.currency ?? null : null;
|
|
2110
|
+
openValueCard(
|
|
2111
|
+
session,
|
|
2112
|
+
unit2 ? el(doc, "div", { class: "with-unit" }, [
|
|
1896
2113
|
input,
|
|
1897
|
-
el(doc, "
|
|
1898
|
-
|
|
1899
|
-
|
|
1900
|
-
|
|
1901
|
-
|
|
1902
|
-
|
|
2114
|
+
el(doc, "span", { class: "muted", text: unit2 })
|
|
2115
|
+
]) : input,
|
|
2116
|
+
input,
|
|
2117
|
+
field.type === "URL" ? 280 : 220
|
|
2118
|
+
);
|
|
2119
|
+
}
|
|
2120
|
+
function openChoiceCard(session) {
|
|
2121
|
+
const buttons = (session.field.options ?? []).map(
|
|
2122
|
+
(option) => el(
|
|
2123
|
+
doc,
|
|
2124
|
+
"button",
|
|
2125
|
+
{
|
|
2126
|
+
class: "option",
|
|
2127
|
+
attrs: {
|
|
2128
|
+
type: "button",
|
|
2129
|
+
role: "option",
|
|
2130
|
+
"aria-selected": String(option.id === session.raw),
|
|
2131
|
+
tabindex: "-1"
|
|
2132
|
+
},
|
|
2133
|
+
on: {
|
|
2134
|
+
click: () => {
|
|
2135
|
+
session.picked = option.id;
|
|
2136
|
+
commitEdit(session);
|
|
2137
|
+
}
|
|
2138
|
+
}
|
|
2139
|
+
},
|
|
2140
|
+
[
|
|
2141
|
+
el(doc, "span", { class: "option-label", text: option.label }),
|
|
2142
|
+
option.id === session.raw ? icon(doc, "tick", 14) : null
|
|
2143
|
+
]
|
|
2144
|
+
)
|
|
2145
|
+
);
|
|
2146
|
+
const first = buttons.find((button) => button.getAttribute("aria-selected") === "true") ?? buttons[0];
|
|
2147
|
+
first.tabIndex = 0;
|
|
2148
|
+
const list2 = el(
|
|
2149
|
+
doc,
|
|
2150
|
+
"div",
|
|
2151
|
+
{
|
|
2152
|
+
class: "options",
|
|
2153
|
+
attrs: { role: "listbox", "aria-label": m.options(session.field.label) },
|
|
2154
|
+
on: {
|
|
2155
|
+
keydown: (event) => {
|
|
2156
|
+
const e = event;
|
|
2157
|
+
const at = buttons.indexOf(host.root.activeElement);
|
|
2158
|
+
const to = { ArrowDown: at + 1, ArrowUp: at - 1, Home: 0, End: buttons.length - 1 }[e.key];
|
|
2159
|
+
if (to === void 0) return;
|
|
2160
|
+
e.preventDefault();
|
|
2161
|
+
buttons[Math.min(buttons.length - 1, Math.max(0, to))]?.focus();
|
|
2162
|
+
}
|
|
2163
|
+
}
|
|
2164
|
+
},
|
|
2165
|
+
buttons
|
|
2166
|
+
);
|
|
2167
|
+
openValueCard(session, list2, first, 230);
|
|
2168
|
+
}
|
|
2169
|
+
function openSwitchCard(session) {
|
|
2170
|
+
session.picked = session.raw === true;
|
|
2171
|
+
const label = session.field.label || session.tagged.fieldKey;
|
|
2172
|
+
const toggle = el(
|
|
2173
|
+
doc,
|
|
2174
|
+
"button",
|
|
2175
|
+
{
|
|
2176
|
+
class: "switch",
|
|
2177
|
+
attrs: {
|
|
2178
|
+
type: "button",
|
|
2179
|
+
role: "switch",
|
|
2180
|
+
"aria-checked": String(session.picked),
|
|
2181
|
+
"aria-label": label
|
|
2182
|
+
},
|
|
2183
|
+
on: {
|
|
2184
|
+
click: () => {
|
|
2185
|
+
session.picked = !session.picked;
|
|
2186
|
+
toggle.setAttribute("aria-checked", String(session.picked));
|
|
2187
|
+
}
|
|
2188
|
+
}
|
|
2189
|
+
},
|
|
2190
|
+
[el(doc, "span", { class: "knob" })]
|
|
2191
|
+
);
|
|
2192
|
+
openValueCard(
|
|
2193
|
+
session,
|
|
2194
|
+
el(doc, "div", { class: "switch-row" }, [
|
|
2195
|
+
el(doc, "span", { class: "title", text: label }),
|
|
2196
|
+
toggle
|
|
2197
|
+
]),
|
|
2198
|
+
toggle,
|
|
2199
|
+
220
|
|
2200
|
+
);
|
|
2201
|
+
}
|
|
2202
|
+
function openInfoCard(tagged) {
|
|
2203
|
+
closeInfo();
|
|
2204
|
+
const field = fieldOf(data, tagged);
|
|
2205
|
+
const label = field?.label || tagged.fieldKey;
|
|
2206
|
+
const card = el(
|
|
2207
|
+
doc,
|
|
2208
|
+
"div",
|
|
2209
|
+
{ class: "card info", attrs: { role: "dialog", "aria-label": m.editInZap } },
|
|
2210
|
+
[
|
|
2211
|
+
icon(doc, "info", 16),
|
|
2212
|
+
el(doc, "div", { class: "toast-text" }, [
|
|
2213
|
+
el(doc, "span", { class: "title", text: m.editInZap }),
|
|
2214
|
+
el(doc, "span", {
|
|
2215
|
+
class: "muted",
|
|
2216
|
+
text: m.editInZapBody(label, field?.type ?? "", tagged.recordRef.startsWith("doc:"))
|
|
1903
2217
|
}),
|
|
1904
|
-
el(doc, "
|
|
1905
|
-
|
|
1906
|
-
|
|
1907
|
-
|
|
1908
|
-
|
|
1909
|
-
|
|
2218
|
+
el(doc, "div", { class: "actions start" }, [
|
|
2219
|
+
el(
|
|
2220
|
+
doc,
|
|
2221
|
+
"button",
|
|
2222
|
+
{
|
|
2223
|
+
class: "btn btn-outline",
|
|
2224
|
+
attrs: { type: "button", "data-action": "open-field" },
|
|
2225
|
+
on: {
|
|
2226
|
+
click: () => {
|
|
2227
|
+
closeInfo();
|
|
2228
|
+
win.open(fieldEditorUrl(tagged), "_blank", "noopener");
|
|
2229
|
+
}
|
|
2230
|
+
}
|
|
2231
|
+
},
|
|
2232
|
+
[icon(doc, "external", 14), m.openInZap]
|
|
2233
|
+
),
|
|
2234
|
+
el(
|
|
2235
|
+
doc,
|
|
2236
|
+
"button",
|
|
2237
|
+
{
|
|
2238
|
+
class: "btn btn-ghost",
|
|
2239
|
+
attrs: { type: "button", "data-action": "comment-field" },
|
|
2240
|
+
on: {
|
|
2241
|
+
click: () => {
|
|
2242
|
+
closeInfo();
|
|
2243
|
+
setMode("comment");
|
|
2244
|
+
if (mode === "comment") openComposer([tagged.element], null);
|
|
2245
|
+
}
|
|
2246
|
+
}
|
|
2247
|
+
},
|
|
2248
|
+
[icon(doc, "commentPlus", 14), m.comment]
|
|
2249
|
+
)
|
|
2250
|
+
])
|
|
1910
2251
|
])
|
|
1911
|
-
]
|
|
1912
|
-
|
|
1913
|
-
|
|
1914
|
-
session.input = input;
|
|
2252
|
+
]
|
|
2253
|
+
);
|
|
2254
|
+
info = { tagged, card };
|
|
1915
2255
|
layer.append(card);
|
|
1916
|
-
position(card,
|
|
1917
|
-
|
|
2256
|
+
position(card, tagged.element.getBoundingClientRect(), 300);
|
|
2257
|
+
}
|
|
2258
|
+
function closeInfo() {
|
|
2259
|
+
info?.card.remove();
|
|
2260
|
+
info = null;
|
|
2261
|
+
}
|
|
2262
|
+
function fieldEditorUrl(tagged) {
|
|
2263
|
+
const own = !data?.records?.[tagged.recordRef];
|
|
2264
|
+
const base = data?.editorUrls?.[tagged.recordRef] ?? (own ? data?.viewer?.editorUrl : null) ?? null;
|
|
2265
|
+
const safe = zapUrl(base, ctx.zapOrigin);
|
|
2266
|
+
if (!base) return safe;
|
|
2267
|
+
try {
|
|
2268
|
+
const url = new URL(safe);
|
|
2269
|
+
url.searchParams.set("field", tagged.fieldKey);
|
|
2270
|
+
return url.href;
|
|
2271
|
+
} catch {
|
|
2272
|
+
return safe;
|
|
2273
|
+
}
|
|
1918
2274
|
}
|
|
1919
2275
|
function beginInlineEdit(target) {
|
|
1920
2276
|
const children = Array.from(target.childNodes);
|
|
@@ -1956,6 +2312,8 @@ function start(ctx, options = {}) {
|
|
|
1956
2312
|
session.card?.remove();
|
|
1957
2313
|
if (editing === session) editing = null;
|
|
1958
2314
|
renderBottom();
|
|
2315
|
+
const back = session.returnFocus;
|
|
2316
|
+
if (session.card && back?.isConnected) back.focus?.({ preventScroll: true });
|
|
1959
2317
|
}
|
|
1960
2318
|
function cancelEdit(session) {
|
|
1961
2319
|
if (session.done) return;
|
|
@@ -1963,42 +2321,77 @@ function start(ctx, options = {}) {
|
|
|
1963
2321
|
}
|
|
1964
2322
|
function commitEdit(session) {
|
|
1965
2323
|
if (session.done) return;
|
|
1966
|
-
const
|
|
1967
|
-
|
|
1968
|
-
|
|
1969
|
-
|
|
1970
|
-
|
|
2324
|
+
const { field, kind } = session;
|
|
2325
|
+
const text = kind === "text" || field.type === "URL";
|
|
2326
|
+
let value;
|
|
2327
|
+
if (text) {
|
|
2328
|
+
const parsed = parseProposed(field.type, typedValue(session));
|
|
2329
|
+
const before = parseProposed(field.type, session.before);
|
|
2330
|
+
if (parsed.ok && before.ok && sameValue(before.value, parsed.value)) {
|
|
2331
|
+
finishEdit(session, false);
|
|
2332
|
+
return;
|
|
2333
|
+
}
|
|
2334
|
+
if (!parsed.ok) return invalid(session);
|
|
2335
|
+
value = parsed.value;
|
|
2336
|
+
} else if (kind === "input") {
|
|
2337
|
+
if (session.input?.validity?.badInput) return invalid(session);
|
|
2338
|
+
const parsed = parseInput(field, typedValue(session));
|
|
2339
|
+
if (!parsed.ok) return invalid(session);
|
|
2340
|
+
value = parsed.value;
|
|
2341
|
+
} else {
|
|
2342
|
+
value = session.picked ?? null;
|
|
1971
2343
|
}
|
|
1972
|
-
|
|
1973
|
-
if (before.ok && sameValue(before.value, parsed.value)) {
|
|
2344
|
+
if (!text && sameRaw(field.type, session.raw, value)) {
|
|
1974
2345
|
finishEdit(session, false);
|
|
1975
2346
|
return;
|
|
1976
2347
|
}
|
|
1977
2348
|
finishEdit(session, true);
|
|
1978
|
-
void save(
|
|
1979
|
-
|
|
1980
|
-
locale: session.tagged.locale,
|
|
1981
|
-
value:
|
|
1982
|
-
|
|
2349
|
+
void save(
|
|
2350
|
+
session,
|
|
2351
|
+
{ fieldKey: session.tagged.fieldKey, locale: session.tagged.locale, value },
|
|
2352
|
+
kind === "choice" ? field.options?.find((option) => option.id === value)?.label : kind === "switch" ? value ? m.yes : m.no : void 0
|
|
2353
|
+
);
|
|
2354
|
+
}
|
|
2355
|
+
function invalid(session) {
|
|
2356
|
+
finishEdit(session, false);
|
|
2357
|
+
setToast({ title: m.saveFailed, body: m.invalidNumber, error: true });
|
|
1983
2358
|
}
|
|
1984
|
-
async function save(session, proposal) {
|
|
2359
|
+
async function save(session, proposal, display) {
|
|
1985
2360
|
setStatus("saving");
|
|
1986
2361
|
const body = buildDraftBody({
|
|
1987
2362
|
recordRef: session.recordRef,
|
|
1988
2363
|
pageUrl: session.pageUrl,
|
|
1989
2364
|
anchors: session.anchors,
|
|
1990
|
-
proposal
|
|
2365
|
+
proposal,
|
|
2366
|
+
display
|
|
1991
2367
|
});
|
|
1992
2368
|
const draftKey = newIdempotencyKey(win);
|
|
1993
2369
|
const result = await auth.call((token) => saveToDraft(ctx, token, body, draftKey));
|
|
1994
2370
|
if (destroyed) return;
|
|
1995
2371
|
if (!result.ok) {
|
|
1996
|
-
session.revert();
|
|
1997
2372
|
setStatus(null);
|
|
2373
|
+
if (result.kind === "unchanged") return;
|
|
2374
|
+
session.revert();
|
|
1998
2375
|
onFailure(result, "edit");
|
|
1999
2376
|
return;
|
|
2000
2377
|
}
|
|
2001
2378
|
setStatus("saved");
|
|
2379
|
+
if (data && session.raw !== void 0)
|
|
2380
|
+
(data.values ?? (data.values = {}))[session.recordRef] = {
|
|
2381
|
+
...data.values[session.recordRef],
|
|
2382
|
+
[session.tagged.fieldKey]: proposal.value
|
|
2383
|
+
};
|
|
2384
|
+
showSaved(session, display);
|
|
2385
|
+
}
|
|
2386
|
+
function showSaved(session, display) {
|
|
2387
|
+
const { field, tagged, element } = session;
|
|
2388
|
+
if (session.kind === "text" || field.type === "URL") return;
|
|
2389
|
+
if (field.type === "ENUM" && display !== void 0 && tagged.source === "attr" && element.children.length === 0) {
|
|
2390
|
+
element.textContent = display;
|
|
2391
|
+
return;
|
|
2392
|
+
}
|
|
2393
|
+
rememberEditIntent(win);
|
|
2394
|
+
win.setTimeout(() => win.location.reload(), RELOAD_AFTER_SAVE_MS);
|
|
2002
2395
|
}
|
|
2003
2396
|
function endEdit() {
|
|
2004
2397
|
if (editing && !editing.done) commitEdit(editing);
|
|
@@ -2019,6 +2412,11 @@ function start(ctx, options = {}) {
|
|
|
2019
2412
|
closeComposer();
|
|
2020
2413
|
return;
|
|
2021
2414
|
}
|
|
2415
|
+
if (info && event.key === "Escape") {
|
|
2416
|
+
event.preventDefault();
|
|
2417
|
+
closeInfo();
|
|
2418
|
+
return;
|
|
2419
|
+
}
|
|
2022
2420
|
const session = editing;
|
|
2023
2421
|
if (!session || session.done) return;
|
|
2024
2422
|
if (event.key === "Escape") {
|
|
@@ -2124,11 +2522,9 @@ var CSS = `
|
|
|
2124
2522
|
border-radius: 7px; background: #101828; color: #FFFFFF; font-weight: 500; font-size: 12px; line-height: 18px;
|
|
2125
2523
|
white-space: nowrap; pointer-events: none; }
|
|
2126
2524
|
.tip:not(.on) { display: none; }
|
|
2127
|
-
.kbd { font-size: 11px; line-height: 16px; color: #CBD5E1; padding: 1px 5px; border-radius: 4px;
|
|
2128
|
-
background: rgba(255,255,255,.12); }
|
|
2129
2525
|
[data-enter] { animation: eelzap-in .16s cubic-bezier(.4,0,.2,1); }
|
|
2130
2526
|
@keyframes eelzap-in { from { opacity: 0; transform: scale(.96); } }
|
|
2131
|
-
@media (prefers-reduced-motion: reduce) { .seg, .tool, .grip, .pill { transition: none; } [data-enter] { animation: none; } }
|
|
2527
|
+
@media (prefers-reduced-motion: reduce) { .seg, .tool, .grip, .pill, .switch, .knob { transition: none; } [data-enter] { animation: none; } }
|
|
2132
2528
|
.tool.strong { color: ${TOKENS.fg}; }
|
|
2133
2529
|
.divider { width: 1px; height: 22px; background: ${TOKENS.border}; }
|
|
2134
2530
|
.avatar { width: 26px; height: 26px; border-radius: 7px; background: #DBEAFE; color: #1D4ED8;
|
|
@@ -2152,7 +2548,35 @@ var CSS = `
|
|
|
2152
2548
|
.refused { align-self: center; margin-right: 0; width: min(420px, calc(100vw - 16px)); align-items: flex-start; }
|
|
2153
2549
|
.toast > .icon { color: #16A34A; }
|
|
2154
2550
|
.refused > .icon, .toast-error > .icon { color: ${TOKENS.danger}; }
|
|
2155
|
-
.paused > .icon { color: ${TOKENS.muted}; }
|
|
2551
|
+
.paused > .icon, .info > .icon { color: ${TOKENS.muted}; }
|
|
2552
|
+
.editor { min-width: max-content; }
|
|
2553
|
+
.editor, .info { position: fixed; padding: 8px; border-radius: 7px;
|
|
2554
|
+
box-shadow: 0 4px 6px -1px rgba(15,23,42,.08), 0 2px 4px -2px rgba(15,23,42,.06); }
|
|
2555
|
+
.info { display: flex; gap: 10px; padding: 12px 14px; }
|
|
2556
|
+
.info > .icon { margin-top: 1px; }
|
|
2557
|
+
.info .title { font-weight: 500; }
|
|
2558
|
+
.info .muted { font-size: 12px; }
|
|
2559
|
+
.actions.start { justify-content: flex-start; margin-top: 10px; gap: 6px; }
|
|
2560
|
+
.actions.start .btn { height: 28px; gap: 7px; font-size: 12px; }
|
|
2561
|
+
.btn-ghost { border: 1px solid transparent; }
|
|
2562
|
+
.btn-ghost:hover { background: ${TOKENS.subtle}; }
|
|
2563
|
+
.keys { display: flex; align-items: center; gap: 5px; margin: 8px 2px 0; font-size: 12px; color: ${TOKENS.muted}; white-space: nowrap; }
|
|
2564
|
+
.editor input.value { height: 34px; border-radius: 8px; }
|
|
2565
|
+
.with-unit { display: flex; align-items: center; gap: 8px; }
|
|
2566
|
+
.options { display: flex; flex-direction: column; max-height: 260px; overflow: auto; }
|
|
2567
|
+
.option { width: 100%; height: 32px; gap: 8px; padding: 0 8px; border-radius: 6px; font-weight: 400; text-align: left; }
|
|
2568
|
+
.option:hover, .option:focus-visible { background: #F1F5F9; box-shadow: none; }
|
|
2569
|
+
.option-label { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; }
|
|
2570
|
+
.switch-row { display: flex; align-items: center; justify-content: space-between; gap: 12px; padding: 2px 2px 0; }
|
|
2571
|
+
.switch-row .title { font-weight: 500; }
|
|
2572
|
+
.switch { position: relative; width: 32px; height: 18px; border-radius: 999px; background: #CBD5E1; transition: background .15s; }
|
|
2573
|
+
.switch[aria-checked="true"] { background: ${TOKENS.primary}; }
|
|
2574
|
+
.knob { position: absolute; top: 2px; left: 2px; width: 14px; height: 14px; border-radius: 999px; background: ${TOKENS.bg};
|
|
2575
|
+
box-shadow: 0 1px 2px rgba(0,0,0,.15); transition: left .15s; }
|
|
2576
|
+
.switch[aria-checked="true"] .knob { left: 16px; }
|
|
2577
|
+
.mark-chip { position: absolute; right: -5px; top: -27px; display: inline-flex; align-items: center; height: 20px;
|
|
2578
|
+
padding: 0 7px; border-radius: 6px; background: ${TOKENS.gold}; color: #FFFFFF; font: 600 12px Poppins, ui-sans-serif, system-ui, sans-serif;
|
|
2579
|
+
white-space: nowrap; }
|
|
2156
2580
|
.toast-text { display: flex; flex-direction: column; gap: 3px; flex: 1; min-width: 0; }
|
|
2157
2581
|
.title { font-weight: 600; }
|
|
2158
2582
|
.head { display: flex; align-items: center; justify-content: space-between; gap: 8px; }
|
|
@@ -2188,4 +2612,4 @@ export {
|
|
|
2188
2612
|
start,
|
|
2189
2613
|
zapUrl
|
|
2190
2614
|
};
|
|
2191
|
-
//# sourceMappingURL=suggest-
|
|
2615
|
+
//# sourceMappingURL=suggest-ERMR7PCE.js.map
|