@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
@@ -91,10 +91,10 @@ const SallaAddProductButton = class {
91
91
  return '';
92
92
  }
93
93
  if (this.hasSubscribedOptions) {
94
- return index.h(index.Host, null, index.h("salla-product-availability", Object.assign({}, this.getBtnAttributes(), { "is-subscribed": true })));
94
+ return index.h(index.Host, null, index.h("salla-product-availability", Object.assign({}, this.getBtnAttributes(), { "is-subscribed": true }), index.h("span", { class: "s-hidden" }, index.h("slot", null))));
95
95
  }
96
96
  if ((this.productStatus === 'out-and-notify' && this.channels) || this.hasOutOfStockOption) {
97
- return index.h(index.Host, null, index.h("salla-product-availability", Object.assign({}, this.getBtnAttributes())));
97
+ return index.h(index.Host, null, index.h("salla-product-availability", Object.assign({}, this.getBtnAttributes()), index.h("span", { class: "s-hidden" }, index.h("slot", null))));
98
98
  }
99
99
  return index.h(index.Host, null, index.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" }), index.h("slot", null)));
100
100
  }
@@ -103,19 +103,20 @@ const SallaAddProductButton = class {
103
103
  return;
104
104
  }
105
105
  salla.event.on('product-options::change', async (data) => {
106
- var _a;
106
+ var _a, _b;
107
107
  if (!['thumbnail', 'color', 'single-option'].includes(data.option.type)) {
108
108
  return;
109
109
  }
110
- this.hasOutOfStockOption = await ((_a = document.querySelector(`salla-product-options[product-id="${this.productId}"]`)) === null || _a === void 0 ? void 0 : _a.hasOutOfStockOption());
110
+ this.hasSubscribedOptions = false;
111
111
  this.hasLabel = false;
112
- if (!this.subscribedOptions || this.subscribedOptions === 'null') {
112
+ this.selectedOptions = await ((_a = document.querySelector(`salla-product-options[product-id="${this.productId}"]`)) === null || _a === void 0 ? void 0 : _a.getSelectedOptions());
113
+ this.hasOutOfStockOption = await ((_b = document.querySelector(`salla-product-options[product-id="${this.productId}"]`)) === null || _b === void 0 ? void 0 : _b.hasOutOfStockOption());
114
+ let subscribedDetails = salla.storage.get(`product-${this.productId}-subscribed-options`);
115
+ if (!subscribedDetails && !this.subscribedOptions || !this.hasOutOfStockOption) {
113
116
  return;
114
117
  }
115
- //check if subscribedOptions are the selected now
116
- this.hasSubscribedOptions = JSON.parse(this.subscribedOptions)
117
- .filter(ids => ids.every(id => this.selectedOptions.some(option => option.id === id)))
118
- .length > 0;
118
+ const parsedSubscribedDetails = subscribedDetails ? subscribedDetails.map(ids => ids.split(',').map(id => parseInt(id))) : [];
119
+ this.hasSubscribedOptions = parsedSubscribedDetails.length > 0 && parsedSubscribedDetails.some(ids => ids.every(id => this.selectedOptions.some(option => option.id === id)));
119
120
  });
120
121
  }
