beez-ui 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 (51) hide show
  1. package/CHANGELOG.md +31 -19
  2. package/README.md +49 -11
  3. package/dist/adapters/unpic-image.js +92 -14
  4. package/dist/components/alert-dialog.js +444 -26
  5. package/dist/components/alert.js +153 -18
  6. package/dist/components/animated-theme-toggler.js +215 -76
  7. package/dist/components/avatar.js +247 -15
  8. package/dist/components/badge.js +18 -16
  9. package/dist/components/button.js +85 -28
  10. package/dist/components/calendar.js +327 -68
  11. package/dist/components/card.js +39 -9
  12. package/dist/components/carousel.js +454 -84
  13. package/dist/components/checkbox.js +50 -3
  14. package/dist/components/data-table.js +827 -583
  15. package/dist/components/dialog.js +363 -22
  16. package/dist/components/dropdown-menu.js +543 -32
  17. package/dist/components/filter-query-bar.js +809 -468
  18. package/dist/components/filter-query-grammar.js +781 -736
  19. package/dist/components/form.js +305 -43
  20. package/dist/components/highlighter.js +142 -87
  21. package/dist/components/hover-card.js +97 -7
  22. package/dist/components/input-group.js +261 -41
  23. package/dist/components/input.js +8 -2
  24. package/dist/components/label.js +38 -3
  25. package/dist/components/link.js +45 -7
  26. package/dist/components/pagination.js +332 -16
  27. package/dist/components/popover.js +222 -16
  28. package/dist/components/radio-group.js +85 -5
  29. package/dist/components/select.js +451 -22
  30. package/dist/components/separator.js +43 -3
  31. package/dist/components/sheet.js +345 -22
  32. package/dist/components/sidebar.js +1385 -193
  33. package/dist/components/skeleton.js +6 -2
  34. package/dist/components/svgs/whatsappIcon.js +11 -1
  35. package/dist/components/switch.js +55 -3
  36. package/dist/components/table.js +288 -18
  37. package/dist/components/tabs.js +161 -18
  38. package/dist/components/textarea.js +7 -2
  39. package/dist/components/themed-toaster.js +63 -20
  40. package/dist/components/tooltip.js +128 -9
  41. package/dist/components/typing-animation.js +335 -150
  42. package/dist/constants/sidebar.js +1 -4
  43. package/dist/hooks/use-mobile.js +8 -8
  44. package/dist/hooks/use-sidebar-persistence.js +27 -27
  45. package/dist/lib/image-dimensions.js +2 -2
  46. package/dist/lib/year-month-filter.js +38 -40
  47. package/dist/next.js +69 -17
  48. package/dist/providers/beez-ui-provider.js +48 -7
  49. package/dist/providers/native-provider.js +40 -6
  50. package/dist/tanstack.js +35 -23
  51. package/package.json +7 -1
@@ -1,12 +1,13 @@
1
1
  "use client";
2
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { c as _c } from "react/compiler-runtime";
3
3
  import * as React from "react";
4
- import { Calendar, Check, CircleDot, CircleMinus, CircleSlash, Folder, Hash, List, Type, User, X, } from "lucide-react";
4
+ import { Calendar, Check, CircleDot, CircleMinus, CircleSlash, Folder, Hash, List, Type, User, X } from "lucide-react";
5
5
  import { Button } from "./button.js";
6
6
  import { Input } from "./input.js";
7
- import { Popover, PopoverAnchor, PopoverContent, } from "./popover.js";
7
+ import { Popover, PopoverAnchor, PopoverContent } from "./popover.js";
8
8
  import { cn } from "../lib/utils.js";
9
- import { getActiveFilterToken, getValueHighlightRanges, normalizeFilterSlug, parseFilterQuery, PRESENCE_HAS_META_KEY, PRESENCE_NO_META_KEY, UNASSIGNED_FOLDER_FILTER_VALUE, } from "./filter-query-grammar.js";
9
+ import { getActiveFilterToken, getValueHighlightRanges, normalizeFilterSlug, parseFilterQuery, PRESENCE_HAS_META_KEY, PRESENCE_NO_META_KEY, UNASSIGNED_FOLDER_FILTER_VALUE } from "./filter-query-grammar.js";
10
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
10
11
  const DEFAULT_PLACEHOLDER = "Filtrar por campo o palabra clave";
11
12
  const DEFAULT_ARIA_LABEL = "Filtrar gastos";
12
13
  const CLEAR_FILTER_ARIA_LABEL = "Limpiar todos los filtros";
@@ -15,512 +16,852 @@ const VALUE_GROUP_LABEL = "Valores";
15
16
  const LISTBOX_ID = "filter-query-bar-listbox";
16
17
  const OPTION_ID_PREFIX = "filter-query-bar-option";
17
18
  const NUMBER_RANGE_VALUE_SUGGESTIONS = [
18
- { label: "Mayor o igual (≥)", slug: ">=" },
19
- { label: "Menor o igual (≤)", slug: "<=" },
20
- { label: "Rango (100..500)", slug: ".." },
19
+ {
20
+ label: "Mayor o igual (≥)",
21
+ slug: ">="
22
+ },
23
+ {
24
+ label: "Menor o igual (≤)",
25
+ slug: "<="
26
+ },
27
+ {
28
+ label: "Rango (100..500)",
29
+ slug: ".."
30
+ }
21
31
  ];
22
32
  /**
23
- * Operadores para qualifiers de fecha (mes-año). Reflejan los numéricos: cota
24
- * inferior (`>=`), cota superior (`<=`) y rango (`..`). La presencia/ausencia de
25
- * fecha se cubre con las meta-claves `tiene:`/`no:` del bloque universal.
26
- */
33
+ * Operadores para qualifiers de fecha (mes-año). Reflejan los numéricos: cota
34
+ * inferior (`>=`), cota superior (`<=`) y rango (`..`). La presencia/ausencia de
35
+ * fecha se cubre con las meta-claves `tiene:`/`no:` del bloque universal.
36
+ */
27
37
  const YEAR_MONTH_VALUE_SUGGESTIONS = [
28
- { keepOpen: true, label: "Desde (≥)", slug: ">=" },
29
- { keepOpen: true, label: "Hasta (≤)", slug: "<=" },
30
- { keepOpen: true, label: "Rango (2026-01..2026-06)", slug: ".." },
38
+ {
39
+ keepOpen: true,
40
+ label: "Desde (≥)",
41
+ slug: ">="
42
+ },
43
+ {
44
+ keepOpen: true,
45
+ label: "Hasta (≤)",
46
+ slug: "<="
47
+ },
48
+ {
49
+ keepOpen: true,
50
+ label: "Rango (2026-01..2026-06)",
51
+ slug: ".."
52
+ }
31
53
  ];
32
54
  const TEXT_MATCH_VALUE_SUGGESTIONS = [
33
- // `texto*`: el caret queda antes del `*` para escribir el prefijo.
34
- { caretOffset: 0, insertText: "*", keepOpen: true, label: "Empieza por…", name: "starts", slug: "" },
35
- // `*texto*`: el caret queda entre los dos `*`.
36
- { caretOffset: 1, insertText: "**", keepOpen: true, label: "Contiene…", name: "contains", slug: "" },
37
- // `*texto`: el caret queda después del `*` para escribir el sufijo.
38
- { insertText: "*", keepOpen: true, label: "Termina con…", name: "ends", slug: "" },
55
+ {
56
+ caretOffset: 0,
57
+ insertText: "*",
58
+ keepOpen: true,
59
+ label: "Empieza por…",
60
+ name: "starts",
61
+ slug: ""
62
+ },
63
+ {
64
+ caretOffset: 1,
65
+ insertText: "**",
66
+ keepOpen: true,
67
+ label: "Contiene…",
68
+ name: "contains",
69
+ slug: ""
70
+ },
71
+ {
72
+ insertText: "*",
73
+ keepOpen: true,
74
+ label: "Termina con…",
75
+ name: "ends",
76
+ slug: ""
77
+ }
39
78
  ];
40
79
  /** Meta-claves de presencia ofrecidas también como campos de primer nivel. */
41
- const PRESENCE_META_KEY_SUGGESTIONS = [
42
- { icon: Check, key: PRESENCE_HAS_META_KEY, label: "Tiene…" },
43
- { icon: CircleSlash, key: PRESENCE_NO_META_KEY, label: "No tiene…" },
44
- ];
80
+ const PRESENCE_META_KEY_SUGGESTIONS = [{
81
+ icon: Check,
82
+ key: PRESENCE_HAS_META_KEY,
83
+ label: "Tiene…"
84
+ }, {
85
+ icon: CircleSlash,
86
+ key: PRESENCE_NO_META_KEY,
87
+ label: "No tiene…"
88
+ }];
45
89
  /** Icono representativo de cada tipo de qualifier, mostrado a su izquierda. */
