isotone 1.1.2 → 1.2.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/AGENTS.md +84 -6
- package/CHANGELOG.md +46 -0
- package/README.md +64 -14
- package/dist/{IterationPanel-phJRYtUO.d.ts → IterationPanel-Bq77d5NT.d.ts} +3 -1
- package/dist/{chunk-FKEKBZ3E.js → chunk-2FY2JNPG.js} +95 -9
- package/dist/chunk-2FY2JNPG.js.map +1 -0
- package/dist/chunk-BDTB3DFR.js +100 -0
- package/dist/chunk-BDTB3DFR.js.map +1 -0
- package/dist/{chunk-PUXQGUMQ.js → chunk-BTJHQXF7.js} +104 -5
- package/dist/chunk-BTJHQXF7.js.map +1 -0
- package/dist/chunk-JCTWHR7U.js +13 -0
- package/dist/chunk-JCTWHR7U.js.map +1 -0
- package/dist/{chunk-I7CZUPDO.js → chunk-KRS57POF.js} +17 -33
- package/dist/chunk-KRS57POF.js.map +1 -0
- package/dist/chunk-L6O4HYGP.js +1150 -0
- package/dist/chunk-L6O4HYGP.js.map +1 -0
- package/dist/chunk-MX4RQJFV.js +9 -0
- package/dist/chunk-MX4RQJFV.js.map +1 -0
- package/dist/chunk-YPDV3PX4.js +142 -0
- package/dist/chunk-YPDV3PX4.js.map +1 -0
- package/dist/cli.js +1079 -148
- package/dist/cli.js.map +1 -1
- package/dist/core.d.ts +135 -10
- package/dist/core.js +12 -3
- package/dist/index.d.ts +13 -28
- package/dist/index.js +618 -281
- package/dist/index.js.map +1 -1
- package/dist/isotone.global.js +98 -42
- package/dist/serve.d.ts +5 -3
- package/dist/serve.js +60 -23
- package/dist/serve.js.map +1 -1
- package/dist/server.d.ts +7 -0
- package/dist/server.js +24 -11
- package/dist/server.js.map +1 -1
- package/dist/vite-plugin.d.ts +4 -3
- package/dist/vite-plugin.js +48 -128
- package/dist/vite-plugin.js.map +1 -1
- package/docs/blast.md +164 -0
- package/docs/integrations.md +75 -15
- package/docs/migration.md +19 -0
- package/docs/updates.md +110 -4
- package/docs/variants.md +102 -9
- package/package.json +2 -1
- package/dist/chunk-A4DX2V4C.js +0 -7
- package/dist/chunk-A4DX2V4C.js.map +0 -1
- package/dist/chunk-FKEKBZ3E.js.map +0 -1
- package/dist/chunk-I7CZUPDO.js.map +0 -1
- package/dist/chunk-PUXQGUMQ.js.map +0 -1
- package/dist/chunk-RQ2YI5KS.js +0 -24
- package/dist/chunk-RQ2YI5KS.js.map +0 -1
- package/dist/chunk-TGAU3WTF.js +0 -549
- package/dist/chunk-TGAU3WTF.js.map +0 -1
- package/dist/chunk-X6V2AKJL.js +0 -375
- package/dist/chunk-X6V2AKJL.js.map +0 -1
package/dist/index.js
CHANGED
|
@@ -4,15 +4,22 @@ import {
|
|
|
4
4
|
describeIterationElement,
|
|
5
5
|
elementAsNode,
|
|
6
6
|
isRouteVisible
|
|
7
|
-
} from "./chunk-
|
|
7
|
+
} from "./chunk-2FY2JNPG.js";
|
|
8
|
+
import {
|
|
9
|
+
updateNoteText,
|
|
10
|
+
updatePrompt
|
|
11
|
+
} from "./chunk-JCTWHR7U.js";
|
|
8
12
|
import {
|
|
9
13
|
FEEDBACK_STORAGE_KEY,
|
|
10
14
|
KNOBS_STORAGE_KEY,
|
|
15
|
+
MAX_FEEDBACK_LENGTH,
|
|
11
16
|
OVERLAY_DEFAULT_OPTION,
|
|
12
17
|
OVERLAY_GROUP_ID,
|
|
18
|
+
SECTION_SEPARATOR,
|
|
13
19
|
VARIANTS_ENDPOINT,
|
|
14
20
|
applyKnobValues,
|
|
15
21
|
axisDefault,
|
|
22
|
+
axisSection,
|
|
16
23
|
createOverlayVariantsSource,
|
|
17
24
|
createVariantsSource,
|
|
18
25
|
defaultSelections,
|
|
@@ -29,13 +36,12 @@ import {
|
|
|
29
36
|
parseVariantParam,
|
|
30
37
|
readBrowserStorage,
|
|
31
38
|
removeBrowserStorage,
|
|
39
|
+
sectionAxes,
|
|
32
40
|
serializeVariantParam,
|
|
41
|
+
splitSectionLabel,
|
|
33
42
|
visibleAxes,
|
|
34
43
|
writeBrowserStorage
|
|
35
|
-
} from "./chunk-
|
|
36
|
-
import {
|
|
37
|
-
MAX_FEEDBACK_LENGTH
|
|
38
|
-
} from "./chunk-A4DX2V4C.js";
|
|
44
|
+
} from "./chunk-BTJHQXF7.js";
|
|
39
45
|
|
|
40
46
|
// src/react/tooltips.ts
|
|
41
47
|
function showOverflowTitle(event) {
|
|
@@ -45,7 +51,7 @@ function showOverflowTitle(event) {
|
|
|
45
51
|
}
|
|
46
52
|
|
|
47
53
|
// src/react/IterationPanel.tsx
|
|
48
|
-
import { useCallback, useEffect as
|
|
54
|
+
import { useCallback, useEffect as useEffect4, useId as useId3, useLayoutEffect as useLayoutEffect4, useMemo as useMemo3, useRef as useRef4, useState as useState3 } from "react";
|
|
49
55
|
|
|
50
56
|
// src/react/appearance.ts
|
|
51
57
|
var darkPalette = {
|
|
@@ -584,11 +590,16 @@ function Icon2({ name, size = 16 }) {
|
|
|
584
590
|
|
|
585
591
|
// src/react/UpdateNotice.tsx
|
|
586
592
|
import { Fragment, jsx as jsx2, jsxs } from "react/jsx-runtime";
|
|
587
|
-
function UpdateNotice({ endpoint, active }) {
|
|
593
|
+
function UpdateNotice({ endpoint, active, route = "/", requested = false, onRequested }) {
|
|
588
594
|
const [info, setInfo] = useState(null);
|
|
589
595
|
const [open, setOpen] = useState(false);
|
|
590
|
-
const [
|
|
596
|
+
const [showCommand, setShowCommand] = useState(false);
|
|
597
|
+
const [status, setStatus] = useState("");
|
|
598
|
+
const [busy, setBusy] = useState(false);
|
|
599
|
+
const [asked, setAsked] = useState(false);
|
|
591
600
|
const trigger = useRef(null);
|
|
601
|
+
const primary = useRef(null);
|
|
602
|
+
const done = useRef(null);
|
|
592
603
|
const command = useRef(null);
|
|
593
604
|
const closeButton = useRef(null);
|
|
594
605
|
const id = useId();
|
|
@@ -618,28 +629,57 @@ function UpdateNotice({ endpoint, active }) {
|
|
|
618
629
|
};
|
|
619
630
|
}, [endpoint, active]);
|
|
620
631
|
useEffect(() => {
|
|
621
|
-
if (
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
632
|
+
if (requested) setAsked(false);
|
|
633
|
+
}, [requested]);
|
|
634
|
+
useEffect(() => {
|
|
635
|
+
if (!open) return;
|
|
636
|
+
setStatus("");
|
|
637
|
+
setShowCommand(false);
|
|
638
|
+
(primary.current ?? closeButton.current)?.focus();
|
|
625
639
|
}, [open]);
|
|
640
|
+
useEffect(() => {
|
|
641
|
+
if (showCommand) command.current?.focus();
|
|
642
|
+
}, [showCommand]);
|
|
626
643
|
function close() {
|
|
627
644
|
setOpen(false);
|
|
628
645
|
trigger.current?.focus();
|
|
629
646
|
}
|
|
630
|
-
async function
|
|
631
|
-
if (!
|
|
647
|
+
async function ask() {
|
|
648
|
+
if (!endpoint || busy) return;
|
|
649
|
+
setBusy(true);
|
|
650
|
+
setStatus("");
|
|
651
|
+
try {
|
|
652
|
+
const response = await fetch(endpoint, {
|
|
653
|
+
method: "POST",
|
|
654
|
+
headers: { "Content-Type": "application/json" },
|
|
655
|
+
body: JSON.stringify({ route })
|
|
656
|
+
});
|
|
657
|
+
if (!response.ok) throw new Error(String(response.status));
|
|
658
|
+
setAsked(true);
|
|
659
|
+
onRequested?.();
|
|
660
|
+
requestAnimationFrame(() => done.current?.focus());
|
|
661
|
+
} catch {
|
|
662
|
+
setStatus("Could not ask. Copy the prompt instead.");
|
|
663
|
+
} finally {
|
|
664
|
+
setBusy(false);
|
|
665
|
+
}
|
|
666
|
+
}
|
|
667
|
+
async function copyPrompt() {
|
|
668
|
+
if (!info?.upgrade || !info.latestVersion) return;
|
|
669
|
+
const text = updatePrompt({ from: info.currentVersion, to: info.latestVersion, ...info.upgrade });
|
|
632
670
|
try {
|
|
633
|
-
await navigator.clipboard.writeText(
|
|
634
|
-
|
|
671
|
+
await navigator.clipboard.writeText(text);
|
|
672
|
+
setStatus("Prompt copied");
|
|
635
673
|
} catch {
|
|
636
|
-
|
|
637
|
-
command.
|
|
674
|
+
setShowCommand(true);
|
|
675
|
+
setStatus("Could not copy. The command is below.");
|
|
638
676
|
}
|
|
639
677
|
}
|
|
640
678
|
if (!active || !info) return null;
|
|
679
|
+
const pending = requested || asked;
|
|
680
|
+
const canAsk = Boolean(onRequested && info.upgrade);
|
|
641
681
|
return /* @__PURE__ */ jsxs("div", { className: "isotone-update", children: [
|
|
642
|
-
/* @__PURE__ */
|
|
682
|
+
/* @__PURE__ */ jsx2(
|
|
643
683
|
"button",
|
|
644
684
|
{
|
|
645
685
|
ref: trigger,
|
|
@@ -647,11 +687,8 @@ function UpdateNotice({ endpoint, active }) {
|
|
|
647
687
|
className: "isotone-update-trigger",
|
|
648
688
|
"aria-expanded": open,
|
|
649
689
|
"aria-controls": id,
|
|
650
|
-
onClick: () => setOpen(
|
|
651
|
-
children:
|
|
652
|
-
"Update ",
|
|
653
|
-
info.latestVersion
|
|
654
|
-
]
|
|
690
|
+
onClick: () => setOpen(!open),
|
|
691
|
+
children: pending ? "Update requested" : `Isotone ${info.latestVersion} is available`
|
|
655
692
|
}
|
|
656
693
|
),
|
|
657
694
|
open && /* @__PURE__ */ jsxs(
|
|
@@ -670,25 +707,27 @@ function UpdateNotice({ endpoint, active }) {
|
|
|
670
707
|
},
|
|
671
708
|
children: [
|
|
672
709
|
/* @__PURE__ */ jsxs("div", { className: "isotone-menu-title", children: [
|
|
673
|
-
/* @__PURE__ */
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
"
|
|
679
|
-
info.latestVersion
|
|
710
|
+
/* @__PURE__ */ jsxs("strong", { className: "isotone-update-versions", children: [
|
|
711
|
+
info.currentVersion,
|
|
712
|
+
" \u2192 ",
|
|
713
|
+
info.latestVersion
|
|
714
|
+
] }),
|
|
715
|
+
/* @__PURE__ */ jsx2("button", { ref: closeButton, type: "button", className: "isotone-icon", "aria-label": "Close update", title: "Close update", onClick: close, children: /* @__PURE__ */ jsx2(Icon2, { name: "close" }) })
|
|
680
716
|
] }),
|
|
681
717
|
info.upgrade ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
682
|
-
/* @__PURE__ */
|
|
683
|
-
"Run in ",
|
|
684
|
-
/* @__PURE__ */ jsx2("span", { className: "isotone-update-path", onPointerOver: showOverflowTitle, children: info.upgrade.cwd })
|
|
685
|
-
] }),
|
|
686
|
-
/* @__PURE__ */ jsx2("input", { ref: command, "aria-label": "Upgrade command", readOnly: true, value: info.upgrade.command, onFocus: (event) => event.currentTarget.select() }),
|
|
718
|
+
canAsk && (pending ? /* @__PURE__ */ jsx2("p", { ref: done, tabIndex: -1, className: "isotone-update-requested", children: "Update requested \xB7 in Feedback" }) : /* @__PURE__ */ jsx2("button", { ref: primary, type: "button", className: "isotone-save isotone-update-ask", disabled: busy, onClick: () => void ask(), children: "Ask agent to update" })),
|
|
687
719
|
/* @__PURE__ */ jsxs("div", { className: "isotone-update-actions", children: [
|
|
688
|
-
/* @__PURE__ */ jsx2("button", { type: "button", className: "isotone-
|
|
689
|
-
/* @__PURE__ */ jsx2("
|
|
720
|
+
/* @__PURE__ */ jsx2("button", { ref: canAsk ? void 0 : primary, type: "button", className: "isotone-update-quiet", onClick: () => void copyPrompt(), children: "Copy prompt" }),
|
|
721
|
+
/* @__PURE__ */ jsx2("button", { type: "button", className: "isotone-update-quiet", "aria-expanded": showCommand, onClick: () => setShowCommand((value) => !value), children: "Command" }),
|
|
722
|
+
/* @__PURE__ */ jsx2("span", { role: "status", children: status })
|
|
690
723
|
] }),
|
|
691
|
-
/* @__PURE__ */
|
|
724
|
+
showCommand && /* @__PURE__ */ jsxs("div", { className: "isotone-update-command", children: [
|
|
725
|
+
/* @__PURE__ */ jsx2("input", { ref: command, "aria-label": "Upgrade command", readOnly: true, value: info.upgrade.command, onFocus: (event) => event.currentTarget.select() }),
|
|
726
|
+
/* @__PURE__ */ jsxs("span", { className: "isotone-update-path", onPointerOver: showOverflowTitle, children: [
|
|
727
|
+
"in ",
|
|
728
|
+
info.upgrade.cwd
|
|
729
|
+
] })
|
|
730
|
+
] })
|
|
692
731
|
] }) : /* @__PURE__ */ jsx2("p", { children: info.message ?? "Run isotone check-update in the app directory for upgrade instructions." })
|
|
693
732
|
]
|
|
694
733
|
}
|
|
@@ -696,18 +735,26 @@ function UpdateNotice({ endpoint, active }) {
|
|
|
696
735
|
] });
|
|
697
736
|
}
|
|
698
737
|
var UPDATE_NOTICE_CSS = `
|
|
699
|
-
.isotone-
|
|
700
|
-
.isotone-root[data-tools='1'] .isotone-
|
|
738
|
+
.isotone-notices { position: absolute; z-index: 5; bottom: 10px; right: calc(var(--iso-rail-length) + 10px); display: flex; flex-direction: row-reverse; flex-wrap: wrap-reverse; align-items: center; justify-content: flex-start; gap: 6px; width: max-content; max-width: calc(100vw - var(--iso-rail-length) - 36px); pointer-events: none; }
|
|
739
|
+
.isotone-root[data-tools='1'] .isotone-notices { right: calc(var(--iso-rail-size) + 10px); max-width: calc(100vw - var(--iso-rail-size) - 36px); }
|
|
740
|
+
.isotone-notices > * { pointer-events: auto; }
|
|
701
741
|
.isotone-update-trigger { white-space: nowrap; color: var(--iso-muted); font-size: 10px; padding: 4px 6px; border-radius: 5px; background: var(--iso-bg); box-shadow: 0 0 0 1px var(--iso-line); }
|
|
702
|
-
.isotone-update-dialog { position: absolute; right: calc(0px - var(--iso-rail-length) - 10px); bottom: 40px; width: var(--iso-dock-width); max-height: calc(100dvh - 100px); overflow: auto; padding: var(--iso-space); display: grid; gap: 8px; border-radius: 8px; background: var(--iso-bg); box-shadow: var(--iso-popup-shadow); font-size: 11px; }
|
|
742
|
+
.isotone-update-dialog { position: absolute; right: calc(0px - var(--iso-rail-length) - 10px); bottom: 40px; width: min(var(--iso-dock-width), 260px); max-width: calc(100vw - 32px); max-height: calc(100dvh - 100px); overflow: auto; padding: 10px var(--iso-space) var(--iso-space); display: grid; gap: 8px; border-radius: 8px; background: var(--iso-bg); box-shadow: var(--iso-popup-shadow); font-size: 11px; }
|
|
703
743
|
.isotone-root[data-tools='1'] .isotone-update-dialog { right: calc(0px - var(--iso-rail-size) - 10px); }
|
|
704
744
|
.isotone-update-dialog .isotone-menu-title { padding: 0; background: none; }
|
|
745
|
+
.isotone-update-versions { font-variant-numeric: tabular-nums; }
|
|
705
746
|
.isotone-update-dialog > p { color: var(--iso-muted); }
|
|
706
|
-
.isotone-update-
|
|
707
|
-
.isotone-
|
|
708
|
-
.isotone-update-actions { display: flex; align-items: center; gap:
|
|
709
|
-
.isotone-update-
|
|
710
|
-
|
|
747
|
+
.isotone-root .isotone-update-ask { min-height: 28px; padding: 4px 10px; font-weight: 500; }
|
|
748
|
+
.isotone-update-requested { min-height: 28px; display: flex; align-items: center; outline-offset: 2px; }
|
|
749
|
+
.isotone-update-actions { display: flex; align-items: center; gap: 2px; margin: -4px 0 -4px -6px; }
|
|
750
|
+
.isotone-root .isotone-update-quiet { padding: 4px 6px; border-radius: 5px; color: var(--iso-muted); font-size: 11px; background: none; box-shadow: none; }
|
|
751
|
+
.isotone-update-quiet[aria-expanded='true'] { color: var(--iso-fg); }
|
|
752
|
+
.isotone-update-actions [role='status'] { min-width: 0; margin-left: auto; color: var(--iso-muted); font-size: 10px; text-align: right; }
|
|
753
|
+
.isotone-update-command { display: grid; gap: 4px; min-width: 0; }
|
|
754
|
+
.isotone-update-path { display: block; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; color: var(--iso-muted); font-size: 10px; }
|
|
755
|
+
.isotone-root .isotone-update-dialog input { width: 100%; padding: 6px 8px; border-radius: 5px; background: var(--iso-well); box-shadow: var(--iso-well-shadow); font-size: 11px; }
|
|
756
|
+
@media (pointer: coarse) { .isotone-root .isotone-update-quiet { padding: 9px 6px; } .isotone-update-actions { margin: -9px 0 -9px -6px; } }
|
|
757
|
+
@media (hover: hover) and (pointer: fine) { .isotone-update-trigger:hover, .isotone-root .isotone-update-quiet:hover { color: var(--iso-fg); } }
|
|
711
758
|
`;
|
|
712
759
|
|
|
713
760
|
// src/react/styles.ts
|
|
@@ -742,8 +789,11 @@ ${APPEARANCE_CSS}
|
|
|
742
789
|
--iso-ease-out: ${MOTION.easeOut};
|
|
743
790
|
--iso-fast: ${MOTION.exit}ms; --iso-enter: ${MOTION.enter}ms; --iso-exit: ${MOTION.exit}ms;
|
|
744
791
|
--iso-dock-width: min(340px, calc(100vw - 32px));
|
|
745
|
-
|
|
746
|
-
|
|
792
|
+
/* The height the dock may fill: what is visible (above an on-screen keyboard,
|
|
793
|
+
see viewport.ts) less the safe areas it keeps clear of. */
|
|
794
|
+
--iso-view-height: calc(var(--iso-viewport, 100dvh) - env(safe-area-inset-top, 0px) - max(0px, env(safe-area-inset-bottom, 0px) - 16px));
|
|
795
|
+
position: fixed; right: max(16px, env(safe-area-inset-right)); bottom: max(calc(16px + var(--iso-keyboard, 0px)), env(safe-area-inset-bottom));
|
|
796
|
+
z-index: 2147483000; width: 44px; display: flex; flex-direction: column; isolation: isolate; max-height: calc(var(--iso-view-height) - 32px); color: var(--iso-fg);
|
|
747
797
|
font: 12px/1.5 var(--iso-sans); -webkit-font-smoothing: antialiased;
|
|
748
798
|
text-align: left; letter-spacing: normal; color-scheme: dark;
|
|
749
799
|
}
|
|
@@ -779,8 +829,8 @@ ${APPEARANCE_CSS}
|
|
|
779
829
|
/* Panels are sections inside the dock. Only long content scrolls. */
|
|
780
830
|
.isotone-panel { order: 0; position: relative; display: none; flex-direction: column; min-height: 0; flex: 0 1 auto; width: 100%; }
|
|
781
831
|
.isotone-panel[data-open='true'] { display: flex; }
|
|
782
|
-
.isotone-variants-panel { flex-shrink: 1; max-height: calc(
|
|
783
|
-
.isotone-notes-panel { flex-shrink: 0; max-height: calc(
|
|
832
|
+
.isotone-variants-panel { flex-shrink: 1; max-height: calc(var(--iso-view-height) - 76px); }
|
|
833
|
+
.isotone-notes-panel { flex-shrink: 0; max-height: calc(var(--iso-view-height) - 76px); padding: var(--iso-space); gap: var(--iso-space); }
|
|
784
834
|
.isotone-root .isotone-panel { background: var(--iso-bg); border: 1px solid var(--iso-line); border-radius: 12px; box-shadow: var(--iso-panel-shadow); }
|
|
785
835
|
/* The close control sits on the panel's top-left corner, half outside it, with
|
|
786
836
|
the same face as a note's delete button, a touch larger. It fades in while the
|
|
@@ -804,13 +854,30 @@ ${APPEARANCE_CSS}
|
|
|
804
854
|
.isotone-icon { width: 28px; height: 28px; display: inline-grid; place-items: center; flex: none; color: var(--iso-muted); border-radius: 6px; }
|
|
805
855
|
.isotone-count { margin-left: 5px; color: var(--iso-muted); font-size: 10px; font-variant-numeric: tabular-nums; }
|
|
806
856
|
.isotone-vars { flex: 1; display: flex; flex-direction: column; min-height: 0; position: relative; }
|
|
807
|
-
.isotone-vars { min-height: min(var(--iso-menu-need, 0px), calc(
|
|
857
|
+
.isotone-vars { min-height: min(var(--iso-menu-need, 0px), calc(var(--iso-view-height) - 76px)); }
|
|
808
858
|
/* The panel no longer clips, so its corner close can overhang; the variants body clips itself. */
|
|
809
859
|
.isotone-root .isotone-panel > .isotone-vars { overflow: hidden; border-radius: 11px; }
|
|
810
860
|
.isotone-vars-body { flex: 1; overflow-y: auto; min-height: 0; overscroll-behavior: contain; padding: var(--iso-space); display: flex; flex-direction: column; gap: var(--iso-space); }
|
|
811
861
|
/* The footer overlaps only the scroller's bottom padding: content fades beneath
|
|
812
862
|
it while scrolling, but the last control remains fully visible at the end. */
|
|
813
863
|
.isotone-axis { flex: none; }
|
|
864
|
+
/* Sections name the part of the page their selectors change. The header sticks to
|
|
865
|
+
the top of the scroll area while its own selectors pass beneath and fade into the
|
|
866
|
+
surface; the next section's header pushes it out. At rest its padding occupies
|
|
867
|
+
the inset or gap above it, so sticking does not move the text. */
|
|
868
|
+
.isotone-section, .isotone-section-body { flex: none; display: flex; flex-direction: column; gap: var(--iso-space); }
|
|
869
|
+
.isotone-section-body[hidden] { display: none; }
|
|
870
|
+
.isotone-vars-body > * + .isotone-section { padding-top: 8px; }
|
|
871
|
+
/* Sticky offsets start inside the scroller's padding; the negative top reaches its edge. */
|
|
872
|
+
.isotone-section-head { position: sticky; top: calc(0px - var(--iso-space)); z-index: 1; margin: calc(0px - var(--iso-space)) calc(0px - var(--iso-space)) -4px; padding: var(--iso-space) var(--iso-space) 0; background: var(--iso-bg); font: inherit; }
|
|
873
|
+
.isotone-section-head::after { content: ''; position: absolute; left: 0; right: 0; top: 100%; height: 8px; background: linear-gradient(var(--iso-bg), transparent); pointer-events: none; }
|
|
874
|
+
.isotone-section-toggle { display: inline-flex; align-items: center; gap: 5px; min-width: 0; max-width: calc(100% + 7px); min-height: 24px; padding: 2px 7px; margin-left: -7px; border-radius: 6px; text-align: left; color: var(--iso-muted); font-size: 11px; font-weight: 600; }
|
|
875
|
+
.isotone-section-toggle > span { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
876
|
+
.isotone-section-toggle > svg, .isotone-section-toggle > small { flex: none; }
|
|
877
|
+
.isotone-section-toggle > small { color: var(--iso-faint); font-size: 10px; font-weight: 500; font-variant-numeric: tabular-nums; }
|
|
878
|
+
.isotone-section[data-collapsed='true'] .isotone-section-toggle > svg { transform: rotate(-90deg); }
|
|
879
|
+
/* Keyboard focus reveal keeps a focused control clear of the sticky header. */
|
|
880
|
+
.isotone-vars-body:has(> .isotone-section) { scroll-padding-top: 44px; }
|
|
814
881
|
.isotone-axis-head { display: flex; align-items: center; justify-content: space-between; min-height: 24px; gap: 8px; margin-bottom: 6px; }
|
|
815
882
|
.isotone-axis-label { display: inline-flex; align-items: center; gap: 5px; min-width: 0; max-width: 100%; min-height: 24px; padding: 2px 7px; margin-left: -7px; border-radius: 6px; text-align: left; }
|
|
816
883
|
.isotone-axis-label, .isotone-menu-title > strong { font: inherit; font-weight: 550; }
|
|
@@ -866,8 +933,13 @@ ${PILL_ACTIVE_CSS}
|
|
|
866
933
|
.isotone-menu-action { display: flex; align-items: center; gap: 9px; width: 100%; min-height: 34px; border-radius: 6px; text-align: left; padding: 7px; font-size: 11px; }
|
|
867
934
|
.isotone-menu-options { padding: 7px 0; margin: 5px 0; border-top: 1px solid var(--iso-line); border-bottom: 1px solid var(--iso-line); }
|
|
868
935
|
.isotone-option-row { display: flex; align-items: center; padding: 2px 4px 2px 7px; gap: 4px; }
|
|
869
|
-
.isotone-option-
|
|
870
|
-
.isotone-option-
|
|
936
|
+
.isotone-option-name { flex: 1; min-width: 0; display: flex; align-items: baseline; gap: 6px; }
|
|
937
|
+
.isotone-option-label { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
938
|
+
/* Tags never shrink, so a long label truncates before them instead of hiding them. */
|
|
939
|
+
.isotone-option-tags { flex: none; display: flex; gap: 6px; white-space: nowrap; }
|
|
940
|
+
.isotone-option-tags small { font-size: 9px; color: var(--iso-muted); }
|
|
941
|
+
/* A pending removal stays listed and readable; only its text quietens. */
|
|
942
|
+
.isotone-option-row[data-requested] .isotone-option-label { color: var(--iso-muted); }
|
|
871
943
|
.isotone-menu-footer { flex-shrink: 0; padding: 9px 12px; border-top: 1px solid var(--iso-line); font-size: 10px; color: var(--iso-muted); min-height: 0; }
|
|
872
944
|
.isotone-confirm { display: flex; justify-content: flex-end; gap: 6px; margin-top: 7px; }
|
|
873
945
|
.isotone-danger { color: var(--iso-danger); }
|
|
@@ -883,6 +955,11 @@ ${PILL_ACTIVE_CSS}
|
|
|
883
955
|
.isotone-note-hit { text-align: left; display: block; width: 100%; border-radius: 8px; padding: var(--iso-note-padding-y) var(--iso-note-padding-x); line-height: 18px; }
|
|
884
956
|
.isotone-note-hit p { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; font-size: 12px; }
|
|
885
957
|
.isotone-meta { display: flex; align-items: center; gap: 4px; min-width: 0; margin-top: 4px; }
|
|
958
|
+
/* The agent's question takes a second line under the note, muted, clipped like it. */
|
|
959
|
+
.isotone-note-hit:has(> .isotone-reply) { flex-wrap: wrap; row-gap: 1px; }
|
|
960
|
+
.isotone-note-hit > p.isotone-reply { order: 3; flex: 1 0 100%; color: var(--iso-muted); font-size: 11px; }
|
|
961
|
+
.isotone-reply > span, .isotone-editing-reply > span { color: var(--iso-fg); font-weight: 500; }
|
|
962
|
+
.isotone-editing-reply { flex: none; max-height: 58px; overflow-y: auto; padding: 8px var(--iso-input-padding) 0; color: var(--iso-muted); font-size: 11px; line-height: 16px; overflow-wrap: anywhere; }
|
|
886
963
|
.isotone-meta .isotone-chip { flex: 0 1 auto; min-width: 0; height: 18px; padding: 0 5px; font-size: 9px; }
|
|
887
964
|
/* A lone reference gets all available space; multiple references share the row. */
|
|
888
965
|
.isotone-meta .isotone-chip:not(:only-child) { max-width: 140px; }
|
|
@@ -931,14 +1008,15 @@ ${FEEDBACK_LAYOUT_CSS}
|
|
|
931
1008
|
.isotone-picking { position: absolute; inset: -4px; z-index: 1; display: flex; align-items: center; gap: 10px; padding: 0 4px 0 8px; border-radius: 6px; background: var(--iso-well); cursor: default; }
|
|
932
1009
|
.isotone-picking > svg { flex: none; color: var(--iso-fg); }
|
|
933
1010
|
.isotone-picking p { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 2px; margin: 0; }
|
|
934
|
-
.
|
|
935
|
-
.isotone-picking
|
|
1011
|
+
/* Hints are never truncated (design.md, "Hints and instructions"): one line, copy short enough to fit. */
|
|
1012
|
+
.isotone-picking span { font-size: 12px; line-height: 16px; color: var(--iso-fg); font-variant-numeric: tabular-nums; white-space: nowrap; }
|
|
1013
|
+
.isotone-picking small { font-size: 10px; line-height: 14px; color: var(--iso-muted); white-space: nowrap; }
|
|
936
1014
|
.isotone-picking button { flex: none; display: flex; align-items: center; gap: 8px; height: 28px; padding: 0 9px; }
|
|
937
1015
|
.isotone-picking kbd { font: inherit; color: var(--iso-muted); }
|
|
938
1016
|
/* After a pin, the composer floats beside the element on a panel surface, placed by
|
|
939
1017
|
IterationPanel inside the viewport. It grows out of the point that was clicked. */
|
|
940
1018
|
.isotone-float { position: fixed; z-index: 2147483002; width: min(300px, calc(100vw - 16px)); padding: 4px; background: var(--iso-bg); border: 1px solid var(--iso-line); border-radius: 12px; box-shadow: var(--iso-panel-shadow); }
|
|
941
|
-
.isotone-float-hint { flex: 1
|
|
1019
|
+
.isotone-float-hint { flex: 1 0 auto; white-space: nowrap; font-size: 10px; color: var(--iso-muted); }
|
|
942
1020
|
/* Meanwhile the dock keeps the composer's slot and says where the writing went. */
|
|
943
1021
|
.isotone-away { display: flex; align-items: center; gap: 10px; width: 100%; min-height: 50px; padding: 8px 8px 8px 10px; text-align: left; color: var(--iso-fg); background: var(--iso-well); border: 1px solid var(--iso-input-border); box-shadow: var(--iso-input-shadow); border-radius: 8px; }
|
|
944
1022
|
.isotone-away > svg { flex: none; }
|
|
@@ -951,8 +1029,8 @@ ${FEEDBACK_LAYOUT_CSS}
|
|
|
951
1029
|
.isotone-hover { position: fixed; pointer-events: none; border: 1px solid var(--iso-pick-line); background: var(--iso-pick-fill); border-radius: 5px; z-index: 2147483001; }
|
|
952
1030
|
.isotone-selected { border: 2px solid var(--iso-pick-line); background: var(--iso-pick-selected-fill); }
|
|
953
1031
|
@media (hover: hover) and (pointer: fine) {
|
|
954
|
-
.isotone-root :where(.isotone-icon:not(.isotone-x), .isotone-menu-action, .isotone-axis-label):not(:disabled):not([aria-pressed='true']):hover { background: var(--iso-hover); box-shadow: var(--iso-hover-shadow); color: var(--iso-fg); }
|
|
955
|
-
.isotone-note:not(.isotone-note-editing):hover .isotone-note-hit:not(:disabled) { background: var(--iso-hover); box-shadow: var(--iso-hover-shadow); }
|
|
1032
|
+
.isotone-root :where(.isotone-icon:not(.isotone-x), .isotone-menu-action, .isotone-axis-label, .isotone-section-toggle):not(:disabled):not([aria-pressed='true']):hover { background: var(--iso-hover); box-shadow: var(--iso-hover-shadow); color: var(--iso-fg); }
|
|
1033
|
+
.isotone-note:not(.isotone-note-editing):hover .isotone-note-hit:not(:disabled):not([data-static]) { background: var(--iso-hover); box-shadow: var(--iso-hover-shadow); }
|
|
956
1034
|
.isotone-rail-button:not(:disabled):hover { color: var(--iso-fg); }
|
|
957
1035
|
.isotone-seg:not(:disabled):hover { color: var(--iso-fg); }
|
|
958
1036
|
.isotone-seg[aria-pressed='false']:not(:disabled):hover { box-shadow: var(--iso-hover-shadow); }
|
|
@@ -964,6 +1042,23 @@ ${FEEDBACK_LAYOUT_CSS}
|
|
|
964
1042
|
.isotone-note .isotone-x { opacity: 0; }
|
|
965
1043
|
.isotone-note:hover .isotone-x, .isotone-note:focus-within .isotone-x, .isotone-note-editing .isotone-x { opacity: 1; }
|
|
966
1044
|
}
|
|
1045
|
+
/* Phones. Panels take turns (see viewport.ts). Variants stop at about half the
|
|
1046
|
+
screen, so the page they change stays in view; action dialogs keep their
|
|
1047
|
+
360px. While selecting, the dock shrinks to the composer so the page can be
|
|
1048
|
+
reached, and the saved notes return afterwards. */
|
|
1049
|
+
@media (max-width: 640px) {
|
|
1050
|
+
.isotone-variants-panel { max-height: min(calc(var(--iso-view-height) - 76px), max(50dvh, 360px)); }
|
|
1051
|
+
}
|
|
1052
|
+
@media (max-width: 640px), (max-height: 500px) {
|
|
1053
|
+
.isotone-root[data-selecting='true'] .isotone-notes-panel > :is(.isotone-list, .isotone-pages) { display: none; }
|
|
1054
|
+
}
|
|
1055
|
+
/* A finger needs more than the drawn faces. Hit areas grow without moving
|
|
1056
|
+
anything; the faces keep Instrument's geometry. */
|
|
1057
|
+
@media (pointer: coarse) {
|
|
1058
|
+
.isotone-pick::after, .isotone-send::after { inset: -8px -10px; }
|
|
1059
|
+
.isotone-chip button { position: relative; }
|
|
1060
|
+
.isotone-chip button::after, .isotone-note > .isotone-x::after, .isotone-panel-close::after { content: ''; position: absolute; inset: -6px; border-radius: 50%; }
|
|
1061
|
+
}
|
|
967
1062
|
@media (max-height: 580px) { .isotone-list { --iso-list-height: 72px; } .isotone-root .isotone-textarea { max-height: calc(64px + var(--iso-textarea-inset)); } }
|
|
968
1063
|
@media (prefers-reduced-motion: reduce) {
|
|
969
1064
|
.isotone-ring { box-shadow: 0 0 0 2px color-mix(in srgb, var(--iso-focus) 50%, transparent); }
|
|
@@ -988,7 +1083,7 @@ function ensurePanelStyles(target) {
|
|
|
988
1083
|
}
|
|
989
1084
|
|
|
990
1085
|
// src/react/VariantsView.tsx
|
|
991
|
-
import { useEffect as useEffect2, useLayoutEffect as useLayoutEffect2, useMemo as useMemo2, useRef as useRef3, useState as useState2 } from "react";
|
|
1086
|
+
import { useEffect as useEffect2, useId as useId2, useLayoutEffect as useLayoutEffect2, useMemo as useMemo2, useRef as useRef3, useState as useState2 } from "react";
|
|
992
1087
|
|
|
993
1088
|
// src/react/useLayoutMotion.ts
|
|
994
1089
|
import { useLayoutEffect, useRef as useRef2 } from "react";
|
|
@@ -1046,13 +1141,13 @@ function useLayoutMotion(ref, revision, active = true) {
|
|
|
1046
1141
|
previous.current = next;
|
|
1047
1142
|
}, [ref, revision, active]);
|
|
1048
1143
|
useLayoutEffect(() => {
|
|
1049
|
-
const
|
|
1144
|
+
const media2 = typeof matchMedia === "function" ? matchMedia("(prefers-reduced-motion: reduce)") : null;
|
|
1050
1145
|
const changed = () => {
|
|
1051
|
-
if (
|
|
1146
|
+
if (media2?.matches) clearPositions(previous.current);
|
|
1052
1147
|
};
|
|
1053
|
-
|
|
1148
|
+
media2?.addEventListener("change", changed);
|
|
1054
1149
|
return () => {
|
|
1055
|
-
|
|
1150
|
+
media2?.removeEventListener("change", changed);
|
|
1056
1151
|
clearPositions(previous.current);
|
|
1057
1152
|
};
|
|
1058
1153
|
}, []);
|
|
@@ -1072,6 +1167,7 @@ function useVariantsSource(source) {
|
|
|
1072
1167
|
return revision;
|
|
1073
1168
|
}
|
|
1074
1169
|
var NO_PENDING_ACTIONS = [];
|
|
1170
|
+
var DEFAULT_EMPTY_TEXT = "No variants on this page yet. Ask your agent to create variants of any design and they will appear here.";
|
|
1075
1171
|
function VariantsView({
|
|
1076
1172
|
source,
|
|
1077
1173
|
showTitle = true,
|
|
@@ -1080,26 +1176,35 @@ function VariantsView({
|
|
|
1080
1176
|
onClose,
|
|
1081
1177
|
onSaved,
|
|
1082
1178
|
onManage,
|
|
1083
|
-
pendingActions = NO_PENDING_ACTIONS
|
|
1179
|
+
pendingActions = NO_PENDING_ACTIONS,
|
|
1180
|
+
emptyText = DEFAULT_EMPTY_TEXT
|
|
1084
1181
|
}) {
|
|
1085
1182
|
const revision = useVariantsSource(source);
|
|
1086
1183
|
useEffect2(() => {
|
|
1087
1184
|
if (active) refreshSource(source);
|
|
1088
1185
|
}, [active, source]);
|
|
1089
|
-
const { selections, shown, sourceError } = useMemo2(() => {
|
|
1186
|
+
const { selections, shown, sections, names, sourceError } = useMemo2(() => {
|
|
1090
1187
|
const selections2 = source.getSelections();
|
|
1188
|
+
const shown2 = visibleAxes(source.getAxes(), selections2);
|
|
1189
|
+
const sections2 = sectionAxes(shown2);
|
|
1091
1190
|
return {
|
|
1092
1191
|
selections: selections2,
|
|
1093
|
-
shown:
|
|
1192
|
+
shown: shown2,
|
|
1193
|
+
sections: sections2,
|
|
1194
|
+
names: new Map(sections2.flatMap((section) => section.axes).map((entry) => [entry.axis.id, entry])),
|
|
1094
1195
|
sourceError: source.getError?.()
|
|
1095
1196
|
};
|
|
1096
1197
|
}, [source, revision]);
|
|
1198
|
+
const [collapsed, setCollapsed] = useState2(() => /* @__PURE__ */ new Set());
|
|
1097
1199
|
const [menuId, setMenuId] = useState2(null);
|
|
1098
1200
|
const menu = shown.find((axis) => axis.id === menuId) ?? null;
|
|
1201
|
+
const nameOf = (axis) => names.get(axis.id) ?? axisSection(axis);
|
|
1202
|
+
const menuName = menu ? nameOf(menu) : null;
|
|
1099
1203
|
const [menuOpen, setMenuOpen] = useState2(false);
|
|
1100
1204
|
const [busy, setBusy] = useState2(false);
|
|
1101
1205
|
const [error, setError] = useState2("");
|
|
1102
1206
|
const menuRef = useRef3(null);
|
|
1207
|
+
const pendingFocus = useRef3(null);
|
|
1103
1208
|
const bodyRef = useRef3(null);
|
|
1104
1209
|
useLayoutMotion(bodyRef, shown, active);
|
|
1105
1210
|
const trigger = useRef3(null);
|
|
@@ -1111,15 +1216,28 @@ function VariantsView({
|
|
|
1111
1216
|
...o,
|
|
1112
1217
|
isDefault: o.id === menu.defaultOption
|
|
1113
1218
|
})) : [];
|
|
1114
|
-
const closeMenu = () => {
|
|
1219
|
+
const closeMenu = (restoreFocus = true) => {
|
|
1115
1220
|
setMenuOpen(false);
|
|
1116
1221
|
setError("");
|
|
1222
|
+
pendingFocus.current = null;
|
|
1223
|
+
if (!restoreFocus) return;
|
|
1117
1224
|
requestAnimationFrame(() => {
|
|
1118
1225
|
if (trigger.current?.isConnected) trigger.current.focus();
|
|
1119
1226
|
else
|
|
1120
1227
|
bodyRef.current?.parentElement?.querySelector("button")?.focus();
|
|
1121
1228
|
});
|
|
1122
1229
|
};
|
|
1230
|
+
useEffect2(() => {
|
|
1231
|
+
const target = pendingFocus.current;
|
|
1232
|
+
const dialog = menuRef.current;
|
|
1233
|
+
if (busy || !target || !dialog || !menuOpen) return;
|
|
1234
|
+
pendingFocus.current = null;
|
|
1235
|
+
const rows = Array.from(dialog.querySelectorAll(".isotone-option-row"));
|
|
1236
|
+
const at = rows.findIndex((row) => row.dataset.optionId === target.optionId);
|
|
1237
|
+
const removable = (row) => row?.querySelector("button[data-remove]:not(:disabled)") ?? null;
|
|
1238
|
+
const next = target.retry ? removable(rows[at]) : [...rows.slice(at + 1), ...rows.slice(0, Math.max(at, 0)).reverse()].map(removable).find(Boolean);
|
|
1239
|
+
(next ?? dialog.querySelector("[data-reset]"))?.focus();
|
|
1240
|
+
}, [busy, pendingActions, menuOpen]);
|
|
1123
1241
|
useEffect2(() => {
|
|
1124
1242
|
if (!menu) setMenuOpen(false);
|
|
1125
1243
|
}, [menu]);
|
|
@@ -1170,10 +1288,10 @@ function VariantsView({
|
|
|
1170
1288
|
const option = axis.kind === "range" ? void 0 : (axis.kind === "color" ? axis.swatches : axis.options).find(
|
|
1171
1289
|
(o) => o.id === optionId
|
|
1172
1290
|
);
|
|
1173
|
-
closeMenu();
|
|
1291
|
+
closeMenu(false);
|
|
1174
1292
|
onFeedback?.({
|
|
1175
1293
|
axisId: axis.id,
|
|
1176
|
-
label: axis.
|
|
1294
|
+
label: nameOf(axis).fullLabel,
|
|
1177
1295
|
value: value(axis),
|
|
1178
1296
|
...option ? { optionId: option.id, optionLabel: option.label } : {}
|
|
1179
1297
|
});
|
|
@@ -1181,18 +1299,21 @@ function VariantsView({
|
|
|
1181
1299
|
const requested = (action) => pendingActions.some((pending) => pending.axisId === action.axisId && pending.action === action.action && pending.optionId === action.optionId);
|
|
1182
1300
|
async function manage(action) {
|
|
1183
1301
|
if (!menu || busy || !onManage || requested(action)) return;
|
|
1302
|
+
const option = action.action === "remove-option" ? action.optionId : void 0;
|
|
1184
1303
|
setBusy(true);
|
|
1185
1304
|
setError("");
|
|
1186
1305
|
try {
|
|
1187
|
-
const
|
|
1306
|
+
const chosen = menuOptions.find((o) => o.id === action.optionId);
|
|
1188
1307
|
await onManage(action, {
|
|
1189
1308
|
axisId: menu.id,
|
|
1190
|
-
label: menu.label,
|
|
1309
|
+
label: menuName?.fullLabel ?? menu.label,
|
|
1191
1310
|
value: value(menu),
|
|
1192
|
-
...
|
|
1311
|
+
...chosen ? { optionId: chosen.id, optionLabel: chosen.label } : {}
|
|
1193
1312
|
});
|
|
1194
|
-
|
|
1313
|
+
if (option !== void 0) pendingFocus.current = { optionId: option, retry: false };
|
|
1314
|
+
else closeMenu();
|
|
1195
1315
|
} catch (e) {
|
|
1316
|
+
if (option !== void 0) pendingFocus.current = { optionId: option, retry: true };
|
|
1196
1317
|
setError(
|
|
1197
1318
|
e instanceof Error ? e.message : "Could not update this selector."
|
|
1198
1319
|
);
|
|
@@ -1200,6 +1321,100 @@ function VariantsView({
|
|
|
1200
1321
|
setBusy(false);
|
|
1201
1322
|
}
|
|
1202
1323
|
}
|
|
1324
|
+
function renderAxis({ axis, label, fullLabel }) {
|
|
1325
|
+
const thumbnails = axis.kind !== "range" && axis.kind !== "color" && axis.options.length > 0 && axis.options.every((option) => option.image);
|
|
1326
|
+
return /* @__PURE__ */ jsxs2(
|
|
1327
|
+
"div",
|
|
1328
|
+
{
|
|
1329
|
+
className: "isotone-axis",
|
|
1330
|
+
"data-motion-key": axis.id,
|
|
1331
|
+
children: [
|
|
1332
|
+
/* @__PURE__ */ jsxs2("div", { className: "isotone-axis-head", children: [
|
|
1333
|
+
/* @__PURE__ */ jsxs2(
|
|
1334
|
+
"button",
|
|
1335
|
+
{
|
|
1336
|
+
type: "button",
|
|
1337
|
+
className: "isotone-axis-label",
|
|
1338
|
+
"aria-label": `${fullLabel} actions`,
|
|
1339
|
+
"aria-haspopup": "dialog",
|
|
1340
|
+
"aria-expanded": menuOpen && menu?.id === axis.id,
|
|
1341
|
+
onClick: (e) => {
|
|
1342
|
+
trigger.current = e.currentTarget;
|
|
1343
|
+
setMenuId(axis.id);
|
|
1344
|
+
setError("");
|
|
1345
|
+
setMenuOpen(true);
|
|
1346
|
+
},
|
|
1347
|
+
children: [
|
|
1348
|
+
/* @__PURE__ */ jsx3("span", { onPointerOver: showOverflowTitle, children: label }),
|
|
1349
|
+
/* @__PURE__ */ jsx3(Icon2, { name: "chevron", size: 12 })
|
|
1350
|
+
]
|
|
1351
|
+
}
|
|
1352
|
+
),
|
|
1353
|
+
(axis.kind === "range" || axis.kind === "color") && /* @__PURE__ */ jsx3("span", { className: "isotone-axis-value", children: axis.kind === "range" ? formatRangeValue(axis, value(axis)) : value(axis).toUpperCase() })
|
|
1354
|
+
] }),
|
|
1355
|
+
axis.kind === "range" ? /* @__PURE__ */ jsx3(
|
|
1356
|
+
"input",
|
|
1357
|
+
{
|
|
1358
|
+
type: "range",
|
|
1359
|
+
className: "isotone-range",
|
|
1360
|
+
"aria-label": fullLabel,
|
|
1361
|
+
"aria-valuetext": formatRangeValue(axis, value(axis)),
|
|
1362
|
+
min: axis.min,
|
|
1363
|
+
max: axis.max,
|
|
1364
|
+
step: axis.step,
|
|
1365
|
+
value: Number(value(axis)),
|
|
1366
|
+
onChange: (e) => source.setSelection(axis.id, e.target.value)
|
|
1367
|
+
}
|
|
1368
|
+
) : axis.kind === "color" ? /* @__PURE__ */ jsxs2("fieldset", { className: "isotone-swatches", "aria-label": fullLabel, children: [
|
|
1369
|
+
axis.swatches.map((sw) => /* @__PURE__ */ jsx3(
|
|
1370
|
+
"button",
|
|
1371
|
+
{
|
|
1372
|
+
type: "button",
|
|
1373
|
+
title: sw.label,
|
|
1374
|
+
"aria-label": sw.label,
|
|
1375
|
+
"aria-pressed": value(axis).toLowerCase() === sw.value.toLowerCase(),
|
|
1376
|
+
className: "isotone-swatch",
|
|
1377
|
+
style: { backgroundColor: sw.value },
|
|
1378
|
+
onClick: () => source.setSelection(axis.id, sw.value)
|
|
1379
|
+
},
|
|
1380
|
+
sw.id
|
|
1381
|
+
)),
|
|
1382
|
+
/* @__PURE__ */ jsx3("label", { className: "isotone-picker", title: "Custom colour", children: /* @__PURE__ */ jsx3(
|
|
1383
|
+
"input",
|
|
1384
|
+
{
|
|
1385
|
+
type: "color",
|
|
1386
|
+
"aria-label": `${fullLabel}: custom colour`,
|
|
1387
|
+
value: value(axis),
|
|
1388
|
+
onChange: (e) => source.setSelection(axis.id, e.target.value)
|
|
1389
|
+
}
|
|
1390
|
+
) })
|
|
1391
|
+
] }) : /* @__PURE__ */ jsx3(
|
|
1392
|
+
"fieldset",
|
|
1393
|
+
{
|
|
1394
|
+
"aria-label": fullLabel,
|
|
1395
|
+
className: thumbnails ? "isotone-grid" : "isotone-segs",
|
|
1396
|
+
children: axis.options.map((o) => /* @__PURE__ */ jsxs2(
|
|
1397
|
+
"button",
|
|
1398
|
+
{
|
|
1399
|
+
type: "button",
|
|
1400
|
+
"aria-label": o.label,
|
|
1401
|
+
"aria-pressed": value(axis) === o.id,
|
|
1402
|
+
className: `${thumbnails ? "isotone-thumb" : "isotone-seg"}${value(axis) === o.id ? " isotone-seg-on" : ""}`,
|
|
1403
|
+
onClick: () => source.setSelection(axis.id, o.id),
|
|
1404
|
+
children: [
|
|
1405
|
+
thumbnails && /* @__PURE__ */ jsx3("img", { src: o.image, alt: "" }),
|
|
1406
|
+
/* @__PURE__ */ jsx3("span", { onPointerOver: showOverflowTitle, children: o.label })
|
|
1407
|
+
]
|
|
1408
|
+
},
|
|
1409
|
+
o.id
|
|
1410
|
+
))
|
|
1411
|
+
}
|
|
1412
|
+
)
|
|
1413
|
+
]
|
|
1414
|
+
},
|
|
1415
|
+
axis.id
|
|
1416
|
+
);
|
|
1417
|
+
}
|
|
1203
1418
|
return /* @__PURE__ */ jsxs2("div", { className: "isotone-vars", children: [
|
|
1204
1419
|
/* @__PURE__ */ jsxs2("div", { className: "isotone-panel-head", children: [
|
|
1205
1420
|
showTitle && /* @__PURE__ */ jsx3("h2", { children: source.title ?? "Variants" }),
|
|
@@ -1223,101 +1438,23 @@ function VariantsView({
|
|
|
1223
1438
|
node?.toggleAttribute("inert", menuOpen);
|
|
1224
1439
|
}, children: [
|
|
1225
1440
|
sourceError && /* @__PURE__ */ jsx3("p", { className: "isotone-danger", role: "alert", children: sourceError }),
|
|
1226
|
-
shown.length === 0 && /* @__PURE__ */ jsx3("p", { className: "isotone-empty", children:
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
"aria-expanded": menuOpen && menu?.id === axis.id,
|
|
1244
|
-
onClick: (e) => {
|
|
1245
|
-
trigger.current = e.currentTarget;
|
|
1246
|
-
setMenuId(axis.id);
|
|
1247
|
-
setError("");
|
|
1248
|
-
setMenuOpen(true);
|
|
1249
|
-
},
|
|
1250
|
-
children: [
|
|
1251
|
-
/* @__PURE__ */ jsx3("span", { onPointerOver: showOverflowTitle, children: axis.label }),
|
|
1252
|
-
/* @__PURE__ */ jsx3(Icon2, { name: "chevron", size: 12 })
|
|
1253
|
-
]
|
|
1254
|
-
}
|
|
1255
|
-
),
|
|
1256
|
-
(axis.kind === "range" || axis.kind === "color") && /* @__PURE__ */ jsx3("span", { className: "isotone-axis-value", children: axis.kind === "range" ? formatRangeValue(axis, value(axis)) : value(axis).toUpperCase() })
|
|
1257
|
-
] }),
|
|
1258
|
-
axis.kind === "range" ? /* @__PURE__ */ jsx3(
|
|
1259
|
-
"input",
|
|
1260
|
-
{
|
|
1261
|
-
type: "range",
|
|
1262
|
-
className: "isotone-range",
|
|
1263
|
-
"aria-label": axis.label,
|
|
1264
|
-
"aria-valuetext": formatRangeValue(axis, value(axis)),
|
|
1265
|
-
min: axis.min,
|
|
1266
|
-
max: axis.max,
|
|
1267
|
-
step: axis.step,
|
|
1268
|
-
value: Number(value(axis)),
|
|
1269
|
-
onChange: (e) => source.setSelection(axis.id, e.target.value)
|
|
1270
|
-
}
|
|
1271
|
-
) : axis.kind === "color" ? /* @__PURE__ */ jsxs2("fieldset", { className: "isotone-swatches", "aria-label": axis.label, children: [
|
|
1272
|
-
axis.swatches.map((sw) => /* @__PURE__ */ jsx3(
|
|
1273
|
-
"button",
|
|
1274
|
-
{
|
|
1275
|
-
type: "button",
|
|
1276
|
-
title: sw.label,
|
|
1277
|
-
"aria-label": sw.label,
|
|
1278
|
-
"aria-pressed": value(axis).toLowerCase() === sw.value.toLowerCase(),
|
|
1279
|
-
className: "isotone-swatch",
|
|
1280
|
-
style: { backgroundColor: sw.value },
|
|
1281
|
-
onClick: () => source.setSelection(axis.id, sw.value)
|
|
1282
|
-
},
|
|
1283
|
-
sw.id
|
|
1284
|
-
)),
|
|
1285
|
-
/* @__PURE__ */ jsx3("label", { className: "isotone-picker", title: "Custom colour", children: /* @__PURE__ */ jsx3(
|
|
1286
|
-
"input",
|
|
1287
|
-
{
|
|
1288
|
-
type: "color",
|
|
1289
|
-
"aria-label": `${axis.label}: custom colour`,
|
|
1290
|
-
value: value(axis),
|
|
1291
|
-
onChange: (e) => source.setSelection(axis.id, e.target.value)
|
|
1292
|
-
}
|
|
1293
|
-
) })
|
|
1294
|
-
] }) : /* @__PURE__ */ jsx3(
|
|
1295
|
-
"fieldset",
|
|
1296
|
-
{
|
|
1297
|
-
"aria-label": axis.label,
|
|
1298
|
-
className: thumbnails ? "isotone-grid" : "isotone-segs",
|
|
1299
|
-
children: axis.options.map((o) => /* @__PURE__ */ jsxs2(
|
|
1300
|
-
"button",
|
|
1301
|
-
{
|
|
1302
|
-
type: "button",
|
|
1303
|
-
"aria-label": o.label,
|
|
1304
|
-
"aria-pressed": value(axis) === o.id,
|
|
1305
|
-
className: `${thumbnails ? "isotone-thumb" : "isotone-seg"}${value(axis) === o.id ? " isotone-seg-on" : ""}`,
|
|
1306
|
-
onClick: () => source.setSelection(axis.id, o.id),
|
|
1307
|
-
children: [
|
|
1308
|
-
thumbnails && /* @__PURE__ */ jsx3("img", { src: o.image, alt: "" }),
|
|
1309
|
-
/* @__PURE__ */ jsx3("span", { onPointerOver: showOverflowTitle, children: o.label })
|
|
1310
|
-
]
|
|
1311
|
-
},
|
|
1312
|
-
o.id
|
|
1313
|
-
))
|
|
1314
|
-
}
|
|
1315
|
-
)
|
|
1316
|
-
]
|
|
1317
|
-
},
|
|
1318
|
-
axis.id
|
|
1319
|
-
);
|
|
1320
|
-
})
|
|
1441
|
+
shown.length === 0 && /* @__PURE__ */ jsx3("p", { className: "isotone-empty", children: emptyText }),
|
|
1442
|
+
sections.map((section) => section.name === null ? section.axes.map(renderAxis) : /* @__PURE__ */ jsx3(
|
|
1443
|
+
SectionBlock,
|
|
1444
|
+
{
|
|
1445
|
+
name: section.name,
|
|
1446
|
+
sectionKey: section.key,
|
|
1447
|
+
count: section.axes.length,
|
|
1448
|
+
collapsed: collapsed.has(section.key),
|
|
1449
|
+
onToggle: () => setCollapsed((previous) => {
|
|
1450
|
+
const next = new Set(previous);
|
|
1451
|
+
if (!next.delete(section.key)) next.add(section.key);
|
|
1452
|
+
return next;
|
|
1453
|
+
}),
|
|
1454
|
+
children: section.axes.map(renderAxis)
|
|
1455
|
+
},
|
|
1456
|
+
`section:${section.key}`
|
|
1457
|
+
))
|
|
1321
1458
|
] }),
|
|
1322
1459
|
menu && /* @__PURE__ */ jsxs2(
|
|
1323
1460
|
"div",
|
|
@@ -1328,7 +1465,7 @@ function VariantsView({
|
|
|
1328
1465
|
node?.toggleAttribute("inert", !menuOpen);
|
|
1329
1466
|
},
|
|
1330
1467
|
role: "dialog",
|
|
1331
|
-
"aria-label": `${menu.label} actions`,
|
|
1468
|
+
"aria-label": `${menuName?.fullLabel ?? menu.label} actions`,
|
|
1332
1469
|
"aria-hidden": !menuOpen,
|
|
1333
1470
|
"data-open": menuOpen,
|
|
1334
1471
|
onKeyDown: (e) => {
|
|
@@ -1340,7 +1477,7 @@ function VariantsView({
|
|
|
1340
1477
|
},
|
|
1341
1478
|
children: [
|
|
1342
1479
|
/* @__PURE__ */ jsxs2("div", { className: "isotone-menu-title", children: [
|
|
1343
|
-
/* @__PURE__ */ jsx3("strong", { children: menu.label }),
|
|
1480
|
+
/* @__PURE__ */ jsx3("strong", { children: menuName?.label ?? menu.label }),
|
|
1344
1481
|
/* @__PURE__ */ jsx3(
|
|
1345
1482
|
"button",
|
|
1346
1483
|
{
|
|
@@ -1348,7 +1485,7 @@ function VariantsView({
|
|
|
1348
1485
|
className: "isotone-icon",
|
|
1349
1486
|
"aria-label": "Close selector actions",
|
|
1350
1487
|
title: "Close selector actions",
|
|
1351
|
-
onClick: closeMenu,
|
|
1488
|
+
onClick: () => closeMenu(),
|
|
1352
1489
|
children: /* @__PURE__ */ jsx3(Icon2, { name: "close" })
|
|
1353
1490
|
}
|
|
1354
1491
|
)
|
|
@@ -1366,44 +1503,52 @@ function VariantsView({
|
|
|
1366
1503
|
]
|
|
1367
1504
|
}
|
|
1368
1505
|
),
|
|
1369
|
-
menu.kind !== "range" && /* @__PURE__ */ jsx3("div", { className: "isotone-menu-options", children: menuOptions.map((option) =>
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
1506
|
+
menu.kind !== "range" && /* @__PURE__ */ jsx3("div", { className: "isotone-menu-options", children: menuOptions.map((option) => {
|
|
1507
|
+
const pending = requested({ axisId: menu.id, action: "remove-option", optionId: option.id });
|
|
1508
|
+
return /* @__PURE__ */ jsxs2("div", { className: "isotone-option-row", "data-option-id": option.id, "data-requested": pending || void 0, children: [
|
|
1509
|
+
/* @__PURE__ */ jsxs2("span", { className: "isotone-option-name", children: [
|
|
1510
|
+
/* @__PURE__ */ jsx3("span", { className: "isotone-option-label", children: option.label }),
|
|
1511
|
+
(option.isDefault || pending) && /* @__PURE__ */ jsxs2("span", { className: "isotone-option-tags", children: [
|
|
1512
|
+
option.isDefault && /* @__PURE__ */ jsx3("small", { children: "Default" }),
|
|
1513
|
+
pending && /* @__PURE__ */ jsx3("small", { children: "Removal requested" })
|
|
1514
|
+
] })
|
|
1515
|
+
] }),
|
|
1516
|
+
onFeedback && /* @__PURE__ */ jsx3(
|
|
1517
|
+
"button",
|
|
1518
|
+
{
|
|
1519
|
+
type: "button",
|
|
1520
|
+
className: "isotone-icon",
|
|
1521
|
+
"aria-label": `Feedback on ${option.label}`,
|
|
1522
|
+
title: "Give feedback",
|
|
1523
|
+
onClick: () => feedback(menu, option.id),
|
|
1524
|
+
children: /* @__PURE__ */ jsx3(Icon2, { name: "message" })
|
|
1525
|
+
}
|
|
1526
|
+
),
|
|
1527
|
+
onManage && /* @__PURE__ */ jsx3(
|
|
1528
|
+
"button",
|
|
1529
|
+
{
|
|
1530
|
+
type: "button",
|
|
1531
|
+
className: "isotone-icon",
|
|
1532
|
+
"data-remove": "",
|
|
1533
|
+
"aria-label": `Remove ${option.label}`,
|
|
1534
|
+
disabled: option.isDefault || busy || pending,
|
|
1535
|
+
title: option.isDefault ? "Default option" : pending ? "Removal requested" : "Request option removal",
|
|
1536
|
+
onClick: () => void manage({
|
|
1537
|
+
axisId: menu.id,
|
|
1538
|
+
action: "remove-option",
|
|
1539
|
+
optionId: option.id
|
|
1540
|
+
}),
|
|
1541
|
+
children: /* @__PURE__ */ jsx3(Icon2, { name: "trash" })
|
|
1542
|
+
}
|
|
1543
|
+
)
|
|
1544
|
+
] }, option.id);
|
|
1545
|
+
}) }),
|
|
1402
1546
|
/* @__PURE__ */ jsxs2(
|
|
1403
1547
|
"button",
|
|
1404
1548
|
{
|
|
1405
1549
|
type: "button",
|
|
1406
1550
|
className: "isotone-menu-action",
|
|
1551
|
+
"data-reset": "",
|
|
1407
1552
|
onClick: () => {
|
|
1408
1553
|
source.setSelection(menu.id, axisDefault(menu));
|
|
1409
1554
|
closeMenu();
|
|
@@ -1449,6 +1594,39 @@ function VariantsView({
|
|
|
1449
1594
|
)
|
|
1450
1595
|
] });
|
|
1451
1596
|
}
|
|
1597
|
+
function SectionBlock({
|
|
1598
|
+
name,
|
|
1599
|
+
sectionKey,
|
|
1600
|
+
count,
|
|
1601
|
+
collapsed,
|
|
1602
|
+
onToggle,
|
|
1603
|
+
children
|
|
1604
|
+
}) {
|
|
1605
|
+
const id = useId2();
|
|
1606
|
+
return (
|
|
1607
|
+
// The heading carries the structure (heading navigation, a disclosure button);
|
|
1608
|
+
// a region or group per section would only add landmarks to a long list.
|
|
1609
|
+
/* @__PURE__ */ jsxs2("div", { className: "isotone-section", "data-collapsed": collapsed, children: [
|
|
1610
|
+
/* @__PURE__ */ jsx3("h3", { className: "isotone-section-head", "data-motion-key": `section:${sectionKey}`, children: /* @__PURE__ */ jsxs2(
|
|
1611
|
+
"button",
|
|
1612
|
+
{
|
|
1613
|
+
type: "button",
|
|
1614
|
+
className: "isotone-section-toggle",
|
|
1615
|
+
"aria-label": `${name}, ${count} ${count === 1 ? "selector" : "selectors"}`,
|
|
1616
|
+
"aria-expanded": !collapsed,
|
|
1617
|
+
"aria-controls": `${id}-body`,
|
|
1618
|
+
onClick: onToggle,
|
|
1619
|
+
children: [
|
|
1620
|
+
/* @__PURE__ */ jsx3(Icon2, { name: "chevron", size: 12 }),
|
|
1621
|
+
/* @__PURE__ */ jsx3("span", { onPointerOver: showOverflowTitle, children: name }),
|
|
1622
|
+
/* @__PURE__ */ jsx3("small", { children: count })
|
|
1623
|
+
]
|
|
1624
|
+
}
|
|
1625
|
+
) }),
|
|
1626
|
+
/* @__PURE__ */ jsx3("div", { className: "isotone-section-body", id: `${id}-body`, hidden: collapsed, children: collapsed ? null : children })
|
|
1627
|
+
] })
|
|
1628
|
+
);
|
|
1629
|
+
}
|
|
1452
1630
|
function SaveDefaults({
|
|
1453
1631
|
source,
|
|
1454
1632
|
onSaved
|
|
@@ -1626,8 +1804,8 @@ function observeTheme(onTheme, preference = "auto") {
|
|
|
1626
1804
|
sample();
|
|
1627
1805
|
if (preference === "light" || preference === "dark") return () => {
|
|
1628
1806
|
};
|
|
1629
|
-
const
|
|
1630
|
-
|
|
1807
|
+
const media2 = window.matchMedia?.("(prefers-color-scheme: dark)");
|
|
1808
|
+
media2?.addEventListener("change", schedule);
|
|
1631
1809
|
const pageEvent = (event) => {
|
|
1632
1810
|
if (!isPanel(event.target instanceof Node ? event.target : null)) schedule();
|
|
1633
1811
|
};
|
|
@@ -1642,7 +1820,7 @@ function observeTheme(onTheme, preference = "auto") {
|
|
|
1642
1820
|
disposed = true;
|
|
1643
1821
|
observer?.disconnect();
|
|
1644
1822
|
if (frame !== void 0) window.cancelAnimationFrame(frame);
|
|
1645
|
-
|
|
1823
|
+
media2?.removeEventListener("change", schedule);
|
|
1646
1824
|
window.removeEventListener("resize", schedule);
|
|
1647
1825
|
document.removeEventListener("scroll", pageEvent, true);
|
|
1648
1826
|
document.removeEventListener("load", pageEvent, true);
|
|
@@ -1656,7 +1834,20 @@ import { Fragment as Fragment3, jsx as jsx4, jsxs as jsxs3 } from "react/jsx-run
|
|
|
1656
1834
|
function SummaryTag({ label }) {
|
|
1657
1835
|
return /* @__PURE__ */ jsx4("span", { className: "isotone-chip", children: /* @__PURE__ */ jsx4("span", { onPointerOver: showOverflowTitle, children: label }) });
|
|
1658
1836
|
}
|
|
1837
|
+
function Reply({ note }) {
|
|
1838
|
+
if (!note.reply?.text || note.reply.answered) return null;
|
|
1839
|
+
return /* @__PURE__ */ jsxs3("p", { className: "isotone-reply", onPointerOver: showOverflowTitle, children: [
|
|
1840
|
+
/* @__PURE__ */ jsx4("span", { children: "Agent asks" }),
|
|
1841
|
+
" ",
|
|
1842
|
+
note.reply.text
|
|
1843
|
+
] });
|
|
1844
|
+
}
|
|
1659
1845
|
function NoteSummary({ note, page }) {
|
|
1846
|
+
if (note.update) return /* @__PURE__ */ jsxs3(Fragment3, { children: [
|
|
1847
|
+
/* @__PURE__ */ jsx4("p", { children: updateNoteText(note.update) }),
|
|
1848
|
+
/* @__PURE__ */ jsx4(Reply, { note }),
|
|
1849
|
+
/* @__PURE__ */ jsx4("span", { className: "isotone-meta", children: /* @__PURE__ */ jsx4(SummaryTag, { label: "Update" }) })
|
|
1850
|
+
] });
|
|
1660
1851
|
if (!note.feedback.startsWith("[set-defaults]")) {
|
|
1661
1852
|
const elements = note.elements ?? (note.element ? [note.element] : []);
|
|
1662
1853
|
const references = [
|
|
@@ -1669,6 +1860,7 @@ function NoteSummary({ note, page }) {
|
|
|
1669
1860
|
];
|
|
1670
1861
|
return /* @__PURE__ */ jsxs3(Fragment3, { children: [
|
|
1671
1862
|
/* @__PURE__ */ jsx4("p", { children: note.feedback }),
|
|
1863
|
+
/* @__PURE__ */ jsx4(Reply, { note }),
|
|
1672
1864
|
references.length > 0 && /* @__PURE__ */ jsx4("span", { className: "isotone-meta", children: references.map((reference) => /* @__PURE__ */ jsx4(SummaryTag, { label: reference.label }, reference.key)) })
|
|
1673
1865
|
] });
|
|
1674
1866
|
}
|
|
@@ -1692,6 +1884,7 @@ function NoteSummary({ note, page }) {
|
|
|
1692
1884
|
}
|
|
1693
1885
|
return /* @__PURE__ */ jsxs3(Fragment3, { children: [
|
|
1694
1886
|
/* @__PURE__ */ jsx4("p", { children: note.feedback }),
|
|
1887
|
+
/* @__PURE__ */ jsx4(Reply, { note }),
|
|
1695
1888
|
(page || tags.length > 0) && /* @__PURE__ */ jsxs3("span", { className: "isotone-meta", children: [
|
|
1696
1889
|
page && /* @__PURE__ */ jsx4(SummaryTag, { label: page }),
|
|
1697
1890
|
tags.slice(0, 3).map((tag) => /* @__PURE__ */ jsx4(SummaryTag, { label: `${tag.name} ${tag.value}` }, `${tag.name}-${tag.value}`)),
|
|
@@ -1789,11 +1982,14 @@ function useDockMotion(ref, visible) {
|
|
|
1789
1982
|
if (changed) measure();
|
|
1790
1983
|
});
|
|
1791
1984
|
mutations.observe(root, { subtree: true, childList: true, attributes: true, attributeFilter: ["data-open", "data-instant"] });
|
|
1985
|
+
const lifted = new MutationObserver(measure);
|
|
1986
|
+
lifted.observe(root, { attributes: true, attributeFilter: ["style"] });
|
|
1792
1987
|
reduced?.addEventListener("change", measure);
|
|
1793
1988
|
window.addEventListener("resize", measure);
|
|
1794
1989
|
return () => {
|
|
1795
1990
|
observer?.disconnect();
|
|
1796
1991
|
mutations.disconnect();
|
|
1992
|
+
lifted.disconnect();
|
|
1797
1993
|
reduced?.removeEventListener("change", measure);
|
|
1798
1994
|
window.removeEventListener("resize", measure);
|
|
1799
1995
|
for (const element of exits.keys()) removeExit(element);
|
|
@@ -1802,6 +1998,73 @@ function useDockMotion(ref, visible) {
|
|
|
1802
1998
|
}, [ref, visible]);
|
|
1803
1999
|
}
|
|
1804
2000
|
|
|
2001
|
+
// src/react/hints.ts
|
|
2002
|
+
var HINTS = {
|
|
2003
|
+
/** The select-mode cover over the docked composer: what to do, then how to pause. */
|
|
2004
|
+
picking: {
|
|
2005
|
+
pointer: { title: "Click elements on the page", pause: "Hold \u21E7 to use the page" },
|
|
2006
|
+
touch: { title: "Tap elements on the page", pause: "Tap Done to use the page" }
|
|
2007
|
+
},
|
|
2008
|
+
/** The floating composer beside a pinned element, while still selecting. */
|
|
2009
|
+
floating: {
|
|
2010
|
+
pointer: "Click to pin more \xB7 \u21E7 uses the page",
|
|
2011
|
+
touch: "Tap to pin more \xB7 crosshair pauses"
|
|
2012
|
+
}
|
|
2013
|
+
};
|
|
2014
|
+
|
|
2015
|
+
// src/react/viewport.ts
|
|
2016
|
+
import { useEffect as useEffect3 } from "react";
|
|
2017
|
+
var COMPACT_QUERY = "(max-width: 640px), (max-height: 500px)";
|
|
2018
|
+
var TOUCH_QUERY = "(hover: none) and (pointer: coarse)";
|
|
2019
|
+
var KEYBOARD_MIN = 80;
|
|
2020
|
+
function media(query) {
|
|
2021
|
+
return typeof matchMedia === "function" && Boolean(matchMedia(query)?.matches);
|
|
2022
|
+
}
|
|
2023
|
+
function isCompactViewport() {
|
|
2024
|
+
return media(COMPACT_QUERY);
|
|
2025
|
+
}
|
|
2026
|
+
function prefersTouch() {
|
|
2027
|
+
return media(TOUCH_QUERY);
|
|
2028
|
+
}
|
|
2029
|
+
function visibleBand() {
|
|
2030
|
+
const viewport = typeof window !== "undefined" ? window.visualViewport : null;
|
|
2031
|
+
if (!viewport || Math.abs(viewport.scale - 1) > 0.01 || innerHeight - viewport.height < KEYBOARD_MIN)
|
|
2032
|
+
return { top: 0, bottom: innerHeight };
|
|
2033
|
+
return { top: viewport.offsetTop, bottom: viewport.offsetTop + viewport.height };
|
|
2034
|
+
}
|
|
2035
|
+
function useKeyboardInset(ref, active) {
|
|
2036
|
+
useEffect3(() => {
|
|
2037
|
+
const node = ref.current;
|
|
2038
|
+
const viewport = typeof window !== "undefined" ? window.visualViewport : null;
|
|
2039
|
+
if (!active || !node || !viewport) return;
|
|
2040
|
+
let frame = 0;
|
|
2041
|
+
const apply = () => {
|
|
2042
|
+
frame = 0;
|
|
2043
|
+
const band = visibleBand();
|
|
2044
|
+
if (band.top === 0 && band.bottom === innerHeight) {
|
|
2045
|
+
node.style.removeProperty("--iso-keyboard");
|
|
2046
|
+
node.style.removeProperty("--iso-viewport");
|
|
2047
|
+
return;
|
|
2048
|
+
}
|
|
2049
|
+
node.style.setProperty("--iso-keyboard", `${Math.max(0, Math.round(innerHeight - band.bottom))}px`);
|
|
2050
|
+
node.style.setProperty("--iso-viewport", `${Math.round(band.bottom - band.top)}px`);
|
|
2051
|
+
};
|
|
2052
|
+
const schedule = () => {
|
|
2053
|
+
frame ||= requestAnimationFrame(apply);
|
|
2054
|
+
};
|
|
2055
|
+
apply();
|
|
2056
|
+
viewport.addEventListener("resize", schedule);
|
|
2057
|
+
viewport.addEventListener("scroll", schedule);
|
|
2058
|
+
return () => {
|
|
2059
|
+
cancelAnimationFrame(frame);
|
|
2060
|
+
viewport.removeEventListener("resize", schedule);
|
|
2061
|
+
viewport.removeEventListener("scroll", schedule);
|
|
2062
|
+
node.style.removeProperty("--iso-keyboard");
|
|
2063
|
+
node.style.removeProperty("--iso-viewport");
|
|
2064
|
+
};
|
|
2065
|
+
}, [ref, active]);
|
|
2066
|
+
}
|
|
2067
|
+
|
|
1805
2068
|
// src/react/IterationPanel.tsx
|
|
1806
2069
|
import { jsx as jsx5, jsxs as jsxs4 } from "react/jsx-runtime";
|
|
1807
2070
|
var PULSE_MS = 2e3;
|
|
@@ -1859,6 +2122,7 @@ function IterationPanel({
|
|
|
1859
2122
|
rule,
|
|
1860
2123
|
getPayload,
|
|
1861
2124
|
variants,
|
|
2125
|
+
variantsEmptyText,
|
|
1862
2126
|
defaultTab = "notes",
|
|
1863
2127
|
shortcut = DEFAULT_SHORTCUT
|
|
1864
2128
|
}) {
|
|
@@ -1887,6 +2151,16 @@ function IterationPanel({
|
|
|
1887
2151
|
);
|
|
1888
2152
|
const [hoverRect, setHoverRect] = useState3(null);
|
|
1889
2153
|
const [rects, setRects] = useState3([]);
|
|
2154
|
+
const pinnedElements = useRef4(/* @__PURE__ */ new Map());
|
|
2155
|
+
const locate = useCallback((selector) => {
|
|
2156
|
+
const pinned = pinnedElements.current.get(selector);
|
|
2157
|
+
if (pinned?.isConnected) return pinned;
|
|
2158
|
+
try {
|
|
2159
|
+
return document.querySelector(selector);
|
|
2160
|
+
} catch {
|
|
2161
|
+
return null;
|
|
2162
|
+
}
|
|
2163
|
+
}, []);
|
|
1890
2164
|
const [text, setText] = useState3("");
|
|
1891
2165
|
const [notes, setNotes] = useState3([]);
|
|
1892
2166
|
const [scope, setScope] = useState3(null);
|
|
@@ -1899,17 +2173,19 @@ function IterationPanel({
|
|
|
1899
2173
|
const [notice, setNotice] = useState3("");
|
|
1900
2174
|
const [theme, setTheme] = useState3(() => detectTheme(themePreference));
|
|
1901
2175
|
const [instant, setInstant] = useState3(false);
|
|
2176
|
+
const [touch, setTouch] = useState3(prefersTouch);
|
|
2177
|
+
const touchRef = useRef4(touch);
|
|
1902
2178
|
const rootRef = useRef4(null);
|
|
1903
2179
|
const textareaRef = useRef4(null);
|
|
1904
2180
|
const focusFrame = useRef4(0);
|
|
1905
|
-
|
|
2181
|
+
useEffect4(() => () => cancelAnimationFrame(focusFrame.current), []);
|
|
1906
2182
|
const listRef = useRef4(null);
|
|
1907
2183
|
useLayoutMotion(listRef, notes, notesOpen);
|
|
1908
2184
|
const fabRef = useRef4(null);
|
|
1909
2185
|
const previousFocus = useRef4(null);
|
|
1910
2186
|
const draftRestored = useRef4("");
|
|
1911
2187
|
const restoringDraft = useRef4(false);
|
|
1912
|
-
const id =
|
|
2188
|
+
const id = useId3();
|
|
1913
2189
|
const notesAvailable = Boolean(storeCfg.endpoint);
|
|
1914
2190
|
useVariantsSource(variants);
|
|
1915
2191
|
const variantsAvailable = Boolean(
|
|
@@ -1937,7 +2213,7 @@ function IterationPanel({
|
|
|
1937
2213
|
observer.observe(field);
|
|
1938
2214
|
return () => observer.disconnect();
|
|
1939
2215
|
}, [text, notesOpen, visible, hasContext, floating]);
|
|
1940
|
-
|
|
2216
|
+
useEffect4(() => {
|
|
1941
2217
|
if (!visible) {
|
|
1942
2218
|
setHovered(false);
|
|
1943
2219
|
setFocused(false);
|
|
@@ -1965,9 +2241,21 @@ function IterationPanel({
|
|
|
1965
2241
|
};
|
|
1966
2242
|
}, [visible]);
|
|
1967
2243
|
useDockMotion(rootRef, visible);
|
|
2244
|
+
useKeyboardInset(rootRef, visible);
|
|
2245
|
+
const wasOpen = useRef4({ notes: notesOpen, variants: variantsOpen });
|
|
2246
|
+
useLayoutEffect4(() => {
|
|
2247
|
+
const was = wasOpen.current;
|
|
2248
|
+
wasOpen.current = { notes: notesOpen, variants: variantsOpen };
|
|
2249
|
+
if (!notesOpen || !variantsOpen || !isCompactViewport()) return;
|
|
2250
|
+
if (was.variants) setVariantsOpen(false);
|
|
2251
|
+
else {
|
|
2252
|
+
setNotesOpen(false);
|
|
2253
|
+
setPicking(false);
|
|
2254
|
+
}
|
|
2255
|
+
}, [notesOpen, variantsOpen]);
|
|
1968
2256
|
const open = notesOpen || variantsOpen;
|
|
1969
2257
|
const railOpen = expanded || hovered || focused && instant || open || picking;
|
|
1970
|
-
|
|
2258
|
+
useEffect4(() => {
|
|
1971
2259
|
if (!railOpen || !instant) return;
|
|
1972
2260
|
const active = rootRef.current?.getRootNode()?.activeElement;
|
|
1973
2261
|
if (active === fabRef.current) rootRef.current?.querySelector(".isotone-rail-button")?.focus();
|
|
@@ -1976,7 +2264,7 @@ function IterationPanel({
|
|
|
1976
2264
|
const openNotes = useMemo3(() => notes.filter((n) => !n.done), [notes]);
|
|
1977
2265
|
const pages = useMemo3(() => {
|
|
1978
2266
|
const counts = /* @__PURE__ */ new Map();
|
|
1979
|
-
for (const n of openNotes) counts.set(n.route, (counts.get(n.route) ?? 0) + 1);
|
|
2267
|
+
for (const n of openNotes) if (!n.update) counts.set(n.route, (counts.get(n.route) ?? 0) + 1);
|
|
1980
2268
|
const others = [...counts.keys()].filter((r) => r !== route).sort();
|
|
1981
2269
|
return { counts, others };
|
|
1982
2270
|
}, [openNotes, route]);
|
|
@@ -1984,7 +2272,8 @@ function IterationPanel({
|
|
|
1984
2272
|
const chosen = (scope ?? [route]).filter((r) => r === route || pages.counts.has(r));
|
|
1985
2273
|
return new Set(chosen.length ? chosen : [route]);
|
|
1986
2274
|
}, [scope, route, pages]);
|
|
1987
|
-
const
|
|
2275
|
+
const updateRequested = useMemo3(() => openNotes.some((n) => n.update), [openNotes]);
|
|
2276
|
+
const visibleNotes = useMemo3(() => openNotes.filter((n) => n.update || selectedPages.has(n.route)).sort((a, b) => a.createdAt.localeCompare(b.createdAt)), [openNotes, selectedPages]);
|
|
1988
2277
|
const working = useMemo3(() => new Set(visibleNotes.filter((n) => n.workingAt && clock - Date.parse(n.workingAt) < WORKING_FRESH_MS).map((n) => n.id)), [visibleNotes, clock]);
|
|
1989
2278
|
function togglePage(page) {
|
|
1990
2279
|
const next = new Set(selectedPages);
|
|
@@ -2184,10 +2473,10 @@ function IterationPanel({
|
|
|
2184
2473
|
finish();
|
|
2185
2474
|
}
|
|
2186
2475
|
}
|
|
2187
|
-
|
|
2476
|
+
useEffect4(() => {
|
|
2188
2477
|
loadNotes();
|
|
2189
2478
|
}, [notesOpen, variantsOpen, route]);
|
|
2190
|
-
|
|
2479
|
+
useEffect4(() => {
|
|
2191
2480
|
if (!notesAvailable || !visible) return;
|
|
2192
2481
|
let stopped = false;
|
|
2193
2482
|
let running = false;
|
|
@@ -2236,13 +2525,13 @@ function IterationPanel({
|
|
|
2236
2525
|
window.removeEventListener("focus", wake);
|
|
2237
2526
|
};
|
|
2238
2527
|
}, [notesAvailable, visible, store]);
|
|
2239
|
-
|
|
2528
|
+
useEffect4(() => {
|
|
2240
2529
|
if (variants && notesOpen && !variantsOpen) refreshSource(variants);
|
|
2241
2530
|
}, [notesOpen, variantsOpen, variants]);
|
|
2242
|
-
|
|
2531
|
+
useEffect4(() => {
|
|
2243
2532
|
if (variants && !variantsAvailable) refreshSource(variants);
|
|
2244
2533
|
}, [variants]);
|
|
2245
|
-
|
|
2534
|
+
useEffect4(() => {
|
|
2246
2535
|
if (draftRestored.current === draftKey) return;
|
|
2247
2536
|
draftRestored.current = draftKey;
|
|
2248
2537
|
restoringDraft.current = true;
|
|
@@ -2267,7 +2556,7 @@ function IterationPanel({
|
|
|
2267
2556
|
} catch {
|
|
2268
2557
|
}
|
|
2269
2558
|
}, [draftKey]);
|
|
2270
|
-
|
|
2559
|
+
useEffect4(() => {
|
|
2271
2560
|
if (restoringDraft.current) {
|
|
2272
2561
|
restoringDraft.current = false;
|
|
2273
2562
|
return;
|
|
@@ -2285,7 +2574,7 @@ function IterationPanel({
|
|
|
2285
2574
|
} catch {
|
|
2286
2575
|
}
|
|
2287
2576
|
}, [text, targets, variantTarget, editing, draftKey]);
|
|
2288
|
-
|
|
2577
|
+
useEffect4(() => {
|
|
2289
2578
|
const el = listRef.current;
|
|
2290
2579
|
if (el) el.scrollTop = el.scrollHeight;
|
|
2291
2580
|
}, [notes, notesOpen]);
|
|
@@ -2296,7 +2585,7 @@ function IterationPanel({
|
|
|
2296
2585
|
ensurePanelStyles(root);
|
|
2297
2586
|
return observeTheme(setTheme, themePreference);
|
|
2298
2587
|
}, [visible, open, route, themePreference]);
|
|
2299
|
-
|
|
2588
|
+
useEffect4(() => {
|
|
2300
2589
|
if (!visible) return;
|
|
2301
2590
|
const onKey = (e) => {
|
|
2302
2591
|
rootRef.current?.setAttribute("data-instant", "true");
|
|
@@ -2317,9 +2606,13 @@ function IterationPanel({
|
|
|
2317
2606
|
startFeedback(true);
|
|
2318
2607
|
}
|
|
2319
2608
|
};
|
|
2320
|
-
const onPointer = () => {
|
|
2609
|
+
const onPointer = (e) => {
|
|
2321
2610
|
rootRef.current?.setAttribute("data-instant", "false");
|
|
2322
2611
|
setInstant(false);
|
|
2612
|
+
if (e.pointerType) {
|
|
2613
|
+
touchRef.current = e.pointerType === "touch";
|
|
2614
|
+
setTouch(touchRef.current);
|
|
2615
|
+
}
|
|
2323
2616
|
};
|
|
2324
2617
|
window.addEventListener("keydown", onKey, true);
|
|
2325
2618
|
window.addEventListener("pointerdown", onPointer, true);
|
|
@@ -2328,7 +2621,7 @@ function IterationPanel({
|
|
|
2328
2621
|
window.removeEventListener("pointerdown", onPointer, true);
|
|
2329
2622
|
};
|
|
2330
2623
|
});
|
|
2331
|
-
|
|
2624
|
+
useEffect4(() => {
|
|
2332
2625
|
if (!open || picking) return;
|
|
2333
2626
|
const onKey = (e) => {
|
|
2334
2627
|
if (e.key !== "Escape" || e.defaultPrevented) return;
|
|
@@ -2339,7 +2632,7 @@ function IterationPanel({
|
|
|
2339
2632
|
window.addEventListener("keydown", onKey);
|
|
2340
2633
|
return () => window.removeEventListener("keydown", onKey);
|
|
2341
2634
|
});
|
|
2342
|
-
|
|
2635
|
+
useEffect4(() => {
|
|
2343
2636
|
if (!picking) return;
|
|
2344
2637
|
const cursor = document.body.style.cursor;
|
|
2345
2638
|
document.body.style.cursor = "crosshair";
|
|
@@ -2349,6 +2642,7 @@ function IterationPanel({
|
|
|
2349
2642
|
return el && !el.closest("[data-isotone]") ? el : null;
|
|
2350
2643
|
};
|
|
2351
2644
|
const onMove = (e) => {
|
|
2645
|
+
if (e.pointerType === "touch") return;
|
|
2352
2646
|
cancelAnimationFrame(frame);
|
|
2353
2647
|
const deferred = e.shiftKey;
|
|
2354
2648
|
frame = requestAnimationFrame(() => {
|
|
@@ -2358,6 +2652,7 @@ function IterationPanel({
|
|
|
2358
2652
|
};
|
|
2359
2653
|
const toggleTarget = (el) => {
|
|
2360
2654
|
const target = describeIterationElement(elementAsNode(el));
|
|
2655
|
+
pinnedElements.current.set(target.selector, el);
|
|
2361
2656
|
const added = !targetsRef.current.some((t) => t.selector === target.selector);
|
|
2362
2657
|
setTargets(
|
|
2363
2658
|
(prev) => prev.some((t) => t.selector === target.selector) ? prev.filter((t) => t.selector !== target.selector) : prev.length < 50 ? [...prev, target] : prev
|
|
@@ -2378,7 +2673,7 @@ function IterationPanel({
|
|
|
2378
2673
|
const y = pointed ? e.clientY : r.bottom;
|
|
2379
2674
|
if (!anchorRef.current)
|
|
2380
2675
|
slotHeight.current = rootRef.current?.querySelector(".isotone-notes-panel .isotone-input")?.offsetHeight ?? 0;
|
|
2381
|
-
setAnchor({ selector: added.selector, dx: x - r.left, dy: y - r.top, x, y });
|
|
2676
|
+
setAnchor({ selector: added.selector, dx: x - r.left, dy: y - r.top, x, y, beside: touchRef.current && pointed });
|
|
2382
2677
|
setNotesOpen(true);
|
|
2383
2678
|
focusWhilePicking();
|
|
2384
2679
|
}
|
|
@@ -2415,19 +2710,19 @@ function IterationPanel({
|
|
|
2415
2710
|
setNotesOpen(true);
|
|
2416
2711
|
focusComposer();
|
|
2417
2712
|
};
|
|
2418
|
-
window.addEventListener("
|
|
2713
|
+
window.addEventListener("pointermove", onMove, true);
|
|
2419
2714
|
window.addEventListener("click", onClick, true);
|
|
2420
2715
|
window.addEventListener("keydown", onKey, true);
|
|
2421
2716
|
return () => {
|
|
2422
2717
|
document.body.style.cursor = cursor;
|
|
2423
2718
|
cancelAnimationFrame(frame);
|
|
2424
2719
|
setHoverRect(null);
|
|
2425
|
-
window.removeEventListener("
|
|
2720
|
+
window.removeEventListener("pointermove", onMove, true);
|
|
2426
2721
|
window.removeEventListener("click", onClick, true);
|
|
2427
2722
|
window.removeEventListener("keydown", onKey, true);
|
|
2428
2723
|
};
|
|
2429
2724
|
}, [picking, focusComposer, focusWhilePicking]);
|
|
2430
|
-
|
|
2725
|
+
useEffect4(() => {
|
|
2431
2726
|
const shadow = rootRef.current?.getRootNode();
|
|
2432
2727
|
if (!(shadow instanceof ShadowRoot)) return;
|
|
2433
2728
|
const stop = (e) => e.stopPropagation();
|
|
@@ -2437,7 +2732,7 @@ function IterationPanel({
|
|
|
2437
2732
|
for (const type of types) shadow.removeEventListener(type, stop);
|
|
2438
2733
|
};
|
|
2439
2734
|
}, []);
|
|
2440
|
-
|
|
2735
|
+
useEffect4(() => {
|
|
2441
2736
|
if (!notesOpen) setAnchor(null);
|
|
2442
2737
|
}, [notesOpen]);
|
|
2443
2738
|
useLayoutEffect4(() => {
|
|
@@ -2453,39 +2748,46 @@ function IterationPanel({
|
|
|
2453
2748
|
const place = () => {
|
|
2454
2749
|
frame = void 0;
|
|
2455
2750
|
let { x, y } = anchor;
|
|
2456
|
-
|
|
2457
|
-
|
|
2458
|
-
|
|
2459
|
-
|
|
2460
|
-
|
|
2461
|
-
y = r.top + anchor.dy;
|
|
2462
|
-
}
|
|
2463
|
-
} catch {
|
|
2751
|
+
const el = locate(anchor.selector);
|
|
2752
|
+
const r = el?.getBoundingClientRect();
|
|
2753
|
+
if (r) {
|
|
2754
|
+
x = r.left + anchor.dx;
|
|
2755
|
+
y = r.top + anchor.dy;
|
|
2464
2756
|
}
|
|
2465
2757
|
const vw = document.documentElement.clientWidth || innerWidth;
|
|
2466
2758
|
const vh = innerHeight;
|
|
2759
|
+
const band = visibleBand();
|
|
2760
|
+
const minY = band.top + FLOAT_MARGIN;
|
|
2761
|
+
const maxY = band.bottom - FLOAT_MARGIN;
|
|
2467
2762
|
const w = float.offsetWidth;
|
|
2468
2763
|
const h = float.offsetHeight;
|
|
2764
|
+
const fits = (below, above) => [below + FLOAT_GAP + h <= maxY, above - FLOAT_GAP - h >= minY];
|
|
2765
|
+
let belowY = y;
|
|
2766
|
+
let aboveY = y;
|
|
2469
2767
|
let left = x + FLOAT_GAP;
|
|
2470
|
-
if (
|
|
2768
|
+
if (anchor.beside && r && fits(r.bottom, r.top).some(Boolean)) {
|
|
2769
|
+
belowY = r.bottom;
|
|
2770
|
+
aboveY = r.top;
|
|
2771
|
+
left = x - w / 2;
|
|
2772
|
+
} else if (left + w > vw - FLOAT_MARGIN) left = x - FLOAT_GAP - w;
|
|
2471
2773
|
left = Math.max(FLOAT_MARGIN, Math.min(left, vw - w - FLOAT_MARGIN));
|
|
2472
|
-
const fitsBelow =
|
|
2473
|
-
const fitsAbove = y - FLOAT_GAP - h >= FLOAT_MARGIN;
|
|
2774
|
+
const [fitsBelow, fitsAbove] = fits(belowY, aboveY);
|
|
2474
2775
|
if (side === null || (side === "below" ? !fitsBelow && fitsAbove : !fitsAbove && fitsBelow))
|
|
2475
2776
|
side = fitsBelow || !fitsAbove ? "below" : "above";
|
|
2476
2777
|
float.style.left = `${left}px`;
|
|
2778
|
+
let top;
|
|
2477
2779
|
if (side === "below") {
|
|
2478
|
-
|
|
2780
|
+
top = Math.max(minY, Math.min(belowY + FLOAT_GAP, maxY - h));
|
|
2479
2781
|
float.style.top = `${top}px`;
|
|
2480
2782
|
float.style.bottom = "auto";
|
|
2481
|
-
float.style.transformOrigin = `${x - left}px ${y - top}px`;
|
|
2482
2783
|
} else {
|
|
2483
|
-
const bottom = Math.max(
|
|
2784
|
+
const bottom = Math.max(vh - maxY, Math.min(vh - aboveY + FLOAT_GAP, vh - h - minY));
|
|
2484
2785
|
float.style.bottom = `${bottom}px`;
|
|
2485
2786
|
float.style.top = "auto";
|
|
2486
|
-
|
|
2787
|
+
top = vh - bottom - h;
|
|
2487
2788
|
}
|
|
2488
|
-
|
|
2789
|
+
float.style.transformOrigin = `${x - left}px ${y - top}px`;
|
|
2790
|
+
lastFloat.current = { left, top };
|
|
2489
2791
|
};
|
|
2490
2792
|
const schedule = () => {
|
|
2491
2793
|
frame ??= requestAnimationFrame(place);
|
|
@@ -2509,14 +2811,19 @@ function IterationPanel({
|
|
|
2509
2811
|
observer?.observe(float);
|
|
2510
2812
|
window.addEventListener("scroll", schedule, { capture: true, passive: true });
|
|
2511
2813
|
window.addEventListener("resize", schedule);
|
|
2814
|
+
const viewport = window.visualViewport;
|
|
2815
|
+
viewport?.addEventListener("resize", schedule);
|
|
2816
|
+
viewport?.addEventListener("scroll", schedule);
|
|
2512
2817
|
return () => {
|
|
2513
2818
|
if (frame !== void 0) cancelAnimationFrame(frame);
|
|
2514
2819
|
glide?.cancel();
|
|
2515
2820
|
observer?.disconnect();
|
|
2516
2821
|
window.removeEventListener("scroll", schedule, true);
|
|
2517
2822
|
window.removeEventListener("resize", schedule);
|
|
2823
|
+
viewport?.removeEventListener("resize", schedule);
|
|
2824
|
+
viewport?.removeEventListener("scroll", schedule);
|
|
2518
2825
|
};
|
|
2519
|
-
}, [anchor]);
|
|
2826
|
+
}, [anchor, locate]);
|
|
2520
2827
|
useLayoutEffect4(() => {
|
|
2521
2828
|
const last = floatNode.current;
|
|
2522
2829
|
const root = rootRef.current;
|
|
@@ -2543,7 +2850,11 @@ function IterationPanel({
|
|
|
2543
2850
|
ghost.remove();
|
|
2544
2851
|
};
|
|
2545
2852
|
}, [floating]);
|
|
2546
|
-
|
|
2853
|
+
useEffect4(() => {
|
|
2854
|
+
const kept = new Set(targets.map((t) => t.selector));
|
|
2855
|
+
for (const selector of pinnedElements.current.keys()) if (!kept.has(selector)) pinnedElements.current.delete(selector);
|
|
2856
|
+
}, [targets]);
|
|
2857
|
+
useEffect4(() => {
|
|
2547
2858
|
if (!picking && !floating) {
|
|
2548
2859
|
setRects([]);
|
|
2549
2860
|
return;
|
|
@@ -2553,22 +2864,18 @@ function IterationPanel({
|
|
|
2553
2864
|
frame = void 0;
|
|
2554
2865
|
setRects(
|
|
2555
2866
|
targets.flatMap((t) => {
|
|
2556
|
-
|
|
2557
|
-
|
|
2558
|
-
|
|
2559
|
-
|
|
2560
|
-
|
|
2561
|
-
|
|
2562
|
-
|
|
2563
|
-
|
|
2564
|
-
|
|
2565
|
-
|
|
2566
|
-
|
|
2567
|
-
|
|
2568
|
-
];
|
|
2569
|
-
} catch {
|
|
2570
|
-
return [];
|
|
2571
|
-
}
|
|
2867
|
+
const el = locate(t.selector);
|
|
2868
|
+
if (!el) return [];
|
|
2869
|
+
const r = el.getBoundingClientRect();
|
|
2870
|
+
return [
|
|
2871
|
+
{
|
|
2872
|
+
top: r.top,
|
|
2873
|
+
left: r.left,
|
|
2874
|
+
width: r.width,
|
|
2875
|
+
height: r.height,
|
|
2876
|
+
selector: t.selector
|
|
2877
|
+
}
|
|
2878
|
+
];
|
|
2572
2879
|
})
|
|
2573
2880
|
);
|
|
2574
2881
|
};
|
|
@@ -2583,7 +2890,7 @@ function IterationPanel({
|
|
|
2583
2890
|
window.removeEventListener("scroll", schedule, true);
|
|
2584
2891
|
window.removeEventListener("resize", schedule);
|
|
2585
2892
|
};
|
|
2586
|
-
}, [targets, picking, floating]);
|
|
2893
|
+
}, [targets, picking, floating, locate]);
|
|
2587
2894
|
const variantsPanel = variants && /* @__PURE__ */ jsxs4(
|
|
2588
2895
|
"section",
|
|
2589
2896
|
{
|
|
@@ -2613,7 +2920,8 @@ function IterationPanel({
|
|
|
2613
2920
|
onFeedback: notesAvailable ? feedback : void 0,
|
|
2614
2921
|
onSaved: loadNotes,
|
|
2615
2922
|
onManage: notesAvailable ? queueRemoval : void 0,
|
|
2616
|
-
pendingActions
|
|
2923
|
+
pendingActions,
|
|
2924
|
+
emptyText: variantsEmptyText
|
|
2617
2925
|
}
|
|
2618
2926
|
)
|
|
2619
2927
|
]
|
|
@@ -2635,6 +2943,11 @@ function IterationPanel({
|
|
|
2635
2943
|
textareaRef.current?.focus({ preventScroll: true });
|
|
2636
2944
|
},
|
|
2637
2945
|
children: [
|
|
2946
|
+
editing?.reply?.text && !editing.reply.answered && /* @__PURE__ */ jsxs4("p", { className: "isotone-editing-reply", children: [
|
|
2947
|
+
/* @__PURE__ */ jsx5("span", { children: "Agent asks" }),
|
|
2948
|
+
" ",
|
|
2949
|
+
editing.reply.text
|
|
2950
|
+
] }),
|
|
2638
2951
|
/* @__PURE__ */ jsxs4("div", { className: "isotone-context", "aria-live": "polite", children: [
|
|
2639
2952
|
editing && /* @__PURE__ */ jsxs4("span", { className: "isotone-chip", children: [
|
|
2640
2953
|
"Editing note",
|
|
@@ -2734,7 +3047,7 @@ function IterationPanel({
|
|
|
2734
3047
|
children: /* @__PURE__ */ jsx5(Icon2, { name: "pin", size: 14 })
|
|
2735
3048
|
}
|
|
2736
3049
|
),
|
|
2737
|
-
floating && picking && /* @__PURE__ */ jsx5("span", { ref: fadeIn, className: "isotone-float-hint", children:
|
|
3050
|
+
floating && picking && /* @__PURE__ */ jsx5("span", { ref: fadeIn, className: "isotone-float-hint", children: HINTS.floating[touch ? "touch" : "pointer"] }),
|
|
2738
3051
|
/* @__PURE__ */ jsx5(
|
|
2739
3052
|
"button",
|
|
2740
3053
|
{
|
|
@@ -2751,8 +3064,8 @@ function IterationPanel({
|
|
|
2751
3064
|
picking && !floating && /* @__PURE__ */ jsxs4("div", { ref: fadeIn, className: "isotone-picking", "data-still": pickingWithPanel, children: [
|
|
2752
3065
|
/* @__PURE__ */ jsx5(Icon2, { name: "pin", size: 14 }),
|
|
2753
3066
|
/* @__PURE__ */ jsxs4("p", { children: [
|
|
2754
|
-
/* @__PURE__ */ jsx5("span", { children: targets.length ? `${targets.length} selected` :
|
|
2755
|
-
/* @__PURE__ */ jsx5("small", { children:
|
|
3067
|
+
/* @__PURE__ */ jsx5("span", { children: targets.length ? `${targets.length} selected` : HINTS.picking[touch ? "touch" : "pointer"].title }),
|
|
3068
|
+
/* @__PURE__ */ jsx5("small", { children: HINTS.picking[touch ? "touch" : "pointer"].pause })
|
|
2756
3069
|
] }),
|
|
2757
3070
|
/* @__PURE__ */ jsxs4(
|
|
2758
3071
|
"button",
|
|
@@ -2764,8 +3077,8 @@ function IterationPanel({
|
|
|
2764
3077
|
focusComposer();
|
|
2765
3078
|
},
|
|
2766
3079
|
children: [
|
|
2767
|
-
"Done
|
|
2768
|
-
/* @__PURE__ */ jsx5("kbd", { children: "\u21B5" })
|
|
3080
|
+
"Done",
|
|
3081
|
+
!touch && /* @__PURE__ */ jsx5("kbd", { children: "\u21B5" })
|
|
2769
3082
|
]
|
|
2770
3083
|
}
|
|
2771
3084
|
)
|
|
@@ -2842,7 +3155,14 @@ function IterationPanel({
|
|
|
2842
3155
|
"data-motion-key": note.id,
|
|
2843
3156
|
className: `isotone-note${editing?.id === note.id ? " isotone-note-editing" : ""}`,
|
|
2844
3157
|
children: [
|
|
2845
|
-
|
|
3158
|
+
note.update ? (
|
|
3159
|
+
// Isotone writes an update note's text from its request, so
|
|
3160
|
+
// there is nothing to edit; its corner action cancels it.
|
|
3161
|
+
/* @__PURE__ */ jsxs4("div", { className: "isotone-note-hit", "data-static": "", children: [
|
|
3162
|
+
working.has(note.id) && /* @__PURE__ */ jsx5("span", { className: "isotone-working", ref: dotPulse, role: "img", "aria-label": "An agent is working on this", title: "An agent is working on this" }),
|
|
3163
|
+
/* @__PURE__ */ jsx5(NoteSummary, { note })
|
|
3164
|
+
] })
|
|
3165
|
+
) : /* @__PURE__ */ jsxs4(
|
|
2846
3166
|
"button",
|
|
2847
3167
|
{
|
|
2848
3168
|
type: "button",
|
|
@@ -2860,8 +3180,8 @@ function IterationPanel({
|
|
|
2860
3180
|
{
|
|
2861
3181
|
type: "button",
|
|
2862
3182
|
className: "isotone-x isotone-icon",
|
|
2863
|
-
"aria-label": note.variantAction ? "Cancel removal" : "Delete note",
|
|
2864
|
-
title: note.variantAction ? "Cancel removal" : "Delete note",
|
|
3183
|
+
"aria-label": note.variantAction ? "Cancel removal" : note.update ? "Cancel update request" : "Delete note",
|
|
3184
|
+
title: note.variantAction ? "Cancel removal" : note.update ? "Cancel update request" : "Delete note",
|
|
2865
3185
|
onClick: () => void removeNote(note),
|
|
2866
3186
|
children: /* @__PURE__ */ jsx5(Icon2, { name: "close", size: 12 })
|
|
2867
3187
|
}
|
|
@@ -2904,6 +3224,7 @@ function IterationPanel({
|
|
|
2904
3224
|
"data-tools": Number(Boolean(variants)) + Number(notesAvailable),
|
|
2905
3225
|
"data-instant": instant,
|
|
2906
3226
|
"data-notes-open": notesOpen,
|
|
3227
|
+
"data-selecting": picking || floating,
|
|
2907
3228
|
"data-working": working.size > 0,
|
|
2908
3229
|
"data-open": open,
|
|
2909
3230
|
className: "isotone-root",
|
|
@@ -2944,7 +3265,16 @@ function IterationPanel({
|
|
|
2944
3265
|
children: composer
|
|
2945
3266
|
}
|
|
2946
3267
|
),
|
|
2947
|
-
/* @__PURE__ */ jsx5(
|
|
3268
|
+
/* @__PURE__ */ jsx5("div", { className: "isotone-notices", children: /* @__PURE__ */ jsx5(
|
|
3269
|
+
UpdateNotice,
|
|
3270
|
+
{
|
|
3271
|
+
endpoint: updateEndpoint,
|
|
3272
|
+
active: open,
|
|
3273
|
+
route,
|
|
3274
|
+
requested: updateRequested,
|
|
3275
|
+
onRequested: notesAvailable ? loadNotes : void 0
|
|
3276
|
+
}
|
|
3277
|
+
) }),
|
|
2948
3278
|
/* @__PURE__ */ jsxs4(
|
|
2949
3279
|
"fieldset",
|
|
2950
3280
|
{
|
|
@@ -3069,9 +3399,10 @@ function withDocumentUntouched(create) {
|
|
|
3069
3399
|
}
|
|
3070
3400
|
return result;
|
|
3071
3401
|
}
|
|
3402
|
+
var KNOBS_EMPTY_TEXT = "No knobs on this page yet. Ask your agent to add knobs for this page to .isotone/knobs.json and they will appear here.";
|
|
3072
3403
|
function mountIteration(opts) {
|
|
3073
3404
|
const getRoute = opts.getRoute ?? (() => window.location.pathname);
|
|
3074
|
-
const variants = opts.variants === "overlay" ? overlaySourceWithSave(getRoute, opts.endpoint, opts.key) : opts.variants;
|
|
3405
|
+
const variants = opts.variants === "overlay" || opts.variants === "knobs" ? overlaySourceWithSave(getRoute, opts.endpoint, opts.key, opts.variantsEndpoint) : opts.variants;
|
|
3075
3406
|
if (!opts.endpoint && !variants) return () => {
|
|
3076
3407
|
};
|
|
3077
3408
|
if (!opts.endpoint && variants && variants.getAxes().length === 0 && typeof variants.refresh !== "function") {
|
|
@@ -3103,6 +3434,7 @@ function mountIteration(opts) {
|
|
|
3103
3434
|
rule: opts.rule,
|
|
3104
3435
|
getPayload: opts.getPayload,
|
|
3105
3436
|
variants,
|
|
3437
|
+
variantsEmptyText: opts.variants === "knobs" ? KNOBS_EMPTY_TEXT : void 0,
|
|
3106
3438
|
defaultTab: opts.defaultTab,
|
|
3107
3439
|
shortcut: opts.shortcut,
|
|
3108
3440
|
updateEndpoint: opts.updateEndpoint,
|
|
@@ -3149,17 +3481,18 @@ function mountIteration(opts) {
|
|
|
3149
3481
|
}, 0);
|
|
3150
3482
|
};
|
|
3151
3483
|
}
|
|
3152
|
-
function overlaySourceWithSave(getRoute, endpoint, key) {
|
|
3153
|
-
const source = createOverlayVariantsSource({ endpoint:
|
|
3484
|
+
function overlaySourceWithSave(getRoute, endpoint, key, variantsEndpoint = VARIANTS_ENDPOINT) {
|
|
3485
|
+
const source = createOverlayVariantsSource({ endpoint: variantsEndpoint, getRoute });
|
|
3154
3486
|
if (!endpoint) return source;
|
|
3155
3487
|
const notes = createIterationStore({ endpoint, key });
|
|
3156
3488
|
source.saveDefaults = async (selections) => {
|
|
3157
|
-
const picked = source.getAxes().filter((axis) => axis.kind === void 0 || axis.kind === "select").flatMap((axis) => {
|
|
3489
|
+
const picked = source.getAxes().filter((axis) => (axis.kind === void 0 || axis.kind === "select") && !source.knobs.axes.includes(axis)).flatMap((axis) => {
|
|
3158
3490
|
const id = selections[axis.id];
|
|
3159
3491
|
if (!id || id === OVERLAY_DEFAULT_OPTION) return [];
|
|
3160
3492
|
const variant = source.list?.variants.find((v) => v.id === id);
|
|
3161
3493
|
const label = variant?.label ?? id;
|
|
3162
|
-
const
|
|
3494
|
+
const selector = axisSection(axis).fullLabel;
|
|
3495
|
+
const named = selector === label || axis.label === label ? label : `${selector}: ${label}`;
|
|
3163
3496
|
return [{ id, named, element: variant?.element ?? null }];
|
|
3164
3497
|
});
|
|
3165
3498
|
const moved = knobDelta(source.knobs.axes, source.knobs.values);
|
|
@@ -3168,7 +3501,7 @@ function overlaySourceWithSave(getRoute, endpoint, key) {
|
|
|
3168
3501
|
parts.push(`Make the default: ${picked.map((p) => `"${p.named}" (${p.id})`).join("; ")}.`);
|
|
3169
3502
|
}
|
|
3170
3503
|
if (moved.length) {
|
|
3171
|
-
parts.push(`Knobs: ${moved.map((m) => `${m.axis.
|
|
3504
|
+
parts.push(`Knobs: ${moved.map((m) => `${axisSection(m.axis).fullLabel} ${m.shown} (${m.axis.id})`).join("; ")}.`);
|
|
3172
3505
|
}
|
|
3173
3506
|
const feedback = parts.length ? `[set-defaults] ${parts.join(" ")}` : "[set-defaults] The current defaults stay on this route.";
|
|
3174
3507
|
await notes.saveNote(
|
|
@@ -3191,10 +3524,12 @@ export {
|
|
|
3191
3524
|
OVERLAY_DEFAULT_OPTION,
|
|
3192
3525
|
OVERLAY_GROUP_ID,
|
|
3193
3526
|
PANEL_CSS,
|
|
3527
|
+
SECTION_SEPARATOR,
|
|
3194
3528
|
VARIANTS_ENDPOINT,
|
|
3195
3529
|
VariantsView,
|
|
3196
3530
|
applyKnobValues,
|
|
3197
3531
|
axisDefault,
|
|
3532
|
+
axisSection,
|
|
3198
3533
|
createOverlayVariantsSource,
|
|
3199
3534
|
createVariantsSource,
|
|
3200
3535
|
defaultSelections,
|
|
@@ -3214,7 +3549,9 @@ export {
|
|
|
3214
3549
|
overlayAxes,
|
|
3215
3550
|
overlayGroups,
|
|
3216
3551
|
parseVariantParam,
|
|
3552
|
+
sectionAxes,
|
|
3217
3553
|
serializeVariantParam,
|
|
3554
|
+
splitSectionLabel,
|
|
3218
3555
|
visibleAxes
|
|
3219
3556
|
};
|
|
3220
3557
|
/*! Bundled license information:
|