Repository: jsdom/cssstyle
Pull request #233 (2025-07-18 22:38:55)

Implement `display` property
Related: https://github.com/jsdom/jsdom/issues/3895


asamuzaK: > Would it be helpful to do a release after merging this?

Yes please.
I will do a fix on monday.


asamuzaK: PR ready.


Patch:
diff --git a/lib/parsers.js b/lib/parsers.js
index b3a6726..1237138 100644
--- a/lib/parsers.js
+++ b/lib/parsers.js
@@ -398,7 +398,7 @@ exports.parseKeyword = function parseKeyword(val, validKeywords = []) {
   if (varRegEx.test(val)) {
     return val;
   }
-  val = asciiLowercase(val.toString());
+  val = asciiLowercase(val);
   if (validKeywords.includes(val) || GLOBAL_VALUE.includes(val)) {
     return val;
   }
diff --git a/lib/properties/display.js b/lib/properties/display.js
new file mode 100644
index 0000000..95db3ca
--- /dev/null
+++ b/lib/properties/display.js
@@ -0,0 +1,217 @@
+"use strict";
+
+const parsers = require("../parsers");
+
+/* keywords */
+const displayOutside = ["block", "inline", "run-in"];
+const displayFlow = ["flow", "flow-root"];
+const displayInside = ["table", "flex", "grid", "ruby", ...displayFlow];
+const displayListItem = ["list-item"];
+const displayInternal = [
+  "table-row-group",
+  "table-header-group",
+  "table-footer-group",
+  "table-row",
+  "table-cell",
+  "table-column-group",
+  "table-column",
+  "table-caption",
+  "ruby-base",
+  "ruby-text",
+  "ruby-base-container",
+  "ruby-text-container"
+];
+const displayBox = ["contents", "none"];
+const displayLegacy = ["inline-block", "inline-table", "inline-flex", "inline-grid"];
+const keywords = [
+  ...displayOutside,
+  ...displayInside,
+  ...displayListItem,
+  ...displayInternal,
+  ...displayBox,
+  ...displayLegacy
+];
+
+module.exports.parse = function parse(v) {
+  if (v === "") {
+    return v;
+  }
+  const values = parsers.splitValue(v);
+  switch (values.length) {
+    case 1: {
+      let [v1] = values;
+      v1 = parsers.parseKeyword(v1, keywords);
+      if (v1) {
+        if (v1 === "flow") {
+          return "block";
+        }
+        return v1;
+      }
+      break;
+    }
+    case 2: {
+      let [v1, v2] = values;
+      v1 = parsers.parseKeyword(v1, keywords);
+      v2 = parsers.parseKeyword(v2, keywords);
+      if (!v1 || !v2) {
+        return;
+      }
+      let outerValue = "";
+      let innerValue = "";
+      if (v1 === "list-item") {
+        outerValue = v2;
+        innerValue = v1;
+      } else if (v2 === "list-item") {
+        outerValue = v1;
+        innerValue = v2;
+      } else if (displayOutside.includes(v1)) {
+        outerValue = v1;
+        innerValue = v2;
+      } else if (displayOutside.includes(v2)) {
+        outerValue = v2;
+        innerValue = v1;
+      }
+      if (innerValue === "list-item") {
+        switch (outerValue) {
+          case "block":
+          case "flow": {
+            return innerValue;
+          }
+          case "flow-root":
+          case "inline":
+          case "run-in": {
+            return `${outerValue} ${innerValue}`;
+          }
+          default:
+        }
+      } else if (outerValue === "block") {
+        switch (innerValue) {
+          case "flow": {
+            return outerValue;
+          }
+          case "flow-root":
+          case "flex":
+          case "grid":
+          case "table": {
+            return innerValue;
+          }
+          case "ruby": {
+            return `${outerValue} ${innerValue}`;
+          }
+          default:
+        }
+      } else if (outerValue === "inline") {
+        switch (innerValue) {
+          case "flow": {
+            return outerValue;
+          }
+          case "flow-root": {
+            return `${outerValue}-block`;
+          }
+          case "flex":
+          case "grid":
+          case "table": {
+            return `${outerValue}-${innerValue}`;
+          }
+          case "ruby": {
+            return innerValue;
+          }
+          default:
+        }
+      } else if (outerValue === "run-in") {
+        switch (innerValue) {
+          case "flow": {
+            return outerValue;
+          }
+          case "flow-root":
+          case "flex":
+          case "grid":
+          case "table":
+          case "ruby": {
+            return `${outerValue} ${innerValue}`;
+          }
+          default:
+        }
+      }
+      break;
+    }
+    case 3: {
+      let [v1, v2, v3] = values;
+      v1 = parsers.parseKeyword(v1, keywords);
+      v2 = parsers.parseKeyword(v2, keywords);
+      v3 = parsers.parseKeyword(v3, keywords);
+      if (!v1 || !v2 || !v3) {
+        return;
+      }
+      let outerValue = "";
+      let flowValue = "";
+      let listItemValue = "";
+      if (v1 === "list-item") {
+        listItemValue = v1;
+        if (displayFlow.includes(v2)) {
+          flowValue = v2;
+          outerValue = v3;
+        } else if (displayFlow.includes(v3)) {
+          flowValue = v3;
+          outerValue = v2;
+        }
+      } else if (v2 === "list-item") {
+        listItemValue = v2;
+        if (displayFlow.includes(v1)) {
+          flowValue = v1;
+          outerValue = v3;
+        } else if (displayFlow.includes(v3)) {
+          flowValue = v3;
+          outerValue = v1;
+        }
+      } else if (v3 === "list-item") {
+        listItemValue = v3;
+        if (displayFlow.includes(v1)) {
+          flowValue = v1;
+          outerValue = v2;
+        } else if (displayFlow.includes(v2)) {
+          flowValue = v2;
+          outerValue = v1;
+        }
+      }
+      if (outerValue && flowValue && listItemValue) {
+        switch (outerValue) {
+          case "block": {
+            if (flowValue === "flow") {
+              return listItemValue;
+            }
+            return `${flowValue} ${listItemValue}`;
+          }
+          case "inline":
+          case "run-in": {
+            if (flowValue === "flow") {
+              return `${outerValue} ${listItemValue}`;
+            }
+            return `${outerValue} ${flowValue} ${listItemValue}`;
+          }
+        }
+      }
+      break;
+    }
+    default:
+  }
+};
+
+module.exports.isValid = function isValid(v) {
+  if (v === "") {
+    return true;
+  }
+  return typeof module.exports.parse(v) === "string";
+};
+
+module.exports.definition = {
+  set(v) {
+    v = parsers.prepareValue(v, this._global);
+    this._setProperty("display", module.exports.parse(v));
+  },
+  get() {
+    return this.getPropertyValue("display");
+  },
+  enumerable: true,
+  configurable: true
+};


Test patch:
diff --git a/test/properties.test.js b/test/properties.test.js
index 4aab15d..9c7c321 100644
--- a/test/properties.test.js
+++ b/test/properties.test.js
@@ -1336,6 +1336,204 @@ describe("color", () => {
   });
 });
 