46
90
  function getKindIcon(kind) {
47
- switch (kind) {
48
- case "numberRange":
49
- return Hash;
50
- case "textMatch":
51
- return Type;
52
- case "enum":
53
- return List;
54
- case "presence":
55
- return CircleDot;
56
- case "yearMonthRange":
57
- return Calendar;
58
- case "folder":
59
- return Folder;
60
- default:
61
- return Type;
62
- }
91
+ switch (kind) {
92
+ case "numberRange": return Hash;
93
+ case "textMatch": return Type;
94
+ case "enum": return List;
95
+ case "presence": return CircleDot;
96
+ case "yearMonthRange": return Calendar;
97
+ case "folder": return Folder;
98
+ default: return Type;
99
+ }
63
100
  }
64
101
  /** Iconos por nombre lógico declarado en el qualifier (`config.iconName`). */
65
- const ICON_BY_NAME = {
66
- user: User,
67
- };
102
+ const ICON_BY_NAME = { user: User };
68
103
  /** Icono de un qualifier: override por `iconName`, o el icono por defecto del kind. */
69
104
  function getQualifierIcon(config) {
70
- if (config.iconName && ICON_BY_NAME[config.iconName]) {
71
- return ICON_BY_NAME[config.iconName];
72
- }
73
- return getKindIcon(config.kind);
105
+ if (config.iconName && ICON_BY_NAME[config.iconName]) {
106
+ return ICON_BY_NAME[config.iconName];
107
+ }
108
+ return getKindIcon(config.kind);
74
109
  }
75
110
  function startsWithNormalized(candidate, prefix) {
76
- if (!prefix) {
77
- return true;
78
- }
79
- return normalizeFilterSlug(candidate).startsWith(normalizeFilterSlug(prefix));
111
+ if (!prefix) {
112
+ return true;
113
+ }
114
+ return normalizeFilterSlug(candidate).startsWith(normalizeFilterSlug(prefix));
80
115
  }
81
116
  /** Etiqueta de la opción raíz de exclusión (arranca un token `-campo:`). */
82
117
  const EXCLUDE_ROOT_LABEL = "Excluir";
83
118
  function buildKeySuggestions(configs, keyPart, { negated, includeExclude, appliedFilters }) {
84
- // En modo exclusión el campo elegido conserva el `-` para formar `-campo:`.
85
- const prefix = negated ? "-" : "";
86
- const fieldSuggestions = configs
87
- .filter((config) => config.kind !== "text" && config.key)
88
- .filter((config) => getAppliedPresenceValue(config, appliedFilters) !== "noValue")
89
- .filter((config) => startsWithNormalized(config.key, keyPart) ||
90
- startsWithNormalized(config.label, keyPart))
91
- .map((config) => ({
92
- group: "key",
93
- icon: getQualifierIcon(config),
94
- id: `${OPTION_ID_PREFIX}-key-${negated ? "neg-" : ""}${config.key}`,
95
- insertText: `${prefix}${config.key}:`,
96
- keepOpen: true,
97
- label: config.label,
98
- }));
99
- // Meta-claves de presencia como campos de primer nivel (`tiene:` / `no:`); no
100
- // se ofrecen en modo exclusión (la ausencia ya se cubre con `no:`) ni cuando
101
- // ningún campo soporta presencia (todos tienen valor siempre).
102
- const hasPresenceCapableFields = configs.some((config) => config.kind !== "text" &&
103
- Boolean(config.key) &&
104
- config.supportsPresence !== false);
105
- const metaSuggestions = negated || !hasPresenceCapableFields
106
- ? []
107
- : PRESENCE_META_KEY_SUGGESTIONS.filter((meta) => startsWithNormalized(meta.key, keyPart) ||
108
- startsWithNormalized(meta.label, keyPart)).map((meta) => ({
109
- group: "meta",
110
- icon: meta.icon,
111
- id: `${OPTION_ID_PREFIX}-key-${meta.key}`,
112
- insertText: `${meta.key}:`,
113
- keepOpen: true,
114
- label: meta.label,
115
- }));
116
- // "Excluir" al final (estilo GitHub): inserta `-` y reabre la lista de campos
117
- // para elegir cuál excluir.
118
- const excludeSuggestion = includeExclude
119
- ? [
120
- {
121
- group: "meta",
122
- icon: CircleMinus,
123
- id: `${OPTION_ID_PREFIX}-exclude-root`,
124
- insertText: "-",
125
- keepOpen: true,
126
- label: EXCLUDE_ROOT_LABEL,
127
- },
128
- ]
129
- : [];
130
- return [...fieldSuggestions, ...metaSuggestions, ...excludeSuggestion];
119
+ const prefix = negated ? "-" : "";
120
+ const fieldSuggestions = configs.filter((config) => config.kind !== "text" && config.key).filter((config) => getAppliedPresenceValue(config, appliedFilters) !== "noValue").filter((config) => startsWithNormalized(config.key, keyPart) || startsWithNormalized(config.label, keyPart)).map((config) => ({
121
+ group: "key",
122
+ icon: getQualifierIcon(config),
123
+ id: `${OPTION_ID_PREFIX}-key-${negated ? "neg-" : ""}${config.key}`,
124
+ insertText: `${prefix}${config.key}:`,
125
+ keepOpen: true,
126
+ label: config.label
127
+ }));
128
+ const hasPresenceCapableFields = configs.some((config) => config.kind !== "text" && Boolean(config.key) && config.supportsPresence !== false);
129
+ const metaSuggestions = negated || !hasPresenceCapableFields ? [] : PRESENCE_META_KEY_SUGGESTIONS.filter((meta) => startsWithNormalized(meta.key, keyPart) || startsWithNormalized(meta.label, keyPart)).map((meta) => ({
130
+ group: "meta",
131
+ icon: meta.icon,
132
+ id: `${OPTION_ID_PREFIX}-key-${meta.key}`,
133
+ insertText: `${meta.key}:`,
134
+ keepOpen: true,
135
+ label: meta.label
136
+ }));
137
+ const excludeSuggestion = includeExclude ? [{
138
+ group: "meta",
139
+ icon: CircleMinus,
140
+ id: `${OPTION_ID_PREFIX}-exclude-root`,
141
+ insertText: "-",
142
+ keepOpen: true,
143
+ label: EXCLUDE_ROOT_LABEL
144
+ }] : [];
145
+ return [
146
+ ...fieldSuggestions,
147
+ ...metaSuggestions,
148
+ ...excludeSuggestion
149
+ ];
131
150
  }
132
151
  /**
133
- * Sugerencias de valor para una meta-clave de presencia (`tiene:` / `no:`): el
134
- * "valor" es la clave del campo destino (`tiene:enviados`, `no:carpeta`).
135
- */
152
+ * Sugerencias de valor para una meta-clave de presencia (`tiene:` / `no:`): el
153
+ * "valor" es la clave del campo destino (`tiene:enviados`, `no:carpeta`).
154
+ */
136
155
  function buildMetaValueSuggestions(configs, metaKey, valuePart, appliedFilters) {
137
- return configs
138
- .filter((config) => config.kind !== "text" && config.key)
139
- .filter((config) => config.supportsPresence !== false)
140
- .filter((config) => !hasPresenceMetaFilterAlreadyApplied(config, appliedFilters))
141
- .filter((config) => startsWithNormalized(config.key, valuePart) ||
142
- startsWithNormalized(config.label, valuePart))
143
- .map((config) => ({
144
- group: "value",
145
- icon: getQualifierIcon(config),
146
- id: `${OPTION_ID_PREFIX}-meta-${metaKey}-${config.key}`,
147
- insertText: `${config.key} `,
148
- keepOpen: false,
149
- label: config.label,
150
- }));
156
+ return configs.filter((config) => config.kind !== "text" && config.key).filter((config) => config.supportsPresence !== false).filter((config) => !hasPresenceMetaFilterAlreadyApplied(config, appliedFilters)).filter((config) => startsWithNormalized(config.key, valuePart) || startsWithNormalized(config.label, valuePart)).map((config) => ({
157
+ group: "value",
158
+ icon: getQualifierIcon(config),
159
+ id: `${OPTION_ID_PREFIX}-meta-${metaKey}-${config.key}`,
160
+ insertText: `${config.key} `,
161
+ keepOpen: false,
162
+ label: config.label
163
+ }));
151
164
  }
