@mk-kit/ui 0.56.0 → 0.58.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,223 @@
1
+ import * as i0 from '@angular/core';
2
+ import { InjectionToken, OnDestroy, EnvironmentProviders } from '@angular/core';
3
+ import * as _mk_kit_ui_core from '@mk-kit/ui/core';
4
+
5
+ /** Options for {@link MkTabAttention}, set with {@link provideMkTabAttention}. */
6
+ interface MkTabAttentionConfig {
7
+ /** Badge fill colour (any CSS colour). Default `#e53935`. */
8
+ badgeColor?: string;
9
+ /** Title blink period in ms while the tab is hidden. Default `1200`. */
10
+ blinkMs?: number;
11
+ }
12
+ declare const MK_TAB_ATTENTION_CONFIG: InjectionToken<MkTabAttentionConfig>;
13
+ /** Register options for {@link MkTabAttention}. Optional — the defaults work. */
14
+ declare function provideMkTabAttention(config: MkTabAttentionConfig): {
15
+ provide: InjectionToken<MkTabAttentionConfig>;
16
+ useValue: MkTabAttentionConfig;
17
+ };
18
+ /**
19
+ * Messenger-style tab attention: while unhandled work exists the favicon
20
+ * carries a red counter badge, and — only while the tab is hidden — the
21
+ * title alternates with "(N) label" so a pinned tab flashes in the tab strip.
22
+ * Focusing the tab stops the blinking (someone is looking) but keeps the
23
+ * badge until the count reaches zero. SSR-safe: every entry point bails
24
+ * without a `document`; the blink timer runs outside the Angular zone.
25
+ *
26
+ * ```ts
27
+ * private attention = inject(MkTabAttention);
28
+ * effect(() => this.attention.set(this.pending().length, 'new orders'));
29
+ * ```
30
+ */
31
+ declare class MkTabAttention {
32
+ private readonly zone;
33
+ private readonly config;
34
+ private label;
35
+ private originalTitle;
36
+ private originalFavicon;
37
+ private blinkTimer;
38
+ private showingAttention;
39
+ private listening;
40
+ private readonly visibilityHandler;
41
+ /** The count currently shown (0 = nothing pending). */
42
+ readonly count: i0.WritableSignal<number>;
43
+ /** Update the pending count and the label used in the blinking title. */
44
+ set(count: number, label?: string): void;
45
+ /** Drop the badge and the blinking entirely and stop listening. */
46
+ clear(): void;
47
+ private sync;
48
+ private startBlink;
49
+ private stopBlink;
50
+ private toggleTitle;
51
+ private restoreTitle;
52
+ private faviconLink;
53
+ private setFavicon;
54
+ private restoreFavicon;
55
+ /** Coloured circle + white count, as an inline SVG data URI. */
56
+ private badgeFavicon;
57
+ static ɵfac: i0.ɵɵFactoryDeclaration<MkTabAttention, never>;
58
+ static ɵprov: i0.ɵɵInjectableDeclaration<MkTabAttention>;
59
+ }
60
+
61
+ /** One selectable alert sound. `url: null` = the synthesised chime. */
62
+ interface MkSoundPreset {
63
+ id: string;
64
+ label: string;
65
+ url: string | null;
66
+ }
67
+ /** Options for {@link MkNotificationSound}. */
68
+ interface MkNotificationSoundConfig {
69
+ /** Selectable sounds; the ids `custom` and `none` are reserved. Default: the chime only. */
70
+ presets?: MkSoundPreset[];
71
+ /** localStorage key for the on/off preference — a function so it can vary per tenant / user. */
72
+ storageKey?: () => string;
73
+ /** Output gain for file presets (0–1). Default `0.8`. */
74
+ volume?: number;
75
+ }
76
+ declare const MK_NOTIFICATION_SOUND_CONFIG: InjectionToken<MkNotificationSoundConfig>;
77
+ /** Register presets / storage key for {@link MkNotificationSound}. Optional. */
78
+ declare function provideMkNotificationSound(config: MkNotificationSoundConfig): {
79
+ provide: InjectionToken<MkNotificationSoundConfig>;
80
+ useValue: MkNotificationSoundConfig;
81
+ };
82
+ /** The always-available synthesised sound. */
83
+ declare const MK_CHIME_PRESET: MkSoundPreset;
84
+ /**
85
+ * Alert sounds for incoming work (orders, messages, tickets) with the
86
+ * browser's autoplay rules handled: an `AudioContext` stays suspended until a
87
+ * user gesture, so a sound fired by a WebSocket message would be silent. The
88
+ * context is unlocked when the user enables sound (a click) and lazily on the
89
+ * first interaction anywhere in the app. The default sound is synthesised
90
+ * (a short C–E–G chime) — nothing to ship, no CORS, lowest latency; file
91
+ * presets are fetched and decoded once and fall back to the chime when they
92
+ * fail. The on/off preference lives in localStorage under a configurable key.
93
+ *
94
+ * ```ts
95
+ * provideMkNotificationSound({ presets: [MK_CHIME_PRESET, { id: 'ding', label: 'Ding', url: '/assets/ding.wav' }] })
96
+ * sound.primeOnFirstInteraction(); // at app start
97
+ * sound.play(settings.newOrderSound); // on an event, honours the device mute
98
+ * sound.preview('ding'); // settings page test button
99
+ * ```
100
+ */
101
+ declare class MkNotificationSound {
102
+ private readonly config;
103
+ private ctx;
104
+ private gesturePrimed;
105
+ /** Decoded file presets by url; null = fetch/decode failed. */
106
+ private readonly buffers;
107
+ /** The selectable presets (always includes the chime). */
108
+ get presets(): MkSoundPreset[];
109
+ private storageKey;
110
+ /** Whether the device has sound on. */
111
+ isEnabled(): boolean;
112
+ /** Whether the user ever answered the "enable sound?" question on this device. */
113
+ hasBeenAsked(): boolean;
114
+ /** Persist the preference; call from a click so audio unlocks at once. */
115
+ setEnabled(enabled: boolean): void;
116
+ /** Arm a one-time listener so the first pointer/key interaction unlocks audio. */
117
+ primeOnFirstInteraction(): void;
118
+ /** The default chime, if the device has sound on. */
119
+ chime(): void;
120
+ /**
121
+ * Play the sound configured for an event: a preset id, `custom` (with
122
+ * `customUrl`) or `none` (silent). Unknown ids and failed loads fall back
123
+ * to the chime. Honours the device mute.
124
+ */
125
+ play(soundId: string, customUrl?: string | null): void;
126
+ /** Same as `play()` but ignores the device mute — for settings test buttons. */
127
+ preview(soundId: string, customUrl?: string | null): void;
128
+ private playById;
129
+ private playUrl;
130
+ /** Ascending C5–E5–G5 chime (~0.75 s) with a bell-like timbre. */
131
+ private playChime;
132
+ private scheduleNote;
133
+ private unlock;
134
+ private ensureCtx;
135
+ private read;
136
+ static ɵfac: i0.ɵɵFactoryDeclaration<MkNotificationSound, never>;
137
+ static ɵprov: i0.ɵɵInjectableDeclaration<MkNotificationSound>;
138
+ }
139
+
140
+ /** Options for {@link provideMkSessionExpiry}. */
141
+ interface MkSessionExpiryConfig {
142
+ /** Epoch ms when the session lapses, or `null` when there is none. Read reactively. */
143
+ expiresAt: () => number | null;
144
+ /** How long before the lapse the dialog appears. Default 2 minutes. */
145
+ warnBeforeMs?: number;
146
+ /** Extend the session (refresh the token). Resolve = extended, reject = nothing to extend. */
147
+ extend: () => Promise<unknown>;
148
+ /** End the session (sign out, navigate). */
149
+ onExpire: () => void;
150
+ /** Read reactively; `false` suspends the watcher (a kiosk / PIN mode, say). */
151
+ enabled?: () => boolean;
152
+ }
153
+ declare const MK_SESSION_EXPIRY_CONFIG: InjectionToken<MkSessionExpiryConfig>;
154
+ /** Data handed to {@link MkSessionExpiryDialog}. */
155
+ interface MkSessionExpiryDialogData {
156
+ expiresAt: number;
157
+ extend: () => Promise<unknown>;
158
+ onExpire: () => void;
159
+ }
160
+ /**
161
+ * Last call before a session ends: counts down and offers to extend.
162
+ * Reaching zero ends the session, so doing nothing still produces a definite,
163
+ * visible outcome. Opened by {@link MkSessionExpiry}; usable on its own.
164
+ */
165
+ declare class MkSessionExpiryDialog implements OnDestroy {
166
+ protected readonly i18n: _mk_kit_ui_core.MkI18nStrings;
167
+ private readonly data;
168
+ private readonly ref;
169
+ private readonly zone;
170
+ protected readonly extending: i0.WritableSignal<boolean>;
171
+ private readonly remainingMs;
172
+ /** "1:04" — floored at zero. */
173
+ protected readonly countdown: i0.Signal<string>;
174
+ private readonly ticker;
175
+ ngOnDestroy(): void;
176
+ protected extend(): void;
177
+ protected signOut(): void;
178
+ private expire;
179
+ static ɵfac: i0.ɵɵFactoryDeclaration<MkSessionExpiryDialog, never>;
180
+ static ɵcmp: i0.ɵɵComponentDeclaration<MkSessionExpiryDialog, "mk-session-expiry-dialog", never, {}, {}, never, never, true, never>;
181
+ }
182
+ /**
183
+ * Watches `expiresAt()` and warns BEFORE the session lapses, so a session
184
+ * never ends silently: the dialog offers to extend, or signs out at zero.
185
+ * Re-arms itself whenever `expiresAt()` changes (every token rotation),
186
+ * runs the timer outside the Angular zone and only in the browser. Started
187
+ * automatically by {@link provideMkSessionExpiry}.
188
+ */
189
+ declare class MkSessionExpiry {
190
+ private readonly config;
191
+ private readonly dialog;
192
+ private readonly zone;
193
+ private readonly injector;
194
+ private readonly isBrowser;
195
+ private timer;
196
+ private started;
197
+ /** Whether the dialog is currently open. */
198
+ readonly open: i0.WritableSignal<boolean>;
199
+ constructor();
200
+ /** Begin watching. Idempotent; called by the provider's initializer. */
201
+ start(): void;
202
+ private clear;
203
+ private schedule;
204
+ private warn;
205
+ static ɵfac: i0.ɵɵFactoryDeclaration<MkSessionExpiry, never>;
206
+ static ɵprov: i0.ɵɵInjectableDeclaration<MkSessionExpiry>;
207
+ }
208
+ /**
209
+ * Register the session-expiry watcher; it starts with the application.
210
+ *
211
+ * ```ts
212
+ * provideMkSessionExpiry({
213
+ * expiresAt: () => auth.tokenExpiresAt(),
214
+ * extend: () => firstValueFrom(auth.refresh()),
215
+ * onExpire: () => auth.logout(),
216
+ * warnBeforeMs: 2 * 60_000,
217
+ * })
218
+ * ```
219
+ */
220
+ declare function provideMkSessionExpiry(config: MkSessionExpiryConfig): EnvironmentProviders;
221
+
222
+ export { MK_CHIME_PRESET, MK_NOTIFICATION_SOUND_CONFIG, MK_SESSION_EXPIRY_CONFIG, MK_TAB_ATTENTION_CONFIG, MkNotificationSound, MkSessionExpiry, MkSessionExpiryDialog, MkTabAttention, provideMkNotificationSound, provideMkSessionExpiry, provideMkTabAttention };
223
+ export type { MkNotificationSoundConfig, MkSessionExpiryConfig, MkSessionExpiryDialogData, MkSoundPreset, MkTabAttentionConfig };
@@ -119,6 +119,60 @@ declare class MkThemeService {
119
119
  static ɵprov: _angular_core.ɵɵInjectableDeclaration<MkThemeService>;
120
120
  }
