@zinejs/core 0.9.0 → 0.9.2

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 (37) hide show
  1. package/LICENSE +73 -0
  2. package/dist/controls/icons.d.ts +4 -0
  3. package/dist/controls/sidebar.d.ts +2 -0
  4. package/dist/controls/types.d.ts +3 -0
  5. package/dist/{controls-CcOu_19g.js → controls-Dh1okIx6.js} +60 -44
  6. package/dist/controls-Dh1okIx6.js.map +1 -0
  7. package/dist/{cssRenderer-Deskm2ND.js → cssRenderer-CTaM4cAj.js} +35 -29
  8. package/dist/cssRenderer-CTaM4cAj.js.map +1 -0
  9. package/dist/flipSound-BFXSkWBo.js +78 -0
  10. package/dist/flipSound-BFXSkWBo.js.map +1 -0
  11. package/dist/index.d.ts +2 -0
  12. package/dist/index.js +709 -569
  13. package/dist/index.js.map +1 -1
  14. package/dist/index.umd.js +4 -4
  15. package/dist/index.umd.js.map +1 -1
  16. package/dist/loading/loading.d.ts +2 -1
  17. package/dist/{loading-DF88K9yJ.js → loading-BgbqJz5k.js} +7 -8
  18. package/dist/loading-BgbqJz5k.js.map +1 -0
  19. package/dist/renderer/types.d.ts +7 -0
  20. package/dist/{share-BrQa6aYm.js → share-CZorhnKI.js} +25 -22
  21. package/dist/share-CZorhnKI.js.map +1 -0
  22. package/dist/sound/clip.d.ts +9 -0
  23. package/dist/sound/flipSound.d.ts +30 -0
  24. package/dist/strings.d.ts +74 -0
  25. package/dist/{styles-D_zOGk2q.js → styles-C1Qp5pby.js} +4 -2
  26. package/dist/styles-C1Qp5pby.js.map +1 -0
  27. package/dist/{webglRenderer-CmQ_mUrX.js → webglRenderer-DQn4pc1S.js} +85 -79
  28. package/dist/webglRenderer-DQn4pc1S.js.map +1 -0
  29. package/dist/zine.d.ts +35 -0
  30. package/options.schema.json +27 -0
  31. package/package.json +12 -10
  32. package/dist/controls-CcOu_19g.js.map +0 -1
  33. package/dist/cssRenderer-Deskm2ND.js.map +0 -1
  34. package/dist/loading-DF88K9yJ.js.map +0 -1
  35. package/dist/share-BrQa6aYm.js.map +0 -1
  36. package/dist/styles-D_zOGk2q.js.map +0 -1
  37. package/dist/webglRenderer-CmQ_mUrX.js.map +0 -1
