@salla.sa/twilight-components 2.11.53 → 2.11.55

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (44) hide show
  1. package/dist/cjs/salla-add-product-button.cjs.entry.js +10 -9
  2. package/dist/cjs/salla-button_36.cjs.entry.js +82 -29
  3. package/dist/cjs/salla-product-options.cjs.entry.js +26 -17
  4. package/dist/collection/components/salla-add-product-button/salla-add-product-button.js +14 -9
  5. package/dist/collection/components/salla-color-picker/salla-color-picker.js +22 -0
  6. package/dist/collection/components/salla-datetime-picker/salla-datetime-picker.js +23 -1
  7. package/dist/collection/components/salla-file-upload/salla-file-upload.js +31 -1
  8. package/dist/collection/components/salla-map/salla-map.js +22 -0
  9. package/dist/collection/components/salla-product-availability/salla-product-availability.js +42 -28
  10. package/dist/collection/components/salla-product-options/salla-product-options.js +27 -18
  11. package/dist/components/salla-add-product-button.js +10 -9
  12. package/dist/components/salla-color-picker2.js +8 -0
  13. package/dist/components/salla-datetime-picker2.js +8 -0
  14. package/dist/components/salla-file-upload2.js +16 -1
  15. package/dist/components/salla-map2.js +8 -0
  16. package/dist/components/salla-product-availability2.js +42 -28
  17. package/dist/components/salla-product-options.js +26 -17
  18. package/dist/esm/salla-add-product-button.entry.js +10 -9
  19. package/dist/esm/salla-button_36.entry.js +82 -29
  20. package/dist/esm/salla-product-options.entry.js +26 -17
  21. package/dist/esm-es5/salla-add-product-button.entry.js +2 -2
  22. package/dist/esm-es5/salla-button_36.entry.js +5 -5
  23. package/dist/esm-es5/salla-product-options.entry.js +2 -2
  24. package/dist/twilight/p-368bbdf1.entry.js +4 -0
  25. package/dist/twilight/p-6dc9f167.entry.js +36 -0
  26. package/dist/twilight/p-7075226e.system.entry.js +4 -0
  27. package/dist/twilight/p-84d8b36b.system.js +1 -1
  28. package/dist/twilight/p-9fbf1baf.system.entry.js +53 -0
  29. package/dist/twilight/p-ca92ba53.system.entry.js +4 -0
  30. package/dist/twilight/p-edc8a654.entry.js +4 -0
  31. package/dist/twilight/twilight.esm.js +1 -1
  32. package/dist/types/components/salla-color-picker/salla-color-picker.d.ts +4 -0
  33. package/dist/types/components/salla-datetime-picker/salla-datetime-picker.d.ts +5 -1
  34. package/dist/types/components/salla-file-upload/salla-file-upload.d.ts +5 -0
  35. package/dist/types/components/salla-map/salla-map.d.ts +4 -0
  36. package/dist/types/components/salla-product-options/salla-product-options.d.ts +2 -1
  37. package/dist/types/components.d.ts +18 -2
  38. package/package.json +3 -3
  39. package/dist/twilight/p-126f5fed.system.entry.js +0 -4
  40. package/dist/twilight/p-96e39318.system.entry.js +0 -53
  41. package/dist/twilight/p-ae6ad245.entry.js +0 -36
  42. package/dist/twilight/p-cdb6aa9e.entry.js +0 -4
  43. package/dist/twilight/p-d01e39b0.entry.js +0 -4
  44. package/dist/twilight/p-d9579b04.system.entry.js +0 -4
@@ -120,7 +120,7 @@ const SallaProductOptions$1 = /*@__PURE__*/ proxyCustomElement(class extends HTM
120
120
  return selectedOptions;
121
121
  }
122
122
  /**
123
- * Get the id's of the selected options.
123
+ * Report options form validity.
124
124
  * */
125
125
  async reportValidity() {
126
126
  let requiredElements = this.host.querySelectorAll('[required]');
@@ -151,6 +151,12 @@ const SallaProductOptions$1 = /*@__PURE__*/ proxyCustomElement(class extends HTM
151
151
  async getOption(option_id) {
152
152
  return this.optionsData.find(option => option.id === option_id);
153
153
  }
154
+ // @ts-ignore
155
+ invalidHandler(event, option) {
156
+ const closestProductOption = event.target.closest('.s-product-options-option');
157
+ closestProductOption.scrollIntoView({ behavior: 'smooth', block: 'center' });
158
+ closestProductOption.classList.add('s-product-options-option-error');
159
+ }
154
160
  changedHandler(event, option) {
155
161
  let data = { event: event, option: option, detail: null };
156
162
  if (option.details) {
@@ -159,6 +165,10 @@ const SallaProductOptions$1 = /*@__PURE__*/ proxyCustomElement(class extends HTM
159
165
  });
160
166
  data.detail = detail;
161
167
  }
168
+ let optionElement = event.target.closest('.s-product-options-option');
169
+ if (event.target.value || event.type == 'added') {
170
+ optionElement.classList.remove('s-product-options-option-error');
171
+ }
162
172
  const index = this.selectedOptions.findIndex(option => option.option_id === data.option.id);
163
173
  index > -1 ? this.selectedOptions[index] = Object.assign(Object.assign({}, data.detail), { option_id: data.option.id }) : this.selectedOptions.push(Object.assign(Object.assign({}, data.detail), { option_id: data.option.id }));
164
174
  this.setSelectedSkus();
@@ -219,16 +229,16 @@ const SallaProductOptions$1 = /*@__PURE__*/ proxyCustomElement(class extends HTM
219
229
  }
220
230
  return (h(Host, { class: "s-product-options-wrapper" }, h("salla-conditional-fields", null, this.optionsData.map((option) => h("div", Object.assign({ class: `s-product-options-option-container${option.visibility_condition ? ' hidden' : ''}`, "data-option-id": option.id }, this.getOptionShownWhen(option)), option.name == 'splitter' ?
221
231
  this.splitterOption()
222
- : h("div", { class: "s-product-options-option" }, h("label", { htmlFor: 'options[' + option.id + ']', class: "s-product-options-option-label" }, h("b", null, option.name, option.required && h("span", null, " * "), " "), h("small", null, option.placeholder)), h("div", { class: "s-product-options-option-content" }, this.getDisplayForType(option))))))));
232
+ : h("div", { class: "s-product-options-option", "data-option-type": option.type, "data-option-required": `${option.required}` }, h("label", { htmlFor: 'options[' + option.id + ']', class: "s-product-options-option-label" }, h("b", null, option.name, option.required && h("span", null, " * "), " "), h("small", null, option.placeholder)), h("div", { class: "s-product-options-option-content" }, this.getDisplayForType(option))))))));
223
233
  }
224
234
  //@ts-ignore