121
122
  componentDidRender() {
@@ -2519,6 +2519,7 @@ const SallaColorPicker = class {
2519
2519
  constructor(hostRef) {
2520
2520
  index.registerInstance(this, hostRef);
2521
2521
  this.colorChanged = index.createEvent(this, "colorChanged", 7);
2522
+ this.invalidInput = index.createEvent(this, "invalidInput", 7);
2522
2523
  this.submitted = index.createEvent(this, "submitted", 7);
2523
2524
  this.popupOpened = index.createEvent(this, "popupOpened", 7);
2524
2525
  this.popupClosed = index.createEvent(this, "popupClosed", 7);
@@ -2625,6 +2626,13 @@ const SallaColorPicker = class {
2625
2626
  componentDidLoad() {
2626
2627
  this.canvas.style.backgroundColor = this.color;
2627
2628
  this.initColorPicker();
2629
+ this.colorInput.addEventListener('invalid', e => {
2630
+ this.invalidInput.emit(e);
2631
+ });
2632
+ this.colorInput.addEventListener('input', () => {
2633
+ this.colorInput.setCustomValidity('');
2634
+ this.colorInput.reportValidity();
2635
+ });
2628
2636
  }
2629
2637
  setPopIpPosition() {
2630
2638
  const popup = this.host.querySelector('.picker_wrapper');
@@ -5304,6 +5312,7 @@ const SallaDatetimePicker = class {
5304
5312
  constructor(hostRef) {
5305
5313
  index.registerInstance(this, hostRef);
5306
5314
  this.picked = index.createEvent(this, "picked", 7);
5315
+ this.invalidInput = index.createEvent(this, "invalidInput", 7);
5307
5316
  /**
5308
5317
  * Two way data binding to retrieve the selected date[time] value
5309
5318
  */
@@ -5528,6 +5537,13 @@ const SallaDatetimePicker = class {
5528
5537
  // onClose: this.handleOnClose(selectedDates, dateStr, instance)
5529
5538
  };
5530
5539
  flatpickr(this.dateInput, options);
5540
+ this.dateInput.addEventListener('invalid', e => {
5541
+ this.invalidInput.emit(e);
5542
+ });
5543
+ this.dateInput.addEventListener('input', () => {
5544
+ this.dateInput.setCustomValidity('');
5545
+ this.dateInput.reportValidity();
5546
+ });
5531
5547
  }
5532
5548
  };
5533
5549
  SallaDatetimePicker.style = sallaDatetimePickerCss;
@@ -20637,6 +20653,7 @@ const SallaFileUpload = class {
20637
20653
  constructor(hostRef) {
20638
20654
  index.registerInstance(this, hostRef);
20639
20655
  this.added = index.createEvent(this, "added", 7);
20656
+ this.invalidInput = index.createEvent(this, "invalidInput", 7);
20640
20657
  this.uploaded = index.createEvent(this, "uploaded", 7);
20641
20658
  this.removed = index.createEvent(this, "removed", 7);
20642
20659
  /**
@@ -20797,15 +20814,18 @@ const SallaFileUpload = class {
20797
20814
  return this.uploaded.emit(this.value);
20798
20815
  }
20799
20816
  removedHandler(deletedFile) {
20817
+ var _a;
20800
20818
  let fileInput = this.getFormDataFileInput();
20801
20819
  fileInput.type = 'hidden';
20802
20820
  fileInput.value = '';
20821
+ (_a = this.host.closest('.s-product-options-option')) === null || _a === void 0 ? void 0 : _a.removeAttribute('data-has-value');
20803
20822
  if (deletedFile.getMetadata('id')) {
20804
20823
  salla.cart.api.deleteImage(deletedFile.getMetadata('id'));
20805
20824
  }
20806
20825
  if (this.height) {
20807
20826
  setTimeout(() => this.host.querySelector('.filepond--root').style.height = this.height, 1000);
20808
20827
  }
20828
+ this.hiddenInput.value = '';
20809
20829
  fileInput.dispatchEvent(new window.Event('change', { bubbles: true }));
20810
20830
  return this.removed.emit(deletedFile);
20811
20831
  }
@@ -20838,6 +20858,7 @@ const SallaFileUpload = class {
20838
20858
  return this.host.querySelector('.filepond--data input');
20839
20859
  }
20840
20860
  getFiles() {
20861
+ var _a;
20841
20862
  if (!this.value && !this.files) {
20842
20863
  return [];
20843
20864
  }
@@ -20845,6 +20866,9 @@ const SallaFileUpload = class {
20845
20866
  let files = this.files
20846
20867
  ? JSON.parse(this.files)
20847
20868
  : this.value.split(',').map(file => ({ url: file }));
20869
+ if (files.length) {
20870
+ (_a = this.host.closest('.s-product-options-option')) === null || _a === void 0 ? void 0 : _a.setAttribute('data-has-value', 'true');
20871
+ }
20848
20872
  return files.map(file => ({
20849
20873
  source: file.id ? `${file.id}` : file.url,
20850
20874
  options: {
@@ -20872,7 +20896,7 @@ const SallaFileUpload = class {
20872
20896
  return (index.h(index.Host, { class: {
20873
20897
  "s-file-upload": true,
20874
20898
  "s-file-upload-profile-image": this.profileImage,
20875
- } }, index.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 })));
20899
+ } }, index.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 }), index.h("input", { class: "s-file-upload-hidden-input", name: 'hidden-' + this.name, required: this.required, value: this.value, ref: input => this.hiddenInput = input })));
20876
20900
  }
20877
20901
  componentDidLoad() {
20878
20902
  let files = this.getFiles();
@@ -20964,6 +20988,13 @@ const SallaFileUpload = class {
20964
20988
  resolve(true);
20965
20989
  }),
20966
20990
  });
20991
+ this.hiddenInput.addEventListener('invalid', e => {
20992
+ this.invalidInput.emit(e);
20993
+ });
20994
+ this.hiddenInput.addEventListener('change', () => {
20995
+ this.hiddenInput.setCustomValidity('');
20996
+ this.hiddenInput.reportValidity();
20997
+ });
20967
20998
  }
20968
20999
  get host() { return index.getElement(this); }
20969
21000
  };
@@ -22574,6 +22605,7 @@ const SallaMap = class {
22574
22605
  this.selected = index.createEvent(this, "selected", 7);
22575
22606
  this.mapClicked = index.createEvent(this, "mapClicked", 7);
22576
22607
  this.currentLocationChanged = index.createEvent(this, "currentLocationChanged", 7);
22608
+ this.invalidInput = index.createEvent(this, "invalidInput", 7);
22577
22609
  this.defaultLat = 21.419421; //Mecca 🕋
22578
22610
  this.defaultLng = 39.82553; //Mecca 🕋
22579
22611
  // state variables
@@ -22778,6 +22810,13 @@ const SallaMap = class {
22778
22810
  }
22779
22811
  });
22780
22812
  }
22813
+ this.mapInput.addEventListener('invalid', e => {
22814
+ this.invalidInput.emit(e);
22815
+ });
22816
+ this.mapInput.addEventListener('input', () => {
22817
+ this.mapInput.setCustomValidity('');
22818
+ this.mapInput.reportValidity();
22819
+ });
22781
22820
  }
22782
22821
  /**
22783
22822
  * Open location component
@@ -23210,12 +23249,34 @@ const SallaProductAvailability = class {
23210
23249
  * is current user already subscribed
23211
23250
  */
23212
23251
  this.isSubscribed = false;
23252
+ this.handleSubmitOptions = async () => {
23253
+ let payload = { id: this.productId };
23254
+ if (!this.notifyOptionsAvailability) {
23255
+ return payload;
23256
+ }
23257
+ let optionsElement = document.querySelector(`salla-product-options[product-id="${this.productId}"]`);
23258
+ let options = Object.values(await (optionsElement === null || optionsElement === void 0 ? void 0 : optionsElement.getSelectedOptionsData()) || {});
23259
+ //if all options not selected, show message && throw exception
23260
+ if (options.length && !await (optionsElement === null || optionsElement === void 0 ? void 0 : optionsElement.reportValidity())) {
23261
+ let errorMessage = salla.lang.get('common.messages.required_fields');
23262
+ salla.error(errorMessage);
23263
+ throw errorMessage;
23264
+ }
23265
+ payload.options = [];
23266
+ options.forEach(option => {
23267
+ //inject numbers only, without zeros
23268
+ if (option && !isNaN(option)) {
23269
+ payload.options.push(Number(option));
23270
+ }
23271
+ });
23272
+ return payload;
23273
+ };
23213
23274
  // helpers
23214
- this.typing = (e, submitMethod) => {
23275
+ this.typing = (e) => {
23215
23276
  const error = e.target.nextElementSibling;
23216
23277
  e.target.classList.remove('s-has-error');
23217
23278
  (error === null || error === void 0 ? void 0 : error.classList.contains('s-product-availability-error-msg')) && (error.innerText = '');
23218
- e.key == 'Enter' && submitMethod();
23279
+ e.keyCode === 13 && this.submit();
23219
23280
  };
23220
23281
  salla.lang.onLoaded(() => {
23221
23282
  var _a;
@@ -23237,28 +23298,6 @@ const SallaProductAvailability = class {
23237
23298
  channelsWatcher(newValue) {
23238
23299
  this.channels_ = !!newValue ? newValue.split(',') : [];
23239
23300
  }
23240
- async handleSubmitOptions() {
23241
- let payload = { id: this.productId };
23242
- if (!this.notifyOptionsAvailability) {
23243
- return payload;
23244
- }
23245
- let optionsElement = document.querySelector(`salla-product-options[product-id="${this.productId}"]`);
23246
- let options = Object.values(await (optionsElement === null || optionsElement === void 0 ? void 0 : optionsElement.getSelectedOptionsData()) || {});
23247
- //if all options not selected, show message && throw exception
23248
- if (options.length && !await (optionsElement === null || optionsElement === void 0 ? void 0 : optionsElement.reportValidity())) {
23249
- let errorMessage = salla.lang.get('common.messages.required_fields');
23250
- salla.error(errorMessage);
23251
- throw errorMessage;
23252
- }
23253
- payload.options = [];
23254
- options.forEach(option => {
23255
- //inject numbers only, without zeros
23256
- if (option && !isNaN(option)) {
23257
- payload.options.push(Number(option));
23258
- }
23259
- });
23260
- return payload;
23261
- }
23262
23301
  openModel() {
23263
23302
  this.handleSubmitOptions().then(isSuccess => isSuccess ? this.modal.open() : null);
23264
23303
  }
@@ -23281,9 +23320,23 @@ const SallaProductAvailability = class {
23281
23320
  .then(() => this.btn.disable())
23282
23321
  .then(() => salla.api.product.availabilitySubscribe(payload))
23283
23322
  .then(() => {
23323
+ if (!this.notifyOptionsAvailability) {
23324
+ salla.storage.set(`product-${this.productId}-subscribed`, true);
23325
+ return;
23326
+ }
23327
+ if (payload.options.length) {
23328
+ let options = salla.storage.get(`product-${this.productId}-subscribed-options`) || [];
23329
+ let selectedOptionsString = payload.options.join(',');
23330
+ if (!options.includes(selectedOptionsString)) {
23331
+ options.push(selectedOptionsString);
23332
+ salla.storage.set(`product-${this.productId}-subscribed-options`, options);
23333
+ }
23334
+ else {
23335
+ salla.log('already subscribed to this options');
23336
+ }
23337
+ }
23284
23338
  this.isSubscribed = true;
23285
- salla.storage.set(`product-${this.productId}-subscribed`, true);
23286
- }) //no need to wait until finishing alert animation
23339
+ })
23287
23340
  .then(() => this.btn.stop())
23288
23341
  .then(() => this.modal.close())
23289
23342
  .catch(() => this.btn.stop() && this.btn.enable());
@@ -23319,11 +23372,11 @@ const SallaProductAvailability = class {
23319
23372
  renderModal() {
23320
23373
  return (index.h("salla-modal", { ref: modal => this.modal = modal, "modal-title": this.title_, subTitle: salla.lang.get('pages.products.notify_availability_subtitle'), width: "sm" }, index.h("span", { slot: 'icon', class: "s-product-availability-header-icon", innerHTML: BellRing }), index.h("div", { class: "s-product-availability-body" }, this.channels_.includes('email') ? [
23321
23374
  index.h("label", { class: "s-product-availability-label" }, salla.lang.get('common.elements.email')),
23322
- index.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" }),
23375
+ index.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" }),
23323
23376
  index.h("span", { class: "s-product-availability-error-msg" })
23324
23377
  ] : '', this.channels_.includes('sms') ? [
23325
23378
  index.h("label", { class: "s-product-availability-label" }, salla.lang.get('common.elements.mobile')),
23326
- index.h("salla-tel-input", { ref: el => this.mobileInput = el, onKeyDown: e => this.typing(e, this.submit) })
23379
+ index.h("salla-tel-input", { ref: el => this.mobileInput = el, onKeyDown: e => this.typing(e) })
23327
23380
  ] : ''), index.h("div", { slot: "footer", class: "s-product-availability-footer" }, index.h("salla-button", { class: "modal-cancel-btn", width: "wide", color: "light", fill: "outline", onClick: () => this.modal.close() }, salla.lang.get('common.elements.cancel')), index.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')))));
23328
23381
  }
23329
23382
  get host() { return index.getElement(this); }
@@ -114,7 +114,7 @@ const SallaProductOptions = class {
114
114
  return selectedOptions;
115
115
  }
116
116
  /**
117
- * Get the id's of the selected options.
117
+ * Report options form validity.
118
118
  * */
119
119
  async reportValidity() {
120
120
  let requiredElements = this.host.querySelectorAll('[required]');
@@ -145,6 +145,12 @@ const SallaProductOptions = class {
145
145
  async getOption(option_id) {
146
146
  return this.optionsData.find(option => option.id === option_id);
147
147
  }
148
+ // @ts-ignore
149
+ invalidHandler(event, option) {
150
+ const closestProductOption = event.target.closest('.s-product-options-option');
151
+ closestProductOption.scrollIntoView({ behavior: 'smooth', block: 'center' });
152
+ closestProductOption.classList.add('s-product-options-option-error');
153
+ }
148
154
  changedHandler(event, option) {
149
155
  let data = { event: event, option: option, detail: null };
150
156
  if (option.details) {
@@ -153,6 +159,10 @@ const SallaProductOptions = class {
153
159
  });
154
160
  data.detail = detail;
155
161
  }
162
+ let optionElement = event.target.closest('.s-product-options-option');
163
+ if (event.target.value || event.type == 'added') {
164
+ optionElement.classList.remove('s-product-options-option-error');
165
+ }
156
166
  const index = this.selectedOptions.findIndex(option => option.option_id === data.option.id);
157
167
  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 }));
158
168
  this.setSelectedSkus();
@@ -213,16 +223,16 @@ const SallaProductOptions = class {
213
223
  }
214
224
  return (index.h(index.Host, { class: "s-product-options-wrapper" }, index.h("salla-conditional-fields", null, this.optionsData.map((option) => index.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' ?
215
225
  this.splitterOption()
216
- : index.h("div", { class: "s-product-options-option" }, index.h("label", { htmlFor: 'options[' + option.id + ']', class: "s-product-options-option-label" }, index.h("b", null, option.name, option.required && index.h("span", null, " * "), " "), index.h("small", null, option.placeholder)), index.h("div", { class: "s-product-options-option-content" }, this.getDisplayForType(option))))))));
226
+ : index.h("div", { class: "s-product-options-option", "data-option-type": option.type, "data-option-required": `${option.required}` }, index.h("label", { htmlFor: 'options[' + option.id + ']', class: "s-product-options-option-label" }, index.h("b", null, option.name, option.required && index.h("span", null, " * "), " "), index.h("small", null, option.placeholder)), index.h("div", { class: "s-product-options-option-content" }, this.getDisplayForType(option))))))));
217
227
  }
218
228
  //@ts-ignore
219
229
  donationOption(option, product) {
220
230
  return index.h("div", { class: "s-product-options-donation-wrapper" }, option.donation ?
221
231
  index.h("div", { class: "s-product-options-donation-progress" }, index.h("salla-progress-bar", { donation: option.donation }))
222
- : '', index.h("div", { class: "s-product-options-donation-input-group" }, index.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) }), index.h("span", { class: "s-product-options-donation-amount-currency" }, salla.config.currency(salla.config.get('user.currency_code')).symbol)));
232
+ : '', index.h("div", { class: "s-product-options-donation-input-group" }, index.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) }), index.h("span", { class: "s-product-options-donation-amount-currency" }, salla.config.currency(salla.config.get('user.currency_code')).symbol)));
223
233
  }
