@shipfox/react-ui 0.32.2 → 0.33.1

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 (125) hide show
  1. package/dist/components/alert/index.d.ts +1 -1
  2. package/dist/components/avatar/avatar-group.d.ts +1 -1
  3. package/dist/components/avatar/avatar.d.ts +1 -1
  4. package/dist/components/avatar/index.d.ts +2 -2
  5. package/dist/components/badge/badge.d.ts +1 -1
  6. package/dist/components/badge/icon-badge.d.ts +1 -1
  7. package/dist/components/badge/index.d.ts +4 -4
  8. package/dist/components/button/button-link.d.ts +1 -1
  9. package/dist/components/button/button.d.ts +1 -1
  10. package/dist/components/button/icon-button.d.ts +1 -1
  11. package/dist/components/button/index.d.ts +3 -3
  12. package/dist/components/button-group/index.d.ts +1 -1
  13. package/dist/components/calendar/index.d.ts +1 -1
  14. package/dist/components/card/card.d.ts +1 -1
  15. package/dist/components/card/index.d.ts +1 -1
  16. package/dist/components/checkbox/checkbox-label.d.ts +1 -1
  17. package/dist/components/checkbox/checkbox-links.d.ts +1 -1
  18. package/dist/components/checkbox/index.d.ts +3 -3
  19. package/dist/components/code-block/code-block-footer.js +7 -2
  20. package/dist/components/code-block/index.d.ts +3 -3
  21. package/dist/components/combobox/combobox.d.ts +1 -1
  22. package/dist/components/combobox/index.d.ts +1 -1
  23. package/dist/components/command/command.d.ts +1 -1
  24. package/dist/components/command/index.d.ts +1 -1
  25. package/dist/components/confetti/index.d.ts +1 -1
  26. package/dist/components/count-up/index.d.ts +1 -1
  27. package/dist/components/dashboard/components/charts/bar-chart.d.ts +1 -1
  28. package/dist/components/dashboard/components/charts/index.d.ts +5 -5
  29. package/dist/components/dashboard/components/charts/line-chart.d.ts +1 -1
  30. package/dist/components/dashboard/components/charts/utils.d.ts +2 -2
  31. package/dist/components/dashboard/components/dashboard-alert.d.ts +1 -1
  32. package/dist/components/dashboard/components/mobile-sidebar.d.ts +1 -1
  33. package/dist/components/dashboard/components/sidebar.d.ts +1 -1
  34. package/dist/components/dashboard/components/topbar-button.d.ts +1 -1
  35. package/dist/components/dashboard/context/dashboard-context.d.ts +2 -2
  36. package/dist/components/dashboard/context/index.d.ts +4 -4
  37. package/dist/components/dashboard/context/types.d.ts +1 -1
  38. package/dist/components/dashboard/context/utils.d.ts +1 -1
  39. package/dist/components/dashboard/index.d.ts +18 -18
  40. package/dist/components/dashboard/pages/index.d.ts +2 -2
  41. package/dist/components/dashboard/table/index.d.ts +2 -2
  42. package/dist/components/dashboard/toolbar/filter-button.d.ts +1 -1
  43. package/dist/components/dashboard/toolbar/index.d.ts +9 -9
  44. package/dist/components/date-picker/index.d.ts +1 -1
  45. package/dist/components/date-time-range-picker/index.d.ts +1 -1
  46. package/dist/components/dot-grid/index.d.ts +1 -1
  47. package/dist/components/dropdown-menu/dropdown-menu.d.ts +1 -1
  48. package/dist/components/dropdown-menu/index.d.ts +1 -1
  49. package/dist/components/dynamic-item/dynamic-item.d.ts +1 -1
  50. package/dist/components/dynamic-item/index.d.ts +1 -1
  51. package/dist/components/empty-state/empty-state.d.ts +1 -1
  52. package/dist/components/empty-state/index.d.ts +1 -1
  53. package/dist/components/form/index.d.ts +1 -1
  54. package/dist/components/icon/custom/index.d.ts +14 -14
  55. package/dist/components/icon/icon.d.ts +1 -1
  56. package/dist/components/icon/index.d.ts +1 -1
  57. package/dist/components/index.d.ts +47 -47
  58. package/dist/components/inline-tips/index.d.ts +1 -1
  59. package/dist/components/input/index.d.ts +1 -1
  60. package/dist/components/interval-selector/hooks/index.d.ts +3 -3
  61. package/dist/components/interval-selector/hooks/use-interval-selector-input.d.ts +1 -1
  62. package/dist/components/interval-selector/hooks/use-interval-selector-navigation.d.ts +1 -1
  63. package/dist/components/interval-selector/hooks/use-interval-selector.d.ts +2 -2
  64. package/dist/components/interval-selector/index.d.ts +2 -2
  65. package/dist/components/interval-selector/interval-selector-calendar.d.ts +1 -1
  66. package/dist/components/interval-selector/interval-selector-input.d.ts +1 -1
  67. package/dist/components/interval-selector/interval-selector-suggestions.d.ts +1 -1
  68. package/dist/components/interval-selector/interval-selector.d.ts +1 -1
  69. package/dist/components/interval-selector/utils/format.d.ts +1 -1
  70. package/dist/components/interval-selector/utils/index.d.ts +2 -2
  71. package/dist/components/item/index.d.ts +1 -1
  72. package/dist/components/kbd/index.d.ts +1 -1
  73. package/dist/components/label/index.d.ts +1 -1
  74. package/dist/components/modal/index.d.ts +1 -1
  75. package/dist/components/moving-border/index.d.ts +1 -1
  76. package/dist/components/popover/index.d.ts +1 -1
  77. package/dist/components/scroll-area/index.d.ts +1 -1
  78. package/dist/components/search/index.d.ts +6 -6
  79. package/dist/components/search/search-inline.d.ts +1 -1
  80. package/dist/components/search/search-modal.d.ts +1 -1
  81. package/dist/components/search/search-trigger.d.ts +1 -1
  82. package/dist/components/select/index.d.ts +1 -1
  83. package/dist/components/select/select.d.ts +1 -1
  84. package/dist/components/sheet/index.d.ts +1 -1
  85. package/dist/components/shiny-text/index.d.ts +1 -1
  86. package/dist/components/shipql-editor/index.d.ts +2 -2
  87. package/dist/components/shipql-editor/lexical/on-blur-plugin.d.ts +2 -1
  88. package/dist/components/shipql-editor/lexical/on-blur-plugin.js +8 -4
  89. package/dist/components/shipql-editor/lexical/shipql-leaf-node.d.ts +5 -2
  90. package/dist/components/shipql-editor/lexical/shipql-leaf-node.js +18 -8
  91. package/dist/components/shipql-editor/lexical/shipql-plugin.d.ts +3 -2
  92. package/dist/components/shipql-editor/lexical/shipql-plugin.js +188 -34
  93. package/dist/components/shipql-editor/shipql-editor-inner.d.ts +2 -2
  94. package/dist/components/shipql-editor/shipql-editor-inner.js +52 -7
  95. package/dist/components/shipql-editor/shipql-editor.d.ts +4 -3
  96. package/dist/components/shipql-editor/suggestions/generate-suggestions.d.ts +9 -3
  97. package/dist/components/shipql-editor/suggestions/generate-suggestions.js +94 -20
  98. package/dist/components/shipql-editor/suggestions/shipql-range-facet-panel.d.ts +1 -1
  99. package/dist/components/shipql-editor/suggestions/shipql-range-facet-panel.js +2 -2
  100. package/dist/components/shipql-editor/suggestions/shipql-suggestion-item.d.ts +2 -2
  101. package/dist/components/shipql-editor/suggestions/shipql-suggestion-item.js +74 -10
  102. package/dist/components/shipql-editor/suggestions/shipql-suggestions-dropdown.d.ts +4 -3
  103. package/dist/components/shipql-editor/suggestions/shipql-suggestions-dropdown.js +56 -11
  104. package/dist/components/shipql-editor/suggestions/shipql-suggestions-plugin.d.ts +2 -2
  105. package/dist/components/shipql-editor/suggestions/shipql-suggestions-plugin.js +7 -5
  106. package/dist/components/shipql-editor/suggestions/types.d.ts +25 -3
  107. package/dist/components/skeleton/index.d.ts +1 -1
  108. package/dist/components/slider/index.d.ts +1 -1
  109. package/dist/components/switch/index.d.ts +1 -1
  110. package/dist/components/table/index.d.ts +4 -4
  111. package/dist/components/table/table.stories.columns.d.ts +1 -1
  112. package/dist/components/tabs/index.d.ts +1 -1
  113. package/dist/components/textarea/index.d.ts +1 -1
  114. package/dist/components/theme/index.d.ts +1 -1
  115. package/dist/components/theme/theme-provider.d.ts +1 -1
  116. package/dist/components/toast/index.d.ts +2 -2
  117. package/dist/components/tooltip/index.d.ts +1 -1
  118. package/dist/components/typography/index.d.ts +3 -3
  119. package/dist/hooks/index.d.ts +6 -6
  120. package/dist/hooks/useTheme.d.ts +1 -1
  121. package/dist/index.d.ts +4 -4
  122. package/dist/styles.css +2 -1
  123. package/dist/utils/format/index.d.ts +5 -5
  124. package/dist/utils/index.d.ts +6 -6
  125. package/package.json +10 -10
