From d30be4cbe2104f0db2877d6de928c8cbe42fa995 Mon Sep 17 00:00:00 2001 From: webreflection Date: Mon, 13 Nov 2023 15:12:23 +0100 Subject: [PATCH] forgot to add docs ... --- docs/README.md | 294 +++++++++++++++++++++++++ esm/keyed.js | 2 +- esm/{render-any.js => render-keyed.js} | 0 3 files changed, 295 insertions(+), 1 deletion(-) create mode 100644 docs/README.md rename esm/{render-any.js => render-keyed.js} (100%) diff --git a/docs/README.md b/docs/README.md new file mode 100644 index 0000000..282e0e6 --- /dev/null +++ b/docs/README.md @@ -0,0 +1,294 @@ +### Template Tag Features + +``` + ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ tag +html` +
+ ┃ ┗━━━━━━━━━━━━━ boolean + ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ attribute + +
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ self closing +

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

+
+`; +``` + +
+ tag +
+ +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 +
+ +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 +
+ +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 +
+ +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 +
+ +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 +
+ +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. + +`
    `, `
      `, `` and whatnot, are all valid use cases to use a list placeholder and not some unique node, together with `
      ` and literally any other use case that might render or not multiple nodes in the very same place after updates. + + +```js +import { render, html } from 'uhtml'; + +const handler = { + handleEvent(event) { + console.log(event.type); + } +}; + +render(document.querySelector('#todos'), html` +
        + ${databaseResults.map(value => html`
      • ${value}
      • `)} +
      +`); +``` + +Please note that whenever a specific placeholder in the template might shrink in the future, it is always possible to still use an array to represent a single content: + +```js +html` +
      + ${items.length ? items : [ + html`...loading content` + // still valid hole content + // or a direct DOM node to render + ]} +
      +` +``` + +**Please also note** that an *array* is always expected to contain a *hole* or an actual DOM Node. + +
+
+ +
+ self closing +
+ +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 + + + + + */ +``` + +
+
+ +
+ hole +
+ +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