225
235
  donationOption(option, product) {
226
236
  return h("div", { class: "s-product-options-donation-wrapper" }, option.donation ?
227
237
  h("div", { class: "s-product-options-donation-progress" }, h("salla-progress-bar", { donation: option.donation }))
228
- : '', h("div", { class: "s-product-options-donation-input-group" }, h("input", { type: "text", id: "donating-amount", name: "donating_amount", class: "s-form-control", value: option.value, required: true, placeholder: option.placeholder, onInput: e => salla.helpers.inputDigitsOnly(e.target), onBlur: e => this.changedHandler(e, option) }), h("span", { class: "s-product-options-donation-amount-currency" }, salla.config.currency(salla.config.get('user.currency_code')).symbol)));
238
+ : '', h("div", { class: "s-product-options-donation-input-group" }, h("input", { type: "text", id: "donating-amount", name: "donating_amount", class: "s-form-control", value: option.value, required: true, placeholder: option.placeholder, onInput: e => salla.helpers.inputDigitsOnly(e.target), onBlur: e => this.changedHandler(e, option), onInvalid: (e) => this.invalidHandler(e, option) }), h("span", { class: "s-product-options-donation-amount-currency" }, salla.config.currency(salla.config.get('user.currency_code')).symbol)));
229
239
  }
