@rivinia/modal 0.5.3 → 0.6.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 +130 -6
- package/fesm2022/rivinia-modal-ui.mjs +156 -9
- package/fesm2022/rivinia-modal-ui.mjs.map +1 -1
- package/fesm2022/rivinia-modal.mjs +6 -2
- package/fesm2022/rivinia-modal.mjs.map +1 -1
- package/package.json +2 -1
- package/schematics/ng-add/index.cjs +31 -1
- package/types/rivinia-modal-ui.d.ts +112 -8
- package/types/rivinia-modal.d.ts +48 -2
package/README.md
CHANGED
|
@@ -5,7 +5,7 @@ policies, async guards, and optional ready-made UI. The core package owns
|
|
|
5
5
|
modal state and lifecycle but does not render or style dialogs.
|
|
6
6
|
|
|
7
7
|
- Install: `npm install @rivinia/modal`
|
|
8
|
-
- Package version: **0.
|
|
8
|
+
- Package version: **0.6.0**
|
|
9
9
|
- Peer dependencies: `@angular/core ^22.0.0` and `@angular/common ^22.0.0`
|
|
10
10
|
- `@rivinia/modal` is the headless manager.
|
|
11
11
|
- `@rivinia/modal/ui` contains standalone dialog and host components.
|
|
@@ -58,7 +58,6 @@ interface DeleteData {
|
|
|
58
58
|
}
|
|
59
59
|
|
|
60
60
|
@Component({
|
|
61
|
-
standalone: true,
|
|
62
61
|
imports: [RiviniaModalHost],
|
|
63
62
|
templateUrl: './users-page.component.html',
|
|
64
63
|
})
|
|
@@ -142,7 +141,6 @@ interface DeleteData {
|
|
|
142
141
|
}
|
|
143
142
|
|
|
144
143
|
@Component({
|
|
145
|
-
standalone: true,
|
|
146
144
|
imports: [RiviniaModalHost],
|
|
147
145
|
templateUrl: './users-page.component.html',
|
|
148
146
|
})
|
|
@@ -212,7 +210,6 @@ import { Component, inject } from '@angular/core';
|
|
|
212
210
|
import { RIVINIA_MODAL_CONTEXT } from '@rivinia/modal';
|
|
213
211
|
|
|
214
212
|
@Component({
|
|
215
|
-
standalone: true,
|
|
216
213
|
templateUrl: './delete-confirmation.component.html',
|
|
217
214
|
})
|
|
218
215
|
export class DeleteConfirmationComponent {
|
|
@@ -276,7 +273,6 @@ import { RiviniaToast } from '@rivinia/toast';
|
|
|
276
273
|
import { RiviniaToastList } from '@rivinia/toast/ui';
|
|
277
274
|
|
|
278
275
|
@Component({
|
|
279
|
-
standalone: true,
|
|
280
276
|
imports: [RiviniaModalHost, RiviniaToastList],
|
|
281
277
|
templateUrl: './payment-page.component.html',
|
|
282
278
|
})
|
|
@@ -399,7 +395,6 @@ import { Component } from '@angular/core';
|
|
|
399
395
|
import { RiviniaModalDialog } from '@rivinia/modal/ui';
|
|
400
396
|
|
|
401
397
|
@Component({
|
|
402
|
-
standalone: true,
|
|
403
398
|
imports: [RiviniaModalDialog],
|
|
404
399
|
templateUrl: './preview-dialog.component.html',
|
|
405
400
|
})
|
|
@@ -440,6 +435,135 @@ closed and opens on the client after hydration. Theme it with CSS custom
|
|
|
440
435
|
properties such as `--rv-modal-bg`, `--rv-modal-fg`, `--rv-modal-border`,
|
|
441
436
|
`--rv-modal-radius`, and `--rv-modal-width-md`.
|
|
442
437
|
|
|
438
|
+
## Theming
|
|
439
|
+
|
|
440
|
+
Every component reads **its own token → the shared semantic layer → its
|
|
441
|
+
built-in default**:
|
|
442
|
+
|
|
443
|
+
```css
|
|
444
|
+
--rv-modal-bg → --rv-bg → #ffffff
|
|
445
|
+
```
|
|
446
|
+
|
|
447
|
+
| Level | Token | Scope |
|
|
448
|
+
| -------------- | --------------- | ----------------------------------- |
|
|
449
|
+
| 1. per package | `--rv-modal-bg` | that dialog only |
|
|
450
|
+
| 2. global | `--rv-bg` | every Rivinia component, everywhere |
|
|
451
|
+
| 3. default | — | what you get if you set nothing |
|
|
452
|
+
|
|
453
|
+
```css
|
|
454
|
+
@import '@rivinia/theme';
|
|
455
|
+
|
|
456
|
+
:root {
|
|
457
|
+
--rv-bg: #ffffff;
|
|
458
|
+
--rv-fg: #111111;
|
|
459
|
+
--rv-border: #e0e0e0;
|
|
460
|
+
}
|
|
461
|
+
```
|
|
462
|
+
|
|
463
|
+
Its defaults sit in `@layer rivinia-theme`, so your unlayered `:root` rules win
|
|
464
|
+
wherever they appear. **Map it on `:root`, never on a component element** — an
|
|
465
|
+
element-level declaration shadows the root value and the override silently
|
|
466
|
+
stops working.
|
|
467
|
+
|
|
468
|
+
### The library's own tokens
|
|
469
|
+
|
|
470
|
+
| Token | Default | Applies to |
|
|
471
|
+
| --------------------- | ----------------------------- | -------------- |
|
|
472
|
+
| `--rv-modal-bg` | `#ffffff` | the dialog |
|
|
473
|
+
| `--rv-modal-fg` | `#111111` | the dialog |
|
|
474
|
+
| `--rv-modal-muted` | `#555555` | secondary text |
|
|
475
|
+
| `--rv-modal-border` | `#e0e0e0` | the dialog |
|
|
476
|
+
| `--rv-modal-radius` | `12px` | the dialog |
|
|
477
|
+
| `--rv-modal-shadow` | `0 24px 64px rgba(0,0,0,.28)` | the dialog |
|
|
478
|
+
| `--rv-modal-backdrop` | `rgba(0,0,0,.5)` | the scrim |
|
|
479
|
+
| `--rv-modal-pad` | `20px` | the dialog |
|
|
480
|
+
| `--rv-modal-width-md` | `32rem` | the dialog |
|
|
481
|
+
|
|
482
|
+
**No forced dark mode** — there is no `prefers-color-scheme` block in this
|
|
483
|
+
package, so a dialog never overrides your explicit choice.
|
|
484
|
+
|
|
485
|
+
### Which styling stack?
|
|
486
|
+
|
|
487
|
+
| Stack | Import |
|
|
488
|
+
| --------------- | -------------------------------------------- |
|
|
489
|
+
| **Plain CSS** | `@import '@rivinia/theme';` |
|
|
490
|
+
| **Tailwind v4** | `@import '@rivinia/theme/tailwind.css';` |
|
|
491
|
+
| **Bootstrap** | `@import '@rivinia/theme/bootstrap.css';` |
|
|
492
|
+
| **Sass / SCSS** | `@use '@rivinia/theme/scss';` |
|
|
493
|
+
| **Less** | `@import (less) '@rivinia/theme/theme.css';` |
|
|
494
|
+
|
|
495
|
+
## Sizing, stacking and who opened it
|
|
496
|
+
|
|
497
|
+
**A size per dialog.** The host's `size` is the default for every instance; a
|
|
498
|
+
dialog may override it when it is opened:
|
|
499
|
+
|
|
500
|
+
```ts
|
|
501
|
+
this.modal.openModal(this.confirmTpl, { ref: 'confirm', size: 'sm' });
|
|
502
|
+
```
|
|
503
|
+
|
|
504
|
+
```html
|
|
505
|
+
<rv-modal-host [modal]="modal" size="lg" />
|
|
506
|
+
```
|
|
507
|
+
|
|
508
|
+
The presets are **responsive** — each is a `clamp()`, so it scales with the
|
|
509
|
+
viewport between a floor and a ceiling instead of being a fixed width that
|
|
510
|
+
either overflows or gets capped:
|
|
511
|
+
|
|
512
|
+
| Preset | Width |
|
|
513
|
+
| ------ | --------------------------- |
|
|
514
|
+
| `sm` | `clamp(16rem, 40vw, 24rem)` |
|
|
515
|
+
| `md` | `clamp(20rem, 55vw, 32rem)` |
|
|
516
|
+
| `lg` | `clamp(24rem, 70vw, 48rem)` |
|
|
517
|
+
| `xl` | `clamp(28rem, 85vw, 64rem)` |
|
|
518
|
+
| `full` | `calc(100dvw - 2rem)` |
|
|
519
|
+
| `auto` | sized to its content |
|
|
520
|
+
|
|
521
|
+
Every one is a plain custom property, so the consumer overrides the preset in
|
|
522
|
+
CSS without touching the library:
|
|
523
|
+
|
|
524
|
+
```css
|
|
525
|
+
my-scope {
|
|
526
|
+
--rv-modal-width-lg: clamp(30rem, 80vw, 60rem);
|
|
527
|
+
}
|
|
528
|
+
```
|
|
529
|
+
|
|
530
|
+
**One dialog can cover the ones below it.** A stack shows every dialog, so the
|
|
531
|
+
ones below stay visible behind the top one — right for a wizard, wrong for a
|
|
532
|
+
flow where the detail view REPLACES the list view:
|
|
533
|
+
|
|
534
|
+
```ts
|
|
535
|
+
this.modal.openModal(this.detailTpl, { ref: 'detail', coversOthers: true });
|
|
536
|
+
```
|
|
537
|
+
|
|
538
|
+
The dialogs below are not rendered while the cover is up, and they come back
|
|
539
|
+
when it closes. They stay in the manager the whole time.
|
|
540
|
+
|
|
541
|
+
**The opener is attributable.** `openModal` is normally called from an event
|
|
542
|
+
handler, which is not an injection context, so the manager cannot discover the
|
|
543
|
+
caller. Naming it puts `data-caller` on the dialog, which the DevTools
|
|
544
|
+
inspector shows — so a dialog that appears unexpectedly points at its source:
|
|
545
|
+
|
|
546
|
+
```ts
|
|
547
|
+
export class UsersScreen {
|
|
548
|
+
open() {
|
|
549
|
+
this.modal.openModal(this.detailTpl, { caller: UsersScreen });
|
|
550
|
+
}
|
|
551
|
+
}
|
|
552
|
+
```
|
|
553
|
+
|
|
554
|
+
```html
|
|
555
|
+
<dialog class="rv-modal" data-caller="UsersScreen">…</dialog>
|
|
556
|
+
```
|
|
557
|
+
|
|
558
|
+
**Smoothness.** The dialog transitions its own box — width, height and the
|
|
559
|
+
offsets — so a size change or a dialog opening on top of another is a
|
|
560
|
+
movement rather than a jump. The stack change is additionally wrapped in a
|
|
561
|
+
**view transition** where the browser supports it, so the lower dialog is
|
|
562
|
+
painted continuously instead of flickering. During a viewport resize the
|
|
563
|
+
transition is switched off (`data-resizing="true"`): the dialog's size is
|
|
564
|
+
derived from the viewport, so animating towards a moving target lags behind
|
|
565
|
+
the pointer.
|
|
566
|
+
|
|
443
567
|
## Public core exports
|
|
444
568
|
|
|
445
569
|
The core entry point exports:
|
|
@@ -135,6 +135,32 @@ var RiviniaModalDialog = class RiviniaModalDialog {
|
|
|
135
135
|
animation = input("scale", ...ngDevMode ? [{ debugName: "animation" }] : /* istanbul ignore next */ []);
|
|
136
136
|
closeOnEscape = input(true, ...ngDevMode ? [{ debugName: "closeOnEscape" }] : /* istanbul ignore next */ []);
|
|
137
137
|
backdrop = input("close", ...ngDevMode ? [{ debugName: "backdrop" }] : /* istanbul ignore next */ []);
|
|
138
|
+
resizing = signal(false, ...ngDevMode ? [{ debugName: "resizing" }] : /* istanbul ignore next */ []);
|
|
139
|
+
viewportQuery = signal(null, ...ngDevMode ? [{ debugName: "viewportQuery" }] : /* istanbul ignore next */ []);
|
|
140
|
+
stopResizeWatch = (() => {
|
|
141
|
+
if (typeof window === "undefined") return () => void 0;
|
|
142
|
+
let query = null;
|
|
143
|
+
const onChange = () => {
|
|
144
|
+
this.resizing.set(true);
|
|
145
|
+
if (this.settleTimer !== null) clearTimeout(this.settleTimer);
|
|
146
|
+
this.settleTimer = setTimeout(() => this.resizing.set(false), 120);
|
|
147
|
+
};
|
|
148
|
+
const attach = () => {
|
|
149
|
+
query?.removeEventListener("change", onChange);
|
|
150
|
+
if (typeof window.matchMedia !== "function") return;
|
|
151
|
+
const ratio = window.devicePixelRatio ?? 1;
|
|
152
|
+
query = window.matchMedia(`(resolution: ${ratio}dppx)`);
|
|
153
|
+
query.addEventListener("change", onChange, { once: true });
|
|
154
|
+
this.viewportQuery.set(query);
|
|
155
|
+
};
|
|
156
|
+
attach();
|
|
157
|
+
return () => query?.removeEventListener("change", onChange);
|
|
158
|
+
})();
|
|
159
|
+
settleTimer = null;
|
|
160
|
+
ngOnDestroy() {
|
|
161
|
+
this.stopResizeWatch();
|
|
162
|
+
if (this.settleTimer !== null) clearTimeout(this.settleTimer);
|
|
163
|
+
}
|
|
138
164
|
showCloseButton = input(true, ...ngDevMode ? [{ debugName: "showCloseButton" }] : /* istanbul ignore next */ []);
|
|
139
165
|
managed = input(false, ...ngDevMode ? [{ debugName: "managed" }] : /* istanbul ignore next */ []);
|
|
140
166
|
closing = input(false, ...ngDevMode ? [{ debugName: "closing" }] : /* istanbul ignore next */ []);
|
|
@@ -461,6 +487,7 @@ var RiviniaModalDialog = class RiviniaModalDialog {
|
|
|
461
487
|
[attr.data-position]="position()"
|
|
462
488
|
[attr.data-animation]="animation()"
|
|
463
489
|
[attr.data-state]="state()"
|
|
490
|
+
[attr.data-resizing]="resizing()"
|
|
464
491
|
[attr.aria-label]="computedLabel()"
|
|
465
492
|
[attr.aria-labelledby]="title() && !ariaLabel() ? titleId : null"
|
|
466
493
|
[attr.aria-describedby]="description() ? descriptionId : null"
|
|
@@ -505,7 +532,7 @@ var RiviniaModalDialog = class RiviniaModalDialog {
|
|
|
505
532
|
</dialog>
|
|
506
533
|
`,
|
|
507
534
|
isInline: true,
|
|
508
|
-
styles: [".rv-modal{color-scheme:light dark;--rv-modal-
|
|
535
|
+
styles: [".rv-modal{color-scheme:light dark;--rv-modal-pad: 20px;--rv-modal-gap: 12px;--rv-modal-width-sm: clamp(16rem, 40vw, 24rem);--rv-modal-width-md: clamp(20rem, 55vw, 32rem);--rv-modal-width-lg: clamp(24rem, 70vw, 48rem);--rv-modal-width-xl: clamp(28rem, 85vw, 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: 1.25rem;--rv-modal-width-panel: 24rem;padding:0;border:1px solid var(--rv-modal-border, var(--rv-border, #e0e0e0));border-radius:var(--rv-modal-radius, var(--rv-radius, 12px));background:var(--rv-modal-bg, var(--rv-bg, #ffffff));color:var(--rv-modal-fg, var(--rv-fg, #111111));box-shadow:var(--rv-modal-shadow, var(--rv-shadow, 0 24px 64px rgba(0, 0, 0, .28)));max-height:var(--rv-modal-max-height);width:var(--rv-modal-width-md);max-width:calc(100dvw - 2rem);transition:width var(--rv-modal-size-duration, .18s) var(--rv-modal-easing, ease-out),height var(--rv-modal-size-duration, .18s) var(--rv-modal-easing, ease-out),max-height var(--rv-modal-size-duration, .18s) var(--rv-modal-easing, ease-out),inset var(--rv-modal-size-duration, .18s) var(--rv-modal-easing, ease-out),margin var(--rv-modal-size-duration, .18s) var(--rv-modal-easing, ease-out)}.rv-modal[data-resizing=true]{transition:none!important}.rv-modal-loading{display:flex;align-items:center;justify-content:center;gap:var(--rv-modal-loading-gap, .625rem);padding:var(--rv-modal-loading-padding, 2rem);min-height:var(--rv-modal-loading-min-height, 6rem);color:var(--rv-modal-muted, var(--rv-muted, #555555));font-size:var(--rv-modal-loading-size, .875rem)}.rv-modal-spinner{inline-size:var(--rv-modal-spinner-size, 1.125rem);block-size:var(--rv-modal-spinner-size, 1.125rem);border:2px solid currentColor;border-block-start-color:transparent;border-radius:50%;animation:rv-modal-spin var(--rv-modal-spinner-duration, .7s) linear infinite}@keyframes rv-modal-spin{to{transform:rotate(360deg)}}@media(prefers-reduced-motion:reduce){.rv-modal-spinner{animation:none}}.rv-modal::backdrop{background:var(--rv-modal-backdrop, rgba(0, 0, 0, .5))}.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:.25rem}.rv-modal-description{margin:0;font-size:.875rem;color:var(--rv-modal-muted, var(--rv-muted, #555555))}.rv-modal-close{margin-inline-start:auto;flex:none;border:1px solid var(--rv-modal-border, var(--rv-border, #e0e0e0));border-radius:.5rem;background:transparent;color:inherit;font-size:1.25rem;line-height:1;padding:.25rem .625rem;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
536
|
encapsulation: i0.ViewEncapsulation.None
|
|
510
537
|
});
|
|
511
538
|
};
|
|
@@ -518,7 +545,6 @@ i0.ɵɵngDeclareClassMetadata({
|
|
|
518
545
|
type: Component,
|
|
519
546
|
args: [{
|
|
520
547
|
selector: "rv-modal-dialog",
|
|
521
|
-
standalone: true,
|
|
522
548
|
encapsulation: ViewEncapsulation.None,
|
|
523
549
|
template: `
|
|
524
550
|
<dialog
|
|
@@ -529,6 +555,7 @@ i0.ɵɵngDeclareClassMetadata({
|
|
|
529
555
|
[attr.data-position]="position()"
|
|
530
556
|
[attr.data-animation]="animation()"
|
|
531
557
|
[attr.data-state]="state()"
|
|
558
|
+
[attr.data-resizing]="resizing()"
|
|
532
559
|
[attr.aria-label]="computedLabel()"
|
|
533
560
|
[attr.aria-labelledby]="title() && !ariaLabel() ? titleId : null"
|
|
534
561
|
[attr.aria-describedby]="description() ? descriptionId : null"
|
|
@@ -572,7 +599,7 @@ i0.ɵɵngDeclareClassMetadata({
|
|
|
572
599
|
}
|
|
573
600
|
</dialog>
|
|
574
601
|
`,
|
|
575
|
-
styles: [".rv-modal{color-scheme:light dark;--rv-modal-
|
|
602
|
+
styles: [".rv-modal{color-scheme:light dark;--rv-modal-pad: 20px;--rv-modal-gap: 12px;--rv-modal-width-sm: clamp(16rem, 40vw, 24rem);--rv-modal-width-md: clamp(20rem, 55vw, 32rem);--rv-modal-width-lg: clamp(24rem, 70vw, 48rem);--rv-modal-width-xl: clamp(28rem, 85vw, 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: 1.25rem;--rv-modal-width-panel: 24rem;padding:0;border:1px solid var(--rv-modal-border, var(--rv-border, #e0e0e0));border-radius:var(--rv-modal-radius, var(--rv-radius, 12px));background:var(--rv-modal-bg, var(--rv-bg, #ffffff));color:var(--rv-modal-fg, var(--rv-fg, #111111));box-shadow:var(--rv-modal-shadow, var(--rv-shadow, 0 24px 64px rgba(0, 0, 0, .28)));max-height:var(--rv-modal-max-height);width:var(--rv-modal-width-md);max-width:calc(100dvw - 2rem);transition:width var(--rv-modal-size-duration, .18s) var(--rv-modal-easing, ease-out),height var(--rv-modal-size-duration, .18s) var(--rv-modal-easing, ease-out),max-height var(--rv-modal-size-duration, .18s) var(--rv-modal-easing, ease-out),inset var(--rv-modal-size-duration, .18s) var(--rv-modal-easing, ease-out),margin var(--rv-modal-size-duration, .18s) var(--rv-modal-easing, ease-out)}.rv-modal[data-resizing=true]{transition:none!important}.rv-modal-loading{display:flex;align-items:center;justify-content:center;gap:var(--rv-modal-loading-gap, .625rem);padding:var(--rv-modal-loading-padding, 2rem);min-height:var(--rv-modal-loading-min-height, 6rem);color:var(--rv-modal-muted, var(--rv-muted, #555555));font-size:var(--rv-modal-loading-size, .875rem)}.rv-modal-spinner{inline-size:var(--rv-modal-spinner-size, 1.125rem);block-size:var(--rv-modal-spinner-size, 1.125rem);border:2px solid currentColor;border-block-start-color:transparent;border-radius:50%;animation:rv-modal-spin var(--rv-modal-spinner-duration, .7s) linear infinite}@keyframes rv-modal-spin{to{transform:rotate(360deg)}}@media(prefers-reduced-motion:reduce){.rv-modal-spinner{animation:none}}.rv-modal::backdrop{background:var(--rv-modal-backdrop, rgba(0, 0, 0, .5))}.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:.25rem}.rv-modal-description{margin:0;font-size:.875rem;color:var(--rv-modal-muted, var(--rv-muted, #555555))}.rv-modal-close{margin-inline-start:auto;flex:none;border:1px solid var(--rv-modal-border, var(--rv-border, #e0e0e0));border-radius:.5rem;background:transparent;color:inherit;font-size:1.25rem;line-height:1;padding:.25rem .625rem;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"]
|
|
576
603
|
}]
|
|
577
604
|
}],
|
|
578
605
|
ctorParameters: () => [],
|
|
@@ -732,12 +759,67 @@ function focusFirst(dialog, closeVisible) {
|
|
|
732
759
|
dialog.focus({ preventScroll: true });
|
|
733
760
|
} catch {}
|
|
734
761
|
}
|
|
762
|
+
const MODAL_COVERS = "coversOthers";
|
|
763
|
+
function covers(instance) {
|
|
764
|
+
return instance.metadata?.[MODAL_COVERS] === true;
|
|
765
|
+
}
|
|
735
766
|
var RiviniaModalHost = class RiviniaModalHost {
|
|
736
767
|
modal = input.required(...ngDevMode ? [{ debugName: "modal" }] : /* istanbul ignore next */ []);
|
|
737
768
|
size = input("md", ...ngDevMode ? [{ debugName: "size" }] : /* istanbul ignore next */ []);
|
|
738
769
|
showCloseButton = input(true, ...ngDevMode ? [{ debugName: "showCloseButton" }] : /* istanbul ignore next */ []);
|
|
739
770
|
titleOf = input(...ngDevMode ? [void 0, { debugName: "titleOf" }] : /* istanbul ignore next */ []);
|
|
740
|
-
|
|
771
|
+
loadingOf = input(...ngDevMode ? [void 0, { debugName: "loadingOf" }] : /* istanbul ignore next */ []);
|
|
772
|
+
loadingLabel = input("Loading…", ...ngDevMode ? [{ debugName: "loadingLabel" }] : /* istanbul ignore next */ []);
|
|
773
|
+
visible = signal([], ...ngDevMode ? [{ debugName: "visible" }] : /* istanbul ignore next */ []);
|
|
774
|
+
syncStack = effect(() => {
|
|
775
|
+
const next = this.modal().visible();
|
|
776
|
+
this.withTransition(() => this.visible.set(next));
|
|
777
|
+
}, ...ngDevMode ? [{ debugName: "syncStack" }] : /* istanbul ignore next */ []);
|
|
778
|
+
transition = (() => {
|
|
779
|
+
if (typeof document === "undefined") return false;
|
|
780
|
+
return typeof document.startViewTransition === "function";
|
|
781
|
+
})();
|
|
782
|
+
lastStackKey = "";
|
|
783
|
+
withTransition(change) {
|
|
784
|
+
const key = this.modal().visible().map((i) => `${i.id}:${i.status}`).join("|");
|
|
785
|
+
if (!this.transition || key === this.lastStackKey) {
|
|
786
|
+
change();
|
|
787
|
+
this.lastStackKey = key;
|
|
788
|
+
return;
|
|
789
|
+
}
|
|
790
|
+
const doc = document;
|
|
791
|
+
try {
|
|
792
|
+
doc.startViewTransition?.(() => {
|
|
793
|
+
change();
|
|
794
|
+
this.lastStackKey = key;
|
|
795
|
+
});
|
|
796
|
+
} catch {
|
|
797
|
+
change();
|
|
798
|
+
this.lastStackKey = key;
|
|
799
|
+
}
|
|
800
|
+
}
|
|
801
|
+
covering = computed(() => {
|
|
802
|
+
const list = this.visible();
|
|
803
|
+
let coverId = null;
|
|
804
|
+
for (const instance of list) if (covers(instance)) coverId = instance.id;
|
|
805
|
+
return coverId;
|
|
806
|
+
}, ...ngDevMode ? [{ debugName: "covering" }] : /* istanbul ignore next */ []);
|
|
807
|
+
isLoading(instance) {
|
|
808
|
+
try {
|
|
809
|
+
return this.loadingOf()?.(instance) === true;
|
|
810
|
+
} catch {
|
|
811
|
+
return false;
|
|
812
|
+
}
|
|
813
|
+
}
|
|
814
|
+
callerName(instance) {
|
|
815
|
+
return instance.caller ?? null;
|
|
816
|
+
}
|
|
817
|
+
sizeOf(instance) {
|
|
818
|
+
return instance.metadata?.["size"] ?? void 0 ?? this.size();
|
|
819
|
+
}
|
|
820
|
+
isCovered(instance) {
|
|
821
|
+
return this.visible().some((other) => other.id !== instance.id && covers(other));
|
|
822
|
+
}
|
|
741
823
|
titleOfInstance(instance) {
|
|
742
824
|
try {
|
|
743
825
|
const custom = this.titleOf()?.(instance);
|
|
@@ -827,24 +909,55 @@ var RiviniaModalHost = class RiviniaModalHost {
|
|
|
827
909
|
isSignal: true,
|
|
828
910
|
isRequired: false,
|
|
829
911
|
transformFunction: null
|
|
912
|
+
},
|
|
913
|
+
loadingOf: {
|
|
914
|
+
classPropertyName: "loadingOf",
|
|
915
|
+
publicName: "loadingOf",
|
|
916
|
+
isSignal: true,
|
|
917
|
+
isRequired: false,
|
|
918
|
+
transformFunction: null
|
|
919
|
+
},
|
|
920
|
+
loadingLabel: {
|
|
921
|
+
classPropertyName: "loadingLabel",
|
|
922
|
+
publicName: "loadingLabel",
|
|
923
|
+
isSignal: true,
|
|
924
|
+
isRequired: false,
|
|
925
|
+
transformFunction: null
|
|
830
926
|
}
|
|
831
927
|
},
|
|
832
928
|
ngImport: i0,
|
|
833
929
|
template: `
|
|
834
930
|
@for (instance of visible(); track instance.id) {
|
|
931
|
+
@if (isCovered(instance)) {
|
|
932
|
+
<!--
|
|
933
|
+
Hidden by a dialog above it that asked to cover the others. It stays
|
|
934
|
+
in the manager — only the rendering is skipped, so it comes back when
|
|
935
|
+
the cover closes.
|
|
936
|
+
-->
|
|
937
|
+
} @else {
|
|
835
938
|
<rv-modal-dialog
|
|
836
939
|
[open]="true"
|
|
837
940
|
[managed]="true"
|
|
838
941
|
[closing]="instance.status === 'closing'"
|
|
839
942
|
[title]="titleOfInstance(instance)"
|
|
840
|
-
[size]="
|
|
943
|
+
[size]="sizeOf(instance)"
|
|
944
|
+
[attr.data-caller]="callerName(instance)"
|
|
841
945
|
[showCloseButton]="showCloseButton()"
|
|
842
946
|
[closeOnEscape]="instance.dismiss.closeOnEscape ?? true"
|
|
843
947
|
[backdrop]="instance.dismiss.backdrop ?? 'close'"
|
|
844
948
|
(dismissRequested)="onIntent(instance.id, $event)"
|
|
845
949
|
(exitDone)="onExitDone(instance.id)"
|
|
846
950
|
>
|
|
847
|
-
@if (instance
|
|
951
|
+
@if (isLoading(instance)) {
|
|
952
|
+
<!--
|
|
953
|
+
The data has not arrived yet. A loader says so; an empty dialog
|
|
954
|
+
looks broken.
|
|
955
|
+
-->
|
|
956
|
+
<div class="rv-modal-loading" role="status" aria-live="polite">
|
|
957
|
+
<span class="rv-modal-spinner" aria-hidden="true"></span>
|
|
958
|
+
<span>{{ loadingLabel() }}</span>
|
|
959
|
+
</div>
|
|
960
|
+
} @else if (instance.content.kind === 'template') {
|
|
848
961
|
<ng-container
|
|
849
962
|
*ngTemplateOutlet="instance.content.template; context: templateContext(instance)"
|
|
850
963
|
></ng-container>
|
|
@@ -858,6 +971,7 @@ var RiviniaModalHost = class RiviniaModalHost {
|
|
|
858
971
|
></ng-container>
|
|
859
972
|
}
|
|
860
973
|
</rv-modal-dialog>
|
|
974
|
+
}
|
|
861
975
|
}
|
|
862
976
|
`,
|
|
863
977
|
isInline: true,
|
|
@@ -925,7 +1039,6 @@ i0.ɵɵngDeclareClassMetadata({
|
|
|
925
1039
|
type: Component,
|
|
926
1040
|
args: [{
|
|
927
1041
|
selector: "rv-modal-host",
|
|
928
|
-
standalone: true,
|
|
929
1042
|
imports: [
|
|
930
1043
|
RiviniaModalDialog,
|
|
931
1044
|
NgComponentOutlet,
|
|
@@ -933,19 +1046,36 @@ i0.ɵɵngDeclareClassMetadata({
|
|
|
933
1046
|
],
|
|
934
1047
|
template: `
|
|
935
1048
|
@for (instance of visible(); track instance.id) {
|
|
1049
|
+
@if (isCovered(instance)) {
|
|
1050
|
+
<!--
|
|
1051
|
+
Hidden by a dialog above it that asked to cover the others. It stays
|
|
1052
|
+
in the manager — only the rendering is skipped, so it comes back when
|
|
1053
|
+
the cover closes.
|
|
1054
|
+
-->
|
|
1055
|
+
} @else {
|
|
936
1056
|
<rv-modal-dialog
|
|
937
1057
|
[open]="true"
|
|
938
1058
|
[managed]="true"
|
|
939
1059
|
[closing]="instance.status === 'closing'"
|
|
940
1060
|
[title]="titleOfInstance(instance)"
|
|
941
|
-
[size]="
|
|
1061
|
+
[size]="sizeOf(instance)"
|
|
1062
|
+
[attr.data-caller]="callerName(instance)"
|
|
942
1063
|
[showCloseButton]="showCloseButton()"
|
|
943
1064
|
[closeOnEscape]="instance.dismiss.closeOnEscape ?? true"
|
|
944
1065
|
[backdrop]="instance.dismiss.backdrop ?? 'close'"
|
|
945
1066
|
(dismissRequested)="onIntent(instance.id, $event)"
|
|
946
1067
|
(exitDone)="onExitDone(instance.id)"
|
|
947
1068
|
>
|
|
948
|
-
@if (instance
|
|
1069
|
+
@if (isLoading(instance)) {
|
|
1070
|
+
<!--
|
|
1071
|
+
The data has not arrived yet. A loader says so; an empty dialog
|
|
1072
|
+
looks broken.
|
|
1073
|
+
-->
|
|
1074
|
+
<div class="rv-modal-loading" role="status" aria-live="polite">
|
|
1075
|
+
<span class="rv-modal-spinner" aria-hidden="true"></span>
|
|
1076
|
+
<span>{{ loadingLabel() }}</span>
|
|
1077
|
+
</div>
|
|
1078
|
+
} @else if (instance.content.kind === 'template') {
|
|
949
1079
|
<ng-container
|
|
950
1080
|
*ngTemplateOutlet="instance.content.template; context: templateContext(instance)"
|
|
951
1081
|
></ng-container>
|
|
@@ -959,6 +1089,7 @@ i0.ɵɵngDeclareClassMetadata({
|
|
|
959
1089
|
></ng-container>
|
|
960
1090
|
}
|
|
961
1091
|
</rv-modal-dialog>
|
|
1092
|
+
}
|
|
962
1093
|
}
|
|
963
1094
|
`
|
|
964
1095
|
}]
|
|
@@ -995,6 +1126,22 @@ i0.ɵɵngDeclareClassMetadata({
|
|
|
995
1126
|
alias: "titleOf",
|
|
996
1127
|
required: false
|
|
997
1128
|
}]
|
|
1129
|
+
}],
|
|
1130
|
+
loadingOf: [{
|
|
1131
|
+
type: i0.Input,
|
|
1132
|
+
args: [{
|
|
1133
|
+
isSignal: true,
|
|
1134
|
+
alias: "loadingOf",
|
|
1135
|
+
required: false
|
|
1136
|
+
}]
|
|
1137
|
+
}],
|
|
1138
|
+
loadingLabel: [{
|
|
1139
|
+
type: i0.Input,
|
|
1140
|
+
args: [{
|
|
1141
|
+
isSignal: true,
|
|
1142
|
+
alias: "loadingLabel",
|
|
1143
|
+
required: false
|
|
1144
|
+
}]
|
|
998
1145
|
}]
|
|
999
1146
|
}
|
|
1000
1147
|
});
|