@innovaccer/design-system 4.27.0 → 4.28.0-1
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/CHANGELOG.md +24 -0
- package/css/SCOPED_CSS.md +145 -0
- package/css/dist/index.css +4 -4
- package/css/dist/index.css.map +1 -1
- package/css/dist/mds-scoped.css +13826 -0
- package/css/gulpfile.js +34 -25
- package/css/scoped/package.json +5 -0
- package/css/scripts/build-scoped.mjs +313 -0
- package/css/scripts/css-sources.cjs +49 -0
- package/css/scripts/portal-roots.cjs +47 -0
- package/css/src/components/spinner.module.css +4 -4
- package/dist/brotli/index.js +1 -1
- package/dist/brotli/index.js.br +0 -0
- package/dist/cjs/index.js +1 -1
- package/dist/esm/index.js +181 -181
- package/dist/gzip/index.js +1 -1
- package/dist/gzip/index.js.gz +0 -0
- package/dist/index.js +181 -181
- package/dist/index.umd.css +4 -4
- package/dist/index.umd.js +1 -1
- package/package.json +3 -2
package/css/gulpfile.js
CHANGED
|
@@ -5,28 +5,10 @@ const sourcemaps = require('gulp-sourcemaps');
|
|
|
5
5
|
const concat = require('gulp-concat');
|
|
6
6
|
const postcssColorMod = require('postcss-color-mod-function');
|
|
7
7
|
const cleaner = require('gulp-clean');
|
|
8
|
+
const path = require('path');
|
|
9
|
+
const { execFile } = require('child_process');
|
|
8
10
|
|
|
9
|
-
const
|
|
10
|
-
const materialFont = './material-design-icons/iconfont/*.{ttf,otf,woff2}';
|
|
11
|
-
|
|
12
|
-
const typographyCssPath = [
|
|
13
|
-
'./src/components/text.module.css',
|
|
14
|
-
'./src/components/heading.module.css',
|
|
15
|
-
'./src/components/label.module.css',
|
|
16
|
-
'./src/components/caption.module.css',
|
|
17
|
-
'./src/components/subheading.module.css',
|
|
18
|
-
];
|
|
19
|
-
|
|
20
|
-
const sources = [
|
|
21
|
-
'./src/tokens/*.css',
|
|
22
|
-
'./src/variables/*.css',
|
|
23
|
-
materialIcons,
|
|
24
|
-
'./src/core/*.css',
|
|
25
|
-
...typographyCssPath,
|
|
26
|
-
'./src/components/*.css',
|
|
27
|
-
'./src/ai-components/*.css',
|
|
28
|
-
'./src/utils/*.css',
|
|
29
|
-
];
|
|
11
|
+
const { materialFont, sources } = require('./scripts/css-sources.cjs');
|
|
30
12
|
|
|
31
13
|
function clean() {
|
|
32
14
|
return gulp.src('./dist/*', { allowEmpty: true }).pipe(cleaner());
|
|
@@ -47,10 +29,37 @@ function font() {
|
|
|
47
29
|
return gulp.src([materialFont]).pipe(gulp.dest('./dist'));
|
|
48
30
|
}
|
|
49
31
|
|
|
50
|
-
|
|
32
|
+
/**
|
|
33
|
+
* Builds `dist/mds-scoped.css` — the same CSS wrapped in `@scope ([data-mds-root])`.
|
|
34
|
+
*
|
|
35
|
+
* Runs out of process because the scoped build is authored as an ES module (it needs
|
|
36
|
+
* postcss' AST directly to hoist `@keyframes` above the `@scope` block, which a
|
|
37
|
+
* streaming gulp pipeline cannot express). Errors propagate through `cb` so a broken
|
|
38
|
+
* scoped build fails `gulp build` rather than passing silently.
|
|
39
|
+
*/
|
|
40
|
+
function cssScoped(cb) {
|
|
41
|
+
execFile(
|
|
42
|
+
process.execPath,
|
|
43
|
+
[path.join(__dirname, 'scripts/build-scoped.mjs')],
|
|
44
|
+
{ cwd: __dirname },
|
|
45
|
+
(err, stdout, stderr) => {
|
|
46
|
+
if (stdout) process.stdout.write(stdout);
|
|
47
|
+
if (stderr) process.stderr.write(stderr);
|
|
48
|
+
cb(err);
|
|
49
|
+
}
|
|
50
|
+
);
|
|
51
|
+
}
|
|
52
|
+
cssScoped.displayName = 'css:scoped';
|
|
53
|
+
|
|
54
|
+
/** Both bundles read the same sources, so one watch rebuilds both. */
|
|
55
|
+
function watch() {
|
|
56
|
+
return gulp.watch(sources, gulp.parallel(css, cssScoped));
|
|
57
|
+
}
|
|
58
|
+
watch.displayName = 'watch';
|
|
51
59
|
|
|
60
|
+
exports.build = gulp.series(clean, gulp.parallel(css, font, cssScoped));
|
|
61
|
+
exports.cssScoped = cssScoped;
|
|
52
62
|
exports.clean = clean;
|
|
63
|
+
exports.watch = watch;
|
|
53
64
|
|
|
54
|
-
gulp.task('watch',
|
|
55
|
-
gulp.watch(sources, gulp.series(css));
|
|
56
|
-
});
|
|
65
|
+
gulp.task('watch', watch);
|
|
@@ -0,0 +1,313 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Builds css/dist/mds-scoped.css — the design-system stylesheet wrapped in
|
|
3
|
+
* `@scope ([data-mds-root])`, so it only styles subtrees the consumer opts into
|
|
4
|
+
* by adding `data-mds-root` to a container element.
|
|
5
|
+
*
|
|
6
|
+
* Composition (not a general selector rewriter):
|
|
7
|
+
* 1. Concatenate the same sources, in the same order, as the default build
|
|
8
|
+
* 2. Hoist globally-named constructs (@keyframes / @font-face / @property /
|
|
9
|
+
* @counter-style) above @scope — they are not scopable and would be dropped
|
|
10
|
+
* 3. Remap :root / html / body / :host -> :scope so tokens and base styles
|
|
11
|
+
* attach to the scope root instead of the document
|
|
12
|
+
* 4. Add a root-anchored variant of every selector, so a rule can also match the
|
|
13
|
+
* scope root itself and not just its descendants (see below)
|
|
14
|
+
* 5. Wrap the remaining rules in @scope (<selector>)
|
|
15
|
+
*
|
|
16
|
+
* This is a CSS-only feature: it requires no changes in `core/`, so the stylesheet can
|
|
17
|
+
* be updated independently of the library version and works with releases already in
|
|
18
|
+
* the wild. Everything it needs to hook onto is markup the components already render
|
|
19
|
+
* (see PORTAL_ROOT_SELECTORS).
|
|
20
|
+
*
|
|
21
|
+
* Nothing is added to the scope root itself. A `[data-mds-root]` element is consumer
|
|
22
|
+
* markup, so anything declared on it would compete with the consumer's own layout —
|
|
23
|
+
* notably, a custom-element scope root (`<ui-shell-app data-mds-root>`) needs its own
|
|
24
|
+
* `display`, which is the consumer's call, not this build's.
|
|
25
|
+
*
|
|
26
|
+
* The scope selector is overridable, e.g. to also scope on web-component hosts:
|
|
27
|
+
* MDS_SCOPE_SELECTOR='[data-mds-root], ui-shell-app' npm run build-css-scoped
|
|
28
|
+
* CSS has no tag-name wildcard, so patterned hosts must either be enumerated
|
|
29
|
+
* here or carry the `data-mds-root` attribute.
|
|
30
|
+
*
|
|
31
|
+
* The default css/dist/index.css build is unchanged.
|
|
32
|
+
*/
|
|
33
|
+
import fs from 'node:fs';
|
|
34
|
+
import path from 'node:path';
|
|
35
|
+
import { fileURLToPath } from 'node:url';
|
|
36
|
+
import { createRequire } from 'node:module';
|
|
37
|
+
import postcss from 'postcss';
|
|
38
|
+
|
|
39
|
+
const require = createRequire(import.meta.url);
|
|
40
|
+
const postcssColorMod = require('postcss-color-mod-function');
|
|
41
|
+
const autoprefixer = require('autoprefixer');
|
|
42
|
+
const { sources } = require('./css-sources.cjs');
|
|
43
|
+
|
|
44
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
45
|
+
const cssRoot = path.resolve(__dirname, '..');
|
|
46
|
+
|
|
47
|
+
const SCOPE_SELECTOR = process.env.MDS_SCOPE_SELECTOR || '[data-mds-root]';
|
|
48
|
+
const OUTPUT_FILE = process.env.MDS_SCOPE_OUTPUT || 'mds-scoped.css';
|
|
49
|
+
|
|
50
|
+
/** At-rules whose names live in a global namespace and therefore cannot be scoped. */
|
|
51
|
+
const GLOBAL_AT_RULES = ['keyframes', 'font-face', 'property', 'counter-style'];
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Scope roots for MDS's own portaled overlays — see `portal-roots.cjs` for why each
|
|
55
|
+
* selector looks the way it does. Shared with the test that guards them.
|
|
56
|
+
*/
|
|
57
|
+
const { PORTAL_ROOT_SELECTORS } = require('./portal-roots.cjs');
|
|
58
|
+
|
|
59
|
+
function expandGlobs(patterns) {
|
|
60
|
+
const results = [];
|
|
61
|
+
for (const pattern of patterns) {
|
|
62
|
+
if (pattern.includes('*')) {
|
|
63
|
+
const dir = path.join(cssRoot, path.dirname(pattern));
|
|
64
|
+
const fileGlob = path.basename(pattern);
|
|
65
|
+
if (!fs.existsSync(dir)) continue;
|
|
66
|
+
for (const name of fs.readdirSync(dir).sort()) {
|
|
67
|
+
const full = path.join(dir, name);
|
|
68
|
+
if (!fs.statSync(full).isFile()) continue;
|
|
69
|
+
if (fileGlob === '*.css' && name.endsWith('.css')) results.push(full);
|
|
70
|
+
}
|
|
71
|
+
} else {
|
|
72
|
+
results.push(path.join(cssRoot, pattern));
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
return results;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Splits a selector list on top-level commas.
|
|
80
|
+
*
|
|
81
|
+
* A naive `split(',')` corrupts escaped identifiers — MDS ships class names such as
|
|
82
|
+
* `.MdsGrid-templateColumns--repeat\(1\,\ 1fr\)`, where the comma and space are part
|
|
83
|
+
* of the class name. Backslash escapes, quoted strings, and `()` / `[]` nesting
|
|
84
|
+
* (`:not(a, b)`, `[data-x="a,b"]`) all have to be honoured.
|
|
85
|
+
*/
|
|
86
|
+
function splitSelectorList(selector) {
|
|
87
|
+
const parts = [];
|
|
88
|
+
let current = '';
|
|
89
|
+
let depth = 0;
|
|
90
|
+
let quote = null;
|
|
91
|
+
|
|
92
|
+
for (let i = 0; i < selector.length; i++) {
|
|
93
|
+
const ch = selector[i];
|
|
94
|
+
|
|
95
|
+
if (ch === '\\') {
|
|
96
|
+
current += ch + (selector[i + 1] ?? '');
|
|
97
|
+
i++;
|
|
98
|
+
continue;
|
|
99
|
+
}
|
|
100
|
+
if (quote) {
|
|
101
|
+
current += ch;
|
|
102
|
+
if (ch === quote) quote = null;
|
|
103
|
+
continue;
|
|
104
|
+
}
|
|
105
|
+
if (ch === '"' || ch === "'") {
|
|
106
|
+
quote = ch;
|
|
107
|
+
current += ch;
|
|
108
|
+
continue;
|
|
109
|
+
}
|
|
110
|
+
if (ch === '(' || ch === '[') depth++;
|
|
111
|
+
else if (ch === ')' || ch === ']') depth--;
|
|
112
|
+
else if (ch === ',' && depth === 0) {
|
|
113
|
+
parts.push(current.trim());
|
|
114
|
+
current = '';
|
|
115
|
+
continue;
|
|
116
|
+
}
|
|
117
|
+
current += ch;
|
|
118
|
+
}
|
|
119
|
+
parts.push(current.trim());
|
|
120
|
+
|
|
121
|
+
return parts.filter((part) => part !== '');
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Index at which the first compound selector ends, i.e. the first top-level
|
|
126
|
+
* combinator. Escape- and nesting-aware for the same reasons as
|
|
127
|
+
* {@link splitSelectorList} — `~` in `[attr~=x]` and `+` in `:nth-child(2n+1)`
|
|
128
|
+
* are not combinators, and `\ ` is an escaped space inside an identifier.
|
|
129
|
+
*/
|
|
130
|
+
function firstCompoundEnd(selector) {
|
|
131
|
+
let depth = 0;
|
|
132
|
+
let quote = null;
|
|
133
|
+
|
|
134
|
+
for (let i = 0; i < selector.length; i++) {
|
|
135
|
+
const ch = selector[i];
|
|
136
|
+
|
|
137
|
+
if (ch === '\\') {
|
|
138
|
+
i++;
|
|
139
|
+
continue;
|
|
140
|
+
}
|
|
141
|
+
if (quote) {
|
|
142
|
+
if (ch === quote) quote = null;
|
|
143
|
+
continue;
|
|
144
|
+
}
|
|
145
|
+
if (ch === '"' || ch === "'") {
|
|
146
|
+
quote = ch;
|
|
147
|
+
continue;
|
|
148
|
+
}
|
|
149
|
+
if (ch === '(' || ch === '[') depth++;
|
|
150
|
+
else if (ch === ')' || ch === ']') depth--;
|
|
151
|
+
else if (depth === 0 && (ch === ' ' || ch === '\t' || ch === '\n' || ch === '>' || ch === '+' || ch === '~')) {
|
|
152
|
+
return i;
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
return selector.length;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* Returns a variant of `selector` anchored to the scope root, or `null` when the
|
|
160
|
+
* selector is already root-anchored.
|
|
161
|
+
*
|
|
162
|
+
* Inside `@scope`, a rule is implicitly relative to the scope root — `.Backdrop`
|
|
163
|
+
* behaves as `:scope .Backdrop` and so matches *descendants only*. Overlays that
|
|
164
|
+
* portal to `document.body` carry `data-mds-root` on the portaled element itself,
|
|
165
|
+
* making that element the scope root; without this variant its own rules would
|
|
166
|
+
* never apply. Fusing `:scope` onto the first compound (`:scope.Backdrop`) covers
|
|
167
|
+
* that case, while the original selector keeps covering descendants.
|
|
168
|
+
*
|
|
169
|
+
* Every rule gets a variant, so all rules matching a given scope root are bumped
|
|
170
|
+
* by the same `:scope` (0,1,0) — relative specificity, and therefore the cascade
|
|
171
|
+
* between MDS rules, is preserved.
|
|
172
|
+
*/
|
|
173
|
+
function rootAnchoredVariant(selector) {
|
|
174
|
+
const end = firstCompoundEnd(selector);
|
|
175
|
+
const head = selector.slice(0, end);
|
|
176
|
+
const tail = selector.slice(end);
|
|
177
|
+
|
|
178
|
+
if (!head) return null;
|
|
179
|
+
// `:root` is already remapped to `:scope` by this point.
|
|
180
|
+
if (head.includes(':scope')) return null;
|
|
181
|
+
|
|
182
|
+
// A type selector must remain first in its compound: `div:scope`, not `:scope div`.
|
|
183
|
+
const type = head.match(/^(\*|[a-zA-Z][\w-]*)/);
|
|
184
|
+
if (type) return `${type[0]}:scope${head.slice(type[0].length)}${tail}`;
|
|
185
|
+
|
|
186
|
+
return `:scope${head}${tail}`;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
function isInsideKeyframes(node) {
|
|
190
|
+
for (let parent = node.parent; parent; parent = parent.parent) {
|
|
191
|
+
if (parent.type === 'atrule' && /keyframes$/.test(parent.name)) return true;
|
|
192
|
+
}
|
|
193
|
+
return false;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
function addRootAnchoredVariants(root) {
|
|
197
|
+
root.walkRules((rule) => {
|
|
198
|
+
if (isInsideKeyframes(rule)) return;
|
|
199
|
+
|
|
200
|
+
const parts = splitSelectorList(rule.selector);
|
|
201
|
+
const variants = parts.map(rootAnchoredVariant).filter(Boolean);
|
|
202
|
+
if (variants.length) rule.selector = [...parts, ...variants].join(', ');
|
|
203
|
+
});
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
function hoistGlobalAtRules(cssText) {
|
|
207
|
+
const root = postcss.parse(cssText);
|
|
208
|
+
const hoisted = [];
|
|
209
|
+
root.each((node) => {
|
|
210
|
+
if (node.type === 'atrule' && GLOBAL_AT_RULES.includes(node.name)) {
|
|
211
|
+
hoisted.push(node);
|
|
212
|
+
node.remove();
|
|
213
|
+
}
|
|
214
|
+
});
|
|
215
|
+
return { remaining: root, hoisted };
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
/**
|
|
219
|
+
* Document-level selectors have no meaning inside `@scope`: the scope root is the
|
|
220
|
+
* closest thing to a document root, so tokens declared on `:root` are remapped to
|
|
221
|
+
* `:scope`. Without this, every custom-property reference in the bundle would
|
|
222
|
+
* resolve to nothing.
|
|
223
|
+
*/
|
|
224
|
+
function remapRootSelectors(root) {
|
|
225
|
+
root.walkRules((rule) => {
|
|
226
|
+
const parts = splitSelectorList(rule.selector);
|
|
227
|
+
let changed = false;
|
|
228
|
+
|
|
229
|
+
const next = parts.map((sel) => {
|
|
230
|
+
if (sel === ':root' || sel === 'html' || sel === 'body' || sel === ':host') {
|
|
231
|
+
changed = true;
|
|
232
|
+
return ':scope';
|
|
233
|
+
}
|
|
234
|
+
if (/^(body|html)([.#[:\s].*)?$/.test(sel)) {
|
|
235
|
+
changed = true;
|
|
236
|
+
return sel.replace(/^(body|html)/, ':scope');
|
|
237
|
+
}
|
|
238
|
+
return sel;
|
|
239
|
+
});
|
|
240
|
+
|
|
241
|
+
if (changed) rule.selector = next.join(', ');
|
|
242
|
+
});
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
/**
|
|
246
|
+
* A global-namespace at-rule nested inside e.g. `@media` is not reachable by the
|
|
247
|
+
* top-level hoist and would be silently dropped by the browser once scoped.
|
|
248
|
+
* Fail loudly instead of shipping a bundle with dead animations.
|
|
249
|
+
*/
|
|
250
|
+
function assertNoGlobalAtRulesRemain(root) {
|
|
251
|
+
const stranded = [];
|
|
252
|
+
root.walkAtRules((atRule) => {
|
|
253
|
+
if (GLOBAL_AT_RULES.includes(atRule.name)) {
|
|
254
|
+
stranded.push(`@${atRule.name} ${atRule.params}`.trim());
|
|
255
|
+
}
|
|
256
|
+
});
|
|
257
|
+
if (stranded.length) {
|
|
258
|
+
throw new Error(
|
|
259
|
+
`Cannot scope ${stranded.length} global at-rule(s) nested inside another at-rule; ` +
|
|
260
|
+
`move them to the top level of their source file:\n ${stranded.join('\n ')}`
|
|
261
|
+
);
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
/**
|
|
266
|
+
* Returns the `@scope` prelude: the consumer's opt-in selector plus MDS's own portal
|
|
267
|
+
* roots.
|
|
268
|
+
*
|
|
269
|
+
* The portal roots are unconditional. They target MDS-owned markup rather than anything
|
|
270
|
+
* the consumer controls, so no consumer selector can subsume them — and omitting them
|
|
271
|
+
* would leave every modal, popper and backdrop outside all scopes, rendering unstyled.
|
|
272
|
+
*/
|
|
273
|
+
function buildScopePrelude(consumerSelector) {
|
|
274
|
+
return [...splitSelectorList(consumerSelector), ...PORTAL_ROOT_SELECTORS].join(', ');
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
async function build() {
|
|
278
|
+
const files = expandGlobs(sources).filter((f) => fs.existsSync(f));
|
|
279
|
+
const seen = new Set();
|
|
280
|
+
const unique = files.filter((f) => (seen.has(f) ? false : (seen.add(f), true)));
|
|
281
|
+
|
|
282
|
+
const concatenated = unique
|
|
283
|
+
.map((f) => `/* Source: ${path.relative(cssRoot, f)} */\n${fs.readFileSync(f, 'utf8')}`)
|
|
284
|
+
.join('\n\n');
|
|
285
|
+
|
|
286
|
+
const processed = await postcss([postcssColorMod(), autoprefixer()]).process(concatenated, {
|
|
287
|
+
from: undefined,
|
|
288
|
+
});
|
|
289
|
+
|
|
290
|
+
const { remaining, hoisted } = hoistGlobalAtRules(processed.css);
|
|
291
|
+
assertNoGlobalAtRulesRemain(remaining);
|
|
292
|
+
remapRootSelectors(remaining);
|
|
293
|
+
addRootAnchoredVariants(remaining);
|
|
294
|
+
|
|
295
|
+
const prelude = buildScopePrelude(SCOPE_SELECTOR);
|
|
296
|
+
const outRoot = postcss.root();
|
|
297
|
+
for (const node of hoisted) outRoot.append(node.clone());
|
|
298
|
+
outRoot.append(postcss.parse(`@scope (${prelude}) {\n${remaining.toString()}\n}`));
|
|
299
|
+
|
|
300
|
+
const outDir = path.join(cssRoot, 'dist');
|
|
301
|
+
fs.mkdirSync(outDir, { recursive: true });
|
|
302
|
+
const outPath = path.join(outDir, OUTPUT_FILE);
|
|
303
|
+
fs.writeFileSync(outPath, outRoot.toString());
|
|
304
|
+
console.log(
|
|
305
|
+
`Wrote ${path.relative(cssRoot, outPath)} (${fs.statSync(outPath).size} bytes) ` +
|
|
306
|
+
`from ${unique.length} sources, scoped to \`${prelude}\``
|
|
307
|
+
);
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
build().catch((err) => {
|
|
311
|
+
console.error(err);
|
|
312
|
+
process.exit(1);
|
|
313
|
+
});
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Single source of truth for the CSS build order.
|
|
3
|
+
*
|
|
4
|
+
* Consumed by both `css/gulpfile.js` (default `dist/index.css`) and
|
|
5
|
+
* `css/scripts/build-scoped.mjs` (scoped `dist/mds-scoped.css`) so the two
|
|
6
|
+
* bundles cannot drift apart when a new source directory is added.
|
|
7
|
+
*
|
|
8
|
+
* Paths are globs relative to the `css/` directory.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
const materialIcons = './material-design-icons/iconfont/material-icons.css';
|
|
12
|
+
const materialFont = './material-design-icons/iconfont/*.{ttf,otf,woff2}';
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Typography is listed explicitly ahead of `components/*` so that, on equal
|
|
16
|
+
* specificity, component rules win over the base typography rules. These files
|
|
17
|
+
* also match the `components/*` glob below; the duplicate is de-duplicated by
|
|
18
|
+
* path (glob-stream in gulp, an explicit Set in the scoped build), keeping the
|
|
19
|
+
* earlier position.
|
|
20
|
+
*/
|
|
21
|
+
const typographyCssPath = [
|
|
22
|
+
'./src/components/text.module.css',
|
|
23
|
+
'./src/components/heading.module.css',
|
|
24
|
+
'./src/components/label.module.css',
|
|
25
|
+
'./src/components/caption.module.css',
|
|
26
|
+
'./src/components/subheading.module.css',
|
|
27
|
+
];
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Cascade order, shared by every bundle. The scoped bundle deliberately uses the
|
|
31
|
+
* same list with nothing appended — it is the same CSS, only wrapped in `@scope`.
|
|
32
|
+
*/
|
|
33
|
+
const sources = [
|
|
34
|
+
'./src/tokens/*.css',
|
|
35
|
+
'./src/variables/*.css',
|
|
36
|
+
materialIcons,
|
|
37
|
+
'./src/core/*.css',
|
|
38
|
+
...typographyCssPath,
|
|
39
|
+
'./src/components/*.css',
|
|
40
|
+
'./src/ai-components/*.css',
|
|
41
|
+
'./src/utils/*.css',
|
|
42
|
+
];
|
|
43
|
+
|
|
44
|
+
module.exports = {
|
|
45
|
+
materialIcons,
|
|
46
|
+
materialFont,
|
|
47
|
+
typographyCssPath,
|
|
48
|
+
sources,
|
|
49
|
+
};
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Scope roots for MDS's own portaled overlays, always added to the scoped bundle's
|
|
3
|
+
* `@scope` prelude by `build-scoped.mjs`.
|
|
4
|
+
*
|
|
5
|
+
* Every overlay listed here is portaled to `document.body` by `ReactDOM.createPortal`,
|
|
6
|
+
* escaping the consumer's `[data-mds-root]` container. Inside `@scope`, a rule like
|
|
7
|
+
* `.Backdrop` is implicitly `:scope .Backdrop` and so matches descendants only, so these
|
|
8
|
+
* elements have to be scope roots in their own right — and there is no MDS-owned ancestor
|
|
9
|
+
* to hang that on. Their own rules then match via the root-anchored selector variants the
|
|
10
|
+
* build adds (`.Backdrop, :scope.Backdrop`).
|
|
11
|
+
*
|
|
12
|
+
* Each selector targets markup the components *already* render. That is what keeps the
|
|
13
|
+
* scoped stylesheet a CSS-only feature: nothing in `core/` changes, so the CSS can be
|
|
14
|
+
* updated independently of the library version and works with releases already shipped.
|
|
15
|
+
*
|
|
16
|
+
* Each is also qualified so it cannot collide with a host app's own class names:
|
|
17
|
+
*
|
|
18
|
+
* - `body >` — every one of these lands as a direct child of `<body>`, so a colliding
|
|
19
|
+
* host class would have to be at body level too.
|
|
20
|
+
* - `:has([data-layer])` — `data-layer` is an MDS-only attribute, present on all five
|
|
21
|
+
* portal roots. `.Overlay-wrapper` is a generic enough name that a host app could
|
|
22
|
+
* plausibly reuse it, so the class alone is not sufficient. The `:has()` matches a
|
|
23
|
+
* descendant rather than a child because Modal wraps its container in `OutsideClick`
|
|
24
|
+
* when `backdropClose` is set.
|
|
25
|
+
* - `data-test='DesignSystem-*'` — already namespaced, so `body >` is enough.
|
|
26
|
+
*
|
|
27
|
+
* This file is the single source of truth, shared with
|
|
28
|
+
* `core/utils/__tests__/scopedPortalRoots.test.tsx`, which renders each overlay and
|
|
29
|
+
* asserts these selectors still match — so the coupling to component markup cannot rot
|
|
30
|
+
* silently.
|
|
31
|
+
*/
|
|
32
|
+
const PORTAL_ROOT_SELECTORS = [
|
|
33
|
+
/** Modal, Sidesheet and FullscreenModal all portal into this shared container. */
|
|
34
|
+
'body > .Overlay-wrapper:has([data-layer])',
|
|
35
|
+
/** Popover and everything built on it: Tooltip, Dropdown, Menu, DateRangePicker, … */
|
|
36
|
+
"body > [data-test='DesignSystem-Popover']",
|
|
37
|
+
/** Backdrop, including the one Modal/Sidesheet render. */
|
|
38
|
+
"body > [data-test='DesignSystem-Backdrop']",
|
|
39
|
+
/**
|
|
40
|
+
* Listbox drag ghost. No `data-test` exists on it, so `body >` is the only qualifier
|
|
41
|
+
* available; the BEM class name is at least MDS-specific. Worth tightening with a
|
|
42
|
+
* `data-test` the next time component changes ship.
|
|
43
|
+
*/
|
|
44
|
+
'body > .Listbox-item--draggable',
|
|
45
|
+
];
|
|
46
|
+
|
|
47
|
+
module.exports = { PORTAL_ROOT_SELECTORS };
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
@keyframes rotate {
|
|
1
|
+
@keyframes mds-rotate {
|
|
2
2
|
0% {
|
|
3
3
|
transform: rotate(0deg);
|
|
4
4
|
}
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
}
|
|
8
8
|
}
|
|
9
9
|
|
|
10
|
-
@keyframes spin {
|
|
10
|
+
@keyframes mds-spin {
|
|
11
11
|
0% {
|
|
12
12
|
stroke-dasharray: 1, 200;
|
|
13
13
|
stroke-dashoffset: 0;
|
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
}
|
|
24
24
|
|
|
25
25
|
.Spinner {
|
|
26
|
-
animation: rotate 2s linear infinite;
|
|
26
|
+
animation: mds-rotate 2s linear infinite;
|
|
27
27
|
}
|
|
28
28
|
|
|
29
29
|
.Spinner--xsmall {
|
|
@@ -50,7 +50,7 @@
|
|
|
50
50
|
stroke-linecap: round;
|
|
51
51
|
stroke-dasharray: 1 200;
|
|
52
52
|
stroke-dashoffset: 0;
|
|
53
|
-
animation: spin 1.5s ease-in-out infinite;
|
|
53
|
+
animation: mds-spin 1.5s ease-in-out infinite;
|
|
54
54
|
}
|
|
55
55
|
|
|
56
56
|
.Circle--primary {
|