@schukai/monster 4.143.8 → 4.143.10

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.143.8"}
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.143.10"}
@@ -275,15 +275,12 @@ class ControlBar extends CustomElement {
275
275
  needsMeasure: true,
276
276
  needsLayout: true,
277
277
  needsObserve: true,
278
- initialLayoutPending: true,
279
- initialLayoutOpacity: undefined,
280
278
  suppressSlotChange: false,
281
279
  suppressMutation: false,
282
280
  suppressResize: false,
283
281
  };
284
282
 
285
283
  initControlReferences.call(this);
286
- hideControlBarUntilInitialLayout.call(this);
287
284
  initEventHandler.call(this);
288
285
 
289
286
  // setup structure
@@ -482,44 +479,6 @@ function isElementSelfHidden(element) {
482
479
  );
483
480
  }
484
481
 
485
- /**
486
- * @private
487
- * @return {void}
488
- */
489
- function hideControlBarUntilInitialLayout() {
490
- const state = this[layoutStateSymbol];
491
- if (!state || !(this[controlBarElementSymbol] instanceof HTMLElement)) {
492
- return;
493
- }
494
-
495
- state.initialLayoutOpacity = this[controlBarElementSymbol].style.opacity;
496
- this[controlBarElementSymbol].style.opacity = "0";
497
- }
498
-
499
- /**
500
- * @private
501
- * @return {void}
502
- */
503
- function revealControlBarAfterInitialLayout() {
504
- const state = this[layoutStateSymbol];
505
- if (
506
- !state ||
507
- state.initialLayoutPending !== true ||
508
- !(this[controlBarElementSymbol] instanceof HTMLElement)
509
- ) {
510
- return;
511
- }
512
-
513
- state.initialLayoutPending = false;
514
- const initialOpacity = state.initialLayoutOpacity;
515
- if (typeof initialOpacity === "string" && initialOpacity !== "") {
516
- this[controlBarElementSymbol].style.opacity = initialOpacity;
517
- return;
518
- }
519
-
520
- this[controlBarElementSymbol].style.removeProperty("opacity");
521
- }
522
-
523
482
  /**
524
483
  * @private
525
484
  */
@@ -734,7 +693,6 @@ function runLayout() {
734
693
  })
