diff --git a/docs/Bootstrap5Migration.md b/docs/Bootstrap5Migration.md index 344443cd9..9689318ea 100644 --- a/docs/Bootstrap5Migration.md +++ b/docs/Bootstrap5Migration.md @@ -329,9 +329,9 @@ These insights were gathered during the migration process and may be helpful for - [x] ~~Create a plan for jQuery removal (if desired)~~ (tracked in [issue #7600](https://github.com/compiler-explorer/compiler-explorer/issues/7600)) - [x] ~~Identify non-Bootstrap jQuery usage that would need refactoring~~ (tracked in [issue #7600](https://github.com/compiler-explorer/compiler-explorer/issues/7600)) -- [ ] Remove the temporary `bootstrap-utils.ts` compatibility layer - - [ ] Replace all uses with direct Bootstrap 5 API calls - - [ ] Document the native Bootstrap 5 API for future reference +- [x] ~~Remove the temporary `bootstrap-utils.ts` compatibility layer~~ (Decision: Keep this utility for the foreseeable future as it provides valuable functionality for jQuery-Bootstrap 5 integration) + - [x] ~~Replace all uses with direct Bootstrap 5 API calls~~ (Not necessary - updated documentation to indicate direct API usage when possible) + - [x] ~~Document the native Bootstrap 5 API for future reference~~ (Added documentation in the utilities themselves) - [ ] ~~Investigate and fix modal accessibility warnings~~ (tracked in [issue #7602](https://github.com/compiler-explorer/compiler-explorer/issues/7602)) - [ ] ~~Address the warning: "Blocked aria-hidden on an element because its descendant retained focus"~~ (part of issue #7602) - [ ] ~~Update modal template markup to leverage Bootstrap 5.3's built-in support for the `inert` attribute~~ (part of issue #7602) diff --git a/static/bootstrap-utils.ts b/static/bootstrap-utils.ts index 7e3963f36..8313ed9c2 100644 --- a/static/bootstrap-utils.ts +++ b/static/bootstrap-utils.ts @@ -23,29 +23,34 @@ // POSSIBILITY OF SUCH DAMAGE. /** - * TEMPORARY COMPATIBILITY LAYER + * Bootstrap Utilities * - * This module provides utilities to help transition from Bootstrap 4's jQuery-based API - * to Bootstrap 5's vanilla JavaScript API. This is intended as a temporary solution - * during the migration from Bootstrap 4 to 5 and should be removed once the migration - * is complete. + * This module provides utilities that bridge Bootstrap 5's vanilla JavaScript API + * with jQuery-based code in the Compiler Explorer codebase. It centralizes Bootstrap + * API interactions to provide consistent behavior across the application. * - * The goal is to minimize changes throughout the codebase by centralizing the Bootstrap - * API changes in this file, while still allowing for gradual migration to direct API calls. - * - * @deprecated This module should be removed after the Bootstrap 5 migration is complete. + * Key benefits: + * - Handles conversion between jQuery objects and DOM elements + * - Provides simplified event handling compatible with jQuery patterns + * - Maintains consistent API for Bootstrap components + * - Simplifies Bootstrap component initialization and management */ import $ from 'jquery'; import 'bootstrap'; -import {Collapse, Dropdown, Modal, Popover, Tab, Toast, Tooltip} from 'bootstrap'; +import {Dropdown, Modal, Popover, Toast} from 'bootstrap'; // Private event listener tracking map const eventListenerMap = new WeakMap>(); /** * Helper method to get an HTMLElement from various input types + * + * This is a core utility function that bridges jQuery objects with native DOM elements, + * allowing our codebase to work with both paradigms while the Bootstrap 5 API requires + * native DOM elements. + * * @param elementOrSelector Element, jQuery object, or selector * @returns HTMLElement or null */ @@ -121,36 +126,6 @@ export function setElementEventHandler( setDomElementEventHandler(element, eventName, handler); } -/** - * Initialize a modal - * @param elementOrSelector Element or selector for the modal - * @param options Modal options - * @returns Modal instance - * @throws Error if the element cannot be found - */ -export function initModal(elementOrSelector: string | HTMLElement | JQuery, options?: Partial): Modal { - const element = getElement(elementOrSelector); - if (!element) throw new Error(`Failed to find element for modal: ${elementOrSelector}`); - - return new Modal(element, options); -} - -/** - * Initialize a modal if the element exists, returning null otherwise - * @param elementOrSelector Element or selector for the modal - * @param options Modal options - * @returns Modal instance or null if the element cannot be found - */ -export function initModalIfExists( - elementOrSelector: string | HTMLElement | JQuery, - options?: Partial, -): Modal | null { - const element = getElement(elementOrSelector); - if (!element) return null; - - return new Modal(element, options); -} - /** * Get an existing modal instance for an element * @param elementOrSelector Element or selector for the modal @@ -167,6 +142,12 @@ export function getModalInstance(elementOrSelector: string | HTMLElement | JQuer * Show a modal * @param elementOrSelector Element or selector for the modal * @param relatedTarget Optional related target element + * + * Note: When possible, prefer direct Bootstrap 5 API: + * ``` + * const modal = Modal.getInstance(element) || new Modal(element); + * modal.show(relatedTarget); + * ``` */ export function showModal(elementOrSelector: string | HTMLElement | JQuery, relatedTarget?: HTMLElement): void { const element = getElement(elementOrSelector); @@ -188,50 +169,25 @@ export function hideModal(elementOrSelector: string | HTMLElement | JQuery): voi if (modal) modal.hide(); } -/** - * Initialize a toast - * @param elementOrSelector Element or selector for the toast - * @param options Toast options - * @returns Toast instance - */ -export function initToast(elementOrSelector: string | HTMLElement | JQuery, options?: Partial): Toast { - const element = getElement(elementOrSelector); - if (!element) throw new Error(`Failed to find element for toast: ${elementOrSelector}`); - - return new Toast(element, options); -} - -/** - * Initialize a toast if the element exists - * @param elementOrSelector Element or selector for the toast - * @param options Toast options - * @returns Toast instance or null if element doesn't exist - */ -export function initToastIfExists( - elementOrSelector: string | HTMLElement | JQuery, - options?: Partial, -): Toast | null { - const element = getElement(elementOrSelector); - if (!element) return null; - - return new Toast(element, options); -} - /** * Show a toast * @param elementOrSelector Element or selector for the toast + * @param options Optional Toast options if a new instance needs to be created */ -export function showToast(elementOrSelector: string | HTMLElement | JQuery): void { +export function showToast(elementOrSelector: string | HTMLElement | JQuery, options?: Partial): void { const element = getElement(elementOrSelector); if (!element) return; - const toast = Toast.getInstance(element) || new Toast(element); + const toast = Toast.getInstance(element) || new Toast(element, options); toast.show(); } /** * Hide a toast * @param elementOrSelector Element or selector for the toast + * + * Note: When possible, prefer direct Bootstrap 5 API: + * `const toast = Toast.getInstance(element); if (toast) toast.hide();` */ export function hideToast(elementOrSelector: string | HTMLElement | JQuery): void { const element = getElement(elementOrSelector); @@ -241,39 +197,6 @@ export function hideToast(elementOrSelector: string | HTMLElement | JQuery): voi if (toast) toast.hide(); } -/** - * Initialize a dropdown - * @param elementOrSelector Element or selector for the dropdown - * @param options Dropdown options - * @returns Dropdown instance - * @throws Error if the element cannot be found - */ -export function initDropdown( - elementOrSelector: string | HTMLElement | JQuery, - options?: Partial, -): Dropdown { - const element = getElement(elementOrSelector); - if (!element) throw new Error(`Failed to find element for dropdown: ${elementOrSelector}`); - - return new Dropdown(element, options); -} - -/** - * Initialize a dropdown if the element exists, returning null otherwise - * @param elementOrSelector Element or selector for the dropdown - * @param options Dropdown options - * @returns Dropdown instance or null if the element cannot be found - */ -export function initDropdownIfExists( - elementOrSelector: string | HTMLElement | JQuery, - options?: Partial, -): Dropdown | null { - const element = getElement(elementOrSelector); - if (!element) return null; - - return new Dropdown(element, options); -} - /** * Get an existing dropdown instance for an element * @param elementOrSelector Element or selector for the dropdown @@ -301,6 +224,9 @@ export function showDropdown(elementOrSelector: string | HTMLElement | JQuery): /** * Hide a dropdown * @param elementOrSelector Element or selector for the dropdown + * + * Note: When possible, prefer direct Bootstrap 5 API: + * `const dropdown = Dropdown.getInstance(element); if (dropdown) dropdown.hide();` */ export function hideDropdown(elementOrSelector: string | HTMLElement | JQuery): void { const element = getElement(elementOrSelector); @@ -310,38 +236,6 @@ export function hideDropdown(elementOrSelector: string | HTMLElement | JQuery): if (dropdown) dropdown.hide(); } -/** - * Initialize a tooltip - * @param elementOrSelector Element or selector for the tooltip - * @param options Tooltip options - * @returns Tooltip instance - */ -export function initTooltip( - elementOrSelector: string | HTMLElement | JQuery, - options?: Partial, -): Tooltip { - const element = getElement(elementOrSelector); - if (!element) throw new Error(`Failed to find element for tooltip: ${elementOrSelector}`); - - return new Tooltip(element, options); -} - -/** - * Initialize a tooltip if the element exists - * @param elementOrSelector Element or selector for the tooltip - * @param options Tooltip options - * @returns Tooltip instance or null if element doesn't exist - */ -export function initTooltipIfExists( - elementOrSelector: string | HTMLElement | JQuery, - options?: Partial, -): Tooltip | null { - const element = getElement(elementOrSelector); - if (!element) return null; - - return new Tooltip(element, options); -} - /** * Initialize a popover * @param elementOrSelector Element or selector for the popover @@ -387,62 +281,6 @@ export function getPopoverInstance(elementOrSelector: string | HTMLElement | JQu return Popover.getInstance(element); } -/** - * Initialize a tab - * @param elementOrSelector Element or selector for the tab - * @returns Tab instance - */ -export function initTab(elementOrSelector: string | HTMLElement | JQuery): Tab { - const element = getElement(elementOrSelector); - if (!element) throw new Error(`Failed to find element for tab: ${elementOrSelector}`); - - return new Tab(element); -} - -/** - * Initialize a tab if the element exists - * @param elementOrSelector Element or selector for the tab - * @returns Tab instance or null if element doesn't exist - */ -export function initTabIfExists(elementOrSelector: string | HTMLElement | JQuery): Tab | null { - const element = getElement(elementOrSelector); - if (!element) return null; - - return new Tab(element); -} - -/** - * Initialize a collapse - * @param elementOrSelector Element or selector for the collapse - * @param options Collapse options - * @returns Collapse instance - */ -export function initCollapse( - elementOrSelector: string | HTMLElement | JQuery, - options?: Partial, -): Collapse { - const element = getElement(elementOrSelector); - if (!element) throw new Error(`Failed to find element for collapse: ${elementOrSelector}`); - - return new Collapse(element, options); -} - -/** - * Initialize a collapse if the element exists - * @param elementOrSelector Element or selector for the collapse - * @param options Collapse options - * @returns Collapse instance or null if element doesn't exist - */ -export function initCollapseIfExists( - elementOrSelector: string | HTMLElement | JQuery, - options?: Partial, -): Collapse | null { - const element = getElement(elementOrSelector); - if (!element) return null; - - return new Collapse(element, options); -} - /** * Hide an existing popover if it exists * @param elementOrSelector Element or selector for the popover @@ -451,21 +289,3 @@ export function hidePopover(elementOrSelector: string | HTMLElement | JQuery): v const popover = getPopoverInstance(elementOrSelector); if (popover) popover.hide(); } - -/** - * Show an existing popover if it exists - * @param elementOrSelector Element or selector for the popover - */ -export function showPopover(elementOrSelector: string | HTMLElement | JQuery): void { - const popover = getPopoverInstance(elementOrSelector); - if (popover) popover.show(); -} - -/** - * Show an existing modal if it exists (uses existing instance only) - * @param elementOrSelector Element or selector for the modal - */ -export function showModalIfExists(elementOrSelector: string | HTMLElement | JQuery): void { - const modal = getModalInstance(elementOrSelector); - if (modal) modal.show(); -} diff --git a/static/sharing.ts b/static/sharing.ts index fe4f38056..ee489529b 100644 --- a/static/sharing.ts +++ b/static/sharing.ts @@ -131,7 +131,6 @@ export class Sharing { }); this.layout.on('stateChanged', this.onStateChanged.bind(this)); - BootstrapUtils.initModal(this.shareLinkDialog); this.shareLinkDialog.addEventListener('show.bs.modal', this.onOpenModalPane.bind(this)); this.shareLinkDialog.addEventListener('hidden.bs.modal', this.onCloseModalPane.bind(this)); @@ -334,7 +333,7 @@ export class Sharing { // Create and show new tooltip try { - const tooltip = BootstrapUtils.initTooltip(tooltipEl, { + const tooltip = new Tooltip(tooltipEl, { placement: 'bottom', trigger: 'manual', title: message, diff --git a/static/widgets/alert.ts b/static/widgets/alert.ts index 304659cf0..6e22145da 100644 --- a/static/widgets/alert.ts +++ b/static/widgets/alert.ts @@ -22,6 +22,7 @@ // ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE // POSSIBILITY OF SUCH DAMAGE. +import {Toast} from 'bootstrap'; import $ from 'jquery'; import * as BootstrapUtils from '../bootstrap-utils.js'; @@ -123,7 +124,7 @@ export class Alert { delay: dismissTime, }; - BootstrapUtils.initToast(newElement, toastOptions); + new Toast(newElement[0], toastOptions); if (group !== '') { if (collapseSimilar) {