isotone 1.0.1 → 1.0.3
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 +7 -4
- package/CHANGELOG.md +8 -0
- package/README.md +2 -2
- package/dist/{chunk-3BVAXPSO.js → chunk-QDOV7KYU.js} +2 -2
- package/dist/{chunk-3BVAXPSO.js.map → chunk-QDOV7KYU.js.map} +1 -1
- package/dist/cli.js +12 -4
- package/dist/cli.js.map +1 -1
- package/dist/index.js +83 -24
- package/dist/index.js.map +1 -1
- package/dist/isotone.global.js +15 -16
- package/dist/serve.js +1 -1
- package/dist/vite-plugin.js +1 -1
- package/docs/integrations.md +4 -2
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -45,7 +45,7 @@ function showOverflowTitle(event) {
|
|
|
45
45
|
}
|
|
46
46
|
|
|
47
47
|
// src/react/IterationPanel.tsx
|
|
48
|
-
import { useCallback, useEffect as useEffect3, useId as useId2, useLayoutEffect as
|
|
48
|
+
import { useCallback, useEffect as useEffect3, useId as useId2, useLayoutEffect as useLayoutEffect4, useMemo as useMemo3, useRef as useRef4, useState as useState3 } from "react";
|
|
49
49
|
|
|
50
50
|
// src/react/appearance.ts
|
|
51
51
|
var darkPalette = {
|
|
@@ -788,14 +788,14 @@ ${APPEARANCE_CSS}
|
|
|
788
788
|
.isotone-root .isotone-vars > .isotone-panel-head:has(.isotone-save) { position: relative; z-index: 1; margin-top: calc(0px - var(--iso-space)); background: linear-gradient(to bottom, transparent, var(--iso-bg) var(--iso-space)); }
|
|
789
789
|
.isotone-root .isotone-vars > .isotone-panel-head:not(:has(.isotone-save)) { display: none; }
|
|
790
790
|
.isotone-root .isotone-vars > .isotone-panel-head :is(h2, .isotone-icon) { display: none; }
|
|
791
|
-
.isotone-root .isotone-axis-menu { top: 6px;
|
|
791
|
+
.isotone-root .isotone-axis-menu { top: var(--iso-menu-top, 6px); max-height: calc(100% - var(--iso-menu-top, 6px) - 6px); }
|
|
792
792
|
.isotone-root .isotone-save-state { top: auto; bottom: 27px; }
|
|
793
793
|
.isotone-panel-head h2 { font-size: 12px; font-weight: 600; letter-spacing: -.01em; }
|
|
794
794
|
.isotone-head-actions { display: flex; align-items: center; gap: 6px; margin-right: auto; }
|
|
795
795
|
.isotone-icon { width: 28px; height: 28px; display: inline-grid; place-items: center; flex: none; color: var(--iso-muted); border-radius: 6px; }
|
|
796
796
|
.isotone-count { margin-left: 5px; color: var(--iso-muted); font-size: 10px; font-variant-numeric: tabular-nums; }
|
|
797
797
|
.isotone-vars { flex: 1; display: flex; flex-direction: column; min-height: 0; position: relative; }
|
|
798
|
-
.isotone-
|
|
798
|
+
.isotone-vars { min-height: min(var(--iso-menu-need, 0px), calc(100dvh - 76px)); }
|
|
799
799
|
.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); }
|
|
800
800
|
/* The footer overlaps only the scroller's bottom padding: content fades beneath
|
|
801
801
|
it while scrolling, but the last control remains fully visible at the end. */
|
|
@@ -863,7 +863,7 @@ ${PILL_ACTIVE_CSS}
|
|
|
863
863
|
Cancelling margin/padding keeps the first row in place at scroll start; the
|
|
864
864
|
expanded height preserves the content allowance and composer position.
|
|
865
865
|
The side gutters contain the corner action and keyboard focus outlines. */
|
|
866
|
-
.isotone-list { display: flex; flex-direction: column; gap: 0; flex: 0 1 auto; max-height: calc(var(--iso-list-height) + 2 * var(--iso-space) - 8px); min-height: 0; overflow-y: auto; overscroll-behavior: contain; margin: calc(0px - var(--iso-space)) calc(-8px - var(--iso-note-outset)); padding: var(--iso-space) 8px; scroll-padding-block: var(--iso-space); mask-image: linear-gradient(to bottom, transparent, #000 var(--iso-space), #000 calc(100% - var(--iso-space)), transparent); }
|
|
866
|
+
.isotone-list { display: flex; flex-direction: column; gap: 0; flex: 0 1 auto; max-height: calc(var(--iso-list-height) + 2 * var(--iso-space) - 8px); min-height: 0; overflow-y: auto; overscroll-behavior: contain; margin: calc(0px - var(--iso-space)) calc(-8px - var(--iso-note-outset)) calc(-2px - var(--iso-space)); padding: var(--iso-space) 8px; scroll-padding-block: var(--iso-space); mask-image: linear-gradient(to bottom, transparent, #000 var(--iso-space), #000 calc(100% - var(--iso-space)), transparent); }
|
|
867
867
|
.isotone-empty { color: var(--iso-muted); font-size: 11px; line-height: 1.7; padding: 8px; }
|
|
868
868
|
.isotone-note { position: relative; flex: none; }
|
|
869
869
|
.isotone-request-heading { display: flex; align-items: center; gap: 8px; line-height: 14px; }
|
|
@@ -895,14 +895,13 @@ ${FEEDBACK_LAYOUT_CSS}
|
|
|
895
895
|
.isotone-context > .isotone-chip:not(:only-child) { max-width: calc(100% - 32px); }
|
|
896
896
|
.isotone-chip > span { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
897
897
|
.isotone-chip button { display: grid; place-items: center; flex: none; width: 16px; height: 16px; border-radius: 3px; background: transparent; box-shadow: none; }
|
|
898
|
-
.isotone-root .isotone-textarea { --iso-textarea-inset: 8px; display: block; width: 100%; flex: none; height:
|
|
898
|
+
.isotone-root .isotone-textarea { --iso-textarea-inset: 8px; display: block; width: 100%; flex: none; height: 22px; min-height: calc(22px + var(--iso-textarea-inset)); max-height: calc(120px + var(--iso-textarea-inset)); overflow-y: auto; resize: none; margin-top: calc(0px - var(--iso-textarea-inset)); padding: calc(var(--iso-textarea-inset) + 1px) 1px 1px; scrollbar-width: thin; scrollbar-color: color-mix(in srgb, var(--iso-fg) 14%, transparent) transparent; color: var(--iso-fg); background: transparent; border: 0; border-radius: 0; font-size: 12px; line-height: 20px; box-shadow: none; cursor: text; }
|
|
899
899
|
/* Text scrolls through the gap below tags, or the field inset without tags. */
|
|
900
900
|
.isotone-root .isotone-context:empty + .isotone-textarea { --iso-textarea-inset: var(--iso-input-padding); }
|
|
901
901
|
.isotone-textarea::placeholder { color: var(--iso-faint); }
|
|
902
902
|
.isotone-root .isotone-textarea:focus-visible { outline: none; }
|
|
903
903
|
.isotone-textarea[data-scrolled='true'] { mask-image: linear-gradient(to bottom, transparent, #000 8px); }
|
|
904
904
|
.isotone-toolbar { display: flex; flex: none; align-items: center; gap: 7px; height: 28px; }
|
|
905
|
-
.isotone-key-hint { color: var(--iso-faint); font-size: 9px; }
|
|
906
905
|
.isotone-pick[aria-pressed='true'] { color: var(--iso-fg); }
|
|
907
906
|
.isotone-pick, .isotone-send { position: relative; width: 24px; height: 24px; }
|
|
908
907
|
/* Keep a 28px hit area around the quieter composer faces. */
|
|
@@ -949,7 +948,7 @@ function ensurePanelStyles(target) {
|
|
|
949
948
|
}
|
|
950
949
|
|
|
951
950
|
// src/react/VariantsView.tsx
|
|
952
|
-
import { useEffect as useEffect2, useMemo as useMemo2, useRef as useRef3, useState as useState2 } from "react";
|
|
951
|
+
import { useEffect as useEffect2, useLayoutEffect as useLayoutEffect2, useMemo as useMemo2, useRef as useRef3, useState as useState2 } from "react";
|
|
953
952
|
|
|
954
953
|
// src/react/useLayoutMotion.ts
|
|
955
954
|
import { useLayoutEffect, useRef as useRef2 } from "react";
|
|
@@ -1084,6 +1083,45 @@ function VariantsView({
|
|
|
1084
1083
|
useEffect2(() => {
|
|
1085
1084
|
if (!menu) setMenuOpen(false);
|
|
1086
1085
|
}, [menu]);
|
|
1086
|
+
useLayoutEffect2(() => {
|
|
1087
|
+
const root = bodyRef.current?.parentElement;
|
|
1088
|
+
const dialog = menuRef.current;
|
|
1089
|
+
const scroll = dialog?.querySelector(".isotone-menu-scroll");
|
|
1090
|
+
if (!root) return;
|
|
1091
|
+
const clear = () => {
|
|
1092
|
+
root.style.removeProperty("--iso-menu-need");
|
|
1093
|
+
root.style.removeProperty("--iso-menu-top");
|
|
1094
|
+
};
|
|
1095
|
+
if (!menuOpen || !dialog || !scroll) {
|
|
1096
|
+
clear();
|
|
1097
|
+
return;
|
|
1098
|
+
}
|
|
1099
|
+
const place = () => {
|
|
1100
|
+
const natural = dialog.offsetHeight + scroll.scrollHeight - scroll.clientHeight;
|
|
1101
|
+
root.style.setProperty("--iso-menu-need", `${natural + 12}px`);
|
|
1102
|
+
const heading = trigger.current?.isConnected ? trigger.current.getBoundingClientRect().top : 0;
|
|
1103
|
+
const top = heading - root.getBoundingClientRect().top - natural - 6;
|
|
1104
|
+
root.style.setProperty("--iso-menu-top", `${Number.isFinite(top) && top >= 6 ? top : 6}px`);
|
|
1105
|
+
};
|
|
1106
|
+
place();
|
|
1107
|
+
window.addEventListener("resize", place);
|
|
1108
|
+
const observer = typeof ResizeObserver === "undefined" ? null : new ResizeObserver(place);
|
|
1109
|
+
observer?.observe(scroll);
|
|
1110
|
+
return () => {
|
|
1111
|
+
window.removeEventListener("resize", place);
|
|
1112
|
+
observer?.disconnect();
|
|
1113
|
+
clear();
|
|
1114
|
+
};
|
|
1115
|
+
}, [menuOpen, menu, menuOptions.length, error]);
|
|
1116
|
+
useEffect2(() => {
|
|
1117
|
+
if (!menuOpen) return;
|
|
1118
|
+
const onPointerDown = (e) => {
|
|
1119
|
+
const dialog = menuRef.current;
|
|
1120
|
+
if (dialog && !e.composedPath().includes(dialog)) closeMenu();
|
|
1121
|
+
};
|
|
1122
|
+
window.addEventListener("pointerdown", onPointerDown, true);
|
|
1123
|
+
return () => window.removeEventListener("pointerdown", onPointerDown, true);
|
|
1124
|
+
}, [menuOpen]);
|
|
1087
1125
|
useEffect2(() => {
|
|
1088
1126
|
if (menuOpen)
|
|
1089
1127
|
menuRef.current?.querySelector("button")?.focus();
|
|
@@ -1145,7 +1183,7 @@ function VariantsView({
|
|
|
1145
1183
|
node?.toggleAttribute("inert", menuOpen);
|
|
1146
1184
|
}, children: [
|
|
1147
1185
|
sourceError && /* @__PURE__ */ jsx3("p", { className: "isotone-danger", role: "alert", children: sourceError }),
|
|
1148
|
-
shown.length === 0 && /* @__PURE__ */ jsx3("p", { className: "isotone-empty", children: "No
|
|
1186
|
+
shown.length === 0 && /* @__PURE__ */ jsx3("p", { className: "isotone-empty", children: "No variants on this page yet. Ask your agent to create variants of any design and they will appear here." }),
|
|
1149
1187
|
shown.map((axis) => {
|
|
1150
1188
|
const thumbnails = axis.kind !== "range" && axis.kind !== "color" && axis.options.length > 0 && axis.options.every((option) => option.image);
|
|
1151
1189
|
return /* @__PURE__ */ jsxs2(
|
|
@@ -1598,9 +1636,9 @@ function NoteSummary({ note }) {
|
|
|
1598
1636
|
}
|
|
1599
1637
|
|
|
1600
1638
|
// src/react/useDockMotion.ts
|
|
1601
|
-
import { useLayoutEffect as
|
|
1639
|
+
import { useLayoutEffect as useLayoutEffect3 } from "react";
|
|
1602
1640
|
function useDockMotion(ref, visible) {
|
|
1603
|
-
|
|
1641
|
+
useLayoutEffect3(() => {
|
|
1604
1642
|
const root = ref.current;
|
|
1605
1643
|
if (!root || !visible) return;
|
|
1606
1644
|
const previous = /* @__PURE__ */ new Map();
|
|
@@ -1734,7 +1772,6 @@ function IterationPanel({
|
|
|
1734
1772
|
const pendingWrites = useRef4(0);
|
|
1735
1773
|
const noteRequest = useRef4(0);
|
|
1736
1774
|
const [editing, setEditing] = useState3(null);
|
|
1737
|
-
const [unsynced, setUnsynced] = useState3(false);
|
|
1738
1775
|
const [notice, setNotice] = useState3("");
|
|
1739
1776
|
const [theme, setTheme] = useState3(() => detectTheme(themePreference));
|
|
1740
1777
|
const [instant, setInstant] = useState3(false);
|
|
@@ -1755,13 +1792,13 @@ function IterationPanel({
|
|
|
1755
1792
|
variants && visibleAxes(variants.getAxes(), variants.getSelections()).length
|
|
1756
1793
|
);
|
|
1757
1794
|
const visible = isRouteVisible(route, rule) && (notesAvailable || variantsAvailable || variantsOpen);
|
|
1758
|
-
const hasContext = Boolean(editing || variantTarget || targets.length || notice
|
|
1759
|
-
|
|
1795
|
+
const hasContext = Boolean(editing || variantTarget || targets.length || notice);
|
|
1796
|
+
useLayoutEffect4(() => {
|
|
1760
1797
|
const field = textareaRef.current;
|
|
1761
1798
|
if (!field || !notesOpen || !visible) return;
|
|
1762
1799
|
const resize = () => {
|
|
1763
1800
|
field.style.height = "0px";
|
|
1764
|
-
field.style.height = `${text ? Math.max(
|
|
1801
|
+
field.style.height = `${text ? Math.max(22, field.scrollHeight) : 22}px`;
|
|
1765
1802
|
field.dataset.scrolled = String(field.scrollTop > 0);
|
|
1766
1803
|
};
|
|
1767
1804
|
resize();
|
|
@@ -1827,7 +1864,6 @@ function IterationPanel({
|
|
|
1827
1864
|
void store.listNotes().then((fresh) => {
|
|
1828
1865
|
if (request !== noteRequest.current || revision !== noteRevision.current || pendingWrites.current) return;
|
|
1829
1866
|
setNotes(fresh);
|
|
1830
|
-
setUnsynced(store.hasUnsyncedNotes);
|
|
1831
1867
|
}).catch(() => {
|
|
1832
1868
|
});
|
|
1833
1869
|
}
|
|
@@ -1839,6 +1875,15 @@ function IterationPanel({
|
|
|
1839
1875
|
pendingWrites.current--;
|
|
1840
1876
|
};
|
|
1841
1877
|
}
|
|
1878
|
+
const keepPicking = useRef4(false);
|
|
1879
|
+
const focusWhilePicking = useCallback(() => {
|
|
1880
|
+
cancelAnimationFrame(focusFrame.current);
|
|
1881
|
+
focusFrame.current = requestAnimationFrame(() => {
|
|
1882
|
+
keepPicking.current = true;
|
|
1883
|
+
textareaRef.current?.focus();
|
|
1884
|
+
keepPicking.current = false;
|
|
1885
|
+
});
|
|
1886
|
+
}, []);
|
|
1842
1887
|
const focusComposer = useCallback(() => {
|
|
1843
1888
|
cancelAnimationFrame(focusFrame.current);
|
|
1844
1889
|
focusFrame.current = requestAnimationFrame(
|
|
@@ -1894,7 +1939,6 @@ function IterationPanel({
|
|
|
1894
1939
|
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1895
1940
|
...context
|
|
1896
1941
|
}]);
|
|
1897
|
-
setUnsynced(store.hasUnsyncedNotes);
|
|
1898
1942
|
setNotesOpen(true);
|
|
1899
1943
|
} finally {
|
|
1900
1944
|
finish();
|
|
@@ -1949,7 +1993,6 @@ function IterationPanel({
|
|
|
1949
1993
|
setNotes(
|
|
1950
1994
|
(prev) => prev.map((n) => n.id === tempId ? { ...n, id: savedId } : n)
|
|
1951
1995
|
);
|
|
1952
|
-
setUnsynced(store.hasUnsyncedNotes);
|
|
1953
1996
|
} catch {
|
|
1954
1997
|
setNotes(
|
|
1955
1998
|
(prev) => previous ? prev.map((n) => n.id === tempId ? previous : n) : prev.filter((n) => n.id !== tempId)
|
|
@@ -2038,7 +2081,7 @@ function IterationPanel({
|
|
|
2038
2081
|
const el = listRef.current;
|
|
2039
2082
|
if (el) el.scrollTop = el.scrollHeight;
|
|
2040
2083
|
}, [notes, notesOpen]);
|
|
2041
|
-
|
|
2084
|
+
useLayoutEffect4(() => {
|
|
2042
2085
|
if (!visible) return;
|
|
2043
2086
|
const root = rootRef.current?.getRootNode();
|
|
2044
2087
|
if (root instanceof ShadowRoot || root instanceof Document)
|
|
@@ -2098,8 +2141,9 @@ function IterationPanel({
|
|
|
2098
2141
|
};
|
|
2099
2142
|
const onMove = (e) => {
|
|
2100
2143
|
cancelAnimationFrame(frame);
|
|
2144
|
+
const deferred = e.shiftKey;
|
|
2101
2145
|
frame = requestAnimationFrame(() => {
|
|
2102
|
-
const el = elementAt(e);
|
|
2146
|
+
const el = deferred ? null : elementAt(e);
|
|
2103
2147
|
setHoverRect(el ? el.getBoundingClientRect() : null);
|
|
2104
2148
|
});
|
|
2105
2149
|
};
|
|
@@ -2110,11 +2154,13 @@ function IterationPanel({
|
|
|
2110
2154
|
);
|
|
2111
2155
|
};
|
|
2112
2156
|
const onClick = (e) => {
|
|
2157
|
+
if (e.shiftKey) return;
|
|
2113
2158
|
const el = elementAt(e);
|
|
2114
2159
|
if (!el) return;
|
|
2115
2160
|
e.preventDefault();
|
|
2116
2161
|
e.stopImmediatePropagation();
|
|
2117
2162
|
toggleTarget(el);
|
|
2163
|
+
focusWhilePicking();
|
|
2118
2164
|
};
|
|
2119
2165
|
const onKey = (e) => {
|
|
2120
2166
|
if (e.key === " ") {
|
|
@@ -2145,7 +2191,17 @@ function IterationPanel({
|
|
|
2145
2191
|
window.removeEventListener("click", onClick, true);
|
|
2146
2192
|
window.removeEventListener("keydown", onKey, true);
|
|
2147
2193
|
};
|
|
2148
|
-
}, [picking, focusComposer]);
|
|
2194
|
+
}, [picking, focusComposer, focusWhilePicking]);
|
|
2195
|
+
useEffect3(() => {
|
|
2196
|
+
const shadow = rootRef.current?.getRootNode();
|
|
2197
|
+
if (!(shadow instanceof ShadowRoot)) return;
|
|
2198
|
+
const stop = (e) => e.stopPropagation();
|
|
2199
|
+
const types = ["pointerdown", "pointerup", "mousedown", "mouseup", "touchstart", "touchend", "click"];
|
|
2200
|
+
for (const type of types) shadow.addEventListener(type, stop);
|
|
2201
|
+
return () => {
|
|
2202
|
+
for (const type of types) shadow.removeEventListener(type, stop);
|
|
2203
|
+
};
|
|
2204
|
+
}, []);
|
|
2149
2205
|
useEffect3(() => {
|
|
2150
2206
|
if (!picking) {
|
|
2151
2207
|
setRects([]);
|
|
@@ -2347,7 +2403,7 @@ function IterationPanel({
|
|
|
2347
2403
|
},
|
|
2348
2404
|
target.selector
|
|
2349
2405
|
)),
|
|
2350
|
-
|
|
2406
|
+
notice && /* @__PURE__ */ jsx5("p", { role: "status", className: "isotone-notice", children: notice })
|
|
2351
2407
|
] }),
|
|
2352
2408
|
/* @__PURE__ */ jsx5(
|
|
2353
2409
|
"textarea",
|
|
@@ -2356,7 +2412,10 @@ function IterationPanel({
|
|
|
2356
2412
|
value: text,
|
|
2357
2413
|
"aria-label": "Feedback for the agent",
|
|
2358
2414
|
onChange: (e) => setText(e.target.value),
|
|
2359
|
-
onFocus: () =>
|
|
2415
|
+
onFocus: () => {
|
|
2416
|
+
if (!keepPicking.current) setPicking(false);
|
|
2417
|
+
},
|
|
2418
|
+
onPointerDown: () => setPicking(false),
|
|
2360
2419
|
onScroll: (event) => {
|
|
2361
2420
|
event.currentTarget.dataset.scrolled = String(event.currentTarget.scrollTop > 0);
|
|
2362
2421
|
},
|
|
@@ -2380,6 +2439,7 @@ function IterationPanel({
|
|
|
2380
2439
|
"aria-label": "Select elements",
|
|
2381
2440
|
"aria-pressed": picking,
|
|
2382
2441
|
className: "isotone-pick isotone-icon",
|
|
2442
|
+
onMouseDown: (e) => e.preventDefault(),
|
|
2383
2443
|
onClick: () => {
|
|
2384
2444
|
cancelAnimationFrame(focusFrame.current);
|
|
2385
2445
|
setPicking((v) => !v);
|
|
@@ -2387,7 +2447,6 @@ function IterationPanel({
|
|
|
2387
2447
|
children: /* @__PURE__ */ jsx5(Icon2, { name: "pin", size: 14 })
|
|
2388
2448
|
}
|
|
2389
2449
|
),
|
|
2390
|
-
picking && /* @__PURE__ */ jsx5("span", { className: "isotone-key-hint", children: "Click or Space to pin \xB7 Enter to finish" }),
|
|
2391
2450
|
/* @__PURE__ */ jsx5(
|
|
2392
2451
|
"button",
|
|
2393
2452
|
{
|
|
@@ -2425,7 +2484,7 @@ function IterationPanel({
|
|
|
2425
2484
|
picking && /* @__PURE__ */ jsxs4("div", { className: "isotone-hint", "data-isotone": true, children: [
|
|
2426
2485
|
/* @__PURE__ */ jsxs4("span", { children: [
|
|
2427
2486
|
targets.length,
|
|
2428
|
-
" selected \xB7 click to add or remove"
|
|
2487
|
+
" selected \xB7 click to add or remove \xB7 hold \u21E7 to use the page"
|
|
2429
2488
|
] }),
|
|
2430
2489
|
/* @__PURE__ */ jsxs4(
|
|
2431
2490
|
"button",
|