@ascribed/elements 0.4.4 → 0.4.5-next.34
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CONTRACT.md +23 -3
- package/README.md +5 -3
- package/css/style.css +108 -17
- package/dist/copy.d.ts +2 -0
- package/dist/copy.js +151 -0
- package/dist/copy.js.map +1 -0
- package/dist/index.js +5 -2
- package/dist/index.js.map +1 -1
- package/dist/names.d.ts +21 -0
- package/dist/names.js +21 -0
- package/dist/names.js.map +1 -1
- package/dist/tabs.js +11 -3
- package/dist/tabs.js.map +1 -1
- package/package.json +2 -1
package/CONTRACT.md
CHANGED
|
@@ -15,12 +15,13 @@ The site output (SPEC §9.4) is markdown plus custom elements. This contract is
|
|
|
15
15
|
| A tested example's `@snippet` | [A caption line on its code block](#9-a-tested-code-block), with the class `ascribe-code-tested` | CSS only |
|
|
16
16
|
| A link out of a line of a versioned build | [An ordinary link](#10-a-link-out-of-a-line), marked with `data-ascribe-release-line` | Optional CSS |
|
|
17
17
|
| `@operation` | [`<ascribe-operation>`](#11-ascribe-operation) | CSS only |
|
|
18
|
+
| A code block | [A copy button after its `<pre>`](#12-a-code-blocks-copy-button), which the script adds | JavaScript and CSS |
|
|
18
19
|
|
|
19
20
|
A change to this contract changes the site emitter (`crates/ascribe-emit`), the element library, and the site-render fixtures (`tests/render/`) together.
|
|
20
21
|
|
|
21
22
|
## 0. Rules for every element
|
|
22
23
|
|
|
23
|
-
**Light DOM, CSS, no behavior.** Elements render into the light DOM, so site styles apply and content stays visible to search engines and assistive technology (SPEC §9.7). They're styled with CSS and themed with custom properties whose names start with `--ascribe-`; the package README lists them. The names are part of this contract; their default values aren't, and a release may change them, noting it in the changelog. Only `<ascribe-tabs
|
|
24
|
+
**Light DOM, CSS, no behavior.** Elements render into the light DOM, so site styles apply and content stays visible to search engines and assistive technology (SPEC §9.7). They're styled with CSS and themed with custom properties whose names start with `--ascribe-`; the package README lists them. The names are part of this contract; their default values aren't, and a release may change them, noting it in the changelog. Only `<ascribe-tabs>`, and the copy button the script adds to code blocks (§12), run JavaScript. Everything else must render meaningfully with the library's CSS and no script, and must stay readable with neither.
|
|
24
25
|
|
|
25
26
|
**How the emitter writes elements.** Custom elements are raw HTML blocks in CommonMark, so the layout matters:
|
|
26
27
|
|
|
@@ -98,13 +99,15 @@ A `@variant` group that keeps more than one arm in the build (SPEC §9.3, §9.4)
|
|
|
98
99
|
| `value` | dimensional arms only | The arm's values for the group's `sync` dimension, separated by single spaces: `npm`, or `npm pnpm` for an arm written `{pm=npm\|pnpm}`. Dimension values are names without spaces, so the list is unambiguous. |
|
|
99
100
|
| `label` | yes | What the tab is called. For a labeled arm, the plain text of its title (§0). For a dimensional arm, each of its attributes, in canonical order, as the labels of its values joined by ` / `, with attributes joined by `, `: `npm`, `Quill Cloud, npm`, `npm / pnpm`. |
|
|
100
101
|
|
|
101
|
-
**Children:** the arm's content, as markdown blocks.
|
|
102
|
+
**Children:** first, the tab's `label` as text, a paragraph of its own: `<p class="ascribe-tab-label">`, holding the label escaped as an attribute value is (§0), as one HTML block. Then the arm's content, as markdown blocks. The paragraph is there for a reader of the HTML that runs neither the script nor the stylesheet, such as an HTML-to-text or HTML-to-Markdown converter, which then finds each tab's label before its content ([decision #1038](../../project-docs/decisions/1038-tab-labels-and-code-languages.md)).
|
|
102
103
|
|
|
103
104
|
````html
|
|
104
105
|
<ascribe-tabs sync="pm">
|
|
105
106
|
|
|
106
107
|
<ascribe-tab value="npm" label="npm">
|
|
107
108
|
|
|
109
|
+
<p class="ascribe-tab-label">npm</p>
|
|
110
|
+
|
|
108
111
|
```shell
|
|
109
112
|
npm install -g @quill/agent
|
|
110
113
|
```
|
|
@@ -113,6 +116,8 @@ npm install -g @quill/agent
|
|
|
113
116
|
|
|
114
117
|
<ascribe-tab value="pnpm" label="pnpm">
|
|
115
118
|
|
|
119
|
+
<p class="ascribe-tab-label">pnpm</p>
|
|
120
|
+
|
|
116
121
|
```shell
|
|
117
122
|
pnpm add -g @quill/agent
|
|
118
123
|
```
|
|
@@ -124,11 +129,12 @@ pnpm add -g @quill/agent
|
|
|
124
129
|
|
|
125
130
|
### Rendering
|
|
126
131
|
|
|
127
|
-
**Without JavaScript** every tab shows, one after another, each introduced by its
|
|
132
|
+
**Without JavaScript** every tab shows, one after another, each introduced by its label paragraph, so no content is hidden (SPEC §9.7).
|
|
128
133
|
|
|
129
134
|
**With JavaScript** the element follows the WAI-ARIA Authoring Practices tabs pattern:
|
|
130
135
|
|
|
131
136
|
- It inserts a tab list as its first child: an element with `role="tablist"` holding one `<button role="tab">` per tab, whose text is the tab's `label`. Each `<ascribe-tab>` gets `role="tabpanel"`, an `id` if it has none, and `aria-labelledby` pointing at its button; the buttons get `aria-controls`, `aria-selected`, and a roving `tabindex`.
|
|
137
|
+
- Each tab's label paragraph gets `aria-hidden="true"`, so a screen reader hears the label once, from the tab's button, and the stylesheet puts it out of sight, visually hidden as the pointer for agents is (§8): not `display: none`, so a reader that takes the page's rendered text still finds it.
|
|
132
138
|
- One tab shows at a time; the others get the `hidden` attribute.
|
|
133
139
|
- Left and Right arrows move between tabs and select them, wrapping at the ends; Home and End go to the first and last tab.
|
|
134
140
|
- **Initial selection:** for a group with `sync`, the first tab whose `value` contains the remembered value for that dimension; otherwise, and for labeled groups, the first tab.
|
|
@@ -316,3 +322,17 @@ An operation of a declared API (SPEC §4.13, §7.11), from an `@operation` whose
|
|
|
316
322
|
```
|
|
317
323
|
|
|
318
324
|
**Rendering, with or without JavaScript:** a block holding the line, a deprecated operation's link struck through and its "(deprecated)" in the deprecated state's color. A site styles the method or the path by selecting on the attributes (`ascribe-operation[method="delete"]`). With no CSS, it's the link and its words: the deprecation is in the text, never only in the styling.
|
|
325
|
+
|
|
326
|
+
## 12. A code block's copy button
|
|
327
|
+
|
|
328
|
+
There's no element for a code block, and the emitter writes nothing for this: the library's script adds a button to each code block in the page, a `<pre>` with a `<code>` child, as the site output's fenced code blocks render, including one in a figure with a title or a tested line (§9) and one in a tab (§3). It copies the code ([decision #1011](../../project-docs/decisions/1011-copy-button.md)).
|
|
329
|
+
|
|
330
|
+
- **Where it goes:** the button is a `<button type="button" class="ascribe-copy">`, inserted directly after the `<pre>`, as its next sibling: outside the `<pre>` and its `<code>`, so their text, and an HTML-to-text converter's, is the code alone. It has no text of its own; its words are the stylesheet's (below).
|
|
331
|
+
- **What it copies:** the `<code>` element's text, without the newline that ends the block, and nothing else: not a figure's caption, not the button.
|
|
332
|
+
- **Naming and saying:** its accessible name, `aria-label`, is the stylesheet's word for it when it's made, "Copy". After a click it carries `data-ascribe-copy="copied"`, or `"failed"` when the clipboard refuses, for two seconds, and the page's one live region, a `<div class="ascribe-copy-status" role="status">` the script adds to the end of `<body>`, says the stylesheet's words for that state, "Copied" or "Couldn't copy", then empties.
|
|
333
|
+
- **Placement:** the script gives each `<pre>` an `anchor-name` of its own and the button the same `position-anchor`, in their `style`, and the stylesheet puts the button in the block's top corner, at its inline end. A browser without CSS anchor positioning shows it under the block, at its end.
|
|
334
|
+
- **Code blocks added later,** by a script, or a page swapped in place, get a button too.
|
|
335
|
+
|
|
336
|
+
**Without JavaScript** there's no button, and the page reads as it does with no element library: the script adds the button, and the stylesheet doesn't. **Without a clipboard,** on a page that isn't a secure context, the script adds none either.
|
|
337
|
+
|
|
338
|
+
**Theming:** `--ascribe-copy-display` is the button's `display`, `block` by default; a site sets it to `none` to leave the button out, on `:root` or on any element around the blocks it means. Its colors are `--ascribe-copy-color`, `--ascribe-copy-background`, `--ascribe-copy-hover-background`, and `--ascribe-copy-focus-color`. Its words are generated content, `.ascribe-copy::before`, and `.ascribe-copy[data-ascribe-copy="copied"]::before` and `[data-ascribe-copy="failed"]` for its two states, so a site restyles or translates them once, as it does "Available:", and the button's name and the live region follow. It isn't printed.
|
package/README.md
CHANGED
|
@@ -6,8 +6,9 @@ The custom elements [Ascribe](https://github.com/ascribed-dev/ascribe)'s site ou
|
|
|
6
6
|
`<ascribe-availability-target>`, `<ascribe-group>`, and `<ascribe-for-agents>`.
|
|
7
7
|
|
|
8
8
|
- Elements render into the light DOM and are styled by CSS.
|
|
9
|
-
- Only `<ascribe-tabs>` uses JavaScript
|
|
10
|
-
with its label
|
|
9
|
+
- Only `<ascribe-tabs>` uses JavaScript, and the script adds a copy button to
|
|
10
|
+
each code block. Without the script, every tab shows with its label, nothing
|
|
11
|
+
is hidden, and there's no copy button.
|
|
11
12
|
|
|
12
13
|
An Astro site gets them through `@ascribed/astro`, whose `<Elements />`
|
|
13
14
|
component loads the stylesheet and the script.
|
|
@@ -15,7 +16,8 @@ component loads the stylesheet and the script.
|
|
|
15
16
|
## Using it
|
|
16
17
|
|
|
17
18
|
```js
|
|
18
|
-
// Registers <ascribe-tabs>, <ascribe-tab>, and <ascribe-group
|
|
19
|
+
// Registers <ascribe-tabs>, <ascribe-tab>, and <ascribe-group>, and adds
|
|
20
|
+
// the copy buttons.
|
|
19
21
|
import "@ascribed/elements";
|
|
20
22
|
// The styles. Import this alone to style the elements without the script.
|
|
21
23
|
import "@ascribed/elements/style.css";
|
package/css/style.css
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
/*
|
|
2
2
|
* @ascribed/elements: styles for the Ascribe site output (CONTRACT.md).
|
|
3
3
|
*
|
|
4
|
-
* Everything here works without the script; only <ascribe-tabs
|
|
5
|
-
* behavior. Theme it by setting
|
|
6
|
-
* README.md) on :root or any
|
|
4
|
+
* Everything here works without the script; only <ascribe-tabs>, and the copy
|
|
5
|
+
* button the script adds to code blocks, have behavior. Theme it by setting
|
|
6
|
+
* the --ascribe-* custom properties (listed in README.md) on :root or any
|
|
7
|
+
* ancestor.
|
|
7
8
|
*
|
|
8
9
|
* The colors are light or dark as the page is: dark where the page's
|
|
9
10
|
* `color-scheme` is `dark`, or `light dark` with the reader's system in dark
|
|
@@ -63,6 +64,15 @@
|
|
|
63
64
|
--ascribe-tab-hover-background: #e1ebf1;
|
|
64
65
|
/* End of generated tabs. */
|
|
65
66
|
|
|
67
|
+
/* The copy button on code blocks: `none` leaves it out. */
|
|
68
|
+
--ascribe-copy-display: block;
|
|
69
|
+
/* Generated from design/tokens.toml by scripts/design/tokens.ts: copy, light. */
|
|
70
|
+
--ascribe-copy-color: #47606f;
|
|
71
|
+
--ascribe-copy-background: #ffffff;
|
|
72
|
+
--ascribe-copy-hover-background: #e1ebf1;
|
|
73
|
+
--ascribe-copy-focus-color: #b4530f;
|
|
74
|
+
/* End of generated copy. */
|
|
75
|
+
|
|
66
76
|
/* Availability lifecycle states: an accent color and a background each. */
|
|
67
77
|
/* Generated from design/tokens.toml by scripts/design/tokens.ts: states, light. */
|
|
68
78
|
--ascribe-state-color: #47606f;
|
|
@@ -118,6 +128,13 @@
|
|
|
118
128
|
--ascribe-tab-hover-background: light-dark(#e1ebf1, #1f303e);
|
|
119
129
|
/* End of generated tabs. */
|
|
120
130
|
|
|
131
|
+
/* Generated from design/tokens.toml by scripts/design/tokens.ts: copy, light-dark. */
|
|
132
|
+
--ascribe-copy-color: light-dark(#47606f, #8aa6b8);
|
|
133
|
+
--ascribe-copy-background: light-dark(#ffffff, #112233);
|
|
134
|
+
--ascribe-copy-hover-background: light-dark(#e1ebf1, #1f303e);
|
|
135
|
+
--ascribe-copy-focus-color: light-dark(#b4530f, #f0a35e);
|
|
136
|
+
/* End of generated copy. */
|
|
137
|
+
|
|
121
138
|
/* Generated from design/tokens.toml by scripts/design/tokens.ts: states, light-dark. */
|
|
122
139
|
--ascribe-state-color: light-dark(#47606f, #8aa6b8);
|
|
123
140
|
--ascribe-state-background: light-dark(#deeaf0, #1f303e);
|
|
@@ -136,12 +153,26 @@
|
|
|
136
153
|
|
|
137
154
|
/* A page that says which it is. */
|
|
138
155
|
:root[data-ascribe-scheme="light"]
|
|
139
|
-
:is(
|
|
156
|
+
:is(
|
|
157
|
+
ascribe-note,
|
|
158
|
+
ascribe-steps,
|
|
159
|
+
ascribe-tabs,
|
|
160
|
+
ascribe-availability,
|
|
161
|
+
ascribe-operation,
|
|
162
|
+
.ascribe-copy
|
|
163
|
+
) {
|
|
140
164
|
color-scheme: light;
|
|
141
165
|
}
|
|
142
166
|
|
|
143
167
|
:root[data-ascribe-scheme="dark"]
|
|
144
|
-
:is(
|
|
168
|
+
:is(
|
|
169
|
+
ascribe-note,
|
|
170
|
+
ascribe-steps,
|
|
171
|
+
ascribe-tabs,
|
|
172
|
+
ascribe-availability,
|
|
173
|
+
ascribe-operation,
|
|
174
|
+
.ascribe-copy
|
|
175
|
+
) {
|
|
145
176
|
color-scheme: dark;
|
|
146
177
|
}
|
|
147
178
|
}
|
|
@@ -268,20 +299,15 @@ ascribe-tab[hidden] {
|
|
|
268
299
|
display: none;
|
|
269
300
|
}
|
|
270
301
|
|
|
271
|
-
/* Without the script: every tab shows, introduced by its label
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
margin-
|
|
302
|
+
/* Without the script: every tab shows, introduced by its label, which is
|
|
303
|
+
text in the page, its first paragraph. With it, the tab list carries the
|
|
304
|
+
labels, and the paragraph is out of sight (below). */
|
|
305
|
+
.ascribe-tab-label {
|
|
306
|
+
margin-block: var(--ascribe-space) 0;
|
|
276
307
|
color: var(--ascribe-muted-color);
|
|
277
308
|
font-weight: var(--ascribe-font-weight-strong);
|
|
278
309
|
}
|
|
279
310
|
|
|
280
|
-
/* With the script: the tab list carries the labels. */
|
|
281
|
-
ascribe-tab[role="tabpanel"]::before {
|
|
282
|
-
display: none;
|
|
283
|
-
}
|
|
284
|
-
|
|
285
311
|
ascribe-tab[role="tabpanel"] {
|
|
286
312
|
padding-top: var(--ascribe-space-small);
|
|
287
313
|
}
|
|
@@ -439,14 +465,79 @@ ascribe-group {
|
|
|
439
465
|
color: var(--ascribe-muted-color);
|
|
440
466
|
}
|
|
441
467
|
|
|
468
|
+
/* A code block's copy button (CONTRACT.md §12) -------------------------- */
|
|
469
|
+
|
|
470
|
+
/* The script adds the button after a code block's <pre>, so with no script
|
|
471
|
+
there's none. Its words are generated content: a site restyles or
|
|
472
|
+
translates them here, and they're never in the page's text, nor in what a
|
|
473
|
+
reader copies. A site sets --ascribe-copy-display to `none` to leave the
|
|
474
|
+
button out. Where the browser can anchor it, it sits in the block's top
|
|
475
|
+
corner; elsewhere it's under the block, at its end. */
|
|
476
|
+
.ascribe-copy {
|
|
477
|
+
display: var(--ascribe-copy-display);
|
|
478
|
+
inline-size: fit-content;
|
|
479
|
+
margin-inline-start: auto;
|
|
480
|
+
padding: 0.125rem var(--ascribe-space-small);
|
|
481
|
+
border: var(--ascribe-border-width) solid var(--ascribe-border-color);
|
|
482
|
+
border-radius: var(--ascribe-radius);
|
|
483
|
+
background: var(--ascribe-copy-background);
|
|
484
|
+
color: var(--ascribe-copy-color);
|
|
485
|
+
font-family: var(--ascribe-font-family);
|
|
486
|
+
font-size: var(--ascribe-font-size-small);
|
|
487
|
+
line-height: 1.5;
|
|
488
|
+
cursor: pointer;
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
.ascribe-copy::before {
|
|
492
|
+
content: "Copy";
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
.ascribe-copy[data-ascribe-copy="copied"]::before {
|
|
496
|
+
content: "Copied";
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
.ascribe-copy[data-ascribe-copy="failed"]::before {
|
|
500
|
+
content: "Couldn't copy";
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
.ascribe-copy:hover {
|
|
504
|
+
background: var(--ascribe-copy-hover-background);
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
.ascribe-copy:focus-visible {
|
|
508
|
+
outline: 2px solid var(--ascribe-copy-focus-color);
|
|
509
|
+
outline-offset: 2px;
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
/* The script names each code block as an anchor, and its button's
|
|
513
|
+
`position-anchor` is that name. */
|
|
514
|
+
@supports (anchor-name: --a) {
|
|
515
|
+
.ascribe-copy {
|
|
516
|
+
position: absolute;
|
|
517
|
+
inset-block-start: calc(anchor(start) + var(--ascribe-space-small) / 2);
|
|
518
|
+
inset-inline-end: calc(anchor(end) + var(--ascribe-space-small) / 2);
|
|
519
|
+
margin: 0;
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
@media print {
|
|
524
|
+
.ascribe-copy {
|
|
525
|
+
display: none;
|
|
526
|
+
}
|
|
527
|
+
}
|
|
528
|
+
|
|
442
529
|
/* <ascribe-for-agents> --------------------------------------------------- */
|
|
443
530
|
|
|
444
531
|
/* The pointer to llms.txt: out of sight, but in the page for an agent (and a
|
|
445
532
|
screen reader) to read. Not `display: none`, which some readers skip. A
|
|
446
533
|
table row's "(Available: " and ")" around its badges are hidden the same
|
|
447
|
-
way, so a screen reader says what the badges are, as the Markdown does
|
|
534
|
+
way, so a screen reader says what the badges are, as the Markdown does,
|
|
535
|
+
and so is what a copy button says when it has copied, and a tab's label
|
|
536
|
+
once the tab list shows it, for a reader that takes the page's text. */
|
|
448
537
|
ascribe-for-agents,
|
|
449
|
-
.ascribe-
|
|
538
|
+
ascribe-tab[role="tabpanel"] > .ascribe-tab-label,
|
|
539
|
+
.ascribe-availability-lead-in,
|
|
540
|
+
.ascribe-copy-status {
|
|
450
541
|
position: absolute;
|
|
451
542
|
width: 1px;
|
|
452
543
|
height: 1px;
|
package/dist/copy.d.ts
ADDED
package/dist/copy.js
ADDED
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
// The copy button on code blocks (CONTRACT.md §12). The script adds a
|
|
2
|
+
// <button> after each code block's <pre>, outside it, so the code's text, and
|
|
3
|
+
// a copy of it, never holds the button. The button has no text of its own:
|
|
4
|
+
// its words, "Copy", "Copied", and "Couldn't copy", are the stylesheet's
|
|
5
|
+
// generated content, and the script reads them from there for the button's
|
|
6
|
+
// name and for the visually hidden live region that says a click's outcome.
|
|
7
|
+
// Each block is an anchor of its own, with a name the script gives it, so
|
|
8
|
+
// the stylesheet can put the button in the block's corner. Without the
|
|
9
|
+
// script there's no button, and without a clipboard (a page that isn't a
|
|
10
|
+
// secure context) the script adds none.
|
|
11
|
+
import { CLASS_COPY, CLASS_COPY_STATUS, DATA_COPY } from "./names.js";
|
|
12
|
+
/** How long a button says "Copied" or "Couldn't copy" before it says "Copy" again. */
|
|
13
|
+
const SHOWN_MS = 2000;
|
|
14
|
+
/** What a button says when the stylesheet gives it no words. */
|
|
15
|
+
const FALLBACK = { copy: "Copy", copied: "Copied", failed: "Couldn't copy" };
|
|
16
|
+
const timers = new WeakMap();
|
|
17
|
+
/** The anchor name each block was given; a copy of a block (review's) has none here. */
|
|
18
|
+
const anchors = new WeakMap();
|
|
19
|
+
let anchorCount = 0;
|
|
20
|
+
let status = null;
|
|
21
|
+
/** The code element of a code block: a `<pre>` whose child is a `<code>`. */
|
|
22
|
+
function codeOf(element) {
|
|
23
|
+
if (element.localName !== "pre")
|
|
24
|
+
return null;
|
|
25
|
+
return Array.from(element.children).find((child) => child.localName === "code") ?? null;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Give `pre` its button, unless it's not a code block or already has one, and
|
|
29
|
+
* an anchor name of its own for the button. Returns a button it made.
|
|
30
|
+
*/
|
|
31
|
+
function addButton(pre) {
|
|
32
|
+
if (codeOf(pre) === null || anchors.has(pre))
|
|
33
|
+
return undefined;
|
|
34
|
+
const name = `--ascribe-code-${++anchorCount}`;
|
|
35
|
+
anchors.set(pre, name);
|
|
36
|
+
pre.style.setProperty("anchor-name", name);
|
|
37
|
+
// A copy of a block whose button was copied with it keeps that button.
|
|
38
|
+
const next = pre.nextElementSibling;
|
|
39
|
+
if (next instanceof HTMLButtonElement && next.classList.contains(CLASS_COPY)) {
|
|
40
|
+
next.style.setProperty("position-anchor", name);
|
|
41
|
+
return undefined;
|
|
42
|
+
}
|
|
43
|
+
const button = document.createElement("button");
|
|
44
|
+
button.type = "button";
|
|
45
|
+
button.className = CLASS_COPY;
|
|
46
|
+
button.style.setProperty("position-anchor", name);
|
|
47
|
+
pre.after(button);
|
|
48
|
+
return button;
|
|
49
|
+
}
|
|
50
|
+
/** Give every code block in `root`, and `root` itself, a button. */
|
|
51
|
+
function addButtons(root) {
|
|
52
|
+
const made = [root, ...Array.from(root.querySelectorAll("pre"))]
|
|
53
|
+
.map(addButton)
|
|
54
|
+
.filter((button) => button !== undefined);
|
|
55
|
+
// Named after they're all in, so the page's style is worked out once.
|
|
56
|
+
for (const button of made)
|
|
57
|
+
button.setAttribute("aria-label", wordsOn(button) || FALLBACK.copy);
|
|
58
|
+
}
|
|
59
|
+
/** The code block a button copies: the nearest `<pre>` before it. */
|
|
60
|
+
function blockOf(button) {
|
|
61
|
+
for (let el = button.previousElementSibling; el !== null; el = el.previousElementSibling) {
|
|
62
|
+
if (codeOf(el) !== null)
|
|
63
|
+
return el;
|
|
64
|
+
}
|
|
65
|
+
return null;
|
|
66
|
+
}
|
|
67
|
+
/** The live region, made once, the first time it's needed. */
|
|
68
|
+
function liveRegion() {
|
|
69
|
+
if (status === null || !status.isConnected) {
|
|
70
|
+
status = document.createElement("div");
|
|
71
|
+
status.className = CLASS_COPY_STATUS;
|
|
72
|
+
status.setAttribute("role", "status");
|
|
73
|
+
document.body.append(status);
|
|
74
|
+
}
|
|
75
|
+
return status;
|
|
76
|
+
}
|
|
77
|
+
/** The words the stylesheet shows on a button, or none. */
|
|
78
|
+
function wordsOn(button) {
|
|
79
|
+
const content = getComputedStyle(button, "::before").content;
|
|
80
|
+
// One string or several (`"Copied" "!"`), as engines serialize `content`.
|
|
81
|
+
const parts = content.match(/"(?:[^"\\]|\\.)*"/g) ?? [];
|
|
82
|
+
return parts.map((part) => part.slice(1, -1).replace(/\\(.)/g, "$1")).join("");
|
|
83
|
+
}
|
|
84
|
+
/** Show `outcome` on the button for a moment, and say it in the live region. */
|
|
85
|
+
function show(button, outcome) {
|
|
86
|
+
const region = liveRegion();
|
|
87
|
+
button.setAttribute(DATA_COPY, outcome);
|
|
88
|
+
// Empty first, so the same words a second time are announced again.
|
|
89
|
+
region.textContent = "";
|
|
90
|
+
region.textContent = wordsOn(button) || FALLBACK[outcome];
|
|
91
|
+
clearTimeout(timers.get(button));
|
|
92
|
+
timers.set(button, setTimeout(() => {
|
|
93
|
+
button.removeAttribute(DATA_COPY);
|
|
94
|
+
if (region.textContent !== "")
|
|
95
|
+
region.textContent = "";
|
|
96
|
+
}, SHOWN_MS));
|
|
97
|
+
}
|
|
98
|
+
async function copy(button) {
|
|
99
|
+
const block = blockOf(button);
|
|
100
|
+
const code = block === null ? null : codeOf(block);
|
|
101
|
+
if (code === null)
|
|
102
|
+
return;
|
|
103
|
+
// The code as written, without the newline that ends the block, so a
|
|
104
|
+
// pasted command doesn't run before the reader has read it.
|
|
105
|
+
const text = (code.textContent ?? "").replace(/\n+$/, "");
|
|
106
|
+
try {
|
|
107
|
+
await navigator.clipboard.writeText(text);
|
|
108
|
+
show(button, "copied");
|
|
109
|
+
}
|
|
110
|
+
catch {
|
|
111
|
+
show(button, "failed");
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
function onClick(event) {
|
|
115
|
+
const target = event.target;
|
|
116
|
+
if (!(target instanceof Element))
|
|
117
|
+
return;
|
|
118
|
+
const button = target.closest(`button.${CLASS_COPY}`);
|
|
119
|
+
if (button !== null)
|
|
120
|
+
void copy(button);
|
|
121
|
+
}
|
|
122
|
+
function setup() {
|
|
123
|
+
addButtons(document.body);
|
|
124
|
+
// Code blocks a page adds later, or a new body: a preview that redraws, a
|
|
125
|
+
// site that swaps pages in place.
|
|
126
|
+
new MutationObserver((records) => {
|
|
127
|
+
for (const record of records) {
|
|
128
|
+
for (const node of Array.from(record.addedNodes)) {
|
|
129
|
+
if (node instanceof Element)
|
|
130
|
+
addButtons(node);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
}).observe(document.documentElement, { childList: true, subtree: true });
|
|
134
|
+
}
|
|
135
|
+
let started = false;
|
|
136
|
+
/** Add copy buttons to the page's code blocks, now and as they're added. */
|
|
137
|
+
export function startCopyButtons() {
|
|
138
|
+
if (started || typeof document === "undefined")
|
|
139
|
+
return;
|
|
140
|
+
started = true;
|
|
141
|
+
if (typeof navigator.clipboard?.writeText !== "function")
|
|
142
|
+
return;
|
|
143
|
+
document.addEventListener("click", onClick);
|
|
144
|
+
if (document.readyState === "loading") {
|
|
145
|
+
document.addEventListener("DOMContentLoaded", setup, { once: true });
|
|
146
|
+
}
|
|
147
|
+
else {
|
|
148
|
+
setup();
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
//# sourceMappingURL=copy.js.map
|
package/dist/copy.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"copy.js","sourceRoot":"","sources":["../src/copy.ts"],"names":[],"mappings":"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","sourcesContent":["// The copy button on code blocks (CONTRACT.md §12). The script adds a\n// <button> after each code block's <pre>, outside it, so the code's text, and\n// a copy of it, never holds the button. The button has no text of its own:\n// its words, \"Copy\", \"Copied\", and \"Couldn't copy\", are the stylesheet's\n// generated content, and the script reads them from there for the button's\n// name and for the visually hidden live region that says a click's outcome.\n// Each block is an anchor of its own, with a name the script gives it, so\n// the stylesheet can put the button in the block's corner. Without the\n// script there's no button, and without a clipboard (a page that isn't a\n// secure context) the script adds none.\n\nimport { CLASS_COPY, CLASS_COPY_STATUS, DATA_COPY } from \"./names.js\";\n\n/** How long a button says \"Copied\" or \"Couldn't copy\" before it says \"Copy\" again. */\nconst SHOWN_MS = 2000;\n\n/** What a button says when the stylesheet gives it no words. */\nconst FALLBACK = { copy: \"Copy\", copied: \"Copied\", failed: \"Couldn't copy\" } as const;\n\ntype Outcome = \"copied\" | \"failed\";\n\nconst timers = new WeakMap<Element, ReturnType<typeof setTimeout>>();\n\n/** The anchor name each block was given; a copy of a block (review's) has none here. */\nconst anchors = new WeakMap<Element, string>();\n\nlet anchorCount = 0;\n\nlet status: HTMLElement | null = null;\n\n/** The code element of a code block: a `<pre>` whose child is a `<code>`. */\nfunction codeOf(element: Element): Element | null {\n if (element.localName !== \"pre\") return null;\n return Array.from(element.children).find((child) => child.localName === \"code\") ?? null;\n}\n\n/**\n * Give `pre` its button, unless it's not a code block or already has one, and\n * an anchor name of its own for the button. Returns a button it made.\n */\nfunction addButton(pre: Element): HTMLButtonElement | undefined {\n if (codeOf(pre) === null || anchors.has(pre)) return undefined;\n const name = `--ascribe-code-${++anchorCount}`;\n anchors.set(pre, name);\n (pre as HTMLElement).style.setProperty(\"anchor-name\", name);\n // A copy of a block whose button was copied with it keeps that button.\n const next = pre.nextElementSibling;\n if (next instanceof HTMLButtonElement && next.classList.contains(CLASS_COPY)) {\n next.style.setProperty(\"position-anchor\", name);\n return undefined;\n }\n const button = document.createElement(\"button\");\n button.type = \"button\";\n button.className = CLASS_COPY;\n button.style.setProperty(\"position-anchor\", name);\n pre.after(button);\n return button;\n}\n\n/** Give every code block in `root`, and `root` itself, a button. */\nfunction addButtons(root: Element): void {\n const made = [root, ...Array.from(root.querySelectorAll(\"pre\"))]\n .map(addButton)\n .filter((button) => button !== undefined);\n // Named after they're all in, so the page's style is worked out once.\n for (const button of made) button.setAttribute(\"aria-label\", wordsOn(button) || FALLBACK.copy);\n}\n\n/** The code block a button copies: the nearest `<pre>` before it. */\nfunction blockOf(button: Element): Element | null {\n for (let el = button.previousElementSibling; el !== null; el = el.previousElementSibling) {\n if (codeOf(el) !== null) return el;\n }\n return null;\n}\n\n/** The live region, made once, the first time it's needed. */\nfunction liveRegion(): HTMLElement {\n if (status === null || !status.isConnected) {\n status = document.createElement(\"div\");\n status.className = CLASS_COPY_STATUS;\n status.setAttribute(\"role\", \"status\");\n document.body.append(status);\n }\n return status;\n}\n\n/** The words the stylesheet shows on a button, or none. */\nfunction wordsOn(button: Element): string {\n const content = getComputedStyle(button, \"::before\").content;\n // One string or several (`\"Copied\" \"!\"`), as engines serialize `content`.\n const parts = content.match(/\"(?:[^\"\\\\]|\\\\.)*\"/g) ?? [];\n return parts.map((part) => part.slice(1, -1).replace(/\\\\(.)/g, \"$1\")).join(\"\");\n}\n\n/** Show `outcome` on the button for a moment, and say it in the live region. */\nfunction show(button: Element, outcome: Outcome): void {\n const region = liveRegion();\n button.setAttribute(DATA_COPY, outcome);\n // Empty first, so the same words a second time are announced again.\n region.textContent = \"\";\n region.textContent = wordsOn(button) || FALLBACK[outcome];\n clearTimeout(timers.get(button));\n timers.set(\n button,\n setTimeout(() => {\n button.removeAttribute(DATA_COPY);\n if (region.textContent !== \"\") region.textContent = \"\";\n }, SHOWN_MS),\n );\n}\n\nasync function copy(button: Element): Promise<void> {\n const block = blockOf(button);\n const code = block === null ? null : codeOf(block);\n if (code === null) return;\n // The code as written, without the newline that ends the block, so a\n // pasted command doesn't run before the reader has read it.\n const text = (code.textContent ?? \"\").replace(/\\n+$/, \"\");\n try {\n await navigator.clipboard.writeText(text);\n show(button, \"copied\");\n } catch {\n show(button, \"failed\");\n }\n}\n\nfunction onClick(event: MouseEvent): void {\n const target = event.target;\n if (!(target instanceof Element)) return;\n const button = target.closest(`button.${CLASS_COPY}`);\n if (button !== null) void copy(button);\n}\n\nfunction setup(): void {\n addButtons(document.body);\n // Code blocks a page adds later, or a new body: a preview that redraws, a\n // site that swaps pages in place.\n new MutationObserver((records) => {\n for (const record of records) {\n for (const node of Array.from(record.addedNodes)) {\n if (node instanceof Element) addButtons(node);\n }\n }\n }).observe(document.documentElement, { childList: true, subtree: true });\n}\n\nlet started = false;\n\n/** Add copy buttons to the page's code blocks, now and as they're added. */\nexport function startCopyButtons(): void {\n if (started || typeof document === \"undefined\") return;\n started = true;\n if (typeof navigator.clipboard?.writeText !== \"function\") return;\n document.addEventListener(\"click\", onClick);\n if (document.readyState === \"loading\") {\n document.addEventListener(\"DOMContentLoaded\", setup, { once: true });\n } else {\n setup();\n }\n}\n"]}
|
package/dist/index.js
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
|
-
// @ascribed/elements: registers Ascribe's custom elements
|
|
2
|
-
// <ascribe-tabs>
|
|
1
|
+
// @ascribed/elements: registers Ascribe's custom elements, and adds a copy
|
|
2
|
+
// button to each code block. Only <ascribe-tabs> and the copy buttons have
|
|
3
|
+
// behavior; the rest are styled by the CSS file
|
|
3
4
|
// (`@ascribed/elements/style.css`), which works without this script.
|
|
5
|
+
import { startCopyButtons } from "./copy.js";
|
|
4
6
|
import { AscribeGroup } from "./group.js";
|
|
5
7
|
import { AscribeTab, AscribeTabs, choose } from "./tabs.js";
|
|
6
8
|
import { ELEMENT_GROUP, ELEMENT_TAB, ELEMENT_TABS } from "./names.js";
|
|
@@ -14,4 +16,5 @@ for (const [name, constructor] of registry) {
|
|
|
14
16
|
if (!customElements.get(name))
|
|
15
17
|
customElements.define(name, constructor);
|
|
16
18
|
}
|
|
19
|
+
startCopyButtons();
|
|
17
20
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,2EAA2E;AAC3E,2EAA2E;AAC3E,gDAAgD;AAChD,qEAAqE;AAErE,OAAO,EAAE,gBAAgB,EAAE,MAAM,WAAW,CAAC;AAC7C,OAAO,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAC1C,OAAO,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,EAAE,MAAM,WAAW,CAAC;AAC5D,OAAO,EAAE,aAAa,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAEtE,OAAO,EAAE,YAAY,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,EAAE,CAAC;AAEzD,MAAM,QAAQ,GAAyC;IACrD,CAAC,YAAY,EAAE,WAAW,CAAC;IAC3B,CAAC,WAAW,EAAE,UAAU,CAAC;IACzB,CAAC,aAAa,EAAE,YAAY,CAAC;CAC9B,CAAC;AAEF,KAAK,MAAM,CAAC,IAAI,EAAE,WAAW,CAAC,IAAI,QAAQ,EAAE,CAAC;IAC3C,IAAI,CAAC,cAAc,CAAC,GAAG,CAAC,IAAI,CAAC;QAAE,cAAc,CAAC,MAAM,CAAC,IAAI,EAAE,WAAW,CAAC,CAAC;AAC1E,CAAC;AAED,gBAAgB,EAAE,CAAC","sourcesContent":["// @ascribed/elements: registers Ascribe's custom elements, and adds a copy\n// button to each code block. Only <ascribe-tabs> and the copy buttons have\n// behavior; the rest are styled by the CSS file\n// (`@ascribed/elements/style.css`), which works without this script.\n\nimport { startCopyButtons } from \"./copy.js\";\nimport { AscribeGroup } from \"./group.js\";\nimport { AscribeTab, AscribeTabs, choose } from \"./tabs.js\";\nimport { ELEMENT_GROUP, ELEMENT_TAB, ELEMENT_TABS } from \"./names.js\";\n\nexport { AscribeGroup, AscribeTab, AscribeTabs, choose };\n\nconst registry: [string, CustomElementConstructor][] = [\n [ELEMENT_TABS, AscribeTabs],\n [ELEMENT_TAB, AscribeTab],\n [ELEMENT_GROUP, AscribeGroup],\n];\n\nfor (const [name, constructor] of registry) {\n if (!customElements.get(name)) customElements.define(name, constructor);\n}\n\nstartCopyButtons();\n"]}
|
package/dist/names.d.ts
CHANGED
|
@@ -6,6 +6,11 @@ export declare const ELEMENT_STEPS = "ascribe-steps";
|
|
|
6
6
|
export declare const ELEMENT_TABS = "ascribe-tabs";
|
|
7
7
|
/** `<ascribe-tab>`: one arm of `<ascribe-tabs>` (packages/elements/CONTRACT.md §3). */
|
|
8
8
|
export declare const ELEMENT_TAB = "ascribe-tab";
|
|
9
|
+
/**
|
|
10
|
+
* On the paragraph that starts an `<ascribe-tab>` and holds its label as
|
|
11
|
+
* text, for a reader of the HTML with no script (packages/elements/CONTRACT.md §3).
|
|
12
|
+
*/
|
|
13
|
+
export declare const CLASS_TAB_LABEL = "ascribe-tab-label";
|
|
9
14
|
/** `<ascribe-availability>`: a badge or filter (packages/elements/CONTRACT.md §4). */
|
|
10
15
|
export declare const ELEMENT_AVAILABILITY = "ascribe-availability";
|
|
11
16
|
/**
|
|
@@ -24,6 +29,22 @@ export declare const CLASS_AVAILABILITY_LEAD_IN = "ascribe-availability-lead-in"
|
|
|
24
29
|
* (packages/elements/CONTRACT.md §9).
|
|
25
30
|
*/
|
|
26
31
|
export declare const CLASS_CODE_TESTED = "ascribe-code-tested";
|
|
32
|
+
/**
|
|
33
|
+
* On the button the element library's script adds after a code block's
|
|
34
|
+
* `<pre>`, which copies its code; its words are the stylesheet's
|
|
35
|
+
* (packages/elements/CONTRACT.md §12).
|
|
36
|
+
*/
|
|
37
|
+
export declare const CLASS_COPY = "ascribe-copy";
|
|
38
|
+
/**
|
|
39
|
+
* On a copy button for a moment after a click: `copied`, or `failed`
|
|
40
|
+
* when the clipboard refused (packages/elements/CONTRACT.md §12).
|
|
41
|
+
*/
|
|
42
|
+
export declare const DATA_COPY = "data-ascribe-copy";
|
|
43
|
+
/**
|
|
44
|
+
* The visually hidden live region that says a copy button copied
|
|
45
|
+
* (packages/elements/CONTRACT.md §12).
|
|
46
|
+
*/
|
|
47
|
+
export declare const CLASS_COPY_STATUS = "ascribe-copy-status";
|
|
27
48
|
/**
|
|
28
49
|
* `<ascribe-operation>`: an operation of a declared API, from `@operation`
|
|
29
50
|
* (packages/elements/CONTRACT.md §11).
|
package/dist/names.js
CHANGED
|
@@ -9,6 +9,11 @@ export const ELEMENT_STEPS = "ascribe-steps";
|
|
|
9
9
|
export const ELEMENT_TABS = "ascribe-tabs";
|
|
10
10
|
/** `<ascribe-tab>`: one arm of `<ascribe-tabs>` (packages/elements/CONTRACT.md §3). */
|
|
11
11
|
export const ELEMENT_TAB = "ascribe-tab";
|
|
12
|
+
/**
|
|
13
|
+
* On the paragraph that starts an `<ascribe-tab>` and holds its label as
|
|
14
|
+
* text, for a reader of the HTML with no script (packages/elements/CONTRACT.md §3).
|
|
15
|
+
*/
|
|
16
|
+
export const CLASS_TAB_LABEL = "ascribe-tab-label";
|
|
12
17
|
/** `<ascribe-availability>`: a badge or filter (packages/elements/CONTRACT.md §4). */
|
|
13
18
|
export const ELEMENT_AVAILABILITY = "ascribe-availability";
|
|
14
19
|
/**
|
|
@@ -27,6 +32,22 @@ export const CLASS_AVAILABILITY_LEAD_IN = "ascribe-availability-lead-in";
|
|
|
27
32
|
* (packages/elements/CONTRACT.md §9).
|
|
28
33
|
*/
|
|
29
34
|
export const CLASS_CODE_TESTED = "ascribe-code-tested";
|
|
35
|
+
/**
|
|
36
|
+
* On the button the element library's script adds after a code block's
|
|
37
|
+
* `<pre>`, which copies its code; its words are the stylesheet's
|
|
38
|
+
* (packages/elements/CONTRACT.md §12).
|
|
39
|
+
*/
|
|
40
|
+
export const CLASS_COPY = "ascribe-copy";
|
|
41
|
+
/**
|
|
42
|
+
* On a copy button for a moment after a click: `copied`, or `failed`
|
|
43
|
+
* when the clipboard refused (packages/elements/CONTRACT.md §12).
|
|
44
|
+
*/
|
|
45
|
+
export const DATA_COPY = "data-ascribe-copy";
|
|
46
|
+
/**
|
|
47
|
+
* The visually hidden live region that says a copy button copied
|
|
48
|
+
* (packages/elements/CONTRACT.md §12).
|
|
49
|
+
*/
|
|
50
|
+
export const CLASS_COPY_STATUS = "ascribe-copy-status";
|
|
30
51
|
/**
|
|
31
52
|
* `<ascribe-operation>`: an operation of a declared API, from `@operation`
|
|
32
53
|
* (packages/elements/CONTRACT.md §11).
|
package/dist/names.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"names.js","sourceRoot":"","sources":["../src/names.ts"],"names":[],"mappings":"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","sourcesContent":["// The names Ascribe puts on a page: elements, attributes, classes, and ids.\n// Generated from crates/ascribe-core/src/names.rs by\n// `ASCRIBE_BLESS=1 cargo test -p ascribe-core --test names`. Don't edit it.\n\n/** `<ascribe-note>`: a note (packages/elements/CONTRACT.md §1). */\nexport const ELEMENT_NOTE = \"ascribe-note\";\n\n/** `<ascribe-steps>`: a procedure (packages/elements/CONTRACT.md §2). */\nexport const ELEMENT_STEPS = \"ascribe-steps\";\n\n/** `<ascribe-tabs>`: a variant group of several arms (packages/elements/CONTRACT.md §3). */\nexport const ELEMENT_TABS = \"ascribe-tabs\";\n\n/** `<ascribe-tab>`: one arm of `<ascribe-tabs>` (packages/elements/CONTRACT.md §3). */\nexport const ELEMENT_TAB = \"ascribe-tab\";\n\n/** `<ascribe-availability>`: a badge or filter (packages/elements/CONTRACT.md §4). */\nexport const ELEMENT_AVAILABILITY = \"ascribe-availability\";\n\n/**\n * `<ascribe-availability-target>`: one target of `<ascribe-availability>`\n * (packages/elements/CONTRACT.md §4).\n */\nexport const ELEMENT_AVAILABILITY_TARGET = \"ascribe-availability-target\";\n\n/**\n * On the visually hidden `(Available: ` and `)` around a table row's\n * `<ascribe-availability>` targets (packages/elements/CONTRACT.md §4).\n */\nexport const CLASS_AVAILABILITY_LEAD_IN = \"ascribe-availability-lead-in\";\n\n/**\n * On the line of a code block's caption that says what a tested example\n * was tested with; its lead-in, \"Tested with\", is the stylesheet's\n * (packages/elements/CONTRACT.md §9).\n */\nexport const CLASS_CODE_TESTED = \"ascribe-code-tested\";\n\n/**\n * `<ascribe-operation>`: an operation of a declared API, from `@operation`\n * (packages/elements/CONTRACT.md §11).\n */\nexport const ELEMENT_OPERATION = \"ascribe-operation\";\n\n/**\n * On the `(deprecated)` after a deprecated operation's link in\n * `<ascribe-operation>` (packages/elements/CONTRACT.md §11).\n */\nexport const CLASS_OPERATION_DEPRECATED = \"ascribe-operation-deprecated\";\n\n/** `<ascribe-group>`: a project widget's group (packages/elements/CONTRACT.md §6). */\nexport const ELEMENT_GROUP = \"ascribe-group\";\n\n/**\n * `<ascribe-for-agents>`: the visually hidden pointer to `llms.txt` at the top\n * of a page, for agents (packages/elements/CONTRACT.md §8).\n */\nexport const ELEMENT_FOR_AGENTS = \"ascribe-for-agents\";\n\n/** `<ascribe-attributes>`: the attribute marker (site-render contract §1). */\nexport const ELEMENT_ATTRIBUTES = \"ascribe-attributes\";\n\n/** The name an anchor comment starts with, `<!--ascribe-anchor` (site-render contract §7.3). */\nexport const COMMENT_ANCHOR = \"ascribe-anchor\";\n\n/** A block's source anchor (site-render contract §7.1). */\nexport const DATA_SOURCE = \"data-ascribe-source\";\n\n/** The includes a block came through (site-render contract §7.1). */\nexport const DATA_VIA = \"data-ascribe-via\";\n\n/** A glossary term's id, on its link (packages/elements/CONTRACT.md §7). */\nexport const DATA_TERM = \"data-ascribe-term\";\n\n/** The line a link out of a line leads to, on its link (packages/elements/CONTRACT.md §10). */\nexport const DATA_RELEASE_LINE = \"data-ascribe-release-line\";\n\n/** The element the report draws into. */\nexport const ID_REPORT = \"ascribe-review\";\n\n/** The `<script type=\"application/json\">` holding the report's data. */\nexport const ID_REPORT_DATA = \"ascribe-review-data\";\n\n/**\n * The page's light or dark scheme, `light` or `dark`, on the root\n * element (docs/content/astro/reference.md).\n */\nexport const DATA_SCHEME = \"data-ascribe-scheme\";\n\n/** On a marked page's root: what it shows, `changes`, `will`, or `was`. */\nexport const DATA_SHOW = \"data-ascribe-show\";\n\n/**\n * On a marked block: its change, `added`, `changed`, `removed`, `moved`,\n * or `moved-from`.\n */\nexport const DATA_CHANGE = \"data-ascribe-change\";\n\n/** On every element review adds to the page, so it can be taken away again. */\nexport const DATA_UI = \"data-ascribe-ui\";\n\n/** On a change's label: the change. */\nexport const DATA_LABEL = \"data-ascribe-label\";\n\n/** On a tab's hint: `added` or `changed`. */\nexport const DATA_HINT = \"data-ascribe-hint\";\n\n/** On a moved block: the id its \"moved from\" link points at. */\nexport const DATA_MOVE = \"data-ascribe-move\";\n\n/** On a changed block whose copy as it was follows it. */\nexport const DATA_HAS_WAS = \"data-ascribe-has-was\";\n\n/** On a copy of a block as it was, shown only with the page as it was. */\nexport const DATA_WAS_ONLY = \"data-ascribe-was-only\";\n\n/** On a changed, removed, or moved block: its source anchor as it was. */\nexport const DATA_WAS_SOURCE = \"data-ascribe-was-source\";\n\n/** On the elements that hold the threads' overlay. */\nexport const DATA_OVERLAY = \"data-ascribe-overlay\";\n\n/** On the style element the Astro dev toolbar adds for the marks. */\nexport const DATA_REVIEW = \"data-ascribe-review\";\n\n/** On the root of a page whose changes are marked. */\nexport const CLASS_MARKS = \"ascribe-marks\";\n\n/** A change's label. */\nexport const CLASS_LABEL = \"ascribe-label\";\n\n/** A label placed before its block rather than over it. */\nexport const CLASS_LABEL_BEFORE = \"ascribe-label-before\";\n\n/** Inserted text. */\nexport const CLASS_INS = \"ascribe-ins\";\n\n/** Deleted text. */\nexport const CLASS_DEL = \"ascribe-del\";\n\n/** A removed block. */\nexport const CLASS_REMOVED = \"ascribe-removed\";\n\n/** A removed block's content. */\nexport const CLASS_REMOVED_BODY = \"ascribe-removed-body\";\n\n/** The button that shows or collapses a removed block. */\nexport const CLASS_TOGGLE = \"ascribe-toggle\";\n\n/** On a removed block that's shown. */\nexport const CLASS_OPEN = \"ascribe-open\";\n\n/** What stands where a moved block was. */\nexport const CLASS_MOVED_FROM = \"ascribe-moved-from\";\n\n/** The note with a moved block's link. */\nexport const CLASS_MOVE_NOTE = \"ascribe-move-note\";\n\n/** A move note after its block, not inside it. */\nexport const CLASS_MOVE_AFTER = \"ascribe-move-after\";\n\n/** The link between a moved block and where it was. */\nexport const CLASS_MOVE_LINK = \"ascribe-move-link\";\n\n/** A moved block's copy as it was, where it was. */\nexport const CLASS_WAS_COPY = \"ascribe-was-copy\";\n\n/** A tab's hint that it holds changes. */\nexport const CLASS_HINT = \"ascribe-hint\";\n\n/** A block flashed after going to it. */\nexport const CLASS_FLASH = \"ascribe-flash\";\n\n/** The tip that shows a block's source on hover and focus. */\nexport const CLASS_WHERE = \"ascribe-where\";\n"]}
|
|
1
|
+
{"version":3,"file":"names.js","sourceRoot":"","sources":["../src/names.ts"],"names":[],"mappings":"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","sourcesContent":["// The names Ascribe puts on a page: elements, attributes, classes, and ids.\n// Generated from crates/ascribe-core/src/names.rs by\n// `ASCRIBE_BLESS=1 cargo test -p ascribe-core --test names`. Don't edit it.\n\n/** `<ascribe-note>`: a note (packages/elements/CONTRACT.md §1). */\nexport const ELEMENT_NOTE = \"ascribe-note\";\n\n/** `<ascribe-steps>`: a procedure (packages/elements/CONTRACT.md §2). */\nexport const ELEMENT_STEPS = \"ascribe-steps\";\n\n/** `<ascribe-tabs>`: a variant group of several arms (packages/elements/CONTRACT.md §3). */\nexport const ELEMENT_TABS = \"ascribe-tabs\";\n\n/** `<ascribe-tab>`: one arm of `<ascribe-tabs>` (packages/elements/CONTRACT.md §3). */\nexport const ELEMENT_TAB = \"ascribe-tab\";\n\n/**\n * On the paragraph that starts an `<ascribe-tab>` and holds its label as\n * text, for a reader of the HTML with no script (packages/elements/CONTRACT.md §3).\n */\nexport const CLASS_TAB_LABEL = \"ascribe-tab-label\";\n\n/** `<ascribe-availability>`: a badge or filter (packages/elements/CONTRACT.md §4). */\nexport const ELEMENT_AVAILABILITY = \"ascribe-availability\";\n\n/**\n * `<ascribe-availability-target>`: one target of `<ascribe-availability>`\n * (packages/elements/CONTRACT.md §4).\n */\nexport const ELEMENT_AVAILABILITY_TARGET = \"ascribe-availability-target\";\n\n/**\n * On the visually hidden `(Available: ` and `)` around a table row's\n * `<ascribe-availability>` targets (packages/elements/CONTRACT.md §4).\n */\nexport const CLASS_AVAILABILITY_LEAD_IN = \"ascribe-availability-lead-in\";\n\n/**\n * On the line of a code block's caption that says what a tested example\n * was tested with; its lead-in, \"Tested with\", is the stylesheet's\n * (packages/elements/CONTRACT.md §9).\n */\nexport const CLASS_CODE_TESTED = \"ascribe-code-tested\";\n\n/**\n * On the button the element library's script adds after a code block's\n * `<pre>`, which copies its code; its words are the stylesheet's\n * (packages/elements/CONTRACT.md §12).\n */\nexport const CLASS_COPY = \"ascribe-copy\";\n\n/**\n * On a copy button for a moment after a click: `copied`, or `failed`\n * when the clipboard refused (packages/elements/CONTRACT.md §12).\n */\nexport const DATA_COPY = \"data-ascribe-copy\";\n\n/**\n * The visually hidden live region that says a copy button copied\n * (packages/elements/CONTRACT.md §12).\n */\nexport const CLASS_COPY_STATUS = \"ascribe-copy-status\";\n\n/**\n * `<ascribe-operation>`: an operation of a declared API, from `@operation`\n * (packages/elements/CONTRACT.md §11).\n */\nexport const ELEMENT_OPERATION = \"ascribe-operation\";\n\n/**\n * On the `(deprecated)` after a deprecated operation's link in\n * `<ascribe-operation>` (packages/elements/CONTRACT.md §11).\n */\nexport const CLASS_OPERATION_DEPRECATED = \"ascribe-operation-deprecated\";\n\n/** `<ascribe-group>`: a project widget's group (packages/elements/CONTRACT.md §6). */\nexport const ELEMENT_GROUP = \"ascribe-group\";\n\n/**\n * `<ascribe-for-agents>`: the visually hidden pointer to `llms.txt` at the top\n * of a page, for agents (packages/elements/CONTRACT.md §8).\n */\nexport const ELEMENT_FOR_AGENTS = \"ascribe-for-agents\";\n\n/** `<ascribe-attributes>`: the attribute marker (site-render contract §1). */\nexport const ELEMENT_ATTRIBUTES = \"ascribe-attributes\";\n\n/** The name an anchor comment starts with, `<!--ascribe-anchor` (site-render contract §7.3). */\nexport const COMMENT_ANCHOR = \"ascribe-anchor\";\n\n/** A block's source anchor (site-render contract §7.1). */\nexport const DATA_SOURCE = \"data-ascribe-source\";\n\n/** The includes a block came through (site-render contract §7.1). */\nexport const DATA_VIA = \"data-ascribe-via\";\n\n/** A glossary term's id, on its link (packages/elements/CONTRACT.md §7). */\nexport const DATA_TERM = \"data-ascribe-term\";\n\n/** The line a link out of a line leads to, on its link (packages/elements/CONTRACT.md §10). */\nexport const DATA_RELEASE_LINE = \"data-ascribe-release-line\";\n\n/** The element the report draws into. */\nexport const ID_REPORT = \"ascribe-review\";\n\n/** The `<script type=\"application/json\">` holding the report's data. */\nexport const ID_REPORT_DATA = \"ascribe-review-data\";\n\n/**\n * The page's light or dark scheme, `light` or `dark`, on the root\n * element (docs/content/astro/reference.md).\n */\nexport const DATA_SCHEME = \"data-ascribe-scheme\";\n\n/** On a marked page's root: what it shows, `changes`, `will`, or `was`. */\nexport const DATA_SHOW = \"data-ascribe-show\";\n\n/**\n * On a marked block: its change, `added`, `changed`, `removed`, `moved`,\n * or `moved-from`.\n */\nexport const DATA_CHANGE = \"data-ascribe-change\";\n\n/** On every element review adds to the page, so it can be taken away again. */\nexport const DATA_UI = \"data-ascribe-ui\";\n\n/** On a change's label: the change. */\nexport const DATA_LABEL = \"data-ascribe-label\";\n\n/** On a tab's hint: `added` or `changed`. */\nexport const DATA_HINT = \"data-ascribe-hint\";\n\n/** On a moved block: the id its \"moved from\" link points at. */\nexport const DATA_MOVE = \"data-ascribe-move\";\n\n/** On a changed block whose copy as it was follows it. */\nexport const DATA_HAS_WAS = \"data-ascribe-has-was\";\n\n/** On a copy of a block as it was, shown only with the page as it was. */\nexport const DATA_WAS_ONLY = \"data-ascribe-was-only\";\n\n/** On a changed, removed, or moved block: its source anchor as it was. */\nexport const DATA_WAS_SOURCE = \"data-ascribe-was-source\";\n\n/** On the elements that hold the threads' overlay. */\nexport const DATA_OVERLAY = \"data-ascribe-overlay\";\n\n/** On the style element the Astro dev toolbar adds for the marks. */\nexport const DATA_REVIEW = \"data-ascribe-review\";\n\n/** On the root of a page whose changes are marked. */\nexport const CLASS_MARKS = \"ascribe-marks\";\n\n/** A change's label. */\nexport const CLASS_LABEL = \"ascribe-label\";\n\n/** A label placed before its block rather than over it. */\nexport const CLASS_LABEL_BEFORE = \"ascribe-label-before\";\n\n/** Inserted text. */\nexport const CLASS_INS = \"ascribe-ins\";\n\n/** Deleted text. */\nexport const CLASS_DEL = \"ascribe-del\";\n\n/** A removed block. */\nexport const CLASS_REMOVED = \"ascribe-removed\";\n\n/** A removed block's content. */\nexport const CLASS_REMOVED_BODY = \"ascribe-removed-body\";\n\n/** The button that shows or collapses a removed block. */\nexport const CLASS_TOGGLE = \"ascribe-toggle\";\n\n/** On a removed block that's shown. */\nexport const CLASS_OPEN = \"ascribe-open\";\n\n/** What stands where a moved block was. */\nexport const CLASS_MOVED_FROM = \"ascribe-moved-from\";\n\n/** The note with a moved block's link. */\nexport const CLASS_MOVE_NOTE = \"ascribe-move-note\";\n\n/** A move note after its block, not inside it. */\nexport const CLASS_MOVE_AFTER = \"ascribe-move-after\";\n\n/** The link between a moved block and where it was. */\nexport const CLASS_MOVE_LINK = \"ascribe-move-link\";\n\n/** A moved block's copy as it was, where it was. */\nexport const CLASS_WAS_COPY = \"ascribe-was-copy\";\n\n/** A tab's hint that it holds changes. */\nexport const CLASS_HINT = \"ascribe-hint\";\n\n/** A block flashed after going to it. */\nexport const CLASS_FLASH = \"ascribe-flash\";\n\n/** The tip that shows a block's source on hover and focus. */\nexport const CLASS_WHERE = \"ascribe-where\";\n"]}
|
package/dist/tabs.js
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
// <ascribe-tabs> and <ascribe-tab> (CONTRACT.md §3): the library's only
|
|
2
2
|
// element with behavior. Without this script every tab shows with its label
|
|
3
|
-
// (
|
|
4
|
-
// pattern
|
|
5
|
-
|
|
3
|
+
// (its first paragraph); with it, the group follows the WAI-ARIA tabs
|
|
4
|
+
// pattern, and the paragraph is out of sight and hidden from screen readers,
|
|
5
|
+
// which hear the label from the tab's button.
|
|
6
|
+
import { CLASS_TAB_LABEL, ELEMENT_TAB } from "./names.js";
|
|
6
7
|
const STORAGE_PREFIX = "ascribe-tabs:";
|
|
7
8
|
/** Choices made this page load, for readers whose storage is unavailable. */
|
|
8
9
|
const pageChoices = new Map();
|
|
@@ -35,6 +36,11 @@ function writeChoice(dimension, value) {
|
|
|
35
36
|
function valuesOf(tab) {
|
|
36
37
|
return (tab.getAttribute("value") ?? "").split(/\s+/).filter(Boolean);
|
|
37
38
|
}
|
|
39
|
+
/** A tab's label paragraph, its label as text, when it has one. */
|
|
40
|
+
function labelOf(tab) {
|
|
41
|
+
const first = tab.firstElementChild;
|
|
42
|
+
return first?.classList.contains(CLASS_TAB_LABEL) ? first : undefined;
|
|
43
|
+
}
|
|
38
44
|
/** Select `value` in every group on the page that switches on `dimension`, but `except`. */
|
|
39
45
|
let followChoice;
|
|
40
46
|
/**
|
|
@@ -100,6 +106,7 @@ export class AscribeTabs extends HTMLElement {
|
|
|
100
106
|
tablist.append(button);
|
|
101
107
|
tab.setAttribute("role", "tabpanel");
|
|
102
108
|
tab.setAttribute("aria-labelledby", button.id);
|
|
109
|
+
labelOf(tab)?.setAttribute("aria-hidden", "true");
|
|
103
110
|
return button;
|
|
104
111
|
});
|
|
105
112
|
this.prepend(tablist);
|
|
@@ -123,6 +130,7 @@ export class AscribeTabs extends HTMLElement {
|
|
|
123
130
|
tab.removeAttribute("role");
|
|
124
131
|
tab.removeAttribute("aria-labelledby");
|
|
125
132
|
tab.removeAttribute("hidden");
|
|
133
|
+
labelOf(tab)?.removeAttribute("aria-hidden");
|
|
126
134
|
}
|
|
127
135
|
this.#tablist = null;
|
|
128
136
|
this.#tabs = [];
|
package/dist/tabs.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tabs.js","sourceRoot":"","sources":["../src/tabs.ts"],"names":[],"mappings":"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","sourcesContent":["// <ascribe-tabs> and <ascribe-tab> (CONTRACT.md §3): the library's only\n// element with behavior. Without this script every tab shows with its label\n// (CSS generated content); with it, the group follows the WAI-ARIA tabs\n// pattern.\n\nimport { ELEMENT_TAB } from \"./names.js\";\n\nconst STORAGE_PREFIX = \"ascribe-tabs:\";\n\n/** Choices made this page load, for readers whose storage is unavailable. */\nconst pageChoices = new Map<string, string>();\n\n/** Every connected, initialized group with a `sync` attribute. */\nconst syncedGroups = new Set<AscribeTabs>();\n\nlet idCounter = 0;\n\nfunction readChoice(dimension: string): string | null {\n // This page's choices are always at least as fresh as storage, which a\n // failed write leaves behind.\n const chosen = pageChoices.get(dimension);\n if (chosen !== undefined) return chosen;\n try {\n return localStorage.getItem(STORAGE_PREFIX + dimension);\n } catch {\n // Storage can be blocked or unavailable.\n return null;\n }\n}\n\nfunction writeChoice(dimension: string, value: string): void {\n pageChoices.set(dimension, value);\n try {\n localStorage.setItem(STORAGE_PREFIX + dimension, value);\n } catch {\n // The choice lasts for the page only.\n }\n}\n\nfunction valuesOf(tab: Element): string[] {\n return (tab.getAttribute(\"value\") ?? \"\").split(/\\s+/).filter(Boolean);\n}\n\n/** Select `value` in every group on the page that switches on `dimension`, but `except`. */\nlet followChoice: (dimension: string, value: string, except?: AscribeTabs) => void;\n\n/**\n * Choose `value` for `dimension`, as a reader selecting a tab with that value\n * does: remember it across pages, and select it in every group on this page\n * that switches on the dimension. A group with no tab for it keeps its\n * selection. The value isn't checked.\n */\nexport function choose(dimension: string, value: string): void {\n writeChoice(dimension, value);\n followChoice(dimension, value);\n}\n\n/** A tab panel: one arm of a `@variant` group. Styled by CSS; no behavior. */\nexport class AscribeTab extends HTMLElement {}\n\nexport class AscribeTabs extends HTMLElement {\n #tablist: HTMLElement | null = null;\n #tabs: HTMLElement[] = [];\n #buttons: HTMLButtonElement[] = [];\n #selected = 0;\n #ready = false;\n\n static {\n followChoice = (dimension, value, except) => {\n for (const group of syncedGroups) {\n if (group !== except && group.getAttribute(\"sync\") === dimension) group.#follow(value);\n }\n };\n }\n\n connectedCallback(): void {\n // A parser-created element is connected before its children are parsed.\n if (document.readyState === \"loading\") {\n document.addEventListener(\"DOMContentLoaded\", () => this.#setup(), { once: true });\n } else {\n this.#setup();\n }\n }\n\n disconnectedCallback(): void {\n this.#teardown();\n }\n\n #setup(): void {\n if (!this.isConnected || this.#ready) return;\n this.#tabs = Array.from(this.children).filter(\n (child): child is HTMLElement => child.localName === ELEMENT_TAB,\n );\n if (this.#tabs.length === 0) return;\n this.#ready = true;\n\n const tablist = document.createElement(\"div\");\n tablist.setAttribute(\"role\", \"tablist\");\n this.#tablist = tablist;\n\n this.#buttons = this.#tabs.map((tab) => {\n if (!tab.id) tab.id = `ascribe-tab-${++idCounter}`;\n const button = document.createElement(\"button\");\n button.type = \"button\";\n button.id = `${tab.id}-button`;\n button.setAttribute(\"role\", \"tab\");\n button.setAttribute(\"aria-controls\", tab.id);\n button.textContent = tab.getAttribute(\"label\") ?? \"\";\n button.addEventListener(\"click\", () => this.#choose(this.#buttons.indexOf(button)));\n button.addEventListener(\"keydown\", (event) => this.#onKeydown(event));\n tablist.append(button);\n tab.setAttribute(\"role\", \"tabpanel\");\n tab.setAttribute(\"aria-labelledby\", button.id);\n return button;\n });\n this.prepend(tablist);\n\n const sync = this.getAttribute(\"sync\");\n let initial = 0;\n if (sync !== null) {\n syncedGroups.add(this);\n const remembered = readChoice(sync);\n const match = remembered === null ? -1 : this.#indexOfValue(remembered);\n if (match >= 0) initial = match;\n }\n this.#select(initial);\n }\n\n #teardown(): void {\n if (!this.#ready) return;\n syncedGroups.delete(this);\n this.#tablist?.remove();\n for (const tab of this.#tabs) {\n tab.removeAttribute(\"role\");\n tab.removeAttribute(\"aria-labelledby\");\n tab.removeAttribute(\"hidden\");\n }\n this.#tablist = null;\n this.#tabs = [];\n this.#buttons = [];\n this.#ready = false;\n }\n\n #indexOfValue(value: string): number {\n return this.#tabs.findIndex((tab) => valuesOf(tab).includes(value));\n }\n\n /** Select a tab without recording or syncing the choice. */\n #select(index: number): void {\n this.#selected = index;\n this.#tabs.forEach((tab, i) => {\n tab.toggleAttribute(\"hidden\", i !== index);\n });\n this.#buttons.forEach((button, i) => {\n button.setAttribute(\"aria-selected\", String(i === index));\n button.tabIndex = i === index ? 0 : -1;\n });\n }\n\n /** The reader picked a tab: select it and, for a synced group, tell the rest. */\n #choose(index: number): void {\n this.#select(index);\n const sync = this.getAttribute(\"sync\");\n const tab = this.#tabs[index];\n const choice = tab ? valuesOf(tab)[0] : undefined;\n if (sync === null || choice === undefined) return;\n writeChoice(sync, choice);\n followChoice(sync, choice, this);\n }\n\n /** Another group with the same `sync` made a choice; keep our selection if we have no match. */\n #follow(value: string): void {\n const index = this.#indexOfValue(value);\n if (index >= 0) this.#select(index);\n }\n\n #onKeydown(event: KeyboardEvent): void {\n const last = this.#tabs.length - 1;\n let next: number;\n switch (event.key) {\n case \"ArrowRight\":\n next = this.#selected === last ? 0 : this.#selected + 1;\n break;\n case \"ArrowLeft\":\n next = this.#selected === 0 ? last : this.#selected - 1;\n break;\n case \"Home\":\n next = 0;\n break;\n case \"End\":\n next = last;\n break;\n default:\n return;\n }\n event.preventDefault();\n this.#choose(next);\n this.#buttons[next]?.focus();\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"tabs.js","sourceRoot":"","sources":["../src/tabs.ts"],"names":[],"mappings":"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","sourcesContent":["// <ascribe-tabs> and <ascribe-tab> (CONTRACT.md §3): the library's only\n// element with behavior. Without this script every tab shows with its label\n// (its first paragraph); with it, the group follows the WAI-ARIA tabs\n// pattern, and the paragraph is out of sight and hidden from screen readers,\n// which hear the label from the tab's button.\n\nimport { CLASS_TAB_LABEL, ELEMENT_TAB } from \"./names.js\";\n\nconst STORAGE_PREFIX = \"ascribe-tabs:\";\n\n/** Choices made this page load, for readers whose storage is unavailable. */\nconst pageChoices = new Map<string, string>();\n\n/** Every connected, initialized group with a `sync` attribute. */\nconst syncedGroups = new Set<AscribeTabs>();\n\nlet idCounter = 0;\n\nfunction readChoice(dimension: string): string | null {\n // This page's choices are always at least as fresh as storage, which a\n // failed write leaves behind.\n const chosen = pageChoices.get(dimension);\n if (chosen !== undefined) return chosen;\n try {\n return localStorage.getItem(STORAGE_PREFIX + dimension);\n } catch {\n // Storage can be blocked or unavailable.\n return null;\n }\n}\n\nfunction writeChoice(dimension: string, value: string): void {\n pageChoices.set(dimension, value);\n try {\n localStorage.setItem(STORAGE_PREFIX + dimension, value);\n } catch {\n // The choice lasts for the page only.\n }\n}\n\nfunction valuesOf(tab: Element): string[] {\n return (tab.getAttribute(\"value\") ?? \"\").split(/\\s+/).filter(Boolean);\n}\n\n/** A tab's label paragraph, its label as text, when it has one. */\nfunction labelOf(tab: Element): Element | undefined {\n const first = tab.firstElementChild;\n return first?.classList.contains(CLASS_TAB_LABEL) ? first : undefined;\n}\n\n/** Select `value` in every group on the page that switches on `dimension`, but `except`. */\nlet followChoice: (dimension: string, value: string, except?: AscribeTabs) => void;\n\n/**\n * Choose `value` for `dimension`, as a reader selecting a tab with that value\n * does: remember it across pages, and select it in every group on this page\n * that switches on the dimension. A group with no tab for it keeps its\n * selection. The value isn't checked.\n */\nexport function choose(dimension: string, value: string): void {\n writeChoice(dimension, value);\n followChoice(dimension, value);\n}\n\n/** A tab panel: one arm of a `@variant` group. Styled by CSS; no behavior. */\nexport class AscribeTab extends HTMLElement {}\n\nexport class AscribeTabs extends HTMLElement {\n #tablist: HTMLElement | null = null;\n #tabs: HTMLElement[] = [];\n #buttons: HTMLButtonElement[] = [];\n #selected = 0;\n #ready = false;\n\n static {\n followChoice = (dimension, value, except) => {\n for (const group of syncedGroups) {\n if (group !== except && group.getAttribute(\"sync\") === dimension) group.#follow(value);\n }\n };\n }\n\n connectedCallback(): void {\n // A parser-created element is connected before its children are parsed.\n if (document.readyState === \"loading\") {\n document.addEventListener(\"DOMContentLoaded\", () => this.#setup(), { once: true });\n } else {\n this.#setup();\n }\n }\n\n disconnectedCallback(): void {\n this.#teardown();\n }\n\n #setup(): void {\n if (!this.isConnected || this.#ready) return;\n this.#tabs = Array.from(this.children).filter(\n (child): child is HTMLElement => child.localName === ELEMENT_TAB,\n );\n if (this.#tabs.length === 0) return;\n this.#ready = true;\n\n const tablist = document.createElement(\"div\");\n tablist.setAttribute(\"role\", \"tablist\");\n this.#tablist = tablist;\n\n this.#buttons = this.#tabs.map((tab) => {\n if (!tab.id) tab.id = `ascribe-tab-${++idCounter}`;\n const button = document.createElement(\"button\");\n button.type = \"button\";\n button.id = `${tab.id}-button`;\n button.setAttribute(\"role\", \"tab\");\n button.setAttribute(\"aria-controls\", tab.id);\n button.textContent = tab.getAttribute(\"label\") ?? \"\";\n button.addEventListener(\"click\", () => this.#choose(this.#buttons.indexOf(button)));\n button.addEventListener(\"keydown\", (event) => this.#onKeydown(event));\n tablist.append(button);\n tab.setAttribute(\"role\", \"tabpanel\");\n tab.setAttribute(\"aria-labelledby\", button.id);\n labelOf(tab)?.setAttribute(\"aria-hidden\", \"true\");\n return button;\n });\n this.prepend(tablist);\n\n const sync = this.getAttribute(\"sync\");\n let initial = 0;\n if (sync !== null) {\n syncedGroups.add(this);\n const remembered = readChoice(sync);\n const match = remembered === null ? -1 : this.#indexOfValue(remembered);\n if (match >= 0) initial = match;\n }\n this.#select(initial);\n }\n\n #teardown(): void {\n if (!this.#ready) return;\n syncedGroups.delete(this);\n this.#tablist?.remove();\n for (const tab of this.#tabs) {\n tab.removeAttribute(\"role\");\n tab.removeAttribute(\"aria-labelledby\");\n tab.removeAttribute(\"hidden\");\n labelOf(tab)?.removeAttribute(\"aria-hidden\");\n }\n this.#tablist = null;\n this.#tabs = [];\n this.#buttons = [];\n this.#ready = false;\n }\n\n #indexOfValue(value: string): number {\n return this.#tabs.findIndex((tab) => valuesOf(tab).includes(value));\n }\n\n /** Select a tab without recording or syncing the choice. */\n #select(index: number): void {\n this.#selected = index;\n this.#tabs.forEach((tab, i) => {\n tab.toggleAttribute(\"hidden\", i !== index);\n });\n this.#buttons.forEach((button, i) => {\n button.setAttribute(\"aria-selected\", String(i === index));\n button.tabIndex = i === index ? 0 : -1;\n });\n }\n\n /** The reader picked a tab: select it and, for a synced group, tell the rest. */\n #choose(index: number): void {\n this.#select(index);\n const sync = this.getAttribute(\"sync\");\n const tab = this.#tabs[index];\n const choice = tab ? valuesOf(tab)[0] : undefined;\n if (sync === null || choice === undefined) return;\n writeChoice(sync, choice);\n followChoice(sync, choice, this);\n }\n\n /** Another group with the same `sync` made a choice; keep our selection if we have no match. */\n #follow(value: string): void {\n const index = this.#indexOfValue(value);\n if (index >= 0) this.#select(index);\n }\n\n #onKeydown(event: KeyboardEvent): void {\n const last = this.#tabs.length - 1;\n let next: number;\n switch (event.key) {\n case \"ArrowRight\":\n next = this.#selected === last ? 0 : this.#selected + 1;\n break;\n case \"ArrowLeft\":\n next = this.#selected === 0 ? last : this.#selected - 1;\n break;\n case \"Home\":\n next = 0;\n break;\n case \"End\":\n next = last;\n break;\n default:\n return;\n }\n event.preventDefault();\n this.#choose(next);\n this.#buttons[next]?.focus();\n }\n}\n"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ascribed/elements",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.5-next.34",
|
|
4
4
|
"description": "A toolchain for documentation written as code. This package is the web components its site output uses.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MPL-2.0",
|
|
@@ -44,6 +44,7 @@
|
|
|
44
44
|
"publishConfig": {
|
|
45
45
|
"access": "public"
|
|
46
46
|
},
|
|
47
|
+
"gitHead": "570d8f155ffbc71857ce05a47a70840f1bbab67e",
|
|
47
48
|
"scripts": {
|
|
48
49
|
"build": "tsc -p tsconfig.build.json",
|
|
49
50
|
"typecheck": "tsc --noEmit",
|