@primitiv-ui/web 0.1.53 → 0.1.55
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +69 -4
- package/dist/cdn/accordion.js +1 -1
- package/dist/cdn/chunks/chunk-EDLRMP2K.js +1 -0
- package/dist/cdn/chunks/chunk-HXGMU6SD.js +1 -0
- package/dist/cdn/chunks/chunk-PKW6AKXD.js +1 -0
- package/dist/cdn/chunks/chunk-QVNDDONZ.js +1 -0
- package/dist/cdn/collapsible.js +1 -1
- package/dist/cdn/modal.js +1 -0
- package/dist/cdn/popover.js +1 -0
- package/dist/cdn/tabs.js +1 -1
- package/dist/custom-elements.json +749 -0
- package/dist/define/index.d.ts +2 -0
- package/dist/define/index.js +2 -0
- package/dist/define/modal.d.ts +1 -0
- package/dist/define/modal.js +11 -0
- package/dist/define/popover.d.ts +1 -0
- package/dist/define/popover.js +11 -0
- package/dist/frameworks/events.d.ts +8 -0
- package/dist/frameworks/react.d.ts +25 -2
- package/dist/frameworks/svelte.d.ts +22 -1
- package/dist/frameworks/vue.d.ts +22 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.js +2 -0
- package/dist/modal/ModalController.d.ts +58 -0
- package/dist/modal/ModalController.js +91 -0
- package/dist/modal/elements.d.ts +86 -0
- package/dist/modal/elements.js +235 -0
- package/dist/modal/index.d.ts +2 -0
- package/dist/modal/index.js +2 -0
- package/dist/popover/PopoverController.d.ts +55 -0
- package/dist/popover/PopoverController.js +100 -0
- package/dist/popover/elements.d.ts +98 -0
- package/dist/popover/elements.js +230 -0
- package/dist/popover/index.d.ts +2 -0
- package/dist/popover/index.js +2 -0
- package/dist/primitiv-web.js +1 -1
- package/dist/tags.d.ts +15 -0
- package/package.json +18 -2
- package/dist/cdn/chunks/chunk-WVI5UEAW.js +0 -1
package/dist/define/index.d.ts
CHANGED
package/dist/define/index.js
CHANGED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Side-effect entry: importing this module defines the six Modal elements
|
|
3
|
+
* (`<primitiv-modal>`, `-trigger`, `-content`, `-title`, `-description`,
|
|
4
|
+
* `-close`) on the global `customElements` registry.
|
|
5
|
+
*
|
|
6
|
+
* ```html
|
|
7
|
+
* <script type="module">import "@primitiv-ui/web/define/modal";</script>
|
|
8
|
+
* ```
|
|
9
|
+
*/
|
|
10
|
+
import { defineModal } from "../modal/elements.js";
|
|
11
|
+
defineModal();
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Side-effect entry: importing this module defines the seven Popover elements
|
|
3
|
+
* (`<primitiv-popover>`, `-trigger`, `-anchor`, `-content`, `-title`,
|
|
4
|
+
* `-description`, `-close`) on the global `customElements` registry.
|
|
5
|
+
*
|
|
6
|
+
* ```html
|
|
7
|
+
* <script type="module">import "@primitiv-ui/web/define/popover";</script>
|
|
8
|
+
* ```
|
|
9
|
+
*/
|
|
10
|
+
import { definePopover } from "../popover/elements.js";
|
|
11
|
+
definePopover();
|
|
@@ -15,3 +15,11 @@ export type PrimitivAccordionChangeEvent = CustomEvent<{
|
|
|
15
15
|
export type PrimitivCollapsibleChangeEvent = CustomEvent<{
|
|
16
16
|
open: boolean;
|
|
17
17
|
}>;
|
|
18
|
+
/** The `primitiv-modal-change` event: bubbles, cancelable, fired before the change applies, with the open state afterwards. */
|
|
19
|
+
export type PrimitivModalChangeEvent = CustomEvent<{
|
|
20
|
+
open: boolean;
|
|
21
|
+
}>;
|
|
22
|
+
/** The `primitiv-popover-change` event: bubbles, cancelable, fired before the change applies, with the open state afterwards. */
|
|
23
|
+
export type PrimitivPopoverChangeEvent = CustomEvent<{
|
|
24
|
+
open: boolean;
|
|
25
|
+
}>;
|
|
@@ -20,13 +20,15 @@
|
|
|
20
20
|
import type { DetailedHTMLProps, HTMLAttributes } from "react";
|
|
21
21
|
import type { PrimitivAccordionContentElement, PrimitivAccordionElement, PrimitivAccordionItemElement, PrimitivAccordionTriggerElement, PrimitivAccordionTriggerIconElement } from "../accordion/elements.js";
|
|
22
22
|
import type { PrimitivCollapsibleContentElement, PrimitivCollapsibleElement, PrimitivCollapsibleTriggerElement, PrimitivCollapsibleTriggerIconElement } from "../collapsible/elements.js";
|
|
23
|
+
import type { PrimitivModalCloseElement, PrimitivModalContentElement, PrimitivModalDescriptionElement, PrimitivModalElement, PrimitivModalTitleElement, PrimitivModalTriggerElement } from "../modal/elements.js";
|
|
24
|
+
import type { PrimitivPopoverAnchorElement, PrimitivPopoverCloseElement, PrimitivPopoverContentElement, PrimitivPopoverDescriptionElement, PrimitivPopoverElement, PrimitivPopoverTitleElement, PrimitivPopoverTriggerElement } from "../popover/elements.js";
|
|
23
25
|
import type { PrimitivTabsContentElement, PrimitivTabsElement, PrimitivTabsListElement, PrimitivTabsTriggerElement } from "../tabs/elements.js";
|
|
24
|
-
import type { PrimitivAccordionChangeEvent, PrimitivCollapsibleChangeEvent, PrimitivTabsChangeEvent } from "./events.js";
|
|
26
|
+
import type { PrimitivAccordionChangeEvent, PrimitivCollapsibleChangeEvent, PrimitivModalChangeEvent, PrimitivPopoverChangeEvent, PrimitivTabsChangeEvent } from "./events.js";
|
|
25
27
|
/** React's own props for an element, plus `class`, which React 19 passes to custom elements. */
|
|
26
28
|
type ElementProps<E extends HTMLElement> = DetailedHTMLProps<HTMLAttributes<E>, E> & {
|
|
27
29
|
class?: string;
|
|
28
30
|
};
|
|
29
|
-
export type { PrimitivAccordionChangeEvent, PrimitivCollapsibleChangeEvent, PrimitivTabsChangeEvent };
|
|
31
|
+
export type { PrimitivAccordionChangeEvent, PrimitivCollapsibleChangeEvent, PrimitivModalChangeEvent, PrimitivPopoverChangeEvent, PrimitivTabsChangeEvent, };
|
|
30
32
|
declare module "react" {
|
|
31
33
|
namespace JSX {
|
|
32
34
|
interface IntrinsicElements {
|
|
@@ -59,6 +61,27 @@ declare module "react" {
|
|
|
59
61
|
"onprimitiv-collapsible-show"?: (event: CustomEvent) => void;
|
|
60
62
|
};
|
|
61
63
|
"primitiv-collapsible-trigger-icon": ElementProps<PrimitivCollapsibleTriggerIconElement>;
|
|
64
|
+
"primitiv-modal": ElementProps<PrimitivModalElement> & {
|
|
65
|
+
open?: boolean;
|
|
66
|
+
"onprimitiv-modal-change"?: (event: PrimitivModalChangeEvent) => void;
|
|
67
|
+
};
|
|
68
|
+
"primitiv-modal-trigger": ElementProps<PrimitivModalTriggerElement>;
|
|
69
|
+
"primitiv-modal-content": ElementProps<PrimitivModalContentElement>;
|
|
70
|
+
"primitiv-modal-title": ElementProps<PrimitivModalTitleElement>;
|
|
71
|
+
"primitiv-modal-description": ElementProps<PrimitivModalDescriptionElement>;
|
|
72
|
+
"primitiv-modal-close": ElementProps<PrimitivModalCloseElement>;
|
|
73
|
+
"primitiv-popover": ElementProps<PrimitivPopoverElement> & {
|
|
74
|
+
open?: boolean;
|
|
75
|
+
"onprimitiv-popover-change"?: (event: PrimitivPopoverChangeEvent) => void;
|
|
76
|
+
};
|
|
77
|
+
"primitiv-popover-trigger": ElementProps<PrimitivPopoverTriggerElement>;
|
|
78
|
+
"primitiv-popover-anchor": ElementProps<PrimitivPopoverAnchorElement>;
|
|
79
|
+
"primitiv-popover-title": ElementProps<PrimitivPopoverTitleElement>;
|
|
80
|
+
"primitiv-popover-description": ElementProps<PrimitivPopoverDescriptionElement>;
|
|
81
|
+
"primitiv-popover-close": ElementProps<PrimitivPopoverCloseElement>;
|
|
82
|
+
"primitiv-popover-content": ElementProps<PrimitivPopoverContentElement> & {
|
|
83
|
+
popover?: "auto" | "manual" | "";
|
|
84
|
+
};
|
|
62
85
|
"primitiv-tabs": ElementProps<PrimitivTabsElement> & {
|
|
63
86
|
value?: string;
|
|
64
87
|
orientation?: "horizontal" | "vertical";
|
|
@@ -19,8 +19,10 @@
|
|
|
19
19
|
import type { HTMLAttributes } from "svelte/elements";
|
|
20
20
|
import type { PrimitivAccordionContentElement, PrimitivAccordionElement, PrimitivAccordionItemElement, PrimitivAccordionTriggerElement, PrimitivAccordionTriggerIconElement } from "../accordion/elements.js";
|
|
21
21
|
import type { PrimitivCollapsibleContentElement, PrimitivCollapsibleElement, PrimitivCollapsibleTriggerElement, PrimitivCollapsibleTriggerIconElement } from "../collapsible/elements.js";
|
|
22
|
+
import type { PrimitivModalCloseElement, PrimitivModalContentElement, PrimitivModalDescriptionElement, PrimitivModalElement, PrimitivModalTitleElement, PrimitivModalTriggerElement } from "../modal/elements.js";
|
|
23
|
+
import type { PrimitivPopoverAnchorElement, PrimitivPopoverCloseElement, PrimitivPopoverContentElement, PrimitivPopoverDescriptionElement, PrimitivPopoverElement, PrimitivPopoverTitleElement, PrimitivPopoverTriggerElement } from "../popover/elements.js";
|
|
22
24
|
import type { PrimitivTabsContentElement, PrimitivTabsElement, PrimitivTabsListElement, PrimitivTabsTriggerElement } from "../tabs/elements.js";
|
|
23
|
-
import type { PrimitivAccordionChangeEvent, PrimitivCollapsibleChangeEvent, PrimitivTabsChangeEvent } from "./events.js";
|
|
25
|
+
import type { PrimitivAccordionChangeEvent, PrimitivCollapsibleChangeEvent, PrimitivModalChangeEvent, PrimitivPopoverChangeEvent, PrimitivTabsChangeEvent } from "./events.js";
|
|
24
26
|
/** Svelte's own attributes and events for an element. */
|
|
25
27
|
type Attributes<E extends HTMLElement> = HTMLAttributes<E>;
|
|
26
28
|
declare module "svelte/elements" {
|
|
@@ -54,6 +56,25 @@ declare module "svelte/elements" {
|
|
|
54
56
|
"onprimitiv-collapsible-show"?: (event: CustomEvent) => void;
|
|
55
57
|
};
|
|
56
58
|
"primitiv-collapsible-trigger-icon": Attributes<PrimitivCollapsibleTriggerIconElement>;
|
|
59
|
+
"primitiv-modal": Attributes<PrimitivModalElement> & {
|
|
60
|
+
open?: boolean;
|
|
61
|
+
"onprimitiv-modal-change"?: (event: PrimitivModalChangeEvent) => void;
|
|
62
|
+
};
|
|
63
|
+
"primitiv-modal-trigger": Attributes<PrimitivModalTriggerElement>;
|
|
64
|
+
"primitiv-modal-content": Attributes<PrimitivModalContentElement>;
|
|
65
|
+
"primitiv-modal-title": Attributes<PrimitivModalTitleElement>;
|
|
66
|
+
"primitiv-modal-description": Attributes<PrimitivModalDescriptionElement>;
|
|
67
|
+
"primitiv-modal-close": Attributes<PrimitivModalCloseElement>;
|
|
68
|
+
"primitiv-popover": Attributes<PrimitivPopoverElement> & {
|
|
69
|
+
open?: boolean;
|
|
70
|
+
"onprimitiv-popover-change"?: (event: PrimitivPopoverChangeEvent) => void;
|
|
71
|
+
};
|
|
72
|
+
"primitiv-popover-trigger": Attributes<PrimitivPopoverTriggerElement>;
|
|
73
|
+
"primitiv-popover-anchor": Attributes<PrimitivPopoverAnchorElement>;
|
|
74
|
+
"primitiv-popover-content": Attributes<PrimitivPopoverContentElement>;
|
|
75
|
+
"primitiv-popover-title": Attributes<PrimitivPopoverTitleElement>;
|
|
76
|
+
"primitiv-popover-description": Attributes<PrimitivPopoverDescriptionElement>;
|
|
77
|
+
"primitiv-popover-close": Attributes<PrimitivPopoverCloseElement>;
|
|
57
78
|
"primitiv-tabs": Attributes<PrimitivTabsElement> & {
|
|
58
79
|
value?: string;
|
|
59
80
|
orientation?: "horizontal" | "vertical";
|
package/dist/frameworks/vue.d.ts
CHANGED
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
* JSR does not allow a published module to do.
|
|
20
20
|
*/
|
|
21
21
|
import type { DefineComponent, EmitsOptions } from "vue";
|
|
22
|
-
import type { PrimitivAccordionChangeEvent, PrimitivCollapsibleChangeEvent, PrimitivTabsChangeEvent } from "./events.js";
|
|
22
|
+
import type { PrimitivAccordionChangeEvent, PrimitivCollapsibleChangeEvent, PrimitivModalChangeEvent, PrimitivPopoverChangeEvent, PrimitivTabsChangeEvent } from "./events.js";
|
|
23
23
|
/** A tag as Vue's template checker sees it: its props, and the events it emits. */
|
|
24
24
|
type Tag<Props, Events extends EmitsOptions = Record<never, never>> = DefineComponent<Props, Record<never, never>, Record<never, never>, Record<never, never>, Record<never, never>, Record<never, never>, Record<never, never>, Events>;
|
|
25
25
|
declare module "vue" {
|
|
@@ -57,6 +57,27 @@ declare module "vue" {
|
|
|
57
57
|
"primitiv-collapsible-show": (event: CustomEvent) => void;
|
|
58
58
|
}>;
|
|
59
59
|
"primitiv-collapsible-trigger-icon": Tag<Record<never, never>>;
|
|
60
|
+
"primitiv-modal": Tag<{
|
|
61
|
+
open?: boolean | "";
|
|
62
|
+
}, {
|
|
63
|
+
"primitiv-modal-change": (event: PrimitivModalChangeEvent) => void;
|
|
64
|
+
}>;
|
|
65
|
+
"primitiv-modal-trigger": Tag<Record<never, never>>;
|
|
66
|
+
"primitiv-modal-content": Tag<Record<never, never>>;
|
|
67
|
+
"primitiv-modal-title": Tag<Record<never, never>>;
|
|
68
|
+
"primitiv-modal-description": Tag<Record<never, never>>;
|
|
69
|
+
"primitiv-modal-close": Tag<Record<never, never>>;
|
|
70
|
+
"primitiv-popover": Tag<{
|
|
71
|
+
open?: boolean | "";
|
|
72
|
+
}, {
|
|
73
|
+
"primitiv-popover-change": (event: PrimitivPopoverChangeEvent) => void;
|
|
74
|
+
}>;
|
|
75
|
+
"primitiv-popover-trigger": Tag<Record<never, never>>;
|
|
76
|
+
"primitiv-popover-anchor": Tag<Record<never, never>>;
|
|
77
|
+
"primitiv-popover-content": Tag<Record<never, never>>;
|
|
78
|
+
"primitiv-popover-title": Tag<Record<never, never>>;
|
|
79
|
+
"primitiv-popover-description": Tag<Record<never, never>>;
|
|
80
|
+
"primitiv-popover-close": Tag<Record<never, never>>;
|
|
60
81
|
"primitiv-tabs": Tag<{
|
|
61
82
|
value?: string;
|
|
62
83
|
orientation?: "horizontal" | "vertical";
|
package/dist/index.d.ts
CHANGED
package/dist/index.js
CHANGED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What a {@link ModalController} asks of whoever hosts it. The controller owns
|
|
3
|
+
* behaviour and never owns state (RFC 0033 D28): the custom elements keep the
|
|
4
|
+
* open state in a reflected `open` attribute, the React adapter keeps it in
|
|
5
|
+
* React, and each answers these calls from wherever it keeps it.
|
|
6
|
+
*/
|
|
7
|
+
export type ModalControllerHost = {
|
|
8
|
+
/** The state the controller cannot see for itself. */
|
|
9
|
+
state(): {
|
|
10
|
+
/** Whether the modal is open. */
|
|
11
|
+
open: boolean;
|
|
12
|
+
};
|
|
13
|
+
/** Apply a change the user asked for: `open` is the state afterwards. */
|
|
14
|
+
change(open: boolean): void;
|
|
15
|
+
/**
|
|
16
|
+
* Escape was pressed (the dialog's native `cancel` event). Call
|
|
17
|
+
* `preventDefault()` on `event` to keep the modal open. Optional.
|
|
18
|
+
*/
|
|
19
|
+
escapeKeyDown?(event: Event): void;
|
|
20
|
+
/**
|
|
21
|
+
* A pointer went down outside the dialog's box — on the `::backdrop`. Call
|
|
22
|
+
* `preventDefault()` on `event` to keep the modal open. Optional.
|
|
23
|
+
*/
|
|
24
|
+
pointerDownOutside?(event: PointerEvent): void;
|
|
25
|
+
};
|
|
26
|
+
/**
|
|
27
|
+
* The DOM behaviour of a modal dialog, over a native `<dialog>` and its
|
|
28
|
+
* `showModal()`: the browser owns the inert background, the top layer and
|
|
29
|
+
* Escape, and this adds what it leaves out (WAI-ARIA APG Modal Dialog):
|
|
30
|
+
*
|
|
31
|
+
* - **Sync** — {@link ModalController.sync | `sync()`} shows the dialog
|
|
32
|
+
* modally or closes it to match the host's state. A host calls it whenever
|
|
33
|
+
* that state may have changed.
|
|
34
|
+
* - **Dismissal** — Escape and a press on the backdrop ask the host to close,
|
|
35
|
+
* each vetoable through its optional hook, and Escape never lets the browser
|
|
36
|
+
* close the dialog itself, so the host's state stays the only source of
|
|
37
|
+
* truth. A dialog that closes natively anyway (a `<form method="dialog">`)
|
|
38
|
+
* reports the close.
|
|
39
|
+
* - **Focus trap** — Tab wraps from the last focusable to the first and
|
|
40
|
+
* Shift+Tab back, since a native modal dialog lets focus escape to the
|
|
41
|
+
* browser chrome.
|
|
42
|
+
*
|
|
43
|
+
* Opening and closing from a trigger or a close button are the host's, as
|
|
44
|
+
* plain clicks: the React adapter renders those buttons apart from the dialog.
|
|
45
|
+
*/
|
|
46
|
+
export declare class ModalController {
|
|
47
|
+
#private;
|
|
48
|
+
constructor(dialog: HTMLDialogElement, host: ModalControllerHost);
|
|
49
|
+
/**
|
|
50
|
+
* Start listening on the dialog. Connecting again while connected does
|
|
51
|
+
* nothing, so a host whose lifecycle runs twice never stacks handlers.
|
|
52
|
+
*/
|
|
53
|
+
connect(): void;
|
|
54
|
+
/** Stop listening. A later `connect` resumes. */
|
|
55
|
+
disconnect(): void;
|
|
56
|
+
/** Show the dialog modally, or close it, to match the host's state. */
|
|
57
|
+
sync(): void;
|
|
58
|
+
}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import { FOCUSABLE_SELECTOR, modalPointerOutside, modalTabKeydown } from "@primitiv-ui/core";
|
|
2
|
+
/**
|
|
3
|
+
* The DOM behaviour of a modal dialog, over a native `<dialog>` and its
|
|
4
|
+
* `showModal()`: the browser owns the inert background, the top layer and
|
|
5
|
+
* Escape, and this adds what it leaves out (WAI-ARIA APG Modal Dialog):
|
|
6
|
+
*
|
|
7
|
+
* - **Sync** — {@link ModalController.sync | `sync()`} shows the dialog
|
|
8
|
+
* modally or closes it to match the host's state. A host calls it whenever
|
|
9
|
+
* that state may have changed.
|
|
10
|
+
* - **Dismissal** — Escape and a press on the backdrop ask the host to close,
|
|
11
|
+
* each vetoable through its optional hook, and Escape never lets the browser
|
|
12
|
+
* close the dialog itself, so the host's state stays the only source of
|
|
13
|
+
* truth. A dialog that closes natively anyway (a `<form method="dialog">`)
|
|
14
|
+
* reports the close.
|
|
15
|
+
* - **Focus trap** — Tab wraps from the last focusable to the first and
|
|
16
|
+
* Shift+Tab back, since a native modal dialog lets focus escape to the
|
|
17
|
+
* browser chrome.
|
|
18
|
+
*
|
|
19
|
+
* Opening and closing from a trigger or a close button are the host's, as
|
|
20
|
+
* plain clicks: the React adapter renders those buttons apart from the dialog.
|
|
21
|
+
*/
|
|
22
|
+
export class ModalController {
|
|
23
|
+
#dialog;
|
|
24
|
+
#host;
|
|
25
|
+
#connection = null;
|
|
26
|
+
constructor(dialog, host) {
|
|
27
|
+
this.#dialog = dialog;
|
|
28
|
+
this.#host = host;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Start listening on the dialog. Connecting again while connected does
|
|
32
|
+
* nothing, so a host whose lifecycle runs twice never stacks handlers.
|
|
33
|
+
*/
|
|
34
|
+
connect() {
|
|
35
|
+
if (this.#connection)
|
|
36
|
+
return;
|
|
37
|
+
this.#connection = new AbortController();
|
|
38
|
+
const { signal } = this.#connection;
|
|
39
|
+
const dialog = this.#dialog;
|
|
40
|
+
dialog.addEventListener("close", () => this.#host.change(false), { signal });
|
|
41
|
+
dialog.addEventListener("cancel", (event) => this.#cancel(event), { signal });
|
|
42
|
+
dialog.addEventListener("pointerdown", (event) => this.#pointerdown(event), { signal });
|
|
43
|
+
dialog.addEventListener("keydown", (event) => this.#keydown(event), { signal });
|
|
44
|
+
}
|
|
45
|
+
/** Stop listening. A later `connect` resumes. */
|
|
46
|
+
disconnect() {
|
|
47
|
+
this.#connection?.abort();
|
|
48
|
+
this.#connection = null;
|
|
49
|
+
}
|
|
50
|
+
/** Show the dialog modally, or close it, to match the host's state. */
|
|
51
|
+
sync() {
|
|
52
|
+
const { open } = this.#host.state();
|
|
53
|
+
if (open && !this.#dialog.open)
|
|
54
|
+
this.#dialog.showModal();
|
|
55
|
+
else if (!open && this.#dialog.open)
|
|
56
|
+
this.#dialog.close();
|
|
57
|
+
}
|
|
58
|
+
#cancel(event) {
|
|
59
|
+
this.#host.escapeKeyDown?.(event);
|
|
60
|
+
const vetoed = event.defaultPrevented;
|
|
61
|
+
// Never let the browser close the dialog itself: the host's state decides,
|
|
62
|
+
// and `sync()` closes it.
|
|
63
|
+
event.preventDefault();
|
|
64
|
+
if (!vetoed)
|
|
65
|
+
this.#host.change(false);
|
|
66
|
+
}
|
|
67
|
+
#pointerdown(event) {
|
|
68
|
+
const point = { x: event.clientX, y: event.clientY };
|
|
69
|
+
if (!modalPointerOutside(this.#dialog.getBoundingClientRect(), point))
|
|
70
|
+
return;
|
|
71
|
+
this.#host.pointerDownOutside?.(event);
|
|
72
|
+
if (!event.defaultPrevented)
|
|
73
|
+
this.#host.change(false);
|
|
74
|
+
}
|
|
75
|
+
#keydown(event) {
|
|
76
|
+
if (!this.#dialog.open)
|
|
77
|
+
return;
|
|
78
|
+
const focusables = [...this.#dialog.querySelectorAll(FOCUSABLE_SELECTOR)];
|
|
79
|
+
const outcome = modalTabKeydown({
|
|
80
|
+
key: event.key,
|
|
81
|
+
shiftKey: event.shiftKey,
|
|
82
|
+
active: focusables.indexOf(document.activeElement),
|
|
83
|
+
count: focusables.length,
|
|
84
|
+
});
|
|
85
|
+
if (!outcome)
|
|
86
|
+
return;
|
|
87
|
+
event.preventDefault();
|
|
88
|
+
if (outcome.focus !== null)
|
|
89
|
+
focusables[outcome.focus].focus();
|
|
90
|
+
}
|
|
91
|
+
}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { ElementBase, type ElementRegistry } from "../internal/element.js";
|
|
2
|
+
import { PrimitivRootElement } from "../internal/RootElement.js";
|
|
3
|
+
export type { ElementRegistry };
|
|
4
|
+
/**
|
|
5
|
+
* `<primitiv-modal>` — the state owner of a modal dialog, built on the native
|
|
6
|
+
* `<dialog>` and its `showModal()`: the browser makes the page behind it inert,
|
|
7
|
+
* paints the `::backdrop` and owns the top layer, and the element adds the
|
|
8
|
+
* rest of the WAI-ARIA Modal Dialog pattern — Tab wraps inside the dialog,
|
|
9
|
+
* Escape and a press on the backdrop close it, and closing hands focus back to
|
|
10
|
+
* the button that opened it.
|
|
11
|
+
*
|
|
12
|
+
* The root finds its own parts, so a modal nested in a dialog keeps its own,
|
|
13
|
+
* and reconciles their ARIA from its reflected state whenever a part arrives,
|
|
14
|
+
* leaves, or changes an attribute the root reads (the lifecycle is
|
|
15
|
+
* `PrimitivRootElement`'s). Leave `open` off the `<dialog>` itself: the root
|
|
16
|
+
* opens it, modally.
|
|
17
|
+
*
|
|
18
|
+
* @customElement primitiv-modal
|
|
19
|
+
* @summary The state owner of a modal dialog on the native `<dialog>`.
|
|
20
|
+
* @attr {boolean} open - Whether the dialog is open. Reflected: it changes as the user opens and dismisses the dialog, so a server re-render or a history snapshot sees the live state. Set it (or the `open` property) to open or close the dialog from script.
|
|
21
|
+
* @fires {CustomEvent<{ open: boolean }>} primitiv-modal-change - Bubbles, cancelable, fired before a user-driven change is applied — a trigger or close click, Escape, a press on the backdrop, or the dialog closing natively — with the open state afterwards. Call `preventDefault()` to veto it, e.g. to keep a form with unsaved changes open.
|
|
22
|
+
*/
|
|
23
|
+
export declare class PrimitivModalElement extends PrimitivRootElement {
|
|
24
|
+
#private;
|
|
25
|
+
protected readonly controller: {
|
|
26
|
+
connect(): void;
|
|
27
|
+
disconnect(): void;
|
|
28
|
+
};
|
|
29
|
+
protected readonly reconcileOn: readonly string[];
|
|
30
|
+
/** Whether the dialog is open, reflected as the boolean `open` attribute. */
|
|
31
|
+
get open(): boolean;
|
|
32
|
+
set open(next: boolean);
|
|
33
|
+
protected reconcile(): void;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* `<primitiv-modal-trigger>` — an interactive part. It decorates exactly one
|
|
37
|
+
* child the consumer writes, a `<button>`, which opens the dialog and receives
|
|
38
|
+
* `aria-haspopup="dialog"`, `aria-expanded` and `aria-controls`, so the
|
|
39
|
+
* rendered DOM matches React's (RFC 0033 D24). A modal may have several.
|
|
40
|
+
*
|
|
41
|
+
* @customElement primitiv-modal-trigger
|
|
42
|
+
*/
|
|
43
|
+
export declare class PrimitivModalTriggerElement extends ElementBase {
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* `<primitiv-modal-content>` — decorates exactly one native `<dialog>` the
|
|
47
|
+
* consumer writes, which the root opens with `showModal()`, closes, and gives
|
|
48
|
+
* an id, `data-state`, and `aria-labelledby` / `aria-describedby` pointing at
|
|
49
|
+
* the title and description. The dialog keeps its implicit `dialog` role.
|
|
50
|
+
*
|
|
51
|
+
* @customElement primitiv-modal-content
|
|
52
|
+
*/
|
|
53
|
+
export declare class PrimitivModalContentElement extends ElementBase {
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* `<primitiv-modal-title>` — decorates exactly one heading the consumer writes
|
|
57
|
+
* (an `<h2>`, usually), whose id becomes the dialog's `aria-labelledby`, so it
|
|
58
|
+
* is the name a screen reader announces as the dialog opens.
|
|
59
|
+
*
|
|
60
|
+
* @customElement primitiv-modal-title
|
|
61
|
+
*/
|
|
62
|
+
export declare class PrimitivModalTitleElement extends ElementBase {
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* `<primitiv-modal-description>` — decorates exactly one element the consumer
|
|
66
|
+
* writes (a `<p>`, usually), whose id becomes the dialog's `aria-describedby`.
|
|
67
|
+
*
|
|
68
|
+
* @customElement primitiv-modal-description
|
|
69
|
+
*/
|
|
70
|
+
export declare class PrimitivModalDescriptionElement extends ElementBase {
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* `<primitiv-modal-close>` — an interactive part. It decorates exactly one
|
|
74
|
+
* `<button>` the consumer writes, which closes the dialog. A modal may have
|
|
75
|
+
* several: the header's icon button and the footer's Cancel.
|
|
76
|
+
*
|
|
77
|
+
* @customElement primitiv-modal-close
|
|
78
|
+
*/
|
|
79
|
+
export declare class PrimitivModalCloseElement extends ElementBase {
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Define the six Modal elements on `registry` (the global `customElements` by
|
|
83
|
+
* default). Safe to call more than once, and a no-op where there is no
|
|
84
|
+
* registry (a server render).
|
|
85
|
+
*/
|
|
86
|
+
export declare function defineModal(registry?: ElementRegistry | undefined): void;
|