@stackone/malachite 2.33.1 → 2.33.2
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.
|
@@ -1,24 +1,22 @@
|
|
|
1
|
-
import { jsxs as
|
|
2
|
-
import
|
|
3
|
-
import
|
|
4
|
-
import
|
|
5
|
-
import
|
|
6
|
-
import { CONTENT_INSET as
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
.${Y} {
|
|
16
|
-
clip-path: inset(-${W}px -${W}px 0);
|
|
1
|
+
import { jsxs as U, jsx as h } from "react/jsx-runtime";
|
|
2
|
+
import C, { useRef as O, useState as nt, useLayoutEffect as w, useCallback as S } from "react";
|
|
3
|
+
import re from "./CustomIcons/ArrowLeftIcon.js";
|
|
4
|
+
import ne from "./CustomIcons/ForwardArrowIcon.js";
|
|
5
|
+
import oe from "./CustomIcons/PlusIcon.js";
|
|
6
|
+
import { CONTENT_INSET as $t } from "./PageLayout/constants.js";
|
|
7
|
+
import { STRIP_RULE_PX as ae, CARD_FLARE_TIP_PX as ct, CARD_WIDTH_PX as ce, FLARE_CLASS as se, CARD_FLARE_PX as q } from "./SelectableCard.js";
|
|
8
|
+
import { revealTargetFor as Dt, pageTargetFor as ie, EDGE_EPSILON_PX as ot, isCardInView as le } from "./cardStripGeometry.js";
|
|
9
|
+
import { CardStripSlotContext as yt, PINNED_SLOT as de, SCROLLED_SLOT as ue } from "./cardStripSlot.js";
|
|
10
|
+
import { FOCUS_RING as fe } from "./focusRing.js";
|
|
11
|
+
import { useAdoptedStyleSheet as Ot } from "./useAdoptedStyleSheet.js";
|
|
12
|
+
const pe = /* @__PURE__ */ new Set(["ArrowLeft", "ArrowRight", "ArrowUp", "ArrowDown", "Home", "End"]), G = '[role="radio"]', he = "data-card-strip-scroller", me = "data-card-strip-trough", K = "malachite-card-strip", g = "malachite-card-strip-scroller", Xt = "malachite-card-strip-fade", R = "malachite-card-strip-add-tile", st = "data-card-strip-add-slot", Wt = "[data-selectable-card-actions]", be = 'a[href], area[href], button, input, select, textarea, iframe, [contenteditable="true"], [tabindex]', B = "data-card-strip-prev-tabindex", ge = 80, vt = 11, H = 12, zt = "var(--malachite-shadow-default)", Re = "var(--malachite-card-strip-arrow-background, var(--malachite-card-background, #ffffff))", _e = "var(--malachite-card-strip-arrow-border, #e8e8e8)", it = "var(--malachite-card-strip-background, #f1f1f1)", wt = "var(--malachite-radius-xxl, 14px)", Ae = `${wt} ${wt} 0 0`, Ct = "var(--malachite-radius-xxxl, 16px)", Se = `${Ct} ${Ct} 0 0`, v = 4, xe = H + v, Yt = H, It = ge + (Yt - v), dt = ce, Le = 2 * (H + v) + dt, Ee = "var(--malachite-card-strip-divider, #dddddd)", Pt = "var(--malachite-card-strip-fade, transparent)", Te = "var(--malachite-selectable-card-hover-background, #e8e8e8)", $e = "var(--malachite-card-strip-arrow-border, #e8e8e8)", kt = "var(--malachite-selectable-card-hover-rule, #bbbbbb)", Nt = "0px 1px 0px 0px rgba(0, 0, 0, 0.4)", De = "var(--malachite-card-background, #ffffff)", ye = "var(--malachite-text-primary, #0a0a0a)", Oe = "var(--malachite-card-strip-add-primary-background, var(--malachite-text-primary, #0a0a0a))", ve = "var(--malachite-card-strip-add-primary-foreground, var(--malachite-card-background, #ffffff))", we = "0px 1px 0px 0px rgba(10, 10, 10, 0.7)", ut = ae, Ft = 16, M = 9999, Ce = 4, Ie = `
|
|
13
|
+
.${K} {
|
|
14
|
+
clip-path: inset(-${M}px -${M}px 0);
|
|
17
15
|
}
|
|
18
|
-
.${
|
|
19
|
-
clip-path: inset(-${
|
|
16
|
+
.${K}:has(:focus-visible) {
|
|
17
|
+
clip-path: inset(-${M}px -${M}px -${Ce}px);
|
|
20
18
|
}
|
|
21
|
-
.${
|
|
19
|
+
.${g} {
|
|
22
20
|
display: flex;
|
|
23
21
|
flex-wrap: nowrap;
|
|
24
22
|
overflow-x: auto;
|
|
@@ -26,405 +24,471 @@ const Jt = /* @__PURE__ */ new Set(["ArrowLeft", "ArrowRight", "ArrowUp", "Arrow
|
|
|
26
24
|
scroll-behavior: smooth;
|
|
27
25
|
scrollbar-width: none;
|
|
28
26
|
-ms-overflow-style: none;
|
|
29
|
-
padding-block: ${
|
|
30
|
-
margin-block: -${
|
|
27
|
+
padding-block: ${Ft}px;
|
|
28
|
+
margin-block: -${Ft}px;
|
|
31
29
|
/* overflow-x clips both axes, so the end cards would lose a flare; the negative margin trades
|
|
32
30
|
the padding back out and scroll-padding keeps the rest position at zero. */
|
|
33
|
-
padding-inline: ${
|
|
34
|
-
margin-inline: -${
|
|
35
|
-
scroll-padding-inline: ${
|
|
31
|
+
padding-inline: ${q}px;
|
|
32
|
+
margin-inline: -${q}px;
|
|
33
|
+
scroll-padding-inline: ${q}px;
|
|
36
34
|
}
|
|
37
35
|
/* A card focused at a faded edge has to stay readable, so the fade lifts entirely. */
|
|
38
|
-
.${
|
|
36
|
+
.${K}:has(:focus-visible) .${Xt} {
|
|
39
37
|
display: none;
|
|
40
38
|
}
|
|
41
|
-
.${
|
|
39
|
+
.${g}::-webkit-scrollbar {
|
|
42
40
|
display: none;
|
|
43
41
|
}
|
|
44
42
|
/* No bottom edge: the strip's rule is the baseline for the add tile as much as for a card. */
|
|
45
|
-
.${
|
|
46
|
-
background: ${
|
|
47
|
-
border-radius: ${
|
|
43
|
+
.${R} > * {
|
|
44
|
+
background: ${it};
|
|
45
|
+
border-radius: ${Ae};
|
|
48
46
|
}
|
|
49
|
-
.${
|
|
50
|
-
background: ${
|
|
47
|
+
.${R}:hover:not(:has(button:disabled)) > * {
|
|
48
|
+
background: ${Te};
|
|
51
49
|
}
|
|
52
|
-
.${
|
|
50
|
+
.${R} button:active:not(:disabled) {
|
|
53
51
|
opacity: 0.8;
|
|
54
52
|
}
|
|
55
|
-
.${
|
|
56
|
-
background: ${
|
|
53
|
+
.${R}:has(button:active) > * {
|
|
54
|
+
background: ${it};
|
|
57
55
|
}
|
|
58
|
-
.${
|
|
59
|
-
border-color: ${
|
|
60
|
-
box-shadow: ${
|
|
56
|
+
.${R}[data-emphasis='secondary'] button {
|
|
57
|
+
border-color: ${$e};
|
|
58
|
+
box-shadow: ${zt};
|
|
61
59
|
}
|
|
62
|
-
.${
|
|
63
|
-
border-color: ${
|
|
64
|
-
box-shadow: ${
|
|
60
|
+
.${R}[data-emphasis='secondary']:hover:not(:has(button:disabled)) button {
|
|
61
|
+
border-color: ${kt};
|
|
62
|
+
box-shadow: ${Nt};
|
|
65
63
|
}
|
|
66
|
-
.${
|
|
67
|
-
border-color: ${
|
|
68
|
-
box-shadow: ${
|
|
64
|
+
.${R}[data-emphasis='secondary'] button:active:not(:disabled) {
|
|
65
|
+
border-color: ${kt};
|
|
66
|
+
box-shadow: ${Nt};
|
|
69
67
|
}
|
|
70
|
-
.${
|
|
68
|
+
.${R}[data-emphasis='primary'] button {
|
|
71
69
|
border-color: transparent;
|
|
72
|
-
box-shadow: ${
|
|
70
|
+
box-shadow: ${we};
|
|
73
71
|
}
|
|
74
|
-
.${
|
|
72
|
+
.${g} > * {
|
|
75
73
|
flex: 0 0 auto;
|
|
76
74
|
min-width: 0;
|
|
77
75
|
}
|
|
78
|
-
.${
|
|
79
|
-
border-left: 1px solid ${
|
|
76
|
+
.${g} > * + * {
|
|
77
|
+
border-left: 1px solid ${Ee};
|
|
80
78
|
}
|
|
81
|
-
.${
|
|
79
|
+
.${g}[data-snap='true'] {
|
|
82
80
|
scroll-snap-type: x proximity;
|
|
83
81
|
}
|
|
84
|
-
.${
|
|
82
|
+
.${g}[data-snap='true'] > * {
|
|
85
83
|
scroll-snap-align: start;
|
|
86
84
|
}
|
|
87
85
|
@media (prefers-reduced-motion: reduce) {
|
|
88
|
-
.${
|
|
86
|
+
.${g} {
|
|
89
87
|
scroll-behavior: auto;
|
|
90
88
|
}
|
|
91
|
-
.${
|
|
89
|
+
.${g}[data-snap='true'] {
|
|
92
90
|
scroll-snap-type: none;
|
|
93
91
|
}
|
|
94
|
-
.${
|
|
92
|
+
.${g}[data-snap='true'] > * {
|
|
95
93
|
scroll-snap-align: none;
|
|
96
94
|
}
|
|
97
|
-
}`,
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
95
|
+
}`, Pe = "malachite-card-strip-style", ke = "malachite-card-strip-add-slot", Ne = `
|
|
96
|
+
.${g} > [${st}] {
|
|
97
|
+
display: flex;
|
|
98
|
+
border-left: none;
|
|
99
|
+
}
|
|
100
|
+
[${st}] > .${R} {
|
|
101
|
+
height: 100% !important;
|
|
102
|
+
padding: 0 !important;
|
|
103
|
+
}
|
|
104
|
+
`, Ut = () => typeof window > "u" || typeof window.matchMedia != "function" ? !0 : window.matchMedia("(prefers-reduced-motion: reduce)").matches, x = (e) => e.dataset.value ?? e.getAttribute("value"), at = (e) => e.hasAttribute("disabled") || e.getAttribute("aria-disabled") === "true", Fe = (e) => {
|
|
105
|
+
const a = e.parentElement;
|
|
106
|
+
return !a || a.querySelectorAll(G).length !== 1 ? null : a.querySelector(Wt);
|
|
107
|
+
}, Ue = (e, a) => {
|
|
108
|
+
const l = e.closest(G);
|
|
109
|
+
if (l)
|
|
110
|
+
return l;
|
|
111
|
+
if (e.closest(Wt))
|
|
105
112
|
return null;
|
|
106
|
-
let
|
|
107
|
-
for (;
|
|
108
|
-
const
|
|
109
|
-
if (
|
|
110
|
-
return
|
|
111
|
-
|
|
113
|
+
let u = e;
|
|
114
|
+
for (; u && a.contains(u); ) {
|
|
115
|
+
const f = u.querySelectorAll(G);
|
|
116
|
+
if (f.length === 1 && f[0].parentElement === u)
|
|
117
|
+
return f[0];
|
|
118
|
+
u = u.parentElement;
|
|
112
119
|
}
|
|
113
120
|
return null;
|
|
114
|
-
},
|
|
115
|
-
const
|
|
116
|
-
return
|
|
117
|
-
},
|
|
118
|
-
|
|
119
|
-
},
|
|
120
|
-
if (!
|
|
121
|
+
}, Be = (e) => {
|
|
122
|
+
const a = Fe(e);
|
|
123
|
+
return a ? Array.from(a.querySelectorAll(be)) : [];
|
|
124
|
+
}, Ge = (e) => {
|
|
125
|
+
e.hasAttribute(B) || e.setAttribute(B, e.getAttribute("tabindex") ?? ""), e.setAttribute("tabindex", "-1");
|
|
126
|
+
}, He = (e) => {
|
|
127
|
+
if (!e.hasAttribute(B))
|
|
121
128
|
return;
|
|
122
|
-
const
|
|
123
|
-
|
|
124
|
-
},
|
|
129
|
+
const a = e.getAttribute(B);
|
|
130
|
+
e.removeAttribute(B), a ? e.setAttribute("tabindex", a) : e.removeAttribute("tabindex");
|
|
131
|
+
}, Xe = {
|
|
125
132
|
profile: "var(--malachite-selectable-card-selected-border, #047b43)",
|
|
126
133
|
library: "var(--malachite-selectable-card-library-selected-border, #bbbbbb)",
|
|
127
134
|
editing: "var(--malachite-selectable-card-pending-foreground, #96580a)"
|
|
128
|
-
},
|
|
135
|
+
}, We = {
|
|
129
136
|
backgroundRepeat: "no-repeat",
|
|
130
137
|
backgroundPosition: "left bottom",
|
|
131
|
-
backgroundSize: `100% ${
|
|
132
|
-
},
|
|
133
|
-
const
|
|
134
|
-
if (!
|
|
138
|
+
backgroundSize: `100% ${ut}px`
|
|
139
|
+
}, Bt = 0.5, Gt = q - ct, ze = (e) => {
|
|
140
|
+
const a = e.querySelector(`:scope > .${se}`);
|
|
141
|
+
if (!a)
|
|
135
142
|
return null;
|
|
136
|
-
const { left:
|
|
137
|
-
return { left:
|
|
138
|
-
},
|
|
139
|
-
if (!
|
|
143
|
+
const { left: l, right: u } = a.getBoundingClientRect();
|
|
144
|
+
return { left: l + Gt, right: u - Gt };
|
|
145
|
+
}, Ye = (e, a, l, u) => {
|
|
146
|
+
if (!e)
|
|
140
147
|
return;
|
|
141
|
-
const
|
|
142
|
-
|
|
143
|
-
},
|
|
144
|
-
marginInline: `calc(${
|
|
145
|
-
paddingInline:
|
|
146
|
-
},
|
|
147
|
-
background:
|
|
148
|
-
borderRadius:
|
|
149
|
-
paddingTop: `${
|
|
150
|
-
paddingInline: `${
|
|
151
|
-
marginBottom: `${
|
|
152
|
-
},
|
|
153
|
-
...
|
|
154
|
-
marginInline: `${
|
|
155
|
-
},
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
`${t}:${r}`,
|
|
159
|
-
{ ...z, marginInline: `${t}px ${r}px` }
|
|
160
|
-
])
|
|
161
|
-
)
|
|
162
|
-
), ke = z, Ne = z, Ue = {
|
|
163
|
-
marginInlineStart: `${T}px`
|
|
164
|
-
}, Be = {
|
|
148
|
+
const f = Xe[u], T = e.getBoundingClientRect(), I = T.left, _ = l && (a != null && a.contains(l)) ? a.getBoundingClientRect() : T, L = l == null ? void 0 : l.getBoundingClientRect(), m = l ? ze(l) : null, j = (m == null ? void 0 : m.left) ?? (L ? L.left - ct : 0), J = (m == null ? void 0 : m.right) ?? (L ? L.right + ct : 0), P = L ? Math.max(j, _.left) + Bt - I : 0, b = L ? Math.min(J, _.right) - Bt - I : 0;
|
|
149
|
+
e.style.backgroundImage = b > P ? `linear-gradient(to right, ${f} 0 ${P}px, transparent ${P}px ${b}px, ${f} ${b}px 100%)` : `linear-gradient(to right, ${f} 0 100%)`;
|
|
150
|
+
}, Ve = {
|
|
151
|
+
marginInline: `calc(${$t} * -1)`,
|
|
152
|
+
paddingInline: $t
|
|
153
|
+
}, ft = {
|
|
154
|
+
background: it,
|
|
155
|
+
borderRadius: Se,
|
|
156
|
+
paddingTop: `${v}px`,
|
|
157
|
+
paddingInline: `${v}px`,
|
|
158
|
+
marginBottom: `${ut}px`
|
|
159
|
+
}, Me = {
|
|
160
|
+
...ft,
|
|
161
|
+
marginInline: `${H}px`
|
|
162
|
+
}, qe = ft, Ke = ft, je = {
|
|
163
|
+
marginInlineStart: `${H}px`
|
|
164
|
+
}, Je = {
|
|
165
165
|
primary: {
|
|
166
|
-
backgroundColor:
|
|
167
|
-
color:
|
|
166
|
+
backgroundColor: Oe,
|
|
167
|
+
color: ve
|
|
168
168
|
},
|
|
169
|
-
secondary: { backgroundColor:
|
|
170
|
-
},
|
|
171
|
-
label:
|
|
172
|
-
icon:
|
|
173
|
-
onClick:
|
|
174
|
-
disabled:
|
|
175
|
-
emphasis:
|
|
176
|
-
className:
|
|
177
|
-
}) => /* @__PURE__ */
|
|
169
|
+
secondary: { backgroundColor: De, color: ye }
|
|
170
|
+
}, Vt = ({
|
|
171
|
+
label: e,
|
|
172
|
+
icon: a,
|
|
173
|
+
onClick: l,
|
|
174
|
+
disabled: u = !1,
|
|
175
|
+
emphasis: f = "secondary",
|
|
176
|
+
className: T = ""
|
|
177
|
+
}) => /* @__PURE__ */ h(
|
|
178
178
|
"div",
|
|
179
179
|
{
|
|
180
|
-
className:
|
|
181
|
-
"data-emphasis":
|
|
180
|
+
className: R,
|
|
181
|
+
"data-emphasis": f,
|
|
182
182
|
style: {
|
|
183
|
-
...
|
|
184
|
-
width: `${
|
|
185
|
-
height: `calc(100% - ${
|
|
183
|
+
...qe,
|
|
184
|
+
width: `${dt}px`,
|
|
185
|
+
height: `calc(100% - ${ut}px)`
|
|
186
186
|
},
|
|
187
|
-
children: /* @__PURE__ */
|
|
187
|
+
children: /* @__PURE__ */ h("div", { className: "flex h-full items-center justify-center px-4", children: /* @__PURE__ */ U(
|
|
188
188
|
"button",
|
|
189
189
|
{
|
|
190
190
|
type: "button",
|
|
191
|
-
onClick:
|
|
192
|
-
disabled:
|
|
193
|
-
style:
|
|
194
|
-
className: `${
|
|
191
|
+
onClick: l,
|
|
192
|
+
disabled: u,
|
|
193
|
+
style: Je[f],
|
|
194
|
+
className: `${fe} inline-flex items-center justify-center gap-1.5 h-[var(--malachite-control-height-medium,32px)] w-[108px] px-2.5 rounded-xl border border-solid text-[length:var(--malachite-font-size-small,13px)] font-medium whitespace-nowrap ${u ? "opacity-50 cursor-not-allowed" : "cursor-pointer"} ${T}`,
|
|
195
195
|
children: [
|
|
196
|
-
/* @__PURE__ */
|
|
197
|
-
|
|
196
|
+
/* @__PURE__ */ h("span", { className: "inline-flex h-[13px] w-[13px] shrink-0 items-center justify-center", children: a ?? /* @__PURE__ */ h(oe, { size: 13 }) }),
|
|
197
|
+
e
|
|
198
198
|
]
|
|
199
199
|
}
|
|
200
200
|
) })
|
|
201
201
|
}
|
|
202
|
-
),
|
|
202
|
+
), lt = (e) => C.isValidElement(e) && e.type === Vt, Qe = (e) => C.isValidElement(e) && e.type === C.Fragment && C.Children.toArray(e.props.children).some(lt), Ht = ({ side: e, onClick: a }) => /* @__PURE__ */ h(
|
|
203
203
|
"button",
|
|
204
204
|
{
|
|
205
205
|
type: "button",
|
|
206
206
|
tabIndex: -1,
|
|
207
207
|
"aria-hidden": "true",
|
|
208
|
-
"aria-label":
|
|
209
|
-
onClick:
|
|
208
|
+
"aria-label": e === "left" ? "Scroll left" : "Scroll right",
|
|
209
|
+
onClick: a,
|
|
210
210
|
className: "absolute top-1/2 -translate-y-1/2 z-10 inline-flex items-center justify-center w-5 h-5 rounded-lg cursor-pointer text-[var(--malachite-text-secondary)]",
|
|
211
211
|
style: {
|
|
212
|
-
[
|
|
213
|
-
background:
|
|
214
|
-
border: `1px solid ${
|
|
215
|
-
boxShadow:
|
|
212
|
+
[e]: `-${xe}px`,
|
|
213
|
+
background: Re,
|
|
214
|
+
border: `1px solid ${_e}`,
|
|
215
|
+
boxShadow: zt
|
|
216
216
|
},
|
|
217
|
-
children:
|
|
217
|
+
children: e === "left" ? /* @__PURE__ */ h(re, { size: vt }) : /* @__PURE__ */ h(ne, { size: vt })
|
|
218
218
|
}
|
|
219
|
-
),
|
|
220
|
-
value:
|
|
221
|
-
tone:
|
|
222
|
-
onChange:
|
|
223
|
-
children:
|
|
224
|
-
leading:
|
|
225
|
-
arrows:
|
|
226
|
-
fadeEdges:
|
|
227
|
-
snap:
|
|
228
|
-
bleed:
|
|
229
|
-
scrollSelectedIntoView:
|
|
230
|
-
"aria-label":
|
|
231
|
-
"aria-labelledby":
|
|
232
|
-
className:
|
|
219
|
+
), Ze = ({
|
|
220
|
+
value: e,
|
|
221
|
+
tone: a,
|
|
222
|
+
onChange: l,
|
|
223
|
+
children: u,
|
|
224
|
+
leading: f,
|
|
225
|
+
arrows: T = !0,
|
|
226
|
+
fadeEdges: I = !0,
|
|
227
|
+
snap: _ = !0,
|
|
228
|
+
bleed: L = !1,
|
|
229
|
+
scrollSelectedIntoView: m = !0,
|
|
230
|
+
"aria-label": j,
|
|
231
|
+
"aria-labelledby": J,
|
|
232
|
+
className: P = "",
|
|
233
233
|
style: b
|
|
234
234
|
}) => {
|
|
235
|
-
const
|
|
236
|
-
|
|
237
|
-
const
|
|
238
|
-
(
|
|
239
|
-
),
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
const
|
|
254
|
-
|
|
255
|
-
const
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
235
|
+
const $ = O(null), pt = O(null), k = O(null), ht = O(!1), [X, Mt] = nt({ canScrollLeft: !1, canScrollRight: !1 }), [qt, mt] = nt(!1), W = O(null), bt = O(!1);
|
|
236
|
+
Ot($, Ie, Pe), Ot($, Ne, ke);
|
|
237
|
+
const Q = C.Children.toArray(u), Z = Q.filter((t) => !lt(t)), Kt = Z.some(Qe), D = Z.length > 0, z = Q.filter(lt).map(
|
|
238
|
+
(t) => t.props.emphasis === void 0 ? C.cloneElement(t, { emphasis: D ? "secondary" : "primary" }) : t
|
|
239
|
+
), gt = Q.length, tt = z.length;
|
|
240
|
+
w(() => {
|
|
241
|
+
const t = $.current;
|
|
242
|
+
if (!t || !D || tt === 0) {
|
|
243
|
+
mt(!1);
|
|
244
|
+
return;
|
|
245
|
+
}
|
|
246
|
+
const r = pt.current, { paddingLeft: o, paddingRight: s } = getComputedStyle(t), i = (parseFloat(o) || 0) + (parseFloat(s) || 0);
|
|
247
|
+
let d = t.clientWidth - i, p = (r == null ? void 0 : r.offsetWidth) ?? 0;
|
|
248
|
+
const n = () => {
|
|
249
|
+
const y = p + tt * dt + Le;
|
|
250
|
+
mt(d > 0 && d < y);
|
|
251
|
+
};
|
|
252
|
+
n();
|
|
253
|
+
const c = typeof ResizeObserver < "u" ? new ResizeObserver((y) => {
|
|
254
|
+
var E, Tt;
|
|
255
|
+
for (const V of y ?? [])
|
|
256
|
+
V.target === t ? d = V.contentRect.width : p = ((Tt = (E = V.borderBoxSize) == null ? void 0 : E[0]) == null ? void 0 : Tt.inlineSize) ?? V.contentRect.width + 2 * v;
|
|
257
|
+
n();
|
|
258
|
+
}) : null;
|
|
259
|
+
return c == null || c.observe(t), r && (c == null || c.observe(r)), () => c == null ? void 0 : c.disconnect();
|
|
260
|
+
}, [D, tt, !!f]);
|
|
261
|
+
const et = qt && D, Rt = S((t) => {
|
|
262
|
+
var o;
|
|
263
|
+
const r = t.type === "focus" ? t.target : t.relatedTarget;
|
|
264
|
+
bt.current = !!(r instanceof Node && ((o = W.current) != null && o.contains(r)));
|
|
265
|
+
}, []);
|
|
266
|
+
w(() => {
|
|
267
|
+
var o;
|
|
268
|
+
const t = W.current, r = t ? t.getRootNode().activeElement : null;
|
|
269
|
+
bt.current && t && !(r && t.contains(r)) && ((o = t.querySelector("button")) == null || o.focus({ preventScroll: !0 }));
|
|
270
|
+
}, [et]);
|
|
271
|
+
const A = S(() => {
|
|
272
|
+
const t = $.current;
|
|
273
|
+
return t ? Array.from(t.querySelectorAll(G)) : [];
|
|
274
|
+
}, []), rt = S(
|
|
275
|
+
() => A().filter((t) => x(t) !== null),
|
|
276
|
+
[A]
|
|
277
|
+
), _t = O(/* @__PURE__ */ new Set()), N = S((t, r) => {
|
|
278
|
+
_t.current.has(t) || (_t.current.add(t), console.warn(r));
|
|
279
|
+
}, []), [jt, Jt] = nt("profile");
|
|
280
|
+
w(() => {
|
|
281
|
+
var r, o;
|
|
282
|
+
const t = (o = (r = A().find((s) => x(s) === e)) == null ? void 0 : r.parentElement) == null ? void 0 : o.getAttribute("data-variant");
|
|
283
|
+
(t === "profile" || t === "library" || t === "editing") && Jt(t);
|
|
284
|
+
}, [A, e]), w(() => {
|
|
285
|
+
const t = A(), r = rt(), s = r.find((n) => x(n) === e) ?? r.find((n) => !at(n)) ?? null;
|
|
286
|
+
if (t.forEach((n) => {
|
|
287
|
+
const c = n === s, y = c ? 0 : -1;
|
|
288
|
+
n.tabIndex !== y && (n.tabIndex = y);
|
|
289
|
+
for (const E of Be(n))
|
|
290
|
+
c ? He(E) : Ge(E);
|
|
259
291
|
}), process.env.NODE_ENV === "production")
|
|
260
292
|
return;
|
|
261
|
-
|
|
293
|
+
r.length !== t.length && N(
|
|
262
294
|
"missing-value",
|
|
263
295
|
'CardStrip: a child with role="radio" has no `data-value`. It cannot be selected or reached by arrow keys, so it is excluded from the radio set. Give every card a `data-value`.'
|
|
264
296
|
);
|
|
265
|
-
const
|
|
266
|
-
for (const
|
|
267
|
-
const
|
|
268
|
-
|
|
269
|
-
`duplicate:${
|
|
270
|
-
`CardStrip: two cards share the data-value "${
|
|
271
|
-
),
|
|
297
|
+
const i = /* @__PURE__ */ new Set();
|
|
298
|
+
for (const n of r) {
|
|
299
|
+
const c = x(n);
|
|
300
|
+
c !== null && (i.has(c) && N(
|
|
301
|
+
`duplicate:${c}`,
|
|
302
|
+
`CardStrip: two cards share the data-value "${c}". Both will read as checked and only the first is selectable — data-value must be unique.`
|
|
303
|
+
), i.add(c));
|
|
272
304
|
}
|
|
273
|
-
const
|
|
274
|
-
|
|
275
|
-
`unmatched:${
|
|
276
|
-
`CardStrip: value "${
|
|
305
|
+
const d = e !== "";
|
|
306
|
+
t.length > 0 && d && !i.has(e) && N(
|
|
307
|
+
`unmatched:${e}`,
|
|
308
|
+
`CardStrip: value "${e}" matches no card's data-value, so the group reports no selection.`
|
|
277
309
|
);
|
|
278
|
-
const
|
|
279
|
-
(
|
|
310
|
+
const p = t.filter(
|
|
311
|
+
(n) => n.getAttribute("aria-checked") === "true"
|
|
280
312
|
).length;
|
|
281
|
-
|
|
282
|
-
`checked:${
|
|
283
|
-
`CardStrip: ${
|
|
284
|
-
),
|
|
313
|
+
t.length > 0 && d && p !== 1 && N(
|
|
314
|
+
`checked:${p}`,
|
|
315
|
+
`CardStrip: ${p} cards are aria-checked, but a radiogroup has exactly one. Drive each card's checked state from the strip's \`value\`.`
|
|
316
|
+
), Kt && N(
|
|
285
317
|
"wrapped-add-tile",
|
|
286
318
|
"CardStrip: a CardStrip.AddTile inside a fragment is treated as a card and scrolls with them. The add tile must be a direct child of CardStrip."
|
|
287
319
|
);
|
|
288
|
-
})
|
|
289
|
-
|
|
320
|
+
});
|
|
321
|
+
const F = S(() => {
|
|
322
|
+
const t = A().find((r) => x(r) === e);
|
|
323
|
+
return t ? t.closest('[role="radio"]') ?? t : null;
|
|
324
|
+
}, [A, e]);
|
|
325
|
+
w(() => {
|
|
326
|
+
if (!m)
|
|
290
327
|
return;
|
|
291
|
-
const
|
|
292
|
-
if (!
|
|
328
|
+
const t = k.current, r = F();
|
|
329
|
+
if (!t || !r || typeof t.scrollTo != "function")
|
|
293
330
|
return;
|
|
294
|
-
const
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
331
|
+
const o = ht.current;
|
|
332
|
+
ht.current = !0;
|
|
333
|
+
const s = Dt(t, r, _);
|
|
334
|
+
s !== null && t.scrollTo({
|
|
335
|
+
left: s,
|
|
336
|
+
behavior: o && !Ut() ? "smooth" : "instant"
|
|
298
337
|
});
|
|
299
|
-
}, [
|
|
300
|
-
const
|
|
301
|
-
var
|
|
302
|
-
const
|
|
303
|
-
if (
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
(
|
|
307
|
-
|
|
308
|
-
), !
|
|
338
|
+
}, [F, m, _, gt]);
|
|
339
|
+
const At = a ?? jt, St = (b == null ? void 0 : b.background) !== void 0 || (b == null ? void 0 : b.backgroundImage) !== void 0, Y = S(() => {
|
|
340
|
+
var p;
|
|
341
|
+
const t = k.current;
|
|
342
|
+
if (St || Ye(
|
|
343
|
+
$.current,
|
|
344
|
+
t,
|
|
345
|
+
(p = A().find((n) => x(n) === e)) == null ? void 0 : p.parentElement,
|
|
346
|
+
At
|
|
347
|
+
), !t)
|
|
309
348
|
return;
|
|
310
|
-
const { scrollWidth:
|
|
311
|
-
canScrollLeft:
|
|
312
|
-
canScrollRight:
|
|
349
|
+
const { scrollWidth: r, clientWidth: o, scrollLeft: s } = t, i = r > o + ot, d = {
|
|
350
|
+
canScrollLeft: i && s > ot,
|
|
351
|
+
canScrollRight: i && s + o < r - ot
|
|
313
352
|
};
|
|
314
|
-
|
|
315
|
-
(
|
|
353
|
+
Mt(
|
|
354
|
+
(n) => n.canScrollLeft === d.canScrollLeft && n.canScrollRight === d.canScrollRight ? n : d
|
|
316
355
|
);
|
|
317
|
-
}, [
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
const
|
|
321
|
-
if (!
|
|
356
|
+
}, [St, A, At, e]);
|
|
357
|
+
w(() => {
|
|
358
|
+
Y();
|
|
359
|
+
const t = k.current;
|
|
360
|
+
if (!t)
|
|
322
361
|
return;
|
|
323
|
-
const
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
return a == null || a.observe(e), () => {
|
|
327
|
-
e.removeEventListener("scroll", n), a == null || a.disconnect();
|
|
362
|
+
const r = () => {
|
|
363
|
+
const c = F();
|
|
364
|
+
return c !== null && le(t, c);
|
|
328
365
|
};
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
366
|
+
let o = r();
|
|
367
|
+
const s = () => {
|
|
368
|
+
Y(), o = r();
|
|
369
|
+
};
|
|
370
|
+
t.addEventListener("scroll", s, { passive: !0 });
|
|
371
|
+
let i = 0;
|
|
372
|
+
const d = () => {
|
|
373
|
+
const c = F(), E = m && o && typeof t.scrollTo == "function" && c ? Dt(t, c, _) : null;
|
|
374
|
+
E !== null && t.scrollTo({ left: E, behavior: "instant" }), o = r();
|
|
375
|
+
}, p = () => {
|
|
376
|
+
if (Y(), typeof requestAnimationFrame != "function") {
|
|
377
|
+
d();
|
|
378
|
+
return;
|
|
379
|
+
}
|
|
380
|
+
cancelAnimationFrame(i), i = requestAnimationFrame(d);
|
|
381
|
+
}, n = typeof ResizeObserver < "u" ? new ResizeObserver(p) : null;
|
|
382
|
+
return n == null || n.observe(t), () => {
|
|
383
|
+
t.removeEventListener("scroll", s), n == null || n.disconnect(), typeof cancelAnimationFrame == "function" && cancelAnimationFrame(i);
|
|
384
|
+
};
|
|
385
|
+
}, [Y, F, m, _, gt]);
|
|
386
|
+
const xt = S(
|
|
387
|
+
(t) => {
|
|
388
|
+
const r = k.current;
|
|
389
|
+
!r || typeof r.scrollBy != "function" || r.scrollBy({
|
|
390
|
+
left: ie(r, t, _) - r.scrollLeft,
|
|
391
|
+
behavior: Ut() ? "auto" : "smooth"
|
|
392
|
+
});
|
|
393
|
+
},
|
|
394
|
+
[_]
|
|
395
|
+
), Qt = S(
|
|
396
|
+
(t) => {
|
|
397
|
+
if (!pe.has(t.key))
|
|
339
398
|
return;
|
|
340
|
-
const
|
|
341
|
-
if (!(
|
|
399
|
+
const r = t.target;
|
|
400
|
+
if (!(r instanceof Element) || !r.closest(G))
|
|
342
401
|
return;
|
|
343
|
-
const
|
|
344
|
-
if (
|
|
402
|
+
const o = rt().filter((n) => !at(n));
|
|
403
|
+
if (o.length === 0)
|
|
345
404
|
return;
|
|
346
|
-
|
|
347
|
-
const
|
|
348
|
-
let
|
|
349
|
-
if (
|
|
350
|
-
|
|
351
|
-
else if (
|
|
352
|
-
|
|
405
|
+
t.preventDefault();
|
|
406
|
+
const s = o.findIndex((n) => x(n) === e);
|
|
407
|
+
let i;
|
|
408
|
+
if (t.key === "Home")
|
|
409
|
+
i = 0;
|
|
410
|
+
else if (t.key === "End")
|
|
411
|
+
i = o.length - 1;
|
|
353
412
|
else {
|
|
354
|
-
const
|
|
355
|
-
|
|
413
|
+
const n = t.key === "ArrowLeft" || t.key === "ArrowUp" ? -1 : 1;
|
|
414
|
+
i = ((s === -1 ? 0 : s) + n + o.length) % o.length;
|
|
356
415
|
}
|
|
357
|
-
const
|
|
358
|
-
|
|
416
|
+
const d = o[i], p = x(d);
|
|
417
|
+
p !== null && p !== e && l(p), d.focus();
|
|
359
418
|
},
|
|
360
|
-
[
|
|
361
|
-
),
|
|
362
|
-
(
|
|
363
|
-
const
|
|
364
|
-
if (!(
|
|
419
|
+
[rt, l, e]
|
|
420
|
+
), Zt = S(
|
|
421
|
+
(t) => {
|
|
422
|
+
const r = t.target;
|
|
423
|
+
if (!(r instanceof Element) || r.closest(`.${R}`))
|
|
365
424
|
return;
|
|
366
|
-
const
|
|
367
|
-
if (!
|
|
425
|
+
const o = $.current;
|
|
426
|
+
if (!o)
|
|
368
427
|
return;
|
|
369
|
-
const
|
|
370
|
-
if (!
|
|
428
|
+
const s = Ue(r, o);
|
|
429
|
+
if (!s || at(s))
|
|
371
430
|
return;
|
|
372
|
-
const
|
|
373
|
-
|
|
431
|
+
const i = x(s);
|
|
432
|
+
i !== null && i !== e && l(i);
|
|
374
433
|
},
|
|
375
|
-
[
|
|
376
|
-
),
|
|
377
|
-
return /* @__PURE__ */
|
|
434
|
+
[l, e]
|
|
435
|
+
), te = T && X.canScrollLeft, ee = T && X.canScrollRight, Lt = I && X.canScrollLeft, Et = I && X.canScrollRight;
|
|
436
|
+
return /* @__PURE__ */ U(
|
|
378
437
|
"div",
|
|
379
438
|
{
|
|
380
|
-
ref:
|
|
381
|
-
onKeyDown:
|
|
382
|
-
|
|
383
|
-
|
|
439
|
+
ref: $,
|
|
440
|
+
onKeyDown: Qt,
|
|
441
|
+
onFocus: Rt,
|
|
442
|
+
onBlur: Rt,
|
|
443
|
+
onClick: Zt,
|
|
444
|
+
className: `${K} flex items-stretch ${P}`,
|
|
384
445
|
style: {
|
|
385
|
-
...
|
|
386
|
-
...
|
|
446
|
+
...We,
|
|
447
|
+
...L ? Ve : null,
|
|
387
448
|
...b
|
|
388
449
|
},
|
|
389
450
|
children: [
|
|
390
|
-
/* @__PURE__ */
|
|
451
|
+
/* @__PURE__ */ U(
|
|
391
452
|
"div",
|
|
392
453
|
{
|
|
393
454
|
role: "radiogroup",
|
|
394
|
-
"aria-label":
|
|
395
|
-
"aria-labelledby":
|
|
396
|
-
className:
|
|
455
|
+
"aria-label": j,
|
|
456
|
+
"aria-labelledby": J,
|
|
457
|
+
className: D ? "flex items-stretch min-w-0" : "flex items-stretch",
|
|
397
458
|
children: [
|
|
398
|
-
|
|
399
|
-
|
|
459
|
+
f ? /* @__PURE__ */ h(yt.Provider, { value: de, children: /* @__PURE__ */ h("div", { ref: pt, className: "shrink-0", style: Ke, children: f }) }) : null,
|
|
460
|
+
D ? /* @__PURE__ */ U(
|
|
400
461
|
"div",
|
|
401
462
|
{
|
|
402
|
-
[
|
|
463
|
+
[me]: "",
|
|
403
464
|
className: "relative min-w-0 shrink basis-auto",
|
|
404
|
-
style:
|
|
465
|
+
style: Me,
|
|
405
466
|
children: [
|
|
406
|
-
/* @__PURE__ */
|
|
467
|
+
/* @__PURE__ */ U(
|
|
407
468
|
"div",
|
|
408
469
|
{
|
|
409
|
-
ref:
|
|
470
|
+
ref: k,
|
|
410
471
|
tabIndex: -1,
|
|
411
|
-
[
|
|
412
|
-
"data-snap":
|
|
413
|
-
className:
|
|
414
|
-
children:
|
|
472
|
+
[he]: "",
|
|
473
|
+
"data-snap": _ ? "true" : "false",
|
|
474
|
+
className: g,
|
|
475
|
+
children: [
|
|
476
|
+
/* @__PURE__ */ h(yt.Provider, { value: ue, children: Z }),
|
|
477
|
+
et ? /* @__PURE__ */ h("div", { ref: W, [st]: "", children: z }) : null
|
|
478
|
+
]
|
|
415
479
|
}
|
|
416
480
|
),
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
481
|
+
te ? /* @__PURE__ */ h(Ht, { side: "left", onClick: () => xt(-1) }) : null,
|
|
482
|
+
ee ? /* @__PURE__ */ h(Ht, { side: "right", onClick: () => xt(1) }) : null,
|
|
483
|
+
Lt || Et ? /* @__PURE__ */ h(
|
|
420
484
|
"div",
|
|
421
485
|
{
|
|
422
486
|
"aria-hidden": !0,
|
|
423
487
|
"data-card-strip-fade": "",
|
|
424
|
-
className: `${
|
|
488
|
+
className: `${Xt} absolute pointer-events-none`,
|
|
425
489
|
style: {
|
|
426
|
-
inset: `-${
|
|
427
|
-
background: `linear-gradient(to right, ${
|
|
490
|
+
inset: `-${v}px -${Yt}px 0`,
|
|
491
|
+
background: `linear-gradient(to right, ${Lt ? Pt : "transparent"} 0, transparent ${It}px, transparent calc(100% - ${It}px), ${Et ? Pt : "transparent"} 100%)`
|
|
428
492
|
}
|
|
429
493
|
}
|
|
430
494
|
) : null
|
|
@@ -434,21 +498,22 @@ const Jt = /* @__PURE__ */ new Set(["ArrowLeft", "ArrowRight", "ArrowUp", "Arrow
|
|
|
434
498
|
]
|
|
435
499
|
}
|
|
436
500
|
),
|
|
437
|
-
|
|
501
|
+
z.length > 0 && !et ? /* @__PURE__ */ h(
|
|
438
502
|
"div",
|
|
439
503
|
{
|
|
504
|
+
ref: W,
|
|
440
505
|
className: "shrink-0",
|
|
441
|
-
style: !
|
|
442
|
-
children:
|
|
506
|
+
style: !D && f ? je : void 0,
|
|
507
|
+
children: z
|
|
443
508
|
}
|
|
444
509
|
) : null
|
|
445
510
|
]
|
|
446
511
|
}
|
|
447
512
|
);
|
|
448
513
|
};
|
|
449
|
-
|
|
514
|
+
Ze.AddTile = Vt;
|
|
450
515
|
export {
|
|
451
|
-
|
|
452
|
-
|
|
516
|
+
Ze as CardStrip,
|
|
517
|
+
Ze as default
|
|
453
518
|
};
|
|
454
519
|
//# sourceMappingURL=CardStrip.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CardStrip.js","sources":["../../src/components/CardStrip.tsx"],"sourcesContent":["import React, { useCallback, useLayoutEffect, useRef, useState } from 'react';\nimport ArrowLeftIcon from './CustomIcons/ArrowLeftIcon';\nimport ForwardArrowIcon from './CustomIcons/ForwardArrowIcon';\nimport PlusIcon from './CustomIcons/PlusIcon';\nimport { CONTENT_INSET } from './PageLayout/constants';\nimport {\n CARD_FLARE_PX,\n CARD_FLARE_TIP_PX,\n CARD_WIDTH_PX,\n FLARE_CLASS,\n STRIP_RULE_PX,\n} from './SelectableCard';\nimport { CardStripSlotContext, PINNED_SLOT, SCROLLED_SLOT } from './cardStripSlot';\nimport { FOCUS_RING } from './focusRing';\nimport { useAdoptedStyleSheet } from './useAdoptedStyleSheet';\n\nexport type CardStripAddTileEmphasis = 'primary' | 'secondary';\n\nexport interface CardStripAddTileProps {\n /** Visible label, e.g. \"Add Profile\". Also names the button for assistive tech. */\n label: string;\n /** Defaults to a plus. */\n icon?: React.ReactNode;\n onClick: () => void;\n disabled?: boolean;\n /** Leave unset: CardStrip picks `primary` while the strip is empty and `secondary` once cards exist. */\n emphasis?: CardStripAddTileEmphasis;\n className?: string;\n}\n\n/** Palette for the strip's rule. Pass the tone of whatever card is selected. */\nexport type CardStripTone = 'profile' | 'library' | 'editing';\n\nexport interface CardStripProps {\n /** The selected card's value, matched against each radio child's `data-value`. */\n value: string;\n /** Overrides the palette the strip reads off the selected card. See `CardStripTone`. */\n tone?: CardStripTone;\n onChange: (value: string) => void;\n /** Cards. Any element with `role=\"radio\"` and a `data-value` joins the group; an\n * add tile must be a direct child or it scrolls with the cards. */\n children: React.ReactNode;\n /** Pinned, non-scrolling slot before the cards (the Library card in the profile picker). */\n leading?: React.ReactNode;\n /** Circular scroll affordances, shown only on an edge that can actually scroll. */\n arrows?: boolean;\n /** Fade the cards out at any edge they scroll past. */\n fadeEdges?: boolean;\n /** Snap cards to the leading edge as the strip scrolls. */\n snap?: boolean;\n /** Run the bottom rule the full width of the page card while the cards stay on the gutter.\n * Only has an effect inside a layout that publishes its inset (`PageLayout`). */\n bleed?: boolean;\n /** Whether a selection change scrolls the selected card into view. The strip's opening\n * selection is placed instantly; later ones slide, unless motion is reduced. Set `false`\n * when the value comes from a URL, or a deep-linked strip below the fold jumps the page\n * on load. */\n scrollSelectedIntoView?: boolean;\n 'aria-label'?: string;\n 'aria-labelledby'?: string;\n className?: string;\n style?: React.CSSProperties;\n}\n\n/** Arrow keys move AND select, per the WAI-ARIA radiogroup pattern. */\nconst NAV_KEYS = new Set(['ArrowLeft', 'ArrowRight', 'ArrowUp', 'ArrowDown', 'Home', 'End']);\n\nconst RADIO_SELECTOR = '[role=\"radio\"]';\nconst SCROLLER_ATTR = 'data-card-strip-scroller';\nconst TROUGH_ATTR = 'data-card-strip-trough';\nconst ROOT_CLASS = 'malachite-card-strip';\nconst SCROLLER_CLASS = 'malachite-card-strip-scroller';\nconst FADE_CLASS = 'malachite-card-strip-fade';\nconst ADD_TILE_CLASS = 'malachite-card-strip-add-tile';\n\nconst ACTIONS_SELECTOR = '[data-selectable-card-actions]';\nconst FOCUSABLE_SELECTOR =\n 'a[href], area[href], button, input, select, textarea, iframe, [contenteditable=\"true\"], [tabindex]';\nconst PREV_TABINDEX_ATTR = 'data-card-strip-prev-tabindex';\n\nconst EDGE_EPSILON_PX = 1;\nconst SCROLL_STEP_RATIO = 0.8;\n\nconst scrollStepFor = (scroller: HTMLElement): number => {\n const [first, second] = Array.from(scroller.children);\n const pitch =\n first && second\n ? second.getBoundingClientRect().left - first.getBoundingClientRect().left\n : (first?.getBoundingClientRect().width ?? 0);\n // `clientWidth` counts the flare padding, which is not width a card can be read in.\n const visible = scroller.clientWidth - CARD_FLARE_PX * 2;\n\n if (pitch <= 0) {\n return visible * SCROLL_STEP_RATIO;\n }\n\n return Math.max(1, Math.floor(visible / pitch)) * pitch;\n};\nconst FADE_WIDTH_PX = 80;\n\nconst ARROW_SIZE_PX = 20;\nconst ARROW_GLYPH_PX = 11;\nconst GUTTER_REST_PX = 12;\nconst GUTTER_OPEN_PX = 52;\nconst ARROW_OFFSET_PX = (GUTTER_OPEN_PX + ARROW_SIZE_PX) / 2;\n\nconst LIP_SHADOW = 'var(--malachite-shadow-default)';\nconst ARROW_BACKGROUND =\n 'var(--malachite-card-strip-arrow-background, var(--malachite-card-background, #ffffff))';\nconst ARROW_BORDER = 'var(--malachite-card-strip-arrow-border, #e8e8e8)';\nconst TROUGH_BACKGROUND = 'var(--malachite-card-strip-background, #f1f1f1)';\nconst CARD_CORNER = 'var(--malachite-radius-xxl, 14px)';\nconst CARD_RADIUS = `${CARD_CORNER} ${CARD_CORNER} 0 0`;\nconst TROUGH_CORNER = 'var(--malachite-radius-xxxl, 16px)';\nconst TROUGH_RADIUS = `${TROUGH_CORNER} ${TROUGH_CORNER} 0 0`;\nconst TROUGH_SHOULDER_PX = 4;\nconst FADE_RAMP_PX = FADE_WIDTH_PX + (CARD_FLARE_PX - TROUGH_SHOULDER_PX);\nconst SLOT_WIDTH_PX = CARD_WIDTH_PX;\nconst DIVIDER_COLOR = 'var(--malachite-card-strip-divider, #dddddd)';\nconst FADE_COLOR = 'var(--malachite-card-strip-fade, transparent)';\nconst ADD_HOVER_BACKGROUND = 'var(--malachite-selectable-card-hover-background, #e8e8e8)';\nconst ADD_PILL_BORDER = 'var(--malachite-card-strip-arrow-border, #e8e8e8)';\nconst ADD_PILL_BORDER_ACTIVE = 'var(--malachite-selectable-card-hover-rule, #bbbbbb)';\nconst ADD_PILL_SHADOW_DEEP = '0px 1px 0px 0px rgba(0, 0, 0, 0.4)';\nconst ADD_PILL_BACKGROUND = 'var(--malachite-card-background, #ffffff)';\nconst ADD_PILL_FOREGROUND = 'var(--malachite-text-primary, #0a0a0a)';\nconst ADD_PILL_PRIMARY_BACKGROUND =\n 'var(--malachite-card-strip-add-primary-background, var(--malachite-text-primary, #0a0a0a))';\nconst ADD_PILL_PRIMARY_FOREGROUND =\n 'var(--malachite-card-strip-add-primary-foreground, var(--malachite-card-background, #ffffff))';\nconst ADD_PILL_PRIMARY_SHADOW = '0px 1px 0px 0px rgba(10, 10, 10, 0.7)';\n\nconst BOTTOM_RULE_WEIGHT_PX = STRIP_RULE_PX;\n\nconst OVERFLOW_INSET_PX = 16;\n\nconst CLIP_SLACK_PX = 9999;\nconst FOCUS_RING_REACH_PX = 4;\n\n/** A sheet, not inline styles: these rules have to reach children this component does not render. */\nconst SCROLLER_CSS = `\n.${ROOT_CLASS} {\n clip-path: inset(-${CLIP_SLACK_PX}px -${CLIP_SLACK_PX}px 0);\n}\n.${ROOT_CLASS}:has(:focus-visible) {\n clip-path: inset(-${CLIP_SLACK_PX}px -${CLIP_SLACK_PX}px -${FOCUS_RING_REACH_PX}px);\n}\n.${SCROLLER_CLASS} {\n display: flex;\n flex-wrap: nowrap;\n overflow-x: auto;\n overflow-y: hidden;\n scroll-behavior: smooth;\n scrollbar-width: none;\n -ms-overflow-style: none;\n padding-block: ${OVERFLOW_INSET_PX}px;\n margin-block: -${OVERFLOW_INSET_PX}px;\n /* overflow-x clips both axes, so the end cards would lose a flare; the negative margin trades\n the padding back out and scroll-padding keeps the rest position at zero. */\n padding-inline: ${CARD_FLARE_PX}px;\n margin-inline: -${CARD_FLARE_PX}px;\n scroll-padding-inline: ${CARD_FLARE_PX}px;\n}\n/* A card focused at a faded edge has to stay readable, so the fade lifts entirely. */\n.${ROOT_CLASS}:has(:focus-visible) .${FADE_CLASS} {\n display: none;\n}\n.${SCROLLER_CLASS}::-webkit-scrollbar {\n display: none;\n}\n/* No bottom edge: the strip's rule is the baseline for the add tile as much as for a card. */\n.${ADD_TILE_CLASS} > * {\n background: ${TROUGH_BACKGROUND};\n border-radius: ${CARD_RADIUS};\n}\n.${ADD_TILE_CLASS}:hover:not(:has(button:disabled)) > * {\n background: ${ADD_HOVER_BACKGROUND};\n}\n.${ADD_TILE_CLASS} button:active:not(:disabled) {\n opacity: 0.8;\n}\n.${ADD_TILE_CLASS}:has(button:active) > * {\n background: ${TROUGH_BACKGROUND};\n}\n.${ADD_TILE_CLASS}[data-emphasis='secondary'] button {\n border-color: ${ADD_PILL_BORDER};\n box-shadow: ${LIP_SHADOW};\n}\n.${ADD_TILE_CLASS}[data-emphasis='secondary']:hover:not(:has(button:disabled)) button {\n border-color: ${ADD_PILL_BORDER_ACTIVE};\n box-shadow: ${ADD_PILL_SHADOW_DEEP};\n}\n.${ADD_TILE_CLASS}[data-emphasis='secondary'] button:active:not(:disabled) {\n border-color: ${ADD_PILL_BORDER_ACTIVE};\n box-shadow: ${ADD_PILL_SHADOW_DEEP};\n}\n.${ADD_TILE_CLASS}[data-emphasis='primary'] button {\n border-color: transparent;\n box-shadow: ${ADD_PILL_PRIMARY_SHADOW};\n}\n.${SCROLLER_CLASS} > * {\n flex: 0 0 auto;\n min-width: 0;\n}\n.${SCROLLER_CLASS} > * + * {\n border-left: 1px solid ${DIVIDER_COLOR};\n}\n.${SCROLLER_CLASS}[data-snap='true'] {\n scroll-snap-type: x proximity;\n}\n.${SCROLLER_CLASS}[data-snap='true'] > * {\n scroll-snap-align: start;\n}\n@media (prefers-reduced-motion: reduce) {\n .${SCROLLER_CLASS} {\n scroll-behavior: auto;\n }\n .${SCROLLER_CLASS}[data-snap='true'] {\n scroll-snap-type: none;\n }\n .${SCROLLER_CLASS}[data-snap='true'] > * {\n scroll-snap-align: none;\n }\n}`;\n\nconst STYLE_ID = 'malachite-card-strip-style';\n\n/** `window.matchMedia` is absent under SSR and in older test environments. */\nconst prefersReducedMotion = (): boolean => {\n if (typeof window === 'undefined' || typeof window.matchMedia !== 'function') {\n return true;\n }\n return window.matchMedia('(prefers-reduced-motion: reduce)').matches;\n};\n\nconst radioValue = (radio: HTMLElement): string | null =>\n radio.dataset.value ?? radio.getAttribute('value');\n\nconst isRadioDisabled = (radio: HTMLElement): boolean =>\n radio.hasAttribute('disabled') || radio.getAttribute('aria-disabled') === 'true';\n\nconst actionsFor = (radio: HTMLElement): HTMLElement | null => {\n const card = radio.parentElement;\n if (!card || card.querySelectorAll(RADIO_SELECTOR).length !== 1) {\n return null;\n }\n return card.querySelector<HTMLElement>(ACTIONS_SELECTOR);\n};\n\n/** A card is wider than its radio, so `closest` misses clicks on the count row. */\nconst radioForClick = (target: Element, root: Element): HTMLElement | null => {\n const direct = target.closest<HTMLElement>(RADIO_SELECTOR);\n if (direct) {\n return direct;\n }\n if (target.closest(ACTIONS_SELECTOR)) {\n return null;\n }\n let node: Element | null = target;\n while (node && root.contains(node)) {\n const radios: NodeListOf<HTMLElement> = node.querySelectorAll(RADIO_SELECTOR);\n if (radios.length === 1 && radios[0].parentElement === node) {\n return radios[0];\n }\n node = node.parentElement;\n }\n return null;\n};\n\nconst actionControls = (radio: HTMLElement): HTMLElement[] => {\n const actions = actionsFor(radio);\n return actions ? Array.from(actions.querySelectorAll<HTMLElement>(FOCUSABLE_SELECTOR)) : [];\n};\n\nconst suppressTabStop = (element: HTMLElement): void => {\n if (!element.hasAttribute(PREV_TABINDEX_ATTR)) {\n element.setAttribute(PREV_TABINDEX_ATTR, element.getAttribute('tabindex') ?? '');\n }\n element.setAttribute('tabindex', '-1');\n};\n\nconst restoreTabStop = (element: HTMLElement): void => {\n if (!element.hasAttribute(PREV_TABINDEX_ATTR)) {\n return;\n }\n const previous = element.getAttribute(PREV_TABINDEX_ATTR);\n element.removeAttribute(PREV_TABINDEX_ATTR);\n if (previous) {\n element.setAttribute('tabindex', previous);\n } else {\n element.removeAttribute('tabindex');\n }\n};\n\nconst TONE_RULE: Record<CardStripTone, string> = {\n profile: 'var(--malachite-selectable-card-selected-border, #047b43)',\n library: 'var(--malachite-selectable-card-library-selected-border, #bbbbbb)',\n editing: 'var(--malachite-selectable-card-pending-foreground, #96580a)',\n};\nconst RULE_ROW_STYLE: React.CSSProperties = {\n backgroundRepeat: 'no-repeat',\n backgroundPosition: 'left bottom',\n backgroundSize: `100% ${BOTTOM_RULE_WEIGHT_PX}px`,\n};\n\n/** The rule runs under the flare's tip rather than butting against it, so antialiasing\n * cannot open the junction into a gap. */\nconst RULE_GAP_OVERLAP_PX = 0.5;\n\nconst FLARE_TIP_INSET_PX = CARD_FLARE_PX - CARD_FLARE_TIP_PX;\n\nconst flareTips = (card: HTMLElement): { left: number; right: number } | null => {\n const flare = card.querySelector(`:scope > .${FLARE_CLASS}`);\n if (!flare) {\n return null;\n }\n const { left, right } = flare.getBoundingClientRect();\n return { left: left + FLARE_TIP_INSET_PX, right: right - FLARE_TIP_INSET_PX };\n};\n\n/** The rule is cut where the selected card stands, never covered: the strip's clip lands on the\n * rule's own edge, so a cover takes the same partial alpha and the rule bleeds through it. */\nconst paintRule = (\n root: HTMLElement | null,\n scroller: HTMLElement | null,\n card: HTMLElement | null | undefined,\n tone: CardStripTone,\n): void => {\n if (!root) {\n return;\n }\n const colour = TONE_RULE[tone];\n const rootRect = root.getBoundingClientRect();\n const rootLeft = rootRect.left;\n const clip = card && scroller?.contains(card) ? scroller.getBoundingClientRect() : rootRect;\n const cardRect = card?.getBoundingClientRect();\n const tips = card ? flareTips(card) : null;\n const tipLeft = tips?.left ?? (cardRect ? cardRect.left - CARD_FLARE_TIP_PX : 0);\n const tipRight = tips?.right ?? (cardRect ? cardRect.right + CARD_FLARE_TIP_PX : 0);\n const from = cardRect ? Math.max(tipLeft, clip.left) + RULE_GAP_OVERLAP_PX - rootLeft : 0;\n const to = cardRect ? Math.min(tipRight, clip.right) - RULE_GAP_OVERLAP_PX - rootLeft : 0;\n\n root.style.backgroundImage =\n to > from\n ? `linear-gradient(to right, ${colour} 0 ${from}px, transparent ${from}px ${to}px, ${colour} ${to}px 100%)`\n : `linear-gradient(to right, ${colour} 0 100%)`;\n};\n\nconst BLEED_STYLE: React.CSSProperties = {\n marginInline: `calc(${CONTENT_INSET} * -1)`,\n paddingInline: CONTENT_INSET,\n};\n\n/** The rule's row is a margin, never padding: a background paints under its own padding, which\n * would hide the very line the troughs stand on. */\nconst TROUGH_SURFACE: React.CSSProperties = {\n background: TROUGH_BACKGROUND,\n borderRadius: TROUGH_RADIUS,\n paddingTop: `${TROUGH_SHOULDER_PX}px`,\n paddingInline: `${TROUGH_SHOULDER_PX}px`,\n marginBottom: `${BOTTOM_RULE_WEIGHT_PX}px`,\n};\nconst TROUGH_STYLE: React.CSSProperties = {\n ...TROUGH_SURFACE,\n marginInline: `${GUTTER_REST_PX}px`,\n};\nconst TROUGH_GUTTER_STYLES: Record<string, React.CSSProperties> = Object.fromEntries(\n [GUTTER_REST_PX, GUTTER_OPEN_PX].flatMap((start) =>\n [GUTTER_REST_PX, GUTTER_OPEN_PX].map((end) => [\n `${start}:${end}`,\n { ...TROUGH_SURFACE, marginInline: `${start}px ${end}px` },\n ]),\n ),\n);\nconst ADD_TILE_SHAPE_STYLE: React.CSSProperties = TROUGH_SURFACE;\n\nconst PINNED_SLOT_STYLE: React.CSSProperties = TROUGH_SURFACE;\n\nconst EMPTY_ADD_SLOT_STYLE: React.CSSProperties = {\n marginInlineStart: `${GUTTER_REST_PX}px`,\n};\n\nconst ADD_PILL_FILL: Record<CardStripAddTileEmphasis, React.CSSProperties> = {\n primary: {\n backgroundColor: ADD_PILL_PRIMARY_BACKGROUND,\n color: ADD_PILL_PRIMARY_FOREGROUND,\n },\n secondary: { backgroundColor: ADD_PILL_BACKGROUND, color: ADD_PILL_FOREGROUND },\n};\n\nconst AddTile: React.FC<CardStripAddTileProps> = ({\n label,\n icon,\n onClick,\n disabled = false,\n emphasis = 'secondary',\n className = '',\n}) => (\n <div\n className={ADD_TILE_CLASS}\n data-emphasis={emphasis}\n style={{\n ...ADD_TILE_SHAPE_STYLE,\n width: `${SLOT_WIDTH_PX}px`,\n height: `calc(100% - ${BOTTOM_RULE_WEIGHT_PX}px)`,\n }}\n >\n <div className=\"flex h-full items-center justify-center px-4\">\n <button\n type=\"button\"\n onClick={onClick}\n disabled={disabled}\n style={ADD_PILL_FILL[emphasis]}\n className={`${FOCUS_RING} inline-flex items-center justify-center gap-1.5 h-[var(--malachite-control-height-medium,32px)] w-[108px] px-2.5 rounded-xl border border-solid text-[length:var(--malachite-font-size-small,13px)] font-medium whitespace-nowrap ${\n disabled ? 'opacity-50 cursor-not-allowed' : 'cursor-pointer'\n } ${className}`}\n >\n <span className=\"inline-flex h-[13px] w-[13px] shrink-0 items-center justify-center\">\n {icon ?? <PlusIcon size={13} />}\n </span>\n {label}\n </button>\n </div>\n </div>\n);\n\nconst isAddTile = (node: React.ReactNode): node is React.ReactElement<CardStripAddTileProps> =>\n React.isValidElement(node) && node.type === AddTile;\n\nconst hidesAddTile = (child: React.ReactNode): boolean =>\n React.isValidElement<{ children?: React.ReactNode }>(child) &&\n child.type === React.Fragment &&\n React.Children.toArray(child.props.children).some(isAddTile);\n\ninterface ArrowButtonProps {\n side: 'left' | 'right';\n onClick: () => void;\n}\n\nconst ArrowButton: React.FC<ArrowButtonProps> = ({ side, onClick }) => (\n <button\n type=\"button\"\n tabIndex={-1}\n aria-hidden=\"true\"\n aria-label={side === 'left' ? 'Scroll left' : 'Scroll right'}\n onClick={onClick}\n className=\"absolute top-1/2 -translate-y-1/2 z-10 inline-flex items-center justify-center w-5 h-5 rounded-lg cursor-pointer text-[var(--malachite-text-secondary)]\"\n style={{\n [side]: `-${ARROW_OFFSET_PX}px`,\n background: ARROW_BACKGROUND,\n border: `1px solid ${ARROW_BORDER}`,\n boxShadow: LIP_SHADOW,\n }}\n >\n {side === 'left' ? (\n <ArrowLeftIcon size={ARROW_GLYPH_PX} />\n ) : (\n <ForwardArrowIcon size={ARROW_GLYPH_PX} />\n )}\n </button>\n);\n\n/** A horizontally scrolling radiogroup of cards — the \"pick one of these\" strip. */\nconst CardStrip: React.FC<CardStripProps> & { AddTile: React.FC<CardStripAddTileProps> } = ({\n value,\n tone,\n onChange,\n children,\n leading,\n arrows = true,\n fadeEdges = true,\n snap = true,\n bleed = false,\n scrollSelectedIntoView = true,\n 'aria-label': ariaLabel,\n 'aria-labelledby': ariaLabelledBy,\n className = '',\n style,\n}) => {\n const rootRef = useRef<HTMLDivElement>(null);\n const scrollerRef = useRef<HTMLDivElement>(null);\n const hasPositioned = useRef(false);\n const [edges, setEdges] = useState({ canScrollLeft: false, canScrollRight: false });\n\n useAdoptedStyleSheet(rootRef, SCROLLER_CSS, STYLE_ID);\n\n const childList = React.Children.toArray(children);\n const cards = childList.filter((child) => !isAddTile(child));\n const wrappedAddTile = cards.some(hidesAddTile);\n const hasCards = cards.length > 0;\n\n const addTiles = childList\n .filter(isAddTile)\n .map((tile) =>\n tile.props.emphasis === undefined\n ? React.cloneElement(tile, { emphasis: hasCards ? 'secondary' : 'primary' })\n : tile,\n );\n\n const childCount = childList.length;\n\n const getRadios = useCallback((): HTMLElement[] => {\n const root = rootRef.current;\n if (!root) {\n return [];\n }\n return Array.from(root.querySelectorAll<HTMLElement>(RADIO_SELECTOR));\n }, []);\n\n const getNavigableRadios = useCallback(\n (): HTMLElement[] => getRadios().filter((radio) => radioValue(radio) !== null),\n [getRadios],\n );\n\n const warned = useRef(new Set<string>());\n const warnOnce = useCallback((key: string, message: string): void => {\n if (warned.current.has(key)) {\n return;\n }\n warned.current.add(key);\n console.warn(message);\n }, []);\n\n const [derivedTone, setDerivedTone] = useState<CardStripTone>('profile');\n\n useLayoutEffect(() => {\n const selectedCard = getRadios()\n .find((radio) => radioValue(radio) === value)\n ?.parentElement?.getAttribute('data-variant');\n if (\n selectedCard === 'profile' ||\n selectedCard === 'library' ||\n selectedCard === 'editing'\n ) {\n setDerivedTone(selectedCard);\n }\n }, [getRadios, value]);\n\n useLayoutEffect(() => {\n const radios = getRadios();\n const navigable = getNavigableRadios();\n const selected = navigable.find((radio) => radioValue(radio) === value);\n const tabStop = selected ?? navigable.find((radio) => !isRadioDisabled(radio)) ?? null;\n\n radios.forEach((radio) => {\n const holdsTabStop = radio === tabStop;\n const nextTabIndex = holdsTabStop ? 0 : -1;\n if (radio.tabIndex !== nextTabIndex) {\n radio.tabIndex = nextTabIndex;\n }\n for (const control of actionControls(radio)) {\n if (holdsTabStop) {\n restoreTabStop(control);\n } else {\n suppressTabStop(control);\n }\n }\n });\n\n if (process.env.NODE_ENV === 'production') {\n return;\n }\n\n if (navigable.length !== radios.length) {\n warnOnce(\n 'missing-value',\n 'CardStrip: a child with role=\"radio\" has no `data-value`. It cannot be selected or reached by arrow keys, so it is excluded from the radio set. Give every card a `data-value`.',\n );\n }\n\n const values = new Set<string>();\n for (const radio of navigable) {\n const current = radioValue(radio);\n if (current === null) {\n continue;\n }\n if (values.has(current)) {\n warnOnce(\n `duplicate:${current}`,\n `CardStrip: two cards share the data-value \"${current}\". Both will read as checked and only the first is selectable — data-value must be unique.`,\n );\n }\n values.add(current);\n }\n\n const selectionSettled = value !== '';\n\n if (radios.length > 0 && selectionSettled && !values.has(value)) {\n warnOnce(\n `unmatched:${value}`,\n `CardStrip: value \"${value}\" matches no card's data-value, so the group reports no selection.`,\n );\n }\n\n const checked = radios.filter(\n (radio) => radio.getAttribute('aria-checked') === 'true',\n ).length;\n if (radios.length > 0 && selectionSettled && checked !== 1) {\n warnOnce(\n `checked:${checked}`,\n `CardStrip: ${checked} cards are aria-checked, but a radiogroup has exactly one. Drive each card's checked state from the strip's \\`value\\`.`,\n );\n }\n\n if (wrappedAddTile) {\n warnOnce(\n 'wrapped-add-tile',\n 'CardStrip: a CardStrip.AddTile inside a fragment is treated as a card and scrolls with them. The add tile must be a direct child of CardStrip.',\n );\n }\n });\n\n // biome-ignore lint/correctness/useExhaustiveDependencies: cards arriving is the signal\n useLayoutEffect(() => {\n if (!scrollSelectedIntoView) {\n return;\n }\n const scroller = scrollerRef.current;\n const selected = getRadios().find((radio) => radioValue(radio) === value);\n // jsdom implements neither, and a strip that cannot scroll has nothing to bring into view.\n if (!scroller || !selected || typeof scroller.scrollTo !== 'function') {\n return;\n }\n\n // Positioned rather than asked for: the track is scroll-snapped, and `scrollIntoView`\n // on a card several widths away lands back on the nearest snap point instead — so a\n // card appended past the end never came into view at all.\n // Measured against the scroller rather than an offset parent: the cards are flex\n // children whose `offsetParent` is not the scroller, so `offsetLeft` is not its frame.\n const card = (selected.closest('[role=\"radio\"]') ?? selected) as HTMLElement;\n const cardRect = card.getBoundingClientRect();\n const viewRect = scroller.getBoundingClientRect();\n const left = scroller.scrollLeft + (cardRect.left - viewRect.left);\n const right = left + cardRect.width;\n const visibleFrom = scroller.scrollLeft + CARD_FLARE_PX;\n const visibleTo = scroller.scrollLeft + scroller.clientWidth - CARD_FLARE_PX;\n\n // The first placement is where the strip opens — a deep link, not a move — so it is\n // instant. Every later one is the selection travelling, and travel is legible only if\n // you can see it happen.\n const wasPositioned = hasPositioned.current;\n hasPositioned.current = true;\n\n if (left >= visibleFrom && right <= visibleTo) {\n return;\n }\n\n scroller.scrollTo({\n left:\n right > visibleTo\n ? right - scroller.clientWidth + CARD_FLARE_PX\n : left - CARD_FLARE_PX,\n behavior: wasPositioned && !prefersReducedMotion() ? 'smooth' : 'instant',\n });\n }, [getRadios, scrollSelectedIntoView, value, childCount]);\n\n const ruleTone = tone ?? derivedTone;\n const callerPaintsBackground =\n style?.background !== undefined || style?.backgroundImage !== undefined;\n\n const measure = useCallback(() => {\n const scroller = scrollerRef.current;\n if (!callerPaintsBackground) {\n paintRule(\n rootRef.current,\n scroller,\n getRadios().find((radio) => radioValue(radio) === value)?.parentElement,\n ruleTone,\n );\n }\n if (!scroller) {\n return;\n }\n const { scrollWidth, clientWidth, scrollLeft } = scroller;\n const overflows = scrollWidth > clientWidth + EDGE_EPSILON_PX;\n const next = {\n canScrollLeft: overflows && scrollLeft > EDGE_EPSILON_PX,\n canScrollRight: overflows && scrollLeft + clientWidth < scrollWidth - EDGE_EPSILON_PX,\n };\n setEdges((previous) =>\n previous.canScrollLeft === next.canScrollLeft &&\n previous.canScrollRight === next.canScrollRight\n ? previous\n : next,\n );\n }, [callerPaintsBackground, getRadios, ruleTone, value]);\n\n // biome-ignore lint/correctness/useExhaustiveDependencies: adding or removing a card changes the scroll width, so re-measure on the count\n useLayoutEffect(() => {\n measure();\n\n const scroller = scrollerRef.current;\n if (!scroller) {\n return;\n }\n\n const onScroll = (): void => measure();\n scroller.addEventListener('scroll', onScroll, { passive: true });\n\n const observer =\n typeof ResizeObserver !== 'undefined' ? new ResizeObserver(() => measure()) : null;\n observer?.observe(scroller);\n\n return () => {\n scroller.removeEventListener('scroll', onScroll);\n observer?.disconnect();\n };\n }, [measure, childCount]);\n\n const scrollByStep = useCallback((direction: -1 | 1) => {\n const scroller = scrollerRef.current;\n if (!scroller || typeof scroller.scrollBy !== 'function') {\n return;\n }\n scroller.scrollBy({\n left: direction * scrollStepFor(scroller),\n behavior: prefersReducedMotion() ? 'auto' : 'smooth',\n });\n }, []);\n\n const handleKeyDown = useCallback(\n (event: React.KeyboardEvent) => {\n if (!NAV_KEYS.has(event.key)) {\n return;\n }\n\n const target = event.target;\n if (!(target instanceof Element) || !target.closest(RADIO_SELECTOR)) {\n return;\n }\n\n const enabled = getNavigableRadios().filter((radio) => !isRadioDisabled(radio));\n if (enabled.length === 0) {\n return;\n }\n\n event.preventDefault();\n\n const currentPosition = enabled.findIndex((radio) => radioValue(radio) === value);\n let nextPosition: number;\n if (event.key === 'Home') {\n nextPosition = 0;\n } else if (event.key === 'End') {\n nextPosition = enabled.length - 1;\n } else {\n const delta = event.key === 'ArrowLeft' || event.key === 'ArrowUp' ? -1 : 1;\n const from = currentPosition === -1 ? 0 : currentPosition;\n nextPosition = (from + delta + enabled.length) % enabled.length;\n }\n\n const next = enabled[nextPosition];\n const nextValue = radioValue(next);\n if (nextValue !== null && nextValue !== value) {\n onChange(nextValue);\n }\n next.focus();\n },\n [getNavigableRadios, onChange, value],\n );\n\n const handleClick = useCallback(\n (event: React.MouseEvent) => {\n const target = event.target;\n if (!(target instanceof Element)) {\n return;\n }\n const root = rootRef.current;\n if (!root) {\n return;\n }\n const radio = radioForClick(target, root);\n if (!radio || isRadioDisabled(radio)) {\n return;\n }\n const clicked = radioValue(radio);\n if (clicked !== null && clicked !== value) {\n onChange(clicked);\n }\n },\n [onChange, value],\n );\n\n const showLeftArrow = arrows && edges.canScrollLeft;\n const showRightArrow = arrows && edges.canScrollRight;\n\n const fadeLeft = fadeEdges && edges.canScrollLeft;\n const fadeRight = fadeEdges && edges.canScrollRight;\n const troughStyle = arrows\n ? TROUGH_GUTTER_STYLES[\n `${showLeftArrow ? GUTTER_OPEN_PX : GUTTER_REST_PX}:${\n showRightArrow ? GUTTER_OPEN_PX : GUTTER_REST_PX\n }`\n ]\n : TROUGH_STYLE;\n\n return (\n <div\n ref={rootRef}\n onKeyDown={handleKeyDown}\n onClick={handleClick}\n className={`${ROOT_CLASS} flex items-stretch ${className}`}\n style={{\n ...RULE_ROW_STYLE,\n ...(bleed ? BLEED_STYLE : null),\n ...style,\n }}\n >\n <div\n role=\"radiogroup\"\n aria-label={ariaLabel}\n aria-labelledby={ariaLabelledBy}\n className={hasCards ? 'flex items-stretch min-w-0' : 'flex items-stretch'}\n >\n {leading ? (\n <CardStripSlotContext.Provider value={PINNED_SLOT}>\n <div className=\"shrink-0\" style={PINNED_SLOT_STYLE}>\n {leading}\n </div>\n </CardStripSlotContext.Provider>\n ) : null}\n {hasCards ? (\n <div\n {...{ [TROUGH_ATTR]: '' }}\n className=\"relative min-w-0 shrink basis-auto\"\n style={troughStyle}\n >\n <div\n ref={scrollerRef}\n tabIndex={-1}\n {...{ [SCROLLER_ATTR]: '' }}\n data-snap={snap ? 'true' : 'false'}\n className={SCROLLER_CLASS}\n >\n <CardStripSlotContext.Provider value={SCROLLED_SLOT}>\n {cards}\n </CardStripSlotContext.Provider>\n </div>\n {showLeftArrow ? (\n <ArrowButton side=\"left\" onClick={() => scrollByStep(-1)} />\n ) : null}\n {showRightArrow ? (\n <ArrowButton side=\"right\" onClick={() => scrollByStep(1)} />\n ) : null}\n {fadeLeft || fadeRight ? (\n <div\n aria-hidden\n data-card-strip-fade=\"\"\n className={`${FADE_CLASS} absolute pointer-events-none`}\n style={{\n inset: `-${TROUGH_SHOULDER_PX}px -${CARD_FLARE_PX}px 0`,\n background: `linear-gradient(to right, ${fadeLeft ? FADE_COLOR : 'transparent'} 0, transparent ${FADE_RAMP_PX}px, transparent calc(100% - ${FADE_RAMP_PX}px), ${fadeRight ? FADE_COLOR : 'transparent'} 100%)`,\n }}\n />\n ) : null}\n </div>\n ) : null}\n </div>\n {addTiles.length > 0 ? (\n <div\n className=\"shrink-0\"\n style={!hasCards && leading ? EMPTY_ADD_SLOT_STYLE : undefined}\n >\n {addTiles}\n </div>\n ) : null}\n </div>\n );\n};\n\nCardStrip.AddTile = AddTile;\n\nexport default CardStrip;\nexport { CardStrip };\n"],"names":["NAV_KEYS","RADIO_SELECTOR","SCROLLER_ATTR","TROUGH_ATTR","ROOT_CLASS","SCROLLER_CLASS","FADE_CLASS","ADD_TILE_CLASS","ACTIONS_SELECTOR","FOCUSABLE_SELECTOR","PREV_TABINDEX_ATTR","EDGE_EPSILON_PX","SCROLL_STEP_RATIO","scrollStepFor","scroller","first","second","pitch","visible","CARD_FLARE_PX","FADE_WIDTH_PX","ARROW_SIZE_PX","ARROW_GLYPH_PX","GUTTER_REST_PX","GUTTER_OPEN_PX","ARROW_OFFSET_PX","LIP_SHADOW","ARROW_BACKGROUND","ARROW_BORDER","TROUGH_BACKGROUND","CARD_CORNER","CARD_RADIUS","TROUGH_CORNER","TROUGH_RADIUS","TROUGH_SHOULDER_PX","FADE_RAMP_PX","SLOT_WIDTH_PX","CARD_WIDTH_PX","DIVIDER_COLOR","FADE_COLOR","ADD_HOVER_BACKGROUND","ADD_PILL_BORDER","ADD_PILL_BORDER_ACTIVE","ADD_PILL_SHADOW_DEEP","ADD_PILL_BACKGROUND","ADD_PILL_FOREGROUND","ADD_PILL_PRIMARY_BACKGROUND","ADD_PILL_PRIMARY_FOREGROUND","ADD_PILL_PRIMARY_SHADOW","BOTTOM_RULE_WEIGHT_PX","STRIP_RULE_PX","OVERFLOW_INSET_PX","CLIP_SLACK_PX","FOCUS_RING_REACH_PX","SCROLLER_CSS","STYLE_ID","prefersReducedMotion","radioValue","radio","isRadioDisabled","actionsFor","card","radioForClick","target","root","direct","node","radios","actionControls","actions","suppressTabStop","element","restoreTabStop","previous","TONE_RULE","RULE_ROW_STYLE","RULE_GAP_OVERLAP_PX","FLARE_TIP_INSET_PX","CARD_FLARE_TIP_PX","flareTips","flare","FLARE_CLASS","left","right","paintRule","tone","colour","rootRect","rootLeft","clip","cardRect","tips","tipLeft","tipRight","from","to","BLEED_STYLE","CONTENT_INSET","TROUGH_SURFACE","TROUGH_STYLE","TROUGH_GUTTER_STYLES","start","end","ADD_TILE_SHAPE_STYLE","PINNED_SLOT_STYLE","EMPTY_ADD_SLOT_STYLE","ADD_PILL_FILL","AddTile","label","icon","onClick","disabled","emphasis","className","jsx","jsxs","FOCUS_RING","PlusIcon","isAddTile","React","hidesAddTile","child","ArrowButton","side","ArrowLeftIcon","ForwardArrowIcon","CardStrip","value","onChange","children","leading","arrows","fadeEdges","snap","bleed","scrollSelectedIntoView","ariaLabel","ariaLabelledBy","style","rootRef","useRef","scrollerRef","hasPositioned","edges","setEdges","useState","useAdoptedStyleSheet","childList","cards","wrappedAddTile","hasCards","addTiles","tile","childCount","getRadios","useCallback","getNavigableRadios","warned","warnOnce","key","message","derivedTone","setDerivedTone","useLayoutEffect","selectedCard","_b","_a","navigable","tabStop","holdsTabStop","nextTabIndex","control","values","current","selectionSettled","checked","selected","viewRect","visibleFrom","visibleTo","wasPositioned","ruleTone","callerPaintsBackground","measure","scrollWidth","clientWidth","scrollLeft","overflows","next","onScroll","observer","scrollByStep","direction","handleKeyDown","event","enabled","currentPosition","nextPosition","delta","nextValue","handleClick","clicked","showLeftArrow","showRightArrow","fadeLeft","fadeRight","troughStyle","CardStripSlotContext","PINNED_SLOT","SCROLLED_SLOT"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"CardStrip.js","sources":["../../src/components/CardStrip.tsx"],"sourcesContent":["import React, { useCallback, useLayoutEffect, useRef, useState } from 'react';\nimport ArrowLeftIcon from './CustomIcons/ArrowLeftIcon';\nimport ForwardArrowIcon from './CustomIcons/ForwardArrowIcon';\nimport PlusIcon from './CustomIcons/PlusIcon';\nimport { CONTENT_INSET } from './PageLayout/constants';\nimport {\n CARD_FLARE_PX,\n CARD_FLARE_TIP_PX,\n CARD_WIDTH_PX,\n FLARE_CLASS,\n STRIP_RULE_PX,\n} from './SelectableCard';\nimport {\n EDGE_EPSILON_PX,\n isCardInView,\n pageTargetFor,\n revealTargetFor,\n} from './cardStripGeometry';\nimport { CardStripSlotContext, PINNED_SLOT, SCROLLED_SLOT } from './cardStripSlot';\nimport { FOCUS_RING } from './focusRing';\nimport { useAdoptedStyleSheet } from './useAdoptedStyleSheet';\n\nexport type CardStripAddTileEmphasis = 'primary' | 'secondary';\n\nexport interface CardStripAddTileProps {\n /** Visible label, e.g. \"Add Profile\". Also names the button for assistive tech. */\n label: string;\n /** Defaults to a plus. */\n icon?: React.ReactNode;\n onClick: () => void;\n disabled?: boolean;\n /** Leave unset: CardStrip picks `primary` while the strip is empty and `secondary` once cards exist. */\n emphasis?: CardStripAddTileEmphasis;\n className?: string;\n}\n\n/** Palette for the strip's rule. Pass the tone of whatever card is selected. */\nexport type CardStripTone = 'profile' | 'library' | 'editing';\n\nexport interface CardStripProps {\n /** The selected card's value, matched against each radio child's `data-value`. */\n value: string;\n /** Overrides the palette the strip reads off the selected card. See `CardStripTone`. */\n tone?: CardStripTone;\n onChange: (value: string) => void;\n /** Cards. Any element with `role=\"radio\"` and a `data-value` joins the group; an\n * add tile must be a direct child or it scrolls with the cards. */\n children: React.ReactNode;\n /** Pinned, non-scrolling slot before the cards (the Library card in the profile picker). */\n leading?: React.ReactNode;\n /** Circular scroll affordances, shown only on an edge that can actually scroll. */\n arrows?: boolean;\n /** Fade the cards out at any edge they scroll past. */\n fadeEdges?: boolean;\n /** Snap cards to the leading edge as the strip scrolls. */\n snap?: boolean;\n /** Run the bottom rule the full width of the page card while the cards stay on the gutter.\n * Only has an effect inside a layout that publishes its inset (`PageLayout`). */\n bleed?: boolean;\n /** Whether a selection change scrolls the selected card into view. The strip's opening\n * selection is placed instantly; later ones slide, unless motion is reduced. Set `false`\n * when the value comes from a URL, or a deep-linked strip below the fold jumps the page\n * on load. */\n scrollSelectedIntoView?: boolean;\n 'aria-label'?: string;\n 'aria-labelledby'?: string;\n className?: string;\n style?: React.CSSProperties;\n}\n\n/** Arrow keys move AND select, per the WAI-ARIA radiogroup pattern. */\nconst NAV_KEYS = new Set(['ArrowLeft', 'ArrowRight', 'ArrowUp', 'ArrowDown', 'Home', 'End']);\n\nconst RADIO_SELECTOR = '[role=\"radio\"]';\nconst SCROLLER_ATTR = 'data-card-strip-scroller';\nconst TROUGH_ATTR = 'data-card-strip-trough';\nconst ROOT_CLASS = 'malachite-card-strip';\nconst SCROLLER_CLASS = 'malachite-card-strip-scroller';\nconst FADE_CLASS = 'malachite-card-strip-fade';\nconst ADD_TILE_CLASS = 'malachite-card-strip-add-tile';\nconst ADD_SLOT_ATTR = 'data-card-strip-add-slot';\n\nconst ACTIONS_SELECTOR = '[data-selectable-card-actions]';\nconst FOCUSABLE_SELECTOR =\n 'a[href], area[href], button, input, select, textarea, iframe, [contenteditable=\"true\"], [tabindex]';\nconst PREV_TABINDEX_ATTR = 'data-card-strip-prev-tabindex';\n\nconst FADE_WIDTH_PX = 80;\n\nconst ARROW_GLYPH_PX = 11;\nconst GUTTER_REST_PX = 12;\n\nconst LIP_SHADOW = 'var(--malachite-shadow-default)';\nconst ARROW_BACKGROUND =\n 'var(--malachite-card-strip-arrow-background, var(--malachite-card-background, #ffffff))';\nconst ARROW_BORDER = 'var(--malachite-card-strip-arrow-border, #e8e8e8)';\nconst TROUGH_BACKGROUND = 'var(--malachite-card-strip-background, #f1f1f1)';\nconst CARD_CORNER = 'var(--malachite-radius-xxl, 14px)';\nconst CARD_RADIUS = `${CARD_CORNER} ${CARD_CORNER} 0 0`;\nconst TROUGH_CORNER = 'var(--malachite-radius-xxxl, 16px)';\nconst TROUGH_RADIUS = `${TROUGH_CORNER} ${TROUGH_CORNER} 0 0`;\nconst TROUGH_SHOULDER_PX = 4;\n// Fills the gap between the edge card and its neighbour, covering neither.\nconst ARROW_OFFSET_PX = GUTTER_REST_PX + TROUGH_SHOULDER_PX;\n// Any wider and the fade paints over the neighbouring slot.\nconst FADE_OVERHANG_PX = GUTTER_REST_PX;\nconst FADE_RAMP_PX = FADE_WIDTH_PX + (FADE_OVERHANG_PX - TROUGH_SHOULDER_PX);\nconst SLOT_WIDTH_PX = CARD_WIDTH_PX;\nconst TROUGH_FLOOR_PX = 2 * (GUTTER_REST_PX + TROUGH_SHOULDER_PX) + SLOT_WIDTH_PX;\nconst DIVIDER_COLOR = 'var(--malachite-card-strip-divider, #dddddd)';\nconst FADE_COLOR = 'var(--malachite-card-strip-fade, transparent)';\nconst ADD_HOVER_BACKGROUND = 'var(--malachite-selectable-card-hover-background, #e8e8e8)';\nconst ADD_PILL_BORDER = 'var(--malachite-card-strip-arrow-border, #e8e8e8)';\nconst ADD_PILL_BORDER_ACTIVE = 'var(--malachite-selectable-card-hover-rule, #bbbbbb)';\nconst ADD_PILL_SHADOW_DEEP = '0px 1px 0px 0px rgba(0, 0, 0, 0.4)';\nconst ADD_PILL_BACKGROUND = 'var(--malachite-card-background, #ffffff)';\nconst ADD_PILL_FOREGROUND = 'var(--malachite-text-primary, #0a0a0a)';\nconst ADD_PILL_PRIMARY_BACKGROUND =\n 'var(--malachite-card-strip-add-primary-background, var(--malachite-text-primary, #0a0a0a))';\nconst ADD_PILL_PRIMARY_FOREGROUND =\n 'var(--malachite-card-strip-add-primary-foreground, var(--malachite-card-background, #ffffff))';\nconst ADD_PILL_PRIMARY_SHADOW = '0px 1px 0px 0px rgba(10, 10, 10, 0.7)';\n\nconst BOTTOM_RULE_WEIGHT_PX = STRIP_RULE_PX;\n\nconst OVERFLOW_INSET_PX = 16;\n\nconst CLIP_SLACK_PX = 9999;\nconst FOCUS_RING_REACH_PX = 4;\n\n/** A sheet, not inline styles: these rules have to reach children this component does not render. */\nconst SCROLLER_CSS = `\n.${ROOT_CLASS} {\n clip-path: inset(-${CLIP_SLACK_PX}px -${CLIP_SLACK_PX}px 0);\n}\n.${ROOT_CLASS}:has(:focus-visible) {\n clip-path: inset(-${CLIP_SLACK_PX}px -${CLIP_SLACK_PX}px -${FOCUS_RING_REACH_PX}px);\n}\n.${SCROLLER_CLASS} {\n display: flex;\n flex-wrap: nowrap;\n overflow-x: auto;\n overflow-y: hidden;\n scroll-behavior: smooth;\n scrollbar-width: none;\n -ms-overflow-style: none;\n padding-block: ${OVERFLOW_INSET_PX}px;\n margin-block: -${OVERFLOW_INSET_PX}px;\n /* overflow-x clips both axes, so the end cards would lose a flare; the negative margin trades\n the padding back out and scroll-padding keeps the rest position at zero. */\n padding-inline: ${CARD_FLARE_PX}px;\n margin-inline: -${CARD_FLARE_PX}px;\n scroll-padding-inline: ${CARD_FLARE_PX}px;\n}\n/* A card focused at a faded edge has to stay readable, so the fade lifts entirely. */\n.${ROOT_CLASS}:has(:focus-visible) .${FADE_CLASS} {\n display: none;\n}\n.${SCROLLER_CLASS}::-webkit-scrollbar {\n display: none;\n}\n/* No bottom edge: the strip's rule is the baseline for the add tile as much as for a card. */\n.${ADD_TILE_CLASS} > * {\n background: ${TROUGH_BACKGROUND};\n border-radius: ${CARD_RADIUS};\n}\n.${ADD_TILE_CLASS}:hover:not(:has(button:disabled)) > * {\n background: ${ADD_HOVER_BACKGROUND};\n}\n.${ADD_TILE_CLASS} button:active:not(:disabled) {\n opacity: 0.8;\n}\n.${ADD_TILE_CLASS}:has(button:active) > * {\n background: ${TROUGH_BACKGROUND};\n}\n.${ADD_TILE_CLASS}[data-emphasis='secondary'] button {\n border-color: ${ADD_PILL_BORDER};\n box-shadow: ${LIP_SHADOW};\n}\n.${ADD_TILE_CLASS}[data-emphasis='secondary']:hover:not(:has(button:disabled)) button {\n border-color: ${ADD_PILL_BORDER_ACTIVE};\n box-shadow: ${ADD_PILL_SHADOW_DEEP};\n}\n.${ADD_TILE_CLASS}[data-emphasis='secondary'] button:active:not(:disabled) {\n border-color: ${ADD_PILL_BORDER_ACTIVE};\n box-shadow: ${ADD_PILL_SHADOW_DEEP};\n}\n.${ADD_TILE_CLASS}[data-emphasis='primary'] button {\n border-color: transparent;\n box-shadow: ${ADD_PILL_PRIMARY_SHADOW};\n}\n.${SCROLLER_CLASS} > * {\n flex: 0 0 auto;\n min-width: 0;\n}\n.${SCROLLER_CLASS} > * + * {\n border-left: 1px solid ${DIVIDER_COLOR};\n}\n.${SCROLLER_CLASS}[data-snap='true'] {\n scroll-snap-type: x proximity;\n}\n.${SCROLLER_CLASS}[data-snap='true'] > * {\n scroll-snap-align: start;\n}\n@media (prefers-reduced-motion: reduce) {\n .${SCROLLER_CLASS} {\n scroll-behavior: auto;\n }\n .${SCROLLER_CLASS}[data-snap='true'] {\n scroll-snap-type: none;\n }\n .${SCROLLER_CLASS}[data-snap='true'] > * {\n scroll-snap-align: none;\n }\n}`;\n\nconst STYLE_ID = 'malachite-card-strip-style';\n// Its own id: an older malachite copy on the page may already own STYLE_ID without these rules.\nconst ADD_SLOT_STYLE_ID = 'malachite-card-strip-add-slot';\n// In the track the trough is already the tile's shoulder, and it is no card to rule off.\nconst ADD_SLOT_CSS = `\n.${SCROLLER_CLASS} > [${ADD_SLOT_ATTR}] {\n display: flex;\n border-left: none;\n}\n[${ADD_SLOT_ATTR}] > .${ADD_TILE_CLASS} {\n height: 100% !important;\n padding: 0 !important;\n}\n`;\n\n/** `window.matchMedia` is absent under SSR and in older test environments. */\nconst prefersReducedMotion = (): boolean => {\n if (typeof window === 'undefined' || typeof window.matchMedia !== 'function') {\n return true;\n }\n return window.matchMedia('(prefers-reduced-motion: reduce)').matches;\n};\n\nconst radioValue = (radio: HTMLElement): string | null =>\n radio.dataset.value ?? radio.getAttribute('value');\n\nconst isRadioDisabled = (radio: HTMLElement): boolean =>\n radio.hasAttribute('disabled') || radio.getAttribute('aria-disabled') === 'true';\n\nconst actionsFor = (radio: HTMLElement): HTMLElement | null => {\n const card = radio.parentElement;\n if (!card || card.querySelectorAll(RADIO_SELECTOR).length !== 1) {\n return null;\n }\n return card.querySelector<HTMLElement>(ACTIONS_SELECTOR);\n};\n\n/** A card is wider than its radio, so `closest` misses clicks on the count row. */\nconst radioForClick = (target: Element, root: Element): HTMLElement | null => {\n const direct = target.closest<HTMLElement>(RADIO_SELECTOR);\n if (direct) {\n return direct;\n }\n if (target.closest(ACTIONS_SELECTOR)) {\n return null;\n }\n let node: Element | null = target;\n while (node && root.contains(node)) {\n const radios: NodeListOf<HTMLElement> = node.querySelectorAll(RADIO_SELECTOR);\n if (radios.length === 1 && radios[0].parentElement === node) {\n return radios[0];\n }\n node = node.parentElement;\n }\n return null;\n};\n\nconst actionControls = (radio: HTMLElement): HTMLElement[] => {\n const actions = actionsFor(radio);\n return actions ? Array.from(actions.querySelectorAll<HTMLElement>(FOCUSABLE_SELECTOR)) : [];\n};\n\nconst suppressTabStop = (element: HTMLElement): void => {\n if (!element.hasAttribute(PREV_TABINDEX_ATTR)) {\n element.setAttribute(PREV_TABINDEX_ATTR, element.getAttribute('tabindex') ?? '');\n }\n element.setAttribute('tabindex', '-1');\n};\n\nconst restoreTabStop = (element: HTMLElement): void => {\n if (!element.hasAttribute(PREV_TABINDEX_ATTR)) {\n return;\n }\n const previous = element.getAttribute(PREV_TABINDEX_ATTR);\n element.removeAttribute(PREV_TABINDEX_ATTR);\n if (previous) {\n element.setAttribute('tabindex', previous);\n } else {\n element.removeAttribute('tabindex');\n }\n};\n\nconst TONE_RULE: Record<CardStripTone, string> = {\n profile: 'var(--malachite-selectable-card-selected-border, #047b43)',\n library: 'var(--malachite-selectable-card-library-selected-border, #bbbbbb)',\n editing: 'var(--malachite-selectable-card-pending-foreground, #96580a)',\n};\nconst RULE_ROW_STYLE: React.CSSProperties = {\n backgroundRepeat: 'no-repeat',\n backgroundPosition: 'left bottom',\n backgroundSize: `100% ${BOTTOM_RULE_WEIGHT_PX}px`,\n};\n\n/** The rule runs under the flare's tip rather than butting against it, so antialiasing\n * cannot open the junction into a gap. */\nconst RULE_GAP_OVERLAP_PX = 0.5;\n\nconst FLARE_TIP_INSET_PX = CARD_FLARE_PX - CARD_FLARE_TIP_PX;\n\nconst flareTips = (card: HTMLElement): { left: number; right: number } | null => {\n const flare = card.querySelector(`:scope > .${FLARE_CLASS}`);\n if (!flare) {\n return null;\n }\n const { left, right } = flare.getBoundingClientRect();\n return { left: left + FLARE_TIP_INSET_PX, right: right - FLARE_TIP_INSET_PX };\n};\n\n/** The rule is cut where the selected card stands, never covered: the strip's clip lands on the\n * rule's own edge, so a cover takes the same partial alpha and the rule bleeds through it. */\nconst paintRule = (\n root: HTMLElement | null,\n scroller: HTMLElement | null,\n card: HTMLElement | null | undefined,\n tone: CardStripTone,\n): void => {\n if (!root) {\n return;\n }\n const colour = TONE_RULE[tone];\n const rootRect = root.getBoundingClientRect();\n const rootLeft = rootRect.left;\n const clip = card && scroller?.contains(card) ? scroller.getBoundingClientRect() : rootRect;\n const cardRect = card?.getBoundingClientRect();\n const tips = card ? flareTips(card) : null;\n const tipLeft = tips?.left ?? (cardRect ? cardRect.left - CARD_FLARE_TIP_PX : 0);\n const tipRight = tips?.right ?? (cardRect ? cardRect.right + CARD_FLARE_TIP_PX : 0);\n const from = cardRect ? Math.max(tipLeft, clip.left) + RULE_GAP_OVERLAP_PX - rootLeft : 0;\n const to = cardRect ? Math.min(tipRight, clip.right) - RULE_GAP_OVERLAP_PX - rootLeft : 0;\n\n root.style.backgroundImage =\n to > from\n ? `linear-gradient(to right, ${colour} 0 ${from}px, transparent ${from}px ${to}px, ${colour} ${to}px 100%)`\n : `linear-gradient(to right, ${colour} 0 100%)`;\n};\n\nconst BLEED_STYLE: React.CSSProperties = {\n marginInline: `calc(${CONTENT_INSET} * -1)`,\n paddingInline: CONTENT_INSET,\n};\n\n/** The rule's row is a margin, never padding: a background paints under its own padding, which\n * would hide the very line the troughs stand on. */\nconst TROUGH_SURFACE: React.CSSProperties = {\n background: TROUGH_BACKGROUND,\n borderRadius: TROUGH_RADIUS,\n paddingTop: `${TROUGH_SHOULDER_PX}px`,\n paddingInline: `${TROUGH_SHOULDER_PX}px`,\n marginBottom: `${BOTTOM_RULE_WEIGHT_PX}px`,\n};\nconst TROUGH_STYLE: React.CSSProperties = {\n ...TROUGH_SURFACE,\n marginInline: `${GUTTER_REST_PX}px`,\n};\nconst ADD_TILE_SHAPE_STYLE: React.CSSProperties = TROUGH_SURFACE;\n\nconst PINNED_SLOT_STYLE: React.CSSProperties = TROUGH_SURFACE;\n\nconst EMPTY_ADD_SLOT_STYLE: React.CSSProperties = {\n marginInlineStart: `${GUTTER_REST_PX}px`,\n};\n\nconst ADD_PILL_FILL: Record<CardStripAddTileEmphasis, React.CSSProperties> = {\n primary: {\n backgroundColor: ADD_PILL_PRIMARY_BACKGROUND,\n color: ADD_PILL_PRIMARY_FOREGROUND,\n },\n secondary: { backgroundColor: ADD_PILL_BACKGROUND, color: ADD_PILL_FOREGROUND },\n};\n\nconst AddTile: React.FC<CardStripAddTileProps> = ({\n label,\n icon,\n onClick,\n disabled = false,\n emphasis = 'secondary',\n className = '',\n}) => (\n <div\n className={ADD_TILE_CLASS}\n data-emphasis={emphasis}\n style={{\n ...ADD_TILE_SHAPE_STYLE,\n width: `${SLOT_WIDTH_PX}px`,\n height: `calc(100% - ${BOTTOM_RULE_WEIGHT_PX}px)`,\n }}\n >\n <div className=\"flex h-full items-center justify-center px-4\">\n <button\n type=\"button\"\n onClick={onClick}\n disabled={disabled}\n style={ADD_PILL_FILL[emphasis]}\n className={`${FOCUS_RING} inline-flex items-center justify-center gap-1.5 h-[var(--malachite-control-height-medium,32px)] w-[108px] px-2.5 rounded-xl border border-solid text-[length:var(--malachite-font-size-small,13px)] font-medium whitespace-nowrap ${\n disabled ? 'opacity-50 cursor-not-allowed' : 'cursor-pointer'\n } ${className}`}\n >\n <span className=\"inline-flex h-[13px] w-[13px] shrink-0 items-center justify-center\">\n {icon ?? <PlusIcon size={13} />}\n </span>\n {label}\n </button>\n </div>\n </div>\n);\n\nconst isAddTile = (node: React.ReactNode): node is React.ReactElement<CardStripAddTileProps> =>\n React.isValidElement(node) && node.type === AddTile;\n\nconst hidesAddTile = (child: React.ReactNode): boolean =>\n React.isValidElement<{ children?: React.ReactNode }>(child) &&\n child.type === React.Fragment &&\n React.Children.toArray(child.props.children).some(isAddTile);\n\ninterface ArrowButtonProps {\n side: 'left' | 'right';\n onClick: () => void;\n}\n\nconst ArrowButton: React.FC<ArrowButtonProps> = ({ side, onClick }) => (\n <button\n type=\"button\"\n tabIndex={-1}\n aria-hidden=\"true\"\n aria-label={side === 'left' ? 'Scroll left' : 'Scroll right'}\n onClick={onClick}\n className=\"absolute top-1/2 -translate-y-1/2 z-10 inline-flex items-center justify-center w-5 h-5 rounded-lg cursor-pointer text-[var(--malachite-text-secondary)]\"\n style={{\n [side]: `-${ARROW_OFFSET_PX}px`,\n background: ARROW_BACKGROUND,\n border: `1px solid ${ARROW_BORDER}`,\n boxShadow: LIP_SHADOW,\n }}\n >\n {side === 'left' ? (\n <ArrowLeftIcon size={ARROW_GLYPH_PX} />\n ) : (\n <ForwardArrowIcon size={ARROW_GLYPH_PX} />\n )}\n </button>\n);\n\n/** A horizontally scrolling radiogroup of cards — the \"pick one of these\" strip. */\nconst CardStrip: React.FC<CardStripProps> & { AddTile: React.FC<CardStripAddTileProps> } = ({\n value,\n tone,\n onChange,\n children,\n leading,\n arrows = true,\n fadeEdges = true,\n snap = true,\n bleed = false,\n scrollSelectedIntoView = true,\n 'aria-label': ariaLabel,\n 'aria-labelledby': ariaLabelledBy,\n className = '',\n style,\n}) => {\n const rootRef = useRef<HTMLDivElement>(null);\n const pinnedRef = useRef<HTMLDivElement>(null);\n const scrollerRef = useRef<HTMLDivElement>(null);\n const hasPositioned = useRef(false);\n const [edges, setEdges] = useState({ canScrollLeft: false, canScrollRight: false });\n const [cramped, setCramped] = useState(false);\n const addHostRef = useRef<HTMLDivElement>(null);\n const addTileFocused = useRef(false);\n\n useAdoptedStyleSheet(rootRef, SCROLLER_CSS, STYLE_ID);\n useAdoptedStyleSheet(rootRef, ADD_SLOT_CSS, ADD_SLOT_STYLE_ID);\n\n const childList = React.Children.toArray(children);\n const cards = childList.filter((child) => !isAddTile(child));\n const wrappedAddTile = cards.some(hidesAddTile);\n const hasCards = cards.length > 0;\n\n const addTiles = childList\n .filter(isAddTile)\n .map((tile) =>\n tile.props.emphasis === undefined\n ? React.cloneElement(tile, { emphasis: hasCards ? 'secondary' : 'primary' })\n : tile,\n );\n\n const childCount = childList.length;\n const addTileCount = addTiles.length;\n const hasLeading = Boolean(leading);\n\n // With no room for a card between the pinned tiles, the add tile scrolls with the cards.\n // biome-ignore lint/correctness/useExhaustiveDependencies: a new pinned slot needs observing\n useLayoutEffect(() => {\n const root = rootRef.current;\n if (!root || !hasCards || addTileCount === 0) {\n setCramped(false);\n return;\n }\n const pinnedSlot = pinnedRef.current;\n const { paddingLeft, paddingRight } = getComputedStyle(root);\n const inset = (parseFloat(paddingLeft) || 0) + (parseFloat(paddingRight) || 0);\n let content = root.clientWidth - inset;\n let pinned = pinnedSlot?.offsetWidth ?? 0;\n const decide = (): void => {\n const needs = pinned + addTileCount * SLOT_WIDTH_PX + TROUGH_FLOOR_PX;\n setCramped(content > 0 && content < needs);\n };\n decide();\n // Sizes from the entries: reading layout here would force the layout the observer spares.\n const observer =\n typeof ResizeObserver !== 'undefined'\n ? new ResizeObserver((entries) => {\n for (const entry of entries ?? []) {\n if (entry.target === root) {\n content = entry.contentRect.width;\n } else {\n pinned =\n entry.borderBoxSize?.[0]?.inlineSize ??\n entry.contentRect.width + 2 * TROUGH_SHOULDER_PX;\n }\n }\n decide();\n })\n : null;\n observer?.observe(root);\n if (pinnedSlot) {\n observer?.observe(pinnedSlot);\n }\n return () => observer?.disconnect();\n }, [hasCards, addTileCount, hasLeading]);\n\n const addInTrack = cramped && hasCards;\n const trackAddTileFocus = useCallback((event: React.FocusEvent) => {\n const focused = event.type === 'focus' ? event.target : event.relatedTarget;\n addTileFocused.current = Boolean(\n focused instanceof Node && addHostRef.current?.contains(focused),\n );\n }, []);\n\n // The tile remounts wherever it moves, so focus has to follow it there. Removing a focused\n // element fires no blur, so the flag survives the move.\n // biome-ignore lint/correctness/useExhaustiveDependencies: the tile moving is the signal\n useLayoutEffect(() => {\n const host = addHostRef.current;\n const active = host ? (host.getRootNode() as Document | ShadowRoot).activeElement : null;\n if (addTileFocused.current && host && !(active && host.contains(active))) {\n host.querySelector('button')?.focus({ preventScroll: true });\n }\n }, [addInTrack]);\n\n const getRadios = useCallback((): HTMLElement[] => {\n const root = rootRef.current;\n if (!root) {\n return [];\n }\n return Array.from(root.querySelectorAll<HTMLElement>(RADIO_SELECTOR));\n }, []);\n\n const getNavigableRadios = useCallback(\n (): HTMLElement[] => getRadios().filter((radio) => radioValue(radio) !== null),\n [getRadios],\n );\n\n const warned = useRef(new Set<string>());\n const warnOnce = useCallback((key: string, message: string): void => {\n if (warned.current.has(key)) {\n return;\n }\n warned.current.add(key);\n console.warn(message);\n }, []);\n\n const [derivedTone, setDerivedTone] = useState<CardStripTone>('profile');\n\n useLayoutEffect(() => {\n const selectedCard = getRadios()\n .find((radio) => radioValue(radio) === value)\n ?.parentElement?.getAttribute('data-variant');\n if (\n selectedCard === 'profile' ||\n selectedCard === 'library' ||\n selectedCard === 'editing'\n ) {\n setDerivedTone(selectedCard);\n }\n }, [getRadios, value]);\n\n useLayoutEffect(() => {\n const radios = getRadios();\n const navigable = getNavigableRadios();\n const selected = navigable.find((radio) => radioValue(radio) === value);\n const tabStop = selected ?? navigable.find((radio) => !isRadioDisabled(radio)) ?? null;\n\n radios.forEach((radio) => {\n const holdsTabStop = radio === tabStop;\n const nextTabIndex = holdsTabStop ? 0 : -1;\n if (radio.tabIndex !== nextTabIndex) {\n radio.tabIndex = nextTabIndex;\n }\n for (const control of actionControls(radio)) {\n if (holdsTabStop) {\n restoreTabStop(control);\n } else {\n suppressTabStop(control);\n }\n }\n });\n\n if (process.env.NODE_ENV === 'production') {\n return;\n }\n\n if (navigable.length !== radios.length) {\n warnOnce(\n 'missing-value',\n 'CardStrip: a child with role=\"radio\" has no `data-value`. It cannot be selected or reached by arrow keys, so it is excluded from the radio set. Give every card a `data-value`.',\n );\n }\n\n const values = new Set<string>();\n for (const radio of navigable) {\n const current = radioValue(radio);\n if (current === null) {\n continue;\n }\n if (values.has(current)) {\n warnOnce(\n `duplicate:${current}`,\n `CardStrip: two cards share the data-value \"${current}\". Both will read as checked and only the first is selectable — data-value must be unique.`,\n );\n }\n values.add(current);\n }\n\n const selectionSettled = value !== '';\n\n if (radios.length > 0 && selectionSettled && !values.has(value)) {\n warnOnce(\n `unmatched:${value}`,\n `CardStrip: value \"${value}\" matches no card's data-value, so the group reports no selection.`,\n );\n }\n\n const checked = radios.filter(\n (radio) => radio.getAttribute('aria-checked') === 'true',\n ).length;\n if (radios.length > 0 && selectionSettled && checked !== 1) {\n warnOnce(\n `checked:${checked}`,\n `CardStrip: ${checked} cards are aria-checked, but a radiogroup has exactly one. Drive each card's checked state from the strip's \\`value\\`.`,\n );\n }\n\n if (wrappedAddTile) {\n warnOnce(\n 'wrapped-add-tile',\n 'CardStrip: a CardStrip.AddTile inside a fragment is treated as a card and scrolls with them. The add tile must be a direct child of CardStrip.',\n );\n }\n });\n\n const selectedCard = useCallback((): HTMLElement | null => {\n const selected = getRadios().find((radio) => radioValue(radio) === value);\n return selected ? ((selected.closest('[role=\"radio\"]') ?? selected) as HTMLElement) : null;\n }, [getRadios, value]);\n\n // biome-ignore lint/correctness/useExhaustiveDependencies: cards arriving is the signal\n useLayoutEffect(() => {\n if (!scrollSelectedIntoView) {\n return;\n }\n const scroller = scrollerRef.current;\n const card = selectedCard();\n // jsdom implements neither, and a strip that cannot scroll has nothing to bring into view.\n if (!scroller || !card || typeof scroller.scrollTo !== 'function') {\n return;\n }\n\n // The first placement is where the strip opens — a deep link, not a move — so it is\n // instant. Every later one is the selection travelling, and travel is legible only if\n // you can see it happen.\n const wasPositioned = hasPositioned.current;\n hasPositioned.current = true;\n\n const target = revealTargetFor(scroller, card, snap);\n if (target !== null) {\n scroller.scrollTo({\n left: target,\n behavior: wasPositioned && !prefersReducedMotion() ? 'smooth' : 'instant',\n });\n }\n }, [selectedCard, scrollSelectedIntoView, snap, childCount]);\n\n const ruleTone = tone ?? derivedTone;\n const callerPaintsBackground =\n style?.background !== undefined || style?.backgroundImage !== undefined;\n\n const measure = useCallback(() => {\n const scroller = scrollerRef.current;\n if (!callerPaintsBackground) {\n paintRule(\n rootRef.current,\n scroller,\n getRadios().find((radio) => radioValue(radio) === value)?.parentElement,\n ruleTone,\n );\n }\n if (!scroller) {\n return;\n }\n const { scrollWidth, clientWidth, scrollLeft } = scroller;\n const overflows = scrollWidth > clientWidth + EDGE_EPSILON_PX;\n const next = {\n canScrollLeft: overflows && scrollLeft > EDGE_EPSILON_PX,\n canScrollRight: overflows && scrollLeft + clientWidth < scrollWidth - EDGE_EPSILON_PX,\n };\n setEdges((previous) =>\n previous.canScrollLeft === next.canScrollLeft &&\n previous.canScrollRight === next.canScrollRight\n ? previous\n : next,\n );\n }, [callerPaintsBackground, getRadios, ruleTone, value]);\n\n // biome-ignore lint/correctness/useExhaustiveDependencies: adding or removing a card changes the scroll width, so re-measure on the count\n useLayoutEffect(() => {\n measure();\n\n const scroller = scrollerRef.current;\n if (!scroller) {\n return;\n }\n\n const selectedInView = (): boolean => {\n const card = selectedCard();\n return card !== null && isCardInView(scroller, card);\n };\n let wasInView = selectedInView();\n\n const onScroll = (): void => {\n measure();\n wasInView = selectedInView();\n };\n scroller.addEventListener('scroll', onScroll, { passive: true });\n\n // Bring back a selected card the resize cut off, unless the reader scrolled it away.\n // Next frame rather than in the callback, whose layout reads would force a layout.\n let frame = 0;\n const reveal = (): void => {\n const card = selectedCard();\n const canReveal =\n scrollSelectedIntoView && wasInView && typeof scroller.scrollTo === 'function';\n const target = canReveal && card ? revealTargetFor(scroller, card, snap) : null;\n if (target !== null) {\n scroller.scrollTo({ left: target, behavior: 'instant' });\n }\n wasInView = selectedInView();\n };\n const onResize = (): void => {\n measure();\n if (typeof requestAnimationFrame !== 'function') {\n reveal();\n return;\n }\n cancelAnimationFrame(frame);\n frame = requestAnimationFrame(reveal);\n };\n const observer =\n typeof ResizeObserver !== 'undefined' ? new ResizeObserver(onResize) : null;\n observer?.observe(scroller);\n\n return () => {\n scroller.removeEventListener('scroll', onScroll);\n observer?.disconnect();\n if (typeof cancelAnimationFrame === 'function') {\n cancelAnimationFrame(frame);\n }\n };\n }, [measure, selectedCard, scrollSelectedIntoView, snap, childCount]);\n\n const scrollByStep = useCallback(\n (direction: -1 | 1) => {\n const scroller = scrollerRef.current;\n if (!scroller || typeof scroller.scrollBy !== 'function') {\n return;\n }\n scroller.scrollBy({\n left: pageTargetFor(scroller, direction, snap) - scroller.scrollLeft,\n behavior: prefersReducedMotion() ? 'auto' : 'smooth',\n });\n },\n [snap],\n );\n\n const handleKeyDown = useCallback(\n (event: React.KeyboardEvent) => {\n if (!NAV_KEYS.has(event.key)) {\n return;\n }\n\n const target = event.target;\n if (!(target instanceof Element) || !target.closest(RADIO_SELECTOR)) {\n return;\n }\n\n const enabled = getNavigableRadios().filter((radio) => !isRadioDisabled(radio));\n if (enabled.length === 0) {\n return;\n }\n\n event.preventDefault();\n\n const currentPosition = enabled.findIndex((radio) => radioValue(radio) === value);\n let nextPosition: number;\n if (event.key === 'Home') {\n nextPosition = 0;\n } else if (event.key === 'End') {\n nextPosition = enabled.length - 1;\n } else {\n const delta = event.key === 'ArrowLeft' || event.key === 'ArrowUp' ? -1 : 1;\n const from = currentPosition === -1 ? 0 : currentPosition;\n nextPosition = (from + delta + enabled.length) % enabled.length;\n }\n\n const next = enabled[nextPosition];\n const nextValue = radioValue(next);\n if (nextValue !== null && nextValue !== value) {\n onChange(nextValue);\n }\n next.focus();\n },\n [getNavigableRadios, onChange, value],\n );\n\n const handleClick = useCallback(\n (event: React.MouseEvent) => {\n const target = event.target;\n if (!(target instanceof Element) || target.closest(`.${ADD_TILE_CLASS}`)) {\n return;\n }\n const root = rootRef.current;\n if (!root) {\n return;\n }\n const radio = radioForClick(target, root);\n if (!radio || isRadioDisabled(radio)) {\n return;\n }\n const clicked = radioValue(radio);\n if (clicked !== null && clicked !== value) {\n onChange(clicked);\n }\n },\n [onChange, value],\n );\n\n const showLeftArrow = arrows && edges.canScrollLeft;\n const showRightArrow = arrows && edges.canScrollRight;\n\n const fadeLeft = fadeEdges && edges.canScrollLeft;\n const fadeRight = fadeEdges && edges.canScrollRight;\n\n return (\n <div\n ref={rootRef}\n onKeyDown={handleKeyDown}\n onFocus={trackAddTileFocus}\n onBlur={trackAddTileFocus}\n onClick={handleClick}\n className={`${ROOT_CLASS} flex items-stretch ${className}`}\n style={{\n ...RULE_ROW_STYLE,\n ...(bleed ? BLEED_STYLE : null),\n ...style,\n }}\n >\n <div\n role=\"radiogroup\"\n aria-label={ariaLabel}\n aria-labelledby={ariaLabelledBy}\n className={hasCards ? 'flex items-stretch min-w-0' : 'flex items-stretch'}\n >\n {leading ? (\n <CardStripSlotContext.Provider value={PINNED_SLOT}>\n <div ref={pinnedRef} className=\"shrink-0\" style={PINNED_SLOT_STYLE}>\n {leading}\n </div>\n </CardStripSlotContext.Provider>\n ) : null}\n {hasCards ? (\n <div\n {...{ [TROUGH_ATTR]: '' }}\n className=\"relative min-w-0 shrink basis-auto\"\n style={TROUGH_STYLE}\n >\n <div\n ref={scrollerRef}\n tabIndex={-1}\n {...{ [SCROLLER_ATTR]: '' }}\n data-snap={snap ? 'true' : 'false'}\n className={SCROLLER_CLASS}\n >\n <CardStripSlotContext.Provider value={SCROLLED_SLOT}>\n {cards}\n </CardStripSlotContext.Provider>\n {addInTrack ? (\n <div ref={addHostRef} {...{ [ADD_SLOT_ATTR]: '' }}>\n {addTiles}\n </div>\n ) : null}\n </div>\n {showLeftArrow ? (\n <ArrowButton side=\"left\" onClick={() => scrollByStep(-1)} />\n ) : null}\n {showRightArrow ? (\n <ArrowButton side=\"right\" onClick={() => scrollByStep(1)} />\n ) : null}\n {fadeLeft || fadeRight ? (\n <div\n aria-hidden\n data-card-strip-fade=\"\"\n className={`${FADE_CLASS} absolute pointer-events-none`}\n style={{\n inset: `-${TROUGH_SHOULDER_PX}px -${FADE_OVERHANG_PX}px 0`,\n background: `linear-gradient(to right, ${fadeLeft ? FADE_COLOR : 'transparent'} 0, transparent ${FADE_RAMP_PX}px, transparent calc(100% - ${FADE_RAMP_PX}px), ${fadeRight ? FADE_COLOR : 'transparent'} 100%)`,\n }}\n />\n ) : null}\n </div>\n ) : null}\n </div>\n {addTiles.length > 0 && !addInTrack ? (\n <div\n ref={addHostRef}\n className=\"shrink-0\"\n style={!hasCards && leading ? EMPTY_ADD_SLOT_STYLE : undefined}\n >\n {addTiles}\n </div>\n ) : null}\n </div>\n );\n};\n\nCardStrip.AddTile = AddTile;\n\nexport default CardStrip;\nexport { CardStrip };\n"],"names":["NAV_KEYS","RADIO_SELECTOR","SCROLLER_ATTR","TROUGH_ATTR","ROOT_CLASS","SCROLLER_CLASS","FADE_CLASS","ADD_TILE_CLASS","ADD_SLOT_ATTR","ACTIONS_SELECTOR","FOCUSABLE_SELECTOR","PREV_TABINDEX_ATTR","FADE_WIDTH_PX","ARROW_GLYPH_PX","GUTTER_REST_PX","LIP_SHADOW","ARROW_BACKGROUND","ARROW_BORDER","TROUGH_BACKGROUND","CARD_CORNER","CARD_RADIUS","TROUGH_CORNER","TROUGH_RADIUS","TROUGH_SHOULDER_PX","ARROW_OFFSET_PX","FADE_OVERHANG_PX","FADE_RAMP_PX","SLOT_WIDTH_PX","CARD_WIDTH_PX","TROUGH_FLOOR_PX","DIVIDER_COLOR","FADE_COLOR","ADD_HOVER_BACKGROUND","ADD_PILL_BORDER","ADD_PILL_BORDER_ACTIVE","ADD_PILL_SHADOW_DEEP","ADD_PILL_BACKGROUND","ADD_PILL_FOREGROUND","ADD_PILL_PRIMARY_BACKGROUND","ADD_PILL_PRIMARY_FOREGROUND","ADD_PILL_PRIMARY_SHADOW","BOTTOM_RULE_WEIGHT_PX","STRIP_RULE_PX","OVERFLOW_INSET_PX","CLIP_SLACK_PX","FOCUS_RING_REACH_PX","SCROLLER_CSS","CARD_FLARE_PX","STYLE_ID","ADD_SLOT_STYLE_ID","ADD_SLOT_CSS","prefersReducedMotion","radioValue","radio","isRadioDisabled","actionsFor","card","radioForClick","target","root","direct","node","radios","actionControls","actions","suppressTabStop","element","restoreTabStop","previous","TONE_RULE","RULE_ROW_STYLE","RULE_GAP_OVERLAP_PX","FLARE_TIP_INSET_PX","CARD_FLARE_TIP_PX","flareTips","flare","FLARE_CLASS","left","right","paintRule","scroller","tone","colour","rootRect","rootLeft","clip","cardRect","tips","tipLeft","tipRight","from","to","BLEED_STYLE","CONTENT_INSET","TROUGH_SURFACE","TROUGH_STYLE","ADD_TILE_SHAPE_STYLE","PINNED_SLOT_STYLE","EMPTY_ADD_SLOT_STYLE","ADD_PILL_FILL","AddTile","label","icon","onClick","disabled","emphasis","className","jsx","jsxs","FOCUS_RING","PlusIcon","isAddTile","React","hidesAddTile","child","ArrowButton","side","ArrowLeftIcon","ForwardArrowIcon","CardStrip","value","onChange","children","leading","arrows","fadeEdges","snap","bleed","scrollSelectedIntoView","ariaLabel","ariaLabelledBy","style","rootRef","useRef","pinnedRef","scrollerRef","hasPositioned","edges","setEdges","useState","cramped","setCramped","addHostRef","addTileFocused","useAdoptedStyleSheet","childList","cards","wrappedAddTile","hasCards","addTiles","tile","childCount","addTileCount","useLayoutEffect","pinnedSlot","paddingLeft","paddingRight","inset","content","pinned","decide","needs","observer","entries","entry","_b","_a","addInTrack","trackAddTileFocus","useCallback","event","focused","host","active","getRadios","getNavigableRadios","warned","warnOnce","key","message","derivedTone","setDerivedTone","selectedCard","navigable","tabStop","holdsTabStop","nextTabIndex","control","values","current","selectionSettled","checked","selected","wasPositioned","revealTargetFor","ruleTone","callerPaintsBackground","measure","scrollWidth","clientWidth","scrollLeft","overflows","EDGE_EPSILON_PX","next","selectedInView","isCardInView","wasInView","onScroll","frame","reveal","onResize","scrollByStep","direction","pageTargetFor","handleKeyDown","enabled","currentPosition","nextPosition","delta","nextValue","handleClick","clicked","showLeftArrow","showRightArrow","fadeLeft","fadeRight","CardStripSlotContext","PINNED_SLOT","SCROLLED_SLOT"],"mappings":"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"}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { CARD_FLARE_PX as s } from "./SelectableCard.js";
|
|
2
|
+
const r = 1, m = 0.8, R = (t) => {
|
|
3
|
+
const [n, i] = Array.from(t.children), o = n && i ? i.getBoundingClientRect().left - n.getBoundingClientRect().left : (n == null ? void 0 : n.getBoundingClientRect().width) ?? 0, e = t.clientWidth - s * 2;
|
|
4
|
+
return o <= 0 ? e * m : Math.max(1, Math.floor(e / o)) * o;
|
|
5
|
+
}, l = (t, n) => {
|
|
6
|
+
const i = n.getBoundingClientRect(), o = t.getBoundingClientRect(), e = t.scrollLeft + (i.left - o.left);
|
|
7
|
+
return {
|
|
8
|
+
left: e,
|
|
9
|
+
right: e + i.width,
|
|
10
|
+
visibleFrom: t.scrollLeft + s,
|
|
11
|
+
visibleTo: t.scrollLeft + t.clientWidth - s
|
|
12
|
+
};
|
|
13
|
+
}, d = (t, n) => {
|
|
14
|
+
if (!t.contains(n))
|
|
15
|
+
return !1;
|
|
16
|
+
const { left: i, right: o, visibleFrom: e, visibleTo: f } = l(t, n);
|
|
17
|
+
return i >= e - r && o <= f + r;
|
|
18
|
+
}, h = (t) => {
|
|
19
|
+
const n = t.getBoundingClientRect().left;
|
|
20
|
+
return Array.from(t.children).map(
|
|
21
|
+
(i) => t.scrollLeft + (i.getBoundingClientRect().left - n) - s
|
|
22
|
+
);
|
|
23
|
+
}, L = (t, n, i) => {
|
|
24
|
+
if (!t.contains(n) || d(t, n))
|
|
25
|
+
return null;
|
|
26
|
+
const { left: o, right: e, visibleTo: f } = l(t, n);
|
|
27
|
+
if (e <= f)
|
|
28
|
+
return o - s;
|
|
29
|
+
const a = e - t.clientWidth + s;
|
|
30
|
+
return i ? Math.min(
|
|
31
|
+
...h(t).filter((u) => u >= a - r),
|
|
32
|
+
o - s
|
|
33
|
+
) : a;
|
|
34
|
+
}, v = (t, n, i) => {
|
|
35
|
+
const o = t.scrollLeft, e = o + n * R(t);
|
|
36
|
+
if (!i)
|
|
37
|
+
return e;
|
|
38
|
+
const f = (c) => n * (c - o) > r, a = (c) => n * (e - c) > -r, u = h(t).filter((c) => f(c) && a(c)), g = n > 0 ? Math.max(...u) : Math.min(...u);
|
|
39
|
+
return Number.isFinite(g) ? g : e;
|
|
40
|
+
};
|
|
41
|
+
export {
|
|
42
|
+
r as EDGE_EPSILON_PX,
|
|
43
|
+
d as isCardInView,
|
|
44
|
+
v as pageTargetFor,
|
|
45
|
+
L as revealTargetFor
|
|
46
|
+
};
|
|
47
|
+
//# sourceMappingURL=cardStripGeometry.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cardStripGeometry.js","sources":["../../src/components/cardStripGeometry.ts"],"sourcesContent":["import { CARD_FLARE_PX } from './SelectableCard';\n\n// Scroll geometry for CardStrip's track. Positions are scrollLeft values on the scroller.\n\nexport const EDGE_EPSILON_PX = 1;\nconst SCROLL_STEP_RATIO = 0.8;\n\nconst scrollStepFor = (scroller: HTMLElement): number => {\n const [first, second] = Array.from(scroller.children);\n const pitch =\n first && second\n ? second.getBoundingClientRect().left - first.getBoundingClientRect().left\n : (first?.getBoundingClientRect().width ?? 0);\n // `clientWidth` counts the flare padding, which is not width a card can be read in.\n const visible = scroller.clientWidth - CARD_FLARE_PX * 2;\n\n if (pitch <= 0) {\n return visible * SCROLL_STEP_RATIO;\n }\n\n return Math.max(1, Math.floor(visible / pitch)) * pitch;\n};\n\nconst trackSpan = (scroller: HTMLElement, card: HTMLElement) => {\n // Against the scroller: the cards' `offsetParent` is not it.\n const cardRect = card.getBoundingClientRect();\n const viewRect = scroller.getBoundingClientRect();\n const left = scroller.scrollLeft + (cardRect.left - viewRect.left);\n return {\n left,\n right: left + cardRect.width,\n visibleFrom: scroller.scrollLeft + CARD_FLARE_PX,\n visibleTo: scroller.scrollLeft + scroller.clientWidth - CARD_FLARE_PX,\n };\n};\n\nexport const isCardInView = (scroller: HTMLElement, card: HTMLElement): boolean => {\n if (!scroller.contains(card)) {\n return false;\n }\n const { left, right, visibleFrom, visibleTo } = trackSpan(scroller, card);\n return left >= visibleFrom - EDGE_EPSILON_PX && right <= visibleTo + EDGE_EPSILON_PX;\n};\n\n/** The scroll positions that start each slot, which are the track's snap points. */\nconst slotStarts = (scroller: HTMLElement): number[] => {\n const viewLeft = scroller.getBoundingClientRect().left;\n return Array.from(scroller.children).map(\n (slot) =>\n scroller.scrollLeft + (slot.getBoundingClientRect().left - viewLeft) - CARD_FLARE_PX,\n );\n};\n\n// Positioned rather than `scrollIntoView`, which a snapped track pulls back to the nearest snap.\nexport const revealTargetFor = (\n scroller: HTMLElement,\n card: HTMLElement,\n snap: boolean,\n): number | null => {\n if (!scroller.contains(card) || isCardInView(scroller, card)) {\n return null;\n }\n const { left, right, visibleTo } = trackSpan(scroller, card);\n if (right <= visibleTo) {\n return left - CARD_FLARE_PX;\n }\n const fitsFrom = right - scroller.clientWidth + CARD_FLARE_PX;\n if (!snap) {\n return fitsFrom;\n }\n // Land on a card boundary: proximity snapping pulls any other stop back short of the card.\n return Math.min(\n ...slotStarts(scroller).filter((start) => start >= fitsFrom - EDGE_EPSILON_PX),\n left - CARD_FLARE_PX,\n );\n};\n\n/** Where an arrow press pages to: a whole view of cards, onto a card boundary when snapping. */\nexport const pageTargetFor = (scroller: HTMLElement, direction: -1 | 1, snap: boolean): number => {\n const from = scroller.scrollLeft;\n const to = from + direction * scrollStepFor(scroller);\n if (!snap) {\n return to;\n }\n // Onto the nearest card boundary, also from a stop between cards (the clamped end). The\n // epsilon absorbs fractional scroll positions, which otherwise pick the boundary already here.\n const ahead = (start: number): boolean => direction * (start - from) > EDGE_EPSILON_PX;\n const within = (start: number): boolean => direction * (to - start) > -EDGE_EPSILON_PX;\n const starts = slotStarts(scroller).filter((start) => ahead(start) && within(start));\n const onto = direction > 0 ? Math.max(...starts) : Math.min(...starts);\n return Number.isFinite(onto) ? onto : to;\n};\n"],"names":["EDGE_EPSILON_PX","SCROLL_STEP_RATIO","scrollStepFor","scroller","first","second","pitch","visible","CARD_FLARE_PX","trackSpan","card","cardRect","viewRect","left","isCardInView","right","visibleFrom","visibleTo","slotStarts","viewLeft","slot","revealTargetFor","snap","fitsFrom","start","pageTargetFor","direction","from","to","ahead","within","starts","onto"],"mappings":";AAIO,MAAMA,IAAkB,GACzBC,IAAoB,KAEpBC,IAAgB,CAACC,MAAkC;AACrD,QAAM,CAACC,GAAOC,CAAM,IAAI,MAAM,KAAKF,EAAS,QAAQ,GAC9CG,IACFF,KAASC,IACHA,EAAO,wBAAwB,OAAOD,EAAM,sBAAA,EAAwB,QACnEA,KAAA,gBAAAA,EAAO,wBAAwB,UAAS,GAE7CG,IAAUJ,EAAS,cAAcK,IAAgB;AAEvD,SAAIF,KAAS,IACFC,IAAUN,IAGd,KAAK,IAAI,GAAG,KAAK,MAAMM,IAAUD,CAAK,CAAC,IAAIA;AACtD,GAEMG,IAAY,CAACN,GAAuBO,MAAsB;AAE5D,QAAMC,IAAWD,EAAK,sBAAA,GAChBE,IAAWT,EAAS,sBAAA,GACpBU,IAAOV,EAAS,cAAcQ,EAAS,OAAOC,EAAS;AAC7D,SAAO;AAAA,IACH,MAAAC;AAAA,IACA,OAAOA,IAAOF,EAAS;AAAA,IACvB,aAAaR,EAAS,aAAaK;AAAA,IACnC,WAAWL,EAAS,aAAaA,EAAS,cAAcK;AAAA,EAAA;AAEhE,GAEaM,IAAe,CAACX,GAAuBO,MAA+B;AAC/E,MAAI,CAACP,EAAS,SAASO,CAAI;AACvB,WAAO;AAEX,QAAM,EAAE,MAAAG,GAAM,OAAAE,GAAO,aAAAC,GAAa,WAAAC,MAAcR,EAAUN,GAAUO,CAAI;AACxE,SAAOG,KAAQG,IAAchB,KAAmBe,KAASE,IAAYjB;AACzE,GAGMkB,IAAa,CAACf,MAAoC;AACpD,QAAMgB,IAAWhB,EAAS,sBAAA,EAAwB;AAClD,SAAO,MAAM,KAAKA,EAAS,QAAQ,EAAE;AAAA,IACjC,CAACiB,MACGjB,EAAS,cAAciB,EAAK,wBAAwB,OAAOD,KAAYX;AAAA,EAAA;AAEnF,GAGaa,IAAkB,CAC3BlB,GACAO,GACAY,MACgB;AAChB,MAAI,CAACnB,EAAS,SAASO,CAAI,KAAKI,EAAaX,GAAUO,CAAI;AACvD,WAAO;AAEX,QAAM,EAAE,MAAAG,GAAM,OAAAE,GAAO,WAAAE,MAAcR,EAAUN,GAAUO,CAAI;AAC3D,MAAIK,KAASE;AACT,WAAOJ,IAAOL;AAElB,QAAMe,IAAWR,IAAQZ,EAAS,cAAcK;AAChD,SAAKc,IAIE,KAAK;AAAA,IACR,GAAGJ,EAAWf,CAAQ,EAAE,OAAO,CAACqB,MAAUA,KAASD,IAAWvB,CAAe;AAAA,IAC7Ea,IAAOL;AAAA,EAAA,IALAe;AAOf,GAGaE,IAAgB,CAACtB,GAAuBuB,GAAmBJ,MAA0B;AAC9F,QAAMK,IAAOxB,EAAS,YAChByB,IAAKD,IAAOD,IAAYxB,EAAcC,CAAQ;AACpD,MAAI,CAACmB;AACD,WAAOM;AAIX,QAAMC,IAAQ,CAACL,MAA2BE,KAAaF,IAAQG,KAAQ3B,GACjE8B,IAAS,CAACN,MAA2BE,KAAaE,IAAKJ,KAAS,CAACxB,GACjE+B,IAASb,EAAWf,CAAQ,EAAE,OAAO,CAACqB,MAAUK,EAAML,CAAK,KAAKM,EAAON,CAAK,CAAC,GAC7EQ,IAAON,IAAY,IAAI,KAAK,IAAI,GAAGK,CAAM,IAAI,KAAK,IAAI,GAAGA,CAAM;AACrE,SAAO,OAAO,SAASC,CAAI,IAAIA,IAAOJ;AAC1C;"}
|
package/package.json
CHANGED