@reopt-ai/opt-ui 1.8.0 → 1.10.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-Bqfp6dgg.cjs → field-sidebar-B6Ndr8HE.cjs} +304 -289
- package/dist/{field-sidebar-BYD_UIjG.js → field-sidebar-BXjvV7Cz.js} +25 -10
- package/dist/{field-sidebar-BYD_UIjG.js.map → field-sidebar-BXjvV7Cz.js.map} +1 -1
- package/dist/{field-sidebar-5KYVlpSs.d.ts → field-sidebar-DWfJAJlx.d.ts} +32 -3
- package/dist/{field-sidebar-5KYVlpSs.d.ts.map → field-sidebar-DWfJAJlx.d.ts.map} +1 -1
- package/dist/{field-sidebar-BP0iWkoV.d.cts → field-sidebar-nwcE9LC4.d.cts} +32 -3
- package/dist/{field-sidebar-BP0iWkoV.d.cts.map → field-sidebar-nwcE9LC4.d.cts.map} +1 -1
- 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-BQunU20e.js} +727 -19
- package/dist/key-pad-menu-BQunU20e.js.map +1 -0
- package/dist/{shader-surface-BfTP9c1Q.d.ts → key-pad-menu-BsgJMjD4.d.ts} +415 -3
- package/dist/key-pad-menu-BsgJMjD4.d.ts.map +1 -0
- package/dist/{shader-surface-Fav5K9UC.cjs → key-pad-menu-D5mQcS5v.cjs} +969 -201
- package/dist/{shader-surface-DRrcLrWf.d.cts → key-pad-menu-DfZbeXhG.d.cts} +415 -3
- package/dist/key-pad-menu-DfZbeXhG.d.cts.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/dist/{types-D0FlcYnM.d.ts → text-truncate-BGr7bB_8.d.cts} +110 -2
- package/dist/text-truncate-BGr7bB_8.d.cts.map +1 -0
- package/dist/{types-D0FlcYnM.d.cts → text-truncate-BGr7bB_8.d.ts} +110 -2
- package/dist/text-truncate-BGr7bB_8.d.ts.map +1 -0
- package/dist/{pagination-DjfxBcjX.cjs → text-truncate-CxdzAnhS.cjs} +418 -8
- package/dist/{pagination-DWbvACuN.js → text-truncate-j6X3z7Pv.js} +392 -12
- package/dist/text-truncate-j6X3z7Pv.js.map +1 -0
- package/package.json +3 -3
- 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
|
@@ -3,9 +3,9 @@ import { clsx } from "clsx";
|
|
|
3
3
|
import { twMerge } from "tailwind-merge";
|
|
4
4
|
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
5
5
|
import * as React from "react";
|
|
6
|
-
import { createContext, forwardRef, useCallback, useContext, useEffect, useId, useMemo, useRef, useState } from "react";
|
|
6
|
+
import { createContext, forwardRef, useCallback, useContext, useEffect, useId, useLayoutEffect, useMemo, useRef, useState } from "react";
|
|
7
7
|
import { AlertDialogPanel, ComboboxGroup, ComboboxGroupLabel, ComboboxInput, ComboboxItem, ComboboxPopover, ComboboxRoot, Composite, CompositeItem, CompositeProvider, CompositeRow, DialogDescription, DialogDisclosure, DialogDismiss, DialogHeading, DialogPanel, DialogRoot, DisclosureContent, DisclosureRoot, DisclosureTrigger, FormCheckbox, FormControl, FormDescription, FormError, FormField, FormGroup, FormGroupLabel, FormInput, FormLabel, FormProvider, FormPush, FormRadio, FormRadioGroup, FormRemove, FormReset, FormRoot, FormSubmit, FormSwitch, MenuButtonArrow, MenuItem, MenuItemCheckbox, MenuItemRadio, MenuPopover, MenuRoot, MenuSeparator, MenuTrigger, MenubarContainer, MenubarRoot, PopoverClose, PopoverContent, PopoverRoot, PopoverTrigger, SelectItem, SelectLabel, SelectPopover, SelectRoot, SelectTrigger, Tab, TabList, TabPanel, TabsRoot, ToolbarButton, ToolbarContainer, ToolbarRoot, ToolbarSeparator, Tooltip, TooltipAnchor, TooltipProvider, useFieldValue, useFormContext, useFormStore } from "@reopt-ai/opt-ui-primitives";
|
|
8
|
-
import { Bell, CalendarDays, ChartColumn, Check, ChevronLeft, ChevronRight, CircleAlert, CircleCheck, CreditCard, Download, Eye, FileText, Flag, Folder, Globe, Heart, Info, Link2, LogIn, Mail, MessageCircle, MousePointerClick, Search, Settings, Share2, ShoppingCart, Star, TrendingUp, TriangleAlert, Upload, User, UserPlus, XCircle, Zap } from "lucide-react";
|
|
8
|
+
import { AtSign, Bell, Binary, Braces, Brackets, Calendar, CalendarClock, CalendarDays, ChartColumn, Check, ChevronLeft, ChevronRight, CircleAlert, CircleCheck, CircleDollarSign, CircleHelp, CreditCard, Download, Eye, FileText, Fingerprint, Flag, Folder, Globe, Hash, Heart, Info, Layers, Link2, List, LogIn, Mail, MapPin, MessageCircle, MousePointerClick, Network, Percent, Search, Settings, Share2, ShoppingCart, Sigma, Star, Timer, ToggleLeft, TrendingUp, TriangleAlert, Type, Upload, User, UserPlus, XCircle, Zap } from "lucide-react";
|
|
9
9
|
//#region src/lib/cn.ts
|
|
10
10
|
/** Handles cn. */
|
|
11
11
|
function cn(...inputs) {
|
|
@@ -324,7 +324,7 @@ const variantStyles$2 = {
|
|
|
324
324
|
ghost: "text-text-secondary hover:bg-bg-subtle active:bg-bg-muted focus-visible:ring-offset-1",
|
|
325
325
|
danger: "bg-danger text-danger-fg hover:bg-danger active:bg-danger focus-visible:ring-offset-2"
|
|
326
326
|
};
|
|
327
|
-
const sizeStyles$
|
|
327
|
+
const sizeStyles$4 = {
|
|
328
328
|
sm: "h-[var(--opt-control-sm)] px-[var(--opt-control-padding-sm)] text-xs gap-[var(--opt-space-1-5)]",
|
|
329
329
|
md: "h-[var(--opt-control-md)] px-[var(--opt-control-padding-md)] text-sm gap-[var(--opt-space-2)]",
|
|
330
330
|
lg: "h-[var(--opt-control-lg)] px-[var(--opt-control-padding-lg)] text-base gap-[var(--opt-space-2)]"
|
|
@@ -335,7 +335,7 @@ function Button(_ref) {
|
|
|
335
335
|
const isDisabled = disabled || loading;
|
|
336
336
|
return /* @__PURE__ */ jsxs("button", _objectSpread2(_objectSpread2({
|
|
337
337
|
type: "button",
|
|
338
|
-
className: cn("inline-flex cursor-pointer items-center justify-center rounded-lg font-medium transition-colors", OPT_FOCUS_VISIBLE, variantStyles$2[variant], sizeStyles$
|
|
338
|
+
className: cn("inline-flex cursor-pointer items-center justify-center rounded-lg font-medium transition-colors", OPT_FOCUS_VISIBLE, variantStyles$2[variant], sizeStyles$4[size], isDisabled && "pointer-events-none cursor-not-allowed opacity-50", className),
|
|
339
339
|
disabled: isDisabled
|
|
340
340
|
}, props), {}, {
|
|
341
341
|
"aria-busy": loading ? true : ariaBusy,
|
|
@@ -444,7 +444,7 @@ const progressStroke = {
|
|
|
444
444
|
error: "stroke-danger",
|
|
445
445
|
info: "stroke-info"
|
|
446
446
|
};
|
|
447
|
-
const sizeStyles$
|
|
447
|
+
const sizeStyles$3 = {
|
|
448
448
|
sm: "px-1.5 py-0.5 text-[10px]",
|
|
449
449
|
md: "px-2 py-0.5 text-[11px]"
|
|
450
450
|
};
|
|
@@ -456,7 +456,7 @@ function Badge(_ref) {
|
|
|
456
456
|
const radius = (progressSize - 3) / 2;
|
|
457
457
|
const circumference = 2 * Math.PI * radius;
|
|
458
458
|
const offset = circumference - clampedProgress / 100 * circumference;
|
|
459
|
-
return /* @__PURE__ */ jsxs("span", _objectSpread2(_objectSpread2({ className: cn(OPT_BADGE, variantStyles$1[variant], sizeStyles$
|
|
459
|
+
return /* @__PURE__ */ jsxs("span", _objectSpread2(_objectSpread2({ className: cn(OPT_BADGE, variantStyles$1[variant], sizeStyles$3[size], className) }, props), {}, {
|
|
460
460
|
"data-opt-id": "9GBGZ",
|
|
461
461
|
"data-opt-slug": "badge.Badge",
|
|
462
462
|
children: [
|
|
@@ -503,7 +503,7 @@ const _excluded$13 = [
|
|
|
503
503
|
"aria-hidden",
|
|
504
504
|
"aria-label"
|
|
505
505
|
];
|
|
506
|
-
const sizeStyles$
|
|
506
|
+
const sizeStyles$2 = {
|
|
507
507
|
sm: "h-4 w-4",
|
|
508
508
|
md: "h-6 w-6",
|
|
509
509
|
lg: "h-8 w-8"
|
|
@@ -513,7 +513,7 @@ function Spinner(_ref) {
|
|
|
513
513
|
let { size = "md", className, role, "aria-hidden": ariaHidden, "aria-label": ariaLabel } = _ref, props = _objectWithoutProperties(_ref, _excluded$13);
|
|
514
514
|
const isDecorative = ariaHidden === true || ariaHidden === "true";
|
|
515
515
|
return /* @__PURE__ */ jsxs("svg", _objectSpread2(_objectSpread2({
|
|
516
|
-
className: cn("animate-spin text-current", sizeStyles$
|
|
516
|
+
className: cn("animate-spin text-current", sizeStyles$2[size], className),
|
|
517
517
|
viewBox: "0 0 24 24",
|
|
518
518
|
fill: "none",
|
|
519
519
|
role: isDecorative ? void 0 : role !== null && role !== void 0 ? role : "status",
|
|
@@ -3532,7 +3532,7 @@ function setForwardedRef(ref, value) {
|
|
|
3532
3532
|
if (typeof ref === "function") ref(value);
|
|
3533
3533
|
else if (ref) ref.current = value;
|
|
3534
3534
|
}
|
|
3535
|
-
const sizeStyles = {
|
|
3535
|
+
const sizeStyles$1 = {
|
|
3536
3536
|
sm: {
|
|
3537
3537
|
box: "h-4 w-4",
|
|
3538
3538
|
icon: "h-3 w-3",
|
|
@@ -3598,7 +3598,7 @@ const Checkbox = forwardRef((_ref, ref) => {
|
|
|
3598
3598
|
const checkboxId = (_useInputId = useInputId(id, label, "checkbox")) !== null && _useInputId !== void 0 ? _useInputId : `checkbox-${fallbackId}`;
|
|
3599
3599
|
const hintText = hint !== null && hint !== void 0 ? hint : description;
|
|
3600
3600
|
const hasError = Boolean(error);
|
|
3601
|
-
const styles = sizeStyles[size];
|
|
3601
|
+
const styles = sizeStyles$1[size];
|
|
3602
3602
|
const isIndeterminate = Boolean(indeterminate);
|
|
3603
3603
|
const inputRef = useCallback((node) => {
|
|
3604
3604
|
if (node) node.indeterminate = isIndeterminate;
|
|
@@ -3845,6 +3845,386 @@ function Pagination(_ref) {
|
|
|
3845
3845
|
}));
|
|
3846
3846
|
}
|
|
3847
3847
|
//#endregion
|
|
3848
|
-
|
|
3848
|
+
//#region src/core/field-token.tsx
|
|
3849
|
+
/**
|
|
3850
|
+
* The glyph and palette slot each field type reads as. Exported so a product
|
|
3851
|
+
* can render the same legend the tokens imply.
|
|
3852
|
+
*/
|
|
3853
|
+
const FIELD_TYPE_TOKENS = {
|
|
3854
|
+
string: {
|
|
3855
|
+
icon: Type,
|
|
3856
|
+
tone: 1,
|
|
3857
|
+
name: "String"
|
|
3858
|
+
},
|
|
3859
|
+
number: {
|
|
3860
|
+
icon: Hash,
|
|
3861
|
+
tone: 2,
|
|
3862
|
+
name: "Number"
|
|
3863
|
+
},
|
|
3864
|
+
boolean: {
|
|
3865
|
+
icon: ToggleLeft,
|
|
3866
|
+
tone: 3,
|
|
3867
|
+
name: "Boolean"
|
|
3868
|
+
},
|
|
3869
|
+
date: {
|
|
3870
|
+
icon: Calendar,
|
|
3871
|
+
tone: 4,
|
|
3872
|
+
name: "Date"
|
|
3873
|
+
},
|
|
3874
|
+
datetime: {
|
|
3875
|
+
icon: CalendarClock,
|
|
3876
|
+
tone: 4,
|
|
3877
|
+
name: "Date and time"
|
|
3878
|
+
},
|
|
3879
|
+
duration: {
|
|
3880
|
+
icon: Timer,
|
|
3881
|
+
tone: 5,
|
|
3882
|
+
name: "Duration"
|
|
3883
|
+
},
|
|
3884
|
+
percent: {
|
|
3885
|
+
icon: Percent,
|
|
3886
|
+
tone: 6,
|
|
3887
|
+
name: "Percent"
|
|
3888
|
+
},
|
|
3889
|
+
currency: {
|
|
3890
|
+
icon: CircleDollarSign,
|
|
3891
|
+
tone: 7,
|
|
3892
|
+
name: "Currency"
|
|
3893
|
+
},
|
|
3894
|
+
enum: {
|
|
3895
|
+
icon: List,
|
|
3896
|
+
tone: 8,
|
|
3897
|
+
name: "Enum"
|
|
3898
|
+
},
|
|
3899
|
+
array: {
|
|
3900
|
+
icon: Brackets,
|
|
3901
|
+
tone: 9,
|
|
3902
|
+
name: "Array"
|
|
3903
|
+
},
|
|
3904
|
+
object: {
|
|
3905
|
+
icon: Braces,
|
|
3906
|
+
tone: 10,
|
|
3907
|
+
name: "Object"
|
|
3908
|
+
},
|
|
3909
|
+
json: {
|
|
3910
|
+
icon: Braces,
|
|
3911
|
+
tone: 10,
|
|
3912
|
+
name: "JSON"
|
|
3913
|
+
},
|
|
3914
|
+
ip: {
|
|
3915
|
+
icon: Network,
|
|
3916
|
+
tone: 11,
|
|
3917
|
+
name: "IP address"
|
|
3918
|
+
},
|
|
3919
|
+
geo: {
|
|
3920
|
+
icon: MapPin,
|
|
3921
|
+
tone: 12,
|
|
3922
|
+
name: "Geo point"
|
|
3923
|
+
},
|
|
3924
|
+
url: {
|
|
3925
|
+
icon: Link2,
|
|
3926
|
+
tone: 13,
|
|
3927
|
+
name: "URL"
|
|
3928
|
+
},
|
|
3929
|
+
email: {
|
|
3930
|
+
icon: AtSign,
|
|
3931
|
+
tone: 14,
|
|
3932
|
+
name: "Email"
|
|
3933
|
+
},
|
|
3934
|
+
id: {
|
|
3935
|
+
icon: Fingerprint,
|
|
3936
|
+
tone: 15,
|
|
3937
|
+
name: "Identifier"
|
|
3938
|
+
},
|
|
3939
|
+
binary: {
|
|
3940
|
+
icon: Binary,
|
|
3941
|
+
tone: 16,
|
|
3942
|
+
name: "Binary"
|
|
3943
|
+
},
|
|
3944
|
+
aggregate: {
|
|
3945
|
+
icon: Sigma,
|
|
3946
|
+
tone: 2,
|
|
3947
|
+
name: "Aggregate"
|
|
3948
|
+
},
|
|
3949
|
+
nested: {
|
|
3950
|
+
icon: Layers,
|
|
3951
|
+
tone: 9,
|
|
3952
|
+
name: "Nested"
|
|
3953
|
+
},
|
|
3954
|
+
unknown: {
|
|
3955
|
+
icon: CircleHelp,
|
|
3956
|
+
tone: 0,
|
|
3957
|
+
name: "Unknown"
|
|
3958
|
+
}
|
|
3959
|
+
};
|
|
3960
|
+
/**
|
|
3961
|
+
* Names a backend type as one of ours. Accepts the spellings Postgres,
|
|
3962
|
+
* ClickHouse, DuckDB, and JSON Schema actually emit, so a caller can hand
|
|
3963
|
+
* over a column type verbatim instead of maintaining its own table.
|
|
3964
|
+
*/
|
|
3965
|
+
function resolveFieldType(raw) {
|
|
3966
|
+
if (!raw) return "unknown";
|
|
3967
|
+
const t = raw.trim().toLowerCase();
|
|
3968
|
+
if (t in FIELD_TYPE_TOKENS) return t;
|
|
3969
|
+
if (t.endsWith("[]")) return "array";
|
|
3970
|
+
if (/^(array|list|set|multi)/.test(t)) return "array";
|
|
3971
|
+
if (/^(bytea|blob|binary|fixedstring)/.test(t)) return "binary";
|
|
3972
|
+
if (/^bool/.test(t)) return "boolean";
|
|
3973
|
+
if (/^(timestamp|datetime)/.test(t)) return "datetime";
|
|
3974
|
+
if (/^date/.test(t)) return "date";
|
|
3975
|
+
if (/^interval/.test(t)) return "duration";
|
|
3976
|
+
if (/^(uuid|guid)/.test(t)) return "id";
|
|
3977
|
+
if (/^(inet|cidr|ipv?[46])/.test(t)) return "ip";
|
|
3978
|
+
if (/^(point|geo|geography|geometry|latlon)/.test(t)) return "geo";
|
|
3979
|
+
if (/^(jsonb?|map|struct|record|tuple)/.test(t)) return "json";
|
|
3980
|
+
if (/^(enum|lowcardinality|category|categorical)/.test(t)) return "enum";
|
|
3981
|
+
if (/^(u?int|float|double|decimal|numeric|real|bigint|smallint|tinyint|serial|money)/.test(t)) return "number";
|
|
3982
|
+
if (/^(text|n?varchar|n?char|keyword|str|string)/.test(t)) return "string";
|
|
3983
|
+
return "unknown";
|
|
3984
|
+
}
|
|
3985
|
+
const sizeStyles = {
|
|
3986
|
+
xs: "size-4 rounded-[3px] [&>svg]:size-2.5",
|
|
3987
|
+
sm: "size-5 rounded [&>svg]:size-3",
|
|
3988
|
+
md: "size-6 rounded-md [&>svg]:size-3.5",
|
|
3989
|
+
lg: "size-8 rounded-lg [&>svg]:size-4"
|
|
3990
|
+
};
|
|
3991
|
+
const shapeStyles = {
|
|
3992
|
+
square: "",
|
|
3993
|
+
circle: "rounded-full"
|
|
3994
|
+
};
|
|
3995
|
+
/**
|
|
3996
|
+
* FieldToken — the type of a field, as a glyph.
|
|
3997
|
+
*
|
|
3998
|
+
* A field list, a column header, and a property table all have to answer
|
|
3999
|
+
* "what kind of thing is this?" before anything else, and they have to answer
|
|
4000
|
+
* it in the width of an icon. This is that answer: one glyph per type, one
|
|
4001
|
+
* palette slot per glyph, stable across every surface that shows fields, so a
|
|
4002
|
+
* reader learns the vocabulary once.
|
|
4003
|
+
*
|
|
4004
|
+
* @example
|
|
4005
|
+
* ```tsx
|
|
4006
|
+
* <FieldToken type={resolveFieldType(column.dataType)} title={column.name} />
|
|
4007
|
+
* ```
|
|
4008
|
+
*/
|
|
4009
|
+
function FieldToken({ type, size = "sm", fill = "subtle", shape = "square", title, decorative = false, className }) {
|
|
4010
|
+
var _FIELD_TYPE_TOKENS$ty;
|
|
4011
|
+
const appearance = (_FIELD_TYPE_TOKENS$ty = FIELD_TYPE_TOKENS[type]) !== null && _FIELD_TYPE_TOKENS$ty !== void 0 ? _FIELD_TYPE_TOKENS$ty : FIELD_TYPE_TOKENS.unknown;
|
|
4012
|
+
const Icon = appearance.icon;
|
|
4013
|
+
const label = title !== null && title !== void 0 ? title : appearance.name;
|
|
4014
|
+
const hue = appearance.tone === 0 ? "var(--color-text-tertiary)" : `var(--opt-chart-${appearance.tone})`;
|
|
4015
|
+
const style = fill === "solid" ? {
|
|
4016
|
+
backgroundColor: hue,
|
|
4017
|
+
color: "var(--color-surface)"
|
|
4018
|
+
} : fill === "subtle" ? {
|
|
4019
|
+
backgroundColor: `color-mix(in oklab, ${hue} 14%, transparent)`,
|
|
4020
|
+
color: hue
|
|
4021
|
+
} : { color: hue };
|
|
4022
|
+
return /* @__PURE__ */ jsx("span", {
|
|
4023
|
+
className: cn("inline-flex shrink-0 items-center justify-center", sizeStyles[size], shapeStyles[shape], className),
|
|
4024
|
+
style,
|
|
4025
|
+
role: decorative ? void 0 : "img",
|
|
4026
|
+
"aria-label": decorative ? void 0 : label,
|
|
4027
|
+
"aria-hidden": decorative ? true : void 0,
|
|
4028
|
+
"data-opt-id": "V3AJ4",
|
|
4029
|
+
"data-opt-slug": "field-token.FieldToken",
|
|
4030
|
+
"data-field-type": type,
|
|
4031
|
+
children: /* @__PURE__ */ jsx(Icon, { "aria-hidden": "true" })
|
|
4032
|
+
});
|
|
4033
|
+
}
|
|
4034
|
+
//#endregion
|
|
4035
|
+
//#region src/core/highlight.tsx
|
|
4036
|
+
function escapeForRegExp(value) {
|
|
4037
|
+
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
4038
|
+
}
|
|
4039
|
+
/**
|
|
4040
|
+
* Splits `text` on every occurrence of every term. Terms are matched longest
|
|
4041
|
+
* first so a search for both "check" and "checkout" marks "checkout" whole
|
|
4042
|
+
* rather than leaving a stray "out".
|
|
4043
|
+
*/
|
|
4044
|
+
function segment(text, terms, caseSensitive, firstOnly) {
|
|
4045
|
+
const usable = terms.filter((term) => term.length > 0).sort((a, b) => b.length - a.length);
|
|
4046
|
+
if (usable.length === 0) return [{
|
|
4047
|
+
text,
|
|
4048
|
+
hit: false
|
|
4049
|
+
}];
|
|
4050
|
+
const pattern = new RegExp(`(${usable.map(escapeForRegExp).join("|")})`, caseSensitive ? "g" : "gi");
|
|
4051
|
+
const out = [];
|
|
4052
|
+
let cursor = 0;
|
|
4053
|
+
let matched = false;
|
|
4054
|
+
for (const match of text.matchAll(pattern)) {
|
|
4055
|
+
var _match$index;
|
|
4056
|
+
if (firstOnly && matched) break;
|
|
4057
|
+
const at = (_match$index = match.index) !== null && _match$index !== void 0 ? _match$index : 0;
|
|
4058
|
+
if (at > cursor) out.push({
|
|
4059
|
+
text: text.slice(cursor, at),
|
|
4060
|
+
hit: false
|
|
4061
|
+
});
|
|
4062
|
+
out.push({
|
|
4063
|
+
text: match[0],
|
|
4064
|
+
hit: true
|
|
4065
|
+
});
|
|
4066
|
+
cursor = at + match[0].length;
|
|
4067
|
+
matched = true;
|
|
4068
|
+
}
|
|
4069
|
+
if (cursor < text.length) out.push({
|
|
4070
|
+
text: text.slice(cursor),
|
|
4071
|
+
hit: false
|
|
4072
|
+
});
|
|
4073
|
+
return out;
|
|
4074
|
+
}
|
|
4075
|
+
/**
|
|
4076
|
+
* Highlight — marks the part of a string the reader searched for.
|
|
4077
|
+
*
|
|
4078
|
+
* Uses a real `<mark>`, so the match is carried by semantics rather than by
|
|
4079
|
+
* colour alone and survives a high-contrast theme. Terms are matched longest
|
|
4080
|
+
* first, so overlapping terms produce whole marks instead of fragments.
|
|
4081
|
+
*
|
|
4082
|
+
* @example
|
|
4083
|
+
* ```tsx
|
|
4084
|
+
* <Highlight text={row.message} search={query} />
|
|
4085
|
+
* ```
|
|
4086
|
+
*/
|
|
4087
|
+
function Highlight({ text, search, caseSensitive = false, firstOnly = false, className, markClassName }) {
|
|
4088
|
+
const segments = segment(text, Array.isArray(search) ? search : [search], caseSensitive, firstOnly);
|
|
4089
|
+
return /* @__PURE__ */ jsx("span", {
|
|
4090
|
+
className,
|
|
4091
|
+
"data-opt-id": "448TR",
|
|
4092
|
+
"data-opt-slug": "highlight.Highlight",
|
|
4093
|
+
children: segments.map((part, index) => part.hit ? /* @__PURE__ */ jsx("mark", {
|
|
4094
|
+
className: cn("bg-warning-subtle text-text-primary rounded-[2px] px-0.5 font-medium", markClassName),
|
|
4095
|
+
children: part.text
|
|
4096
|
+
}, index) : /* @__PURE__ */ jsx("span", { children: part.text }, index))
|
|
4097
|
+
});
|
|
4098
|
+
}
|
|
4099
|
+
//#endregion
|
|
4100
|
+
//#region src/core/text-truncate.tsx
|
|
4101
|
+
const useIsomorphicLayoutEffect = typeof window === "undefined" ? useEffect : useLayoutEffect;
|
|
4102
|
+
/** Measures text in the element's own resolved font, not an assumed one. */
|
|
4103
|
+
function createMeasurer(element) {
|
|
4104
|
+
const context = document.createElement("canvas").getContext("2d");
|
|
4105
|
+
if (!context) return null;
|
|
4106
|
+
const computed = window.getComputedStyle(element);
|
|
4107
|
+
context.font = [
|
|
4108
|
+
computed.fontStyle,
|
|
4109
|
+
computed.fontVariant,
|
|
4110
|
+
computed.fontWeight,
|
|
4111
|
+
`${computed.fontSize}/${computed.lineHeight}`,
|
|
4112
|
+
computed.fontFamily
|
|
4113
|
+
].join(" ");
|
|
4114
|
+
const spacing = Number.parseFloat(computed.letterSpacing) || 0;
|
|
4115
|
+
return (value) => context.measureText(value).width + spacing * value.length;
|
|
4116
|
+
}
|
|
4117
|
+
/** Largest `n` in `[0, max]` for which `fits(n)` holds. */
|
|
4118
|
+
function largestFitting(max, fits) {
|
|
4119
|
+
let low = 0;
|
|
4120
|
+
let high = max;
|
|
4121
|
+
while (low < high) {
|
|
4122
|
+
const mid = Math.ceil((low + high) / 2);
|
|
4123
|
+
if (fits(mid)) low = mid;
|
|
4124
|
+
else high = mid - 1;
|
|
4125
|
+
}
|
|
4126
|
+
return low;
|
|
4127
|
+
}
|
|
4128
|
+
function truncateToWidth(text, side, ellipsis, available, measure) {
|
|
4129
|
+
if (measure(text) <= available) return text;
|
|
4130
|
+
const chars = Array.from(text);
|
|
4131
|
+
if (side === "end") {
|
|
4132
|
+
const keep = largestFitting(chars.length, (n) => measure(chars.slice(0, n).join("") + ellipsis) <= available);
|
|
4133
|
+
return chars.slice(0, keep).join("") + ellipsis;
|
|
4134
|
+
}
|
|
4135
|
+
if (side === "start") {
|
|
4136
|
+
const keep = largestFitting(chars.length, (n) => measure(ellipsis + chars.slice(chars.length - n).join("")) <= available);
|
|
4137
|
+
return ellipsis + chars.slice(chars.length - keep).join("");
|
|
4138
|
+
}
|
|
4139
|
+
if (side === "startEnd") {
|
|
4140
|
+
const keep = largestFitting(Math.floor(chars.length / 2), (n) => {
|
|
4141
|
+
const head = chars.slice(0, n).join("");
|
|
4142
|
+
const tail = chars.slice(chars.length - n).join("");
|
|
4143
|
+
return measure(head + ellipsis + tail) <= available;
|
|
4144
|
+
});
|
|
4145
|
+
if (keep === 0) return ellipsis;
|
|
4146
|
+
return chars.slice(0, keep).join("") + ellipsis + chars.slice(chars.length - keep).join("");
|
|
4147
|
+
}
|
|
4148
|
+
const span = largestFitting(chars.length, (n) => {
|
|
4149
|
+
const start = Math.floor((chars.length - n) / 2);
|
|
4150
|
+
return measure(ellipsis + chars.slice(start, start + n).join("") + ellipsis) <= available;
|
|
4151
|
+
});
|
|
4152
|
+
if (span === 0) return ellipsis;
|
|
4153
|
+
const start = Math.floor((chars.length - span) / 2);
|
|
4154
|
+
return ellipsis + chars.slice(start, start + span).join("") + ellipsis;
|
|
4155
|
+
}
|
|
4156
|
+
/**
|
|
4157
|
+
* TextTruncate — drops characters from the part of a string that identifies it
|
|
4158
|
+
* least.
|
|
4159
|
+
*
|
|
4160
|
+
* CSS can only cut the end. That is the wrong end for the strings analytics
|
|
4161
|
+
* shows most: two URLs that differ in their query string, two ids that share a
|
|
4162
|
+
* prefix, a stack frame whose filename is at the tail. This measures the real
|
|
4163
|
+
* text in the real font against the real available width and cuts where the
|
|
4164
|
+
* caller says.
|
|
4165
|
+
*
|
|
4166
|
+
* The full string stays in the accessibility tree and in `title`, so
|
|
4167
|
+
* truncation never costs the reader information — only space.
|
|
4168
|
+
*
|
|
4169
|
+
* @example
|
|
4170
|
+
* ```tsx
|
|
4171
|
+
* <TextTruncate text={event.url} truncation="startEnd" />
|
|
4172
|
+
* ```
|
|
4173
|
+
*/
|
|
4174
|
+
function TextTruncate({ text, truncation = "startEnd", ellipsis = "…", width, children, className }) {
|
|
4175
|
+
const ref = useRef(null);
|
|
4176
|
+
const [available, setAvailable] = useState(width !== null && width !== void 0 ? width : null);
|
|
4177
|
+
useIsomorphicLayoutEffect(() => {
|
|
4178
|
+
if (width !== void 0) {
|
|
4179
|
+
setAvailable(width);
|
|
4180
|
+
return;
|
|
4181
|
+
}
|
|
4182
|
+
const element = ref.current;
|
|
4183
|
+
if (!element || typeof ResizeObserver === "undefined") return;
|
|
4184
|
+
const observer = new ResizeObserver((entries) => {
|
|
4185
|
+
const entry = entries[0];
|
|
4186
|
+
if (entry) setAvailable(entry.contentRect.width);
|
|
4187
|
+
});
|
|
4188
|
+
observer.observe(element);
|
|
4189
|
+
setAvailable(element.getBoundingClientRect().width);
|
|
4190
|
+
return () => observer.disconnect();
|
|
4191
|
+
}, [width]);
|
|
4192
|
+
const [rendered, setRendered] = useState(text);
|
|
4193
|
+
useIsomorphicLayoutEffect(() => {
|
|
4194
|
+
const element = ref.current;
|
|
4195
|
+
if (!element || available === null || available <= 0) {
|
|
4196
|
+
setRendered(text);
|
|
4197
|
+
return;
|
|
4198
|
+
}
|
|
4199
|
+
const measure = createMeasurer(element);
|
|
4200
|
+
if (!measure) {
|
|
4201
|
+
setRendered(text);
|
|
4202
|
+
return;
|
|
4203
|
+
}
|
|
4204
|
+
setRendered(truncateToWidth(text, truncation, ellipsis, available, measure));
|
|
4205
|
+
}, [
|
|
4206
|
+
text,
|
|
4207
|
+
truncation,
|
|
4208
|
+
ellipsis,
|
|
4209
|
+
available
|
|
4210
|
+
]);
|
|
4211
|
+
const truncated = rendered !== text;
|
|
4212
|
+
return /* @__PURE__ */ jsxs("span", {
|
|
4213
|
+
ref,
|
|
4214
|
+
className: cn("block w-full overflow-hidden whitespace-nowrap", className),
|
|
4215
|
+
title: truncated ? text : void 0,
|
|
4216
|
+
"data-opt-id": "Y6BGX",
|
|
4217
|
+
"data-opt-slug": "text-truncate.TextTruncate",
|
|
4218
|
+
children: [/* @__PURE__ */ jsx("span", {
|
|
4219
|
+
"aria-hidden": truncated ? "true" : void 0,
|
|
4220
|
+
children: children ? children(rendered) : rendered
|
|
4221
|
+
}), truncated && /* @__PURE__ */ jsx("span", {
|
|
4222
|
+
className: "sr-only",
|
|
4223
|
+
children: text
|
|
4224
|
+
})]
|
|
4225
|
+
});
|
|
4226
|
+
}
|
|
4227
|
+
//#endregion
|
|
4228
|
+
export { ToolbarButton$1 as $, Alert as $t, FormSelectItem as A, OPT_HOVER_ITEM as An, DialogHeading$1 as At, useFormStore as B, OPT_TEXT_LABEL as Bn, TabPanel$1 as Bt, FormPush$1 as C, OPT_DIVIDER as Cn, AlertDialogPanel$1 as Ct, FormReset$1 as D, OPT_DOT_YELLOW as Dn, DialogDismiss$1 as Dt, FormRemove$1 as E, OPT_DOT_RED as En, DialogDisclosure$1 as Et, FormSwitch$1 as F, OPT_SURFACE_POPOVER as Fn, SelectPopover$1 as Ft, getIconRegistryEntry as G, _objectSpread2 as Gn, Composite as Gt, DateRangePicker as H, OPT_TEXT_SECONDARY as Hn, DisclosureContent$1 as Ht, FormTagInput as I, OPT_TABLE_ROW_ACTIVE as In, SelectRoot$1 as It, PopoverContent$1 as J, cn as Jn, CompositeRow as Jt, ColorPicker as K, _objectWithoutProperties as Kn, CompositeItem as Kt, FormTextarea as L, OPT_TD as Ln, SelectTrigger$1 as Lt, FormSlider as M, OPT_INPUT_ERROR as Mn, DialogRoot$1 as Mt, FormStarRating as N, OPT_ITEM_BASE as Nn, SelectItem$1 as Nt, FormRoot$1 as O, OPT_FOCUS as On, DialogFooter as Ot, FormSubmit$1 as P, OPT_SURFACE as Pn, SelectLabel$1 as Pt, NumberInput as Q, useInputId as Qt, useFieldValue as R, OPT_TD_MUTED as Rn, Tab$1 as Rt, FormProvider$1 as S, OPT_DISCLOSURE_TRIGGER as Sn, ComboboxRoot$1 as St, FormRadioGroup$1 as T, OPT_DOT_GREEN as Tn, DialogDescription$1 as Tt, TagInput as U, OPT_TEXT_TERTIARY as Un, DisclosureRoot$1 as Ut, DEFAULT_PRESETS as V, OPT_TEXT_PRIMARY as Vn, TabsRoot$1 as Vt, IconPicker as W, OPT_TH as Wn, DisclosureTrigger$1 as Wt, PopoverTrigger$1 as X, StarRating as Xt, PopoverRoot$1 as Y, CompositeZone as Yt, Slider as Z, Input as Zt, FormGroupLabel$1 as _, OPT_BADGE_NEUTRAL as _n, ComboboxGroup$1 as _t, resolveFieldType as a, koComboboxLabels as an, TooltipProvider$1 as at, FormLabel$1 as b, OPT_BORDER as bn, ComboboxItem$1 as bt, CheckboxGroup as c, useControlledState as cn, MenuItemCheckbox$1 as ct, FormControl$1 as d, getDescribedBy as dn, MenuRoot$1 as dt, Spinner as en, ToolbarContainer$1 as et, FormDateRangePicker as f, OPT_ACTIVE_ITEM as fn, MenuSeparator$1 as ft, FormGroup$1 as g, OPT_BADGE_GREEN as gn, ComboboxEmpty as gt, FormField$1 as h, OPT_BADGE_BLUE as hn, MenubarRoot$1 as ht, FieldToken as i, koBreadcrumbLabels as in, TooltipAnchor$1 as it, FormSelectPopover as j, OPT_INPUT as jn, DialogPanel$1 as jt, FormSelect as k, OPT_FOCUS_VISIBLE as kn, DialogHeader as kt, FormCheckbox$1 as l, FieldMessages as ln, MenuItemRadio$1 as lt, FormError$1 as m, OPT_ANIMATE_ENTER_EXIT as mn, MenubarContainer$1 as mt, Highlight as n, EmptyState as nn, ToolbarSeparator$1 as nt, Pagination as o, koStarRatingLabels as on, MenuButtonArrow$1 as ot, FormDescription$1 as p, OPT_ANIMATE_DRAWER as pn, MenuTrigger$1 as pt, PopoverClose$1 as q, Kbd as qn, CompositeProvider as qt, FIELD_TYPE_TOKENS as r, Button as rn, Tooltip$1 as rt, Checkbox as s, StatCard as sn, MenuItem$1 as st, TextTruncate as t, Badge as tn, ToolbarRoot$1 as tt, FormColorPicker as u, RequiredMark as un, MenuPopover$1 as ut, FormIconPicker as v, OPT_BADGE_RED as vn, ComboboxGroupLabel$1 as vt, FormRadio$1 as w, OPT_DOT_GRAY as wn, DialogBody as wt, FormNumberInput as x, OPT_DISCLOSURE_CONTENT as xn, ComboboxPopover$1 as xt, FormInput$1 as y, OPT_BADGE_YELLOW as yn, ComboboxInput$1 as yt, useFormContext as z, OPT_TEXTAREA_SIZE as zn, TabList$1 as zt };
|
|
3849
4229
|
|
|
3850
|
-
//# sourceMappingURL=
|
|
4230
|
+
//# sourceMappingURL=text-truncate-j6X3z7Pv.js.map
|