@rcarls/rc-dialog 0.1.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.
@@ -0,0 +1,177 @@
1
+ import { LitElement as a, nothing as d } from "lit";
2
+ import { property as i } from "lit/decorators.js";
3
+ import { DragController as u, ResizeController as c } from "@rcarls/rc-common";
4
+ var f = Object.defineProperty, _ = Object.getOwnPropertyDescriptor, o = (n, e, t, l) => {
5
+ for (var s = l > 1 ? void 0 : l ? _(e, t) : e, p = n.length - 1, h; p >= 0; p--)
6
+ (h = n[p]) && (s = (l ? h(e, t, s) : h(s)) || s);
7
+ return l && s && f(e, t, s), s;
8
+ };
9
+ class r extends a {
10
+ constructor() {
11
+ super(...arguments), this.movable = !1, this.moveHandle = "", this.moveBounds = "viewport", this.moveStep = 4, this.resize = "none", this.resizeThreshold = 8, this.resizeStep = 4, this.closedBy = "", this.lightDismiss = !1, this._controlledOpen = void 0, this._defaultOpen = !1, this._dialogRef = null, this._observer = null, this._suppressNextCloseToggle = !1, this._openerEl = null, this._onClose = () => {
12
+ this._openerEl instanceof HTMLElement && this._openerEl.isConnected && this._openerEl.focus(), this._openerEl = null, this.dispatchEvent(
13
+ new CustomEvent("rc-dialog-close", {
14
+ bubbles: !0,
15
+ composed: !0,
16
+ detail: { returnValue: this.returnValue }
17
+ })
18
+ ), this._suppressNextCloseToggle || this._dispatchToggle(!1), this._suppressNextCloseToggle = !1, this.requestUpdate("open");
19
+ }, this._onCancel = (e) => {
20
+ const t = new CustomEvent("rc-dialog-request-close", {
21
+ bubbles: !0,
22
+ composed: !0,
23
+ cancelable: !0,
24
+ detail: { returnValue: this.returnValue }
25
+ });
26
+ !this.dispatchEvent(t) ? e.preventDefault() : this.dispatchEvent(
27
+ new CustomEvent("rc-dialog-cancel", { bubbles: !0, composed: !0 })
28
+ );
29
+ }, this._onBackdropClick = (e) => {
30
+ e.target === this._dlg() && this.requestClose();
31
+ };
32
+ }
33
+ createRenderRoot() {
34
+ return this;
35
+ }
36
+ get open() {
37
+ return this._dlg()?.open ?? !1;
38
+ }
39
+ set open(e) {
40
+ const t = this._controlledOpen;
41
+ this._controlledOpen = e, e !== void 0 && this._applyOpen(e, !0), this.requestUpdate("open", t);
42
+ }
43
+ get defaultOpen() {
44
+ return this._defaultOpen;
45
+ }
46
+ set defaultOpen(e) {
47
+ const t = this._defaultOpen;
48
+ this._defaultOpen = e, this._controlledOpen === void 0 && e && this._applyOpen(!0, !0), this.requestUpdate("defaultOpen", t);
49
+ }
50
+ /** The return value set when the dialog was closed. */
51
+ get returnValue() {
52
+ return this._dlg()?.returnValue ?? "";
53
+ }
54
+ /** Opens the inner `<dialog>` as a modal and fires `rc-dialog-open`. */
55
+ showModal() {
56
+ this._applyOpen(!0, !1, !0) && (this.dispatchEvent(new CustomEvent("rc-dialog-open", { bubbles: !0, composed: !0 })), this._dispatchToggle(!0));
57
+ }
58
+ /** Opens the inner `<dialog>` as a non-modal and fires `rc-dialog-open`. */
59
+ show() {
60
+ this._applyOpen(!0, !1, !1) && (this.dispatchEvent(new CustomEvent("rc-dialog-open", { bubbles: !0, composed: !0 })), this._dispatchToggle(!0));
61
+ }
62
+ /** Closes the inner `<dialog>`, optionally setting a return value. */
63
+ close(e) {
64
+ this._dlg()?.close(e);
65
+ }
66
+ /**
67
+ * Requests the inner `<dialog>` to close, firing a cancelable `cancel` event
68
+ * first. If the consumer prevents `rc-dialog-request-close`, the close is
69
+ * blocked. Falls back to a synthesized cancel → close sequence in browsers
70
+ * that do not yet support the native `requestClose()` method.
71
+ */
72
+ requestClose(e) {
73
+ const t = this._dlg();
74
+ if (t)
75
+ if ("requestClose" in t && typeof t.requestClose == "function")
76
+ t.requestClose(e);
77
+ else {
78
+ const l = new Event("cancel", { cancelable: !0, bubbles: !1 });
79
+ !t.dispatchEvent(l) || t.close(e);
80
+ }
81
+ }
82
+ // ---- Internals ----------------------------------------------------------
83
+ _dlg() {
84
+ return this._dialogRef?.deref() ?? this.querySelector(":scope > dialog");
85
+ }
86
+ render() {
87
+ return d;
88
+ }
89
+ connectedCallback() {
90
+ super.connectedCallback(), this._observer = new MutationObserver(() => this._setupDialog()), this._observer.observe(this, { childList: !0 });
91
+ }
92
+ firstUpdated() {
93
+ this._setupDialog();
94
+ }
95
+ _setupDialog() {
96
+ const e = this._dlg();
97
+ if (e && this._dialogRef?.deref() !== e) {
98
+ if (this._teardownDialog(), this._dialogRef = new WeakRef(e), e.addEventListener("close", this._onClose), e.addEventListener("cancel", this._onCancel), this.lightDismiss && "closedBy" in HTMLDialogElement.prototype ? e.setAttribute("closedby", "any") : this.lightDismiss ? e.addEventListener("click", this._onBackdropClick) : this.closedBy && e.setAttribute("closedby", this.closedBy), this.movable || this.moveHandle) {
99
+ const t = this.moveHandle ? e.querySelector(this.moveHandle) ?? e : e;
100
+ new u(this, {
101
+ target: e,
102
+ handle: t,
103
+ bounds: this.moveBounds,
104
+ step: this.moveStep
105
+ });
106
+ }
107
+ this.resize !== "none" && new c(this, {
108
+ target: e,
109
+ direction: this.resize,
110
+ threshold: this.resizeThreshold,
111
+ step: this.resizeStep,
112
+ bounds: this.moveBounds
113
+ }), this._controlledOpen !== void 0 ? this._applyOpen(this._controlledOpen, !0) : this.defaultOpen && this._applyOpen(!0, !0);
114
+ }
115
+ }
116
+ updated(e) {
117
+ const t = this._dlg();
118
+ t && (e.has("closedBy") && (this.closedBy ? t.setAttribute("closedby", this.closedBy) : t.removeAttribute("closedby")), e.has("lightDismiss") && (this.lightDismiss && "closedBy" in HTMLDialogElement.prototype ? (t.setAttribute("closedby", "any"), t.removeEventListener("click", this._onBackdropClick)) : this.lightDismiss ? t.addEventListener("click", this._onBackdropClick) : (t.removeEventListener("click", this._onBackdropClick), this.closedBy ? t.setAttribute("closedby", this.closedBy) : t.removeAttribute("closedby"))));
119
+ }
120
+ disconnectedCallback() {
121
+ this._observer?.disconnect(), this._observer = null, this._teardownDialog(), super.disconnectedCallback();
122
+ }
123
+ _teardownDialog() {
124
+ const e = this._dialogRef?.deref();
125
+ e && (e.removeEventListener("close", this._onClose), e.removeEventListener("cancel", this._onCancel), e.removeEventListener("click", this._onBackdropClick), this._dialogRef = null, this._openerEl = null);
126
+ }
127
+ _applyOpen(e, t, l = !0) {
128
+ const s = this._dlg();
129
+ return s ? e ? s.open ? !1 : (this._openerEl = document.activeElement, l ? s.showModal() : s.show(), this.requestUpdate("open"), !0) : s.open ? (this._suppressNextCloseToggle = t, s.close(), this.requestUpdate("open"), !0) : !1 : !1;
130
+ }
131
+ _dispatchToggle(e) {
132
+ this.dispatchEvent(
133
+ new CustomEvent("rc-dialog-toggle", {
134
+ bubbles: !0,
135
+ composed: !0,
136
+ detail: { open: e, returnValue: this.returnValue }
137
+ })
138
+ );
139
+ }
140
+ }
141
+ o([
142
+ i({ type: Boolean, reflect: !0 })
143
+ ], r.prototype, "movable", 2);
144
+ o([
145
+ i({ type: String, attribute: "move-handle" })
146
+ ], r.prototype, "moveHandle", 2);
147
+ o([
148
+ i({ type: String, attribute: "move-bounds" })
149
+ ], r.prototype, "moveBounds", 2);
150
+ o([
151
+ i({ type: Number, attribute: "move-step" })
152
+ ], r.prototype, "moveStep", 2);
153
+ o([
154
+ i({ type: String, reflect: !0 })
155
+ ], r.prototype, "resize", 2);
156
+ o([
157
+ i({ type: Number, attribute: "resize-threshold" })
158
+ ], r.prototype, "resizeThreshold", 2);
159
+ o([
160
+ i({ type: Number, attribute: "resize-step" })
161
+ ], r.prototype, "resizeStep", 2);
162
+ o([
163
+ i({ type: String, attribute: "closed-by", reflect: !0 })
164
+ ], r.prototype, "closedBy", 2);
165
+ o([
166
+ i({ type: Boolean, attribute: "light-dismiss" })
167
+ ], r.prototype, "lightDismiss", 2);
168
+ o([
169
+ i({ type: Boolean, attribute: "open", reflect: !0 })
170
+ ], r.prototype, "open", 1);
171
+ o([
172
+ i({ type: Boolean, attribute: "default-open" })
173
+ ], r.prototype, "defaultOpen", 1);
174
+ export {
175
+ r as R
176
+ };
177
+ //# sourceMappingURL=rc-dialog-BGtJgQDG.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rc-dialog-BGtJgQDG.js","sources":["../src/rc-dialog.ts"],"sourcesContent":["import { LitElement, nothing, type PropertyValues } from 'lit';\nimport { property } from 'lit/decorators.js';\nimport { DragController, ResizeController } from '@rcarls/rc-common';\n\n/** Detail shape for `rc-dialog-close` and `rc-dialog-request-close`. */\nexport interface RCDialogCloseEvent {\n returnValue: string;\n}\n\n/** Detail shape for `rc-dialog-toggle`. */\nexport interface RCDialogToggleEvent {\n open: boolean;\n returnValue: string;\n}\n\ndeclare global {\n interface HTMLElementTagNameMap {\n 'rc-dialog': RCDialog;\n }\n\n interface HTMLElementEventMap {\n 'rc-dialog-open': CustomEvent<Record<string, never>>;\n 'rc-dialog-close': CustomEvent<RCDialogCloseEvent>;\n 'rc-dialog-request-close': CustomEvent<RCDialogCloseEvent>;\n 'rc-dialog-cancel': CustomEvent<Record<string, never>>;\n 'rc-dialog-toggle': CustomEvent<RCDialogToggleEvent>;\n }\n}\n\n/**\n * Enhances a consumer-provided `<dialog>` child with drag, resize, and\n * accessible event forwarding. Renders nothing itself — the `<dialog>` element\n * remains in the document's light DOM for full CSS and AT access.\n *\n * @slot - Place a `<dialog>` element with your content here.\n *\n * @fires rc-dialog-open - Fired when the dialog opens via `showModal()` or `show()`.\n * @fires rc-dialog-toggle - Fired when user/native interaction changes open state.\n *\n * @fires rc-dialog-request-close - Fired before the dialog closes (Escape key,\n * backdrop click when `light-dismiss` is set, or a call to `requestClose()`).\n * **Cancelable** — call `preventDefault()` to block the close (e.g. unsaved-\n * changes guard). `detail: { returnValue: string }`\n *\n * @fires rc-dialog-cancel - Mirrors the inner `<dialog>` cancel event when the\n * close was not prevented. Backward-compatible alias for `rc-dialog-request-close`.\n *\n * @fires rc-dialog-close - Mirrors the inner `<dialog>` close event.\n * `detail: { returnValue: string }`\n *\n * @see https://www.w3.org/WAI/ARIA/apg/patterns/dialog-modal/\n */\nexport class RCDialog extends LitElement {\n override createRenderRoot() { return this; }\n\n /**\n * Allow the dialog to be moved by dragging. Use `move-handle` to restrict\n * dragging to a specific child element (e.g. a titlebar).\n * Named `movable` — not `draggable` — to avoid colliding with the\n * built-in HTML Drag and Drop `draggable` global attribute.\n */\n @property({ type: Boolean, reflect: true })\n movable = false;\n\n /** CSS selector (within the inner `<dialog>`) for the drag handle element. */\n @property({ type: String, attribute: 'move-handle' })\n moveHandle = '';\n\n /** Bounds constraint for movement. */\n @property({ type: String, attribute: 'move-bounds' })\n moveBounds: 'viewport' | 'parent' = 'viewport';\n\n /** Keyboard arrow-key step in px for moving. */\n @property({ type: Number, attribute: 'move-step' })\n moveStep = 4;\n\n /** Enable resizing, mirroring the CSS `resize` property values. */\n @property({ type: String, reflect: true })\n resize: 'none' | 'both' | 'horizontal' | 'vertical' = 'none';\n\n /** Edge hit-test thickness in px for resize detection. */\n @property({ type: Number, attribute: 'resize-threshold' })\n resizeThreshold = 8;\n\n /** Keyboard arrow-key step in px for resizing. */\n @property({ type: Number, attribute: 'resize-step' })\n resizeStep = 4;\n\n /**\n * Proxied to the inner `<dialog closedby=\"...\">` attribute (Chrome 134+,\n * Safari 18.4+, Firefox 139+). Progressive enhancement — no-op in older browsers.\n *\n * - `'any'` — Escape OR backdrop click closes the dialog.\n * - `'closerequest'` — Escape only (browser default for modal dialogs).\n * - `'none'` — Only programmatic `close()` / `requestClose()`.\n * - `''` — Attribute absent; use `light-dismiss` for JS fallback.\n */\n @property({ type: String, attribute: 'closed-by', reflect: true })\n closedBy: 'any' | 'closerequest' | 'none' | '' = '';\n\n /**\n * JS fallback for backdrop-click dismissal. When true, a click on the\n * `<dialog>` element itself (i.e. the backdrop area, not a child element)\n * calls `requestClose()`. Complements native `closed-by=\"any\"` for browsers\n * that do not yet support the `closedby` attribute.\n */\n @property({ type: Boolean, attribute: 'light-dismiss' })\n lightDismiss = false;\n\n // ---- Native <dialog> delegation ----------------------------------------\n\n private _controlledOpen: boolean | undefined = undefined;\n private _defaultOpen = false;\n private _dialogRef: WeakRef<HTMLDialogElement> | null = null;\n // Initialized in connectedCallback — MutationObserver is browser-only.\n private _observer: MutationObserver | null = null;\n private _suppressNextCloseToggle = false;\n /** The element that had focus when the dialog was opened; restored on close. */\n private _openerEl: Element | null = null;\n\n /** Whether the inner `<dialog>` is currently open. Host writes update silently. */\n @property({ type: Boolean, attribute: 'open', reflect: true })\n get open(): boolean { return this._dlg()?.open ?? false; }\n set open(value: boolean | undefined) {\n const oldValue = this._controlledOpen;\n\n this._controlledOpen = value;\n\n if (value !== undefined) {\n this._applyOpen(value, true);\n }\n\n this.requestUpdate('open', oldValue);\n }\n\n /** Initial uncontrolled open state. */\n @property({ type: Boolean, attribute: 'default-open' })\n get defaultOpen(): boolean { return this._defaultOpen; }\n set defaultOpen(value: boolean) {\n const oldValue = this._defaultOpen;\n\n this._defaultOpen = value;\n\n if (this._controlledOpen === undefined && value) {\n this._applyOpen(true, true);\n }\n\n this.requestUpdate('defaultOpen', oldValue);\n }\n\n /** The return value set when the dialog was closed. */\n get returnValue(): string { return this._dlg()?.returnValue ?? ''; }\n\n /** Opens the inner `<dialog>` as a modal and fires `rc-dialog-open`. */\n showModal(): void {\n if (this._applyOpen(true, false, true)) {\n this.dispatchEvent(new CustomEvent('rc-dialog-open', { bubbles: true, composed: true }));\n this._dispatchToggle(true);\n }\n }\n\n /** Opens the inner `<dialog>` as a non-modal and fires `rc-dialog-open`. */\n show(): void {\n if (this._applyOpen(true, false, false)) {\n this.dispatchEvent(new CustomEvent('rc-dialog-open', { bubbles: true, composed: true }));\n this._dispatchToggle(true);\n }\n }\n\n /** Closes the inner `<dialog>`, optionally setting a return value. */\n close(returnValue?: string): void { this._dlg()?.close(returnValue); }\n\n /**\n * Requests the inner `<dialog>` to close, firing a cancelable `cancel` event\n * first. If the consumer prevents `rc-dialog-request-close`, the close is\n * blocked. Falls back to a synthesized cancel → close sequence in browsers\n * that do not yet support the native `requestClose()` method.\n */\n requestClose(returnValue?: string): void {\n const dlg = this._dlg();\n if (!dlg) return;\n if ('requestClose' in dlg && typeof dlg.requestClose === 'function') {\n dlg.requestClose(returnValue);\n } else {\n // Synthesize a cancelable cancel event; _onCancel fires rc-dialog-request-close.\n // If not prevented, _onCancel does not call dlg.close() — we do it here.\n const ev = new Event('cancel', { cancelable: true, bubbles: false });\n const prevented = !dlg.dispatchEvent(ev);\n if (!prevented) dlg.close(returnValue);\n }\n }\n\n // ---- Internals ----------------------------------------------------------\n\n private _dlg(): HTMLDialogElement | null {\n return this._dialogRef?.deref() ?? this.querySelector<HTMLDialogElement>(':scope > dialog');\n }\n\n override render() { return nothing; }\n\n override connectedCallback() {\n super.connectedCallback();\n this._observer = new MutationObserver(() => this._setupDialog());\n this._observer.observe(this, { childList: true });\n }\n\n override firstUpdated() {\n this._setupDialog();\n }\n\n private _setupDialog() {\n const dlg = this._dlg();\n if (!dlg) {\n if (import.meta.env.DEV) {\n console.warn(\n '[rc-dialog] No <dialog> child found. Place a <dialog> element ' +\n 'directly inside <rc-dialog>.',\n this,\n );\n }\n return;\n }\n\n if (this._dialogRef?.deref() === dlg) return;\n\n this._teardownDialog();\n this._dialogRef = new WeakRef(dlg);\n\n if (import.meta.env.DEV) {\n const hasLabel =\n dlg.hasAttribute('aria-labelledby') || dlg.hasAttribute('aria-label');\n if (!hasLabel) {\n console.warn(\n '[rc-dialog] The inner <dialog> is missing aria-labelledby or ' +\n 'aria-label. Add one to satisfy the ARIA dialog pattern.',\n dlg,\n );\n }\n if (dlg.getAttribute('role') === 'alertdialog' && !dlg.hasAttribute('aria-describedby')) {\n console.warn(\n '[rc-dialog] <dialog role=\"alertdialog\"> should have aria-describedby ' +\n 'pointing to the alert message text.',\n dlg,\n );\n }\n }\n\n dlg.addEventListener('close', this._onClose);\n dlg.addEventListener('cancel', this._onCancel);\n\n if (this.lightDismiss && 'closedBy' in HTMLDialogElement.prototype) {\n dlg.setAttribute('closedby', 'any');\n } else if (this.lightDismiss) {\n dlg.addEventListener('click', this._onBackdropClick);\n } else {\n if (this.closedBy) dlg.setAttribute('closedby', this.closedBy);\n }\n\n if (this.movable || this.moveHandle) {\n const handle = this.moveHandle\n ? (dlg.querySelector<Element>(this.moveHandle) ?? dlg)\n : dlg;\n new DragController(this, {\n target: dlg,\n handle,\n bounds: this.moveBounds,\n step: this.moveStep,\n });\n }\n\n if (this.resize !== 'none') {\n // TODO: add a separate resize-bounds attribute; moveBounds is a proxy for now.\n new ResizeController(this, {\n target: dlg,\n direction: this.resize,\n threshold: this.resizeThreshold,\n step: this.resizeStep,\n bounds: this.moveBounds,\n });\n }\n\n if (this._controlledOpen !== undefined) {\n this._applyOpen(this._controlledOpen, true);\n } else if (this.defaultOpen) {\n this._applyOpen(true, true);\n }\n }\n\n override updated(changed: PropertyValues) {\n const dlg = this._dlg();\n if (!dlg) return;\n\n if (changed.has('closedBy')) {\n if (this.closedBy) dlg.setAttribute('closedby', this.closedBy);\n else dlg.removeAttribute('closedby');\n }\n\n if (changed.has('lightDismiss')) {\n if (this.lightDismiss && 'closedBy' in HTMLDialogElement.prototype) {\n // native support available — delegate to closedby=\"any\" instead of JS handler\n dlg.setAttribute('closedby', 'any');\n dlg.removeEventListener('click', this._onBackdropClick);\n } else if (this.lightDismiss) {\n dlg.addEventListener('click', this._onBackdropClick);\n } else {\n dlg.removeEventListener('click', this._onBackdropClick);\n if (this.closedBy) dlg.setAttribute('closedby', this.closedBy);\n else dlg.removeAttribute('closedby');\n }\n }\n }\n\n override disconnectedCallback() {\n this._observer?.disconnect();\n this._observer = null;\n this._teardownDialog();\n super.disconnectedCallback();\n }\n\n private _teardownDialog(): void {\n const dlg = this._dialogRef?.deref();\n if (!dlg) return;\n\n dlg.removeEventListener('close', this._onClose);\n dlg.removeEventListener('cancel', this._onCancel);\n dlg.removeEventListener('click', this._onBackdropClick);\n this._dialogRef = null;\n this._openerEl = null;\n }\n\n private _onClose = () => {\n // Restore focus to the element that triggered the open, per APG focus management.\n if (this._openerEl instanceof HTMLElement && this._openerEl.isConnected) {\n this._openerEl.focus();\n }\n this._openerEl = null;\n\n this.dispatchEvent(\n new CustomEvent('rc-dialog-close', {\n bubbles: true,\n composed: true,\n detail: { returnValue: this.returnValue },\n }),\n );\n\n if (!this._suppressNextCloseToggle) {\n this._dispatchToggle(false);\n }\n\n this._suppressNextCloseToggle = false;\n this.requestUpdate('open');\n };\n\n /**\n * Intercepts the native `cancel` event (Escape key or `requestClose()`).\n * Fires the cancelable `rc-dialog-request-close` event first. If prevented,\n * the native cancel is suppressed and the dialog stays open. If not prevented,\n * the backward-compatible `rc-dialog-cancel` is also fired and the browser\n * proceeds to close the dialog naturally (no explicit `dlg.close()` call here,\n * so the `requestClose()` fallback can close after `dispatchEvent` returns).\n */\n private _onCancel = (e: Event) => {\n const requestCloseEvent = new CustomEvent('rc-dialog-request-close', {\n bubbles: true,\n composed: true,\n cancelable: true,\n detail: { returnValue: this.returnValue },\n });\n const prevented = !this.dispatchEvent(requestCloseEvent);\n\n if (prevented) {\n e.preventDefault();\n } else {\n this.dispatchEvent(\n new CustomEvent('rc-dialog-cancel', { bubbles: true, composed: true }),\n );\n }\n };\n\n /**\n * Detects backdrop clicks: a click whose `target` is the `<dialog>` element\n * itself (not a descendant) means the user clicked outside the dialog's\n * content box on the backdrop area.\n */\n private _onBackdropClick = (e: MouseEvent) => {\n if (e.target === this._dlg()) this.requestClose();\n };\n\n private _applyOpen(open: boolean, silent: boolean, modal = true): boolean {\n const dlg = this._dlg();\n if (!dlg) return false;\n\n if (open) {\n if (dlg.open) return false;\n // Capture focus owner before the dialog steals it, so we can restore on close.\n this._openerEl = document.activeElement;\n modal ? dlg.showModal() : dlg.show();\n this.requestUpdate('open');\n return true;\n }\n\n if (!dlg.open) return false;\n\n this._suppressNextCloseToggle = silent;\n dlg.close();\n this.requestUpdate('open');\n\n return true;\n }\n\n private _dispatchToggle(open: boolean): void {\n this.dispatchEvent(\n new CustomEvent('rc-dialog-toggle', {\n bubbles: true,\n composed: true,\n detail: { open, returnValue: this.returnValue },\n }),\n );\n }\n}\n\nexport default RCDialog;\n"],"names":["RCDialog","LitElement","requestCloseEvent","value","oldValue","returnValue","dlg","ev","nothing","handle","DragController","ResizeController","changed","open","silent","modal","__decorateClass","property"],"mappings":"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"}
@@ -0,0 +1,6 @@
1
+ import { R as e } from "./rc-dialog-BGtJgQDG.js";
2
+ customElements.get("rc-dialog") || customElements.define("rc-dialog", e);
3
+ export {
4
+ e as RCDialog
5
+ };
6
+ //# sourceMappingURL=rc-dialog-define.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rc-dialog-define.js","sources":["../src/define.ts"],"sourcesContent":["import { RCDialog } from './index.js';\n\ncustomElements.get('rc-dialog') || customElements.define('rc-dialog', RCDialog);\n\nexport * from './index.js';\n"],"names":["RCDialog"],"mappings":";AAEA,eAAe,IAAI,WAAW,KAAK,eAAe,OAAO,aAAaA,CAAQ;"}
@@ -0,0 +1,5 @@
1
+ import { R as r } from "./rc-dialog-BGtJgQDG.js";
2
+ export {
3
+ r as RCDialog
4
+ };
5
+ //# sourceMappingURL=rc-dialog.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rc-dialog.js","sources":[],"sourcesContent":[],"names":[],"mappings":";"}
@@ -0,0 +1 @@
1
+ export * from './index.js';
@@ -0,0 +1 @@
1
+ export * from './rc-dialog.js';
@@ -0,0 +1,139 @@
1
+ import { LitElement, PropertyValues } from 'lit';
2
+ /** Detail shape for `rc-dialog-close` and `rc-dialog-request-close`. */
3
+ export interface RCDialogCloseEvent {
4
+ returnValue: string;
5
+ }
6
+ /** Detail shape for `rc-dialog-toggle`. */
7
+ export interface RCDialogToggleEvent {
8
+ open: boolean;
9
+ returnValue: string;
10
+ }
11
+ declare global {
12
+ interface HTMLElementTagNameMap {
13
+ 'rc-dialog': RCDialog;
14
+ }
15
+ interface HTMLElementEventMap {
16
+ 'rc-dialog-open': CustomEvent<Record<string, never>>;
17
+ 'rc-dialog-close': CustomEvent<RCDialogCloseEvent>;
18
+ 'rc-dialog-request-close': CustomEvent<RCDialogCloseEvent>;
19
+ 'rc-dialog-cancel': CustomEvent<Record<string, never>>;
20
+ 'rc-dialog-toggle': CustomEvent<RCDialogToggleEvent>;
21
+ }
22
+ }
23
+ /**
24
+ * Enhances a consumer-provided `<dialog>` child with drag, resize, and
25
+ * accessible event forwarding. Renders nothing itself — the `<dialog>` element
26
+ * remains in the document's light DOM for full CSS and AT access.
27
+ *
28
+ * @slot - Place a `<dialog>` element with your content here.
29
+ *
30
+ * @fires rc-dialog-open - Fired when the dialog opens via `showModal()` or `show()`.
31
+ * @fires rc-dialog-toggle - Fired when user/native interaction changes open state.
32
+ *
33
+ * @fires rc-dialog-request-close - Fired before the dialog closes (Escape key,
34
+ * backdrop click when `light-dismiss` is set, or a call to `requestClose()`).
35
+ * **Cancelable** — call `preventDefault()` to block the close (e.g. unsaved-
36
+ * changes guard). `detail: { returnValue: string }`
37
+ *
38
+ * @fires rc-dialog-cancel - Mirrors the inner `<dialog>` cancel event when the
39
+ * close was not prevented. Backward-compatible alias for `rc-dialog-request-close`.
40
+ *
41
+ * @fires rc-dialog-close - Mirrors the inner `<dialog>` close event.
42
+ * `detail: { returnValue: string }`
43
+ *
44
+ * @see https://www.w3.org/WAI/ARIA/apg/patterns/dialog-modal/
45
+ */
46
+ export declare class RCDialog extends LitElement {
47
+ createRenderRoot(): this;
48
+ /**
49
+ * Allow the dialog to be moved by dragging. Use `move-handle` to restrict
50
+ * dragging to a specific child element (e.g. a titlebar).
51
+ * Named `movable` — not `draggable` — to avoid colliding with the
52
+ * built-in HTML Drag and Drop `draggable` global attribute.
53
+ */
54
+ movable: boolean;
55
+ /** CSS selector (within the inner `<dialog>`) for the drag handle element. */
56
+ moveHandle: string;
57
+ /** Bounds constraint for movement. */
58
+ moveBounds: 'viewport' | 'parent';
59
+ /** Keyboard arrow-key step in px for moving. */
60
+ moveStep: number;
61
+ /** Enable resizing, mirroring the CSS `resize` property values. */
62
+ resize: 'none' | 'both' | 'horizontal' | 'vertical';
63
+ /** Edge hit-test thickness in px for resize detection. */
64
+ resizeThreshold: number;
65
+ /** Keyboard arrow-key step in px for resizing. */
66
+ resizeStep: number;
67
+ /**
68
+ * Proxied to the inner `<dialog closedby="...">` attribute (Chrome 134+,
69
+ * Safari 18.4+, Firefox 139+). Progressive enhancement — no-op in older browsers.
70
+ *
71
+ * - `'any'` — Escape OR backdrop click closes the dialog.
72
+ * - `'closerequest'` — Escape only (browser default for modal dialogs).
73
+ * - `'none'` — Only programmatic `close()` / `requestClose()`.
74
+ * - `''` — Attribute absent; use `light-dismiss` for JS fallback.
75
+ */
76
+ closedBy: 'any' | 'closerequest' | 'none' | '';
77
+ /**
78
+ * JS fallback for backdrop-click dismissal. When true, a click on the
79
+ * `<dialog>` element itself (i.e. the backdrop area, not a child element)
80
+ * calls `requestClose()`. Complements native `closed-by="any"` for browsers
81
+ * that do not yet support the `closedby` attribute.
82
+ */
83
+ lightDismiss: boolean;
84
+ private _controlledOpen;
85
+ private _defaultOpen;
86
+ private _dialogRef;
87
+ private _observer;
88
+ private _suppressNextCloseToggle;
89
+ /** The element that had focus when the dialog was opened; restored on close. */
90
+ private _openerEl;
91
+ /** Whether the inner `<dialog>` is currently open. Host writes update silently. */
92
+ get open(): boolean;
93
+ set open(value: boolean | undefined);
94
+ /** Initial uncontrolled open state. */
95
+ get defaultOpen(): boolean;
96
+ set defaultOpen(value: boolean);
97
+ /** The return value set when the dialog was closed. */
98
+ get returnValue(): string;
99
+ /** Opens the inner `<dialog>` as a modal and fires `rc-dialog-open`. */
100
+ showModal(): void;
101
+ /** Opens the inner `<dialog>` as a non-modal and fires `rc-dialog-open`. */
102
+ show(): void;
103
+ /** Closes the inner `<dialog>`, optionally setting a return value. */
104
+ close(returnValue?: string): void;
105
+ /**
106
+ * Requests the inner `<dialog>` to close, firing a cancelable `cancel` event
107
+ * first. If the consumer prevents `rc-dialog-request-close`, the close is
108
+ * blocked. Falls back to a synthesized cancel → close sequence in browsers
109
+ * that do not yet support the native `requestClose()` method.
110
+ */
111
+ requestClose(returnValue?: string): void;
112
+ private _dlg;
113
+ render(): symbol;
114
+ connectedCallback(): void;
115
+ firstUpdated(): void;
116
+ private _setupDialog;
117
+ updated(changed: PropertyValues): void;
118
+ disconnectedCallback(): void;
119
+ private _teardownDialog;
120
+ private _onClose;
121
+ /**
122
+ * Intercepts the native `cancel` event (Escape key or `requestClose()`).
123
+ * Fires the cancelable `rc-dialog-request-close` event first. If prevented,
124
+ * the native cancel is suppressed and the dialog stays open. If not prevented,
125
+ * the backward-compatible `rc-dialog-cancel` is also fired and the browser
126
+ * proceeds to close the dialog naturally (no explicit `dlg.close()` call here,
127
+ * so the `requestClose()` fallback can close after `dispatchEvent` returns).
128
+ */
129
+ private _onCancel;
130
+ /**
131
+ * Detects backdrop clicks: a click whose `target` is the `<dialog>` element
132
+ * itself (not a descendant) means the user clicked outside the dialog's
133
+ * content box on the backdrop area.
134
+ */
135
+ private _onBackdropClick;
136
+ private _applyOpen;
137
+ private _dispatchToggle;
138
+ }
139
+ export default RCDialog;
package/package.json ADDED
@@ -0,0 +1,68 @@
1
+ {
2
+ "name": "@rcarls/rc-dialog",
3
+ "publishConfig": {
4
+ "access": "public"
5
+ },
6
+ "version": "0.1.0",
7
+ "description": "Headless draggable and resizable native dialog wrapper, built with Lit",
8
+ "repository": {
9
+ "type": "git",
10
+ "url": "git+https://github.com/richardcarls/rc-webcomponents.git",
11
+ "directory": "packages/rc-dialog"
12
+ },
13
+ "homepage": "https://github.com/richardcarls/rc-webcomponents#readme",
14
+ "license": "MIT",
15
+ "type": "module",
16
+ "files": [
17
+ "dist"
18
+ ],
19
+ "module": "./dist/rc-dialog.js",
20
+ "exports": {
21
+ ".": {
22
+ "import": {
23
+ "types": "./dist/types/packages/rc-dialog/src/index.d.ts",
24
+ "default": "./dist/rc-dialog.js"
25
+ }
26
+ },
27
+ "./define": {
28
+ "import": {
29
+ "types": "./dist/types/packages/rc-dialog/src/define.d.ts",
30
+ "default": "./dist/rc-dialog-define.js"
31
+ }
32
+ }
33
+ },
34
+ "types": "./dist/types/packages/rc-dialog/src/index.d.ts",
35
+ "sideEffects": [
36
+ "./dist/rc-dialog-define.js"
37
+ ],
38
+ "customElements": "dist/custom-elements.json",
39
+ "scripts": {
40
+ "dev": "vite",
41
+ "build": "tsc && vite build && cem analyze",
42
+ "cem:analyze": "cem analyze",
43
+ "preview": "vite preview",
44
+ "test:browser": "vitest",
45
+ "test:browser:chrome": "vitest --project=chromium",
46
+ "test:browser:firefox": "vitest --project=firefox",
47
+ "yalc:publish": "yalc publish --push"
48
+ },
49
+ "dependencies": {
50
+ "@rcarls/rc-common": "workspace:^"
51
+ },
52
+ "devDependencies": {
53
+ "@custom-elements-manifest/analyzer": "0.11.0",
54
+ "@guanghechen/rollup-plugin-copy": "^6.0.9",
55
+ "@vitest/browser-playwright": "4.1.5",
56
+ "lit": "^3.0.0",
57
+ "playwright": "^1.56.0",
58
+ "rollup": "^4.60.2",
59
+ "typescript": "~5.9.3",
60
+ "vite": "^7.1.7",
61
+ "vite-plugin-dts": "^4.5.4",
62
+ "vitest": "^4.0.6",
63
+ "vitest-browser-lit": "^1.0.1"
64
+ },
65
+ "peerDependencies": {
66
+ "lit": "^3.0.0"
67
+ }
68
+ }