1+ type Combinator = '>' | '~' | '+' ;
2+
13/**
24 * Generate an HTML document from CSS.
35 * @param css The style sheet.
@@ -45,8 +47,8 @@ export function cssToHtml(css: CSSRuleList | string): HTMLBodyElement {
4547 const descriptor = {
4648 previousElement : undefined as HTMLElement | undefined ,
4749 previousCharacter : '' ,
48- combinator : '' ,
49- addressCharacter : '' ,
50+ combinator : '' as '' | Combinator ,
51+ addressCharacter : '' as '' | '.' | '#' ,
5052 classes : [ '' ] ,
5153 id : '' ,
5254 tag : '' ,
@@ -68,11 +70,38 @@ export function cssToHtml(css: CSSRuleList | string): HTMLBodyElement {
6870 descriptor . classes = [ '' ] ;
6971 descriptor . id = '' ;
7072 descriptor . tag = '' ;
73+ } ,
74+ matchesElement : ( element : Element ) : boolean => {
75+ const descriptorTag = ( descriptor . tag . toUpperCase ( ) || 'DIV' ) ;
76+ const descriptorClasses = descriptor . classes . filter ( ( c ) => Boolean ( c ) ) ;
77+ // Compare tag, id, and classlist length.
78+ if (
79+ element . tagName !== descriptorTag
80+ || element . id !== descriptor . id
81+ || element . classList . length !== descriptorClasses . length
82+ ) {
83+ return false ;
84+ }
85+ // Compare classlists.
86+ const differingClasses = descriptorClasses . filter ( ( c ) => ! element . classList . contains ( c ) ) ;
87+ return ! differingClasses . length ;
7188 }
7289 } ;
7390
7491 function addElementToOutput ( ) : void {
75- // Create the element.
92+ // If an identical element already exists, skip adding the new element.
93+ const existingElements = Array . from (
94+ ( descriptor . combinator === '>' ?
95+ descriptor . previousElement ?. children :
96+ descriptor . previousElement ?. parentElement ?. children ) ?? output . children
97+ ) ;
98+ const matchingElement = existingElements . find ( ( element ) => descriptor . matchesElement ( element ) ) ;
99+ if ( matchingElement ) {
100+ // Reference the matching element so properties such as `content` can cascade.
101+ descriptor . previousElement = matchingElement as HTMLElement ;
102+ return ;
103+ }
104+ // Create the new element.
76105 const newElement = document . createElement ( descriptor . tag || 'div' ) ;
77106 // Add the classes.
78107 for ( const c of descriptor . classes ) {
@@ -105,7 +134,7 @@ export function cssToHtml(css: CSSRuleList | string): HTMLBodyElement {
105134 // The start of a new selector.
106135 if ( ! descriptor . previousCharacter ) {
107136 if ( / (?: \+ | ~ | > ) / . test ( character ) ) {
108- descriptor . combinator = character ;
137+ descriptor . combinator = character as Combinator ;
109138 }
110139 else if ( character === '.' || character === '#' ) {
111140 descriptor . addressCharacter = character ;
@@ -130,7 +159,7 @@ export function cssToHtml(css: CSSRuleList | string): HTMLBodyElement {
130159 else if ( / (?: \+ | ~ | > ) / . test ( character ) ) {
131160 addElementToOutput ( ) ;
132161 descriptor . clear ( ) ;
133- descriptor . combinator = character ;
162+ descriptor . combinator = character as Combinator ;
134163 }
135164 // The character none of the above.
136165 else {
0 commit comments