224
234
  fileUploader(option, additions = null) {
225
- return index.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 } }), index.h("div", { class: "s-product-options-filepond-placeholder" }, index.h("span", { class: "s-product-options-filepond-placeholder-icon", innerHTML: additions.accept && additions.accept.split(',').every(type => type.includes('image'))
235
+ return index.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 } }), index.h("div", { class: "s-product-options-filepond-placeholder" }, index.h("span", { class: "s-product-options-filepond-placeholder-icon", innerHTML: additions.accept && additions.accept.split(',').every(type => type.includes('image'))
226
236
  ? camera.CameraIcon
227
237
  : FileIcon }), index.h("p", { class: "s-product-options-filepond-placeholder-text" }, salla.lang.get('common.uploader.drag_and_drop')), index.h("span", { class: "filepond--label-action" }, salla.lang.get('common.uploader.browse'))));
228
238
  }
@@ -239,7 +249,7 @@ const SallaProductOptions = class {
239
249
  }
240
250
  //@ts-ignore
241
251
  numberOption(option) {
242
- return index.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) });
252
+ return index.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) });
243
253
  }
244
254
  //@ts-ignore
245
255
  splitterOption() {
@@ -247,41 +257,40 @@ const SallaProductOptions = class {
247
257
  }
248
258
  //@ts-ignore
249
259
  textOption(option) {
250
- return index.h("div", { class: "s-product-options-text" }, index.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) }));
260
+ return index.h("div", { class: "s-product-options-text" }, index.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) }));
251
261
  }
