Skip to content
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

Added support for Array<string | number | boolean | null | undefined | Record<string, boolean>> in classList #308

Open
wants to merge 4 commits into
base: main
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
7 changes: 5 additions & 2 deletions packages/dom-expressions/src/client.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,10 +40,13 @@ export function addEventListener(
handler: () => void,
delegate: boolean
): void;
type ClassList =
| Record<string, boolean>
| Array<string | number | boolean | null | undefined | Record<string, boolean>>;
export function classList(
node: Element,
value: { [k: string]: boolean },
prev?: { [k: string]: boolean }
value: ClassList,
prev?: ClassList
): { [k: string]: boolean };
export function style(
node: Element,
Expand Down
28 changes: 25 additions & 3 deletions packages/dom-expressions/src/client.js
Original file line number Diff line number Diff line change
Expand Up @@ -130,23 +130,45 @@ export function addEventListener(node, name, handler, delegate) {
} else node.addEventListener(name, handler);
}

export function classListToObject(classList) {
if (Array.isArray(classList)) {
const result = {};

for (let i = 0, len = classList.length; i < len; i++) {
const key = classList[i];
if (typeof key === "object" && key !== null) Object.assign(result, key);
else if (key || key === 0) result[key] = true;
}

return result;
}

return classList;
}

export function classList(node, value, prev = {}) {
const classKeys = Object.keys(value || {}),
prevKeys = Object.keys(prev);
prev = classListToObject(prev);
value = classListToObject(value);

const classKeys = Object.keys(value || {});
const prevKeys = Object.keys(prev);

let i, len;
for (i = 0, len = prevKeys.length; i < len; i++) {
const key = prevKeys[i];
if (!key || key === "undefined" || value[key]) continue;
toggleClassKey(node, key, false);
delete prev[key];
}

for (i = 0, len = classKeys.length; i < len; i++) {
const key = classKeys[i],
classValue = !!value[key];
if (!key || key === "undefined" || prev[key] === classValue || !classValue) continue;
toggleClassKey(node, key, true);
prev[key] = classValue;
}

return prev;
}

Expand Down Expand Up @@ -549,7 +571,7 @@ export function Hydration(props) {
return props.children;
}

const voidFn = () => undefined
const voidFn = () => undefined;

// experimental
export const RequestContext = Symbol();
Expand Down
64 changes: 58 additions & 6 deletions packages/dom-expressions/test/dom/classList.spec.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -71,12 +71,64 @@ describe("Test classList binding", () => {
test("With prop class and className", () => {
let div;
S.root(() => {
div = <div
className="px-1"
class="py-2"
classList={{ "text-sm": true, "danger": false }}
/>;
div = <div className="px-1" class="py-2" classList={{ "text-sm": true, danger: false }} />;
expect(div.className).toBe("px-1 py-2 text-sm");
});
})
});

test("Array of strings", () => {
let div;
S.root(() => {
div = <div classList={["one", "two", "three"]} />;
expect(div.className).toBe("one two three");
});
});

test("Array of strings and booleans", () => {
let div;
S.root(() => {
div = <div classList={["one", false, "three"]} />;
expect(div.className).toBe("one three");
});
});

test("Array of Array of strings with trailing spaces", () => {
let div;
S.root(() => {
div = <div classList={[" one ", "two", "three"]} />;
expect(div.className).toBe("one two three");
});
});

test("Array with undefined", () => {
let div;
S.root(() => {
div = <div classList={["one", undefined, "three"]} />;
expect(div.className).toBe("one three");
});
});

test("Array with null", () => {
let div;
S.root(() => {
div = <div classList={["one", null, "three"]} />;
expect(div.className).toBe("one three");
});
});

test("Array with numbers", () => {
let div;
S.root(() => {
div = <div classList={["one", 0, "three"]} />;
expect(div.className).toBe("0 one three");
});
});

test("Array with objects", () => {
let div;
S.root(() => {
div = <div classList={["one", "two", "three", { two: false, four: true }]} />;
expect(div.className).toBe("one three four");
});
});
});