igniteui-angular 22.2.0-rc.1 → 22.2.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 +1 -1
- package/button-group/README.md +42 -9
- package/calendar/README.md +30 -20
- package/card/README.md +1 -1
- package/fesm2022/igniteui-angular-accordion.mjs +7 -7
- package/fesm2022/igniteui-angular-action-strip.mjs +11 -22
- package/fesm2022/igniteui-angular-action-strip.mjs.map +1 -1
- package/fesm2022/igniteui-angular-avatar.mjs +7 -7
- package/fesm2022/igniteui-angular-badge.mjs +7 -7
- package/fesm2022/igniteui-angular-banner.mjs +10 -10
- package/fesm2022/igniteui-angular-bottom-nav.mjs +22 -22
- package/fesm2022/igniteui-angular-button-group.mjs +32 -35
- package/fesm2022/igniteui-angular-button-group.mjs.map +1 -1
- package/fesm2022/igniteui-angular-calendar.mjs +132 -180
- package/fesm2022/igniteui-angular-calendar.mjs.map +1 -1
- package/fesm2022/igniteui-angular-card.mjs +47 -58
- package/fesm2022/igniteui-angular-card.mjs.map +1 -1
- package/fesm2022/igniteui-angular-carousel.mjs +22 -22
- package/fesm2022/igniteui-angular-chat-extras.mjs +6 -6
- package/fesm2022/igniteui-angular-chat.mjs +12 -12
- package/fesm2022/igniteui-angular-checkbox.mjs +7 -7
- package/fesm2022/igniteui-angular-chips.mjs +10 -10
- package/fesm2022/igniteui-angular-combo.mjs +66 -60
- package/fesm2022/igniteui-angular-combo.mjs.map +1 -1
- package/fesm2022/igniteui-angular-core.mjs +107 -79
- package/fesm2022/igniteui-angular-core.mjs.map +1 -1
- package/fesm2022/igniteui-angular-date-picker.mjs +38 -38
- package/fesm2022/igniteui-angular-dialog.mjs +13 -13
- package/fesm2022/igniteui-angular-directives.mjs +194 -194
- package/fesm2022/igniteui-angular-drop-down.mjs +29 -29
- package/fesm2022/igniteui-angular-expansion-panel.mjs +28 -28
- package/fesm2022/igniteui-angular-grids-core.mjs +737 -661
- package/fesm2022/igniteui-angular-grids-core.mjs.map +1 -1
- package/fesm2022/igniteui-angular-grids-grid.mjs +69 -58
- package/fesm2022/igniteui-angular-grids-grid.mjs.map +1 -1
- package/fesm2022/igniteui-angular-grids-hierarchical-grid.mjs +37 -37
- package/fesm2022/igniteui-angular-grids-lite.mjs +25 -17
- package/fesm2022/igniteui-angular-grids-lite.mjs.map +1 -1
- package/fesm2022/igniteui-angular-grids-pivot-grid.mjs +92 -81
- package/fesm2022/igniteui-angular-grids-pivot-grid.mjs.map +1 -1
- package/fesm2022/igniteui-angular-grids-tree-grid.mjs +55 -55
- package/fesm2022/igniteui-angular-icon.mjs +10 -10
- package/fesm2022/igniteui-angular-input-group.mjs +25 -25
- package/fesm2022/igniteui-angular-list.mjs +40 -40
- package/fesm2022/igniteui-angular-navbar.mjs +13 -13
- package/fesm2022/igniteui-angular-navigation-drawer.mjs +43 -38
- package/fesm2022/igniteui-angular-navigation-drawer.mjs.map +1 -1
- package/fesm2022/igniteui-angular-paginator.mjs +19 -19
- package/fesm2022/igniteui-angular-progressbar.mjs +19 -19
- package/fesm2022/igniteui-angular-query-builder.mjs +22 -22
- package/fesm2022/igniteui-angular-radio.mjs +25 -21
- package/fesm2022/igniteui-angular-radio.mjs.map +1 -1
- package/fesm2022/igniteui-angular-select.mjs +25 -25
- package/fesm2022/igniteui-angular-simple-combo.mjs +9 -9
- package/fesm2022/igniteui-angular-simple-combo.mjs.map +1 -1
- package/fesm2022/igniteui-angular-slider.mjs +28 -28
- package/fesm2022/igniteui-angular-snackbar.mjs +7 -7
- package/fesm2022/igniteui-angular-splitter.mjs +13 -13
- package/fesm2022/igniteui-angular-stepper.mjs +34 -34
- package/fesm2022/igniteui-angular-switch.mjs +7 -7
- package/fesm2022/igniteui-angular-tabs.mjs +34 -34
- package/fesm2022/igniteui-angular-time-picker.mjs +19 -19
- package/fesm2022/igniteui-angular-toast.mjs +7 -7
- package/fesm2022/igniteui-angular-tree.mjs +28 -28
- package/fesm2022/igniteui-angular-virtual-scroll.mjs +497 -145
- package/fesm2022/igniteui-angular-virtual-scroll.mjs.map +1 -1
- package/migrations/common/UpdateChanges.d.ts +25 -5
- package/migrations/common/UpdateChanges.js +218 -34
- package/migrations/common/UpdateChanges.spec.js +763 -0
- package/migrations/migration-collection.json +1 -1
- package/migrations/update-22_2_0/index.js +145 -0
- package/migrations/update-22_2_0/index.spec.js +133 -0
- package/navigation-drawer/README.md +1 -1
- package/package.json +4 -4
- package/schematics/tsconfig.tsbuildinfo +1 -1
- package/skills/igniteui-angular-components/SKILL.md +9 -5
- package/skills/igniteui-angular-components/references/form-controls.md +1 -1
- package/skills/igniteui-angular-components/references/mcp-setup.md +14 -2
- package/skills/igniteui-angular-figma-to-app/SKILL.md +112 -525
- package/skills/igniteui-angular-figma-to-app/references/asset-extraction.md +49 -75
- package/skills/igniteui-angular-figma-to-app/references/design-provenance.md +201 -0
- package/skills/igniteui-angular-figma-to-app/references/design-token-bridge.md +175 -52
- package/skills/igniteui-angular-figma-to-app/references/figma-component-map.md +153 -99
- package/skills/igniteui-angular-figma-to-app/references/figma-exploration.md +226 -0
- package/skills/igniteui-angular-figma-to-app/references/mcp-setup.md +71 -105
- package/skills/igniteui-angular-figma-to-app/references/project-setup.md +63 -0
- package/skills/igniteui-angular-figma-to-app/references/theme-generation.md +120 -0
- package/skills/igniteui-angular-figma-to-app/references/validation-patterns.md +48 -50
- package/skills/igniteui-angular-generate-from-image-design/SKILL.md +11 -7
- package/skills/igniteui-angular-grids/SKILL.md +7 -3
- package/skills/igniteui-angular-grids/references/editing.md +1 -2
- package/skills/igniteui-angular-grids/references/grid-migration.md +1 -1
- package/skills/igniteui-angular-theming/SKILL.md +9 -5
- package/skills/igniteui-angular-theming/references/mcp-setup.md +12 -2
- package/types/igniteui-angular-button-group.d.ts +49 -34
- package/types/igniteui-angular-calendar.d.ts +33 -50
- package/types/igniteui-angular-card.d.ts +12 -17
- package/types/igniteui-angular-combo.d.ts +6 -0
- package/types/igniteui-angular-core.d.ts +10 -2
- package/types/igniteui-angular-grids-core.d.ts +48 -4
- package/types/igniteui-angular-grids-grid.d.ts +4 -0
- package/types/igniteui-angular-grids-lite.d.ts +5 -1
- package/types/igniteui-angular-grids-pivot-grid.d.ts +5 -1
- package/types/igniteui-angular-navigation-drawer.d.ts +1 -0
- package/types/igniteui-angular-radio.d.ts +5 -0
- package/types/igniteui-angular-virtual-scroll.d.ts +41 -13
- package/virtual-scroll/README.md +32 -1
|
@@ -48,6 +48,8 @@ const util_1 = require("./util");
|
|
|
48
48
|
const project_service_container_1 = require("./project-service-container");
|
|
49
49
|
const TSCONFIG_PATH = 'tsconfig.json';
|
|
50
50
|
const URL_TOKEN = 'url(';
|
|
51
|
+
/** Dependency and build output folders never searched for stylesheets. */
|
|
52
|
+
const SKIPPED_STYLE_DIRS = ['node_modules', 'dist', '.angular', '.git'];
|
|
51
53
|
var InputPropertyType;
|
|
52
54
|
(function (InputPropertyType) {
|
|
53
55
|
InputPropertyType["EVAL"] = "eval";
|
|
@@ -101,17 +103,25 @@ class UpdateChanges {
|
|
|
101
103
|
/** Sass (both .scss and .sass) files in the project being updated. */
|
|
102
104
|
get sassFiles() {
|
|
103
105
|
if (!this._sassFiles.length) {
|
|
104
|
-
//
|
|
106
|
+
// stylesheets can sit anywhere in the workspace, not only under sourceRoot
|
|
105
107
|
// also ignore schematics `styleext` as Sass can be used regardless
|
|
106
|
-
|
|
107
|
-
this.sourceDirsVisitor((fulPath, entry) => {
|
|
108
|
-
if (fulPath.endsWith('.scss') || fulPath.endsWith('.sass')) {
|
|
109
|
-
this._sassFiles.push(entry.path);
|
|
110
|
-
}
|
|
111
|
-
}, sourceDirs);
|
|
108
|
+
this.collectSassFiles(this.host.getDir('/'), this._sassFiles);
|
|
112
109
|
}
|
|
113
110
|
return this._sassFiles;
|
|
114
111
|
}
|
|
112
|
+
/** Collects the Sass files under a folder, skipping dependency and build output folders. */
|
|
113
|
+
collectSassFiles(dir, files) {
|
|
114
|
+
for (const name of dir.subfiles) {
|
|
115
|
+
if (name.endsWith('.scss') || name.endsWith('.sass')) {
|
|
116
|
+
files.push(dir.file(name).path);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
for (const name of dir.subdirs) {
|
|
120
|
+
if (!SKIPPED_STYLE_DIRS.includes(name)) {
|
|
121
|
+
this.collectSassFiles(dir.dir(name), files);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
}
|
|
115
125
|
get service() {
|
|
116
126
|
if (!this._service) {
|
|
117
127
|
this._service = (0, tsUtils_1.getLanguageService)(this.tsFiles, this.host);
|
|
@@ -372,7 +382,7 @@ class UpdateChanges {
|
|
|
372
382
|
continue;
|
|
373
383
|
}
|
|
374
384
|
/** owner-func:( * ) */
|
|
375
|
-
const calls = this.findFunctionCalls(fileContent, change.owner);
|
|
385
|
+
const calls = this.findFunctionCalls(fileContent, change.owner, entryPath.endsWith('.sass'));
|
|
376
386
|
// rewrite back to front so the collected indices stay valid
|
|
377
387
|
for (const call of calls.reverse()) {
|
|
378
388
|
const rawBody = fileContent.substring(call.bodyStart, call.bodyEnd);
|
|
@@ -381,26 +391,40 @@ class UpdateChanges {
|
|
|
381
391
|
}
|
|
382
392
|
const name = (0, util_1.escapeRegExp)(change.name);
|
|
383
393
|
const replaceWith = change.replaceWith ? (0, util_1.escapeRegExp)(change.replaceWith) : undefined;
|
|
384
|
-
|
|
385
|
-
const
|
|
394
|
+
// both are matched at the argument's code start; Sass allows whitespace before the colon
|
|
395
|
+
const reg = new RegExp(String.raw `^${name}\s*:`);
|
|
396
|
+
const existing = new RegExp(String.raw `^${replaceWith}\s*:`);
|
|
386
397
|
// keep whatever sits in front of the closing bracket so the formatting is preserved
|
|
387
398
|
const trailing = /\s*$/.exec(rawBody).pop();
|
|
388
399
|
const body = rawBody.substring(0, rawBody.length - trailing.length);
|
|
389
|
-
|
|
390
|
-
params =
|
|
391
|
-
|
|
392
|
-
|
|
400
|
+
const eol = fileContent.includes('\r\n') ? '\r\n' : '\n';
|
|
401
|
+
const params = this.splitFunctionProps(body);
|
|
402
|
+
const codeStarts = params.map(param => this.leadingCommentsEnd(param));
|
|
403
|
+
// renaming onto a name the call already passes would make it a duplicate argument
|
|
404
|
+
const duplicate = !!replaceWith && params.some((param, i) => existing.test(param.substring(codeStarts[i])));
|
|
405
|
+
// comments in front of a removed argument usually describe the previous one, so they are kept
|
|
406
|
+
let carried = '';
|
|
407
|
+
const kept = params.reduce((arr, param, i) => {
|
|
408
|
+
const codeStart = codeStarts[i];
|
|
409
|
+
if (reg.test(param.substring(codeStart))) {
|
|
393
410
|
if (!change.remove && !duplicate) {
|
|
394
|
-
|
|
411
|
+
// the name sits at the code start, so renaming must not search the comments
|
|
412
|
+
arr.push(carried + this.commentBreak(carried, param, eol) + param.substring(0, codeStart)
|
|
413
|
+
+ change.replaceWith + param.substring(codeStart + change.name.length));
|
|
414
|
+
carried = '';
|
|
415
|
+
}
|
|
416
|
+
else {
|
|
417
|
+
carried += param.substring(0, codeStart).trimEnd();
|
|
395
418
|
}
|
|
396
419
|
}
|
|
397
420
|
else {
|
|
398
|
-
arr.push(param);
|
|
421
|
+
arr.push(carried + this.commentBreak(carried, param, eol) + param);
|
|
422
|
+
carried = '';
|
|
399
423
|
}
|
|
400
424
|
return arr;
|
|
401
425
|
}, []);
|
|
402
426
|
fileContent = fileContent.substring(0, call.bodyStart)
|
|
403
|
-
+
|
|
427
|
+
+ kept.join(',') + carried + this.commentBreak(carried, trailing, eol)
|
|
404
428
|
+ trailing
|
|
405
429
|
+ fileContent.substring(call.bodyEnd);
|
|
406
430
|
overwrite = true;
|
|
@@ -413,21 +437,29 @@ class UpdateChanges {
|
|
|
413
437
|
/**
|
|
414
438
|
* Returns the argument list boundaries of every top-level `owner(...)` call in the content.
|
|
415
439
|
* Strings, comments and `url()` tokens are scanned over, so an `owner(` that is only mentioned
|
|
416
|
-
* in one of those is not taken for a call, and
|
|
417
|
-
*
|
|
440
|
+
* in one of those is not taken for a call, and anything the stylesheet declares itself is left
|
|
441
|
+
* alone. The brackets are tracked too, so a call nested in another one -
|
|
418
442
|
* `@include scrollbar(scrollbar-theme($sb-size: 6px))` - reports its own closing bracket
|
|
419
443
|
* rather than the one of the call surrounding it.
|
|
444
|
+
*
|
|
445
|
+
* An owner is matched anywhere in a name, because migrations rely on it: `circular-theme` is
|
|
446
|
+
* how `progress-circular-theme` is addressed, and `theme` covers every `*-theme` mixin.
|
|
420
447
|
*/
|
|
421
|
-
findFunctionCalls(content, owner) {
|
|
448
|
+
findFunctionCalls(content, owner, indented) {
|
|
422
449
|
const calls = [];
|
|
423
450
|
const opening = `${owner}(`;
|
|
451
|
+
const code = this.sassCode(content);
|
|
452
|
+
// Earlier changes can move both calls and scope boundaries, so collect them together.
|
|
453
|
+
const scopes = this.declarationScopes(content, code, indented);
|
|
454
|
+
let brackets = 0;
|
|
424
455
|
for (let i = 0; i < content.length; i++) {
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
456
|
+
if (code[i] === '(' || code[i] === '[') {
|
|
457
|
+
brackets++;
|
|
458
|
+
}
|
|
459
|
+
else if (code[i] === ')' || code[i] === ']') {
|
|
460
|
+
brackets--;
|
|
429
461
|
}
|
|
430
|
-
if (!
|
|
462
|
+
if (!code.startsWith(opening, i) || this.isOwnMember(code, i, owner, scopes, indented && !brackets)) {
|
|
431
463
|
continue;
|
|
432
464
|
}
|
|
433
465
|
const bodyStart = i + opening.length;
|
|
@@ -467,21 +499,173 @@ class UpdateChanges {
|
|
|
467
499
|
}
|
|
468
500
|
return -1;
|
|
469
501
|
}
|
|
502
|
+
/** Collects declarations within their brace or indentation scopes in code with non-code masked. */
|
|
503
|
+
declarationScopes(content, code, indented) {
|
|
504
|
+
const scopes = [];
|
|
505
|
+
const stack = [];
|
|
506
|
+
let brackets = 0;
|
|
507
|
+
let pendingDeclaration;
|
|
508
|
+
const enterScope = (start, indentation = 0) => {
|
|
509
|
+
const scope = {
|
|
510
|
+
start, end: code.length, mixins: new Map(), functions: new Map(), declarations: []
|
|
511
|
+
};
|
|
512
|
+
scopes.push(scope);
|
|
513
|
+
// Braces in an interpolated default argument do not start the declaration's body.
|
|
514
|
+
const declaration = brackets ? undefined : pendingDeclaration;
|
|
515
|
+
stack.push({ scope, indentation, declaration });
|
|
516
|
+
if (declaration) {
|
|
517
|
+
pendingDeclaration = undefined;
|
|
518
|
+
}
|
|
519
|
+
};
|
|
520
|
+
const leaveScope = (end) => {
|
|
521
|
+
const { scope, declaration } = stack.pop();
|
|
522
|
+
scope.end = end;
|
|
523
|
+
if (declaration) {
|
|
524
|
+
declaration.end = end;
|
|
525
|
+
}
|
|
526
|
+
};
|
|
527
|
+
enterScope(0);
|
|
528
|
+
const declaration = /(?:@(mixin|function)\s+|=\s*)([\w-]+)/y;
|
|
529
|
+
for (let i = 0; i < code.length; i++) {
|
|
530
|
+
// Indentation inside a multiline argument/list is not a new Sass scope.
|
|
531
|
+
if (indented && !brackets && (i === 0 || code[i - 1] === '\n')) {
|
|
532
|
+
const lineEnd = code.indexOf('\n', i);
|
|
533
|
+
const line = code.substring(i, lineEnd === -1 ? code.length : lineEnd);
|
|
534
|
+
if (line.trim()) {
|
|
535
|
+
// Masked comments are whitespace too, but are not part of the source indentation.
|
|
536
|
+
const indentation = /^[ \t]*/.exec(content.substring(i))[0].length;
|
|
537
|
+
// An indented mixin may have defaults but no body.
|
|
538
|
+
if (pendingDeclaration && indentation <= stack[stack.length - 1].indentation) {
|
|
539
|
+
pendingDeclaration.end = i;
|
|
540
|
+
pendingDeclaration = undefined;
|
|
541
|
+
}
|
|
542
|
+
while (stack.length > 1 && indentation < stack[stack.length - 1].indentation) {
|
|
543
|
+
leaveScope(i);
|
|
544
|
+
}
|
|
545
|
+
if (indentation > stack[stack.length - 1].indentation) {
|
|
546
|
+
enterScope(i, indentation);
|
|
547
|
+
}
|
|
548
|
+
}
|
|
549
|
+
}
|
|
550
|
+
const char = code[i];
|
|
551
|
+
if (!indented && char === '{') {
|
|
552
|
+
enterScope(i);
|
|
553
|
+
}
|
|
554
|
+
else if (!indented && char === '}' && stack.length > 1) {
|
|
555
|
+
leaveScope(i);
|
|
556
|
+
}
|
|
557
|
+
else if (char === '(' || char === '[') {
|
|
558
|
+
brackets++;
|
|
559
|
+
}
|
|
560
|
+
else if (char === ')' || char === ']') {
|
|
561
|
+
brackets--;
|
|
562
|
+
}
|
|
563
|
+
else if (char === '@' || (indented && !brackets && char === '='
|
|
564
|
+
&& this.keywordBefore(code, i + 1, true) === '@mixin')) {
|
|
565
|
+
declaration.lastIndex = i;
|
|
566
|
+
const match = declaration.exec(code);
|
|
567
|
+
if (match) {
|
|
568
|
+
const scope = stack[stack.length - 1].scope;
|
|
569
|
+
// Sass treats underscores and hyphens as the same identifier character.
|
|
570
|
+
const members = scope[match[1] === 'function' ? 'functions' : 'mixins'];
|
|
571
|
+
const name = match[2].replace(/_/g, '-');
|
|
572
|
+
if (!members.has(name)) {
|
|
573
|
+
members.set(name, i);
|
|
574
|
+
}
|
|
575
|
+
pendingDeclaration = { start: i, end: code.length };
|
|
576
|
+
scope.declarations.push(pendingDeclaration);
|
|
577
|
+
i = declaration.lastIndex - 1;
|
|
578
|
+
}
|
|
579
|
+
}
|
|
580
|
+
}
|
|
581
|
+
return scopes;
|
|
582
|
+
}
|
|
470
583
|
/**
|
|
471
|
-
* Tells whether the
|
|
472
|
-
*
|
|
584
|
+
* Tells whether the `owner` at `index` is part of a member the stylesheet declares itself. A
|
|
585
|
+
* user's own `app-grid-summary-theme` ends with a theme function name, so without this its
|
|
586
|
+
* declaration would lose a parameter its body reads.
|
|
587
|
+
*
|
|
588
|
+
* Only this stylesheet is read. Telling which member a call loaded from another one refers to
|
|
589
|
+
* needs the module rules of `@use` and `@forward` - re-exports, `hide` and `as prefix-*` - so a
|
|
590
|
+
* member of the app that another stylesheet declares is still taken for the Ignite UI one.
|
|
473
591
|
*/
|
|
474
|
-
|
|
475
|
-
let
|
|
592
|
+
isOwnMember(content, index, owner, scopes, indented) {
|
|
593
|
+
let start = index;
|
|
594
|
+
while (start > 0 && /[\w-]/.test(content[start - 1])) {
|
|
595
|
+
start--;
|
|
596
|
+
}
|
|
597
|
+
// a namespace holds the members of another stylesheet, never the ones declared here
|
|
598
|
+
if (content[start - 1] === '.') {
|
|
599
|
+
return false;
|
|
600
|
+
}
|
|
601
|
+
const keyword = this.keywordBefore(content, start, indented);
|
|
602
|
+
if (keyword === '@mixin' || keyword === '@function') {
|
|
603
|
+
// a declaration, whose body reads the parameters it lists
|
|
604
|
+
return true;
|
|
605
|
+
}
|
|
606
|
+
const name = content.substring(start, index + owner.length).replace(/_/g, '-');
|
|
607
|
+
// Bodies and defaults can run after a later declaration becomes available. Keep their
|
|
608
|
+
// existing protection; declaration order is conclusive only for immediate calls.
|
|
609
|
+
const deferred = scopes.some(scope => scope.declarations.some(member => member.start <= index && index < member.end));
|
|
610
|
+
// a mixin and a function of one name are separate members, so only the called one shadows
|
|
611
|
+
return scopes.some(scope => {
|
|
612
|
+
const declaredAt = (keyword === '@include' ? scope.mixins : scope.functions).get(name);
|
|
613
|
+
return scope.start <= index && index < scope.end && declaredAt !== undefined
|
|
614
|
+
&& (deferred || declaredAt <= index);
|
|
615
|
+
});
|
|
616
|
+
}
|
|
617
|
+
/** The at-rule before a member name, or indented shorthand when outside an expression. */
|
|
618
|
+
keywordBefore(content, start, indented) {
|
|
619
|
+
let end = start;
|
|
476
620
|
while (end > 0 && /\s/.test(content[end - 1])) {
|
|
477
621
|
end--;
|
|
478
622
|
}
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
623
|
+
// Shorthand starts a statement. Unlike '=', '+' must be adjacent to the mixin name.
|
|
624
|
+
const shorthand = content[end - 1];
|
|
625
|
+
if (indented && (shorthand === '=' || (shorthand === '+' && end === start))
|
|
626
|
+
&& !content.substring(content.lastIndexOf('\n', end - 1) + 1, end - 1).trim()) {
|
|
627
|
+
return shorthand === '=' ? '@mixin' : '@include';
|
|
482
628
|
}
|
|
483
|
-
|
|
484
|
-
|
|
629
|
+
let keywordStart = end;
|
|
630
|
+
while (keywordStart > 0 && /[\w@-]/.test(content[keywordStart - 1])) {
|
|
631
|
+
keywordStart--;
|
|
632
|
+
}
|
|
633
|
+
return content.substring(keywordStart, end);
|
|
634
|
+
}
|
|
635
|
+
/** Masks non-code with whitespace, preserving offsets, line endings and the original stylesheet. */
|
|
636
|
+
sassCode(content) {
|
|
637
|
+
const parts = [];
|
|
638
|
+
let start = 0;
|
|
639
|
+
for (let i = 0; i < content.length; i++) {
|
|
640
|
+
const nonCodeEnd = this.skipNonCode(content, i);
|
|
641
|
+
if (nonCodeEnd !== -1) {
|
|
642
|
+
parts.push(content.substring(start, i), content.substring(i, nonCodeEnd + 1).replace(/[^\r\n]/g, ' '));
|
|
643
|
+
start = nonCodeEnd + 1;
|
|
644
|
+
i = nonCodeEnd;
|
|
645
|
+
}
|
|
646
|
+
}
|
|
647
|
+
parts.push(content.substring(start));
|
|
648
|
+
return parts.join('');
|
|
649
|
+
}
|
|
650
|
+
/** Index where an argument's code starts, after its leading whitespace and comments. */
|
|
651
|
+
leadingCommentsEnd(param) {
|
|
652
|
+
let i = 0;
|
|
653
|
+
while (i < param.length) {
|
|
654
|
+
if (/\s/.test(param[i])) {
|
|
655
|
+
i++;
|
|
656
|
+
}
|
|
657
|
+
else if (param[i] === '/' && (param[i + 1] === '/' || param[i + 1] === '*')) {
|
|
658
|
+
i = this.skipNonCode(param, i) + 1;
|
|
659
|
+
}
|
|
660
|
+
else {
|
|
661
|
+
break;
|
|
662
|
+
}
|
|
663
|
+
}
|
|
664
|
+
return i;
|
|
665
|
+
}
|
|
666
|
+
/** The line break a carried `//` comment needs so that it does not swallow what follows it. */
|
|
667
|
+
commentBreak(carried, next, eol) {
|
|
668
|
+
return /\/\/[^\n]*$/.test(carried) && !/^[ \t]*\r?\n/.test(next) ? eol : '';
|
|
485
669
|
}
|
|
486
670
|
/**
|
|
487
671
|
* When a string, a comment or a `url()` token starts at `index`, returns the index of its last
|