@seekora-ai/ui-sdk-vanilla 1.0.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 (71) hide show
  1. package/dist/components/clear-refinements.d.ts +39 -0
  2. package/dist/components/clear-refinements.d.ts.map +1 -0
  3. package/dist/components/clear-refinements.js +133 -0
  4. package/dist/components/current-refinements.d.ts +36 -0
  5. package/dist/components/current-refinements.d.ts.map +1 -0
  6. package/dist/components/current-refinements.js +186 -0
  7. package/dist/components/facets.d.ts +45 -0
  8. package/dist/components/facets.d.ts.map +1 -0
  9. package/dist/components/facets.js +259 -0
  10. package/dist/components/hits-per-page.d.ts +37 -0
  11. package/dist/components/hits-per-page.d.ts.map +1 -0
  12. package/dist/components/hits-per-page.js +132 -0
  13. package/dist/components/infinite-hits.d.ts +61 -0
  14. package/dist/components/infinite-hits.d.ts.map +1 -0
  15. package/dist/components/infinite-hits.js +316 -0
  16. package/dist/components/pagination.d.ts +33 -0
  17. package/dist/components/pagination.d.ts.map +1 -0
  18. package/dist/components/pagination.js +364 -0
  19. package/dist/components/query-suggestions.d.ts +39 -0
  20. package/dist/components/query-suggestions.d.ts.map +1 -0
  21. package/dist/components/query-suggestions.js +217 -0
  22. package/dist/components/range-input.d.ts +42 -0
  23. package/dist/components/range-input.d.ts.map +1 -0
  24. package/dist/components/range-input.js +274 -0
  25. package/dist/components/search-bar.d.ts +140 -0
  26. package/dist/components/search-bar.d.ts.map +1 -0
  27. package/dist/components/search-bar.js +899 -0
  28. package/dist/components/search-layout.d.ts +35 -0
  29. package/dist/components/search-layout.d.ts.map +1 -0
  30. package/dist/components/search-layout.js +144 -0
  31. package/dist/components/search-provider.d.ts +28 -0
  32. package/dist/components/search-provider.d.ts.map +1 -0
  33. package/dist/components/search-provider.js +44 -0
  34. package/dist/components/search-results.d.ts +55 -0
  35. package/dist/components/search-results.d.ts.map +1 -0
  36. package/dist/components/search-results.js +537 -0
  37. package/dist/components/sort-by.d.ts +33 -0
  38. package/dist/components/sort-by.d.ts.map +1 -0
  39. package/dist/components/sort-by.js +122 -0
  40. package/dist/components/stats.d.ts +36 -0
  41. package/dist/components/stats.d.ts.map +1 -0
  42. package/dist/components/stats.js +138 -0
  43. package/dist/index.d.ts +670 -0
  44. package/dist/index.d.ts.map +1 -0
  45. package/dist/index.esm.js +4008 -0
  46. package/dist/index.esm.js.map +1 -0
  47. package/dist/index.js +4055 -0
  48. package/dist/index.js.map +1 -0
  49. package/dist/index.umd.js +1 -0
  50. package/dist/themes/createTheme.d.ts +8 -0
  51. package/dist/themes/createTheme.d.ts.map +1 -0
  52. package/dist/themes/createTheme.js +10 -0
  53. package/dist/themes/dark.d.ts +6 -0
  54. package/dist/themes/dark.d.ts.map +1 -0
  55. package/dist/themes/dark.js +34 -0
  56. package/dist/themes/default.d.ts +6 -0
  57. package/dist/themes/default.d.ts.map +1 -0
  58. package/dist/themes/default.js +71 -0
  59. package/dist/themes/mergeThemes.d.ts +7 -0
  60. package/dist/themes/mergeThemes.d.ts.map +1 -0
  61. package/dist/themes/mergeThemes.js +6 -0
  62. package/dist/themes/minimal.d.ts +6 -0
  63. package/dist/themes/minimal.d.ts.map +1 -0
  64. package/dist/themes/minimal.js +34 -0
  65. package/dist/themes/types.d.ts +7 -0
  66. package/dist/themes/types.d.ts.map +1 -0
  67. package/dist/themes/types.js +6 -0
  68. package/dist/utils/search-manager.d.ts +33 -0
  69. package/dist/utils/search-manager.d.ts.map +1 -0
  70. package/dist/utils/search-manager.js +89 -0
  71. package/package.json +60 -0