735
694
  .finally(() => {
736
695
  state.running = false;
737
- revealControlBarAfterInitialLayout.call(this);
738
696
  if (state.needsObserve || state.needsMeasure || state.needsLayout) {
739
697
  scheduleLayoutFrame.call(this);
740
698
  }
@@ -299,6 +299,7 @@ const optionsMapSymbol = Symbol("optionsMap");
299
299
  const optionsMapVersionSnapshotSymbol = Symbol("optionsMapVersionSnapshot");
300
300
  const selectionVersionSymbol = Symbol("selectionVersion");
301
301
  const closeOnSelectAutoSymbol = Symbol("closeOnSelectAuto");
302
+ const lastFilterValueSymbol = Symbol("lastFilterValue");
302
303
 
303
304
  /**
304
305
  * @private
@@ -3893,22 +3894,28 @@ function handleFilterKeyboardEvents(event) {
3893
3894
  ) {
3894
3895
  this.click();
3895
3896
  }
3896
-
3897
- handleFilterKeyEvents.call(this);
3898
3897
  }
3899
3898
  }
3900
3899
 
3901
3900
  /**
3902
- * Method handleFilterKeyEvents is used to handle filter key events.
3901
+ * Method handleFilterInputEvents is used to handle filter input events.
3903
3902
  * Debounce is used to prevent multiple calls.
3904
3903
  *
3905
3904
  * @function
3906
- * @name handleFilterKeyEvents
3905
+ * @name handleFilterInputEvents
3907
3906
  *
3908
3907
  * @private
3909
3908
  * @return {void} This method does not return anything.
3910
3909
  */
3911
- function handleFilterKeyEvents() {
3910
+ function handleFilterInputEvents() {
3911
+ const filterValue = getCurrentFilterInputValue.call(this);
3912
+
3913
+ if (filterValue === this[lastFilterValueSymbol]) {
3914
+ return;
3915
+ }
3916
+
3917
+ this[lastFilterValueSymbol] = filterValue;
3918
+
3912
3919
  if (this[keyFilterEventSymbol] instanceof DeadMansSwitch) {
3913
3920
  try {
3914
3921
  this[keyFilterEventSymbol].touch();
@@ -3934,6 +3941,27 @@ function handleFilterKeyEvents() {
3934
3941
  });
3935
3942
  }
3936
3943
 
3944
+ /**
3945
+ * @private
3946
+ * @returns {string|undefined}
3947
+ */
3948
+ function getCurrentFilterInputValue() {
3949
+ switch (this.getOption("filter.position")) {
3950
+ case FILTER_POSITION_INLINE:
3951
+ if (this[inlineFilterElementSymbol] instanceof HTMLInputElement) {
3952
+ return this[inlineFilterElementSymbol].value.toLowerCase();
3953
+ }
3954
+ break;
3955
+ case FILTER_POSITION_POPPER:
3956
+ default:
3957
+ if (this[popperFilterElementSymbol] instanceof HTMLInputElement) {
3958
+ return this[popperFilterElementSymbol].value.toLowerCase();
3959
+ }
3960
+ }
3961
+
3962
+ return undefined;
3963
+ }
3964
+
3937
3965
  /**
3938
3966
  * @private
3939
3967
  */
@@ -5336,6 +5364,7 @@ function initEventHandler() {
5336
5364
  element.hasAttribute(ATTRIBUTE_ROLE) &&
5337
5365
  element.getAttribute(ATTRIBUTE_ROLE) === "filter"
5338
5366
  ) {
5367
+ handleFilterInputEvents.call(self);
5339
5368
  }
5340
5369
  }
5341
5370
  };
@@ -88,6 +88,15 @@ describe("ControlBar", function () {
88
88
  );
89
89
  });
90
90
 
91
+ it("should keep the control bar visible while the initial layout is pending", function () {
92
+ const bar = document.getElementById("bar");
93
+ const controlBar = bar.shadowRoot.querySelector(
94
+ '[data-monster-role="control-bar"]',
95
+ );
96
+
97
+ expect(controlBar.style.opacity).to.not.equal("0");
98
+ });
99
+
91
100
  it("should allow configuring the control bar layout alignment to right", function (done) {
92
101
  const bar = document.getElementById("bar-right");
93
102
 
@@ -968,7 +977,7 @@ describe("ControlBar", function () {
968
977
  value: 20,
969
978
  });
970
979
 
971
- expect(controlBar.style.opacity).to.equal("0");
980
+ expect(controlBar.style.opacity).to.not.equal("0");
972
981
 
973
982
  await flushFrames();
974
983
  await new Promise((resolve) => setTimeout(resolve, 0));
@@ -1070,7 +1079,7 @@ describe("ControlBar", function () {
1070
1079
  }
1071
1080
  });
1072
1081
 
1073
- it("should reveal the control bar after reconnecting before initial layout", async function () {
1082
+ it("should keep the control bar visible after reconnecting before initial layout", async function () {
1074
1083
  const originalRequestAnimationFrame = window.requestAnimationFrame;
1075
1084
  const originalGlobalRequestAnimationFrame = globalThis.requestAnimationFrame;
1076
1085
 
@@ -1145,7 +1154,7 @@ describe("ControlBar", function () {
1145
1154
  });
1146
1155
  }
1147
1156
 
1148
- expect(controlBar.style.opacity).to.equal("0");
1157
+ expect(controlBar.style.opacity).to.not.equal("0");
1149
1158
 
1150
1159
  bar.remove();
1151
1160
  wrapper.append(bar);
@@ -2283,12 +2283,10 @@ describe('Select', function () {
2283
2283
  select.setOption('mapping.objectsPerPage', 'pagination.perPage');
2284
2284
  mocks.appendChild(select);
2285
2285
 
2286
- const dispatchFilterKey = (input, code, key) => {
2287
- input.dispatchEvent(new KeyboardEvent('keydown', {
2286
+ const dispatchFilterInput = (input) => {
2287
+ input.dispatchEvent(new Event('input', {
2288
2288
  bubbles: true,
2289
- composed: true,
2290
- code,
2291
- key
2289
+ composed: true
2292
2290
  }));
2293
2291
  };
2294
2292
 
@@ -2304,12 +2302,12 @@ describe('Select', function () {
2304
2302
  expect(requests[0]).to.equal('https://example.com/defaults?page=1');
2305
2303
 
2306
2304
  filterInput.value = 'alpha';
2307
- dispatchFilterKey(filterInput, 'KeyA', 'a');
2305
+ dispatchFilterInput(filterInput);
2308
2306
  await waitForCondition(() => requests.length >= 2);
2309
2307
  expect(requests[1]).to.equal('https://example.com/items?filter=alpha&page=1');
2310
2308
 
2311
2309
  filterInput.value = '';
2312
- dispatchFilterKey(filterInput, 'Backspace', 'Backspace');
2310
+ dispatchFilterInput(filterInput);
2313
2311
  await waitForCondition(() => requests.length >= 3);
2314
2312
  expect(requests[2]).to.equal('https://example.com/defaults?page=1');
2315
2313
 
@@ -2321,6 +2319,129 @@ describe('Select', function () {
2321
2319
  expect(requests[3]).to.equal('https://example.com/defaults?page=1');
2322
2320
  });
2323
2321
 
2322
+ it('should not search when filter cursor keys do not change the value', async function () {
2323
+ this.timeout(5000);
2324
+
2325
+ let mocks = document.getElementById('mocks');
2326
+ const requests = [];
2327
+
2328
+ global['fetch'] = function (url) {
2329
+ requests.push(String(url));
2330
+
2331
+ return createJsonResponse({
2332
+ items: [
2333
+ {id: 'alpha', name: 'Alpha'}
2334
+ ],
2335
+ pagination: {
2336
+ total: 1,
2337
+ page: 1,
2338
+ perPage: 1
2339
+ }
2340
+ });
2341
+ };
2342
+
2343
+ const select = document.createElement('monster-select');
2344
+ select.setOption('url', 'https://example.com/items?filter={filter}&page={page}');
2345
+ select.setOption('filter.mode', 'remote');
2346
+ select.setOption('filter.position', 'popper');
2347
+ select.setOption('mapping.selector', 'items.*');
2348
+ select.setOption('mapping.labelTemplate', '${name}');
2349
+ select.setOption('mapping.valueTemplate', '${id}');
2350
+ select.setOption('mapping.total', 'pagination.total');
2351
+ select.setOption('mapping.currentPage', 'pagination.page');
2352
+ select.setOption('mapping.objectsPerPage', 'pagination.perPage');
2353
+ mocks.appendChild(select);
2354
+
2355
+ await waitForCondition(() => {
2356
+ return select.shadowRoot.querySelector('[data-monster-role=filter][name="popper-filter"]') instanceof HTMLInputElement;
2357
+ });
2358
+
2359
+ const filterInput = select.shadowRoot.querySelector('[data-monster-role=filter][name="popper-filter"]');
2360
+ filterInput.value = 'alpha';
2361
+
2362
+ filterInput.dispatchEvent(new KeyboardEvent('keydown', {
2363
+ bubbles: true,
2364
+ composed: true,
2365
+ code: 'ArrowLeft',
2366
+ key: 'ArrowLeft'
2367
+ }));
2368
+ filterInput.dispatchEvent(new KeyboardEvent('keydown', {
2369
+ bubbles: true,
2370
+ composed: true,
2371
+ code: 'ArrowRight',
2372
+ key: 'ArrowRight'
2373
+ }));
2374
+
2375
+ await new Promise((resolve) => setTimeout(resolve, 260));
2376
+ expect(requests).to.have.length(0);
2377
+
2378
+ filterInput.dispatchEvent(new Event('input', {
2379
+ bubbles: true,
2380
+ composed: true
2381
+ }));
2382
+
2383
+ await waitForCondition(() => requests.length === 1);
2384
+ expect(requests[0]).to.equal('https://example.com/items?filter=alpha&page=1');
2385
+ });
2386
+
2387
+ it('should debounce repeated remote filter input events', async function () {
2388
+ this.timeout(5000);
2389
+
2390
+ let mocks = document.getElementById('mocks');
2391
+ const requests = [];
2392
+
2393
+ global['fetch'] = function (url) {
2394
+ requests.push(String(url));
2395
+
2396
+ return createJsonResponse({
2397
+ items: [
2398
+ {id: 'alpha', name: 'Alpha'}
2399
+ ],
2400
+ pagination: {
2401
+ total: 1,
2402
+ page: 1,
2403
+ perPage: 1
2404
+ }
2405
+ });
2406
+ };
2407
+
2408
+ const select = document.createElement('monster-select');
2409
+ select.setOption('url', 'https://example.com/items?filter={filter}&page={page}');
2410
+ select.setOption('filter.mode', 'remote');
2411
+ select.setOption('filter.position', 'popper');
2412
+ select.setOption('mapping.selector', 'items.*');
2413
+ select.setOption('mapping.labelTemplate', '${name}');
2414
+ select.setOption('mapping.valueTemplate', '${id}');
2415
+ select.setOption('mapping.total', 'pagination.total');
2416
+ select.setOption('mapping.currentPage', 'pagination.page');
2417
+ select.setOption('mapping.objectsPerPage', 'pagination.perPage');
2418
+ mocks.appendChild(select);
2419
+
2420
+ await waitForCondition(() => {
2421
+ return select.shadowRoot.querySelector('[data-monster-role=filter][name="popper-filter"]') instanceof HTMLInputElement;
2422
+ });
2423
+
2424
+ const filterInput = select.shadowRoot.querySelector('[data-monster-role=filter][name="popper-filter"]');
2425
+ const dispatchInput = (value) => {
2426
+ filterInput.value = value;
2427
+ filterInput.dispatchEvent(new Event('input', {
2428
+ bubbles: true,
2429
+ composed: true
2430
+ }));
2431
+ };
2432
+
2433
+ dispatchInput('a');
2434
+ dispatchInput('al');
2435
+ dispatchInput('alp');
2436
+ dispatchInput('alpha');
2437
+
2438
+ await waitForCondition(() => requests.length === 1);
2439
+ expect(requests[0]).to.equal('https://example.com/items?filter=alpha&page=1');
2440
+
2441
+ await new Promise((resolve) => setTimeout(resolve, 260));
2442
+ expect(requests).to.have.length(1);
2443
+ });
2444
+
2324
2445
  it('should keep unresolved lookup values visible and mark their badge', function (done) {
2325
2446
  this.timeout(3000);
2326
2447