UNPKG

@salla.sa/twilight-components

Version:
319 lines (318 loc) 13.5 kB
/*! * Crafted with ❤ by Salla */ import { h } from "@stencil/core"; import Search from "../../assets/svg/search.svg"; import Helper from "../../Helpers/Helper"; /** * @slot product - Replaces products card in the results, has replaceable props `{name}`, `{price}`, `{regular_price}`, `{image}`. */ export class SallaSearch { constructor() { this.inputValue = ''; this.translationLoaded = false; this.loading = false; this.typing = false; this.debounce = setTimeout(() => '', 1000); /** * Set the component to display as a placeholder (button) that will open the modal. Defaults to `false` */ this.placeholder = false; /** * Set the component display without modal window. Defaults to `false` */ this.inline = false; /** * Adds a border radius to the input. Half of the height. */ this.oval = false; /** * Sets the height of the input */ this.height = 60; /** * Maximum number of words allowed in the input */ this.maxWords = 5; /** * Show submit action buttons at the end. */ this.showAction = false; this.productSlot = this.host.querySelector('[slot="product"]')?.innerHTML || this.getDefaultProductSlot(); salla.event.on('search::open', () => this.open()); salla.lang.onLoaded(() => { this.translationLoaded = true; }); salla.event.on('modalClosed', () => this.onModalClose()); } async open() { if (!this.inline) { await this.modal.open().then(() => setTimeout(() => this.searchInput.focus(), 300)); } } onModalClose() { this.searchInput.value = ''; this.results = undefined; this.afterSearching(); this.container.classList.remove('s-search-no-results'); } handleKeyDown(ev) { if (ev.key === 'Enter' && this.search_term?.length) { window.location.href = salla.url.get('search?q=' + encodeURI(this.search_term)); } } getDefaultProductSlot() { return ('<div class="s-search-product-image-container">' + ' <img class="s-search-product-image" src="{image}" alt="{name}"/>' + '</div>' + '<div class="s-search-product-details">' + ' <div class="s-search-product-title">{name}</div> <div class="s-search-product-price">{price} <span class="s-search-product-regular-price">{regular_price}</span></div>' + '</div>'); } debounceSearch(event) { this.typing = true; clearTimeout(this.debounce); this.debounce = setTimeout(() => { this.typing = false; this.search_term = event.target.value; }, 700); } limitWordsAndSearch(event) { let value = event.target.value; const words = value.trim().split(/\s+/); if (words.length > this.maxWords) { const truncatedInput = words.slice(0, this.maxWords).join(' '); this.searchInput.value = truncatedInput; } this.debounceSearch(event); } getQueryParam(param) { const urlParams = new URLSearchParams(window.location.search); return urlParams.get(param); } handleActionClick() { if (!!this.search_term && this.search_term.length > 2) { return this.search(this.search_term); } } handleSearch(val) { this.inputValue = val; if (val.length > 2) { this.search(val); } else { this.results = undefined; this.afterSearching(); } } search(val) { this.noResults.style.display = 'none'; //run loading spinner or stop it this.loading = true; salla.product .fetch({ source: 'search', source_value: val }) .then(response => { this.results = response; salla.event.emit('Products Searched', val); }) .catch(err => (err !== 'Query is same as previous one!' ? (this.results = undefined) : null)) .finally(() => this.afterSearching(/*isEmpty*/ false)); } afterSearching(isEmpty = true) { this.noResults.style.display = isEmpty || this.results?.data.length > 0 ? 'none' : 'block'; Helper.toggleElementClassIf(this.container, 's-search-container-open', 's-search-no-results', () => this.results?.data.length); this.loading = false; salla.product.api.previousQuery = ''; //avoid having error 'Query is same as previous one!' after reopen modal; this.inputValue.length < 3 ? this.container.classList.remove('s-search-no-results') : ''; } render() { const searchContent = (h("div", { key: 'a403e7257b398cce5c26c61f55e143775575bb7b', class: { 's-search-container': true, 's-search-inline': this.inline }, ref: container => (this.container = container) }, h("div", { key: '3c3d523d181ec1c7b4e9d9e05988d1d2a4e0362d', class: "s-search-input-wrapper" }, h("span", { key: '86a8bcf0ab96c8818fc940d7ed4cfd70429ee2c1', class: "s-search-icon-wrap" }, h("span", { key: '0bacbc11b2b9c63c2167e79f46c73cb80804869a', class: "s-search-icon", innerHTML: this.loading ? '<i class="s-search-spinner-loader"/>' : Search })), h("input", { key: '766e7ae8ce9c2bdcf984d76c47a234ba5dc60d43', type: "search", enterkeyhint: "search", autocomplete: "off", class: "s-search-input", placeholder: salla.lang.get('blocks.header.search_placeholder'), onInput: (e) => this.limitWordsAndSearch(e), onKeyDown: e => this.handleKeyDown(e), ref: input => (this.searchInput = input), style: { height: this.height + 'px', borderRadius: this.oval ? this.height / 2 + 'px' : '', } }), this.showAction ? (h("salla-button", { loading: this.loading, class: { 's-search-action': true, 's-search-action-oval': this.oval }, onClick: () => this.handleActionClick() }, !this.loading && h("span", { innerHTML: Search }))) : null), h("div", { key: '307c8fc64860af188bf1f1ba6eea480c357ac46d', class: "s-search-results" }, this.results?.data.map(product => (h("a", { href: product.url + '?from=search-bar', class: { 's-search-product': true, 's-search-product-not-available': !product.is_available, }, innerHTML: this.productSlot .replace(/\{name\}/g, product.name) .replace(/\{price\}/g, product.price ? salla.money(product.price) : '') .replace(/\{regular_price\}/g, product.is_on_sale && product.regular_price ? salla.money(product.regular_price) : '') .replace(/\{image\}/g, product.image.url) }))), h("p", { key: 'b0971a0eb27a98f2e4e53da6462b0a8fa1b7fa42', ref: el => (this.noResults = el), class: "s-search-no-results-placeholder" }, salla.lang.get('common.elements.no_options'))))); const modalContent = (h("salla-modal", { key: '9394500f337c06afad44d5202e8e84dc2feb317f', position: "top", class: "s-search-modal", ref: modal => (this.modal = modal) }, searchContent)); const placeholderContent = (h("div", { key: 'f092b80ca35842209eae7b1ef143619d2a718229', class: "s-search-placeholder-wrapper s-search-inline" }, h("div", { key: '020d98faaa926578057e49734d7f11077a0b933b', class: "s-search-input-wrapper" }, h("span", { key: '44b6f78b74952d562efcb6dcd652432b5b1a7053', class: "s-search-icon-wrap" }, h("span", { key: 'd4e4166510cc4d38e53668f0f4f3995861a65d24', class: "s-search-icon", innerHTML: Search })), h("button", { key: '615e5dd776936c182fa7f50efaebbe5fa0fb7c61', type: "button", class: "s-search-input s-search-placeholder-trigger", onClick: () => this.open(), style: { height: this.height + 'px', borderRadius: this.oval ? this.height / 2 + 'px' : '', } }, salla.lang.get('blocks.header.search_placeholder'))), modalContent)); return this.placeholder ? (placeholderContent) : this.inline ? (h("div", { class: "s-search-modal" }, searchContent)) : (modalContent); } /** * Run it one time after load */ componentDidLoad() { this.afterSearching(); this.searchInput.value = this.getQueryParam('q'); } static get is() { return "salla-search"; } static get originalStyleUrls() { return { "$": ["salla-search.scss"] }; } static get styleUrls() { return { "$": ["salla-search.css"] }; } static get properties() { return { "placeholder": { "type": "boolean", "attribute": "placeholder", "mutable": false, "complexType": { "original": "boolean", "resolved": "boolean", "references": {} }, "required": false, "optional": false, "docs": { "tags": [], "text": "Set the component to display as a placeholder (button) that will open the modal. Defaults to `false`" }, "getter": false, "setter": false, "reflect": false, "defaultValue": "false" }, "inline": { "type": "boolean", "attribute": "inline", "mutable": false, "complexType": { "original": "boolean", "resolved": "boolean", "references": {} }, "required": false, "optional": false, "docs": { "tags": [], "text": "Set the component display without modal window. Defaults to `false`" }, "getter": false, "setter": false, "reflect": false, "defaultValue": "false" }, "oval": { "type": "boolean", "attribute": "oval", "mutable": false, "complexType": { "original": "boolean", "resolved": "boolean", "references": {} }, "required": false, "optional": false, "docs": { "tags": [], "text": "Adds a border radius to the input. Half of the height." }, "getter": false, "setter": false, "reflect": false, "defaultValue": "false" }, "height": { "type": "number", "attribute": "height", "mutable": false, "complexType": { "original": "number", "resolved": "number", "references": {} }, "required": false, "optional": false, "docs": { "tags": [], "text": "Sets the height of the input" }, "getter": false, "setter": false, "reflect": false, "defaultValue": "60" }, "maxWords": { "type": "number", "attribute": "max-words", "mutable": false, "complexType": { "original": "number", "resolved": "number", "references": {} }, "required": false, "optional": false, "docs": { "tags": [], "text": "Maximum number of words allowed in the input" }, "getter": false, "setter": false, "reflect": false, "defaultValue": "5" }, "showAction": { "type": "boolean", "attribute": "show-action", "mutable": false, "complexType": { "original": "boolean", "resolved": "boolean", "references": {} }, "required": false, "optional": false, "docs": { "tags": [], "text": "Show submit action buttons at the end." }, "getter": false, "setter": false, "reflect": false, "defaultValue": "false" } }; } static get states() { return { "translationLoaded": {}, "results": {}, "loading": {}, "typing": {}, "debounce": {}, "search_term": {} }; } static get elementRef() { return "host"; } static get watchers() { return [{ "propName": "search_term", "methodName": "handleSearch" }]; } static get listeners() { return [{ "name": "keydown", "method": "handleKeyDown", "target": undefined, "capture": false, "passive": false }]; } }