252
262
  //@ts-ignore
253
263
  textareaOption(option) {
254
264
  //todo::remove mt-1 class, and if it's okay to remove the tag itself will be great
255
- return index.h("div", { class: "s-product-options-textarea" }, index.h("div", { class: "mt-1" }, index.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) })));
265
+ return index.h("div", { class: "s-product-options-textarea" }, index.h("div", { class: "mt-1" }, index.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) })));
256
266
  }
257
267
  //@ts-ignore
258
268
  mapOption(option) {
259
- return index.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) });
269
+ return index.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) });
260
270
  }
261
271
  colorPickerOption(option) {
262
- return index.h("salla-color-picker", { onSubmitted: e => this.changedHandler(e, option), name: `options[${option.id}]`, required: option.required, color: option.value || '#5dd5c4' });
272
+ return index.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' });
263
273
  }
264
274
  /**
265
275
  * ============= Date Time options =============
266
276
  */
267
277
  //@ts-ignore
268
278
  timeOption(option) {
269
- return index.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) });
279
+ return index.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) });
270
280
  }
271
281
  //@ts-ignore
272
282
  dateOption(option) {
273
283
  //todo:: consider date-range @see https://github.com/SallaApp/theme-raed/blob/master/src/assets/js/partials/product-options.js#L8-L23
274
- return index.h("div", { class: "s-product-options-date-element" }, index.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) }));
284
+ return index.h("div", { class: "s-product-options-date-element" }, index.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) }));
275
285
  }
