@wildmason/aegis 2.1.0 → 2.2.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/CHANGELOG.md +301 -1
- package/dist/fesm2022/wildmason-aegis.mjs +589 -40
- package/dist/fesm2022/wildmason-aegis.mjs.map +1 -1
- package/dist/types/wildmason-aegis.d.ts +371 -24
- package/inputs.css +85 -6
- package/package.json +6 -2
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { CdkTrapFocus } from '@angular/cdk/a11y';
|
|
2
2
|
import * as i0 from '@angular/core';
|
|
3
|
-
import { input, output, HostListener, ChangeDetectionStrategy, Component, model, signal, computed, inject, ElementRef, Renderer2, ChangeDetectorRef, viewChild, effect, ViewEncapsulation, Input, Directive, contentChildren, forwardRef, ViewChild, Injectable, Injector, DestroyRef, TemplateRef, contentChild } from '@angular/core';
|
|
3
|
+
import { input, output, HostListener, ChangeDetectionStrategy, Component, model, signal, computed, inject, ElementRef, Renderer2, ChangeDetectorRef, viewChild, effect, ViewEncapsulation, Input, Directive, contentChildren, booleanAttribute, forwardRef, HostAttributeToken, linkedSignal, ViewChild, Injectable, Injector, DestroyRef, TemplateRef, contentChild } from '@angular/core';
|
|
4
4
|
import { CdkConnectedOverlay, CdkOverlayOrigin, createFlexibleConnectedPositionStrategy, createOverlayRef, createRepositionScrollStrategy } from '@angular/cdk/overlay';
|
|
5
5
|
import { NgTemplateOutlet } from '@angular/common';
|
|
6
6
|
import { NG_VALUE_ACCESSOR } from '@angular/forms';
|
|
@@ -14,7 +14,16 @@ import { ComponentPortal } from '@angular/cdk/portal';
|
|
|
14
14
|
*
|
|
15
15
|
* Owns the fixed backdrop, centering, keyboard/pointer dismissal AND the focus trap,
|
|
16
16
|
* so individual modal components don't need to repeat this logic or add their own
|
|
17
|
-
* HostListeners.
|
|
17
|
+
* HostListeners. The consumer supplies the whole panel: its surface, size, header
|
|
18
|
+
* and close control.
|
|
19
|
+
*
|
|
20
|
+
* ## wm-modal or wm-dialog
|
|
21
|
+
*
|
|
22
|
+
* Reach for `wm-dialog` first. It is a complete dialog — header, title wired to
|
|
23
|
+
* `aria-labelledby`, close button, padded scrolling body — and it is not limited to
|
|
24
|
+
* its `size` presets: `--wm-dialog-width` sets any width (Helm uses up to 960px).
|
|
25
|
+
* Use `wm-modal` when that chrome is wrong for the surface: a two-pane browser, a
|
|
26
|
+
* command palette, a panel with its own toolbar or its own scroll regions.
|
|
18
27
|
*
|
|
19
28
|
* ## Focus is trapped here, and it has to be
|
|
20
29
|
*
|
|
@@ -33,8 +42,9 @@ import { ComponentPortal } from '@angular/cdk/portal';
|
|
|
33
42
|
*
|
|
34
43
|
* Usage:
|
|
35
44
|
* <wm-modal [visible]="visible()" (dismiss)="close()">
|
|
36
|
-
* <div class="
|
|
37
|
-
*
|
|
45
|
+
* <div class="panel" role="dialog" aria-modal="true" aria-labelledby="panel-title">
|
|
46
|
+
* <h2 id="panel-title">Browse snapshot</h2>
|
|
47
|
+
* <!-- modal panel content, including a visible close control -->
|
|
38
48
|
* </div>
|
|
39
49
|
* </wm-modal>
|
|
40
50
|
*
|
|
@@ -43,6 +53,11 @@ import { ComponentPortal } from '@angular/cdk/portal';
|
|
|
43
53
|
*
|
|
44
54
|
* The projected content is wrapped in a stop-propagation container so clicks inside
|
|
45
55
|
* the panel do not bubble up to the backdrop and trigger a dismiss.
|
|
56
|
+
*
|
|
57
|
+
* The backdrop's layout lives in this component's stylesheet, so it needs no utility
|
|
58
|
+
* framework in the consumer. Its colour stays an inline
|
|
59
|
+
* `background: var(--wm-modal-backdrop)`, because consumers detect an open modal
|
|
60
|
+
* with `[style*="--wm-modal-backdrop"]`.
|
|
46
61
|
*/
|
|
47
62
|
class WmModal {
|
|
48
63
|
/** Whether the modal is currently open. */
|
|
@@ -51,8 +66,8 @@ class WmModal {
|
|
|
51
66
|
dismiss = output();
|
|
52
67
|
/**
|
|
53
68
|
* Vertical alignment of the panel within the backdrop.
|
|
54
|
-
* - 'center' (default): panel is
|
|
55
|
-
* - 'top': panel
|
|
69
|
+
* - 'center' (default): panel is centered on both axes
|
|
70
|
+
* - 'top': panel sits 5rem below the top edge, for search/command overlays
|
|
56
71
|
*/
|
|
57
72
|
align = input('center', ...(ngDevMode ? [{ debugName: "align" }] : /* istanbul ignore next */ []));
|
|
58
73
|
onEscape() {
|
|
@@ -60,11 +75,11 @@ class WmModal {
|
|
|
60
75
|
this.dismiss.emit();
|
|
61
76
|
}
|
|
62
77
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.23", ngImport: i0, type: WmModal, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
63
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.23", type: WmModal, isStandalone: true, selector: "wm-modal", inputs: { visible: { classPropertyName: "visible", publicName: "visible", isSignal: true, isRequired: true, transformFunction: null }, align: { classPropertyName: "align", publicName: "align", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { dismiss: "dismiss" }, host: { listeners: { "document:keydown.escape": "onEscape()" } }, ngImport: i0, template: "@if (visible()) {\n
|
|
78
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.23", type: WmModal, isStandalone: true, selector: "wm-modal", inputs: { visible: { classPropertyName: "visible", publicName: "visible", isSignal: true, isRequired: true, transformFunction: null }, align: { classPropertyName: "align", publicName: "align", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { dismiss: "dismiss" }, host: { listeners: { "document:keydown.escape": "onEscape()" } }, ngImport: i0, template: "@if (visible()) {\n <!-- The inline background is a contract, not a leftover: consumers detect an open\n modal with `[style*=\"--wm-modal-backdrop\"]`, so it must stay on this element. -->\n <div class=\"wm-modal-backdrop\"\n [class.wm-modal-backdrop--top]=\"align() === 'top'\"\n style=\"background: var(--wm-modal-backdrop)\"\n (click)=\"dismiss.emit()\">\n <!-- The trap must sit on an element this template owns, inside this `@if`, so it\n has a parentNode by the time CdkTrapFocus runs ngAfterContentInit. A consumer\n cannot achieve that on projected content \u2014 see the class doc. -->\n <div (click)=\"$event.stopPropagation()\" cdkTrapFocus [cdkTrapFocusAutoCapture]=\"true\">\n <ng-content />\n </div>\n </div>\n}\n", styles: [".wm-modal-backdrop{position:fixed;inset:0;z-index:50;display:flex;align-items:center;justify-content:center}.wm-modal-backdrop--top{align-items:flex-start;padding-top:5rem}\n"], dependencies: [{ kind: "directive", type: CdkTrapFocus, selector: "[cdkTrapFocus]", inputs: ["cdkTrapFocus", "cdkTrapFocusAutoCapture"], exportAs: ["cdkTrapFocus"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
64
79
|
}
|
|
65
80
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.23", ngImport: i0, type: WmModal, decorators: [{
|
|
66
81
|
type: Component,
|
|
67
|
-
args: [{ selector: 'wm-modal', standalone: true, changeDetection: ChangeDetectionStrategy.OnPush, imports: [CdkTrapFocus], template: "@if (visible()) {\n
|
|
82
|
+
args: [{ selector: 'wm-modal', standalone: true, changeDetection: ChangeDetectionStrategy.OnPush, imports: [CdkTrapFocus], template: "@if (visible()) {\n <!-- The inline background is a contract, not a leftover: consumers detect an open\n modal with `[style*=\"--wm-modal-backdrop\"]`, so it must stay on this element. -->\n <div class=\"wm-modal-backdrop\"\n [class.wm-modal-backdrop--top]=\"align() === 'top'\"\n style=\"background: var(--wm-modal-backdrop)\"\n (click)=\"dismiss.emit()\">\n <!-- The trap must sit on an element this template owns, inside this `@if`, so it\n has a parentNode by the time CdkTrapFocus runs ngAfterContentInit. A consumer\n cannot achieve that on projected content \u2014 see the class doc. -->\n <div (click)=\"$event.stopPropagation()\" cdkTrapFocus [cdkTrapFocusAutoCapture]=\"true\">\n <ng-content />\n </div>\n </div>\n}\n", styles: [".wm-modal-backdrop{position:fixed;inset:0;z-index:50;display:flex;align-items:center;justify-content:center}.wm-modal-backdrop--top{align-items:flex-start;padding-top:5rem}\n"] }]
|
|
68
83
|
}], propDecorators: { visible: [{ type: i0.Input, args: [{ isSignal: true, alias: "visible", required: true }] }], dismiss: [{ type: i0.Output, args: ["dismiss"] }], align: [{ type: i0.Input, args: [{ isSignal: true, alias: "align", required: false }] }], onEscape: [{
|
|
69
84
|
type: HostListener,
|
|
70
85
|
args: ['document:keydown.escape']
|
|
@@ -88,21 +103,36 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.23", ngImpo
|
|
|
88
103
|
* </div>
|
|
89
104
|
* </wm-dialog>
|
|
90
105
|
*
|
|
106
|
+
* Width:
|
|
107
|
+
* `size` picks a preset (sm 360px, md 480px, lg 640px). For any other width, set the
|
|
108
|
+
* `--wm-dialog-width` custom property on the host; it overrides every preset:
|
|
109
|
+
* <wm-dialog [(isOpen)]="open" title="Line history" style="--wm-dialog-width: 800px">
|
|
110
|
+
* Fluid values work too, e.g. `min(90vw, 48rem)`. Whatever the width, the panel is
|
|
111
|
+
* capped at `calc(100vw - 2rem)` wide and `calc(100dvh - 4rem)` tall.
|
|
112
|
+
*
|
|
113
|
+
* When the dialog chrome itself is wrong for the surface (a two-pane browser, a
|
|
114
|
+
* command palette, a panel with its own toolbar), use `wm-modal` with your own panel.
|
|
115
|
+
*
|
|
91
116
|
* Accessibility:
|
|
92
117
|
* - role="dialog" + aria-modal="true" on the panel
|
|
93
118
|
* - aria-labelledby wired to the title element (when title is set)
|
|
94
|
-
* - Focus is trapped inside the panel when open (cdkTrapFocus)
|
|
119
|
+
* - Focus is trapped inside the panel when open (cdkTrapFocus). Do not add a
|
|
120
|
+
* cdkTrapFocus of your own inside the body; it would nest a second trap.
|
|
95
121
|
* - Escape key closes the dialog
|
|
96
122
|
* - Clicking the backdrop closes the dialog
|
|
97
|
-
* - On close, focus returns to the element that
|
|
98
|
-
* (
|
|
123
|
+
* - On close, focus returns to the element that had it when the dialog opened
|
|
124
|
+
* (cdkTrapFocusAutoCapture records that element and refocuses it when the
|
|
125
|
+
* trap is destroyed)
|
|
99
126
|
*/
|
|
100
127
|
class WmDialog {
|
|
101
128
|
/** Two-way binding for open/closed state. */
|
|
102
129
|
isOpen = model(false, ...(ngDevMode ? [{ debugName: "isOpen" }] : /* istanbul ignore next */ []));
|
|
103
130
|
/** Dialog heading text. When set, aria-labelledby is applied automatically. */
|
|
104
131
|
title = input('', ...(ngDevMode ? [{ debugName: "title" }] : /* istanbul ignore next */ []));
|
|
105
|
-
/**
|
|
132
|
+
/**
|
|
133
|
+
* Panel width preset. sm = 360px, md = 480px (default), lg = 640px.
|
|
134
|
+
* `--wm-dialog-width` on the host overrides every preset.
|
|
135
|
+
*/
|
|
106
136
|
size = input('md', ...(ngDevMode ? [{ debugName: "size" }] : /* istanbul ignore next */ []));
|
|
107
137
|
close() { this.isOpen.set(false); }
|
|
108
138
|
onEscape() {
|
|
@@ -302,8 +332,14 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.23", ngImpo
|
|
|
302
332
|
* backdrop, focus-trapping, and ARIA attribute management.
|
|
303
333
|
*
|
|
304
334
|
* Slots:
|
|
305
|
-
* - [trigger] — the anchor element
|
|
306
|
-
*
|
|
335
|
+
* - [trigger] — the anchor element. Put the attribute on the focusable
|
|
336
|
+
* element itself, never on a wrapper or on a component host
|
|
337
|
+
* such as <wm-button>: the ARIA below goes to the element
|
|
338
|
+
* that carries it. For an Aegis-styled trigger, use
|
|
339
|
+
* `<button wmButton trigger>`. Nothing reads
|
|
340
|
+
* `data-popover-trigger`
|
|
341
|
+
* - [content] — the panel body (menu items, form, etc.). The panel carries
|
|
342
|
+
* the role, so the body takes no role of its own
|
|
307
343
|
*
|
|
308
344
|
* Usage:
|
|
309
345
|
* ```html
|
|
@@ -316,16 +352,26 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.23", ngImpo
|
|
|
316
352
|
* align="end"
|
|
317
353
|
* >
|
|
318
354
|
* <button trigger (click)="open.set(!open())">Options</button>
|
|
319
|
-
* <div content
|
|
355
|
+
* <div content>
|
|
320
356
|
* <button role="menuitem" (click)="doThing()">Do thing</button>
|
|
321
357
|
* </div>
|
|
322
358
|
* </wm-popover>
|
|
323
359
|
* ```
|
|
324
360
|
*
|
|
325
361
|
* Accessibility:
|
|
326
|
-
* - Trigger element automatically receives aria-haspopup
|
|
327
|
-
*
|
|
362
|
+
* - Trigger element automatically receives aria-haspopup (from `hasPopup`),
|
|
363
|
+
* aria-expanded and aria-controls, plus role="button" when it has no role.
|
|
364
|
+
* They are rewritten on every change to `isOpen` or `hasPopup`, so the
|
|
365
|
+
* consumer does not bind them
|
|
366
|
+
* - Panel receives the configured role and optional accessible name. The
|
|
367
|
+
* role goes on the panel only: the host element, which wraps the trigger,
|
|
368
|
+
* never carries it, even when a consumer writes `role` as a static attribute
|
|
369
|
+
* - A `dialog` or `alertdialog` panel receives aria-modal="true" while it
|
|
370
|
+
* captures focus (see `trapFocusAutoCapture`)
|
|
328
371
|
* - Focus is trapped inside the open panel (cdkTrapFocus)
|
|
372
|
+
* - A modal panel whose content has nothing tabbable (text only, or a lone
|
|
373
|
+
* disabled button) takes focus itself (tabindex="-1") and keeps it on Tab,
|
|
374
|
+
* so focus is never left on the trigger that aria-modal makes inert
|
|
329
375
|
* - Escape key closes the panel
|
|
330
376
|
* - Backdrop click closes the panel
|
|
331
377
|
*
|
|
@@ -378,6 +424,30 @@ class WmPopover {
|
|
|
378
424
|
* auto-restore.
|
|
379
425
|
*/
|
|
380
426
|
trapFocusAutoCapture = input(true, ...(ngDevMode ? [{ debugName: "trapFocusAutoCapture" }] : /* istanbul ignore next */ []));
|
|
427
|
+
/**
|
|
428
|
+
* `"true"` for a `dialog` or `alertdialog` panel that captures focus, else
|
|
429
|
+
* no attribute.
|
|
430
|
+
*
|
|
431
|
+
* The panel opens over a click-catching backdrop, and a modal panel keeps
|
|
432
|
+
* focus inside itself (see `onAttach` and `onPanelKeydown`), so nothing
|
|
433
|
+
* outside it can be reached until it closes. A dialog that is modal must say
|
|
434
|
+
* so, or a screen reader keeps reading the page behind it. `aria-modal` is
|
|
435
|
+
* defined for those two roles only, so a menu or listbox panel gets no
|
|
436
|
+
* attribute. With `trapFocusAutoCapture` off, focus is meant to stay on the
|
|
437
|
+
* trigger outside the panel, and `aria-modal` would hide that focused
|
|
438
|
+
* trigger from a screen reader, so the panel is not declared modal.
|
|
439
|
+
*/
|
|
440
|
+
ariaModal = computed(() => {
|
|
441
|
+
const role = this.role();
|
|
442
|
+
const modalRole = role === 'dialog' || role === 'alertdialog';
|
|
443
|
+
return modalRole && this.trapFocusAutoCapture() ? 'true' : null;
|
|
444
|
+
}, ...(ngDevMode ? [{ debugName: "ariaModal" }] : /* istanbul ignore next */ []));
|
|
445
|
+
/**
|
|
446
|
+
* `-1` on a modal panel, so the panel itself can take focus when its content
|
|
447
|
+
* has nothing tabbable. A non-modal panel stays unfocusable: a click on it
|
|
448
|
+
* must not pull focus off a typeahead trigger input.
|
|
449
|
+
*/
|
|
450
|
+
panelTabIndex = computed(() => (this.ariaModal() ? -1 : null), ...(ngDevMode ? [{ debugName: "panelTabIndex" }] : /* istanbul ignore next */ []));
|
|
381
451
|
/** Emitted when the panel should close (backdrop click, Escape key, CDK detach). */
|
|
382
452
|
closed = output();
|
|
383
453
|
renderer = inject(Renderer2);
|
|
@@ -385,6 +455,7 @@ class WmPopover {
|
|
|
385
455
|
cdr = inject(ChangeDetectorRef);
|
|
386
456
|
panelId = 'wm-popover-' + Math.random().toString(36).substring(2, 9);
|
|
387
457
|
panel = viewChild('panel', ...(ngDevMode ? [{ debugName: "panel" }] : /* istanbul ignore next */ []));
|
|
458
|
+
trap = viewChild(CdkTrapFocus, ...(ngDevMode ? [{ debugName: "trap" }] : /* istanbul ignore next */ []));
|
|
388
459
|
constructor() {
|
|
389
460
|
effect(() => {
|
|
390
461
|
const open = this.isOpen();
|
|
@@ -408,14 +479,26 @@ class WmPopover {
|
|
|
408
479
|
{ originX: x, originY: 'top', overlayX: x, overlayY: 'bottom', offsetY: -4 },
|
|
409
480
|
];
|
|
410
481
|
}
|
|
482
|
+
/**
|
|
483
|
+
* Runs after the focus trap's own capture, which CDK defers to the next
|
|
484
|
+
* render. A `[wmAutoFocus]` element takes focus first. Otherwise a modal
|
|
485
|
+
* panel that the trap left without focus takes focus itself. The trap finds
|
|
486
|
+
* nothing to focus when the content has no tabbable element (text only, or a
|
|
487
|
+
* lone disabled button), and it then leaves focus on the trigger, which
|
|
488
|
+
* `aria-modal` has just made inert to a screen reader.
|
|
489
|
+
*/
|
|
411
490
|
onAttach() {
|
|
412
491
|
setTimeout(() => {
|
|
413
|
-
const panelEl = this.panel();
|
|
492
|
+
const panelEl = this.panel()?.nativeElement;
|
|
414
493
|
if (!panelEl)
|
|
415
494
|
return;
|
|
416
|
-
const el = panelEl.
|
|
417
|
-
if (!el)
|
|
495
|
+
const el = panelEl.querySelector('[wmAutoFocus]');
|
|
496
|
+
if (!el) {
|
|
497
|
+
if (this.ariaModal() && !panelEl.contains(panelEl.ownerDocument.activeElement)) {
|
|
498
|
+
panelEl.focus();
|
|
499
|
+
}
|
|
418
500
|
return;
|
|
501
|
+
}
|
|
419
502
|
el.focus();
|
|
420
503
|
if (typeof el.showPicker === 'function') {
|
|
421
504
|
const type = el.type;
|
|
@@ -428,16 +511,44 @@ class WmPopover {
|
|
|
428
511
|
}
|
|
429
512
|
}, 10);
|
|
430
513
|
}
|
|
514
|
+
/**
|
|
515
|
+
* Tab and Shift+Tab while focus is on a modal panel itself, not on a control
|
|
516
|
+
* inside it. Focus moves to the first or the last tabbable control. With none,
|
|
517
|
+
* focus stays on the panel. The trap's own boundary anchors cannot do this:
|
|
518
|
+
* with nothing tabbable inside, the anchor keeps focus, and the next Tab
|
|
519
|
+
* leaves the overlay for the page behind the backdrop.
|
|
520
|
+
*/
|
|
521
|
+
onPanelKeydown(event) {
|
|
522
|
+
if (event.key !== 'Tab' || !this.ariaModal())
|
|
523
|
+
return;
|
|
524
|
+
if (event.target !== this.panel()?.nativeElement)
|
|
525
|
+
return;
|
|
526
|
+
event.preventDefault();
|
|
527
|
+
const focusTrap = this.trap()?.focusTrap;
|
|
528
|
+
if (event.shiftKey) {
|
|
529
|
+
focusTrap?.focusLastTabbableElement();
|
|
530
|
+
}
|
|
531
|
+
else {
|
|
532
|
+
focusTrap?.focusFirstTabbableElement();
|
|
533
|
+
}
|
|
534
|
+
}
|
|
431
535
|
onClose() {
|
|
432
536
|
this.closed.emit();
|
|
433
537
|
}
|
|
434
538
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.23", ngImport: i0, type: WmPopover, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
435
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.2.0", version: "21.2.23", type: WmPopover, isStandalone: true, selector: "wm-popover", inputs: { isOpen: { classPropertyName: "isOpen", publicName: "isOpen", isSignal: true, isRequired: false, transformFunction: null }, width: { classPropertyName: "width", publicName: "width", isSignal: true, isRequired: false, transformFunction: null }, role: { classPropertyName: "role", publicName: "role", isSignal: true, isRequired: false, transformFunction: null }, ariaLabel: { classPropertyName: "ariaLabel", publicName: "ariaLabel", isSignal: true, isRequired: false, transformFunction: null }, hasPopup: { classPropertyName: "hasPopup", publicName: "hasPopup", isSignal: true, isRequired: false, transformFunction: null }, align: { classPropertyName: "align", publicName: "align", isSignal: true, isRequired: false, transformFunction: null }, trapFocusAutoCapture: { classPropertyName: "trapFocusAutoCapture", publicName: "trapFocusAutoCapture", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { closed: "closed" }, viewQueries: [{ propertyName: "panel", first: true, predicate: ["panel"], descendants: true, isSignal: true }], ngImport: i0, template: "<!-- Trigger anchor -->\n<div cdkOverlayOrigin #origin=\"cdkOverlayOrigin\" class=\"wm-popover-trigger\">\n <ng-content select=\"[trigger]\"></ng-content>\n</div>\n\n<!-- Floating panel -->\n<ng-template\n cdkConnectedOverlay\n [cdkConnectedOverlayOrigin]=\"origin\"\n [cdkConnectedOverlayOpen]=\"isOpen()\"\n [cdkConnectedOverlayPositions]=\"positions\"\n [cdkConnectedOverlayScrollStrategy]=\"scrollStrategy\"\n [cdkConnectedOverlayHasBackdrop]=\"true\"\n cdkConnectedOverlayBackdropClass=\"cdk-overlay-transparent-backdrop\"\n (backdropClick)=\"onClose()\"\n (detach)=\"onClose()\"\n (attach)=\"onAttach()\"\n>\n <div\n #panel\n [id]=\"panelId\"\n [attr.role]=\"role()\"\n [attr.aria-label]=\"ariaLabel()\"\n class=\"wm-popover-panel\"\n [style.width]=\"width() === 'auto' ? 'auto' : width() + 'px'\"\n cdkTrapFocus\n [cdkTrapFocusAutoCapture]=\"trapFocusAutoCapture()\"\n (keydown.escape)=\"onClose()\"\n >\n <ng-content select=\"[content]\"></ng-content>\n </div>\n</ng-template>\n", styles: ["wm-popover{display:inline-flex;align-items:center}.wm-popover-trigger{display:block;width:100%}.wm-popover-panel{background:var(--wm-bg-raised);border:1px solid var(--wm-border);border-radius:var(--wm-radius-lg);box-shadow:0 4px 6px -1px #00000014,0 10px 20px -4px #00000024;overflow:hidden;outline:none}\n"], dependencies: [{ kind: "directive", type: CdkConnectedOverlay, selector: "[cdk-connected-overlay], [connected-overlay], [cdkConnectedOverlay]", inputs: ["cdkConnectedOverlayOrigin", "cdkConnectedOverlayPositions", "cdkConnectedOverlayPositionStrategy", "cdkConnectedOverlayOffsetX", "cdkConnectedOverlayOffsetY", "cdkConnectedOverlayWidth", "cdkConnectedOverlayHeight", "cdkConnectedOverlayMinWidth", "cdkConnectedOverlayMinHeight", "cdkConnectedOverlayBackdropClass", "cdkConnectedOverlayPanelClass", "cdkConnectedOverlayViewportMargin", "cdkConnectedOverlayScrollStrategy", "cdkConnectedOverlayOpen", "cdkConnectedOverlayDisableClose", "cdkConnectedOverlayTransformOriginOn", "cdkConnectedOverlayHasBackdrop", "cdkConnectedOverlayLockPosition", "cdkConnectedOverlayFlexibleDimensions", "cdkConnectedOverlayGrowAfterOpen", "cdkConnectedOverlayPush", "cdkConnectedOverlayDisposeOnNavigation", "cdkConnectedOverlayUsePopover", "cdkConnectedOverlayMatchWidth", "cdkConnectedOverlay"], outputs: ["backdropClick", "positionChange", "attach", "detach", "overlayKeydown", "overlayOutsideClick"], exportAs: ["cdkConnectedOverlay"] }, { kind: "directive", type: CdkOverlayOrigin, selector: "[cdk-overlay-origin], [overlay-origin], [cdkOverlayOrigin]", exportAs: ["cdkOverlayOrigin"] }, { kind: "directive", type: CdkTrapFocus, selector: "[cdkTrapFocus]", inputs: ["cdkTrapFocus", "cdkTrapFocusAutoCapture"], exportAs: ["cdkTrapFocus"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
|
|
539
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.2.0", version: "21.2.23", type: WmPopover, isStandalone: true, selector: "wm-popover", inputs: { isOpen: { classPropertyName: "isOpen", publicName: "isOpen", isSignal: true, isRequired: false, transformFunction: null }, width: { classPropertyName: "width", publicName: "width", isSignal: true, isRequired: false, transformFunction: null }, role: { classPropertyName: "role", publicName: "role", isSignal: true, isRequired: false, transformFunction: null }, ariaLabel: { classPropertyName: "ariaLabel", publicName: "ariaLabel", isSignal: true, isRequired: false, transformFunction: null }, hasPopup: { classPropertyName: "hasPopup", publicName: "hasPopup", isSignal: true, isRequired: false, transformFunction: null }, align: { classPropertyName: "align", publicName: "align", isSignal: true, isRequired: false, transformFunction: null }, trapFocusAutoCapture: { classPropertyName: "trapFocusAutoCapture", publicName: "trapFocusAutoCapture", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { closed: "closed" }, host: { properties: { "attr.role": "null" } }, viewQueries: [{ propertyName: "panel", first: true, predicate: ["panel"], descendants: true, isSignal: true }, { propertyName: "trap", first: true, predicate: CdkTrapFocus, descendants: true, isSignal: true }], ngImport: i0, template: "<!-- Trigger anchor -->\n<div cdkOverlayOrigin #origin=\"cdkOverlayOrigin\" class=\"wm-popover-trigger\">\n <ng-content select=\"[trigger]\"></ng-content>\n</div>\n\n<!-- Floating panel -->\n<ng-template\n cdkConnectedOverlay\n [cdkConnectedOverlayOrigin]=\"origin\"\n [cdkConnectedOverlayOpen]=\"isOpen()\"\n [cdkConnectedOverlayPositions]=\"positions\"\n [cdkConnectedOverlayScrollStrategy]=\"scrollStrategy\"\n [cdkConnectedOverlayHasBackdrop]=\"true\"\n cdkConnectedOverlayBackdropClass=\"cdk-overlay-transparent-backdrop\"\n (backdropClick)=\"onClose()\"\n (detach)=\"onClose()\"\n (attach)=\"onAttach()\"\n>\n <div\n #panel\n [id]=\"panelId\"\n [attr.role]=\"role()\"\n [attr.aria-modal]=\"ariaModal()\"\n [attr.aria-label]=\"ariaLabel()\"\n [attr.tabindex]=\"panelTabIndex()\"\n class=\"wm-popover-panel\"\n [style.width]=\"width() === 'auto' ? 'auto' : width() + 'px'\"\n cdkTrapFocus\n [cdkTrapFocusAutoCapture]=\"trapFocusAutoCapture()\"\n (keydown.escape)=\"onClose()\"\n (keydown)=\"onPanelKeydown($event)\"\n >\n <ng-content select=\"[content]\"></ng-content>\n </div>\n</ng-template>\n", styles: ["wm-popover{display:inline-flex;align-items:center}.wm-popover-trigger{display:block;width:100%}.wm-popover-panel{background:var(--wm-bg-raised);border:1px solid var(--wm-border);border-radius:var(--wm-radius-lg);box-shadow:0 4px 6px -1px #00000014,0 10px 20px -4px #00000024;overflow:hidden;outline:none}\n"], dependencies: [{ kind: "directive", type: CdkConnectedOverlay, selector: "[cdk-connected-overlay], [connected-overlay], [cdkConnectedOverlay]", inputs: ["cdkConnectedOverlayOrigin", "cdkConnectedOverlayPositions", "cdkConnectedOverlayPositionStrategy", "cdkConnectedOverlayOffsetX", "cdkConnectedOverlayOffsetY", "cdkConnectedOverlayWidth", "cdkConnectedOverlayHeight", "cdkConnectedOverlayMinWidth", "cdkConnectedOverlayMinHeight", "cdkConnectedOverlayBackdropClass", "cdkConnectedOverlayPanelClass", "cdkConnectedOverlayViewportMargin", "cdkConnectedOverlayScrollStrategy", "cdkConnectedOverlayOpen", "cdkConnectedOverlayDisableClose", "cdkConnectedOverlayTransformOriginOn", "cdkConnectedOverlayHasBackdrop", "cdkConnectedOverlayLockPosition", "cdkConnectedOverlayFlexibleDimensions", "cdkConnectedOverlayGrowAfterOpen", "cdkConnectedOverlayPush", "cdkConnectedOverlayDisposeOnNavigation", "cdkConnectedOverlayUsePopover", "cdkConnectedOverlayMatchWidth", "cdkConnectedOverlay"], outputs: ["backdropClick", "positionChange", "attach", "detach", "overlayKeydown", "overlayOutsideClick"], exportAs: ["cdkConnectedOverlay"] }, { kind: "directive", type: CdkOverlayOrigin, selector: "[cdk-overlay-origin], [overlay-origin], [cdkOverlayOrigin]", exportAs: ["cdkOverlayOrigin"] }, { kind: "directive", type: CdkTrapFocus, selector: "[cdkTrapFocus]", inputs: ["cdkTrapFocus", "cdkTrapFocusAutoCapture"], exportAs: ["cdkTrapFocus"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
|
|
436
540
|
}
|
|
437
541
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.23", ngImport: i0, type: WmPopover, decorators: [{
|
|
438
542
|
type: Component,
|
|
439
|
-
args: [{ selector: 'wm-popover', imports: [CdkConnectedOverlay, CdkOverlayOrigin, CdkTrapFocus], changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None,
|
|
440
|
-
|
|
543
|
+
args: [{ selector: 'wm-popover', imports: [CdkConnectedOverlay, CdkOverlayOrigin, CdkTrapFocus], changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, host: {
|
|
544
|
+
// A consumer writes `role="dialog"` as a plain attribute, and Angular
|
|
545
|
+
// copies a static attribute onto the host element as well as into the
|
|
546
|
+
// `role` input. The host wraps only the trigger, so that copy would put
|
|
547
|
+
// the trigger inside a second, unnamed dialog, or inside a menu that owns
|
|
548
|
+
// no menuitem. The role belongs to the panel alone.
|
|
549
|
+
'[attr.role]': 'null',
|
|
550
|
+
}, template: "<!-- Trigger anchor -->\n<div cdkOverlayOrigin #origin=\"cdkOverlayOrigin\" class=\"wm-popover-trigger\">\n <ng-content select=\"[trigger]\"></ng-content>\n</div>\n\n<!-- Floating panel -->\n<ng-template\n cdkConnectedOverlay\n [cdkConnectedOverlayOrigin]=\"origin\"\n [cdkConnectedOverlayOpen]=\"isOpen()\"\n [cdkConnectedOverlayPositions]=\"positions\"\n [cdkConnectedOverlayScrollStrategy]=\"scrollStrategy\"\n [cdkConnectedOverlayHasBackdrop]=\"true\"\n cdkConnectedOverlayBackdropClass=\"cdk-overlay-transparent-backdrop\"\n (backdropClick)=\"onClose()\"\n (detach)=\"onClose()\"\n (attach)=\"onAttach()\"\n>\n <div\n #panel\n [id]=\"panelId\"\n [attr.role]=\"role()\"\n [attr.aria-modal]=\"ariaModal()\"\n [attr.aria-label]=\"ariaLabel()\"\n [attr.tabindex]=\"panelTabIndex()\"\n class=\"wm-popover-panel\"\n [style.width]=\"width() === 'auto' ? 'auto' : width() + 'px'\"\n cdkTrapFocus\n [cdkTrapFocusAutoCapture]=\"trapFocusAutoCapture()\"\n (keydown.escape)=\"onClose()\"\n (keydown)=\"onPanelKeydown($event)\"\n >\n <ng-content select=\"[content]\"></ng-content>\n </div>\n</ng-template>\n", styles: ["wm-popover{display:inline-flex;align-items:center}.wm-popover-trigger{display:block;width:100%}.wm-popover-panel{background:var(--wm-bg-raised);border:1px solid var(--wm-border);border-radius:var(--wm-radius-lg);box-shadow:0 4px 6px -1px #00000014,0 10px 20px -4px #00000024;overflow:hidden;outline:none}\n"] }]
|
|
551
|
+
}], ctorParameters: () => [], propDecorators: { isOpen: [{ type: i0.Input, args: [{ isSignal: true, alias: "isOpen", required: false }] }], width: [{ type: i0.Input, args: [{ isSignal: true, alias: "width", required: false }] }], role: [{ type: i0.Input, args: [{ isSignal: true, alias: "role", required: false }] }], ariaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaLabel", required: false }] }], hasPopup: [{ type: i0.Input, args: [{ isSignal: true, alias: "hasPopup", required: false }] }], align: [{ type: i0.Input, args: [{ isSignal: true, alias: "align", required: false }] }], trapFocusAutoCapture: [{ type: i0.Input, args: [{ isSignal: true, alias: "trapFocusAutoCapture", required: false }] }], closed: [{ type: i0.Output, args: ["closed"] }], panel: [{ type: i0.ViewChild, args: ['panel', { isSignal: true }] }], trap: [{ type: i0.ViewChild, args: [i0.forwardRef(() => CdkTrapFocus), { isSignal: true }] }] } });
|
|
441
552
|
|
|
442
553
|
/**
|
|
443
554
|
* WmAutoFocus — focuses an element when it enters the DOM.
|
|
@@ -513,21 +624,85 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.23", ngImpo
|
|
|
513
624
|
}]
|
|
514
625
|
}], propDecorators: { value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: true }] }], unavailable: [{ type: i0.Input, args: [{ isSignal: true, alias: "unavailable", required: false }] }], unavailableReason: [{ type: i0.Input, args: [{ isSignal: true, alias: "unavailableReason", required: false }] }], separatorBefore: [{ type: i0.Input, args: [{ isSignal: true, alias: "separatorBefore", required: false }] }], tmpl: [{ type: i0.ViewChild, args: ['tmpl', { isSignal: true }] }] } });
|
|
515
626
|
|
|
627
|
+
/**
|
|
628
|
+
* WmTabs — an ARIA tablist of `<wm-tab>` children with a roving tab stop.
|
|
629
|
+
*
|
|
630
|
+
* Usage:
|
|
631
|
+
* <wm-tabs [activeValue]="tool()" ariaLabel="Navigator tools" (select)="tool.set($event)">
|
|
632
|
+
* <wm-tab value="graph">Graph</wm-tab>
|
|
633
|
+
* <wm-tab value="search">Search</wm-tab>
|
|
634
|
+
* </wm-tabs>
|
|
635
|
+
*
|
|
636
|
+
* Selection:
|
|
637
|
+
* - `activeValue` names the selected tab. `null`, or a value no tab carries,
|
|
638
|
+
* selects nothing: every tab reports aria-selected="false" and none paints
|
|
639
|
+
* as active. Use it when the stage shows something that is not any tab's
|
|
640
|
+
* panel, such as a document opened from a second tablist in the same row.
|
|
641
|
+
* - The tablist always keeps exactly one tab stop: the focused tab while
|
|
642
|
+
* focus is inside, otherwise the selected tab, otherwise the first tab that
|
|
643
|
+
* is not unavailable (the first tab when every tab is unavailable).
|
|
644
|
+
* - Arrow keys, Home and End move focus only. A tab is selected on click,
|
|
645
|
+
* Enter or Space, which emit `(select)`; the consumer sets `activeValue`.
|
|
646
|
+
*
|
|
647
|
+
* Accessibility:
|
|
648
|
+
* - `ariaLabel` or `ariaLabelledby` names the inner role="tablist". Name
|
|
649
|
+
* every tablist that shares a surface with another one, or assistive
|
|
650
|
+
* technology cannot tell them apart. `ariaLabelledby` wins when both are
|
|
651
|
+
* set. An `aria-label` attribute on the `<wm-tabs>` host names nothing: the
|
|
652
|
+
* host has no role, so the attribute never reaches the tablist.
|
|
653
|
+
* - Each tab is `id="wm-tab-{value}"` with `aria-controls="wm-panel-{value}"`.
|
|
654
|
+
* Give the panel that id and role="tabpanel". The ids are built from the
|
|
655
|
+
* value alone, so values must be unique across every tablist on the page.
|
|
656
|
+
*/
|
|
516
657
|
class WmTabs {
|
|
658
|
+
/**
|
|
659
|
+
* Value of the selected tab. `null`, or a value that matches no tab, selects
|
|
660
|
+
* nothing; the tablist still keeps one tab stop (see the class comment).
|
|
661
|
+
*/
|
|
517
662
|
activeValue = input.required(...(ngDevMode ? [{ debugName: "activeValue" }] : /* istanbul ignore next */ []));
|
|
518
663
|
variant = input('line', ...(ngDevMode ? [{ debugName: "variant" }] : /* istanbul ignore next */ []));
|
|
664
|
+
/** Accessible name for the tablist. Ignored while `ariaLabelledby` is set. */
|
|
665
|
+
ariaLabel = input(null, ...(ngDevMode ? [{ debugName: "ariaLabel" }] : /* istanbul ignore next */ []));
|
|
666
|
+
/**
|
|
667
|
+
* Id of a visible element that names the tablist. Prefer it over
|
|
668
|
+
* `ariaLabel` when a visible caption exists, so the accessible name matches
|
|
669
|
+
* the visible text (WCAG 2.5.3, Label in Name).
|
|
670
|
+
*/
|
|
671
|
+
ariaLabelledby = input(null, ...(ngDevMode ? [{ debugName: "ariaLabelledby" }] : /* istanbul ignore next */ []));
|
|
519
672
|
select = output();
|
|
520
673
|
tabs = contentChildren(WmTab, ...(ngDevMode ? [{ debugName: "tabs" }] : /* istanbul ignore next */ []));
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
674
|
+
/**
|
|
675
|
+
* Value of the tab that holds focus, or null while focus is outside the
|
|
676
|
+
* tablist. Keyed by value, not index, so a tab added or removed before it
|
|
677
|
+
* cannot move the stop onto a different tab or off the end of the list.
|
|
678
|
+
*/
|
|
679
|
+
focusedValue = signal(null, ...(ngDevMode ? [{ debugName: "focusedValue" }] : /* istanbul ignore next */ []));
|
|
680
|
+
/** Index of the one tab that is in the page's Tab order. */
|
|
681
|
+
tabStop = computed(() => {
|
|
682
|
+
const tabs = this.tabs();
|
|
683
|
+
const focused = this.focusedValue();
|
|
684
|
+
const focusedIndex = focused === null ? -1 : tabs.findIndex(t => t.value() === focused);
|
|
685
|
+
if (focusedIndex >= 0)
|
|
686
|
+
return focusedIndex;
|
|
687
|
+
const active = this.activeValue();
|
|
688
|
+
const activeIndex = active === null ? -1 : tabs.findIndex(t => t.value() === active);
|
|
689
|
+
if (activeIndex >= 0)
|
|
690
|
+
return activeIndex;
|
|
691
|
+
const firstAvailable = tabs.findIndex(t => !t.unavailable());
|
|
692
|
+
return firstAvailable >= 0 ? firstAvailable : 0;
|
|
693
|
+
}, ...(ngDevMode ? [{ debugName: "tabStop" }] : /* istanbul ignore next */ []));
|
|
694
|
+
host = inject(ElementRef);
|
|
695
|
+
selectTab(tab) {
|
|
696
|
+
this.focusedValue.set(tab.value());
|
|
524
697
|
if (!tab.unavailable())
|
|
525
698
|
this.select.emit(tab.value());
|
|
526
699
|
}
|
|
527
700
|
onBlur() {
|
|
528
701
|
setTimeout(() => {
|
|
529
|
-
|
|
530
|
-
|
|
702
|
+
// This tablist only. Focus that moved to a second <wm-tabs> must still
|
|
703
|
+
// return this one's stop to its selected tab.
|
|
704
|
+
if (!this.host.nativeElement.contains(document.activeElement)) {
|
|
705
|
+
this.focusedValue.set(null);
|
|
531
706
|
}
|
|
532
707
|
}, 0);
|
|
533
708
|
}
|
|
@@ -535,8 +710,7 @@ class WmTabs {
|
|
|
535
710
|
const tabs = this.tabs();
|
|
536
711
|
if (tabs.length === 0)
|
|
537
712
|
return;
|
|
538
|
-
const
|
|
539
|
-
const currentIndex = this.focusedIndex() ?? (activeIndex >= 0 ? activeIndex : 0);
|
|
713
|
+
const currentIndex = this.tabStop();
|
|
540
714
|
let nextIndex = null;
|
|
541
715
|
switch (event.key) {
|
|
542
716
|
case 'ArrowRight':
|
|
@@ -556,19 +730,43 @@ class WmTabs {
|
|
|
556
730
|
}
|
|
557
731
|
if (nextIndex !== null) {
|
|
558
732
|
event.preventDefault();
|
|
559
|
-
this.
|
|
733
|
+
this.focusedValue.set(tabs[nextIndex].value());
|
|
560
734
|
const btns = container.querySelectorAll('[role="tab"]');
|
|
561
735
|
btns[nextIndex]?.focus();
|
|
562
736
|
}
|
|
563
737
|
}
|
|
564
738
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.23", ngImport: i0, type: WmTabs, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
565
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.23", type: WmTabs, isStandalone: true, selector: "wm-tabs", inputs: { activeValue: { classPropertyName: "activeValue", publicName: "activeValue", isSignal: true, isRequired: true, transformFunction: null }, variant: { classPropertyName: "variant", publicName: "variant", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { select: "select" }, queries: [{ propertyName: "tabs", predicate: WmTab, isSignal: true }], ngImport: i0, template: "<div\n class=\"wm-tabs\"\n [class.wm-tabs--line]=\"variant() === 'line'\"\n [class.wm-tabs--pill]=\"variant() === 'pill'\"\n role=\"tablist\"\n #container\n (keydown)=\"onKeyDown($event, container)\"\n>\n @for (tab of tabs(); track tab.value(); let i = $index) {\n @if (tab.separatorBefore()) {\n <span\n class=\"wm-tab-separator\"\n role=\"separator\"\n aria-orientation=\"vertical\"\n ></span>\n }\n <button\n type=\"button\"\n role=\"tab\"\n class=\"wm-tab\"\n [id]=\"'wm-tab-' + tab.value()\"\n [attr.aria-selected]=\"activeValue() === tab.value()\"\n [attr.aria-controls]=\"'wm-panel-' + tab.value()\"\n [attr.aria-disabled]=\"tab.unavailable() ? true : null\"\n [attr.aria-description]=\"tab.unavailable() ? tab.unavailableReason() : null\"\n [attr.title]=\"tab.unavailable() ? tab.unavailableReason() : null\"\n [tabIndex]=\"(
|
|
739
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.23", type: WmTabs, isStandalone: true, selector: "wm-tabs", inputs: { activeValue: { classPropertyName: "activeValue", publicName: "activeValue", isSignal: true, isRequired: true, transformFunction: null }, variant: { classPropertyName: "variant", publicName: "variant", isSignal: true, isRequired: false, transformFunction: null }, ariaLabel: { classPropertyName: "ariaLabel", publicName: "ariaLabel", isSignal: true, isRequired: false, transformFunction: null }, ariaLabelledby: { classPropertyName: "ariaLabelledby", publicName: "ariaLabelledby", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { select: "select" }, queries: [{ propertyName: "tabs", predicate: WmTab, isSignal: true }], ngImport: i0, template: "<div\n class=\"wm-tabs\"\n [class.wm-tabs--line]=\"variant() === 'line'\"\n [class.wm-tabs--pill]=\"variant() === 'pill'\"\n role=\"tablist\"\n [attr.aria-label]=\"ariaLabelledby() ? null : ariaLabel() || null\"\n [attr.aria-labelledby]=\"ariaLabelledby() || null\"\n #container\n (keydown)=\"onKeyDown($event, container)\"\n>\n @for (tab of tabs(); track tab.value(); let i = $index) {\n @if (tab.separatorBefore()) {\n <span\n class=\"wm-tab-separator\"\n role=\"separator\"\n aria-orientation=\"vertical\"\n ></span>\n }\n <button\n type=\"button\"\n role=\"tab\"\n class=\"wm-tab\"\n [id]=\"'wm-tab-' + tab.value()\"\n [attr.aria-selected]=\"activeValue() === tab.value()\"\n [attr.aria-controls]=\"'wm-panel-' + tab.value()\"\n [attr.aria-disabled]=\"tab.unavailable() ? true : null\"\n [attr.aria-description]=\"tab.unavailable() ? tab.unavailableReason() : null\"\n [attr.title]=\"tab.unavailable() ? tab.unavailableReason() : null\"\n [tabIndex]=\"tabStop() === i ? 0 : -1\"\n [class.is-active]=\"activeValue() === tab.value()\"\n [class.is-unavailable]=\"tab.unavailable()\"\n (click)=\"selectTab(tab)\"\n (focus)=\"focusedValue.set(tab.value())\"\n (blur)=\"onBlur()\"\n >\n <ng-container [ngTemplateOutlet]=\"tab.tmpl()\" />\n </button>\n }\n</div>\n", styles: [".wm-tab{display:inline-flex;align-items:center;gap:6px;font-size:.8125rem;font-weight:600;background:transparent;border:none;cursor:pointer;white-space:nowrap;transition:color var(--wm-duration-fast) var(--wm-ease-standard),background var(--wm-duration-fast) var(--wm-ease-standard),border-color var(--wm-duration-fast) var(--wm-ease-standard)}.wm-tab:focus-visible{outline:2px solid var(--wm-color-accent);outline-offset:-2px;border-radius:var(--wm-radius-sm)}.wm-tab.is-unavailable{cursor:not-allowed;color:color-mix(in srgb,var(--wm-text-muted) 52%,transparent);box-shadow:none}.wm-tab.is-unavailable:hover{background:transparent;color:color-mix(in srgb,var(--wm-text-muted) 52%,transparent)}.wm-tab-separator{flex:0 0 1px;align-self:center;width:1px;height:1.25rem;margin:0 .375rem;background:var(--wm-border-subtle)}.wm-tabs--line{display:flex;gap:1.5rem;border-bottom:1px solid var(--wm-border-subtle)}.wm-tabs--line .wm-tab{padding:.625rem .25rem;color:var(--wm-text-muted);border-radius:0;border-bottom:2px solid transparent;margin-bottom:-1px}.wm-tabs--line .wm-tab:hover{color:var(--wm-text-secondary)}.wm-tabs--line .wm-tab.is-active{color:var(--wm-color-accent-readable);border-bottom-color:var(--wm-color-accent-readable)}.wm-tabs--pill{display:inline-flex;background:var(--wm-bg-sunken);padding:.25rem;border-radius:var(--wm-radius-md);gap:.125rem;border:1px solid var(--wm-border-subtle)}.wm-tabs--pill .wm-tab{padding:.375rem .75rem;border-radius:var(--wm-radius-sm);color:var(--wm-text-muted)}.wm-tabs--pill .wm-tab:hover{background:color-mix(in srgb,var(--wm-color-accent) 5%,transparent);color:var(--wm-text-secondary)}.wm-tabs--pill .wm-tab.is-active{background:var(--wm-bg-float);color:var(--wm-text-primary);box-shadow:var(--wm-shadow-sm)}.wm-tabs--line .wm-tab.is-unavailable,.wm-tabs--line .wm-tab.is-unavailable:hover,.wm-tabs--pill .wm-tab.is-unavailable,.wm-tabs--pill .wm-tab.is-unavailable:hover{background:transparent;color:color-mix(in srgb,var(--wm-text-muted) 52%,transparent);box-shadow:none}@media(prefers-reduced-motion:reduce){.wm-tab{transition:none}}\n"], dependencies: [{ kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
566
740
|
}
|
|
567
741
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.23", ngImport: i0, type: WmTabs, decorators: [{
|
|
568
742
|
type: Component,
|
|
569
|
-
args: [{ selector: 'wm-tabs', imports: [NgTemplateOutlet], changeDetection: ChangeDetectionStrategy.OnPush, template: "<div\n class=\"wm-tabs\"\n [class.wm-tabs--line]=\"variant() === 'line'\"\n [class.wm-tabs--pill]=\"variant() === 'pill'\"\n role=\"tablist\"\n #container\n (keydown)=\"onKeyDown($event, container)\"\n>\n @for (tab of tabs(); track tab.value(); let i = $index) {\n @if (tab.separatorBefore()) {\n <span\n class=\"wm-tab-separator\"\n role=\"separator\"\n aria-orientation=\"vertical\"\n ></span>\n }\n <button\n type=\"button\"\n role=\"tab\"\n class=\"wm-tab\"\n [id]=\"'wm-tab-' + tab.value()\"\n [attr.aria-selected]=\"activeValue() === tab.value()\"\n [attr.aria-controls]=\"'wm-panel-' + tab.value()\"\n [attr.aria-disabled]=\"tab.unavailable() ? true : null\"\n [attr.aria-description]=\"tab.unavailable() ? tab.unavailableReason() : null\"\n [attr.title]=\"tab.unavailable() ? tab.unavailableReason() : null\"\n [tabIndex]=\"(
|
|
570
|
-
}], propDecorators: { activeValue: [{ type: i0.Input, args: [{ isSignal: true, alias: "activeValue", required: true }] }], variant: [{ type: i0.Input, args: [{ isSignal: true, alias: "variant", required: false }] }], select: [{ type: i0.Output, args: ["select"] }], tabs: [{ type: i0.ContentChildren, args: [i0.forwardRef(() => WmTab), { isSignal: true }] }] } });
|
|
743
|
+
args: [{ selector: 'wm-tabs', imports: [NgTemplateOutlet], changeDetection: ChangeDetectionStrategy.OnPush, template: "<div\n class=\"wm-tabs\"\n [class.wm-tabs--line]=\"variant() === 'line'\"\n [class.wm-tabs--pill]=\"variant() === 'pill'\"\n role=\"tablist\"\n [attr.aria-label]=\"ariaLabelledby() ? null : ariaLabel() || null\"\n [attr.aria-labelledby]=\"ariaLabelledby() || null\"\n #container\n (keydown)=\"onKeyDown($event, container)\"\n>\n @for (tab of tabs(); track tab.value(); let i = $index) {\n @if (tab.separatorBefore()) {\n <span\n class=\"wm-tab-separator\"\n role=\"separator\"\n aria-orientation=\"vertical\"\n ></span>\n }\n <button\n type=\"button\"\n role=\"tab\"\n class=\"wm-tab\"\n [id]=\"'wm-tab-' + tab.value()\"\n [attr.aria-selected]=\"activeValue() === tab.value()\"\n [attr.aria-controls]=\"'wm-panel-' + tab.value()\"\n [attr.aria-disabled]=\"tab.unavailable() ? true : null\"\n [attr.aria-description]=\"tab.unavailable() ? tab.unavailableReason() : null\"\n [attr.title]=\"tab.unavailable() ? tab.unavailableReason() : null\"\n [tabIndex]=\"tabStop() === i ? 0 : -1\"\n [class.is-active]=\"activeValue() === tab.value()\"\n [class.is-unavailable]=\"tab.unavailable()\"\n (click)=\"selectTab(tab)\"\n (focus)=\"focusedValue.set(tab.value())\"\n (blur)=\"onBlur()\"\n >\n <ng-container [ngTemplateOutlet]=\"tab.tmpl()\" />\n </button>\n }\n</div>\n", styles: [".wm-tab{display:inline-flex;align-items:center;gap:6px;font-size:.8125rem;font-weight:600;background:transparent;border:none;cursor:pointer;white-space:nowrap;transition:color var(--wm-duration-fast) var(--wm-ease-standard),background var(--wm-duration-fast) var(--wm-ease-standard),border-color var(--wm-duration-fast) var(--wm-ease-standard)}.wm-tab:focus-visible{outline:2px solid var(--wm-color-accent);outline-offset:-2px;border-radius:var(--wm-radius-sm)}.wm-tab.is-unavailable{cursor:not-allowed;color:color-mix(in srgb,var(--wm-text-muted) 52%,transparent);box-shadow:none}.wm-tab.is-unavailable:hover{background:transparent;color:color-mix(in srgb,var(--wm-text-muted) 52%,transparent)}.wm-tab-separator{flex:0 0 1px;align-self:center;width:1px;height:1.25rem;margin:0 .375rem;background:var(--wm-border-subtle)}.wm-tabs--line{display:flex;gap:1.5rem;border-bottom:1px solid var(--wm-border-subtle)}.wm-tabs--line .wm-tab{padding:.625rem .25rem;color:var(--wm-text-muted);border-radius:0;border-bottom:2px solid transparent;margin-bottom:-1px}.wm-tabs--line .wm-tab:hover{color:var(--wm-text-secondary)}.wm-tabs--line .wm-tab.is-active{color:var(--wm-color-accent-readable);border-bottom-color:var(--wm-color-accent-readable)}.wm-tabs--pill{display:inline-flex;background:var(--wm-bg-sunken);padding:.25rem;border-radius:var(--wm-radius-md);gap:.125rem;border:1px solid var(--wm-border-subtle)}.wm-tabs--pill .wm-tab{padding:.375rem .75rem;border-radius:var(--wm-radius-sm);color:var(--wm-text-muted)}.wm-tabs--pill .wm-tab:hover{background:color-mix(in srgb,var(--wm-color-accent) 5%,transparent);color:var(--wm-text-secondary)}.wm-tabs--pill .wm-tab.is-active{background:var(--wm-bg-float);color:var(--wm-text-primary);box-shadow:var(--wm-shadow-sm)}.wm-tabs--line .wm-tab.is-unavailable,.wm-tabs--line .wm-tab.is-unavailable:hover,.wm-tabs--pill .wm-tab.is-unavailable,.wm-tabs--pill .wm-tab.is-unavailable:hover{background:transparent;color:color-mix(in srgb,var(--wm-text-muted) 52%,transparent);box-shadow:none}@media(prefers-reduced-motion:reduce){.wm-tab{transition:none}}\n"] }]
|
|
744
|
+
}], propDecorators: { activeValue: [{ type: i0.Input, args: [{ isSignal: true, alias: "activeValue", required: true }] }], variant: [{ type: i0.Input, args: [{ isSignal: true, alias: "variant", required: false }] }], ariaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaLabel", required: false }] }], ariaLabelledby: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaLabelledby", required: false }] }], select: [{ type: i0.Output, args: ["select"] }], tabs: [{ type: i0.ContentChildren, args: [i0.forwardRef(() => WmTab), { isSignal: true }] }] } });
|
|
571
745
|
|
|
746
|
+
/**
|
|
747
|
+
* The class set for one button. `<wm-button>` puts it on its inner `<button>`
|
|
748
|
+
* and `[wmButton]` puts it on the host, so the two forms cannot drift apart.
|
|
749
|
+
*/
|
|
750
|
+
function wmButtonClass(variant, size, iconOnly) {
|
|
751
|
+
const base = `wm-btn wm-btn--${variant} wm-btn--${size}`;
|
|
752
|
+
return iconOnly ? `${base} btn-icon-only` : base;
|
|
753
|
+
}
|
|
754
|
+
/**
|
|
755
|
+
* WmButton — an Aegis button as an element: `<wm-button variant="ghost">`.
|
|
756
|
+
*
|
|
757
|
+
* The host wraps a real `<button>` and passes it four inputs: `disabled`,
|
|
758
|
+
* `type`, `ariaLabel` (as `aria-label`) and `btnTitle` (as `title`). Nothing
|
|
759
|
+
* else crosses. An attribute, class or directive written on `<wm-button>` stays on
|
|
760
|
+
* the host, which has no role and cannot take focus. When something must reach
|
|
761
|
+
* the element a screen reader and the keyboard use (ARIA state such as
|
|
762
|
+
* `aria-pressed` or `aria-expanded`, `cdkFocusInitial`, an `id` another element
|
|
763
|
+
* points at, a popover `trigger`, `[wmTooltip]`, a class that changes the
|
|
764
|
+
* button's own box), write a native element with `[wmButton]` instead:
|
|
765
|
+
*
|
|
766
|
+
* ```html
|
|
767
|
+
* <button wmButton variant="ghost" [attr.aria-pressed]="wrap()">Wrap</button>
|
|
768
|
+
* ```
|
|
769
|
+
*/
|
|
572
770
|
class WmButton {
|
|
573
771
|
variant = input('primary', ...(ngDevMode ? [{ debugName: "variant" }] : /* istanbul ignore next */ []));
|
|
574
772
|
size = input('md', ...(ngDevMode ? [{ debugName: "size" }] : /* istanbul ignore next */ []));
|
|
@@ -580,10 +778,7 @@ class WmButton {
|
|
|
580
778
|
ariaLabel = input('', ...(ngDevMode ? [{ debugName: "ariaLabel" }] : /* istanbul ignore next */ []));
|
|
581
779
|
/** Tooltip text forwarded to the inner <button>. Named btnTitle to avoid native attribute conflict. */
|
|
582
780
|
btnTitle = input('', ...(ngDevMode ? [{ debugName: "btnTitle" }] : /* istanbul ignore next */ []));
|
|
583
|
-
btnClass = computed(() => {
|
|
584
|
-
const base = `wm-btn wm-btn--${this.variant()} wm-btn--${this.size()}`;
|
|
585
|
-
return this.iconOnly() ? `${base} btn-icon-only` : base;
|
|
586
|
-
}, ...(ngDevMode ? [{ debugName: "btnClass" }] : /* istanbul ignore next */ []));
|
|
781
|
+
btnClass = computed(() => wmButtonClass(this.variant(), this.size(), this.iconOnly()), ...(ngDevMode ? [{ debugName: "btnClass" }] : /* istanbul ignore next */ []));
|
|
587
782
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.23", ngImport: i0, type: WmButton, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
588
783
|
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "21.2.23", type: WmButton, isStandalone: true, selector: "wm-button", inputs: { variant: { classPropertyName: "variant", publicName: "variant", isSignal: true, isRequired: false, transformFunction: null }, size: { classPropertyName: "size", publicName: "size", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, type: { classPropertyName: "type", publicName: "type", isSignal: true, isRequired: false, transformFunction: null }, iconOnly: { classPropertyName: "iconOnly", publicName: "iconOnly", isSignal: true, isRequired: false, transformFunction: null }, ariaLabel: { classPropertyName: "ariaLabel", publicName: "ariaLabel", isSignal: true, isRequired: false, transformFunction: null }, btnTitle: { classPropertyName: "btnTitle", publicName: "btnTitle", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: "<button\n [class]=\"btnClass()\"\n [disabled]=\"disabled()\"\n [type]=\"type()\"\n [attr.aria-label]=\"ariaLabel() || null\"\n [attr.title]=\"btnTitle() || null\"\n>\n <ng-content />\n</button>\n", styles: [":host{display:inline-flex}\n"] });
|
|
589
784
|
}
|
|
@@ -592,6 +787,61 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.23", ngImpo
|
|
|
592
787
|
args: [{ selector: 'wm-button', template: "<button\n [class]=\"btnClass()\"\n [disabled]=\"disabled()\"\n [type]=\"type()\"\n [attr.aria-label]=\"ariaLabel() || null\"\n [attr.title]=\"btnTitle() || null\"\n>\n <ng-content />\n</button>\n", styles: [":host{display:inline-flex}\n"] }]
|
|
593
788
|
}], propDecorators: { variant: [{ type: i0.Input, args: [{ isSignal: true, alias: "variant", required: false }] }], size: [{ type: i0.Input, args: [{ isSignal: true, alias: "size", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], type: [{ type: i0.Input, args: [{ isSignal: true, alias: "type", required: false }] }], iconOnly: [{ type: i0.Input, args: [{ isSignal: true, alias: "iconOnly", required: false }] }], ariaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaLabel", required: false }] }], btnTitle: [{ type: i0.Input, args: [{ isSignal: true, alias: "btnTitle", required: false }] }] } });
|
|
594
789
|
|
|
790
|
+
/**
|
|
791
|
+
* WmButtonDirective — the Aegis button classes on a native `<button>` or `<a>`.
|
|
792
|
+
*
|
|
793
|
+
* It renders exactly the class set `<wm-button>` puts on its inner button, but
|
|
794
|
+
* on the element the consumer writes. That element is the focusable one, so
|
|
795
|
+
* every attribute and directive on it reaches the right place with no
|
|
796
|
+
* forwarding list: ARIA state, `cdkFocusInitial`, `id`, `form`, a popover
|
|
797
|
+
* `trigger`, `[wmTooltip]`, test ids and consumer classes.
|
|
798
|
+
*
|
|
799
|
+
* Use `<wm-button>` by default. Use this when one of those has to land on the
|
|
800
|
+
* button itself (see `WmButton` for what the component forwards).
|
|
801
|
+
*
|
|
802
|
+
* - `variant`, `size` and `iconOnly` mean what they mean on `<wm-button>`.
|
|
803
|
+
* - Classes the consumer writes, static or bound, are kept.
|
|
804
|
+
* - A `<button>` with no `type` gets `type="button"`, the `<wm-button>`
|
|
805
|
+
* default, so it does not submit a surrounding form. A written or bound
|
|
806
|
+
* `type` wins. An `<a>` is left alone.
|
|
807
|
+
* - Everything else is native: write `disabled`, `aria-label` and `title`
|
|
808
|
+
* directly. An `<a>` cannot be disabled.
|
|
809
|
+
*
|
|
810
|
+
* @example
|
|
811
|
+
* <button wmButton variant="ghost" size="sm" [attr.aria-pressed]="wrap()" (click)="toggleWrap()">
|
|
812
|
+
* Wrap lines
|
|
813
|
+
* </button>
|
|
814
|
+
* <button wmButton variant="secondary" cdkFocusInitial (click)="cancel()">Keep editing</button>
|
|
815
|
+
* <a wmButton variant="ghost" href="/back">Back</a>
|
|
816
|
+
*/
|
|
817
|
+
class WmButtonDirective {
|
|
818
|
+
variant = input('primary', ...(ngDevMode ? [{ debugName: "variant" }] : /* istanbul ignore next */ []));
|
|
819
|
+
size = input('md', ...(ngDevMode ? [{ debugName: "size" }] : /* istanbul ignore next */ []));
|
|
820
|
+
/** Makes the button square. An icon-only button still needs an `aria-label`. */
|
|
821
|
+
iconOnly = input(false, { ...(ngDevMode ? { debugName: "iconOnly" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
|
|
822
|
+
hostClass = computed(() => wmButtonClass(this.variant(), this.size(), this.iconOnly()), ...(ngDevMode ? [{ debugName: "hostClass" }] : /* istanbul ignore next */ []));
|
|
823
|
+
constructor() {
|
|
824
|
+
// Static attributes are on the element before a directive is constructed,
|
|
825
|
+
// and a `[type]` or `[attr.type]` binding runs after, so this sets a default
|
|
826
|
+
// that anything the consumer writes overrides.
|
|
827
|
+
const element = inject(ElementRef).nativeElement;
|
|
828
|
+
if (element.tagName === 'BUTTON' && !element.hasAttribute('type')) {
|
|
829
|
+
element.setAttribute('type', 'button');
|
|
830
|
+
}
|
|
831
|
+
}
|
|
832
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.23", ngImport: i0, type: WmButtonDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive });
|
|
833
|
+
static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "21.2.23", type: WmButtonDirective, isStandalone: true, selector: "button[wmButton], a[wmButton]", inputs: { variant: { classPropertyName: "variant", publicName: "variant", isSignal: true, isRequired: false, transformFunction: null }, size: { classPropertyName: "size", publicName: "size", isSignal: true, isRequired: false, transformFunction: null }, iconOnly: { classPropertyName: "iconOnly", publicName: "iconOnly", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "class": "hostClass()" } }, ngImport: i0 });
|
|
834
|
+
}
|
|
835
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.23", ngImport: i0, type: WmButtonDirective, decorators: [{
|
|
836
|
+
type: Directive,
|
|
837
|
+
args: [{
|
|
838
|
+
selector: 'button[wmButton], a[wmButton]',
|
|
839
|
+
host: {
|
|
840
|
+
'[class]': 'hostClass()',
|
|
841
|
+
},
|
|
842
|
+
}]
|
|
843
|
+
}], ctorParameters: () => [], propDecorators: { variant: [{ type: i0.Input, args: [{ isSignal: true, alias: "variant", required: false }] }], size: [{ type: i0.Input, args: [{ isSignal: true, alias: "size", required: false }] }], iconOnly: [{ type: i0.Input, args: [{ isSignal: true, alias: "iconOnly", required: false }] }] } });
|
|
844
|
+
|
|
595
845
|
/**
|
|
596
846
|
* WmCheckbox — Boolean selection control with optional indeterminate state.
|
|
597
847
|
*
|
|
@@ -811,7 +1061,7 @@ const MAX_VALUE_CANDIDATES = 64;
|
|
|
811
1061
|
*
|
|
812
1062
|
* When NOT to use:
|
|
813
1063
|
* - An exact number the user knows before they start typing it → <wm-number-input>
|
|
814
|
-
* - Fewer than about five discrete choices → <wm-
|
|
1064
|
+
* - Fewer than about five discrete choices → <wm-toggle-group> or <wm-select>
|
|
815
1065
|
* - An unbounded value — a slider has to have a min and a max
|
|
816
1066
|
*
|
|
817
1067
|
* Value pipeline: every value, whether it arrives from a two-way binding, a
|
|
@@ -1109,6 +1359,305 @@ function trim(fraction) {
|
|
|
1109
1359
|
return String(round(fraction, 6));
|
|
1110
1360
|
}
|
|
1111
1361
|
|
|
1362
|
+
/**
|
|
1363
|
+
* WmToggleGroupItem — one choice in a `<wm-toggle-group>`.
|
|
1364
|
+
*
|
|
1365
|
+
* The element itself is the `role="radio"`, so anything written on it lands
|
|
1366
|
+
* on the control a screen reader and the keyboard use: `aria-label` for an
|
|
1367
|
+
* icon-only item, `[wmTooltip]`, `data-testid`, a consumer class.
|
|
1368
|
+
*/
|
|
1369
|
+
class WmToggleGroupItem {
|
|
1370
|
+
/**
|
|
1371
|
+
* The value the group takes, and emits, when this item is checked. Give
|
|
1372
|
+
* every item one.
|
|
1373
|
+
*
|
|
1374
|
+
* Not `input.required`: the group reads every item's value to place the tab
|
|
1375
|
+
* stop, and an item rendered by `@for` has its host bindings run before the
|
|
1376
|
+
* next item's inputs are set. A required input throws NG0950 on that read;
|
|
1377
|
+
* this one reads `undefined`, the group places the stop without it, and the
|
|
1378
|
+
* binding settles when the next item's value arrives in the same pass.
|
|
1379
|
+
*/
|
|
1380
|
+
value = input(undefined, ...(ngDevMode ? [{ debugName: "value" }] : /* istanbul ignore next */ []));
|
|
1381
|
+
/** Takes this item out of the focus order and out of selection. */
|
|
1382
|
+
disabled = input(false, ...(ngDevMode ? [{ debugName: "disabled" }] : /* istanbul ignore next */ []));
|
|
1383
|
+
group = inject(WmToggleGroup);
|
|
1384
|
+
/** @internal The item's own element, which carries role="radio". */
|
|
1385
|
+
element = inject(ElementRef).nativeElement;
|
|
1386
|
+
checked = computed(() => this.group.isChecked(this), ...(ngDevMode ? [{ debugName: "checked" }] : /* istanbul ignore next */ []));
|
|
1387
|
+
/** @internal Disabled by its own input or by the group. */
|
|
1388
|
+
isDisabled = computed(() => this.disabled() || this.group.isDisabled(), ...(ngDevMode ? [{ debugName: "isDisabled" }] : /* istanbul ignore next */ []));
|
|
1389
|
+
/**
|
|
1390
|
+
* 0 for the one tab stop, -1 for the rest. A disabled item gets no tabindex
|
|
1391
|
+
* at all: -1 would still let a pointer press focus it.
|
|
1392
|
+
*/
|
|
1393
|
+
tabIndex = computed(() => {
|
|
1394
|
+
if (this.isDisabled())
|
|
1395
|
+
return null;
|
|
1396
|
+
return this.group.tabStop() === this ? 0 : -1;
|
|
1397
|
+
}, ...(ngDevMode ? [{ debugName: "tabIndex" }] : /* istanbul ignore next */ []));
|
|
1398
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.23", ngImport: i0, type: WmToggleGroupItem, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
1399
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "21.2.23", type: WmToggleGroupItem, isStandalone: true, selector: "wm-toggle-group-item", inputs: { value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null } }, host: { attributes: { "role": "radio" }, listeners: { "click": "group.select(this)" }, properties: { "class.is-checked": "checked()", "class.is-disabled": "isDisabled()", "attr.aria-checked": "checked()", "attr.aria-disabled": "isDisabled() || null", "attr.tabindex": "tabIndex()" }, classAttribute: "wm-toggle-group-item" }, ngImport: i0, template: '<ng-content />', isInline: true, changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
1400
|
+
}
|
|
1401
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.23", ngImport: i0, type: WmToggleGroupItem, decorators: [{
|
|
1402
|
+
type: Component,
|
|
1403
|
+
args: [{
|
|
1404
|
+
selector: 'wm-toggle-group-item',
|
|
1405
|
+
template: '<ng-content />',
|
|
1406
|
+
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
1407
|
+
host: {
|
|
1408
|
+
role: 'radio',
|
|
1409
|
+
class: 'wm-toggle-group-item',
|
|
1410
|
+
'[class.is-checked]': 'checked()',
|
|
1411
|
+
'[class.is-disabled]': 'isDisabled()',
|
|
1412
|
+
'[attr.aria-checked]': 'checked()',
|
|
1413
|
+
'[attr.aria-disabled]': 'isDisabled() || null',
|
|
1414
|
+
'[attr.tabindex]': 'tabIndex()',
|
|
1415
|
+
'(click)': 'group.select(this)',
|
|
1416
|
+
},
|
|
1417
|
+
}]
|
|
1418
|
+
}], propDecorators: { value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }] } });
|
|
1419
|
+
/**
|
|
1420
|
+
* WmToggleGroup — a single-select choice among a few options, drawn as joined
|
|
1421
|
+
* segments or as separate chips.
|
|
1422
|
+
*
|
|
1423
|
+
* Usage:
|
|
1424
|
+
* <wm-toggle-group [(value)]="strategy" ariaLabel="Pull strategy">
|
|
1425
|
+
* <wm-toggle-group-item value="merge">Merge</wm-toggle-group-item>
|
|
1426
|
+
* <wm-toggle-group-item value="rebase">Rebase</wm-toggle-group-item>
|
|
1427
|
+
* </wm-toggle-group>
|
|
1428
|
+
*
|
|
1429
|
+
* When to use:
|
|
1430
|
+
* - Two to about five mutually exclusive options that should all stay visible:
|
|
1431
|
+
* a setting (pull strategy, tab size, config scope), a view mode, a verdict
|
|
1432
|
+
* - A single-select filter row → `appearance="chip"`
|
|
1433
|
+
*
|
|
1434
|
+
* When NOT to use:
|
|
1435
|
+
* - Choosing which panel shows → `<wm-tabs>`. A tab controls a panel; a toggle
|
|
1436
|
+
* group sets a value
|
|
1437
|
+
* - An on/off setting → `<wm-toggle>` or a checkbox
|
|
1438
|
+
* - Several options that can be on together → checkboxes
|
|
1439
|
+
* - More options than fit on one line → `<wm-select>`
|
|
1440
|
+
*
|
|
1441
|
+
* Selection:
|
|
1442
|
+
* - `value` names the checked item. `null`, or a value no item carries,
|
|
1443
|
+
* checks nothing; the group still keeps one tab stop.
|
|
1444
|
+
* - `(valueChange)` fires once per user choice and only then: never for a
|
|
1445
|
+
* value written from outside, never for the item that is already checked,
|
|
1446
|
+
* and never with null. A two-way binding to a signal that cannot hold null
|
|
1447
|
+
* therefore type-checks.
|
|
1448
|
+
* - Items compare by `Object.is`, so a value keeps its type: `[value]="4"` on
|
|
1449
|
+
* an item emits the number 4, not the string "4".
|
|
1450
|
+
*
|
|
1451
|
+
* Keyboard (the WAI-ARIA APG radio group):
|
|
1452
|
+
* - Tab enters the group on one stop: the focused item while focus is inside,
|
|
1453
|
+
* otherwise the checked item, otherwise the first item that is not disabled.
|
|
1454
|
+
* - Arrow keys move focus AND check, wrapping at both ends and stepping over
|
|
1455
|
+
* disabled items. Left and Right swap in a right-to-left layout; Up and Down
|
|
1456
|
+
* follow document order. `Home` and `End` go to the first and last items.
|
|
1457
|
+
* - `Space` checks the focused item. `Enter` does not, as on a native radio.
|
|
1458
|
+
*
|
|
1459
|
+
* Accessibility:
|
|
1460
|
+
* - The `<wm-toggle-group>` element is the `role="radiogroup"` and each item
|
|
1461
|
+
* is a `role="radio"` with `aria-checked`. Name the group with `ariaLabel`,
|
|
1462
|
+
* or `ariaLabelledby` when a visible caption exists; `ariaLabelledby` wins.
|
|
1463
|
+
* An `aria-label` written straight on the element also works, because the
|
|
1464
|
+
* element carries the role.
|
|
1465
|
+
* - An icon-only item needs its own `aria-label`, written on the item.
|
|
1466
|
+
* - A disabled item keeps `aria-disabled="true"` and leaves the focus order.
|
|
1467
|
+
* A disabled group does the same for every item.
|
|
1468
|
+
*
|
|
1469
|
+
* Form integration: a ControlValueAccessor, so `formControlName`,
|
|
1470
|
+
* `[formControl]` and `[(ngModel)]` all work. The control is marked touched
|
|
1471
|
+
* when focus leaves the group.
|
|
1472
|
+
*/
|
|
1473
|
+
class WmToggleGroup {
|
|
1474
|
+
/** Value of the checked item. `null`, or a value no item carries, checks nothing. */
|
|
1475
|
+
value = input(null, ...(ngDevMode ? [{ debugName: "value" }] : /* istanbul ignore next */ []));
|
|
1476
|
+
/** The value of the item the user checked. Never null; see the class comment. */
|
|
1477
|
+
valueChange = output();
|
|
1478
|
+
/** Accessible name for the radiogroup. Ignored while `ariaLabelledby` is set. */
|
|
1479
|
+
ariaLabel = input(null, ...(ngDevMode ? [{ debugName: "ariaLabel" }] : /* istanbul ignore next */ []));
|
|
1480
|
+
/**
|
|
1481
|
+
* Id of a visible element that names the group. Prefer it over `ariaLabel`
|
|
1482
|
+
* when a visible caption exists, so the accessible name matches the visible
|
|
1483
|
+
* text (WCAG 2.5.3, Label in Name).
|
|
1484
|
+
*/
|
|
1485
|
+
ariaLabelledby = input(null, ...(ngDevMode ? [{ debugName: "ariaLabelledby" }] : /* istanbul ignore next */ []));
|
|
1486
|
+
/**
|
|
1487
|
+
* `segmented` joins the items in one sunken track, for a setting or a view
|
|
1488
|
+
* mode. `chip` spaces them as separate rounded chips that wrap, for a filter
|
|
1489
|
+
* row or a verdict.
|
|
1490
|
+
*/
|
|
1491
|
+
appearance = input('segmented', ...(ngDevMode ? [{ debugName: "appearance" }] : /* istanbul ignore next */ []));
|
|
1492
|
+
size = input('md', ...(ngDevMode ? [{ debugName: "size" }] : /* istanbul ignore next */ []));
|
|
1493
|
+
/** Disables every item. Reactive forms set this independently. */
|
|
1494
|
+
disabled = input(false, ...(ngDevMode ? [{ debugName: "disabled" }] : /* istanbul ignore next */ []));
|
|
1495
|
+
items = contentChildren(WmToggleGroupItem, { ...(ngDevMode ? { debugName: "items" } : /* istanbul ignore next */ {}), descendants: true });
|
|
1496
|
+
host = inject(ElementRef).nativeElement;
|
|
1497
|
+
// An aria-label or aria-labelledby attribute written on the element is kept
|
|
1498
|
+
// when the matching input is unset. The element carries the role, so the
|
|
1499
|
+
// attribute is a valid way to name it, and the host binding below would
|
|
1500
|
+
// otherwise erase it on the first change detection.
|
|
1501
|
+
staticLabel = inject(new HostAttributeToken('aria-label'), { optional: true });
|
|
1502
|
+
staticLabelledby = inject(new HostAttributeToken('aria-labelledby'), {
|
|
1503
|
+
optional: true,
|
|
1504
|
+
});
|
|
1505
|
+
accessibleLabelledby = computed(() => this.ariaLabelledby() || this.staticLabelledby || null, ...(ngDevMode ? [{ debugName: "accessibleLabelledby" }] : /* istanbul ignore next */ []));
|
|
1506
|
+
accessibleLabel = computed(() => this.accessibleLabelledby() ? null : this.ariaLabel() || this.staticLabel || null, ...(ngDevMode ? [{ debugName: "accessibleLabel" }] : /* istanbul ignore next */ []));
|
|
1507
|
+
/** The checked value. Follows `value`, and moves at once on a user choice or a form write. */
|
|
1508
|
+
checkedValue = linkedSignal(() => this.value(), ...(ngDevMode ? [{ debugName: "checkedValue" }] : /* istanbul ignore next */ []));
|
|
1509
|
+
/** Set by reactive forms through setDisabledState, independent of the input. */
|
|
1510
|
+
formDisabled = signal(false, ...(ngDevMode ? [{ debugName: "formDisabled" }] : /* istanbul ignore next */ []));
|
|
1511
|
+
/** @internal */
|
|
1512
|
+
isDisabled = computed(() => this.disabled() || this.formDisabled(), ...(ngDevMode ? [{ debugName: "isDisabled" }] : /* istanbul ignore next */ []));
|
|
1513
|
+
/**
|
|
1514
|
+
* The item that holds focus, or null while focus is outside the group. Kept
|
|
1515
|
+
* as the item itself rather than an index, so an item added or removed
|
|
1516
|
+
* before it cannot move the stop onto another item.
|
|
1517
|
+
*/
|
|
1518
|
+
focusedItem = signal(null, ...(ngDevMode ? [{ debugName: "focusedItem" }] : /* istanbul ignore next */ []));
|
|
1519
|
+
/** @internal The one item in the page's Tab order, or null when none can take focus. */
|
|
1520
|
+
tabStop = computed(() => {
|
|
1521
|
+
if (this.isDisabled())
|
|
1522
|
+
return null;
|
|
1523
|
+
const items = this.items();
|
|
1524
|
+
const focused = this.focusedItem();
|
|
1525
|
+
if (focused && items.includes(focused) && !focused.disabled())
|
|
1526
|
+
return focused;
|
|
1527
|
+
const checked = items.find((item) => !item.disabled() && this.isChecked(item));
|
|
1528
|
+
return checked ?? items.find((item) => !item.disabled()) ?? null;
|
|
1529
|
+
}, ...(ngDevMode ? [{ debugName: "tabStop" }] : /* istanbul ignore next */ []));
|
|
1530
|
+
onChange = () => { };
|
|
1531
|
+
onTouched = () => { };
|
|
1532
|
+
/** @internal */
|
|
1533
|
+
isChecked(item) {
|
|
1534
|
+
const checked = this.checkedValue();
|
|
1535
|
+
return checked !== null && Object.is(item.value(), checked);
|
|
1536
|
+
}
|
|
1537
|
+
/** @internal Checks `item` on a user action. */
|
|
1538
|
+
select(item) {
|
|
1539
|
+
if (item.isDisabled())
|
|
1540
|
+
return;
|
|
1541
|
+
const value = item.value();
|
|
1542
|
+
if (Object.is(this.checkedValue(), value))
|
|
1543
|
+
return;
|
|
1544
|
+
this.checkedValue.set(value);
|
|
1545
|
+
this.onChange(value);
|
|
1546
|
+
this.valueChange.emit(value);
|
|
1547
|
+
}
|
|
1548
|
+
onKeydown(event) {
|
|
1549
|
+
if (this.isDisabled() || event.altKey || event.ctrlKey || event.metaKey)
|
|
1550
|
+
return;
|
|
1551
|
+
const items = this.items();
|
|
1552
|
+
const current = items.find((item) => item.element.contains(event.target));
|
|
1553
|
+
if (!current)
|
|
1554
|
+
return;
|
|
1555
|
+
if (event.key === ' ') {
|
|
1556
|
+
event.preventDefault();
|
|
1557
|
+
this.select(current);
|
|
1558
|
+
return;
|
|
1559
|
+
}
|
|
1560
|
+
const rtl = getComputedStyle(this.host).direction === 'rtl';
|
|
1561
|
+
let target;
|
|
1562
|
+
switch (event.key) {
|
|
1563
|
+
case 'ArrowDown':
|
|
1564
|
+
target = this.step(items, current, 1);
|
|
1565
|
+
break;
|
|
1566
|
+
case 'ArrowUp':
|
|
1567
|
+
target = this.step(items, current, -1);
|
|
1568
|
+
break;
|
|
1569
|
+
case 'ArrowRight':
|
|
1570
|
+
target = this.step(items, current, rtl ? -1 : 1);
|
|
1571
|
+
break;
|
|
1572
|
+
case 'ArrowLeft':
|
|
1573
|
+
target = this.step(items, current, rtl ? 1 : -1);
|
|
1574
|
+
break;
|
|
1575
|
+
case 'Home':
|
|
1576
|
+
target = items.find((item) => !item.disabled());
|
|
1577
|
+
break;
|
|
1578
|
+
case 'End': {
|
|
1579
|
+
const enabled = items.filter((item) => !item.disabled());
|
|
1580
|
+
target = enabled[enabled.length - 1];
|
|
1581
|
+
break;
|
|
1582
|
+
}
|
|
1583
|
+
default:
|
|
1584
|
+
return;
|
|
1585
|
+
}
|
|
1586
|
+
event.preventDefault();
|
|
1587
|
+
if (!target)
|
|
1588
|
+
return;
|
|
1589
|
+
this.focusedItem.set(target);
|
|
1590
|
+
target.element.focus();
|
|
1591
|
+
this.select(target);
|
|
1592
|
+
}
|
|
1593
|
+
onFocusIn(event) {
|
|
1594
|
+
const item = this.items().find((candidate) => candidate.element.contains(event.target));
|
|
1595
|
+
if (item)
|
|
1596
|
+
this.focusedItem.set(item);
|
|
1597
|
+
}
|
|
1598
|
+
onFocusOut(event) {
|
|
1599
|
+
const next = event.relatedTarget;
|
|
1600
|
+
if (next && this.host.contains(next))
|
|
1601
|
+
return;
|
|
1602
|
+
this.focusedItem.set(null);
|
|
1603
|
+
this.onTouched();
|
|
1604
|
+
}
|
|
1605
|
+
/** The next item in `direction` that is not disabled, wrapping at both ends. */
|
|
1606
|
+
step(items, from, direction) {
|
|
1607
|
+
const count = items.length;
|
|
1608
|
+
const start = items.indexOf(from);
|
|
1609
|
+
for (let offset = 1; offset <= count; offset += 1) {
|
|
1610
|
+
const candidate = items[(((start + direction * offset) % count) + count) % count];
|
|
1611
|
+
if (!candidate.disabled())
|
|
1612
|
+
return candidate;
|
|
1613
|
+
}
|
|
1614
|
+
return undefined;
|
|
1615
|
+
}
|
|
1616
|
+
// ── ControlValueAccessor ──────────────────────────────────────────────────
|
|
1617
|
+
writeValue(value) {
|
|
1618
|
+
this.checkedValue.set(value ?? null);
|
|
1619
|
+
}
|
|
1620
|
+
registerOnChange(fn) {
|
|
1621
|
+
this.onChange = fn;
|
|
1622
|
+
}
|
|
1623
|
+
registerOnTouched(fn) {
|
|
1624
|
+
this.onTouched = fn;
|
|
1625
|
+
}
|
|
1626
|
+
setDisabledState(isDisabled) {
|
|
1627
|
+
this.formDisabled.set(isDisabled);
|
|
1628
|
+
}
|
|
1629
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.23", ngImport: i0, type: WmToggleGroup, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
1630
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.2.0", version: "21.2.23", type: WmToggleGroup, isStandalone: true, selector: "wm-toggle-group", inputs: { value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, ariaLabel: { classPropertyName: "ariaLabel", publicName: "ariaLabel", isSignal: true, isRequired: false, transformFunction: null }, ariaLabelledby: { classPropertyName: "ariaLabelledby", publicName: "ariaLabelledby", isSignal: true, isRequired: false, transformFunction: null }, appearance: { classPropertyName: "appearance", publicName: "appearance", isSignal: true, isRequired: false, transformFunction: null }, size: { classPropertyName: "size", publicName: "size", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { valueChange: "valueChange" }, host: { attributes: { "role": "radiogroup" }, listeners: { "keydown": "onKeydown($event)", "focusin": "onFocusIn($event)", "focusout": "onFocusOut($event)" }, properties: { "class.wm-toggle-group--segmented": "appearance() === 'segmented'", "class.wm-toggle-group--chip": "appearance() === 'chip'", "class.wm-toggle-group--sm": "size() === 'sm'", "attr.aria-label": "accessibleLabel()", "attr.aria-labelledby": "accessibleLabelledby()", "attr.aria-disabled": "isDisabled() || null" }, classAttribute: "wm-toggle-group" }, providers: [
|
|
1631
|
+
{
|
|
1632
|
+
provide: NG_VALUE_ACCESSOR,
|
|
1633
|
+
useExisting: forwardRef(() => WmToggleGroup),
|
|
1634
|
+
multi: true,
|
|
1635
|
+
},
|
|
1636
|
+
], queries: [{ propertyName: "items", predicate: WmToggleGroupItem, descendants: true, isSignal: true }], ngImport: i0, template: '<ng-content />', isInline: true, styles: [".wm-toggle-group{display:inline-flex;align-items:stretch;max-width:100%}.wm-toggle-group-item{display:inline-flex;align-items:center;justify-content:center;gap:6px;box-sizing:border-box;min-height:28px;padding:0 12px;border:1px solid transparent;background-color:transparent;color:var(--wm-text-secondary);font-family:inherit;font-size:.8125rem;font-weight:600;line-height:1;white-space:nowrap;cursor:pointer;-webkit-user-select:none;user-select:none;outline:none;transition:background-color var(--wm-transition-fast),border-color var(--wm-transition-fast),color var(--wm-transition-fast)}.wm-toggle-group-item svg{flex-shrink:0;width:14px;height:14px}.wm-toggle-group-item:focus-visible{outline:2px solid var(--wm-color-accent-readable);outline-offset:2px}.wm-toggle-group--segmented{gap:2px;padding:3px;border:1px solid var(--wm-border-subtle);border-radius:var(--wm-radius-md);background-color:var(--wm-bg-sunken)}.wm-toggle-group--segmented .wm-toggle-group-item{border-radius:var(--wm-radius-sm)}.wm-toggle-group--segmented .wm-toggle-group-item:hover:not(.is-checked):not(.is-disabled){background-color:var(--wm-bg-float);color:var(--wm-text-primary)}.wm-toggle-group--chip{display:flex;flex-wrap:wrap;gap:6px}.wm-toggle-group--chip .wm-toggle-group-item{border-color:var(--wm-border-input);border-radius:var(--wm-radius-full)}.wm-toggle-group--chip .wm-toggle-group-item:hover:not(.is-checked):not(.is-disabled){background-color:var(--wm-bg-sunken);border-color:var(--wm-border-hover);color:var(--wm-text-primary)}.wm-toggle-group-item.is-checked{background-color:var(--wm-color-accent);border-color:var(--wm-color-accent-readable);color:var(--wm-color-accent-text)}.wm-toggle-group-item.is-checked:hover:not(.is-disabled){background-color:var(--wm-color-accent-hover)}.wm-toggle-group-item.is-disabled{opacity:.5;cursor:not-allowed}.wm-toggle-group--sm .wm-toggle-group-item{min-height:24px;padding:0 8px;font-size:.75rem}.wm-toggle-group--sm.wm-toggle-group--segmented{padding:2px;border-radius:var(--wm-radius-sm)}.wm-toggle-group--sm.wm-toggle-group--segmented .wm-toggle-group-item{border-radius:var(--wm-radius-xs)}@media(forced-colors:active){.wm-toggle-group-item{border-color:ButtonText}.wm-toggle-group-item.is-checked{forced-color-adjust:none;background-color:SelectedItem;border-color:SelectedItem;color:SelectedItemText}.wm-toggle-group-item.is-disabled{border-color:GrayText;color:GrayText}}@media(prefers-reduced-motion:reduce){.wm-toggle-group-item{transition:none}}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
|
|
1637
|
+
}
|
|
1638
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.23", ngImport: i0, type: WmToggleGroup, decorators: [{
|
|
1639
|
+
type: Component,
|
|
1640
|
+
args: [{ selector: 'wm-toggle-group', template: '<ng-content />', encapsulation: ViewEncapsulation.None, changeDetection: ChangeDetectionStrategy.OnPush, providers: [
|
|
1641
|
+
{
|
|
1642
|
+
provide: NG_VALUE_ACCESSOR,
|
|
1643
|
+
useExisting: forwardRef(() => WmToggleGroup),
|
|
1644
|
+
multi: true,
|
|
1645
|
+
},
|
|
1646
|
+
], host: {
|
|
1647
|
+
role: 'radiogroup',
|
|
1648
|
+
class: 'wm-toggle-group',
|
|
1649
|
+
'[class.wm-toggle-group--segmented]': "appearance() === 'segmented'",
|
|
1650
|
+
'[class.wm-toggle-group--chip]': "appearance() === 'chip'",
|
|
1651
|
+
'[class.wm-toggle-group--sm]': "size() === 'sm'",
|
|
1652
|
+
'[attr.aria-label]': 'accessibleLabel()',
|
|
1653
|
+
'[attr.aria-labelledby]': 'accessibleLabelledby()',
|
|
1654
|
+
'[attr.aria-disabled]': 'isDisabled() || null',
|
|
1655
|
+
'(keydown)': 'onKeydown($event)',
|
|
1656
|
+
'(focusin)': 'onFocusIn($event)',
|
|
1657
|
+
'(focusout)': 'onFocusOut($event)',
|
|
1658
|
+
}, styles: [".wm-toggle-group{display:inline-flex;align-items:stretch;max-width:100%}.wm-toggle-group-item{display:inline-flex;align-items:center;justify-content:center;gap:6px;box-sizing:border-box;min-height:28px;padding:0 12px;border:1px solid transparent;background-color:transparent;color:var(--wm-text-secondary);font-family:inherit;font-size:.8125rem;font-weight:600;line-height:1;white-space:nowrap;cursor:pointer;-webkit-user-select:none;user-select:none;outline:none;transition:background-color var(--wm-transition-fast),border-color var(--wm-transition-fast),color var(--wm-transition-fast)}.wm-toggle-group-item svg{flex-shrink:0;width:14px;height:14px}.wm-toggle-group-item:focus-visible{outline:2px solid var(--wm-color-accent-readable);outline-offset:2px}.wm-toggle-group--segmented{gap:2px;padding:3px;border:1px solid var(--wm-border-subtle);border-radius:var(--wm-radius-md);background-color:var(--wm-bg-sunken)}.wm-toggle-group--segmented .wm-toggle-group-item{border-radius:var(--wm-radius-sm)}.wm-toggle-group--segmented .wm-toggle-group-item:hover:not(.is-checked):not(.is-disabled){background-color:var(--wm-bg-float);color:var(--wm-text-primary)}.wm-toggle-group--chip{display:flex;flex-wrap:wrap;gap:6px}.wm-toggle-group--chip .wm-toggle-group-item{border-color:var(--wm-border-input);border-radius:var(--wm-radius-full)}.wm-toggle-group--chip .wm-toggle-group-item:hover:not(.is-checked):not(.is-disabled){background-color:var(--wm-bg-sunken);border-color:var(--wm-border-hover);color:var(--wm-text-primary)}.wm-toggle-group-item.is-checked{background-color:var(--wm-color-accent);border-color:var(--wm-color-accent-readable);color:var(--wm-color-accent-text)}.wm-toggle-group-item.is-checked:hover:not(.is-disabled){background-color:var(--wm-color-accent-hover)}.wm-toggle-group-item.is-disabled{opacity:.5;cursor:not-allowed}.wm-toggle-group--sm .wm-toggle-group-item{min-height:24px;padding:0 8px;font-size:.75rem}.wm-toggle-group--sm.wm-toggle-group--segmented{padding:2px;border-radius:var(--wm-radius-sm)}.wm-toggle-group--sm.wm-toggle-group--segmented .wm-toggle-group-item{border-radius:var(--wm-radius-xs)}@media(forced-colors:active){.wm-toggle-group-item{border-color:ButtonText}.wm-toggle-group-item.is-checked{forced-color-adjust:none;background-color:SelectedItem;border-color:SelectedItem;color:SelectedItemText}.wm-toggle-group-item.is-disabled{border-color:GrayText;color:GrayText}}@media(prefers-reduced-motion:reduce){.wm-toggle-group-item{transition:none}}\n"] }]
|
|
1659
|
+
}], propDecorators: { value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }], valueChange: [{ type: i0.Output, args: ["valueChange"] }], ariaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaLabel", required: false }] }], ariaLabelledby: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaLabelledby", required: false }] }], appearance: [{ type: i0.Input, args: [{ isSignal: true, alias: "appearance", required: false }] }], size: [{ type: i0.Input, args: [{ isSignal: true, alias: "size", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], items: [{ type: i0.ContentChildren, args: [i0.forwardRef(() => WmToggleGroupItem), { ...{ descendants: true }, isSignal: true }] }] } });
|
|
1660
|
+
|
|
1112
1661
|
/**
|
|
1113
1662
|
* GhostField — transparent-until-hover wrapper for inline-editable fields.
|
|
1114
1663
|
*
|
|
@@ -2936,5 +3485,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.23", ngImpo
|
|
|
2936
3485
|
* Generated bundle index. Do not edit.
|
|
2937
3486
|
*/
|
|
2938
3487
|
|
|
2939
|
-
export { Checkbox, GhostField, NumberInput, PageHeader, Toggle, WmAnchoredScrollStrategy, WmAutoFocus, WmButton, WmCombobox, WmDialog, WmModal, WmPopover, WmRichTooltip, WmRichTooltipContent, WmRichTooltipTrigger, WmSelect, WmSlider, WmTab, WmTabs, WmToastContainer, WmToastService, WmTooltip, onViewportChange };
|
|
3488
|
+
export { Checkbox, GhostField, NumberInput, PageHeader, Toggle, WmAnchoredScrollStrategy, WmAutoFocus, WmButton, WmButtonDirective, WmCombobox, WmDialog, WmModal, WmPopover, WmRichTooltip, WmRichTooltipContent, WmRichTooltipTrigger, WmSelect, WmSlider, WmTab, WmTabs, WmToastContainer, WmToastService, WmToggleGroup, WmToggleGroupItem, WmTooltip, onViewportChange };
|
|
2940
3489
|
//# sourceMappingURL=wildmason-aegis.mjs.map
|