1- import { useState , useRef , useEffect , useCallback } from 'react' ;
2- import { HtmlEditor } from './components/HtmlEditor' ;
3- import { SelectorPanel } from './components/SelectorPanel' ;
4- import { mapSelectorsToLines , getLineCount , type SelectorWithTiming } from './utils/selectorMapper' ;
5- import { formatHtml } from './utils/formatHtml' ;
1+ import { useState , useEffect } from "react" ;
2+ import {
3+ createScenarioFrame ,
4+ scenarios ,
5+ } from "css-selector-generator-scenarios" ;
6+ import { HtmlEditor } from "./components/HtmlEditor" ;
7+ import { SelectorPanel } from "./components/SelectorPanel" ;
8+ import { ScenarioPicker } from "./components/ScenarioPicker" ;
9+ import {
10+ mapSelectorsToLines ,
11+ getLineCount ,
12+ type SelectorWithTiming ,
13+ } from "./utils/selectorMapper" ;
14+ import { formatHtml } from "./utils/formatHtml" ;
15+
16+ // The line mapper pairs source tags with elements found by walking the
17+ // rendered DOM, which cannot see into shadow roots.
18+ const PICKABLE_SCENARIOS = scenarios . filter (
19+ ( scenario ) => ! scenario . tags . includes ( "shadow-dom" ) ,
20+ ) ;
621
722const SAMPLE_HTML = `<section class="demo">
823 <article>
@@ -49,35 +64,49 @@ const SAMPLE_HTML = `<section class="demo">
4964
5065function App ( ) {
5166 const [ htmlSource , setHtmlSource ] = useState ( SAMPLE_HTML ) ;
52- const [ selectorsByLine , setSelectorsByLine ] = useState < Map < number , SelectorWithTiming [ ] > > ( new Map ( ) ) ;
67+ const [ selectorsByLine , setSelectorsByLine ] = useState <
68+ Map < number , SelectorWithTiming [ ] >
69+ > ( new Map ( ) ) ;
5370 const [ totalTimeMs , setTotalTimeMs ] = useState ( 0 ) ;
5471 const [ showInfoPopover , setShowInfoPopover ] = useState ( false ) ;
55- const iframeRef = useRef < HTMLIFrameElement > ( null ) ;
72+ const [ scenarioId , setScenarioId ] = useState ( "" ) ;
5673
57- const updateSelectors = useCallback ( ( ) => {
58- if ( ! iframeRef . current ?. contentDocument ) return ;
59-
60- const doc = iframeRef . current . contentDocument ;
61-
62- // Write HTML to the iframe's document body
63- doc . body . innerHTML = htmlSource ;
64-
65- // Generate selectors using the iframe's body as root
66- const result = mapSelectorsToLines ( htmlSource , doc . body ) ;
67- setSelectorsByLine ( result . selectorsByLine ) ;
68- setTotalTimeMs ( result . totalTimeMs ) ;
74+ useEffect ( ( ) => {
75+ let cancelled = false ;
76+
77+ void ( async ( ) => {
78+ // Loaded the same way as in the library's tests, so that the document
79+ // shape matches. Selectors can still differ from a scenario's stated
80+ // expectation, because everything here is generated against the body.
81+ const iframe = await createScenarioFrame ( htmlSource , document ) ;
82+ try {
83+ const doc = iframe . contentDocument ;
84+ if ( ! doc || cancelled ) return ;
85+ const result = mapSelectorsToLines ( htmlSource , doc . body ) ;
86+ setSelectorsByLine ( result . selectorsByLine ) ;
87+ setTotalTimeMs ( result . totalTimeMs ) ;
88+ } finally {
89+ iframe . remove ( ) ;
90+ }
91+ } ) ( ) ;
92+
93+ return ( ) => {
94+ cancelled = true ;
95+ } ;
6996 } , [ htmlSource ] ) ;
7097
71- useEffect ( ( ) => {
72- updateSelectors ( ) ;
73- } , [ updateSelectors ] ) ;
98+ const handleScenarioChange = ( id : string ) => {
99+ setScenarioId ( id ) ;
100+ const scenario = PICKABLE_SCENARIOS . find ( ( item ) => item . id === id ) ;
101+ setHtmlSource ( scenario ? scenario . html . trim ( ) : SAMPLE_HTML ) ;
102+ } ;
74103
75104 const lineCount = getLineCount ( htmlSource ) ;
76105
77106 // Calculate total element count
78107 const elementCount = Array . from ( selectorsByLine . values ( ) ) . reduce (
79108 ( total , selectors ) => total + selectors . length ,
80- 0
109+ 0 ,
81110 ) ;
82111
83112 const handleFormat = ( ) => {
@@ -89,17 +118,27 @@ function App() {
89118 < div className = "app-title" >
90119 < h1 > CSS Selector Generator Sandbox</ h1 >
91120 < p >
92- A playground for testing{ ' ' }
93- < a href = "https://github.com/fczbkk/css-selector-generator#readme" > css-selector-generator</ a > ,
94- a JavaScript library that generates unique CSS selectors for any HTML element.
121+ A playground for testing{ " " }
122+ < a href = "https://github.com/fczbkk/css-selector-generator#readme" >
123+ css-selector-generator
124+ </ a >
125+ , a JavaScript library that generates unique CSS selectors for any
126+ HTML element.
95127 </ p >
96128 </ div >
97129 < div className = "panels-content" >
98130 < div className = "panel-column" >
99131 < div className = "panel-header" >
100132 HTML
101- < button className = "format-btn" onClick = { handleFormat } > Format</ button >
133+ < button className = "format-btn" onClick = { handleFormat } >
134+ Format
135+ </ button >
102136 </ div >
137+ < ScenarioPicker
138+ scenarios = { PICKABLE_SCENARIOS }
139+ value = { scenarioId }
140+ onChange = { handleScenarioChange }
141+ />
103142 < HtmlEditor value = { htmlSource } onChange = { setHtmlSource } />
104143 </ div >
105144 < div className = "panel-column" >
@@ -116,15 +155,20 @@ function App() {
116155 { showInfoPopover && (
117156 < div className = "info-popover" >
118157 < p > Total time to generate all CSS selectors.</ p >
119- < p > Hover over individual selectors to see their generation time.</ p >
158+ < p >
159+ Hover over individual selectors to see their generation
160+ time.
161+ </ p >
120162 </ div >
121163 ) }
122164 </ span >
123165 </ div >
124- < SelectorPanel selectorsByLine = { selectorsByLine } lineCount = { lineCount } />
166+ < SelectorPanel
167+ selectorsByLine = { selectorsByLine }
168+ lineCount = { lineCount }
169+ />
125170 </ div >
126171 </ div >
127- < iframe ref = { iframeRef } className = "hidden-container" title = "HTML Renderer" />
128172 </ div >
129173 ) ;
130174}
0 commit comments