@mk-kit/ui 0.38.0 → 0.40.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,325 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.MK_STYLE_PATH = void 0;
4
+ exports.transformTypeScript = transformTypeScript;
5
+ exports.transformTemplate = transformTemplate;
6
+ exports.transformAngularJson = transformAngularJson;
7
+ exports.transformPackageJson = transformPackageJson;
8
+ exports.renderReport = renderReport;
9
+ /**
10
+ * Pure string transforms behind the `migrate-primeng` schematic. No devkit
11
+ * types here so they can be unit-tested on fixtures and reused by other
12
+ * tooling. Every function returns the new text plus what it did.
13
+ */
14
+ const mapping_1 = require("./mapping");
15
+ const MODULE_BY_PATH = new Map(mapping_1.MODULES.map((m) => [m.path, m]));
16
+ function escapeRe(s) {
17
+ return s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
18
+ }
19
+ function docsUrl(route) {
20
+ return route ? `${mapping_1.DOCS_BASE}${route}` : undefined;
21
+ }
22
+ /* ------------------------------------------------------------------------ */
23
+ /* TypeScript */
24
+ /* ------------------------------------------------------------------------ */
25
+ const IMPORT_RE = /import\s*(type\s+)?\{([^}]*)\}\s*from\s*['"]primeng\/([\w-]+)['"];?[ \t]*\r?\n?/g;
26
+ /**
27
+ * Rewrites `primeng/*` imports to `@mk-kit/ui`, renames the imported
28
+ * identifiers everywhere in the file, keeps unmapped symbols on their
29
+ * original import, and dedupes `imports: [...]` arrays.
30
+ */
31
+ function transformTypeScript(source) {
32
+ const findings = [];
33
+ const mkNames = new Set();
34
+ const renames = new Map();
35
+ const keep = [];
36
+ let text = source;
37
+ let firstImportIndex = -1;
38
+ text = text.replace(IMPORT_RE, (whole, isType, names, path, offset) => {
39
+ const rule = MODULE_BY_PATH.get(path);
40
+ if (firstImportIndex === -1)
41
+ firstImportIndex = offset;
42
+ const symbols = names
43
+ .split(',')
44
+ .map((n) => n.trim())
45
+ .filter(Boolean);
46
+ const unmapped = [];
47
+ for (const raw of symbols) {
48
+ const [name, alias] = raw.split(/\s+as\s+/).map((s) => s.trim());
49
+ const target = rule?.symbols[name];
50
+ if (target) {
51
+ mkNames.add(target);
52
+ renames.set(alias ?? name, target);
53
+ add(findings, `import:primeng/${path}`, `${name} → ${target}`, 'rewrite', rule?.docs);
54
+ }
55
+ else {
56
+ unmapped.push(raw);
57
+ add(findings, `import:primeng/${path}`, `${name} (primeng/${path}) has no drop-in equivalent — kept`, rule ? 'manual' : 'unmapped', rule?.docs);
58
+ }
59
+ }
60
+ if (unmapped.length)
61
+ keep.push(`import ${isType ?? ''}{ ${unmapped.join(', ')} } from 'primeng/${path}';\n`);
62
+ if (rule?.note && symbols.some((s) => rule.symbols[s.split(/\s+as\s+/)[0].trim()])) {
63
+ add(findings, `note:primeng/${path}`, rule.note, 'manual', rule.docs);
64
+ }
65
+ return '';
66
+ });
67
+ if (firstImportIndex === -1)
68
+ return { text: source, changed: false, findings };
69
+ // Rename identifiers (whole words) — includes `imports: [...]` entries and injected services.
70
+ for (const [from, to] of renames) {
71
+ if (from === to)
72
+ continue;
73
+ text = text.replace(new RegExp(`\\b${escapeRe(from)}\\b`, 'g'), to);
74
+ }
75
+ // Dedupe entries inside `imports: [ ... ]` arrays (several PrimeNG symbols map to one class).
76
+ text = text.replace(/imports:\s*\[([^\]]*)\]/g, (whole, inner) => {
77
+ const seen = new Set();
78
+ const items = inner
79
+ .split(',')
80
+ .map((s) => s.trim())
81
+ .filter((s) => s && !seen.has(s) && seen.add(s));
82
+ const multiline = inner.includes('\n');
83
+ return multiline ? `imports: [\n ${items.join(',\n ')},\n ]` : `imports: [${items.join(', ')}]`;
84
+ });
85
+ // Insert the mk-kit import where the first primeng import was, merging with an existing one.
86
+ const header = [...(keep.length ? keep : [])];
87
+ if (mkNames.size) {
88
+ const existing = /import\s*\{([^}]*)\}\s*from\s*['"]@mk-kit\/ui['"];?[ \t]*\r?\n?/;
89
+ const m = existing.exec(text);
90
+ if (m) {
91
+ const merged = new Set(m[1].split(',').map((s) => s.trim()).filter(Boolean));
92
+ for (const n of mkNames)
93
+ merged.add(n);
94
+ text = text.replace(existing, `import { ${[...merged].join(', ')} } from '@mk-kit/ui';\n`);
95
+ }
96
+ else {
97
+ header.unshift(`import { ${[...mkNames].join(', ')} } from '@mk-kit/ui';\n`);
98
+ }
99
+ }
100
+ text = text.slice(0, firstImportIndex) + header.join('') + text.slice(firstImportIndex);
101
+ // Service usage hints (MessageService / ConfirmationService bodies are not mechanical).
102
+ if (renames.has('MessageService')) {
103
+ text = text.replace(/(\n[ \t]*)([^\n]*\.add\(\s*\{[^\n]*severity[^\n]*)/g, '$1// mk-kit: MessageService.add({severity, summary, detail}) → toast.<severity>(detail, { title: summary })$1$2');
104
+ }
105
+ if (renames.has('ConfirmationService')) {
106
+ text = text.replace(/(\n[ \t]*)([^\n]*\.confirm\(\s*\{[^\n]*)/g, '$1// mk-kit: ConfirmationService.confirm({...}) → await dialog.confirm({ title, message, tone }) returns boolean$1$2');
107
+ }
108
+ // Inline templates get the template pass too.
109
+ const tpl = transformTemplate(text, true);
110
+ text = tpl.text;
111
+ findings.push(...tpl.findings);
112
+ return { text, changed: text !== source, findings };
113
+ }
114
+ /* ------------------------------------------------------------------------ */
115
+ /* Templates */
116
+ /* ------------------------------------------------------------------------ */
117
+ /** Longest selectors first so `p-inputgroupaddon` is not eaten by `p-inputgroup`. */
118
+ const ELEMENT_RULES = mapping_1.SELECTORS.filter((r) => r.kind === 'element').sort((a, b) => b.from.length - a.from.length);
119
+ const ATTRIBUTE_RULES = mapping_1.SELECTORS.filter((r) => r.kind === 'attribute').sort((a, b) => b.from.length - a.from.length);
120
+ function noteComment(rule, inline) {
121
+ const text = `mk-kit: ${rule.from} → ${rule.to ?? 'manual'} — ${rule.note ?? ''}`.trim();
122
+ // Inside a TS template literal `-->` is fine; keep one comment style everywhere.
123
+ return inline ? `<!-- ${text} -->` : `<!-- ${text} -->`;
124
+ }
125
+ function renameAttrs(openTag, attrs) {
126
+ if (!attrs)
127
+ return openTag;
128
+ let out = openTag;
129
+ for (const [from, to] of Object.entries(attrs)) {
130
+ const re = new RegExp(`(\\s)${escapeRe(from)}(?=[\\s=>/])`, 'g');
131
+ out = out.replace(re, (_m, ws) => (to === null ? ws.trimEnd() : `${ws}${to}`));
132
+ }
133
+ return out;
134
+ }
135
+ /**
136
+ * Rewrites PrimeNG selectors in a template (an .html file, or a whole .ts file
137
+ * when `inline` — only `<p-…` tags and known attribute tokens are touched).
138
+ */
139
+ function transformTemplate(source, inline = false) {
140
+ const findings = [];
141
+ let text = source;
142
+ for (const rule of ELEMENT_RULES) {
143
+ const open = new RegExp(`<${escapeRe(rule.from)}(?=[\\s>/])`, 'g');
144
+ const close = new RegExp(`</${escapeRe(rule.from)}\\s*>`, 'g');
145
+ const matches = text.match(open);
146
+ if (!matches)
147
+ continue;
148
+ const n = matches.length;
149
+ if (rule.to) {
150
+ // Rename open/close tags, then attributes on each opening tag.
151
+ text = text.replace(open, `<${rule.to}`).replace(close, `</${rule.to}>`);
152
+ if (rule.attrs) {
153
+ const tagRe = new RegExp(`<${escapeRe(rule.to)}(\\s[^>]*)?>`, 'g');
154
+ text = text.replace(tagRe, (tag) => renameAttrs(tag, rule.attrs));
155
+ }
156
+ add(findings, `element:${rule.from}`, `<${rule.from}> → <${rule.to}>`, 'rewrite', rule.docs, n);
157
+ if (rule.manual && rule.note) {
158
+ text = insertNote(text, `<${rule.to}`, noteComment(rule, inline));
159
+ add(findings, `manual:${rule.from}`, rule.note, 'manual', rule.docs, n);
160
+ }
161
+ }
162
+ else {
163
+ text = insertNote(text, `<${rule.from}`, noteComment(rule, inline));
164
+ add(findings, `element:${rule.from}`, rule.note ?? 'no drop-in equivalent', 'unmapped', rule.docs, n);
165
+ }
166
+ }
167
+ for (const rule of ATTRIBUTE_RULES) {
168
+ // Attribute token forms: `pButton`, `[pTooltip]="…"`, `pTooltip="…"`.
169
+ const token = new RegExp(`(?<=[\\s\\[])${escapeRe(rule.from)}(?=[\\s=\\]>/])`, 'g');
170
+ const matches = text.match(token);
171
+ if (!matches)
172
+ continue;
173
+ const n = matches.length;
174
+ if (rule.to) {
175
+ text = text.replace(token, rule.to);
176
+ if (rule.attrs) {
177
+ const tagRe = new RegExp(`<[a-zA-Z][^>]*\\b${escapeRe(rule.to)}\\b[^>]*>`, 'g');
178
+ text = text.replace(tagRe, (tag) => renameAttrs(tag, rule.attrs));
179
+ }
180
+ add(findings, `attr:${rule.from}`, `${rule.from} → ${rule.to}`, 'rewrite', rule.docs, n);
181
+ if (rule.manual && rule.note) {
182
+ text = insertNoteBeforeTagWith(text, rule.to, noteComment(rule, inline));
183
+ add(findings, `manual:${rule.from}`, rule.note, 'manual', rule.docs, n);
184
+ }
185
+ }
186
+ else {
187
+ text = insertNoteBeforeTagWith(text, rule.from, noteComment(rule, inline));
188
+ add(findings, `attr:${rule.from}`, rule.note ?? 'no drop-in equivalent', 'unmapped', rule.docs, n);
189
+ }
190
+ }
191
+ return { text, changed: text !== source, findings };
192
+ }
193
+ /** Insert `note` once, right before the first occurrence of `needle` (keeps indentation). */
194
+ function insertNote(text, needle, note) {
195
+ if (text.includes(note))
196
+ return text;
197
+ const i = text.indexOf(needle);
198
+ if (i === -1)
199
+ return text;
200
+ const lineStart = text.lastIndexOf('\n', i) + 1;
201
+ const indent = text.slice(lineStart, i).match(/^[ \t]*$/) ? text.slice(lineStart, i) : '';
202
+ return indent ? text.slice(0, lineStart) + indent + note + '\n' + text.slice(lineStart) : text.slice(0, i) + note + ' ' + text.slice(i);
203
+ }
204
+ /** Insert `note` once before the first tag that carries `attr`. */
205
+ function insertNoteBeforeTagWith(text, attr, note) {
206
+ if (text.includes(note))
207
+ return text;
208
+ const re = new RegExp(`<[a-zA-Z][^>]*(?<=[\\s\\[])${escapeRe(attr)}(?=[\\s=\\]>/])`);
209
+ const m = re.exec(text);
210
+ if (!m)
211
+ return text;
212
+ return insertNote(text, m[0], note);
213
+ }
214
+ /* ------------------------------------------------------------------------ */
215
+ /* Workspace files */
216
+ /* ------------------------------------------------------------------------ */
217
+ exports.MK_STYLE_PATH = 'node_modules/@mk-kit/ui/styles/mk-kit.css';
218
+ /** Remove PrimeNG / primeicons / primeflex style entries from every project's `styles`; add the mk-kit theme. */
219
+ function transformAngularJson(source) {
220
+ const findings = [];
221
+ let json;
222
+ try {
223
+ json = JSON.parse(source);
224
+ }
225
+ catch {
226
+ return { text: source, changed: false, findings };
227
+ }
228
+ let changed = false;
229
+ for (const project of Object.values(json.projects ?? {})) {
230
+ const options = project?.architect?.build?.options;
231
+ if (!options || !Array.isArray(options.styles))
232
+ continue;
233
+ const before = options.styles.length;
234
+ options.styles = options.styles.filter((s) => {
235
+ const p = typeof s === 'string' ? s : s?.input ?? '';
236
+ return !/prime(ng|icons|flex|uix)/i.test(p);
237
+ });
238
+ if (options.styles.length !== before) {
239
+ changed = true;
240
+ add(findings, 'styles:primeng', `removed ${before - options.styles.length} PrimeNG/primeicons style entries`, 'rewrite', '/theming', before - options.styles.length);
241
+ }
242
+ const has = options.styles.some((s) => (typeof s === 'string' ? s : s?.input ?? '').includes('@mk-kit/ui/styles'));
243
+ if (!has) {
244
+ options.styles.unshift(exports.MK_STYLE_PATH);
245
+ changed = true;
246
+ add(findings, 'styles:mk-kit', `added ${exports.MK_STYLE_PATH}`, 'rewrite', '/theming');
247
+ }
248
+ }
249
+ return { text: changed ? JSON.stringify(json, null, 2) + '\n' : source, changed, findings };
250
+ }
251
+ /** Drop PrimeNG packages from dependencies and make sure @mk-kit/ui is present. */
252
+ function transformPackageJson(source, packages, mkVersion = '^0.38.0') {
253
+ const findings = [];
254
+ let json;
255
+ try {
256
+ json = JSON.parse(source);
257
+ }
258
+ catch {
259
+ return { text: source, changed: false, findings };
260
+ }
261
+ let changed = false;
262
+ for (const field of ['dependencies', 'devDependencies']) {
263
+ const deps = json[field];
264
+ if (!deps)
265
+ continue;
266
+ for (const p of packages) {
267
+ if (p in deps) {
268
+ delete deps[p];
269
+ changed = true;
270
+ add(findings, 'package:remove', `removed ${p} from ${field}`, 'rewrite');
271
+ }
272
+ }
273
+ }
274
+ json.dependencies ??= {};
275
+ if (!json.dependencies['@mk-kit/ui']) {
276
+ json.dependencies['@mk-kit/ui'] = mkVersion;
277
+ changed = true;
278
+ add(findings, 'package:add', `added @mk-kit/ui ${mkVersion}`, 'rewrite');
279
+ }
280
+ return { text: changed ? JSON.stringify(json, null, 2) + '\n' : source, changed, findings };
281
+ }
282
+ function add(findings, rule, message, kind, docs, count = 1) {
283
+ const existing = findings.find((f) => f.rule === rule && f.message === message);
284
+ if (existing)
285
+ existing.count += count;
286
+ else
287
+ findings.push({ rule, message, kind, docs: docsUrl(docs), count });
288
+ }
289
+ /** Markdown report of a whole run. */
290
+ function renderReport(files, options) {
291
+ const all = files.flatMap((f) => f.findings.map((x) => ({ ...x, file: f.path })));
292
+ const changed = files.filter((f) => f.changed);
293
+ const byKind = (kind) => all.filter((f) => f.kind === kind);
294
+ const sum = (list) => list.reduce((n, f) => n + f.count, 0);
295
+ const lines = [];
296
+ lines.push('# PrimeNG → mk-kit migration report', '');
297
+ lines.push(`${options.dryRun ? '**Dry run** — nothing was written. ' : ''}Scanned ${options.scanned} files, ${options.dryRun ? 'would change' : 'changed'} ${changed.length}.`, '');
298
+ lines.push(`- Automatic rewrites: **${sum(byKind('rewrite'))}**`);
299
+ lines.push(`- Needs a manual touch: **${sum(byKind('manual'))}**`);
300
+ lines.push(`- No mk-kit equivalent: **${sum(byKind('unmapped'))}**`, '');
301
+ lines.push('## Rewrites', '');
302
+ const rewrites = group(byKind('rewrite'));
303
+ lines.push(...(rewrites.length ? rewrites.map(([msg, n]) => `- ${msg} ×${n}`) : ['- (none)']), '');
304
+ lines.push('## Manual steps', '', 'Look for `<!-- mk-kit: … -->` and `// mk-kit:` comments in the changed files.', '');
305
+ const manual = group(byKind('manual'), true);
306
+ lines.push(...(manual.length ? manual.map(([msg, n, docs]) => `- ${msg} ×${n}${docs ? ` — [docs](${docs})` : ''}`) : ['- (none)']), '');
307
+ lines.push('## Not available in mk-kit', '');
308
+ const unmapped = group(byKind('unmapped'), true);
309
+ lines.push(...(unmapped.length ? unmapped.map(([msg, n, docs]) => `- ${msg} ×${n}${docs ? ` — closest: [docs](${docs})` : ''}`) : ['- (none)']), '');
310
+ lines.push('## Files', '');
311
+ lines.push(...(changed.length ? changed.map((f) => `- \`${f.path}\` — ${f.findings.map((x) => `${x.rule}×${x.count}`).join(', ')}`) : ['- (none)']), '');
312
+ lines.push('## Next', '');
313
+ lines.push('1. Run `ng build` and fix the remaining template errors — most are the `<!-- mk-kit -->` notes above.', '2. Replace `MessageService.add(...)` calls with `MkToastService` and `ConfirmationService.confirm(...)` with `await MkDialogService.confirm(...)`.', '3. Icons: `pi pi-*` classes → `<mk-icon name="…" />` (see https://mk-kit.dev/components/icon).', `4. Theme: \`--mk-*\` tokens replace the PrimeNG preset — https://mk-kit.dev/theming.`, '');
314
+ return lines.join('\n');
315
+ }
316
+ function group(list, withDocs = false) {
317
+ const map = new Map();
318
+ for (const f of list) {
319
+ const key = f.message;
320
+ const cur = map.get(key) ?? [0, f.docs];
321
+ cur[0] += f.count;
322
+ map.set(key, cur);
323
+ }
324
+ return [...map.entries()].map(([msg, [n, docs]]) => [msg, n, withDocs ? docs : undefined]);
325
+ }
@@ -1758,6 +1758,21 @@ declare class MkCountdown {
1758
1758
 
1759
1759
  /** Layout for {@link MkDescriptionList}. */
1760
1760
  type MkDescriptionLayout = 'grid' | 'stacked';
1761
+ /**
1762
+ * A single term/detail row of {@link MkDescriptionList}: `term` becomes the
1763
+ * `<dt>`, the projected content the `<dd>`. The row itself renders nothing —
1764
+ * the list reads its rows and writes `<dt>`/`<dd>` as direct children of the
1765
+ * `<dl>`, which is what HTML and assistive tech require (a wrapper element
1766
+ * between `<dl>` and `<dt>` fails axe `definition-list` / `dlitem`).
1767
+ */
1768
+ declare class MkDescItem {
1769
+ /** The term (label) shown in the `<dt>`. */
1770
+ readonly term: _angular_core.InputSignal<string>;
1771
+ /** The row's detail content, rendered by the list inside its `<dd>`. @internal */
1772
+ readonly content: _angular_core.Signal<TemplateRef<unknown>>;
1773
+ static ɵfac: _angular_core.ɵɵFactoryDeclaration<MkDescItem, never>;
1774
+ static ɵcmp: _angular_core.ɵɵComponentDeclaration<MkDescItem, "mk-desc-item", never, { "term": { "alias": "term"; "required": false; "isSignal": true; }; }, {}, never, ["*"], true, never>;
1775
+ }
1761
1776
  /**
1762
1777
  * DescriptionList — a semantic `<dl>` of term/detail pairs for entity-detail and
1763
1778
  * metadata panels. Project {@link MkDescItem} rows; values may be rich content
@@ -1770,25 +1785,18 @@ type MkDescriptionLayout = 'grid' | 'stacked';
1770
1785
  * <mk-desc-item term="Owner">Ada Lovelace</mk-desc-item>
1771
1786
  * </mk-description-list>
1772
1787
  * ```
1788
+ *
1789
+ * Only `mk-desc-item` children are rendered; the `<dl>` always contains plain
1790
+ * `<dt>`/`<dd>` pairs.
1773
1791
  */
1774
1792
  declare class MkDescriptionList {
1775
1793
  /** `grid` aligns terms in a column; `stacked` puts each term above its value. */
1776
1794
  readonly layout: _angular_core.InputSignal<MkDescriptionLayout>;
1777
1795
  /** Draw a divider between rows. */
1778
1796
  readonly divided: _angular_core.InputSignalWithTransform<boolean, unknown>;
1797
+ protected readonly items: _angular_core.Signal<readonly MkDescItem[]>;
1779
1798
  static ɵfac: _angular_core.ɵɵFactoryDeclaration<MkDescriptionList, never>;
1780
- static ɵcmp: _angular_core.ɵɵComponentDeclaration<MkDescriptionList, "mk-description-list", never, { "layout": { "alias": "layout"; "required": false; "isSignal": true; }; "divided": { "alias": "divided"; "required": false; "isSignal": true; }; }, {}, never, ["*"], true, never>;
1781
- }
1782
- /**
1783
- * A single term/detail row inside {@link MkDescriptionList}. Renders a `<dt>`
1784
- * (the `term`) and a `<dd>` (the projected value). The host is `display:contents`
1785
- * so the `<dt>`/`<dd>` participate directly in the list's grid.
1786
- */
1787
- declare class MkDescItem {
1788
- /** The term (label) shown in the `<dt>`. */
1789
- readonly term: _angular_core.InputSignal<string>;
1790
- static ɵfac: _angular_core.ɵɵFactoryDeclaration<MkDescItem, never>;
1791
- static ɵcmp: _angular_core.ɵɵComponentDeclaration<MkDescItem, "mk-desc-item", never, { "term": { "alias": "term"; "required": false; "isSignal": true; }; }, {}, never, ["*"], true, never>;
1799
+ static ɵcmp: _angular_core.ɵɵComponentDeclaration<MkDescriptionList, "mk-description-list", never, { "layout": { "alias": "layout"; "required": false; "isSignal": true; }; "divided": { "alias": "divided"; "required": false; "isSignal": true; }; }, {}, ["items"], never, true, never>;
1792
1800
  }
1793
1801
 
1794
1802
  /**
@@ -210,6 +210,8 @@ declare class MkDrag<T = unknown> {
210
210
  private cleanupDom;
211
211
  private emit;
212
212
  private isHandleTarget;
213
+ /** Whether `target` belongs to this item rather than to a nested `[mkDrag]`. */
214
+ private isOwnTarget;
213
215
  private prefersReducedMotion;
214
216
  static ɵfac: _angular_core.ɵɵFactoryDeclaration<MkDrag<any>, never>;
215
217
  static ɵcmp: _angular_core.ɵɵComponentDeclaration<MkDrag<any>, "[mkDrag]", ["mkDrag"], { "mkDragData": { "alias": "mkDragData"; "required": false; "isSignal": true; }; "mkDragDisabled": { "alias": "mkDragDisabled"; "required": false; "isSignal": true; }; "mkDragTouchDelay": { "alias": "mkDragTouchDelay"; "required": false; "isSignal": true; }; }, {}, ["handles"], ["*"], true, never>;
@@ -643,6 +643,8 @@ declare class MkNavGroup {
643
643
  readonly expanded: _angular_core.ModelSignal<boolean>;
644
644
  /** Id of the items region (for `aria-controls`). */
645
645
  readonly regionId: string;
646
+ /** Id of the header, used to name the nested item list. */
647
+ readonly headerId: string;
646
648
  /** Whether the enclosing list is a collapsed icon rail. */
647
649
  protected readonly railCollapsed: _angular_core.Signal<boolean>;
648
650
  /** Items are visible unless collapsible-and-closed. */