Skip to content

Commit

Permalink
New syntax: boolean attributes
Browse files Browse the repository at this point in the history
Shamelessly borrowed from [lit-html boolean syntax](https://terodox.tech/lit-html-part-2/), and hat tip to the *lit* team for the idea, as `?` works as well as `.` as attributes prefix, this MR brings an end to [the debate about boolean attributes](WebReflection/discussions#13).

This is no breaking change, so it goes as minor update, I hope you'll use, and appreciate, this change 👋
  • Loading branch information
WebReflection committed Feb 11, 2021
1 parent d865699 commit 80e7ad2
Show file tree
Hide file tree
Showing 10 changed files with 70 additions and 50 deletions.
13 changes: 7 additions & 6 deletions DOCUMENTATION.md
Original file line number Diff line number Diff line change
Expand Up @@ -365,14 +365,15 @@ render(document.body, html`

These are the rules to follow for attributes:

* interpolated attributes don't require the usage of quotes, but these work either ways. `name=${value}` is OK, and so is `name="${value}"` or even `name='${value}'`.
* you cannot have sparse attribute interpolations: always use one interpolation to define each attribute that needs one, but never write things like `style="top:${x};left${y}"` as the parser will simply break with the error _bad template_. Use template literals within interpolations, if you want to obtain exact same result: ``style=${`top:${x};left${y}`}``.
* if the passed value is `null` or `undefined`, the attribute will be removed. If the value is something else, it will be set as is as value. If the attribute was previously removed, the same attribute will be placed back again. If the value is the same as it was before, nothing happens.
* interpolated attributes don't require the usage of quotes, but these work either ways. `name=${value}` is OK, and so is `name="${value}"` or even `name='${value}'`
* you cannot have sparse attribute interpolations: always use one interpolation to define each attribute that needs one, but never write things like `style="top:${x};left${y}"` as the parser will simply break with the error _bad template_. Use template literals within interpolations, if you want to obtain exact same result: ``style=${`top:${x};left${y}`}``
* if the passed value is `null` or `undefined`, the attribute will be removed. If the value is something else, it will be set as is as value. If the attribute was previously removed, the same attribute will be placed back again. If the value is the same as it was before, nothing happens
* if the attribute name starts with `on`, as example, `onclick=${...}`, it will be set as listener. If the listener changes, the previous one will be automatically removed. If the listener is an `Array` like `[listener, {once:true}]`, the second entry of the array would be used as listener's options.
* if the attribute starts with a `.` dot, as in `.setter=${value}`, the value will be passed directly to the element per each update. If such value is a known setter, either native elements or defined via Custom Elements, the setter will be invoked per each update, even if the value is the same.
* if the setter name is `dataset`, as in `.dataset=${object}`, the `node.dataset` gets populated with all values.
* if the attribute name is `ref`, as in `ref=${object}`, the `object.current` property will be assigned to the node, once this is rendered, and per each update.
* if the attribute starts with a `.` dot, as in `.setter=${value}`, the value will be passed directly to the element per each update. If such value is a known setter, either native elements or defined via Custom Elements, the setter will be invoked per each update, even if the value is the same
* **new**: if the attribute starts with a `?` question mark, as in `?hidden=${value}`, the value will be toggled, accordingly with its *truthy*, or *falsy*, value.
* if the attribute name is `ref`, as in `ref=${object}`, the `object.current` property will be assigned to the node, once this is rendered, and per each update. If a callback is passed instead, the callback will receive the node right away, same way [React ref](https://reactjs.org/docs/refs-and-the-dom.html) does.
* if the attribute name is `aria`, as in `aria=${object}`, aria attributes are applied to the node, including the `role` one.
* if the attribute name is `.dataset`, as in `.dataset=${object}`, the `node.dataset` gets populated with all values.


Following an example of both `aria` and `.dataset` cases:
Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,7 @@ These are the rules to follow for attributes:
* if the passed value is `null` or `undefined`, the attribute will be removed. If the value is something else, it will be set as is as value. If the attribute was previously removed, the same attribute will be placed back again. If the value is the same as it was before, nothing happens
* if the attribute name starts with `on`, as example, `onclick=${...}`, it will be set as listener. If the listener changes, the previous one will be automatically removed. If the listener is an `Array` like `[listener, {once:true}]`, the second entry of the array would be used as listener's options.
* if the attribute starts with a `.` dot, as in `.setter=${value}`, the value will be passed directly to the element per each update. If such value is a known setter, either native elements or defined via Custom Elements, the setter will be invoked per each update, even if the value is the same
* **new**: if the attribute starts with a `?` question mark, as in `?hidden=${value}`, the value will be toggled, accordingly with its *truthy*, or *falsy*, value.
* if the attribute name is `ref`, as in `ref=${object}`, the `object.current` property will be assigned to the node, once this is rendered, and per each update. If a callback is passed instead, the callback will receive the node right away, same way [React ref](https://reactjs.org/docs/refs-and-the-dom.html) does.
* if the attribute name is `aria`, as in `aria=${object}`, aria attributes are applied to the node, including the `role` one.
* if the attribute name is `.dataset`, as in `.dataset=${object}`, the `node.dataset` gets populated with all values.
Expand Down
2 changes: 1 addition & 1 deletion async.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

24 changes: 10 additions & 14 deletions cjs/handlers.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
'use strict';
const {isArray, slice} = require('uarray');
const udomdiff = (m => m.__esModule ? /* c8 ignore next */ m.default : /* c8 ignore next */ m)(require('udomdiff'));
const {aria, attribute, data, event, ref, setter, text} = require('uhandlers');
const {aria, attribute, boolean, event, ref, setter, text} = require('uhandlers');
const {diffable} = require('uwire');

const {reducePath} = require('./node.js');
Expand Down Expand Up @@ -104,20 +104,16 @@ const handleAnything = comment => {
// * onevent=${...} to automatically handle event listeners
// * generic=${...} to handle an attribute just like an attribute
const handleAttribute = (node, name/*, svg*/) => {
if (name === 'ref')
return ref(node);
switch (name[0]) {
case '?': return boolean(node, name.slice(1));
case '.': return setter(node, name.slice(1));
case 'o': if (name[1] === 'n') return event(node, name);
}

if (name === 'aria')
return aria(node);

if (name === '.dataset')
return data(node);

if (name.slice(0, 1) === '.')
return setter(node, name.slice(1));

if (name.slice(0, 2) === 'on')
return event(node, name);
switch (name) {
case 'ref': return ref(node);
case 'aria': return aria(node);
}

return attribute(node, name/*, svg*/);
};
Expand Down

0 comments on commit 80e7ad2

Please sign in to comment.