276
286
  //@ts-ignore
277
287
  datetimeOption(option) {
278
288
  //todo:: consider date-range @see https://github.com/SallaApp/theme-raed/blob/master/src/assets/js/partials/product-options.js#L8-L23
279
- return index.h("div", { class: "s-product-options-datetime-element" }, index.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) }));
289
+ return index.h("div", { class: "s-product-options-datetime-element" }, index.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) }));
280
290
  }
281
291
  /**
282
292
  * ============= Advanced options =============
283
293
  */
284
- // <<<<<<< HEAD
285
294
  getOptionDetailName(detail, outOfStock = true, optionType) {
286
295
  if (optionType && optionType == DisplayType.COLOR) {
287
296
  return detail.name
@@ -311,24 +320,24 @@ const SallaProductOptions = class {
311
320
  return this.selectedOptions.some(option => option.is_out && option.option_id !== detail.option_id);
312
321
  }
313
322
  singleOption(option) {
314
- return index.h("div", null, index.h("select", { name: `options[${option.id}]`, required: option.required, class: "s-form-control", onChange: e => this.changedHandler(e, option) }, index.h("option", { value: "" }, option.placeholder), option === null || option === void 0 ? void 0 :
323
+ return index.h("div", null, index.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) }, index.h("option", { value: "" }, option.placeholder), option === null || option === void 0 ? void 0 :
315
324
  option.details.map((detail) => {
316
325
  return index.h("option", { value: detail.id, disabled: this.canDisabled && this.isOptionDetailOut(detail), selected: detail.is_selected }, this.getOptionDetailName(detail));
317
326
  })));
318
327
  }
319
328
  multipleOptions(option) {
320
329
  return index.h("div", { class: { "s-product-options-multiple-options-wrapper": true, 'required': option.required } }, option === null || option === void 0 ? void 0 : option.details.map((detail) => {
321
- return index.h("div", null, index.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` }), index.h("label", { htmlFor: `field-${option.id}-${detail.id}` }, this.getOptionDetailName(detail)));
330
+ return index.h("div", null, index.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` }), index.h("label", { htmlFor: `field-${option.id}-${detail.id}` }, this.getOptionDetailName(detail)));
322
331
  }));
