maverick-wave 5.16.0 → 5.17.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/.claude/skills/mw-maverick-wave/SKILL.md +10 -7
- package/.claude/skills/mw-maverick-wave/examples/angular-list-page.md +40 -35
- package/.claude/skills/mw-maverick-wave/examples/angular-services.md +95 -33
- package/.claude/skills/mw-maverick-wave/examples/static-landing-page.md +38 -53
- package/.claude/skills/mw-maverick-wave/references/components.md +45 -46
- package/.claude/skills/mw-maverick-wave/references/javascript.md +16 -17
- package/CHANGELOG.md +7 -0
- package/README.md +2 -2
- package/index.html +0 -4
- package/maverick-wave.min.css +2 -2
- package/package.json +2 -2
- package/release.sh +5 -0
- package/scripts/verify.js +23 -10
- package/src/partials/get-started-container.html +5 -3
- package/src/partials/modals-container.html +137 -186
- package/src/scss/abstracts/_mixins.scss +30 -6
- package/src/scss/components/_modals.scss +13 -2
- package/src/scss/layout/_main.scss +18 -0
- package/src/scss/utilities/_touch-targets.scss +5 -4
|
@@ -48,18 +48,20 @@ Load the one you need - do not read them all up front.
|
|
|
48
48
|
/>
|
|
49
49
|
<link
|
|
50
50
|
rel="stylesheet"
|
|
51
|
-
href="https://cdn.jsdelivr.net/npm/maverick-wave@5
|
|
51
|
+
href="https://cdn.jsdelivr.net/npm/maverick-wave@5/maverick-wave.min.css"
|
|
52
52
|
/>
|
|
53
53
|
<link
|
|
54
54
|
rel="stylesheet"
|
|
55
55
|
href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.0/css/all.min.css"
|
|
56
56
|
/>
|
|
57
57
|
...
|
|
58
|
-
<script src="https://cdn.jsdelivr.net/npm/maverick-wave@5
|
|
58
|
+
<script src="https://cdn.jsdelivr.net/npm/maverick-wave@5/maverick-wave.min.js"></script>
|
|
59
59
|
```
|
|
60
60
|
|
|
61
|
-
|
|
62
|
-
|
|
61
|
+
`@5` keeps this snippet current; in a real project pin the exact version so a
|
|
62
|
+
build stays reproducible. The JS file is optional and only for
|
|
63
|
+
server-rendered/static pages - see below. `viewport-fit=cover` is not optional -
|
|
64
|
+
see pitfall 30.
|
|
63
65
|
|
|
64
66
|
### Angular (or any SPA)
|
|
65
67
|
|
|
@@ -129,7 +131,7 @@ The classes that mean something _other_ than "on" keep their own names:
|
|
|
129
131
|
| Checkbox list item (`li`) | `mw-selected` |
|
|
130
132
|
| Calendar day, picked | `mw-selected` |
|
|
131
133
|
| Kanban ticket being edited | `mw-kanban-editing` |
|
|
132
|
-
| Modal overlay
|
|
134
|
+
| Modal, older overlay div | `mw-modal-open` (a `<dialog>` carries `open` itself) |
|
|
133
135
|
| Alert, dismissing / dismissed | `mw-alert-closing` → `mw-alert-closed` (`display: none`) |
|
|
134
136
|
| Field wrapper in error | `mw-field-has-error` |
|
|
135
137
|
| Single form control in error | `mw-form-element-error` |
|
|
@@ -234,8 +236,9 @@ none of them survive into the compiled CSS. Full list in `references/theming.md`
|
|
|
234
236
|
- `touch-context($bp: 'md')` - coarse pointer _or_ narrow viewport, the condition
|
|
235
237
|
every target-size rule in the framework hangs under.
|
|
236
238
|
- `touch-floor($size: 2.75rem)` - that condition plus a `min-height`.
|
|
237
|
-
- `hit-area($grow: 6px)` - grows the hit area through `::after`
|
|
238
|
-
the silhouette.
|
|
239
|
+
- `hit-area($grow: 6px, $box: 1.5rem)` - grows the hit area through `::after`
|
|
240
|
+
without touching the silhouette. An element carrying a `data-tooltip` owns that
|
|
241
|
+
pseudo for its arrow, so those are skipped and grow to `$box` instead.
|
|
239
242
|
- `hover` / `hover-move`, `focus-ring`, `focus-ring-inset`, `field-focus`,
|
|
240
243
|
`truncate`, `surface`.
|
|
241
244
|
|
|
@@ -248,40 +248,44 @@ export class InvoiceListComponent {
|
|
|
248
248
|
</div>
|
|
249
249
|
}
|
|
250
250
|
|
|
251
|
-
<!-- Delete confirmation -->
|
|
252
|
-
<
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
</div>
|
|
266
|
-
<div class="mw-modal-footer">
|
|
267
|
-
<button
|
|
268
|
-
type="button"
|
|
269
|
-
class="mw-btn mw-btn-outline"
|
|
270
|
-
(click)="toDelete.set(null)"
|
|
271
|
-
>
|
|
272
|
-
Cancel
|
|
273
|
-
</button>
|
|
274
|
-
<button
|
|
275
|
-
type="button"
|
|
276
|
-
class="mw-btn mw-btn-danger"
|
|
277
|
-
(click)="confirmDelete()"
|
|
278
|
-
>
|
|
279
|
-
<i class="fas fa-trash"></i> Delete
|
|
280
|
-
</button>
|
|
281
|
-
</div>
|
|
251
|
+
<!-- Delete confirmation - ModalDirective is in angular-services.md -->
|
|
252
|
+
<dialog
|
|
253
|
+
class="mw-modal mw-modal-sm"
|
|
254
|
+
closedby="any"
|
|
255
|
+
appModal
|
|
256
|
+
[isOpen]="toDelete() !== null"
|
|
257
|
+
(dismiss)="toDelete.set(null)"
|
|
258
|
+
aria-labelledby="delete-title"
|
|
259
|
+
>
|
|
260
|
+
<div class="mw-modal-header">
|
|
261
|
+
<h4 class="mw-modal-title" id="delete-title">Delete invoice</h4>
|
|
262
|
+
<button type="button" class="mw-modal-close" (click)="toDelete.set(null)">
|
|
263
|
+
𝗫
|
|
264
|
+
</button>
|
|
282
265
|
</div>
|
|
283
|
-
<div class="mw-modal-
|
|
284
|
-
|
|
266
|
+
<div class="mw-modal-body">
|
|
267
|
+
<p class="mw-text-center">
|
|
268
|
+
<strong>Delete {{ toDelete()?.number }}?</strong>
|
|
269
|
+
</p>
|
|
270
|
+
<p class="mw-text-center mw-text-muted">This action cannot be undone.</p>
|
|
271
|
+
</div>
|
|
272
|
+
<div class="mw-modal-footer">
|
|
273
|
+
<button
|
|
274
|
+
type="button"
|
|
275
|
+
class="mw-btn mw-btn-outline"
|
|
276
|
+
(click)="toDelete.set(null)"
|
|
277
|
+
>
|
|
278
|
+
Cancel
|
|
279
|
+
</button>
|
|
280
|
+
<button
|
|
281
|
+
type="button"
|
|
282
|
+
class="mw-btn mw-btn-danger"
|
|
283
|
+
(click)="confirmDelete()"
|
|
284
|
+
>
|
|
285
|
+
<i class="fas fa-trash"></i> Delete
|
|
286
|
+
</button>
|
|
287
|
+
</div>
|
|
288
|
+
</dialog>
|
|
285
289
|
```
|
|
286
290
|
|
|
287
291
|
## Notes
|
|
@@ -298,7 +302,8 @@ export class InvoiceListComponent {
|
|
|
298
302
|
sits left while the figures sit right.
|
|
299
303
|
- **`mw-tag`, not `mw-tags`**, for a single status chip in a cell.
|
|
300
304
|
- **`mw-btn-danger` for the destructive action** in the modal footer.
|
|
301
|
-
- The modal is always in the DOM
|
|
302
|
-
scroll lock
|
|
305
|
+
- The modal is always in the DOM; `ModalDirective` calls `showModal()` when the
|
|
306
|
+
signal turns true, and Escape, the focus trap and the scroll lock follow from
|
|
307
|
+
the element.
|
|
303
308
|
- The toast stack lives in the app shell, not on this page - see
|
|
304
309
|
`angular-services.md`.
|
|
@@ -159,45 +159,94 @@ from the signal is cleaner.
|
|
|
159
159
|
|
|
160
160
|
## Modal
|
|
161
161
|
|
|
162
|
-
|
|
163
|
-
|
|
162
|
+
A `<dialog>` with the same `mw-modal` classes. Escape, the focus trap, the inert
|
|
163
|
+
page behind it and the body scroll lock all come from the element, so what is
|
|
164
|
+
left to write is opening it.
|
|
165
|
+
|
|
166
|
+
`[open]="isOpen()"` does **not** work: the attribute opens a non-modal dialog -
|
|
167
|
+
no top layer, no backdrop, no focus trap. It has to be `showModal()`, which
|
|
168
|
+
needs a directive.
|
|
169
|
+
|
|
170
|
+
```ts
|
|
171
|
+
@Directive({
|
|
172
|
+
selector: 'dialog[appModal]',
|
|
173
|
+
host: {
|
|
174
|
+
'(close)': 'onClose()',
|
|
175
|
+
'(click)': 'onClick($event)',
|
|
176
|
+
},
|
|
177
|
+
})
|
|
178
|
+
export class ModalDirective {
|
|
179
|
+
readonly isOpen = input(false);
|
|
180
|
+
/** the dialog closed itself - Escape, a backdrop click or a form submit */
|
|
181
|
+
readonly dismiss = output<void>();
|
|
182
|
+
|
|
183
|
+
private readonly host =
|
|
184
|
+
inject<ElementRef<HTMLDialogElement>>(ElementRef).nativeElement;
|
|
185
|
+
|
|
186
|
+
constructor() {
|
|
187
|
+
const isBrowser = isPlatformBrowser(inject(PLATFORM_ID));
|
|
188
|
+
|
|
189
|
+
effect(() => {
|
|
190
|
+
const open = this.isOpen();
|
|
191
|
+
if (!isBrowser) return;
|
|
192
|
+
if (open && !this.host.open) this.host.showModal();
|
|
193
|
+
else if (!open && this.host.open) this.host.close();
|
|
194
|
+
});
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
// Only when the dialog closed itself - closing it through the signal already
|
|
198
|
+
// went through whatever set the signal, and reporting that back would run the
|
|
199
|
+
// caller's dismiss handler a second time.
|
|
200
|
+
protected onClose(): void {
|
|
201
|
+
if (this.isOpen()) this.dismiss.emit();
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
// A click on the dialog element is a click on its backdrop; the content sits
|
|
205
|
+
// in .mw-modal-header/body/footer inside it. Stands in for `closedby="any"`
|
|
206
|
+
// where that is not supported yet.
|
|
207
|
+
protected onClick(event: MouseEvent): void {
|
|
208
|
+
if (event.target === this.host) this.host.close();
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
```
|
|
164
212
|
|
|
165
213
|
```ts
|
|
166
214
|
@Component({
|
|
167
215
|
selector: 'app-confirm-dialog',
|
|
216
|
+
imports: [ModalDirective],
|
|
168
217
|
template: `
|
|
169
|
-
<
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
<div class="mw-modal-footer">
|
|
183
|
-
<button
|
|
184
|
-
type="button"
|
|
185
|
-
class="mw-btn mw-btn-outline"
|
|
186
|
-
(click)="cancelled.emit()"
|
|
187
|
-
>
|
|
188
|
-
Cancel
|
|
189
|
-
</button>
|
|
190
|
-
<button
|
|
191
|
-
type="button"
|
|
192
|
-
class="mw-btn mw-btn-danger"
|
|
193
|
-
(click)="confirmed.emit()"
|
|
194
|
-
>
|
|
195
|
-
Delete
|
|
196
|
-
</button>
|
|
197
|
-
</div>
|
|
218
|
+
<dialog
|
|
219
|
+
class="mw-modal mw-modal-sm"
|
|
220
|
+
closedby="any"
|
|
221
|
+
appModal
|
|
222
|
+
[isOpen]="open()"
|
|
223
|
+
(dismiss)="cancelled.emit()"
|
|
224
|
+
aria-labelledby="confirm-title"
|
|
225
|
+
>
|
|
226
|
+
<div class="mw-modal-header">
|
|
227
|
+
<h4 class="mw-modal-title" id="confirm-title">{{ title() }}</h4>
|
|
228
|
+
<button type="button" class="mw-modal-close" (click)="cancelled.emit()">
|
|
229
|
+
𝗫
|
|
230
|
+
</button>
|
|
198
231
|
</div>
|
|
199
|
-
<div class="mw-modal-
|
|
200
|
-
|
|
232
|
+
<div class="mw-modal-body"><ng-content /></div>
|
|
233
|
+
<div class="mw-modal-footer">
|
|
234
|
+
<button
|
|
235
|
+
type="button"
|
|
236
|
+
class="mw-btn mw-btn-outline"
|
|
237
|
+
(click)="cancelled.emit()"
|
|
238
|
+
>
|
|
239
|
+
Cancel
|
|
240
|
+
</button>
|
|
241
|
+
<button
|
|
242
|
+
type="button"
|
|
243
|
+
class="mw-btn mw-btn-danger"
|
|
244
|
+
(click)="confirmed.emit()"
|
|
245
|
+
>
|
|
246
|
+
Delete
|
|
247
|
+
</button>
|
|
248
|
+
</div>
|
|
249
|
+
</dialog>
|
|
201
250
|
`,
|
|
202
251
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
203
252
|
})
|
|
@@ -209,6 +258,19 @@ export class ConfirmDialogComponent {
|
|
|
209
258
|
}
|
|
210
259
|
```
|
|
211
260
|
|
|
261
|
+
`role="dialog"` and `aria-modal` come from `showModal()`; the title is named
|
|
262
|
+
with `aria-labelledby`. Without `autofocus` on something the browser focuses the
|
|
263
|
+
first focusable element, usually the close button - put it on the field or the
|
|
264
|
+
safe action instead.
|
|
265
|
+
|
|
266
|
+
In tests, jsdom implements none of `<dialog>` - not `showModal`, `show` or
|
|
267
|
+
`close`, only the `open` attribute reflects. Specs that render an open modal
|
|
268
|
+
fail with `showModal is not a function` until a setup file adds them.
|
|
269
|
+
|
|
270
|
+
The `mw-modal-overlay` div toggled by `mw-modal-open` is still styled, for
|
|
271
|
+
markup that predates this - it cannot trap focus or make the page inert, so it
|
|
272
|
+
is not what to write now.
|
|
273
|
+
|
|
212
274
|
## Accordion
|
|
213
275
|
|
|
214
276
|
`mw-active` on the header **and** the content.
|
|
@@ -23,7 +23,7 @@ accordion FAQ.
|
|
|
23
23
|
|
|
24
24
|
<link
|
|
25
25
|
rel="stylesheet"
|
|
26
|
-
href="https://cdn.jsdelivr.net/npm/maverick-wave@5
|
|
26
|
+
href="https://cdn.jsdelivr.net/npm/maverick-wave@5/maverick-wave.min.css"
|
|
27
27
|
/>
|
|
28
28
|
<link
|
|
29
29
|
rel="stylesheet"
|
|
@@ -123,7 +123,7 @@ accordion FAQ.
|
|
|
123
123
|
<button
|
|
124
124
|
type="button"
|
|
125
125
|
class="mw-btn mw-btn-primary mw-btn-lg"
|
|
126
|
-
|
|
126
|
+
data-mw-modal="demo"
|
|
127
127
|
>
|
|
128
128
|
Book a demo
|
|
129
129
|
</button>
|
|
@@ -302,7 +302,7 @@ accordion FAQ.
|
|
|
302
302
|
<button
|
|
303
303
|
type="button"
|
|
304
304
|
class="mw-btn mw-btn-outline"
|
|
305
|
-
|
|
305
|
+
data-mw-modal="demo"
|
|
306
306
|
>
|
|
307
307
|
Contact us
|
|
308
308
|
</button>
|
|
@@ -412,11 +412,7 @@ accordion FAQ.
|
|
|
412
412
|
<!-- A link in running text is mw-link, not a button class - it
|
|
413
413
|
keeps the line height of the paragraph around it -->
|
|
414
414
|
<p>
|
|
415
|
-
<button
|
|
416
|
-
type="button"
|
|
417
|
-
class="mw-link"
|
|
418
|
-
onclick="openModal('demo')"
|
|
419
|
-
>
|
|
415
|
+
<button type="button" class="mw-link" data-mw-modal="demo">
|
|
420
416
|
Book a 20 minute demo</button
|
|
421
417
|
>, no sales pitch.
|
|
422
418
|
</p>
|
|
@@ -454,53 +450,42 @@ accordion FAQ.
|
|
|
454
450
|
</div>
|
|
455
451
|
</footer>
|
|
456
452
|
|
|
457
|
-
<!--
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
453
|
+
<!-- Markup stays in the DOM; a trigger carrying data-mw-modal="demo" opens
|
|
454
|
+
it, .mw-modal-close closes it, closedby="any" dismisses on the backdrop -->
|
|
455
|
+
<dialog
|
|
456
|
+
id="demo"
|
|
457
|
+
class="mw-modal"
|
|
458
|
+
closedby="any"
|
|
459
|
+
aria-labelledby="demo-title"
|
|
460
|
+
>
|
|
461
|
+
<div class="mw-modal-header">
|
|
462
|
+
<h4 class="mw-modal-title" id="demo-title">Book a demo</h4>
|
|
463
|
+
<button type="button" class="mw-modal-close">𝗫</button>
|
|
464
|
+
</div>
|
|
465
|
+
<div class="mw-modal-body">
|
|
466
|
+
<form class="mw-form">
|
|
467
|
+
<div class="mw-field">
|
|
468
|
+
<label class="mw-field-label mw-required" for="demo-email"
|
|
469
|
+
>Email</label
|
|
470
|
+
>
|
|
471
|
+
<div class="mw-input-group">
|
|
472
|
+
<span class="mw-input-group-prefix"
|
|
473
|
+
><i class="fas fa-envelope"></i
|
|
474
|
+
></span>
|
|
475
|
+
<input id="demo-email" type="email" class="mw-input" required />
|
|
476
476
|
</div>
|
|
477
|
-
</
|
|
478
|
-
</
|
|
479
|
-
<div class="mw-modal-footer">
|
|
480
|
-
<button
|
|
481
|
-
type="button"
|
|
482
|
-
class="mw-btn mw-btn-outline"
|
|
483
|
-
onclick="closeModal('demo')"
|
|
484
|
-
>
|
|
485
|
-
Cancel
|
|
486
|
-
</button>
|
|
487
|
-
<button type="button" class="mw-btn mw-btn-primary">Send</button>
|
|
488
|
-
</div>
|
|
477
|
+
</div>
|
|
478
|
+
</form>
|
|
489
479
|
</div>
|
|
490
|
-
<div class="mw-modal-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
}
|
|
500
|
-
function closeModal(id) {
|
|
501
|
-
document.getElementById(id).classList.remove('mw-modal-open');
|
|
502
|
-
}
|
|
503
|
-
</script>
|
|
480
|
+
<div class="mw-modal-footer">
|
|
481
|
+
<button type="button" class="mw-modal-close mw-btn mw-btn-outline">
|
|
482
|
+
Cancel
|
|
483
|
+
</button>
|
|
484
|
+
<button type="button" class="mw-btn mw-btn-primary">Send</button>
|
|
485
|
+
</div>
|
|
486
|
+
</dialog>
|
|
487
|
+
|
|
488
|
+
<script src="https://cdn.jsdelivr.net/npm/maverick-wave@5/maverick-wave.min.js"></script>
|
|
504
489
|
</body>
|
|
505
490
|
</html>
|
|
506
491
|
```
|
|
@@ -590,66 +590,65 @@ Toggling is JS - see `references/javascript.md`; the shipped script keeps
|
|
|
590
590
|
## Modal
|
|
591
591
|
|
|
592
592
|
```html
|
|
593
|
-
<
|
|
594
|
-
<div class="mw-modal
|
|
595
|
-
<
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
</
|
|
599
|
-
<div class="mw-modal-body">
|
|
600
|
-
<p>Invoice 2026-0042 will be removed.</p>
|
|
601
|
-
</div>
|
|
602
|
-
<div class="mw-modal-footer">
|
|
603
|
-
<p class="mw-actions-note">This action cannot be undone.</p>
|
|
604
|
-
<button class="mw-btn mw-btn-outline">Cancel</button>
|
|
605
|
-
<button class="mw-btn mw-btn-danger">Delete</button>
|
|
606
|
-
</div>
|
|
593
|
+
<dialog id="delete-modal" class="mw-modal mw-modal-sm" closedby="any">
|
|
594
|
+
<div class="mw-modal-header">
|
|
595
|
+
<h4 class="mw-modal-title">Delete invoice</h4>
|
|
596
|
+
<button class="mw-modal-close" type="button" aria-label="Close">
|
|
597
|
+
𝗫
|
|
598
|
+
</button>
|
|
607
599
|
</div>
|
|
608
|
-
<div class="mw-modal-
|
|
609
|
-
|
|
600
|
+
<div class="mw-modal-body">
|
|
601
|
+
<p>Invoice 2026-0042 will be removed.</p>
|
|
602
|
+
</div>
|
|
603
|
+
<div class="mw-modal-footer">
|
|
604
|
+
<p class="mw-actions-note">This action cannot be undone.</p>
|
|
605
|
+
<button class="mw-modal-close mw-btn mw-btn-outline">Cancel</button>
|
|
606
|
+
<button class="mw-btn mw-btn-danger">Delete</button>
|
|
607
|
+
</div>
|
|
608
|
+
</dialog>
|
|
610
609
|
```
|
|
611
610
|
|
|
612
|
-
-
|
|
613
|
-
|
|
614
|
-
-
|
|
615
|
-
`
|
|
611
|
+
- Escape, the focus trap, `inert` on the page behind it and the body scroll lock
|
|
612
|
+
all come from the element. There is no overlay wrapper and no backdrop div.
|
|
613
|
+
- Opened with `showModal()`, closed with `close()`. `main.js` wires the close
|
|
614
|
+
buttons and exposes `mwOpenModal(id)` / `mwCloseModal(id)`; a trigger can also
|
|
615
|
+
carry `data-mw-modal="delete-modal"` and needs no script at all.
|
|
616
|
+
- `closedby="any"` dismisses it on a backdrop click; where that attribute is not
|
|
617
|
+
understood yet the script handles the click instead.
|
|
616
618
|
- Sizes: `mw-modal-sm` 370px, default 520px, `mw-modal-lg` 720px,
|
|
617
|
-
`mw-modal-xl` 960px. Height is capped at 80-92dvh, the body scrolls.
|
|
618
|
-
|
|
619
|
+
`mw-modal-xl` 960px. Height is capped at 80-92dvh, the body scrolls. Below
|
|
620
|
+
576px it becomes a bottom sheet.
|
|
621
|
+
- `mw-modal-close` is the hook, not a look: on the header X it styles the button,
|
|
622
|
+
on a footer action it only closes. Combine it with `mw-btn mw-btn-primary` and
|
|
623
|
+
the button keeps its own paint.
|
|
619
624
|
- `mw-modal-body` takes a `mw-form` directly - the form brings the field gaps,
|
|
620
625
|
the body brings the padding.
|
|
621
626
|
- `mw-actions-note` is the muted line above the buttons - see below.
|
|
627
|
+
- Name it with `aria-labelledby` pointing at the `mw-modal-title`; `role` and
|
|
628
|
+
`aria-modal` come from `showModal()`. Without `autofocus` the browser focuses
|
|
629
|
+
the first focusable element, usually the close button.
|
|
622
630
|
|
|
623
|
-
Angular:
|
|
631
|
+
Angular: `[open]="isOpen()"` does **not** work - the attribute opens a non-modal
|
|
632
|
+
dialog with no top layer, backdrop or focus trap. It takes a directive calling
|
|
633
|
+
`showModal()`, see `examples/angular-services.md`.
|
|
624
634
|
|
|
625
|
-
### The
|
|
635
|
+
### The older overlay div
|
|
626
636
|
|
|
627
|
-
|
|
637
|
+
Still styled, for markup that predates the `<dialog>` above. It cannot trap
|
|
638
|
+
focus or make the page inert, so it is not what to write now.
|
|
628
639
|
|
|
629
640
|
```html
|
|
630
|
-
<
|
|
631
|
-
<div class="mw-modal-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
𝗫
|
|
635
|
-
</button>
|
|
636
|
-
</div>
|
|
637
|
-
<div class="mw-modal-body">…</div>
|
|
638
|
-
<div class="mw-modal-footer">…</div>
|
|
639
|
-
</dialog>
|
|
641
|
+
<div id="delete-modal" class="mw-modal-overlay">
|
|
642
|
+
<div class="mw-modal mw-modal-sm">…</div>
|
|
643
|
+
<div class="mw-modal-backdrop"></div>
|
|
644
|
+
</div>
|
|
640
645
|
```
|
|
641
646
|
|
|
642
|
-
-
|
|
643
|
-
|
|
644
|
-
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
- `closedby="any"` dismisses it on a backdrop click; where that attribute is not
|
|
648
|
-
understood the script handles the click instead.
|
|
649
|
-
- Sizes, the bottom sheet below 576px and every `mw-modal-*` part behave exactly
|
|
650
|
-
as above - those rules are class-based.
|
|
651
|
-
|
|
652
|
-
Angular: bind nothing, call `showModal()` on a `viewChild` ref.
|
|
647
|
+
- `display: none` until `mw-modal-open` is added to the overlay - that class is
|
|
648
|
+
the whole open/close mechanism, in Angular
|
|
649
|
+
`[class.mw-modal-open]="isOpen()"`.
|
|
650
|
+
- Body scroll lock comes from `body:has(.mw-modal-open)`.
|
|
651
|
+
- `mw-modal-backdrop` is the click-to-close surface; put the handler on it.
|
|
653
652
|
|
|
654
653
|
## Alerts & toasts
|
|
655
654
|
|
|
@@ -29,7 +29,7 @@ classes are the entire contract.
|
|
|
29
29
|
| ------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
30
30
|
| Accordion | Toggles `mw-active` on `mw-accordion-header` and the following `mw-accordion-content`, and writes `aria-expanded` when the header is a `<button>` | `[class.mw-active]="isOpen()"` and `[attr.aria-expanded]="isOpen()"` on the header, `mw-active` on the panel |
|
|
31
31
|
| Tabs | `data-tab` → panel `id`; sets `mw-active` on nav item and panel, and wires the whole tablist: `role`, `aria-selected`, `aria-controls`, `aria-labelledby`, a roving `tabindex` and arrow/Home/End keys | Track the selected index/key, bind `mw-active` on both; drop `data-tab` |
|
|
32
|
-
| Modal |
|
|
32
|
+
| Modal | `data-mw-modal="<id>"` on a trigger opens that modal; a click on `mw-modal-close` closes the `<dialog>` it sits in, or clears `mw-modal-open` on an overlay div | A `<dialog>` needs a directive calling `showModal()` - `[open]` only opens it non-modally. See `examples/angular-services.md` |
|
|
33
33
|
| Mobile nav | Toggles `open` on `mw-menu-btn` and `mw-navbar`, writes `aria-expanded` when the button is a `<button>`, closes on anchor click and on Escape (focus returns to the button) | One signal, bound to both; reset it on navigation end |
|
|
34
34
|
| Scroll spy | Sets `mw-active` on `mw-navbar-link` from the scroll position | Router-based: `routerLinkActive="mw-active"` |
|
|
35
35
|
| Anchor scrolling | Intercepts `a[href^="#"]` and runs its own eased scroll - duration scales with distance, capped at 1.4s, cancelled by wheel or touch. Lands on `scroll-padding-top`, moves focus to the target, writes the hash with `replaceState`, and measures a sticky target unpinned | The router; for in-page anchors `scrollIntoView({ behavior: 'smooth' })` or your own animation |
|
|
@@ -69,10 +69,10 @@ difference, not a missing effect.
|
|
|
69
69
|
## Modals and progress bars
|
|
70
70
|
|
|
71
71
|
`mwOpenModal(id)` / `mwCloseModal(id)` are on `window` and handle both modal
|
|
72
|
-
shapes -
|
|
73
|
-
buttons and `data-mw-modal="<id>"` triggers are delegated from the
|
|
74
|
-
markup rendered later still works
|
|
75
|
-
`showModal()` and `close()` on the element.
|
|
72
|
+
shapes - a `<dialog class="mw-modal">` and the older `mw-modal-overlay` div.
|
|
73
|
+
Close buttons and `data-mw-modal="<id>"` triggers are delegated from the
|
|
74
|
+
document, so markup rendered later still works and a static page needs no code
|
|
75
|
+
of its own. In a SPA, call `showModal()` and `close()` on the element instead.
|
|
76
76
|
|
|
77
77
|
`mw-progress-fill` takes its target width from `data-value="75"` or an inline
|
|
78
78
|
`style="width: 75%"`. Where scroll-driven animations are supported the bar fills
|
|
@@ -93,8 +93,8 @@ other spelling exists.
|
|
|
93
93
|
Pure CSS, nothing to wire up: hover, focus and press states, the card lift,
|
|
94
94
|
tooltips (`data-tooltip`), `mw-rating` (via `data-rating`), the responsive table
|
|
95
95
|
card view (`data-label`), all grids and utilities, the body scroll lock while a
|
|
96
|
-
modal is open (`body:has(.mw-modal-open)`
|
|
97
|
-
sheet below 576px, toast entry animations, the sticky table header, the scroll
|
|
96
|
+
modal is open (from the `<dialog>` element, or `body:has(.mw-modal-open)` for an
|
|
97
|
+
overlay div), the modal turning into a bottom sheet below 576px, toast entry animations, the sticky table header, the scroll
|
|
98
98
|
hint on a tab bar (four gradients, no scroll listener), the kanban empty-lane
|
|
99
99
|
placeholder (hidden via `:has()` as soon as the lane holds a ticket), touch
|
|
100
100
|
target sizing on a coarse pointer, `prefers-reduced-motion` handling.
|
|
@@ -108,17 +108,16 @@ still a working menu, not a broken one.
|
|
|
108
108
|
## When you do keep the shipped JS
|
|
109
109
|
|
|
110
110
|
For a static page, a landing page or a server-rendered site (Thymeleaf, Twig,
|
|
111
|
-
Jekyll, plain HTML) it is exactly right - load it at the end of `<body
|
|
112
|
-
|
|
111
|
+
Jekyll, plain HTML) it is exactly right - load it at the end of `<body>` and
|
|
112
|
+
write no JavaScript at all. A modal opens from its trigger:
|
|
113
113
|
|
|
114
114
|
```html
|
|
115
|
+
<button class="mw-btn mw-btn-primary" data-mw-modal="demo">Book a demo</button>
|
|
116
|
+
|
|
117
|
+
<dialog id="demo" class="mw-modal" closedby="any">
|
|
118
|
+
…
|
|
119
|
+
<button class="mw-modal-close mw-btn mw-btn-outline">Cancel</button>
|
|
120
|
+
</dialog>
|
|
121
|
+
|
|
115
122
|
<script src="maverick-wave.min.js"></script>
|
|
116
|
-
<script>
|
|
117
|
-
function openModal(id) {
|
|
118
|
-
document.getElementById(id).classList.add('mw-modal-open');
|
|
119
|
-
}
|
|
120
|
-
function closeModal(id) {
|
|
121
|
-
document.getElementById(id).classList.remove('mw-modal-open');
|
|
122
|
-
}
|
|
123
|
-
</script>
|
|
124
123
|
```
|
package/CHANGELOG.md
CHANGED
|
@@ -6,6 +6,13 @@ Patch releases are only for test purposes - here I only document major and minor
|
|
|
6
6
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
7
7
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
8
8
|
|
|
9
|
+
## [5.17.0] - 2026-09-17
|
|
10
|
+
|
|
11
|
+
### Fixed
|
|
12
|
+
|
|
13
|
+
- tooltips
|
|
14
|
+
- modals use dialog
|
|
15
|
+
|
|
9
16
|
## [5.16.0] - 2026-09-15
|
|
10
17
|
|
|
11
18
|
### Added
|
package/README.md
CHANGED
|
@@ -45,12 +45,12 @@ The result is a framework that balances utility with simplicity, offering develo
|
|
|
45
45
|
<title>My MaverickWave Project</title>
|
|
46
46
|
<link
|
|
47
47
|
rel="stylesheet"
|
|
48
|
-
href="https://cdn.jsdelivr.net/npm/maverick-wave@5.
|
|
48
|
+
href="https://cdn.jsdelivr.net/npm/maverick-wave@5.17.0/maverick-wave.min.css"
|
|
49
49
|
/>
|
|
50
50
|
</head>
|
|
51
51
|
<body>
|
|
52
52
|
<!-- Your content here -->
|
|
53
|
-
<script src="https://cdn.jsdelivr.net/npm/maverick-wave@5.
|
|
53
|
+
<script src="https://cdn.jsdelivr.net/npm/maverick-wave@5.17.0/maverick-wave.min.js"></script>
|
|
54
54
|
</body>
|
|
55
55
|
</html>
|
|
56
56
|
```
|
package/index.html
CHANGED
|
@@ -1605,10 +1605,6 @@
|
|
|
1605
1605
|
}, 1600);
|
|
1606
1606
|
});
|
|
1607
1607
|
});
|
|
1608
|
-
|
|
1609
|
-
// for the modals - the framework handles both shapes, div and <dialog>
|
|
1610
|
-
const openModal = window.mwOpenModal;
|
|
1611
|
-
const closeModal = window.mwCloseModal;
|
|
1612
1608
|
</script>
|
|
1613
1609
|
</body>
|
|
1614
1610
|
</html>
|