@rivinia/modal 0.5.0 → 0.5.1
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
CHANGED
|
@@ -162,7 +162,29 @@ reference-counted scroll lock restores previous styles; focus trap + focus
|
|
|
162
162
|
restore + `aria-labelledby`/`aria-label`; `dir=rtl` works via logical
|
|
163
163
|
properties; SSR renders closed and opens after hydration; lazy-load the
|
|
164
164
|
entry (`loadComponent`) to split it into its own chunk (~16.7 kB raw,
|
|
165
|
-
~4.9 kB transfer, measured).
|
|
165
|
+
~4.9 kB transfer, measured).
|
|
166
|
+
|
|
167
|
+
## Toasts above an open modal (measured pattern)
|
|
168
|
+
|
|
169
|
+
A toast host outside the dialog can never paint above it — neither a high
|
|
170
|
+
`z-index` nor `popover="manual"` escapes the modal top layer (verified in
|
|
171
|
+
Chrome via `elementFromPoint`). When an error toast must appear over an open
|
|
172
|
+
modal (e.g. a data error inside the form), host the list **inside** the
|
|
173
|
+
dialog content instead: as a descendant of the top-layer element it paints
|
|
174
|
+
above the dialog background with zero extra code and zero coupling:
|
|
175
|
+
|
|
176
|
+
```html
|
|
177
|
+
<rv-modal-dialog [(open)]="open" [title]="'Edit user'">
|
|
178
|
+
<!-- paints above this dialog; positions are still viewport-anchored -->
|
|
179
|
+
<rv-toast-list [manager]="injectRiviniaToast()" [position]="'top-end'" />
|
|
180
|
+
<form>…</form>
|
|
181
|
+
</rv-modal-dialog>
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
Notes: positions stay viewport-anchored (`fixed`), so `top-end` means the
|
|
185
|
+
viewport corner in both LTR and RTL (logical properties). Toasts fired
|
|
186
|
+
while no dialog is open still need a page-level list — the two lists share
|
|
187
|
+
the same manager, so nothing is lost or duplicated. Toasts: MEASURED (Chrome, `elementFromPoint`) —
|
|
166
188
|
neither a high `z-index` nor `popover="manual"` paints above an open modal
|
|
167
189
|
dialog; toasts queue behind it. No modal→toast dependency by design.
|
|
168
190
|
|
|
@@ -179,6 +201,14 @@ Core checks `ModalHandle<TResult>.close(result?: TResult)` and
|
|
|
179
201
|
- The internal deferred-promise cast to `Promise<ModalResult<TResult>>` —
|
|
180
202
|
`close()` settles whatever it receives, never validated.
|
|
181
203
|
|
|
204
|
+
## Browser back button (non-goal)
|
|
205
|
+
|
|
206
|
+
Native `<dialog>.showModal()` does not integrate with history: the back
|
|
207
|
+
button neither closes the dialog nor is blocked by it (platform behavior,
|
|
208
|
+
not a Rivinia choice). If your app needs back-to-dismiss, wire it at the
|
|
209
|
+
app layer (router guard / `popstate` → `dismiss()`); the manager's
|
|
210
|
+
`dismissRequested` + guards flow stays the single teardown path.
|
|
211
|
+
|
|
182
212
|
## License
|
|
183
213
|
|
|
184
214
|
MIT.
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as i0 from "@angular/core";
|
|
2
|
-
import { Component, DOCUMENT, Directive, Injector, Service, computed, contentChild, effect, inject, input, model, output, signal, viewChild } from "@angular/core";
|
|
2
|
+
import { Component, DOCUMENT, Directive, Injector, Service, ViewEncapsulation, computed, contentChild, effect, inject, input, model, output, signal, viewChild } from "@angular/core";
|
|
3
3
|
import { NgComponentOutlet, NgTemplateOutlet } from "@angular/common";
|
|
4
4
|
import { RIVINIA_MODAL_CONTEXT } from "@rivinia/modal";
|
|
5
5
|
var RiviniaModalScrollLock = class RiviniaModalScrollLock {
|
|
@@ -505,7 +505,8 @@ var RiviniaModalDialog = class RiviniaModalDialog {
|
|
|
505
505
|
</dialog>
|
|
506
506
|
`,
|
|
507
507
|
isInline: true,
|
|
508
|
-
styles: [".rv-modal{color-scheme:light dark;--rv-modal-bg: #ffffff;--rv-modal-fg: #111111;--rv-modal-muted: #555555;--rv-modal-border: #e0e0e0;--rv-modal-radius: 12px;--rv-modal-shadow: 0 24px 64px rgba(0, 0, 0, .28);--rv-modal-backdrop: rgba(0, 0, 0, .5);--rv-modal-pad: 20px;--rv-modal-gap: 12px;--rv-modal-width-sm: 24rem;--rv-modal-width-md: 32rem;--rv-modal-width-lg: 48rem;--rv-modal-width-xl: 64rem;--rv-modal-max-height: min(86dvh, 100%);--rv-modal-duration: .15s;--rv-modal-duration-in: var(--rv-modal-duration);--rv-modal-duration-out: .12s;--rv-modal-easing: ease-out;--rv-modal-easing-in: var(--rv-modal-easing);--rv-modal-easing-out: ease-in;--rv-modal-radius-sheet: 20px;--rv-modal-width-panel: 24rem;padding:0;border:1px solid var(--rv-modal-border);border-radius:var(--rv-modal-radius);background:var(--rv-modal-bg);color:var(--rv-modal-fg);box-shadow:var(--rv-modal-shadow);max-height:var(--rv-modal-max-height);width:var(--rv-modal-width-md);max-width:calc(100dvw - 2rem)}@media(prefers-color-scheme:dark){.rv-modal{--rv-modal-bg: #161616;--rv-modal-fg: #f2f2f2;--rv-modal-muted: #a8a8a8;--rv-modal-border: #333333;--rv-modal-shadow: 0 24px 64px rgba(0, 0, 0, .6)}}.rv-modal::backdrop{background:var(--rv-modal-backdrop)}.rv-modal[data-size=sm]{width:var(--rv-modal-width-sm)}.rv-modal[data-size=lg]{width:var(--rv-modal-width-lg)}.rv-modal[data-size=xl]{width:var(--rv-modal-width-xl)}.rv-modal[data-size=full]{width:calc(100dvw - 2rem)}.rv-modal[data-size=auto]{width:auto}.rv-modal[data-position=top]{margin-block-start:8dvh}.rv-modal[data-position=bottom]{margin-block-start:auto;margin-block-end:8dvh}.rv-modal[data-position=top-start],.rv-modal[data-position=center-start]{margin-inline-start:1rem;margin-inline-end:auto}.rv-modal[data-position=top-end],.rv-modal[data-position=center-end]{margin-inline-start:auto;margin-inline-end:1rem}.rv-modal[data-position=bottom-start]{margin-block-start:auto;margin-inline-start:1rem}.rv-modal[data-position=bottom-end]{margin-block-start:auto;margin-inline-end:1rem}.rv-modal[data-position=fullscreen]{inset:0;width:100dvw;height:100dvh;max-width:none;max-height:none;border-radius:0}.rv-modal[data-position=side-start],.rv-modal[data-position=side-end]{height:100dvh;max-height:none;width:var(--rv-modal-width-panel);margin-block:0}.rv-modal[data-position=side-start]{margin-inline-start:0;margin-inline-end:auto;border-start-start-radius:0;border-end-start-radius:0}.rv-modal[data-position=side-end]{margin-inline-start:auto;margin-inline-end:0;border-start-end-radius:0;border-end-end-radius:0}@media(max-width:640px){.rv-modal[data-position=center],.rv-modal[data-position=top],.rv-modal[data-position=bottom],.rv-modal[data-position=bottom-start],.rv-modal[data-position=bottom-end]{margin-block-start:auto;margin-block-end:0;margin-inline:0;width:100dvw;max-width:none;border-end-end-radius:0;border-end-start-radius:0;border-start-start-radius:var(--rv-modal-radius-sheet);border-start-end-radius:var(--rv-modal-radius-sheet)}.rv-modal[data-position=center][data-state=open],.rv-modal[data-position=top][data-state=open],.rv-modal[data-position=bottom][data-state=open],.rv-modal[data-position=bottom-start][data-state=open],.rv-modal[data-position=bottom-end][data-state=open]{animation-name:rv-modal-in-bottom}}.rv-modal[data-animation]:not([data-animation=none])[data-state=open]{animation:var(--rv-modal-animation-in, rv-modal-in) var(--rv-modal-duration-in) var(--rv-modal-easing-in)}.rv-modal[data-animation=scale][data-state=open]{--rv-modal-animation-in: rv-modal-in-scale}.rv-modal[data-animation=slide-top][data-state=open]{--rv-modal-animation-in: rv-modal-in-top}.rv-modal[data-animation=slide-bottom][data-state=open]{--rv-modal-animation-in: rv-modal-in-bottom}.rv-modal[data-animation=slide-start][data-state=open]{--rv-modal-animation-in: rv-modal-in-start}.rv-modal[data-animation=slide-end][data-state=open],[dir=rtl] .rv-modal[data-animation=slide-start][data-state=open]{--rv-modal-animation-in: rv-modal-in-end}[dir=rtl] .rv-modal[data-animation=slide-end][data-state=open]{--rv-modal-animation-in: rv-modal-in-start}.rv-modal[data-state=closing]{animation:rv-modal-out var(--rv-modal-duration-out) var(--rv-modal-easing-out) forwards}@keyframes rv-modal-in{0%{opacity:0}}@keyframes rv-modal-in-scale{0%{opacity:0;transform:translateY(8px) scale(.98)}}@keyframes rv-modal-in-top{0%{opacity:0;transform:translateY(-24px)}}@keyframes rv-modal-in-bottom{0%{opacity:0;transform:translateY(24px)}}@keyframes rv-modal-in-start{0%{opacity:0;transform:translate(-24px)}}@keyframes rv-modal-in-end{0%{opacity:0;transform:translate(24px)}}@keyframes rv-modal-out{to{opacity:0;transform:translateY(8px) scale(.98)}}@media(prefers-reduced-motion:reduce){.rv-modal[data-animation]{animation:none}}.rv-modal-header{display:flex;align-items:flex-start;gap:var(--rv-modal-gap);padding:var(--rv-modal-pad) var(--rv-modal-pad) 0}.rv-modal-header-custom>:first-child{flex:1;min-width:0}.rv-modal-heading:empty{display:none}.rv-modal-title{margin:0;font-size:1.125rem;flex:1}.rv-modal-heading{flex:1;display:flex;flex-direction:column;gap:4px}.rv-modal-description{margin:0;font-size:.875rem;color:var(--rv-modal-muted)}.rv-modal-close{margin-inline-start:auto;flex:none;border:1px solid var(--rv-modal-border);border-radius:8px;background:transparent;color:inherit;font-size:1.25rem;line-height:1;padding:4px 10px;cursor:pointer}.rv-modal-close:focus-visible{outline:2px solid currentColor;outline-offset:2px}.rv-modal-body{padding:var(--rv-modal-pad);overflow:auto;max-height:inherit}.rv-modal-footer{display:flex;gap:var(--rv-modal-gap);justify-content:flex-end;padding:0 var(--rv-modal-pad) var(--rv-modal-pad)}.rv-modal-footer:empty{display:none}\n"]
|
|
508
|
+
styles: [".rv-modal{color-scheme:light dark;--rv-modal-bg: #ffffff;--rv-modal-fg: #111111;--rv-modal-muted: #555555;--rv-modal-border: #e0e0e0;--rv-modal-radius: 12px;--rv-modal-shadow: 0 24px 64px rgba(0, 0, 0, .28);--rv-modal-backdrop: rgba(0, 0, 0, .5);--rv-modal-pad: 20px;--rv-modal-gap: 12px;--rv-modal-width-sm: 24rem;--rv-modal-width-md: 32rem;--rv-modal-width-lg: 48rem;--rv-modal-width-xl: 64rem;--rv-modal-max-height: min(86dvh, 100%);--rv-modal-duration: .15s;--rv-modal-duration-in: var(--rv-modal-duration);--rv-modal-duration-out: .12s;--rv-modal-easing: ease-out;--rv-modal-easing-in: var(--rv-modal-easing);--rv-modal-easing-out: ease-in;--rv-modal-radius-sheet: 20px;--rv-modal-width-panel: 24rem;padding:0;border:1px solid var(--rv-modal-border);border-radius:var(--rv-modal-radius);background:var(--rv-modal-bg);color:var(--rv-modal-fg);box-shadow:var(--rv-modal-shadow);max-height:var(--rv-modal-max-height);width:var(--rv-modal-width-md);max-width:calc(100dvw - 2rem)}@media(prefers-color-scheme:dark){.rv-modal{--rv-modal-bg: #161616;--rv-modal-fg: #f2f2f2;--rv-modal-muted: #a8a8a8;--rv-modal-border: #333333;--rv-modal-shadow: 0 24px 64px rgba(0, 0, 0, .6)}}.rv-modal::backdrop{background:var(--rv-modal-backdrop)}.rv-modal[data-size=sm]{width:var(--rv-modal-width-sm)}.rv-modal[data-size=lg]{width:var(--rv-modal-width-lg)}.rv-modal[data-size=xl]{width:var(--rv-modal-width-xl)}.rv-modal[data-size=full]{width:calc(100dvw - 2rem)}.rv-modal[data-size=auto]{width:auto}.rv-modal[data-position=top]{margin-block-start:8dvh}.rv-modal[data-position=bottom]{margin-block-start:auto;margin-block-end:8dvh}.rv-modal[data-position=top-start],.rv-modal[data-position=center-start]{margin-inline-start:1rem;margin-inline-end:auto}.rv-modal[data-position=top-end],.rv-modal[data-position=center-end]{margin-inline-start:auto;margin-inline-end:1rem}.rv-modal[data-position=bottom-start]{margin-block-start:auto;margin-inline-start:1rem}.rv-modal[data-position=bottom-end]{margin-block-start:auto;margin-inline-end:1rem}.rv-modal[data-position=fullscreen]{inset:0;width:100dvw;height:100dvh;max-width:none;max-height:none;border-radius:0}.rv-modal[data-position=side-start],.rv-modal[data-position=side-end]{height:100dvh;max-height:none;width:var(--rv-modal-width-panel);margin-block:0}.rv-modal[data-position=side-start]{margin-inline-start:0;margin-inline-end:auto;border-start-start-radius:0;border-end-start-radius:0}.rv-modal[data-position=side-end]{margin-inline-start:auto;margin-inline-end:0;border-start-end-radius:0;border-end-end-radius:0}@media(max-width:640px){.rv-modal[data-position=center],.rv-modal[data-position=top],.rv-modal[data-position=bottom],.rv-modal[data-position=bottom-start],.rv-modal[data-position=bottom-end]{margin-block-start:auto;margin-block-end:0;margin-inline:0;width:100dvw;max-width:none;border-end-end-radius:0;border-end-start-radius:0;border-start-start-radius:var(--rv-modal-radius-sheet);border-start-end-radius:var(--rv-modal-radius-sheet)}.rv-modal[data-position=center][data-state=open],.rv-modal[data-position=top][data-state=open],.rv-modal[data-position=bottom][data-state=open],.rv-modal[data-position=bottom-start][data-state=open],.rv-modal[data-position=bottom-end][data-state=open]{animation-name:rv-modal-in-bottom}}.rv-modal[data-animation]:not([data-animation=none])[data-state=open]{animation:var(--rv-modal-animation-in, rv-modal-in) var(--rv-modal-duration-in) var(--rv-modal-easing-in)}.rv-modal[data-animation=scale][data-state=open]{--rv-modal-animation-in: rv-modal-in-scale}.rv-modal[data-animation=slide-top][data-state=open]{--rv-modal-animation-in: rv-modal-in-top}.rv-modal[data-animation=slide-bottom][data-state=open]{--rv-modal-animation-in: rv-modal-in-bottom}.rv-modal[data-animation=slide-start][data-state=open]{--rv-modal-animation-in: rv-modal-in-start}.rv-modal[data-animation=slide-end][data-state=open],[dir=rtl] .rv-modal[data-animation=slide-start][data-state=open]{--rv-modal-animation-in: rv-modal-in-end}[dir=rtl] .rv-modal[data-animation=slide-end][data-state=open]{--rv-modal-animation-in: rv-modal-in-start}.rv-modal[data-state=closing]{animation:rv-modal-out var(--rv-modal-duration-out) var(--rv-modal-easing-out) forwards}@keyframes rv-modal-in{0%{opacity:0}}@keyframes rv-modal-in-scale{0%{opacity:0;transform:translateY(8px) scale(.98)}}@keyframes rv-modal-in-top{0%{opacity:0;transform:translateY(-24px)}}@keyframes rv-modal-in-bottom{0%{opacity:0;transform:translateY(24px)}}@keyframes rv-modal-in-start{0%{opacity:0;transform:translate(-24px)}}@keyframes rv-modal-in-end{0%{opacity:0;transform:translate(24px)}}@keyframes rv-modal-out{to{opacity:0;transform:translateY(8px) scale(.98)}}@media(prefers-reduced-motion:reduce){.rv-modal[data-animation]{animation:none}}.rv-modal-header{display:flex;align-items:flex-start;gap:var(--rv-modal-gap);padding:var(--rv-modal-pad) var(--rv-modal-pad) 0}.rv-modal-header-custom>:first-child{flex:1;min-width:0}.rv-modal-heading:empty{display:none}.rv-modal-title{margin:0;font-size:1.125rem;flex:1}.rv-modal-heading{flex:1;display:flex;flex-direction:column;gap:4px}.rv-modal-description{margin:0;font-size:.875rem;color:var(--rv-modal-muted)}.rv-modal-close{margin-inline-start:auto;flex:none;border:1px solid var(--rv-modal-border);border-radius:8px;background:transparent;color:inherit;font-size:1.25rem;line-height:1;padding:4px 10px;cursor:pointer}.rv-modal-close:focus-visible{outline:2px solid currentColor;outline-offset:2px}.rv-modal-body{padding:var(--rv-modal-pad);overflow:auto;max-height:inherit}.rv-modal-footer{display:flex;gap:var(--rv-modal-gap);justify-content:flex-end;padding:0 var(--rv-modal-pad) var(--rv-modal-pad)}.rv-modal-footer:empty{display:none}\n"],
|
|
509
|
+
encapsulation: i0.ViewEncapsulation.None
|
|
509
510
|
});
|
|
510
511
|
};
|
|
511
512
|
i0.ɵɵngDeclareClassMetadata({
|
|
@@ -518,6 +519,7 @@ i0.ɵɵngDeclareClassMetadata({
|
|
|
518
519
|
args: [{
|
|
519
520
|
selector: "rv-modal-dialog",
|
|
520
521
|
standalone: true,
|
|
522
|
+
encapsulation: ViewEncapsulation.None,
|
|
521
523
|
template: `
|
|
522
524
|
<dialog
|
|
523
525
|
#dialog
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"rivinia-modal-ui.mjs","names":[],"sources":["../../../projects/modal/ui/src/lib/modal-scroll-lock.ts","../../../projects/modal/ui/src/lib/modal-dialog.ts","../../../projects/modal/ui/src/lib/modal-host.ts"],"sourcesContent":["import { DOCUMENT, Service, inject } from '@angular/core';\n\n/**\n * Reference-counted background scroll lock. Multiple overlays share one lock:\n * closing one modal never unlocks the page while another still needs it.\n * Restores the exact previous `overflow` value (never blanks app-owned\n * styles). All DOM access happens inside methods — never at module\n * evaluation — and every method is a no-op without a document (SSR).\n */\n@Service()\nexport class RiviniaModalScrollLock {\n private readonly _doc = inject(DOCUMENT, { optional: true });\n private _locks = 0;\n private _previousOverflow: string | null = null;\n\n /** Number of active locks (diagnostics/tests). */\n get active(): number {\n return this._locks;\n }\n\n acquire(): void {\n const doc = this.document;\n if (!doc) return;\n this._locks += 1;\n if (this._locks > 1) return;\n const body = doc.body;\n this._previousOverflow = body.style.overflow || null;\n body.style.overflow = 'hidden';\n // Reserve the scrollbar gutter where supported so content does not jump.\n // Overlay scrollbars ignore it; that is documented, not promised.\n doc.documentElement.style.setProperty('scrollbar-gutter', 'stable');\n }\n\n release(): void {\n const doc = this.document;\n if (!doc || this._locks === 0) {\n this._locks = 0;\n return;\n }\n this._locks -= 1;\n if (this._locks > 0) return;\n const body = doc.body;\n if (this._previousOverflow === null) body.style.removeProperty('overflow');\n else body.style.overflow = this._previousOverflow;\n this._previousOverflow = null;\n doc.documentElement.style.removeProperty('scrollbar-gutter');\n }\n\n private get document(): Document | null {\n try {\n return this._doc ?? (typeof document === 'undefined' ? null : document);\n } catch {\n return null;\n }\n }\n}\n","import {\n Component,\n Directive,\n DOCUMENT,\n ElementRef,\n computed,\n contentChild,\n effect,\n inject,\n input,\n model,\n output,\n signal,\n viewChild,\n} from '@angular/core';\nimport { RiviniaModalScrollLock } from './modal-scroll-lock';\nimport type {\n RiviniaModalAnimation,\n RiviniaModalBackdrop,\n RiviniaModalPosition,\n RiviniaModalSize,\n} from './ui-types';\n\nconst EXIT_FALLBACK_MS = 200;\n\n/**\n * Marker for custom header content. Put it on the element carrying\n * `rv-modal-header` — its presence (not just the attribute) tells the\n * dialog to hide the default title + close button:\n *\n * ```html\n * <div rv-modal-header rvModalHeader>My header</div>\n * ```\n */\n@Directive({ selector: '[rvModalHeader]', standalone: true })\nexport class RvModalHeader {}\n\n/**\n * Marker for custom footer content. Put it on the element carrying\n * `rv-modal-footer`. Without it the footer region never renders.\n */\n@Directive({ selector: '[rvModalFooter]', standalone: true })\nexport class RvModalFooter {}\n\n/**\n * Ready-made modal dialog on the native `<dialog>` element (top layer,\n * `::backdrop`, inert background, Escape `cancel` event — all platform\n * behavior, zero overlay dependencies).\n *\n * UI-only by design: it owns its own `open` state and never imports the\n * headless controller. Pair it with `@rivinia/modal` through\n * {@link RiviniaModalHost} (or your own adapter) when you want managed\n * lifecycle, guards and typed results.\n *\n * Projection: `[rv-modal-header]` replaces the default header (title +\n * close button); default `<ng-content>` is the body; `[rv-modal-footer]`\n * is optional and renders nothing when absent.\n */\n@Component({\n selector: 'rv-modal-dialog',\n standalone: true,\n template: `\n <dialog\n #dialog\n tabindex=\"-1\"\n class=\"rv-modal\"\n [attr.data-size]=\"size()\"\n [attr.data-position]=\"position()\"\n [attr.data-animation]=\"animation()\"\n [attr.data-state]=\"state()\"\n [attr.aria-label]=\"computedLabel()\"\n [attr.aria-labelledby]=\"title() && !ariaLabel() ? titleId : null\"\n [attr.aria-describedby]=\"description() ? descriptionId : null\"\n (cancel)=\"onCancel($event)\"\n (click)=\"onBackdropClick($event)\"\n (keydown)=\"onKeydown($event)\"\n >\n @if (hasCustomHeader()) {\n <div class=\"rv-modal-header rv-modal-header-custom\">\n <ng-content select=\"[rv-modal-header]\"></ng-content>\n </div>\n } @else if (title() || showCloseButton()) {\n <div class=\"rv-modal-header\">\n <div class=\"rv-modal-heading\">\n @if (title()) {\n <h2 class=\"rv-modal-title\" [id]=\"titleId\">{{ title() }}</h2>\n }\n @if (description()) {\n <p class=\"rv-modal-description\" [id]=\"descriptionId\">{{ description() }}</p>\n }\n </div>\n @if (showCloseButton()) {\n <button\n type=\"button\"\n class=\"rv-modal-close\"\n (click)=\"onCloseButton()\"\n aria-label=\"Close dialog\"\n >\n <span aria-hidden=\"true\">×</span>\n </button>\n }\n </div>\n }\n <div class=\"rv-modal-body\">\n <ng-content></ng-content>\n </div>\n @if (hasCustomFooter()) {\n <div class=\"rv-modal-footer\">\n <ng-content select=\"[rv-modal-footer]\"></ng-content>\n </div>\n }\n </dialog>\n `,\n styles: [\n `\n .rv-modal {\n color-scheme: light dark;\n --rv-modal-bg: #ffffff;\n --rv-modal-fg: #111111;\n --rv-modal-muted: #555555;\n --rv-modal-border: #e0e0e0;\n --rv-modal-radius: 12px;\n --rv-modal-shadow: 0 24px 64px rgba(0, 0, 0, 0.28);\n --rv-modal-backdrop: rgba(0, 0, 0, 0.5);\n --rv-modal-pad: 20px;\n --rv-modal-gap: 12px;\n --rv-modal-width-sm: 24rem;\n --rv-modal-width-md: 32rem;\n --rv-modal-width-lg: 48rem;\n --rv-modal-width-xl: 64rem;\n --rv-modal-max-height: min(86dvh, 100%);\n --rv-modal-duration: 150ms;\n --rv-modal-duration-in: var(--rv-modal-duration);\n --rv-modal-duration-out: 120ms;\n --rv-modal-easing: ease-out;\n --rv-modal-easing-in: var(--rv-modal-easing);\n --rv-modal-easing-out: ease-in;\n --rv-modal-radius-sheet: 20px;\n --rv-modal-width-panel: 24rem;\n padding: 0;\n border: 1px solid var(--rv-modal-border);\n border-radius: var(--rv-modal-radius);\n background: var(--rv-modal-bg);\n color: var(--rv-modal-fg);\n box-shadow: var(--rv-modal-shadow);\n max-height: var(--rv-modal-max-height);\n width: var(--rv-modal-width-md);\n max-width: calc(100dvw - 2rem);\n }\n @media (prefers-color-scheme: dark) {\n .rv-modal {\n --rv-modal-bg: #161616;\n --rv-modal-fg: #f2f2f2;\n --rv-modal-muted: #a8a8a8;\n --rv-modal-border: #333333;\n --rv-modal-shadow: 0 24px 64px rgba(0, 0, 0, 0.6);\n }\n }\n .rv-modal::backdrop {\n background: var(--rv-modal-backdrop);\n }\n .rv-modal[data-size='sm'] {\n width: var(--rv-modal-width-sm);\n }\n .rv-modal[data-size='lg'] {\n width: var(--rv-modal-width-lg);\n }\n .rv-modal[data-size='xl'] {\n width: var(--rv-modal-width-xl);\n }\n .rv-modal[data-size='full'] {\n width: calc(100dvw - 2rem);\n }\n .rv-modal[data-size='auto'] {\n width: auto;\n }\n .rv-modal[data-position='top'] {\n margin-block-start: 8dvh;\n }\n .rv-modal[data-position='bottom'] {\n margin-block-start: auto;\n margin-block-end: 8dvh;\n }\n .rv-modal[data-position='top-start'],\n .rv-modal[data-position='center-start'] {\n margin-inline-start: 1rem;\n margin-inline-end: auto;\n }\n .rv-modal[data-position='top-end'],\n .rv-modal[data-position='center-end'] {\n margin-inline-start: auto;\n margin-inline-end: 1rem;\n }\n .rv-modal[data-position='bottom-start'] {\n margin-block-start: auto;\n margin-inline-start: 1rem;\n }\n .rv-modal[data-position='bottom-end'] {\n margin-block-start: auto;\n margin-inline-end: 1rem;\n }\n .rv-modal[data-position='fullscreen'] {\n inset: 0;\n width: 100dvw;\n height: 100dvh;\n height: 100dvh;\n max-width: none;\n max-height: none;\n border-radius: 0;\n }\n .rv-modal[data-position='side-start'],\n .rv-modal[data-position='side-end'] {\n height: 100dvh;\n max-height: none;\n width: var(--rv-modal-width-panel);\n margin-block: 0;\n }\n .rv-modal[data-position='side-start'] {\n margin-inline-start: 0;\n margin-inline-end: auto;\n border-start-start-radius: 0;\n border-end-start-radius: 0;\n }\n .rv-modal[data-position='side-end'] {\n margin-inline-start: auto;\n margin-inline-end: 0;\n border-start-end-radius: 0;\n border-end-end-radius: 0;\n }\n @media (max-width: 640px) {\n .rv-modal[data-position='center'],\n .rv-modal[data-position='top'],\n .rv-modal[data-position='bottom'],\n .rv-modal[data-position='bottom-start'],\n .rv-modal[data-position='bottom-end'] {\n margin-block-start: auto;\n margin-block-end: 0;\n margin-inline: 0;\n width: 100dvw;\n max-width: none;\n border-end-end-radius: 0;\n border-end-start-radius: 0;\n border-start-start-radius: var(--rv-modal-radius-sheet);\n border-start-end-radius: var(--rv-modal-radius-sheet);\n }\n .rv-modal[data-position='center'][data-state='open'],\n .rv-modal[data-position='top'][data-state='open'],\n .rv-modal[data-position='bottom'][data-state='open'],\n .rv-modal[data-position='bottom-start'][data-state='open'],\n .rv-modal[data-position='bottom-end'][data-state='open'] {\n animation-name: rv-modal-in-bottom;\n }\n }\n .rv-modal[data-animation]:not([data-animation='none'])[data-state='open'] {\n animation: var(--rv-modal-animation-in, rv-modal-in) var(--rv-modal-duration-in)\n var(--rv-modal-easing-in);\n }\n .rv-modal[data-animation='scale'][data-state='open'] {\n --rv-modal-animation-in: rv-modal-in-scale;\n }\n .rv-modal[data-animation='slide-top'][data-state='open'] {\n --rv-modal-animation-in: rv-modal-in-top;\n }\n .rv-modal[data-animation='slide-bottom'][data-state='open'] {\n --rv-modal-animation-in: rv-modal-in-bottom;\n }\n .rv-modal[data-animation='slide-start'][data-state='open'] {\n --rv-modal-animation-in: rv-modal-in-start;\n }\n .rv-modal[data-animation='slide-end'][data-state='open'] {\n --rv-modal-animation-in: rv-modal-in-end;\n }\n [dir='rtl'] .rv-modal[data-animation='slide-start'][data-state='open'] {\n --rv-modal-animation-in: rv-modal-in-end;\n }\n [dir='rtl'] .rv-modal[data-animation='slide-end'][data-state='open'] {\n --rv-modal-animation-in: rv-modal-in-start;\n }\n .rv-modal[data-state='closing'] {\n animation: rv-modal-out var(--rv-modal-duration-out) var(--rv-modal-easing-out) forwards;\n }\n @keyframes rv-modal-in {\n from {\n opacity: 0;\n }\n }\n @keyframes rv-modal-in-scale {\n from {\n opacity: 0;\n transform: translateY(8px) scale(0.98);\n }\n }\n @keyframes rv-modal-in-top {\n from {\n opacity: 0;\n transform: translateY(-24px);\n }\n }\n @keyframes rv-modal-in-bottom {\n from {\n opacity: 0;\n transform: translateY(24px);\n }\n }\n @keyframes rv-modal-in-start {\n from {\n opacity: 0;\n transform: translateX(-24px);\n }\n }\n @keyframes rv-modal-in-end {\n from {\n opacity: 0;\n transform: translateX(24px);\n }\n }\n @keyframes rv-modal-out {\n to {\n opacity: 0;\n transform: translateY(8px) scale(0.98);\n }\n }\n @media (prefers-reduced-motion: reduce) {\n .rv-modal[data-animation] {\n animation: none;\n }\n }\n .rv-modal-header {\n display: flex;\n align-items: flex-start;\n gap: var(--rv-modal-gap);\n padding: var(--rv-modal-pad) var(--rv-modal-pad) 0;\n }\n .rv-modal-header-custom > :first-child {\n flex: 1;\n min-width: 0;\n }\n .rv-modal-heading:empty {\n display: none;\n }\n .rv-modal-title {\n margin: 0;\n font-size: 1.125rem;\n flex: 1;\n }\n .rv-modal-heading {\n flex: 1;\n display: flex;\n flex-direction: column;\n gap: 4px;\n }\n .rv-modal-description {\n margin: 0;\n font-size: 0.875rem;\n color: var(--rv-modal-muted);\n }\n .rv-modal-close {\n margin-inline-start: auto;\n flex: none;\n border: 1px solid var(--rv-modal-border);\n border-radius: 8px;\n background: transparent;\n color: inherit;\n font-size: 1.25rem;\n line-height: 1;\n padding: 4px 10px;\n cursor: pointer;\n }\n .rv-modal-close:focus-visible {\n outline: 2px solid currentColor;\n outline-offset: 2px;\n }\n .rv-modal-body {\n padding: var(--rv-modal-pad);\n overflow: auto;\n max-height: inherit;\n }\n .rv-modal-footer {\n display: flex;\n gap: var(--rv-modal-gap);\n justify-content: flex-end;\n padding: 0 var(--rv-modal-pad) var(--rv-modal-pad);\n }\n .rv-modal-footer:empty {\n display: none;\n }\n `,\n ],\n})\nexport class RiviniaModalDialog {\n /** Two-way open state. UI-only mode owns this; the host drives it in managed mode. */\n readonly open = model<boolean>(false);\n\n readonly title = input<string>('');\n /**\n * Supporting text under the title (shadcn-style description). Rendered in\n * the default header only, and wired as the dialog's `aria-describedby`\n * for free accessible naming. Empty = no description element at all.\n */\n readonly description = input<string>('');\n /** Explicit accessible name. When set, `aria-label` wins over the title reference. */\n readonly ariaLabel = input<string>('');\n readonly size = input<RiviniaModalSize>('md');\n readonly position = input<RiviniaModalPosition>('center');\n readonly animation = input<RiviniaModalAnimation>('scale');\n readonly closeOnEscape = input<boolean>(true);\n readonly backdrop = input<RiviniaModalBackdrop>('close');\n readonly showCloseButton = input<boolean>(true);\n /**\n * Managed mode (for {@link RiviniaModalHost}): UI intents (X button,\n * Escape, backdrop) emit `dismissRequested` INSTEAD of dismissing, so the\n * manager runs guards first and settles results. Drive the exit with the\n * `closing` input; acknowledge teardown with `exitDone`.\n * UI-only mode (`false`, default) dismisses locally.\n */\n readonly managed = input<boolean>(false);\n /**\n * Managed-mode exit trigger. Set `true` when the manager reports this\n * instance `closing`: the dialog plays its exit animation, then emits\n * `exitDone` (call `confirmClosed()` there). Ignored in UI-only mode.\n */\n readonly closing = input<boolean>(false);\n\n /** Settled with a value (primary/confirm actions call `close(value)`). */\n readonly confirmed = output<unknown>();\n /** Settled without a value (X button, backdrop, Escape, `dismiss()`). */\n readonly dismissed = output<string | undefined>();\n /** Explicit cancellation (`cancel()`). Kept distinct from dismissal. */\n readonly cancelled = output<string | undefined>();\n /**\n * Managed-mode intent (X button / Escape / backdrop). Emitted INSTEAD of\n * dismissing — the host routes it to the manager first. Never emitted in\n * UI-only mode.\n */\n readonly dismissRequested = output<'button' | 'escape' | 'backdrop'>();\n /** Managed-mode exit completion. Acknowledge with `confirmClosed()`. */\n readonly exitDone = output<void>();\n\n private readonly dialog = viewChild<ElementRef<HTMLDialogElement>>('dialog');\n private readonly headerSlot = contentChild(RvModalHeader);\n private readonly footerSlot = contentChild(RvModalFooter);\n private readonly doc = inject(DOCUMENT, { optional: true });\n private readonly scrollLock = inject(RiviniaModalScrollLock, { optional: true });\n private readonly titleId = `rv-modal-${++dialogSeq}`;\n private readonly descriptionId = `rv-modal-desc-${dialogSeq}`;\n private readonly phase = signal<'closed' | 'open' | 'closing'>('closed');\n private readonly invoker: { current: Element | null } = { current: null };\n private exitTimer: ReturnType<typeof setTimeout> | null = null;\n\n /** Template state for animation styling. Never rendered open on the server. */\n readonly state = computed(() => this.phase());\n readonly hasCustomHeader = computed(() => this.headerSlot() !== undefined);\n readonly hasCustomFooter = computed(() => this.footerSlot() !== undefined);\n readonly computedLabel = computed(() => this.ariaLabel() || null);\n\n constructor() {\n effect(() => {\n if (this.open()) this.show();\n // External close acts only while fully open: during 'closing' the\n // exit animation owns teardown (its timer finalizes). This guard is\n // what keeps `finish()` from cutting the animation via `open.set(false)`.\n else if (this.phase() === 'open') this.hide();\n });\n effect(() => {\n // Managed exit: the manager moved this instance to `closing`.\n if (this.managed() && this.closing() && this.phase() === 'open') {\n this.playExit(() => this.exitDone.emit());\n }\n });\n }\n\n /** Confirm with a value. No-op while closing/closed. */\n confirm(value?: unknown): boolean {\n if (this.phase() !== 'open') return false;\n this.finish('confirmed', value);\n return true;\n }\n\n /** Backwards-compatible close: confirms when given a value, dismisses otherwise. */\n close(value?: unknown): boolean {\n return value === undefined ? this.dismiss() : this.confirm(value);\n }\n\n /** Dismiss without a value (X button, backdrop, Escape route here). */\n dismiss(reason?: string): boolean {\n if (this.phase() !== 'open') return false;\n this.finish('dismissed', undefined, reason);\n return true;\n }\n\n /** Explicit cancellation. */\n cancel(reason?: string): boolean {\n if (this.phase() !== 'open') return false;\n this.finish('cancelled', undefined, reason);\n return true;\n }\n\n protected onCloseButton(): void {\n if (this.managed()) {\n if (this.phase() === 'open') this.dismissRequested.emit('button');\n return;\n }\n this.dismiss('close-button');\n }\n\n protected onCancel(event: Event): void {\n if (!this.closeOnEscape()) {\n event.preventDefault();\n return;\n }\n // Native `cancel` already closed nothing yet — route through our pipeline\n // so scroll-lock, focus restore and outputs stay consistent.\n event.preventDefault();\n if (this.managed()) {\n if (this.phase() === 'open') this.dismissRequested.emit('escape');\n return;\n }\n this.dismiss('escape');\n }\n\n protected onBackdropClick(event: MouseEvent): void {\n const el = this.dialog()?.nativeElement;\n if (!el || event.target !== el) return; // inside clicks never count\n if (this.backdrop() !== 'close') return;\n if (this.managed()) {\n if (this.phase() === 'open') this.dismissRequested.emit('backdrop');\n return;\n }\n this.dismiss('backdrop');\n }\n\n protected onKeydown(event: KeyboardEvent): void {\n if (event.key !== 'Tab') return;\n const el = this.dialog()?.nativeElement;\n if (!el) return;\n const items = focusables(el);\n if (items.length === 0) {\n event.preventDefault();\n return;\n }\n const first = items[0];\n const last = items[items.length - 1];\n const active = (this.doc ?? document).activeElement;\n if (event.shiftKey && (active === first || !el.contains(active))) {\n event.preventDefault();\n last.focus();\n } else if (!event.shiftKey && active === last) {\n event.preventDefault();\n first.focus();\n }\n }\n\n private get live(): boolean {\n return typeof window !== 'undefined' && !!this.doc;\n }\n\n private show(): void {\n if (!this.live) return; // SSR: stay closed until client hydration opens us\n const el = this.dialog()?.nativeElement;\n if (!el || this.phase() !== 'closed') return;\n try {\n this.invoker.current = this.doc?.activeElement ?? null;\n } catch {\n this.invoker.current = null;\n }\n this.scrollLock?.acquire();\n this.phase.set('open');\n if (!el.open) {\n try {\n el.showModal();\n } catch {\n // Already open (e.g. double effect run): continue — state is ours.\n }\n }\n focusFirst(el, this.showCloseButton());\n }\n\n private hide(): void {\n if (this.phase() === 'closed') return;\n this.teardown(false);\n }\n\n private finish(\n kind: 'confirmed' | 'dismissed' | 'cancelled',\n value?: unknown,\n reason?: string,\n ): void {\n this.teardown(true);\n if (kind === 'confirmed') this.confirmed.emit(value);\n else if (kind === 'dismissed') this.dismissed.emit(reason);\n else this.cancelled.emit(reason);\n if (this.open()) this.open.set(false);\n }\n\n private teardown(animate: boolean): void {\n if (!animate) {\n if (this.exitTimer) {\n clearTimeout(this.exitTimer);\n this.exitTimer = null;\n }\n this.finalize(this.dialog()?.nativeElement ?? null);\n return;\n }\n this.playExit(() => {});\n }\n\n /** Plays the exit animation (when warranted), then runs `done`. Idempotent. */\n private playExit(done: () => void): void {\n if (this.phase() !== 'open') {\n done();\n return;\n }\n if (this.exitTimer) {\n clearTimeout(this.exitTimer);\n this.exitTimer = null;\n }\n const el = this.dialog()?.nativeElement ?? null;\n const animated = this.animation() !== 'none' && this.prefersMotion() && !!el?.open;\n this.phase.set(animated ? 'closing' : 'closed');\n if (!animated) {\n this.finalize(el);\n done();\n return;\n }\n // Honor consumer-overridden durations instead of a fixed fallback, so a\n // longer `--rv-modal-duration-out` is never cut short.\n this.exitTimer = setTimeout(() => {\n this.finalize(el);\n done();\n }, this.exitDurationMs(el));\n }\n\n private exitDurationMs(el: HTMLDialogElement | null): number {\n try {\n const raw = el ? getComputedStyle(el).animationDuration : '';\n const seconds = raw\n .split(',')\n .map((part) => parseFloat(part))\n .filter((n) => Number.isFinite(n));\n if (seconds.length > 0) return Math.max(0, Math.max(...seconds) * 1000) + 50;\n } catch {\n // Non-CSS environments (tests): fall through to the fallback.\n }\n return EXIT_FALLBACK_MS;\n }\n\n private finalize(el: HTMLDialogElement | null): void {\n this.exitTimer = null;\n try {\n el?.close();\n } catch {\n // Already closed natively — ours to reconcile, not to throw.\n }\n this.phase.set('closed');\n this.scrollLock?.release();\n const invoker = this.invoker.current as HTMLElement | null;\n this.invoker.current = null;\n try {\n invoker?.focus?.();\n } catch {\n // Invoker gone (navigated away) — nothing to restore to.\n }\n }\n\n private prefersMotion(): boolean {\n try {\n return (\n typeof window !== 'undefined' &&\n !window.matchMedia('(prefers-reduced-motion: reduce)').matches\n );\n } catch {\n return true;\n }\n }\n}\n\nlet dialogSeq = 0;\n\nfunction focusables(root: HTMLElement): HTMLElement[] {\n // No visibility filtering on purpose: jsdom reports zero rects (unit\n // tests), and `disabled` is already excluded by the selector. Hidden\n // focusables inside a visible dialog are a consumer edge, not ours.\n const selector =\n 'a[href], button:not([disabled]), input:not([disabled]), select:not([disabled]), ' +\n 'textarea:not([disabled]), [tabindex]:not([tabindex=\"-1\"])';\n return Array.from(root.querySelectorAll<HTMLElement>(selector));\n}\n\nfunction focusFirst(dialog: HTMLDialogElement, closeVisible: boolean): void {\n try {\n const auto = dialog.querySelector<HTMLElement>('[autofocus]');\n if (auto) {\n auto.focus({ preventScroll: true });\n return;\n }\n // Atlassian rule: no close button → focus the title; otherwise the\n // dialog itself (its tabindex=\"-1\" makes it focusable).\n if (!closeVisible) {\n const title = dialog.querySelector<HTMLElement>('.rv-modal-title');\n if (title) {\n title.setAttribute('tabindex', '-1');\n title.focus({ preventScroll: true });\n return;\n }\n }\n dialog.focus({ preventScroll: true });\n } catch {\n // Focus is best-effort (e.g. during teardown races).\n }\n}\n","import { Component, Injector, computed, input } from '@angular/core';\nimport type { Signal } from '@angular/core';\nimport { NgComponentOutlet, NgTemplateOutlet } from '@angular/common';\nimport { RIVINIA_MODAL_CONTEXT } from '@rivinia/modal';\nimport type {\n ModalContext,\n ModalDismissOrigin,\n ModalInstance,\n ModalTemplateContext,\n} from '@rivinia/modal';\nimport { RiviniaModalDialog } from './modal-dialog';\nimport type { RiviniaModalSize } from './ui-types';\n\n/**\n * Managed adapter: renders every `visible()` instance of the headless\n * `RiviniaModal` inside a {@link RiviniaModalDialog} and routes all UI\n * intents through the manager first (guards run; results settle in Core —\n * the single source of truth). The dialog never settles anything itself in\n * this mode; listen to `ModalHandle.result`, not dialog outputs.\n *\n * Exit coordination: when Core reports an instance `closing`, the dialog\n * plays its exit animation and the host acknowledges with `confirmClosed()`\n * (idempotent — safe if `closingTimeout` auto-finalized first). Match\n * `closingTimeout` to the animation duration so exits stay visible.\n *\n * Title resolution: `titleOf` per instance (default: `metadata.title` →\n * `ref` → `''`). Content descriptors render as-is: templates via\n * `ngTemplateOutlet` with `{$implicit: data, modal}`, components via\n * `NgComponentOutlet` with `inputs` + a per-instance\n * `RIVINIA_MODAL_CONTEXT` injector (the lab-proven pattern).\n *\n * The manager arrives via `[modal]` input (D06 input-composition: the UI\n * entry never instantiates Core itself, so UI-only consumers pay nothing).\n * The input is typed as {@link RiviniaModalHostManager} — the structural\n * slice the host actually drives — so the full manager (or a test double)\n * satisfies it from any module realm:\n *\n * ```html\n * <rv-modal-host [modal]=\"injectRiviniaModal()\" />\n * ```\n */\nexport interface RiviniaModalHostManager {\n readonly visible: Signal<readonly ModalInstance<unknown>[]>;\n requestDismiss(id: string, origin: ModalDismissOrigin): Promise<boolean>;\n confirmClosed(id: string): boolean;\n close(id: string, value?: unknown): boolean;\n dismiss(id: string, reason?: string): boolean;\n}\n\n@Component({\n selector: 'rv-modal-host',\n standalone: true,\n imports: [RiviniaModalDialog, NgComponentOutlet, NgTemplateOutlet],\n template: `\n @for (instance of visible(); track instance.id) {\n <rv-modal-dialog\n [open]=\"true\"\n [managed]=\"true\"\n [closing]=\"instance.status === 'closing'\"\n [title]=\"titleOfInstance(instance)\"\n [size]=\"size()\"\n [showCloseButton]=\"showCloseButton()\"\n [closeOnEscape]=\"instance.dismiss.closeOnEscape ?? true\"\n [backdrop]=\"instance.dismiss.backdrop ?? 'close'\"\n (dismissRequested)=\"onIntent(instance.id, $event)\"\n (exitDone)=\"onExitDone(instance.id)\"\n >\n @if (instance.content.kind === 'template') {\n <ng-container\n *ngTemplateOutlet=\"instance.content.template; context: templateContext(instance)\"\n ></ng-container>\n } @else {\n <ng-container\n *ngComponentOutlet=\"\n instance.content.component;\n inputs: instance.content.inputs;\n injector: contextInjector(instance)\n \"\n ></ng-container>\n }\n </rv-modal-dialog>\n }\n `,\n})\nexport class RiviniaModalHost {\n /**\n * The headless manager to render. Typed as {@link RiviniaModalHostManager}\n * (structural) — pass `injectRiviniaModal()` from your app code.\n */\n readonly modal = input.required<RiviniaModalHostManager>();\n\n /** Dialog size for every managed instance. @default 'md' */\n readonly size = input<RiviniaModalSize>('md');\n /** Render the header close button on every managed instance. @default true */\n readonly showCloseButton = input<boolean>(true);\n /** Resolve a title per instance. Default: `metadata.title` → `ref` → `''`. */\n readonly titleOf = input<(instance: ModalInstance<unknown>) => string | undefined>();\n\n protected readonly visible = computed(() => this.modal().visible());\n\n protected titleOfInstance(instance: ModalInstance<unknown>): string {\n try {\n const custom = this.titleOf()?.(instance);\n if (custom !== undefined) return custom;\n } catch {\n // A throwing resolver must never break rendering — fall through.\n }\n const meta = instance.metadata?.['title'];\n if (typeof meta === 'string' && meta.length > 0) return meta;\n return instance.ref ?? '';\n }\n\n protected templateContext(instance: ModalInstance<unknown>): ModalTemplateContext<unknown> {\n return { $implicit: instance.data, modal: this.modalContext(instance) };\n }\n\n protected contextInjector(instance: ModalInstance<unknown>): Injector {\n const modal = this.modal();\n return Injector.create({\n providers: [\n {\n provide: RIVINIA_MODAL_CONTEXT,\n useValue: {\n id: instance.id,\n data: instance.data,\n parentId: instance.parentId,\n close: (result?: unknown) => modal.close(instance.id, result),\n dismiss: (reason?: string) => modal.dismiss(instance.id, reason),\n } satisfies ModalContext<unknown>,\n },\n ],\n });\n }\n\n protected async onIntent(id: string, origin: 'button' | 'escape' | 'backdrop'): Promise<void> {\n try {\n await this.modal().requestDismiss(id, origin);\n } catch {\n // requestDismiss never rejects (deny-safe by contract) — defensive only.\n }\n // Granted → status flips to `closing` → dialog exit → `exitDone`.\n // Denied → nothing moves; the dialog never left `open`. No manual toggle.\n }\n\n protected onExitDone(id: string): void {\n this.modal().confirmClosed(id);\n }\n\n private modalContext(instance: ModalInstance<unknown>): ModalContext<unknown> {\n const modal = this.modal();\n const id = instance.id;\n return {\n id,\n data: instance.data,\n parentId: instance.parentId,\n close: (result?: unknown) => modal.close(id, result),\n dismiss: (reason?: string) => modal.dismiss(id, reason),\n };\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"rivinia-modal-ui.mjs","names":[],"sources":["../../../projects/modal/ui/src/lib/modal-scroll-lock.ts","../../../projects/modal/ui/src/lib/modal-dialog.ts","../../../projects/modal/ui/src/lib/modal-host.ts"],"sourcesContent":["import { DOCUMENT, Service, inject } from '@angular/core';\n\n/**\n * Reference-counted background scroll lock. Multiple overlays share one lock:\n * closing one modal never unlocks the page while another still needs it.\n * Restores the exact previous `overflow` value (never blanks app-owned\n * styles). All DOM access happens inside methods — never at module\n * evaluation — and every method is a no-op without a document (SSR).\n */\n@Service()\nexport class RiviniaModalScrollLock {\n private readonly _doc = inject(DOCUMENT, { optional: true });\n private _locks = 0;\n private _previousOverflow: string | null = null;\n\n /** Number of active locks (diagnostics/tests). */\n get active(): number {\n return this._locks;\n }\n\n acquire(): void {\n const doc = this.document;\n if (!doc) return;\n this._locks += 1;\n if (this._locks > 1) return;\n const body = doc.body;\n this._previousOverflow = body.style.overflow || null;\n body.style.overflow = 'hidden';\n // Reserve the scrollbar gutter where supported so content does not jump.\n // Overlay scrollbars ignore it; that is documented, not promised.\n doc.documentElement.style.setProperty('scrollbar-gutter', 'stable');\n }\n\n release(): void {\n const doc = this.document;\n if (!doc || this._locks === 0) {\n this._locks = 0;\n return;\n }\n this._locks -= 1;\n if (this._locks > 0) return;\n const body = doc.body;\n if (this._previousOverflow === null) body.style.removeProperty('overflow');\n else body.style.overflow = this._previousOverflow;\n this._previousOverflow = null;\n doc.documentElement.style.removeProperty('scrollbar-gutter');\n }\n\n private get document(): Document | null {\n try {\n return this._doc ?? (typeof document === 'undefined' ? null : document);\n } catch {\n return null;\n }\n }\n}\n","import {\n Component,\n Directive,\n DOCUMENT,\n ElementRef,\n ViewEncapsulation,\n computed,\n contentChild,\n effect,\n inject,\n input,\n model,\n output,\n signal,\n viewChild,\n} from '@angular/core';\nimport { RiviniaModalScrollLock } from './modal-scroll-lock';\nimport type {\n RiviniaModalAnimation,\n RiviniaModalBackdrop,\n RiviniaModalPosition,\n RiviniaModalSize,\n} from './ui-types';\n\nconst EXIT_FALLBACK_MS = 200;\n\n/**\n * Marker for custom header content. Put it on the element carrying\n * `rv-modal-header` — its presence (not just the attribute) tells the\n * dialog to hide the default title + close button:\n *\n * ```html\n * <div rv-modal-header rvModalHeader>My header</div>\n * ```\n */\n@Directive({ selector: '[rvModalHeader]', standalone: true })\nexport class RvModalHeader {}\n\n/**\n * Marker for custom footer content. Put it on the element carrying\n * `rv-modal-footer`. Without it the footer region never renders.\n */\n@Directive({ selector: '[rvModalFooter]', standalone: true })\nexport class RvModalFooter {}\n\n/**\n * Ready-made modal dialog on the native `<dialog>` element (top layer,\n * `::backdrop`, inert background, Escape `cancel` event — all platform\n * behavior, zero overlay dependencies).\n *\n * UI-only by design: it owns its own `open` state and never imports the\n * headless controller. Pair it with `@rivinia/modal` through\n * {@link RiviniaModalHost} (or your own adapter) when you want managed\n * lifecycle, guards and typed results.\n *\n * Projection: `[rv-modal-header]` replaces the default header (title +\n * close button); default `<ng-content>` is the body; `[rv-modal-footer]`\n * is optional and renders nothing when absent.\n */\n@Component({\n selector: 'rv-modal-dialog',\n standalone: true,\n // Unencapsulated like rv-menu: custom projected headers/footers belong to\n // the consumer's template, which emulated selectors cannot reach. Every\n // rule is scoped under `.rv-modal`, so nothing leaks.\n encapsulation: ViewEncapsulation.None,\n template: `\n <dialog\n #dialog\n tabindex=\"-1\"\n class=\"rv-modal\"\n [attr.data-size]=\"size()\"\n [attr.data-position]=\"position()\"\n [attr.data-animation]=\"animation()\"\n [attr.data-state]=\"state()\"\n [attr.aria-label]=\"computedLabel()\"\n [attr.aria-labelledby]=\"title() && !ariaLabel() ? titleId : null\"\n [attr.aria-describedby]=\"description() ? descriptionId : null\"\n (cancel)=\"onCancel($event)\"\n (click)=\"onBackdropClick($event)\"\n (keydown)=\"onKeydown($event)\"\n >\n @if (hasCustomHeader()) {\n <div class=\"rv-modal-header rv-modal-header-custom\">\n <ng-content select=\"[rv-modal-header]\"></ng-content>\n </div>\n } @else if (title() || showCloseButton()) {\n <div class=\"rv-modal-header\">\n <div class=\"rv-modal-heading\">\n @if (title()) {\n <h2 class=\"rv-modal-title\" [id]=\"titleId\">{{ title() }}</h2>\n }\n @if (description()) {\n <p class=\"rv-modal-description\" [id]=\"descriptionId\">{{ description() }}</p>\n }\n </div>\n @if (showCloseButton()) {\n <button\n type=\"button\"\n class=\"rv-modal-close\"\n (click)=\"onCloseButton()\"\n aria-label=\"Close dialog\"\n >\n <span aria-hidden=\"true\">×</span>\n </button>\n }\n </div>\n }\n <div class=\"rv-modal-body\">\n <ng-content></ng-content>\n </div>\n @if (hasCustomFooter()) {\n <div class=\"rv-modal-footer\">\n <ng-content select=\"[rv-modal-footer]\"></ng-content>\n </div>\n }\n </dialog>\n `,\n styles: [\n `\n .rv-modal {\n color-scheme: light dark;\n --rv-modal-bg: #ffffff;\n --rv-modal-fg: #111111;\n --rv-modal-muted: #555555;\n --rv-modal-border: #e0e0e0;\n --rv-modal-radius: 12px;\n --rv-modal-shadow: 0 24px 64px rgba(0, 0, 0, 0.28);\n --rv-modal-backdrop: rgba(0, 0, 0, 0.5);\n --rv-modal-pad: 20px;\n --rv-modal-gap: 12px;\n --rv-modal-width-sm: 24rem;\n --rv-modal-width-md: 32rem;\n --rv-modal-width-lg: 48rem;\n --rv-modal-width-xl: 64rem;\n --rv-modal-max-height: min(86dvh, 100%);\n --rv-modal-duration: 150ms;\n --rv-modal-duration-in: var(--rv-modal-duration);\n --rv-modal-duration-out: 120ms;\n --rv-modal-easing: ease-out;\n --rv-modal-easing-in: var(--rv-modal-easing);\n --rv-modal-easing-out: ease-in;\n --rv-modal-radius-sheet: 20px;\n --rv-modal-width-panel: 24rem;\n padding: 0;\n border: 1px solid var(--rv-modal-border);\n border-radius: var(--rv-modal-radius);\n background: var(--rv-modal-bg);\n color: var(--rv-modal-fg);\n box-shadow: var(--rv-modal-shadow);\n max-height: var(--rv-modal-max-height);\n width: var(--rv-modal-width-md);\n max-width: calc(100dvw - 2rem);\n }\n @media (prefers-color-scheme: dark) {\n .rv-modal {\n --rv-modal-bg: #161616;\n --rv-modal-fg: #f2f2f2;\n --rv-modal-muted: #a8a8a8;\n --rv-modal-border: #333333;\n --rv-modal-shadow: 0 24px 64px rgba(0, 0, 0, 0.6);\n }\n }\n .rv-modal::backdrop {\n background: var(--rv-modal-backdrop);\n }\n .rv-modal[data-size='sm'] {\n width: var(--rv-modal-width-sm);\n }\n .rv-modal[data-size='lg'] {\n width: var(--rv-modal-width-lg);\n }\n .rv-modal[data-size='xl'] {\n width: var(--rv-modal-width-xl);\n }\n .rv-modal[data-size='full'] {\n width: calc(100dvw - 2rem);\n }\n .rv-modal[data-size='auto'] {\n width: auto;\n }\n .rv-modal[data-position='top'] {\n margin-block-start: 8dvh;\n }\n .rv-modal[data-position='bottom'] {\n margin-block-start: auto;\n margin-block-end: 8dvh;\n }\n .rv-modal[data-position='top-start'],\n .rv-modal[data-position='center-start'] {\n margin-inline-start: 1rem;\n margin-inline-end: auto;\n }\n .rv-modal[data-position='top-end'],\n .rv-modal[data-position='center-end'] {\n margin-inline-start: auto;\n margin-inline-end: 1rem;\n }\n .rv-modal[data-position='bottom-start'] {\n margin-block-start: auto;\n margin-inline-start: 1rem;\n }\n .rv-modal[data-position='bottom-end'] {\n margin-block-start: auto;\n margin-inline-end: 1rem;\n }\n .rv-modal[data-position='fullscreen'] {\n inset: 0;\n width: 100dvw;\n height: 100dvh;\n height: 100dvh;\n max-width: none;\n max-height: none;\n border-radius: 0;\n }\n .rv-modal[data-position='side-start'],\n .rv-modal[data-position='side-end'] {\n height: 100dvh;\n max-height: none;\n width: var(--rv-modal-width-panel);\n margin-block: 0;\n }\n .rv-modal[data-position='side-start'] {\n margin-inline-start: 0;\n margin-inline-end: auto;\n border-start-start-radius: 0;\n border-end-start-radius: 0;\n }\n .rv-modal[data-position='side-end'] {\n margin-inline-start: auto;\n margin-inline-end: 0;\n border-start-end-radius: 0;\n border-end-end-radius: 0;\n }\n @media (max-width: 640px) {\n .rv-modal[data-position='center'],\n .rv-modal[data-position='top'],\n .rv-modal[data-position='bottom'],\n .rv-modal[data-position='bottom-start'],\n .rv-modal[data-position='bottom-end'] {\n margin-block-start: auto;\n margin-block-end: 0;\n margin-inline: 0;\n width: 100dvw;\n max-width: none;\n border-end-end-radius: 0;\n border-end-start-radius: 0;\n border-start-start-radius: var(--rv-modal-radius-sheet);\n border-start-end-radius: var(--rv-modal-radius-sheet);\n }\n .rv-modal[data-position='center'][data-state='open'],\n .rv-modal[data-position='top'][data-state='open'],\n .rv-modal[data-position='bottom'][data-state='open'],\n .rv-modal[data-position='bottom-start'][data-state='open'],\n .rv-modal[data-position='bottom-end'][data-state='open'] {\n animation-name: rv-modal-in-bottom;\n }\n }\n .rv-modal[data-animation]:not([data-animation='none'])[data-state='open'] {\n animation: var(--rv-modal-animation-in, rv-modal-in) var(--rv-modal-duration-in)\n var(--rv-modal-easing-in);\n }\n .rv-modal[data-animation='scale'][data-state='open'] {\n --rv-modal-animation-in: rv-modal-in-scale;\n }\n .rv-modal[data-animation='slide-top'][data-state='open'] {\n --rv-modal-animation-in: rv-modal-in-top;\n }\n .rv-modal[data-animation='slide-bottom'][data-state='open'] {\n --rv-modal-animation-in: rv-modal-in-bottom;\n }\n .rv-modal[data-animation='slide-start'][data-state='open'] {\n --rv-modal-animation-in: rv-modal-in-start;\n }\n .rv-modal[data-animation='slide-end'][data-state='open'] {\n --rv-modal-animation-in: rv-modal-in-end;\n }\n [dir='rtl'] .rv-modal[data-animation='slide-start'][data-state='open'] {\n --rv-modal-animation-in: rv-modal-in-end;\n }\n [dir='rtl'] .rv-modal[data-animation='slide-end'][data-state='open'] {\n --rv-modal-animation-in: rv-modal-in-start;\n }\n .rv-modal[data-state='closing'] {\n animation: rv-modal-out var(--rv-modal-duration-out) var(--rv-modal-easing-out) forwards;\n }\n @keyframes rv-modal-in {\n from {\n opacity: 0;\n }\n }\n @keyframes rv-modal-in-scale {\n from {\n opacity: 0;\n transform: translateY(8px) scale(0.98);\n }\n }\n @keyframes rv-modal-in-top {\n from {\n opacity: 0;\n transform: translateY(-24px);\n }\n }\n @keyframes rv-modal-in-bottom {\n from {\n opacity: 0;\n transform: translateY(24px);\n }\n }\n @keyframes rv-modal-in-start {\n from {\n opacity: 0;\n transform: translateX(-24px);\n }\n }\n @keyframes rv-modal-in-end {\n from {\n opacity: 0;\n transform: translateX(24px);\n }\n }\n @keyframes rv-modal-out {\n to {\n opacity: 0;\n transform: translateY(8px) scale(0.98);\n }\n }\n @media (prefers-reduced-motion: reduce) {\n .rv-modal[data-animation] {\n animation: none;\n }\n }\n .rv-modal-header {\n display: flex;\n align-items: flex-start;\n gap: var(--rv-modal-gap);\n padding: var(--rv-modal-pad) var(--rv-modal-pad) 0;\n }\n .rv-modal-header-custom > :first-child {\n flex: 1;\n min-width: 0;\n }\n .rv-modal-heading:empty {\n display: none;\n }\n .rv-modal-title {\n margin: 0;\n font-size: 1.125rem;\n flex: 1;\n }\n .rv-modal-heading {\n flex: 1;\n display: flex;\n flex-direction: column;\n gap: 4px;\n }\n .rv-modal-description {\n margin: 0;\n font-size: 0.875rem;\n color: var(--rv-modal-muted);\n }\n .rv-modal-close {\n margin-inline-start: auto;\n flex: none;\n border: 1px solid var(--rv-modal-border);\n border-radius: 8px;\n background: transparent;\n color: inherit;\n font-size: 1.25rem;\n line-height: 1;\n padding: 4px 10px;\n cursor: pointer;\n }\n .rv-modal-close:focus-visible {\n outline: 2px solid currentColor;\n outline-offset: 2px;\n }\n .rv-modal-body {\n padding: var(--rv-modal-pad);\n overflow: auto;\n max-height: inherit;\n }\n .rv-modal-footer {\n display: flex;\n gap: var(--rv-modal-gap);\n justify-content: flex-end;\n padding: 0 var(--rv-modal-pad) var(--rv-modal-pad);\n }\n .rv-modal-footer:empty {\n display: none;\n }\n `,\n ],\n})\nexport class RiviniaModalDialog {\n /** Two-way open state. UI-only mode owns this; the host drives it in managed mode. */\n readonly open = model<boolean>(false);\n\n readonly title = input<string>('');\n /**\n * Supporting text under the title (shadcn-style description). Rendered in\n * the default header only, and wired as the dialog's `aria-describedby`\n * for free accessible naming. Empty = no description element at all.\n */\n readonly description = input<string>('');\n /** Explicit accessible name. When set, `aria-label` wins over the title reference. */\n readonly ariaLabel = input<string>('');\n readonly size = input<RiviniaModalSize>('md');\n readonly position = input<RiviniaModalPosition>('center');\n readonly animation = input<RiviniaModalAnimation>('scale');\n readonly closeOnEscape = input<boolean>(true);\n readonly backdrop = input<RiviniaModalBackdrop>('close');\n readonly showCloseButton = input<boolean>(true);\n /**\n * Managed mode (for {@link RiviniaModalHost}): UI intents (X button,\n * Escape, backdrop) emit `dismissRequested` INSTEAD of dismissing, so the\n * manager runs guards first and settles results. Drive the exit with the\n * `closing` input; acknowledge teardown with `exitDone`.\n * UI-only mode (`false`, default) dismisses locally.\n */\n readonly managed = input<boolean>(false);\n /**\n * Managed-mode exit trigger. Set `true` when the manager reports this\n * instance `closing`: the dialog plays its exit animation, then emits\n * `exitDone` (call `confirmClosed()` there). Ignored in UI-only mode.\n */\n readonly closing = input<boolean>(false);\n\n /** Settled with a value (primary/confirm actions call `close(value)`). */\n readonly confirmed = output<unknown>();\n /** Settled without a value (X button, backdrop, Escape, `dismiss()`). */\n readonly dismissed = output<string | undefined>();\n /** Explicit cancellation (`cancel()`). Kept distinct from dismissal. */\n readonly cancelled = output<string | undefined>();\n /**\n * Managed-mode intent (X button / Escape / backdrop). Emitted INSTEAD of\n * dismissing — the host routes it to the manager first. Never emitted in\n * UI-only mode.\n */\n readonly dismissRequested = output<'button' | 'escape' | 'backdrop'>();\n /** Managed-mode exit completion. Acknowledge with `confirmClosed()`. */\n readonly exitDone = output<void>();\n\n private readonly dialog = viewChild<ElementRef<HTMLDialogElement>>('dialog');\n private readonly headerSlot = contentChild(RvModalHeader);\n private readonly footerSlot = contentChild(RvModalFooter);\n private readonly doc = inject(DOCUMENT, { optional: true });\n private readonly scrollLock = inject(RiviniaModalScrollLock, { optional: true });\n private readonly titleId = `rv-modal-${++dialogSeq}`;\n private readonly descriptionId = `rv-modal-desc-${dialogSeq}`;\n private readonly phase = signal<'closed' | 'open' | 'closing'>('closed');\n private readonly invoker: { current: Element | null } = { current: null };\n private exitTimer: ReturnType<typeof setTimeout> | null = null;\n\n /** Template state for animation styling. Never rendered open on the server. */\n readonly state = computed(() => this.phase());\n readonly hasCustomHeader = computed(() => this.headerSlot() !== undefined);\n readonly hasCustomFooter = computed(() => this.footerSlot() !== undefined);\n readonly computedLabel = computed(() => this.ariaLabel() || null);\n\n constructor() {\n effect(() => {\n if (this.open()) this.show();\n // External close acts only while fully open: during 'closing' the\n // exit animation owns teardown (its timer finalizes). This guard is\n // what keeps `finish()` from cutting the animation via `open.set(false)`.\n else if (this.phase() === 'open') this.hide();\n });\n effect(() => {\n // Managed exit: the manager moved this instance to `closing`.\n if (this.managed() && this.closing() && this.phase() === 'open') {\n this.playExit(() => this.exitDone.emit());\n }\n });\n }\n\n /** Confirm with a value. No-op while closing/closed. */\n confirm(value?: unknown): boolean {\n if (this.phase() !== 'open') return false;\n this.finish('confirmed', value);\n return true;\n }\n\n /** Backwards-compatible close: confirms when given a value, dismisses otherwise. */\n close(value?: unknown): boolean {\n return value === undefined ? this.dismiss() : this.confirm(value);\n }\n\n /** Dismiss without a value (X button, backdrop, Escape route here). */\n dismiss(reason?: string): boolean {\n if (this.phase() !== 'open') return false;\n this.finish('dismissed', undefined, reason);\n return true;\n }\n\n /** Explicit cancellation. */\n cancel(reason?: string): boolean {\n if (this.phase() !== 'open') return false;\n this.finish('cancelled', undefined, reason);\n return true;\n }\n\n protected onCloseButton(): void {\n if (this.managed()) {\n if (this.phase() === 'open') this.dismissRequested.emit('button');\n return;\n }\n this.dismiss('close-button');\n }\n\n protected onCancel(event: Event): void {\n if (!this.closeOnEscape()) {\n event.preventDefault();\n return;\n }\n // Native `cancel` already closed nothing yet — route through our pipeline\n // so scroll-lock, focus restore and outputs stay consistent.\n event.preventDefault();\n if (this.managed()) {\n if (this.phase() === 'open') this.dismissRequested.emit('escape');\n return;\n }\n this.dismiss('escape');\n }\n\n protected onBackdropClick(event: MouseEvent): void {\n const el = this.dialog()?.nativeElement;\n if (!el || event.target !== el) return; // inside clicks never count\n if (this.backdrop() !== 'close') return;\n if (this.managed()) {\n if (this.phase() === 'open') this.dismissRequested.emit('backdrop');\n return;\n }\n this.dismiss('backdrop');\n }\n\n protected onKeydown(event: KeyboardEvent): void {\n if (event.key !== 'Tab') return;\n const el = this.dialog()?.nativeElement;\n if (!el) return;\n const items = focusables(el);\n if (items.length === 0) {\n event.preventDefault();\n return;\n }\n const first = items[0];\n const last = items[items.length - 1];\n const active = (this.doc ?? document).activeElement;\n if (event.shiftKey && (active === first || !el.contains(active))) {\n event.preventDefault();\n last.focus();\n } else if (!event.shiftKey && active === last) {\n event.preventDefault();\n first.focus();\n }\n }\n\n private get live(): boolean {\n return typeof window !== 'undefined' && !!this.doc;\n }\n\n private show(): void {\n if (!this.live) return; // SSR: stay closed until client hydration opens us\n const el = this.dialog()?.nativeElement;\n if (!el || this.phase() !== 'closed') return;\n try {\n this.invoker.current = this.doc?.activeElement ?? null;\n } catch {\n this.invoker.current = null;\n }\n this.scrollLock?.acquire();\n this.phase.set('open');\n if (!el.open) {\n try {\n el.showModal();\n } catch {\n // Already open (e.g. double effect run): continue — state is ours.\n }\n }\n focusFirst(el, this.showCloseButton());\n }\n\n private hide(): void {\n if (this.phase() === 'closed') return;\n this.teardown(false);\n }\n\n private finish(\n kind: 'confirmed' | 'dismissed' | 'cancelled',\n value?: unknown,\n reason?: string,\n ): void {\n this.teardown(true);\n if (kind === 'confirmed') this.confirmed.emit(value);\n else if (kind === 'dismissed') this.dismissed.emit(reason);\n else this.cancelled.emit(reason);\n if (this.open()) this.open.set(false);\n }\n\n private teardown(animate: boolean): void {\n if (!animate) {\n if (this.exitTimer) {\n clearTimeout(this.exitTimer);\n this.exitTimer = null;\n }\n this.finalize(this.dialog()?.nativeElement ?? null);\n return;\n }\n this.playExit(() => {});\n }\n\n /** Plays the exit animation (when warranted), then runs `done`. Idempotent. */\n private playExit(done: () => void): void {\n if (this.phase() !== 'open') {\n done();\n return;\n }\n if (this.exitTimer) {\n clearTimeout(this.exitTimer);\n this.exitTimer = null;\n }\n const el = this.dialog()?.nativeElement ?? null;\n const animated = this.animation() !== 'none' && this.prefersMotion() && !!el?.open;\n this.phase.set(animated ? 'closing' : 'closed');\n if (!animated) {\n this.finalize(el);\n done();\n return;\n }\n // Honor consumer-overridden durations instead of a fixed fallback, so a\n // longer `--rv-modal-duration-out` is never cut short.\n this.exitTimer = setTimeout(() => {\n this.finalize(el);\n done();\n }, this.exitDurationMs(el));\n }\n\n private exitDurationMs(el: HTMLDialogElement | null): number {\n try {\n const raw = el ? getComputedStyle(el).animationDuration : '';\n const seconds = raw\n .split(',')\n .map((part) => parseFloat(part))\n .filter((n) => Number.isFinite(n));\n if (seconds.length > 0) return Math.max(0, Math.max(...seconds) * 1000) + 50;\n } catch {\n // Non-CSS environments (tests): fall through to the fallback.\n }\n return EXIT_FALLBACK_MS;\n }\n\n private finalize(el: HTMLDialogElement | null): void {\n this.exitTimer = null;\n try {\n el?.close();\n } catch {\n // Already closed natively — ours to reconcile, not to throw.\n }\n this.phase.set('closed');\n this.scrollLock?.release();\n const invoker = this.invoker.current as HTMLElement | null;\n this.invoker.current = null;\n try {\n invoker?.focus?.();\n } catch {\n // Invoker gone (navigated away) — nothing to restore to.\n }\n }\n\n private prefersMotion(): boolean {\n try {\n return (\n typeof window !== 'undefined' &&\n !window.matchMedia('(prefers-reduced-motion: reduce)').matches\n );\n } catch {\n return true;\n }\n }\n}\n\nlet dialogSeq = 0;\n\nfunction focusables(root: HTMLElement): HTMLElement[] {\n // No visibility filtering on purpose: jsdom reports zero rects (unit\n // tests), and `disabled` is already excluded by the selector. Hidden\n // focusables inside a visible dialog are a consumer edge, not ours.\n const selector =\n 'a[href], button:not([disabled]), input:not([disabled]), select:not([disabled]), ' +\n 'textarea:not([disabled]), [tabindex]:not([tabindex=\"-1\"])';\n return Array.from(root.querySelectorAll<HTMLElement>(selector));\n}\n\nfunction focusFirst(dialog: HTMLDialogElement, closeVisible: boolean): void {\n try {\n const auto = dialog.querySelector<HTMLElement>('[autofocus]');\n if (auto) {\n auto.focus({ preventScroll: true });\n return;\n }\n // Atlassian rule: no close button → focus the title; otherwise the\n // dialog itself (its tabindex=\"-1\" makes it focusable).\n if (!closeVisible) {\n const title = dialog.querySelector<HTMLElement>('.rv-modal-title');\n if (title) {\n title.setAttribute('tabindex', '-1');\n title.focus({ preventScroll: true });\n return;\n }\n }\n dialog.focus({ preventScroll: true });\n } catch {\n // Focus is best-effort (e.g. during teardown races).\n }\n}\n","import { Component, Injector, computed, input } from '@angular/core';\nimport type { Signal } from '@angular/core';\nimport { NgComponentOutlet, NgTemplateOutlet } from '@angular/common';\nimport { RIVINIA_MODAL_CONTEXT } from '@rivinia/modal';\nimport type {\n ModalContext,\n ModalDismissOrigin,\n ModalInstance,\n ModalTemplateContext,\n} from '@rivinia/modal';\nimport { RiviniaModalDialog } from './modal-dialog';\nimport type { RiviniaModalSize } from './ui-types';\n\n/**\n * Managed adapter: renders every `visible()` instance of the headless\n * `RiviniaModal` inside a {@link RiviniaModalDialog} and routes all UI\n * intents through the manager first (guards run; results settle in Core —\n * the single source of truth). The dialog never settles anything itself in\n * this mode; listen to `ModalHandle.result`, not dialog outputs.\n *\n * Exit coordination: when Core reports an instance `closing`, the dialog\n * plays its exit animation and the host acknowledges with `confirmClosed()`\n * (idempotent — safe if `closingTimeout` auto-finalized first). Match\n * `closingTimeout` to the animation duration so exits stay visible.\n *\n * Title resolution: `titleOf` per instance (default: `metadata.title` →\n * `ref` → `''`). Content descriptors render as-is: templates via\n * `ngTemplateOutlet` with `{$implicit: data, modal}`, components via\n * `NgComponentOutlet` with `inputs` + a per-instance\n * `RIVINIA_MODAL_CONTEXT` injector (the lab-proven pattern).\n *\n * The manager arrives via `[modal]` input (D06 input-composition: the UI\n * entry never instantiates Core itself, so UI-only consumers pay nothing).\n * The input is typed as {@link RiviniaModalHostManager} — the structural\n * slice the host actually drives — so the full manager (or a test double)\n * satisfies it from any module realm:\n *\n * ```html\n * <rv-modal-host [modal]=\"injectRiviniaModal()\" />\n * ```\n */\nexport interface RiviniaModalHostManager {\n readonly visible: Signal<readonly ModalInstance<unknown>[]>;\n requestDismiss(id: string, origin: ModalDismissOrigin): Promise<boolean>;\n confirmClosed(id: string): boolean;\n close(id: string, value?: unknown): boolean;\n dismiss(id: string, reason?: string): boolean;\n}\n\n@Component({\n selector: 'rv-modal-host',\n standalone: true,\n imports: [RiviniaModalDialog, NgComponentOutlet, NgTemplateOutlet],\n template: `\n @for (instance of visible(); track instance.id) {\n <rv-modal-dialog\n [open]=\"true\"\n [managed]=\"true\"\n [closing]=\"instance.status === 'closing'\"\n [title]=\"titleOfInstance(instance)\"\n [size]=\"size()\"\n [showCloseButton]=\"showCloseButton()\"\n [closeOnEscape]=\"instance.dismiss.closeOnEscape ?? true\"\n [backdrop]=\"instance.dismiss.backdrop ?? 'close'\"\n (dismissRequested)=\"onIntent(instance.id, $event)\"\n (exitDone)=\"onExitDone(instance.id)\"\n >\n @if (instance.content.kind === 'template') {\n <ng-container\n *ngTemplateOutlet=\"instance.content.template; context: templateContext(instance)\"\n ></ng-container>\n } @else {\n <ng-container\n *ngComponentOutlet=\"\n instance.content.component;\n inputs: instance.content.inputs;\n injector: contextInjector(instance)\n \"\n ></ng-container>\n }\n </rv-modal-dialog>\n }\n `,\n})\nexport class RiviniaModalHost {\n /**\n * The headless manager to render. Typed as {@link RiviniaModalHostManager}\n * (structural) — pass `injectRiviniaModal()` from your app code.\n */\n readonly modal = input.required<RiviniaModalHostManager>();\n\n /** Dialog size for every managed instance. @default 'md' */\n readonly size = input<RiviniaModalSize>('md');\n /** Render the header close button on every managed instance. @default true */\n readonly showCloseButton = input<boolean>(true);\n /** Resolve a title per instance. Default: `metadata.title` → `ref` → `''`. */\n readonly titleOf = input<(instance: ModalInstance<unknown>) => string | undefined>();\n\n protected readonly visible = computed(() => this.modal().visible());\n\n protected titleOfInstance(instance: ModalInstance<unknown>): string {\n try {\n const custom = this.titleOf()?.(instance);\n if (custom !== undefined) return custom;\n } catch {\n // A throwing resolver must never break rendering — fall through.\n }\n const meta = instance.metadata?.['title'];\n if (typeof meta === 'string' && meta.length > 0) return meta;\n return instance.ref ?? '';\n }\n\n protected templateContext(instance: ModalInstance<unknown>): ModalTemplateContext<unknown> {\n return { $implicit: instance.data, modal: this.modalContext(instance) };\n }\n\n protected contextInjector(instance: ModalInstance<unknown>): Injector {\n const modal = this.modal();\n return Injector.create({\n providers: [\n {\n provide: RIVINIA_MODAL_CONTEXT,\n useValue: {\n id: instance.id,\n data: instance.data,\n parentId: instance.parentId,\n close: (result?: unknown) => modal.close(instance.id, result),\n dismiss: (reason?: string) => modal.dismiss(instance.id, reason),\n } satisfies ModalContext<unknown>,\n },\n ],\n });\n }\n\n protected async onIntent(id: string, origin: 'button' | 'escape' | 'backdrop'): Promise<void> {\n try {\n await this.modal().requestDismiss(id, origin);\n } catch {\n // requestDismiss never rejects (deny-safe by contract) — defensive only.\n }\n // Granted → status flips to `closing` → dialog exit → `exitDone`.\n // Denied → nothing moves; the dialog never left `open`. No manual toggle.\n }\n\n protected onExitDone(id: string): void {\n this.modal().confirmClosed(id);\n }\n\n private modalContext(instance: ModalInstance<unknown>): ModalContext<unknown> {\n const modal = this.modal();\n const id = instance.id;\n return {\n id,\n data: instance.data,\n parentId: instance.parentId,\n close: (result?: unknown) => modal.close(id, result),\n dismiss: (reason?: string) => modal.dismiss(id, reason),\n };\n }\n}\n"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rivinia/modal",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.1",
|
|
4
4
|
"description": "Headless modal manager for Angular: stack/queue strategies, typed results, guards, ownership.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"angular",
|
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
],
|
|
12
12
|
"license": "MIT",
|
|
13
13
|
"peerDependencies": {
|
|
14
|
+
"@angular/common": "^22.0.0",
|
|
14
15
|
"@angular/core": "^22.0.0"
|
|
15
16
|
},
|
|
16
17
|
"sideEffects": false,
|