- import Teleport from 'vue2-teleport';
import debounce from 'lodash/debounce';
import useKResponsiveWindow from './composables/useKResponsiveWindow';
@@ -120,9 +119,6 @@
*/
export default {
name: 'KModal',
- components: {
- Teleport,
- },
setup() {
const { windowHeight, windowWidth } = useKResponsiveWindow();
return { windowHeight, windowWidth };
@@ -197,9 +193,10 @@
required: false,
},
/**
- * Whether or not the modal should be teleported to the root of the document
+ * Whether or not the modal should be moved
+ * to the overlay container element `#k-overlay`
*/
- appendToRoot: {
+ appendToOverlay: {
type: Boolean,
default: false,
},
@@ -236,10 +233,7 @@
};
},
wrapper() {
- return this.appendToRoot ? 'Teleport' : 'div';
- },
- wrapperProps() {
- return this.appendToRoot ? { to: 'body' } : {};
+ return this.appendToOverlay ? 'KOverlay' : 'div';
},
},
created() {
diff --git a/lib/KOverlay/index.vue b/lib/KOverlay/index.vue
new file mode 100644
index 000000000..1d191de95
--- /dev/null
+++ b/lib/KOverlay/index.vue
@@ -0,0 +1,36 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/lib/KThemePlugin.js b/lib/KThemePlugin.js
index 8f6ba05a2..1a8355ac0 100644
--- a/lib/KThemePlugin.js
+++ b/lib/KThemePlugin.js
@@ -31,6 +31,7 @@ import KSwitch from './KSwitch';
import KTabs from './tabs/KTabs';
import KTabsList from './tabs/KTabsList';
import KTabsPanel from './tabs/KTabsPanel';
+import KOverlay from './KOverlay';
import KTextbox from './KTextbox';
import KTooltip from './KTooltip';
import KTransition from './KTransition';
@@ -43,8 +44,10 @@ import { themeTokens, themeBrand, themePalette, themeOutlineStyle } from './styl
import globalThemeState from './styles/globalThemeState';
import useKLiveRegion from './composables/useKLiveRegion';
+import _useOverlay from './composables/_useOverlay';
const { _mountLiveRegion } = useKLiveRegion();
+const { mountOverlay } = _useOverlay();
require('./grids/globalStyles.js'); // global grid styles
@@ -53,13 +56,10 @@ require('./grids/globalStyles.js'); // global grid styles
* Also, set up global state, listeners, and styles.
*/
export default function KThemePlugin(Vue) {
- // Note that if DOM live regions need to be demostrated
- // on the KDS website, and therefore attached to the DOM,
- // just call _mountLiveRegion() in the relevant documentation
- // page's 'mounted' (see 'docs/pages/usekliveregio.vue' for an example)
if (!isNuxtServerSideRendering()) {
const onDomReady = () => {
_mountLiveRegion();
+ mountOverlay();
document.removeEventListener('DOMContentLoaded', onDomReady);
};
@@ -152,6 +152,7 @@ export default function KThemePlugin(Vue) {
Vue.component('KTabs', KTabs);
Vue.component('KTabsList', KTabsList);
Vue.component('KTabsPanel', KTabsPanel);
+ Vue.component('KOverlay', KOverlay);
Vue.component('KTextbox', KTextbox);
Vue.component('KTooltip', KTooltip);
Vue.component('KTransition', KTransition);
diff --git a/lib/KTooltip/Popper.vue b/lib/KTooltip/Popper.vue
index dc8997ade..71de05d44 100644
--- a/lib/KTooltip/Popper.vue
+++ b/lib/KTooltip/Popper.vue
@@ -4,6 +4,11 @@
Vendored from https://github.com/RobinCK/vue-popper/
pending
https://github.com/RobinCK/vue-popper/pull/73
+
+ LE customizations
+ - Allow for appending to a chosen element rather than to body,
+ typically to the overlay container element #k-overlay.
+ 'appendToBody' prop changedto 'appendToEl' and related changes.
-->
@@ -89,9 +94,14 @@
default: false,
},
dataValue: { default: null }, // eslint-disable-line
- appendToBody: {
- type: Boolean,
- default: false,
+ /* An HTML element the tooltip should be appended to */
+ // 'type: HTMLElement' breaks the documentation build (??)
+ // 'type: Object' causes 'Invalid prop: type check failed for prop
+ // "appendToEl". Expected Object, got HTMLDivElement' warning
+ // in consumers
+ appendToEl: {
+ type: null,
+ default: null,
},
visibleArrow: {
type: Boolean,
@@ -155,7 +165,7 @@
created() {
this.appendedArrow = false;
- this.appendedToBody = false;
+ this.isAppendedToEl = false;
this.popperOptions = Object.assign(this.popperOptions, this.options);
},
@@ -210,9 +220,9 @@
this.popperJS = null;
}
- if (this.appendedToBody) {
- this.appendedToBody = false;
- document.body.removeChild(this.popper.parentElement);
+ if (this.isAppendedToEl) {
+ this.isAppendedToEl = false;
+ this.appendToEl.removeChild(this.popper.parentElement);
}
},
@@ -222,9 +232,9 @@
this.appendArrow(this.popper);
}
- if (this.appendToBody && !this.appendedToBody) {
- this.appendedToBody = true;
- document.body.appendChild(this.popper.parentElement);
+ if (this.appendToEl && !this.isAppendedToEl) {
+ this.isAppendedToEl = true;
+ this.appendToEl.appendChild(this.popper.parentElement);
}
if (this.popperJS && this.popperJS.destroy) {
diff --git a/lib/KTooltip/index.vue b/lib/KTooltip/index.vue
index 6de8baab0..fe053cd20 100644
--- a/lib/KTooltip/index.vue
+++ b/lib/KTooltip/index.vue
@@ -6,6 +6,7 @@
:disabled="disabled"
:visibleArrow="false"
:options="options"
+ :appendToEl="appendToEl"
trigger="hover"
>
import isArray from 'lodash/isArray';
+ import _useOverlay from '../composables/_useOverlay';
import Popper from './Popper';
/**
@@ -38,6 +40,12 @@
components: {
Popper,
},
+
+ setup(props) {
+ const { getOverlayEl } = _useOverlay();
+ const appendToEl = props.appendToOverlay ? getOverlayEl() : null;
+ return { appendToEl };
+ },
props: {
/**
* Name of `ref` element within the parent's `$refs` object. Tooltip will be
@@ -86,6 +94,15 @@
type: String,
default: null,
},
+ /**
+ * Whether or not the tooltip should be moved
+ * to the overlay container element `#k-overlay`
+ */
+ // eslint-disable-next-line kolibri/vue-no-unused-properties
+ appendToOverlay: {
+ type: Boolean,
+ default: false,
+ },
},
data() {
return {
diff --git a/lib/composables/_useOverlay/__tests__/index.spec.js b/lib/composables/_useOverlay/__tests__/index.spec.js
new file mode 100644
index 000000000..a6b8453f2
--- /dev/null
+++ b/lib/composables/_useOverlay/__tests__/index.spec.js
@@ -0,0 +1,10 @@
+describe('_useOverlay', () => {
+ // this is taken care of by KThemePlugin.js that is already registered
+ // in the global jest setup
+ it(`document body contains the overlay container element #k-overlay upon the KDS plugin initialization`, () => {
+ expect(`
+
+
+ `).toBeInDom();
+ });
+});
diff --git a/lib/composables/_useOverlay/index.js b/lib/composables/_useOverlay/index.js
new file mode 100644
index 000000000..4e23013f6
--- /dev/null
+++ b/lib/composables/_useOverlay/index.js
@@ -0,0 +1,61 @@
+const OVERLAY_EL_ID = 'k-overlay';
+
+/**
+ * A private composable that takes care of everything
+ * related to the overlay container element #k-overlay that is
+ * inserted to an application's document body during the KDS
+ * initialization (see KThemePlugin.js)
+ */
+export default function _useOverlay() {
+ const overlayElSelector = `#${OVERLAY_EL_ID}`;
+
+ /**
+ * Inserts the overlay container element #k-overlay
+ * to an application's document body and saves the element
+ * to window for later use. Should be called only once,
+ * typically during app initialization.
+ */
+ function mountOverlay() {
+ const overlayEl = document.getElementById(OVERLAY_EL_ID);
+
+ // Already mounted, so don't mount again,
+ // just make sure the element is available in window
+ if (overlayEl) {
+ window.overlayEl = overlayEl;
+ return;
+ }
+
+ const newOverlayEl = document.createElement('div');
+ newOverlayEl.id = OVERLAY_EL_ID;
+
+ document.body.insertBefore(newOverlayEl, document.body.firstChild);
+
+ // Save for later use so that we don't need to query
+ // every time we call the 'getOverlayEl'
+ // (frequent operation due to the usage from
+ // KTooltip and other components).
+ window.overlayEl = newOverlayEl;
+ }
+
+ /**
+ * @returns {HTMLElement} The overlay container element #k-overlay
+ */
+ function getOverlayEl() {
+ // do not query DOM for performance reasons
+ const overlayEl = window.overlayEl;
+ // unlikely to happen, but just in case
+ if (!overlayEl) {
+ console.error(
+ '[KDS] The overlay container element #k-overlay is missing. KDS initialization failed?'
+ );
+ return;
+ }
+ return overlayEl;
+ }
+
+ return {
+ mountOverlay,
+ getOverlayEl,
+ overlayElSelector,
+ };
+}
diff --git a/lib/composables/useKLiveRegion/__tests__/index.spec.js b/lib/composables/useKLiveRegion/__tests__/index.spec.js
index 708000ab5..ade2665cf 100644
--- a/lib/composables/useKLiveRegion/__tests__/index.spec.js
+++ b/lib/composables/useKLiveRegion/__tests__/index.spec.js
@@ -3,7 +3,7 @@ import useKLiveRegion from '../index.js';
const { sendPoliteMessage, sendAssertiveMessage } = useKLiveRegion();
describe('useKLiveRegion', () => {
- // this is take care of by KThemePlugin.js that is already registered
+ // this is taken care of by KThemePlugin.js that is already registered
// in the global jest setup
it(`document body contains live regions upon the KDS plugin initialization`, () => {
expect(`