@wildmason/aegis 1.5.4
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/dist/fesm2022/wildmason-aegis.mjs +883 -0
- package/dist/fesm2022/wildmason-aegis.mjs.map +1 -0
- package/dist/types/wildmason-aegis.d.ts +545 -0
- package/inputs.css +1537 -0
- package/package.json +57 -0
- package/themes.css +778 -0
|
@@ -0,0 +1,883 @@
|
|
|
1
|
+
import * as i0 from '@angular/core';
|
|
2
|
+
import { input, output, HostListener, ChangeDetectionStrategy, Component, model, signal, computed, inject, ElementRef, Renderer2, ChangeDetectorRef, viewChild, effect, ViewEncapsulation, Input, Directive, contentChildren, forwardRef, Injectable } from '@angular/core';
|
|
3
|
+
import { CdkTrapFocus } from '@angular/cdk/a11y';
|
|
4
|
+
import { CdkConnectedOverlay, CdkOverlayOrigin } from '@angular/cdk/overlay';
|
|
5
|
+
import { NgTemplateOutlet } from '@angular/common';
|
|
6
|
+
import { NG_VALUE_ACCESSOR } from '@angular/forms';
|
|
7
|
+
import { invoke } from '@tauri-apps/api/core';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* WmModal — Lightweight modal backdrop with Escape key and click-outside dismissal.
|
|
11
|
+
*
|
|
12
|
+
* Owns the fixed backdrop, centering, and keyboard/pointer dismissal so individual
|
|
13
|
+
* modal components don't need to repeat this logic or add their own HostListeners.
|
|
14
|
+
*
|
|
15
|
+
* Usage:
|
|
16
|
+
* <wm-modal [visible]="visible()" (dismiss)="close()">
|
|
17
|
+
* <div class="p-6 wm-r-md w-80" style="background: var(--wm-bg-raised)">
|
|
18
|
+
* <!-- modal panel content -->
|
|
19
|
+
* </div>
|
|
20
|
+
* </wm-modal>
|
|
21
|
+
*
|
|
22
|
+
* For top-aligned panels (e.g. search/command palettes), use align="top":
|
|
23
|
+
* <wm-modal [visible]="visible()" (dismiss)="close()" align="top">
|
|
24
|
+
*
|
|
25
|
+
* The projected content is wrapped in a stop-propagation container so clicks inside
|
|
26
|
+
* the panel do not bubble up to the backdrop and trigger a dismiss.
|
|
27
|
+
*/
|
|
28
|
+
class WmModal {
|
|
29
|
+
/** Whether the modal is currently open. */
|
|
30
|
+
visible = input.required(...(ngDevMode ? [{ debugName: "visible" }] : []));
|
|
31
|
+
/** Emitted when the user requests the modal to close (Escape or backdrop click). */
|
|
32
|
+
dismiss = output();
|
|
33
|
+
/**
|
|
34
|
+
* Vertical alignment of the panel within the backdrop.
|
|
35
|
+
* - 'center' (default): panel is vertically centered (items-center)
|
|
36
|
+
* - 'top': panel appears near the top (items-start pt-20), for search/command overlays
|
|
37
|
+
*/
|
|
38
|
+
align = input('center', ...(ngDevMode ? [{ debugName: "align" }] : []));
|
|
39
|
+
onEscape() {
|
|
40
|
+
if (this.visible())
|
|
41
|
+
this.dismiss.emit();
|
|
42
|
+
}
|
|
43
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.1", ngImport: i0, type: WmModal, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
44
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.1", 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()) {\r\n <div [class]=\"align() === 'top'\r\n ? 'fixed inset-0 flex items-start justify-center pt-20 z-50'\r\n : 'fixed inset-0 flex items-center justify-center z-50'\"\r\n style=\"background: var(--wm-modal-backdrop)\"\r\n (click)=\"dismiss.emit()\">\r\n <div (click)=\"$event.stopPropagation()\">\r\n <ng-content />\r\n </div>\r\n </div>\r\n}\r\n", changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
45
|
+
}
|
|
46
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.1", ngImport: i0, type: WmModal, decorators: [{
|
|
47
|
+
type: Component,
|
|
48
|
+
args: [{ selector: 'wm-modal', standalone: true, changeDetection: ChangeDetectionStrategy.OnPush, template: "@if (visible()) {\r\n <div [class]=\"align() === 'top'\r\n ? 'fixed inset-0 flex items-start justify-center pt-20 z-50'\r\n : 'fixed inset-0 flex items-center justify-center z-50'\"\r\n style=\"background: var(--wm-modal-backdrop)\"\r\n (click)=\"dismiss.emit()\">\r\n <div (click)=\"$event.stopPropagation()\">\r\n <ng-content />\r\n </div>\r\n </div>\r\n}\r\n" }]
|
|
49
|
+
}], 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: [{
|
|
50
|
+
type: HostListener,
|
|
51
|
+
args: ['document:keydown.escape']
|
|
52
|
+
}] } });
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* WmDialog — Modal dialog with backdrop, focus trap, and Escape key handling.
|
|
56
|
+
*
|
|
57
|
+
* Renders a fixed-position overlay that is always viewport-relative (not clipped
|
|
58
|
+
* by ancestor overflow:hidden). Do NOT place inside a container that uses
|
|
59
|
+
* CSS transform, perspective, or filter — fixed positioning breaks in that context.
|
|
60
|
+
*
|
|
61
|
+
* Usage:
|
|
62
|
+
* <wm-dialog [(isOpen)]="open" title="Confirm action">
|
|
63
|
+
* <div body>
|
|
64
|
+
* <p>Are you sure?</p>
|
|
65
|
+
* <div class="wm-dialog-footer">
|
|
66
|
+
* <wm-button variant="secondary" (click)="open.set(false)">Cancel</wm-button>
|
|
67
|
+
* <wm-button variant="primary" (click)="onConfirm()">Confirm</wm-button>
|
|
68
|
+
* </div>
|
|
69
|
+
* </div>
|
|
70
|
+
* </wm-dialog>
|
|
71
|
+
*
|
|
72
|
+
* Accessibility:
|
|
73
|
+
* - role="dialog" + aria-modal="true" on the panel
|
|
74
|
+
* - aria-labelledby wired to the title element (when title is set)
|
|
75
|
+
* - Focus is trapped inside the panel when open (cdkTrapFocus)
|
|
76
|
+
* - Escape key closes the dialog
|
|
77
|
+
* - Clicking the backdrop closes the dialog
|
|
78
|
+
* - On close, focus returns to the element that opened the dialog
|
|
79
|
+
* (browser default when focus trap is released)
|
|
80
|
+
*/
|
|
81
|
+
class WmDialog {
|
|
82
|
+
/** Two-way binding for open/closed state. */
|
|
83
|
+
isOpen = model(false, ...(ngDevMode ? [{ debugName: "isOpen" }] : []));
|
|
84
|
+
/** Dialog heading text. When set, aria-labelledby is applied automatically. */
|
|
85
|
+
title = input('', ...(ngDevMode ? [{ debugName: "title" }] : []));
|
|
86
|
+
/** Panel width preset. sm = 360px, md = 480px (default), lg = 640px. */
|
|
87
|
+
size = input('md', ...(ngDevMode ? [{ debugName: "size" }] : []));
|
|
88
|
+
close() { this.isOpen.set(false); }
|
|
89
|
+
onEscape() {
|
|
90
|
+
if (this.isOpen())
|
|
91
|
+
this.close();
|
|
92
|
+
}
|
|
93
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.1", ngImport: i0, type: WmDialog, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
94
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.1", type: WmDialog, isStandalone: true, selector: "wm-dialog", inputs: { isOpen: { classPropertyName: "isOpen", publicName: "isOpen", isSignal: true, isRequired: false, transformFunction: null }, title: { classPropertyName: "title", publicName: "title", isSignal: true, isRequired: false, transformFunction: null }, size: { classPropertyName: "size", publicName: "size", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { isOpen: "isOpenChange" }, host: { listeners: { "document:keydown.escape": "onEscape()" } }, ngImport: i0, template: "@if (isOpen()) {\r\n <!-- Backdrop \u2014 click to close -->\r\n <div class=\"wm-dialog-backdrop\" (click)=\"close()\" aria-hidden=\"true\"></div>\r\n\r\n <!-- Panel -->\r\n <div\r\n class=\"wm-dialog\"\r\n [class]=\"'wm-dialog--' + size()\"\r\n role=\"dialog\"\r\n aria-modal=\"true\"\r\n [attr.aria-labelledby]=\"title() ? 'wm-dialog-title' : null\"\r\n cdkTrapFocus\r\n [cdkTrapFocusAutoCapture]=\"true\"\r\n >\r\n <div class=\"wm-dialog-header\">\r\n <h2 class=\"wm-dialog-title\" id=\"wm-dialog-title\">{{ title() }}</h2>\r\n <button\r\n class=\"wm-dialog-close\"\r\n type=\"button\"\r\n (click)=\"close()\"\r\n aria-label=\"Close dialog\"\r\n >\r\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\"\r\n stroke=\"currentColor\" stroke-width=\"2\"\r\n stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\">\r\n <line x1=\"18\" y1=\"6\" x2=\"6\" y2=\"18\"/>\r\n <line x1=\"6\" y1=\"6\" x2=\"18\" y2=\"18\"/>\r\n </svg>\r\n </button>\r\n </div>\r\n\r\n <div class=\"wm-dialog-body\">\r\n <ng-content select=\"[body]\" />\r\n </div>\r\n </div>\r\n}\r\n", styles: [":host{display:contents}.wm-dialog-backdrop{position:fixed;inset:0;background:rgba(0,0,0,var(--wm-backdrop-opacity));z-index:100}.wm-dialog{position:fixed;top:50%;left:50%;transform:translate(-50%,-50%);z-index:101;background:var(--wm-bg-float);border:1px solid var(--wm-border);border-radius:var(--wm-radius-lg);box-shadow:var(--wm-shadow-lg);width:var(--wm-dialog-width, 480px);max-width:calc(100vw - 2rem);max-height:calc(100dvh - 4rem);display:flex;flex-direction:column;overflow:hidden;outline:none}.wm-dialog--sm{width:var(--wm-dialog-width, 360px)}.wm-dialog--lg{width:var(--wm-dialog-width, 640px)}.wm-dialog-header{display:flex;align-items:center;justify-content:space-between;gap:.75rem;padding:1.125rem 1.25rem;flex-shrink:0;background:var(--wm-bg-raised)}.wm-dialog-title{font-size:1rem;font-weight:600;color:var(--wm-text-primary);margin:0;line-height:1.3}.wm-dialog-close{display:flex;align-items:center;justify-content:center;width:28px;height:28px;flex-shrink:0;border-radius:var(--wm-radius-sm);border:none;background:transparent;color:var(--wm-text-muted);cursor:pointer;transition:background var(--wm-transition-fast),color var(--wm-transition-fast)}.wm-dialog-close:hover{background:var(--wm-bg-hover);color:var(--wm-text-primary)}.wm-dialog-close:focus-visible{outline:2px solid var(--wm-color-accent);outline-offset:1px}.wm-dialog-body{flex:1;overflow-y:auto;padding:1rem 1.25rem 1.25rem;font-size:.875rem;color:var(--wm-text-secondary);line-height:1.6}@media(prefers-reduced-motion:reduce){.wm-dialog-close{transition:none}}\n"], dependencies: [{ kind: "directive", type: CdkTrapFocus, selector: "[cdkTrapFocus]", inputs: ["cdkTrapFocus", "cdkTrapFocusAutoCapture"], exportAs: ["cdkTrapFocus"] }] });
|
|
95
|
+
}
|
|
96
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.1", ngImport: i0, type: WmDialog, decorators: [{
|
|
97
|
+
type: Component,
|
|
98
|
+
args: [{ selector: 'wm-dialog', imports: [CdkTrapFocus], template: "@if (isOpen()) {\r\n <!-- Backdrop \u2014 click to close -->\r\n <div class=\"wm-dialog-backdrop\" (click)=\"close()\" aria-hidden=\"true\"></div>\r\n\r\n <!-- Panel -->\r\n <div\r\n class=\"wm-dialog\"\r\n [class]=\"'wm-dialog--' + size()\"\r\n role=\"dialog\"\r\n aria-modal=\"true\"\r\n [attr.aria-labelledby]=\"title() ? 'wm-dialog-title' : null\"\r\n cdkTrapFocus\r\n [cdkTrapFocusAutoCapture]=\"true\"\r\n >\r\n <div class=\"wm-dialog-header\">\r\n <h2 class=\"wm-dialog-title\" id=\"wm-dialog-title\">{{ title() }}</h2>\r\n <button\r\n class=\"wm-dialog-close\"\r\n type=\"button\"\r\n (click)=\"close()\"\r\n aria-label=\"Close dialog\"\r\n >\r\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\"\r\n stroke=\"currentColor\" stroke-width=\"2\"\r\n stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\">\r\n <line x1=\"18\" y1=\"6\" x2=\"6\" y2=\"18\"/>\r\n <line x1=\"6\" y1=\"6\" x2=\"18\" y2=\"18\"/>\r\n </svg>\r\n </button>\r\n </div>\r\n\r\n <div class=\"wm-dialog-body\">\r\n <ng-content select=\"[body]\" />\r\n </div>\r\n </div>\r\n}\r\n", styles: [":host{display:contents}.wm-dialog-backdrop{position:fixed;inset:0;background:rgba(0,0,0,var(--wm-backdrop-opacity));z-index:100}.wm-dialog{position:fixed;top:50%;left:50%;transform:translate(-50%,-50%);z-index:101;background:var(--wm-bg-float);border:1px solid var(--wm-border);border-radius:var(--wm-radius-lg);box-shadow:var(--wm-shadow-lg);width:var(--wm-dialog-width, 480px);max-width:calc(100vw - 2rem);max-height:calc(100dvh - 4rem);display:flex;flex-direction:column;overflow:hidden;outline:none}.wm-dialog--sm{width:var(--wm-dialog-width, 360px)}.wm-dialog--lg{width:var(--wm-dialog-width, 640px)}.wm-dialog-header{display:flex;align-items:center;justify-content:space-between;gap:.75rem;padding:1.125rem 1.25rem;flex-shrink:0;background:var(--wm-bg-raised)}.wm-dialog-title{font-size:1rem;font-weight:600;color:var(--wm-text-primary);margin:0;line-height:1.3}.wm-dialog-close{display:flex;align-items:center;justify-content:center;width:28px;height:28px;flex-shrink:0;border-radius:var(--wm-radius-sm);border:none;background:transparent;color:var(--wm-text-muted);cursor:pointer;transition:background var(--wm-transition-fast),color var(--wm-transition-fast)}.wm-dialog-close:hover{background:var(--wm-bg-hover);color:var(--wm-text-primary)}.wm-dialog-close:focus-visible{outline:2px solid var(--wm-color-accent);outline-offset:1px}.wm-dialog-body{flex:1;overflow-y:auto;padding:1rem 1.25rem 1.25rem;font-size:.875rem;color:var(--wm-text-secondary);line-height:1.6}@media(prefers-reduced-motion:reduce){.wm-dialog-close{transition:none}}\n"] }]
|
|
99
|
+
}], propDecorators: { isOpen: [{ type: i0.Input, args: [{ isSignal: true, alias: "isOpen", required: false }] }, { type: i0.Output, args: ["isOpenChange"] }], title: [{ type: i0.Input, args: [{ isSignal: true, alias: "title", required: false }] }], size: [{ type: i0.Input, args: [{ isSignal: true, alias: "size", required: false }] }], onEscape: [{
|
|
100
|
+
type: HostListener,
|
|
101
|
+
args: ['document:keydown.escape']
|
|
102
|
+
}] } });
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* WmSelect — Accessible dropdown select built on Angular CDK Overlay.
|
|
106
|
+
*
|
|
107
|
+
* Provides a styled, keyboard-accessible alternative to the native <select> element
|
|
108
|
+
* when you need custom option rendering or consistent cross-platform appearance.
|
|
109
|
+
*
|
|
110
|
+
* When to use:
|
|
111
|
+
* - Single selection from a list of 4+ options
|
|
112
|
+
* - When option labels are complex (icons, descriptions, status badges)
|
|
113
|
+
* - When native <select> appearance is insufficient
|
|
114
|
+
*
|
|
115
|
+
* When NOT to use:
|
|
116
|
+
* - 3 or fewer options → use a radio group (more scannable, no extra click)
|
|
117
|
+
* - Multi-select → use a checkbox group or a combobox with chips
|
|
118
|
+
* - Yes/No choices → use a Toggle or two radio buttons
|
|
119
|
+
*
|
|
120
|
+
* Options API:
|
|
121
|
+
* options: SelectOption[] — Array of { value, label, disabled? } objects.
|
|
122
|
+
*
|
|
123
|
+
* Two-way binding:
|
|
124
|
+
* <wm-select [(value)]="selectedValue" [options]="myOptions" />
|
|
125
|
+
*
|
|
126
|
+
* Accessibility:
|
|
127
|
+
* - Trigger has role="combobox", aria-expanded, aria-haspopup="listbox"
|
|
128
|
+
* - Panel has role="listbox"; each option has role="option" + aria-selected
|
|
129
|
+
* - Disabled options have aria-disabled="true"
|
|
130
|
+
* - Focus is trapped inside the open panel (cdkTrapFocus)
|
|
131
|
+
* - Clicking the backdrop closes the panel and returns focus to the trigger
|
|
132
|
+
* - Keyboard: Enter/Space on trigger opens; Escape closes; arrow keys
|
|
133
|
+
* navigate options (relies on natural tab order within the panel)
|
|
134
|
+
* - Always pair with a visible label via <wm-label> or aria-label on the host
|
|
135
|
+
*/
|
|
136
|
+
class WmSelect {
|
|
137
|
+
/** Array of options to display. */
|
|
138
|
+
options = input([], ...(ngDevMode ? [{ debugName: "options" }] : []));
|
|
139
|
+
/** The currently selected value. Two-way bindable. */
|
|
140
|
+
value = model(null, ...(ngDevMode ? [{ debugName: "value" }] : []));
|
|
141
|
+
/** Placeholder text shown when no option is selected. */
|
|
142
|
+
placeholder = input('Select…', ...(ngDevMode ? [{ debugName: "placeholder" }] : []));
|
|
143
|
+
/** Disables the trigger and prevents the panel from opening. */
|
|
144
|
+
disabled = input(false, ...(ngDevMode ? [{ debugName: "disabled" }] : []));
|
|
145
|
+
isOpen = signal(false, ...(ngDevMode ? [{ debugName: "isOpen" }] : []));
|
|
146
|
+
selectedOption = computed(() => this.options().find((o) => o.value === this.value()) ?? null, ...(ngDevMode ? [{ debugName: "selectedOption" }] : []));
|
|
147
|
+
hostEl = inject((ElementRef));
|
|
148
|
+
get overlayWidth() {
|
|
149
|
+
return this.hostEl.nativeElement.offsetWidth || 200;
|
|
150
|
+
}
|
|
151
|
+
positions = [
|
|
152
|
+
{ originX: 'start', originY: 'bottom', overlayX: 'start', overlayY: 'top', offsetY: 4 },
|
|
153
|
+
{ originX: 'start', originY: 'top', overlayX: 'start', overlayY: 'bottom', offsetY: -4 },
|
|
154
|
+
];
|
|
155
|
+
toggleOpen() {
|
|
156
|
+
if (!this.disabled()) {
|
|
157
|
+
this.isOpen.update((v) => !v);
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
selectOption(opt) {
|
|
161
|
+
if (!opt.disabled) {
|
|
162
|
+
this.value.set(opt.value);
|
|
163
|
+
this.isOpen.set(false);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.1", ngImport: i0, type: WmSelect, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
167
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.1", type: WmSelect, isStandalone: true, selector: "wm-select", inputs: { options: { classPropertyName: "options", publicName: "options", isSignal: true, isRequired: false, transformFunction: null }, value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, placeholder: { classPropertyName: "placeholder", publicName: "placeholder", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { value: "valueChange" }, ngImport: i0, template: "<!-- Trigger button \u2014 anchors the CDK overlay -->\r\n<div\r\n class=\"wm-select-host\"\r\n cdkOverlayOrigin\r\n #origin=\"cdkOverlayOrigin\"\r\n>\r\n <button\r\n type=\"button\"\r\n role=\"combobox\"\r\n class=\"wm-select-trigger\"\r\n [class.is-open]=\"isOpen()\"\r\n [class.is-disabled]=\"disabled()\"\r\n [attr.aria-expanded]=\"isOpen()\"\r\n aria-haspopup=\"listbox\"\r\n [attr.aria-disabled]=\"disabled() ? true : null\"\r\n [disabled]=\"disabled() || null\"\r\n (click)=\"toggleOpen()\"\r\n >\r\n <span class=\"wm-select-value\" [class.is-placeholder]=\"!selectedOption()\">\r\n {{ selectedOption()?.label ?? placeholder() }}\r\n </span>\r\n <!-- Chevron icon \u2014 rotates when open -->\r\n <svg\r\n class=\"wm-select-chevron\"\r\n [class.is-open]=\"isOpen()\"\r\n width=\"16\" height=\"16\" viewBox=\"0 0 24 24\"\r\n fill=\"none\" aria-hidden=\"true\"\r\n >\r\n <path d=\"M6 9l6 6 6-6\"\r\n stroke=\"currentColor\" stroke-width=\"2\"\r\n stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\r\n </svg>\r\n </button>\r\n</div>\r\n\r\n<!-- Dropdown panel \u2014 rendered in CDK overlay container -->\r\n<ng-template\r\n cdkConnectedOverlay\r\n [cdkConnectedOverlayOrigin]=\"origin\"\r\n [cdkConnectedOverlayOpen]=\"isOpen()\"\r\n [cdkConnectedOverlayPositions]=\"positions\"\r\n [cdkConnectedOverlayWidth]=\"overlayWidth\"\r\n [cdkConnectedOverlayHasBackdrop]=\"true\"\r\n cdkConnectedOverlayBackdropClass=\"wm-select-backdrop\"\r\n (backdropClick)=\"isOpen.set(false)\"\r\n (detach)=\"isOpen.set(false)\"\r\n>\r\n <div\r\n class=\"wm-select-panel\"\r\n role=\"listbox\"\r\n cdkTrapFocus\r\n [cdkTrapFocusAutoCapture]=\"true\"\r\n >\r\n @for (opt of options(); track opt.value) {\r\n <button\r\n type=\"button\"\r\n role=\"option\"\r\n class=\"wm-select-option\"\r\n [class.is-selected]=\"opt.value === value()\"\r\n [class.is-disabled]=\"!!opt.disabled\"\r\n [attr.aria-selected]=\"opt.value === value()\"\r\n [attr.aria-disabled]=\"opt.disabled ? true : null\"\r\n [disabled]=\"opt.disabled || null\"\r\n (click)=\"selectOption(opt)\"\r\n >\r\n <span class=\"wm-select-option-label\">{{ opt.label }}</span>\r\n @if (opt.value === value()) {\r\n <!-- Checkmark for selected option -->\r\n <svg width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" aria-hidden=\"true\">\r\n <polyline points=\"20 6 9 17 4 12\"\r\n stroke=\"currentColor\" stroke-width=\"2.5\"\r\n stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\r\n </svg>\r\n }\r\n </button>\r\n }\r\n </div>\r\n</ng-template>\r\n", styles: [":host{display:block}.wm-select-host{display:block}.wm-select-trigger{display:flex;align-items:center;justify-content:space-between;gap:.5rem;width:100%;padding:0 .75rem;height:36px;background:var(--wm-bg-base);border:1px solid var(--wm-border-input);border-radius:var(--wm-radius-md);color:var(--wm-text-primary);font-size:.875rem;font-family:inherit;cursor:pointer;transition:border-color var(--wm-transition-fast),box-shadow var(--wm-transition-fast);outline:none;text-align:left}.wm-select-trigger:hover:not(.is-disabled){border-color:var(--wm-color-accent)}.wm-select-trigger:focus-visible{border-color:var(--wm-color-accent);box-shadow:0 0 0 3px color-mix(in srgb,var(--wm-color-accent) 25%,transparent)}.wm-select-trigger.is-open{border-color:var(--wm-color-accent);box-shadow:0 0 0 3px color-mix(in srgb,var(--wm-color-accent) 20%,transparent)}.wm-select-trigger.is-disabled{opacity:.45;cursor:not-allowed}.wm-select-value{flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.wm-select-value.is-placeholder{color:var(--wm-text-placeholder)}.wm-select-chevron{flex-shrink:0;color:var(--wm-text-muted);transition:transform var(--wm-transition-fast)}.wm-select-chevron.is-open{transform:rotate(180deg)}.wm-select-backdrop{position:fixed;inset:0}.wm-select-panel{width:100%;background:var(--wm-bg-float);border:1px solid var(--wm-border);border-radius:var(--wm-radius-lg);box-shadow:0 4px 6px -1px #00000014,0 10px 20px -4px #0000001f;overflow:hidden;outline:none;padding:.25rem}.wm-select-option{display:flex;align-items:center;justify-content:space-between;gap:.5rem;width:100%;padding:.4375rem .625rem;border:none;border-radius:var(--wm-radius-sm);background:transparent;color:var(--wm-text-primary);font-size:.875rem;font-family:inherit;cursor:pointer;text-align:left;transition:background var(--wm-transition-fast)}.wm-select-option:hover:not(.is-disabled):not(.is-selected){background:var(--wm-bg-active)}.wm-select-option.is-selected{background:color-mix(in srgb,var(--wm-color-accent) 10%,transparent);color:var(--wm-color-accent);font-weight:500}.wm-select-option.is-disabled{opacity:.4;cursor:not-allowed}.wm-select-option-label{flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.wm-select-option svg{flex-shrink:0;color:var(--wm-color-accent)}\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"] }] });
|
|
168
|
+
}
|
|
169
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.1", ngImport: i0, type: WmSelect, decorators: [{
|
|
170
|
+
type: Component,
|
|
171
|
+
args: [{ selector: 'wm-select', imports: [CdkConnectedOverlay, CdkOverlayOrigin, CdkTrapFocus], template: "<!-- Trigger button \u2014 anchors the CDK overlay -->\r\n<div\r\n class=\"wm-select-host\"\r\n cdkOverlayOrigin\r\n #origin=\"cdkOverlayOrigin\"\r\n>\r\n <button\r\n type=\"button\"\r\n role=\"combobox\"\r\n class=\"wm-select-trigger\"\r\n [class.is-open]=\"isOpen()\"\r\n [class.is-disabled]=\"disabled()\"\r\n [attr.aria-expanded]=\"isOpen()\"\r\n aria-haspopup=\"listbox\"\r\n [attr.aria-disabled]=\"disabled() ? true : null\"\r\n [disabled]=\"disabled() || null\"\r\n (click)=\"toggleOpen()\"\r\n >\r\n <span class=\"wm-select-value\" [class.is-placeholder]=\"!selectedOption()\">\r\n {{ selectedOption()?.label ?? placeholder() }}\r\n </span>\r\n <!-- Chevron icon \u2014 rotates when open -->\r\n <svg\r\n class=\"wm-select-chevron\"\r\n [class.is-open]=\"isOpen()\"\r\n width=\"16\" height=\"16\" viewBox=\"0 0 24 24\"\r\n fill=\"none\" aria-hidden=\"true\"\r\n >\r\n <path d=\"M6 9l6 6 6-6\"\r\n stroke=\"currentColor\" stroke-width=\"2\"\r\n stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\r\n </svg>\r\n </button>\r\n</div>\r\n\r\n<!-- Dropdown panel \u2014 rendered in CDK overlay container -->\r\n<ng-template\r\n cdkConnectedOverlay\r\n [cdkConnectedOverlayOrigin]=\"origin\"\r\n [cdkConnectedOverlayOpen]=\"isOpen()\"\r\n [cdkConnectedOverlayPositions]=\"positions\"\r\n [cdkConnectedOverlayWidth]=\"overlayWidth\"\r\n [cdkConnectedOverlayHasBackdrop]=\"true\"\r\n cdkConnectedOverlayBackdropClass=\"wm-select-backdrop\"\r\n (backdropClick)=\"isOpen.set(false)\"\r\n (detach)=\"isOpen.set(false)\"\r\n>\r\n <div\r\n class=\"wm-select-panel\"\r\n role=\"listbox\"\r\n cdkTrapFocus\r\n [cdkTrapFocusAutoCapture]=\"true\"\r\n >\r\n @for (opt of options(); track opt.value) {\r\n <button\r\n type=\"button\"\r\n role=\"option\"\r\n class=\"wm-select-option\"\r\n [class.is-selected]=\"opt.value === value()\"\r\n [class.is-disabled]=\"!!opt.disabled\"\r\n [attr.aria-selected]=\"opt.value === value()\"\r\n [attr.aria-disabled]=\"opt.disabled ? true : null\"\r\n [disabled]=\"opt.disabled || null\"\r\n (click)=\"selectOption(opt)\"\r\n >\r\n <span class=\"wm-select-option-label\">{{ opt.label }}</span>\r\n @if (opt.value === value()) {\r\n <!-- Checkmark for selected option -->\r\n <svg width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" aria-hidden=\"true\">\r\n <polyline points=\"20 6 9 17 4 12\"\r\n stroke=\"currentColor\" stroke-width=\"2.5\"\r\n stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\r\n </svg>\r\n }\r\n </button>\r\n }\r\n </div>\r\n</ng-template>\r\n", styles: [":host{display:block}.wm-select-host{display:block}.wm-select-trigger{display:flex;align-items:center;justify-content:space-between;gap:.5rem;width:100%;padding:0 .75rem;height:36px;background:var(--wm-bg-base);border:1px solid var(--wm-border-input);border-radius:var(--wm-radius-md);color:var(--wm-text-primary);font-size:.875rem;font-family:inherit;cursor:pointer;transition:border-color var(--wm-transition-fast),box-shadow var(--wm-transition-fast);outline:none;text-align:left}.wm-select-trigger:hover:not(.is-disabled){border-color:var(--wm-color-accent)}.wm-select-trigger:focus-visible{border-color:var(--wm-color-accent);box-shadow:0 0 0 3px color-mix(in srgb,var(--wm-color-accent) 25%,transparent)}.wm-select-trigger.is-open{border-color:var(--wm-color-accent);box-shadow:0 0 0 3px color-mix(in srgb,var(--wm-color-accent) 20%,transparent)}.wm-select-trigger.is-disabled{opacity:.45;cursor:not-allowed}.wm-select-value{flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.wm-select-value.is-placeholder{color:var(--wm-text-placeholder)}.wm-select-chevron{flex-shrink:0;color:var(--wm-text-muted);transition:transform var(--wm-transition-fast)}.wm-select-chevron.is-open{transform:rotate(180deg)}.wm-select-backdrop{position:fixed;inset:0}.wm-select-panel{width:100%;background:var(--wm-bg-float);border:1px solid var(--wm-border);border-radius:var(--wm-radius-lg);box-shadow:0 4px 6px -1px #00000014,0 10px 20px -4px #0000001f;overflow:hidden;outline:none;padding:.25rem}.wm-select-option{display:flex;align-items:center;justify-content:space-between;gap:.5rem;width:100%;padding:.4375rem .625rem;border:none;border-radius:var(--wm-radius-sm);background:transparent;color:var(--wm-text-primary);font-size:.875rem;font-family:inherit;cursor:pointer;text-align:left;transition:background var(--wm-transition-fast)}.wm-select-option:hover:not(.is-disabled):not(.is-selected){background:var(--wm-bg-active)}.wm-select-option.is-selected{background:color-mix(in srgb,var(--wm-color-accent) 10%,transparent);color:var(--wm-color-accent);font-weight:500}.wm-select-option.is-disabled{opacity:.4;cursor:not-allowed}.wm-select-option-label{flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.wm-select-option svg{flex-shrink:0;color:var(--wm-color-accent)}\n"] }]
|
|
172
|
+
}], propDecorators: { options: [{ type: i0.Input, args: [{ isSignal: true, alias: "options", required: false }] }], value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }, { type: i0.Output, args: ["valueChange"] }], placeholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "placeholder", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }] } });
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* WmPopover — general-purpose CDK-overlay floating panel.
|
|
176
|
+
*
|
|
177
|
+
* A low-level primitive for dropdowns, menus, pickers, and any floating UI.
|
|
178
|
+
* The parent controls open/close state; this component handles positioning,
|
|
179
|
+
* backdrop, focus-trapping, and ARIA attribute management.
|
|
180
|
+
*
|
|
181
|
+
* Slots:
|
|
182
|
+
* - [trigger] — the anchor element (button, icon, etc.)
|
|
183
|
+
* - [content] — the panel body (menu items, form, etc.)
|
|
184
|
+
*
|
|
185
|
+
* Usage:
|
|
186
|
+
* ```html
|
|
187
|
+
* <wm-popover [isOpen]="open()" (closed)="open.set(false)" role="menu" hasPopup="menu" align="end">
|
|
188
|
+
* <button trigger (click)="open.set(!open())">Options</button>
|
|
189
|
+
* <div content role="menu">
|
|
190
|
+
* <button role="menuitem" (click)="doThing()">Do thing</button>
|
|
191
|
+
* </div>
|
|
192
|
+
* </wm-popover>
|
|
193
|
+
* ```
|
|
194
|
+
*
|
|
195
|
+
* Accessibility:
|
|
196
|
+
* - Trigger element automatically receives aria-haspopup, aria-expanded, aria-controls
|
|
197
|
+
* - Panel receives the configured role
|
|
198
|
+
* - Focus is trapped inside the open panel (cdkTrapFocus)
|
|
199
|
+
* - Escape key closes the panel
|
|
200
|
+
* - Backdrop click closes the panel
|
|
201
|
+
*
|
|
202
|
+
* Auto-focus:
|
|
203
|
+
* - Add [wmAutoFocus] to any element inside [content] to focus it on open.
|
|
204
|
+
* Import WmAutoFocus from @wildmason/aegis/components and add it to your component's imports.
|
|
205
|
+
*/
|
|
206
|
+
class WmPopover {
|
|
207
|
+
/** Whether the panel is currently open. */
|
|
208
|
+
isOpen = input(false, ...(ngDevMode ? [{ debugName: "isOpen" }] : []));
|
|
209
|
+
/** Panel width in pixels, or 'auto' to match content. */
|
|
210
|
+
width = input('auto', ...(ngDevMode ? [{ debugName: "width" }] : []));
|
|
211
|
+
/** ARIA role for the floating panel element. Use 'menu', 'listbox', 'dialog', etc. */
|
|
212
|
+
role = input('dialog', ...(ngDevMode ? [{ debugName: "role" }] : []));
|
|
213
|
+
/** Value for aria-haspopup on the trigger element. */
|
|
214
|
+
hasPopup = input('dialog', ...(ngDevMode ? [{ debugName: "hasPopup" }] : []));
|
|
215
|
+
/** Horizontal alignment: 'start' aligns left edges, 'end' aligns right edges. */
|
|
216
|
+
align = input('start', ...(ngDevMode ? [{ debugName: "align" }] : []));
|
|
217
|
+
/** Emitted when the panel should close (backdrop click, Escape key, CDK detach). */
|
|
218
|
+
closed = output();
|
|
219
|
+
renderer = inject(Renderer2);
|
|
220
|
+
hostEl = inject(ElementRef);
|
|
221
|
+
cdr = inject(ChangeDetectorRef);
|
|
222
|
+
panelId = 'wm-popover-' + Math.random().toString(36).substring(2, 9);
|
|
223
|
+
panel = viewChild('panel', ...(ngDevMode ? [{ debugName: "panel" }] : []));
|
|
224
|
+
constructor() {
|
|
225
|
+
effect(() => {
|
|
226
|
+
const open = this.isOpen();
|
|
227
|
+
const popup = this.hasPopup();
|
|
228
|
+
this.cdr.markForCheck();
|
|
229
|
+
const el = this.hostEl.nativeElement.querySelector('[trigger]');
|
|
230
|
+
if (!el)
|
|
231
|
+
return;
|
|
232
|
+
this.renderer.setAttribute(el, 'aria-haspopup', String(popup));
|
|
233
|
+
this.renderer.setAttribute(el, 'aria-expanded', String(open));
|
|
234
|
+
this.renderer.setAttribute(el, 'aria-controls', this.panelId);
|
|
235
|
+
if (!el.getAttribute('role')) {
|
|
236
|
+
this.renderer.setAttribute(el, 'role', 'button');
|
|
237
|
+
}
|
|
238
|
+
});
|
|
239
|
+
}
|
|
240
|
+
get positions() {
|
|
241
|
+
const x = this.align();
|
|
242
|
+
return [
|
|
243
|
+
{ originX: x, originY: 'bottom', overlayX: x, overlayY: 'top', offsetY: 4 },
|
|
244
|
+
{ originX: x, originY: 'top', overlayX: x, overlayY: 'bottom', offsetY: -4 },
|
|
245
|
+
];
|
|
246
|
+
}
|
|
247
|
+
onAttach() {
|
|
248
|
+
setTimeout(() => {
|
|
249
|
+
const panelEl = this.panel();
|
|
250
|
+
if (!panelEl)
|
|
251
|
+
return;
|
|
252
|
+
const el = panelEl.nativeElement.querySelector('[wmAutoFocus]');
|
|
253
|
+
if (!el)
|
|
254
|
+
return;
|
|
255
|
+
el.focus();
|
|
256
|
+
if (typeof el.showPicker === 'function') {
|
|
257
|
+
const type = el.type;
|
|
258
|
+
if (el.tagName === 'SELECT' || ['date', 'time', 'color'].includes(type)) {
|
|
259
|
+
try {
|
|
260
|
+
el.showPicker();
|
|
261
|
+
}
|
|
262
|
+
catch { /* ignore */ }
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
}, 10);
|
|
266
|
+
}
|
|
267
|
+
onClose() {
|
|
268
|
+
this.closed.emit();
|
|
269
|
+
}
|
|
270
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.1", ngImport: i0, type: WmPopover, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
271
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.2.0", version: "21.2.1", 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 }, hasPopup: { classPropertyName: "hasPopup", publicName: "hasPopup", isSignal: true, isRequired: false, transformFunction: null }, align: { classPropertyName: "align", publicName: "align", 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 -->\r\n<div cdkOverlayOrigin #origin=\"cdkOverlayOrigin\" class=\"wm-popover-trigger\">\r\n <ng-content select=\"[trigger]\"></ng-content>\r\n</div>\r\n\r\n<!-- Floating panel -->\r\n<ng-template\r\n cdkConnectedOverlay\r\n [cdkConnectedOverlayOrigin]=\"origin\"\r\n [cdkConnectedOverlayOpen]=\"isOpen()\"\r\n [cdkConnectedOverlayPositions]=\"positions\"\r\n [cdkConnectedOverlayHasBackdrop]=\"true\"\r\n cdkConnectedOverlayBackdropClass=\"cdk-overlay-transparent-backdrop\"\r\n (backdropClick)=\"onClose()\"\r\n (detach)=\"onClose()\"\r\n (attach)=\"onAttach()\"\r\n>\r\n <div\r\n #panel\r\n [id]=\"panelId\"\r\n [attr.role]=\"role()\"\r\n class=\"wm-popover-panel\"\r\n [style.width]=\"width() === 'auto' ? 'auto' : width() + 'px'\"\r\n cdkTrapFocus\r\n [cdkTrapFocusAutoCapture]=\"true\"\r\n (keydown.escape)=\"onClose()\"\r\n >\r\n <ng-content select=\"[content]\"></ng-content>\r\n </div>\r\n</ng-template>\r\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 });
|
|
272
|
+
}
|
|
273
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.1", ngImport: i0, type: WmPopover, decorators: [{
|
|
274
|
+
type: Component,
|
|
275
|
+
args: [{ selector: 'wm-popover', imports: [CdkConnectedOverlay, CdkOverlayOrigin, CdkTrapFocus], changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, template: "<!-- Trigger anchor -->\r\n<div cdkOverlayOrigin #origin=\"cdkOverlayOrigin\" class=\"wm-popover-trigger\">\r\n <ng-content select=\"[trigger]\"></ng-content>\r\n</div>\r\n\r\n<!-- Floating panel -->\r\n<ng-template\r\n cdkConnectedOverlay\r\n [cdkConnectedOverlayOrigin]=\"origin\"\r\n [cdkConnectedOverlayOpen]=\"isOpen()\"\r\n [cdkConnectedOverlayPositions]=\"positions\"\r\n [cdkConnectedOverlayHasBackdrop]=\"true\"\r\n cdkConnectedOverlayBackdropClass=\"cdk-overlay-transparent-backdrop\"\r\n (backdropClick)=\"onClose()\"\r\n (detach)=\"onClose()\"\r\n (attach)=\"onAttach()\"\r\n>\r\n <div\r\n #panel\r\n [id]=\"panelId\"\r\n [attr.role]=\"role()\"\r\n class=\"wm-popover-panel\"\r\n [style.width]=\"width() === 'auto' ? 'auto' : width() + 'px'\"\r\n cdkTrapFocus\r\n [cdkTrapFocusAutoCapture]=\"true\"\r\n (keydown.escape)=\"onClose()\"\r\n >\r\n <ng-content select=\"[content]\"></ng-content>\r\n </div>\r\n</ng-template>\r\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"] }]
|
|
276
|
+
}], 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 }] }], hasPopup: [{ type: i0.Input, args: [{ isSignal: true, alias: "hasPopup", required: false }] }], align: [{ type: i0.Input, args: [{ isSignal: true, alias: "align", required: false }] }], closed: [{ type: i0.Output, args: ["closed"] }], panel: [{ type: i0.ViewChild, args: ['panel', { isSignal: true }] }] } });
|
|
277
|
+
|
|
278
|
+
/**
|
|
279
|
+
* WmAutoFocus — focuses an element when it enters the DOM.
|
|
280
|
+
*
|
|
281
|
+
* Usage: add [wmAutoFocus] to any focusable element inside a <wm-popover> panel.
|
|
282
|
+
* For date/time/color/select inputs, also attempts to open the native picker UI.
|
|
283
|
+
*
|
|
284
|
+
* @example
|
|
285
|
+
* <input wmAutoFocus type="text" />
|
|
286
|
+
* <input [wmAutoFocus]="shouldFocus" type="date" />
|
|
287
|
+
*/
|
|
288
|
+
class WmAutoFocus {
|
|
289
|
+
enabled = true;
|
|
290
|
+
#el = inject((ElementRef));
|
|
291
|
+
ngAfterViewInit() {
|
|
292
|
+
if (this.enabled === false)
|
|
293
|
+
return;
|
|
294
|
+
setTimeout(() => {
|
|
295
|
+
const el = this.#el.nativeElement;
|
|
296
|
+
if (!el)
|
|
297
|
+
return;
|
|
298
|
+
el.focus();
|
|
299
|
+
const isPickerType = el.tagName === 'SELECT' ||
|
|
300
|
+
(el.tagName === 'INPUT' &&
|
|
301
|
+
['date', 'time', 'color', 'datetime-local', 'month', 'week'].includes(el.type));
|
|
302
|
+
if (isPickerType && typeof el.showPicker === 'function') {
|
|
303
|
+
try {
|
|
304
|
+
el.showPicker();
|
|
305
|
+
}
|
|
306
|
+
catch {
|
|
307
|
+
// Browser may reject showPicker() if not triggered by user gesture
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
}, 50);
|
|
311
|
+
}
|
|
312
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.1", ngImport: i0, type: WmAutoFocus, deps: [], target: i0.ɵɵFactoryTarget.Directive });
|
|
313
|
+
static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.2.1", type: WmAutoFocus, isStandalone: true, selector: "[wmAutoFocus]", inputs: { enabled: ["wmAutoFocus", "enabled"] }, ngImport: i0 });
|
|
314
|
+
}
|
|
315
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.1", ngImport: i0, type: WmAutoFocus, decorators: [{
|
|
316
|
+
type: Directive,
|
|
317
|
+
args: [{
|
|
318
|
+
selector: '[wmAutoFocus]',
|
|
319
|
+
}]
|
|
320
|
+
}], propDecorators: { enabled: [{
|
|
321
|
+
type: Input,
|
|
322
|
+
args: ['wmAutoFocus']
|
|
323
|
+
}] } });
|
|
324
|
+
|
|
325
|
+
class WmTab {
|
|
326
|
+
value = input.required(...(ngDevMode ? [{ debugName: "value" }] : []));
|
|
327
|
+
tmpl = viewChild.required('tmpl');
|
|
328
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.1", ngImport: i0, type: WmTab, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
329
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.2.0", version: "21.2.1", type: WmTab, isStandalone: true, selector: "wm-tab", inputs: { value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: true, transformFunction: null } }, host: { styleAttribute: "display: none" }, viewQueries: [{ propertyName: "tmpl", first: true, predicate: ["tmpl"], descendants: true, isSignal: true }], ngImport: i0, template: `<ng-template #tmpl><ng-content /></ng-template>`, isInline: true, changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
330
|
+
}
|
|
331
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.1", ngImport: i0, type: WmTab, decorators: [{
|
|
332
|
+
type: Component,
|
|
333
|
+
args: [{
|
|
334
|
+
selector: 'wm-tab',
|
|
335
|
+
template: `<ng-template #tmpl><ng-content /></ng-template>`,
|
|
336
|
+
host: { style: 'display: none' },
|
|
337
|
+
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
338
|
+
}]
|
|
339
|
+
}], propDecorators: { value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: true }] }], tmpl: [{ type: i0.ViewChild, args: ['tmpl', { isSignal: true }] }] } });
|
|
340
|
+
|
|
341
|
+
class WmTabs {
|
|
342
|
+
activeValue = input.required(...(ngDevMode ? [{ debugName: "activeValue" }] : []));
|
|
343
|
+
variant = input('line', ...(ngDevMode ? [{ debugName: "variant" }] : []));
|
|
344
|
+
select = output();
|
|
345
|
+
tabs = contentChildren(WmTab, ...(ngDevMode ? [{ debugName: "tabs" }] : []));
|
|
346
|
+
focusedIndex = signal(null, ...(ngDevMode ? [{ debugName: "focusedIndex" }] : []));
|
|
347
|
+
selectTab(value, index) {
|
|
348
|
+
this.focusedIndex.set(index);
|
|
349
|
+
this.select.emit(value);
|
|
350
|
+
}
|
|
351
|
+
onBlur() {
|
|
352
|
+
setTimeout(() => {
|
|
353
|
+
if (!document.activeElement?.closest('.wm-tabs')) {
|
|
354
|
+
this.focusedIndex.set(null);
|
|
355
|
+
}
|
|
356
|
+
}, 0);
|
|
357
|
+
}
|
|
358
|
+
onKeyDown(event, container) {
|
|
359
|
+
const tabs = this.tabs();
|
|
360
|
+
const currentIndex = this.focusedIndex() ?? tabs.findIndex(t => t.value() === this.activeValue());
|
|
361
|
+
let nextIndex = null;
|
|
362
|
+
switch (event.key) {
|
|
363
|
+
case 'ArrowRight':
|
|
364
|
+
case 'ArrowDown':
|
|
365
|
+
nextIndex = (currentIndex + 1) % tabs.length;
|
|
366
|
+
break;
|
|
367
|
+
case 'ArrowLeft':
|
|
368
|
+
case 'ArrowUp':
|
|
369
|
+
nextIndex = (currentIndex - 1 + tabs.length) % tabs.length;
|
|
370
|
+
break;
|
|
371
|
+
case 'Home':
|
|
372
|
+
nextIndex = 0;
|
|
373
|
+
break;
|
|
374
|
+
case 'End':
|
|
375
|
+
nextIndex = tabs.length - 1;
|
|
376
|
+
break;
|
|
377
|
+
}
|
|
378
|
+
if (nextIndex !== null) {
|
|
379
|
+
event.preventDefault();
|
|
380
|
+
this.focusedIndex.set(nextIndex);
|
|
381
|
+
const btns = container.querySelectorAll('[role="tab"]');
|
|
382
|
+
btns[nextIndex]?.focus();
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.1", ngImport: i0, type: WmTabs, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
386
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.1", 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\r\n class=\"wm-tabs\"\r\n [class.wm-tabs--line]=\"variant() === 'line'\"\r\n [class.wm-tabs--pill]=\"variant() === 'pill'\"\r\n role=\"tablist\"\r\n #container\r\n (keydown)=\"onKeyDown($event, container)\"\r\n>\r\n @for (tab of tabs(); track tab.value(); let i = $index) {\r\n <button\r\n type=\"button\"\r\n role=\"tab\"\r\n class=\"wm-tab\"\r\n [id]=\"'wm-tab-' + tab.value()\"\r\n [attr.aria-selected]=\"activeValue() === tab.value()\"\r\n [attr.aria-controls]=\"'wm-panel-' + tab.value()\"\r\n [tabIndex]=\"(focusedIndex() === null ? activeValue() === tab.value() : focusedIndex() === i) ? 0 : -1\"\r\n [class.is-active]=\"activeValue() === tab.value()\"\r\n (click)=\"selectTab(tab.value(), i)\"\r\n (focus)=\"focusedIndex.set(i)\"\r\n (blur)=\"onBlur()\"\r\n >\r\n <ng-container [ngTemplateOutlet]=\"tab.tmpl()\" />\r\n </button>\r\n }\r\n</div>\r\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-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);border-bottom-color:var(--wm-color-accent)}.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)}@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 });
|
|
387
|
+
}
|
|
388
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.1", ngImport: i0, type: WmTabs, decorators: [{
|
|
389
|
+
type: Component,
|
|
390
|
+
args: [{ selector: 'wm-tabs', imports: [NgTemplateOutlet], changeDetection: ChangeDetectionStrategy.OnPush, template: "<div\r\n class=\"wm-tabs\"\r\n [class.wm-tabs--line]=\"variant() === 'line'\"\r\n [class.wm-tabs--pill]=\"variant() === 'pill'\"\r\n role=\"tablist\"\r\n #container\r\n (keydown)=\"onKeyDown($event, container)\"\r\n>\r\n @for (tab of tabs(); track tab.value(); let i = $index) {\r\n <button\r\n type=\"button\"\r\n role=\"tab\"\r\n class=\"wm-tab\"\r\n [id]=\"'wm-tab-' + tab.value()\"\r\n [attr.aria-selected]=\"activeValue() === tab.value()\"\r\n [attr.aria-controls]=\"'wm-panel-' + tab.value()\"\r\n [tabIndex]=\"(focusedIndex() === null ? activeValue() === tab.value() : focusedIndex() === i) ? 0 : -1\"\r\n [class.is-active]=\"activeValue() === tab.value()\"\r\n (click)=\"selectTab(tab.value(), i)\"\r\n (focus)=\"focusedIndex.set(i)\"\r\n (blur)=\"onBlur()\"\r\n >\r\n <ng-container [ngTemplateOutlet]=\"tab.tmpl()\" />\r\n </button>\r\n }\r\n</div>\r\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-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);border-bottom-color:var(--wm-color-accent)}.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)}@media(prefers-reduced-motion:reduce){.wm-tab{transition:none}}\n"] }]
|
|
391
|
+
}], 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 }] }] } });
|
|
392
|
+
|
|
393
|
+
class WmButton {
|
|
394
|
+
variant = input('primary', ...(ngDevMode ? [{ debugName: "variant" }] : []));
|
|
395
|
+
size = input('md', ...(ngDevMode ? [{ debugName: "size" }] : []));
|
|
396
|
+
disabled = input(false, ...(ngDevMode ? [{ debugName: "disabled" }] : []));
|
|
397
|
+
type = input('button', ...(ngDevMode ? [{ debugName: "type" }] : []));
|
|
398
|
+
/** Makes the button square (equal width/height). Use for icon-only buttons. */
|
|
399
|
+
iconOnly = input(false, ...(ngDevMode ? [{ debugName: "iconOnly" }] : []));
|
|
400
|
+
/** Accessible label forwarded to the inner <button>. Required for icon-only buttons. */
|
|
401
|
+
ariaLabel = input('', ...(ngDevMode ? [{ debugName: "ariaLabel" }] : []));
|
|
402
|
+
/** Tooltip text forwarded to the inner <button>. Named btnTitle to avoid native attribute conflict. */
|
|
403
|
+
btnTitle = input('', ...(ngDevMode ? [{ debugName: "btnTitle" }] : []));
|
|
404
|
+
btnClass = computed(() => {
|
|
405
|
+
const base = `wm-btn wm-btn--${this.variant()} wm-btn--${this.size()}`;
|
|
406
|
+
return this.iconOnly() ? `${base} btn-icon-only` : base;
|
|
407
|
+
}, ...(ngDevMode ? [{ debugName: "btnClass" }] : []));
|
|
408
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.1", ngImport: i0, type: WmButton, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
409
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "21.2.1", 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\r\n [class]=\"btnClass()\"\r\n [disabled]=\"disabled()\"\r\n [type]=\"type()\"\r\n [attr.aria-label]=\"ariaLabel() || null\"\r\n [attr.title]=\"btnTitle() || null\"\r\n>\r\n <ng-content />\r\n</button>\r\n", styles: [":host{display:inline-flex}\n"] });
|
|
410
|
+
}
|
|
411
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.1", ngImport: i0, type: WmButton, decorators: [{
|
|
412
|
+
type: Component,
|
|
413
|
+
args: [{ selector: 'wm-button', template: "<button\r\n [class]=\"btnClass()\"\r\n [disabled]=\"disabled()\"\r\n [type]=\"type()\"\r\n [attr.aria-label]=\"ariaLabel() || null\"\r\n [attr.title]=\"btnTitle() || null\"\r\n>\r\n <ng-content />\r\n</button>\r\n", styles: [":host{display:inline-flex}\n"] }]
|
|
414
|
+
}], 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 }] }] } });
|
|
415
|
+
|
|
416
|
+
/**
|
|
417
|
+
* WmCheckbox — Boolean selection control with optional indeterminate state.
|
|
418
|
+
*
|
|
419
|
+
* When to use:
|
|
420
|
+
* - Multi-select lists (each item is independent)
|
|
421
|
+
* - Form options that require explicit confirmation before taking effect
|
|
422
|
+
* - Parent/child hierarchical selection (use indeterminate for "some selected")
|
|
423
|
+
*
|
|
424
|
+
* When NOT to use:
|
|
425
|
+
* - Mutually exclusive choices → use radio buttons instead
|
|
426
|
+
* - Immediate-effect toggles (e.g. dark mode) → use Toggle instead
|
|
427
|
+
*
|
|
428
|
+
* Accessibility:
|
|
429
|
+
* - role="checkbox" with aria-checked="true|false|mixed"
|
|
430
|
+
* - "mixed" maps to the indeterminate state for screen readers
|
|
431
|
+
* - Space activates the checkbox (native button behaviour)
|
|
432
|
+
* - Focus ring uses a double-ring (canvas gap + accent) pattern for maximum visibility
|
|
433
|
+
* across all themes, meeting WCAG 2.1 SC 1.4.11 Non-text Contrast
|
|
434
|
+
* - When wrapping in a group, add role="group" + aria-labelledby to the container
|
|
435
|
+
*/
|
|
436
|
+
class Checkbox {
|
|
437
|
+
/** Two-way bindable checked state. */
|
|
438
|
+
checked = model(false, ...(ngDevMode ? [{ debugName: "checked" }] : []));
|
|
439
|
+
/**
|
|
440
|
+
* Shows a dash icon instead of a checkmark. Typically used for a "select all"
|
|
441
|
+
* checkbox when only some child items are selected.
|
|
442
|
+
* Note: indeterminate is display-only — clicking always toggles to true/false.
|
|
443
|
+
*/
|
|
444
|
+
indeterminate = input(false, ...(ngDevMode ? [{ debugName: "indeterminate" }] : []));
|
|
445
|
+
/** Visible text label rendered next to the checkbox box. */
|
|
446
|
+
label = input('', ...(ngDevMode ? [{ debugName: "label" }] : []));
|
|
447
|
+
disabled = input(false, ...(ngDevMode ? [{ debugName: "disabled" }] : []));
|
|
448
|
+
/** Maps to aria-checked: true, false, or "mixed" (for indeterminate). */
|
|
449
|
+
ariaChecked = computed(() => {
|
|
450
|
+
if (this.indeterminate() && !this.checked())
|
|
451
|
+
return 'mixed';
|
|
452
|
+
return this.checked();
|
|
453
|
+
}, ...(ngDevMode ? [{ debugName: "ariaChecked" }] : []));
|
|
454
|
+
toggle() {
|
|
455
|
+
if (!this.disabled()) {
|
|
456
|
+
this.checked.update(v => !v);
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.1", ngImport: i0, type: Checkbox, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
460
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.1", type: Checkbox, isStandalone: true, selector: "wm-checkbox", inputs: { checked: { classPropertyName: "checked", publicName: "checked", isSignal: true, isRequired: false, transformFunction: null }, indeterminate: { classPropertyName: "indeterminate", publicName: "indeterminate", isSignal: true, isRequired: false, transformFunction: null }, label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { checked: "checkedChange" }, ngImport: i0, template: "<label class=\"wm-checkbox-wrap\" [class.is-disabled]=\"disabled()\">\r\n <button\r\n type=\"button\"\r\n role=\"checkbox\"\r\n class=\"wm-checkbox-box\"\r\n [class.is-checked]=\"checked()\"\r\n [class.is-indeterminate]=\"indeterminate() && !checked()\"\r\n [attr.aria-checked]=\"ariaChecked()\"\r\n [attr.aria-disabled]=\"disabled() ? true : null\"\r\n [disabled]=\"disabled() || null\"\r\n (click)=\"toggle()\"\r\n (keydown.space)=\"$event.preventDefault(); toggle()\"\r\n >\r\n @if (indeterminate() && !checked()) {\r\n <!-- Dash for indeterminate (\"some selected\") state -->\r\n <svg class=\"wm-checkbox-icon\" viewBox=\"0 0 12 12\" aria-hidden=\"true\">\r\n <line x1=\"2\" y1=\"6\" x2=\"10\" y2=\"6\"\r\n stroke=\"currentColor\" stroke-width=\"2.25\" stroke-linecap=\"round\"/>\r\n </svg>\r\n } @else if (checked()) {\r\n <!-- Animated checkmark for selected state -->\r\n <svg class=\"wm-checkbox-icon\" viewBox=\"0 0 12 12\" aria-hidden=\"true\">\r\n <polyline class=\"check-mark\" points=\"2,6.5 4.5,9 10,3\"\r\n fill=\"none\" stroke=\"currentColor\"\r\n stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\r\n </svg>\r\n }\r\n </button>\r\n\r\n @if (label()) {\r\n <span class=\"wm-checkbox-label\">{{ label() }}</span>\r\n }\r\n</label>\r\n", styles: [":host{display:inline-flex}.wm-checkbox-wrap{display:inline-flex;align-items:center;gap:.5rem;cursor:pointer;-webkit-user-select:none;user-select:none}.wm-checkbox-wrap.is-disabled{opacity:.45;cursor:not-allowed;pointer-events:none}.wm-checkbox-box{display:inline-flex;align-items:center;justify-content:center;width:18px;height:18px;flex-shrink:0;border:2px solid var(--wm-border-input);border-radius:4px;background:var(--wm-bg-base);color:#fff;cursor:pointer;padding:0;outline:none;transition:background var(--wm-transition-fast),border-color var(--wm-transition-fast),box-shadow var(--wm-transition-fast)}.wm-checkbox-box:focus-visible{box-shadow:0 0 0 2px var(--wm-bg-base),0 0 0 4px var(--wm-color-accent)}.wm-checkbox-box.is-checked,.wm-checkbox-box.is-indeterminate{background:var(--wm-color-accent);border-color:var(--wm-color-accent)}.wm-checkbox-icon{width:12px;height:12px;display:block;flex-shrink:0}@keyframes check-draw{0%{stroke-dashoffset:16}to{stroke-dashoffset:0}}.check-mark{stroke-dasharray:16;stroke-dashoffset:0;animation:check-draw .18s ease-out forwards}.wm-checkbox-label{font-size:.875rem;color:var(--wm-text-primary);line-height:1.4}@media(prefers-reduced-motion:reduce){.wm-checkbox-box{transition:none}.check-mark{animation:none}}\n"] });
|
|
461
|
+
}
|
|
462
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.1", ngImport: i0, type: Checkbox, decorators: [{
|
|
463
|
+
type: Component,
|
|
464
|
+
args: [{ selector: 'wm-checkbox', template: "<label class=\"wm-checkbox-wrap\" [class.is-disabled]=\"disabled()\">\r\n <button\r\n type=\"button\"\r\n role=\"checkbox\"\r\n class=\"wm-checkbox-box\"\r\n [class.is-checked]=\"checked()\"\r\n [class.is-indeterminate]=\"indeterminate() && !checked()\"\r\n [attr.aria-checked]=\"ariaChecked()\"\r\n [attr.aria-disabled]=\"disabled() ? true : null\"\r\n [disabled]=\"disabled() || null\"\r\n (click)=\"toggle()\"\r\n (keydown.space)=\"$event.preventDefault(); toggle()\"\r\n >\r\n @if (indeterminate() && !checked()) {\r\n <!-- Dash for indeterminate (\"some selected\") state -->\r\n <svg class=\"wm-checkbox-icon\" viewBox=\"0 0 12 12\" aria-hidden=\"true\">\r\n <line x1=\"2\" y1=\"6\" x2=\"10\" y2=\"6\"\r\n stroke=\"currentColor\" stroke-width=\"2.25\" stroke-linecap=\"round\"/>\r\n </svg>\r\n } @else if (checked()) {\r\n <!-- Animated checkmark for selected state -->\r\n <svg class=\"wm-checkbox-icon\" viewBox=\"0 0 12 12\" aria-hidden=\"true\">\r\n <polyline class=\"check-mark\" points=\"2,6.5 4.5,9 10,3\"\r\n fill=\"none\" stroke=\"currentColor\"\r\n stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\r\n </svg>\r\n }\r\n </button>\r\n\r\n @if (label()) {\r\n <span class=\"wm-checkbox-label\">{{ label() }}</span>\r\n }\r\n</label>\r\n", styles: [":host{display:inline-flex}.wm-checkbox-wrap{display:inline-flex;align-items:center;gap:.5rem;cursor:pointer;-webkit-user-select:none;user-select:none}.wm-checkbox-wrap.is-disabled{opacity:.45;cursor:not-allowed;pointer-events:none}.wm-checkbox-box{display:inline-flex;align-items:center;justify-content:center;width:18px;height:18px;flex-shrink:0;border:2px solid var(--wm-border-input);border-radius:4px;background:var(--wm-bg-base);color:#fff;cursor:pointer;padding:0;outline:none;transition:background var(--wm-transition-fast),border-color var(--wm-transition-fast),box-shadow var(--wm-transition-fast)}.wm-checkbox-box:focus-visible{box-shadow:0 0 0 2px var(--wm-bg-base),0 0 0 4px var(--wm-color-accent)}.wm-checkbox-box.is-checked,.wm-checkbox-box.is-indeterminate{background:var(--wm-color-accent);border-color:var(--wm-color-accent)}.wm-checkbox-icon{width:12px;height:12px;display:block;flex-shrink:0}@keyframes check-draw{0%{stroke-dashoffset:16}to{stroke-dashoffset:0}}.check-mark{stroke-dasharray:16;stroke-dashoffset:0;animation:check-draw .18s ease-out forwards}.wm-checkbox-label{font-size:.875rem;color:var(--wm-text-primary);line-height:1.4}@media(prefers-reduced-motion:reduce){.wm-checkbox-box{transition:none}.check-mark{animation:none}}\n"] }]
|
|
465
|
+
}], propDecorators: { checked: [{ type: i0.Input, args: [{ isSignal: true, alias: "checked", required: false }] }, { type: i0.Output, args: ["checkedChange"] }], indeterminate: [{ type: i0.Input, args: [{ isSignal: true, alias: "indeterminate", required: false }] }], label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }] } });
|
|
466
|
+
|
|
467
|
+
/**
|
|
468
|
+
* WmNumberInput — Numeric input with stepper buttons.
|
|
469
|
+
*
|
|
470
|
+
* Implements ControlValueAccessor so it works with both template-driven forms
|
|
471
|
+
* (ngModel) and reactive forms (formControl / formControlName).
|
|
472
|
+
*
|
|
473
|
+
* When to use:
|
|
474
|
+
* - Quantity fields (items in cart, repetitions, page count)
|
|
475
|
+
* - Bounded numeric settings with a known min/max (timeout seconds, font size)
|
|
476
|
+
*
|
|
477
|
+
* When NOT to use:
|
|
478
|
+
* - Free-form numeric entry without bounds → plain <input type="number">
|
|
479
|
+
* - Currency or phone numbers → use a text input with masking
|
|
480
|
+
*
|
|
481
|
+
* Accessibility:
|
|
482
|
+
* - The underlying <input type="number"> is the focusable control.
|
|
483
|
+
* - Stepper buttons have tabindex="-1" — they are mouse/touch targets only.
|
|
484
|
+
* Keyboard users use the arrow keys on the input itself (native browser behaviour).
|
|
485
|
+
* - Always provide an aria-label or visible label via <wm-field> + <wm-label>.
|
|
486
|
+
* - The buttons use mousedown + preventDefault to avoid stealing focus from the input.
|
|
487
|
+
*
|
|
488
|
+
* Form integration:
|
|
489
|
+
* <!-- Template-driven -->
|
|
490
|
+
* <wm-number-input [(ngModel)]="count" [min]="0" [max]="100" />
|
|
491
|
+
*
|
|
492
|
+
* <!-- Reactive -->
|
|
493
|
+
* <wm-number-input formControlName="quantity" [step]="5" />
|
|
494
|
+
*/
|
|
495
|
+
class NumberInput {
|
|
496
|
+
/** Minimum allowed value. */
|
|
497
|
+
min = input(null, ...(ngDevMode ? [{ debugName: "min" }] : []));
|
|
498
|
+
/** Maximum allowed value. */
|
|
499
|
+
max = input(null, ...(ngDevMode ? [{ debugName: "max" }] : []));
|
|
500
|
+
/** Amount to increment/decrement per step. Defaults to 1. */
|
|
501
|
+
step = input(1, ...(ngDevMode ? [{ debugName: "step" }] : []));
|
|
502
|
+
/** Placeholder text shown when the field is empty. */
|
|
503
|
+
placeholder = input('0', ...(ngDevMode ? [{ debugName: "placeholder" }] : []));
|
|
504
|
+
/** Disables the control from the template. Also set automatically by reactive forms. */
|
|
505
|
+
disabled = input(false, ...(ngDevMode ? [{ debugName: "disabled" }] : []));
|
|
506
|
+
inputRef = viewChild('inputEl', ...(ngDevMode ? [{ debugName: "inputRef" }] : []));
|
|
507
|
+
internalValue = signal(null, ...(ngDevMode ? [{ debugName: "internalValue" }] : []));
|
|
508
|
+
onChangeFn = () => { };
|
|
509
|
+
onTouchedFn = () => { };
|
|
510
|
+
/** CVA interface — called when the form model changes programmatically. */
|
|
511
|
+
writeValue(value) {
|
|
512
|
+
this.internalValue.set(value ?? null);
|
|
513
|
+
}
|
|
514
|
+
registerOnChange(fn) {
|
|
515
|
+
this.onChangeFn = fn;
|
|
516
|
+
}
|
|
517
|
+
registerOnTouched(fn) {
|
|
518
|
+
this.onTouchedFn = fn;
|
|
519
|
+
}
|
|
520
|
+
onInputChange(event) {
|
|
521
|
+
const raw = event.target.value;
|
|
522
|
+
const num = raw === '' ? null : parseFloat(raw);
|
|
523
|
+
this.internalValue.set(num);
|
|
524
|
+
this.onChangeFn(num);
|
|
525
|
+
this.onTouchedFn();
|
|
526
|
+
}
|
|
527
|
+
increment() {
|
|
528
|
+
const curr = this.internalValue() ?? 0;
|
|
529
|
+
const next = curr + this.step();
|
|
530
|
+
const max = this.max();
|
|
531
|
+
this.setValue(max !== null ? Math.min(next, max) : next);
|
|
532
|
+
}
|
|
533
|
+
decrement() {
|
|
534
|
+
const curr = this.internalValue() ?? 0;
|
|
535
|
+
const next = curr - this.step();
|
|
536
|
+
const min = this.min();
|
|
537
|
+
this.setValue(min !== null ? Math.max(next, min) : next);
|
|
538
|
+
}
|
|
539
|
+
/**
|
|
540
|
+
* Prevents the stepper buttons from stealing focus away from the input element.
|
|
541
|
+
* Without this, clicking a button would blur the input, hiding any validation state.
|
|
542
|
+
*/
|
|
543
|
+
preventFocusSteal(event) {
|
|
544
|
+
event.preventDefault();
|
|
545
|
+
}
|
|
546
|
+
setValue(val) {
|
|
547
|
+
this.internalValue.set(val);
|
|
548
|
+
this.onChangeFn(val);
|
|
549
|
+
this.onTouchedFn();
|
|
550
|
+
const el = this.inputRef();
|
|
551
|
+
if (el)
|
|
552
|
+
el.nativeElement.value = String(val);
|
|
553
|
+
}
|
|
554
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.1", ngImport: i0, type: NumberInput, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
555
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.2.0", version: "21.2.1", type: NumberInput, isStandalone: true, selector: "wm-number-input", inputs: { min: { classPropertyName: "min", publicName: "min", isSignal: true, isRequired: false, transformFunction: null }, max: { classPropertyName: "max", publicName: "max", isSignal: true, isRequired: false, transformFunction: null }, step: { classPropertyName: "step", publicName: "step", isSignal: true, isRequired: false, transformFunction: null }, placeholder: { classPropertyName: "placeholder", publicName: "placeholder", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null } }, providers: [
|
|
556
|
+
{
|
|
557
|
+
provide: NG_VALUE_ACCESSOR,
|
|
558
|
+
useExisting: forwardRef(() => NumberInput),
|
|
559
|
+
multi: true,
|
|
560
|
+
},
|
|
561
|
+
], viewQueries: [{ propertyName: "inputRef", first: true, predicate: ["inputEl"], descendants: true, isSignal: true }], ngImport: i0, template: "<!--\r\n Uses .wm-input-group and .wm-number-* classes from inputs.css.\r\n The input fills the group; the controls column sits at the trailing edge.\r\n-->\r\n<div\r\n class=\"wm-input-group wm-number-field\"\r\n [class.is-disabled]=\"disabled()\"\r\n>\r\n <input\r\n #inputEl\r\n type=\"number\"\r\n class=\"wm-input-bare wm-number-input\"\r\n [value]=\"internalValue() ?? ''\"\r\n [placeholder]=\"placeholder()\"\r\n [attr.min]=\"min() ?? null\"\r\n [attr.max]=\"max() ?? null\"\r\n [step]=\"step()\"\r\n [disabled]=\"disabled() || null\"\r\n (change)=\"onInputChange($event)\"\r\n (input)=\"onInputChange($event)\"\r\n (blur)=\"onTouchedFn()\"\r\n />\r\n\r\n <div class=\"wm-number-controls\" aria-hidden=\"true\">\r\n <button\r\n type=\"button\"\r\n class=\"wm-number-btn\"\r\n tabindex=\"-1\"\r\n [disabled]=\"disabled() || null\"\r\n (mousedown)=\"preventFocusSteal($event)\"\r\n (click)=\"increment()\"\r\n aria-label=\"Increment\"\r\n >\r\n <!-- chevron-up (lucide) -->\r\n <svg width=\"10\" height=\"10\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\">\r\n <path d=\"m18 15-6-6-6 6\"/>\r\n </svg>\r\n </button>\r\n <button\r\n type=\"button\"\r\n class=\"wm-number-btn\"\r\n tabindex=\"-1\"\r\n [disabled]=\"disabled() || null\"\r\n (mousedown)=\"preventFocusSteal($event)\"\r\n (click)=\"decrement()\"\r\n aria-label=\"Decrement\"\r\n >\r\n <!-- chevron-down (lucide) -->\r\n <svg width=\"10\" height=\"10\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\">\r\n <path d=\"m6 9 6 6 6-6\"/>\r\n </svg>\r\n </button>\r\n </div>\r\n</div>\r\n", styles: [":host{display:block}.wm-number-field.is-disabled{opacity:.45;pointer-events:none}\n"] });
|
|
562
|
+
}
|
|
563
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.1", ngImport: i0, type: NumberInput, decorators: [{
|
|
564
|
+
type: Component,
|
|
565
|
+
args: [{ selector: 'wm-number-input', providers: [
|
|
566
|
+
{
|
|
567
|
+
provide: NG_VALUE_ACCESSOR,
|
|
568
|
+
useExisting: forwardRef(() => NumberInput),
|
|
569
|
+
multi: true,
|
|
570
|
+
},
|
|
571
|
+
], template: "<!--\r\n Uses .wm-input-group and .wm-number-* classes from inputs.css.\r\n The input fills the group; the controls column sits at the trailing edge.\r\n-->\r\n<div\r\n class=\"wm-input-group wm-number-field\"\r\n [class.is-disabled]=\"disabled()\"\r\n>\r\n <input\r\n #inputEl\r\n type=\"number\"\r\n class=\"wm-input-bare wm-number-input\"\r\n [value]=\"internalValue() ?? ''\"\r\n [placeholder]=\"placeholder()\"\r\n [attr.min]=\"min() ?? null\"\r\n [attr.max]=\"max() ?? null\"\r\n [step]=\"step()\"\r\n [disabled]=\"disabled() || null\"\r\n (change)=\"onInputChange($event)\"\r\n (input)=\"onInputChange($event)\"\r\n (blur)=\"onTouchedFn()\"\r\n />\r\n\r\n <div class=\"wm-number-controls\" aria-hidden=\"true\">\r\n <button\r\n type=\"button\"\r\n class=\"wm-number-btn\"\r\n tabindex=\"-1\"\r\n [disabled]=\"disabled() || null\"\r\n (mousedown)=\"preventFocusSteal($event)\"\r\n (click)=\"increment()\"\r\n aria-label=\"Increment\"\r\n >\r\n <!-- chevron-up (lucide) -->\r\n <svg width=\"10\" height=\"10\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\">\r\n <path d=\"m18 15-6-6-6 6\"/>\r\n </svg>\r\n </button>\r\n <button\r\n type=\"button\"\r\n class=\"wm-number-btn\"\r\n tabindex=\"-1\"\r\n [disabled]=\"disabled() || null\"\r\n (mousedown)=\"preventFocusSteal($event)\"\r\n (click)=\"decrement()\"\r\n aria-label=\"Decrement\"\r\n >\r\n <!-- chevron-down (lucide) -->\r\n <svg width=\"10\" height=\"10\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\">\r\n <path d=\"m6 9 6 6 6-6\"/>\r\n </svg>\r\n </button>\r\n </div>\r\n</div>\r\n", styles: [":host{display:block}.wm-number-field.is-disabled{opacity:.45;pointer-events:none}\n"] }]
|
|
572
|
+
}], propDecorators: { min: [{ type: i0.Input, args: [{ isSignal: true, alias: "min", required: false }] }], max: [{ type: i0.Input, args: [{ isSignal: true, alias: "max", required: false }] }], step: [{ type: i0.Input, args: [{ isSignal: true, alias: "step", required: false }] }], placeholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "placeholder", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], inputRef: [{ type: i0.ViewChild, args: ['inputEl', { isSignal: true }] }] } });
|
|
573
|
+
|
|
574
|
+
/**
|
|
575
|
+
* WmToggle — Binary on/off control.
|
|
576
|
+
*
|
|
577
|
+
* Use for settings that take effect immediately without a form submit.
|
|
578
|
+
* For settings that need an explicit "Save" action, prefer a checkbox.
|
|
579
|
+
*
|
|
580
|
+
* Accessibility:
|
|
581
|
+
* - role="switch" communicates binary state to screen readers
|
|
582
|
+
* - aria-checked reflects true/false
|
|
583
|
+
* - aria-label required when no visible text label surrounds the toggle
|
|
584
|
+
* - Space and Enter activate the toggle (native button behaviour)
|
|
585
|
+
* - When disabled, aria state is preserved so SR users understand why it can't be changed
|
|
586
|
+
*/
|
|
587
|
+
class Toggle {
|
|
588
|
+
/** Two-way bindable state. Use [(checked)]="mySignal" or [checked]="val" (changeChecked)="val=$event". */
|
|
589
|
+
checked = model(false, ...(ngDevMode ? [{ debugName: "checked" }] : []));
|
|
590
|
+
/** Visually and functionally disables the toggle. State is still readable by assistive tech. */
|
|
591
|
+
disabled = input(false, ...(ngDevMode ? [{ debugName: "disabled" }] : []));
|
|
592
|
+
/** Accessible label. Required if no visible label text surrounds the toggle in the DOM. */
|
|
593
|
+
ariaLabel = input('Toggle', ...(ngDevMode ? [{ debugName: "ariaLabel" }] : []));
|
|
594
|
+
toggle() {
|
|
595
|
+
if (!this.disabled()) {
|
|
596
|
+
this.checked.update(v => !v);
|
|
597
|
+
}
|
|
598
|
+
}
|
|
599
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.1", ngImport: i0, type: Toggle, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
600
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "21.2.1", type: Toggle, isStandalone: true, selector: "wm-toggle", inputs: { checked: { classPropertyName: "checked", publicName: "checked", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, ariaLabel: { classPropertyName: "ariaLabel", publicName: "ariaLabel", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { checked: "checkedChange" }, ngImport: i0, template: "<button\r\n type=\"button\"\r\n role=\"switch\"\r\n class=\"wm-toggle\"\r\n [class.is-on]=\"checked()\"\r\n [class.is-disabled]=\"disabled()\"\r\n [attr.aria-checked]=\"checked()\"\r\n [attr.aria-label]=\"ariaLabel()\"\r\n [attr.aria-disabled]=\"disabled() ? true : null\"\r\n [disabled]=\"disabled() || null\"\r\n (click)=\"toggle()\"\r\n>\r\n <span class=\"wm-toggle-track\" aria-hidden=\"true\">\r\n <span class=\"wm-toggle-thumb\"></span>\r\n </span>\r\n</button>\r\n", styles: [":host{display:inline-flex}.wm-toggle{display:inline-flex;align-items:center;background:none;border:none;padding:2px;margin:-2px;cursor:pointer;border-radius:var(--wm-radius-full);outline:none}.wm-toggle:focus-visible .wm-toggle-track{outline:2px solid var(--wm-color-accent);outline-offset:2px}.wm-toggle.is-disabled{opacity:.45;cursor:not-allowed;pointer-events:none}.wm-toggle-track{position:relative;width:40px;height:22px;background:var(--wm-border-input);border-radius:var(--wm-radius-full);transition:background var(--wm-transition-fast);flex-shrink:0}.wm-toggle.is-on .wm-toggle-track{background:var(--wm-color-accent)}.wm-toggle-thumb{position:absolute;top:3px;left:3px;width:16px;height:16px;background:#fff;border-radius:var(--wm-radius-full);box-shadow:0 1px 3px #00000040;transition:transform var(--wm-transition-fast)}.wm-toggle.is-on .wm-toggle-thumb{transform:translate(18px)}@media(prefers-reduced-motion:reduce){.wm-toggle-track,.wm-toggle-thumb{transition:none}}\n"] });
|
|
601
|
+
}
|
|
602
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.1", ngImport: i0, type: Toggle, decorators: [{
|
|
603
|
+
type: Component,
|
|
604
|
+
args: [{ selector: 'wm-toggle', template: "<button\r\n type=\"button\"\r\n role=\"switch\"\r\n class=\"wm-toggle\"\r\n [class.is-on]=\"checked()\"\r\n [class.is-disabled]=\"disabled()\"\r\n [attr.aria-checked]=\"checked()\"\r\n [attr.aria-label]=\"ariaLabel()\"\r\n [attr.aria-disabled]=\"disabled() ? true : null\"\r\n [disabled]=\"disabled() || null\"\r\n (click)=\"toggle()\"\r\n>\r\n <span class=\"wm-toggle-track\" aria-hidden=\"true\">\r\n <span class=\"wm-toggle-thumb\"></span>\r\n </span>\r\n</button>\r\n", styles: [":host{display:inline-flex}.wm-toggle{display:inline-flex;align-items:center;background:none;border:none;padding:2px;margin:-2px;cursor:pointer;border-radius:var(--wm-radius-full);outline:none}.wm-toggle:focus-visible .wm-toggle-track{outline:2px solid var(--wm-color-accent);outline-offset:2px}.wm-toggle.is-disabled{opacity:.45;cursor:not-allowed;pointer-events:none}.wm-toggle-track{position:relative;width:40px;height:22px;background:var(--wm-border-input);border-radius:var(--wm-radius-full);transition:background var(--wm-transition-fast);flex-shrink:0}.wm-toggle.is-on .wm-toggle-track{background:var(--wm-color-accent)}.wm-toggle-thumb{position:absolute;top:3px;left:3px;width:16px;height:16px;background:#fff;border-radius:var(--wm-radius-full);box-shadow:0 1px 3px #00000040;transition:transform var(--wm-transition-fast)}.wm-toggle.is-on .wm-toggle-thumb{transform:translate(18px)}@media(prefers-reduced-motion:reduce){.wm-toggle-track,.wm-toggle-thumb{transition:none}}\n"] }]
|
|
605
|
+
}], propDecorators: { checked: [{ type: i0.Input, args: [{ isSignal: true, alias: "checked", required: false }] }, { type: i0.Output, args: ["checkedChange"] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], ariaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaLabel", required: false }] }] } });
|
|
606
|
+
|
|
607
|
+
/**
|
|
608
|
+
* GhostField — transparent-until-hover wrapper for inline-editable fields.
|
|
609
|
+
*
|
|
610
|
+
* Applies .wm-ghost-field to its content; all visual behaviour is CSS-driven
|
|
611
|
+
* (inputs.css). No inputs or outputs — interactivity belongs to the inner
|
|
612
|
+
* element projected via ng-content.
|
|
613
|
+
*
|
|
614
|
+
* Usage:
|
|
615
|
+
* <wm-ghost-field>
|
|
616
|
+
* <input class="wm-input-bare" [(ngModel)]="title" aria-label="Task title" />
|
|
617
|
+
* </wm-ghost-field>
|
|
618
|
+
*/
|
|
619
|
+
class GhostField {
|
|
620
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.1", ngImport: i0, type: GhostField, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
621
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "21.2.1", type: GhostField, isStandalone: true, selector: "wm-ghost-field", ngImport: i0, template: `<div class="wm-ghost-field"><ng-content /></div>`, isInline: true, encapsulation: i0.ViewEncapsulation.None });
|
|
622
|
+
}
|
|
623
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.1", ngImport: i0, type: GhostField, decorators: [{
|
|
624
|
+
type: Component,
|
|
625
|
+
args: [{
|
|
626
|
+
selector: 'wm-ghost-field',
|
|
627
|
+
standalone: true,
|
|
628
|
+
template: `<div class="wm-ghost-field"><ng-content /></div>`,
|
|
629
|
+
encapsulation: ViewEncapsulation.None,
|
|
630
|
+
}]
|
|
631
|
+
}] });
|
|
632
|
+
|
|
633
|
+
/**
|
|
634
|
+
* PageHeader — standardised page-level heading with eyebrow label, title,
|
|
635
|
+
* optional subtitle, optional status badge, and projected action/back slots.
|
|
636
|
+
*
|
|
637
|
+
* Slots:
|
|
638
|
+
* [icon] — 12×12 icon projected into the eyebrow row (optional)
|
|
639
|
+
* [back] — back-navigation element (link or button) placed in the status row (optional)
|
|
640
|
+
* default — action buttons / controls placed below the status row
|
|
641
|
+
*
|
|
642
|
+
* Usage:
|
|
643
|
+
* <wm-page-header flavourText="Sprints" title="Active Sprint" [badgeLabel]="daysLeft">
|
|
644
|
+
* <svg icon .../>
|
|
645
|
+
* <a back href="...">← Board</a>
|
|
646
|
+
* <wm-button>New Sprint</wm-button>
|
|
647
|
+
* </wm-page-header>
|
|
648
|
+
*/
|
|
649
|
+
class PageHeader {
|
|
650
|
+
flavourText = input.required(...(ngDevMode ? [{ debugName: "flavourText" }] : []));
|
|
651
|
+
title = input.required(...(ngDevMode ? [{ debugName: "title" }] : []));
|
|
652
|
+
subtitle = input(...(ngDevMode ? [undefined, { debugName: "subtitle" }] : []));
|
|
653
|
+
badgeLabel = input(...(ngDevMode ? [undefined, { debugName: "badgeLabel" }] : []));
|
|
654
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.1", ngImport: i0, type: PageHeader, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
655
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.1", type: PageHeader, isStandalone: true, selector: "wm-page-header", inputs: { flavourText: { classPropertyName: "flavourText", publicName: "flavourText", isSignal: true, isRequired: true, transformFunction: null }, title: { classPropertyName: "title", publicName: "title", isSignal: true, isRequired: true, transformFunction: null }, subtitle: { classPropertyName: "subtitle", publicName: "subtitle", isSignal: true, isRequired: false, transformFunction: null }, badgeLabel: { classPropertyName: "badgeLabel", publicName: "badgeLabel", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: "<header class=\"wm-page-header\">\r\n <div class=\"wm-page-header__left\">\r\n <div class=\"wm-page-header__eyebrow\">\r\n <ng-content select=\"[icon]\" />\r\n <span>{{ flavourText() }}</span>\r\n </div>\r\n <h2 class=\"wm-page-header__title\">{{ title() }}</h2>\r\n @if (subtitle()) {\r\n <p class=\"wm-page-header__subtitle\">{{ subtitle() }}</p>\r\n }\r\n </div>\r\n\r\n <div class=\"wm-page-header__right\">\r\n <div class=\"wm-page-header__status\">\r\n @if (badgeLabel()) {\r\n <span class=\"wm-page-header__badge\">{{ badgeLabel() }}</span>\r\n }\r\n <ng-content select=\"[back]\" />\r\n </div>\r\n <div class=\"wm-page-header__actions\">\r\n <ng-content />\r\n </div>\r\n </div>\r\n</header>\r\n", styles: ["wm-page-header,.wm-page-header{display:flex;align-items:flex-end;justify-content:space-between;border-bottom:1px solid var(--wm-border-subtle);padding-bottom:1.5rem}.wm-page-header__left{display:flex;flex-direction:column;gap:4px}.wm-page-header__eyebrow{display:flex;align-items:center;gap:8px;font-size:.6875rem;font-weight:700;text-transform:uppercase;letter-spacing:.1em;color:var(--wm-text-muted)}.wm-page-header__eyebrow svg,.wm-page-header__eyebrow img{width:12px;height:12px;flex-shrink:0}.wm-page-header__title{margin:0;font-size:1.5rem;font-weight:700;letter-spacing:-.025em;color:var(--wm-text-primary)}.wm-page-header__subtitle{margin:0;font-size:.875rem;color:var(--wm-text-muted)}.wm-page-header__right{display:flex;flex-direction:column;align-items:flex-end;gap:12px}.wm-page-header__status,.wm-page-header__actions{display:flex;align-items:center;gap:12px}.wm-page-header__actions:empty{display:none}.wm-page-header__badge{font-size:.6875rem;font-family:monospace;font-weight:700;text-transform:uppercase;letter-spacing:-.05em;color:var(--wm-text-muted);background:var(--wm-bg-float);padding:4px 8px;border-radius:var(--wm-radius-sm);border:1px solid var(--wm-border-subtle);box-shadow:0 1px 2px #0000000d;white-space:nowrap}@media(max-width:767px){wm-page-header,.wm-page-header{flex-direction:column;align-items:stretch;gap:6px;padding-bottom:0;border-bottom:none}.wm-page-header__right{align-items:flex-start}}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
|
|
656
|
+
}
|
|
657
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.1", ngImport: i0, type: PageHeader, decorators: [{
|
|
658
|
+
type: Component,
|
|
659
|
+
args: [{ selector: 'wm-page-header', standalone: true, encapsulation: ViewEncapsulation.None, changeDetection: ChangeDetectionStrategy.OnPush, template: "<header class=\"wm-page-header\">\r\n <div class=\"wm-page-header__left\">\r\n <div class=\"wm-page-header__eyebrow\">\r\n <ng-content select=\"[icon]\" />\r\n <span>{{ flavourText() }}</span>\r\n </div>\r\n <h2 class=\"wm-page-header__title\">{{ title() }}</h2>\r\n @if (subtitle()) {\r\n <p class=\"wm-page-header__subtitle\">{{ subtitle() }}</p>\r\n }\r\n </div>\r\n\r\n <div class=\"wm-page-header__right\">\r\n <div class=\"wm-page-header__status\">\r\n @if (badgeLabel()) {\r\n <span class=\"wm-page-header__badge\">{{ badgeLabel() }}</span>\r\n }\r\n <ng-content select=\"[back]\" />\r\n </div>\r\n <div class=\"wm-page-header__actions\">\r\n <ng-content />\r\n </div>\r\n </div>\r\n</header>\r\n", styles: ["wm-page-header,.wm-page-header{display:flex;align-items:flex-end;justify-content:space-between;border-bottom:1px solid var(--wm-border-subtle);padding-bottom:1.5rem}.wm-page-header__left{display:flex;flex-direction:column;gap:4px}.wm-page-header__eyebrow{display:flex;align-items:center;gap:8px;font-size:.6875rem;font-weight:700;text-transform:uppercase;letter-spacing:.1em;color:var(--wm-text-muted)}.wm-page-header__eyebrow svg,.wm-page-header__eyebrow img{width:12px;height:12px;flex-shrink:0}.wm-page-header__title{margin:0;font-size:1.5rem;font-weight:700;letter-spacing:-.025em;color:var(--wm-text-primary)}.wm-page-header__subtitle{margin:0;font-size:.875rem;color:var(--wm-text-muted)}.wm-page-header__right{display:flex;flex-direction:column;align-items:flex-end;gap:12px}.wm-page-header__status,.wm-page-header__actions{display:flex;align-items:center;gap:12px}.wm-page-header__actions:empty{display:none}.wm-page-header__badge{font-size:.6875rem;font-family:monospace;font-weight:700;text-transform:uppercase;letter-spacing:-.05em;color:var(--wm-text-muted);background:var(--wm-bg-float);padding:4px 8px;border-radius:var(--wm-radius-sm);border:1px solid var(--wm-border-subtle);box-shadow:0 1px 2px #0000000d;white-space:nowrap}@media(max-width:767px){wm-page-header,.wm-page-header{flex-direction:column;align-items:stretch;gap:6px;padding-bottom:0;border-bottom:none}.wm-page-header__right{align-items:flex-start}}\n"] }]
|
|
660
|
+
}], propDecorators: { flavourText: [{ type: i0.Input, args: [{ isSignal: true, alias: "flavourText", required: true }] }], title: [{ type: i0.Input, args: [{ isSignal: true, alias: "title", required: true }] }], subtitle: [{ type: i0.Input, args: [{ isSignal: true, alias: "subtitle", required: false }] }], badgeLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "badgeLabel", required: false }] }] } });
|
|
661
|
+
|
|
662
|
+
let nextId = 0;
|
|
663
|
+
/**
|
|
664
|
+
* WmToastService — Application-wide toast notification manager.
|
|
665
|
+
*
|
|
666
|
+
* Provided in root; inject anywhere and call success(), error(), info(), or progress().
|
|
667
|
+
* Render `<wm-toast-container>` once at the app root to display toasts.
|
|
668
|
+
*
|
|
669
|
+
* Auto-dismiss: success/info after 4 s, error after 6 s.
|
|
670
|
+
* Progress toasts persist until explicitly dismissed.
|
|
671
|
+
* Custom duration via show(message, type, durationMs).
|
|
672
|
+
*/
|
|
673
|
+
class WmToastService {
|
|
674
|
+
toasts = signal([], ...(ngDevMode ? [{ debugName: "toasts" }] : []));
|
|
675
|
+
show(message, type = 'info', duration) {
|
|
676
|
+
const id = ++nextId;
|
|
677
|
+
this.toasts.update(list => [...list, { id, message, type }]);
|
|
678
|
+
const ms = duration ?? (type === 'error' ? 6000 : 4000);
|
|
679
|
+
setTimeout(() => this.dismiss(id), ms);
|
|
680
|
+
return id;
|
|
681
|
+
}
|
|
682
|
+
success(message) { return this.show(message, 'success'); }
|
|
683
|
+
error(message) { return this.show(message, 'error'); }
|
|
684
|
+
info(message) { return this.show(message, 'info'); }
|
|
685
|
+
/**
|
|
686
|
+
* Show a persistent progress toast with spinner and elapsed timer.
|
|
687
|
+
* Returns the toast ID — caller must call dismiss(id) when done.
|
|
688
|
+
*
|
|
689
|
+
* @param message - Display message (e.g. "Generating docs…")
|
|
690
|
+
* @param onCancel - Optional callback invoked when the user clicks Cancel.
|
|
691
|
+
* The caller should abort the operation and then dismiss the toast.
|
|
692
|
+
*/
|
|
693
|
+
progress(message, onCancel) {
|
|
694
|
+
const id = ++nextId;
|
|
695
|
+
this.toasts.update(list => [
|
|
696
|
+
...list,
|
|
697
|
+
{ id, message, type: 'progress', persistent: true, createdAt: Date.now(), onCancel },
|
|
698
|
+
]);
|
|
699
|
+
return id;
|
|
700
|
+
}
|
|
701
|
+
dismiss(id) {
|
|
702
|
+
this.toasts.update(list => list.filter(t => t.id !== id));
|
|
703
|
+
}
|
|
704
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.1", ngImport: i0, type: WmToastService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
705
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.1", ngImport: i0, type: WmToastService, providedIn: 'root' });
|
|
706
|
+
}
|
|
707
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.1", ngImport: i0, type: WmToastService, decorators: [{
|
|
708
|
+
type: Injectable,
|
|
709
|
+
args: [{ providedIn: 'root' }]
|
|
710
|
+
}] });
|
|
711
|
+
|
|
712
|
+
/**
|
|
713
|
+
* WmToastContainer — Renders the current stack of toasts.
|
|
714
|
+
*
|
|
715
|
+
* Place once at the application root (e.g. inside the shell or app component).
|
|
716
|
+
* Uses inline SVG icons — no external icon library dependency.
|
|
717
|
+
*
|
|
718
|
+
* Accessibility:
|
|
719
|
+
* - Container has role="log" + aria-live="polite" so new toasts are announced
|
|
720
|
+
* - Each toast has role="status"
|
|
721
|
+
* - Dismiss button has an accessible label
|
|
722
|
+
*/
|
|
723
|
+
class WmToastContainer {
|
|
724
|
+
toastService = inject(WmToastService);
|
|
725
|
+
/** Tick signal that updates every second — drives the elapsed time display. */
|
|
726
|
+
tick = signal(Date.now(), ...(ngDevMode ? [{ debugName: "tick" }] : []));
|
|
727
|
+
tickInterval = setInterval(() => {
|
|
728
|
+
// Only tick when there are progress toasts with createdAt
|
|
729
|
+
if (this.toastService.toasts().some(t => t.type === 'progress' && t.createdAt)) {
|
|
730
|
+
this.tick.set(Date.now());
|
|
731
|
+
}
|
|
732
|
+
}, 1000);
|
|
733
|
+
ngOnDestroy() {
|
|
734
|
+
clearInterval(this.tickInterval);
|
|
735
|
+
}
|
|
736
|
+
dismiss(id) {
|
|
737
|
+
this.toastService.dismiss(id);
|
|
738
|
+
}
|
|
739
|
+
cancel(toast) {
|
|
740
|
+
toast.onCancel?.();
|
|
741
|
+
this.toastService.dismiss(toast.id);
|
|
742
|
+
}
|
|
743
|
+
typeClass(toast) {
|
|
744
|
+
return `wm-toast wm-toast--${toast.type}`;
|
|
745
|
+
}
|
|
746
|
+
elapsed(createdAt) {
|
|
747
|
+
const now = this.tick(); // reactive dependency on tick
|
|
748
|
+
const diff = Math.max(0, Math.floor((now - createdAt) / 1000));
|
|
749
|
+
if (diff < 60)
|
|
750
|
+
return `${diff}s`;
|
|
751
|
+
const mins = Math.floor(diff / 60);
|
|
752
|
+
const secs = diff % 60;
|
|
753
|
+
return `${mins}m ${secs.toString().padStart(2, '0')}s`;
|
|
754
|
+
}
|
|
755
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.1", ngImport: i0, type: WmToastContainer, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
756
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.1", type: WmToastContainer, isStandalone: true, selector: "wm-toast-container", ngImport: i0, template: "<div class=\"wm-toast-container\" role=\"log\" aria-live=\"polite\" aria-label=\"Notifications\">\n @for (toast of toastService.toasts(); track toast.id) {\n <div [class]=\"typeClass(toast)\" role=\"status\">\n <!-- Success icon (check-circle) -->\n @if (toast.type === 'success') {\n <svg class=\"wm-toast-icon\" width=\"15\" height=\"15\" viewBox=\"0 0 24 24\"\n fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"\n aria-hidden=\"true\">\n <path d=\"M22 11.08V12a10 10 0 1 1-5.93-9.14\"/>\n <polyline points=\"22 4 12 14.01 9 11.01\"/>\n </svg>\n }\n <!-- Error icon (x-circle) -->\n @if (toast.type === 'error') {\n <svg class=\"wm-toast-icon\" width=\"15\" height=\"15\" viewBox=\"0 0 24 24\"\n fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"\n aria-hidden=\"true\">\n <circle cx=\"12\" cy=\"12\" r=\"10\"/>\n <line x1=\"15\" y1=\"9\" x2=\"9\" y2=\"15\"/>\n <line x1=\"9\" y1=\"9\" x2=\"15\" y2=\"15\"/>\n </svg>\n }\n <!-- Info icon -->\n @if (toast.type === 'info') {\n <svg class=\"wm-toast-icon\" width=\"15\" height=\"15\" viewBox=\"0 0 24 24\"\n fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"\n aria-hidden=\"true\">\n <circle cx=\"12\" cy=\"12\" r=\"10\"/>\n <line x1=\"12\" y1=\"16\" x2=\"12\" y2=\"12\"/>\n <line x1=\"12\" y1=\"8\" x2=\"12.01\" y2=\"8\"/>\n </svg>\n }\n <!-- Progress spinner -->\n @if (toast.type === 'progress') {\n <span class=\"wm-toast-spinner\" aria-hidden=\"true\"></span>\n }\n\n <span class=\"wm-toast-message\">\n {{ toast.message }}\n @if (toast.type === 'progress' && toast.createdAt) {\n <span class=\"wm-toast-elapsed\"> ({{ elapsed(toast.createdAt) }})</span>\n }\n </span>\n\n <!-- Cancel button for progress toasts with an onCancel callback -->\n @if (toast.type === 'progress' && toast.onCancel) {\n <button class=\"wm-toast-cancel\" (click)=\"cancel(toast)\" aria-label=\"Cancel operation\">Cancel</button>\n }\n\n <!-- Dismiss button \u2014 always shown -->\n <button class=\"wm-toast-dismiss\" (click)=\"dismiss(toast.id)\" aria-label=\"Dismiss notification\">\n <svg width=\"13\" height=\"13\" viewBox=\"0 0 24 24\"\n fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"\n aria-hidden=\"true\">\n <line x1=\"18\" y1=\"6\" x2=\"6\" y2=\"18\"/>\n <line x1=\"6\" y1=\"6\" x2=\"18\" y2=\"18\"/>\n </svg>\n </button>\n </div>\n }\n</div>\n", styles: [".wm-toast-container{position:fixed;bottom:1rem;right:1rem;display:flex;flex-direction:column;gap:.5rem;z-index:9999;pointer-events:none}.wm-toast{display:flex;align-items:flex-start;gap:.625rem;padding:.625rem .875rem;border-radius:var(--wm-radius-sm, 4px);box-shadow:var(--wm-shadow-md, 0 4px 12px rgba(0,0,0,.25));font-size:.8125rem;line-height:1.4;min-width:220px;max-width:360px;pointer-events:auto;animation:wm-toast-slide-in .15s ease-out both}.wm-toast--success{background:color-mix(in srgb,var(--wm-color-success) 20%,var(--wm-bg-raised));color:var(--wm-color-success);border:1px solid color-mix(in srgb,var(--wm-color-success) 40%,transparent)}.wm-toast--error{background:color-mix(in srgb,var(--wm-color-danger) 20%,var(--wm-bg-raised));color:var(--wm-color-danger);border:1px solid color-mix(in srgb,var(--wm-color-danger) 40%,transparent)}.wm-toast--info{background:var(--wm-bg-raised);color:var(--wm-text-primary);border:1px solid var(--wm-border)}.wm-toast--progress{background:var(--wm-bg-raised);color:var(--wm-text-primary);border:1px solid color-mix(in srgb,var(--wm-color-accent) 40%,transparent)}.wm-toast-icon{flex-shrink:0;margin-top:1px}.wm-toast-message{flex:1;line-height:1.4}.wm-toast-dismiss{flex-shrink:0;background:none;border:none;color:inherit;opacity:.6;cursor:pointer;padding:0;margin-left:.25rem;transition:opacity .15s ease}.wm-toast-dismiss:hover{opacity:1}.wm-toast-spinner{width:14px;height:14px;border:2px solid color-mix(in srgb,var(--wm-color-accent) 30%,transparent);border-top-color:var(--wm-color-accent);border-radius:50%;flex-shrink:0;margin-top:1px;animation:wm-toast-spin .7s linear infinite}.wm-toast-elapsed{opacity:.6;font-size:.75rem;font-variant-numeric:tabular-nums}.wm-toast-cancel{flex-shrink:0;background:none;border:1px solid color-mix(in srgb,currentColor 30%,transparent);border-radius:var(--wm-radius-sm, 4px);color:inherit;opacity:.7;cursor:pointer;padding:.125rem .5rem;font-size:.75rem;transition:opacity .15s ease,border-color .15s ease}.wm-toast-cancel:hover{opacity:1;border-color:currentColor}@keyframes wm-toast-spin{to{transform:rotate(360deg)}}@keyframes wm-toast-slide-in{0%{opacity:0;transform:translate(1rem)}to{opacity:1;transform:translate(0)}}@media(prefers-reduced-motion:reduce){.wm-toast{animation:none}}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
757
|
+
}
|
|
758
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.1", ngImport: i0, type: WmToastContainer, decorators: [{
|
|
759
|
+
type: Component,
|
|
760
|
+
args: [{ selector: 'wm-toast-container', changeDetection: ChangeDetectionStrategy.OnPush, template: "<div class=\"wm-toast-container\" role=\"log\" aria-live=\"polite\" aria-label=\"Notifications\">\n @for (toast of toastService.toasts(); track toast.id) {\n <div [class]=\"typeClass(toast)\" role=\"status\">\n <!-- Success icon (check-circle) -->\n @if (toast.type === 'success') {\n <svg class=\"wm-toast-icon\" width=\"15\" height=\"15\" viewBox=\"0 0 24 24\"\n fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"\n aria-hidden=\"true\">\n <path d=\"M22 11.08V12a10 10 0 1 1-5.93-9.14\"/>\n <polyline points=\"22 4 12 14.01 9 11.01\"/>\n </svg>\n }\n <!-- Error icon (x-circle) -->\n @if (toast.type === 'error') {\n <svg class=\"wm-toast-icon\" width=\"15\" height=\"15\" viewBox=\"0 0 24 24\"\n fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"\n aria-hidden=\"true\">\n <circle cx=\"12\" cy=\"12\" r=\"10\"/>\n <line x1=\"15\" y1=\"9\" x2=\"9\" y2=\"15\"/>\n <line x1=\"9\" y1=\"9\" x2=\"15\" y2=\"15\"/>\n </svg>\n }\n <!-- Info icon -->\n @if (toast.type === 'info') {\n <svg class=\"wm-toast-icon\" width=\"15\" height=\"15\" viewBox=\"0 0 24 24\"\n fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"\n aria-hidden=\"true\">\n <circle cx=\"12\" cy=\"12\" r=\"10\"/>\n <line x1=\"12\" y1=\"16\" x2=\"12\" y2=\"12\"/>\n <line x1=\"12\" y1=\"8\" x2=\"12.01\" y2=\"8\"/>\n </svg>\n }\n <!-- Progress spinner -->\n @if (toast.type === 'progress') {\n <span class=\"wm-toast-spinner\" aria-hidden=\"true\"></span>\n }\n\n <span class=\"wm-toast-message\">\n {{ toast.message }}\n @if (toast.type === 'progress' && toast.createdAt) {\n <span class=\"wm-toast-elapsed\"> ({{ elapsed(toast.createdAt) }})</span>\n }\n </span>\n\n <!-- Cancel button for progress toasts with an onCancel callback -->\n @if (toast.type === 'progress' && toast.onCancel) {\n <button class=\"wm-toast-cancel\" (click)=\"cancel(toast)\" aria-label=\"Cancel operation\">Cancel</button>\n }\n\n <!-- Dismiss button \u2014 always shown -->\n <button class=\"wm-toast-dismiss\" (click)=\"dismiss(toast.id)\" aria-label=\"Dismiss notification\">\n <svg width=\"13\" height=\"13\" viewBox=\"0 0 24 24\"\n fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"\n aria-hidden=\"true\">\n <line x1=\"18\" y1=\"6\" x2=\"6\" y2=\"18\"/>\n <line x1=\"6\" y1=\"6\" x2=\"18\" y2=\"18\"/>\n </svg>\n </button>\n </div>\n }\n</div>\n", styles: [".wm-toast-container{position:fixed;bottom:1rem;right:1rem;display:flex;flex-direction:column;gap:.5rem;z-index:9999;pointer-events:none}.wm-toast{display:flex;align-items:flex-start;gap:.625rem;padding:.625rem .875rem;border-radius:var(--wm-radius-sm, 4px);box-shadow:var(--wm-shadow-md, 0 4px 12px rgba(0,0,0,.25));font-size:.8125rem;line-height:1.4;min-width:220px;max-width:360px;pointer-events:auto;animation:wm-toast-slide-in .15s ease-out both}.wm-toast--success{background:color-mix(in srgb,var(--wm-color-success) 20%,var(--wm-bg-raised));color:var(--wm-color-success);border:1px solid color-mix(in srgb,var(--wm-color-success) 40%,transparent)}.wm-toast--error{background:color-mix(in srgb,var(--wm-color-danger) 20%,var(--wm-bg-raised));color:var(--wm-color-danger);border:1px solid color-mix(in srgb,var(--wm-color-danger) 40%,transparent)}.wm-toast--info{background:var(--wm-bg-raised);color:var(--wm-text-primary);border:1px solid var(--wm-border)}.wm-toast--progress{background:var(--wm-bg-raised);color:var(--wm-text-primary);border:1px solid color-mix(in srgb,var(--wm-color-accent) 40%,transparent)}.wm-toast-icon{flex-shrink:0;margin-top:1px}.wm-toast-message{flex:1;line-height:1.4}.wm-toast-dismiss{flex-shrink:0;background:none;border:none;color:inherit;opacity:.6;cursor:pointer;padding:0;margin-left:.25rem;transition:opacity .15s ease}.wm-toast-dismiss:hover{opacity:1}.wm-toast-spinner{width:14px;height:14px;border:2px solid color-mix(in srgb,var(--wm-color-accent) 30%,transparent);border-top-color:var(--wm-color-accent);border-radius:50%;flex-shrink:0;margin-top:1px;animation:wm-toast-spin .7s linear infinite}.wm-toast-elapsed{opacity:.6;font-size:.75rem;font-variant-numeric:tabular-nums}.wm-toast-cancel{flex-shrink:0;background:none;border:1px solid color-mix(in srgb,currentColor 30%,transparent);border-radius:var(--wm-radius-sm, 4px);color:inherit;opacity:.7;cursor:pointer;padding:.125rem .5rem;font-size:.75rem;transition:opacity .15s ease,border-color .15s ease}.wm-toast-cancel:hover{opacity:1;border-color:currentColor}@keyframes wm-toast-spin{to{transform:rotate(360deg)}}@keyframes wm-toast-slide-in{0%{opacity:0;transform:translate(1rem)}to{opacity:1;transform:translate(0)}}@media(prefers-reduced-motion:reduce){.wm-toast{animation:none}}\n"] }]
|
|
761
|
+
}] });
|
|
762
|
+
|
|
763
|
+
class WmLicenseService {
|
|
764
|
+
toast = inject(WmToastService);
|
|
765
|
+
license = signal(null, ...(ngDevMode ? [{ debugName: "license" }] : []));
|
|
766
|
+
isLicensed = computed(() => this.license()?.valid ?? false, ...(ngDevMode ? [{ debugName: "isLicensed" }] : []));
|
|
767
|
+
orgName = computed(() => this.license()?.org ?? null, ...(ngDevMode ? [{ debugName: "orgName" }] : []));
|
|
768
|
+
async checkLicense() {
|
|
769
|
+
try {
|
|
770
|
+
const status = await invoke('get_license_status');
|
|
771
|
+
this.license.set(status);
|
|
772
|
+
if (!status.valid && status.error) {
|
|
773
|
+
const message = status.error === 'expired'
|
|
774
|
+
? 'Your license key has expired.'
|
|
775
|
+
: 'Your license key is not valid.';
|
|
776
|
+
this.toast.error(message);
|
|
777
|
+
}
|
|
778
|
+
}
|
|
779
|
+
catch (e) {
|
|
780
|
+
console.error('License check failed:', e);
|
|
781
|
+
this.license.set(null);
|
|
782
|
+
}
|
|
783
|
+
}
|
|
784
|
+
async installKey(jwt) {
|
|
785
|
+
const status = await invoke('install_license', { key: jwt });
|
|
786
|
+
this.license.set(status);
|
|
787
|
+
return status;
|
|
788
|
+
}
|
|
789
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.1", ngImport: i0, type: WmLicenseService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
790
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.1", ngImport: i0, type: WmLicenseService, providedIn: 'root' });
|
|
791
|
+
}
|
|
792
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.1", ngImport: i0, type: WmLicenseService, decorators: [{
|
|
793
|
+
type: Injectable,
|
|
794
|
+
args: [{ providedIn: 'root' }]
|
|
795
|
+
}] });
|
|
796
|
+
|
|
797
|
+
class WmLicenseNudge {
|
|
798
|
+
licenseService = inject(WmLicenseService);
|
|
799
|
+
dismissed = signal(false, ...(ngDevMode ? [{ debugName: "dismissed" }] : []));
|
|
800
|
+
enterLicense = output();
|
|
801
|
+
shouldShow = () => !this.licenseService.isLicensed() && !this.dismissed();
|
|
802
|
+
dismiss() {
|
|
803
|
+
this.dismissed.set(true);
|
|
804
|
+
}
|
|
805
|
+
onEnterLicense() {
|
|
806
|
+
this.enterLicense.emit();
|
|
807
|
+
}
|
|
808
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.1", ngImport: i0, type: WmLicenseNudge, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
809
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.1", type: WmLicenseNudge, isStandalone: true, selector: "wm-license-nudge", outputs: { enterLicense: "enterLicense" }, ngImport: i0, template: "@if (shouldShow()) {\n <div class=\"wm-license-nudge\">\n <span class=\"wm-license-nudge__text\">\n Using this at work? Organizations require a commercial license.\n </span>\n <div class=\"wm-license-nudge__actions\">\n <wm-button variant=\"ghost\" size=\"sm\" (click)=\"onEnterLicense()\">Enter License Key</wm-button>\n <wm-button variant=\"ghost\" size=\"sm\" (click)=\"dismiss()\">Dismiss</wm-button>\n </div>\n </div>\n}\n", styles: [".wm-license-nudge{display:flex;align-items:center;justify-content:space-between;gap:var(--wm-space-md, 12px);padding:var(--wm-space-sm, 8px) var(--wm-space-md, 12px);background:var(--wm-bg-subtle, #2a2a2a);border-bottom:1px solid var(--wm-border, #3a3a3a);font-size:var(--wm-font-sm, 13px);color:var(--wm-fg-secondary, #999)}.wm-license-nudge__text{flex:1}.wm-license-nudge__actions{display:flex;gap:var(--wm-space-xs, 4px);flex-shrink:0}\n"], dependencies: [{ kind: "component", type: WmButton, selector: "wm-button", inputs: ["variant", "size", "disabled", "type", "iconOnly", "ariaLabel", "btnTitle"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
810
|
+
}
|
|
811
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.1", ngImport: i0, type: WmLicenseNudge, decorators: [{
|
|
812
|
+
type: Component,
|
|
813
|
+
args: [{ selector: 'wm-license-nudge', standalone: true, imports: [WmButton], changeDetection: ChangeDetectionStrategy.OnPush, template: "@if (shouldShow()) {\n <div class=\"wm-license-nudge\">\n <span class=\"wm-license-nudge__text\">\n Using this at work? Organizations require a commercial license.\n </span>\n <div class=\"wm-license-nudge__actions\">\n <wm-button variant=\"ghost\" size=\"sm\" (click)=\"onEnterLicense()\">Enter License Key</wm-button>\n <wm-button variant=\"ghost\" size=\"sm\" (click)=\"dismiss()\">Dismiss</wm-button>\n </div>\n </div>\n}\n", styles: [".wm-license-nudge{display:flex;align-items:center;justify-content:space-between;gap:var(--wm-space-md, 12px);padding:var(--wm-space-sm, 8px) var(--wm-space-md, 12px);background:var(--wm-bg-subtle, #2a2a2a);border-bottom:1px solid var(--wm-border, #3a3a3a);font-size:var(--wm-font-sm, 13px);color:var(--wm-fg-secondary, #999)}.wm-license-nudge__text{flex:1}.wm-license-nudge__actions{display:flex;gap:var(--wm-space-xs, 4px);flex-shrink:0}\n"] }]
|
|
814
|
+
}], propDecorators: { enterLicense: [{ type: i0.Output, args: ["enterLicense"] }] } });
|
|
815
|
+
|
|
816
|
+
class WmLicenseDialog {
|
|
817
|
+
licenseService = inject(WmLicenseService);
|
|
818
|
+
visible = input.required(...(ngDevMode ? [{ debugName: "visible" }] : []));
|
|
819
|
+
dismiss = output();
|
|
820
|
+
keyInput = signal('', ...(ngDevMode ? [{ debugName: "keyInput" }] : []));
|
|
821
|
+
validationResult = signal(null, ...(ngDevMode ? [{ debugName: "validationResult" }] : []));
|
|
822
|
+
validating = signal(false, ...(ngDevMode ? [{ debugName: "validating" }] : []));
|
|
823
|
+
installed = signal(false, ...(ngDevMode ? [{ debugName: "installed" }] : []));
|
|
824
|
+
onDismiss() {
|
|
825
|
+
this.reset();
|
|
826
|
+
this.dismiss.emit();
|
|
827
|
+
}
|
|
828
|
+
onKeyInput(event) {
|
|
829
|
+
const value = event.target.value;
|
|
830
|
+
this.keyInput.set(value);
|
|
831
|
+
this.validationResult.set(null);
|
|
832
|
+
this.installed.set(false);
|
|
833
|
+
}
|
|
834
|
+
async validate() {
|
|
835
|
+
const jwt = this.keyInput().trim();
|
|
836
|
+
if (!jwt)
|
|
837
|
+
return;
|
|
838
|
+
this.validating.set(true);
|
|
839
|
+
try {
|
|
840
|
+
const status = await this.licenseService.installKey(jwt);
|
|
841
|
+
this.validationResult.set(status);
|
|
842
|
+
if (status.valid) {
|
|
843
|
+
this.installed.set(true);
|
|
844
|
+
}
|
|
845
|
+
}
|
|
846
|
+
catch (e) {
|
|
847
|
+
this.validationResult.set({
|
|
848
|
+
valid: false,
|
|
849
|
+
org: null,
|
|
850
|
+
product: '',
|
|
851
|
+
seats: null,
|
|
852
|
+
tier: null,
|
|
853
|
+
expires: null,
|
|
854
|
+
key_id: null,
|
|
855
|
+
error: String(e),
|
|
856
|
+
});
|
|
857
|
+
}
|
|
858
|
+
finally {
|
|
859
|
+
this.validating.set(false);
|
|
860
|
+
}
|
|
861
|
+
}
|
|
862
|
+
reset() {
|
|
863
|
+
this.keyInput.set('');
|
|
864
|
+
this.validationResult.set(null);
|
|
865
|
+
this.validating.set(false);
|
|
866
|
+
this.installed.set(false);
|
|
867
|
+
}
|
|
868
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.1", ngImport: i0, type: WmLicenseDialog, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
869
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.1", type: WmLicenseDialog, isStandalone: true, selector: "wm-license-dialog", inputs: { visible: { classPropertyName: "visible", publicName: "visible", isSignal: true, isRequired: true, transformFunction: null } }, outputs: { dismiss: "dismiss" }, ngImport: i0, template: "<wm-modal [visible]=\"visible()\" (dismiss)=\"onDismiss()\">\n <div class=\"wm-license-dialog\">\n <h2 class=\"wm-license-dialog__title\">Enter License Key</h2>\n\n @if (installed()) {\n <div class=\"wm-license-dialog__success\">\n <p class=\"wm-license-dialog__success-text\">License activated successfully.</p>\n <dl class=\"wm-license-dialog__details\">\n <dt>Organization</dt>\n <dd>{{ validationResult()?.org }}</dd>\n <dt>Seats</dt>\n <dd>{{ validationResult()?.seats }}</dd>\n <dt>Expires</dt>\n <dd>{{ validationResult()?.expires?.slice(0, 10) }}</dd>\n </dl>\n <wm-button variant=\"primary\" size=\"sm\" (click)=\"onDismiss()\">Done</wm-button>\n </div>\n } @else {\n <textarea\n class=\"wm-license-dialog__input\"\n placeholder=\"Paste your license key here...\"\n [value]=\"keyInput()\"\n (input)=\"onKeyInput($event)\"\n rows=\"4\"\n ></textarea>\n\n @if (validationResult(); as result) {\n @if (!result.valid) {\n <p class=\"wm-license-dialog__error\">\n @if (result.error === 'expired') {\n This license key has expired.\n } @else {\n This is not a valid license key for this product.\n }\n </p>\n }\n }\n\n <div class=\"wm-license-dialog__actions\">\n <wm-button\n variant=\"primary\"\n size=\"sm\"\n [disabled]=\"!keyInput().trim() || validating()\"\n (click)=\"validate()\"\n >\n @if (validating()) { Validating... } @else { Activate }\n </wm-button>\n </div>\n }\n </div>\n</wm-modal>\n", styles: [".wm-license-dialog{background:var(--wm-bg-raised, #1e1e1e);border-radius:var(--wm-radius-md, 8px);padding:var(--wm-space-lg, 20px);width:480px;max-width:90vw;display:flex;flex-direction:column;gap:var(--wm-space-md, 12px)}.wm-license-dialog__title{font-size:var(--wm-font-lg, 16px);font-weight:600;color:var(--wm-fg-primary, #fff);margin:0}.wm-license-dialog__input{width:100%;resize:vertical;font-family:var(--wm-font-mono, monospace);font-size:var(--wm-font-sm, 13px);padding:var(--wm-space-sm, 8px);border:1px solid var(--wm-border, #3a3a3a);border-radius:var(--wm-radius-sm, 4px);background:var(--wm-bg-input, #141414);color:var(--wm-fg-primary, #fff);box-sizing:border-box}.wm-license-dialog__input:focus{outline:none;border-color:var(--wm-accent, #4a9eff)}.wm-license-dialog__error{color:var(--wm-fg-error, #f44);font-size:var(--wm-font-sm, 13px);margin:0}.wm-license-dialog__success{display:flex;flex-direction:column;gap:var(--wm-space-md, 12px)}.wm-license-dialog__success-text{color:var(--wm-fg-success, #4caf50);font-size:var(--wm-font-sm, 13px);margin:0}.wm-license-dialog__details{display:grid;grid-template-columns:auto 1fr;gap:var(--wm-space-xs, 4px) var(--wm-space-md, 12px);font-size:var(--wm-font-sm, 13px);margin:0}.wm-license-dialog__details dt{color:var(--wm-fg-secondary, #999)}.wm-license-dialog__details dd{color:var(--wm-fg-primary, #fff);margin:0}.wm-license-dialog__actions{display:flex;justify-content:flex-end;gap:var(--wm-space-sm, 8px)}\n"], dependencies: [{ kind: "component", type: WmModal, selector: "wm-modal", inputs: ["visible", "align"], outputs: ["dismiss"] }, { kind: "component", type: WmButton, selector: "wm-button", inputs: ["variant", "size", "disabled", "type", "iconOnly", "ariaLabel", "btnTitle"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
870
|
+
}
|
|
871
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.1", ngImport: i0, type: WmLicenseDialog, decorators: [{
|
|
872
|
+
type: Component,
|
|
873
|
+
args: [{ selector: 'wm-license-dialog', standalone: true, imports: [WmModal, WmButton], changeDetection: ChangeDetectionStrategy.OnPush, template: "<wm-modal [visible]=\"visible()\" (dismiss)=\"onDismiss()\">\n <div class=\"wm-license-dialog\">\n <h2 class=\"wm-license-dialog__title\">Enter License Key</h2>\n\n @if (installed()) {\n <div class=\"wm-license-dialog__success\">\n <p class=\"wm-license-dialog__success-text\">License activated successfully.</p>\n <dl class=\"wm-license-dialog__details\">\n <dt>Organization</dt>\n <dd>{{ validationResult()?.org }}</dd>\n <dt>Seats</dt>\n <dd>{{ validationResult()?.seats }}</dd>\n <dt>Expires</dt>\n <dd>{{ validationResult()?.expires?.slice(0, 10) }}</dd>\n </dl>\n <wm-button variant=\"primary\" size=\"sm\" (click)=\"onDismiss()\">Done</wm-button>\n </div>\n } @else {\n <textarea\n class=\"wm-license-dialog__input\"\n placeholder=\"Paste your license key here...\"\n [value]=\"keyInput()\"\n (input)=\"onKeyInput($event)\"\n rows=\"4\"\n ></textarea>\n\n @if (validationResult(); as result) {\n @if (!result.valid) {\n <p class=\"wm-license-dialog__error\">\n @if (result.error === 'expired') {\n This license key has expired.\n } @else {\n This is not a valid license key for this product.\n }\n </p>\n }\n }\n\n <div class=\"wm-license-dialog__actions\">\n <wm-button\n variant=\"primary\"\n size=\"sm\"\n [disabled]=\"!keyInput().trim() || validating()\"\n (click)=\"validate()\"\n >\n @if (validating()) { Validating... } @else { Activate }\n </wm-button>\n </div>\n }\n </div>\n</wm-modal>\n", styles: [".wm-license-dialog{background:var(--wm-bg-raised, #1e1e1e);border-radius:var(--wm-radius-md, 8px);padding:var(--wm-space-lg, 20px);width:480px;max-width:90vw;display:flex;flex-direction:column;gap:var(--wm-space-md, 12px)}.wm-license-dialog__title{font-size:var(--wm-font-lg, 16px);font-weight:600;color:var(--wm-fg-primary, #fff);margin:0}.wm-license-dialog__input{width:100%;resize:vertical;font-family:var(--wm-font-mono, monospace);font-size:var(--wm-font-sm, 13px);padding:var(--wm-space-sm, 8px);border:1px solid var(--wm-border, #3a3a3a);border-radius:var(--wm-radius-sm, 4px);background:var(--wm-bg-input, #141414);color:var(--wm-fg-primary, #fff);box-sizing:border-box}.wm-license-dialog__input:focus{outline:none;border-color:var(--wm-accent, #4a9eff)}.wm-license-dialog__error{color:var(--wm-fg-error, #f44);font-size:var(--wm-font-sm, 13px);margin:0}.wm-license-dialog__success{display:flex;flex-direction:column;gap:var(--wm-space-md, 12px)}.wm-license-dialog__success-text{color:var(--wm-fg-success, #4caf50);font-size:var(--wm-font-sm, 13px);margin:0}.wm-license-dialog__details{display:grid;grid-template-columns:auto 1fr;gap:var(--wm-space-xs, 4px) var(--wm-space-md, 12px);font-size:var(--wm-font-sm, 13px);margin:0}.wm-license-dialog__details dt{color:var(--wm-fg-secondary, #999)}.wm-license-dialog__details dd{color:var(--wm-fg-primary, #fff);margin:0}.wm-license-dialog__actions{display:flex;justify-content:flex-end;gap:var(--wm-space-sm, 8px)}\n"] }]
|
|
874
|
+
}], propDecorators: { visible: [{ type: i0.Input, args: [{ isSignal: true, alias: "visible", required: true }] }], dismiss: [{ type: i0.Output, args: ["dismiss"] }] } });
|
|
875
|
+
|
|
876
|
+
// Components
|
|
877
|
+
|
|
878
|
+
/**
|
|
879
|
+
* Generated bundle index. Do not edit.
|
|
880
|
+
*/
|
|
881
|
+
|
|
882
|
+
export { Checkbox, GhostField, NumberInput, PageHeader, Toggle, WmAutoFocus, WmButton, WmDialog, WmLicenseDialog, WmLicenseNudge, WmLicenseService, WmModal, WmPopover, WmSelect, WmTab, WmTabs, WmToastContainer, WmToastService };
|
|
883
|
+
//# sourceMappingURL=wildmason-aegis.mjs.map
|