@lynx-json/jsua-style

0.3.15 • Public • Published

JSUA Style

Querying

You can query an element or an array of elements:

var element = document.createElement("div");
element.id = "one";

query(element).each(el => console.log(`Selected "${el.id}"`));

// => Selected "one"

var elementArray = [element];
query(elementArray).each(el => console.log(`Selected "${el.id}"`));

// => Selected "one"

You can convert a selection to an array:

var element = document.createElement("div");
element.id = "one";

var a = query(element).toArray();

console.log(`a[0] "${a[0].id}"`);
// => Selected "one"

You can select from the query using CSS selectors or predicate selectors:

var element = document.createElement("div");
element.id = "one";

query(element).select("#one").each(el => console.log(`Selected "${el.id}"`));

// => Selected "one"

query(element).select(el => el.id === "one").each(el => console.log(`Selected "${el.id}"`));

// => Selected "one"

You can filter the current selection using CSS selectors or predicate selectors:

var element = document.createElement("div");
element.id = "one";

query(element).filter("#one").each(el => console.log(`Selected "${el.id}"`));

// => Selected "one"

query(element).filter(el => el.id === "one").each(el => console.log(`Selected "${el.id}"`));

// => Selected "one"

In most cases, rather than chaining a filter function to a query, it makes sense to do a simple filter operation (or many consecutive filter operations) while maintaining the original context. This can be done with the standalone filter function.

var parent = document.createElement("div");
parent.id = "parent";
parent.innerHTML = `
<div id="one"></div>
<div id="two"></div>
<div id="three"></div>
`;

query(parent).map(mappers.children()).each([
  filter("#one", el => console.log(`Selected "${el.id}"`)),
  filter("#two", el => console.log(`Selected "${el.id}"`)),
  filter("#three", el => console.log(`Selected "${el.id}"`))
]);

// => Selected "one"
// => Selected "two"
// => Selected "three"

query(parent.firstElementChild).map(el => el.parent).each(el => console.log(`Selected "${el.id}"`));

// => Selected "parent"

Selectors

In addition to standard CSS selectors for select or filter operations, jsua-style includes the following predicate selectors.

firstChild

Select all elements that are the first child matching the specified selector.

var parent = document.createElement("div");

parent.innerHTML = `
<pre id="one"></pre>
<div id="two"></div>
<div id="three">
  <div id="four"></div>
</div>
`;

query(parent).select(firstChild("div")).each(el => console.log(`Selected "${el.id}"`));

// => Selected "two"
// => Selected "four"

lastChild

Select all elements that are the last child matching the specified selector.

var parent = document.createElement("div");

parent.innerHTML = `
<pre id="one"></pre>
<div id="two"></div>
<div id="three">
  <div id="four"></div>
</div>
<pre id="five"></pre>
`;

query(parent).select(lastChild("div")).each(el => console.log(`Selected "${el.id}"`));

// => Selected "three"
// => Selected "four"

nthChild

Select all child elements at a specified index after filtering by the specified selector.

var parent = document.createElement("div");

parent.innerHTML = `
<pre id="one"></pre>
<div id="two"></div>
<div id="three">
  <div id="four"></div>
</div>
<pre id="five"></pre>
`;

query(parent).select(nthChild(0, "div")).each(el => console.log(`Selected "${el.id}"`));

// => Selected "two"
// => Selected "four"

Note that firstChild, lastChild, and nthChild are similar to, but more flexible than, CSS selectors :nth-of-type, :first-of-type, and :last-of-type.

first

Select only the first element in a selection set.

var parent = document.createElement("div");

parent.innerHTML = `
<pre id="one"></pre>
<div id="two"></div>
<div id="three">
  <div id="four"></div>
</div>
<pre id="five"></pre>
`;

query(parent).select(first("pre")).each(el => console.log(`Selected "${el.id}"`));

// => Selected "one"

has

Select only elements that map to a set of related elements.

var parent = document.createElement("div");
parent.id = "parent";

parent.innerHTML = `
<pre id="one"></pre>
<div id="two">
  <pre></pre>
</div>
<div id="three">
  <div></div>
  <div></div>
</div>
<pre id="four"></pre>
`;

query(parent).select(has(mappers.children("div"))).each(el => console.log(`Selected "${el.id}"`));

// => Selected "parent"
// => Selected "three"

hasOne

Select only elements that map to a single related element.

var parent = document.createElement("div");
parent.id = "parent";

parent.innerHTML = `
<pre id="one"></pre>
<div id="two">
  <pre></pre>
</div>
<div id="three">
  <div></div>
</div>
<pre id="four"></pre>
`;

query(parent).select(hasOne(mappers.children("div"))).each(el => console.log(`Selected "${el.id}"`));

// => Selected "three"

Styling

You can apply style to each item in a selection:

var element = document.createElement("div");

