aboutsummaryrefslogtreecommitdiffstatshomepage
diff options
context:
space:
mode:
authorDeniz Akşimşek <deniz@aksimsek.tr>2024-12-11 22:28:46 +0300
committerGitHub <noreply@github.com>2024-12-11 22:28:46 +0300
commit1e2d5a420f2b4c6fe0b6bdc2e8adfedaed99af74 (patch)
treefac3ddfe56da6be6da978b994e93dc4c13cb963d
parent0efeb805e84b86f54b8727ea6613e68cbd53a57a (diff)
parent82dee6361c8ee4c213990c646a0d2100a9fd4644 (diff)
downloadmissing-dev.tar.gz
missing-dev.zip
Merge pull request #69 from bigskysoftware/toggleHEADdev
Toggle
-rw-r--r--src/aria.css74
-rw-r--r--src/core/sanitize.css2
-rw-r--r--src/js/19.js50
-rw-r--r--www/docs/40-aria.md124
4 files changed, 222 insertions, 28 deletions
diff --git a/src/aria.css b/src/aria.css
index 6a57157..ed12c76 100644
--- a/src/aria.css
+++ b/src/aria.css
@@ -116,3 +116,77 @@
width: fit-content;
text-align: center;
}
+
+
+input[type=checkbox][role=switch] {
+ all: unset;
+ appearance: none;
+
+ display: inline-grid;
+ vertical-align: middle;
+ grid-template-columns: repeat(2, 1rem);
+ aspect-ratio: 2 / 1;
+ margin-block: auto;
+
+ &::before, &::after {
+ content: '';
+ display: inline-block;
+
+ transition: transform .2s ease-in-out, background-color .2s ease-in-out, background-color .2s ease-in-out;
+ }
+
+ &::before {
+ grid-column: 1 / span 2;
+ grid-row: 1;
+ border: 1px solid var(--graphical-fg);
+ background: var(--bg);
+ border-radius: 9999rem;
+ }
+
+ &::after {
+ --toggle-nub-margin: 2px;
+ grid-column: 1;
+ grid-row: 1;
+ margin: var(--toggle-nub-margin);
+ border-radius: 99999rem;
+ background: var(--graphical-fg);
+ }
+
+ &:checked {
+ &::before {
+ border-color: var(--accent);
+ background: var(--accent);
+ }
+ &::after {
+ transform: translateX(calc(100% + 2 * var(--toggle-nub-margin)));
+ background: var(--bg);
+ }
+ }
+
+ &:indeterminate {
+ &::after {
+ transform: translateX(calc(50% + var(--toggle-nub-margin)));
+ background: var(--bg);
+ border: 1px solid var(--graphical-fg);
+ }
+ }
+
+ &:is(label > *):not(#specificity-hack) {
+ margin-block-end: auto;
+ }
+ &:not(label > *) {
+ padding-block: calc(var(--gap) / 4 + (var(--rhythm) - 1em) / 2);
+ }
+
+ :is(label:has(> &)) {
+ /* Lightning CSS requires :is() when nested selector doesn't start with & */
+ display: flex;
+ gap: var(--gap);
+ flex-direction: row;
+ }
+ :is(label:has(+ &)) {
+ /* Lightning CSS requires :is() when nested selector doesn't start with & */
+ width: 100%;
+ }
+
+}
diff --git a/src/core/sanitize.css b/src/core/sanitize.css
index f2c8f26..9906d65 100644
--- a/src/core/sanitize.css
+++ b/src/core/sanitize.css
@@ -116,7 +116,7 @@ button, input, select {
* Correct the inability to style buttons in iOS and Safari.
*/
-button, [type="button"], [type="reset"], [type="submit"]) {
+button, [type="button"], [type="reset"], [type="submit"] {
-webkit-appearance: button;
}
diff --git a/src/js/19.js b/src/js/19.js
index 43c797b..d238888 100644
--- a/src/js/19.js
+++ b/src/js/19.js
@@ -1,4 +1,4 @@
-/**
+/**
* a DOM helper library.
* "1 US$ = 18.5842 TR₺ · Oct 16, 2022, 20:52 UTC"
*/
@@ -42,13 +42,13 @@
/**
* Creates a logging function.
* The {@link scope} will be prepended to each message logged.
- *
+ *
* We usually use `ilog` as a name for the resulting function.
* It returns its last argument,
* which makes it easier to log intermediate values in an expression:
- *
+ *
* const x = a + ilog("b:", b); // x = a + b
- *
+ *
* @param {string} scope The name of the component/module/etc. that will use this logger.
* @returns {Logger} The `ilog` function.
*/
@@ -103,14 +103,14 @@ export function traverse(
? $(root, selector)
: $$(root, selector).at(-1);
}
-
+
if (!current) return wrapIt();
-
+
// Traverse left to right, bottom to top.
//
// (begin ascii art diagram)
// (R)
- // / \
+ // / \
// (r) (4) <- return value
// / | \ / \
// current -> (1) (2) (3) (*) (*)
@@ -118,11 +118,11 @@ export function traverse(
//
// In the diagram above, 1, 2, 3 are tested by the selector (assuming we
// start at 1). Then, having run out of siblings, we move up (as many times
- // as needed) before advancing, ending up at 4.
+ // as needed) before advancing, ending up at 4.
//
// To "test" an element, ee call Element#matches, then if that returns false,
// querySelector. The querySelector call is how the items marked with
- // asterisks can be checked.
+ // asterisks can be checked.
let cursor = current;
while (true) {
while (cursor[advance] === null) { // 3
@@ -169,7 +169,7 @@ export function $$(scope, sel) {
* @property {EventTarget} target
* @property {string} type
* @property {EventListener} listener
- * @property {object} options
+ * @property {object} options
*/
/**
@@ -211,7 +211,7 @@ export function off({ target, type, listener, options }) {
/**
* "Halt" an event -- convenient wrapper for `preventDefault`, `stopPropagation`, and `stopImmediatePropagation`.
* @param {string} o - How to halt. Space-separated list of "default", "bubbling" and "propagation".
- * @param {Event} e - The event.
+ * @param {Event} e - The event.
*/
export function halt(o, e) {
for (const t of o.split(" ")) {
@@ -226,7 +226,7 @@ export function halt(o, e) {
/**
* Decorator for any event listener to call {@link halt}.
- *
+ *
* on(el, "click", halts("default", e => ...))
*
* @template {Event} T
@@ -251,13 +251,13 @@ export function dispatch(el, type, detail, options) {
/**
* Get, remove or set an attribute.
- *
+ *
* - attr(el, "name") Get the attribute "name"
* - attr(el, "name", "value") Set the attribute "name" to "value"
* - attr(el, "name", null) Remove the attribute "name"
* - attr(el, [ nameA: "valueA", nameB: "valueB" ]) Set the attributes name-a to "valueA", name-b to "valueB"
- *
- * @param {Element} el
+ *
+ * @param {Element} el
* @param {string | Record<string, unknown>} name - The attribute name **or** a map of names to values.
* If an object is passed, camelCase attribute names will be converted to kebab-case.
* @param {unknown} value - The value of the attribute, when setting. Pass `null` to remove an attribute.
@@ -333,9 +333,9 @@ export function htmlescape(s) {
* Template literal that escapes HTML in interpolated values and returns a DocumentFragment.
* Can also be called with a string to parse it as HTML.
* To let trusted HTML through escaping, parse it first:
- *
+ *
* html`<p>My trusted markup: ${html(trustedMarkup)}</p>`
- *
+ *
* @param {TemplateStringsArray | string} str
* @param {...unknown} values
* @returns {DocumentFragment}
@@ -421,10 +421,10 @@ export function prev(root, selector, current, options = {}) {
/**
* Create a handler for keyboard events using a keyboard shortcut DSL.
- *
+ *
* - "ArrowLeft"
* - "Ctrl+Alt+3"
- *
+ *
* @param {Record<string, KeyboardEventListener>} hotkeys
* @returns KeyboardEventListener
*/
@@ -440,7 +440,7 @@ export function hotkey(hotkeys) {
const
tokens = hotkeySpec.split("+"), key = /** @type {string} */ (tokens.pop());
let modifiers = 0 | 0;
- for (const token in tokens)
+ for (const token of tokens)
switch (token.toLowerCase()) {
case "alt": modifiers |= alt; break;
case "ctrl": modifiers |= ctrl; break;
@@ -460,8 +460,8 @@ export function hotkey(hotkeys) {
/**
* Debounce a function.
- *
- * @template {unknown[]} TArgs
+ *
+ * @template {unknown[]} TArgs
* @param {number} t The debounce time.
* @param {(...args: TArgs) => void} f The function.
* @param {object} [options]
@@ -507,13 +507,13 @@ export function behavior(selector, init) {
/**
* @template TData
* @typedef {object} Repeater
- *
+ *
* @property {(datum: TData) => string} idOf
* Returns the HTML id for a given data value.
- *
+ *
* @property {(datum: TData, ctx: { id: string }) => ChildNode} create
* Creates a an element for a data value.
- *
+ *
* @property {(el: Element, datum: TData) => Element | null} update
* Update an element for a new data value.
*/
diff --git a/www/docs/40-aria.md b/www/docs/40-aria.md
index 6c02abd..396837f 100644
--- a/www/docs/40-aria.md
+++ b/www/docs/40-aria.md
@@ -165,7 +165,7 @@ The fiex direction will be set based on `aria-orientation`.
## Feed
-Use `feed` role with `<article/>` children — see [WAI: Feed][]. Nested feeds are supported.
+Use `feed` role with `<article>` children — see [WAI: Feed][]. Nested feeds are supported.
To get the actual behavior of an accessible feed, you can use [Missing.js &sect; Feed](/docs/js#feed).
@@ -198,5 +198,125 @@ To get the actual behavior of an accessible feed, you can use [Missing.js &sect;
</div>
</figure>
-
[WAI: Feed]: https://www.w3.org/WAI/ARIA/apg/patterns/feed/
+
+
+## Toggle Switch
+
+Use `switch` role with `<input type="checkbox">`. The indeterminate state is supported, but it must be set with JavaScript.
+
+<figure>
+<figcaption>Code: Toggle Switches</figcaption>
+
+ ~~~ html
+ <div class="f-switch">
+ <fieldset class="f-col">
+ <legend>Toggles inside labels</legend>
+ <label><input type="checkbox" role="switch">Toggle me</label>
+ <label><input type="checkbox" role="switch" checked>But not me</label>
+ <label><input type="checkbox" role="switch" class="indeterminate">I'm not sure</label>
+ </fieldset>
+ <fieldset class="f-col">
+ <legend>Toggles inside labels, flipped</legend>
+ <label class="justify-content:space-between">Toggle me<input type="checkbox" role="switch"></label>
+ <label class="justify-content:space-between">But not me <input type="checkbox" role="switch" checked></label>
+ <label class="justify-content:space-between">I'm not sure <input type="checkbox" role="switch" class="indeterminate"></label>
+ </fieldset>
+ <script>
+ document.querySelectorAll('.indeterminate').forEach(
+ el => {el.indeterminate = true;}
+ )
+ </script>
+ </div>
+ ~~~
+
+ <div class="f-switch">
+ <fieldset class="f-col">
+ <legend>Toggles inside labels</legend>
+ <label><input type="checkbox" role="switch">Toggle me</label>
+ <label><input type="checkbox" role="switch" checked>But not me</label>
+ <label><input type="checkbox" role="switch" class="indeterminate">I'm not sure</label>
+ </fieldset>
+ <fieldset class="f-col">
+ <legend>Toggles inside labels, flipped</legend>
+ <label class="justify-content:space-between">Toggle me<input type="checkbox" role="switch"></label>
+ <label class="justify-content:space-between">But not me <input type="checkbox" role="switch" checked></label>
+ <label class="justify-content:space-between">I'm not sure <input type="checkbox" role="switch" class="indeterminate"></label>
+ </fieldset>
+ </div>
+
+ ~~~ html
+ <div class="f-switch">
+ <fieldset class="table rows">
+ <legend>Toggles before labels</legend>
+ <div>
+ <input id="toggle-1" type="checkbox" role="switch">
+ <label for="toggle-1">Toggle me</label>
+ </div>
+ <div>
+ <input id="toggle-2"type="checkbox" role="switch" checked>
+ <label for="toggle-2">But not me</label>
+ </div>
+ <div>
+ <input id="toggle-3" type="checkbox" role="switch" class="indeterminate">
+ <label for="toggle-3">I'm not sure</label>
+ </div>
+ </fieldset>
+ <fieldset class="table rows">
+ <legend>Toggles after labels</legend>
+ <div>
+ <label for="toggle-4">Toggle me</label>
+ <input id="toggle-4" type="checkbox" role="switch">
+ </div>
+ <div>
+ <label for="toggle-5">But not me</label>
+ <input id="toggle-5" type="checkbox" role="switch" checked>
+ </div>
+ <div>
+ <label for="toggle-6">I'm not sure</label>
+ <input id="toggle-6" type="checkbox" role="switch" class="indeterminate">
+ </div>
+ </fieldset>
+ <script>
+ document.querySelectorAll('.indeterminate').forEach(
+ el => {el.indeterminate = true;}
+ )
+ </script>
+ </div>
+ ~~~
+
+ <div class="f-switch">
+ <fieldset class="table rows">
+ <legend>Toggles before labels</legend>
+ <div>
+ <input id="toggle-1" type="checkbox" role="switch">
+ <label for="toggle-1">Toggle me</label>
+ </div>
+ <div>
+ <input id="toggle-2"type="checkbox" role="switch" checked>
+ <label for="toggle-2">But not me</label>
+ </div>
+ <div>
+ <input id="toggle-3" type="checkbox" role="switch" class="indeterminate">
+ <label for="toggle-3">I'm not sure</label>
+ </div>
+ </fieldset>
+ <fieldset class="table rows">
+ <legend>Toggles after labels</legend>
+ <div>
+ <label for="toggle-4">Toggle me</label>
+ <input id="toggle-4" type="checkbox" role="switch">
+ </div>
+ <div>
+ <label for="toggle-5">But not me</label>
+ <input id="toggle-5" type="checkbox" role="switch" checked>
+ </div>
+ <div>
+ <label for="toggle-6">I'm not sure</label>
+ <input id="toggle-6" type="checkbox" role="switch" class="indeterminate">
+ </div>
+ </fieldset>
+ </div>
+
+ <script>document.querySelectorAll('.indeterminate').forEach(el => {el.indeterminate = true;})</script>
+</figure>