@momentum-design/components 0.139.1 → 0.139.3

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.
@@ -12,6 +12,8 @@ const fetchIcon = async (request) => fetch(request).then(response => {
12
12
  }
13
13
  return response;
14
14
  });
15
+ // Map of in-flight fetch promises keyed by URL to deduplicate concurrent requests
16
+ const pendingFetches = new Map();
15
17
  /**
16
18
  * Fetches a dynamic SVG icon based on the provided `url`, `name` and `fileExtension`.
17
19
  * The fetch is aborted if the signal is aborted.
@@ -37,7 +39,8 @@ const svgFetch = async ({ url, name, fileExtension, cacheStrategy, cacheName, re
37
39
  // abort the previous fetch request if it is still pending
38
40
  // and create a new signal
39
41
  const signal = renewSignal();
40
- const request = new Request(`${url}/${name}.${fileExtension}`, {
42
+ const iconUrl = `${url}/${name}.${fileExtension}`;
43
+ const request = new Request(iconUrl, {
41
44
  signal,
42
45
  });
43
46
  // if there is no cache defined (cacheName and cacheStrategy properly set),
@@ -52,11 +55,16 @@ const svgFetch = async ({ url, name, fileExtension, cacheStrategy, cacheName, re
52
55
  if (responseFromCache) {
53
56
  return responseFromCache;
54
57
  }
58
+ // **Check for in-flight fetch for same URL**
59
+ const pending = pendingFetches.get(iconUrl);
60
+ if (pending) {
61
+ return pending;
62
+ }
55
63
  // **Otherwise, fetch and cache if successful**
56
64
  // Both fetchIcon() and iconsCache.set() "consume" the request,
57
65
  // so we need to make a copy.
58
66
  // (see https://developer.mozilla.org/en-US/docs/Web/API/Request/clone)
59
- return fetchIcon(request.clone()).then(response => {
67
+ const fetchPromise = fetchIcon(new Request(iconUrl)).then(response => {
60
68
  var _a;
61
69
  // This avoids caching responses that we know are errors
62
70
  // (i.e. HTTP status code of 4xx or 5xx).
@@ -66,7 +74,11 @@ const svgFetch = async ({ url, name, fileExtension, cacheStrategy, cacheName, re
66
74
  return (_a = iconsCache.set) === null || _a === void 0 ? void 0 : _a.call(iconsCache, request, response.clone()).then(() => response.text());
67
75
  }
68
76
  return response.text();
77
+ }).finally(() => {
78
+ pendingFetches.delete(iconUrl);
69
79
  });
80
+ pendingFetches.set(iconUrl, fetchPromise);
81
+ return fetchPromise;
70
82
  })
71
83
  .catch(error => {
72
84
  // Note that a HTTP error response (e.g. 404) will NOT trigger
@@ -12,6 +12,8 @@ const fetchIllustration = async (request) => fetch(request).then(response => {
12
12
  }
13
13
  return response;
14
14
  });
15
+ // Map of in-flight fetch promises keyed by URL to deduplicate concurrent requests
16
+ const pendingFetches = new Map();
15
17
  /**
16
18
  * Fetches a dynamic SVG illustration based on the provided `url`, `name` and `fileExtension`.
17
19
  * The fetch is aborted if the signal is aborted.
@@ -37,7 +39,8 @@ const svgFetch = async ({ url, name, fileExtension, cacheStrategy, cacheName, re
37
39
  // abort the previous fetch request if it is still pending
38
40
  // and create a new signal
39
41
  const signal = renewSignal();
40
- const request = new Request(`${url}/${name}.${fileExtension}`, {
42
+ const illustrationUrl = `${url}/${name}.${fileExtension}`;
43
+ const request = new Request(illustrationUrl, {
41
44
  signal,
42
45
  });
43
46
  // if there is no cache defined (cacheName and cacheStrategy properly set),
@@ -52,11 +55,16 @@ const svgFetch = async ({ url, name, fileExtension, cacheStrategy, cacheName, re
52
55
  if (responseFromCache) {
53
56
  return responseFromCache;
54
57
  }
58
+ // **Check for in-flight fetch for same URL**
59
+ const pending = pendingFetches.get(illustrationUrl);
60
+ if (pending) {
61
+ return pending;
62
+ }
55
63
  // **Otherwise, fetch and cache if successful**
56
64
  // Both fetchIllustration() and illustrationsCache.set() "consume" the request,
57
65
  // so we need to make a copy.
58
66
  // (see https://developer.mozilla.org/en-US/docs/Web/API/Request/clone)
59
- return fetchIllustration(request.clone()).then(response => {
67
+ const fetchPromise = fetchIllustration(new Request(illustrationUrl)).then(response => {
60
68
  var _a;
61
69
  // This avoids caching responses that we know are errors
62
70
  // (i.e. HTTP status code of 4xx or 5xx).
@@ -66,7 +74,11 @@ const svgFetch = async ({ url, name, fileExtension, cacheStrategy, cacheName, re
66
74
  return (_a = illustrationsCache.set) === null || _a === void 0 ? void 0 : _a.call(illustrationsCache, request, response.clone()).then(() => response.text());
67
75
  }
68
76
  return response.text();
77
+ }).finally(() => {
78
+ pendingFetches.delete(illustrationUrl);
69
79
  });
80
+ pendingFetches.set(illustrationUrl, fetchPromise);
81
+ return fetchPromise;
70
82
  })
71
83
  .catch(error => {
72
84
  // Note that a HTTP error response (e.g. 404) will NOT trigger
@@ -82,6 +82,7 @@ declare class TabList extends TabList_base {
82
82
  * @internal
83
83
  */