152
165
  /**
153
- * Bloque universal que aparece al entrar a cualquier filtro: presencia
154
- * (`tiene:<clave>`), ausencia (`no:<clave>`) y exclusión de un valor
155
- * (`-<clave>:`). Reescriben el token completo, por eso usan `replaceTokenStart`.
156
- */
166
+ * Bloque universal que aparece al entrar a cualquier filtro: presencia
167
+ * (`tiene:<clave>`), ausencia (`no:<clave>`) y exclusión de un valor
168
+ * (`-<clave>:`). Reescriben el token completo, por eso usan `replaceTokenStart`.
169
+ */
157
170
  function buildPresenceAndExcludeSuggestions(config, appliedFilters) {
158
- const appliedPresenceValue = getAppliedPresenceValue(config, appliedFilters);
159
- const hasAppliedPresence = appliedPresenceValue != null;
160
- const suggestions = hasAppliedPresence || config.supportsPresence === false
161
- ? []
162
- : [
163
- {
164
- group: "meta",
165
- icon: CircleSlash,
166
- id: `${OPTION_ID_PREFIX}-presence-no-${config.key}`,
167
- insertText: `${PRESENCE_NO_META_KEY}:${config.key} `,
168
- keepOpen: false,
169
- label: `No tiene ${config.label}`,
170
- replaceTokenStart: true,
171
- },
172
- {
173
- group: "meta",
174
- icon: Check,
175
- id: `${OPTION_ID_PREFIX}-presence-has-${config.key}`,
176
- insertText: `${PRESENCE_HAS_META_KEY}:${config.key} `,
177
- keepOpen: false,
178
- label: `Tiene ${config.label}`,
179
- replaceTokenStart: true,
180
- },
181
- ];
182
- // Excluir un valor concreto solo aplica a kinds con valores; en presencia pura
183
- // (deuda) el único filtro es tener/no tener, ya cubierto arriba.
184
- if (config.kind !== "presence" && appliedPresenceValue !== "noValue") {
185
- suggestions.push({
186
- group: "meta",
187
- icon: CircleMinus,
188
- id: `${OPTION_ID_PREFIX}-exclude-${config.key}`,
189
- insertText: `-${config.key}:`,
190
- keepOpen: true,
191
- label: `Excluir ${config.label}…`,
192
- replaceTokenStart: true,
193
- });
194
- }
195
- return suggestions;
171
+ const appliedPresenceValue = getAppliedPresenceValue(config, appliedFilters);
172
+ const hasAppliedPresence = appliedPresenceValue != null;
173
+ const suggestions = hasAppliedPresence || config.supportsPresence === false ? [] : [{
174
+ group: "meta",
175
+ icon: CircleSlash,
176
+ id: `${OPTION_ID_PREFIX}-presence-no-${config.key}`,
177
+ insertText: `${PRESENCE_NO_META_KEY}:${config.key} `,
178
+ keepOpen: false,
179
+ label: `No tiene ${config.label}`,
180
+ replaceTokenStart: true
181
+ }, {
182
+ group: "meta",
183
+ icon: Check,
184
+ id: `${OPTION_ID_PREFIX}-presence-has-${config.key}`,
185
+ insertText: `${PRESENCE_HAS_META_KEY}:${config.key} `,
186
+ keepOpen: false,
187
+ label: `Tiene ${config.label}`,
188
+ replaceTokenStart: true
189
+ }];
190
+ if (config.kind !== "presence" && appliedPresenceValue !== "noValue") {
191
+ suggestions.push({
192
+ group: "meta",
193
+ icon: CircleMinus,
194
+ id: `${OPTION_ID_PREFIX}-exclude-${config.key}`,
195
+ insertText: `-${config.key}:`,
196
+ keepOpen: true,
197
+ label: `Excluir ${config.label}…`,
198
+ replaceTokenStart: true
199
+ });
200
+ }
201
+ return suggestions;
196
202
  }
197
203
  function getValueSuggestionSource(config) {
198
- if (config.kind === "enum" || config.kind === "folder") {
199
- return (config.options ?? []).map((option) => ({
200
- label: option.label,
201
- slug: option.slug,
202
- value: option.value,
203
- }));
204
- }
205
- if (config.kind === "presence") {
206
- // La presencia se ofrece con el bloque universal No/Tiene, no como valores.
207
- return [];
208
- }
209
- if (config.kind === "yearMonthRange") {
210
- return YEAR_MONTH_VALUE_SUGGESTIONS;
211
- }
212
- if (config.kind === "numberRange") {
213
- return NUMBER_RANGE_VALUE_SUGGESTIONS;
214
- }
215
- if (config.kind === "textMatch") {
216
- return TEXT_MATCH_VALUE_SUGGESTIONS;
217
- }
218
- return [];
204
+ if (config.kind === "enum" || config.kind === "folder") {
205
+ return (config.options ?? []).map((option) => ({
206
+ label: option.label,
207
+ slug: option.slug,
208
+ value: option.value
209
+ }));
210
+ }
211
+ if (config.kind === "presence") {
212
+ return [];
213
+ }
214
+ if (config.kind === "yearMonthRange") {
215
+ return YEAR_MONTH_VALUE_SUGGESTIONS;
216
+ }
217
+ if (config.kind === "numberRange") {
218
+ return NUMBER_RANGE_VALUE_SUGGESTIONS;
219
+ }
220
+ if (config.kind === "textMatch") {
221
+ return TEXT_MATCH_VALUE_SUGGESTIONS;
222
+ }
223
+ return [];
219
224
  }
220
225
  function hasPresenceMetaFilterAlreadyApplied(config, appliedFilters) {
221
- return appliedFilters.some((appliedFilter) => appliedFilter.key === config.key &&
222
- !appliedFilter.negated &&
223
- appliedFilter.value.kind === "presence");
226
+ return appliedFilters.some((appliedFilter) => appliedFilter.key === config.key && !appliedFilter.negated && appliedFilter.value.kind === "presence");
224
227
  }
225
228
  function hasSelectableValueAlreadyApplied(config, option, appliedFilters) {
226
- if (config.kind === "enum") {
227
- return appliedFilters.some((appliedFilter) => appliedFilter.key === config.key &&
228
- appliedFilter.value.kind === "enum" &&
229
- appliedFilter.value.value === option.value);
230
- }
231
- if (config.kind === "folder") {
232
- return appliedFilters.some((appliedFilter) => appliedFilter.key === config.key &&
233
- appliedFilter.value.kind === "folder" &&
234
- appliedFilter.value.folderId === option.value);
235
- }
236
- return false;
229
+ if (config.kind === "enum") {
230
+ return appliedFilters.some((appliedFilter) => appliedFilter.key === config.key && appliedFilter.value.kind === "enum" && appliedFilter.value.value === option.value);
231
+ }
232
+ if (config.kind === "folder") {
233
+ return appliedFilters.some((appliedFilter) => appliedFilter.key === config.key && appliedFilter.value.kind === "folder" && appliedFilter.value.folderId === option.value);
234
+ }
235
+ return false;
237
236
  }
238
237
  function getAppliedPresenceValue(config, appliedFilters) {
239
- const presenceFilter = appliedFilters.find((appliedFilter) => appliedFilter.key === config.key &&
240
- !appliedFilter.negated &&
241
- appliedFilter.value.kind === "presence");
242
- return presenceFilter?.value.kind === "presence"
243
- ? presenceFilter.value.value
244
- : null;
238
+ const presenceFilter = appliedFilters.find((appliedFilter) => appliedFilter.key === config.key && !appliedFilter.negated && appliedFilter.value.kind === "presence");
239
+ return presenceFilter?.value.kind === "presence" ? presenceFilter.value.value : null;
245
240
  }
246
241
  function isSelectableValueCompatibleWithPresence(config, option, appliedFilters) {
247
- const presenceValue = getAppliedPresenceValue(config, appliedFilters);
248
- if (presenceValue === "noValue") {
249
- return false;
250
- }
251
- if (config.kind !== "folder") {
252
- return true;
253
- }
254
- if (presenceValue === "hasValue") {
255
- return option.value !== UNASSIGNED_FOLDER_FILTER_VALUE;
256
- }
257
- return true;
242
+ const presenceValue = getAppliedPresenceValue(config, appliedFilters);
243
+ if (presenceValue === "noValue") {
244
+ return false;
245
+ }
246
+ if (config.kind !== "folder") {
247
+ return true;
248
+ }
249
+ if (presenceValue === "hasValue") {
250
+ return option.value !== UNASSIGNED_FOLDER_FILTER_VALUE;
251
+ }
252
+ return true;
258
253
  }
