@schukai/monster 4.148.6 → 4.148.7

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.148.6"}
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.148.7"}
@@ -2110,6 +2110,7 @@ function finishTrackedFetch(token) {
2110
2110
  function fetchIt(url, controlOptions) {
2111
2111
  const self = this;
2112
2112
  const lifecycleVersion = getRemoteLifecycleVersion.call(this);
2113
+ const lazyLoadRequest = controlOptions?.lazyLoad === true;
2113
2114
 
2114
2115
  if (url instanceof URL) {
2115
2116
  url = url.toString();
@@ -2144,6 +2145,11 @@ function fetchIt(url, controlOptions) {
2144
2145
  }
2145
2146
 
2146
2147
  new Processing(10, () => {
2148
+ if (lazyLoadRequest && shouldLazyLoadOptions.call(this) !== true) {
2149
+ resolve(false);
2150
+ return;
2151
+ }
2152
+
2147
2153
  fetchData
2148
2154
  .call(this, url)
2149
2155
  .then((map) => {
@@ -2190,7 +2196,7 @@ function fetchIt(url, controlOptions) {
2190
2196
  updatePopper.call(this);
2191
2197
  setStatusOrRemoveBadges.call(this, "closed");
2192
2198
 
2193
- resolve();
2199
+ resolve(lazyLoadRequest ? true : undefined);
2194
2200
  });
2195
2201
 
2196
2202
  return;
@@ -4001,10 +4007,7 @@ function handleFilterKeyboardEvents(event) {
4001
4007
  event.preventDefault();
4002
4008
  break;
4003
4009
  default:
4004
- if (
4005
- this.getOption("features.lazyLoad") === true &&
4006
- this[lazyLoadDoneSymbol] !== true
4007
- ) {
4010
+ if (shouldLazyLoadOptions.call(this)) {
4008
4011
  this.click();
4009
4012
  }
4010
4013
  }
@@ -4322,6 +4325,14 @@ function getFilterMode() {
4322
4325
  }
4323
4326
  }
4324
4327
 
4328
+ function shouldLazyLoadOptions() {
4329
+ return (
4330
+ getFilterMode.call(this) !== FILTER_MODE_REMOTE &&
4331
+ this.getOption("features.lazyLoad") === true &&
4332
+ this[lazyLoadDoneSymbol] !== true
4333
+ );
4334
+ }
4335
+
4325
4336
  function getCurrentFilterValue() {
4326
4337
  if (this.getOption("filter.position") === FILTER_POSITION_INLINE) {
4327
4338
  if (this[inlineFilterElementSymbol] instanceof HTMLInputElement) {
@@ -4652,8 +4663,7 @@ function areOptionsAvailableAndInitInternal() {
4652
4663
  msg = this.getOption("labels.cannot-be-loaded");
4653
4664
  } else if (
4654
4665
  this.getOption("url") !== null &&
4655
- this.getOption("features.lazyLoad") === true &&
4656
- this[lazyLoadDoneSymbol] !== true
4666
+ shouldLazyLoadOptions.call(this)
4657
4667
  ) {
4658
4668
  msg = this.getOption("labels.click-to-load-options");
4659
4669
  }
@@ -5122,10 +5132,7 @@ function show() {
5122
5132
 
5123
5133
  focusFilter.call(this);
5124
5134
 
5125
- const lazyLoadFlag =
5126
- this.getOption("features.lazyLoad") && this[lazyLoadDoneSymbol] !== true;
5127
-
5128
- if (lazyLoadFlag === true) {
5135
+ if (shouldLazyLoadOptions.call(this)) {
5129
5136
  if (this[lazyLoadRequestSymbol] instanceof Promise) {
5130
5137
  return;
5131
5138
  }
@@ -5141,17 +5148,20 @@ function show() {
5141
5148
  const lazyLoadRequest = new Processing(200, () => {
5142
5149
  if (
5143
5150
  !isRemoteLifecycleCurrent.call(this, lifecycleVersion) ||
5144
- this.isConnected !== true
5151
+ this.isConnected !== true ||
5152
+ shouldLazyLoadOptions.call(this) !== true
5145
5153
  ) {
5146
- return;
5154
+ return false;
5147
5155
  }
5148
5156
 
5149
- return this.fetch();
5157
+ return fetchIt.call(this, undefined, {
5158
+ lazyLoad: true,
5159
+ });
5150
5160
  }).run();
5151
5161
  this[lazyLoadRequestSymbol] = lazyLoadRequest;
5152
5162
 
5153
5163
  lazyLoadRequest
5154
- .then(() => {
5164
+ .then((lazyLoadCompleted) => {
5155
5165
  if (
5156
5166
  this[lazyLoadRequestSymbol] !== lazyLoadRequest ||
5157
5167
  !isRemoteLifecycleCurrent.call(this, lifecycleVersion) ||
@@ -5161,6 +5171,19 @@ function show() {
5161
5171
  }
5162
5172
 
5163
5173
  delete this[lazyLoadRequestSymbol];
5174
+ if (lazyLoadCompleted !== true) {
5175
+ checkOptionState.call(this);
5176
+ requestAnimationFrame(() => {
5177
+ if (
5178
+ isRemoteLifecycleCurrent.call(this, lifecycleVersion) &&
5179
+ this.isConnected === true
5180
+ ) {
5181
+ show.call(this);
5182
+ }
5183
+ });
5184
+ return;
5185
+ }
5186
+
5164
5187
  this[lazyLoadDoneSymbol] = true;
5165
5188
  delete this[lazyLoadErrorSymbol];
5166
5189
  checkOptionState.call(this);
@@ -97,6 +97,23 @@ function configureRemotePaginatedSelect(select) {
97
97
  select.setOption('mapping.objectsPerPage', 'pagination.perPage');
98
98
  }
99
99
 
100
+ function configureInteractiveRemoteSelect(select) {
101
+ configureRemotePaginatedSelect(select);
102
+ select.setOption('filter.position', 'popper');
103
+ select.setOption('filter.defaultValue', '');
104
+ }
105
+
106
+ function createRemotePageResponse() {
107
+ return createJsonResponse({
108
+ items: [{id: 'alpha', name: 'Alpha'}],
109
+ pagination: {
110
+ total: 1,
111
+ page: 1,
112
+ perPage: 10
113
+ }
114
+ });
115
+ }
116
+
100
117
  function configureLazySelect(select) {
101
118
  select.setOption('url', 'https://example.com/lazy-options');
102
119
  select.setOption('features.lazyLoad', true);
@@ -660,6 +677,128 @@ describe('Select', function () {
660
677
  global['fetch'] = fetchReference;
661
678
  });
662
679
 
680
+ it('should use the remote lifecycle when lazy loading is configured before connection', async function () {
681
+ const requests = [];
682
+ global['fetch'] = function (url) {
683
+ requests.push(String(url));
684
+ return createRemotePageResponse();
685
+ };
686
+
687
+ const mocks = document.getElementById('mocks');
688
+ const select = document.createElement('monster-select');
689
+ configureInteractiveRemoteSelect(select);
690
+ select.setOption('features.lazyLoad', true);
691
+ mocks.appendChild(select);
692
+
693
+ await waitForCondition(() => {
694
+ return select.shadowRoot.querySelector('[data-monster-role=container]') instanceof HTMLElement;
695
+ });
696
+
697
+ expect(select.getOption('features.lazyLoad')).to.equal(false);
698
+ select.shadowRoot.querySelector('[data-monster-role=container]').click();
699
+
700
+ await waitForCondition(() => requests.length === 1);
701
+ expect(requests).to.deep.equal([
702
+ 'https://example.com/items?filter=&page=1'
703
+ ]);
704
+ });
705
+
706
+ it('should ignore lazy loading reapplied to a connected remote select', async function () {
707
+ const requests = [];
708
+ global['fetch'] = function (url) {
709
+ requests.push(String(url));
710
+ return createRemotePageResponse();
711
+ };
712
+
713
+ const mocks = document.getElementById('mocks');
714
+ const select = document.createElement('monster-select');
715
+ configureInteractiveRemoteSelect(select);
716
+ select.setOption('features.lazyLoad', true);
717
+ mocks.appendChild(select);
718
+
719
+ await waitForCondition(() => {
720
+ return select.getOption('features.lazyLoad') === false &&
721
+ select.shadowRoot.querySelector('[data-monster-role=container]') instanceof HTMLElement;
722
+ });
723
+
724
+ select.setOption('features', {
725
+ ...select.getOption('features'),
726
+ lazyLoad: true
727
+ });
728
+ select.setOption('filter', {
729
+ ...select.getOption('filter'),
730
+ mode: 'remote'
731
+ });
732
+
733
+ expect(select.getOption('features.lazyLoad')).to.equal(true);
734
+ select.shadowRoot.querySelector('[data-monster-role=container]').click();
735
+
736
+ await waitForCondition(() => requests.length === 1);
737
+ await new Promise(resolve => setTimeout(resolve, 300));
738
+ expect(requests).to.deep.equal([
739
+ 'https://example.com/items?filter=&page=1'
740
+ ]);
741
+ expect(select.getOption('options').map(option => option.value)).to.deep.equal(['alpha']);
742
+ expect(select.getAttribute('data-monster-error') ?? '').to.not.contain('No options available.');
743
+ });
744
+
745
+ it('should switch from lazy option loading to remote loading before interaction', async function () {
746
+ const requests = [];
747
+ global['fetch'] = function (url) {
748
+ requests.push(String(url));
749
+ return createRemotePageResponse();
750
+ };
751
+
752
+ const mocks = document.getElementById('mocks');
753
+ const select = document.createElement('monster-select');
754
+ configureInteractiveRemoteSelect(select);
755
+ select.setOption('filter.mode', 'options');
756
+ select.setOption('features.lazyLoad', true);
757
+ mocks.appendChild(select);
758
+
759
+ await waitForCondition(() => {
760
+ return select.shadowRoot.querySelector('[data-monster-role=container]') instanceof HTMLElement;
761
+ });
762
+
763
+ select.setOption('filter.mode', 'remote');
764
+ select.shadowRoot.querySelector('[data-monster-role=container]').click();
765
+
766
+ await waitForCondition(() => requests.length === 1);
767
+ expect(requests).to.deep.equal([
768
+ 'https://example.com/items?filter=&page=1'
769
+ ]);
770
+ });
771
+
772
+ it('should cancel a delayed lazy branch after switching to remote mode', async function () {
773
+ const requests = [];
774
+ global['fetch'] = function (url) {
775
+ requests.push(String(url));
776
+ return createRemotePageResponse();
777
+ };
778
+
779
+ const mocks = document.getElementById('mocks');
780
+ const select = document.createElement('monster-select');
781
+ configureInteractiveRemoteSelect(select);
782
+ select.setOption('filter.mode', 'options');
783
+ select.setOption('features.lazyLoad', true);
784
+ mocks.appendChild(select);
785
+
786
+ await waitForCondition(() => {
787
+ return select.shadowRoot.querySelector('[data-monster-role=container]') instanceof HTMLElement;
788
+ });
789
+
790
+ select.shadowRoot.querySelector('[data-monster-role=container]').click();
791
+ select.setOption('filter.mode', 'remote');
792
+
793
+ await waitForCondition(() => requests.length === 1);
794
+ await new Promise(resolve => setTimeout(resolve, 300));
795
+ expect(requests).to.deep.equal([
796
+ 'https://example.com/items?filter=&page=1'
797
+ ]);
798
+ expect(select.getOption('options').map(option => option.value)).to.deep.equal(['alpha']);
799
+ expect(select.getAttribute('data-monster-error') ?? '').to.not.contain('No options available.');
800
+ });
801
+
663
802
  it('should open from the first click after a delayed lazy response renders', async function () {
664
803
  const deferredResponse = createDeferred();
665
804
  let requestCount = 0;
@@ -476,7 +476,7 @@ describe("Tabs", function () {
476
476
  });
477
477
 
478
478
  it("should move overflowing tabs into the popper without losing them", function (done) {
479
- this.timeout(5000);
479
+ this.timeout(15000);
480
480
 
481
481
  let mocks = document.getElementById("mocks");
482
482
  const OriginalResizeObserver = window.ResizeObserver;
@@ -624,7 +624,7 @@ describe("Tabs", function () {
624
624
  });
625
625
 
626
626
  it("should preserve the overflow split and recalculate widths while rebuilding tab buttons", async function () {
627
- this.timeout(5000);
627
+ this.timeout(15000);
628
628
 
629
629
  const mocks = document.getElementById("mocks");
630
630
  restoreBoundingClientRect = installOverflowTabGeometryMock();