@nextlyhq/ui 0.0.2-alpha.54 → 0.0.2-alpha.57
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/color.cjs +193 -0
- package/dist/color.cjs.map +1 -0
- package/dist/color.d.cts +251 -0
- package/dist/color.d.ts +251 -0
- package/dist/color.mjs +179 -0
- package/dist/color.mjs.map +1 -0
- package/dist/index.cjs +1246 -51
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +445 -1
- package/dist/index.d.ts +445 -1
- package/dist/index.mjs +1230 -44
- package/dist/index.mjs.map +1 -1
- package/dist/styles.css +1 -1
- package/dist/styles.scoped.css +1 -1
- package/dist/theme.css +138 -104
- package/docs/plugin-ui-authoring.md +12 -10
- package/package.json +22 -8
package/dist/index.mjs
CHANGED
|
@@ -22,11 +22,15 @@ var buttonVariants = cva(
|
|
|
22
22
|
variant: {
|
|
23
23
|
default: "bg-primary text-primary-foreground border border-transparent hover:opacity-90",
|
|
24
24
|
primary: "bg-primary text-primary-foreground border border-transparent hover:opacity-90",
|
|
25
|
-
// Solid fill uses the emphasis token so
|
|
26
|
-
//
|
|
27
|
-
// Hover darkens to a deeper shade
|
|
28
|
-
// composite the fill toward the page and drop
|
|
29
|
-
|
|
25
|
+
// Solid fill uses the emphasis token so on-color text stays AA in dark
|
|
26
|
+
// mode (the base token is the readable text color, too light here).
|
|
27
|
+
// Hover darkens to a deeper shade rather than opacity-90, which would
|
|
28
|
+
// composite the fill toward the page and drop the label under 4.5:1.
|
|
29
|
+
// One step, not two: the label is white in light mode and black in
|
|
30
|
+
// dark, so mixing the fill toward black moves it away from the label in
|
|
31
|
+
// one mode and into it in the other. `-600` clears both (5.92:1 light,
|
|
32
|
+
// 5.67:1 dark); `-700` reads at 3.70:1 against the dark label.
|
|
33
|
+
destructive: "bg-destructive-solid text-destructive-foreground border border-transparent hover:bg-destructive-600",
|
|
30
34
|
// border-border is the decorative separator token, and it is the right
|
|
31
35
|
// one here: a button is identified by its label and fill, so its edge
|
|
32
36
|
// carries no meaning on its own and is not held to the 3:1 minimum that
|
|
@@ -2107,7 +2111,7 @@ var TableSkeleton = ({
|
|
|
2107
2111
|
] })
|
|
2108
2112
|
] }) : /* @__PURE__ */ jsx33(GrayBar, { className: "h-4 w-[60%] max-w-[120px]" }) }, colIdx)) }, rowIdx)) })
|
|
2109
2113
|
] }) }),
|
|
2110
|
-
!hideFooter && /* @__PURE__ */ jsx33("div", { className: "table-footer border-t border-border", children: /* @__PURE__ */ jsxs12("div", { className: "flex items-center justify-between px-2 py-4
|
|
2114
|
+
!hideFooter && /* @__PURE__ */ jsx33("div", { className: "table-footer border-t border-border", children: /* @__PURE__ */ jsxs12("div", { className: "flex items-center justify-between px-2 py-4", children: [
|
|
2111
2115
|
/* @__PURE__ */ jsx33("div", { className: "flex items-center gap-2 text-sm", children: /* @__PURE__ */ jsx33(GrayBar, { className: "h-4 w-[120px]" }) }),
|
|
2112
2116
|
/* @__PURE__ */ jsxs12("div", { className: "flex items-center gap-6", children: [
|
|
2113
2117
|
/* @__PURE__ */ jsxs12("div", { className: "flex items-center gap-2", children: [
|
|
@@ -2132,11 +2136,353 @@ var TableSkeleton = ({
|
|
|
2132
2136
|
};
|
|
2133
2137
|
TableSkeleton.displayName = "TableSkeleton";
|
|
2134
2138
|
|
|
2139
|
+
// src/components/color-picker.tsx
|
|
2140
|
+
import { Pipette } from "lucide-react";
|
|
2141
|
+
import * as React9 from "react";
|
|
2142
|
+
|
|
2143
|
+
// src/lib/color/convert.ts
|
|
2144
|
+
var clamp01 = (n) => n < 0 ? 0 : n > 1 ? 1 : n;
|
|
2145
|
+
function normalizeHue(hue) {
|
|
2146
|
+
if (!Number.isFinite(hue)) return 0;
|
|
2147
|
+
const wrapped = hue % 360;
|
|
2148
|
+
return wrapped < 0 ? wrapped + 360 : wrapped;
|
|
2149
|
+
}
|
|
2150
|
+
function hsvToRgb({ h, s, v }) {
|
|
2151
|
+
const hue = normalizeHue(h);
|
|
2152
|
+
const sat = clamp01(s);
|
|
2153
|
+
const val = clamp01(v);
|
|
2154
|
+
const sector = hue / 60;
|
|
2155
|
+
const chroma = val * sat;
|
|
2156
|
+
const x = chroma * (1 - Math.abs(sector % 2 - 1));
|
|
2157
|
+
const base = val - chroma;
|
|
2158
|
+
let rgb;
|
|
2159
|
+
if (sector < 1) rgb = [chroma, x, 0];
|
|
2160
|
+
else if (sector < 2) rgb = [x, chroma, 0];
|
|
2161
|
+
else if (sector < 3) rgb = [0, chroma, x];
|
|
2162
|
+
else if (sector < 4) rgb = [0, x, chroma];
|
|
2163
|
+
else if (sector < 5) rgb = [x, 0, chroma];
|
|
2164
|
+
else rgb = [chroma, 0, x];
|
|
2165
|
+
return { r: rgb[0] + base, g: rgb[1] + base, b: rgb[2] + base };
|
|
2166
|
+
}
|
|
2167
|
+
function rgbToHsv({ r, g, b }) {
|
|
2168
|
+
const red = clamp01(r);
|
|
2169
|
+
const green = clamp01(g);
|
|
2170
|
+
const blue = clamp01(b);
|
|
2171
|
+
const max = Math.max(red, green, blue);
|
|
2172
|
+
const min = Math.min(red, green, blue);
|
|
2173
|
+
const chroma = max - min;
|
|
2174
|
+
let hue = 0;
|
|
2175
|
+
if (chroma !== 0) {
|
|
2176
|
+
if (max === red) hue = (green - blue) / chroma % 6;
|
|
2177
|
+
else if (max === green) hue = (blue - red) / chroma + 2;
|
|
2178
|
+
else hue = (red - green) / chroma + 4;
|
|
2179
|
+
hue *= 60;
|
|
2180
|
+
}
|
|
2181
|
+
return {
|
|
2182
|
+
h: normalizeHue(hue),
|
|
2183
|
+
s: max === 0 ? 0 : chroma / max,
|
|
2184
|
+
v: max
|
|
2185
|
+
};
|
|
2186
|
+
}
|
|
2187
|
+
|
|
2188
|
+
// src/lib/color/hex.ts
|
|
2189
|
+
var HEX = /^#?(?:[0-9a-f]{3,4}|[0-9a-f]{6}|[0-9a-f]{8})$/i;
|
|
2190
|
+
var clamp012 = (n) => n < 0 ? 0 : n > 1 ? 1 : n;
|
|
2191
|
+
function pair(channel) {
|
|
2192
|
+
const value = Number.isFinite(channel) ? clamp012(channel) : 0;
|
|
2193
|
+
return Math.round(value * 255).toString(16).padStart(2, "0");
|
|
2194
|
+
}
|
|
2195
|
+
function parseHex(input) {
|
|
2196
|
+
const text = input.trim();
|
|
2197
|
+
if (!HEX.test(text)) return null;
|
|
2198
|
+
const digits = text.replace("#", "");
|
|
2199
|
+
const short = digits.length < 6;
|
|
2200
|
+
const size = short ? 1 : 2;
|
|
2201
|
+
const channel = (index) => {
|
|
2202
|
+
const slice = digits.slice(index * size, index * size + size);
|
|
2203
|
+
return parseInt(short ? slice + slice : slice, 16) / 255;
|
|
2204
|
+
};
|
|
2205
|
+
const hasAlpha = digits.length === 4 || digits.length === 8;
|
|
2206
|
+
return {
|
|
2207
|
+
r: channel(0),
|
|
2208
|
+
g: channel(1),
|
|
2209
|
+
b: channel(2),
|
|
2210
|
+
alpha: hasAlpha ? channel(3) : 1
|
|
2211
|
+
};
|
|
2212
|
+
}
|
|
2213
|
+
function toHex(color, alpha = 1) {
|
|
2214
|
+
const opacity = Number.isFinite(alpha) ? clamp012(alpha) : 1;
|
|
2215
|
+
const opaque = `#${pair(color.r)}${pair(color.g)}${pair(color.b)}`;
|
|
2216
|
+
return opacity === 1 ? opaque : `${opaque}${pair(opacity)}`;
|
|
2217
|
+
}
|
|
2218
|
+
|
|
2219
|
+
// src/lib/color/picker-geometry.ts
|
|
2220
|
+
var clamp013 = (n) => n < 0 ? 0 : n > 1 ? 1 : n;
|
|
2221
|
+
function pointOnSurface(clientX, clientY, rect) {
|
|
2222
|
+
return {
|
|
2223
|
+
x: rect.width === 0 ? 0 : clamp013((clientX - rect.left) / rect.width),
|
|
2224
|
+
y: rect.height === 0 ? 0 : clamp013((clientY - rect.top) / rect.height)
|
|
2225
|
+
};
|
|
2226
|
+
}
|
|
2227
|
+
function saturationValueAt(point) {
|
|
2228
|
+
return { s: clamp013(point.x), v: 1 - clamp013(point.y) };
|
|
2229
|
+
}
|
|
2230
|
+
function surfacePointFor(s, v) {
|
|
2231
|
+
return { x: clamp013(s), y: 1 - clamp013(v) };
|
|
2232
|
+
}
|
|
2233
|
+
function hueAt(fraction) {
|
|
2234
|
+
const hue = clamp013(fraction) * 360;
|
|
2235
|
+
return hue >= 360 ? 0 : hue;
|
|
2236
|
+
}
|
|
2237
|
+
function huePosition(hue) {
|
|
2238
|
+
const wrapped = (hue % 360 + 360) % 360;
|
|
2239
|
+
return wrapped / 360;
|
|
2240
|
+
}
|
|
2241
|
+
function hueSliderValue(hue, max) {
|
|
2242
|
+
const step = Math.round(huePosition(hue) * (max + 1));
|
|
2243
|
+
return step > max ? max : step;
|
|
2244
|
+
}
|
|
2245
|
+
|
|
2246
|
+
// src/components/color-picker.tsx
|
|
2247
|
+
import { Fragment as Fragment2, jsx as jsx34, jsxs as jsxs13 } from "react/jsx-runtime";
|
|
2248
|
+
function toHsva(hex) {
|
|
2249
|
+
const parsed = parseHex(hex);
|
|
2250
|
+
if (!parsed) return { h: 0, s: 0, v: 0, a: 1 };
|
|
2251
|
+
const { r, g, b, alpha } = parsed;
|
|
2252
|
+
return { ...rgbToHsv({ r, g, b }), a: alpha };
|
|
2253
|
+
}
|
|
2254
|
+
function toHexString(hsva, withAlpha) {
|
|
2255
|
+
return toHex(hsvToRgb(hsva), withAlpha ? hsva.a : 1);
|
|
2256
|
+
}
|
|
2257
|
+
var HUE_MAX = 359;
|
|
2258
|
+
function hsvaFrom(color, alpha, currentHue) {
|
|
2259
|
+
const hsv = rgbToHsv(color);
|
|
2260
|
+
return { ...hsv, h: hsv.s === 0 ? currentHue : hsv.h, a: alpha };
|
|
2261
|
+
}
|
|
2262
|
+
var SLIDER = "h-3 w-full cursor-pointer appearance-none rounded-full";
|
|
2263
|
+
var CHECKERBOARD = "repeating-conic-gradient(#c8c8c8 0% 25%, #ffffff 0% 50%)";
|
|
2264
|
+
function eyeDropperSupported() {
|
|
2265
|
+
return typeof window !== "undefined" && "EyeDropper" in window;
|
|
2266
|
+
}
|
|
2267
|
+
function ColorPicker({
|
|
2268
|
+
color,
|
|
2269
|
+
onColorChange,
|
|
2270
|
+
swatches = [],
|
|
2271
|
+
onSwatchSelect,
|
|
2272
|
+
recentColors = [],
|
|
2273
|
+
showAlpha = false,
|
|
2274
|
+
className
|
|
2275
|
+
}) {
|
|
2276
|
+
const fieldId = React9.useId();
|
|
2277
|
+
const surfaceRef = React9.useRef(null);
|
|
2278
|
+
const [hsva, setHsva] = React9.useState(() => toHsva(color));
|
|
2279
|
+
const [draftHex, setDraftHex] = React9.useState(null);
|
|
2280
|
+
const rendered = toHexString(hsva, showAlpha);
|
|
2281
|
+
React9.useEffect(() => {
|
|
2282
|
+
const incoming = parseHex(color);
|
|
2283
|
+
if (incoming && toHex(incoming, showAlpha ? incoming.alpha : 1) !== rendered) {
|
|
2284
|
+
setHsva((prev) => hsvaFrom(incoming, incoming.alpha, prev.h));
|
|
2285
|
+
}
|
|
2286
|
+
}, [color, rendered, showAlpha]);
|
|
2287
|
+
const commit = (next) => {
|
|
2288
|
+
setHsva(next);
|
|
2289
|
+
setDraftHex(null);
|
|
2290
|
+
onColorChange(toHexString(next, showAlpha));
|
|
2291
|
+
};
|
|
2292
|
+
const trackPointer = (event) => {
|
|
2293
|
+
const rect = surfaceRef.current?.getBoundingClientRect();
|
|
2294
|
+
if (!rect) return;
|
|
2295
|
+
const { s, v } = saturationValueAt(
|
|
2296
|
+
pointOnSurface(event.clientX, event.clientY, rect)
|
|
2297
|
+
);
|
|
2298
|
+
commit({ ...hsva, s, v });
|
|
2299
|
+
};
|
|
2300
|
+
const nudge = (ds, dv) => {
|
|
2301
|
+
const { s, v } = saturationValueAt(
|
|
2302
|
+
surfacePointFor(hsva.s + ds, hsva.v + dv)
|
|
2303
|
+
);
|
|
2304
|
+
commit({ ...hsva, s, v });
|
|
2305
|
+
};
|
|
2306
|
+
const handleSurfaceKey = (event) => {
|
|
2307
|
+
const step = event.shiftKey ? 0.1 : 0.01;
|
|
2308
|
+
const moves = {
|
|
2309
|
+
ArrowLeft: [-step, 0],
|
|
2310
|
+
ArrowRight: [step, 0],
|
|
2311
|
+
ArrowUp: [0, step],
|
|
2312
|
+
ArrowDown: [0, -step]
|
|
2313
|
+
};
|
|
2314
|
+
const move = moves[event.key];
|
|
2315
|
+
if (!move) return;
|
|
2316
|
+
event.preventDefault();
|
|
2317
|
+
nudge(move[0], move[1]);
|
|
2318
|
+
};
|
|
2319
|
+
const [canPickFromScreen, setCanPickFromScreen] = React9.useState(false);
|
|
2320
|
+
React9.useEffect(() => {
|
|
2321
|
+
setCanPickFromScreen(eyeDropperSupported());
|
|
2322
|
+
}, []);
|
|
2323
|
+
const handle = surfacePointFor(hsva.s, hsva.v);
|
|
2324
|
+
const hueOnly = toHex(hsvToRgb({ h: hsva.h, s: 1, v: 1 }));
|
|
2325
|
+
const pickFromScreen = async () => {
|
|
2326
|
+
const ctor = window.EyeDropper;
|
|
2327
|
+
if (!ctor) return;
|
|
2328
|
+
let sampled;
|
|
2329
|
+
try {
|
|
2330
|
+
sampled = (await new ctor().open()).sRGBHex;
|
|
2331
|
+
} catch {
|
|
2332
|
+
return;
|
|
2333
|
+
}
|
|
2334
|
+
const parsed = parseHex(sampled);
|
|
2335
|
+
if (parsed) commit(hsvaFrom(parsed, hsva.a, hsva.h));
|
|
2336
|
+
};
|
|
2337
|
+
return /* @__PURE__ */ jsxs13("div", { className: cn("w-64 space-y-3", className), children: [
|
|
2338
|
+
/* @__PURE__ */ jsx34(
|
|
2339
|
+
"div",
|
|
2340
|
+
{
|
|
2341
|
+
ref: surfaceRef,
|
|
2342
|
+
role: "application",
|
|
2343
|
+
tabIndex: 0,
|
|
2344
|
+
"aria-label": `Saturation and brightness: ${Math.round(hsva.s * 100)}% saturation, ${Math.round(hsva.v * 100)}% brightness. Arrow keys adjust.`,
|
|
2345
|
+
className: "ring-offset-background focus-visible:ring-ring relative h-40 w-full cursor-crosshair touch-none rounded-md focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:outline-none",
|
|
2346
|
+
style: {
|
|
2347
|
+
backgroundColor: hueOnly,
|
|
2348
|
+
// Value on TOP of saturation. CSS paints the first layer nearest the
|
|
2349
|
+
// viewer, so the reverse order lets the opaque white end of the
|
|
2350
|
+
// saturation ramp cover the black end of the value ramp: the
|
|
2351
|
+
// bottom-left corner displays white while selecting black.
|
|
2352
|
+
backgroundImage: "linear-gradient(to top, #000, transparent), linear-gradient(to right, #fff, transparent)"
|
|
2353
|
+
},
|
|
2354
|
+
onKeyDown: handleSurfaceKey,
|
|
2355
|
+
onPointerDown: (event) => {
|
|
2356
|
+
event.currentTarget.setPointerCapture(event.pointerId);
|
|
2357
|
+
trackPointer(event);
|
|
2358
|
+
},
|
|
2359
|
+
onPointerMove: (event) => {
|
|
2360
|
+
if (event.buttons === 1) trackPointer(event);
|
|
2361
|
+
},
|
|
2362
|
+
children: /* @__PURE__ */ jsx34(
|
|
2363
|
+
"span",
|
|
2364
|
+
{
|
|
2365
|
+
"aria-hidden": "true",
|
|
2366
|
+
className: "pointer-events-none absolute size-3 -translate-x-1/2 -translate-y-1/2 rounded-full border-2 border-white shadow-sm ring-1 ring-black/60",
|
|
2367
|
+
style: { left: `${handle.x * 100}%`, top: `${handle.y * 100}%` }
|
|
2368
|
+
}
|
|
2369
|
+
)
|
|
2370
|
+
}
|
|
2371
|
+
),
|
|
2372
|
+
/* @__PURE__ */ jsx34("label", { className: "sr-only", htmlFor: `${fieldId}-hue`, children: "Hue" }),
|
|
2373
|
+
/* @__PURE__ */ jsx34(
|
|
2374
|
+
"input",
|
|
2375
|
+
{
|
|
2376
|
+
id: `${fieldId}-hue`,
|
|
2377
|
+
type: "range",
|
|
2378
|
+
min: 0,
|
|
2379
|
+
max: HUE_MAX,
|
|
2380
|
+
step: 1,
|
|
2381
|
+
value: hueSliderValue(hsva.h, HUE_MAX),
|
|
2382
|
+
onChange: (event) => commit({ ...hsva, h: hueAt(+event.target.value / (HUE_MAX + 1)) }),
|
|
2383
|
+
className: SLIDER,
|
|
2384
|
+
style: {
|
|
2385
|
+
backgroundImage: "linear-gradient(to right, #f00, #ff0, #0f0, #0ff, #00f, #f0f, #f00)"
|
|
2386
|
+
}
|
|
2387
|
+
}
|
|
2388
|
+
),
|
|
2389
|
+
showAlpha && /* @__PURE__ */ jsxs13(Fragment2, { children: [
|
|
2390
|
+
/* @__PURE__ */ jsx34("label", { className: "sr-only", htmlFor: `${fieldId}-alpha`, children: "Opacity" }),
|
|
2391
|
+
/* @__PURE__ */ jsx34(
|
|
2392
|
+
"input",
|
|
2393
|
+
{
|
|
2394
|
+
id: `${fieldId}-alpha`,
|
|
2395
|
+
type: "range",
|
|
2396
|
+
min: 0,
|
|
2397
|
+
max: 100,
|
|
2398
|
+
step: 1,
|
|
2399
|
+
value: Math.round(hsva.a * 100),
|
|
2400
|
+
onChange: (event) => commit({ ...hsva, a: +event.target.value / 100 }),
|
|
2401
|
+
className: SLIDER,
|
|
2402
|
+
style: {
|
|
2403
|
+
// The ramp runs to the colour being edited, over a chequerboard,
|
|
2404
|
+
// so the track shows what the slider actually controls. Without
|
|
2405
|
+
// any background this rendered as a blank 12px strip whose only
|
|
2406
|
+
// label was screen-reader-only.
|
|
2407
|
+
backgroundImage: `linear-gradient(to right, transparent, ${toHexString({ ...hsva, a: 1 }, false)}), ${CHECKERBOARD}`,
|
|
2408
|
+
backgroundSize: "100% 100%, 8px 8px"
|
|
2409
|
+
}
|
|
2410
|
+
}
|
|
2411
|
+
)
|
|
2412
|
+
] }),
|
|
2413
|
+
/* @__PURE__ */ jsxs13("div", { className: "flex items-center gap-2", children: [
|
|
2414
|
+
/* @__PURE__ */ jsx34("label", { className: "sr-only", htmlFor: `${fieldId}-hex`, children: "Hex colour" }),
|
|
2415
|
+
/* @__PURE__ */ jsx34(
|
|
2416
|
+
Input,
|
|
2417
|
+
{
|
|
2418
|
+
id: `${fieldId}-hex`,
|
|
2419
|
+
className: "font-mono",
|
|
2420
|
+
value: draftHex ?? rendered,
|
|
2421
|
+
onChange: (event) => {
|
|
2422
|
+
const text = event.target.value;
|
|
2423
|
+
setDraftHex(text);
|
|
2424
|
+
const parsed = parseHex(text);
|
|
2425
|
+
if (parsed) {
|
|
2426
|
+
setHsva(hsvaFrom(parsed, parsed.alpha, hsva.h));
|
|
2427
|
+
onColorChange(toHex(parsed, showAlpha ? parsed.alpha : 1));
|
|
2428
|
+
}
|
|
2429
|
+
},
|
|
2430
|
+
onBlur: () => setDraftHex(null)
|
|
2431
|
+
}
|
|
2432
|
+
),
|
|
2433
|
+
canPickFromScreen && /* @__PURE__ */ jsx34(
|
|
2434
|
+
Button,
|
|
2435
|
+
{
|
|
2436
|
+
type: "button",
|
|
2437
|
+
variant: "outline",
|
|
2438
|
+
size: "icon",
|
|
2439
|
+
"aria-label": "Pick a colour from the screen",
|
|
2440
|
+
onClick: () => void pickFromScreen(),
|
|
2441
|
+
children: /* @__PURE__ */ jsx34(Pipette, { className: "size-4" })
|
|
2442
|
+
}
|
|
2443
|
+
)
|
|
2444
|
+
] }),
|
|
2445
|
+
swatches.length > 0 && /* @__PURE__ */ jsxs13("div", { children: [
|
|
2446
|
+
/* @__PURE__ */ jsx34("p", { className: "text-muted-foreground mb-1 text-xs", children: "Presets" }),
|
|
2447
|
+
/* @__PURE__ */ jsx34("div", { className: "flex flex-wrap gap-1", children: swatches.map((swatch) => /* @__PURE__ */ jsx34(
|
|
2448
|
+
"button",
|
|
2449
|
+
{
|
|
2450
|
+
type: "button",
|
|
2451
|
+
title: swatch.label,
|
|
2452
|
+
"aria-label": swatch.label,
|
|
2453
|
+
className: "size-6 rounded border shadow-sm",
|
|
2454
|
+
style: { backgroundColor: swatch.color },
|
|
2455
|
+
onClick: () => onSwatchSelect?.(swatch)
|
|
2456
|
+
},
|
|
2457
|
+
swatch.id
|
|
2458
|
+
)) })
|
|
2459
|
+
] }),
|
|
2460
|
+
recentColors.length > 0 && /* @__PURE__ */ jsxs13("div", { children: [
|
|
2461
|
+
/* @__PURE__ */ jsx34("p", { className: "text-muted-foreground mb-1 text-xs", children: "Recent" }),
|
|
2462
|
+
/* @__PURE__ */ jsx34("div", { className: "flex flex-wrap gap-1", children: recentColors.map((recent) => /* @__PURE__ */ jsx34(
|
|
2463
|
+
"button",
|
|
2464
|
+
{
|
|
2465
|
+
type: "button",
|
|
2466
|
+
title: recent,
|
|
2467
|
+
"aria-label": recent,
|
|
2468
|
+
className: "size-6 rounded border shadow-sm",
|
|
2469
|
+
style: { backgroundColor: recent },
|
|
2470
|
+
onClick: () => {
|
|
2471
|
+
const parsed = parseHex(recent);
|
|
2472
|
+
if (parsed) commit(hsvaFrom(parsed, parsed.alpha, hsva.h));
|
|
2473
|
+
}
|
|
2474
|
+
},
|
|
2475
|
+
recent
|
|
2476
|
+
)) })
|
|
2477
|
+
] })
|
|
2478
|
+
] });
|
|
2479
|
+
}
|
|
2480
|
+
|
|
2135
2481
|
// src/components/context-menu.tsx
|
|
2136
2482
|
import * as ContextMenuPrimitive from "@radix-ui/react-context-menu";
|
|
2137
2483
|
import { Check as Check4, ChevronRight as ChevronRight2, Circle as Circle2 } from "lucide-react";
|
|
2138
|
-
import * as
|
|
2139
|
-
import { jsx as
|
|
2484
|
+
import * as React10 from "react";
|
|
2485
|
+
import { jsx as jsx35, jsxs as jsxs14 } from "react/jsx-runtime";
|
|
2140
2486
|
var menuItemBase2 = "cursor-pointer transition-colors data-[highlighted]:bg-muted data-[highlighted]:text-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50";
|
|
2141
2487
|
var menuSurface = "z-50 max-h-[var(--radix-context-menu-content-available-height)] min-w-[8rem] overflow-y-auto overflow-x-hidden rounded-lg border border-border bg-popover p-1 text-popover-foreground shadow-md data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 origin-[--radix-context-menu-content-transform-origin]";
|
|
2142
2488
|
var ContextMenu = ContextMenuPrimitive.Root;
|
|
@@ -2144,7 +2490,7 @@ var ContextMenuTrigger = ContextMenuPrimitive.Trigger;
|
|
|
2144
2490
|
var ContextMenuGroup = ContextMenuPrimitive.Group;
|
|
2145
2491
|
var ContextMenuSub = ContextMenuPrimitive.Sub;
|
|
2146
2492
|
var ContextMenuRadioGroup = ContextMenuPrimitive.RadioGroup;
|
|
2147
|
-
var ContextMenuSubTrigger =
|
|
2493
|
+
var ContextMenuSubTrigger = React10.forwardRef(({ className, inset, children, ...props }, ref) => /* @__PURE__ */ jsxs14(
|
|
2148
2494
|
ContextMenuPrimitive.SubTrigger,
|
|
2149
2495
|
{
|
|
2150
2496
|
ref,
|
|
@@ -2157,14 +2503,14 @@ var ContextMenuSubTrigger = React9.forwardRef(({ className, inset, children, ...
|
|
|
2157
2503
|
...props,
|
|
2158
2504
|
children: [
|
|
2159
2505
|
children,
|
|
2160
|
-
/* @__PURE__ */
|
|
2506
|
+
/* @__PURE__ */ jsx35(ChevronRight2, { className: "ml-auto" })
|
|
2161
2507
|
]
|
|
2162
2508
|
}
|
|
2163
2509
|
));
|
|
2164
2510
|
ContextMenuSubTrigger.displayName = ContextMenuPrimitive.SubTrigger.displayName;
|
|
2165
|
-
var ContextMenuSubContent =
|
|
2511
|
+
var ContextMenuSubContent = React10.forwardRef(({ className, ...props }, ref) => {
|
|
2166
2512
|
const portalContainer = usePortalContainer();
|
|
2167
|
-
return /* @__PURE__ */
|
|
2513
|
+
return /* @__PURE__ */ jsx35(ContextMenuPrimitive.Portal, { container: portalContainer, children: /* @__PURE__ */ jsx35(
|
|
2168
2514
|
ContextMenuPrimitive.SubContent,
|
|
2169
2515
|
{
|
|
2170
2516
|
ref,
|
|
@@ -2174,9 +2520,9 @@ var ContextMenuSubContent = React9.forwardRef(({ className, ...props }, ref) =>
|
|
|
2174
2520
|
) });
|
|
2175
2521
|
});
|
|
2176
2522
|
ContextMenuSubContent.displayName = ContextMenuPrimitive.SubContent.displayName;
|
|
2177
|
-
var ContextMenuContent =
|
|
2523
|
+
var ContextMenuContent = React10.forwardRef(({ className, ...props }, ref) => {
|
|
2178
2524
|
const portalContainer = usePortalContainer();
|
|
2179
|
-
return /* @__PURE__ */
|
|
2525
|
+
return /* @__PURE__ */ jsx35(ContextMenuPrimitive.Portal, { container: portalContainer, children: /* @__PURE__ */ jsx35(
|
|
2180
2526
|
ContextMenuPrimitive.Content,
|
|
2181
2527
|
{
|
|
2182
2528
|
ref,
|
|
@@ -2186,7 +2532,7 @@ var ContextMenuContent = React9.forwardRef(({ className, ...props }, ref) => {
|
|
|
2186
2532
|
) });
|
|
2187
2533
|
});
|
|
2188
2534
|
ContextMenuContent.displayName = ContextMenuPrimitive.Content.displayName;
|
|
2189
|
-
var ContextMenuItem =
|
|
2535
|
+
var ContextMenuItem = React10.forwardRef(({ className, inset, ...props }, ref) => /* @__PURE__ */ jsx35(
|
|
2190
2536
|
ContextMenuPrimitive.Item,
|
|
2191
2537
|
{
|
|
2192
2538
|
ref,
|
|
@@ -2200,7 +2546,7 @@ var ContextMenuItem = React9.forwardRef(({ className, inset, ...props }, ref) =>
|
|
|
2200
2546
|
}
|
|
2201
2547
|
));
|
|
2202
2548
|
ContextMenuItem.displayName = ContextMenuPrimitive.Item.displayName;
|
|
2203
|
-
var ContextMenuCheckboxItem =
|
|
2549
|
+
var ContextMenuCheckboxItem = React10.forwardRef(({ className, children, checked, ...props }, ref) => /* @__PURE__ */ jsxs14(
|
|
2204
2550
|
ContextMenuPrimitive.CheckboxItem,
|
|
2205
2551
|
{
|
|
2206
2552
|
ref,
|
|
@@ -2212,13 +2558,13 @@ var ContextMenuCheckboxItem = React9.forwardRef(({ className, children, checked,
|
|
|
2212
2558
|
checked,
|
|
2213
2559
|
...props,
|
|
2214
2560
|
children: [
|
|
2215
|
-
/* @__PURE__ */
|
|
2561
|
+
/* @__PURE__ */ jsx35("span", { className: "absolute left-2 flex h-3.5 w-3.5 items-center justify-center", children: /* @__PURE__ */ jsx35(ContextMenuPrimitive.ItemIndicator, { children: /* @__PURE__ */ jsx35(Check4, { className: "h-4 w-4" }) }) }),
|
|
2216
2562
|
children
|
|
2217
2563
|
]
|
|
2218
2564
|
}
|
|
2219
2565
|
));
|
|
2220
2566
|
ContextMenuCheckboxItem.displayName = ContextMenuPrimitive.CheckboxItem.displayName;
|
|
2221
|
-
var ContextMenuRadioItem =
|
|
2567
|
+
var ContextMenuRadioItem = React10.forwardRef(({ className, children, ...props }, ref) => /* @__PURE__ */ jsxs14(
|
|
2222
2568
|
ContextMenuPrimitive.RadioItem,
|
|
2223
2569
|
{
|
|
2224
2570
|
ref,
|
|
@@ -2229,13 +2575,13 @@ var ContextMenuRadioItem = React9.forwardRef(({ className, children, ...props },
|
|
|
2229
2575
|
),
|
|
2230
2576
|
...props,
|
|
2231
2577
|
children: [
|
|
2232
|
-
/* @__PURE__ */
|
|
2578
|
+
/* @__PURE__ */ jsx35("span", { className: "absolute left-2 flex h-3.5 w-3.5 items-center justify-center", children: /* @__PURE__ */ jsx35(ContextMenuPrimitive.ItemIndicator, { children: /* @__PURE__ */ jsx35(Circle2, { className: "h-2 w-2 fill-current" }) }) }),
|
|
2233
2579
|
children
|
|
2234
2580
|
]
|
|
2235
2581
|
}
|
|
2236
2582
|
));
|
|
2237
2583
|
ContextMenuRadioItem.displayName = ContextMenuPrimitive.RadioItem.displayName;
|
|
2238
|
-
var ContextMenuLabel =
|
|
2584
|
+
var ContextMenuLabel = React10.forwardRef(({ className, inset, ...props }, ref) => /* @__PURE__ */ jsx35(
|
|
2239
2585
|
ContextMenuPrimitive.Label,
|
|
2240
2586
|
{
|
|
2241
2587
|
ref,
|
|
@@ -2248,7 +2594,7 @@ var ContextMenuLabel = React9.forwardRef(({ className, inset, ...props }, ref) =
|
|
|
2248
2594
|
}
|
|
2249
2595
|
));
|
|
2250
2596
|
ContextMenuLabel.displayName = ContextMenuPrimitive.Label.displayName;
|
|
2251
|
-
var ContextMenuSeparator =
|
|
2597
|
+
var ContextMenuSeparator = React10.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsx35(
|
|
2252
2598
|
ContextMenuPrimitive.Separator,
|
|
2253
2599
|
{
|
|
2254
2600
|
ref,
|
|
@@ -2260,7 +2606,7 @@ ContextMenuSeparator.displayName = ContextMenuPrimitive.Separator.displayName;
|
|
|
2260
2606
|
var ContextMenuShortcut = ({
|
|
2261
2607
|
className,
|
|
2262
2608
|
...props
|
|
2263
|
-
}) => /* @__PURE__ */
|
|
2609
|
+
}) => /* @__PURE__ */ jsx35(
|
|
2264
2610
|
"span",
|
|
2265
2611
|
{
|
|
2266
2612
|
className: cn(
|
|
@@ -2275,11 +2621,11 @@ ContextMenuShortcut.displayName = "ContextMenuShortcut";
|
|
|
2275
2621
|
// src/components/resizable.tsx
|
|
2276
2622
|
import { GripVertical } from "lucide-react";
|
|
2277
2623
|
import * as ResizablePrimitive from "react-resizable-panels";
|
|
2278
|
-
import { jsx as
|
|
2624
|
+
import { jsx as jsx36, jsxs as jsxs15 } from "react/jsx-runtime";
|
|
2279
2625
|
var ResizablePanelGroup = ({
|
|
2280
2626
|
className,
|
|
2281
2627
|
...props
|
|
2282
|
-
}) => /* @__PURE__ */
|
|
2628
|
+
}) => /* @__PURE__ */ jsx36(
|
|
2283
2629
|
ResizablePrimitive.Group,
|
|
2284
2630
|
{
|
|
2285
2631
|
className: cn("h-full w-full", className),
|
|
@@ -2292,7 +2638,7 @@ var ResizableHandle = ({
|
|
|
2292
2638
|
className,
|
|
2293
2639
|
children,
|
|
2294
2640
|
...props
|
|
2295
|
-
}) => /* @__PURE__ */
|
|
2641
|
+
}) => /* @__PURE__ */ jsxs15(
|
|
2296
2642
|
ResizablePrimitive.Separator,
|
|
2297
2643
|
{
|
|
2298
2644
|
className: cn(
|
|
@@ -2312,7 +2658,7 @@ var ResizableHandle = ({
|
|
|
2312
2658
|
),
|
|
2313
2659
|
...props,
|
|
2314
2660
|
children: [
|
|
2315
|
-
withGrip ? /* @__PURE__ */
|
|
2661
|
+
withGrip ? /* @__PURE__ */ jsx36("div", { className: "z-10 flex h-4 w-3 items-center justify-center rounded-sm border border-border bg-border", children: /* @__PURE__ */ jsx36(GripVertical, { className: "h-2.5 w-2.5 text-muted-foreground" }) }) : null,
|
|
2316
2662
|
children
|
|
2317
2663
|
]
|
|
2318
2664
|
}
|
|
@@ -2321,8 +2667,8 @@ var ResizableHandle = ({
|
|
|
2321
2667
|
// src/components/tree-view.tsx
|
|
2322
2668
|
import { useVirtualizer } from "@tanstack/react-virtual";
|
|
2323
2669
|
import { ChevronRight as ChevronRight3 } from "lucide-react";
|
|
2324
|
-
import * as
|
|
2325
|
-
import { jsx as
|
|
2670
|
+
import * as React11 from "react";
|
|
2671
|
+
import { jsx as jsx37, jsxs as jsxs16 } from "react/jsx-runtime";
|
|
2326
2672
|
var ROW_HEIGHT = 28;
|
|
2327
2673
|
var INDENT_PER_LEVEL = 12;
|
|
2328
2674
|
function textOf(node) {
|
|
@@ -2363,13 +2709,13 @@ function flatten(nodes, expanded) {
|
|
|
2363
2709
|
return rows;
|
|
2364
2710
|
}
|
|
2365
2711
|
function useControllable(controlled, fallback) {
|
|
2366
|
-
const [uncontrolled, setUncontrolled] =
|
|
2712
|
+
const [uncontrolled, setUncontrolled] = React11.useState(fallback);
|
|
2367
2713
|
return [
|
|
2368
2714
|
controlled === void 0 ? uncontrolled : controlled,
|
|
2369
2715
|
setUncontrolled
|
|
2370
2716
|
];
|
|
2371
2717
|
}
|
|
2372
|
-
var TreeView =
|
|
2718
|
+
var TreeView = React11.forwardRef(
|
|
2373
2719
|
({
|
|
2374
2720
|
nodes,
|
|
2375
2721
|
expandedIds,
|
|
@@ -2384,8 +2730,8 @@ var TreeView = React10.forwardRef(
|
|
|
2384
2730
|
"aria-describedby": ariaDescribedBy,
|
|
2385
2731
|
...props
|
|
2386
2732
|
}, forwardedRef) => {
|
|
2387
|
-
const scrollRef =
|
|
2388
|
-
const attachScroll =
|
|
2733
|
+
const scrollRef = React11.useRef(null);
|
|
2734
|
+
const attachScroll = React11.useCallback(
|
|
2389
2735
|
(node) => {
|
|
2390
2736
|
scrollRef.current = node;
|
|
2391
2737
|
if (typeof forwardedRef === "function") forwardedRef(node);
|
|
@@ -2399,7 +2745,7 @@ var TreeView = React10.forwardRef(
|
|
|
2399
2745
|
expandedIds === void 0 ? void 0 : [...expandedIds],
|
|
2400
2746
|
[...defaultExpandedIds ?? []]
|
|
2401
2747
|
);
|
|
2402
|
-
const expanded =
|
|
2748
|
+
const expanded = React11.useMemo(
|
|
2403
2749
|
() => new Set(expandedIds ?? expandedState),
|
|
2404
2750
|
[expandedIds, expandedState]
|
|
2405
2751
|
);
|
|
@@ -2407,11 +2753,11 @@ var TreeView = React10.forwardRef(
|
|
|
2407
2753
|
selectedId === void 0 ? void 0 : selectedId,
|
|
2408
2754
|
defaultSelectedId ?? null
|
|
2409
2755
|
);
|
|
2410
|
-
const rows =
|
|
2756
|
+
const rows = React11.useMemo(
|
|
2411
2757
|
() => flatten(nodes, expanded),
|
|
2412
2758
|
[nodes, expanded]
|
|
2413
2759
|
);
|
|
2414
|
-
const [activeId, setActiveId] =
|
|
2760
|
+
const [activeId, setActiveId] = React11.useState(null);
|
|
2415
2761
|
const activeIndex = Math.max(
|
|
2416
2762
|
0,
|
|
2417
2763
|
rows.findIndex((row) => row.node.id === (activeId ?? selected))
|
|
@@ -2455,7 +2801,7 @@ var TreeView = React10.forwardRef(
|
|
|
2455
2801
|
}
|
|
2456
2802
|
return from;
|
|
2457
2803
|
};
|
|
2458
|
-
const typeahead =
|
|
2804
|
+
const typeahead = React11.useRef({ query: "", at: 0 });
|
|
2459
2805
|
const onKeyDown = (event) => {
|
|
2460
2806
|
const index = activeIndex;
|
|
2461
2807
|
const row = rows[index];
|
|
@@ -2552,13 +2898,13 @@ var TreeView = React10.forwardRef(
|
|
|
2552
2898
|
const virtualItems = virtualizer.getVirtualItems();
|
|
2553
2899
|
const usable = (index) => rows[index]?.node.disabled !== true;
|
|
2554
2900
|
const tabStopIndex = virtualItems.some((item) => item.index === activeIndex) && usable(activeIndex) ? activeIndex : virtualItems.find((item) => usable(item.index))?.index ?? -1;
|
|
2555
|
-
return /* @__PURE__ */
|
|
2901
|
+
return /* @__PURE__ */ jsx37(
|
|
2556
2902
|
"div",
|
|
2557
2903
|
{
|
|
2558
2904
|
ref: attachScroll,
|
|
2559
2905
|
className: cn("overflow-auto", className),
|
|
2560
2906
|
...props,
|
|
2561
|
-
children: /* @__PURE__ */
|
|
2907
|
+
children: /* @__PURE__ */ jsx37(
|
|
2562
2908
|
"div",
|
|
2563
2909
|
{
|
|
2564
2910
|
role: "tree",
|
|
@@ -2571,7 +2917,7 @@ var TreeView = React10.forwardRef(
|
|
|
2571
2917
|
const row = rows[item.index];
|
|
2572
2918
|
if (row === void 0) return null;
|
|
2573
2919
|
const isSelected = selected === row.node.id;
|
|
2574
|
-
return /* @__PURE__ */
|
|
2920
|
+
return /* @__PURE__ */ jsxs16(
|
|
2575
2921
|
"div",
|
|
2576
2922
|
{
|
|
2577
2923
|
"data-tree-index": item.index,
|
|
@@ -2601,7 +2947,7 @@ var TreeView = React10.forwardRef(
|
|
|
2601
2947
|
paddingLeft: 4 + row.level * INDENT_PER_LEVEL
|
|
2602
2948
|
},
|
|
2603
2949
|
children: [
|
|
2604
|
-
/* @__PURE__ */
|
|
2950
|
+
/* @__PURE__ */ jsx37(
|
|
2605
2951
|
"span",
|
|
2606
2952
|
{
|
|
2607
2953
|
"aria-hidden": "true",
|
|
@@ -2611,7 +2957,7 @@ var TreeView = React10.forwardRef(
|
|
|
2611
2957
|
event.stopPropagation();
|
|
2612
2958
|
setExpansion(row.node.id, !expanded.has(row.node.id));
|
|
2613
2959
|
},
|
|
2614
|
-
children: row.hasChildren ? /* @__PURE__ */
|
|
2960
|
+
children: row.hasChildren ? /* @__PURE__ */ jsx37(
|
|
2615
2961
|
ChevronRight3,
|
|
2616
2962
|
{
|
|
2617
2963
|
className: cn(
|
|
@@ -2622,8 +2968,8 @@ var TreeView = React10.forwardRef(
|
|
|
2622
2968
|
) : null
|
|
2623
2969
|
}
|
|
2624
2970
|
),
|
|
2625
|
-
row.node.icon !== void 0 ? /* @__PURE__ */
|
|
2626
|
-
/* @__PURE__ */
|
|
2971
|
+
row.node.icon !== void 0 ? /* @__PURE__ */ jsx37("span", { className: "flex size-4 shrink-0 items-center justify-center text-muted-foreground", children: row.node.icon }) : null,
|
|
2972
|
+
/* @__PURE__ */ jsx37("span", { className: "truncate", children: row.node.label })
|
|
2627
2973
|
]
|
|
2628
2974
|
},
|
|
2629
2975
|
row.node.id
|
|
@@ -2636,6 +2982,837 @@ var TreeView = React10.forwardRef(
|
|
|
2636
2982
|
}
|
|
2637
2983
|
);
|
|
2638
2984
|
TreeView.displayName = "TreeView";
|
|
2985
|
+
|
|
2986
|
+
// src/components/slider.tsx
|
|
2987
|
+
import * as SliderPrimitive from "@radix-ui/react-slider";
|
|
2988
|
+
import * as React12 from "react";
|
|
2989
|
+
|
|
2990
|
+
// src/lib/dev-warn.ts
|
|
2991
|
+
var emitted = /* @__PURE__ */ new Set();
|
|
2992
|
+
var SPEAKING_ENVIRONMENTS = /* @__PURE__ */ new Set(["development", "test"]);
|
|
2993
|
+
function isDevelopmentRuntime() {
|
|
2994
|
+
if (typeof process === "undefined") return false;
|
|
2995
|
+
const env = process?.env?.NODE_ENV;
|
|
2996
|
+
return env !== void 0 && SPEAKING_ENVIRONMENTS.has(env);
|
|
2997
|
+
}
|
|
2998
|
+
function devWarnOnce(condition, message) {
|
|
2999
|
+
if (condition) return;
|
|
3000
|
+
if (!isDevelopmentRuntime()) return;
|
|
3001
|
+
if (emitted.has(message)) return;
|
|
3002
|
+
emitted.add(message);
|
|
3003
|
+
console.warn(`[@nextlyhq/ui] ${message}`);
|
|
3004
|
+
}
|
|
3005
|
+
|
|
3006
|
+
// src/components/slider.tsx
|
|
3007
|
+
import { jsx as jsx38, jsxs as jsxs17 } from "react/jsx-runtime";
|
|
3008
|
+
function thumbCount(value, defaultValue) {
|
|
3009
|
+
return Math.max(1, value?.length ?? defaultValue?.length ?? 1);
|
|
3010
|
+
}
|
|
3011
|
+
function hasAccessibleName(value) {
|
|
3012
|
+
return value !== void 0 && value.trim() !== "";
|
|
3013
|
+
}
|
|
3014
|
+
var Slider = React12.forwardRef(
|
|
3015
|
+
({
|
|
3016
|
+
className,
|
|
3017
|
+
value,
|
|
3018
|
+
defaultValue,
|
|
3019
|
+
thumbs,
|
|
3020
|
+
orientation = "horizontal",
|
|
3021
|
+
"aria-label": ariaLabel,
|
|
3022
|
+
"aria-labelledby": ariaLabelledBy,
|
|
3023
|
+
...props
|
|
3024
|
+
}, ref) => {
|
|
3025
|
+
const initialUncontrolledCount = React12.useRef(
|
|
3026
|
+
thumbCount(void 0, defaultValue)
|
|
3027
|
+
).current;
|
|
3028
|
+
const count = value?.length ?? initialUncontrolledCount;
|
|
3029
|
+
const isEmptyDefault = defaultValue !== void 0 && defaultValue.length === 0;
|
|
3030
|
+
const isEmptyControlled = value !== void 0 && value.length === 0;
|
|
3031
|
+
devWarnOnce(
|
|
3032
|
+
!isEmptyDefault && !isEmptyControlled,
|
|
3033
|
+
"Slider: `value`/`defaultValue` must hold one number per thumb, and an empty array holds none \u2014 the control has nothing to slide. An empty `defaultValue` falls back to `min`; an empty `value` renders nothing at all, because a controlled slider cannot be given a value without taking state the caller owns. Render nothing until the value is loaded rather than passing `[]`."
|
|
3034
|
+
);
|
|
3035
|
+
if (isEmptyControlled) return null;
|
|
3036
|
+
const isNamed = (index) => {
|
|
3037
|
+
const own = thumbs?.[index];
|
|
3038
|
+
if (hasAccessibleName(own?.["aria-label"])) return true;
|
|
3039
|
+
if (hasAccessibleName(own?.["aria-labelledby"])) return true;
|
|
3040
|
+
return count === 1 && (hasAccessibleName(ariaLabel) || hasAccessibleName(ariaLabelledBy));
|
|
3041
|
+
};
|
|
3042
|
+
devWarnOnce(
|
|
3043
|
+
Array.from({ length: count }).every((_, i) => isNamed(i)),
|
|
3044
|
+
"Slider: every thumb needs an accessible name. A single thumb may take it from the root's `aria-label`/`aria-labelledby`; a range needs one `thumbs` entry per thumb, because the root's name is not inherited and two thumbs sharing one name are announced identically."
|
|
3045
|
+
);
|
|
3046
|
+
const ariaFor = (index) => {
|
|
3047
|
+
const supplied = thumbs?.[index] ?? {};
|
|
3048
|
+
const ownLabel = hasAccessibleName(supplied["aria-label"]) ? supplied["aria-label"] : void 0;
|
|
3049
|
+
const ownLabelledBy = hasAccessibleName(supplied["aria-labelledby"]) ? supplied["aria-labelledby"] : void 0;
|
|
3050
|
+
if (count !== 1) {
|
|
3051
|
+
return {
|
|
3052
|
+
...supplied,
|
|
3053
|
+
"aria-label": ownLabel,
|
|
3054
|
+
"aria-labelledby": ownLabelledBy
|
|
3055
|
+
};
|
|
3056
|
+
}
|
|
3057
|
+
const namesItself = ownLabel !== void 0 || ownLabelledBy !== void 0;
|
|
3058
|
+
return {
|
|
3059
|
+
"aria-label": namesItself ? ownLabel : ariaLabel,
|
|
3060
|
+
"aria-labelledby": namesItself ? ownLabelledBy : ariaLabelledBy,
|
|
3061
|
+
"aria-valuetext": supplied["aria-valuetext"],
|
|
3062
|
+
"aria-describedby": supplied["aria-describedby"]
|
|
3063
|
+
};
|
|
3064
|
+
};
|
|
3065
|
+
const isVertical = orientation === "vertical";
|
|
3066
|
+
return (
|
|
3067
|
+
// `aria-label`/`aria-labelledby` are destructured out above rather than
|
|
3068
|
+
// spread here: left on the root they would be a second, roleless copy
|
|
3069
|
+
// of a name only the thumb is read for.
|
|
3070
|
+
/* @__PURE__ */ jsxs17(
|
|
3071
|
+
SliderPrimitive.Root,
|
|
3072
|
+
{
|
|
3073
|
+
ref,
|
|
3074
|
+
className: cn(
|
|
3075
|
+
"relative flex touch-none select-none items-center",
|
|
3076
|
+
// WCAG 2.5.8 wants a 24px target. Padding alone does not reach it: the
|
|
3077
|
+
// thumb is absolutely positioned, so the cross-axis size is the 6px
|
|
3078
|
+
// track plus the padding — 22px with `py-2`. An explicit minimum
|
|
3079
|
+
// states the target rather than leaving it to arithmetic that moves
|
|
3080
|
+
// whenever the track thickness does.
|
|
3081
|
+
isVertical ? (
|
|
3082
|
+
// A vertical slider needs a LENGTH, and it cannot inherit one:
|
|
3083
|
+
// `h-full` inside an auto-height parent resolves to zero, leaving
|
|
3084
|
+
// a control with no track to drag along. A concrete default is
|
|
3085
|
+
// usable everywhere and, being a plain utility, is replaced by a
|
|
3086
|
+
// caller's own `h-*` — including `h-full`, for the fill-the-parent
|
|
3087
|
+
// case this default gives up.
|
|
3088
|
+
"h-44 min-w-6 flex-col px-2"
|
|
3089
|
+
) : "min-h-6 w-full py-2",
|
|
3090
|
+
"data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
|
|
3091
|
+
className
|
|
3092
|
+
),
|
|
3093
|
+
orientation,
|
|
3094
|
+
value,
|
|
3095
|
+
defaultValue: isEmptyDefault ? void 0 : defaultValue,
|
|
3096
|
+
...props,
|
|
3097
|
+
children: [
|
|
3098
|
+
/* @__PURE__ */ jsx38(
|
|
3099
|
+
SliderPrimitive.Track,
|
|
3100
|
+
{
|
|
3101
|
+
className: cn(
|
|
3102
|
+
"bg-secondary relative grow overflow-hidden rounded-full",
|
|
3103
|
+
isVertical ? "h-full w-1.5" : "h-1.5 w-full"
|
|
3104
|
+
),
|
|
3105
|
+
children: /* @__PURE__ */ jsx38(
|
|
3106
|
+
SliderPrimitive.Range,
|
|
3107
|
+
{
|
|
3108
|
+
className: cn(
|
|
3109
|
+
"bg-primary absolute",
|
|
3110
|
+
isVertical ? "w-full" : "h-full"
|
|
3111
|
+
)
|
|
3112
|
+
}
|
|
3113
|
+
)
|
|
3114
|
+
}
|
|
3115
|
+
),
|
|
3116
|
+
Array.from({ length: count }, (_, i) => /* @__PURE__ */ jsx38(
|
|
3117
|
+
SliderPrimitive.Thumb,
|
|
3118
|
+
{
|
|
3119
|
+
...ariaFor(i),
|
|
3120
|
+
className: cn(
|
|
3121
|
+
"border-primary bg-background block h-4 w-4 rounded-full border-2",
|
|
3122
|
+
"ring-offset-background transition-colors",
|
|
3123
|
+
"focus-visible:ring-ring focus-visible:outline-none focus-visible:ring-2",
|
|
3124
|
+
"focus-visible:ring-offset-2",
|
|
3125
|
+
"disabled:pointer-events-none disabled:opacity-50"
|
|
3126
|
+
)
|
|
3127
|
+
},
|
|
3128
|
+
i
|
|
3129
|
+
))
|
|
3130
|
+
]
|
|
3131
|
+
}
|
|
3132
|
+
)
|
|
3133
|
+
);
|
|
3134
|
+
}
|
|
3135
|
+
);
|
|
3136
|
+
Slider.displayName = SliderPrimitive.Root.displayName;
|
|
3137
|
+
|
|
3138
|
+
// src/lib/shortcuts/react.tsx
|
|
3139
|
+
import * as React13 from "react";
|
|
3140
|
+
|
|
3141
|
+
// src/lib/shortcuts/key-spec.ts
|
|
3142
|
+
function normalizeKey(key) {
|
|
3143
|
+
return [...key].length === 1 ? key.toLowerCase() : key;
|
|
3144
|
+
}
|
|
3145
|
+
function shiftIsMeaningful(key) {
|
|
3146
|
+
if (key.length > 1) return true;
|
|
3147
|
+
if (key === " ") return true;
|
|
3148
|
+
return /[\p{L}\p{N}]/u.test(key);
|
|
3149
|
+
}
|
|
3150
|
+
function parseKeys(spec) {
|
|
3151
|
+
const steps = spec.trim().split(/\s+/).filter(Boolean);
|
|
3152
|
+
if (steps.length === 0) {
|
|
3153
|
+
throw new Error(`Shortcut spec is empty: ${JSON.stringify(spec)}`);
|
|
3154
|
+
}
|
|
3155
|
+
return steps.map((step) => parseChord(step, spec));
|
|
3156
|
+
}
|
|
3157
|
+
function parseChord(step, spec) {
|
|
3158
|
+
const trailingPlusIsKey = step.length > 2 && step.endsWith("++");
|
|
3159
|
+
const body = trailingPlusIsKey ? step.slice(0, -1) : step;
|
|
3160
|
+
const parts = step === "+" ? ["+"] : body.split("+").filter(Boolean);
|
|
3161
|
+
let mod = false;
|
|
3162
|
+
let ctrl = false;
|
|
3163
|
+
let meta = false;
|
|
3164
|
+
let alt = false;
|
|
3165
|
+
let shift = false;
|
|
3166
|
+
let key;
|
|
3167
|
+
for (const raw of parts) {
|
|
3168
|
+
switch (raw.toLowerCase()) {
|
|
3169
|
+
case "mod":
|
|
3170
|
+
mod = true;
|
|
3171
|
+
break;
|
|
3172
|
+
case "ctrl":
|
|
3173
|
+
case "control":
|
|
3174
|
+
ctrl = true;
|
|
3175
|
+
break;
|
|
3176
|
+
case "meta":
|
|
3177
|
+
case "cmd":
|
|
3178
|
+
case "command":
|
|
3179
|
+
meta = true;
|
|
3180
|
+
break;
|
|
3181
|
+
case "alt":
|
|
3182
|
+
case "option":
|
|
3183
|
+
alt = true;
|
|
3184
|
+
break;
|
|
3185
|
+
case "shift":
|
|
3186
|
+
shift = true;
|
|
3187
|
+
break;
|
|
3188
|
+
case "space":
|
|
3189
|
+
key = " ";
|
|
3190
|
+
break;
|
|
3191
|
+
default:
|
|
3192
|
+
if (key !== void 0) {
|
|
3193
|
+
throw new Error(
|
|
3194
|
+
`Shortcut step "${step}" names two keys ("${key}" and "${raw}") in ${JSON.stringify(spec)}`
|
|
3195
|
+
);
|
|
3196
|
+
}
|
|
3197
|
+
key = raw;
|
|
3198
|
+
}
|
|
3199
|
+
}
|
|
3200
|
+
if (trailingPlusIsKey) {
|
|
3201
|
+
if (key !== void 0) {
|
|
3202
|
+
throw new Error(
|
|
3203
|
+
`Shortcut step has more than one key: ${JSON.stringify(step)} in ${JSON.stringify(spec)}`
|
|
3204
|
+
);
|
|
3205
|
+
}
|
|
3206
|
+
key = "+";
|
|
3207
|
+
}
|
|
3208
|
+
if (key === void 0) {
|
|
3209
|
+
throw new Error(
|
|
3210
|
+
`Shortcut step "${step}" names modifiers but no key, in ${JSON.stringify(spec)}`
|
|
3211
|
+
);
|
|
3212
|
+
}
|
|
3213
|
+
return { key: normalizeKey(key), mod, ctrl, meta, alt, shift };
|
|
3214
|
+
}
|
|
3215
|
+
function chordMatches(chord, key, state, isApple) {
|
|
3216
|
+
if (normalizeKey(key) !== chord.key) return false;
|
|
3217
|
+
const wantsCtrl = chord.ctrl || chord.mod && !isApple;
|
|
3218
|
+
const wantsMeta = chord.meta || chord.mod && isApple;
|
|
3219
|
+
if (state.metaKey !== wantsMeta) return false;
|
|
3220
|
+
const altGraph = state.getModifierState?.("AltGraph") ?? false;
|
|
3221
|
+
const synthetic = altGraph && [...chord.key].length === 1 && !wantsCtrl && !chord.alt;
|
|
3222
|
+
if (!synthetic) {
|
|
3223
|
+
if (state.ctrlKey !== wantsCtrl) return false;
|
|
3224
|
+
if (state.altKey !== chord.alt) return false;
|
|
3225
|
+
}
|
|
3226
|
+
if (shiftIsMeaningful(chord.key) && state.shiftKey !== chord.shift)
|
|
3227
|
+
return false;
|
|
3228
|
+
return true;
|
|
3229
|
+
}
|
|
3230
|
+
function detectApplePlatform() {
|
|
3231
|
+
if (typeof navigator === "undefined") return false;
|
|
3232
|
+
const candidate = navigator;
|
|
3233
|
+
const platform = candidate.userAgentData?.platform ?? navigator.platform ?? "";
|
|
3234
|
+
return /mac|iphone|ipad|ipod/i.test(platform);
|
|
3235
|
+
}
|
|
3236
|
+
|
|
3237
|
+
// src/lib/shortcuts/manager.ts
|
|
3238
|
+
var DEFAULT_SEQUENCE_TIMEOUT_MS = 1e3;
|
|
3239
|
+
function signature(event) {
|
|
3240
|
+
return event.code || event.key;
|
|
3241
|
+
}
|
|
3242
|
+
function eventTarget(event) {
|
|
3243
|
+
const path = event.composedPath?.();
|
|
3244
|
+
return path && path.length > 0 ? path[0] ?? null : event.target;
|
|
3245
|
+
}
|
|
3246
|
+
function asElement(target) {
|
|
3247
|
+
if (target === null || typeof target !== "object") return null;
|
|
3248
|
+
const node = target;
|
|
3249
|
+
if (node.nodeType !== 1 || typeof node.tagName !== "string") return null;
|
|
3250
|
+
return target;
|
|
3251
|
+
}
|
|
3252
|
+
function inputType(element) {
|
|
3253
|
+
if (element.tagName !== "INPUT") return "";
|
|
3254
|
+
const value = element.type;
|
|
3255
|
+
return typeof value === "string" ? value.toLowerCase() : "";
|
|
3256
|
+
}
|
|
3257
|
+
function controlOwnsKey(target, event) {
|
|
3258
|
+
if (event.ctrlKey || event.metaKey || event.altKey) return false;
|
|
3259
|
+
const element = asElement(target);
|
|
3260
|
+
if (!element) return false;
|
|
3261
|
+
const tag = element.tagName;
|
|
3262
|
+
const type = inputType(element);
|
|
3263
|
+
if (tag === "BUTTON" || type === "button" || type === "submit" || type === "reset" || type === "image") {
|
|
3264
|
+
return event.key === " " || event.key === "Enter";
|
|
3265
|
+
}
|
|
3266
|
+
if (tag === "A" && element.getAttribute("href") !== null) {
|
|
3267
|
+
return event.key === "Enter";
|
|
3268
|
+
}
|
|
3269
|
+
if (tag === "SUMMARY") return event.key === " " || event.key === "Enter";
|
|
3270
|
+
if (type === "checkbox") return event.key === " ";
|
|
3271
|
+
if (type === "color") return event.key === " " || event.key === "Enter";
|
|
3272
|
+
if (type === "file") return event.key === " " || event.key === "Enter";
|
|
3273
|
+
if (type === "range") {
|
|
3274
|
+
return event.key.startsWith("Arrow") || RANGE_KEYS.has(event.key);
|
|
3275
|
+
}
|
|
3276
|
+
if (type === "radio") {
|
|
3277
|
+
return event.key === " " || event.key.startsWith("Arrow");
|
|
3278
|
+
}
|
|
3279
|
+
return false;
|
|
3280
|
+
}
|
|
3281
|
+
function isTypingTarget(target) {
|
|
3282
|
+
const element = asElement(target);
|
|
3283
|
+
if (!element) return false;
|
|
3284
|
+
if (element.isContentEditable) return true;
|
|
3285
|
+
const tag = element.tagName;
|
|
3286
|
+
if (tag === "TEXTAREA") return true;
|
|
3287
|
+
if (tag === "SELECT") return true;
|
|
3288
|
+
if (tag === "INPUT") {
|
|
3289
|
+
return !NON_TEXT_INPUT_TYPES.has(inputType(element));
|
|
3290
|
+
}
|
|
3291
|
+
const role = element.getAttribute("role");
|
|
3292
|
+
return role !== null && TYPE_AHEAD_ROLES.has(role);
|
|
3293
|
+
}
|
|
3294
|
+
var NON_TEXT_INPUT_TYPES = /* @__PURE__ */ new Set([
|
|
3295
|
+
"button",
|
|
3296
|
+
"checkbox",
|
|
3297
|
+
"color",
|
|
3298
|
+
"file",
|
|
3299
|
+
"hidden",
|
|
3300
|
+
"image",
|
|
3301
|
+
"radio",
|
|
3302
|
+
"range",
|
|
3303
|
+
"reset",
|
|
3304
|
+
"submit"
|
|
3305
|
+
]);
|
|
3306
|
+
function firesWhileTyping(prepared) {
|
|
3307
|
+
const explicit = prepared.binding.whenTyping;
|
|
3308
|
+
if (explicit !== void 0) return explicit;
|
|
3309
|
+
const first = prepared.keys[0];
|
|
3310
|
+
if (first === void 0) return false;
|
|
3311
|
+
return first.mod || first.ctrl || first.meta || first.alt || first.key === "Escape";
|
|
3312
|
+
}
|
|
3313
|
+
function createShortcutManager(options = {}) {
|
|
3314
|
+
const isApple = options.isApple ?? detectApplePlatform();
|
|
3315
|
+
const sequenceTimeoutMs = options.sequenceTimeoutMs ?? DEFAULT_SEQUENCE_TIMEOUT_MS;
|
|
3316
|
+
const now = options.now ?? (() => Date.now());
|
|
3317
|
+
const layers = /* @__PURE__ */ new Set();
|
|
3318
|
+
let nextSequence = 0;
|
|
3319
|
+
let pendingAt = null;
|
|
3320
|
+
let pendingLayer = null;
|
|
3321
|
+
const consumedPresses = /* @__PURE__ */ new Map();
|
|
3322
|
+
let pendingKey = null;
|
|
3323
|
+
function layerShape(bindings, options2) {
|
|
3324
|
+
const keys = bindings.map((b) => b.binding.keys).join("\0");
|
|
3325
|
+
return `${keys}${options2.depth}${options2.blocking === true}${options2.enabled !== false}`;
|
|
3326
|
+
}
|
|
3327
|
+
function blocking() {
|
|
3328
|
+
return ordered().some((layer) => layer.options.blocking === true);
|
|
3329
|
+
}
|
|
3330
|
+
function abandonSequence() {
|
|
3331
|
+
pendingAt = null;
|
|
3332
|
+
pressedEvents.length = 0;
|
|
3333
|
+
pendingLayer = null;
|
|
3334
|
+
pendingKey = null;
|
|
3335
|
+
}
|
|
3336
|
+
function prepare(bindings) {
|
|
3337
|
+
return bindings.map((binding) => ({
|
|
3338
|
+
binding,
|
|
3339
|
+
keys: parseKeys(binding.keys)
|
|
3340
|
+
}));
|
|
3341
|
+
}
|
|
3342
|
+
function ordered() {
|
|
3343
|
+
return [...layers].filter((layer) => layer.options.enabled !== false).sort(
|
|
3344
|
+
(a, b) => b.options.depth - a.options.depth || b.sequence - a.sequence
|
|
3345
|
+
);
|
|
3346
|
+
}
|
|
3347
|
+
function matchDepth(prepared, pressed) {
|
|
3348
|
+
if (pressed.length > prepared.keys.length) return "none";
|
|
3349
|
+
for (let i = 0; i < pressed.length; i++) {
|
|
3350
|
+
const chord = prepared.keys[i];
|
|
3351
|
+
const event = pressed[i];
|
|
3352
|
+
if (chord === void 0 || event === void 0) return "none";
|
|
3353
|
+
if (!chordMatches(chord, event.key, event, isApple)) return "none";
|
|
3354
|
+
}
|
|
3355
|
+
return pressed.length === prepared.keys.length ? "exact" : "prefix";
|
|
3356
|
+
}
|
|
3357
|
+
function fire(prepared, event, invoke) {
|
|
3358
|
+
if (prepared.binding.preventDefault !== false) event.preventDefault();
|
|
3359
|
+
if (invoke) prepared.binding.run(event);
|
|
3360
|
+
}
|
|
3361
|
+
function insertsText(event, typing) {
|
|
3362
|
+
if (event.key === "Tab")
|
|
3363
|
+
return !event.ctrlKey && !event.metaKey && !event.altKey;
|
|
3364
|
+
if (!typing) return false;
|
|
3365
|
+
const altGraph = event.getModifierState?.("AltGraph") ?? false;
|
|
3366
|
+
if (!altGraph && (event.ctrlKey || event.metaKey)) {
|
|
3367
|
+
const letter = event.key.length === 1 ? event.key.toLowerCase() : event.key;
|
|
3368
|
+
if (letter === "z" && event.shiftKey) return !event.altKey;
|
|
3369
|
+
if (letter === REDO_LETTER)
|
|
3370
|
+
return !isApple && !event.shiftKey && !event.altKey;
|
|
3371
|
+
if (EDITING_NAVIGATION.has(event.key)) return !event.altKey || isApple;
|
|
3372
|
+
if (event.shiftKey || event.altKey) return false;
|
|
3373
|
+
return EDITING_LETTERS.has(letter);
|
|
3374
|
+
}
|
|
3375
|
+
if (!altGraph && event.altKey) {
|
|
3376
|
+
if ((event.key === "ArrowDown" || event.key === "ArrowUp") && asElement(eventTarget(event))?.tagName === "SELECT") {
|
|
3377
|
+
return true;
|
|
3378
|
+
}
|
|
3379
|
+
if (!isApple) return false;
|
|
3380
|
+
if (EDITING_NAVIGATION.has(event.key)) return true;
|
|
3381
|
+
}
|
|
3382
|
+
if (event.key === "Dead" || event.key === "Process") return true;
|
|
3383
|
+
if ([...event.key].length === 1) return true;
|
|
3384
|
+
if (AMBIGUOUS_KEYS.has(event.key)) return targetOwnsAmbiguousKey(event);
|
|
3385
|
+
return FIELD_KEYS.has(event.key);
|
|
3386
|
+
}
|
|
3387
|
+
function offer(pressed, event, typing, invoke) {
|
|
3388
|
+
for (const layer of ordered()) {
|
|
3389
|
+
const mayMatch = pressed.length <= 1 || pendingLayer === null || layer === pendingLayer;
|
|
3390
|
+
if (mayMatch) {
|
|
3391
|
+
let prefixed = false;
|
|
3392
|
+
for (const prepared of layer.bindings) {
|
|
3393
|
+
if (typing && !firesWhileTyping(prepared)) continue;
|
|
3394
|
+
if (prepared.binding.when && !prepared.binding.when()) continue;
|
|
3395
|
+
const depth = matchDepth(prepared, pressed);
|
|
3396
|
+
if (depth === "exact") {
|
|
3397
|
+
fire(prepared, event, invoke);
|
|
3398
|
+
return "fired";
|
|
3399
|
+
}
|
|
3400
|
+
if (depth === "prefix") prefixed = true;
|
|
3401
|
+
}
|
|
3402
|
+
if (prefixed) {
|
|
3403
|
+
pendingLayer = layer;
|
|
3404
|
+
event.preventDefault();
|
|
3405
|
+
return "pending";
|
|
3406
|
+
}
|
|
3407
|
+
}
|
|
3408
|
+
if (layer.options.blocking) return "blocked";
|
|
3409
|
+
}
|
|
3410
|
+
return "none";
|
|
3411
|
+
}
|
|
3412
|
+
function warnOnPrefixConflicts(prepared, layerName) {
|
|
3413
|
+
const resolved = (chord) => {
|
|
3414
|
+
const ctrl = chord.ctrl || chord.mod && !isApple;
|
|
3415
|
+
const meta = chord.meta || chord.mod && isApple;
|
|
3416
|
+
const shift = shiftIsMeaningful(chord.key) ? chord.shift : false;
|
|
3417
|
+
return `${chord.key}\0${ctrl}${meta}${chord.alt}${shift}`;
|
|
3418
|
+
};
|
|
3419
|
+
const sameChord = (a, b) => resolved(a) === resolved(b);
|
|
3420
|
+
for (const short of prepared) {
|
|
3421
|
+
for (const long of prepared) {
|
|
3422
|
+
if (short === long || short.keys.length >= long.keys.length) continue;
|
|
3423
|
+
if (short.binding.when !== void 0) continue;
|
|
3424
|
+
if (!firesWhileTyping(short) && firesWhileTyping(long)) continue;
|
|
3425
|
+
if (short.keys.every((chord, i) => sameChord(chord, long.keys[i]))) {
|
|
3426
|
+
devWarnOnce(
|
|
3427
|
+
false,
|
|
3428
|
+
`shortcuts: in layer "${layerName}", "${short.binding.keys}" is a prefix of "${long.binding.keys}", so the longer one can never fire. Bind one or the other.`
|
|
3429
|
+
);
|
|
3430
|
+
}
|
|
3431
|
+
}
|
|
3432
|
+
}
|
|
3433
|
+
}
|
|
3434
|
+
const watchers = /* @__PURE__ */ new Set();
|
|
3435
|
+
let snapshot = null;
|
|
3436
|
+
function computeSnapshot() {
|
|
3437
|
+
return ordered().flatMap(
|
|
3438
|
+
(layer) => layer.bindings.map((prepared) => ({
|
|
3439
|
+
keys: prepared.binding.keys,
|
|
3440
|
+
description: prepared.binding.description,
|
|
3441
|
+
layer: layer.options.name
|
|
3442
|
+
}))
|
|
3443
|
+
);
|
|
3444
|
+
}
|
|
3445
|
+
function sameShortcuts(a, b) {
|
|
3446
|
+
return a.length === b.length && a.every(
|
|
3447
|
+
(entry, index) => entry.keys === b[index]?.keys && entry.description === b[index]?.description && entry.layer === b[index]?.layer
|
|
3448
|
+
);
|
|
3449
|
+
}
|
|
3450
|
+
function changed() {
|
|
3451
|
+
const previous = snapshot;
|
|
3452
|
+
snapshot = null;
|
|
3453
|
+
if (watchers.size === 0) return;
|
|
3454
|
+
const next = computeSnapshot();
|
|
3455
|
+
if (previous && sameShortcuts(previous, next)) {
|
|
3456
|
+
snapshot = previous;
|
|
3457
|
+
return;
|
|
3458
|
+
}
|
|
3459
|
+
snapshot = next;
|
|
3460
|
+
for (const watcher of watchers) watcher();
|
|
3461
|
+
}
|
|
3462
|
+
const pressedEvents = [];
|
|
3463
|
+
function runOffer(pressed, event, typing) {
|
|
3464
|
+
try {
|
|
3465
|
+
return offer(pressed, event, typing, true);
|
|
3466
|
+
} catch (error) {
|
|
3467
|
+
abandonSequence();
|
|
3468
|
+
consumedPresses.delete(signature(event));
|
|
3469
|
+
throw error;
|
|
3470
|
+
}
|
|
3471
|
+
}
|
|
3472
|
+
function handle(event) {
|
|
3473
|
+
if (typeof event.key !== "string") return false;
|
|
3474
|
+
if (event.defaultPrevented) {
|
|
3475
|
+
abandonSequence();
|
|
3476
|
+
return true;
|
|
3477
|
+
}
|
|
3478
|
+
if (event.isComposing) {
|
|
3479
|
+
abandonSequence();
|
|
3480
|
+
return true;
|
|
3481
|
+
}
|
|
3482
|
+
if (MODIFIER_KEYS.has(event.key)) return blocking();
|
|
3483
|
+
if (controlOwnsKey(eventTarget(event), event)) {
|
|
3484
|
+
abandonSequence();
|
|
3485
|
+
return true;
|
|
3486
|
+
}
|
|
3487
|
+
const typing = isTypingTarget(eventTarget(event));
|
|
3488
|
+
if (event.repeat) {
|
|
3489
|
+
if (pendingKey !== signature(event)) {
|
|
3490
|
+
abandonSequence();
|
|
3491
|
+
}
|
|
3492
|
+
const held = consumedPresses.get(signature(event));
|
|
3493
|
+
if (held) {
|
|
3494
|
+
if (held.prevented) {
|
|
3495
|
+
event.preventDefault();
|
|
3496
|
+
} else {
|
|
3497
|
+
const still = offer([event], event, typing, false);
|
|
3498
|
+
if (still !== "fired" && !insertsText(event, typing)) {
|
|
3499
|
+
event.preventDefault();
|
|
3500
|
+
}
|
|
3501
|
+
}
|
|
3502
|
+
return true;
|
|
3503
|
+
}
|
|
3504
|
+
const repeated = offer([event], event, typing, false);
|
|
3505
|
+
if (repeated === "blocked" && !insertsText(event, typing)) {
|
|
3506
|
+
event.preventDefault();
|
|
3507
|
+
}
|
|
3508
|
+
return repeated !== "none";
|
|
3509
|
+
}
|
|
3510
|
+
if (pendingAt !== null && now() - pendingAt > sequenceTimeoutMs) {
|
|
3511
|
+
abandonSequence();
|
|
3512
|
+
}
|
|
3513
|
+
pressedEvents.push(event);
|
|
3514
|
+
let outcome = runOffer(pressedEvents, event, typing);
|
|
3515
|
+
if (pressedEvents.length > 1 && (outcome === "none" || outcome === "blocked")) {
|
|
3516
|
+
abandonSequence();
|
|
3517
|
+
pressedEvents.push(event);
|
|
3518
|
+
outcome = runOffer(pressedEvents, event, typing);
|
|
3519
|
+
}
|
|
3520
|
+
if (outcome === "pending") {
|
|
3521
|
+
pendingAt = now();
|
|
3522
|
+
pendingKey = signature(event);
|
|
3523
|
+
consumedPresses.set(signature(event), {
|
|
3524
|
+
prevented: event.defaultPrevented
|
|
3525
|
+
});
|
|
3526
|
+
return true;
|
|
3527
|
+
}
|
|
3528
|
+
abandonSequence();
|
|
3529
|
+
if (outcome === "blocked") {
|
|
3530
|
+
if (!insertsText(event, typing)) event.preventDefault();
|
|
3531
|
+
}
|
|
3532
|
+
const consumed = outcome === "fired" || outcome === "blocked";
|
|
3533
|
+
if (consumed) {
|
|
3534
|
+
consumedPresses.set(signature(event), {
|
|
3535
|
+
prevented: event.defaultPrevented
|
|
3536
|
+
});
|
|
3537
|
+
} else {
|
|
3538
|
+
consumedPresses.delete(signature(event));
|
|
3539
|
+
}
|
|
3540
|
+
return consumed;
|
|
3541
|
+
}
|
|
3542
|
+
return {
|
|
3543
|
+
register(bindings, layerOptions) {
|
|
3544
|
+
const prepared = prepare(bindings);
|
|
3545
|
+
const layer = {
|
|
3546
|
+
options: layerOptions,
|
|
3547
|
+
bindings: prepared,
|
|
3548
|
+
sequence: nextSequence++,
|
|
3549
|
+
shape: layerShape(prepared, layerOptions)
|
|
3550
|
+
};
|
|
3551
|
+
warnOnPrefixConflicts(layer.bindings, layerOptions.name);
|
|
3552
|
+
layers.add(layer);
|
|
3553
|
+
changed();
|
|
3554
|
+
return {
|
|
3555
|
+
update(nextBindings, nextOptions) {
|
|
3556
|
+
layer.bindings = prepare(nextBindings);
|
|
3557
|
+
layer.options = nextOptions;
|
|
3558
|
+
warnOnPrefixConflicts(layer.bindings, nextOptions.name);
|
|
3559
|
+
const shape = layerShape(layer.bindings, nextOptions);
|
|
3560
|
+
const shapeChanged = shape !== layer.shape;
|
|
3561
|
+
layer.shape = shape;
|
|
3562
|
+
if (shapeChanged && pendingLayer === layer) abandonSequence();
|
|
3563
|
+
changed();
|
|
3564
|
+
},
|
|
3565
|
+
dispose() {
|
|
3566
|
+
layers.delete(layer);
|
|
3567
|
+
changed();
|
|
3568
|
+
if (pendingLayer === layer) abandonSequence();
|
|
3569
|
+
}
|
|
3570
|
+
};
|
|
3571
|
+
},
|
|
3572
|
+
handle,
|
|
3573
|
+
attach(target) {
|
|
3574
|
+
const listener = (event) => {
|
|
3575
|
+
try {
|
|
3576
|
+
if (handle(event)) event.stopPropagation();
|
|
3577
|
+
} catch (error) {
|
|
3578
|
+
event.stopPropagation();
|
|
3579
|
+
throw error;
|
|
3580
|
+
}
|
|
3581
|
+
};
|
|
3582
|
+
target.addEventListener("keydown", listener);
|
|
3583
|
+
return () => {
|
|
3584
|
+
target.removeEventListener("keydown", listener);
|
|
3585
|
+
abandonSequence();
|
|
3586
|
+
consumedPresses.clear();
|
|
3587
|
+
};
|
|
3588
|
+
},
|
|
3589
|
+
subscribe(onChange) {
|
|
3590
|
+
watchers.add(onChange);
|
|
3591
|
+
return () => {
|
|
3592
|
+
watchers.delete(onChange);
|
|
3593
|
+
};
|
|
3594
|
+
},
|
|
3595
|
+
activeBindings() {
|
|
3596
|
+
if (snapshot) return snapshot;
|
|
3597
|
+
snapshot = computeSnapshot();
|
|
3598
|
+
return snapshot;
|
|
3599
|
+
}
|
|
3600
|
+
};
|
|
3601
|
+
}
|
|
3602
|
+
var MODIFIER_KEYS = /* @__PURE__ */ new Set([
|
|
3603
|
+
"Control",
|
|
3604
|
+
"Meta",
|
|
3605
|
+
"Alt",
|
|
3606
|
+
"Shift",
|
|
3607
|
+
// A dedicated AltGraph key reports its own keydown before the character-producing one. Without
|
|
3608
|
+
// it here, pressing AltGraph mid-sequence abandons the sequence before the character that would
|
|
3609
|
+
// have completed it ever arrives.
|
|
3610
|
+
"AltGraph"
|
|
3611
|
+
]);
|
|
3612
|
+
var TYPE_AHEAD_ROLES = /* @__PURE__ */ new Set([
|
|
3613
|
+
"textbox",
|
|
3614
|
+
"combobox",
|
|
3615
|
+
"listbox",
|
|
3616
|
+
// The focused element inside an open listbox is the OPTION, and it is what the event reports;
|
|
3617
|
+
// the listbox itself is only its ancestor.
|
|
3618
|
+
"option",
|
|
3619
|
+
"menu",
|
|
3620
|
+
"menuitem",
|
|
3621
|
+
"menuitemcheckbox",
|
|
3622
|
+
"menuitemradio"
|
|
3623
|
+
]);
|
|
3624
|
+
var RANGE_KEYS = /* @__PURE__ */ new Set(["Home", "End", "PageUp", "PageDown"]);
|
|
3625
|
+
var EDITING_LETTERS = /* @__PURE__ */ new Set(["a", "c", "v", "x", "z"]);
|
|
3626
|
+
var REDO_LETTER = "y";
|
|
3627
|
+
var EDITING_NAVIGATION = /* @__PURE__ */ new Set([
|
|
3628
|
+
"Insert",
|
|
3629
|
+
"ArrowLeft",
|
|
3630
|
+
"ArrowRight",
|
|
3631
|
+
"ArrowUp",
|
|
3632
|
+
"ArrowDown",
|
|
3633
|
+
"Home",
|
|
3634
|
+
"End",
|
|
3635
|
+
"Backspace",
|
|
3636
|
+
"Delete"
|
|
3637
|
+
]);
|
|
3638
|
+
var AMBIGUOUS_KEYS = /* @__PURE__ */ new Set(["Enter", "PageUp", "PageDown"]);
|
|
3639
|
+
function targetOwnsAmbiguousKey(event) {
|
|
3640
|
+
const element = asElement(eventTarget(event));
|
|
3641
|
+
if (element === null) return false;
|
|
3642
|
+
const multiline = element.tagName === "TEXTAREA" || element.isContentEditable;
|
|
3643
|
+
if (event.key === "Enter") return multiline;
|
|
3644
|
+
return multiline || element.tagName === "SELECT";
|
|
3645
|
+
}
|
|
3646
|
+
var FIELD_KEYS = /* @__PURE__ */ new Set([
|
|
3647
|
+
"Backspace",
|
|
3648
|
+
"Delete",
|
|
3649
|
+
"ArrowUp",
|
|
3650
|
+
"ArrowDown",
|
|
3651
|
+
"ArrowLeft",
|
|
3652
|
+
"ArrowRight",
|
|
3653
|
+
"Home",
|
|
3654
|
+
"End",
|
|
3655
|
+
// Shift+Insert pastes and carries no ctrl or meta, so it arrives here rather than at the
|
|
3656
|
+
// chord branch where Ctrl+Insert is recognised.
|
|
3657
|
+
"Insert",
|
|
3658
|
+
// Tab moves focus, and a blocking layer must NOT take it. The documented case for blocking is
|
|
3659
|
+
// a modal, whose focus trap only calls `preventDefault()` at the first and last tabbable
|
|
3660
|
+
// element — ordinary moves between the controls inside it rely on the browser default.
|
|
3661
|
+
// Suppressing every Tab therefore pinned focus to one control in exactly the situation
|
|
3662
|
+
// blocking exists to serve. A layer that genuinely wants Tab binds it.
|
|
3663
|
+
"Tab"
|
|
3664
|
+
]);
|
|
3665
|
+
|
|
3666
|
+
// src/lib/shortcuts/react.tsx
|
|
3667
|
+
import { jsx as jsx39 } from "react/jsx-runtime";
|
|
3668
|
+
var ShortcutContext = React13.createContext(null);
|
|
3669
|
+
var ownersByTarget = /* @__PURE__ */ new WeakMap();
|
|
3670
|
+
var useIsomorphicLayoutEffect = typeof document === "undefined" ? React13.useEffect : React13.useLayoutEffect;
|
|
3671
|
+
function optionsFingerprint(options) {
|
|
3672
|
+
return [
|
|
3673
|
+
options.isApple ?? "auto",
|
|
3674
|
+
options.sequenceTimeoutMs ?? "default",
|
|
3675
|
+
options.now ? "clock" : "no-clock"
|
|
3676
|
+
].join("\0");
|
|
3677
|
+
}
|
|
3678
|
+
function ShortcutProvider({
|
|
3679
|
+
children,
|
|
3680
|
+
target,
|
|
3681
|
+
...managerOptions
|
|
3682
|
+
}) {
|
|
3683
|
+
const parent = React13.useContext(ShortcutContext);
|
|
3684
|
+
const resolvedTarget = target === null ? null : target ?? (typeof document === "undefined" ? null : document);
|
|
3685
|
+
const nestedOnSameTarget = parent !== null && parent.target === resolvedTarget;
|
|
3686
|
+
const optionsRef = React13.useRef(managerOptions);
|
|
3687
|
+
const ownManagers = React13.useRef(/* @__PURE__ */ new WeakMap());
|
|
3688
|
+
const ownDetached = React13.useRef(null);
|
|
3689
|
+
const detached = React13.useMemo(() => {
|
|
3690
|
+
if (resolvedTarget === null) {
|
|
3691
|
+
ownDetached.current ??= createShortcutManager(optionsRef.current);
|
|
3692
|
+
return ownDetached.current;
|
|
3693
|
+
}
|
|
3694
|
+
const existing = ownManagers.current.get(resolvedTarget);
|
|
3695
|
+
if (existing) return existing;
|
|
3696
|
+
const created = createShortcutManager(optionsRef.current);
|
|
3697
|
+
ownManagers.current.set(resolvedTarget, created);
|
|
3698
|
+
return created;
|
|
3699
|
+
}, [resolvedTarget]);
|
|
3700
|
+
let owner = resolvedTarget === null ? null : ownersByTarget.get(resolvedTarget);
|
|
3701
|
+
const fingerprint = optionsFingerprint(optionsRef.current);
|
|
3702
|
+
if (resolvedTarget !== null && (!owner || owner.retired)) {
|
|
3703
|
+
owner = {
|
|
3704
|
+
manager: detached,
|
|
3705
|
+
providers: 0,
|
|
3706
|
+
retired: false,
|
|
3707
|
+
options: fingerprint
|
|
3708
|
+
};
|
|
3709
|
+
ownersByTarget.set(resolvedTarget, owner);
|
|
3710
|
+
}
|
|
3711
|
+
const adoptedDiffers = Boolean(owner) && owner?.options !== fingerprint || nestedOnSameTarget && parent !== null && parent.options !== fingerprint;
|
|
3712
|
+
devWarnOnce(
|
|
3713
|
+
!adoptedDiffers,
|
|
3714
|
+
"ShortcutProvider: another provider is already listening on this target with different options, so the ones passed here are being ignored. Managers are shared per target; give the providers matching options, or a target of their own."
|
|
3715
|
+
);
|
|
3716
|
+
const manager = nestedOnSameTarget && parent ? parent.manager : owner ? owner.manager : detached;
|
|
3717
|
+
useIsomorphicLayoutEffect(() => {
|
|
3718
|
+
if (resolvedTarget === null) return;
|
|
3719
|
+
let entry = ownersByTarget.get(resolvedTarget);
|
|
3720
|
+
if (!entry) {
|
|
3721
|
+
entry = {
|
|
3722
|
+
manager,
|
|
3723
|
+
providers: 0,
|
|
3724
|
+
retired: false,
|
|
3725
|
+
options: optionsFingerprint(optionsRef.current)
|
|
3726
|
+
};
|
|
3727
|
+
ownersByTarget.set(resolvedTarget, entry);
|
|
3728
|
+
}
|
|
3729
|
+
const owned = entry;
|
|
3730
|
+
owned.providers += 1;
|
|
3731
|
+
owned.retired = false;
|
|
3732
|
+
if (owned.providers === 1) {
|
|
3733
|
+
owned.detach = owned.manager.attach(resolvedTarget);
|
|
3734
|
+
}
|
|
3735
|
+
return () => {
|
|
3736
|
+
owned.providers -= 1;
|
|
3737
|
+
if (owned.providers === 0) {
|
|
3738
|
+
owned.detach?.();
|
|
3739
|
+
owned.detach = void 0;
|
|
3740
|
+
owned.retired = true;
|
|
3741
|
+
}
|
|
3742
|
+
};
|
|
3743
|
+
}, [resolvedTarget, manager]);
|
|
3744
|
+
const depth = nestedOnSameTarget && parent ? parent.depth : 0;
|
|
3745
|
+
const value = React13.useMemo(
|
|
3746
|
+
() => ({ manager, depth, target: resolvedTarget, options: fingerprint }),
|
|
3747
|
+
[manager, depth, resolvedTarget, fingerprint]
|
|
3748
|
+
);
|
|
3749
|
+
return /* @__PURE__ */ jsx39(ShortcutContext.Provider, { value, children });
|
|
3750
|
+
}
|
|
3751
|
+
function ShortcutScope({
|
|
3752
|
+
children
|
|
3753
|
+
}) {
|
|
3754
|
+
const parent = React13.useContext(ShortcutContext);
|
|
3755
|
+
if (!parent) {
|
|
3756
|
+
throw new Error("ShortcutScope must be rendered inside a ShortcutProvider");
|
|
3757
|
+
}
|
|
3758
|
+
const value = React13.useMemo(
|
|
3759
|
+
() => ({
|
|
3760
|
+
manager: parent.manager,
|
|
3761
|
+
depth: parent.depth + 1,
|
|
3762
|
+
target: parent.target,
|
|
3763
|
+
// Inherited: a scope raises precedence, it does not build a manager, so the options in force
|
|
3764
|
+
// are still the ones the provider above it used.
|
|
3765
|
+
options: parent.options
|
|
3766
|
+
}),
|
|
3767
|
+
[parent.manager, parent.depth, parent.target, parent.options]
|
|
3768
|
+
);
|
|
3769
|
+
return /* @__PURE__ */ jsx39(ShortcutContext.Provider, { value, children });
|
|
3770
|
+
}
|
|
3771
|
+
function useShortcuts(bindings, options) {
|
|
3772
|
+
const context = React13.useContext(ShortcutContext);
|
|
3773
|
+
if (!context) {
|
|
3774
|
+
throw new Error("useShortcuts must be called inside a ShortcutProvider");
|
|
3775
|
+
}
|
|
3776
|
+
const { manager, depth } = context;
|
|
3777
|
+
const registration = React13.useRef(null);
|
|
3778
|
+
const latest = React13.useRef({ bindings, options });
|
|
3779
|
+
useIsomorphicLayoutEffect(() => {
|
|
3780
|
+
registration.current = manager.register([], {
|
|
3781
|
+
name: latest.current.options.name,
|
|
3782
|
+
depth
|
|
3783
|
+
});
|
|
3784
|
+
return () => {
|
|
3785
|
+
registration.current?.dispose();
|
|
3786
|
+
registration.current = null;
|
|
3787
|
+
};
|
|
3788
|
+
}, [manager, depth]);
|
|
3789
|
+
useIsomorphicLayoutEffect(() => {
|
|
3790
|
+
latest.current = { bindings, options };
|
|
3791
|
+
registration.current?.update(bindings, {
|
|
3792
|
+
name: options.name,
|
|
3793
|
+
depth,
|
|
3794
|
+
enabled: options.enabled,
|
|
3795
|
+
blocking: options.blocking
|
|
3796
|
+
});
|
|
3797
|
+
});
|
|
3798
|
+
}
|
|
3799
|
+
function useShortcutManager() {
|
|
3800
|
+
const context = React13.useContext(ShortcutContext);
|
|
3801
|
+
if (!context) {
|
|
3802
|
+
throw new Error(
|
|
3803
|
+
"useShortcutManager must be called inside a ShortcutProvider"
|
|
3804
|
+
);
|
|
3805
|
+
}
|
|
3806
|
+
return context.manager;
|
|
3807
|
+
}
|
|
3808
|
+
function useActiveShortcuts() {
|
|
3809
|
+
const manager = useShortcutManager();
|
|
3810
|
+
return React13.useSyncExternalStore(
|
|
3811
|
+
manager.subscribe,
|
|
3812
|
+
manager.activeBindings,
|
|
3813
|
+
manager.activeBindings
|
|
3814
|
+
);
|
|
3815
|
+
}
|
|
2639
3816
|
export {
|
|
2640
3817
|
Accordion,
|
|
2641
3818
|
AccordionContent,
|
|
@@ -2671,6 +3848,7 @@ export {
|
|
|
2671
3848
|
Collapsible,
|
|
2672
3849
|
CollapsibleContent2 as CollapsibleContent,
|
|
2673
3850
|
CollapsibleTrigger2 as CollapsibleTrigger,
|
|
3851
|
+
ColorPicker,
|
|
2674
3852
|
Command,
|
|
2675
3853
|
CommandDialog,
|
|
2676
3854
|
CommandEmpty,
|
|
@@ -2755,7 +3933,10 @@ export {
|
|
|
2755
3933
|
SheetPortal,
|
|
2756
3934
|
SheetTitle,
|
|
2757
3935
|
SheetTrigger,
|
|
3936
|
+
ShortcutProvider,
|
|
3937
|
+
ShortcutScope,
|
|
2758
3938
|
Skeleton,
|
|
3939
|
+
Slider,
|
|
2759
3940
|
Spinner,
|
|
2760
3941
|
Stack,
|
|
2761
3942
|
Stat,
|
|
@@ -2789,13 +3970,18 @@ export {
|
|
|
2789
3970
|
badgeVariants,
|
|
2790
3971
|
buttonVariants,
|
|
2791
3972
|
cardVariants,
|
|
3973
|
+
createShortcutManager,
|
|
2792
3974
|
dialogContentVariants,
|
|
2793
3975
|
inputVariants,
|
|
3976
|
+
parseKeys,
|
|
2794
3977
|
progressVariants,
|
|
2795
3978
|
selectTriggerVariants,
|
|
2796
3979
|
sheetVariants,
|
|
2797
3980
|
spinnerVariants,
|
|
2798
3981
|
toast,
|
|
2799
|
-
|
|
3982
|
+
useActiveShortcuts,
|
|
3983
|
+
usePortalContainer,
|
|
3984
|
+
useShortcutManager,
|
|
3985
|
+
useShortcuts
|
|
2800
3986
|
};
|
|
2801
3987
|
//# sourceMappingURL=index.mjs.map
|