@openleaf-editor/ui 0.1.0-beta.0 → 0.1.0-beta.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 (62) hide show
  1. package/dist/content-css.d.ts +13 -0
  2. package/dist/content-css.d.ts.map +1 -0
  3. package/dist/content-css.js +57 -0
  4. package/dist/content-css.js.map +1 -0
  5. package/dist/dialog.d.ts +87 -2
  6. package/dist/dialog.d.ts.map +1 -1
  7. package/dist/dialog.js +358 -68
  8. package/dist/dialog.js.map +1 -1
  9. package/dist/floating.d.ts +25 -0
  10. package/dist/floating.d.ts.map +1 -0
  11. package/dist/floating.js +82 -0
  12. package/dist/floating.js.map +1 -0
  13. package/dist/help.d.ts +9 -0
  14. package/dist/help.d.ts.map +1 -0
  15. package/dist/help.js +73 -0
  16. package/dist/help.js.map +1 -0
  17. package/dist/i18n.d.ts +31 -0
  18. package/dist/i18n.d.ts.map +1 -0
  19. package/dist/i18n.js +89 -0
  20. package/dist/i18n.js.map +1 -0
  21. package/dist/icons.d.ts +1 -1
  22. package/dist/icons.d.ts.map +1 -1
  23. package/dist/icons.js +17 -1
  24. package/dist/icons.js.map +1 -1
  25. package/dist/index.d.ts +10 -3
  26. package/dist/index.d.ts.map +1 -1
  27. package/dist/index.js +10 -3
  28. package/dist/index.js.map +1 -1
  29. package/dist/items.d.ts +2 -0
  30. package/dist/items.d.ts.map +1 -1
  31. package/dist/items.js +81 -4
  32. package/dist/items.js.map +1 -1
  33. package/dist/menu.d.ts +56 -0
  34. package/dist/menu.d.ts.map +1 -0
  35. package/dist/menu.js +360 -0
  36. package/dist/menu.js.map +1 -0
  37. package/dist/openleaf.css +216 -0
  38. package/dist/overflow.d.ts +17 -0
  39. package/dist/overflow.d.ts.map +1 -0
  40. package/dist/overflow.js +138 -0
  41. package/dist/overflow.js.map +1 -0
  42. package/dist/pickers.d.ts +43 -0
  43. package/dist/pickers.d.ts.map +1 -0
  44. package/dist/pickers.js +52 -0
  45. package/dist/pickers.js.map +1 -0
  46. package/dist/registry.d.ts +49 -8
  47. package/dist/registry.d.ts.map +1 -1
  48. package/dist/registry.js +14 -0
  49. package/dist/registry.js.map +1 -1
  50. package/dist/styles.d.ts +1 -1
  51. package/dist/styles.d.ts.map +1 -1
  52. package/dist/styles.js +216 -0
  53. package/dist/styles.js.map +1 -1
  54. package/dist/toolbar.d.ts +20 -0
  55. package/dist/toolbar.d.ts.map +1 -1
  56. package/dist/toolbar.js +181 -21
  57. package/dist/toolbar.js.map +1 -1
  58. package/dist/upload.d.ts +95 -0
  59. package/dist/upload.d.ts.map +1 -0
  60. package/dist/upload.js +104 -0
  61. package/dist/upload.js.map +1 -0
  62. package/package.json +2 -2
package/dist/styles.js CHANGED
@@ -87,6 +87,7 @@ export const CSS = `
87
87
  --ol-focus-offset: var(--openleaf-focus-offset, 1px);
88
88
 
89
89
  display: block;
90
+ position: relative;
90
91
  color: var(--ol-text);
91
92
  }
92
93
 
@@ -335,6 +336,15 @@ export const CSS = `
335
336
  position: relative;
336
337
  }
337
338
 
