@gnome-ui/react 1.45.2 → 1.46.0

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.
Files changed (31) hide show
  1. package/dist/components/AboutDialog/AboutDialog.cjs +1 -1
  2. package/dist/components/AboutDialog/AboutDialog.cjs.map +1 -1
  3. package/dist/components/AboutDialog/AboutDialog.js +105 -106
  4. package/dist/components/AboutDialog/AboutDialog.js.map +1 -1
  5. package/dist/components/BottomSheet/BottomSheet.cjs +1 -1
  6. package/dist/components/BottomSheet/BottomSheet.cjs.map +1 -1
  7. package/dist/components/BottomSheet/BottomSheet.js +36 -36
  8. package/dist/components/BottomSheet/BottomSheet.js.map +1 -1
  9. package/dist/components/Dialog/Dialog.cjs +1 -1
  10. package/dist/components/Dialog/Dialog.cjs.map +1 -1
  11. package/dist/components/Dialog/Dialog.js +50 -51
  12. package/dist/components/Dialog/Dialog.js.map +1 -1
  13. package/dist/components/Dialog/dialogUtils.cjs +1 -1
  14. package/dist/components/Dialog/dialogUtils.cjs.map +1 -1
  15. package/dist/components/Dialog/dialogUtils.d.ts +6 -0
  16. package/dist/components/Dialog/dialogUtils.js +11 -2
  17. package/dist/components/Dialog/dialogUtils.js.map +1 -1
  18. package/dist/components/Dropdown/Dropdown.cjs +1 -1
  19. package/dist/components/Dropdown/Dropdown.cjs.map +1 -1
  20. package/dist/components/Dropdown/Dropdown.js +88 -84
  21. package/dist/components/Dropdown/Dropdown.js.map +1 -1
  22. package/dist/components/PasswordEntryRow/PasswordEntryRow.cjs +1 -1
  23. package/dist/components/PasswordEntryRow/PasswordEntryRow.cjs.map +1 -1
  24. package/dist/components/PasswordEntryRow/PasswordEntryRow.d.ts +1 -1
  25. package/dist/components/PasswordEntryRow/PasswordEntryRow.js +10 -9
  26. package/dist/components/PasswordEntryRow/PasswordEntryRow.js.map +1 -1
  27. package/dist/components/ShortcutsDialog/ShortcutsDialog.cjs +1 -1
  28. package/dist/components/ShortcutsDialog/ShortcutsDialog.cjs.map +1 -1
  29. package/dist/components/ShortcutsDialog/ShortcutsDialog.js +74 -74
  30. package/dist/components/ShortcutsDialog/ShortcutsDialog.js.map +1 -1
  31. package/package.json +1 -1
@@ -1,2 +1,2 @@
1
- const e=require("./dialogUtils.cjs"),t=require("./Dialog.module.css.cjs");let n=require("react"),r=require("react-dom"),i=require("react/jsx-runtime");var a=({open:a,title:o,children:s,buttons:c=[],onClose:l,closeOnBackdrop:u=!0,role:d=`dialog`,responses:f,onResponse:p,className:m,...h})=>{let g=(0,n.useRef)(null),_=(0,n.useId)(),v=(0,n.useRef)(null),y=!!f,b=e.useVisualViewport();(0,n.useEffect)(()=>{a?(v.current=document.activeElement,(g.current?.querySelector(e.FOCUSABLE))?.focus()):v.current?.focus()},[a]);let x=(0,n.useCallback)(()=>{let e=f?.find(e=>e.variant!==`destructive`&&!e.disabled);e&&p?.(e.id)},[f,p]),S=y?x:u?l:void 0,C=(0,n.useCallback)(t=>{if(t.key===`Escape`){t.preventDefault(),y?x():l?.();return}e.trapFocus(t,g)},[y,x,l]);if(!a)return null;let w=()=>y&&f?(0,i.jsx)(`div`,{className:t.default.footer,children:f.map(e=>(0,i.jsx)(`button`,{type:`button`,disabled:e.disabled,className:[t.default.btn,t.default[`btn-${e.variant??`default`}`]].filter(Boolean).join(` `),onClick:()=>p?.(e.id),children:e.label},e.id))}):c.length>0?(0,i.jsx)(`div`,{className:t.default.footer,children:c.map(e=>(0,i.jsx)(`button`,{type:`button`,disabled:e.disabled,className:[t.default.btn,t.default[`btn-${e.variant??`default`}`]].filter(Boolean).join(` `),onClick:e.onClick,children:e.label},e.label))}):null,T=(0,i.jsx)(`div`,{className:t.default.backdrop,style:b,onClick:S,"aria-hidden":`true`,children:(0,i.jsxs)(`div`,{ref:g,role:d,"aria-modal":`true`,"aria-labelledby":_,className:[t.default.dialog,m].filter(Boolean).join(` `),onKeyDown:C,onClick:e=>e.stopPropagation(),...h,children:[o&&(0,i.jsx)(`div`,{id:_,className:t.default.title,children:o}),s&&(0,i.jsx)(`div`,{className:t.default.body,children:s}),w()]})});return typeof document>`u`?T:(0,r.createPortal)(T,document.body)};exports.Dialog=a;
1
+ const e=require("./dialogUtils.cjs"),t=require("./Dialog.module.css.cjs");let n=require("react"),r=require("react-dom"),i=require("react/jsx-runtime");var a=({open:a,title:o,children:s,buttons:c=[],onClose:l,closeOnBackdrop:u=!0,role:d=`dialog`,responses:f,onResponse:p,className:m,...h})=>{let g=(0,n.useRef)(null),_=(0,n.useId)(),v=(0,n.useRef)(null),y=!!f,b=e.useVisualViewport();e.useBodyScrollLock(a),(0,n.useEffect)(()=>{a?(v.current=document.activeElement,(g.current?.querySelector(e.FOCUSABLE))?.focus()):v.current?.focus()},[a]);let x=(0,n.useCallback)(()=>{let e=f?.find(e=>e.variant!==`destructive`&&!e.disabled);e&&p?.(e.id)},[f,p]),S=y?x:u?l:void 0,C=(0,n.useCallback)(t=>{if(t.key===`Escape`){t.preventDefault(),y?x():l?.();return}e.trapFocus(t,g)},[y,x,l]);if(!a)return null;let w=()=>y&&f?(0,i.jsx)(`div`,{className:t.default.footer,children:f.map(e=>(0,i.jsx)(`button`,{type:`button`,disabled:e.disabled,className:[t.default.btn,t.default[`btn-${e.variant??`default`}`]].filter(Boolean).join(` `),onClick:()=>p?.(e.id),children:e.label},e.id))}):c.length>0?(0,i.jsx)(`div`,{className:t.default.footer,children:c.map(e=>(0,i.jsx)(`button`,{type:`button`,disabled:e.disabled,className:[t.default.btn,t.default[`btn-${e.variant??`default`}`]].filter(Boolean).join(` `),onClick:e.onClick,children:e.label},e.label))}):null,T=(0,i.jsx)(`div`,{className:t.default.backdrop,style:b,onClick:S,children:(0,i.jsxs)(`div`,{ref:g,role:d,"aria-modal":`true`,"aria-labelledby":o?_:void 0,className:[t.default.dialog,m].filter(Boolean).join(` `),onKeyDown:C,onClick:e=>e.stopPropagation(),...h,children:[o&&(0,i.jsx)(`div`,{id:_,className:t.default.title,children:o}),s&&(0,i.jsx)(`div`,{className:t.default.body,children:s}),w()]})});return typeof document>`u`?T:(0,r.createPortal)(T,document.body)};exports.Dialog=a;
2
2
  //# sourceMappingURL=Dialog.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"Dialog.cjs","names":[],"sources":["../../../src/components/Dialog/Dialog.tsx"],"sourcesContent":["import {\n type HTMLAttributes,\n type KeyboardEvent,\n type ReactNode,\n useCallback,\n useEffect,\n useId,\n useRef,\n} from 'react';\n\nimport { createPortal } from 'react-dom';\n\nimport styles from './Dialog.module.css';\nimport { FOCUSABLE, trapFocus, useVisualViewport } from './dialogUtils';\n\n// ─── AlertDialog types ────────────────────────────────────────────────────────\n\nexport type AlertDialogResponseVariant = 'default' | 'suggested' | 'destructive';\n\nexport interface AlertDialogResponse {\n /**\n * Unique identifier returned via `onResponse`.\n * Use `\"cancel\"` by convention for the dismissive action.\n */\n id: string;\n /** Button label. */\n label: string;\n /** Visual emphasis. Defaults to `\"default\"`. */\n variant?: AlertDialogResponseVariant;\n /** Disables the button. */\n disabled?: boolean;\n}\n\n// ─── Dialog ───────────────────────────────────────────────────────────────────\n\nexport interface DialogButton {\n /** Button label. */\n label: string;\n /** Visual variant. Defaults to `\"default\"`. */\n variant?: 'default' | 'suggested' | 'destructive';\n /** Called when the button is clicked. */\n onClick: () => void;\n /** Whether the button is disabled. */\n disabled?: boolean;\n}\n\nexport interface DialogProps extends Omit<HTMLAttributes<HTMLDivElement>, 'title'> {\n /** Whether the dialog is visible. */\n open: boolean;\n\n // ── Standard dialog ──────────────────────────────────────────────────────\n\n /** Dialog heading. */\n title?: ReactNode;\n /** Body content. */\n children?: ReactNode;\n /** Action buttons (standard dialog API). */\n buttons?: DialogButton[];\n /** Called on Escape or backdrop click. */\n onClose?: () => void;\n /** Whether clicking the backdrop closes the dialog. Defaults to `true`. */\n closeOnBackdrop?: boolean;\n\n // ── AlertDialog extension ─────────────────────────────────────────────────\n\n /**\n * ARIA role. Use `\"alertdialog\"` for confirmations and destructive warnings —\n * screen readers announce it immediately. Defaults to `\"dialog\"`.\n *\n * When using `role=\"alertdialog\"`, prefer the `responses`/`onResponse` API\n * over `buttons`/`onClick` for semantic clarity.\n */\n role?: 'dialog' | 'alertdialog';\n\n /**\n * Response buttons (AlertDialog API). Alternative to `buttons` — each\n * response has a semantic `id` returned via `onResponse`.\n * Escape and backdrop click fire the first non-destructive response.\n */\n responses?: AlertDialogResponse[];\n\n /**\n * Called with the `id` of the response button clicked.\n * Required when `responses` is provided.\n */\n onResponse?: (id: string) => void;\n}\n\n/**\n * Blocking modal dialog following the Adwaita pattern.\n *\n * **Standard** — `title` + `children` + `buttons[]`.\n *\n * **Alert** — add `role=\"alertdialog\"` + `responses[]` + `onResponse`.\n * Uses a semantic response id instead of per-button `onClick`.\n * Escape / backdrop fire the first non-destructive response.\n * Mirrors `AdwAlertDialog`.\n *\n * For the app-info dialog, use `<AboutDialog />` instead.\n *\n * @see https://gnome.pages.gitlab.gnome.org/libadwaita/doc/main/class.Dialog.html\n * @see https://gnome.pages.gitlab.gnome.org/libadwaita/doc/main/class.AlertDialog.html\n */\nexport const Dialog = ({\n open,\n title,\n children,\n buttons = [],\n onClose,\n closeOnBackdrop = true,\n role = 'dialog',\n responses,\n onResponse,\n className,\n ...props\n}: DialogProps) => {\n const dialogRef = useRef<HTMLDivElement>(null);\n const titleId = useId();\n const previouslyFocused = useRef<Element | null>(null);\n\n const isAlert = !!responses;\n const viewportStyle = useVisualViewport();\n\n // ── Focus management ───────────────────────────────────────────────────────\n useEffect(() => {\n if (open) {\n previouslyFocused.current = document.activeElement;\n const el = dialogRef.current?.querySelector<HTMLElement>(FOCUSABLE);\n\n el?.focus();\n } else {\n (previouslyFocused.current as HTMLElement | null)?.focus();\n }\n }, [open]);\n\n // ── Dismiss helpers ────────────────────────────────────────────────────────\n const dismissAlert = useCallback(() => {\n const cancel = responses?.find((r) => r.variant !== 'destructive' && !r.disabled);\n\n if (cancel) {\n onResponse?.(cancel.id);\n }\n }, [responses, onResponse]);\n\n const handleBackdrop = isAlert ? dismissAlert : closeOnBackdrop ? onClose : undefined;\n\n // ── Keyboard ───────────────────────────────────────────────────────────────\n const handleKeyDown = useCallback(\n (e: KeyboardEvent<HTMLDivElement>) => {\n if (e.key === 'Escape') {\n e.preventDefault();\n if (isAlert) {\n dismissAlert();\n } else {\n onClose?.();\n }\n\n return;\n }\n\n trapFocus(e, dialogRef);\n },\n [isAlert, dismissAlert, onClose],\n );\n\n if (!open) {\n return null;\n }\n\n // ── Render buttons (standard or alert) ────────────────────────────────────\n const renderButtons = () => {\n if (isAlert && responses) {\n return (\n <div className={styles.footer}>\n {responses.map((r) => (\n <button\n key={r.id}\n type=\"button\"\n disabled={r.disabled}\n className={[styles.btn, styles[`btn-${r.variant ?? 'default'}`]]\n .filter(Boolean)\n .join(' ')}\n onClick={() => onResponse?.(r.id)}\n >\n {r.label}\n </button>\n ))}\n </div>\n );\n }\n\n if (buttons.length > 0) {\n return (\n <div className={styles.footer}>\n {buttons.map((btn) => (\n <button\n key={btn.label}\n type=\"button\"\n disabled={btn.disabled}\n className={[styles.btn, styles[`btn-${btn.variant ?? 'default'}`]]\n .filter(Boolean)\n .join(' ')}\n onClick={btn.onClick}\n >\n {btn.label}\n </button>\n ))}\n </div>\n );\n }\n\n return null;\n };\n\n const node = (\n <div\n className={styles.backdrop}\n style={viewportStyle}\n onClick={handleBackdrop}\n aria-hidden=\"true\"\n >\n <div\n ref={dialogRef}\n role={role}\n aria-modal=\"true\"\n aria-labelledby={titleId}\n className={[styles.dialog, className].filter(Boolean).join(' ')}\n onKeyDown={handleKeyDown}\n onClick={(e) => e.stopPropagation()}\n {...props}\n >\n {title && (\n <div id={titleId} className={styles.title}>\n {title}\n </div>\n )}\n {children && <div className={styles.body}>{children}</div>}\n {renderButtons()}\n </div>\n </div>\n );\n\n if (typeof document === 'undefined') {\n return node;\n }\n\n return createPortal(node, document.body);\n};\n"],"mappings":"uJAuGA,IAAa,GAAU,CACrB,OACA,QACA,WACA,UAAU,CAAC,EACX,UACA,kBAAkB,GAClB,OAAO,SACP,YACA,aACA,YACA,GAAG,KACc,CACjB,IAAM,GAAA,EAAA,EAAA,QAAmC,IAAI,EACvC,GAAA,EAAA,EAAA,OAAgB,EAChB,GAAA,EAAA,EAAA,QAA2C,IAAI,EAE/C,EAAU,CAAC,CAAC,EACZ,EAAgB,EAAA,kBAAkB,GAGxC,EAAA,EAAA,eAAgB,CACV,GACF,EAAkB,QAAU,SAAS,eAC1B,EAAU,SAAS,cAA2B,EAAA,SAAS,IAE9D,MAAM,GAEV,EAAmB,SAAgC,MAAM,CAE7D,EAAG,CAAC,CAAI,CAAC,EAGT,IAAM,GAAA,EAAA,EAAA,iBAAiC,CACrC,IAAM,EAAS,GAAW,KAAM,GAAM,EAAE,UAAY,eAAiB,CAAC,EAAE,QAAQ,EAE5E,GACF,IAAa,EAAO,EAAE,CAE1B,EAAG,CAAC,EAAW,CAAU,CAAC,EAEpB,EAAiB,EAAU,EAAe,EAAkB,EAAU,IAAA,GAGtE,GAAA,EAAA,EAAA,aACH,GAAqC,CACpC,GAAI,EAAE,MAAQ,SAAU,CACtB,EAAE,eAAe,EACb,EACF,EAAa,EAEb,IAAU,EAGZ,MACF,CAEA,EAAA,UAAU,EAAG,CAAS,CACxB,EACA,CAAC,EAAS,EAAc,CAAO,CACjC,EAEA,GAAI,CAAC,EACH,OAAO,KAIT,IAAM,MACA,GAAW,GAEX,EAAA,EAAA,KAAC,MAAD,CAAK,UAAW,EAAA,QAAO,gBACpB,EAAU,IAAK,IACd,EAAA,EAAA,KAAC,SAAD,CAEE,KAAK,SACL,SAAU,EAAE,SACZ,UAAW,CAAC,EAAA,QAAO,IAAK,EAAA,QAAO,OAAO,EAAE,SAAW,YAAY,EAC5D,OAAO,OAAO,EACd,KAAK,GAAG,EACX,YAAe,IAAa,EAAE,EAAE,WAE/B,EAAE,KACG,EATD,EAAE,EASD,CACT,CACE,CAAA,EAIL,EAAQ,OAAS,GAEjB,EAAA,EAAA,KAAC,MAAD,CAAK,UAAW,EAAA,QAAO,gBACpB,EAAQ,IAAK,IACZ,EAAA,EAAA,KAAC,SAAD,CAEE,KAAK,SACL,SAAU,EAAI,SACd,UAAW,CAAC,EAAA,QAAO,IAAK,EAAA,QAAO,OAAO,EAAI,SAAW,YAAY,EAC9D,OAAO,OAAO,EACd,KAAK,GAAG,EACX,QAAS,EAAI,iBAEZ,EAAI,KACC,EATD,EAAI,KASH,CACT,CACE,CAAA,EAIF,KAGH,GACJ,EAAA,EAAA,KAAC,MAAD,CACE,UAAW,EAAA,QAAO,SAClB,MAAO,EACP,QAAS,EACT,cAAY,iBAEZ,EAAA,EAAA,MAAC,MAAD,CACE,IAAK,EACC,OACN,aAAW,OACX,kBAAiB,EACjB,UAAW,CAAC,EAAA,QAAO,OAAQ,CAAS,EAAE,OAAO,OAAO,EAAE,KAAK,GAAG,EAC9D,UAAW,EACX,QAAU,GAAM,EAAE,gBAAgB,EAClC,GAAI,WARN,CAUG,IACC,EAAA,EAAA,KAAC,MAAD,CAAK,GAAI,EAAS,UAAW,EAAA,QAAO,eACjC,CACE,CAAA,EAEN,IAAY,EAAA,EAAA,KAAC,MAAD,CAAK,UAAW,EAAA,QAAO,KAAO,UAAc,CAAA,EACxD,EAAc,CACZ,GACF,CAAA,EAOP,OAJI,OAAO,SAAa,IACf,GAGT,EAAA,EAAA,cAAoB,EAAM,SAAS,IAAI,CACzC"}
