@searchspring/snap-store-mobx
Advanced tools
Comparing version 0.55.0 to 0.56.0
@@ -109,3 +109,2 @@ "use strict"; | ||
this.error = undefined; | ||
this.loaded = !!data.pagination; | ||
this.meta = new MetaStore_1.MetaStore(data.meta); | ||
@@ -131,3 +130,3 @@ // set the query to match the actual queried term and not the input query | ||
this.filters = new Stores_1.SearchFilterStore(this.services, data.filters, this.meta.data); | ||
this.results = new Stores_1.SearchResultStore(this.config, this.services, this.meta.data, data.results || [], data.pagination, data.merchandising); | ||
this.results = new Stores_1.SearchResultStore(this.config, this.services, this.meta.data, data.results || [], data.pagination, data.merchandising, this.loaded); | ||
if ((this.results.length === 0 && !this.trending.filter(function (term) { return term.active; }).length) || ((_a = this.terms) === null || _a === void 0 ? void 0 : _a.filter(function (term) { return term.active; }).length)) { | ||
@@ -140,2 +139,3 @@ // if a trending term was selected and then a subsequent search yields no results, reset trending terms to remove active state | ||
this.sorting = new Stores_1.SearchSortingStore(this.services, data.sorting || [], data.search || {}, this.meta.data); | ||
this.loaded = !!data.pagination; | ||
}; | ||
@@ -142,0 +142,0 @@ return AutocompleteStore; |
@@ -50,3 +50,3 @@ "use strict"; | ||
var _a; | ||
price += (((_a = item.display.mappings.core) === null || _a === void 0 ? void 0 : _a.price) || 0) * item.quantity; | ||
price += +(((_a = item.display.mappings.core) === null || _a === void 0 ? void 0 : _a.price) || 0) * item.quantity; | ||
}); | ||
@@ -63,3 +63,3 @@ return price; | ||
var _a, _b; | ||
price += (((_a = item.display.mappings.core) === null || _a === void 0 ? void 0 : _a.msrp) || ((_b = item.display.mappings.core) === null || _b === void 0 ? void 0 : _b.price) || 0) * item.quantity; | ||
price += (+(((_a = item.display.mappings.core) === null || _a === void 0 ? void 0 : _a.msrp) || 0) || +(((_b = item.display.mappings.core) === null || _b === void 0 ? void 0 : _b.price) || 0) || 0) * item.quantity; | ||
}); | ||
@@ -66,0 +66,0 @@ return price; |
@@ -47,6 +47,5 @@ "use strict"; | ||
this.error = undefined; | ||
this.loaded = !!(data === null || data === void 0 ? void 0 : data.profile); | ||
this.meta = new MetaStore_1.MetaStore(data === null || data === void 0 ? void 0 : data.meta); | ||
this.profile = new Stores_2.RecommendationProfileStore(this.services, data); | ||
this.results = new Stores_1.SearchResultStore(this.config, this.services, this.meta.data, data === null || data === void 0 ? void 0 : data.results); | ||
this.results = new Stores_1.SearchResultStore(this.config, this.services, this.meta.data, data === null || data === void 0 ? void 0 : data.results, undefined, undefined, this.loaded); | ||
// only create a cart store when type is bundle | ||
@@ -56,2 +55,3 @@ if (this.profile.type == 'bundle') { | ||
} | ||
this.loaded = !!(data === null || data === void 0 ? void 0 : data.profile); | ||
}; | ||
@@ -58,0 +58,0 @@ return RecommendationStore; |
@@ -73,3 +73,2 @@ "use strict"; | ||
this.error = undefined; | ||
this.loaded = !!data.pagination; | ||
this.meta = new MetaStore_1.MetaStore(data.meta); | ||
@@ -80,5 +79,6 @@ this.merchandising = new Stores_1.SearchMerchandisingStore(this.services, (data === null || data === void 0 ? void 0 : data.merchandising) || {}); | ||
this.filters = new Stores_1.SearchFilterStore(this.services, data.filters, this.meta.data); | ||
this.results = new Stores_1.SearchResultStore(this.config, this.services, this.meta.data, (data === null || data === void 0 ? void 0 : data.results) || [], data.pagination, data.merchandising); | ||
this.results = new Stores_1.SearchResultStore(this.config, this.services, this.meta.data, (data === null || data === void 0 ? void 0 : data.results) || [], data.pagination, data.merchandising, this.loaded); | ||
this.pagination = new Stores_1.SearchPaginationStore(this.config, this.services, data.pagination, this.meta.data); | ||
this.sorting = new Stores_1.SearchSortingStore(this.services, (data === null || data === void 0 ? void 0 : data.sorting) || [], (data === null || data === void 0 ? void 0 : data.search) || {}, this.meta.data); | ||
this.loaded = !!data.pagination; | ||
}; | ||
@@ -85,0 +85,0 @@ return SearchStore; |
@@ -5,3 +5,3 @@ export { SearchMerchandisingStore, BannerContent, ContentType } from './SearchMerchandisingStore'; | ||
export { SearchPaginationStore } from './SearchPaginationStore'; | ||
export { SearchResultStore, Product, Banner } from './SearchResultStore'; | ||
export { SearchResultStore, Product, Banner, VariantSelection, VariantSelectionValue } from './SearchResultStore'; | ||
export { SearchSortingStore } from './SearchSortingStore'; | ||
@@ -8,0 +8,0 @@ export { SearchQueryStore } from './SearchQueryStore'; |
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.SearchHistoryStore = exports.SearchQueryStore = exports.SearchSortingStore = exports.Banner = exports.Product = exports.SearchResultStore = exports.SearchPaginationStore = exports.Filter = exports.SearchFilterStore = exports.FacetRangeValue = exports.FacetHierarchyValue = exports.FacetValue = exports.RangeFacet = exports.ValueFacet = exports.SearchFacetStore = exports.ContentType = exports.SearchMerchandisingStore = void 0; | ||
exports.SearchHistoryStore = exports.SearchQueryStore = exports.SearchSortingStore = exports.VariantSelection = exports.Banner = exports.Product = exports.SearchResultStore = exports.SearchPaginationStore = exports.Filter = exports.SearchFilterStore = exports.FacetRangeValue = exports.FacetHierarchyValue = exports.FacetValue = exports.RangeFacet = exports.ValueFacet = exports.SearchFacetStore = exports.ContentType = exports.SearchMerchandisingStore = void 0; | ||
var SearchMerchandisingStore_1 = require("./SearchMerchandisingStore"); | ||
@@ -23,2 +23,3 @@ Object.defineProperty(exports, "SearchMerchandisingStore", { enumerable: true, get: function () { return SearchMerchandisingStore_1.SearchMerchandisingStore; } }); | ||
Object.defineProperty(exports, "Banner", { enumerable: true, get: function () { return SearchResultStore_1.Banner; } }); | ||
Object.defineProperty(exports, "VariantSelection", { enumerable: true, get: function () { return SearchResultStore_1.VariantSelection; } }); | ||
var SearchSortingStore_1 = require("./SearchSortingStore"); | ||
@@ -25,0 +26,0 @@ Object.defineProperty(exports, "SearchSortingStore", { enumerable: true, get: function () { return SearchSortingStore_1.SearchSortingStore; } }); |
@@ -1,6 +0,6 @@ | ||
import type { StoreServices, StoreConfigs, VariantSelectionOptions, ResultBadge, VariantConfig } from '../../types'; | ||
import type { StoreServices, StoreConfigs, ResultBadge, VariantOptionConfig, VariantConfig } from '../../types'; | ||
import type { SearchResponseModelResult, SearchResponseModelPagination, SearchResponseModelMerchandising, SearchResponseModelResultMappings, SearchResponseModelMerchandisingContentInline, SearchResponseModelMerchandisingContentConfig, MetaResponseModel } from '@searchspring/snapi-types'; | ||
export declare class SearchResultStore extends Array<Product | Banner> { | ||
static get [Symbol.species](): ArrayConstructor; | ||
constructor(config: StoreConfigs, services: StoreServices, metaData: MetaResponseModel, resultData?: SearchResponseModelResult[], paginationData?: SearchResponseModelPagination, merchData?: SearchResponseModelMerchandising); | ||
constructor(config: StoreConfigs, services: StoreServices, metaData: MetaResponseModel, resultData?: SearchResponseModelResult[], paginationData?: SearchResponseModelPagination, merchData?: SearchResponseModelMerchandising, loaded?: boolean); | ||
} | ||
@@ -70,6 +70,7 @@ export declare class Banner { | ||
} | ||
type SelectionValue = { | ||
export type VariantSelectionValue = { | ||
value: string; | ||
label?: string; | ||
thumbnailImageUrl?: string; | ||
backgroundImageUrl?: string; | ||
background?: string; | ||
@@ -81,7 +82,8 @@ available?: boolean; | ||
label: string; | ||
selected?: string; | ||
previouslySelected?: string; | ||
values: SelectionValue[]; | ||
selected?: VariantSelectionValue; | ||
previouslySelected?: VariantSelectionValue; | ||
values: VariantSelectionValue[]; | ||
private config; | ||
private variantsUpdate; | ||
constructor(variants: Variants, selectorConfig: VariantSelectionOptions); | ||
constructor(variants: Variants, selectorField: string, variantConfig?: VariantOptionConfig); | ||
refineValues(variants: Variants): void; | ||
@@ -88,0 +90,0 @@ reset(): void; |
@@ -45,10 +45,42 @@ "use strict"; | ||
var is_plain_object_1 = require("is-plain-object"); | ||
var VARIANT_ATTRIBUTE = 'ss-variant-option'; | ||
var VARIANT_ATTRIBUTE_SELECTED = 'ss-variant-option-selected'; | ||
var SearchResultStore = /** @class */ (function (_super) { | ||
__extends(SearchResultStore, _super); | ||
function SearchResultStore(config, services, metaData, resultData, paginationData, merchData) { | ||
var _a; | ||
function SearchResultStore(config, services, metaData, resultData, paginationData, merchData, loaded) { | ||
var _a, _b, _c, _d; | ||
var results = (resultData || []).map(function (result) { | ||
return new Product(services, result, metaData, config); | ||
}); | ||
if ((_a = merchData === null || merchData === void 0 ? void 0 : merchData.content) === null || _a === void 0 ? void 0 : _a.inline) { | ||
var variantConfig = (_a = config === null || config === void 0 ? void 0 : config.settings) === null || _a === void 0 ? void 0 : _a.variants; | ||
// preselected variant options | ||
if ((_b = variantConfig === null || variantConfig === void 0 ? void 0 : variantConfig.realtime) === null || _b === void 0 ? void 0 : _b.enabled) { | ||
// attach click events - ONLY happens once (known limitation) | ||
if (!loaded && results.length) { | ||
document.querySelectorAll("[".concat(VARIANT_ATTRIBUTE, "]")).forEach(function (elem) { | ||
var _a; | ||
if ((variantConfig === null || variantConfig === void 0 ? void 0 : variantConfig.field) && !((_a = variantConfig === null || variantConfig === void 0 ? void 0 : variantConfig.realtime) === null || _a === void 0 ? void 0 : _a.enabled) === false) { | ||
elem.addEventListener('click', function () { return variantOptionClick(elem, variantConfig, results); }); | ||
} | ||
}); | ||
} | ||
// check for attributes for preselection | ||
if (results.length) { | ||
if ((variantConfig === null || variantConfig === void 0 ? void 0 : variantConfig.field) && !((_c = variantConfig === null || variantConfig === void 0 ? void 0 : variantConfig.realtime) === null || _c === void 0 ? void 0 : _c.enabled) === false) { | ||
var options_1 = {}; | ||
// grab values from elements on the page to form preselected elements | ||
document.querySelectorAll("[".concat(VARIANT_ATTRIBUTE_SELECTED, "]")).forEach(function (elem) { | ||
var attr = elem.getAttribute(VARIANT_ATTRIBUTE); | ||
if (attr) { | ||
var _a = attr.split(':'), option = _a[0], value = _a[1]; | ||
if (option && value) { | ||
options_1[option.toLowerCase()] = [value.toLowerCase()]; | ||
} | ||
} | ||
}); | ||
makeVariantSelections(variantConfig, options_1, results); | ||
} | ||
} | ||
} | ||
if ((_d = merchData === null || merchData === void 0 ? void 0 : merchData.content) === null || _d === void 0 ? void 0 : _d.inline) { | ||
var banners = merchData.content.inline | ||
@@ -118,3 +150,3 @@ .sort(function (a, b) { | ||
var parsedVariants = JSON.parse(this.attributes[variantsField]); | ||
this.variants = new Variants(parsedVariants, this.mask, (_c = config.settings) === null || _c === void 0 ? void 0 : _c.variants); | ||
this.variants = new Variants(parsedVariants, this.mask, (_c = config === null || config === void 0 ? void 0 : config.settings) === null || _c === void 0 ? void 0 : _c.variants); | ||
} | ||
@@ -251,3 +283,5 @@ catch (err) { | ||
}; | ||
this.config = config; | ||
if (config) { | ||
this.config = config; | ||
} | ||
this.update(variantData, config); | ||
@@ -259,8 +293,8 @@ } | ||
try { | ||
var options_1 = []; | ||
var options_2 = []; | ||
// create variants objects | ||
this.data = variantData.map(function (variant) { | ||
Object.keys(variant.options).forEach(function (variantOption) { | ||
if (!options_1.includes(variantOption)) { | ||
options_1.push(variantOption); | ||
if (!options_2.includes(variantOption)) { | ||
options_2.push(variantOption); | ||
} | ||
@@ -272,9 +306,6 @@ }); | ||
this.selections = []; | ||
options_1.map(function (option) { | ||
// TODO - merge with variant config before constructing selection (for label overrides and swatch mappings) | ||
var optionConfig = { | ||
field: option, | ||
label: option, | ||
}; | ||
_this.selections.push(new VariantSelection(_this, optionConfig)); | ||
options_2.map(function (option) { | ||
var _a; | ||
var variantOptionConfig = ((_a = _this.config) === null || _a === void 0 ? void 0 : _a.options) && _this.config.options[option]; | ||
_this.selections.push(new VariantSelection(_this, option, variantOptionConfig)); | ||
}); | ||
@@ -299,3 +330,3 @@ var preselectedOptions_1 = {}; | ||
// options = {color: 'Blue', size: 'L'}; | ||
if (!options) { | ||
if (!options || !Object.keys(options).length) { | ||
// select first available for each selection | ||
@@ -315,3 +346,3 @@ this.selections.forEach(function (selection) { | ||
var preferedOptions = options[selection.field]; | ||
var preferencedOption = availableOptions[0]; | ||
var preferencedOption = selection.selected || availableOptions[0]; | ||
// if theres a preference for that field | ||
@@ -321,3 +352,3 @@ if (preferedOptions) { | ||
//see if that option is in the available options | ||
var availablePreferedOptions = availableOptions.find(function (value) { return value.value.toLowerCase() == preference.toLowerCase(); }); | ||
var availablePreferedOptions = availableOptions.find(function (value) { return value.value.toString().toLowerCase() == (preference === null || preference === void 0 ? void 0 : preference.toString().toLowerCase()); }); | ||
//use it | ||
@@ -339,3 +370,3 @@ if (availablePreferedOptions) { | ||
if (preferencedOption) { | ||
selection.select(preferencedOption.value); | ||
selection.select(preferencedOption.value, true); | ||
} | ||
@@ -358,7 +389,7 @@ }); | ||
// check to see if we have enough selections made to update the display | ||
var selectedSelections = this.selections.filter(function (selection) { return selection.selected; }); | ||
var selectedSelections = this.selections.filter(function (selection) { var _a, _b; return (_b = (_a = selection.selected) === null || _a === void 0 ? void 0 : _a.value) === null || _b === void 0 ? void 0 : _b.length; }); | ||
if (selectedSelections.length) { | ||
var availableVariants = this.data; | ||
var _loop_1 = function (selectedSelection) { | ||
availableVariants = availableVariants.filter(function (variant) { return selectedSelection.selected == variant.options[selectedSelection.field].value && variant.available; }); | ||
availableVariants = availableVariants.filter(function (variant) { var _a; return ((_a = selectedSelection.selected) === null || _a === void 0 ? void 0 : _a.value) == variant.options[selectedSelection.field].value && variant.available; }); | ||
}; | ||
@@ -380,9 +411,10 @@ // loop through selectedSelections and only include available products that match current selections | ||
var VariantSelection = /** @class */ (function () { | ||
function VariantSelection(variants, selectorConfig) { | ||
function VariantSelection(variants, selectorField, variantConfig) { | ||
var _this = this; | ||
this.selected = ''; //ex: blue | ||
this.previouslySelected = ''; | ||
this.selected = undefined; | ||
this.previouslySelected = undefined; | ||
this.values = []; | ||
this.field = selectorConfig.field; | ||
this.label = selectorConfig.label; | ||
this.field = selectorField; | ||
this.label = (variantConfig === null || variantConfig === void 0 ? void 0 : variantConfig.label) || selectorField; | ||
this.config = variantConfig || {}; | ||
// needed to prevent attaching variants as class property | ||
@@ -403,3 +435,3 @@ this.variantsUpdate = function () { return variants.refineSelections(_this); }; | ||
var _loop_2 = function (selectedSelection) { | ||
availableVariants = availableVariants.filter(function (variant) { return selectedSelection.selected == variant.options[selectedSelection.field].value && variant.available; }); | ||
availableVariants = availableVariants.filter(function (variant) { var _a; return ((_a = selectedSelection.selected) === null || _a === void 0 ? void 0 : _a.value) == variant.options[selectedSelection.field].value && variant.available; }); | ||
}; | ||
@@ -416,6 +448,26 @@ // loop through selectedSelections and remove products that do not match | ||
if (!values.some(function (val) { return variant.options[_this.field].value == val.value; })) { | ||
values.push(__assign({ label: variant.options[_this.field].value, | ||
// TODO: use configurable mappings from config | ||
// TODO: set background for swatches (via configurable mappings) from config | ||
thumbnailImageUrl: (_a = variant.mappings.core) === null || _a === void 0 ? void 0 : _a.thumbnailImageUrl, available: Boolean(availableVariants.some(function (availableVariant) { return availableVariant.options[_this.field].value == variant.options[_this.field].value; })) }, variant.options[_this.field])); | ||
var value = variant.options[_this.field].value; | ||
var thumbnailImageUrl = (_a = variant.mappings.core) === null || _a === void 0 ? void 0 : _a.thumbnailImageUrl; | ||
var mappedValue = { | ||
value: value, | ||
label: value, | ||
thumbnailImageUrl: thumbnailImageUrl, | ||
available: Boolean(availableVariants.some(function (availableVariant) { return availableVariant.options[_this.field].value == variant.options[_this.field].value; })), | ||
}; | ||
if (_this.config.thumbnailBackgroundImages) { | ||
mappedValue.backgroundImageUrl = thumbnailImageUrl; | ||
} | ||
if (_this.config.mappings && _this.config.mappings && _this.config.mappings[value.toString().toLowerCase()]) { | ||
var mapping = _this.config.mappings[value.toString().toLowerCase()]; | ||
if (mapping.label) { | ||
mappedValue.label = mapping.label; | ||
} | ||
if (mapping.background) { | ||
mappedValue.background = mapping.background; | ||
} | ||
if (mapping.backgroundImageUrl) { | ||
mappedValue.backgroundImageUrl = mapping.backgroundImageUrl; | ||
} | ||
} | ||
values.push(mappedValue); | ||
} | ||
@@ -428,8 +480,8 @@ // TODO: use sorting function from config | ||
// check if the selection is stil available | ||
if (!newValues.some(function (val) { return val.value == _this.selected && val.available; })) { | ||
if (!newValues.some(function (val) { var _a; return val.value == ((_a = _this.selected) === null || _a === void 0 ? void 0 : _a.value) && val.available; })) { | ||
// the selection is no longer available, attempt to select previous selection | ||
if (this.selected !== this.previouslySelected && | ||
this.previouslySelected && | ||
newValues.some(function (val) { return val.value == _this.previouslySelected && val.available; })) { | ||
this.select(this.previouslySelected, true); | ||
newValues.some(function (val) { var _a; return val.value == ((_a = _this.previouslySelected) === null || _a === void 0 ? void 0 : _a.value) && val.available; })) { | ||
this.select(this.previouslySelected.value, true); | ||
} | ||
@@ -441,3 +493,3 @@ else { | ||
var nextAvailableValue = availableValues[0].value; | ||
if (this.selected !== nextAvailableValue) { | ||
if (this.selected.value !== nextAvailableValue) { | ||
this.select(nextAvailableValue, true); | ||
@@ -452,3 +504,3 @@ } | ||
VariantSelection.prototype.reset = function () { | ||
this.selected = ''; | ||
this.selected = undefined; | ||
this.values.forEach(function (val) { return (val.available = false); }); | ||
@@ -463,3 +515,3 @@ }; | ||
} | ||
this.selected = value; | ||
this.selected = valueExist; | ||
this.variantsUpdate(); | ||
@@ -530,1 +582,30 @@ } | ||
} | ||
function variantOptionClick(elem, variantConfig, results) { | ||
var options = {}; | ||
var attr = elem.getAttribute(VARIANT_ATTRIBUTE); | ||
if (attr) { | ||
var _a = attr.split(':'), option = _a[0], value = _a[1]; | ||
options[option.toLowerCase()] = [value.toLowerCase()]; | ||
makeVariantSelections(variantConfig, options, results); | ||
} | ||
} | ||
function makeVariantSelections(variantConfig, options, results) { | ||
var _a, _b; | ||
var filteredResults = results; | ||
// filter based on config | ||
(_b = (_a = variantConfig.realtime) === null || _a === void 0 ? void 0 : _a.filters) === null || _b === void 0 ? void 0 : _b.forEach(function (filter) { | ||
if (filter == 'first') { | ||
filteredResults = [filteredResults[0]]; | ||
} | ||
if (filter == 'unaltered') { | ||
filteredResults = filteredResults.filter(function (result) { var _a; return !((_a = result.variants) === null || _a === void 0 ? void 0 : _a.selections.some(function (selection) { return selection.previouslySelected; })); }); | ||
} | ||
}); | ||
filteredResults.forEach(function (result) { | ||
var _a; | ||
// no banner types | ||
if (result.type == 'product') { | ||
(_a = result.variants) === null || _a === void 0 ? void 0 : _a.makeSelections(options); | ||
} | ||
}); | ||
} |
@@ -7,14 +7,26 @@ import type { UrlManager } from '@searchspring/snap-url-manager'; | ||
}; | ||
export type VariantConfigFilterTypes = 'first' | 'unaltered'; | ||
export type VariantConfig = { | ||
field: string; | ||
realtime?: { | ||
enabled: boolean; | ||
filters?: VariantConfigFilterTypes[]; | ||
}; | ||
options?: { | ||
[field: string]: { | ||
preSelected?: string[]; | ||
}; | ||
[optionField: string]: VariantOptionConfig; | ||
}; | ||
}; | ||
export type VariantSelectionOptions = { | ||
field: string; | ||
label: string; | ||
export type VariantOptionConfig = { | ||
label?: string; | ||
preSelected?: string[]; | ||
thumbnailBackgroundImages?: boolean; | ||
mappings?: VariantOptionConfigMappings; | ||
}; | ||
export type VariantOptionConfigMappings = { | ||
[optionValue: string]: { | ||
label?: string; | ||
background?: string; | ||
backgroundImageUrl?: string; | ||
}; | ||
}; | ||
export type SearchStoreConfig = StoreConfig & { | ||
@@ -110,3 +122,5 @@ globals?: Partial<SearchRequestModel>; | ||
order?: number; | ||
variants?: VariantConfig; | ||
settings?: { | ||
variants?: VariantConfig; | ||
}; | ||
}; | ||
@@ -113,0 +127,0 @@ export type StoreConfigs = SearchStoreConfig | AutocompleteStoreConfig | FinderStoreConfig | RecommendationStoreConfig; |
@@ -79,3 +79,2 @@ import { makeObservable, observable } from 'mobx'; | ||
this.error = undefined; | ||
this.loaded = !!data.pagination; | ||
this.meta = new MetaStore(data.meta); | ||
@@ -101,3 +100,3 @@ // set the query to match the actual queried term and not the input query | ||
this.filters = new SearchFilterStore(this.services, data.filters, this.meta.data); | ||
this.results = new SearchResultStore(this.config, this.services, this.meta.data, data.results || [], data.pagination, data.merchandising); | ||
this.results = new SearchResultStore(this.config, this.services, this.meta.data, data.results || [], data.pagination, data.merchandising, this.loaded); | ||
if ((this.results.length === 0 && !this.trending.filter((term) => term.active).length) || this.terms?.filter((term) => term.active).length) { | ||
@@ -110,3 +109,4 @@ // if a trending term was selected and then a subsequent search yields no results, reset trending terms to remove active state | ||
this.sorting = new SearchSortingStore(this.services, data.sorting || [], data.search || {}, this.meta.data); | ||
this.loaded = !!data.pagination; | ||
} | ||
} |
@@ -27,3 +27,3 @@ import { observable, computed, makeObservable } from 'mobx'; | ||
this.items.forEach((item) => { | ||
price += (item.display.mappings.core?.price || 0) * item.quantity; | ||
price += +(item.display.mappings.core?.price || 0) * item.quantity; | ||
}); | ||
@@ -35,3 +35,3 @@ return price; | ||
this.items.forEach((item) => { | ||
price += (item.display.mappings.core?.msrp || item.display.mappings.core?.price || 0) * item.quantity; | ||
price += (+(item.display.mappings.core?.msrp || 0) || +(item.display.mappings.core?.price || 0) || 0) * item.quantity; | ||
}); | ||
@@ -38,0 +38,0 @@ return price; |
@@ -26,6 +26,5 @@ import { makeObservable, observable } from 'mobx'; | ||
this.error = undefined; | ||
this.loaded = !!data?.profile; | ||
this.meta = new MetaStore(data?.meta); | ||
this.profile = new RecommendationProfileStore(this.services, data); | ||
this.results = new SearchResultStore(this.config, this.services, this.meta.data, data?.results); | ||
this.results = new SearchResultStore(this.config, this.services, this.meta.data, data?.results, undefined, undefined, this.loaded); | ||
// only create a cart store when type is bundle | ||
@@ -35,3 +34,4 @@ if (this.profile.type == 'bundle') { | ||
} | ||
this.loaded = !!data?.profile; | ||
} | ||
} |
@@ -51,3 +51,2 @@ import { makeObservable, observable } from 'mobx'; | ||
this.error = undefined; | ||
this.loaded = !!data.pagination; | ||
this.meta = new MetaStore(data.meta); | ||
@@ -58,6 +57,7 @@ this.merchandising = new SearchMerchandisingStore(this.services, data?.merchandising || {}); | ||
this.filters = new SearchFilterStore(this.services, data.filters, this.meta.data); | ||
this.results = new SearchResultStore(this.config, this.services, this.meta.data, data?.results || [], data.pagination, data.merchandising); | ||
this.results = new SearchResultStore(this.config, this.services, this.meta.data, data?.results || [], data.pagination, data.merchandising, this.loaded); | ||
this.pagination = new SearchPaginationStore(this.config, this.services, data.pagination, this.meta.data); | ||
this.sorting = new SearchSortingStore(this.services, data?.sorting || [], data?.search || {}, this.meta.data); | ||
this.loaded = !!data.pagination; | ||
} | ||
} |
@@ -5,3 +5,3 @@ export { SearchMerchandisingStore, BannerContent, ContentType } from './SearchMerchandisingStore'; | ||
export { SearchPaginationStore } from './SearchPaginationStore'; | ||
export { SearchResultStore, Product, Banner } from './SearchResultStore'; | ||
export { SearchResultStore, Product, Banner, VariantSelection, VariantSelectionValue } from './SearchResultStore'; | ||
export { SearchSortingStore } from './SearchSortingStore'; | ||
@@ -8,0 +8,0 @@ export { SearchQueryStore } from './SearchQueryStore'; |
@@ -5,5 +5,5 @@ export { SearchMerchandisingStore, ContentType } from './SearchMerchandisingStore'; | ||
export { SearchPaginationStore } from './SearchPaginationStore'; | ||
export { SearchResultStore, Product, Banner } from './SearchResultStore'; | ||
export { SearchResultStore, Product, Banner, VariantSelection } from './SearchResultStore'; | ||
export { SearchSortingStore } from './SearchSortingStore'; | ||
export { SearchQueryStore } from './SearchQueryStore'; | ||
export { SearchHistoryStore } from './SearchHistoryStore'; |
@@ -1,6 +0,6 @@ | ||
import type { StoreServices, StoreConfigs, VariantSelectionOptions, ResultBadge, VariantConfig } from '../../types'; | ||
import type { StoreServices, StoreConfigs, ResultBadge, VariantOptionConfig, VariantConfig } from '../../types'; | ||
import type { SearchResponseModelResult, SearchResponseModelPagination, SearchResponseModelMerchandising, SearchResponseModelResultMappings, SearchResponseModelMerchandisingContentInline, SearchResponseModelMerchandisingContentConfig, MetaResponseModel } from '@searchspring/snapi-types'; | ||
export declare class SearchResultStore extends Array<Product | Banner> { | ||
static get [Symbol.species](): ArrayConstructor; | ||
constructor(config: StoreConfigs, services: StoreServices, metaData: MetaResponseModel, resultData?: SearchResponseModelResult[], paginationData?: SearchResponseModelPagination, merchData?: SearchResponseModelMerchandising); | ||
constructor(config: StoreConfigs, services: StoreServices, metaData: MetaResponseModel, resultData?: SearchResponseModelResult[], paginationData?: SearchResponseModelPagination, merchData?: SearchResponseModelMerchandising, loaded?: boolean); | ||
} | ||
@@ -70,6 +70,7 @@ export declare class Banner { | ||
} | ||
type SelectionValue = { | ||
export type VariantSelectionValue = { | ||
value: string; | ||
label?: string; | ||
thumbnailImageUrl?: string; | ||
backgroundImageUrl?: string; | ||
background?: string; | ||
@@ -81,7 +82,8 @@ available?: boolean; | ||
label: string; | ||
selected?: string; | ||
previouslySelected?: string; | ||
values: SelectionValue[]; | ||
selected?: VariantSelectionValue; | ||
previouslySelected?: VariantSelectionValue; | ||
values: VariantSelectionValue[]; | ||
private config; | ||
private variantsUpdate; | ||
constructor(variants: Variants, selectorConfig: VariantSelectionOptions); | ||
constructor(variants: Variants, selectorField: string, variantConfig?: VariantOptionConfig); | ||
refineValues(variants: Variants): void; | ||
@@ -88,0 +90,0 @@ reset(): void; |
import { computed, makeObservable, observable } from 'mobx'; | ||
import deepmerge from 'deepmerge'; | ||
import { isPlainObject } from 'is-plain-object'; | ||
const VARIANT_ATTRIBUTE = 'ss-variant-option'; | ||
const VARIANT_ATTRIBUTE_SELECTED = 'ss-variant-option-selected'; | ||
export class SearchResultStore extends Array { | ||
@@ -8,6 +10,35 @@ static get [Symbol.species]() { | ||
} | ||
constructor(config, services, metaData, resultData, paginationData, merchData) { | ||
constructor(config, services, metaData, resultData, paginationData, merchData, loaded) { | ||
let results = (resultData || []).map((result) => { | ||
return new Product(services, result, metaData, config); | ||
}); | ||
const variantConfig = config?.settings?.variants; | ||
// preselected variant options | ||
if (variantConfig?.realtime?.enabled) { | ||
// attach click events - ONLY happens once (known limitation) | ||
if (!loaded && results.length) { | ||
document.querySelectorAll(`[${VARIANT_ATTRIBUTE}]`).forEach((elem) => { | ||
if (variantConfig?.field && !variantConfig?.realtime?.enabled === false) { | ||
elem.addEventListener('click', () => variantOptionClick(elem, variantConfig, results)); | ||
} | ||
}); | ||
} | ||
// check for attributes for preselection | ||
if (results.length) { | ||
if (variantConfig?.field && !variantConfig?.realtime?.enabled === false) { | ||
const options = {}; | ||
// grab values from elements on the page to form preselected elements | ||
document.querySelectorAll(`[${VARIANT_ATTRIBUTE_SELECTED}]`).forEach((elem) => { | ||
const attr = elem.getAttribute(VARIANT_ATTRIBUTE); | ||
if (attr) { | ||
const [option, value] = attr.split(':'); | ||
if (option && value) { | ||
options[option.toLowerCase()] = [value.toLowerCase()]; | ||
} | ||
} | ||
}); | ||
makeVariantSelections(variantConfig, options, results); | ||
} | ||
} | ||
} | ||
if (merchData?.content?.inline) { | ||
@@ -66,3 +97,3 @@ const banners = merchData.content.inline | ||
const parsedVariants = JSON.parse(this.attributes[variantsField]); | ||
this.variants = new Variants(parsedVariants, this.mask, config.settings?.variants); | ||
this.variants = new Variants(parsedVariants, this.mask, config?.settings?.variants); | ||
} | ||
@@ -186,3 +217,5 @@ catch (err) { | ||
}; | ||
this.config = config; | ||
if (config) { | ||
this.config = config; | ||
} | ||
this.update(variantData, config); | ||
@@ -205,8 +238,4 @@ } | ||
options.map((option) => { | ||
// TODO - merge with variant config before constructing selection (for label overrides and swatch mappings) | ||
const optionConfig = { | ||
field: option, | ||
label: option, | ||
}; | ||
this.selections.push(new VariantSelection(this, optionConfig)); | ||
const variantOptionConfig = this.config?.options && this.config.options[option]; | ||
this.selections.push(new VariantSelection(this, option, variantOptionConfig)); | ||
}); | ||
@@ -231,3 +260,3 @@ const preselectedOptions = {}; | ||
// options = {color: 'Blue', size: 'L'}; | ||
if (!options) { | ||
if (!options || !Object.keys(options).length) { | ||
// select first available for each selection | ||
@@ -247,3 +276,3 @@ this.selections.forEach((selection) => { | ||
const preferedOptions = options[selection.field]; | ||
let preferencedOption = availableOptions[0]; | ||
let preferencedOption = selection.selected || availableOptions[0]; | ||
// if theres a preference for that field | ||
@@ -253,3 +282,3 @@ if (preferedOptions) { | ||
//see if that option is in the available options | ||
const availablePreferedOptions = availableOptions.find((value) => value.value.toLowerCase() == preference.toLowerCase()); | ||
const availablePreferedOptions = availableOptions.find((value) => value.value.toString().toLowerCase() == preference?.toString().toLowerCase()); | ||
//use it | ||
@@ -271,3 +300,3 @@ if (availablePreferedOptions) { | ||
if (preferencedOption) { | ||
selection.select(preferencedOption.value); | ||
selection.select(preferencedOption.value, true); | ||
} | ||
@@ -289,3 +318,3 @@ }); | ||
// check to see if we have enough selections made to update the display | ||
const selectedSelections = this.selections.filter((selection) => selection.selected); | ||
const selectedSelections = this.selections.filter((selection) => selection.selected?.value?.length); | ||
if (selectedSelections.length) { | ||
@@ -295,3 +324,3 @@ let availableVariants = this.data; | ||
for (const selectedSelection of selectedSelections) { | ||
availableVariants = availableVariants.filter((variant) => selectedSelection.selected == variant.options[selectedSelection.field].value && variant.available); | ||
availableVariants = availableVariants.filter((variant) => selectedSelection.selected?.value == variant.options[selectedSelection.field].value && variant.available); | ||
} | ||
@@ -306,8 +335,9 @@ // set active variant | ||
export class VariantSelection { | ||
constructor(variants, selectorConfig) { | ||
this.selected = ''; //ex: blue | ||
this.previouslySelected = ''; | ||
constructor(variants, selectorField, variantConfig) { | ||
this.selected = undefined; | ||
this.previouslySelected = undefined; | ||
this.values = []; | ||
this.field = selectorConfig.field; | ||
this.label = selectorConfig.label; | ||
this.field = selectorField; | ||
this.label = variantConfig?.label || selectorField; | ||
this.config = variantConfig || {}; | ||
// needed to prevent attaching variants as class property | ||
@@ -328,3 +358,3 @@ this.variantsUpdate = () => variants.refineSelections(this); | ||
for (const selectedSelection of selectedSelections) { | ||
availableVariants = availableVariants.filter((variant) => selectedSelection.selected == variant.options[selectedSelection.field].value && variant.available); | ||
availableVariants = availableVariants.filter((variant) => selectedSelection.selected?.value == variant.options[selectedSelection.field].value && variant.available); | ||
} | ||
@@ -335,10 +365,26 @@ const newValues = variants.data | ||
if (!values.some((val) => variant.options[this.field].value == val.value)) { | ||
values.push({ | ||
label: variant.options[this.field].value, | ||
// TODO: use configurable mappings from config | ||
// TODO: set background for swatches (via configurable mappings) from config | ||
thumbnailImageUrl: variant.mappings.core?.thumbnailImageUrl, | ||
const value = variant.options[this.field].value; | ||
const thumbnailImageUrl = variant.mappings.core?.thumbnailImageUrl; | ||
const mappedValue = { | ||
value: value, | ||
label: value, | ||
thumbnailImageUrl: thumbnailImageUrl, | ||
available: Boolean(availableVariants.some((availableVariant) => availableVariant.options[this.field].value == variant.options[this.field].value)), | ||
...variant.options[this.field], | ||
}); | ||
}; | ||
if (this.config.thumbnailBackgroundImages) { | ||
mappedValue.backgroundImageUrl = thumbnailImageUrl; | ||
} | ||
if (this.config.mappings && this.config.mappings && this.config.mappings[value.toString().toLowerCase()]) { | ||
const mapping = this.config.mappings[value.toString().toLowerCase()]; | ||
if (mapping.label) { | ||
mappedValue.label = mapping.label; | ||
} | ||
if (mapping.background) { | ||
mappedValue.background = mapping.background; | ||
} | ||
if (mapping.backgroundImageUrl) { | ||
mappedValue.backgroundImageUrl = mapping.backgroundImageUrl; | ||
} | ||
} | ||
values.push(mappedValue); | ||
} | ||
@@ -351,8 +397,8 @@ // TODO: use sorting function from config | ||
// check if the selection is stil available | ||
if (!newValues.some((val) => val.value == this.selected && val.available)) { | ||
if (!newValues.some((val) => val.value == this.selected?.value && val.available)) { | ||
// the selection is no longer available, attempt to select previous selection | ||
if (this.selected !== this.previouslySelected && | ||
this.previouslySelected && | ||
newValues.some((val) => val.value == this.previouslySelected && val.available)) { | ||
this.select(this.previouslySelected, true); | ||
newValues.some((val) => val.value == this.previouslySelected?.value && val.available)) { | ||
this.select(this.previouslySelected.value, true); | ||
} | ||
@@ -364,3 +410,3 @@ else { | ||
const nextAvailableValue = availableValues[0].value; | ||
if (this.selected !== nextAvailableValue) { | ||
if (this.selected.value !== nextAvailableValue) { | ||
this.select(nextAvailableValue, true); | ||
@@ -375,3 +421,3 @@ } | ||
reset() { | ||
this.selected = ''; | ||
this.selected = undefined; | ||
this.values.forEach((val) => (val.available = false)); | ||
@@ -385,3 +431,3 @@ } | ||
} | ||
this.selected = value; | ||
this.selected = valueExist; | ||
this.variantsUpdate(); | ||
@@ -446,1 +492,28 @@ } | ||
} | ||
function variantOptionClick(elem, variantConfig, results) { | ||
const options = {}; | ||
const attr = elem.getAttribute(VARIANT_ATTRIBUTE); | ||
if (attr) { | ||
const [option, value] = attr.split(':'); | ||
options[option.toLowerCase()] = [value.toLowerCase()]; | ||
makeVariantSelections(variantConfig, options, results); | ||
} | ||
} | ||
function makeVariantSelections(variantConfig, options, results) { | ||
let filteredResults = results; | ||
// filter based on config | ||
variantConfig.realtime?.filters?.forEach((filter) => { | ||
if (filter == 'first') { | ||
filteredResults = [filteredResults[0]]; | ||
} | ||
if (filter == 'unaltered') { | ||
filteredResults = filteredResults.filter((result) => !result.variants?.selections.some((selection) => selection.previouslySelected)); | ||
} | ||
}); | ||
filteredResults.forEach((result) => { | ||
// no banner types | ||
if (result.type == 'product') { | ||
result.variants?.makeSelections(options); | ||
} | ||
}); | ||
} |
@@ -7,14 +7,26 @@ import type { UrlManager } from '@searchspring/snap-url-manager'; | ||
}; | ||
export type VariantConfigFilterTypes = 'first' | 'unaltered'; | ||
export type VariantConfig = { | ||
field: string; | ||
realtime?: { | ||
enabled: boolean; | ||
filters?: VariantConfigFilterTypes[]; | ||
}; | ||
options?: { | ||
[field: string]: { | ||
preSelected?: string[]; | ||
}; | ||
[optionField: string]: VariantOptionConfig; | ||
}; | ||
}; | ||
export type VariantSelectionOptions = { | ||
field: string; | ||
label: string; | ||
export type VariantOptionConfig = { | ||
label?: string; | ||
preSelected?: string[]; | ||
thumbnailBackgroundImages?: boolean; | ||
mappings?: VariantOptionConfigMappings; | ||
}; | ||
export type VariantOptionConfigMappings = { | ||
[optionValue: string]: { | ||
label?: string; | ||
background?: string; | ||
backgroundImageUrl?: string; | ||
}; | ||
}; | ||
export type SearchStoreConfig = StoreConfig & { | ||
@@ -110,3 +122,5 @@ globals?: Partial<SearchRequestModel>; | ||
order?: number; | ||
variants?: VariantConfig; | ||
settings?: { | ||
variants?: VariantConfig; | ||
}; | ||
}; | ||
@@ -113,0 +127,0 @@ export type StoreConfigs = SearchStoreConfig | AutocompleteStoreConfig | FinderStoreConfig | RecommendationStoreConfig; |
{ | ||
"name": "@searchspring/snap-store-mobx", | ||
"version": "0.55.0", | ||
"version": "0.56.0", | ||
"description": "Snap MobX Store", | ||
@@ -23,8 +23,8 @@ "main": "dist/cjs/index.js", | ||
"dependencies": { | ||
"@searchspring/snap-toolbox": "^0.55.0", | ||
"@searchspring/snap-toolbox": "^0.56.0", | ||
"mobx": "6.9.0" | ||
}, | ||
"devDependencies": { | ||
"@searchspring/snap-client": "^0.55.0", | ||
"@searchspring/snap-url-manager": "^0.55.0" | ||
"@searchspring/snap-client": "^0.56.0", | ||
"@searchspring/snap-url-manager": "^0.56.0" | ||
}, | ||
@@ -35,3 +35,3 @@ "sideEffects": false, | ||
], | ||
"gitHead": "ad413e17c9756a28e1972173b3b25f0a4dcb127c" | ||
"gitHead": "3326303c739fd3bbb23e6477b275b89f7df3bb74" | ||
} |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
368897
6998
+ Added@searchspring/snap-toolbox@0.56.6(transitive)
- Removed@searchspring/snap-toolbox@0.55.0(transitive)