@salla.sa/twilight-components
Version:
Salla Web Component
699 lines (641 loc) • 25.5 kB
JavaScript
/*!
* 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