UNPKG

@salla.sa/twilight-components

Version:
699 lines (641 loc) 25.5 kB
/*! * Crafted with ❤ by Salla */ import { a as require_root, d as requireIsObject, e as requireIsSymbol, f as getDefaultExportFromCjs, r as registerInstance, h, g as getElement } from './index-DWStDVKB.js'; import { S as ShoppingBag } from './shopping-bag-DiKTtDW5.js'; import { H as Helper } from './Helper-CzEjEM5j.js'; import './anime.es-CgtvEd63.js'; var now_1; var hasRequiredNow; function requireNow () { if (hasRequiredNow) return now_1; hasRequiredNow = 1; var root = require_root(); /** * Gets the timestamp of the number of milliseconds that have elapsed since * the Unix epoch (1 January 1970 00:00:00 UTC). * * @static * @memberOf _ * @since 2.4.0 * @category Date * @returns {number} Returns the timestamp. * @example * * _.defer(function(stamp) { * console.log(_.now() - stamp); * }, _.now()); * // => Logs the number of milliseconds it took for the deferred invocation. */ var now = function() { return root.Date.now(); }; now_1 = now; return now_1; } /** Used to match a single whitespace character. */ var _trimmedEndIndex; var hasRequired_trimmedEndIndex; function require_trimmedEndIndex () { if (hasRequired_trimmedEndIndex) return _trimmedEndIndex; hasRequired_trimmedEndIndex = 1; var reWhitespace = /\s/; /** * Used by `_.trim` and `_.trimEnd` to get the index of the last non-whitespace * character of `string`. * * @private * @param {string} string The string to inspect. * @returns {number} Returns the index of the last non-whitespace character. */ function trimmedEndIndex(string) { var index = string.length; while (index-- && reWhitespace.test(string.charAt(index))) {} return index; } _trimmedEndIndex = trimmedEndIndex; return _trimmedEndIndex; } var _baseTrim; var hasRequired_baseTrim; function require_baseTrim () { if (hasRequired_baseTrim) return _baseTrim; hasRequired_baseTrim = 1; var trimmedEndIndex = require_trimmedEndIndex(); /** Used to match leading whitespace. */ var reTrimStart = /^\s+/; /** * The base implementation of `_.trim`. * * @private * @param {string} string The string to trim. * @returns {string} Returns the trimmed string. */ function baseTrim(string) { return string ? string.slice(0, trimmedEndIndex(string) + 1).replace(reTrimStart, '') : string; } _baseTrim = baseTrim; return _baseTrim; } var toNumber_1; var hasRequiredToNumber; function requireToNumber () { if (hasRequiredToNumber) return toNumber_1; hasRequiredToNumber = 1; var baseTrim = require_baseTrim(), isObject = requireIsObject(), isSymbol = requireIsSymbol(); /** Used as references for various `Number` constants. */ var NAN = 0 / 0; /** Used to detect bad signed hexadecimal string values. */ var reIsBadHex = /^[-+]0x[0-9a-f]+$/i; /** Used to detect binary string values. */ var reIsBinary = /^0b[01]+$/i; /** Used to detect octal string values. */ var reIsOctal = /^0o[0-7]+$/i; /** Built-in method references without a dependency on `root`. */ var freeParseInt = parseInt; /** * Converts `value` to a number. * * @static * @memberOf _ * @since 4.0.0 * @category Lang * @param {*} value The value to process. * @returns {number} Returns the number. * @example * * _.toNumber(3.2); * // => 3.2 * * _.toNumber(Number.MIN_VALUE); * // => 5e-324 * * _.toNumber(Infinity); * // => Infinity * * _.toNumber('3.2'); * // => 3.2 */ function toNumber(value) { if (typeof value == 'number') { return value; } if (isSymbol(value)) { return NAN; } if (isObject(value)) { var other = typeof value.valueOf == 'function' ? value.valueOf() : value; value = isObject(other) ? (other + '') : other; } if (typeof value != 'string') { return value === 0 ? value : +value; } value = baseTrim(value); var isBinary = reIsBinary.test(value); return (isBinary || reIsOctal.test(value)) ? freeParseInt(value.slice(2), isBinary ? 2 : 8) : (reIsBadHex.test(value) ? NAN : +value); } toNumber_1 = toNumber; return toNumber_1; } var debounce_1; var hasRequiredDebounce; function requireDebounce () { if (hasRequiredDebounce) return debounce_1; hasRequiredDebounce = 1; var isObject = requireIsObject(), now = requireNow(), toNumber = requireToNumber(); /** Error message constants. */ var FUNC_ERROR_TEXT = 'Expected a function'; /* Built-in method references for those with the same name as other `lodash` methods. */ var nativeMax = Math.max, nativeMin = Math.min; /** * Creates a debounced function that delays invoking `func` until after `wait` * milliseconds have elapsed since the last time the debounced function was * invoked. The debounced function comes with a `cancel` method to cancel * delayed `func` invocations and a `flush` method to immediately invoke them. * Provide `options` to indicate whether `func` should be invoked on the * leading and/or trailing edge of the `wait` timeout. The `func` is invoked * with the last arguments provided to the debounced function. Subsequent * calls to the debounced function return the result of the last `func` * invocation. * * **Note:** If `leading` and `trailing` options are `true`, `func` is * invoked on the trailing edge of the timeout only if the debounced function * is invoked more than once during the `wait` timeout. * * If `wait` is `0` and `leading` is `false`, `func` invocation is deferred * until to the next tick, similar to `setTimeout` with a timeout of `0`. * * See [David Corbacho's article](https://css-tricks.com/debouncing-throttling-explained-examples/) * for details over the differences between `_.debounce` and `_.throttle`. * * @static * @memberOf _ * @since 0.1.0 * @category Function * @param {Function} func The function to debounce. * @param {number} [wait=0] The number of milliseconds to delay. * @param {Object} [options={}] The options object. * @param {boolean} [options.leading=false] * Specify invoking on the leading edge of the timeout. * @param {number} [options.maxWait] * The maximum time `func` is allowed to be delayed before it's invoked. * @param {boolean} [options.trailing=true] * Specify invoking on the trailing edge of the timeout. * @returns {Function} Returns the new debounced function. * @example * * // Avoid costly calculations while the window size is in flux. * jQuery(window).on('resize', _.debounce(calculateLayout, 150)); * * // Invoke `sendMail` when clicked, debouncing subsequent calls. * jQuery(element).on('click', _.debounce(sendMail, 300, { * 'leading': true, * 'trailing': false * })); * * // Ensure `batchLog` is invoked once after 1 second of debounced calls. * var debounced = _.debounce(batchLog, 250, { 'maxWait': 1000 }); * var source = new EventSource('/stream'); * jQuery(source).on('message', debounced); * * // Cancel the trailing debounced invocation. * jQuery(window).on('popstate', debounced.cancel); */ function debounce(func, wait, options) { var lastArgs, lastThis, maxWait, result, timerId, lastCallTime, lastInvokeTime = 0, leading = false, maxing = false, trailing = true; if (typeof func != 'function') { throw new TypeError(FUNC_ERROR_TEXT); } wait = toNumber(wait) || 0; if (isObject(options)) { leading = !!options.leading; maxing = 'maxWait' in options; maxWait = maxing ? nativeMax(toNumber(options.maxWait) || 0, wait) : maxWait; trailing = 'trailing' in options ? !!options.trailing : trailing; } function invokeFunc(time) { var args = lastArgs, thisArg = lastThis; lastArgs = lastThis = undefined; lastInvokeTime = time; result = func.apply(thisArg, args); return result; } function leadingEdge(time) { // Reset any `maxWait` timer. lastInvokeTime = time; // Start the timer for the trailing edge. timerId = setTimeout(timerExpired, wait); // Invoke the leading edge. return leading ? invokeFunc(time) : result; } function remainingWait(time) { var timeSinceLastCall = time - lastCallTime, timeSinceLastInvoke = time - lastInvokeTime, timeWaiting = wait - timeSinceLastCall; return maxing ? nativeMin(timeWaiting, maxWait - timeSinceLastInvoke) : timeWaiting; } function shouldInvoke(time) { var timeSinceLastCall = time - lastCallTime, timeSinceLastInvoke = time - lastInvokeTime; // Either this is the first call, activity has stopped and we're at the // trailing edge, the system time has gone backwards and we're treating // it as the trailing edge, or we've hit the `maxWait` limit. return (lastCallTime === undefined || (timeSinceLastCall >= wait) || (timeSinceLastCall < 0) || (maxing && timeSinceLastInvoke >= maxWait)); } function timerExpired() { var time = now(); if (shouldInvoke(time)) { return trailingEdge(time); } // Restart the timer. timerId = setTimeout(timerExpired, remainingWait(time)); } function trailingEdge(time) { timerId = undefined; // Only invoke if we have `lastArgs` which means `func` has been // debounced at least once. if (trailing && lastArgs) { return invokeFunc(time); } lastArgs = lastThis = undefined; return result; } function cancel() { if (timerId !== undefined) { clearTimeout(timerId); } lastInvokeTime = 0; lastArgs = lastCallTime = lastThis = timerId = undefined; } function flush() { return timerId === undefined ? result : trailingEdge(now()); } function debounced() { var time = now(), isInvoking = shouldInvoke(time); lastArgs = arguments; lastThis = this; lastCallTime = time; if (isInvoking) { if (timerId === undefined) { return leadingEdge(lastCallTime); } if (maxing) { // Handle invocations in a tight loop. clearTimeout(timerId); timerId = setTimeout(timerExpired, wait); return invokeFunc(lastCallTime); } } if (timerId === undefined) { timerId = setTimeout(timerExpired, wait); } return result; } debounced.cancel = cancel; debounced.flush = flush; return debounced; } debounce_1 = debounce; return debounce_1; } var throttle_1; var hasRequiredThrottle; function requireThrottle () { if (hasRequiredThrottle) return throttle_1; hasRequiredThrottle = 1; var debounce = requireDebounce(), isObject = requireIsObject(); /** Error message constants. */ var FUNC_ERROR_TEXT = 'Expected a function'; /** * Creates a throttled function that only invokes `func` at most once per * every `wait` milliseconds. The throttled function comes with a `cancel` * method to cancel delayed `func` invocations and a `flush` method to * immediately invoke them. Provide `options` to indicate whether `func` * should be invoked on the leading and/or trailing edge of the `wait` * timeout. The `func` is invoked with the last arguments provided to the * throttled function. Subsequent calls to the throttled function return the * result of the last `func` invocation. * * **Note:** If `leading` and `trailing` options are `true`, `func` is * invoked on the trailing edge of the timeout only if the throttled function * is invoked more than once during the `wait` timeout. * * If `wait` is `0` and `leading` is `false`, `func` invocation is deferred * until to the next tick, similar to `setTimeout` with a timeout of `0`. * * See [David Corbacho's article](https://css-tricks.com/debouncing-throttling-explained-examples/) * for details over the differences between `_.throttle` and `_.debounce`. * * @static * @memberOf _ * @since 0.1.0 * @category Function * @param {Function} func The function to throttle. * @param {number} [wait=0] The number of milliseconds to throttle invocations to. * @param {Object} [options={}] The options object. * @param {boolean} [options.leading=true] * Specify invoking on the leading edge of the timeout. * @param {boolean} [options.trailing=true] * Specify invoking on the trailing edge of the timeout. * @returns {Function} Returns the new throttled function. * @example * * // Avoid excessively updating the position while scrolling. * jQuery(window).on('scroll', _.throttle(updatePosition, 100)); * * // Invoke `renewToken` when the click event is fired, but not more than once every 5 minutes. * var throttled = _.throttle(renewToken, 300000, { 'trailing': false }); * jQuery(element).on('click', throttled); * * // Cancel the trailing throttled invocation. * jQuery(window).on('popstate', throttled.cancel); */ function throttle(func, wait, options) { var leading = true, trailing = true; if (typeof func != 'function') { throw new TypeError(FUNC_ERROR_TEXT); } if (isObject(options)) { leading = 'leading' in options ? !!options.leading : leading; trailing = 'trailing' in options ? !!options.trailing : trailing; } return debounce(func, wait, { 'leading': leading, 'maxWait': wait, 'trailing': trailing }); } throttle_1 = throttle; return throttle_1; } var throttleExports = requireThrottle(); var throttle = /*@__PURE__*/getDefaultExportFromCjs(throttleExports); class MasonryLayout { constructor(grid) { this.grid = grid; this.create = () => { this.mutationObserver.observe(this.grid, { childList: true, }); for (const item of Array.from(this.grid.children)) { this.resizeObserver.observe(item); } }; this.onContainerMutation = (mutations) => { const removedNodes = mutations.flatMap((mutation) => Array.from(mutation.removedNodes)); const addedNodes = mutations.flatMap((mutation) => Array.from(mutation.addedNodes)); for (const node of removedNodes) { if (node instanceof Element) { this.resizeObserver.unobserve(node); } } for (const node of addedNodes) { if (node instanceof Element) { this.resizeObserver.observe(node); } } if (removedNodes.length > 0 && addedNodes.length === 0) { this.update(); } }; this.onChildrenResize = (entries) => { const entriesToUpdate = entries.filter((entry) => entry.target.parentElement !== null); if (entriesToUpdate.length > 0) { this.update(); } }; this.update = throttle(() => { const computedStyle = window.getComputedStyle(this.grid); if (computedStyle.getPropertyValue("display").includes("grid") === false) { this.clean(); return; } const columns = parseGridTemplateColumns(this.grid); if (columns.length <= 1) { this.clean(); return; } const rowGap = Number.parseFloat(computedStyle.getPropertyValue("row-gap").trim()) || 0; const items = Array.from(this.grid.children); for (let columnIndex = 0; columnIndex < columns.length; columnIndex++) { const firstItemInColumn = items[columnIndex]; firstItemInColumn === null || firstItemInColumn === void 0 ? void 0 : firstItemInColumn.style.removeProperty("margin-top"); } for (let index = 0; index < items.length; index++) { const prevItem = items[index - columns.length]; const nextItem = items[index]; if (prevItem !== undefined && nextItem !== undefined) { const prevBottom = prevItem.getBoundingClientRect().bottom; nextItem.style.removeProperty("margin-top"); const nextTop = nextItem.getBoundingClientRect().top; if (nextTop - rowGap !== prevBottom) { const margin = Math.round((prevBottom - (nextTop - rowGap) + Number.EPSILON) * 100) / 100; nextItem.style.setProperty("margin-top", `${margin}px`); } } } }, 32); this.destroy = () => { this.resizeObserver.disconnect(); this.mutationObserver.disconnect(); this.clean(); }; this.clean = () => { for (const item of Array.from(this.grid.children)) { item.style.removeProperty("margin-top"); } }; this.mutationObserver = new MutationObserver(this.onContainerMutation); this.resizeObserver = new ResizeObserver(this.onChildrenResize); if (CSS.supports("grid-template-rows", "masonry") === false) { this.create(); } } [Symbol.dispose]() { this.destroy(); } } function parseGridTemplateColumns(grid) { const computedStyle = window.getComputedStyle(grid); const gridTemplateColumns = computedStyle.getPropertyValue("grid-template-columns"); return gridTemplateColumns .trim() .split(/\s+(?=(?:[^()]*\([^()]*\))*[^()]*$)/); } const sallaReviewsPageCss = ":host{display:block}"; const SallaReviewsPage = class { constructor(hostRef) { registerInstance(this, hostRef); this.reviews = []; this.isLoading = false; this.pagination = null; this.sort = "latest"; } getUrlParams() { const params = new URLSearchParams(window.location.search); return { sort: params.get('sort') || null, page: Number.parseInt(params.get('page')) || 1 }; } updateUrlParams(params) { const url = new URL(window.location.href); for (const [key, value] of Object.entries(params)) { if (value) { url.searchParams.set(key, value.toString()); } else { url.searchParams.delete(key); } } window.history.replaceState({}, '', url.toString()); } fetchReviews(sort, page) { const urlParams = this.getUrlParams(); return salla.api.request('reviews', { params: { type: 'products', format: 'lite', per_page: 8, page: page || urlParams.page || 1, sort: sort || urlParams.sort || null } }); } async initializeMasonry() { const grid = this.el.querySelector('.s-reviews-page-grid'); if (!grid) return; try { new MasonryLayout(grid); salla.logger.info('Masonry initialized successfully'); } catch (error) { salla.logger.error('Masonry initialization failed:', error); } } animateReviewCards() { const items = this.wrapper.querySelectorAll('salla-review-card:not(.animated)'); Helper.animateItems(items); } initiateInfiniteScroll() { var _a, _b, _c; if (!this.wrapper) { salla.logger.error('Wrapper is undefined. Cannot initiate infinite scroll.'); return; } this.infiniteScroll = salla.infiniteScroll.initiate(this.wrapper, this.wrapper, { path: () => { var _a, _b; return ((_b = (_a = this.pagination) === null || _a === void 0 ? void 0 : _a.links) === null || _b === void 0 ? void 0 : _b.next) || null; }, history: false, scrollThreshold: false, }, true); (_a = this.infiniteScroll) === null || _a === void 0 ? void 0 : _a.on('request', () => { this.isLoading = true; }); (_b = this.infiniteScroll) === null || _b === void 0 ? void 0 : _b.on('load', response => { this.pagination = response.pagination; this.reviews = [...this.reviews, ...response.data]; this.isLoading = false; // Update URL with the current page this.updateUrlParams({ page: response.pagination.current_page }); }); (_c = this.infiniteScroll) === null || _c === void 0 ? void 0 : _c.on('error', (e) => { salla.logger.error('Error loading more reviews:', e); this.isLoading = false; }); } componentDidRender() { setTimeout(() => { requestAnimationFrame(this.animateReviewCards.bind(this)); }, 176); } async componentWillLoad() { var _a; try { await salla.onReady(); // Initialize language variables this.langTitlesReviews = salla.lang.get("common.titles.reviews"); this.langSorting = salla.lang.get('pages.categories.sorting'); this.placeholderText = salla.lang.choice("pages.rating.reviews", 0); this.langLoadMore = salla.lang.get('common.elements.load_more'); this.langSortByTopRating = salla.lang.get('pages.testimonials.sort_by_rating_desc'); this.langSortByMostRecent = salla.lang.get('pages.testimonials.sort_by_date_desc'); this.langSortByLeastRated = salla.lang.get('pages.testimonials.sort_by_rating_asc'); this.langSortByLeastRecent = salla.lang.get('pages.testimonials.sort_by_date_asc'); const urlParams = this.getUrlParams(); const response = await this.fetchReviews(urlParams.sort); this.sort = urlParams.sort; this.reviews = response.data; this.pagination = response.pagination; this.langRatingReviews = salla.lang.choice("pages.rating.reviews", (_a = this.pagination) === null || _a === void 0 ? void 0 : _a.total); } catch (error) { salla.logger.error('Error loading reviews:', error); } } async handleSorting(e) { const value = e.target.value; this.sort = value; this.updateUrlParams({ sort: value, page: 1 }); // Reset to page 1 when changing sort const response = await this.fetchReviews(value, 1); this.reviews = response.data; this.pagination = response.pagination; } async componentDidLoad() { await this.initializeMasonry(); this.initiateInfiniteScroll(); this.sort = this.getUrlParams().sort || "latest"; this.updateUrlParams({ sort: this.sort, page: 1 }); } disconnectedCallback() { if (this.infiniteScroll) { this.infiniteScroll.destroy(); } } renderSortingOptions() { const options = [ { value: 'latest', label: this.langSortByMostRecent }, { value: 'oldest', label: this.langSortByLeastRecent }, { value: 'top_rating', label: this.langSortByTopRating }, { value: 'bottom_rating', label: this.langSortByLeastRated }, ]; return options.map(option => (h("option", { key: option.value, value: option.value, selected: option.value === this.sort }, option.label))); } render() { var _a, _b; return (h("host", { key: '399a4d822a8442fb0f9a906f0db52fddec92ffc4' }, h("div", { key: 'fa4fa15c19b774d7eabf56f67f5bcae6b71572ab', class: "s-reviews-page-header-wrapper" }, h("h2", { key: '169d557070590248e506718c5b9aa116a84e5634', class: "s-reviews-page-title" }, this.langTitlesReviews, h("span", { key: '2549bf0697263d25aa3f2666164705e20dc51527', class: "s-reviews-page-count" }, "(", this.langRatingReviews, ")")), h("div", { key: 'bfc9cef261a830871a9832e0f137e2f181051c95', class: "s-reviews-page-filter-wrapper" }, h("label", { key: 'e4e245499b86254383ed7fb3290d0a09ee9bc604', class: "s-reviews-page-filter-label", htmlFor: "testimonials-filter" }, this.langSorting), h("select", { key: '6fcf56fa0a74026fb568d884218c55a96cd820f1', onChange: (e) => this.handleSorting(e), disabled: !this.reviews.length, class: "s-reviews-page-filter" }, this.renderSortingOptions()))), this.reviews.length ? h("main", { class: "s-reviews-page-grid", ref: el => { this.wrapper = el; } }, this.reviews.map(review => (h("salla-review-card", { key: review.id, review: review })))) : h("div", { class: "s-products-list-placeholder" }, h("span", { innerHTML: ShoppingBag }), h("p", null, this.placeholderText)), ((_b = (_a = this.pagination) === null || _a === void 0 ? void 0 : _a.links) === null || _b === void 0 ? void 0 : _b.next) && this.reviews.length ? (h("div", { class: "s-reviews-page-load-more-container" }, h("salla-button", { class: "s-reviews-page-load-more-btn", loading: this.isLoading, onClick: () => { var _a; return (_a = this.infiniteScroll) === null || _a === void 0 ? void 0 : _a.loadNextPage(); }, onKeyUp: () => { var _a; return (_a = this.infiniteScroll) === null || _a === void 0 ? void 0 : _a.loadNextPage(); } }, this.langLoadMore))) : null)); } get el() { return getElement(this); } }; SallaReviewsPage.style = sallaReviewsPageCss; export { SallaReviewsPage as salla_reviews_page }; //# sourceMappingURL=salla-reviews-page.entry.js.map //# sourceMappingURL=salla-reviews-page.entry.js.map