mirror of
https://gitlab.com/octtspacc/sitoctt
synced 2025-06-05 22:09:20 +02:00
235 lines
6.3 KiB
JavaScript
235 lines
6.3 KiB
JavaScript
"use strict";
|
|
|
|
const { parse } = require('./style_parser');
|
|
|
|
module.exports = function (elt) {
|
|
const style = new CSSStyleDeclaration(elt)
|
|
const handler = {
|
|
get: function(target, property) {
|
|
return property in target ? target[property] : target.getPropertyValue(dasherizeProperty(property));
|
|
},
|
|
has: function(target, key) {
|
|
return true;
|
|
},
|
|
set: function(target, property, value) {
|
|
if (property in target) {
|
|
target[property] = value;
|
|
} else {
|
|
target.setProperty(dasherizeProperty(property), value ?? undefined);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
};
|
|
|
|
return new Proxy(style, handler);
|
|
};
|
|
|
|
function dasherizeProperty(property) {
|
|
return property.replace(/([a-z])([A-Z])/g, '$1-$2').toLowerCase();
|
|
}
|
|
|
|
|
|
function CSSStyleDeclaration(elt) {
|
|
this._element = elt;
|
|
}
|
|
|
|
const IMPORTANT_BANG = '!important';
|
|
|
|
// Utility function for parsing style declarations
|
|
// Pass in a string like "margin-left: 5px; border-style: solid"
|
|
// and this function returns an object like
|
|
// {"margin-left":"5px", "border-style":"solid"}
|
|
function parseStyles(value) {
|
|
const result = {
|
|
property: {},
|
|
priority: {},
|
|
}
|
|
|
|
if (!value) {
|
|
return result;
|
|
}
|
|
|
|
const styleValues = parse(value);
|
|
if (styleValues.length < 2) {
|
|
return result;
|
|
}
|
|
|
|
for (let i = 0; i < styleValues.length; i += 2) {
|
|
const name = styleValues[i];
|
|
let value = styleValues[i+1];
|
|
|
|
if (value.endsWith(IMPORTANT_BANG)) {
|
|
result.priority[name] = 'important';
|
|
value = value.slice(0, -IMPORTANT_BANG.length).trim();
|
|
}
|
|
|
|
result.property[name] = value;
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
var NO_CHANGE = {}; // Private marker object
|
|
|
|
CSSStyleDeclaration.prototype = Object.create(Object.prototype, {
|
|
|
|
// Return the parsed form of the element's style attribute.
|
|
// If the element's style attribute has never been parsed
|
|
// or if it has changed since the last parse, then reparse it
|
|
// Note that the styles don't get parsed until they're actually needed
|
|
_parsed: { get: function() {
|
|
if (!this._parsedStyles || this.cssText !== this._lastParsedText) {
|
|
var text = this.cssText;
|
|
this._parsedStyles = parseStyles(text);
|
|
this._lastParsedText = text;
|
|
delete this._names;
|
|
}
|
|
return this._parsedStyles;
|
|
}},
|
|
|
|
// Call this method any time the parsed representation of the
|
|
// style changes. It converts the style properties to a string and
|
|
// sets cssText and the element's style attribute
|
|
_serialize: { value: function() {
|
|
var styles = this._parsed;
|
|
var s = "";
|
|
|
|
for(var name in styles.property) {
|
|
if (s) s += " ";
|
|
s += name + ": " + styles.property[name];
|
|
if (styles.priority[name]) {
|
|
s += " !" + styles.priority[name];
|
|
}
|
|
s += ";";
|
|
}
|
|
|
|
this.cssText = s; // also sets the style attribute
|
|
this._lastParsedText = s; // so we don't reparse
|
|
delete this._names;
|
|
}},
|
|
|
|
cssText: {
|
|
get: function() {
|
|
// XXX: this is a CSSStyleDeclaration for an element.
|
|
// A different impl might be necessary for a set of styles
|
|
// associated returned by getComputedStyle(), e.g.
|
|
return this._element.getAttribute("style");
|
|
},
|
|
set: function(value) {
|
|
// XXX: I should parse and serialize the value to
|
|
// normalize it and remove errors. FF and chrome do that.
|
|
this._element.setAttribute("style", value);
|
|
}
|
|
},
|
|
|
|
length: { get: function() {
|
|
if (!this._names)
|
|
this._names = Object.getOwnPropertyNames(this._parsed.property);
|
|
return this._names.length;
|
|
}},
|
|
|
|
item: { value: function(n) {
|
|
if (!this._names)
|
|
this._names = Object.getOwnPropertyNames(this._parsed.property);
|
|
return this._names[n];
|
|
}},
|
|
|
|
getPropertyValue: { value: function(property) {
|
|
property = property.toLowerCase();
|
|
return this._parsed.property[property] || "";
|
|
}},
|
|
|
|
getPropertyPriority: { value: function(property) {
|
|
property = property.toLowerCase();
|
|
return this._parsed.priority[property] || "";
|
|
}},
|
|
|
|
setProperty: { value: function(property, value, priority) {
|
|
property = property.toLowerCase();
|
|
if (value === null || value === undefined) {
|
|
value = "";
|
|
}
|
|
if (priority === null || priority === undefined) {
|
|
priority = "";
|
|
}
|
|
|
|
// String coercion
|
|
if (value !== NO_CHANGE) {
|
|
value = "" + value;
|
|
}
|
|
|
|
value = value.trim();
|
|
if (value === "") {
|
|
this.removeProperty(property);
|
|
return;
|
|
}
|
|
|
|
if (priority !== "" && priority !== NO_CHANGE &&
|
|
!/^important$/i.test(priority)) {
|
|
return;
|
|
}
|
|
|
|
var styles = this._parsed;
|
|
if (value === NO_CHANGE) {
|
|
if (!styles.property[property]) {
|
|
return; // Not a valid property name.
|
|
}
|
|
if (priority !== "") {
|
|
styles.priority[property] = "important";
|
|
} else {
|
|
delete styles.priority[property];
|
|
}
|
|
} else {
|
|
// We don't just accept the property value. Instead
|
|
// we parse it to ensure that it is something valid.
|
|
// If it contains a semicolon it is invalid
|
|
if (value.indexOf(";") !== -1) return;
|
|
|
|
var newprops = parseStyles(property + ":" + value);
|
|
if (Object.getOwnPropertyNames(newprops.property).length === 0) {
|
|
return; // no valid property found
|
|
}
|
|
if (Object.getOwnPropertyNames(newprops.priority).length !== 0) {
|
|
return; // if the value included '!important' it wasn't valid.
|
|
}
|
|
|
|
// XXX handle shorthand properties
|
|
|
|
for (var p in newprops.property) {
|
|
styles.property[p] = newprops.property[p];
|
|
if (priority === NO_CHANGE) {
|
|
continue;
|
|
} else if (priority !== "") {
|
|
styles.priority[p] = "important";
|
|
} else if (styles.priority[p]) {
|
|
delete styles.priority[p];
|
|
}
|
|
}
|
|
}
|
|
|
|
// Serialize and update cssText and element.style!
|
|
this._serialize();
|
|
}},
|
|
|
|
setPropertyValue: { value: function(property, value) {
|
|
return this.setProperty(property, value, NO_CHANGE);
|
|
}},
|
|
|
|
setPropertyPriority: { value: function(property, priority) {
|
|
return this.setProperty(property, NO_CHANGE, priority);
|
|
}},
|
|
|
|
removeProperty: { value: function(property) {
|
|
property = property.toLowerCase();
|
|
var styles = this._parsed;
|
|
if (property in styles.property) {
|
|
delete styles.property[property];
|
|
delete styles.priority[property];
|
|
|
|
// Serialize and update cssText and element.style!
|
|
this._serialize();
|
|
}
|
|
}},
|
|
});
|