From: Mark Otto Date: Sun, 1 Feb 2026 03:52:38 +0000 (-0800) Subject: More improvements X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=f5802cd26728ad93677672b8e3d008bb519504f1;p=thirdparty%2Fbootstrap.git More improvements --- diff --git a/js/index.esm.js b/js/index.esm.js index 01d298e05f..e52911e2ac 100644 --- a/js/index.esm.js +++ b/js/index.esm.js @@ -12,6 +12,7 @@ export { default as Collapse } from './src/collapse.js' export { default as Datepicker } from './src/datepicker.js' export { default as Dialog } from './src/dialog.js' export { default as Dropdown } from './src/dropdown.js' +export { default as NavOverflow } from './src/nav-overflow.js' export { default as Offcanvas } from './src/offcanvas.js' export { default as Strength } from './src/strength.js' export { default as OtpInput } from './src/otp-input.js' diff --git a/js/index.umd.js b/js/index.umd.js index 73f12b424e..73e7b45c83 100644 --- a/js/index.umd.js +++ b/js/index.umd.js @@ -12,6 +12,7 @@ import Collapse from './src/collapse.js' import Datepicker from './src/datepicker.js' import Dialog from './src/dialog.js' import Dropdown from './src/dropdown.js' +import NavOverflow from './src/nav-overflow.js' import Offcanvas from './src/offcanvas.js' import Strength from './src/strength.js' import OtpInput from './src/otp-input.js' @@ -30,6 +31,7 @@ export default { Datepicker, Dialog, Dropdown, + NavOverflow, Offcanvas, Strength, OtpInput, diff --git a/js/src/dropdown.js b/js/src/dropdown.js index 5bd5323aaa..2053b46dc7 100644 --- a/js/src/dropdown.js +++ b/js/src/dropdown.js @@ -97,6 +97,7 @@ const triangleSign = (p1, p2, p3) => const Default = { autoClose: true, boundary: 'clippingParents', + container: false, display: 'dynamic', offset: [0, 2], floatingConfig: null, @@ -111,6 +112,7 @@ const Default = { const DefaultType = { autoClose: '(boolean|string)', boundary: '(string|element)', + container: '(string|element|boolean)', display: 'string', offset: '(array|string|function)', floatingConfig: '(null|object|function)', @@ -147,6 +149,9 @@ class Dropdown extends BaseComponent { SelectorEngine.prev(this._element, SELECTOR_MENU)[0] || SelectorEngine.findOne(SELECTOR_MENU, this._parent) + // Store original menu parent for container option + this._menuOriginalParent = this._menu?.parentNode + // Parse responsive placements on init this._parseResponsivePlacements() @@ -187,6 +192,9 @@ class Dropdown extends BaseComponent { return } + // Move menu to container if specified (to escape overflow clipping) + this._moveMenuToContainer() + this._createFloating() // If this is a touch-enabled device we add extra @@ -222,6 +230,7 @@ class Dropdown extends BaseComponent { dispose() { this._disposeFloating() + this._restoreMenuToOriginalParent() this._disposeMediaQueryListeners() this._closeAllSubmenus() this._clearAllSubmenuTimeouts() @@ -254,6 +263,9 @@ class Dropdown extends BaseComponent { this._disposeFloating() + // Restore menu to original parent if it was moved + this._restoreMenuToOriginalParent() + this._menu.classList.remove(CLASS_NAME_SHOW) this._element.classList.remove(CLASS_NAME_SHOW) this._parent.classList.remove(CLASS_NAME_SHOW) @@ -454,6 +466,38 @@ class Dropdown extends BaseComponent { } } + _getContainer() { + const { container } = this._config + if (container === false) { + return null + } + + return container === true ? document.body : getElement(container) + } + + _moveMenuToContainer() { + const container = this._getContainer() + if (!container || !this._menu) { + return + } + + // Only move if not already in the container + if (this._menu.parentNode !== container) { + container.append(this._menu) + } + } + + _restoreMenuToOriginalParent() { + if (!this._menuOriginalParent || !this._menu) { + return + } + + // Only restore if menu was moved + if (this._menu.parentNode !== this._menuOriginalParent) { + this._menuOriginalParent.append(this._menu) + } + } + // Shared helper for positioning any floating element async _applyFloatingPosition(reference, floating, placement, middleware, strategy = 'absolute') { if (!floating.isConnected) { diff --git a/js/src/nav-overflow.js b/js/src/nav-overflow.js index c723371d8a..7e0ef54c70 100644 --- a/js/src/nav-overflow.js +++ b/js/src/nav-overflow.js @@ -8,7 +8,6 @@ import BaseComponent from './base-component.js' import EventHandler from './dom/event-handler.js' import SelectorEngine from './dom/selector-engine.js' -import Dropdown from './dropdown.js' /** * Constants @@ -134,7 +133,7 @@ class NavOverflow extends BaseComponent { const overflowItem = document.createElement('li') overflowItem.className = 'nav-item nav-overflow-item dropdown' overflowItem.innerHTML = ` - @@ -144,11 +143,6 @@ class NavOverflow extends BaseComponent { this._element.append(overflowItem) this._overflowToggle = overflowItem.querySelector(SELECTOR_OVERFLOW_TOGGLE) this._overflowMenu = overflowItem.querySelector(SELECTOR_OVERFLOW_MENU) - - // Initialize dropdown with fixed strategy to escape overflow containers - Dropdown.getOrCreateInstance(this._overflowToggle, { - strategy: 'fixed' - }) } _setupResizeObserver() { diff --git a/js/tests/unit/dropdown.spec.js b/js/tests/unit/dropdown.spec.js index 00462e1068..9b79de40dc 100644 --- a/js/tests/unit/dropdown.spec.js +++ b/js/tests/unit/dropdown.spec.js @@ -788,6 +788,142 @@ describe('Dropdown', () => { }, 10) }) }) + + it('should move menu to body when container is set to body', () => { + return new Promise(resolve => { + fixtureEl.innerHTML = [ + '' + ].join('') + + const btnDropdown = fixtureEl.querySelector('[data-bs-toggle="dropdown"]') + const dropdownMenu = fixtureEl.querySelector('.dropdown-menu') + const dropdown = new Dropdown(btnDropdown, { + container: 'body' + }) + + btnDropdown.addEventListener('shown.bs.dropdown', () => { + expect(dropdownMenu.parentNode).toEqual(document.body) + resolve() + }) + + dropdown.show() + }) + }) + + it('should move menu to body when container is set to true', () => { + return new Promise(resolve => { + fixtureEl.innerHTML = [ + '' + ].join('') + + const btnDropdown = fixtureEl.querySelector('[data-bs-toggle="dropdown"]') + const dropdownMenu = fixtureEl.querySelector('.dropdown-menu') + const dropdown = new Dropdown(btnDropdown, { + container: true + }) + + btnDropdown.addEventListener('shown.bs.dropdown', () => { + expect(dropdownMenu.parentNode).toEqual(document.body) + resolve() + }) + + dropdown.show() + }) + }) + + it('should move menu to specified element when container is an element', () => { + return new Promise(resolve => { + fixtureEl.innerHTML = [ + '
', + '' + ].join('') + + const btnDropdown = fixtureEl.querySelector('[data-bs-toggle="dropdown"]') + const dropdownMenu = fixtureEl.querySelector('.dropdown-menu') + const customContainer = fixtureEl.querySelector('#custom-container') + const dropdown = new Dropdown(btnDropdown, { + container: customContainer + }) + + btnDropdown.addEventListener('shown.bs.dropdown', () => { + expect(dropdownMenu.parentNode).toEqual(customContainer) + resolve() + }) + + dropdown.show() + }) + }) + + it('should restore menu to original parent when hidden', () => { + return new Promise(resolve => { + fixtureEl.innerHTML = [ + '' + ].join('') + + const btnDropdown = fixtureEl.querySelector('[data-bs-toggle="dropdown"]') + const dropdownMenu = fixtureEl.querySelector('.dropdown-menu') + const originalParent = dropdownMenu.parentNode + const dropdown = new Dropdown(btnDropdown, { + container: 'body' + }) + + btnDropdown.addEventListener('shown.bs.dropdown', () => { + expect(dropdownMenu.parentNode).toEqual(document.body) + dropdown.hide() + }) + + btnDropdown.addEventListener('hidden.bs.dropdown', () => { + expect(dropdownMenu.parentNode).toEqual(originalParent) + resolve() + }) + + dropdown.show() + }) + }) + + it('should work with container via data attribute', () => { + return new Promise(resolve => { + fixtureEl.innerHTML = [ + '' + ].join('') + + const btnDropdown = fixtureEl.querySelector('[data-bs-toggle="dropdown"]') + const dropdownMenu = fixtureEl.querySelector('.dropdown-menu') + const dropdown = new Dropdown(btnDropdown) + + btnDropdown.addEventListener('shown.bs.dropdown', () => { + expect(dropdownMenu.parentNode).toEqual(document.body) + resolve() + }) + + dropdown.show() + }) + }) }) describe('hide', () => { diff --git a/js/tests/unit/nav-overflow.spec.js b/js/tests/unit/nav-overflow.spec.js new file mode 100644 index 0000000000..89cad83d50 --- /dev/null +++ b/js/tests/unit/nav-overflow.spec.js @@ -0,0 +1,271 @@ +import NavOverflow from '../../src/nav-overflow.js' +import { clearFixture, getFixture } from '../helpers/fixture.js' + +describe('NavOverflow', () => { + let fixtureEl + + beforeAll(() => { + fixtureEl = getFixture() + }) + + afterEach(() => { + clearFixture() + }) + + describe('VERSION', () => { + it('should return plugin version', () => { + expect(NavOverflow.VERSION).toEqual(jasmine.any(String)) + }) + }) + + describe('Default', () => { + it('should return plugin default config', () => { + expect(NavOverflow.Default).toEqual(jasmine.any(Object)) + expect(NavOverflow.Default.moreText).toEqual('More') + expect(NavOverflow.Default.threshold).toEqual(0) + }) + }) + + describe('DATA_KEY', () => { + it('should return plugin data key', () => { + expect(NavOverflow.DATA_KEY).toEqual('bs.navoverflow') + }) + }) + + describe('constructor', () => { + it('should take care of element either passed as a CSS selector or DOM element', () => { + fixtureEl.innerHTML = [ + '' + ].join('') + + const navEl = fixtureEl.querySelector('[data-bs-toggle="nav-overflow"]') + const navBySelector = new NavOverflow('[data-bs-toggle="nav-overflow"]') + const navByElement = new NavOverflow(navEl) + + expect(navBySelector._element).toEqual(navEl) + expect(navByElement._element).toEqual(navEl) + + navBySelector.dispose() + navByElement.dispose() + }) + + it('should add nav-overflow class to element', () => { + fixtureEl.innerHTML = [ + '' + ].join('') + + const navEl = fixtureEl.querySelector('[data-bs-toggle="nav-overflow"]') + const navOverflow = new NavOverflow(navEl) + + expect(navEl).toHaveClass('nav-overflow') + + navOverflow.dispose() + }) + + it('should create overflow menu toggle and dropdown', () => { + fixtureEl.innerHTML = [ + '' + ].join('') + + const navEl = fixtureEl.querySelector('[data-bs-toggle="nav-overflow"]') + const navOverflow = new NavOverflow(navEl) + + const toggle = navEl.querySelector('.nav-overflow-toggle') + const menu = navEl.querySelector('.nav-overflow-menu') + + expect(toggle).not.toBeNull() + expect(menu).not.toBeNull() + expect(toggle).toHaveClass('dropdown-toggle') + expect(menu).toHaveClass('dropdown-menu') + + navOverflow.dispose() + }) + + it('should store order data on nav items', () => { + fixtureEl.innerHTML = [ + '' + ].join('') + + const navEl = fixtureEl.querySelector('[data-bs-toggle="nav-overflow"]') + const navOverflow = new NavOverflow(navEl) + const items = navEl.querySelectorAll('.nav-item:not(.nav-overflow-item)') + + expect(items[0].dataset.bsNavOrder).toEqual('0') + expect(items[1].dataset.bsNavOrder).toEqual('1') + expect(items[2].dataset.bsNavOrder).toEqual('2') + + navOverflow.dispose() + }) + + it('should respect custom moreText option', () => { + fixtureEl.innerHTML = [ + '' + ].join('') + + const navEl = fixtureEl.querySelector('[data-bs-toggle="nav-overflow"]') + const navOverflow = new NavOverflow(navEl, { + moreText: 'See all' + }) + + const toggleText = navEl.querySelector('.nav-overflow-text') + expect(toggleText.textContent).toEqual('See all') + + navOverflow.dispose() + }) + }) + + describe('update', () => { + it('should trigger update event', () => { + return new Promise(resolve => { + fixtureEl.innerHTML = [ + '' + ].join('') + + const navEl = fixtureEl.querySelector('[data-bs-toggle="nav-overflow"]') + const navOverflow = new NavOverflow(navEl) + + navEl.addEventListener('update.bs.navoverflow', () => { + navOverflow.dispose() + resolve() + }) + + navOverflow.update() + }) + }) + }) + + describe('dispose', () => { + it('should dispose nav overflow and remove overflow menu', () => { + fixtureEl.innerHTML = [ + '' + ].join('') + + const navEl = fixtureEl.querySelector('[data-bs-toggle="nav-overflow"]') + const navOverflow = new NavOverflow(navEl) + + expect(NavOverflow.getInstance(navEl)).not.toBeNull() + expect(navEl.querySelector('.nav-overflow-toggle')).not.toBeNull() + + navOverflow.dispose() + + expect(NavOverflow.getInstance(navEl)).toBeNull() + expect(navEl.querySelector('.nav-overflow-toggle')).toBeNull() + }) + }) + + describe('getInstance', () => { + it('should return nav overflow instance', () => { + fixtureEl.innerHTML = [ + '' + ].join('') + + const navEl = fixtureEl.querySelector('[data-bs-toggle="nav-overflow"]') + const navOverflow = new NavOverflow(navEl) + + expect(NavOverflow.getInstance(navEl)).toEqual(navOverflow) + expect(NavOverflow.getInstance(navEl)).toBeInstanceOf(NavOverflow) + + navOverflow.dispose() + }) + + it('should return null when there is no instance', () => { + fixtureEl.innerHTML = '' + + const navEl = fixtureEl.querySelector('.nav') + + expect(NavOverflow.getInstance(navEl)).toBeNull() + }) + }) + + describe('getOrCreateInstance', () => { + it('should return nav overflow instance', () => { + fixtureEl.innerHTML = [ + '' + ].join('') + + const navEl = fixtureEl.querySelector('[data-bs-toggle="nav-overflow"]') + const navOverflow = new NavOverflow(navEl) + + expect(NavOverflow.getOrCreateInstance(navEl)).toEqual(navOverflow) + expect(NavOverflow.getInstance(navEl)).toEqual(NavOverflow.getOrCreateInstance(navEl, {})) + expect(NavOverflow.getOrCreateInstance(navEl)).toBeInstanceOf(NavOverflow) + + navOverflow.dispose() + }) + + it('should return new instance when there is no instance', () => { + fixtureEl.innerHTML = [ + '' + ].join('') + + const navEl = fixtureEl.querySelector('.nav') + + expect(NavOverflow.getInstance(navEl)).toBeNull() + + const instance = NavOverflow.getOrCreateInstance(navEl) + expect(instance).toBeInstanceOf(NavOverflow) + + instance.dispose() + }) + }) + + describe('overflow behavior', () => { + it('should use dropdown with container option for overflow menu', () => { + fixtureEl.innerHTML = [ + '' + ].join('') + + const navEl = fixtureEl.querySelector('[data-bs-toggle="nav-overflow"]') + const navOverflow = new NavOverflow(navEl) + + const toggle = navEl.querySelector('.nav-overflow-toggle') + expect(toggle.getAttribute('data-bs-container')).toEqual('body') + expect(toggle.getAttribute('data-bs-strategy')).toEqual('fixed') + + navOverflow.dispose() + }) + + it('should preserve nav-overflow-keep items', () => { + fixtureEl.innerHTML = [ + '' + ].join('') + + const navEl = fixtureEl.querySelector('[data-bs-toggle="nav-overflow"]') + const navOverflow = new NavOverflow(navEl) + const keepItem = navEl.querySelector('.nav-overflow-keep') + + // The keep item should never be hidden + expect(keepItem).not.toHaveClass('d-none') + + navOverflow.dispose() + }) + }) +}) diff --git a/scss/_nav-overflow.scss b/scss/_nav-overflow.scss index 862c6f280d..fb85ef3ed1 100644 --- a/scss/_nav-overflow.scss +++ b/scss/_nav-overflow.scss @@ -9,6 +9,7 @@ @layer components { .nav-overflow { flex-wrap: nowrap; + min-width: 0; // Allow flex child to shrink below content width } // Container item for overflow diff --git a/scss/_navbar.scss b/scss/_navbar.scss index 2a21fc77ed..005abdbb1e 100644 --- a/scss/_navbar.scss +++ b/scss/_navbar.scss @@ -21,6 +21,7 @@ $navbar-brand-height: 1.5rem !default; $navbar-brand-padding-y: $navbar-brand-height * .5 !default; $navbar-brand-margin-end: 1rem !default; +$navbar-toggler-width: 2rem !default; $navbar-toggler-padding-y: .375rem !default; $navbar-toggler-padding-x: .375rem !default; $navbar-toggler-font-size: $font-size-lg !default; @@ -63,6 +64,7 @@ $navbar-dark-brand-hover-color: $navbar-dark-active-color !default; --navbar-brand-color: #{$navbar-light-brand-color}; --navbar-brand-hover-color: #{$navbar-light-brand-hover-color}; --navbar-nav-link-padding-x: #{$navbar-nav-link-padding-x}; + --navbar-toggler-width: #{$navbar-toggler-width}; --navbar-toggler-padding-y: #{$navbar-toggler-padding-y}; --navbar-toggler-padding-x: #{$navbar-toggler-padding-x}; --navbar-toggler-font-size: #{$navbar-toggler-font-size}; @@ -77,7 +79,7 @@ $navbar-dark-brand-hover-color: $navbar-dark-active-color !default; align-items: center; justify-content: space-between; padding: var(--navbar-padding-y) var(--navbar-padding-x); - container-type: inline-size; // Enable container queries for responsive behavior + @include set-container(); @include gradient-bg(); // Container properties for nested containers @@ -181,12 +183,18 @@ $navbar-dark-brand-hover-color: $navbar-dark-active-color !default; // Button for toggling the navbar when in its collapsed state .navbar-toggler { - padding: var(--navbar-toggler-padding-y) var(--navbar-toggler-padding-x); - font-size: var(--navbar-toggler-font-size); - line-height: 1; + display: flex; + align-items: center; + justify-content: center; + width: var(--navbar-toggler-width); + aspect-ratio: 1 / 1; + // padding: var(--navbar-toggler-padding-y) var(--navbar-toggler-padding-x); + // font-size: var(--navbar-toggler-font-size); + // line-height: 1; color: var(--navbar-color); background-color: transparent; - border: var(--border-width) solid var(--navbar-toggler-border-color); + border: 0; + // border: var(--border-width) solid var(--navbar-toggler-border-color); @include border-radius(var(--navbar-toggler-border-radius)); @include transition(var(--navbar-toggler-transition)); @@ -203,10 +211,9 @@ $navbar-dark-brand-hover-color: $navbar-dark-active-color !default; // Navbar toggler icon (inline SVG) .navbar-toggler-icon { display: inline-block; - width: 1em; - height: 1em; + width: 1rem; + height: 1rem; color: var(--navbar-color); - vertical-align: -.125em; } @@ -256,6 +263,7 @@ $navbar-dark-brand-hover-color: $navbar-dark-active-color !default; .offcanvas-body { display: flex; flex-grow: 0; + flex-direction: row; align-items: center; padding: 0; overflow-y: visible; @@ -276,11 +284,10 @@ $navbar-dark-brand-hover-color: $navbar-dark-active-color !default; @each $breakpoint in map.keys($grid-breakpoints) { $next: breakpoint-next($breakpoint, $grid-breakpoints); $infix: breakpoint-infix($next, $grid-breakpoints); - $min-width: breakpoint-min($next, $grid-breakpoints); - @if $next and $min-width { + @if $next { .navbar-expand#{$infix} { - @container (min-width: #{$min-width}) { + @include container-breakpoint-up($next) { @include navbar-expanded(); } } diff --git a/scss/_offcanvas.scss b/scss/_offcanvas.scss index 8d6ad0dcb5..4ce9ada18b 100644 --- a/scss/_offcanvas.scss +++ b/scss/_offcanvas.scss @@ -151,8 +151,8 @@ $offcanvas-backdrop-opacity: .5 !default; } .offcanvas-body { - display: flex; flex-grow: 0; + flex-direction: row; padding: 0; overflow-y: visible; background-color: transparent !important; // stylelint-disable-line declaration-no-important @@ -190,7 +190,10 @@ $offcanvas-backdrop-opacity: .5 !default; // Scrollable body .offcanvas-body { + display: flex; flex-grow: 1; + flex-direction: column; + gap: var(--offcanvas-padding-y); padding: var(--offcanvas-padding-y) var(--offcanvas-padding-x); overflow-y: auto; } diff --git a/scss/content/_prose.scss b/scss/content/_prose.scss index 10215f1761..d887c94e76 100644 --- a/scss/content/_prose.scss +++ b/scss/content/_prose.scss @@ -77,7 +77,7 @@ h5, h6 { &:not(:first-child) { - margin-top: calc(var(--content-gap) * 1.25); + margin-top: var(--content-gap); } } diff --git a/scss/forms/_form-variables.scss b/scss/forms/_form-variables.scss index b4927460fe..eaf497db14 100644 --- a/scss/forms/_form-variables.scss +++ b/scss/forms/_form-variables.scss @@ -2,7 +2,7 @@ @use "../colors" as *; @use "../variables" as *; -$control-min-height: 2.5rem !default; +$control-min-height: 2.25rem !default; $control-min-height-sm: 2rem !default; $control-min-height-lg: 3rem !default; $control-padding-y: .375rem !default; diff --git a/scss/layout/_breakpoints.scss b/scss/layout/_breakpoints.scss index 5ec006d3c0..0a872fbc07 100644 --- a/scss/layout/_breakpoints.scss +++ b/scss/layout/_breakpoints.scss @@ -135,3 +135,136 @@ } } } + + +// Container queries +// +// Container queries allow elements to respond to the size of a containing element +// rather than the viewport. These mixins mirror the media-breakpoint-* mixins above. +// +// scss-docs-start container-query-mixins + +// Set an element as a query container. +// +// @include set-container(); // container-type: inline-size +// @include set-container(size); // container-type: size +// @include set-container(inline-size, sidebar); // container: sidebar / inline-size +// +@mixin set-container($type: inline-size, $name: null) { + @if $name { + container: #{$name} / #{$type}; + } @else { + container-type: #{$type}; + } +} + +// Container query of at least the minimum breakpoint width. No query for the smallest breakpoint. +// Makes the @content apply to the given breakpoint and wider within the container. +// +// @include container-breakpoint-up(md) { ... } +// @include container-breakpoint-up(lg, sidebar) { ... } // Query named container +// +@mixin container-breakpoint-up($name, $container-name: null, $breakpoints: $grid-breakpoints) { + $min: breakpoint-min($name, $breakpoints); + @if $min { + @if $container-name { + @container #{$container-name} (width >= #{$min}) { + @content; + } + } @else { + @container (width >= #{$min}) { + @content; + } + } + } @else { + @content; + } +} + +// Container query of at most the maximum breakpoint width. No query for the largest breakpoint. +// Makes the @content apply to the given breakpoint and narrower within the container. +// +// @include container-breakpoint-down(lg) { ... } +// @include container-breakpoint-down(lg, sidebar) { ... } // Query named container +// +@mixin container-breakpoint-down($name, $container-name: null, $breakpoints: $grid-breakpoints) { + $max: breakpoint-max($name, $breakpoints); + @if $max { + @if $container-name { + @container #{$container-name} (width < #{$max}) { + @content; + } + } @else { + @container (width < #{$max}) { + @content; + } + } + } @else { + @content; + } +} + +// Container query that spans multiple breakpoint widths. +// Makes the @content apply between the min and max breakpoints within the container. +// +// @include container-breakpoint-between(md, xl) { ... } +// @include container-breakpoint-between(md, xl, sidebar) { ... } // Query named container +// +@mixin container-breakpoint-between($lower, $upper, $container-name: null, $breakpoints: $grid-breakpoints) { + $min: breakpoint-min($lower, $breakpoints); + $max: breakpoint-max($upper, $breakpoints); + + @if $min != null and $max != null { + @if $container-name { + @container #{$container-name} (width >= #{$min}) and (width < #{$max}) { + @content; + } + } @else { + @container (width >= #{$min}) and (width < #{$max}) { + @content; + } + } + } @else if $max == null { + @include container-breakpoint-up($lower, $container-name, $breakpoints) { + @content; + } + } @else if $min == null { + @include container-breakpoint-down($upper, $container-name, $breakpoints) { + @content; + } + } +} + +// Container query between the breakpoint's minimum and maximum widths. +// No minimum for the smallest breakpoint, and no maximum for the largest one. +// Makes the @content apply only to the given breakpoint within the container. +// +// @include container-breakpoint-only(md) { ... } +// @include container-breakpoint-only(md, sidebar) { ... } // Query named container +// +@mixin container-breakpoint-only($name, $container-name: null, $breakpoints: $grid-breakpoints) { + $min: breakpoint-min($name, $breakpoints); + $next: breakpoint-next($name, $breakpoints); + $max: breakpoint-max($next, $breakpoints); + + @if $min != null and $max != null { + @if $container-name { + @container #{$container-name} (width >= #{$min}) and (width < #{$max}) { + @content; + } + } @else { + @container (width >= #{$min}) and (width < #{$max}) { + @content; + } + } + } @else if $max == null { + @include container-breakpoint-up($name, $container-name, $breakpoints) { + @content; + } + } @else if $min == null { + @include container-breakpoint-down($next, $container-name, $breakpoints) { + @content; + } + } +} +// scss-docs-end container-query-mixins diff --git a/site/src/content/docs/components/dropdown.mdx b/site/src/content/docs/components/dropdown.mdx index d2d26925f0..eb04349e2c 100644 --- a/site/src/content/docs/components/dropdown.mdx +++ b/site/src/content/docs/components/dropdown.mdx @@ -582,6 +582,7 @@ The dropdown plugin requires the following JavaScript files if you're building B | --- | --- | --- | --- | | `autoClose` | boolean, string | `true` | Configure the auto close behavior of the dropdown: Note: the dropdown can always be closed with the Esc key. | | `boundary` | string, element | `'clippingParents'` | Overflow constraint boundary of the dropdown menu (applies only to the shift middleware). By default it's `clippingParents` and can accept an HTMLElement reference (via JavaScript only). For more information refer to Floating UI's [shift docs](https://floating-ui.com/docs/shift). | +| `container` | string, element, boolean | `false` | Appends the dropdown menu to a specific element when shown. Use `'body'` or `true` to append to the document body, which helps escape containers with `overflow: hidden`. The menu is moved back to its original position when hidden. | | `display` | string | `'dynamic'` | By default, we use Floating UI for dynamic positioning. Disable this with `static`. | | `offset` | array, string, function | `[0, 2]` | Offset of the dropdown relative to its target. You can pass a string in data attributes with comma separated values like: `data-bs-offset="10,20"`. When a function is used to determine the offset, it is called with an object containing the placement, the reference, and floating rects as its first argument. The triggering element DOM node is passed as the second argument. The function must return an array with two numbers: [skidding, distance]. For more information refer to Floating UI's [offset docs](https://floating-ui.com/docs/offset). | | `floatingConfig` | null, object, function | `null` | To change Bootstrap's default Floating UI config, see [Floating UI's configuration](https://floating-ui.com/docs/computePosition). When a function is used to create the Floating UI configuration, it's called with an object that contains the Bootstrap's default Floating UI configuration. It helps you use and merge the default with your own configuration. The function must return a configuration object for Floating UI. | diff --git a/site/src/content/docs/components/nav-overflow.mdx b/site/src/content/docs/components/nav-overflow.mdx index 75838c34dc..542e27efa1 100644 --- a/site/src/content/docs/components/nav-overflow.mdx +++ b/site/src/content/docs/components/nav-overflow.mdx @@ -160,7 +160,7 @@ The nav overflow pattern can also be used within a [navbar]([[docsref:/component
Brand -
-