84
84
  private showBackwardArrowButton;
85
+ private readonly boundHandleFocus;
85
86
  /**
86
87
  * @internal
87
88
  */
@@ -126,6 +126,9 @@ class TabList extends ListNavigationMixin(CaptureDestroyEventForChildElement(Key
126
126
  * @internal
127
127
  */
128
128
  this.showBackwardArrowButton = false;
129
+ this.boundHandleFocus = (event) => {
130
+ void this.handleFocus(event);
131
+ };
129
132
  // This must be initialized after the destroyed event listener
130
133
  // to keep the element in the itemStore in order to move the focus correctly
131
134
  this.itemsStore = new ElementStore(this, {
@@ -168,12 +171,12 @@ class TabList extends ListNavigationMixin(CaptureDestroyEventForChildElement(Key
168
171
  if (!this.activeTabId) {
169
172
  this.activeTabId = (_a = getFirstTab(this.navItems)) === null || _a === void 0 ? void 0 : _a.tabId;
170
173
  }
171
- (_b = this.tabsContainer) === null || _b === void 0 ? void 0 : _b.addEventListener('focusin', this.handleFocus.bind(this));
174
+ (_b = this.tabsContainer) === null || _b === void 0 ? void 0 : _b.addEventListener('focusin', this.boundHandleFocus);
172
175
  }
173
176
  disconnectedCallback() {
174
177
  var _a;
175
178
  super.disconnectedCallback();
176
- (_a = this.tabsContainer) === null || _a === void 0 ? void 0 : _a.removeEventListener('focusin', this.handleFocus);
179
+ (_a = this.tabsContainer) === null || _a === void 0 ? void 0 : _a.removeEventListener('focusin', this.boundHandleFocus);
177
180
  }
178
181
  /**
179
182
  * Override focus to delegate to the active tab.
@@ -205,6 +208,11 @@ class TabList extends ListNavigationMixin(CaptureDestroyEventForChildElement(Key
205
208
  if (event.relatedTarget instanceof Tab || !(event.target instanceof Tab)) {
206
209
  return;
207
210
  }
211
+ // After scrolling with arrow buttons, focus moves from the button to the clicked tab.
212
+ // Do not redirect that focus back to the currently active tab.
213
+ if (event.relatedTarget instanceof Button) {
214
+ return;
215
+ }
208
216
  const activeTab = getActiveTab(this.navItems || []);
209
217
  if (!(activeTab instanceof Tab)) {
210
218
  return;
@@ -48481,6 +48481,12 @@
48481
48481
  "module": "utils/mixins/TabListComponentMixin.js"
48482
48482
  }
48483
48483
  },
48484
+ {
48485
+ "kind": "field",
48486
+ "name": "boundHandleFocus",
48487
+ "privacy": "private",
48488
+ "readonly": true
48489
+ },
48484
48490
  {
48485
48491
  "kind": "field",
48486
48492
  "name": "dataAriaLabel",
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@momentum-design/components",
3
3
  "packageManager": "yarn@3.2.4",
4
- "version": "0.139.1",
4
+ "version": "0.139.3",
5
5
  "engines": {
6
6
  "node": ">=24.0.0",
7
7
  "npm": ">=8.0.0"