diff --git a/README.md b/README.md index 896180c..4d627ef 100644 --- a/README.md +++ b/README.md @@ -12,6 +12,8 @@ _micro html_ is a [lighterhtml](https://github.com/WebReflection/lighterhtml#rea Please check the [Release Notes](https://github.com/WebReflection/uhtml/pull/86) to know more about what improved, what changed or broke and what's not there yet (or anymore). +Please **[read the latest user friendly docs](https://webreflection.github.io/uhtml/)** too if you're either new to this project or curious about how it works. + --- ### V3.0 Update diff --git a/docs/README.md b/docs/README.md new file mode 100644 index 0000000..04f5f69 --- /dev/null +++ b/docs/README.md @@ -0,0 +1,385 @@ +*uhtml* (micro *µ* html) is one of the smallest, fastest, memory consumption friendly, yet zero-tools based, library to help creating or manipulating DOM content. + +It is entirely Web standards based and it adds just the minimal amount of *spices* to the templates literals it's able to understand and optimized for either repeated updates or one-off operations. + +This page describes, without going into too many details, all the features delivered via this module which is roughly 2.5K once minified and compressed, or even bundled within your project. + +- - - + +## In a nutshell + +The following code is an abstract representation of all features delivered by *uhtml* and it's explained in details preserving the same order. + +You can skip to details directly via the following links: + + * [render](./#render) - to reveal tags content + * [tag](./#tag) - to create content + * [boolean](./#boolean) - to toggle attributes + * [attribute](./#attribute) - to assign attributes + * [direct](./#direct) - to assign properties + * [listener](./#listener) - to add listeners + * [list](./#list) - to grow or shrink list of nodes + * [self closing](./#self-closing) - to simplify life + * [hole](./#hole) - to represent generic content + +``` + ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ render + ┃ ┏━━━━━━━━━━━━━━━━━━━ tag +render(document.body, html` +
+ ┃ ┗━━━━━━━━━━━━━ boolean + ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ attribute + + ━━━━━━━━━━━━━━━━━━━━━━━━━ self closing +

+ ${show ? `${order} results` : null} + ┗━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━┛ + ┗━━━━━━━━━━━━━━━━━━━━━ hole +

+
+`); +``` + +- - - + +### render + +
+ details +
+ +To reveal template literal tags within a specific element we need a helper which goal is to understand if the +content to render was already known but also, in case it's a *hole*, to orchestrate a "*smart dance*" to render such content. + +The `render` exported helper is a function that, given a place *where* to render such content, returns that very +same place with all nodes in the right place, nodes returned by the *tag* used to render or, for convenience, after +invoking the callback that will return *tags* returned content to render. + +```js +import { render, html } from 'uhtml'; + +const whom = 'World'; + +render(document.body, () => html`Hello ${whom}!`); + +/** results into + + Hello World! + + */ +``` + +
+
+ + +### tag + +
+ details +
+ +A template literal tag can be either the `html` or the `svg` one, both directly exported from this module: + +```js +import { html, svg } from 'uhtml'; + +html`
+
+ + +### boolean + +
+ details +
+ +Fully inspired by *lit*, boolean attributes are simply a **toggle** indirection to either have, or not, such attribute. + +```js +import { render, html } from 'uhtml'; + +render(document.body, html` +
I am visible
+
I am invisible
+`); + +/** results into + +
I am visible
+ + + */ +``` + +
+
+ + +### attribute + +
+ details +
+ +Every attribute that doesn't have a specialized syntax prefix, such as `?`, `@` or `.`, is handled in the following way and only if different from its previous value: + + * if the exported `attr` *Map* knows the attribute, a callback related to it will be used to update + * `aria` attribute accepts and handle an object literal with `role` and other *aria* attributes + * `class` attribute handles a direct `element.className` assignment + * `data` attribute accepts and handle an object literal with `dataset` names to directly set to the node + * `ref` attribute handles *React* like *ref* property by updating the `ref.current` value to the current node, or invoking `ref(element)` when it's a callback + * `style` attribute handles a direct `element.style.cssText` assignment + * it is possible to augment the `attr` *Map* with any custom attribute name that doesn't have an already known prefix and it's not part of the already known list (although one could override known attributes too). In this case, `attr.set("my-attr", (element, newValue, name, oldValue) => newValue)` is the expected signature to augment attributes in the wild, as the stack retains only the current value and it will invoke the callback only if the new value is different. + * if the attribute is unknown in the `attr` map, a `name in element` check is performed once (per template, not per element) and if that's `true`, a *direct* assignment will be used to update the value + * `"onclick" in element`, like any other native listener, will directly assign the callback via `element[name] = value`, when `value` is different, providing a way to simplify events handling in the wild + * `"value" in input`, like any other understood accessor for the currently related node, will directly use `input[name] = value`, when `value` is different + * `"hidden" in element`, as defined by standard, will also directly set `element[name] = value`, when `value` is different, somehow overlapping with the *boolean* feature + * any other `"accessor" in element` will simply follow the exact same rule and use the direct `element[name] = value`, when `value` is different + * in all other cases the attribute is set via `element.setAttribute(name, value)` and removed via `element.removeAttribute(name)` when `value` is either `null` or `undefined` + +
+
+ + +### direct + +
+ details +
+ +A direct attribute is simply passed along to the element, no matter its name or special standard behavior. + +```js +import { render, html } from 'uhtml'; + +const state = { + some: 'special state' +}; + +render(document.body, html` +
content
+`); + +document.querySelector('#direct').state === state; +// true +``` + +If the name is already a special standard accessor, this will be set with the current value, whenever it's different from the previous one, so that *direct* syntax *could* be also used to set `.hidden` or `.value`, for input or textarea, but that's just explicit, as these accessors would work regardless that way, without needing special syntax hints and as already explained in the *attribute* section. + +
+
+ + +### listener + +
+ details +
+ +As already explained in the *attribute* section, common listeners can be already attached via `onclick=${callback}` and everything would work already as expected, with also less moving parts behind the scene ... but what if the listener is a custom event name or it requires options such as `{ once: true }` ? + +This is where `@click=${[handler, { once: true }]}` helps, so that `addEventListener`, and `removeEventListener` when the listener changes, are used instead of direct `on*=${callback}` assignment. + +```js +import { render, html } from 'uhtml'; + +const handler = { + handleEvent(event) { + console.log(event.type); + } +}; + +render(document.body, html` +
+ content +
+`); + +const div = document.querySelector('div'); + +div.dispatchEvent(new Event('custom:type')); +// logs "custom:type" + +div.click(); +// logs "click" + +div.click(); +// nothing, as it was once +``` + +**Please note** that even if options such as `{ once: true }` are used, if the handler / listener is different each time the listener itself will be added, as for logic sake that's indeed a different listener. + +
+
+ + +### list + +
+ details +
+ +Most of the time, the template defines just static parts of the content and this is not likely to grow or shrink over time *but*, when that's the case or desired, it is possible to use an *array* to delimit an area that over time could grow or shrink. + +`
+
+ + +### self closing + +
+ details +
+ +Fully inspired by *XHTML* first and *JSX* after, any element that self closes won't result into surprises so that *custom-elements* as well as any other standard node that doesn't have nodes in it works out of the box. + +```js +import { render, html } from 'uhtml'; + +render(document.body, html` + + +`); + +/** results into + + + + + */ +``` + +Please note this is an *optional* feature, not a mandatory one: you don't need to self-close standard void elements such as `
`, `` or others, but you can self-close even these if consistency in templates is what you are after. + +
+
+ + +### hole + +
+ details +
+ +Technically speaking, in the template literal tags world all values part of the template are called *interpolations*. + +```js +const tag = (template, interpolations) => { + console.log(template.join()); + // logs "this is , and this is ," + console.log(interpolations); + // logs [1, 2] +}; + +tag`this is ${1} and this is ${2}`; +``` + +Mostly because the name *Interpolation* is both verbose and boring plus it doesn't really describe the value *kind* within a DOM context, in *uhtml* the chosen name for "*yet unknown content to be rendered*" values is *hole*. + +By current TypeScript definition, a *hole* can be either: + + * a `string`, a `boolean` or a `number` to show as it is on the rendered node + * `null` or `undefined` to signal that *hole* has currently no content whatsoever + * an actual `instanceof Hole` exported class, which is what `html` or `svg` tags return once invoked + * an *array* that contains a list of instances of *Hole* or DOM nodes to deal with + +
+
diff --git a/esm/keyed.js b/esm/keyed.js index 426656d..455aeb3 100644 --- a/esm/keyed.js +++ b/esm/keyed.js @@ -3,7 +3,7 @@ import { Hole, unroll } from './rabbit.js'; import { empty, set } from './utils.js'; import { html, svg } from './index.js'; import { attr } from './handler.js'; -import render from './render-any.js'; +import render from './render-keyed.js'; /** @typedef {import("./literals.js").Cache} Cache */ /** @typedef {import("./literals.js").Target} Target */ diff --git a/esm/render-any.js b/esm/render-keyed.js similarity index 100% rename from esm/render-any.js rename to esm/render-keyed.js