Viewing File: /usr/local/cpanel/base/frontend/jupiter/domains/directives/domainSearchModalDirective.js
// Copyright 2026 WebPros International, LLC
// All rights reserved.
// copyright@cpanel.net http://cpanel.net
// This code is subject to the cPanel license. Unauthorized copying is prohibited.
/* global define */
/** @namespace cpanel.domains.directive.domainSearchModal */
define(
[
"angular",
"lodash",
"cjt/util/locale",
"uiBootstrap",
"app/services/domains",
"app/directives/tldPriorityInputDirective",
"cjt/services/alertService",
],
function(angular, _, LOCALE) {
"use strict";
var MODULE_NAMESPACE = "cpanel.domains.domainSearchModal";
var module = angular.module(MODULE_NAMESPACE, [
"ui.bootstrap",
"cpanel.domains.domains.service",
"cpanel.domains.tldPriorityInput",
"cjt2.services.alert",
]);
var TEMPLATE_URL = "directives/domainSearchModal.ptt";
var WINDOW_CLASS = "domain-search-modal-dialog";
// Modal controller — extracted from the previous inline controller in
// views/manageDomain.js so it can be reused in two places (the manage
// recommendations banner and the create-domain "purchase" chooser).
var ModalController = ["$scope", "$uibModalInstance", "$sce", "$timeout", "$document", "domains", "alertService", "options",
function($modalScope, $uibModalInstance, $sce, $timeout, $document, $domainsService, $alertService, options) {
options = options || {};
var cachedExploreData = options.cachedExploreData || null;
var exploreDataPromise = options.exploreDataPromise || null;
// "create" = opened from the create-domain flow;
// "manage" = opened from the manage-domain banner.
var _source = options.source || "unknown";
// Optional host callback invoked with the just-purchased
// domain when the user chooses to add it to cPanel from the
// post-redirect success panel. Lets the modal hand the domain
// back to its caller so the caller can open its own add-domain
// flow with the name prefilled, mirroring the "Registered
// Domain" workflow. When absent, the success panel offers
// only "Done".
var _onAddDomain = typeof options.onAddDomain === "function"
? options.onAddDomain
: null;
$modalScope.canAddDomain = !!_onAddDomain;
$modalScope.loading = false;
$modalScope.searching = false;
$modalScope.searchQuery = options.initialQuery || "";
$modalScope.showSearchOptions = false;
// Distinguish "caller seeded the filters" (e.g. the
// create-domain chooser, which passes its own
// initialTldFilters — already pre-populated from NVData on
// that page) from "caller left them unset" (e.g. the
// manage-domain recommendations banner). Only in the latter
// case do we load the user's saved prioritisation here, so we
// never clobber a selection the caller explicitly handed us.
var _callerSeededTldFilters =
Object.prototype.hasOwnProperty.call(options, "initialTldFilters");
$modalScope.tldFilters = _callerSeededTldFilters && angular.isArray(options.initialTldFilters)
? options.initialTldFilters.slice()
: [];
// Pre-populate the "Prioritize TLDs" control from the user's
// persisted NVData preference so a prioritisation set in a
// previous session carries over and is applied to the next
// search the user runs (CPANEL-53293).
//
// The load is async, so track it: a search fired before it
// settles must wait (see searchDomains), otherwise it would
// run with an empty set and persist that empty set back,
// clearing the saved preference.
var _savedTldFiltersLoad = null;
var _savedTldFiltersLoaded = _callerSeededTldFilters;
if (!_callerSeededTldFilters) {
_savedTldFiltersLoad = $domainsService.getPrioritizedTlds()
.then(function(savedTlds) {
if (savedTlds && savedTlds.length && !$modalScope.tldFilters.length) {
$modalScope.tldFilters = savedTlds;
}
})
.finally(function() {
_savedTldFiltersLoaded = true;
});
}
$modalScope.searchPerformed = false;
// True only after a search resolves with no featured or
// available results, so the title can switch from
// "We Think You'll Like …" to "No Domains Found".
$modalScope.noResults = false;
$modalScope.featured = cachedExploreData ? cachedExploreData.featured : [];
$modalScope.available = cachedExploreData ? cachedExploreData.available : [];
// The modal can be opened while the banner's recommendations
// request is still in flight. When that happens there is no
// cached explore data yet, so show the loading skeleton until
// the same promise resolves rather than rendering an empty view.
if (!cachedExploreData && exploreDataPromise) {
$modalScope.loading = true;
exploreDataPromise
.then(function(exploreData) {
// If the user already started a manual search while
// the initial request was in flight, keep their
// results instead of overwriting them.
if ($modalScope.searchPerformed) {
return;
}
exploreData = exploreData || {};
$modalScope.featured = exploreData.featured || [];
$modalScope.available = exploreData.available || [];
})
.catch(function() {
// Banner fetch failed; leave the lists empty so the
// user can still search manually.
})
.finally(function() {
$modalScope.loading = false;
});
}
$modalScope.matchedDomain = null;
$modalScope.unavailableDomain = null;
$modalScope.pendingSearchDomain = null;
// null = still loading; [] = loaded but empty / unavailable
$modalScope.supportedTlds = null;
var storeConfig = null;
var _storeConfigReady = $domainsService.fetchStoreConfig()
.then(function(config) {
storeConfig = config;
})
.catch(function() {
storeConfig = {};
});
function _getMode() {
return (storeConfig &&
storeConfig.store_type &&
storeConfig.store_type.toLowerCase() === "whmcs")
? "whmcs"
: "unknown";
}
function _trackEvent(eventName, props) {
if (window.mixpanel && typeof window.mixpanel.track === "function") {
window.mixpanel.track(eventName, props || {});
}
}
$domainsService.fetchSupportedTlds()
.then(function(tlds) {
$modalScope.supportedTlds = tlds;
})
.catch(function() {
$modalScope.supportedTlds = [];
});
$modalScope.highlightTld = function(domain) {
var dotIndex = domain.indexOf(".");
if (dotIndex === -1) {
return $sce.trustAsHtml(_.escape(domain));
}
var name = domain.substring(0, dotIndex);
var tld = domain.substring(dotIndex);
return $sce.trustAsHtml(_.escape(name) + "<strong>" + _.escape(tld) + "</strong>");
};
$modalScope.searchDomains = function() {
if (!$modalScope.searchQuery || $modalScope.searching) {
return;
}
// Defer until the saved-TLD preference has loaded, so the
// first search on the manage-domain banner path (which
// opens the modal without seeded filters) applies and
// persists the saved filters instead of racing ahead with
// an empty set and clearing the stored value (CPANEL-53293).
if (!_savedTldFiltersLoaded && _savedTldFiltersLoad) {
_savedTldFiltersLoad.finally(function() {
$modalScope.searchDomains();
});
return;
}
$modalScope.searching = true;
$modalScope.searchPerformed = true;
var trimmedQuery = $modalScope.searchQuery.trim();
// A manual search supersedes the initial in-flight load, so
// clear its skeleton even if that request has not resolved.
$modalScope.loading = false;
_storeConfigReady.finally(function() {
_trackEvent("Domain-Search-Started", {
source: _source,
query: trimmedQuery,
mode: _getMode(),
});
});
var filterTlds = $modalScope.tldFilters.length
? $modalScope.tldFilters
: null;
// Persist the prioritisation that is actually being
// searched so it survives across sessions (CPANEL-53293).
// Saving on search (rather than on every keystroke) keeps
// the stored value in step with what the user committed to,
// and an empty set clears the saved preference. Fire and
// forget — the service logs and swallows any write error so
// a persistence hiccup never blocks the search.
$domainsService.setPrioritizedTlds($modalScope.tldFilters);
var rawQuery = trimmedQuery.toLowerCase();
var queryHasTld = rawQuery.indexOf(".") !== -1;
$modalScope.pendingSearchDomain = rawQuery;
$domainsService.fetchRecommendations(trimmedQuery, filterTlds)
.then(function(result) {
$modalScope.matchedDomain = null;
$modalScope.unavailableDomain = null;
var featured = result.explore.featured || [];
var available = result.explore.available || [];
if (queryHasTld) {
var findMatch = function(list) {
for (var i = 0; i < list.length; i++) {
if (list[i] && list[i].domain &&
list[i].domain.toLowerCase() === rawQuery) {
return i;
}
}
return -1;
};
var fIdx = findMatch(featured);
if (fIdx !== -1) {
$modalScope.matchedDomain = featured[fIdx];
featured = featured.slice(0, fIdx).concat(featured.slice(fIdx + 1));
} else {
var aIdx = findMatch(available);
if (aIdx !== -1) {
$modalScope.matchedDomain = available[aIdx];
available = available.slice(0, aIdx).concat(available.slice(aIdx + 1));
}
}
}
if ($modalScope.matchedDomain) {
// Match found: success banner replaces featured cards;
// any "featured" recs roll into the "Also available" list.
available = featured.concat(available);
featured = [];
} else if (queryHasTld) {
// User searched a fully-qualified domain that
// isn't available: surface the "We're sorry"
// header and keep the featured suggestions
// as alternatives.
$modalScope.unavailableDomain = rawQuery;
}
$modalScope.featured = featured;
$modalScope.available = available;
})
.catch(function() {
// Clear any prior search's results so a failed
// search collapses to the empty "No Domains Found"
// state (computed in finally) instead of leaving the
// previous success/unavailable banner and lists on
// screen alongside the error alert.
$modalScope.matchedDomain = null;
$modalScope.unavailableDomain = null;
$modalScope.featured = [];
$modalScope.available = [];
$alertService.add({
type: "danger",
message: LOCALE.maketext("The domain search failed. Try again."),
});
})
.finally(function() {
$modalScope.searching = false;
$modalScope.pendingSearchDomain = null;
// Compute here, not in .then, so a failed search
// (e.g. missing API credentials) also flips the
// title to "No Domains Found" instead of leaving
// the stale "We Think You'll Like …" heading.
$modalScope.noResults =
!$modalScope.featured.length &&
!$modalScope.available.length;
});
};
$modalScope.handleSearchKeydown = function($event) {
if ($event.key === "Enter") {
$modalScope.searchDomains();
}
};
$modalScope.toggleSearchOptions = function() {
$modalScope.showSearchOptions = !$modalScope.showSearchOptions;
};
$modalScope.purchasingDomain = null;
$modalScope.purchaseConfirmation = null;
// Toggles the "Redirection Failed" overlay (Figma node
// 86:10861) that sits on top of the confirmation panel when
// the purchase-URL fetch fails.
$modalScope.redirectFailed = false;
// When the browser silently blocks window.open() (popup
// blocker fires because the user-gesture context was lost
// across the async fetchDomainPurchaseUrl boundary), this
// holds the URL so the template can surface a clickable
// fallback link. A direct anchor click IS a fresh user
// gesture and is not blocked.
$modalScope.blockedPurchaseUrl = null;
// Called when the user clicks the fallback "Continue to
// Store" link inside the "Redirecting to Store"
// overlay. The click is a fresh user gesture that opens
// the WHMCS cart in a new tab; once that handoff is in
// flight, swap the overlay to the post-redirect success
// state (CPANEL-53285) so the modal mirrors the direct
// success path — the user gets the same "Redirected to
// Store" panel whether the browser allowed the popup
// or they had to click the fallback link.
//
// Two sequential $timeout ticks are used so the steps
// settle into separate digests:
//
// 1. The first tick clears blockedPurchaseUrl. This
// removes the banner (which contains the anchor)
// via ng-if, but ONLY after the browser's native
// target="_blank" navigation has fired — a
// synchronous mutation would detach the anchor
// mid-click and could suppress the new tab in some
// browsers.
// 2. The second tick calls _showRedirectSuccess(),
// which sets redirectSucceeded = true. That swaps
// the "Redirecting to Store" overlay (its ng-if
// depends on !redirectSucceeded) for the success
// overlay in a fresh digest, and queues the
// heading focus on a third tick. Splitting the
// overlay swap off prevents it from coinciding
// with the banner's ng-if teardown; chaining the
// mutations across two digests is the most
// defensive ordering against AngularJS render-time
// surprises.
$modalScope.dismissBlockedPurchaseUrl = function() {
$timeout(function() {
$modalScope.blockedPurchaseUrl = null;
$timeout(function() {
_showRedirectSuccess();
});
});
};
// Toggles the post-redirect success overlay (CPANEL-53285).
// Set once the store tab has been opened so the modal does
// not look frozen in its pre-redirect state when the user
// returns from checkout.
$modalScope.redirectSucceeded = false;
// Remember which list item opened the panel so we can restore
// focus to the matching button after Angular re-renders the
// main content (the originally-clicked DOM node is destroyed
// by `ng-if="!purchaseConfirmation"`).
var _lastFocusedDomain = null;
// Direct DOM reference to the originating element. Preferred
// over the domain-attribute lookup because it disambiguates
// duplicate `data-purchase-domain` values (e.g. the success
// banner button and a list item for the same domain).
var _lastFocusedElement = null;
// Monotonic token used to ignore stale fetchDomainPurchaseUrl
// responses after the user cancels the confirmation panel.
var _activeRequestToken = 0;
function _findRenderedButton(domain) {
if (!domain) {
return null;
}
var escaped = window.CSS && typeof window.CSS.escape === "function"
? window.CSS.escape(domain)
: /^[A-Za-z0-9.\-]+$/.test(domain) ? domain : null;
if (!escaped) {
return null;
}
var sel = '[data-purchase-domain="' + escaped + '"]';
return $document[0].querySelector(sel);
}
function _findModalHeading() {
// Fallback target if the originating button can't be
// found (e.g. the list re-loaded). Prefers whichever
// heading is currently rendered.
var doc = $document[0];
return doc.querySelector("#domainSearchModalTitle") ||
doc.querySelector("#domainSearchModalNoResultsTitle") ||
doc.querySelector("#domainSearchModalPendingTitle") ||
doc.querySelector("#domainSearchModalUnavailableTitle");
}
var FOCUSABLE_SELECTOR =
"a[href], button:not([disabled]), input:not([disabled]), " +
"select:not([disabled]), textarea:not([disabled]), " +
"[tabindex]:not([tabindex=\"-1\"])";
function _trapFocus($event, container) {
if ($event.key !== "Tab" || !container) {
return;
}
var focusable = Array.prototype.slice.call(
container.querySelectorAll(FOCUSABLE_SELECTOR)
);
if (!focusable.length) {
$event.preventDefault();
return;
}
var first = focusable[0];
var last = focusable[focusable.length - 1];
var active = $document[0].activeElement;
if ($event.shiftKey && active === first) {
last.focus();
$event.preventDefault();
} else if (!$event.shiftKey && active === last) {
first.focus();
$event.preventDefault();
}
}
function _restoreLastFocus() {
var domain = _lastFocusedDomain;
var element = _lastFocusedElement;
_lastFocusedDomain = null;
_lastFocusedElement = null;
$timeout(function() {
var target = null;
// Prefer the exact element that opened the panel,
// but only if it is still in the document (it may
// have been removed by an intervening re-render).
if (element && $document[0].contains(element)) {
target = element;
}
if (!target) {
target = _findRenderedButton(domain) || _findModalHeading();
}
if (target && typeof target.focus === "function") {
target.focus();
}
});
}
// rec must be a domain recommendation object: { domain, price, ... }
// $event is the click event; its currentTarget is used as the
// preferred focus-restoration target when the panel closes.
$modalScope.purchaseDomain = function(rec, $event) {
_lastFocusedDomain = rec && rec.domain ? rec.domain : null;
_lastFocusedElement = $event && $event.currentTarget ? $event.currentTarget : null;
$modalScope.purchaseConfirmation = rec;
// Track purchase intent at the moment the confirmation
// panel opens. Deferred via _storeConfigReady so
// _getMode() reports the correct value even when the user
// clicks before fetchStoreConfig() has settled.
_storeConfigReady.finally(function() {
_trackEvent("Domain-Purchase-Clicked", {
domain: rec && rec.domain ? rec.domain : null,
mode: _getMode(),
source: _source,
});
});
// Move focus to the new heading so screen readers
// announce the panel change. The heading carries
// tabindex="-1" specifically for this purpose.
$timeout(function() {
var heading = $document[0].querySelector("#domainSearchModalConfirmationTitle");
if (heading) {
heading.focus();
}
});
};
$modalScope.cancelPurchaseConfirmation = function() {
// Invalidate any in-flight fetchDomainPurchaseUrl call so
// its resolution cannot reopen the panel or call
// window.open() after the user has cancelled.
_activeRequestToken++;
$modalScope.purchasingDomain = null;
$modalScope.purchaseConfirmation = null;
$modalScope.redirectFailed = false;
$modalScope.redirectSucceeded = false;
$modalScope.blockedPurchaseUrl = null;
_restoreLastFocus();
};
// Stop Escape bubbling so ui-bootstrap's modal-level handler
// does not also dismiss the whole modal. Tab/Shift+Tab are
// cycled within the card to satisfy the WCAG 2.1 focus-trap
// requirement for role="dialog".
$modalScope.handleConfirmationKeydown = function($event) {
if ($event.key === "Escape") {
$modalScope.cancelPurchaseConfirmation();
$event.stopPropagation();
$event.preventDefault();
} else {
_trapFocus($event, $document[0].querySelector(
".domain-search-modal__confirmation"
));
}
};
// Surface the "Redirection Failed" overlay (Figma node
// 86:10861). purchaseConfirmation is left set so the failure
// panel can retry the redirect or the user can dismiss back
// to the confirmation panel with Escape.
function _showRedirectFailure() {
$modalScope.redirectFailed = true;
$timeout(function() {
var heading = $document[0].querySelector("#domainSearchModalFailureTitle");
if (heading) {
heading.focus();
}
});
}
// Swap the confirmation panel for the post-redirect success
// state (CPANEL-53285, option A). purchaseConfirmation is
// left set so the success panel keeps showing the purchased
// domain.
function _showRedirectSuccess() {
$modalScope.redirectSucceeded = true;
$timeout(function() {
var heading = $document[0].querySelector("#domainSearchModalSuccessTitle");
if (heading) {
heading.focus();
}
});
}
$modalScope.confirmPurchase = function() {
if (!$modalScope.purchaseConfirmation || $modalScope.purchasingDomain) {
return;
}
var domain = $modalScope.purchaseConfirmation.domain;
$modalScope.purchasingDomain = domain;
$modalScope.redirectFailed = false;
// Clear any stale popup-blocked banner from a previous
// attempt so a retry does not surface the old URL.
$modalScope.blockedPurchaseUrl = null;
// Capture the token for this request; if the user cancels
// before the promise settles, _activeRequestToken will be
// incremented and we will ignore the response below.
var token = ++_activeRequestToken;
$domainsService.fetchDomainPurchaseUrl(domain, 1)
.then(function(result) {
if (token !== _activeRequestToken) {
return;
}
var purchaseUrl = result && result.purchase_url;
if (purchaseUrl && /^https?:\/\//i.test(purchaseUrl)) {
// Fire the store-visited event BEFORE
// window.open() so it is unambiguously
// associated with the original page's network
// context. The browser context-switches when
// the new tab is created, which can otherwise
// drop the XHR from the originating page in
// browser tooling. Mode is accurate here
// because the fetchDomainPurchaseUrl round-trip
// settles well after fetchStoreConfig.
_trackEvent("Domain-Retail-Store-Visited", {
source: _source,
mode: _getMode(),
domain: domain,
store_url: purchaseUrl,
});
// Don't pass "noopener" in the features
// string: it makes window.open() return null
// even on success in some browsers, which
// would defeat the popup-blocked detection
// below. Sever the opener manually instead
// to keep the no-back-reference guarantee.
var openedWindow = window.open(purchaseUrl, "_blank");
if (openedWindow) {
openedWindow.opener = null;
$modalScope.redirectFailed = false;
// _showRedirectSuccess() leaves
// purchaseConfirmation set so the
// post-redirect success panel
// (CPANEL-53285) keeps showing the
// purchased domain — don't clear it
// here.
_showRedirectSuccess();
} else {
// Pop-up blocker fired: keep the
// "Redirecting to Store" overlay open and
// surface the URL via an inline banner
// rendered inside the overlay, just above
// the Go Back / Continue actions. Move
// focus to the fallback link once the
// banner renders so keyboard and
// screen-reader users land on the
// actionable element.
$modalScope.blockedPurchaseUrl = purchaseUrl;
$timeout(function() {
// This id must stay in sync with the
// fallback link's id in
// domainSearchModal.ptt.
var linkEl = $document[0].getElementById(
"domainSearchPopupBlockedLink"
);
if (linkEl) {
linkEl.focus();
}
});
}
} else {
_showRedirectFailure();
}
})
.catch(function() {
if (token !== _activeRequestToken) {
return;
}
_showRedirectFailure();
})
.finally(function() {
if (token === _activeRequestToken) {
$modalScope.purchasingDomain = null;
}
});
};
// "Continue" action on the "Redirection Failed" overlay —
// dismiss both the failure overlay and the confirmation panel
// and return the user to the search results. We deliberately do
// NOT re-attempt the redirect here: retrying re-fires the same
// failing fetchDomainPurchaseUrl call and traps the user in a
// failure loop (CPANEL-53113). cancelPurchaseConfirmation()
// already invalidates any in-flight request, clears the panel
// state, and restores focus to the originating button.
$modalScope.dismissRedirectFailure = function() {
$modalScope.cancelPurchaseConfirmation();
};
// Escape from the failure overlay returns to the confirmation
// panel (rather than closing the whole modal) and restores
// focus to its heading. Tab/Shift+Tab cycle within the card.
$modalScope.handleFailureKeydown = function($event) {
if ($event.key === "Escape") {
$modalScope.redirectFailed = false;
$event.stopPropagation();
$event.preventDefault();
$timeout(function() {
var heading = $document[0].querySelector("#domainSearchModalConfirmationTitle");
if (heading) {
heading.focus();
}
});
} else {
_trapFocus($event, $document[0].querySelector(
".domain-search-modal__confirmation"
));
}
};
// "Done" on the post-redirect success panel — dismiss both
// the success overlay and the surrounding search modal
// (CPANEL-53285). The domain list is intentionally not
// refreshed: there is no post-purchase automation yet, so a
// reload would not surface the new domain.
$modalScope.dismissRedirectSuccess = function() {
$modalScope.closeModal();
};
// "Add Domain" on the post-redirect success panel — hand
// the just-purchased domain back to the host (via the
// onAddDomain callback) so it can open its add-domain flow
// with the name prefilled, then dismiss this modal. Only
// rendered when a callback was supplied (see canAddDomain).
// The domain comes from
// purchaseConfirmation, which _showRedirectSuccess()
// deliberately leaves set on the success panel.
$modalScope.addPurchasedDomain = function() {
var domain = $modalScope.purchaseConfirmation &&
$modalScope.purchaseConfirmation.domain;
if (!_onAddDomain || !domain) {
return;
}
_trackEvent("Domain-Add-After-Purchase-Clicked", {
source: _source,
mode: _getMode(),
domain: domain,
});
$modalScope.closeModal();
_onAddDomain(domain);
};
// Escape from the success overlay mirrors its ✕ action and
// closes the whole modal. Returning to the search results is
// a deliberate, explicit choice via the panel's "Go Back"
// link (cancelPurchaseConfirmation); ✕, Escape, and backdrop
// are the "I'm done" dismiss gesture and are kept distinct
// from it. Tab/Shift+Tab cycle within the card.
$modalScope.handleSuccessKeydown = function($event) {
if ($event.key === "Escape") {
$modalScope.dismissRedirectSuccess();
$event.stopPropagation();
$event.preventDefault();
} else {
_trapFocus($event, $document[0].querySelector(
".domain-search-modal__confirmation"
));
}
};
$modalScope.closeModal = function() {
$uibModalInstance.dismiss("cancel");
};
if (options.autoSearch && $modalScope.searchQuery) {
// searchDomains() defers its Mixpanel call via
// _storeConfigReady internally, so mode is accurate even
// when the modal opens before fetchStoreConfig settles.
$modalScope.searchDomains();
}
},
];
/**
* Service that opens the reusable Domain Search modal.
*
* @example
* domainSearchModalService.open({
* cachedExploreData: { featured: [...], available: [...] },
* initialQuery: "catclothes",
* initialTldFilters: [".com"],
* autoSearch: true,
* });
*
* @return {Object} The $uibModal instance.
*/
module.factory("domainSearchModalService", ["$uibModal", "$document",
function($uibModal, $document) {
return {
TEMPLATE_URL: TEMPLATE_URL,
WINDOW_CLASS: WINDOW_CLASS,
ModalController: ModalController,
open: function(options) {
var modalInstance = $uibModal.open({
templateUrl: TEMPLATE_URL,
windowClass: WINDOW_CLASS,
controller: ModalController,
resolve: {
options: function() {
return options || {};
},
},
});
modalInstance.rendered.then(function() {
var dialogEl = $document[0].querySelector(
"." + WINDOW_CLASS + " [role='dialog']"
);
if (dialogEl) {
dialogEl.setAttribute(
"aria-labelledby",
"domainSearchModalTitle domainSearchModalNoResultsTitle domainSearchModalPendingTitle domainSearchModalUnavailableTitle domainSearchModalConfirmationTitle domainSearchModalSuccessTitle domainSearchModalFailureTitle"
);
}
});
return modalInstance;
},
};
},
]);
return {
namespace: MODULE_NAMESPACE,
ModalController: ModalController,
};
}
);
Back to Directory