@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.
- package/dist/content-css.d.ts +13 -0
- package/dist/content-css.d.ts.map +1 -0
- package/dist/content-css.js +57 -0
- package/dist/content-css.js.map +1 -0
- package/dist/dialog.d.ts +87 -2
- package/dist/dialog.d.ts.map +1 -1
- package/dist/dialog.js +358 -68
- package/dist/dialog.js.map +1 -1
- package/dist/floating.d.ts +25 -0
- package/dist/floating.d.ts.map +1 -0
- package/dist/floating.js +82 -0
- package/dist/floating.js.map +1 -0
- package/dist/help.d.ts +9 -0
- package/dist/help.d.ts.map +1 -0
- package/dist/help.js +73 -0
- package/dist/help.js.map +1 -0
- package/dist/i18n.d.ts +31 -0
- package/dist/i18n.d.ts.map +1 -0
- package/dist/i18n.js +89 -0
- package/dist/i18n.js.map +1 -0
- package/dist/icons.d.ts +1 -1
- package/dist/icons.d.ts.map +1 -1
- package/dist/icons.js +17 -1
- package/dist/icons.js.map +1 -1
- package/dist/index.d.ts +10 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +10 -3
- package/dist/index.js.map +1 -1
- package/dist/items.d.ts +2 -0
- package/dist/items.d.ts.map +1 -1
- package/dist/items.js +81 -4
- package/dist/items.js.map +1 -1
- package/dist/menu.d.ts +56 -0
- package/dist/menu.d.ts.map +1 -0
- package/dist/menu.js +360 -0
- package/dist/menu.js.map +1 -0
- package/dist/openleaf.css +216 -0
- package/dist/overflow.d.ts +17 -0
- package/dist/overflow.d.ts.map +1 -0
- package/dist/overflow.js +138 -0
- package/dist/overflow.js.map +1 -0
- package/dist/pickers.d.ts +43 -0
- package/dist/pickers.d.ts.map +1 -0
- package/dist/pickers.js +52 -0
- package/dist/pickers.js.map +1 -0
- package/dist/registry.d.ts +49 -8
- package/dist/registry.d.ts.map +1 -1
- package/dist/registry.js +14 -0
- package/dist/registry.js.map +1 -1
- package/dist/styles.d.ts +1 -1
- package/dist/styles.d.ts.map +1 -1
- package/dist/styles.js +216 -0
- package/dist/styles.js.map +1 -1
- package/dist/toolbar.d.ts +20 -0
- package/dist/toolbar.d.ts.map +1 -1
- package/dist/toolbar.js +181 -21
- package/dist/toolbar.js.map +1 -1
- package/dist/upload.d.ts +95 -0
- package/dist/upload.d.ts.map +1 -0
- package/dist/upload.js +104 -0
- package/dist/upload.js.map +1 -0
- 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) {
|
package/dist/styles.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"styles.js","sourceRoot":"","sources":["../src/styles.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAoCG;AAEH;;;;;;;GAOG;AACH,MAAM,WAAW,GAAG;;;;;;;;;CASnB,CAAA;AAED,MAAM,CAAC,MAAM,GAAG,GAAG
|
|
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;
|
package/dist/toolbar.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"toolbar.d.ts","sourceRoot":"","sources":["../src/toolbar.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;
|
|
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,
|
|
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',
|
|
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
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
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
|
-
|
|
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
|
-
|
|
302
|
-
|
|
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
|
|
390
|
-
|
|
391
|
-
|
|
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(', '));
|