@getlupa/vue 0.18.3 → 0.18.4

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.
@@ -49,7 +49,7 @@ var __async = (__this, __arguments, generator) => {
49
49
  step((generator = generator.apply(__this, __arguments)).next());
50
50
  });
51
51
  };
52
- import { toRaw, isRef, isReactive, toRef, effectScope, ref, markRaw, hasInjectionContext, inject, getCurrentInstance, watch, reactive, nextTick, computed, unref, getCurrentScope, onScopeDispose, toRefs, defineComponent, openBlock, createElementBlock, createElementVNode, normalizeClass, withDirectives, mergeProps, vModelText, createCommentVNode, toDisplayString, onMounted, onBeforeMount, Fragment, renderList, createBlock, onBeforeUnmount, normalizeStyle, Transition, withCtx, toHandlers, withModifiers, resolveDynamicComponent, renderSlot, createSlots, createVNode, createTextVNode, normalizeProps, guardReactiveProps, onUnmounted, resolveComponent, vModelSelect, vShow, h as h$1, provide, cloneVNode } from "vue";
52
+ import { toRaw, isRef, isReactive, toRef, effectScope, ref, markRaw, hasInjectionContext, inject, getCurrentInstance, watch, reactive, nextTick, computed, unref, getCurrentScope, onScopeDispose, toRefs, defineComponent, openBlock, createElementBlock, onBeforeUnmount, onMounted, createElementVNode, toDisplayString, normalizeClass, createVNode, createCommentVNode, withDirectives, mergeProps, vModelText, createBlock, onBeforeMount, Fragment, renderList, normalizeStyle, Transition, withCtx, toHandlers, withModifiers, resolveDynamicComponent, renderSlot, createSlots, createTextVNode, normalizeProps, guardReactiveProps, onUnmounted, resolveComponent, vModelSelect, vShow, h as h$1, provide, cloneVNode } from "vue";
53
53
  var isVue2 = false;
