elcrm 1.1.17 → 1.1.19
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/dist/vite/plugin-css-scoped.d.ts +3 -0
- package/dist/vite/plugin-css-scoped.js +40 -8
- package/package.json +1 -1
- package/templates/elcrm-docs/CLI.elCRM.md +1 -1
- package/templates/elcrm-docs/COMPONENTS.elCRM.md +2 -2
- package/templates/orbit/web/src/style/theme.css +11 -0
- package/templates/panel/web/src/style/theme.css +11 -0
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Vite helper: короткие unique className для CSS Modules elCRM.
|
|
3
|
+
* Формат: 3 символа `{prefix}{буква}{класс}`.
|
|
3
4
|
* @see createCssScoped
|
|
4
5
|
*/
|
|
5
6
|
export type CssScopedOptions = {
|
|
6
7
|
/** Первая буква пакета (c = components, b = button, …) */
|
|
7
8
|
prefix: string;
|
|
9
|
+
/** Свой prefix у компонента, если слотов пакета не хватает */
|
|
10
|
+
prefixOverrides?: Record<string, string>;
|
|
8
11
|
/** Папка компонента → одна буква */
|
|
9
12
|
components: Record<string, string>;
|
|
10
13
|
/** Каталог с папками компонентов (абсолютный путь) */
|
|
@@ -15,15 +15,22 @@ function listModuleCss(dir) {
|
|
|
15
15
|
}
|
|
16
16
|
function localClasses(css) {
|
|
17
17
|
const set = new Set;
|
|
18
|
-
for (const m of css.matchAll(/(?:^|[,{\s>+~])\.([a-
|
|
19
|
-
set.add(m[1]);
|
|
18
|
+
for (const m of css.matchAll(/(?:^|[,{\s>+~])\.([a-z0-9])\b/gi)) {
|
|
19
|
+
set.add(m[1].toLowerCase());
|
|
20
20
|
}
|
|
21
21
|
return [...set];
|
|
22
22
|
}
|
|
23
|
+
function assertPrefix(value, label) {
|
|
24
|
+
if (!/^[a-z]$/.test(value)) {
|
|
25
|
+
throw new Error(`[css-scoped] ${label} должен быть одной буквой a-z, получено: "${value}"`);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
23
28
|
function createCssScoped(options) {
|
|
24
29
|
const prefix = options.prefix;
|
|
25
|
-
|
|
26
|
-
|
|
30
|
+
assertPrefix(prefix, "prefix");
|
|
31
|
+
const prefixOverrides = options.prefixOverrides ?? {};
|
|
32
|
+
for (const [name, pfx] of Object.entries(prefixOverrides)) {
|
|
33
|
+
assertPrefix(pfx, `prefixOverrides["${name}"]`);
|
|
27
34
|
}
|
|
28
35
|
const components = options.components;
|
|
29
36
|
const libDir = options.libDir;
|
|
@@ -37,9 +44,12 @@ function createCssScoped(options) {
|
|
|
37
44
|
}
|
|
38
45
|
return m[1];
|
|
39
46
|
}
|
|
47
|
+
function prefixFor(component) {
|
|
48
|
+
return prefixOverrides[component] ?? prefix;
|
|
49
|
+
}
|
|
40
50
|
function generateScopedName(name, filename) {
|
|
41
|
-
if (!/^[a-
|
|
42
|
-
throw new Error(`[css-scoped] локальный класс
|
|
51
|
+
if (!/^[a-z0-9]$/.test(name)) {
|
|
52
|
+
throw new Error(`[css-scoped] локальный класс — 1 знак [a-z0-9] (scoped = 3 символа): ".${name}" (${filename})`);
|
|
43
53
|
}
|
|
44
54
|
const component = componentFromFilename(filename);
|
|
45
55
|
const letter = components[component];
|
|
@@ -49,7 +59,10 @@ function createCssScoped(options) {
|
|
|
49
59
|
if (!/^[a-z0-9A-Z]$/.test(letter)) {
|
|
50
60
|
throw new Error(`[css-scoped] ключ компонента "${component}" должен быть a-z0-9A-Z: "${letter}"`);
|
|
51
61
|
}
|
|
52
|
-
const scoped = `${
|
|
62
|
+
const scoped = `${prefixFor(component)}${letter}${name}`;
|
|
63
|
+
if (scoped.length !== 3) {
|
|
64
|
+
throw new Error(`[css-scoped] "${scoped}" ≠ 3 символа (${component}.${name})`);
|
|
65
|
+
}
|
|
53
66
|
const owner = `${component}.${name}`;
|
|
54
67
|
const prev = registry.get(scoped);
|
|
55
68
|
if (prev && prev !== owner) {
|
|
@@ -72,6 +85,10 @@ function createCssScoped(options) {
|
|
|
72
85
|
if (missingMap.length) {
|
|
73
86
|
throw new Error(`[css-scoped] нет буквы в components: ${missingMap.join(", ")}`);
|
|
74
87
|
}
|
|
88
|
+
const unknownOverride = Object.keys(prefixOverrides).filter((n) => !mapped.has(n));
|
|
89
|
+
if (unknownOverride.length) {
|
|
90
|
+
throw new Error(`[css-scoped] prefixOverrides без компонента: ${unknownOverride.join(", ")}`);
|
|
91
|
+
}
|
|
75
92
|
const letters = Object.values(components);
|
|
76
93
|
const dupLetter = [
|
|
77
94
|
...new Set(letters.filter((l, i) => letters.indexOf(l) !== i))
|
|
@@ -79,15 +96,30 @@ function createCssScoped(options) {
|
|
|
79
96
|
if (dupLetter.length) {
|
|
80
97
|
throw new Error(`[css-scoped] дубли букв компонентов: ${dupLetter.join(", ")}`);
|
|
81
98
|
}
|
|
99
|
+
const namespaces = Object.entries(components).map(([name, letter]) => `${prefixFor(name)}${letter}`);
|
|
100
|
+
const dupNs = [
|
|
101
|
+
...new Set(namespaces.filter((n, i) => namespaces.indexOf(n) !== i))
|
|
102
|
+
];
|
|
103
|
+
if (dupNs.length) {
|
|
104
|
+
throw new Error(`[css-scoped] дубли пространства {prefix}{буква}: ${dupNs.join(", ")}`);
|
|
105
|
+
}
|
|
82
106
|
const files = listModuleCss(libDir);
|
|
83
107
|
for (const file of files) {
|
|
84
108
|
const css = readFileSync(file, "utf8");
|
|
109
|
+
const long = [
|
|
110
|
+
...css.matchAll(/(?:^|[,{\s>+~])\.([a-z][a-z0-9]+)\b/gi)
|
|
111
|
+
].map((m) => m[1]);
|
|
112
|
+
if (long.length) {
|
|
113
|
+
const component = componentFromFilename(file);
|
|
114
|
+
throw new Error(`[css-scoped] ${component}: класс длиннее 1 знака (${[...new Set(long)].join(", ")}). Сократи или вынеси в prefixOverrides + 1 знак.`);
|
|
115
|
+
}
|
|
85
116
|
for (const local of localClasses(css)) {
|
|
86
117
|
generateScopedName(local, file);
|
|
87
118
|
}
|
|
88
119
|
}
|
|
89
120
|
const snap = snapshot();
|
|
90
|
-
|
|
121
|
+
const extra = Object.entries(prefixOverrides).map(([n, p]) => `${n}=${p}`).join(", ");
|
|
122
|
+
console.log(`[css-scoped] OK: ${files.length} module.css, ${snap.size} классов (prefix=${prefix}${extra ? `; ${extra}` : ""})`);
|
|
91
123
|
return snap;
|
|
92
124
|
}
|
|
93
125
|
function plugin() {
|
package/package.json
CHANGED
|
@@ -111,7 +111,7 @@ minifyCssVars({ var: 2 })
|
|
|
111
111
|
|
|
112
112
|
Короткие имена **уникальны и подряд**: `--aa`, `--ab`, `--ac`… Занятые слоты пропускаются; `--field` не затирает `--field-border`. Чанки JSZip не минифицируются (декремент `--n`). Если слотов нет — сборка падает.
|
|
113
113
|
|
|
114
|
-
Также: `elcrm/vite/plugin-css-scoped`, `discover-lib`, `concat-css`, `check-tokens`, `postbuild`.
|
|
114
|
+
Также: `elcrm/vite/plugin-css-scoped` (`prefix` + `prefixOverrides`, scoped = 3 знака), `discover-lib`, `concat-css`, `check-tokens`, `postbuild`.
|
|
115
115
|
|
|
116
116
|
### Прочее
|
|
117
117
|
|
|
@@ -35,8 +35,8 @@ import type { PricingPlan, HealthmapData } from "@elcrm/components";
|
|
|
35
35
|
- **Stack** — ряд/колонка/сетка (`direction="row"|"column"|"grid"`). Не `Block` / `Row` / `Column`. Сетка: `auto-fit` + `--stack-grid-min`, либо `columns={4}` / `columns={["1fr", "100px", "auto"]}`. С `columns` ряды `stretch` (карточки не наезжают); в одну линию — `style={{ alignItems: "center" }}`. Не `height: 100%` у детей сетки.
|
|
36
36
|
- **Card** — курсор: `--card-pointer` (`auto` / `pointer`). Сетка Stack: `--stack-grid-min`.
|
|
37
37
|
- **Stat** — метрика: `label` + `value`, `tone` как у Badge, `labelMuted`. Не собирать из `span` в приложении.
|
|
38
|
-
- **Healthmap** — теплокарта дней (`data.days`). Данные грузит приложение; слоты `brandName` / `brandSub` / `unit` / `hint`. Токены `--healthmap-l0`…`l4`.
|
|
39
|
-
- **PanelInfo** — выезд справа (шапка / body / footer). На узком экране — оверлей. Слоты: `children`, `footer`, `headerExtra`, `close`. Токены `--panel-info
|
|
38
|
+
- **Healthmap** — теплокарта дней (`data.days`). Данные грузит приложение; слоты `brandName` / `brandSub` / `unit` / `hint`. Токены `--healthmap-cell`, `--healthmap-gap`, `--healthmap-l0`…`l4`.
|
|
39
|
+
- **PanelInfo** — выезд справа (шапка / body / footer). На узком экране — оверлей. Слоты: `children`, `footer`, `headerExtra`, `close`. Токены `--panel-info-width`, `--panel-info-width-min`, `--panel-info-overlay-max`, `--panel-info-backdrop`.
|
|
40
40
|
- **ActionGroup** — тулбар: `label` + `items` (узлы) и/или `children`. Кнопки — `@elcrm/button`, не нативный `<button>`.
|
|
41
41
|
- **TextGroup** — две строки: `title` + `description` (без аватара; с аватаром — AvatarName).
|
|
42
42
|
- **Item** — строка списка. `as` button/a/li, `active`. `variant="card"` — рамка и muted-фон (идеи / тикеты / inbox). Не дублировать `.desk-row` в приложении.
|
|
@@ -153,6 +153,17 @@
|
|
|
153
153
|
--chat-messages-header-padding: 14px 16px;
|
|
154
154
|
--chat-messages-body-padding: 16px 16px 20px;
|
|
155
155
|
--chat-messages-footer-padding: 10px 12px 12px;
|
|
156
|
+
--panel-info-width: min(22rem, 42vw);
|
|
157
|
+
--panel-info-width-min: 16rem;
|
|
158
|
+
--panel-info-overlay-max: 24rem;
|
|
159
|
+
--panel-info-backdrop: color-mix(in srgb, var(--shell-color) 35%, transparent);
|
|
160
|
+
--healthmap-cell: 12px;
|
|
161
|
+
--healthmap-gap: 3px;
|
|
162
|
+
--healthmap-l0: var(--shell-background-muted);
|
|
163
|
+
--healthmap-l1: color-mix(in srgb, var(--shell-color-success) 25%, var(--shell-background-muted));
|
|
164
|
+
--healthmap-l2: color-mix(in srgb, var(--shell-color-success) 45%, transparent);
|
|
165
|
+
--healthmap-l3: color-mix(in srgb, var(--shell-color-success) 70%, transparent);
|
|
166
|
+
--healthmap-l4: var(--shell-color-success);
|
|
156
167
|
}
|
|
157
168
|
|
|
158
169
|
/* —— база приложения (без захардкоженных цветов) —— */
|
|
@@ -164,6 +164,17 @@
|
|
|
164
164
|
--chat-messages-header-padding: 14px 16px;
|
|
165
165
|
--chat-messages-body-padding: 16px 16px 20px;
|
|
166
166
|
--chat-messages-footer-padding: 10px 12px 12px;
|
|
167
|
+
--panel-info-width: min(22rem, 42vw);
|
|
168
|
+
--panel-info-width-min: 16rem;
|
|
169
|
+
--panel-info-overlay-max: 24rem;
|
|
170
|
+
--panel-info-backdrop: color-mix(in srgb, var(--shell-color) 35%, transparent);
|
|
171
|
+
--healthmap-cell: 12px;
|
|
172
|
+
--healthmap-gap: 3px;
|
|
173
|
+
--healthmap-l0: var(--shell-background-muted);
|
|
174
|
+
--healthmap-l1: color-mix(in srgb, var(--shell-color-success) 25%, var(--shell-background-muted));
|
|
175
|
+
--healthmap-l2: color-mix(in srgb, var(--shell-color-success) 45%, transparent);
|
|
176
|
+
--healthmap-l3: color-mix(in srgb, var(--shell-color-success) 70%, transparent);
|
|
177
|
+
--healthmap-l4: var(--shell-color-success);
|
|
167
178
|
}
|
|
168
179
|
|
|
169
180
|
/* —— база приложения (без захардкоженных цветов) —— */
|