@altnovatrail/canvas 0.0.0-stage → 0.1.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.
Files changed (75) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +59 -2
  3. package/lib/core/boxes.d.ts +54 -0
  4. package/lib/core/boxes.js +116 -0
  5. package/lib/core/clock.d.ts +25 -0
  6. package/lib/core/clock.js +39 -0
  7. package/lib/core/crop.d.ts +87 -0
  8. package/lib/core/crop.js +227 -0
  9. package/lib/core/eraser.d.ts +42 -0
  10. package/lib/core/eraser.js +127 -0
  11. package/lib/core/example.d.ts +66 -0
  12. package/lib/core/example.js +116 -0
  13. package/lib/core/fields.d.ts +29 -0
  14. package/lib/core/fields.js +78 -0
  15. package/lib/core/geometry.d.ts +38 -0
  16. package/lib/core/geometry.js +107 -0
  17. package/lib/core/grid.d.ts +30 -0
  18. package/lib/core/grid.js +47 -0
  19. package/lib/core/groups.d.ts +55 -0
  20. package/lib/core/groups.js +172 -0
  21. package/lib/core/hands.d.ts +120 -0
  22. package/lib/core/hands.js +273 -0
  23. package/lib/core/history.d.ts +42 -0
  24. package/lib/core/history.js +228 -0
  25. package/lib/core/index.d.ts +12 -0
  26. package/lib/core/index.js +15 -0
  27. package/lib/core/ink.d.ts +1 -0
  28. package/lib/core/ink.js +5 -0
  29. package/lib/core/manifest.d.ts +22 -0
  30. package/lib/core/manifest.js +50 -0
  31. package/lib/core/marks.d.ts +145 -0
  32. package/lib/core/marks.js +454 -0
  33. package/lib/core/outline.d.ts +22 -0
  34. package/lib/core/outline.js +50 -0
  35. package/lib/core/page.d.ts +62 -0
  36. package/lib/core/page.js +309 -0
  37. package/lib/core/png.d.ts +13 -0
  38. package/lib/core/png.js +231 -0
  39. package/lib/core/raster.d.ts +14 -0
  40. package/lib/core/raster.js +72 -0
  41. package/lib/core/recorder.d.ts +73 -0
  42. package/lib/core/recorder.js +127 -0
  43. package/lib/core/screen.d.ts +35 -0
  44. package/lib/core/screen.js +36 -0
  45. package/lib/core/sets.d.ts +10 -0
  46. package/lib/core/sets.js +31 -0
  47. package/lib/core/sheet.d.ts +9 -0
  48. package/lib/core/sheet.js +6 -0
  49. package/lib/core/submission.d.ts +41 -0
  50. package/lib/core/submission.js +63 -0
  51. package/lib/core/task.d.ts +214 -0
  52. package/lib/core/task.js +343 -0
  53. package/lib/core/widths.d.ts +57 -0
  54. package/lib/core/widths.js +218 -0
  55. package/lib/core/work.d.ts +77 -0
  56. package/lib/core/work.js +293 -0
  57. package/lib/element/attributes.d.ts +11 -0
  58. package/lib/element/attributes.js +22 -0
  59. package/lib/element/index.d.ts +219 -0
  60. package/lib/element/index.js +1898 -0
  61. package/lib/element/labels.d.ts +41 -0
  62. package/lib/element/labels.js +74 -0
  63. package/lib/element/parts.d.ts +63 -0
  64. package/lib/element/parts.js +191 -0
  65. package/lib/element/pictures.d.ts +45 -0
  66. package/lib/element/pictures.js +167 -0
  67. package/lib/element/probe.d.ts +33 -0
  68. package/lib/element/probe.js +18 -0
  69. package/lib/element/session.d.ts +3 -0
  70. package/lib/element/session.js +7 -0
  71. package/lib/element/statement.d.ts +35 -0
  72. package/lib/element/statement.js +116 -0
  73. package/lib/element/tools.d.ts +64 -0
  74. package/lib/element/tools.js +254 -0
  75. package/package.json +41 -4
