@rivinia/modal 0.3.0 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +102 -9
- package/fesm2022/rivinia-modal-ui.mjs +1001 -0
- package/fesm2022/rivinia-modal-ui.mjs.map +1 -0
- package/fesm2022/rivinia-modal.mjs +44 -20
- package/fesm2022/rivinia-modal.mjs.map +1 -1
- package/package.json +5 -1
- package/types/rivinia-modal-ui.d.ts +332 -0
- package/types/rivinia-modal.d.ts +22 -1
package/README.md
CHANGED
|
@@ -8,7 +8,7 @@ Headless modal manager for Angular 22+. Stack/Queue strategies, typed
|
|
|
8
8
|
zero styles, zero router dependency. A future `@rivinia/modal/ui` entry point
|
|
9
9
|
will render this state; Core never imports UI.
|
|
10
10
|
|
|
11
|
-
- Version: **0.
|
|
11
|
+
- Version: **0.5.0**
|
|
12
12
|
- Requires: **Angular `^22.0.0`**, `tslib`
|
|
13
13
|
|
|
14
14
|
## Install
|
|
@@ -47,14 +47,14 @@ export class Users {
|
|
|
47
47
|
|
|
48
48
|
## API
|
|
49
49
|
|
|
50
|
-
| Export
|
|
51
|
-
|
|
52
|
-
| `provideRiviniaModal(config?)`
|
|
53
|
-
| `RiviniaModal`
|
|
54
|
-
| `injectRiviniaModal()`
|
|
55
|
-
| `RIVINIA_MODAL_CONTEXT`
|
|
56
|
-
| `StackStrategy` / `QueueStrategy` | classes
|
|
57
|
-
| types
|
|
50
|
+
| Export | Kind | Purpose |
|
|
51
|
+
| --------------------------------- | ------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
52
|
+
| `provideRiviniaModal(config?)` | provider | `{ strategy?, dismiss?, maxVisible?, maxQueue?, overflow?, descendants?, presets?, closingTimeout?, diagnostics? }` |
|
|
53
|
+
| `RiviniaModal` | `@Service()` | `openModal/close/dismiss/cancel/requestDismiss/update/confirmClosed/dismissAll/findByRef/useStrategy/debugState` + `active/queued/closing/visible/top` signals |
|
|
54
|
+
| `injectRiviniaModal()` | function | shorthand for `inject(RiviniaModal)` |
|
|
55
|
+
| `RIVINIA_MODAL_CONTEXT` | token | per-instance context for dynamic components |
|
|
56
|
+
| `StackStrategy` / `QueueStrategy` | classes | built-in ordering policies (`ModalStrategy` for custom) |
|
|
57
|
+
| types | types | `ModalHandle`, `ModalResult`, `ModalContent`, `ModalOpenOptions`, `ModalDismissPolicy`, `ModalGuard`, … |
|
|
58
58
|
|
|
59
59
|
Results settle exactly once, at dismissal initiation — exit animations never
|
|
60
60
|
delay callers. `close/dismiss/cancel` are guard-free primitives (booleans);
|
|
@@ -86,6 +86,99 @@ Closing a parent cascades to descendants (`dismissed: parent-closed`) unless
|
|
|
86
86
|
`descendants: 'prevent-parent-close'` blocks it. Swapping strategies with live
|
|
87
87
|
modals throws — close everything first.
|
|
88
88
|
|
|
89
|
+
## Ready-made UI (`@rivinia/modal/ui`)
|
|
90
|
+
|
|
91
|
+
Optional secondary entry point. Zero new runtime dependencies (platform
|
|
92
|
+
`<dialog>` + Angular only), no design system, no CSS framework. Headless
|
|
93
|
+
consumers never load it (proven: core FESM contains zero UI markers).
|
|
94
|
+
|
|
95
|
+
```ts
|
|
96
|
+
import { RiviniaModalDialog } from '@rivinia/modal/ui'; // UI-only
|
|
97
|
+
import { RiviniaModalHost } from '@rivinia/modal/ui'; // managed adapter
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
**Mode A — UI-only** (no controller; owns its `open` state):
|
|
101
|
+
|
|
102
|
+
```html
|
|
103
|
+
<rv-modal-dialog [(open)]="open" [title]="'Delete?'" [size]="'sm'" (confirmed)="remove()">
|
|
104
|
+
<p>Delete {{ user.name }}?</p>
|
|
105
|
+
</rv-modal-dialog>
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
**Mode B — headless + custom UI:** keep using `@rivinia/modal` alone.
|
|
109
|
+
Nothing here loads, no styles leak.
|
|
110
|
+
|
|
111
|
+
**Mode C — headless + ready-made UI** (single source of truth stays Core):
|
|
112
|
+
|
|
113
|
+
```html
|
|
114
|
+
<rv-modal-host [modal]="injectRiviniaModal()" />
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
Intents (X, Escape, backdrop) route through `requestDismiss` (guards run);
|
|
118
|
+
exits play, then the host calls `confirmClosed()` (match `closingTimeout`
|
|
119
|
+
to the animation duration; default 300 ms covers the 150 ms default).
|
|
120
|
+
|
|
121
|
+
Custom chrome (no X button, custom header/footer, own actions in content):
|
|
122
|
+
|
|
123
|
+
```html
|
|
124
|
+
<rv-modal-dialog [(open)]="open">
|
|
125
|
+
<div rv-modal-header rvModalHeader>My header, no X</div>
|
|
126
|
+
<p>Body.</p>
|
|
127
|
+
<div rv-modal-footer rvModalFooter><button (click)="save()">Save</button></div>
|
|
128
|
+
</rv-modal-dialog>
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
No header at all: omit the title (no custom header either). Header rules:
|
|
132
|
+
title + optional `description` (wired as `aria-describedby`), top-aligned
|
|
133
|
+
with a shrink-proof X; an empty heading collapses (no dead space); a custom
|
|
134
|
+
header renders inside the same padded row. Focus goes to the title when
|
|
135
|
+
there is no close button, to the dialog otherwise. No footer:
|
|
136
|
+
omit the footer slot (renders nothing). Positions: `center` (default),
|
|
137
|
+
`top/bottom`, `-start/-end` variants (RTL-aware), `side-start/side-end`
|
|
138
|
+
full-height panels, `fullscreen`. Under 640px viewports, center/top/bottom
|
|
139
|
+
shapes become bottom sheets automatically (top-only radius, slide-up
|
|
140
|
+
entrance); panels and fullscreen keep their shape.
|
|
141
|
+
Sizes `sm/md/lg/xl/full/auto` are ceilings via `--rv-modal-width-*` vars.
|
|
142
|
+
|
|
143
|
+
Theming (no theme service; consumer owns it):
|
|
144
|
+
|
|
145
|
+
```css
|
|
146
|
+
:root {
|
|
147
|
+
--rv-modal-bg: #1c1c1e;
|
|
148
|
+
--rv-modal-fg: #f5f5f7;
|
|
149
|
+
}
|
|
150
|
+
@media (prefers-color-scheme: dark) {
|
|
151
|
+
.rv-modal {
|
|
152
|
+
/* built-in dark defaults already apply; override vars to brand it */
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
Plus: `prefers-reduced-motion` disables animation; `animation: 'none'`
|
|
158
|
+
opt-out; `backdrop: 'close' | 'static' | 'disabled'`; Escape/backdrop/X
|
|
159
|
+
policies stay independent (no `disableClose` bundle); long content scrolls
|
|
160
|
+
inside (`rv-modal-body`); `scrollbar-gutter: stable` while open;
|
|
161
|
+
reference-counted scroll lock restores previous styles; focus trap + focus
|
|
162
|
+
restore + `aria-labelledby`/`aria-label`; `dir=rtl` works via logical
|
|
163
|
+
properties; SSR renders closed and opens after hydration; lazy-load the
|
|
164
|
+
entry (`loadComponent`) to split it into its own chunk (~16.7 kB raw,
|
|
165
|
+
~4.9 kB transfer, measured). Toasts: MEASURED (Chrome, `elementFromPoint`) —
|
|
166
|
+
neither a high `z-index` nor `popover="manual"` paints above an open modal
|
|
167
|
+
dialog; toasts queue behind it. No modal→toast dependency by design.
|
|
168
|
+
|
|
169
|
+
## Type holes (honest, by design)
|
|
170
|
+
|
|
171
|
+
Core checks `ModalHandle<TResult>.close(result?: TResult)` and
|
|
172
|
+
`ModalResult`/`ModalContext.data` typing (see `modal-type-safety.spec.ts`
|
|
173
|
+
`@ts-expect-error` tests). It does NOT check:
|
|
174
|
+
|
|
175
|
+
- `ModalContext.close(result?: unknown)` — any payload compiles; the mismatch
|
|
176
|
+
reaches the opener's awaited `ModalResult` unchecked.
|
|
177
|
+
- `inputs?: Record<string, unknown>` (options + component content) — any shape
|
|
178
|
+
compiles; misspelled keys fail silently at render. The UI owns binding.
|
|
179
|
+
- The internal deferred-promise cast to `Promise<ModalResult<TResult>>` —
|
|
180
|
+
`close()` settles whatever it receives, never validated.
|
|
181
|
+
|
|
89
182
|
## License
|
|
90
183
|
|
|
91
184
|
MIT.
|