@underverse-ui/underverse 1.0.97 → 1.0.99

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/README.md CHANGED
@@ -109,27 +109,26 @@ All components follow [Vercel Web Interface Guidelines](https://github.com/verce
109
109
 
110
110
  ---
111
111
 
112
- ## Entry Points
112
+ ## Package Exports
113
113
 
114
- Package được chia thành 2 entry points để tối ưu cho Server Components:
115
-
116
- ### Main Entry (Server-safe)
114
+ Hiện tại package publish **một public entry point duy nhất**:
117
115
 
118
116
  ```tsx
119
- // dist/index.js - Các components không phụ thuộc react-hook-form
120
- // Có thể sử dụng trong cả Server Components và Client Components
121
- import { Button, Skeleton, DatePicker, DataTable } from "@underverse-ui/underverse";
117
+ import {
118
+ Button,
119
+ DataTable,
120
+ Form,
121
+ FormField,
122
+ UEditor,
123
+ } from "@underverse-ui/underverse";
122
124
  ```
123
125
 
124
- ### Form Entry (Client-only)
125
-
126
- ```tsx
127
- // dist/form.js - Form components (phụ thuộc react-hook-form)
128
- // Chỉ sử dụng trong Client Components ("use client")
129
- import { Form, FormField, FormItem, FormLabel, FormMessage } from "@underverse-ui/underverse/form";
130
- ```
126
+ Không subpath export như `@underverse-ui/underverse/form` ở version hiện tại.
131
127
 
132
- **Lưu ý:** Form components yêu cầu `react-hook-form` và `@hookform/resolvers` nên chỉ hoạt động ở client-side.
128
+ Lưu ý:
129
+ - Nhiều component trong package là client component và nên dùng trong môi trường React client.
130
+ - Form primitives yêu cầu `react-hook-form` và `@hookform/resolvers`.
131
+ - `UEditor` và các component dựa trên Tiptap yêu cầu peer dependencies tương ứng.
133
132
 
134
133
  ---
135
134
 
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "package": "@underverse-ui/underverse",
3
- "version": "1.0.97",
3
+ "version": "1.0.99",
4
4
  "sourceEntry": "src/index.ts",
5
5
  "totalExports": 225,
6
6
  "exports": [
@@ -361,7 +361,7 @@
361
361
  {
362
362
  "name": "cn",
363
363
  "kind": "value",
364
- "source": "../../../lib/utils/cn",
364
+ "source": "./utils/cn",
365
365
  "reexport": true,
366
366
  "local": false
367
367
  },
@@ -1487,14 +1487,14 @@
1487
1487
  {
1488
1488
  "name": "UnderverseProvider",
1489
1489
  "kind": "value",
1490
- "source": "../../../lib/i18n/translation-adapter",
1490
+ "source": "./contexts/translation-adapter",
1491
1491
  "reexport": true,
1492
1492
  "local": false
1493
1493
  },
1494
1494
  {
1495
1495
  "name": "UnderverseProviderProps",
1496
1496
  "kind": "type",
1497
- "source": "../../../lib/i18n/translation-adapter",
1497
+ "source": "./contexts/translation-adapter",
1498
1498
  "reexport": true,
1499
1499
  "local": false
1500
1500
  },
@@ -1557,7 +1557,7 @@
1557
1557
  {
1558
1558
  "name": "useUnderverseI18n",
1559
1559
  "kind": "value",
1560
- "source": "../../../lib/i18n/translation-adapter",
1560
+ "source": "./contexts/translation-adapter",
1561
1561
  "reexport": true,
1562
1562
  "local": false,
1563
1563
  "aliasOf": "useTranslations"
@@ -1565,7 +1565,7 @@
1565
1565
  {
1566
1566
  "name": "useUnderverseI18nLocale",
1567
1567
  "kind": "value",
1568
- "source": "../../../lib/i18n/translation-adapter",
1568
+ "source": "./contexts/translation-adapter",
1569
1569
  "reexport": true,
1570
1570
  "local": false,
1571
1571
  "aliasOf": "useLocale"
@@ -1587,7 +1587,7 @@
1587
1587
  {
1588
1588
  "name": "VARIANT_STYLES_ALERT",
1589
1589
  "kind": "value",
1590
- "source": "../../../lib/constants/constants-ui/alert",
1590
+ "source": "./constants/alert",
1591
1591
  "reexport": true,
1592
1592
  "local": false
1593
1593
  },
package/dist/index.cjs CHANGED
@@ -171,7 +171,7 @@ __export(index_exports, {
171
171
  VIETNAM_HOLIDAYS: () => VIETNAM_HOLIDAYS,
172
172
  VerticalTabs: () => VerticalTabs,
173
173
  Watermark: () => Watermark_default,
174
- cn: () => cn2,
174
+ cn: () => cn,
175
175
  cnLocal: () => cn,
176
176
  extractImageSrcsFromHtml: () => extractImageSrcsFromHtml,
177
177
  getAnimationStyles: () => getAnimationStyles,
@@ -2000,8 +2000,9 @@ function useSmartTranslations(namespace) {
2000
2000
  return internalT;
2001
2001
  }
2002
2002
  return (key) => {
2003
- const primaryLocale = nextIntlBridge?.locale ?? internalLocale;
2004
- const fallbackLocale = environmentLocale && environmentLocale !== primaryLocale ? environmentLocale : null;
2003
+ const resolvedEnvironmentLocale = environmentLocale && environmentLocale !== internalLocale ? environmentLocale : null;
2004
+ const primaryLocale = nextIntlBridge?.locale ?? resolvedEnvironmentLocale ?? internalLocale;
2005
+ const fallbackLocale = resolvedEnvironmentLocale && resolvedEnvironmentLocale !== primaryLocale ? resolvedEnvironmentLocale : null;
2005
2006
  let translated = null;
2006
2007
  if (nextIntlBridge) {
2007
2008
  const nextIntlResult = nextIntlBridge.translate(namespace, key);
@@ -2033,10 +2034,20 @@ function useSmartLocale() {
2033
2034
  const forceInternal = React6.useContext(ForceInternalContext);
2034
2035
  const nextIntlBridge = useNextIntlBridge();
2035
2036
  const internalLocale = useUnderverseLocale();
2037
+ const [environmentLocale, setEnvironmentLocale] = React6.useState(null);
2038
+ React6.useEffect(() => {
2039
+ if (forceInternal) return;
2040
+ if (nextIntlBridge) return;
2041
+ if (internalLocale !== "en") return;
2042
+ const detected = getEnvironmentLocale(internalLocale);
2043
+ if (detected !== internalLocale) {
2044
+ setEnvironmentLocale(detected);
2045
+ }
2046
+ }, [forceInternal, internalLocale, nextIntlBridge]);
2036
2047
  if (forceInternal) {
2037
2048
  return internalLocale;
2038
2049
  }
2039
- return nextIntlBridge?.locale ?? internalLocale;
2050
+ return nextIntlBridge?.locale ?? environmentLocale ?? internalLocale;
2040
2051
  }
2041
2052
 
2042
2053
  // src/components/Input.tsx
@@ -7381,9 +7392,10 @@ var Combobox = ({
7381
7392
  {
7382
7393
  id: `combobox-item-${index}`,
7383
7394
  type: "button",
7395
+ role: "option",
7384
7396
  tabIndex: -1,
7385
7397
  disabled: itemDisabled,
7386
- "aria-pressed": isSelected,
7398
+ "aria-selected": isSelected,
7387
7399
  onClick: () => handleSelect(item),
7388
7400
  style: {
7389
7401
  animationDelay: open ? `${Math.min(index * 15, 150)}ms` : "0ms"
@@ -7488,40 +7500,50 @@ var Combobox = ({
7488
7500
  }
7489
7501
  )
7490
7502
  ] }) }),
7491
- /* @__PURE__ */ (0, import_jsx_runtime29.jsx)("div", { ref: optionsViewportRef, className: "overflow-y-auto overscroll-contain", style: { maxHeight }, children: /* @__PURE__ */ (0, import_jsx_runtime29.jsx)("div", { className: cn(size === "sm" ? "p-1" : size === "lg" ? "p-2" : "p-1.5"), children: loading2 ? /* @__PURE__ */ (0, import_jsx_runtime29.jsx)("div", { className: "px-3 py-10 text-center", children: /* @__PURE__ */ (0, import_jsx_runtime29.jsxs)("div", { className: "flex flex-col items-center gap-3 animate-in fade-in-0 zoom-in-95 duration-300", children: [
7492
- /* @__PURE__ */ (0, import_jsx_runtime29.jsx)("div", { className: "relative", children: /* @__PURE__ */ (0, import_jsx_runtime29.jsx)("div", { className: "w-10 h-10 rounded-full border-2 border-primary/20 border-t-primary animate-spin" }) }),
7493
- /* @__PURE__ */ (0, import_jsx_runtime29.jsx)("span", { className: "text-sm text-muted-foreground", children: loadingText })
7494
- ] }) }) : filteredOptions.length > 0 ? groupedOptions ? (
7495
- // Render grouped options with global index tracking
7496
- (() => {
7497
- let globalIndex = 0;
7498
- return Object.entries(groupedOptions).map(([group, items]) => /* @__PURE__ */ (0, import_jsx_runtime29.jsxs)("div", { className: cn(globalIndex > 0 && "mt-2 pt-2 border-t border-border/30"), children: [
7499
- /* @__PURE__ */ (0, import_jsx_runtime29.jsx)("div", { className: "px-3 py-1.5 text-xs font-semibold text-muted-foreground uppercase tracking-wider", children: group }),
7500
- /* @__PURE__ */ (0, import_jsx_runtime29.jsx)("ul", { className: "space-y-0.5", children: items.map((item) => {
7501
- const index = globalIndex++;
7502
- return renderOptionItem(item, index);
7503
- }) })
7504
- ] }, group));
7505
- })()
7506
- ) : (
7507
- // Render flat options
7508
- /* @__PURE__ */ (0, import_jsx_runtime29.jsx)("ul", { className: "space-y-0.5", children: filteredOptions.map((item, index) => renderOptionItem(item, index)) })
7509
- ) : /* @__PURE__ */ (0, import_jsx_runtime29.jsx)("div", { className: "px-3 py-10 text-center", children: /* @__PURE__ */ (0, import_jsx_runtime29.jsxs)("div", { className: "flex flex-col items-center gap-3 animate-in fade-in-0 zoom-in-95 duration-300", children: [
7510
- /* @__PURE__ */ (0, import_jsx_runtime29.jsx)("div", { className: "w-12 h-12 rounded-full bg-muted/50 flex items-center justify-center", children: /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(import_lucide_react13.SearchX, { className: "h-6 w-6 text-muted-foreground/60" }) }),
7511
- /* @__PURE__ */ (0, import_jsx_runtime29.jsxs)("div", { className: "space-y-1", children: [
7512
- /* @__PURE__ */ (0, import_jsx_runtime29.jsx)("span", { className: "block text-sm font-medium text-foreground", children: emptyText }),
7513
- query && /* @__PURE__ */ (0, import_jsx_runtime29.jsx)("span", { className: "block text-xs text-muted-foreground", children: "Try a different search term" })
7514
- ] }),
7515
- query && /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(
7516
- "button",
7517
- {
7518
- type: "button",
7519
- onClick: () => setQuery(""),
7520
- className: "px-3 py-1.5 text-xs font-medium text-primary bg-primary/10 rounded-full hover:bg-primary/20 transition-colors",
7521
- children: "Clear search"
7522
- }
7523
- )
7524
- ] }) }) }) })
7503
+ /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(
7504
+ "div",
7505
+ {
7506
+ ref: optionsViewportRef,
7507
+ role: "listbox",
7508
+ "aria-labelledby": labelId,
7509
+ className: "overflow-y-auto overscroll-contain",
7510
+ style: { maxHeight },
7511
+ children: /* @__PURE__ */ (0, import_jsx_runtime29.jsx)("div", { className: cn(size === "sm" ? "p-1" : size === "lg" ? "p-2" : "p-1.5"), children: loading2 ? /* @__PURE__ */ (0, import_jsx_runtime29.jsx)("div", { className: "px-3 py-10 text-center", children: /* @__PURE__ */ (0, import_jsx_runtime29.jsxs)("div", { className: "flex flex-col items-center gap-3 animate-in fade-in-0 zoom-in-95 duration-300", children: [
7512
+ /* @__PURE__ */ (0, import_jsx_runtime29.jsx)("div", { className: "relative", children: /* @__PURE__ */ (0, import_jsx_runtime29.jsx)("div", { className: "w-10 h-10 rounded-full border-2 border-primary/20 border-t-primary animate-spin" }) }),
7513
+ /* @__PURE__ */ (0, import_jsx_runtime29.jsx)("span", { className: "text-sm text-muted-foreground", children: loadingText })
7514
+ ] }) }) : filteredOptions.length > 0 ? groupedOptions ? (
7515
+ // Render grouped options with global index tracking
7516
+ (() => {
7517
+ let globalIndex = 0;
7518
+ return Object.entries(groupedOptions).map(([group, items]) => /* @__PURE__ */ (0, import_jsx_runtime29.jsxs)("div", { className: cn(globalIndex > 0 && "mt-2 pt-2 border-t border-border/30"), children: [
7519
+ /* @__PURE__ */ (0, import_jsx_runtime29.jsx)("div", { className: "px-3 py-1.5 text-xs font-semibold text-muted-foreground uppercase tracking-wider", children: group }),
7520
+ /* @__PURE__ */ (0, import_jsx_runtime29.jsx)("ul", { className: "space-y-0.5", children: items.map((item) => {
7521
+ const index = globalIndex++;
7522
+ return renderOptionItem(item, index);
7523
+ }) })
7524
+ ] }, group));
7525
+ })()
7526
+ ) : (
7527
+ // Render flat options
7528
+ /* @__PURE__ */ (0, import_jsx_runtime29.jsx)("ul", { className: "space-y-0.5", children: filteredOptions.map((item, index) => renderOptionItem(item, index)) })
7529
+ ) : /* @__PURE__ */ (0, import_jsx_runtime29.jsx)("div", { className: "px-3 py-10 text-center", children: /* @__PURE__ */ (0, import_jsx_runtime29.jsxs)("div", { className: "flex flex-col items-center gap-3 animate-in fade-in-0 zoom-in-95 duration-300", children: [
7530
+ /* @__PURE__ */ (0, import_jsx_runtime29.jsx)("div", { className: "w-12 h-12 rounded-full bg-muted/50 flex items-center justify-center", children: /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(import_lucide_react13.SearchX, { className: "h-6 w-6 text-muted-foreground/60" }) }),
7531
+ /* @__PURE__ */ (0, import_jsx_runtime29.jsxs)("div", { className: "space-y-1", children: [
7532
+ /* @__PURE__ */ (0, import_jsx_runtime29.jsx)("span", { className: "block text-sm font-medium text-foreground", children: emptyText }),
7533
+ query && /* @__PURE__ */ (0, import_jsx_runtime29.jsx)("span", { className: "block text-xs text-muted-foreground", children: "Try a different search term" })
7534
+ ] }),
7535
+ query && /* @__PURE__ */ (0, import_jsx_runtime29.jsx)(
7536
+ "button",
7537
+ {
7538
+ type: "button",
7539
+ onClick: () => setQuery(""),
7540
+ className: "px-3 py-1.5 text-xs font-medium text-primary bg-primary/10 rounded-full hover:bg-primary/20 transition-colors",
7541
+ children: "Clear search"
7542
+ }
7543
+ )
7544
+ ] }) }) })
7545
+ }
7546
+ )
7525
7547
  ]
7526
7548
  }
7527
7549
  );
@@ -17119,15 +17141,83 @@ var TREE_NODE_INDENT_REM = 1;
17119
17141
  var TREE_BRANCH_OFFSET_CLASS = "ml-1.5 pl-1.5";
17120
17142
  var TREE_NODE_GAP_CLASS = "gap-1.5";
17121
17143
  var TREE_EXPANDER_PLACEHOLDER_CLASS = "w-5";
17122
- function getInitialExpandedNodes(categories, defaultExpanded, viewOnly, inline) {
17123
- if (!(viewOnly || inline) || !defaultExpanded) return /* @__PURE__ */ new Set();
17124
- const parentIds = /* @__PURE__ */ new Set();
17125
- for (const category of categories) {
17126
- if (typeof category.parent_id === "number") {
17127
- parentIds.add(category.parent_id);
17144
+ function getAncestorPathIds(categories, targetId) {
17145
+ const byId = new Map(categories.map((category) => [category.id, category]));
17146
+ const expanded = /* @__PURE__ */ new Set();
17147
+ let current = byId.get(targetId);
17148
+ let guard = 0;
17149
+ while (current && guard++ < categories.length) {
17150
+ expanded.add(current.id);
17151
+ if (typeof current.parent_id !== "number") break;
17152
+ current = byId.get(current.parent_id);
17153
+ }
17154
+ return expanded;
17155
+ }
17156
+ function getInitialExpandedNodes(categories, {
17157
+ defaultExpanded,
17158
+ defaultExpandedIds,
17159
+ expandToId,
17160
+ viewOnly,
17161
+ inline
17162
+ }) {
17163
+ const expanded = /* @__PURE__ */ new Set();
17164
+ if ((viewOnly || inline) && defaultExpanded) {
17165
+ for (const category of categories) {
17166
+ if (typeof category.parent_id === "number") {
17167
+ expanded.add(category.parent_id);
17168
+ }
17169
+ }
17170
+ }
17171
+ for (const id of defaultExpandedIds ?? []) {
17172
+ if (typeof id === "number") {
17173
+ expanded.add(id);
17128
17174
  }
17129
17175
  }
17130
- return parentIds;
17176
+ if (typeof expandToId === "number") {
17177
+ for (const id of getAncestorPathIds(categories, expandToId)) {
17178
+ expanded.add(id);
17179
+ }
17180
+ }
17181
+ return expanded;
17182
+ }
17183
+ function getExpandedNodesState(expandedIds, uncontrolledExpandedNodes) {
17184
+ return expandedIds !== void 0 ? new Set(expandedIds) : uncontrolledExpandedNodes;
17185
+ }
17186
+ function collectAncestorIds(categories, categoryId) {
17187
+ const ancestorIds = getAncestorPathIds(categories, categoryId);
17188
+ ancestorIds.delete(categoryId);
17189
+ return ancestorIds;
17190
+ }
17191
+ function collectDescendantIds(childrenMap, categoryId) {
17192
+ const descendants = /* @__PURE__ */ new Set();
17193
+ const stack = [categoryId];
17194
+ while (stack.length > 0) {
17195
+ const currentId = stack.pop();
17196
+ for (const child of childrenMap.get(currentId) ?? []) {
17197
+ if (descendants.has(child.id)) continue;
17198
+ descendants.add(child.id);
17199
+ stack.push(child.id);
17200
+ }
17201
+ }
17202
+ return descendants;
17203
+ }
17204
+ function pruneAncestorSelection(categories, childrenMap, selected, fromCategoryId) {
17205
+ const byId = new Map(categories.map((category) => [category.id, category]));
17206
+ let current = byId.get(fromCategoryId);
17207
+ let guard = 0;
17208
+ while (current && typeof current.parent_id === "number" && guard++ < categories.length) {
17209
+ const parent = byId.get(current.parent_id);
17210
+ if (!parent) break;
17211
+ const descendantIds = collectDescendantIds(childrenMap, parent.id);
17212
+ const hasSelectedDescendant = Array.from(descendantIds).some((id) => selected.has(id));
17213
+ if (!hasSelectedDescendant) {
17214
+ selected.delete(parent.id);
17215
+ }
17216
+ current = parent;
17217
+ }
17218
+ }
17219
+ function toCategoryOrderSelection(categories, selected) {
17220
+ return categories.map((category) => category.id).filter((categoryId) => selected.has(categoryId));
17131
17221
  }
17132
17222
  function CategoryTreeSelect(props) {
17133
17223
  const tv = useSmartTranslations("ValidationInput");
@@ -17146,6 +17236,10 @@ function CategoryTreeSelect(props) {
17146
17236
  helperText,
17147
17237
  viewOnly = false,
17148
17238
  defaultExpanded = false,
17239
+ defaultExpandedIds,
17240
+ expandToId = null,
17241
+ expandedIds,
17242
+ onExpandedChange,
17149
17243
  enableSearch,
17150
17244
  labels,
17151
17245
  inline = false,
@@ -17156,7 +17250,9 @@ function CategoryTreeSelect(props) {
17156
17250
  singleSelect = false
17157
17251
  } = props;
17158
17252
  const [isOpen, setIsOpen] = (0, import_react22.useState)(false);
17159
- const [expandedNodes, setExpandedNodes] = (0, import_react22.useState)(() => getInitialExpandedNodes(categories, defaultExpanded, viewOnly, inline));
17253
+ const [expandedNodes, setExpandedNodes] = (0, import_react22.useState)(
17254
+ () => getInitialExpandedNodes(categories, { defaultExpanded, defaultExpandedIds, expandToId, viewOnly, inline })
17255
+ );
17160
17256
  const [query, setQuery] = (0, import_react22.useState)("");
17161
17257
  const [localRequiredError, setLocalRequiredError] = (0, import_react22.useState)();
17162
17258
  const searchInputRef = (0, import_react22.useRef)(null);
@@ -17193,6 +17289,7 @@ function CategoryTreeSelect(props) {
17193
17289
  const isSearchEnabled = (0, import_react22.useMemo)(() => enableSearch ?? categories.length > 10, [enableSearch, categories.length]);
17194
17290
  const normalizedQuery = (0, import_react22.useMemo)(() => query.trim().toLowerCase(), [query]);
17195
17291
  const isSearchMode = isSearchEnabled && normalizedQuery.length > 0;
17292
+ const effectiveExpandedNodes = (0, import_react22.useMemo)(() => getExpandedNodesState(expandedIds, expandedNodes), [expandedIds, expandedNodes]);
17196
17293
  const visibleIds = (0, import_react22.useMemo)(() => {
17197
17294
  if (!isSearchMode) return null;
17198
17295
  const matches = categories.filter((c) => c.name.toLowerCase().includes(normalizedQuery));
@@ -17245,13 +17342,16 @@ function CategoryTreeSelect(props) {
17245
17342
  }, [disabled, required, valueArray.length]);
17246
17343
  const toggleExpand = (id2) => {
17247
17344
  if (isSearchMode) return;
17248
- const newExpanded = new Set(expandedNodes);
17345
+ const newExpanded = new Set(effectiveExpandedNodes);
17249
17346
  if (newExpanded.has(id2)) {
17250
17347
  newExpanded.delete(id2);
17251
17348
  } else {
17252
17349
  newExpanded.add(id2);
17253
17350
  }
17254
- setExpandedNodes(newExpanded);
17351
+ if (expandedIds === void 0) {
17352
+ setExpandedNodes(newExpanded);
17353
+ }
17354
+ onExpandedChange?.(Array.from(newExpanded));
17255
17355
  };
17256
17356
  const handleSelect = (categoryId, category) => {
17257
17357
  if (viewOnly) return;
@@ -17279,21 +17379,23 @@ function CategoryTreeSelect(props) {
17279
17379
  const newSelected = new Set(valueArray);
17280
17380
  if (newSelected.has(categoryId)) {
17281
17381
  newSelected.delete(categoryId);
17282
- const children = childrenMap.get(categoryId) || [];
17283
- children.forEach((child) => newSelected.delete(child.id));
17382
+ for (const descendantId of collectDescendantIds(childrenMap, categoryId)) {
17383
+ newSelected.delete(descendantId);
17384
+ }
17385
+ pruneAncestorSelection(categories, childrenMap, newSelected, categoryId);
17284
17386
  } else {
17285
17387
  newSelected.add(categoryId);
17286
- if (category.parent_id) {
17287
- newSelected.add(category.parent_id);
17388
+ for (const ancestorId of collectAncestorIds(categories, categoryId)) {
17389
+ newSelected.add(ancestorId);
17288
17390
  }
17289
17391
  }
17290
- onChange(Array.from(newSelected));
17392
+ onChange(toCategoryOrderSelection(categories, newSelected));
17291
17393
  }
17292
17394
  };
17293
17395
  const renderCategory = (category, level = 0) => {
17294
17396
  const children = effectiveChildrenMap.get(category.id) || [];
17295
17397
  const hasChildren = children.length > 0;
17296
- const isExpanded = hasChildren && (isSearchMode || expandedNodes.has(category.id));
17398
+ const isExpanded = hasChildren && (isSearchMode || effectiveExpandedNodes.has(category.id));
17297
17399
  const isSelected = selectedIds.has(category.id);
17298
17400
  const isSelectable = !viewOnly && (!leafOnlySelect || !hasChildren);
17299
17401
  return /* @__PURE__ */ (0, import_jsx_runtime49.jsxs)(
@@ -17641,11 +17743,10 @@ function CategoryTreeSelect(props) {
17641
17743
  "shadow-2xl backdrop-blur-xl"
17642
17744
  ),
17643
17745
  trigger: /* @__PURE__ */ (0, import_jsx_runtime49.jsxs)(
17644
- "button",
17746
+ "div",
17645
17747
  {
17646
17748
  id: resolvedId,
17647
- type: "button",
17648
- disabled,
17749
+ tabIndex: disabled ? -1 : 0,
17649
17750
  role: "combobox",
17650
17751
  "aria-haspopup": "tree",
17651
17752
  "aria-expanded": isOpen,
@@ -17654,6 +17755,13 @@ function CategoryTreeSelect(props) {
17654
17755
  "aria-describedby": describedBy,
17655
17756
  "aria-required": required,
17656
17757
  "aria-invalid": !!effectiveError,
17758
+ onKeyDown: (event) => {
17759
+ if (disabled) return;
17760
+ if (event.key === "Enter" || event.key === " " || event.key === "ArrowDown") {
17761
+ event.preventDefault();
17762
+ handleOpenChange(!isOpen);
17763
+ }
17764
+ },
17657
17765
  className: cn(
17658
17766
  "group flex w-full items-center justify-between rounded-full transition-all duration-200",
17659
17767
  "backdrop-blur-sm",
@@ -17692,13 +17800,11 @@ function CategoryTreeSelect(props) {
17692
17800
  ] }),
17693
17801
  /* @__PURE__ */ (0, import_jsx_runtime49.jsxs)("div", { className: "ml-2 flex shrink-0 items-center gap-1.5", children: [
17694
17802
  allowClear && selectedCount > 0 && !disabled && /* @__PURE__ */ (0, import_jsx_runtime49.jsx)(
17695
- "div",
17803
+ "button",
17696
17804
  {
17697
- role: "button",
17698
- tabIndex: 0,
17805
+ type: "button",
17699
17806
  "aria-label": "Clear selection",
17700
17807
  onClick: handleClear,
17701
- onKeyDown: (event) => (event.key === "Enter" || event.key === " ") && handleClear(event),
17702
17808
  className: cn(
17703
17809
  "opacity-0 group-hover:opacity-100 transition-all duration-200",
17704
17810
  "p-1 rounded-lg hover:bg-destructive/10 text-muted-foreground hover:text-destructive",
@@ -21405,6 +21511,18 @@ function DataTableBodyRows({
21405
21511
  var import_react30 = __toESM(require("react"), 1);
21406
21512
  var import_lucide_react35 = require("lucide-react");
21407
21513
  var import_jsx_runtime64 = require("react/jsx-runtime");
21514
+ function getColumnLabel(title) {
21515
+ if (typeof title === "string" || typeof title === "number") {
21516
+ return String(title).replace(/\s+/g, " ").trim();
21517
+ }
21518
+ if (Array.isArray(title)) {
21519
+ return title.map((item) => getColumnLabel(item)).filter(Boolean).join(" ").replace(/\s+/g, " ").trim();
21520
+ }
21521
+ if (import_react30.default.isValidElement(title)) {
21522
+ return getColumnLabel(title.props.children);
21523
+ }
21524
+ return "";
21525
+ }
21408
21526
  function DataTableHeader({
21409
21527
  headerRows,
21410
21528
  headerAlign,
@@ -21429,12 +21547,13 @@ function DataTableHeader({
21429
21547
  if (!col.filter) return null;
21430
21548
  const key = col.key;
21431
21549
  const commonProps = { className: "w-full", size };
21550
+ const columnLabel = getColumnLabel(col.title) || key;
21432
21551
  if (col.filter.type === "text") {
21433
21552
  return /* @__PURE__ */ (0, import_jsx_runtime64.jsx)(
21434
21553
  Input_default,
21435
21554
  {
21436
21555
  ...commonProps,
21437
- placeholder: col.filter.placeholder || `Search ${String(col.title)}`,
21556
+ placeholder: col.filter.placeholder || `Search ${columnLabel}`,
21438
21557
  value: filters[key] || "",
21439
21558
  onChange: (e) => {
21440
21559
  setCurPage(1);
@@ -21455,7 +21574,7 @@ function DataTableHeader({
21455
21574
  setCurPage(1);
21456
21575
  setFilters((prev) => ({ ...prev, [key]: value || void 0 }));
21457
21576
  },
21458
- placeholder: col.filter.placeholder || `Select ${String(col.title)}`
21577
+ placeholder: col.filter.placeholder || `Select ${columnLabel}`
21459
21578
  }
21460
21579
  );
21461
21580
  }
@@ -21464,7 +21583,7 @@ function DataTableHeader({
21464
21583
  DatePicker,
21465
21584
  {
21466
21585
  size,
21467
- placeholder: col.filter.placeholder || `Select ${String(col.title)}`,
21586
+ placeholder: col.filter.placeholder || `Select ${columnLabel}`,
21468
21587
  value: filters[key] || null,
21469
21588
  onChange: (date) => {
21470
21589
  setCurPage(1);
@@ -21496,16 +21615,19 @@ function DataTableHeader({
21496
21615
  }
21497
21616
  const isRightAlign = col.align === "right" || !col.align && headerAlign === "right";
21498
21617
  const isCenterAlign = col.align === "center" || !col.align && headerAlign === "center";
21618
+ const columnLabel = getColumnLabel(col.title) || col.key;
21499
21619
  const titleContent = /* @__PURE__ */ (0, import_jsx_runtime64.jsxs)("div", { className: "flex items-center gap-1", children: [
21500
21620
  /* @__PURE__ */ (0, import_jsx_runtime64.jsx)("span", { className: cn("font-medium whitespace-nowrap select-text", headerTitleClass), children: col.title }),
21501
21621
  col.sortable && /* @__PURE__ */ (0, import_jsx_runtime64.jsx)(
21502
21622
  Tooltip,
21503
21623
  {
21504
21624
  placement: "top",
21505
- content: /* @__PURE__ */ (0, import_jsx_runtime64.jsx)("span", { className: "text-xs font-medium", children: `Sort by ${String(col.title)}` }),
21625
+ content: /* @__PURE__ */ (0, import_jsx_runtime64.jsx)("span", { className: "text-xs font-medium", children: `Sort by ${columnLabel}` }),
21506
21626
  children: /* @__PURE__ */ (0, import_jsx_runtime64.jsx)(
21507
21627
  "button",
21508
21628
  {
21629
+ type: "button",
21630
+ title: `Sort by ${columnLabel}`,
21509
21631
  className: cn(
21510
21632
  "p-1 rounded-lg transition-all duration-200 hover:bg-accent",
21511
21633
  sort?.key === col.key ? "opacity-100 bg-accent" : "opacity-60 hover:opacity-100"
@@ -21518,7 +21640,7 @@ function DataTableHeader({
21518
21640
  return null;
21519
21641
  });
21520
21642
  },
21521
- "aria-label": "Sort",
21643
+ "aria-label": `Sort by ${columnLabel}`,
21522
21644
  children: /* @__PURE__ */ (0, import_jsx_runtime64.jsxs)("svg", { viewBox: "0 0 20 20", fill: "none", className: cn("inline-block", sortIconClass), children: [
21523
21645
  /* @__PURE__ */ (0, import_jsx_runtime64.jsx)(
21524
21646
  "path",
@@ -21556,15 +21678,17 @@ function DataTableHeader({
21556
21678
  Tooltip,
21557
21679
  {
21558
21680
  placement: "top",
21559
- content: /* @__PURE__ */ (0, import_jsx_runtime64.jsx)("span", { className: "text-xs font-medium", children: `Filter by ${String(col.title)}` }),
21681
+ content: /* @__PURE__ */ (0, import_jsx_runtime64.jsx)("span", { className: "text-xs font-medium", children: `Filter by ${columnLabel}` }),
21560
21682
  children: /* @__PURE__ */ (0, import_jsx_runtime64.jsx)(
21561
21683
  "button",
21562
21684
  {
21685
+ type: "button",
21686
+ title: `Filter by ${columnLabel}`,
21563
21687
  className: cn(
21564
21688
  "p-1.5 rounded-lg transition-all duration-200 hover:bg-accent",
21565
21689
  filters[col.key] ? "bg-accent text-primary" : "text-muted-foreground"
21566
21690
  ),
21567
- "aria-label": "Filter",
21691
+ "aria-label": `Filter by ${columnLabel}`,
21568
21692
  children: /* @__PURE__ */ (0, import_jsx_runtime64.jsx)(import_lucide_react35.Filter, { className: "w-4 h-4" })
21569
21693
  }
21570
21694
  )
@@ -23080,16 +23204,8 @@ function LanguageSwitcherHeadless({
23080
23204
  ] });
23081
23205
  }
23082
23206
 
23083
- // ../../lib/utils/cn.ts
23084
- var import_clsx2 = require("clsx");
23085
- var import_tailwind_merge2 = require("tailwind-merge");
23086
- function cn2(...inputs) {
23087
- return (0, import_tailwind_merge2.twMerge)((0, import_clsx2.clsx)(...inputs));
23088
- }
23089
-
23090
- // ../../lib/constants/constants-ui/alert.ts
23207
+ // src/constants/alert.ts
23091
23208
  var VARIANT_STYLES_ALERT = {
23092
- // Use system colors for background + border; leave text colors to content
23093
23209
  default: "border border-border bg-card/60 backdrop-blur-sm",
23094
23210
  info: "border border-info/30 bg-info/10 backdrop-blur-sm",
23095
23211
  success: "border border-success/30 bg-success/10 backdrop-blur-sm",
@@ -23097,7 +23213,7 @@ var VARIANT_STYLES_ALERT = {
23097
23213
  error: "border border-destructive/30 bg-destructive/10 backdrop-blur-sm"
23098
23214
  };
23099
23215
 
23100
- // ../../lib/i18n/translation-adapter.tsx
23216
+ // src/contexts/translation-adapter.tsx
23101
23217
  var React65 = __toESM(require("react"), 1);
23102
23218
  var import_jsx_runtime73 = require("react/jsx-runtime");
23103
23219
  function isUnresolvedTranslation2(value, namespace, key) {
@@ -31745,6 +31861,7 @@ var UEditor = import_react52.default.forwardRef(({
31745
31861
  const t = useSmartTranslations("UEditor");
31746
31862
  const effectivePlaceholder = placeholder ?? t("placeholder");
31747
31863
  const inFlightPrepareRef = (0, import_react52.useRef)(null);
31864
+ const lastAppliedContentRef = (0, import_react52.useRef)(content ?? "");
31748
31865
  const editorContentRef = (0, import_react52.useRef)(null);
31749
31866
  const tableColumnGuideRef = (0, import_react52.useRef)(null);
31750
31867
  const tableRowGuideRef = (0, import_react52.useRef)(null);
@@ -32047,10 +32164,12 @@ var UEditor = import_react52.default.forwardRef(({
32047
32164
  [content, editor, uploadImageForSave]
32048
32165
  );
32049
32166
  (0, import_react52.useEffect)(() => {
32050
- if (editor && content !== editor.getHTML()) {
32051
- if (editor.isEmpty && content) {
32052
- editor.commands.setContent(content);
32053
- }
32167
+ if (!editor) return;
32168
+ const nextContent = content ?? "";
32169
+ if (lastAppliedContentRef.current === nextContent) return;
32170
+ lastAppliedContentRef.current = nextContent;
32171
+ if (editor.getHTML() !== nextContent) {
32172
+ editor.commands.setContent(nextContent, { emitUpdate: false });
32054
32173
  }
32055
32174
  }, [content, editor]);
32056
32175
  (0, import_react52.useEffect)(() => {