1+ import { fetch } from '@alessiofrittoli/fetcher/fetch'
2+ import { Url , type UrlInput } from '@alessiofrittoli/url-utils'
3+
14/**
25 * Prevent Element Overflow.
36 *
@@ -26,4 +29,155 @@ export const restoreScroll = ( target: HTMLElement = document.documentElement )
2629 target . style . removeProperty ( 'padding-right' )
2730 target . style . removeProperty ( '--scrollbar-size' )
2831
32+ }
33+
34+ /**
35+ * Represents a style input.
36+ *
37+ *
38+ * @property {UrlInput } - A URL string or input pointing to an external stylesheet
39+ * @property {HTMLStyleElement } - An HTML style element containing CSS rules
40+ * @property {CSSStyleSheet } - A CSS stylesheet object
41+ * @property {StyleSheetList } - A collection of CSS stylesheets
42+ */
43+ export type Style = UrlInput | HTMLStyleElement | CSSStyleSheet | StyleSheetList
44+
45+
46+ /**
47+ * Represents a single style object or an array of style objects.
48+ *
49+ * @typeParam Style The style object type. See {@link Style}.
50+ */
51+ export type Styles = Style | Style [ ]
52+
53+
54+ /**
55+ * Clones a StyleSheetList or array of CSSStyleSheets into an array of `HTMLStyleElement` objects.
56+ *
57+ * This function extracts CSS rules from each stylesheet and creates corresponding `<style>`
58+ * elements containing the serialized CSS text. If an error occurs while processing a stylesheet,
59+ * the error is logged and that stylesheet is skipped.
60+ *
61+ * @param styles The source `StyleSheetList` or array of `CSSStyleSheet` objects to clone.
62+ *
63+ * @returns An array of `HTMLStyleElement` objects, each containing the CSS rules from the source stylesheets.
64+ * Failed stylesheets are filtered out and not included in the result.
65+ *
66+ * @example
67+ *
68+ * ```ts
69+ * const styles = cloneStyleSheetList( document.styleSheets )
70+ * styles.forEach( style => shadowRoot.appendChild( style ) )
71+ * ```
72+ */
73+ export const cloneStyleSheetList = ( styles : StyleSheetList | CSSStyleSheet [ ] ) => (
74+ [ ...styles ] . map ( ( { cssRules } ) => {
75+ try {
76+
77+ const style = document . createElement ( 'style' )
78+
79+ for ( let i = 0 ; i < cssRules . length ; i ++ ) {
80+ const rule = cssRules [ i ]
81+
82+ if ( ! rule ) continue
83+
84+ style . appendChild (
85+ document . createTextNode ( rule . cssText )
86+ )
87+ }
88+
89+ return style
90+
91+ } catch ( error ) {
92+
93+ console . error ( 'Error while cloning styles.' , error )
94+
95+ }
96+ } ) . filter ( Boolean ) as HTMLStyleElement [ ]
97+ )
98+
99+
100+ /**
101+ * Clones style sheets from various sources into new `HTMLStyleElement` instances.
102+ *
103+ * @param styles A single style source or array of style sources. Can be:
104+ * - `StyleSheetList`: A list of stylesheets
105+ * - `CSSStyleSheet`: A single stylesheet object
106+ * - `HTMLStyleElement`: A style DOM element
107+ * - `UrlInput`: A URL string or object pointing to a stylesheet
108+ *
109+ * @returns A promise that resolves to an array of cloned `HTMLStyleElement` nodes.
110+ * Each element is a new style element containing the CSS rules from the source.
111+ */
112+ export const cloneStyleSheets = async ( styles : Styles ) : Promise < HTMLStyleElement [ ] > => {
113+
114+ if ( ! Array . isArray ( styles ) ) {
115+ return cloneStyleSheets ( [ styles ] )
116+ }
117+
118+ const styleNodes : HTMLStyleElement [ ] = [ ]
119+ const styleSheetList : StyleSheetList [ ] = [ ]
120+ const styleSheets : CSSStyleSheet [ ] = [ ]
121+ const styleElements : HTMLStyleElement [ ] = [ ]
122+ const styleUrls : UrlInput [ ] = [ ]
123+
124+ styles . forEach ( style => {
125+ if ( style instanceof StyleSheetList ) {
126+ styleSheetList . push ( style )
127+ return
128+ }
129+ if ( style instanceof CSSStyleSheet ) {
130+ styleSheets . push ( style )
131+ return
132+ }
133+ if ( style instanceof HTMLStyleElement ) {
134+ styleElements . push ( style )
135+ return
136+ }
137+
138+ styleUrls . push ( style )
139+ } )
140+
141+ if ( styleSheetList . length > 0 ) {
142+ styleNodes . push ( ...styleSheetList . flatMap (
143+ styleSheetList => cloneStyleSheetList ( styleSheetList )
144+ ) )
145+ }
146+
147+ if ( styleSheets . length > 0 ) {
148+ styleNodes . push ( ...cloneStyleSheetList ( styleSheets ) )
149+ }
150+
151+ if ( styleElements . length > 0 ) {
152+ styleNodes . push ( ...styleElements . map ( style => {
153+ const target = document . createElement ( 'style' )
154+ target . appendChild (
155+ document . createTextNode ( style . innerText )
156+ )
157+ return target
158+ } ) )
159+ }
160+
161+ await Promise . allSettled (
162+ styleUrls . map ( async urlInput => {
163+
164+ const { data, error } = await fetch < string > ( Url . format ( urlInput ) )
165+
166+ if ( error ) throw error
167+
168+ const style = document . createElement ( 'style' )
169+
170+ style . appendChild (
171+ document . createTextNode ( data )
172+ )
173+
174+ styleNodes . push ( style )
175+
176+ return style
177+
178+ } )
179+ )
180+
181+ return styleNodes
182+
29183}
0 commit comments