@schukai/monster 4.136.10 → 4.136.12

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.
package/package.json CHANGED
@@ -1 +1 @@
1
- {"author":"Volker Schukai","dependencies":{"@floating-ui/dom":"^1.7.6"},"description":"Monster is a simple library for creating fast, robust and lightweight websites.","homepage":"https://monsterjs.org/","keywords":["framework","web","dom","css","sass","mobile-first","app","front-end","templates","schukai","core","shopcloud","alvine","monster","buildmap","stack","observer","observable","uuid","node","nodelist","css-in-js","logger","log","theme"],"license":"AGPL 3.0","main":"source/monster.mjs","module":"source/monster.mjs","name":"@schukai/monster","repository":{"type":"git","url":"https://gitlab.schukai.com/oss/libraries/javascript/monster.git"},"type":"module","version":"4.136.10"}
1
+ {"author":"Volker Schukai","dependencies":{"@floating-ui/dom":"^1.7.6"},"description":"Monster is a simple library for creating fast, robust and lightweight websites.","homepage":"https://monsterjs.org/","keywords":["framework","web","dom","css","sass","mobile-first","app","front-end","templates","schukai","core","shopcloud","alvine","monster","buildmap","stack","observer","observable","uuid","node","nodelist","css-in-js","logger","log","theme"],"license":"AGPL 3.0","main":"source/monster.mjs","module":"source/monster.mjs","name":"@schukai/monster","repository":{"type":"git","url":"https://gitlab.schukai.com/oss/libraries/javascript/monster.git"},"type":"module","version":"4.136.12"}
@@ -1014,7 +1014,7 @@ function processAndApplyPaginationData(data) {
1014
1014
  const mappingCurrentPage = this.getOption("mapping.currentPage");
1015
1015
  const mappingObjectsPerPage = this.getOption("mapping.objectsPerPage");
1016
1016
 
1017
- if (!mappingTotal || !mappingCurrentPage || !mappingObjectsPerPage) {
1017
+ if (!mappingTotal) {
1018
1018
  this.setOption("total", null);
1019
1019
  resetPaginationState.call(this);
1020
1020
  return;
@@ -1023,8 +1023,6 @@ function processAndApplyPaginationData(data) {
1023
1023
  try {
1024
1024
  const pathfinder = new Pathfinder(data);
1025
1025
  const total = pathfinder.getVia(mappingTotal);
1026
- const currentPage = pathfinder.getVia(mappingCurrentPage);
1027
- const objectsPerPage = pathfinder.getVia(mappingObjectsPerPage);
1028
1026
 
1029
1027
  if (!isInteger(total)) {
1030
1028
  addErrorAttribute(this, "total is not an integer");
@@ -1035,8 +1033,16 @@ function processAndApplyPaginationData(data) {
1035
1033
 
1036
1034
  this.setOption("total", total);
1037
1035
 
1036
+ if (!mappingCurrentPage || !mappingObjectsPerPage) {
1037
+ resetPaginationState.call(this, false);
1038
+ return;
1039
+ }
1040
+
1041
+ const currentPage = pathfinder.getVia(mappingCurrentPage);
1042
+ const objectsPerPage = pathfinder.getVia(mappingObjectsPerPage);
1043
+
1038
1044
  if (total === 0) {
1039
- resetPaginationState.call(this);
1045
+ resetPaginationState.call(this, false);
1040
1046
  return;
1041
1047
  }
1042
1048
 
@@ -1120,6 +1126,52 @@ function getSelectionSyncState() {
1120
1126
  return { attrValue, selection, optionsLength };
1121
1127
  }
1122
1128
 
1129
+ function hasSelectionLikeValue(candidate) {
1130
+ if (isArray(candidate)) {
1131
+ return candidate.some((entry) => {
1132
+ if (isObject(entry) && Object.prototype.hasOwnProperty.call(entry, "value")) {
1133
+ return hasSelectionLikeValue.call(this, entry.value);
1134
+ }
1135
+
1136
+ return hasSelectionLikeValue.call(this, entry);
1137
+ });
1138
+ }
1139
+
1140
+ if (isObject(candidate)) {
1141
+ for (const key of ["value", "id", "key"]) {
1142
+ if (Object.prototype.hasOwnProperty.call(candidate, key)) {
1143
+ return hasSelectionLikeValue.call(this, candidate[key]);
1144
+ }
1145
+ }
1146
+ }
1147
+
1148
+ if (candidate === undefined || candidate === null) {
1149
+ return false;
1150
+ }
1151
+
1152
+ if (isValueIsEmpty.call(this, candidate)) {
1153
+ return false;
1154
+ }
1155
+
1156
+ if (isString(candidate)) {
1157
+ return candidate.trim() !== "";
1158
+ }
1159
+
1160
+ return true;
1161
+ }
1162
+
1163
+ function hasRequestedSelectionValue() {
1164
+ const pendingValue = this[pendingSelectionSymbol]?.value;
1165
+ const optionValue = this.getOption("value");
1166
+ const attrValue = this.getAttribute("value");
1167
+
1168
+ return (
1169
+ hasSelectionLikeValue.call(this, pendingValue) ||
1170
+ hasSelectionLikeValue.call(this, optionValue) ||
1171
+ hasSelectionLikeValue.call(this, attrValue)
1172
+ );
1173
+ }
1174
+
1123
1175
  /**
1124
1176
  * @private
1125
1177
  * @param {number} version
@@ -1907,6 +1959,14 @@ function fetchIt(url, controlOptions) {
1907
1959
  this[fetchRequestVersionSymbol] += 1;
1908
1960
  const requestVersion = this[fetchRequestVersionSymbol];
1909
1961
 
1962
+ if (getFilterMode.call(this) === FILTER_MODE_REMOTE) {
1963
+ let classes = new TokenList(this.getOption("classes.noOptions"));
1964
+ classes.add("d-none");
1965
+ this.setOption("classes.noOptions", classes.toString());
1966
+ this.setOption("messages.emptyOptions", "");
1967
+ this.setOption("messages.total", "");
1968
+ }
1969
+
1910
1970
  new Processing(10, () => {
1911
1971
  fetchData
1912
1972
  .call(this, url)
@@ -2712,7 +2772,17 @@ function setTotalText() {
2712
2772
  return;
2713
2773
  }
2714
2774
 
2775
+ if (this[isLoadingSymbol] === true) {
2776
+ this.setOption("messages.total", "");
2777
+ return;
2778
+ }
2779
+
2715
2780
  const count = this.getOption("options").length;
2781
+ if (count === 0) {
2782
+ this.setOption("messages.total", "");
2783
+ return;
2784
+ }
2785
+
2716
2786
  const total = Number.parseInt(this.getOption("total"));
2717
2787
  if (Number.isNaN(total)) {
2718
2788
  this.setOption("messages.total", "");
@@ -2763,7 +2833,7 @@ function setSummaryAndControlText() {
2763
2833
  current === msg ||
2764
2834
  current === null
2765
2835
  ) {
2766
- if (selections.length === 0) {
2836
+ if (selections.length === 0 && hasRequestedSelectionValue.call(this) !== true) {
2767
2837
  this.setOption("messages.control", msg);
2768
2838
  } else {
2769
2839
  this.setOption("messages.control", "");
@@ -3907,8 +3977,12 @@ function areOptionsAvailableAndInitInternal() {
3907
3977
  setStatusOrRemoveBadges.call(this, "empty");
3908
3978
  if (getFilterMode.call(this) === FILTER_MODE_REMOTE) {
3909
3979
  if (this[isLoadingSymbol] !== true) {
3910
- this.setOption("total", null);
3911
- resetPaginationState.call(this);
3980
+ if (isInteger(this.getOption("total"))) {
3981
+ resetPaginationState.call(this, false);
3982
+ } else {
3983
+ this.setOption("total", null);
3984
+ resetPaginationState.call(this);
3985
+ }
3912
3986
  }
3913
3987
  }
3914
3988
 
@@ -3960,7 +4034,7 @@ function areOptionsAvailableAndInitInternal() {
3960
4034
  if (
3961
4035
  selections === undefined ||
3962
4036
  selections === null ||
3963
- selections.length === 0
4037
+ (selections.length === 0 && hasRequestedSelectionValue.call(this) !== true)
3964
4038
  ) {
3965
4039
  this.setOption(
3966
4040
  "messages.control",
@@ -4547,6 +4621,7 @@ function initTotal() {
4547
4621
  }
4548
4622
 
4549
4623
  const fetchOptions = this.getOption("fetch", {});
4624
+ this.setOption("messages.total", "");
4550
4625
 
4551
4626
  const remoteInfoRequest = getGlobal()
4552
4627
  .fetch(url, fetchOptions)
@@ -4592,7 +4667,7 @@ function updatePagination(total, currentPage, objectsPerPage) {
4592
4667
  });
4593
4668
  }
4594
4669
 
4595
- function resetPaginationState() {
4670
+ function resetPaginationState(clearTotalMessage = true) {
4596
4671
  const paginationElement = this[paginationElementSymbol];
4597
4672
  if (!paginationElement) {
4598
4673
  return;
@@ -4602,7 +4677,9 @@ function resetPaginationState() {
4602
4677
  paginationElement.setOption("pages", null);
4603
4678
  paginationElement.setOption("currentPage", null);
4604
4679
  paginationElement.setOption("objectsPerPage", null);
4605
- this.setOption("messages.total", "");
4680
+ if (clearTotalMessage === true) {
4681
+ this.setOption("messages.total", "");
4682
+ }
4606
4683
  }
4607
4684
 
4608
4685
  function clearOptionsOnError() {
@@ -900,6 +900,122 @@ describe('Select', function () {
900
900
  expect(requests.filter((url) => url === remoteInfoUrl)).to.have.length(1);
901
901
  });
902
902
 
903
+ it('should keep total-only remote totals for loaded options', async function () {
904
+ this.timeout(3000);
905
+
906
+ let mocks = document.getElementById('mocks');
907
+
908
+ global['fetch'] = function () {
909
+ return createJsonResponse({
910
+ items: [
911
+ {id: 'alpha', name: 'Alpha'}
912
+ ],
913
+ pagination: {
914
+ total: 1
915
+ }
916
+ });
917
+ };
918
+
919
+ const select = document.createElement('monster-select');
920
+ select.setOption('url', 'https://example.com/items?filter={filter}&page={page}');
921
+ select.setOption('filter.mode', 'remote');
922
+ select.setOption('mapping.selector', 'items.*');
923
+ select.setOption('mapping.labelTemplate', '${name}');
924
+ select.setOption('mapping.valueTemplate', '${id}');
925
+ select.setOption('mapping.total', 'pagination.total');
926
+ mocks.appendChild(select);
927
+
928
+ await waitForCondition(() => {
929
+ return select.shadowRoot.querySelector('[data-monster-role=container]') instanceof HTMLElement;
930
+ });
931
+
932
+ await select.fetch('https://example.com/items?filter=alpha&page=1');
933
+
934
+ expect(select.getOption('total')).to.equal(1);
935
+ expect(select.getOption('messages.total')).to.contain('No additional entries are available');
936
+ });
937
+
938
+ it('should avoid duplicate remote-info badges for empty remote filter results', async function () {
939
+ this.timeout(4000);
940
+
941
+ let mocks = document.getElementById('mocks');
942
+
943
+ global['fetch'] = function () {
944
+ return createJsonResponse({
945
+ items: [],
946
+ pagination: {
947
+ total: 0
948
+ }
949
+ });
950
+ };
951
+
952
+ const select = document.createElement('monster-select');
953
+ select.setOption('url', 'https://example.com/items?filter={filter}&page={page}');
954
+ select.setOption('filter.mode', 'remote');
955
+ select.setOption('filter.position', 'popper');
956
+ select.setOption('mapping.selector', 'items.*');
957
+ select.setOption('mapping.labelTemplate', '${name}');
958
+ select.setOption('mapping.valueTemplate', '${id}');
959
+ select.setOption('mapping.total', 'pagination.total');
960
+ mocks.appendChild(select);
961
+
962
+ await waitForCondition(() => {
963
+ return select.shadowRoot.querySelector('[data-monster-role=filter][name=\"popper-filter\"]') instanceof HTMLInputElement;
964
+ });
965
+
966
+ const container = select.shadowRoot.querySelector('[data-monster-role=container]');
967
+ const filterInput = select.shadowRoot.querySelector('[data-monster-role=filter][name="popper-filter"]');
968
+ filterInput.value = 'alpha';
969
+
970
+ container.click();
971
+
972
+ await waitForCondition(() => {
973
+ return select.getOption('total') === 0;
974
+ });
975
+
976
+ expect(select.getOption('total')).to.equal(0);
977
+ expect(select.getOption('messages.total')).to.equal('');
978
+ expect(select.getOption('messages.emptyOptions')).to.contain('Please consider modifying the filter');
979
+ });
980
+
981
+ it('should not show the select placeholder while a value sync is still pending', async function () {
982
+ this.timeout(3000);
983
+
984
+ let mocks = document.getElementById('mocks');
985
+ const savedGlobalQueueMicrotask = global.queueMicrotask;
986
+ const savedWindowQueueMicrotask = window.queueMicrotask;
987
+ const delayedQueueMicrotask = (callback) => setTimeout(callback, 50);
988
+
989
+ global.queueMicrotask = delayedQueueMicrotask;
990
+ window.queueMicrotask = delayedQueueMicrotask;
991
+
992
+ try {
993
+ const select = document.createElement('monster-select');
994
+ select.setOption('options', [
995
+ {label: 'Alpha', value: 'a'}
996
+ ]);
997
+ select.setAttribute('value', 'a');
998
+ mocks.appendChild(select);
999
+
1000
+ await new Promise((resolve) => setTimeout(resolve, 20));
1001
+
1002
+ expect(select.getOption('messages.control')).to.not.equal(
1003
+ select.getOption('labels.select-an-option')
1004
+ );
1005
+
1006
+ await waitForCondition(() => {
1007
+ return Array.isArray(select.getOption('selection')) &&
1008
+ select.getOption('selection').length === 1;
1009
+ });
1010
+
1011
+ expect(select.value).to.equal('a');
1012
+ expect(select.getOption('messages.control')).to.equal('');
1013
+ } finally {
1014
+ global.queueMicrotask = savedGlobalQueueMicrotask;
1015
+ window.queueMicrotask = savedWindowQueueMicrotask;
1016
+ }
1017
+ });
1018
+
903
1019
  it('should keep empty equivalent matching type-safe for numeric zero', function (done) {
904
1020
  this.timeout(2000);
905
1021