Skip to content

Implement toCss utility and fix styles prop #114

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 7 commits into
base: master
Choose a base branch
from
Open
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
13 changes: 13 additions & 0 deletions package-lock.json

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

3 changes: 3 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,9 @@
"url": "https://github.com/atomicojs/atomico/issues"
},
"homepage": "https://github.com/atomicojs/atomico#readme",
"dependencies": {
"csstype": "^3.1.2"
},
"devDependencies": {
"@esm-bundle/chai": "^4.3.4-fix.0",
"@rollup/plugin-node-resolve": "^13.3.0",
Expand Down
4 changes: 2 additions & 2 deletions src/element/set-prototype.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { isFunction, isObject } from "../utils.js";
import { isFunction, isNumber, isObject } from "../utils.js";
import { PropError } from "./errors.js";

export const CUSTOM_TYPE_NAME = "Custom";
Expand Down Expand Up @@ -192,7 +192,7 @@ export const filterValue = (type, value) =>
value,
error:
type == Number
? typeof value != "number"
? !isNumber(value)
? true
: Number.isNaN(value)
: type == String
Expand Down
24 changes: 19 additions & 5 deletions src/render.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,11 @@
import { isFunction, isObject, isArray, flat, isHydrate } from "./utils.js";
import {
isFunction,
isObject,
isArray,
flat,
isHydrate,
isNumber,
} from "./utils.js";
import { options } from "./options.js";
// Object used to know which properties are extracted directly
// from the node to verify 2 if they have changed
Expand Down Expand Up @@ -31,6 +38,11 @@ const INTERNAL_PROPS = {
const EMPTY_PROPS = {};
// Immutable for empty children comparison
const EMPTY_CHILDREN = [];
/**
* @see https://github.com/preactjs/preact/blob/main/src/constants.js
*/
export const IS_NON_DIMENSIONAL =
/acit|ex(?:s|g|n|p|$)|rph|grid|ows|mnc|ntw|ine[ch]|zoo|^ord|itera/i;
// Alias for document
export const $ = document;
// Fragment marker
Expand Down Expand Up @@ -303,7 +315,7 @@ export function renderChildren(children, fragment, parent, id, hydrate, isSvg) {

children &&
flat(children, (child) => {
if (typeof child == "object" && child.$$ != $$) {
if (isObject(child) && child.$$ != $$) {
return;
}

Expand Down Expand Up @@ -526,18 +538,20 @@ export function setEvent(node, type, nextHandler, handlers) {
*
* @param {*} style
* @param {string} key
* @param {string} value
* @param {string|number} value
*/
export function setPropertyStyle(style, key, value) {
let method = "setProperty";
if (value == null) {
method = "removeProperty";
value = null;
}
if (~key.indexOf("-")) {
if (key[0] === "-") {
style[method](key, value);
} else {
} else if (!isNumber(value) || IS_NON_DIMENSIONAL.test(key)) {
style[key] = value;
} else {
style[key] = `${value}px`;
}
}

Expand Down
26 changes: 24 additions & 2 deletions src/tests/render-internals.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ describe("src/render#setEvent", () => {
//@ts-ignore
setEvent(container, "click", handler, handlers);
container.click();

// @ts-ignore
setEvent(container, "click", null, handlers);
container.click();

Expand All @@ -31,10 +31,30 @@ describe("src/render#setPropertyStyle", () => {

expect(container.style.width).to.equal("100px");

setPropertyStyle(container.style, "width", 100);

expect(container.style.width).to.equal("100px");

setPropertyStyle(container.style, "width", null);

expect(container.style.width).to.equal("");

setPropertyStyle(container.style, "lineHeight", 1);

expect(container.style.lineHeight).to.equal("1");

setPropertyStyle(container.style, "float", "left");

expect(container.style.float).to.equal("left");

setPropertyStyle(container.style, "flex", 1);

expect(container.style.flex).to.equal("1 1 0%");

setPropertyStyle(container.style, "WebkitLineClamp", 2);

expect(container.style.webkitLineClamp).to.equal("2");

setPropertyStyle(container.style, "--my-custom-property", "red");

expect(
Expand All @@ -50,10 +70,12 @@ describe("src/render#diffProps", () => {
const nextProps = { class: "my-class" };
const handlers = {};

// @ts-ignore
diffProps(container, props, nextProps, handlers, false);

expect(container.className).to.equal("my-class");

// @ts-ignore
diffProps(container, nextProps, props, handlers, false);

expect(container.className).to.equal("");
Expand All @@ -77,7 +99,6 @@ describe("src/render#setProperty", () => {
it("setProperty#style", () => {
const container = document.createElement("div");
const handlers = {};
//@ts-ignore
setProperty(
container,
"style",
Expand All @@ -86,6 +107,7 @@ describe("src/render#setProperty", () => {
width: "100px",
},
false,
// @ts-ignore
handlers
);

Expand Down
15 changes: 10 additions & 5 deletions src/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,12 @@ export const isObject = (value) => typeof value == "object";

export const { isArray } = Array;

/**
* @param {any} value
* @returns {value is number}
*/
export const isNumber = (value) => typeof value == "number";

/**
*
* @param {Element & {dataset?:object}} node
Expand All @@ -53,17 +59,16 @@ export function flat(list, callback) {
let { length } = list;
for (let i = 0; i < length; i++) {
const value = list[i];
if (value && Array.isArray(value)) {
if (value && isArray(value)) {
reduce(value);
} else {
const type = typeof value;
if (
value == null ||
type === "function" ||
type === "boolean"
isFunction(value) ||
typeof value === "boolean"
) {
continue;
} else if (type === "string" || type === "number") {
} else if (typeof value === "string" || isNumber(value)) {
if (last == null) last = "";
last += value;
} else {
Expand Down
28 changes: 24 additions & 4 deletions tests/utils.test.js
Original file line number Diff line number Diff line change
@@ -1,22 +1,42 @@
import { expect } from "@esm-bundle/chai";
import { serialize, checkIncompatibility } from "../utils";
import { serialize, checkIncompatibility, toCss } from "../utils";

describe("utils", () => {
it("serialize", () => {
expect(serialize(true && "1", true && "2", true && "3")).to.equal(
"1 2 3"
"1 2 3",
);

expect(serialize(false && "1", true && "2", true && "3")).to.equal(
"2 3"
"2 3",
);

expect(serialize(false && "1", true && "2", false && "3")).to.equal(
"2"
"2",
);
});
it("checkIncompatibility", () => {
expect(checkIncompatibility()).to.instanceOf(Array);
expect(checkIncompatibility().length).to.equal(0);
});
it("toCss", () => {
const extract = (s) => Object.values(s.cssRules).map((v) => v.cssText);
let styleSheet = toCss({
":host": {
width: 696,
height: 100,
flex: 1,
},
".root": {
fontSize: 12,
lineHeight: 1.5,
},
});
expect(extract(styleSheet)).to.eql([
":host { width: 696px; height: 100px; flex: 1 1 0%; }",
".root { font-size: 12px; line-height: 1.5; }",
]);
styleSheet = toCss("https://unpkg.com/open-props");
expect(extract(styleSheet)).to.have.length.gt(1);
});
});
3 changes: 2 additions & 1 deletion types/dom.d.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import * as CSS from "csstype";
import { SVGProperties } from "./dom-svg.js";
import { DOMFormElements, DOMFormElement } from "./dom-html.js";
import { Sheets, Sheet } from "./css.js";
Expand All @@ -19,7 +20,7 @@ type DOMRef<Target> = {
};

interface DOMGenericProperties {
style?: string | Partial<CSSStyleDeclaration> | object;
style?: string | CSS.Properties<string | number>;
class?: string;
id?: string;
slot?: string;
Expand Down
8 changes: 8 additions & 0 deletions types/utils.d.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
import * as CSS from "csstype";
import { Sheet } from "./css.js";

/**
* Filter the parameters and join in a string only those that are considered different from
* `"" | false | 0 | null | undefined`.
Expand All @@ -12,3 +15,8 @@ export function serialize(...args: any): string;
* check Atomico's leveraged compatibility with the current browser
*/
export function checkIncompatibility(): string[];

export function toCss(obj: {
[key: string]: CSS.Properties<string | number>;
}): Sheet;
export function toCss(obj: string): Sheet | undefined;
64 changes: 64 additions & 0 deletions utils.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
import { css } from "./src/css.js";
import { IS_NON_DIMENSIONAL } from "./src/render.js";
import { isNumber, isObject } from "./src/utils.js";

const W = globalThis;

const COMPATIBILITY_LIST = [
Expand Down Expand Up @@ -26,3 +30,63 @@ export const checkIncompatibility = () =>
//@ts-ignore
.map(([check, ctx]) => (!ctx || !(check in ctx) ? check : 0))
.filter((check) => check);

/**
* @type {{[id:string]:string}}
*/
const PROPS = {};

/**
* @param {string} str
*/
const hyphenate = (str) =>
(PROPS[str] =
PROPS[str] ||
str
.replace(/([A-Z])/g, "-$1")
.toLowerCase()
.replace(/^ms-/, "-ms-"));

/**
* @param {object} obj
* @returns {string}
*/
const stringify = (obj) =>
Object.entries(obj)
.map(([key, value]) => {
if (isObject(value)) {
return `${key}{${stringify(value)};}`.replace(/;;/g, ";");
}
if (key[0] === "-") {
return `${key}:${value}`;
}
return `${hyphenate(key)}:${
!isNumber(value) || IS_NON_DIMENSIONAL.test(key)
? value
: `${value}px`
}`;
})
.join(";")
.replace(/};/g, "}");

/**
* Create a Style from an object
* @param {{[key:string]:import("csstype").Properties<string | number>}|string} obj
* @returns {import("./types/css.js").Sheet | undefined}
*/
export function toCss(obj) {
if (typeof obj === "string") {
if (obj.match(/^https?:\/\//)) {
const request = new XMLHttpRequest();
request.open("get", obj, false);
request.send();
if (request.status === 200)
return css`
${request.responseText}
`;
}
}
return css`
${stringify(obj)}
`;
}