1
+ {"version":3,"file":"Dialog.cjs","names":[],"sources":["../../../src/components/Dialog/Dialog.tsx"],"sourcesContent":["import {\n type HTMLAttributes,\n type KeyboardEvent,\n type ReactNode,\n useCallback,\n useEffect,\n useId,\n useRef,\n} from 'react';\n\nimport { createPortal } from 'react-dom';\n\nimport styles from './Dialog.module.css';\nimport { FOCUSABLE, trapFocus, useBodyScrollLock, useVisualViewport } from './dialogUtils';\n\n// ─── AlertDialog types ────────────────────────────────────────────────────────\n\nexport type AlertDialogResponseVariant = 'default' | 'suggested' | 'destructive';\n\nexport interface AlertDialogResponse {\n /**\n * Unique identifier returned via `onResponse`.\n * Use `\"cancel\"` by convention for the dismissive action.\n */\n id: string;\n /** Button label. */\n label: string;\n /** Visual emphasis. Defaults to `\"default\"`. */\n variant?: AlertDialogResponseVariant;\n /** Disables the button. */\n disabled?: boolean;\n}\n\n// ─── Dialog ───────────────────────────────────────────────────────────────────\n\nexport interface DialogButton {\n /** Button label. */\n label: string;\n /** Visual variant. Defaults to `\"default\"`. */\n variant?: 'default' | 'suggested' | 'destructive';\n /** Called when the button is clicked. */\n onClick: () => void;\n /** Whether the button is disabled. */\n disabled?: boolean;\n}\n\nexport interface DialogProps extends Omit<HTMLAttributes<HTMLDivElement>, 'title'> {\n /** Whether the dialog is visible. */\n open: boolean;\n\n // ── Standard dialog ──────────────────────────────────────────────────────\n\n /** Dialog heading. */\n title?: ReactNode;\n /** Body content. */\n children?: ReactNode;\n /** Action buttons (standard dialog API). */\n buttons?: DialogButton[];\n /** Called on Escape or backdrop click. */\n onClose?: () => void;\n /** Whether clicking the backdrop closes the dialog. Defaults to `true`. */\n closeOnBackdrop?: boolean;\n\n // ── AlertDialog extension ─────────────────────────────────────────────────\n\n /**\n * ARIA role. Use `\"alertdialog\"` for confirmations and destructive warnings —\n * screen readers announce it immediately. Defaults to `\"dialog\"`.\n *\n * When using `role=\"alertdialog\"`, prefer the `responses`/`onResponse` API\n * over `buttons`/`onClick` for semantic clarity.\n */\n role?: 'dialog' | 'alertdialog';\n\n /**\n * Response buttons (AlertDialog API). Alternative to `buttons` — each\n * response has a semantic `id` returned via `onResponse`.\n * Escape and backdrop click fire the first non-destructive response.\n */\n responses?: AlertDialogResponse[];\n\n /**\n * Called with the `id` of the response button clicked.\n * Required when `responses` is provided.\n */\n onResponse?: (id: string) => void;\n}\n\n/**\n * Blocking modal dialog following the Adwaita pattern.\n *\n * **Standard** — `title` + `children` + `buttons[]`.\n *\n * **Alert** — add `role=\"alertdialog\"` + `responses[]` + `onResponse`.\n * Uses a semantic response id instead of per-button `onClick`.\n * Escape / backdrop fire the first non-destructive response.\n * Mirrors `AdwAlertDialog`.\n *\n * For the app-info dialog, use `<AboutDialog />` instead.\n *\n * @see https://gnome.pages.gitlab.gnome.org/libadwaita/doc/main/class.Dialog.html\n * @see https://gnome.pages.gitlab.gnome.org/libadwaita/doc/main/class.AlertDialog.html\n */\nexport const Dialog = ({\n open,\n title,\n children,\n buttons = [],\n onClose,\n closeOnBackdrop = true,\n role = 'dialog',\n responses,\n onResponse,\n className,\n ...props\n}: DialogProps) => {\n const dialogRef = useRef<HTMLDivElement>(null);\n const titleId = useId();\n const previouslyFocused = useRef<Element | null>(null);\n\n const isAlert = !!responses;\n const viewportStyle = useVisualViewport();\n\n useBodyScrollLock(open);\n\n // ── Focus management ───────────────────────────────────────────────────────\n useEffect(() => {\n if (open) {\n previouslyFocused.current = document.activeElement;\n const el = dialogRef.current?.querySelector<HTMLElement>(FOCUSABLE);\n\n el?.focus();\n } else {\n (previouslyFocused.current as HTMLElement | null)?.focus();\n }\n }, [open]);\n\n // ── Dismiss helpers ────────────────────────────────────────────────────────\n const dismissAlert = useCallback(() => {\n const cancel = responses?.find((r) => r.variant !== 'destructive' && !r.disabled);\n\n if (cancel) {\n onResponse?.(cancel.id);\n }\n }, [responses, onResponse]);\n\n const handleBackdrop = isAlert ? dismissAlert : closeOnBackdrop ? onClose : undefined;\n\n // ── Keyboard ───────────────────────────────────────────────────────────────\n const handleKeyDown = useCallback(\n (e: KeyboardEvent<HTMLDivElement>) => {\n if (e.key === 'Escape') {\n e.preventDefault();\n if (isAlert) {\n dismissAlert();\n } else {\n onClose?.();\n }\n\n return;\n }\n\n trapFocus(e, dialogRef);\n },\n [isAlert, dismissAlert, onClose],\n );\n\n if (!open) {\n return null;\n }\n\n // ── Render buttons (standard or alert) ────────────────────────────────────\n const renderButtons = () => {\n if (isAlert && responses) {\n return (\n <div className={styles.footer}>\n {responses.map((r) => (\n <button\n key={r.id}\n type=\"button\"\n disabled={r.disabled}\n className={[styles.btn, styles[`btn-${r.variant ?? 'default'}`]]\n .filter(Boolean)\n .join(' ')}\n onClick={() => onResponse?.(r.id)}\n >\n {r.label}\n </button>\n ))}\n </div>\n );\n }\n\n if (buttons.length > 0) {\n return (\n <div className={styles.footer}>\n {buttons.map((btn) => (\n <button\n key={btn.label}\n type=\"button\"\n disabled={btn.disabled}\n className={[styles.btn, styles[`btn-${btn.variant ?? 'default'}`]]\n .filter(Boolean)\n .join(' ')}\n onClick={btn.onClick}\n >\n {btn.label}\n </button>\n ))}\n </div>\n );\n }\n\n return null;\n };\n\n const node = (\n <div className={styles.backdrop} style={viewportStyle} onClick={handleBackdrop}>\n <div\n ref={dialogRef}\n role={role}\n aria-modal=\"true\"\n aria-labelledby={title ? titleId : undefined}\n className={[styles.dialog, className].filter(Boolean).join(' ')}\n onKeyDown={handleKeyDown}\n onClick={(e) => e.stopPropagation()}\n {...props}\n >\n {title && (\n <div id={titleId} className={styles.title}>\n {title}\n </div>\n )}\n {children && <div className={styles.body}>{children}</div>}\n {renderButtons()}\n </div>\n </div>\n );\n\n if (typeof document === 'undefined') {\n return node;\n }\n\n return createPortal(node, document.body);\n};\n"],"mappings":"uJAuGA,IAAa,GAAU,CACrB,OACA,QACA,WACA,UAAU,CAAC,EACX,UACA,kBAAkB,GAClB,OAAO,SACP,YACA,aACA,YACA,GAAG,KACc,CACjB,IAAM,GAAA,EAAA,EAAA,QAAmC,IAAI,EACvC,GAAA,EAAA,EAAA,OAAgB,EAChB,GAAA,EAAA,EAAA,QAA2C,IAAI,EAE/C,EAAU,CAAC,CAAC,EACZ,EAAgB,EAAA,kBAAkB,EAExC,EAAA,kBAAkB,CAAI,GAGtB,EAAA,EAAA,eAAgB,CACV,GACF,EAAkB,QAAU,SAAS,eAC1B,EAAU,SAAS,cAA2B,EAAA,SAAS,IAE9D,MAAM,GAEV,EAAmB,SAAgC,MAAM,CAE7D,EAAG,CAAC,CAAI,CAAC,EAGT,IAAM,GAAA,EAAA,EAAA,iBAAiC,CACrC,IAAM,EAAS,GAAW,KAAM,GAAM,EAAE,UAAY,eAAiB,CAAC,EAAE,QAAQ,EAE5E,GACF,IAAa,EAAO,EAAE,CAE1B,EAAG,CAAC,EAAW,CAAU,CAAC,EAEpB,EAAiB,EAAU,EAAe,EAAkB,EAAU,IAAA,GAGtE,GAAA,EAAA,EAAA,aACH,GAAqC,CACpC,GAAI,EAAE,MAAQ,SAAU,CACtB,EAAE,eAAe,EACb,EACF,EAAa,EAEb,IAAU,EAGZ,MACF,CAEA,EAAA,UAAU,EAAG,CAAS,CACxB,EACA,CAAC,EAAS,EAAc,CAAO,CACjC,EAEA,GAAI,CAAC,EACH,OAAO,KAIT,IAAM,MACA,GAAW,GAEX,EAAA,EAAA,KAAC,MAAD,CAAK,UAAW,EAAA,QAAO,gBACpB,EAAU,IAAK,IACd,EAAA,EAAA,KAAC,SAAD,CAEE,KAAK,SACL,SAAU,EAAE,SACZ,UAAW,CAAC,EAAA,QAAO,IAAK,EAAA,QAAO,OAAO,EAAE,SAAW,YAAY,EAC5D,OAAO,OAAO,EACd,KAAK,GAAG,EACX,YAAe,IAAa,EAAE,EAAE,WAE/B,EAAE,KACG,EATD,EAAE,EASD,CACT,CACE,CAAA,EAIL,EAAQ,OAAS,GAEjB,EAAA,EAAA,KAAC,MAAD,CAAK,UAAW,EAAA,QAAO,gBACpB,EAAQ,IAAK,IACZ,EAAA,EAAA,KAAC,SAAD,CAEE,KAAK,SACL,SAAU,EAAI,SACd,UAAW,CAAC,EAAA,QAAO,IAAK,EAAA,QAAO,OAAO,EAAI,SAAW,YAAY,EAC9D,OAAO,OAAO,EACd,KAAK,GAAG,EACX,QAAS,EAAI,iBAEZ,EAAI,KACC,EATD,EAAI,KASH,CACT,CACE,CAAA,EAIF,KAGH,GACJ,EAAA,EAAA,KAAC,MAAD,CAAK,UAAW,EAAA,QAAO,SAAU,MAAO,EAAe,QAAS,YAC9D,EAAA,EAAA,MAAC,MAAD,CACE,IAAK,EACC,OACN,aAAW,OACX,kBAAiB,EAAQ,EAAU,IAAA,GACnC,UAAW,CAAC,EAAA,QAAO,OAAQ,CAAS,EAAE,OAAO,OAAO,EAAE,KAAK,GAAG,EAC9D,UAAW,EACX,QAAU,GAAM,EAAE,gBAAgB,EAClC,GAAI,WARN,CAUG,IACC,EAAA,EAAA,KAAC,MAAD,CAAK,GAAI,EAAS,UAAW,EAAA,QAAO,eACjC,CACE,CAAA,EAEN,IAAY,EAAA,EAAA,KAAC,MAAD,CAAK,UAAW,EAAA,QAAO,KAAO,UAAc,CAAA,EACxD,EAAc,CACZ,GACF,CAAA,EAOP,OAJI,OAAO,SAAa,IACf,GAGT,EAAA,EAAA,cAAoB,EAAM,SAAS,IAAI,CACzC"}
@@ -1,78 +1,77 @@
1
- import { FOCUSABLE as e, trapFocus as t, useVisualViewport as n } from "./dialogUtils.js";
2
- import r from "./Dialog.module.css.js";
3
- import { useCallback as i, useEffect as a, useId as o, useRef as s } from "react";
4
- import { createPortal as c } from "react-dom";
5
- import { jsx as l, jsxs as u } from "react/jsx-runtime";
1
+ import { FOCUSABLE as e, trapFocus as t, useBodyScrollLock as n, useVisualViewport as r } from "./dialogUtils.js";
2
+ import i from "./Dialog.module.css.js";
3
+ import { useCallback as a, useEffect as o, useId as s, useRef as c } from "react";
4
+ import { createPortal as l } from "react-dom";
5
+ import { jsx as u, jsxs as d } from "react/jsx-runtime";
6
6
  //#region src/components/Dialog/Dialog.tsx