259
254
  function buildValueSuggestions(config, valuePart, negated, appliedFilters) {
260
- // Los operadores numéricos no cierran el popover: el usuario completa el número.
261
- const defaultKeepOpen = config.kind === "numberRange";
262
- // Para texto, los operadores (empieza/contiene/termina) son un menú inicial:
263
- // aparecen al entrar al filtro y desaparecen apenas se escribe el texto, igual
264
- // que en GitHub. El resto de los kinds filtra sus valores a medida que se tipea.
265
- const sourceOptions = config.kind === "textMatch" && valuePart !== ""
266
- ? []
267
- : getValueSuggestionSource(config);
268
- // Para listas seleccionables (enum/folder) se tolera un `@` inicial al estilo
269
- // "mención" (p. ej. `prestamista:@vero`) al filtrar las opciones.
270
- const filterValuePart = config.kind === "enum" || config.kind === "folder"
271
- ? valuePart.replace(/^@/, "")
272
- : valuePart;
273
- const valueSuggestions = sourceOptions
274
- .filter((option) => !hasSelectableValueAlreadyApplied(config, option, appliedFilters) &&
275
- isSelectableValueCompatibleWithPresence(config, option, appliedFilters))
276
- .filter((option) => startsWithNormalized(option.slug, filterValuePart))
277
- .map((option) => {
278
- const keepOpen = option.keepOpen ?? defaultKeepOpen;
279
- const insertText = option.insertText ?? (keepOpen ? option.slug : `${option.slug} `);
280
- return {
281
- caretOffset: option.caretOffset,
282
- group: "value",
283
- icon: getQualifierIcon(config),
284
- id: `${OPTION_ID_PREFIX}-value-${config.key}-${option.name ?? option.slug}`,
285
- insertText,
286
- keepOpen,
287
- label: option.label,
288
- };
289
- });
290
- // El bloque universal (No tiene / Tiene / Excluir) encabeza las sugerencias al
291
- // entrar al filtro (valor vacío) y solo en tokens no negados: dentro de
292
- // `-clave:` el usuario ya excluye y elige directamente el valor a excluir.
293
- if (negated || valuePart.length > 0) {
294
- return valueSuggestions;
295
- }
296
- return [
297
- ...buildPresenceAndExcludeSuggestions(config, appliedFilters),
298
- ...valueSuggestions,
299
- ];
255
+ const defaultKeepOpen = config.kind === "numberRange";
256
+ const sourceOptions = config.kind === "textMatch" && valuePart !== "" ? [] : getValueSuggestionSource(config);
257
+ const filterValuePart = config.kind === "enum" || config.kind === "folder" ? valuePart.replace(/^@/, "") : valuePart;
258
+ const valueSuggestions = sourceOptions.filter((option) => !hasSelectableValueAlreadyApplied(config, option, appliedFilters) && isSelectableValueCompatibleWithPresence(config, option, appliedFilters)).filter((option) => startsWithNormalized(option.slug, filterValuePart)).map((option) => {
259
+ const keepOpen = option.keepOpen ?? defaultKeepOpen;
260
+ const insertText = option.insertText ?? (keepOpen ? option.slug : `${option.slug} `);
261
+ return {
262
+ caretOffset: option.caretOffset,
263
+ group: "value",
264
+ icon: getQualifierIcon(config),
265
+ id: `${OPTION_ID_PREFIX}-value-${config.key}-${option.name ?? option.slug}`,
266
+ insertText,
267
+ keepOpen,
268
+ label: option.label
269
+ };
270
+ });
271
+ if (negated || valuePart.length > 0) {
272
+ return valueSuggestions;
273
+ }
274
+ return [...buildPresenceAndExcludeSuggestions(config, appliedFilters), ...valueSuggestions];
300
275
  }
301
276
  function resolveConfigByKey(configs, rawKey) {
302
- const normalized = normalizeFilterSlug(rawKey);
303
- return configs.find((config) => {
304
- if (config.kind === "text" || !config.key) {
305
- return false;
306
- }
307
- if (normalizeFilterSlug(config.key) === normalized) {
308
- return true;
309
- }
310
- return (config.aliases ?? []).some((alias) => normalizeFilterSlug(alias) === normalized);
311
- });
277
+ const normalized = normalizeFilterSlug(rawKey);
278
+ return configs.find((config) => {
279
+ if (config.kind === "text" || !config.key) {
280
+ return false;
281
+ }
282
+ if (normalizeFilterSlug(config.key) === normalized) {
283
+ return true;
284
+ }
285
+ return (config.aliases ?? []).some((alias) => normalizeFilterSlug(alias) === normalized);
286
+ });
312
287
  }
313
288
  /**
314
- * Parte la query en nodos para el overlay de resaltado: los rangos de valores
315
- * válidos se pintan con color de acento; el resto queda con el color normal.
316
- */
289
+ * Parte la query en nodos para el overlay de resaltado: los rangos de valores
290
+ * válidos se pintan con color de acento; el resto queda con el color normal.
291
+ */
317
292
  function renderHighlightedQuery(value, ranges) {
318
- if (ranges.length === 0) {
319
- return value;
320
- }
321
- const nodes = [];
322
- let cursor = 0;
323
- ranges.forEach((range, index) => {
324
- if (range.start > cursor) {
325
- nodes.push(value.slice(cursor, range.start));
326
- }
327
- nodes.push(_jsx("span", { className: "text-primary", children: value.slice(range.start, range.end) }, `highlight-${index}`));
328
- cursor = range.end;
329
- });
330
- if (cursor < value.length) {
331
- nodes.push(value.slice(cursor));
332
- }
333
- return nodes;
293
+ if (ranges.length === 0) {
294
+ return value;
295
+ }
296
+ const nodes = [];
297
+ let cursor = 0;
298
+ ranges.forEach((range, index) => {
299
+ if (range.start > cursor) {
300
+ nodes.push(value.slice(cursor, range.start));
301
+ }
302
+ nodes.push(/* @__PURE__ */ _jsx("span", {
303
+ className: "text-primary",
304
+ children: value.slice(range.start, range.end)
305
+ }, `highlight-${index}`));
306
+ cursor = range.end;
307
+ });
308
+ if (cursor < value.length) {
309
+ nodes.push(value.slice(cursor));
310
+ }
311
+ return nodes;
334
312
  }
