Skip to content

Commit e3f49db

Browse files
committed
feat: add stylesheet root and comment node
1 parent 1850ce0 commit e3f49db

File tree

9 files changed

+787
-18
lines changed

9 files changed

+787
-18
lines changed

package.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,5 +51,8 @@
5151
"typescript": "^5.8.3",
5252
"unbuild": "^3.6.0",
5353
"vitest": "^3.2.4"
54+
},
55+
"dependencies": {
56+
"window": "^4.2.7"
5457
}
5558
}

pnpm-lock.yaml

Lines changed: 652 additions & 16 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/entities/CSSComment.ts

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
export type CSSCommentInlineCSSText = `/* ${string} */`;
2+
export type CSSCommentBlockCSSText = `/**\n${string}\n */`;
3+
export type CSSCommentCSSText = CSSCommentInlineCSSText | CSSCommentBlockCSSText;
4+
5+
export type CSSCommentType = 'inline' | 'block';
6+
export interface CSSCommentOptions {
7+
type: CSSCommentType;
8+
}
9+
10+
/**
11+
* A comment in a CSS stylesheet.
12+
*/
13+
export class CSSComment {
14+
static defaultOptions: CSSCommentOptions = {
15+
type: 'inline',
16+
};
17+
18+
value: string;
19+
type: CSSCommentType;
20+
21+
constructor(value: string, options: CSSCommentOptions = CSSComment.defaultOptions) {
22+
this.value = value;
23+
this.type = options.type;
24+
}
25+
26+
get cssText(): CSSCommentCSSText {
27+
if (this.type === 'inline') {
28+
return `/* ${this.value} */`;
29+
}
30+
31+
if (this.type === 'block') {
32+
// Format the value as a block comment
33+
const lines = this.value.split('\n');
34+
const indentedLines = lines.map((line) => ` * ${line}`);
35+
const formattedLines = indentedLines.join('\n');
36+
37+
return `/**\n${formattedLines}\n */`;
38+
}
39+
40+
throw new Error('Invalid comment type');
41+
}
42+
}

src/entities/CSSStyleSheet.ts

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
// biome-ignore lint/suspicious/noTsIgnore: no types available for this package
2+
// @ts-ignore-next-line no-restricted-globals
3+
import Window from 'window';
4+
import type { CSSComment } from './CSSComment';
5+
6+
const window = new Window();
7+
8+
interface CSSStyleSheetInit extends Window.CSSStyleSheetInit {
9+
nodes: CSSComment[];
10+
}
11+
12+
export class CSSStyleSheet extends window.CSSStyleSheet {
13+
nodes: CSSComment[];
14+
15+
constructor(options: CSSStyleSheetInit) {
16+
super(options);
17+
18+
this.nodes = options.nodes || [];
19+
}
20+
21+
append(node: CSSComment) {
22+
this.nodes.push(node);
23+
}
24+
25+
remove(node: CSSComment) {
26+
this.nodes = this.nodes.filter((n) => n !== node);
27+
}
28+
29+
get cssText() {
30+
return `${this.nodes.map((node) => node.cssText).join('\n\n')}\n`;
31+
}
32+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import { describe, expect, it } from 'vitest';
2+
import { CSSComment } from '../CSSComment';
3+
4+
describe('CSSComment', () => {
5+
it('should create a comment', () => {
6+
const comment = new CSSComment('This is a comment');
7+
expect(comment.cssText).toMatchSnapshot();
8+
});
9+
it('should create a block comment', () => {
10+
const comment = new CSSComment('This is a comment', { type: 'block' });
11+
expect(comment.cssText).toMatchSnapshot();
12+
});
13+
});
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import { describe, expect, it } from 'vitest';
2+
import { CSSComment } from '../CSSComment';
3+
import { CSSStyleSheet } from '../CSSStyleSheet';
4+
5+
describe('CSSStyleSheet', () => {
6+
it('should create a stylesheet', () => {
7+
const stylesheet = new CSSStyleSheet({ nodes: [] });
8+
expect(stylesheet.cssText).toBe('\n');
9+
});
10+
11+
it.each([[[]], [[new CSSComment('This is a block comment', { type: 'block' })]]])(
12+
'always ends with a single blank newline',
13+
(nodes) => {
14+
const stylesheet = new CSSStyleSheet({ nodes });
15+
expect(stylesheet.cssText.endsWith('\n')).toBe(true);
16+
expect(stylesheet.cssText.endsWith('\n\n')).toBe(false);
17+
},
18+
);
19+
20+
it('should create a stylesheet with a comment', () => {
21+
const stylesheet = new CSSStyleSheet({ nodes: [] });
22+
const comment = new CSSComment('This is a block comment', { type: 'block' });
23+
stylesheet.append(comment);
24+
expect(stylesheet.cssText).toMatchSnapshot();
25+
});
26+
});
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
2+
3+
exports[`CSSComment > should create a block comment 1`] = `
4+
"/**
5+
* This is a comment
6+
*/"
7+
`;
8+
9+
exports[`CSSComment > should create a comment 1`] = `"/* This is a comment */"`;
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
2+
3+
exports[`CSSStyleSheet > should create a stylesheet with a comment 1`] = `
4+
"/**
5+
* This is a block comment
6+
*/
7+
"
8+
`;

tsconfig.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@
44
"declarationMap": true,
55
"erasableSyntaxOnly": true,
66
"lib": ["DOM", "ESNext"],
7-
"module": "NodeNext",
8-
"moduleResolution": "nodenext",
7+
"module": "preserve",
8+
"moduleResolution": "bundler",
99
"noFallthroughCasesInSwitch": true,
1010
"noUncheckedIndexedAccess": true,
1111
"noUnusedLocals": true,

0 commit comments

Comments
 (0)