Skip to content

Commit

Permalink
Not supported property values
Browse files Browse the repository at this point in the history
  • Loading branch information
NiedziolkaMichal committed Oct 1, 2022
1 parent cc94fd1 commit ed88fed
Show file tree
Hide file tree
Showing 4 changed files with 127 additions and 17 deletions.
6 changes: 6 additions & 0 deletions editor/js/editable-css.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import * as clippy from "./editor-libs/clippy.js";
import * as mceEvents from "./editor-libs/events.js";
import * as mceUtils from "./editor-libs/mce-utils.js";
import * as cssEditorUtils from "./editor-libs/css-editor-utils.js";

import "../css/editor-libs/ui-fonts.css";
import "../css/editor-libs/common.css";
Expand Down Expand Up @@ -47,6 +48,8 @@ import "../css/editable-css.css";
mceEvents.register();
handleResetEvents();
handleChoiceHover();
// Adding or removing class "invalid"
cssEditorUtils.applyInitialSupportWarningState(exampleChoices);

clippy.addClippy();
}
Expand All @@ -70,6 +73,9 @@ import "../css/editable-css.css";
exampleChoices[i].querySelector("code").innerHTML = highlighted;
}

// Adding or removing class "invalid"
cssEditorUtils.applyInitialSupportWarningState(exampleChoices);