121
121
 
122
+ /** One of the eight kit accents (the Momentum palette). */
123
+ type MkAccentKey = 'indigo' | 'blue' | 'teal' | 'violet' | 'coral' | 'lime' | 'pink' | 'bumblebee';
124
+ /** `fill` = bars, rings and buttons · `ink` = small accent text on a light ground. */
125
+ interface MkAccent {
126
+ fill: string;
127
+ ink: string;
128
+ name: string;
129
+ }
130
+ /** High-saturation, distinct, energising accents — the set the `momentum` preset was designed around. */
131
+ declare const MK_ACCENTS: Record<MkAccentKey, MkAccent>;
132
+ /** Picker order. */
133
+ declare const MK_ACCENT_ORDER: readonly MkAccentKey[];
134
+ /** The swatch a picker shows for an accent (bumblebee is the black / yellow split). */
135
+ declare function mkAccentSwatch(key: MkAccentKey): string;
136
+ /** `#rrggbb` → `rgba(r,g,b,a)`. */
137
+ declare function mkHexAlpha(hex: string, alpha: number): string;
138
+ /**
139
+ * Runtime accent — the user picks one of {@link MK_ACCENTS} and every kit
140
+ * control recolours: the service writes the `--mk-primary` family
141
+ * (`primary`, `-hover`, `-active`, `-subtle`, `-subtle-hover`,
142
+ * `-subtle-text`), `--mk-focus-ring`, `--mk-selected-bg` / `-text` and three
143
+ * accent tokens of its own — `--mk-accent`, `--mk-accent-ink`,
144
+ * `--mk-accent-glow` — onto `<html>`, and mirrors the key as
145
+ * `data-mk-accent`. Hover / active are a touch darker in light and a touch
146
+ * lighter in dark (it follows {@link MkThemeService}). The choice persists
147
+ * in `localStorage` (`mk-kit-accent`); nothing is written until `set()` is
148
+ * called, so an app that never picks keeps the preset's own primary.
149
+ *
150
+ * ```ts
151
+ * readonly accent = inject(MkAccentService);
152
+ * accent.set('coral'); // every button, ring and chip turns coral
153
+ * accent.key(); // 'coral'
154
+ * mkAccentSwatch(accent.key()); // for the picker
155
+ * ```
156
+ */
157
+ declare class MkAccentService {
158
+ private readonly document;
159
+ private readonly theme;
160
+ private readonly isBrowser;
161
+ private readonly _key;
162
+ /** The chosen accent, or `null` while the preset's own primary is in use. */
163
+ readonly key: _angular_core.Signal<MkAccentKey | null>;
164
+ /** The chosen accent's definition (indigo when none is chosen). */
165
+ readonly accent: _angular_core.Signal<MkAccent>;
166
+ constructor();
167
+ /** Pick an accent; persisted. */
168
+ set(key: MkAccentKey): void;
169
+ /** Back to the preset's own primary; the stored choice is cleared. */
170
+ reset(): void;
171
+ private readInitial;
172
+ static ɵfac: _angular_core.ɵɵFactoryDeclaration<MkAccentService, never>;
173
+ static ɵprov: _angular_core.ɵɵInjectableDeclaration<MkAccentService>;
174
+ }
175
+
122
176
  /** Named viewport ranges, mobile-first: each starts at its min-width. */
