diff --git a/lib/KModal.vue b/lib/KModal.vue
index dca8a956c..c917d8ee5 100644
--- a/lib/KModal.vue
+++ b/lib/KModal.vue
@@ -24,7 +24,7 @@
:style="[
modalSizeStyles,
{ background: $themeTokens.surface },
- containsKSelect ? { overflowY: 'unset' } : { overflowY: 'auto' },
+ { overflowY: 'auto' },
]"
>
@@ -65,7 +65,6 @@
]"
:class="{
'scroll-shadow': scrollShadow,
- 'contains-kselect': containsKSelect,
}"
>
@@ -216,7 +215,6 @@
return {
lastFocus: null,
maxContentHeight: '1000',
- containsKSelect: false,
scrollShadow: false,
delayedEnough: false,
};
@@ -278,9 +276,6 @@
window.addEventListener('focus', this.focusElementTest, true);
window.setTimeout(() => (this.delayedEnough = true), 500);
- // if modal contains KSelect, special classes & styles will be applied
- const kSelectCheck = document.querySelector('div.modal div.ui-select');
- this.containsKSelect = !!kSelectCheck;
this.updateContentSectionStyle();
},
updated() {
@@ -320,9 +315,7 @@
// make sure that overflow-y won't be updated to 'auto' if this function is running
// for the first time (otherwise Firefox would add a vertical scrollbar right away)
- // + don't apply if modal contains KSelect
- // (otherwise KSelect will be trapped inside modal if KSelect is opened a second time)
- if (this.$refs.content.clientHeight !== 0 && !this.containsKSelect) {
+ if (this.$refs.content.clientHeight !== 0) {
// add a vertical scrollbar if content doesn't fit
if (this.$refs.content.scrollHeight > this.$refs.content.clientHeight) {
this.$refs.content.style.overflowY = 'auto';
@@ -453,10 +446,6 @@
100% 10px;
}
- .contains-kselect {
- overflow: unset;
- }
-
.actions {
padding: 24px;
text-align: right;
diff --git a/lib/KSelect/index.vue b/lib/KSelect/index.vue
index 4437ebbce..1fb67ab9a 100644
--- a/lib/KSelect/index.vue
+++ b/lib/KSelect/index.vue
@@ -28,14 +28,15 @@
class="ui-select-label"
:class="$computedClass({ ':focus': $coreOutline })"
:tabindex="disabled ? null : '0'"
- @click="toggleDropdown"
@focus="onFocus"
- @keydown.enter.prevent="openDropdown"
- @keydown.space.prevent="openDropdown"
+ @blur="selectBlur"
+ @keydown.enter.prevent="onEnterSpace"
+ @keydown.space.prevent="onEnterSpace"
@keydown.tab="onBlur"
@keydown.up.prevent="highlightPreviousOption"
@keydown.down.prevent="highlightNextOption"
@keydown.self="highlightQuickMatch"
+ @keydown.esc.prevent="closeDropdown()"
>
-
-
+
+ the option in the options dropdown -->
-
+
{
@@ -225,6 +232,7 @@
components: {
UiIcon,
KSelectOption,
+ Popper,
},
model: {
event: 'change',
@@ -354,7 +362,17 @@
default: false,
},
},
+ setup() {
+ const { getOverlayEl } = _useOverlay();
+ const appendToEl = ref(null);
+
+ onMounted(() => {
+ const overlayEl = getOverlayEl();
+ appendToEl.value = overlayEl;
+ });
+ return { appendToEl };
+ },
data() {
return {
query: '',
@@ -362,11 +380,10 @@
isActive: false,
isTouched: false,
highlightedOption: null,
- showDropdown: false,
+ isDropdownOpen: false,
initialValue: JSON.stringify(this.value),
quickMatchString: '',
quickMatchTimeout: null,
- scrollableAncestor: null,
dropdownButtonBottom: 'auto',
maxDropdownHeight: 256,
// workaround for Keen-ui not displaying floating labels for empty objects
@@ -455,6 +472,26 @@
return this.filteredOptions.length === 0;
},
+ dropdownPopperOptions() {
+ return {
+ placement: 'bottom-start',
+ modifiers: {
+ applyStyle: {
+ enabled: true,
+ fn: throttle((data) => {
+ data.styles.width = `${data.offsets.reference.width}px`;
+ Object.assign(data.instance.popper.style, data.styles);
+ return data;
+ }, 50),
+ },
+ preventOverflow: {
+ enabled: true,
+ boundariesElement: 'viewport',
+ },
+ },
+ }
+ },
+
submittedValue() {
// Assuming that if there is no name, then there's no
// need to computed the submittedValue
@@ -544,23 +581,6 @@
this.highlightedOption = this.filteredOptions[0];
resetScroll(this.$refs.optionsList);
},
-
- showDropdown() {
- if (this.showDropdown) {
- this.onOpen();
- /**
- * Emit on opening dropdown
- */
- this.$emit('dropdown-open');
- } else {
- this.onClose();
- /**
- * Emit on closing dropdown
- */
- this.$emit('dropdown-close');
- }
- },
-
query() {
/**
* Emits whenever the query value changes.
@@ -598,25 +618,6 @@
},
mounted() {
- document.addEventListener('click', this.onExternalClick);
- // Find nearest scrollable ancestor
- this.scrollableAncestor = this.$el;
- while (
- (this.scrollableAncestor &&
- this.scrollableAncestor.clientHeight < this.scrollableAncestor.scrollHeight) ||
- !/auto|scroll/.test(window.getComputedStyle(this.scrollableAncestor).overflowY)
- ) {
- if (!this.scrollableAncestor.parentNode) {
- break;
- }
- this.scrollableAncestor = this.scrollableAncestor.parentNode;
-
- // Stop if we reach the body-- tagName is likely uppercase
- if (/body/i.test(this.scrollableAncestor.tagName)) {
- break;
- }
- }
-
// look for KSelects nested within modals
const allSelects = document.querySelectorAll('div.modal div.ui-select');
// create array from a nodelist [IE does not support Array.from()]
@@ -624,10 +625,6 @@
this.isInsideModal = allSelectsArr.includes(this.$el);
},
- beforeDestroy() {
- document.removeEventListener('click', this.onExternalClick);
- },
-
methods: {
setValue(value) {
value = value ? value : this.multiple ? [] : '';
@@ -696,7 +693,6 @@
}
this.highlightedOption = option;
- this.openDropdown();
if (options.autoScroll) {
const index = this.filteredOptions.findIndex(option =>
@@ -793,51 +789,25 @@
this.query = '';
},
- toggleDropdown() {
- // if called on dropdown inside modal,
- // dropdown will generally render above input/placeholder when opened, rather than below it.
- // We want to render dropdown above input only in cases where there isn't enough space
- // available beneath input, but when dropdown extends outside a modal
- // the func doesn't work as intended
- if (!this.isInsideModal) this.calculateSpaceBelow();
-
- this[this.showDropdown ? 'closeDropdown' : 'openDropdown']();
- },
-
- openDropdown() {
- if (this.disabled || this.clearableState) {
- return;
- }
-
- if (this.highlightedIndex === -1) {
- this.highlightNextOption();
- }
-
- this.showDropdown = true;
- // IE: clicking label doesn't focus the select element
- // to set isActive to true
- if (!this.isActive) {
- this.isActive = true;
+ onEnterSpace() {
+ if (this.isDropdownOpen) {
+ this.selectHighlighted();
+ } else {
+ this.$refs.label.click();
}
},
- closeDropdown(options = { autoBlur: false }) {
- this.showDropdown = false;
- this.query = '';
- if (!this.isTouched) {
- this.isTouched = true;
- this.$emit('touch');
- }
-
- if (options.autoBlur) {
- this.isActive = false;
- } else {
+ async closeDropdown(options = { autoBlur: false }) {
+ this.$refs.dropdownPopper.doClose();
+ await this.$nextTick();
+ if (!options.autoBlur) {
this.$refs.label.focus();
+ this.isActive = true;
}
},
onMouseover(option) {
- if (this.showDropdown) {
+ if (this.isDropdownOpen) {
this.highlightOption(option, { autoScroll: false });
}
},
@@ -854,6 +824,12 @@
this.$emit('focus', e);
},
+ selectBlur() {
+ if (!this.isDropdownOpen) {
+ this.isActive = false;
+ }
+ },
+
onBlur(e) {
this.isActive = false;
/**
@@ -861,12 +837,18 @@
*/
this.$emit('blur', e);
- if (this.showDropdown) {
+ if (this.isDropdownOpen) {
this.closeDropdown({ autoBlur: true });
}
},
onOpen() {
+ if (this.highlightedIndex === -1) {
+ this.highlightNextOption();
+ }
+
+ this.isActive = true;
+
this.highlightedOption = this.multiple ? null : this.selection;
this.$nextTick(() => {
this.$refs['dropdown'].focus();
@@ -879,20 +861,21 @@
);
}
});
+ this.isDropdownOpen = true;
+ this.$emit('dropdown-open');
},
onClose() {
- this.highlightedOption = this.multiple ? null : this.selection;
- },
-
- onExternalClick(e) {
- if (!this.$el.contains(e.target)) {
- if (this.showDropdown) {
- this.closeDropdown({ autoBlur: true });
- } else if (this.isActive) {
- this.isActive = false;
- }
+ this.isActive = false;
+ this.query = '';
+ if (!this.isTouched) {
+ this.isTouched = true;
+ this.$emit('touch');
}
+
+ this.highlightedOption = this.multiple ? null : this.selection;
+ this.isDropdownOpen = false;
+ this.$emit('dropdown-close');
},
scrollOptionIntoView(optionEl) {
@@ -916,23 +899,8 @@
resetTouched(options = { touched: false }) {
this.isTouched = options.touched;
},
- calculateSpaceBelow() {
- // Get the height of element
- const buttonHeight = this.$el.getBoundingClientRect().height;
-
- // Get the position of the element relative to the viewport
- const buttonPosition = this.$el.getBoundingClientRect().top;
-
- // Check if there is enough space below element
- // and update the "dropdownButtonBottom" data property accordingly
- const notEnoughSpaceBelow =
- buttonPosition > this.maxDropdownHeight &&
- this.scrollableAncestor.offsetHeight - buttonPosition <
- buttonHeight + this.maxDropdownHeight;
-
- this.dropdownButtonBottom = notEnoughSpaceBelow ? buttonHeight + 'px' : 'auto';
- },
},
+
};
@@ -1028,6 +996,7 @@
}
&.is-disabled {
+ pointer-events: none;
.ui-select-display {
color: $ui-input-text-color--disabled;
cursor: default;
@@ -1111,16 +1080,10 @@
.ui-select-dropdown {
@extend %dropshadow-2dp;
- position: absolute;
z-index: $z-index-dropdown;
- display: block;
- width: 100%;
- min-width: rem-calc(180px);
- padding: 0;
- margin: 0;
- margin-bottom: rem-calc(8px);
list-style-type: none;
outline: none;
+ margin-top: 2px;
}
.ui-select-options {
diff --git a/lib/KTooltip/Popper.vue b/lib/KTooltip/Popper.vue
index 094ef5bef..3eeb91552 100644
--- a/lib/KTooltip/Popper.vue
+++ b/lib/KTooltip/Popper.vue
@@ -201,10 +201,16 @@
}
},
+ /**
+ * @public
+ */
doShow() {
this.showPopper = true;
},
+ /**
+ * @public
+ */
doClose() {
this.showPopper = false;
},
diff --git a/lib/composables/_useOverlay/index.js b/lib/composables/_useOverlay/index.js
index a1f39e024..6439f1bfd 100644
--- a/lib/composables/_useOverlay/index.js
+++ b/lib/composables/_useOverlay/index.js
@@ -41,6 +41,10 @@ export default function _useOverlay() {
* @returns {HTMLElement} The overlay container element #k-overlay
*/
function getOverlayEl() {
+ // skip for SSR
+ if (typeof window === 'undefined') {
+ return;
+ }
// do not query DOM for performance reasons
const overlayEl = window.overlayEl;
// unlikely to happen, but just in case