323
332
  }
324
333
  //@ts-ignore
325
334
  colorOption(option) {
326
- return index.h("fieldset", { class: "s-product-options-colors-wrapper" }, option === null || option === void 0 ? void 0 : option.details.map((detail) => index.h("div", { class: "s-product-options-colors-item" }, index.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) }), index.h("label", { htmlFor: `color-${this.productId}-${option.id}-${detail.id}` }, index.h("span", { style: { "background-color": detail.color } }), index.h("div", { innerHTML: this.getOptionDetailName(detail, true, option.type) })))));
335
+ return index.h("fieldset", { class: "s-product-options-colors-wrapper" }, option === null || option === void 0 ? void 0 : option.details.map((detail) => index.h("div", { class: "s-product-options-colors-item" }, index.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) }), index.h("label", { htmlFor: `color-${this.productId}-${option.id}-${detail.id}` }, index.h("span", { style: { "background-color": detail.color } }), index.h("div", { innerHTML: this.getOptionDetailName(detail, true, option.type) })))));
327
336
  }
328
337
  //@ts-ignore
329
338
  thumbnailOption(option) {
330
339
  return index.h("div", { class: "s-product-options-thumbnails-wrapper" }, option.details.map((detail) => {
331
- return index.h("div", null, index.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) }), index.h("label", { htmlFor: `option_${this.productId}-${option.id}_${detail.id}`, "data-img-id": detail.option_value, class: "go-to-slide" }, index.h("img", { "data-src": detail.image, src: detail.image, title: detail.name, alt: detail.name }), index.h("span", { innerHTML: CheckCircleIcon, class: "s-product-options-thumbnails-icon" }), this.isOptionDetailOut(detail) ?
340
+ return index.h("div", null, index.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) }), index.h("label", { htmlFor: `option_${this.productId}-${option.id}_${detail.id}`, "data-img-id": detail.option_value, class: "go-to-slide" }, index.h("img", { "data-src": detail.image, src: detail.image, title: detail.name, alt: detail.name }), index.h("span", { innerHTML: CheckCircleIcon, class: "s-product-options-thumbnails-icon" }), this.isOptionDetailOut(detail) ?
332
341
  [
333
342
  index.h("small", { class: "s-product-options-thumbnails-stock-badge" }, this.outOfStockText),
334
343
  this.canDisabled ? index.h("div", { class: "s-product-options-thumbnails-badge-overlay" }) : '',
@@ -82,11 +82,15 @@ export class SallaAddProductButton {
82
82
  }
83
83
  if (this.hasSubscribedOptions) {
84
84
  return h(Host, null,
85
- h("salla-product-availability", Object.assign({}, this.getBtnAttributes(), { "is-subscribed": true })));
85
+ h("salla-product-availability", Object.assign({}, this.getBtnAttributes(), { "is-subscribed": true }),
86
+ h("span", { class: "s-hidden" },
87
+ h("slot", null))));
86
88
  }
87
89
  if ((this.productStatus === 'out-and-notify' && this.channels) || this.hasOutOfStockOption) {
88
90
  return h(Host, null,
89
- h("salla-product-availability", Object.assign({}, this.getBtnAttributes())));
91
+ h("salla-product-availability", Object.assign({}, this.getBtnAttributes()),
92
+ h("span", { class: "s-hidden" },
93
+ h("slot", null))));
90
94
  }
91
95
  return h(Host, null,
92
96
  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" }),
@@ -97,19 +101,20 @@ export class SallaAddProductButton {
97
101
  return;
98
102
  }
99
103
  salla.event.on('product-options::change', async (data) => {
100
- var _a;
104
+ var _a, _b;
101
105
  if (!['thumbnail', 'color', 'single-option'].includes(data.option.type)) {
102
106
  return;
103
107
  }
104
- this.hasOutOfStockOption = await ((_a = document.querySelector(`salla-product-options[product-id="${this.productId}"]`)) === null || _a === void 0 ? void 0 : _a.hasOutOfStockOption());
108
+ this.hasSubscribedOptions = false;
105
109
  this.hasLabel = false;
106
- if (!this.subscribedOptions || this.subscribedOptions === 'null') {
110
+ this.selectedOptions = await ((_a = document.querySelector(`salla-product-options[product-id="${this.productId}"]`)) === null || _a === void 0 ? void 0 : _a.getSelectedOptions());
111
+ this.hasOutOfStockOption = await ((_b = document.querySelector(`salla-product-options[product-id="${this.productId}"]`)) === null || _b === void 0 ? void 0 : _b.hasOutOfStockOption());
112
+ let subscribedDetails = salla.storage.get(`product-${this.productId}-subscribed-options`);
113
+ if (!subscribedDetails && !this.subscribedOptions || !this.hasOutOfStockOption) {
107
114
  return;
108
115
  }
109
- //check if subscribedOptions are the selected now
110
- this.hasSubscribedOptions = JSON.parse(this.subscribedOptions)
111
- .filter(ids => ids.every(id => this.selectedOptions.some(option => option.id === id)))
112
- .length > 0;
116
+ const parsedSubscribedDetails = subscribedDetails ? subscribedDetails.map(ids => ids.split(',').map(id => parseInt(id))) : [];
117
+ this.hasSubscribedOptions = parsedSubscribedDetails.length > 0 && parsedSubscribedDetails.some(ids => ids.every(id => this.selectedOptions.some(option => option.id === id)));
113
118
  });
114
119
  }
115
120
  componentDidRender() {
@@ -109,6 +109,13 @@ export class SallaColorPicker {
109
109
  componentDidLoad() {
110
110
  this.canvas.style.backgroundColor = this.color;
111
111
  this.initColorPicker();
112
+ this.colorInput.addEventListener('invalid', e => {
113
+ this.invalidInput.emit(e);
114
+ });
115
+ this.colorInput.addEventListener('input', () => {
116
+ this.colorInput.setCustomValidity('');
117
+ this.colorInput.reportValidity();
118
+ });
112
119
  }
113
120
  setPopIpPosition() {
114
121
  const popup = this.host.querySelector('.picker_wrapper');
@@ -311,6 +318,21 @@ export class SallaColorPicker {
311
318
  }
312
319
  }
313
320
  }
321
+ }, {
322
+ "method": "invalidInput",
323
+ "name": "invalidInput",
324
+ "bubbles": true,
325
+ "cancelable": true,
326
+ "composed": true,
327
+ "docs": {
328
+ "tags": [],
329
+ "text": "Event emitted when the input is invalid."
330
+ },
331
+ "complexType": {
332
+ "original": "any",
333
+ "resolved": "any",
334
+ "references": {}
335
+ }
314
336
  }, {
315
337
  "method": "submitted",
316
338
  "name": "submitted",
@@ -230,6 +230,13 @@ export class SallaDatetimePicker {
230
230
  // onClose: this.handleOnClose(selectedDates, dateStr, instance)
231
231
  };
232
232
  flatpickr(this.dateInput, options);
233
+ this.dateInput.addEventListener('invalid', e => {
234
+ this.invalidInput.emit(e);
235
+ });
236
+ this.dateInput.addEventListener('input', () => {
237
+ this.dateInput.setCustomValidity('');
238
+ this.dateInput.reportValidity();
239
+ });
233
240
  }
234
241
  static get is() { return "salla-datetime-picker"; }
235
242
  static get originalStyleUrls() { return {
@@ -1163,7 +1170,22 @@ export class SallaDatetimePicker {
1163
1170
  "composed": true,
1164
1171
  "docs": {
1165
1172
  "tags": [],
1166
- "text": "Event emitted when the file input gets changed by the user when selecting file(s)."
1173
+ "text": "Event emitted when the date input gets changed by the user when selecting file(s)."
1174
+ },
1175
+ "complexType": {
1176
+ "original": "any",
1177
+ "resolved": "any",
1178
+ "references": {}
1179
+ }
1180
+ }, {
1181
+ "method": "invalidInput",
1182
+ "name": "invalidInput",
1183
+ "bubbles": true,
1184
+ "cancelable": true,
1185
+ "composed": true,
1186
+ "docs": {
1187
+ "tags": [],
1188
+ "text": "Event emitted when the input is invalid."
1167
1189
  },
1168
1190
  "complexType": {
1169
1191
  "original": "any",
@@ -169,15 +169,18 @@ export class SallaFileUpload {
169
169
  return this.uploaded.emit(this.value);
170
170
  }
171
171
  removedHandler(deletedFile) {
172
+ var _a;
172
173
  let fileInput = this.getFormDataFileInput();
173
174
  fileInput.type = 'hidden';
174
175
  fileInput.value = '';
176
+ (_a = this.host.closest('.s-product-options-option')) === null || _a === void 0 ? void 0 : _a.removeAttribute('data-has-value');
175
177
  if (deletedFile.getMetadata('id')) {
176
178
  salla.cart.api.deleteImage(deletedFile.getMetadata('id'));
177
179
  }
178
180
  if (this.height) {
179
181
  setTimeout(() => this.host.querySelector('.filepond--root').style.height = this.height, 1000);
180
182
  }
183
+ this.hiddenInput.value = '';
181
184
  fileInput.dispatchEvent(new window.Event('change', { bubbles: true }));
182
185
  return this.removed.emit(deletedFile);
183
186
  }
@@ -210,6 +213,7 @@ export class SallaFileUpload {
210
213
  return this.host.querySelector('.filepond--data input');
211
214
  }
212
215
  getFiles() {
216
+ var _a;
213
217
  if (!this.value && !this.files) {
214
218
  return [];
215
219
  }
@@ -217,6 +221,9 @@ export class SallaFileUpload {
217
221
  let files = this.files
218
222
  ? JSON.parse(this.files)
219
223
  : this.value.split(',').map(file => ({ url: file }));
224
+ if (files.length) {
225
+ (_a = this.host.closest('.s-product-options-option')) === null || _a === void 0 ? void 0 : _a.setAttribute('data-has-value', 'true');
226
+ }
220
227
  return files.map(file => ({
221
228
  source: file.id ? `${file.id}` : file.url,
222
229
  options: {
@@ -245,7 +252,8 @@ export class SallaFileUpload {
245
252
  "s-file-upload": true,
246
253
  "s-file-upload-profile-image": this.profileImage,
247
254
  } },
248
- 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 })));
255
+ 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 }),
256
+ h("input", { class: "s-file-upload-hidden-input", name: 'hidden-' + this.name, required: this.required, value: this.value, ref: input => this.hiddenInput = input })));
249
257
  }
250
258
  componentDidLoad() {
251
259
  let files = this.getFiles();
@@ -337,6 +345,13 @@ export class SallaFileUpload {
337
345
  resolve(true);
338
346
  }),
339
347
  });
348
+ this.hiddenInput.addEventListener('invalid', e => {
349
+ this.invalidInput.emit(e);
350
+ });
351
+ this.hiddenInput.addEventListener('change', () => {
352
+ this.hiddenInput.setCustomValidity('');
353
+ this.hiddenInput.reportValidity();
354
+ });
340
355
  }
341
356
  static get is() { return "salla-file-upload"; }
342
357
  static get originalStyleUrls() { return {
@@ -1223,6 +1238,21 @@ export class SallaFileUpload {
1223
1238
  }
1224
1239
  }
1225
1240
  }
1241
+ }, {
1242
+ "method": "invalidInput",
1243
+ "name": "invalidInput",
1244
+ "bubbles": true,
1245
+ "cancelable": true,
1246
+ "composed": true,
1247
+ "docs": {
1248
+ "tags": [],
1249
+ "text": "Event emitted when the input is invalid"
1250
+ },
1251
+ "complexType": {
1252
+ "original": "any",
1253
+ "resolved": "any",
1254
+ "references": {}
1255
+ }
1226
1256
  }, {
1227
1257
  "method": "uploaded",
1228
1258
  "name": "uploaded",