| | |
| | let dynamicStyleSheet = null; |
| | |
| | let dynamicExtensionStyleSheet = null; |
| |
|
| | |
| | |
| | |
| | |
| | const observer = new MutationObserver(mutations => { |
| | mutations.forEach(mutation => { |
| | if (mutation.type !== 'childList') return; |
| |
|
| | mutation.addedNodes.forEach(node => { |
| | if (node instanceof HTMLLinkElement && node.tagName === 'LINK' && node.rel === 'stylesheet') { |
| | node.addEventListener('load', () => { |
| | try { |
| | applyDynamicFocusStyles(node.sheet); |
| | } catch (e) { |
| | console.warn('Failed to process new stylesheet:', e); |
| | } |
| | }); |
| | } |
| | }); |
| | }); |
| | }); |
| |
|
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | function applyDynamicFocusStyles(styleSheet, { fromExtension = false } = {}) { |
| | |
| | |
| | const hoverRules = []; |
| | |
| | const focusRules = new Set(); |
| |
|
| | const PLACEHOLDER = ':__PLACEHOLDER__'; |
| |
|
| | |
| | |
| | |
| | |
| | |
| | |
| | function wrapperSignature(wrappers) { |
| | return wrappers.map(w => `${w.type}:${w.conditionText}`).join(';'); |
| | } |
| |
|
| | |
| | |
| | |
| | |
| | |
| | function processRules(rules, wrappers = []) { |
| | Array.from(rules).forEach(rule => { |
| | if (rule instanceof CSSImportRule) { |
| | |
| | |
| | |
| | const extra = (rule.media && rule.media.mediaText) ? [{ type: 'media', conditionText: rule.media.mediaText }] : []; |
| | processImportedStylesheet(rule.styleSheet, [...wrappers, ...extra]); |
| | } else if (rule instanceof CSSStyleRule) { |
| | |
| | const selectors = rule.selectorText.split(',').map(s => s.trim()); |
| |
|
| | |
| | selectors.forEach(selector => { |
| | const isHover = selector.includes(':hover'), isFocus = selector.includes(':focus'); |
| | if (isHover && isFocus) { |
| | |
| | } |
| | else if (isHover) { |
| | const baseSelector = selector.replace(/:hover/g, PLACEHOLDER).trim(); |
| | hoverRules.push({ baseSelector, rule, wrappers: [...wrappers] }); |
| | } else if (isFocus) { |
| | |
| | const baseSelector = selector.replace(/:focus(-within|-visible)?/g, PLACEHOLDER).trim(); |
| | focusRules.add(`${baseSelector}|${wrapperSignature(wrappers)}`); |
| | } |
| | }); |
| | } else if (rule instanceof CSSMediaRule) { |
| | |
| | processRules(rule.cssRules, [...wrappers, { type: 'media', conditionText: rule.conditionText }]); |
| | } else if (rule instanceof CSSSupportsRule) { |
| | |
| | processRules(rule.cssRules, [...wrappers, { type: 'supports', conditionText: rule.conditionText }]); |
| | } else if (rule instanceof window.CSSContainerRule) { |
| | |
| | |
| | |
| | processRules(rule.cssRules, [...wrappers, { type: 'container', conditionText: rule.conditionText }]); |
| | } |
| | }); |
| | } |
| |
|
| | |
| | |
| | |
| | |
| | |
| | function processImportedStylesheet(sheet, wrappers = []) { |
| | if (sheet && sheet.cssRules) { |
| | processRules(sheet.cssRules, wrappers); |
| | } |
| | } |
| |
|
| | processRules(styleSheet.cssRules, []); |
| |
|
| | |
| | let targetStyleSheet = null; |
| |
|
| | |
| | hoverRules.forEach(({ baseSelector, rule, wrappers }) => { |
| | if (!focusRules.has(`${baseSelector}|${wrapperSignature(wrappers)}`)) { |
| | |
| | targetStyleSheet ??= getDynamicStyleSheet({ fromExtension }); |
| |
|
| | |
| | |
| | |
| | |
| | |
| | |
| | const focusSelector = rule.selectorText.replace(/:hover/g, ':focus-visible'); |
| | let focusRule = `${focusSelector} { ${rule.style.cssText} }`; |
| |
|
| | |
| | if (wrappers.length > 0) { |
| | |
| | |
| | focusRule = wrappers.reduceRight((inner, w) => { |
| | if (w.type === 'media') return `@media ${w.conditionText} { ${inner} }`; |
| | if (w.type === 'supports') return `@supports ${w.conditionText} { ${inner} }`; |
| | if (w.type === 'container') return `@container ${w.conditionText} { ${inner} }`; |
| | return inner; |
| | }, focusRule); |
| | } |
| |
|
| | try { |
| | targetStyleSheet.insertRule(focusRule, targetStyleSheet.cssRules.length); |
| | } catch (e) { |
| | console.warn('Failed to insert focus rule:', e); |
| | } |
| | } |
| | }); |
| | } |
| |
|
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | function getDynamicStyleSheet({ fromExtension = false } = {}) { |
| | if (fromExtension) { |
| | if (!dynamicExtensionStyleSheet) { |
| | const styleSheetElement = document.createElement('style'); |
| | styleSheetElement.setAttribute('id', 'dynamic-extension-styles'); |
| | document.head.appendChild(styleSheetElement); |
| | dynamicExtensionStyleSheet = styleSheetElement.sheet; |
| | } |
| | return dynamicExtensionStyleSheet; |
| | } else { |
| | if (!dynamicStyleSheet) { |
| | const styleSheetElement = document.createElement('style'); |
| | styleSheetElement.setAttribute('id', 'dynamic-styles'); |
| | document.head.appendChild(styleSheetElement); |
| | dynamicStyleSheet = styleSheetElement.sheet; |
| | } |
| | return dynamicStyleSheet; |
| | } |
| | } |
| |
|
| | |
| | |
| | |
| | export function initDynamicStyles() { |
| | |
| | observer.observe(document.head, { |
| | childList: true, |
| | subtree: true, |
| | }); |
| |
|
| | |
| | Array.from(document.styleSheets).forEach(sheet => { |
| | try { |
| | applyDynamicFocusStyles(sheet, { fromExtension: sheet.href?.toLowerCase().includes('scripts/extensions') == true }); |
| | } catch (e) { |
| | console.warn('Failed to process stylesheet on initial load:', e); |
| | } |
| | }); |
| | } |
| |
|