@mk-kit/ui 0.55.1 → 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.
- package/bin/mk-translate.mjs +223 -0
- package/fesm2022/mk-kit-ui-attention.mjs +552 -0
- package/fesm2022/mk-kit-ui-attention.mjs.map +1 -0
- package/fesm2022/mk-kit-ui-core.mjs +16 -0
- package/fesm2022/mk-kit-ui-core.mjs.map +1 -1
- package/fesm2022/mk-kit-ui-locales-de.mjs +16 -0
- package/fesm2022/mk-kit-ui-locales-de.mjs.map +1 -1
- package/fesm2022/mk-kit-ui-locales-es.mjs +16 -0
- package/fesm2022/mk-kit-ui-locales-es.mjs.map +1 -1
- package/fesm2022/mk-kit-ui-locales-fr.mjs +16 -0
- package/fesm2022/mk-kit-ui-locales-fr.mjs.map +1 -1
- package/fesm2022/mk-kit-ui-locales-pl.mjs +16 -0
- package/fesm2022/mk-kit-ui-locales-pl.mjs.map +1 -1
- package/fesm2022/mk-kit-ui-locales-uk.mjs +16 -0
- package/fesm2022/mk-kit-ui-locales-uk.mjs.map +1 -1
- package/fesm2022/mk-kit-ui-media-scanner.mjs +189 -0
- package/fesm2022/mk-kit-ui-media-scanner.mjs.map +1 -0
- package/fesm2022/mk-kit-ui-translate-editor.mjs +157 -0
- package/fesm2022/mk-kit-ui-translate-editor.mjs.map +1 -0
- package/fesm2022/mk-kit-ui.mjs +1 -0
- package/fesm2022/mk-kit-ui.mjs.map +1 -1
- package/package.json +23 -2
- package/types/mk-kit-ui-attention.d.ts +223 -0
- package/types/mk-kit-ui-core.d.ts +19 -0
- package/types/mk-kit-ui-media-scanner.d.ts +74 -0
- package/types/mk-kit-ui-translate-editor.d.ts +76 -0
- package/types/mk-kit-ui.d.ts +1 -0
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
import * as i0 from '@angular/core';
|
|
2
|
+
import { inject, input, output, signal, computed, ChangeDetectionStrategy, Component } from '@angular/core';
|
|
3
|
+
import { MK_I18N } from '@mk-kit/ui/core';
|
|
4
|
+
import { MkButton } from '@mk-kit/ui/button';
|
|
5
|
+
import { MkIcon } from '@mk-kit/ui/icon';
|
|
6
|
+
import { MkInlineEdit } from '@mk-kit/ui/data';
|
|
7
|
+
import { MkInput } from '@mk-kit/ui/forms';
|
|
8
|
+
import { mkExportCsv, MkTable, MkTableCell } from '@mk-kit/ui/table';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Translation editor — keys as rows, locales as columns, click a cell to
|
|
12
|
+
* edit. The base strings are your bundled files; edits are **overrides**
|
|
13
|
+
* kept apart from them, so a rebuild never loses them and a cell can be
|
|
14
|
+
* restored to the file text. Search by key or text, filter to the keys a
|
|
15
|
+
* locale is missing or the ones already edited, export the grid as CSV.
|
|
16
|
+
* Persistence is yours: every edit is emitted as `changed`, feed the result
|
|
17
|
+
* back through `overrides`.
|
|
18
|
+
*
|
|
19
|
+
* ```html
|
|
20
|
+
* <mk-translation-editor
|
|
21
|
+
* [locales]="['pl', 'en', 'ru']"
|
|
22
|
+
* [base]="base()"
|
|
23
|
+
* [overrides]="overrides()"
|
|
24
|
+
* (changed)="save($event)" />
|
|
25
|
+
* ```
|
|
26
|
+
*/
|
|
27
|
+
class MkTranslationEditor {
|
|
28
|
+
i18n = inject(MK_I18N);
|
|
29
|
+
/** Locale codes, in column order. The first one is the reference language. */
|
|
30
|
+
locales = input.required(/* @ts-ignore */
|
|
31
|
+
...(ngDevMode ? [{ debugName: "locales" }] : /* istanbul ignore next */ []));
|
|
32
|
+
/** Bundled strings per locale, flat (`'a.b.c': 'text'`) or nested. */
|
|
33
|
+
base = input.required(/* @ts-ignore */
|
|
34
|
+
...(ngDevMode ? [{ debugName: "base" }] : /* istanbul ignore next */ []));
|
|
35
|
+
/** Edits per locale, flat. Missing locales are fine. */
|
|
36
|
+
overrides = input({}, /* @ts-ignore */
|
|
37
|
+
...(ngDevMode ? [{ debugName: "overrides" }] : /* istanbul ignore next */ []));
|
|
38
|
+
/** Show but do not edit. */
|
|
39
|
+
readonly = input(false, /* @ts-ignore */
|
|
40
|
+
...(ngDevMode ? [{ debugName: "readonly" }] : /* istanbul ignore next */ []));
|
|
41
|
+
/** File name of the CSV export. */
|
|
42
|
+
exportFilename = input('translations.csv', /* @ts-ignore */
|
|
43
|
+
...(ngDevMode ? [{ debugName: "exportFilename" }] : /* istanbul ignore next */ []));
|
|
44
|
+
/** An edit or a restore; persist it and update `overrides`. */
|
|
45
|
+
changed = output();
|
|
46
|
+
query = signal('', /* @ts-ignore */
|
|
47
|
+
...(ngDevMode ? [{ debugName: "query" }] : /* istanbul ignore next */ []));
|
|
48
|
+
filter = signal('all', /* @ts-ignore */
|
|
49
|
+
...(ngDevMode ? [{ debugName: "filter" }] : /* istanbul ignore next */ []));
|
|
50
|
+
/** Every key any locale knows, sorted. */
|
|
51
|
+
keys = computed(() => {
|
|
52
|
+
const set = new Set();
|
|
53
|
+
for (const locale of this.locales()) {
|
|
54
|
+
for (const k of Object.keys(this.base()[locale] ?? {}))
|
|
55
|
+
set.add(k);
|
|
56
|
+
for (const k of Object.keys(this.overrides()[locale] ?? {}))
|
|
57
|
+
set.add(k);
|
|
58
|
+
}
|
|
59
|
+
return [...set].sort();
|
|
60
|
+
}, /* @ts-ignore */
|
|
61
|
+
...(ngDevMode ? [{ debugName: "keys" }] : /* istanbul ignore next */ []));
|
|
62
|
+
/** Per-locale counts shown as filter chips. */
|
|
63
|
+
stats = computed(() => this.locales().map((locale) => {
|
|
64
|
+
const b = this.base()[locale] ?? {};
|
|
65
|
+
const o = this.overrides()[locale] ?? {};
|
|
66
|
+
let missing = 0;
|
|
67
|
+
for (const k of this.keys())
|
|
68
|
+
if (!(k in b) && !(k in o))
|
|
69
|
+
missing++;
|
|
70
|
+
return { locale, missing, overridden: Object.keys(o).length };
|
|
71
|
+
}), /* @ts-ignore */
|
|
72
|
+
...(ngDevMode ? [{ debugName: "stats" }] : /* istanbul ignore next */ []));
|
|
73
|
+
overriddenTotal = computed(() => this.stats().reduce((n, s) => n + s.overridden, 0), /* @ts-ignore */
|
|
74
|
+
...(ngDevMode ? [{ debugName: "overriddenTotal" }] : /* istanbul ignore next */ []));
|
|
75
|
+
rows = computed(() => {
|
|
76
|
+
const q = this.query().trim().toLowerCase();
|
|
77
|
+
const filter = this.filter();
|
|
78
|
+
const locales = this.locales();
|
|
79
|
+
const out = [];
|
|
80
|
+
for (const key of this.keys()) {
|
|
81
|
+
if (filter === 'overridden' && !locales.some((l) => this.hasOverride(l, key)))
|
|
82
|
+
continue;
|
|
83
|
+
if (filter.startsWith('missing:')) {
|
|
84
|
+
const l = filter.slice('missing:'.length);
|
|
85
|
+
if (this.effective(l, key) !== null)
|
|
86
|
+
continue;
|
|
87
|
+
}
|
|
88
|
+
const row = { key };
|
|
89
|
+
for (const l of locales)
|
|
90
|
+
row[l] = this.effective(l, key) ?? '';
|
|
91
|
+
if (q && !key.toLowerCase().includes(q) && !locales.some((l) => row[l].toLowerCase().includes(q)))
|
|
92
|
+
continue;
|
|
93
|
+
out.push(row);
|
|
94
|
+
}
|
|
95
|
+
return out;
|
|
96
|
+
}, /* @ts-ignore */
|
|
97
|
+
...(ngDevMode ? [{ debugName: "rows" }] : /* istanbul ignore next */ []));
|
|
98
|
+
columns = computed(() => [
|
|
99
|
+
{ key: 'key', header: this.i18n.translationEditorKey, width: '32%', pinned: 'left' },
|
|
100
|
+
...this.locales().map((locale) => ({ key: locale, header: locale.toUpperCase() })),
|
|
101
|
+
], /* @ts-ignore */
|
|
102
|
+
...(ngDevMode ? [{ debugName: "columns" }] : /* istanbul ignore next */ []));
|
|
103
|
+
effective(locale, key) {
|
|
104
|
+
const o = this.overrides()[locale]?.[key];
|
|
105
|
+
if (o !== undefined)
|
|
106
|
+
return o;
|
|
107
|
+
const b = this.base()[locale]?.[key];
|
|
108
|
+
return b === undefined ? null : b;
|
|
109
|
+
}
|
|
110
|
+
hasOverride(locale, key) {
|
|
111
|
+
return this.overrides()[locale]?.[key] !== undefined;
|
|
112
|
+
}
|
|
113
|
+
isMissing(locale, key) {
|
|
114
|
+
return this.effective(locale, key) === null;
|
|
115
|
+
}
|
|
116
|
+
onSaved(locale, key, value) {
|
|
117
|
+
if (this.readonly())
|
|
118
|
+
return;
|
|
119
|
+
const previous = this.effective(locale, key);
|
|
120
|
+
const next = value.trim();
|
|
121
|
+
// Typing the file text back is a restore, not an override.
|
|
122
|
+
if (next === (this.base()[locale]?.[key] ?? '')) {
|
|
123
|
+
if (this.hasOverride(locale, key))
|
|
124
|
+
this.changed.emit({ locale, key, value: null, previous });
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
if (next === previous)
|
|
128
|
+
return;
|
|
129
|
+
this.changed.emit({ locale, key, value: next, previous });
|
|
130
|
+
}
|
|
131
|
+
reset(locale, key) {
|
|
132
|
+
if (this.readonly() || !this.hasOverride(locale, key))
|
|
133
|
+
return;
|
|
134
|
+
this.changed.emit({ locale, key, value: null, previous: this.effective(locale, key) });
|
|
135
|
+
}
|
|
136
|
+
setFilter(next) {
|
|
137
|
+
const filter = next;
|
|
138
|
+
this.filter.set(this.filter() === filter && filter !== 'all' ? 'all' : filter);
|
|
139
|
+
}
|
|
140
|
+
exportCsv() {
|
|
141
|
+
const locales = this.locales();
|
|
142
|
+
mkExportCsv(this.rows(), [{ key: 'key', header: 'key' }, ...locales.map((l) => ({ key: l, header: l }))], { filename: this.exportFilename() });
|
|
143
|
+
}
|
|
144
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: MkTranslationEditor, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
145
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.7", type: MkTranslationEditor, isStandalone: true, selector: "mk-translation-editor", inputs: { locales: { classPropertyName: "locales", publicName: "locales", isSignal: true, isRequired: true, transformFunction: null }, base: { classPropertyName: "base", publicName: "base", isSignal: true, isRequired: true, transformFunction: null }, overrides: { classPropertyName: "overrides", publicName: "overrides", isSignal: true, isRequired: false, transformFunction: null }, readonly: { classPropertyName: "readonly", publicName: "readonly", isSignal: true, isRequired: false, transformFunction: null }, exportFilename: { classPropertyName: "exportFilename", publicName: "exportFilename", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { changed: "changed" }, host: { classAttribute: "mk-translation-editor" }, ngImport: i0, template: "<div class=\"mk-translation-editor__toolbar\">\n <input\n mkInput\n type=\"search\"\n class=\"mk-translation-editor__search\"\n [value]=\"query()\"\n (input)=\"query.set($any($event.target).value)\"\n [placeholder]=\"i18n.translationEditorSearch\"\n [attr.aria-label]=\"i18n.translationEditorSearch\"\n />\n <div class=\"mk-translation-editor__filters\" role=\"group\">\n <button\n mkButton\n size=\"sm\"\n [variant]=\"filter() === 'all' ? 'solid' : 'outline'\"\n (click)=\"setFilter('all')\"\n >\n {{ i18n.translationEditorAll }} \u00B7 {{ keys().length }}\n </button>\n <button\n mkButton\n size=\"sm\"\n [variant]=\"filter() === 'overridden' ? 'solid' : 'outline'\"\n [attr.aria-pressed]=\"filter() === 'overridden'\"\n (click)=\"setFilter('overridden')\"\n >\n {{ i18n.translationEditorOverridden }} \u00B7 {{ overriddenTotal() }}\n </button>\n @for (s of stats(); track s.locale) {\n @if (s.missing > 0) {\n <button\n mkButton\n size=\"sm\"\n tone=\"warning\"\n [variant]=\"filter() === 'missing:' + s.locale ? 'solid' : 'outline'\"\n [attr.aria-pressed]=\"filter() === 'missing:' + s.locale\"\n (click)=\"setFilter('missing:' + s.locale)\"\n >\n {{ i18n.translationEditorMissing }} {{ s.locale.toUpperCase() }} \u00B7 {{ s.missing }}\n </button>\n }\n }\n </div>\n <span class=\"mk-translation-editor__count\">{{ rows().length }} {{ i18n.translationEditorKeys }}</span>\n <button mkButton size=\"sm\" variant=\"ghost\" (click)=\"exportCsv()\">\n <mk-icon name=\"download\" [size]=\"16\" />\n {{ i18n.translationEditorExport }}\n </button>\n</div>\n\n<mk-table\n [columns]=\"columns()\"\n [data]=\"rows()\"\n density=\"compact\"\n stickyHeader\n virtual\n [rowHeight]=\"44\"\n class=\"mk-translation-editor__table\"\n>\n <ng-template mkTableCell=\"key\" let-row=\"row\">\n <code class=\"mk-translation-editor__key\">{{ row.key }}</code>\n </ng-template>\n @for (locale of locales(); track locale) {\n <ng-template [mkTableCell]=\"locale\" let-row=\"row\">\n <div\n class=\"mk-translation-editor__cell\"\n [class.mk-translation-editor__cell--overridden]=\"hasOverride(locale, row.key)\"\n [class.mk-translation-editor__cell--missing]=\"isMissing(locale, row.key)\"\n >\n <mk-inline-edit\n size=\"sm\"\n [value]=\"row[locale]\"\n [disabled]=\"readonly()\"\n [placeholder]=\"isMissing(locale, row.key) ? i18n.translationEditorMissing : i18n.empty\"\n [ariaLabel]=\"row.key + ' \u00B7 ' + locale\"\n (saved)=\"onSaved(locale, row.key, $event)\"\n />\n @if (hasOverride(locale, row.key) && !readonly()) {\n <button\n mkButton\n variant=\"ghost\"\n size=\"sm\"\n iconOnly\n class=\"mk-translation-editor__reset\"\n [attr.aria-label]=\"i18n.translationEditorReset\"\n [attr.title]=\"i18n.translationEditorReset\"\n (click)=\"reset(locale, row.key)\"\n >\n <mk-icon name=\"undo\" [size]=\"16\" />\n </button>\n }\n </div>\n </ng-template>\n }\n</mk-table>\n", styles: [":host{display:flex;flex-direction:column;gap:var(--mk-space-3);min-height:0}.mk-translation-editor__toolbar{display:flex;flex-wrap:wrap;align-items:center;gap:var(--mk-space-2)}.mk-translation-editor__search{flex:1 1 16rem;min-width:12rem}.mk-translation-editor__filters{display:flex;flex-wrap:wrap;gap:var(--mk-space-1)}.mk-translation-editor__count{margin-inline-start:auto;color:var(--mk-text-muted);font-size:var(--mk-font-size-sm)}.mk-translation-editor__table{flex:1 1 auto;min-height:0;max-height:70vh}.mk-translation-editor__key{font-family:var(--mk-font-mono);font-size:var(--mk-font-size-sm);color:var(--mk-text-muted);word-break:break-all}.mk-translation-editor__cell{display:flex;align-items:center;gap:var(--mk-space-1);min-width:0}.mk-translation-editor__cell mk-inline-edit{flex:1 1 auto;min-width:0}.mk-translation-editor__cell--overridden{border-inline-start:2px solid var(--mk-primary);padding-inline-start:var(--mk-space-2)}.mk-translation-editor__cell--missing{border-inline-start:2px solid var(--mk-warning);padding-inline-start:var(--mk-space-2)}.mk-translation-editor__reset{flex:none;opacity:.6}.mk-translation-editor__reset:hover,.mk-translation-editor__reset:focus-visible{opacity:1}\n"], dependencies: [{ kind: "component", type: MkTable, selector: "mk-table", inputs: ["columns", "data", "stickyHeader", "zebra", "hover", "density", "stackAt", "clickableRows", "emptyMessage", "selectable", "selected", "trackKey", "rowClass", "expandable", "singleExpand", "resizableColumns", "reorderableColumns", "groupBy", "groupLabel", "virtual", "rowHeight", "overscan", "height", "maxHeight", "filterable", "filters", "clientFilter", "childrenKey"], outputs: ["selectedChange", "filtersChange", "sortChange", "rowClick", "selectionChange", "expandedChange", "columnResize", "columnReorder", "cellEdit", "groupToggle", "treeToggle"] }, { kind: "directive", type: MkTableCell, selector: "[mkTableCell]", inputs: ["mkTableCell"] }, { kind: "component", type: MkInlineEdit, selector: "mk-inline-edit", inputs: ["value", "placeholder", "multiline", "saveOnBlur", "ariaLabel", "disabled", "invalid", "size"], outputs: ["valueChange", "saved", "cancelled"] }, { kind: "component", type: MkInput, selector: "input[mkInput], textarea[mkInput]", inputs: ["size", "invalid"] }, { kind: "component", type: MkButton, selector: "button[mkButton], a[mkButton]", inputs: ["variant", "tone", "size", "loading", "fullWidth", "iconOnly", "disabled"] }, { kind: "component", type: MkIcon, selector: "mk-icon", inputs: ["name", "size", "label"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
146
|
+
}
|
|
147
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: MkTranslationEditor, decorators: [{
|
|
148
|
+
type: Component,
|
|
149
|
+
args: [{ selector: 'mk-translation-editor', imports: [MkTable, MkTableCell, MkInlineEdit, MkInput, MkButton, MkIcon], changeDetection: ChangeDetectionStrategy.OnPush, host: { class: 'mk-translation-editor' }, template: "<div class=\"mk-translation-editor__toolbar\">\n <input\n mkInput\n type=\"search\"\n class=\"mk-translation-editor__search\"\n [value]=\"query()\"\n (input)=\"query.set($any($event.target).value)\"\n [placeholder]=\"i18n.translationEditorSearch\"\n [attr.aria-label]=\"i18n.translationEditorSearch\"\n />\n <div class=\"mk-translation-editor__filters\" role=\"group\">\n <button\n mkButton\n size=\"sm\"\n [variant]=\"filter() === 'all' ? 'solid' : 'outline'\"\n (click)=\"setFilter('all')\"\n >\n {{ i18n.translationEditorAll }} \u00B7 {{ keys().length }}\n </button>\n <button\n mkButton\n size=\"sm\"\n [variant]=\"filter() === 'overridden' ? 'solid' : 'outline'\"\n [attr.aria-pressed]=\"filter() === 'overridden'\"\n (click)=\"setFilter('overridden')\"\n >\n {{ i18n.translationEditorOverridden }} \u00B7 {{ overriddenTotal() }}\n </button>\n @for (s of stats(); track s.locale) {\n @if (s.missing > 0) {\n <button\n mkButton\n size=\"sm\"\n tone=\"warning\"\n [variant]=\"filter() === 'missing:' + s.locale ? 'solid' : 'outline'\"\n [attr.aria-pressed]=\"filter() === 'missing:' + s.locale\"\n (click)=\"setFilter('missing:' + s.locale)\"\n >\n {{ i18n.translationEditorMissing }} {{ s.locale.toUpperCase() }} \u00B7 {{ s.missing }}\n </button>\n }\n }\n </div>\n <span class=\"mk-translation-editor__count\">{{ rows().length }} {{ i18n.translationEditorKeys }}</span>\n <button mkButton size=\"sm\" variant=\"ghost\" (click)=\"exportCsv()\">\n <mk-icon name=\"download\" [size]=\"16\" />\n {{ i18n.translationEditorExport }}\n </button>\n</div>\n\n<mk-table\n [columns]=\"columns()\"\n [data]=\"rows()\"\n density=\"compact\"\n stickyHeader\n virtual\n [rowHeight]=\"44\"\n class=\"mk-translation-editor__table\"\n>\n <ng-template mkTableCell=\"key\" let-row=\"row\">\n <code class=\"mk-translation-editor__key\">{{ row.key }}</code>\n </ng-template>\n @for (locale of locales(); track locale) {\n <ng-template [mkTableCell]=\"locale\" let-row=\"row\">\n <div\n class=\"mk-translation-editor__cell\"\n [class.mk-translation-editor__cell--overridden]=\"hasOverride(locale, row.key)\"\n [class.mk-translation-editor__cell--missing]=\"isMissing(locale, row.key)\"\n >\n <mk-inline-edit\n size=\"sm\"\n [value]=\"row[locale]\"\n [disabled]=\"readonly()\"\n [placeholder]=\"isMissing(locale, row.key) ? i18n.translationEditorMissing : i18n.empty\"\n [ariaLabel]=\"row.key + ' \u00B7 ' + locale\"\n (saved)=\"onSaved(locale, row.key, $event)\"\n />\n @if (hasOverride(locale, row.key) && !readonly()) {\n <button\n mkButton\n variant=\"ghost\"\n size=\"sm\"\n iconOnly\n class=\"mk-translation-editor__reset\"\n [attr.aria-label]=\"i18n.translationEditorReset\"\n [attr.title]=\"i18n.translationEditorReset\"\n (click)=\"reset(locale, row.key)\"\n >\n <mk-icon name=\"undo\" [size]=\"16\" />\n </button>\n }\n </div>\n </ng-template>\n }\n</mk-table>\n", styles: [":host{display:flex;flex-direction:column;gap:var(--mk-space-3);min-height:0}.mk-translation-editor__toolbar{display:flex;flex-wrap:wrap;align-items:center;gap:var(--mk-space-2)}.mk-translation-editor__search{flex:1 1 16rem;min-width:12rem}.mk-translation-editor__filters{display:flex;flex-wrap:wrap;gap:var(--mk-space-1)}.mk-translation-editor__count{margin-inline-start:auto;color:var(--mk-text-muted);font-size:var(--mk-font-size-sm)}.mk-translation-editor__table{flex:1 1 auto;min-height:0;max-height:70vh}.mk-translation-editor__key{font-family:var(--mk-font-mono);font-size:var(--mk-font-size-sm);color:var(--mk-text-muted);word-break:break-all}.mk-translation-editor__cell{display:flex;align-items:center;gap:var(--mk-space-1);min-width:0}.mk-translation-editor__cell mk-inline-edit{flex:1 1 auto;min-width:0}.mk-translation-editor__cell--overridden{border-inline-start:2px solid var(--mk-primary);padding-inline-start:var(--mk-space-2)}.mk-translation-editor__cell--missing{border-inline-start:2px solid var(--mk-warning);padding-inline-start:var(--mk-space-2)}.mk-translation-editor__reset{flex:none;opacity:.6}.mk-translation-editor__reset:hover,.mk-translation-editor__reset:focus-visible{opacity:1}\n"] }]
|
|
150
|
+
}], propDecorators: { locales: [{ type: i0.Input, args: [{ isSignal: true, alias: "locales", required: true }] }], base: [{ type: i0.Input, args: [{ isSignal: true, alias: "base", required: true }] }], overrides: [{ type: i0.Input, args: [{ isSignal: true, alias: "overrides", required: false }] }], readonly: [{ type: i0.Input, args: [{ isSignal: true, alias: "readonly", required: false }] }], exportFilename: [{ type: i0.Input, args: [{ isSignal: true, alias: "exportFilename", required: false }] }], changed: [{ type: i0.Output, args: ["changed"] }] } });
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Generated bundle index. Do not edit.
|
|
154
|
+
*/
|
|
155
|
+
|
|
156
|
+
export { MkTranslationEditor };
|
|
157
|
+
//# sourceMappingURL=mk-kit-ui-translate-editor.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mk-kit-ui-translate-editor.mjs","sources":["../../../projects/mk-kit/translate/editor/translation-editor.ts","../../../projects/mk-kit/translate/editor/translation-editor.html","../../../projects/mk-kit/translate/editor/mk-kit-ui-translate-editor.ts"],"sourcesContent":["import {\n ChangeDetectionStrategy,\n Component,\n computed,\n inject,\n input,\n output,\n signal,\n} from '@angular/core';\nimport { MK_I18N } from '@mk-kit/ui/core';\nimport { MkButton } from '@mk-kit/ui/button';\nimport { MkIcon } from '@mk-kit/ui/icon';\nimport { MkInlineEdit } from '@mk-kit/ui/data';\nimport { MkInput } from '@mk-kit/ui/forms';\nimport { MkTable, MkTableCell, type MkTableColumn, mkExportCsv } from '@mk-kit/ui/table';\nimport type { MkFlatTranslations } from '@mk-kit/ui/translate';\n\n/** One edit made in the editor. `value: null` clears the override. */\nexport interface MkTranslationChange {\n locale: string;\n key: string;\n value: string | null;\n /** The text shown before the edit (override, or the base string). */\n previous: string | null;\n}\n\n/** A row of the editor: the key plus the effective text per locale. */\nexport interface MkTranslationRow {\n key: string;\n [locale: string]: string;\n}\n\ntype Filter = 'all' | 'overridden' | `missing:${string}`;\n\n/**\n * Translation editor — keys as rows, locales as columns, click a cell to\n * edit. The base strings are your bundled files; edits are **overrides**\n * kept apart from them, so a rebuild never loses them and a cell can be\n * restored to the file text. Search by key or text, filter to the keys a\n * locale is missing or the ones already edited, export the grid as CSV.\n * Persistence is yours: every edit is emitted as `changed`, feed the result\n * back through `overrides`.\n *\n * ```html\n * <mk-translation-editor\n * [locales]=\"['pl', 'en', 'ru']\"\n * [base]=\"base()\"\n * [overrides]=\"overrides()\"\n * (changed)=\"save($event)\" />\n * ```\n */\n@Component({\n selector: 'mk-translation-editor',\n imports: [MkTable, MkTableCell, MkInlineEdit, MkInput, MkButton, MkIcon],\n templateUrl: './translation-editor.html',\n styleUrl: './translation-editor.scss',\n changeDetection: ChangeDetectionStrategy.OnPush,\n host: { class: 'mk-translation-editor' },\n})\nexport class MkTranslationEditor {\n protected readonly i18n = inject(MK_I18N);\n\n /** Locale codes, in column order. The first one is the reference language. */\n readonly locales = input.required<string[]>();\n /** Bundled strings per locale, flat (`'a.b.c': 'text'`) or nested. */\n readonly base = input.required<Record<string, MkFlatTranslations>>();\n /** Edits per locale, flat. Missing locales are fine. */\n readonly overrides = input<Record<string, MkFlatTranslations>>({});\n /** Show but do not edit. */\n readonly readonly = input(false);\n /** File name of the CSV export. */\n readonly exportFilename = input('translations.csv');\n\n /** An edit or a restore; persist it and update `overrides`. */\n readonly changed = output<MkTranslationChange>();\n\n protected readonly query = signal('');\n protected readonly filter = signal<Filter>('all');\n\n /** Every key any locale knows, sorted. */\n protected readonly keys = computed(() => {\n const set = new Set<string>();\n for (const locale of this.locales()) {\n for (const k of Object.keys(this.base()[locale] ?? {})) set.add(k);\n for (const k of Object.keys(this.overrides()[locale] ?? {})) set.add(k);\n }\n return [...set].sort();\n });\n\n /** Per-locale counts shown as filter chips. */\n protected readonly stats = computed(() =>\n this.locales().map((locale) => {\n const b = this.base()[locale] ?? {};\n const o = this.overrides()[locale] ?? {};\n let missing = 0;\n for (const k of this.keys()) if (!(k in b) && !(k in o)) missing++;\n return { locale, missing, overridden: Object.keys(o).length };\n }),\n );\n\n protected readonly overriddenTotal = computed(() =>\n this.stats().reduce((n, s) => n + s.overridden, 0),\n );\n\n protected readonly rows = computed<MkTranslationRow[]>(() => {\n const q = this.query().trim().toLowerCase();\n const filter = this.filter();\n const locales = this.locales();\n const out: MkTranslationRow[] = [];\n for (const key of this.keys()) {\n if (filter === 'overridden' && !locales.some((l) => this.hasOverride(l, key))) continue;\n if (filter.startsWith('missing:')) {\n const l = filter.slice('missing:'.length);\n if (this.effective(l, key) !== null) continue;\n }\n const row: MkTranslationRow = { key };\n for (const l of locales) row[l] = this.effective(l, key) ?? '';\n if (q && !key.toLowerCase().includes(q) && !locales.some((l) => row[l].toLowerCase().includes(q))) continue;\n out.push(row);\n }\n return out;\n });\n\n protected readonly columns = computed<MkTableColumn<MkTranslationRow>[]>(() => [\n { key: 'key', header: this.i18n.translationEditorKey, width: '32%', pinned: 'left' },\n ...this.locales().map((locale) => ({ key: locale, header: locale.toUpperCase() })),\n ]);\n\n protected effective(locale: string, key: string): string | null {\n const o = this.overrides()[locale]?.[key];\n if (o !== undefined) return o;\n const b = this.base()[locale]?.[key];\n return b === undefined ? null : b;\n }\n\n protected hasOverride(locale: string, key: string): boolean {\n return this.overrides()[locale]?.[key] !== undefined;\n }\n\n protected isMissing(locale: string, key: string): boolean {\n return this.effective(locale, key) === null;\n }\n\n protected onSaved(locale: string, key: string, value: string): void {\n if (this.readonly()) return;\n const previous = this.effective(locale, key);\n const next = value.trim();\n // Typing the file text back is a restore, not an override.\n if (next === (this.base()[locale]?.[key] ?? '')) {\n if (this.hasOverride(locale, key)) this.changed.emit({ locale, key, value: null, previous });\n return;\n }\n if (next === previous) return;\n this.changed.emit({ locale, key, value: next, previous });\n }\n\n protected reset(locale: string, key: string): void {\n if (this.readonly() || !this.hasOverride(locale, key)) return;\n this.changed.emit({ locale, key, value: null, previous: this.effective(locale, key) });\n }\n\n protected setFilter(next: string): void {\n const filter = next as Filter;\n this.filter.set(this.filter() === filter && filter !== 'all' ? 'all' : filter);\n }\n\n protected exportCsv(): void {\n const locales = this.locales();\n mkExportCsv(\n this.rows(),\n [{ key: 'key', header: 'key' }, ...locales.map((l) => ({ key: l, header: l }))],\n { filename: this.exportFilename() },\n );\n }\n}\n","<div class=\"mk-translation-editor__toolbar\">\n <input\n mkInput\n type=\"search\"\n class=\"mk-translation-editor__search\"\n [value]=\"query()\"\n (input)=\"query.set($any($event.target).value)\"\n [placeholder]=\"i18n.translationEditorSearch\"\n [attr.aria-label]=\"i18n.translationEditorSearch\"\n />\n <div class=\"mk-translation-editor__filters\" role=\"group\">\n <button\n mkButton\n size=\"sm\"\n [variant]=\"filter() === 'all' ? 'solid' : 'outline'\"\n (click)=\"setFilter('all')\"\n >\n {{ i18n.translationEditorAll }} · {{ keys().length }}\n </button>\n <button\n mkButton\n size=\"sm\"\n [variant]=\"filter() === 'overridden' ? 'solid' : 'outline'\"\n [attr.aria-pressed]=\"filter() === 'overridden'\"\n (click)=\"setFilter('overridden')\"\n >\n {{ i18n.translationEditorOverridden }} · {{ overriddenTotal() }}\n </button>\n @for (s of stats(); track s.locale) {\n @if (s.missing > 0) {\n <button\n mkButton\n size=\"sm\"\n tone=\"warning\"\n [variant]=\"filter() === 'missing:' + s.locale ? 'solid' : 'outline'\"\n [attr.aria-pressed]=\"filter() === 'missing:' + s.locale\"\n (click)=\"setFilter('missing:' + s.locale)\"\n >\n {{ i18n.translationEditorMissing }} {{ s.locale.toUpperCase() }} · {{ s.missing }}\n </button>\n }\n }\n </div>\n <span class=\"mk-translation-editor__count\">{{ rows().length }} {{ i18n.translationEditorKeys }}</span>\n <button mkButton size=\"sm\" variant=\"ghost\" (click)=\"exportCsv()\">\n <mk-icon name=\"download\" [size]=\"16\" />\n {{ i18n.translationEditorExport }}\n </button>\n</div>\n\n<mk-table\n [columns]=\"columns()\"\n [data]=\"rows()\"\n density=\"compact\"\n stickyHeader\n virtual\n [rowHeight]=\"44\"\n class=\"mk-translation-editor__table\"\n>\n <ng-template mkTableCell=\"key\" let-row=\"row\">\n <code class=\"mk-translation-editor__key\">{{ row.key }}</code>\n </ng-template>\n @for (locale of locales(); track locale) {\n <ng-template [mkTableCell]=\"locale\" let-row=\"row\">\n <div\n class=\"mk-translation-editor__cell\"\n [class.mk-translation-editor__cell--overridden]=\"hasOverride(locale, row.key)\"\n [class.mk-translation-editor__cell--missing]=\"isMissing(locale, row.key)\"\n >\n <mk-inline-edit\n size=\"sm\"\n [value]=\"row[locale]\"\n [disabled]=\"readonly()\"\n [placeholder]=\"isMissing(locale, row.key) ? i18n.translationEditorMissing : i18n.empty\"\n [ariaLabel]=\"row.key + ' · ' + locale\"\n (saved)=\"onSaved(locale, row.key, $event)\"\n />\n @if (hasOverride(locale, row.key) && !readonly()) {\n <button\n mkButton\n variant=\"ghost\"\n size=\"sm\"\n iconOnly\n class=\"mk-translation-editor__reset\"\n [attr.aria-label]=\"i18n.translationEditorReset\"\n [attr.title]=\"i18n.translationEditorReset\"\n (click)=\"reset(locale, row.key)\"\n >\n <mk-icon name=\"undo\" [size]=\"16\" />\n </button>\n }\n </div>\n </ng-template>\n }\n</mk-table>\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './index';\n"],"names":[],"mappings":"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"}
|
package/fesm2022/mk-kit-ui.mjs
CHANGED
|
@@ -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';
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"mk-kit-ui.mjs","sources":["../../../projects/mk-kit/src/public-api.ts","../../../projects/mk-kit/src/mk-kit-ui.ts"],"sourcesContent":["/*\n * Public API Surface of @mk-kit/ui.\n *\n * The library ships Material-style secondary entry points — import from the\n * group entries (`@mk-kit/ui/forms`, `@mk-kit/ui/table`, …) so a code-split\n * app only carries the groups each chunk uses. This root entry re-exports\n * everything for convenience; importing it eagerly pulls all groups into the\n * importing chunk.\n */\nexport * from '@mk-kit/ui/core';\nexport * from '@mk-kit/ui/core/signal-forms';\nexport * from '@mk-kit/ui/icon';\nexport * from '@mk-kit/ui/icon/extended';\nexport * from '@mk-kit/ui/chip';\nexport * from '@mk-kit/ui/checkbox';\nexport * from '@mk-kit/ui/button';\nexport * from '@mk-kit/ui/directives';\nexport * from '@mk-kit/ui/dnd';\nexport * from '@mk-kit/ui/navigation';\nexport * from '@mk-kit/ui/forms';\nexport * from '@mk-kit/ui/datetime';\nexport * from '@mk-kit/ui/table';\nexport * from '@mk-kit/ui/status';\nexport * from '@mk-kit/ui/data';\nexport * from '@mk-kit/ui/kanban';\nexport * from '@mk-kit/ui/translate';\nexport * from '@mk-kit/ui/feedback';\nexport * from '@mk-kit/ui/rich-text';\nexport * from '@mk-kit/ui/block-editor';\nexport * from '@mk-kit/ui/context-menu';\nexport * from '@mk-kit/ui/layout';\nexport * from '@mk-kit/ui/chat';\nexport * from '@mk-kit/ui/query-builder';\nexport * from '@mk-kit/ui/dynamic-form';\nexport * from '@mk-kit/ui/media';\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './public-api';\n"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"mk-kit-ui.mjs","sources":["../../../projects/mk-kit/src/public-api.ts","../../../projects/mk-kit/src/mk-kit-ui.ts"],"sourcesContent":["/*\n * Public API Surface of @mk-kit/ui.\n *\n * The library ships Material-style secondary entry points — import from the\n * group entries (`@mk-kit/ui/forms`, `@mk-kit/ui/table`, …) so a code-split\n * app only carries the groups each chunk uses. This root entry re-exports\n * everything for convenience; importing it eagerly pulls all groups into the\n * importing chunk.\n */\nexport * from '@mk-kit/ui/core';\nexport * from '@mk-kit/ui/core/signal-forms';\nexport * from '@mk-kit/ui/icon';\nexport * from '@mk-kit/ui/icon/extended';\nexport * from '@mk-kit/ui/chip';\nexport * from '@mk-kit/ui/checkbox';\nexport * from '@mk-kit/ui/button';\nexport * from '@mk-kit/ui/directives';\nexport * from '@mk-kit/ui/dnd';\nexport * from '@mk-kit/ui/navigation';\nexport * from '@mk-kit/ui/forms';\nexport * from '@mk-kit/ui/datetime';\nexport * from '@mk-kit/ui/table';\nexport * from '@mk-kit/ui/status';\nexport * from '@mk-kit/ui/data';\nexport * from '@mk-kit/ui/kanban';\nexport * from '@mk-kit/ui/translate';\nexport * from '@mk-kit/ui/attention';\nexport * from '@mk-kit/ui/feedback';\nexport * from '@mk-kit/ui/rich-text';\nexport * from '@mk-kit/ui/block-editor';\nexport * from '@mk-kit/ui/context-menu';\nexport * from '@mk-kit/ui/layout';\nexport * from '@mk-kit/ui/chat';\nexport * from '@mk-kit/ui/query-builder';\nexport * from '@mk-kit/ui/dynamic-form';\nexport * from '@mk-kit/ui/media';\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './public-api';\n"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;;;;;;;;AAQG;;ACRH;;AAEG"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mk-kit/ui",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.57.0",
|
|
4
|
+
"bin": {
|
|
5
|
+
"mk-translate": "./bin/mk-translate.mjs"
|
|
6
|
+
},
|
|
4
7
|
"publishConfig": {
|
|
5
8
|
"access": "public"
|
|
6
9
|
},
|
|
@@ -36,7 +39,8 @@
|
|
|
36
39
|
"@angular/core": "^22.0.0",
|
|
37
40
|
"@angular/forms": "^22.0.0",
|
|
38
41
|
"@angular/platform-browser": "^22.0.0",
|
|
39
|
-
"rxjs": "^7.8.0"
|
|
42
|
+
"rxjs": "^7.8.0",
|
|
43
|
+
"html5-qrcode": ">=2.3.0"
|
|
40
44
|
},
|
|
41
45
|
"dependencies": {
|
|
42
46
|
"@mk-kit/core": "^0.1.0",
|
|
@@ -61,6 +65,10 @@
|
|
|
61
65
|
"types": "./types/mk-kit-ui.d.ts",
|
|
62
66
|
"default": "./fesm2022/mk-kit-ui.mjs"
|
|
63
67
|
},
|
|
68
|
+
"./attention": {
|
|
69
|
+
"types": "./types/mk-kit-ui-attention.d.ts",
|
|
70
|
+
"default": "./fesm2022/mk-kit-ui-attention.mjs"
|
|
71
|
+
},
|
|
64
72
|
"./block-editor": {
|
|
65
73
|
"types": "./types/mk-kit-ui-block-editor.d.ts",
|
|
66
74
|
"default": "./fesm2022/mk-kit-ui-block-editor.mjs"
|
|
@@ -165,6 +173,10 @@
|
|
|
165
173
|
"types": "./types/mk-kit-ui-media.d.ts",
|
|
166
174
|
"default": "./fesm2022/mk-kit-ui-media.mjs"
|
|
167
175
|
},
|
|
176
|
+
"./media/scanner": {
|
|
177
|
+
"types": "./types/mk-kit-ui-media-scanner.d.ts",
|
|
178
|
+
"default": "./fesm2022/mk-kit-ui-media-scanner.mjs"
|
|
179
|
+
},
|
|
168
180
|
"./navigation": {
|
|
169
181
|
"types": "./types/mk-kit-ui-navigation.d.ts",
|
|
170
182
|
"default": "./fesm2022/mk-kit-ui-navigation.mjs"
|
|
@@ -193,11 +205,20 @@
|
|
|
193
205
|
"types": "./types/mk-kit-ui-translate.d.ts",
|
|
194
206
|
"default": "./fesm2022/mk-kit-ui-translate.mjs"
|
|
195
207
|
},
|
|
208
|
+
"./translate/editor": {
|
|
209
|
+
"types": "./types/mk-kit-ui-translate-editor.d.ts",
|
|
210
|
+
"default": "./fesm2022/mk-kit-ui-translate-editor.mjs"
|
|
211
|
+
},
|
|
196
212
|
"./translate/server": {
|
|
197
213
|
"types": "./types/mk-kit-ui-translate-server.d.ts",
|
|
198
214
|
"default": "./fesm2022/mk-kit-ui-translate-server.mjs"
|
|
199
215
|
}
|
|
200
216
|
},
|
|
217
|
+
"peerDependenciesMeta": {
|
|
218
|
+
"html5-qrcode": {
|
|
219
|
+
"optional": true
|
|
220
|
+
}
|
|
221
|
+
},
|
|
201
222
|
"module": "fesm2022/mk-kit-ui.mjs",
|
|
202
223
|
"typings": "types/mk-kit-ui.d.ts",
|
|
203
224
|
"type": "module"
|
|
@@ -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 };
|
|
@@ -771,6 +771,25 @@ interface MkI18nStrings {
|
|
|
771
771
|
noResults: string;
|
|
772
772
|
/** Empty data table / list. */
|
|
773
773
|
noData: string;
|
|
774
|
+
/** Translation editor (`@mk-kit/ui/translate/editor`). */
|
|
775
|
+
translationEditorSearch: string;
|
|
776
|
+
translationEditorAll: string;
|
|
777
|
+
translationEditorOverridden: string;
|
|
778
|
+
translationEditorMissing: string;
|
|
779
|
+
translationEditorKey: string;
|
|
780
|
+
translationEditorReset: string;
|
|
781
|
+
translationEditorExport: string;
|
|
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;
|
|
774
793
|
/** Announced when a filterable list updates (autocomplete, multi-select, command palette). */
|
|
775
794
|
resultsCount: (count: number) => string;
|
|
776
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 };
|