@pie-players/pie-tool-annotation-toolbar 0.3.74 → 1.0.0-beta.0
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/README.md +40 -57
- package/custom-elements.json +76 -0
- package/dist/tool-annotation-toolbar.js +1478 -1413
- package/package.json +8 -5
package/README.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @pie-players/pie-tool-annotation-toolbar
|
|
2
2
|
|
|
3
3
|
A text selection toolbar for highlighting and annotating text in PIE assessment players. Uses modern CSS Custom Highlight API for zero DOM mutation and optimal performance.
|
|
4
4
|
|
|
@@ -6,24 +6,19 @@ A text selection toolbar for highlighting and annotating text in PIE assessment
|
|
|
6
6
|
|
|
7
7
|
- **4-Color Highlighting**: Yellow, pink, blue, and green highlight swatches
|
|
8
8
|
- **Underline Annotation**: Underline selected text
|
|
9
|
-
- **Persistent Annotations**:
|
|
9
|
+
- **Persistent Annotations**: Kept per section and attempt in the toolkit's tool-state store
|
|
10
10
|
- **Clear Annotations**: Remove annotations from selected text or clear all
|
|
11
|
-
- **Text-to-Speech (Read Aloud)**: Read selected text aloud
|
|
11
|
+
- **Text-to-Speech (Read Aloud)**: Read selected text aloud through the toolkit's TTS service, which highlights by sentence on browser voices and by word on server voices that return speech marks ([TTS deep dive](../../docs/accessibility/tts-deep-dive.md))
|
|
12
12
|
- **Selection Actions**: Host-supplied actions on the current selection; see below
|
|
13
13
|
- **Viewport-Aware Placement**: Sits above the selection where it fits, flips below when it does not, and clamps so no control leaves the viewport. `data-pie-placement` on the strip reads `above` or `below`.
|
|
14
14
|
- **Modern CSS Custom Highlight API**:
|
|
15
15
|
- Zero DOM mutation (no `<span>` wrappers)
|
|
16
|
-
- 10-50x faster than traditional approaches
|
|
17
|
-
- 5-10x less memory usage
|
|
18
16
|
- Framework-compatible (works with React, Vue, Svelte)
|
|
19
17
|
- **Accessibility**:
|
|
20
18
|
- Full ARIA labels and screen reader support
|
|
21
19
|
- Keyboard navigation (Escape to close)
|
|
22
20
|
- Focus-visible outlines
|
|
23
|
-
|
|
24
|
-
- **Dark Mode**: Automatic adaptation to system color scheme
|
|
25
|
-
- **High Contrast Mode**: Enhanced visibility in high contrast settings
|
|
26
|
-
- **Responsive Design**: Optimized for mobile and desktop viewports
|
|
21
|
+
- **Theme-aware**: the outline follows the declared `color-scheme` through `light-dark()`, and the `data-color-scheme` palettes set all three tokens (see Theming)
|
|
27
22
|
|
|
28
23
|
## Usage
|
|
29
24
|
|
|
@@ -39,16 +34,14 @@ A text selection toolbar for highlighting and annotating text in PIE assessment
|
|
|
39
34
|
|
|
40
35
|
## Props
|
|
41
36
|
|
|
42
|
-
The strip shows itself when text is selected and hides when the selection is cleared or Escape is pressed.
|
|
37
|
+
The strip shows itself when text is selected and hides when the selection is cleared or Escape is pressed. It records annotations through the toolkit runtime context's `highlightCoordinator` and offers read-aloud when that context carries a `ttsService`; without a highlight coordinator the highlight controls no-op.
|
|
43
38
|
|
|
44
|
-
| Name
|
|
45
|
-
|
|
|
46
|
-
| `enabled`
|
|
47
|
-
| `
|
|
48
|
-
| `ttsService` | — | object | Read-aloud is offered only when present. |
|
|
49
|
-
| `selectionActions` | — | `ToolSelectionAction[]` | Host-supplied actions on the current selection. See below. |
|
|
39
|
+
| Name | Attribute | Type | Notes |
|
|
40
|
+
| ------------------ | --------- | ----------------------- | ------------------------------------------------------------ |
|
|
41
|
+
| `enabled` | `enabled` | boolean | Defaults to `true`; `false` stops it reacting to selections. |
|
|
42
|
+
| `selectionActions` | — | `ToolSelectionAction[]` | Host-supplied actions on the current selection. See below. |
|
|
50
43
|
|
|
51
|
-
Under `<pie-assessment-toolkit>`
|
|
44
|
+
Under `<pie-assessment-toolkit>` both are supplied by the capability's registration, so a host mounting the strip through the toolkit passes nothing.
|
|
52
45
|
|
|
53
46
|
### Selection actions
|
|
54
47
|
|
|
@@ -74,15 +67,17 @@ The toolbar takes its surface and text from the canonical `--pie-background` and
|
|
|
74
67
|
| `--pie-annotation-underline` | `#4221d5` | Underline mark on a light page |
|
|
75
68
|
| `--pie-annotation-underline-dark` | `#9c89ec` | Underline mark on a dark page |
|
|
76
69
|
|
|
77
|
-
|
|
70
|
+
All three are registered `component-public` tokens.
|
|
71
|
+
|
|
72
|
+
The underline tokens are applied by `HighlightCoordinator` in `@pie-players/pie-assessment-toolkit`, which owns the `::highlight()` rules. They exist as a pair because one value cannot serve both surfaces — `#4221d5` is 2.41:1 on black and `#9c89ec` is 2.85:1 on white — and as two separate tokens so overriding one never silently moves the other. Under `data-theme="light"` and `data-theme="dark"` the underline reads only its own token, because a theme accent is chosen against one background and is illegible on the other. Under any other `data-theme` value, such as a host or DaisyUI palette, it reads `--pie-annotation-underline` and falls back to that palette's `--pie-primary`.
|
|
78
73
|
|
|
79
74
|
Which of the pair applies is decided by `[data-theme]`, which reports what the **page** declares — not which color scheme is active. A host that declares itself light while running a dark scheme would otherwise pin the light value over a dark background, so the accessibility palettes hand both states their own accent (below).
|
|
80
75
|
|
|
81
|
-
The outline does **not** read `--pie-border`. Under the DaisyUI bridge that token resolves to `--color-base-300`, a surface tint rather than a boundary
|
|
76
|
+
The outline does **not** read `--pie-border`. Under the DaisyUI bridge that token resolves to `--color-base-300`, a surface tint rather than a boundary color, which renders the outline at 1.16:1 on the light base and 1.12:1 on the dark one — well short of the 3:1 that WCAG 2.2 SC 1.4.11 requires of a component boundary.
|
|
82
77
|
|
|
83
|
-
The defaults above are measured instead. A boundary on a light surface has to be dark and one on a dark surface has to be light, so the arms are the dark
|
|
78
|
+
The defaults above are measured instead. A boundary on a light surface has to be dark and one on a dark surface has to be light, so the arms are the dark gray first and the light gray second. Both are measured against the surfaces the toolbar is actually drawn on rather than against pure white and pure black — real theme bases are off-white and off-black, and a gray chosen at the edge of passing against an extreme drops under threshold on everything else. Across DaisyUI's 21 light and 14 dark themes plus the PIE light and dark palettes, every light surface needs a gray no lighter than `#828282` and every dark surface one no darker than `#878787`; the ranges are disjoint, so one value cannot serve both. `#5c5c5c` holds 5.22:1 as its worst case on the light surfaces and `#949494` holds 3.56:1 on the dark ones. `light-dark()` follows the declared `color-scheme`, so every dark DaisyUI theme picks the dark value rather than only the theme literally named `dark`.
|
|
84
79
|
|
|
85
|
-
Palettes that choose their own
|
|
80
|
+
Palettes that choose their own colors deliberately set all three tokens in `@pie-players/pie-theme`: the ten `data-color-scheme` accessibility schemes point the outline at their own `--pie-border` and both underline states at their own `--pie-primary`, and the PIE dark theme pins the outline to `#949494`. Several of those palettes use mid-tone backgrounds that no single gray clears 3:1 on, which is why they override rather than inherit.
|
|
86
81
|
|
|
87
82
|
Those per-scheme values arrive by either delivery route: `<pie-theme scheme="…">` applies them as inline styles with no CSS import needed, and importing `@pie-players/pie-theme/color-schemes.css` plus setting `data-color-scheme` yourself produces the same result. Prefer the element — a host that hand-maintains its own copy of the scheme blocks stops receiving tokens added later.
|
|
88
83
|
|
|
@@ -92,7 +87,7 @@ Hosts may override any of the three, but must keep 3:1 against both the toolbar
|
|
|
92
87
|
|
|
93
88
|
The annotation toolbar integrates with PIE's shared highlight infrastructure:
|
|
94
89
|
|
|
95
|
-
- **HighlightCoordinator**:
|
|
90
|
+
- **HighlightCoordinator**: One per `ToolkitCoordinator`, shared by TTS and annotation highlights
|
|
96
91
|
- **RangeSerializer**: Serializes/deserializes DOM ranges for persistence
|
|
97
92
|
- **CSS Custom Highlight API**: Modern browser API for non-invasive highlighting
|
|
98
93
|
|
|
@@ -102,33 +97,34 @@ Requires CSS Custom Highlight API support:
|
|
|
102
97
|
|
|
103
98
|
- Chrome/Edge 105+
|
|
104
99
|
- Safari 17.2+
|
|
105
|
-
- Firefox
|
|
100
|
+
- Firefox 140+
|
|
106
101
|
|
|
107
102
|
For unsupported browsers, the component gracefully degrades (no highlights shown).
|
|
108
103
|
|
|
109
104
|
## Annotation Persistence
|
|
110
105
|
|
|
111
|
-
Annotations are
|
|
106
|
+
Annotations are recorded in the toolkit's `elementToolStateStore` after each change, under tool id `annotationToolbar` and a key naming the assessment, section and attempt (`attempt-id` on the toolkit), plus the item when the toolbar sits in an item's shell. Two seconds after the toolbar mounts, and whenever that key changes, the shown annotations are replaced by the key's record, so a new attempt starts without the previous one's. They last as long as the toolkit coordinator; a host keeps them across reloads with the coordinator's `loadToolState` and `saveToolState` hooks.
|
|
112
107
|
|
|
113
|
-
|
|
108
|
+
Recorded format, an array with one record per annotation:
|
|
114
109
|
|
|
115
110
|
```typescript
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
111
|
+
[
|
|
112
|
+
{
|
|
113
|
+
id: "annotation-0",
|
|
114
|
+
color: "yellow",
|
|
115
|
+
timestamp: 1234567890,
|
|
116
|
+
startContainer: "div:nth-of-type(2) > p",
|
|
119
117
|
startOffset: 10,
|
|
120
|
-
endContainer:
|
|
118
|
+
endContainer: "div:nth-of-type(2) > p",
|
|
121
119
|
endOffset: 20,
|
|
122
120
|
text: "highlighted text"
|
|
123
121
|
}
|
|
124
|
-
|
|
122
|
+
]
|
|
125
123
|
```
|
|
126
124
|
|
|
127
|
-
|
|
125
|
+
`startContainer` and `endContainer` are selector paths relative to the scope root, with ` >>> ` marking a step into a shadow root.
|
|
128
126
|
|
|
129
|
-
|
|
130
|
-
- User navigates to different content
|
|
131
|
-
- sessionStorage is cleared
|
|
127
|
+
"Clear all" records an empty list under the key.
|
|
132
128
|
|
|
133
129
|
## Text-to-Speech Integration
|
|
134
130
|
|
|
@@ -139,31 +135,30 @@ The annotation toolbar includes a "Read" button that uses the TTS service to rea
|
|
|
139
135
|
1. **User selects text** in the assessment content
|
|
140
136
|
2. **Annotation toolbar appears** with highlight and read buttons
|
|
141
137
|
3. **User clicks "Read"** button (speaker icon)
|
|
142
|
-
4. **TTS service speaks the selected text** using
|
|
138
|
+
4. **TTS service speaks the selected text** using the toolkit's configured TTS backend
|
|
143
139
|
5. **Words are highlighted** in sync with speech using CSS Custom Highlight API
|
|
144
140
|
|
|
145
141
|
### Technical Implementation
|
|
146
142
|
|
|
147
|
-
The toolbar
|
|
143
|
+
The toolbar passes the selected range to `ttsService.speak()`, which highlights
|
|
144
|
+
from the selection's offset within its content root:
|
|
148
145
|
|
|
149
146
|
```typescript
|
|
150
147
|
ttsSpeaking = true;
|
|
151
148
|
try {
|
|
152
|
-
// speakRange() calculates text offset for accurate highlighting.
|
|
153
149
|
// contentRoot: the item or passage scope element, else the document element.
|
|
154
|
-
|
|
150
|
+
// language: the toolkit's `content-language`; `speak` lets a `lang` in
|
|
151
|
+
// the content win over it.
|
|
152
|
+
// catalogContext: from the shell holding the selection.
|
|
153
|
+
await ttsService.speak(selectedRange, { contentRoot, language, catalogContext });
|
|
155
154
|
} finally {
|
|
156
155
|
ttsSpeaking = false;
|
|
157
156
|
}
|
|
158
157
|
```
|
|
159
158
|
|
|
160
|
-
`
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
- User selects text in the middle of a paragraph
|
|
165
|
-
- `speak(text)` would highlight from the beginning of the container (wrong)
|
|
166
|
-
- `speakRange(range)` highlights the exact selected text (correct)
|
|
159
|
+
`speak` resolves when reading ends and rejects on a playback error. A
|
|
160
|
+
`data-catalog-idref` node the selection holds whole reads its spoken card, as
|
|
161
|
+
under tts-inline; part of one reads as the selected text.
|
|
167
162
|
|
|
168
163
|
### UX Details
|
|
169
164
|
|
|
@@ -174,16 +169,4 @@ try {
|
|
|
174
169
|
|
|
175
170
|
### TTS Browser Support
|
|
176
171
|
|
|
177
|
-
- **TTS (Web Speech API)**: 97%+ browser support
|
|
178
|
-
- **Word Highlighting**: 85-90% browser support (CSS Custom Highlight API)
|
|
179
172
|
- **Graceful degradation**: TTS works without highlighting in older browsers
|
|
180
|
-
|
|
181
|
-
## Based On
|
|
182
|
-
|
|
183
|
-
This implementation is inspired by production annotation toolbar patterns but uses modern 2025 web standards:
|
|
184
|
-
|
|
185
|
-
- CSS Custom Highlight API instead of DOM mutation
|
|
186
|
-
- Svelte 5 patterns and best practices
|
|
187
|
-
- Modern accessibility (WCAG 2.2)
|
|
188
|
-
- Dark mode and high contrast support
|
|
189
|
-
- Responsive design for mobile devices
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
{
|
|
2
|
+
"schemaVersion": "2.1.0",
|
|
3
|
+
"modules": [
|
|
4
|
+
{
|
|
5
|
+
"kind": "javascript-module",
|
|
6
|
+
"path": "dist/tool-annotation-toolbar.js",
|
|
7
|
+
"declarations": [
|
|
8
|
+
{
|
|
9
|
+
"kind": "class",
|
|
10
|
+
"description": "Toolbar that appears at a text selection, offering four highlight colours, underline, read-aloud, removal, clear-all and the host's `selectionActions`. Annotations are drawn through the runtime context's highlight coordinator and recorded per section and attempt in its element tool state store; read-aloud uses the context's TTS service. Under `<pie-assessment-toolkit>` the capability's registration supplies `enabled` and `selectionActions`.",
|
|
11
|
+
"name": "ToolAnnotationToolbar",
|
|
12
|
+
"tagName": "pie-tool-annotation-toolbar",
|
|
13
|
+
"customElement": true,
|
|
14
|
+
"members": [
|
|
15
|
+
{
|
|
16
|
+
"kind": "field",
|
|
17
|
+
"name": "enabled",
|
|
18
|
+
"type": {
|
|
19
|
+
"text": "boolean"
|
|
20
|
+
},
|
|
21
|
+
"default": "true",
|
|
22
|
+
"attribute": "enabled"
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
"kind": "field",
|
|
26
|
+
"name": "selectionActions",
|
|
27
|
+
"type": {
|
|
28
|
+
"text": "ToolSelectionAction[] | null"
|
|
29
|
+
},
|
|
30
|
+
"default": "null",
|
|
31
|
+
"description": "Actions on the current selection, supplied by whoever mounts this gateway. This strip does not know what they do — it renders the buttons and hands each one the selection. Pairing an action to a capability is the composer's job, which is what keeps a highlighter from naming a dictionary and lets a host contribute an action for a capability PIE does not ship.",
|
|
32
|
+
"attribute": "selectionactions"
|
|
33
|
+
}
|
|
34
|
+
],
|
|
35
|
+
"attributes": [
|
|
36
|
+
{
|
|
37
|
+
"name": "enabled",
|
|
38
|
+
"type": {
|
|
39
|
+
"text": "boolean"
|
|
40
|
+
},
|
|
41
|
+
"default": "true",
|
|
42
|
+
"fieldName": "enabled"
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
"name": "selectionactions",
|
|
46
|
+
"type": {
|
|
47
|
+
"text": "ToolSelectionAction[] | null"
|
|
48
|
+
},
|
|
49
|
+
"default": "null",
|
|
50
|
+
"description": "Actions on the current selection, supplied by whoever mounts this gateway. This strip does not know what they do — it renders the buttons and hands each one the selection. Pairing an action to a capability is the composer's job, which is what keeps a highlighter from naming a dictionary and lets a host contribute an action for a capability PIE does not ship.",
|
|
51
|
+
"fieldName": "selectionActions"
|
|
52
|
+
}
|
|
53
|
+
],
|
|
54
|
+
"cssProperties": [
|
|
55
|
+
{
|
|
56
|
+
"name": "--pie-tool-annotation-toolbar-border",
|
|
57
|
+
"description": "Outline of the floating annotation toolbar. Deliberately does not fall through --pie-border, which the DaisyUI bridge maps to the base-300 surface tint and which leaves the outline near 1.1:1; the component default is a measured light-dark(#5c5c5c, #949494) pair instead -- dark grey on light surfaces, light grey on dark ones, each measured against DaisyUI's real theme bases rather than pure white and black. The data-color-scheme accessibility palettes and the PIE dark theme set it explicitly so their own boundary colour wins. Hosts overriding it must keep 3:1 against both the toolbar surface and the content behind it (WCAG 2.2 SC 1.4.11)."
|
|
58
|
+
}
|
|
59
|
+
],
|
|
60
|
+
"x-pie": {
|
|
61
|
+
"shadow": "open"
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
],
|
|
65
|
+
"exports": [
|
|
66
|
+
{
|
|
67
|
+
"kind": "custom-element-definition",
|
|
68
|
+
"name": "pie-tool-annotation-toolbar",
|
|
69
|
+
"declaration": {
|
|
70
|
+
"name": "ToolAnnotationToolbar"
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
]
|
|
74
|
+
}
|
|
75
|
+
]
|
|
76
|
+
}
|