@mk-kit/ui 0.56.0 → 0.57.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 };
@@ -780,6 +780,16 @@ interface MkI18nStrings {
780
780
  translationEditorReset: string;
781
781
  translationEditorExport: string;
782
782
  translationEditorKeys: string;
783
+ /** Session-expiry dialog (`@mk-kit/ui/attention`). */
784
+ sessionExpiryTitle: string;
785
+ sessionExpiryExtend: string;
786
+ sessionExpiryExtending: string;
787
+ sessionExpiryLogout: string;
788
+ sessionExpiryBody: (countdown: string) => string;
789
+ /** Barcode scanner (`@mk-kit/ui/media/scanner`). */
790
+ scannerTitle: string;
791
+ scannerHint: string;
792
+ scannerCameraError: string;
783
793
  /** Announced when a filterable list updates (autocomplete, multi-select, command palette). */
784
794
  resultsCount: (count: number) => string;
785
795
  /** Pagination: previous page control. */
@@ -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';