@reopt-ai/opt-ui 1.7.0 → 1.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/COMPONENT_CATALOG.md +222 -34
- package/README.md +70 -6
- package/dist/core/index.cjs +170 -155
- package/dist/core/index.d.cts +3 -2
- package/dist/core/index.d.ts +3 -2
- package/dist/core/index.js +3 -3
- package/dist/docs/02-components/01-core.md +223 -18
- package/dist/docs/02-components/02-visuals.md +1 -1
- package/dist/docs/02-components/03-shells.md +17 -16
- package/dist/docs/02-components/04-surfaces.md +1 -1
- package/dist/docs/02-components/index.md +4 -4
- package/dist/{field-sidebar-CB8vjvUM.d.ts → field-sidebar-B5vk3luQ.d.cts} +30 -3
- package/dist/{field-sidebar-BTZCBviA.d.cts.map → field-sidebar-B5vk3luQ.d.cts.map} +1 -1
- package/dist/{field-sidebar-Cs-J-5VE.js → field-sidebar-B7QkWvMG.js} +52 -17
- package/dist/{field-sidebar-Cs-J-5VE.js.map → field-sidebar-B7QkWvMG.js.map} +1 -1
- package/dist/{field-sidebar-BTZCBviA.d.cts → field-sidebar-Dk9gJGA_.d.ts} +30 -3
- package/dist/{field-sidebar-CB8vjvUM.d.ts.map → field-sidebar-Dk9gJGA_.d.ts.map} +1 -1
- package/dist/{field-sidebar-CzIQX70-.cjs → field-sidebar-mFHemxBz.cjs} +328 -293
- package/dist/{types-D0FlcYnM.d.ts → field-token-CSOrU5gl.d.cts} +66 -2
- package/dist/field-token-CSOrU5gl.d.cts.map +1 -0
- package/dist/{types-D0FlcYnM.d.cts → field-token-CSOrU5gl.d.ts} +66 -2
- package/dist/field-token-CSOrU5gl.d.ts.map +1 -0
- package/dist/{pagination-DWbvACuN.js → highlight-C2sfnJN8.js} +263 -11
- package/dist/highlight-C2sfnJN8.js.map +1 -0
- package/dist/{pagination-DjfxBcjX.cjs → highlight-CGcrYGMC.cjs} +284 -8
- package/dist/id-registry.cjs +98 -2
- package/dist/id-registry.d.cts.map +1 -1
- package/dist/id-registry.d.ts.map +1 -1
- package/dist/id-registry.js +98 -2
- package/dist/id-registry.js.map +1 -1
- package/dist/id-registry.json +202 -4
- package/dist/index.cjs +664 -643
- package/dist/index.d.cts +5 -4
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.ts +5 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +10 -4
- package/dist/index.js.map +1 -1
- package/dist/{shader-surface-BUPZ0P-n.js → key-pad-menu-BFd79JoS.js} +855 -19
- package/dist/key-pad-menu-BFd79JoS.js.map +1 -0
- package/dist/{shader-surface-DRrcLrWf.d.cts → key-pad-menu-D8OS_VCT.d.cts} +459 -3
- package/dist/key-pad-menu-D8OS_VCT.d.cts.map +1 -0
- package/dist/{shader-surface-Fav5K9UC.cjs → key-pad-menu-DoGskWbp.cjs} +1103 -201
- package/dist/{shader-surface-BfTP9c1Q.d.ts → key-pad-menu-EEb6Ho5h.d.ts} +459 -3
- package/dist/key-pad-menu-EEb6Ho5h.d.ts.map +1 -0
- package/dist/meta.cjs +641 -0
- package/dist/meta.d.cts.map +1 -1
- package/dist/meta.d.ts.map +1 -1
- package/dist/meta.js +641 -0
- package/dist/meta.js.map +1 -1
- package/dist/shells/index.cjs +1 -1
- package/dist/shells/index.d.cts +1 -1
- package/dist/shells/index.d.ts +1 -1
- package/dist/shells/index.js +1 -1
- package/package.json +2 -2
- package/dist/pagination-DWbvACuN.js.map +0 -1
- package/dist/shader-surface-BUPZ0P-n.js.map +0 -1
- package/dist/shader-surface-BfTP9c1Q.d.ts.map +0 -1
- package/dist/shader-surface-DRrcLrWf.d.cts.map +0 -1
- package/dist/types-D0FlcYnM.d.cts.map +0 -1
- package/dist/types-D0FlcYnM.d.ts.map +0 -1
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
"use client";
|
|
2
|
-
import { $t as
|
|
2
|
+
import { $t as Spinner, An as OPT_INPUT, Bn as OPT_TEXT_PRIMARY, Dn as OPT_FOCUS, Ft as SelectRoot$1, Gn as _objectWithoutProperties, Hn as OPT_TEXT_TERTIARY, It as SelectTrigger$1, Mt as SelectItem$1, Nn as OPT_SURFACE, On as OPT_FOCUS_VISIBLE, Pt as SelectPopover$1, Rn as OPT_TEXTAREA_SIZE, Vn as OPT_TEXT_SECONDARY, Wn as _objectSpread2, Zt as useInputId, cn as FieldMessages, dt as MenuSeparator$1, fn as OPT_ANIMATE_DRAWER, ft as MenuTrigger$1, ln as RequiredMark, lt as MenuPopover$1, nn as Button, ot as MenuItem$1, pn as OPT_ANIMATE_ENTER_EXIT, qn as cn, rn as koBreadcrumbLabels, sn as useControlledState, un as getDescribedBy, ut as MenuRoot$1, yn as OPT_BORDER } from "./highlight-C2sfnJN8.js";
|
|
3
3
|
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
4
4
|
import * as React from "react";
|
|
5
5
|
import { createContext, createElement, forwardRef, useCallback, useContext, useEffect, useId, useImperativeHandle, useLayoutEffect, useMemo, useRef, useState } from "react";
|
|
6
6
|
import { AccordionContent, AccordionItem, AccordionRoot, AccordionTrigger, DialogDismiss, DialogPanel, DialogRoot, Meter, RadioGroupRoot, Separator, Toggle, ToggleGroup, useDialogClose, useRadioGroupContext } from "@reopt-ai/opt-ui-primitives";
|
|
7
|
-
import { ChevronDown, GripVertical } from "lucide-react";
|
|
7
|
+
import { Check, ChevronDown, GripVertical, Pencil, X } from "lucide-react";
|
|
8
8
|
import { Group, Panel, Separator as Separator$1 } from "react-resizable-panels";
|
|
9
9
|
import { Mesh, Program, Renderer, Triangle } from "ogl";
|
|
10
10
|
import { hexToSrgb } from "@reopt-ai/opt-palette";
|
|
@@ -17,7 +17,7 @@ const _excluded$17 = [
|
|
|
17
17
|
"className",
|
|
18
18
|
"children"
|
|
19
19
|
];
|
|
20
|
-
const toneStyles = {
|
|
20
|
+
const toneStyles$1 = {
|
|
21
21
|
primary: "text-text-primary",
|
|
22
22
|
secondary: "text-text-secondary",
|
|
23
23
|
tertiary: "text-text-tertiary",
|
|
@@ -28,7 +28,7 @@ function Text(_ref) {
|
|
|
28
28
|
let { as, variant = "body", tone = "primary", truncate = false, className, children } = _ref, props = _objectWithoutProperties(_ref, _excluded$17);
|
|
29
29
|
const Component = as !== null && as !== void 0 ? as : "span";
|
|
30
30
|
const elementProps = _objectSpread2(_objectSpread2({}, props), {}, {
|
|
31
|
-
className: cn(`opt-type-${variant}`, toneStyles[tone], truncate && "truncate", className),
|
|
31
|
+
className: cn(`opt-type-${variant}`, toneStyles$1[tone], truncate && "truncate", className),
|
|
32
32
|
"data-opt-component": "text",
|
|
33
33
|
"data-opt-part": "root",
|
|
34
34
|
"data-opt-id": "NFKP2",
|
|
@@ -277,7 +277,7 @@ const _excluded2$8 = [
|
|
|
277
277
|
"children",
|
|
278
278
|
"orientation"
|
|
279
279
|
];
|
|
280
|
-
const sizeStyles$
|
|
280
|
+
const sizeStyles$8 = {
|
|
281
281
|
sm: "h-8 min-w-8 px-2 text-xs gap-1.5",
|
|
282
282
|
md: "h-10 min-w-10 px-3 text-sm gap-2"
|
|
283
283
|
};
|
|
@@ -285,7 +285,7 @@ const toggleBase = cn("inline-flex cursor-pointer items-center justify-center ro
|
|
|
285
285
|
/** A two-state (pressed) button. Use standalone or inside `ToggleGroup`. */
|
|
286
286
|
function Toggle$1(_ref) {
|
|
287
287
|
let { size = "md", className, children } = _ref, props = _objectWithoutProperties(_ref, _excluded$15);
|
|
288
|
-
return /* @__PURE__ */ jsx(Toggle, _objectSpread2(_objectSpread2({ className: cn(toggleBase, sizeStyles$
|
|
288
|
+
return /* @__PURE__ */ jsx(Toggle, _objectSpread2(_objectSpread2({ className: cn(toggleBase, sizeStyles$8[size], className) }, props), {}, {
|
|
289
289
|
"data-opt-id": "GFCMY",
|
|
290
290
|
"data-opt-slug": "toggle.Toggle",
|
|
291
291
|
children
|
|
@@ -559,7 +559,7 @@ const _excluded$12 = [
|
|
|
559
559
|
"aria-label",
|
|
560
560
|
"aria-labelledby"
|
|
561
561
|
];
|
|
562
|
-
const sizeStyles$
|
|
562
|
+
const sizeStyles$7 = {
|
|
563
563
|
sm: {
|
|
564
564
|
track: "h-5 w-9",
|
|
565
565
|
thumb: "h-4 w-4",
|
|
@@ -583,7 +583,7 @@ const Switch = forwardRef((_ref, ref) => {
|
|
|
583
583
|
const switchId = (_useInputId = useInputId(id, label, "switch")) !== null && _useInputId !== void 0 ? _useInputId : `switch-${fallbackId}`;
|
|
584
584
|
const hintText = hint !== null && hint !== void 0 ? hint : description;
|
|
585
585
|
const hasError = Boolean(error);
|
|
586
|
-
const styles = sizeStyles$
|
|
586
|
+
const styles = sizeStyles$7[size];
|
|
587
587
|
const labelId = label ? `${switchId}-label` : void 0;
|
|
588
588
|
const switchLabelledBy = ariaLabelledBy !== null && ariaLabelledBy !== void 0 ? ariaLabelledBy : ariaLabel ? void 0 : labelId;
|
|
589
589
|
const switchAriaLabel = ariaLabel !== null && ariaLabel !== void 0 ? ariaLabel : switchLabelledBy ? void 0 : "Switch";
|
|
@@ -889,7 +889,7 @@ function useImageLoadingStatus(src, { srcSet, sizes, crossOrigin, referrerPolicy
|
|
|
889
889
|
]);
|
|
890
890
|
return status;
|
|
891
891
|
}
|
|
892
|
-
const sizeStyles$
|
|
892
|
+
const sizeStyles$6 = {
|
|
893
893
|
xs: "h-6 w-6 text-[10px]",
|
|
894
894
|
sm: "h-8 w-8 text-xs",
|
|
895
895
|
md: "h-10 w-10 text-sm",
|
|
@@ -973,7 +973,7 @@ function Avatar(_ref) {
|
|
|
973
973
|
return /* @__PURE__ */ jsxs("span", _objectSpread2(_objectSpread2({
|
|
974
974
|
role: "img",
|
|
975
975
|
"aria-label": avatarLabel,
|
|
976
|
-
className: cn("relative inline-flex shrink-0 items-center justify-center overflow-hidden rounded-full", sizeStyles$
|
|
976
|
+
className: cn("relative inline-flex shrink-0 items-center justify-center overflow-hidden rounded-full", sizeStyles$6[size], !showImage && bgColor, className)
|
|
977
977
|
}, props), {}, {
|
|
978
978
|
"data-opt-id": "SCMM0",
|
|
979
979
|
"data-opt-slug": "avatar.Avatar",
|
|
@@ -996,7 +996,7 @@ function AvatarGroup(_ref5) {
|
|
|
996
996
|
className: "ring-surface relative -ml-2 inline-block rounded-full ring-2 first:ml-0",
|
|
997
997
|
children: child
|
|
998
998
|
}, index)), remainingCount > 0 && /* @__PURE__ */ jsxs("span", {
|
|
999
|
-
className: cn("bg-bg-subtle text-text-secondary ring-surface relative inline-flex items-center justify-center rounded-full font-medium ring-2", sizeStyles$
|
|
999
|
+
className: cn("bg-bg-subtle text-text-secondary ring-surface relative inline-flex items-center justify-center rounded-full font-medium ring-2", sizeStyles$6[size]),
|
|
1000
1000
|
children: ["+", remainingCount]
|
|
1001
1001
|
})]
|
|
1002
1002
|
}));
|
|
@@ -1025,7 +1025,7 @@ const _excluded2$3 = [
|
|
|
1025
1025
|
"indeterminate",
|
|
1026
1026
|
"className"
|
|
1027
1027
|
];
|
|
1028
|
-
const sizeStyles$
|
|
1028
|
+
const sizeStyles$5 = {
|
|
1029
1029
|
sm: "h-1",
|
|
1030
1030
|
md: "h-2",
|
|
1031
1031
|
lg: "h-3"
|
|
@@ -1065,7 +1065,7 @@ function Progress(_ref) {
|
|
|
1065
1065
|
children: progress.valueText
|
|
1066
1066
|
})]
|
|
1067
1067
|
}), /* @__PURE__ */ jsx("div", {
|
|
1068
|
-
className: cn("bg-bg-subtle w-full overflow-hidden rounded-full", sizeStyles$
|
|
1068
|
+
className: cn("bg-bg-subtle w-full overflow-hidden rounded-full", sizeStyles$5[size]),
|
|
1069
1069
|
role: "progressbar",
|
|
1070
1070
|
"aria-valuenow": progress === null || progress === void 0 ? void 0 : progress.clampedValue,
|
|
1071
1071
|
"aria-valuemin": progress ? 0 : void 0,
|
|
@@ -1158,7 +1158,7 @@ const _excluded$7 = [
|
|
|
1158
1158
|
"getValueLabel",
|
|
1159
1159
|
"className"
|
|
1160
1160
|
];
|
|
1161
|
-
const sizeStyles$
|
|
1161
|
+
const sizeStyles$4 = {
|
|
1162
1162
|
sm: "h-1",
|
|
1163
1163
|
md: "h-2",
|
|
1164
1164
|
lg: "h-3"
|
|
@@ -1196,7 +1196,7 @@ function Meter$1(_ref) {
|
|
|
1196
1196
|
max,
|
|
1197
1197
|
getValueLabel,
|
|
1198
1198
|
"aria-label": label !== null && label !== void 0 ? label : `${Math.round(percentage)}%`,
|
|
1199
|
-
className: cn("bg-bg-subtle w-full overflow-hidden rounded-full", sizeStyles$
|
|
1199
|
+
className: cn("bg-bg-subtle w-full overflow-hidden rounded-full", sizeStyles$4[size]),
|
|
1200
1200
|
children: /* @__PURE__ */ jsx("div", {
|
|
1201
1201
|
className: cn("h-full rounded-full transition-all duration-300 ease-out", variantStyles[variant]),
|
|
1202
1202
|
style: { width: `${percentage}%` }
|
|
@@ -1793,7 +1793,7 @@ const _excluded$5 = [
|
|
|
1793
1793
|
"disabled",
|
|
1794
1794
|
"className"
|
|
1795
1795
|
];
|
|
1796
|
-
const sizeStyles = {
|
|
1796
|
+
const sizeStyles$3 = {
|
|
1797
1797
|
sm: {
|
|
1798
1798
|
radio: "h-4 w-4",
|
|
1799
1799
|
dot: "h-2 w-2",
|
|
@@ -1861,7 +1861,7 @@ const Radio = forwardRef((_ref, ref) => {
|
|
|
1861
1861
|
const size = sizeProp !== null && sizeProp !== void 0 ? sizeProp : groupContext.size;
|
|
1862
1862
|
const disabled = (_ref2 = disabledProp !== null && disabledProp !== void 0 ? disabledProp : groupContext.disabled) !== null && _ref2 !== void 0 ? _ref2 : primContext === null || primContext === void 0 ? void 0 : primContext.disabled;
|
|
1863
1863
|
const hintText = hint !== null && hint !== void 0 ? hint : description;
|
|
1864
|
-
const styles = sizeStyles[size];
|
|
1864
|
+
const styles = sizeStyles$3[size];
|
|
1865
1865
|
const isChecked = (primContext === null || primContext === void 0 ? void 0 : primContext.value) === value;
|
|
1866
1866
|
const radioId = `radio-${useId().replace(/:/g, "")}`;
|
|
1867
1867
|
const labelId = label ? `${radioId}-label` : void 0;
|
|
@@ -3649,6 +3649,842 @@ const ShaderSurfaceComponent = forwardRef(function ShaderSurface({ preset, color
|
|
|
3649
3649
|
ShaderSurfaceComponent.displayName = "ShaderSurface";
|
|
3650
3650
|
const ShaderSurface = ShaderSurfaceComponent;
|
|
3651
3651
|
//#endregion
|
|
3652
|
-
|
|
3652
|
+
//#region src/core/facet.tsx
|
|
3653
|
+
const defaultLabels$1 = {
|
|
3654
|
+
empty: "No values in this range.",
|
|
3655
|
+
loading: "Counting…",
|
|
3656
|
+
showMore: (remaining) => `Show ${remaining} more`,
|
|
3657
|
+
showLess: "Show fewer",
|
|
3658
|
+
selectHint: (label, count, share) => `${label}, ${count} (${share})`
|
|
3659
|
+
};
|
|
3660
|
+
function defaultCountFormat(value) {
|
|
3661
|
+
return new Intl.NumberFormat(void 0, { notation: "compact" }).format(value);
|
|
3662
|
+
}
|
|
3663
|
+
function formatShare(share) {
|
|
3664
|
+
const percent = share * 100;
|
|
3665
|
+
if (percent > 0 && percent < .1) return "<0.1%";
|
|
3666
|
+
return `${percent >= 10 ? Math.round(percent) : percent.toFixed(1)}%`;
|
|
3667
|
+
}
|
|
3668
|
+
/**
|
|
3669
|
+
* Facet — one value of a field, its count, and its share of the whole.
|
|
3670
|
+
*
|
|
3671
|
+
* The row is the filter: reading it and acting on it are the same gesture,
|
|
3672
|
+
* which is what separates a facet from a legend.
|
|
3673
|
+
*/
|
|
3674
|
+
function Facet({ item, selected = false, onSelect, showShare = true, share, countFormatter = defaultCountFormat, layout = "vertical", labels, className }) {
|
|
3675
|
+
var _ref;
|
|
3676
|
+
const text = _objectSpread2(_objectSpread2({}, defaultLabels$1), labels);
|
|
3677
|
+
const resolvedShare = (_ref = share !== null && share !== void 0 ? share : item.share) !== null && _ref !== void 0 ? _ref : 0;
|
|
3678
|
+
const interactive = onSelect !== void 0 && !item.disabled;
|
|
3679
|
+
const countText = item.count === void 0 ? "" : countFormatter(item.count);
|
|
3680
|
+
const shareText = formatShare(resolvedShare);
|
|
3681
|
+
const content = /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
3682
|
+
showShare && layout === "vertical" && /* @__PURE__ */ jsx("span", {
|
|
3683
|
+
"aria-hidden": "true",
|
|
3684
|
+
className: cn("absolute inset-y-0 left-0 rounded-[inherit] transition-[width] duration-300", selected ? "bg-accent/25" : "bg-accent/10"),
|
|
3685
|
+
style: {
|
|
3686
|
+
width: `${Math.max(resolvedShare * 100, resolvedShare > 0 ? 1.5 : 0)}%`,
|
|
3687
|
+
backgroundColor: item.color ? `color-mix(in oklab, ${item.color} ${selected ? 30 : 16}%, transparent)` : void 0
|
|
3688
|
+
}
|
|
3689
|
+
}),
|
|
3690
|
+
/* @__PURE__ */ jsxs("span", {
|
|
3691
|
+
className: "gap-element relative flex min-w-0 flex-1 items-center",
|
|
3692
|
+
children: [
|
|
3693
|
+
item.color && !item.icon && /* @__PURE__ */ jsx("span", {
|
|
3694
|
+
"aria-hidden": "true",
|
|
3695
|
+
className: "size-2 shrink-0 rounded-full",
|
|
3696
|
+
style: { backgroundColor: item.color }
|
|
3697
|
+
}),
|
|
3698
|
+
item.icon && /* @__PURE__ */ jsx("span", {
|
|
3699
|
+
"aria-hidden": "true",
|
|
3700
|
+
className: "shrink-0",
|
|
3701
|
+
children: item.icon
|
|
3702
|
+
}),
|
|
3703
|
+
/* @__PURE__ */ jsx("span", {
|
|
3704
|
+
className: "truncate",
|
|
3705
|
+
children: item.label
|
|
3706
|
+
})
|
|
3707
|
+
]
|
|
3708
|
+
}),
|
|
3709
|
+
item.count !== void 0 && /* @__PURE__ */ jsx("span", {
|
|
3710
|
+
className: "text-text-tertiary relative shrink-0 tabular-nums",
|
|
3711
|
+
children: countText
|
|
3712
|
+
}),
|
|
3713
|
+
showShare && item.count !== void 0 && /* @__PURE__ */ jsx("span", {
|
|
3714
|
+
className: "text-text-tertiary relative w-11 shrink-0 text-right text-xs tabular-nums",
|
|
3715
|
+
children: shareText
|
|
3716
|
+
})
|
|
3717
|
+
] });
|
|
3718
|
+
const shellClass = cn("relative flex w-full items-center gap-element overflow-hidden rounded-md px-2 py-1.5 text-left text-sm", layout === "horizontal" && "w-auto", selected ? "text-text-primary font-medium" : "text-text-secondary", interactive && "hover:bg-accent-subtle hover:text-text-primary cursor-pointer transition-colors", item.disabled && "cursor-not-allowed opacity-50", className);
|
|
3719
|
+
if (!interactive) return /* @__PURE__ */ jsx("div", {
|
|
3720
|
+
className: shellClass,
|
|
3721
|
+
"data-selected": selected || void 0,
|
|
3722
|
+
"data-opt-id": "E9H18",
|
|
3723
|
+
"data-opt-slug": "facet.Facet",
|
|
3724
|
+
children: content
|
|
3725
|
+
});
|
|
3726
|
+
return /* @__PURE__ */ jsx("button", {
|
|
3727
|
+
type: "button",
|
|
3728
|
+
className: cn(shellClass, OPT_FOCUS_VISIBLE),
|
|
3729
|
+
"aria-pressed": selected,
|
|
3730
|
+
"aria-label": text.selectHint(typeof item.label === "string" ? item.label : item.id, countText, shareText),
|
|
3731
|
+
disabled: item.disabled,
|
|
3732
|
+
onClick: () => onSelect(item.id),
|
|
3733
|
+
"data-selected": selected || void 0,
|
|
3734
|
+
"data-opt-id": "E9H18",
|
|
3735
|
+
"data-opt-slug": "facet.Facet",
|
|
3736
|
+
children: content
|
|
3737
|
+
});
|
|
3738
|
+
}
|
|
3739
|
+
/**
|
|
3740
|
+
* FacetGroup — the distribution of one field, as a list you can filter with.
|
|
3741
|
+
*
|
|
3742
|
+
* Counts alone do not say whether 4,000 is most of the data or a rounding
|
|
3743
|
+
* error, so every row carries its share of the whole as a bar behind the
|
|
3744
|
+
* label. The tail collapses by default: a high-cardinality field would
|
|
3745
|
+
* otherwise push everything else off the screen.
|
|
3746
|
+
*
|
|
3747
|
+
* @example
|
|
3748
|
+
* ```tsx
|
|
3749
|
+
* <FacetGroup
|
|
3750
|
+
* items={topValues}
|
|
3751
|
+
* selected={filters}
|
|
3752
|
+
* onSelectedChange={setFilters}
|
|
3753
|
+
* total={sampledRows}
|
|
3754
|
+
* max={5}
|
|
3755
|
+
* />
|
|
3756
|
+
* ```
|
|
3757
|
+
*/
|
|
3758
|
+
function FacetGroup({ items, selected, onSelectedChange, selection = "multiple", layout = "vertical", showShare = true, countFormatter = defaultCountFormat, max, total, loading = false, emptyState, labels, className, "aria-label": ariaLabel }) {
|
|
3759
|
+
const text = _objectSpread2(_objectSpread2({}, defaultLabels$1), labels);
|
|
3760
|
+
const [uncontrolled, setUncontrolled] = useState([]);
|
|
3761
|
+
const [expanded, setExpanded] = useState(false);
|
|
3762
|
+
const current = selected !== null && selected !== void 0 ? selected : uncontrolled;
|
|
3763
|
+
const denominator = useMemo(() => {
|
|
3764
|
+
if (total !== void 0 && total > 0) return total;
|
|
3765
|
+
const sum = items.reduce((acc, item) => {
|
|
3766
|
+
var _item$count;
|
|
3767
|
+
return acc + ((_item$count = item.count) !== null && _item$count !== void 0 ? _item$count : 0);
|
|
3768
|
+
}, 0);
|
|
3769
|
+
return sum > 0 ? sum : 1;
|
|
3770
|
+
}, [items, total]);
|
|
3771
|
+
const visible = max === void 0 || expanded ? items : items.slice(0, Math.max(max, 1));
|
|
3772
|
+
const remaining = items.length - visible.length;
|
|
3773
|
+
const handleSelect = (id) => {
|
|
3774
|
+
if (selection === "none") return;
|
|
3775
|
+
const next = selection === "single" ? current.includes(id) ? [] : [id] : current.includes(id) ? current.filter((value) => value !== id) : [...current, id];
|
|
3776
|
+
if (selected === void 0) setUncontrolled(next);
|
|
3777
|
+
onSelectedChange === null || onSelectedChange === void 0 || onSelectedChange(next);
|
|
3778
|
+
};
|
|
3779
|
+
if (loading) return /* @__PURE__ */ jsxs("div", {
|
|
3780
|
+
className: cn("flex flex-col gap-element", className),
|
|
3781
|
+
"aria-busy": "true",
|
|
3782
|
+
"aria-label": ariaLabel,
|
|
3783
|
+
"data-opt-id": "G1RNN",
|
|
3784
|
+
"data-opt-slug": "facet.FacetGroup",
|
|
3785
|
+
children: [/* @__PURE__ */ jsx("span", {
|
|
3786
|
+
className: "sr-only",
|
|
3787
|
+
children: text.loading
|
|
3788
|
+
}), Array.from({ length: Math.min(max !== null && max !== void 0 ? max : 4, 6) }, (_, index) => /* @__PURE__ */ jsx("span", {
|
|
3789
|
+
"aria-hidden": "true",
|
|
3790
|
+
className: "bg-bg-subtle h-7 animate-pulse rounded-md"
|
|
3791
|
+
}, index))]
|
|
3792
|
+
});
|
|
3793
|
+
if (items.length === 0) return /* @__PURE__ */ jsx("div", {
|
|
3794
|
+
className: cn("text-text-tertiary px-2 py-1.5 text-sm", className),
|
|
3795
|
+
"aria-label": ariaLabel,
|
|
3796
|
+
"data-opt-id": "G1RNN",
|
|
3797
|
+
"data-opt-slug": "facet.FacetGroup",
|
|
3798
|
+
children: emptyState !== null && emptyState !== void 0 ? emptyState : text.empty
|
|
3799
|
+
});
|
|
3800
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
3801
|
+
className: cn(layout === "vertical" ? "flex flex-col gap-0.5" : "flex flex-wrap items-center gap-element", className),
|
|
3802
|
+
role: selection === "none" ? "list" : "group",
|
|
3803
|
+
"aria-label": ariaLabel,
|
|
3804
|
+
"data-opt-id": "G1RNN",
|
|
3805
|
+
"data-opt-slug": "facet.FacetGroup",
|
|
3806
|
+
children: [visible.map((item) => {
|
|
3807
|
+
var _item$share, _item$count2;
|
|
3808
|
+
return /* @__PURE__ */ jsx(Facet, {
|
|
3809
|
+
item,
|
|
3810
|
+
layout,
|
|
3811
|
+
selected: current.includes(item.id),
|
|
3812
|
+
onSelect: selection === "none" ? void 0 : handleSelect,
|
|
3813
|
+
showShare,
|
|
3814
|
+
share: (_item$share = item.share) !== null && _item$share !== void 0 ? _item$share : ((_item$count2 = item.count) !== null && _item$count2 !== void 0 ? _item$count2 : 0) / denominator,
|
|
3815
|
+
countFormatter,
|
|
3816
|
+
labels
|
|
3817
|
+
}, item.id);
|
|
3818
|
+
}), (remaining > 0 || expanded) && max !== void 0 && /* @__PURE__ */ jsx("button", {
|
|
3819
|
+
type: "button",
|
|
3820
|
+
className: cn("text-text-tertiary hover:text-text-primary rounded-md px-2 py-1 text-left text-xs", "outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-1 focus-visible:ring-offset-ring-offset"),
|
|
3821
|
+
onClick: () => setExpanded((value) => !value),
|
|
3822
|
+
children: expanded ? text.showLess : text.showMore(remaining)
|
|
3823
|
+
})]
|
|
3824
|
+
});
|
|
3825
|
+
}
|
|
3826
|
+
//#endregion
|
|
3827
|
+
//#region src/core/expression.tsx
|
|
3828
|
+
const toneStyles = {
|
|
3829
|
+
accent: {
|
|
3830
|
+
text: "text-accent",
|
|
3831
|
+
rule: "decoration-accent"
|
|
3832
|
+
},
|
|
3833
|
+
success: {
|
|
3834
|
+
text: "text-success",
|
|
3835
|
+
rule: "decoration-success"
|
|
3836
|
+
},
|
|
3837
|
+
warning: {
|
|
3838
|
+
text: "text-warning",
|
|
3839
|
+
rule: "decoration-warning"
|
|
3840
|
+
},
|
|
3841
|
+
danger: {
|
|
3842
|
+
text: "text-danger",
|
|
3843
|
+
rule: "decoration-danger"
|
|
3844
|
+
},
|
|
3845
|
+
info: {
|
|
3846
|
+
text: "text-info",
|
|
3847
|
+
rule: "decoration-info"
|
|
3848
|
+
},
|
|
3849
|
+
muted: {
|
|
3850
|
+
text: "text-text-secondary",
|
|
3851
|
+
rule: "decoration-border-hover"
|
|
3852
|
+
}
|
|
3853
|
+
};
|
|
3854
|
+
/**
|
|
3855
|
+
* Expression — one clause of a condition, written as a sentence and edited in
|
|
3856
|
+
* place.
|
|
3857
|
+
*
|
|
3858
|
+
* A rule like "when the count of checkout events is above 100 over 5 minutes"
|
|
3859
|
+
* is a sentence before it is a form. Rendering it as one — with only the
|
|
3860
|
+
* variable halves clickable — lets a reader verify the rule by reading it,
|
|
3861
|
+
* which is the step a stack of labelled inputs skips.
|
|
3862
|
+
*
|
|
3863
|
+
* @example
|
|
3864
|
+
* ```tsx
|
|
3865
|
+
* <ExpressionGroup>
|
|
3866
|
+
* <Expression description="when" value="count()" onClick={openAggregation} />
|
|
3867
|
+
* <Expression description="is above" value="100" tone="danger" onClick={openThreshold} />
|
|
3868
|
+
* <Expression description="for the last" value="5 minutes" onClick={openWindow} />
|
|
3869
|
+
* </ExpressionGroup>
|
|
3870
|
+
* ```
|
|
3871
|
+
*/
|
|
3872
|
+
function Expression({ description, value, tone = "accent", active = false, invalid = false, disabled = false, onClick, display = "inline", "aria-label": ariaLabel, className }) {
|
|
3873
|
+
const palette = invalid ? toneStyles.danger : toneStyles[tone];
|
|
3874
|
+
const interactive = onClick !== void 0 && !disabled;
|
|
3875
|
+
const body = /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx("span", {
|
|
3876
|
+
className: cn("text-text-secondary text-xs font-semibold tracking-wide uppercase", disabled && "opacity-60"),
|
|
3877
|
+
children: description
|
|
3878
|
+
}), value !== void 0 && value !== null && value !== "" && /* @__PURE__ */ jsx("span", {
|
|
3879
|
+
className: cn("text-sm font-semibold", palette.text, interactive && cn("underline decoration-2 underline-offset-4", palette.rule, active ? "decoration-solid" : "decoration-dotted"), disabled && "opacity-60"),
|
|
3880
|
+
children: value
|
|
3881
|
+
})] });
|
|
3882
|
+
const shell = cn(display === "stacked" ? "flex flex-col items-start gap-0.5" : "inline-flex items-baseline gap-1.5", "rounded-md px-1.5 py-1 text-left", interactive && "hover:bg-accent-subtle cursor-pointer transition-colors", active && "bg-accent-subtle", disabled && "cursor-not-allowed", className);
|
|
3883
|
+
if (!interactive) return /* @__PURE__ */ jsx("span", {
|
|
3884
|
+
className: shell,
|
|
3885
|
+
"data-invalid": invalid || void 0,
|
|
3886
|
+
"data-opt-id": "TXGA8",
|
|
3887
|
+
"data-opt-slug": "expression.Expression",
|
|
3888
|
+
children: body
|
|
3889
|
+
});
|
|
3890
|
+
return /* @__PURE__ */ jsx("button", {
|
|
3891
|
+
type: "button",
|
|
3892
|
+
className: cn(shell, OPT_FOCUS_VISIBLE),
|
|
3893
|
+
onClick,
|
|
3894
|
+
disabled,
|
|
3895
|
+
"aria-expanded": active,
|
|
3896
|
+
"aria-label": ariaLabel,
|
|
3897
|
+
"data-invalid": invalid || void 0,
|
|
3898
|
+
"data-opt-id": "TXGA8",
|
|
3899
|
+
"data-opt-slug": "expression.Expression",
|
|
3900
|
+
children: body
|
|
3901
|
+
});
|
|
3902
|
+
}
|
|
3903
|
+
/**
|
|
3904
|
+
* ExpressionGroup — the clauses of one condition, laid out so they read as a
|
|
3905
|
+
* single sentence rather than as a row of unrelated controls.
|
|
3906
|
+
*/
|
|
3907
|
+
function ExpressionGroup({ children, display = "sentence", className, "aria-label": ariaLabel }) {
|
|
3908
|
+
return /* @__PURE__ */ jsx("div", {
|
|
3909
|
+
className: cn(display === "sentence" ? "flex flex-wrap items-baseline gap-x-1 gap-y-1" : "grid grid-flow-col auto-cols-fr items-start gap-group", className),
|
|
3910
|
+
role: "group",
|
|
3911
|
+
"aria-label": ariaLabel,
|
|
3912
|
+
"data-opt-id": "F78CE",
|
|
3913
|
+
"data-opt-slug": "expression.ExpressionGroup",
|
|
3914
|
+
children
|
|
3915
|
+
});
|
|
3916
|
+
}
|
|
3917
|
+
//#endregion
|
|
3918
|
+
//#region src/core/text-truncate.tsx
|
|
3919
|
+
const useIsomorphicLayoutEffect = typeof window === "undefined" ? useEffect : useLayoutEffect;
|
|
3920
|
+
/** Measures text in the element's own resolved font, not an assumed one. */
|
|
3921
|
+
function createMeasurer(element) {
|
|
3922
|
+
const context = document.createElement("canvas").getContext("2d");
|
|
3923
|
+
if (!context) return null;
|
|
3924
|
+
const computed = window.getComputedStyle(element);
|
|
3925
|
+
context.font = [
|
|
3926
|
+
computed.fontStyle,
|
|
3927
|
+
computed.fontVariant,
|
|
3928
|
+
computed.fontWeight,
|
|
3929
|
+
`${computed.fontSize}/${computed.lineHeight}`,
|
|
3930
|
+
computed.fontFamily
|
|
3931
|
+
].join(" ");
|
|
3932
|
+
const spacing = Number.parseFloat(computed.letterSpacing) || 0;
|
|
3933
|
+
return (value) => context.measureText(value).width + spacing * value.length;
|
|
3934
|
+
}
|
|
3935
|
+
/** Largest `n` in `[0, max]` for which `fits(n)` holds. */
|
|
3936
|
+
function largestFitting(max, fits) {
|
|
3937
|
+
let low = 0;
|
|
3938
|
+
let high = max;
|
|
3939
|
+
while (low < high) {
|
|
3940
|
+
const mid = Math.ceil((low + high) / 2);
|
|
3941
|
+
if (fits(mid)) low = mid;
|
|
3942
|
+
else high = mid - 1;
|
|
3943
|
+
}
|
|
3944
|
+
return low;
|
|
3945
|
+
}
|
|
3946
|
+
function truncateToWidth(text, side, ellipsis, available, measure) {
|
|
3947
|
+
if (measure(text) <= available) return text;
|
|
3948
|
+
const chars = Array.from(text);
|
|
3949
|
+
if (side === "end") {
|
|
3950
|
+
const keep = largestFitting(chars.length, (n) => measure(chars.slice(0, n).join("") + ellipsis) <= available);
|
|
3951
|
+
return chars.slice(0, keep).join("") + ellipsis;
|
|
3952
|
+
}
|
|
3953
|
+
if (side === "start") {
|
|
3954
|
+
const keep = largestFitting(chars.length, (n) => measure(ellipsis + chars.slice(chars.length - n).join("")) <= available);
|
|
3955
|
+
return ellipsis + chars.slice(chars.length - keep).join("");
|
|
3956
|
+
}
|
|
3957
|
+
if (side === "startEnd") {
|
|
3958
|
+
const keep = largestFitting(Math.floor(chars.length / 2), (n) => {
|
|
3959
|
+
const head = chars.slice(0, n).join("");
|
|
3960
|
+
const tail = chars.slice(chars.length - n).join("");
|
|
3961
|
+
return measure(head + ellipsis + tail) <= available;
|
|
3962
|
+
});
|
|
3963
|
+
if (keep === 0) return ellipsis;
|
|
3964
|
+
return chars.slice(0, keep).join("") + ellipsis + chars.slice(chars.length - keep).join("");
|
|
3965
|
+
}
|
|
3966
|
+
const span = largestFitting(chars.length, (n) => {
|
|
3967
|
+
const start = Math.floor((chars.length - n) / 2);
|
|
3968
|
+
return measure(ellipsis + chars.slice(start, start + n).join("") + ellipsis) <= available;
|
|
3969
|
+
});
|
|
3970
|
+
if (span === 0) return ellipsis;
|
|
3971
|
+
const start = Math.floor((chars.length - span) / 2);
|
|
3972
|
+
return ellipsis + chars.slice(start, start + span).join("") + ellipsis;
|
|
3973
|
+
}
|
|
3974
|
+
/**
|
|
3975
|
+
* TextTruncate — drops characters from the part of a string that identifies it
|
|
3976
|
+
* least.
|
|
3977
|
+
*
|
|
3978
|
+
* CSS can only cut the end. That is the wrong end for the strings analytics
|
|
3979
|
+
* shows most: two URLs that differ in their query string, two ids that share a
|
|
3980
|
+
* prefix, a stack frame whose filename is at the tail. This measures the real
|
|
3981
|
+
* text in the real font against the real available width and cuts where the
|
|
3982
|
+
* caller says.
|
|
3983
|
+
*
|
|
3984
|
+
* The full string stays in the accessibility tree and in `title`, so
|
|
3985
|
+
* truncation never costs the reader information — only space.
|
|
3986
|
+
*
|
|
3987
|
+
* @example
|
|
3988
|
+
* ```tsx
|
|
3989
|
+
* <TextTruncate text={event.url} truncation="startEnd" />
|
|
3990
|
+
* ```
|
|
3991
|
+
*/
|
|
3992
|
+
function TextTruncate({ text, truncation = "startEnd", ellipsis = "…", width, children, className }) {
|
|
3993
|
+
const ref = useRef(null);
|
|
3994
|
+
const [available, setAvailable] = useState(width !== null && width !== void 0 ? width : null);
|
|
3995
|
+
useIsomorphicLayoutEffect(() => {
|
|
3996
|
+
if (width !== void 0) {
|
|
3997
|
+
setAvailable(width);
|
|
3998
|
+
return;
|
|
3999
|
+
}
|
|
4000
|
+
const element = ref.current;
|
|
4001
|
+
if (!element || typeof ResizeObserver === "undefined") return;
|
|
4002
|
+
const observer = new ResizeObserver((entries) => {
|
|
4003
|
+
const entry = entries[0];
|
|
4004
|
+
if (entry) setAvailable(entry.contentRect.width);
|
|
4005
|
+
});
|
|
4006
|
+
observer.observe(element);
|
|
4007
|
+
setAvailable(element.getBoundingClientRect().width);
|
|
4008
|
+
return () => observer.disconnect();
|
|
4009
|
+
}, [width]);
|
|
4010
|
+
const [rendered, setRendered] = useState(text);
|
|
4011
|
+
useIsomorphicLayoutEffect(() => {
|
|
4012
|
+
const element = ref.current;
|
|
4013
|
+
if (!element || available === null || available <= 0) {
|
|
4014
|
+
setRendered(text);
|
|
4015
|
+
return;
|
|
4016
|
+
}
|
|
4017
|
+
const measure = createMeasurer(element);
|
|
4018
|
+
if (!measure) {
|
|
4019
|
+
setRendered(text);
|
|
4020
|
+
return;
|
|
4021
|
+
}
|
|
4022
|
+
setRendered(truncateToWidth(text, truncation, ellipsis, available, measure));
|
|
4023
|
+
}, [
|
|
4024
|
+
text,
|
|
4025
|
+
truncation,
|
|
4026
|
+
ellipsis,
|
|
4027
|
+
available
|
|
4028
|
+
]);
|
|
4029
|
+
const truncated = rendered !== text;
|
|
4030
|
+
return /* @__PURE__ */ jsxs("span", {
|
|
4031
|
+
ref,
|
|
4032
|
+
className: cn("block w-full overflow-hidden whitespace-nowrap", className),
|
|
4033
|
+
title: truncated ? text : void 0,
|
|
4034
|
+
"data-opt-id": "Y6BGX",
|
|
4035
|
+
"data-opt-slug": "text-truncate.TextTruncate",
|
|
4036
|
+
children: [/* @__PURE__ */ jsx("span", {
|
|
4037
|
+
"aria-hidden": truncated ? "true" : void 0,
|
|
4038
|
+
children: children ? children(rendered) : rendered
|
|
4039
|
+
}), truncated && /* @__PURE__ */ jsx("span", {
|
|
4040
|
+
className: "sr-only",
|
|
4041
|
+
children: text
|
|
4042
|
+
})]
|
|
4043
|
+
});
|
|
4044
|
+
}
|
|
4045
|
+
//#endregion
|
|
4046
|
+
//#region src/core/live-region.tsx
|
|
4047
|
+
/**
|
|
4048
|
+
* LiveRegion — says out loud what a live view changed.
|
|
4049
|
+
*
|
|
4050
|
+
* Data that updates without a page change is invisible to a screen reader
|
|
4051
|
+
* unless something narrates it. This is that narrator: one polite region per
|
|
4052
|
+
* live surface, throttled so a fast stream produces a readable summary rather
|
|
4053
|
+
* than a torrent, and alternating between two nodes so a repeat of the same
|
|
4054
|
+
* words is still announced rather than swallowed as an unchanged region.
|
|
4055
|
+
*
|
|
4056
|
+
* @example
|
|
4057
|
+
* ```tsx
|
|
4058
|
+
* <LiveRegion
|
|
4059
|
+
* message={`${newRows} new events`}
|
|
4060
|
+
* nonce={batchId}
|
|
4061
|
+
* throttleMs={4000}
|
|
4062
|
+
* muted={paused}
|
|
4063
|
+
* />
|
|
4064
|
+
* ```
|
|
4065
|
+
*/
|
|
4066
|
+
function LiveRegion({ message, nonce, politeness = "polite", throttleMs = 0, muted = false }) {
|
|
4067
|
+
const [slots, setSlots] = useState(["", ""]);
|
|
4068
|
+
const activeSlot = useRef(0);
|
|
4069
|
+
const pending = useRef(null);
|
|
4070
|
+
const timer = useRef(null);
|
|
4071
|
+
useEffect(() => {
|
|
4072
|
+
if (muted || message === "") return;
|
|
4073
|
+
const flush = () => {
|
|
4074
|
+
const next = pending.current;
|
|
4075
|
+
pending.current = null;
|
|
4076
|
+
timer.current = null;
|
|
4077
|
+
if (next === null) return;
|
|
4078
|
+
const slot = activeSlot.current;
|
|
4079
|
+
activeSlot.current = slot === 0 ? 1 : 0;
|
|
4080
|
+
setSlots(slot === 0 ? [next, ""] : ["", next]);
|
|
4081
|
+
};
|
|
4082
|
+
pending.current = message;
|
|
4083
|
+
if (throttleMs <= 0) {
|
|
4084
|
+
flush();
|
|
4085
|
+
return;
|
|
4086
|
+
}
|
|
4087
|
+
if (timer.current === null) timer.current = setTimeout(flush, throttleMs);
|
|
4088
|
+
return () => {
|
|
4089
|
+
if (timer.current !== null) {
|
|
4090
|
+
clearTimeout(timer.current);
|
|
4091
|
+
timer.current = null;
|
|
4092
|
+
}
|
|
4093
|
+
};
|
|
4094
|
+
}, [
|
|
4095
|
+
message,
|
|
4096
|
+
nonce,
|
|
4097
|
+
throttleMs,
|
|
4098
|
+
muted
|
|
4099
|
+
]);
|
|
4100
|
+
useEffect(() => {
|
|
4101
|
+
if (!muted) return;
|
|
4102
|
+
pending.current = null;
|
|
4103
|
+
if (timer.current !== null) {
|
|
4104
|
+
clearTimeout(timer.current);
|
|
4105
|
+
timer.current = null;
|
|
4106
|
+
}
|
|
4107
|
+
setSlots(["", ""]);
|
|
4108
|
+
}, [muted]);
|
|
4109
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
4110
|
+
className: "sr-only",
|
|
4111
|
+
"data-opt-id": "0CFGR",
|
|
4112
|
+
"data-opt-slug": "live-region.LiveRegion",
|
|
4113
|
+
children: [/* @__PURE__ */ jsx("div", {
|
|
4114
|
+
role: "status",
|
|
4115
|
+
"aria-live": politeness,
|
|
4116
|
+
"aria-atomic": "true",
|
|
4117
|
+
children: slots[0]
|
|
4118
|
+
}), /* @__PURE__ */ jsx("div", {
|
|
4119
|
+
role: "status",
|
|
4120
|
+
"aria-live": politeness,
|
|
4121
|
+
"aria-atomic": "true",
|
|
4122
|
+
children: slots[1]
|
|
4123
|
+
})]
|
|
4124
|
+
});
|
|
4125
|
+
}
|
|
4126
|
+
//#endregion
|
|
4127
|
+
//#region src/core/color-palette.tsx
|
|
4128
|
+
/** The theme's own chart ramps, so a picker can offer them without a literal. */
|
|
4129
|
+
const THEME_PALETTES = [
|
|
4130
|
+
{
|
|
4131
|
+
id: "categorical",
|
|
4132
|
+
label: "Categorical",
|
|
4133
|
+
kind: "categorical",
|
|
4134
|
+
colors: Array.from({ length: 10 }, (_, index) => `var(--opt-chart-${index + 1})`)
|
|
4135
|
+
},
|
|
4136
|
+
{
|
|
4137
|
+
id: "sequential",
|
|
4138
|
+
label: "Sequential",
|
|
4139
|
+
kind: "sequential",
|
|
4140
|
+
colors: Array.from({ length: 5 }, (_, index) => `var(--opt-chart-sequential-${index + 1})`)
|
|
4141
|
+
},
|
|
4142
|
+
{
|
|
4143
|
+
id: "diverging",
|
|
4144
|
+
label: "Diverging",
|
|
4145
|
+
kind: "diverging",
|
|
4146
|
+
colors: [
|
|
4147
|
+
"var(--opt-chart-diverging-negative)",
|
|
4148
|
+
"var(--opt-chart-diverging-neutral)",
|
|
4149
|
+
"var(--opt-chart-diverging-positive)"
|
|
4150
|
+
]
|
|
4151
|
+
}
|
|
4152
|
+
];
|
|
4153
|
+
const sizeStyles$2 = {
|
|
4154
|
+
xs: "h-1.5",
|
|
4155
|
+
sm: "h-2.5",
|
|
4156
|
+
md: "h-4",
|
|
4157
|
+
lg: "h-6"
|
|
4158
|
+
};
|
|
4159
|
+
/**
|
|
4160
|
+
* ColorPaletteDisplay — a palette, shown as the thing it will look like.
|
|
4161
|
+
*
|
|
4162
|
+
* A palette name tells a reader nothing; the colours do. Categorical palettes
|
|
4163
|
+
* draw as discrete blocks and ramps draw as a blend, so the picture itself
|
|
4164
|
+
* states which kind of data the palette is for.
|
|
4165
|
+
*/
|
|
4166
|
+
function ColorPaletteDisplay({ palette, render, size = "sm", "aria-label": ariaLabel, className }) {
|
|
4167
|
+
const mode = render !== null && render !== void 0 ? render : palette.kind === "categorical" ? "stops" : "gradient";
|
|
4168
|
+
const colors = palette.colors.length > 0 ? palette.colors : ["transparent"];
|
|
4169
|
+
return /* @__PURE__ */ jsx("span", {
|
|
4170
|
+
className: cn("flex w-full overflow-hidden rounded-sm", sizeStyles$2[size], className),
|
|
4171
|
+
role: "img",
|
|
4172
|
+
"aria-label": ariaLabel !== null && ariaLabel !== void 0 ? ariaLabel : palette.label,
|
|
4173
|
+
style: mode === "gradient" ? { backgroundImage: `linear-gradient(to right, ${colors.join(", ")})` } : void 0,
|
|
4174
|
+
"data-opt-id": "E7T89",
|
|
4175
|
+
"data-opt-slug": "color-palette.ColorPaletteDisplay",
|
|
4176
|
+
children: mode === "stops" && colors.map((color, index) => /* @__PURE__ */ jsx("span", {
|
|
4177
|
+
className: "h-full flex-1",
|
|
4178
|
+
style: { backgroundColor: color }
|
|
4179
|
+
}, `${color}-${index}`))
|
|
4180
|
+
});
|
|
4181
|
+
}
|
|
4182
|
+
/**
|
|
4183
|
+
* ColorPalettePicker — choose the colours a visualisation is drawn in.
|
|
4184
|
+
*
|
|
4185
|
+
* Each option shows its own palette, so the choice is made by looking rather
|
|
4186
|
+
* than by remembering what "Cool" meant last time.
|
|
4187
|
+
*
|
|
4188
|
+
* @example
|
|
4189
|
+
* ```tsx
|
|
4190
|
+
* <ColorPalettePicker
|
|
4191
|
+
* palettes={THEME_PALETTES}
|
|
4192
|
+
* value={paletteId}
|
|
4193
|
+
* onValueChange={setPaletteId}
|
|
4194
|
+
* label="Series colours"
|
|
4195
|
+
* />
|
|
4196
|
+
* ```
|
|
4197
|
+
*/
|
|
4198
|
+
function ColorPalettePicker({ palettes, value, onValueChange, label = "Colour palette", hideLabel = false, size = "sm", disabled = false, className }) {
|
|
4199
|
+
var _palettes$find, _ref;
|
|
4200
|
+
const labelId = useId();
|
|
4201
|
+
const selected = (_palettes$find = palettes.find((palette) => palette.id === value)) !== null && _palettes$find !== void 0 ? _palettes$find : palettes[0];
|
|
4202
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
4203
|
+
className: cn("flex flex-col gap-element", className),
|
|
4204
|
+
"data-opt-id": "5R0PG",
|
|
4205
|
+
"data-opt-slug": "color-palette.ColorPalettePicker",
|
|
4206
|
+
children: [/* @__PURE__ */ jsx("span", {
|
|
4207
|
+
id: labelId,
|
|
4208
|
+
className: cn("text-text-primary text-sm font-medium", hideLabel && "sr-only"),
|
|
4209
|
+
children: label
|
|
4210
|
+
}), /* @__PURE__ */ jsxs(SelectRoot$1, {
|
|
4211
|
+
value: (_ref = value !== null && value !== void 0 ? value : selected === null || selected === void 0 ? void 0 : selected.id) !== null && _ref !== void 0 ? _ref : "",
|
|
4212
|
+
setValue: (next) => {
|
|
4213
|
+
if (typeof next === "string") onValueChange === null || onValueChange === void 0 || onValueChange(next);
|
|
4214
|
+
},
|
|
4215
|
+
children: [/* @__PURE__ */ jsx(SelectTrigger$1, {
|
|
4216
|
+
"aria-labelledby": labelId,
|
|
4217
|
+
disabled,
|
|
4218
|
+
className: cn("gap-group", OPT_FOCUS_VISIBLE),
|
|
4219
|
+
children: /* @__PURE__ */ jsxs("span", {
|
|
4220
|
+
className: "gap-group flex min-w-0 flex-1 items-center",
|
|
4221
|
+
children: [/* @__PURE__ */ jsx("span", {
|
|
4222
|
+
className: "text-text-primary shrink-0 truncate text-sm",
|
|
4223
|
+
children: selected === null || selected === void 0 ? void 0 : selected.label
|
|
4224
|
+
}), selected && /* @__PURE__ */ jsx(ColorPaletteDisplay, {
|
|
4225
|
+
palette: selected,
|
|
4226
|
+
size,
|
|
4227
|
+
className: cn("min-w-16 flex-1", "border border-border"),
|
|
4228
|
+
"aria-label": ""
|
|
4229
|
+
})]
|
|
4230
|
+
})
|
|
4231
|
+
}), /* @__PURE__ */ jsx(SelectPopover$1, { children: palettes.map((palette) => /* @__PURE__ */ jsx(SelectItem$1, {
|
|
4232
|
+
value: palette.id,
|
|
4233
|
+
children: /* @__PURE__ */ jsxs("span", {
|
|
4234
|
+
className: "gap-group flex w-full min-w-40 items-center",
|
|
4235
|
+
children: [/* @__PURE__ */ jsx("span", {
|
|
4236
|
+
className: "shrink-0 truncate text-sm",
|
|
4237
|
+
children: palette.label
|
|
4238
|
+
}), /* @__PURE__ */ jsx(ColorPaletteDisplay, {
|
|
4239
|
+
palette,
|
|
4240
|
+
size,
|
|
4241
|
+
className: "min-w-16 flex-1",
|
|
4242
|
+
"aria-label": ""
|
|
4243
|
+
})]
|
|
4244
|
+
})
|
|
4245
|
+
}, palette.id)) })]
|
|
4246
|
+
})]
|
|
4247
|
+
});
|
|
4248
|
+
}
|
|
4249
|
+
//#endregion
|
|
4250
|
+
//#region src/core/inline-edit.tsx
|
|
4251
|
+
const defaultLabels = {
|
|
4252
|
+
edit: (value) => `Edit ${value}`,
|
|
4253
|
+
save: "Save",
|
|
4254
|
+
cancel: "Cancel",
|
|
4255
|
+
invalid: "That value cannot be saved."
|
|
4256
|
+
};
|
|
4257
|
+
const sizeStyles$1 = {
|
|
4258
|
+
sm: "text-sm",
|
|
4259
|
+
md: "text-base",
|
|
4260
|
+
lg: "text-lg font-semibold",
|
|
4261
|
+
xl: "text-2xl font-semibold tracking-tight"
|
|
4262
|
+
};
|
|
4263
|
+
/**
|
|
4264
|
+
* InlineEdit — renames a thing where the thing is, instead of in a dialog.
|
|
4265
|
+
*
|
|
4266
|
+
* A dashboard title, a metric name, a saved segment: each is read far more
|
|
4267
|
+
* often than it is renamed, so the control has to disappear into the text and
|
|
4268
|
+
* reappear on intent. Read mode is a button carrying the text; edit mode is a
|
|
4269
|
+
* field sized to match it, so the words do not move when the mode does.
|
|
4270
|
+
*
|
|
4271
|
+
* Enter commits, Escape reverts, and a rejected save keeps the field open with
|
|
4272
|
+
* what was typed — losing an edit to a validation error is worse than the
|
|
4273
|
+
* error.
|
|
4274
|
+
*
|
|
4275
|
+
* @example
|
|
4276
|
+
* ```tsx
|
|
4277
|
+
* <InlineEdit as="h2" size="xl" value={dashboard.name} onSave={rename} />
|
|
4278
|
+
* ```
|
|
4279
|
+
*/
|
|
4280
|
+
function InlineEdit({ value, onSave, as: Tag = "span", size = "md", placeholder, emptyText = "Untitled", disabled = false, maxLength, suffix, labels, className }) {
|
|
4281
|
+
const text = _objectSpread2(_objectSpread2({}, defaultLabels), labels);
|
|
4282
|
+
const [editing, setEditing] = useState(false);
|
|
4283
|
+
const [draft, setDraft] = useState(value);
|
|
4284
|
+
const [rejected, setRejected] = useState(false);
|
|
4285
|
+
const [saving, setSaving] = useState(false);
|
|
4286
|
+
const inputRef = useRef(null);
|
|
4287
|
+
useEffect(() => {
|
|
4288
|
+
if (!editing) setDraft(value);
|
|
4289
|
+
}, [value, editing]);
|
|
4290
|
+
useEffect(() => {
|
|
4291
|
+
if (editing) {
|
|
4292
|
+
var _inputRef$current, _inputRef$current2;
|
|
4293
|
+
(_inputRef$current = inputRef.current) === null || _inputRef$current === void 0 || _inputRef$current.focus();
|
|
4294
|
+
(_inputRef$current2 = inputRef.current) === null || _inputRef$current2 === void 0 || _inputRef$current2.select();
|
|
4295
|
+
}
|
|
4296
|
+
}, [editing]);
|
|
4297
|
+
const commit = async () => {
|
|
4298
|
+
if (saving) return;
|
|
4299
|
+
const next = draft.trim();
|
|
4300
|
+
if (next === value) {
|
|
4301
|
+
setEditing(false);
|
|
4302
|
+
setRejected(false);
|
|
4303
|
+
return;
|
|
4304
|
+
}
|
|
4305
|
+
setSaving(true);
|
|
4306
|
+
try {
|
|
4307
|
+
if (await onSave(next) === false) {
|
|
4308
|
+
setRejected(true);
|
|
4309
|
+
return;
|
|
4310
|
+
}
|
|
4311
|
+
setRejected(false);
|
|
4312
|
+
setEditing(false);
|
|
4313
|
+
} finally {
|
|
4314
|
+
setSaving(false);
|
|
4315
|
+
}
|
|
4316
|
+
};
|
|
4317
|
+
const cancel = () => {
|
|
4318
|
+
setDraft(value);
|
|
4319
|
+
setRejected(false);
|
|
4320
|
+
setEditing(false);
|
|
4321
|
+
};
|
|
4322
|
+
const handleKeyDown = (event) => {
|
|
4323
|
+
if (event.key === "Enter") {
|
|
4324
|
+
event.preventDefault();
|
|
4325
|
+
commit();
|
|
4326
|
+
} else if (event.key === "Escape") {
|
|
4327
|
+
event.preventDefault();
|
|
4328
|
+
cancel();
|
|
4329
|
+
}
|
|
4330
|
+
};
|
|
4331
|
+
if (editing) return /* @__PURE__ */ jsxs("span", {
|
|
4332
|
+
className: cn("inline-flex items-center gap-element", className),
|
|
4333
|
+
"data-opt-id": "RQ0H2",
|
|
4334
|
+
"data-opt-slug": "inline-edit.InlineEdit",
|
|
4335
|
+
children: [
|
|
4336
|
+
/* @__PURE__ */ jsx("input", {
|
|
4337
|
+
ref: inputRef,
|
|
4338
|
+
value: draft,
|
|
4339
|
+
onChange: (event) => {
|
|
4340
|
+
setDraft(event.target.value);
|
|
4341
|
+
setRejected(false);
|
|
4342
|
+
},
|
|
4343
|
+
onKeyDown: handleKeyDown,
|
|
4344
|
+
onBlur: () => void commit(),
|
|
4345
|
+
placeholder,
|
|
4346
|
+
maxLength,
|
|
4347
|
+
disabled: saving,
|
|
4348
|
+
"aria-invalid": rejected || void 0,
|
|
4349
|
+
"aria-errormessage": rejected ? "inline-edit-error" : void 0,
|
|
4350
|
+
className: cn(OPT_INPUT, sizeStyles$1[size], "min-w-0 flex-1")
|
|
4351
|
+
}),
|
|
4352
|
+
rejected && /* @__PURE__ */ jsx("span", {
|
|
4353
|
+
id: "inline-edit-error",
|
|
4354
|
+
role: "alert",
|
|
4355
|
+
className: "sr-only",
|
|
4356
|
+
children: text.invalid
|
|
4357
|
+
}),
|
|
4358
|
+
/* @__PURE__ */ jsx("button", {
|
|
4359
|
+
type: "button",
|
|
4360
|
+
"aria-label": text.save,
|
|
4361
|
+
disabled: saving,
|
|
4362
|
+
onMouseDown: (event) => event.preventDefault(),
|
|
4363
|
+
onClick: () => void commit(),
|
|
4364
|
+
className: cn("text-success hover:bg-success-subtle rounded-md p-1", OPT_FOCUS_VISIBLE),
|
|
4365
|
+
children: /* @__PURE__ */ jsx(Check, {
|
|
4366
|
+
className: "size-4",
|
|
4367
|
+
"aria-hidden": "true"
|
|
4368
|
+
})
|
|
4369
|
+
}),
|
|
4370
|
+
/* @__PURE__ */ jsx("button", {
|
|
4371
|
+
type: "button",
|
|
4372
|
+
"aria-label": text.cancel,
|
|
4373
|
+
onMouseDown: (event) => event.preventDefault(),
|
|
4374
|
+
onClick: cancel,
|
|
4375
|
+
className: cn("text-text-tertiary hover:bg-bg-subtle rounded-md p-1", OPT_FOCUS_VISIBLE),
|
|
4376
|
+
children: /* @__PURE__ */ jsx(X, {
|
|
4377
|
+
className: "size-4",
|
|
4378
|
+
"aria-hidden": "true"
|
|
4379
|
+
})
|
|
4380
|
+
})
|
|
4381
|
+
]
|
|
4382
|
+
});
|
|
4383
|
+
return /* @__PURE__ */ jsxs("span", {
|
|
4384
|
+
className: cn("inline-flex items-center gap-element", className),
|
|
4385
|
+
"data-opt-id": "RQ0H2",
|
|
4386
|
+
"data-opt-slug": "inline-edit.InlineEdit",
|
|
4387
|
+
children: [/* @__PURE__ */ jsxs("button", {
|
|
4388
|
+
type: "button",
|
|
4389
|
+
disabled,
|
|
4390
|
+
onClick: () => setEditing(true),
|
|
4391
|
+
"aria-label": text.edit(value || emptyText),
|
|
4392
|
+
className: cn("group -mx-1.5 -my-0.5 inline-flex max-w-full items-center gap-element rounded-md px-1.5 py-0.5 text-left", !disabled && "hover:bg-bg-subtle cursor-pointer", disabled && "cursor-default", OPT_FOCUS_VISIBLE),
|
|
4393
|
+
children: [/* @__PURE__ */ jsx(Tag, {
|
|
4394
|
+
className: cn("truncate", sizeStyles$1[size], value ? "text-text-primary" : "text-text-tertiary italic"),
|
|
4395
|
+
children: value || emptyText
|
|
4396
|
+
}), !disabled && /* @__PURE__ */ jsx(Pencil, {
|
|
4397
|
+
"aria-hidden": "true",
|
|
4398
|
+
className: cn("text-text-tertiary size-3.5 shrink-0 opacity-0 transition-opacity", "group-hover:opacity-100 group-focus-visible:opacity-100")
|
|
4399
|
+
})]
|
|
4400
|
+
}), suffix]
|
|
4401
|
+
});
|
|
4402
|
+
}
|
|
4403
|
+
//#endregion
|
|
4404
|
+
//#region src/core/key-pad-menu.tsx
|
|
4405
|
+
const sizeStyles = {
|
|
4406
|
+
sm: "min-h-20 p-2 text-xs [&_[data-keypad-icon]]:size-5",
|
|
4407
|
+
md: "min-h-24 p-3 text-sm [&_[data-keypad-icon]]:size-6"
|
|
4408
|
+
};
|
|
4409
|
+
/**
|
|
4410
|
+
* KeyPadMenu — a choice between things that are easier to recognise than to
|
|
4411
|
+
* name.
|
|
4412
|
+
*
|
|
4413
|
+
* A chart type, a layout, an import source: the picture is the label, and a
|
|
4414
|
+
* dropdown of words makes the reader translate twice. Tiles are real radios or
|
|
4415
|
+
* checkboxes underneath, so arrow keys, screen readers, and form submission
|
|
4416
|
+
* all behave as they would for any other group.
|
|
4417
|
+
*
|
|
4418
|
+
* @example
|
|
4419
|
+
* ```tsx
|
|
4420
|
+
* <KeyPadMenu
|
|
4421
|
+
* label="Chart type"
|
|
4422
|
+
* items={vizTypes}
|
|
4423
|
+
* value={vizType}
|
|
4424
|
+
* onValueChange={setVizType}
|
|
4425
|
+
* />
|
|
4426
|
+
* ```
|
|
4427
|
+
*/
|
|
4428
|
+
function KeyPadMenu({ items, label, selection = "single", value, onValueChange, columns = "auto", size = "md", hideLabel = false, className }) {
|
|
4429
|
+
const name = useId();
|
|
4430
|
+
const selected = new Set(value === void 0 ? [] : Array.isArray(value) ? value : [value]);
|
|
4431
|
+
const handleChange = (id) => {
|
|
4432
|
+
if (selection === "multiple") {
|
|
4433
|
+
const next = selected.has(id) ? [...selected].filter((item) => item !== id) : [...selected, id];
|
|
4434
|
+
onValueChange === null || onValueChange === void 0 || onValueChange(next);
|
|
4435
|
+
return;
|
|
4436
|
+
}
|
|
4437
|
+
onValueChange === null || onValueChange === void 0 || onValueChange(id);
|
|
4438
|
+
};
|
|
4439
|
+
return /* @__PURE__ */ jsxs("fieldset", {
|
|
4440
|
+
className: cn("min-w-0", className),
|
|
4441
|
+
"data-opt-id": "ZZG2R",
|
|
4442
|
+
"data-opt-slug": "key-pad-menu.KeyPadMenu",
|
|
4443
|
+
children: [/* @__PURE__ */ jsx("legend", {
|
|
4444
|
+
className: cn("text-text-primary mb-element text-sm font-medium", hideLabel && "sr-only"),
|
|
4445
|
+
children: label
|
|
4446
|
+
}), /* @__PURE__ */ jsx("div", {
|
|
4447
|
+
className: "gap-element grid",
|
|
4448
|
+
style: { gridTemplateColumns: columns === "auto" ? "repeat(auto-fill, minmax(6.5rem, 1fr))" : `repeat(${columns}, minmax(0, 1fr))` },
|
|
4449
|
+
children: items.map((item) => {
|
|
4450
|
+
const isSelected = selected.has(item.id);
|
|
4451
|
+
return /* @__PURE__ */ jsxs("label", {
|
|
4452
|
+
className: cn("relative flex cursor-pointer flex-col items-center justify-center gap-1.5 rounded-lg text-center transition-colors", OPT_BORDER, sizeStyles[size], isSelected ? "border-accent bg-accent-subtle text-text-primary" : "text-text-secondary hover:border-border-hover hover:bg-bg-subtle", item.disabled && "cursor-not-allowed opacity-50", "has-[:focus-visible]:outline-ring has-[:focus-visible]:outline-2"),
|
|
4453
|
+
children: [
|
|
4454
|
+
/* @__PURE__ */ jsx("input", {
|
|
4455
|
+
type: selection === "single" ? "radio" : "checkbox",
|
|
4456
|
+
name: selection === "single" ? name : void 0,
|
|
4457
|
+
value: item.id,
|
|
4458
|
+
checked: isSelected,
|
|
4459
|
+
disabled: item.disabled,
|
|
4460
|
+
onChange: () => handleChange(item.id),
|
|
4461
|
+
className: cn("sr-only", OPT_FOCUS_VISIBLE)
|
|
4462
|
+
}),
|
|
4463
|
+
/* @__PURE__ */ jsx("span", {
|
|
4464
|
+
"data-keypad-icon": "",
|
|
4465
|
+
"aria-hidden": "true",
|
|
4466
|
+
className: cn("flex items-center justify-center", isSelected ? "text-accent" : "text-text-tertiary"),
|
|
4467
|
+
children: item.icon
|
|
4468
|
+
}),
|
|
4469
|
+
/* @__PURE__ */ jsx("span", {
|
|
4470
|
+
className: "font-medium",
|
|
4471
|
+
children: item.label
|
|
4472
|
+
}),
|
|
4473
|
+
item.hint && /* @__PURE__ */ jsx("span", {
|
|
4474
|
+
className: "text-text-tertiary line-clamp-2 text-[11px] leading-tight",
|
|
4475
|
+
children: item.hint
|
|
4476
|
+
}),
|
|
4477
|
+
item.badge && /* @__PURE__ */ jsx("span", {
|
|
4478
|
+
className: "bg-info-subtle text-info absolute top-1 right-1 rounded px-1 text-[10px] font-semibold",
|
|
4479
|
+
children: item.badge
|
|
4480
|
+
})
|
|
4481
|
+
]
|
|
4482
|
+
}, item.id);
|
|
4483
|
+
})
|
|
4484
|
+
})]
|
|
4485
|
+
});
|
|
4486
|
+
}
|
|
4487
|
+
//#endregion
|
|
4488
|
+
export { Textarea as $, ScrollArea as A, DropdownSeparator as B, DrawerRoot as C, ResizablePanelGroup as D, ResizablePanel as E, Radio as F, formatOptShortDate as G, ConditionBuilder as H, RadioGroup as I, Meter$1 as J, formatOptTime as K, DropdownContent as L, createBlock as M, BlockLayout as N, Section as O, LoadingOverlay as P, AvatarGroup as Q, DropdownItem as R, DrawerPanel as S, Text as St, ResizableHandle as T, formatOptDateTime as U, DropdownTrigger as V, formatOptNumber as W, Progress as X, CircularProgress as Y, Avatar as Z, SHADER_PRESET_IDS as _, Toggle$1 as _t, THEME_PALETTES as a, Switch as at, VirtualList as b, Separator$2 as bt, Expression as c, CardDescription as ct, FacetGroup as d, CardTitle as dt, Skeleton as et, ShaderSurface as f, Breadcrumb as ft, SHADER_PRESETS as g, AccordionTrigger$1 as gt, SHADER_PARAM_KEYS as h, AccordionRoot$1 as ht, ColorPalettePicker as i, SkeletonText as it, PageHeader as j, Container as k, ExpressionGroup as l, CardFooter as lt, SHADER_PARAM_FALLBACK as m, AccordionItem$1 as mt, InlineEdit as n, SkeletonCard as nt, LiveRegion as o, Card as ot, SHADER_OVERLAY_PARAM_KEYS as p, AccordionContent$1 as pt, StepBuilder as q, ColorPaletteDisplay as r, SkeletonTable as rt, TextTruncate as s, CardContent as st, KeyPadMenu as t, SkeletonAvatar as tt, Facet as u, CardHeader as ut, getShaderFragment as v, ToggleGroup$1 as vt, Logo as w, DrawerClose as x, Heading as xt, getShaderPreset as y, OtpField as yt, DropdownMenu as z };
|
|
3653
4489
|
|
|
3654
|
-
//# sourceMappingURL=
|
|
4490
|
+
//# sourceMappingURL=key-pad-menu-BFd79JoS.js.map
|