54
54
  function set(target, key, val) {
55
55
  if (Array.isArray(target)) {
@@ -7323,6 +7323,11 @@ const getRandomString = (length) => {
7323
7323
  }
7324
7324
  return result2;
7325
7325
  };
7326
+ const getSocketClientId = () => {
7327
+ const timestamp = Date.now().toString(36);
7328
+ const randomString = getRandomString(8);
7329
+ return `${timestamp}-${randomString}`;
7330
+ };
7326
7331
  const toFixedIfNecessary = (value, precision = 2) => {
7327
7332
  return (+parseFloat(value).toFixed(precision)).toString();
7328
7333
  };
@@ -7437,6 +7442,10 @@ const DEFAULT_SEARCH_BOX_OPTIONS = {
7437
7442
  links: {
7438
7443
  searchResults: "/search"
7439
7444
  },
7445
+ voiceSearch: {
7446
+ enabled: false,
7447
+ queryKey: ""
7448
+ },
7440
7449
  panels: [
7441
7450
  {
7442
7451
  type: "suggestion",
@@ -9018,6 +9027,350 @@ const useSearchBoxStore = defineStore("searchBox", () => {
9018
9027
  resetHighlightIndex
9019
9028
  };
9020
9029
  });
9030
+ const Env = {
9031
+ production: "https://api.lupasearch.com/v1/",
9032
+ staging: "https://api.staging.lupasearch.com/v1/"
9033
+ };
9034
+ const VoiceServiceEnv = {
9035
+ production: "ws://voice.lupasearch.com:3001",
9036
+ staging: "ws://voice.lupasearch.dev:3001"
9037
+ };
9038
+ const DEFAULT_REQUEST_CONFIG = {
9039
+ method: "POST",
9040
+ headers: { "Content-Type": "application/json" }
9041
+ };
9042
+ const DEFAULT_HEADERS = DEFAULT_REQUEST_CONFIG.headers;
9043
+ const getVoiceServiceApiUrl = (environment, customVoiceServiceUrl) => {
9044
+ if (customVoiceServiceUrl) {
9045
+ return customVoiceServiceUrl;
9046
+ }
9047
+ return VoiceServiceEnv[environment] || VoiceServiceEnv["production"];
9048
+ };
9049
+ const getApiUrl = (environment, customBaseUrl) => {
9050
+ if (customBaseUrl) {
9051
+ return customBaseUrl;
9052
+ }
9053
+ return Env[environment] || Env["production"];
9054
+ };
9055
+ const _sfc_main$1z = /* @__PURE__ */ defineComponent({
9056
+ __name: "VoiceSearchProgressCircle",
9057
+ props: {
9058
+ isRecording: { type: Boolean },
9059
+ timesliceLimit: {},
9060
+ timeSliceLength: {}
9061
+ },
9062
+ setup(__props, { expose: __expose }) {
9063
+ const props = __props;
9064
+ const progressBar = ref(null);
9065
+ const getProgressBarColor = (progressBarStyle) => {
9066
+ if (!progressBarStyle.backgroundImage.startsWith("conic-gradient")) {
9067
+ return progressBarStyle.backgroundColor;
9068
+ }
9069
+ const colorStops = progressBarStyle.backgroundImage.replace(/conic-gradient\(|\)$/g, "").split(")");
9070
+ if (colorStops.length > 1) {
9071
+ return `${colorStops[0]})`;
9072
+ } else {
9073
+ return progressBarStyle.backgroundColor;
9074
+ }
9075
+ };
9076
+ const startProgressBar = () => {
9077
+ if (!progressBar.value || !props.isRecording) {
9078
+ return;
9079
+ }
9080
+ const duration = props.timesliceLimit * props.timeSliceLength;
9081
+ const progressBarStyle = window.getComputedStyle(progressBar.value);
9082
+ const progressBarColor = getProgressBarColor(progressBarStyle);
9083
+ progressBar.value.style.background = `conic-gradient(${progressBarColor} 0%, transparent 0%)`;
9084
+ let startTime = null;
9085
+ function updateProgress(timestamp) {
9086
+ if (!progressBar.value || !props.isRecording) {
9087
+ return;
9088
+ }
9089
+ if (!startTime)
9090
+ startTime = timestamp;
9091
+ const elapsed = timestamp - startTime;
9092
+ const progress = Math.min(elapsed / duration, 1) * 100;
9093
+ progressBar.value.style.background = `conic-gradient(${progressBarColor} ${progress}%, transparent ${progress}%)`;
9094
+ if (elapsed < duration) {
9095
+ requestAnimationFrame(updateProgress);
9096
+ }
9097
+ }
9098
+ requestAnimationFrame(updateProgress);
9099
+ };
9100
+ const stopProgressBar = () => {
9101
+ if (!progressBar.value) {
9102
+ return;
9103
+ }
9104
+ progressBar.value.style.background = "";
9105
+ };
9106
+ __expose({
9107
+ startProgressBar,
9108
+ stopProgressBar
9109
+ });
9110
+ return (_ctx, _cache) => {
9111
+ return openBlock(), createElementBlock("div", {
9112
+ ref_key: "progressBar",
9113
+ ref: progressBar,
9114
+ class: "lupa-progress-circle"
9115
+ }, null, 512);
9116
+ };
9117
+ }
9118
+ });
9119
+ const buildSocketMessageFrameHeader = (event, payloadLength) => {
9120
+ const headerObj = { event, length: payloadLength };
9121
+ const headerJson = JSON.stringify(headerObj);
9122
+ const headerBytes = new TextEncoder().encode(headerJson);
9123
+ const headerLength = new Uint32Array([headerBytes.length]);
9124
+ const headerLengthBytes = new Uint8Array(headerLength.buffer);
9125
+ const result2 = new Uint8Array(4 + headerBytes.length);
9126
+ result2.set(headerLengthBytes, 0);
9127
+ result2.set(headerBytes, 4);
9128
+ return result2;
9129
+ };
9130
+ function useVoiceRecorder(options) {
9131
+ const socket = ref(null);
9132
+ const mediaStream = ref(null);
9133
+ const mediaRecorder = ref(null);
9134
+ const isRecording = ref(false);
9135
+ const errorRef = ref(null);
9136
+ const transcription = ref("");
9137
+ const timeSliceLength = computed(() => {
9138
+ var _a;
9139
+ return (_a = options.timesliceLength) != null ? _a : 1e3;
9140
+ });
9141
+ onBeforeUnmount(() => {
9142
+ closeSocket();
9143
+ stopRecording();
9144
+ });
9145
+ const initSocket = (url, onMessage) => {
9146
+ socket.value = new WebSocket(url);
9147
+ socket.value.onopen = () => __async(this, null, function* () {
9148
+ var _a;
9149
+ if (((_a = mediaRecorder.value) == null ? void 0 : _a.state) !== "recording") {
9150
+ yield startRecording();
9151
+ }
9152
+ });
9153
+ socket.value.onmessage = (event) => {
9154
+ const msg = JSON.parse(event.data);
9155
+ if (msg.event === "transcription") {
9156
+ transcription.value = msg.transcription;
9157
+ onMessage == null ? void 0 : onMessage(msg.transcription);
9158
+ stopSocketConnection();
9159
+ } else if (msg.event === "error") {
9160
+ errorRef.value = "Server error during transcription";
9161
+ stopRecording();
9162
+ }
9163
+ };
9164
+ socket.value.onclose = () => {
9165
+ stopRecording();
9166
+ };
9167
+ socket.value.onerror = () => {
9168
+ stopRecording();
9169
+ errorRef.value = "Service connection error";
9170
+ };
9171
+ };
9172
+ const onMediaRecorderDataAvailable = (event) => __async(this, null, function* () {
9173
+ var _a, _b;
9174
+ if (((_a = mediaRecorder.value) == null ? void 0 : _a.state) !== "recording")
9175
+ return;
9176
+ const audioBuffer = yield event.data.arrayBuffer();
9177
+ const header = buildSocketMessageFrameHeader("audio-chunk", audioBuffer.byteLength);
9178
+ const buffer = new Uint8Array(header.length + audioBuffer.byteLength);
9179
+ buffer.set(header, 0);
9180
+ buffer.set(new Uint8Array(audioBuffer), header.length);
9181
+ (_b = socket.value) == null ? void 0 : _b.send(buffer);
9182
+ });
9183
+ const startRecording = () => __async(this, null, function* () {
9184
+ mediaStream.value = yield navigator.mediaDevices.getUserMedia({
9185
+ video: false,
9186
+ audio: {
9187
+ channelCount: 1,
9188
+ echoCancellation: true,
9189
+ sampleRate: 16e3
9190
+ }
9191
+ });
9192
+ mediaRecorder.value = new MediaRecorder(mediaStream.value, {
9193
+ mimeType: "audio/webm; codecs=opus"
9194
+ });
9195
+ mediaRecorder.value.ondataavailable = onMediaRecorderDataAvailable;
9196
+ mediaRecorder.value.start(timeSliceLength.value);
9197
+ isRecording.value = true;
9198
+ });
9199
+ const stopRecording = () => {
9200
+ var _a, _b;
9201
+ (_a = mediaRecorder.value) == null ? void 0 : _a.stop();
9202
+ (_b = mediaStream.value) == null ? void 0 : _b.getTracks().forEach((track2) => {
9203
+ track2.stop();
9204
+ });
9205
+ isRecording.value = false;
9206
+ };
9207
+ const stopSocketConnection = () => {
9208
+ if (socket.value && socket.value.readyState === WebSocket.OPEN) {
9209
+ const endHeader = buildSocketMessageFrameHeader("audio-chunk-end", 0);
9210
+ socket.value.send(endHeader);
9211
+ setTimeout(() => {
9212
+ closeSocket();
9213
+ }, 1e3);
9214
+ }
9215
+ };
9216
+ const closeSocket = () => {
9217
+ var _a;
9218
+ (_a = socket.value) == null ? void 0 : _a.close();
9219
+ socket.value = null;
9220
+ };
9221
+ const reset = () => {
9222
+ stopRecording();
9223
+ closeSocket();
9224
+ transcription.value = "";
9225
+ errorRef.value = null;
9226
+ isRecording.value = false;
9227
+ };
9228
+ return {
9229
+ isRecording,
9230
+ transcription,
9231
+ errorRef,
9232
+ initSocket,
9233
+ startRecording,
9234
+ stopRecording,
9235
+ stopSocketConnection,
9236
+ reset,
9237
+ closeSocket
9238
+ };
9239
+ }
9240
+ const _hoisted_1$1l = {
9241
+ key: 0,
9242
+ class: "lupa-dialog-overlay"
9243
+ };
9244
+ const _hoisted_2$W = { class: "lupa-dialog-content" };
9245
+ const _hoisted_3$F = { class: "lupa-listening-text" };
9246
+ const _hoisted_4$v = { class: "lupa-mic-button-wrapper" };
9247
+ const _sfc_main$1y = /* @__PURE__ */ defineComponent({
9248
+ __name: "VoiceSearchDialog",
9249
+ props: {
9250
+ isOpen: { type: Boolean },
9251
+ options: {}
9252
+ },
9253
+ emits: [
9254
+ "close",
9255
+ "transcript-update",
9256
+ "stop-recognize"
9257
+ ],
9258
+ setup(__props, { expose: __expose, emit }) {
9259
+ const props = __props;
9260
+ const optionsStore = useOptionsStore();
9261
+ const {
9262
+ isRecording,
9263
+ transcription,
9264
+ errorRef,
9265
+ initSocket,
9266
+ stopSocketConnection,
9267
+ reset
9268
+ } = useVoiceRecorder(props.options);
9269
+ const clientId = ref(null);
9270
+ const voiceSearchProgressBar = ref(null);
9271
+ const timesliceLimit = computed(() => {
9272
+ var _a;
9273
+ return (_a = props.options.timesliceLimit) != null ? _a : 4;
9274
+ });
9275
+ const timeSliceLength = computed(() => {
9276
+ var _a;
9277
+ return (_a = props.options.timesliceLength) != null ? _a : 1e3;
9278
+ });
9279
+ const stopDelay = computed(() => {
9280
+ var _a;
9281
+ return (_a = props.options.stopDelay) != null ? _a : 700;
9282
+ });
9283
+ const labels = computed(() => {
9284
+ var _a;
9285
+ return (_a = props.options.labels) != null ? _a : {};
9286
+ });
9287
+ const description = computed(() => {
9288
+ var _a, _b, _c;
9289
+ if (errorRef.value) {
9290
+ return (_a = labels.value.serviceError) != null ? _a : errorRef.value;
9291
+ }
9292
+ if (!isRecording.value) {
9293
+ return (_b = labels.value.microphoneOff) != null ? _b : "Microphone is off. Try again.";
9294
+ }
9295
+ return (_c = labels.value.listening) != null ? _c : "Listening...";
9296
+ });
9297
+ watch(transcription, (newValue) => {
9298
+ emit("transcript-update", newValue);
9299
+ });
9300
+ const handleRecordingButtonClick = () => {
9301
+ var _a, _b;
9302
+ if (isRecording.value) {
9303
+ setTimeout(() => {
9304
+ stopSocketConnection();
9305
+ handleOnStopEvent();
9306
+ }, stopDelay.value);
9307
+ return;
9308
+ }
9309
+ const voiceServiceUrl = getVoiceServiceApiUrl(
9310
+ optionsStore.envOptions.environment,
9311
+ props.options.customVoiceServiceUrl
9312
+ );
9313
+ const socketUrl = `${voiceServiceUrl}?clientId=${clientId.value}&queryKey=${props.options.queryKey}&languageCode=${(_a = props.options.language) != null ? _a : "en-US"}&connectionType=write-first`;
9314
+ initSocket(socketUrl);
9315
+ (_b = voiceSearchProgressBar.value) == null ? void 0 : _b.startProgressBar();
9316
+ setTimeout(() => {
9317
+ stopSocketConnection();
9318
+ handleOnStopEvent();
9319
+ }, timesliceLimit.value * timeSliceLength.value);
9320
+ };
9321
+ const handleOnStopEvent = () => {
9322
+ var _a;
9323
+ setTimeout(() => {
9324
+ if (errorRef.value)
9325
+ return;
9326
+ emit("stop-recognize", transcription.value);
9327
+ }, 1500);
9328
+ (_a = voiceSearchProgressBar.value) == null ? void 0 : _a.stopProgressBar();
9329
+ };
9330
+ onMounted(() => {
9331
+ clientId.value = getSocketClientId();
9332
+ });
9333
+ onBeforeUnmount(() => {
9334
+ clientId.value = null;
9335
+ });
9336
+ const dialogReset = () => {
9337
+ var _a;
9338
+ reset();
9339
+ (_a = voiceSearchProgressBar.value) == null ? void 0 : _a.stopProgressBar();
9340
+ };
9341
+ __expose({
9342
+ handleRecordingButtonClick,
9343
+ reset: dialogReset
9344
+ });
9345
+ return (_ctx, _cache) => {
9346
+ return openBlock(), createElementBlock("div", null, [
9347
+ props.isOpen ? (openBlock(), createElementBlock("div", _hoisted_1$1l, [
9348
+ createElementVNode("button", {
9349
+ class: "lupa-dialog-box-close-button",
9350
+ onClick: _cache[0] || (_cache[0] = () => emit("close"))
9351
+ }),
9352
+ createElementVNode("div", _hoisted_2$W, [
9353
+ createElementVNode("p", _hoisted_3$F, toDisplayString(description.value), 1),
9354
+ createElementVNode("div", _hoisted_4$v, [
9355
+ createElementVNode("button", {
9356
+ class: normalizeClass(["lupa-mic-button", { recording: unref(isRecording) }]),
9357
+ onClick: handleRecordingButtonClick
9358
+ }, null, 2),
9359
+ createVNode(_sfc_main$1z, {
9360
+ ref_key: "voiceSearchProgressBar",
9361
+ ref: voiceSearchProgressBar,
9362
+ class: "lupa-progress-circle",
9363
+ isRecording: unref(isRecording),
9364
+ timesliceLimit: timesliceLimit.value,
9365
+ timeSliceLength: timeSliceLength.value
9366
+ }, null, 8, ["isRecording", "timesliceLimit", "timeSliceLength"])
9367
+ ])
9368
+ ])
9369
+ ])) : createCommentVNode("", true)
9370
+ ]);
9371
+ };
9372
+ }
9373
+ });
9021
9374
  const _hoisted_1$1k = { id: "lupa-search-box-input-container" };
9022
9375
  const _hoisted_2$V = { class: "lupa-input-clear" };
9023
9376
  const _hoisted_3$E = { id: "lupa-search-box-input" };
@@ -9031,6 +9384,7 @@ const _hoisted_8$3 = {
9031
9384
  key: 0,
9032
9385
  class: "lupa-close-label"
9033
9386
  };
9387
+ const _hoisted_9$3 = { key: 1 };
9034
9388
  const _sfc_main$1x = /* @__PURE__ */ defineComponent({
9035
9389
  __name: "SearchBoxInput",
9036
9390
  props: {
@@ -9046,6 +9400,8 @@ const _sfc_main$1x = /* @__PURE__ */ defineComponent({
9046
9400
  const searchBoxStore = useSearchBoxStore();
9047
9401
  const { query } = storeToRefs(paramStore);
9048
9402
  const mainInput = ref(null);
9403
+ const voiceDialogOverlay = ref(null);
9404
+ const isVoiceDialogOpen = ref(false);
9049
9405
  const emitInputOnFocus = computed(() => {
9050
9406
  var _a;
9051
9407
  return (_a = props.emitInputOnFocus) != null ? _a : true;
@@ -9056,6 +9412,10 @@ const _sfc_main$1x = /* @__PURE__ */ defineComponent({
9056
9412
  return (_a = props.suggestedValue) != null ? _a : { value: "", override: false, item: { suggestion: "" } };
9057
9413
  }
9058
9414
  );
9415
+ const isVoiceSearchEnabled = computed(() => {
9416
+ var _a, _b;
9417
+ return (_b = (_a = props.options.voiceSearch) == null ? void 0 : _a.enabled) != null ? _b : false;
9418
+ });
9059
9419
  const labels = computed(() => props.options.labels);
9060
9420
  const input2 = ref("");
9061
9421
  const inputValue = computed({
@@ -9079,6 +9439,12 @@ const _sfc_main$1x = /* @__PURE__ */ defineComponent({
9079
9439
  var _a;
9080
9440
  return (_a = labels.value.searchInputAriaLabel) != null ? _a : "Search input";
9081
9441
  });
9442
+ onMounted(() => {
9443
+ document.addEventListener("click", handleClickOutsideVoiceDialogOverlay);
9444
+ });
9445
+ onBeforeUnmount(() => {
9446
+ document.removeEventListener("click", handleClickOutsideVoiceDialogOverlay);
9447
+ });
9082
9448
  watch(suggestedValue, () => {
9083
9449
  if (suggestedValue.value.override) {
9084
9450
  input2.value = suggestedValue.value.item.suggestion;
@@ -9113,6 +9479,37 @@ const _sfc_main$1x = /* @__PURE__ */ defineComponent({
9113
9479
  }
9114
9480
  (_a = mainInput == null ? void 0 : mainInput.value) == null ? void 0 : _a.focus();
9115
9481
  };
9482
+ const openVoiceSearchDialog = () => {
9483
+ var _a;
9484
+ isVoiceDialogOpen.value = true;
9485
+ (_a = voiceDialogOverlay.value) == null ? void 0 : _a.handleRecordingButtonClick();
9486
+ };
9487
+ const closeDialog = () => {
9488
+ var _a;
9489
+ isVoiceDialogOpen.value = false;
9490
+ (_a = voiceDialogOverlay.value) == null ? void 0 : _a.reset();
9491
+ };
9492
+ const handleVoiceSearchOutput = (transcription) => {
9493
+ inputValue.value = transcription;
9494
+ handleSubmit();
9495
+ };
9496
+ const stopRecognition = (trascription) => {
9497
+ setTimeout(() => {
9498
+ isVoiceDialogOpen.value = false;
9499
+ handleVoiceSearchOutput(trascription);
9500
+ }, 500);
9501
+ };
9502
+ const handleClickOutsideVoiceDialogOverlay = (event) => {
9503
+ if (event.target.classList.contains("lupa-voice-search-button")) {
9504
+ return;
9505
+ }
9506
+ if (voiceDialogOverlay.value && voiceDialogOverlay.value.$el.contains(event.target)) {
9507
+ return;
9508
+ }
9509
+ if (isVoiceDialogOpen.value) {
9510
+ closeDialog();
9511
+ }
9512
+ };
9116
9513
  __expose({ focus });
9117
9514
  return (_ctx, _cache) => {
9118
9515
  return openBlock(), createElementBlock("div", _hoisted_1$1k, [
@@ -9156,7 +9553,23 @@ const _sfc_main$1x = /* @__PURE__ */ defineComponent({
9156
9553
  onClick: _cache[1] || (_cache[1] = ($event) => _ctx.$emit("close"))
9157
9554
  }, [
9158
9555
  labels.value.close ? (openBlock(), createElementBlock("span", _hoisted_8$3, toDisplayString(labels.value.close), 1)) : createCommentVNode("", true)
9159
- ])) : createCommentVNode("", true)
9556
+ ])) : createCommentVNode("", true),
9557
+ isVoiceSearchEnabled.value ? (openBlock(), createElementBlock("div", _hoisted_9$3, [
9558
+ createElementVNode("button", {
9559
+ onClick: openVoiceSearchDialog,
9560
+ class: "lupa-voice-search-button"
9561
+ })
9562
+ ])) : createCommentVNode("", true),
9563
+ isVoiceSearchEnabled.value ? (openBlock(), createBlock(_sfc_main$1y, {
9564
+ key: 2,
9565
+ ref_key: "voiceDialogOverlay",
9566
+ ref: voiceDialogOverlay,
9567
+ isOpen: isVoiceDialogOpen.value,
9568
+ options: props.options.voiceSearch,
9569
+ onClose: closeDialog,
9570
+ onTranscriptUpdate: handleVoiceSearchOutput,
9571
+ onStopRecognize: stopRecognition
9572
+ }, null, 8, ["isOpen", "options"])) : createCommentVNode("", true)
9160
9573
  ]);
9161
9574
  };
9162
9575
  }
@@ -20745,7 +21158,8 @@ const _sfc_main$12 = /* @__PURE__ */ defineComponent({
20745
21158
  "labels",
20746
21159
  "links",
20747
21160
  "inputAttributes",
20748
- "showSubmitButton"
21161
+ "showSubmitButton",
21162
+ "voiceSearch"
20749
21163
  ])
20750
21164
  );
20751
21165
  const panelOptions = computed(
@@ -21345,22 +21759,42 @@ const _sfc_main$_ = /* @__PURE__ */ defineComponent({
21345
21759
  emits: ["remove"],
21346
21760
  setup(__props, { emit }) {
21347
21761
  const props = __props;
21348
- const facetKeyClass = computed(() => {
21349
- return `lupa-facet-active-filter-${props.filter.key}`;
21762
+ const facetKeyClass = computed(() => `lupa-facet-active-filter-${props.filter.key}`);
21763
+ const { searchResultOptions } = storeToRefs(useOptionsStore());
21764
+ const units = computed(() => {
21765
+ var _a;
21766
+ return (_a = searchResultOptions.value.filters.facets.stats.units) != null ? _a : {};
21350
21767
  });
21351
- const handleClick = () => {
21768
+ function handleClick() {
21352
21769
  emit("remove", { filter: props.filter });
21353
- };
21770
+ }
21771
+ function formatFilterValue(filter2) {
21772
+ const unit = units.value[filter2.key] || "";
21773
+ let min, max;
21774
+ if (Array.isArray(filter2.value)) {
21775
+ [min, max] = filter2.value.map(String);
21776
+ } else if (typeof filter2.value === "string" && filter2.value.includes("-")) {
21777
+ const parts = filter2.value.split("-").map((s) => s.trim());
21778
+ if (parts.length === 2)
21779
+ [min, max] = parts;
21780
+ }
21781
+ if (min != null && max != null) {
21782
+ return `${min} ${unit} – ${max} ${unit}`;
21783
+ }
21784
+ return `${filter2.value} ${unit}`.trim();
21785
+ }
21354
21786
  return (_ctx, _cache) => {
21355
21787
  return openBlock(), createElementBlock("div", {
21356
- class: normalizeClass(["lupa-search-result-filter-value", { [facetKeyClass.value]: true }])
21788
+ class: normalizeClass(["lupa-search-result-filter-value", [facetKeyClass.value]]),
21789
+ "data-cy": "lupa-current-filter-item"
21357
21790
  }, [
21358
21791
  createElementVNode("div", {
21359
21792
  class: "lupa-current-filter-action",
21360
- onClick: handleClick
21361
- }, ""),
21793
+ onClick: handleClick,
21794
+ "aria-label": "Remove filter"
21795
+ }, " ⨉ "),
21362
21796
  createElementVNode("div", _hoisted_1$U, toDisplayString(_ctx.filter.label) + ": ", 1),
21363
- createElementVNode("div", _hoisted_2$F, toDisplayString(_ctx.filter.value), 1)
21797
+ createElementVNode("div", _hoisted_2$F, toDisplayString(formatFilterValue(props.filter)), 1)
21364
21798
  ], 2);
21365
21799
  };
21366
21800
  }
@@ -21382,6 +21816,14 @@ const _sfc_main$Z = /* @__PURE__ */ defineComponent({
21382
21816
  expandable: { type: Boolean }
21383
21817
  },
21384
21818
  setup(__props) {
21819
+ const optionsStore = useOptionsStore();
21820
+ const { searchResultOptions } = storeToRefs(optionsStore);
21821
+ const units = computed(
21822
+ () => {
21823
+ var _a, _b, _c, _d, _e;
21824
+ return (_e = (_d = (_c = (_b = (_a = searchResultOptions == null ? void 0 : searchResultOptions.value) == null ? void 0 : _a.filters) == null ? void 0 : _b.facets) == null ? void 0 : _c.stats) == null ? void 0 : _d.units) != null ? _e : {};
21825
+ }
21826
+ );
21385
21827
  const isOpen = ref(false);
21386
21828
  const paramsStore = useParamsStore();
21387
21829
  const optionStore = useOptionsStore();
@@ -21463,8 +21905,9 @@ const _sfc_main$Z = /* @__PURE__ */ defineComponent({
21463
21905
  return openBlock(), createBlock(_sfc_main$_, {
21464
21906
  key: filter2.key + "_" + filter2.value,
21465
21907
  filter: filter2,
21908
+ units: units.value,
21466
21909
  onRemove: handleRemove
21467
- }, null, 8, ["filter"]);
21910
+ }, null, 8, ["filter", "units"]);
21468
21911
  }), 128))
21469
21912
  ]),
21470
21913
  createElementVNode("div", {
@@ -22788,15 +23231,17 @@ const _hoisted_4$j = {
22788
23231
  const _hoisted_5$d = { class: "lupa-stats-from" };
22789
23232
  const _hoisted_6$7 = ["max", "min", "pattern", "aria-label"];
22790
23233
  const _hoisted_7$5 = { key: 0 };
22791
- const _hoisted_8$1 = /* @__PURE__ */ createElementVNode("div", { class: "lupa-stats-separator" }, null, -1);
22792
- const _hoisted_9$1 = {
23234
+ const _hoisted_8$1 = { key: 1 };
23235
+ const _hoisted_9$1 = /* @__PURE__ */ createElementVNode("div", { class: "lupa-stats-separator" }, null, -1);
23236
+ const _hoisted_10 = {
22793
23237
  key: 0,
22794
23238
  class: "lupa-stats-range-label"
22795
23239
  };
22796
- const _hoisted_10 = { class: "lupa-stats-to" };
22797
- const _hoisted_11 = ["max", "min", "pattern", "aria-label"];
22798
- const _hoisted_12 = { key: 0 };
22799
- const _hoisted_13 = {
23240
+ const _hoisted_11 = { class: "lupa-stats-to" };
23241
+ const _hoisted_12 = ["max", "min", "pattern", "aria-label"];
23242
+ const _hoisted_13 = { key: 0 };
23243
+ const _hoisted_14 = { key: 1 };
23244
+ const _hoisted_15 = {
22800
23245
  key: 2,
22801
23246
  class: "lupa-stats-slider-wrapper"
22802
23247
  };
@@ -22863,7 +23308,7 @@ const _sfc_main$V = /* @__PURE__ */ defineComponent({
22863
23308
  if (!value || value > facetMax.value) {
22864
23309
  return;
22865
23310
  }
22866
- innerSliderRange.value = [value, sliderRange.value[1]];
23311
+ innerSliderRange.value = [sliderRange.value[1], value];
22867
23312
  handleInputChange();
22868
23313
  }
22869
23314
  });
@@ -22919,7 +23364,18 @@ const _sfc_main$V = /* @__PURE__ */ defineComponent({
22919
23364
  });
22920
23365
  const statsSummary = computed(() => {
22921
23366
  const [min, max] = sliderRange.value;
22922
- return isPrice.value ? formatPriceSummary([min, max], currency.value, separator.value, currencyTemplate.value) : formatRange({ gte: min, lte: max });
23367
+ if (isPrice.value) {
23368
+ return formatPriceSummary(
23369
+ [min, max],
23370
+ currency.value,
23371
+ separator.value,
23372
+ currencyTemplate.value
23373
+ );
23374
+ }
23375
+ if (unit.value) {
23376
+ return `${min} ${unit.value} - ${max} ${unit.value}`;
23377
+ }
23378
+ return formatRange({ gte: min, lte: max });
22923
23379
  });
22924
23380
  const separator = computed(() => {
22925
23381
  var _a, _b, _c;
@@ -22980,6 +23436,12 @@ const _sfc_main$V = /* @__PURE__ */ defineComponent({
22980
23436
  const handleDragging = (value) => {
22981
23437
  innerSliderRange.value = value;
22982
23438
  };
23439
+ const unit = computed(
23440
+ () => {
23441
+ var _a, _b, _c, _d, _e;
23442
+ return (_e = (_d = (_a = props.options.stats) == null ? void 0 : _a.units) == null ? void 0 : _d[(_c = (_b = props.facet) == null ? void 0 : _b.key) != null ? _c : ""]) != null ? _e : "";
23443
+ }
23444
+ );
22983
23445
  return (_ctx, _cache) => {
22984
23446
  return openBlock(), createElementBlock("div", _hoisted_1$P, [
22985
23447
  !isInputVisible.value ? (openBlock(), createElementBlock("div", _hoisted_2$B, toDisplayString(statsSummary.value), 1)) : (openBlock(), createElementBlock("div", _hoisted_3$s, [
@@ -23002,13 +23464,14 @@ const _sfc_main$V = /* @__PURE__ */ defineComponent({
23002
23464
  { lazy: true }
23003
23465
  ]
23004
23466
  ]),
23005
- isPrice.value ? (openBlock(), createElementBlock("span", _hoisted_7$5, toDisplayString(currency.value), 1)) : createCommentVNode("", true)
23467
+ isPrice.value ? (openBlock(), createElementBlock("span", _hoisted_7$5, toDisplayString(currency.value), 1)) : createCommentVNode("", true),
23468
+ unit.value ? (openBlock(), createElementBlock("span", _hoisted_8$1, toDisplayString(unit.value), 1)) : createCommentVNode("", true)
23006
23469
  ])
23007
23470
  ]),
23008
- _hoisted_8$1,
23471
+ _hoisted_9$1,
23009
23472
  createElementVNode("div", null, [
23010
- rangeLabelTo.value ? (openBlock(), createElementBlock("div", _hoisted_9$1, toDisplayString(rangeLabelTo.value), 1)) : createCommentVNode("", true),
23011
- createElementVNode("div", _hoisted_10, [
23473
+ rangeLabelTo.value ? (openBlock(), createElementBlock("div", _hoisted_10, toDisplayString(rangeLabelTo.value), 1)) : createCommentVNode("", true),
23474
+ createElementVNode("div", _hoisted_11, [
23012
23475
  withDirectives(createElementVNode("input", {
23013
23476
  "onUpdate:modelValue": _cache[1] || (_cache[1] = ($event) => toValue.value = $event),
23014
23477
  type: "text",
@@ -23017,7 +23480,7 @@ const _sfc_main$V = /* @__PURE__ */ defineComponent({
23017
23480
  min: facetMin.value,
23018
23481
  pattern: sliderInputFormat.value,
23019
23482
  "aria-label": ariaLabelTo.value
23020
- }, null, 8, _hoisted_11), [
23483
+ }, null, 8, _hoisted_12), [
23021
23484
  [
23022
23485
  vModelText,
23023
23486
  toValue.value,
@@ -23025,11 +23488,12 @@ const _sfc_main$V = /* @__PURE__ */ defineComponent({
23025
23488
  { lazy: true }
23026
23489
  ]
23027
23490
  ]),
23028
- isPrice.value ? (openBlock(), createElementBlock("span", _hoisted_12, toDisplayString(currency.value), 1)) : createCommentVNode("", true)
23491
+ isPrice.value ? (openBlock(), createElementBlock("span", _hoisted_13, toDisplayString(currency.value), 1)) : createCommentVNode("", true),
23492
+ unit.value ? (openBlock(), createElementBlock("span", _hoisted_14, toDisplayString(unit.value), 1)) : createCommentVNode("", true)
23029
23493
  ])
23030
23494
  ])
23031
23495
  ])),
23032
- isSliderVisible.value ? (openBlock(), createElementBlock("div", _hoisted_13, [
23496
+ isSliderVisible.value ? (openBlock(), createElementBlock("div", _hoisted_15, [
23033
23497
  createVNode(unref(m), {
23034
23498
  class: "slider",
23035
23499
  tooltips: false,
@@ -27932,21 +28396,6 @@ const _sfc_main$8 = /* @__PURE__ */ defineComponent({
27932
28396
  };
27933
28397
  }
27934
28398
  });
27935
- const Env = {
27936
- production: "https://api.lupasearch.com/v1/",
27937
- staging: "https://api.staging.lupasearch.com/v1/"
27938
- };
27939
- const DEFAULT_REQUEST_CONFIG = {
27940
- method: "POST",
27941
- headers: { "Content-Type": "application/json" }
27942
- };
27943
- const DEFAULT_HEADERS = DEFAULT_REQUEST_CONFIG.headers;
27944
- const getApiUrl = (environment, customBaseUrl) => {
27945
- if (customBaseUrl) {
27946
- return customBaseUrl;
27947
- }
27948
- return Env[environment] || Env["production"];
27949
- };
27950
28399
  const suggestSearchChatPhrases = (options, request, chatSettings) => __async(void 0, null, function* () {
27951
28400
  var _a, _b, _c;
27952
28401
  const { environment, customBaseUrl } = options;