query(element).each(el => el.style.backgroundColor = "red");

Mapping

You can map to one or more related elements:

var parent = document.createElement("div");
parent.id = "parent";
parent.innerHTML = `
<div id="one"></div>
<div id="two"></div>
<div id="three"></div>
`;

query(parent).map(el => el.children).each(el => console.log(`Selected "${el.id}"`));

// => Selected "one"
// => Selected "two"
// => Selected "three"

query(parent.firstElementChild).map(el => el.parent).each(el => console.log(`Selected "${el.id}"`));

// => Selected "parent"

In most cases, rather than chaining a map function to a query, it makes sense to do a simple map operation (or many consecutive map operations) while maintaining the original context. This can be done with the standalone map function.

var parent = document.createElement("div");
parent.id = "parent";
parent.innerHTML = `
<div id="one"></div>
<div id="two"></div>
<div id="three"></div>
`;

query(parent).each([
  map("#one", el => console.log(`Selected "${el.id}"`)),
  map("#two", el => console.log(`Selected "${el.id}"`)),
  map("#three", el => console.log(`Selected "${el.id}"`))
]);

// => Selected "one"
// => Selected "two"
// => Selected "three"

query(parent.firstElementChild).map(el => el.parent).each(el => console.log(`Selected "${el.id}"`));

// => Selected "parent"

Mappers

The map function will take any element or iterable of elements. The jsua-style module provides the following commonly used functions.

  • ancestors: All ancestors of the specified element.

All mappers are used as follows:

query(element).map(mappers.ancestors());

first, last, and nth

TODO: Document

children

var parent = document.createElement("div");
parent.id = "parent";
parent.innerHTML = `
<div id="one"></div>
<div id="two"></div>
<div id="three"></div>
`;

query(parent).map(mappers.children()).each(el => console.log(`Selected "${el.id}"`));

// => Selected "one"
// => Selected "two"
// => Selected "three"

// Include an inline filter
query(parent).map(mappers.children("#two")).each(el => console.log(`Selected "${el.id}"`));

// => Selected "two"

realChildren

var parent = document.createElement("div");
parent.id = "parent";
parent.innerHTML = `
<div role="presentation">
  <div id="one"></div>
</div>
<div role="presentation">
  <div role="presentation">
    <div id="two"></div>
  </div>
</div>
<div id="three"></div>
`;

query(parent).map(mappers.realChildren()).each(el => console.log(`Selected "${el.id}"`));

// => Selected "one"
// => Selected "two"
// => Selected "three"

// Include a filter parameter
query(parent).map(mappers.realChildren("#two")).each(el => console.log(`Selected "${el.id}"`));

// => Selected "two"

parent

var parent = document.createElement("div");
parent.id = "parent";
parent.innerHTML = `
<div id="one"></div>
<div id="two"></div>
<div id="three"></div>
`;

query(parent).select("#two").map(mappers.parent()).each(el => console.log(`Selected "${el.id}"`));

// => Selected "parent"

// Include a filter parameter
query(parent).select("#two").map(mappers.parent("#parent")).each(el => console.log(`Selected "${el.id}"`));

// => Selected "parent"

realParent

var parent = document.createElement("div");
parent.id = "parent";
parent.innerHTML = `
<div role="presentation">
  <div id="one"></div>
</div>
<div role="presentation">
  <div role="presentation">
    <div id="two"></div>
  </div>
</div>
<div id="three"></div>
`;

query(parent).select("#two").map(mappers.realParent()).each(el => console.log(`Selected "${el.id}"`));

// => Selected "parent"

// Include a filter parameter
query(parent).select("#two").map(mappers.realParent("#parent")).each(el => console.log(`Selected "${el.id}"`));

// => Selected "parent"

ancestors

var parent = document.createElement("div");
parent.id = "root";
parent.innerHTML = `
<div id="one">
  <div id="two"></div>
</div>
`;

query(parent).select("#two").map(mappers.ancestors()).each(el => console.log(`Selected "${el.id}"`));

// => Selected "one"
// => Selected "root"

// Include an inline filter
query(parent).select("#two").map(mappers.ancestors("#root")).each(el => console.log(`Selected "${el.id}"`));

// => Selected "root"

descendants

var parent = document.createElement("div");
parent.id = "root";
parent.innerHTML = `
<div id="one">
  <div id="two"></div>
</div>
`;

query(parent).map(mappers.descendants()).each(el => console.log(`Selected "${el.id}"`));

// => Selected "one"
// => Selected "two"

// Include an inline filter
query(parent).map(mappers.descendants("#one")).each(el => console.log(`Selected "${el.id}"`));

// => Selected "one"

previousSiblings and nextSiblings

var parent = document.createElement("div");
parent.id = "root";
parent.innerHTML = `
<div id="one"></div>
<div id="two"></div>
<div id="three"></div>
<div id="four"></div>
`;