123
177
  type MkBreakpoint = 'xs' | 'sm' | 'md' | 'lg' | 'xl' | '2xl';
124
178
  /** Min-widths (px) at which each named breakpoint begins; `xs` is everything below `sm`. */
@@ -780,6 +834,16 @@ interface MkI18nStrings {
780
834
  translationEditorReset: string;
781
835
  translationEditorExport: string;
782
836
  translationEditorKeys: string;
837
+ /** Session-expiry dialog (`@mk-kit/ui/attention`). */
838
+ sessionExpiryTitle: string;
839
+ sessionExpiryExtend: string;
840
+ sessionExpiryExtending: string;
841
+ sessionExpiryLogout: string;
842
+ sessionExpiryBody: (countdown: string) => string;
843
+ /** Barcode scanner (`@mk-kit/ui/media/scanner`). */
844
+ scannerTitle: string;
845
+ scannerHint: string;
846
+ scannerCameraError: string;
783
847
  /** Announced when a filterable list updates (autocomplete, multi-select, command palette). */
784
848
  resultsCount: (count: number) => string;
785
849
  /** Pagination: previous page control. */
@@ -1348,5 +1412,5 @@ declare function mkSignalErrorsToValidationErrors(errors: readonly MkSignalValid
1348
1412
  */
1349
1413
  declare function mkSignalErrorMessage(errors: readonly MkSignalValidationError[] | null | undefined, strings: MkValidationStrings, overrides?: MkErrorMessages): string | null;
1350
1414
 
1351
- export { MK_BREAKPOINTS, MK_DEFAULT_BREAKPOINTS, MK_DEFAULT_DATE_NAMES, MK_DEFAULT_I18N, MK_DEFAULT_VALIDATION, MK_I18N, MK_OVERLAY_DATA, MK_OVERLAY_ROOT, MkAnchoredPanel, MkBreakpointService, MkFieldContext, MkFocusTrap, MkLiveAnnouncer, MkOverlayRef, MkOverlayService, MkThemeService, mkBodyLevelAncestor, mkFirstErrorMessage, mkGetFocusable, mkIsResponsive, mkMergeI18n, mkSignalErrorMessage, mkSignalErrorsToValidationErrors, mkValidatorChange, provideMkI18n };
1352
- export type { MkAriaLivePoliteness, MkBlockEditorStrings, MkBreakpoint, MkBreakpoints, MkContrastPreference, MkDensity, MkErrorMessages, MkI18nOverrides, MkI18nStrings, MkOverlayConfig, MkOverlayRootFn, MkResolvedContrast, MkResolvedTheme, MkResponsive, MkSignalValidationError, MkSize, MkSortAnnounceDirection, MkThemePreference, MkTone, MkValidationStrings, MkValidatorChangeRef, MkVariant };
1415
+ export { MK_ACCENTS, MK_ACCENT_ORDER, MK_BREAKPOINTS, MK_DEFAULT_BREAKPOINTS, MK_DEFAULT_DATE_NAMES, MK_DEFAULT_I18N, MK_DEFAULT_VALIDATION, MK_I18N, MK_OVERLAY_DATA, MK_OVERLAY_ROOT, MkAccentService, MkAnchoredPanel, MkBreakpointService, MkFieldContext, MkFocusTrap, MkLiveAnnouncer, MkOverlayRef, MkOverlayService, MkThemeService, mkAccentSwatch, mkBodyLevelAncestor, mkFirstErrorMessage, mkGetFocusable, mkHexAlpha, mkIsResponsive, mkMergeI18n, mkSignalErrorMessage, mkSignalErrorsToValidationErrors, mkValidatorChange, provideMkI18n };
1416
+ export type { MkAccent, MkAccentKey, MkAriaLivePoliteness, MkBlockEditorStrings, MkBreakpoint, MkBreakpoints, MkContrastPreference, MkDensity, MkErrorMessages, MkI18nOverrides, MkI18nStrings, MkOverlayConfig, MkOverlayRootFn, MkResolvedContrast, MkResolvedTheme, MkResponsive, MkSignalValidationError, MkSize, MkSortAnnounceDirection, MkThemePreference, MkTone, MkValidationStrings, MkValidatorChangeRef, MkVariant };
@@ -0,0 +1,74 @@
1
+ import * as _angular_core from '@angular/core';
2
+ import { AfterViewInit, OnDestroy } from '@angular/core';
3
+ import * as _mk_kit_ui_core from '@mk-kit/ui/core';
4
+ import { MkOverlayRef } from '@mk-kit/ui/core';
5
+
6
+ /** Symbologies the scanner reads; names follow `Html5QrcodeSupportedFormats`. */
7
+ type MkBarcodeFormat = 'QR_CODE' | 'EAN_13' | 'EAN_8' | 'CODE_128' | 'CODE_39' | 'UPC_A' | 'UPC_E' | 'DATA_MATRIX' | 'ITF' | 'CODABAR';
8
+ declare const MK_BARCODE_DEFAULT_FORMATS: MkBarcodeFormat[];
9
+ /**
10
+ * Camera barcode / QR reader. Starts the rear camera when it appears, emits
11
+ * `scanned` once with the first decoded text and stops. The decoder
12
+ * (`html5-qrcode`, an optional peer dependency) is loaded on demand, so pages
13
+ * that only *offer* scanning ship nothing extra until a scan starts. Use
14
+ * inline, or through {@link MkBarcodeScannerDialog}.
15
+ *
16
+ * ```html
17
+ * <mk-barcode-scanner (scanned)="onCode($event)" (failed)="show($event)" />
18
+ * ```
19
+ */
20
+ declare class MkBarcodeScanner implements AfterViewInit, OnDestroy {
21
+ protected readonly i18n: _mk_kit_ui_core.MkI18nStrings;
22
+ private readonly readerEl;
23
+ /** Symbologies to decode. Default: QR + the retail 1-D codes. */
24
+ readonly formats: _angular_core.InputSignal<MkBarcodeFormat[]>;
25
+ /** Frames per second offered to the decoder. Default 10. */
26
+ readonly fps: _angular_core.InputSignal<number>;
27
+ /** Show the built-in hint line above the viewfinder. Default `true`. */
28
+ readonly hint: _angular_core.InputSignal<boolean>;
29
+ /** Keep scanning after a hit instead of stopping. Default `false`. */
30
+ readonly continuous: _angular_core.InputSignal<boolean>;
31
+ /** Decoded text. */
32
+ readonly scanned: _angular_core.OutputEmitterRef<string>;
33
+ /** The camera could not start (permission, no device, insecure context). */
34
+ readonly failed: _angular_core.OutputEmitterRef<string>;
35
+ protected readonly error: _angular_core.WritableSignal<string | null>;
36
+ protected readonly scanning: _angular_core.WritableSignal<boolean>;
37
+ private scanner;
38
+ private destroyed;
39
+ private lastHit;
40
+ ngAfterViewInit(): Promise<void>;
41
+ ngOnDestroy(): void;
42
+ /** Stop the camera and release it. Safe to call twice. */
43
+ stop(): Promise<void>;
44
+ private onHit;
45
+ private fail;
46
+ static ɵfac: _angular_core.ɵɵFactoryDeclaration<MkBarcodeScanner, never>;
47
+ static ɵcmp: _angular_core.ɵɵComponentDeclaration<MkBarcodeScanner, "mk-barcode-scanner", never, { "formats": { "alias": "formats"; "required": false; "isSignal": true; }; "fps": { "alias": "fps"; "required": false; "isSignal": true; }; "hint": { "alias": "hint"; "required": false; "isSignal": true; }; "continuous": { "alias": "continuous"; "required": false; "isSignal": true; }; }, { "scanned": "scanned"; "failed": "failed"; }, never, never, true, never>;
48
+ }
49
+
50
+ /** Optional data for {@link MkBarcodeScannerDialog}. */
51
+ interface MkBarcodeScannerDialogData {
52
+ title?: string;
53
+ formats?: MkBarcodeFormat[];
54
+ }
55
+ /**
56
+ * The scanner in a dialog: resolves with the decoded text, or `null` when
57
+ * cancelled. Open it with `MkDialogService`:
58
+ *
59
+ * ```ts
60
+ * const code = await dialog.open<MkBarcodeScannerDialog, string | null>(MkBarcodeScannerDialog, { size: 'sm' }).afterClosed;
61
+ * if (code) this.search.setValue(code);
62
+ * ```
63
+ */
64
+ declare class MkBarcodeScannerDialog {
65
+ protected readonly i18n: _mk_kit_ui_core.MkI18nStrings;
66
+ protected readonly data: MkBarcodeScannerDialogData | null;
67
+ protected readonly ref: MkOverlayRef<string | null, unknown>;
68
+ protected readonly defaultFormats: MkBarcodeFormat[];
69
+ static ɵfac: _angular_core.ɵɵFactoryDeclaration<MkBarcodeScannerDialog, never>;
70
+ static ɵcmp: _angular_core.ɵɵComponentDeclaration<MkBarcodeScannerDialog, "mk-barcode-scanner-dialog", never, {}, {}, never, never, true, never>;
71
+ }
72
+
73
+ export { MK_BARCODE_DEFAULT_FORMATS, MkBarcodeScanner, MkBarcodeScannerDialog };
74
+ export type { MkBarcodeFormat, MkBarcodeScannerDialogData };
@@ -15,6 +15,7 @@ export * from '@mk-kit/ui/status';
15
15
  export * from '@mk-kit/ui/data';
16
16
  export * from '@mk-kit/ui/kanban';
17
17
  export * from '@mk-kit/ui/translate';
18
+ export * from '@mk-kit/ui/attention';
18
19
  export * from '@mk-kit/ui/feedback';
19
20
  export * from '@mk-kit/ui/rich-text';
20
21
  export * from '@mk-kit/ui/block-editor';