@schukai/monster 4.136.3 → 4.136.5

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.
@@ -33,7 +33,13 @@ let html2 = `
33
33
  </div>
34
34
  `;
35
35
 
36
- let Select, fetchReference;
36
+ let Select,
37
+ getDefaultSelectPopperPositionProfile,
38
+ resolveSelectListDimension,
39
+ resolveSelectPopperWidthConstraints,
40
+ resolveSelectVisibleRect,
41
+ resolveSelectViewportMetrics,
42
+ fetchReference;
37
43
 
38
44
  describe('Select', function () {
39
45
 
@@ -50,6 +56,11 @@ describe('Select', function () {
50
56
 
51
57
  import("../../../../source/components/form/select.mjs").then((m) => {
52
58
  Select = m['Select'];
59
+ getDefaultSelectPopperPositionProfile = m['getDefaultSelectPopperPositionProfile'];
60
+ resolveSelectListDimension = m['resolveSelectListDimension'];
61
+ resolveSelectPopperWidthConstraints = m['resolveSelectPopperWidthConstraints'];
62
+ resolveSelectVisibleRect = m['resolveSelectVisibleRect'];
63
+ resolveSelectViewportMetrics = m['resolveSelectViewportMetrics'];
53
64
  done()
54
65
  }).catch(e => done(e))
55
66
 
@@ -185,6 +196,189 @@ describe('Select', function () {
185
196
 
186
197
  });
187
198
 
199
+ describe('Popper sizing', function () {
200
+ afterEach(() => {
201
+ let mocks = document.getElementById('mocks');
202
+ mocks.innerHTML = "";
203
+ });
204
+
205
+ it('should allow the popper to become wider than a narrow control', function (done) {
206
+ const mocks = document.getElementById('mocks');
207
+ const select = document.createElement('monster-select');
208
+
209
+ select.setOption('options', [
210
+ {label: 'Alpha', value: 'alpha'},
211
+ {label: 'Beta', value: 'beta'}
212
+ ]);
213
+
214
+ mocks.appendChild(select);
215
+
216
+ const shadowRoot = select.shadowRoot;
217
+ const control = shadowRoot.querySelector('[data-monster-role=control]');
218
+ const popper = shadowRoot.querySelector('[data-monster-role=popper]');
219
+
220
+ control.getBoundingClientRect = () => ({
221
+ width: 120,
222
+ height: 36,
223
+ top: 100,
224
+ left: 40,
225
+ right: 160,
226
+ bottom: 136,
227
+ x: 40,
228
+ y: 100
229
+ });
230
+
231
+ setTimeout(() => {
232
+ try {
233
+ shadowRoot.querySelector('[data-monster-role=container]').click();
234
+ setTimeout(() => {
235
+ try {
236
+ expect(popper.style.minWidth).to.equal('');
237
+ expect(popper.dataset.monsterPreferredWidth).to.equal('240');
238
+ done();
239
+ } catch (e) {
240
+ done(e);
241
+ }
242
+ }, 80);
243
+ } catch (e) {
244
+ done(e);
245
+ }
246
+ }, 20);
247
+ });
248
+
249
+ it('should keep the option list height tight for short lists', function () {
250
+ const result = resolveSelectListDimension({
251
+ visibleOptionHeights: [28, 28],
252
+ maxVisibleOptions: 20,
253
+ availableHeight: 500,
254
+ padding: 0,
255
+ margin: 0
256
+ });
257
+
258
+ expect(result.desiredHeight).to.equal(56);
259
+ expect(result.maxHeight).to.equal(500);
260
+ expect(result.overflowY).to.equal('hidden');
261
+ });
262
+
263
+ it('should refresh the content max height when the available popper height grows again', function (done) {
264
+ const mocks = document.getElementById('mocks');
265
+ const select = document.createElement('monster-select');
266
+
267
+ select.setOption('options', Array.from({length: 24}, (_, index) => ({
268
+ label: `Option ${index + 1}`,
269
+ value: `${index + 1}`
270
+ })));
271
+
272
+ mocks.appendChild(select);
273
+
274
+ const shadowRoot = select.shadowRoot;
275
+ const control = shadowRoot.querySelector('[data-monster-role=control]');
276
+ const popper = shadowRoot.querySelector('[data-monster-role=popper]');
277
+ const content = shadowRoot.querySelector('[part=content]');
278
+ const options = shadowRoot.querySelector('[data-monster-role=options]');
279
+
280
+ control.getBoundingClientRect = () => ({
281
+ width: 200,
282
+ height: 36,
283
+ top: 100,
284
+ left: 40,
285
+ right: 240,
286
+ bottom: 136,
287
+ x: 40,
288
+ y: 100
289
+ });
290
+
291
+ setTimeout(() => {
292
+ try {
293
+ shadowRoot.querySelector('[data-monster-role=container]').click();
294
+ setTimeout(() => {
295
+ try {
296
+ content.style.maxHeight = '20px';
297
+ options.style.height = '20px';
298
+ options.style.maxHeight = '20px';
299
+
300
+ select.calcAndSetOptionsDimension();
301
+
302
+ expect(parseFloat(content.style.maxHeight)).to.be.greaterThan(20);
303
+ expect(content.style.maxHeight).to.equal(options.style.maxHeight);
304
+ expect(parseFloat(popper.style.maxHeight)).to.be.at.least(parseFloat(content.style.maxHeight));
305
+ done();
306
+ } catch (e) {
307
+ done(e);
308
+ }
309
+ }, 80);
310
+ } catch (e) {
311
+ done(e);
312
+ }
313
+ }, 20);
314
+ });
315
+
316
+ it('should clamp preferred width to the visible viewport width', function () {
317
+ const result = resolveSelectPopperWidthConstraints({
318
+ controlWidth: 120,
319
+ availableWidth: 176
320
+ });
321
+
322
+ expect(result.preferredWidth).to.equal(240);
323
+ expect(result.maxWidth).to.equal(176);
324
+ });
325
+
326
+ it('should keep start placement while enabling cross-axis shifting', function () {
327
+ const result = getDefaultSelectPopperPositionProfile();
328
+
329
+ expect(result.placement).to.equal('bottom-start');
330
+ expect(result.middleware).to.deep.equal([
331
+ 'flip',
332
+ 'offset:4',
333
+ 'shift:crossAxis',
334
+ 'size'
335
+ ]);
336
+ });
337
+
338
+ it('should prefer the larger live viewport metrics after a resize', function () {
339
+ const result = resolveSelectViewportMetrics({
340
+ layoutWidth: 1400,
341
+ layoutHeight: 900,
342
+ visualWidth: 1024,
343
+ visualHeight: 700,
344
+ offsetLeft: 20,
345
+ offsetTop: 30,
346
+ padding: 12
347
+ });
348
+
349
+ expect(result.width).to.equal(1400);
350
+ expect(result.height).to.equal(900);
351
+ expect(result.left).to.equal(20);
352
+ expect(result.top).to.equal(30);
353
+ expect(result.padding).to.equal(12);
354
+ });
355
+
356
+ it('should intersect viewport and split container for the visible rect', function () {
357
+ const result = resolveSelectVisibleRect({
358
+ viewportMetrics: {
359
+ width: 1400,
360
+ height: 900,
361
+ left: 0,
362
+ top: 0,
363
+ padding: 12
364
+ },
365
+ boundaryRect: {
366
+ left: 100,
367
+ top: 80,
368
+ right: 620,
369
+ bottom: 700
370
+ }
371
+ });
372
+
373
+ expect(result.left).to.equal(100);
374
+ expect(result.top).to.equal(80);
375
+ expect(result.right).to.equal(620);
376
+ expect(result.bottom).to.equal(700);
377
+ expect(result.width).to.equal(520);
378
+ expect(result.height).to.equal(620);
379
+ });
380
+ });
381
+
188
382
  describe('Remote filter pagination', function () {
189
383
  let requestCount = 0;
190
384
 
@@ -549,6 +743,46 @@ describe('Select', function () {
549
743
  }, 50);
550
744
  });
551
745
 
746
+ it('should not throw when IntersectionObserver is unavailable', function (done) {
747
+ this.timeout(2000);
748
+
749
+ let mocks = document.getElementById('mocks');
750
+ const savedIntersectionObserver = global.IntersectionObserver;
751
+ global.IntersectionObserver = undefined;
752
+ window.IntersectionObserver = undefined;
753
+ const failures = [];
754
+ const onError = (event) => {
755
+ failures.push(event?.error || event);
756
+ };
757
+ window.addEventListener('error', onError);
758
+
759
+ const select = document.createElement('monster-select');
760
+ select.setOption('url', 'https://example.com/items?filter={filter}&page={page}');
761
+ select.setOption('filter.mode', 'remote');
762
+ select.setOption('mapping.selector', 'items.*');
763
+ select.setOption('mapping.labelTemplate', '${name}');
764
+ select.setOption('mapping.valueTemplate', '${id}');
765
+ select.setOption('mapping.total', 'pagination.total');
766
+ select.setOption('mapping.currentPage', 'pagination.page');
767
+ select.setOption('mapping.objectsPerPage', 'pagination.perPage');
768
+ select.setOption('selection', [{value: 'alpha'}]);
769
+ mocks.appendChild(select);
770
+
771
+ setTimeout(() => {
772
+ try {
773
+ expect(failures).to.have.length(0);
774
+ } catch (e) {
775
+ return done(e);
776
+ } finally {
777
+ window.removeEventListener('error', onError);
778
+ global.IntersectionObserver = savedIntersectionObserver;
779
+ window.IntersectionObserver = savedIntersectionObserver;
780
+ }
781
+
782
+ done();
783
+ }, 250);
784
+ });
785
+
552
786
  });
553
787
 
554
788
 
@@ -14,6 +14,7 @@ export function setupIntersectionObserverMock(
14
14
  } = {}) {
15
15
 
16
16
  const savedImplementation = window.IntersectionObserver;
17
+ const savedGlobalImplementation = global.IntersectionObserver;
17
18
 
18
19
  let lastObject;
19
20
 
@@ -61,9 +62,10 @@ export function setupIntersectionObserverMock(
61
62
  return {
62
63
  restore: function () {
63
64
  window.IntersectionObserver = savedImplementation;
65
+ global.IntersectionObserver = savedGlobalImplementation;
64
66
  },
65
67
  getInstance: function () {
66
68
  return lastObject;
67
69
  }
68
70
  }
69
- }
71
+ }