@@ -11,14 +11,37 @@ export function tryParse(text) {
11
11
  }
12
12
  // ─── Facet normalization ───────────────────────────────────────────────────────
13
13
  export function normalizeFacets(facets) {
14
- return facets.map((f)=>typeof f === 'string' ? f : f.name);
14
+ return facets.map((f)=>typeof f === 'string' ? f : f.id);
15
15
  }
16
- export function getFacetConfig(facets, name) {
16
+ export function getFacetConfig(facets, id) {
17
17
  for (const f of facets){
18
- if (typeof f !== 'string' && f.name.toLowerCase() === name.toLowerCase()) return f.config;
18
+ if (typeof f !== 'string' && f.id.toLowerCase() === id.toLowerCase()) return f.config;
19
19
  }
20
20
  return undefined;
21
21
  }
22
+ // Private: finds the object-form FacetDef by exact id match.
23
+ function findFacet(facets, id) {
24
+ for (const f of facets){
25
+ if (typeof f !== 'string' && f.id === id) return f;
26
+ }
27
+ return undefined;
28
+ }
29
+ /** Returns the human-readable display label, falling back to the raw id. */ export function getFacetLabel(facets, id) {
30
+ return findFacet(facets, id)?.metadata?.label ?? id;
31
+ }
32
+ /** Returns the description for a facet, if any. */ export function getFacetDescription(facets, id) {
33
+ return findFacet(facets, id)?.metadata?.description;
34
+ }
35
+ /** Returns grouping information for a facet, with defaults applied. */ export function getFacetGroupInfo(facets, id) {
36
+ const metadata = findFacet(facets, id)?.metadata;
37
+ const key = metadata?.group ?? '';
38
+ return {
39
+ key,
40
+ label: metadata?.groupLabel ?? (key ? key.charAt(0).toUpperCase() + key.slice(1) : undefined),
41
+ order: metadata?.groupOrder ?? Infinity,
42
+ icon: metadata?.groupIcon
43
+ };
44
+ }
22
45
  // ─── Leaf helpers ─────────────────────────────────────────────────────────────
23
46
  export function extractFacetFromLeaf(leaf) {
24
47
  if (leaf.type === 'match') return leaf.facet;
@@ -70,13 +93,32 @@ export function detectFacetContext(activeText, facets) {
70
93
  // ─── Suggestion builder ───────────────────────────────────────────────────────
71
94
  export function buildSuggestionItems(facets, valueSuggestions, activeText, focusedLeaf) {
72
95
  const facetNames = normalizeFacets(facets);
73
- const header = (label)=>({
96
+ const header = (label, iconName)=>({
74
97
  value: `__header__${label}`,
75
98
  label,
76
- icon: null,
99
+ icon: iconName ? /*#__PURE__*/ _jsx(Icon, {
100
+ name: iconName,
101
+ className: "size-12 text-foreground-neutral-muted"
102
+ }) : null,
77
103
  selected: false,
78
104
  type: 'section-header'
79
105
  });
106
+ const facetContext = (id)=>{
107
+ const groupInfo = getFacetGroupInfo(facets, id);
108
+ return {
109
+ value: `__facet-context__${id}`,
110
+ label: getFacetLabel(facets, id),
111
+ icon: groupInfo.icon ? /*#__PURE__*/ _jsx(Icon, {
112
+ name: groupInfo.icon,
113
+ className: "size-12 text-foreground-neutral-muted"
114
+ }) : null,
115
+ selected: false,
116
+ type: 'facet-context',
117
+ facetName: id,
118
+ description: getFacetDescription(facets, id),
119
+ sectionLabel: groupInfo.label
120
+ };
121
+ };
80
122
  // Focused leaf — show values with current value marked selected.
81
123
  // Text-type leaves (bare words) are not facet:value matches, so fall through
82
124
  // to facet filtering using the leaf's text as the partial query.
@@ -99,7 +141,7 @@ export function buildSuggestionItems(facets, valueSuggestions, activeText, focus
99
141
  const currentValue = extractValueFromLeaf(focusedLeaf);
100
142
  if (valueSuggestions.length === 0) return [];
101
143
  return [
102
- header(facetName.toUpperCase()),
144
+ facetContext(facetName),
103
145
  ...valueSuggestions.map((v)=>{
104
146
  const selected = v === currentValue;
105
147
  return {
@@ -134,7 +176,7 @@ export function buildSuggestionItems(facets, valueSuggestions, activeText, focus
134
176
  // Regular facet — show value suggestions (parent is responsible for filtering)
135
177
  if (valueSuggestions.length === 0) return [];
136
178
  return [
137
- header(facetCtx.facet.toUpperCase()),
179
+ facetContext(facetCtx.facet),
138
180
  ...valueSuggestions.map((v)=>({
139
181
  value: v,
140
182
  label: v,
@@ -151,20 +193,52 @@ export function buildSuggestionItems(facets, valueSuggestions, activeText, focus
151
193
  // before matching so "NOT sta" still suggests "status", "-sta" suggests "status", etc.
152
194
  const rawPartial = focusedLeaf?.type === 'text' ? focusedLeaf.value : activeText;
153
195
  const partial = stripNegationPrefix(rawPartial.trim()).stripped.toLowerCase();
154
- const filtered = partial ? facetNames.filter((f)=>f.toLowerCase().includes(partial)) : facetNames;
196
+ // Filter against raw id AND metadata label
197
+ const filtered = partial ? facets.filter((f)=>{
198
+ const id = typeof f === 'string' ? f : f.id;
199
+ const label = typeof f !== 'string' ? f.metadata?.label : undefined;
200
+ return id.toLowerCase().includes(partial) || (label?.toLowerCase().includes(partial) ?? false);
201
+ }) : facets;
155
202
  if (filtered.length === 0) return [];
156
- return [
157
- header('TYPE'),
158
- ...filtered.map((f)=>({
159
- value: f,
160
- label: f,
161
- icon: /*#__PURE__*/ _jsx(Icon, {
162
- name: "searchLine",
163
- className: "size-16 text-foreground-neutral-subtle"
164
- }),
165
- selected: false
166
- }))
167
- ];
203
+ const groups = new Map();
204
+ for (const f of filtered){
205
+ const id = typeof f === 'string' ? f : f.id;
206
+ const { key: group, label: groupLabel, order: groupOrder, icon: groupIcon } = getFacetGroupInfo(facets, id);
207
+ if (!groups.has(group)) groups.set(group, []);
208
+ const groupList = groups.get(group);
209
+ if (groupList) groupList.push({
210
+ id,
211
+ label: getFacetLabel(facets, id),
212
+ description: getFacetDescription(facets, id),
213
+ groupOrder,
214
+ groupLabel,
215
+ groupIcon
216
+ });
217
+ }
218
+ const sortedGroups = [
219
+ ...groups.entries()
220
+ ].sort(([, aItems], [, bItems])=>{
221
+ const aOrder = aItems[0]?.groupOrder ?? Infinity;
222
+ const bOrder = bItems[0]?.groupOrder ?? Infinity;
223
+ return aOrder - bOrder;
224
+ });
225
+ const items = [];
226
+ for (const [, groupItems] of sortedGroups){
227
+ groupItems.sort((a, b)=>a.label.localeCompare(b.label));
228
+ const firstItem = groupItems[0];
229
+ if (!firstItem) continue;
230
+ if (firstItem.groupLabel) items.push(header(firstItem.groupLabel, firstItem.groupIcon));
231
+ for (const entry of groupItems){
232
+ items.push({
233
+ value: entry.id,
234
+ label: entry.label,
235
+ icon: null,
236
+ selected: false,
237
+ description: entry.description
238
+ });
239
+ }
240
+ }
241
+ return items;
168
242
  }
169
243
 
170
244
  //# sourceMappingURL=generate-suggestions.js.map
@@ -1,4 +1,4 @@
1
- import type { RangeFacetConfig } from './types';
1
+ import type { RangeFacetConfig } from './types.js';
2
2
  export interface ShipQLRangeFacetPanelProps {
3
3
  facetName: string;
4
4
  config: RangeFacetConfig;
@@ -151,10 +151,10 @@ export function ShipQLRangeFacetPanel({ facetName, config, onApply, isSelectingR
151
151
  isSelectingRef.current = true;
152
152
  };
153
153
  const onUp = ()=>{
154
- // Small delay so the blur handler fires first and sees isSelectingRef=true
154
+ // Defer reset so the blur handler still sees isSelectingRef=true.
155
155
  setTimeout(()=>{
156
156
  isSelectingRef.current = false;
157
- }, 150);
157
+ }, 0);
158
158
  };
159
159
  el.addEventListener('pointerdown', onDown);
160
160
  window.addEventListener('pointerup', onUp);
@@ -1,10 +1,10 @@
1
- import type { SuggestionItem } from './types';
1
+ import type { SuggestionItem } from './types.js';
2
2
  interface ShipQLSuggestionItemProps {
3
3
  item: SuggestionItem;
4
4
  isHighlighted: boolean;
5
5
  isNegated?: boolean;
6
6
  onMouseDown: (value: string) => void;
7
- itemRef?: (el: HTMLButtonElement | null) => void;
7
+ itemRef?: (el: HTMLElement | null) => void;
8
8
  }
9
9
  export declare function ShipQLSuggestionItem({ item, isHighlighted, isNegated, onMouseDown, itemRef, }: ShipQLSuggestionItemProps): import("react/jsx-runtime").JSX.Element;
10
10
  export {};
@@ -2,14 +2,56 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { cn } from '../../../utils/cn.js';
3
3
  export function ShipQLSuggestionItem({ item, isHighlighted, isNegated, onMouseDown, itemRef }) {
4
4
  if (item.type === 'section-header') {
5
- return /*#__PURE__*/ _jsx("div", {
6
- className: "flex w-full items-center px-8 h-30 shrink-0",
7
- children: /*#__PURE__*/ _jsx("span", {
8
- className: "text-xs font-normal uppercase text-foreground-neutral-muted",
9
- children: item.label
10
- })
5
+ return /*#__PURE__*/ _jsxs("div", {
6
+ ref: itemRef,
7
+ className: "flex w-full items-center gap-6 px-8 h-30 shrink-0",
8
+ children: [
9
+ item.icon,
10
+ /*#__PURE__*/ _jsx("span", {
11
+ className: "text-xs font-normal uppercase text-foreground-neutral-muted",
12
+ children: item.label
13
+ })
14
+ ]
15
+ });
16
+ }
17
+ if (item.type === 'facet-context') {
18
+ const showRawFacetName = typeof item.label === 'string' && item.facetName && item.label !== item.facetName;
19
+ return /*#__PURE__*/ _jsxs("div", {
20
+ ref: itemRef,
21
+ className: "flex w-full flex-col gap-2 px-8 py-8 border-b border-border-neutral-subtle shrink-0",
22
+ children: [
23
+ item.sectionLabel && /*#__PURE__*/ _jsxs("div", {
24
+ className: "flex items-center gap-4",
25
+ children: [
26
+ item.icon,
27
+ /*#__PURE__*/ _jsx("span", {
28
+ className: "text-xs text-foreground-neutral-muted",
29
+ children: item.sectionLabel
30
+ })
31
+ ]
32
+ }),
33
+ /*#__PURE__*/ _jsxs("div", {
34
+ className: "flex items-baseline gap-8",
35
+ children: [
36
+ /*#__PURE__*/ _jsx("span", {
37
+ className: "text-sm font-medium text-foreground-neutral-base",
38
+ children: item.label
39
+ }),
40
+ showRawFacetName && /*#__PURE__*/ _jsx("span", {
41
+ className: "font-mono text-xs text-foreground-neutral-muted",
42
+ children: item.facetName
43
+ })
44
+ ]
45
+ }),
46
+ item.description && /*#__PURE__*/ _jsx("span", {
47
+ className: "text-xs text-foreground-neutral-muted",
48
+ children: item.description
49
+ })
50
+ ]
11
51
  });
12
52
  }
53
+ const labelText = isNegated ? `-${item.label}` : item.label;
54
+ const showRawId = typeof item.label === 'string' && item.label !== item.value;
13
55
  return /*#__PURE__*/ _jsxs("button", {
14
56
  ref: itemRef,
15
57
  type: "button",
@@ -17,15 +59,37 @@ export function ShipQLSuggestionItem({ item, isHighlighted, isNegated, onMouseDo
17
59
  e.preventDefault();
18
60
  onMouseDown(item.value);
19
61
  },
20
- className: cn('flex w-full items-center gap-12 rounded-none px-8 py-6 h-24 text-left transition-colors duration-75 cursor-pointer', isHighlighted ? 'bg-background-button-transparent-hover' : 'hover:bg-background-button-transparent-hover'),
62
+ className: cn('flex w-full gap-12 rounded-none px-8 py-6 text-left transition-colors duration-75 cursor-pointer', item.description ? 'items-start' : 'items-center h-24', isHighlighted ? 'bg-background-button-transparent-hover' : 'hover:bg-background-button-transparent-hover'),
21
63
  children: [
22
64
  /*#__PURE__*/ _jsxs("div", {
23
- className: "flex min-w-0 flex-1 items-center gap-12",
65
+ className: cn('flex min-w-0 flex-1 gap-12', item.description ? 'items-start' : 'items-center'),
24
66
  children: [
25
67
  item.icon,
26
- /*#__PURE__*/ _jsx("span", {
68
+ item.description ? /*#__PURE__*/ _jsxs("div", {
69
+ className: "flex min-w-0 flex-1 flex-col",
70
+ children: [
71
+ /*#__PURE__*/ _jsxs("div", {
72
+ className: "flex items-center gap-8",
73
+ children: [
74
+ /*#__PURE__*/ _jsx("span", {
75
+ className: cn('flex-1 truncate text-sm', isNegated ? 'text-foreground-highlights-interactive' : item.selected ? 'text-foreground-neutral-base' : 'text-foreground-neutral-subtle'),
76
+ children: labelText
77
+ }),
78
+ showRawId && /*#__PURE__*/ _jsx("span", {
79
+ className: "shrink-0 max-w-[40%] truncate font-mono text-xs text-foreground-neutral-muted",
80
+ children: item.value
81
+ })
82
+ ]
83
+ }),
84
+ /*#__PURE__*/ _jsx("span", {
85
+ title: item.description,
86
+ className: "truncate text-xs text-foreground-neutral-muted",
87
+ children: item.description
88
+ })
89
+ ]
90
+ }) : /*#__PURE__*/ _jsx("span", {
27
91
  className: cn('flex-1 truncate text-sm', isNegated ? 'text-foreground-highlights-interactive' : item.selected ? 'text-foreground-neutral-base' : 'text-foreground-neutral-subtle'),
28
- children: isNegated ? `-${item.label}` : item.label
92
+ children: labelText
29
93
  })
30
94
  ]
31
95
  }),
@@ -1,10 +1,11 @@
1
- import { type SyntaxHintMode } from './shipql-suggestions-footer';
2
- import type { SuggestionItem } from './types';
1
+ import { type SyntaxHintMode } from './shipql-suggestions-footer.js';
2
+ import type { SuggestionItem } from './types.js';
3
3
  interface ShipQLSuggestionsDropdownProps {
4
4
  items: SuggestionItem[];
5
5
  selectedIndex: number;
6
6
  isSelectingRef: React.RefObject<boolean>;
7
7
  onSelect: (value: string) => void;
8
+ onClose: () => void;
8
9
  isLoading?: boolean;
9
10
  isNegated: boolean;
10
11
  onToggleNegate: (negated: boolean) => void;
@@ -14,6 +15,6 @@ interface ShipQLSuggestionsDropdownProps {
14
15
  isError?: boolean;
15
16
  syntaxHintMode: SyntaxHintMode;
16
17
  }
17
- export declare function ShipQLSuggestionsDropdown({ items, selectedIndex, isSelectingRef, onSelect, isLoading, isNegated, onToggleNegate, showValueActions, showSyntaxHelp, onToggleSyntaxHelp, isError, syntaxHintMode, }: ShipQLSuggestionsDropdownProps): import("react/jsx-runtime").JSX.Element;
18
+ export declare function ShipQLSuggestionsDropdown({ items, selectedIndex, isSelectingRef, onSelect, onClose, isLoading, isNegated, onToggleNegate, showValueActions, showSyntaxHelp, onToggleSyntaxHelp, isError, syntaxHintMode, }: ShipQLSuggestionsDropdownProps): import("react/jsx-runtime").JSX.Element;
18
19
  export {};
19
20
  //# sourceMappingURL=shipql-suggestions-dropdown.d.ts.map
@@ -6,16 +6,40 @@ import { useCallback, useEffect, useRef } from 'react';
6
6
  import { ShipQLRangeFacetPanel } from './shipql-range-facet-panel.js';
7
7
  import { ShipQLSuggestionItem } from './shipql-suggestion-item.js';
8
8
  import { ShipQLSuggestionsFooter } from './shipql-suggestions-footer.js';
9
- export function ShipQLSuggestionsDropdown({ items, selectedIndex, isSelectingRef, onSelect, isLoading, isNegated, onToggleNegate, showValueActions, showSyntaxHelp, onToggleSyntaxHelp, isError, syntaxHintMode }) {
9
+ export function ShipQLSuggestionsDropdown({ items, selectedIndex, isSelectingRef, onSelect, onClose, isLoading, isNegated, onToggleNegate, showValueActions, showSyntaxHelp, onToggleSyntaxHelp, isError, syntaxHintMode }) {
10
10
  const itemRefs = useRef([]);
11
+ const prevSelectedIndexRef = useRef(-1);
11
12
  useEffect(()=>{
12
- const el = itemRefs.current[selectedIndex];
13
- if (el) el.scrollIntoView({
14
- behavior: 'smooth',
15
- block: 'nearest'
16
- });
13
+ const prevIndex = prevSelectedIndexRef.current;
14
+ prevSelectedIndexRef.current = selectedIndex;
15
+ const prevItem = items[selectedIndex - 1];
16
+ const isPrecededByHeader = prevItem?.type === 'section-header' || prevItem?.type === 'facet-context';
17
+ const isWrapping = prevIndex >= 0 && Math.abs(selectedIndex - prevIndex) > items.length / 2;
18
+ // When wrapping bottom→top the header is safe to include (it's just above the
19
+ // first item). When wrapping top→bottom we skip the header because it sits far
20
+ // above the last item and pinning to it would push the actual item off-screen.
21
+ const isWrappingToBottom = isWrapping && selectedIndex > prevIndex;
22
+ const isGoingForward = selectedIndex > prevIndex && !isWrapping || isWrapping && !isWrappingToBottom;
23
+ if (!isWrappingToBottom && isPrecededByHeader) {
24
+ const headerEl = itemRefs.current[selectedIndex - 1] ?? itemRefs.current[selectedIndex];
25
+ // Going forward / wrapping to top: pin the header to the top so the selected
26
+ // item below it is visible. Going backward: nearest is enough because the
27
+ // header is above and scrolling up will bring it into view at the top edge.
28
+ const block = isGoingForward ? 'start' : 'nearest';
29
+ if (headerEl) headerEl.scrollIntoView({
30
+ behavior: 'smooth',
31
+ block
32
+ });
33
+ } else {
34
+ const el = itemRefs.current[selectedIndex];
35
+ if (el) el.scrollIntoView({
36
+ behavior: 'smooth',
37
+ block: 'nearest'
38
+ });
39
+ }
17
40
  }, [
18
- selectedIndex
41
+ selectedIndex,
42
+ items
19
43
  ]);
20
44
  // Shift key toggles negation while dropdown is visible
21
45
  useEffect(()=>{
@@ -48,7 +72,7 @@ export function ShipQLSuggestionsDropdown({ items, selectedIndex, isSelectingRef
48
72
  onSelect(value);
49
73
  setTimeout(()=>{
50
74
  isSelectingRef.current = false;
51
- }, 150);
75
+ }, 0);
52
76
  }, [
53
77
  isSelectingRef,
54
78
  onSelect
@@ -76,7 +100,7 @@ export function ShipQLSuggestionsDropdown({ items, selectedIndex, isSelectingRef
76
100
  })
77
101
  ]
78
102
  }) : /*#__PURE__*/ _jsxs("div", {
79
- className: "flex flex-col overflow-hidden rounded-8 bg-background-neutral-base shadow-tooltip max-h-[min(70vh,320px)] min-h-0",
103
+ className: "flex flex-col overflow-hidden rounded-8 bg-background-neutral-base shadow-tooltip max-h-[min(70vh,400px)] min-h-0",
80
104
  children: [
81
105
  /*#__PURE__*/ _jsx(ScrollArea, {
82
106
  className: "flex-1 min-h-0 overflow-y-auto scrollbar",
@@ -111,15 +135,36 @@ export function ShipQLSuggestionsDropdown({ items, selectedIndex, isSelectingRef
111
135
  ]
112
136
  });
113
137
  return /*#__PURE__*/ _jsx(PopoverContent, {
138
+ "data-shipql-suggestions": true,
114
139
  align: "start",
115
140
  sideOffset: 4,
116
141
  className: "p-0 w-(--radix-popover-trigger-width) rounded-8",
117
142
  onOpenAutoFocus: (e)=>e.preventDefault(),
118
143
  onInteractOutside: (e)=>{
119
- if (isSelectingRef.current) e.preventDefault();
144
+ if (isSelectingRef.current) {
145
+ e.preventDefault();
146
+ return;
147
+ }
148
+ const target = e.detail?.originalEvent?.target;
149
+ if (target instanceof Element && target.closest('[data-shipql-editor]')) {
150
+ e.preventDefault();
151
+ return;
152
+ }
153
+ onClose();
120
154
  },
121
155
  onPointerDownOutside: (e)=>{
122
- if (isSelectingRef.current) e.preventDefault();
156
+ if (isSelectingRef.current) {
157
+ e.preventDefault();
158
+ return;
159
+ }
160
+ // Clicks inside the editor are handled by Lexical's focus/blur
161
+ // commands — only dismiss for clicks truly outside the editor.
162
+ const target = e.detail?.originalEvent?.target;
163
+ if (target instanceof Element && target.closest('[data-shipql-editor]')) {
164
+ e.preventDefault();
165
+ return;
166
+ }
167
+ onClose();
123
168
  },
124
169
  children: popoverContent
125
170
  });
@@ -1,6 +1,6 @@
1
1
  import type { AstNode } from '@shipfox/shipql-parser';
2
- import type { LeafAstNode } from '../lexical/shipql-leaf-node';
3
- import type { FacetDef, SuggestionItem } from './types';
2
+ import type { LeafAstNode } from '../lexical/shipql-leaf-node.js';
3
+ import type { FacetDef, SuggestionItem } from './types.js';
4
4
  interface ShipQLSuggestionsPluginProps {
5
5
  facets: FacetDef[];
6
6
  currentFacet: string | null;
@@ -280,11 +280,12 @@ export function ShipQLSuggestionsPlugin({ facets, currentFacet, setCurrentFacet,
280
280
  if (!openRef.current || itemsRef.current.length === 0) return false;
281
281
  e?.preventDefault();
282
282
  const its = itemsRef.current;
283
+ const isNonSelectable = (i)=>its[i]?.type === 'section-header' || its[i]?.type === 'facet-context';
283
284
  let next = selectedIndexRef.current + 1;
284
- while(next < its.length && its[next]?.type === 'section-header')next++;
285
+ while(next < its.length && isNonSelectable(next))next++;
285
286
  if (next >= its.length) {
286
287
  next = 0;
287
- while(next < its.length && its[next]?.type === 'section-header')next++;
288
+ while(next < its.length && isNonSelectable(next))next++;
288
289
  }
289
290
  if (next < its.length) {
290
291
  hasNavigatedRef.current = true;
@@ -296,11 +297,12 @@ export function ShipQLSuggestionsPlugin({ facets, currentFacet, setCurrentFacet,
296
297
  if (!openRef.current || itemsRef.current.length === 0) return false;
297
298
  e?.preventDefault();
298
299
  const its = itemsRef.current;
300
+ const isNonSelectable = (i)=>its[i]?.type === 'section-header' || its[i]?.type === 'facet-context';
299
301
  let prev = selectedIndexRef.current - 1;
300
- while(prev >= 0 && its[prev]?.type === 'section-header')prev--;
302
+ while(prev >= 0 && isNonSelectable(prev))prev--;
301
303
  if (prev < 0) {
302
304
  prev = its.length - 1;
303
- while(prev >= 0 && its[prev]?.type === 'section-header')prev--;
305
+ while(prev >= 0 && isNonSelectable(prev))prev--;
304
306
  }
305
307
  if (prev >= 0) {
306
308
  hasNavigatedRef.current = true;
@@ -312,7 +314,7 @@ export function ShipQLSuggestionsPlugin({ facets, currentFacet, setCurrentFacet,
312
314
  if (!openRef.current || itemsRef.current.length === 0) return false;
313
315
  if (!hasNavigatedRef.current || selectedIndexRef.current < 0) return false;
314
316
  const item = itemsRef.current[selectedIndexRef.current];
315
- if (!item || item.type === 'section-header') return false;
317
+ if (!item || item.type === 'section-header' || item.type === 'facet-context') return false;
316
318
  e?.preventDefault();
317
319
  applyRef.current?.(item.value);
318
320
  return true;
@@ -7,9 +7,28 @@ export interface RangeFacetConfig {
7
7
  presets?: string[];
8
8
  format?: (value: string) => string;
9
9
  }
10
+ export interface FacetGroupInfo {
11
+ /** Group key (empty string = ungrouped). */
12
+ key: string;
13
+ /** Display label for the section header. Derived from key when not explicitly set. */
14
+ label: string | undefined;
15
+ /** Sort order for the group. Defaults to Infinity (last). */
16
+ order: number;
17
+ /** Icon name for the section header, if any. */
18
+ icon: string | undefined;
19
+ }
20
+ export interface FacetMetadata {
21
+ label?: string;
22
+ description?: string;
23
+ group?: string;
24
+ groupLabel?: string;
25
+ groupOrder?: number;
26
+ groupIcon?: string;
27
+ }
10
28
  export type FacetDef = string | {
11
- name: string;
12
- config: RangeFacetConfig;
29
+ id: string;
30
+ config?: RangeFacetConfig;
31
+ metadata?: FacetMetadata;
13
32
  };
14
33
  export type FormatLeafDisplay = (source: string, node: AstNode) => string;
15
34
  export interface SuggestionItem {
@@ -17,8 +36,11 @@ export interface SuggestionItem {
17
36
  label: React.ReactNode;
18
37
  icon: React.ReactNode | null;
19
38
  selected: boolean;
20
- type?: 'section-header' | 'range-slider';
39
+ type?: 'section-header' | 'range-slider' | 'facet-context';
21
40
  rangeFacetConfig?: RangeFacetConfig;
22
41
  facetName?: string;
42
+ description?: string;
43
+ /** For facet-context items: the group label shown above the facet name */
44
+ sectionLabel?: string;
23
45
  }
24
46
  //# sourceMappingURL=types.d.ts.map
@@ -1,2 +1,2 @@
1
- export * from './skeleton';
1
+ export * from './skeleton.js';
2
2
  //# sourceMappingURL=index.d.ts.map
@@ -1,2 +1,2 @@
1
- export * from './slider';
1
+ export * from './slider.js';
2
2
  //# sourceMappingURL=index.d.ts.map
@@ -1,2 +1,2 @@
1
- export * from './switch';
1
+ export * from './switch.js';
2
2
  //# sourceMappingURL=index.d.ts.map
@@ -1,6 +1,6 @@
1
1
  export type { ColumnDef } from '@tanstack/react-table';
2
- export * from './data-table';
3
- export * from './table';
4
- export * from './table-column-header';
5
- export * from './table-pagination';
2
+ export * from './data-table.js';
3
+ export * from './table.js';
4
+ export * from './table-column-header.js';
5
+ export * from './table-pagination.js';
6
6
  //# sourceMappingURL=index.d.ts.map
@@ -2,7 +2,7 @@
2
2
  * Column definitions for Table stories
3
3
  */
4
4
  import type { ColumnDef } from '@tanstack/react-table';
5
- import type { JobData, SearchJobData, User } from './table.stories.data';
5
+ import type { JobData, SearchJobData, User } from './table.stories.data.js';
6
6
  /**
7
7
  * Create column definitions for JobData
8
8
  */
@@ -1,2 +1,2 @@
1
- export * from './tabs';
1
+ export * from './tabs.js';
2
2
  //# sourceMappingURL=index.d.ts.map
@@ -1,2 +1,2 @@
1
- export * from './textarea';
1
+ export * from './textarea.js';
2
2
  //# sourceMappingURL=index.d.ts.map
@@ -1,2 +1,2 @@
1
- export * from './theme-provider';
1
+ export * from './theme-provider.js';
2
2
  //# sourceMappingURL=index.d.ts.map
@@ -1,5 +1,5 @@
1
1
  import { type ReactNode } from 'react';
2
- import { type Theme } from '../../state/theme';
2
+ import { type Theme } from '../../state/theme.js';
3
3
  type ThemeProviderProps = {
4
4
  children: ReactNode;
5
5
  defaultTheme?: Theme;
@@ -1,3 +1,3 @@
1
- export * from './toast';
2
- export * from './toast-custom';
1
+ export * from './toast.js';
2
+ export * from './toast-custom.js';
3
3
  //# sourceMappingURL=index.d.ts.map
@@ -1,2 +1,2 @@
1
- export * from './tooltip';
1
+ export * from './tooltip.js';
2
2
  //# sourceMappingURL=index.d.ts.map
@@ -1,4 +1,4 @@
1
- export * from './code';
2
- export * from './header';
3
- export * from './text';
1
+ export * from './code.js';
2
+ export * from './header.js';
3
+ export * from './text.js';
4
4
  //# sourceMappingURL=index.d.ts.map
@@ -1,7 +1,7 @@
1
- export * from './useCopyToClipboard';
2
- export * from './useMediaQuery';
3
- export * from './useResolvedTheme';
4
- export * from './useShikiHighlight';
5
- export * from './useShikiStyleInjection';
6
- export * from './useTheme';
1
+ export * from './useCopyToClipboard.js';
2
+ export * from './useMediaQuery.js';
3
+ export * from './useResolvedTheme.js';
4
+ export * from './useShikiHighlight.js';
5
+ export * from './useShikiStyleInjection.js';
6
+ export * from './useTheme.js';
7
7
  //# sourceMappingURL=index.d.ts.map
@@ -1,2 +1,2 @@
1
- export declare const useTheme: () => import("../state/theme").ThemeProviderState;
1
+ export declare const useTheme: () => import("../state/theme.js").ThemeProviderState;
2
2
  //# sourceMappingURL=useTheme.d.ts.map
package/dist/index.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  export { ShipfoxLoader } from 'shipfox-loader-react';
2
- export * from './components';
3
- export * from './hooks';
4
- export type { Theme, ThemeProviderState } from './state/theme';
5
- export * from './utils';
2
+ export * from './components/index.js';
3
+ export * from './hooks/index.js';
4
+ export type { Theme, ThemeProviderState } from './state/theme.js';
5
+ export * from './utils/index.js';
6
6
  //# sourceMappingURL=index.d.ts.map