@atlaskit/smart-user-picker 11.3.1 → 11.3.2

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/CHANGELOG.md CHANGED
@@ -1,5 +1,18 @@
1
1
  # @atlassian/smart-user-picker
2
2
 
3
+ ## 11.3.2
4
+
5
+ ### Patch Changes
6
+
7
+ - [`4bdae29c77827`](https://bitbucket.org/atlassian/atlassian-frontend-monorepo/commits/4bdae29c77827) -
8
+ Fix suggestEmailsForDomain being suppressed for partial (non-email) input when enableEmailSearch
9
+ and allowEmailSelectionWhenEmailMatched=false are both set. The WAR-5498 FG cleanup (2026-03-04)
10
+ made the isEmailQuery restriction unconditional, breaking the domain-suggestion creatable option
11
+ (e.g. "asdf@atlassian.com"). Behind the new gate smart_user_picker_suggest_emails_for_domain,
12
+ partial queries now allow the domain suggestion to render, while the email option remains
13
+ suppressed when a full email query matches an existing user, or when the synthesized
14
+ "<query>@<domain>" address already belongs to a returned user.
15
+
3
16
  ## 11.3.1
4
17
 
5
18
  ### Patch Changes
@@ -12,7 +12,7 @@ function ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbol
12
12
  function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { (0, _defineProperty2.default)(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; } // eslint-disable-next-line @typescript-eslint/consistent-type-imports
13
13
  // eslint-disable-next-line @atlaskit/platform/prefer-crypto-random-uuid -- Use crypto.randomUUID instead
14
14
  var packageName = "@atlaskit/smart-user-picker";
15
- var packageVersion = "11.3.0";
15
+ var packageVersion = "11.3.1";
16
16
  var startSession = exports.startSession = function startSession() {
17
17
  return {
18
18
  // eslint-disable-next-line @atlaskit/platform/prefer-crypto-random-uuid -- Use crypto.randomUUID instead
@@ -32,7 +32,7 @@ var _MessagesIntlProvider = _interopRequireDefault(require("./MessagesIntlProvid
32
32
  var _types = require("../types");
33
33
  var _service = require("../service");
34
34
  var _ufoExperiences = require("../ufoExperiences");
35
- var _excluded = ["allowEmail", "enableEmailSearch", "allowEmailSelectionWhenEmailMatched"];
35
+ var _excluded = ["allowEmail", "enableEmailSearch", "allowEmailSelectionWhenEmailMatched", "suggestEmailsForDomain"];
36
36
  function _interopRequireWildcard(e, t) { if ("function" == typeof WeakMap) var r = new WeakMap(), n = new WeakMap(); return (_interopRequireWildcard = function _interopRequireWildcard(e, t) { if (!t && e && e.__esModule) return e; var o, i, f = { __proto__: null, default: e }; if (null === e || "object" != _typeof(e) && "function" != typeof e) return f; if (o = t ? n : r) { if (o.has(e)) return o.get(e); o.set(e, f); } for (var _t1 in e) "default" !== _t1 && {}.hasOwnProperty.call(e, _t1) && ((i = (o = Object.defineProperty) && Object.getOwnPropertyDescriptor(e, _t1)) && (i.get || i.set) ? o(f, _t1, i) : f[_t1] = e[_t1]); return f; })(e, t); }
37
37
  function _createForOfIteratorHelper(r, e) { var t = "undefined" != typeof Symbol && r[Symbol.iterator] || r["@@iterator"]; if (!t) { if (Array.isArray(r) || (t = _unsupportedIterableToArray(r)) || e && r && "number" == typeof r.length) { t && (r = t); var _n = 0, F = function F() {}; return { s: F, n: function n() { return _n >= r.length ? { done: !0 } : { done: !1, value: r[_n++] }; }, e: function e(r) { throw r; }, f: F }; } throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); } var o, a = !0, u = !1; return { s: function s() { t = t.call(r); }, n: function n() { var r = t.next(); return a = r.done, r; }, e: function e(r) { u = !0, o = r; }, f: function f() { try { a || null == t.return || t.return(); } finally { if (u) throw o; } } }; }
38
38
  function _unsupportedIterableToArray(r, a) { if (r) { if ("string" == typeof r) return _arrayLikeToArray(r, a); var t = {}.toString.call(r).slice(8, -1); return "Object" === t && r.constructor && (t = r.constructor.name), "Map" === t || "Set" === t ? Array.from(r) : "Arguments" === t || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(t) ? _arrayLikeToArray(r, a) : void 0; } }
@@ -93,6 +93,9 @@ var SmartUserPickerWithoutAnalytics = exports.SmartUserPickerWithoutAnalytics =
93
93
  });
94
94
  // Track if the last search was an email search that found matches
95
95
  (0, _defineProperty2.default)(_this, "lastEmailSearchFoundMatches", false);
96
+ // Track if the domain-suggested email (`<query>@<suggestEmailsForDomain>`) for a partial
97
+ // query already belongs to a returned user, in which case the suggestion is redundant
98
+ (0, _defineProperty2.default)(_this, "lastDomainSuggestionMatchedUser", false);
96
99
  (0, _defineProperty2.default)(_this, "abortOptionsShownUfoExperience", function () {
97
100
  if (_this.optionsShownUfoExperienceInstance.state.id === _ufo.UFOExperienceState.STARTED.id) {
98
101
  // There may be an existing UFO timing running from previous key entry or focus,
@@ -118,7 +121,7 @@ var SmartUserPickerWithoutAnalytics = exports.SmartUserPickerWithoutAnalytics =
118
121
  });
119
122
  (0, _defineProperty2.default)(_this, "memoizedFilterOptions", (0, _memoizeOne.default)(_this.filterOptions));
120
123
  (0, _defineProperty2.default)(_this, "getUsers", (0, _debounce.default)( /*#__PURE__*/(0, _asyncToGenerator2.default)( /*#__PURE__*/_regenerator.default.mark(function _callee() {
121
- var _this$state, query, sessionId, closed, _this$props, baseUrl, childObjectId, containerId, fieldId, includeGroups, includeTeams, includeTeamsUpdates, includeUsers, includeNonLicensedUsers, intl, fetchOptions, maxOptions, objectId, onEmpty, onError, overrideByline, displayEmailInByline, verifiedTeams, orgId, userbaseId, principalId, productAttributes, productKey, restrictTo, searchQueryFilter, siteId, isTeamSyncedToGroupDirectoryFilter, transformOptions, userResolvers, enableEmailSearch, maxNumberOfResults, startTime, isEmail, recommendationsRequest, _yield$onEmpty, _query, recommendedUsers, userRecommendationsPromise, userResolversPromises, _yield$Promise$all, _yield$Promise$all2, mainRecommendations, userResolverResults, _iterator, _step, option, _iterator2, _step2, _option, _iterator3, _step3, _option2, userMatches, elapsedTimeMilli, transformedOptions, displayedUsers, is5xxEvent, onErrorProducedError, defaultUsers, _elapsedTimeMilli, _t, _t2, _t3, _t4, _t5, _t6, _t7, _t8, _t9;
124
+ var _this$state, query, sessionId, closed, _this$props, baseUrl, childObjectId, containerId, fieldId, includeGroups, includeTeams, includeTeamsUpdates, includeUsers, includeNonLicensedUsers, intl, fetchOptions, maxOptions, objectId, onEmpty, onError, overrideByline, displayEmailInByline, verifiedTeams, orgId, userbaseId, principalId, productAttributes, productKey, restrictTo, searchQueryFilter, siteId, isTeamSyncedToGroupDirectoryFilter, transformOptions, userResolvers, enableEmailSearch, maxNumberOfResults, startTime, isEmail, recommendationsRequest, _yield$onEmpty, _query, recommendedUsers, userRecommendationsPromise, userResolversPromises, _yield$Promise$all, _yield$Promise$all2, mainRecommendations, userResolverResults, _iterator, _step, option, _iterator2, _step2, _option, _iterator3, _step3, _option2, userMatches, suggestEmailsForDomain, suggestedEmail, elapsedTimeMilli, transformedOptions, displayedUsers, is5xxEvent, onErrorProducedError, defaultUsers, _elapsedTimeMilli, _t, _t2, _t3, _t4, _t5, _t6, _t7, _t8, _t9;
122
125
  return _regenerator.default.wrap(function (_context) {
123
126
  while (1) switch (_context.prev = _context.next) {
124
127
  case 0:
@@ -259,6 +262,20 @@ var SmartUserPickerWithoutAnalytics = exports.SmartUserPickerWithoutAnalytics =
259
262
  } else {
260
263
  _this.lastEmailSearchFoundMatches = false;
261
264
  }
265
+
266
+ // For a partial query with a suggested domain, check whether the email we would
267
+ // synthesize (`<query>@<domain>`) already belongs to one of the returned users. If so,
268
+ // the real user option is shown instead and the email suggestion is suppressed.
269
+ suggestEmailsForDomain = _this.props.suggestEmailsForDomain;
270
+ if (suggestEmailsForDomain && !isEmail && _query) {
271
+ suggestedEmail = "".concat(_query, "@").concat(suggestEmailsForDomain).toLowerCase();
272
+ _this.lastDomainSuggestionMatchedUser = recommendedUsers.some(function (option) {
273
+ var _option$email;
274
+ return ((0, _userPicker.isUser)(option) || (0, _userPicker.isExternalUser)(option)) && ((_option$email = option.email) === null || _option$email === void 0 ? void 0 : _option$email.toLowerCase()) === suggestedEmail;
275
+ });
276
+ } else {
277
+ _this.lastDomainSuggestionMatchedUser = false;
278
+ }
262
279
  elapsedTimeMilli = window.performance.now() - startTime;
263
280
  if (!transformOptions) {
264
281
  _context.next = 7;
@@ -566,19 +583,34 @@ var SmartUserPickerWithoutAnalytics = exports.SmartUserPickerWithoutAnalytics =
566
583
  allowEmail = _this$props2.allowEmail,
567
584
  enableEmailSearch = _this$props2.enableEmailSearch,
568
585
  allowEmailSelectionWhenEmailMatched = _this$props2.allowEmailSelectionWhenEmailMatched,
586
+ suggestEmailsForDomain = _this$props2.suggestEmailsForDomain,
569
587
  restProps = (0, _objectWithoutProperties2.default)(_this$props2, _excluded);
570
588
 
571
589
  // Determine whether to allow email selection based on allowEmailSelectionWhenEmailMatched, if needed
572
590
  var shouldAllowEmail = allowEmail;
591
+
592
+ // When suggestEmailsForDomain is active, partial (non-email) input must still reach the base
593
+ // picker's domain-suggestion creatable logic, so the isEmailQuery restriction is relaxed for
594
+ // those queries only. Full email queries that matched an existing user remain suppressed.
595
+ var isDomainSuggestionEnabled = (0, _platformFeatureFlags.fg)('smart_user_picker_suggest_emails_for_domain') && !!suggestEmailsForDomain;
573
596
  if (allowEmail && enableEmailSearch && !allowEmailSelectionWhenEmailMatched) {
574
597
  var isCurrentQueryEmail = isEmailQuery(this.state.query);
575
- // Only allow email selection when:
576
- // 1. The query matches email format (validated by regex)
577
- // 2. No user/external user matches were found (only teams/groups suggested)
578
- shouldAllowEmail = isCurrentQueryEmail && !this.lastEmailSearchFoundMatches;
598
+ if (isDomainSuggestionEnabled) {
599
+ // Allow email for partial queries so the "<query>@<domain>" suggestion can render,
600
+ // but still suppress selection when a full email query matched an existing user,
601
+ // or when the synthesized "<query>@<domain>" is itself an existing user's email.
602
+ shouldAllowEmail = isCurrentQueryEmail ? !this.lastEmailSearchFoundMatches // full email: suppress when user matched
603
+ : !this.lastDomainSuggestionMatchedUser; // partial: suppress when synth email matched a user
604
+ } else {
605
+ // Only allow email selection when:
606
+ // 1. The query matches email format (validated by regex)
607
+ // 2. No user/external user matches were found (only teams/groups suggested)
608
+ shouldAllowEmail = isCurrentQueryEmail && !this.lastEmailSearchFoundMatches;
609
+ }
579
610
  }
580
611
  return /*#__PURE__*/_react.default.createElement(_MessagesIntlProvider.default, null, /*#__PURE__*/_react.default.createElement(_userPicker.default, (0, _extends2.default)({}, restProps, {
581
612
  allowEmail: shouldAllowEmail,
613
+ suggestEmailsForDomain: suggestEmailsForDomain,
582
614
  onInputChange: this.onInputChange,
583
615
  onBlur: this.onBlur,
584
616
  onFocus: this.onFocus,
@@ -3,7 +3,7 @@ import { createAndFireEvent } from '@atlaskit/analytics-next';
3
3
  // eslint-disable-next-line @atlaskit/platform/prefer-crypto-random-uuid -- Use crypto.randomUUID instead
4
4
  import { v4 as uuid } from 'uuid';
5
5
  const packageName = "@atlaskit/smart-user-picker";
6
- const packageVersion = "11.3.0";
6
+ const packageVersion = "11.3.1";
7
7
  export const startSession = () => ({
8
8
  // eslint-disable-next-line @atlaskit/platform/prefer-crypto-random-uuid -- Use crypto.randomUUID instead
9
9
  id: uuid(),
@@ -58,6 +58,9 @@ export class SmartUserPickerWithoutAnalytics extends React.Component {
58
58
  });
59
59
  // Track if the last search was an email search that found matches
60
60
  _defineProperty(this, "lastEmailSearchFoundMatches", false);
61
+ // Track if the domain-suggested email (`<query>@<suggestEmailsForDomain>`) for a partial
62
+ // query already belongs to a returned user, in which case the suggestion is redundant
63
+ _defineProperty(this, "lastDomainSuggestionMatchedUser", false);
61
64
  _defineProperty(this, "abortOptionsShownUfoExperience", () => {
62
65
  if (this.optionsShownUfoExperienceInstance.state.id === UFOExperienceState.STARTED.id) {
63
66
  // There may be an existing UFO timing running from previous key entry or focus,
@@ -218,6 +221,22 @@ export class SmartUserPickerWithoutAnalytics extends React.Component {
218
221
  } else {
219
222
  this.lastEmailSearchFoundMatches = false;
220
223
  }
224
+
225
+ // For a partial query with a suggested domain, check whether the email we would
226
+ // synthesize (`<query>@<domain>`) already belongs to one of the returned users. If so,
227
+ // the real user option is shown instead and the email suggestion is suppressed.
228
+ const {
229
+ suggestEmailsForDomain
230
+ } = this.props;
231
+ if (suggestEmailsForDomain && !isEmail && query) {
232
+ const suggestedEmail = `${query}@${suggestEmailsForDomain}`.toLowerCase();
233
+ this.lastDomainSuggestionMatchedUser = recommendedUsers.some(option => {
234
+ var _option$email;
235
+ return (isUser(option) || isExternalUser(option)) && ((_option$email = option.email) === null || _option$email === void 0 ? void 0 : _option$email.toLowerCase()) === suggestedEmail;
236
+ });
237
+ } else {
238
+ this.lastDomainSuggestionMatchedUser = false;
239
+ }
221
240
  const elapsedTimeMilli = window.performance.now() - startTime;
222
241
  const transformedOptions = transformOptions ? await transformOptions(recommendedUsers, query) : recommendedUsers;
223
242
  const displayedUsers = transformedOptions.length === 0 && onEmpty ? (_await$onEmpty = await onEmpty(query)) !== null && _await$onEmpty !== void 0 ? _await$onEmpty : [] : transformedOptions;
@@ -419,20 +438,35 @@ export class SmartUserPickerWithoutAnalytics extends React.Component {
419
438
  allowEmail,
420
439
  enableEmailSearch,
421
440
  allowEmailSelectionWhenEmailMatched,
441
+ suggestEmailsForDomain,
422
442
  ...restProps
423
443
  } = this.props;
424
444
 
425
445
  // Determine whether to allow email selection based on allowEmailSelectionWhenEmailMatched, if needed
426
446
  let shouldAllowEmail = allowEmail;
447
+
448
+ // When suggestEmailsForDomain is active, partial (non-email) input must still reach the base
449
+ // picker's domain-suggestion creatable logic, so the isEmailQuery restriction is relaxed for
450
+ // those queries only. Full email queries that matched an existing user remain suppressed.
451
+ const isDomainSuggestionEnabled = fg('smart_user_picker_suggest_emails_for_domain') && !!suggestEmailsForDomain;
427
452
  if (allowEmail && enableEmailSearch && !allowEmailSelectionWhenEmailMatched) {
428
453
  const isCurrentQueryEmail = isEmailQuery(this.state.query);
429
- // Only allow email selection when:
430
- // 1. The query matches email format (validated by regex)
431
- // 2. No user/external user matches were found (only teams/groups suggested)
432
- shouldAllowEmail = isCurrentQueryEmail && !this.lastEmailSearchFoundMatches;
454
+ if (isDomainSuggestionEnabled) {
455
+ // Allow email for partial queries so the "<query>@<domain>" suggestion can render,
456
+ // but still suppress selection when a full email query matched an existing user,
457
+ // or when the synthesized "<query>@<domain>" is itself an existing user's email.
458
+ shouldAllowEmail = isCurrentQueryEmail ? !this.lastEmailSearchFoundMatches // full email: suppress when user matched
459
+ : !this.lastDomainSuggestionMatchedUser; // partial: suppress when synth email matched a user
460
+ } else {
461
+ // Only allow email selection when:
462
+ // 1. The query matches email format (validated by regex)
463
+ // 2. No user/external user matches were found (only teams/groups suggested)
464
+ shouldAllowEmail = isCurrentQueryEmail && !this.lastEmailSearchFoundMatches;
465
+ }
433
466
  }
434
467
  return /*#__PURE__*/React.createElement(MessagesIntlProvider, null, /*#__PURE__*/React.createElement(UserPicker, _extends({}, restProps, {
435
468
  allowEmail: shouldAllowEmail,
469
+ suggestEmailsForDomain: suggestEmailsForDomain,
436
470
  onInputChange: this.onInputChange,
437
471
  onBlur: this.onBlur,
438
472
  onFocus: this.onFocus,
@@ -6,7 +6,7 @@ import { createAndFireEvent } from '@atlaskit/analytics-next';
6
6
  // eslint-disable-next-line @atlaskit/platform/prefer-crypto-random-uuid -- Use crypto.randomUUID instead
7
7
  import { v4 as uuid } from 'uuid';
8
8
  var packageName = "@atlaskit/smart-user-picker";
9
- var packageVersion = "11.3.0";
9
+ var packageVersion = "11.3.1";
10
10
  export var startSession = function startSession() {
11
11
  return {
12
12
  // eslint-disable-next-line @atlaskit/platform/prefer-crypto-random-uuid -- Use crypto.randomUUID instead
@@ -9,7 +9,7 @@ import _possibleConstructorReturn from "@babel/runtime/helpers/possibleConstruct
9
9
  import _getPrototypeOf from "@babel/runtime/helpers/getPrototypeOf";
10
10
  import _inherits from "@babel/runtime/helpers/inherits";
11
11
  import _defineProperty from "@babel/runtime/helpers/defineProperty";
12
- var _excluded = ["allowEmail", "enableEmailSearch", "allowEmailSelectionWhenEmailMatched"];
12
+ var _excluded = ["allowEmail", "enableEmailSearch", "allowEmailSelectionWhenEmailMatched", "suggestEmailsForDomain"];
13
13
  import _regeneratorRuntime from "@babel/runtime/regenerator";
14
14
  function _createForOfIteratorHelper(r, e) { var t = "undefined" != typeof Symbol && r[Symbol.iterator] || r["@@iterator"]; if (!t) { if (Array.isArray(r) || (t = _unsupportedIterableToArray(r)) || e && r && "number" == typeof r.length) { t && (r = t); var _n = 0, F = function F() {}; return { s: F, n: function n() { return _n >= r.length ? { done: !0 } : { done: !1, value: r[_n++] }; }, e: function e(r) { throw r; }, f: F }; } throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); } var o, a = !0, u = !1; return { s: function s() { t = t.call(r); }, n: function n() { var r = t.next(); return a = r.done, r; }, e: function e(r) { u = !0, o = r; }, f: function f() { try { a || null == t.return || t.return(); } finally { if (u) throw o; } } }; }
15
15
  function _unsupportedIterableToArray(r, a) { if (r) { if ("string" == typeof r) return _arrayLikeToArray(r, a); var t = {}.toString.call(r).slice(8, -1); return "Object" === t && r.constructor && (t = r.constructor.name), "Map" === t || "Set" === t ? Array.from(r) : "Arguments" === t || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(t) ? _arrayLikeToArray(r, a) : void 0; } }
@@ -85,6 +85,9 @@ export var SmartUserPickerWithoutAnalytics = /*#__PURE__*/function (_React$Compo
85
85
  });
86
86
  // Track if the last search was an email search that found matches
87
87
  _defineProperty(_this, "lastEmailSearchFoundMatches", false);
88
+ // Track if the domain-suggested email (`<query>@<suggestEmailsForDomain>`) for a partial
89
+ // query already belongs to a returned user, in which case the suggestion is redundant
90
+ _defineProperty(_this, "lastDomainSuggestionMatchedUser", false);
88
91
  _defineProperty(_this, "abortOptionsShownUfoExperience", function () {
89
92
  if (_this.optionsShownUfoExperienceInstance.state.id === UFOExperienceState.STARTED.id) {
90
93
  // There may be an existing UFO timing running from previous key entry or focus,
@@ -110,7 +113,7 @@ export var SmartUserPickerWithoutAnalytics = /*#__PURE__*/function (_React$Compo
110
113
  });
111
114
  _defineProperty(_this, "memoizedFilterOptions", memoizeOne(_this.filterOptions));
112
115
  _defineProperty(_this, "getUsers", debounce( /*#__PURE__*/_asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee() {
113
- var _this$state, query, sessionId, closed, _this$props, baseUrl, childObjectId, containerId, fieldId, includeGroups, includeTeams, includeTeamsUpdates, includeUsers, includeNonLicensedUsers, intl, fetchOptions, maxOptions, objectId, onEmpty, onError, overrideByline, displayEmailInByline, verifiedTeams, orgId, userbaseId, principalId, productAttributes, productKey, restrictTo, searchQueryFilter, siteId, isTeamSyncedToGroupDirectoryFilter, transformOptions, userResolvers, enableEmailSearch, maxNumberOfResults, startTime, isEmail, recommendationsRequest, _yield$onEmpty, _query, recommendedUsers, userRecommendationsPromise, userResolversPromises, _yield$Promise$all, _yield$Promise$all2, mainRecommendations, userResolverResults, _iterator, _step, option, _iterator2, _step2, _option, _iterator3, _step3, _option2, userMatches, elapsedTimeMilli, transformedOptions, displayedUsers, is5xxEvent, onErrorProducedError, defaultUsers, _elapsedTimeMilli, _t, _t2, _t3, _t4, _t5, _t6, _t7, _t8, _t9;
116
+ var _this$state, query, sessionId, closed, _this$props, baseUrl, childObjectId, containerId, fieldId, includeGroups, includeTeams, includeTeamsUpdates, includeUsers, includeNonLicensedUsers, intl, fetchOptions, maxOptions, objectId, onEmpty, onError, overrideByline, displayEmailInByline, verifiedTeams, orgId, userbaseId, principalId, productAttributes, productKey, restrictTo, searchQueryFilter, siteId, isTeamSyncedToGroupDirectoryFilter, transformOptions, userResolvers, enableEmailSearch, maxNumberOfResults, startTime, isEmail, recommendationsRequest, _yield$onEmpty, _query, recommendedUsers, userRecommendationsPromise, userResolversPromises, _yield$Promise$all, _yield$Promise$all2, mainRecommendations, userResolverResults, _iterator, _step, option, _iterator2, _step2, _option, _iterator3, _step3, _option2, userMatches, suggestEmailsForDomain, suggestedEmail, elapsedTimeMilli, transformedOptions, displayedUsers, is5xxEvent, onErrorProducedError, defaultUsers, _elapsedTimeMilli, _t, _t2, _t3, _t4, _t5, _t6, _t7, _t8, _t9;
114
117
  return _regeneratorRuntime.wrap(function (_context) {
115
118
  while (1) switch (_context.prev = _context.next) {
116
119
  case 0:
@@ -251,6 +254,20 @@ export var SmartUserPickerWithoutAnalytics = /*#__PURE__*/function (_React$Compo
251
254
  } else {
252
255
  _this.lastEmailSearchFoundMatches = false;
253
256
  }
257
+
258
+ // For a partial query with a suggested domain, check whether the email we would
259
+ // synthesize (`<query>@<domain>`) already belongs to one of the returned users. If so,
260
+ // the real user option is shown instead and the email suggestion is suppressed.
261
+ suggestEmailsForDomain = _this.props.suggestEmailsForDomain;
262
+ if (suggestEmailsForDomain && !isEmail && _query) {
263
+ suggestedEmail = "".concat(_query, "@").concat(suggestEmailsForDomain).toLowerCase();
264
+ _this.lastDomainSuggestionMatchedUser = recommendedUsers.some(function (option) {
265
+ var _option$email;
266
+ return (isUser(option) || isExternalUser(option)) && ((_option$email = option.email) === null || _option$email === void 0 ? void 0 : _option$email.toLowerCase()) === suggestedEmail;
267
+ });
268
+ } else {
269
+ _this.lastDomainSuggestionMatchedUser = false;
270
+ }
254
271
  elapsedTimeMilli = window.performance.now() - startTime;
255
272
  if (!transformOptions) {
256
273
  _context.next = 7;
@@ -558,19 +575,34 @@ export var SmartUserPickerWithoutAnalytics = /*#__PURE__*/function (_React$Compo
558
575
  allowEmail = _this$props2.allowEmail,
559
576
  enableEmailSearch = _this$props2.enableEmailSearch,
560
577
  allowEmailSelectionWhenEmailMatched = _this$props2.allowEmailSelectionWhenEmailMatched,
578
+ suggestEmailsForDomain = _this$props2.suggestEmailsForDomain,
561
579
  restProps = _objectWithoutProperties(_this$props2, _excluded);
562
580
 
563
581
  // Determine whether to allow email selection based on allowEmailSelectionWhenEmailMatched, if needed
564
582
  var shouldAllowEmail = allowEmail;
583
+
584
+ // When suggestEmailsForDomain is active, partial (non-email) input must still reach the base
585
+ // picker's domain-suggestion creatable logic, so the isEmailQuery restriction is relaxed for
586
+ // those queries only. Full email queries that matched an existing user remain suppressed.
587
+ var isDomainSuggestionEnabled = fg('smart_user_picker_suggest_emails_for_domain') && !!suggestEmailsForDomain;
565
588
  if (allowEmail && enableEmailSearch && !allowEmailSelectionWhenEmailMatched) {
566
589
  var isCurrentQueryEmail = isEmailQuery(this.state.query);
567
- // Only allow email selection when:
568
- // 1. The query matches email format (validated by regex)
569
- // 2. No user/external user matches were found (only teams/groups suggested)
570
- shouldAllowEmail = isCurrentQueryEmail && !this.lastEmailSearchFoundMatches;
590
+ if (isDomainSuggestionEnabled) {
591
+ // Allow email for partial queries so the "<query>@<domain>" suggestion can render,
592
+ // but still suppress selection when a full email query matched an existing user,
593
+ // or when the synthesized "<query>@<domain>" is itself an existing user's email.
594
+ shouldAllowEmail = isCurrentQueryEmail ? !this.lastEmailSearchFoundMatches // full email: suppress when user matched
595
+ : !this.lastDomainSuggestionMatchedUser; // partial: suppress when synth email matched a user
596
+ } else {
597
+ // Only allow email selection when:
598
+ // 1. The query matches email format (validated by regex)
599
+ // 2. No user/external user matches were found (only teams/groups suggested)
600
+ shouldAllowEmail = isCurrentQueryEmail && !this.lastEmailSearchFoundMatches;
601
+ }
571
602
  }
572
603
  return /*#__PURE__*/React.createElement(MessagesIntlProvider, null, /*#__PURE__*/React.createElement(UserPicker, _extends({}, restProps, {
573
604
  allowEmail: shouldAllowEmail,
605
+ suggestEmailsForDomain: suggestEmailsForDomain,
574
606
  onInputChange: this.onInputChange,
575
607
  onBlur: this.onBlur,
576
608
  onFocus: this.onFocus,
@@ -8,6 +8,7 @@ import type { DebouncedFunc } from 'lodash';
8
8
  export declare class SmartUserPickerWithoutAnalytics extends React.Component<Props & WrappedComponentProps, State> {
9
9
  state: State;
10
10
  private lastEmailSearchFoundMatches;
11
+ private lastDomainSuggestionMatchedUser;
11
12
  optionsShownUfoExperienceInstance: UFOExperience;
12
13
  static defaultProps: {
13
14
  baseUrl: string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@atlaskit/smart-user-picker",
3
- "version": "11.3.1",
3
+ "version": "11.3.2",
4
4
  "license": "Apache-2.0",
5
5
  "publishConfig": {
6
6
  "registry": "https://registry.npmjs.org/"
@@ -57,6 +57,9 @@
57
57
  "react-intl": "^7.0.0"
58
58
  },
59
59
  "platform-feature-flags": {
60
+ "smart_user_picker_suggest_emails_for_domain": {
61
+ "type": "boolean"
62
+ },
60
63
  "smart-user-picker-load-options-gate": {
61
64
  "type": "boolean"
62
65
  },