Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/chilled-elephants-kneel.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@acemir/cssom": patch
---

feat: support CSSPageRule and implementation improvements
49 changes: 46 additions & 3 deletions helpers/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,13 +29,41 @@ function getObjectKeysWithGetters(object) {
var keys = Object.keys(object);

// Filter out specific __ prefixed properties that have getter equivalents
var hiddenProperties = ['__parentRule', '__parentStyleSheet', '__selectorText', '__style'];
var hiddenProperties = [
'__conditionText',
'__href',
'__layerName',
'__media',
'__namespaceURI',
'__parentRule',
'__parentStyleSheet',
'__prefix',
'__selectorText',
'__style',
'__styleSheet',
'__supportsText'
];
keys = keys.filter(function(key) {
return hiddenProperties.indexOf(key) === -1;
});

// Add prototype getters for CSSOM objects
var prototypeGetters = ['parentRule', 'parentStyleSheet', 'selectorText', 'style'];
var prototypeGetters = [
'conditionText',
'containerName',
'containerQuery',
'href',
'layerName',
'media',
'namespaceURI',
'parentRule',
'parentStyleSheet',
'prefix',
'selectorText',
'style',
'styleSheet',
'supportsText'
];
for (var i = 0; i < prototypeGetters.length; i++) {
var prop = prototypeGetters[i];
// Check if the property exists as a getter on the prototype chain
Expand Down Expand Up @@ -71,7 +99,22 @@ function materializeGetters(object, stack) {
// Add current object to stack
stack.push(object);

var prototypeGetters = ['parentRule', 'parentStyleSheet', 'selectorText', 'style'];
var prototypeGetters = [
'conditionText',
'containerName',
'containerQuery',
'href',
'layerName',
'media',
'namespaceURI',
'parentRule',
'parentStyleSheet',
'prefix',
'selectorText',
'style',
'styleSheet',
'supportsText'
];
for (var i = 0; i < prototypeGetters.length; i++) {
var prop = prototypeGetters[i];
if (!object.hasOwnProperty(prop) && hasPrototypeGetter(object, prop)) {
Expand Down
9 changes: 7 additions & 2 deletions lib/CSSConditionRule.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,17 @@ var CSSOM = {
*/
CSSOM.CSSConditionRule = function CSSConditionRule() {
CSSOM.CSSGroupingRule.call(this);
this.__conditionText = '';
};

CSSOM.CSSConditionRule.prototype = new CSSOM.CSSGroupingRule();
CSSOM.CSSConditionRule.prototype.constructor = CSSOM.CSSConditionRule;
CSSOM.CSSConditionRule.prototype.conditionText = ''
CSSOM.CSSConditionRule.prototype.cssText = ''

Object.defineProperty(CSSOM.CSSConditionRule.prototype, "conditionText", {
get: function () {
return this.__conditionText;
}
});

//.CommonJS
exports.CSSConditionRule = CSSOM.CSSConditionRule;
Expand Down
32 changes: 20 additions & 12 deletions lib/CSSContainerRule.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,27 +22,35 @@ CSSOM.CSSContainerRule.prototype.constructor = CSSOM.CSSContainerRule;
CSSOM.CSSContainerRule.prototype.type = 17;

Object.defineProperties(CSSOM.CSSContainerRule.prototype, {
"conditionText": {
get: function() {
return this.containerText;
},
set: function(value) {
this.containerText = value;
},
configurable: true,
enumerable: true
},
"cssText": {
get: function() {
var cssTexts = [];
for (var i=0, length=this.cssRules.length; i < length; i++) {
cssTexts.push(this.cssRules[i].cssText);
}
return "@container " + this.containerText + " {" + (cssTexts.length ? "\n " + cssTexts.join("\n ") : "") + "\n}";
return "@container " + this.conditionText + " {" + (cssTexts.length ? "\n " + cssTexts.join("\n ") : "") + "\n}";
},
configurable: true,
enumerable: true
}
},
"containerName": {
get: function() {
var parts = this.conditionText.trim().split(/\s+/);
if (parts.length > 1 && parts[0] !== '(' && !parts[0].startsWith('(')) {
return parts[0];
}
return "";
}
},
"containerQuery": {
get: function() {
var parts = this.conditionText.trim().split(/\s+/);
if (parts.length > 1 && parts[0] !== '(' && !parts[0].startsWith('(')) {
return parts.slice(1).join(' ');
}
return this.conditionText;
}
},
});


Expand Down
111 changes: 92 additions & 19 deletions lib/CSSImportRule.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,21 +14,25 @@ var CSSOM = {
*/
CSSOM.CSSImportRule = function CSSImportRule() {
CSSOM.CSSRule.call(this);
this.href = "";
this.media = new CSSOM.MediaList();
this.layerName = null;
this.supportsText = null;
this.styleSheet = new CSSOM.CSSStyleSheet();
this.__href = "";
this.__media = new CSSOM.MediaList();
this.__layerName = null;
this.__supportsText = null;
this.__styleSheet = new CSSOM.CSSStyleSheet();
};

CSSOM.CSSImportRule.prototype = new CSSOM.CSSRule();
CSSOM.CSSImportRule.prototype.constructor = CSSOM.CSSImportRule;
CSSOM.CSSImportRule.prototype.type = 3;

Object.defineProperty(CSSOM.CSSImportRule.prototype, "type", {
value: 3,
writable: false
});

Object.defineProperty(CSSOM.CSSImportRule.prototype, "cssText", {
get: function() {
var mediaText = this.media.mediaText;
return "@import url(\"" + this.href + "\")" + (this.layerName !== null ? " layer" + (this.layerName && "(" + this.layerName + ")") : "" ) + (this.supportsText ? " supports(" + this.supportsText + ")" : "" ) + (mediaText ? " " + mediaText : "") + ";";
return "@import url(\"" + this.href.replace(/\\/g, '\\\\').replace(/"/g, '\\"') + "\")" + (this.layerName !== null ? " layer" + (this.layerName && "(" + this.layerName + ")") : "" ) + (this.supportsText ? " supports(" + this.supportsText + ")" : "" ) + (mediaText ? " " + mediaText : "") + ";";
},
set: function(cssText) {
var i = 0;
Expand All @@ -47,8 +51,40 @@ Object.defineProperty(CSSOM.CSSImportRule.prototype, "cssText", {

var layerRegExp = /layer\(([^)]*)\)/;
var layerRuleNameRegExp = /^(-?[_a-zA-Z]+(\.[_a-zA-Z]+)*[_a-zA-Z0-9-]*)$/;
var supportsRegExp = /supports\(([^)]+)\)/;
var doubleOrMoreSpacesRegExp = /\s{2,}/g;

/**
* Extracts the content inside supports() handling nested parentheses.
* @param {string} text - The text to parse
* @returns {object|null} - {content: string, endIndex: number} or null if not found
*/
function extractSupportsContent(text) {
var supportsIndex = text.indexOf('supports(');
if (supportsIndex !== 0) {
return null;
}

var depth = 0;
var start = supportsIndex + 'supports('.length;
var i = start;

for (; i < text.length; i++) {
if (text[i] === '(') {
depth++;
} else if (text[i] === ')') {
if (depth === 0) {
// Found the closing parenthesis for supports()
return {
content: text.slice(start, i),
endIndex: i
};
}
depth--;
}
}

return null; // Unbalanced parentheses
}

for (var character; (character = cssText.charAt(i)); i++) {

Expand Down Expand Up @@ -89,7 +125,7 @@ Object.defineProperty(CSSOM.CSSImportRule.prototype, "cssText", {
url = url.slice(1, -1);
}
}
this.href = url;
this.__href = url;
i = index;
state = 'media';
}
Expand All @@ -101,7 +137,7 @@ Object.defineProperty(CSSOM.CSSImportRule.prototype, "cssText", {
if (!index) {
throw i + ": '\"' not found";
}
this.href = cssText.slice(i + 1, index);
this.__href = cssText.slice(i + 1, index);
i = index;
state = 'media';
}
Expand All @@ -113,7 +149,7 @@ Object.defineProperty(CSSOM.CSSImportRule.prototype, "cssText", {
if (!index) {
throw i + ': "\'" not found';
}
this.href = cssText.slice(i + 1, index);
this.__href = cssText.slice(i + 1, index);
i = index;
state = 'media';
}
Expand All @@ -131,7 +167,7 @@ Object.defineProperty(CSSOM.CSSImportRule.prototype, "cssText", {
var layerName = layerMatch[1].trim();

if (layerName.match(layerRuleNameRegExp) !== null) {
this.layerName = layerMatch[1].trim();
this.__layerName = layerMatch[1].trim();
bufferTrimmed = bufferTrimmed.replace(layerRegExp, '')
.replace(doubleOrMoreSpacesRegExp, ' ') // Replace double or more spaces with single space
.trim();
Expand All @@ -144,19 +180,19 @@ Object.defineProperty(CSSOM.CSSImportRule.prototype, "cssText", {
}
}
} else {
this.layerName = "";
this.__layerName = "";
bufferTrimmed = bufferTrimmed.substring('layer'.length).trim()
}
}

var supportsMatch = bufferTrimmed.match(supportsRegExp);
var supportsResult = extractSupportsContent(bufferTrimmed);

if (supportsMatch && supportsMatch.index === 0) {
if (supportsResult) {
// REVIEW: In the browser, an empty supports() invalidates and ignores the entire @import rule
this.supportsText = supportsMatch[1].trim();
bufferTrimmed = bufferTrimmed.replace(supportsRegExp, '')
.replace(doubleOrMoreSpacesRegExp, ' ') // Replace double or more spaces with single space
.trim();
this.__supportsText = supportsResult.content.trim();
// Remove the entire supports(...) from the buffer
bufferTrimmed = bufferTrimmed.slice(0, 0) + bufferTrimmed.slice(supportsResult.endIndex + 1);
bufferTrimmed = bufferTrimmed.replace(doubleOrMoreSpacesRegExp, ' ').trim();
}

// REVIEW: In the browser, any invalid media is replaced with 'not all'
Expand All @@ -177,6 +213,43 @@ Object.defineProperty(CSSOM.CSSImportRule.prototype, "cssText", {
}
});

Object.defineProperty(CSSOM.CSSImportRule.prototype, "href", {
get: function() {
return this.__href;
}
});

Object.defineProperty(CSSOM.CSSImportRule.prototype, "media", {
get: function() {
return this.__media;
},
set: function(value) {
if (typeof value === "string") {
this.__media.mediaText = value;
} else {
this.__media = value;
}
}
});

Object.defineProperty(CSSOM.CSSImportRule.prototype, "layerName", {
get: function() {
return this.__layerName;
}
});

Object.defineProperty(CSSOM.CSSImportRule.prototype, "supportsText", {
get: function() {
return this.__supportsText;
}
});

Object.defineProperty(CSSOM.CSSImportRule.prototype, "styleSheet", {
get: function() {
return this.__styleSheet;
}
});


//.CommonJS
exports.CSSImportRule = CSSOM.CSSImportRule;
Expand Down
18 changes: 13 additions & 5 deletions lib/CSSMediaRule.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ var CSSOM = {
*/
CSSOM.CSSMediaRule = function CSSMediaRule() {
CSSOM.CSSConditionRule.call(this);
this.media = new CSSOM.MediaList();
this.__media = new CSSOM.MediaList();
};

CSSOM.CSSMediaRule.prototype = new CSSOM.CSSConditionRule();
Expand All @@ -25,16 +25,24 @@ CSSOM.CSSMediaRule.prototype.type = 4;

// https://opensource.apple.com/source/WebCore/WebCore-7611.1.21.161.3/css/CSSMediaRule.cpp
Object.defineProperties(CSSOM.CSSMediaRule.prototype, {
"conditionText": {
"media": {
get: function() {
return this.media.mediaText;
return this.__media;
},
set: function(value) {
this.media.mediaText = value;
if (typeof value === "string") {
this.__media.mediaText = value;
} else {
this.__media = value;
}
},
configurable: true,
enumerable: true
},
"conditionText": {
get: function() {
return this.media.mediaText;
}
},
"cssText": {
get: function() {
var cssTexts = [];
Expand Down
Loading