# Barneo Search Widget

Библиотека компонентов для поиска по каталогу Barneo с помощью Ensi

## 📋 Содержание

- [Установка](#-установка)
- [Подключение и конфигурация](#️-подключение-и-конфигурация)
- [Использование библиотеки](#-использование-библиотеки)
- [Компоненты](#-компоненты)
- [Кастомные слоты](#-кастомные-слоты---примеры)
- [Аналитика](#-аналитика)
- [Дополнительные возможности](#-дополнительные-возможности)
- [TypeScript поддержка](#-typescript-поддержка)
- [Поддержка](#-поддержка)
- [Лицензия](#-лицензия)

## 📦 Установка

```bash
npm install barneo-search-widget-lib
# или
yarn add barneo-search-widget-lib
```

## ⚙️ Подключение и конфигурация

### Vue 3

#### Глобальная регистрация (рекомендуется)

```typescript
// main.ts
import { createApp } from "vue";
import App from "./App.vue";
import BarneoSearchPlugin from "barneo-search-widget-lib";

const app = createApp(App);

const config = {
  api: {
    baseUrl: "https://api.search.ensi.cloud",
    token: "your-api-token",
    customerId: "4",
    locationId: "1",
    apiVersion: "v1",
  },
  searchWidget: {
    placeholder: "Найти товары",
    debounceMs: 300,
    minSearchLength: 2,
    darkMode: false,
    focusMode: false,
  },
  filtersWidget: {
    maxValuesPerFilter: 5,
    debounceMs: 300,
    darkMode: false,
  },
  productsWidget: {
    itemsPerPage: 20,
    mobileItemWidth: 160,
    desktopItemWidth: 200,
    darkMode: false,
    showPagination: true,
    showTotalCount: true,
  },
  sortWidget: {
    darkMode: false,
    defaultSort: "relevance",
  },
};

app.use(BarneoSearchPlugin, config);
app.mount("#app");
```

**Примечание**: URL менеджер инициализируется в компонентах через `useUrlManagerInit()` composable, а не автоматически в плагине.

#### Локальная регистрация

```vue
<template>
  <div>
    <barneo-search-widget v-model="query" />
    <barneo-filters-widget />
    <barneo-sort-widget />
    <barneo-products-widget />
  </div>
</template>

<script setup>
import { ref } from "vue";
import {
  BarneoSearchWidget,
  BarneoFiltersWidget,
  BarneoSortWidget,
  BarneoProductsWidget,
} from "barneo-search-widget-lib";

const query = ref("");
</script>
```

### Nuxt 3

#### Создание плагина

```typescript
// plugins/barneo-search.client.ts
import { defineNuxtPlugin } from "nuxt/app";
import BarneoSearchPlugin from "barneo-search-widget-lib";

export default defineNuxtPlugin((nuxtApp: any) => {
  const config = {
    api: {
      baseUrl:
        process.env.NUXT_BARNEO_ENSI_BASE_URL ||
        "https://api.search.ensi.cloud",
      token:
        process.env.NUXT_BARNEO_ENSI_TOKEN ||
        "95246bb090387d5c92186f1b6d25cc72",
      customerId: process.env.NUXT_BARNEO_ENSI_CUSTOMER_ID || "4",
      locationId: process.env.NUXT_BARNEO_ENSI_LOCATION_ID || "1",
      apiVersion: process.env.NUXT_BARNEO_ENSI_API_VERSION || "v1",
    },
    searchWidget: {
      placeholder: "Найти товары",
      debounceMs: 300,
      minSearchLength: 2,
      darkMode: false,
      focusMode: false,
    },
    filtersWidget: {
      maxValuesPerFilter: 5,
      debounceMs: 300,
      darkMode: false,
    },
    productsWidget: {
      itemsPerPage: 20,
      mobileItemWidth: 160,
      desktopItemWidth: 200,
      darkMode: false,
      showPagination: true,
      showTotalCount: true,
    },
    sortWidget: {
      darkMode: false,
      defaultSort: "relevance",
    },
  };

  // Используем плагин библиотеки для глобальной регистрации
  nuxtApp.vueApp.use(BarneoSearchPlugin, config);

  // URL менеджер инициализируется в компонентах через useUrlManagerInit()
});
```

#### Настройка в nuxt.config.ts

```typescript
// nuxt.config.ts
export default defineNuxtConfig({
  css: ["barneo-search-widget-lib/dist/style.css"],
  runtimeConfig: {
    public: {
      barneoEnsiBaseUrl: process.env.NUXT_BARNEO_ENSI_BASE_URL,
      barneoEnsiToken: process.env.NUXT_BARNEO_ENSI_TOKEN,
      barneoEnsiCustomerId: process.env.NUXT_BARNEO_ENSI_CUSTOMER_ID,
      barneoEnsiLocationId: process.env.NUXT_BARNEO_ENSI_LOCATION_ID,
      barneoEnsiApiVersion: process.env.NUXT_BARNEO_ENSI_API_VERSION,
    },
  },
});
```

### Описание конфигурации

#### API конфигурация

```typescript
api: {
  baseUrl: string,        // Базовый URL API (обязательно)
  token: string,          // API токен для авторизации (обязательно)
  customerId: string,     // ID клиента (обязательно)
  locationId: string,     // ID локации (обязательно)
  apiVersion?: string     // Версия API (опционально, если не указана используется только baseUrl)
}
```

#### Конфигурация поиска

```typescript
searchWidget: {
  placeholder: string,    // Placeholder для поля поиска (по умолчанию: 'Поиск...')
  debounceMs: number,     // Задержка перед отправкой запроса в мс (по умолчанию: 300)
  minSearchLength: number, // Минимальная длина для поиска (по умолчанию: 2)
  darkMode: boolean,      // Принудительная темная тема (по умолчанию: false)
  focusMode: boolean      // Автофокус при монтировании (по умолчанию: false)
}
```

#### Конфигурация фильтров

```typescript
filtersWidget: {
  maxValuesPerFilter: number, // Максимум значений в фильтре (по умолчанию: 5)
  debounceMs: number,         // Задержка применения фильтров в мс (по умолчанию: 300)
  darkMode: boolean           // Принудительная темная тема (по умолчанию: false)
}
```

#### Конфигурация товаров

```typescript
productsWidget: {
  itemsPerPage: number,       // Количество товаров на странице (по умолчанию: 20)
  mobileItemWidth: number,    // Ширина элемента на мобильных (по умолчанию: 160)
  desktopItemWidth: number,   // Ширина элемента на десктопе (по умолчанию: 200)
  darkMode: boolean,          // Принудительная темная тема (по умолчанию: false)
  showPagination: boolean,    // Показывать пагинацию (по умолчанию: true)
  showTotalCount: boolean     // Показывать общее количество (по умолчанию: true)
}
```

#### Конфигурация сортировки

```typescript
sortWidget: {
  darkMode: boolean,          // Принудительная темная тема (по умолчанию: false)
  defaultSort: string         // Сортировка по умолчанию (по умолчанию: 'relevance')
}
```

## 🚀 Использование библиотеки

### Базовое использование

```vue
<template>
  <div>
    <barneo-search-widget v-model="searchQuery" @search="handleSearch" />
    <barneo-filters-widget @filter-applied="handleFilter" />
    <barneo-sort-widget @sort-changed="handleSort" />
    <barneo-products-widget @product-selected="handleProductSelect" />
    <barneo-range-selector
      ref="rangeSelector"
      :min="0"
      :max="10000"
      @change="handleRangeChange"
    />
  </div>
</template>

<script setup>
import { ref } from "vue";

const searchQuery = ref("");
const rangeSelector = ref();

const handleSearch = (query) => {
  console.log("Поиск выполнен:", query);
};

const handleFilter = (filterCode, valueId, isSelected) => {
  console.log("Фильтр применен:", filterCode, valueId, isSelected);
};

const handleSort = (sortValue) => {
  console.log("Сортировка изменена:", sortValue);
};

const handleProductSelect = (product) => {
  console.log("Товар выбран:", product);
};

const handleRangeChange = ([min, max]) => {
  console.log("Диапазон изменен:", { min, max });
};
</script>
```

### Использование composables

```vue
<template>
  <div>
    <input v-model="searchQuery" @input="handleSearch" />
    <div v-if="isLoading">Загрузка...</div>
    <div v-else>
      <div v-for="result in searchResults" :key="result.id">
        {{ result.name }}
      </div>
    </div>
  </div>
</template>

<script setup>
import { ref } from "vue";
import { useSearchWidget, SearchApiService } from "barneo-search-widget-lib";

const searchQuery = ref("");
const isLoading = ref(false);
const searchResults = ref([]);

const { performSearch } = useSearchWidget();
const apiService = new SearchApiService({
  baseUrl: "https://api.search.ensi.cloud",
  token: "your-token",
});

const handleSearch = async () => {
  if (searchQuery.value.length < 2) return;

  isLoading.value = true;
  try {
    const results = await performSearch(searchQuery.value, apiService);
    searchResults.value = results.data?.full_products || [];
  } catch (error) {
    console.error("Ошибка поиска:", error);
  } finally {
    isLoading.value = false;
  }
};
</script>
```

## 🧩 Компоненты

### BarneoSearchWidget

Компонент поиска с автодополнением, быстрыми результатами и полным поиском.

#### Props

```typescript
interface SearchWidgetProps {
  modelValue?: string; // Значение поля поиска (v-model)
  placeholder?: string; // Placeholder для поля ввода
  debounceMs?: number; // Задержка перед отправкой запроса
  minSearchLength?: number; // Минимальная длина для поиска
  darkMode?: boolean; // Принудительная темная тема
  focusMode?: boolean; // Автофокус при монтировании
  apiService?: SearchApiService; // Кастомный API сервис
}
```

#### События

- `@search` - выполнен поиск (автоматический или ручной)
  ```typescript
  (query: string) => void
  ```
- `@select` - выбран товар из результатов

  ```typescript
  (product: Product, event?: MouseEvent) => void
  ```

  **Примечание**: Событие поддерживает `MouseEvent` для обработки средней кнопки мыши. При `event.button === 1` рекомендуется открывать товар в новой вкладке.

- `@searchResults` - получены результаты поиска
  ```typescript
  (results: SearchResults) => void
  ```
- `@loading` - изменение состояния загрузки
  ```typescript
  (isLoading: boolean) => void
  ```
- `@error` - произошла ошибка
  ```typescript
  (error: string) => void
  ```
- `@focus` - поле получило фокус
  ```typescript
  () => void
  ```
- `@blur` - поле потеряло фокус
  ```typescript
  () => void
  ```
- `@input` - изменение значения поля
  ```typescript
  (value: string) => void
  ```

#### Слоты

- `search-header` - заголовок компонента поиска

```vue
<template #search-header="{ config }">
  <h1>🔍 Поиск товаров</h1>
</template>
```

- `search-input` - кастомное поле ввода
  ```vue
  <template #search-input="{ value, onInput, onFocus, onBlur }">
    <input :value="value" @input="onInput" @focus="onFocus" @blur="onBlur" />
  </template>
  ```
- `search-results` - кастомные результаты поиска
  ```vue
  <template
    #search-results="{ results, isLoading, selectProduct, handleAuxClick }"
  >
    <div v-if="isLoading">Загрузка...</div>
    <div v-else>
      <div
        v-for="result in results"
        :key="result.id"
        @click="(event) => selectProduct(result, event)"
        @auxclick="(event) => handleAuxClick(result, event)"
      >
        {{ result.name }}
      </div>
    </div>
  </template>
  ```
- `search-empty` - пустое состояние
  ```vue
  <template #search-empty="{ query }">
    <p>По запросу "{{ query }}" ничего не найдено</p>
  </template>
  ```
- `loading` - состояние загрузки
  ```vue
  <template #loading="{ isLoading }">
    <div v-if="isLoading">⏳ Загрузка...</div>
  </template>
  ```
- `error` - состояние ошибки
  ```vue
  <template #error="{ error }">
    <div v-if="error">❌ {{ error }}</div>
  </template>
  ```

### BarneoFiltersWidget

Компонент фильтров с динамической загрузкой, автоматическим применением и кастомными слотами.

#### Props

```typescript
interface FiltersWidgetProps {
  loading?: boolean; // Состояние загрузки
  error?: string | null; // Текст ошибки
  activeFilters?: Record<string, string[]>; // Активные фильтры
  config?: FiltersWidgetConfig; // Локальная конфигурация
  apiService?: SearchApiService; // Кастомный API сервис
}
```

#### События

- `@filter-applied` - применен фильтр
  ```typescript
  (filterCode: string, valueId: string, isSelected: boolean) => void
  ```
- `@filter-removed` - удален фильтр
  ```typescript
  (filterCode: string, valueId: string) => void
  ```
- `@filters-reset` - сброшены все фильтры
  ```typescript
  () => void
  ```
- `@filters-applied` - применены фильтры
  ```typescript
  (activeFilters: Record<string, string[]>) => void
  ```
- `@active-filters-changed` - изменились активные фильтры
  ```typescript
  (activeFilters: Record<string, string[]>) => void
  ```
- `@loading` - изменение состояния загрузки
  ```typescript
  (isLoading: boolean) => void
  ```
- `@error` - произошла ошибка
  ```typescript
  (error: string) => void
  ```

#### Слоты

- `filters-title` - заголовок фильтров

```vue
<template #filters-title="{ config }">
  <h2>🎯 Фильтры ({{ config.maxValuesPerFilter }} значений)</h2>
</template>
```

- `filters-skeleton` - скелетон загрузки
  ```vue
  <template #filters-skeleton="{ isLoading, skeletonData }">
    <div v-if="isLoading">
      <div v-for="group in skeletonData" :key="group.id">
        <!-- Скелетон группы -->
      </div>
    </div>
  </template>
  ```

````
- `filter-group` - группа фильтров
```vue
  <template
    #filter-group="{ filter, visibleValues, isExpanded, toggleFilterExpansion }"
  >
    <div class="filter-group">
      <h3>{{ filter.name }}</h3>
      <div v-if="isExpanded">
        <!-- Значения фильтров -->
        </div>
      <button @click="toggleFilterExpansion(filter.code)">
        {{ isExpanded ? "Свернуть" : "Развернуть" }}
          </button>
        </div>
  </template>
````

- `filter-value` - значение фильтра
  ```vue
  <template #filter-value="{ filterCode, value, isActive, handleClick }">
    <div @click="handleClick(filterCode, value.id)">
      <input type="checkbox" :checked="isActive" />
      <label>{{ value.name }}</label>
    </div>
  </template>
  ```
- `filter-checkbox` - кастомный чекбокс
  ```vue
  <template #filter-checkbox="{ filterCode, value, isActive, handleClick }">
    <div class="custom-checkbox">
      <input
        type="checkbox"
        :checked="isActive"
        @change="handleClick(filterCode, value.id)"
      />
      <label>{{ value.name }}</label>
    </div>
  </template>
  ```

````
- `filter-toggle` - кнопка "Показать все"
```vue
  <template #filter-toggle="{ filterCode, isExpanded, handleToggle }">
    <button @click="handleToggle(filterCode)">
      {{ isExpanded ? "Скрыть" : "Показать все" }}
    </button>
  </template>
````

- `filter-range` - кастомный range фильтр с под-слотами

  ```vue
  <template
    #filter-range="{
      filter,
      handleRangeSelectorChange,
      rangeSelectorValue,
      rangeStep,
      rangeFormatValue,
      isRangeActive,
      rangeMin,
      rangeMax,
    }"
  >
    <div class="custom-range-filter">
      <!-- Заголовок range фильтра -->
      <template #filter-range-header="{ filter }">
        <h4>{{ filter.description }}</h4>
      </template>

      <!-- Индикатор активного диапазона -->
      <template #filter-range-indicator="{ isRangeActive, rangeFormatValue }">
        <div v-if="isRangeActive" class="range-indicator">
          Выбран диапазон: {{ rangeFormatValue }}
        </div>
      </template>

      <!-- Кнопка сброса -->
      <template
        #filter-range-reset="{ handleRangeSelectorChange, rangeMin, rangeMax }"
      >
        <button
          @click="handleRangeSelectorChange(filter.code, [rangeMin, rangeMax])"
        >
          Сбросить
        </button>
      </template>
    </div>
  </template>
  ```

- `filters-empty` - пустое состояние
  ```vue
  <template #filters-empty="{ hasFilters }">
    <div v-if="!hasFilters">
      <p>🔍 Выполните поиск для получения фильтров</p>
    </div>
  </template>
  ```
- `loading` - состояние загрузки
  ```vue
  <template #loading="{ isLoading }">
    <div v-if="isLoading">⏳ Загрузка фильтров...</div>
  </template>
  ```
- `error` - состояние ошибки
  ```vue
  <template #error="{ error }">
    <div v-if="error">❌ {{ error }}</div>
  </template>
  ```

````

### BarneoSortWidget

Компонент сортировки с предустановленными опциями и кастомными слотами.

#### Props

```typescript
interface SortWidgetProps {
  modelValue?: string; // Текущая сортировка (v-model)
  options?: SortOption[]; // Кастомные опции сортировки
  showLabels?: boolean; // Показывать метки опций
  defaultSort?: string; // Сортировка по умолчанию
  darkMode?: boolean; // Принудительная темная тема
  disabled?: boolean; // Отключить компонент
}
````

#### События

- `@update:modelValue` - изменена сортировка

```typescript
  (sortValue: string) => void
```

- `@sort-changed` - изменена сортировка
  ```typescript
  (sortValue: string) => void
  ```
- `@loading` - изменение состояния загрузки
  ```typescript
  (isLoading: boolean) => void
  ```
- `@error` - произошла ошибка
  ```typescript
  (error: string) => void
  ```

#### Слоты

- `sort-options` - кастомный контейнер с опциями сортировки

```vue
<template
  #sort-options="{ sortOptions, currentSort, isLoading, handleSortChange }"
>
  <div class="custom-sort-container">
    <div
      v-for="option in sortOptions"
      :key="option.value"
      class="custom-sort-option"
      :class="{ active: option.value === currentSort }"
      @click="handleSortChange(option.value)"
    >
      {{ option.label }}
    </div>
  </div>
</template>
```

- `sort-option` - кастомная опция сортировки

```vue
<template #sort-option="{ option, isActive, isDisabled }">
  <div
    class="custom-sort-option"
    :class="{ active: isActive, disabled: isDisabled }"
  >
    <span>{{ option.label }}</span>
    <span v-if="isActive" class="loading-indicator">⏳</span>
  </div>
</template>
```

- `loading` - состояние загрузки
  ```vue
  <template #loading="{ isLoading }">
    <div v-if="isLoading">⏳ Обновление...</div>
  </template>
  ```
- `error` - состояние ошибки

```vue
<template #error="{ error }">
  <div v-if="error">❌ {{ error }}</div>
</template>
```

### BarneoRangeSelector

Компонент для выбора диапазонов с прогрессивной шкалой, двухползунковым слайдером и капсуловидными полями ввода.

#### Особенности

- **Прогрессивная шкала**: Более точный выбор в нижних диапазонах (первые 50% движения покрывают 25% значений)
- **Двухползунковый слайдер**: Интуитивный выбор минимального и максимального значения
- **Капсуловидные поля ввода**: Прямой ввод значений с метками "ОТ" и "ДО"
- **Конвертация цен**: Автоматическая конвертация копеек в рубли для ценовых фильтров
- **Адаптивная разделительная линия**: Линия между полями ввода растягивается на доступное пространство
- **События при отпускании**: События эмитятся только при отпускании ползунка для оптимизации производительности

#### Props

```typescript
interface RangeSelectorProps {
  min: number; // Минимальное значение
  max: number; // Максимальное значение
  value?: [number, number]; // Текущие значения [min, max]
  step?: number; // Шаг изменения (по умолчанию: 1)
  formatValue?: (value: number) => string; // Функция форматирования значений
}
```

#### События

- `@update:value` - изменены значения
  ```typescript
  (value: [number, number]) => void
  ```
- `@change` - изменены значения (эмитится при отпускании ползунка или потере фокуса)
  ```typescript
  (value: [number, number]) => void
  ```

#### Методы (через ref)

```typescript
interface RangeSelectorExpose {
  setValue: (min: number, max: number) => void; // Установить значения
  getValue: () => [number, number]; // Получить текущие значения
}
```

#### Пример использования

```vue
<template>
  <barneo-range-selector
    ref="rangeSelector"
    :min="0"
    :max="10000"
    :value="[1000, 5000]"
    :step="100"
    :format-value="formatPrice"
    @change="handleRangeChange"
  />
</template>

<script setup>
import { ref } from "vue";

const rangeSelector = ref();

const formatPrice = (value: number) => {
  return `${Math.floor(value / 100)} ₽`; // Конвертация копеек в рубли
};

const handleRangeChange = ([min, max]: [number, number]) => {
  console.log("Диапазон изменен:", { min, max });
};

// Программное изменение значений
const resetRange = () => {
  rangeSelector.value?.setValue(0, 10000);
};
</script>
```

### BarneoProductsWidget

Компонент отображения товаров с пагинацией, адаптивной сеткой и кастомными слотами.

#### Props

```typescript
interface ProductsWidgetProps {
  products?: Product[]; // Список товаров для отображения
  loading?: boolean; // Состояние загрузки
  error?: string | null; // Текст ошибки
  pagination?: PaginationState; // Состояние пагинации
  config?: ProductsWidgetConfig; // Локальная конфигурация
  apiService?: SearchApiService; // Кастомный API сервис
}
```

#### События

- `@product-selected` - выбран товар

```typescript
  (product: Product, event?: MouseEvent) => void
```

**Примечание**: Событие поддерживает `MouseEvent` для обработки средней кнопки мыши. При `event.button === 1` рекомендуется открывать товар в новой вкладке.

- `@page-changed` - изменена страница
  ```typescript
  (page: number) => void
  ```
- `@loading` - изменение состояния загрузки
  ```typescript
  (isLoading: boolean) => void
  ```
- `@error` - произошла ошибка
  ```typescript
  (error: string) => void
  ```

#### Слоты

- `products-grid` - кастомная сетка товаров

```vue
<template #products-grid="{ products, isLoading, itemWidth }">
  <div class="custom-grid" :style="{ '--item-width': itemWidth + 'px' }">
    <div v-for="product in products" :key="product.id" class="grid-item">
      <!-- Кастомная карточка товара -->
    </div>
  </div>
</template>
```

- `product-card` - кастомная карточка товара
  ```vue
  <template #product-card="{ product, index, onSelect, handleAuxClick }">
    <div
      class="custom-product-card"
      @click="(event) => onSelect(product, event)"
      @auxclick="(event) => handleAuxClick(product, event)"
    >
      <img :src="product.image" :alt="product.name" />
      <h3>{{ product.name }}</h3>
      <p class="price">{{ formatPrice(product.price) }}</p>
    </div>
  </template>
  ```
- `products-empty` - пустое состояние
  ```vue
  <template #products-empty="{ hasProducts, query }">
    <div v-if="!hasProducts && query">
      <p>По запросу "{{ query }}" товары не найдены</p>
    </div>
  </template>
  ```
- `products-pagination` - кастомная пагинация
  ```vue
  <template #products-pagination="{ pagination, onPageChange }">
    <div class="custom-pagination">
      <button
        v-for="page in pagination.totalPages"
        :key="page"
        :class="{ active: page === pagination.currentPage }"
        @click="onPageChange(page)"
      >
        {{ page }}
      </button>
    </div>
  </template>
  ```
- `loading` - состояние загрузки
  ```vue
  <template #loading="{ isLoading }">
    <div v-if="isLoading">⏳ Загрузка товаров...</div>
  </template>
  ```
- `error` - состояние ошибки
  ```vue
  <template #error="{ error }">
    <div v-if="error">❌ {{ error }}</div>
  </template>
  ```

## 🎨 Кастомные слоты - примеры

### Поиск с кастомными слотами

```vue
<barneo-search-widget v-model="query">
  <template #search-header="{ config }">
    <div class="custom-header">
      <h1>🔍 Поиск товаров</h1>
      <p>Конфигурация: {{ JSON.stringify(config) }}</p>
    </div>
  </template>
  
  <template #search-results="{ results, isLoading }">
    <div v-if="isLoading" class="loading">Загрузка...</div>
    <div v-else class="results">
      <div v-for="result in results" :key="result.id" class="result-item">
        <h3>{{ result.name }}</h3>
        <p>{{ result.description }}</p>
      </div>
    </div>
  </template>
</barneo-search-widget>
```

### Фильтры с кастомными слотами

```vue
<barneo-filters-widget>
  <template #filters-title="{ config }">
    <div class="custom-filters-title">
      🎯 Фильтры ({{ config.maxValuesPerFilter }} значений)
    </div>
  </template>
  
  <template #filter-value="{ filterCode, value, isActive, handleClick }">
    <div class="custom-filter-value" @click="handleClick(filterCode, value.id)">
      <input 
        type="checkbox" 
        :checked="isActive"
        :id="`${filterCode}-${value.id}`"
      />
      <label :for="`${filterCode}-${value.id}`">
        {{ value.name }}
      </label>
  </div>
</template>

  <template #filter-range="{ 
    filter, 
    handleRangeSelectorChange, 
    rangeSelectorValue, 
    rangeStep, 
    rangeFormatValue, 
    isRangeActive, 
    rangeMin, 
    rangeMax 
  }">
    <div class="custom-range-filter">
      <!-- Кастомный заголовок range фильтра -->
      <template #filter-range-header="{ filter }">
        <h4 class="range-title">{{ filter.description }}</h4>
      </template>
      
      <!-- Кастомный индикатор активного диапазона -->
      <template #filter-range-indicator="{ isRangeActive, rangeFormatValue }">
        <div v-if="isRangeActive" class="custom-range-indicator">
          <span class="indicator-icon">🎯</span>
          <span class="indicator-text">{{ rangeFormatValue }}</span>
        </div>
      </template>
      
      <!-- Кастомная кнопка сброса -->
      <template #filter-range-reset="{ handleRangeSelectorChange, rangeMin, rangeMax }">
        <button 
          class="custom-reset-button"
          @click="handleRangeSelectorChange(filter.code, [rangeMin, rangeMax])"
        >
          <span class="reset-icon">🔄</span>
          Сбросить
        </button>
      </template>
    </div>
  </template>

  <template #filters-empty="{ hasFilters }">
    <div v-if="!hasFilters" class="custom-empty">
      <p>🔍 Выполните поиск для получения фильтров</p>
    </div>
  </template>
</barneo-filters-widget>
```

### Сортировка с кастомными слотами

```vue
<barneo-sort-widget v-model="sortValue">
  <template #sort-options="{ sortOptions, currentSort, isLoading, handleSortChange }">
    <div class="custom-sort-container">
      <div 
        v-for="option in sortOptions" 
        :key="option.value"
        class="custom-sort-option"
        :class="{ active: option.value === currentSort }"
        @click="handleSortChange(option.value)"
      >
        {{ option.label }}
      </div>
    </div>
  </template>
  
  <template #sort-option="{ option, isActive, isDisabled }">
    <div 
      class="custom-sort-option" 
      :class="{ active: isActive, disabled: isDisabled }"
    >
      <span class="sort-label">{{ option.label }}</span>
      <span v-if="isActive" class="loading-spinner">⏳</span>
    </div>
  </template>
  
  <template #loading="{ isLoading }">
    <div v-if="isLoading" class="sort-loading">
      Обновление результатов...
    </div>
  </template>
</barneo-sort-widget>
```

### Товары с кастомными слотами

```vue
<barneo-products-widget>
  <template #product-card="{ product, index, onSelect, handleAuxClick }">
    <div 
      class="custom-product-card" 
      @click="(event) => onSelect(product, event)"
      @auxclick="(event) => handleAuxClick(product, event)"
    >
      <div class="product-image">
        <img :src="product.image" :alt="product.name" />
      </div>
      <div class="product-info">
        <h3 class="product-title">{{ product.name }}</h3>
        <p class="product-price">{{ formatPrice(product.price) }}</p>
        <p class="product-description">{{ product.description }}</p>
      </div>
    </div>
  </template>
  
  <template #products-pagination="{ pagination, onPageChange }">
    <div class="custom-pagination">
      <button 
        :disabled="pagination.currentPage === 1"
        @click="onPageChange(pagination.currentPage - 1)"
      >
        ← Назад
      </button>
      
      <span class="page-info">
        Страница {{ pagination.currentPage }} из {{ pagination.totalPages }}
      </span>
      
      <button 
        :disabled="pagination.currentPage === pagination.totalPages"
        @click="onPageChange(pagination.currentPage + 1)"
      >
        Вперед →
      </button>
    </div>
  </template>
  
  <template #products-empty="{ hasProducts, query }">
    <div v-if="!hasProducts && query" class="custom-empty">
      <p>😔 По запросу "{{ query }}" товары не найдены</p>
      <p>Попробуйте изменить параметры поиска</p>
  </div>
</template>
</barneo-products-widget>
```

### Обработка средней кнопки мыши

Библиотека поддерживает открытие товаров в новой вкладке при нажатии средней кнопки мыши (колесико).

#### Пример обработки в BarneoSearchWidget

```vue
<template>
  <barneo-search-widget v-model="query" @select="handleProductSelect" />
</template>

<script setup>
import { useRouter } from "vue-router";

const router = useRouter();

const handleProductSelect = (product, event) => {
  console.log("Выбран товар:", product);

  // Если это средняя кнопка мыши (колесико), открываем в новой вкладке
  if (event && event.button === 1) {
    window.open(`/product/${product.id}`, "_blank");
  } else {
    // Обычный клик - переходим в текущей вкладке
    router.push(`/product/${product.id}`);
  }
};
</script>
```

#### Пример обработки в BarneoProductsWidget

```vue
<template>
  <barneo-products-widget @product-select="handleProductSelect" />
</template>

<script setup>
import { useRouter } from "vue-router";

const router = useRouter();

const handleProductSelect = (product, event) => {
  console.log("Выбран товар из списка:", product);

  // Если это средняя кнопка мыши (колесико), открываем в новой вкладке
  if (event && event.button === 1) {
    window.open(`/product/${product.id}`, "_blank");
  } else {
    // Обычный клик - переходим в текущей вкладке
    router.push(`/product/${product.id}`);
  }
};
</script>
```

#### Использование в кастомных слотах

```vue
<template>
  <barneo-search-widget>
    <template
      #search-results="{ searchResults, selectProduct, handleAuxClick }"
    >
      <div v-for="product in searchResults" :key="product.id">
        <div
          class="product-item"
          @click="(event) => selectProduct(product, event)"
          @auxclick="(event) => handleAuxClick(product, event)"
        >
          <h3>{{ product.name }}</h3>
          <p>{{ product.price }}</p>
        </div>
      </div>
    </template>
  </barneo-search-widget>
</template>
```

## 📊 Аналитика

Библиотека включает в себя модуль аналитики для отслеживания пользовательского поведения и взаимодействий с поисковым интерфейсом.

### Использование аналитики

#### Базовое использование

```typescript
import { useAnalytics } from "barneo-search-widget-lib";

// Инициализация аналитики (автоматически использует конфигурацию из BarneoConfigManager)
const analytics = useAnalytics();

// Проверка инициализации
console.log("Аналитика инициализирована:", analytics.isInitialized.value);
console.log("Конфигурация:", analytics.getConfigInfo.value);
```

#### Отслеживание событий

```typescript
// Объединение неавторизованного и авторизованного пользователя
await analytics.mergeCustomers("guest-id-123", "authorized-id-456");

// Отслеживание поискового запроса
await analytics.trackQueryUse("сковорода");

// Отслеживание использования функций поиска
await analytics.trackFunctionUse("filter"); // фильтры
await analytics.trackFunctionUse("hint"); // подсказки
await analytics.trackFunctionUse("history"); // история

// Отслеживание применения подсказки
await analytics.trackHintUse("сков", "сковорода");

// Отслеживание конверсии товара
await analytics.trackProductConversion("product-123", "click", {
  position: 1,
  query: "сковорода",
  source: "search",
});

// Отслеживание конверсии категории
await analytics.trackCategoryConversion("category-456", {
  query: "посуда",
  source: "search",
});
```

#### Быстрые методы (quickTrack)

```typescript
// Быстрое отслеживание клика по товару
await analytics.quickTrack.productClick("product-123", {
  position: 1,
  query: "сковорода",
  source: "search",
});

// Быстрое отслеживание добавления в корзину
await analytics.quickTrack.productBasket("product-123", {
  position: 2,
  query: "сковорода",
  source: "search",
});

// Быстрое отслеживание заказа
await analytics.quickTrack.productOrder("product-123", {
  position: 3,
  query: "сковорода",
  source: "search",
});

// Быстрое отслеживание клика по категории
await analytics.quickTrack.categoryClick("category-456", {
  query: "посуда",
  source: "search",
});

// Быстрое отслеживание использования функций
await analytics.quickTrack.filterUse(); // фильтры
await analytics.quickTrack.hintUse(); // подсказки
await analytics.quickTrack.historyUse(); // история
```

## 🎯 Рекомендации (Adviser)

Библиотека включает в себя модуль рекомендаций для получения умных рекомендаций товаров на основе поведения пользователей.

### Использование рекомендаций

#### Базовое использование

```typescript
import { useAdviser } from "barneo-search-widget-lib";

// Инициализация рекомендаций (автоматически использует конфигурацию из BarneoConfigManager)
const adviser = useAdviser({
  enableCache: true,
  cacheTTL: 300000, // 5 минут
  apiLimit: 8,
});

// Проверка инициализации
console.log("Рекомендации инициализированы:", adviser.state.value);
```

#### Получение рекомендаций

```typescript
// "People also search for this product" - что искали с этим товаром
const peopleAlsoSearch = await adviser.getPeopleAlsoSearch("product-123");

// "Searchers also viewed" - что смотрели искавшие
const searchersAlsoViewed = await adviser.getSearchersAlsoViewed("сковорода");

// "Cross Sell" - с этим товаром покупают
const crossSell = await adviser.getCrossSell("product-123");

// "Up Sell" - более дорогие альтернативы
const upSell = await adviser.getUpSell("product-123");

// "Up Sell New" - новая версия up sell
const upSellNew = await adviser.getUpSellNew("product-123");

// "Recently watched" - недавно просмотренные
const recentlyWatched = await adviser.getRecentlyWatched("customer-456");

// "Popular products" - популярные товары
const popularProducts = await adviser.getPopularProducts();

// "Similar products" - похожие товары
const similarProducts = await adviser.getSimilarProducts("product-123");
```

#### Состояние и кэширование

```typescript
// Проверка состояния загрузки
if (adviser.state.value.loadingStatus === "loading") {
  console.log("Загрузка рекомендаций...");
}

// Обработка ошибок
if (adviser.state.value.error) {
  console.error("Ошибка рекомендаций:", adviser.state.value.error);
}

// Принудительная очистка кэша
adviser.forceClear();

// Получение всех рекомендаций сразу
const allRecommendations = adviser.state.value;
console.log("Все рекомендации:", allRecommendations);
```

#### Тестирование всех рекомендаций

```typescript
// Тестирование всех типов рекомендаций
const testAdviser = async () => {
  console.log("🎯 Тестирование модуля adviser...");

  // Очищаем состояние для чистого теста
  adviser.forceClear();

  // Тестируем все типы рекомендаций
  await adviser.getPeopleAlsoSearch("test-product");
  await adviser.getSearchersAlsoViewed("тестовый запрос");
  await adviser.getCrossSell("test-product");
  await adviser.getUpSell("test-product");
  await adviser.getUpSellNew("test-product");
  await adviser.getRecentlyWatched("test-customer");
  await adviser.getPopularProducts();
  await adviser.getSimilarProducts("test-product");

  console.log("✅ Все тесты рекомендаций завершены");
};
```

### Использование аналитики

#### Базовое использование

```typescript
import { useAnalytics } from "barneo-search-widget-lib";

// Инициализация аналитики (автоматически использует конфигурацию из BarneoConfigManager)
const analytics = useAnalytics();

// Проверка инициализации
console.log("Аналитика инициализирована:", analytics.isInitialized.value);
console.log("Конфигурация:", analytics.getConfigInfo.value);
```

#### Отслеживание событий

```typescript
// Объединение неавторизованного и авторизованного пользователя
await analytics.mergeCustomers("guest-id-123", "authorized-id-456");

// Отслеживание поискового запроса
await analytics.trackQueryUse("сковорода");

// Отслеживание использования функций поиска
await analytics.trackFunctionUse("filter"); // фильтры
await analytics.trackFunctionUse("hint"); // подсказки
await analytics.trackFunctionUse("history"); // история

// Отслеживание применения подсказки
await analytics.trackHintUse("сков", "сковорода");

// Отслеживание конверсии товара
await analytics.trackProductConversion("product-123", "click", {
  position: 1,
  query: "сковорода",
  source: "search",
});

// Отслеживание конверсии категории
await analytics.trackCategoryConversion("category-456", {
  query: "посуда",
  source: "search",
});
```

#### Быстрые методы (quickTrack)

```typescript
// Быстрое отслеживание клика по товару
await analytics.quickTrack.productClick("product-123", {
  position: 1,
  query: "сковорода",
  source: "search",
});

// Быстрое отслеживание добавления в корзину
await analytics.quickTrack.productBasket("product-123", {
  position: 2,
  query: "сковорода",
  source: "search",
});

// Быстрое отслеживание заказа
await analytics.quickTrack.productOrder("product-123", {
  position: 3,
  query: "сковорода",
  source: "search",
});

// Быстрое отслеживание клика по категории
await analytics.quickTrack.categoryClick("category-456", {
  query: "посуда",
  source: "search",
});

// Быстрое отслеживание использования функций
await analytics.quickTrack.filterUse(); // фильтры
await analytics.quickTrack.hintUse(); // подсказки
await analytics.quickTrack.historyUse(); // история
```

#### Состояние и обработка ошибок

```typescript
// Проверка состояния загрузки
if (analytics.isLoading.value) {
  console.log("Отправка аналитических данных...");
}

// Обработка ошибок
if (analytics.error.value) {
  console.error("Ошибка аналитики:", analytics.error.value);
}

// Очистка ошибки
analytics.clearError();
```

#### Ручная инициализация

```typescript
// Если нужно инициализировать аналитику с кастомной конфигурацией
analytics.initializeAnalytics({
  baseUrl: "https://api.search.ensi.cloud",
  token: "your-token",
  customerId: "4",
  locationId: "1",
  apiVersion: "v1",
});
```

### Доступные типы событий

#### SearchFunction (функции поиска)

- `filter` - использование фильтров
- `hint` - использование подсказок
- `history` - использование истории поиска
- `popular_auto_queries` - популярные автозапросы
- `popular_manual_queries` - популярные ручные запросы
- `recommended_queries` - рекомендуемые запросы

#### ProductConversionType (типы конверсии товаров)

- `click` - клик по товару
- `basket` - добавление в корзину
- `order` - заказ товара

#### ProductConversionSource (источники конверсии товаров)

- `search` - из поиска
- `category` - из категории
- `recommendation` - из рекомендаций

#### CategoryConversionSource (источники конверсии категорий)

- `search` - из поиска
- `navigation` - из навигации

### Интеграция с компонентами

Аналитика автоматически интегрируется с компонентами библиотеки через `BarneoConfigManager`:

```typescript
import { useBarneoConfig } from "barneo-search-widget-lib";

const { analyticsService } = useBarneoConfig();

// Прямой доступ к сервису аналитики
await analyticsService.trackQueryUse("тестовый запрос");
```

### Пример полного использования

```vue
<template>
  <div>
    <barneo-search-widget v-model="query" @search="handleSearch" />
    <barneo-products-widget @product-select="handleProductSelect" />

    <!-- Кнопка для тестирования аналитики -->
    <button @click="testAnalytics">🧪 Тестировать аналитику</button>
  </div>
</template>

<script setup>
import { ref } from "vue";
import {
  useAnalytics,
  ProductConversionSource,
} from "barneo-search-widget-lib";

const query = ref("");
const analytics = useAnalytics();

const handleSearch = async (searchQuery) => {
  console.log("Поиск:", searchQuery);

  // Отслеживаем поисковый запрос
  await analytics.trackQueryUse(searchQuery);
};

const handleProductSelect = async (product, event) => {
  console.log("Выбран товар:", product);

  // Отслеживаем клик по товару
  await analytics.quickTrack.productClick(product.id, {
    position: 1,
    query: query.value,
    source: ProductConversionSource.SEARCH,
  });

  // Обработка средней кнопки мыши
  if (event && event.button === 1) {
    window.open(`/product/${product.id}`, "_blank");
  } else {
    router.push(`/product/${product.id}`);
  }
};

const testAnalytics = async () => {
  try {
    // Тестируем все функции аналитики
    await analytics.mergeCustomers("guest-123", "auth-456");
    await analytics.trackQueryUse("тестовый запрос");
    await analytics.quickTrack.filterUse();
    await analytics.quickTrack.productClick("test-product", {
      position: 1,
      source: ProductConversionSource.SEARCH,
    });

    console.log("✅ Все тесты аналитики завершены");
  } catch (error) {
    console.error("❌ Ошибка аналитики:", error);
  }
};
</script>
```

## 🔧 Дополнительные возможности

### URL менеджер

Библиотека поддерживает управление URL параметрами для сохранения состояния поиска. URL менеджер инициализируется в компонентах через composable.

#### Инициализация URL менеджера

```vue
<script setup>
import { useUrlManagerInit } from "barneo-search-widget-lib";

// Инициализируем URL менеджер для работы с query параметрами
const { urlManager } = useUrlManagerInit();
</script>
```

#### Использование в компонентах

```vue
<template>
  <div>
    <barneo-search-widget v-model="query" />
    <barneo-filters-widget />
    <barneo-sort-widget />
    <barneo-products-widget />
  </div>
</template>

<script setup>
import { useUrlManagerInit } from "barneo-search-widget-lib";

// Инициализируем URL менеджер в компоненте
const { urlManager } = useUrlManagerInit();

// URL менеджер автоматически синхронизирует состояние с URL параметрами
</script>
```

#### Управляемые URL параметры

- `q` - поисковый запрос
- `sort` - текущая сортировка
- `page` - текущая страница
- `filters` - активные фильтры (JSON)

### Динамическое обновление конфигурации

```typescript
import { BarneoConfigManager } from "barneo-search-widget-lib";

const configManager = BarneoConfigManager.getInstance();

// Обновление конфигурации поиска
configManager.updateSearchWidgetConfig({
  placeholder: "Новый placeholder",
  darkMode: true,
});

// Обновление конфигурации фильтров
configManager.updateFiltersWidgetConfig({
  maxValuesPerFilter: 10,
  darkMode: true,
});

// Обновление конфигурации товаров
configManager.updateProductsWidgetConfig({
  itemsPerPage: 30,
  showPagination: false,
});

// Обновление конфигурации сортировки
configManager.updateSortWidgetConfig({
  defaultSort: "price",
  darkMode: true,
});
```

### Управление URL параметрами

Библиотека автоматически управляет URL параметрами для сохранения состояния поиска:

- `q` - поисковый запрос
- `sort` - текущая сортировка
- `page` - текущая страница
- `filters` - активные фильтры (JSON)

```typescript
// Пример URL с параметрами
// https://example.com/search?q=сковорода&sort=price&page=2&filters=%7B%22brands%22%3A%5B%22Tefal%22%5D%7D
```

### Использование composables

```typescript
import {
  useSearchWidget,
  useFiltersWidget,
  useSortWidget,
  useProductsWidget,
  useSharedState,
} from "barneo-search-widget-lib";

// Общее состояние
const { searchState } = useSharedState();

// Поиск
const { performSearch, searchQuery } = useSearchWidget();

// Фильтры
const { applyFilter, clearFilters, activeFilters } = useFiltersWidget();

// Сортировка
const { setSort, currentSort } = useSortWidget();

// Товары
const { products, pagination, changePage } = useProductsWidget();
```

## 📘 TypeScript поддержка

### Автоматическая типизация глобальных компонентов

При использовании плагина библиотеки глобальные компоненты автоматически получают типизацию:

```vue
<template>
  <!-- TypeScript автоматически распознает типы этих компонентов -->
  <barneo-search-widget v-model="query" @search="handleSearch" />
  <barneo-filters-widget @filter-applied="handleFilter" />
  <barneo-sort-widget @sort-changed="handleSort" />
  <barneo-products-widget @product-selected="handleProductSelect" />
  <barneo-range-selector
    ref="rangeSelector"
    :min="0"
    :max="10000"
    @change="handleRangeChange"
  />
</template>
```

<script setup lang="ts">
// Типизация работает автоматически без дополнительных импортов
const query = ref("")
const handleSearch = (query: string) => {
  console.log("Поиск:", query)
}
</script>

````

### Ручная типизация (если нужно)

Если вы используете локальную регистрацию компонентов, добавьте типизацию:

```typescript
// types/barneo.d.ts
import type { BarneoSearchWidget } from 'barneo-search-widget-lib'
import type { BarneoFiltersWidget } from 'barneo-search-widget-lib'
import type { BarneoSortWidget } from 'barneo-search-widget-lib'
import type { BarneoProductsWidget } from 'barneo-search-widget-lib'
import type { BarneoRangeSelector } from 'barneo-search-widget-lib'

declare module '@vue/runtime-core' {
  export interface GlobalComponents {
    'barneo-search-widget': typeof BarneoSearchWidget
    'barneo-filters-widget': typeof BarneoFiltersWidget
    'barneo-sort-widget': typeof BarneoSortWidget
    'barneo-products-widget': typeof BarneoProductsWidget
    'barneo-range-selector': typeof BarneoRangeSelector
    BarneoSearchWidget: typeof BarneoSearchWidget
    BarneoFiltersWidget: typeof BarneoFiltersWidget
    BarneoSortWidget: typeof BarneoSortWidget
    BarneoProductsWidget: typeof BarneoProductsWidget
    BarneoRangeSelector: typeof BarneoRangeSelector
  }
}

export {}
````

### Импорт типов

```typescript
import type {
  BarneoSearchWidget,
  BarneoFiltersWidget,
  BarneoSortWidget,
  BarneoProductsWidget,
  BarneoRangeSelector,
  SearchConfig,
  SearchResult,
  SearchHint,
  SearchApiService,
  SearchRequest,
  SearchResponse,
  // Типы аналитики
  SearchFunction,
  ProductConversionType,
  ProductConversionSource,
  CategoryConversionSource,
  CategoryConversionType,
  AuthorizeCustomerRequest,
  QueryUseRequest,
  FunctionalUseRequest,
  HintsUseRequest,
  ProductsUseRequest,
  CategoriesUseRequest,
  // Типы рекомендаций
  AdviserBlockType,
  AdviserFilterType,
  AdviserSortType,
  LoadingStatus,
  IncludeType,
  FullProduct,
  ProductProperty,
} from "barneo-search-widget-lib";
```

## 🤝 Поддержка

- Vue 3.x
- TypeScript
- Nuxt 3
- Vite
- Webpack

## 📄 Лицензия

MIT License
