@abreen/tada 1.13.2 → 1.13.4
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/build/utils/trace-core.ts +3 -7
- package/init/content/labs/02/selection_sort.py +1 -1
- package/package.json +1 -1
- package/src/_base.scss +67 -0
- package/src/anchor/README.md +9 -0
- package/src/anchor/index.ts +115 -16
- package/src/anchor/style.scss +70 -10
- package/src/print/style.scss +0 -4
- package/src/slides/index.ts +40 -26
- package/src/trace/index.ts +25 -7
- package/templates/_theme.scss +1 -0
- package/templates/default.html +4 -3
|
@@ -187,17 +187,13 @@ export function renderTraceWidgetHtml({
|
|
|
187
187
|
`<button class="trace-btn trace-first" disabled tabindex="-1" aria-label="First step" title="First step">${iconFirst}</button>` +
|
|
188
188
|
`<button class="trace-btn trace-prev" disabled tabindex="-1" aria-label="Previous step" title="Previous step">${iconPrev}</button>` +
|
|
189
189
|
`<span class="trace-step-counter"${counterStyle}>${counterContent}</span>` +
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
: `<button class="trace-btn trace-next" aria-label="Next step" title="Next step">${iconNext}</button>`) +
|
|
193
|
-
(disabled
|
|
194
|
-
? `<button class="trace-btn trace-last" disabled tabindex="-1" aria-label="Last step" title="Last step">${iconLast}</button>`
|
|
195
|
-
: `<button class="trace-btn trace-last" tabindex="-1" aria-label="Last step" title="Last step">${iconLast}</button>`);
|
|
190
|
+
`<button class="trace-btn trace-next" disabled tabindex="-1" aria-label="Next step" title="Next step">${iconNext}</button>` +
|
|
191
|
+
`<button class="trace-btn trace-last" disabled tabindex="-1" aria-label="Last step" title="Last step">${iconLast}</button>`;
|
|
196
192
|
const wrapperAttrs = manifestUrl
|
|
197
193
|
? ` data-trace-manifest="${manifestUrl}"`
|
|
198
194
|
: '';
|
|
199
195
|
const wrapperClass = disabled
|
|
200
196
|
? 'trace-widget trace-disabled'
|
|
201
197
|
: 'trace-widget';
|
|
202
|
-
return `<div class="${wrapperClass}"${wrapperAttrs}><noscript><p>This interactive trace requires JavaScript.</p></noscript><div class="trace-body"><div class="trace-toolbar"><div class="trace-controls" role="toolbar" aria-label="Trace navigation">${controls}</div></div><div class="trace-content"><div class="trace-diagram"></div><div class="trace-source-wrapper"><div class="trace-source">${highlightedSource}</div></div
|
|
198
|
+
return `<div class="${wrapperClass}"${wrapperAttrs}><noscript><p>This interactive trace requires JavaScript.</p></noscript><div class="trace-body"><div class="trace-toolbar"><div class="trace-controls" role="toolbar" aria-label="Trace navigation">${controls}</div></div><div class="trace-content"><div class="trace-diagram"></div><div class="trace-source-wrapper"><div class="trace-source">${highlightedSource}</div></div></div></div></div>`;
|
|
203
199
|
}
|
package/package.json
CHANGED
package/src/_base.scss
CHANGED
|
@@ -180,6 +180,73 @@ input[type='search'] {
|
|
|
180
180
|
}
|
|
181
181
|
}
|
|
182
182
|
|
|
183
|
+
label:has(> input[type='checkbox']) {
|
|
184
|
+
display: inline-flex;
|
|
185
|
+
gap: 0.45em;
|
|
186
|
+
align-items: center;
|
|
187
|
+
font-size: var(--font-size-small);
|
|
188
|
+
font-weight: 500;
|
|
189
|
+
|
|
190
|
+
@include user-select(none);
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
input[type='checkbox'] {
|
|
194
|
+
display: inline-grid;
|
|
195
|
+
flex: 0 0 auto;
|
|
196
|
+
place-content: center;
|
|
197
|
+
width: 1.5em;
|
|
198
|
+
height: 1.25em;
|
|
199
|
+
padding: 0;
|
|
200
|
+
margin: 0;
|
|
201
|
+
font: inherit;
|
|
202
|
+
color: var(--fg-color);
|
|
203
|
+
appearance: none;
|
|
204
|
+
cursor: pointer;
|
|
205
|
+
background: var(--bg-color);
|
|
206
|
+
border: var(--control-border-width) solid var(--fg2-color);
|
|
207
|
+
border-radius: 0.25em;
|
|
208
|
+
|
|
209
|
+
&::after {
|
|
210
|
+
display: block;
|
|
211
|
+
width: 1.1em;
|
|
212
|
+
height: 1.1em;
|
|
213
|
+
content: '';
|
|
214
|
+
background-color: currentcolor;
|
|
215
|
+
opacity: 0;
|
|
216
|
+
|
|
217
|
+
@include contained-mask(var(--icon-check));
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
&:checked {
|
|
221
|
+
&::after {
|
|
222
|
+
opacity: 1;
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
&:disabled {
|
|
227
|
+
cursor: not-allowed;
|
|
228
|
+
opacity: 0.4;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
&:active:not(:disabled) {
|
|
232
|
+
color: var(--bg-color) !important;
|
|
233
|
+
background: var(--link-active-color) !important;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
&:focus {
|
|
237
|
+
outline: 2px solid var(--theme-color);
|
|
238
|
+
outline-offset: 1px;
|
|
239
|
+
background-color: var(--bg2-color);
|
|
240
|
+
border-color: var(--fg-color);
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
@media (hover: hover) {
|
|
245
|
+
input[type='checkbox']:hover:not(:disabled) {
|
|
246
|
+
background: var(--bg2-color);
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
|
|
183
250
|
figure {
|
|
184
251
|
margin: 1rem 0;
|
|
185
252
|
|
package/src/anchor/README.md
CHANGED
|
@@ -4,3 +4,12 @@ Append a link to each heading having an `id` attribute.
|
|
|
4
4
|
|
|
5
5
|
When the link is clicked, `window.location` is updated and the heading is
|
|
6
6
|
highlighted using the same function as the `toc` component.
|
|
7
|
+
|
|
8
|
+
The heading link includes a real inline SVG hash icon, revealed on heading
|
|
9
|
+
hover or link focus.
|
|
10
|
+
|
|
11
|
+
For slide title headings (`div.slide > h2:first-child`), append a
|
|
12
|
+
`Present from this slide` icon button. Clicking the button dispatches a
|
|
13
|
+
bubbling `tada:slides-present` custom event with the slide index so the slides
|
|
14
|
+
component can open presentation mode from that slide using the current
|
|
15
|
+
`Full screen` checkbox state.
|
package/src/anchor/index.ts
CHANGED
|
@@ -2,32 +2,131 @@ function getElements(parent: HTMLElement): HTMLHeadingElement[] {
|
|
|
2
2
|
return Array.from(parent.querySelectorAll('h1, h2, h3, h4, h5, h6'));
|
|
3
3
|
}
|
|
4
4
|
|
|
5
|
+
function createIcon(window: Window, type: 'hash' | 'present'): SVGSVGElement {
|
|
6
|
+
const svg = window.document.createElementNS(
|
|
7
|
+
'http://www.w3.org/2000/svg',
|
|
8
|
+
'svg',
|
|
9
|
+
);
|
|
10
|
+
svg.setAttribute('aria-hidden', 'true');
|
|
11
|
+
svg.setAttribute('viewBox', '0 0 24 24');
|
|
12
|
+
svg.setAttribute('width', '16');
|
|
13
|
+
svg.setAttribute('height', '16');
|
|
14
|
+
svg.setAttribute('fill', 'none');
|
|
15
|
+
svg.setAttribute('stroke', 'currentColor');
|
|
16
|
+
svg.setAttribute('stroke-width', '1.5');
|
|
17
|
+
svg.setAttribute('stroke-linecap', 'round');
|
|
18
|
+
svg.setAttribute('stroke-linejoin', 'round');
|
|
19
|
+
|
|
20
|
+
const paths =
|
|
21
|
+
type === 'hash'
|
|
22
|
+
? ['M10 3 8 21', 'M16 3l-2 18', 'M4 9h17', 'M3 15h17']
|
|
23
|
+
: [
|
|
24
|
+
'M5 5h14a1 1 0 0 1 1 1v9a1 1 0 0 1-1 1H5a1 1 0 0 1-1-1V6a1 1 0 0 1 1-1Z',
|
|
25
|
+
'M9 20h6',
|
|
26
|
+
];
|
|
27
|
+
|
|
28
|
+
for (const d of paths) {
|
|
29
|
+
const path = window.document.createElementNS(
|
|
30
|
+
'http://www.w3.org/2000/svg',
|
|
31
|
+
'path',
|
|
32
|
+
);
|
|
33
|
+
path.setAttribute('d', d);
|
|
34
|
+
svg.appendChild(path);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
return svg;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function getSlideIndex(slide: HTMLElement): number {
|
|
41
|
+
const rawIndex = slide.getAttribute('data-slide-index');
|
|
42
|
+
if (rawIndex != null) {
|
|
43
|
+
const parsed = Number.parseInt(rawIndex, 10);
|
|
44
|
+
if (Number.isFinite(parsed)) {
|
|
45
|
+
return parsed;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const root = slide.closest('[data-slides-root]');
|
|
50
|
+
if (!root) {
|
|
51
|
+
return 0;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
return Array.from(root.querySelectorAll('.slide')).indexOf(slide);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function isSlideTitleHeading(
|
|
58
|
+
heading: HTMLHeadingElement,
|
|
59
|
+
): heading is HTMLHeadingElement & { parentElement: HTMLElement } {
|
|
60
|
+
const slide = heading.parentElement;
|
|
61
|
+
return (
|
|
62
|
+
heading.tagName === 'H2' &&
|
|
63
|
+
slide?.tagName === 'DIV' &&
|
|
64
|
+
slide.classList.contains('slide') &&
|
|
65
|
+
slide.firstElementChild === heading
|
|
66
|
+
);
|
|
67
|
+
}
|
|
68
|
+
|
|
5
69
|
export default (window: Window) => {
|
|
70
|
+
const view = window as Window & typeof globalThis;
|
|
6
71
|
const elements = getElements(window.document.body);
|
|
7
|
-
|
|
8
|
-
return;
|
|
9
|
-
}
|
|
72
|
+
const cleanups: (() => void)[] = [];
|
|
10
73
|
|
|
11
74
|
elements.forEach(el => {
|
|
12
|
-
if (
|
|
75
|
+
if (el.id) {
|
|
76
|
+
const link = window.document.createElement('a');
|
|
77
|
+
link.className = 'heading-anchor';
|
|
78
|
+
link.href = `#${el.id}`;
|
|
79
|
+
link.title = 'Link to this heading';
|
|
80
|
+
|
|
81
|
+
// Move all existing child nodes into the link.
|
|
82
|
+
while (el.firstChild) {
|
|
83
|
+
link.appendChild(el.firstChild);
|
|
84
|
+
}
|
|
85
|
+
link.appendChild(createIcon(window, 'hash'));
|
|
86
|
+
el.appendChild(link);
|
|
87
|
+
|
|
88
|
+
const handleClick = () => {
|
|
89
|
+
el.focus();
|
|
90
|
+
};
|
|
91
|
+
link.addEventListener('click', handleClick);
|
|
92
|
+
cleanups.push(() => {
|
|
93
|
+
link.removeEventListener('click', handleClick);
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
if (!isSlideTitleHeading(el)) {
|
|
13
98
|
return;
|
|
14
99
|
}
|
|
15
100
|
|
|
16
|
-
const
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
101
|
+
const button = window.document.createElement('button');
|
|
102
|
+
button.type = 'button';
|
|
103
|
+
button.className = 'heading-present-button';
|
|
104
|
+
button.setAttribute('aria-label', 'Present from this slide');
|
|
105
|
+
button.title = 'Present from this slide';
|
|
106
|
+
button.appendChild(createIcon(window, 'present'));
|
|
107
|
+
el.appendChild(button);
|
|
20
108
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
link.appendChild(el.firstChild);
|
|
24
|
-
}
|
|
25
|
-
el.appendChild(link);
|
|
109
|
+
const slide = el.parentElement;
|
|
110
|
+
const slideIndex = getSlideIndex(slide);
|
|
26
111
|
|
|
27
|
-
|
|
28
|
-
|
|
112
|
+
const handlePresentClick = () => {
|
|
113
|
+
button.dispatchEvent(
|
|
114
|
+
new view.CustomEvent('tada:slides-present', {
|
|
115
|
+
bubbles: true,
|
|
116
|
+
detail: { slideIndex },
|
|
117
|
+
}),
|
|
118
|
+
);
|
|
119
|
+
};
|
|
120
|
+
button.addEventListener('click', handlePresentClick);
|
|
121
|
+
|
|
122
|
+
cleanups.push(() => {
|
|
123
|
+
button.removeEventListener('click', handlePresentClick);
|
|
29
124
|
});
|
|
30
125
|
});
|
|
31
126
|
|
|
32
|
-
return () => {
|
|
127
|
+
return () => {
|
|
128
|
+
for (const cleanup of cleanups) {
|
|
129
|
+
cleanup();
|
|
130
|
+
}
|
|
131
|
+
};
|
|
33
132
|
};
|
package/src/anchor/style.scss
CHANGED
|
@@ -7,29 +7,89 @@ a.heading-anchor {
|
|
|
7
7
|
@include subdued-link;
|
|
8
8
|
}
|
|
9
9
|
|
|
10
|
-
|
|
10
|
+
.heading-anchor svg,
|
|
11
|
+
.heading-present-button svg {
|
|
11
12
|
display: inline-block;
|
|
12
13
|
width: 1em;
|
|
13
14
|
height: 1em;
|
|
15
|
+
vertical-align: middle;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
.heading-anchor svg {
|
|
14
19
|
margin-left: 4px;
|
|
15
20
|
vertical-align: middle;
|
|
16
|
-
content: '';
|
|
17
21
|
opacity: 0;
|
|
22
|
+
}
|
|
18
23
|
|
|
19
|
-
|
|
24
|
+
.heading-present-button {
|
|
25
|
+
display: inline-flex;
|
|
26
|
+
align-items: center;
|
|
27
|
+
justify-content: center;
|
|
28
|
+
width: 1em;
|
|
29
|
+
height: 1em;
|
|
30
|
+
padding: 0;
|
|
31
|
+
margin-left: 4px;
|
|
32
|
+
font: inherit;
|
|
33
|
+
line-height: 1;
|
|
34
|
+
vertical-align: middle;
|
|
35
|
+
color: var(--fg-color);
|
|
36
|
+
background: transparent;
|
|
37
|
+
border: none;
|
|
38
|
+
border-radius: 0;
|
|
39
|
+
opacity: 0;
|
|
20
40
|
}
|
|
21
41
|
|
|
22
|
-
a.heading-anchor:focus-visible
|
|
42
|
+
a.heading-anchor:focus-visible svg,
|
|
43
|
+
.heading-present-button:focus {
|
|
23
44
|
opacity: 1;
|
|
24
45
|
}
|
|
25
46
|
|
|
47
|
+
.heading-present-button:focus {
|
|
48
|
+
outline-offset: 2px;
|
|
49
|
+
background-color: transparent;
|
|
50
|
+
border-color: transparent;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
button.heading-present-button:active:not(:disabled) {
|
|
54
|
+
color: var(--link-active-color) !important;
|
|
55
|
+
background: transparent !important;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
body.is-presenting .heading-present-button {
|
|
59
|
+
display: none;
|
|
60
|
+
}
|
|
61
|
+
|
|
26
62
|
@media (hover: hover) {
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
63
|
+
button.heading-present-button:hover:not(:disabled) {
|
|
64
|
+
color: var(--fg2-color);
|
|
65
|
+
background: transparent;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
h1:hover a.heading-anchor svg,
|
|
69
|
+
h2:hover a.heading-anchor svg,
|
|
70
|
+
h3:hover a.heading-anchor svg,
|
|
71
|
+
h4:hover a.heading-anchor svg,
|
|
72
|
+
h5:hover a.heading-anchor svg,
|
|
73
|
+
h6:hover a.heading-anchor svg,
|
|
74
|
+
h1:hover .heading-present-button,
|
|
75
|
+
h2:hover .heading-present-button,
|
|
76
|
+
h3:hover .heading-present-button,
|
|
77
|
+
h4:hover .heading-present-button,
|
|
78
|
+
h5:hover .heading-present-button,
|
|
79
|
+
h6:hover .heading-present-button {
|
|
33
80
|
opacity: 1;
|
|
34
81
|
}
|
|
35
82
|
}
|
|
83
|
+
|
|
84
|
+
@media (hover: none) {
|
|
85
|
+
.heading-present-button {
|
|
86
|
+
display: none;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
@media print {
|
|
91
|
+
.heading-anchor svg,
|
|
92
|
+
.heading-present-button {
|
|
93
|
+
display: none;
|
|
94
|
+
}
|
|
95
|
+
}
|
package/src/print/style.scss
CHANGED
package/src/slides/index.ts
CHANGED
|
@@ -5,6 +5,10 @@ interface TraceToolbarState {
|
|
|
5
5
|
|
|
6
6
|
type PresentationMode = 'normal' | 'fullscreen';
|
|
7
7
|
|
|
8
|
+
interface SlidesPresentDetail {
|
|
9
|
+
slideIndex?: number;
|
|
10
|
+
}
|
|
11
|
+
|
|
8
12
|
function isInteractive(
|
|
9
13
|
view: Window & typeof globalThis,
|
|
10
14
|
target: EventTarget | null,
|
|
@@ -45,17 +49,17 @@ export default function mountSlides(window: Window): void | (() => void) {
|
|
|
45
49
|
const presentButton = document.querySelector(
|
|
46
50
|
'[data-slides-present]',
|
|
47
51
|
) as HTMLButtonElement | null;
|
|
48
|
-
const
|
|
49
|
-
'[data-slides-
|
|
50
|
-
) as
|
|
51
|
-
const rootScopeButton = presentButton ??
|
|
52
|
+
const fullscreenCheckbox = document.querySelector(
|
|
53
|
+
'[data-slides-fullscreen]',
|
|
54
|
+
) as HTMLInputElement | null;
|
|
55
|
+
const rootScopeButton = presentButton ?? fullscreenCheckbox;
|
|
52
56
|
const scopedRoot = rootScopeButton
|
|
53
57
|
?.closest('main')
|
|
54
58
|
?.querySelector('[data-slides-root]');
|
|
55
59
|
const root = (scopedRoot ??
|
|
56
60
|
document.querySelector('[data-slides-root]')) as HTMLElement | null;
|
|
57
61
|
|
|
58
|
-
if (!view || !root
|
|
62
|
+
if (!view || !root) {
|
|
59
63
|
return;
|
|
60
64
|
}
|
|
61
65
|
|
|
@@ -78,8 +82,8 @@ export default function mountSlides(window: Window): void | (() => void) {
|
|
|
78
82
|
if (presentButton) {
|
|
79
83
|
presentButton.disabled = false;
|
|
80
84
|
}
|
|
81
|
-
if (
|
|
82
|
-
|
|
85
|
+
if (fullscreenCheckbox) {
|
|
86
|
+
fullscreenCheckbox.disabled = false;
|
|
83
87
|
}
|
|
84
88
|
|
|
85
89
|
const overlay = document.createElement('div');
|
|
@@ -252,7 +256,7 @@ export default function mountSlides(window: Window): void | (() => void) {
|
|
|
252
256
|
}
|
|
253
257
|
}
|
|
254
258
|
|
|
255
|
-
function enterPresentation(mode: PresentationMode): void {
|
|
259
|
+
function enterPresentation(mode: PresentationMode, slideIndex = 0): void {
|
|
256
260
|
isPresenting = true;
|
|
257
261
|
presentationMode = mode;
|
|
258
262
|
hidePresentationControls();
|
|
@@ -260,8 +264,8 @@ export default function mountSlides(window: Window): void | (() => void) {
|
|
|
260
264
|
document.body.classList.add('is-presenting');
|
|
261
265
|
setTraceToolbarVisibility(true);
|
|
262
266
|
resetReadyTraces();
|
|
263
|
-
setActiveSlide(
|
|
264
|
-
focusSlide(slides[
|
|
267
|
+
setActiveSlide(slideIndex);
|
|
268
|
+
focusSlide(slides[activeIndex]);
|
|
265
269
|
}
|
|
266
270
|
|
|
267
271
|
function driveTrace(delta: 1 | -1): boolean {
|
|
@@ -329,12 +333,8 @@ export default function mountSlides(window: Window): void | (() => void) {
|
|
|
329
333
|
focusSlide(slides[activeIndex]);
|
|
330
334
|
}
|
|
331
335
|
|
|
332
|
-
function
|
|
333
|
-
enterPresentation('
|
|
334
|
-
}
|
|
335
|
-
|
|
336
|
-
async function handlePresentFullscreenClick(): Promise<void> {
|
|
337
|
-
enterPresentation('fullscreen');
|
|
336
|
+
async function handlePresentFullscreenClick(slideIndex = 0): Promise<void> {
|
|
337
|
+
enterPresentation('fullscreen', slideIndex);
|
|
338
338
|
|
|
339
339
|
if (typeof fullscreenTarget.requestFullscreen !== 'function') {
|
|
340
340
|
presentationMode = 'normal';
|
|
@@ -348,8 +348,17 @@ export default function mountSlides(window: Window): void | (() => void) {
|
|
|
348
348
|
}
|
|
349
349
|
}
|
|
350
350
|
|
|
351
|
-
function
|
|
352
|
-
|
|
351
|
+
function startPresentation(slideIndex = 0): void {
|
|
352
|
+
if (fullscreenCheckbox?.checked === true) {
|
|
353
|
+
void handlePresentFullscreenClick(slideIndex);
|
|
354
|
+
return;
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
enterPresentation('normal', slideIndex);
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
function handlePresentClick(): void {
|
|
361
|
+
startPresentation(0);
|
|
353
362
|
}
|
|
354
363
|
|
|
355
364
|
function handleCloseClick(): void {
|
|
@@ -439,29 +448,34 @@ export default function mountSlides(window: Window): void | (() => void) {
|
|
|
439
448
|
}
|
|
440
449
|
}
|
|
441
450
|
|
|
451
|
+
function handleSlidesPresent(event: Event): void {
|
|
452
|
+
const detail = (event as CustomEvent<SlidesPresentDetail>).detail ?? {};
|
|
453
|
+
const slideIndex =
|
|
454
|
+
typeof detail.slideIndex === 'number' &&
|
|
455
|
+
Number.isFinite(detail.slideIndex)
|
|
456
|
+
? detail.slideIndex
|
|
457
|
+
: 0;
|
|
458
|
+
|
|
459
|
+
startPresentation(slideIndex);
|
|
460
|
+
}
|
|
461
|
+
|
|
442
462
|
presentButton?.addEventListener('click', handlePresentClick);
|
|
443
|
-
presentFullscreenButton?.addEventListener(
|
|
444
|
-
'click',
|
|
445
|
-
handlePresentFullscreenButtonClick,
|
|
446
|
-
);
|
|
447
463
|
closeButton.addEventListener('click', handleCloseClick);
|
|
448
464
|
document.addEventListener('fullscreenchange', handleFullscreenChange);
|
|
449
465
|
window.addEventListener('keydown', handleKeydown);
|
|
450
466
|
window.addEventListener('mousemove', handleMouseMove);
|
|
451
467
|
slidesRoot.addEventListener('click', handleSlideClick);
|
|
468
|
+
slidesRoot.addEventListener('tada:slides-present', handleSlidesPresent);
|
|
452
469
|
|
|
453
470
|
return () => {
|
|
454
471
|
exitPresentation();
|
|
455
472
|
presentButton?.removeEventListener('click', handlePresentClick);
|
|
456
|
-
presentFullscreenButton?.removeEventListener(
|
|
457
|
-
'click',
|
|
458
|
-
handlePresentFullscreenButtonClick,
|
|
459
|
-
);
|
|
460
473
|
closeButton.removeEventListener('click', handleCloseClick);
|
|
461
474
|
document.removeEventListener('fullscreenchange', handleFullscreenChange);
|
|
462
475
|
window.removeEventListener('keydown', handleKeydown);
|
|
463
476
|
window.removeEventListener('mousemove', handleMouseMove);
|
|
464
477
|
slidesRoot.removeEventListener('click', handleSlideClick);
|
|
478
|
+
slidesRoot.removeEventListener('tada:slides-present', handleSlidesPresent);
|
|
465
479
|
overlay.remove();
|
|
466
480
|
};
|
|
467
481
|
}
|
package/src/trace/index.ts
CHANGED
|
@@ -11,7 +11,7 @@ interface WidgetElements {
|
|
|
11
11
|
root: HTMLElement;
|
|
12
12
|
source: HTMLElement;
|
|
13
13
|
controls: HTMLElement;
|
|
14
|
-
|
|
14
|
+
content: HTMLElement;
|
|
15
15
|
diagram: HTMLElement;
|
|
16
16
|
}
|
|
17
17
|
|
|
@@ -193,6 +193,27 @@ function scaleDiagramSvg(diagram: HTMLElement, doc: Document): void {
|
|
|
193
193
|
svg.setAttribute('height', String(baseHeight * scale));
|
|
194
194
|
}
|
|
195
195
|
|
|
196
|
+
function updateOutput(
|
|
197
|
+
content: HTMLElement,
|
|
198
|
+
output: string,
|
|
199
|
+
doc: Document,
|
|
200
|
+
): void {
|
|
201
|
+
const existing = content.querySelector(
|
|
202
|
+
':scope > .trace-output',
|
|
203
|
+
) as HTMLPreElement | null;
|
|
204
|
+
if (!output) {
|
|
205
|
+
existing?.remove();
|
|
206
|
+
return;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
const outputElement = existing ?? doc.createElement('pre');
|
|
210
|
+
if (!existing) {
|
|
211
|
+
outputElement.className = 'trace-output';
|
|
212
|
+
content.append(outputElement);
|
|
213
|
+
}
|
|
214
|
+
outputElement.textContent = output;
|
|
215
|
+
}
|
|
216
|
+
|
|
196
217
|
function renderWidgetState(
|
|
197
218
|
state: WidgetState,
|
|
198
219
|
elements: WidgetElements,
|
|
@@ -215,7 +236,7 @@ function renderWidgetState(
|
|
|
215
236
|
output += s.stdout;
|
|
216
237
|
}
|
|
217
238
|
}
|
|
218
|
-
elements.output
|
|
239
|
+
updateOutput(elements.content, output, doc);
|
|
219
240
|
|
|
220
241
|
const prevHeight = elements.diagram.scrollHeight;
|
|
221
242
|
elements.diagram.innerHTML = entry.svg;
|
|
@@ -249,12 +270,9 @@ async function initWidget(root: HTMLElement, doc: Document): Promise<void> {
|
|
|
249
270
|
|
|
250
271
|
await loadChunk(state, manifestUrl, 0);
|
|
251
272
|
|
|
252
|
-
const entry = getStep(state);
|
|
253
|
-
|
|
254
273
|
const source = root.querySelector('.trace-source') as HTMLElement;
|
|
274
|
+
const content = root.querySelector('.trace-content') as HTMLElement;
|
|
255
275
|
const diagram = root.querySelector('.trace-diagram') as HTMLElement;
|
|
256
|
-
const output = root.querySelector('.trace-output') as HTMLPreElement;
|
|
257
|
-
output.textContent = entry.stdout || '';
|
|
258
276
|
|
|
259
277
|
const controls = root.querySelector('.trace-controls') as HTMLElement;
|
|
260
278
|
const firstBtn = controls.querySelector('.trace-first') as HTMLButtonElement;
|
|
@@ -296,7 +314,7 @@ async function initWidget(root: HTMLElement, doc: Document): Promise<void> {
|
|
|
296
314
|
next.focus();
|
|
297
315
|
});
|
|
298
316
|
|
|
299
|
-
const elements: WidgetElements = { root, source, controls,
|
|
317
|
+
const elements: WidgetElements = { root, source, controls, content, diagram };
|
|
300
318
|
|
|
301
319
|
renderWidgetState(state, elements, doc);
|
|
302
320
|
}
|
package/templates/_theme.scss
CHANGED
|
@@ -58,6 +58,7 @@
|
|
|
58
58
|
--icon-anchor: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 24 28'><path d='M11 4 L8 20 M17 4 L14 20 M5 9 H21 M3 15 H19' stroke='black' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round' fill='none'/></svg>");
|
|
59
59
|
--icon-warning: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><polygon points='12,2 22,20 2,20' fill='none' stroke='black' stroke-width='1.5' stroke-linejoin='round'/><line x1='12' y1='9' x2='12' y2='13' stroke='black' stroke-width='2' stroke-linecap='round'/><line x1='12' y1='16' x2='12' y2='16' stroke='black' stroke-width='2' stroke-linecap='round'/></svg>");
|
|
60
60
|
--icon-note: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><circle cx='12' cy='12' r='10' fill='none' stroke='black' stroke-width='1.5'/><line x1='12' y1='10' x2='12' y2='16' stroke='black' stroke-width='2' stroke-linecap='round'/><line x1='12' y1='7' x2='12' y2='7' stroke='black' stroke-width='2' stroke-linecap='round'/></svg>");
|
|
61
|
+
--icon-check: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 24 24'><path d='M5 12.5 10 17 19 7' stroke='black' stroke-width='2.5' stroke-linecap='round' stroke-linejoin='round' fill='none'/></svg>");
|
|
61
62
|
--icon-search: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 24 24'><circle cx='11' cy='11' r='7' stroke='<%= iconColor %>' stroke-width='2' fill='none'/><line x1='16.5' y1='16.5' x2='22' y2='22' stroke='<%= iconColor %>' stroke-width='2' stroke-linecap='round'/></svg>");
|
|
62
63
|
--icon-chevron: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24'><polyline points='9 18 15 12 9 6' stroke='<%= iconColor %>' stroke-width='2' fill='none' stroke-linecap='round' stroke-linejoin='round'/></svg>");
|
|
63
64
|
--icon-breadcrumb: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 24 24'><path d='M3 7 H17 V22' stroke='black' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round' fill='none'/><polyline points='11 16 17 22 23 16' stroke='black' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round' fill='none'/></svg>");
|
package/templates/default.html
CHANGED
|
@@ -6,9 +6,10 @@
|
|
|
6
6
|
<% if (page.slides) { %>
|
|
7
7
|
<div class="slides-header" data-pagefind-ignore>
|
|
8
8
|
<button type="button" data-slides-present disabled>Present</button>
|
|
9
|
-
<
|
|
10
|
-
|
|
11
|
-
|
|
9
|
+
<label>
|
|
10
|
+
<input type="checkbox" data-slides-fullscreen checked disabled>
|
|
11
|
+
Full screen
|
|
12
|
+
</label>
|
|
12
13
|
</div>
|
|
13
14
|
<% } %>
|
|
14
15
|
<%= content %>
|