@unpunnyfuns/swatchbook-blocks 0.52.0 → 0.53.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.mjs +266 -78
- package/dist/index.mjs.map +1 -1
- package/dist/style.css +1 -1
- package/package.json +5 -3
package/dist/index.mjs
CHANGED
|
@@ -46,16 +46,16 @@ function formatColor(value, format, fallback = DEFAULT_FALLBACK) {
|
|
|
46
46
|
function coerce(value) {
|
|
47
47
|
if (!value || typeof value !== "object") return null;
|
|
48
48
|
const v = value;
|
|
49
|
-
const colorSpace = typeof v
|
|
50
|
-
const components = Array.isArray(v
|
|
49
|
+
const colorSpace = typeof v.colorSpace === "string" ? v.colorSpace : void 0;
|
|
50
|
+
const components = Array.isArray(v.components) ? v.components : Array.isArray(v.channels) ? v.channels : void 0;
|
|
51
51
|
if (!colorSpace || !components) {
|
|
52
|
-
if (typeof v
|
|
52
|
+
if (typeof v.hex === "string") return {
|
|
53
53
|
colorSpace: "srgb",
|
|
54
|
-
components: hexToComponents(v
|
|
54
|
+
components: hexToComponents(v.hex)
|
|
55
55
|
};
|
|
56
56
|
return null;
|
|
57
57
|
}
|
|
58
|
-
const alpha = typeof v
|
|
58
|
+
const alpha = typeof v.alpha === "number" ? v.alpha : void 0;
|
|
59
59
|
const hex = typeof v["hex"] === "string" ? v["hex"] : void 0;
|
|
60
60
|
return {
|
|
61
61
|
colorSpace,
|
|
@@ -926,11 +926,7 @@ function CopyButton$1({ value, label, variant = "icon", className }) {
|
|
|
926
926
|
};
|
|
927
927
|
}, []);
|
|
928
928
|
const handleClick = useCallback(() => {
|
|
929
|
-
|
|
930
|
-
navigator.clipboard?.writeText(value);
|
|
931
|
-
} catch {
|
|
932
|
-
return;
|
|
933
|
-
}
|
|
929
|
+
navigator.clipboard?.writeText(value).catch(() => {});
|
|
934
930
|
setCopied(true);
|
|
935
931
|
if (timerRef.current !== null) clearTimeout(timerRef.current);
|
|
936
932
|
timerRef.current = setTimeout(() => setCopied(false), 1500);
|
|
@@ -1658,13 +1654,13 @@ function formatShadow(v, colorFormat) {
|
|
|
1658
1654
|
if (!layer || typeof layer !== "object") return formatUnknown(layer);
|
|
1659
1655
|
const s = layer;
|
|
1660
1656
|
const pieces = [
|
|
1661
|
-
formatDimension$1(s
|
|
1662
|
-
formatDimension$1(s
|
|
1663
|
-
formatDimension$1(s
|
|
1664
|
-
formatDimension$1(s
|
|
1665
|
-
formatColor(s
|
|
1657
|
+
formatDimension$1(s.offsetX),
|
|
1658
|
+
formatDimension$1(s.offsetY),
|
|
1659
|
+
formatDimension$1(s.blur),
|
|
1660
|
+
formatDimension$1(s.spread),
|
|
1661
|
+
formatColor(s.color, colorFormat).value
|
|
1666
1662
|
].filter((p) => p !== "");
|
|
1667
|
-
if (s
|
|
1663
|
+
if (s.inset) pieces.push("inset");
|
|
1668
1664
|
return pieces.join(" ");
|
|
1669
1665
|
}).join(", ");
|
|
1670
1666
|
}
|
|
@@ -1672,9 +1668,9 @@ function formatBorder(v, colorFormat) {
|
|
|
1672
1668
|
if (!v || typeof v !== "object") return formatUnknown(v);
|
|
1673
1669
|
const b = v;
|
|
1674
1670
|
return [
|
|
1675
|
-
formatDimension$1(b
|
|
1676
|
-
formatPrimitive$1(b
|
|
1677
|
-
formatColor(b
|
|
1671
|
+
formatDimension$1(b.width),
|
|
1672
|
+
formatPrimitive$1(b.style),
|
|
1673
|
+
formatColor(b.color, colorFormat).value
|
|
1678
1674
|
].filter((p) => p !== "").join(" ");
|
|
1679
1675
|
}
|
|
1680
1676
|
function formatUnknown(v) {
|
|
@@ -2999,27 +2995,27 @@ function CompositeBreakdownContent({ type, rawValue, partialAliasOf, resolved, c
|
|
|
2999
2995
|
return renderKeyValueList([
|
|
3000
2996
|
[
|
|
3001
2997
|
"fontFamily",
|
|
3002
|
-
formatFontFamily(v
|
|
2998
|
+
formatFontFamily(v.fontFamily),
|
|
3003
2999
|
aliasFor("fontFamily")
|
|
3004
3000
|
],
|
|
3005
3001
|
[
|
|
3006
3002
|
"fontSize",
|
|
3007
|
-
formatDimensionValue(v
|
|
3003
|
+
formatDimensionValue(v.fontSize),
|
|
3008
3004
|
aliasFor("fontSize")
|
|
3009
3005
|
],
|
|
3010
3006
|
[
|
|
3011
3007
|
"fontWeight",
|
|
3012
|
-
formatPrimitive(v
|
|
3008
|
+
formatPrimitive(v.fontWeight),
|
|
3013
3009
|
aliasFor("fontWeight")
|
|
3014
3010
|
],
|
|
3015
3011
|
[
|
|
3016
3012
|
"lineHeight",
|
|
3017
|
-
formatPrimitive(v
|
|
3013
|
+
formatPrimitive(v.lineHeight),
|
|
3018
3014
|
aliasFor("lineHeight")
|
|
3019
3015
|
],
|
|
3020
3016
|
[
|
|
3021
3017
|
"letterSpacing",
|
|
3022
|
-
formatDimensionValue(v
|
|
3018
|
+
formatDimensionValue(v.letterSpacing),
|
|
3023
3019
|
aliasFor("letterSpacing")
|
|
3024
3020
|
]
|
|
3025
3021
|
]);
|
|
@@ -3029,17 +3025,17 @@ function CompositeBreakdownContent({ type, rawValue, partialAliasOf, resolved, c
|
|
|
3029
3025
|
return renderKeyValueList([
|
|
3030
3026
|
[
|
|
3031
3027
|
"color",
|
|
3032
|
-
formatColorSubValue(v
|
|
3028
|
+
formatColorSubValue(v.color, colorFormat),
|
|
3033
3029
|
aliasFor("color")
|
|
3034
3030
|
],
|
|
3035
3031
|
[
|
|
3036
3032
|
"width",
|
|
3037
|
-
formatDimensionValue(v
|
|
3033
|
+
formatDimensionValue(v.width),
|
|
3038
3034
|
aliasFor("width")
|
|
3039
3035
|
],
|
|
3040
3036
|
[
|
|
3041
3037
|
"style",
|
|
3042
|
-
formatPrimitive(v
|
|
3038
|
+
formatPrimitive(v.style),
|
|
3043
3039
|
aliasFor("style")
|
|
3044
3040
|
]
|
|
3045
3041
|
]);
|
|
@@ -3049,17 +3045,17 @@ function CompositeBreakdownContent({ type, rawValue, partialAliasOf, resolved, c
|
|
|
3049
3045
|
return renderKeyValueList([
|
|
3050
3046
|
[
|
|
3051
3047
|
"duration",
|
|
3052
|
-
formatDimensionValue(v
|
|
3048
|
+
formatDimensionValue(v.duration),
|
|
3053
3049
|
aliasFor("duration")
|
|
3054
3050
|
],
|
|
3055
3051
|
[
|
|
3056
3052
|
"timingFunction",
|
|
3057
|
-
formatPrimitive(v
|
|
3053
|
+
formatPrimitive(v.timingFunction),
|
|
3058
3054
|
aliasFor("timingFunction")
|
|
3059
3055
|
],
|
|
3060
3056
|
[
|
|
3061
3057
|
"delay",
|
|
3062
|
-
formatDimensionValue(v
|
|
3058
|
+
formatDimensionValue(v.delay),
|
|
3063
3059
|
aliasFor("delay")
|
|
3064
3060
|
]
|
|
3065
3061
|
]);
|
|
@@ -3081,32 +3077,32 @@ function CompositeBreakdownContent({ type, rawValue, partialAliasOf, resolved, c
|
|
|
3081
3077
|
}),
|
|
3082
3078
|
/* @__PURE__ */ jsx(KeyValueRow, {
|
|
3083
3079
|
label: "color",
|
|
3084
|
-
value: formatColorSubValue(v
|
|
3080
|
+
value: formatColorSubValue(v.color, colorFormat),
|
|
3085
3081
|
alias: layerAliasFor(i, "color")
|
|
3086
3082
|
}),
|
|
3087
3083
|
/* @__PURE__ */ jsx(KeyValueRow, {
|
|
3088
3084
|
label: "offsetX",
|
|
3089
|
-
value: formatDimensionValue(v
|
|
3085
|
+
value: formatDimensionValue(v.offsetX),
|
|
3090
3086
|
alias: layerAliasFor(i, "offsetX")
|
|
3091
3087
|
}),
|
|
3092
3088
|
/* @__PURE__ */ jsx(KeyValueRow, {
|
|
3093
3089
|
label: "offsetY",
|
|
3094
|
-
value: formatDimensionValue(v
|
|
3090
|
+
value: formatDimensionValue(v.offsetY),
|
|
3095
3091
|
alias: layerAliasFor(i, "offsetY")
|
|
3096
3092
|
}),
|
|
3097
3093
|
/* @__PURE__ */ jsx(KeyValueRow, {
|
|
3098
3094
|
label: "blur",
|
|
3099
|
-
value: formatDimensionValue(v
|
|
3095
|
+
value: formatDimensionValue(v.blur),
|
|
3100
3096
|
alias: layerAliasFor(i, "blur")
|
|
3101
3097
|
}),
|
|
3102
3098
|
/* @__PURE__ */ jsx(KeyValueRow, {
|
|
3103
3099
|
label: "spread",
|
|
3104
|
-
value: formatDimensionValue(v
|
|
3100
|
+
value: formatDimensionValue(v.spread),
|
|
3105
3101
|
alias: layerAliasFor(i, "spread")
|
|
3106
3102
|
}),
|
|
3107
|
-
|
|
3103
|
+
v.inset !== void 0 && /* @__PURE__ */ jsx(KeyValueRow, {
|
|
3108
3104
|
label: "inset",
|
|
3109
|
-
value: formatPrimitive(v
|
|
3105
|
+
value: formatPrimitive(v.inset),
|
|
3110
3106
|
alias: void 0
|
|
3111
3107
|
})
|
|
3112
3108
|
]
|
|
@@ -3123,8 +3119,8 @@ function CompositeBreakdownContent({ type, rawValue, partialAliasOf, resolved, c
|
|
|
3123
3119
|
children: stops.map((stop, i) => {
|
|
3124
3120
|
const v = stop;
|
|
3125
3121
|
return /* @__PURE__ */ jsx(KeyValueRow, {
|
|
3126
|
-
label: `${((typeof v
|
|
3127
|
-
value: formatColorSubValue(v
|
|
3122
|
+
label: `${((typeof v.position === "number" ? v.position : 0) * 100).toFixed(0)}%`,
|
|
3123
|
+
value: formatColorSubValue(v.color, colorFormat),
|
|
3128
3124
|
alias: stopAliasFor(i)
|
|
3129
3125
|
}, gradientStopKey(v, i));
|
|
3130
3126
|
})
|
|
@@ -3215,16 +3211,16 @@ function subValueChain(aliasTarget, resolved) {
|
|
|
3215
3211
|
}
|
|
3216
3212
|
function shadowLayerKey(layer, fallback) {
|
|
3217
3213
|
return `shadow|${[
|
|
3218
|
-
layer
|
|
3219
|
-
layer
|
|
3220
|
-
layer
|
|
3221
|
-
layer
|
|
3222
|
-
layer
|
|
3223
|
-
layer
|
|
3214
|
+
layer.color,
|
|
3215
|
+
layer.offsetX,
|
|
3216
|
+
layer.offsetY,
|
|
3217
|
+
layer.blur,
|
|
3218
|
+
layer.spread,
|
|
3219
|
+
layer.inset
|
|
3224
3220
|
].map((p) => p === void 0 ? "" : JSON.stringify(p)).join("|")}|${fallback}`;
|
|
3225
3221
|
}
|
|
3226
3222
|
function gradientStopKey(stop, fallback) {
|
|
3227
|
-
return `stop|${stop
|
|
3223
|
+
return `stop|${stop.position ?? fallback}|${JSON.stringify(stop.color)}`;
|
|
3228
3224
|
}
|
|
3229
3225
|
//#endregion
|
|
3230
3226
|
//#region src/token-detail/CompositePreview.tsx
|
|
@@ -3600,7 +3596,30 @@ function TokenDetail({ path, heading }) {
|
|
|
3600
3596
|
}
|
|
3601
3597
|
//#endregion
|
|
3602
3598
|
//#region src/internal/DetailOverlay.tsx
|
|
3599
|
+
/**
|
|
3600
|
+
* Selector for elements the trap considers focus stops. Mirrors the
|
|
3601
|
+
* "tabbable" set most focus-trap libraries use; the `:not(...)` clauses
|
|
3602
|
+
* skip the panel wrapper itself (we focus it manually on mount via its
|
|
3603
|
+
* own ref) and any explicitly-detabbed descendants.
|
|
3604
|
+
*/
|
|
3605
|
+
const FOCUSABLE_SELECTOR = [
|
|
3606
|
+
"a[href]",
|
|
3607
|
+
"button:not([disabled])",
|
|
3608
|
+
"input:not([disabled])",
|
|
3609
|
+
"select:not([disabled])",
|
|
3610
|
+
"textarea:not([disabled])",
|
|
3611
|
+
"[tabindex]:not([tabindex=\"-1\"])"
|
|
3612
|
+
].join(", ");
|
|
3603
3613
|
function DetailOverlay({ path, onClose, testId = "swatchbook-overlay" }) {
|
|
3614
|
+
const panelRef = useRef(null);
|
|
3615
|
+
const openerRef = useRef(null);
|
|
3616
|
+
useEffect(() => {
|
|
3617
|
+
openerRef.current = document.activeElement instanceof HTMLElement ? document.activeElement : null;
|
|
3618
|
+
panelRef.current?.focus();
|
|
3619
|
+
return () => {
|
|
3620
|
+
openerRef.current?.focus();
|
|
3621
|
+
};
|
|
3622
|
+
}, []);
|
|
3604
3623
|
useEffect(() => {
|
|
3605
3624
|
const onKey = (e) => {
|
|
3606
3625
|
if (e.key === "Escape") onClose();
|
|
@@ -3608,17 +3627,48 @@ function DetailOverlay({ path, onClose, testId = "swatchbook-overlay" }) {
|
|
|
3608
3627
|
window.addEventListener("keydown", onKey);
|
|
3609
3628
|
return () => window.removeEventListener("keydown", onKey);
|
|
3610
3629
|
}, [onClose]);
|
|
3630
|
+
/**
|
|
3631
|
+
* Wrap Tab inside the panel: from the last focusable, jump to the first;
|
|
3632
|
+
* from the first (or from the panel itself), Shift+Tab jumps to the last.
|
|
3633
|
+
* Defers to the browser otherwise.
|
|
3634
|
+
*/
|
|
3635
|
+
const onPanelKeyDown = (e) => {
|
|
3636
|
+
if (e.key !== "Tab") return;
|
|
3637
|
+
const panel = panelRef.current;
|
|
3638
|
+
if (!panel) return;
|
|
3639
|
+
const focusables = panel.querySelectorAll(FOCUSABLE_SELECTOR);
|
|
3640
|
+
if (focusables.length === 0) {
|
|
3641
|
+
e.preventDefault();
|
|
3642
|
+
return;
|
|
3643
|
+
}
|
|
3644
|
+
const first = focusables[0];
|
|
3645
|
+
const last = focusables[focusables.length - 1];
|
|
3646
|
+
const active = document.activeElement;
|
|
3647
|
+
if (!first || !last) return;
|
|
3648
|
+
if (e.shiftKey) {
|
|
3649
|
+
if (active === first || active === panel) {
|
|
3650
|
+
e.preventDefault();
|
|
3651
|
+
last.focus();
|
|
3652
|
+
}
|
|
3653
|
+
} else if (active === last) {
|
|
3654
|
+
e.preventDefault();
|
|
3655
|
+
first.focus();
|
|
3656
|
+
}
|
|
3657
|
+
};
|
|
3611
3658
|
return /* @__PURE__ */ jsx("div", {
|
|
3612
3659
|
className: "sb-detail-overlay__backdrop",
|
|
3613
3660
|
onClick: onClose,
|
|
3614
3661
|
role: "presentation",
|
|
3615
3662
|
"data-testid": testId,
|
|
3616
3663
|
children: /* @__PURE__ */ jsxs("div", {
|
|
3664
|
+
ref: panelRef,
|
|
3617
3665
|
className: "sb-detail-overlay__panel",
|
|
3618
3666
|
onClick: (e) => e.stopPropagation(),
|
|
3667
|
+
onKeyDown: onPanelKeyDown,
|
|
3619
3668
|
role: "dialog",
|
|
3620
3669
|
"aria-modal": "true",
|
|
3621
3670
|
"aria-label": `Token detail for ${path}`,
|
|
3671
|
+
tabIndex: -1,
|
|
3622
3672
|
children: [/* @__PURE__ */ jsx("button", {
|
|
3623
3673
|
type: "button",
|
|
3624
3674
|
className: "sb-detail-overlay__close",
|
|
@@ -3707,6 +3757,16 @@ function collectLeafPaths(nodes, out) {
|
|
|
3707
3757
|
for (const node of nodes) if (node.kind === "leaf") out.push(node.path);
|
|
3708
3758
|
else collectLeafPaths(node.children, out);
|
|
3709
3759
|
}
|
|
3760
|
+
function flattenVisible(nodes, expanded, parentPath, out) {
|
|
3761
|
+
for (const node of nodes) {
|
|
3762
|
+
out.push({
|
|
3763
|
+
path: node.path,
|
|
3764
|
+
kind: node.kind,
|
|
3765
|
+
parentPath
|
|
3766
|
+
});
|
|
3767
|
+
if (node.kind === "group" && expanded.has(node.path)) flattenVisible(node.children, expanded, node.path, out);
|
|
3768
|
+
}
|
|
3769
|
+
}
|
|
3710
3770
|
/**
|
|
3711
3771
|
* Return a pruned copy of the tree keeping only leaves whose path is in
|
|
3712
3772
|
* `matches`, plus the groups on the way to them. Every surviving group's
|
|
@@ -3786,6 +3846,125 @@ function TokenNavigator({ root, type, initiallyExpanded = 1, searchable = true,
|
|
|
3786
3846
|
if (onSelect) onSelect(path);
|
|
3787
3847
|
else setSelectedPath(path);
|
|
3788
3848
|
}, [onSelect]);
|
|
3849
|
+
const [focusedPath, setFocusedPath] = useState(null);
|
|
3850
|
+
const treeItemRefs = useRef(/* @__PURE__ */ new Map());
|
|
3851
|
+
const registerTreeItem = useCallback((path) => (el) => {
|
|
3852
|
+
if (el) treeItemRefs.current.set(path, el);
|
|
3853
|
+
else treeItemRefs.current.delete(path);
|
|
3854
|
+
}, []);
|
|
3855
|
+
const flatVisible = useMemo(() => {
|
|
3856
|
+
const out = [];
|
|
3857
|
+
flattenVisible(visibleTree, effectiveExpanded, null, out);
|
|
3858
|
+
return out;
|
|
3859
|
+
}, [visibleTree, effectiveExpanded]);
|
|
3860
|
+
useEffect(() => {
|
|
3861
|
+
if (flatVisible.length === 0) {
|
|
3862
|
+
setFocusedPath(null);
|
|
3863
|
+
return;
|
|
3864
|
+
}
|
|
3865
|
+
setFocusedPath((prev) => {
|
|
3866
|
+
if (prev && flatVisible.some((entry) => entry.path === prev)) return prev;
|
|
3867
|
+
return flatVisible[0]?.path ?? null;
|
|
3868
|
+
});
|
|
3869
|
+
}, [flatVisible]);
|
|
3870
|
+
const focusByPath = useCallback((path) => {
|
|
3871
|
+
const node = treeItemRefs.current.get(path);
|
|
3872
|
+
if (node) {
|
|
3873
|
+
node.focus();
|
|
3874
|
+
setFocusedPath(path);
|
|
3875
|
+
} else setFocusedPath(path);
|
|
3876
|
+
}, []);
|
|
3877
|
+
useEffect(() => {
|
|
3878
|
+
if (focusedPath === null) return;
|
|
3879
|
+
const node = treeItemRefs.current.get(focusedPath);
|
|
3880
|
+
if (node && document.activeElement !== node) {
|
|
3881
|
+
const active = document.activeElement;
|
|
3882
|
+
if (active instanceof HTMLElement && active.closest("[role=\"tree\"]")) node.focus();
|
|
3883
|
+
}
|
|
3884
|
+
}, [focusedPath]);
|
|
3885
|
+
const handleTreeKeyDown = useCallback((e) => {
|
|
3886
|
+
if (flatVisible.length === 0) return;
|
|
3887
|
+
const active = document.activeElement;
|
|
3888
|
+
if (!(active instanceof HTMLLIElement)) return;
|
|
3889
|
+
const activePath = active.getAttribute("data-path");
|
|
3890
|
+
if (activePath === null) return;
|
|
3891
|
+
const currentIndex = flatVisible.findIndex((entry) => entry.path === activePath);
|
|
3892
|
+
if (currentIndex < 0) return;
|
|
3893
|
+
const current = flatVisible[currentIndex];
|
|
3894
|
+
if (!current) return;
|
|
3895
|
+
switch (e.key) {
|
|
3896
|
+
case "ArrowDown": {
|
|
3897
|
+
const next = flatVisible[currentIndex + 1];
|
|
3898
|
+
if (next) {
|
|
3899
|
+
e.preventDefault();
|
|
3900
|
+
focusByPath(next.path);
|
|
3901
|
+
}
|
|
3902
|
+
return;
|
|
3903
|
+
}
|
|
3904
|
+
case "ArrowUp": {
|
|
3905
|
+
const prev = flatVisible[currentIndex - 1];
|
|
3906
|
+
if (prev) {
|
|
3907
|
+
e.preventDefault();
|
|
3908
|
+
focusByPath(prev.path);
|
|
3909
|
+
}
|
|
3910
|
+
return;
|
|
3911
|
+
}
|
|
3912
|
+
case "Home": {
|
|
3913
|
+
const first = flatVisible[0];
|
|
3914
|
+
if (first) {
|
|
3915
|
+
e.preventDefault();
|
|
3916
|
+
focusByPath(first.path);
|
|
3917
|
+
}
|
|
3918
|
+
return;
|
|
3919
|
+
}
|
|
3920
|
+
case "End": {
|
|
3921
|
+
const last = flatVisible[flatVisible.length - 1];
|
|
3922
|
+
if (last) {
|
|
3923
|
+
e.preventDefault();
|
|
3924
|
+
focusByPath(last.path);
|
|
3925
|
+
}
|
|
3926
|
+
return;
|
|
3927
|
+
}
|
|
3928
|
+
case "ArrowRight":
|
|
3929
|
+
if (current.kind === "group") {
|
|
3930
|
+
if (!effectiveExpanded.has(current.path)) {
|
|
3931
|
+
e.preventDefault();
|
|
3932
|
+
toggle(current.path);
|
|
3933
|
+
return;
|
|
3934
|
+
}
|
|
3935
|
+
const firstChild = flatVisible[currentIndex + 1];
|
|
3936
|
+
if (firstChild && firstChild.parentPath === current.path) {
|
|
3937
|
+
e.preventDefault();
|
|
3938
|
+
focusByPath(firstChild.path);
|
|
3939
|
+
}
|
|
3940
|
+
}
|
|
3941
|
+
return;
|
|
3942
|
+
case "ArrowLeft":
|
|
3943
|
+
if (current.kind === "group" && effectiveExpanded.has(current.path)) {
|
|
3944
|
+
e.preventDefault();
|
|
3945
|
+
toggle(current.path);
|
|
3946
|
+
return;
|
|
3947
|
+
}
|
|
3948
|
+
if (current.parentPath !== null) {
|
|
3949
|
+
e.preventDefault();
|
|
3950
|
+
focusByPath(current.parentPath);
|
|
3951
|
+
}
|
|
3952
|
+
return;
|
|
3953
|
+
case "Enter":
|
|
3954
|
+
case " ":
|
|
3955
|
+
e.preventDefault();
|
|
3956
|
+
if (current.kind === "group") toggle(current.path);
|
|
3957
|
+
else handleLeafClick(current.path);
|
|
3958
|
+
return;
|
|
3959
|
+
default: return;
|
|
3960
|
+
}
|
|
3961
|
+
}, [
|
|
3962
|
+
flatVisible,
|
|
3963
|
+
effectiveExpanded,
|
|
3964
|
+
toggle,
|
|
3965
|
+
focusByPath,
|
|
3966
|
+
handleLeafClick
|
|
3967
|
+
]);
|
|
3789
3968
|
const typeLabel = typeFilter ? ` · ${[...typeFilter].map((t) => `$type=${t}`).join(", ")}` : "";
|
|
3790
3969
|
const trimmedQuery = query.trim();
|
|
3791
3970
|
const matchCount = useMemo(() => {
|
|
@@ -3834,10 +4013,15 @@ function TokenNavigator({ root, type, initiallyExpanded = 1, searchable = true,
|
|
|
3834
4013
|
}) : /* @__PURE__ */ jsx("ul", {
|
|
3835
4014
|
className: "sb-token-navigator__tree",
|
|
3836
4015
|
role: "tree",
|
|
4016
|
+
"aria-label": "Token graph",
|
|
4017
|
+
onKeyDown: handleTreeKeyDown,
|
|
3837
4018
|
children: visibleTree.map((node) => /* @__PURE__ */ jsx(TreeNodeRow, {
|
|
3838
4019
|
node,
|
|
3839
4020
|
expanded: effectiveExpanded,
|
|
4021
|
+
focusedPath,
|
|
4022
|
+
registerTreeItem,
|
|
3840
4023
|
onToggle: toggle,
|
|
4024
|
+
onFocusPath: setFocusedPath,
|
|
3841
4025
|
onLeafClick: handleLeafClick
|
|
3842
4026
|
}, node.path || node.segment))
|
|
3843
4027
|
}),
|
|
@@ -3849,29 +4033,31 @@ function TokenNavigator({ root, type, initiallyExpanded = 1, searchable = true,
|
|
|
3849
4033
|
]
|
|
3850
4034
|
});
|
|
3851
4035
|
}
|
|
3852
|
-
function TreeNodeRow({ node, expanded, onToggle, onLeafClick }) {
|
|
4036
|
+
function TreeNodeRow({ node, expanded, focusedPath, registerTreeItem, onToggle, onFocusPath, onLeafClick }) {
|
|
3853
4037
|
if (node.kind === "leaf") return /* @__PURE__ */ jsx(LeafRow, {
|
|
3854
4038
|
node,
|
|
4039
|
+
focusedPath,
|
|
4040
|
+
registerTreeItem,
|
|
4041
|
+
onFocusPath,
|
|
3855
4042
|
onLeafClick
|
|
3856
4043
|
});
|
|
3857
4044
|
const isOpen = expanded.has(node.path);
|
|
3858
|
-
const
|
|
3859
|
-
if (e.key === "Enter" || e.key === " ") {
|
|
3860
|
-
e.preventDefault();
|
|
3861
|
-
onToggle(node.path);
|
|
3862
|
-
}
|
|
3863
|
-
};
|
|
4045
|
+
const isFocused = focusedPath === node.path;
|
|
3864
4046
|
return /* @__PURE__ */ jsxs("li", {
|
|
4047
|
+
ref: registerTreeItem(node.path),
|
|
3865
4048
|
role: "treeitem",
|
|
3866
4049
|
"aria-expanded": isOpen,
|
|
4050
|
+
tabIndex: isFocused ? 0 : -1,
|
|
4051
|
+
onFocus: () => onFocusPath(node.path),
|
|
4052
|
+
"data-path": node.path,
|
|
4053
|
+
"data-testid": "token-navigator-group",
|
|
3867
4054
|
children: [/* @__PURE__ */ jsxs("div", {
|
|
3868
|
-
role: "button",
|
|
3869
|
-
tabIndex: 0,
|
|
3870
4055
|
className: "sb-token-navigator__group-row",
|
|
3871
|
-
|
|
3872
|
-
|
|
3873
|
-
|
|
3874
|
-
|
|
4056
|
+
"data-testid": "token-navigator-group-row",
|
|
4057
|
+
onClick: () => {
|
|
4058
|
+
onFocusPath(node.path);
|
|
4059
|
+
onToggle(node.path);
|
|
4060
|
+
},
|
|
3875
4061
|
children: [
|
|
3876
4062
|
/* @__PURE__ */ jsx("span", {
|
|
3877
4063
|
className: "sb-token-navigator__caret",
|
|
@@ -3890,30 +4076,32 @@ function TreeNodeRow({ node, expanded, onToggle, onLeafClick }) {
|
|
|
3890
4076
|
children: node.children.map((c) => /* @__PURE__ */ jsx(TreeNodeRow, {
|
|
3891
4077
|
node: c,
|
|
3892
4078
|
expanded,
|
|
4079
|
+
focusedPath,
|
|
4080
|
+
registerTreeItem,
|
|
3893
4081
|
onToggle,
|
|
4082
|
+
onFocusPath,
|
|
3894
4083
|
onLeafClick
|
|
3895
4084
|
}, c.path || c.segment))
|
|
3896
4085
|
})]
|
|
3897
4086
|
});
|
|
3898
4087
|
}
|
|
3899
|
-
function LeafRow({ node, onLeafClick }) {
|
|
3900
|
-
const onKey = (e) => {
|
|
3901
|
-
if (e.key === "Enter" || e.key === " ") {
|
|
3902
|
-
e.preventDefault();
|
|
3903
|
-
onLeafClick(node.path);
|
|
3904
|
-
}
|
|
3905
|
-
};
|
|
4088
|
+
function LeafRow({ node, focusedPath, registerTreeItem, onFocusPath, onLeafClick }) {
|
|
3906
4089
|
const type = node.token.$type ?? "";
|
|
4090
|
+
const isFocused = focusedPath === node.path;
|
|
3907
4091
|
return /* @__PURE__ */ jsx("li", {
|
|
4092
|
+
ref: registerTreeItem(node.path),
|
|
3908
4093
|
role: "treeitem",
|
|
4094
|
+
tabIndex: isFocused ? 0 : -1,
|
|
4095
|
+
onFocus: () => onFocusPath(node.path),
|
|
4096
|
+
"data-path": node.path,
|
|
4097
|
+
"data-testid": "token-navigator-leaf",
|
|
3909
4098
|
children: /* @__PURE__ */ jsxs("div", {
|
|
3910
|
-
role: "button",
|
|
3911
|
-
tabIndex: 0,
|
|
3912
4099
|
className: "sb-token-navigator__leaf-row",
|
|
3913
|
-
|
|
3914
|
-
|
|
3915
|
-
|
|
3916
|
-
|
|
4100
|
+
"data-testid": "token-navigator-leaf-row",
|
|
4101
|
+
onClick: () => {
|
|
4102
|
+
onFocusPath(node.path);
|
|
4103
|
+
onLeafClick(node.path);
|
|
4104
|
+
},
|
|
3917
4105
|
children: [
|
|
3918
4106
|
/* @__PURE__ */ jsx("span", {
|
|
3919
4107
|
className: "sb-token-navigator__caret",
|
|
@@ -4165,7 +4353,7 @@ function asDimension(raw) {
|
|
|
4165
4353
|
if (typeof raw === "string" || typeof raw === "number") return String(raw);
|
|
4166
4354
|
if (typeof raw === "object") {
|
|
4167
4355
|
const v = raw;
|
|
4168
|
-
if (
|
|
4356
|
+
if (v.value !== void 0 && v.unit !== void 0) return `${String(v.value)}${String(v.unit)}`;
|
|
4169
4357
|
}
|
|
4170
4358
|
}
|
|
4171
4359
|
function asFontFamily(raw) {
|
|
@@ -4173,11 +4361,11 @@ function asFontFamily(raw) {
|
|
|
4173
4361
|
if (Array.isArray(raw)) return raw.map(String).join(", ");
|
|
4174
4362
|
}
|
|
4175
4363
|
function buildRow(path, composite) {
|
|
4176
|
-
const fontFamily = asFontFamily(composite
|
|
4177
|
-
const fontSize = asDimension(composite
|
|
4178
|
-
const fontWeight = composite
|
|
4179
|
-
const lineHeight = composite
|
|
4180
|
-
const letterSpacing = asDimension(composite
|
|
4364
|
+
const fontFamily = asFontFamily(composite.fontFamily);
|
|
4365
|
+
const fontSize = asDimension(composite.fontSize);
|
|
4366
|
+
const fontWeight = composite.fontWeight == null ? void 0 : String(composite.fontWeight);
|
|
4367
|
+
const lineHeight = composite.lineHeight == null ? void 0 : String(composite.lineHeight);
|
|
4368
|
+
const letterSpacing = asDimension(composite.letterSpacing);
|
|
4181
4369
|
const sampleStyle = {};
|
|
4182
4370
|
if (fontFamily) sampleStyle.fontFamily = fontFamily;
|
|
4183
4371
|
if (fontSize) sampleStyle.fontSize = fontSize;
|