bonsai-search 3.0.8

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.
@@ -0,0 +1,1698 @@
1
+ (function(exports, marked) {
2
+
3
+
4
+ //#region sdk.css?raw
5
+ var sdk_default = "/* Bonsai Search SDK Styles\n * Ported from React prototype design system\n * All classes prefixed with bonsai- for isolation\n */\n\n/* ============================================================================\n * CSS VARIABLES - Design Tokens\n * ============================================================================ */\n:root {\n /* Colors - Default theme */\n --bonsai-brand-color: #0a5b3b;\n --bonsai-text-color: #303030;\n --bonsai-suggestions-text-color: #303030;\n --bonsai-input-text-color: #303030;\n --bonsai-results-text-color: #303030;\n --bonsai-card-text-color: #303030;\n --bonsai-muted-color: #9ca3af;\n --bonsai-input-bg: #f5f5f5;\n --bonsai-card-bg: #f5f5f5;\n --bonsai-canvas-color: #fafafa;\n --bonsai-surface-color: #ffffff;\n --bonsai-border-color: rgba(0, 0, 0, 0.06);\n --bonsai-border-color-hover: rgba(0, 0, 0, 0.12);\n --bonsai-hover-bg: rgba(0, 0, 0, 0.04);\n --bonsai-suggestions-hover-bg: rgba(0, 0, 0, 0.04);\n --bonsai-error-bg: rgba(220, 53, 69, 0.1);\n --bonsai-error-color: #c82333;\n\n /* Spacing (4px base) */\n --bonsai-space-1: 0.25rem;\n /* 4px */\n --bonsai-space-2: 0.5rem;\n /* 8px */\n --bonsai-space-3: 0.75rem;\n /* 12px */\n --bonsai-space-4: 1rem;\n /* 16px */\n --bonsai-space-5: 1.25rem;\n /* 20px */\n --bonsai-space-6: 1.5rem;\n /* 24px */\n\n /* Typography */\n --bonsai-font-heading: system-ui, -apple-system, sans-serif;\n --bonsai-font-body: system-ui, -apple-system, sans-serif;\n --bonsai-font-mono: ui-monospace, monospace;\n --bonsai-font-size-xs: 0.75rem;\n /* 12px */\n --bonsai-font-size-sm: 0.875rem;\n /* 14px */\n --bonsai-font-size-base: 1rem;\n /* 16px */\n --bonsai-font-size-lg: 1.125rem;\n /* 18px */\n --bonsai-font-size-xl: 1.25rem;\n /* 20px */\n\n /* Border Radius */\n --bonsai-radius-none: 0;\n --bonsai-radius-sm: 0.25rem;\n /* 4px */\n --bonsai-radius-md: 0.375rem;\n /* 6px */\n --bonsai-radius-lg: 0.5rem;\n /* 8px */\n --bonsai-radius-xl: 0.75rem;\n /* 12px */\n --bonsai-radius-full: 9999px;\n\n /* Animation */\n --bonsai-duration-fast: 150ms;\n --bonsai-duration-base: 200ms;\n --bonsai-duration-slow: 300ms;\n --bonsai-easing: cubic-bezier(0, 0, 0.2, 1);\n\n /* Shadows */\n --bonsai-shadow-sm: 0 1px 2px 0 rgb(0 0 0 / 0.05);\n --bonsai-shadow-md: 0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1);\n --bonsai-shadow-lg: 0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1);\n\n /* Component Sizes */\n --bonsai-search-min-height: 3.5rem;\n /* 56px */\n --bonsai-search-max-width: 42rem;\n /* 672px */\n --bonsai-icon-size: 1.25rem;\n /* 20px */\n --bonsai-icon-size-sm: 1rem;\n /* 16px */\n}\n\n:host([data-theme=\"dark\"]) {\n --bonsai-text-color: #e5e7eb;\n --bonsai-suggestions-text-color: #e5e7eb;\n --bonsai-input-text-color: #e5e7eb;\n --bonsai-results-text-color: #e5e7eb;\n --bonsai-card-text-color: #e5e7eb;\n --bonsai-muted-color: #a1a1aa;\n --bonsai-input-bg: #1f2229;\n --bonsai-card-bg: #1f2229;\n --bonsai-canvas-color: #0f1115;\n --bonsai-surface-color: #181a20;\n --bonsai-border-color: rgba(255, 255, 255, 0.08);\n --bonsai-border-color-hover: rgba(255, 255, 255, 0.14);\n --bonsai-hover-bg: rgba(255, 255, 255, 0.06);\n --bonsai-suggestions-hover-bg: rgba(255, 255, 255, 0.06);\n --bonsai-error-bg: rgba(248, 113, 113, 0.15);\n --bonsai-error-color: #fca5a5;\n --bonsai-shadow-sm: 0 1px 2px 0 rgb(0 0 0 / 0.5);\n --bonsai-shadow-md: 0 4px 6px -1px rgb(0 0 0 / 0.55), 0 2px 4px -2px rgb(0 0 0 / 0.5);\n --bonsai-shadow-lg: 0 10px 15px -3px rgb(0 0 0 / 0.6), 0 4px 6px -4px rgb(0 0 0 / 0.55);\n}\n\n/* ============================================================================\n * BASE CONTAINER\n * ============================================================================ */\n.bonsai-search-container {\n max-width: var(--bonsai-search-max-width);\n font-family: var(--bonsai-font-heading);\n box-sizing: border-box;\n}\n\n.bonsai-search-container *,\n.bonsai-search-container *::before,\n.bonsai-search-container *::after {\n box-sizing: border-box;\n}\n\n.bonsai-search-wrapper {\n display: flex;\n flex-direction: column;\n gap: var(--bonsai-space-1);\n position: relative;\n}\n\n/* ============================================================================\n * SEARCH INPUT CONTAINER\n * ============================================================================ */\n.bonsai-search-bar {\n position: relative;\n z-index: 2;\n display: flex;\n align-items: center;\n min-height: var(--bonsai-search-min-height);\n background-color: var(--bonsai-input-bg);\n border-radius: var(--bonsai-radius-lg);\n transition: box-shadow var(--bonsai-duration-base) var(--bonsai-easing);\n}\n\n.bonsai-search-bar.bonsai-focused {\n box-shadow: 0 0 0 2px var(--bonsai-brand-color);\n}\n\n/* ============================================================================\n * SEARCH ICON (Left side)\n * ============================================================================ */\n.bonsai-search-icon {\n display: flex;\n align-items: center;\n justify-content: center;\n padding-left: var(--bonsai-space-4);\n padding-right: var(--bonsai-space-2);\n color: var(--bonsai-input-text-color, var(--bonsai-text-color));\n transition: color var(--bonsai-duration-base) var(--bonsai-easing);\n}\n\n.bonsai-search-bar:hover .bonsai-search-icon,\n.bonsai-search-bar.bonsai-focused .bonsai-search-icon {\n color: var(--bonsai-input-text-color, var(--bonsai-text-color));\n}\n\n.bonsai-search-icon svg {\n width: var(--bonsai-icon-size);\n height: var(--bonsai-icon-size);\n stroke: currentColor;\n stroke-width: 2;\n fill: none;\n}\n\n/* ============================================================================\n * INPUT FIELD\n * ============================================================================ */\n.bonsai-search-input {\n flex: 1;\n min-width: 0;\n padding: var(--bonsai-space-4) var(--bonsai-space-4) var(--bonsai-space-4) 0;\n border: none;\n background: transparent;\n font-family: var(--bonsai-font-body);\n font-size: var(--bonsai-font-size-base);\n color: var(--bonsai-input-text-color, var(--bonsai-text-color));\n outline: none;\n}\n\n.bonsai-search-input::placeholder {\n color: var(--bonsai-muted-color);\n}\n\n.bonsai-search-input:disabled {\n cursor: not-allowed;\n opacity: 0.7;\n}\n\n/* Webkit search clear button styling */\n.bonsai-search-input::-webkit-search-cancel-button {\n -webkit-appearance: none;\n appearance: none;\n width: var(--bonsai-icon-size-sm);\n height: var(--bonsai-icon-size-sm);\n cursor: pointer;\n background-color: var(--bonsai-muted-color);\n mask-image: url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='currentColor' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M18 6L6 18'%3E%3C/path%3E%3Cpath d='M6 6l12 12'%3E%3C/path%3E%3C/svg%3E\");\n -webkit-mask-image: url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='currentColor' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M18 6L6 18'%3E%3C/path%3E%3Cpath d='M6 6l12 12'%3E%3C/path%3E%3C/svg%3E\");\n mask-size: contain;\n -webkit-mask-size: contain;\n mask-repeat: no-repeat;\n -webkit-mask-repeat: no-repeat;\n mask-position: center;\n -webkit-mask-position: center;\n opacity: 0.7;\n transition: opacity var(--bonsai-duration-fast) var(--bonsai-easing);\n}\n\n.bonsai-search-input::-webkit-search-cancel-button:hover {\n opacity: 1;\n}\n\n/* ============================================================================\n * ACTION BUTTONS AREA (Right side)\n * ============================================================================ */\n.bonsai-search-actions {\n display: flex;\n align-items: center;\n gap: var(--bonsai-space-2);\n padding-right: var(--bonsai-space-3);\n flex: 0 0 auto;\n min-width: max-content;\n}\n\n/* Submit Button */\n.bonsai-submit-btn {\n display: none;\n align-items: center;\n justify-content: center;\n width: 2rem;\n height: 2rem;\n padding: var(--bonsai-space-2);\n border: none;\n border-radius: var(--bonsai-radius-lg);\n background-color: var(--bonsai-brand-color);\n cursor: pointer;\n touch-action: manipulation;\n flex-shrink: 0;\n transition:\n opacity var(--bonsai-duration-fast) var(--bonsai-easing),\n transform var(--bonsai-duration-fast) var(--bonsai-easing);\n}\n\n.bonsai-submit-btn.bonsai-visible {\n display: flex;\n}\n\n.bonsai-submit-btn:hover {\n opacity: 0.8;\n}\n\n.bonsai-submit-btn:active {\n transform: scale(0.95);\n}\n\n.bonsai-submit-btn svg {\n width: var(--bonsai-icon-size-sm);\n height: var(--bonsai-icon-size-sm);\n stroke: white;\n stroke-width: 2;\n fill: none;\n}\n\n/* Loading State */\n.bonsai-loading-state {\n display: none;\n align-items: center;\n gap: var(--bonsai-space-2);\n flex-shrink: 0;\n}\n\n.bonsai-loading-state.bonsai-visible {\n display: flex;\n}\n\n.bonsai-loading-text {\n position: relative;\n height: 1.25rem;\n overflow: hidden;\n min-width: 5rem;\n text-align: right;\n}\n\n.bonsai-loading-text-inner {\n display: flex;\n flex-direction: column;\n transition: transform var(--bonsai-duration-base) var(--bonsai-easing);\n}\n\n.bonsai-loading-text span {\n display: block;\n height: 1.25rem;\n line-height: 1.25rem;\n font-family: var(--bonsai-font-mono);\n font-size: var(--bonsai-font-size-sm);\n color: var(--bonsai-muted-color);\n white-space: nowrap;\n}\n\n/* Spinner */\n.bonsai-spinner {\n width: var(--bonsai-icon-size-sm);\n height: var(--bonsai-icon-size-sm);\n animation: bonsai-spin 1s linear infinite;\n transform-origin: center;\n}\n\n.bonsai-spinner svg {\n display: block;\n width: 100%;\n height: 100%;\n transform-origin: center;\n}\n\n.bonsai-spinner-track {\n stroke: var(--bonsai-muted-color);\n stroke-opacity: 0.25;\n}\n\n.bonsai-spinner-fill {\n fill: var(--bonsai-brand-color);\n}\n\n@keyframes bonsai-spin {\n from {\n transform: rotate(0deg);\n }\n\n to {\n transform: rotate(360deg);\n }\n}\n\n/* Done State */\n.bonsai-done-state {\n display: none;\n align-items: center;\n gap: var(--bonsai-space-2);\n flex-shrink: 0;\n}\n\n.bonsai-done-state.bonsai-visible {\n display: flex;\n}\n\n.bonsai-done-text {\n font-family: var(--bonsai-font-mono);\n font-size: var(--bonsai-font-size-sm);\n color: var(--bonsai-muted-color);\n}\n\n.bonsai-done-icon svg {\n width: var(--bonsai-icon-size-sm);\n height: var(--bonsai-icon-size-sm);\n stroke: var(--bonsai-text-color);\n stroke-width: 2;\n fill: none;\n}\n\n/* Reset/Retry Button */\n.bonsai-reset-btn {\n display: none;\n align-items: center;\n gap: var(--bonsai-space-2);\n padding: 0;\n border: none;\n background: transparent;\n cursor: pointer;\n touch-action: manipulation;\n flex-shrink: 0;\n transition: opacity var(--bonsai-duration-fast) var(--bonsai-easing);\n}\n\n.bonsai-reset-btn.bonsai-visible {\n display: flex;\n}\n\n.bonsai-reset-btn:hover {\n opacity: 0.7;\n}\n\n.bonsai-reset-text {\n font-family: var(--bonsai-font-mono);\n font-size: var(--bonsai-font-size-sm);\n color: var(--bonsai-muted-color);\n}\n\n.bonsai-reset-icon {\n transition: transform var(--bonsai-duration-slow) var(--bonsai-easing);\n transform-origin: center;\n}\n\n.bonsai-reset-btn:hover .bonsai-reset-icon {\n transform: rotate(-180deg);\n}\n\n.bonsai-reset-icon svg {\n width: var(--bonsai-icon-size-sm);\n height: var(--bonsai-icon-size-sm);\n stroke: var(--bonsai-muted-color);\n stroke-width: 2;\n fill: none;\n transform-origin: center;\n display: block;\n}\n\n/* ============================================================================\n * SUGGESTIONS DROPDOWN\n * ============================================================================ */\n.bonsai-suggestions {\n position: absolute;\n top: calc(100% + var(--bonsai-space-1));\n left: 0;\n right: 0;\n background-color: var(--bonsai-input-bg);\n border-radius: var(--bonsai-radius-lg);\n box-shadow: var(--bonsai-shadow-lg);\n overflow: hidden;\n z-index: 10;\n opacity: 0;\n transform: translateY(-10px);\n pointer-events: none;\n transition:\n opacity var(--bonsai-duration-fast) var(--bonsai-easing),\n transform var(--bonsai-duration-fast) var(--bonsai-easing);\n}\n\n.bonsai-suggestions.bonsai-visible {\n opacity: 1;\n transform: translateY(0);\n pointer-events: auto;\n}\n\n.bonsai-suggestion-item {\n display: flex;\n align-items: center;\n justify-content: space-between;\n width: 100%;\n padding: var(--bonsai-space-2) var(--bonsai-space-4);\n border: none;\n background: transparent;\n font-family: var(--bonsai-font-body);\n font-size: var(--bonsai-font-size-sm);\n color: var(--bonsai-suggestions-text-color, var(--bonsai-text-color));\n text-align: left;\n cursor: pointer;\n transition: background-color var(--bonsai-duration-fast) var(--bonsai-easing);\n}\n\n.bonsai-suggestion-item:hover {\n background-color: var(--bonsai-suggestions-hover-bg, var(--bonsai-hover-bg));\n}\n\n.bonsai-suggestion-arrow {\n opacity: 0;\n visibility: hidden;\n transition:\n opacity var(--bonsai-duration-fast) var(--bonsai-easing),\n visibility 0s linear var(--bonsai-duration-fast);\n}\n\n.bonsai-suggestion-item:hover .bonsai-suggestion-arrow {\n opacity: 1;\n visibility: visible;\n transition-delay: 0s;\n}\n\n.bonsai-suggestion-arrow svg {\n width: var(--bonsai-icon-size-sm);\n height: var(--bonsai-icon-size-sm);\n stroke: var(--bonsai-muted-color);\n stroke-width: 2;\n fill: none;\n}\n\n/* ============================================================================\n * POWERED BY BRANDING\n * ============================================================================ */\n.bonsai-powered-by {\n position: relative;\n z-index: 1;\n height: 1.5rem;\n margin-top: var(--bonsai-space-1);\n text-align: right;\n font-family: var(--bonsai-font-mono);\n font-size: var(--bonsai-font-size-xs);\n color: var(--bonsai-muted-color);\n opacity: 1;\n transition: opacity var(--bonsai-duration-base) var(--bonsai-easing);\n}\n\n.bonsai-powered-by.bonsai-hidden {\n opacity: 0;\n}\n\n.bonsai-powered-by a {\n color: var(--bonsai-brand-color);\n text-decoration: none;\n display: inline-flex;\n align-items: center;\n gap: 0;\n transition: gap var(--bonsai-duration-base) var(--bonsai-easing);\n}\n\n.bonsai-powered-by a:hover {\n gap: var(--bonsai-space-1);\n}\n\n.bonsai-powered-by-dot {\n display: inline-block;\n width: 0;\n height: 0.5rem;\n border-radius: var(--bonsai-radius-full);\n background-color: var(--bonsai-brand-color);\n transform: scale(0);\n transition:\n width var(--bonsai-duration-base) var(--bonsai-easing),\n transform var(--bonsai-duration-base) var(--bonsai-easing);\n}\n\n.bonsai-powered-by a:hover .bonsai-powered-by-dot {\n width: 0.5rem;\n transform: scale(1);\n}\n\n/* ============================================================================\n * RESULTS SECTION\n * ============================================================================ */\n.bonsai-results {\n margin-top: var(--bonsai-space-2);\n padding: var(--bonsai-space-4);\n opacity: 0;\n transform: translateY(20px);\n transition:\n opacity var(--bonsai-duration-slow) var(--bonsai-easing),\n transform var(--bonsai-duration-slow) var(--bonsai-easing);\n}\n\n.bonsai-results.bonsai-visible {\n opacity: 1;\n transform: translateY(0);\n}\n\n/* AI Summary / Recommendation Text */\n.bonsai-summary {\n margin-bottom: var(--bonsai-space-6);\n}\n\n.bonsai-summary-text {\n font-family: var(--bonsai-font-body);\n font-size: var(--bonsai-font-size-base);\n line-height: 1.6;\n color: var(--bonsai-results-text-color, var(--bonsai-text-color));\n margin: 0;\n}\n\n/* Markdown formatting within summary text */\n.bonsai-summary-text h1,\n.bonsai-summary-text h2,\n.bonsai-summary-text h3,\n.bonsai-summary-text h4,\n.bonsai-summary-text h5,\n.bonsai-summary-text h6 {\n font-family: var(--bonsai-font-heading);\n font-weight: 600;\n color: var(--bonsai-results-text-color, var(--bonsai-text-color));\n margin: var(--bonsai-space-4) 0 var(--bonsai-space-2) 0;\n line-height: 1.25;\n}\n\n.bonsai-summary-text h1 {\n font-size: var(--bonsai-font-size-xl);\n}\n\n.bonsai-summary-text h2 {\n font-size: var(--bonsai-font-size-lg);\n}\n\n.bonsai-summary-text h3 {\n font-size: var(--bonsai-font-size-base);\n}\n\n.bonsai-summary-text h4,\n.bonsai-summary-text h5,\n.bonsai-summary-text h6 {\n font-size: var(--bonsai-font-size-sm);\n}\n\n.bonsai-summary-text p {\n margin: var(--bonsai-space-2) 0;\n}\n\n.bonsai-summary-text p:first-child {\n margin-top: 0;\n}\n\n.bonsai-summary-text p:last-child {\n margin-bottom: 0;\n}\n\n.bonsai-summary-text strong {\n font-weight: 600;\n color: var(--bonsai-results-text-color, var(--bonsai-text-color));\n}\n\n.bonsai-summary-text em {\n font-style: italic;\n}\n\n.bonsai-summary-text ul,\n.bonsai-summary-text ol {\n margin: var(--bonsai-space-2) 0;\n padding-left: var(--bonsai-space-5);\n}\n\n.bonsai-summary-text li {\n margin: var(--bonsai-space-1) 0;\n}\n\n.bonsai-summary-text a {\n color: var(--bonsai-brand-color);\n text-decoration: underline;\n text-underline-offset: 2px;\n}\n\n.bonsai-summary-text a:hover {\n text-decoration: none;\n}\n\n/* Results Header */\n.bonsai-results-header {\n font-family: var(--bonsai-font-heading);\n font-size: var(--bonsai-font-size-sm);\n font-weight: 500;\n color: var(--bonsai-muted-color);\n text-transform: uppercase;\n letter-spacing: 0.05em;\n margin: 0 0 var(--bonsai-space-3) 0;\n /* border-top: 1px solid rgba(0, 0, 0, 0.06); */\n}\n\n/* Results Grid */\n.bonsai-results-grid {\n display: grid;\n grid-template-columns: repeat(var(--bonsai-results-columns, 3), 1fr);\n gap: var(--bonsai-space-4);\n}\n\n@media (max-width: 640px) {\n .bonsai-results-grid {\n grid-template-columns: repeat(2, 1fr);\n }\n}\n\n@media (max-width: 400px) {\n .bonsai-results-grid {\n grid-template-columns: 1fr;\n }\n}\n\n/* ============================================================================\n * RESULT CARD\n * ============================================================================ */\n.bonsai-result-card {\n display: flex;\n flex-direction: column;\n overflow: hidden;\n border-radius: var(--bonsai-radius-md);\n border: 1px solid var(--bonsai-border-color);\n background-color: var(--bonsai-card-bg);\n text-decoration: none;\n cursor: pointer;\n opacity: 0;\n transform: translateY(10px);\n transition:\n opacity var(--bonsai-duration-base) var(--bonsai-easing),\n transform var(--bonsai-duration-base) var(--bonsai-easing),\n border-color var(--bonsai-duration-fast) var(--bonsai-easing);\n}\n\n.bonsai-result-card.bonsai-animate-in {\n opacity: 1;\n transform: translateY(0);\n}\n\n.bonsai-result-card:hover {\n border-color: var(--bonsai-border-color-hover);\n}\n\n/* Card Image */\n.bonsai-result-image {\n width: 100%;\n aspect-ratio: 1;\n object-fit: var(--bonsai-image-object-fit, cover);\n background-color: var(--bonsai-surface-color);\n}\n\n.bonsai-result-image-placeholder {\n width: 100%;\n aspect-ratio: 1;\n background-color: var(--bonsai-surface-color);\n opacity: 0.2;\n}\n\n/* Card Content */\n.bonsai-result-content {\n flex: 1;\n min-width: 0;\n padding: var(--bonsai-space-3) var(--bonsai-space-4);\n transition: background-color var(--bonsai-duration-fast) var(--bonsai-easing);\n}\n\n.bonsai-result-card:hover .bonsai-result-content {\n background-color: var(--bonsai-hover-bg);\n}\n\n.bonsai-result-title {\n font-family: var(--bonsai-font-heading);\n font-size: var(--bonsai-font-size-base);\n font-weight: 500;\n line-height: 1.25;\n color: var(--bonsai-card-text-color, var(--bonsai-text-color));\n margin: 0;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n.bonsai-result-price-container {\n display: flex;\n align-items: center;\n gap: var(--bonsai-space-2);\n margin: var(--bonsai-space-1) 0 0 0;\n}\n\n.bonsai-result-caption {\n font-family: var(--bonsai-font-body);\n font-size: var(--bonsai-font-size-sm);\n line-height: 1.5;\n color: var(--bonsai-card-text-color, var(--bonsai-text-color));\n opacity: 0.75;\n margin: var(--bonsai-space-1) 0 0 0;\n display: -webkit-box;\n -webkit-line-clamp: 2;\n -webkit-box-orient: vertical;\n overflow: hidden;\n}\n\n.bonsai-result-price {\n font-family: var(--bonsai-font-body);\n font-size: var(--bonsai-font-size-sm);\n font-weight: 600;\n line-height: 1.5;\n color: var(--bonsai-card-text-color, var(--bonsai-text-color));\n}\n\n.bonsai-result-compare-at {\n font-family: var(--bonsai-font-body);\n font-size: var(--bonsai-font-size-sm);\n line-height: 1.5;\n color: var(--bonsai-card-text-color, var(--bonsai-text-color));\n opacity: 0.5;\n margin: 0;\n text-decoration: line-through;\n}\n\n/* ============================================================================\n * RECOMMENDATIONS LOADING INDICATOR\n * ============================================================================ */\n.bonsai-recommendations-loading {\n display: flex;\n align-items: center;\n gap: var(--bonsai-space-3);\n padding: var(--bonsai-space-4);\n margin-bottom: var(--bonsai-space-4);\n background-color: var(--bonsai-input-bg);\n border-radius: var(--bonsai-radius-lg);\n}\n\n.bonsai-recommendations-loading-spinner {\n width: var(--bonsai-icon-size);\n height: var(--bonsai-icon-size);\n animation: bonsai-spin 1s linear infinite;\n}\n\n.bonsai-recommendations-loading-spinner svg {\n width: 100%;\n height: 100%;\n}\n\n.bonsai-recommendations-loading-text {\n font-family: var(--bonsai-font-body);\n font-size: var(--bonsai-font-size-sm);\n color: var(--bonsai-muted-color);\n}\n\n/* ============================================================================\n * RECOMMENDATION PRODUCTS (existing SDK feature)\n * ============================================================================ */\n.bonsai-recommendation-products {\n margin-top: var(--bonsai-space-4);\n padding-top: var(--bonsai-space-4);\n}\n\n.bonsai-recommendation-products-header {\n font-family: var(--bonsai-font-heading);\n font-size: var(--bonsai-font-size-xl);\n font-weight: 500;\n color: var(--bonsai-results-text-color, var(--bonsai-text-color));\n margin: 0 0 var(--bonsai-space-3) 0;\n}\n\n/* ============================================================================\n * EMPTY & ERROR STATES\n * ============================================================================ */\n.bonsai-empty-state {\n text-align: center;\n padding: var(--bonsai-space-6);\n font-family: var(--bonsai-font-body);\n font-size: var(--bonsai-font-size-sm);\n color: var(--bonsai-muted-color);\n}\n\n.bonsai-error {\n padding: var(--bonsai-space-4);\n border-radius: var(--bonsai-radius-lg);\n background-color: var(--bonsai-error-bg);\n color: var(--bonsai-error-color);\n font-family: var(--bonsai-font-body);\n font-size: var(--bonsai-font-size-sm);\n}\n\n/* ============================================================================\n * UTILITY CLASSES\n * ============================================================================ */\n.bonsai-sr-only {\n position: absolute;\n width: 1px;\n height: 1px;\n padding: 0;\n margin: -1px;\n overflow: hidden;\n clip: rect(0, 0, 0, 0);\n white-space: nowrap;\n border: 0;\n}\n";
6
+
7
+ //#endregion
8
+ //#region src/bonsai-search-webcomponent.ts
9
+ const SDK_CSS$1 = sdk_default || "";
10
+ if (typeof window !== "undefined" && !SDK_CSS$1) console.warn("BonsaiSearch: CSS not loaded from file, will use fallback");
11
+ /**
12
+ * Web Component for Bonsai Search
13
+ * Fully encapsulated with Shadow DOM, no inherited styles
14
+ *
15
+ * Usage:
16
+ * <bonsai-search
17
+ * api-key="your-api-key"
18
+ * base-url="https://api.hibonsai.com/rest/search/v2/"
19
+ * placeholder="Describe what you're looking for..."
20
+ * suggestions='["suggestion 1", "suggestion 2"]'
21
+ * max-results="20"
22
+ * render-price
23
+ * brand-color="#0A5B3B"
24
+ * text-color="#303030"
25
+ * muted-color="#9CA3AF"
26
+ * input-bg="#f5f5f5"
27
+ * card-bg="#f5f5f5"
28
+ * image-object-fit="cover"
29
+ * featured-items-label="Featured Items"
30
+ * more-items-label="More Items"
31
+ * items-label="Items">
32
+ * </bonsai-search>
33
+ */
34
+ var BonsaiSearchElement = class extends HTMLElement {
35
+ search = null;
36
+ shadow;
37
+ container = null;
38
+ static get observedAttributes() {
39
+ return [
40
+ "api-key",
41
+ "base-url",
42
+ "placeholder",
43
+ "suggestions",
44
+ "max-results",
45
+ "timeout-ms",
46
+ "render-price",
47
+ "render-price-with-title",
48
+ "brand-color",
49
+ "text-color",
50
+ "suggestions-text-color",
51
+ "input-text-color",
52
+ "results-text-color",
53
+ "card-text-color",
54
+ "results-columns",
55
+ "suggestions-hover-bg",
56
+ "muted-color",
57
+ "input-bg",
58
+ "card-bg",
59
+ "surface-color",
60
+ "canvas-color",
61
+ "image-object-fit",
62
+ "markdown",
63
+ "theme",
64
+ "featured-items-label",
65
+ "more-items-label",
66
+ "items-label"
67
+ ];
68
+ }
69
+ constructor() {
70
+ super();
71
+ this.shadow = this.attachShadow({ mode: "closed" });
72
+ }
73
+ connectedCallback() {
74
+ this.render();
75
+ this.initialize();
76
+ }
77
+ disconnectedCallback() {
78
+ this.search?.destroy();
79
+ this.search = null;
80
+ }
81
+ attributeChangedCallback(name, oldValue, newValue) {
82
+ if (oldValue === newValue || !this.search) return;
83
+ if ([
84
+ "api-key",
85
+ "base-url",
86
+ "placeholder",
87
+ "suggestions",
88
+ "markdown",
89
+ "featured-items-label",
90
+ "more-items-label",
91
+ "items-label"
92
+ ].includes(name)) {
93
+ this.disconnectedCallback();
94
+ this.connectedCallback();
95
+ } else this.updateStyles();
96
+ }
97
+ render() {
98
+ let css = SDK_CSS$1 || "";
99
+ if (!css || css.trim().length < 100) {
100
+ console.warn("BonsaiSearch: CSS not loaded, using fallback");
101
+ css = this.getDefaultCSS();
102
+ }
103
+ css = css.replace(/:root\s*\{/g, ":host {");
104
+ const style = document.createElement("style");
105
+ style.textContent = css;
106
+ this.shadow.appendChild(style);
107
+ this.container = document.createElement("div");
108
+ this.container.style.width = "100%";
109
+ this.shadow.appendChild(this.container);
110
+ this.updateStyles();
111
+ }
112
+ updateStyles() {
113
+ const themeAttr = this.getAttribute("theme") || "light";
114
+ const normalizedTheme = themeAttr === "dark" || themeAttr === "auto" ? themeAttr : "light";
115
+ const prefersDark = typeof window !== "undefined" && window.matchMedia && window.matchMedia("(prefers-color-scheme: dark)").matches;
116
+ const resolvedTheme = normalizedTheme === "auto" ? prefersDark ? "dark" : "light" : normalizedTheme;
117
+ this.setAttribute("data-theme", resolvedTheme);
118
+ const imageObjectFitAttr = this.getAttribute("image-object-fit");
119
+ const validObjectFit = imageObjectFitAttr === "contain" ? "contain" : imageObjectFitAttr === "cover" ? "cover" : null;
120
+ const setOrClear = (name, value) => {
121
+ if (value && value.trim().length > 0) this.style.setProperty(name, value);
122
+ else this.style.removeProperty(name);
123
+ };
124
+ setOrClear("--bonsai-brand-color", this.getAttribute("brand-color"));
125
+ setOrClear("--bonsai-text-color", this.getAttribute("text-color"));
126
+ const suggestionsTextColor = this.getAttribute("suggestions-text-color");
127
+ const inputTextColor = this.getAttribute("input-text-color");
128
+ setOrClear("--bonsai-suggestions-text-color", suggestionsTextColor || inputTextColor);
129
+ setOrClear("--bonsai-input-text-color", this.getAttribute("input-text-color"));
130
+ setOrClear("--bonsai-results-text-color", this.getAttribute("results-text-color"));
131
+ setOrClear("--bonsai-card-text-color", this.getAttribute("card-text-color"));
132
+ setOrClear("--bonsai-results-columns", this.getAttribute("results-columns"));
133
+ setOrClear("--bonsai-suggestions-hover-bg", this.getAttribute("suggestions-hover-bg"));
134
+ setOrClear("--bonsai-muted-color", this.getAttribute("muted-color"));
135
+ setOrClear("--bonsai-input-bg", this.getAttribute("input-bg"));
136
+ setOrClear("--bonsai-card-bg", this.getAttribute("card-bg"));
137
+ setOrClear("--bonsai-surface-color", this.getAttribute("surface-color"));
138
+ setOrClear("--bonsai-canvas-color", this.getAttribute("canvas-color"));
139
+ setOrClear("--bonsai-image-object-fit", validObjectFit);
140
+ }
141
+ initialize() {
142
+ if (!this.container) return;
143
+ const apiKey = this.getAttribute("api-key");
144
+ if (!apiKey) {
145
+ console.error("bonsai-search: api-key attribute is required");
146
+ return;
147
+ }
148
+ const suggestionsAttr = this.getAttribute("suggestions");
149
+ let suggestions = [];
150
+ if (suggestionsAttr) try {
151
+ suggestions = JSON.parse(suggestionsAttr);
152
+ } catch (e) {
153
+ console.warn("bonsai-search: Invalid suggestions JSON, using empty array");
154
+ }
155
+ const imageObjectFit = this.getAttribute("image-object-fit") === "contain" ? "contain" : "cover";
156
+ this.search = new BonsaiSearch({
157
+ container: this.container,
158
+ apiKey,
159
+ baseUrl: this.getAttribute("base-url") || void 0,
160
+ placeholder: this.getAttribute("placeholder") || "Describe what you're looking for...",
161
+ suggestions,
162
+ debounceMs: 0,
163
+ maxResults: this.getAttribute("max-results") ? parseInt(this.getAttribute("max-results")) : 50,
164
+ timeoutMs: this.getAttribute("timeout-ms") ? parseInt(this.getAttribute("timeout-ms")) : 3e4,
165
+ renderPrice: this.hasAttribute("render-price"),
166
+ renderPriceWithTitle: this.getAttribute("render-price-with-title") !== "false",
167
+ imageObjectFit,
168
+ markdown: this.hasAttribute("markdown"),
169
+ featuredItemsLabel: this.getAttribute("featured-items-label") || void 0,
170
+ moreItemsLabel: this.getAttribute("more-items-label") || void 0,
171
+ itemsLabel: this.getAttribute("items-label") || void 0,
172
+ onPrice: (result, price) => {
173
+ return price || "";
174
+ },
175
+ onSearch: (query) => {
176
+ this.dispatchEvent(new CustomEvent("search", {
177
+ detail: { query },
178
+ bubbles: true,
179
+ composed: true
180
+ }));
181
+ },
182
+ onResults: (results) => {
183
+ this.dispatchEvent(new CustomEvent("results", {
184
+ detail: { results },
185
+ bubbles: true,
186
+ composed: true
187
+ }));
188
+ },
189
+ onAi: (data) => {
190
+ this.dispatchEvent(new CustomEvent("ai", {
191
+ detail: data,
192
+ bubbles: true,
193
+ composed: true
194
+ }));
195
+ },
196
+ onError: (error) => {
197
+ this.dispatchEvent(new CustomEvent("error", {
198
+ detail: { error: error.message },
199
+ bubbles: true,
200
+ composed: true
201
+ }));
202
+ }
203
+ });
204
+ }
205
+ setSuggestion(suggestion) {
206
+ this.search?.setSuggestion(suggestion);
207
+ }
208
+ clear() {
209
+ this.search?.clear();
210
+ }
211
+ focus() {
212
+ this.search?.focus();
213
+ }
214
+ getDefaultCSS() {
215
+ return `
216
+ :host {
217
+ display: block;
218
+ width: 100%;
219
+ --bonsai-brand-color: #0A5B3B;
220
+ --bonsai-text-color: #303030;
221
+ --bonsai-suggestions-text-color: #303030;
222
+ --bonsai-input-text-color: #303030;
223
+ --bonsai-results-text-color: #303030;
224
+ --bonsai-card-text-color: #303030;
225
+ --bonsai-suggestions-hover-bg: rgba(0, 0, 0, 0.04);
226
+ --bonsai-muted-color: #9CA3AF;
227
+ --bonsai-input-bg: #f5f5f5;
228
+ --bonsai-card-bg: #f5f5f5;
229
+ --bonsai-surface-color: #ffffff;
230
+ --bonsai-canvas-color: #fafafa;
231
+ --bonsai-image-object-fit: cover;
232
+ --bonsai-border-color: rgba(0, 0, 0, 0.06);
233
+ --bonsai-border-color-hover: rgba(0, 0, 0, 0.12);
234
+ --bonsai-hover-bg: rgba(0, 0, 0, 0.04);
235
+ --bonsai-error-bg: rgba(220, 53, 69, 0.1);
236
+ --bonsai-error-color: #c82333;
237
+ }
238
+
239
+ :host([data-theme="dark"]) {
240
+ --bonsai-text-color: #e5e7eb;
241
+ --bonsai-suggestions-text-color: #e5e7eb;
242
+ --bonsai-input-text-color: #e5e7eb;
243
+ --bonsai-results-text-color: #e5e7eb;
244
+ --bonsai-card-text-color: #e5e7eb;
245
+ --bonsai-suggestions-hover-bg: rgba(255, 255, 255, 0.06);
246
+ --bonsai-muted-color: #a1a1aa;
247
+ --bonsai-input-bg: #1f2229;
248
+ --bonsai-card-bg: #1f2229;
249
+ --bonsai-surface-color: #181a20;
250
+ --bonsai-canvas-color: #0f1115;
251
+ --bonsai-border-color: rgba(255, 255, 255, 0.08);
252
+ --bonsai-border-color-hover: rgba(255, 255, 255, 0.14);
253
+ --bonsai-hover-bg: rgba(255, 255, 255, 0.06);
254
+ --bonsai-error-bg: rgba(248, 113, 113, 0.15);
255
+ --bonsai-error-color: #fca5a5;
256
+ }
257
+ `;
258
+ }
259
+ };
260
+ if (typeof window !== "undefined" && !customElements.get("bonsai-search")) customElements.define("bonsai-search", BonsaiSearchElement);
261
+
262
+ //#endregion
263
+ //#region src/searchbar.ts
264
+ const VERSION$1 = "3.0.8";
265
+ const ICONS$1 = {
266
+ search: `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
267
+ <circle cx="11" cy="11" r="8"></circle>
268
+ <path d="m21 21-4.35-4.35"></path>
269
+ </svg>`,
270
+ arrowRight: `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
271
+ <path d="M5 12h14"></path>
272
+ <path d="m12 5 7 7-7 7"></path>
273
+ </svg>`,
274
+ close: `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
275
+ <path d="M18 6L6 18"></path>
276
+ <path d="M6 6l12 12"></path>
277
+ </svg>`
278
+ };
279
+ const SEARCHBAR_STYLES = `
280
+ @keyframes bonsai-searchbar-fade-in {
281
+ from { opacity: 0; transform: translateY(-10px); }
282
+ to { opacity: 1; transform: translateY(0); }
283
+ }
284
+ @keyframes bonsai-searchbar-scale-in {
285
+ from { opacity: 0; transform: scale(0.8); }
286
+ to { opacity: 1; transform: scale(1); }
287
+ }
288
+ .bonsai-searchbar-container {
289
+ margin: 24px auto;
290
+ font-family: system-ui, -apple-system, sans-serif;
291
+ }
292
+ .bonsai-searchbar-wrapper {
293
+ position: relative;
294
+ }
295
+ .bonsai-searchbar {
296
+ display: flex;
297
+ align-items: center;
298
+ min-height: 56px;
299
+ position: relative;
300
+ z-index: 2;
301
+ background-color: var(--bonsai-input-bg);
302
+ border-radius: 8px;
303
+ transition: box-shadow 200ms ease-out;
304
+ }
305
+ .bonsai-searchbar.bonsai-searchbar-focused {
306
+ box-shadow: 0 0 0 2px var(--bonsai-brand-color);
307
+ }
308
+ .bonsai-searchbar-icon {
309
+ display: flex;
310
+ align-items: center;
311
+ justify-content: center;
312
+ padding-left: 16px;
313
+ padding-right: 8px;
314
+ color: var(--bonsai-muted-color);
315
+ transition: color 200ms ease-out;
316
+ }
317
+ .bonsai-searchbar-icon svg {
318
+ width: 20px;
319
+ height: 20px;
320
+ }
321
+ .bonsai-searchbar:hover .bonsai-searchbar-icon,
322
+ .bonsai-searchbar.bonsai-searchbar-focused .bonsai-searchbar-icon {
323
+ color: var(--bonsai-input-text-color, var(--bonsai-text-color));
324
+ }
325
+ .bonsai-searchbar-input {
326
+ flex: 1;
327
+ min-width: 0;
328
+ padding: 16px 16px 16px 0;
329
+ border: none;
330
+ background: transparent;
331
+ font-family: inherit;
332
+ font-size: 16px;
333
+ color: var(--bonsai-input-text-color, var(--bonsai-text-color));
334
+ outline: none;
335
+ }
336
+ .bonsai-searchbar-input::placeholder {
337
+ color: var(--bonsai-muted-color);
338
+ }
339
+ .bonsai-searchbar-actions {
340
+ display: flex;
341
+ align-items: center;
342
+ gap: 8px;
343
+ padding-right: 12px;
344
+ flex: 0 0 auto;
345
+ min-width: max-content;
346
+ }
347
+ .bonsai-searchbar-submit {
348
+ display: none;
349
+ align-items: center;
350
+ justify-content: center;
351
+ width: 32px;
352
+ height: 32px;
353
+ padding: 8px;
354
+ border: none;
355
+ border-radius: 8px;
356
+ background-color: var(--bonsai-brand-color);
357
+ cursor: pointer;
358
+ touch-action: manipulation;
359
+ flex-shrink: 0;
360
+ color: white;
361
+ animation: bonsai-searchbar-scale-in 0.2s ease-out forwards;
362
+ }
363
+ .bonsai-searchbar-submit.bonsai-searchbar-visible {
364
+ display: flex;
365
+ }
366
+ .bonsai-searchbar-submit svg {
367
+ width: 16px;
368
+ height: 16px;
369
+ }
370
+ .bonsai-searchbar-close {
371
+ display: none;
372
+ align-items: center;
373
+ justify-content: center;
374
+ width: 32px;
375
+ height: 32px;
376
+ padding: 8px;
377
+ border: none;
378
+ border-radius: 8px;
379
+ background: transparent;
380
+ cursor: pointer;
381
+ touch-action: manipulation;
382
+ flex-shrink: 0;
383
+ color: var(--bonsai-muted-color);
384
+ transition: color 200ms ease-out;
385
+ }
386
+ .bonsai-searchbar-close.bonsai-searchbar-visible {
387
+ display: flex;
388
+ }
389
+ .bonsai-searchbar-close:hover {
390
+ color: var(--bonsai-text-color);
391
+ }
392
+ .bonsai-searchbar-close svg {
393
+ width: 16px;
394
+ height: 16px;
395
+ }
396
+ .bonsai-searchbar-suggestions {
397
+ position: absolute;
398
+ top: calc(100% + 4px);
399
+ left: 0;
400
+ right: 0;
401
+ background-color: var(--bonsai-input-bg);
402
+ border-radius: 8px;
403
+ box-shadow: 0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1);
404
+ overflow: hidden;
405
+ z-index: 10;
406
+ display: none;
407
+ animation: bonsai-searchbar-fade-in 0.2s ease-out forwards;
408
+ }
409
+ .bonsai-searchbar-suggestions.bonsai-searchbar-visible {
410
+ display: block;
411
+ }
412
+ .bonsai-searchbar-suggestion {
413
+ display: flex;
414
+ align-items: center;
415
+ justify-content: space-between;
416
+ width: 100%;
417
+ padding: 8px 16px;
418
+ border: none;
419
+ font-family: inherit;
420
+ font-size: 14px;
421
+ color: var(--bonsai-suggestions-text-color, var(--bonsai-text-color));
422
+ text-align: left;
423
+ background-color: var(--bonsai-input-bg);
424
+ cursor: pointer;
425
+ transition: background-color 150ms ease-out;
426
+ }
427
+ .bonsai-searchbar-suggestion:hover {
428
+ background-color: var(--bonsai-suggestions-hover-bg, rgba(0, 0, 0, 0.04));
429
+ }
430
+ .bonsai-searchbar-suggestion-arrow {
431
+ color: var(--bonsai-muted-color);
432
+ opacity: 0;
433
+ visibility: hidden;
434
+ transition: opacity 150ms ease-out, visibility 0s linear 150ms;
435
+ }
436
+ .bonsai-searchbar-suggestion:hover .bonsai-searchbar-suggestion-arrow {
437
+ opacity: 1;
438
+ visibility: visible;
439
+ transition-delay: 0s;
440
+ }
441
+ .bonsai-searchbar-suggestion-arrow svg {
442
+ width: 16px;
443
+ height: 16px;
444
+ }
445
+ .bonsai-searchbar-powered {
446
+ position: relative;
447
+ z-index: 1;
448
+ height: 24px;
449
+ margin-top: 4px;
450
+ text-align: right;
451
+ font-family: ui-monospace, monospace;
452
+ font-size: 12px;
453
+ color: var(--bonsai-muted-color);
454
+ }
455
+ .bonsai-searchbar-powered-link {
456
+ color: var(--bonsai-brand-color);
457
+ text-decoration: none;
458
+ display: inline-flex;
459
+ align-items: center;
460
+ gap: 0;
461
+ transition: gap 0.2s ease-out;
462
+ }
463
+ .bonsai-searchbar-powered-link:hover {
464
+ gap: 4px;
465
+ }
466
+ .bonsai-searchbar-powered-dot {
467
+ display: inline-block;
468
+ height: 8px;
469
+ width: 0;
470
+ border-radius: 50%;
471
+ background-color: var(--bonsai-brand-color);
472
+ transform: scale(0);
473
+ transition: transform 0.2s ease-out, width 0.2s ease-out;
474
+ }
475
+ .bonsai-searchbar-powered-link:hover .bonsai-searchbar-powered-dot {
476
+ transform: scale(1);
477
+ width: 8px;
478
+ }
479
+ `;
480
+ var BonsaiSearchBar = class {
481
+ config;
482
+ state = "default";
483
+ container;
484
+ searchBar = null;
485
+ searchInput = null;
486
+ submitBtn = null;
487
+ suggestionsDropdown = null;
488
+ query = "";
489
+ stylesInjected = false;
490
+ constructor(config) {
491
+ this.container = config.container;
492
+ const inputBg = config.inputBg || config.inputBackground || "#f5f5f5";
493
+ this.config = {
494
+ container: config.container,
495
+ searchPath: config.searchPath || "/ai-search",
496
+ placeholder: config.placeholder || "Describe what you're looking for...",
497
+ suggestions: config.suggestions || [],
498
+ brandColor: config.brandColor || "#0A5B3B",
499
+ textColor: config.textColor || "#303030",
500
+ suggestionsTextColor: config.suggestionsTextColor || "",
501
+ inputTextColor: config.inputTextColor || "",
502
+ mutedColor: config.mutedColor || "#9CA3AF",
503
+ inputBg,
504
+ inputBackground: inputBg,
505
+ inputOpacity: config.inputOpacity || "1",
506
+ suggestionsHoverBg: config.suggestionsHoverBg || "",
507
+ theme: config.theme || "light",
508
+ closeButton: config.closeButton || false,
509
+ onClose: config.onClose || (() => {}),
510
+ classAdditions: config.classAdditions || {}
511
+ };
512
+ this.validateConfig();
513
+ this.injectStyles();
514
+ this.render();
515
+ this.attachEventListeners();
516
+ }
517
+ /** Get the SDK version */
518
+ static get version() {
519
+ return VERSION$1;
520
+ }
521
+ /** Validate configuration */
522
+ validateConfig() {
523
+ if (!this.config.container) throw new Error("BonsaiSearchBar: container element is required");
524
+ }
525
+ /** Build class name string with any configured additional classes */
526
+ buildClassName(baseClass) {
527
+ const additions = this.config.classAdditions[baseClass];
528
+ if (additions && additions.length > 0) return `${baseClass} ${additions.join(" ")}`;
529
+ return baseClass;
530
+ }
531
+ /** Apply a shadow part name for external styling */
532
+ applyPart(el, part) {
533
+ el.setAttribute("part", part);
534
+ }
535
+ /** Inject CSS styles into the document */
536
+ injectStyles() {
537
+ if (this.stylesInjected) return;
538
+ if (document.getElementById("bonsai-searchbar-styles")) {
539
+ this.stylesInjected = true;
540
+ return;
541
+ }
542
+ const styleElement = document.createElement("style");
543
+ styleElement.id = "bonsai-searchbar-styles";
544
+ styleElement.textContent = SEARCHBAR_STYLES;
545
+ document.head.appendChild(styleElement);
546
+ this.stylesInjected = true;
547
+ }
548
+ /** Render the search bar UI */
549
+ render() {
550
+ this.container.innerHTML = "";
551
+ this.container.classList.add("bonsai-searchbar-container");
552
+ this.applyPart(this.container, "searchbar-container");
553
+ this.container.setAttribute("data-theme", this.config.theme);
554
+ this.container.style.setProperty("--bonsai-brand-color", this.config.brandColor);
555
+ this.container.style.setProperty("--bonsai-text-color", this.config.textColor);
556
+ this.container.style.setProperty("--bonsai-muted-color", this.config.mutedColor);
557
+ this.container.style.setProperty("--bonsai-input-bg", this.config.inputBg);
558
+ if (this.config.suggestionsTextColor) this.container.style.setProperty("--bonsai-suggestions-text-color", this.config.suggestionsTextColor);
559
+ if (this.config.inputTextColor) this.container.style.setProperty("--bonsai-input-text-color", this.config.inputTextColor);
560
+ if (this.config.suggestionsHoverBg) this.container.style.setProperty("--bonsai-suggestions-hover-bg", this.config.suggestionsHoverBg);
561
+ const wrapper = document.createElement("div");
562
+ wrapper.className = this.buildClassName("bonsai-searchbar-wrapper");
563
+ this.applyPart(wrapper, "searchbar-wrapper");
564
+ this.searchBar = document.createElement("div");
565
+ this.searchBar.className = this.buildClassName("bonsai-searchbar");
566
+ this.applyPart(this.searchBar, "searchbar");
567
+ this.searchBar.style.opacity = this.config.inputOpacity;
568
+ const searchIcon = document.createElement("div");
569
+ searchIcon.className = this.buildClassName("bonsai-searchbar-icon");
570
+ this.applyPart(searchIcon, "searchbar-icon");
571
+ searchIcon.innerHTML = ICONS$1.search;
572
+ this.searchBar.appendChild(searchIcon);
573
+ this.searchInput = document.createElement("input");
574
+ this.searchInput.type = "text";
575
+ this.searchInput.className = this.buildClassName("bonsai-searchbar-input");
576
+ this.applyPart(this.searchInput, "searchbar-input");
577
+ this.searchInput.placeholder = this.config.placeholder;
578
+ this.searchInput.setAttribute("aria-label", "Search input");
579
+ this.searchBar.appendChild(this.searchInput);
580
+ const actionsContainer = document.createElement("div");
581
+ actionsContainer.className = this.buildClassName("bonsai-searchbar-actions");
582
+ this.applyPart(actionsContainer, "searchbar-actions");
583
+ this.submitBtn = document.createElement("button");
584
+ this.submitBtn.type = "button";
585
+ this.submitBtn.className = this.buildClassName("bonsai-searchbar-submit");
586
+ this.applyPart(this.submitBtn, "searchbar-submit");
587
+ this.submitBtn.setAttribute("aria-label", "Submit search");
588
+ this.submitBtn.innerHTML = ICONS$1.arrowRight;
589
+ actionsContainer.appendChild(this.submitBtn);
590
+ const closeBtn = document.createElement("button");
591
+ closeBtn.type = "button";
592
+ closeBtn.className = this.buildClassName("bonsai-searchbar-close");
593
+ if (this.config.closeButton) closeBtn.classList.add("bonsai-searchbar-visible");
594
+ this.applyPart(closeBtn, "searchbar-close");
595
+ closeBtn.setAttribute("aria-label", "Close search");
596
+ closeBtn.innerHTML = ICONS$1.close;
597
+ closeBtn.addEventListener("click", () => this.config.onClose());
598
+ actionsContainer.appendChild(closeBtn);
599
+ this.searchBar.appendChild(actionsContainer);
600
+ wrapper.appendChild(this.searchBar);
601
+ if (this.config.suggestions.length > 0) {
602
+ this.suggestionsDropdown = document.createElement("div");
603
+ this.suggestionsDropdown.className = this.buildClassName("bonsai-searchbar-suggestions");
604
+ this.applyPart(this.suggestionsDropdown, "searchbar-suggestions");
605
+ this.suggestionsDropdown.setAttribute("role", "listbox");
606
+ this.renderSuggestions();
607
+ wrapper.appendChild(this.suggestionsDropdown);
608
+ }
609
+ this.container.appendChild(wrapper);
610
+ const poweredBy = document.createElement("div");
611
+ poweredBy.className = this.buildClassName("bonsai-searchbar-powered");
612
+ poweredBy.innerHTML = `powered by <a href="https://www.hibonsai.com/" target="_blank" rel="noopener" class="bonsai-searchbar-powered-link v${VERSION$1}">Bonsai<span class="bonsai-searchbar-powered-dot"></span></a>`;
613
+ this.applyPart(poweredBy, "searchbar-powered");
614
+ const poweredLink = poweredBy.querySelector(".bonsai-searchbar-powered-link");
615
+ if (poweredLink) this.applyPart(poweredLink, "searchbar-powered-link");
616
+ const poweredDot = poweredBy.querySelector(".bonsai-searchbar-powered-dot");
617
+ if (poweredDot) this.applyPart(poweredDot, "searchbar-powered-dot");
618
+ this.container.appendChild(poweredBy);
619
+ }
620
+ /** Render suggestions in the dropdown */
621
+ renderSuggestions() {
622
+ if (!this.suggestionsDropdown) return;
623
+ this.suggestionsDropdown.innerHTML = "";
624
+ (this.query ? this.config.suggestions.filter((s) => s.toLowerCase().includes(this.query.toLowerCase())).slice(0, 5) : this.config.suggestions.slice(0, 5)).forEach((suggestion) => {
625
+ const item = document.createElement("button");
626
+ item.type = "button";
627
+ item.className = this.buildClassName("bonsai-searchbar-suggestion");
628
+ this.applyPart(item, "searchbar-suggestion");
629
+ item.setAttribute("role", "option");
630
+ const text = document.createElement("span");
631
+ text.textContent = suggestion;
632
+ this.applyPart(text, "searchbar-suggestion-text");
633
+ item.appendChild(text);
634
+ const arrow = document.createElement("div");
635
+ arrow.className = this.buildClassName("bonsai-searchbar-suggestion-arrow");
636
+ this.applyPart(arrow, "searchbar-suggestion-arrow");
637
+ arrow.innerHTML = ICONS$1.arrowRight;
638
+ item.appendChild(arrow);
639
+ item.addEventListener("click", () => this.handleSuggestionClick(suggestion));
640
+ this.suggestionsDropdown.appendChild(item);
641
+ });
642
+ }
643
+ /** Attach event listeners */
644
+ attachEventListeners() {
645
+ if (!this.searchInput) return;
646
+ this.searchInput.addEventListener("focus", () => this.handleFocus());
647
+ this.searchInput.addEventListener("blur", () => this.handleBlur());
648
+ this.searchInput.addEventListener("input", (e) => this.handleInput(e));
649
+ this.searchInput.addEventListener("keydown", (e) => this.handleKeyDown(e));
650
+ this.submitBtn?.addEventListener("click", () => this.handleSubmit());
651
+ }
652
+ /** Handle input focus */
653
+ handleFocus() {
654
+ if (this.state === "default") this.setState("focused");
655
+ this.searchBar?.classList.add("bonsai-searchbar-focused");
656
+ if (this.suggestionsDropdown) {
657
+ this.renderSuggestions();
658
+ this.suggestionsDropdown.classList.add("bonsai-searchbar-visible");
659
+ }
660
+ }
661
+ /** Handle input blur */
662
+ handleBlur() {
663
+ this.searchBar?.classList.remove("bonsai-searchbar-focused");
664
+ setTimeout(() => {
665
+ this.suggestionsDropdown?.classList.remove("bonsai-searchbar-visible");
666
+ }, 200);
667
+ }
668
+ /** Handle input change */
669
+ handleInput(e) {
670
+ this.query = e.target.value;
671
+ if (this.query) {
672
+ this.setState("filled");
673
+ this.suggestionsDropdown?.classList.remove("bonsai-searchbar-visible");
674
+ } else {
675
+ this.setState("focused");
676
+ if (this.suggestionsDropdown) {
677
+ this.renderSuggestions();
678
+ this.suggestionsDropdown.classList.add("bonsai-searchbar-visible");
679
+ }
680
+ }
681
+ }
682
+ /** Handle key down */
683
+ handleKeyDown(e) {
684
+ if (e.key === "Enter" && this.query) {
685
+ e.preventDefault();
686
+ this.handleSubmit();
687
+ }
688
+ }
689
+ /** Handle suggestion click */
690
+ handleSuggestionClick(suggestion) {
691
+ this.query = suggestion;
692
+ if (this.searchInput) this.searchInput.value = suggestion;
693
+ this.suggestionsDropdown?.classList.remove("bonsai-searchbar-visible");
694
+ this.handleSubmit();
695
+ }
696
+ /** Handle search submit - redirect to search page */
697
+ handleSubmit(searchQuery) {
698
+ const queryToSearch = searchQuery ?? this.query;
699
+ if (!queryToSearch) return;
700
+ try {
701
+ localStorage.setItem("bonsai-search-query", queryToSearch);
702
+ } catch {}
703
+ const url = `${this.config.searchPath}?q=${encodeURIComponent(queryToSearch)}`;
704
+ window.location.href = url;
705
+ }
706
+ /** Set the current state and update UI */
707
+ setState(newState) {
708
+ this.state = newState;
709
+ this.updateSubmitButton();
710
+ }
711
+ /** Update submit button visibility based on state */
712
+ updateSubmitButton() {
713
+ if (!this.submitBtn) return;
714
+ if (this.state === "filled") this.submitBtn.classList.add("bonsai-searchbar-visible");
715
+ else this.submitBtn.classList.remove("bonsai-searchbar-visible");
716
+ }
717
+ /** Set the search query programmatically */
718
+ setQuery(query) {
719
+ this.query = query;
720
+ if (this.searchInput) this.searchInput.value = query;
721
+ if (query) this.setState("filled");
722
+ else this.setState("default");
723
+ }
724
+ /** Submit the search programmatically */
725
+ submit() {
726
+ this.handleSubmit();
727
+ }
728
+ /** Clear the search input */
729
+ clear() {
730
+ this.query = "";
731
+ if (this.searchInput) this.searchInput.value = "";
732
+ this.setState("default");
733
+ }
734
+ /** Destroy the instance and clean up */
735
+ destroy() {
736
+ this.container.innerHTML = "";
737
+ }
738
+ };
739
+ if (typeof window !== "undefined") window.BonsaiSearchBar = BonsaiSearchBar;
740
+
741
+ //#endregion
742
+ //#region src/bonsai-searchbar-webcomponent.ts
743
+ const SDK_CSS = sdk_default || "";
744
+ if (typeof window !== "undefined" && !SDK_CSS) console.warn("BonsaiSearchBar: CSS not loaded from file, will use fallback");
745
+ /**
746
+ * Web Component for Bonsai Search Bar
747
+ * Fully encapsulated with Shadow DOM, no inherited styles
748
+ *
749
+ * Usage:
750
+ * <bonsai-searchbar
751
+ * search-path="/ai-search"
752
+ * placeholder="Describe what you're looking for..."
753
+ * suggestions='["suggestion 1", "suggestion 2"]'
754
+ * brand-color="#0A5B3B"
755
+ * text-color="#303030"
756
+ * muted-color="#9CA3AF"
757
+ * input-background="#f5f5f5"
758
+ * input-opacity="1"
759
+ * close-button>
760
+ * </bonsai-searchbar>
761
+ */
762
+ var BonsaiSearchBarElement = class extends HTMLElement {
763
+ searchBar = null;
764
+ shadow;
765
+ container = null;
766
+ static get observedAttributes() {
767
+ return [
768
+ "search-path",
769
+ "placeholder",
770
+ "suggestions",
771
+ "brand-color",
772
+ "text-color",
773
+ "suggestions-text-color",
774
+ "input-text-color",
775
+ "muted-color",
776
+ "input-bg",
777
+ "input-background",
778
+ "input-opacity",
779
+ "suggestions-hover-bg",
780
+ "theme",
781
+ "close-button"
782
+ ];
783
+ }
784
+ constructor() {
785
+ super();
786
+ this.shadow = this.attachShadow({ mode: "closed" });
787
+ }
788
+ connectedCallback() {
789
+ this.render();
790
+ this.initialize();
791
+ }
792
+ disconnectedCallback() {
793
+ this.searchBar?.destroy();
794
+ this.searchBar = null;
795
+ }
796
+ attributeChangedCallback(name, oldValue, newValue) {
797
+ if (oldValue === newValue || !this.searchBar) return;
798
+ if ([
799
+ "search-path",
800
+ "placeholder",
801
+ "suggestions"
802
+ ].includes(name)) {
803
+ this.disconnectedCallback();
804
+ this.connectedCallback();
805
+ } else this.updateStyles();
806
+ }
807
+ render() {
808
+ let css = (SDK_CSS + "\n" + SEARCHBAR_STYLES).trim();
809
+ if (!css || css.trim().length < 100) {
810
+ console.warn("BonsaiSearchBar: CSS not loaded, using fallback");
811
+ css = this.getDefaultCSS();
812
+ }
813
+ css = css.replace(/:root\s*\{/g, ":host {");
814
+ const style = document.createElement("style");
815
+ style.textContent = css;
816
+ this.shadow.appendChild(style);
817
+ this.container = document.createElement("div");
818
+ this.container.style.width = "100%";
819
+ this.shadow.appendChild(this.container);
820
+ this.updateStyles();
821
+ }
822
+ updateStyles() {
823
+ const themeAttr = this.getAttribute("theme") || "light";
824
+ const normalizedTheme = themeAttr === "dark" || themeAttr === "auto" ? themeAttr : "light";
825
+ const prefersDark = typeof window !== "undefined" && window.matchMedia && window.matchMedia("(prefers-color-scheme: dark)").matches;
826
+ const resolvedTheme = normalizedTheme === "auto" ? prefersDark ? "dark" : "light" : normalizedTheme;
827
+ this.setAttribute("data-theme", resolvedTheme);
828
+ const setOrClear = (name, value) => {
829
+ if (value && value.trim().length > 0) this.style.setProperty(name, value);
830
+ else this.style.removeProperty(name);
831
+ };
832
+ setOrClear("--bonsai-brand-color", this.getAttribute("brand-color") || "#0A5B3B");
833
+ setOrClear("--bonsai-text-color", this.getAttribute("text-color") || "#303030");
834
+ setOrClear("--bonsai-muted-color", this.getAttribute("muted-color") || "#9CA3AF");
835
+ setOrClear("--bonsai-input-bg", this.getAttribute("input-bg") || this.getAttribute("input-background") || "#f5f5f5");
836
+ const suggestionsTextColor = this.getAttribute("suggestions-text-color");
837
+ const inputTextColor = this.getAttribute("input-text-color");
838
+ setOrClear("--bonsai-suggestions-text-color", suggestionsTextColor || inputTextColor);
839
+ setOrClear("--bonsai-input-text-color", inputTextColor);
840
+ setOrClear("--bonsai-suggestions-hover-bg", this.getAttribute("suggestions-hover-bg"));
841
+ }
842
+ initialize() {
843
+ if (!this.container) return;
844
+ const suggestionsAttr = this.getAttribute("suggestions");
845
+ let suggestions = [];
846
+ if (suggestionsAttr) try {
847
+ suggestions = JSON.parse(suggestionsAttr);
848
+ } catch (e) {
849
+ console.warn("bonsai-searchbar: Invalid suggestions JSON, using empty array");
850
+ }
851
+ const themeAttr = this.getAttribute("theme") || "light";
852
+ const theme = themeAttr === "dark" || themeAttr === "auto" ? themeAttr : "light";
853
+ this.searchBar = new BonsaiSearchBar({
854
+ container: this.container,
855
+ searchPath: this.getAttribute("search-path") || "/ai-search",
856
+ placeholder: this.getAttribute("placeholder") || "Describe what you're looking for...",
857
+ suggestions,
858
+ brandColor: this.getAttribute("brand-color") || "#0A5B3B",
859
+ textColor: this.getAttribute("text-color") || "#303030",
860
+ suggestionsTextColor: this.getAttribute("suggestions-text-color") || void 0,
861
+ inputTextColor: this.getAttribute("input-text-color") || void 0,
862
+ mutedColor: this.getAttribute("muted-color") || "#9CA3AF",
863
+ inputBg: this.getAttribute("input-bg") || this.getAttribute("input-background") || "#f5f5f5",
864
+ inputOpacity: this.getAttribute("input-opacity") || "1",
865
+ suggestionsHoverBg: this.getAttribute("suggestions-hover-bg") || void 0,
866
+ theme,
867
+ closeButton: this.hasAttribute("close-button"),
868
+ onClose: () => {
869
+ this.dispatchEvent(new CustomEvent("close", {
870
+ bubbles: true,
871
+ composed: true
872
+ }));
873
+ }
874
+ });
875
+ }
876
+ setQuery(query) {
877
+ this.searchBar?.setQuery(query);
878
+ }
879
+ submit() {
880
+ this.searchBar?.submit();
881
+ }
882
+ clear() {
883
+ this.searchBar?.clear();
884
+ }
885
+ getDefaultCSS() {
886
+ return `
887
+ :host {
888
+ display: block;
889
+ width: 100%;
890
+ --bonsai-brand-color: #0A5B3B;
891
+ --bonsai-text-color: #303030;
892
+ --bonsai-suggestions-text-color: #303030;
893
+ --bonsai-input-text-color: #303030;
894
+ --bonsai-suggestions-hover-bg: rgba(0, 0, 0, 0.04);
895
+ --bonsai-muted-color: #9CA3AF;
896
+ --bonsai-input-bg: #f5f5f5;
897
+ }
898
+
899
+ :host([data-theme="dark"]) {
900
+ --bonsai-text-color: #e5e7eb;
901
+ --bonsai-suggestions-text-color: #e5e7eb;
902
+ --bonsai-input-text-color: #e5e7eb;
903
+ --bonsai-suggestions-hover-bg: rgba(255, 255, 255, 0.06);
904
+ --bonsai-muted-color: #a1a1aa;
905
+ --bonsai-input-bg: #1f2229;
906
+ }
907
+ `;
908
+ }
909
+ };
910
+ if (typeof window !== "undefined" && !customElements.get("bonsai-searchbar")) customElements.define("bonsai-searchbar", BonsaiSearchBarElement);
911
+
912
+ //#endregion
913
+ //#region src/index.ts
914
+ const VERSION = "3.0.8";
915
+ const ICONS = {
916
+ search: `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
917
+ <circle cx="11" cy="11" r="8"></circle>
918
+ <path d="m21 21-4.35-4.35"></path>
919
+ </svg>`,
920
+ arrowRight: `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
921
+ <path d="M5 12h14"></path>
922
+ <path d="m12 5 7 7-7 7"></path>
923
+ </svg>`,
924
+ check: `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
925
+ <polyline points="20 6 9 17 4 12"></polyline>
926
+ </svg>`,
927
+ reset: `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
928
+ <path d="M3 12a9 9 0 1 0 9-9 9.75 9.75 0 0 0-6.74 2.74L3 8"></path>
929
+ <path d="M3 3v5h5"></path>
930
+ </svg>`,
931
+ spinner: `<svg viewBox="0 0 24 24">
932
+ <circle class="bonsai-spinner-track" cx="12" cy="12" r="10" stroke-width="3" fill="none"></circle>
933
+ <path class="bonsai-spinner-fill" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
934
+ </svg>`
935
+ };
936
+ var BonsaiSearch = class {
937
+ config;
938
+ state = "default";
939
+ container;
940
+ searchBar = null;
941
+ searchInput = null;
942
+ actionsContainer = null;
943
+ submitBtn = null;
944
+ loadingState = null;
945
+ loadingTextInner = null;
946
+ doneState = null;
947
+ resetBtn = null;
948
+ suggestionsDropdown = null;
949
+ poweredBy = null;
950
+ resultsContainer = null;
951
+ query = "";
952
+ loadingIndex = 0;
953
+ loadingInterval = null;
954
+ debounceTimer = null;
955
+ abortController = null;
956
+ loadingStates = [
957
+ "Searching",
958
+ "Analyzing",
959
+ "Curating"
960
+ ];
961
+ constructor(config) {
962
+ this.container = config.container;
963
+ this.config = {
964
+ container: config.container,
965
+ apiKey: config.apiKey,
966
+ baseUrl: config.baseUrl || this.inferBaseUrl(),
967
+ placeholder: config.placeholder || "Describe what you're looking for...",
968
+ suggestions: config.suggestions || [],
969
+ debounceMs: config.debounceMs ?? 0,
970
+ maxResults: config.maxResults ?? 50,
971
+ timeoutMs: config.timeoutMs ?? 3e4,
972
+ imageHostname: config.imageHostname || "",
973
+ generateSrcSet: config.generateSrcSet ?? false,
974
+ srcSetSizes: config.srcSetSizes || [],
975
+ classAdditions: config.classAdditions || {},
976
+ renderPrice: config.renderPrice ?? false,
977
+ renderPriceWithTitle: config.renderPriceWithTitle ?? true,
978
+ imageObjectFit: config.imageObjectFit || "cover",
979
+ markdown: config.markdown ?? false,
980
+ featuredItemsLabel: config.featuredItemsLabel || "Featured Items",
981
+ moreItemsLabel: config.moreItemsLabel || "More Items",
982
+ itemsLabel: config.itemsLabel || "Items",
983
+ onPrice: config.onPrice ?? (() => ""),
984
+ onImg: config.onImg ?? (() => {}),
985
+ onSearch: config.onSearch ?? (() => {}),
986
+ onResults: config.onResults ?? (() => {}),
987
+ onAi: config.onAi ?? (() => {}),
988
+ onError: config.onError ?? (() => {})
989
+ };
990
+ this.validateConfig();
991
+ this.render();
992
+ this.attachEventListeners();
993
+ this.checkForQueryParam();
994
+ }
995
+ /** Get the SDK version */
996
+ static get version() {
997
+ return VERSION;
998
+ }
999
+ /** Infer base URL from script tag or use current origin */
1000
+ inferBaseUrl() {
1001
+ const script = document.querySelector("script[src*=\"bonsai-search\"]");
1002
+ if (script?.src) {
1003
+ const url = new URL(script.src);
1004
+ return `${url.protocol}//${url.host}`;
1005
+ }
1006
+ return window.location.origin;
1007
+ }
1008
+ /** Build class name string with any configured additional classes */
1009
+ buildClassName(baseClass) {
1010
+ const additions = this.config.classAdditions[baseClass];
1011
+ if (additions && additions.length > 0) return `${baseClass} ${additions.join(" ")}`;
1012
+ return baseClass;
1013
+ }
1014
+ /** Apply a shadow part name for external styling */
1015
+ applyPart(el, part) {
1016
+ el.setAttribute("part", part);
1017
+ }
1018
+ /** Validate configuration */
1019
+ validateConfig() {
1020
+ if (!this.config.container) throw new Error("BonsaiSearch: container element is required");
1021
+ if (!this.config.apiKey) throw new Error("BonsaiSearch: apiKey is required");
1022
+ }
1023
+ /** Render the search UI */
1024
+ render() {
1025
+ this.container.innerHTML = "";
1026
+ this.container.classList.add("bonsai-search-container");
1027
+ const wrapper = document.createElement("div");
1028
+ wrapper.className = this.buildClassName("bonsai-search-wrapper");
1029
+ this.applyPart(wrapper, "search-wrapper");
1030
+ this.searchBar = document.createElement("div");
1031
+ this.searchBar.className = this.buildClassName("bonsai-search-bar");
1032
+ this.applyPart(this.searchBar, "search-bar");
1033
+ const searchIcon = document.createElement("div");
1034
+ searchIcon.className = this.buildClassName("bonsai-search-icon");
1035
+ this.applyPart(searchIcon, "search-icon");
1036
+ searchIcon.innerHTML = ICONS.search;
1037
+ this.searchBar.appendChild(searchIcon);
1038
+ this.searchInput = document.createElement("input");
1039
+ this.searchInput.type = "text";
1040
+ this.searchInput.className = this.buildClassName("bonsai-search-input");
1041
+ this.applyPart(this.searchInput, "search-input");
1042
+ this.searchInput.placeholder = this.config.placeholder;
1043
+ this.searchInput.setAttribute("aria-label", "Search input");
1044
+ this.searchBar.appendChild(this.searchInput);
1045
+ this.actionsContainer = document.createElement("div");
1046
+ this.actionsContainer.className = this.buildClassName("bonsai-search-actions");
1047
+ this.applyPart(this.actionsContainer, "search-actions");
1048
+ this.submitBtn = document.createElement("button");
1049
+ this.submitBtn.type = "button";
1050
+ this.submitBtn.className = this.buildClassName("bonsai-submit-btn");
1051
+ this.applyPart(this.submitBtn, "submit-button");
1052
+ this.submitBtn.setAttribute("aria-label", "Submit search");
1053
+ this.submitBtn.innerHTML = ICONS.arrowRight;
1054
+ this.actionsContainer.appendChild(this.submitBtn);
1055
+ this.loadingState = document.createElement("div");
1056
+ this.loadingState.className = this.buildClassName("bonsai-loading-state");
1057
+ this.applyPart(this.loadingState, "loading-state");
1058
+ const loadingText = document.createElement("div");
1059
+ loadingText.className = this.buildClassName("bonsai-loading-text");
1060
+ this.loadingTextInner = document.createElement("div");
1061
+ this.loadingTextInner.className = this.buildClassName("bonsai-loading-text-inner");
1062
+ this.applyPart(loadingText, "loading-text");
1063
+ this.applyPart(this.loadingTextInner, "loading-text-inner");
1064
+ this.loadingStates.forEach((text) => {
1065
+ const span = document.createElement("span");
1066
+ span.textContent = text;
1067
+ this.loadingTextInner.appendChild(span);
1068
+ });
1069
+ loadingText.appendChild(this.loadingTextInner);
1070
+ this.loadingState.appendChild(loadingText);
1071
+ const spinner = document.createElement("div");
1072
+ spinner.className = this.buildClassName("bonsai-spinner");
1073
+ this.applyPart(spinner, "spinner");
1074
+ spinner.innerHTML = ICONS.spinner;
1075
+ this.loadingState.appendChild(spinner);
1076
+ this.actionsContainer.appendChild(this.loadingState);
1077
+ this.doneState = document.createElement("div");
1078
+ this.doneState.className = this.buildClassName("bonsai-done-state");
1079
+ this.applyPart(this.doneState, "done-state");
1080
+ const doneText = document.createElement("span");
1081
+ doneText.className = this.buildClassName("bonsai-done-text");
1082
+ this.applyPart(doneText, "done-text");
1083
+ doneText.textContent = "Done!";
1084
+ this.doneState.appendChild(doneText);
1085
+ const doneIcon = document.createElement("div");
1086
+ doneIcon.className = this.buildClassName("bonsai-done-icon");
1087
+ this.applyPart(doneIcon, "done-icon");
1088
+ doneIcon.innerHTML = ICONS.check;
1089
+ this.doneState.appendChild(doneIcon);
1090
+ this.actionsContainer.appendChild(this.doneState);
1091
+ this.resetBtn = document.createElement("button");
1092
+ this.resetBtn.type = "button";
1093
+ this.resetBtn.className = this.buildClassName("bonsai-reset-btn");
1094
+ this.applyPart(this.resetBtn, "reset-button");
1095
+ this.resetBtn.setAttribute("aria-label", "Reset search");
1096
+ const resetText = document.createElement("span");
1097
+ resetText.className = this.buildClassName("bonsai-reset-text");
1098
+ this.applyPart(resetText, "reset-text");
1099
+ resetText.textContent = "Reset";
1100
+ this.resetBtn.appendChild(resetText);
1101
+ const resetIcon = document.createElement("div");
1102
+ resetIcon.className = this.buildClassName("bonsai-reset-icon");
1103
+ this.applyPart(resetIcon, "reset-icon");
1104
+ resetIcon.innerHTML = ICONS.reset;
1105
+ this.resetBtn.appendChild(resetIcon);
1106
+ this.actionsContainer.appendChild(this.resetBtn);
1107
+ this.searchBar.appendChild(this.actionsContainer);
1108
+ if (this.config.suggestions.length > 0) {
1109
+ this.suggestionsDropdown = document.createElement("div");
1110
+ this.suggestionsDropdown.className = this.buildClassName("bonsai-suggestions");
1111
+ this.applyPart(this.suggestionsDropdown, "suggestions");
1112
+ this.suggestionsDropdown.id = "bonsai-suggestions";
1113
+ this.suggestionsDropdown.setAttribute("role", "listbox");
1114
+ this.renderSuggestions();
1115
+ this.searchBar.appendChild(this.suggestionsDropdown);
1116
+ }
1117
+ wrapper.appendChild(this.searchBar);
1118
+ this.poweredBy = document.createElement("div");
1119
+ this.poweredBy.className = this.buildClassName("bonsai-powered-by");
1120
+ this.applyPart(this.poweredBy, "powered-by");
1121
+ this.poweredBy.innerHTML = `powered by <a href="https://www.hibonsai.com/" target="_blank" rel="noopener">Bonsai<span class="bonsai-powered-by-dot"></span></a>`;
1122
+ wrapper.appendChild(this.poweredBy);
1123
+ this.resultsContainer = document.createElement("div");
1124
+ this.resultsContainer.className = this.buildClassName("bonsai-results");
1125
+ this.applyPart(this.resultsContainer, "results");
1126
+ wrapper.appendChild(this.resultsContainer);
1127
+ this.container.appendChild(wrapper);
1128
+ }
1129
+ /** Render suggestions in the dropdown */
1130
+ renderSuggestions() {
1131
+ if (!this.suggestionsDropdown) return;
1132
+ this.suggestionsDropdown.innerHTML = "";
1133
+ this.config.suggestions.slice(0, 5).forEach((suggestion) => {
1134
+ const item = document.createElement("button");
1135
+ item.type = "button";
1136
+ item.className = this.buildClassName("bonsai-suggestion-item");
1137
+ this.applyPart(item, "suggestion-item");
1138
+ item.setAttribute("role", "option");
1139
+ const text = document.createElement("span");
1140
+ text.textContent = suggestion;
1141
+ this.applyPart(text, "suggestion-text");
1142
+ item.appendChild(text);
1143
+ const arrow = document.createElement("div");
1144
+ arrow.className = this.buildClassName("bonsai-suggestion-arrow");
1145
+ this.applyPart(arrow, "suggestion-arrow");
1146
+ arrow.innerHTML = ICONS.arrowRight;
1147
+ item.appendChild(arrow);
1148
+ item.addEventListener("click", () => this.handleSuggestionClick(suggestion));
1149
+ this.suggestionsDropdown.appendChild(item);
1150
+ });
1151
+ }
1152
+ /** Attach event listeners */
1153
+ attachEventListeners() {
1154
+ if (!this.searchInput) return;
1155
+ this.searchInput.addEventListener("focus", () => this.handleFocus());
1156
+ this.searchInput.addEventListener("blur", () => this.handleBlur());
1157
+ this.searchInput.addEventListener("input", (e) => this.handleInput(e));
1158
+ this.searchInput.addEventListener("keydown", (e) => this.handleKeyDown(e));
1159
+ this.submitBtn?.addEventListener("click", (e) => {
1160
+ e.preventDefault();
1161
+ e.stopPropagation();
1162
+ this.handleSubmit();
1163
+ });
1164
+ this.resetBtn?.addEventListener("click", (e) => {
1165
+ e.preventDefault();
1166
+ e.stopPropagation();
1167
+ this.handleReset();
1168
+ });
1169
+ }
1170
+ /** Handle input focus */
1171
+ handleFocus() {
1172
+ if (this.state === "default" || this.state === "retry") {
1173
+ if (!this.query && this.searchInput) this.searchInput.value = "";
1174
+ else if (this.query && this.searchInput) this.searchInput.value = this.query;
1175
+ if (!this.query) this.setState("focused");
1176
+ }
1177
+ this.searchBar?.classList.add("bonsai-focused");
1178
+ if (!this.query && this.suggestionsDropdown) {
1179
+ this.renderSuggestions();
1180
+ this.suggestionsDropdown.classList.add("bonsai-visible");
1181
+ }
1182
+ }
1183
+ /** Handle input blur */
1184
+ handleBlur() {
1185
+ this.searchBar?.classList.remove("bonsai-focused");
1186
+ setTimeout(() => {
1187
+ this.suggestionsDropdown?.classList.remove("bonsai-visible");
1188
+ }, 200);
1189
+ }
1190
+ /** Handle input change */
1191
+ handleInput(e) {
1192
+ this.query = e.target.value;
1193
+ if (this.debounceTimer !== null) {
1194
+ window.clearTimeout(this.debounceTimer);
1195
+ this.debounceTimer = null;
1196
+ }
1197
+ if (this.query) {
1198
+ if (this.state === "retry" || this.state === "done") this.setState("filled");
1199
+ else if (this.state !== "loading") this.setState("filled");
1200
+ this.suggestionsDropdown?.classList.remove("bonsai-visible");
1201
+ } else {
1202
+ if (this.state !== "loading" && this.state !== "done" && this.state !== "retry") this.setState("focused");
1203
+ if (this.suggestionsDropdown) {
1204
+ this.renderSuggestions();
1205
+ this.suggestionsDropdown.classList.add("bonsai-visible");
1206
+ }
1207
+ }
1208
+ }
1209
+ /** Handle key down */
1210
+ handleKeyDown(e) {
1211
+ if (e.key === "Enter" && this.query) {
1212
+ e.preventDefault();
1213
+ if (this.debounceTimer !== null) {
1214
+ window.clearTimeout(this.debounceTimer);
1215
+ this.debounceTimer = null;
1216
+ }
1217
+ this.handleSubmit();
1218
+ }
1219
+ }
1220
+ /** Handle suggestion click */
1221
+ handleSuggestionClick(suggestion) {
1222
+ this.query = suggestion;
1223
+ if (this.searchInput) this.searchInput.value = suggestion;
1224
+ this.suggestionsDropdown?.classList.remove("bonsai-visible");
1225
+ this.handleSubmit();
1226
+ }
1227
+ /** Handle search submit using Server-Sent Events */
1228
+ async handleSubmit() {
1229
+ if (!this.query || !this.query.trim()) return;
1230
+ if (this.state === "loading") return;
1231
+ if (this.debounceTimer !== null) {
1232
+ window.clearTimeout(this.debounceTimer);
1233
+ this.debounceTimer = null;
1234
+ }
1235
+ if (this.abortController) this.abortController.abort();
1236
+ this.abortController = new AbortController();
1237
+ this.setState("loading");
1238
+ this.config.onSearch(this.query);
1239
+ this.startLoadingAnimation();
1240
+ let initialResults = [];
1241
+ let hasReceivedResults = false;
1242
+ let hasReceivedAi = false;
1243
+ try {
1244
+ this.resultsContainer.innerHTML = "";
1245
+ const url = new URL(this.config.baseUrl);
1246
+ url.searchParams.set("q", this.query);
1247
+ if (this.config.markdown) url.searchParams.set("markdown", "true");
1248
+ const response = await fetch(url.toString(), {
1249
+ method: "GET",
1250
+ headers: {
1251
+ Accept: "text/event-stream",
1252
+ "X-API-Key": this.config.apiKey
1253
+ },
1254
+ signal: this.abortController.signal
1255
+ });
1256
+ if (!response.ok) {
1257
+ const errorData = await response.json().catch(() => ({}));
1258
+ throw new Error(errorData.error || `HTTP error! status: ${response.status}`);
1259
+ }
1260
+ const reader = response.body?.getReader();
1261
+ if (!reader) throw new Error("Unable to read response stream");
1262
+ const decoder = new TextDecoder();
1263
+ let buffer = "";
1264
+ while (true) {
1265
+ const { done, value } = await reader.read();
1266
+ if (done) break;
1267
+ buffer += decoder.decode(value, { stream: true });
1268
+ const events = this.parseSSEEvents(buffer);
1269
+ buffer = events.remaining;
1270
+ for (const event of events.parsed) if (event.type === "results") {
1271
+ hasReceivedResults = true;
1272
+ const data = event.data;
1273
+ if (!data.success && data.error) throw new Error(data.error);
1274
+ initialResults = (data.results || []).filter((r) => r.publicUrl && r.publicUrl.trim() !== "");
1275
+ initialResults = this.config.maxResults ? initialResults.slice(0, this.config.maxResults) : initialResults;
1276
+ this.renderResults([], initialResults);
1277
+ this.showRecommendationsLoading();
1278
+ this.config.onResults(initialResults);
1279
+ } else if (event.type === "ai") {
1280
+ hasReceivedAi = true;
1281
+ const data = event.data;
1282
+ let finalResults = (data.results || []).filter((r) => r.publicUrl && r.publicUrl.trim() !== "");
1283
+ finalResults = this.config.maxResults ? finalResults.slice(0, this.config.maxResults) : finalResults;
1284
+ this.hideRecommendationsLoading();
1285
+ let resultsToRender = finalResults;
1286
+ if (finalResults.length === 0) {
1287
+ const recProductIds = (data.recommendations || []).flatMap((r) => r.productIds || []);
1288
+ if (recProductIds.length > 0) {
1289
+ const matched = recProductIds.map((id) => initialResults.find((r) => r.id === id)).filter((r) => r != null);
1290
+ resultsToRender = matched.length > 0 ? matched : initialResults;
1291
+ } else resultsToRender = initialResults;
1292
+ }
1293
+ this.renderResults(data.recommendations || [], resultsToRender);
1294
+ this.config.onAi(data);
1295
+ this.stopLoadingAnimation();
1296
+ this.setState("done");
1297
+ setTimeout(() => {
1298
+ if (this.state === "done") this.setState("retry");
1299
+ }, 2e3);
1300
+ }
1301
+ }
1302
+ if (hasReceivedResults && !hasReceivedAi) {
1303
+ this.stopLoadingAnimation();
1304
+ this.setState("done");
1305
+ setTimeout(() => {
1306
+ if (this.state === "done") this.setState("retry");
1307
+ }, 2e3);
1308
+ }
1309
+ } catch (error) {
1310
+ if (error instanceof Error) {
1311
+ if (error.name === "AbortError") return;
1312
+ this.renderError(error.message);
1313
+ this.config.onError(error);
1314
+ }
1315
+ this.setState("retry");
1316
+ this.stopLoadingAnimation();
1317
+ }
1318
+ }
1319
+ /** Parse SSE events from a buffer string */
1320
+ parseSSEEvents(buffer) {
1321
+ const parsed = [];
1322
+ const lines = buffer.split("\n");
1323
+ let currentEvent = {};
1324
+ let remaining = "";
1325
+ for (let i = 0; i < lines.length; i++) {
1326
+ const line = lines[i];
1327
+ if (i === lines.length - 1 && line !== "") {
1328
+ remaining = line;
1329
+ continue;
1330
+ }
1331
+ if (line.startsWith("event: ")) currentEvent.type = line.slice(7).trim();
1332
+ else if (line.startsWith("data: ")) currentEvent.data = line.slice(6);
1333
+ else if (line === "" && currentEvent.type && currentEvent.data) {
1334
+ try {
1335
+ const data = JSON.parse(currentEvent.data);
1336
+ parsed.push({
1337
+ type: currentEvent.type,
1338
+ data
1339
+ });
1340
+ } catch (e) {
1341
+ console.error("Failed to parse SSE data:", e);
1342
+ }
1343
+ currentEvent = {};
1344
+ }
1345
+ }
1346
+ if (currentEvent.type || currentEvent.data) {
1347
+ if (currentEvent.type) remaining = `event: ${currentEvent.type}\n` + remaining;
1348
+ if (currentEvent.data) remaining = (remaining ? remaining + "\n" : "") + `data: ${currentEvent.data}`;
1349
+ }
1350
+ return {
1351
+ parsed,
1352
+ remaining
1353
+ };
1354
+ }
1355
+ /** Handle reset */
1356
+ handleReset() {
1357
+ this.query = "";
1358
+ if (this.searchInput) this.searchInput.value = "";
1359
+ this.setState("focused");
1360
+ this.searchInput?.focus();
1361
+ if (this.resultsContainer) this.resultsContainer.classList.remove("bonsai-visible");
1362
+ if (this.suggestionsDropdown) {
1363
+ this.renderSuggestions();
1364
+ this.suggestionsDropdown.classList.add("bonsai-visible");
1365
+ }
1366
+ }
1367
+ /** Set the current state and update UI */
1368
+ setState(newState) {
1369
+ this.state = newState;
1370
+ this.updateActionButtons();
1371
+ this.updateInputState();
1372
+ }
1373
+ /** Update action buttons visibility based on state */
1374
+ updateActionButtons() {
1375
+ this.submitBtn?.classList.remove("bonsai-visible");
1376
+ this.loadingState?.classList.remove("bonsai-visible");
1377
+ this.doneState?.classList.remove("bonsai-visible");
1378
+ this.resetBtn?.classList.remove("bonsai-visible");
1379
+ switch (this.state) {
1380
+ case "filled":
1381
+ this.submitBtn?.classList.add("bonsai-visible");
1382
+ break;
1383
+ case "loading":
1384
+ this.loadingState?.classList.add("bonsai-visible");
1385
+ break;
1386
+ case "done":
1387
+ this.doneState?.classList.add("bonsai-visible");
1388
+ break;
1389
+ case "retry":
1390
+ this.resetBtn?.classList.add("bonsai-visible");
1391
+ break;
1392
+ }
1393
+ }
1394
+ /** Update input state (disabled only during loading) */
1395
+ updateInputState() {
1396
+ if (!this.searchInput) return;
1397
+ const isDisabled = this.state === "loading";
1398
+ const wasDisabled = this.searchInput.disabled;
1399
+ this.searchInput.disabled = isDisabled;
1400
+ if (wasDisabled && !isDisabled && this.query) this.searchInput.value = this.query;
1401
+ }
1402
+ /** Start loading animation */
1403
+ startLoadingAnimation() {
1404
+ this.loadingIndex = 0;
1405
+ this.updateLoadingText();
1406
+ this.loadingInterval = window.setInterval(() => {
1407
+ if (this.loadingIndex < this.loadingStates.length - 1) {
1408
+ this.loadingIndex++;
1409
+ this.updateLoadingText();
1410
+ }
1411
+ }, 1e3);
1412
+ }
1413
+ /** Update loading text position */
1414
+ updateLoadingText() {
1415
+ if (this.loadingTextInner) {
1416
+ const offset = this.loadingIndex * 1.25;
1417
+ this.loadingTextInner.style.transform = `translateY(-${offset}rem)`;
1418
+ }
1419
+ }
1420
+ /** Stop loading animation */
1421
+ stopLoadingAnimation() {
1422
+ if (this.loadingInterval !== null) {
1423
+ window.clearInterval(this.loadingInterval);
1424
+ this.loadingInterval = null;
1425
+ }
1426
+ }
1427
+ /** Show recommendations loading indicator */
1428
+ showRecommendationsLoading() {
1429
+ if (!this.resultsContainer) return;
1430
+ this.hideRecommendationsLoading();
1431
+ const loadingIndicator = document.createElement("div");
1432
+ loadingIndicator.className = this.buildClassName("bonsai-recommendations-loading");
1433
+ this.applyPart(loadingIndicator, "recommendations-loading");
1434
+ loadingIndicator.innerHTML = `
1435
+ <div class="${this.buildClassName("bonsai-recommendations-loading-spinner")}">
1436
+ ${ICONS.spinner}
1437
+ </div>
1438
+ <span class="${this.buildClassName("bonsai-recommendations-loading-text")}">Loading recommendations...</span>
1439
+ `;
1440
+ const loadingSpinner = loadingIndicator.querySelector(".bonsai-recommendations-loading-spinner");
1441
+ if (loadingSpinner) this.applyPart(loadingSpinner, "recommendations-loading-spinner");
1442
+ const loadingText = loadingIndicator.querySelector(".bonsai-recommendations-loading-text");
1443
+ if (loadingText) this.applyPart(loadingText, "recommendations-loading-text");
1444
+ this.resultsContainer.insertBefore(loadingIndicator, this.resultsContainer.firstChild);
1445
+ }
1446
+ /** Hide recommendations loading indicator */
1447
+ hideRecommendationsLoading() {
1448
+ if (!this.resultsContainer) return;
1449
+ const loadingIndicator = this.resultsContainer.querySelector(".bonsai-recommendations-loading");
1450
+ if (loadingIndicator) loadingIndicator.remove();
1451
+ }
1452
+ /** Render search results */
1453
+ renderResults(recommendations, results) {
1454
+ if (!this.resultsContainer) return;
1455
+ if (results.length === 0 && recommendations.length === 0) {
1456
+ this.renderEmpty();
1457
+ return;
1458
+ }
1459
+ const recommendedProductIds = /* @__PURE__ */ new Set();
1460
+ for (const rec of recommendations) if (rec.productIds) for (const id of rec.productIds) recommendedProductIds.add(id);
1461
+ if (recommendations.length > 0) for (const rec of recommendations) {
1462
+ const summarySection = document.createElement("div");
1463
+ summarySection.className = this.buildClassName("bonsai-summary");
1464
+ this.applyPart(summarySection, "summary");
1465
+ const summaryText = document.createElement(this.config.markdown ? "div" : "p");
1466
+ summaryText.className = this.buildClassName("bonsai-summary-text");
1467
+ this.applyPart(summaryText, "summary-text");
1468
+ if (this.config.markdown) summaryText.innerHTML = marked.marked.parse(rec.text, { async: false });
1469
+ else summaryText.textContent = rec.text;
1470
+ summarySection.appendChild(summaryText);
1471
+ if (rec.productIds && rec.productIds.length > 0) {
1472
+ const uniqueProductIds = [...new Set(rec.productIds)];
1473
+ const recResults = [];
1474
+ for (const prodId of uniqueProductIds) {
1475
+ const product = results.find((r) => r.id === prodId);
1476
+ if (product && product.publicUrl && product.publicUrl.trim() !== "") recResults.push(product);
1477
+ }
1478
+ const recProducts = document.createElement("div");
1479
+ const recGrid = document.createElement("div");
1480
+ if (recResults.length > 0) {
1481
+ recProducts.className = this.buildClassName("bonsai-recommendation-products");
1482
+ this.applyPart(recProducts, "recommendation-products");
1483
+ const recHeader = document.createElement("h4");
1484
+ recHeader.className = this.buildClassName("bonsai-recommendation-products-header");
1485
+ this.applyPart(recHeader, "recommendation-products-header");
1486
+ recHeader.textContent = this.config.featuredItemsLabel;
1487
+ recProducts.appendChild(recHeader);
1488
+ recGrid.className = this.buildClassName("bonsai-results-grid");
1489
+ this.applyPart(recGrid, "results-grid");
1490
+ for (const product of recResults) {
1491
+ const card = this.createResultCard(product);
1492
+ recGrid.appendChild(card);
1493
+ }
1494
+ }
1495
+ recProducts.appendChild(recGrid);
1496
+ summarySection.appendChild(recProducts);
1497
+ }
1498
+ this.resultsContainer.prepend(summarySection);
1499
+ }
1500
+ else if (results.length > 0) {
1501
+ const resultsSection = document.createElement("div");
1502
+ this.applyPart(resultsSection, "results-section");
1503
+ const resultsHeader = document.createElement("h3");
1504
+ resultsHeader.className = this.buildClassName("bonsai-results-header");
1505
+ this.applyPart(resultsHeader, "results-header");
1506
+ resultsHeader.textContent = recommendedProductIds.size > 0 ? this.config.moreItemsLabel : this.config.itemsLabel;
1507
+ resultsSection.appendChild(resultsHeader);
1508
+ const resultsGrid = document.createElement("div");
1509
+ resultsGrid.className = this.buildClassName("bonsai-results-grid");
1510
+ this.applyPart(resultsGrid, "results-grid");
1511
+ for (const result of results) {
1512
+ const card = this.createResultCard(result);
1513
+ resultsGrid.appendChild(card);
1514
+ }
1515
+ resultsSection.appendChild(resultsGrid);
1516
+ this.resultsContainer.appendChild(resultsSection);
1517
+ }
1518
+ this.resultsContainer.classList.add("bonsai-visible");
1519
+ setTimeout(() => {
1520
+ this.resultsContainer.querySelectorAll(".bonsai-result-card").forEach((card, index) => {
1521
+ setTimeout(() => {
1522
+ card.classList.add("bonsai-animate-in");
1523
+ }, index * 100);
1524
+ });
1525
+ }, 50);
1526
+ }
1527
+ /** Generate srcset attribute for an image URL */
1528
+ getSrcSet(url) {
1529
+ if (!this.config.generateSrcSet || !this.config.imageHostname || this.config.srcSetSizes.length === 0) return "";
1530
+ const filename = url.split("/").pop();
1531
+ if (!filename) return "";
1532
+ let baseUrl = `//${this.config.imageHostname}/cdn/shop/`;
1533
+ if (url.includes("/products/")) baseUrl += "products/";
1534
+ else baseUrl += "files/";
1535
+ const fullPath = baseUrl + filename;
1536
+ const srcSetEntries = [];
1537
+ for (const size of this.config.srcSetSizes) {
1538
+ const entry = fullPath.replace(/(\.\w+)(\?v=\d+)?$/, `_${size.width}x${size.height}_${size.crop}$1$2 ${size.width}w`);
1539
+ srcSetEntries.push(entry);
1540
+ }
1541
+ return srcSetEntries.join(", ");
1542
+ }
1543
+ /** Resolve an image value that may be a string URL or an object with sourceUrl/source_url */
1544
+ resolveImageUrl(image) {
1545
+ let url = image;
1546
+ if (url && typeof url === "object") {
1547
+ const obj = url;
1548
+ url = obj.sourceUrl || obj.source_url || null;
1549
+ }
1550
+ if (typeof url !== "string" || url.length === 0) return null;
1551
+ try {
1552
+ const parsed = new URL(url);
1553
+ if (parsed.protocol === "http:" || parsed.protocol === "https:") return url;
1554
+ } catch {}
1555
+ return null;
1556
+ }
1557
+ /** Create a result card */
1558
+ createResultCard(result) {
1559
+ const card = document.createElement("a");
1560
+ card.href = result.publicUrl;
1561
+ card.className = this.buildClassName("bonsai-result-card");
1562
+ this.applyPart(card, "result-card");
1563
+ card.target = "_blank";
1564
+ card.rel = "noopener";
1565
+ const imageUrl = this.resolveImageUrl(result.image);
1566
+ if (imageUrl) {
1567
+ const imgWrapper = document.createElement("div");
1568
+ imgWrapper.className = this.buildClassName("bonsai-result-image-wrapper");
1569
+ this.applyPart(imgWrapper, "result-image-wrapper");
1570
+ card.appendChild(imgWrapper);
1571
+ const img = document.createElement("img");
1572
+ img.className = this.buildClassName("bonsai-result-image");
1573
+ this.applyPart(img, "result-image");
1574
+ img.src = imageUrl;
1575
+ img.alt = result.name;
1576
+ img.loading = "lazy";
1577
+ img.style.objectFit = this.config.imageObjectFit;
1578
+ const srcset = this.getSrcSet(imageUrl);
1579
+ if (srcset) img.srcset = srcset;
1580
+ this.config.onImg(result, img, imgWrapper, this);
1581
+ imgWrapper.appendChild(img);
1582
+ }
1583
+ const content = document.createElement("div");
1584
+ content.className = this.buildClassName("bonsai-result-content");
1585
+ this.applyPart(content, "result-content");
1586
+ const title = document.createElement("h4");
1587
+ title.className = this.buildClassName("bonsai-result-title");
1588
+ this.applyPart(title, "result-title");
1589
+ title.textContent = result.name;
1590
+ content.appendChild(title);
1591
+ if (this.config.renderPrice && result.price) {
1592
+ const priceContainer = document.createElement("div");
1593
+ priceContainer.className = this.buildClassName("bonsai-result-price-container");
1594
+ this.applyPart(priceContainer, "result-price-container");
1595
+ const price = document.createElement("span");
1596
+ price.className = this.buildClassName("bonsai-result-price");
1597
+ this.applyPart(price, "result-price");
1598
+ price.textContent = this.config.onPrice(result, result.price, priceContainer);
1599
+ priceContainer.appendChild(price);
1600
+ if (result.compareAtPrice && result.compareAtPrice !== result.price) {
1601
+ const compareAt = document.createElement("span");
1602
+ compareAt.className = this.buildClassName("bonsai-result-compare-at");
1603
+ this.applyPart(compareAt, "result-compare-at");
1604
+ compareAt.textContent = this.config.onPrice(result, result.compareAtPrice);
1605
+ priceContainer.appendChild(compareAt);
1606
+ }
1607
+ content.appendChild(priceContainer);
1608
+ }
1609
+ if (result.description) {
1610
+ const caption = document.createElement("p");
1611
+ caption.className = this.buildClassName("bonsai-result-caption");
1612
+ this.applyPart(caption, "result-caption");
1613
+ caption.textContent = result.description;
1614
+ content.appendChild(caption);
1615
+ } else if (!this.config.renderPrice && result.price) {
1616
+ const caption = document.createElement("p");
1617
+ caption.className = this.buildClassName("bonsai-result-caption");
1618
+ this.applyPart(caption, "result-caption");
1619
+ caption.textContent = this.config.onPrice(result, result.price, caption);
1620
+ content.appendChild(caption);
1621
+ }
1622
+ card.appendChild(content);
1623
+ return card;
1624
+ }
1625
+ /** Render empty state */
1626
+ renderEmpty() {
1627
+ if (!this.resultsContainer) return;
1628
+ const empty = document.createElement("p");
1629
+ empty.className = this.buildClassName("bonsai-empty-state");
1630
+ this.applyPart(empty, "empty-state");
1631
+ empty.textContent = "No matching products found. Try a different search query.";
1632
+ this.resultsContainer.appendChild(empty);
1633
+ this.resultsContainer.classList.add("bonsai-visible");
1634
+ }
1635
+ /** Render error state */
1636
+ renderError(message) {
1637
+ if (!this.resultsContainer) return;
1638
+ this.resultsContainer.innerHTML = "";
1639
+ const error = document.createElement("div");
1640
+ error.className = this.buildClassName("bonsai-error");
1641
+ this.applyPart(error, "error");
1642
+ error.textContent = `Error: ${message}`;
1643
+ this.resultsContainer.appendChild(error);
1644
+ this.resultsContainer.classList.add("bonsai-visible");
1645
+ }
1646
+ /** Check for query parameter in URL or localStorage */
1647
+ checkForQueryParam() {
1648
+ const urlParams = new URLSearchParams(window.location.search);
1649
+ let queryParam = urlParams.get("q") || urlParams.get("query");
1650
+ if (!queryParam) try {
1651
+ const storedQuery = localStorage.getItem("bonsai-search-query");
1652
+ if (storedQuery) {
1653
+ queryParam = storedQuery;
1654
+ localStorage.removeItem("bonsai-search-query");
1655
+ }
1656
+ } catch {}
1657
+ if (queryParam && this.searchInput) {
1658
+ this.searchInput.value = queryParam;
1659
+ this.query = queryParam;
1660
+ this.handleSubmit();
1661
+ }
1662
+ }
1663
+ /** Set a suggestion programmatically and trigger search */
1664
+ setSuggestion(suggestion) {
1665
+ if (this.searchInput) {
1666
+ this.searchInput.value = suggestion;
1667
+ this.query = suggestion;
1668
+ this.handleSubmit();
1669
+ }
1670
+ }
1671
+ /** Clear the search and results */
1672
+ clear() {
1673
+ this.handleReset();
1674
+ if (this.resultsContainer) {
1675
+ this.resultsContainer.innerHTML = "";
1676
+ this.resultsContainer.classList.remove("bonsai-visible");
1677
+ }
1678
+ }
1679
+ /** Focus the search input */
1680
+ focus() {
1681
+ this.searchInput?.focus();
1682
+ }
1683
+ /** Destroy the instance and clean up */
1684
+ destroy() {
1685
+ if (this.abortController) this.abortController.abort();
1686
+ if (this.debounceTimer !== null) window.clearTimeout(this.debounceTimer);
1687
+ if (this.loadingInterval !== null) window.clearInterval(this.loadingInterval);
1688
+ this.container.innerHTML = "";
1689
+ }
1690
+ };
1691
+ if (typeof window !== "undefined") window.BonsaiSearch = BonsaiSearch;
1692
+
1693
+ //#endregion
1694
+ exports.BonsaiSearch = BonsaiSearch;
1695
+ exports.BonsaiSearchBarElement = BonsaiSearchBarElement;
1696
+ exports.BonsaiSearchElement = BonsaiSearchElement;
1697
+ return exports;
1698
+ })({}, marked);