@entur/modal 2.0.1 → 2.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/modal.cjs CHANGED
@@ -174,7 +174,7 @@ const Drawer = ({
174
174
  react.useEffect(
175
175
  function handleFocus() {
176
176
  if (!open || overlay) return;
177
- previouslyFocusedRef.current = document.activeElement;
177
+ previouslyFocusedRef.current = utils.getActiveElement();
178
178
  drawerRef.current?.querySelector("[data-autofocus]")?.focus();
179
179
  return () => {
180
180
  if (previouslyFocusedRef.current instanceof HTMLElement) {
@@ -1 +1 @@
1
- {"version":3,"file":"modal.cjs","sources":["../src/ModalOverlay.tsx","../src/ModalContent.tsx","../src/Modal.tsx","../src/Drawer.tsx","../src/index.tsx"],"sourcesContent":["import React, { useCallback, useEffect, useRef, useState } from 'react';\nimport { createPortal } from 'react-dom';\nimport classNames from 'classnames';\nimport { ContrastContext } from '@entur/layout';\n\nexport type ModalOverlayProps = {\n /** Flagg som sier om modalen er åpen */\n open?: boolean;\n /** Callback som kalles når brukeren ber om å lukke modalen */\n onDismiss?: () => void;\n /** Innholdet i modalen */\n children: React.ReactNode;\n /** Ekstra klassenavn */\n className?: string;\n /** En ref til elementet som skal være fokusert når modalen åpnes. Defaulter til lukkeknappen */\n initialFocusRef?: React.RefObject<HTMLElement>;\n /** Om modalen skal lukkes når man klikker utenfor den\n * @default true\n */\n closeOnClickOutside?: boolean;\n} & Omit<React.DialogHTMLAttributes<HTMLDialogElement>, 'open' | 'onClick'>;\n\nexport const ModalOverlay = ({\n className,\n open,\n onDismiss,\n initialFocusRef,\n closeOnClickOutside = true,\n children,\n ...rest\n}: ModalOverlayProps) => {\n const dialogRef = useRef<HTMLDialogElement>(null);\n // The portal can only render after hydration — server and first client render\n // must match, and the server has no <dialog> to hydrate against.\n const [isMounted, setIsMounted] = useState(false);\n\n useEffect(() => setIsMounted(true), []);\n\n // Toggle native dialog open state. Always stays in DOM so the browser can\n // manage initial focus storage + restore on close.\n useEffect(() => {\n const dialog = dialogRef.current;\n if (!dialog) return;\n if (open && !dialog.open) {\n dialog.showModal();\n const target =\n initialFocusRef?.current ??\n dialog.querySelector<HTMLElement>('[data-autofocus]') ??\n dialog;\n target.focus();\n } else if (!open && dialog.open) {\n dialog.close();\n }\n }, [open, initialFocusRef, isMounted]);\n\n // Esc fires cancel — preventDefault so we route through onDismiss + state,\n // which triggers the effect above to call close() (native restore runs).\n useEffect(\n function syncCancelEvent() {\n const dialog = dialogRef.current;\n if (!dialog) return;\n const handleCancel = (e: Event) => {\n e.preventDefault();\n onDismiss?.();\n };\n dialog.addEventListener('cancel', handleCancel);\n return () => dialog.removeEventListener('cancel', handleCancel);\n },\n [onDismiss, isMounted],\n );\n\n const handleClick = useCallback(\n (e: React.MouseEvent<HTMLDialogElement>) => {\n if (!closeOnClickOutside) return;\n if (e.target === dialogRef.current) onDismiss?.();\n },\n [onDismiss, closeOnClickOutside],\n );\n\n if (!isMounted) return null;\n\n return createPortal(\n <ContrastContext.Provider value={false}>\n <dialog\n ref={dialogRef}\n className={classNames('eds-modal__overlay', className)}\n {...rest}\n onClick={handleClick}\n >\n {open && children}\n </dialog>\n </ContrastContext.Provider>,\n document.body,\n );\n};\n","import React, { useId } from 'react';\nimport classNames from 'classnames';\nimport { Heading4, Heading3, Heading2 } from '@entur/typography';\n\nexport type ModalContentProps = {\n /** Innholdet i modalen */\n children: React.ReactNode;\n /** Ekstra klassenavn */\n className?: string;\n /** Størrelsen på modalen */\n size: 'extraSmall' | 'small' | 'medium' | 'large' | 'extraLarge';\n /** Tittelen som vises i modalen */\n title?: React.ReactNode;\n /** Hvordan innholdet skal plasseres i modalen\n * @default 'start'\n */\n align?: 'start' | 'center' | 'end';\n} & Omit<React.HTMLAttributes<HTMLDivElement>, 'title'>;\n\nexport const headingsMap = {\n extraSmall: Heading4,\n small: Heading3,\n medium: Heading2,\n large: Heading2,\n extraLarge: Heading2,\n};\n\nexport const ModalContent = ({\n children,\n className,\n size,\n title,\n align = 'start',\n ...rest\n}: ModalContentProps) => {\n const Heading: React.ElementType = headingsMap[size] || Heading2;\n const randomId = useId();\n return (\n <div\n className={classNames(\n 'eds-modal__content',\n `eds-modal__content--size-${size}`,\n `eds-modal__content--align-${align}`,\n className,\n )}\n aria-labelledby={title ? randomId : undefined}\n {...rest}\n >\n {title && (\n <Heading margin=\"bottom\" as=\"h2\" id={randomId}>\n {title}\n </Heading>\n )}\n {children}\n </div>\n );\n};\n","import React, { useId } from 'react';\n\nimport { CloseIcon } from '@entur/icons';\nimport { IconButton } from '@entur/button';\nimport { Heading2 } from '@entur/typography';\n\nimport { ModalOverlay } from './ModalOverlay';\nimport { ModalContent, headingsMap } from './ModalContent';\n\nimport './Modal.scss';\n\nexport type ModalProps = {\n /** Innholdet i modalen */\n children: React.ReactNode;\n /** Skjermleser-label til lukk-knappen */\n closeLabel?: string;\n /** En ref til elementet som skal være fokusert når modalen åpnes. Defaulter til tittelen, ellers første interaktive element */\n initialFocusRef?: React.RefObject<HTMLElement>;\n /** Styrer om modalen er synlig */\n open?: boolean;\n /** Callback som kalles når brukeren ber om å lukke modalen. Påkrevd: Esc og klikk utenfor lukker alltid (nettleserens innebygde atferd for native dialog) */\n onDismiss: () => void;\n /** Om lukkeknappen skal vises i øvre høyre hjørne\n * @default true\n */\n showCloseButton?: boolean;\n /** Størrelsen på modalen */\n size: 'extraSmall' | 'small' | 'medium' | 'large' | 'extraLarge';\n /** Hvordan innholdet skal plasseres i modalen\n * @default 'start'\n */\n align?: 'start' | 'center' | 'end';\n /** Tittelen som vises i modalen */\n title?: React.ReactNode;\n /** Tilgjengelig navn for modalen når title ikke er satt */\n 'aria-label'?: string;\n /** Om modalen skal lukkes når man klikker på utsiden av den\n * @default true\n */\n closeOnClickOutside?: boolean;\n} & Omit<React.HTMLAttributes<HTMLDivElement>, 'title' | 'aria-label'>;\n\nexport const Modal = ({\n children,\n closeLabel = 'Lukk',\n initialFocusRef,\n open,\n onDismiss,\n size,\n align = 'start',\n title,\n closeOnClickOutside = true,\n showCloseButton = true,\n 'aria-label': ariaLabel,\n ...rest\n}: ModalProps) => {\n const randomId = useId();\n const Heading: React.ElementType = headingsMap[size] || Heading2;\n\n return (\n <ModalOverlay\n open={open}\n onDismiss={onDismiss}\n closeOnClickOutside={closeOnClickOutside}\n initialFocusRef={initialFocusRef}\n className={`eds-modal__overlay--size-${size}`}\n aria-labelledby={title ? randomId : undefined}\n aria-label={!title ? ariaLabel : undefined}\n >\n {showCloseButton && (\n <IconButton\n className=\"eds-modal__close\"\n aria-label={closeLabel}\n onClick={onDismiss}\n type=\"button\"\n >\n <CloseIcon />\n </IconButton>\n )}\n <ModalContent size={size} align={align} {...rest}>\n {title && (\n <Heading\n margin=\"bottom\"\n as=\"h2\"\n id={randomId}\n tabIndex={-1}\n data-autofocus={initialFocusRef ? undefined : ''}\n >\n {title}\n </Heading>\n )}\n {children}\n </ModalContent>\n </ModalOverlay>\n );\n};\n","import React, { useEffect, useId, useRef } from 'react';\nimport classNames from 'classnames';\nimport { Contrast } from '@entur/layout';\nimport { CloseIcon } from '@entur/icons';\nimport { Heading3 } from '@entur/typography';\nimport { IconButton } from '@entur/button';\nimport { ConditionalWrapper } from '@entur/utils';\nimport { ModalOverlay } from './ModalOverlay';\n\nimport './Drawer.scss';\n\nexport type DrawerProps = {\n /** Innholdet. Typisk tekst, lenker eller knapper */\n children: React.ReactNode;\n /** Ekstra klassenavn */\n className?: string;\n /** Tekst som beskriver lukkeknappen for skjermlesere\n * @default 'Lukk skuff'\n */\n closeLabel?: string;\n /** Om draweren skal vises i mørk variant\n * @default false\n */\n contrast?: boolean;\n /** Callback som kalles når brukeren ønsker å lukke draweren */\n onDismiss: () => void;\n /** Om draweren er åpen eller ikke\n * @default true\n */\n open?: boolean;\n /** Tittel på toppen av draweren */\n title: string;\n /** Styling som sendes til Drawer */\n style?: React.CSSProperties;\n /** Legger på et overlay over resten av siden */\n overlay?: boolean;\n};\n\nexport const Drawer = ({\n children,\n className,\n closeLabel = 'Lukk skuff',\n contrast = false,\n open = true,\n onDismiss,\n title,\n style,\n overlay = false,\n}: DrawerProps) => {\n const titleId = useId();\n const drawerRef = useRef<HTMLDivElement>(null);\n const previouslyFocusedRef = useRef<Element | null>(null);\n\n useEffect(\n function handleFocus() {\n if (!open || overlay) return;\n previouslyFocusedRef.current = document.activeElement;\n drawerRef.current\n ?.querySelector<HTMLElement>('[data-autofocus]')\n ?.focus();\n\n return () => {\n if (previouslyFocusedRef.current instanceof HTMLElement) {\n previouslyFocusedRef.current.focus();\n }\n };\n },\n [open, overlay],\n );\n\n const shouldRemoveFromDOM = !open && !overlay;\n if (shouldRemoveFromDOM) return null;\n\n function handleKeyDown(e: React.KeyboardEvent) {\n if (e.key === 'Escape') {\n e.stopPropagation();\n onDismiss();\n }\n }\n\n const Wrapper = contrast ? Contrast : 'div';\n const nonModalDialogProps = {\n role: 'dialog' as const,\n 'aria-modal': false,\n 'aria-labelledby': titleId,\n };\n\n return (\n <ConditionalWrapper\n condition={overlay}\n wrapper={(children: React.ReactNode) => (\n <ModalOverlay\n open={open}\n onDismiss={onDismiss}\n aria-labelledby={titleId}\n >\n {children}\n </ModalOverlay>\n )}\n >\n <Wrapper\n ref={drawerRef}\n {...(!overlay && nonModalDialogProps)}\n className={classNames('eds-drawer', className)}\n onKeyDown={handleKeyDown}\n style={style}\n >\n <IconButton\n className=\"eds-drawer__close-button\"\n onClick={onDismiss}\n type=\"button\"\n aria-label={closeLabel}\n >\n <CloseIcon aria-hidden />\n </IconButton>\n <div className=\"eds-drawer__content\">\n <Heading3 as=\"h2\" id={titleId} tabIndex={-1} data-autofocus=\"\">\n {title}\n </Heading3>\n {children}\n </div>\n </Wrapper>\n </ConditionalWrapper>\n );\n};\n","import { warnAboutMissingStyles } from '@entur/utils';\nimport './index.scss';\n\nwarnAboutMissingStyles('modal', 'icons', 'typography', 'a11y', 'button');\n\nexport * from './Modal';\nexport * from './ModalOverlay';\nexport * from './ModalContent';\nexport * from './Drawer';\n"],"names":["useRef","useState","useEffect","useCallback","createPortal","jsx","ContrastContext","Heading4","Heading3","Heading2","useId","jsxs","IconButton","CloseIcon","Contrast","ConditionalWrapper","children","warnAboutMissingStyles"],"mappings":"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"}
1
+ {"version":3,"file":"modal.cjs","sources":["../src/ModalOverlay.tsx","../src/ModalContent.tsx","../src/Modal.tsx","../src/Drawer.tsx","../src/index.tsx"],"sourcesContent":["import React, { useCallback, useEffect, useRef, useState } from 'react';\nimport { createPortal } from 'react-dom';\nimport classNames from 'classnames';\nimport { ContrastContext } from '@entur/layout';\n\nexport type ModalOverlayProps = {\n /** Flagg som sier om modalen er åpen */\n open?: boolean;\n /** Callback som kalles når brukeren ber om å lukke modalen */\n onDismiss?: () => void;\n /** Innholdet i modalen */\n children: React.ReactNode;\n /** Ekstra klassenavn */\n className?: string;\n /** En ref til elementet som skal være fokusert når modalen åpnes. Defaulter til lukkeknappen */\n initialFocusRef?: React.RefObject<HTMLElement>;\n /** Om modalen skal lukkes når man klikker utenfor den\n * @default true\n */\n closeOnClickOutside?: boolean;\n} & Omit<React.DialogHTMLAttributes<HTMLDialogElement>, 'open' | 'onClick'>;\n\nexport const ModalOverlay = ({\n className,\n open,\n onDismiss,\n initialFocusRef,\n closeOnClickOutside = true,\n children,\n ...rest\n}: ModalOverlayProps) => {\n const dialogRef = useRef<HTMLDialogElement>(null);\n // The portal can only render after hydration — server and first client render\n // must match, and the server has no <dialog> to hydrate against.\n const [isMounted, setIsMounted] = useState(false);\n\n useEffect(() => setIsMounted(true), []);\n\n // Toggle native dialog open state. Always stays in DOM so the browser can\n // manage initial focus storage + restore on close.\n useEffect(() => {\n const dialog = dialogRef.current;\n if (!dialog) return;\n if (open && !dialog.open) {\n dialog.showModal();\n const target =\n initialFocusRef?.current ??\n dialog.querySelector<HTMLElement>('[data-autofocus]') ??\n dialog;\n target.focus();\n } else if (!open && dialog.open) {\n dialog.close();\n }\n }, [open, initialFocusRef, isMounted]);\n\n // Esc fires cancel — preventDefault so we route through onDismiss + state,\n // which triggers the effect above to call close() (native restore runs).\n useEffect(\n function syncCancelEvent() {\n const dialog = dialogRef.current;\n if (!dialog) return;\n const handleCancel = (e: Event) => {\n e.preventDefault();\n onDismiss?.();\n };\n dialog.addEventListener('cancel', handleCancel);\n return () => dialog.removeEventListener('cancel', handleCancel);\n },\n [onDismiss, isMounted],\n );\n\n const handleClick = useCallback(\n (e: React.MouseEvent<HTMLDialogElement>) => {\n if (!closeOnClickOutside) return;\n if (e.target === dialogRef.current) onDismiss?.();\n },\n [onDismiss, closeOnClickOutside],\n );\n\n if (!isMounted) return null;\n\n return createPortal(\n <ContrastContext.Provider value={false}>\n <dialog\n ref={dialogRef}\n className={classNames('eds-modal__overlay', className)}\n {...rest}\n onClick={handleClick}\n >\n {open && children}\n </dialog>\n </ContrastContext.Provider>,\n document.body,\n );\n};\n","import React, { useId } from 'react';\nimport classNames from 'classnames';\nimport { Heading4, Heading3, Heading2 } from '@entur/typography';\n\nexport type ModalContentProps = {\n /** Innholdet i modalen */\n children: React.ReactNode;\n /** Ekstra klassenavn */\n className?: string;\n /** Størrelsen på modalen */\n size: 'extraSmall' | 'small' | 'medium' | 'large' | 'extraLarge';\n /** Tittelen som vises i modalen */\n title?: React.ReactNode;\n /** Hvordan innholdet skal plasseres i modalen\n * @default 'start'\n */\n align?: 'start' | 'center' | 'end';\n} & Omit<React.HTMLAttributes<HTMLDivElement>, 'title'>;\n\nexport const headingsMap = {\n extraSmall: Heading4,\n small: Heading3,\n medium: Heading2,\n large: Heading2,\n extraLarge: Heading2,\n};\n\nexport const ModalContent = ({\n children,\n className,\n size,\n title,\n align = 'start',\n ...rest\n}: ModalContentProps) => {\n const Heading: React.ElementType = headingsMap[size] || Heading2;\n const randomId = useId();\n return (\n <div\n className={classNames(\n 'eds-modal__content',\n `eds-modal__content--size-${size}`,\n `eds-modal__content--align-${align}`,\n className,\n )}\n aria-labelledby={title ? randomId : undefined}\n {...rest}\n >\n {title && (\n <Heading margin=\"bottom\" as=\"h2\" id={randomId}>\n {title}\n </Heading>\n )}\n {children}\n </div>\n );\n};\n","import React, { useId } from 'react';\n\nimport { CloseIcon } from '@entur/icons';\nimport { IconButton } from '@entur/button';\nimport { Heading2 } from '@entur/typography';\n\nimport { ModalOverlay } from './ModalOverlay';\nimport { ModalContent, headingsMap } from './ModalContent';\n\nimport './Modal.scss';\n\nexport type ModalProps = {\n /** Innholdet i modalen */\n children: React.ReactNode;\n /** Skjermleser-label til lukk-knappen */\n closeLabel?: string;\n /** En ref til elementet som skal være fokusert når modalen åpnes. Defaulter til tittelen, ellers første interaktive element */\n initialFocusRef?: React.RefObject<HTMLElement>;\n /** Styrer om modalen er synlig */\n open?: boolean;\n /** Callback som kalles når brukeren ber om å lukke modalen. Påkrevd: Esc og klikk utenfor lukker alltid (nettleserens innebygde atferd for native dialog) */\n onDismiss: () => void;\n /** Om lukkeknappen skal vises i øvre høyre hjørne\n * @default true\n */\n showCloseButton?: boolean;\n /** Størrelsen på modalen */\n size: 'extraSmall' | 'small' | 'medium' | 'large' | 'extraLarge';\n /** Hvordan innholdet skal plasseres i modalen\n * @default 'start'\n */\n align?: 'start' | 'center' | 'end';\n /** Tittelen som vises i modalen */\n title?: React.ReactNode;\n /** Tilgjengelig navn for modalen når title ikke er satt */\n 'aria-label'?: string;\n /** Om modalen skal lukkes når man klikker på utsiden av den\n * @default true\n */\n closeOnClickOutside?: boolean;\n} & Omit<React.HTMLAttributes<HTMLDivElement>, 'title' | 'aria-label'>;\n\nexport const Modal = ({\n children,\n closeLabel = 'Lukk',\n initialFocusRef,\n open,\n onDismiss,\n size,\n align = 'start',\n title,\n closeOnClickOutside = true,\n showCloseButton = true,\n 'aria-label': ariaLabel,\n ...rest\n}: ModalProps) => {\n const randomId = useId();\n const Heading: React.ElementType = headingsMap[size] || Heading2;\n\n return (\n <ModalOverlay\n open={open}\n onDismiss={onDismiss}\n closeOnClickOutside={closeOnClickOutside}\n initialFocusRef={initialFocusRef}\n className={`eds-modal__overlay--size-${size}`}\n aria-labelledby={title ? randomId : undefined}\n aria-label={!title ? ariaLabel : undefined}\n >\n {showCloseButton && (\n <IconButton\n className=\"eds-modal__close\"\n aria-label={closeLabel}\n onClick={onDismiss}\n type=\"button\"\n >\n <CloseIcon />\n </IconButton>\n )}\n <ModalContent size={size} align={align} {...rest}>\n {title && (\n <Heading\n margin=\"bottom\"\n as=\"h2\"\n id={randomId}\n tabIndex={-1}\n data-autofocus={initialFocusRef ? undefined : ''}\n >\n {title}\n </Heading>\n )}\n {children}\n </ModalContent>\n </ModalOverlay>\n );\n};\n","import React, { useEffect, useId, useRef } from 'react';\nimport classNames from 'classnames';\nimport { Contrast } from '@entur/layout';\nimport { CloseIcon } from '@entur/icons';\nimport { Heading3 } from '@entur/typography';\nimport { IconButton } from '@entur/button';\nimport { ConditionalWrapper, getActiveElement } from '@entur/utils';\nimport { ModalOverlay } from './ModalOverlay';\n\nimport './Drawer.scss';\n\nexport type DrawerProps = {\n /** Innholdet. Typisk tekst, lenker eller knapper */\n children: React.ReactNode;\n /** Ekstra klassenavn */\n className?: string;\n /** Tekst som beskriver lukkeknappen for skjermlesere\n * @default 'Lukk skuff'\n */\n closeLabel?: string;\n /** Om draweren skal vises i mørk variant\n * @default false\n */\n contrast?: boolean;\n /** Callback som kalles når brukeren ønsker å lukke draweren */\n onDismiss: () => void;\n /** Om draweren er åpen eller ikke\n * @default true\n */\n open?: boolean;\n /** Tittel på toppen av draweren */\n title: string;\n /** Styling som sendes til Drawer */\n style?: React.CSSProperties;\n /** Legger på et overlay over resten av siden */\n overlay?: boolean;\n};\n\nexport const Drawer = ({\n children,\n className,\n closeLabel = 'Lukk skuff',\n contrast = false,\n open = true,\n onDismiss,\n title,\n style,\n overlay = false,\n}: DrawerProps) => {\n const titleId = useId();\n const drawerRef = useRef<HTMLDivElement>(null);\n const previouslyFocusedRef = useRef<Element | null>(null);\n\n useEffect(\n function handleFocus() {\n if (!open || overlay) return;\n previouslyFocusedRef.current = getActiveElement();\n drawerRef.current\n ?.querySelector<HTMLElement>('[data-autofocus]')\n ?.focus();\n\n return () => {\n if (previouslyFocusedRef.current instanceof HTMLElement) {\n previouslyFocusedRef.current.focus();\n }\n };\n },\n [open, overlay],\n );\n\n const shouldRemoveFromDOM = !open && !overlay;\n if (shouldRemoveFromDOM) return null;\n\n function handleKeyDown(e: React.KeyboardEvent) {\n if (e.key === 'Escape') {\n e.stopPropagation();\n onDismiss();\n }\n }\n\n const Wrapper = contrast ? Contrast : 'div';\n const nonModalDialogProps = {\n role: 'dialog' as const,\n 'aria-modal': false,\n 'aria-labelledby': titleId,\n };\n\n return (\n <ConditionalWrapper\n condition={overlay}\n wrapper={(children: React.ReactNode) => (\n <ModalOverlay\n open={open}\n onDismiss={onDismiss}\n aria-labelledby={titleId}\n >\n {children}\n </ModalOverlay>\n )}\n >\n <Wrapper\n ref={drawerRef}\n {...(!overlay && nonModalDialogProps)}\n className={classNames('eds-drawer', className)}\n onKeyDown={handleKeyDown}\n style={style}\n >\n <IconButton\n className=\"eds-drawer__close-button\"\n onClick={onDismiss}\n type=\"button\"\n aria-label={closeLabel}\n >\n <CloseIcon aria-hidden />\n </IconButton>\n <div className=\"eds-drawer__content\">\n <Heading3 as=\"h2\" id={titleId} tabIndex={-1} data-autofocus=\"\">\n {title}\n </Heading3>\n {children}\n </div>\n </Wrapper>\n </ConditionalWrapper>\n );\n};\n","import { warnAboutMissingStyles } from '@entur/utils';\nimport './index.scss';\n\nwarnAboutMissingStyles('modal', 'icons', 'typography', 'a11y', 'button');\n\nexport * from './Modal';\nexport * from './ModalOverlay';\nexport * from './ModalContent';\nexport * from './Drawer';\n"],"names":["useRef","useState","useEffect","useCallback","createPortal","jsx","ContrastContext","Heading4","Heading3","Heading2","useId","jsxs","IconButton","CloseIcon","getActiveElement","Contrast","ConditionalWrapper","children","warnAboutMissingStyles"],"mappings":"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"}
package/dist/modal.mjs CHANGED
@@ -1,4 +1,4 @@
1
- import { ConditionalWrapper, warnAboutMissingStyles } from "@entur/utils";
1
+ import { getActiveElement, ConditionalWrapper, warnAboutMissingStyles } from "@entur/utils";
2
2
  import { jsx, jsxs } from "react/jsx-runtime";
3
3
  import { useRef, useState, useEffect, useCallback, useId } from "react";
4
4
  import { CloseIcon } from "@entur/icons";
@@ -172,7 +172,7 @@ const Drawer = ({
172
172
  useEffect(
173
173
  function handleFocus() {
174
174
  if (!open || overlay) return;
175
- previouslyFocusedRef.current = document.activeElement;
175
+ previouslyFocusedRef.current = getActiveElement();
176
176
  drawerRef.current?.querySelector("[data-autofocus]")?.focus();
177
177
  return () => {
178
178
  if (previouslyFocusedRef.current instanceof HTMLElement) {
@@ -1 +1 @@
1
- {"version":3,"file":"modal.mjs","sources":["../src/ModalOverlay.tsx","../src/ModalContent.tsx","../src/Modal.tsx","../src/Drawer.tsx","../src/index.tsx"],"sourcesContent":["import React, { useCallback, useEffect, useRef, useState } from 'react';\nimport { createPortal } from 'react-dom';\nimport classNames from 'classnames';\nimport { ContrastContext } from '@entur/layout';\n\nexport type ModalOverlayProps = {\n /** Flagg som sier om modalen er åpen */\n open?: boolean;\n /** Callback som kalles når brukeren ber om å lukke modalen */\n onDismiss?: () => void;\n /** Innholdet i modalen */\n children: React.ReactNode;\n /** Ekstra klassenavn */\n className?: string;\n /** En ref til elementet som skal være fokusert når modalen åpnes. Defaulter til lukkeknappen */\n initialFocusRef?: React.RefObject<HTMLElement>;\n /** Om modalen skal lukkes når man klikker utenfor den\n * @default true\n */\n closeOnClickOutside?: boolean;\n} & Omit<React.DialogHTMLAttributes<HTMLDialogElement>, 'open' | 'onClick'>;\n\nexport const ModalOverlay = ({\n className,\n open,\n onDismiss,\n initialFocusRef,\n closeOnClickOutside = true,\n children,\n ...rest\n}: ModalOverlayProps) => {\n const dialogRef = useRef<HTMLDialogElement>(null);\n // The portal can only render after hydration — server and first client render\n // must match, and the server has no <dialog> to hydrate against.\n const [isMounted, setIsMounted] = useState(false);\n\n useEffect(() => setIsMounted(true), []);\n\n // Toggle native dialog open state. Always stays in DOM so the browser can\n // manage initial focus storage + restore on close.\n useEffect(() => {\n const dialog = dialogRef.current;\n if (!dialog) return;\n if (open && !dialog.open) {\n dialog.showModal();\n const target =\n initialFocusRef?.current ??\n dialog.querySelector<HTMLElement>('[data-autofocus]') ??\n dialog;\n target.focus();\n } else if (!open && dialog.open) {\n dialog.close();\n }\n }, [open, initialFocusRef, isMounted]);\n\n // Esc fires cancel — preventDefault so we route through onDismiss + state,\n // which triggers the effect above to call close() (native restore runs).\n useEffect(\n function syncCancelEvent() {\n const dialog = dialogRef.current;\n if (!dialog) return;\n const handleCancel = (e: Event) => {\n e.preventDefault();\n onDismiss?.();\n };\n dialog.addEventListener('cancel', handleCancel);\n return () => dialog.removeEventListener('cancel', handleCancel);\n },\n [onDismiss, isMounted],\n );\n\n const handleClick = useCallback(\n (e: React.MouseEvent<HTMLDialogElement>) => {\n if (!closeOnClickOutside) return;\n if (e.target === dialogRef.current) onDismiss?.();\n },\n [onDismiss, closeOnClickOutside],\n );\n\n if (!isMounted) return null;\n\n return createPortal(\n <ContrastContext.Provider value={false}>\n <dialog\n ref={dialogRef}\n className={classNames('eds-modal__overlay', className)}\n {...rest}\n onClick={handleClick}\n >\n {open && children}\n </dialog>\n </ContrastContext.Provider>,\n document.body,\n );\n};\n","import React, { useId } from 'react';\nimport classNames from 'classnames';\nimport { Heading4, Heading3, Heading2 } from '@entur/typography';\n\nexport type ModalContentProps = {\n /** Innholdet i modalen */\n children: React.ReactNode;\n /** Ekstra klassenavn */\n className?: string;\n /** Størrelsen på modalen */\n size: 'extraSmall' | 'small' | 'medium' | 'large' | 'extraLarge';\n /** Tittelen som vises i modalen */\n title?: React.ReactNode;\n /** Hvordan innholdet skal plasseres i modalen\n * @default 'start'\n */\n align?: 'start' | 'center' | 'end';\n} & Omit<React.HTMLAttributes<HTMLDivElement>, 'title'>;\n\nexport const headingsMap = {\n extraSmall: Heading4,\n small: Heading3,\n medium: Heading2,\n large: Heading2,\n extraLarge: Heading2,\n};\n\nexport const ModalContent = ({\n children,\n className,\n size,\n title,\n align = 'start',\n ...rest\n}: ModalContentProps) => {\n const Heading: React.ElementType = headingsMap[size] || Heading2;\n const randomId = useId();\n return (\n <div\n className={classNames(\n 'eds-modal__content',\n `eds-modal__content--size-${size}`,\n `eds-modal__content--align-${align}`,\n className,\n )}\n aria-labelledby={title ? randomId : undefined}\n {...rest}\n >\n {title && (\n <Heading margin=\"bottom\" as=\"h2\" id={randomId}>\n {title}\n </Heading>\n )}\n {children}\n </div>\n );\n};\n","import React, { useId } from 'react';\n\nimport { CloseIcon } from '@entur/icons';\nimport { IconButton } from '@entur/button';\nimport { Heading2 } from '@entur/typography';\n\nimport { ModalOverlay } from './ModalOverlay';\nimport { ModalContent, headingsMap } from './ModalContent';\n\nimport './Modal.scss';\n\nexport type ModalProps = {\n /** Innholdet i modalen */\n children: React.ReactNode;\n /** Skjermleser-label til lukk-knappen */\n closeLabel?: string;\n /** En ref til elementet som skal være fokusert når modalen åpnes. Defaulter til tittelen, ellers første interaktive element */\n initialFocusRef?: React.RefObject<HTMLElement>;\n /** Styrer om modalen er synlig */\n open?: boolean;\n /** Callback som kalles når brukeren ber om å lukke modalen. Påkrevd: Esc og klikk utenfor lukker alltid (nettleserens innebygde atferd for native dialog) */\n onDismiss: () => void;\n /** Om lukkeknappen skal vises i øvre høyre hjørne\n * @default true\n */\n showCloseButton?: boolean;\n /** Størrelsen på modalen */\n size: 'extraSmall' | 'small' | 'medium' | 'large' | 'extraLarge';\n /** Hvordan innholdet skal plasseres i modalen\n * @default 'start'\n */\n align?: 'start' | 'center' | 'end';\n /** Tittelen som vises i modalen */\n title?: React.ReactNode;\n /** Tilgjengelig navn for modalen når title ikke er satt */\n 'aria-label'?: string;\n /** Om modalen skal lukkes når man klikker på utsiden av den\n * @default true\n */\n closeOnClickOutside?: boolean;\n} & Omit<React.HTMLAttributes<HTMLDivElement>, 'title' | 'aria-label'>;\n\nexport const Modal = ({\n children,\n closeLabel = 'Lukk',\n initialFocusRef,\n open,\n onDismiss,\n size,\n align = 'start',\n title,\n closeOnClickOutside = true,\n showCloseButton = true,\n 'aria-label': ariaLabel,\n ...rest\n}: ModalProps) => {\n const randomId = useId();\n const Heading: React.ElementType = headingsMap[size] || Heading2;\n\n return (\n <ModalOverlay\n open={open}\n onDismiss={onDismiss}\n closeOnClickOutside={closeOnClickOutside}\n initialFocusRef={initialFocusRef}\n className={`eds-modal__overlay--size-${size}`}\n aria-labelledby={title ? randomId : undefined}\n aria-label={!title ? ariaLabel : undefined}\n >\n {showCloseButton && (\n <IconButton\n className=\"eds-modal__close\"\n aria-label={closeLabel}\n onClick={onDismiss}\n type=\"button\"\n >\n <CloseIcon />\n </IconButton>\n )}\n <ModalContent size={size} align={align} {...rest}>\n {title && (\n <Heading\n margin=\"bottom\"\n as=\"h2\"\n id={randomId}\n tabIndex={-1}\n data-autofocus={initialFocusRef ? undefined : ''}\n >\n {title}\n </Heading>\n )}\n {children}\n </ModalContent>\n </ModalOverlay>\n );\n};\n","import React, { useEffect, useId, useRef } from 'react';\nimport classNames from 'classnames';\nimport { Contrast } from '@entur/layout';\nimport { CloseIcon } from '@entur/icons';\nimport { Heading3 } from '@entur/typography';\nimport { IconButton } from '@entur/button';\nimport { ConditionalWrapper } from '@entur/utils';\nimport { ModalOverlay } from './ModalOverlay';\n\nimport './Drawer.scss';\n\nexport type DrawerProps = {\n /** Innholdet. Typisk tekst, lenker eller knapper */\n children: React.ReactNode;\n /** Ekstra klassenavn */\n className?: string;\n /** Tekst som beskriver lukkeknappen for skjermlesere\n * @default 'Lukk skuff'\n */\n closeLabel?: string;\n /** Om draweren skal vises i mørk variant\n * @default false\n */\n contrast?: boolean;\n /** Callback som kalles når brukeren ønsker å lukke draweren */\n onDismiss: () => void;\n /** Om draweren er åpen eller ikke\n * @default true\n */\n open?: boolean;\n /** Tittel på toppen av draweren */\n title: string;\n /** Styling som sendes til Drawer */\n style?: React.CSSProperties;\n /** Legger på et overlay over resten av siden */\n overlay?: boolean;\n};\n\nexport const Drawer = ({\n children,\n className,\n closeLabel = 'Lukk skuff',\n contrast = false,\n open = true,\n onDismiss,\n title,\n style,\n overlay = false,\n}: DrawerProps) => {\n const titleId = useId();\n const drawerRef = useRef<HTMLDivElement>(null);\n const previouslyFocusedRef = useRef<Element | null>(null);\n\n useEffect(\n function handleFocus() {\n if (!open || overlay) return;\n previouslyFocusedRef.current = document.activeElement;\n drawerRef.current\n ?.querySelector<HTMLElement>('[data-autofocus]')\n ?.focus();\n\n return () => {\n if (previouslyFocusedRef.current instanceof HTMLElement) {\n previouslyFocusedRef.current.focus();\n }\n };\n },\n [open, overlay],\n );\n\n const shouldRemoveFromDOM = !open && !overlay;\n if (shouldRemoveFromDOM) return null;\n\n function handleKeyDown(e: React.KeyboardEvent) {\n if (e.key === 'Escape') {\n e.stopPropagation();\n onDismiss();\n }\n }\n\n const Wrapper = contrast ? Contrast : 'div';\n const nonModalDialogProps = {\n role: 'dialog' as const,\n 'aria-modal': false,\n 'aria-labelledby': titleId,\n };\n\n return (\n <ConditionalWrapper\n condition={overlay}\n wrapper={(children: React.ReactNode) => (\n <ModalOverlay\n open={open}\n onDismiss={onDismiss}\n aria-labelledby={titleId}\n >\n {children}\n </ModalOverlay>\n )}\n >\n <Wrapper\n ref={drawerRef}\n {...(!overlay && nonModalDialogProps)}\n className={classNames('eds-drawer', className)}\n onKeyDown={handleKeyDown}\n style={style}\n >\n <IconButton\n className=\"eds-drawer__close-button\"\n onClick={onDismiss}\n type=\"button\"\n aria-label={closeLabel}\n >\n <CloseIcon aria-hidden />\n </IconButton>\n <div className=\"eds-drawer__content\">\n <Heading3 as=\"h2\" id={titleId} tabIndex={-1} data-autofocus=\"\">\n {title}\n </Heading3>\n {children}\n </div>\n </Wrapper>\n </ConditionalWrapper>\n );\n};\n","import { warnAboutMissingStyles } from '@entur/utils';\nimport './index.scss';\n\nwarnAboutMissingStyles('modal', 'icons', 'typography', 'a11y', 'button');\n\nexport * from './Modal';\nexport * from './ModalOverlay';\nexport * from './ModalContent';\nexport * from './Drawer';\n"],"names":["children"],"mappings":"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"}
1
+ {"version":3,"file":"modal.mjs","sources":["../src/ModalOverlay.tsx","../src/ModalContent.tsx","../src/Modal.tsx","../src/Drawer.tsx","../src/index.tsx"],"sourcesContent":["import React, { useCallback, useEffect, useRef, useState } from 'react';\nimport { createPortal } from 'react-dom';\nimport classNames from 'classnames';\nimport { ContrastContext } from '@entur/layout';\n\nexport type ModalOverlayProps = {\n /** Flagg som sier om modalen er åpen */\n open?: boolean;\n /** Callback som kalles når brukeren ber om å lukke modalen */\n onDismiss?: () => void;\n /** Innholdet i modalen */\n children: React.ReactNode;\n /** Ekstra klassenavn */\n className?: string;\n /** En ref til elementet som skal være fokusert når modalen åpnes. Defaulter til lukkeknappen */\n initialFocusRef?: React.RefObject<HTMLElement>;\n /** Om modalen skal lukkes når man klikker utenfor den\n * @default true\n */\n closeOnClickOutside?: boolean;\n} & Omit<React.DialogHTMLAttributes<HTMLDialogElement>, 'open' | 'onClick'>;\n\nexport const ModalOverlay = ({\n className,\n open,\n onDismiss,\n initialFocusRef,\n closeOnClickOutside = true,\n children,\n ...rest\n}: ModalOverlayProps) => {\n const dialogRef = useRef<HTMLDialogElement>(null);\n // The portal can only render after hydration — server and first client render\n // must match, and the server has no <dialog> to hydrate against.\n const [isMounted, setIsMounted] = useState(false);\n\n useEffect(() => setIsMounted(true), []);\n\n // Toggle native dialog open state. Always stays in DOM so the browser can\n // manage initial focus storage + restore on close.\n useEffect(() => {\n const dialog = dialogRef.current;\n if (!dialog) return;\n if (open && !dialog.open) {\n dialog.showModal();\n const target =\n initialFocusRef?.current ??\n dialog.querySelector<HTMLElement>('[data-autofocus]') ??\n dialog;\n target.focus();\n } else if (!open && dialog.open) {\n dialog.close();\n }\n }, [open, initialFocusRef, isMounted]);\n\n // Esc fires cancel — preventDefault so we route through onDismiss + state,\n // which triggers the effect above to call close() (native restore runs).\n useEffect(\n function syncCancelEvent() {\n const dialog = dialogRef.current;\n if (!dialog) return;\n const handleCancel = (e: Event) => {\n e.preventDefault();\n onDismiss?.();\n };\n dialog.addEventListener('cancel', handleCancel);\n return () => dialog.removeEventListener('cancel', handleCancel);\n },\n [onDismiss, isMounted],\n );\n\n const handleClick = useCallback(\n (e: React.MouseEvent<HTMLDialogElement>) => {\n if (!closeOnClickOutside) return;\n if (e.target === dialogRef.current) onDismiss?.();\n },\n [onDismiss, closeOnClickOutside],\n );\n\n if (!isMounted) return null;\n\n return createPortal(\n <ContrastContext.Provider value={false}>\n <dialog\n ref={dialogRef}\n className={classNames('eds-modal__overlay', className)}\n {...rest}\n onClick={handleClick}\n >\n {open && children}\n </dialog>\n </ContrastContext.Provider>,\n document.body,\n );\n};\n","import React, { useId } from 'react';\nimport classNames from 'classnames';\nimport { Heading4, Heading3, Heading2 } from '@entur/typography';\n\nexport type ModalContentProps = {\n /** Innholdet i modalen */\n children: React.ReactNode;\n /** Ekstra klassenavn */\n className?: string;\n /** Størrelsen på modalen */\n size: 'extraSmall' | 'small' | 'medium' | 'large' | 'extraLarge';\n /** Tittelen som vises i modalen */\n title?: React.ReactNode;\n /** Hvordan innholdet skal plasseres i modalen\n * @default 'start'\n */\n align?: 'start' | 'center' | 'end';\n} & Omit<React.HTMLAttributes<HTMLDivElement>, 'title'>;\n\nexport const headingsMap = {\n extraSmall: Heading4,\n small: Heading3,\n medium: Heading2,\n large: Heading2,\n extraLarge: Heading2,\n};\n\nexport const ModalContent = ({\n children,\n className,\n size,\n title,\n align = 'start',\n ...rest\n}: ModalContentProps) => {\n const Heading: React.ElementType = headingsMap[size] || Heading2;\n const randomId = useId();\n return (\n <div\n className={classNames(\n 'eds-modal__content',\n `eds-modal__content--size-${size}`,\n `eds-modal__content--align-${align}`,\n className,\n )}\n aria-labelledby={title ? randomId : undefined}\n {...rest}\n >\n {title && (\n <Heading margin=\"bottom\" as=\"h2\" id={randomId}>\n {title}\n </Heading>\n )}\n {children}\n </div>\n );\n};\n","import React, { useId } from 'react';\n\nimport { CloseIcon } from '@entur/icons';\nimport { IconButton } from '@entur/button';\nimport { Heading2 } from '@entur/typography';\n\nimport { ModalOverlay } from './ModalOverlay';\nimport { ModalContent, headingsMap } from './ModalContent';\n\nimport './Modal.scss';\n\nexport type ModalProps = {\n /** Innholdet i modalen */\n children: React.ReactNode;\n /** Skjermleser-label til lukk-knappen */\n closeLabel?: string;\n /** En ref til elementet som skal være fokusert når modalen åpnes. Defaulter til tittelen, ellers første interaktive element */\n initialFocusRef?: React.RefObject<HTMLElement>;\n /** Styrer om modalen er synlig */\n open?: boolean;\n /** Callback som kalles når brukeren ber om å lukke modalen. Påkrevd: Esc og klikk utenfor lukker alltid (nettleserens innebygde atferd for native dialog) */\n onDismiss: () => void;\n /** Om lukkeknappen skal vises i øvre høyre hjørne\n * @default true\n */\n showCloseButton?: boolean;\n /** Størrelsen på modalen */\n size: 'extraSmall' | 'small' | 'medium' | 'large' | 'extraLarge';\n /** Hvordan innholdet skal plasseres i modalen\n * @default 'start'\n */\n align?: 'start' | 'center' | 'end';\n /** Tittelen som vises i modalen */\n title?: React.ReactNode;\n /** Tilgjengelig navn for modalen når title ikke er satt */\n 'aria-label'?: string;\n /** Om modalen skal lukkes når man klikker på utsiden av den\n * @default true\n */\n closeOnClickOutside?: boolean;\n} & Omit<React.HTMLAttributes<HTMLDivElement>, 'title' | 'aria-label'>;\n\nexport const Modal = ({\n children,\n closeLabel = 'Lukk',\n initialFocusRef,\n open,\n onDismiss,\n size,\n align = 'start',\n title,\n closeOnClickOutside = true,\n showCloseButton = true,\n 'aria-label': ariaLabel,\n ...rest\n}: ModalProps) => {\n const randomId = useId();\n const Heading: React.ElementType = headingsMap[size] || Heading2;\n\n return (\n <ModalOverlay\n open={open}\n onDismiss={onDismiss}\n closeOnClickOutside={closeOnClickOutside}\n initialFocusRef={initialFocusRef}\n className={`eds-modal__overlay--size-${size}`}\n aria-labelledby={title ? randomId : undefined}\n aria-label={!title ? ariaLabel : undefined}\n >\n {showCloseButton && (\n <IconButton\n className=\"eds-modal__close\"\n aria-label={closeLabel}\n onClick={onDismiss}\n type=\"button\"\n >\n <CloseIcon />\n </IconButton>\n )}\n <ModalContent size={size} align={align} {...rest}>\n {title && (\n <Heading\n margin=\"bottom\"\n as=\"h2\"\n id={randomId}\n tabIndex={-1}\n data-autofocus={initialFocusRef ? undefined : ''}\n >\n {title}\n </Heading>\n )}\n {children}\n </ModalContent>\n </ModalOverlay>\n );\n};\n","import React, { useEffect, useId, useRef } from 'react';\nimport classNames from 'classnames';\nimport { Contrast } from '@entur/layout';\nimport { CloseIcon } from '@entur/icons';\nimport { Heading3 } from '@entur/typography';\nimport { IconButton } from '@entur/button';\nimport { ConditionalWrapper, getActiveElement } from '@entur/utils';\nimport { ModalOverlay } from './ModalOverlay';\n\nimport './Drawer.scss';\n\nexport type DrawerProps = {\n /** Innholdet. Typisk tekst, lenker eller knapper */\n children: React.ReactNode;\n /** Ekstra klassenavn */\n className?: string;\n /** Tekst som beskriver lukkeknappen for skjermlesere\n * @default 'Lukk skuff'\n */\n closeLabel?: string;\n /** Om draweren skal vises i mørk variant\n * @default false\n */\n contrast?: boolean;\n /** Callback som kalles når brukeren ønsker å lukke draweren */\n onDismiss: () => void;\n /** Om draweren er åpen eller ikke\n * @default true\n */\n open?: boolean;\n /** Tittel på toppen av draweren */\n title: string;\n /** Styling som sendes til Drawer */\n style?: React.CSSProperties;\n /** Legger på et overlay over resten av siden */\n overlay?: boolean;\n};\n\nexport const Drawer = ({\n children,\n className,\n closeLabel = 'Lukk skuff',\n contrast = false,\n open = true,\n onDismiss,\n title,\n style,\n overlay = false,\n}: DrawerProps) => {\n const titleId = useId();\n const drawerRef = useRef<HTMLDivElement>(null);\n const previouslyFocusedRef = useRef<Element | null>(null);\n\n useEffect(\n function handleFocus() {\n if (!open || overlay) return;\n previouslyFocusedRef.current = getActiveElement();\n drawerRef.current\n ?.querySelector<HTMLElement>('[data-autofocus]')\n ?.focus();\n\n return () => {\n if (previouslyFocusedRef.current instanceof HTMLElement) {\n previouslyFocusedRef.current.focus();\n }\n };\n },\n [open, overlay],\n );\n\n const shouldRemoveFromDOM = !open && !overlay;\n if (shouldRemoveFromDOM) return null;\n\n function handleKeyDown(e: React.KeyboardEvent) {\n if (e.key === 'Escape') {\n e.stopPropagation();\n onDismiss();\n }\n }\n\n const Wrapper = contrast ? Contrast : 'div';\n const nonModalDialogProps = {\n role: 'dialog' as const,\n 'aria-modal': false,\n 'aria-labelledby': titleId,\n };\n\n return (\n <ConditionalWrapper\n condition={overlay}\n wrapper={(children: React.ReactNode) => (\n <ModalOverlay\n open={open}\n onDismiss={onDismiss}\n aria-labelledby={titleId}\n >\n {children}\n </ModalOverlay>\n )}\n >\n <Wrapper\n ref={drawerRef}\n {...(!overlay && nonModalDialogProps)}\n className={classNames('eds-drawer', className)}\n onKeyDown={handleKeyDown}\n style={style}\n >\n <IconButton\n className=\"eds-drawer__close-button\"\n onClick={onDismiss}\n type=\"button\"\n aria-label={closeLabel}\n >\n <CloseIcon aria-hidden />\n </IconButton>\n <div className=\"eds-drawer__content\">\n <Heading3 as=\"h2\" id={titleId} tabIndex={-1} data-autofocus=\"\">\n {title}\n </Heading3>\n {children}\n </div>\n </Wrapper>\n </ConditionalWrapper>\n );\n};\n","import { warnAboutMissingStyles } from '@entur/utils';\nimport './index.scss';\n\nwarnAboutMissingStyles('modal', 'icons', 'typography', 'a11y', 'button');\n\nexport * from './Modal';\nexport * from './ModalOverlay';\nexport * from './ModalContent';\nexport * from './Drawer';\n"],"names":["children"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@entur/modal",
3
- "version": "2.0.1",
3
+ "version": "2.0.2",
4
4
  "license": "EUPL-1.2",
5
5
  "main": "dist/modal.cjs",
6
6
  "module": "dist/modal.mjs",
@@ -26,13 +26,13 @@
26
26
  "react-dom": ">=18.0.0"
27
27
  },
28
28
  "dependencies": {
29
- "@entur/a11y": "^0.3.1",
30
- "@entur/button": "^5.0.1",
29
+ "@entur/a11y": "^0.3.2",
30
+ "@entur/button": "^5.0.2",
31
31
  "@entur/icons": "^10.0.1",
32
- "@entur/layout": "^4.0.1",
32
+ "@entur/layout": "^4.0.2",
33
33
  "@entur/tokens": "^4.0.1",
34
- "@entur/typography": "^3.0.1",
35
- "@entur/utils": "^0.14.1",
34
+ "@entur/typography": "^3.0.2",
35
+ "@entur/utils": "^0.15.0",
36
36
  "classnames": "^2.5.1"
37
37
  },
38
38
  "devDependencies": {
@@ -58,5 +58,5 @@
58
58
  "./dist/styles.css": "./dist/styles.css",
59
59
  "./package.json": "./package.json"
60
60
  },
61
- "gitHead": "e00299fc4e221064f31533990a2ed39be56443cc"
61
+ "gitHead": "ed2856b4a001b0101f6038c4a38f1ad7fc4d1fe2"
62
62
  }