Skip to content

Instantly share code, notes, and snippets.

@uxdxdev
Last active October 17, 2022 09:07
Show Gist options
  • Save uxdxdev/467336d64f1a130ef622499634dd098c to your computer and use it in GitHub Desktop.
Save uxdxdev/467336d64f1a130ef622499634dd098c to your computer and use it in GitHub Desktop.
// ==UserScript==
// @name SPM Custom CSS Properties
// @namespace http://tampermonkey.net/
// @version 0.35
// @description SPM UI theme creation using CSS properties
// @author https://github.com/daithimorton
// @match https://*/*
// @match http://*/*
// @grant none
// @downloadUrl https://gist.github.com/daithimorton/467336d64f1a130ef622499634dd098c/raw/spmcssvar.user.js
// @updateUrl https://gist.github.com/daithimorton/467336d64f1a130ef622499634dd098c/raw/spmcssvar.user.js
// ==/UserScript==
(function() {
'use strict';
// functions
function isCuramApp() {
return document.getElementsByTagName('body')[0].classList.contains('curam')
}
// only run in Curam apps
if(!isCuramApp()){
return;
}
function ready(fn) {
if (document.readyState != 'loading'){
fn();
} else {
document.addEventListener('DOMContentLoaded', fn);
}
}
function inRootWindow() {
try {
return window.self === window.top;
} catch (e) {
return true;
}
}
function toggleSidebar() {
const sidebarPanel = document.getElementsByClassName("sidebar__panel")[0];
if(sidebarPanel.classList.contains('hide')){
sidebarPanel.classList.remove("hide");
} else {
sidebarPanel.classList.add("hide");
}
}
function setPx(value) {
return value + 'px';
}
function calculateLeft(boundingBox){
let left = 0;
if((boundingBox.x + boundingBox.width) < (window.innerWidth / 2)){
left = window.innerWidth / 2;
}
return setPx(left);
}
function positionTooltip(element) {
const boundingBox = element.getBoundingClientRect();
window.top.tooltip.style.left = calculateLeft(boundingBox);
window.top.tooltip.style.top = 0;
}
function addBorderToElement(el) {
let element = el;
var sheets = document.styleSheets;
if(element){
element.matches = element.matches || element.webkitMatchesSelector || element.mozMatchesSelector || element.msMatchesSelector || element.oMatchesSelector;
const data = [];
for (var i in sheets) {
var rules = sheets[i].rules || sheets[i].cssRules;
for (var r in rules) {
if (rules[r].selectorText && element.matches(rules[r].selectorText) && rules[r].style.cssText.includes('var(--')) {
data.push({
selector: rules[r].selectorText,
props: rules[r].style.cssText.split(";").filter(item => item.includes('var(--'))
})
element.style.cssText += 'border: 2px solid red !important;'
if(!window.top.tooltip.classList.contains('showtooltip')){
window.top.tooltip.classList.add('showtooltip');
}
positionTooltip(element)
}
}
}
window.top.tooltip.innerHTML = ``;
data.forEach((item, index) => {
const selectorText = document.createElement('p');
selectorText.classList.add('font-bold')
selectorText.innerHTML = item.selector;
window.top.tooltip.append(selectorText);
item.props && item.props.forEach(propData =>{
const propText = document.createElement('p');
propText.innerHTML = propData;
window.top.tooltip.append(propText);
})
if(index !== (data.length -1)){
const lineBreak = document.createElement('br');
window.top.tooltip.append(lineBreak);
}
})
}
}
function removeBorderFromElement(element) {
const regex = /border:.*!important;/
const newStyle = element.style.cssText.replace(regex, '');
element.style.cssText = newStyle;
// hide tooltip
window.top.tooltip.classList.remove(`showtooltip`)
}
const defaultRootCssOverrides = `:root {
}
`;
const defaultOtherCSSOverrides = `
.infotooltip {
position: fixed;
z-index: 1000;
display: block;
background: #ffffff;
padding: 1rem;
font-size: 1rem;
border: 1px solid #161616;
max-width: 50vw;
color: #161616;
}
.hidetooltip {
visibility: hidden;
}
.showtooltip {
visibility: visible;
}
.font-bold {
font-weight: 700;
}
`;
// add a '\' to escape any regex special characters before using this string in regex.
// e.g. if there is a '+' character in your string it will now become \+ so that regex does not
// interperet it.
function escapeRegExp(text) {
return text.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, '\\$&');
}
const highlightElementsUsingSelector = (selector) =>{
for(let iframeStyleIndex = 0; iframeStyleIndex < window.top.iframeStyles.length; iframeStyleIndex++){
const found = window.top.iframeStyles[iframeStyleIndex].innerHTML.includes(`\n${selector} {`);
if(found){
var regex = new RegExp("^" + escapeRegExp(selector) + " {.*\n?", "m");
const newStyle = window.top.iframeStyles[iframeStyleIndex].innerHTML.replace(regex, '');
window.top.iframeStyles[iframeStyleIndex].innerHTML = newStyle;
} else {
window.top.iframeStyles[iframeStyleIndex].innerHTML += `${selector} { border: 2px solid red !important; } \n`;
}
}
}
// search all stylesheets for properties using CSS variables
const getCssPropVarMap = () => {
// TODO: optimize this section, its taking too long
console.time('-> -> SPM: propVarMap');
const propVarMap = [...document.styleSheets]
.reduce((finalArr, sheet) => finalArr.concat([...sheet.cssRules]
// we only want to parse type 1 rules https://developer.mozilla.org/en-US/docs/Web/API/CSSRule
.filter(rule => rule.type === 1)
.reduce((propValArr, rule) => {
// for each rule get the property name and its value
// TODO: fix bug in Firefox related to Object.keys(rule.style), can't convert to array of keys
const props = Object.keys(rule.style)
// we only want values using variables, e.g. var()
.filter(propName => {
return rule.style[propName].includes("var(--")
})
.map((propName) => {
return {
selector: rule.selectorText,
props: rule.style.cssText.split(";").filter(item => item.includes('var(--'))
}
})
return [...propValArr, ...props];
},[])),[]);
console.timeEnd('-> -> SPM: propVarMap');
console.time('-> -> SPM: filteredPropVarMap');
const filteredPropVarMap = propVarMap.reduce((finalArray, current) => {
const {selector, props} = current;
const foundDuplicate = finalArray.find(item => {
const dup = item.selector === selector;
return dup;
});
// check if already added to UI list
const isInPropVarMap = window.top.propVarMap && window.top.propVarMap.find(item => {
const dup = item.selector === selector;
return dup;
});
if (!foundDuplicate && !isInPropVarMap) {
window.top.propVarMap.push(current);
return finalArray.concat([current]);
} else {
return finalArray;
}
}, []);
console.timeEnd('-> -> SPM: filteredPropVarMap');
return filteredPropVarMap;
}
const updateSelectorCssPropList = () =>{
const sortBySelectorName = (a, b)=>{
const aPropName = a.selector;
const bPropName = b.selector;
if (aPropName < bPropName) return -1;
if (aPropName > bPropName) return 1;
return 0;
}
console.time('-> SPM: getCssPropVarMap()');
const result = getCssPropVarMap().sort(sortBySelectorName);
console.timeEnd('-> SPM: getCssPropVarMap()');
// display the exposed CSS variables in a list on the sidebar
for (let i = 0; i < result.length; i++) {
const { selector, props } = result[i];
const cssVarListItem = document.createElement('li');
cssVarListItem.classList.add('mb-4');
const selectorName = document.createElement('p');
selectorName.classList.add('font-bold', 'mt-0', 'mb-0');
selectorName.innerHTML = selector;
selectorName.classList.add('selector__name');
cssVarListItem.append(selectorName);
props.forEach(item => {
const propValue = document.createElement('p');
propValue.classList.add('mt-0', 'mb-0');
propValue.innerHTML = item;
cssVarListItem.append(propValue);
})
const highlightElementButton = document.createElement('button');
highlightElementButton.innerHTML = 'Highlight off'
highlightElementButton.classList.add('sidebar__highlight-element-button', 'mb-4', 'mt-2', 'leading-normal')
highlightElementButton.onclick = function(){
highlightElementsUsingSelector(selector)
// toggle on/off
if(highlightElementButton.classList.contains("on")){
highlightElementButton.classList.remove("on");
highlightElementButton.innerHTML = 'Highlight off'
}else{
highlightElementButton.classList.add("on");
highlightElementButton.innerHTML = 'Highlight on'
}
}
cssVarListItem.append(highlightElementButton)
window.top.cssPropVarList.append(cssVarListItem)
if(i === (result.length - 1)){
const hr = document.createElement('hr');
window.top.cssPropVarList.append(hr)
}
}
}
const updateExistingRootCssPropsList = () =>{
let rootVariables = [...document.styleSheets].map(styleSheet => [...styleSheet.cssRules].filter(rule => rule.type === 1 && rule.selectorText === ':root')).flat()
// rootVariables[0] are the existing CSS properties
// rootVariables[1] are the CSS properties defined in the above "defaultRootCssOverrides" variable
// e.g. if a customer creates a custom.css file with properties defined in :root
// then they will show up in the sidebar UI to highlight that some cusomisations are already
// being made.
const originalRootCssVariables = rootVariables[0];
// create list of all properties
const originalRootCssVariablesStyle = originalRootCssVariables.style;
const rootCssNameValueArr = Object.keys(originalRootCssVariablesStyle).map(key=>{
return {
name: key,
value: originalRootCssVariablesStyle.getPropertyValue(key)
}
})
for (let i = 0; i < originalRootCssVariablesStyle.length; i++) {
const propertyName = originalRootCssVariablesStyle.item(i);
const propertyValue = originalRootCssVariablesStyle[propertyName];
const cssRootVarListItem = document.createElement('li');
const variableNameSpan = document.createElement('span');
variableNameSpan.innerHTML = propertyName + ': '
const variableValueSpan = document.createElement('span');
variableValueSpan.classList.add('font-bold')
variableValueSpan.innerHTML = propertyValue + ';'
cssRootVarListItem.append(variableNameSpan)
cssRootVarListItem.append(variableValueSpan)
window.top.cssRootVarList.append(cssRootVarListItem)
if(i === (originalRootCssVariablesStyle.length - 1)){
const hr = document.createElement('hr');
window.top.cssRootVarList.append(hr)
}
}
}
const getCustomCssProps = () => {
// get all CSS variables used in all stylesheets
const allCssVariableRules = [...document.styleSheets].map(styleSheet => [...styleSheet.cssRules]).flat().filter(rule => rule.type === 1 && rule.cssText.includes('var(--'))
// tokenise the rule string that contains both the selector and stringified CSS styling
// we want the variable name and its fallback value
// we can't get the computed value for this variable here because
// we would need to process each element indiviually to compute it's styling
const allCssVariableMap = allCssVariableRules.map(rule => {
// console.log('spm', rule.style.cssText.split(';').filter(item => item.includes('var(--')));
// match the contents of the highest level var(<contents>)
// in some cases there will be nested var() but we only want
// the highest level one.
const regex = /var\((.*?)\)/;
const tokens = rule.cssText.match(regex);
const varStr = tokens && tokens[1];
// the variable name is the first item in the match array
const variableName = varStr.split(/,(.+)/)[0]
// the variable value is the second item in the match array
let defaultVariableValue = varStr.split(/,(.+)/)[1];
// if there is a bunch of nested var() then just append a ')' to the end and display
if(defaultVariableValue && defaultVariableValue.includes('var(')) defaultVariableValue += ')'
return {
variableName,
defaultVariableValue
}
});
// remove null values and only duplicates of the same key && value
// we want to keep duplicate CSS variables with different values to identify
// those CSS variables with different defaults, this may highlight variables in
// the Curam infrastructure that have a different default to the Carbon grey10 theme
let filteredAllCssVariableMap = allCssVariableMap.reduce((finalArray, current) => {
const foundDuplicate = finalArray.find(item => {
const dup = item.variableName === current.variableName && item.defaultVariableValue.replace(/ /g, '') === current.defaultVariableValue.replace(/ /g, '');
return dup;
});
const isInCssVarMap = window.top.cssVarMap.find(item => {
const dup = item.variableName === current.variableName && item.defaultVariableValue.replace(/ /g, '') === current.defaultVariableValue.replace(/ /g, '');
return dup;
});
if (!foundDuplicate && !isInCssVarMap) {
window.top.cssVarMap.push(current)
return finalArray.concat([current]);
} else {
return finalArray;
}
}, [])
return filteredAllCssVariableMap;
}
const updateCustomCssPropertiesList = () => {
const result = getCustomCssProps().sort((a, b)=> {
if (a.variableName < b.variableName) return -1;
if (a.variableName > b.variableName) return 1;
return 0;
});
// display the exposed CSS variables in a list on the sidebar
for (let i = 0; i < result.length; i++) {
const propertyName = result[i].variableName
const propertyValue = result[i].defaultVariableValue
const cssVarListItem = document.createElement('li');
const variableNameSpan = document.createElement('span');
variableNameSpan.innerHTML = propertyName + ': '
const variableValueSpan = document.createElement('span');
variableValueSpan.classList.add('font-bold')
variableValueSpan.innerHTML = propertyValue + ';'
const color = document.createElement('input');
color.setAttribute('type', 'color');
color.setAttribute('value', propertyValue.trim());
color.disabled = true;
cssVarListItem.append(variableNameSpan)
cssVarListItem.append(variableValueSpan)
cssVarListItem.append(color)
window.top.customCssPropList.append(cssVarListItem)
if(i === (result.length - 1)){
const hr = document.createElement('hr');
window.top.customCssPropList.append(hr)
}
}
}
// main
ready(function () {
console.time('SPM: custom Css properties script');
// we only want to define some things once in the root window like global vars, sidebar UI, etc.
if(inRootWindow()) {
// GLOBAL VARIABLES
// iframeStyles is a global iframes array to store both root window
// and iframe window style elements. we will iterate
// these style elements to update CSS styling as the user
// types in the textarea input. this will apply custom styling
// to all <style> elements in all iframes.
window.iframeRootStyles = [];
window.iframeStyles = [];
window.rootCssOverrides = defaultRootCssOverrides;
window.propVarMap = []
window.cssVarMap = []
window.rootCssVarMap = []
// FEATURE TOOGLES
// HOVER HIGHLIGHT
window.hoverHightlightFeature = false;
function toggleHoverHighlight() {
if(window.hoverHightlightFeature){
window.hoverHightlightFeature = false
} else {
window.hoverHightlightFeature = true
}
}
const headElement = document.getElementsByTagName('head')[0];
// SIDEBAR STYLE
const sidebarStyle = document.createElement('style')
sidebarStyle.type = 'text/css';
// sidebar styling to overlay the caseworker app UI.
sidebarStyle.innerHTML = `
.list-none {
list-style-type: none;
}
.pl-0 {
padding-left: 0;
}
.hide {
right: -34rem !important;
}
.mb-0 {
margin-bottom: 0;
}
.mb-2 {
margin-bottom: 0.5rem;
}
.mb-4 {
margin-bottom: 1rem;
}
.mt-0 {
margin-top: 0;
}
.mt-2 {
margin-top: 0.5rem;
}
.text-base {
font-size: 1rem;
}
.text-xl {
font-size: 1.25rem;
}
.italic {
font-style: italic;
}
.font-bold {
font-weight: 700;
}
.leading-normal {
line-height: 1.5rem;
}
.text-right {
text-align: right;
}
.block {
display: block;
}
.selector__name {
color: #393939
}
.on {
background-color: #0f62fe !important;
}
.sidebar__button {
background-color: #393939;
border-color: rgba(0,0,0,0);
color: #fff;
cursor: pointer;
outline: none;
height: 2rem;
}
.sidebar__highlight-element-button {
background-color: #393939;
border-color: rgba(0,0,0,0);
color: #fff;
cursor: pointer;
outline: none;
height: 2rem;
}
.sidebar__panel {
position: fixed;
color: #161616;
right: 0;
top: 2rem;
height: calc(100vh - 4rem);
max-width: 31rem;
background-color: #ffffff;
z-index: 100;
border-top: 1px solid #dfe3e6;
border-left: 1px solid #dfe3e6;
padding: 1rem;
overflow: hidden scroll;
direction: ltr;
}
/* class name duplicated 3 times to incease specificity over old Curam styling */
.sidebar__textarea.sidebar__textarea.sidebar__textarea {
width: 28rem;
resize: none;
padding: 1rem !important;
background-color: #f4f4f4;
color: #161616;
}
`
headElement.append(sidebarStyle);
// SIDEBAR PANEL UI
const sidebarPanel = document.createElement('div');
sidebarPanel.classList.add('sidebar__panel', 'hide', 'text-base', 'leading-normal')
const informationLinksDiv = document.createElement('div');
informationLinksDiv.classList.add('mb-4');
const mdnCssCustomPropertiesLink = document.createElement('a');
mdnCssCustomPropertiesLink.innerHTML = 'MDN: CSS Custom Properties';
mdnCssCustomPropertiesLink.setAttribute('href', 'https://developer.mozilla.org/en-US/docs/Web/CSS/Using_CSS_custom_properties');
mdnCssCustomPropertiesLink.setAttribute('target', '_blank');
mdnCssCustomPropertiesLink.classList.add('block');
const carbonComponentsRefLink = document.createElement('a');
carbonComponentsRefLink.innerHTML = 'Carbon Components Themes';
carbonComponentsRefLink.setAttribute('href', 'https://themes.carbondesignsystem.com/');
carbonComponentsRefLink.setAttribute('target', '_blank');
carbonComponentsRefLink.classList.add('block');
informationLinksDiv.append(mdnCssCustomPropertiesLink)
informationLinksDiv.append(carbonComponentsRefLink)
sidebarPanel.append(informationLinksDiv)
const sidebarTitle = document.createElement('h1');
sidebarTitle.innerHTML = 'Theme';
sidebarTitle.classList.add('text-xl', 'font-bold', 'mb-4', 'leading-normal');
sidebarPanel.append(sidebarTitle)
// allow users to input their own CSS variable declarations
const sidebarTextarea = document.createElement('textarea');
sidebarTextarea.innerHTML = window.rootCssOverrides;
sidebarTextarea.classList.add('sidebar__textarea', 'mb-4');
sidebarTextarea.setAttribute('rows', 20);
// when a user types in the textarea input we need to iterate
// over all of the stored style elements and set the inner html
// to the new styling. this will apply the styling to both the main
// window UI and all iframes.
sidebarTextarea.addEventListener('input', function () {
let value = sidebarTextarea.value;
window.rootCssOverrides = value;
for(let iframeStyleIndex = 0; iframeStyleIndex < window.iframeRootStyles.length; iframeStyleIndex++){
window.iframeRootStyles[iframeStyleIndex].innerHTML = value;
}
})
// js to allow Tab to be used in textarea https://stackoverflow.com/a/6637396/2600522
sidebarTextarea.addEventListener('keydown', function(e) {
if (e.key == 'Tab') {
e.preventDefault();
var start = this.selectionStart;
var end = this.selectionEnd;
// set textarea value to: text before caret + tab + text after caret
this.value = this.value.substring(0, start) +
"\t" + this.value.substring(end);
// put caret at right position again
this.selectionStart =
this.selectionEnd = start + 1;
}
});
sidebarPanel.append(sidebarTextarea)
const copyThemeButton = document.createElement('button');
copyThemeButton.innerHTML = 'Copy theme to clipboard'
copyThemeButton.classList.add('sidebar__button', 'mb-4', 'leading-normal')
copyThemeButton.onclick = function(){
document.querySelector(".sidebar__textarea").select();
document.execCommand('copy');
}
sidebarPanel.append(copyThemeButton)
const sidebarCustomCssVariableExampleHeading = document.createElement('h2');
sidebarCustomCssVariableExampleHeading.innerHTML = 'Example';
sidebarCustomCssVariableExampleHeading.classList.add('text-xl', 'font-bold', 'mb-4');
sidebarPanel.append(sidebarCustomCssVariableExampleHeading);
// example usage of CSS properties
const sidebarCustomCssVariableExampleTextarea = document.createElement('textarea');
sidebarCustomCssVariableExampleTextarea.innerHTML = `:root {
--ui-background: white;
--interactive-01: hotpink;
--text-01: black;
}`;
sidebarCustomCssVariableExampleTextarea.classList.add('sidebar__textarea', 'mb-4');
sidebarCustomCssVariableExampleTextarea.setAttribute('rows', 5);
sidebarCustomCssVariableExampleTextarea.setAttribute('readonly', true);
sidebarPanel.append(sidebarCustomCssVariableExampleTextarea)
// const cssRootVariablesTitle = document.createElement('h1');
// cssRootVariablesTitle.innerHTML = 'Existing :root CSS Properties';
// cssRootVariablesTitle.classList.add('text-xl', 'font-bold', 'mb-4', 'leading-normal');
// sidebarPanel.append(cssRootVariablesTitle)
const cssRootVarList = document.createElement('ul');
cssRootVarList.classList.add('mb-4', 'list-none', 'pl-0')
sidebarPanel.append(cssRootVarList)
// store the root var list globally
window.cssRootVarList = cssRootVarList;
const cssAllVariablesTitle = document.createElement('h1');
cssAllVariablesTitle.innerHTML = 'Custom CSS Properties';
cssAllVariablesTitle.classList.add('text-xl', 'font-bold', 'mb-4', 'leading-normal');
sidebarPanel.append(cssAllVariablesTitle);
const customCssPropList = document.createElement('ul');
customCssPropList.classList.add('mb-4', 'list-none', 'pl-0');
sidebarPanel.append(customCssPropList)
// store the custom css properties list globally
window.customCssPropList = customCssPropList;
const cssPropVarListTitle = document.createElement('h1');
cssPropVarListTitle.innerHTML = 'Selector/Custom CSS Properties List';
cssPropVarListTitle.classList.add('text-xl', 'font-bold', 'mb-4', 'leading-normal');
sidebarPanel.append(cssPropVarListTitle);
const cssPropVarList = document.createElement('ul');
cssPropVarList.classList.add('mb-4', 'list-none', 'pl-0')
sidebarPanel.append(cssPropVarList)
// store the propvar list globally
window.cssPropVarList = cssPropVarList;
// add the sidebar to the root <body> element
const bodyElement = document.getElementsByTagName('body')[0];
bodyElement.prepend(sidebarPanel)
// SIDEBAR PANEL UI END
// TOP MENU TABS UI
const topLevelMenuContainer = document.createElement('div');
topLevelMenuContainer.classList.add('text-right')
const sidebarShowHideButton = document.createElement('button');
sidebarShowHideButton.innerHTML = 'Toggle theming sidebar';
sidebarShowHideButton.classList.add('sidebar__button', 'leading-normal')
// toggle the sidebar panel by moving it out of view using the .hide class
sidebarShowHideButton.onclick = function() {
toggleSidebar();
if(sidebarShowHideButton.classList.contains('on')){
sidebarShowHideButton.classList.remove("on");
} else {
sidebarShowHideButton.classList.add("on");
}
}
const hoverHighlightToggle = document.createElement('button');
hoverHighlightToggle.innerHTML = 'Toggle hover highlight';
hoverHighlightToggle.classList.add('sidebar__button', 'leading-normal')
hoverHighlightToggle.onclick = function() {
toggleHoverHighlight();
if(hoverHighlightToggle.classList.contains('on')){
hoverHighlightToggle.classList.remove("on");
} else {
hoverHighlightToggle.classList.add("on");
}
}
topLevelMenuContainer.append(hoverHighlightToggle)
topLevelMenuContainer.append(sidebarShowHideButton)
bodyElement.prepend(topLevelMenuContainer);
// TOP MENU TABS UI END
const tooltip = document.createElement('div');
tooltip.classList.add('infotooltip', 'hidetooltip');
bodyElement.prepend(tooltip);
window.tooltip = tooltip;
}
// TODO: fix bug in firefox where .iframeRootStyles is undefined when this section runs. Using setTimeout(500ms) works.
// create style element for :root custom properties
const rootStyles = document.createElement('style')
rootStyles.type = 'text/css';
rootStyles.innerHTML = window.top.rootCssOverrides;
// create style element for styling outside of :root
const otherStyles = document.createElement('style')
otherStyles.type = 'text/css';
otherStyles.innerHTML = defaultOtherCSSOverrides;
const rootHead = document.getElementsByTagName('head')[0];
rootHead.append(rootStyles);
rootHead.append(otherStyles);
window.top.iframeRootStyles.push(rootStyles)
window.top.iframeStyles.push(otherStyles)
// RUN IN ALL WINDOWS INCLUDING IFRAMES
// Existing :root CSS Properties
// console.time('SPM: updateExistingRootCssPropsList()');
// updateExistingRootCssPropsList();
// console.timeEnd('SPM: updateExistingRootCssPropsList()');
// Custom CSS Properties
console.time('SPM: updateCustomCssPropertiesList()');
updateCustomCssPropertiesList();
console.timeEnd('SPM: updateCustomCssPropertiesList()');
// Selector/CSS Properties List
console.time('SPM: updateSelectorCssPropList()');
updateSelectorCssPropList();
console.timeEnd('SPM: updateSelectorCssPropList()');
// set event listeners
document.addEventListener('mouseover', function (e) {
if(window.top.hoverHightlightFeature){
addBorderToElement(e.target)
}
});
document.addEventListener('mouseout', function (e) {
if(window.top.hoverHightlightFeature){
removeBorderFromElement(e.target)
}
});
console.timeEnd('SPM: custom Css properties script');
})
})();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment