Skip to content

Commit

Permalink
feat(hiccup-css): add CSSOpts, mediaQuery(), fn expansion
Browse files Browse the repository at this point in the history
- add CSSOpts w/ autoprefix & vendor config
- split FORMATS => COMPACT, PRETTY consts
- update css() to accept options arg
- update css() to accept function as rules
- update formatDecls() to accept fns as attrib values
- add nested indentation support for pretty printing
  • Loading branch information
postspectacular committed Mar 3, 2018
1 parent 5247b8f commit d837199
Show file tree
Hide file tree
Showing 2 changed files with 77 additions and 26 deletions.
97 changes: 74 additions & 23 deletions packages/hiccup-css/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import { IObjectOf } from "@thi.ng/api/api";
import { isArray } from "@thi.ng/checks/is-array";
import { isFunction } from "@thi.ng/checks/is-function";
import { isPlainObject } from "@thi.ng/checks/is-plain-object";
import { transduce } from "@thi.ng/transducers/transduce";
import { permutations } from "@thi.ng/transducers/iter/permutations";
import { repeat } from "@thi.ng/transducers/iter/repeat";
import { str } from "@thi.ng/transducers/rfn/str";
import { flatten } from "@thi.ng/transducers/xform/flatten";
import { map } from "@thi.ng/transducers/xform/map";
Expand All @@ -12,72 +13,122 @@ export interface Format {
ruleSep: string;
valSep: string;
decls: string;
declsStart: string;
declStart: string;
declEnd: string;
indent: string;
}

export interface CSSOpts {
format: Format;
autoprefix: string[] | Set<string>;
vendors: string[];
depth: number;
}

const NO_SPACES = ".:[";

export const FORMATS: IObjectOf<Format> = {
min: { rules: "", ruleSep: ",", valSep: "", decls: "", declsStart: "{", declEnd: "}", indent: "" },
pretty: { rules: "\n", ruleSep: ", ", valSep: " ", decls: "\n", declsStart: " {\n", declEnd: "\n}", indent: " " },
};
export const COMPACT: Format = { rules: "", ruleSep: ",", valSep: "", decls: "", declStart: "{", declEnd: "}", indent: "" };
export const PRETTY: Format = { rules: "\n", ruleSep: ", ", valSep: " ", decls: "\n", declStart: " {\n", declEnd: "}\n", indent: " " };

const xfSel = ((a, b) => (x) => a(b(x)))(
flatten(),
map((x: string) => NO_SPACES.indexOf(x.charAt(0)) >= 0 ? x : " " + x)
);

export function css(rules: any, fmt = FORMATS.min) {
export function css(rules: any, opts?: Partial<CSSOpts>) {
opts = { format: COMPACT, autoprefix: new Set(), vendors: [], depth: 0, ...opts };
if (isArray(opts.autoprefix)) {
opts.autoprefix = new Set(opts.autoprefix);
}
if (isArray(rules)) {
return _css([], [], rules, fmt).join(fmt.rules);
return _css([], [], rules, <CSSOpts>opts).join(opts.format.rules);
}
if (isFunction(rules)) {
return rules([], opts).join(opts.format.rules);
}
if (isPlainObject(rules)) {
return format(rules, fmt);
return formatDecls(rules, <CSSOpts>opts);
}
}

function _css(acc: string[], parent: any[], rules: any[], fmt: Format) {
function _css(acc: string[], parent: any[], rules: any[], opts: CSSOpts) {
const n = rules.length;
const sel: string[] = [];
let curr: any;
for (let i = 0; i < n; i++) {
const r = rules[i];
if (isArray(r)) {
_css(acc, makeSelector(parent, sel), r, fmt);
_css(acc, makeSelector(parent, sel), r, opts);
} else if (isFunction(r)) {
if (parent.length === 0) {
r(acc, opts);
} else {
sel.push(r());
}
} else if (isPlainObject(r)) {
curr = Object.assign(curr || {}, r);
} else {
sel.push(r);
}
}
if (curr) {
acc.push(formatRule(parent, sel, curr, fmt));
acc.push(formatRule(parent, sel, curr, opts));
}
return acc;
}

function format(rules: any, fmt: Format) {
export function mediaQuery(cond, rules: any[]) {
return (acc: string[], opts: CSSOpts) => {
const space = indent(opts);
acc.push(`${space}@media(${cond})${opts.format.declStart}`);
opts.depth++;
_css(acc, [], rules, opts);
opts.depth--;
acc.push(space + opts.format.declEnd);
return acc;
};
}

function formatDecls(rules: any, opts: CSSOpts) {
const f = opts.format;
const space = indent(opts, opts.depth + 1);
const acc = [];
for (let r in rules) {
if (rules.hasOwnProperty(r)) {
acc.push(`${fmt.indent}${r}:${fmt.valSep}${rules[r]};`);
let val = rules[r];
if (isFunction(val)) {
val = val(rules);
}
if ((<Set<string>>opts.autoprefix).has(r)) {
for (let v of opts.vendors) {
acc.push(`${space}${v}-${r}:${f.valSep}${val};`);
}
} else {
acc.push(`${space}${r}:${f.valSep}${val};`);
}
}
}
return acc.join(fmt.decls);
return acc.join(f.decls) + f.decls;
}

function makeSelector(parent: any[], curr: any[]) {
return parent.length ? [...permutations(parent, curr)] : curr;
}

function formatRule(parent: any[], sel: any[], curr: any, fmt: Format) {
return transduce(
map((sel: any[]) => transduce(xfSel, str(), isArray(sel) ? sel : [sel]).trim()),
str(fmt.ruleSep),
makeSelector(parent, sel))
+ fmt.declsStart
+ format(curr, fmt)
+ fmt.declEnd;
function formatRule(parent: any[], sel: any[], curr: any, opts: CSSOpts) {
const f = opts.format;
const space = indent(opts);
return space
+ transduce(
map((sel: any[]) => transduce(xfSel, str(), isArray(sel) ? sel : [sel]).trim()),
str(f.ruleSep),
makeSelector(parent, sel))
+ f.declStart
+ formatDecls(curr, opts)
+ space
+ f.declEnd;
}

function indent(opts: CSSOpts, d = opts.depth) {
return d > 1 ? [...repeat(opts.format.indent, d)].join("") : d > 0 ? opts.format.indent : "";
}
6 changes: 3 additions & 3 deletions packages/hiccup-css/test/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import * as assert from "assert";

import { css, FORMATS } from "../src";
import { css, PRETTY } from "../src";

const rules = {
a: { color: "red" },
Expand Down Expand Up @@ -59,8 +59,8 @@ describe("hiccup-css", () => {
["div", {}],
["[attr]",
["span", rules.a]]]],
FORMATS.pretty),
"#id h1 {\n\n}\n#id h2 div, #id h3 div {\n\n}\n#id h2[attr] span, #id h3[attr] span {\n color:red;\n}"
{ format: PRETTY }),
"#id h1 {\n\n}\n\n#id h2 div, #id h3 div {\n\n}\n\n#id h2[attr] span, #id h3[attr] span {\n color: red;\n}\n"
);
});
});

0 comments on commit d837199

Please sign in to comment.