@seekora-ai/ui-sdk-core 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.
package/dist/index.js ADDED
@@ -0,0 +1,1994 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * Field Mapping Utilities
5
+ *
6
+ * Shared utilities for extracting and mapping fields from API responses
7
+ */
8
+ /**
9
+ * Get nested value from object using dot notation path
10
+ */
11
+ const getNestedValue$1 = (obj, path) => {
12
+ if (!path)
13
+ return undefined;
14
+ return path.split('.').reduce((current, key) => current?.[key], obj);
15
+ };
16
+ /**
17
+ * Format price value with currency
18
+ */
19
+ const formatPrice = (value, currency = '₹') => {
20
+ if (value === undefined || value === null)
21
+ return undefined;
22
+ if (typeof value === 'number')
23
+ return `${currency}${value}`;
24
+ if (typeof value === 'string') {
25
+ const num = parseFloat(value);
26
+ if (!isNaN(num))
27
+ return `${currency}${num}`;
28
+ return value;
29
+ }
30
+ return String(value);
31
+ };
32
+ /**
33
+ * Extract field value using dot notation path
34
+ */
35
+ const extractField = (item, path) => {
36
+ if (!path)
37
+ return undefined;
38
+ return getNestedValue$1(item, path);
39
+ };
40
+
41
+ /**
42
+ * Theme Management Utilities
43
+ *
44
+ * Shared theme utilities for creating and merging themes
45
+ */
46
+ /**
47
+ * Create a complete theme from a partial theme configuration
48
+ */
49
+ const createTheme = (config, baseTheme) => {
50
+ return {
51
+ colors: {
52
+ ...baseTheme.colors,
53
+ ...config.colors,
54
+ },
55
+ typography: {
56
+ ...baseTheme.typography,
57
+ ...config.typography,
58
+ fontSize: {
59
+ ...baseTheme.typography.fontSize,
60
+ ...config.typography?.fontSize,
61
+ },
62
+ },
63
+ spacing: {
64
+ ...baseTheme.spacing,
65
+ ...config.spacing,
66
+ },
67
+ borderRadius: (config.borderRadius ?? baseTheme.borderRadius),
68
+ shadows: {
69
+ ...baseTheme.shadows,
70
+ ...config.shadows,
71
+ },
72
+ transitions: {
73
+ ...baseTheme.transitions,
74
+ ...config.transitions,
75
+ },
76
+ breakpoints: {
77
+ ...baseTheme.breakpoints,
78
+ ...config.breakpoints,
79
+ },
80
+ zIndex: {
81
+ ...baseTheme.zIndex,
82
+ ...config.zIndex,
83
+ },
84
+ };
85
+ };
86
+ /**
87
+ * Merge multiple themes together
88
+ */
89
+ const mergeThemes = (baseTheme, ...themes) => {
90
+ return themes.reduce((acc, theme) => {
91
+ return {
92
+ colors: { ...acc.colors, ...theme.colors },
93
+ typography: {
94
+ ...acc.typography,
95
+ ...theme.typography,
96
+ fontSize: {
97
+ ...acc.typography?.fontSize,
98
+ ...theme.typography?.fontSize,
99
+ },
100
+ },
101
+ spacing: { ...acc.spacing, ...theme.spacing },
102
+ borderRadius: (theme.borderRadius ?? acc.borderRadius),
103
+ shadows: { ...acc.shadows, ...theme.shadows },
104
+ transitions: { ...acc.transitions, ...theme.transitions },
105
+ breakpoints: { ...acc.breakpoints, ...theme.breakpoints },
106
+ zIndex: { ...acc.zIndex, ...theme.zIndex },
107
+ };
108
+ }, baseTheme);
109
+ };
110
+
111
+ /**
112
+ * CSS Variables Theme System
113
+ *
114
+ * Generates CSS variables from theme configuration
115
+ * Provides utilities for injecting and managing CSS themes
116
+ */
117
+ /**
118
+ * Convert a theme object to CSS variables
119
+ */
120
+ function themeToCSSVariables(theme, config = {}) {
121
+ const { scope = ':root', prefix = '--seekora' } = config;
122
+ const variables = [];
123
+ // Colors
124
+ Object.entries(theme.colors).forEach(([key, value]) => {
125
+ variables.push(`${prefix}-color-${key}: ${value};`);
126
+ });
127
+ // Typography
128
+ variables.push(`${prefix}-font-family: ${theme.typography.fontFamily};`);
129
+ Object.entries(theme.typography.fontSize).forEach(([key, value]) => {
130
+ variables.push(`${prefix}-font-size-${key}: ${value};`);
131
+ });
132
+ if (theme.typography.fontWeight) {
133
+ Object.entries(theme.typography.fontWeight).forEach(([key, value]) => {
134
+ variables.push(`${prefix}-font-weight-${key}: ${value};`);
135
+ });
136
+ }
137
+ if (theme.typography.lineHeight) {
138
+ Object.entries(theme.typography.lineHeight).forEach(([key, value]) => {
139
+ variables.push(`${prefix}-line-height-${key}: ${value};`);
140
+ });
141
+ }
142
+ // Spacing
143
+ Object.entries(theme.spacing).forEach(([key, value]) => {
144
+ variables.push(`${prefix}-spacing-${key}: ${value};`);
145
+ });
146
+ // Border Radius
147
+ if (typeof theme.borderRadius === 'string') {
148
+ variables.push(`${prefix}-border-radius: ${theme.borderRadius};`);
149
+ }
150
+ else {
151
+ Object.entries(theme.borderRadius).forEach(([key, value]) => {
152
+ variables.push(`${prefix}-border-radius-${key}: ${value};`);
153
+ });
154
+ }
155
+ // Shadows
156
+ if (theme.shadows) {
157
+ Object.entries(theme.shadows).forEach(([key, value]) => {
158
+ variables.push(`${prefix}-shadow-${key}: ${value};`);
159
+ });
160
+ }
161
+ // Transitions
162
+ if (theme.transitions) {
163
+ Object.entries(theme.transitions).forEach(([key, value]) => {
164
+ variables.push(`${prefix}-transition-${key}: ${value};`);
165
+ });
166
+ }
167
+ // Z-Index
168
+ if (theme.zIndex) {
169
+ Object.entries(theme.zIndex).forEach(([key, value]) => {
170
+ variables.push(`${prefix}-z-index-${key}: ${value};`);
171
+ });
172
+ }
173
+ return `${scope} {\n ${variables.join('\n ')}\n}`;
174
+ }
175
+ /**
176
+ * Generate complete CSS stylesheet with variables and optional component classes
177
+ */
178
+ function generateThemeStylesheet(theme, config = {}) {
179
+ const { prefix = '--seekora', includeClasses = true } = config;
180
+ const sections = [];
181
+ // CSS Variables
182
+ sections.push(themeToCSSVariables(theme, config));
183
+ // Component classes (BEM naming convention)
184
+ if (includeClasses) {
185
+ sections.push(generateComponentClasses(prefix));
186
+ }
187
+ return sections.join('\n\n');
188
+ }
189
+ /**
190
+ * Generate BEM-style component classes
191
+ */
192
+ function generateComponentClasses(prefix) {
193
+ return `
194
+ /* Seekora UI SDK Component Classes */
195
+
196
+ /* Search Bar */
197
+ .seekora-search-bar {
198
+ position: relative;
199
+ display: flex;
200
+ align-items: center;
201
+ background-color: var(${prefix}-color-background);
202
+ border: 1px solid var(${prefix}-color-border);
203
+ border-radius: var(${prefix}-border-radius-medium, var(${prefix}-border-radius));
204
+ transition: var(${prefix}-transition-fast);
205
+ }
206
+
207
+ .seekora-search-bar--focused {
208
+ border-color: var(${prefix}-color-primary);
209
+ box-shadow: 0 0 0 2px var(${prefix}-color-focus);
210
+ }
211
+
212
+ .seekora-search-bar__input {
213
+ flex: 1;
214
+ padding: var(${prefix}-spacing-medium);
215
+ font-size: var(${prefix}-font-size-medium);
216
+ font-family: var(${prefix}-font-family);
217
+ color: var(${prefix}-color-text);
218
+ background: transparent;
219
+ border: none;
220
+ outline: none;
221
+ }
222
+
223
+ .seekora-search-bar__input::placeholder {
224
+ color: var(${prefix}-color-textSecondary);
225
+ }
226
+
227
+ .seekora-search-bar__button {
228
+ padding: var(${prefix}-spacing-small) var(${prefix}-spacing-medium);
229
+ font-size: var(${prefix}-font-size-medium);
230
+ color: #ffffff;
231
+ background-color: var(${prefix}-color-primary);
232
+ border: none;
233
+ border-radius: var(${prefix}-border-radius-medium, var(${prefix}-border-radius));
234
+ cursor: pointer;
235
+ transition: var(${prefix}-transition-fast);
236
+ }
237
+
238
+ .seekora-search-bar__button:hover {
239
+ background-color: var(${prefix}-color-hover);
240
+ }
241
+
242
+ /* Search Results */
243
+ .seekora-results {
244
+ background-color: var(${prefix}-color-background);
245
+ }
246
+
247
+ .seekora-results__list {
248
+ list-style: none;
249
+ margin: 0;
250
+ padding: 0;
251
+ }
252
+
253
+ .seekora-results__item {
254
+ display: flex;
255
+ gap: var(${prefix}-spacing-medium);
256
+ padding: var(${prefix}-spacing-medium);
257
+ border-bottom: 1px solid var(${prefix}-color-border);
258
+ cursor: pointer;
259
+ transition: var(${prefix}-transition-fast);
260
+ }
261
+
262
+ .seekora-results__item:hover {
263
+ background-color: var(${prefix}-color-hover);
264
+ }
265
+
266
+ .seekora-results__item--active {
267
+ background-color: var(${prefix}-color-hover);
268
+ }
269
+
270
+ .seekora-results__image {
271
+ width: 80px;
272
+ height: 80px;
273
+ object-fit: cover;
274
+ border-radius: var(${prefix}-border-radius-small, var(${prefix}-border-radius));
275
+ }
276
+
277
+ .seekora-results__content {
278
+ flex: 1;
279
+ }
280
+
281
+ .seekora-results__title {
282
+ margin: 0 0 var(${prefix}-spacing-small) 0;
283
+ font-size: var(${prefix}-font-size-medium);
284
+ font-weight: var(${prefix}-font-weight-semibold, 600);
285
+ color: var(${prefix}-color-text);
286
+ }
287
+
288
+ .seekora-results__description {
289
+ margin: 0;
290
+ font-size: var(${prefix}-font-size-small);
291
+ color: var(${prefix}-color-textSecondary);
292
+ line-height: var(${prefix}-line-height-normal, 1.5);
293
+ }
294
+
295
+ .seekora-results__price {
296
+ margin-top: var(${prefix}-spacing-small);
297
+ font-size: var(${prefix}-font-size-medium);
298
+ font-weight: var(${prefix}-font-weight-bold, 700);
299
+ color: var(${prefix}-color-primary);
300
+ }
301
+
302
+ .seekora-results__empty {
303
+ padding: var(${prefix}-spacing-large);
304
+ text-align: center;
305
+ color: var(${prefix}-color-textSecondary);
306
+ }
307
+
308
+ .seekora-results__loading {
309
+ padding: var(${prefix}-spacing-large);
310
+ text-align: center;
311
+ color: var(${prefix}-color-textSecondary);
312
+ }
313
+
314
+ /* Pagination */
315
+ .seekora-pagination {
316
+ display: flex;
317
+ justify-content: center;
318
+ gap: var(${prefix}-spacing-small);
319
+ padding: var(${prefix}-spacing-medium) 0;
320
+ }
321
+
322
+ .seekora-pagination__item {
323
+ min-width: 36px;
324
+ height: 36px;
325
+ display: flex;
326
+ align-items: center;
327
+ justify-content: center;
328
+ padding: 0 var(${prefix}-spacing-small);
329
+ font-size: var(${prefix}-font-size-medium);
330
+ color: var(${prefix}-color-text);
331
+ background-color: var(${prefix}-color-background);
332
+ border: 1px solid var(${prefix}-color-border);
333
+ border-radius: var(${prefix}-border-radius-small, var(${prefix}-border-radius));
334
+ cursor: pointer;
335
+ transition: var(${prefix}-transition-fast);
336
+ }
337
+
338
+ .seekora-pagination__item:hover:not(.seekora-pagination__item--disabled) {
339
+ background-color: var(${prefix}-color-hover);
340
+ border-color: var(${prefix}-color-primary);
341
+ }
342
+
343
+ .seekora-pagination__item--active {
344
+ background-color: var(${prefix}-color-primary);
345
+ border-color: var(${prefix}-color-primary);
346
+ color: #ffffff;
347
+ }
348
+
349
+ .seekora-pagination__item--disabled {
350
+ opacity: 0.5;
351
+ cursor: not-allowed;
352
+ }
353
+
354
+ /* Facets */
355
+ .seekora-facets {
356
+ font-family: var(${prefix}-font-family);
357
+ }
358
+
359
+ .seekora-facets__facet {
360
+ margin-bottom: var(${prefix}-spacing-large);
361
+ }
362
+
363
+ .seekora-facets__title {
364
+ margin: 0 0 var(${prefix}-spacing-small) 0;
365
+ font-size: var(${prefix}-font-size-medium);
366
+ font-weight: var(${prefix}-font-weight-semibold, 600);
367
+ color: var(${prefix}-color-text);
368
+ }
369
+
370
+ .seekora-facets__list {
371
+ list-style: none;
372
+ margin: 0;
373
+ padding: 0;
374
+ }
375
+
376
+ .seekora-facets__item {
377
+ display: flex;
378
+ align-items: center;
379
+ gap: var(${prefix}-spacing-small);
380
+ padding: var(${prefix}-spacing-small) 0;
381
+ cursor: pointer;
382
+ }
383
+
384
+ .seekora-facets__checkbox {
385
+ width: 18px;
386
+ height: 18px;
387
+ accent-color: var(${prefix}-color-primary);
388
+ }
389
+
390
+ .seekora-facets__label {
391
+ flex: 1;
392
+ font-size: var(${prefix}-font-size-small);
393
+ color: var(${prefix}-color-text);
394
+ }
395
+
396
+ .seekora-facets__count {
397
+ font-size: var(${prefix}-font-size-small);
398
+ color: var(${prefix}-color-textSecondary);
399
+ }
400
+
401
+ .seekora-facets__show-more {
402
+ margin-top: var(${prefix}-spacing-small);
403
+ font-size: var(${prefix}-font-size-small);
404
+ color: var(${prefix}-color-primary);
405
+ background: none;
406
+ border: none;
407
+ cursor: pointer;
408
+ text-decoration: underline;
409
+ }
410
+
411
+ /* Stats */
412
+ .seekora-stats {
413
+ font-size: var(${prefix}-font-size-small);
414
+ color: var(${prefix}-color-textSecondary);
415
+ }
416
+
417
+ .seekora-stats__count {
418
+ font-weight: var(${prefix}-font-weight-semibold, 600);
419
+ color: var(${prefix}-color-text);
420
+ }
421
+
422
+ /* Sort By */
423
+ .seekora-sort-by {
424
+ display: flex;
425
+ align-items: center;
426
+ gap: var(${prefix}-spacing-small);
427
+ }
428
+
429
+ .seekora-sort-by__label {
430
+ font-size: var(${prefix}-font-size-medium);
431
+ color: var(${prefix}-color-text);
432
+ }
433
+
434
+ .seekora-sort-by__select {
435
+ padding: var(${prefix}-spacing-small) var(${prefix}-spacing-medium);
436
+ font-size: var(${prefix}-font-size-medium);
437
+ color: var(${prefix}-color-text);
438
+ background-color: var(${prefix}-color-background);
439
+ border: 1px solid var(${prefix}-color-border);
440
+ border-radius: var(${prefix}-border-radius-medium, var(${prefix}-border-radius));
441
+ cursor: pointer;
442
+ outline: none;
443
+ }
444
+
445
+ /* Current Refinements */
446
+ .seekora-refinements {
447
+ display: flex;
448
+ flex-wrap: wrap;
449
+ gap: var(${prefix}-spacing-small);
450
+ }
451
+
452
+ .seekora-refinements__item {
453
+ display: inline-flex;
454
+ align-items: center;
455
+ gap: var(${prefix}-spacing-small);
456
+ padding: var(${prefix}-spacing-small) var(${prefix}-spacing-medium);
457
+ font-size: var(${prefix}-font-size-small);
458
+ background-color: var(${prefix}-color-hover);
459
+ border: 1px solid var(${prefix}-color-border);
460
+ border-radius: var(${prefix}-border-radius-full, 9999px);
461
+ }
462
+
463
+ .seekora-refinements__label {
464
+ font-weight: var(${prefix}-font-weight-medium, 500);
465
+ color: var(${prefix}-color-text);
466
+ }
467
+
468
+ .seekora-refinements__value {
469
+ color: var(${prefix}-color-text);
470
+ }
471
+
472
+ .seekora-refinements__clear {
473
+ width: 16px;
474
+ height: 16px;
475
+ display: flex;
476
+ align-items: center;
477
+ justify-content: center;
478
+ padding: 0;
479
+ font-size: 14px;
480
+ color: var(${prefix}-color-textSecondary);
481
+ background: none;
482
+ border: none;
483
+ border-radius: 50%;
484
+ cursor: pointer;
485
+ transition: var(${prefix}-transition-fast);
486
+ }
487
+
488
+ .seekora-refinements__clear:hover {
489
+ color: var(${prefix}-color-text);
490
+ background-color: var(${prefix}-color-border);
491
+ }
492
+
493
+ .seekora-refinements__clear-all {
494
+ padding: var(${prefix}-spacing-small) var(${prefix}-spacing-medium);
495
+ font-size: var(${prefix}-font-size-small);
496
+ color: var(${prefix}-color-primary);
497
+ background: none;
498
+ border: none;
499
+ cursor: pointer;
500
+ text-decoration: underline;
501
+ }
502
+
503
+ /* Range Input */
504
+ .seekora-range-input {
505
+ font-family: var(${prefix}-font-family);
506
+ }
507
+
508
+ .seekora-range-input__label {
509
+ display: block;
510
+ margin-bottom: var(${prefix}-spacing-small);
511
+ font-size: var(${prefix}-font-size-medium);
512
+ font-weight: var(${prefix}-font-weight-medium, 500);
513
+ color: var(${prefix}-color-text);
514
+ }
515
+
516
+ .seekora-range-input__group {
517
+ display: flex;
518
+ align-items: center;
519
+ gap: var(${prefix}-spacing-small);
520
+ }
521
+
522
+ .seekora-range-input__input {
523
+ flex: 1;
524
+ padding: var(${prefix}-spacing-small);
525
+ font-size: var(${prefix}-font-size-medium);
526
+ color: var(${prefix}-color-text);
527
+ background-color: var(${prefix}-color-background);
528
+ border: 1px solid var(${prefix}-color-border);
529
+ border-radius: var(${prefix}-border-radius-medium, var(${prefix}-border-radius));
530
+ outline: none;
531
+ }
532
+
533
+ .seekora-range-input__separator {
534
+ color: var(${prefix}-color-textSecondary);
535
+ }
536
+
537
+ .seekora-range-input__button {
538
+ padding: var(${prefix}-spacing-small) var(${prefix}-spacing-medium);
539
+ font-size: var(${prefix}-font-size-medium);
540
+ color: #ffffff;
541
+ background-color: var(${prefix}-color-primary);
542
+ border: none;
543
+ border-radius: var(${prefix}-border-radius-medium, var(${prefix}-border-radius));
544
+ cursor: pointer;
545
+ }
546
+
547
+ /* Highlight */
548
+ .seekora-highlight mark,
549
+ .seekora-highlight__highlighted {
550
+ background-color: var(${prefix}-color-warning, #fff59d);
551
+ font-weight: var(${prefix}-font-weight-semibold, 600);
552
+ padding: 0 2px;
553
+ border-radius: 2px;
554
+ }
555
+
556
+ /* Infinite Hits */
557
+ .seekora-infinite-hits__show-more {
558
+ display: block;
559
+ width: 100%;
560
+ padding: var(${prefix}-spacing-medium);
561
+ margin-top: var(${prefix}-spacing-medium);
562
+ font-size: var(${prefix}-font-size-medium);
563
+ font-weight: var(${prefix}-font-weight-medium, 500);
564
+ color: #ffffff;
565
+ background-color: var(${prefix}-color-primary);
566
+ border: none;
567
+ border-radius: var(${prefix}-border-radius-medium, var(${prefix}-border-radius));
568
+ cursor: pointer;
569
+ transition: var(${prefix}-transition-fast);
570
+ }
571
+
572
+ .seekora-infinite-hits__show-more:disabled {
573
+ background-color: var(${prefix}-color-hover);
574
+ color: var(${prefix}-color-textSecondary);
575
+ cursor: not-allowed;
576
+ }
577
+ `;
578
+ }
579
+ /**
580
+ * Theme presets
581
+ */
582
+ const lightThemeVariables = `
583
+ :root, [data-seekora-theme="light"] {
584
+ --seekora-color-primary: #0066cc;
585
+ --seekora-color-secondary: #6c757d;
586
+ --seekora-color-background: #ffffff;
587
+ --seekora-color-surface: #f8f9fa;
588
+ --seekora-color-text: #212529;
589
+ --seekora-color-textSecondary: #6c757d;
590
+ --seekora-color-border: #dee2e6;
591
+ --seekora-color-hover: #f1f3f5;
592
+ --seekora-color-focus: rgba(0, 102, 204, 0.25);
593
+ --seekora-color-error: #dc3545;
594
+ --seekora-color-success: #28a745;
595
+ --seekora-color-warning: #ffc107;
596
+
597
+ --seekora-font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, sans-serif;
598
+ --seekora-font-size-small: 0.875rem;
599
+ --seekora-font-size-medium: 1rem;
600
+ --seekora-font-size-large: 1.25rem;
601
+ --seekora-font-weight-normal: 400;
602
+ --seekora-font-weight-medium: 500;
603
+ --seekora-font-weight-semibold: 600;
604
+ --seekora-font-weight-bold: 700;
605
+ --seekora-line-height-tight: 1.25;
606
+ --seekora-line-height-normal: 1.5;
607
+ --seekora-line-height-relaxed: 1.75;
608
+
609
+ --seekora-spacing-small: 0.5rem;
610
+ --seekora-spacing-medium: 1rem;
611
+ --seekora-spacing-large: 1.5rem;
612
+
613
+ --seekora-border-radius: 4px;
614
+ --seekora-border-radius-none: 0;
615
+ --seekora-border-radius-small: 2px;
616
+ --seekora-border-radius-medium: 4px;
617
+ --seekora-border-radius-large: 8px;
618
+ --seekora-border-radius-full: 9999px;
619
+
620
+ --seekora-shadow-small: 0 1px 2px rgba(0, 0, 0, 0.05);
621
+ --seekora-shadow-medium: 0 4px 6px rgba(0, 0, 0, 0.1);
622
+ --seekora-shadow-large: 0 10px 15px rgba(0, 0, 0, 0.1);
623
+
624
+ --seekora-transition-fast: 150ms ease-in-out;
625
+ --seekora-transition-normal: 300ms ease-in-out;
626
+ --seekora-transition-slow: 500ms ease-in-out;
627
+
628
+ --seekora-z-index-dropdown: 1000;
629
+ --seekora-z-index-modal: 1050;
630
+ --seekora-z-index-tooltip: 1100;
631
+ }
632
+ `;
633
+ const darkThemeVariables = `
634
+ [data-seekora-theme="dark"] {
635
+ --seekora-color-primary: #4da6ff;
636
+ --seekora-color-secondary: #adb5bd;
637
+ --seekora-color-background: #1a1a1a;
638
+ --seekora-color-surface: #2d2d2d;
639
+ --seekora-color-text: #f8f9fa;
640
+ --seekora-color-textSecondary: #adb5bd;
641
+ --seekora-color-border: #495057;
642
+ --seekora-color-hover: #343a40;
643
+ --seekora-color-focus: rgba(77, 166, 255, 0.25);
644
+ --seekora-color-error: #f87171;
645
+ --seekora-color-success: #4ade80;
646
+ --seekora-color-warning: #fbbf24;
647
+
648
+ --seekora-shadow-small: 0 1px 2px rgba(0, 0, 0, 0.3);
649
+ --seekora-shadow-medium: 0 4px 6px rgba(0, 0, 0, 0.4);
650
+ --seekora-shadow-large: 0 10px 15px rgba(0, 0, 0, 0.5);
651
+ }
652
+ `;
653
+ const minimalThemeVariables = `
654
+ [data-seekora-theme="minimal"] {
655
+ --seekora-color-primary: #000000;
656
+ --seekora-color-secondary: #666666;
657
+ --seekora-color-background: #ffffff;
658
+ --seekora-color-surface: #fafafa;
659
+ --seekora-color-text: #000000;
660
+ --seekora-color-textSecondary: #666666;
661
+ --seekora-color-border: #e5e5e5;
662
+ --seekora-color-hover: #f5f5f5;
663
+ --seekora-color-focus: rgba(0, 0, 0, 0.1);
664
+
665
+ --seekora-border-radius: 0;
666
+ --seekora-border-radius-none: 0;
667
+ --seekora-border-radius-small: 0;
668
+ --seekora-border-radius-medium: 0;
669
+ --seekora-border-radius-large: 0;
670
+ --seekora-border-radius-full: 0;
671
+
672
+ --seekora-shadow-small: none;
673
+ --seekora-shadow-medium: none;
674
+ --seekora-shadow-large: none;
675
+ }
676
+ `;
677
+ const highContrastThemeVariables = `
678
+ [data-seekora-theme="high-contrast"] {
679
+ --seekora-color-primary: #0000ff;
680
+ --seekora-color-secondary: #000000;
681
+ --seekora-color-background: #ffffff;
682
+ --seekora-color-surface: #ffffff;
683
+ --seekora-color-text: #000000;
684
+ --seekora-color-textSecondary: #000000;
685
+ --seekora-color-border: #000000;
686
+ --seekora-color-hover: #ffff00;
687
+ --seekora-color-focus: #0000ff;
688
+ --seekora-color-error: #ff0000;
689
+ --seekora-color-success: #008000;
690
+ --seekora-color-warning: #ffff00;
691
+
692
+ --seekora-font-weight-normal: 500;
693
+ --seekora-font-weight-medium: 600;
694
+ --seekora-font-weight-semibold: 700;
695
+ --seekora-font-weight-bold: 900;
696
+ }
697
+ `;
698
+ /**
699
+ * Complete stylesheet with all presets and component classes
700
+ */
701
+ function generateCompleteStylesheet() {
702
+ return `
703
+ /* Seekora UI SDK Styles */
704
+ /* Generated CSS Variables and Component Classes */
705
+
706
+ ${lightThemeVariables}
707
+
708
+ ${darkThemeVariables}
709
+
710
+ ${minimalThemeVariables}
711
+
712
+ ${highContrastThemeVariables}
713
+
714
+ ${generateComponentClasses('--seekora')}
715
+ `;
716
+ }
717
+ /**
718
+ * Inject styles into the document head
719
+ */
720
+ function injectStyles(css, id = 'seekora-theme') {
721
+ if (typeof document === 'undefined')
722
+ return;
723
+ // Remove existing style if present
724
+ const existing = document.getElementById(id);
725
+ if (existing) {
726
+ existing.remove();
727
+ }
728
+ const style = document.createElement('style');
729
+ style.id = id;
730
+ style.textContent = css;
731
+ document.head.appendChild(style);
732
+ }
733
+ /**
734
+ * Set the active theme
735
+ */
736
+ function setTheme(themeName) {
737
+ if (typeof document === 'undefined')
738
+ return;
739
+ document.documentElement.setAttribute('data-seekora-theme', themeName);
740
+ }
741
+ /**
742
+ * Get the current theme
743
+ */
744
+ function getTheme() {
745
+ if (typeof document === 'undefined')
746
+ return 'light';
747
+ return document.documentElement.getAttribute('data-seekora-theme') || 'light';
748
+ }
749
+
750
+ /**
751
+ * Logger utility for Seekora UI SDK
752
+ *
753
+ * Provides structured logging with different log levels:
754
+ * - verbose: Detailed debugging information
755
+ * - info: General informational messages
756
+ * - warn: Warning messages for potential issues
757
+ * - error: Error messages for failures
758
+ */
759
+ exports.LogLevel = void 0;
760
+ (function (LogLevel) {
761
+ LogLevel[LogLevel["VERBOSE"] = 0] = "VERBOSE";
762
+ LogLevel[LogLevel["INFO"] = 1] = "INFO";
763
+ LogLevel[LogLevel["WARN"] = 2] = "WARN";
764
+ LogLevel[LogLevel["ERROR"] = 3] = "ERROR";
765
+ LogLevel[LogLevel["SILENT"] = 4] = "SILENT";
766
+ })(exports.LogLevel || (exports.LogLevel = {}));
767
+ class Logger {
768
+ constructor(config = {}) {
769
+ this.level = config.level ?? this.getDefaultLevel();
770
+ this.prefix = config.prefix ?? '[Seekora UI SDK]';
771
+ this.enableTimestamp = config.enableTimestamp ?? false;
772
+ }
773
+ getDefaultLevel() {
774
+ // Check for environment variable or default to INFO
775
+ if (({})) {
776
+ const envLevel = undefined?.toUpperCase();
777
+ switch (envLevel) {
778
+ case 'VERBOSE':
779
+ return exports.LogLevel.VERBOSE;
780
+ case 'INFO':
781
+ return exports.LogLevel.INFO;
782
+ case 'WARN':
783
+ return exports.LogLevel.WARN;
784
+ case 'ERROR':
785
+ return exports.LogLevel.ERROR;
786
+ case 'SILENT':
787
+ return exports.LogLevel.SILENT;
788
+ }
789
+ }
790
+ // Default to INFO in production, VERBOSE in development
791
+ if (typeof window !== 'undefined' && window.__SEEKORA_DEBUG__) {
792
+ return exports.LogLevel.VERBOSE;
793
+ }
794
+ return exports.LogLevel.INFO;
795
+ }
796
+ formatMessage(level, ...args) {
797
+ const parts = [];
798
+ if (this.enableTimestamp) {
799
+ parts.push(`[${new Date().toISOString()}]`);
800
+ }
801
+ parts.push(this.prefix, `[${level}]`);
802
+ return [...parts, ...args];
803
+ }
804
+ verbose(...args) {
805
+ if (this.level <= exports.LogLevel.VERBOSE) {
806
+ console.log(...this.formatMessage('VERBOSE', ...args));
807
+ }
808
+ }
809
+ info(...args) {
810
+ if (this.level <= exports.LogLevel.INFO) {
811
+ console.info(...this.formatMessage('INFO', ...args));
812
+ }
813
+ }
814
+ warn(...args) {
815
+ if (this.level <= exports.LogLevel.WARN) {
816
+ console.warn(...this.formatMessage('WARN', ...args));
817
+ }
818
+ }
819
+ error(...args) {
820
+ if (this.level <= exports.LogLevel.ERROR) {
821
+ console.error(...this.formatMessage('ERROR', ...args));
822
+ }
823
+ }
824
+ setLevel(level) {
825
+ this.level = level;
826
+ }
827
+ getLevel() {
828
+ return this.level;
829
+ }
830
+ setPrefix(prefix) {
831
+ this.prefix = prefix;
832
+ }
833
+ setTimestamp(enabled) {
834
+ this.enableTimestamp = enabled;
835
+ }
836
+ }
837
+ // Create default logger instance
838
+ let defaultLogger = null;
839
+ function createLogger(config = {}) {
840
+ return new Logger(config);
841
+ }
842
+ function getLogger() {
843
+ if (!defaultLogger) {
844
+ defaultLogger = createLogger();
845
+ }
846
+ return defaultLogger;
847
+ }
848
+ function setDefaultLogger(logger) {
849
+ defaultLogger = logger;
850
+ }
851
+ function setLogLevel(level) {
852
+ getLogger().setLevel(level);
853
+ }
854
+ function setLogPrefix(prefix) {
855
+ getLogger().setPrefix(prefix);
856
+ }
857
+ function setLogTimestamp(enabled) {
858
+ getLogger().setTimestamp(enabled);
859
+ }
860
+ // Convenience functions that use the default logger
861
+ const log = {
862
+ verbose: (...args) => getLogger().verbose(...args),
863
+ info: (...args) => getLogger().info(...args),
864
+ warn: (...args) => getLogger().warn(...args),
865
+ error: (...args) => getLogger().error(...args),
866
+ };
867
+ // Export default logger instance
868
+ getLogger();
869
+
870
+ /**
871
+ * Highlight Utilities
872
+ *
873
+ * Core utilities for highlighting matching terms in search results
874
+ * Framework-agnostic functions shared across all packages
875
+ */
876
+ /**
877
+ * Get highlighted value from a hit
878
+ * Looks for _highlightResult or highlight_result field
879
+ */
880
+ function getHighlightedValue(options) {
881
+ const { attribute, hit, preTag = '<mark>', postTag = '</mark>', fallback, } = options;
882
+ // Check for various highlight result formats
883
+ const highlightResult = hit._highlightResult?.[attribute]?.value ||
884
+ hit.highlight_result?.[attribute]?.value ||
885
+ hit._highlight?.[attribute] ||
886
+ hit.highlight?.[attribute];
887
+ if (highlightResult) {
888
+ // Replace highlight markers with custom tags
889
+ return highlightResult
890
+ .replace(/<em>/g, preTag)
891
+ .replace(/<\/em>/g, postTag)
892
+ .replace(/<mark>/g, preTag)
893
+ .replace(/<\/mark>/g, postTag);
894
+ }
895
+ // Fall back to plain attribute value
896
+ const plainValue = getNestedValue(hit, attribute);
897
+ return plainValue !== undefined ? String(plainValue) : (fallback || '');
898
+ }
899
+ /**
900
+ * Get snippet (truncated highlighted value) from a hit
901
+ */
902
+ function getSnippetedValue(options) {
903
+ const { attribute, hit, preTag = '<mark>', postTag = '</mark>', fallback, maxLength = 100, ellipsis = '...', } = options;
904
+ // Check for snippet result
905
+ const snippetResult = hit._snippetResult?.[attribute]?.value ||
906
+ hit.snippet_result?.[attribute]?.value ||
907
+ hit._snippet?.[attribute] ||
908
+ hit.snippet?.[attribute];
909
+ let text = '';
910
+ if (snippetResult) {
911
+ text = snippetResult
912
+ .replace(/<em>/g, preTag)
913
+ .replace(/<\/em>/g, postTag)
914
+ .replace(/<mark>/g, preTag)
915
+ .replace(/<\/mark>/g, postTag);
916
+ }
917
+ else {
918
+ // Fall back to highlighted or plain value
919
+ text = getHighlightedValue(options);
920
+ }
921
+ // Truncate if needed
922
+ if (text.length > maxLength) {
923
+ // Try to truncate at word boundary
924
+ const truncated = text.substring(0, maxLength);
925
+ const lastSpace = truncated.lastIndexOf(' ');
926
+ if (lastSpace > maxLength * 0.7) {
927
+ return truncated.substring(0, lastSpace) + ellipsis;
928
+ }
929
+ return truncated + ellipsis;
930
+ }
931
+ return text;
932
+ }
933
+ /**
934
+ * Parse highlighted value into parts for custom rendering
935
+ */
936
+ function parseHighlightedParts(highlightedValue, preTag = '<mark>', postTag = '</mark>') {
937
+ const parts = [];
938
+ let remaining = highlightedValue;
939
+ while (remaining.length > 0) {
940
+ const startIdx = remaining.indexOf(preTag);
941
+ if (startIdx === -1) {
942
+ // No more highlights, add remaining as non-highlighted
943
+ if (remaining.length > 0) {
944
+ parts.push({ value: remaining, isHighlighted: false });
945
+ }
946
+ break;
947
+ }
948
+ // Add text before highlight
949
+ if (startIdx > 0) {
950
+ parts.push({ value: remaining.substring(0, startIdx), isHighlighted: false });
951
+ }
952
+ // Find end of highlight
953
+ const afterPreTag = remaining.substring(startIdx + preTag.length);
954
+ const endIdx = afterPreTag.indexOf(postTag);
955
+ if (endIdx === -1) {
956
+ // Malformed, add rest as non-highlighted
957
+ parts.push({ value: remaining.substring(startIdx), isHighlighted: false });
958
+ break;
959
+ }
960
+ // Add highlighted part
961
+ parts.push({ value: afterPreTag.substring(0, endIdx), isHighlighted: true });
962
+ // Continue with rest
963
+ remaining = afterPreTag.substring(endIdx + postTag.length);
964
+ }
965
+ return parts;
966
+ }
967
+ /**
968
+ * Highlight a query in text (client-side highlighting)
969
+ * Useful when server doesn't provide highlight data
970
+ */
971
+ function highlightQuery(text, query, options = {}) {
972
+ const { preTag = '<mark>', postTag = '</mark>', caseSensitive = false } = options;
973
+ if (!query || !text)
974
+ return text;
975
+ const flags = caseSensitive ? 'g' : 'gi';
976
+ const escapedQuery = query.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
977
+ const regex = new RegExp(`(${escapedQuery})`, flags);
978
+ return text.replace(regex, `${preTag}$1${postTag}`);
979
+ }
980
+ /**
981
+ * Parse query-highlighted text into parts
982
+ */
983
+ function parseQueryHighlightParts(text, query, caseSensitive = false) {
984
+ if (!query || !text) {
985
+ return [{ value: text, isHighlighted: false }];
986
+ }
987
+ const parts = [];
988
+ const flags = caseSensitive ? 'g' : 'gi';
989
+ const escapedQuery = query.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
990
+ const regex = new RegExp(escapedQuery, flags);
991
+ let lastIndex = 0;
992
+ let match;
993
+ while ((match = regex.exec(text)) !== null) {
994
+ // Add text before match
995
+ if (match.index > lastIndex) {
996
+ parts.push({ value: text.substring(lastIndex, match.index), isHighlighted: false });
997
+ }
998
+ // Add highlighted match
999
+ parts.push({ value: match[0], isHighlighted: true });
1000
+ lastIndex = regex.lastIndex;
1001
+ }
1002
+ // Add remaining text
1003
+ if (lastIndex < text.length) {
1004
+ parts.push({ value: text.substring(lastIndex), isHighlighted: false });
1005
+ }
1006
+ return parts.length > 0 ? parts : [{ value: text, isHighlighted: false }];
1007
+ }
1008
+ /**
1009
+ * Get nested value from object using dot notation
1010
+ */
1011
+ function getNestedValue(obj, path) {
1012
+ return path.split('.').reduce((current, key) => {
1013
+ return current && current[key] !== undefined ? current[key] : undefined;
1014
+ }, obj);
1015
+ }
1016
+ /**
1017
+ * Strip HTML tags from highlighted text
1018
+ */
1019
+ function stripHighlightTags(text) {
1020
+ return text
1021
+ .replace(/<mark>/g, '')
1022
+ .replace(/<\/mark>/g, '')
1023
+ .replace(/<em>/g, '')
1024
+ .replace(/<\/em>/g, '');
1025
+ }
1026
+
1027
+ /**
1028
+ * Accessibility Utilities
1029
+ *
1030
+ * Shared accessibility helpers for keyboard navigation, focus management,
1031
+ * screen reader announcements, and ARIA attributes.
1032
+ */
1033
+ // ============================================================================
1034
+ // Screen Reader Announcements (Live Regions)
1035
+ // ============================================================================
1036
+ let announcer = null;
1037
+ /**
1038
+ * Create or get the screen reader announcer element
1039
+ */
1040
+ function getAnnouncer() {
1041
+ if (announcer)
1042
+ return announcer;
1043
+ if (typeof document === 'undefined') {
1044
+ throw new Error('Cannot create announcer: document is undefined');
1045
+ }
1046
+ announcer = document.createElement('div');
1047
+ announcer.id = 'seekora-announcer';
1048
+ announcer.setAttribute('role', 'status');
1049
+ announcer.setAttribute('aria-live', 'polite');
1050
+ announcer.setAttribute('aria-atomic', 'true');
1051
+ announcer.style.cssText = `
1052
+ position: absolute;
1053
+ width: 1px;
1054
+ height: 1px;
1055
+ padding: 0;
1056
+ margin: -1px;
1057
+ overflow: hidden;
1058
+ clip: rect(0, 0, 0, 0);
1059
+ white-space: nowrap;
1060
+ border: 0;
1061
+ `;
1062
+ document.body.appendChild(announcer);
1063
+ return announcer;
1064
+ }
1065
+ /**
1066
+ * Announce a message to screen readers
1067
+ */
1068
+ function announce(message, priority = 'polite') {
1069
+ if (typeof document === 'undefined')
1070
+ return;
1071
+ try {
1072
+ const el = getAnnouncer();
1073
+ el.setAttribute('aria-live', priority);
1074
+ // Clear and set message (needs a brief delay to trigger announcement)
1075
+ el.textContent = '';
1076
+ setTimeout(() => {
1077
+ el.textContent = message;
1078
+ }, 50);
1079
+ }
1080
+ catch (e) {
1081
+ // Ignore errors in SSR
1082
+ }
1083
+ }
1084
+ /**
1085
+ * Announce search results count
1086
+ */
1087
+ function announceResults(count, query) {
1088
+ let message = '';
1089
+ if (count === 0) {
1090
+ message = query
1091
+ ? `No results found for "${query}"`
1092
+ : 'No results found';
1093
+ }
1094
+ else if (count === 1) {
1095
+ message = query
1096
+ ? `1 result found for "${query}"`
1097
+ : '1 result found';
1098
+ }
1099
+ else {
1100
+ message = query
1101
+ ? `${count} results found for "${query}"`
1102
+ : `${count} results found`;
1103
+ }
1104
+ announce(message);
1105
+ }
1106
+ /**
1107
+ * Announce filter changes
1108
+ */
1109
+ function announceFilterChange(action, filterName, filterValue) {
1110
+ let message = '';
1111
+ switch (action) {
1112
+ case 'added':
1113
+ message = `Filter ${filterName}: ${filterValue} applied`;
1114
+ break;
1115
+ case 'removed':
1116
+ message = `Filter ${filterName}: ${filterValue} removed`;
1117
+ break;
1118
+ case 'cleared':
1119
+ message = 'All filters cleared';
1120
+ break;
1121
+ }
1122
+ announce(message);
1123
+ }
1124
+ /**
1125
+ * Handle keyboard navigation for a list of items
1126
+ */
1127
+ function handleKeyboardNavigation(event, options) {
1128
+ const { items, activeIndex, onActiveChange, onSelect, onCancel, wrap = true, orientation = 'vertical', } = options;
1129
+ const itemCount = items.length;
1130
+ if (itemCount === 0)
1131
+ return false;
1132
+ let handled = false;
1133
+ let newIndex = activeIndex;
1134
+ switch (event.key) {
1135
+ case 'ArrowDown':
1136
+ if (orientation === 'vertical' || orientation === 'both') {
1137
+ event.preventDefault();
1138
+ if (activeIndex < itemCount - 1) {
1139
+ newIndex = activeIndex + 1;
1140
+ }
1141
+ else if (wrap) {
1142
+ newIndex = 0;
1143
+ }
1144
+ handled = true;
1145
+ }
1146
+ break;
1147
+ case 'ArrowUp':
1148
+ if (orientation === 'vertical' || orientation === 'both') {
1149
+ event.preventDefault();
1150
+ if (activeIndex > 0) {
1151
+ newIndex = activeIndex - 1;
1152
+ }
1153
+ else if (wrap) {
1154
+ newIndex = itemCount - 1;
1155
+ }
1156
+ handled = true;
1157
+ }
1158
+ break;
1159
+ case 'ArrowRight':
1160
+ if (orientation === 'horizontal' || orientation === 'both') {
1161
+ event.preventDefault();
1162
+ if (activeIndex < itemCount - 1) {
1163
+ newIndex = activeIndex + 1;
1164
+ }
1165
+ else if (wrap) {
1166
+ newIndex = 0;
1167
+ }
1168
+ handled = true;
1169
+ }
1170
+ break;
1171
+ case 'ArrowLeft':
1172
+ if (orientation === 'horizontal' || orientation === 'both') {
1173
+ event.preventDefault();
1174
+ if (activeIndex > 0) {
1175
+ newIndex = activeIndex - 1;
1176
+ }
1177
+ else if (wrap) {
1178
+ newIndex = itemCount - 1;
1179
+ }
1180
+ handled = true;
1181
+ }
1182
+ break;
1183
+ case 'Home':
1184
+ event.preventDefault();
1185
+ newIndex = 0;
1186
+ handled = true;
1187
+ break;
1188
+ case 'End':
1189
+ event.preventDefault();
1190
+ newIndex = itemCount - 1;
1191
+ handled = true;
1192
+ break;
1193
+ case 'Enter':
1194
+ case ' ':
1195
+ event.preventDefault();
1196
+ if (activeIndex >= 0 && onSelect) {
1197
+ onSelect(activeIndex);
1198
+ }
1199
+ handled = true;
1200
+ break;
1201
+ case 'Escape':
1202
+ if (onCancel) {
1203
+ event.preventDefault();
1204
+ onCancel();
1205
+ handled = true;
1206
+ }
1207
+ break;
1208
+ }
1209
+ if (newIndex !== activeIndex && newIndex >= 0 && newIndex < itemCount) {
1210
+ onActiveChange(newIndex);
1211
+ // Focus the new item
1212
+ const item = items[newIndex];
1213
+ if (item) {
1214
+ item.focus();
1215
+ }
1216
+ }
1217
+ return handled;
1218
+ }
1219
+ /**
1220
+ * Create a roving tabindex manager for accessible list navigation
1221
+ */
1222
+ function createRovingTabIndex(container, selector) {
1223
+ const items = Array.from(container.querySelectorAll(selector));
1224
+ // Initialize: first item gets tabindex 0, others get -1
1225
+ items.forEach((item, index) => {
1226
+ item.setAttribute('tabindex', index === 0 ? '0' : '-1');
1227
+ });
1228
+ return {
1229
+ update: (activeIndex) => {
1230
+ items.forEach((item, index) => {
1231
+ item.setAttribute('tabindex', index === activeIndex ? '0' : '-1');
1232
+ });
1233
+ },
1234
+ destroy: () => {
1235
+ items.forEach(item => {
1236
+ item.removeAttribute('tabindex');
1237
+ });
1238
+ },
1239
+ };
1240
+ }
1241
+ // ============================================================================
1242
+ // Focus Management
1243
+ // ============================================================================
1244
+ /**
1245
+ * Get all focusable elements within a container
1246
+ */
1247
+ function getFocusableElements(container) {
1248
+ const selector = [
1249
+ 'button:not([disabled])',
1250
+ '[href]',
1251
+ 'input:not([disabled])',
1252
+ 'select:not([disabled])',
1253
+ 'textarea:not([disabled])',
1254
+ '[tabindex]:not([tabindex="-1"])',
1255
+ ].join(', ');
1256
+ return Array.from(container.querySelectorAll(selector));
1257
+ }
1258
+ /**
1259
+ * Create a focus trap within a container (for modals/dialogs)
1260
+ */
1261
+ function createFocusTrap(container) {
1262
+ let previouslyFocused = null;
1263
+ let handleKeyDown = null;
1264
+ return {
1265
+ activate: () => {
1266
+ previouslyFocused = document.activeElement;
1267
+ const focusable = getFocusableElements(container);
1268
+ if (focusable.length === 0)
1269
+ return;
1270
+ const firstFocusable = focusable[0];
1271
+ const lastFocusable = focusable[focusable.length - 1];
1272
+ handleKeyDown = (e) => {
1273
+ if (e.key !== 'Tab')
1274
+ return;
1275
+ if (e.shiftKey) {
1276
+ if (document.activeElement === firstFocusable) {
1277
+ e.preventDefault();
1278
+ lastFocusable.focus();
1279
+ }
1280
+ }
1281
+ else {
1282
+ if (document.activeElement === lastFocusable) {
1283
+ e.preventDefault();
1284
+ firstFocusable.focus();
1285
+ }
1286
+ }
1287
+ };
1288
+ document.addEventListener('keydown', handleKeyDown);
1289
+ firstFocusable.focus();
1290
+ },
1291
+ deactivate: () => {
1292
+ if (handleKeyDown) {
1293
+ document.removeEventListener('keydown', handleKeyDown);
1294
+ handleKeyDown = null;
1295
+ }
1296
+ if (previouslyFocused) {
1297
+ previouslyFocused.focus();
1298
+ previouslyFocused = null;
1299
+ }
1300
+ },
1301
+ };
1302
+ }
1303
+ /**
1304
+ * Focus first error or first input in a form
1305
+ */
1306
+ function focusFirstError(container) {
1307
+ const error = container.querySelector('[aria-invalid="true"]');
1308
+ if (error) {
1309
+ error.focus();
1310
+ return true;
1311
+ }
1312
+ return false;
1313
+ }
1314
+ // ============================================================================
1315
+ // ARIA Helpers
1316
+ // ============================================================================
1317
+ /**
1318
+ * Generate a unique ID for ARIA relationships
1319
+ */
1320
+ let idCounter = 0;
1321
+ function generateId(prefix = 'seekora') {
1322
+ return `${prefix}-${++idCounter}`;
1323
+ }
1324
+ /**
1325
+ * Set up ARIA describedby relationship
1326
+ */
1327
+ function setAriaDescribedBy(element, describer) {
1328
+ const id = describer.id || generateId('description');
1329
+ describer.id = id;
1330
+ const existing = element.getAttribute('aria-describedby');
1331
+ const newValue = existing ? `${existing} ${id}` : id;
1332
+ element.setAttribute('aria-describedby', newValue);
1333
+ return () => {
1334
+ const current = element.getAttribute('aria-describedby');
1335
+ if (current) {
1336
+ const ids = current.split(' ').filter(i => i !== id);
1337
+ if (ids.length > 0) {
1338
+ element.setAttribute('aria-describedby', ids.join(' '));
1339
+ }
1340
+ else {
1341
+ element.removeAttribute('aria-describedby');
1342
+ }
1343
+ }
1344
+ };
1345
+ }
1346
+ /**
1347
+ * Check if user prefers reduced motion
1348
+ */
1349
+ function prefersReducedMotion() {
1350
+ if (typeof window === 'undefined')
1351
+ return false;
1352
+ return window.matchMedia('(prefers-reduced-motion: reduce)').matches;
1353
+ }
1354
+ /**
1355
+ * Check if user prefers high contrast
1356
+ */
1357
+ function prefersHighContrast() {
1358
+ if (typeof window === 'undefined')
1359
+ return false;
1360
+ return window.matchMedia('(prefers-contrast: more)').matches;
1361
+ }
1362
+ /**
1363
+ * Create a keyboard shortcuts manager
1364
+ */
1365
+ function createKeyboardShortcuts(shortcuts) {
1366
+ const handleKeyDown = (e) => {
1367
+ for (const shortcut of shortcuts) {
1368
+ const keyMatches = e.key.toLowerCase() === shortcut.key.toLowerCase();
1369
+ const ctrlMatches = !shortcut.ctrl || e.ctrlKey;
1370
+ const altMatches = !shortcut.alt || e.altKey;
1371
+ const shiftMatches = !shortcut.shift || e.shiftKey;
1372
+ const metaMatches = !shortcut.meta || e.metaKey;
1373
+ if (keyMatches && ctrlMatches && altMatches && shiftMatches && metaMatches) {
1374
+ e.preventDefault();
1375
+ shortcut.handler(e);
1376
+ break;
1377
+ }
1378
+ }
1379
+ };
1380
+ return {
1381
+ attach: () => {
1382
+ document.addEventListener('keydown', handleKeyDown);
1383
+ },
1384
+ detach: () => {
1385
+ document.removeEventListener('keydown', handleKeyDown);
1386
+ },
1387
+ getShortcuts: () => [...shortcuts],
1388
+ };
1389
+ }
1390
+
1391
+ /**
1392
+ * SearchStateManager
1393
+ *
1394
+ * Centralized state management for search operations
1395
+ * Framework-agnostic core logic shared across all packages
1396
+ * Handles query, refinements, pagination, sorting, and automatic search triggering
1397
+ * Similar to InstantSearch.js architecture
1398
+ */
1399
+ class SearchStateManager {
1400
+ constructor(config) {
1401
+ this.listeners = [];
1402
+ this.debounceTimer = null;
1403
+ this.client = config.client;
1404
+ this.autoSearch = config.autoSearch !== false;
1405
+ this.debounceMs = config.debounceMs || 300;
1406
+ this.defaultSearchOptions = config.defaultSearchOptions || { widget_mode: true };
1407
+ this.state = {
1408
+ query: config.initialQuery || '',
1409
+ refinements: [],
1410
+ currentPage: 1,
1411
+ itemsPerPage: config.itemsPerPage || 10,
1412
+ sortBy: undefined,
1413
+ results: null,
1414
+ loading: false,
1415
+ error: null,
1416
+ };
1417
+ log.info('SearchStateManager: Initialized', {
1418
+ autoSearch: this.autoSearch,
1419
+ debounceMs: this.debounceMs,
1420
+ });
1421
+ }
1422
+ // State getters
1423
+ getState() {
1424
+ return { ...this.state };
1425
+ }
1426
+ getQuery() {
1427
+ return this.state.query;
1428
+ }
1429
+ getRefinements() {
1430
+ return [...this.state.refinements];
1431
+ }
1432
+ getCurrentPage() {
1433
+ return this.state.currentPage;
1434
+ }
1435
+ getResults() {
1436
+ return this.state.results;
1437
+ }
1438
+ getLoading() {
1439
+ return this.state.loading;
1440
+ }
1441
+ getError() {
1442
+ return this.state.error;
1443
+ }
1444
+ // State setters (automatically trigger search if autoSearch is enabled)
1445
+ setQuery(query, triggerSearch = true) {
1446
+ if (this.state.query === query) {
1447
+ log.verbose('SearchStateManager: Query unchanged, skipping update', { query });
1448
+ // Even if query is the same, trigger search if explicitly requested
1449
+ if (triggerSearch && this.autoSearch) {
1450
+ log.verbose('SearchStateManager: Query unchanged but triggerSearch=true, triggering search');
1451
+ this.debouncedSearch();
1452
+ }
1453
+ return;
1454
+ }
1455
+ this.state.query = query;
1456
+ this.state.currentPage = 1; // Reset to first page on new query
1457
+ log.verbose('SearchStateManager: Query updated', { query, triggerSearch, autoSearch: this.autoSearch });
1458
+ this.notifyListeners();
1459
+ if (this.autoSearch && triggerSearch) {
1460
+ log.verbose('SearchStateManager: Triggering debounced search');
1461
+ this.debouncedSearch();
1462
+ }
1463
+ else {
1464
+ log.verbose('SearchStateManager: Search not triggered', { autoSearch: this.autoSearch, triggerSearch });
1465
+ }
1466
+ }
1467
+ addRefinement(field, value, triggerSearch = true) {
1468
+ // Check if refinement already exists
1469
+ const exists = this.state.refinements.some(r => r.field === field && r.value === value);
1470
+ if (exists)
1471
+ return;
1472
+ this.state.refinements.push({ field, value });
1473
+ this.state.currentPage = 1; // Reset to first page on filter change
1474
+ log.verbose('SearchStateManager: Refinement added', { field, value });
1475
+ this.notifyListeners();
1476
+ if (this.autoSearch && triggerSearch) {
1477
+ this.debouncedSearch();
1478
+ }
1479
+ }
1480
+ removeRefinement(field, value, triggerSearch = true) {
1481
+ const index = this.state.refinements.findIndex(r => r.field === field && r.value === value);
1482
+ if (index === -1)
1483
+ return;
1484
+ this.state.refinements.splice(index, 1);
1485
+ this.state.currentPage = 1; // Reset to first page on filter change
1486
+ log.verbose('SearchStateManager: Refinement removed', { field, value });
1487
+ this.notifyListeners();
1488
+ if (this.autoSearch && triggerSearch) {
1489
+ this.debouncedSearch();
1490
+ }
1491
+ }
1492
+ clearRefinements(triggerSearch = true) {
1493
+ if (this.state.refinements.length === 0)
1494
+ return;
1495
+ this.state.refinements = [];
1496
+ this.state.currentPage = 1;
1497
+ log.verbose('SearchStateManager: Refinements cleared');
1498
+ this.notifyListeners();
1499
+ if (this.autoSearch && triggerSearch) {
1500
+ this.debouncedSearch();
1501
+ }
1502
+ }
1503
+ setPage(page, triggerSearch = true) {
1504
+ if (this.state.currentPage === page)
1505
+ return;
1506
+ this.state.currentPage = page;
1507
+ log.verbose('SearchStateManager: Page updated', { page });
1508
+ this.notifyListeners();
1509
+ if (this.autoSearch && triggerSearch) {
1510
+ this.debouncedSearch();
1511
+ }
1512
+ }
1513
+ setSortBy(sortBy, triggerSearch = true) {
1514
+ if (this.state.sortBy === sortBy)
1515
+ return;
1516
+ this.state.sortBy = sortBy;
1517
+ this.state.currentPage = 1; // Reset to first page on sort change
1518
+ log.verbose('SearchStateManager: Sort updated', { sortBy });
1519
+ this.notifyListeners();
1520
+ if (this.autoSearch && triggerSearch) {
1521
+ this.debouncedSearch();
1522
+ }
1523
+ }
1524
+ setItemsPerPage(itemsPerPage, triggerSearch = true) {
1525
+ if (this.state.itemsPerPage === itemsPerPage)
1526
+ return;
1527
+ this.state.itemsPerPage = itemsPerPage;
1528
+ this.state.currentPage = 1; // Reset to first page when changing items per page
1529
+ log.verbose('SearchStateManager: Items per page updated', { itemsPerPage });
1530
+ this.notifyListeners();
1531
+ if (this.autoSearch && triggerSearch) {
1532
+ this.debouncedSearch();
1533
+ }
1534
+ }
1535
+ // Manual search trigger
1536
+ async search(additionalOptions) {
1537
+ // Clear debounce timer if exists
1538
+ if (this.debounceTimer) {
1539
+ clearTimeout(this.debounceTimer);
1540
+ this.debounceTimer = null;
1541
+ }
1542
+ this.setState({ loading: true, error: null });
1543
+ try {
1544
+ const searchOptions = this.buildSearchOptions(additionalOptions);
1545
+ log.verbose('SearchStateManager: Performing search', {
1546
+ query: this.state.query,
1547
+ refinementsCount: this.state.refinements.length,
1548
+ currentPage: this.state.currentPage,
1549
+ sortBy: this.state.sortBy,
1550
+ filter_by: searchOptions.filter_by,
1551
+ });
1552
+ const response = await this.client.search(this.state.query, searchOptions);
1553
+ this.setState({
1554
+ results: response,
1555
+ loading: false,
1556
+ error: null,
1557
+ });
1558
+ log.info('SearchStateManager: Search completed', {
1559
+ query: this.state.query,
1560
+ resultsCount: response?.results?.length || 0,
1561
+ totalResults: response?.totalResults || 0,
1562
+ });
1563
+ return response;
1564
+ }
1565
+ catch (err) {
1566
+ const error = err instanceof Error ? err : new Error(String(err));
1567
+ this.setState({
1568
+ results: null,
1569
+ loading: false,
1570
+ error,
1571
+ });
1572
+ log.error('SearchStateManager: Search failed', {
1573
+ query: this.state.query,
1574
+ error: error.message,
1575
+ });
1576
+ return null;
1577
+ }
1578
+ }
1579
+ // Build search options from current state
1580
+ buildSearchOptions(additionalOptions) {
1581
+ const options = {
1582
+ ...this.defaultSearchOptions,
1583
+ page: this.state.currentPage,
1584
+ per_page: this.state.itemsPerPage,
1585
+ ...additionalOptions,
1586
+ };
1587
+ // Add sort_by if set and not "relevance" (relevance is the default, no sort field needed)
1588
+ if (this.state.sortBy && this.state.sortBy !== 'relevance') {
1589
+ options.sort_by = this.state.sortBy;
1590
+ }
1591
+ // Build filter_by from refinements
1592
+ if (this.state.refinements.length > 0) {
1593
+ const filtersByField = {};
1594
+ this.state.refinements.forEach((refinement) => {
1595
+ if (!filtersByField[refinement.field]) {
1596
+ filtersByField[refinement.field] = [];
1597
+ }
1598
+ filtersByField[refinement.field].push(refinement.value);
1599
+ });
1600
+ const filterParts = [];
1601
+ Object.entries(filtersByField).forEach(([field, values]) => {
1602
+ values.forEach((value) => {
1603
+ filterParts.push(`${field}:${value}`);
1604
+ });
1605
+ });
1606
+ if (filterParts.length > 0) {
1607
+ options.filter_by = filterParts.join(' && ');
1608
+ }
1609
+ }
1610
+ return options;
1611
+ }
1612
+ // Debounced search trigger
1613
+ debouncedSearch() {
1614
+ if (this.debounceTimer) {
1615
+ clearTimeout(this.debounceTimer);
1616
+ }
1617
+ log.verbose('SearchStateManager: Scheduling debounced search', { debounceMs: this.debounceMs });
1618
+ this.debounceTimer = setTimeout(() => {
1619
+ log.verbose('SearchStateManager: Debounce timer fired, calling search()');
1620
+ this.search();
1621
+ }, this.debounceMs);
1622
+ }
1623
+ // Subscribe to state changes
1624
+ subscribe(listener) {
1625
+ this.listeners.push(listener);
1626
+ // Immediately call listener with current state
1627
+ listener(this.getState());
1628
+ // Return unsubscribe function
1629
+ return () => {
1630
+ this.listeners = this.listeners.filter(l => l !== listener);
1631
+ };
1632
+ }
1633
+ // Update state and notify listeners
1634
+ setState(updates) {
1635
+ this.state = { ...this.state, ...updates };
1636
+ this.notifyListeners();
1637
+ }
1638
+ // Notify all listeners of state changes
1639
+ notifyListeners() {
1640
+ const state = this.getState();
1641
+ this.listeners.forEach(listener => {
1642
+ try {
1643
+ listener(state);
1644
+ }
1645
+ catch (err) {
1646
+ const error = err instanceof Error ? err : new Error(String(err));
1647
+ log.error('SearchStateManager: Error in listener', {
1648
+ error: error.message,
1649
+ });
1650
+ }
1651
+ });
1652
+ }
1653
+ // Clear all state
1654
+ clear() {
1655
+ this.state = {
1656
+ query: '',
1657
+ refinements: [],
1658
+ currentPage: 1,
1659
+ itemsPerPage: this.state.itemsPerPage,
1660
+ sortBy: undefined,
1661
+ results: null,
1662
+ loading: false,
1663
+ error: null,
1664
+ };
1665
+ this.notifyListeners();
1666
+ }
1667
+ }
1668
+
1669
+ /**
1670
+ * URL Router
1671
+ *
1672
+ * Syncs search state with browser URL for shareable links and back/forward navigation
1673
+ * Framework-agnostic core logic shared across all packages
1674
+ */
1675
+ class URLRouter {
1676
+ constructor(config) {
1677
+ this.unsubscribe = null;
1678
+ this.debounceTimer = null;
1679
+ this.isUpdatingFromUrl = false;
1680
+ /**
1681
+ * Handle browser back/forward navigation
1682
+ */
1683
+ this.handlePopState = () => {
1684
+ if (this.readFromUrl) {
1685
+ // Clear current refinements first
1686
+ this.stateManager.clear();
1687
+ this.readStateFromUrl();
1688
+ }
1689
+ };
1690
+ this.stateManager = config.stateManager;
1691
+ this.writeToUrl = config.writeToUrl !== false;
1692
+ this.readFromUrl = config.readFromUrl !== false;
1693
+ this.historyMode = config.historyMode || 'push';
1694
+ this.debounceMs = config.debounceMs ?? 400;
1695
+ this.basePath = config.basePath || (typeof window !== 'undefined' ? window.location.pathname : '/');
1696
+ this.triggerSearchOnChange = config.triggerSearchOnChange !== false;
1697
+ this.mapping = {
1698
+ query: config.mapping?.query || 'q',
1699
+ page: config.mapping?.page || 'page',
1700
+ hitsPerPage: config.mapping?.hitsPerPage || 'hitsPerPage',
1701
+ sortBy: config.mapping?.sortBy || 'sortBy',
1702
+ refinementPrefix: config.mapping?.refinementPrefix || '',
1703
+ refinementSerializer: config.mapping?.refinementSerializer,
1704
+ refinementDeserializer: config.mapping?.refinementDeserializer,
1705
+ };
1706
+ // Initialize
1707
+ this.init();
1708
+ }
1709
+ init() {
1710
+ if (typeof window === 'undefined') {
1711
+ log.warn('URLRouter: Not in browser environment, skipping initialization');
1712
+ return;
1713
+ }
1714
+ // Read initial state from URL
1715
+ if (this.readFromUrl) {
1716
+ this.readStateFromUrl();
1717
+ }
1718
+ // Subscribe to state changes
1719
+ if (this.writeToUrl) {
1720
+ this.unsubscribe = this.stateManager.subscribe((state) => {
1721
+ if (!this.isUpdatingFromUrl) {
1722
+ this.debouncedWriteToUrl(state);
1723
+ }
1724
+ });
1725
+ }
1726
+ // Listen for popstate (back/forward navigation)
1727
+ window.addEventListener('popstate', this.handlePopState);
1728
+ log.info('URLRouter: Initialized', {
1729
+ writeToUrl: this.writeToUrl,
1730
+ readFromUrl: this.readFromUrl,
1731
+ historyMode: this.historyMode,
1732
+ });
1733
+ }
1734
+ /**
1735
+ * Read state from current URL and update StateManager
1736
+ */
1737
+ readStateFromUrl() {
1738
+ const params = new URLSearchParams(window.location.search);
1739
+ this.isUpdatingFromUrl = true;
1740
+ try {
1741
+ // Query
1742
+ const query = params.get(this.mapping.query);
1743
+ if (query !== null) {
1744
+ this.stateManager.setQuery(query, false);
1745
+ }
1746
+ // Page
1747
+ const page = params.get(this.mapping.page);
1748
+ if (page !== null) {
1749
+ const pageNum = parseInt(page, 10);
1750
+ if (!isNaN(pageNum) && pageNum > 0) {
1751
+ this.stateManager.setPage(pageNum, false);
1752
+ }
1753
+ }
1754
+ // Hits per page
1755
+ const hitsPerPage = params.get(this.mapping.hitsPerPage);
1756
+ if (hitsPerPage !== null) {
1757
+ const hpp = parseInt(hitsPerPage, 10);
1758
+ if (!isNaN(hpp) && hpp > 0) {
1759
+ this.stateManager.setItemsPerPage(hpp, false);
1760
+ }
1761
+ }
1762
+ // Sort by
1763
+ const sortBy = params.get(this.mapping.sortBy);
1764
+ if (sortBy !== null) {
1765
+ this.stateManager.setSortBy(sortBy, false);
1766
+ }
1767
+ // Refinements
1768
+ const refinements = this.deserializeRefinements(params);
1769
+ refinements.forEach(r => {
1770
+ this.stateManager.addRefinement(r.field, r.value, false);
1771
+ });
1772
+ // Trigger search if there's any state
1773
+ if (this.triggerSearchOnChange && (query || refinements.length > 0)) {
1774
+ this.stateManager.search();
1775
+ }
1776
+ log.verbose('URLRouter: Read state from URL', {
1777
+ query,
1778
+ page,
1779
+ refinements: refinements.length,
1780
+ });
1781
+ }
1782
+ finally {
1783
+ this.isUpdatingFromUrl = false;
1784
+ }
1785
+ }
1786
+ /**
1787
+ * Write state to URL
1788
+ */
1789
+ writeStateToUrl(state) {
1790
+ const params = new URLSearchParams();
1791
+ // Query
1792
+ if (state.query) {
1793
+ params.set(this.mapping.query, state.query);
1794
+ }
1795
+ // Page (only if not 1)
1796
+ if (state.currentPage > 1) {
1797
+ params.set(this.mapping.page, state.currentPage.toString());
1798
+ }
1799
+ // Hits per page (only if not default)
1800
+ if (state.itemsPerPage !== 10) {
1801
+ params.set(this.mapping.hitsPerPage, state.itemsPerPage.toString());
1802
+ }
1803
+ // Sort by
1804
+ if (state.sortBy && state.sortBy !== 'relevance') {
1805
+ params.set(this.mapping.sortBy, state.sortBy);
1806
+ }
1807
+ // Refinements
1808
+ this.serializeRefinements(state.refinements, params);
1809
+ // Build URL
1810
+ const queryString = params.toString();
1811
+ const url = queryString ? `${this.basePath}?${queryString}` : this.basePath;
1812
+ // Update URL
1813
+ if (this.historyMode === 'push') {
1814
+ window.history.pushState({}, '', url);
1815
+ }
1816
+ else {
1817
+ window.history.replaceState({}, '', url);
1818
+ }
1819
+ log.verbose('URLRouter: Wrote state to URL', { url });
1820
+ }
1821
+ /**
1822
+ * Debounced write to URL
1823
+ */
1824
+ debouncedWriteToUrl(state) {
1825
+ if (this.debounceTimer) {
1826
+ clearTimeout(this.debounceTimer);
1827
+ }
1828
+ this.debounceTimer = setTimeout(() => {
1829
+ this.writeStateToUrl(state);
1830
+ }, this.debounceMs);
1831
+ }
1832
+ /**
1833
+ * Serialize refinements to URL params
1834
+ */
1835
+ serializeRefinements(refinements, params) {
1836
+ if (this.mapping.refinementSerializer) {
1837
+ const serialized = this.mapping.refinementSerializer(refinements);
1838
+ Object.entries(serialized).forEach(([key, value]) => {
1839
+ params.set(key, value);
1840
+ });
1841
+ return;
1842
+ }
1843
+ // Default serialization: field[]=value
1844
+ const byField = {};
1845
+ refinements.forEach(r => {
1846
+ const key = this.mapping.refinementPrefix + r.field;
1847
+ if (!byField[key]) {
1848
+ byField[key] = [];
1849
+ }
1850
+ byField[key].push(r.value);
1851
+ });
1852
+ Object.entries(byField).forEach(([field, values]) => {
1853
+ values.forEach(value => {
1854
+ params.append(field, value);
1855
+ });
1856
+ });
1857
+ }
1858
+ /**
1859
+ * Deserialize refinements from URL params
1860
+ */
1861
+ deserializeRefinements(params) {
1862
+ if (this.mapping.refinementDeserializer) {
1863
+ return this.mapping.refinementDeserializer(params);
1864
+ }
1865
+ // Default deserialization
1866
+ const refinements = [];
1867
+ const knownParams = [
1868
+ this.mapping.query,
1869
+ this.mapping.page,
1870
+ this.mapping.hitsPerPage,
1871
+ this.mapping.sortBy,
1872
+ ];
1873
+ params.forEach((value, key) => {
1874
+ // Skip known params
1875
+ if (knownParams.includes(key))
1876
+ return;
1877
+ // Remove prefix if present
1878
+ const field = this.mapping.refinementPrefix
1879
+ ? key.replace(new RegExp(`^${this.mapping.refinementPrefix}`), '')
1880
+ : key;
1881
+ refinements.push({ field, value });
1882
+ });
1883
+ return refinements;
1884
+ }
1885
+ /**
1886
+ * Manually update URL from current state
1887
+ */
1888
+ refresh() {
1889
+ if (this.writeToUrl) {
1890
+ this.writeStateToUrl(this.stateManager.getState());
1891
+ }
1892
+ }
1893
+ /**
1894
+ * Get current URL
1895
+ */
1896
+ getUrl() {
1897
+ if (typeof window === 'undefined')
1898
+ return '';
1899
+ return window.location.href;
1900
+ }
1901
+ /**
1902
+ * Generate shareable URL from state
1903
+ */
1904
+ createUrl(state) {
1905
+ const currentState = this.stateManager.getState();
1906
+ const mergedState = state ? { ...currentState, ...state } : currentState;
1907
+ const params = new URLSearchParams();
1908
+ if (mergedState.query) {
1909
+ params.set(this.mapping.query, mergedState.query);
1910
+ }
1911
+ if (mergedState.currentPage > 1) {
1912
+ params.set(this.mapping.page, mergedState.currentPage.toString());
1913
+ }
1914
+ if (mergedState.itemsPerPage !== 10) {
1915
+ params.set(this.mapping.hitsPerPage, mergedState.itemsPerPage.toString());
1916
+ }
1917
+ if (mergedState.sortBy && mergedState.sortBy !== 'relevance') {
1918
+ params.set(this.mapping.sortBy, mergedState.sortBy);
1919
+ }
1920
+ this.serializeRefinements(mergedState.refinements, params);
1921
+ const queryString = params.toString();
1922
+ const origin = typeof window !== 'undefined' ? window.location.origin : '';
1923
+ return queryString ? `${origin}${this.basePath}?${queryString}` : `${origin}${this.basePath}`;
1924
+ }
1925
+ /**
1926
+ * Destroy the router and cleanup
1927
+ */
1928
+ destroy() {
1929
+ if (this.unsubscribe) {
1930
+ this.unsubscribe();
1931
+ this.unsubscribe = null;
1932
+ }
1933
+ if (this.debounceTimer) {
1934
+ clearTimeout(this.debounceTimer);
1935
+ this.debounceTimer = null;
1936
+ }
1937
+ if (typeof window !== 'undefined') {
1938
+ window.removeEventListener('popstate', this.handlePopState);
1939
+ }
1940
+ log.info('URLRouter: Destroyed');
1941
+ }
1942
+ }
1943
+ /**
1944
+ * Create a URL router instance
1945
+ */
1946
+ function createURLRouter(config) {
1947
+ return new URLRouter(config);
1948
+ }
1949
+
1950
+ exports.SearchStateManager = SearchStateManager;
1951
+ exports.URLRouter = URLRouter;
1952
+ exports.announce = announce;
1953
+ exports.announceFilterChange = announceFilterChange;
1954
+ exports.announceResults = announceResults;
1955
+ exports.createFocusTrap = createFocusTrap;
1956
+ exports.createKeyboardShortcuts = createKeyboardShortcuts;
1957
+ exports.createLogger = createLogger;
1958
+ exports.createRovingTabIndex = createRovingTabIndex;
1959
+ exports.createTheme = createTheme;
1960
+ exports.createURLRouter = createURLRouter;
1961
+ exports.darkThemeVariables = darkThemeVariables;
1962
+ exports.extractField = extractField;
1963
+ exports.focusFirstError = focusFirstError;
1964
+ exports.formatPrice = formatPrice;
1965
+ exports.generateCompleteStylesheet = generateCompleteStylesheet;
1966
+ exports.generateId = generateId;
1967
+ exports.generateThemeStylesheet = generateThemeStylesheet;
1968
+ exports.getFocusableElements = getFocusableElements;
1969
+ exports.getHighlightedValue = getHighlightedValue;
1970
+ exports.getLogger = getLogger;
1971
+ exports.getNestedValue = getNestedValue$1;
1972
+ exports.getSnippetedValue = getSnippetedValue;
1973
+ exports.getTheme = getTheme;
1974
+ exports.handleKeyboardNavigation = handleKeyboardNavigation;
1975
+ exports.highContrastThemeVariables = highContrastThemeVariables;
1976
+ exports.highlightQuery = highlightQuery;
1977
+ exports.injectStyles = injectStyles;
1978
+ exports.lightThemeVariables = lightThemeVariables;
1979
+ exports.log = log;
1980
+ exports.mergeThemes = mergeThemes;
1981
+ exports.minimalThemeVariables = minimalThemeVariables;
1982
+ exports.parseHighlightedParts = parseHighlightedParts;
1983
+ exports.parseQueryHighlightParts = parseQueryHighlightParts;
1984
+ exports.prefersHighContrast = prefersHighContrast;
1985
+ exports.prefersReducedMotion = prefersReducedMotion;
1986
+ exports.setAriaDescribedBy = setAriaDescribedBy;
1987
+ exports.setDefaultLogger = setDefaultLogger;
1988
+ exports.setLogLevel = setLogLevel;
1989
+ exports.setLogPrefix = setLogPrefix;
1990
+ exports.setLogTimestamp = setLogTimestamp;
1991
+ exports.setTheme = setTheme;
1992
+ exports.stripHighlightTags = stripHighlightTags;
1993
+ exports.themeToCSSVariables = themeToCSSVariables;
1994
+ //# sourceMappingURL=index.js.map