diff --git a/CHANGELOG.md b/CHANGELOG.md index bb4a3598f..2b1fa0086 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -209,6 +209,28 @@ Changelog is rather internal in nature. See release notes for the public overvie ## Version 3.x.x (`release-v3` branch) +- [#586] + - **Description:** Adds a new prop `constrainToScrollParent ` to `KDropdownMenu` to allow overriding of its popover flipping behavior. + - **Products impact:** Bugfix + - **Addresses:** [#432](https://github.com/learningequality/kolibri-design-system/issues/432) + - **Components:** KDropdownMenu + - **Breaking:** no + - **Impacts a11y:** no + - **Guidance:** Use the `constrainToScrollParent` prop to override the default popover flipping behavior of the `KDropdownMenu` component prop where necessary. + +[#586]: https://github.com/learningequality/kolibri-design-system/pull/586 + +- [#573] + - **Description:** More precise calculation of list with in KListWithOverflow. + - **Products impact:** bugfix. + - **Addresses:** -. + - **Components:** KListWithOverflow. + - **Breaking:** no. + - **Impacts a11y:** no. + - **Guidance:** -. + +[#573]: https://github.com/learningequality/kolibri-design-system/pull/573 + - [#552] - **Description:** New `KListWithOverflow` component. - **Products impact:** new API. @@ -1382,4 +1404,4 @@ This was the first release of the Design System, with documentation written in a ## Version 0.1.0 -The design system was originally based on a set of internal Kolibri components and their use as documented in the Kolibri Style Guide, which was first introduced into the Kolibri code base [in version 0.6](https://github.com/learningequality/kolibri/tree/release-v0.6.x/kolibri/plugins/style_guide). This remained until [version 0.13](https://github.com/learningequality/kolibri/tree/release-v0.13.x/kolibri/plugins/style_guide) after which the content was migrated to the [current site](https://design-system.learningequality.org/ 'Kolibri Design System Documentation'). +The design system was originally based on a set of internal Kolibri components and their use as documented in the Kolibri Style Guide, which was first introduced into the Kolibri code base [in version 0.6](https://github.com/learningequality/kolibri/tree/release-v0.6.x/kolibri/plugins/style_guide). This remained until [version 0.13](https://github.com/learningequality/kolibri/tree/release-v0.13.x/kolibri/plugins/style_guide) after which the content was migrated to the [current site](https://design-system.learningequality.org/ 'Kolibri Design System Documentation'). \ No newline at end of file diff --git a/lib/KDropdownMenu.vue b/lib/KDropdownMenu.vue index 355288e53..824d992c1 100644 --- a/lib/KDropdownMenu.vue +++ b/lib/KDropdownMenu.vue @@ -5,6 +5,7 @@ :z-index="99" :containFocus="true" :dropdownPosition="position" + :constrainToScrollParent="constrainToScrollParent" @close="handleClose" @open="handleOpen" > @@ -34,6 +35,13 @@ UiMenu, }, props: { + /** + * The dropdown menu popover flips its position to avoid overflows within the parent. Setting it to false disables the flipping behavior. + */ + constrainToScrollParent: { + type: Boolean, + default: true, + }, /** * An array of options objects, with one object per dropdown item */ diff --git a/lib/KListWithOverflow.vue b/lib/KListWithOverflow.vue index ee54d69df..db8d852da 100644 --- a/lib/KListWithOverflow.vue +++ b/lib/KListWithOverflow.vue @@ -103,26 +103,46 @@ this.$watch('elementWidth', this.throttledSetOverflowItems); }, methods: { + getSize(element) { + if (!element) { + return { width: 0, height: 0 }; + } + const { width, height } = element.getBoundingClientRect(); + return { width, height }; + }, /** * Sets the items that overflow the list, the visibility of the more button, * and overrides the `visibility` of the list DOM elements that overflow the list. */ setOverflowItems() { - const { list, listWrapper } = this.$refs; + const { list, listWrapper, moreButtonWrapper } = this.$refs; if (!this.mounted || !listWrapper || !list) { this.overflowItems = []; return; } - let availableWidth = listWrapper.clientWidth; + const newMoreButtonWidth = this.getSize(moreButtonWrapper).width; + if (this.isMoreButtonVisible && newMoreButtonWidth > 0) { + this.moreButtonWidth = newMoreButtonWidth; + } + + let availableWidth = this.getSize(listWrapper).width; availableWidth -= this.moreButtonWidth; let maxWidth = 0; let maxHeight = 0; + const itemsSizes = []; + + for (let i = 0; i < list.children.length; i++) { + const item = list.children[i]; + const itemSize = this.getSize(item); + itemsSizes.push(itemSize); + } + const overflowItemsIdx = []; for (let i = 0; i < list.children.length; i++) { const item = list.children[i]; - const itemWidth = item.clientWidth; + const itemWidth = itemsSizes[i].width; // If the item dont fit in the available space or if we have already // overflowed items, we hide it. This means that once one item overflows, @@ -134,15 +154,16 @@ item.style.visibility = 'visible'; maxWidth += itemWidth; availableWidth -= itemWidth; - if (item.clientHeight > maxHeight) { - maxHeight = item.clientHeight; + const itemHeight = itemsSizes[i].height; + if (itemHeight > maxHeight) { + maxHeight = itemHeight; } } } // check if overflowed items would fit if the moreButton were not visible const overflowedWidth = overflowItemsIdx.reduce( - (acc, idx) => acc + list.children[idx].clientWidth, + (acc, idx) => acc + itemsSizes[idx].width, 0 ); if (overflowedWidth <= this.moreButtonWidth + availableWidth) { @@ -150,15 +171,16 @@ const idx = overflowItemsIdx.pop(); const item = list.children[idx]; item.style.visibility = 'visible'; - maxWidth += item.clientWidth; + maxWidth += itemsSizes[idx].width; } } - const removedDividerWidth = this.fixDividersVisibility(overflowItemsIdx); + const removedDividerWidth = this.fixDividersVisibility(overflowItemsIdx, itemsSizes); if (removedDividerWidth) { maxWidth -= removedDividerWidth; } + maxWidth = Math.ceil(maxWidth); this.overflowItems = overflowItemsIdx.map(idx => this.items[idx]); this.isMoreButtonVisible = overflowItemsIdx.length > 0; list.style.maxWidth = `${maxWidth}px`; @@ -169,9 +191,10 @@ * The visible list should not end with a divider, and the overflowed items should not * start with a divider. * @param {Array} overflowItemsIdx - The indexes of the items that overflow the list + * @param {Array} itemsSizes - The sizes of the items in the list * @returns {Number} The width of the removed divider from the visible list, if any */ - fixDividersVisibility(overflowItemsIdx) { + fixDividersVisibility(overflowItemsIdx, itemsSizes) { if (overflowItemsIdx.length === 0) { return; } @@ -186,7 +209,7 @@ if (this.isDivider(this.items[lastVisibleIdx])) { const dividerNode = list.children[lastVisibleIdx]; dividerNode.style.visibility = 'hidden'; - return dividerNode.clientWidth; + return itemsSizes[lastVisibleIdx].width; } }, /** @@ -201,7 +224,7 @@ if (!moreButtonWrapper) { return; } - this.moreButtonWidth = moreButtonWrapper.clientWidth; + this.moreButtonWidth = this.getSize(moreButtonWrapper).width; this.isMoreButtonVisible = false; moreButtonWrapper.style.visibility = 'visible';