isotone 1.1.3 → 1.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +52 -5
- package/CHANGELOG.md +42 -0
- package/README.md +63 -14
- package/dist/{IterationPanel-phJRYtUO.d.ts → IterationPanel-Bq77d5NT.d.ts} +3 -1
- package/dist/{chunk-K5RAC2BT.js → chunk-2FY2JNPG.js} +3 -5
- package/dist/chunk-2FY2JNPG.js.map +1 -0
- package/dist/{chunk-RKZWO37H.js → chunk-BTJHQXF7.js} +85 -1
- 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-L6O4HYGP.js +1150 -0
- package/dist/chunk-L6O4HYGP.js.map +1 -0
- package/dist/{chunk-YTEUMO3O.js → chunk-LD7UWI7W.js} +17 -33
- package/dist/chunk-LD7UWI7W.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 +1015 -148
- package/dist/cli.js.map +1 -1
- package/dist/core.d.ts +92 -2
- package/dist/core.js +10 -3
- package/dist/index.d.ts +13 -28
- package/dist/index.js +586 -257
- 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 +23 -19
- package/dist/vite-plugin.js.map +1 -1
- package/docs/blast.md +133 -0
- package/docs/integrations.md +75 -15
- package/docs/migration.md +19 -0
- package/docs/updates.md +110 -4
- package/docs/variants.md +65 -7
- package/package.json +2 -1
- package/dist/chunk-A4DX2V4C.js +0 -7
- package/dist/chunk-A4DX2V4C.js.map +0 -1
- package/dist/chunk-BRBXIGZP.js +0 -593
- package/dist/chunk-BRBXIGZP.js.map +0 -1
- package/dist/chunk-K5RAC2BT.js.map +0 -1
- package/dist/chunk-LXW6WFOC.js +0 -402
- package/dist/chunk-LXW6WFOC.js.map +0 -1
- package/dist/chunk-RKZWO37H.js.map +0 -1
- package/dist/chunk-RQ2YI5KS.js +0 -24
- package/dist/chunk-RQ2YI5KS.js.map +0 -1
- package/dist/chunk-YTEUMO3O.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("");
|
|
632
651
|
try {
|
|
633
|
-
await
|
|
634
|
-
|
|
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());
|
|
635
661
|
} catch {
|
|
636
|
-
|
|
637
|
-
|
|
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 });
|
|
670
|
+
try {
|
|
671
|
+
await navigator.clipboard.writeText(text);
|
|
672
|
+
setStatus("Prompt copied");
|
|
673
|
+
} catch {
|
|
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
|
}) {
|
|
@@ -1909,17 +2173,19 @@ function IterationPanel({
|
|
|
1909
2173
|
const [notice, setNotice] = useState3("");
|
|
1910
2174
|
const [theme, setTheme] = useState3(() => detectTheme(themePreference));
|
|
1911
2175
|
const [instant, setInstant] = useState3(false);
|
|
2176
|
+
const [touch, setTouch] = useState3(prefersTouch);
|
|
2177
|
+
const touchRef = useRef4(touch);
|
|
1912
2178
|
const rootRef = useRef4(null);
|
|
1913
2179
|
const textareaRef = useRef4(null);
|
|
1914
2180
|
const focusFrame = useRef4(0);
|
|
1915
|
-
|
|
2181
|
+
useEffect4(() => () => cancelAnimationFrame(focusFrame.current), []);
|
|
1916
2182
|
const listRef = useRef4(null);
|
|
1917
2183
|
useLayoutMotion(listRef, notes, notesOpen);
|
|
1918
2184
|
const fabRef = useRef4(null);
|
|
1919
2185
|
const previousFocus = useRef4(null);
|
|
1920
2186
|
const draftRestored = useRef4("");
|
|
1921
2187
|
const restoringDraft = useRef4(false);
|
|
1922
|
-
const id =
|
|
2188
|
+
const id = useId3();
|
|
1923
2189
|
const notesAvailable = Boolean(storeCfg.endpoint);
|
|
1924
2190
|
useVariantsSource(variants);
|
|
1925
2191
|
const variantsAvailable = Boolean(
|
|
@@ -1947,7 +2213,7 @@ function IterationPanel({
|
|
|
1947
2213
|
observer.observe(field);
|
|
1948
2214
|
return () => observer.disconnect();
|
|
1949
2215
|
}, [text, notesOpen, visible, hasContext, floating]);
|
|
1950
|
-
|
|
2216
|
+
useEffect4(() => {
|
|
1951
2217
|
if (!visible) {
|
|
1952
2218
|
setHovered(false);
|
|
1953
2219
|
setFocused(false);
|
|
@@ -1975,9 +2241,21 @@ function IterationPanel({
|
|
|
1975
2241
|
};
|
|
1976
2242
|
}, [visible]);
|
|
1977
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]);
|
|
1978
2256
|
const open = notesOpen || variantsOpen;
|
|
1979
2257
|
const railOpen = expanded || hovered || focused && instant || open || picking;
|
|
1980
|
-
|
|
2258
|
+
useEffect4(() => {
|
|
1981
2259
|
if (!railOpen || !instant) return;
|
|
1982
2260
|
const active = rootRef.current?.getRootNode()?.activeElement;
|
|
1983
2261
|
if (active === fabRef.current) rootRef.current?.querySelector(".isotone-rail-button")?.focus();
|
|
@@ -1986,7 +2264,7 @@ function IterationPanel({
|
|
|
1986
2264
|
const openNotes = useMemo3(() => notes.filter((n) => !n.done), [notes]);
|
|
1987
2265
|
const pages = useMemo3(() => {
|
|
1988
2266
|
const counts = /* @__PURE__ */ new Map();
|
|
1989
|
-
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);
|
|
1990
2268
|
const others = [...counts.keys()].filter((r) => r !== route).sort();
|
|
1991
2269
|
return { counts, others };
|
|
1992
2270
|
}, [openNotes, route]);
|
|
@@ -1994,7 +2272,8 @@ function IterationPanel({
|
|
|
1994
2272
|
const chosen = (scope ?? [route]).filter((r) => r === route || pages.counts.has(r));
|
|
1995
2273
|
return new Set(chosen.length ? chosen : [route]);
|
|
1996
2274
|
}, [scope, route, pages]);
|
|
1997
|
-
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]);
|
|
1998
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]);
|
|
1999
2278
|
function togglePage(page) {
|
|
2000
2279
|
const next = new Set(selectedPages);
|
|
@@ -2194,10 +2473,10 @@ function IterationPanel({
|
|
|
2194
2473
|
finish();
|
|
2195
2474
|
}
|
|
2196
2475
|
}
|
|
2197
|
-
|
|
2476
|
+
useEffect4(() => {
|
|
2198
2477
|
loadNotes();
|
|
2199
2478
|
}, [notesOpen, variantsOpen, route]);
|
|
2200
|
-
|
|
2479
|
+
useEffect4(() => {
|
|
2201
2480
|
if (!notesAvailable || !visible) return;
|
|
2202
2481
|
let stopped = false;
|
|
2203
2482
|
let running = false;
|
|
@@ -2246,13 +2525,13 @@ function IterationPanel({
|
|
|
2246
2525
|
window.removeEventListener("focus", wake);
|
|
2247
2526
|
};
|
|
2248
2527
|
}, [notesAvailable, visible, store]);
|
|
2249
|
-
|
|
2528
|
+
useEffect4(() => {
|
|
2250
2529
|
if (variants && notesOpen && !variantsOpen) refreshSource(variants);
|
|
2251
2530
|
}, [notesOpen, variantsOpen, variants]);
|
|
2252
|
-
|
|
2531
|
+
useEffect4(() => {
|
|
2253
2532
|
if (variants && !variantsAvailable) refreshSource(variants);
|
|
2254
2533
|
}, [variants]);
|
|
2255
|
-
|
|
2534
|
+
useEffect4(() => {
|
|
2256
2535
|
if (draftRestored.current === draftKey) return;
|
|
2257
2536
|
draftRestored.current = draftKey;
|
|
2258
2537
|
restoringDraft.current = true;
|
|
@@ -2277,7 +2556,7 @@ function IterationPanel({
|
|
|
2277
2556
|
} catch {
|
|
2278
2557
|
}
|
|
2279
2558
|
}, [draftKey]);
|
|
2280
|
-
|
|
2559
|
+
useEffect4(() => {
|
|
2281
2560
|
if (restoringDraft.current) {
|
|
2282
2561
|
restoringDraft.current = false;
|
|
2283
2562
|
return;
|
|
@@ -2295,7 +2574,7 @@ function IterationPanel({
|
|
|
2295
2574
|
} catch {
|
|
2296
2575
|
}
|
|
2297
2576
|
}, [text, targets, variantTarget, editing, draftKey]);
|
|
2298
|
-
|
|
2577
|
+
useEffect4(() => {
|
|
2299
2578
|
const el = listRef.current;
|
|
2300
2579
|
if (el) el.scrollTop = el.scrollHeight;
|
|
2301
2580
|
}, [notes, notesOpen]);
|
|
@@ -2306,7 +2585,7 @@ function IterationPanel({
|
|
|
2306
2585
|
ensurePanelStyles(root);
|
|
2307
2586
|
return observeTheme(setTheme, themePreference);
|
|
2308
2587
|
}, [visible, open, route, themePreference]);
|
|
2309
|
-
|
|
2588
|
+
useEffect4(() => {
|
|
2310
2589
|
if (!visible) return;
|
|
2311
2590
|
const onKey = (e) => {
|
|
2312
2591
|
rootRef.current?.setAttribute("data-instant", "true");
|
|
@@ -2327,9 +2606,13 @@ function IterationPanel({
|
|
|
2327
2606
|
startFeedback(true);
|
|
2328
2607
|
}
|
|
2329
2608
|
};
|
|
2330
|
-
const onPointer = () => {
|
|
2609
|
+
const onPointer = (e) => {
|
|
2331
2610
|
rootRef.current?.setAttribute("data-instant", "false");
|
|
2332
2611
|
setInstant(false);
|
|
2612
|
+
if (e.pointerType) {
|
|
2613
|
+
touchRef.current = e.pointerType === "touch";
|
|
2614
|
+
setTouch(touchRef.current);
|
|
2615
|
+
}
|
|
2333
2616
|
};
|
|
2334
2617
|
window.addEventListener("keydown", onKey, true);
|
|
2335
2618
|
window.addEventListener("pointerdown", onPointer, true);
|
|
@@ -2338,7 +2621,7 @@ function IterationPanel({
|
|
|
2338
2621
|
window.removeEventListener("pointerdown", onPointer, true);
|
|
2339
2622
|
};
|
|
2340
2623
|
});
|
|
2341
|
-
|
|
2624
|
+
useEffect4(() => {
|
|
2342
2625
|
if (!open || picking) return;
|
|
2343
2626
|
const onKey = (e) => {
|
|
2344
2627
|
if (e.key !== "Escape" || e.defaultPrevented) return;
|
|
@@ -2349,7 +2632,7 @@ function IterationPanel({
|
|
|
2349
2632
|
window.addEventListener("keydown", onKey);
|
|
2350
2633
|
return () => window.removeEventListener("keydown", onKey);
|
|
2351
2634
|
});
|
|
2352
|
-
|
|
2635
|
+
useEffect4(() => {
|
|
2353
2636
|
if (!picking) return;
|
|
2354
2637
|
const cursor = document.body.style.cursor;
|
|
2355
2638
|
document.body.style.cursor = "crosshair";
|
|
@@ -2359,6 +2642,7 @@ function IterationPanel({
|
|
|
2359
2642
|
return el && !el.closest("[data-isotone]") ? el : null;
|
|
2360
2643
|
};
|
|
2361
2644
|
const onMove = (e) => {
|
|
2645
|
+
if (e.pointerType === "touch") return;
|
|
2362
2646
|
cancelAnimationFrame(frame);
|
|
2363
2647
|
const deferred = e.shiftKey;
|
|
2364
2648
|
frame = requestAnimationFrame(() => {
|
|
@@ -2389,7 +2673,7 @@ function IterationPanel({
|
|
|
2389
2673
|
const y = pointed ? e.clientY : r.bottom;
|
|
2390
2674
|
if (!anchorRef.current)
|
|
2391
2675
|
slotHeight.current = rootRef.current?.querySelector(".isotone-notes-panel .isotone-input")?.offsetHeight ?? 0;
|
|
2392
|
-
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 });
|
|
2393
2677
|
setNotesOpen(true);
|
|
2394
2678
|
focusWhilePicking();
|
|
2395
2679
|
}
|
|
@@ -2426,19 +2710,19 @@ function IterationPanel({
|
|
|
2426
2710
|
setNotesOpen(true);
|
|
2427
2711
|
focusComposer();
|
|
2428
2712
|
};
|
|
2429
|
-
window.addEventListener("
|
|
2713
|
+
window.addEventListener("pointermove", onMove, true);
|
|
2430
2714
|
window.addEventListener("click", onClick, true);
|
|
2431
2715
|
window.addEventListener("keydown", onKey, true);
|
|
2432
2716
|
return () => {
|
|
2433
2717
|
document.body.style.cursor = cursor;
|
|
2434
2718
|
cancelAnimationFrame(frame);
|
|
2435
2719
|
setHoverRect(null);
|
|
2436
|
-
window.removeEventListener("
|
|
2720
|
+
window.removeEventListener("pointermove", onMove, true);
|
|
2437
2721
|
window.removeEventListener("click", onClick, true);
|
|
2438
2722
|
window.removeEventListener("keydown", onKey, true);
|
|
2439
2723
|
};
|
|
2440
2724
|
}, [picking, focusComposer, focusWhilePicking]);
|
|
2441
|
-
|
|
2725
|
+
useEffect4(() => {
|
|
2442
2726
|
const shadow = rootRef.current?.getRootNode();
|
|
2443
2727
|
if (!(shadow instanceof ShadowRoot)) return;
|
|
2444
2728
|
const stop = (e) => e.stopPropagation();
|
|
@@ -2448,7 +2732,7 @@ function IterationPanel({
|
|
|
2448
2732
|
for (const type of types) shadow.removeEventListener(type, stop);
|
|
2449
2733
|
};
|
|
2450
2734
|
}, []);
|
|
2451
|
-
|
|
2735
|
+
useEffect4(() => {
|
|
2452
2736
|
if (!notesOpen) setAnchor(null);
|
|
2453
2737
|
}, [notesOpen]);
|
|
2454
2738
|
useLayoutEffect4(() => {
|
|
@@ -2465,35 +2749,45 @@ function IterationPanel({
|
|
|
2465
2749
|
frame = void 0;
|
|
2466
2750
|
let { x, y } = anchor;
|
|
2467
2751
|
const el = locate(anchor.selector);
|
|
2468
|
-
|
|
2469
|
-
|
|
2752
|
+
const r = el?.getBoundingClientRect();
|
|
2753
|
+
if (r) {
|
|
2470
2754
|
x = r.left + anchor.dx;
|
|
2471
2755
|
y = r.top + anchor.dy;
|
|
2472
2756
|
}
|
|
2473
2757
|
const vw = document.documentElement.clientWidth || innerWidth;
|
|
2474
2758
|
const vh = innerHeight;
|
|
2759
|
+
const band = visibleBand();
|
|
2760
|
+
const minY = band.top + FLOAT_MARGIN;
|
|
2761
|
+
const maxY = band.bottom - FLOAT_MARGIN;
|
|
2475
2762
|
const w = float.offsetWidth;
|
|
2476
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;
|
|
2477
2767
|
let left = x + FLOAT_GAP;
|
|
2478
|
-
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;
|
|
2479
2773
|
left = Math.max(FLOAT_MARGIN, Math.min(left, vw - w - FLOAT_MARGIN));
|
|
2480
|
-
const fitsBelow =
|
|
2481
|
-
const fitsAbove = y - FLOAT_GAP - h >= FLOAT_MARGIN;
|
|
2774
|
+
const [fitsBelow, fitsAbove] = fits(belowY, aboveY);
|
|
2482
2775
|
if (side === null || (side === "below" ? !fitsBelow && fitsAbove : !fitsAbove && fitsBelow))
|
|
2483
2776
|
side = fitsBelow || !fitsAbove ? "below" : "above";
|
|
2484
2777
|
float.style.left = `${left}px`;
|
|
2778
|
+
let top;
|
|
2485
2779
|
if (side === "below") {
|
|
2486
|
-
|
|
2780
|
+
top = Math.max(minY, Math.min(belowY + FLOAT_GAP, maxY - h));
|
|
2487
2781
|
float.style.top = `${top}px`;
|
|
2488
2782
|
float.style.bottom = "auto";
|
|
2489
|
-
float.style.transformOrigin = `${x - left}px ${y - top}px`;
|
|
2490
2783
|
} else {
|
|
2491
|
-
const bottom = Math.max(
|
|
2784
|
+
const bottom = Math.max(vh - maxY, Math.min(vh - aboveY + FLOAT_GAP, vh - h - minY));
|
|
2492
2785
|
float.style.bottom = `${bottom}px`;
|
|
2493
2786
|
float.style.top = "auto";
|
|
2494
|
-
|
|
2787
|
+
top = vh - bottom - h;
|
|
2495
2788
|
}
|
|
2496
|
-
|
|
2789
|
+
float.style.transformOrigin = `${x - left}px ${y - top}px`;
|
|
2790
|
+
lastFloat.current = { left, top };
|
|
2497
2791
|
};
|
|
2498
2792
|
const schedule = () => {
|
|
2499
2793
|
frame ??= requestAnimationFrame(place);
|
|
@@ -2517,12 +2811,17 @@ function IterationPanel({
|
|
|
2517
2811
|
observer?.observe(float);
|
|
2518
2812
|
window.addEventListener("scroll", schedule, { capture: true, passive: true });
|
|
2519
2813
|
window.addEventListener("resize", schedule);
|
|
2814
|
+
const viewport = window.visualViewport;
|
|
2815
|
+
viewport?.addEventListener("resize", schedule);
|
|
2816
|
+
viewport?.addEventListener("scroll", schedule);
|
|
2520
2817
|
return () => {
|
|
2521
2818
|
if (frame !== void 0) cancelAnimationFrame(frame);
|
|
2522
2819
|
glide?.cancel();
|
|
2523
2820
|
observer?.disconnect();
|
|
2524
2821
|
window.removeEventListener("scroll", schedule, true);
|
|
2525
2822
|
window.removeEventListener("resize", schedule);
|
|
2823
|
+
viewport?.removeEventListener("resize", schedule);
|
|
2824
|
+
viewport?.removeEventListener("scroll", schedule);
|
|
2526
2825
|
};
|
|
2527
2826
|
}, [anchor, locate]);
|
|
2528
2827
|
useLayoutEffect4(() => {
|
|
@@ -2551,11 +2850,11 @@ function IterationPanel({
|
|
|
2551
2850
|
ghost.remove();
|
|
2552
2851
|
};
|
|
2553
2852
|
}, [floating]);
|
|
2554
|
-
|
|
2853
|
+
useEffect4(() => {
|
|
2555
2854
|
const kept = new Set(targets.map((t) => t.selector));
|
|
2556
2855
|
for (const selector of pinnedElements.current.keys()) if (!kept.has(selector)) pinnedElements.current.delete(selector);
|
|
2557
2856
|
}, [targets]);
|
|
2558
|
-
|
|
2857
|
+
useEffect4(() => {
|
|
2559
2858
|
if (!picking && !floating) {
|
|
2560
2859
|
setRects([]);
|
|
2561
2860
|
return;
|
|
@@ -2621,7 +2920,8 @@ function IterationPanel({
|
|
|
2621
2920
|
onFeedback: notesAvailable ? feedback : void 0,
|
|
2622
2921
|
onSaved: loadNotes,
|
|
2623
2922
|
onManage: notesAvailable ? queueRemoval : void 0,
|
|
2624
|
-
pendingActions
|
|
2923
|
+
pendingActions,
|
|
2924
|
+
emptyText: variantsEmptyText
|
|
2625
2925
|
}
|
|
2626
2926
|
)
|
|
2627
2927
|
]
|
|
@@ -2643,6 +2943,11 @@ function IterationPanel({
|
|
|
2643
2943
|
textareaRef.current?.focus({ preventScroll: true });
|
|
2644
2944
|
},
|
|
2645
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
|
+
] }),
|
|
2646
2951
|
/* @__PURE__ */ jsxs4("div", { className: "isotone-context", "aria-live": "polite", children: [
|
|
2647
2952
|
editing && /* @__PURE__ */ jsxs4("span", { className: "isotone-chip", children: [
|
|
2648
2953
|
"Editing note",
|
|
@@ -2742,7 +3047,7 @@ function IterationPanel({
|
|
|
2742
3047
|
children: /* @__PURE__ */ jsx5(Icon2, { name: "pin", size: 14 })
|
|
2743
3048
|
}
|
|
2744
3049
|
),
|
|
2745
|
-
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"] }),
|
|
2746
3051
|
/* @__PURE__ */ jsx5(
|
|
2747
3052
|
"button",
|
|
2748
3053
|
{
|
|
@@ -2759,8 +3064,8 @@ function IterationPanel({
|
|
|
2759
3064
|
picking && !floating && /* @__PURE__ */ jsxs4("div", { ref: fadeIn, className: "isotone-picking", "data-still": pickingWithPanel, children: [
|
|
2760
3065
|
/* @__PURE__ */ jsx5(Icon2, { name: "pin", size: 14 }),
|
|
2761
3066
|
/* @__PURE__ */ jsxs4("p", { children: [
|
|
2762
|
-
/* @__PURE__ */ jsx5("span", { children: targets.length ? `${targets.length} selected` :
|
|
2763
|
-
/* @__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 })
|
|
2764
3069
|
] }),
|
|
2765
3070
|
/* @__PURE__ */ jsxs4(
|
|
2766
3071
|
"button",
|
|
@@ -2772,8 +3077,8 @@ function IterationPanel({
|
|
|
2772
3077
|
focusComposer();
|
|
2773
3078
|
},
|
|
2774
3079
|
children: [
|
|
2775
|
-
"Done
|
|
2776
|
-
/* @__PURE__ */ jsx5("kbd", { children: "\u21B5" })
|
|
3080
|
+
"Done",
|
|
3081
|
+
!touch && /* @__PURE__ */ jsx5("kbd", { children: "\u21B5" })
|
|
2777
3082
|
]
|
|
2778
3083
|
}
|
|
2779
3084
|
)
|
|
@@ -2850,7 +3155,14 @@ function IterationPanel({
|
|
|
2850
3155
|
"data-motion-key": note.id,
|
|
2851
3156
|
className: `isotone-note${editing?.id === note.id ? " isotone-note-editing" : ""}`,
|
|
2852
3157
|
children: [
|
|
2853
|
-
|
|
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(
|
|
2854
3166
|
"button",
|
|
2855
3167
|
{
|
|
2856
3168
|
type: "button",
|
|
@@ -2868,8 +3180,8 @@ function IterationPanel({
|
|
|
2868
3180
|
{
|
|
2869
3181
|
type: "button",
|
|
2870
3182
|
className: "isotone-x isotone-icon",
|
|
2871
|
-
"aria-label": note.variantAction ? "Cancel removal" : "Delete note",
|
|
2872
|
-
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",
|
|
2873
3185
|
onClick: () => void removeNote(note),
|
|
2874
3186
|
children: /* @__PURE__ */ jsx5(Icon2, { name: "close", size: 12 })
|
|
2875
3187
|
}
|
|
@@ -2912,6 +3224,7 @@ function IterationPanel({
|
|
|
2912
3224
|
"data-tools": Number(Boolean(variants)) + Number(notesAvailable),
|
|
2913
3225
|
"data-instant": instant,
|
|
2914
3226
|
"data-notes-open": notesOpen,
|
|
3227
|
+
"data-selecting": picking || floating,
|
|
2915
3228
|
"data-working": working.size > 0,
|
|
2916
3229
|
"data-open": open,
|
|
2917
3230
|
className: "isotone-root",
|
|
@@ -2952,7 +3265,16 @@ function IterationPanel({
|
|
|
2952
3265
|
children: composer
|
|
2953
3266
|
}
|
|
2954
3267
|
),
|
|
2955
|
-
/* @__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
|
+
) }),
|
|
2956
3278
|
/* @__PURE__ */ jsxs4(
|
|
2957
3279
|
"fieldset",
|
|
2958
3280
|
{
|
|
@@ -3077,9 +3399,10 @@ function withDocumentUntouched(create) {
|
|
|
3077
3399
|
}
|
|
3078
3400
|
return result;
|
|
3079
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.";
|
|
3080
3403
|
function mountIteration(opts) {
|
|
3081
3404
|
const getRoute = opts.getRoute ?? (() => window.location.pathname);
|
|
3082
|
-
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;
|
|
3083
3406
|
if (!opts.endpoint && !variants) return () => {
|
|
3084
3407
|
};
|
|
3085
3408
|
if (!opts.endpoint && variants && variants.getAxes().length === 0 && typeof variants.refresh !== "function") {
|
|
@@ -3111,6 +3434,7 @@ function mountIteration(opts) {
|
|
|
3111
3434
|
rule: opts.rule,
|
|
3112
3435
|
getPayload: opts.getPayload,
|
|
3113
3436
|
variants,
|
|
3437
|
+
variantsEmptyText: opts.variants === "knobs" ? KNOBS_EMPTY_TEXT : void 0,
|
|
3114
3438
|
defaultTab: opts.defaultTab,
|
|
3115
3439
|
shortcut: opts.shortcut,
|
|
3116
3440
|
updateEndpoint: opts.updateEndpoint,
|
|
@@ -3157,8 +3481,8 @@ function mountIteration(opts) {
|
|
|
3157
3481
|
}, 0);
|
|
3158
3482
|
};
|
|
3159
3483
|
}
|
|
3160
|
-
function overlaySourceWithSave(getRoute, endpoint, key) {
|
|
3161
|
-
const source = createOverlayVariantsSource({ endpoint:
|
|
3484
|
+
function overlaySourceWithSave(getRoute, endpoint, key, variantsEndpoint = VARIANTS_ENDPOINT) {
|
|
3485
|
+
const source = createOverlayVariantsSource({ endpoint: variantsEndpoint, getRoute });
|
|
3162
3486
|
if (!endpoint) return source;
|
|
3163
3487
|
const notes = createIterationStore({ endpoint, key });
|
|
3164
3488
|
source.saveDefaults = async (selections) => {
|
|
@@ -3167,7 +3491,8 @@ function overlaySourceWithSave(getRoute, endpoint, key) {
|
|
|
3167
3491
|
if (!id || id === OVERLAY_DEFAULT_OPTION) return [];
|
|
3168
3492
|
const variant = source.list?.variants.find((v) => v.id === id);
|
|
3169
3493
|
const label = variant?.label ?? id;
|
|
3170
|
-
const
|
|
3494
|
+
const selector = axisSection(axis).fullLabel;
|
|
3495
|
+
const named = selector === label || axis.label === label ? label : `${selector}: ${label}`;
|
|
3171
3496
|
return [{ id, named, element: variant?.element ?? null }];
|
|
3172
3497
|
});
|
|
3173
3498
|
const moved = knobDelta(source.knobs.axes, source.knobs.values);
|
|
@@ -3176,7 +3501,7 @@ function overlaySourceWithSave(getRoute, endpoint, key) {
|
|
|
3176
3501
|
parts.push(`Make the default: ${picked.map((p) => `"${p.named}" (${p.id})`).join("; ")}.`);
|
|
3177
3502
|
}
|
|
3178
3503
|
if (moved.length) {
|
|
3179
|
-
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("; ")}.`);
|
|
3180
3505
|
}
|
|
3181
3506
|
const feedback = parts.length ? `[set-defaults] ${parts.join(" ")}` : "[set-defaults] The current defaults stay on this route.";
|
|
3182
3507
|
await notes.saveNote(
|
|
@@ -3199,10 +3524,12 @@ export {
|
|
|
3199
3524
|
OVERLAY_DEFAULT_OPTION,
|
|
3200
3525
|
OVERLAY_GROUP_ID,
|
|
3201
3526
|
PANEL_CSS,
|
|
3527
|
+
SECTION_SEPARATOR,
|
|
3202
3528
|
VARIANTS_ENDPOINT,
|
|
3203
3529
|
VariantsView,
|
|
3204
3530
|
applyKnobValues,
|
|
3205
3531
|
axisDefault,
|
|
3532
|
+
axisSection,
|
|
3206
3533
|
createOverlayVariantsSource,
|
|
3207
3534
|
createVariantsSource,
|
|
3208
3535
|
defaultSelections,
|
|
@@ -3222,7 +3549,9 @@ export {
|
|
|
3222
3549
|
overlayAxes,
|
|
3223
3550
|
overlayGroups,
|
|
3224
3551
|
parseVariantParam,
|
|
3552
|
+
sectionAxes,
|
|
3225
3553
|
serializeVariantParam,
|
|
3554
|
+
splitSectionLabel,
|
|
3226
3555
|
visibleAxes
|
|
3227
3556
|
};
|
|
3228
3557
|
/*! Bundled license information:
|