From: Mark Otto Date: Wed, 24 Sep 2025 16:10:28 +0000 (-0700) Subject: Update v6 to Floating UI X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=166d5e7a13fd711e79fcac2f0ad85077ffdbf8e3;p=thirdparty%2Fbootstrap.git Update v6 to Floating UI --- diff --git a/build/generate-sri.mjs b/build/generate-sri.mjs index 5622843f34..ec178ff816 100644 --- a/build/generate-sri.mjs +++ b/build/generate-sri.mjs @@ -42,8 +42,8 @@ const files = [ configPropertyName: 'js_bundle_hash' }, { - file: 'node_modules/@popperjs/core/dist/umd/popper.min.js', - configPropertyName: 'popper_hash' + file: 'node_modules/@floating-ui/dom/dist/floating-ui.dom.umd.min.js', + configPropertyName: 'floating_ui_hash' } ] diff --git a/build/rollup.config.mjs b/build/rollup.config.mjs index dd6c7d13e6..46369de5bf 100644 --- a/build/rollup.config.mjs +++ b/build/rollup.config.mjs @@ -12,7 +12,7 @@ const BUNDLE = process.env.BUNDLE === 'true' const ESM = process.env.ESM === 'true' let destinationFile = `bootstrap${ESM ? '.esm' : ''}` -const external = ['@popperjs/core'] +const external = ['@floating-ui/dom'] const plugins = [ babel({ // Only transpile our source code @@ -22,14 +22,14 @@ const plugins = [ }) ] const globals = { - '@popperjs/core': 'Popper' + '@floating-ui/dom': 'FloatingUIDOM' } if (BUNDLE) { destinationFile += '.bundle' - // Remove last entry in external array to bundle Popper + // Remove last entry in external array to bundle FloatingUI external.pop() - delete globals['@popperjs/core'] + delete globals['@floating-ui/dom'] plugins.push( replace({ 'process.env.NODE_ENV': '"production"', diff --git a/config.yml b/config.yml index 24d49eae57..8fd8438ddd 100644 --- a/config.yml +++ b/config.yml @@ -42,9 +42,9 @@ cdn: js_hash: "sha384-G/EV+4j2dNv+tEPo3++6LCgdCROaejBqfUeNjuKAiuXbjrxilcCdDz6ZAVfHWe1Y" js_bundle: "https://cdn.jsdelivr.net/npm/bootstrap@5.3.8/dist/js/bootstrap.bundle.min.js" js_bundle_hash: "sha384-FKyoEForCGlyvwx9Hj09JcYn3nv7wiPVlz7YYwJrWVcXK/BmnVDxM+D2scQbITxI" - popper: "https://cdn.jsdelivr.net/npm/@popperjs/core@2.11.8/dist/umd/popper.min.js" - popper_hash: "sha384-I7E8VVD/ismYTF4hNIPjVp/Zjvgyol6VFvRkX/vR+Vc4jQkC+hVqc2pM8ODewa9r" - popper_esm: "https://cdn.jsdelivr.net/npm/@popperjs/core@2.11.8/dist/esm/popper.min.js" + floating_ui: "https://cdn.jsdelivr.net/npm/@floating-ui/dom@1.6.12/dist/floating-ui.dom.umd.min.js" + floating_ui_hash: "sha384-Os8n9bzoYJ/ESbGD7cW0VOTLk0hO++SO+Y4swXBE2dHrxiZkjADEr5ZGOcc9CorD" + floating_ui_esm: "https://cdn.jsdelivr.net/npm/@floating-ui/dom@1.6.12/dist/floating-ui.dom.min.js" anchors: min: 2 diff --git a/js/src/dropdown.js b/js/src/dropdown.js index a31f801d5b..edada78aa9 100644 --- a/js/src/dropdown.js +++ b/js/src/dropdown.js @@ -5,21 +5,18 @@ * -------------------------------------------------------------------------- */ -import * as Popper from '@popperjs/core' +import { inline, offset, shift } from '@floating-ui/dom' import BaseComponent from './base-component.js' import EventHandler from './dom/event-handler.js' -import Manipulator from './dom/manipulator.js' import SelectorEngine from './dom/selector-engine.js' import { execute, - getElement, getNextActiveElement, isDisabled, - isElement, - isRTL, isVisible, noop } from './util/index.js' +import FloatingUi from './util/floating-ui.js' /** * Constants @@ -58,30 +55,28 @@ const SELECTOR_NAVBAR = '.navbar' const SELECTOR_NAVBAR_NAV = '.navbar-nav' const SELECTOR_VISIBLE_ITEMS = '.dropdown-menu .dropdown-item:not(.disabled):not(:disabled)' -const PLACEMENT_TOP = isRTL() ? 'top-end' : 'top-start' -const PLACEMENT_TOPEND = isRTL() ? 'top-start' : 'top-end' -const PLACEMENT_BOTTOM = isRTL() ? 'bottom-end' : 'bottom-start' -const PLACEMENT_BOTTOMEND = isRTL() ? 'bottom-start' : 'bottom-end' -const PLACEMENT_RIGHT = isRTL() ? 'left-start' : 'right-start' -const PLACEMENT_LEFT = isRTL() ? 'right-start' : 'left-start' const PLACEMENT_TOPCENTER = 'top' +const PLACEMENT_TOPEND = 'top-end' +const PLACEMENT_TOP = 'top-start' const PLACEMENT_BOTTOMCENTER = 'bottom' +const PLACEMENT_BOTTOMEND = 'bottom-end' +const PLACEMENT_BOTTOM = 'bottom-start' +const PLACEMENT_RIGHT = 'right-start' +const PLACEMENT_LEFT = 'left-start' const Default = { autoClose: true, - boundary: 'clippingParents', display: 'dynamic', - offset: [0, 2], - popperConfig: null, + offset: 10, + positionConfig: null, reference: 'toggle' } const DefaultType = { autoClose: '(boolean|string)', - boundary: '(string|element)', display: 'string', - offset: '(array|string|function)', - popperConfig: '(null|object|function)', + offset: '(number|array|string|function)', + positionConfig: '(null|object|function)', reference: '(string|element|object)' } @@ -93,13 +88,12 @@ class Dropdown extends BaseComponent { constructor(element, config) { super(element, config) - this._popper = null this._parent = this._element.parentNode // dropdown wrapper // TODO: v6 revert #37011 & change markup https://getbootstrap.com/docs/5.3/forms/input-group/ this._menu = SelectorEngine.next(this._element, SELECTOR_MENU)[0] || SelectorEngine.prev(this._element, SELECTOR_MENU)[0] || SelectorEngine.findOne(SELECTOR_MENU, this._parent) - this._inNavbar = this._detectNavbar() + this._positionHelper = new FloatingUi(this._element) } // Getters @@ -135,7 +129,7 @@ class Dropdown extends BaseComponent { return } - this._createPopper() + this.update() // If this is a touch-enabled device we add extra // empty mouseover listeners to the body's immediate children; @@ -167,19 +161,9 @@ class Dropdown extends BaseComponent { this._completeHide(relatedTarget) } - dispose() { - if (this._popper) { - this._popper.destroy() - } - - super.dispose() - } - update() { - this._inNavbar = this._detectNavbar() - if (this._popper) { - this._popper.update() - } + const reference = this._positionHelper.getReferenceElement(this._config.reference, this._parent, NAME) + this._positionHelper.calculate(reference, this._menu, this._getFloatingUiConfig()) } // Private @@ -197,47 +181,28 @@ class Dropdown extends BaseComponent { } } - if (this._popper) { - this._popper.destroy() - } - this._menu.classList.remove(CLASS_NAME_SHOW) this._element.classList.remove(CLASS_NAME_SHOW) this._element.setAttribute('aria-expanded', 'false') - Manipulator.removeDataAttribute(this._menu, 'popper') + this._positionHelper.stop() EventHandler.trigger(this._element, EVENT_HIDDEN, relatedTarget) } - _getConfig(config) { - config = super._getConfig(config) - - if (typeof config.reference === 'object' && !isElement(config.reference) && - typeof config.reference.getBoundingClientRect !== 'function' - ) { - // Popper virtual elements require a getBoundingClientRect method - throw new TypeError(`${NAME.toUpperCase()}: Option "reference" provided type "object" without a required "getBoundingClientRect" method.`) + _getFloatingUiConfig() { + const defaultBsConfig = { + placement: this._getPlacement(), + middleware: [offset(this._positionHelper.parseOffset(this._config.offset)), shift()] } - return config - } - - _createPopper() { - if (typeof Popper === 'undefined') { - throw new TypeError('Bootstrap\'s dropdowns require Popper (https://popper.js.org/docs/v2/)') + // Disable positioning if we have a static display or Dropdown is in Navbar + if (this._detectNavbar() || this._config.display === 'static') { + defaultBsConfig.middleware.push(inline()) } - let referenceElement = this._element - - if (this._config.reference === 'parent') { - referenceElement = this._parent - } else if (isElement(this._config.reference)) { - referenceElement = getElement(this._config.reference) - } else if (typeof this._config.reference === 'object') { - referenceElement = this._config.reference + return { + ...defaultBsConfig, + ...execute(this._config.positionConfig, [undefined, defaultBsConfig]) } - - const popperConfig = this._getPopperConfig() - this._popper = Popper.createPopper(referenceElement, this._menu, popperConfig) } _isShown() { @@ -247,20 +212,15 @@ class Dropdown extends BaseComponent { _getPlacement() { const parentDropdown = this._parent - if (parentDropdown.classList.contains(CLASS_NAME_DROPEND)) { - return PLACEMENT_RIGHT + const matches = { + [CLASS_NAME_DROPEND]: PLACEMENT_RIGHT, + [CLASS_NAME_DROPSTART]: PLACEMENT_LEFT, + [CLASS_NAME_DROPUP_CENTER]: PLACEMENT_TOPCENTER, + [CLASS_NAME_DROPDOWN_CENTER]: PLACEMENT_BOTTOMCENTER } - - if (parentDropdown.classList.contains(CLASS_NAME_DROPSTART)) { - return PLACEMENT_LEFT - } - - if (parentDropdown.classList.contains(CLASS_NAME_DROPUP_CENTER)) { - return PLACEMENT_TOPCENTER - } - - if (parentDropdown.classList.contains(CLASS_NAME_DROPDOWN_CENTER)) { - return PLACEMENT_BOTTOMCENTER + const match = Object.keys(matches).find(keyClass => parentDropdown.classList.contains(keyClass)) + if (match) { + return matches[match] } // We need to trim the value because custom properties can also include spaces @@ -277,52 +237,6 @@ class Dropdown extends BaseComponent { return this._element.closest(SELECTOR_NAVBAR) !== null } - _getOffset() { - const { offset } = this._config - - if (typeof offset === 'string') { - return offset.split(',').map(value => Number.parseInt(value, 10)) - } - - if (typeof offset === 'function') { - return popperData => offset(popperData, this._element) - } - - return offset - } - - _getPopperConfig() { - const defaultBsPopperConfig = { - placement: this._getPlacement(), - modifiers: [{ - name: 'preventOverflow', - options: { - boundary: this._config.boundary - } - }, - { - name: 'offset', - options: { - offset: this._getOffset() - } - }] - } - - // Disable Popper if we have a static display or Dropdown is in Navbar - if (this._inNavbar || this._config.display === 'static') { - Manipulator.setDataAttribute(this._menu, 'popper', 'static') // TODO: v6 remove - defaultBsPopperConfig.modifiers = [{ - name: 'applyStyles', - enabled: false - }] - } - - return { - ...defaultBsPopperConfig, - ...execute(this._config.popperConfig, [undefined, defaultBsPopperConfig]) - } - } - _selectMenuItem({ key, target }) { const items = SelectorEngine.find(SELECTOR_VISIBLE_ITEMS, this._menu).filter(element => isVisible(element)) diff --git a/js/src/popover.js b/js/src/popover.js index b8383dc0d5..74cbe9c20e 100644 --- a/js/src/popover.js +++ b/js/src/popover.js @@ -22,9 +22,10 @@ const Default = { offset: [0, 8], placement: 'right', template: '', trigger: 'click' } diff --git a/js/src/tooltip.js b/js/src/tooltip.js index ca603d20d0..008f79461d 100644 --- a/js/src/tooltip.js +++ b/js/src/tooltip.js @@ -5,15 +5,18 @@ * -------------------------------------------------------------------------- */ -import * as Popper from '@popperjs/core' +import { + flip, hide, offset, shift +} from '@floating-ui/dom' import BaseComponent from './base-component.js' import EventHandler from './dom/event-handler.js' -import Manipulator from './dom/manipulator.js' import { - execute, findShadowRoot, getElement, getUID, isRTL, noop + execute, findShadowRoot, getElement, getUID, isVisible, noop } from './util/index.js' +import Manipulator from './dom/manipulator.js' import { DefaultAllowlist } from './util/sanitizer.js' import TemplateFactory from './util/template-factory.js' +import FloatingUi from './util/floating-ui.js' /** * Constants @@ -50,30 +53,28 @@ const EVENT_MOUSELEAVE = 'mouseleave' const AttachmentMap = { AUTO: 'auto', TOP: 'top', - RIGHT: isRTL() ? 'left' : 'right', + RIGHT: 'right', BOTTOM: 'bottom', - LEFT: isRTL() ? 'right' : 'left' + LEFT: 'left' } const Default = { allowList: DefaultAllowlist, animation: true, - boundary: 'clippingParents', container: false, customClass: '', delay: 0, fallbackPlacements: ['top', 'right', 'bottom', 'left'], html: false, - offset: [0, 6], + offset: 0, placement: 'top', - popperConfig: null, + positionConfig: null, sanitize: true, sanitizeFn: null, selector: false, template: '', + '
' + + '', title: '', trigger: 'hover focus' } @@ -81,15 +82,14 @@ const Default = { const DefaultType = { allowList: 'object', animation: 'boolean', - boundary: '(string|element)', container: '(string|element|boolean)', customClass: '(string|function)', delay: '(number|object)', fallbackPlacements: 'array', html: 'boolean', - offset: '(array|string|function)', + offset: '(number|array|string|function)', placement: '(string|function)', - popperConfig: '(null|object|function)', + positionConfig: '(null|object|function)', sanitize: 'boolean', sanitizeFn: '(null|function)', selector: '(string|boolean)', @@ -104,10 +104,6 @@ const DefaultType = { class Tooltip extends BaseComponent { constructor(element, config) { - if (typeof Popper === 'undefined') { - throw new TypeError('Bootstrap\'s tooltips require Popper (https://popper.js.org/docs/v2/)') - } - super(element, config) // Private @@ -115,7 +111,7 @@ class Tooltip extends BaseComponent { this._timeout = 0 this._isHovered = null this._activeTrigger = {} - this._popper = null + this._positionHelper = new FloatingUi(this._element) this._templateFactory = null this._newContent = null @@ -182,7 +178,7 @@ class Tooltip extends BaseComponent { } show() { - if (this._element.style.display === 'none') { + if (!isVisible(this._element)) { throw new Error('Please use show on visible elements') } @@ -199,22 +195,14 @@ class Tooltip extends BaseComponent { } // TODO: v6 remove this or make it optional - this._disposePopper() - - const tip = this._getTipElement() - - this._element.setAttribute('aria-describedby', tip.getAttribute('id')) - - const { container } = this._config - - if (!this._element.ownerDocument.documentElement.contains(this.tip)) { - container.append(tip) - EventHandler.trigger(this._element, this.constructor.eventName(EVENT_INSERTED)) + if (this.tip) { + this.tip.remove() + this.tip = null } - this._popper = this._createPopper(tip) + this.update() - tip.classList.add(CLASS_NAME_SHOW) + this.tip.classList.add(CLASS_NAME_SHOW) // If this is a touch-enabled device we add extra // empty mouseover listeners to the body's immediate children; @@ -271,7 +259,8 @@ class Tooltip extends BaseComponent { } if (!this._isHovered) { - this._disposePopper() + this._positionHelper.stop() + this.tip.remove() } this._element.removeAttribute('aria-describedby') @@ -282,9 +271,7 @@ class Tooltip extends BaseComponent { } update() { - if (this._popper) { - this._popper.update() - } + this._positionHelper.calculate(this._element, this._getTipElement(), this._getFloatingUiConfig(), { position: 'fixed' }) } // Protected @@ -295,6 +282,14 @@ class Tooltip extends BaseComponent { _getTipElement() { if (!this.tip) { this.tip = this._createTipElement(this._newContent || this._getContentForTemplate()) + this._element.setAttribute('aria-describedby', this.tip.getAttribute('id')) + } + + const { container } = this._config + + if (!this._element.ownerDocument.documentElement.contains(this.tip)) { + container.append(this.tip) + EventHandler.trigger(this._element, this.constructor.eventName(EVENT_INSERTED)) } return this.tip @@ -309,8 +304,6 @@ class Tooltip extends BaseComponent { } tip.classList.remove(CLASS_NAME_FADE, CLASS_NAME_SHOW) - // TODO: v6 the following can be achieved with CSS only - tip.classList.add(`bs-${this.constructor.NAME}-auto`) const tipId = getUID(this.constructor.NAME).toString() @@ -326,7 +319,7 @@ class Tooltip extends BaseComponent { setContent(content) { this._newContent = content if (this._isShown()) { - this._disposePopper() + this._positionHelper.stop() this.show() } } @@ -370,77 +363,33 @@ class Tooltip extends BaseComponent { return this.tip && this.tip.classList.contains(CLASS_NAME_SHOW) } - _createPopper(tip) { - const placement = execute(this._config.placement, [this, tip, this._element]) - const attachment = AttachmentMap[placement.toUpperCase()] - return Popper.createPopper(this._element, tip, this._getPopperConfig(attachment)) - } - - _getOffset() { - const { offset } = this._config - - if (typeof offset === 'string') { - return offset.split(',').map(value => Number.parseInt(value, 10)) + _getFloatingUiConfig() { + const defaultBsConfig = { + strategy: 'fixed', + placement: this._getPlacement(), + middleware: [ + offset(this._positionHelper.parseOffset(this._config.offset)), + flip({ fallbackPlacements: this._config.fallbackPlacements }), + shift(), + hide() + ] } - if (typeof offset === 'function') { - return popperData => offset(popperData, this._element) + return { + ...defaultBsConfig, + ...execute(this._config.positionConfig, [undefined, defaultBsConfig]) } + } - return offset + _getPlacement() { + const placement = execute(this._config.placement, [this, this.tip, this._element]) + return AttachmentMap[placement.toUpperCase()] } _resolvePossibleFunction(arg) { return execute(arg, [this._element, this._element]) } - _getPopperConfig(attachment) { - const defaultBsPopperConfig = { - placement: attachment, - modifiers: [ - { - name: 'flip', - options: { - fallbackPlacements: this._config.fallbackPlacements - } - }, - { - name: 'offset', - options: { - offset: this._getOffset() - } - }, - { - name: 'preventOverflow', - options: { - boundary: this._config.boundary - } - }, - { - name: 'arrow', - options: { - element: `.${this.constructor.NAME}-arrow` - } - }, - { - name: 'preSetPlacement', - enabled: true, - phase: 'beforeMain', - fn: data => { - // Pre-set Popper's placement attribute in order to read the arrow sizes properly. - // Otherwise, Popper mixes up the width and height dimensions since the initial arrow style is for top placement - this._getTipElement().setAttribute('data-popper-placement', data.state.placement) - } - } - ] - } - - return { - ...defaultBsPopperConfig, - ...execute(this._config.popperConfig, [undefined, defaultBsPopperConfig]) - } - } - _setListeners() { const triggers = this._config.trigger.split(' ') @@ -593,17 +542,5 @@ class Tooltip extends BaseComponent { // `Object.fromEntries(keysWithDifferentValues)` return config } - - _disposePopper() { - if (this._popper) { - this._popper.destroy() - this._popper = null - } - - if (this.tip) { - this.tip.remove() - this.tip = null - } - } } export default Tooltip diff --git a/js/src/util/floating-ui.js b/js/src/util/floating-ui.js new file mode 100644 index 0000000000..bad1249d20 --- /dev/null +++ b/js/src/util/floating-ui.js @@ -0,0 +1,98 @@ +/** + * -------------------------------------------------------------------------- + * Bootstrap floating-ui.js + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) + * -------------------------------------------------------------------------- + */ + +import { autoUpdate, computePosition } from '@floating-ui/dom' +import Manipulator from '../dom/manipulator.js' +import { getElement, isElement } from './index.js' + +/** + * Class definition + */ +class FloatingUi { + constructor(element) { + if (typeof computePosition === 'undefined') { + throw new TypeError('Bootstrap\'s tooltips and dropdowns require Floating UI (https://floating-ui.com/)') + } + + this._element = element + this._cleanup = null + } + + calculate(reference, floatingEl, config, extraCss = {}) { + this._cleanup = autoUpdate(reference, floatingEl, () => { + computePosition(reference, floatingEl, config) + .then(({ x, y, placement, middlewareData }) => { + const positionCss = { + left: `${x}px`, + top: `${y}px` + } + + if (middlewareData.hide) { + const { referenceHidden } = middlewareData.hide + + Object.assign(floatingEl.style, { + visibility: referenceHidden ? 'hidden' : 'visible' + }) + } + + Object.assign(floatingEl.style, { ...positionCss, ...extraCss }) + Manipulator.setDataAttribute(floatingEl, 'placement', placement) + }) + }) + } + + stop() { + if (this._cleanup) { + this._cleanup() + } + } + + getReferenceElement(reference, parent, PluginName) { + if (reference === 'parent') { + return parent + } + + if (isElement(reference)) { + return getElement(reference) + } + + if (typeof reference === 'object') { + if (typeof reference.getBoundingClientRect !== 'function') { + throw new TypeError(`${PluginName.toUpperCase()}: Option "reference" provided type "object" without a required "getBoundingClientRect" method.`) + } + + return reference + } + + return this._element + } + + parseOffset(value) { + if (typeof value === 'function') { + return popperData => value(popperData, this._element) + } + + if (typeof value === 'string') { + const values = value.split(',') + value = [ + Number.parseInt(values[0], 10), + Number.parseInt(values[1] || 0, 10) + ] + } + + if (Array.isArray(value)) { + return { + mainAxis: value[0], + crossAxis: value[1] || 0 + } + } + + return value + } +} + +export default FloatingUi diff --git a/package-lock.json b/package-lock.json index 4a3c716ca8..284ad2f668 100644 --- a/package-lock.json +++ b/package-lock.json @@ -18,6 +18,9 @@ } ], "license": "MIT", + "dependencies": { + "@floating-ui/dom": "^1.0.0" + }, "devDependencies": { "@astrojs/check": "^0.9.4", "@astrojs/markdown-remark": "^6.3.6", @@ -28,7 +31,7 @@ "@babel/core": "^7.28.4", "@babel/preset-env": "^7.28.3", "@docsearch/js": "^3.9.0", - "@popperjs/core": "^2.11.8", + "@floating-ui/dom": "^1.0.0", "@rollup/plugin-babel": "^6.0.4", "@rollup/plugin-commonjs": "^28.0.6", "@rollup/plugin-node-resolve": "^16.0.1", @@ -94,7 +97,7 @@ "zod": "^4.1.9" }, "peerDependencies": { - "@popperjs/core": "^2.11.8" + "@floating-ui/dom": "^1.0.0" } }, "node_modules/@adobe/css-tools": { @@ -2971,6 +2974,31 @@ "node": "^12.22.0 || ^14.17.0 || >=16.0.0" } }, + "node_modules/@floating-ui/core": { + "version": "1.7.3", + "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.7.3.tgz", + "integrity": "sha512-sGnvb5dmrJaKEZ+LDIpguvdX3bDlEllmv4/ClQ9awcmCZrlx5jQyyMWFM5kBI+EyNOCDDiKk8il0zeuX3Zlg/w==", + "dev": true, + "dependencies": { + "@floating-ui/utils": "^0.2.10" + } + }, + "node_modules/@floating-ui/dom": { + "version": "1.7.4", + "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.7.4.tgz", + "integrity": "sha512-OOchDgh4F2CchOX94cRVqhvy7b3AFb+/rQXyswmzmGakRfkMgoWVjfnLWkRirfLEfuD4ysVW16eXzwt3jHIzKA==", + "dev": true, + "dependencies": { + "@floating-ui/core": "^1.7.3", + "@floating-ui/utils": "^0.2.10" + } + }, + "node_modules/@floating-ui/utils": { + "version": "0.2.10", + "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.10.tgz", + "integrity": "sha512-aGTxbpbg8/b5JfU1HXSrbH3wXZuLPJcNEcZQFMxLs3oSzgtVu6nFPkbbGGUvBcUjKV2YyB9Wxxabo+HEH9tcRQ==", + "dev": true + }, "node_modules/@humanwhocodes/config-array": { "version": "0.13.0", "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.13.0.tgz", @@ -4099,17 +4127,6 @@ "node": ">=14" } }, - "node_modules/@popperjs/core": { - "version": "2.11.8", - "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz", - "integrity": "sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==", - "dev": true, - "license": "MIT", - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/popperjs" - } - }, "node_modules/@rollup/plugin-babel": { "version": "6.0.4", "resolved": "https://registry.npmjs.org/@rollup/plugin-babel/-/plugin-babel-6.0.4.tgz", diff --git a/package.json b/package.json index 5102b58bf3..1b0066115a 100644 --- a/package.json +++ b/package.json @@ -105,7 +105,10 @@ "astro-preview": "astro preview --root site --port 9001" }, "peerDependencies": { - "@popperjs/core": "^2.11.8" + "@floating-ui/dom": "^1.0.0" + }, + "dependencies": { + "@floating-ui/dom": "^1.0.0" }, "devDependencies": { "@astrojs/check": "^0.9.4", @@ -117,7 +120,7 @@ "@babel/core": "^7.28.4", "@babel/preset-env": "^7.28.3", "@docsearch/js": "^3.9.0", - "@popperjs/core": "^2.11.8", + "@floating-ui/dom": "^1.0.0", "@rollup/plugin-babel": "^6.0.4", "@rollup/plugin-commonjs": "^28.0.6", "@rollup/plugin-node-resolve": "^16.0.1", @@ -195,16 +198,16 @@ "directories": { "lib": "dist" }, - "shim": { - "js/bootstrap": { - "deps": [ - "@popperjs/core" - ] - } - }, + "shim": { + "js/bootstrap": { + "deps": [ + "@floating-ui/dom" + ] + } + }, "dependencies": {}, - "peerDependencies": { - "@popperjs/core": "^2.11.8" - } + "peerDependencies": { + "@floating-ui/dom": "^1.0.0" + } } } diff --git a/scss/_dropdown.scss b/scss/_dropdown.scss index 2de07ffae3..16b4616f5f 100644 --- a/scss/_dropdown.scss +++ b/scss/_dropdown.scss @@ -123,11 +123,6 @@ $dropdown-dark-header-color: $gray-500 !default; @include border-radius(var(--#{$prefix}dropdown-border-radius)); @include box-shadow(var(--#{$prefix}dropdown-box-shadow)); - &[data-bs-popper] { - top: 100%; - left: 0; - margin-top: var(--#{$prefix}dropdown-spacer); - } @if $dropdown-padding-y == 0 { > .dropdown-item:first-child, @@ -144,7 +139,7 @@ $dropdown-dark-header-color: $gray-500 !default; // scss-docs-start responsive-breakpoints // We deliberately hardcode the `bs-` prefix because we check - // this custom property in JS to determine Popper's positioning + // this custom property in JS to determine positioning @each $breakpoint in map.keys($grid-breakpoints) { @include media-breakpoint-up($breakpoint) { @@ -152,20 +147,10 @@ $dropdown-dark-header-color: $gray-500 !default; .dropdown-menu#{$infix}-start { --bs-position: start; - - &[data-bs-popper] { - right: auto; - left: 0; - } } .dropdown-menu#{$infix}-end { --bs-position: end; - - &[data-bs-popper] { - right: 0; - left: auto; - } } } } @@ -174,26 +159,12 @@ $dropdown-dark-header-color: $gray-500 !default; // Allow for dropdowns to go bottom up (aka, dropup-menu) // Just add .dropup after the standard .dropdown class and you're set. .dropup { - .dropdown-menu[data-bs-popper] { - top: auto; - bottom: 100%; - margin-top: 0; - margin-bottom: var(--#{$prefix}dropdown-spacer); - } - .dropdown-toggle { @include caret(up); } } .dropend { - .dropdown-menu[data-bs-popper] { - top: 0; - right: auto; - left: 100%; - margin-top: 0; - margin-left: var(--#{$prefix}dropdown-spacer); - } .dropdown-toggle { @include caret(end); @@ -204,14 +175,6 @@ $dropdown-dark-header-color: $gray-500 !default; } .dropstart { - .dropdown-menu[data-bs-popper] { - top: 0; - right: 100%; - left: auto; - margin-top: 0; - margin-right: var(--#{$prefix}dropdown-spacer); - } - .dropdown-toggle { @include caret(start); &::before { diff --git a/scss/_popover.scss b/scss/_popover.scss index 3093ed265b..a64bf1bfdb 100644 --- a/scss/_popover.scss +++ b/scss/_popover.scss @@ -54,6 +54,9 @@ $popover-arrow-height: .5rem !default; --#{$prefix}popover-arrow-border: var(--#{$prefix}popover-border-color); // scss-docs-end popover-css-vars + position: absolute; + top: 0; + left: 0; z-index: var(--#{$prefix}popover-zindex); display: block; max-width: var(--#{$prefix}popover-max-width); @@ -63,150 +66,121 @@ $popover-arrow-height: .5rem !default; @include font-size(var(--#{$prefix}popover-font-size)); // Allow breaking very long words so they don't overflow the popover's bounds word-wrap: break-word; + + &::before, + &::after { + position: absolute; + display: block; + content: ""; + border-color: transparent; + border-style: solid; + border-width: 0; + transform: translateX(-50%); + } + } + + .popover-inner { + margin: var(--#{$prefix}popover-arrow-height); background-color: var(--#{$prefix}popover-bg); background-clip: padding-box; border: var(--#{$prefix}popover-border-width) solid var(--#{$prefix}popover-border-color); @include border-radius(var(--#{$prefix}popover-border-radius)); @include box-shadow(var(--#{$prefix}popover-box-shadow)); - - .popover-arrow { - display: block; - width: var(--#{$prefix}popover-arrow-width); - height: var(--#{$prefix}popover-arrow-height); - - &::before, - &::after { - position: absolute; - display: block; - content: ""; - border-color: transparent; - border-style: solid; - border-width: 0; - } - } } - .bs-popover-top { - > .popover-arrow { - bottom: calc(-1 * (var(--#{$prefix}popover-arrow-height)) - var(--#{$prefix}popover-border-width)); - - &::before, - &::after { - border-width: var(--#{$prefix}popover-arrow-height) calc(var(--#{$prefix}popover-arrow-width) * .5) 0; - } + .popover[data-bs-placement="top"] { + &::before, + &::after { + left: 50%; + border-width: var(--#{$prefix}popover-arrow-height) calc(var(--#{$prefix}popover-arrow-width) * .5) 0; + } - &::before { - bottom: 0; - border-top-color: var(--#{$prefix}popover-arrow-border); - } + &::before { + bottom: 0; + border-top-color: var(--#{$prefix}popover-arrow-border); + } - &::after { - bottom: var(--#{$prefix}popover-border-width); - border-top-color: var(--#{$prefix}popover-bg); - } + &::after { + bottom: var(--#{$prefix}popover-border-width); + border-top-color: var(--#{$prefix}popover-bg); } } - /* rtl:begin:ignore */ - .bs-popover-end { - > .popover-arrow { - left: calc(-1 * (var(--#{$prefix}popover-arrow-height)) - var(--#{$prefix}popover-border-width)); - width: var(--#{$prefix}popover-arrow-height); - height: var(--#{$prefix}popover-arrow-width); - - &::before, - &::after { - border-width: calc(var(--#{$prefix}popover-arrow-width) * .5) var(--#{$prefix}popover-arrow-height) calc(var(--#{$prefix}popover-arrow-width) * .5) 0; - } - - &::before { - left: 0; - border-right-color: var(--#{$prefix}popover-arrow-border); - } - - &::after { - left: var(--#{$prefix}popover-border-width); - border-right-color: var(--#{$prefix}popover-bg); - } + .popover[data-bs-placement="bottom"] { + &::before, + &::after { + left: 50%; + border-width: 0 calc(var(--#{$prefix}popover-arrow-width) * .5) var(--#{$prefix}popover-arrow-height); } - } - /* rtl:end:ignore */ + &::before { + top: 0; + border-bottom-color: var(--#{$prefix}popover-arrow-border); + } - .bs-popover-bottom { - > .popover-arrow { - top: calc(-1 * (var(--#{$prefix}popover-arrow-height)) - var(--#{$prefix}popover-border-width)); + &::after { + top: var(--#{$prefix}popover-border-width); + border-bottom-color: var(--#{$prefix}popover-bg); + } + } - &::before, - &::after { - border-width: 0 calc(var(--#{$prefix}popover-arrow-width) * .5) var(--#{$prefix}popover-arrow-height); - } - &::before { - top: 0; - border-bottom-color: var(--#{$prefix}popover-arrow-border); - } + /* rtl:begin:ignore */ - &::after { - top: var(--#{$prefix}popover-border-width); - border-bottom-color: var(--#{$prefix}popover-bg); - } + .popover[data-bs-placement="right"] { + &::before, + &::after { + top: 50%; + border-width: calc(var(--#{$prefix}popover-arrow-width) * .5) var(--#{$prefix}popover-arrow-height) calc(var(--#{$prefix}popover-arrow-width) * .5) 0; + transform: translateY(-50%); } - // This will remove the popover-header's border just below the arrow - .popover-header::before { - position: absolute; - top: 0; - left: 50%; - display: block; - width: var(--#{$prefix}popover-arrow-width); - margin-left: calc(-.5 * var(--#{$prefix}popover-arrow-width)); - content: ""; - border-bottom: var(--#{$prefix}popover-border-width) solid var(--#{$prefix}popover-header-bg); + &::before { + left: 0; + border-right-color: var(--#{$prefix}popover-arrow-border); } - } - /* rtl:begin:ignore */ - .bs-popover-start { - > .popover-arrow { - right: calc(-1 * (var(--#{$prefix}popover-arrow-height)) - var(--#{$prefix}popover-border-width)); - width: var(--#{$prefix}popover-arrow-height); - height: var(--#{$prefix}popover-arrow-width); - - &::before, - &::after { - border-width: calc(var(--#{$prefix}popover-arrow-width) * .5) 0 calc(var(--#{$prefix}popover-arrow-width) * .5) var(--#{$prefix}popover-arrow-height); - } - - &::before { - right: 0; - border-left-color: var(--#{$prefix}popover-arrow-border); - } - - &::after { - right: var(--#{$prefix}popover-border-width); - border-left-color: var(--#{$prefix}popover-bg); - } + &::after { + left: var(--#{$prefix}popover-border-width); + border-right-color: var(--#{$prefix}popover-bg); } } - /* rtl:end:ignore */ - .bs-popover-auto { - &[data-popper-placement^="top"] { - @extend .bs-popover-top; - } - &[data-popper-placement^="right"] { - @extend .bs-popover-end; + .popover[data-bs-placement="left"] { + &::before, + &::after { + top: 50%; + border-width: calc(var(--#{$prefix}popover-arrow-width) * .5) 0 calc(var(--#{$prefix}popover-arrow-width) * .5) var(--#{$prefix}popover-arrow-height); + transform: translateY(-50%); } - &[data-popper-placement^="bottom"] { - @extend .bs-popover-bottom; + + &::before { + right: 0; + border-left-color: var(--#{$prefix}popover-arrow-border); } - &[data-popper-placement^="left"] { - @extend .bs-popover-start; + + &::after { + right: var(--#{$prefix}popover-border-width); + border-left-color: var(--#{$prefix}popover-bg); } } + + /* rtl:end:ignore */ + + // This will remove the popover-header's border just below the arrow + .popover-header::before { + position: absolute; + top: 0; + left: 50%; + display: block; + width: var(--#{$prefix}popover-arrow-width); + margin-left: calc(var(--#{$prefix}popover-arrow-width) * -.5); + content: ""; + border-bottom: var(--#{$prefix}popover-border-width) solid var(--#{$prefix}popover-header-bg); + } + // Offset the popover to account for the popover arrow .popover-header { padding: var(--#{$prefix}popover-header-padding-y) var(--#{$prefix}popover-header-padding-x); diff --git a/scss/_tooltip.scss b/scss/_tooltip.scss index 12339704ad..d605bed481 100644 --- a/scss/_tooltip.scss +++ b/scss/_tooltip.scss @@ -14,7 +14,6 @@ $tooltip-border-radius: var(--#{$prefix}border-radius) !default; $tooltip-opacity: .9 !default; $tooltip-padding-y: $spacer * .25 !default; $tooltip-padding-x: $spacer * .5 !default; -$tooltip-margin: null !default; // TODO: remove this in v6 $tooltip-arrow-width: .8rem !default; $tooltip-arrow-height: .4rem !default; @@ -38,7 +37,6 @@ $form-feedback-tooltip-border-radius: $tooltip-border-radius !default; --#{$prefix}tooltip-max-width: #{$tooltip-max-width}; --#{$prefix}tooltip-padding-x: #{$tooltip-padding-x}; --#{$prefix}tooltip-padding-y: #{$tooltip-padding-y}; - --#{$prefix}tooltip-margin: #{$tooltip-margin}; @include rfs($tooltip-font-size, --#{$prefix}tooltip-font-size); --#{$prefix}tooltip-color: #{$tooltip-color}; --#{$prefix}tooltip-bg: #{$tooltip-bg}; @@ -48,10 +46,12 @@ $form-feedback-tooltip-border-radius: $tooltip-border-radius !default; --#{$prefix}tooltip-arrow-height: #{$tooltip-arrow-height}; // scss-docs-end tooltip-css-vars + position: absolute; + top: 0; + left: 0; z-index: var(--#{$prefix}tooltip-zindex); display: block; - margin: var(--#{$prefix}tooltip-margin); - @include deprecate("`$tooltip-margin`", "v5", "v5.x", true); + padding: var(--#{$prefix}tooltip-arrow-height); // Our parent element can be arbitrary since tooltips are by default inserted as a sibling of their target element. // So reset our font and text properties to avoid inheriting weird values. @include reset-text(); @@ -62,83 +62,43 @@ $form-feedback-tooltip-border-radius: $tooltip-border-radius !default; &.show { opacity: var(--#{$prefix}tooltip-opacity); } - .tooltip-arrow { - display: block; - width: var(--#{$prefix}tooltip-arrow-width); - height: var(--#{$prefix}tooltip-arrow-height); - - &::before { - position: absolute; - content: ""; - border-color: transparent; - border-style: solid; - } - } - } - - .bs-tooltip-top .tooltip-arrow { - bottom: calc(-1 * var(--#{$prefix}tooltip-arrow-height)); - &::before { - top: -1px; - border-width: var(--#{$prefix}tooltip-arrow-height) calc(var(--#{$prefix}tooltip-arrow-width) * .5) 0; - border-top-color: var(--#{$prefix}tooltip-bg); + position: absolute; + content: ""; + border-color: transparent; + border-style: solid; + transform: translateX(-50%); } } - /* rtl:begin:ignore */ - .bs-tooltip-end .tooltip-arrow { - left: calc(-1 * var(--#{$prefix}tooltip-arrow-height)); - width: var(--#{$prefix}tooltip-arrow-height); - height: var(--#{$prefix}tooltip-arrow-width); - - &::before { - right: -1px; - border-width: calc(var(--#{$prefix}tooltip-arrow-width) * .5) var(--#{$prefix}tooltip-arrow-height) calc(var(--#{$prefix}tooltip-arrow-width) * .5) 0; - border-right-color: var(--#{$prefix}tooltip-bg); - } + .tooltip[data-bs-placement="top"]::before { + bottom: 0; + left: 50%; + border-width: var(--#{$prefix}tooltip-arrow-height) calc(var(--#{$prefix}tooltip-arrow-width) * .5) 0; + border-top-color: var(--#{$prefix}tooltip-bg); } - /* rtl:end:ignore */ - - .bs-tooltip-bottom .tooltip-arrow { - top: calc(-1 * var(--#{$prefix}tooltip-arrow-height)); - - &::before { - bottom: -1px; - border-width: 0 calc(var(--#{$prefix}tooltip-arrow-width) * .5) var(--#{$prefix}tooltip-arrow-height); - border-bottom-color: var(--#{$prefix}tooltip-bg); - } + .tooltip[data-bs-placement="bottom"]::before { + top: 0; + left: 50%; + border-width: 0 calc(var(--#{$prefix}tooltip-arrow-width) * .5) var(--#{$prefix}tooltip-arrow-height); + border-bottom-color: var(--#{$prefix}tooltip-bg); } - /* rtl:begin:ignore */ - .bs-tooltip-start .tooltip-arrow { - right: calc(-1 * var(--#{$prefix}tooltip-arrow-height)); - width: var(--#{$prefix}tooltip-arrow-height); - height: var(--#{$prefix}tooltip-arrow-width); - - &::before { - left: -1px; - border-width: calc(var(--#{$prefix}tooltip-arrow-width) * .5) 0 calc(var(--#{$prefix}tooltip-arrow-width) * .5) var(--#{$prefix}tooltip-arrow-height); - border-left-color: var(--#{$prefix}tooltip-bg); - } + .tooltip[data-bs-placement="right"]::before { + top: 50%; + left: 0; + border-width: calc(var(--#{$prefix}tooltip-arrow-width) * .5) var(--#{$prefix}tooltip-arrow-height) calc(var(--#{$prefix}tooltip-arrow-width) * .5) 0; + border-right-color: var(--#{$prefix}tooltip-bg); + transform: translateY(-50%); } - /* rtl:end:ignore */ - - .bs-tooltip-auto { - &[data-popper-placement^="top"] { - @extend .bs-tooltip-top; - } - &[data-popper-placement^="right"] { - @extend .bs-tooltip-end; - } - &[data-popper-placement^="bottom"] { - @extend .bs-tooltip-bottom; - } - &[data-popper-placement^="left"] { - @extend .bs-tooltip-start; - } + .tooltip[data-bs-placement="left"]::before { + top: 50%; + right: 0; + border-width: calc(var(--#{$prefix}tooltip-arrow-width) * .5) 0 calc(var(--#{$prefix}tooltip-arrow-width) * .5) var(--#{$prefix}tooltip-arrow-height); + border-left-color: var(--#{$prefix}tooltip-bg); + transform: translateY(-50%); } // Wrapper for the tooltip content diff --git a/site/src/content/docs/components/dropdowns.mdx b/site/src/content/docs/components/dropdowns.mdx index 4cf96bafcf..143a1eab3d 100644 --- a/site/src/content/docs/components/dropdowns.mdx +++ b/site/src/content/docs/components/dropdowns.mdx @@ -8,7 +8,7 @@ toc: true Dropdowns are toggleable, contextual overlays for displaying lists of links and more. They’re made interactive with the included Bootstrap dropdown JavaScript plugin. They’re toggled by clicking, not by hovering; this is [an intentional design decision](https://markdotto.com/blog/bootstrap-explained-dropdowns/). -Dropdowns are built on a third party library, [Popper](https://popper.js.org/docs/v2/), which provides dynamic positioning and viewport detection. Be sure to include [popper.min.js]([[config:cdn.popper]]) before Bootstrap’s JavaScript or use `bootstrap.bundle.min.js` / `bootstrap.bundle.js` which contains Popper. Popper isn’t used to position dropdowns in navbars though as dynamic positioning isn’t required. +Dropdowns are built on a third party library, [Floating UI](https://floating-ui.com/), which provides dynamic positioning and viewport detection. Be sure to include [floating-ui.dom.umd.min.js]([[config:cdn.floating_ui]]) before Bootstrap's JavaScript or use `bootstrap.bundle.min.js` / `bootstrap.bundle.js` which contains Floating UI. Floating UI isn't used to position dropdowns in navbars though as dynamic positioning isn't required. ## Accessibility diff --git a/site/src/content/docs/components/popovers.mdx b/site/src/content/docs/components/popovers.mdx index f4430b2c49..cf62750dcd 100644 --- a/site/src/content/docs/components/popovers.mdx +++ b/site/src/content/docs/components/popovers.mdx @@ -8,7 +8,7 @@ toc: true Things to know when using the popover plugin: -- Popovers rely on the third party library [Popper](https://popper.js.org/docs/v2/) for positioning. You must include [popper.min.js]([[config:cdn.popper]]) before `bootstrap.js`, or use one `bootstrap.bundle.min.js` which contains Popper. +- Popovers rely on the third party library [Floating UI](https://floating-ui.com/) for positioning. You must include [floating-ui.dom.umd.min.js]([[config:cdn.floating_ui]]) before `bootstrap.js`, or use one `bootstrap.bundle.min.js` which contains Floating UI. - Popovers require the [popover plugin]([[docsref:/components/popovers]]) as a dependency. - Popovers are opt-in for performance reasons, so **you must initialize them yourself**. - Zero-length `title` and `content` values will never show a popover. diff --git a/site/src/content/docs/components/tooltips.mdx b/site/src/content/docs/components/tooltips.mdx index 2f26ed8c45..097f3a5ecf 100644 --- a/site/src/content/docs/components/tooltips.mdx +++ b/site/src/content/docs/components/tooltips.mdx @@ -8,7 +8,7 @@ toc: true Things to know when using the tooltip plugin: -- Tooltips rely on the third party library [Popper](https://popper.js.org/docs/v2/) for positioning. You must include [popper.min.js]([[config:cdn.popper]]) before `bootstrap.js`, or use one `bootstrap.bundle.min.js` which contains Popper. +- Tooltips rely on the third party library [Floating UI](https://floating-ui.com/) for positioning. You must include [floating-ui.dom.umd.min.js]([[config:cdn.floating_ui]]) before `bootstrap.js`, or use one `bootstrap.bundle.min.js` which contains Floating UI. - Tooltips are opt-in for performance reasons, so **you must initialize them yourself**. - Tooltips with zero-length titles are never displayed. - Specify `container: 'body'` to avoid rendering problems in more complex components (like our input groups, button groups, etc). diff --git a/site/src/content/docs/getting-started/download.mdx b/site/src/content/docs/getting-started/download.mdx index 4088b79d30..50cbfd07ed 100644 --- a/site/src/content/docs/getting-started/download.mdx +++ b/site/src/content/docs/getting-started/download.mdx @@ -11,7 +11,7 @@ Download ready-to-use compiled code for **Bootstrap v[[config:current_version]]* - Compiled and minified CSS bundles (see [CSS files comparison]([[docsref:/getting-started/contents#css-files]])) - Compiled and minified JavaScript plugins (see [JS files comparison]([[docsref:/getting-started/contents#js-files]])) -This doesn’t include documentation, source files, or any optional JavaScript dependencies like Popper. +This doesn't include documentation, source files, or any optional JavaScript dependencies like Floating UI. Download @@ -41,10 +41,10 @@ Skip the download with [jsDelivr](https://www.jsdelivr.com/) to deliver cached v ``` -If you’re using our compiled JavaScript and prefer to include Popper separately, add Popper before our JS, via a CDN preferably. +If you're using our compiled JavaScript and prefer to include Floating UI separately, add Floating UI before our JS, via a CDN preferably. ```html - + ``` diff --git a/site/src/content/docs/getting-started/introduction.mdx b/site/src/content/docs/getting-started/introduction.mdx index dc2c2d71a7..5415ecd505 100644 --- a/site/src/content/docs/getting-started/introduction.mdx +++ b/site/src/content/docs/getting-started/introduction.mdx @@ -30,7 +30,7 @@ Get started by including Bootstrap’s production-ready CSS and JavaScript via C ``` -2. **Include Bootstrap’s CSS and JS.** Place the `` tag in the `` for our CSS, and the ` + ``` diff --git a/site/src/content/docs/getting-started/javascript.mdx b/site/src/content/docs/getting-started/javascript.mdx index 81177a074a..e6fca8db2f 100644 --- a/site/src/content/docs/getting-started/javascript.mdx +++ b/site/src/content/docs/getting-started/javascript.mdx @@ -41,19 +41,19 @@ We provide a version of Bootstrap built as `ESM` (`bootstrap.esm.js` and `bootst ``` -Compared to JS bundlers, using ESM in the browser requires you to use the full path and filename instead of the module name. [Read more about JS modules in the browser.](https://v8.dev/features/modules#specifiers) That’s why we use `'bootstrap.esm.min.js'` instead of `'bootstrap'` above. However, this is further complicated by our Popper dependency, which imports Popper into our JavaScript like so: +Compared to JS bundlers, using ESM in the browser requires you to use the full path and filename instead of the module name. [Read more about JS modules in the browser.](https://v8.dev/features/modules#specifiers) That’s why we use `'bootstrap.esm.min.js'` instead of `'bootstrap'` above. However, this is further complicated by our Floating UI dependency, which imports Floating UI into our JavaScript like so: ```js -import * as Popper from "@popperjs/core" +import { computePosition } from "@floating-ui/dom" ``` -If you try this as-is, you’ll see an error in the console like the following: +If you try this as-is, you'll see an error in the console like the following: ```text -Uncaught TypeError: Failed to resolve module specifier "@popperjs/core". Relative references must start with either "/", "./", or "../". +Uncaught TypeError: Failed to resolve module specifier "@floating-ui/dom". Relative references must start with either "/", "./", or "../". ``` -To fix this, you can use an `importmap` to resolve the arbitrary module names to complete paths. If your [targeted browsers](https://caniuse.com/?search=importmap) do not support `importmap`, you’ll need to use the [es-module-shims](https://github.com/guybedford/es-module-shims) project. Here’s how it works for Bootstrap and Popper: +To fix this, you can use an `importmap` to resolve the arbitrary module names to complete paths. If your [targeted browsers](https://caniuse.com/?search=importmap) do not support `importmap`, you'll need to use the [es-module-shims](https://github.com/guybedford/es-module-shims) project. Here's how it works for Bootstrap and Floating UI: ```html @@ -72,7 +72,7 @@ To fix this, you can use an `importmap` to resolve the arbitrary module names to - + diff --git a/site/src/libs/config.ts b/site/src/libs/config.ts index be01d8550e..0154108f95 100644 --- a/site/src/libs/config.ts +++ b/site/src/libs/config.ts @@ -29,9 +29,9 @@ const configSchema = z.object({ js_hash: z.string(), js_bundle: z.string().url(), js_bundle_hash: z.string(), - popper: z.string().url(), - popper_esm: z.string().url(), - popper_hash: z.string() + floating_ui: z.string().url(), + floating_ui_esm: z.string().url(), + floating_ui_hash: z.string() }), current_version: zVersionSemver, current_ruby_version: zVersionSemver,