// if there is an initial choice set, set it as selected
if (initialChoice) {
mceEvents.onChoose(exampleChoices[initialChoice]);
Expand Down
126 changes: 114 additions & 12 deletions editor/js/editor-libs/css-editor-utils.js
Original file line number Diff line number Diff line change
@@ -1,27 +1,129 @@
export let editTimer = undefined;

export function applyCode(code, choice, targetElement) {
export function applyCode(code, choice, targetElement, immediateInvalidChange) {
// http://regexr.com/3fvik
var cssCommentsMatch = /(\/\*)[\s\S]+(\*\/)/g;
var element = targetElement || document.getElementById("example-element");

// strip out any CSS comments before applying the code
code.replace(cssCommentsMatch, "");
code = code.replace(cssCommentsMatch, "");
// Checking if every CSS declaration in passed code, is supported by the browser
let codeSupported = isCodeSupported(element, code);

element.style.cssText = code;

// clear any existing timer
clearTimeout(editTimer);
/* Start a new timer. This will ensure that the state is
not marked as invalid, until the user has stopped typing
for 500ms */
editTimer = setTimeout(function () {
if (!element.style.cssText) {
choice.parentNode.classList.add("invalid");

/**
* Adding or removing class "invalid" from choice parent, which will typically be <div class="example-choice">
*/
let setInvalidClass = function() {
if (codeSupported) {
choice.parentNode.classList.remove('invalid');
} else {
choice.parentNode.classList.remove("invalid");
choice.parentNode.classList.add('invalid');
}
};

if(immediateInvalidChange) {
// Setting class immediately
setInvalidClass();
} else {
/* Start a new timer. This will ensure that the state is
not marked as invalid, until the user has stopped typing
for 500ms */
editTimer = setTimeout(setInvalidClass, 500);
}
}

/**
* Checks if every passed declaration is supported by the browser.
* In case browser recognizes property with vendor prefix(like -webkit-), lacking support for unprefixed property is ignored.
* Properties with vendor prefix not recognized by the browser are always ignored.
* @param element - any element on which cssText can be tested
* @param declarations - list of css declarations with no curly brackets. They need to be separated by ";" and declaration key-value needs to be separated by ":". Function expects no comments.
* @returns {boolean} - true if every declaration is supported by the browser. Properties with vendor prefix are excluded.
*/
export function isCodeSupported(element, declarations) {
var vendorPrefixMatch = /^-(?:webkit|moz|ms|o)-/;
var style = element.style;
// Expecting declarations to be separated by ";"
// Declarations with just white space are ignored
var declarationsArray = declarations.split(";")
.map(d => d.trim())
.filter(d => d.length > 0);

/**
* @returns {boolean} - true if declaration starts with -webkit-, -moz-, -ms- or -o-
*/
function hasVendorPrefix(declaration) {
return vendorPrefixMatch.test(declaration);
}

/**
* Looks for property name by cutting off optional vendor prefix at the beginning
* and then cutting off rest of the declaration, starting from any whitespace or ":" in property name.
* @param declaration - single css declaration, with not white space at the beginning
* @returns {string} - property name without vendor prefix.
*/
function getPropertyNameNoPrefix(declaration) {
var prefixMatch = vendorPrefixMatch.exec(declaration);
var prefix = prefixMatch === null ? "" : prefixMatch[0];
var declarationNoPrefix = prefix === null ? declaration : declaration.slice(prefix.length);
// Expecting property name to be over, when any whitespace or ":" is found
var propertyNameSeparator = /[\s:]/;
return declarationNoPrefix.split(propertyNameSeparator)[0];
}
// Clearing previous state
style.cssText = "";

// List of found and applied properties with vendor prefix
let appliedPropertiesWithPrefix = new Set();
// List of not applied properties - because of lack of support for its name or value
let notAppliedProperties = new Set();

for (let declaration of declarationsArray) {
let previousCSSText = style.cssText;
// Declarations are added one by one, because browsers sometimes combine multiple declarations into one
// For example Chrome changes "column-count: auto;column-width: 8rem;" into "columns: 8rem auto;"
style.cssText += declaration + ";"; // ";" was previous removed while using split method
// In case property name or value is not supported, browsers skip single declaration, while leaving rest of them intact
let correctlyApplied = style.cssText !== previousCSSText;

let vendorPrefixFound = hasVendorPrefix(declaration);
let propertyName = getPropertyNameNoPrefix(declaration);

if (correctlyApplied && vendorPrefixFound) {
// We are saving applied properties with prefix, so equivalent property with no prefix doesn't need to be supported
appliedPropertiesWithPrefix.add(propertyName);
} else if (!correctlyApplied && !vendorPrefixFound) {
notAppliedProperties.add(propertyName);
}
}

if (notAppliedProperties.size !== 0) {
// If property with vendor prefix is supported, we can ignore the fact that browser doesn't support property with no prefix
for (let substitute of appliedPropertiesWithPrefix) {
notAppliedProperties.delete(substitute);
}
// If any other declaration is not supported, whole block should be marked as invalid
if (notAppliedProperties.size !== 0)
return false;
}
return true;
}

/**
* Checking support for choices inner code and based on that information adding or removing class "invalid" from them.
* This function will change styles of 'example-element', so it is important to apply them again.
* @param choices - elements containing element code, containing css declarations to apply
*/
export function applyInitialSupportWarningState(choices) {
for(let choice of choices) {
let codeBlock = choice.querySelector("code");
applyCode(codeBlock.textContent, codeBlock.parentNode, undefined, true);
}
}, 500);
}

/**
Expand All @@ -38,7 +140,7 @@ export function choose(choice) {
codeBlock.setAttribute("contentEditable", true);
codeBlock.setAttribute("spellcheck", false);

applyCode(codeBlock.textContent, choice);
applyCode(codeBlock.textContent, codeBlock.parentNode);
}

/**
Expand All @@ -65,7 +167,7 @@ export function resetDefault() {
}

/**
* Resets the UI state by deselcting all example choice
* Resets the UI state by deselecting all example choice
*/
export function resetUIState() {
var exampleChoiceList = document.getElementById("example-choice-list");
Expand Down
10 changes: 6 additions & 4 deletions live-examples/css-examples/animation/animation.html
Original file line number Diff line number Diff line change
Expand Up @@ -4,28 +4,30 @@
data-property="animation"
>
<div class="example-choice">
<pre><code class="language-css">animation: slidein 3s ease-in 1s infinite reverse both running;</code></pre>
<pre><code class="language-css">position: relative;</code></pre>
<button type="button" class="copy hidden" aria-hidden="true">
<span class="visually-hidden">Copy to Clipboard</span>
</button>
</div>

<div class="example-choice">
<pre><code class="language-css">animation: slidein 3s linear 1s infinite running;</code></pre>
<pre><code class="language-css">postion: relative;
top: bananas;
left: banded;</code></pre>
<button type="button" class="copy hidden" aria-hidden="true">
<span class="visually-hidden">Copy to Clipboard</span>
</button>
</div>

<div class="example-choice">
<pre><code class="language-css">animation: slidein 3s linear 1s infinite alternate;</code></pre>
<pre><code class="language-css">transform: rotate3d(0);</code></pre>
<button type="button" class="copy hidden" aria-hidden="true">
<span class="visually-hidden">Copy to Clipboard</span>
</button>
</div>

<div class="example-choice">
<pre><code class="language-css">animation: slidein .5s linear 1s infinite alternate;</code></pre>
<pre><code class="language-css">list-style-type: "�F44D"; // thumbs up sign.</code></pre>
<button type="button" class="copy hidden" aria-hidden="true">
<span class="visually-hidden">Copy to Clipboard</span>
</button>
Expand Down
2 changes: 1 addition & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit ed88fed

Please sign in to comment.