230
240
  fileUploader(option, additions = null) {
231
- return h("salla-file-upload", Object.assign({}, (additions || {}), { "payload-name": "file", value: option.value, "instant-upload": true, name: `options[${option.id}]`, required: option.required, height: "120px", url: salla.cart.api.getUploadImageEndpoint(), "form-data": { cart_item_id: this.productId, product_id: this.productId }, class: { "s-product-options-image-input": true, required: option.required } }), h("div", { class: "s-product-options-filepond-placeholder" }, h("span", { class: "s-product-options-filepond-placeholder-icon", innerHTML: additions.accept && additions.accept.split(',').every(type => type.includes('image'))
241
+ return h("salla-file-upload", Object.assign({}, (additions || {}), { "payload-name": "file", value: option.value, "instant-upload": true, name: `options[${option.id}]`, required: option.required, height: "120px", onAdded: (e) => this.changedHandler(e, option), url: salla.cart.api.getUploadImageEndpoint(), "form-data": { cart_item_id: this.productId, product_id: this.productId }, onInvalidInput: (e) => this.invalidHandler(e, option), class: { "s-product-options-image-input": true, required: option.required } }), h("div", { class: "s-product-options-filepond-placeholder" }, h("span", { class: "s-product-options-filepond-placeholder-icon", innerHTML: additions.accept && additions.accept.split(',').every(type => type.includes('image'))
232
242
  ? CameraIcon
233
243
  : FileIcon }), h("p", { class: "s-product-options-filepond-placeholder-text" }, salla.lang.get('common.uploader.drag_and_drop')), h("span", { class: "filepond--label-action" }, salla.lang.get('common.uploader.browse'))));
234
244
  }
@@ -245,7 +255,7 @@ const SallaProductOptions$1 = /*@__PURE__*/ proxyCustomElement(class extends HTM
245
255
  }
246
256
  //@ts-ignore
247
257
  numberOption(option) {
248
- return h("input", { type: "text", value: option.value, class: "s-form-control", required: option.required, name: `options[${option.id}]`, placeholder: option.placeholder, onBlur: e => this.changedHandler(e, option), onInput: e => salla.helpers.inputDigitsOnly(e.target) });
258
+ return h("input", { type: "text", value: option.value, class: "s-form-control", required: option.required, name: `options[${option.id}]`, placeholder: option.placeholder, onBlur: e => this.changedHandler(e, option), onInvalid: (e) => this.invalidHandler(e, option), onInput: e => salla.helpers.inputDigitsOnly(e.target) });
249
259
  }
250
260
  //@ts-ignore
251
261
  splitterOption() {
@@ -253,41 +263,40 @@ const SallaProductOptions$1 = /*@__PURE__*/ proxyCustomElement(class extends HTM
253
263
  }
254
264
  //@ts-ignore
255
265
  textOption(option) {
256
- return h("div", { class: "s-product-options-text" }, h("input", { type: "text", value: option.value, class: 's-form-control', required: option.required, name: `options[${option.id}]`, placeholder: option.placeholder, onInput: e => this.changedHandler(e, option) }));
266
+ return h("div", { class: "s-product-options-text" }, h("input", { type: "text", value: option.value, class: 's-form-control', required: option.required, name: `options[${option.id}]`, placeholder: option.placeholder, onInvalid: (e) => this.invalidHandler(e, option), onInput: e => this.changedHandler(e, option) }));
257
267
  }
258
268
  //@ts-ignore
259
269
  textareaOption(option) {
260
270
  //todo::remove mt-1 class, and if it's okay to remove the tag itself will be great
261
- return h("div", { class: "s-product-options-textarea" }, h("div", { class: "mt-1" }, h("textarea", { rows: 4, value: option.value, class: "s-form-control", required: option.required, id: `options[${option.id}]`, name: `options[${option.id}]`, placeholder: option.placeholder, onInput: (e) => this.changedHandler(e, option) })));
271
+ return h("div", { class: "s-product-options-textarea" }, h("div", { class: "mt-1" }, h("textarea", { rows: 4, value: option.value, class: "s-form-control", required: option.required, id: `options[${option.id}]`, name: `options[${option.id}]`, placeholder: option.placeholder, onInvalid: (e) => this.invalidHandler(e, option), onInput: (e) => this.changedHandler(e, option) })));
262
272
  }
263
273
  //@ts-ignore
264
274
  mapOption(option) {
265
- return h("salla-map", { zoom: 15, lat: this.getLatLng(option.value, 'lat'), lng: this.getLatLng(option.value, 'lng'), name: `options[${option.id}]`, searchable: true, required: option.required, onSelected: e => this.changedHandler(e, option) });
275
+ return h("salla-map", { zoom: 15, lat: this.getLatLng(option.value, 'lat'), lng: this.getLatLng(option.value, 'lng'), name: `options[${option.id}]`, searchable: true, required: option.required, onInvalidInput: (e) => this.invalidHandler(e, option), onSelected: e => this.changedHandler(e, option) });
266
276
  }
267
277
  colorPickerOption(option) {
268
- return h("salla-color-picker", { onSubmitted: e => this.changedHandler(e, option), name: `options[${option.id}]`, required: option.required, color: option.value || '#5dd5c4' });
278
+ return h("salla-color-picker", { onSubmitted: e => this.changedHandler(e, option), name: `options[${option.id}]`, required: option.required, onInvalidInput: (e) => this.invalidHandler(e, option), color: option.value || '#5dd5c4' });
269
279
  }
270
280
  /**
271
281
  * ============= Date Time options =============
272
282
  */
273
283
  //@ts-ignore
274
284
  timeOption(option) {
275
- return h("salla-datetime-picker", { noCalendar: true, enableTime: true, dateFormat: "h:i K", value: option.value, placeholder: option.name, required: option.required, name: `options[${option.id}]`, class: "s-product-options-time-element", onPicked: e => this.changedHandler(e, option) });
285
+ return h("salla-datetime-picker", { noCalendar: true, enableTime: true, dateFormat: "h:i K", value: option.value, placeholder: option.name, required: option.required, name: `options[${option.id}]`, class: "s-product-options-time-element", onInvalidInput: (e) => this.invalidHandler(e, option), onPicked: e => this.changedHandler(e, option) });
276
286
  }
277
287
  //@ts-ignore
278
288
  dateOption(option) {
279
289
  //todo:: consider date-range @see https://github.com/SallaApp/theme-raed/blob/master/src/assets/js/partials/product-options.js#L8-L23
280
- return h("div", { class: "s-product-options-date-element" }, h("salla-datetime-picker", { value: option.value, placeholder: option.name, required: option.required, minDate: new Date(), name: `options[${option.id}]`, onPicked: e => this.changedHandler(e, option) }));
290
+ return h("div", { class: "s-product-options-date-element" }, h("salla-datetime-picker", { value: option.value, placeholder: option.name, required: option.required, minDate: new Date(), name: `options[${option.id}]`, onInvalidInput: (e) => this.invalidHandler(e, option), onPicked: e => this.changedHandler(e, option) }));
281
291
  }
282
292
  //@ts-ignore
283
293
  datetimeOption(option) {
284
294
  //todo:: consider date-range @see https://github.com/SallaApp/theme-raed/blob/master/src/assets/js/partials/product-options.js#L8-L23
285
- return h("div", { class: "s-product-options-datetime-element" }, h("salla-datetime-picker", { enableTime: true, value: option.value, dateFormat: "Y-m-d G:i:K", placeholder: option.name, required: option.required, name: `options[${option.id}]`, maxDate: option.to_date_time, minDate: option.from_date_time, onPicked: e => this.changedHandler(e, option) }));
295
+ return h("div", { class: "s-product-options-datetime-element" }, h("salla-datetime-picker", { enableTime: true, value: option.value, dateFormat: "Y-m-d G:i:K", placeholder: option.name, required: option.required, name: `options[${option.id}]`, maxDate: option.to_date_time, minDate: option.from_date_time, onInvalidInput: (e) => this.invalidHandler(e, option), onPicked: e => this.changedHandler(e, option) }));
286
296
  }
287
297
  /**
288
298
  * ============= Advanced options =============
289
299
  */
290
- // <<<<<<< HEAD
291
300
  getOptionDetailName(detail, outOfStock = true, optionType) {
292
301
  if (optionType && optionType == DisplayType.COLOR) {
293
302
  return detail.name
@@ -317,24 +326,24 @@ const SallaProductOptions$1 = /*@__PURE__*/ proxyCustomElement(class extends HTM
317
326
  return this.selectedOptions.some(option => option.is_out && option.option_id !== detail.option_id);
318
327
  }
319
328
  singleOption(option) {
320
- return h("div", null, h("select", { name: `options[${option.id}]`, required: option.required, class: "s-form-control", onChange: e => this.changedHandler(e, option) }, h("option", { value: "" }, option.placeholder), option === null || option === void 0 ? void 0 :
329
+ return h("div", null, h("select", { name: `options[${option.id}]`, required: option.required, class: "s-form-control", onInvalid: (e) => this.invalidHandler(e, option), onChange: e => this.changedHandler(e, option) }, h("option", { value: "" }, option.placeholder), option === null || option === void 0 ? void 0 :
321
330
  option.details.map((detail) => {
322
331
  return h("option", { value: detail.id, disabled: this.canDisabled && this.isOptionDetailOut(detail), selected: detail.is_selected }, this.getOptionDetailName(detail));
323
332
  })));
324
333
  }
325
334
  multipleOptions(option) {
326
335
  return h("div", { class: { "s-product-options-multiple-options-wrapper": true, 'required': option.required } }, option === null || option === void 0 ? void 0 : option.details.map((detail) => {
327
- return h("div", null, h("input", { type: "checkbox", value: detail.id, disabled: this.isOptionDetailOut(detail), checked: detail.is_selected, required: option.required, name: `options[${option.id}][]`, id: `field-${option.id}-${detail.id}`, onChange: (e) => this.changedHandler(e, option), "aria-describedby": `options[${option.id}]-description` }), h("label", { htmlFor: `field-${option.id}-${detail.id}` }, this.getOptionDetailName(detail)));
336
+ return h("div", null, h("input", { type: "checkbox", value: detail.id, disabled: this.isOptionDetailOut(detail), checked: detail.is_selected, required: option.required, name: `options[${option.id}][]`, id: `field-${option.id}-${detail.id}`, onChange: (e) => this.changedHandler(e, option), onInvalid: (e) => this.invalidHandler(e, option), "aria-describedby": `options[${option.id}]-description` }), h("label", { htmlFor: `field-${option.id}-${detail.id}` }, this.getOptionDetailName(detail)));
328
337
  }));
329
338
  }
330
339
  //@ts-ignore
331
340
  colorOption(option) {
332
- return h("fieldset", { class: "s-product-options-colors-wrapper" }, option === null || option === void 0 ? void 0 : option.details.map((detail) => h("div", { class: "s-product-options-colors-item" }, h("input", { type: "radio", value: detail.id, required: option.required, checked: detail.is_selected, name: `options[${option.id}]`, disabled: this.canDisabled && this.isOptionDetailOut(detail), id: `color-${this.productId}-${option.id}-${detail.id}`, onChange: e => this.changedHandler(e, option) }), h("label", { htmlFor: `color-${this.productId}-${option.id}-${detail.id}` }, h("span", { style: { "background-color": detail.color } }), h("div", { innerHTML: this.getOptionDetailName(detail, true, option.type) })))));
341
+ return h("fieldset", { class: "s-product-options-colors-wrapper" }, option === null || option === void 0 ? void 0 : option.details.map((detail) => h("div", { class: "s-product-options-colors-item" }, h("input", { type: "radio", value: detail.id, required: option.required, checked: detail.is_selected, name: `options[${option.id}]`, disabled: this.canDisabled && this.isOptionDetailOut(detail), id: `color-${this.productId}-${option.id}-${detail.id}`, onInvalid: (e) => this.invalidHandler(e, option), onChange: e => this.changedHandler(e, option) }), h("label", { htmlFor: `color-${this.productId}-${option.id}-${detail.id}` }, h("span", { style: { "background-color": detail.color } }), h("div", { innerHTML: this.getOptionDetailName(detail, true, option.type) })))));
333
342
  }
334
343
  //@ts-ignore
335
344
  thumbnailOption(option) {
336
345
  return h("div", { class: "s-product-options-thumbnails-wrapper" }, option.details.map((detail) => {
337
- return h("div", null, h("input", { type: "radio", value: detail.id, "data-itemid": detail.id, required: option.required, checked: detail.is_selected, name: `options[${option.id}]`, "data-img-id": detail.option_value, disabled: this.canDisabled && this.isOptionDetailOut(detail), id: `option_${this.productId}-${option.id}_${detail.id}`, onChange: (e) => this.changedHandler(e, option) }), h("label", { htmlFor: `option_${this.productId}-${option.id}_${detail.id}`, "data-img-id": detail.option_value, class: "go-to-slide" }, h("img", { "data-src": detail.image, src: detail.image, title: detail.name, alt: detail.name }), h("span", { innerHTML: CheckCircleIcon, class: "s-product-options-thumbnails-icon" }), this.isOptionDetailOut(detail) ?
346
+ return h("div", null, h("input", { type: "radio", value: detail.id, "data-itemid": detail.id, required: option.required, checked: detail.is_selected, name: `options[${option.id}]`, "data-img-id": detail.option_value, disabled: this.canDisabled && this.isOptionDetailOut(detail), id: `option_${this.productId}-${option.id}_${detail.id}`, onInvalid: (e) => this.invalidHandler(e, option), onChange: (e) => this.changedHandler(e, option) }), h("label", { htmlFor: `option_${this.productId}-${option.id}_${detail.id}`, "data-img-id": detail.option_value, class: "go-to-slide" }, h("img", { "data-src": detail.image, src: detail.image, title: detail.name, alt: detail.name }), h("span", { innerHTML: CheckCircleIcon, class: "s-product-options-thumbnails-icon" }), this.isOptionDetailOut(detail) ?
338
347
  [
339
348
  h("small", { class: "s-product-options-thumbnails-stock-badge" }, this.outOfStockText),
340
349
  this.canDisabled ? h("div", { class: "s-product-options-thumbnails-badge-overlay" }) : '',
@@ -87,10 +87,10 @@ const SallaAddProductButton = class {
87
87
  return '';
88
88
  }
89
89
  if (this.hasSubscribedOptions) {
90
- return h(Host, null, h("salla-product-availability", Object.assign({}, this.getBtnAttributes(), { "is-subscribed": true })));
90
+ return h(Host, null, h("salla-product-availability", Object.assign({}, this.getBtnAttributes(), { "is-subscribed": true }), h("span", { class: "s-hidden" }, h("slot", null))));
91
91
  }
92
92
  if ((this.productStatus === 'out-and-notify' && this.channels) || this.hasOutOfStockOption) {
93
- return h(Host, null, h("salla-product-availability", Object.assign({}, this.getBtnAttributes())));
93
+ return h(Host, null, h("salla-product-availability", Object.assign({}, this.getBtnAttributes()), h("span", { class: "s-hidden" }, h("slot", null))));
94
94
  }
95
95
  return h(Host, null, h("salla-button", Object.assign({ color: this.productStatus === 'sale' ? 'primary' : 'light', type: "button", fill: this.productStatus === 'sale' ? 'solid' : 'outline', ref: el => this.btn = el, onClick: event => this.addProductToCart(event), disabled: this.productStatus !== 'sale' }, this.getBtnAttributes(), { "loader-position": "center" }), h("slot", null)));
96
96
  }
@@ -99,19 +99,20 @@ const SallaAddProductButton = class {
99
99
  return;
100
100
  }
101
101
  salla.event.on('product-options::change', async (data) => {
102
- var _a;
102
+ var _a, _b;
103
103
  if (!['thumbnail', 'color', 'single-option'].includes(data.option.type)) {
104
104
  return;
105
105
  }
106
- this.hasOutOfStockOption = await ((_a = document.querySelector(`salla-product-options[product-id="${this.productId}"]`)) === null || _a === void 0 ? void 0 : _a.hasOutOfStockOption());
106
+ this.hasSubscribedOptions = false;
107
107
  this.hasLabel = false;
108
- if (!this.subscribedOptions || this.subscribedOptions === 'null') {
108
+ this.selectedOptions = await ((_a = document.querySelector(`salla-product-options[product-id="${this.productId}"]`)) === null || _a === void 0 ? void 0 : _a.getSelectedOptions());
109
+ this.hasOutOfStockOption = await ((_b = document.querySelector(`salla-product-options[product-id="${this.productId}"]`)) === null || _b === void 0 ? void 0 : _b.hasOutOfStockOption());
110
+ let subscribedDetails = salla.storage.get(`product-${this.productId}-subscribed-options`);
111
+ if (!subscribedDetails && !this.subscribedOptions || !this.hasOutOfStockOption) {
109
112
  return;
110
113
  }
111
- //check if subscribedOptions are the selected now
112
- this.hasSubscribedOptions = JSON.parse(this.subscribedOptions)
113
- .filter(ids => ids.every(id => this.selectedOptions.some(option => option.id === id)))
114
- .length > 0;
114
+ const parsedSubscribedDetails = subscribedDetails ? subscribedDetails.map(ids => ids.split(',').map(id => parseInt(id))) : [];
115
+ this.hasSubscribedOptions = parsedSubscribedDetails.length > 0 && parsedSubscribedDetails.some(ids => ids.every(id => this.selectedOptions.some(option => option.id === id)));
115
116
  });
116
117
  }
117
118
  componentDidRender() {
@@ -2515,6 +2515,7 @@ const SallaColorPicker = class {
2515
2515
  constructor(hostRef) {
2516
2516
  registerInstance(this, hostRef);
2517
2517
  this.colorChanged = createEvent(this, "colorChanged", 7);
2518
+ this.invalidInput = createEvent(this, "invalidInput", 7);
2518
2519
  this.submitted = createEvent(this, "submitted", 7);
2519
2520
  this.popupOpened = createEvent(this, "popupOpened", 7);
2520
2521
  this.popupClosed = createEvent(this, "popupClosed", 7);
@@ -2621,6 +2622,13 @@ const SallaColorPicker = class {
2621
2622
  componentDidLoad() {
2622
2623
  this.canvas.style.backgroundColor = this.color;
2623
2624
  this.initColorPicker();
2625
+ this.colorInput.addEventListener('invalid', e => {
2626
+ this.invalidInput.emit(e);
2627
+ });
2628
+ this.colorInput.addEventListener('input', () => {
2629
+ this.colorInput.setCustomValidity('');
2630
+ this.colorInput.reportValidity();
2631
+ });
2624
2632
  }
2625
2633
  setPopIpPosition() {
2626
2634
  const popup = this.host.querySelector('.picker_wrapper');
@@ -5300,6 +5308,7 @@ const SallaDatetimePicker = class {
5300
5308
  constructor(hostRef) {
5301
5309
  registerInstance(this, hostRef);
5302
5310
  this.picked = createEvent(this, "picked", 7);
5311
+ this.invalidInput = createEvent(this, "invalidInput", 7);
5303
5312
  /**
5304
5313
  * Two way data binding to retrieve the selected date[time] value
5305
5314
  */
@@ -5524,6 +5533,13 @@ const SallaDatetimePicker = class {
5524
5533
  // onClose: this.handleOnClose(selectedDates, dateStr, instance)
5525
5534
  };
5526
5535
  flatpickr(this.dateInput, options);
5536
+ this.dateInput.addEventListener('invalid', e => {
5537
+ this.invalidInput.emit(e);
5538
+ });
5539
+ this.dateInput.addEventListener('input', () => {
5540
+ this.dateInput.setCustomValidity('');
5541
+ this.dateInput.reportValidity();
5542
+ });
5527
5543
  }
5528
5544
  };
5529
5545
  SallaDatetimePicker.style = sallaDatetimePickerCss;
@@ -20633,6 +20649,7 @@ const SallaFileUpload = class {
20633
20649
  constructor(hostRef) {
20634
20650
  registerInstance(this, hostRef);
20635
20651
  this.added = createEvent(this, "added", 7);
20652
+ this.invalidInput = createEvent(this, "invalidInput", 7);
20636
20653
  this.uploaded = createEvent(this, "uploaded", 7);
20637
20654
  this.removed = createEvent(this, "removed", 7);
20638
20655
  /**
@@ -20793,15 +20810,18 @@ const SallaFileUpload = class {
20793
20810
  return this.uploaded.emit(this.value);
20794
20811
  }
20795
20812
  removedHandler(deletedFile) {
20813
+ var _a;
20796
20814
  let fileInput = this.getFormDataFileInput();
20797
20815
  fileInput.type = 'hidden';
20798
20816
  fileInput.value = '';
20817
+ (_a = this.host.closest('.s-product-options-option')) === null || _a === void 0 ? void 0 : _a.removeAttribute('data-has-value');
20799
20818
  if (deletedFile.getMetadata('id')) {
20800
20819
  salla.cart.api.deleteImage(deletedFile.getMetadata('id'));
20801
20820
  }
20802
20821
  if (this.height) {
20803
20822
  setTimeout(() => this.host.querySelector('.filepond--root').style.height = this.height, 1000);
20804
20823
  }
20824
+ this.hiddenInput.value = '';
20805
20825
  fileInput.dispatchEvent(new window.Event('change', { bubbles: true }));
20806
20826
  return this.removed.emit(deletedFile);
20807
20827
  }
@@ -20834,6 +20854,7 @@ const SallaFileUpload = class {
20834
20854
  return this.host.querySelector('.filepond--data input');
20835
20855
  }
20836
20856
  getFiles() {
20857
+ var _a;
20837
20858
  if (!this.value && !this.files) {
20838
20859
  return [];
20839
20860
  }
@@ -20841,6 +20862,9 @@ const SallaFileUpload = class {
20841
20862
  let files = this.files
20842
20863
  ? JSON.parse(this.files)
20843
20864
  : this.value.split(',').map(file => ({ url: file }));
20865
+ if (files.length) {
20866
+ (_a = this.host.closest('.s-product-options-option')) === null || _a === void 0 ? void 0 : _a.setAttribute('data-has-value', 'true');
20867
+ }
20844
20868
  return files.map(file => ({
20845
20869
  source: file.id ? `${file.id}` : file.url,
20846
20870
  options: {
@@ -20868,7 +20892,7 @@ const SallaFileUpload = class {
20868
20892
  return (h(Host, { class: {
20869
20893
  "s-file-upload": true,
20870
20894
  "s-file-upload-profile-image": this.profileImage,
20871
- } }, h("input", { type: "file", name: this.name, value: this.value, ref: ele => this.fileUploader = ele, required: this.required, class: "s-file-upload-wrapper s-file-upload-input", accept: this.accept })));
20895
+ } }, h("input", { type: "file", name: this.name, value: this.value, ref: ele => this.fileUploader = ele, required: this.required, class: "s-file-upload-wrapper s-file-upload-input", accept: this.accept }), h("input", { class: "s-file-upload-hidden-input", name: 'hidden-' + this.name, required: this.required, value: this.value, ref: input => this.hiddenInput = input })));
20872
20896
  }
20873
20897
  componentDidLoad() {
20874
20898
  let files = this.getFiles();
@@ -20960,6 +20984,13 @@ const SallaFileUpload = class {
20960
20984
  resolve(true);
20961
20985
  }),
20962
20986
  });
20987
+ this.hiddenInput.addEventListener('invalid', e => {
20988
+ this.invalidInput.emit(e);
20989
+ });
20990
+ this.hiddenInput.addEventListener('change', () => {
20991
+ this.hiddenInput.setCustomValidity('');
20992
+ this.hiddenInput.reportValidity();
20993
+ });
20963
20994
  }
20964
20995
  get host() { return getElement(this); }
20965
20996
  };
@@ -22570,6 +22601,7 @@ const SallaMap = class {
22570
22601
  this.selected = createEvent(this, "selected", 7);
22571
22602
  this.mapClicked = createEvent(this, "mapClicked", 7);
22572
22603
  this.currentLocationChanged = createEvent(this, "currentLocationChanged", 7);
22604
+ this.invalidInput = createEvent(this, "invalidInput", 7);
22573
22605
  this.defaultLat = 21.419421; //Mecca 🕋
22574
22606
  this.defaultLng = 39.82553; //Mecca 🕋
22575
22607
  // state variables
@@ -22774,6 +22806,13 @@ const SallaMap = class {
22774
22806
  }
22775
22807
  });
22776
22808
  }
22809
+ this.mapInput.addEventListener('invalid', e => {
22810
+ this.invalidInput.emit(e);
22811
+ });
22812
+ this.mapInput.addEventListener('input', () => {
22813
+ this.mapInput.setCustomValidity('');
22814
+ this.mapInput.reportValidity();
22815
+ });
22777
22816
  }
22778
22817
  /**
22779
22818
  * Open location component
@@ -23206,12 +23245,34 @@ const SallaProductAvailability = class {
23206
23245
  * is current user already subscribed
23207
23246
  */
23208
23247
  this.isSubscribed = false;
23248
+ this.handleSubmitOptions = async () => {
23249
+ let payload = { id: this.productId };
23250
+ if (!this.notifyOptionsAvailability) {
23251
+ return payload;
23252
+ }
23253
+ let optionsElement = document.querySelector(`salla-product-options[product-id="${this.productId}"]`);
23254
+ let options = Object.values(await (optionsElement === null || optionsElement === void 0 ? void 0 : optionsElement.getSelectedOptionsData()) || {});
23255
+ //if all options not selected, show message && throw exception
23256
+ if (options.length && !await (optionsElement === null || optionsElement === void 0 ? void 0 : optionsElement.reportValidity())) {
23257
+ let errorMessage = salla.lang.get('common.messages.required_fields');
23258
+ salla.error(errorMessage);
23259
+ throw errorMessage;
23260
+ }
23261
+ payload.options = [];
23262
+ options.forEach(option => {
23263
+ //inject numbers only, without zeros
23264
+ if (option && !isNaN(option)) {
23265
+ payload.options.push(Number(option));
23266
+ }
23267
+ });
23268
+ return payload;
23269
+ };
23209
23270
  // helpers
23210
- this.typing = (e, submitMethod) => {
23271
+ this.typing = (e) => {
23211
23272
  const error = e.target.nextElementSibling;
23212
23273
  e.target.classList.remove('s-has-error');
23213
23274
  (error === null || error === void 0 ? void 0 : error.classList.contains('s-product-availability-error-msg')) && (error.innerText = '');
23214
- e.key == 'Enter' && submitMethod();
23275
+ e.keyCode === 13 && this.submit();
23215
23276
  };
23216
23277
  salla.lang.onLoaded(() => {
23217
23278
  var _a;
@@ -23233,28 +23294,6 @@ const SallaProductAvailability = class {
23233
23294
  channelsWatcher(newValue) {
23234
23295
  this.channels_ = !!newValue ? newValue.split(',') : [];
23235
23296
  }
23236
- async handleSubmitOptions() {
23237
- let payload = { id: this.productId };
23238
- if (!this.notifyOptionsAvailability) {
23239
- return payload;
23240
- }
23241
- let optionsElement = document.querySelector(`salla-product-options[product-id="${this.productId}"]`);
23242
- let options = Object.values(await (optionsElement === null || optionsElement === void 0 ? void 0 : optionsElement.getSelectedOptionsData()) || {});
23243
- //if all options not selected, show message && throw exception
23244
- if (options.length && !await (optionsElement === null || optionsElement === void 0 ? void 0 : optionsElement.reportValidity())) {
23245
- let errorMessage = salla.lang.get('common.messages.required_fields');
23246
- salla.error(errorMessage);
23247
- throw errorMessage;
23248
- }
23249
- payload.options = [];
23250
- options.forEach(option => {
23251
- //inject numbers only, without zeros
23252
- if (option && !isNaN(option)) {
23253
- payload.options.push(Number(option));
23254
- }
23255
- });
23256
- return payload;
23257
- }
23258
23297
  openModel() {
23259
23298
  this.handleSubmitOptions().then(isSuccess => isSuccess ? this.modal.open() : null);
23260
23299
  }
@@ -23277,9 +23316,23 @@ const SallaProductAvailability = class {
23277
23316
  .then(() => this.btn.disable())
23278
23317
  .then(() => salla.api.product.availabilitySubscribe(payload))
23279
23318
  .then(() => {
23319
+ if (!this.notifyOptionsAvailability) {
23320
+ salla.storage.set(`product-${this.productId}-subscribed`, true);
23321
+ return;
23322
+ }
23323
+ if (payload.options.length) {
23324
+ let options = salla.storage.get(`product-${this.productId}-subscribed-options`) || [];
23325
+ let selectedOptionsString = payload.options.join(',');
23326
+ if (!options.includes(selectedOptionsString)) {
23327
+ options.push(selectedOptionsString);
23328
+ salla.storage.set(`product-${this.productId}-subscribed-options`, options);
23329
+ }
23330
+ else {
23331
+ salla.log('already subscribed to this options');
23332
+ }
23333
+ }
23280
23334
  this.isSubscribed = true;
23281
- salla.storage.set(`product-${this.productId}-subscribed`, true);
23282
- }) //no need to wait until finishing alert animation
23335
+ })
23283
23336
  .then(() => this.btn.stop())
23284
23337
  .then(() => this.modal.close())
23285
23338
  .catch(() => this.btn.stop() && this.btn.enable());
@@ -23315,11 +23368,11 @@ const SallaProductAvailability = class {
23315
23368
  renderModal() {
23316
23369
  return (h("salla-modal", { ref: modal => this.modal = modal, "modal-title": this.title_, subTitle: salla.lang.get('pages.products.notify_availability_subtitle'), width: "sm" }, h("span", { slot: 'icon', class: "s-product-availability-header-icon", innerHTML: BellRing }), h("div", { class: "s-product-availability-body" }, this.channels_.includes('email') ? [
23317
23370
  h("label", { class: "s-product-availability-label" }, salla.lang.get('common.elements.email')),
23318
- h("input", { class: "s-product-availability-input", onKeyDown: e => this.typing(e, this.submit), placeholder: salla.lang.get('common.elements.email_placeholder') || 'your@email.com', ref: el => this.email = el, type: "email" }),
23371
+ h("input", { class: "s-product-availability-input", onKeyDown: e => this.typing(e), placeholder: salla.lang.get('common.elements.email_placeholder') || 'your@email.com', ref: el => this.email = el, type: "email" }),
23319
23372
  h("span", { class: "s-product-availability-error-msg" })
23320
23373
  ] : '', this.channels_.includes('sms') ? [
23321
23374
  h("label", { class: "s-product-availability-label" }, salla.lang.get('common.elements.mobile')),
23322
- h("salla-tel-input", { ref: el => this.mobileInput = el, onKeyDown: e => this.typing(e, this.submit) })
23375
+ h("salla-tel-input", { ref: el => this.mobileInput = el, onKeyDown: e => this.typing(e) })
23323
23376
  ] : ''), h("div", { slot: "footer", class: "s-product-availability-footer" }, h("salla-button", { class: "modal-cancel-btn", width: "wide", color: "light", fill: "outline", onClick: () => this.modal.close() }, salla.lang.get('common.elements.cancel')), h("salla-button", { class: "submit-btn", "loader-position": 'center', width: "wide", ref: btn => this.btn = btn, onClick: () => this.submit() }, salla.lang.get('common.elements.submit')))));
23324
23377
  }
23325
23378
  get host() { return getElement(this); }
@@ -110,7 +110,7 @@ const SallaProductOptions = class {
110
110
  return selectedOptions;
111
111
  }
112
112
  /**
113
- * Get the id's of the selected options.
113
+ * Report options form validity.
114
114
  * */
115
115
  async reportValidity() {
116
116
  let requiredElements = this.host.querySelectorAll('[required]');
@@ -141,6 +141,12 @@ const SallaProductOptions = class {
141
141
  async getOption(option_id) {
142
142
  return this.optionsData.find(option => option.id === option_id);
143
143
  }
144
+ // @ts-ignore
145
+ invalidHandler(event, option) {
146
+ const closestProductOption = event.target.closest('.s-product-options-option');
147
+ closestProductOption.scrollIntoView({ behavior: 'smooth', block: 'center' });
148
+ closestProductOption.classList.add('s-product-options-option-error');
149
+ }
144
150
  changedHandler(event, option) {
145
151
  let data = { event: event, option: option, detail: null };
146
152
  if (option.details) {
@@ -149,6 +155,10 @@ const SallaProductOptions = class {
149
155
  });
150
156
  data.detail = detail;
151
157
  }
158
+ let optionElement = event.target.closest('.s-product-options-option');
159
+ if (event.target.value || event.type == 'added') {
160
+ optionElement.classList.remove('s-product-options-option-error');
161
+ }
152
162
  const index = this.selectedOptions.findIndex(option => option.option_id === data.option.id);
153
163
  index > -1 ? this.selectedOptions[index] = Object.assign(Object.assign({}, data.detail), { option_id: data.option.id }) : this.selectedOptions.push(Object.assign(Object.assign({}, data.detail), { option_id: data.option.id }));
154
164
  this.setSelectedSkus();
@@ -209,16 +219,16 @@ const SallaProductOptions = class {
209
219
  }
210
220
  return (h(Host, { class: "s-product-options-wrapper" }, h("salla-conditional-fields", null, this.optionsData.map((option) => h("div", Object.assign({ class: `s-product-options-option-container${option.visibility_condition ? ' hidden' : ''}`, "data-option-id": option.id }, this.getOptionShownWhen(option)), option.name == 'splitter' ?
211
221
  this.splitterOption()
212
- : h("div", { class: "s-product-options-option" }, h("label", { htmlFor: 'options[' + option.id + ']', class: "s-product-options-option-label" }, h("b", null, option.name, option.required && h("span", null, " * "), " "), h("small", null, option.placeholder)), h("div", { class: "s-product-options-option-content" }, this.getDisplayForType(option))))))));
222
+ : h("div", { class: "s-product-options-option", "data-option-type": option.type, "data-option-required": `${option.required}` }, h("label", { htmlFor: 'options[' + option.id + ']', class: "s-product-options-option-label" }, h("b", null, option.name, option.required && h("span", null, " * "), " "), h("small", null, option.placeholder)), h("div", { class: "s-product-options-option-content" }, this.getDisplayForType(option))))))));
213
223
  }
214
224
  //@ts-ignore
215
225
  donationOption(option, product) {
216
226
  return h("div", { class: "s-product-options-donation-wrapper" }, option.donation ?
217
227
  h("div", { class: "s-product-options-donation-progress" }, h("salla-progress-bar", { donation: option.donation }))
218
- : '', h("div", { class: "s-product-options-donation-input-group" }, h("input", { type: "text", id: "donating-amount", name: "donating_amount", class: "s-form-control", value: option.value, required: true, placeholder: option.placeholder, onInput: e => salla.helpers.inputDigitsOnly(e.target), onBlur: e => this.changedHandler(e, option) }), h("span", { class: "s-product-options-donation-amount-currency" }, salla.config.currency(salla.config.get('user.currency_code')).symbol)));
228
+ : '', h("div", { class: "s-product-options-donation-input-group" }, h("input", { type: "text", id: "donating-amount", name: "donating_amount", class: "s-form-control", value: option.value, required: true, placeholder: option.placeholder, onInput: e => salla.helpers.inputDigitsOnly(e.target), onBlur: e => this.changedHandler(e, option), onInvalid: (e) => this.invalidHandler(e, option) }), h("span", { class: "s-product-options-donation-amount-currency" }, salla.config.currency(salla.config.get('user.currency_code')).symbol)));
219
229
  }
220
230
  fileUploader(option, additions = null) {
221
- return h("salla-file-upload", Object.assign({}, (additions || {}), { "payload-name": "file", value: option.value, "instant-upload": true, name: `options[${option.id}]`, required: option.required, height: "120px", url: salla.cart.api.getUploadImageEndpoint(), "form-data": { cart_item_id: this.productId, product_id: this.productId }, class: { "s-product-options-image-input": true, required: option.required } }), h("div", { class: "s-product-options-filepond-placeholder" }, h("span", { class: "s-product-options-filepond-placeholder-icon", innerHTML: additions.accept && additions.accept.split(',').every(type => type.includes('image'))
231
+ return h("salla-file-upload", Object.assign({}, (additions || {}), { "payload-name": "file", value: option.value, "instant-upload": true, name: `options[${option.id}]`, required: option.required, height: "120px", onAdded: (e) => this.changedHandler(e, option), url: salla.cart.api.getUploadImageEndpoint(), "form-data": { cart_item_id: this.productId, product_id: this.productId }, onInvalidInput: (e) => this.invalidHandler(e, option), class: { "s-product-options-image-input": true, required: option.required } }), h("div", { class: "s-product-options-filepond-placeholder" }, h("span", { class: "s-product-options-filepond-placeholder-icon", innerHTML: additions.accept && additions.accept.split(',').every(type => type.includes('image'))
222
232
  ? CameraIcon
223
233
  : FileIcon }), h("p", { class: "s-product-options-filepond-placeholder-text" }, salla.lang.get('common.uploader.drag_and_drop')), h("span", { class: "filepond--label-action" }, salla.lang.get('common.uploader.browse'))));
224
234
  }
@@ -235,7 +245,7 @@ const SallaProductOptions = class {
235
245
  }
236
246
  //@ts-ignore
237
247
  numberOption(option) {
238
- return h("input", { type: "text", value: option.value, class: "s-form-control", required: option.required, name: `options[${option.id}]`, placeholder: option.placeholder, onBlur: e => this.changedHandler(e, option), onInput: e => salla.helpers.inputDigitsOnly(e.target) });
248
+ return h("input", { type: "text", value: option.value, class: "s-form-control", required: option.required, name: `options[${option.id}]`, placeholder: option.placeholder, onBlur: e => this.changedHandler(e, option), onInvalid: (e) => this.invalidHandler(e, option), onInput: e => salla.helpers.inputDigitsOnly(e.target) });
239
249
  }
240
250
  //@ts-ignore
241
251
  splitterOption() {
@@ -243,41 +253,40 @@ const SallaProductOptions = class {
243
253
  }
244
254
  //@ts-ignore
245
255
  textOption(option) {
246
- return h("div", { class: "s-product-options-text" }, h("input", { type: "text", value: option.value, class: 's-form-control', required: option.required, name: `options[${option.id}]`, placeholder: option.placeholder, onInput: e => this.changedHandler(e, option) }));
256
+ return h("div", { class: "s-product-options-text" }, h("input", { type: "text", value: option.value, class: 's-form-control', required: option.required, name: `options[${option.id}]`, placeholder: option.placeholder, onInvalid: (e) => this.invalidHandler(e, option), onInput: e => this.changedHandler(e, option) }));
247
257
  }
248
258
  //@ts-ignore
249
259
  textareaOption(option) {
250
260
  //todo::remove mt-1 class, and if it's okay to remove the tag itself will be great
251
- return h("div", { class: "s-product-options-textarea" }, h("div", { class: "mt-1" }, h("textarea", { rows: 4, value: option.value, class: "s-form-control", required: option.required, id: `options[${option.id}]`, name: `options[${option.id}]`, placeholder: option.placeholder, onInput: (e) => this.changedHandler(e, option) })));
261
+ return h("div", { class: "s-product-options-textarea" }, h("div", { class: "mt-1" }, h("textarea", { rows: 4, value: option.value, class: "s-form-control", required: option.required, id: `options[${option.id}]`, name: `options[${option.id}]`, placeholder: option.placeholder, onInvalid: (e) => this.invalidHandler(e, option), onInput: (e) => this.changedHandler(e, option) })));
252
262
  }
253
263
  //@ts-ignore
254
264
  mapOption(option) {
255
- return h("salla-map", { zoom: 15, lat: this.getLatLng(option.value, 'lat'), lng: this.getLatLng(option.value, 'lng'), name: `options[${option.id}]`, searchable: true, required: option.required, onSelected: e => this.changedHandler(e, option) });
265
+ return h("salla-map", { zoom: 15, lat: this.getLatLng(option.value, 'lat'), lng: this.getLatLng(option.value, 'lng'), name: `options[${option.id}]`, searchable: true, required: option.required, onInvalidInput: (e) => this.invalidHandler(e, option), onSelected: e => this.changedHandler(e, option) });
256
266
  }
257
267
  colorPickerOption(option) {
258
- return h("salla-color-picker", { onSubmitted: e => this.changedHandler(e, option), name: `options[${option.id}]`, required: option.required, color: option.value || '#5dd5c4' });
268
+ return h("salla-color-picker", { onSubmitted: e => this.changedHandler(e, option), name: `options[${option.id}]`, required: option.required, onInvalidInput: (e) => this.invalidHandler(e, option), color: option.value || '#5dd5c4' });
259
269
  }
260
270
  /**
261
271
  * ============= Date Time options =============
262
272
  */
263
273
  //@ts-ignore
264
274
  timeOption(option) {
265
- return h("salla-datetime-picker", { noCalendar: true, enableTime: true, dateFormat: "h:i K", value: option.value, placeholder: option.name, required: option.required, name: `options[${option.id}]`, class: "s-product-options-time-element", onPicked: e => this.changedHandler(e, option) });
275
+ return h("salla-datetime-picker", { noCalendar: true, enableTime: true, dateFormat: "h:i K", value: option.value, placeholder: option.name, required: option.required, name: `options[${option.id}]`, class: "s-product-options-time-element", onInvalidInput: (e) => this.invalidHandler(e, option), onPicked: e => this.changedHandler(e, option) });
266
276
  }
267
277
  //@ts-ignore
268
278
  dateOption(option) {
269
279
  //todo:: consider date-range @see https://github.com/SallaApp/theme-raed/blob/master/src/assets/js/partials/product-options.js#L8-L23
270
- return h("div", { class: "s-product-options-date-element" }, h("salla-datetime-picker", { value: option.value, placeholder: option.name, required: option.required, minDate: new Date(), name: `options[${option.id}]`, onPicked: e => this.changedHandler(e, option) }));
280
+ return h("div", { class: "s-product-options-date-element" }, h("salla-datetime-picker", { value: option.value, placeholder: option.name, required: option.required, minDate: new Date(), name: `options[${option.id}]`, onInvalidInput: (e) => this.invalidHandler(e, option), onPicked: e => this.changedHandler(e, option) }));
271
281
  }
272
282
  //@ts-ignore
273
283
  datetimeOption(option) {
274
284
  //todo:: consider date-range @see https://github.com/SallaApp/theme-raed/blob/master/src/assets/js/partials/product-options.js#L8-L23
275
- return h("div", { class: "s-product-options-datetime-element" }, h("salla-datetime-picker", { enableTime: true, value: option.value, dateFormat: "Y-m-d G:i:K", placeholder: option.name, required: option.required, name: `options[${option.id}]`, maxDate: option.to_date_time, minDate: option.from_date_time, onPicked: e => this.changedHandler(e, option) }));
285
+ return h("div", { class: "s-product-options-datetime-element" }, h("salla-datetime-picker", { enableTime: true, value: option.value, dateFormat: "Y-m-d G:i:K", placeholder: option.name, required: option.required, name: `options[${option.id}]`, maxDate: option.to_date_time, minDate: option.from_date_time, onInvalidInput: (e) => this.invalidHandler(e, option), onPicked: e => this.changedHandler(e, option) }));
276
286
  }
277
287
  /**
278
288
  * ============= Advanced options =============
279
289
  */
280
- // <<<<<<< HEAD
281
290
  getOptionDetailName(detail, outOfStock = true, optionType) {
282
291
  if (optionType && optionType == DisplayType.COLOR) {
283
292
  return detail.name
@@ -307,24 +316,24 @@ const SallaProductOptions = class {
307
316
  return this.selectedOptions.some(option => option.is_out && option.option_id !== detail.option_id);
308
317
  }
309
318
  singleOption(option) {
310
- return h("div", null, h("select", { name: `options[${option.id}]`, required: option.required, class: "s-form-control", onChange: e => this.changedHandler(e, option) }, h("option", { value: "" }, option.placeholder), option === null || option === void 0 ? void 0 :
319
+ return h("div", null, h("select", { name: `options[${option.id}]`, required: option.required, class: "s-form-control", onInvalid: (e) => this.invalidHandler(e, option), onChange: e => this.changedHandler(e, option) }, h("option", { value: "" }, option.placeholder), option === null || option === void 0 ? void 0 :
311
320
  option.details.map((detail) => {
312
321
  return h("option", { value: detail.id, disabled: this.canDisabled && this.isOptionDetailOut(detail), selected: detail.is_selected }, this.getOptionDetailName(detail));
313
322
  })));
314
323
  }
315
324
  multipleOptions(option) {
316
325
  return h("div", { class: { "s-product-options-multiple-options-wrapper": true, 'required': option.required } }, option === null || option === void 0 ? void 0 : option.details.map((detail) => {
317
- return h("div", null, h("input", { type: "checkbox", value: detail.id, disabled: this.isOptionDetailOut(detail), checked: detail.is_selected, required: option.required, name: `options[${option.id}][]`, id: `field-${option.id}-${detail.id}`, onChange: (e) => this.changedHandler(e, option), "aria-describedby": `options[${option.id}]-description` }), h("label", { htmlFor: `field-${option.id}-${detail.id}` }, this.getOptionDetailName(detail)));
326
+ return h("div", null, h("input", { type: "checkbox", value: detail.id, disabled: this.isOptionDetailOut(detail), checked: detail.is_selected, required: option.required, name: `options[${option.id}][]`, id: `field-${option.id}-${detail.id}`, onChange: (e) => this.changedHandler(e, option), onInvalid: (e) => this.invalidHandler(e, option), "aria-describedby": `options[${option.id}]-description` }), h("label", { htmlFor: `field-${option.id}-${detail.id}` }, this.getOptionDetailName(detail)));
318
327
  }));
319
328
  }
320
329
  //@ts-ignore
321
330
  colorOption(option) {
322
- return h("fieldset", { class: "s-product-options-colors-wrapper" }, option === null || option === void 0 ? void 0 : option.details.map((detail) => h("div", { class: "s-product-options-colors-item" }, h("input", { type: "radio", value: detail.id, required: option.required, checked: detail.is_selected, name: `options[${option.id}]`, disabled: this.canDisabled && this.isOptionDetailOut(detail), id: `color-${this.productId}-${option.id}-${detail.id}`, onChange: e => this.changedHandler(e, option) }), h("label", { htmlFor: `color-${this.productId}-${option.id}-${detail.id}` }, h("span", { style: { "background-color": detail.color } }), h("div", { innerHTML: this.getOptionDetailName(detail, true, option.type) })))));
331
+ return h("fieldset", { class: "s-product-options-colors-wrapper" }, option === null || option === void 0 ? void 0 : option.details.map((detail) => h("div", { class: "s-product-options-colors-item" }, h("input", { type: "radio", value: detail.id, required: option.required, checked: detail.is_selected, name: `options[${option.id}]`, disabled: this.canDisabled && this.isOptionDetailOut(detail), id: `color-${this.productId}-${option.id}-${detail.id}`, onInvalid: (e) => this.invalidHandler(e, option), onChange: e => this.changedHandler(e, option) }), h("label", { htmlFor: `color-${this.productId}-${option.id}-${detail.id}` }, h("span", { style: { "background-color": detail.color } }), h("div", { innerHTML: this.getOptionDetailName(detail, true, option.type) })))));
323
332
  }
324
333
  //@ts-ignore
325
334
  thumbnailOption(option) {
326
335
  return h("div", { class: "s-product-options-thumbnails-wrapper" }, option.details.map((detail) => {
327
- return h("div", null, h("input", { type: "radio", value: detail.id, "data-itemid": detail.id, required: option.required, checked: detail.is_selected, name: `options[${option.id}]`, "data-img-id": detail.option_value, disabled: this.canDisabled && this.isOptionDetailOut(detail), id: `option_${this.productId}-${option.id}_${detail.id}`, onChange: (e) => this.changedHandler(e, option) }), h("label", { htmlFor: `option_${this.productId}-${option.id}_${detail.id}`, "data-img-id": detail.option_value, class: "go-to-slide" }, h("img", { "data-src": detail.image, src: detail.image, title: detail.name, alt: detail.name }), h("span", { innerHTML: CheckCircleIcon, class: "s-product-options-thumbnails-icon" }), this.isOptionDetailOut(detail) ?
336
+ return h("div", null, h("input", { type: "radio", value: detail.id, "data-itemid": detail.id, required: option.required, checked: detail.is_selected, name: `options[${option.id}]`, "data-img-id": detail.option_value, disabled: this.canDisabled && this.isOptionDetailOut(detail), id: `option_${this.productId}-${option.id}_${detail.id}`, onInvalid: (e) => this.invalidHandler(e, option), onChange: (e) => this.changedHandler(e, option) }), h("label", { htmlFor: `option_${this.productId}-${option.id}_${detail.id}`, "data-img-id": detail.option_value, class: "go-to-slide" }, h("img", { "data-src": detail.image, src: detail.image, title: detail.name, alt: detail.name }), h("span", { innerHTML: CheckCircleIcon, class: "s-product-options-thumbnails-icon" }), this.isOptionDetailOut(detail) ?
328
337
  [
329
338
  h("small", { class: "s-product-options-thumbnails-stock-badge" }, this.outOfStockText),
330
339
  this.canDisabled ? h("div", { class: "s-product-options-thumbnails-badge-overlay" }) : '',