Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

headlong

Package Overview
Dependencies
Maintainers
1
Versions
16
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

headlong - npm Package Compare versions

Comparing version 0.1.2 to 0.1.3

85

index.js
import getConfig from "./lib/config.js";
import getParser from "./lib/parser.js";
import getParser, { hasVariant } from "./lib/parser.js";
import keyframes from "./lib/keyframes.js";
import { preflight, variables } from "./lib/styles.js";
import { preflight as preflightStyles, variables } from "./lib/styles.js";
import { theme } from "./lib/selector.js";
const onObserve = (process, filterClasses, appendStyle, appendStyleMedia) => mutations => {
const onObserve = (process, filterClasses, append) => mutations => {
let styles = [...new Set(

@@ -26,3 +26,3 @@ mutations

.map(i => {
let classes = [];
let c = [];
const node = i.addedNodes[0]

@@ -32,5 +32,5 @@ const all = node.getElementsByTagName('*');

for (var i = -1, l = all.length; ++i < l;) {
classes.push((all[i].classList.value ||'').split(' '));
c.push((all[i].classList.value ||'').split(' '));
}
return [...classes.flat(), ...(node.classList.value || '').split(' ')];
return [...c.flat(), ...(node.classList.value || '').split(' ')];
})

@@ -45,4 +45,3 @@ .flat()

appendStyle(styles.filter(s => !s.includes('@media')).join('\n'));
appendStyleMedia(styles.filter(s => s.includes('@media')).join('\n'));
append(styles);
}

@@ -102,11 +101,25 @@

}
document.head.appendChild(el);
}
export function init(userConfig, container = document.querySelector('body')) {
export function init({
container = document.querySelector('body'),
classes: userClasses = new Set(),
config: userConfig = {},
preflight = true,
} = {}) {
if (window && window.$$headlong) return window.$$headlong;
if (!(userClasses instanceof Set)) {
throw new Error('Classes must be instance of Set');
}
const classes = new Set(userClasses);
const s = document.createElement('style');
s.setAttribute('type', 'text/css');
document.head.appendChild(s);
const med = document.createElement('style');
med.setAttribute('type', 'text/css');
document.head.appendChild(med);

@@ -121,5 +134,9 @@ function appendStyleMedia(css) {

const classes = new Set();
const filterClasses = i => Boolean(i) && typeof i === "string" && !i.startsWith('svelte-');
function append(styles) {
appendStyle(styles.filter(s => !s.includes('@media')).join('\n'));
appendStyleMedia(styles.filter(s => s.includes('@media')).join('\n'));
}
const filterClasses = i => Boolean(i) && !classes.has(i) && typeof i === "string";
const configMerged = mergeUserConfig(getConfig(), userConfig);

@@ -137,15 +154,49 @@ const parse = getParser(configMerged);

const initialStyles = getInitialClasses(process, filterClasses);
appendStyle(preflight + variables(configMerged) + initialStyles.filter(s => !s.includes('@media')).join('\n') + keyframes);
appendStyleMedia(initialStyles.filter(s => s.includes('@media')).join('\n'));
appendStyle(
(preflight ? preflightStyles : "")
+ variables(configMerged)
+ keyframes
);
append(initialStyles);
const classObserver = new MutationObserver(onObserve(process, filterClasses, appendStyle, appendStyleMedia));
const classObserver = new MutationObserver(onObserve(process, filterClasses, append));
observeClasses(classObserver, container);
return {
unsubscribe: () => classObserver.disconnect(),
function output() { // TODO: what output options do we need?
return {
classes: new Set(classes),
styles: `<style>${s.innerText}${med.innerText}</style>`.replace(/\n/, '')
};
}
window.$$headlong = {
unsubscribe: () => {
console.log('Headlong generated styles', output());
classObserver.disconnect();
window.$$headlong = null;
return output();
},
parse,
config: configMerged,
output,
apply: (selector, classList) => {
const arr = classList.split(' ');
const noVariantStyles = arr.filter(s => !hasVariant(s)).map(c => parse(c, true)).join('');
appendStyle(`${selector} { ${noVariantStyles} }`);
// Facing the same problem with @apply variant:class like Tailwind 1.x.
// Got to group all uninque variant groups, capture their styles separately,
// then apply variant groups on selector argument (which can be tricky if we allow arbitrary selector)
// so keeping it simple for now.
// const variantStyles = arr.filter(hasVariant).map(c => parse(c).replace(c.split(":").pop(), selector));
// append(variantStyles);
return `${selector} { ${noVariantStyles} }`;
}
};
return window.$$headlong;
}
export default init;

44

index.md

@@ -9,3 +9,3 @@ # Headlong

This library is not intended to replace the original Tailwind. Yet, there are environments where one cannot use PostCSS or maybe needs to interpolate _the living hell_ out of those class names, or play with configuration.
This library is not intended to replace the original Tailwind. Yet, there are environments where one cannot use PostCSS or maybe needs to interpolate class names a lot, or play with configuration.

@@ -22,3 +22,3 @@ Natural advantage of this approach is zero extra build time, _all_ classes are available by default, no need to enable responsive or whatever plugin.

<div class="text-xs block my-8 font-mono p-2 bg-gray-100 dark:bg-gray-800 items-center shadow-lg">
<div class="text-xs block my-8 font-mono p-2 bg-gray-100 dark:bg-gray-800 justify-center shadow-lg">

@@ -46,5 +46,13 @@ { parsed = headlong.parse(className) }

config,
apply, // not quite there yet
} = headlong(config, containerEl);
// returns { styles, classes } of styles string and set of classes
output,
apply,
} = headlong(
config,
{
container, // container element
classes // list of classes to ignore
});
// ...

@@ -65,4 +73,4 @@

- [x] Min/max breakpoints, object, array notation breakpoints
- [ ] `@apply` as a function
- [ ] Combined selectors like ("sm:dark:hover:")
- [x] `@apply` as a function (working apart from combined selectors)
- [x] Combined selectors like ("sm:dark:hover:")
- [ ] Negated values using css `calc` function relying on PostCSS plugin

@@ -74,3 +82,3 @@ - [ ] Keyframes customization

Please refer to Tailwind [documentation](https://tailwindcss.com/docs) for all available classes. Almost all of them work in headlong.
Please refer to Tailwind [documentation](https://tailwindcss.com/docs) for all available classes.

@@ -96,3 +104,3 @@ ### Placeholder color and opacity

```html
<div class="flex space-x-8 align-center items-center text-lg">
<div class="flex space-x-8 align-center justify-center text-lg">
<div class="w-1/5 h-16 py-4 text-xs bg-purple-300 text-cyan-200 text-center">

@@ -110,6 +118,6 @@ 1

<div class="flex space-x-8 align-center items-center text-lg">
<div class="w-1/5 h-8 py-4 text-xs bg-purple-300 text-cyan-200 text-center">1</div>
<div class="w-1/5 h-8 py-4 text-xs bg-purple-300 text-cyan-200 text-center">2</div>
<div class="w-1/5 h-8 py-4 text-xs bg-purple-300 text-cyan-200 text-center">3</div>
<div class="flex space-x-8 align-center justify-center text-lg font-bold font-mono">
<div class="w-1/5 py-4 bg-purple-300 text-cyan-200 text-center">1</div>
<div class="w-1/5 py-4 bg-purple-300 text-cyan-200 text-center">2</div>
<div class="w-1/5 py-4 bg-purple-300 text-cyan-200 text-center">3</div>
</div>

@@ -165,3 +173,3 @@

<div
class="rounded-t-xl overflow-hidden bg-gradient-to-r from-blue-50 to-light-blue-100 grid grid-cols-1 sm:grid-cols-4 gap-6 justify-items-center p-8"
class="rounded-t-xl overflow-hidden bg-gradient-to-r from-blue-50 to-light-blue-100 grid grid-cols-1 sm:grid-cols-4 gap-6 justify-justify-center p-8"
>

@@ -181,3 +189,3 @@ <div

<div class="rounded-t-xl overflow-hidden bg-gradient-to-r from-blue-50 to-light-blue-100 grid grid-cols-1 sm:grid-cols-4 gap-6 justify-items-center p-8">
<div class="rounded-t-xl overflow-hidden bg-gradient-to-r from-blue-50 to-light-blue-100 grid grid-cols-1 sm:grid-cols-4 gap-6 justify-justify-center p-8">
<div class="focus:outline-none text-sm w-24 py-3 rounded-md font-semibold text-white bg-blue-500 ring ring-blue-200 text-center hover:shadow">

@@ -193,3 +201,6 @@ ring

{ headlong = init({}, document.getElementById('md')) }
{ headlong = init({}, {
container: document.getElementById('md'),
preflight: false,
}) }

@@ -202,2 +213,5 @@ </div>

}
html {
line-height: 1.5;
}
</style>

@@ -14,2 +14,3 @@ import options from "./options.js";

disabled,
checked,
} from "./variants";

@@ -25,2 +26,3 @@

// TODO: user-defined variants
const getVariants = config => ({

@@ -35,2 +37,3 @@ ...Object.fromEntries(

disabled,
checked,
"group-hover": groupHover,

@@ -40,18 +43,3 @@ "group-focus": groupFocus,

// dark:sm:bg-red-500 should produce
// @media screen(max-size: 640px) {
// .mode-dark .dark\:sm\:bg-red-500 {
// background-color: red;
// }
//}
// dark:sm:hover:bg-red-500 should produce
// @media screen(max-size: 640px) {
// .mode-dark .dark\:sm\:hover\:bg-red-500:hover {
// background-color: red;
// }
//}
// so there is order
function parseVariant(className, css, variants) {
function parseVariant(className, css, variants, sanitize) {
const vars = className

@@ -62,25 +50,12 @@ .split(':')

return vars.reduce((acc, cur) => variants[cur](sanitize(className), acc, cur), css); // broken
}
const selector = vars.reduce((acc, cur) => variants[cur](acc), sanitize('.' + className)); // TODO: mind the "."
// escape
const e = c => c.replace(':', '\\:');
if (selector.includes('##style##')) return selector.replace('##style##', css);
// TODO: refactor out
function sanitize(name) {
name = name.replace('/', '\\/');
if (name.includes("placeholder-")) {
name = e(name) + "::placeholder";
} else if (name.includes('space-') || /divide-(?!opacity)/.test(name)) {
name = e(name) + " > * + *";
} else {
name = e(name);
}
return name;
return `${selector} { ${css} }`;
}
const hasVariant = s => /([^:]+:)+[^:]+(::)?[^:]+/.test(s);
export const hasVariant = s => /([^:]+:)+[^:]+(::)?[^:]+/.test(s);
// TODO: refactor for more flexible output
export default function getParse(config) {

@@ -90,10 +65,30 @@ const fns = getFns(config);

return className => {
function escape(s) {
return s.replace(new RegExp(`(${Object.keys(variants).join('|')}):`, 'g'), '$1\\:');
}
function sanitize(name) {
name = name.replace('/', '\\/');
if (name.includes("placeholder-")) {
name = escape(name) + "::placeholder";
} else if (name.includes('space-') || /divide-(?!opacity)/.test(name)) {
name = escape(name) + " > * + *";
} else {
name = escape(name);
}
return name;
}
return (className, onlyStyles = false) => {
const classNameNoVariant = className.split(":").pop();
const css = options[classNameNoVariant] || fns.reduce((acc, cur) => acc || cur(classNameNoVariant), "");
let css = options[classNameNoVariant] || fns.reduce((acc, cur) => acc || cur(classNameNoVariant), "");
if (!css) return false;
if (!css.wrap && !css.endsWith(";")) css += ";";
if (hasVariant(className)) {
return parseVariant(className, css, variants);
return parseVariant(className, css, variants, sanitize);
}

@@ -103,4 +98,6 @@

if (onlyStyles) return css;
return `.${sanitize(className)} { ${css} }`;
}
}

@@ -129,8 +129,11 @@ import { simpleMatch } from "./selector.js";

const contStyles = Object.keys(screens).reduce(
(acc, size) => responsive(screens[size])("container", `max-width: ${screens[size]}; ${padded(size)}`),
"",
) + `.container { max-width: 100%; ${padded("DEFAULT")} }`;
const contStyles = `.container { max-width: 100%; ${padded("DEFAULT")} }`
+ Object.keys(screens).reduce(
(acc, size) => acc + responsive(screens[size])(".container").replace('##style##', `max-width: ${screens[size]}; ${padded(size)}`),
"",
);
console.log({ contStyles });
return name => name === 'container' ? { wrap: () => contStyles } : false;
}

@@ -21,18 +21,23 @@ // TODO: print and other media

}
export const responsive = rule => (className, style) => ` @media ${responsiveRuleString(rule)} {
.${className} { ${style} }
const stylePlaceholder = `{ ##style## }`;
export const responsive = rule => (selector, css) => ` @media ${responsiveRuleString(rule)} {
${selector} ${css ? `{ ${css} }` : stylePlaceholder}
}`;
export const dark = (className, style) => `.mode-dark .${className} { ${style} }`;
// TODO: append dot to className before passing here
export const dark = (selector) => `.mode-dark ${selector}`;
export const hover = (className, style) => `.${className}:hover { ${style} }`;
export const hover = (selector) => `${selector}:hover`;
export const focus = (className, style) => `.${className}:focus { ${style} }`;
export const focus = (selector) => `${selector}:focus`;
export const active = (className, style) => `.${className}:active { ${style} }`;
export const active = (selector) => `${selector}:active`;
export const groupHover = (className, style) => `.group:hover .${className} { ${style} }`;
export const checked = (selector) => `${selector}:checked`;
export const groupFocus = (className, style) => `.group:focus .${className} { ${style} }`;
export const groupHover = (selector) => `.group:hover ${selector}`;
export const disabled = (className, style) => `.${className}[disabled] { ${style} }`;
export const groupFocus = (selector) => `.group:focus ${selector}`;
export const disabled = (selector) => `${selector}[disabled]`;
{
"name": "headlong",
"version": "0.1.2",
"version": "0.1.3",
"description": "Tailwind CSS on the fly",

@@ -5,0 +5,0 @@ "scripts": {

@@ -11,3 +11,3 @@ # Headlong

This library is not intended to replace the original Tailwind. Yet, there are environments where one cannot use PostCSS or maybe needs to interpolate _the living hell_ out of those class names, or play with configuration.
This library is not intended to replace the original Tailwind. Yet, there are environments where one cannot use PostCSS or maybe needs to interpolate class names a lot, or play with configuration.

@@ -40,2 +40,12 @@ Natural advantage of this approach is zero extra build time, _all_ classes are available by default, no need to enable responsive or whatever plugin.

## Changelog
2021/2/20
- Disallow multiple instances of Headlong on the same page
- Add "output" method
- @apply for simple classes
- Combined selectors
- Fix container
- Add :checked variant
## Roadmap

@@ -50,5 +60,5 @@

- [x] Min/max breakpoints, object, array notation breakpoints
- [ ] `@apply` as a function
- [ ] Combined selectors like ("sm:dark:hover:")
- [x] `@apply` as a function (apart from combined variants just like in Tailwind 1.x)
- [x] Combined variants like ("sm:dark:hover:")
- [ ] Negated values using css `calc` function relying on PostCSS plugin
- [ ] Keyframes customization

Sorry, the diff of this file is too big to display

Sorry, the diff of this file is not supported yet

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc