-
Notifications
You must be signed in to change notification settings - Fork 2.8k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge branch 'users/srmukher/legendsMultiSelectVBC' of https://github…
….com/microsoft/fluentui into users/srmukher/legendsMultiSelectVBC
- Loading branch information
Showing
3 changed files
with
84 additions
and
66 deletions.
There are no files selected for viewing
7 changes: 7 additions & 0 deletions
7
change/@fluentui-react-message-bar-a4bb3869-7b64-45f3-9084-cfbbca1c8a1c.json
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
{ | ||
"type": "patch", | ||
"comment": "fix: MessageBar auto reflow should handle document reflow with `min-content`", | ||
"packageName": "@fluentui/react-message-bar", | ||
"email": "[email protected]", | ||
"dependentChangeType": "patch" | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
133 changes: 67 additions & 66 deletions
133
...act-components/react-message-bar/library/src/components/MessageBar/useMessageBarReflow.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,91 +1,92 @@ | ||
import * as React from 'react'; | ||
import { useFluent_unstable as useFluent } from '@fluentui/react-shared-contexts'; | ||
import { isHTMLElement } from '@fluentui/react-utilities'; | ||
import { useIsomorphicLayoutEffect } from '@fluentui/react-utilities'; | ||
|
||
export function useMessageBarReflow(enabled: boolean = false) { | ||
const { targetDocument } = useFluent(); | ||
const forceUpdate = React.useReducer(() => ({}), {})[1]; | ||
const reflowingRef = React.useRef(false); | ||
// TODO: exclude types from this lint rule: https://github.com/microsoft/fluentui/issues/31286 | ||
|
||
const resizeObserverRef = React.useRef<ResizeObserver | null>(null); | ||
const prevInlineSizeRef = React.useRef(-1); | ||
const messageBarRef = React.useRef<HTMLElement | null>(null); | ||
|
||
const handleResize: ResizeObserverCallback = React.useCallback( | ||
entries => { | ||
// Resize observer is only owned by this component - one resize observer entry expected | ||
// No need to support multiple fragments - one border box entry expected | ||
if (process.env.NODE_ENV !== 'production' && entries.length > 1) { | ||
// eslint-disable-next-line no-console | ||
console.error( | ||
[ | ||
'useMessageBarReflow: Resize observer should only have one entry. ', | ||
'If multiple entries are observed, the first entry will be used.', | ||
'This is a bug, please report it to the Fluent UI team.', | ||
].join(' '), | ||
); | ||
} | ||
const [reflowing, setReflowing] = React.useState(false); | ||
|
||
const entry = entries[0]; | ||
// `borderBoxSize` is not supported before Chrome 84, Firefox 92, nor Safari 15.4 | ||
const inlineSize = entry?.borderBoxSize?.[0]?.inlineSize ?? entry?.target.getBoundingClientRect().width; | ||
// This layout effect 'sanity checks' what observers have done | ||
// since DOM has not been flushed when observers run | ||
useIsomorphicLayoutEffect(() => { | ||
if (!messageBarRef.current) { | ||
return; | ||
} | ||
|
||
if (inlineSize === undefined || !entry) { | ||
return; | ||
setReflowing(prevReflowing => { | ||
if (!prevReflowing && messageBarRef.current && isReflowing(messageBarRef.current)) { | ||
return true; | ||
} | ||
|
||
const { target } = entry; | ||
return prevReflowing; | ||
}); | ||
}, [reflowing]); | ||
|
||
if (!isHTMLElement(target)) { | ||
return; | ||
} | ||
const handleResize: ResizeObserverCallback = React.useCallback(() => { | ||
if (!messageBarRef.current) { | ||
return; | ||
} | ||
|
||
let nextReflowing: boolean | undefined; | ||
|
||
// No easy way to really determine when the single line layout will fit | ||
// Just keep try to set single line layout as long as the size is growing | ||
// Will cause flickering when size is being adjusted gradually (i.e. drag) - but this should not be a common case | ||
if (reflowingRef.current) { | ||
if (prevInlineSizeRef.current < inlineSize) { | ||
nextReflowing = false; | ||
} | ||
} else { | ||
const scrollWidth = target.scrollWidth; | ||
if (inlineSize < scrollWidth) { | ||
nextReflowing = true; | ||
} | ||
} | ||
const inlineSize = messageBarRef.current.getBoundingClientRect().width; | ||
const scrollWidth = messageBarRef.current.scrollWidth; | ||
|
||
prevInlineSizeRef.current = inlineSize; | ||
if (typeof nextReflowing !== 'undefined' && reflowingRef.current !== nextReflowing) { | ||
reflowingRef.current = nextReflowing; | ||
forceUpdate(); | ||
} | ||
}, | ||
[forceUpdate], | ||
); | ||
const expanding = prevInlineSizeRef.current < inlineSize; | ||
const overflowing = inlineSize < scrollWidth; | ||
|
||
setReflowing(!expanding || overflowing); | ||
}, []); | ||
|
||
const handleIntersection: IntersectionObserverCallback = React.useCallback(entries => { | ||
if (entries[0].intersectionRatio < 1) { | ||
setReflowing(true); | ||
} | ||
}, []); | ||
|
||
const ref = React.useMemo(() => { | ||
let resizeObserver: ResizeObserver | null = null; | ||
let intersectionObserer: IntersectionObserver | null = null; | ||
|
||
const ref = React.useCallback( | ||
(el: HTMLElement | null) => { | ||
return (el: HTMLElement | null) => { | ||
if (!enabled || !el || !targetDocument?.defaultView) { | ||
resizeObserver?.disconnect(); | ||
intersectionObserer?.disconnect(); | ||
return; | ||
} | ||
|
||
resizeObserverRef.current?.disconnect(); | ||
messageBarRef.current = el; | ||
|
||
const win = targetDocument.defaultView; | ||
const resizeObserver = new win.ResizeObserver(handleResize); | ||
resizeObserverRef.current = resizeObserver; | ||
resizeObserver.observe(el, { box: 'border-box' }); | ||
}, | ||
[targetDocument, handleResize, enabled], | ||
); | ||
resizeObserver = new win.ResizeObserver(handleResize); | ||
intersectionObserer = new win.IntersectionObserver(handleIntersection, { threshold: 1 }); | ||
|
||
React.useEffect(() => { | ||
return () => { | ||
resizeObserverRef.current?.disconnect(); | ||
intersectionObserer.observe(el); | ||
resizeObserver.observe(el, { box: 'border-box' }); | ||
}; | ||
}, []); | ||
}, [handleResize, handleIntersection, enabled, targetDocument]); | ||
|
||
return { ref, reflowing: reflowingRef.current }; | ||
return { ref, reflowing }; | ||
} | ||
|
||
const isReflowing = (el: HTMLElement) => { | ||
return el.scrollWidth > el.offsetWidth || !isFullyInViewport(el); | ||
}; | ||
|
||
const isFullyInViewport = (el: HTMLElement) => { | ||
const rect = el.getBoundingClientRect(); | ||
const doc = el.ownerDocument; | ||
const win = doc.defaultView; | ||
|
||
if (!win) { | ||
return true; | ||
} | ||
|
||
return ( | ||
rect.top >= 0 && | ||
rect.left >= 0 && | ||
rect.bottom <= (win.innerHeight || doc.documentElement.clientHeight) && | ||
rect.right <= (win.innerWidth || doc.documentElement.clientWidth) | ||
); | ||
}; |