query(parent).select("#three").map(mappers.previousSiblings()).each(el => console.log(`Selected "${el.id}"`));

// => Selected "two"
// => Selected "one"

// Include a filter parameter
query(parent).select("#two").map(mappers.nextSiblings("#four")).each(el => console.log(`Selected "${el.id}"`));

// => Selected "four"

slot

You can map to a component's named slot (see "Components" below).

query(component).each([
  map(mappers.slot("header"), [
    el => el.style.borderBottom = "1px solid #cccccc"
  ])
]);

In the case where an element uses more than one component, you can pass the name of the component as a second parameter.

query(component).each([
  map(mappers.slot("header", "material-card"), [
    el => el.style.borderBottom = "1px solid #cccccc"
  ])
]);

wrapper

You can wrap an element and return the wrapper.

query(el).each([
  map(mappers.wrapper(), [
    el => el.style.padding = "20px"
  ])
]);

Styling Based on Visual State

You can style an element in one or many states (using on to respond to events and when to apply styles only when the element is in a particular state):

query(element).each([
  el => el.style.backgroundColor = "red",
  on("mouseover", setState("hover")),
  on("mouseout", clearState("hover")),
  when("normal", el => el.style.backgroundColor = "red"),
  when("hover", el => el.style.backgroundColor = "blue"),
  setState("normal")
]);

Styling within a when function will be called in the order of declaration. In the example above, the normal styles will be applied and then, if the element is in the 'hover' state, the hover styles will be applied. The end result, when in the hover state, is a blue background.

Mirroring the State of Another Element

Consider an input field with an invalid input. I'd like to turn the label red when the input is invalid.

Here's the structure of the input field:

<div id="input-field">
  <label>First name</div>
  <input type="text" />
</div>

The input field itself does not have validity, but it can mirror the validity of its input and act as if it does.

select("#input-field", [
  when("validity", "invalid", [
    select("label", el => el.style.color = "red")
  ]),
  // The input field is mirroring the state of its input.
  mirrorState("validity", mappers.children("input"))
])

Other Styling Functions

Context

You can add information about visual context with the context function.

query(element).each([
  context("page")
]);

// Now you can add style based on that context.
query(element).select("[data-jsua-context~=page] > [data-lynx-hints~=header]")
  .each(el => el.style.padding = "16px");

Components

You can create a named component with slotted HTML structure.

var element = document.createElement("div");
var initialHTML = `
<div data-jsua-style-slot-name="label">Label</div>
<div>Other Content</div>
`;

element.innerHTML = initialHTML;

var componentHTML = `
<div role="presentation" data-jsua-style-slot="label"></div>
<div role="presentation" data-jsua-style-slot="content"></div>
`
query(element).each([
  component("material-card", componentHTML)
]);

The component now has the following HTML.

<div data-jsua-style-component="material-card">
  <div role="presentation" data-jsua-style-slot="label">
    <div data-jsua-style-slot-name="label">Label</div>
  </div>
  <div role="presentation" data-jsua-style-slot="content">
    <div>Other Content</div>
  </div>
</div>

By default, children will be added to an element with the attribute data-jsua-style-slot="content". If the child has a data-jsua-style-slot-name attribute, it will be added to an element with the same value for data-jsua-style-slot.

We could also have added an element to a specific slot after the component had been created.

var mapHeader = mappers.realChildren("[data-lynx-hints~=header]");
query(component).each([
  slot("header", mapHeader)
]);

For more on Authoring Components visit the wiki.

Media Queries

You can achieve responsive styling using the media function.

const largeScreen = "(min-width: 840px)";
const mediumScreen = "(min-width: 600px) and (max-width: 839px)";
const smallScreen = "(max-width: 599px)";

query(element).each([
  media(largeScreen, [
    el => el.style.backgroundColor = "green"
  ]),
  media(mediumScreen, [
    el => el.style.backgroundColor = "yellow"
  ]),
  media(smallScreen, [
    el => el.style.backgroundColor = "red"
  ])
]);

Styling Adjustments

After styling has been applied to the element and its children, it is sometimes necessary to make adjustments. For example, it may be necessary to set an overflow state if it turns out there's insufficient room for an element. Use the adjust function for this purpose.

adjust([
  filter(el => el.offsetHeight > 200, setState("overflow")),
  filter(el => el.offsetHeight <= 200, clearState("overflow"))
])

The adjust function is called at the end of a finishing pass and after any changes in the media context. Children are adjusted before parents.

Readme

Keywords

none

Package Sidebar

Install

npm i @lynx-json/jsua-style

Weekly Downloads

1

Version

0.3.15

License

MIT

Unpacked Size

5.94 MB

Total Files

65

Last publish

Collaborators

  • anson.goldade
  • danmork
  • johnhowes