339
+ /* Presentational valign is otherwise beaten by the rule above, which would
340
+ make the cell-properties dialog a no-op on screen. */
341
+ .ol-editor .ol-content .ProseMirror td[valign="middle"],
342
+ .ol-editor .ol-content .ProseMirror th[valign="middle"] { vertical-align: middle; }
343
+ .ol-editor .ol-content .ProseMirror td[valign="bottom"],
344
+ .ol-editor .ol-content .ProseMirror th[valign="bottom"] { vertical-align: bottom; }
345
+ .ol-editor .ol-content .ProseMirror td[valign="baseline"],
346
+ .ol-editor .ol-content .ProseMirror th[valign="baseline"] { vertical-align: baseline; }
347
+
338
348
  .ol-editor .ol-content .ProseMirror th {
339
349
  background: var(--ol-surface-hover);
340
350
  font-weight: 600;
@@ -369,6 +379,47 @@ export const CSS = `
369
379
  cursor: col-resize;
370
380
  }
371
381
 
382
+ .ol-editor .ol-content .ProseMirror img.ol-float-left {
383
+ float: left;
384
+ margin: 0 1em 0.5em 0;
385
+ }
386
+ .ol-editor .ol-content .ProseMirror img.ol-float-right {
387
+ float: right;
388
+ margin: 0 0 0.5em 1em;
389
+ }
390
+ .ol-editor .ol-content .ProseMirror img.ol-align-center {
391
+ display: block;
392
+ margin: 0 auto 0.5em;
393
+ }
394
+ .ol-editor .ol-content .ProseMirror figure {
395
+ margin: 0 0 1em;
396
+ }
397
+ .ol-editor .ol-content .ProseMirror figcaption {
398
+ font-size: .9em;
399
+ opacity: .8;
400
+ margin-top: .35em;
401
+ }
402
+ .ol-editor .ol-content .ProseMirror details {
403
+ border: 1px solid var(--ol-border);
404
+ border-radius: var(--ol-radius);
405
+ padding: .5em .75em;
406
+ margin: 0 0 1em;
407
+ }
408
+ .ol-editor .ol-content .ProseMirror summary {
409
+ cursor: pointer;
410
+ font-weight: 600;
411
+ }
412
+ .ol-editor .ol-content .ProseMirror hr.ol-pagebreak {
413
+ border: 0;
414
+ border-top: 2px dashed var(--ol-border);
415
+ margin: 1.5em 0;
416
+ }
417
+ .ol-editor .ol-content .ProseMirror iframe,
418
+ .ol-editor .ol-content .ProseMirror video,
419
+ .ol-editor .ol-content .ProseMirror audio {
420
+ max-width: 100%;
421
+ }
422
+
372
423
  /* Preserved-but-unrecognised markup, surfaced rather than hidden. */
373
424
  .ol-editor .ol-content .ProseMirror-selectednode {
374
425
  outline: 2px solid var(--ol-focus);
@@ -417,6 +468,171 @@ export const CSS = `
417
468
  border: 0;
418
469
  }
419
470
 
