diff options
Diffstat (limited to 'app/assets/javascripts/lib/utils')
-rw-r--r-- | app/assets/javascripts/lib/utils/common_utils.js | 10 | ||||
-rw-r--r-- | app/assets/javascripts/lib/utils/constants.js | 1 | ||||
-rw-r--r-- | app/assets/javascripts/lib/utils/resize_observer.js | 58 |
3 files changed, 68 insertions, 1 deletions
diff --git a/app/assets/javascripts/lib/utils/common_utils.js b/app/assets/javascripts/lib/utils/common_utils.js index 7235b38848c..eff00dff7a7 100644 --- a/app/assets/javascripts/lib/utils/common_utils.js +++ b/app/assets/javascripts/lib/utils/common_utils.js @@ -181,6 +181,7 @@ export const contentTop = () => { }, () => getOuterHeight('.merge-request-tabs'), () => getOuterHeight('.js-diff-files-changed'), + () => getOuterHeight('.issue-sticky-header.gl-fixed'), ({ desktop }) => { const diffsTabIsActive = window.mrTabs?.currentAction === 'diffs'; let size; @@ -746,3 +747,12 @@ export const isLoggedIn = () => Boolean(window.gon?.current_user_id); */ export const convertArrayOfObjectsToCamelCase = (array) => array.map((o) => convertObjectPropsToCamelCase(o)); + +export const getFirstPropertyValue = (data) => { + if (!data) return null; + + const [key] = Object.keys(data); + if (!key) return null; + + return data[key]; +}; diff --git a/app/assets/javascripts/lib/utils/constants.js b/app/assets/javascripts/lib/utils/constants.js index a108b02bcbf..36c6545164e 100644 --- a/app/assets/javascripts/lib/utils/constants.js +++ b/app/assets/javascripts/lib/utils/constants.js @@ -17,7 +17,6 @@ export const BV_HIDE_MODAL = 'bv::hide::modal'; export const BV_HIDE_TOOLTIP = 'bv::hide::tooltip'; export const BV_DROPDOWN_SHOW = 'bv::dropdown::show'; export const BV_DROPDOWN_HIDE = 'bv::dropdown::hide'; -export const BV_COLLAPSE_STATE = 'bv::collapse::state'; export const DEFAULT_TH_CLASSES = 'gl-bg-transparent! gl-border-b-solid! gl-border-b-gray-100! gl-p-5! gl-border-b-1!'; diff --git a/app/assets/javascripts/lib/utils/resize_observer.js b/app/assets/javascripts/lib/utils/resize_observer.js new file mode 100644 index 00000000000..e72c6fe1679 --- /dev/null +++ b/app/assets/javascripts/lib/utils/resize_observer.js @@ -0,0 +1,58 @@ +import { contentTop } from './common_utils'; + +const interactionEvents = ['mousedown', 'touchstart', 'keydown', 'wheel']; + +export function createResizeObserver() { + return new ResizeObserver((entries) => { + entries.forEach((entry) => { + entry.target.dispatchEvent(new CustomEvent(`ResizeUpdate`, { detail: { entry } })); + }); + }); +} + +// watches for change in size of a container element (e.g. for lazy-loaded images) +// and scroll the target element to the top of the content area +// stop watching after any user input. So if user opens sidebar or manually +// scrolls the page we don't hijack their scroll position +export function scrollToTargetOnResize({ + target = window.location.hash, + container = '#content-body', +} = {}) { + if (!target) return null; + + const ro = createResizeObserver(); + const containerEl = document.querySelector(container); + let interactionListenersAdded = false; + + function keepTargetAtTop() { + const anchorEl = document.querySelector(target); + + if (!anchorEl) return; + + const anchorTop = anchorEl.getBoundingClientRect().top + window.scrollY; + const top = anchorTop - contentTop(); + document.documentElement.scrollTo({ + top, + }); + + if (!interactionListenersAdded) { + interactionEvents.forEach((event) => + // eslint-disable-next-line no-use-before-define + document.addEventListener(event, removeListeners), + ); + interactionListenersAdded = true; + } + } + + function removeListeners() { + interactionEvents.forEach((event) => document.removeEventListener(event, removeListeners)); + + ro.unobserve(containerEl); + containerEl.removeEventListener('ResizeUpdate', keepTargetAtTop); + } + + containerEl.addEventListener('ResizeUpdate', keepTargetAtTop); + + ro.observe(containerEl); + return ro; +} |