+describe("display", () => {
+  it("display should set / get keyword", () => {
+    testPropertyValue("display", "block", "block");
+  });
+
+  it("display should set / get keyword", () => {
+    testPropertyValue("display", "inline", "inline");
+  });
+
+  it("display should set / get keyword", () => {
+    testPropertyValue("display", "inline-block", "inline-block");
+  });
+
+  it("display should set / get keyword", () => {
+    testPropertyValue("display", "flex", "flex");
+  });
+
+  it("display should set / get keyword", () => {
+    testPropertyValue("display", "inline-flex", "inline-flex");
+  });
+
+  it("display should set / get keyword", () => {
+    testPropertyValue("display", "grid", "grid");
+  });
+
+  it("display should set / get keyword", () => {
+    testPropertyValue("display", "inline-grid", "inline-grid");
+  });
+
+  it("display should set / get keyword", () => {
+    testPropertyValue("display", "flow", "block");
+  });
+
+  it("display should set / get keyword", () => {
+    testPropertyValue("display", "flow-root", "flow-root");
+  });
+
+  it("display should set / get keyword", () => {
+    testPropertyValue("display", "none", "none");
+  });
+
+  it("display should set / get keyword", () => {
+    testPropertyValue("display", "contents", "contents");
+  });
+
+  it("display should set / get keyword", () => {
+    testPropertyValue("display", "table-row", "table-row");
+  });
+
+  it("display should set / get keyword", () => {
+    testPropertyValue("display", "flow list-item", "list-item");
+  });
+
+  it("display should set / get keyword", () => {
+    testPropertyValue("display", "list-item flow", "list-item");
+  });
+
+  it("display should set / get keyword", () => {
+    testPropertyValue("display", "block list-item", "list-item");
+  });
+
+  it("display should not set / get keyword", () => {
+    testPropertyValue("display", "list-item block", "list-item");
+  });
+
+  it("display should set / get keyword", () => {
+    testPropertyValue("display", "flow-root list-item", "flow-root list-item");
+  });
+
+  it("display should not set / get keyword", () => {
+    testPropertyValue("display", "list-item flow-root", "flow-root list-item");
+  });
+
+  it("display should set / get keyword", () => {
+    testPropertyValue("display", "block flow list-item", "list-item");
+  });
+
+  it("display should set / get keyword", () => {
+    testPropertyValue("display", "block list-item flow", "list-item");
+  });
+
+  it("display should set / get keyword", () => {
+    testPropertyValue("display", "block flow-root list-item", "flow-root list-item");
+  });
+
+  it("display should set / get keyword", () => {
+    testPropertyValue("display", "block list-item flow-root", "flow-root list-item");
+  });
+
+  it("display should set / get keyword", () => {
+    testPropertyValue("display", "flow block list-item", "list-item");
+  });
+
+  it("display should set / get keyword", () => {
+    testPropertyValue("display", "flow list-item block", "list-item");
+  });
+
+  it("display should set / get keyword", () => {
+    testPropertyValue("display", "flow-root block list-item", "flow-root list-item");
+  });
+
+  it("display should set / get keyword", () => {
+    testPropertyValue("display", "flow-root list-item block", "flow-root list-item");
+  });
+
+  it("display should not set / get keyword", () => {
+    testPropertyValue("display", "list-item block flow", "list-item");
+  });
+
+  it("display should not set / get keyword", () => {
+    testPropertyValue("display", "list-item flow block", "list-item");
+  });
+
+  it("display should not set / get keyword", () => {
+    testPropertyValue("display", "list-item block flow-root", "flow-root list-item");
+  });
+
+  it("display should not set / get keyword", () => {
+    testPropertyValue("display", "list-item flow-root block", "flow-root list-item");
+  });
+
+  it("display should set / get keyword", () => {
+    testPropertyValue("display", "inline flow list-item", "inline list-item");
+  });
+
+  it("display should set / get keyword", () => {
+    testPropertyValue("display", "inline flow-root list-item", "inline flow-root list-item");
+  });
+
+  it("display should set / get keyword", () => {
+    testPropertyValue("display", "run-in flow list-item", "run-in list-item");
+  });
+
+  it("display should set / get keyword", () => {
+    testPropertyValue("display", "run-in flow-root list-item", "run-in flow-root list-item");
+  });
+
+  it("display should set / get keyword", () => {
+    testPropertyValue("display", "block flow", "block");
+  });
+
+  it("display should set / get keyword", () => {
+    testPropertyValue("display", "flow block", "block");
+  });
+
+  it("display should set / get keyword", () => {
+    testPropertyValue("display", "block flow-root", "flow-root");
+  });
+
+  it("display should set / get keyword", () => {
+    testPropertyValue("display", "block flex", "flex");
+  });
+
+  it("display should set / get keyword", () => {
+    testPropertyValue("display", "block ruby", "block ruby");
+  });
+
+  it("display should set / get keyword", () => {
+    testPropertyValue("display", "inline flow", "inline");
+  });
+
+  it("display should set / get keyword", () => {
+    testPropertyValue("display", "flow inline", "inline");
+  });
+
+  it("display should set / get keyword", () => {
+    testPropertyValue("display", "inline flow-root", "inline-block");
+  });
+
+  it("display should set / get keyword", () => {
+    testPropertyValue("display", "inline flex", "inline-flex");
+  });
+
+  it("display should set / get keyword", () => {
+    testPropertyValue("display", "inline ruby", "ruby");
+  });
+
+  it("display should set / get keyword", () => {
+    testPropertyValue("display", "run-in flow", "run-in");
+  });
+
+  it("display should set / get keyword", () => {
+    testPropertyValue("display", "flow run-in", "run-in");
+  });
+
+  it("display should set / get keyword", () => {
+    testPropertyValue("display", "run-in flow-root", "run-in flow-root");
+  });
+
+  it("display should set / get keyword", () => {
+    testPropertyValue("display", "run-in flex", "run-in flex");
+  });
+
+  it("display should set / get keyword", () => {
+    testPropertyValue("display", "run-in ruby", "run-in ruby");
+  });
+});
+
 describe("flex box", () => {
   it("flex-grow should set / get number", () => {
     testPropertyValue("flex-grow", ".5", "0.5");