Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Toggle #69

Merged
merged 6 commits into from
Dec 11, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
74 changes: 74 additions & 0 deletions src/aria.css
Original file line number Diff line number Diff line change
Expand Up @@ -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%;
}

}
2 changes: 1 addition & 1 deletion src/core/sanitize.css
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}

Expand Down
50 changes: 25 additions & 25 deletions src/js/19.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
/**
/**
* a DOM helper library.
* "1 US$ = 18.5842 TR₺ · Oct 16, 2022, 20:52 UTC"
*/
Expand Down Expand Up @@ -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.
*/
Expand Down Expand Up @@ -103,26 +103,26 @@ 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) (*) (*)
// (end diagram)
//
// 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
Expand Down Expand Up @@ -169,7 +169,7 @@ export function $$(scope, sel) {
* @property {EventTarget} target
* @property {string} type
* @property {EventListener} listener
* @property {object} options
* @property {object} options
*/

/**
Expand Down Expand Up @@ -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(" ")) {
Expand All @@ -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
Expand All @@ -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.
Expand Down Expand Up @@ -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}
Expand Down Expand Up @@ -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
*/
Expand All @@ -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;
Expand All @@ -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]
Expand Down Expand Up @@ -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.
*/
Expand Down
124 changes: 122 additions & 2 deletions www/docs/40-aria.md
Original file line number Diff line number Diff line change
Expand Up @@ -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).

Expand Down Expand Up @@ -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>