7
- var d = ({ open: d, title: f, children: p, buttons: m = [], onClose: h, closeOnBackdrop: g = !0, role: _ = "dialog", responses: v, onResponse: y, className: b, ...x }) => {
8
- let S = s(null), C = o(), w = s(null), T = !!v, E = n();
9
- a(() => {
10
- d ? (w.current = document.activeElement, (S.current?.querySelector(e))?.focus()) : w.current?.focus();
11
- }, [d]);
12
- let D = i(() => {
13
- let e = v?.find((e) => e.variant !== "destructive" && !e.disabled);
14
- e && y?.(e.id);
15
- }, [v, y]), O = T ? D : g ? h : void 0, k = i((e) => {
7
+ var f = ({ open: f, title: p, children: m, buttons: h = [], onClose: g, closeOnBackdrop: _ = !0, role: v = "dialog", responses: y, onResponse: b, className: x, ...S }) => {
8
+ let C = c(null), w = s(), T = c(null), E = !!y, D = r();
9
+ n(f), o(() => {
10
+ f ? (T.current = document.activeElement, (C.current?.querySelector(e))?.focus()) : T.current?.focus();
11
+ }, [f]);
12
+ let O = a(() => {
13
+ let e = y?.find((e) => e.variant !== "destructive" && !e.disabled);
14
+ e && b?.(e.id);
15
+ }, [y, b]), k = E ? O : _ ? g : void 0, A = a((e) => {
16
16
  if (e.key === "Escape") {
17
- e.preventDefault(), T ? D() : h?.();
17
+ e.preventDefault(), E ? O() : g?.();
18
18
  return;
19
19
  }
20
- t(e, S);
20
+ t(e, C);
21
21
  }, [
22
- T,
23
- D,
24
- h
22
+ E,
23
+ O,
24
+ g
25
25
  ]);
26
- if (!d) return null;
27
- let A = () => T && v ? /* @__PURE__ */ l("div", {
28
- className: r.footer,
29
- children: v.map((e) => /* @__PURE__ */ l("button", {
26
+ if (!f) return null;
27
+ let j = () => E && y ? /* @__PURE__ */ u("div", {
28
+ className: i.footer,
29
+ children: y.map((e) => /* @__PURE__ */ u("button", {
30
30
  type: "button",
31
31
  disabled: e.disabled,
32
- className: [r.btn, r[`btn-${e.variant ?? "default"}`]].filter(Boolean).join(" "),
33
- onClick: () => y?.(e.id),
32
+ className: [i.btn, i[`btn-${e.variant ?? "default"}`]].filter(Boolean).join(" "),
33
+ onClick: () => b?.(e.id),
34
34
  children: e.label
35
35
  }, e.id))
36
- }) : m.length > 0 ? /* @__PURE__ */ l("div", {
37
- className: r.footer,
38
- children: m.map((e) => /* @__PURE__ */ l("button", {
36
+ }) : h.length > 0 ? /* @__PURE__ */ u("div", {
37
+ className: i.footer,
38
+ children: h.map((e) => /* @__PURE__ */ u("button", {
39
39
  type: "button",
40
40
  disabled: e.disabled,
41
- className: [r.btn, r[`btn-${e.variant ?? "default"}`]].filter(Boolean).join(" "),
41
+ className: [i.btn, i[`btn-${e.variant ?? "default"}`]].filter(Boolean).join(" "),
42
42
  onClick: e.onClick,
43
43
  children: e.label
44
44
  }, e.label))
45
- }) : null, j = /* @__PURE__ */ l("div", {
46
- className: r.backdrop,
47
- style: E,
48
- onClick: O,
49
- "aria-hidden": "true",
50
- children: /* @__PURE__ */ u("div", {
51
- ref: S,
52
- role: _,
45
+ }) : null, M = /* @__PURE__ */ u("div", {
46
+ className: i.backdrop,
47
+ style: D,
48
+ onClick: k,
49
+ children: /* @__PURE__ */ d("div", {
50
+ ref: C,
51
+ role: v,
53
52
  "aria-modal": "true",
54
- "aria-labelledby": C,
55
- className: [r.dialog, b].filter(Boolean).join(" "),
56
- onKeyDown: k,
53
+ "aria-labelledby": p ? w : void 0,
54
+ className: [i.dialog, x].filter(Boolean).join(" "),
55
+ onKeyDown: A,
57
56
  onClick: (e) => e.stopPropagation(),
58
- ...x,
57
+ ...S,
59
58
  children: [
60
- f && /* @__PURE__ */ l("div", {
61
- id: C,
62
- className: r.title,
63
- children: f
64
- }),
65
- p && /* @__PURE__ */ l("div", {
66
- className: r.body,
59
+ p && /* @__PURE__ */ u("div", {
60
+ id: w,
61
+ className: i.title,
67
62
  children: p
68
63
  }),
69
- A()
64
+ m && /* @__PURE__ */ u("div", {
65
+ className: i.body,
66
+ children: m
67
+ }),
68
+ j()
70
69
  ]
71
70
  })
72
71
  });
73
- return typeof document > "u" ? j : c(j, document.body);
72
+ return typeof document > "u" ? M : l(M, document.body);
74
73
  };
75
74
  //#endregion
76
- export { d as Dialog };
75
+ export { f as Dialog };
77
76
 
78
77
  //# sourceMappingURL=Dialog.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"Dialog.js","names":[],"sources":["../../../src/components/Dialog/Dialog.tsx"],"sourcesContent":["import {\n type HTMLAttributes,\n type KeyboardEvent,\n type ReactNode,\n useCallback,\n useEffect,\n useId,\n useRef,\n} from 'react';\n\nimport { createPortal } from 'react-dom';\n\nimport styles from './Dialog.module.css';\nimport { FOCUSABLE, trapFocus, useVisualViewport } from './dialogUtils';\n\n// ─── AlertDialog types ────────────────────────────────────────────────────────\n\nexport type AlertDialogResponseVariant = 'default' | 'suggested' | 'destructive';\n\nexport interface AlertDialogResponse {\n /**\n * Unique identifier returned via `onResponse`.\n * Use `\"cancel\"` by convention for the dismissive action.\n */\n id: string;\n /** Button label. */\n label: string;\n /** Visual emphasis. Defaults to `\"default\"`. */\n variant?: AlertDialogResponseVariant;\n /** Disables the button. */\n disabled?: boolean;\n}\n\n// ─── Dialog ───────────────────────────────────────────────────────────────────\n\nexport interface DialogButton {\n /** Button label. */\n label: string;\n /** Visual variant. Defaults to `\"default\"`. */\n variant?: 'default' | 'suggested' | 'destructive';\n /** Called when the button is clicked. */\n onClick: () => void;\n /** Whether the button is disabled. */\n disabled?: boolean;\n}\n\nexport interface DialogProps extends Omit<HTMLAttributes<HTMLDivElement>, 'title'> {\n /** Whether the dialog is visible. */\n open: boolean;\n\n // ── Standard dialog ──────────────────────────────────────────────────────\n\n /** Dialog heading. */\n title?: ReactNode;\n /** Body content. */\n children?: ReactNode;\n /** Action buttons (standard dialog API). */\n buttons?: DialogButton[];\n /** Called on Escape or backdrop click. */\n onClose?: () => void;\n /** Whether clicking the backdrop closes the dialog. Defaults to `true`. */\n closeOnBackdrop?: boolean;\n\n // ── AlertDialog extension ─────────────────────────────────────────────────\n\n /**\n * ARIA role. Use `\"alertdialog\"` for confirmations and destructive warnings —\n * screen readers announce it immediately. Defaults to `\"dialog\"`.\n *\n * When using `role=\"alertdialog\"`, prefer the `responses`/`onResponse` API\n * over `buttons`/`onClick` for semantic clarity.\n */\n role?: 'dialog' | 'alertdialog';\n\n /**\n * Response buttons (AlertDialog API). Alternative to `buttons` — each\n * response has a semantic `id` returned via `onResponse`.\n * Escape and backdrop click fire the first non-destructive response.\n */\n responses?: AlertDialogResponse[];\n\n /**\n * Called with the `id` of the response button clicked.\n * Required when `responses` is provided.\n */\n onResponse?: (id: string) => void;\n}\n\n/**\n * Blocking modal dialog following the Adwaita pattern.\n *\n * **Standard** — `title` + `children` + `buttons[]`.\n *\n * **Alert** — add `role=\"alertdialog\"` + `responses[]` + `onResponse`.\n * Uses a semantic response id instead of per-button `onClick`.\n * Escape / backdrop fire the first non-destructive response.\n * Mirrors `AdwAlertDialog`.\n *\n * For the app-info dialog, use `<AboutDialog />` instead.\n *\n * @see https://gnome.pages.gitlab.gnome.org/libadwaita/doc/main/class.Dialog.html\n * @see https://gnome.pages.gitlab.gnome.org/libadwaita/doc/main/class.AlertDialog.html\n */\nexport const Dialog = ({\n open,\n title,\n children,\n buttons = [],\n onClose,\n closeOnBackdrop = true,\n role = 'dialog',\n responses,\n onResponse,\n className,\n ...props\n}: DialogProps) => {\n const dialogRef = useRef<HTMLDivElement>(null);\n const titleId = useId();\n const previouslyFocused = useRef<Element | null>(null);\n\n const isAlert = !!responses;\n const viewportStyle = useVisualViewport();\n\n // ── Focus management ───────────────────────────────────────────────────────\n useEffect(() => {\n if (open) {\n previouslyFocused.current = document.activeElement;\n const el = dialogRef.current?.querySelector<HTMLElement>(FOCUSABLE);\n\n el?.focus();\n } else {\n (previouslyFocused.current as HTMLElement | null)?.focus();\n }\n }, [open]);\n\n // ── Dismiss helpers ────────────────────────────────────────────────────────\n const dismissAlert = useCallback(() => {\n const cancel = responses?.find((r) => r.variant !== 'destructive' && !r.disabled);\n\n if (cancel) {\n onResponse?.(cancel.id);\n }\n }, [responses, onResponse]);\n\n const handleBackdrop = isAlert ? dismissAlert : closeOnBackdrop ? onClose : undefined;\n\n // ── Keyboard ───────────────────────────────────────────────────────────────\n const handleKeyDown = useCallback(\n (e: KeyboardEvent<HTMLDivElement>) => {\n if (e.key === 'Escape') {\n e.preventDefault();\n if (isAlert) {\n dismissAlert();\n } else {\n onClose?.();\n }\n\n return;\n }\n\n trapFocus(e, dialogRef);\n },\n [isAlert, dismissAlert, onClose],\n );\n\n if (!open) {\n return null;\n }\n\n // ── Render buttons (standard or alert) ────────────────────────────────────\n const renderButtons = () => {\n if (isAlert && responses) {\n return (\n <div className={styles.footer}>\n {responses.map((r) => (\n <button\n key={r.id}\n type=\"button\"\n disabled={r.disabled}\n className={[styles.btn, styles[`btn-${r.variant ?? 'default'}`]]\n .filter(Boolean)\n .join(' ')}\n onClick={() => onResponse?.(r.id)}\n >\n {r.label}\n </button>\n ))}\n </div>\n );\n }\n\n if (buttons.length > 0) {\n return (\n <div className={styles.footer}>\n {buttons.map((btn) => (\n <button\n key={btn.label}\n type=\"button\"\n disabled={btn.disabled}\n className={[styles.btn, styles[`btn-${btn.variant ?? 'default'}`]]\n .filter(Boolean)\n .join(' ')}\n onClick={btn.onClick}\n >\n {btn.label}\n </button>\n ))}\n </div>\n );\n }\n\n return null;\n };\n\n const node = (\n <div\n className={styles.backdrop}\n style={viewportStyle}\n onClick={handleBackdrop}\n aria-hidden=\"true\"\n >\n <div\n ref={dialogRef}\n role={role}\n aria-modal=\"true\"\n aria-labelledby={titleId}\n className={[styles.dialog, className].filter(Boolean).join(' ')}\n onKeyDown={handleKeyDown}\n onClick={(e) => e.stopPropagation()}\n {...props}\n >\n {title && (\n <div id={titleId} className={styles.title}>\n {title}\n </div>\n )}\n {children && <div className={styles.body}>{children}</div>}\n {renderButtons()}\n </div>\n </div>\n );\n\n if (typeof document === 'undefined') {\n return node;\n }\n\n return createPortal(node, document.body);\n};\n"],"mappings":";;;;;;AAuGA,IAAa,KAAU,EACrB,SACA,UACA,aACA,aAAU,CAAC,GACX,YACA,qBAAkB,IAClB,UAAO,UACP,cACA,eACA,cACA,GAAG,QACc;CACjB,IAAM,IAAY,EAAuB,IAAI,GACvC,IAAU,EAAM,GAChB,IAAoB,EAAuB,IAAI,GAE/C,IAAU,CAAC,CAAC,GACZ,IAAgB,EAAkB;CAGxC,QAAgB;EACd,AAAI,KACF,EAAkB,UAAU,SAAS,gBAC1B,EAAU,SAAS,cAA2B,CAAS,IAE9D,MAAM,KAEV,EAAmB,SAAgC,MAAM;CAE7D,GAAG,CAAC,CAAI,CAAC;CAGT,IAAM,IAAe,QAAkB;EACrC,IAAM,IAAS,GAAW,MAAM,MAAM,EAAE,YAAY,iBAAiB,CAAC,EAAE,QAAQ;EAEhF,AAAI,KACF,IAAa,EAAO,EAAE;CAE1B,GAAG,CAAC,GAAW,CAAU,CAAC,GAEpB,IAAiB,IAAU,IAAe,IAAkB,IAAU,KAAA,GAGtE,IAAgB,GACnB,MAAqC;EACpC,IAAI,EAAE,QAAQ,UAAU;GAEtB,AADA,EAAE,eAAe,GACb,IACF,EAAa,IAEb,IAAU;GAGZ;EACF;EAEA,EAAU,GAAG,CAAS;CACxB,GACA;EAAC;EAAS;EAAc;CAAO,CACjC;CAEA,IAAI,CAAC,GACH,OAAO;CAIT,IAAM,UACA,KAAW,IAEX,kBAAC,OAAD;EAAK,WAAW,EAAO;YACpB,EAAU,KAAK,MACd,kBAAC,UAAD;GAEE,MAAK;GACL,UAAU,EAAE;GACZ,WAAW,CAAC,EAAO,KAAK,EAAO,OAAO,EAAE,WAAW,YAAY,EAC5D,OAAO,OAAO,EACd,KAAK,GAAG;GACX,eAAe,IAAa,EAAE,EAAE;aAE/B,EAAE;EACG,GATD,EAAE,EASD,CACT;CACE,CAAA,IAIL,EAAQ,SAAS,IAEjB,kBAAC,OAAD;EAAK,WAAW,EAAO;YACpB,EAAQ,KAAK,MACZ,kBAAC,UAAD;GAEE,MAAK;GACL,UAAU,EAAI;GACd,WAAW,CAAC,EAAO,KAAK,EAAO,OAAO,EAAI,WAAW,YAAY,EAC9D,OAAO,OAAO,EACd,KAAK,GAAG;GACX,SAAS,EAAI;aAEZ,EAAI;EACC,GATD,EAAI,KASH,CACT;CACE,CAAA,IAIF,MAGH,IACJ,kBAAC,OAAD;EACE,WAAW,EAAO;EAClB,OAAO;EACP,SAAS;EACT,eAAY;YAEZ,kBAAC,OAAD;GACE,KAAK;GACC;GACN,cAAW;GACX,mBAAiB;GACjB,WAAW,CAAC,EAAO,QAAQ,CAAS,EAAE,OAAO,OAAO,EAAE,KAAK,GAAG;GAC9D,WAAW;GACX,UAAU,MAAM,EAAE,gBAAgB;GAClC,GAAI;aARN;IAUG,KACC,kBAAC,OAAD;KAAK,IAAI;KAAS,WAAW,EAAO;eACjC;IACE,CAAA;IAEN,KAAY,kBAAC,OAAD;KAAK,WAAW,EAAO;KAAO;IAAc,CAAA;IACxD,EAAc;GACZ;;CACF,CAAA;CAOP,OAJI,OAAO,WAAa,MACf,IAGF,EAAa,GAAM,SAAS,IAAI;AACzC"}
1
+ {"version":3,"file":"Dialog.js","names":[],"sources":["../../../src/components/Dialog/Dialog.tsx"],"sourcesContent":["import {\n type HTMLAttributes,\n type KeyboardEvent,\n type ReactNode,\n useCallback,\n useEffect,\n useId,\n useRef,\n} from 'react';\n\nimport { createPortal } from 'react-dom';\n\nimport styles from './Dialog.module.css';\nimport { FOCUSABLE, trapFocus, useBodyScrollLock, useVisualViewport } from './dialogUtils';\n\n// ─── AlertDialog types ────────────────────────────────────────────────────────\n\nexport type AlertDialogResponseVariant = 'default' | 'suggested' | 'destructive';\n\nexport interface AlertDialogResponse {\n /**\n * Unique identifier returned via `onResponse`.\n * Use `\"cancel\"` by convention for the dismissive action.\n */\n id: string;\n /** Button label. */\n label: string;\n /** Visual emphasis. Defaults to `\"default\"`. */\n variant?: AlertDialogResponseVariant;\n /** Disables the button. */\n disabled?: boolean;\n}\n\n// ─── Dialog ───────────────────────────────────────────────────────────────────\n\nexport interface DialogButton {\n /** Button label. */\n label: string;\n /** Visual variant. Defaults to `\"default\"`. */\n variant?: 'default' | 'suggested' | 'destructive';\n /** Called when the button is clicked. */\n onClick: () => void;\n /** Whether the button is disabled. */\n disabled?: boolean;\n}\n\nexport interface DialogProps extends Omit<HTMLAttributes<HTMLDivElement>, 'title'> {\n /** Whether the dialog is visible. */\n open: boolean;\n\n // ── Standard dialog ──────────────────────────────────────────────────────\n\n /** Dialog heading. */\n title?: ReactNode;\n /** Body content. */\n children?: ReactNode;\n /** Action buttons (standard dialog API). */\n buttons?: DialogButton[];\n /** Called on Escape or backdrop click. */\n onClose?: () => void;\n /** Whether clicking the backdrop closes the dialog. Defaults to `true`. */\n closeOnBackdrop?: boolean;\n\n // ── AlertDialog extension ─────────────────────────────────────────────────\n\n /**\n * ARIA role. Use `\"alertdialog\"` for confirmations and destructive warnings —\n * screen readers announce it immediately. Defaults to `\"dialog\"`.\n *\n * When using `role=\"alertdialog\"`, prefer the `responses`/`onResponse` API\n * over `buttons`/`onClick` for semantic clarity.\n */\n role?: 'dialog' | 'alertdialog';\n\n /**\n * Response buttons (AlertDialog API). Alternative to `buttons` — each\n * response has a semantic `id` returned via `onResponse`.\n * Escape and backdrop click fire the first non-destructive response.\n */\n responses?: AlertDialogResponse[];\n\n /**\n * Called with the `id` of the response button clicked.\n * Required when `responses` is provided.\n */\n onResponse?: (id: string) => void;\n}\n\n/**\n * Blocking modal dialog following the Adwaita pattern.\n *\n * **Standard** — `title` + `children` + `buttons[]`.\n *\n * **Alert** — add `role=\"alertdialog\"` + `responses[]` + `onResponse`.\n * Uses a semantic response id instead of per-button `onClick`.\n * Escape / backdrop fire the first non-destructive response.\n * Mirrors `AdwAlertDialog`.\n *\n * For the app-info dialog, use `<AboutDialog />` instead.\n *\n * @see https://gnome.pages.gitlab.gnome.org/libadwaita/doc/main/class.Dialog.html\n * @see https://gnome.pages.gitlab.gnome.org/libadwaita/doc/main/class.AlertDialog.html\n */\nexport const Dialog = ({\n open,\n title,\n children,\n buttons = [],\n onClose,\n closeOnBackdrop = true,\n role = 'dialog',\n responses,\n onResponse,\n className,\n ...props\n}: DialogProps) => {\n const dialogRef = useRef<HTMLDivElement>(null);\n const titleId = useId();\n const previouslyFocused = useRef<Element | null>(null);\n\n const isAlert = !!responses;\n const viewportStyle = useVisualViewport();\n\n useBodyScrollLock(open);\n\n // ── Focus management ───────────────────────────────────────────────────────\n useEffect(() => {\n if (open) {\n previouslyFocused.current = document.activeElement;\n const el = dialogRef.current?.querySelector<HTMLElement>(FOCUSABLE);\n\n el?.focus();\n } else {\n (previouslyFocused.current as HTMLElement | null)?.focus();\n }\n }, [open]);\n\n // ── Dismiss helpers ────────────────────────────────────────────────────────\n const dismissAlert = useCallback(() => {\n const cancel = responses?.find((r) => r.variant !== 'destructive' && !r.disabled);\n\n if (cancel) {\n onResponse?.(cancel.id);\n }\n }, [responses, onResponse]);\n\n const handleBackdrop = isAlert ? dismissAlert : closeOnBackdrop ? onClose : undefined;\n\n // ── Keyboard ───────────────────────────────────────────────────────────────\n const handleKeyDown = useCallback(\n (e: KeyboardEvent<HTMLDivElement>) => {\n if (e.key === 'Escape') {\n e.preventDefault();\n if (isAlert) {\n dismissAlert();\n } else {\n onClose?.();\n }\n\n return;\n }\n\n trapFocus(e, dialogRef);\n },\n [isAlert, dismissAlert, onClose],\n );\n\n if (!open) {\n return null;\n }\n\n // ── Render buttons (standard or alert) ────────────────────────────────────\n const renderButtons = () => {\n if (isAlert && responses) {\n return (\n <div className={styles.footer}>\n {responses.map((r) => (\n <button\n key={r.id}\n type=\"button\"\n disabled={r.disabled}\n className={[styles.btn, styles[`btn-${r.variant ?? 'default'}`]]\n .filter(Boolean)\n .join(' ')}\n onClick={() => onResponse?.(r.id)}\n >\n {r.label}\n </button>\n ))}\n </div>\n );\n }\n\n if (buttons.length > 0) {\n return (\n <div className={styles.footer}>\n {buttons.map((btn) => (\n <button\n key={btn.label}\n type=\"button\"\n disabled={btn.disabled}\n className={[styles.btn, styles[`btn-${btn.variant ?? 'default'}`]]\n .filter(Boolean)\n .join(' ')}\n onClick={btn.onClick}\n >\n {btn.label}\n </button>\n ))}\n </div>\n );\n }\n\n return null;\n };\n\n const node = (\n <div className={styles.backdrop} style={viewportStyle} onClick={handleBackdrop}>\n <div\n ref={dialogRef}\n role={role}\n aria-modal=\"true\"\n aria-labelledby={title ? titleId : undefined}\n className={[styles.dialog, className].filter(Boolean).join(' ')}\n onKeyDown={handleKeyDown}\n onClick={(e) => e.stopPropagation()}\n {...props}\n >\n {title && (\n <div id={titleId} className={styles.title}>\n {title}\n </div>\n )}\n {children && <div className={styles.body}>{children}</div>}\n {renderButtons()}\n </div>\n </div>\n );\n\n if (typeof document === 'undefined') {\n return node;\n }\n\n return createPortal(node, document.body);\n};\n"],"mappings":";;;;;;AAuGA,IAAa,KAAU,EACrB,SACA,UACA,aACA,aAAU,CAAC,GACX,YACA,qBAAkB,IAClB,UAAO,UACP,cACA,eACA,cACA,GAAG,QACc;CACjB,IAAM,IAAY,EAAuB,IAAI,GACvC,IAAU,EAAM,GAChB,IAAoB,EAAuB,IAAI,GAE/C,IAAU,CAAC,CAAC,GACZ,IAAgB,EAAkB;CAKxC,AAHA,EAAkB,CAAI,GAGtB,QAAgB;EACd,AAAI,KACF,EAAkB,UAAU,SAAS,gBAC1B,EAAU,SAAS,cAA2B,CAAS,IAE9D,MAAM,KAEV,EAAmB,SAAgC,MAAM;CAE7D,GAAG,CAAC,CAAI,CAAC;CAGT,IAAM,IAAe,QAAkB;EACrC,IAAM,IAAS,GAAW,MAAM,MAAM,EAAE,YAAY,iBAAiB,CAAC,EAAE,QAAQ;EAEhF,AAAI,KACF,IAAa,EAAO,EAAE;CAE1B,GAAG,CAAC,GAAW,CAAU,CAAC,GAEpB,IAAiB,IAAU,IAAe,IAAkB,IAAU,KAAA,GAGtE,IAAgB,GACnB,MAAqC;EACpC,IAAI,EAAE,QAAQ,UAAU;GAEtB,AADA,EAAE,eAAe,GACb,IACF,EAAa,IAEb,IAAU;GAGZ;EACF;EAEA,EAAU,GAAG,CAAS;CACxB,GACA;EAAC;EAAS;EAAc;CAAO,CACjC;CAEA,IAAI,CAAC,GACH,OAAO;CAIT,IAAM,UACA,KAAW,IAEX,kBAAC,OAAD;EAAK,WAAW,EAAO;YACpB,EAAU,KAAK,MACd,kBAAC,UAAD;GAEE,MAAK;GACL,UAAU,EAAE;GACZ,WAAW,CAAC,EAAO,KAAK,EAAO,OAAO,EAAE,WAAW,YAAY,EAC5D,OAAO,OAAO,EACd,KAAK,GAAG;GACX,eAAe,IAAa,EAAE,EAAE;aAE/B,EAAE;EACG,GATD,EAAE,EASD,CACT;CACE,CAAA,IAIL,EAAQ,SAAS,IAEjB,kBAAC,OAAD;EAAK,WAAW,EAAO;YACpB,EAAQ,KAAK,MACZ,kBAAC,UAAD;GAEE,MAAK;GACL,UAAU,EAAI;GACd,WAAW,CAAC,EAAO,KAAK,EAAO,OAAO,EAAI,WAAW,YAAY,EAC9D,OAAO,OAAO,EACd,KAAK,GAAG;GACX,SAAS,EAAI;aAEZ,EAAI;EACC,GATD,EAAI,KASH,CACT;CACE,CAAA,IAIF,MAGH,IACJ,kBAAC,OAAD;EAAK,WAAW,EAAO;EAAU,OAAO;EAAe,SAAS;YAC9D,kBAAC,OAAD;GACE,KAAK;GACC;GACN,cAAW;GACX,mBAAiB,IAAQ,IAAU,KAAA;GACnC,WAAW,CAAC,EAAO,QAAQ,CAAS,EAAE,OAAO,OAAO,EAAE,KAAK,GAAG;GAC9D,WAAW;GACX,UAAU,MAAM,EAAE,gBAAgB;GAClC,GAAI;aARN;IAUG,KACC,kBAAC,OAAD;KAAK,IAAI;KAAS,WAAW,EAAO;eACjC;IACE,CAAA;IAEN,KAAY,kBAAC,OAAD;KAAK,WAAW,EAAO;KAAO;IAAc,CAAA;IACxD,EAAc;GACZ;;CACF,CAAA;CAOP,OAJI,OAAO,WAAa,MACf,IAGF,EAAa,GAAM,SAAS,IAAI;AACzC"}
@@ -1,2 +1,2 @@
1
- let e=require("react");var t=`button:not([disabled]), [href], input:not([disabled]), select:not([disabled]), textarea:not([disabled]), [tabindex]:not([tabindex="-1"])`;function n(){let[t,n]=(0,e.useState)({});return(0,e.useEffect)(()=>{let{visualViewport:e}=window;if(!e)return;let t=()=>{let{offsetLeft:t,offsetTop:r,width:i,height:a}=e;n({top:r,left:t,width:i,height:a})};return t(),e.addEventListener(`resize`,t),e.addEventListener(`scroll`,t),()=>{e.removeEventListener(`resize`,t),e.removeEventListener(`scroll`,t)}},[]),t}function r(e,t){if(e.key!==`Tab`)return;let n=Array.from(t.current?.querySelectorAll(`button:not([disabled]), [href], input:not([disabled]), select:not([disabled]), textarea:not([disabled]), [tabindex]:not([tabindex="-1"])`)??[]);if(!n.length)return;let r=n[0],i=n[n.length-1];e.shiftKey?document.activeElement===r&&(e.preventDefault(),i.focus()):document.activeElement===i&&(e.preventDefault(),r.focus())}exports.FOCUSABLE=t,exports.trapFocus=r,exports.useVisualViewport=n;
1
+ let e=require("react");var t=`button:not([disabled]), [href], input:not([disabled]), select:not([disabled]), textarea:not([disabled]), [tabindex]:not([tabindex="-1"])`;function n(){let[t,n]=(0,e.useState)({});return(0,e.useEffect)(()=>{let{visualViewport:e}=window;if(!e)return;let t=()=>{let{offsetLeft:t,offsetTop:r,width:i,height:a}=e;n({top:r,left:t,width:i,height:a})};return t(),e.addEventListener(`resize`,t),e.addEventListener(`scroll`,t),()=>{e.removeEventListener(`resize`,t),e.removeEventListener(`scroll`,t)}},[]),t}function r(t){(0,e.useEffect)(()=>{if(!t||typeof document>`u`)return;let{body:e}=document,n=e.style.overflow;return e.style.overflow=`hidden`,()=>{e.style.overflow=n}},[t])}function i(e,t){if(e.key!==`Tab`)return;let n=Array.from(t.current?.querySelectorAll(`button:not([disabled]), [href], input:not([disabled]), select:not([disabled]), textarea:not([disabled]), [tabindex]:not([tabindex="-1"])`)??[]);if(!n.length)return;let r=n[0],i=n[n.length-1];e.shiftKey?document.activeElement===r&&(e.preventDefault(),i.focus()):document.activeElement===i&&(e.preventDefault(),r.focus())}exports.FOCUSABLE=t,exports.trapFocus=i,exports.useBodyScrollLock=r,exports.useVisualViewport=n;
2
2
  //# sourceMappingURL=dialogUtils.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"dialogUtils.cjs","names":[],"sources":["../../../src/components/Dialog/dialogUtils.ts"],"sourcesContent":["import { type CSSProperties, type KeyboardEvent, type RefObject, useEffect, useState } from 'react';\n\nexport const FOCUSABLE =\n 'button:not([disabled]), [href], input:not([disabled]), select:not([disabled]), textarea:not([disabled]), [tabindex]:not([tabindex=\"-1\"])';\n\n/**\n * Returns inline styles that anchor a `position:fixed` backdrop to the visual\n * viewport so the dialog stays centered when the virtual keyboard is open on\n * mobile (iOS Safari / Android Chrome shrink `visualViewport` but not the\n * layout viewport that `position:fixed` is relative to).\n */\nexport function useVisualViewport(): CSSProperties {\n const [style, setStyle] = useState<CSSProperties>({});\n\n useEffect(() => {\n const { visualViewport } = window;\n\n if (!visualViewport) {\n return;\n }\n\n const update = (): void => {\n const { offsetLeft: left, offsetTop: top, width, height } = visualViewport;\n\n setStyle({\n top,\n left,\n width,\n height,\n });\n };\n\n update();\n visualViewport.addEventListener('resize', update);\n visualViewport.addEventListener('scroll', update);\n\n return () => {\n visualViewport.removeEventListener('resize', update);\n visualViewport.removeEventListener('scroll', update);\n };\n }, []);\n\n return style;\n}\n\nexport function trapFocus(e: KeyboardEvent<HTMLDivElement>, ref: RefObject<HTMLDivElement | null>) {\n if (e.key !== 'Tab') {\n return;\n }\n\n const focusable = Array.from(ref.current?.querySelectorAll<HTMLElement>(FOCUSABLE) ?? []);\n\n if (!focusable.length) {\n return;\n }\n\n const first = focusable[0];\n const last = focusable[focusable.length - 1];\n\n if (e.shiftKey) {\n if (document.activeElement === first) {\n e.preventDefault();\n last.focus();\n }\n } else {\n if (document.activeElement === last) {\n e.preventDefault();\n first.focus();\n }\n }\n}\n"],"mappings":"uBAEA,IAAa,EACX,2IAQF,SAAgB,GAAmC,CACjD,GAAM,CAAC,EAAO,IAAA,EAAA,EAAA,UAAoC,CAAC,CAAC,EA8BpD,OA5BA,EAAA,EAAA,eAAgB,CACd,GAAM,CAAE,kBAAmB,OAE3B,GAAI,CAAC,EACH,OAGF,IAAM,MAAqB,CACzB,GAAM,CAAE,WAAY,EAAM,UAAW,EAAK,QAAO,UAAW,EAE5D,EAAS,CACP,MACA,OACA,QACA,QACF,CAAC,CACH,EAMA,OAJA,EAAO,EACP,EAAe,iBAAiB,SAAU,CAAM,EAChD,EAAe,iBAAiB,SAAU,CAAM,MAEnC,CACX,EAAe,oBAAoB,SAAU,CAAM,EACnD,EAAe,oBAAoB,SAAU,CAAM,CACrD,CACF,EAAG,CAAC,CAAC,EAEE,CACT,CAEA,SAAgB,EAAU,EAAkC,EAAuC,CACjG,GAAI,EAAE,MAAQ,MACZ,OAGF,IAAM,EAAY,MAAM,KAAK,EAAI,SAAS,iBAAA,0IAAuC,GAAK,CAAC,CAAC,EAExF,GAAI,CAAC,EAAU,OACb,OAGF,IAAM,EAAQ,EAAU,GAClB,EAAO,EAAU,EAAU,OAAS,GAEtC,EAAE,SACA,SAAS,gBAAkB,IAC7B,EAAE,eAAe,EACjB,EAAK,MAAM,GAGT,SAAS,gBAAkB,IAC7B,EAAE,eAAe,EACjB,EAAM,MAAM,EAGlB"}
1
+ {"version":3,"file":"dialogUtils.cjs","names":[],"sources":["../../../src/components/Dialog/dialogUtils.ts"],"sourcesContent":["import { type CSSProperties, type KeyboardEvent, type RefObject, useEffect, useState } from 'react';\n\nexport const FOCUSABLE =\n 'button:not([disabled]), [href], input:not([disabled]), select:not([disabled]), textarea:not([disabled]), [tabindex]:not([tabindex=\"-1\"])';\n\n/**\n * Returns inline styles that anchor a `position:fixed` backdrop to the visual\n * viewport so the dialog stays centered when the virtual keyboard is open on\n * mobile (iOS Safari / Android Chrome shrink `visualViewport` but not the\n * layout viewport that `position:fixed` is relative to).\n */\nexport function useVisualViewport(): CSSProperties {\n const [style, setStyle] = useState<CSSProperties>({});\n\n useEffect(() => {\n const { visualViewport } = window;\n\n if (!visualViewport) {\n return;\n }\n\n const update = (): void => {\n const { offsetLeft: left, offsetTop: top, width, height } = visualViewport;\n\n setStyle({\n top,\n left,\n width,\n height,\n });\n };\n\n update();\n visualViewport.addEventListener('resize', update);\n visualViewport.addEventListener('scroll', update);\n\n return () => {\n visualViewport.removeEventListener('resize', update);\n visualViewport.removeEventListener('scroll', update);\n };\n }, []);\n\n return style;\n}\n\n/**\n * Locks body scroll while `locked` is true so the page behind a modal cannot\n * scroll. Restores the previous inline `overflow` on unlock, which keeps\n * stacked dialogs from clobbering each other.\n */\nexport function useBodyScrollLock(locked: boolean): void {\n useEffect(() => {\n if (!locked || typeof document === 'undefined') {\n return;\n }\n\n const { body } = document;\n const previous = body.style.overflow;\n\n body.style.overflow = 'hidden';\n\n return () => {\n body.style.overflow = previous;\n };\n }, [locked]);\n}\n\nexport function trapFocus(e: KeyboardEvent<HTMLDivElement>, ref: RefObject<HTMLDivElement | null>) {\n if (e.key !== 'Tab') {\n return;\n }\n\n const focusable = Array.from(ref.current?.querySelectorAll<HTMLElement>(FOCUSABLE) ?? []);\n\n if (!focusable.length) {\n return;\n }\n\n const first = focusable[0];\n const last = focusable[focusable.length - 1];\n\n if (e.shiftKey) {\n if (document.activeElement === first) {\n e.preventDefault();\n last.focus();\n }\n } else {\n if (document.activeElement === last) {\n e.preventDefault();\n first.focus();\n }\n }\n}\n"],"mappings":"uBAEA,IAAa,EACX,2IAQF,SAAgB,GAAmC,CACjD,GAAM,CAAC,EAAO,IAAA,EAAA,EAAA,UAAoC,CAAC,CAAC,EA8BpD,OA5BA,EAAA,EAAA,eAAgB,CACd,GAAM,CAAE,kBAAmB,OAE3B,GAAI,CAAC,EACH,OAGF,IAAM,MAAqB,CACzB,GAAM,CAAE,WAAY,EAAM,UAAW,EAAK,QAAO,UAAW,EAE5D,EAAS,CACP,MACA,OACA,QACA,QACF,CAAC,CACH,EAMA,OAJA,EAAO,EACP,EAAe,iBAAiB,SAAU,CAAM,EAChD,EAAe,iBAAiB,SAAU,CAAM,MAEnC,CACX,EAAe,oBAAoB,SAAU,CAAM,EACnD,EAAe,oBAAoB,SAAU,CAAM,CACrD,CACF,EAAG,CAAC,CAAC,EAEE,CACT,CAOA,SAAgB,EAAkB,EAAuB,EACvD,EAAA,EAAA,eAAgB,CACd,GAAI,CAAC,GAAU,OAAO,SAAa,IACjC,OAGF,GAAM,CAAE,QAAS,SACX,EAAW,EAAK,MAAM,SAI5B,MAFA,GAAK,MAAM,SAAW,aAET,CACX,EAAK,MAAM,SAAW,CACxB,CACF,EAAG,CAAC,CAAM,CAAC,CACb,CAEA,SAAgB,EAAU,EAAkC,EAAuC,CACjG,GAAI,EAAE,MAAQ,MACZ,OAGF,IAAM,EAAY,MAAM,KAAK,EAAI,SAAS,iBAAA,0IAAuC,GAAK,CAAC,CAAC,EAExF,GAAI,CAAC,EAAU,OACb,OAGF,IAAM,EAAQ,EAAU,GAClB,EAAO,EAAU,EAAU,OAAS,GAEtC,EAAE,SACA,SAAS,gBAAkB,IAC7B,EAAE,eAAe,EACjB,EAAK,MAAM,GAGT,SAAS,gBAAkB,IAC7B,EAAE,eAAe,EACjB,EAAM,MAAM,EAGlB"}
@@ -7,4 +7,10 @@ export declare const FOCUSABLE = "button:not([disabled]), [href], input:not([dis
7
7
  * layout viewport that `position:fixed` is relative to).
8
8
  */
9
9
  export declare function useVisualViewport(): CSSProperties;
10
+ /**
11
+ * Locks body scroll while `locked` is true so the page behind a modal cannot
12
+ * scroll. Restores the previous inline `overflow` on unlock, which keeps
13
+ * stacked dialogs from clobbering each other.
14
+ */
15
+ export declare function useBodyScrollLock(locked: boolean): void;
10
16
  export declare function trapFocus(e: KeyboardEvent<HTMLDivElement>, ref: RefObject<HTMLDivElement | null>): void;
@@ -20,7 +20,16 @@ function r() {
20
20
  };
21
21
  }, []), n;
22
22
  }
23
- function i(e, t) {
23
+ function i(t) {
24
+ e(() => {
25
+ if (!t || typeof document > "u") return;
26
+ let { body: e } = document, n = e.style.overflow;
27
+ return e.style.overflow = "hidden", () => {
28
+ e.style.overflow = n;
29
+ };
30
+ }, [t]);
31
+ }
32
+ function a(e, t) {
24
33
  if (e.key !== "Tab") return;
25
34
  let n = Array.from(t.current?.querySelectorAll("button:not([disabled]), [href], input:not([disabled]), select:not([disabled]), textarea:not([disabled]), [tabindex]:not([tabindex=\"-1\"])") ?? []);
26
35
  if (!n.length) return;
@@ -28,6 +37,6 @@ function i(e, t) {
28
37
  e.shiftKey ? document.activeElement === r && (e.preventDefault(), i.focus()) : document.activeElement === i && (e.preventDefault(), r.focus());
29
38
  }
30
39
  //#endregion
31
- export { n as FOCUSABLE, i as trapFocus, r as useVisualViewport };
40
+ export { n as FOCUSABLE, a as trapFocus, i as useBodyScrollLock, r as useVisualViewport };
32
41
 
33
42
  //# sourceMappingURL=dialogUtils.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"dialogUtils.js","names":[],"sources":["../../../src/components/Dialog/dialogUtils.ts"],"sourcesContent":["import { type CSSProperties, type KeyboardEvent, type RefObject, useEffect, useState } from 'react';\n\nexport const FOCUSABLE =\n 'button:not([disabled]), [href], input:not([disabled]), select:not([disabled]), textarea:not([disabled]), [tabindex]:not([tabindex=\"-1\"])';\n\n/**\n * Returns inline styles that anchor a `position:fixed` backdrop to the visual\n * viewport so the dialog stays centered when the virtual keyboard is open on\n * mobile (iOS Safari / Android Chrome shrink `visualViewport` but not the\n * layout viewport that `position:fixed` is relative to).\n */\nexport function useVisualViewport(): CSSProperties {\n const [style, setStyle] = useState<CSSProperties>({});\n\n useEffect(() => {\n const { visualViewport } = window;\n\n if (!visualViewport) {\n return;\n }\n\n const update = (): void => {\n const { offsetLeft: left, offsetTop: top, width, height } = visualViewport;\n\n setStyle({\n top,\n left,\n width,\n height,\n });\n };\n\n update();\n visualViewport.addEventListener('resize', update);\n visualViewport.addEventListener('scroll', update);\n\n return () => {\n visualViewport.removeEventListener('resize', update);\n visualViewport.removeEventListener('scroll', update);\n };\n }, []);\n\n return style;\n}\n\nexport function trapFocus(e: KeyboardEvent<HTMLDivElement>, ref: RefObject<HTMLDivElement | null>) {\n if (e.key !== 'Tab') {\n return;\n }\n\n const focusable = Array.from(ref.current?.querySelectorAll<HTMLElement>(FOCUSABLE) ?? []);\n\n if (!focusable.length) {\n return;\n }\n\n const first = focusable[0];\n const last = focusable[focusable.length - 1];\n\n if (e.shiftKey) {\n if (document.activeElement === first) {\n e.preventDefault();\n last.focus();\n }\n } else {\n if (document.activeElement === last) {\n e.preventDefault();\n first.focus();\n }\n }\n}\n"],"mappings":";;AAEA,IAAa,IACX;AAQF,SAAgB,IAAmC;CACjD,IAAM,CAAC,GAAO,KAAY,EAAwB,CAAC,CAAC;CA8BpD,OA5BA,QAAgB;EACd,IAAM,EAAE,sBAAmB;EAE3B,IAAI,CAAC,GACH;EAGF,IAAM,UAAqB;GACzB,IAAM,EAAE,YAAY,GAAM,WAAW,GAAK,UAAO,cAAW;GAE5D,EAAS;IACP;IACA;IACA;IACA;GACF,CAAC;EACH;EAMA,OAJA,EAAO,GACP,EAAe,iBAAiB,UAAU,CAAM,GAChD,EAAe,iBAAiB,UAAU,CAAM,SAEnC;GAEX,AADA,EAAe,oBAAoB,UAAU,CAAM,GACnD,EAAe,oBAAoB,UAAU,CAAM;EACrD;CACF,GAAG,CAAC,CAAC,GAEE;AACT;AAEA,SAAgB,EAAU,GAAkC,GAAuC;CACjG,IAAI,EAAE,QAAQ,OACZ;CAGF,IAAM,IAAY,MAAM,KAAK,EAAI,SAAS,iBAAA,4IAAuC,KAAK,CAAC,CAAC;CAExF,IAAI,CAAC,EAAU,QACb;CAGF,IAAM,IAAQ,EAAU,IAClB,IAAO,EAAU,EAAU,SAAS;CAE1C,AAAI,EAAE,WACA,SAAS,kBAAkB,MAC7B,EAAE,eAAe,GACjB,EAAK,MAAM,KAGT,SAAS,kBAAkB,MAC7B,EAAE,eAAe,GACjB,EAAM,MAAM;AAGlB"}
1
+ {"version":3,"file":"dialogUtils.js","names":[],"sources":["../../../src/components/Dialog/dialogUtils.ts"],"sourcesContent":["import { type CSSProperties, type KeyboardEvent, type RefObject, useEffect, useState } from 'react';\n\nexport const FOCUSABLE =\n 'button:not([disabled]), [href], input:not([disabled]), select:not([disabled]), textarea:not([disabled]), [tabindex]:not([tabindex=\"-1\"])';\n\n/**\n * Returns inline styles that anchor a `position:fixed` backdrop to the visual\n * viewport so the dialog stays centered when the virtual keyboard is open on\n * mobile (iOS Safari / Android Chrome shrink `visualViewport` but not the\n * layout viewport that `position:fixed` is relative to).\n */\nexport function useVisualViewport(): CSSProperties {\n const [style, setStyle] = useState<CSSProperties>({});\n\n useEffect(() => {\n const { visualViewport } = window;\n\n if (!visualViewport) {\n return;\n }\n\n const update = (): void => {\n const { offsetLeft: left, offsetTop: top, width, height } = visualViewport;\n\n setStyle({\n top,\n left,\n width,\n height,\n });\n };\n\n update();\n visualViewport.addEventListener('resize', update);\n visualViewport.addEventListener('scroll', update);\n\n return () => {\n visualViewport.removeEventListener('resize', update);\n visualViewport.removeEventListener('scroll', update);\n };\n }, []);\n\n return style;\n}\n\n/**\n * Locks body scroll while `locked` is true so the page behind a modal cannot\n * scroll. Restores the previous inline `overflow` on unlock, which keeps\n * stacked dialogs from clobbering each other.\n */\nexport function useBodyScrollLock(locked: boolean): void {\n useEffect(() => {\n if (!locked || typeof document === 'undefined') {\n return;\n }\n\n const { body } = document;\n const previous = body.style.overflow;\n\n body.style.overflow = 'hidden';\n\n return () => {\n body.style.overflow = previous;\n };\n }, [locked]);\n}\n\nexport function trapFocus(e: KeyboardEvent<HTMLDivElement>, ref: RefObject<HTMLDivElement | null>) {\n if (e.key !== 'Tab') {\n return;\n }\n\n const focusable = Array.from(ref.current?.querySelectorAll<HTMLElement>(FOCUSABLE) ?? []);\n\n if (!focusable.length) {\n return;\n }\n\n const first = focusable[0];\n const last = focusable[focusable.length - 1];\n\n if (e.shiftKey) {\n if (document.activeElement === first) {\n e.preventDefault();\n last.focus();\n }\n } else {\n if (document.activeElement === last) {\n e.preventDefault();\n first.focus();\n }\n }\n}\n"],"mappings":";;AAEA,IAAa,IACX;AAQF,SAAgB,IAAmC;CACjD,IAAM,CAAC,GAAO,KAAY,EAAwB,CAAC,CAAC;CA8BpD,OA5BA,QAAgB;EACd,IAAM,EAAE,sBAAmB;EAE3B,IAAI,CAAC,GACH;EAGF,IAAM,UAAqB;GACzB,IAAM,EAAE,YAAY,GAAM,WAAW,GAAK,UAAO,cAAW;GAE5D,EAAS;IACP;IACA;IACA;IACA;GACF,CAAC;EACH;EAMA,OAJA,EAAO,GACP,EAAe,iBAAiB,UAAU,CAAM,GAChD,EAAe,iBAAiB,UAAU,CAAM,SAEnC;GAEX,AADA,EAAe,oBAAoB,UAAU,CAAM,GACnD,EAAe,oBAAoB,UAAU,CAAM;EACrD;CACF,GAAG,CAAC,CAAC,GAEE;AACT;AAOA,SAAgB,EAAkB,GAAuB;CACvD,QAAgB;EACd,IAAI,CAAC,KAAU,OAAO,WAAa,KACjC;EAGF,IAAM,EAAE,YAAS,UACX,IAAW,EAAK,MAAM;EAI5B,OAFA,EAAK,MAAM,WAAW,gBAET;GACX,EAAK,MAAM,WAAW;EACxB;CACF,GAAG,CAAC,CAAM,CAAC;AACb;AAEA,SAAgB,EAAU,GAAkC,GAAuC;CACjG,IAAI,EAAE,QAAQ,OACZ;CAGF,IAAM,IAAY,MAAM,KAAK,EAAI,SAAS,iBAAA,4IAAuC,KAAK,CAAC,CAAC;CAExF,IAAI,CAAC,EAAU,QACb;CAGF,IAAM,IAAQ,EAAU,IAClB,IAAO,EAAU,EAAU,SAAS;CAE1C,AAAI,EAAE,WACA,SAAS,kBAAkB,MAC7B,EAAE,eAAe,GACjB,EAAK,MAAM,KAGT,SAAS,kBAAkB,MAC7B,EAAE,eAAe,GACjB,EAAM,MAAM;AAGlB"}
@@ -1,2 +1,2 @@
1
- const e=require("../Icon/Icon.cjs"),t=require("./Dropdown.module.css.cjs");let n=require("react"),r=require("react/jsx-runtime"),i=require("@gnome-ui/icons");var a=({options:a,value:o,onChange:s,placeholder:c=`Select an option`,disabled:l,className:u,...d})=>{let[f,p]=(0,n.useState)(!1),[m,h]=(0,n.useState)(-1),[g,_]=(0,n.useState)(!1),v=(0,n.useId)(),y=(0,n.useId)(),b=(0,n.useRef)(null),x=(0,n.useRef)(null),S=a.find(e=>e.value===o),C=(0,n.useCallback)(()=>{if(!b.current)return;let e=b.current.getBoundingClientRect(),t=window.innerHeight-e.bottom,n=Math.min(a.length*48+8,280);_(t<n&&e.top>n)},[a.length]),w=(0,n.useCallback)(()=>{if(l)return;C(),p(!0);let e=a.findIndex(e=>e.value===o&&!e.disabled);h(e>=0?e:a.findIndex(e=>!e.disabled))},[l,C,a,o]),T=(0,n.useCallback)(()=>{p(!1),h(-1),b.current?.focus()},[]),E=(0,n.useCallback)(e=>{e.disabled||(s?.(e.value),T())},[s,T]);(0,n.useEffect)(()=>{!f||m<0||!x.current||x.current.children[m]?.scrollIntoView({block:`nearest`})},[m,f]),(0,n.useEffect)(()=>{if(!f)return;let e=e=>{!b.current?.contains(e.target)&&!x.current?.contains(e.target)&&T()};return document.addEventListener(`mousedown`,e),()=>document.removeEventListener(`mousedown`,e)},[f,T]);let D=(0,n.useCallback)(e=>{switch(e.key){case`Enter`:case` `:case`ArrowDown`:e.preventDefault(),w();break;case`ArrowUp`:e.preventDefault(),C(),p(!0),h(a.reduce((e,t,n)=>t.disabled?e:n,-1));break}},[w,C,a]),O=(0,n.useCallback)(e=>{let t=a.map((e,t)=>({o:e,i:t})).filter(({o:e})=>!e.disabled).map(({i:e})=>e),n=t.indexOf(m);switch(e.key){case`ArrowDown`:{e.preventDefault();let r=t[Math.min(n+1,t.length-1)];h(r??m);break}case`ArrowUp`:{e.preventDefault();let r=t[Math.max(n-1,0)];h(r??m);break}case`Home`:e.preventDefault(),h(t[0]??-1);break;case`End`:e.preventDefault(),h(t[t.length-1]??-1);break;case`Enter`:case` `:e.preventDefault(),m>=0&&E(a[m]);break;case`Escape`:case`Tab`:T();break}},[a,m,E,T]);return(0,r.jsxs)(`div`,{className:[t.default.wrapper,u].filter(Boolean).join(` `),...d,children:[(0,r.jsxs)(`button`,{ref:b,id:v,type:`button`,role:`combobox`,"aria-haspopup":`listbox`,"aria-expanded":f,"aria-controls":y,"aria-activedescendant":f&&m>=0?`${y}-opt-${m}`:void 0,disabled:l,className:[t.default.trigger,f?t.default.triggerOpen:null].filter(Boolean).join(` `),onClick:()=>f?T():w(),onKeyDown:D,children:[(0,r.jsx)(`span`,{className:[t.default.triggerLabel,S?null:t.default.placeholder].filter(Boolean).join(` `),children:S?.label??c}),(0,r.jsx)(e.Icon,{icon:i.PanDown,size:`md`,"aria-hidden":!0,className:[t.default.chevron,f?t.default.chevronOpen:null].filter(Boolean).join(` `)})]}),f&&(0,r.jsx)(`ul`,{ref:x,id:y,role:`listbox`,"aria-labelledby":v,tabIndex:-1,className:[t.default.list,g?t.default.listUp:t.default.listDown].filter(Boolean).join(` `),onKeyDown:O,children:a.map((n,a)=>(0,r.jsxs)(`li`,{id:`${y}-opt-${a}`,role:`option`,"aria-selected":n.value===o,"aria-disabled":n.disabled,className:[t.default.option,n.value===o?t.default.optionSelected:null,a===m?t.default.optionActive:null,n.disabled?t.default.optionDisabled:null].filter(Boolean).join(` `),onMouseEnter:()=>!n.disabled&&h(a),onClick:()=>E(n),children:[(0,r.jsxs)(`span`,{className:t.default.optionText,children:[(0,r.jsx)(`span`,{className:t.default.optionLabel,children:n.label}),n.description&&(0,r.jsx)(`span`,{className:t.default.optionDesc,children:n.description})]}),n.value===o&&(0,r.jsx)(e.Icon,{icon:i.Check,size:`md`,"aria-hidden":!0,className:t.default.checkIcon})]},n.value))})]})};exports.Dropdown=a;
1
+ const e=require("../Icon/Icon.cjs"),t=require("./Dropdown.module.css.cjs");let n=require("react"),r=require("react/jsx-runtime"),i=require("@gnome-ui/icons");var a=({options:a,value:o,onChange:s,placeholder:c=`Select an option`,disabled:l,className:u,...d})=>{let[f,p]=(0,n.useState)(!1),[m,h]=(0,n.useState)(-1),[g,_]=(0,n.useState)(!1),v=(0,n.useId)(),y=(0,n.useId)(),b=(0,n.useRef)(null),x=(0,n.useRef)(null),S=a.find(e=>e.value===o),C=(0,n.useCallback)(()=>{if(!b.current)return;let e=b.current.getBoundingClientRect(),t=window.innerHeight-e.bottom,n=Math.min(a.length*48+8,280);_(t<n&&e.top>n)},[a.length]),w=(0,n.useCallback)(()=>{if(l)return;C(),p(!0);let e=a.findIndex(e=>e.value===o&&!e.disabled);h(e>=0?e:a.findIndex(e=>!e.disabled))},[l,C,a,o]),T=(0,n.useCallback)(()=>{p(!1),h(-1),b.current?.focus()},[]),E=(0,n.useCallback)(e=>{e.disabled||(s?.(e.value),T())},[s,T]);(0,n.useLayoutEffect)(()=>{if(!f||!b.current||!x.current)return;let e=b.current.getBoundingClientRect(),t=x.current.offsetHeight;_(window.innerHeight-e.bottom<t&&e.top>t)},[f]),(0,n.useEffect)(()=>{!f||m<0||!x.current||x.current.children[m]?.scrollIntoView({block:`nearest`})},[m,f]),(0,n.useEffect)(()=>{if(!f)return;let e=e=>{!b.current?.contains(e.target)&&!x.current?.contains(e.target)&&T()};return document.addEventListener(`mousedown`,e),()=>document.removeEventListener(`mousedown`,e)},[f,T]);let D=(0,n.useCallback)(e=>{switch(e.key){case`Enter`:case` `:case`ArrowDown`:e.preventDefault(),w();break;case`ArrowUp`:e.preventDefault(),C(),p(!0),h(a.reduce((e,t,n)=>t.disabled?e:n,-1));break}},[w,C,a]),O=(0,n.useCallback)(e=>{let t=a.map((e,t)=>({o:e,i:t})).filter(({o:e})=>!e.disabled).map(({i:e})=>e),n=t.indexOf(m);switch(e.key){case`ArrowDown`:{e.preventDefault();let r=t[Math.min(n+1,t.length-1)];h(r??m);break}case`ArrowUp`:{e.preventDefault();let r=t[Math.max(n-1,0)];h(r??m);break}case`Home`:e.preventDefault(),h(t[0]??-1);break;case`End`:e.preventDefault(),h(t[t.length-1]??-1);break;case`Enter`:case` `:e.preventDefault(),m>=0&&E(a[m]);break;case`Escape`:case`Tab`:T();break}},[a,m,E,T]);return(0,r.jsxs)(`div`,{className:[t.default.wrapper,u].filter(Boolean).join(` `),...d,children:[(0,r.jsxs)(`button`,{ref:b,id:v,type:`button`,role:`combobox`,"aria-haspopup":`listbox`,"aria-expanded":f,"aria-controls":y,"aria-activedescendant":f&&m>=0?`${y}-opt-${m}`:void 0,disabled:l,className:[t.default.trigger,f?t.default.triggerOpen:null].filter(Boolean).join(` `),onClick:()=>f?T():w(),onKeyDown:D,children:[(0,r.jsx)(`span`,{className:[t.default.triggerLabel,S?null:t.default.placeholder].filter(Boolean).join(` `),children:S?.label??c}),(0,r.jsx)(e.Icon,{icon:i.PanDown,size:`md`,"aria-hidden":!0,className:[t.default.chevron,f?t.default.chevronOpen:null].filter(Boolean).join(` `)})]}),f&&(0,r.jsx)(`ul`,{ref:x,id:y,role:`listbox`,"aria-labelledby":v,tabIndex:-1,className:[t.default.list,g?t.default.listUp:t.default.listDown].filter(Boolean).join(` `),onKeyDown:O,children:a.map((n,a)=>(0,r.jsxs)(`li`,{id:`${y}-opt-${a}`,role:`option`,"aria-selected":n.value===o,"aria-disabled":n.disabled,className:[t.default.option,n.value===o?t.default.optionSelected:null,a===m?t.default.optionActive:null,n.disabled?t.default.optionDisabled:null].filter(Boolean).join(` `),onMouseEnter:()=>!n.disabled&&h(a),onClick:()=>E(n),children:[(0,r.jsxs)(`span`,{className:t.default.optionText,children:[(0,r.jsx)(`span`,{className:t.default.optionLabel,children:n.label}),n.description&&(0,r.jsx)(`span`,{className:t.default.optionDesc,children:n.description})]}),n.value===o&&(0,r.jsx)(e.Icon,{icon:i.Check,size:`md`,"aria-hidden":!0,className:t.default.checkIcon})]},n.value))})]})};exports.Dropdown=a;
2
2
  //# sourceMappingURL=Dropdown.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"Dropdown.cjs","names":[],"sources":["../../../src/components/Dropdown/Dropdown.tsx"],"sourcesContent":["import { Check, PanDown } from '@gnome-ui/icons';\nimport {\n type HTMLAttributes,\n type KeyboardEvent,\n useCallback,\n useEffect,\n useId,\n useRef,\n useState,\n} from 'react';\n\nimport { Icon } from '../Icon';\n\nimport styles from './Dropdown.module.css';\n\nexport interface DropdownOption<V extends string = string> {\n /** The value submitted / returned on selection. */\n value: V;\n /** Display label shown in the list and trigger. */\n label: string;\n /** Optional descriptive text shown below the label. */\n description?: string;\n /** Whether the option is selectable. */\n disabled?: boolean;\n}\n\nexport interface DropdownProps<V extends string = string>\n extends Omit<HTMLAttributes<HTMLDivElement>, 'onChange'> {\n /** The list of selectable options. */\n options: DropdownOption<V>[];\n /** The currently selected value. */\n value?: V;\n /** Called when the user selects an option. */\n onChange?: (value: V) => void;\n /** Placeholder shown when no option is selected. */\n placeholder?: string;\n /** Accessible label for the control (used as `aria-label`). */\n 'aria-label'?: string;\n /** Disables the entire control. */\n disabled?: boolean;\n}\n\n/**\n * Expandable option list following the Adwaita combo-row / drop-down pattern.\n *\n * - Fully keyboard-navigable: Space/Enter opens; ↑/↓ navigate; Enter selects; Escape closes.\n * - Uses `role=\"combobox\"` + `role=\"listbox\"` + `role=\"option\"` ARIA pattern.\n * - Closes when clicking outside or pressing Escape.\n * - The list flips above the trigger when there is not enough space below.\n *\n * @see https://developer.gnome.org/hig/patterns/controls/drop-down-lists.html\n */\nexport const Dropdown = <V extends string = string>({\n options,\n value,\n onChange,\n placeholder = 'Select an option',\n disabled,\n className,\n ...props\n}: DropdownProps<V>) => {\n const [open, setOpen] = useState(false);\n const [activeIndex, setActiveIndex] = useState<number>(-1);\n const [flipUp, setFlipUp] = useState(false);\n\n const triggerId = useId();\n const listboxId = useId();\n const triggerRef = useRef<HTMLButtonElement>(null);\n const listRef = useRef<HTMLUListElement>(null);\n\n const selected = options.find((o) => o.value === value);\n\n // Compute flip direction when opening\n const computeFlip = useCallback(() => {\n if (!triggerRef.current) {\n return;\n }\n\n const rect = triggerRef.current.getBoundingClientRect();\n const spaceBelow = window.innerHeight - rect.bottom;\n const listH = Math.min(options.length * 48 + 8, 280);\n\n setFlipUp(spaceBelow < listH && rect.top > listH);\n }, [options.length]);\n\n const openList = useCallback(() => {\n if (disabled) {\n return;\n }\n\n computeFlip();\n setOpen(true);\n const idx = options.findIndex((o) => o.value === value && !o.disabled);\n\n setActiveIndex(idx >= 0 ? idx : options.findIndex((o) => !o.disabled));\n }, [disabled, computeFlip, options, value]);\n\n const closeList = useCallback(() => {\n setOpen(false);\n setActiveIndex(-1);\n triggerRef.current?.focus();\n }, []);\n\n const selectOption = useCallback(\n (opt: DropdownOption<V>) => {\n if (opt.disabled) {\n return;\n }\n\n onChange?.(opt.value);\n closeList();\n },\n [onChange, closeList],\n );\n\n // Scroll active item into view\n useEffect(() => {\n if (!open || activeIndex < 0 || !listRef.current) {\n return;\n }\n\n const item = listRef.current.children[activeIndex] as HTMLElement | undefined;\n\n item?.scrollIntoView({ block: 'nearest' });\n }, [activeIndex, open]);\n\n // Close on outside click\n useEffect(() => {\n if (!open) {\n return;\n }\n\n const handler = (e: MouseEvent) => {\n if (\n !triggerRef.current?.contains(e.target as Node) &&\n !listRef.current?.contains(e.target as Node)\n ) {\n closeList();\n }\n };\n\n document.addEventListener('mousedown', handler);\n\n return () => document.removeEventListener('mousedown', handler);\n }, [open, closeList]);\n\n // Trigger keyboard handler\n const handleTriggerKeyDown = useCallback(\n (e: KeyboardEvent<HTMLButtonElement>) => {\n switch (e.key) {\n case 'Enter':\n case ' ':\n case 'ArrowDown':\n e.preventDefault();\n openList();\n break;\n case 'ArrowUp':\n e.preventDefault();\n computeFlip();\n setOpen(true);\n setActiveIndex(options.reduce((last, o, i) => (!o.disabled ? i : last), -1));\n break;\n }\n },\n [openList, computeFlip, options],\n );\n\n // Listbox keyboard handler\n const handleListKeyDown = useCallback(\n (e: KeyboardEvent<HTMLUListElement>) => {\n const enabledIndexes = options\n .map((o, i) => ({ o, i }))\n .filter(({ o }) => !o.disabled)\n .map(({ i }) => i);\n\n const currentPos = enabledIndexes.indexOf(activeIndex);\n\n switch (e.key) {\n case 'ArrowDown': {\n e.preventDefault();\n const next = enabledIndexes[Math.min(currentPos + 1, enabledIndexes.length - 1)];\n\n setActiveIndex(next ?? activeIndex);\n break;\n }\n case 'ArrowUp': {\n e.preventDefault();\n const prev = enabledIndexes[Math.max(currentPos - 1, 0)];\n\n setActiveIndex(prev ?? activeIndex);\n break;\n }\n case 'Home':\n e.preventDefault();\n setActiveIndex(enabledIndexes[0] ?? -1);\n break;\n case 'End':\n e.preventDefault();\n setActiveIndex(enabledIndexes[enabledIndexes.length - 1] ?? -1);\n break;\n case 'Enter':\n case ' ': {\n e.preventDefault();\n if (activeIndex >= 0) {\n selectOption(options[activeIndex]);\n }\n\n break;\n }\n case 'Escape':\n case 'Tab':\n closeList();\n break;\n }\n },\n [options, activeIndex, selectOption, closeList],\n );\n\n return (\n <div className={[styles.wrapper, className].filter(Boolean).join(' ')} {...props}>\n {/* Trigger button */}\n <button\n ref={triggerRef}\n id={triggerId}\n type=\"button\"\n role=\"combobox\"\n aria-haspopup=\"listbox\"\n aria-expanded={open}\n aria-controls={listboxId}\n aria-activedescendant={\n open && activeIndex >= 0 ? `${listboxId}-opt-${activeIndex}` : undefined\n }\n disabled={disabled}\n className={[styles.trigger, open ? styles.triggerOpen : null].filter(Boolean).join(' ')}\n onClick={() => (open ? closeList() : openList())}\n onKeyDown={handleTriggerKeyDown}\n >\n <span\n className={[styles.triggerLabel, !selected ? styles.placeholder : null]\n .filter(Boolean)\n .join(' ')}\n >\n {selected?.label ?? placeholder}\n </span>\n <Icon\n icon={PanDown}\n size=\"md\"\n aria-hidden\n className={[styles.chevron, open ? styles.chevronOpen : null].filter(Boolean).join(' ')}\n />\n </button>\n\n {/* Listbox */}\n {open && (\n <ul\n ref={listRef}\n id={listboxId}\n role=\"listbox\"\n aria-labelledby={triggerId}\n tabIndex={-1}\n className={[styles.list, flipUp ? styles.listUp : styles.listDown]\n .filter(Boolean)\n .join(' ')}\n onKeyDown={handleListKeyDown}\n >\n {options.map((opt, i) => (\n <li\n key={opt.value}\n id={`${listboxId}-opt-${i}`}\n role=\"option\"\n aria-selected={opt.value === value}\n aria-disabled={opt.disabled}\n className={[\n styles.option,\n opt.value === value ? styles.optionSelected : null,\n i === activeIndex ? styles.optionActive : null,\n opt.disabled ? styles.optionDisabled : null,\n ]\n .filter(Boolean)\n .join(' ')}\n onMouseEnter={() => !opt.disabled && setActiveIndex(i)}\n onClick={() => selectOption(opt)}\n >\n <span className={styles.optionText}>\n <span className={styles.optionLabel}>{opt.label}</span>\n {opt.description && <span className={styles.optionDesc}>{opt.description}</span>}\n </span>\n {opt.value === value && (\n <Icon icon={Check} size=\"md\" aria-hidden className={styles.checkIcon} />\n )}\n </li>\n ))}\n </ul>\n )}\n </div>\n );\n};\n"],"mappings":"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"}
1
+ {"version":3,"file":"Dropdown.cjs","names":[],"sources":["../../../src/components/Dropdown/Dropdown.tsx"],"sourcesContent":["import { Check, PanDown } from '@gnome-ui/icons';\nimport {\n type HTMLAttributes,\n type KeyboardEvent,\n useCallback,\n useEffect,\n useId,\n useLayoutEffect,\n useRef,\n useState,\n} from 'react';\n\nimport { Icon } from '../Icon';\n\nimport styles from './Dropdown.module.css';\n\nexport interface DropdownOption<V extends string = string> {\n /** The value submitted / returned on selection. */\n value: V;\n /** Display label shown in the list and trigger. */\n label: string;\n /** Optional descriptive text shown below the label. */\n description?: string;\n /** Whether the option is selectable. */\n disabled?: boolean;\n}\n\nexport interface DropdownProps<V extends string = string>\n extends Omit<HTMLAttributes<HTMLDivElement>, 'onChange'> {\n /** The list of selectable options. */\n options: DropdownOption<V>[];\n /** The currently selected value. */\n value?: V;\n /** Called when the user selects an option. */\n onChange?: (value: V) => void;\n /** Placeholder shown when no option is selected. */\n placeholder?: string;\n /** Accessible label for the control (used as `aria-label`). */\n 'aria-label'?: string;\n /** Disables the entire control. */\n disabled?: boolean;\n}\n\n/**\n * Expandable option list following the Adwaita combo-row / drop-down pattern.\n *\n * - Fully keyboard-navigable: Space/Enter opens; ↑/↓ navigate; Enter selects; Escape closes.\n * - Uses `role=\"combobox\"` + `role=\"listbox\"` + `role=\"option\"` ARIA pattern.\n * - Closes when clicking outside or pressing Escape.\n * - The list flips above the trigger when there is not enough space below.\n *\n * @see https://developer.gnome.org/hig/patterns/controls/drop-down-lists.html\n */\nexport const Dropdown = <V extends string = string>({\n options,\n value,\n onChange,\n placeholder = 'Select an option',\n disabled,\n className,\n ...props\n}: DropdownProps<V>) => {\n const [open, setOpen] = useState(false);\n const [activeIndex, setActiveIndex] = useState<number>(-1);\n const [flipUp, setFlipUp] = useState(false);\n\n const triggerId = useId();\n const listboxId = useId();\n const triggerRef = useRef<HTMLButtonElement>(null);\n const listRef = useRef<HTMLUListElement>(null);\n\n const selected = options.find((o) => o.value === value);\n\n // Compute flip direction when opening\n const computeFlip = useCallback(() => {\n if (!triggerRef.current) {\n return;\n }\n\n const rect = triggerRef.current.getBoundingClientRect();\n const spaceBelow = window.innerHeight - rect.bottom;\n const listH = Math.min(options.length * 48 + 8, 280);\n\n setFlipUp(spaceBelow < listH && rect.top > listH);\n }, [options.length]);\n\n const openList = useCallback(() => {\n if (disabled) {\n return;\n }\n\n computeFlip();\n setOpen(true);\n const idx = options.findIndex((o) => o.value === value && !o.disabled);\n\n setActiveIndex(idx >= 0 ? idx : options.findIndex((o) => !o.disabled));\n }, [disabled, computeFlip, options, value]);\n\n const closeList = useCallback(() => {\n setOpen(false);\n setActiveIndex(-1);\n triggerRef.current?.focus();\n }, []);\n\n const selectOption = useCallback(\n (opt: DropdownOption<V>) => {\n if (opt.disabled) {\n return;\n }\n\n onChange?.(opt.value);\n closeList();\n },\n [onChange, closeList],\n );\n\n // Refine the flip with the real list height once it has rendered — the\n // pre-open estimate can't account for option descriptions or wrapped labels.\n useLayoutEffect(() => {\n if (!open || !triggerRef.current || !listRef.current) {\n return;\n }\n\n const rect = triggerRef.current.getBoundingClientRect();\n const listH = listRef.current.offsetHeight;\n\n setFlipUp(window.innerHeight - rect.bottom < listH && rect.top > listH);\n }, [open]);\n\n // Scroll active item into view\n useEffect(() => {\n if (!open || activeIndex < 0 || !listRef.current) {\n return;\n }\n\n const item = listRef.current.children[activeIndex] as HTMLElement | undefined;\n\n item?.scrollIntoView({ block: 'nearest' });\n }, [activeIndex, open]);\n\n // Close on outside click\n useEffect(() => {\n if (!open) {\n return;\n }\n\n const handler = (e: MouseEvent) => {\n if (\n !triggerRef.current?.contains(e.target as Node) &&\n !listRef.current?.contains(e.target as Node)\n ) {\n closeList();\n }\n };\n\n document.addEventListener('mousedown', handler);\n\n return () => document.removeEventListener('mousedown', handler);\n }, [open, closeList]);\n\n // Trigger keyboard handler\n const handleTriggerKeyDown = useCallback(\n (e: KeyboardEvent<HTMLButtonElement>) => {\n switch (e.key) {\n case 'Enter':\n case ' ':\n case 'ArrowDown':\n e.preventDefault();\n openList();\n break;\n case 'ArrowUp':\n e.preventDefault();\n computeFlip();\n setOpen(true);\n setActiveIndex(options.reduce((last, o, i) => (!o.disabled ? i : last), -1));\n break;\n }\n },\n [openList, computeFlip, options],\n );\n\n // Listbox keyboard handler\n const handleListKeyDown = useCallback(\n (e: KeyboardEvent<HTMLUListElement>) => {\n const enabledIndexes = options\n .map((o, i) => ({ o, i }))\n .filter(({ o }) => !o.disabled)\n .map(({ i }) => i);\n\n const currentPos = enabledIndexes.indexOf(activeIndex);\n\n switch (e.key) {\n case 'ArrowDown': {\n e.preventDefault();\n const next = enabledIndexes[Math.min(currentPos + 1, enabledIndexes.length - 1)];\n\n setActiveIndex(next ?? activeIndex);\n break;\n }\n case 'ArrowUp': {\n e.preventDefault();\n const prev = enabledIndexes[Math.max(currentPos - 1, 0)];\n\n setActiveIndex(prev ?? activeIndex);\n break;\n }\n case 'Home':\n e.preventDefault();\n setActiveIndex(enabledIndexes[0] ?? -1);\n break;\n case 'End':\n e.preventDefault();\n setActiveIndex(enabledIndexes[enabledIndexes.length - 1] ?? -1);\n break;\n case 'Enter':\n case ' ': {\n e.preventDefault();\n if (activeIndex >= 0) {\n selectOption(options[activeIndex]);\n }\n\n break;\n }\n case 'Escape':\n case 'Tab':\n closeList();\n break;\n }\n },\n [options, activeIndex, selectOption, closeList],\n );\n\n return (\n <div className={[styles.wrapper, className].filter(Boolean).join(' ')} {...props}>\n {/* Trigger button */}\n <button\n ref={triggerRef}\n id={triggerId}\n type=\"button\"\n role=\"combobox\"\n aria-haspopup=\"listbox\"\n aria-expanded={open}\n aria-controls={listboxId}\n aria-activedescendant={\n open && activeIndex >= 0 ? `${listboxId}-opt-${activeIndex}` : undefined\n }\n disabled={disabled}\n className={[styles.trigger, open ? styles.triggerOpen : null].filter(Boolean).join(' ')}\n onClick={() => (open ? closeList() : openList())}\n onKeyDown={handleTriggerKeyDown}\n >\n <span\n className={[styles.triggerLabel, !selected ? styles.placeholder : null]\n .filter(Boolean)\n .join(' ')}\n >\n {selected?.label ?? placeholder}\n </span>\n <Icon\n icon={PanDown}\n size=\"md\"\n aria-hidden\n className={[styles.chevron, open ? styles.chevronOpen : null].filter(Boolean).join(' ')}\n />\n </button>\n\n {/* Listbox */}\n {open && (\n <ul\n ref={listRef}\n id={listboxId}\n role=\"listbox\"\n aria-labelledby={triggerId}\n tabIndex={-1}\n className={[styles.list, flipUp ? styles.listUp : styles.listDown]\n .filter(Boolean)\n .join(' ')}\n onKeyDown={handleListKeyDown}\n >\n {options.map((opt, i) => (\n <li\n key={opt.value}\n id={`${listboxId}-opt-${i}`}\n role=\"option\"\n aria-selected={opt.value === value}\n aria-disabled={opt.disabled}\n className={[\n styles.option,\n opt.value === value ? styles.optionSelected : null,\n i === activeIndex ? styles.optionActive : null,\n opt.disabled ? styles.optionDisabled : null,\n ]\n .filter(Boolean)\n .join(' ')}\n onMouseEnter={() => !opt.disabled && setActiveIndex(i)}\n onClick={() => selectOption(opt)}\n >\n <span className={styles.optionText}>\n <span className={styles.optionLabel}>{opt.label}</span>\n {opt.description && <span className={styles.optionDesc}>{opt.description}</span>}\n </span>\n {opt.value === value && (\n <Icon icon={Check} size=\"md\" aria-hidden className={styles.checkIcon} />\n )}\n </li>\n ))}\n </ul>\n )}\n </div>\n );\n};\n"],"mappings":"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"}