335
313
  /**
336
- * Barra de filtro estilo GitHub Issues: input único con autocompletado de
337
- * `clave:valor`. Sugiere primero los campos disponibles y luego los valores de
338
- * cada campo, con navegación por teclado y semántica ARIA de combobox/listbox.
339
- * La navegación es virtual: el foco real nunca abandona el input.
340
- */
341
- export function FilterQueryBar({ configs, value, onValueChange, placeholder = DEFAULT_PLACEHOLDER, ariaLabel = DEFAULT_ARIA_LABEL, className, onFocusChange, trailingAction, }) {
342
- // Ids únicos por instancia: puede haber más de una barra montada a la vez
343
- // (p. ej. la barra principal y el editor de un preset guardado).
344
- const instanceId = React.useId();
345
- const listboxId = `${instanceId}-${LISTBOX_ID}`;
346
- const inputRef = React.useRef(null);
347
- const highlightOverlayRef = React.useRef(null);
348
- const pendingCaretRef = React.useRef(null);
349
- const [caretIndex, setCaretIndex] = React.useState(0);
350
- const [isOpen, setIsOpen] = React.useState(false);
351
- const [activeIndex, setActiveIndex] = React.useState(0);
352
- // El popover se abre al enfocar; sin esta marca, Tab insertaría la primera
353
- // sugerencia en vez de mover el foco. Solo interceptamos Tab cuando el usuario
354
- // navegó explícitamente las sugerencias con las flechas.
355
- const [hasNavigatedSuggestions, setHasNavigatedSuggestions] = React.useState(false);
356
- const suggestions = React.useMemo(() => {
357
- const activeToken = getActiveFilterToken(value, caretIndex);
358
- const appliedFilters = parseFilterQuery(value, configs).appliedFilters;
359
- if (activeToken.mode === "key") {
360
- // En modo clave negado (`-` / `-cam`) se sugieren los campos a excluir, que
361
- // arman `-campo:`. El texto libre `-palabra` sigue funcionando porque una
362
- // palabra que no matchea ninguna clave no produce sugerencias. "Excluir"
363
- // solo aparece con la barra vacía (sin clave a medio tipear ni negación).
364
- return buildKeySuggestions(configs, activeToken.keyPart, {
365
- appliedFilters,
366
- includeExclude: !activeToken.negated && activeToken.keyPart === "",
367
- negated: activeToken.negated,
368
- });
369
- }
370
- const normalizedResolvedKey = normalizeFilterSlug(activeToken.resolvedKey ?? "");
371
- // Meta-claves (`tiene:` / `no:`): el valor a sugerir es la clave del campo.
372
- if (normalizedResolvedKey === PRESENCE_HAS_META_KEY ||
373
- normalizedResolvedKey === PRESENCE_NO_META_KEY) {
374
- return buildMetaValueSuggestions(configs, normalizedResolvedKey, activeToken.valuePart, appliedFilters);
375
- }
376
- const config = resolveConfigByKey(configs, activeToken.resolvedKey ?? "");
377
- if (!config) {
378
- return [];
379
- }
380
- return buildValueSuggestions(config, activeToken.valuePart, activeToken.negated, appliedFilters);
381
- }, [caretIndex, configs, value]);
382
- const invalidTokenCount = React.useMemo(() => parseFilterQuery(value, configs).invalidTokens.length, [configs, value]);
383
- const highlightRanges = React.useMemo(() => getValueHighlightRanges(value, configs), [configs, value]);
384
- // El overlay de resaltado debe seguir el scroll horizontal del input cuando el
385
- // texto excede el ancho visible, para que el color quede alineado al caret.
386
- const syncOverlayScroll = React.useCallback(() => {
387
- if (highlightOverlayRef.current && inputRef.current) {
388
- highlightOverlayRef.current.scrollLeft = inputRef.current.scrollLeft;
389
- }
390
- }, []);
391
- React.useEffect(() => {
392
- setActiveIndex((previousIndex) => previousIndex < suggestions.length ? previousIndex : 0);
393
- }, [suggestions]);
394
- React.useLayoutEffect(() => {
395
- const pendingCaret = pendingCaretRef.current;
396
- if (pendingCaret == null || !inputRef.current) {
397
- return;
398
- }
399
- inputRef.current.setSelectionRange(pendingCaret, pendingCaret);
400
- setCaretIndex(pendingCaret);
401
- pendingCaretRef.current = null;
402
- }, [value]);
403
- React.useLayoutEffect(() => {
404
- syncOverlayScroll();
405
- }, [syncOverlayScroll, value]);
406
- const isPopoverOpen = isOpen && suggestions.length > 0;
407
- const activeSuggestion = isPopoverOpen ? suggestions[activeIndex] : undefined;
408
- const syncCaretFromInput = React.useCallback(() => {
409
- if (inputRef.current) {
410
- setCaretIndex(inputRef.current.selectionStart ?? 0);
411
- }
412
- }, []);
413
- const applySuggestion = React.useCallback((suggestion) => {
414
- const activeToken = getActiveFilterToken(value, inputRef.current?.selectionStart ?? caretIndex);
415
- // Las opciones universales reescriben el token entero (clave incluida); el
416
- // resto solo reemplaza el tramo del valor bajo el caret.
417
- const replaceStart = suggestion.replaceTokenStart
418
- ? activeToken.tokenStart
419
- : activeToken.replaceStart;
420
- const before = value.slice(0, replaceStart);
421
- const after = value.slice(activeToken.replaceEnd);
422
- const nextValue = before + suggestion.insertText + after;
423
- // El caret queda dentro del texto insertado (p. ej. entre los `*` de `**`).
424
- pendingCaretRef.current =
425
- before.length + (suggestion.caretOffset ?? suggestion.insertText.length);
426
- onValueChange(nextValue);
427
- setIsOpen(true);
428
- setActiveIndex(0);
429
- setHasNavigatedSuggestions(false);
430
- if (!suggestion.keepOpen) {
431
- // El valor quedó completo; el siguiente token reabrirá en modo claves.
432
- setIsOpen(false);
433
- }
434
- }, [caretIndex, onValueChange, value]);
435
- const handleKeyDown = React.useCallback((event) => {
436
- if (event.key === "ArrowDown") {
437
- event.preventDefault();
438
- setHasNavigatedSuggestions(true);
439
- if (!isPopoverOpen) {
440
- setIsOpen(true);
441
- return;
442
- }
443
- setActiveIndex((previousIndex) => suggestions.length === 0
444
- ? 0
445
- : (previousIndex + 1) % suggestions.length);
446
- return;
447
- }
448
- if (event.key === "ArrowUp") {
449
- event.preventDefault();
450
- setHasNavigatedSuggestions(true);
451
- if (!isPopoverOpen) {
452
- setIsOpen(true);
453
- return;
454
- }
455
- setActiveIndex((previousIndex) => suggestions.length === 0
456
- ? 0
457
- : (previousIndex - 1 + suggestions.length) % suggestions.length);
458
- return;
459
- }
460
- if (event.key === "Enter" && activeSuggestion) {
461
- event.preventDefault();
462
- applySuggestion(activeSuggestion);
463
- return;
464
- }
465
- // Tab solo acepta una sugerencia si el usuario la resaltó con las flechas;
466
- // de lo contrario deja que el foco se mueva con normalidad.
467
- if (event.key === "Tab" &&
468
- hasNavigatedSuggestions &&
469
- activeSuggestion) {
470
- event.preventDefault();
471
- applySuggestion(activeSuggestion);
472
- return;
473
- }
474
- if (event.key === "Escape" && isPopoverOpen) {
475
- event.preventDefault();
476
- event.stopPropagation();
477
- setIsOpen(false);
478
- }
479
- }, [
480
- activeSuggestion,
481
- applySuggestion,
482
- hasNavigatedSuggestions,
483
- isPopoverOpen,
484
- suggestions.length,
485
- ]);
486
- const handleClear = React.useCallback(() => {
487
- onValueChange("");
488
- pendingCaretRef.current = 0;
489
- setIsOpen(false);
490
- inputRef.current?.focus();
491
- }, [onValueChange]);
492
- return (_jsxs("div", { className: cn("grid w-full gap-2", className), children: [_jsxs(Popover, { onOpenChange: setIsOpen, open: isPopoverOpen, children: [_jsx(PopoverAnchor, { asChild: true, children: _jsxs("div", { className: "relative w-full", children: [_jsx(Input, { "aria-activedescendant": activeSuggestion
493
- ? `${instanceId}-${activeSuggestion.id}`
494
- : undefined, "aria-autocomplete": "list", "aria-controls": listboxId, "aria-expanded": isPopoverOpen, "aria-label": ariaLabel, autoComplete: "off",
495
- // El texto se vuelve transparente: lo dibuja el overlay coloreado de
496
- // abajo. El caret queda visible con el color de texto normal. El
497
- // padding derecho deja lugar a los iconos (limpiar + acción opcional).
498
- className: cn("w-full text-transparent", trailingAction ? "pr-16" : "pr-9"), onChange: (event) => {
499
- onValueChange(event.target.value);
500
- setIsOpen(true);
501
- setActiveIndex(0);
502
- setHasNavigatedSuggestions(false);
503
- setCaretIndex(event.target.selectionStart ?? event.target.value.length);
504
- }, onClick: syncCaretFromInput, onBlur: () => {
505
- setHasNavigatedSuggestions(false);
506
- onFocusChange?.(false);
507
- }, onFocus: () => {
508
- syncCaretFromInput();
509
- setIsOpen(true);
510
- onFocusChange?.(true);
511
- }, onKeyDown: handleKeyDown, onKeyUp: syncCaretFromInput, onScroll: syncOverlayScroll, placeholder: placeholder, ref: inputRef, role: "combobox", style: { caretColor: "var(--foreground)" }, type: "text", value: value }), _jsx("div", { "aria-hidden": "true", className: cn("pointer-events-none absolute inset-0 flex items-center overflow-hidden rounded-lg border border-transparent px-2.5 text-base text-foreground select-none md:text-sm", trailingAction ? "pr-16" : "pr-9"), ref: highlightOverlayRef, children: _jsx("span", { className: "whitespace-pre", children: renderHighlightedQuery(value, highlightRanges) }) }), _jsxs("div", { className: "absolute right-1 top-1/2 flex -translate-y-1/2 items-center gap-0.5", children: [value ? (_jsx(Button, { "aria-label": CLEAR_FILTER_ARIA_LABEL, className: "active:-translate-y-0", onClick: handleClear, size: "icon-xs", type: "button", variant: "ghost", children: _jsx(X, { "aria-hidden": "true" }) })) : null, trailingAction] })] }) }), _jsx(PopoverContent, { align: "start", className: "w-[250px] max-h-[280px] gap-0 overflow-y-auto p-1", onCloseAutoFocus: (event) => event.preventDefault(), onOpenAutoFocus: (event) => event.preventDefault(), children: _jsx("ul", { "aria-label": suggestions.some((suggestion) => suggestion.group === "value")
512
- ? VALUE_GROUP_LABEL
513
- : KEY_GROUP_LABEL, className: "grid gap-0.5", id: listboxId, role: "listbox", children: suggestions.map((suggestion, index) => {
514
- const SuggestionIcon = suggestion.icon;
515
- // Separador entre grupos lógicos (p. ej. campos | presencia/exclusión).
516
- const showSeparator = index > 0 && suggestions[index - 1].group !== suggestion.group;
517
- return (_jsxs(React.Fragment, { children: [showSeparator ? (_jsx("li", { "aria-hidden": "true", className: "my-1 border-t border-border", role: "presentation" })) : null, _jsxs("li", { "aria-selected": index === activeIndex, className: cn("flex cursor-pointer items-center gap-2 rounded-md px-2 py-1.5 text-sm", index === activeIndex
518
- ? "bg-accent text-accent-foreground"
519
- : "text-foreground"), id: `${instanceId}-${suggestion.id}`, onMouseDown: (event) => {
520
- event.preventDefault();
521
- applySuggestion(suggestion);
522
- }, role: "option", children: [_jsx(SuggestionIcon, { "aria-hidden": "true", className: "size-4 shrink-0 text-muted-foreground" }), _jsx("span", { className: "truncate", children: suggestion.label })] })] }, suggestion.id));
523
- }) }) })] }), _jsxs("span", { "aria-live": "polite", className: "sr-only", children: [isPopoverOpen ? `${suggestions.length} sugerencias disponibles` : "", invalidTokenCount > 0
524
- ? ` ${invalidTokenCount} filtros no aplicados`
525
- : ""] })] }));
314
+ * Barra de filtro estilo GitHub Issues: input único con autocompletado de
315
+ * `clave:valor`. Sugiere primero los campos disponibles y luego los valores de
316
+ * cada campo, con navegación por teclado y semántica ARIA de combobox/listbox.
317
+ * La navegación es virtual: el foco real nunca abandona el input.
318
+ */
319
+ export function FilterQueryBar(t0) {
320
+ const $ = _c(97);
321
+ const { configs, value, onValueChange, placeholder: t1, ariaLabel: t2, className, onFocusChange, trailingAction } = t0;
322
+ const placeholder = t1 === undefined ? DEFAULT_PLACEHOLDER : t1;
323
+ const ariaLabel = t2 === undefined ? DEFAULT_ARIA_LABEL : t2;
324
+ const instanceId = React.useId();
325
+ const listboxId = `${instanceId}-${LISTBOX_ID}`;
326
+ const inputRef = React.useRef(null);
327
+ const highlightOverlayRef = React.useRef(null);
328
+ const pendingCaretRef = React.useRef(null);
329
+ const [caretIndex, setCaretIndex] = React.useState(0);
330
+ const [isOpen, setIsOpen] = React.useState(false);
331
+ const [activeIndex, setActiveIndex] = React.useState(0);
332
+ const [hasNavigatedSuggestions, setHasNavigatedSuggestions] = React.useState(false);
333
+ let t3;
334
+ if ($[0] !== caretIndex || $[1] !== configs || $[2] !== value) {
335
+ bb0: {
336
+ const activeToken = getActiveFilterToken(value, caretIndex);
337
+ const appliedFilters = parseFilterQuery(value, configs).appliedFilters;
338
+ if (activeToken.mode === "key") {
339
+ t3 = buildKeySuggestions(configs, activeToken.keyPart, {
340
+ appliedFilters,
341
+ includeExclude: !activeToken.negated && activeToken.keyPart === "",
342
+ negated: activeToken.negated
343
+ });
344
+ break bb0;
345
+ }
346
+ const normalizedResolvedKey = normalizeFilterSlug(activeToken.resolvedKey ?? "");
347
+ if (normalizedResolvedKey === PRESENCE_HAS_META_KEY || normalizedResolvedKey === PRESENCE_NO_META_KEY) {
348
+ t3 = buildMetaValueSuggestions(configs, normalizedResolvedKey, activeToken.valuePart, appliedFilters);
349
+ break bb0;
350
+ }
351
+ const config = resolveConfigByKey(configs, activeToken.resolvedKey ?? "");
352
+ if (!config) {
353
+ let t4;
354
+ if ($[4] === Symbol.for("react.memo_cache_sentinel")) {
355
+ t4 = [];
356
+ $[4] = t4;
357
+ } else {
358
+ t4 = $[4];
359
+ }
360
+ t3 = t4;
361
+ break bb0;
362
+ }
363
+ t3 = buildValueSuggestions(config, activeToken.valuePart, activeToken.negated, appliedFilters);
364
+ }
365
+ $[0] = caretIndex;
366
+ $[1] = configs;
367
+ $[2] = value;
368
+ $[3] = t3;
369
+ } else {
370
+ t3 = $[3];
371
+ }
372
+ const suggestions = t3;
373
+ let t4;
374
+ if ($[5] !== configs || $[6] !== value) {
375
+ t4 = parseFilterQuery(value, configs);
376
+ $[5] = configs;
377
+ $[6] = value;
378
+ $[7] = t4;
379
+ } else {
380
+ t4 = $[7];
381
+ }
382
+ const invalidTokenCount = t4.invalidTokens.length;
383
+ let t5;
384
+ if ($[8] !== configs || $[9] !== value) {
385
+ t5 = getValueHighlightRanges(value, configs);
386
+ $[8] = configs;
387
+ $[9] = value;
388
+ $[10] = t5;
389
+ } else {
390
+ t5 = $[10];
391
+ }
392
+ const highlightRanges = t5;
393
+ let t6;
394
+ if ($[11] === Symbol.for("react.memo_cache_sentinel")) {
395
+ t6 = () => {
396
+ if (highlightOverlayRef.current && inputRef.current) {
397
+ highlightOverlayRef.current.scrollLeft = inputRef.current.scrollLeft;
398
+ }
399
+ };
400
+ $[11] = t6;
401
+ } else {
402
+ t6 = $[11];
403
+ }
404
+ const syncOverlayScroll = t6;
405
+ let t7;
406
+ if ($[12] !== suggestions.length) {
407
+ t7 = () => {
408
+ setActiveIndex((previousIndex) => previousIndex < suggestions.length ? previousIndex : 0);
409
+ };
410
+ $[12] = suggestions.length;
411
+ $[13] = t7;
412
+ } else {
413
+ t7 = $[13];
414
+ }
415
+ let t8;
416
+ if ($[14] !== suggestions) {
417
+ t8 = [suggestions];
418
+ $[14] = suggestions;
419
+ $[15] = t8;
420
+ } else {
421
+ t8 = $[15];
422
+ }
423
+ React.useEffect(t7, t8);
424
+ let t9;
425
+ if ($[16] === Symbol.for("react.memo_cache_sentinel")) {
426
+ t9 = () => {
427
+ const pendingCaret = pendingCaretRef.current;
428
+ if (pendingCaret == null || !inputRef.current) {
429
+ return;
430
+ }
431
+ inputRef.current.setSelectionRange(pendingCaret, pendingCaret);
432
+ setCaretIndex(pendingCaret);
433
+ pendingCaretRef.current = null;
434
+ };
435
+ $[16] = t9;
436
+ } else {
437
+ t9 = $[16];
438
+ }
439
+ let t10;
440
+ if ($[17] !== value) {
441
+ t10 = [value];
442
+ $[17] = value;
443
+ $[18] = t10;
444
+ } else {
445
+ t10 = $[18];
446
+ }
447
+ React.useLayoutEffect(t9, t10);
448
+ let t11;
449
+ if ($[19] === Symbol.for("react.memo_cache_sentinel")) {
450
+ t11 = () => {
451
+ syncOverlayScroll();
452
+ };
453
+ $[19] = t11;
454
+ } else {
455
+ t11 = $[19];
456
+ }
457
+ let t12;
458
+ if ($[20] !== value) {
459
+ t12 = [syncOverlayScroll, value];
460
+ $[20] = value;
461
+ $[21] = t12;
462
+ } else {
463
+ t12 = $[21];
464
+ }
465
+ React.useLayoutEffect(t11, t12);
466
+ const isPopoverOpen = isOpen && suggestions.length > 0;
467
+ const activeSuggestion = isPopoverOpen ? suggestions[activeIndex] : undefined;
468
+ let t13;
469
+ if ($[22] === Symbol.for("react.memo_cache_sentinel")) {
470
+ t13 = () => {
471
+ if (inputRef.current) {
472
+ setCaretIndex(inputRef.current.selectionStart ?? 0);
473
+ }
474
+ };
475
+ $[22] = t13;
476
+ } else {
477
+ t13 = $[22];
478
+ }
479
+ const syncCaretFromInput = t13;
480
+ let t14;
481
+ if ($[23] !== caretIndex || $[24] !== onValueChange || $[25] !== value) {
482
+ t14 = (suggestion) => {
483
+ const activeToken_0 = getActiveFilterToken(value, inputRef.current?.selectionStart ?? caretIndex);
484
+ const replaceStart = suggestion.replaceTokenStart ? activeToken_0.tokenStart : activeToken_0.replaceStart;
485
+ const before = value.slice(0, replaceStart);
486
+ const after = value.slice(activeToken_0.replaceEnd);
487
+ const nextValue = before + suggestion.insertText + after;
488
+ pendingCaretRef.current = before.length + (suggestion.caretOffset ?? suggestion.insertText.length);
489
+ onValueChange(nextValue);
490
+ setIsOpen(true);
491
+ setActiveIndex(0);
492
+ setHasNavigatedSuggestions(false);
493
+ if (!suggestion.keepOpen) {
494
+ setIsOpen(false);
495
+ }
496
+ };
497
+ $[23] = caretIndex;
498
+ $[24] = onValueChange;
499
+ $[25] = value;
500
+ $[26] = t14;
501
+ } else {
502
+ t14 = $[26];
503
+ }
504
+ const applySuggestion = t14;
505
+ let t15;
506
+ if ($[27] !== activeSuggestion || $[28] !== applySuggestion || $[29] !== hasNavigatedSuggestions || $[30] !== isPopoverOpen || $[31] !== suggestions.length) {
507
+ t15 = (event) => {
508
+ if (event.key === "ArrowDown") {
509
+ event.preventDefault();
510
+ setHasNavigatedSuggestions(true);
511
+ if (!isPopoverOpen) {
512
+ setIsOpen(true);
513
+ return;
514
+ }
515
+ setActiveIndex((previousIndex_0) => suggestions.length === 0 ? 0 : (previousIndex_0 + 1) % suggestions.length);
516
+ return;
517
+ }
518
+ if (event.key === "ArrowUp") {
519
+ event.preventDefault();
520
+ setHasNavigatedSuggestions(true);
521
+ if (!isPopoverOpen) {
522
+ setIsOpen(true);
523
+ return;
524
+ }
525
+ setActiveIndex((previousIndex_1) => suggestions.length === 0 ? 0 : (previousIndex_1 - 1 + suggestions.length) % suggestions.length);
526
+ return;
527
+ }
528
+ if (event.key === "Enter" && activeSuggestion) {
529
+ event.preventDefault();
530
+ applySuggestion(activeSuggestion);
531
+ return;
532
+ }
533
+ if (event.key === "Tab" && hasNavigatedSuggestions && activeSuggestion) {
534
+ event.preventDefault();
535
+ applySuggestion(activeSuggestion);
536
+ return;
537
+ }
538
+ if (event.key === "Escape" && isPopoverOpen) {
539
+ event.preventDefault();
540
+ event.stopPropagation();
541
+ setIsOpen(false);
542
+ }
543
+ };
544
+ $[27] = activeSuggestion;
545
+ $[28] = applySuggestion;
546
+ $[29] = hasNavigatedSuggestions;
547
+ $[30] = isPopoverOpen;
548
+ $[31] = suggestions.length;
549
+ $[32] = t15;
550
+ } else {
551
+ t15 = $[32];
552
+ }
553
+ const handleKeyDown = t15;
554
+ let t16;
555
+ if ($[33] !== onValueChange) {
556
+ t16 = () => {
557
+ onValueChange("");
558
+ pendingCaretRef.current = 0;
559
+ setIsOpen(false);
560
+ inputRef.current?.focus();
561
+ };
562
+ $[33] = onValueChange;
563
+ $[34] = t16;
564
+ } else {
565
+ t16 = $[34];
566
+ }
567
+ const handleClear = t16;
568
+ let t17;
569
+ if ($[35] !== className) {
570
+ t17 = cn("grid w-full gap-2", className);
571
+ $[35] = className;
572
+ $[36] = t17;
573
+ } else {
574
+ t17 = $[36];
575
+ }
576
+ const t18 = activeSuggestion ? `${instanceId}-${activeSuggestion.id}` : undefined;
577
+ const t19 = trailingAction ? "pr-16" : "pr-9";
578
+ let t20;
579
+ if ($[37] !== t19) {
580
+ t20 = cn("w-full text-transparent", t19);
581
+ $[37] = t19;
582
+ $[38] = t20;
583
+ } else {
584
+ t20 = $[38];
585
+ }
586
+ let t21;
587
+ if ($[39] !== onValueChange) {
588
+ t21 = (event_0) => {
589
+ onValueChange(event_0.target.value);
590
+ setIsOpen(true);
591
+ setActiveIndex(0);
592
+ setHasNavigatedSuggestions(false);
593
+ setCaretIndex(event_0.target.selectionStart ?? event_0.target.value.length);
594
+ };
595
+ $[39] = onValueChange;
596
+ $[40] = t21;
597
+ } else {
598
+ t21 = $[40];
599
+ }
600
+ let t22;
601
+ let t23;
602
+ if ($[41] !== onFocusChange) {
603
+ t22 = () => {
604
+ setHasNavigatedSuggestions(false);
605
+ onFocusChange?.(false);
606
+ };
607
+ t23 = () => {
608
+ syncCaretFromInput();
609
+ setIsOpen(true);
610
+ onFocusChange?.(true);
611
+ };
612
+ $[41] = onFocusChange;
613
+ $[42] = t22;
614
+ $[43] = t23;
615
+ } else {
616
+ t22 = $[42];
617
+ t23 = $[43];
618
+ }
619
+ let t24;
620
+ if ($[44] === Symbol.for("react.memo_cache_sentinel")) {
621
+ t24 = { caretColor: "var(--foreground)" };
622
+ $[44] = t24;
623
+ } else {
624
+ t24 = $[44];
625
+ }
626
+ let t25;
627
+ if ($[45] !== ariaLabel || $[46] !== handleKeyDown || $[47] !== isPopoverOpen || $[48] !== listboxId || $[49] !== placeholder || $[50] !== t18 || $[51] !== t20 || $[52] !== t21 || $[53] !== t22 || $[54] !== t23 || $[55] !== value) {
628
+ t25 = /* @__PURE__ */ _jsx(Input, {
629
+ "aria-activedescendant": t18,
630
+ "aria-autocomplete": "list",
631
+ "aria-controls": listboxId,
632
+ "aria-expanded": isPopoverOpen,
633
+ "aria-label": ariaLabel,
634
+ autoComplete: "off",
635
+ className: t20,
636
+ onChange: t21,
637
+ onClick: syncCaretFromInput,
638
+ onBlur: t22,
639
+ onFocus: t23,
640
+ onKeyDown: handleKeyDown,
641
+ onKeyUp: syncCaretFromInput,
642
+ onScroll: syncOverlayScroll,
643
+ placeholder,
644
+ ref: inputRef,
645
+ role: "combobox",
646
+ style: t24,
647
+ type: "text",
648
+ value
649
+ });
650
+ $[45] = ariaLabel;
651
+ $[46] = handleKeyDown;
652
+ $[47] = isPopoverOpen;
653
+ $[48] = listboxId;
654
+ $[49] = placeholder;
655
+ $[50] = t18;
656
+ $[51] = t20;
657
+ $[52] = t21;
658
+ $[53] = t22;
659
+ $[54] = t23;
660
+ $[55] = value;
661
+ $[56] = t25;
662
+ } else {
663
+ t25 = $[56];
664
+ }
665
+ const t26 = trailingAction ? "pr-16" : "pr-9";
666
+ let t27;
667
+ if ($[57] !== t26) {
668
+ t27 = cn("pointer-events-none absolute inset-0 flex items-center overflow-hidden rounded-lg border border-transparent px-2.5 text-base text-foreground select-none md:text-sm", t26);
669
+ $[57] = t26;
670
+ $[58] = t27;
671
+ } else {
672
+ t27 = $[58];
673
+ }
674
+ let t28;
675
+ if ($[59] !== highlightRanges || $[60] !== value) {
676
+ t28 = renderHighlightedQuery(value, highlightRanges);
677
+ $[59] = highlightRanges;
678
+ $[60] = value;
679
+ $[61] = t28;
680
+ } else {
681
+ t28 = $[61];
682
+ }
683
+ let t29;
684
+ if ($[62] !== t28) {
685
+ t29 = /* @__PURE__ */ _jsx("span", {
686
+ className: "whitespace-pre",
687
+ children: t28
688
+ });
689
+ $[62] = t28;
690
+ $[63] = t29;
691
+ } else {
692
+ t29 = $[63];
693
+ }
694
+ let t30;
695
+ if ($[64] !== t27 || $[65] !== t29) {
696
+ t30 = /* @__PURE__ */ _jsx("div", {
697
+ "aria-hidden": "true",
698
+ className: t27,
699
+ ref: highlightOverlayRef,
700
+ children: t29
701
+ });
702
+ $[64] = t27;
703
+ $[65] = t29;
704
+ $[66] = t30;
705
+ } else {
706
+ t30 = $[66];
707
+ }
708
+ let t31;
709
+ if ($[67] !== handleClear || $[68] !== value) {
710
+ t31 = value ? /* @__PURE__ */ _jsx(Button, {
711
+ "aria-label": CLEAR_FILTER_ARIA_LABEL,
712
+ className: "active:-translate-y-0",
713
+ onClick: handleClear,
714
+ size: "icon-xs",
715
+ type: "button",
716
+ variant: "ghost",
717
+ children: /* @__PURE__ */ _jsx(X, { "aria-hidden": "true" })
718
+ }) : null;
719
+ $[67] = handleClear;
720
+ $[68] = value;
721
+ $[69] = t31;
722
+ } else {
723
+ t31 = $[69];
724
+ }
725
+ let t32;
726
+ if ($[70] !== t31 || $[71] !== trailingAction) {
727
+ t32 = /* @__PURE__ */ _jsxs("div", {
728
+ className: "absolute right-1 top-1/2 flex -translate-y-1/2 items-center gap-0.5",
729
+ children: [t31, trailingAction]
730
+ });
731
+ $[70] = t31;
732
+ $[71] = trailingAction;
733
+ $[72] = t32;
734
+ } else {
735
+ t32 = $[72];
736
+ }
737
+ let t33;
738
+ if ($[73] !== t25 || $[74] !== t30 || $[75] !== t32) {
739
+ t33 = /* @__PURE__ */ _jsx(PopoverAnchor, {
740
+ asChild: true,
741
+ children: /* @__PURE__ */ _jsxs("div", {
742
+ className: "relative w-full",
743
+ children: [
744
+ t25,
745
+ t30,
746
+ t32
747
+ ]
748
+ })
749
+ });
750
+ $[73] = t25;
751
+ $[74] = t30;
752
+ $[75] = t32;
753
+ $[76] = t33;
754
+ } else {
755
+ t33 = $[76];
756
+ }
757
+ const t34 = suggestions.some(_temp3) ? VALUE_GROUP_LABEL : KEY_GROUP_LABEL;
758
+ let t35;
759
+ if ($[77] !== activeIndex || $[78] !== applySuggestion || $[79] !== instanceId || $[80] !== suggestions) {
760
+ t35 = suggestions.map((suggestion_1, index) => {
761
+ const SuggestionIcon = suggestion_1.icon;
762
+ const showSeparator = index > 0 && suggestions[index - 1].group !== suggestion_1.group;
763
+ return /* @__PURE__ */ _jsxs(React.Fragment, { children: [showSeparator ? /* @__PURE__ */ _jsx("li", {
764
+ "aria-hidden": "true",
765
+ className: "my-1 border-t border-border",
766
+ role: "presentation"
767
+ }) : null, /* @__PURE__ */ _jsxs("li", {
768
+ "aria-selected": index === activeIndex,
769
+ className: cn("flex cursor-pointer items-center gap-2 rounded-md px-2 py-1.5 text-sm", index === activeIndex ? "bg-accent text-accent-foreground" : "text-foreground"),
770
+ id: `${instanceId}-${suggestion_1.id}`,
771
+ onMouseDown: (event_3) => {
772
+ event_3.preventDefault();
773
+ applySuggestion(suggestion_1);
774
+ },
775
+ role: "option",
776
+ children: [/* @__PURE__ */ _jsx(SuggestionIcon, {
777
+ "aria-hidden": "true",
778
+ className: "size-4 shrink-0 text-muted-foreground"
779
+ }), /* @__PURE__ */ _jsx("span", {
780
+ className: "truncate",
781
+ children: suggestion_1.label
782
+ })]
783
+ })] }, suggestion_1.id);
784
+ });
785
+ $[77] = activeIndex;
786
+ $[78] = applySuggestion;
787
+ $[79] = instanceId;
788
+ $[80] = suggestions;
789
+ $[81] = t35;
790
+ } else {
791
+ t35 = $[81];
792
+ }
793
+ let t36;
794
+ if ($[82] !== listboxId || $[83] !== t34 || $[84] !== t35) {
795
+ t36 = /* @__PURE__ */ _jsx(PopoverContent, {
796
+ align: "start",
797
+ className: "w-[250px] max-h-[280px] gap-0 overflow-y-auto p-1",
798
+ onCloseAutoFocus: _temp,
799
+ onOpenAutoFocus: _temp2,
800
+ children: /* @__PURE__ */ _jsx("ul", {
801
+ "aria-label": t34,
802
+ className: "grid gap-0.5",
803
+ id: listboxId,
804
+ role: "listbox",
805
+ children: t35
806
+ })
807
+ });
808
+ $[82] = listboxId;
809
+ $[83] = t34;
810
+ $[84] = t35;
811
+ $[85] = t36;
812
+ } else {
813
+ t36 = $[85];
814
+ }
815
+ let t37;
816
+ if ($[86] !== isPopoverOpen || $[87] !== t33 || $[88] !== t36) {
817
+ t37 = /* @__PURE__ */ _jsxs(Popover, {
818
+ onOpenChange: setIsOpen,
819
+ open: isPopoverOpen,
820
+ children: [t33, t36]
821
+ });
822
+ $[86] = isPopoverOpen;
823
+ $[87] = t33;
824
+ $[88] = t36;
825
+ $[89] = t37;
826
+ } else {
827
+ t37 = $[89];
828
+ }
829
+ const t38 = isPopoverOpen ? `${suggestions.length} sugerencias disponibles` : "";
830
+ const t39 = invalidTokenCount > 0 ? ` ${invalidTokenCount} filtros no aplicados` : "";
831
+ let t40;
832
+ if ($[90] !== t38 || $[91] !== t39) {
833
+ t40 = /* @__PURE__ */ _jsxs("span", {
834
+ "aria-live": "polite",
835
+ className: "sr-only",
836
+ children: [t38, t39]
837
+ });
838
+ $[90] = t38;
839
+ $[91] = t39;
840
+ $[92] = t40;
841
+ } else {
842
+ t40 = $[92];
843
+ }
844
+ let t41;
845
+ if ($[93] !== t17 || $[94] !== t37 || $[95] !== t40) {
846
+ t41 = /* @__PURE__ */ _jsxs("div", {
847
+ className: t17,
848
+ children: [t37, t40]
849
+ });
850
+ $[93] = t17;
851
+ $[94] = t37;
852
+ $[95] = t40;
853
+ $[96] = t41;
854
+ } else {
855
+ t41 = $[96];
856
+ }
857
+ return t41;
858
+ }
859
+ function _temp3(suggestion_0) {
860
+ return suggestion_0.group === "value";
861
+ }
862
+ function _temp2(event_2) {
863
+ return event_2.preventDefault();
864
+ }
865
+ function _temp(event_1) {
866
+ return event_1.preventDefault();
526
867
  }