@@ -0,0 +1,537 @@
1
+ /**
2
+ * SearchResults Component
3
+ *
4
+ * Displays search results with customizable rendering
5
+ */
6
+ import { extractField, formatPrice, log } from '@seekora-ai/ui-sdk-core';
7
+ export class SearchResults {
8
+ constructor(provider, options) {
9
+ this.unsubscribeStateManager = null;
10
+ this.provider = provider;
11
+ const container = typeof options.container === 'string'
12
+ ? document.querySelector(options.container)
13
+ : options.container;
14
+ if (!container) {
15
+ throw new Error('SearchResults: container element not found');
16
+ }
17
+ this.container = container;
18
+ this.options = {
19
+ viewMode: options.viewMode || 'list',
20
+ fieldMapping: options.fieldMapping || {},
21
+ itemsPerPage: options.itemsPerPage || 10,
22
+ results: options.results,
23
+ loading: options.loading,
24
+ error: options.error,
25
+ onResultClick: options.onResultClick,
26
+ renderResult: options.renderResult,
27
+ renderEmpty: options.renderEmpty,
28
+ renderLoading: options.renderLoading,
29
+ renderError: options.renderError,
30
+ };
31
+ // Attach event delegation listener to the persistent container for result clicks
32
+ // This ensures clicks work even when results are recreated during re-renders
33
+ // Note: We attach the listener even if onResultClick is not provided, to support analytics
34
+ this.container.addEventListener('click', async (e) => {
35
+ const target = e.target;
36
+ // Find the result element (could be the target itself or a parent)
37
+ const resultElement = target.hasAttribute('data-result-id')
38
+ ? target
39
+ : target.closest('[data-result-id]');
40
+ if (resultElement) {
41
+ e.preventDefault();
42
+ e.stopPropagation();
43
+ const resultId = resultElement.getAttribute('data-result-id');
44
+ const resultIndexStr = resultElement.getAttribute('data-result-index');
45
+ const resultIndex = resultIndexStr ? parseInt(resultIndexStr, 10) : -1;
46
+ console.log('🔵 SearchResults: Result clicked via delegation', {
47
+ resultId,
48
+ resultIndex,
49
+ target: target.tagName,
50
+ resultElement
51
+ });
52
+ if (resultId && resultIndex >= 0) {
53
+ // Find the result in the current results
54
+ const results = this.extractResults();
55
+ const result = results[resultIndex];
56
+ if (result) {
57
+ const extractedResult = this.extractFields(result);
58
+ // Track analytics event if enabled
59
+ if (this.provider.enableAnalytics) {
60
+ try {
61
+ const searchResponse = this.provider.stateManager.getResults();
62
+ const state = this.provider.stateManager.getState();
63
+ // Calculate absolute position (1-based) accounting for pagination
64
+ // Position = (currentPage - 1) * itemsPerPage + resultIndex + 1
65
+ const absolutePosition = (state.currentPage - 1) * state.itemsPerPage + resultIndex + 1;
66
+ // Build search context from current state and response
67
+ const searchContext = searchResponse?.context || (searchResponse ? {
68
+ query: state.query,
69
+ filters: state.refinements.length > 0
70
+ ? state.refinements.map(r => `${r.field}:${r.value}`).join(' && ')
71
+ : undefined,
72
+ page: state.currentPage,
73
+ sortBy: state.sortBy,
74
+ } : undefined);
75
+ console.log('🔵 SearchResults: Tracking analytics event', {
76
+ resultId,
77
+ resultIndex,
78
+ absolutePosition,
79
+ currentPage: state.currentPage,
80
+ itemsPerPage: state.itemsPerPage,
81
+ hasContext: !!searchContext,
82
+ searchContext
83
+ });
84
+ await this.provider.client.trackEvent({
85
+ event_name: 'product_click',
86
+ clicked_item_id: resultId,
87
+ metadata: {
88
+ result: extractedResult,
89
+ position: absolutePosition,
90
+ },
91
+ }, searchContext);
92
+ console.log('🟢 SearchResults: Analytics event tracked successfully', {
93
+ resultId,
94
+ position: absolutePosition,
95
+ });
96
+ }
97
+ catch (err) {
98
+ const error = err instanceof Error ? err : new Error(String(err));
99
+ log.error('SearchResults: Error tracking analytics event', {
100
+ resultId,
101
+ error: error.message,
102
+ });
103
+ }
104
+ }
105
+ // Call user-provided callback
106
+ if (this.options.onResultClick) {
107
+ try {
108
+ console.log('🔵 SearchResults: Calling onResultClick callback', {
109
+ resultId,
110
+ resultIndex,
111
+ extractedResult
112
+ });
113
+ this.options.onResultClick(extractedResult, resultIndex);
114
+ }
115
+ catch (err) {
116
+ const error = err instanceof Error ? err : new Error(String(err));
117
+ log.error('SearchResults: Error in onResultClick callback', {
118
+ resultId,
119
+ resultIndex,
120
+ error: error.message,
121
+ });
122
+ }
123
+ }
124
+ }
125
+ }
126
+ }
127
+ }, { passive: false });
128
+ console.log('🔵 SearchResults: Event delegation listener attached to container', { container: this.container });
129
+ // Subscribe to state manager for automatic updates
130
+ this.unsubscribeStateManager = this.provider.stateManager.subscribe((state) => {
131
+ this.options.results = state.results;
132
+ this.options.loading = state.loading;
133
+ this.options.error = state.error;
134
+ this.render();
135
+ });
136
+ this.render();
137
+ }
138
+ destroy() {
139
+ if (this.unsubscribeStateManager) {
140
+ this.unsubscribeStateManager();
141
+ this.unsubscribeStateManager = null;
142
+ }
143
+ }
144
+ update(options) {
145
+ // Update options
146
+ if (options.results !== undefined) {
147
+ this.options.results = options.results;
148
+ }
149
+ if (options.loading !== undefined) {
150
+ this.options.loading = options.loading;
151
+ }
152
+ if (options.error !== undefined) {
153
+ this.options.error = options.error;
154
+ }
155
+ // Always re-render when update is called (even if values appear the same, object references may differ)
156
+ const resultsCount = this.extractResults().length;
157
+ log.verbose('SearchResults: Updating and re-rendering', {
158
+ hasResults: !!this.options.results,
159
+ loading: this.options.loading,
160
+ hasError: !!this.options.error,
161
+ resultsCount,
162
+ providedResults: options.results !== undefined,
163
+ providedLoading: options.loading !== undefined,
164
+ providedError: options.error !== undefined,
165
+ });
166
+ this.render();
167
+ }
168
+ render() {
169
+ // Clear container first
170
+ this.container.innerHTML = '';
171
+ log.verbose('SearchResults: Rendering', {
172
+ hasResults: !!this.options.results,
173
+ loading: this.options.loading,
174
+ hasError: !!this.options.error,
175
+ });
176
+ if (this.options.loading) {
177
+ const loadingEl = this.options.renderLoading
178
+ ? this.options.renderLoading()
179
+ : this.renderDefaultLoading();
180
+ this.container.appendChild(loadingEl);
181
+ return;
182
+ }
183
+ if (this.options.error) {
184
+ const errorEl = this.options.renderError
185
+ ? this.options.renderError(this.options.error)
186
+ : this.renderDefaultError(this.options.error);
187
+ this.container.appendChild(errorEl);
188
+ return;
189
+ }
190
+ const displayedResults = this.extractResults();
191
+ if (displayedResults.length === 0) {
192
+ const emptyEl = this.options.renderEmpty
193
+ ? this.options.renderEmpty()
194
+ : this.renderDefaultEmpty();
195
+ this.container.appendChild(emptyEl);
196
+ return;
197
+ }
198
+ // Create a wrapper for grid/card layout
199
+ if (this.options.viewMode === 'card' || this.options.viewMode === 'grid') {
200
+ const gridWrapper = document.createElement('div');
201
+ gridWrapper.style.cssText = this.getGridContainerStyle();
202
+ displayedResults.forEach((result, index) => {
203
+ const resultEl = this.options.renderResult
204
+ ? this.options.renderResult(this.extractFields(result), index)
205
+ : this.renderDefaultResult(this.extractFields(result), index);
206
+ gridWrapper.appendChild(resultEl);
207
+ });
208
+ this.container.appendChild(gridWrapper);
209
+ }
210
+ else {
211
+ // List view - no wrapper needed
212
+ displayedResults.forEach((result, index) => {
213
+ const resultEl = this.options.renderResult
214
+ ? this.options.renderResult(this.extractFields(result), index)
215
+ : this.renderDefaultResult(this.extractFields(result), index);
216
+ this.container.appendChild(resultEl);
217
+ });
218
+ }
219
+ }
220
+ extractResults() {
221
+ if (!this.options.results) {
222
+ log.verbose('SearchResults: No results to extract');
223
+ return [];
224
+ }
225
+ let extracted = [];
226
+ if (Array.isArray(this.options.results)) {
227
+ extracted = this.options.results;
228
+ }
229
+ else {
230
+ const res = this.options.results;
231
+ if (res.results && Array.isArray(res.results)) {
232
+ extracted = res.results;
233
+ }
234
+ else if (res.data) {
235
+ const data = res.data;
236
+ if (Array.isArray(data)) {
237
+ extracted = data;
238
+ }
239
+ else if (data.results && Array.isArray(data.results)) {
240
+ extracted = data.results;
241
+ }
242
+ }
243
+ }
244
+ log.verbose('SearchResults: Extracted results', {
245
+ count: extracted.length,
246
+ viewMode: this.options.viewMode,
247
+ });
248
+ return extracted;
249
+ }
250
+ extractFields(item) {
251
+ try {
252
+ const mapping = this.options.fieldMapping;
253
+ return {
254
+ id: extractField(item, mapping.id) || String(item.id || ''),
255
+ title: extractField(item, mapping.title) || extractField(item, mapping.primaryText) || 'Untitled',
256
+ description: extractField(item, mapping.description) || extractField(item, mapping.secondaryText),
257
+ image: extractField(item, mapping.image) || extractField(item, mapping.imageUrl),
258
+ price: mapping.price ? formatPrice(extractField(item, mapping.price)) : undefined,
259
+ url: extractField(item, mapping.url),
260
+ metadata: item,
261
+ };
262
+ }
263
+ catch (err) {
264
+ const error = err instanceof Error ? err : new Error(String(err));
265
+ log.warn('SearchResults: Error extracting fields from result', {
266
+ error: error.message,
267
+ itemId: item?.id || 'unknown',
268
+ });
269
+ // Return fallback result
270
+ return {
271
+ id: String(item?.id || ''),
272
+ title: 'Error loading result',
273
+ metadata: item,
274
+ };
275
+ }
276
+ }
277
+ renderDefaultLoading() {
278
+ const div = document.createElement('div');
279
+ div.style.cssText = this.getLoadingStyle();
280
+ div.textContent = 'Loading results...';
281
+ return div;
282
+ }
283
+ renderDefaultError(error) {
284
+ const div = document.createElement('div');
285
+ div.style.cssText = this.getErrorStyle();
286
+ div.textContent = `Error: ${error.message}`;
287
+ return div;
288
+ }
289
+ renderDefaultEmpty() {
290
+ const div = document.createElement('div');
291
+ div.style.cssText = this.getEmptyStyle();
292
+ div.textContent = 'No results found';
293
+ return div;
294
+ }
295
+ renderDefaultResult(result, index) {
296
+ const div = document.createElement('div');
297
+ div.style.cssText = this.getResultStyle(index);
298
+ // Always set data attributes for event delegation (even if no onResultClick)
299
+ div.setAttribute('data-result-id', result.id);
300
+ div.setAttribute('data-result-index', String(index));
301
+ if (this.options.onResultClick) {
302
+ div.style.cursor = 'pointer';
303
+ div.setAttribute('role', 'button');
304
+ div.setAttribute('tabindex', '0');
305
+ console.log('SearchResults: Result element created', {
306
+ resultId: result.id,
307
+ index,
308
+ element: div,
309
+ hasOnResultClick: !!this.options.onResultClick
310
+ });
311
+ // No need to attach individual listeners - we use event delegation on the container
312
+ // This ensures clicks work even when results are recreated during re-renders
313
+ // Still handle keyboard accessibility
314
+ div.addEventListener('keydown', (e) => {
315
+ if (e.key === 'Enter' || e.key === ' ') {
316
+ e.preventDefault();
317
+ e.stopPropagation();
318
+ // Trigger the click event which will be handled by delegation
319
+ const clickEvent = new MouseEvent('click', {
320
+ bubbles: true,
321
+ cancelable: true,
322
+ view: window
323
+ });
324
+ div.dispatchEvent(clickEvent);
325
+ }
326
+ });
327
+ }
328
+ if (this.options.viewMode === 'card' || this.options.viewMode === 'grid') {
329
+ this.renderCardResult(div, result);
330
+ }
331
+ else {
332
+ this.renderListResult(div, result);
333
+ }
334
+ return div;
335
+ }
336
+ renderCardResult(container, result) {
337
+ if (result.image) {
338
+ const imgContainer = document.createElement('div');
339
+ imgContainer.style.cssText = this.getImageContainerStyle();
340
+ const img = document.createElement('img');
341
+ img.src = result.image;
342
+ img.alt = result.title;
343
+ img.style.cssText = this.getImageStyle();
344
+ imgContainer.appendChild(img);
345
+ container.appendChild(imgContainer);
346
+ }
347
+ const content = document.createElement('div');
348
+ content.style.cssText = this.getCardContentStyle();
349
+ if (result.title) {
350
+ const h3 = document.createElement('h3');
351
+ h3.style.cssText = this.getTitleStyle();
352
+ h3.textContent = result.title;
353
+ content.appendChild(h3);
354
+ }
355
+ if (result.description) {
356
+ const p = document.createElement('p');
357
+ p.style.cssText = this.getDescriptionStyle();
358
+ p.textContent = result.description;
359
+ content.appendChild(p);
360
+ }
361
+ if (result.price) {
362
+ const priceDiv = document.createElement('div');
363
+ priceDiv.style.cssText = this.getPriceStyle();
364
+ priceDiv.textContent = result.price;
365
+ content.appendChild(priceDiv);
366
+ }
367
+ container.appendChild(content);
368
+ }
369
+ renderListResult(container, result) {
370
+ if (result.image) {
371
+ const img = document.createElement('img');
372
+ img.src = result.image;
373
+ img.alt = result.title;
374
+ img.style.cssText = this.getListImageStyle();
375
+ container.appendChild(img);
376
+ }
377
+ const content = document.createElement('div');
378
+ content.style.cssText = 'flex: 1; min-width: 0;';
379
+ if (result.title) {
380
+ const h3 = document.createElement('h3');
381
+ h3.style.cssText = this.getTitleStyle();
382
+ h3.textContent = result.title;
383
+ content.appendChild(h3);
384
+ }
385
+ if (result.description) {
386
+ const p = document.createElement('p');
387
+ p.style.cssText = this.getDescriptionStyle();
388
+ p.textContent = result.description;
389
+ content.appendChild(p);
390
+ }
391
+ if (result.price) {
392
+ const priceDiv = document.createElement('div');
393
+ priceDiv.style.cssText = this.getPriceStyle();
394
+ priceDiv.textContent = result.price;
395
+ content.appendChild(priceDiv);
396
+ }
397
+ container.appendChild(content);
398
+ }
399
+ get theme() {
400
+ return this.provider.theme;
401
+ }
402
+ getLoadingStyle() {
403
+ return `
404
+ padding: ${this.theme.spacing.large};
405
+ text-align: center;
406
+ color: ${this.theme.colors.text};
407
+ `;
408
+ }
409
+ getErrorStyle() {
410
+ return `
411
+ padding: ${this.theme.spacing.large};
412
+ text-align: center;
413
+ color: ${this.theme.colors.error};
414
+ `;
415
+ }
416
+ getEmptyStyle() {
417
+ return `
418
+ padding: ${this.theme.spacing.large};
419
+ text-align: center;
420
+ color: ${this.theme.colors.textSecondary || this.theme.colors.text};
421
+ `;
422
+ }
423
+ getGridContainerStyle() {
424
+ return `
425
+ display: grid;
426
+ grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
427
+ gap: ${this.theme.spacing.medium};
428
+ width: 100%;
429
+ `;
430
+ }
431
+ getResultStyle(index) {
432
+ const borderRadius = typeof this.theme.borderRadius === 'string'
433
+ ? this.theme.borderRadius
434
+ : this.theme.borderRadius.medium;
435
+ const transition = this.theme.transitions?.normal || '250ms ease-in-out';
436
+ if (this.options.viewMode === 'list') {
437
+ return `
438
+ padding: ${this.theme.spacing.medium};
439
+ border-bottom: 1px solid ${this.theme.colors.border};
440
+ border-radius: 0;
441
+ margin-bottom: 0;
442
+ transition: ${transition};
443
+ background-color: ${this.theme.colors.background};
444
+ display: flex;
445
+ align-items: flex-start;
446
+ gap: ${this.theme.spacing.medium};
447
+ `;
448
+ }
449
+ else {
450
+ // Card/Grid view - cards should be contained within grid
451
+ return `
452
+ padding: 0;
453
+ border: 1px solid ${this.theme.colors.border};
454
+ border-radius: ${borderRadius};
455
+ box-shadow: ${this.theme.shadows.small};
456
+ transition: ${transition};
457
+ background-color: ${this.theme.colors.background};
458
+ display: flex;
459
+ flex-direction: column;
460
+ overflow: hidden;
461
+ height: 100%;
462
+ `;
463
+ }
464
+ }
465
+ getImageContainerStyle() {
466
+ return `
467
+ width: 100%;
468
+ aspect-ratio: ${this.options.viewMode === 'grid' ? '1/1' : '16/9'};
469
+ overflow: hidden;
470
+ background-color: ${this.theme.colors.hover};
471
+ flex-shrink: 0;
472
+ `;
473
+ }
474
+ getImageStyle() {
475
+ return `
476
+ width: 100%;
477
+ height: 100%;
478
+ object-fit: cover;
479
+ `;
480
+ }
481
+ getCardContentStyle() {
482
+ return `
483
+ padding: ${this.theme.spacing.medium};
484
+ display: flex;
485
+ flex-direction: column;
486
+ flex: 1;
487
+ min-height: 0;
488
+ `;
489
+ }
490
+ getListContentStyle() {
491
+ return `
492
+ display: flex;
493
+ align-items: flex-start;
494
+ gap: ${this.theme.spacing.medium};
495
+ flex: 1;
496
+ min-width: 0;
497
+ `;
498
+ }
499
+ getListImageStyle() {
500
+ const borderRadius = typeof this.theme.borderRadius === 'string'
501
+ ? this.theme.borderRadius
502
+ : this.theme.borderRadius.medium;
503
+ return `
504
+ width: 100px;
505
+ height: 100px;
506
+ object-fit: cover;
507
+ border-radius: ${borderRadius};
508
+ flex-shrink: 0;
509
+ `;
510
+ }
511
+ getTitleStyle() {
512
+ return `
513
+ font-size: ${this.theme.typography.fontSize.large};
514
+ font-weight: bold;
515
+ margin: 0;
516
+ margin-bottom: ${this.theme.spacing.small};
517
+ color: ${this.theme.colors.text};
518
+ `;
519
+ }
520
+ getDescriptionStyle() {
521
+ return `
522
+ font-size: ${this.theme.typography.fontSize.medium};
523
+ color: ${this.theme.colors.text};
524
+ margin: 0;
525
+ margin-bottom: ${this.theme.spacing.small};
526
+ opacity: 0.8;
527
+ `;
528
+ }
529
+ getPriceStyle() {
530
+ return `
531
+ font-size: ${this.theme.typography.fontSize.medium};
532
+ font-weight: bold;
533
+ color: ${this.theme.colors.primary};
534
+ margin-top: auto;
535
+ `;
536
+ }
537
+ }
@@ -0,0 +1,33 @@
1
+ /**
2
+ * SortBy Component
3
+ *
4
+ * Displays sort options for search results
5
+ */
6
+ import { SearchProvider } from './search-provider';
7
+ export interface SortOption {
8
+ value: string;
9
+ label: string;
10
+ }
11
+ export interface SortByOptions {
12
+ container: HTMLElement | string;
13
+ options: SortOption[];
14
+ value?: string;
15
+ label?: string;
16
+ onSortChange?: (value: string) => void;
17
+ }
18
+ export declare class SortBy {
19
+ private container;
20
+ private provider;
21
+ private options;
22
+ private currentValue;
23
+ private unsubscribeStateManager;
24
+ constructor(provider: SearchProvider, options: SortByOptions);
25
+ destroy(): void;
26
+ update(options: Partial<Pick<SortByOptions, 'value'>>): void;
27
+ private render;
28
+ private get theme();
29
+ private getContainerStyle;
30
+ private getLabelStyle;
31
+ private getSelectStyle;
32
+ }
33
+ //# sourceMappingURL=sort-by.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sort-by.d.ts","sourceRoot":"","sources":["../../src/components/sort-by.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAGH,OAAO,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AAEnD,MAAM,WAAW,UAAU;IACzB,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,aAAa;IAC5B,SAAS,EAAE,WAAW,GAAG,MAAM,CAAC;IAChC,OAAO,EAAE,UAAU,EAAE,CAAC;IACtB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,YAAY,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;CACxC;AAED,qBAAa,MAAM;IACjB,OAAO,CAAC,SAAS,CAAc;IAC/B,OAAO,CAAC,QAAQ,CAAiB;IACjC,OAAO,CAAC,OAAO,CAIb;IACF,OAAO,CAAC,YAAY,CAAS;IAC7B,OAAO,CAAC,uBAAuB,CAA6B;gBAEhD,QAAQ,EAAE,cAAc,EAAE,OAAO,EAAE,aAAa;IAgC5D,OAAO,IAAI,IAAI;IAOf,MAAM,CAAC,OAAO,EAAE,OAAO,CAAC,IAAI,CAAC,aAAa,EAAE,OAAO,CAAC,CAAC,GAAG,IAAI;IAO5D,OAAO,CAAC,MAAM;IAkDd,OAAO,KAAK,KAAK,GAEhB;IAED,OAAO,CAAC,iBAAiB;IAQzB,OAAO,CAAC,aAAa;IAQrB,OAAO,CAAC,cAAc;CAevB"}
@@ -0,0 +1,122 @@
1
+ /**
2
+ * SortBy Component
3
+ *
4
+ * Displays sort options for search results
5
+ */
6
+ import { log } from '@seekora-ai/ui-sdk-core';
7
+ export class SortBy {
8
+ constructor(provider, options) {
9
+ this.unsubscribeStateManager = null;
10
+ this.provider = provider;
11
+ const container = typeof options.container === 'string'
12
+ ? document.querySelector(options.container)
13
+ : options.container;
14
+ if (!container) {
15
+ const error = new Error('SortBy: container element not found');
16
+ log.error('SortBy: Initialization failed', { error: error.message });
17
+ throw error;
18
+ }
19
+ this.container = container;
20
+ this.currentValue = options.value || options.options[0]?.value || '';
21
+ this.options = {
22
+ options: options.options,
23
+ label: options.label,
24
+ onSortChange: options.onSortChange,
25
+ };
26
+ // Subscribe to state manager to sync sort value
27
+ this.unsubscribeStateManager = this.provider.stateManager.subscribe((state) => {
28
+ if (state.sortBy && state.sortBy !== this.currentValue) {
29
+ this.currentValue = state.sortBy;
30
+ this.render();
31
+ }
32
+ });
33
+ this.render();
34
+ }
35
+ destroy() {
36
+ if (this.unsubscribeStateManager) {
37
+ this.unsubscribeStateManager();
38
+ this.unsubscribeStateManager = null;
39
+ }
40
+ }
41
+ update(options) {
42
+ if (options.value !== undefined) {
43
+ this.currentValue = options.value;
44
+ this.render();
45
+ }
46
+ }
47
+ render() {
48
+ this.container.innerHTML = '';
49
+ const wrapper = document.createElement('div');
50
+ wrapper.style.cssText = this.getContainerStyle();
51
+ if (this.options.label) {
52
+ const label = document.createElement('label');
53
+ label.textContent = this.options.label;
54
+ label.style.cssText = this.getLabelStyle();
55
+ wrapper.appendChild(label);
56
+ }
57
+ const select = document.createElement('select');
58
+ select.value = this.currentValue;
59
+ select.style.cssText = this.getSelectStyle();
60
+ select.setAttribute('aria-label', 'Sort results');
61
+ select.addEventListener('change', (e) => {
62
+ const target = e.target;
63
+ this.currentValue = target.value;
64
+ log.verbose('SortBy: Sort option changed', { value: target.value });
65
+ // Update state manager (automatically triggers search)
66
+ this.provider.stateManager.setSortBy(target.value);
67
+ // Call callback for backwards compatibility
68
+ if (this.options.onSortChange) {
69
+ try {
70
+ this.options.onSortChange(target.value);
71
+ }
72
+ catch (err) {
73
+ const error = err instanceof Error ? err : new Error(String(err));
74
+ log.error('SortBy: Error in onSortChange callback', {
75
+ value: target.value,
76
+ error: error.message,
77
+ });
78
+ }
79
+ }
80
+ });
81
+ this.options.options.forEach((option) => {
82
+ const optionEl = document.createElement('option');
83
+ optionEl.value = option.value;
84
+ optionEl.textContent = option.label;
85
+ select.appendChild(optionEl);
86
+ });
87
+ wrapper.appendChild(select);
88
+ this.container.appendChild(wrapper);
89
+ }
90
+ get theme() {
91
+ return this.provider.theme;
92
+ }
93
+ getContainerStyle() {
94
+ return `
95
+ display: flex;
96
+ align-items: center;
97
+ gap: ${this.theme.spacing.small};
98
+ `;
99
+ }
100
+ getLabelStyle() {
101
+ return `
102
+ font-size: ${this.theme.typography.fontSize.medium};
103
+ color: ${this.theme.colors.text};
104
+ font-weight: ${this.theme.typography.fontWeight?.medium || 500};
105
+ `;
106
+ }
107
+ getSelectStyle() {
108
+ const borderRadius = typeof this.theme.borderRadius === 'string'
109
+ ? this.theme.borderRadius
110
+ : this.theme.borderRadius.medium;
111
+ return `
112
+ padding: ${this.theme.spacing.small} ${this.theme.spacing.medium};
113
+ font-size: ${this.theme.typography.fontSize.medium};
114
+ border: 1px solid ${this.theme.colors.border};
115
+ border-radius: ${borderRadius};
116
+ background-color: ${this.theme.colors.background};
117
+ color: ${this.theme.colors.text};
118
+ cursor: pointer;
119
+ outline: none;
120
+ `;
121
+ }
122
+ }