@burojs/ui-shadcn 0.4.0 → 0.5.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.
Files changed (137) hide show
  1. package/README.md +22 -5
  2. package/dist/bulk-summary-CwSd6Yjb.cjs +108 -0
  3. package/dist/bulk-summary-CwSd6Yjb.cjs.map +1 -0
  4. package/dist/bulk-summary-GxMDP4bw.js +108 -0
  5. package/dist/bulk-summary-GxMDP4bw.js.map +1 -0
  6. package/dist/button-BxnnE28H.cjs +55 -0
  7. package/dist/button-BxnnE28H.cjs.map +1 -0
  8. package/dist/button-MWVeZaqj.js +44 -0
  9. package/dist/button-MWVeZaqj.js.map +1 -0
  10. package/dist/candidates-gate-CX2PBu6l.js +63 -0
  11. package/dist/candidates-gate-CX2PBu6l.js.map +1 -0
  12. package/dist/candidates-gate-DiFI52r7.cjs +70 -0
  13. package/dist/candidates-gate-DiFI52r7.cjs.map +1 -0
  14. package/dist/candidates-relation-picker-CBsVcnPR.js +82 -0
  15. package/dist/candidates-relation-picker-CBsVcnPR.js.map +1 -0
  16. package/dist/candidates-relation-picker-Dd_GVLlu.cjs +84 -0
  17. package/dist/candidates-relation-picker-Dd_GVLlu.cjs.map +1 -0
  18. package/dist/check-confirm-dialog-BRByTFpg.cjs +1534 -0
  19. package/dist/check-confirm-dialog-BRByTFpg.cjs.map +1 -0
  20. package/dist/check-confirm-dialog-CkaVTPT9.js +1232 -0
  21. package/dist/check-confirm-dialog-CkaVTPT9.js.map +1 -0
  22. package/dist/dialog-B_rt9S1c.js +63 -0
  23. package/dist/dialog-B_rt9S1c.js.map +1 -0
  24. package/dist/dialog-DRNUq9EA.cjs +112 -0
  25. package/dist/dialog-DRNUq9EA.cjs.map +1 -0
  26. package/dist/filter-builder-B3mZFL-K.cjs +1761 -0
  27. package/dist/filter-builder-B3mZFL-K.cjs.map +1 -0
  28. package/dist/filter-builder-DOnaXNfP.js +1605 -0
  29. package/dist/filter-builder-DOnaXNfP.js.map +1 -0
  30. package/dist/flow-runtime-B6zOHYqI.cjs +255 -0
  31. package/dist/flow-runtime-B6zOHYqI.cjs.map +1 -0
  32. package/dist/flow-runtime-D8h99FSg.js +252 -0
  33. package/dist/flow-runtime-D8h99FSg.js.map +1 -0
  34. package/dist/flow-screen-CX05b3G9.cjs +705 -0
  35. package/dist/flow-screen-CX05b3G9.cjs.map +1 -0
  36. package/dist/flow-screen-CmbARSqg.js +701 -0
  37. package/dist/flow-screen-CmbARSqg.js.map +1 -0
  38. package/dist/index.cjs +2825 -11912
  39. package/dist/index.cjs.map +1 -1
  40. package/dist/index.d.cts +1612 -1862
  41. package/dist/index.d.ts +1700 -1950
  42. package/dist/index.js +2498 -11605
  43. package/dist/index.js.map +1 -1
  44. package/dist/inline-flow-host-B7U1OJUe.cjs +112 -0
  45. package/dist/inline-flow-host-B7U1OJUe.cjs.map +1 -0
  46. package/dist/inline-flow-host-C3H2UpSA.js +112 -0
  47. package/dist/inline-flow-host-C3H2UpSA.js.map +1 -0
  48. package/dist/input-BI0czGyI.cjs +23 -0
  49. package/dist/input-BI0czGyI.cjs.map +1 -0
  50. package/dist/input-CUPStzZO.js +17 -0
  51. package/dist/input-CUPStzZO.js.map +1 -0
  52. package/dist/label-66W1qbke.js +29 -0
  53. package/dist/label-66W1qbke.js.map +1 -0
  54. package/dist/label-w6JYf2cF.cjs +36 -0
  55. package/dist/label-w6JYf2cF.cjs.map +1 -0
  56. package/dist/lazy-relation-picker-dialog-2oxnA6IR.js +14 -0
  57. package/dist/lazy-relation-picker-dialog-2oxnA6IR.js.map +1 -0
  58. package/dist/lazy-relation-picker-dialog-BfAGKzUG.cjs +19 -0
  59. package/dist/lazy-relation-picker-dialog-BfAGKzUG.cjs.map +1 -0
  60. package/dist/markdown/index.cjs +1 -1
  61. package/dist/markdown/index.js +1 -1
  62. package/dist/page-flow-route-CQ7HsbBm.cjs +277 -0
  63. package/dist/page-flow-route-CQ7HsbBm.cjs.map +1 -0
  64. package/dist/page-flow-route-CyXSvXSE.js +277 -0
  65. package/dist/page-flow-route-CyXSvXSE.js.map +1 -0
  66. package/dist/peek-filter-CnqHwTMU.cjs +51 -0
  67. package/dist/peek-filter-CnqHwTMU.cjs.map +1 -0
  68. package/dist/peek-filter-ErLfO153.js +28 -0
  69. package/dist/peek-filter-ErLfO153.js.map +1 -0
  70. package/dist/place-node--GdsP688.js +7315 -0
  71. package/dist/place-node--GdsP688.js.map +1 -0
  72. package/dist/place-node-SIp4XYQH.cjs +7833 -0
  73. package/dist/place-node-SIp4XYQH.cjs.map +1 -0
  74. package/dist/related-list-cards-B6x9HQQp.js +42 -0
  75. package/dist/related-list-cards-B6x9HQQp.js.map +1 -0
  76. package/dist/related-list-cards-nxuODlIA.cjs +42 -0
  77. package/dist/related-list-cards-nxuODlIA.cjs.map +1 -0
  78. package/dist/related-list-timeline-BYJKEbmA.cjs +142 -0
  79. package/dist/related-list-timeline-BYJKEbmA.cjs.map +1 -0
  80. package/dist/related-list-timeline-DEdfd-rr.js +142 -0
  81. package/dist/related-list-timeline-DEdfd-rr.js.map +1 -0
  82. package/dist/related-rows-B4levHcR.js +186 -0
  83. package/dist/related-rows-B4levHcR.js.map +1 -0
  84. package/dist/related-rows-zh5f_4QR.cjs +209 -0
  85. package/dist/related-rows-zh5f_4QR.cjs.map +1 -0
  86. package/dist/relation-create-BOaUe12G.cjs +362 -0
  87. package/dist/relation-create-BOaUe12G.cjs.map +1 -0
  88. package/dist/relation-create-Bg-eEJLs.js +337 -0
  89. package/dist/relation-create-Bg-eEJLs.js.map +1 -0
  90. package/dist/relation-many-picker-B-NCEUgX.cjs +465 -0
  91. package/dist/relation-many-picker-B-NCEUgX.cjs.map +1 -0
  92. package/dist/relation-many-picker-gdAOD-qK.js +464 -0
  93. package/dist/relation-many-picker-gdAOD-qK.js.map +1 -0
  94. package/dist/relation-picker-BAY77ivO.js +462 -0
  95. package/dist/relation-picker-BAY77ivO.js.map +1 -0
  96. package/dist/relation-picker-Da_uhFvA.cjs +479 -0
  97. package/dist/relation-picker-Da_uhFvA.cjs.map +1 -0
  98. package/dist/relation-picker-dialog-M0pQaWlp.cjs +300 -0
  99. package/dist/relation-picker-dialog-M0pQaWlp.cjs.map +1 -0
  100. package/dist/relation-picker-dialog-hA9rYrZB.js +299 -0
  101. package/dist/relation-picker-dialog-hA9rYrZB.js.map +1 -0
  102. package/dist/resource-page-router-OSQyRA5b.cjs +1779 -0
  103. package/dist/resource-page-router-OSQyRA5b.cjs.map +1 -0
  104. package/dist/resource-page-router-T4vS5gbJ.js +1582 -0
  105. package/dist/resource-page-router-T4vS5gbJ.js.map +1 -0
  106. package/dist/{rich-text-IDFvyvRE.js → rich-text-DFerywgz.js} +7 -4
  107. package/dist/{rich-text-IDFvyvRE.js.map → rich-text-DFerywgz.js.map} +1 -1
  108. package/dist/{rich-text-lndbYqzR.cjs → rich-text-vTyVjDoa.cjs} +25 -22
  109. package/dist/{rich-text-lndbYqzR.cjs.map → rich-text-vTyVjDoa.cjs.map} +1 -1
  110. package/dist/search-access-B7G5oR8n.cjs +812 -0
  111. package/dist/search-access-B7G5oR8n.cjs.map +1 -0
  112. package/dist/search-access-CURsWn-U.js +669 -0
  113. package/dist/search-access-CURsWn-U.js.map +1 -0
  114. package/dist/shell-BPlk8hEa.js +205 -0
  115. package/dist/shell-BPlk8hEa.js.map +1 -0
  116. package/dist/shell-DxhlsPwm.cjs +271 -0
  117. package/dist/shell-DxhlsPwm.cjs.map +1 -0
  118. package/dist/skeleton-U2noefj5.js +13 -0
  119. package/dist/skeleton-U2noefj5.js.map +1 -0
  120. package/dist/skeleton-_1gP7wan.cjs +18 -0
  121. package/dist/skeleton-_1gP7wan.cjs.map +1 -0
  122. package/dist/styles.css +241 -9
  123. package/dist/toaster-C7CiYA4Z.cjs +248 -0
  124. package/dist/toaster-C7CiYA4Z.cjs.map +1 -0
  125. package/dist/toaster-CfCRLgR-.js +164 -0
  126. package/dist/toaster-CfCRLgR-.js.map +1 -0
  127. package/dist/use-dialog-filter-state-BBCC0OEK.cjs +92 -0
  128. package/dist/use-dialog-filter-state-BBCC0OEK.cjs.map +1 -0
  129. package/dist/use-dialog-filter-state-BDcgS5Oz.js +87 -0
  130. package/dist/use-dialog-filter-state-BDcgS5Oz.js.map +1 -0
  131. package/dist/{use-theme-BwPWinB2.cjs → use-theme-DFlpXils.cjs} +12 -1
  132. package/dist/use-theme-DFlpXils.cjs.map +1 -0
  133. package/dist/{use-theme-IHmtrhEt.js → use-theme-dnE3Od3I.js} +7 -2
  134. package/dist/use-theme-dnE3Od3I.js.map +1 -0
  135. package/package.json +16 -14
  136. package/dist/use-theme-BwPWinB2.cjs.map +0 -1
  137. package/dist/use-theme-IHmtrhEt.js.map +0 -1
