diff --git a/components/combobox/demo/api.min.js b/components/combobox/demo/api.min.js index 7c39d703..ab50c665 100644 --- a/components/combobox/demo/api.min.js +++ b/components/combobox/demo/api.min.js @@ -1370,7 +1370,7 @@ function isOverflowElement(element) { overflowX, overflowY, display - } = getComputedStyle(element); + } = getComputedStyle$1(element); return /auto|scroll|overlay|hidden|clip/.test(overflow + overflowY + overflowX) && !['inline', 'contents'].includes(display); } function isTableElement(element) { @@ -1387,7 +1387,7 @@ function isTopLayer(element) { } function isContainingBlock(elementOrCss) { const webkit = isWebKit(); - const css = isElement(elementOrCss) ? getComputedStyle(elementOrCss) : elementOrCss; + const css = isElement(elementOrCss) ? getComputedStyle$1(elementOrCss) : elementOrCss; // https://developer.mozilla.org/en-US/docs/Web/CSS/Containing_block#identifying_the_containing_block return css.transform !== 'none' || css.perspective !== 'none' || (css.containerType ? css.containerType !== 'normal' : false) || !webkit && (css.backdropFilter ? css.backdropFilter !== 'none' : false) || !webkit && (css.filter ? css.filter !== 'none' : false) || ['transform', 'perspective', 'filter'].some(value => (css.willChange || '').includes(value)) || ['paint', 'layout', 'strict', 'content'].some(value => (css.contain || '').includes(value)); @@ -1411,7 +1411,7 @@ function isWebKit() { function isLastTraversableNode(node) { return ['html', 'body', '#document'].includes(getNodeName(node)); } -function getComputedStyle(element) { +function getComputedStyle$1(element) { return getWindow(element).getComputedStyle(element); } function getNodeScroll(element) { @@ -1473,7 +1473,7 @@ function getFrameElement(win) { } function getCssDimensions(element) { - const css = getComputedStyle(element); + const css = getComputedStyle$1(element); // In testing environments, the `width` and `height` properties are empty // strings for SVG elements, returning NaN. Fallback to `0` in this case. let width = parseFloat(css.width) || 0; @@ -1578,7 +1578,7 @@ function getBoundingClientRect(element, includeScale, isFixedStrategy, offsetPar while (currentIFrame && offsetParent && offsetWin !== currentWin) { const iframeScale = getScale(currentIFrame); const iframeRect = currentIFrame.getBoundingClientRect(); - const css = getComputedStyle(currentIFrame); + const css = getComputedStyle$1(currentIFrame); const left = iframeRect.left + (currentIFrame.clientLeft + parseFloat(css.paddingLeft)) * iframeScale.x; const top = iframeRect.top + (currentIFrame.clientTop + parseFloat(css.paddingTop)) * iframeScale.y; x *= iframeScale.x; @@ -1678,7 +1678,7 @@ function getDocumentRect(element) { const height = max(html.scrollHeight, html.clientHeight, body.scrollHeight, body.clientHeight); let x = -scroll.scrollLeft + getWindowScrollBarX(element); const y = -scroll.scrollTop; - if (getComputedStyle(body).direction === 'rtl') { + if (getComputedStyle$1(body).direction === 'rtl') { x += max(html.clientWidth, body.clientWidth) - width; } return { @@ -1755,7 +1755,7 @@ function hasFixedPositionAncestor(element, stopNode) { if (parentNode === stopNode || !isElement(parentNode) || isLastTraversableNode(parentNode)) { return false; } - return getComputedStyle(parentNode).position === 'fixed' || hasFixedPositionAncestor(parentNode, stopNode); + return getComputedStyle$1(parentNode).position === 'fixed' || hasFixedPositionAncestor(parentNode, stopNode); } // A "clipping ancestor" is an `overflow` element with the characteristic of @@ -1768,12 +1768,12 @@ function getClippingElementAncestors(element, cache) { } let result = getOverflowAncestors(element, [], false).filter(el => isElement(el) && getNodeName(el) !== 'body'); let currentContainingBlockComputedStyle = null; - const elementIsFixed = getComputedStyle(element).position === 'fixed'; + const elementIsFixed = getComputedStyle$1(element).position === 'fixed'; let currentNode = elementIsFixed ? getParentNode(element) : element; // https://developer.mozilla.org/en-US/docs/Web/CSS/Containing_block#identifying_the_containing_block while (isElement(currentNode) && !isLastTraversableNode(currentNode)) { - const computedStyle = getComputedStyle(currentNode); + const computedStyle = getComputedStyle$1(currentNode); const currentNodeIsContaining = isContainingBlock(currentNode); if (!currentNodeIsContaining && computedStyle.position === 'fixed') { currentContainingBlockComputedStyle = null; @@ -1867,11 +1867,11 @@ function getRectRelativeToOffsetParent(element, offsetParent, strategy) { } function isStaticPositioned(element) { - return getComputedStyle(element).position === 'static'; + return getComputedStyle$1(element).position === 'static'; } function getTrueOffsetParent(element, polyfill) { - if (!isHTMLElement(element) || getComputedStyle(element).position === 'fixed') { + if (!isHTMLElement(element) || getComputedStyle$1(element).position === 'fixed') { return null; } if (polyfill) { @@ -1932,7 +1932,7 @@ const getElementRects = async function (data) { }; function isRTL(element) { - return getComputedStyle(element).direction === 'rtl'; + return getComputedStyle$1(element).direction === 'rtl'; } const platform = { @@ -2163,32 +2163,115 @@ class AuroFloatingUI { this.keyDownHandler = null; } - position() { - // Define the middlware for the floater configuration - const middleware = [ - offset(this.element.floaterConfig.offset || 0), - ]; - - // Add flip middleware if flip is enabled - if (this.element.floaterConfig.flip) { - middleware.push(flip()); + /** + * @private + * Adjusts the size of the bib content based on the bibSizer dimensions. + * + * This method retrieves the computed styles of the bibSizer element and applies them to the bib content. + * If the fullscreen parameter is true, it resets the dimensions to their default values. Otherwise, it + * mirrors the width and height from the bibSizer, ensuring that they are not set to zero. + * + * @param {boolean} fullscreen - A flag indicating whether to reset the dimensions for fullscreen mode. + * If true, the bib content dimensions are cleared; if false, they are set + * based on the bibSizer's computed styles. + */ + mirrorSize(fullscreen) { + // mirror the boxsize from bibSizer + const sizerStyle = window.getComputedStyle(this.element.bibSizer); + const bibContent = this.element.bib.shadowRoot.querySelector(".container"); + if (fullscreen) { + bibContent.style.width = ''; + bibContent.style.height = ''; + bibContent.style.maxWidth = ''; + bibContent.style.maxHeight = ''; + } else { + if (sizerStyle.width !== '0px') { + bibContent.style.width = sizerStyle.width; + } + if (sizerStyle.height !== '0px') { + bibContent.style.height = sizerStyle.height; + } + bibContent.style.maxWidth = sizerStyle.maxWidth; + bibContent.style.maxHeight = sizerStyle.maxHeight; } + } - // Add autoPlacement middleware if autoPlacement is enabled - if (this.element.floaterConfig.autoPlacement) { - middleware.push(autoPlacement()); + /** + * @private + * Determines the positioning strategy based on the current viewport size and mobile breakpoint. + * + * This method checks if the current viewport width is less than or equal to the specified mobile fullscreen breakpoint + * defined in the bib element. If it is, the strategy is set to 'fullscreen'; otherwise, it defaults to 'floating'. + * + * @returns {String} The positioning strategy, either 'fullscreen' or 'floating'. + */ + getPositioningStrategy() { + let strategy = 'floating'; + if (this.element.bib.mobileFullscreenBreakpoint) { + const isMobile = window.matchMedia(`(max-width: ${this.element.bib.mobileFullscreenBreakpoint})`).matches; + if (isMobile) { + strategy = 'fullscreen'; + } } + return strategy; + } - // Compute the position of the bib - computePosition(this.element.trigger, this.element.bib, { - placement: this.element.floaterConfig.placement || 'bottom', - middleware: middleware || [] - }).then(({x, y}) => { // eslint-disable-line id-length - Object.assign(this.element.bib.style, { - left: `${x}px`, - top: `${y}px`, + /** + * @private + * Positions the bib element based on the current configuration and positioning strategy. + * + * This method determines the appropriate positioning strategy (fullscreen or not) and configures the bib accordingly. + * It also sets up middleware for the floater configuration, computes the position of the bib relative to the trigger element, + * and applies the calculated position to the bib's style. + */ + position() { + const strategy = this.getPositioningStrategy(); + if (strategy === 'fullscreen') { + this.configureBibFullscreen(true); + this.mirrorSize(true); + } else { + this.configureBibFullscreen(false); + this.mirrorSize(false); + + // Define the middlware for the floater configuration + const middleware = [ + offset(this.element.floaterConfig.offset || 0), + ...(this.element.floaterConfig.flip ? [flip()] : []), // Add flip middleware if flip is enabled + ...(this.element.floaterConfig.autoPlacement ? [autoPlacement()] : []), // Add autoPlacement middleware if autoPlacement is enabled + ]; + + // Compute the position of the bib + computePosition(this.element.trigger, this.element.bib, { + placement: this.element.floaterConfig.placement || 'bottom', + middleware: middleware || [] + }).then(({x, y}) => { // eslint-disable-line id-length + Object.assign(this.element.bib.style, { + left: `${x}px`, + top: `${y}px`, + }); }); - }); + } + } + + /** + * @private + * Configures the bib element for fullscreen mode based on the mobile status. + * + * This method sets the 'isFullscreen' attribute on the bib element to "true" if the `isMobile` parameter is true, + * and resets its position to the top-left corner of the viewport. If `isMobile` is false, it removes the + * 'isFullscreen' attribute, indicating that the bib is not in fullscreen mode. + * + * @param {boolean} isMobile - A flag indicating whether the current device is mobile. + */ + configureBibFullscreen(isMobile) { + if (isMobile) { + this.element.bib.setAttribute('isFullscreen', "true"); + // reset the prev position + this.element.bib.style.top = "0px"; + this.element.bib.style.left = "0px"; + } else { + this.element.bib.removeAttribute('isFullscreen'); + } } updateState() { @@ -2418,8 +2501,11 @@ class AuroFloatingUI { this.element = elem; this.element.trigger = this.element.shadowRoot.querySelector('#trigger'); this.element.bib = this.element.shadowRoot.querySelector('#bib'); + this.element.bibSizer = this.element.shadowRoot.querySelector('#bibSizer'); this.element.triggerChevron = this.element.shadowRoot.querySelector('#showStateIcon'); + document.body.append(this.element.bib); + this.handleTriggerTabIndex(); this.element.trigger.addEventListener('keydown', (event) => this.handleEvent(event)); @@ -2851,13 +2937,13 @@ if (!customElements.get("auro-icon")) { var iconVersion$2 = '6.1.1'; -var styleCss$1$2 = i$3$1`:host{position:relative}.label{font-size:var(--ds-text-body-size-xs, 0.75rem);line-height:var(--ds-text-body-size-default, 1rem);white-space:normal}.trigger{position:relative;display:flex;align-items:center;border-width:1px;border-style:solid}@media(hover: hover){.trigger:hover{cursor:pointer}}.triggerContentWrapper{overflow:hidden;flex:1;text-overflow:ellipsis;white-space:nowrap}#showStateIcon{display:flex;height:100%;align-items:center;margin-left:var(--ds-size-100, 0.5rem)}#showStateIcon [auro-icon]{height:var(--ds-size-300, 1.5rem);line-height:var(--ds-size-300, 1.5rem)}#showStateIcon[data-expanded=true] [auro-icon]{transform:rotate(-180deg)}.helpText{margin-top:var(--ds-size-50, 0.25rem);font-size:var(--ds-text-body-size-xs, 0.75rem);line-height:var(--ds-text-body-size-default, 1rem)}:host([disabled]){pointer-events:none}:host([inset]) .trigger{padding:var(--ds-size-150, 0.75rem) var(--ds-size-200, 1rem)}:host([common]) .trigger,:host([inset][bordered]) .trigger{padding:var(--ds-size-200, 1rem) var(--ds-size-150, 0.75rem)}:host([common]) .trigger,:host([rounded]) .trigger{border-radius:var(--ds-border-radius, 0.375rem)}`; +var styleCss$1$2 = i$3$1`:host{position:relative}#bibSizer{position:absolute;z-index:-1;opacity:0;pointer-events:none}.label{font-size:var(--ds-text-body-size-xs, 0.75rem);line-height:var(--ds-text-body-size-default, 1rem);white-space:normal}.trigger{position:relative;display:flex;align-items:center;border-width:1px;border-style:solid}@media(hover: hover){.trigger:hover{cursor:pointer}}.triggerContentWrapper{overflow:hidden;flex:1;text-overflow:ellipsis;white-space:nowrap}#showStateIcon{display:flex;height:100%;align-items:center;margin-left:var(--ds-size-100, 0.5rem)}#showStateIcon [auro-icon]{height:var(--ds-size-300, 1.5rem);line-height:var(--ds-size-300, 1.5rem)}#showStateIcon[data-expanded=true] [auro-icon]{transform:rotate(-180deg)}.helpText{margin-top:var(--ds-size-50, 0.25rem);font-size:var(--ds-text-body-size-xs, 0.75rem);line-height:var(--ds-text-body-size-default, 1rem)}:host([disabled]){pointer-events:none}:host([inset]) .trigger{padding:var(--ds-size-150, 0.75rem) var(--ds-size-200, 1rem)}:host([common]) .trigger,:host([inset][bordered]) .trigger{padding:var(--ds-size-200, 1rem) var(--ds-size-150, 0.75rem)}:host([common]) .trigger,:host([rounded]) .trigger{border-radius:var(--ds-border-radius, 0.375rem)}`; var colorCss$1$2 = i$3$1`.label{color:var(--ds-auro-dropdown-label-text-color)}.trigger{border-color:var(--ds-auro-dropdown-trigger-border-color);background-color:var(--ds-auro-dropdown-trigger-container-color);color:var(--ds-auro-dropdown-trigger-text-color)}.trigger:focus-within,.trigger:active{--ds-auro-dropdown-trigger-border-color: var(--ds-color-border-active-default, #0074c8)}.trigger:focus-within:not(:active){--ds-auro-dropdown-trigger-border-color: var(--ds-color-border-ui-focus-default, #2c67b5);box-shadow:inset 0 0 0 1px var(--ds-color-border-ui-focus-default, #2c67b5)}.trigger:hover{--ds-auro-dropdown-trigger-container-color: var(--ds-color-container-ui-secondary-hover-default, rgba(0, 0, 0, 0.03))}.helpText{color:var(--ds-auro-dropdown-help-text-color)}:host([disabled]){--ds-auro-dropdown-trigger-text-color: var(--ds-color-text-ui-disabled-default, #adadad);--ds-auro-dropdown-label-text-color: var(--ds-color-text-ui-disabled-default, #adadad)}:host([common]),:host([bordered]){--ds-auro-dropdown-trigger-border-color: var(--ds-color-border-primary-default, #585e67)}:host([common]) .trigger:active,:host([common]) .trigger:focus-within,:host([bordered]) .trigger:active,:host([bordered]) .trigger:focus-within{--ds-auro-dropdown-trigger-border-color: var(--ds-color-border-ui-focus-default, #2c67b5)}:host([error]){--ds-auro-dropdown-help-text-color: var(--ds-color-text-error-default, #cc1816);--ds-auro-dropdown-trigger-border-color: var(--ds-color-border-error-default, #cc1816)}:host([error]) .trigger{box-shadow:inset 0 0 0 1px var(--ds-auro-dropdown-trigger-border-color)}:host([error]) .trigger:focus-within{--ds-auro-dropdown-trigger-border-color: var(--ds-color-border-ui-focus-default, #2c67b5);box-shadow:none}:host([error]) .trigger:active{--ds-auro-dropdown-trigger-border-color: var(--ds-color-border-ui-focus-default, #2c67b5);box-shadow:inset 0 0 0 1px var(--ds-auro-dropdown-trigger-border-color)}:host([disabled][common]),:host([disabled][bordered]){--ds-auro-dropdown-trigger-border-color: var(--ds-color-border-ui-disabled-default, #adadad)}`; var tokensCss$5 = i$3$1`:host{--ds-auro-dropdown-help-text-color: var(--ds-color-text-secondary-default, #525252);--ds-auro-dropdown-label-text-color: var(--ds-color-text-secondary-default, #525252);--ds-auro-dropdown-popover-container-color: var(--ds-color-container-primary-default, #ffffff);--ds-auro-dropdown-popover-border-color: transparent;--ds-auro-dropdown-popover-text-color: var(--ds-color-text-primary-default, #2a2a2a);--ds-auro-dropdown-trigger-container-color: var(--ds-color-container-primary-default, #ffffff);--ds-auro-dropdown-trigger-border-color: transparent;--ds-auro-dropdown-trigger-text-color: var(--ds-color-text-primary-default, #2a2a2a);--ds-auro-dropdownbib-boxshadow-color: var(--ds-elevation-200, 0px 0px 10px rgba(0, 0, 0, 0.15));--ds-auro-dropdownbib-container-color: var(--ds-color-container-primary-default, #ffffff);--ds-auro-dropdownbib-text-color: var(--ds-color-text-primary-default, #2a2a2a)}`; -var styleCss$5 = i$3$1`.container{display:inline-block;box-sizing:border-box}:host{position:absolute;z-index:1;display:none}:host([data-show]){display:block}:host([common]) .container,:host([rounded]) .container{border-radius:var(--ds-border-radius, 0.375rem)}:host([common]) .container,:host([inset]) .container{padding:var(--ds-size-50, 0.25rem) var(--ds-size-100, 0.5rem)}`; +var styleCss$5 = i$3$1`:host{position:absolute;z-index:1;display:none}.container{display:inline-block;overflow:auto;box-sizing:border-box}:host([isfullscreen]){position:fixed;top:0;left:0}:host([isfullscreen]) .container{width:100dvw;max-width:none;height:100dvh;max-height:none;border-radius:unset;box-shadow:unset}:host([data-show]){display:block}:host([common]:not([isfullscreen])) .container,:host([rounded]:not([isfullscreen])) .container{border-radius:var(--ds-border-radius, 0.375rem)}:host([common]) .container,:host([inset]) .container{padding:var(--ds-size-50, 0.25rem) var(--ds-size-100, 0.5rem)}:host([common][isfullscreen]) .container,:host([rounded][isfullscreen]) .container{border-radius:unset;box-shadow:unset}`; var colorCss$5 = i$3$1`.container{background-color:var(--ds-auro-dropdownbib-container-color);box-shadow:var(--ds-auro-dropdownbib-boxshadow-color);color:var(--ds-auro-dropdownbib-text-color)}`; @@ -2865,15 +2951,34 @@ var colorCss$5 = i$3$1`.container{background-color:var(--ds-auro-dropdownbib-con // See LICENSE in the project root for license information. + +const DESIGN_TOKEN_BREAKPOINT_PREFIX = '--ds-grid-breakpoint-'; +const DESIGN_TOKEN_BREAKPOINT_OPTIONS = [ + 'lg', + 'md', + 'sm', + 'xs', +]; + /** * @attr { Boolean } common - If declared, will apply all styles for the common theme. * @attr { Boolean } rounded - If declared, will apply border-radius to the bib. * @attr { Boolean } inset - If declared, will apply extra padding to bib content. + * @prop { String } mobileFullscreenBreakpoint - Defines the screen size breakpoint (`lg`, `md`, `sm`, or `xs`) at which the dropdown switches to fullscreen mode on mobile. When expanded, the dropdown will automatically display in fullscreen mode if the screen size is equal to or smaller than the selected breakpoint. * @csspart bibContainer - Apply css to the bib container. */ class AuroDropdownBib extends r$5 { + constructor() { + super(); + + /** + * @private + */ + this._mobileBreakpointValue = undefined; + } + static get styles() { return [ styleCss$5, @@ -2895,10 +3000,26 @@ class AuroDropdownBib extends r$5 { rounded: { type: Boolean, reflect: true - } + }, }; } + set mobileFullscreenBreakpoint(value) { + // verify the defined breakpoint is valid and exit out if not + const validatedValue = DESIGN_TOKEN_BREAKPOINT_OPTIONS.includes(value) ? value : undefined; + if (!validatedValue) { + this._mobileBreakpointValue = undefined; + } else { + // get the pixel value for the defined breakpoint + const docStyle = getComputedStyle(document.documentElement); + this._mobileBreakpointValue = docStyle.getPropertyValue(DESIGN_TOKEN_BREAKPOINT_PREFIX + value); + } + } + + get mobileFullscreenBreakpoint() { + return this._mobileBreakpointValue; + } + // function that renders the HTML and CSS into the scope of the component render() { return u$1$1` @@ -2932,6 +3053,7 @@ if (!customElements.get("auro-dropdownbib")) { * @attr { Boolean } noToggle - If declared, the trigger will only show the the dropdown bib. * @attr { Boolean } focusShow - if declared, the the bib will display when focus is applied to the trigger. * @attr { Boolean } noHideOnThisFocusLoss - If declared, the dropdown will not hide when moving focus outside the element. + * @attr { String } mobileFullscreenBreakpoint - Defines the screen size breakpoint (`lg`, `md`, `sm`, or `xs`) at which the dropdown switches to fullscreen mode on mobile. When expanded, the dropdown will automatically display in fullscreen mode if the screen size is equal to or smaller than the selected breakpoint. * @prop { Boolean } isPopoverVisible - If true, the dropdown bib is displayed. * @slot - Default slot for the popover content. * @slot label - Defines the content of the label. @@ -3072,6 +3194,10 @@ class AuroDropdown extends r$5 { type: Function, reflect: false }, + mobileFullscreenBreakpoint: { + type: String, + reflect: true, + }, /** * @private @@ -3120,18 +3246,19 @@ class AuroDropdown extends r$5 { updated(changedProperties) { this.floater.handleUpdate(changedProperties); + + if (changedProperties.has('mobileFullscreenBreakpoint')) { + this.bibContent.mobileFullscreenBreakpoint = this.mobileFullscreenBreakpoint; + } } firstUpdated() { this.floater.configure(this); + this.bibContent = this.floater.element.bib; // Add the tag name as an attribute if it is different than the component name this.runtimeUtils.handleComponentTagRename(this, 'auro-dropdown'); - this.bibContent = this.shadowRoot.querySelector('auro-dropdownbib'); - - document.body.append(this.bibContent); - this.notifyReady(); } @@ -3254,6 +3381,7 @@ class AuroDropdown extends r$5 {
+
(css.willChange || '').includes(value)) || ['paint', 'layout', 'strict', 'content'].some(value => (css.contain || '').includes(value)); @@ -1322,7 +1322,7 @@ function isWebKit() { function isLastTraversableNode(node) { return ['html', 'body', '#document'].includes(getNodeName(node)); } -function getComputedStyle(element) { +function getComputedStyle$1(element) { return getWindow(element).getComputedStyle(element); } function getNodeScroll(element) { @@ -1384,7 +1384,7 @@ function getFrameElement(win) { } function getCssDimensions(element) { - const css = getComputedStyle(element); + const css = getComputedStyle$1(element); // In testing environments, the `width` and `height` properties are empty // strings for SVG elements, returning NaN. Fallback to `0` in this case. let width = parseFloat(css.width) || 0; @@ -1489,7 +1489,7 @@ function getBoundingClientRect(element, includeScale, isFixedStrategy, offsetPar while (currentIFrame && offsetParent && offsetWin !== currentWin) { const iframeScale = getScale(currentIFrame); const iframeRect = currentIFrame.getBoundingClientRect(); - const css = getComputedStyle(currentIFrame); + const css = getComputedStyle$1(currentIFrame); const left = iframeRect.left + (currentIFrame.clientLeft + parseFloat(css.paddingLeft)) * iframeScale.x; const top = iframeRect.top + (currentIFrame.clientTop + parseFloat(css.paddingTop)) * iframeScale.y; x *= iframeScale.x; @@ -1589,7 +1589,7 @@ function getDocumentRect(element) { const height = max(html.scrollHeight, html.clientHeight, body.scrollHeight, body.clientHeight); let x = -scroll.scrollLeft + getWindowScrollBarX(element); const y = -scroll.scrollTop; - if (getComputedStyle(body).direction === 'rtl') { + if (getComputedStyle$1(body).direction === 'rtl') { x += max(html.clientWidth, body.clientWidth) - width; } return { @@ -1666,7 +1666,7 @@ function hasFixedPositionAncestor(element, stopNode) { if (parentNode === stopNode || !isElement(parentNode) || isLastTraversableNode(parentNode)) { return false; } - return getComputedStyle(parentNode).position === 'fixed' || hasFixedPositionAncestor(parentNode, stopNode); + return getComputedStyle$1(parentNode).position === 'fixed' || hasFixedPositionAncestor(parentNode, stopNode); } // A "clipping ancestor" is an `overflow` element with the characteristic of @@ -1679,12 +1679,12 @@ function getClippingElementAncestors(element, cache) { } let result = getOverflowAncestors(element, [], false).filter(el => isElement(el) && getNodeName(el) !== 'body'); let currentContainingBlockComputedStyle = null; - const elementIsFixed = getComputedStyle(element).position === 'fixed'; + const elementIsFixed = getComputedStyle$1(element).position === 'fixed'; let currentNode = elementIsFixed ? getParentNode(element) : element; // https://developer.mozilla.org/en-US/docs/Web/CSS/Containing_block#identifying_the_containing_block while (isElement(currentNode) && !isLastTraversableNode(currentNode)) { - const computedStyle = getComputedStyle(currentNode); + const computedStyle = getComputedStyle$1(currentNode); const currentNodeIsContaining = isContainingBlock(currentNode); if (!currentNodeIsContaining && computedStyle.position === 'fixed') { currentContainingBlockComputedStyle = null; @@ -1778,11 +1778,11 @@ function getRectRelativeToOffsetParent(element, offsetParent, strategy) { } function isStaticPositioned(element) { - return getComputedStyle(element).position === 'static'; + return getComputedStyle$1(element).position === 'static'; } function getTrueOffsetParent(element, polyfill) { - if (!isHTMLElement(element) || getComputedStyle(element).position === 'fixed') { + if (!isHTMLElement(element) || getComputedStyle$1(element).position === 'fixed') { return null; } if (polyfill) { @@ -1843,7 +1843,7 @@ const getElementRects = async function (data) { }; function isRTL(element) { - return getComputedStyle(element).direction === 'rtl'; + return getComputedStyle$1(element).direction === 'rtl'; } const platform = { @@ -2074,32 +2074,115 @@ class AuroFloatingUI { this.keyDownHandler = null; } - position() { - // Define the middlware for the floater configuration - const middleware = [ - offset(this.element.floaterConfig.offset || 0), - ]; - - // Add flip middleware if flip is enabled - if (this.element.floaterConfig.flip) { - middleware.push(flip()); + /** + * @private + * Adjusts the size of the bib content based on the bibSizer dimensions. + * + * This method retrieves the computed styles of the bibSizer element and applies them to the bib content. + * If the fullscreen parameter is true, it resets the dimensions to their default values. Otherwise, it + * mirrors the width and height from the bibSizer, ensuring that they are not set to zero. + * + * @param {boolean} fullscreen - A flag indicating whether to reset the dimensions for fullscreen mode. + * If true, the bib content dimensions are cleared; if false, they are set + * based on the bibSizer's computed styles. + */ + mirrorSize(fullscreen) { + // mirror the boxsize from bibSizer + const sizerStyle = window.getComputedStyle(this.element.bibSizer); + const bibContent = this.element.bib.shadowRoot.querySelector(".container"); + if (fullscreen) { + bibContent.style.width = ''; + bibContent.style.height = ''; + bibContent.style.maxWidth = ''; + bibContent.style.maxHeight = ''; + } else { + if (sizerStyle.width !== '0px') { + bibContent.style.width = sizerStyle.width; + } + if (sizerStyle.height !== '0px') { + bibContent.style.height = sizerStyle.height; + } + bibContent.style.maxWidth = sizerStyle.maxWidth; + bibContent.style.maxHeight = sizerStyle.maxHeight; } + } - // Add autoPlacement middleware if autoPlacement is enabled - if (this.element.floaterConfig.autoPlacement) { - middleware.push(autoPlacement()); + /** + * @private + * Determines the positioning strategy based on the current viewport size and mobile breakpoint. + * + * This method checks if the current viewport width is less than or equal to the specified mobile fullscreen breakpoint + * defined in the bib element. If it is, the strategy is set to 'fullscreen'; otherwise, it defaults to 'floating'. + * + * @returns {String} The positioning strategy, either 'fullscreen' or 'floating'. + */ + getPositioningStrategy() { + let strategy = 'floating'; + if (this.element.bib.mobileFullscreenBreakpoint) { + const isMobile = window.matchMedia(`(max-width: ${this.element.bib.mobileFullscreenBreakpoint})`).matches; + if (isMobile) { + strategy = 'fullscreen'; + } } + return strategy; + } - // Compute the position of the bib - computePosition(this.element.trigger, this.element.bib, { - placement: this.element.floaterConfig.placement || 'bottom', - middleware: middleware || [] - }).then(({x, y}) => { // eslint-disable-line id-length - Object.assign(this.element.bib.style, { - left: `${x}px`, - top: `${y}px`, + /** + * @private + * Positions the bib element based on the current configuration and positioning strategy. + * + * This method determines the appropriate positioning strategy (fullscreen or not) and configures the bib accordingly. + * It also sets up middleware for the floater configuration, computes the position of the bib relative to the trigger element, + * and applies the calculated position to the bib's style. + */ + position() { + const strategy = this.getPositioningStrategy(); + if (strategy === 'fullscreen') { + this.configureBibFullscreen(true); + this.mirrorSize(true); + } else { + this.configureBibFullscreen(false); + this.mirrorSize(false); + + // Define the middlware for the floater configuration + const middleware = [ + offset(this.element.floaterConfig.offset || 0), + ...(this.element.floaterConfig.flip ? [flip()] : []), // Add flip middleware if flip is enabled + ...(this.element.floaterConfig.autoPlacement ? [autoPlacement()] : []), // Add autoPlacement middleware if autoPlacement is enabled + ]; + + // Compute the position of the bib + computePosition(this.element.trigger, this.element.bib, { + placement: this.element.floaterConfig.placement || 'bottom', + middleware: middleware || [] + }).then(({x, y}) => { // eslint-disable-line id-length + Object.assign(this.element.bib.style, { + left: `${x}px`, + top: `${y}px`, + }); }); - }); + } + } + + /** + * @private + * Configures the bib element for fullscreen mode based on the mobile status. + * + * This method sets the 'isFullscreen' attribute on the bib element to "true" if the `isMobile` parameter is true, + * and resets its position to the top-left corner of the viewport. If `isMobile` is false, it removes the + * 'isFullscreen' attribute, indicating that the bib is not in fullscreen mode. + * + * @param {boolean} isMobile - A flag indicating whether the current device is mobile. + */ + configureBibFullscreen(isMobile) { + if (isMobile) { + this.element.bib.setAttribute('isFullscreen', "true"); + // reset the prev position + this.element.bib.style.top = "0px"; + this.element.bib.style.left = "0px"; + } else { + this.element.bib.removeAttribute('isFullscreen'); + } } updateState() { @@ -2329,8 +2412,11 @@ class AuroFloatingUI { this.element = elem; this.element.trigger = this.element.shadowRoot.querySelector('#trigger'); this.element.bib = this.element.shadowRoot.querySelector('#bib'); + this.element.bibSizer = this.element.shadowRoot.querySelector('#bibSizer'); this.element.triggerChevron = this.element.shadowRoot.querySelector('#showStateIcon'); + document.body.append(this.element.bib); + this.handleTriggerTabIndex(); this.element.trigger.addEventListener('keydown', (event) => this.handleEvent(event)); @@ -2762,13 +2848,13 @@ if (!customElements.get("auro-icon")) { var iconVersion$2 = '6.1.1'; -var styleCss$1$2 = i$3$1`:host{position:relative}.label{font-size:var(--ds-text-body-size-xs, 0.75rem);line-height:var(--ds-text-body-size-default, 1rem);white-space:normal}.trigger{position:relative;display:flex;align-items:center;border-width:1px;border-style:solid}@media(hover: hover){.trigger:hover{cursor:pointer}}.triggerContentWrapper{overflow:hidden;flex:1;text-overflow:ellipsis;white-space:nowrap}#showStateIcon{display:flex;height:100%;align-items:center;margin-left:var(--ds-size-100, 0.5rem)}#showStateIcon [auro-icon]{height:var(--ds-size-300, 1.5rem);line-height:var(--ds-size-300, 1.5rem)}#showStateIcon[data-expanded=true] [auro-icon]{transform:rotate(-180deg)}.helpText{margin-top:var(--ds-size-50, 0.25rem);font-size:var(--ds-text-body-size-xs, 0.75rem);line-height:var(--ds-text-body-size-default, 1rem)}:host([disabled]){pointer-events:none}:host([inset]) .trigger{padding:var(--ds-size-150, 0.75rem) var(--ds-size-200, 1rem)}:host([common]) .trigger,:host([inset][bordered]) .trigger{padding:var(--ds-size-200, 1rem) var(--ds-size-150, 0.75rem)}:host([common]) .trigger,:host([rounded]) .trigger{border-radius:var(--ds-border-radius, 0.375rem)}`; +var styleCss$1$2 = i$3$1`:host{position:relative}#bibSizer{position:absolute;z-index:-1;opacity:0;pointer-events:none}.label{font-size:var(--ds-text-body-size-xs, 0.75rem);line-height:var(--ds-text-body-size-default, 1rem);white-space:normal}.trigger{position:relative;display:flex;align-items:center;border-width:1px;border-style:solid}@media(hover: hover){.trigger:hover{cursor:pointer}}.triggerContentWrapper{overflow:hidden;flex:1;text-overflow:ellipsis;white-space:nowrap}#showStateIcon{display:flex;height:100%;align-items:center;margin-left:var(--ds-size-100, 0.5rem)}#showStateIcon [auro-icon]{height:var(--ds-size-300, 1.5rem);line-height:var(--ds-size-300, 1.5rem)}#showStateIcon[data-expanded=true] [auro-icon]{transform:rotate(-180deg)}.helpText{margin-top:var(--ds-size-50, 0.25rem);font-size:var(--ds-text-body-size-xs, 0.75rem);line-height:var(--ds-text-body-size-default, 1rem)}:host([disabled]){pointer-events:none}:host([inset]) .trigger{padding:var(--ds-size-150, 0.75rem) var(--ds-size-200, 1rem)}:host([common]) .trigger,:host([inset][bordered]) .trigger{padding:var(--ds-size-200, 1rem) var(--ds-size-150, 0.75rem)}:host([common]) .trigger,:host([rounded]) .trigger{border-radius:var(--ds-border-radius, 0.375rem)}`; var colorCss$1$2 = i$3$1`.label{color:var(--ds-auro-dropdown-label-text-color)}.trigger{border-color:var(--ds-auro-dropdown-trigger-border-color);background-color:var(--ds-auro-dropdown-trigger-container-color);color:var(--ds-auro-dropdown-trigger-text-color)}.trigger:focus-within,.trigger:active{--ds-auro-dropdown-trigger-border-color: var(--ds-color-border-active-default, #0074c8)}.trigger:focus-within:not(:active){--ds-auro-dropdown-trigger-border-color: var(--ds-color-border-ui-focus-default, #2c67b5);box-shadow:inset 0 0 0 1px var(--ds-color-border-ui-focus-default, #2c67b5)}.trigger:hover{--ds-auro-dropdown-trigger-container-color: var(--ds-color-container-ui-secondary-hover-default, rgba(0, 0, 0, 0.03))}.helpText{color:var(--ds-auro-dropdown-help-text-color)}:host([disabled]){--ds-auro-dropdown-trigger-text-color: var(--ds-color-text-ui-disabled-default, #adadad);--ds-auro-dropdown-label-text-color: var(--ds-color-text-ui-disabled-default, #adadad)}:host([common]),:host([bordered]){--ds-auro-dropdown-trigger-border-color: var(--ds-color-border-primary-default, #585e67)}:host([common]) .trigger:active,:host([common]) .trigger:focus-within,:host([bordered]) .trigger:active,:host([bordered]) .trigger:focus-within{--ds-auro-dropdown-trigger-border-color: var(--ds-color-border-ui-focus-default, #2c67b5)}:host([error]){--ds-auro-dropdown-help-text-color: var(--ds-color-text-error-default, #cc1816);--ds-auro-dropdown-trigger-border-color: var(--ds-color-border-error-default, #cc1816)}:host([error]) .trigger{box-shadow:inset 0 0 0 1px var(--ds-auro-dropdown-trigger-border-color)}:host([error]) .trigger:focus-within{--ds-auro-dropdown-trigger-border-color: var(--ds-color-border-ui-focus-default, #2c67b5);box-shadow:none}:host([error]) .trigger:active{--ds-auro-dropdown-trigger-border-color: var(--ds-color-border-ui-focus-default, #2c67b5);box-shadow:inset 0 0 0 1px var(--ds-auro-dropdown-trigger-border-color)}:host([disabled][common]),:host([disabled][bordered]){--ds-auro-dropdown-trigger-border-color: var(--ds-color-border-ui-disabled-default, #adadad)}`; var tokensCss$5 = i$3$1`:host{--ds-auro-dropdown-help-text-color: var(--ds-color-text-secondary-default, #525252);--ds-auro-dropdown-label-text-color: var(--ds-color-text-secondary-default, #525252);--ds-auro-dropdown-popover-container-color: var(--ds-color-container-primary-default, #ffffff);--ds-auro-dropdown-popover-border-color: transparent;--ds-auro-dropdown-popover-text-color: var(--ds-color-text-primary-default, #2a2a2a);--ds-auro-dropdown-trigger-container-color: var(--ds-color-container-primary-default, #ffffff);--ds-auro-dropdown-trigger-border-color: transparent;--ds-auro-dropdown-trigger-text-color: var(--ds-color-text-primary-default, #2a2a2a);--ds-auro-dropdownbib-boxshadow-color: var(--ds-elevation-200, 0px 0px 10px rgba(0, 0, 0, 0.15));--ds-auro-dropdownbib-container-color: var(--ds-color-container-primary-default, #ffffff);--ds-auro-dropdownbib-text-color: var(--ds-color-text-primary-default, #2a2a2a)}`; -var styleCss$5 = i$3$1`.container{display:inline-block;box-sizing:border-box}:host{position:absolute;z-index:1;display:none}:host([data-show]){display:block}:host([common]) .container,:host([rounded]) .container{border-radius:var(--ds-border-radius, 0.375rem)}:host([common]) .container,:host([inset]) .container{padding:var(--ds-size-50, 0.25rem) var(--ds-size-100, 0.5rem)}`; +var styleCss$5 = i$3$1`:host{position:absolute;z-index:1;display:none}.container{display:inline-block;overflow:auto;box-sizing:border-box}:host([isfullscreen]){position:fixed;top:0;left:0}:host([isfullscreen]) .container{width:100dvw;max-width:none;height:100dvh;max-height:none;border-radius:unset;box-shadow:unset}:host([data-show]){display:block}:host([common]:not([isfullscreen])) .container,:host([rounded]:not([isfullscreen])) .container{border-radius:var(--ds-border-radius, 0.375rem)}:host([common]) .container,:host([inset]) .container{padding:var(--ds-size-50, 0.25rem) var(--ds-size-100, 0.5rem)}:host([common][isfullscreen]) .container,:host([rounded][isfullscreen]) .container{border-radius:unset;box-shadow:unset}`; var colorCss$5 = i$3$1`.container{background-color:var(--ds-auro-dropdownbib-container-color);box-shadow:var(--ds-auro-dropdownbib-boxshadow-color);color:var(--ds-auro-dropdownbib-text-color)}`; @@ -2776,15 +2862,34 @@ var colorCss$5 = i$3$1`.container{background-color:var(--ds-auro-dropdownbib-con // See LICENSE in the project root for license information. + +const DESIGN_TOKEN_BREAKPOINT_PREFIX = '--ds-grid-breakpoint-'; +const DESIGN_TOKEN_BREAKPOINT_OPTIONS = [ + 'lg', + 'md', + 'sm', + 'xs', +]; + /** * @attr { Boolean } common - If declared, will apply all styles for the common theme. * @attr { Boolean } rounded - If declared, will apply border-radius to the bib. * @attr { Boolean } inset - If declared, will apply extra padding to bib content. + * @prop { String } mobileFullscreenBreakpoint - Defines the screen size breakpoint (`lg`, `md`, `sm`, or `xs`) at which the dropdown switches to fullscreen mode on mobile. When expanded, the dropdown will automatically display in fullscreen mode if the screen size is equal to or smaller than the selected breakpoint. * @csspart bibContainer - Apply css to the bib container. */ class AuroDropdownBib extends r$5 { + constructor() { + super(); + + /** + * @private + */ + this._mobileBreakpointValue = undefined; + } + static get styles() { return [ styleCss$5, @@ -2806,10 +2911,26 @@ class AuroDropdownBib extends r$5 { rounded: { type: Boolean, reflect: true - } + }, }; } + set mobileFullscreenBreakpoint(value) { + // verify the defined breakpoint is valid and exit out if not + const validatedValue = DESIGN_TOKEN_BREAKPOINT_OPTIONS.includes(value) ? value : undefined; + if (!validatedValue) { + this._mobileBreakpointValue = undefined; + } else { + // get the pixel value for the defined breakpoint + const docStyle = getComputedStyle(document.documentElement); + this._mobileBreakpointValue = docStyle.getPropertyValue(DESIGN_TOKEN_BREAKPOINT_PREFIX + value); + } + } + + get mobileFullscreenBreakpoint() { + return this._mobileBreakpointValue; + } + // function that renders the HTML and CSS into the scope of the component render() { return u$1$1` @@ -2843,6 +2964,7 @@ if (!customElements.get("auro-dropdownbib")) { * @attr { Boolean } noToggle - If declared, the trigger will only show the the dropdown bib. * @attr { Boolean } focusShow - if declared, the the bib will display when focus is applied to the trigger. * @attr { Boolean } noHideOnThisFocusLoss - If declared, the dropdown will not hide when moving focus outside the element. + * @attr { String } mobileFullscreenBreakpoint - Defines the screen size breakpoint (`lg`, `md`, `sm`, or `xs`) at which the dropdown switches to fullscreen mode on mobile. When expanded, the dropdown will automatically display in fullscreen mode if the screen size is equal to or smaller than the selected breakpoint. * @prop { Boolean } isPopoverVisible - If true, the dropdown bib is displayed. * @slot - Default slot for the popover content. * @slot label - Defines the content of the label. @@ -2983,6 +3105,10 @@ class AuroDropdown extends r$5 { type: Function, reflect: false }, + mobileFullscreenBreakpoint: { + type: String, + reflect: true, + }, /** * @private @@ -3031,18 +3157,19 @@ class AuroDropdown extends r$5 { updated(changedProperties) { this.floater.handleUpdate(changedProperties); + + if (changedProperties.has('mobileFullscreenBreakpoint')) { + this.bibContent.mobileFullscreenBreakpoint = this.mobileFullscreenBreakpoint; + } } firstUpdated() { this.floater.configure(this); + this.bibContent = this.floater.element.bib; // Add the tag name as an attribute if it is different than the component name this.runtimeUtils.handleComponentTagRename(this, 'auro-dropdown'); - this.bibContent = this.shadowRoot.querySelector('auro-dropdownbib'); - - document.body.append(this.bibContent); - this.notifyReady(); } @@ -3165,6 +3292,7 @@ class AuroDropdown extends r$5 {
+
= 0; + var canEscapeClipping = ['absolute', 'fixed'].indexOf(getComputedStyle$2(element).position) >= 0; var clipperElement = canEscapeClipping && isHTMLElement$1(element) ? getOffsetParent$1(element) : element; if (!isElement$1(clipperElement)) { @@ -11536,7 +11536,7 @@ function isOverflowElement(element) { overflowX, overflowY, display - } = getComputedStyle(element); + } = getComputedStyle$1(element); return /auto|scroll|overlay|hidden|clip/.test(overflow + overflowY + overflowX) && !['inline', 'contents'].includes(display); } function isTableElement(element) { @@ -11553,7 +11553,7 @@ function isTopLayer(element) { } function isContainingBlock(elementOrCss) { const webkit = isWebKit(); - const css = isElement(elementOrCss) ? getComputedStyle(elementOrCss) : elementOrCss; + const css = isElement(elementOrCss) ? getComputedStyle$1(elementOrCss) : elementOrCss; // https://developer.mozilla.org/en-US/docs/Web/CSS/Containing_block#identifying_the_containing_block return css.transform !== 'none' || css.perspective !== 'none' || (css.containerType ? css.containerType !== 'normal' : false) || !webkit && (css.backdropFilter ? css.backdropFilter !== 'none' : false) || !webkit && (css.filter ? css.filter !== 'none' : false) || ['transform', 'perspective', 'filter'].some(value => (css.willChange || '').includes(value)) || ['paint', 'layout', 'strict', 'content'].some(value => (css.contain || '').includes(value)); @@ -11577,7 +11577,7 @@ function isWebKit() { function isLastTraversableNode(node) { return ['html', 'body', '#document'].includes(getNodeName(node)); } -function getComputedStyle(element) { +function getComputedStyle$1(element) { return getWindow(element).getComputedStyle(element); } function getNodeScroll(element) { @@ -11639,7 +11639,7 @@ function getFrameElement(win) { } function getCssDimensions(element) { - const css = getComputedStyle(element); + const css = getComputedStyle$1(element); // In testing environments, the `width` and `height` properties are empty // strings for SVG elements, returning NaN. Fallback to `0` in this case. let width = parseFloat(css.width) || 0; @@ -11744,7 +11744,7 @@ function getBoundingClientRect(element, includeScale, isFixedStrategy, offsetPar while (currentIFrame && offsetParent && offsetWin !== currentWin) { const iframeScale = getScale(currentIFrame); const iframeRect = currentIFrame.getBoundingClientRect(); - const css = getComputedStyle(currentIFrame); + const css = getComputedStyle$1(currentIFrame); const left = iframeRect.left + (currentIFrame.clientLeft + parseFloat(css.paddingLeft)) * iframeScale.x; const top = iframeRect.top + (currentIFrame.clientTop + parseFloat(css.paddingTop)) * iframeScale.y; x *= iframeScale.x; @@ -11844,7 +11844,7 @@ function getDocumentRect(element) { const height = max(html.scrollHeight, html.clientHeight, body.scrollHeight, body.clientHeight); let x = -scroll.scrollLeft + getWindowScrollBarX(element); const y = -scroll.scrollTop; - if (getComputedStyle(body).direction === 'rtl') { + if (getComputedStyle$1(body).direction === 'rtl') { x += max(html.clientWidth, body.clientWidth) - width; } return { @@ -11921,7 +11921,7 @@ function hasFixedPositionAncestor(element, stopNode) { if (parentNode === stopNode || !isElement(parentNode) || isLastTraversableNode(parentNode)) { return false; } - return getComputedStyle(parentNode).position === 'fixed' || hasFixedPositionAncestor(parentNode, stopNode); + return getComputedStyle$1(parentNode).position === 'fixed' || hasFixedPositionAncestor(parentNode, stopNode); } // A "clipping ancestor" is an `overflow` element with the characteristic of @@ -11934,12 +11934,12 @@ function getClippingElementAncestors(element, cache) { } let result = getOverflowAncestors(element, [], false).filter(el => isElement(el) && getNodeName(el) !== 'body'); let currentContainingBlockComputedStyle = null; - const elementIsFixed = getComputedStyle(element).position === 'fixed'; + const elementIsFixed = getComputedStyle$1(element).position === 'fixed'; let currentNode = elementIsFixed ? getParentNode(element) : element; // https://developer.mozilla.org/en-US/docs/Web/CSS/Containing_block#identifying_the_containing_block while (isElement(currentNode) && !isLastTraversableNode(currentNode)) { - const computedStyle = getComputedStyle(currentNode); + const computedStyle = getComputedStyle$1(currentNode); const currentNodeIsContaining = isContainingBlock(currentNode); if (!currentNodeIsContaining && computedStyle.position === 'fixed') { currentContainingBlockComputedStyle = null; @@ -12033,11 +12033,11 @@ function getRectRelativeToOffsetParent(element, offsetParent, strategy) { } function isStaticPositioned(element) { - return getComputedStyle(element).position === 'static'; + return getComputedStyle$1(element).position === 'static'; } function getTrueOffsetParent(element, polyfill) { - if (!isHTMLElement(element) || getComputedStyle(element).position === 'fixed') { + if (!isHTMLElement(element) || getComputedStyle$1(element).position === 'fixed') { return null; } if (polyfill) { @@ -12098,7 +12098,7 @@ const getElementRects = async function (data) { }; function isRTL(element) { - return getComputedStyle(element).direction === 'rtl'; + return getComputedStyle$1(element).direction === 'rtl'; } const platform = { @@ -12329,32 +12329,115 @@ class AuroFloatingUI { this.keyDownHandler = null; } - position() { - // Define the middlware for the floater configuration - const middleware = [ - offset(this.element.floaterConfig.offset || 0), - ]; - - // Add flip middleware if flip is enabled - if (this.element.floaterConfig.flip) { - middleware.push(flip()); + /** + * @private + * Adjusts the size of the bib content based on the bibSizer dimensions. + * + * This method retrieves the computed styles of the bibSizer element and applies them to the bib content. + * If the fullscreen parameter is true, it resets the dimensions to their default values. Otherwise, it + * mirrors the width and height from the bibSizer, ensuring that they are not set to zero. + * + * @param {boolean} fullscreen - A flag indicating whether to reset the dimensions for fullscreen mode. + * If true, the bib content dimensions are cleared; if false, they are set + * based on the bibSizer's computed styles. + */ + mirrorSize(fullscreen) { + // mirror the boxsize from bibSizer + const sizerStyle = window.getComputedStyle(this.element.bibSizer); + const bibContent = this.element.bib.shadowRoot.querySelector(".container"); + if (fullscreen) { + bibContent.style.width = ''; + bibContent.style.height = ''; + bibContent.style.maxWidth = ''; + bibContent.style.maxHeight = ''; + } else { + if (sizerStyle.width !== '0px') { + bibContent.style.width = sizerStyle.width; + } + if (sizerStyle.height !== '0px') { + bibContent.style.height = sizerStyle.height; + } + bibContent.style.maxWidth = sizerStyle.maxWidth; + bibContent.style.maxHeight = sizerStyle.maxHeight; } + } - // Add autoPlacement middleware if autoPlacement is enabled - if (this.element.floaterConfig.autoPlacement) { - middleware.push(autoPlacement()); + /** + * @private + * Determines the positioning strategy based on the current viewport size and mobile breakpoint. + * + * This method checks if the current viewport width is less than or equal to the specified mobile fullscreen breakpoint + * defined in the bib element. If it is, the strategy is set to 'fullscreen'; otherwise, it defaults to 'floating'. + * + * @returns {String} The positioning strategy, either 'fullscreen' or 'floating'. + */ + getPositioningStrategy() { + let strategy = 'floating'; + if (this.element.bib.mobileFullscreenBreakpoint) { + const isMobile = window.matchMedia(`(max-width: ${this.element.bib.mobileFullscreenBreakpoint})`).matches; + if (isMobile) { + strategy = 'fullscreen'; + } } + return strategy; + } - // Compute the position of the bib - computePosition(this.element.trigger, this.element.bib, { - placement: this.element.floaterConfig.placement || 'bottom', - middleware: middleware || [] - }).then(({x, y}) => { // eslint-disable-line id-length - Object.assign(this.element.bib.style, { - left: `${x}px`, - top: `${y}px`, + /** + * @private + * Positions the bib element based on the current configuration and positioning strategy. + * + * This method determines the appropriate positioning strategy (fullscreen or not) and configures the bib accordingly. + * It also sets up middleware for the floater configuration, computes the position of the bib relative to the trigger element, + * and applies the calculated position to the bib's style. + */ + position() { + const strategy = this.getPositioningStrategy(); + if (strategy === 'fullscreen') { + this.configureBibFullscreen(true); + this.mirrorSize(true); + } else { + this.configureBibFullscreen(false); + this.mirrorSize(false); + + // Define the middlware for the floater configuration + const middleware = [ + offset(this.element.floaterConfig.offset || 0), + ...(this.element.floaterConfig.flip ? [flip()] : []), // Add flip middleware if flip is enabled + ...(this.element.floaterConfig.autoPlacement ? [autoPlacement()] : []), // Add autoPlacement middleware if autoPlacement is enabled + ]; + + // Compute the position of the bib + computePosition(this.element.trigger, this.element.bib, { + placement: this.element.floaterConfig.placement || 'bottom', + middleware: middleware || [] + }).then(({x, y}) => { // eslint-disable-line id-length + Object.assign(this.element.bib.style, { + left: `${x}px`, + top: `${y}px`, + }); }); - }); + } + } + + /** + * @private + * Configures the bib element for fullscreen mode based on the mobile status. + * + * This method sets the 'isFullscreen' attribute on the bib element to "true" if the `isMobile` parameter is true, + * and resets its position to the top-left corner of the viewport. If `isMobile` is false, it removes the + * 'isFullscreen' attribute, indicating that the bib is not in fullscreen mode. + * + * @param {boolean} isMobile - A flag indicating whether the current device is mobile. + */ + configureBibFullscreen(isMobile) { + if (isMobile) { + this.element.bib.setAttribute('isFullscreen', "true"); + // reset the prev position + this.element.bib.style.top = "0px"; + this.element.bib.style.left = "0px"; + } else { + this.element.bib.removeAttribute('isFullscreen'); + } } updateState() { @@ -12584,8 +12667,11 @@ class AuroFloatingUI { this.element = elem; this.element.trigger = this.element.shadowRoot.querySelector('#trigger'); this.element.bib = this.element.shadowRoot.querySelector('#bib'); + this.element.bibSizer = this.element.shadowRoot.querySelector('#bibSizer'); this.element.triggerChevron = this.element.shadowRoot.querySelector('#showStateIcon'); + document.body.append(this.element.bib); + this.handleTriggerTabIndex(); this.element.trigger.addEventListener('keydown', (event) => this.handleEvent(event)); @@ -13017,13 +13103,13 @@ if (!customElements.get("auro-icon")) { var iconVersion$1 = '6.1.1'; -var styleCss$1$1 = i$3$1`:host{position:relative}.label{font-size:var(--ds-text-body-size-xs, 0.75rem);line-height:var(--ds-text-body-size-default, 1rem);white-space:normal}.trigger{position:relative;display:flex;align-items:center;border-width:1px;border-style:solid}@media(hover: hover){.trigger:hover{cursor:pointer}}.triggerContentWrapper{overflow:hidden;flex:1;text-overflow:ellipsis;white-space:nowrap}#showStateIcon{display:flex;height:100%;align-items:center;margin-left:var(--ds-size-100, 0.5rem)}#showStateIcon [auro-icon]{height:var(--ds-size-300, 1.5rem);line-height:var(--ds-size-300, 1.5rem)}#showStateIcon[data-expanded=true] [auro-icon]{transform:rotate(-180deg)}.helpText{margin-top:var(--ds-size-50, 0.25rem);font-size:var(--ds-text-body-size-xs, 0.75rem);line-height:var(--ds-text-body-size-default, 1rem)}:host([disabled]){pointer-events:none}:host([inset]) .trigger{padding:var(--ds-size-150, 0.75rem) var(--ds-size-200, 1rem)}:host([common]) .trigger,:host([inset][bordered]) .trigger{padding:var(--ds-size-200, 1rem) var(--ds-size-150, 0.75rem)}:host([common]) .trigger,:host([rounded]) .trigger{border-radius:var(--ds-border-radius, 0.375rem)}`; +var styleCss$1$1 = i$3$1`:host{position:relative}#bibSizer{position:absolute;z-index:-1;opacity:0;pointer-events:none}.label{font-size:var(--ds-text-body-size-xs, 0.75rem);line-height:var(--ds-text-body-size-default, 1rem);white-space:normal}.trigger{position:relative;display:flex;align-items:center;border-width:1px;border-style:solid}@media(hover: hover){.trigger:hover{cursor:pointer}}.triggerContentWrapper{overflow:hidden;flex:1;text-overflow:ellipsis;white-space:nowrap}#showStateIcon{display:flex;height:100%;align-items:center;margin-left:var(--ds-size-100, 0.5rem)}#showStateIcon [auro-icon]{height:var(--ds-size-300, 1.5rem);line-height:var(--ds-size-300, 1.5rem)}#showStateIcon[data-expanded=true] [auro-icon]{transform:rotate(-180deg)}.helpText{margin-top:var(--ds-size-50, 0.25rem);font-size:var(--ds-text-body-size-xs, 0.75rem);line-height:var(--ds-text-body-size-default, 1rem)}:host([disabled]){pointer-events:none}:host([inset]) .trigger{padding:var(--ds-size-150, 0.75rem) var(--ds-size-200, 1rem)}:host([common]) .trigger,:host([inset][bordered]) .trigger{padding:var(--ds-size-200, 1rem) var(--ds-size-150, 0.75rem)}:host([common]) .trigger,:host([rounded]) .trigger{border-radius:var(--ds-border-radius, 0.375rem)}`; var colorCss$1$1 = i$3$1`.label{color:var(--ds-auro-dropdown-label-text-color)}.trigger{border-color:var(--ds-auro-dropdown-trigger-border-color);background-color:var(--ds-auro-dropdown-trigger-container-color);color:var(--ds-auro-dropdown-trigger-text-color)}.trigger:focus-within,.trigger:active{--ds-auro-dropdown-trigger-border-color: var(--ds-color-border-active-default, #0074c8)}.trigger:focus-within:not(:active){--ds-auro-dropdown-trigger-border-color: var(--ds-color-border-ui-focus-default, #2c67b5);box-shadow:inset 0 0 0 1px var(--ds-color-border-ui-focus-default, #2c67b5)}.trigger:hover{--ds-auro-dropdown-trigger-container-color: var(--ds-color-container-ui-secondary-hover-default, rgba(0, 0, 0, 0.03))}.helpText{color:var(--ds-auro-dropdown-help-text-color)}:host([disabled]){--ds-auro-dropdown-trigger-text-color: var(--ds-color-text-ui-disabled-default, #adadad);--ds-auro-dropdown-label-text-color: var(--ds-color-text-ui-disabled-default, #adadad)}:host([common]),:host([bordered]){--ds-auro-dropdown-trigger-border-color: var(--ds-color-border-primary-default, #585e67)}:host([common]) .trigger:active,:host([common]) .trigger:focus-within,:host([bordered]) .trigger:active,:host([bordered]) .trigger:focus-within{--ds-auro-dropdown-trigger-border-color: var(--ds-color-border-ui-focus-default, #2c67b5)}:host([error]){--ds-auro-dropdown-help-text-color: var(--ds-color-text-error-default, #cc1816);--ds-auro-dropdown-trigger-border-color: var(--ds-color-border-error-default, #cc1816)}:host([error]) .trigger{box-shadow:inset 0 0 0 1px var(--ds-auro-dropdown-trigger-border-color)}:host([error]) .trigger:focus-within{--ds-auro-dropdown-trigger-border-color: var(--ds-color-border-ui-focus-default, #2c67b5);box-shadow:none}:host([error]) .trigger:active{--ds-auro-dropdown-trigger-border-color: var(--ds-color-border-ui-focus-default, #2c67b5);box-shadow:inset 0 0 0 1px var(--ds-auro-dropdown-trigger-border-color)}:host([disabled][common]),:host([disabled][bordered]){--ds-auro-dropdown-trigger-border-color: var(--ds-color-border-ui-disabled-default, #adadad)}`; var tokensCss$4 = i$3$1`:host{--ds-auro-dropdown-help-text-color: var(--ds-color-text-secondary-default, #525252);--ds-auro-dropdown-label-text-color: var(--ds-color-text-secondary-default, #525252);--ds-auro-dropdown-popover-container-color: var(--ds-color-container-primary-default, #ffffff);--ds-auro-dropdown-popover-border-color: transparent;--ds-auro-dropdown-popover-text-color: var(--ds-color-text-primary-default, #2a2a2a);--ds-auro-dropdown-trigger-container-color: var(--ds-color-container-primary-default, #ffffff);--ds-auro-dropdown-trigger-border-color: transparent;--ds-auro-dropdown-trigger-text-color: var(--ds-color-text-primary-default, #2a2a2a);--ds-auro-dropdownbib-boxshadow-color: var(--ds-elevation-200, 0px 0px 10px rgba(0, 0, 0, 0.15));--ds-auro-dropdownbib-container-color: var(--ds-color-container-primary-default, #ffffff);--ds-auro-dropdownbib-text-color: var(--ds-color-text-primary-default, #2a2a2a)}`; -var styleCss$4 = i$3$1`.container{display:inline-block;box-sizing:border-box}:host{position:absolute;z-index:1;display:none}:host([data-show]){display:block}:host([common]) .container,:host([rounded]) .container{border-radius:var(--ds-border-radius, 0.375rem)}:host([common]) .container,:host([inset]) .container{padding:var(--ds-size-50, 0.25rem) var(--ds-size-100, 0.5rem)}`; +var styleCss$4 = i$3$1`:host{position:absolute;z-index:1;display:none}.container{display:inline-block;overflow:auto;box-sizing:border-box}:host([isfullscreen]){position:fixed;top:0;left:0}:host([isfullscreen]) .container{width:100dvw;max-width:none;height:100dvh;max-height:none;border-radius:unset;box-shadow:unset}:host([data-show]){display:block}:host([common]:not([isfullscreen])) .container,:host([rounded]:not([isfullscreen])) .container{border-radius:var(--ds-border-radius, 0.375rem)}:host([common]) .container,:host([inset]) .container{padding:var(--ds-size-50, 0.25rem) var(--ds-size-100, 0.5rem)}:host([common][isfullscreen]) .container,:host([rounded][isfullscreen]) .container{border-radius:unset;box-shadow:unset}`; var colorCss$4 = i$3$1`.container{background-color:var(--ds-auro-dropdownbib-container-color);box-shadow:var(--ds-auro-dropdownbib-boxshadow-color);color:var(--ds-auro-dropdownbib-text-color)}`; @@ -13031,15 +13117,34 @@ var colorCss$4 = i$3$1`.container{background-color:var(--ds-auro-dropdownbib-con // See LICENSE in the project root for license information. + +const DESIGN_TOKEN_BREAKPOINT_PREFIX = '--ds-grid-breakpoint-'; +const DESIGN_TOKEN_BREAKPOINT_OPTIONS = [ + 'lg', + 'md', + 'sm', + 'xs', +]; + /** * @attr { Boolean } common - If declared, will apply all styles for the common theme. * @attr { Boolean } rounded - If declared, will apply border-radius to the bib. * @attr { Boolean } inset - If declared, will apply extra padding to bib content. + * @prop { String } mobileFullscreenBreakpoint - Defines the screen size breakpoint (`lg`, `md`, `sm`, or `xs`) at which the dropdown switches to fullscreen mode on mobile. When expanded, the dropdown will automatically display in fullscreen mode if the screen size is equal to or smaller than the selected breakpoint. * @csspart bibContainer - Apply css to the bib container. */ class AuroDropdownBib extends r$5 { + constructor() { + super(); + + /** + * @private + */ + this._mobileBreakpointValue = undefined; + } + static get styles() { return [ styleCss$4, @@ -13061,10 +13166,26 @@ class AuroDropdownBib extends r$5 { rounded: { type: Boolean, reflect: true - } + }, }; } + set mobileFullscreenBreakpoint(value) { + // verify the defined breakpoint is valid and exit out if not + const validatedValue = DESIGN_TOKEN_BREAKPOINT_OPTIONS.includes(value) ? value : undefined; + if (!validatedValue) { + this._mobileBreakpointValue = undefined; + } else { + // get the pixel value for the defined breakpoint + const docStyle = getComputedStyle(document.documentElement); + this._mobileBreakpointValue = docStyle.getPropertyValue(DESIGN_TOKEN_BREAKPOINT_PREFIX + value); + } + } + + get mobileFullscreenBreakpoint() { + return this._mobileBreakpointValue; + } + // function that renders the HTML and CSS into the scope of the component render() { return u$1$1` @@ -13098,6 +13219,7 @@ if (!customElements.get("auro-dropdownbib")) { * @attr { Boolean } noToggle - If declared, the trigger will only show the the dropdown bib. * @attr { Boolean } focusShow - if declared, the the bib will display when focus is applied to the trigger. * @attr { Boolean } noHideOnThisFocusLoss - If declared, the dropdown will not hide when moving focus outside the element. + * @attr { String } mobileFullscreenBreakpoint - Defines the screen size breakpoint (`lg`, `md`, `sm`, or `xs`) at which the dropdown switches to fullscreen mode on mobile. When expanded, the dropdown will automatically display in fullscreen mode if the screen size is equal to or smaller than the selected breakpoint. * @prop { Boolean } isPopoverVisible - If true, the dropdown bib is displayed. * @slot - Default slot for the popover content. * @slot label - Defines the content of the label. @@ -13238,6 +13360,10 @@ class AuroDropdown extends r$5 { type: Function, reflect: false }, + mobileFullscreenBreakpoint: { + type: String, + reflect: true, + }, /** * @private @@ -13286,18 +13412,19 @@ class AuroDropdown extends r$5 { updated(changedProperties) { this.floater.handleUpdate(changedProperties); + + if (changedProperties.has('mobileFullscreenBreakpoint')) { + this.bibContent.mobileFullscreenBreakpoint = this.mobileFullscreenBreakpoint; + } } firstUpdated() { this.floater.configure(this); + this.bibContent = this.floater.element.bib; // Add the tag name as an attribute if it is different than the component name this.runtimeUtils.handleComponentTagRename(this, 'auro-dropdown'); - this.bibContent = this.shadowRoot.querySelector('auro-dropdownbib'); - - document.body.append(this.bibContent); - this.notifyReady(); } @@ -13420,6 +13547,7 @@ class AuroDropdown extends r$5 {
+
= 0; + var canEscapeClipping = ['absolute', 'fixed'].indexOf(getComputedStyle$2(element).position) >= 0; var clipperElement = canEscapeClipping && isHTMLElement$1(element) ? getOffsetParent$1(element) : element; if (!isElement$1(clipperElement)) { @@ -11291,7 +11291,7 @@ function isOverflowElement(element) { overflowX, overflowY, display - } = getComputedStyle(element); + } = getComputedStyle$1(element); return /auto|scroll|overlay|hidden|clip/.test(overflow + overflowY + overflowX) && !['inline', 'contents'].includes(display); } function isTableElement(element) { @@ -11308,7 +11308,7 @@ function isTopLayer(element) { } function isContainingBlock(elementOrCss) { const webkit = isWebKit(); - const css = isElement(elementOrCss) ? getComputedStyle(elementOrCss) : elementOrCss; + const css = isElement(elementOrCss) ? getComputedStyle$1(elementOrCss) : elementOrCss; // https://developer.mozilla.org/en-US/docs/Web/CSS/Containing_block#identifying_the_containing_block return css.transform !== 'none' || css.perspective !== 'none' || (css.containerType ? css.containerType !== 'normal' : false) || !webkit && (css.backdropFilter ? css.backdropFilter !== 'none' : false) || !webkit && (css.filter ? css.filter !== 'none' : false) || ['transform', 'perspective', 'filter'].some(value => (css.willChange || '').includes(value)) || ['paint', 'layout', 'strict', 'content'].some(value => (css.contain || '').includes(value)); @@ -11332,7 +11332,7 @@ function isWebKit() { function isLastTraversableNode(node) { return ['html', 'body', '#document'].includes(getNodeName(node)); } -function getComputedStyle(element) { +function getComputedStyle$1(element) { return getWindow(element).getComputedStyle(element); } function getNodeScroll(element) { @@ -11394,7 +11394,7 @@ function getFrameElement(win) { } function getCssDimensions(element) { - const css = getComputedStyle(element); + const css = getComputedStyle$1(element); // In testing environments, the `width` and `height` properties are empty // strings for SVG elements, returning NaN. Fallback to `0` in this case. let width = parseFloat(css.width) || 0; @@ -11499,7 +11499,7 @@ function getBoundingClientRect(element, includeScale, isFixedStrategy, offsetPar while (currentIFrame && offsetParent && offsetWin !== currentWin) { const iframeScale = getScale(currentIFrame); const iframeRect = currentIFrame.getBoundingClientRect(); - const css = getComputedStyle(currentIFrame); + const css = getComputedStyle$1(currentIFrame); const left = iframeRect.left + (currentIFrame.clientLeft + parseFloat(css.paddingLeft)) * iframeScale.x; const top = iframeRect.top + (currentIFrame.clientTop + parseFloat(css.paddingTop)) * iframeScale.y; x *= iframeScale.x; @@ -11599,7 +11599,7 @@ function getDocumentRect(element) { const height = max(html.scrollHeight, html.clientHeight, body.scrollHeight, body.clientHeight); let x = -scroll.scrollLeft + getWindowScrollBarX(element); const y = -scroll.scrollTop; - if (getComputedStyle(body).direction === 'rtl') { + if (getComputedStyle$1(body).direction === 'rtl') { x += max(html.clientWidth, body.clientWidth) - width; } return { @@ -11676,7 +11676,7 @@ function hasFixedPositionAncestor(element, stopNode) { if (parentNode === stopNode || !isElement(parentNode) || isLastTraversableNode(parentNode)) { return false; } - return getComputedStyle(parentNode).position === 'fixed' || hasFixedPositionAncestor(parentNode, stopNode); + return getComputedStyle$1(parentNode).position === 'fixed' || hasFixedPositionAncestor(parentNode, stopNode); } // A "clipping ancestor" is an `overflow` element with the characteristic of @@ -11689,12 +11689,12 @@ function getClippingElementAncestors(element, cache) { } let result = getOverflowAncestors(element, [], false).filter(el => isElement(el) && getNodeName(el) !== 'body'); let currentContainingBlockComputedStyle = null; - const elementIsFixed = getComputedStyle(element).position === 'fixed'; + const elementIsFixed = getComputedStyle$1(element).position === 'fixed'; let currentNode = elementIsFixed ? getParentNode(element) : element; // https://developer.mozilla.org/en-US/docs/Web/CSS/Containing_block#identifying_the_containing_block while (isElement(currentNode) && !isLastTraversableNode(currentNode)) { - const computedStyle = getComputedStyle(currentNode); + const computedStyle = getComputedStyle$1(currentNode); const currentNodeIsContaining = isContainingBlock(currentNode); if (!currentNodeIsContaining && computedStyle.position === 'fixed') { currentContainingBlockComputedStyle = null; @@ -11788,11 +11788,11 @@ function getRectRelativeToOffsetParent(element, offsetParent, strategy) { } function isStaticPositioned(element) { - return getComputedStyle(element).position === 'static'; + return getComputedStyle$1(element).position === 'static'; } function getTrueOffsetParent(element, polyfill) { - if (!isHTMLElement(element) || getComputedStyle(element).position === 'fixed') { + if (!isHTMLElement(element) || getComputedStyle$1(element).position === 'fixed') { return null; } if (polyfill) { @@ -11853,7 +11853,7 @@ const getElementRects = async function (data) { }; function isRTL(element) { - return getComputedStyle(element).direction === 'rtl'; + return getComputedStyle$1(element).direction === 'rtl'; } const platform = { @@ -12084,32 +12084,115 @@ class AuroFloatingUI { this.keyDownHandler = null; } - position() { - // Define the middlware for the floater configuration - const middleware = [ - offset(this.element.floaterConfig.offset || 0), - ]; - - // Add flip middleware if flip is enabled - if (this.element.floaterConfig.flip) { - middleware.push(flip()); + /** + * @private + * Adjusts the size of the bib content based on the bibSizer dimensions. + * + * This method retrieves the computed styles of the bibSizer element and applies them to the bib content. + * If the fullscreen parameter is true, it resets the dimensions to their default values. Otherwise, it + * mirrors the width and height from the bibSizer, ensuring that they are not set to zero. + * + * @param {boolean} fullscreen - A flag indicating whether to reset the dimensions for fullscreen mode. + * If true, the bib content dimensions are cleared; if false, they are set + * based on the bibSizer's computed styles. + */ + mirrorSize(fullscreen) { + // mirror the boxsize from bibSizer + const sizerStyle = window.getComputedStyle(this.element.bibSizer); + const bibContent = this.element.bib.shadowRoot.querySelector(".container"); + if (fullscreen) { + bibContent.style.width = ''; + bibContent.style.height = ''; + bibContent.style.maxWidth = ''; + bibContent.style.maxHeight = ''; + } else { + if (sizerStyle.width !== '0px') { + bibContent.style.width = sizerStyle.width; + } + if (sizerStyle.height !== '0px') { + bibContent.style.height = sizerStyle.height; + } + bibContent.style.maxWidth = sizerStyle.maxWidth; + bibContent.style.maxHeight = sizerStyle.maxHeight; } + } - // Add autoPlacement middleware if autoPlacement is enabled - if (this.element.floaterConfig.autoPlacement) { - middleware.push(autoPlacement()); + /** + * @private + * Determines the positioning strategy based on the current viewport size and mobile breakpoint. + * + * This method checks if the current viewport width is less than or equal to the specified mobile fullscreen breakpoint + * defined in the bib element. If it is, the strategy is set to 'fullscreen'; otherwise, it defaults to 'floating'. + * + * @returns {String} The positioning strategy, either 'fullscreen' or 'floating'. + */ + getPositioningStrategy() { + let strategy = 'floating'; + if (this.element.bib.mobileFullscreenBreakpoint) { + const isMobile = window.matchMedia(`(max-width: ${this.element.bib.mobileFullscreenBreakpoint})`).matches; + if (isMobile) { + strategy = 'fullscreen'; + } } + return strategy; + } - // Compute the position of the bib - computePosition(this.element.trigger, this.element.bib, { - placement: this.element.floaterConfig.placement || 'bottom', - middleware: middleware || [] - }).then(({x, y}) => { // eslint-disable-line id-length - Object.assign(this.element.bib.style, { - left: `${x}px`, - top: `${y}px`, + /** + * @private + * Positions the bib element based on the current configuration and positioning strategy. + * + * This method determines the appropriate positioning strategy (fullscreen or not) and configures the bib accordingly. + * It also sets up middleware for the floater configuration, computes the position of the bib relative to the trigger element, + * and applies the calculated position to the bib's style. + */ + position() { + const strategy = this.getPositioningStrategy(); + if (strategy === 'fullscreen') { + this.configureBibFullscreen(true); + this.mirrorSize(true); + } else { + this.configureBibFullscreen(false); + this.mirrorSize(false); + + // Define the middlware for the floater configuration + const middleware = [ + offset(this.element.floaterConfig.offset || 0), + ...(this.element.floaterConfig.flip ? [flip()] : []), // Add flip middleware if flip is enabled + ...(this.element.floaterConfig.autoPlacement ? [autoPlacement()] : []), // Add autoPlacement middleware if autoPlacement is enabled + ]; + + // Compute the position of the bib + computePosition(this.element.trigger, this.element.bib, { + placement: this.element.floaterConfig.placement || 'bottom', + middleware: middleware || [] + }).then(({x, y}) => { // eslint-disable-line id-length + Object.assign(this.element.bib.style, { + left: `${x}px`, + top: `${y}px`, + }); }); - }); + } + } + + /** + * @private + * Configures the bib element for fullscreen mode based on the mobile status. + * + * This method sets the 'isFullscreen' attribute on the bib element to "true" if the `isMobile` parameter is true, + * and resets its position to the top-left corner of the viewport. If `isMobile` is false, it removes the + * 'isFullscreen' attribute, indicating that the bib is not in fullscreen mode. + * + * @param {boolean} isMobile - A flag indicating whether the current device is mobile. + */ + configureBibFullscreen(isMobile) { + if (isMobile) { + this.element.bib.setAttribute('isFullscreen', "true"); + // reset the prev position + this.element.bib.style.top = "0px"; + this.element.bib.style.left = "0px"; + } else { + this.element.bib.removeAttribute('isFullscreen'); + } } updateState() { @@ -12339,8 +12422,11 @@ class AuroFloatingUI { this.element = elem; this.element.trigger = this.element.shadowRoot.querySelector('#trigger'); this.element.bib = this.element.shadowRoot.querySelector('#bib'); + this.element.bibSizer = this.element.shadowRoot.querySelector('#bibSizer'); this.element.triggerChevron = this.element.shadowRoot.querySelector('#showStateIcon'); + document.body.append(this.element.bib); + this.handleTriggerTabIndex(); this.element.trigger.addEventListener('keydown', (event) => this.handleEvent(event)); @@ -12772,13 +12858,13 @@ if (!customElements.get("auro-icon")) { var iconVersion$1 = '6.1.1'; -var styleCss$1$1 = i$3$1`:host{position:relative}.label{font-size:var(--ds-text-body-size-xs, 0.75rem);line-height:var(--ds-text-body-size-default, 1rem);white-space:normal}.trigger{position:relative;display:flex;align-items:center;border-width:1px;border-style:solid}@media(hover: hover){.trigger:hover{cursor:pointer}}.triggerContentWrapper{overflow:hidden;flex:1;text-overflow:ellipsis;white-space:nowrap}#showStateIcon{display:flex;height:100%;align-items:center;margin-left:var(--ds-size-100, 0.5rem)}#showStateIcon [auro-icon]{height:var(--ds-size-300, 1.5rem);line-height:var(--ds-size-300, 1.5rem)}#showStateIcon[data-expanded=true] [auro-icon]{transform:rotate(-180deg)}.helpText{margin-top:var(--ds-size-50, 0.25rem);font-size:var(--ds-text-body-size-xs, 0.75rem);line-height:var(--ds-text-body-size-default, 1rem)}:host([disabled]){pointer-events:none}:host([inset]) .trigger{padding:var(--ds-size-150, 0.75rem) var(--ds-size-200, 1rem)}:host([common]) .trigger,:host([inset][bordered]) .trigger{padding:var(--ds-size-200, 1rem) var(--ds-size-150, 0.75rem)}:host([common]) .trigger,:host([rounded]) .trigger{border-radius:var(--ds-border-radius, 0.375rem)}`; +var styleCss$1$1 = i$3$1`:host{position:relative}#bibSizer{position:absolute;z-index:-1;opacity:0;pointer-events:none}.label{font-size:var(--ds-text-body-size-xs, 0.75rem);line-height:var(--ds-text-body-size-default, 1rem);white-space:normal}.trigger{position:relative;display:flex;align-items:center;border-width:1px;border-style:solid}@media(hover: hover){.trigger:hover{cursor:pointer}}.triggerContentWrapper{overflow:hidden;flex:1;text-overflow:ellipsis;white-space:nowrap}#showStateIcon{display:flex;height:100%;align-items:center;margin-left:var(--ds-size-100, 0.5rem)}#showStateIcon [auro-icon]{height:var(--ds-size-300, 1.5rem);line-height:var(--ds-size-300, 1.5rem)}#showStateIcon[data-expanded=true] [auro-icon]{transform:rotate(-180deg)}.helpText{margin-top:var(--ds-size-50, 0.25rem);font-size:var(--ds-text-body-size-xs, 0.75rem);line-height:var(--ds-text-body-size-default, 1rem)}:host([disabled]){pointer-events:none}:host([inset]) .trigger{padding:var(--ds-size-150, 0.75rem) var(--ds-size-200, 1rem)}:host([common]) .trigger,:host([inset][bordered]) .trigger{padding:var(--ds-size-200, 1rem) var(--ds-size-150, 0.75rem)}:host([common]) .trigger,:host([rounded]) .trigger{border-radius:var(--ds-border-radius, 0.375rem)}`; var colorCss$1$1 = i$3$1`.label{color:var(--ds-auro-dropdown-label-text-color)}.trigger{border-color:var(--ds-auro-dropdown-trigger-border-color);background-color:var(--ds-auro-dropdown-trigger-container-color);color:var(--ds-auro-dropdown-trigger-text-color)}.trigger:focus-within,.trigger:active{--ds-auro-dropdown-trigger-border-color: var(--ds-color-border-active-default, #0074c8)}.trigger:focus-within:not(:active){--ds-auro-dropdown-trigger-border-color: var(--ds-color-border-ui-focus-default, #2c67b5);box-shadow:inset 0 0 0 1px var(--ds-color-border-ui-focus-default, #2c67b5)}.trigger:hover{--ds-auro-dropdown-trigger-container-color: var(--ds-color-container-ui-secondary-hover-default, rgba(0, 0, 0, 0.03))}.helpText{color:var(--ds-auro-dropdown-help-text-color)}:host([disabled]){--ds-auro-dropdown-trigger-text-color: var(--ds-color-text-ui-disabled-default, #adadad);--ds-auro-dropdown-label-text-color: var(--ds-color-text-ui-disabled-default, #adadad)}:host([common]),:host([bordered]){--ds-auro-dropdown-trigger-border-color: var(--ds-color-border-primary-default, #585e67)}:host([common]) .trigger:active,:host([common]) .trigger:focus-within,:host([bordered]) .trigger:active,:host([bordered]) .trigger:focus-within{--ds-auro-dropdown-trigger-border-color: var(--ds-color-border-ui-focus-default, #2c67b5)}:host([error]){--ds-auro-dropdown-help-text-color: var(--ds-color-text-error-default, #cc1816);--ds-auro-dropdown-trigger-border-color: var(--ds-color-border-error-default, #cc1816)}:host([error]) .trigger{box-shadow:inset 0 0 0 1px var(--ds-auro-dropdown-trigger-border-color)}:host([error]) .trigger:focus-within{--ds-auro-dropdown-trigger-border-color: var(--ds-color-border-ui-focus-default, #2c67b5);box-shadow:none}:host([error]) .trigger:active{--ds-auro-dropdown-trigger-border-color: var(--ds-color-border-ui-focus-default, #2c67b5);box-shadow:inset 0 0 0 1px var(--ds-auro-dropdown-trigger-border-color)}:host([disabled][common]),:host([disabled][bordered]){--ds-auro-dropdown-trigger-border-color: var(--ds-color-border-ui-disabled-default, #adadad)}`; var tokensCss$4 = i$3$1`:host{--ds-auro-dropdown-help-text-color: var(--ds-color-text-secondary-default, #525252);--ds-auro-dropdown-label-text-color: var(--ds-color-text-secondary-default, #525252);--ds-auro-dropdown-popover-container-color: var(--ds-color-container-primary-default, #ffffff);--ds-auro-dropdown-popover-border-color: transparent;--ds-auro-dropdown-popover-text-color: var(--ds-color-text-primary-default, #2a2a2a);--ds-auro-dropdown-trigger-container-color: var(--ds-color-container-primary-default, #ffffff);--ds-auro-dropdown-trigger-border-color: transparent;--ds-auro-dropdown-trigger-text-color: var(--ds-color-text-primary-default, #2a2a2a);--ds-auro-dropdownbib-boxshadow-color: var(--ds-elevation-200, 0px 0px 10px rgba(0, 0, 0, 0.15));--ds-auro-dropdownbib-container-color: var(--ds-color-container-primary-default, #ffffff);--ds-auro-dropdownbib-text-color: var(--ds-color-text-primary-default, #2a2a2a)}`; -var styleCss$4 = i$3$1`.container{display:inline-block;box-sizing:border-box}:host{position:absolute;z-index:1;display:none}:host([data-show]){display:block}:host([common]) .container,:host([rounded]) .container{border-radius:var(--ds-border-radius, 0.375rem)}:host([common]) .container,:host([inset]) .container{padding:var(--ds-size-50, 0.25rem) var(--ds-size-100, 0.5rem)}`; +var styleCss$4 = i$3$1`:host{position:absolute;z-index:1;display:none}.container{display:inline-block;overflow:auto;box-sizing:border-box}:host([isfullscreen]){position:fixed;top:0;left:0}:host([isfullscreen]) .container{width:100dvw;max-width:none;height:100dvh;max-height:none;border-radius:unset;box-shadow:unset}:host([data-show]){display:block}:host([common]:not([isfullscreen])) .container,:host([rounded]:not([isfullscreen])) .container{border-radius:var(--ds-border-radius, 0.375rem)}:host([common]) .container,:host([inset]) .container{padding:var(--ds-size-50, 0.25rem) var(--ds-size-100, 0.5rem)}:host([common][isfullscreen]) .container,:host([rounded][isfullscreen]) .container{border-radius:unset;box-shadow:unset}`; var colorCss$4 = i$3$1`.container{background-color:var(--ds-auro-dropdownbib-container-color);box-shadow:var(--ds-auro-dropdownbib-boxshadow-color);color:var(--ds-auro-dropdownbib-text-color)}`; @@ -12786,15 +12872,34 @@ var colorCss$4 = i$3$1`.container{background-color:var(--ds-auro-dropdownbib-con // See LICENSE in the project root for license information. + +const DESIGN_TOKEN_BREAKPOINT_PREFIX = '--ds-grid-breakpoint-'; +const DESIGN_TOKEN_BREAKPOINT_OPTIONS = [ + 'lg', + 'md', + 'sm', + 'xs', +]; + /** * @attr { Boolean } common - If declared, will apply all styles for the common theme. * @attr { Boolean } rounded - If declared, will apply border-radius to the bib. * @attr { Boolean } inset - If declared, will apply extra padding to bib content. + * @prop { String } mobileFullscreenBreakpoint - Defines the screen size breakpoint (`lg`, `md`, `sm`, or `xs`) at which the dropdown switches to fullscreen mode on mobile. When expanded, the dropdown will automatically display in fullscreen mode if the screen size is equal to or smaller than the selected breakpoint. * @csspart bibContainer - Apply css to the bib container. */ class AuroDropdownBib extends r$5 { + constructor() { + super(); + + /** + * @private + */ + this._mobileBreakpointValue = undefined; + } + static get styles() { return [ styleCss$4, @@ -12816,10 +12921,26 @@ class AuroDropdownBib extends r$5 { rounded: { type: Boolean, reflect: true - } + }, }; } + set mobileFullscreenBreakpoint(value) { + // verify the defined breakpoint is valid and exit out if not + const validatedValue = DESIGN_TOKEN_BREAKPOINT_OPTIONS.includes(value) ? value : undefined; + if (!validatedValue) { + this._mobileBreakpointValue = undefined; + } else { + // get the pixel value for the defined breakpoint + const docStyle = getComputedStyle(document.documentElement); + this._mobileBreakpointValue = docStyle.getPropertyValue(DESIGN_TOKEN_BREAKPOINT_PREFIX + value); + } + } + + get mobileFullscreenBreakpoint() { + return this._mobileBreakpointValue; + } + // function that renders the HTML and CSS into the scope of the component render() { return u$1$1` @@ -12853,6 +12974,7 @@ if (!customElements.get("auro-dropdownbib")) { * @attr { Boolean } noToggle - If declared, the trigger will only show the the dropdown bib. * @attr { Boolean } focusShow - if declared, the the bib will display when focus is applied to the trigger. * @attr { Boolean } noHideOnThisFocusLoss - If declared, the dropdown will not hide when moving focus outside the element. + * @attr { String } mobileFullscreenBreakpoint - Defines the screen size breakpoint (`lg`, `md`, `sm`, or `xs`) at which the dropdown switches to fullscreen mode on mobile. When expanded, the dropdown will automatically display in fullscreen mode if the screen size is equal to or smaller than the selected breakpoint. * @prop { Boolean } isPopoverVisible - If true, the dropdown bib is displayed. * @slot - Default slot for the popover content. * @slot label - Defines the content of the label. @@ -12993,6 +13115,10 @@ class AuroDropdown extends r$5 { type: Function, reflect: false }, + mobileFullscreenBreakpoint: { + type: String, + reflect: true, + }, /** * @private @@ -13041,18 +13167,19 @@ class AuroDropdown extends r$5 { updated(changedProperties) { this.floater.handleUpdate(changedProperties); + + if (changedProperties.has('mobileFullscreenBreakpoint')) { + this.bibContent.mobileFullscreenBreakpoint = this.mobileFullscreenBreakpoint; + } } firstUpdated() { this.floater.configure(this); + this.bibContent = this.floater.element.bib; // Add the tag name as an attribute if it is different than the component name this.runtimeUtils.handleComponentTagRename(this, 'auro-dropdown'); - this.bibContent = this.shadowRoot.querySelector('auro-dropdownbib'); - - document.body.append(this.bibContent); - this.notifyReady(); } @@ -13175,6 +13302,7 @@ class AuroDropdown extends r$5 {
+
this.handleEvent(event)); diff --git a/components/dropdown/demo/index.min.js b/components/dropdown/demo/index.min.js index 78c79ad0..a5fc3cf7 100644 --- a/components/dropdown/demo/index.min.js +++ b/components/dropdown/demo/index.min.js @@ -1944,6 +1944,8 @@ class AuroFloatingUI { this.element.bibSizer = this.element.shadowRoot.querySelector('#bibSizer'); this.element.triggerChevron = this.element.shadowRoot.querySelector('#showStateIcon'); + document.body.append(this.element.bib); + this.handleTriggerTabIndex(); this.element.trigger.addEventListener('keydown', (event) => this.handleEvent(event)); diff --git a/components/dropdown/src/floatingUI.mjs b/components/dropdown/src/floatingUI.mjs index 92e884a2..40197d75 100644 --- a/components/dropdown/src/floatingUI.mjs +++ b/components/dropdown/src/floatingUI.mjs @@ -352,6 +352,8 @@ export default class AuroFloatingUI { this.element.bibSizer = this.element.shadowRoot.querySelector('#bibSizer'); this.element.triggerChevron = this.element.shadowRoot.querySelector('#showStateIcon'); + document.body.append(this.element.bib); + this.handleTriggerTabIndex(); this.element.trigger.addEventListener('keydown', (event) => this.handleEvent(event)); diff --git a/components/select/demo/api.min.js b/components/select/demo/api.min.js index e18e7f85..494b2fab 100644 --- a/components/select/demo/api.min.js +++ b/components/select/demo/api.min.js @@ -1310,7 +1310,7 @@ function isOverflowElement(element) { overflowX, overflowY, display - } = getComputedStyle(element); + } = getComputedStyle$1(element); return /auto|scroll|overlay|hidden|clip/.test(overflow + overflowY + overflowX) && !['inline', 'contents'].includes(display); } function isTableElement(element) { @@ -1327,7 +1327,7 @@ function isTopLayer(element) { } function isContainingBlock(elementOrCss) { const webkit = isWebKit(); - const css = isElement(elementOrCss) ? getComputedStyle(elementOrCss) : elementOrCss; + const css = isElement(elementOrCss) ? getComputedStyle$1(elementOrCss) : elementOrCss; // https://developer.mozilla.org/en-US/docs/Web/CSS/Containing_block#identifying_the_containing_block return css.transform !== 'none' || css.perspective !== 'none' || (css.containerType ? css.containerType !== 'normal' : false) || !webkit && (css.backdropFilter ? css.backdropFilter !== 'none' : false) || !webkit && (css.filter ? css.filter !== 'none' : false) || ['transform', 'perspective', 'filter'].some(value => (css.willChange || '').includes(value)) || ['paint', 'layout', 'strict', 'content'].some(value => (css.contain || '').includes(value)); @@ -1351,7 +1351,7 @@ function isWebKit() { function isLastTraversableNode(node) { return ['html', 'body', '#document'].includes(getNodeName(node)); } -function getComputedStyle(element) { +function getComputedStyle$1(element) { return getWindow(element).getComputedStyle(element); } function getNodeScroll(element) { @@ -1413,7 +1413,7 @@ function getFrameElement(win) { } function getCssDimensions(element) { - const css = getComputedStyle(element); + const css = getComputedStyle$1(element); // In testing environments, the `width` and `height` properties are empty // strings for SVG elements, returning NaN. Fallback to `0` in this case. let width = parseFloat(css.width) || 0; @@ -1518,7 +1518,7 @@ function getBoundingClientRect(element, includeScale, isFixedStrategy, offsetPar while (currentIFrame && offsetParent && offsetWin !== currentWin) { const iframeScale = getScale(currentIFrame); const iframeRect = currentIFrame.getBoundingClientRect(); - const css = getComputedStyle(currentIFrame); + const css = getComputedStyle$1(currentIFrame); const left = iframeRect.left + (currentIFrame.clientLeft + parseFloat(css.paddingLeft)) * iframeScale.x; const top = iframeRect.top + (currentIFrame.clientTop + parseFloat(css.paddingTop)) * iframeScale.y; x *= iframeScale.x; @@ -1618,7 +1618,7 @@ function getDocumentRect(element) { const height = max(html.scrollHeight, html.clientHeight, body.scrollHeight, body.clientHeight); let x = -scroll.scrollLeft + getWindowScrollBarX(element); const y = -scroll.scrollTop; - if (getComputedStyle(body).direction === 'rtl') { + if (getComputedStyle$1(body).direction === 'rtl') { x += max(html.clientWidth, body.clientWidth) - width; } return { @@ -1695,7 +1695,7 @@ function hasFixedPositionAncestor(element, stopNode) { if (parentNode === stopNode || !isElement(parentNode) || isLastTraversableNode(parentNode)) { return false; } - return getComputedStyle(parentNode).position === 'fixed' || hasFixedPositionAncestor(parentNode, stopNode); + return getComputedStyle$1(parentNode).position === 'fixed' || hasFixedPositionAncestor(parentNode, stopNode); } // A "clipping ancestor" is an `overflow` element with the characteristic of @@ -1708,12 +1708,12 @@ function getClippingElementAncestors(element, cache) { } let result = getOverflowAncestors(element, [], false).filter(el => isElement(el) && getNodeName(el) !== 'body'); let currentContainingBlockComputedStyle = null; - const elementIsFixed = getComputedStyle(element).position === 'fixed'; + const elementIsFixed = getComputedStyle$1(element).position === 'fixed'; let currentNode = elementIsFixed ? getParentNode(element) : element; // https://developer.mozilla.org/en-US/docs/Web/CSS/Containing_block#identifying_the_containing_block while (isElement(currentNode) && !isLastTraversableNode(currentNode)) { - const computedStyle = getComputedStyle(currentNode); + const computedStyle = getComputedStyle$1(currentNode); const currentNodeIsContaining = isContainingBlock(currentNode); if (!currentNodeIsContaining && computedStyle.position === 'fixed') { currentContainingBlockComputedStyle = null; @@ -1807,11 +1807,11 @@ function getRectRelativeToOffsetParent(element, offsetParent, strategy) { } function isStaticPositioned(element) { - return getComputedStyle(element).position === 'static'; + return getComputedStyle$1(element).position === 'static'; } function getTrueOffsetParent(element, polyfill) { - if (!isHTMLElement(element) || getComputedStyle(element).position === 'fixed') { + if (!isHTMLElement(element) || getComputedStyle$1(element).position === 'fixed') { return null; } if (polyfill) { @@ -1872,7 +1872,7 @@ const getElementRects = async function (data) { }; function isRTL(element) { - return getComputedStyle(element).direction === 'rtl'; + return getComputedStyle$1(element).direction === 'rtl'; } const platform = { @@ -2103,32 +2103,115 @@ class AuroFloatingUI { this.keyDownHandler = null; } - position() { - // Define the middlware for the floater configuration - const middleware = [ - offset(this.element.floaterConfig.offset || 0), - ]; - - // Add flip middleware if flip is enabled - if (this.element.floaterConfig.flip) { - middleware.push(flip()); + /** + * @private + * Adjusts the size of the bib content based on the bibSizer dimensions. + * + * This method retrieves the computed styles of the bibSizer element and applies them to the bib content. + * If the fullscreen parameter is true, it resets the dimensions to their default values. Otherwise, it + * mirrors the width and height from the bibSizer, ensuring that they are not set to zero. + * + * @param {boolean} fullscreen - A flag indicating whether to reset the dimensions for fullscreen mode. + * If true, the bib content dimensions are cleared; if false, they are set + * based on the bibSizer's computed styles. + */ + mirrorSize(fullscreen) { + // mirror the boxsize from bibSizer + const sizerStyle = window.getComputedStyle(this.element.bibSizer); + const bibContent = this.element.bib.shadowRoot.querySelector(".container"); + if (fullscreen) { + bibContent.style.width = ''; + bibContent.style.height = ''; + bibContent.style.maxWidth = ''; + bibContent.style.maxHeight = ''; + } else { + if (sizerStyle.width !== '0px') { + bibContent.style.width = sizerStyle.width; + } + if (sizerStyle.height !== '0px') { + bibContent.style.height = sizerStyle.height; + } + bibContent.style.maxWidth = sizerStyle.maxWidth; + bibContent.style.maxHeight = sizerStyle.maxHeight; } + } - // Add autoPlacement middleware if autoPlacement is enabled - if (this.element.floaterConfig.autoPlacement) { - middleware.push(autoPlacement()); + /** + * @private + * Determines the positioning strategy based on the current viewport size and mobile breakpoint. + * + * This method checks if the current viewport width is less than or equal to the specified mobile fullscreen breakpoint + * defined in the bib element. If it is, the strategy is set to 'fullscreen'; otherwise, it defaults to 'floating'. + * + * @returns {String} The positioning strategy, either 'fullscreen' or 'floating'. + */ + getPositioningStrategy() { + let strategy = 'floating'; + if (this.element.bib.mobileFullscreenBreakpoint) { + const isMobile = window.matchMedia(`(max-width: ${this.element.bib.mobileFullscreenBreakpoint})`).matches; + if (isMobile) { + strategy = 'fullscreen'; + } } + return strategy; + } - // Compute the position of the bib - computePosition(this.element.trigger, this.element.bib, { - placement: this.element.floaterConfig.placement || 'bottom', - middleware: middleware || [] - }).then(({x, y}) => { // eslint-disable-line id-length - Object.assign(this.element.bib.style, { - left: `${x}px`, - top: `${y}px`, + /** + * @private + * Positions the bib element based on the current configuration and positioning strategy. + * + * This method determines the appropriate positioning strategy (fullscreen or not) and configures the bib accordingly. + * It also sets up middleware for the floater configuration, computes the position of the bib relative to the trigger element, + * and applies the calculated position to the bib's style. + */ + position() { + const strategy = this.getPositioningStrategy(); + if (strategy === 'fullscreen') { + this.configureBibFullscreen(true); + this.mirrorSize(true); + } else { + this.configureBibFullscreen(false); + this.mirrorSize(false); + + // Define the middlware for the floater configuration + const middleware = [ + offset(this.element.floaterConfig.offset || 0), + ...(this.element.floaterConfig.flip ? [flip()] : []), // Add flip middleware if flip is enabled + ...(this.element.floaterConfig.autoPlacement ? [autoPlacement()] : []), // Add autoPlacement middleware if autoPlacement is enabled + ]; + + // Compute the position of the bib + computePosition(this.element.trigger, this.element.bib, { + placement: this.element.floaterConfig.placement || 'bottom', + middleware: middleware || [] + }).then(({x, y}) => { // eslint-disable-line id-length + Object.assign(this.element.bib.style, { + left: `${x}px`, + top: `${y}px`, + }); }); - }); + } + } + + /** + * @private + * Configures the bib element for fullscreen mode based on the mobile status. + * + * This method sets the 'isFullscreen' attribute on the bib element to "true" if the `isMobile` parameter is true, + * and resets its position to the top-left corner of the viewport. If `isMobile` is false, it removes the + * 'isFullscreen' attribute, indicating that the bib is not in fullscreen mode. + * + * @param {boolean} isMobile - A flag indicating whether the current device is mobile. + */ + configureBibFullscreen(isMobile) { + if (isMobile) { + this.element.bib.setAttribute('isFullscreen', "true"); + // reset the prev position + this.element.bib.style.top = "0px"; + this.element.bib.style.left = "0px"; + } else { + this.element.bib.removeAttribute('isFullscreen'); + } } updateState() { @@ -2358,8 +2441,11 @@ class AuroFloatingUI { this.element = elem; this.element.trigger = this.element.shadowRoot.querySelector('#trigger'); this.element.bib = this.element.shadowRoot.querySelector('#bib'); + this.element.bibSizer = this.element.shadowRoot.querySelector('#bibSizer'); this.element.triggerChevron = this.element.shadowRoot.querySelector('#showStateIcon'); + document.body.append(this.element.bib); + this.handleTriggerTabIndex(); this.element.trigger.addEventListener('keydown', (event) => this.handleEvent(event)); @@ -2791,13 +2877,13 @@ if (!customElements.get("auro-icon")) { var iconVersion$1 = '6.1.1'; -var styleCss$1$1 = i$3`:host{position:relative}.label{font-size:var(--ds-text-body-size-xs, 0.75rem);line-height:var(--ds-text-body-size-default, 1rem);white-space:normal}.trigger{position:relative;display:flex;align-items:center;border-width:1px;border-style:solid}@media(hover: hover){.trigger:hover{cursor:pointer}}.triggerContentWrapper{overflow:hidden;flex:1;text-overflow:ellipsis;white-space:nowrap}#showStateIcon{display:flex;height:100%;align-items:center;margin-left:var(--ds-size-100, 0.5rem)}#showStateIcon [auro-icon]{height:var(--ds-size-300, 1.5rem);line-height:var(--ds-size-300, 1.5rem)}#showStateIcon[data-expanded=true] [auro-icon]{transform:rotate(-180deg)}.helpText{margin-top:var(--ds-size-50, 0.25rem);font-size:var(--ds-text-body-size-xs, 0.75rem);line-height:var(--ds-text-body-size-default, 1rem)}:host([disabled]){pointer-events:none}:host([inset]) .trigger{padding:var(--ds-size-150, 0.75rem) var(--ds-size-200, 1rem)}:host([common]) .trigger,:host([inset][bordered]) .trigger{padding:var(--ds-size-200, 1rem) var(--ds-size-150, 0.75rem)}:host([common]) .trigger,:host([rounded]) .trigger{border-radius:var(--ds-border-radius, 0.375rem)}`; +var styleCss$1$1 = i$3`:host{position:relative}#bibSizer{position:absolute;z-index:-1;opacity:0;pointer-events:none}.label{font-size:var(--ds-text-body-size-xs, 0.75rem);line-height:var(--ds-text-body-size-default, 1rem);white-space:normal}.trigger{position:relative;display:flex;align-items:center;border-width:1px;border-style:solid}@media(hover: hover){.trigger:hover{cursor:pointer}}.triggerContentWrapper{overflow:hidden;flex:1;text-overflow:ellipsis;white-space:nowrap}#showStateIcon{display:flex;height:100%;align-items:center;margin-left:var(--ds-size-100, 0.5rem)}#showStateIcon [auro-icon]{height:var(--ds-size-300, 1.5rem);line-height:var(--ds-size-300, 1.5rem)}#showStateIcon[data-expanded=true] [auro-icon]{transform:rotate(-180deg)}.helpText{margin-top:var(--ds-size-50, 0.25rem);font-size:var(--ds-text-body-size-xs, 0.75rem);line-height:var(--ds-text-body-size-default, 1rem)}:host([disabled]){pointer-events:none}:host([inset]) .trigger{padding:var(--ds-size-150, 0.75rem) var(--ds-size-200, 1rem)}:host([common]) .trigger,:host([inset][bordered]) .trigger{padding:var(--ds-size-200, 1rem) var(--ds-size-150, 0.75rem)}:host([common]) .trigger,:host([rounded]) .trigger{border-radius:var(--ds-border-radius, 0.375rem)}`; var colorCss$1$1 = i$3`.label{color:var(--ds-auro-dropdown-label-text-color)}.trigger{border-color:var(--ds-auro-dropdown-trigger-border-color);background-color:var(--ds-auro-dropdown-trigger-container-color);color:var(--ds-auro-dropdown-trigger-text-color)}.trigger:focus-within,.trigger:active{--ds-auro-dropdown-trigger-border-color: var(--ds-color-border-active-default, #0074c8)}.trigger:focus-within:not(:active){--ds-auro-dropdown-trigger-border-color: var(--ds-color-border-ui-focus-default, #2c67b5);box-shadow:inset 0 0 0 1px var(--ds-color-border-ui-focus-default, #2c67b5)}.trigger:hover{--ds-auro-dropdown-trigger-container-color: var(--ds-color-container-ui-secondary-hover-default, rgba(0, 0, 0, 0.03))}.helpText{color:var(--ds-auro-dropdown-help-text-color)}:host([disabled]){--ds-auro-dropdown-trigger-text-color: var(--ds-color-text-ui-disabled-default, #adadad);--ds-auro-dropdown-label-text-color: var(--ds-color-text-ui-disabled-default, #adadad)}:host([common]),:host([bordered]){--ds-auro-dropdown-trigger-border-color: var(--ds-color-border-primary-default, #585e67)}:host([common]) .trigger:active,:host([common]) .trigger:focus-within,:host([bordered]) .trigger:active,:host([bordered]) .trigger:focus-within{--ds-auro-dropdown-trigger-border-color: var(--ds-color-border-ui-focus-default, #2c67b5)}:host([error]){--ds-auro-dropdown-help-text-color: var(--ds-color-text-error-default, #cc1816);--ds-auro-dropdown-trigger-border-color: var(--ds-color-border-error-default, #cc1816)}:host([error]) .trigger{box-shadow:inset 0 0 0 1px var(--ds-auro-dropdown-trigger-border-color)}:host([error]) .trigger:focus-within{--ds-auro-dropdown-trigger-border-color: var(--ds-color-border-ui-focus-default, #2c67b5);box-shadow:none}:host([error]) .trigger:active{--ds-auro-dropdown-trigger-border-color: var(--ds-color-border-ui-focus-default, #2c67b5);box-shadow:inset 0 0 0 1px var(--ds-auro-dropdown-trigger-border-color)}:host([disabled][common]),:host([disabled][bordered]){--ds-auro-dropdown-trigger-border-color: var(--ds-color-border-ui-disabled-default, #adadad)}`; var tokensCss$3 = i$3`:host{--ds-auro-dropdown-help-text-color: var(--ds-color-text-secondary-default, #525252);--ds-auro-dropdown-label-text-color: var(--ds-color-text-secondary-default, #525252);--ds-auro-dropdown-popover-container-color: var(--ds-color-container-primary-default, #ffffff);--ds-auro-dropdown-popover-border-color: transparent;--ds-auro-dropdown-popover-text-color: var(--ds-color-text-primary-default, #2a2a2a);--ds-auro-dropdown-trigger-container-color: var(--ds-color-container-primary-default, #ffffff);--ds-auro-dropdown-trigger-border-color: transparent;--ds-auro-dropdown-trigger-text-color: var(--ds-color-text-primary-default, #2a2a2a);--ds-auro-dropdownbib-boxshadow-color: var(--ds-elevation-200, 0px 0px 10px rgba(0, 0, 0, 0.15));--ds-auro-dropdownbib-container-color: var(--ds-color-container-primary-default, #ffffff);--ds-auro-dropdownbib-text-color: var(--ds-color-text-primary-default, #2a2a2a)}`; -var styleCss$4 = i$3`.container{display:inline-block;box-sizing:border-box}:host{position:absolute;z-index:1;display:none}:host([data-show]){display:block}:host([common]) .container,:host([rounded]) .container{border-radius:var(--ds-border-radius, 0.375rem)}:host([common]) .container,:host([inset]) .container{padding:var(--ds-size-50, 0.25rem) var(--ds-size-100, 0.5rem)}`; +var styleCss$4 = i$3`:host{position:absolute;z-index:1;display:none}.container{display:inline-block;overflow:auto;box-sizing:border-box}:host([isfullscreen]){position:fixed;top:0;left:0}:host([isfullscreen]) .container{width:100dvw;max-width:none;height:100dvh;max-height:none;border-radius:unset;box-shadow:unset}:host([data-show]){display:block}:host([common]:not([isfullscreen])) .container,:host([rounded]:not([isfullscreen])) .container{border-radius:var(--ds-border-radius, 0.375rem)}:host([common]) .container,:host([inset]) .container{padding:var(--ds-size-50, 0.25rem) var(--ds-size-100, 0.5rem)}:host([common][isfullscreen]) .container,:host([rounded][isfullscreen]) .container{border-radius:unset;box-shadow:unset}`; var colorCss$4 = i$3`.container{background-color:var(--ds-auro-dropdownbib-container-color);box-shadow:var(--ds-auro-dropdownbib-boxshadow-color);color:var(--ds-auro-dropdownbib-text-color)}`; @@ -2805,15 +2891,34 @@ var colorCss$4 = i$3`.container{background-color:var(--ds-auro-dropdownbib-conta // See LICENSE in the project root for license information. + +const DESIGN_TOKEN_BREAKPOINT_PREFIX = '--ds-grid-breakpoint-'; +const DESIGN_TOKEN_BREAKPOINT_OPTIONS = [ + 'lg', + 'md', + 'sm', + 'xs', +]; + /** * @attr { Boolean } common - If declared, will apply all styles for the common theme. * @attr { Boolean } rounded - If declared, will apply border-radius to the bib. * @attr { Boolean } inset - If declared, will apply extra padding to bib content. + * @prop { String } mobileFullscreenBreakpoint - Defines the screen size breakpoint (`lg`, `md`, `sm`, or `xs`) at which the dropdown switches to fullscreen mode on mobile. When expanded, the dropdown will automatically display in fullscreen mode if the screen size is equal to or smaller than the selected breakpoint. * @csspart bibContainer - Apply css to the bib container. */ class AuroDropdownBib extends r { + constructor() { + super(); + + /** + * @private + */ + this._mobileBreakpointValue = undefined; + } + static get styles() { return [ styleCss$4, @@ -2835,10 +2940,26 @@ class AuroDropdownBib extends r { rounded: { type: Boolean, reflect: true - } + }, }; } + set mobileFullscreenBreakpoint(value) { + // verify the defined breakpoint is valid and exit out if not + const validatedValue = DESIGN_TOKEN_BREAKPOINT_OPTIONS.includes(value) ? value : undefined; + if (!validatedValue) { + this._mobileBreakpointValue = undefined; + } else { + // get the pixel value for the defined breakpoint + const docStyle = getComputedStyle(document.documentElement); + this._mobileBreakpointValue = docStyle.getPropertyValue(DESIGN_TOKEN_BREAKPOINT_PREFIX + value); + } + } + + get mobileFullscreenBreakpoint() { + return this._mobileBreakpointValue; + } + // function that renders the HTML and CSS into the scope of the component render() { return u$1` @@ -2872,6 +2993,7 @@ if (!customElements.get("auro-dropdownbib")) { * @attr { Boolean } noToggle - If declared, the trigger will only show the the dropdown bib. * @attr { Boolean } focusShow - if declared, the the bib will display when focus is applied to the trigger. * @attr { Boolean } noHideOnThisFocusLoss - If declared, the dropdown will not hide when moving focus outside the element. + * @attr { String } mobileFullscreenBreakpoint - Defines the screen size breakpoint (`lg`, `md`, `sm`, or `xs`) at which the dropdown switches to fullscreen mode on mobile. When expanded, the dropdown will automatically display in fullscreen mode if the screen size is equal to or smaller than the selected breakpoint. * @prop { Boolean } isPopoverVisible - If true, the dropdown bib is displayed. * @slot - Default slot for the popover content. * @slot label - Defines the content of the label. @@ -3012,6 +3134,10 @@ class AuroDropdown extends r { type: Function, reflect: false }, + mobileFullscreenBreakpoint: { + type: String, + reflect: true, + }, /** * @private @@ -3060,18 +3186,19 @@ class AuroDropdown extends r { updated(changedProperties) { this.floater.handleUpdate(changedProperties); + + if (changedProperties.has('mobileFullscreenBreakpoint')) { + this.bibContent.mobileFullscreenBreakpoint = this.mobileFullscreenBreakpoint; + } } firstUpdated() { this.floater.configure(this); + this.bibContent = this.floater.element.bib; // Add the tag name as an attribute if it is different than the component name this.runtimeUtils.handleComponentTagRename(this, 'auro-dropdown'); - this.bibContent = this.shadowRoot.querySelector('auro-dropdownbib'); - - document.body.append(this.bibContent); - this.notifyReady(); } @@ -3194,6 +3321,7 @@ class AuroDropdown extends r {
+
(css.willChange || '').includes(value)) || ['paint', 'layout', 'strict', 'content'].some(value => (css.contain || '').includes(value)); @@ -1299,7 +1299,7 @@ function isWebKit() { function isLastTraversableNode(node) { return ['html', 'body', '#document'].includes(getNodeName(node)); } -function getComputedStyle(element) { +function getComputedStyle$1(element) { return getWindow(element).getComputedStyle(element); } function getNodeScroll(element) { @@ -1361,7 +1361,7 @@ function getFrameElement(win) { } function getCssDimensions(element) { - const css = getComputedStyle(element); + const css = getComputedStyle$1(element); // In testing environments, the `width` and `height` properties are empty // strings for SVG elements, returning NaN. Fallback to `0` in this case. let width = parseFloat(css.width) || 0; @@ -1466,7 +1466,7 @@ function getBoundingClientRect(element, includeScale, isFixedStrategy, offsetPar while (currentIFrame && offsetParent && offsetWin !== currentWin) { const iframeScale = getScale(currentIFrame); const iframeRect = currentIFrame.getBoundingClientRect(); - const css = getComputedStyle(currentIFrame); + const css = getComputedStyle$1(currentIFrame); const left = iframeRect.left + (currentIFrame.clientLeft + parseFloat(css.paddingLeft)) * iframeScale.x; const top = iframeRect.top + (currentIFrame.clientTop + parseFloat(css.paddingTop)) * iframeScale.y; x *= iframeScale.x; @@ -1566,7 +1566,7 @@ function getDocumentRect(element) { const height = max(html.scrollHeight, html.clientHeight, body.scrollHeight, body.clientHeight); let x = -scroll.scrollLeft + getWindowScrollBarX(element); const y = -scroll.scrollTop; - if (getComputedStyle(body).direction === 'rtl') { + if (getComputedStyle$1(body).direction === 'rtl') { x += max(html.clientWidth, body.clientWidth) - width; } return { @@ -1643,7 +1643,7 @@ function hasFixedPositionAncestor(element, stopNode) { if (parentNode === stopNode || !isElement(parentNode) || isLastTraversableNode(parentNode)) { return false; } - return getComputedStyle(parentNode).position === 'fixed' || hasFixedPositionAncestor(parentNode, stopNode); + return getComputedStyle$1(parentNode).position === 'fixed' || hasFixedPositionAncestor(parentNode, stopNode); } // A "clipping ancestor" is an `overflow` element with the characteristic of @@ -1656,12 +1656,12 @@ function getClippingElementAncestors(element, cache) { } let result = getOverflowAncestors(element, [], false).filter(el => isElement(el) && getNodeName(el) !== 'body'); let currentContainingBlockComputedStyle = null; - const elementIsFixed = getComputedStyle(element).position === 'fixed'; + const elementIsFixed = getComputedStyle$1(element).position === 'fixed'; let currentNode = elementIsFixed ? getParentNode(element) : element; // https://developer.mozilla.org/en-US/docs/Web/CSS/Containing_block#identifying_the_containing_block while (isElement(currentNode) && !isLastTraversableNode(currentNode)) { - const computedStyle = getComputedStyle(currentNode); + const computedStyle = getComputedStyle$1(currentNode); const currentNodeIsContaining = isContainingBlock(currentNode); if (!currentNodeIsContaining && computedStyle.position === 'fixed') { currentContainingBlockComputedStyle = null; @@ -1755,11 +1755,11 @@ function getRectRelativeToOffsetParent(element, offsetParent, strategy) { } function isStaticPositioned(element) { - return getComputedStyle(element).position === 'static'; + return getComputedStyle$1(element).position === 'static'; } function getTrueOffsetParent(element, polyfill) { - if (!isHTMLElement(element) || getComputedStyle(element).position === 'fixed') { + if (!isHTMLElement(element) || getComputedStyle$1(element).position === 'fixed') { return null; } if (polyfill) { @@ -1820,7 +1820,7 @@ const getElementRects = async function (data) { }; function isRTL(element) { - return getComputedStyle(element).direction === 'rtl'; + return getComputedStyle$1(element).direction === 'rtl'; } const platform = { @@ -2051,32 +2051,115 @@ class AuroFloatingUI { this.keyDownHandler = null; } - position() { - // Define the middlware for the floater configuration - const middleware = [ - offset(this.element.floaterConfig.offset || 0), - ]; - - // Add flip middleware if flip is enabled - if (this.element.floaterConfig.flip) { - middleware.push(flip()); + /** + * @private + * Adjusts the size of the bib content based on the bibSizer dimensions. + * + * This method retrieves the computed styles of the bibSizer element and applies them to the bib content. + * If the fullscreen parameter is true, it resets the dimensions to their default values. Otherwise, it + * mirrors the width and height from the bibSizer, ensuring that they are not set to zero. + * + * @param {boolean} fullscreen - A flag indicating whether to reset the dimensions for fullscreen mode. + * If true, the bib content dimensions are cleared; if false, they are set + * based on the bibSizer's computed styles. + */ + mirrorSize(fullscreen) { + // mirror the boxsize from bibSizer + const sizerStyle = window.getComputedStyle(this.element.bibSizer); + const bibContent = this.element.bib.shadowRoot.querySelector(".container"); + if (fullscreen) { + bibContent.style.width = ''; + bibContent.style.height = ''; + bibContent.style.maxWidth = ''; + bibContent.style.maxHeight = ''; + } else { + if (sizerStyle.width !== '0px') { + bibContent.style.width = sizerStyle.width; + } + if (sizerStyle.height !== '0px') { + bibContent.style.height = sizerStyle.height; + } + bibContent.style.maxWidth = sizerStyle.maxWidth; + bibContent.style.maxHeight = sizerStyle.maxHeight; } + } - // Add autoPlacement middleware if autoPlacement is enabled - if (this.element.floaterConfig.autoPlacement) { - middleware.push(autoPlacement()); + /** + * @private + * Determines the positioning strategy based on the current viewport size and mobile breakpoint. + * + * This method checks if the current viewport width is less than or equal to the specified mobile fullscreen breakpoint + * defined in the bib element. If it is, the strategy is set to 'fullscreen'; otherwise, it defaults to 'floating'. + * + * @returns {String} The positioning strategy, either 'fullscreen' or 'floating'. + */ + getPositioningStrategy() { + let strategy = 'floating'; + if (this.element.bib.mobileFullscreenBreakpoint) { + const isMobile = window.matchMedia(`(max-width: ${this.element.bib.mobileFullscreenBreakpoint})`).matches; + if (isMobile) { + strategy = 'fullscreen'; + } } + return strategy; + } - // Compute the position of the bib - computePosition(this.element.trigger, this.element.bib, { - placement: this.element.floaterConfig.placement || 'bottom', - middleware: middleware || [] - }).then(({x, y}) => { // eslint-disable-line id-length - Object.assign(this.element.bib.style, { - left: `${x}px`, - top: `${y}px`, + /** + * @private + * Positions the bib element based on the current configuration and positioning strategy. + * + * This method determines the appropriate positioning strategy (fullscreen or not) and configures the bib accordingly. + * It also sets up middleware for the floater configuration, computes the position of the bib relative to the trigger element, + * and applies the calculated position to the bib's style. + */ + position() { + const strategy = this.getPositioningStrategy(); + if (strategy === 'fullscreen') { + this.configureBibFullscreen(true); + this.mirrorSize(true); + } else { + this.configureBibFullscreen(false); + this.mirrorSize(false); + + // Define the middlware for the floater configuration + const middleware = [ + offset(this.element.floaterConfig.offset || 0), + ...(this.element.floaterConfig.flip ? [flip()] : []), // Add flip middleware if flip is enabled + ...(this.element.floaterConfig.autoPlacement ? [autoPlacement()] : []), // Add autoPlacement middleware if autoPlacement is enabled + ]; + + // Compute the position of the bib + computePosition(this.element.trigger, this.element.bib, { + placement: this.element.floaterConfig.placement || 'bottom', + middleware: middleware || [] + }).then(({x, y}) => { // eslint-disable-line id-length + Object.assign(this.element.bib.style, { + left: `${x}px`, + top: `${y}px`, + }); }); - }); + } + } + + /** + * @private + * Configures the bib element for fullscreen mode based on the mobile status. + * + * This method sets the 'isFullscreen' attribute on the bib element to "true" if the `isMobile` parameter is true, + * and resets its position to the top-left corner of the viewport. If `isMobile` is false, it removes the + * 'isFullscreen' attribute, indicating that the bib is not in fullscreen mode. + * + * @param {boolean} isMobile - A flag indicating whether the current device is mobile. + */ + configureBibFullscreen(isMobile) { + if (isMobile) { + this.element.bib.setAttribute('isFullscreen', "true"); + // reset the prev position + this.element.bib.style.top = "0px"; + this.element.bib.style.left = "0px"; + } else { + this.element.bib.removeAttribute('isFullscreen'); + } } updateState() { @@ -2306,8 +2389,11 @@ class AuroFloatingUI { this.element = elem; this.element.trigger = this.element.shadowRoot.querySelector('#trigger'); this.element.bib = this.element.shadowRoot.querySelector('#bib'); + this.element.bibSizer = this.element.shadowRoot.querySelector('#bibSizer'); this.element.triggerChevron = this.element.shadowRoot.querySelector('#showStateIcon'); + document.body.append(this.element.bib); + this.handleTriggerTabIndex(); this.element.trigger.addEventListener('keydown', (event) => this.handleEvent(event)); @@ -2739,13 +2825,13 @@ if (!customElements.get("auro-icon")) { var iconVersion$1 = '6.1.1'; -var styleCss$1$1 = i$3`:host{position:relative}.label{font-size:var(--ds-text-body-size-xs, 0.75rem);line-height:var(--ds-text-body-size-default, 1rem);white-space:normal}.trigger{position:relative;display:flex;align-items:center;border-width:1px;border-style:solid}@media(hover: hover){.trigger:hover{cursor:pointer}}.triggerContentWrapper{overflow:hidden;flex:1;text-overflow:ellipsis;white-space:nowrap}#showStateIcon{display:flex;height:100%;align-items:center;margin-left:var(--ds-size-100, 0.5rem)}#showStateIcon [auro-icon]{height:var(--ds-size-300, 1.5rem);line-height:var(--ds-size-300, 1.5rem)}#showStateIcon[data-expanded=true] [auro-icon]{transform:rotate(-180deg)}.helpText{margin-top:var(--ds-size-50, 0.25rem);font-size:var(--ds-text-body-size-xs, 0.75rem);line-height:var(--ds-text-body-size-default, 1rem)}:host([disabled]){pointer-events:none}:host([inset]) .trigger{padding:var(--ds-size-150, 0.75rem) var(--ds-size-200, 1rem)}:host([common]) .trigger,:host([inset][bordered]) .trigger{padding:var(--ds-size-200, 1rem) var(--ds-size-150, 0.75rem)}:host([common]) .trigger,:host([rounded]) .trigger{border-radius:var(--ds-border-radius, 0.375rem)}`; +var styleCss$1$1 = i$3`:host{position:relative}#bibSizer{position:absolute;z-index:-1;opacity:0;pointer-events:none}.label{font-size:var(--ds-text-body-size-xs, 0.75rem);line-height:var(--ds-text-body-size-default, 1rem);white-space:normal}.trigger{position:relative;display:flex;align-items:center;border-width:1px;border-style:solid}@media(hover: hover){.trigger:hover{cursor:pointer}}.triggerContentWrapper{overflow:hidden;flex:1;text-overflow:ellipsis;white-space:nowrap}#showStateIcon{display:flex;height:100%;align-items:center;margin-left:var(--ds-size-100, 0.5rem)}#showStateIcon [auro-icon]{height:var(--ds-size-300, 1.5rem);line-height:var(--ds-size-300, 1.5rem)}#showStateIcon[data-expanded=true] [auro-icon]{transform:rotate(-180deg)}.helpText{margin-top:var(--ds-size-50, 0.25rem);font-size:var(--ds-text-body-size-xs, 0.75rem);line-height:var(--ds-text-body-size-default, 1rem)}:host([disabled]){pointer-events:none}:host([inset]) .trigger{padding:var(--ds-size-150, 0.75rem) var(--ds-size-200, 1rem)}:host([common]) .trigger,:host([inset][bordered]) .trigger{padding:var(--ds-size-200, 1rem) var(--ds-size-150, 0.75rem)}:host([common]) .trigger,:host([rounded]) .trigger{border-radius:var(--ds-border-radius, 0.375rem)}`; var colorCss$1$1 = i$3`.label{color:var(--ds-auro-dropdown-label-text-color)}.trigger{border-color:var(--ds-auro-dropdown-trigger-border-color);background-color:var(--ds-auro-dropdown-trigger-container-color);color:var(--ds-auro-dropdown-trigger-text-color)}.trigger:focus-within,.trigger:active{--ds-auro-dropdown-trigger-border-color: var(--ds-color-border-active-default, #0074c8)}.trigger:focus-within:not(:active){--ds-auro-dropdown-trigger-border-color: var(--ds-color-border-ui-focus-default, #2c67b5);box-shadow:inset 0 0 0 1px var(--ds-color-border-ui-focus-default, #2c67b5)}.trigger:hover{--ds-auro-dropdown-trigger-container-color: var(--ds-color-container-ui-secondary-hover-default, rgba(0, 0, 0, 0.03))}.helpText{color:var(--ds-auro-dropdown-help-text-color)}:host([disabled]){--ds-auro-dropdown-trigger-text-color: var(--ds-color-text-ui-disabled-default, #adadad);--ds-auro-dropdown-label-text-color: var(--ds-color-text-ui-disabled-default, #adadad)}:host([common]),:host([bordered]){--ds-auro-dropdown-trigger-border-color: var(--ds-color-border-primary-default, #585e67)}:host([common]) .trigger:active,:host([common]) .trigger:focus-within,:host([bordered]) .trigger:active,:host([bordered]) .trigger:focus-within{--ds-auro-dropdown-trigger-border-color: var(--ds-color-border-ui-focus-default, #2c67b5)}:host([error]){--ds-auro-dropdown-help-text-color: var(--ds-color-text-error-default, #cc1816);--ds-auro-dropdown-trigger-border-color: var(--ds-color-border-error-default, #cc1816)}:host([error]) .trigger{box-shadow:inset 0 0 0 1px var(--ds-auro-dropdown-trigger-border-color)}:host([error]) .trigger:focus-within{--ds-auro-dropdown-trigger-border-color: var(--ds-color-border-ui-focus-default, #2c67b5);box-shadow:none}:host([error]) .trigger:active{--ds-auro-dropdown-trigger-border-color: var(--ds-color-border-ui-focus-default, #2c67b5);box-shadow:inset 0 0 0 1px var(--ds-auro-dropdown-trigger-border-color)}:host([disabled][common]),:host([disabled][bordered]){--ds-auro-dropdown-trigger-border-color: var(--ds-color-border-ui-disabled-default, #adadad)}`; var tokensCss$3 = i$3`:host{--ds-auro-dropdown-help-text-color: var(--ds-color-text-secondary-default, #525252);--ds-auro-dropdown-label-text-color: var(--ds-color-text-secondary-default, #525252);--ds-auro-dropdown-popover-container-color: var(--ds-color-container-primary-default, #ffffff);--ds-auro-dropdown-popover-border-color: transparent;--ds-auro-dropdown-popover-text-color: var(--ds-color-text-primary-default, #2a2a2a);--ds-auro-dropdown-trigger-container-color: var(--ds-color-container-primary-default, #ffffff);--ds-auro-dropdown-trigger-border-color: transparent;--ds-auro-dropdown-trigger-text-color: var(--ds-color-text-primary-default, #2a2a2a);--ds-auro-dropdownbib-boxshadow-color: var(--ds-elevation-200, 0px 0px 10px rgba(0, 0, 0, 0.15));--ds-auro-dropdownbib-container-color: var(--ds-color-container-primary-default, #ffffff);--ds-auro-dropdownbib-text-color: var(--ds-color-text-primary-default, #2a2a2a)}`; -var styleCss$4 = i$3`.container{display:inline-block;box-sizing:border-box}:host{position:absolute;z-index:1;display:none}:host([data-show]){display:block}:host([common]) .container,:host([rounded]) .container{border-radius:var(--ds-border-radius, 0.375rem)}:host([common]) .container,:host([inset]) .container{padding:var(--ds-size-50, 0.25rem) var(--ds-size-100, 0.5rem)}`; +var styleCss$4 = i$3`:host{position:absolute;z-index:1;display:none}.container{display:inline-block;overflow:auto;box-sizing:border-box}:host([isfullscreen]){position:fixed;top:0;left:0}:host([isfullscreen]) .container{width:100dvw;max-width:none;height:100dvh;max-height:none;border-radius:unset;box-shadow:unset}:host([data-show]){display:block}:host([common]:not([isfullscreen])) .container,:host([rounded]:not([isfullscreen])) .container{border-radius:var(--ds-border-radius, 0.375rem)}:host([common]) .container,:host([inset]) .container{padding:var(--ds-size-50, 0.25rem) var(--ds-size-100, 0.5rem)}:host([common][isfullscreen]) .container,:host([rounded][isfullscreen]) .container{border-radius:unset;box-shadow:unset}`; var colorCss$4 = i$3`.container{background-color:var(--ds-auro-dropdownbib-container-color);box-shadow:var(--ds-auro-dropdownbib-boxshadow-color);color:var(--ds-auro-dropdownbib-text-color)}`; @@ -2753,15 +2839,34 @@ var colorCss$4 = i$3`.container{background-color:var(--ds-auro-dropdownbib-conta // See LICENSE in the project root for license information. + +const DESIGN_TOKEN_BREAKPOINT_PREFIX = '--ds-grid-breakpoint-'; +const DESIGN_TOKEN_BREAKPOINT_OPTIONS = [ + 'lg', + 'md', + 'sm', + 'xs', +]; + /** * @attr { Boolean } common - If declared, will apply all styles for the common theme. * @attr { Boolean } rounded - If declared, will apply border-radius to the bib. * @attr { Boolean } inset - If declared, will apply extra padding to bib content. + * @prop { String } mobileFullscreenBreakpoint - Defines the screen size breakpoint (`lg`, `md`, `sm`, or `xs`) at which the dropdown switches to fullscreen mode on mobile. When expanded, the dropdown will automatically display in fullscreen mode if the screen size is equal to or smaller than the selected breakpoint. * @csspart bibContainer - Apply css to the bib container. */ class AuroDropdownBib extends r { + constructor() { + super(); + + /** + * @private + */ + this._mobileBreakpointValue = undefined; + } + static get styles() { return [ styleCss$4, @@ -2783,10 +2888,26 @@ class AuroDropdownBib extends r { rounded: { type: Boolean, reflect: true - } + }, }; } + set mobileFullscreenBreakpoint(value) { + // verify the defined breakpoint is valid and exit out if not + const validatedValue = DESIGN_TOKEN_BREAKPOINT_OPTIONS.includes(value) ? value : undefined; + if (!validatedValue) { + this._mobileBreakpointValue = undefined; + } else { + // get the pixel value for the defined breakpoint + const docStyle = getComputedStyle(document.documentElement); + this._mobileBreakpointValue = docStyle.getPropertyValue(DESIGN_TOKEN_BREAKPOINT_PREFIX + value); + } + } + + get mobileFullscreenBreakpoint() { + return this._mobileBreakpointValue; + } + // function that renders the HTML and CSS into the scope of the component render() { return u$1` @@ -2820,6 +2941,7 @@ if (!customElements.get("auro-dropdownbib")) { * @attr { Boolean } noToggle - If declared, the trigger will only show the the dropdown bib. * @attr { Boolean } focusShow - if declared, the the bib will display when focus is applied to the trigger. * @attr { Boolean } noHideOnThisFocusLoss - If declared, the dropdown will not hide when moving focus outside the element. + * @attr { String } mobileFullscreenBreakpoint - Defines the screen size breakpoint (`lg`, `md`, `sm`, or `xs`) at which the dropdown switches to fullscreen mode on mobile. When expanded, the dropdown will automatically display in fullscreen mode if the screen size is equal to or smaller than the selected breakpoint. * @prop { Boolean } isPopoverVisible - If true, the dropdown bib is displayed. * @slot - Default slot for the popover content. * @slot label - Defines the content of the label. @@ -2960,6 +3082,10 @@ class AuroDropdown extends r { type: Function, reflect: false }, + mobileFullscreenBreakpoint: { + type: String, + reflect: true, + }, /** * @private @@ -3008,18 +3134,19 @@ class AuroDropdown extends r { updated(changedProperties) { this.floater.handleUpdate(changedProperties); + + if (changedProperties.has('mobileFullscreenBreakpoint')) { + this.bibContent.mobileFullscreenBreakpoint = this.mobileFullscreenBreakpoint; + } } firstUpdated() { this.floater.configure(this); + this.bibContent = this.floater.element.bib; // Add the tag name as an attribute if it is different than the component name this.runtimeUtils.handleComponentTagRename(this, 'auro-dropdown'); - this.bibContent = this.shadowRoot.querySelector('auro-dropdownbib'); - - document.body.append(this.bibContent); - this.notifyReady(); } @@ -3142,6 +3269,7 @@ class AuroDropdown extends r {
+