@@ -0,0 +1,35 @@
1
+ import type { PicturePart, StatementPart } from "../core/task.js";
2
+ import type { Pictures } from "./pictures.js";
3
+ /** The statement of a task above the sheet, as the element shows it. */
4
+ export declare class Statement {
5
+ #private;
6
+ /**
7
+ * The statement's element: the paragraphs and the pictures, in the module's explanation language,
8
+ * or in the element's own without a manifest (R32).
9
+ */
10
+ readonly element: HTMLDivElement;
11
+ /** A statement whose pictures the element's pictures give their files. */
12
+ constructor(pictures: Pictures);
13
+ /**
14
+ * The language the statement is in, a BCP 47 tag, or `undefined` for the element's own, which
15
+ * the statement inherits. A new language changes the attribute alone: the paragraphs and the
16
+ * pictures stay as they are.
17
+ */
18
+ set lang(lang: string | undefined);
19
+ /**
20
+ * The statement of a task, in place of the last, whose pictures are taken off. A picture whose
21
+ * file has come shows it at once: the element names the new statement's pictures first.
22
+ */
23
+ show(statement: readonly StatementPart[]): void;
24
+ }
25
+ /** A picture beside the sheet in its frame, and what takes it off when the frame leaves. */
26
+ export interface Framed {
27
+ readonly frame: HTMLElement;
28
+ readonly off: () => void;
29
+ }
30
+ /**
31
+ * A picture beside the sheet in a frame of its size, placed with the element's pictures: a
32
+ * statement's, and a worked example's in its list of parts (R42). The frame shows the picture once
33
+ * its file has come and is decoded, and what it shows in words till then, or if it never does.
34
+ */
35
+ export declare function framed(pictures: Pictures, part: PicturePart): Framed;
@@ -0,0 +1,116 @@
1
+ // The statement of a task, above the sheet, in the element's own scrolling (R23): its paragraphs
2
+ // as text and its pictures from the consumer's media, which the element's pictures load (R42).
3
+ // Each picture keeps a frame of its own size, so that nothing moves under the pen when it comes,
4
+ // and the frame shows what the picture shows in words until it comes, or if it never does. This
5
+ // module is no entry point of the package.
6
+ /** The statement of a task above the sheet, as the element shows it. */
7
+ export class Statement {
8
+ /**
9
+ * The statement's element: the paragraphs and the pictures, in the module's explanation language,
10
+ * or in the element's own without a manifest (R32).
11
+ */
12
+ element = document.createElement("div");
13
+ #pictures;
14
+ /** What takes the statement's pictures off, once another statement takes its place. */
15
+ #off = [];
16
+ /** A statement whose pictures the element's pictures give their files. */
17
+ constructor(pictures) {
18
+ this.#pictures = pictures;
19
+ this.element.className = "statement";
20
+ }
21
+ /**
22
+ * The language the statement is in, a BCP 47 tag, or `undefined` for the element's own, which
23
+ * the statement inherits. A new language changes the attribute alone: the paragraphs and the
24
+ * pictures stay as they are.
25
+ */
26
+ set lang(lang) {
27
+ if (lang === undefined)
28
+ this.element.removeAttribute("lang");
29
+ else
30
+ this.element.lang = lang;
31
+ }
32
+ /**
33
+ * The statement of a task, in place of the last, whose pictures are taken off. A picture whose
34
+ * file has come shows it at once: the element names the new statement's pictures first.
35
+ */
36
+ show(statement) {
37
+ for (const off of this.#off)
38
+ off();
39
+ const off = [];
40
+ const parts = statement.map((part) => {
41
+ if ("text" in part) {
42
+ const paragraph = document.createElement("p");
43
+ // Text, never markup: a module is data (R02 spec).
44
+ paragraph.textContent = part.text;
45
+ return paragraph;
46
+ }
47
+ const picture = framed(this.#pictures, part);
48
+ off.push(picture.off);
49
+ return picture.frame;
50
+ });
51
+ this.element.replaceChildren(...parts);
52
+ this.#off = off;
53
+ }
54
+ }
55
+ /**
56
+ * A picture beside the sheet in a frame of its size, placed with the element's pictures: a
57
+ * statement's, and a worked example's in its list of parts (R42). The frame shows the picture once
58
+ * its file has come and is decoded, and what it shows in words till then, or if it never does.
59
+ */
60
+ export function framed(pictures, part) {
61
+ const frame = document.createElement("div");
62
+ frame.className = "picture";
63
+ frame.style.width = `${part.width}px`;
64
+ frame.style.aspectRatio = `${part.width} / ${part.height}`;
65
+ fillFrame(frame, part);
66
+ /** The address the frame is to show, until it goes. */
67
+ let shown;
68
+ const off = pictures.place(part.picture, {
69
+ show(url) {
70
+ shown = url;
71
+ const image = document.createElement("img");
72
+ image.alt = part.alt;
73
+ image.src = url;
74
+ const swap = () => {
75
+ frame.removeAttribute("role");
76
+ frame.removeAttribute("aria-label");
77
+ frame.classList.add("shown");
78
+ frame.replaceChildren(image);
79
+ };
80
+ // A browser that keeps the decoded address at hand has the picture whole at once, as
81
+ // Chromium does; another decodes it again, and the frame shows its words till then (T64).
82
+ if (image.complete && image.naturalWidth > 0) {
83
+ swap();
84
+ return;
85
+ }
86
+ image.decode().then(() => {
87
+ if (shown === url)
88
+ swap();
89
+ },
90
+ // The address went while it was decoded: the frame keeps its words.
91
+ () => undefined);
92
+ },
93
+ hide() {
94
+ shown = undefined;
95
+ fillFrame(frame, part);
96
+ },
97
+ });
98
+ return { frame, off };
99
+ }
100
+ /**
101
+ * A frame without its picture shows what the picture shows in words, and is read as the picture.
102
+ * One that only decorates shows nothing and is not read.
103
+ */
104
+ function fillFrame(frame, part) {
105
+ frame.classList.remove("shown");
106
+ if (part.alt === "") {
107
+ frame.classList.add("decorative");
108
+ frame.removeAttribute("role");
109
+ frame.removeAttribute("aria-label");
110
+ frame.replaceChildren();
111
+ return;
112
+ }
113
+ frame.setAttribute("role", "img");
114
+ frame.setAttribute("aria-label", part.alt);
115
+ frame.textContent = part.alt;
116
+ }
@@ -0,0 +1,64 @@
1
+ import type { Tool } from "../core/task.js";
2
+ /**
3
+ * What the row asks of the element: a tool the learner chose — perhaps the one in use — an undo, a
4
+ * clearing, a scroll on down the sheet, for `onward`, or back up it, the finger to write again,
5
+ * out of pen mode, and on a worked example the next part of its demo, or the whole demo hidden or
6
+ * shown (R42).
7
+ */
8
+ export interface ToolActions {
9
+ readonly choose: (tool: Tool) => void;
10
+ readonly undo: () => void;
11
+ readonly clear: () => void;
12
+ readonly upOrDown: (onward: boolean) => void;
13
+ readonly fingers: () => void;
14
+ readonly next: () => void;
15
+ readonly toggle: () => void;
16
+ }
17
+ /** What Next and Demo control, by their IDs in the element's shadow root (R42). */
18
+ export interface DemoControls {
19
+ /** The region that tells of the part Next opens. */
20
+ readonly status: string;
21
+ /** The list of the open parts, which Demo hides and shows. */
22
+ readonly parts: string;
23
+ }
24
+ /** What the row is of, and is named by: a sheet, a workbook or a worked example (R36, R42). */
25
+ export type RowKind = "sheet" | "workbook" | "example";
26
+ /**
27
+ * What the row shows: what it is of, the tools the task allows, the one in use, whether undo and
28
+ * clearing have anything to do, whether the finger may be asked to write again, and on a worked
29
+ * example whether Next has a part to open and whether the demo is shown (R42).
30
+ */
31
+ export interface ToolState {
32
+ readonly kind: RowKind;
33
+ readonly tools: readonly Tool[];
34
+ readonly tool: Tool;
35
+ readonly undoable: boolean;
36
+ readonly clearable: boolean;
37
+ readonly finger: boolean;
38
+ readonly opens: boolean;
39
+ readonly expanded: boolean;
40
+ }
41
+ /** The row of the element's own buttons, as the element shows it. */
42
+ export declare class Tools {
43
+ #private;
44
+ /** The row: a toolbar, named in the element's language. */
45
+ readonly element: HTMLDivElement;
46
+ constructor(actions: ToolActions, controls: DemoControls);
47
+ /**
48
+ * The row as the sheet now is. Only what has changed is written. A worked example's row has no
49
+ * tools of writing, no undo and no clearing, for the learner does not write on its sheet, and has
50
+ * Next, unavailable while the demo is hidden or open whole, and Demo, expanded while the demo is
51
+ * shown (R42).
52
+ */
53
+ show({ kind, tools, tool, undoable, clearable, finger, opens, expanded }: ToolState): void;
54
+ /**
55
+ * Whether anything scrolls back up the sheet, for Up, and on down it, for Down: a button with
56
+ * nowhere to go stands unavailable. Only what has changed is written.
57
+ */
58
+ edges(above: boolean, below: boolean): void;
59
+ /**
60
+ * The labels in the language of a tag — the element's `lang` — and the row's own `lang` theirs,
61
+ * so that a screen reader reads them in the language they are in.
62
+ */
63
+ label(tag: string | null): void;
64
+ }
@@ -0,0 +1,254 @@
1
+ // The element's own row of buttons (R28): the tools the task allows — the pen and the eraser — and
2
+ // undo and clearing. It is a toolbar as WAI-ARIA's Authoring Practices have one: one stop of Tab,
3
+ // the arrow keys from button to button, and the tools a group of radio buttons that the arrows go
4
+ // through without choosing one. Undo and Clear with nothing to take back or to clear stay where the
5
+ // focus can reach them, marked unavailable: a last undo from the keyboard leaves the focus where it
6
+ // was. Up and Down scroll the sheet with a single pointer, and stand unavailable at the end they
7
+ // scroll to (R33). In pen mode a last button lets a finger write again (R29). The labels are in
8
+ // the element's language (labels.ts), and the row is named by the canvas kind: the sheet or the
9
+ // workbook (R36). A lesson's worked example names it too, and has no tools of writing in it: the
10
+ // learner does not write on its sheet. Its first buttons are its demo's, Next, which opens the next
11
+ // part, and Demo, a disclosure button that hides the whole demo and shows it again (R42). A button
12
+ // that goes gives its stop of Tab and the focus to the first button shown. This module is no entry
13
+ // point of the package.
14
+ import { SVG, write } from "./attributes.js";
15
+ import { labelsFor } from "./labels.js";
16
+ /** The row of the element's own buttons, as the element shows it. */
17
+ export class Tools {
18
+ /** The row: a toolbar, named in the element's language. */
19
+ element = document.createElement("div");
20
+ /** Next and Demo, a worked example's first buttons, kept together on a narrow screen (R42). */
21
+ #demoPair = document.createElement("div");
22
+ #next = plainButton();
23
+ #demo = plainButton();
24
+ #demoLabel = document.createElement("span");
25
+ #group = document.createElement("div");
26
+ #pen = radioButton();
27
+ #eraser = radioButton();
28
+ #undo = plainButton();
29
+ #clear = plainButton();
30
+ /** Up and Down, a pair that a row wrapping on a narrow screen keeps together. */
31
+ #pair = document.createElement("div");
32
+ #up = plainButton();
33
+ #down = plainButton();
34
+ #finger = plainButton();
35
+ /** The buttons in the order the arrow keys go through them: the row's own. */
36
+ #buttons = [
37
+ this.#next,
38
+ this.#demo,
39
+ this.#pen,
40
+ this.#eraser,
41
+ this.#undo,
42
+ this.#clear,
43
+ this.#up,
44
+ this.#down,
45
+ this.#finger,
46
+ ];
47
+ /** The language of the labels shown. */
48
+ #lang;
49
+ /** What the row is of, named so. */
50
+ #kind = "sheet";
51
+ /** Whether a button of the row has had the focus: Tab comes to the first button until one has. */
52
+ #chosen = false;
53
+ constructor(actions, controls) {
54
+ this.element.className = "tools";
55
+ this.element.setAttribute("role", "toolbar");
56
+ this.#demoPair.className = "demo";
57
+ // Next and Demo control what they do not hold: the region that tells of a part, and the list.
58
+ this.#next.setAttribute("aria-controls", controls.status);
59
+ this.#demo.setAttribute("aria-controls", controls.parts);
60
+ this.#demo.setAttribute("aria-expanded", "true");
61
+ this.#demo.append(triangle(), this.#demoLabel);
62
+ this.#demoPair.append(this.#next, this.#demo);
63
+ this.#group.className = "choice";
64
+ this.#group.setAttribute("role", "radiogroup");
65
+ this.#group.append(this.#pen, this.#eraser);
66
+ this.#pair.className = "updown";
67
+ this.#pair.append(this.#up, this.#down);
68
+ // The finger's button comes last: coming and going, it moves no other button of the row.
69
+ this.element.append(this.#demoPair, this.#group, this.#undo, this.#clear, this.#pair, this.#finger);
70
+ // A worked example's buttons and the finger's come with what they are for.
71
+ for (const hidden of [this.#demoPair, this.#next, this.#demo, this.#finger]) {
72
+ hidden.hidden = true;
73
+ }
74
+ // Tab comes to the first button shown, until another has had the focus.
75
+ this.#stopAt(this.#first());
76
+ // The element decides whether a choice is a change: the tool in use, chosen again, is none.
77
+ this.#pen.addEventListener("click", () => actions.choose("pen"));
78
+ this.#eraser.addEventListener("click", () => actions.choose("eraser"));
79
+ const doing = (button, act) => {
80
+ // aria-disabled stops no click: the button does nothing while it stands unavailable.
81
+ button.addEventListener("click", () => {
82
+ if (button.getAttribute("aria-disabled") !== "true")
83
+ act();
84
+ });
85
+ };
86
+ doing(this.#undo, actions.undo);
87
+ doing(this.#clear, actions.clear);
88
+ doing(this.#next, actions.next);
89
+ this.#demo.addEventListener("click", () => actions.toggle());
90
+ // Up and Down ask the element anew at every press, unavailable or not: a scroll the element did
91
+ // not hear may have left either standing as it no longer is.
92
+ this.#up.addEventListener("click", () => actions.upOrDown(false));
93
+ this.#down.addEventListener("click", () => actions.upOrDown(true));
94
+ this.#finger.addEventListener("click", () => actions.fingers());
95
+ this.element.addEventListener("keydown", (event) => this.#key(event));
96
+ // The button that had the focus last is the one Tab comes back to.
97
+ this.element.addEventListener("focusin", (event) => {
98
+ const focused = this.#buttons.find((button) => button === event.target);
99
+ if (focused === undefined)
100
+ return;
101
+ this.#chosen = true;
102
+ this.#stopAt(focused);
103
+ });
104
+ this.label(null);
105
+ }
106
+ /**
107
+ * The row as the sheet now is. Only what has changed is written. A worked example's row has no
108
+ * tools of writing, no undo and no clearing, for the learner does not write on its sheet, and has
109
+ * Next, unavailable while the demo is hidden or open whole, and Demo, expanded while the demo is
110
+ * shown (R42).
111
+ */
112
+ show({ kind, tools, tool, undoable, clearable, finger, opens, expanded }) {
113
+ if (kind !== this.#kind) {
114
+ this.#kind = kind;
115
+ this.#name();
116
+ }
117
+ const writes = kind !== "example";
118
+ this.#reveal([
119
+ [this.#demoPair, !writes],
120
+ [this.#next, !writes],
121
+ [this.#demo, !writes],
122
+ [this.#group, writes],
123
+ [this.#pen, writes],
124
+ [this.#eraser, writes && tools.includes("eraser")],
125
+ [this.#undo, writes],
126
+ [this.#clear, writes],
127
+ [this.#finger, writes && finger],
128
+ ]);
129
+ write(this.#pen, "aria-checked", String(tool === "pen"));
130
+ write(this.#eraser, "aria-checked", String(tool === "eraser"));
131
+ write(this.#undo, "aria-disabled", undoable ? null : "true");
132
+ write(this.#clear, "aria-disabled", clearable ? null : "true");
133
+ write(this.#next, "aria-disabled", opens ? null : "true");
134
+ write(this.#demo, "aria-expanded", String(expanded));
135
+ }
136
+ /**
137
+ * Whether anything scrolls back up the sheet, for Up, and on down it, for Down: a button with
138
+ * nowhere to go stands unavailable. Only what has changed is written.
139
+ */
140
+ edges(above, below) {
141
+ write(this.#up, "aria-disabled", above ? null : "true");
142
+ write(this.#down, "aria-disabled", below ? null : "true");
143
+ }
144
+ /**
145
+ * The labels in the language of a tag — the element's `lang` — and the row's own `lang` theirs,
146
+ * so that a screen reader reads them in the language they are in.
147
+ */
148
+ label(tag) {
149
+ const { lang, labels } = labelsFor(tag);
150
+ if (lang === this.#lang)
151
+ return;
152
+ this.#lang = lang;
153
+ this.element.lang = lang;
154
+ this.#name();
155
+ this.#group.setAttribute("aria-label", labels.tool);
156
+ this.#next.textContent = labels.next;
157
+ this.#demoLabel.textContent = labels.demo;
158
+ this.#pen.textContent = labels.pen;
159
+ this.#eraser.textContent = labels.eraser;
160
+ this.#undo.textContent = labels.undo;
161
+ this.#clear.textContent = labels.clear;
162
+ this.#up.textContent = labels.up;
163
+ this.#down.textContent = labels.down;
164
+ this.#finger.textContent = labels.finger;
165
+ }
166
+ /** The row's name: the canvas kind's, in the labels' language. */
167
+ #name() {
168
+ const { labels } = labelsFor(this.#lang ?? null);
169
+ write(this.element, "aria-label", labels[this.#kind]);
170
+ }
171
+ /**
172
+ * The arrow keys move the focus to the next button or the one before, round the row, and Home
173
+ * and End to the first and the last: nothing is chosen, and no other key is the row's.
174
+ */
175
+ #key(event) {
176
+ if (event.altKey || event.ctrlKey || event.metaKey)
177
+ return;
178
+ const shown = this.#shown();
179
+ const at = shown.indexOf(event.target);
180
+ if (at < 0)
181
+ return;
182
+ let next;
183
+ if (event.key === "ArrowRight")
184
+ next = shown[(at + 1) % shown.length];
185
+ else if (event.key === "ArrowLeft")
186
+ next = shown[(at - 1 + shown.length) % shown.length];
187
+ else if (event.key === "Home")
188
+ next = shown[0];
189
+ else if (event.key === "End")
190
+ next = shown.at(-1);
191
+ else
192
+ return;
193
+ event.preventDefault();
194
+ next?.focus();
195
+ }
196
+ /**
197
+ * Buttons come or go, all of them at once, and then a button that went gives its stop of Tab and
198
+ * the focus to the first button shown: the pen on a sheet, Next on a worked example (R28, R42). Till
199
+ * a button has had the focus, the stop is the first button shown, whichever it is now. Nothing is
200
+ * read or written while every button stays as it is: the row is shown anew after every stroke.
201
+ */
202
+ #reveal(wanted) {
203
+ if (wanted.every(([element, shown]) => element.hidden !== shown))
204
+ return;
205
+ // Read before a button hides: a hidden one loses the focus at the next update of the style.
206
+ const focused = this.#buttons.find((button) => button.matches(":focus"));
207
+ for (const [element, shown] of wanted)
208
+ element.hidden = !shown;
209
+ const first = this.#first();
210
+ if (!this.#chosen || this.#buttons.some((button) => button.tabIndex === 0 && button.hidden)) {
211
+ this.#stopAt(first);
212
+ }
213
+ if (focused?.hidden)
214
+ first.focus();
215
+ }
216
+ /** The buttons the row shows, in its order. */
217
+ #shown() {
218
+ return this.#buttons.filter((button) => !button.hidden);
219
+ }
220
+ /** The first button the row shows: Up and Down are on every row (R33). */
221
+ #first() {
222
+ return this.#shown()[0] ?? this.#up;
223
+ }
224
+ /** The one button Tab comes to: the arrow keys reach the others. */
225
+ #stopAt(stop) {
226
+ for (const button of this.#buttons)
227
+ button.tabIndex = button === stop ? 0 : -1;
228
+ }
229
+ }
230
+ /** A button of a tool: a radio button of the group, a button still, which the arrows leave alone. */
231
+ function radioButton() {
232
+ const button = plainButton();
233
+ button.setAttribute("role", "radio");
234
+ button.setAttribute("aria-checked", "false");
235
+ return button;
236
+ }
237
+ /**
238
+ * The triangle of a disclosure button, pointing down while what it shows is shown: a picture that
239
+ * says nothing, for aria-expanded says it (R42).
240
+ */
241
+ function triangle() {
242
+ const svg = document.createElementNS(SVG, "svg");
243
+ svg.setAttribute("viewBox", "0 0 10 10");
244
+ svg.setAttribute("aria-hidden", "true");
245
+ const path = document.createElementNS(SVG, "path");
246
+ path.setAttribute("d", "M1 3h8L5 8z");
247
+ svg.append(path);
248
+ return svg;
249
+ }
250
+ function plainButton() {
251
+ const button = document.createElement("button");
252
+ button.type = "button";
253
+ return button;
254
+ }
package/package.json CHANGED
@@ -1,6 +1,43 @@
1
1
  {
2
2
  "name": "@altnovatrail/canvas",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "0.1.0",
4
+ "description": "NovaTrail's canvases: the workplace of a task, where the learner writes, draws and types.",
5
+ "license": "Apache-2.0",
6
+ "repository": {
7
+ "type": "git",
8
+ "url": "git+https://github.com/altnovatrail/canvas.git"
9
+ },
10
+ "type": "module",
11
+ "files": [
12
+ "lib"
13
+ ],
14
+ "exports": {
15
+ ".": {
16
+ "types": "./lib/element/index.d.ts",
17
+ "default": "./lib/element/index.js"
18
+ },
19
+ "./core": {
20
+ "types": "./lib/core/index.d.ts",
21
+ "default": "./lib/core/index.js"
22
+ }
23
+ },
24
+ "sideEffects": [
25
+ "./lib/element/index.js"
26
+ ],
27
+ "dependencies": {
28
+ "@altnovatrail/spec": "0.6.2",
29
+ "perfect-freehand": "1.2.3",
30
+ "rbush": "4.0.1"
31
+ },
32
+ "devDependencies": {
33
+ "@biomejs/biome": "2.6.4",
34
+ "@types/node": "24.19.2",
35
+ "@types/rbush": "4.0.0",
36
+ "@vitest/browser-playwright": "5.0.5",
37
+ "@vitest/coverage-v8": "5.0.5",
38
+ "playwright": "1.64.1",
39
+ "typescript": "7.0.2",
40
+ "vite": "8.3.5",
41
+ "vitest": "5.0.5"
42
+ }
43
+ }