package/LICENSE ADDED
@@ -0,0 +1,73 @@
1
+ # PolyForm Noncommercial License 1.0.0
2
+
3
+ <https://polyformproject.org/licenses/noncommercial/1.0.0>
4
+
5
+ ## Acceptance
6
+
7
+ In order to get any license under these terms, you must agree to them as both strict obligations and conditions to all your licenses.
8
+
9
+ ## Copyright License
10
+
11
+ The licensor grants you a copyright license for the software to do everything you might do with the software that would otherwise infringe the licensor's copyright in it for any permitted purpose. However, you may only distribute the software according to [Distribution License](#distribution-license) and make changes or new works based on the software according to [Changes and New Works License](#changes-and-new-works-license).
12
+
13
+ ## Distribution License
14
+
15
+ The licensor grants you an additional copyright license to distribute copies of the software. Your license to distribute covers distributing the software with changes and new works permitted by [Changes and New Works License](#changes-and-new-works-license).
16
+
17
+ ## Notices
18
+
19
+ You must ensure that anyone who gets a copy of any part of the software from you also gets a copy of these terms or the URL for them above, as well as copies of any plain-text lines beginning with `Required Notice:` that the licensor provided with the software. For example:
20
+
21
+ > Required Notice: Copyright Yoyodyne, Inc. (http://example.com)
22
+
23
+ ## Changes and New Works License
24
+
25
+ The licensor grants you an additional copyright license to make changes and new works based on the software for any permitted purpose.
26
+
27
+ ## Patent License
28
+
29
+ The licensor grants you a patent license for the software that covers patent claims the licensor can license, or becomes able to license, that you would infringe by using the software.
30
+
31
+ ## Noncommercial Purposes
32
+
33
+ Any noncommercial purpose is a permitted purpose.
34
+
35
+ ## Personal Uses
36
+
37
+ Personal use for research, experiment, and testing for the benefit of public knowledge, personal study, private entertainment, hobby projects, amateur pursuits, or religious observance, without any anticipated commercial application, is use for a permitted purpose.
38
+
39
+ ## Noncommercial Organizations
40
+
41
+ Use by any charitable organization, educational institution, public research organization, public safety or health organization, environmental protection organization, or government institution is use for a permitted purpose regardless of the source of funding or obligations resulting from the funding.
42
+
43
+ ## Fair Use
44
+
45
+ You may have "fair use" rights for the software under the law. These terms do not limit them.
46
+
47
+ ## No Other Rights
48
+
49
+ These terms do not allow you to sublicense or transfer any of your licenses to anyone else, or prevent the licensor from granting licenses to anyone else. These terms do not imply any other licenses.
50
+
51
+ ## Patent Defense
52
+
53
+ If you make any written claim that the software infringes or contributes to infringement of any patent, your patent license for the software granted under these terms ends immediately. If your company makes such a claim, your patent license ends immediately for work on behalf of your company.
54
+
55
+ ## Violations
56
+
57
+ The first time you are notified in writing that you have violated any of these terms, or done anything with the software not covered by your licenses, your licenses can nonetheless continue if you come into full compliance with these terms, and take practical steps to correct past violations, within 32 days of receiving notice. Otherwise, all your licenses end immediately.
58
+
59
+ ## No Liability
60
+
61
+ ***As far as the law allows, the software comes as is, without any warranty or condition, and the licensor will not be liable to you for any damages arising out of these terms or the use or nature of the software, under any kind of legal claim.***
62
+
63
+ ## Definitions
64
+
65
+ The **licensor** is the individual or entity offering these terms, and the **software** is the software the licensor makes available under these terms.
66
+
67
+ **You** refers to the individual or entity agreeing to these terms.
68
+
69
+ **Your company** is any legal entity, sole proprietorship, or other kind of organization that you work for, plus all organizations that have control over, are under the control of, or are under common control with that organization. **Control** means ownership of substantially all the assets of an entity, or the power to direct its management and policies by vote, contract, or otherwise. Control can be direct or indirect.
70
+
71
+ **Your licenses** are all the licenses granted to you for the software under these terms.
72
+
73
+ **Use** means anything you do with the software requiring one of your licenses.
@@ -46,6 +46,10 @@ export declare const ICONS: {
46
46
  download: string;
47
47
  /** x */
48
48
  close: string;
49
+ /** volume-2: sound on */
50
+ soundOn: string;
51
+ /** volume-x: muted */
52
+ soundOff: string;
49
53
  };
50
54
  /** Build an `<svg>` for one of the {@link ICONS}, or null if the name is unknown. */
51
55
  export declare function createIcon(doc: Document, markup: string): SVGSVGElement;
@@ -46,6 +46,8 @@ export declare class Sidebar {
46
46
  * than the whole container, which is taller whenever the page is letterboxed (its aspect not
47
47
  * matching the container's) or a docked toolbar padded the wrap. Re-read on every resize. */
48
48
  pageBox?: () => PageBox | null;
49
+ /** aria-label for the drawer's close button. Defaults to 'Close'. */
50
+ closeLabel?: string;
49
51
  });
50
52
  /** Resize the rail, e.g. once a thumbnail list knows whether it needs one column or two. */
51
53
  setWidth(px: number): void;
@@ -1,4 +1,5 @@
1
1
  import type { Zine } from '../zine';
2
+ import type { ZineStrings } from '../strings';
2
3
  /** Where the toolbar sits relative to the book. */
3
4
  export type ControlsPosition = 'top' | 'bottom' | 'left' | 'right';
4
5
  /** How the controls decide between their light and dark palettes. */
@@ -12,6 +13,8 @@ export interface ControlContext {
12
13
  /** The controls' resolved colour scheme, for a control that mounts its own root outside the
13
14
  * toolbar (the share dialog) and so has to carry the scheme across to it. */
14
15
  readonly colorScheme: ControlsColorScheme;
16
+ /** Resolved reader-facing text (English defaults merged with the `strings` option). */
17
+ readonly strings: ZineStrings;
15
18
  }
16
19
  /**
17
20
  * One button in the toolbar.
@@ -1,17 +1,17 @@
1
1
  import { i as e, n as t, r as n, t as r } from "./registry-5nuYFMd4.js";
2
- import { i, n as a, r as o, t as s } from "./styles-D_zOGk2q.js";
2
+ import { i, n as a, r as o, t as s } from "./styles-C1Qp5pby.js";
3
3
  //#region src/controls/builtins.ts
4
4
  var c = Math.SQRT2, l = (e) => e.zine.container.ownerDocument?.fullscreenElement === e.zine.container;
5
5
  function u() {
6
6
  t({
7
7
  id: "prev",
8
- title: "Previous page",
8
+ title: (e) => e.strings.prevPage,
9
9
  icon: o.prev,
10
10
  isDisabled: (e) => !e.zine.canFlipPrev(),
11
11
  action: (e) => e.zine.flipPrev()
12
12
  }), t({
13
13
  id: "next",
14
- title: "Next page",
14
+ title: (e) => e.strings.nextPage,
15
15
  icon: o.next,
16
16
  isDisabled: (e) => !e.zine.canFlipNext(),
17
17
  action: (e) => e.zine.flipNext()
@@ -19,7 +19,7 @@ function u() {
19
19
  let e = (e) => e.zine.getDirection() === "rtl";
20
20
  t({
21
21
  id: "first",
22
- title: "First page",
22
+ title: (e) => e.strings.firstPage,
23
23
  icon: (t) => e(t) ? o.last : o.first,
24
24
  isDisabled: (e) => !e.zine.canFlipPrev(),
25
25
  action: (e) => {
@@ -27,7 +27,7 @@ function u() {
27
27
  }
28
28
  }), t({
29
29
  id: "last",
30
- title: "Last page",
30
+ title: (e) => e.strings.lastPage,
31
31
  icon: (t) => e(t) ? o.first : o.last,
32
32
  isDisabled: (e) => !e.zine.canFlipNext(),
33
33
  action: (e) => {
@@ -35,19 +35,19 @@ function u() {
35
35
  }
36
36
  }), t({
37
37
  id: "zoomIn",
38
- title: "Zoom in",
38
+ title: (e) => e.strings.zoomIn,
39
39
  icon: o.zoomIn,
40
40
  isDisabled: (e) => e.zine.getZoom() >= e.zine.getMaxZoom() - 1e-6,
41
41
  action: (e) => e.zine.setZoom(e.zine.getZoom() * c)
42
42
  }), t({
43
43
  id: "zoomOut",
44
- title: "Zoom out",
44
+ title: (e) => e.strings.zoomOut,
45
45
  icon: o.zoomOut,
46
46
  isDisabled: (e) => e.zine.getZoom() <= 1.000001,
47
47
  action: (e) => e.zine.setZoom(e.zine.getZoom() / c)
48
48
  }), t({
49
49
  id: "fullscreen",
50
- title: "Fullscreen",
50
+ title: (e) => e.strings.fullscreen,
51
51
  icon: o.fullscreen,
52
52
  isVisible: (e) => typeof e.zine.container.requestFullscreen == "function",
53
53
  isActive: l,
@@ -55,18 +55,25 @@ function u() {
55
55
  let t = e.zine.container.ownerDocument;
56
56
  l(e) ? t?.exitFullscreen?.() : e.zine.container.requestFullscreen?.(), e.close();
57
57
  }
58
+ }), t({
59
+ id: "mute",
60
+ title: (e) => e.zine.isSoundMuted() ? e.strings.unmute : e.strings.mute,
61
+ icon: (e) => e.zine.isSoundMuted() ? o.soundOff : o.soundOn,
62
+ isVisible: (e) => e.zine.isSoundEnabled(),
63
+ isActive: (e) => e.zine.isSoundMuted(),
64
+ action: (e) => e.zine.setSoundMuted(!e.zine.isSoundMuted())
58
65
  }), t({
59
66
  id: "share",
60
- title: "Share",
67
+ title: (e) => e.strings.share,
61
68
  icon: o.share,
62
69
  action: (e) => {
63
- e.close(), import("./share-BrQa6aYm.js").then(({ ShareDialog: t }) => new t(e.zine, e.zine.container, e.colorScheme)).catch(() => {
70
+ e.close(), import("./share-CZorhnKI.js").then(({ ShareDialog: t }) => new t(e.zine, e.zine.container, e.colorScheme)).catch(() => {
64
71
  navigator.clipboard?.writeText(e.zine.pageLink()).catch(() => {});
65
72
  });
66
73
  }
67
74
  }), t({
68
75
  id: "download",
69
- title: "Download PDF",
76
+ title: (e) => e.strings.downloadPdf,
70
77
  icon: o.download,
71
78
  isVisible: (e) => e.zine.canDownload(),
72
79
  action: async (e) => {
@@ -74,7 +81,7 @@ function u() {
74
81
  }
75
82
  }), t({
76
83
  id: "spread",
77
- title: (e) => e.zine.isSinglePage() ? "Show two pages" : "Show one page",
84
+ title: (e) => e.zine.isSinglePage() ? e.strings.showTwoPages : e.strings.showOnePage,
78
85
  icon: (e) => e.zine.isSinglePage() ? o.twoPages : o.onePage,
79
86
  isVisible: (e) => !e.zine.isResponsiveSingle(),
80
87
  action: (e) => {
@@ -82,7 +89,7 @@ function u() {
82
89
  }
83
90
  }), t({
84
91
  id: "print",
85
- title: "Print",
92
+ title: (e) => e.strings.print,
86
93
  icon: o.print,
87
94
  isVisible: (e) => e.zine.canPrint(),
88
95
  action: async (e) => {
@@ -90,7 +97,7 @@ function u() {
90
97
  }
91
98
  }), t({
92
99
  id: "menu",
93
- title: "More",
100
+ title: (e) => e.strings.more,
94
101
  icon: o.menu,
95
102
  children: [
96
103
  "first",
@@ -98,6 +105,7 @@ function u() {
98
105
  "spread",
99
106
  "thumbnails",
100
107
  "outline",
108
+ "mute",
101
109
  "print",
102
110
  "download"
103
111
  ]
@@ -222,7 +230,7 @@ var C = class {
222
230
  "dblclick",
223
231
  "wheel"
224
232
  ]) this.root.addEventListener(e, (e) => e.stopPropagation());
225
- this.#u(e, t, n.rtl ?? !1, n.overlay ?? !1), n.onDismiss && this.#d(e, n.onDismiss), this.#c(t);
233
+ this.#u(e, t, n.rtl ?? !1, n.overlay ?? !1), n.onDismiss && this.#d(e, n.onDismiss, n.closeLabel ?? "Close"), this.#c(t);
226
234
  }
227
235
  #c(e) {
228
236
  this.#o && (this.#l(), !(typeof ResizeObserver > "u") && (this.#s = new ResizeObserver(() => this.#l()), this.#s.observe(e)));
@@ -242,11 +250,11 @@ var C = class {
242
250
  let i = t.closest(".zine-controls-wrap") ?? t, a = e.createElement("div");
243
251
  a.className = "zine-panel-wrap", n && a.classList.add("zine-panel-wrap-rtl"), r && a.classList.add("zine-panel-wrap-overlay"), i.parentNode.insertBefore(a, i), a.append(this.#e, i), this.#t = a;
244
252
  }
245
- #d(e, t) {
246
- let n = e.createElement("button");
247
- if (n.type = "button", n.className = "zine-panel-close", n.setAttribute("aria-label", "Close"), n.appendChild(i(e, o.close)), n.addEventListener("click", (e) => {
253
+ #d(e, t, n) {
254
+ let r = e.createElement("button");
255
+ if (r.type = "button", r.className = "zine-panel-close", r.setAttribute("aria-label", n), r.appendChild(i(e, o.close)), r.addEventListener("click", (e) => {
248
256
  e.stopPropagation(), t();
249
- }), this.#e.appendChild(n), this.#r = n, this.#t) {
257
+ }), this.#e.appendChild(r), this.#r = r, this.#t) {
250
258
  let n = e.createElement("div");
251
259
  n.className = "zine-panel-scrim", this.#t.insertBefore(n, this.#e);
252
260
  for (let e of ["pointerdown", "click"]) n.addEventListener(e, (e) => {
@@ -280,7 +288,8 @@ var C = class {
280
288
  let r = t.ownerDocument;
281
289
  this.#t = r, this.#n = new C(r, t, {
282
290
  className: "zine-thumbs",
283
- label: "Pages",
291
+ label: e.strings.thumbnailsLabel,
292
+ closeLabel: e.strings.close,
284
293
  width: 90,
285
294
  onDismiss: n.onDismiss,
286
295
  rtl: e.getDirection() === "rtl",
@@ -300,7 +309,7 @@ var C = class {
300
309
  n.className = "zine-thumbs-cell", n.dataset.page = String(t), e.appendChild(n);
301
310
  }
302
311
  let o = [...a].sort((e, t) => e - t).map((e) => e + 1);
303
- e.setAttribute("aria-label", o.length > 1 ? `Pages ${o[0]}–${o.at(-1)}` : `Page ${o[0] ?? ""}`);
312
+ e.setAttribute("aria-label", this.#e.strings.thumbnailAria(o.map(String)));
304
313
  let s = this.#t.createElement("span");
305
314
  s.className = "zine-thumbs-caption", s.textContent = o.join("–"), e.appendChild(s), e.addEventListener("click", () => {
306
315
  a.length > 0 && this.#e.flipTo(Math.min(...a));
@@ -367,13 +376,14 @@ var C = class {
367
376
  let r = t.ownerDocument;
368
377
  this.#t = r, this.#a = n.onDismiss, this.#n = new C(r, t, {
369
378
  className: "zine-outline",
370
- label: "Outline",
379
+ label: e.strings.outlineLabel,
380
+ closeLabel: e.strings.close,
371
381
  width: E,
372
382
  onDismiss: n.onDismiss,
373
383
  rtl: e.getDirection() === "rtl",
374
384
  overlay: !0,
375
385
  pageBox: () => e.getPageBox()
376
- }), this.root = this.#n.root, this.#o("Loading…"), this.#s(), this.#i.push(e.on("pageChanged", () => this.#l()));
386
+ }), this.root = this.#n.root, this.#o(e.strings.outlineLoading), this.#s(), this.#i.push(e.on("pageChanged", () => this.#l()));
377
387
  }
378
388
  #o(e) {
379
389
  let t = this.#t.createElement("div");
@@ -383,7 +393,7 @@ var C = class {
383
393
  let e = await this.#e.getOutline();
384
394
  if (this.#n.root.isConnected) {
385
395
  if (e.length === 0) {
386
- this.#o("This document has no outline.");
396
+ this.#o(this.#e.strings.outlineEmpty);
387
397
  return;
388
398
  }
389
399
  this.#n.root.replaceChildren(), this.#c(e, 0), this.#l();
@@ -392,10 +402,12 @@ var C = class {
392
402
  #c(e, t) {
393
403
  for (let n of e) {
394
404
  let e = this.#t.createElement("button");
395
- if (e.type = "button", e.className = "zine-outline-row", e.setAttribute("role", "option"), e.style.paddingLeft = `${8 + t * D}px`, e.textContent = n.title, e.title = n.title, n.page === null) e.disabled = !0;
405
+ e.type = "button", e.className = "zine-outline-row", e.setAttribute("role", "option"), e.style.paddingLeft = `${8 + t * D}px`;
406
+ let r = n.title.trim() || this.#e.strings.untitled;
407
+ if (e.textContent = r, e.title = r, n.page === null) e.disabled = !0;
396
408
  else {
397
409
  let t = n.page;
398
- e.setAttribute("aria-label", `${n.title}, page ${t + 1}`), e.addEventListener("click", () => {
410
+ e.setAttribute("aria-label", this.#e.strings.outlineEntryAria(r, t + 1)), e.addEventListener("click", () => {
399
411
  this.#e.flipTo(t), this.#a?.();
400
412
  }), this.#r.push({
401
413
  el: e,
@@ -430,13 +442,14 @@ var C = class {
430
442
  let r = t.ownerDocument;
431
443
  this.#t = r, this.#s = n.onDismiss, this.#n = new C(r, t, {
432
444
  className: "zine-search",
433
- label: "Search results",
445
+ label: e.strings.searchResultsLabel,
446
+ closeLabel: e.strings.close,
434
447
  width: k,
435
448
  onDismiss: n.onDismiss,
436
449
  rtl: e.getDirection() === "rtl",
437
450
  overlay: !0,
438
451
  pageBox: () => e.getPageBox()
439
- }), this.root = this.#n.root, this.#r = r.createElement("input"), this.#r.type = "search", this.#r.className = "zine-search-input", this.#r.placeholder = "Search…", this.#r.setAttribute("aria-label", "Search the document"), this.#r.addEventListener("input", () => {
452
+ }), this.root = this.#n.root, this.#r = r.createElement("input"), this.#r.type = "search", this.#r.className = "zine-search-input", this.#r.placeholder = e.strings.searchPlaceholder, this.#r.setAttribute("aria-label", e.strings.searchInputLabel), this.#r.addEventListener("input", () => {
440
453
  this.#o && clearTimeout(this.#o), this.#o = setTimeout(() => void this.#u(), A);
441
454
  }), this.#r.addEventListener("keydown", (e) => {
442
455
  e.key === "Enter" && (this.#o && clearTimeout(this.#o), this.#u());
@@ -460,20 +473,20 @@ var C = class {
460
473
  this.#c = [], this.#i.replaceChildren();
461
474
  return;
462
475
  }
463
- this.#l("Searching…");
476
+ this.#l(this.#e.strings.searching);
464
477
  let n = await this.#e.search(e);
465
478
  t !== this.#a || !this.#n.root.isConnected || this.#d(e, n);
466
479
  }
467
480
  #d(e, t) {
468
481
  if (this.#c = t, t.length === 0) {
469
- this.#l(`No matches for “${e}”`);
482
+ this.#l(this.#e.strings.noMatches(e));
470
483
  return;
471
484
  }
472
485
  this.#i.replaceChildren(...t.map((e) => {
473
486
  let t = this.#t.createElement("button");
474
- t.type = "button", t.className = "zine-search-hit", t.setAttribute("role", "option"), t.setAttribute("aria-label", `Page ${e.page + 1}: ${e.excerpt}`);
487
+ t.type = "button", t.className = "zine-search-hit", t.setAttribute("role", "option"), t.setAttribute("aria-label", this.#e.strings.searchHitAria(e.page + 1, e.excerpt));
475
488
  let n = this.#t.createElement("span");
476
- n.className = "zine-search-page", n.textContent = `Page ${e.page + 1}`;
489
+ n.className = "zine-search-page", n.textContent = this.#e.strings.searchHitLabel(e.page + 1);
477
490
  let r = this.#t.createElement("small");
478
491
  return r.textContent = e.excerpt, t.append(n, r), t.addEventListener("click", () => {
479
492
  this.#e.flipTo(e.page), this.#s?.();
@@ -493,7 +506,7 @@ var C = class {
493
506
  constructor(e, t, n = "auto", r = null) {
494
507
  this.#e = e, this.#i = r;
495
508
  let i = t.ownerDocument, a = e.getDirection() === "rtl";
496
- if (this.#t = this.#o(i, a ? o.next : o.prev, "Previous page", () => e.flipPrev()), this.#n = this.#o(i, a ? o.prev : o.next, "Next page", () => e.flipNext()), this.#s(i, t), this.#r) s(this.#r, n);
509
+ if (this.#t = this.#o(i, a ? o.next : o.prev, e.strings.prevPage, () => e.flipPrev()), this.#n = this.#o(i, a ? o.prev : o.next, e.strings.nextPage, () => e.flipNext()), this.#s(i, t), this.#r) s(this.#r, n);
497
510
  else for (let e of [this.#t, this.#n]) s(e, n);
498
511
  this.#a.push(e.on("pageChanged", ({ page: e }) => this.#c(e))), this.#a.push(e.on("flipEnd", () => this.#c())), this.#c();
499
512
  }
@@ -589,7 +602,7 @@ var L = class {
589
602
  `zine-controls-${o}`,
590
603
  c ? "zine-controls-docked" : "zine-controls-floating",
591
604
  n.className
592
- ].filter(Boolean).join(" "), this.#n.setAttribute("role", "toolbar"), this.#n.setAttribute("aria-label", "Flipbook controls"), s(this.#n, this.#h), this.#r = i.createElement("div"), this.#r.className = "zine-controls-bar", this.#n.appendChild(this.#r), this.#n.addEventListener("keydown", (e) => this.#E(e));
605
+ ].filter(Boolean).join(" "), this.#n.setAttribute("role", "toolbar"), this.#n.setAttribute("aria-label", this.#e.strings.controlsLabel), s(this.#n, this.#h), this.#r = i.createElement("div"), this.#r.className = "zine-controls-bar", this.#n.appendChild(this.#r), this.#n.addEventListener("keydown", (e) => this.#E(e));
593
606
  for (let e of F) this.#n.addEventListener(e, (e) => this.#y(e));
594
607
  this.#v(t, o, c);
595
608
  let l = () => this.#D();
@@ -632,7 +645,8 @@ var L = class {
632
645
  return {
633
646
  zine: this.#e,
634
647
  close: e,
635
- colorScheme: this.#h
648
+ colorScheme: this.#h,
649
+ strings: this.#e.strings
636
650
  };
637
651
  }
638
652
  #x(t) {
@@ -704,9 +718,9 @@ var L = class {
704
718
  let e = this.#t.createElement("div");
705
719
  e.className = "zine-controls-page";
706
720
  let t = this.#t.createElement("input");
707
- t.type = "number", t.min = "1", t.max = String(this.#e.getPageCount()), t.value = String(this.#e.getPage() + 1), t.setAttribute("aria-label", "Page number");
721
+ t.type = "number", t.min = "1", t.max = String(this.#e.getPageCount()), t.value = String(this.#e.getPage() + 1), t.setAttribute("aria-label", this.#e.strings.pageNumberLabel);
708
722
  let n = this.#t.createElement("span");
709
- n.textContent = `/ ${this.#e.getPageCount()}`;
723
+ n.textContent = this.#e.strings.pageTotal(this.#e.getPageCount());
710
724
  let r = !1, i = () => {
711
725
  if (r) return;
712
726
  r = !0;
@@ -747,7 +761,7 @@ var L = class {
747
761
  function R(e) {
748
762
  u(), t({
749
763
  id: "thumbnails",
750
- title: () => e.openPanel() === "thumbnails" ? "Hide thumbnails" : "Show thumbnails",
764
+ title: (t) => e.openPanel() === "thumbnails" ? t.strings.hideThumbnails : t.strings.showThumbnails,
751
765
  icon: o.thumbnails,
752
766
  isVisible: (e) => e.zine.isDocument(),
753
767
  isActive: () => e.openPanel() === "thumbnails",
@@ -756,7 +770,7 @@ function R(e) {
756
770
  }
757
771
  }), t({
758
772
  id: "outline",
759
- title: () => e.openPanel() === "outline" ? "Hide outline" : "Show outline",
773
+ title: (t) => e.openPanel() === "outline" ? t.strings.hideOutline : t.strings.showOutline,
760
774
  icon: o.outline,
761
775
  isVisible: () => e.hasOutline(),
762
776
  isActive: () => e.openPanel() === "outline",
@@ -765,11 +779,11 @@ function R(e) {
765
779
  }
766
780
  }), t({
767
781
  id: "pageInput",
768
- title: "Page",
782
+ title: (e) => e.strings.pageWidgetLabel,
769
783
  render: () => e.makePageInput()
770
784
  }), t({
771
785
  id: "search",
772
- title: () => e.openPanel() === "search" ? "Hide search" : "Search",
786
+ title: (t) => e.openPanel() === "search" ? t.strings.searchClose : t.strings.searchOpen,
773
787
  icon: o.search,
774
788
  isVisible: (e) => e.zine.canSearch(),
775
789
  isActive: () => e.openPanel() === "search",
@@ -786,6 +800,7 @@ var z = [
786
800
  "next",
787
801
  "|",
788
802
  "fullscreen",
803
+ "mute",
789
804
  "|",
790
805
  "print",
791
806
  "download",
@@ -823,12 +838,13 @@ var z = [
823
838
  return {
824
839
  zine: this.#e,
825
840
  close: () => this.#d(),
826
- colorScheme: this.#i
841
+ colorScheme: this.#i,
842
+ strings: this.#e.strings
827
843
  };
828
844
  }
829
845
  #l(e, t) {
830
846
  this.#d();
831
- let n = new x(this.#n, () => this.#c(), () => this.#o?.refresh(), this.#a);
847
+ let n = new x(this.#n, () => this.#c(), () => this.#d(), this.#a);
832
848
  if (n.build(this.#r), !n.el.querySelector("button")) return;
833
849
  let r = this.#n.createElement("div");
834
850
  r.className = "zine-context-layer", s(r, this.#i), r.appendChild(n.el), this.#t.appendChild(r), this.#o = n, n.refresh(), this.#u(n.el, e, t), n.focusFirst();
@@ -866,4 +882,4 @@ function U(e, t, n = {}, r = /* @__PURE__ */ new Set()) {
866
882
  //#endregion
867
883
  export { U as mountContextMenu, H as mountControls };
868
884
 
869
- //# sourceMappingURL=controls-CcOu_19g.js.map
885
+ //# sourceMappingURL=controls-Dh1okIx6.js.map