diff --git a/docs/src/mdx/data/mdx-hooks-data.ts b/docs/src/mdx/data/mdx-hooks-data.ts index f6947be0883..ea9bad85bbb 100644 --- a/docs/src/mdx/data/mdx-hooks-data.ts +++ b/docs/src/mdx/data/mdx-hooks-data.ts @@ -152,4 +152,8 @@ export const MDX_HOOKS_DATA: Record = { useWindowScroll: hDocs('useWindowScroll', 'Tracks window scroll position'), usePagination: hDocs('usePagination', 'Manages pagination state'), useInViewport: hDocs('useInViewport', 'Detects if element is visible in the viewport'), + useMutationObserver: hDocs( + 'useMutationObserver', + 'Subscribe to changes being made to the DOM tree' + ), }; diff --git a/docs/src/mdx/mdx-pages-group.ts b/docs/src/mdx/mdx-pages-group.ts index fac3b810bf9..fd8b27061f4 100644 --- a/docs/src/mdx/mdx-pages-group.ts +++ b/docs/src/mdx/mdx-pages-group.ts @@ -81,6 +81,7 @@ export const MDX_PAGES_GROUPS: MdxPagesGroup[] = [ MDX_DATA.useMove, MDX_DATA.useReducedMotion, MDX_DATA.useResizeObserver, + MDX_DATA.useMutationObserver, MDX_DATA.useScrollIntoView, MDX_DATA.useViewportSize, MDX_DATA.useWindowEvent, diff --git a/docs/src/pages/changelog/7-7-0.mdx b/docs/src/pages/changelog/7-7-0.mdx index 4aa9aa74161..296812f0948 100644 --- a/docs/src/pages/changelog/7-7-0.mdx +++ b/docs/src/pages/changelog/7-7-0.mdx @@ -1,4 +1,17 @@ +import { HooksDemos, ScatterChartDemos } from '@docs/demos'; import { Layout } from '@/layout'; import { MDX_DATA } from '@/mdx'; export default Layout(MDX_DATA.Changelog770); + +## ScatterChart component + +New [ScatterChart](/charts/scatter-chart) component: + + + +## use-mutation-observer hook + +New [useMutationObserver](/hooks/use-mutation-observer) hook: + + diff --git a/docs/src/pages/hooks/use-mutation-observer.mdx b/docs/src/pages/hooks/use-mutation-observer.mdx new file mode 100644 index 00000000000..9cfe76ad9cd --- /dev/null +++ b/docs/src/pages/hooks/use-mutation-observer.mdx @@ -0,0 +1,29 @@ +import { HooksDemos } from '@docs/demos'; +import { Layout } from '@/layout'; +import { MDX_DATA } from '@/mdx'; + +export default Layout(MDX_DATA.useMutationObserver); + +## Usage + +`use-mutation-observer` is a wrapper for the [MutationObserver](https://developer.mozilla.org/en-US/docs/Web/API/MutationObserver). +It allows subscribing changes being made to the DOM tree. + + + +## Target element + +If you cannot pass `ref` to the target element, you can pass a function to resolve +the target element as a third argument. + + + +## Definition + +```tsx +function useMutationObserver( + callback: MutationCallback, + options: MutationObserverInit, + target?: HTMLElement | (() => HTMLElement) | null +): RefObject; +``` diff --git a/packages/@docs/demos/src/demos/hooks/Hooks.demos.story.tsx b/packages/@docs/demos/src/demos/hooks/Hooks.demos.story.tsx index 07369017baa..95f585534c1 100644 --- a/packages/@docs/demos/src/demos/hooks/Hooks.demos.story.tsx +++ b/packages/@docs/demos/src/demos/hooks/Hooks.demos.story.tsx @@ -267,3 +267,13 @@ export const Demo_useInViewportUsage = { name: '⭐ Demo: useInViewportUsage', render: renderDemo(demos.useInViewportDemo), }; + +export const Demo_useMutationObserverUsage = { + name: '⭐ Demo: useMutationObserverUsage', + render: renderDemo(demos.useMutationObserverUsage), +}; + +export const Demo_useMutationObserverTarget = { + name: '⭐ Demo: useMutationObserverTarget', + render: renderDemo(demos.useMutationObserverTarget), +}; diff --git a/packages/@docs/demos/src/demos/hooks/index.ts b/packages/@docs/demos/src/demos/hooks/index.ts index 6455d44ca27..c6d5a05a7cc 100644 --- a/packages/@docs/demos/src/demos/hooks/index.ts +++ b/packages/@docs/demos/src/demos/hooks/index.ts @@ -51,3 +51,5 @@ export { usePreviousUsage } from './use-previous.demo.usage'; export { useFaviconUsage } from './use-favicon.demo.usage'; export { useEyeDropperUsage } from './use-eye-dropper.demo.usage'; export { useInViewportDemo } from './use-in-viewport.demo.usage'; +export { useMutationObserverUsage } from './use-mutation-observer.demo.usage'; +export { useMutationObserverTarget } from './use-mutation-observer.demo.target'; diff --git a/packages/@docs/demos/src/demos/hooks/use-mutation-observer.demo.target.tsx b/packages/@docs/demos/src/demos/hooks/use-mutation-observer.demo.target.tsx new file mode 100644 index 00000000000..a9f05bd9260 --- /dev/null +++ b/packages/@docs/demos/src/demos/hooks/use-mutation-observer.demo.target.tsx @@ -0,0 +1,76 @@ +import React, { useState } from 'react'; +import { Kbd, Text } from '@mantine/core'; +import { useMutationObserver } from '@mantine/hooks'; +import { MantineDemo } from '@mantinex/demo'; + +const code = ` +import { useState } from 'react'; +import { Kbd, Text } from '@mantine/core'; +import { useMutationObserver } from '@mantine/hooks'; + +function Demo() { + const [lastMutation, setLastMutation] = useState(''); + + useMutationObserver( + (mutations) => { + mutations.forEach((mutation) => { + if (mutation.type === 'attributes' && mutation.attributeName === 'dir') { + mutation.target instanceof HTMLElement && + setLastMutation(mutation.target.getAttribute('dir') || ''); + } + }); + }, + { + attributes: true, + attributeFilter: ['dir'], + }, + () => document.documentElement + ); + + return ( + <> + + Press Ctrl + Shift + L to change direction + + + Direction was changed to: {lastMutation || 'Not changed yet'} + + ); +} +`; + +function Demo() { + const [lastMutation, setLastMutation] = useState(''); + + useMutationObserver( + (mutations) => { + mutations.forEach((mutation) => { + if (mutation.type === 'attributes' && mutation.attributeName === 'dir') { + mutation.target instanceof HTMLElement && + setLastMutation(mutation.target.getAttribute('dir') || ''); + } + }); + }, + { + attributes: true, + attributeFilter: ['dir'], + }, + () => document.documentElement + ); + + return ( + <> + + Press Ctrl + Shift + L to change direction + + + Direction was changed to: {lastMutation || 'Not changed yet'} + + ); +} + +export const useMutationObserverTarget: MantineDemo = { + type: 'code', + component: Demo, + code, +}; diff --git a/packages/@docs/demos/src/demos/hooks/use-mutation-observer.demo.usage.tsx b/packages/@docs/demos/src/demos/hooks/use-mutation-observer.demo.usage.tsx new file mode 100644 index 00000000000..a8d2a5170a2 --- /dev/null +++ b/packages/@docs/demos/src/demos/hooks/use-mutation-observer.demo.usage.tsx @@ -0,0 +1,86 @@ +import React, { useState } from 'react'; +import { Button, Text } from '@mantine/core'; +import { useMutationObserver } from '@mantine/hooks'; +import { MantineDemo } from '@mantinex/demo'; + +const code = ` +import { useState } from 'react'; +import { Button, Text } from '@mantine/core'; +import { useMutationObserver } from '@mantine/hooks'; + +function Demo() { + const [lastMutation, setLastMutation] = useState(''); + + const ref = useMutationObserver( + (mutations) => { + mutations.forEach((mutation) => { + if (mutation.type === 'attributes' && mutation.attributeName === 'data-mutation') { + mutation.target instanceof HTMLElement && + setLastMutation(mutation.target.dataset.mutation || ''); + } + }); + }, + { + attributes: true, + attributeFilter: ['data-mutation'], + } + ); + + return ( + <> + + + Last detected mutation: {lastMutation || 'Not mutated yet'} + + + ); +} +`; + +function Demo() { + const [lastMutation, setLastMutation] = useState(''); + + const ref = useMutationObserver( + (mutations) => { + mutations.forEach((mutation) => { + if (mutation.type === 'attributes' && mutation.attributeName === 'dir') { + mutation.target instanceof HTMLElement && + setLastMutation(mutation.target.dataset.mutation || ''); + } + }); + }, + { + attributes: true, + attributeFilter: ['data-mutation'], + } + ); + + return ( + <> + + + Last detected mutation: {lastMutation || 'Not mutated yet'} + + + ); +} + +export const useMutationObserverUsage: MantineDemo = { + type: 'code', + component: Demo, + code, +}; diff --git a/packages/@mantine/hooks/src/use-mutation-observer/use-mutation-observer.ts b/packages/@mantine/hooks/src/use-mutation-observer/use-mutation-observer.ts index cb7778abe0e..4b53a552587 100644 --- a/packages/@mantine/hooks/src/use-mutation-observer/use-mutation-observer.ts +++ b/packages/@mantine/hooks/src/use-mutation-observer/use-mutation-observer.ts @@ -1,6 +1,6 @@ import { RefObject, useEffect, useRef } from 'react'; -export function useMutationObserver( +export function useMutationObserver( callback: MutationCallback, options: MutationObserverInit, target?: HTMLElement | (() => HTMLElement) | null