471
+ /* Menubar, menus, floating chrome, visual aids, fullscreen, inline. */
472
+ .ol-editor .ol-menubar {
473
+ display: flex;
474
+ flex-wrap: wrap;
475
+ gap: var(--ol-gap);
476
+ box-sizing: border-box;
477
+ margin: 0;
478
+ padding: 2px 4px;
479
+ border: 1px solid var(--ol-border);
480
+ border-bottom: 0;
481
+ border-radius: var(--ol-radius) var(--ol-radius) 0 0;
482
+ background: var(--ol-surface);
483
+ position: relative;
484
+ z-index: var(--ol-z);
485
+ }
486
+ .ol-editor .ol-menubar + .ol-toolbar {
487
+ border-radius: 0;
488
+ }
489
+ .ol-editor .ol-menu-trigger {
490
+ box-sizing: border-box;
491
+ margin: 0;
492
+ padding: 4px 8px;
493
+ border: 1px solid transparent;
494
+ border-radius: var(--ol-radius);
495
+ background: transparent;
496
+ color: var(--ol-text);
497
+ font: inherit;
498
+ font-family: var(--ol-font);
499
+ font-size: var(--ol-font-size);
500
+ cursor: pointer;
501
+ appearance: none;
502
+ }
503
+ .ol-editor .ol-menu-trigger:hover,
504
+ .ol-editor .ol-menu-trigger[aria-expanded="true"] {
505
+ background: var(--ol-surface-hover);
506
+ }
507
+ .ol-editor .ol-menu-trigger:focus-visible {
508
+ outline: var(--ol-focus-width) solid var(--ol-focus);
509
+ outline-offset: var(--ol-focus-offset);
510
+ }
511
+ .ol-editor .ol-menu {
512
+ position: absolute;
513
+ z-index: calc(var(--ol-z) + 20);
514
+ min-width: 12rem;
515
+ margin: 0;
516
+ padding: 4px;
517
+ border: 1px solid var(--ol-border);
518
+ border-radius: var(--ol-radius);
519
+ background: var(--ol-surface);
520
+ box-shadow: 0 8px 24px rgb(0 0 0 / 12%);
521
+ }
522
+ .ol-editor .ol-menu-item {
523
+ display: block;
524
+ box-sizing: border-box;
525
+ width: 100%;
526
+ margin: 0;
527
+ padding: 6px 10px;
528
+ border: 0;
529
+ border-radius: var(--ol-radius);
530
+ background: transparent;
531
+ color: var(--ol-text);
532
+ font: inherit;
533
+ font-family: var(--ol-font);
534
+ font-size: var(--ol-font-size);
535
+ text-align: start;
536
+ cursor: pointer;
537
+ appearance: none;
538
+ }
539
+ .ol-editor .ol-menu-item:hover,
540
+ .ol-editor .ol-menu-item:focus-visible {
541
+ background: var(--ol-surface-hover);
542
+ outline: none;
543
+ }
544
+ .ol-editor .ol-menu-item[aria-disabled="true"] {
545
+ opacity: 0.4;
546
+ cursor: default;
547
+ }
548
+ .ol-editor .ol-menu-sep {
549
+ height: 1px;
550
+ margin: 4px 0;
551
+ background: var(--ol-border);
552
+ }
553
+ .ol-editor .ol-toolbar.ol-floating {
554
+ position: absolute;
555
+ flex-wrap: nowrap;
556
+ border: 1px solid var(--ol-border);
557
+ border-radius: var(--ol-radius);
558
+ box-shadow: 0 8px 24px rgb(0 0 0 / 12%);
559
+ z-index: calc(var(--ol-z) + 10);
560
+ }
561
+ .ol-editor .ol-toolbar.ol-toolbar--overflow {
562
+ flex-wrap: nowrap;
563
+ overflow: hidden;
564
+ }
565
+ .ol-editor.ol-visual-aids .ol-empty-block {
566
+ outline: 1px dashed var(--ol-border);
567
+ outline-offset: 2px;
568
+ min-height: 1.2em;
569
+ }
570
+ .ol-editor.ol-visual-aids .ol-nbsp {
571
+ background: color-mix(in srgb, var(--ol-accent) 25%, transparent);
572
+ box-shadow: inset 0 -1px 0 var(--ol-accent);
573
+ }
574
+ .ol-editor.ol-visual-aids .ol-hidden-structure {
575
+ outline: 1px dotted var(--ol-accent);
576
+ outline-offset: 2px;
577
+ }
578
+ .ol-editor .ol-noneditable {
579
+ cursor: default;
580
+ background: repeating-linear-gradient(
581
+ -45deg,
582
+ transparent,
583
+ transparent 6px,
584
+ color-mix(in srgb, var(--ol-border) 35%, transparent) 6px,
585
+ color-mix(in srgb, var(--ol-border) 35%, transparent) 7px
586
+ );
587
+ }
588
+ .ol-editor.ol-fullscreen {
589
+ position: fixed;
590
+ inset: 0;
591
+ z-index: 2147483000;
592
+ margin: 0;
593
+ border-radius: 0;
594
+ background: var(--ol-surface);
595
+ display: flex;
596
+ flex-direction: column;
597
+ }
598
+ .ol-editor.ol-fullscreen .ol-content,
599
+ .ol-editor.ol-fullscreen .ol-source {
600
+ flex: 1 1 auto;
601
+ min-height: 0;
602
+ overflow: auto;
603
+ }
604
+ .ol-editor.ol-autoresize .ol-content .ProseMirror {
605
+ overflow: hidden;
606
+ }
607
+ .ol-editor.ol-inline:not(.ol-inline-active) .ol-toolbar,
608
+ .ol-editor.ol-inline:not(.ol-inline-active) .ol-menubar {
609
+ position: absolute;
610
+ width: 1px;
611
+ height: 1px;
612
+ overflow: hidden;
613
+ clip: rect(0 0 0 0);
614
+ clip-path: inset(50%);
615
+ }
616
+ .ol-editor.ol-inline .ol-content {
617
+ border-radius: var(--ol-radius);
618
+ }
619
+ .ol-help-table {
620
+ width: 100%;
621
+ border-collapse: collapse;
622
+ font: inherit;
623
+ }
624
+ .ol-help-table th,
625
+ .ol-help-table td {
626
+ padding: 4px 8px;
627
+ text-align: start;
628
+ vertical-align: top;
629
+ border-bottom: 1px solid var(--openleaf-color-border, #d1d9e0);
630
+ }
631
+ .ol-help-table th {
632
+ font-weight: 600;
633
+ white-space: nowrap;
634
+ }
635
+
420
636
  /* Coarse pointers get a larger target. WCAG 2.2 SC 2.5.8 asks for 24x24 CSS px
421
637
  minimum; 32 clears that on a mouse and 40 is comfortable on a thumb. */
422
638
  @media (pointer: coarse) {
@@ -1 +1 @@
1
- {"version":3,"file":"styles.js","sourceRoot":"","sources":["../src/styles.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"styles.js","sourceRoot":"","sources":["../src/styles.ts"],"names":[],"mappings":"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"}
package/dist/toolbar.d.ts CHANGED
@@ -28,6 +28,7 @@
28
28
  * is blind-Tabbing through the rest of the host's form. `Alt+F10` enters,
29
29
  * Escape leaves, matching TinyMCE and CKEditor 5 so muscle memory transfers.
30
30
  */
31
+ import { type FormatSpec } from '@openleaf-editor/core';
31
32
  import type { EditorState, Transaction } from 'prosemirror-state';
32
33
  import type { EditorView } from 'prosemirror-view';
33
34
  export interface ToolbarOptions {
@@ -35,11 +36,30 @@ export interface ToolbarOptions {
35
36
  label?: string;
36
37
  /** Space-separated item ids, `|` for a separator. */
37
38
  layout?: string;
39
+ /**
40
+ * Collapse groups that do not fit into a More menu. Off by default: wrapping
41
+ * keeps every control visible, which is the safer accessibility default.
42
+ */
43
+ overflow?: boolean;
44
+ /** Extra block formats, typically classes from the host's content CSS. */
45
+ formats?: readonly FormatSpec[];
46
+ /**
47
+ * Language for this toolbar's labels, independent of every other editor's.
48
+ * Absent means the document-wide locale.
49
+ */
50
+ locale?: string | null;
38
51
  }
39
52
  export declare class Toolbar {
40
53
  #private;
41
54
  readonly el: HTMLDivElement;
42
55
  constructor(host: HTMLElement, doc: Document, options?: ToolbarOptions);
56
+ /**
57
+ * Change this toolbar's language and rebuild its labels.
58
+ *
59
+ * Per toolbar rather than per document, so one editor switching language does
60
+ * not relabel every other editor on the page.
61
+ */
62
+ setLocale(next: string | null): void;
43
63
  /** Attach to a view and build the controls. */
44
64
  mount(view: EditorView): void;
45
65
  destroy(): void;
@@ -1 +1 @@
1
- {"version":3,"file":"toolbar.d.ts","sourceRoot":"","sources":["../src/toolbar.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AAGH,OAAO,KAAK,EAAE,WAAW,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAA;AACjE,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAA;AAUlD,MAAM,WAAW,cAAc;IAC7B,gDAAgD;IAChD,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,qDAAqD;IACrD,MAAM,CAAC,EAAE,MAAM,CAAA;CAChB;AAiDD,qBAAa,OAAO;;IAClB,QAAQ,CAAC,EAAE,EAAE,cAAc,CAAA;gBAef,IAAI,EAAE,WAAW,EAAE,GAAG,EAAE,QAAQ,EAAE,OAAO,GAAE,cAAmB;IA8B1E,+CAA+C;IAC/C,KAAK,CAAC,IAAI,EAAE,UAAU,GAAG,IAAI;IAM7B,OAAO,IAAI,IAAI;IAQf,2DAA2D;IAC3D,IAAI,UAAU,IAAI,cAAc,CAE/B;IAwPD;;;;;;;;;OASG;IACH,MAAM,CAAC,KAAK,EAAE,WAAW,EAAE,EAAE,CAAC,EAAE,WAAW,GAAG,IAAI;IAmDlD;;;;;;;OAOG;IACH,YAAY,CAAC,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE;QAAE,MAAM,CAAC,EAAE,OAAO,CAAC;QAAC,OAAO,CAAC,EAAE,OAAO,CAAA;KAAE,GAAG,IAAI;IA8F9E,iEAAiE;IACjE,YAAY,IAAI,IAAI;IAYpB,mEAAmE;IACnE,oBAAoB,IAAI,IAAI;CAG7B"}
1
+ {"version":3,"file":"toolbar.d.ts","sourceRoot":"","sources":["../src/toolbar.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AAEH,OAAO,EASL,KAAK,UAAU,EAChB,MAAM,uBAAuB,CAAA;AAC9B,OAAO,KAAK,EAAE,WAAW,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAA;AACjE,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAA;AAalD,MAAM,WAAW,cAAc;IAC7B,gDAAgD;IAChD,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,qDAAqD;IACrD,MAAM,CAAC,EAAE,MAAM,CAAA;IACf;;;OAGG;IACH,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB,0EAA0E;IAC1E,OAAO,CAAC,EAAE,SAAS,UAAU,EAAE,CAAA;IAC/B;;;OAGG;IACH,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;CACvB;AAuDD,qBAAa,OAAO;;IAClB,QAAQ,CAAC,EAAE,EAAE,cAAc,CAAA;gBAsBf,IAAI,EAAE,WAAW,EAAE,GAAG,EAAE,QAAQ,EAAE,OAAO,GAAE,cAAmB;IAqC1E;;;;;OAKG;IACH,SAAS,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,GAAG,IAAI;IAOpC,+CAA+C;IAC/C,KAAK,CAAC,IAAI,EAAE,UAAU,GAAG,IAAI;IAS7B,OAAO,IAAI,IAAI;IA+Bf,2DAA2D;IAC3D,IAAI,UAAU,IAAI,cAAc,CAE/B;IAkUD;;;;;;;;;OASG;IACH,MAAM,CAAC,KAAK,EAAE,WAAW,EAAE,EAAE,CAAC,EAAE,WAAW,GAAG,IAAI;IAkFlD;;;;;;;OAOG;IACH,YAAY,CAAC,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE;QAAE,MAAM,CAAC,EAAE,OAAO,CAAC;QAAC,OAAO,CAAC,EAAE,OAAO,CAAA;KAAE,GAAG,IAAI;IA8F9E,iEAAiE;IACjE,YAAY,IAAI,IAAI;IAYpB,mEAAmE;IACnE,oBAAoB,IAAI,IAAI;CAG7B"}
package/dist/toolbar.js CHANGED
@@ -28,8 +28,10 @@
28
28
  * is blind-Tabbing through the rest of the host's form. `Alt+F10` enters,
29
29
  * Escape leaves, matching TinyMCE and CKEditor 5 so muscle memory transfers.
30
30
  */
31
- import { activeHeadingLevel, shortcutFor, toggleHeading, setParagraph } from '@openleaf-editor/core';
31
+ import { activeBlockClass, activeHeadingLevel, formatParts, setBlockClass, setHeading, setParagraph, shortcutFor, toggleHeading, } from '@openleaf-editor/core';
32
32
  import { ensureSprite, iconElement } from './icons.js';
33
+ import { t, onLocaleChange, withLocale } from './i18n.js';
34
+ import { ToolbarOverflow } from './overflow.js';
33
35
  import { DEFAULT_LAYOUT, getToolbarItem, onRegistryChange, } from './registry.js';
34
36
  import { ensureStyles } from './styles.js';
35
37
  const BLOCK_TYPE_ID = 'blockType';
@@ -69,11 +71,18 @@ export class Toolbar {
69
71
  #host;
70
72
  #view = null;
71
73
  #controls = new Map();
74
+ #customs = [];
72
75
  #select = null;
73
76
  #live;
74
77
  #liveTimer;
75
78
  #layout;
79
+ #label;
80
+ #formats;
81
+ #locale;
82
+ #wantsOverflow;
83
+ #overflow = null;
76
84
  #unsubscribe;
85
+ #unlocale;
77
86
  /** Focusable buttons in DOM order; the roving tabindex walks this. */
78
87
  #focusables = [];
79
88
  #rovingIndex = 0;
@@ -81,12 +90,16 @@ export class Toolbar {
81
90
  this.#host = host;
82
91
  this.#doc = doc;
83
92
  this.#layout = options.layout ?? DEFAULT_LAYOUT;
93
+ this.#label = options.label ?? 'Formatting';
94
+ this.#formats = options.formats ?? [];
95
+ this.#locale = options.locale ?? null;
96
+ this.#wantsOverflow = options.overflow === true;
84
97
  ensureStyles(doc);
85
98
  ensureSprite(doc);
86
99
  this.el = doc.createElement('div');
87
100
  this.el.className = 'ol-toolbar';
88
101
  this.el.setAttribute('role', 'toolbar');
89
- this.el.setAttribute('aria-label', options.label ?? 'Formatting');
102
+ this.el.setAttribute('aria-label', t(this.#label));
90
103
  this.#live = doc.createElement('div');
91
104
  this.#live.className = 'ol-live';
92
105
  // Polite and atomic: an assertive region would interrupt the author
@@ -101,20 +114,63 @@ export class Toolbar {
101
114
  this.#unsubscribe = onRegistryChange(() => {
102
115
  this.#rerenderPreservingState();
103
116
  });
117
+ this.#unlocale = onLocaleChange(() => {
118
+ this.#rerenderPreservingState();
119
+ });
120
+ }
121
+ /**
122
+ * Change this toolbar's language and rebuild its labels.
123
+ *
124
+ * Per toolbar rather than per document, so one editor switching language does
125
+ * not relabel every other editor on the page.
126
+ */
127
+ setLocale(next) {
128
+ const value = next ?? null;
129
+ if (value === this.#locale)
130
+ return;
131
+ this.#locale = value;
132
+ if (this.#view)
133
+ this.#rerenderPreservingState();
104
134
  }
105
135
  /** Attach to a view and build the controls. */
106
136
  mount(view) {
107
137
  this.#view = view;
108
138
  this.#render();
139
+ if (this.#wantsOverflow && !this.#overflow) {
140
+ this.#overflow = new ToolbarOverflow(this.el, this.#host, this.#doc);
141
+ }
109
142
  this.update(view.state);
110
143
  }
111
144
  destroy() {
112
145
  this.#unsubscribe?.();
146
+ this.#unlocale?.();
147
+ this.#overflow?.destroy();
113
148
  clearTimeout(this.#liveTimer);
114
149
  this.el.removeEventListener('keydown', this.#onKeydown);
150
+ this.#destroyCustoms();
115
151
  this.#controls.clear();
116
152
  this.#view = null;
117
153
  }
154
+ /**
155
+ * Tear down `custom` controls.
156
+ *
157
+ * Called on destroy AND before every re-render. A custom control may have put
158
+ * a popover in the top layer, which is outside this element and therefore
159
+ * survives `replaceChildren` -- so without this, a registry change while a
160
+ * colour picker is open leaves an orphaned popover on the page with no trigger
161
+ * attached to it.
162
+ */
163
+ #destroyCustoms() {
164
+ for (const { id, control } of this.#customs) {
165
+ try {
166
+ control.destroy?.();
167
+ }
168
+ catch (error) {
169
+ console.error(`@openleaf-editor/ui: toolbar item "${id}" threw while being destroyed`, error);
170
+ }
171
+ }
172
+ this.#customs = [];
173
+ }
118
174
  /** The live region element, which the host mounts once. */
119
175
  get liveRegion() {
120
176
  return this.#live;
@@ -122,7 +178,15 @@ export class Toolbar {
122
178
  /* -------------------------------------------------------------- *
123
179
  * Rendering
124
180
  * -------------------------------------------------------------- */
181
+ /**
182
+ * Labels are produced inside this toolbar's own locale scope, so two editors
183
+ * with different `lang` values on one page do not overwrite each other.
184
+ */
125
185
  #render() {
186
+ withLocale(this.#locale, () => this.#renderScoped());
187
+ }
188
+ #renderScoped() {
189
+ this.#destroyCustoms();
126
190
  this.el.replaceChildren();
127
191
  this.#controls.clear();
128
192
  this.#select = null;
@@ -146,10 +210,15 @@ export class Toolbar {
146
210
  console.warn(`@openleaf-editor/ui: no toolbar item registered for "${token}"`);
147
211
  continue;
148
212
  }
149
- // Only `button` is implemented. The `type` discriminant exists so that
150
- // adding variants later is not a breaking change to a published config
151
- // shape -- but a declared-and-inert variant is worse than an absent one,
152
- // because the author sees a plausible button and no signal that the
213
+ if (spec.type === 'custom') {
214
+ const el = this.#buildCustom(spec);
215
+ if (el)
216
+ group.appendChild(el);
217
+ continue;
218
+ }
219
+ // `select` is still unimplemented -- the block-type control is special-cased
220
+ // by id, not by type. A declared-and-inert variant is worse than an absent
221
+ // one, because the author sees a plausible button and no signal that the
153
222
  // control they asked for was not built.
154
223
  if (spec.type && spec.type !== 'button') {
155
224
  console.warn(`@openleaf-editor/ui: toolbar item "${spec.id}" declares type "${spec.type}", ` +
@@ -159,7 +228,9 @@ export class Toolbar {
159
228
  }
160
229
  if (group.childElementCount > 0)
161
230
  this.el.appendChild(group);
231
+ this.el.setAttribute('aria-label', t(this.#label));
162
232
  this.#refreshFocusables();
233
+ this.#overflow?.reattach();
163
234
  }
164
235
  /**
165
236
  * Rebuild after a registry change without dropping host-pushed state.
@@ -223,9 +294,10 @@ export class Toolbar {
223
294
  button.dataset['olId'] = spec.id;
224
295
  // The accessible name stays constant across states. Baking "pressed" into
225
296
  // it would double up with what the platform already announces.
226
- button.setAttribute('aria-label', spec.label);
297
+ button.setAttribute('aria-label', t(spec.label));
227
298
  const shortcut = spec.shortcut ? shortcutFor(spec.shortcut) : null;
228
- button.title = shortcut ? `${spec.label} (${shortcut})` : spec.label;
299
+ const label = t(spec.label);
300
+ button.title = shortcut ? `${label} (${shortcut})` : label;
229
301
  if ((spec.kind ?? 'action') === 'toggle') {
230
302
  button.setAttribute('aria-pressed', 'false');
231
303
  }
@@ -239,6 +311,37 @@ export class Toolbar {
239
311
  this.#controls.set(spec.id, { spec, el: button, active: null, enabled: null });
240
312
  return button;
241
313
  }
314
+ /**
315
+ * Build a `custom` control.
316
+ *
317
+ * Everything the item does is third-party code running inside the editor's
318
+ * render path, so a throw here must cost that one control and nothing else: a
319
+ * colour picker that fails to build must not take the rest of the toolbar --
320
+ * and with it Bold, Undo and Save -- down with it.
321
+ */
322
+ #buildCustom(spec) {
323
+ const view = this.#view;
324
+ if (!spec.render) {
325
+ console.warn(`@openleaf-editor/ui: toolbar item "${spec.id}" declares type "custom" but has no ` +
326
+ 'render function, so there is nothing to build.');
327
+ return null;
328
+ }
329
+ // No view yet means this is a pre-mount re-render triggered by a late
330
+ // registration. mount() renders again with a view, so the control appears
331
+ // then rather than being built against nothing.
332
+ if (!view)
333
+ return null;
334
+ try {
335
+ const control = spec.render({ view, host: this.#host });
336
+ this.#customs.push({ id: spec.id, control });
337
+ return control.el;
338
+ }
339
+ catch (error) {
340
+ console.error(`@openleaf-editor/ui: toolbar item "${spec.id}" threw while rendering. ` +
341
+ 'It has been left out; the rest of the toolbar is unaffected.', error);
342
+ return null;
343
+ }
344
+ }
242
345
  /**
243
346
  * The block-type control.
244
347
  *
@@ -250,16 +353,16 @@ export class Toolbar {
250
353
  #buildBlockTypeSelect() {
251
354
  const select = this.#doc.createElement('select');
252
355
  select.className = 'ol-select';
253
- select.setAttribute('aria-label', 'Paragraph style');
356
+ select.setAttribute('aria-label', t('Paragraph style'));
254
357
  select.dataset['olId'] = BLOCK_TYPE_ID;
255
358
  const options = [
256
- ['p', 'Paragraph'],
257
- ['1', 'Heading 1'],
258
- ['2', 'Heading 2'],
259
- ['3', 'Heading 3'],
260
- ['4', 'Heading 4'],
261
- ['5', 'Heading 5'],
262
- ['6', 'Heading 6'],
359
+ ['p', t('Paragraph')],
360
+ ['1', t('Heading 1')],
361
+ ['2', t('Heading 2')],
362
+ ['3', t('Heading 3')],
363
+ ['4', t('Heading 4')],
364
+ ['5', t('Heading 5')],
365
+ ['6', t('Heading 6')],
263
366
  ];
264
367
  for (const [value, label] of options) {
265
368
  const option = this.#doc.createElement('option');
@@ -267,6 +370,12 @@ export class Toolbar {
267
370
  option.textContent = label;
268
371
  select.appendChild(option);
269
372
  }
373
+ for (const format of this.#formats) {
374
+ const option = this.#doc.createElement('option');
375
+ option.value = `format:${format.token}`;
376
+ option.textContent = t(format.label);
377
+ select.appendChild(option);
378
+ }
270
379
  select.addEventListener('mousedown', (event) => event.stopPropagation());
271
380
  /**
272
381
  * Whether the pending change came from a pointer rather than the keyboard.
@@ -298,8 +407,26 @@ export class Toolbar {
298
407
  if (!view)
299
408
  return;
300
409
  const value = select.value;
301
- const command = value === 'p' ? setParagraph : toggleHeading(Number(value));
302
- command(view.state, view.dispatch, view);
410
+ if (value.startsWith('format:')) {
411
+ const { element, className } = formatParts(value.slice('format:'.length));
412
+ // Element first: changing the block type replaces the node, so a class
413
+ // written before that would go with the node it was written on.
414
+ // `setHeading` rather than `toggleHeading` -- picking "Section" twice
415
+ // must not turn the heading back into a paragraph.
416
+ if (element === 'p')
417
+ setParagraph(view.state, view.dispatch, view);
418
+ else if (element !== null && /^h[1-6]$/.test(element)) {
419
+ setHeading(Number(element.slice(1)))(view.state, view.dispatch, view);
420
+ }
421
+ // Null clears, which is what a token naming only an element means: the
422
+ // author picked "Section", not "Section, keeping whatever class was on
423
+ // the paragraph before".
424
+ setBlockClass(className)(view.state, view.dispatch, view);
425
+ }
426
+ else {
427
+ const command = value === 'p' ? setParagraph : toggleHeading(Number(value));
428
+ command(view.state, view.dispatch, view);
429
+ }
303
430
  // Return the caret to the content only when the author committed the
304
431
  // choice by pointer. Keyboard users keep focus and leave with Tab or
305
432
  // Escape.
@@ -352,6 +479,9 @@ export class Toolbar {
352
479
  * `tr` is passed so announcements can be gated on a real formatting change.
353
480
  */
354
481
  update(state, tr) {
482
+ withLocale(this.#locale, () => this.#updateScoped(state, tr));
483
+ }
484
+ #updateScoped(state, tr) {
355
485
  // Announce only on a discrete formatting transition, never on cursor
356
486
  // movement through already-formatted text. That gate is the whole
357
487
  // difference between a useful announcement and a chatty one.
@@ -384,11 +514,41 @@ export class Toolbar {
384
514
  }
385
515
  }
386
516
  }
517
+ for (const { id, control } of this.#customs) {
518
+ // Readonly is the toolbar's business, not each control's: reflecting it
519
+ // here means a custom control gets the same disabled treatment as a button
520
+ // without every plugin author having to remember the attribute exists.
521
+ const trigger = control.el.querySelector('button.ol-btn');
522
+ trigger?.setAttribute('aria-disabled', this.#host.hasAttribute('readonly') ? 'true' : 'false');
523
+ if (!control.update)
524
+ continue;
525
+ guarded(id, 'update', () => {
526
+ control.update?.(state);
527
+ return true;
528
+ });
529
+ }
387
530
  if (this.#select) {
531
+ const formatClass = activeBlockClass(state);
388
532
  const level = activeHeadingLevel(state);
389
- const value = level === null ? 'p' : String(level);
390
- if (this.#select.value !== value)
391
- this.#select.value = value;
533
+ const activeElement = level === null ? 'p' : `h${level}`;
534
+ // Both halves have to agree, or `p.lead` would look active on an
535
+ // `<h2 class="lead">` and picking it again would appear to do nothing.
536
+ const matching = this.#formats.find((format) => {
537
+ const { element, className } = formatParts(format.token);
538
+ if (className !== formatClass)
539
+ return false;
540
+ return element === null || element === activeElement;
541
+ });
542
+ if (matching) {
543
+ const next = `format:${matching.token}`;
544
+ if (this.#select.value !== next)
545
+ this.#select.value = next;
546
+ }
547
+ else {
548
+ const value = level === null ? 'p' : String(level);
549
+ if (this.#select.value !== value)
550
+ this.#select.value = value;
551
+ }
392
552
  }
393
553
  if (transitions.length > 0)
394
554
  this.#announce(transitions.join(', '));