@mk-kit/ui 0.46.0 → 0.48.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/README.md +20 -5
- package/fesm2022/mk-kit-ui-core.mjs +51 -4
- package/fesm2022/mk-kit-ui-core.mjs.map +1 -1
- package/fesm2022/mk-kit-ui-embed.mjs +356 -0
- package/fesm2022/mk-kit-ui-embed.mjs.map +1 -0
- package/fesm2022/mk-kit-ui-feedback.mjs +10 -6
- package/fesm2022/mk-kit-ui-feedback.mjs.map +1 -1
- package/fesm2022/mk-kit-ui-locales-de.mjs +494 -0
- package/fesm2022/mk-kit-ui-locales-de.mjs.map +1 -0
- package/fesm2022/mk-kit-ui-locales-es.mjs +494 -0
- package/fesm2022/mk-kit-ui-locales-es.mjs.map +1 -0
- package/fesm2022/mk-kit-ui-locales-fr.mjs +495 -0
- package/fesm2022/mk-kit-ui-locales-fr.mjs.map +1 -0
- package/fesm2022/mk-kit-ui-locales-uk.mjs +503 -0
- package/fesm2022/mk-kit-ui-locales-uk.mjs.map +1 -0
- package/package.json +21 -1
- package/schematics/collection.json +5 -0
- package/schematics/crud/files.js +615 -0
- package/schematics/crud/index.js +134 -0
- package/schematics/crud/model.js +157 -0
- package/schematics/crud/schema.json +48 -0
- package/types/mk-kit-ui-core.d.ts +31 -2
- package/types/mk-kit-ui-embed.d.ts +124 -0
- package/types/mk-kit-ui-feedback.d.ts +4 -0
- package/types/mk-kit-ui-locales-de.d.ts +40 -0
- package/types/mk-kit-ui-locales-es.d.ts +40 -0
- package/types/mk-kit-ui-locales-fr.d.ts +41 -0
- package/types/mk-kit-ui-locales-uk.d.ts +42 -0
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.crud = crud;
|
|
4
|
+
/**
|
|
5
|
+
* `ng g @mk-kit/ui:crud <entity>`
|
|
6
|
+
*
|
|
7
|
+
* Generates a working CRUD slice for one entity: a model file (interface +
|
|
8
|
+
* table columns + dynamic-form schema as the single source of truth), a data
|
|
9
|
+
* service (in-memory, or HttpClient with `--api`), a list page (`mk-table` +
|
|
10
|
+
* `MkTableDataSource` + search + pagination + delete confirm), a form page
|
|
11
|
+
* (`mk-dynamic-form` for create and edit), lazy routes, and a harness-driven
|
|
12
|
+
* spec — then wires the routes into the application's route table.
|
|
13
|
+
*/
|
|
14
|
+
const schematics_1 = require("@angular-devkit/schematics");
|
|
15
|
+
const utility_1 = require("@schematics/angular/utility");
|
|
16
|
+
const files_1 = require("./files");
|
|
17
|
+
const model_1 = require("./model");
|
|
18
|
+
/** Entry point referenced from collection.json (`./crud/index#crud`). */
|
|
19
|
+
function crud(options) {
|
|
20
|
+
return async (tree, context) => {
|
|
21
|
+
const entity = (0, model_1.buildEntity)(options.entity, options.fields ?? 'name!:string', options.plural);
|
|
22
|
+
const appDir = options.path?.replace(/\/+$/, '') ?? (await defaultAppDir(tree, options.project));
|
|
23
|
+
const targetDir = `${appDir}/${entity.pluralFile}`;
|
|
24
|
+
const files = (0, files_1.crudFiles)(entity, { api: options.api || undefined, spec: options.spec !== false });
|
|
25
|
+
for (const [name, content] of files) {
|
|
26
|
+
const filePath = `${targetDir}/${name}`;
|
|
27
|
+
if (tree.exists(filePath)) {
|
|
28
|
+
throw new schematics_1.SchematicsException(`${filePath} already exists — delete the previous slice or generate under a different --path.`);
|
|
29
|
+
}
|
|
30
|
+
tree.create(filePath, content);
|
|
31
|
+
}
|
|
32
|
+
if (options.route !== false) {
|
|
33
|
+
wireRoute(tree, context, appDir, targetDir, entity.pluralFile, (0, files_1.routesConstName)(entity));
|
|
34
|
+
}
|
|
35
|
+
logNextSteps(context, targetDir, entity.pluralFile, !!options.api);
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
/** `<sourceRoot>/app` of the target (or first application) project. */
|
|
39
|
+
async function defaultAppDir(tree, requested) {
|
|
40
|
+
const workspace = await (0, utility_1.readWorkspace)(tree);
|
|
41
|
+
let project = requested ? workspace.projects.get(requested) : undefined;
|
|
42
|
+
if (requested && !project) {
|
|
43
|
+
throw new schematics_1.SchematicsException(`Project "${requested}" was not found in the workspace. ` +
|
|
44
|
+
`Available projects: ${[...workspace.projects.keys()].join(', ') || '(none)'}.`);
|
|
45
|
+
}
|
|
46
|
+
if (!project) {
|
|
47
|
+
for (const candidate of workspace.projects.values()) {
|
|
48
|
+
if (candidate.extensions['projectType'] === 'application') {
|
|
49
|
+
project = candidate;
|
|
50
|
+
break;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
if (!project) {
|
|
55
|
+
throw new schematics_1.SchematicsException('No application project found in the workspace. Pass --project or --path explicitly.');
|
|
56
|
+
}
|
|
57
|
+
const sourceRoot = (project.sourceRoot ?? `${project.root}/src`).replace(/\/+$/, '');
|
|
58
|
+
return `${sourceRoot}/app`;
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Inserts a lazy route into the app's route table. Looks for `app.routes.ts`
|
|
62
|
+
* beside the target directory (then anywhere under it) and prepends a
|
|
63
|
+
* `loadChildren` entry to the first `Routes = [` array. When the file or the
|
|
64
|
+
* array cannot be found, prints the entry to add manually instead of failing
|
|
65
|
+
* the whole generation.
|
|
66
|
+
*/
|
|
67
|
+
function wireRoute(tree, context, appDir, targetDir, routePath, constName) {
|
|
68
|
+
const entry = `{\n path: '${routePath}',\n loadChildren: () =>\n` +
|
|
69
|
+
` import('./${routePath}/${routePath}.routes').then((m) => m.${constName}),\n },`;
|
|
70
|
+
const routesFilePath = findRoutesFile(tree, appDir);
|
|
71
|
+
if (!routesFilePath) {
|
|
72
|
+
context.logger.warn(`Could not find an app.routes.ts under ${appDir} — add the route yourself:`);
|
|
73
|
+
context.logger.warn(` ${entry}`);
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
const source = tree.read(routesFilePath).toString('utf-8');
|
|
77
|
+
const match = /(:\s*Routes\s*=\s*\[)/.exec(source);
|
|
78
|
+
if (!match) {
|
|
79
|
+
context.logger.warn(`${routesFilePath} has no \`Routes = [\` array — add the route yourself:`);
|
|
80
|
+
context.logger.warn(` ${entry}`);
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
if (source.includes(`path: '${routePath}'`)) {
|
|
84
|
+
context.logger.warn(`${routesFilePath} already routes '${routePath}' — left untouched.`);
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
const relative = relativeImportDir(routesFilePath, targetDir);
|
|
88
|
+
const adjusted = entry.replace(`./${routePath}/`, `${relative}/`);
|
|
89
|
+
const at = match.index + match[1].length;
|
|
90
|
+
tree.overwrite(routesFilePath, `${source.slice(0, at)}\n ${adjusted}${source.slice(at)}`);
|
|
91
|
+
context.logger.info(`Routed '${routePath}' in ${routesFilePath}.`);
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* `app.routes.ts` in `appDir` or any of its ancestors (a `--path` deep in the
|
|
95
|
+
* app still wires the app-level table), else the first one anywhere below.
|
|
96
|
+
*/
|
|
97
|
+
function findRoutesFile(tree, appDir) {
|
|
98
|
+
const segments = appDir.split('/').filter(Boolean);
|
|
99
|
+
for (let i = segments.length; i >= 0; i--) {
|
|
100
|
+
const candidate = `${segments.slice(0, i).join('/')}/app.routes.ts`.replace(/^\//, '');
|
|
101
|
+
if (tree.exists(candidate))
|
|
102
|
+
return candidate;
|
|
103
|
+
}
|
|
104
|
+
let found = null;
|
|
105
|
+
tree.getDir(appDir).visit((filePath) => {
|
|
106
|
+
if (!found && filePath.endsWith('/app.routes.ts'))
|
|
107
|
+
found = filePath;
|
|
108
|
+
});
|
|
109
|
+
return found;
|
|
110
|
+
}
|
|
111
|
+
/** Relative import (no extension) from the routes file's directory to `dir`. */
|
|
112
|
+
function relativeImportDir(fromFile, dir) {
|
|
113
|
+
const from = fromFile.split('/').filter(Boolean).slice(0, -1);
|
|
114
|
+
const to = dir.split('/').filter(Boolean);
|
|
115
|
+
let common = 0;
|
|
116
|
+
while (common < from.length && common < to.length && from[common] === to[common])
|
|
117
|
+
common++;
|
|
118
|
+
const up = from.length - common;
|
|
119
|
+
const down = to.slice(common).join('/');
|
|
120
|
+
if (up === 0)
|
|
121
|
+
return `./${down}`;
|
|
122
|
+
return `${'../'.repeat(up)}${down}`.replace(/\/$/, '');
|
|
123
|
+
}
|
|
124
|
+
function logNextSteps(context, targetDir, routePath, api) {
|
|
125
|
+
context.logger.info('');
|
|
126
|
+
context.logger.info(`CRUD slice generated in ${targetDir}/ — visit /${routePath} to use it.`);
|
|
127
|
+
if (api) {
|
|
128
|
+
context.logger.info(' - The service uses HttpClient: make sure provideHttpClient() is in your app config.');
|
|
129
|
+
}
|
|
130
|
+
else {
|
|
131
|
+
context.logger.info(' - The service is in-memory; swap its method bodies for API calls when ready.');
|
|
132
|
+
}
|
|
133
|
+
context.logger.info(` - Columns and the form schema live in the model file — one place to grow the entity.`);
|
|
134
|
+
}
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.CRUD_FIELD_TYPES = void 0;
|
|
4
|
+
exports.classify = classify;
|
|
5
|
+
exports.camelize = camelize;
|
|
6
|
+
exports.dasherize = dasherize;
|
|
7
|
+
exports.pluralize = pluralize;
|
|
8
|
+
exports.humanize = humanize;
|
|
9
|
+
exports.parseFields = parseFields;
|
|
10
|
+
exports.buildEntity = buildEntity;
|
|
11
|
+
exports.tsType = tsType;
|
|
12
|
+
/**
|
|
13
|
+
* Entity model of the `crud` schematic: the parsed `--fields` grammar plus
|
|
14
|
+
* the naming derivations every generated file shares.
|
|
15
|
+
*
|
|
16
|
+
* Field grammar (comma-separated): `key:type`, `key!:type` (required),
|
|
17
|
+
* `key:select=a|b|c` (options). Example:
|
|
18
|
+
* `name!:string,price:currency,status:select=active|archived,createdAt:date`.
|
|
19
|
+
*/
|
|
20
|
+
const schematics_1 = require("@angular-devkit/schematics");
|
|
21
|
+
/** Field kinds the generator understands (a practical subset of the dynamic-form types). */
|
|
22
|
+
exports.CRUD_FIELD_TYPES = [
|
|
23
|
+
'string',
|
|
24
|
+
'textarea',
|
|
25
|
+
'email',
|
|
26
|
+
'url',
|
|
27
|
+
'number',
|
|
28
|
+
'currency',
|
|
29
|
+
'boolean',
|
|
30
|
+
'date',
|
|
31
|
+
'datetime',
|
|
32
|
+
'select',
|
|
33
|
+
'tags',
|
|
34
|
+
];
|
|
35
|
+
/** Split an identifier into lower-case words: camelCase, snake_case, dash-case, spaces. */
|
|
36
|
+
function words(name) {
|
|
37
|
+
return name
|
|
38
|
+
.replace(/([a-z\d])([A-Z])/g, '$1 $2')
|
|
39
|
+
.replace(/([A-Z]+)([A-Z][a-z])/g, '$1 $2')
|
|
40
|
+
.split(/[\s_-]+/)
|
|
41
|
+
.filter(Boolean)
|
|
42
|
+
.map((w) => w.toLowerCase());
|
|
43
|
+
}
|
|
44
|
+
function classify(name) {
|
|
45
|
+
return words(name)
|
|
46
|
+
.map((w) => w[0].toUpperCase() + w.slice(1))
|
|
47
|
+
.join('');
|
|
48
|
+
}
|
|
49
|
+
function camelize(name) {
|
|
50
|
+
const c = classify(name);
|
|
51
|
+
return c ? c[0].toLowerCase() + c.slice(1) : c;
|
|
52
|
+
}
|
|
53
|
+
function dasherize(name) {
|
|
54
|
+
return words(name).join('-');
|
|
55
|
+
}
|
|
56
|
+
/** Naive English pluralizer — `--plural` overrides it when it guesses wrong. */
|
|
57
|
+
function pluralize(word) {
|
|
58
|
+
if (/(s|x|z|ch|sh)$/i.test(word))
|
|
59
|
+
return `${word}es`;
|
|
60
|
+
if (/[^aeiou]y$/i.test(word))
|
|
61
|
+
return `${word.slice(0, -1)}ies`;
|
|
62
|
+
return `${word}s`;
|
|
63
|
+
}
|
|
64
|
+
/** `createdAt` → `Created at`. */
|
|
65
|
+
function humanize(key) {
|
|
66
|
+
const ws = words(key);
|
|
67
|
+
if (!ws.length)
|
|
68
|
+
return key;
|
|
69
|
+
return [ws[0][0].toUpperCase() + ws[0].slice(1), ...ws.slice(1)].join(' ');
|
|
70
|
+
}
|
|
71
|
+
/** Parse one `key[!]:type[=a|b|c]` segment. */
|
|
72
|
+
function parseField(segment) {
|
|
73
|
+
const match = /^\s*([A-Za-z][A-Za-z\d_-]*)(!?)\s*(?::\s*([A-Za-z-]+)\s*(?:=\s*([^,]+))?)?\s*$/.exec(segment);
|
|
74
|
+
if (!match) {
|
|
75
|
+
throw new schematics_1.SchematicsException(`Cannot parse field "${segment.trim()}". Expected "key:type", "key!:type" or "key:select=a|b|c".`);
|
|
76
|
+
}
|
|
77
|
+
const [, rawKey, bang, rawType = 'string', rawOptions] = match;
|
|
78
|
+
const type = rawType.toLowerCase();
|
|
79
|
+
if (!exports.CRUD_FIELD_TYPES.includes(type)) {
|
|
80
|
+
throw new schematics_1.SchematicsException(`Unknown field type "${rawType}" in "${segment.trim()}". Valid types: ${exports.CRUD_FIELD_TYPES.join(', ')}.`);
|
|
81
|
+
}
|
|
82
|
+
const options = rawOptions
|
|
83
|
+
? rawOptions
|
|
84
|
+
.split('|')
|
|
85
|
+
.map((o) => o.trim())
|
|
86
|
+
.filter(Boolean)
|
|
87
|
+
: [];
|
|
88
|
+
if (type === 'select' && !options.length) {
|
|
89
|
+
throw new schematics_1.SchematicsException(`Field "${rawKey}" is a select but lists no options. Write it as "${rawKey}:select=draft|published".`);
|
|
90
|
+
}
|
|
91
|
+
if (type !== 'select' && options.length) {
|
|
92
|
+
throw new schematics_1.SchematicsException(`Only select fields take "=a|b|c" options (field "${rawKey}").`);
|
|
93
|
+
}
|
|
94
|
+
const key = camelize(rawKey);
|
|
95
|
+
return { key, type, required: bang === '!', options, label: humanize(key) };
|
|
96
|
+
}
|
|
97
|
+
/** Parse the `--fields` option into an ordered, key-unique field list. */
|
|
98
|
+
function parseFields(spec) {
|
|
99
|
+
const fields = spec
|
|
100
|
+
.split(',')
|
|
101
|
+
.map((s) => s.trim())
|
|
102
|
+
.filter(Boolean)
|
|
103
|
+
.map(parseField);
|
|
104
|
+
if (!fields.length) {
|
|
105
|
+
throw new schematics_1.SchematicsException('At least one field is required, e.g. --fields "name!:string".');
|
|
106
|
+
}
|
|
107
|
+
const seen = new Set();
|
|
108
|
+
for (const f of fields) {
|
|
109
|
+
if (f.key === 'id') {
|
|
110
|
+
throw new schematics_1.SchematicsException('The "id" field is added automatically — remove it from --fields.');
|
|
111
|
+
}
|
|
112
|
+
if (seen.has(f.key))
|
|
113
|
+
throw new schematics_1.SchematicsException(`Duplicate field key "${f.key}".`);
|
|
114
|
+
seen.add(f.key);
|
|
115
|
+
}
|
|
116
|
+
return fields;
|
|
117
|
+
}
|
|
118
|
+
/** Derive every name the generated files need from the entity option. */
|
|
119
|
+
function buildEntity(name, fieldsSpec, plural) {
|
|
120
|
+
if (!/^[A-Za-z][A-Za-z\d_-]*$/.test(name)) {
|
|
121
|
+
throw new schematics_1.SchematicsException(`"${name}" is not a valid entity name — use letters/digits like "product" or "OrderLine".`);
|
|
122
|
+
}
|
|
123
|
+
const propertyName = camelize(name);
|
|
124
|
+
const pluralProperty = plural ? camelize(plural) : pluralize(propertyName);
|
|
125
|
+
return {
|
|
126
|
+
className: classify(name),
|
|
127
|
+
propertyName,
|
|
128
|
+
fileName: dasherize(name),
|
|
129
|
+
pluralProperty,
|
|
130
|
+
pluralFile: dasherize(plural ?? pluralize(dasherize(name))),
|
|
131
|
+
constName: words(name).join('_').toUpperCase(),
|
|
132
|
+
human: words(name).join(' '),
|
|
133
|
+
humanPlural: words(plural ?? pluralize(propertyName)).join(' '),
|
|
134
|
+
fields: parseFields(fieldsSpec),
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
/** TypeScript type of one field on the entity interface. */
|
|
138
|
+
function tsType(field) {
|
|
139
|
+
switch (field.type) {
|
|
140
|
+
case 'number':
|
|
141
|
+
case 'currency':
|
|
142
|
+
return 'number';
|
|
143
|
+
case 'boolean':
|
|
144
|
+
return 'boolean';
|
|
145
|
+
case 'select':
|
|
146
|
+
return field.options.map((o) => `'${o.replace(/'/g, "\\'")}'`).join(' | ');
|
|
147
|
+
case 'date':
|
|
148
|
+
case 'datetime':
|
|
149
|
+
// What mk-date-picker / mk-datetime-picker controls hold.
|
|
150
|
+
return 'Date | null';
|
|
151
|
+
case 'tags':
|
|
152
|
+
return 'string[]';
|
|
153
|
+
default:
|
|
154
|
+
// string, textarea, email, url.
|
|
155
|
+
return 'string';
|
|
156
|
+
}
|
|
157
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "http://json-schema.org/schema",
|
|
3
|
+
"$id": "MkKitCrud",
|
|
4
|
+
"title": "@mk-kit/ui crud schematic",
|
|
5
|
+
"type": "object",
|
|
6
|
+
"properties": {
|
|
7
|
+
"entity": {
|
|
8
|
+
"type": "string",
|
|
9
|
+
"description": "Singular entity name, e.g. \"product\" or \"OrderLine\".",
|
|
10
|
+
"$default": {
|
|
11
|
+
"$source": "argv",
|
|
12
|
+
"index": 0
|
|
13
|
+
}
|
|
14
|
+
},
|
|
15
|
+
"fields": {
|
|
16
|
+
"type": "string",
|
|
17
|
+
"default": "name!:string",
|
|
18
|
+
"description": "Comma-separated fields as key:type — \"!\" marks required, selects take options: \"name!:string,price:currency,status:select=draft|published,createdAt:date\". Types: string, textarea, email, url, number, currency, boolean, date, datetime, select, tags."
|
|
19
|
+
},
|
|
20
|
+
"plural": {
|
|
21
|
+
"type": "string",
|
|
22
|
+
"description": "Plural override when the naive pluralizer guesses wrong (e.g. --plural people)."
|
|
23
|
+
},
|
|
24
|
+
"api": {
|
|
25
|
+
"type": "string",
|
|
26
|
+
"description": "REST base URL (e.g. /api/products). Set: the service uses HttpClient; unset: an in-memory store that runs immediately."
|
|
27
|
+
},
|
|
28
|
+
"path": {
|
|
29
|
+
"type": "string",
|
|
30
|
+
"description": "Directory the entity folder is created in. Default: the application's src/app."
|
|
31
|
+
},
|
|
32
|
+
"project": {
|
|
33
|
+
"type": "string",
|
|
34
|
+
"description": "Workspace project to target. Default: the first application project."
|
|
35
|
+
},
|
|
36
|
+
"route": {
|
|
37
|
+
"type": "boolean",
|
|
38
|
+
"default": true,
|
|
39
|
+
"description": "Wire a lazy route into the application's app.routes.ts."
|
|
40
|
+
},
|
|
41
|
+
"spec": {
|
|
42
|
+
"type": "boolean",
|
|
43
|
+
"default": true,
|
|
44
|
+
"description": "Generate a harness-driven spec for the pages."
|
|
45
|
+
}
|
|
46
|
+
},
|
|
47
|
+
"required": ["entity"]
|
|
48
|
+
}
|
|
@@ -271,6 +271,33 @@ declare class MkOverlayRef<TResult = unknown, TComponent = unknown> {
|
|
|
271
271
|
close(result?: TResult): void;
|
|
272
272
|
}
|
|
273
273
|
|
|
274
|
+
/**
|
|
275
|
+
* Resolves the element overlay surfaces are appended to. A function (not an
|
|
276
|
+
* element) because the root may be created lazily, after DI is set up.
|
|
277
|
+
*/
|
|
278
|
+
type MkOverlayRootFn = () => HTMLElement;
|
|
279
|
+
/**
|
|
280
|
+
* Where mk-kit mounts everything that leaves the component tree: overlay
|
|
281
|
+
* containers (dialogs), anchored panels (selects, menus, tooltips), toast /
|
|
282
|
+
* snackbar containers and the tour surfaces. Defaults to `document.body`.
|
|
283
|
+
*
|
|
284
|
+
* Override it to confine those surfaces to another element — `@mk-kit/ui/embed`
|
|
285
|
+
* points it at a themed shadow-DOM host so overlays opened by embedded custom
|
|
286
|
+
* elements stay isolated from the host page's stylesheet:
|
|
287
|
+
*
|
|
288
|
+
* ```ts
|
|
289
|
+
* { provide: MK_OVERLAY_ROOT, useValue: () => myOverlayHost }
|
|
290
|
+
* ```
|
|
291
|
+
*/
|
|
292
|
+
declare const MK_OVERLAY_ROOT: InjectionToken<MkOverlayRootFn>;
|
|
293
|
+
/**
|
|
294
|
+
* The direct child of `document.body` an overlay-root descendant lives under,
|
|
295
|
+
* crossing shadow boundaries on the way up — `null` when the node is not under
|
|
296
|
+
* `body` at all. The overlay service uses it to keep the overlay's own host
|
|
297
|
+
* out of the elements it makes `inert` behind a modal.
|
|
298
|
+
*/
|
|
299
|
+
declare function mkBodyLevelAncestor(node: Node, body: HTMLElement): Element | null;
|
|
300
|
+
|
|
274
301
|
interface MkOverlayConfig<TData = unknown> {
|
|
275
302
|
/** Arbitrary data injected via `MK_OVERLAY_DATA`. */
|
|
276
303
|
data?: TData;
|
|
@@ -308,6 +335,7 @@ declare class MkOverlayService implements OnDestroy {
|
|
|
308
335
|
private readonly appRef;
|
|
309
336
|
private readonly envInjector;
|
|
310
337
|
private readonly document;
|
|
338
|
+
private readonly overlayRoot;
|
|
311
339
|
private readonly isBrowser;
|
|
312
340
|
/** Reference count for the body scroll lock (first open locks, last close unlocks). */
|
|
313
341
|
private openOverlays;
|
|
@@ -417,6 +445,7 @@ declare function mkComputeAnchoredPosition(anchor: MkRectLike, panel: MkSize, vi
|
|
|
417
445
|
declare class MkAnchoredPanel implements AfterViewInit, OnDestroy {
|
|
418
446
|
private readonly host;
|
|
419
447
|
private readonly document;
|
|
448
|
+
private readonly overlayRoot;
|
|
420
449
|
private readonly isBrowser;
|
|
421
450
|
/** The trigger element to position against. */
|
|
422
451
|
readonly anchor: _angular_core.InputSignal<HTMLElement | ElementRef<HTMLElement> | undefined>;
|
|
@@ -1489,5 +1518,5 @@ declare function mkQueryOperatorLabel(op: MkQueryOperator, i18n?: MkI18nStrings)
|
|
|
1489
1518
|
*/
|
|
1490
1519
|
declare function mkQueryToText(group: MkQueryGroup, fields?: readonly MkQueryField[], i18n?: MkI18nStrings): string;
|
|
1491
1520
|
|
|
1492
|
-
export { MK_BREAKPOINTS, MK_DEFAULT_BREAKPOINTS, MK_DEFAULT_DATE_NAMES, MK_DEFAULT_I18N, MK_DEFAULT_VALIDATION, MK_I18N, MK_OVERLAY_DATA, MK_QUERY_OPERATORS, MK_QUERY_UNARY, MkAnchoredPanel, MkBreakpointService, MkFieldContext, MkFocusTrap, MkLiveAnnouncer, MkOverlayRef, MkOverlayService, MkThemeService, mkComputeAnchoredPosition, mkCreateQueryGroup, mkCreateQueryRule, mkFirstErrorMessage, mkGetFocusable, mkHighlight, mkHighlightJson, mkInjectFieldTouched, mkIsQueryGroup, mkIsResponsive, mkMergeI18n, mkQueryCompact, mkQueryIsEmpty, mkQueryOperatorLabel, mkQueryOperatorsFor, mkQueryRuleCount, mkQueryRuleIsComplete, mkQueryRuleMatches, mkQueryToPredicate, mkQueryToText, mkSignalErrorMessage, mkSignalErrorsToValidationErrors, mkUniqueId, mkValidatorChange, provideMkI18n };
|
|
1493
|
-
export type { MkAnchoredPosition, MkAnchoredPositionOptions, MkAriaLivePoliteness, MkBlockEditorStrings, MkBreakpoint, MkBreakpoints, MkCodeLanguage, MkContrastPreference, MkDateNames, MkDensity, MkErrorMessages, MkI18nOverrides, MkI18nStrings, MkOverlayConfig, MkPlacement, MkQueryCombinator, MkQueryField, MkQueryFieldOption, MkQueryGroup, MkQueryNode, MkQueryOperator, MkQueryRule, MkQueryValueType, MkResolvedContrast, MkResolvedTheme, MkResponsive, MkSignalValidationError, MkSize$1 as MkSize, MkSortAnnounceDirection, MkThemePreference, MkTone, MkValidationStrings, MkValidatorChangeRef, MkVariant };
|
|
1521
|
+
export { MK_BREAKPOINTS, MK_DEFAULT_BREAKPOINTS, MK_DEFAULT_DATE_NAMES, MK_DEFAULT_I18N, MK_DEFAULT_VALIDATION, MK_I18N, MK_OVERLAY_DATA, MK_OVERLAY_ROOT, MK_QUERY_OPERATORS, MK_QUERY_UNARY, MkAnchoredPanel, MkBreakpointService, MkFieldContext, MkFocusTrap, MkLiveAnnouncer, MkOverlayRef, MkOverlayService, MkThemeService, mkBodyLevelAncestor, mkComputeAnchoredPosition, mkCreateQueryGroup, mkCreateQueryRule, mkFirstErrorMessage, mkGetFocusable, mkHighlight, mkHighlightJson, mkInjectFieldTouched, mkIsQueryGroup, mkIsResponsive, mkMergeI18n, mkQueryCompact, mkQueryIsEmpty, mkQueryOperatorLabel, mkQueryOperatorsFor, mkQueryRuleCount, mkQueryRuleIsComplete, mkQueryRuleMatches, mkQueryToPredicate, mkQueryToText, mkSignalErrorMessage, mkSignalErrorsToValidationErrors, mkUniqueId, mkValidatorChange, provideMkI18n };
|
|
1522
|
+
export type { MkAnchoredPosition, MkAnchoredPositionOptions, MkAriaLivePoliteness, MkBlockEditorStrings, MkBreakpoint, MkBreakpoints, MkCodeLanguage, MkContrastPreference, MkDateNames, MkDensity, MkErrorMessages, MkI18nOverrides, MkI18nStrings, MkOverlayConfig, MkOverlayRootFn, MkPlacement, MkQueryCombinator, MkQueryField, MkQueryFieldOption, MkQueryGroup, MkQueryNode, MkQueryOperator, MkQueryRule, MkQueryValueType, MkResolvedContrast, MkResolvedTheme, MkResponsive, MkSignalValidationError, MkSize$1 as MkSize, MkSortAnnounceDirection, MkThemePreference, MkTone, MkValidationStrings, MkValidatorChangeRef, MkVariant };
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import { Provider, EnvironmentProviders, Type, ApplicationRef } from '@angular/core';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Rewrites a document-level stylesheet for adoption into a shadow root:
|
|
5
|
+
* `:root` never matches inside one, so token blocks like mk-kit's
|
|
6
|
+
* `:root { --mk-primary: … }` are retargeted to `:host`. Theme opt-ins keep
|
|
7
|
+
* working — `:root:not([data-mk-theme='light'])` becomes
|
|
8
|
+
* `:host:not([data-mk-theme='light'])`, so `<my-widget data-mk-theme="dark">`
|
|
9
|
+
* switches one embedded element to the dark palette.
|
|
10
|
+
*
|
|
11
|
+
* ```ts
|
|
12
|
+
* import themeCss from '@mk-kit/ui/styles.css' with { type: 'text' };
|
|
13
|
+
* mkEmbed({ styles: mkShadowCss(themeCss) });
|
|
14
|
+
* ```
|
|
15
|
+
*/
|
|
16
|
+
declare function mkShadowCss(css: string): string;
|
|
17
|
+
|
|
18
|
+
/** Options for {@link mkEmbed}. */
|
|
19
|
+
interface MkEmbedInit {
|
|
20
|
+
/**
|
|
21
|
+
* CSS text adopted into every element's shadow root (and the overlay host).
|
|
22
|
+
* Pass the mk-kit theme through {@link mkShadowCss} so its `:root` token
|
|
23
|
+
* blocks target `:host`; append your own widget CSS after it. Shared as
|
|
24
|
+
* constructable stylesheets when the browser supports them (one parse for
|
|
25
|
+
* any number of instances), `<style>` elements otherwise.
|
|
26
|
+
*/
|
|
27
|
+
styles?: string | readonly string[];
|
|
28
|
+
/**
|
|
29
|
+
* Extra providers for the shared application — `provideMkI18n(…)`,
|
|
30
|
+
* `provideMkExtendedIcons()`, `provideHttpClient()`, your services.
|
|
31
|
+
*/
|
|
32
|
+
providers?: Array<Provider | EnvironmentProviders>;
|
|
33
|
+
/**
|
|
34
|
+
* Mount mk-kit overlays (dialogs, anchored panels, toasts, tours) inside a
|
|
35
|
+
* page-level shadow host that carries the same `styles`, instead of bare
|
|
36
|
+
* `document.body`. Default `true`; set `false` to keep the application
|
|
37
|
+
* default (overlays styled by the page's own stylesheets).
|
|
38
|
+
*/
|
|
39
|
+
overlays?: boolean;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Creates an embed application: a factory for custom elements that render
|
|
43
|
+
* mk-kit-based Angular components behind shadow DOM.
|
|
44
|
+
*
|
|
45
|
+
* - **Lazy**: `element()` only defines the tag; the Angular application is
|
|
46
|
+
* created on the first element actually connected to a document.
|
|
47
|
+
* - **Shared**: every element of one `mkEmbed()` call runs in one zoneless
|
|
48
|
+
* `ApplicationRef` with one provider set.
|
|
49
|
+
* - **Isolated but themable**: the host page's CSS cannot reach the widget
|
|
50
|
+
* internals, while `--mk-*` custom properties still inherit through the
|
|
51
|
+
* shadow boundary — set them on the element (or any ancestor) to theme it.
|
|
52
|
+
* - **Styled**: Angular routes each component's own styles into the shadow
|
|
53
|
+
* root it renders in; the `styles` option supplies the token/theme layer.
|
|
54
|
+
*
|
|
55
|
+
* ```ts
|
|
56
|
+
* import { mkEmbed, mkShadowCss } from '@mk-kit/ui/embed';
|
|
57
|
+
* import themeCss from '@mk-kit/ui/styles.css' with { type: 'text' };
|
|
58
|
+
*
|
|
59
|
+
* mkEmbed({ styles: mkShadowCss(themeCss) })
|
|
60
|
+
* .element('acme-reviews', ReviewsWidget)
|
|
61
|
+
* .element('acme-signup', SignupWidget);
|
|
62
|
+
* ```
|
|
63
|
+
*
|
|
64
|
+
* ```html
|
|
65
|
+
* <acme-reviews product-id="42" style="--mk-primary: #7c3aed"></acme-reviews>
|
|
66
|
+
* ```
|
|
67
|
+
*
|
|
68
|
+
* Inputs are exposed as dash-cased attributes (string values go through the
|
|
69
|
+
* input's `transform`, so `booleanAttribute` / `numberAttribute` inputs coerce
|
|
70
|
+
* as usual) and as camel-cased element properties (any value); outputs become
|
|
71
|
+
* bubbling, composed `CustomEvent`s named after the output, with the emitted
|
|
72
|
+
* value as `detail`.
|
|
73
|
+
*/
|
|
74
|
+
declare function mkEmbed(init?: MkEmbedInit): MkEmbedApp;
|
|
75
|
+
/** One shared embed application. Create it with {@link mkEmbed}. */
|
|
76
|
+
declare class MkEmbedApp {
|
|
77
|
+
/** @internal Adopted into every shadow root this app renders in. */
|
|
78
|
+
readonly _mkStyles: MkEmbedStyles;
|
|
79
|
+
private readonly init;
|
|
80
|
+
private appPromise;
|
|
81
|
+
private appRef;
|
|
82
|
+
private overlayHost;
|
|
83
|
+
private overlayInner;
|
|
84
|
+
private destroyed;
|
|
85
|
+
constructor(init?: MkEmbedInit);
|
|
86
|
+
/**
|
|
87
|
+
* Defines `tag` as a custom element rendering `component`. Chainable; a
|
|
88
|
+
* no-op when the tag is already defined (hot reload, duplicate script) or
|
|
89
|
+
* outside a browser.
|
|
90
|
+
*/
|
|
91
|
+
element(tag: string, component: Type<unknown>): this;
|
|
92
|
+
/** Resolves when the shared application is running (created on demand). */
|
|
93
|
+
ready(): Promise<void>;
|
|
94
|
+
/** Resolves when the application has no pending change detection. */
|
|
95
|
+
whenStable(): Promise<void>;
|
|
96
|
+
/**
|
|
97
|
+
* Destroys the shared application, every mounted component and the overlay
|
|
98
|
+
* host. Defined tags remain registered (the platform cannot undefine them)
|
|
99
|
+
* but render nothing afterwards.
|
|
100
|
+
*/
|
|
101
|
+
destroy(): void;
|
|
102
|
+
/** @internal */
|
|
103
|
+
_mkApplication(): Promise<ApplicationRef>;
|
|
104
|
+
/** @internal The running application — only valid once `ready()` resolved. */
|
|
105
|
+
get _mkAppRef(): ApplicationRef;
|
|
106
|
+
/**
|
|
107
|
+
* Lazily builds the page-level overlay host: a shadow root carrying the
|
|
108
|
+
* embed styles, with an inner container the overlay services append to.
|
|
109
|
+
*/
|
|
110
|
+
private overlayRootElement;
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* The embed styles, parsed once and shared: constructable stylesheets where
|
|
114
|
+
* supported, cloned `<style>` elements otherwise.
|
|
115
|
+
*/
|
|
116
|
+
declare class MkEmbedStyles {
|
|
117
|
+
private readonly css;
|
|
118
|
+
private sheets;
|
|
119
|
+
constructor(css: readonly string[]);
|
|
120
|
+
adopt(root: ShadowRoot): void;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
export { MkEmbedApp, mkEmbed, mkShadowCss };
|
|
124
|
+
export type { MkEmbedInit };
|
|
@@ -78,6 +78,7 @@ declare class MkTooltip {
|
|
|
78
78
|
private readonly appRef;
|
|
79
79
|
private readonly envInjector;
|
|
80
80
|
private readonly document;
|
|
81
|
+
private readonly overlayRoot;
|
|
81
82
|
private readonly isBrowser;
|
|
82
83
|
/** Set by pointerdown so the focusin it causes doesn't re-open the tip. */
|
|
83
84
|
private suppressFocusShow;
|
|
@@ -780,6 +781,7 @@ declare class MkToastService {
|
|
|
780
781
|
private readonly appRef;
|
|
781
782
|
private readonly envInjector;
|
|
782
783
|
private readonly document;
|
|
784
|
+
private readonly overlayRoot;
|
|
783
785
|
private readonly isBrowser;
|
|
784
786
|
private readonly _toasts;
|
|
785
787
|
/** Reactive list of currently visible toasts. */
|
|
@@ -919,6 +921,7 @@ declare class MkSnackbarService {
|
|
|
919
921
|
private readonly appRef;
|
|
920
922
|
private readonly envInjector;
|
|
921
923
|
private readonly document;
|
|
924
|
+
private readonly overlayRoot;
|
|
922
925
|
private readonly isBrowser;
|
|
923
926
|
private readonly _active;
|
|
924
927
|
/** The currently visible snackbar, or `null`. */
|
|
@@ -1255,6 +1258,7 @@ declare class MkTourService {
|
|
|
1255
1258
|
private readonly appRef;
|
|
1256
1259
|
private readonly envInjector;
|
|
1257
1260
|
private readonly document;
|
|
1261
|
+
private readonly overlayRoot;
|
|
1258
1262
|
private readonly isBrowser;
|
|
1259
1263
|
private readonly _steps;
|
|
1260
1264
|
private readonly _index;
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { Provider } from '@angular/core';
|
|
2
|
+
import { MkBlockEditorStrings, MkDateNames, MkI18nStrings, MkValidationStrings, MkI18nOverrides } from '@mk-kit/ui/core';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Picks the German plural form for a count — CLDR's `de` rules for integers:
|
|
6
|
+
* `one` for 1, `other` for everything else.
|
|
7
|
+
*
|
|
8
|
+
* ```ts
|
|
9
|
+
* mkPluralDe(1, 'Ergebnis', 'Ergebnisse'); // 'Ergebnis'
|
|
10
|
+
* mkPluralDe(3, 'Ergebnis', 'Ergebnisse'); // 'Ergebnisse'
|
|
11
|
+
* ```
|
|
12
|
+
*/
|
|
13
|
+
declare function mkPluralDe(count: number, one: string, other: string): string;
|
|
14
|
+
/** German month and weekday names (Sunday-first, as in `Intl`). */
|
|
15
|
+
declare const MK_DE_DATE_NAMES: MkDateNames;
|
|
16
|
+
/** German validation messages rendered by `mk-form-field`. */
|
|
17
|
+
declare const MK_DE_VALIDATION: MkValidationStrings;
|
|
18
|
+
/** German strings of the block editor's chrome. */
|
|
19
|
+
declare const MK_DE_BLOCK_EDITOR: MkBlockEditorStrings;
|
|
20
|
+
/**
|
|
21
|
+
* The complete German string map — every key of {@link MkI18nStrings},
|
|
22
|
+
* `locale: 'de-DE'` and `currency: 'EUR'` for the formatting pipes.
|
|
23
|
+
* Provide it whole with {@link provideMkI18nDe}, or pass it as the base of
|
|
24
|
+
* `provideMkI18n(overrides, MK_DE_I18N)`.
|
|
25
|
+
*/
|
|
26
|
+
declare const MK_DE_I18N: MkI18nStrings;
|
|
27
|
+
/**
|
|
28
|
+
* Provides the German strings — {@link MK_DE_I18N} with any `overrides`
|
|
29
|
+
* merged on top (deep for `dateNames`, `blockEditor` and `validation`, like
|
|
30
|
+
* `provideMkI18n`).
|
|
31
|
+
*
|
|
32
|
+
* ```ts
|
|
33
|
+
* bootstrapApplication(App, {
|
|
34
|
+
* providers: [provideMkI18nDe({ noData: 'Nichts zu sehen' })],
|
|
35
|
+
* });
|
|
36
|
+
* ```
|
|
37
|
+
*/
|
|
38
|
+
declare function provideMkI18nDe(overrides?: MkI18nOverrides): Provider;
|
|
39
|
+
|
|
40
|
+
export { MK_DE_BLOCK_EDITOR, MK_DE_DATE_NAMES, MK_DE_I18N, MK_DE_VALIDATION, mkPluralDe, provideMkI18nDe };
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { Provider } from '@angular/core';
|
|
2
|
+
import { MkBlockEditorStrings, MkDateNames, MkI18nStrings, MkValidationStrings, MkI18nOverrides } from '@mk-kit/ui/core';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Picks the Spanish plural form for a count — CLDR's `es` rules for integers:
|
|
6
|
+
* `one` for 1, `other` for everything else.
|
|
7
|
+
*
|
|
8
|
+
* ```ts
|
|
9
|
+
* mkPluralEs(1, 'resultado', 'resultados'); // 'resultado'
|
|
10
|
+
* mkPluralEs(3, 'resultado', 'resultados'); // 'resultados'
|
|
11
|
+
* ```
|
|
12
|
+
*/
|
|
13
|
+
declare function mkPluralEs(count: number, one: string, other: string): string;
|
|
14
|
+
/** Spanish month and weekday names (Sunday-first, lowercase as in `Intl`). */
|
|
15
|
+
declare const MK_ES_DATE_NAMES: MkDateNames;
|
|
16
|
+
/** Spanish validation messages rendered by `mk-form-field`. */
|
|
17
|
+
declare const MK_ES_VALIDATION: MkValidationStrings;
|
|
18
|
+
/** Spanish strings of the block editor's chrome. */
|
|
19
|
+
declare const MK_ES_BLOCK_EDITOR: MkBlockEditorStrings;
|
|
20
|
+
/**
|
|
21
|
+
* The complete Spanish string map — every key of {@link MkI18nStrings},
|
|
22
|
+
* `locale: 'es-ES'` and `currency: 'EUR'` for the formatting pipes.
|
|
23
|
+
* Provide it whole with {@link provideMkI18nEs}, or pass it as the base of
|
|
24
|
+
* `provideMkI18n(overrides, MK_ES_I18N)`.
|
|
25
|
+
*/
|
|
26
|
+
declare const MK_ES_I18N: MkI18nStrings;
|
|
27
|
+
/**
|
|
28
|
+
* Provides the Spanish strings — {@link MK_ES_I18N} with any `overrides`
|
|
29
|
+
* merged on top (deep for `dateNames`, `blockEditor` and `validation`, like
|
|
30
|
+
* `provideMkI18n`).
|
|
31
|
+
*
|
|
32
|
+
* ```ts
|
|
33
|
+
* bootstrapApplication(App, {
|
|
34
|
+
* providers: [provideMkI18nEs({ noData: 'No hay nada aquí' })],
|
|
35
|
+
* });
|
|
36
|
+
* ```
|
|
37
|
+
*/
|
|
38
|
+
declare function provideMkI18nEs(overrides?: MkI18nOverrides): Provider;
|
|
39
|
+
|
|
40
|
+
export { MK_ES_BLOCK_EDITOR, MK_ES_DATE_NAMES, MK_ES_I18N, MK_ES_VALIDATION, mkPluralEs, provideMkI18nEs };
|