@@ -0,0 +1,1761 @@
1
+ const require_use_theme = require("./use-theme-DFlpXils.cjs");
2
+ const require_toaster = require("./toaster-C7CiYA4Z.cjs");
3
+ const require_button = require("./button-BxnnE28H.cjs");
4
+ const require_input = require("./input-BI0czGyI.cjs");
5
+ const require_search_access = require("./search-access-B7G5oR8n.cjs");
6
+ let _burojs_core = require("@burojs/core");
7
+ let _burojs_react = require("@burojs/react");
8
+ let _tabler_icons_react = require("@tabler/icons-react");
9
+ let react = require("react");
10
+ react = require_use_theme.__toESM(react, 1);
11
+ let react_jsx_runtime = require("react/jsx-runtime");
12
+ let class_variance_authority = require("class-variance-authority");
13
+ let _radix_ui_react_select = require("@radix-ui/react-select");
14
+ _radix_ui_react_select = require_use_theme.__toESM(_radix_ui_react_select, 1);
15
+ //#region src/primitives/badge.tsx
16
+ const badgeVariants = (0, class_variance_authority.cva)("inline-flex max-w-full items-center truncate rounded-full border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-hidden focus:ring-2 focus:ring-ring focus:ring-offset-2", {
17
+ variants: { variant: {
18
+ default: "border-transparent bg-primary text-primary-foreground hover:bg-primary/80",
19
+ secondary: "border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80",
20
+ destructive: "border-transparent bg-destructive text-destructive-foreground hover:bg-destructive/80",
21
+ outline: "text-foreground",
22
+ success: "border-success/20 bg-success/15 text-success-emphasis hover:bg-success/20",
23
+ warning: "border-warning/20 bg-warning/15 text-warning-emphasis hover:bg-warning/20",
24
+ info: "border-info/20 bg-info/15 text-info-emphasis hover:bg-info/20"
25
+ } },
26
+ defaultVariants: { variant: "default" }
27
+ });
28
+ function Badge({ className, variant, children, ...props }) {
29
+ return (0, react_jsx_runtime.jsx)("div", {
30
+ className: require_use_theme.cn(badgeVariants({ variant }), className),
31
+ ...props,
32
+ children: (0, react_jsx_runtime.jsx)("span", {
33
+ className: "min-w-0 truncate",
34
+ children
35
+ })
36
+ });
37
+ }
38
+ //#endregion
39
+ //#region src/primitives/table.tsx
40
+ const Table = react.forwardRef(({ className, ...props }, ref) => (0, react_jsx_runtime.jsx)("div", {
41
+ className: "relative w-full overflow-auto",
42
+ children: (0, react_jsx_runtime.jsx)("table", {
43
+ ref,
44
+ className: require_use_theme.cn("w-full caption-bottom text-sm", className),
45
+ ...props
46
+ })
47
+ }));
48
+ Table.displayName = "Table";
49
+ const TableHeader = react.forwardRef(({ className, ...props }, ref) => (0, react_jsx_runtime.jsx)("thead", {
50
+ ref,
51
+ className: require_use_theme.cn("[&_tr]:border-b", className),
52
+ ...props
53
+ }));
54
+ TableHeader.displayName = "TableHeader";
55
+ const TableBody = react.forwardRef(({ className, ...props }, ref) => (0, react_jsx_runtime.jsx)("tbody", {
56
+ ref,
57
+ className: require_use_theme.cn("[&_tr:last-child]:border-0", className),
58
+ ...props
59
+ }));
60
+ TableBody.displayName = "TableBody";
61
+ const TableRow = react.forwardRef(({ className, ...props }, ref) => (0, react_jsx_runtime.jsx)("tr", {
62
+ ref,
63
+ className: require_use_theme.cn("group/row border-b transition-colors hover:bg-muted/50 data-[state=selected]:bg-primary/10 data-[state=selected]:hover:bg-primary/15", className),
64
+ ...props
65
+ }));
66
+ TableRow.displayName = "TableRow";
67
+ const TableHead = react.forwardRef(({ className, ...props }, ref) => (0, react_jsx_runtime.jsx)("th", {
68
+ ref,
69
+ className: require_use_theme.cn("h-10 px-2 text-left align-middle font-medium text-muted-foreground [&:has([role=checkbox])]:pr-0", className),
70
+ ...props
71
+ }));
72
+ TableHead.displayName = "TableHead";
73
+ const TableCell = react.forwardRef(({ className, ...props }, ref) => (0, react_jsx_runtime.jsx)("td", {
74
+ ref,
75
+ className: require_use_theme.cn("p-2 align-middle [&:has([role=checkbox])]:pr-0", className),
76
+ ...props
77
+ }));
78
+ TableCell.displayName = "TableCell";
79
+ const TableCaption = react.forwardRef(({ className, ...props }, ref) => (0, react_jsx_runtime.jsx)("caption", {
80
+ ref,
81
+ className: require_use_theme.cn("mt-4 text-sm text-muted-foreground", className),
82
+ ...props
83
+ }));
84
+ TableCaption.displayName = "TableCaption";
85
+ //#endregion
86
+ //#region src/primitives/select.tsx
87
+ const Select = _radix_ui_react_select.Root;
88
+ const SelectGroup = _radix_ui_react_select.Group;
89
+ const SelectValue = _radix_ui_react_select.Value;
90
+ const SelectTrigger = react.forwardRef(({ className, children, ...props }, ref) => (0, react_jsx_runtime.jsxs)(_radix_ui_react_select.Trigger, {
91
+ ref,
92
+ className: require_use_theme.cn("flex h-9 w-full items-center justify-between rounded-md border border-input bg-background text-foreground px-3 py-2 text-sm ring-offset-background placeholder:text-muted-foreground focus:outline-hidden focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 disabled:bg-input-disabled [&>span]:line-clamp-1", className),
93
+ ...props,
94
+ children: [children, (0, react_jsx_runtime.jsx)(_radix_ui_react_select.Icon, {
95
+ asChild: true,
96
+ children: (0, react_jsx_runtime.jsx)(_tabler_icons_react.IconChevronDown, { className: "h-4 w-4 opacity-50" })
97
+ })]
98
+ }));
99
+ SelectTrigger.displayName = _radix_ui_react_select.Trigger.displayName;
100
+ const SelectScrollUpButton = react.forwardRef(({ className, ...props }, ref) => (0, react_jsx_runtime.jsx)(_radix_ui_react_select.ScrollUpButton, {
101
+ ref,
102
+ className: require_use_theme.cn("flex cursor-default items-center justify-center py-1", className),
103
+ ...props,
104
+ children: (0, react_jsx_runtime.jsx)(_tabler_icons_react.IconChevronUp, { className: "h-4 w-4" })
105
+ }));
106
+ SelectScrollUpButton.displayName = _radix_ui_react_select.ScrollUpButton.displayName;
107
+ const SelectScrollDownButton = react.forwardRef(({ className, ...props }, ref) => (0, react_jsx_runtime.jsx)(_radix_ui_react_select.ScrollDownButton, {
108
+ ref,
109
+ className: require_use_theme.cn("flex cursor-default items-center justify-center py-1", className),
110
+ ...props,
111
+ children: (0, react_jsx_runtime.jsx)(_tabler_icons_react.IconChevronDown, { className: "h-4 w-4" })
112
+ }));
113
+ SelectScrollDownButton.displayName = _radix_ui_react_select.ScrollDownButton.displayName;
114
+ const SelectContent = react.forwardRef(({ className, children, position = "popper", ...props }, ref) => (0, react_jsx_runtime.jsx)(_radix_ui_react_select.Portal, { children: (0, react_jsx_runtime.jsxs)(_radix_ui_react_select.Content, {
115
+ ref,
116
+ className: require_use_theme.cn("relative z-50 max-h-96 min-w-[8rem] overflow-hidden rounded-md border bg-popover text-popover-foreground shadow-md duration-[var(--duration-overlay)] ease-[var(--ease-overlay)] data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2", position === "popper" && "data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1", className),
117
+ position,
118
+ ...props,
119
+ children: [
120
+ (0, react_jsx_runtime.jsx)(SelectScrollUpButton, {}),
121
+ (0, react_jsx_runtime.jsx)(_radix_ui_react_select.Viewport, {
122
+ className: require_use_theme.cn("p-1", position === "popper" && "h-[var(--radix-select-trigger-height)] w-full min-w-[var(--radix-select-trigger-width)]"),
123
+ children
124
+ }),
125
+ (0, react_jsx_runtime.jsx)(SelectScrollDownButton, {})
126
+ ]
127
+ }) }));
128
+ SelectContent.displayName = _radix_ui_react_select.Content.displayName;
129
+ const SelectLabel = react.forwardRef(({ className, ...props }, ref) => (0, react_jsx_runtime.jsx)(_radix_ui_react_select.Label, {
130
+ ref,
131
+ className: require_use_theme.cn("py-1.5 pl-8 pr-2 text-sm font-semibold", className),
132
+ ...props
133
+ }));
134
+ SelectLabel.displayName = _radix_ui_react_select.Label.displayName;
135
+ const SelectItem = react.forwardRef(({ className, children, ...props }, ref) => (0, react_jsx_runtime.jsxs)(_radix_ui_react_select.Item, {
136
+ ref,
137
+ className: require_use_theme.cn("relative flex w-full cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-hidden focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50", className),
138
+ ...props,
139
+ children: [(0, react_jsx_runtime.jsx)("span", {
140
+ className: "absolute left-2 flex h-3.5 w-3.5 items-center justify-center",
141
+ children: (0, react_jsx_runtime.jsx)(_radix_ui_react_select.ItemIndicator, { children: (0, react_jsx_runtime.jsx)(_tabler_icons_react.IconCheck, { className: "h-4 w-4" }) })
142
+ }), (0, react_jsx_runtime.jsx)(_radix_ui_react_select.ItemText, { children })]
143
+ }));
144
+ SelectItem.displayName = _radix_ui_react_select.Item.displayName;
145
+ const SelectSeparator = react.forwardRef(({ className, ...props }, ref) => (0, react_jsx_runtime.jsx)(_radix_ui_react_select.Separator, {
146
+ ref,
147
+ className: require_use_theme.cn("-mx-1 my-1 h-px bg-muted", className),
148
+ ...props
149
+ }));
150
+ SelectSeparator.displayName = _radix_ui_react_select.Separator.displayName;
151
+ //#endregion
152
+ //#region src/fields/format.ts
153
+ const LOCALE_TAG = {
154
+ ru: "ru-RU",
155
+ en: "en-US"
156
+ };
157
+ function localeTag(locale) {
158
+ if (locale) return LOCALE_TAG[locale] ?? locale;
159
+ return LOCALE_TAG[require_toaster.loadLanguage()] ?? "en-US";
160
+ }
161
+ const dateCache = new Map();
162
+ const dateTimeCache = new Map();
163
+ const numberCache = new Map();
164
+ function cached(cache, key, make) {
165
+ const hit = cache.get(key);
166
+ if (hit) return hit;
167
+ let made;
168
+ try {
169
+ made = make(key);
170
+ } catch {
171
+ made = make("en-US");
172
+ }
173
+ cache.set(key, made);
174
+ return made;
175
+ }
176
+ function formatDate(d, locale) {
177
+ const tag = localeTag(locale);
178
+ return cached(dateCache, tag, (t) => new Intl.DateTimeFormat(t, {
179
+ year: "numeric",
180
+ month: "short",
181
+ day: "numeric"
182
+ })).format(d);
183
+ }
184
+ function formatDateTime(d, locale) {
185
+ const tag = localeTag(locale);
186
+ return cached(dateTimeCache, tag, (t) => new Intl.DateTimeFormat(t, {
187
+ year: "numeric",
188
+ month: "short",
189
+ day: "numeric",
190
+ hour: "2-digit",
191
+ minute: "2-digit"
192
+ })).format(d);
193
+ }
194
+ function formatNumber(n, locale) {
195
+ const tag = localeTag(locale);
196
+ return cached(numberCache, tag, (t) => new Intl.NumberFormat(t)).format(n);
197
+ }
198
+ //#endregion
199
+ //#region src/list-kit/filter-builder/describe-row.ts
200
+ const MAX_DESCRIBE_DEPTH = 2;
201
+ function describeValue(value, t, spec, formatValue) {
202
+ if (value === void 0) return null;
203
+ if (Array.isArray(value)) return t("filters.selectedCount", { count: value.length }, `${value.length} selected`);
204
+ const optionLabel = spec?.options?.find((o) => o.value === String(value))?.label;
205
+ if (optionLabel !== void 0) return optionLabel;
206
+ if (spec && formatValue) {
207
+ const formatted = formatValue(spec.kind, value);
208
+ if (formatted !== void 0) return formatted;
209
+ }
210
+ return String(value);
211
+ }
212
+ function describeAt(row, available, t, depth, formatValue) {
213
+ if ((0, _burojs_core.isFilterGroup)(row)) {
214
+ if (depth > MAX_DESCRIBE_DEPTH) return "…";
215
+ const joinLabel = t(`filters.${row.join}`, void 0, row.join);
216
+ return `(${row.rows.map((child) => describeAt(child, available, t, depth + 1, formatValue)).join(` ${joinLabel} `)})`;
217
+ }
218
+ const spec = available.find((s) => s.field === row.field);
219
+ const label = spec?.label ?? row.field;
220
+ const opLabel = t(`filters.operators.${row.op}`, void 0, row.op);
221
+ const valueText = (0, _burojs_core.opArity)(row.op) === "none" ? null : describeValue(row.value, t, spec, formatValue);
222
+ return valueText === null ? `${label} ${opLabel}` : `${label} ${opLabel} ${valueText}`;
223
+ }
224
+ function describeRow(row, available, t, formatValue) {
225
+ return describeAt(row, available, t, 1, formatValue);
226
+ }
227
+ function relationLabelKey(to, by, value) {
228
+ return `${by === void 0 ? to : `${to}#${by}`}::${value}`;
229
+ }
230
+ function collectFormatRefs(rows, available) {
231
+ const enumNames = new Set();
232
+ const relationRefs = new Map();
233
+ const walk = (nodes) => {
234
+ for (const row of nodes) {
235
+ if ((0, _burojs_core.isFilterGroup)(row)) {
236
+ walk(row.rows);
237
+ continue;
238
+ }
239
+ if (row.value === void 0 || Array.isArray(row.value)) continue;
240
+ const spec = available.find((s) => s.field === row.field);
241
+ if (!spec) continue;
242
+ if (spec.kind.kind === "enum") enumNames.add(spec.kind.enumName);
243
+ else if (spec.kind.kind === "relation") {
244
+ const id = String(row.value);
245
+ const { to, by } = spec.kind;
246
+ relationRefs.set(relationLabelKey(to, by, id), by === void 0 ? {
247
+ to,
248
+ id
249
+ } : {
250
+ to,
251
+ id,
252
+ by
253
+ });
254
+ }
255
+ }
256
+ };
257
+ walk(rows);
258
+ return {
259
+ enumNames: Array.from(enumNames),
260
+ relationRefs: Array.from(relationRefs.values())
261
+ };
262
+ }
263
+ //#endregion
264
+ //#region src/list-kit/filter-builder/describe-value-resolvers.tsx
265
+ function EnumLabelResolver(props) {
266
+ const { enumName, onResolve } = props;
267
+ const { values } = (0, _burojs_react.useEnumValues)(enumName);
268
+ const caps = (0, _burojs_react.useI18nCaps)();
269
+ (0, react.useEffect)(() => {
270
+ if (!values) return;
271
+ const labels = {};
272
+ for (const entry of values) labels[entry.value] = (0, _burojs_core.resolveEnumLabel)(caps, enumName, entry.value, entry.label);
273
+ onResolve(enumName, labels);
274
+ }, [
275
+ values,
276
+ enumName,
277
+ caps,
278
+ onResolve
279
+ ]);
280
+ return null;
281
+ }
282
+ function RelationLabelResolver(props) {
283
+ const { to, id, by, onResolve } = props;
284
+ const targetDef = (0, _burojs_react.useApp)().registry.get(to);
285
+ const { record } = (0, _burojs_react.useRelatedRecord)(to, id, by === void 0 ? void 0 : { by });
286
+ const rowTitle = require_search_access.useRowTitle(to, targetDef?.displayField);
287
+ (0, react.useEffect)(() => {
288
+ if (!record) return;
289
+ onResolve(relationLabelKey(to, by, id), rowTitle(record));
290
+ }, [
291
+ record,
292
+ to,
293
+ by,
294
+ id,
295
+ rowTitle,
296
+ onResolve
297
+ ]);
298
+ return null;
299
+ }
300
+ function FormatResolvers(props) {
301
+ const { refs, onEnumResolved, onRelationResolved } = props;
302
+ return (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [refs.enumNames.map((name) => (0, react_jsx_runtime.jsx)(EnumLabelResolver, {
303
+ enumName: name,
304
+ onResolve: onEnumResolved
305
+ }, `enum:${name}`)), refs.relationRefs.map(({ to, id, by }) => (0, react_jsx_runtime.jsx)(RelationLabelResolver, {
306
+ to,
307
+ id,
308
+ ...by === void 0 ? {} : { by },
309
+ onResolve: onRelationResolved
310
+ }, `relation:${relationLabelKey(to, by, id)}`))] });
311
+ }
312
+ function useFormatValueMaps() {
313
+ const [enumLabels, setEnumLabels] = (0, react.useState)({});
314
+ const [relationLabels, setRelationLabels] = (0, react.useState)({});
315
+ return {
316
+ enumLabels,
317
+ relationLabels,
318
+ onEnumResolved: (0, react.useCallback)((enumName, labels) => {
319
+ setEnumLabels((prev) => prev[enumName] === labels ? prev : {
320
+ ...prev,
321
+ [enumName]: labels
322
+ });
323
+ }, []),
324
+ onRelationResolved: (0, react.useCallback)((key, label) => {
325
+ setRelationLabels((prev) => prev[key] === label ? prev : {
326
+ ...prev,
327
+ [key]: label
328
+ });
329
+ }, [])
330
+ };
331
+ }
332
+ //#endregion
333
+ //#region src/list-kit/filter-builder/pinned-ownership.ts
334
+ function isFilterPredicateExpr(expr) {
335
+ return "field" in expr;
336
+ }
337
+ function findOwnedIndex(value, spec) {
338
+ const op = spec.operators[0] ?? "eq";
339
+ return value.findIndex((expr) => isFilterPredicateExpr(expr) && expr.field === spec.field && expr.op === op);
340
+ }
341
+ function claimedPinnedIndices(value, pinnedFields) {
342
+ const claimed = new Set();
343
+ for (const spec of pinnedFields) {
344
+ const op = spec.operators[0] ?? "eq";
345
+ const index = value.findIndex((expr, i) => !claimed.has(i) && isFilterPredicateExpr(expr) && expr.field === spec.field && expr.op === op);
346
+ if (index >= 0) claimed.add(index);
347
+ }
348
+ return claimed;
349
+ }
350
+ //#endregion
351
+ //#region src/list-kit/filter-builder/filter-badges.tsx
352
+ function badgeKey(row, index) {
353
+ if ((0, _burojs_core.isFilterGroup)(row)) return `${index}:group:${row.join}`;
354
+ return `${index}:${row.field}:${row.op}`;
355
+ }
356
+ function FilterBadges(props) {
357
+ const { group, setGroup, available, fixed, testIdBase, onOpenAt, onRemoved, pinnedFields = [], pinnedValue = [], setPinnedValue } = props;
358
+ const t = (0, _burojs_react.useOptionalT)();
359
+ const tt = (key, params, fallback) => t(key, {
360
+ params,
361
+ ...typeof params?.count === "number" ? { count: params.count } : {},
362
+ defaultValue: fallback
363
+ });
364
+ const removeLabel = t("filters.removeRow", { defaultValue: "Remove filter" });
365
+ const { enumLabels, relationLabels, onEnumResolved, onRelationResolved } = useFormatValueMaps();
366
+ const formatValue = (kind, value) => {
367
+ if (kind.kind === "enum") return enumLabels[kind.enumName]?.[String(value)];
368
+ if (kind.kind === "relation") return relationLabels[relationLabelKey(kind.to, kind.by, String(value))];
369
+ if (kind.kind === "date") {
370
+ const d = new Date(String(value));
371
+ if (Number.isNaN(d.getTime())) return void 0;
372
+ return kind.time ? formatDateTime(d) : formatDate(d);
373
+ }
374
+ };
375
+ const orphanPinned = (0, react.useMemo)(() => {
376
+ const claimed = claimedPinnedIndices(pinnedValue, pinnedFields);
377
+ return pinnedValue.map((expr, index) => ({
378
+ expr,
379
+ index
380
+ })).filter(({ expr, index }) => !claimed.has(index) && isFilterPredicateExpr(expr));
381
+ }, [pinnedValue, pinnedFields]);
382
+ const refs = (0, react.useMemo)(() => collectFormatRefs([...group.rows, ...orphanPinned.map(({ expr }) => expr)], available), [
383
+ group,
384
+ orphanPinned,
385
+ available
386
+ ]);
387
+ const handleRemove = (index) => {
388
+ setGroup({
389
+ ...group,
390
+ rows: group.rows.filter((_, i) => i !== index)
391
+ });
392
+ onRemoved?.(index);
393
+ };
394
+ const handleRemovePinned = (index) => {
395
+ setPinnedValue?.(pinnedValue.filter((_, i) => i !== index));
396
+ };
397
+ const labelledFixed = fixed.filter((entry) => entry.label !== void 0);
398
+ return (0, react_jsx_runtime.jsxs)("div", {
399
+ className: "flex flex-wrap items-center gap-1",
400
+ children: [
401
+ (0, react_jsx_runtime.jsx)(FormatResolvers, {
402
+ refs,
403
+ onEnumResolved,
404
+ onRelationResolved
405
+ }),
406
+ group.rows.map((row, index) => (0, react_jsx_runtime.jsxs)(Badge, {
407
+ variant: "secondary",
408
+ className: "gap-1 py-1 pr-1",
409
+ children: [(0, react_jsx_runtime.jsx)("button", {
410
+ type: "button",
411
+ "data-testid": `${testIdBase}:badge:${index}`,
412
+ onClick: () => onOpenAt([index]),
413
+ className: "max-w-full cursor-pointer truncate bg-transparent p-0 text-left",
414
+ children: describeRow(row, available, tt, formatValue)
415
+ }), (0, react_jsx_runtime.jsx)("button", {
416
+ type: "button",
417
+ "data-testid": `${testIdBase}:badge:${index}:remove`,
418
+ "aria-label": removeLabel,
419
+ onClick: () => handleRemove(index),
420
+ className: "rounded-sm hover:bg-accent",
421
+ children: (0, react_jsx_runtime.jsx)(_tabler_icons_react.IconX, {
422
+ className: "h-3 w-3",
423
+ "aria-hidden": "true"
424
+ })
425
+ })]
426
+ }, badgeKey(row, index))),
427
+ labelledFixed.map((entry, index) => (0, react_jsx_runtime.jsx)(Badge, {
428
+ variant: "outline",
429
+ "data-testid": `${testIdBase}:badge:fixed:${index}`,
430
+ children: entry.label
431
+ }, `fixed-${index}`)),
432
+ orphanPinned.map(({ expr, index }) => (0, react_jsx_runtime.jsxs)(Badge, {
433
+ variant: "secondary",
434
+ className: "gap-1 py-1",
435
+ children: [(0, react_jsx_runtime.jsx)("span", {
436
+ "data-testid": `${testIdBase}:badge:pinned:${index}`,
437
+ children: describeRow(expr, available, tt, formatValue)
438
+ }), (0, react_jsx_runtime.jsx)("button", {
439
+ type: "button",
440
+ "data-testid": `${testIdBase}:badge:pinned:${index}:remove`,
441
+ "aria-label": removeLabel,
442
+ onClick: () => handleRemovePinned(index),
443
+ className: "rounded-sm hover:bg-accent",
444
+ children: (0, react_jsx_runtime.jsx)(_tabler_icons_react.IconX, {
445
+ className: "h-3 w-3",
446
+ "aria-hidden": "true"
447
+ })
448
+ })]
449
+ }, `pinned-${index}`))
450
+ ]
451
+ });
452
+ }
453
+ let counter = 0;
454
+ function nextId() {
455
+ counter += 1;
456
+ return `r${counter}`;
457
+ }
458
+ function isDraftGroup(node) {
459
+ return Object.hasOwn(node, "rows");
460
+ }
461
+ function toDraft(group) {
462
+ return {
463
+ rowId: nextId(),
464
+ join: group.join,
465
+ rows: group.rows.map(toDraftNode)
466
+ };
467
+ }
468
+ function toDraftNode(row) {
469
+ if ((0, _burojs_core.isFilterGroup)(row)) return toDraft(row);
470
+ const predicate = row;
471
+ return {
472
+ rowId: nextId(),
473
+ field: predicate.field,
474
+ op: predicate.op,
475
+ ...Object.hasOwn(predicate, "value") ? { value: predicate.value } : {}
476
+ };
477
+ }
478
+ function fromDraft(draft) {
479
+ return {
480
+ join: draft.join,
481
+ rows: draft.rows.map((node) => isDraftGroup(node) ? fromDraft(node) : {
482
+ field: node.field,
483
+ op: node.op,
484
+ ...Object.hasOwn(node, "value") ? { value: node.value } : {}
485
+ })
486
+ };
487
+ }
488
+ function pruneIncompleteRows(group) {
489
+ const rows = [];
490
+ for (const row of group.rows) if ((0, _burojs_core.isFilterGroup)(row)) {
491
+ const pruned = pruneIncompleteRows(row);
492
+ if (pruned.rows.length > 0) rows.push(pruned);
493
+ } else if ((0, _burojs_core.isCompletePredicate)(row)) rows.push(row);
494
+ return {
495
+ join: group.join,
496
+ rows
497
+ };
498
+ }
499
+ function mapGroupAt(draft, path, replace) {
500
+ if (path.length === 0) return replace(draft);
501
+ const [head, ...rest] = path;
502
+ const child = draft.rows[head];
503
+ if (child === void 0 || !isDraftGroup(child)) return draft;
504
+ const next = mapGroupAt(child, rest, replace);
505
+ if (next === child) return draft;
506
+ const rows = draft.rows.slice();
507
+ rows[head] = next;
508
+ return {
509
+ ...draft,
510
+ rows
511
+ };
512
+ }
513
+ function depthOf(path) {
514
+ return path.length + 1;
515
+ }
516
+ function defaultValueForOp(op) {
517
+ const arity = (0, _burojs_core.opArity)(op);
518
+ if (arity === "none") return void 0;
519
+ if (arity === "multi") return [];
520
+ return "";
521
+ }
522
+ function addRow(draft, path, available) {
523
+ const spec = available[0];
524
+ if (!spec) return draft;
525
+ const op = spec.operators[0] ?? "eq";
526
+ const value = defaultValueForOp(op);
527
+ const row = {
528
+ rowId: nextId(),
529
+ field: spec.field,
530
+ op,
531
+ ...value === void 0 ? {} : { value }
532
+ };
533
+ return mapGroupAt(draft, path, (group) => ({
534
+ ...group,
535
+ rows: [...group.rows, row]
536
+ }));
537
+ }
538
+ function addGroup(draft, path) {
539
+ if (depthOf(path) >= 3) return draft;
540
+ const group = {
541
+ rowId: nextId(),
542
+ join: "and",
543
+ rows: []
544
+ };
545
+ return mapGroupAt(draft, path, (parent) => ({
546
+ ...parent,
547
+ rows: [...parent.rows, group]
548
+ }));
549
+ }
550
+ function removeAt(draft, path) {
551
+ if (path.length === 0) return draft;
552
+ const parentPath = path.slice(0, -1);
553
+ const index = path[path.length - 1];
554
+ return mapGroupAt(draft, parentPath, (group) => {
555
+ if (group.rows[index] === void 0) return group;
556
+ const rows = group.rows.slice();
557
+ rows.splice(index, 1);
558
+ return {
559
+ ...group,
560
+ rows
561
+ };
562
+ });
563
+ }
564
+ function setJoin(draft, path, join) {
565
+ return mapGroupAt(draft, path, (group) => group.join === join ? group : {
566
+ ...group,
567
+ join
568
+ });
569
+ }
570
+ function updateRow(draft, path, patch) {
571
+ if (path.length === 0) return draft;
572
+ const parentPath = path.slice(0, -1);
573
+ const index = path[path.length - 1];
574
+ return mapGroupAt(draft, parentPath, (group) => {
575
+ const current = group.rows[index];
576
+ if (current === void 0 || isDraftGroup(current)) return group;
577
+ const fieldChanged = patch.field !== void 0 && patch.field !== current.field;
578
+ const op = patch.op ?? current.op;
579
+ let value;
580
+ if (fieldChanged) value = defaultValueForOp(op);
581
+ else if (patch.op !== void 0 && patch.op !== current.op) value = (0, _burojs_core.convertValueForOp)(current.value, current.op, patch.op);
582
+ else value = Object.hasOwn(patch, "value") ? patch.value : current.value;
583
+ const next = {
584
+ rowId: current.rowId,
585
+ field: patch.field ?? current.field,
586
+ op,
587
+ ...value === void 0 ? {} : { value }
588
+ };
589
+ const rows = group.rows.slice();
590
+ rows[index] = next;
591
+ return {
592
+ ...group,
593
+ rows
594
+ };
595
+ });
596
+ }
597
+ const EMPTY_DRAFT = () => toDraft(_burojs_core.EMPTY_FILTER_GROUP);
598
+ //#endregion
599
+ //#region src/list-kit/filter-builder/values/use-selectable-value-control.ts
600
+ function useSelectableValueControl(options) {
601
+ const { value, arity, onChange, emitSingle = (token) => token } = options;
602
+ const [open, setOpen] = (0, react.useState)(false);
603
+ const selected = arity === "multi" ? new Set(Array.isArray(value) ? value.map(String) : []) : new Set(value === void 0 || value === null ? [] : [String(value)]);
604
+ const select = (token) => {
605
+ if (arity === "single") {
606
+ onChange(emitSingle(token));
607
+ setOpen(false);
608
+ return;
609
+ }
610
+ const next = new Set(selected);
611
+ if (next.has(token)) next.delete(token);
612
+ else next.add(token);
613
+ onChange(next.size === 0 ? void 0 : Array.from(next));
614
+ };
615
+ const triggerLabel = (labelOf, placeholder) => {
616
+ const picked = Array.from(selected);
617
+ return picked.length === 0 ? placeholder : picked.map(labelOf).join(", ");
618
+ };
619
+ const clear = () => {
620
+ onChange(void 0);
621
+ setOpen(false);
622
+ };
623
+ return {
624
+ open,
625
+ setOpen,
626
+ selected,
627
+ select,
628
+ triggerLabel,
629
+ clear
630
+ };
631
+ }
632
+ //#endregion
633
+ //#region src/list-kit/filter-builder/values/boolean-value-control.tsx
634
+ const OPTIONS = ["true", "false"];
635
+ function BooleanValueControl(props) {
636
+ const { value, onChange, testId, arity, autoFocus, fieldLabel, fill } = props;
637
+ const t = (0, _burojs_react.useOptionalT)();
638
+ const ariaLabel = fieldLabel ? t("filters.valueFor", {
639
+ defaultValue: `Value for ${fieldLabel}`,
640
+ params: { field: fieldLabel }
641
+ }) : t("filters.value", { defaultValue: "Value" });
642
+ const yesLabel = t("boolean.yes", { defaultValue: "Yes" });
643
+ const noLabel = t("boolean.no", { defaultValue: "No" });
644
+ const labelOf = (token) => token === "true" ? yesLabel : noLabel;
645
+ const clearLabel = t("filters.clear", { defaultValue: "Clear" });
646
+ const { open, setOpen, selected, select, triggerLabel, clear } = useSelectableValueControl({
647
+ value,
648
+ arity,
649
+ onChange,
650
+ emitSingle: (token) => token === "true"
651
+ });
652
+ return (0, react_jsx_runtime.jsxs)(require_toaster.Popover, {
653
+ open,
654
+ onOpenChange: setOpen,
655
+ children: [(0, react_jsx_runtime.jsx)(require_toaster.PopoverTrigger, {
656
+ asChild: true,
657
+ children: (0, react_jsx_runtime.jsx)(require_button.Button, {
658
+ type: "button",
659
+ variant: "outline",
660
+ size: "sm",
661
+ autoFocus,
662
+ "data-testid": testId,
663
+ "aria-label": ariaLabel,
664
+ className: require_use_theme.cn("h-8 justify-between", fill && "w-full"),
665
+ children: triggerLabel(labelOf, ariaLabel)
666
+ })
667
+ }), (0, react_jsx_runtime.jsx)(require_toaster.PopoverContent, {
668
+ className: "w-[140px] p-0",
669
+ align: "start",
670
+ children: (0, react_jsx_runtime.jsx)(require_search_access.Command, { children: (0, react_jsx_runtime.jsx)(require_search_access.CommandList, { children: (0, react_jsx_runtime.jsxs)(require_search_access.CommandGroup, { children: [arity === "single" && selected.size > 0 && (0, react_jsx_runtime.jsxs)(require_search_access.CommandItem, {
671
+ "data-testid": `${testId}:clear`,
672
+ value: "__clear__",
673
+ onSelect: () => clear(),
674
+ children: [(0, react_jsx_runtime.jsx)(_tabler_icons_react.IconX, {
675
+ className: "mr-2 h-4 w-4",
676
+ "aria-hidden": "true"
677
+ }), clearLabel]
678
+ }), OPTIONS.map((token) => {
679
+ const isSelected = selected.has(token);
680
+ return (0, react_jsx_runtime.jsxs)(require_search_access.CommandItem, {
681
+ value: labelOf(token),
682
+ "data-testid": `${testId}:option:${token}`,
683
+ onSelect: () => select(token),
684
+ children: [(0, react_jsx_runtime.jsx)(_tabler_icons_react.IconCheck, {
685
+ className: require_use_theme.cn("mr-2 h-4 w-4", isSelected ? "opacity-100" : "opacity-0"),
686
+ "aria-hidden": "true"
687
+ }), labelOf(token)]
688
+ }, token);
689
+ })] }) }) })
690
+ })]
691
+ });
692
+ }
693
+ //#endregion
694
+ //#region src/list-kit/filter-builder/values/select-list-value-control.tsx
695
+ function SelectListValueControl(props) {
696
+ const { options, value, onChange, testId, arity, autoFocus, fieldLabel, isLoading = false } = props;
697
+ const t = (0, _burojs_react.useOptionalT)();
698
+ const ariaLabel = fieldLabel ? t("filters.valueFor", {
699
+ defaultValue: `Value for ${fieldLabel}`,
700
+ params: { field: fieldLabel }
701
+ }) : t("filters.value", { defaultValue: "Value" });
702
+ const loadingLabel = t("table.loading", { defaultValue: "Loading…" });
703
+ const clearLabel = t("filters.clear", { defaultValue: "Clear" });
704
+ const labelFor = props.labelFor ?? ((raw) => options.find((o) => o.value === raw)?.label ?? raw);
705
+ const { open, setOpen, selected, select, triggerLabel, clear } = useSelectableValueControl({
706
+ value,
707
+ arity,
708
+ onChange
709
+ });
710
+ return (0, react_jsx_runtime.jsxs)(require_toaster.Popover, {
711
+ open,
712
+ onOpenChange: setOpen,
713
+ children: [(0, react_jsx_runtime.jsx)(require_toaster.PopoverTrigger, {
714
+ asChild: true,
715
+ children: (0, react_jsx_runtime.jsx)(require_button.Button, {
716
+ type: "button",
717
+ variant: "outline",
718
+ size: "sm",
719
+ autoFocus,
720
+ "data-testid": testId,
721
+ "aria-label": ariaLabel,
722
+ className: require_use_theme.cn("h-8 justify-between truncate", props.fill ? "w-full" : "max-w-[200px]"),
723
+ children: (0, react_jsx_runtime.jsx)("span", {
724
+ className: "truncate",
725
+ children: isLoading ? loadingLabel : triggerLabel(labelFor, ariaLabel)
726
+ })
727
+ })
728
+ }), (0, react_jsx_runtime.jsx)(require_toaster.PopoverContent, {
729
+ className: "w-[220px] p-0",
730
+ align: "start",
731
+ children: (0, react_jsx_runtime.jsxs)(require_search_access.Command, { children: [(0, react_jsx_runtime.jsx)(require_search_access.CommandInput, {
732
+ placeholder: ariaLabel,
733
+ "data-testid": `${testId}:search`
734
+ }), (0, react_jsx_runtime.jsxs)(require_search_access.CommandList, { children: [
735
+ isLoading && (0, react_jsx_runtime.jsxs)("div", {
736
+ className: "flex items-center justify-center gap-2 py-6 text-sm text-muted-foreground",
737
+ "data-testid": `${testId}:loading`,
738
+ children: [(0, react_jsx_runtime.jsx)(_tabler_icons_react.IconLoader2, {
739
+ className: "h-4 w-4 animate-spin",
740
+ "aria-hidden": "true"
741
+ }), loadingLabel]
742
+ }),
743
+ !isLoading && (0, react_jsx_runtime.jsx)(require_search_access.CommandEmpty, { children: t("relation.noResults", { defaultValue: "No results." }) }),
744
+ !isLoading && (0, react_jsx_runtime.jsxs)(require_search_access.CommandGroup, { children: [arity === "single" && selected.size > 0 && (0, react_jsx_runtime.jsxs)(require_search_access.CommandItem, {
745
+ "data-testid": `${testId}:clear`,
746
+ value: "__clear__",
747
+ onSelect: () => clear(),
748
+ children: [(0, react_jsx_runtime.jsx)(_tabler_icons_react.IconX, {
749
+ className: "mr-2 h-4 w-4",
750
+ "aria-hidden": "true"
751
+ }), clearLabel]
752
+ }), options.map((entry) => {
753
+ const isSelected = selected.has(entry.value);
754
+ const label = labelFor(entry.value);
755
+ return (0, react_jsx_runtime.jsxs)(require_search_access.CommandItem, {
756
+ value: label,
757
+ "data-testid": `${testId}:option:${entry.value}`,
758
+ onSelect: () => select(entry.value),
759
+ children: [(0, react_jsx_runtime.jsx)(_tabler_icons_react.IconCheck, {
760
+ className: require_use_theme.cn("mr-2 h-4 w-4", isSelected ? "opacity-100" : "opacity-0"),
761
+ "aria-hidden": "true"
762
+ }), label]
763
+ }, entry.value);
764
+ })] })
765
+ ] })] })
766
+ })]
767
+ });
768
+ }
769
+ //#endregion
770
+ //#region src/list-kit/filter-builder/values/enum-value-control.tsx
771
+ function EnumValueControl(props) {
772
+ const { enumName, value, onChange, testId, arity, autoFocus, fieldLabel, fill } = props;
773
+ const caps = (0, _burojs_react.useI18nCaps)();
774
+ const { values, isLoading } = (0, _burojs_react.useEnumValues)(enumName);
775
+ const labelFor = (raw) => (0, _burojs_core.resolveEnumLabel)(caps, enumName, raw, values?.find((v) => v.value === raw)?.label);
776
+ const options = (values ?? []).map((entry) => ({
777
+ value: entry.value,
778
+ label: entry.value
779
+ }));
780
+ return (0, react_jsx_runtime.jsx)(SelectListValueControl, {
781
+ value,
782
+ onChange,
783
+ testId,
784
+ arity,
785
+ autoFocus,
786
+ fieldLabel,
787
+ options,
788
+ labelFor,
789
+ isLoading,
790
+ fill
791
+ });
792
+ }
793
+ //#endregion
794
+ //#region src/list-kit/use-related-labels.ts
795
+ function useRelatedLabels(to, ids, by) {
796
+ const targetDef = (0, _burojs_react.useApp)().registry.get(to);
797
+ const idField = (targetDef?.data)?.idField;
798
+ const rowTitle = require_search_access.useRowTitle(to, targetDef?.displayField);
799
+ const field = by ?? (typeof idField === "string" && idField.length > 0 ? idField : "id");
800
+ const query = (0, _burojs_react.useList)(to, {
801
+ pagination: {
802
+ kind: "offset",
803
+ page: 1,
804
+ pageSize: Math.min(Math.max(1, ids.length), 100)
805
+ },
806
+ ...ids.length ? { filter: {
807
+ field,
808
+ op: "in",
809
+ value: ids
810
+ } } : {},
811
+ enabled: ids.length > 0
812
+ });
813
+ return (0, react.useMemo)(() => {
814
+ const out = {};
815
+ for (const row of query.data?.data ?? []) {
816
+ const key = row[field];
817
+ if (key !== null && key !== void 0) out[String(key)] = rowTitle(row);
818
+ }
819
+ return out;
820
+ }, [
821
+ query.data,
822
+ rowTitle,
823
+ field
824
+ ]);
825
+ }
826
+ //#endregion
827
+ //#region src/list-kit/filter-builder/values/relation-value-control.tsx
828
+ function RelationValueControl(props) {
829
+ const { to, by, value, onChange, testId, arity, autoFocus, fieldLabel } = props;
830
+ const app = (0, _burojs_react.useApp)();
831
+ const t = (0, _burojs_react.useOptionalT)();
832
+ const targetDef = app.registry.get(to);
833
+ const displayField = targetDef?.displayField ?? "name";
834
+ const [search, setSearch] = (0, react.useState)("");
835
+ const { open, setOpen, selected, select, triggerLabel, clear } = useSelectableValueControl({
836
+ value,
837
+ arity,
838
+ onChange
839
+ });
840
+ const rowTitle = require_search_access.useRowTitle(to, targetDef?.displayField);
841
+ const access = (0, _burojs_react.useListAccess)(to);
842
+ const searchable = (0, react.useMemo)(() => access.status === "ready" && require_search_access.narrowSearchDecl({ fields: [displayField] }, (name) => access.fieldVisible(name)) !== void 0, [access, displayField]);
843
+ const listQuery = (0, _burojs_react.useList)(to, {
844
+ pagination: {
845
+ kind: "offset",
846
+ page: 1,
847
+ pageSize: 10
848
+ },
849
+ ...searchable && search.trim() ? { filter: {
850
+ field: displayField,
851
+ op: "contains",
852
+ value: search.trim()
853
+ } } : {},
854
+ enabled: open && access.status === "ready"
855
+ });
856
+ const rows = listQuery.data?.data ?? [];
857
+ const fallbackLabels = useRelatedLabels(to, Array.from(selected), by);
858
+ const idField = (targetDef?.data)?.idField;
859
+ const valueField = by ?? (typeof idField === "string" && idField.length > 0 ? idField : "id");
860
+ const keyOf = (row) => {
861
+ const key = row[valueField];
862
+ return key === null || key === void 0 || key === "" ? void 0 : String(key);
863
+ };
864
+ const labelFor = (id) => {
865
+ const fromRows = rows.find((r) => keyOf(r) === id);
866
+ if (fromRows) return rowTitle(fromRows);
867
+ return fallbackLabels[id] ?? id;
868
+ };
869
+ const ariaLabel = fieldLabel ? t("filters.valueFor", {
870
+ defaultValue: `Value for ${fieldLabel}`,
871
+ params: { field: fieldLabel }
872
+ }) : t("filters.value", { defaultValue: "Value" });
873
+ const clearLabel = t("filters.clear", { defaultValue: "Clear" });
874
+ return (0, react_jsx_runtime.jsxs)(require_toaster.Popover, {
875
+ open,
876
+ onOpenChange: setOpen,
877
+ children: [(0, react_jsx_runtime.jsx)(require_toaster.PopoverTrigger, {
878
+ asChild: true,
879
+ children: (0, react_jsx_runtime.jsx)(require_button.Button, {
880
+ type: "button",
881
+ variant: "outline",
882
+ size: "sm",
883
+ autoFocus,
884
+ "data-testid": testId,
885
+ "aria-label": ariaLabel,
886
+ className: require_use_theme.cn("h-8 justify-between truncate", props.fill ? "w-full" : "max-w-[220px]"),
887
+ children: (0, react_jsx_runtime.jsx)("span", {
888
+ className: "truncate",
889
+ children: triggerLabel(labelFor, ariaLabel)
890
+ })
891
+ })
892
+ }), (0, react_jsx_runtime.jsx)(require_toaster.PopoverContent, {
893
+ className: "w-[240px] p-0",
894
+ align: "start",
895
+ children: (0, react_jsx_runtime.jsxs)(require_search_access.Command, {
896
+ shouldFilter: false,
897
+ children: [searchable && (0, react_jsx_runtime.jsx)(require_search_access.CommandInput, {
898
+ value: search,
899
+ onValueChange: setSearch,
900
+ placeholder: ariaLabel,
901
+ "data-testid": `${testId}:search`
902
+ }), (0, react_jsx_runtime.jsxs)(require_search_access.CommandList, { children: [
903
+ listQuery.isLoading && (0, react_jsx_runtime.jsxs)("div", {
904
+ className: "flex items-center justify-center gap-2 py-6 text-sm text-muted-foreground",
905
+ "data-testid": `${testId}:loading`,
906
+ children: [(0, react_jsx_runtime.jsx)(_tabler_icons_react.IconLoader2, {
907
+ className: "h-4 w-4 animate-spin",
908
+ "aria-hidden": "true"
909
+ }), t("table.loading", { defaultValue: "Loading…" })]
910
+ }),
911
+ !listQuery.isLoading && rows.length === 0 && (0, react_jsx_runtime.jsx)(require_search_access.CommandEmpty, { children: t("relation.noResults", { defaultValue: "No results." }) }),
912
+ !listQuery.isLoading && arity === "single" && selected.size > 0 && (0, react_jsx_runtime.jsxs)(require_search_access.CommandItem, {
913
+ "data-testid": `${testId}:clear`,
914
+ value: "__clear__",
915
+ onSelect: () => clear(),
916
+ children: [(0, react_jsx_runtime.jsx)(_tabler_icons_react.IconX, {
917
+ className: "mr-2 h-4 w-4",
918
+ "aria-hidden": "true"
919
+ }), clearLabel]
920
+ }),
921
+ !listQuery.isLoading && rows.map((row) => {
922
+ const id = keyOf(row);
923
+ if (id === void 0) return null;
924
+ const label = rowTitle(row);
925
+ const isSelected = selected.has(id);
926
+ return (0, react_jsx_runtime.jsxs)(require_search_access.CommandItem, {
927
+ value: `${label} ${id}`,
928
+ "data-testid": `${testId}:option:${id}`,
929
+ onSelect: () => select(id),
930
+ children: [(0, react_jsx_runtime.jsx)(_tabler_icons_react.IconCheck, {
931
+ className: require_use_theme.cn("mr-2 h-4 w-4", isSelected ? "opacity-100" : "opacity-0"),
932
+ "aria-hidden": "true"
933
+ }), (0, react_jsx_runtime.jsx)("span", {
934
+ className: "truncate",
935
+ children: label
936
+ })]
937
+ }, id);
938
+ })
939
+ ] })]
940
+ })
941
+ })]
942
+ });
943
+ }
944
+ //#endregion
945
+ //#region src/list-kit/filter-builder/values/scalar-value-input.tsx
946
+ function isDateTime(kind) {
947
+ return kind.kind === "date" && kind.time === true;
948
+ }
949
+ function inputTypeFor(kind) {
950
+ switch (kind.kind) {
951
+ case "number": return "number";
952
+ case "date": return isDateTime(kind) ? "datetime-local" : "date";
953
+ default: return "text";
954
+ }
955
+ }
956
+ function toText(kind, value) {
957
+ if (value === void 0 || value === null || value === "") return "";
958
+ if (!isDateTime(kind)) return String(value);
959
+ const d = new Date(String(value));
960
+ if (Number.isNaN(d.getTime())) return "";
961
+ const pad = (n) => String(n).padStart(2, "0");
962
+ return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())}T${pad(d.getHours())}:${pad(d.getMinutes())}`;
963
+ }
964
+ function parseOne(kind, raw) {
965
+ if (raw === "") return void 0;
966
+ if (kind.kind === "number") {
967
+ const n = Number(raw);
968
+ return Number.isFinite(n) ? n : void 0;
969
+ }
970
+ if (isDateTime(kind)) {
971
+ const d = new Date(raw);
972
+ return Number.isNaN(d.getTime()) ? void 0 : d.toISOString();
973
+ }
974
+ return raw;
975
+ }
976
+ function ScalarValueInput(props) {
977
+ const t = (0, _burojs_react.useOptionalT)();
978
+ const inputType = inputTypeFor(props.kind);
979
+ const ariaLabel = props.fieldLabel ? t("filters.valueFor", {
980
+ defaultValue: `Value for ${props.fieldLabel}`,
981
+ params: { field: props.fieldLabel }
982
+ }) : t("filters.value", { defaultValue: "Value" });
983
+ if (props.arity === "multi") return (0, react_jsx_runtime.jsx)(ScalarMultiInput, {
984
+ ...props,
985
+ inputType,
986
+ ariaLabel
987
+ });
988
+ return (0, react_jsx_runtime.jsx)(ScalarSingleInput, {
989
+ ...props,
990
+ inputType,
991
+ ariaLabel
992
+ });
993
+ }
994
+ function ScalarSingleInput(props) {
995
+ const { value, onChange, kind, testId, autoFocus, inputType, ariaLabel, debounceMs = 300 } = props;
996
+ const storedText = toText(kind, value);
997
+ const [text, setText] = (0, react.useState)(storedText);
998
+ const lastPushedRef = (0, react.useRef)(storedText);
999
+ const timerRef = (0, react.useRef)(null);
1000
+ const pendingRawRef = (0, react.useRef)(null);
1001
+ const commitRef = (0, react.useRef)(() => {});
1002
+ (0, react.useEffect)(() => {
1003
+ commitRef.current = (raw) => {
1004
+ const parsed = parseOne(kind, raw);
1005
+ lastPushedRef.current = toText(kind, parsed);
1006
+ pendingRawRef.current = null;
1007
+ onChange(parsed);
1008
+ };
1009
+ });
1010
+ (0, react.useEffect)(() => {
1011
+ if (storedText !== lastPushedRef.current) {
1012
+ setText(storedText);
1013
+ lastPushedRef.current = storedText;
1014
+ if (timerRef.current) {
1015
+ clearTimeout(timerRef.current);
1016
+ timerRef.current = null;
1017
+ }
1018
+ pendingRawRef.current = null;
1019
+ }
1020
+ }, [storedText]);
1021
+ (0, react.useEffect)(() => () => {
1022
+ if (timerRef.current) clearTimeout(timerRef.current);
1023
+ if (pendingRawRef.current !== null) commitRef.current(pendingRawRef.current);
1024
+ }, []);
1025
+ const handleChange = (raw) => {
1026
+ setText(raw);
1027
+ if (timerRef.current) {
1028
+ clearTimeout(timerRef.current);
1029
+ timerRef.current = null;
1030
+ }
1031
+ if (debounceMs <= 0) {
1032
+ pendingRawRef.current = null;
1033
+ commitRef.current(raw);
1034
+ return;
1035
+ }
1036
+ pendingRawRef.current = raw;
1037
+ timerRef.current = setTimeout(() => commitRef.current(raw), debounceMs);
1038
+ };
1039
+ return (0, react_jsx_runtime.jsx)(require_input.Input, {
1040
+ type: inputType,
1041
+ value: text,
1042
+ autoFocus,
1043
+ "aria-label": ariaLabel,
1044
+ placeholder: props.placeholder,
1045
+ "data-testid": testId,
1046
+ onChange: (event) => handleChange(event.target.value),
1047
+ className: require_use_theme.cn("h-8", props.fill ? "w-full" : "w-[150px]")
1048
+ });
1049
+ }
1050
+ function ScalarMultiInput(props) {
1051
+ const { value, onChange, kind, testId, autoFocus, inputType, ariaLabel } = props;
1052
+ const chips = Array.isArray(value) ? value.map((v) => String(v)) : [];
1053
+ const [draft, setDraft] = (0, react.useState)("");
1054
+ const addChip = () => {
1055
+ const parsed = parseOne(kind, draft.trim());
1056
+ if (parsed === void 0) return;
1057
+ const text = String(parsed);
1058
+ setDraft("");
1059
+ if (chips.includes(text)) return;
1060
+ onChange([...chips, text]);
1061
+ };
1062
+ const removeChip = (chip) => {
1063
+ const next = chips.filter((c) => c !== chip);
1064
+ onChange(next.length === 0 ? void 0 : next);
1065
+ };
1066
+ return (0, react_jsx_runtime.jsxs)("div", {
1067
+ className: require_use_theme.cn("flex flex-wrap items-center gap-1", props.fill && "w-full"),
1068
+ children: [chips.map((chip) => (0, react_jsx_runtime.jsxs)(Badge, {
1069
+ variant: "secondary",
1070
+ "data-testid": `${testId}:chip:${chip}`,
1071
+ className: "gap-1 rounded-sm px-1 font-normal",
1072
+ children: [chip, (0, react_jsx_runtime.jsx)("button", {
1073
+ type: "button",
1074
+ "data-testid": `${testId}:chip:${chip}:remove`,
1075
+ "aria-label": `Remove ${chip}`,
1076
+ onClick: () => removeChip(chip),
1077
+ className: "rounded-sm hover:bg-accent",
1078
+ children: (0, react_jsx_runtime.jsx)(_tabler_icons_react.IconX, {
1079
+ className: "h-3 w-3",
1080
+ "aria-hidden": "true"
1081
+ })
1082
+ })]
1083
+ }, chip)), (0, react_jsx_runtime.jsx)(require_input.Input, {
1084
+ type: inputType,
1085
+ value: draft,
1086
+ autoFocus,
1087
+ "aria-label": ariaLabel,
1088
+ placeholder: props.placeholder,
1089
+ "data-testid": testId,
1090
+ onChange: (event) => setDraft(event.target.value),
1091
+ onKeyDown: (event) => {
1092
+ if (event.key !== "Enter") return;
1093
+ event.preventDefault();
1094
+ addChip();
1095
+ },
1096
+ className: require_use_theme.cn("h-8", props.fill ? "min-w-[8rem] flex-1" : "w-[120px]")
1097
+ })]
1098
+ });
1099
+ }
1100
+ //#endregion
1101
+ //#region src/list-kit/filter-builder/values/filter-value-control.tsx
1102
+ function FilterValueControl(props) {
1103
+ const arity = (0, _burojs_core.opArity)(props.op);
1104
+ if (arity === "none") return null;
1105
+ if (props.options && props.options.length > 0) return (0, react_jsx_runtime.jsx)(SelectListValueControl, {
1106
+ value: props.value,
1107
+ onChange: props.onChange,
1108
+ testId: props.testId,
1109
+ arity,
1110
+ autoFocus: props.autoFocus,
1111
+ fieldLabel: props.fieldLabel,
1112
+ options: props.options,
1113
+ fill: props.fill
1114
+ });
1115
+ switch (props.kind.kind) {
1116
+ case "enum": return (0, react_jsx_runtime.jsx)(EnumValueControl, {
1117
+ ...props,
1118
+ enumName: props.kind.enumName,
1119
+ arity
1120
+ });
1121
+ case "enum-values": return (0, react_jsx_runtime.jsx)(SelectListValueControl, {
1122
+ value: props.value,
1123
+ onChange: props.onChange,
1124
+ testId: props.testId,
1125
+ arity,
1126
+ autoFocus: props.autoFocus,
1127
+ fieldLabel: props.fieldLabel,
1128
+ options: props.kind.values.map((entry) => ({
1129
+ value: entry,
1130
+ label: entry
1131
+ }))
1132
+ });
1133
+ case "relation": return (0, react_jsx_runtime.jsx)(RelationValueControl, {
1134
+ ...props,
1135
+ to: props.kind.to,
1136
+ ...props.kind.by === void 0 ? {} : { by: props.kind.by },
1137
+ arity
1138
+ });
1139
+ case "boolean": return (0, react_jsx_runtime.jsx)(BooleanValueControl, {
1140
+ ...props,
1141
+ arity
1142
+ });
1143
+ default: return (0, react_jsx_runtime.jsx)(ScalarValueInput, {
1144
+ ...props,
1145
+ arity
1146
+ });
1147
+ }
1148
+ }
1149
+ //#endregion
1150
+ //#region src/list-kit/filter-builder/filter-row.tsx
1151
+ const ROW_CLASS = "flex min-w-0 items-center gap-2";
1152
+ const FIELD_CLASS = "h-8 min-w-0 shrink basis-40";
1153
+ const OPERATOR_CLASS = "h-8 min-w-0 shrink basis-[9.5rem]";
1154
+ function FilterRow(props) {
1155
+ const { row, path, available, onPatch, onRemove, testIdBase, debounceMs } = props;
1156
+ const t = (0, _burojs_react.useOptionalT)();
1157
+ const base = `${testIdBase}:row:${path.join("-")}`;
1158
+ const spec = available.find((s) => s.field === row.field);
1159
+ const removeLabel = t("filters.removeRow", { defaultValue: "Remove filter" });
1160
+ if (!spec) return (0, react_jsx_runtime.jsxs)("div", {
1161
+ className: ROW_CLASS,
1162
+ children: [
1163
+ (0, react_jsx_runtime.jsx)(require_button.Button, {
1164
+ type: "button",
1165
+ variant: "outline",
1166
+ size: "sm",
1167
+ disabled: true,
1168
+ "data-testid": `${base}:field`,
1169
+ className: require_use_theme.cn(FIELD_CLASS, "justify-start truncate opacity-60"),
1170
+ children: row.field
1171
+ }),
1172
+ (0, react_jsx_runtime.jsx)("div", { className: "flex-1" }),
1173
+ (0, react_jsx_runtime.jsx)(require_button.Button, {
1174
+ type: "button",
1175
+ variant: "ghost",
1176
+ size: "icon",
1177
+ "aria-label": removeLabel,
1178
+ "data-testid": `${base}:remove`,
1179
+ onClick: () => onRemove(path),
1180
+ className: "shrink-0",
1181
+ children: (0, react_jsx_runtime.jsx)(_tabler_icons_react.IconX, {
1182
+ className: "h-4 w-4",
1183
+ "aria-hidden": "true"
1184
+ })
1185
+ })
1186
+ ]
1187
+ });
1188
+ const fieldTestId = `${base}:field`;
1189
+ const opTestId = `${base}:op`;
1190
+ const handleFieldChange = (field) => {
1191
+ const op = available.find((s) => s.field === field)?.operators[0] ?? row.op;
1192
+ onPatch(path, {
1193
+ field,
1194
+ op
1195
+ });
1196
+ };
1197
+ const handleOpChange = (op) => onPatch(path, { op });
1198
+ const handleValueChange = (value) => onPatch(path, { value });
1199
+ const fieldAriaLabel = t("filters.field", { defaultValue: "Field" });
1200
+ const operatorAriaLabel = t("filters.operator", { defaultValue: "Operator" });
1201
+ return (0, react_jsx_runtime.jsxs)("div", {
1202
+ className: ROW_CLASS,
1203
+ children: [
1204
+ (0, react_jsx_runtime.jsxs)(Select, {
1205
+ value: row.field,
1206
+ onValueChange: handleFieldChange,
1207
+ children: [(0, react_jsx_runtime.jsx)(SelectTrigger, {
1208
+ "data-testid": fieldTestId,
1209
+ "aria-label": fieldAriaLabel,
1210
+ className: FIELD_CLASS,
1211
+ children: (0, react_jsx_runtime.jsx)(SelectValue, {})
1212
+ }), (0, react_jsx_runtime.jsx)(SelectContent, { children: available.map((s) => (0, react_jsx_runtime.jsx)(SelectItem, {
1213
+ value: s.field,
1214
+ "data-testid": `${fieldTestId}:option:${s.field}`,
1215
+ children: s.label
1216
+ }, s.field)) })]
1217
+ }),
1218
+ (0, react_jsx_runtime.jsxs)(Select, {
1219
+ value: row.op,
1220
+ onValueChange: handleOpChange,
1221
+ children: [(0, react_jsx_runtime.jsx)(SelectTrigger, {
1222
+ "data-testid": opTestId,
1223
+ "aria-label": operatorAriaLabel,
1224
+ className: OPERATOR_CLASS,
1225
+ children: (0, react_jsx_runtime.jsx)(SelectValue, {})
1226
+ }), (0, react_jsx_runtime.jsx)(SelectContent, { children: spec.operators.map((op) => (0, react_jsx_runtime.jsx)(SelectItem, {
1227
+ value: op,
1228
+ "data-testid": `${opTestId}:option:${op}`,
1229
+ children: t(`filters.operators.${op}`, { defaultValue: op })
1230
+ }, op)) })]
1231
+ }),
1232
+ (0, react_jsx_runtime.jsx)("div", {
1233
+ className: "min-w-0 flex-1",
1234
+ children: (0, react_jsx_runtime.jsx)(FilterValueControl, {
1235
+ kind: spec.kind,
1236
+ op: row.op,
1237
+ value: row.value,
1238
+ onChange: handleValueChange,
1239
+ testId: `${base}:value`,
1240
+ debounceMs,
1241
+ fieldLabel: spec.label,
1242
+ options: spec.options,
1243
+ fill: true
1244
+ })
1245
+ }),
1246
+ (0, react_jsx_runtime.jsx)(require_button.Button, {
1247
+ type: "button",
1248
+ variant: "ghost",
1249
+ size: "icon",
1250
+ "aria-label": removeLabel,
1251
+ "data-testid": `${base}:remove`,
1252
+ onClick: () => onRemove(path),
1253
+ className: "shrink-0",
1254
+ children: (0, react_jsx_runtime.jsx)(_tabler_icons_react.IconX, {
1255
+ className: "h-4 w-4",
1256
+ "aria-hidden": "true"
1257
+ })
1258
+ })
1259
+ ]
1260
+ });
1261
+ }
1262
+ //#endregion
1263
+ //#region src/list-kit/filter-builder/filter-group-editor.tsx
1264
+ const JOIN_VALUES = ["and", "or"];
1265
+ const GROUP_CLASS = "grid grid-cols-[auto_minmax(0,1fr)] items-start gap-2";
1266
+ const CONNECTOR_CLASS = "pt-1.5 text-right text-sm text-muted-foreground";
1267
+ function FilterGroupEditor(props) {
1268
+ const { group, onChange } = props;
1269
+ const addGroupAt = (targetPath) => onChange(addGroup(group, targetPath));
1270
+ return (0, react_jsx_runtime.jsx)(GroupEditorInner, {
1271
+ ...props,
1272
+ addGroupAt
1273
+ });
1274
+ }
1275
+ function GroupEditorInner(props) {
1276
+ const { group, path, available, onChange, testIdBase, onRemove, addGroupAt, debounceMs } = props;
1277
+ const t = (0, _burojs_react.useOptionalT)();
1278
+ const base = `${testIdBase}:group:${path.join("-")}`;
1279
+ const canNestGroup = depthOf(path) < 3;
1280
+ const joinLabel = (join) => t(`filters.${join}`, { defaultValue: join });
1281
+ const handlePatch = (rowPath, patch) => {
1282
+ const index = rowPath[rowPath.length - 1];
1283
+ if (index === void 0) return;
1284
+ onChange(updateRow(group, [index], patch));
1285
+ };
1286
+ const handleRemove = (nodePath) => {
1287
+ const index = nodePath[nodePath.length - 1];
1288
+ if (index === void 0) return;
1289
+ onChange(removeAt(group, [index]));
1290
+ };
1291
+ const handleChildGroupChange = (index, next) => {
1292
+ if (next === group.rows[index]) return;
1293
+ const rows = group.rows.slice();
1294
+ rows[index] = next;
1295
+ onChange({
1296
+ ...group,
1297
+ rows
1298
+ });
1299
+ };
1300
+ const handleAddRow = () => onChange(addRow(group, [], available));
1301
+ const handleAddGroup = () => addGroupAt(path);
1302
+ const handleSetJoin = (join) => onChange(setJoin(group, [], join));
1303
+ const otherJoin = group.join === "and" ? "or" : "and";
1304
+ return (0, react_jsx_runtime.jsxs)("div", {
1305
+ className: GROUP_CLASS,
1306
+ "data-testid": base,
1307
+ children: [
1308
+ group.rows.map((node, index) => {
1309
+ const childPath = [...path, index];
1310
+ return (0, react_jsx_runtime.jsxs)(react.Fragment, { children: [(0, react_jsx_runtime.jsxs)("div", {
1311
+ className: CONNECTOR_CLASS,
1312
+ children: [
1313
+ index === 0 && t("filters.where", { defaultValue: "Where" }),
1314
+ index === 1 && (0, react_jsx_runtime.jsx)("button", {
1315
+ type: "button",
1316
+ "data-testid": `${base}:join`,
1317
+ "aria-label": t("filters.toggleJoin", {
1318
+ defaultValue: `Join: ${joinLabel(group.join)} — activate to switch to ${joinLabel(otherJoin)}`,
1319
+ params: {
1320
+ current: joinLabel(group.join),
1321
+ next: joinLabel(otherJoin)
1322
+ }
1323
+ }),
1324
+ "aria-pressed": group.join === "or",
1325
+ onClick: () => handleSetJoin(otherJoin),
1326
+ className: "inline-grid justify-items-end rounded-xs font-medium text-foreground underline underline-offset-2 hover:no-underline focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-1",
1327
+ children: JOIN_VALUES.map((j) => (0, react_jsx_runtime.jsx)("span", {
1328
+ className: require_use_theme.cn("col-start-1 row-start-1", j !== group.join && "invisible"),
1329
+ children: joinLabel(j)
1330
+ }, j))
1331
+ }),
1332
+ index >= 2 && joinLabel(group.join)
1333
+ ]
1334
+ }), isDraftGroup(node) ? (0, react_jsx_runtime.jsx)("div", {
1335
+ className: "min-w-0 rounded-md border bg-muted/30 p-2",
1336
+ children: (0, react_jsx_runtime.jsx)(GroupEditorInner, {
1337
+ group: node,
1338
+ path: childPath,
1339
+ available,
1340
+ onChange: (next) => handleChildGroupChange(index, next),
1341
+ testIdBase,
1342
+ onRemove: handleRemove,
1343
+ addGroupAt,
1344
+ debounceMs
1345
+ })
1346
+ }) : (0, react_jsx_runtime.jsx)(FilterRow, {
1347
+ row: node,
1348
+ path: childPath,
1349
+ available,
1350
+ onPatch: handlePatch,
1351
+ onRemove: handleRemove,
1352
+ testIdBase,
1353
+ debounceMs
1354
+ })] }, node.rowId);
1355
+ }),
1356
+ (0, react_jsx_runtime.jsx)("div", { "aria-hidden": "true" }),
1357
+ (0, react_jsx_runtime.jsxs)("div", {
1358
+ className: "flex items-center gap-2",
1359
+ children: [
1360
+ (0, react_jsx_runtime.jsxs)(require_button.Button, {
1361
+ type: "button",
1362
+ variant: "ghost",
1363
+ size: "sm",
1364
+ "data-testid": `${base}:add-row`,
1365
+ onClick: handleAddRow,
1366
+ children: [(0, react_jsx_runtime.jsx)(_tabler_icons_react.IconPlus, {
1367
+ className: "h-4 w-4",
1368
+ "aria-hidden": "true"
1369
+ }), t("filters.addRow", { defaultValue: "Add filter" })]
1370
+ }),
1371
+ canNestGroup && (0, react_jsx_runtime.jsxs)(require_button.Button, {
1372
+ type: "button",
1373
+ variant: "ghost",
1374
+ size: "sm",
1375
+ "data-testid": `${base}:add-group`,
1376
+ onClick: handleAddGroup,
1377
+ children: [(0, react_jsx_runtime.jsx)(_tabler_icons_react.IconFolderPlus, {
1378
+ className: "h-4 w-4",
1379
+ "aria-hidden": "true"
1380
+ }), t("filters.addGroup", { defaultValue: "Add group" })]
1381
+ }),
1382
+ onRemove && (0, react_jsx_runtime.jsxs)(require_button.Button, {
1383
+ type: "button",
1384
+ variant: "ghost",
1385
+ size: "sm",
1386
+ "data-testid": `${base}:remove`,
1387
+ onClick: () => onRemove(path),
1388
+ children: [(0, react_jsx_runtime.jsx)(_tabler_icons_react.IconX, {
1389
+ className: "h-4 w-4",
1390
+ "aria-hidden": "true"
1391
+ }), t("filters.removeGroup", { defaultValue: "Remove group" })]
1392
+ })
1393
+ ]
1394
+ })
1395
+ ]
1396
+ });
1397
+ }
1398
+ //#endregion
1399
+ //#region src/list-kit/filter-builder/filter-panel.tsx
1400
+ function countGroupRows(group) {
1401
+ let total = 0;
1402
+ for (const row of group.rows) total += "rows" in row ? countGroupRows(row) : 1;
1403
+ return total;
1404
+ }
1405
+ function FilterPanel(props) {
1406
+ const { group, setGroup, available, setPage, testIdBase, open, onOpenChange, focusPath, externalRemoval } = props;
1407
+ const t = (0, _burojs_react.useOptionalT)();
1408
+ const [draft, setDraft] = (0, react.useState)(() => toDraft(group));
1409
+ const wasOpenRef = (0, react.useRef)(open);
1410
+ const topLevelIdsRef = (0, react.useRef)([]);
1411
+ const panelRef = (0, react.useRef)(null);
1412
+ (0, react.useEffect)(() => {
1413
+ if (open && !wasOpenRef.current) {
1414
+ const seeded = toDraft(group);
1415
+ setDraft(seeded);
1416
+ topLevelIdsRef.current = seeded.rows.map((node) => node.rowId);
1417
+ if (focusPath && focusPath.length > 0) {
1418
+ const targetTestId = `${testIdBase}:row:${focusPath.join("-")}:field`;
1419
+ requestAnimationFrame(() => {
1420
+ panelRef.current?.querySelector(`[data-testid="${targetTestId}"]`)?.focus();
1421
+ });
1422
+ }
1423
+ }
1424
+ wasOpenRef.current = open;
1425
+ }, [open]);
1426
+ (0, react.useEffect)(() => {
1427
+ if (!externalRemoval) return;
1428
+ const { index } = externalRemoval;
1429
+ const rowId = topLevelIdsRef.current[index];
1430
+ topLevelIdsRef.current = topLevelIdsRef.current.filter((_, i) => i !== index);
1431
+ if (rowId === void 0) return;
1432
+ setDraft((current) => {
1433
+ const foundIndex = current.rows.findIndex((node) => node.rowId === rowId);
1434
+ return foundIndex < 0 ? current : removeAt(current, [foundIndex]);
1435
+ });
1436
+ }, [externalRemoval]);
1437
+ const handleApply = () => {
1438
+ setGroup(pruneIncompleteRows(fromDraft(draft)));
1439
+ setPage(1);
1440
+ onOpenChange(false);
1441
+ };
1442
+ const handleReset = () => setDraft(EMPTY_DRAFT());
1443
+ const appliedCount = countGroupRows(group);
1444
+ return (0, react_jsx_runtime.jsxs)(require_toaster.Popover, {
1445
+ open,
1446
+ onOpenChange,
1447
+ children: [(0, react_jsx_runtime.jsx)(require_toaster.PopoverTrigger, {
1448
+ asChild: true,
1449
+ children: (0, react_jsx_runtime.jsxs)(require_button.Button, {
1450
+ type: "button",
1451
+ variant: "outline",
1452
+ size: "sm",
1453
+ "data-testid": `${testIdBase}:button`,
1454
+ children: [
1455
+ (0, react_jsx_runtime.jsx)(_tabler_icons_react.IconFilter, {
1456
+ className: "h-4 w-4",
1457
+ "aria-hidden": "true"
1458
+ }),
1459
+ t("filters.title", { defaultValue: "Filters" }),
1460
+ appliedCount > 0 && (0, react_jsx_runtime.jsx)("span", {
1461
+ "data-testid": `${testIdBase}:count`,
1462
+ className: "ml-1 rounded-full bg-primary px-1.5 text-xs text-primary-foreground",
1463
+ children: appliedCount
1464
+ })
1465
+ ]
1466
+ })
1467
+ }), (0, react_jsx_runtime.jsxs)(require_toaster.PopoverContent, {
1468
+ ref: panelRef,
1469
+ "data-testid": `${testIdBase}:panel`,
1470
+ align: "start",
1471
+ className: "w-[min(46rem,calc(100vw-2rem))]",
1472
+ children: [(0, react_jsx_runtime.jsx)(FilterGroupEditor, {
1473
+ group: draft,
1474
+ path: [],
1475
+ available,
1476
+ onChange: setDraft,
1477
+ testIdBase,
1478
+ debounceMs: 0
1479
+ }), (0, react_jsx_runtime.jsxs)("div", {
1480
+ className: "mt-3 flex justify-end gap-2 border-t pt-3",
1481
+ children: [(0, react_jsx_runtime.jsx)(require_button.Button, {
1482
+ type: "button",
1483
+ variant: "ghost",
1484
+ size: "sm",
1485
+ "data-testid": `${testIdBase}:reset`,
1486
+ onClick: handleReset,
1487
+ children: t("filters.reset", { defaultValue: "Reset" })
1488
+ }), (0, react_jsx_runtime.jsx)(require_button.Button, {
1489
+ type: "button",
1490
+ size: "sm",
1491
+ "data-testid": `${testIdBase}:apply`,
1492
+ onClick: handleApply,
1493
+ children: t("filters.apply", { defaultValue: "Apply" })
1494
+ })]
1495
+ })]
1496
+ })]
1497
+ });
1498
+ }
1499
+ //#endregion
1500
+ //#region src/list-kit/filter-builder/pinned-filter-controls.tsx
1501
+ function isEmptyValue(value) {
1502
+ return value === void 0 || value === "" || Array.isArray(value) && value.length === 0;
1503
+ }
1504
+ function PinnedFilterControls(props) {
1505
+ const { pinnedFields, value, set, testIdBase } = props;
1506
+ return (0, react_jsx_runtime.jsx)(react_jsx_runtime.Fragment, { children: pinnedFields.map((spec) => (0, react_jsx_runtime.jsx)(PinnedControl, {
1507
+ spec,
1508
+ value,
1509
+ set,
1510
+ testIdBase
1511
+ }, spec.field)) });
1512
+ }
1513
+ function PinnedControl(props) {
1514
+ const { spec, value, set, testIdBase } = props;
1515
+ const op = spec.operators[0] ?? "eq";
1516
+ const index = findOwnedIndex(value, spec);
1517
+ const owned = index >= 0 ? value[index] : void 0;
1518
+ const handleChange = (next) => {
1519
+ if (isEmptyValue(next)) {
1520
+ if (index < 0) return;
1521
+ set(value.filter((_, i) => i !== index));
1522
+ return;
1523
+ }
1524
+ const predicate = {
1525
+ field: spec.field,
1526
+ op,
1527
+ value: next
1528
+ };
1529
+ if (index < 0) set([...value, predicate]);
1530
+ else {
1531
+ const nextValue = value.slice();
1532
+ nextValue[index] = predicate;
1533
+ set(nextValue);
1534
+ }
1535
+ };
1536
+ return (0, react_jsx_runtime.jsx)(FilterValueControl, {
1537
+ kind: spec.kind,
1538
+ op,
1539
+ value: owned?.value,
1540
+ onChange: handleChange,
1541
+ testId: `${testIdBase}:pinned:${spec.field}`,
1542
+ fieldLabel: spec.label,
1543
+ placeholder: spec.label,
1544
+ options: spec.options
1545
+ });
1546
+ }
1547
+ //#endregion
1548
+ //#region src/list-kit/filter-builder/filter-builder.tsx
1549
+ const EMPTY_PINNED_FIELDS = Object.freeze([]);
1550
+ const EMPTY_PINNED_VALUE = Object.freeze([]);
1551
+ function FilterBuilder(props) {
1552
+ const { group, setGroup, available, fixed, pinnedFields = EMPTY_PINNED_FIELDS, pinnedValue = EMPTY_PINNED_VALUE, setPinnedValue, setPage, testIdBase } = props;
1553
+ const [open, setOpen] = (0, react.useState)(false);
1554
+ const [focusPath, setFocusPath] = (0, react.useState)(void 0);
1555
+ const [externalRemoval, setExternalRemoval] = (0, react.useState)(void 0);
1556
+ const handleOpenChange = (next) => {
1557
+ setOpen(next);
1558
+ if (!next) setFocusPath(void 0);
1559
+ };
1560
+ const setGroupResettingPage = (next) => {
1561
+ setPage(1);
1562
+ setGroup(next);
1563
+ };
1564
+ const setPinnedResettingPage = (next) => {
1565
+ setPage(1);
1566
+ setPinnedValue?.(next);
1567
+ };
1568
+ return (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
1569
+ (0, react_jsx_runtime.jsx)(PinnedFilterControls, {
1570
+ pinnedFields,
1571
+ value: pinnedValue,
1572
+ set: setPinnedResettingPage,
1573
+ testIdBase
1574
+ }),
1575
+ available.length > 0 && (0, react_jsx_runtime.jsx)(FilterPanel, {
1576
+ group,
1577
+ setGroup,
1578
+ available,
1579
+ setPage,
1580
+ testIdBase,
1581
+ open,
1582
+ onOpenChange: handleOpenChange,
1583
+ focusPath,
1584
+ externalRemoval
1585
+ }),
1586
+ (0, react_jsx_runtime.jsx)(FilterBadges, {
1587
+ group,
1588
+ setGroup: setGroupResettingPage,
1589
+ available,
1590
+ fixed,
1591
+ pinnedFields,
1592
+ pinnedValue,
1593
+ setPinnedValue: setPinnedResettingPage,
1594
+ testIdBase,
1595
+ onOpenAt: (path) => {
1596
+ setFocusPath(path);
1597
+ setOpen(true);
1598
+ },
1599
+ onRemoved: (index) => setExternalRemoval({ index })
1600
+ })
1601
+ ] });
1602
+ }
1603
+ //#endregion
1604
+ Object.defineProperty(exports, "Badge", {
1605
+ enumerable: true,
1606
+ get: function() {
1607
+ return Badge;
1608
+ }
1609
+ });
1610
+ Object.defineProperty(exports, "FilterBadges", {
1611
+ enumerable: true,
1612
+ get: function() {
1613
+ return FilterBadges;
1614
+ }
1615
+ });
1616
+ Object.defineProperty(exports, "FilterBuilder", {
1617
+ enumerable: true,
1618
+ get: function() {
1619
+ return FilterBuilder;
1620
+ }
1621
+ });
1622
+ Object.defineProperty(exports, "FilterPanel", {
1623
+ enumerable: true,
1624
+ get: function() {
1625
+ return FilterPanel;
1626
+ }
1627
+ });
1628
+ Object.defineProperty(exports, "FilterValueControl", {
1629
+ enumerable: true,
1630
+ get: function() {
1631
+ return FilterValueControl;
1632
+ }
1633
+ });
1634
+ Object.defineProperty(exports, "PinnedFilterControls", {
1635
+ enumerable: true,
1636
+ get: function() {
1637
+ return PinnedFilterControls;
1638
+ }
1639
+ });
1640
+ Object.defineProperty(exports, "Select", {
1641
+ enumerable: true,
1642
+ get: function() {
1643
+ return Select;
1644
+ }
1645
+ });
1646
+ Object.defineProperty(exports, "SelectContent", {
1647
+ enumerable: true,
1648
+ get: function() {
1649
+ return SelectContent;
1650
+ }
1651
+ });
1652
+ Object.defineProperty(exports, "SelectGroup", {
1653
+ enumerable: true,
1654
+ get: function() {
1655
+ return SelectGroup;
1656
+ }
1657
+ });
1658
+ Object.defineProperty(exports, "SelectItem", {
1659
+ enumerable: true,
1660
+ get: function() {
1661
+ return SelectItem;
1662
+ }
1663
+ });
1664
+ Object.defineProperty(exports, "SelectLabel", {
1665
+ enumerable: true,
1666
+ get: function() {
1667
+ return SelectLabel;
1668
+ }
1669
+ });
1670
+ Object.defineProperty(exports, "SelectScrollDownButton", {
1671
+ enumerable: true,
1672
+ get: function() {
1673
+ return SelectScrollDownButton;
1674
+ }
1675
+ });
1676
+ Object.defineProperty(exports, "SelectScrollUpButton", {
1677
+ enumerable: true,
1678
+ get: function() {
1679
+ return SelectScrollUpButton;
1680
+ }
1681
+ });
1682
+ Object.defineProperty(exports, "SelectSeparator", {
1683
+ enumerable: true,
1684
+ get: function() {
1685
+ return SelectSeparator;
1686
+ }
1687
+ });
1688
+ Object.defineProperty(exports, "SelectTrigger", {
1689
+ enumerable: true,
1690
+ get: function() {
1691
+ return SelectTrigger;
1692
+ }
1693
+ });
1694
+ Object.defineProperty(exports, "SelectValue", {
1695
+ enumerable: true,
1696
+ get: function() {
1697
+ return SelectValue;
1698
+ }
1699
+ });
1700
+ Object.defineProperty(exports, "Table", {
1701
+ enumerable: true,
1702
+ get: function() {
1703
+ return Table;
1704
+ }
1705
+ });
1706
+ Object.defineProperty(exports, "TableBody", {
1707
+ enumerable: true,
1708
+ get: function() {
1709
+ return TableBody;
1710
+ }
1711
+ });
1712
+ Object.defineProperty(exports, "TableCaption", {
1713
+ enumerable: true,
1714
+ get: function() {
1715
+ return TableCaption;
1716
+ }
1717
+ });
1718
+ Object.defineProperty(exports, "TableCell", {
1719
+ enumerable: true,
1720
+ get: function() {
1721
+ return TableCell;
1722
+ }
1723
+ });
1724
+ Object.defineProperty(exports, "TableHead", {
1725
+ enumerable: true,
1726
+ get: function() {
1727
+ return TableHead;
1728
+ }
1729
+ });
1730
+ Object.defineProperty(exports, "TableHeader", {
1731
+ enumerable: true,
1732
+ get: function() {
1733
+ return TableHeader;
1734
+ }
1735
+ });
1736
+ Object.defineProperty(exports, "TableRow", {
1737
+ enumerable: true,
1738
+ get: function() {
1739
+ return TableRow;
1740
+ }
1741
+ });
1742
+ Object.defineProperty(exports, "badgeVariants", {
1743
+ enumerable: true,
1744
+ get: function() {
1745
+ return badgeVariants;
1746
+ }
1747
+ });
1748
+ Object.defineProperty(exports, "formatDate", {
1749
+ enumerable: true,
1750
+ get: function() {
1751
+ return formatDate;
1752
+ }
1753
+ });
1754
+ Object.defineProperty(exports, "formatNumber", {
1755
+ enumerable: true,
1756
+ get: function() {
1757
+ return formatNumber;
1758
+ }
1759
+ });
1760
+
1761
+ //# sourceMappingURL=filter-builder-B3mZFL-K.cjs.map