@adea-ai/ui 0.70.1 → 0.70.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.
@@ -14,21 +14,30 @@ import { DialogContent } from '../dialog/dialog';
14
14
  * tab order and the accessibility tree without depending on that bookkeeping, and
15
15
  * it restores exactly the elements that did not already have it — so a nested
16
16
  * dialog does not un-inert its parent.
17
- * 3. **It labels itself explicitly.** The title registers its id for
18
- * `aria-labelledby` in a mount effect; if that registration is ever lost, the
19
- * dialog renders with a heading and no name. Passing the label as well costs
20
- * nothing and removes the race.
17
+ * 3. **It sets an explicit accessible name.** The content receives `aria-label`
18
+ * from `title`, so its name does not depend on the title id registration effect.
19
+ * Pass `aria-label` to use a different name while keeping the visible title;
20
+ * pass `aria-labelledby` when another visible label should take precedence.
21
21
  *
22
22
  * Use `Dialog` directly when you need its composition; use this when you want a
23
23
  * dialog that is correct by default.
24
24
  */
25
- export type ModalDialogProps = Omit<ComponentProps<typeof DialogContent>, 'children'> & {
25
+ export type ModalDialogProps = Omit<ComponentProps<typeof DialogContent>, 'children' | 'aria-label' | 'aria-labelledby'> & {
26
26
  open: boolean;
27
27
  onClose: () => void;
28
28
  title: string;
29
+ /**
30
+ * Controls Kobalte's focus, scroll and accessibility modality. Defaults to `true`;
31
+ * background body children remain inert in either mode.
32
+ */
33
+ modal?: boolean;
29
34
  description?: string;
30
35
  /** A mark or icon drawn before the title. */
31
36
  headerLeading?: JSX.Element;
37
+ /** Replaces `title` as the accessible name while keeping the visible title. */
38
+ 'aria-label'?: string;
39
+ /** Names the dialog from a visible element; ARIA gives it precedence over `aria-label`. */
40
+ 'aria-labelledby'?: string;
32
41
  children?: JSX.Element;
33
42
  };
34
43
  export declare function ModalDialog(props: ModalDialogProps): JSX.Element;
@@ -1 +1 @@
1
- {"version":3,"file":"modal-dialog.d.ts","sourceRoot":"","sources":["../../../../src/components/ui/modal-dialog/modal-dialog.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,GAAG,EAAE,MAAM,UAAU,CAAA;AAGnD,OAAO,EAEL,aAAa,EAId,MAAM,kBAAkB,CAAA;AAEzB;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,MAAM,MAAM,gBAAgB,GAAG,IAAI,CAAC,cAAc,CAAC,OAAO,aAAa,CAAC,EAAE,UAAU,CAAC,GAAG;IACtF,IAAI,EAAE,OAAO,CAAA;IACb,OAAO,EAAE,MAAM,IAAI,CAAA;IACnB,KAAK,EAAE,MAAM,CAAA;IACb,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,6CAA6C;IAC7C,aAAa,CAAC,EAAE,GAAG,CAAC,OAAO,CAAA;IAC3B,QAAQ,CAAC,EAAE,GAAG,CAAC,OAAO,CAAA;CACvB,CAAA;AAED,wBAAgB,WAAW,CAAC,KAAK,EAAE,gBAAgB,eAmDlD"}
1
+ {"version":3,"file":"modal-dialog.d.ts","sourceRoot":"","sources":["../../../../src/components/ui/modal-dialog/modal-dialog.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,GAAG,EAAE,MAAM,UAAU,CAAA;AAGnD,OAAO,EAEL,aAAa,EAId,MAAM,kBAAkB,CAAA;AAEzB;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,MAAM,MAAM,gBAAgB,GAAG,IAAI,CACjC,cAAc,CAAC,OAAO,aAAa,CAAC,EACpC,UAAU,GAAG,YAAY,GAAG,iBAAiB,CAC9C,GAAG;IACF,IAAI,EAAE,OAAO,CAAA;IACb,OAAO,EAAE,MAAM,IAAI,CAAA;IACnB,KAAK,EAAE,MAAM,CAAA;IACb;;;OAGG;IACH,KAAK,CAAC,EAAE,OAAO,CAAA;IACf,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,6CAA6C;IAC7C,aAAa,CAAC,EAAE,GAAG,CAAC,OAAO,CAAA;IAC3B,+EAA+E;IAC/E,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,2FAA2F;IAC3F,iBAAiB,CAAC,EAAE,MAAM,CAAA;IAC1B,QAAQ,CAAC,EAAE,GAAG,CAAC,OAAO,CAAA;CACvB,CAAA;AAED,wBAAgB,WAAW,CAAC,KAAK,EAAE,gBAAgB,eA8DlD"}
@@ -8,10 +8,13 @@ function ModalDialog(props) {
8
8
  "open",
9
9
  "onClose",
10
10
  "title",
11
+ "modal",
11
12
  "description",
12
13
  "headerLeading",
13
14
  "class",
14
- "children"
15
+ "children",
16
+ "aria-label",
17
+ "aria-labelledby"
15
18
  ]);
16
19
  const [content, setContent] = createSignal();
17
20
  createEffect(() => {
@@ -34,12 +37,21 @@ function ModalDialog(props) {
34
37
  get children() {
35
38
  return createComponent(Dialog, {
36
39
  open: true,
40
+ get modal() {
41
+ return local.modal ?? true;
42
+ },
37
43
  onOpenChange: (next) => !next && local.onClose(),
38
44
  get children() {
39
45
  return createComponent(DialogContent, mergeProps({
40
46
  ref: setContent,
41
47
  get ["class"]() {
42
48
  return cn("gap-4", local.class);
49
+ },
50
+ get ["aria-label"]() {
51
+ return local["aria-label"] ?? local.title;
52
+ },
53
+ get ["aria-labelledby"]() {
54
+ return local["aria-labelledby"] ?? (local["aria-label"] !== void 0 ? "" : void 0);
43
55
  }
44
56
  }, rest, { get children() {
45
57
  return [createComponent(DialogHeader, { get children() {
@@ -1 +1 @@
1
- {"version":3,"file":"modal-dialog.js","names":["ComponentProps","JSX","Show","createEffect","createSignal","onCleanup","splitProps","cn","Dialog","DialogContent","DialogDescription","DialogHeader","DialogTitle","ModalDialogProps","Omit","open","onClose","title","description","headerLeading","Element","children","ModalDialog","props","local","rest","content","setContent","HTMLElement","element","background","document","body","filter","child","contains","wasInert","map","node","hasAttribute","setAttribute","forEach","index","removeAttribute","_$createComponent","when","onOpenChange","next","_$mergeProps","ref","class","_$memo","_$createComponent","_$mergeProps","_$memo"],"sources":["../../../../src/components/ui/modal-dialog/modal-dialog.tsx"],"sourcesContent":["import type { ComponentProps, JSX } from 'solid-js'\nimport { Show, createEffect, createSignal, onCleanup, splitProps } from 'solid-js'\nimport { cn } from '#lib/utils'\nimport {\n Dialog,\n DialogContent,\n DialogDescription,\n DialogHeader,\n DialogTitle,\n} from '../dialog/dialog'\n\n/**\n * ModalDialog.\n *\n * A dialog that owns three things `Dialog` leaves to its caller, each of which is a\n * real defect when it is missed:\n *\n * 1. **It unmounts while closed.** An always-mounted dialog that closes as a side\n * effect of an async action can leave Kobalte's `aria-hidden` bookkeeping behind,\n * which hides the rest of the application from assistive technology. Unmounting\n * the whole owner makes cleanup the only way the layer can end.\n * 2. **It marks the background `inert`.** `inert` keeps the background out of the\n * tab order and the accessibility tree without depending on that bookkeeping, and\n * it restores exactly the elements that did not already have it — so a nested\n * dialog does not un-inert its parent.\n * 3. **It labels itself explicitly.** The title registers its id for\n * `aria-labelledby` in a mount effect; if that registration is ever lost, the\n * dialog renders with a heading and no name. Passing the label as well costs\n * nothing and removes the race.\n *\n * Use `Dialog` directly when you need its composition; use this when you want a\n * dialog that is correct by default.\n */\nexport type ModalDialogProps = Omit<ComponentProps<typeof DialogContent>, 'children'> & {\n open: boolean\n onClose: () => void\n title: string\n description?: string\n /** A mark or icon drawn before the title. */\n headerLeading?: JSX.Element\n children?: JSX.Element\n}\n\nexport function ModalDialog(props: ModalDialogProps) {\n const [local, rest] = splitProps(props, [\n 'open',\n 'onClose',\n 'title',\n 'description',\n 'headerLeading',\n 'class',\n 'children',\n ])\n\n const [content, setContent] = createSignal<HTMLElement>()\n\n createEffect(() => {\n if (!local.open) return\n const element = content()\n if (!element) return\n\n const background = [...document.body.children].filter(\n (child): child is HTMLElement => child instanceof HTMLElement && !child.contains(element)\n )\n const wasInert = background.map((node) => node.hasAttribute('inert'))\n for (const node of background) node.setAttribute('inert', '')\n\n onCleanup(() => {\n background.forEach((node, index) => {\n // Only the elements that were not already inert are restored, so a nested\n // dialog closing does not undo its parent's containment.\n if (!wasInert[index]) node.removeAttribute('inert')\n })\n })\n })\n\n return (\n <Show when={local.open}>\n <Dialog open onOpenChange={(next) => !next && local.onClose()}>\n <DialogContent ref={setContent} class={cn('gap-4', local.class)} {...rest}>\n <DialogHeader>\n <DialogTitle class=\"flex items-center gap-2\">\n <Show when={local.headerLeading}>{local.headerLeading}</Show>\n {local.title}\n </DialogTitle>\n <Show when={local.description}>\n <DialogDescription>{local.description}</DialogDescription>\n </Show>\n </DialogHeader>\n {local.children}\n </DialogContent>\n </Dialog>\n </Show>\n )\n}\n"],"mappings":";;;;;AA2CA,SAAgBsB,YAAYC,OAAyB;CACnD,MAAM,CAACC,OAAOC,QAAQnB,WAAWiB,OAAO;EACtC;EACA;EACA;EACA;EACA;EACA;EACA;CAAU,CACX;CAED,MAAM,CAACG,SAASC,cAAcvB,aAA0B;CAExDD,mBAAmB;EACjB,IAAI,CAACqB,MAAMT,MAAM;EACjB,MAAMc,UAAUH,QAAQ;EACxB,IAAI,CAACG,SAAS;EAEd,MAAMC,aAAa,CAAC,GAAGC,SAASC,KAAKX,QAAQ,CAAC,CAACY,QAC5CC,UAAgCA,iBAAiBN,eAAe,CAACM,MAAMC,SAASN,OAAO,CAC1F;EACA,MAAMO,WAAWN,WAAWO,KAAKC,SAASA,KAAKC,aAAa,OAAO,CAAC;EACpE,KAAK,MAAMD,QAAQR,YAAYQ,KAAKE,aAAa,SAAS,EAAE;EAE5DnC,gBAAgB;GACdyB,WAAWW,SAASH,MAAMI,UAAU;IAGlC,IAAI,CAACN,SAASM,QAAQJ,KAAKK,gBAAgB,OAAO;GACpD,CAAC;EACH,CAAC;CACH,CAAC;CAED,OAAAC,gBACG1C,MAAI;EAAA,IAAC2C,OAAI;GAAA,OAAErB,MAAMT;EAAI;EAAA,IAAAM,WAAA;GAAA,OAAAuB,gBACnBpC,QAAM;IAACO,MAAI;IAAC+B,eAAeC,SAAS,CAACA,QAAQvB,MAAMR,QAAQ;IAAC,IAAAK,WAAA;KAAA,OAAAuB,gBAC1DnC,eAAauC,WAAA;MAAAC,KAAMtB;MAAU,KAAA,WAAA;OAAA,OAASpB,GAAG,SAASiB,MAAM0B,KAAK;MAAC;KAAA,GAAMzB,MAAI,EAAA,IAAAJ,WAAA;MAAA,OAAA,CAAAuB,gBACtEjC,cAAY,EAAA,IAAAU,WAAA;OAAA,OAAA,CAAAuB,gBACVhC,aAAW;QAAA,SAAA;QAAA,IAAAS,WAAA;SAAA,OAAA,CAAAuB,gBACT1C,MAAI;UAAA,IAAC2C,OAAI;WAAA,OAAErB,MAAML;UAAa;UAAA,IAAAE,WAAA;WAAA,OAAGG,MAAML;UAAa;SAAA,CAAA,GAAAgC,WACpD3B,MAAMP,KAAK,CAAA;QAAA;OAAA,CAAA,GAAA2B,gBAEb1C,MAAI;QAAA,IAAC2C,OAAI;SAAA,OAAErB,MAAMN;QAAW;QAAA,IAAAG,WAAA;SAAA,OAAAuB,gBAC1BlC,mBAAiB,EAAA,IAAAW,WAAA;UAAA,OAAEG,MAAMN;SAAW,EAAA,CAAA;QAAA;OAAA,CAAA,CAAA;MAAA,EAAA,CAAA,GAAAiC,WAGxC3B,MAAMH,QAAQ,CAAA;KAAA,EAAA,CAAA,CAAA;IAAA;GAAA,CAAA;EAAA;CAAA,CAAA;AAKzB"}
1
+ {"version":3,"file":"modal-dialog.js","names":["ComponentProps","JSX","Show","createEffect","createSignal","onCleanup","splitProps","cn","Dialog","DialogContent","DialogDescription","DialogHeader","DialogTitle","ModalDialogProps","Omit","open","onClose","title","modal","description","headerLeading","Element","children","ModalDialog","props","local","rest","content","setContent","HTMLElement","element","background","document","body","filter","child","contains","wasInert","map","node","hasAttribute","setAttribute","forEach","index","removeAttribute","_$createComponent","when","onOpenChange","next","_$mergeProps","ref","class","aria-label","aria-labelledby","undefined","_$memo","_$createComponent","_$mergeProps","_$memo"],"sources":["../../../../src/components/ui/modal-dialog/modal-dialog.tsx"],"sourcesContent":["import type { ComponentProps, JSX } from 'solid-js'\nimport { Show, createEffect, createSignal, onCleanup, splitProps } from 'solid-js'\nimport { cn } from '#lib/utils'\nimport {\n Dialog,\n DialogContent,\n DialogDescription,\n DialogHeader,\n DialogTitle,\n} from '../dialog/dialog'\n\n/**\n * ModalDialog.\n *\n * A dialog that owns three things `Dialog` leaves to its caller, each of which is a\n * real defect when it is missed:\n *\n * 1. **It unmounts while closed.** An always-mounted dialog that closes as a side\n * effect of an async action can leave Kobalte's `aria-hidden` bookkeeping behind,\n * which hides the rest of the application from assistive technology. Unmounting\n * the whole owner makes cleanup the only way the layer can end.\n * 2. **It marks the background `inert`.** `inert` keeps the background out of the\n * tab order and the accessibility tree without depending on that bookkeeping, and\n * it restores exactly the elements that did not already have it — so a nested\n * dialog does not un-inert its parent.\n * 3. **It sets an explicit accessible name.** The content receives `aria-label`\n * from `title`, so its name does not depend on the title id registration effect.\n * Pass `aria-label` to use a different name while keeping the visible title;\n * pass `aria-labelledby` when another visible label should take precedence.\n *\n * Use `Dialog` directly when you need its composition; use this when you want a\n * dialog that is correct by default.\n */\nexport type ModalDialogProps = Omit<\n ComponentProps<typeof DialogContent>,\n 'children' | 'aria-label' | 'aria-labelledby'\n> & {\n open: boolean\n onClose: () => void\n title: string\n /**\n * Controls Kobalte's focus, scroll and accessibility modality. Defaults to `true`;\n * background body children remain inert in either mode.\n */\n modal?: boolean\n description?: string\n /** A mark or icon drawn before the title. */\n headerLeading?: JSX.Element\n /** Replaces `title` as the accessible name while keeping the visible title. */\n 'aria-label'?: string\n /** Names the dialog from a visible element; ARIA gives it precedence over `aria-label`. */\n 'aria-labelledby'?: string\n children?: JSX.Element\n}\n\nexport function ModalDialog(props: ModalDialogProps) {\n const [local, rest] = splitProps(props, [\n 'open',\n 'onClose',\n 'title',\n 'modal',\n 'description',\n 'headerLeading',\n 'class',\n 'children',\n 'aria-label',\n 'aria-labelledby',\n ])\n\n const [content, setContent] = createSignal<HTMLElement>()\n\n createEffect(() => {\n if (!local.open) return\n const element = content()\n if (!element) return\n\n const background = [...document.body.children].filter(\n (child): child is HTMLElement => child instanceof HTMLElement && !child.contains(element)\n )\n const wasInert = background.map((node) => node.hasAttribute('inert'))\n for (const node of background) node.setAttribute('inert', '')\n\n onCleanup(() => {\n background.forEach((node, index) => {\n // Only the elements that were not already inert are restored, so a nested\n // dialog closing does not undo its parent's containment.\n if (!wasInert[index]) node.removeAttribute('inert')\n })\n })\n })\n\n return (\n <Show when={local.open}>\n <Dialog open modal={local.modal ?? true} onOpenChange={(next) => !next && local.onClose()}>\n <DialogContent\n ref={setContent}\n class={cn('gap-4', local.class)}\n aria-label={local['aria-label'] ?? local.title}\n aria-labelledby={\n local['aria-labelledby'] ?? (local['aria-label'] !== undefined ? '' : undefined)\n }\n {...rest}\n >\n <DialogHeader>\n <DialogTitle class=\"flex items-center gap-2\">\n <Show when={local.headerLeading}>{local.headerLeading}</Show>\n {local.title}\n </DialogTitle>\n <Show when={local.description}>\n <DialogDescription>{local.description}</DialogDescription>\n </Show>\n </DialogHeader>\n {local.children}\n </DialogContent>\n </Dialog>\n </Show>\n )\n}\n"],"mappings":";;;;;AAuDA,SAAgBuB,YAAYC,OAAyB;CACnD,MAAM,CAACC,OAAOC,QAAQpB,WAAWkB,OAAO;EACtC;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;CAAiB,CAClB;CAED,MAAM,CAACG,SAASC,cAAcxB,aAA0B;CAExDD,mBAAmB;EACjB,IAAI,CAACsB,MAAMV,MAAM;EACjB,MAAMe,UAAUH,QAAQ;EACxB,IAAI,CAACG,SAAS;EAEd,MAAMC,aAAa,CAAC,GAAGC,SAASC,KAAKX,QAAQ,CAAC,CAACY,QAC5CC,UAAgCA,iBAAiBN,eAAe,CAACM,MAAMC,SAASN,OAAO,CAC1F;EACA,MAAMO,WAAWN,WAAWO,KAAKC,SAASA,KAAKC,aAAa,OAAO,CAAC;EACpE,KAAK,MAAMD,QAAQR,YAAYQ,KAAKE,aAAa,SAAS,EAAE;EAE5DpC,gBAAgB;GACd0B,WAAWW,SAASH,MAAMI,UAAU;IAGlC,IAAI,CAACN,SAASM,QAAQJ,KAAKK,gBAAgB,OAAO;GACpD,CAAC;EACH,CAAC;CACH,CAAC;CAED,OAAAC,gBACG3C,MAAI;EAAA,IAAC4C,OAAI;GAAA,OAAErB,MAAMV;EAAI;EAAA,IAAAO,WAAA;GAAA,OAAAuB,gBACnBrC,QAAM;IAACO,MAAI;IAAA,IAACG,QAAK;KAAA,OAAEO,MAAMP,SAAS;IAAI;IAAE6B,eAAeC,SAAS,CAACA,QAAQvB,MAAMT,QAAQ;IAAC,IAAAM,WAAA;KAAA,OAAAuB,gBACtFpC,eAAawC,WAAA;MAAAC,KACPtB;MAAU,KAAA,WAAA;OAAA,OACRrB,GAAG,SAASkB,MAAM0B,KAAK;MAAC;MAAA,KAAA,gBAAA;OAAA,OACnB1B,MAAM,iBAAiBA,MAAMR;MAAK;MAAA,KAAA,qBAAA;OAAA,OAE5CQ,MAAM,uBAAuBA,MAAM,kBAAkB6B,KAAAA,IAAY,KAAKA,KAAAA;MAAU;KAAA,GAE9E5B,MAAI,EAAA,IAAAJ,WAAA;MAAA,OAAA,CAAAuB,gBAEPlC,cAAY,EAAA,IAAAW,WAAA;OAAA,OAAA,CAAAuB,gBACVjC,aAAW;QAAA,SAAA;QAAA,IAAAU,WAAA;SAAA,OAAA,CAAAuB,gBACT3C,MAAI;UAAA,IAAC4C,OAAI;WAAA,OAAErB,MAAML;UAAa;UAAA,IAAAE,WAAA;WAAA,OAAGG,MAAML;UAAa;SAAA,CAAA,GAAAmC,WACpD9B,MAAMR,KAAK,CAAA;QAAA;OAAA,CAAA,GAAA4B,gBAEb3C,MAAI;QAAA,IAAC4C,OAAI;SAAA,OAAErB,MAAMN;QAAW;QAAA,IAAAG,WAAA;SAAA,OAAAuB,gBAC1BnC,mBAAiB,EAAA,IAAAY,WAAA;UAAA,OAAEG,MAAMN;SAAW,EAAA,CAAA;QAAA;OAAA,CAAA,CAAA;MAAA,EAAA,CAAA,GAAAoC,WAGxC9B,MAAMH,QAAQ,CAAA;KAAA,EAAA,CAAA,CAAA;IAAA;GAAA,CAAA;EAAA;CAAA,CAAA;AAKzB"}
@@ -23,21 +23,33 @@ import {
23
23
  * tab order and the accessibility tree without depending on that bookkeeping, and
24
24
  * it restores exactly the elements that did not already have it — so a nested
25
25
  * dialog does not un-inert its parent.
26
- * 3. **It labels itself explicitly.** The title registers its id for
27
- * `aria-labelledby` in a mount effect; if that registration is ever lost, the
28
- * dialog renders with a heading and no name. Passing the label as well costs
29
- * nothing and removes the race.
26
+ * 3. **It sets an explicit accessible name.** The content receives `aria-label`
27
+ * from `title`, so its name does not depend on the title id registration effect.
28
+ * Pass `aria-label` to use a different name while keeping the visible title;
29
+ * pass `aria-labelledby` when another visible label should take precedence.
30
30
  *
31
31
  * Use `Dialog` directly when you need its composition; use this when you want a
32
32
  * dialog that is correct by default.
33
33
  */
34
- export type ModalDialogProps = Omit<ComponentProps<typeof DialogContent>, 'children'> & {
34
+ export type ModalDialogProps = Omit<
35
+ ComponentProps<typeof DialogContent>,
36
+ 'children' | 'aria-label' | 'aria-labelledby'
37
+ > & {
35
38
  open: boolean
36
39
  onClose: () => void
37
40
  title: string
41
+ /**
42
+ * Controls Kobalte's focus, scroll and accessibility modality. Defaults to `true`;
43
+ * background body children remain inert in either mode.
44
+ */
45
+ modal?: boolean
38
46
  description?: string
39
47
  /** A mark or icon drawn before the title. */
40
48
  headerLeading?: JSX.Element
49
+ /** Replaces `title` as the accessible name while keeping the visible title. */
50
+ 'aria-label'?: string
51
+ /** Names the dialog from a visible element; ARIA gives it precedence over `aria-label`. */
52
+ 'aria-labelledby'?: string
41
53
  children?: JSX.Element
42
54
  }
43
55
 
@@ -46,10 +58,13 @@ export function ModalDialog(props: ModalDialogProps) {
46
58
  'open',
47
59
  'onClose',
48
60
  'title',
61
+ 'modal',
49
62
  'description',
50
63
  'headerLeading',
51
64
  'class',
52
65
  'children',
66
+ 'aria-label',
67
+ 'aria-labelledby',
53
68
  ])
54
69
 
55
70
  const [content, setContent] = createSignal<HTMLElement>()
@@ -76,8 +91,16 @@ export function ModalDialog(props: ModalDialogProps) {
76
91
 
77
92
  return (
78
93
  <Show when={local.open}>
79
- <Dialog open onOpenChange={(next) => !next && local.onClose()}>
80
- <DialogContent ref={setContent} class={cn('gap-4', local.class)} {...rest}>
94
+ <Dialog open modal={local.modal ?? true} onOpenChange={(next) => !next && local.onClose()}>
95
+ <DialogContent
96
+ ref={setContent}
97
+ class={cn('gap-4', local.class)}
98
+ aria-label={local['aria-label'] ?? local.title}
99
+ aria-labelledby={
100
+ local['aria-labelledby'] ?? (local['aria-label'] !== undefined ? '' : undefined)
101
+ }
102
+ {...rest}
103
+ >
81
104
  <DialogHeader>
82
105
  <DialogTitle class="flex items-center gap-2">
83
106
  <Show when={local.headerLeading}>{local.headerLeading}</Show>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adea-ai/ui",
3
- "version": "0.70.1",
3
+ "version": "0.70.2",
4
4
  "description": "The Adea design system: Kobalte-backed Solid components, semantic OKLCH tokens, and the app-shell layout primitives shared by Adea and Cortana.",
5
5
  "keywords": [
6
6
  "adea",
@@ -23,21 +23,33 @@ import {
23
23
  * tab order and the accessibility tree without depending on that bookkeeping, and
24
24
  * it restores exactly the elements that did not already have it — so a nested
25
25
  * dialog does not un-inert its parent.
26
- * 3. **It labels itself explicitly.** The title registers its id for
27
- * `aria-labelledby` in a mount effect; if that registration is ever lost, the
28
- * dialog renders with a heading and no name. Passing the label as well costs
29
- * nothing and removes the race.
26
+ * 3. **It sets an explicit accessible name.** The content receives `aria-label`
27
+ * from `title`, so its name does not depend on the title id registration effect.
28
+ * Pass `aria-label` to use a different name while keeping the visible title;
29
+ * pass `aria-labelledby` when another visible label should take precedence.
30
30
  *
31
31
  * Use `Dialog` directly when you need its composition; use this when you want a
32
32
  * dialog that is correct by default.
33
33
  */
34
- export type ModalDialogProps = Omit<ComponentProps<typeof DialogContent>, 'children'> & {
34
+ export type ModalDialogProps = Omit<
35
+ ComponentProps<typeof DialogContent>,
36
+ 'children' | 'aria-label' | 'aria-labelledby'
37
+ > & {
35
38
  open: boolean
36
39
  onClose: () => void
37
40
  title: string
41
+ /**
42
+ * Controls Kobalte's focus, scroll and accessibility modality. Defaults to `true`;
43
+ * background body children remain inert in either mode.
44
+ */
45
+ modal?: boolean
38
46
  description?: string
39
47
  /** A mark or icon drawn before the title. */
40
48
  headerLeading?: JSX.Element
49
+ /** Replaces `title` as the accessible name while keeping the visible title. */
50
+ 'aria-label'?: string
51
+ /** Names the dialog from a visible element; ARIA gives it precedence over `aria-label`. */
52
+ 'aria-labelledby'?: string
41
53
  children?: JSX.Element
42
54
  }
43
55
 
@@ -46,10 +58,13 @@ export function ModalDialog(props: ModalDialogProps) {
46
58
  'open',
47
59
  'onClose',
48
60
  'title',
61
+ 'modal',
49
62
  'description',
50
63
  'headerLeading',
51
64
  'class',
52
65
  'children',
66
+ 'aria-label',
67
+ 'aria-labelledby',
53
68
  ])
54
69
 
55
70
  const [content, setContent] = createSignal<HTMLElement>()
@@ -76,8 +91,16 @@ export function ModalDialog(props: ModalDialogProps) {
76
91
 
77
92
  return (
78
93
  <Show when={local.open}>
79
- <Dialog open onOpenChange={(next) => !next && local.onClose()}>
80
- <DialogContent ref={setContent} class={cn('gap-4', local.class)} {...rest}>
94
+ <Dialog open modal={local.modal ?? true} onOpenChange={(next) => !next && local.onClose()}>
95
+ <DialogContent
96
+ ref={setContent}
97
+ class={cn('gap-4', local.class)}
98
+ aria-label={local['aria-label'] ?? local.title}
99
+ aria-labelledby={
100
+ local['aria-labelledby'] ?? (local['aria-label'] !== undefined ? '' : undefined)
101
+ }
102
+ {...rest}
103
+ >
81
104
  <DialogHeader>
82
105
  <DialogTitle class="flex items-center gap-2">
83
106
  <Show when={local.headerLeading}>{local.headerLeading}</Show>