@underverse-ui/underverse 1.0.38 → 1.0.40

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.cts CHANGED
@@ -261,7 +261,7 @@ interface AvatarProps extends React$1.HTMLAttributes<HTMLDivElement> {
261
261
  src?: string;
262
262
  alt?: string;
263
263
  fallback?: string;
264
- size?: "sm" | "md" | "lg";
264
+ size?: "sm" | "md" | "lg" | "xl";
265
265
  /** Show status indicator dot */
266
266
  showStatus?: boolean;
267
267
  /** Status type: online, offline, busy, away, none */
@@ -1629,9 +1629,18 @@ interface CategoryTreeSelectLabels {
1629
1629
  noResultsText?: string;
1630
1630
  }
1631
1631
  interface CategoryTreeSelectBaseProps {
1632
+ id?: string;
1633
+ label?: string;
1634
+ labelClassName?: string;
1632
1635
  categories: Category[];
1633
1636
  placeholder?: string;
1634
1637
  disabled?: boolean;
1638
+ required?: boolean;
1639
+ size?: "sm" | "md" | "lg";
1640
+ variant?: "default" | "outline" | "ghost" | "filled";
1641
+ allowClear?: boolean;
1642
+ error?: string;
1643
+ helperText?: string;
1635
1644
  /** When true, renders as a read-only tree view without select functionality */
1636
1645
  viewOnly?: boolean;
1637
1646
  /** Default expanded state for all nodes */
package/dist/index.d.ts CHANGED
@@ -261,7 +261,7 @@ interface AvatarProps extends React$1.HTMLAttributes<HTMLDivElement> {
261
261
  src?: string;
262
262
  alt?: string;
263
263
  fallback?: string;
264
- size?: "sm" | "md" | "lg";
264
+ size?: "sm" | "md" | "lg" | "xl";
265
265
  /** Show status indicator dot */
266
266
  showStatus?: boolean;
267
267
  /** Status type: online, offline, busy, away, none */
@@ -1629,9 +1629,18 @@ interface CategoryTreeSelectLabels {
1629
1629
  noResultsText?: string;
1630
1630
  }
1631
1631
  interface CategoryTreeSelectBaseProps {
1632
+ id?: string;
1633
+ label?: string;
1634
+ labelClassName?: string;
1632
1635
  categories: Category[];
1633
1636
  placeholder?: string;
1634
1637
  disabled?: boolean;
1638
+ required?: boolean;
1639
+ size?: "sm" | "md" | "lg";
1640
+ variant?: "default" | "outline" | "ghost" | "filled";
1641
+ allowClear?: boolean;
1642
+ error?: string;
1643
+ helperText?: string;
1635
1644
  /** When true, renders as a read-only tree view without select functionality */
1636
1645
  viewOnly?: boolean;
1637
1646
  /** Default expanded state for all nodes */
package/dist/index.js CHANGED
@@ -2619,7 +2619,8 @@ import { jsx as jsx12, jsxs as jsxs8 } from "react/jsx-runtime";
2619
2619
  var sizeClasses = {
2620
2620
  sm: "h-8 w-8 text-sm",
2621
2621
  md: "h-10 w-10 text-base",
2622
- lg: "h-14 w-14 text-lg"
2622
+ lg: "h-14 w-14 text-lg",
2623
+ xl: "h-20 w-20 text-xl"
2623
2624
  };
2624
2625
  var statusColors = {
2625
2626
  online: "bg-success",
@@ -2631,7 +2632,8 @@ var statusColors = {
2631
2632
  var statusDotSizes = {
2632
2633
  sm: "w-2 h-2 border",
2633
2634
  md: "w-3 h-3 border-2",
2634
- lg: "w-4 h-4 border-2"
2635
+ lg: "w-4 h-4 border-2",
2636
+ xl: "w-5 h-5 border-[3px]"
2635
2637
  };
2636
2638
  var Avatar = ({
2637
2639
  src,
@@ -14745,7 +14747,7 @@ function OverlayControls({
14745
14747
  }
14746
14748
 
14747
14749
  // src/components/CategoryTreeSelect.tsx
14748
- import { useEffect as useEffect22, useMemo as useMemo17, useRef as useRef17, useState as useState30 } from "react";
14750
+ import { useEffect as useEffect22, useId as useId7, useMemo as useMemo17, useRef as useRef17, useState as useState30 } from "react";
14749
14751
  import { ChevronRight as ChevronRight6, ChevronDown as ChevronDown5, FolderTree, Layers, Search as Search5, SearchX as SearchX3, X as X13 } from "lucide-react";
14750
14752
  import { Fragment as Fragment16, jsx as jsx46, jsxs as jsxs38 } from "react/jsx-runtime";
14751
14753
  var defaultLabels = {
@@ -14756,9 +14758,18 @@ var defaultLabels = {
14756
14758
  };
14757
14759
  function CategoryTreeSelect(props) {
14758
14760
  const {
14761
+ id,
14762
+ label,
14763
+ labelClassName,
14759
14764
  categories,
14760
14765
  placeholder = "Select category",
14761
14766
  disabled,
14767
+ required = false,
14768
+ size = "md",
14769
+ variant = "default",
14770
+ allowClear = false,
14771
+ error,
14772
+ helperText,
14762
14773
  viewOnly = false,
14763
14774
  defaultExpanded = false,
14764
14775
  enableSearch,
@@ -14775,6 +14786,12 @@ function CategoryTreeSelect(props) {
14775
14786
  const searchInputRef = useRef17(null);
14776
14787
  const dropdownViewportRef = useRef17(null);
14777
14788
  useOverlayScrollbarTarget(dropdownViewportRef, { enabled: useOverlayScrollbar });
14789
+ const autoId = useId7();
14790
+ const resolvedId = id ? String(id) : `category-tree-select-${autoId}`;
14791
+ const labelId = label ? `${resolvedId}-label` : void 0;
14792
+ const helperId = helperText && !error ? `${resolvedId}-helper` : void 0;
14793
+ const errorId = error ? `${resolvedId}-error` : void 0;
14794
+ const describedBy = errorId || helperId;
14778
14795
  const mergedLabels = { ...defaultLabels, ...labels };
14779
14796
  const valueArray = singleSelect ? props.value != null ? [props.value] : [] : props.value || [];
14780
14797
  const { parentCategories, childrenMap, byId } = useMemo17(() => {
@@ -14818,8 +14835,8 @@ function CategoryTreeSelect(props) {
14818
14835
  const stack = [rootId];
14819
14836
  let guard = 0;
14820
14837
  while (stack.length > 0 && guard++ < categories.length * 3) {
14821
- const id = stack.pop();
14822
- const children = childrenMap.get(id) ?? [];
14838
+ const id2 = stack.pop();
14839
+ const children = childrenMap.get(id2) ?? [];
14823
14840
  for (const child of children) {
14824
14841
  if (visible.has(child.id)) continue;
14825
14842
  visible.add(child.id);
@@ -14849,13 +14866,13 @@ function CategoryTreeSelect(props) {
14849
14866
  setExpandedNodes(new Set(allParentIds));
14850
14867
  }
14851
14868
  }, [viewOnly, inline, defaultExpanded, categories]);
14852
- const toggleExpand = (id) => {
14869
+ const toggleExpand = (id2) => {
14853
14870
  if (isSearchMode) return;
14854
14871
  const newExpanded = new Set(expandedNodes);
14855
- if (newExpanded.has(id)) {
14856
- newExpanded.delete(id);
14872
+ if (newExpanded.has(id2)) {
14873
+ newExpanded.delete(id2);
14857
14874
  } else {
14858
- newExpanded.add(id);
14875
+ newExpanded.add(id2);
14859
14876
  }
14860
14877
  setExpandedNodes(newExpanded);
14861
14878
  };
@@ -14999,29 +15016,112 @@ function CategoryTreeSelect(props) {
14999
15016
  /* @__PURE__ */ jsx46("div", { className: "w-12 h-12 rounded-2xl bg-muted/50 flex items-center justify-center mb-3", children: isSearchMode ? /* @__PURE__ */ jsx46(SearchX3, { className: "w-6 h-6 text-muted-foreground/50" }) : /* @__PURE__ */ jsx46(Layers, { className: "w-6 h-6 text-muted-foreground/50" }) }),
15000
15017
  /* @__PURE__ */ jsx46("span", { className: "text-sm text-muted-foreground", children: isSearchMode ? mergedLabels.noResultsText : mergedLabels.emptyText })
15001
15018
  ] }) : effectiveParentCategories.map((cat) => renderCategory(cat)) });
15019
+ const renderLabel = () => label ? /* @__PURE__ */ jsx46("div", { className: "flex items-center justify-between", children: /* @__PURE__ */ jsxs38(
15020
+ Label,
15021
+ {
15022
+ id: labelId,
15023
+ htmlFor: resolvedId,
15024
+ className: cn(
15025
+ size === "sm" ? "text-xs" : size === "lg" ? "text-base" : "text-sm",
15026
+ disabled ? "text-muted-foreground" : "text-foreground",
15027
+ error && "text-destructive",
15028
+ labelClassName
15029
+ ),
15030
+ children: [
15031
+ label,
15032
+ required && /* @__PURE__ */ jsx46("span", { className: "ml-1 text-destructive", children: "*" })
15033
+ ]
15034
+ }
15035
+ ) }) : null;
15036
+ const renderAssistiveText = () => error ? /* @__PURE__ */ jsx46("p", { id: errorId, className: "text-sm text-destructive", children: error }) : helperText ? /* @__PURE__ */ jsx46("p", { id: helperId, className: "text-sm text-muted-foreground", children: helperText }) : null;
15002
15037
  if (viewOnly) {
15003
- return /* @__PURE__ */ jsxs38("div", { className: cn("rounded-2xl border border-border/60 bg-card/50 backdrop-blur-sm p-3 shadow-sm", disabled && "opacity-50", className), children: [
15004
- renderSearch(),
15005
- renderTreeContent()
15038
+ return /* @__PURE__ */ jsxs38("div", { className: cn("w-full space-y-2", className), children: [
15039
+ renderLabel(),
15040
+ /* @__PURE__ */ jsxs38(
15041
+ "div",
15042
+ {
15043
+ id: resolvedId,
15044
+ "aria-labelledby": labelId,
15045
+ "aria-describedby": describedBy,
15046
+ className: cn("rounded-2xl border border-border/60 bg-card/50 backdrop-blur-sm p-3 shadow-sm", disabled && "opacity-50"),
15047
+ children: [
15048
+ renderSearch(),
15049
+ renderTreeContent()
15050
+ ]
15051
+ }
15052
+ ),
15053
+ renderAssistiveText()
15006
15054
  ] });
15007
15055
  }
15008
15056
  if (inline) {
15009
- return /* @__PURE__ */ jsxs38(
15010
- "div",
15011
- {
15012
- className: cn(
15013
- "rounded-2xl border border-border/60 bg-card/50 backdrop-blur-sm p-3 shadow-sm",
15014
- disabled && "opacity-50 pointer-events-none",
15015
- className
15016
- ),
15017
- children: [
15018
- renderSearch(),
15019
- renderTreeContent()
15020
- ]
15021
- }
15022
- );
15057
+ return /* @__PURE__ */ jsxs38("div", { className: cn("w-full space-y-2", className), children: [
15058
+ renderLabel(),
15059
+ /* @__PURE__ */ jsxs38(
15060
+ "div",
15061
+ {
15062
+ id: resolvedId,
15063
+ "aria-labelledby": labelId,
15064
+ "aria-describedby": describedBy,
15065
+ className: cn("rounded-2xl border border-border/60 bg-card/50 backdrop-blur-sm p-3 shadow-sm", disabled && "opacity-50 pointer-events-none"),
15066
+ children: [
15067
+ renderSearch(),
15068
+ renderTreeContent()
15069
+ ]
15070
+ }
15071
+ ),
15072
+ renderAssistiveText()
15073
+ ] });
15023
15074
  }
15024
15075
  const selectedCount = valueArray.length;
15076
+ const triggerSizeStyles = {
15077
+ sm: {
15078
+ button: "h-8 px-3 py-1.5 text-sm md:h-7 md:text-xs",
15079
+ iconWrap: "p-1",
15080
+ icon: "w-3.5 h-3.5 md:w-3 md:h-3",
15081
+ text: "text-xs md:text-[11px]",
15082
+ badge: "px-1.5 py-0.5 text-[10px]",
15083
+ actionIcon: "w-3.5 h-3.5",
15084
+ clearIcon: "h-3 w-3"
15085
+ },
15086
+ md: {
15087
+ button: "h-10 px-3 py-2.5 text-sm",
15088
+ iconWrap: "p-1.5",
15089
+ icon: "w-4 h-4",
15090
+ text: "text-sm",
15091
+ badge: "px-2 py-0.5 text-xs",
15092
+ actionIcon: "w-4 h-4",
15093
+ clearIcon: "h-3.5 w-3.5"
15094
+ },
15095
+ lg: {
15096
+ button: "h-12 px-4 py-3 text-base",
15097
+ iconWrap: "p-1.5",
15098
+ icon: "w-5 h-5",
15099
+ text: "text-base",
15100
+ badge: "px-2.5 py-1 text-sm",
15101
+ actionIcon: "w-5 h-5",
15102
+ clearIcon: "h-4 w-4"
15103
+ }
15104
+ };
15105
+ const triggerVariantStyles = {
15106
+ default: "border border-input bg-background shadow-sm hover:border-primary/50",
15107
+ outline: "border-2 border-input bg-transparent hover:border-primary",
15108
+ ghost: "border border-transparent bg-muted/50 hover:bg-muted",
15109
+ filled: "border border-transparent bg-muted/70 hover:bg-muted"
15110
+ };
15111
+ const clearSelection = () => {
15112
+ if (!props.onChange) return;
15113
+ if (singleSelect) {
15114
+ props.onChange(null);
15115
+ return;
15116
+ }
15117
+ props.onChange([]);
15118
+ };
15119
+ const handleClear = (event) => {
15120
+ event.preventDefault();
15121
+ event.stopPropagation();
15122
+ clearSelection();
15123
+ setIsOpen(false);
15124
+ };
15025
15125
  let displayText;
15026
15126
  if (singleSelect) {
15027
15127
  displayText = selectedCount > 0 ? categories.find((c) => c.id === valueArray[0])?.name || placeholder : placeholder;
@@ -15029,69 +15129,111 @@ function CategoryTreeSelect(props) {
15029
15129
  if (selectedCount === 0) {
15030
15130
  displayText = placeholder;
15031
15131
  } else if (selectedCount <= 3) {
15032
- const selectedNames = valueArray.map((id) => categories.find((c) => c.id === id)?.name).filter(Boolean).join(", ");
15132
+ const selectedNames = valueArray.map((id2) => categories.find((c) => c.id === id2)?.name).filter(Boolean).join(", ");
15033
15133
  displayText = selectedNames || placeholder;
15034
15134
  } else {
15035
15135
  displayText = mergedLabels.selectedText(selectedCount);
15036
15136
  }
15037
15137
  }
15038
- return /* @__PURE__ */ jsxs38("div", { className: cn("relative", className), children: [
15039
- /* @__PURE__ */ jsxs38(
15040
- "button",
15041
- {
15042
- type: "button",
15043
- onClick: () => !disabled && setIsOpen(!isOpen),
15044
- disabled,
15045
- className: cn(
15046
- // Modern trigger button styling
15047
- "group flex w-full items-center justify-between px-3 py-2.5",
15048
- "bg-background/80 backdrop-blur-sm border border-border/60",
15049
- "rounded-full h-11 text-sm",
15050
- "hover:bg-accent/10 hover:border-primary/40 hover:shadow-lg hover:shadow-primary/5 hover:-translate-y-0.5",
15051
- "transition-all duration-300 ease-out",
15052
- "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary/50 focus-visible:ring-offset-2 focus-visible:ring-offset-background",
15053
- disabled && "opacity-50 cursor-not-allowed hover:transform-none hover:shadow-none",
15054
- isOpen && "ring-2 ring-primary/30 border-primary/50 shadow-lg shadow-primary/10"
15055
- ),
15056
- children: [
15057
- /* @__PURE__ */ jsxs38("div", { className: "flex items-center gap-2.5", children: [
15058
- /* @__PURE__ */ jsx46(
15059
- "div",
15060
- {
15061
- className: cn(
15062
- "flex items-center justify-center rounded-lg p-1.5 transition-all duration-300",
15063
- isOpen ? "bg-primary/15 text-primary" : "bg-muted/50 text-muted-foreground group-hover:bg-primary/10 group-hover:text-primary"
15064
- ),
15065
- children: /* @__PURE__ */ jsx46(FolderTree, { className: cn("w-4 h-4 transition-transform duration-300", isOpen && "scale-110") })
15066
- }
15067
- ),
15068
- /* @__PURE__ */ jsx46("span", { className: cn("font-medium transition-colors duration-200", selectedCount === 0 ? "text-muted-foreground" : "text-foreground"), children: displayText }),
15069
- selectedCount > 0 && !singleSelect && /* @__PURE__ */ jsx46("span", { className: "ml-1 px-2 py-0.5 text-xs font-bold rounded-full bg-primary/15 text-primary", children: selectedCount })
15070
- ] }),
15071
- /* @__PURE__ */ jsx46("span", { className: cn("transition-all duration-300 text-muted-foreground group-hover:text-foreground", isOpen && "rotate-180 text-primary"), children: /* @__PURE__ */ jsx46(ChevronDown5, { className: "w-4 h-4" }) })
15072
- ]
15073
- }
15074
- ),
15075
- isOpen && !disabled && /* @__PURE__ */ jsxs38(Fragment16, { children: [
15076
- /* @__PURE__ */ jsx46("div", { className: "fixed inset-0 z-10", onClick: () => setIsOpen(false) }),
15138
+ return /* @__PURE__ */ jsxs38("div", { className: cn("w-full space-y-2", className), children: [
15139
+ renderLabel(),
15140
+ /* @__PURE__ */ jsxs38("div", { className: "relative", children: [
15077
15141
  /* @__PURE__ */ jsxs38(
15078
- "div",
15142
+ "button",
15079
15143
  {
15080
- ref: dropdownViewportRef,
15144
+ id: resolvedId,
15145
+ type: "button",
15146
+ onClick: () => !disabled && setIsOpen(!isOpen),
15147
+ disabled,
15148
+ role: "combobox",
15149
+ "aria-haspopup": "tree",
15150
+ "aria-expanded": isOpen,
15151
+ "aria-controls": `${resolvedId}-tree`,
15152
+ "aria-labelledby": labelId,
15153
+ "aria-describedby": describedBy,
15154
+ "aria-invalid": !!error,
15081
15155
  className: cn(
15082
- "absolute z-20 mt-2 w-full max-h-80 overflow-auto",
15083
- "rounded-2xl md:rounded-3xl border border-border/40 bg-popover/95 text-popover-foreground",
15084
- "shadow-2xl backdrop-blur-xl",
15085
- "p-2",
15086
- "animate-in fade-in-0 zoom-in-95 slide-in-from-top-2 duration-300"
15156
+ "group flex w-full items-center justify-between rounded-full transition-all duration-200",
15157
+ "backdrop-blur-sm",
15158
+ triggerSizeStyles[size].button,
15159
+ triggerVariantStyles[variant],
15160
+ "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary/50 focus-visible:ring-offset-2 focus-visible:ring-offset-background",
15161
+ disabled && "opacity-50 cursor-not-allowed hover:transform-none hover:shadow-none",
15162
+ isOpen && "ring-2 ring-primary/30 border-primary/50 shadow-lg shadow-primary/10",
15163
+ error && "border-destructive focus-visible:ring-destructive/30"
15087
15164
  ),
15088
15165
  children: [
15089
- renderSearch(),
15090
- renderTreeContent()
15166
+ /* @__PURE__ */ jsxs38("div", { className: "flex min-w-0 flex-1 items-center gap-2.5 text-left", children: [
15167
+ /* @__PURE__ */ jsx46(
15168
+ "div",
15169
+ {
15170
+ className: cn(
15171
+ "shrink-0 flex items-center justify-center rounded-lg transition-all duration-300",
15172
+ triggerSizeStyles[size].iconWrap,
15173
+ isOpen ? "bg-primary/15 text-primary" : "bg-muted/50 text-muted-foreground group-hover:bg-primary/10 group-hover:text-primary"
15174
+ ),
15175
+ children: /* @__PURE__ */ jsx46(FolderTree, { className: cn(triggerSizeStyles[size].icon, "transition-transform duration-300", isOpen && "scale-110") })
15176
+ }
15177
+ ),
15178
+ /* @__PURE__ */ jsx46(
15179
+ "span",
15180
+ {
15181
+ className: cn(
15182
+ "min-w-0 flex-1 truncate font-medium transition-colors duration-200",
15183
+ triggerSizeStyles[size].text,
15184
+ selectedCount === 0 ? "text-muted-foreground" : "text-foreground"
15185
+ ),
15186
+ title: displayText,
15187
+ children: displayText
15188
+ }
15189
+ )
15190
+ ] }),
15191
+ /* @__PURE__ */ jsxs38("div", { className: "ml-2 flex shrink-0 items-center gap-1.5", children: [
15192
+ allowClear && selectedCount > 0 && !disabled && /* @__PURE__ */ jsx46(
15193
+ "div",
15194
+ {
15195
+ role: "button",
15196
+ tabIndex: 0,
15197
+ "aria-label": "Clear selection",
15198
+ onClick: handleClear,
15199
+ onKeyDown: (event) => (event.key === "Enter" || event.key === " ") && handleClear(event),
15200
+ className: cn(
15201
+ "opacity-0 group-hover:opacity-100 transition-all duration-200",
15202
+ "p-1 rounded-lg hover:bg-destructive/10 text-muted-foreground hover:text-destructive",
15203
+ "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary/30"
15204
+ ),
15205
+ children: /* @__PURE__ */ jsx46(X13, { className: triggerSizeStyles[size].clearIcon })
15206
+ }
15207
+ ),
15208
+ selectedCount > 0 && !singleSelect && /* @__PURE__ */ jsx46("span", { className: cn("rounded-full bg-primary/15 font-bold text-primary", triggerSizeStyles[size].badge), children: selectedCount }),
15209
+ /* @__PURE__ */ jsx46("span", { className: cn("transition-all duration-300 text-muted-foreground group-hover:text-foreground", isOpen && "rotate-180 text-primary"), children: /* @__PURE__ */ jsx46(ChevronDown5, { className: triggerSizeStyles[size].actionIcon }) })
15210
+ ] })
15091
15211
  ]
15092
15212
  }
15093
- )
15094
- ] })
15213
+ ),
15214
+ isOpen && !disabled && /* @__PURE__ */ jsxs38(Fragment16, { children: [
15215
+ /* @__PURE__ */ jsx46("div", { className: "fixed inset-0 z-10", onClick: () => setIsOpen(false) }),
15216
+ /* @__PURE__ */ jsxs38(
15217
+ "div",
15218
+ {
15219
+ ref: dropdownViewportRef,
15220
+ id: `${resolvedId}-tree`,
15221
+ className: cn(
15222
+ "absolute z-20 mt-2 w-full max-h-80 overflow-auto",
15223
+ "rounded-2xl md:rounded-3xl border border-border/40 bg-popover/95 text-popover-foreground",
15224
+ "shadow-2xl backdrop-blur-xl",
15225
+ "p-2",
15226
+ "animate-in fade-in-0 zoom-in-95 slide-in-from-top-2 duration-300"
15227
+ ),
15228
+ children: [
15229
+ renderSearch(),
15230
+ renderTreeContent()
15231
+ ]
15232
+ }
15233
+ )
15234
+ ] })
15235
+ ] }),
15236
+ renderAssistiveText()
15095
15237
  ] });
15096
15238
  }
15097
15239
 
@@ -18050,7 +18192,7 @@ var MusicPlayer = ({
18050
18192
  var MusicPlayer_default = MusicPlayer;
18051
18193
 
18052
18194
  // src/components/Grid.tsx
18053
- import React49, { useId as useId7 } from "react";
18195
+ import React49, { useId as useId8 } from "react";
18054
18196
  import { Fragment as Fragment21, jsx as jsx56, jsxs as jsxs48 } from "react/jsx-runtime";
18055
18197
  var BP_MIN = {
18056
18198
  sm: 640,
@@ -18115,7 +18257,7 @@ var GridRoot = React49.forwardRef(
18115
18257
  children,
18116
18258
  ...rest
18117
18259
  }, ref) => {
18118
- const id = useId7().replace(/[:]/g, "");
18260
+ const id = useId8().replace(/[:]/g, "");
18119
18261
  const baseClass = `uv-grid-${id}`;
18120
18262
  const baseCols = toTemplateCols(columns, minColumnWidth);
18121
18263
  const baseRows = toTemplateRows(rows);