@lalternative/editor 0.6.1 → 0.8.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.
- package/dist/editor.css +337 -39
- package/dist/index.d.ts +168 -2
- package/dist/index.js +387 -22
- package/dist/index.js.map +1 -1
- package/package.json +12 -4
package/dist/editor.css
CHANGED
|
@@ -29,30 +29,56 @@
|
|
|
29
29
|
}
|
|
30
30
|
}
|
|
31
31
|
|
|
32
|
-
/*
|
|
33
|
-
|
|
32
|
+
/* A pill above the keyboard by default, floating beside the selection only
|
|
33
|
+
where there is a pointer and room for it.
|
|
34
34
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
floating bar
|
|
35
|
+
Near the selection is the worst place for it on a phone: the finger that
|
|
36
|
+
made the selection covers it, and iOS draws its own callout in the same
|
|
37
|
+
spot. Sitting above the keyboard puts it in thumb reach instead, and unlike
|
|
38
|
+
a floating bar it cannot be pushed off-screen by a selection near an edge.
|
|
39
|
+
|
|
40
|
+
Shaped as a pill rather than a full-width bar, which is the form the
|
|
41
|
+
platform already uses for a selection: it reads as something hovering over
|
|
42
|
+
the document rather than as a piece of the page's chrome, and its rounded
|
|
43
|
+
ends keep the text either side of it visible.
|
|
44
|
+
|
|
45
|
+
Lifted by whatever covers the bottom of the layout viewport: the on-screen
|
|
46
|
+
keyboard hides the bar without shrinking that viewport, so `bottom: 0` alone
|
|
47
|
+
parks it under the keys. The component measures the strip and sets the
|
|
48
|
+
variable; it is 0 whenever nothing covers anything. */
|
|
39
49
|
.lalt-toolbar {
|
|
40
50
|
position: fixed;
|
|
41
|
-
left: 0;
|
|
42
|
-
right: 0;
|
|
43
|
-
bottom: 0;
|
|
51
|
+
left: 0.5rem;
|
|
52
|
+
right: 0.5rem;
|
|
53
|
+
bottom: calc(var(--lalt-keyboard-inset, 0px) + 0.5rem);
|
|
44
54
|
z-index: 100;
|
|
45
55
|
display: flex;
|
|
46
56
|
align-items: center;
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
57
|
+
justify-content: center;
|
|
58
|
+
gap: 0.125rem;
|
|
59
|
+
width: fit-content;
|
|
60
|
+
max-width: calc(100% - 1rem);
|
|
61
|
+
margin: 0 auto;
|
|
62
|
+
margin-bottom: env(safe-area-inset-bottom);
|
|
63
|
+
padding: 0.25rem;
|
|
64
|
+
border-radius: 999px;
|
|
50
65
|
background: var(--lalt-editor-surface, #ffffff);
|
|
51
|
-
border
|
|
52
|
-
box-shadow:
|
|
66
|
+
border: 1px solid var(--lalt-editor-border, rgb(0 0 0 / 0.1));
|
|
67
|
+
box-shadow:
|
|
68
|
+
0 6px 20px rgb(0 0 0 / 0.18),
|
|
69
|
+
0 1px 3px rgb(0 0 0 / 0.1);
|
|
53
70
|
color: var(--lalt-editor-text, #111827);
|
|
54
71
|
user-select: none;
|
|
72
|
+
/* A long row scrolls rather than stretching the pill past the screen. This
|
|
73
|
+
clips vertically too — `overflow-y: visible` beside it is ignored by the
|
|
74
|
+
spec — so the format menu is positioned against the viewport instead of
|
|
75
|
+
inside this box, and escapes the clip that way. */
|
|
55
76
|
overflow-x: auto;
|
|
77
|
+
scrollbar-width: none;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
.lalt-toolbar::-webkit-scrollbar {
|
|
81
|
+
display: none;
|
|
56
82
|
}
|
|
57
83
|
|
|
58
84
|
@media (hover: hover) and (pointer: fine) {
|
|
@@ -61,11 +87,9 @@
|
|
|
61
87
|
top: var(--lalt-toolbar-top, 0);
|
|
62
88
|
right: auto;
|
|
63
89
|
bottom: auto;
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
padding
|
|
67
|
-
border: 1px solid var(--lalt-editor-border, rgb(0 0 0 / 0.1));
|
|
68
|
-
border-radius: 0.5rem;
|
|
90
|
+
max-width: none;
|
|
91
|
+
margin: 0;
|
|
92
|
+
padding: 0.1875rem;
|
|
69
93
|
/* Deep enough to read as floating over the text rather than sitting in
|
|
70
94
|
it — the bar appears mid-paragraph, with prose on every side. */
|
|
71
95
|
box-shadow:
|
|
@@ -75,35 +99,50 @@
|
|
|
75
99
|
}
|
|
76
100
|
}
|
|
77
101
|
|
|
102
|
+
/* Transparent by default: inside a pill the buttons are read as a row by the
|
|
103
|
+
separators between them, and a filled rectangle each would turn the bar into
|
|
104
|
+
a strip of chips. The fill is what pressing one looks like. */
|
|
78
105
|
.lalt-toolbar__button {
|
|
79
106
|
display: inline-flex;
|
|
80
107
|
align-items: center;
|
|
81
108
|
justify-content: center;
|
|
82
109
|
gap: 0.375rem;
|
|
83
|
-
/*
|
|
84
|
-
|
|
85
|
-
min-height: 2.
|
|
86
|
-
padding: 0.5rem 0.
|
|
87
|
-
border-radius:
|
|
88
|
-
background:
|
|
110
|
+
/* 40px tall inside a 44px pill: still the target a finger needs, once the
|
|
111
|
+
bar's own padding is counted. */
|
|
112
|
+
min-height: 2.5rem;
|
|
113
|
+
padding: 0.5rem 0.875rem;
|
|
114
|
+
border-radius: 999px;
|
|
115
|
+
background: none;
|
|
89
116
|
border: none;
|
|
90
117
|
font: inherit;
|
|
91
|
-
font-size: 0.
|
|
118
|
+
font-size: 0.9375rem;
|
|
92
119
|
font-weight: 500;
|
|
93
120
|
white-space: nowrap;
|
|
94
121
|
cursor: pointer;
|
|
95
122
|
color: inherit;
|
|
96
123
|
}
|
|
97
124
|
|
|
125
|
+
/* A hairline between actions, the way the platform's own callout separates
|
|
126
|
+
Copy from Look Up — cheaper than a box around each. */
|
|
127
|
+
.lalt-toolbar > * + * > .lalt-toolbar__button,
|
|
128
|
+
.lalt-toolbar > .lalt-toolbar__button + .lalt-toolbar__button {
|
|
129
|
+
box-shadow: inset 1px 0 0 var(--lalt-editor-border, rgb(0 0 0 / 0.1));
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
.lalt-toolbar__button:active {
|
|
133
|
+
background: var(--lalt-editor-active, rgb(0 0 0 / 0.12));
|
|
134
|
+
}
|
|
135
|
+
|
|
98
136
|
@media (hover: hover) and (pointer: fine) {
|
|
99
137
|
.lalt-toolbar__button {
|
|
100
138
|
min-height: 0;
|
|
101
|
-
padding: 0.
|
|
139
|
+
padding: 0.3125rem 0.625rem;
|
|
140
|
+
font-size: 0.875rem;
|
|
102
141
|
}
|
|
103
142
|
}
|
|
104
143
|
|
|
105
144
|
.lalt-toolbar__button:hover {
|
|
106
|
-
background: var(--lalt-editor-
|
|
145
|
+
background: var(--lalt-editor-hover, rgb(0 0 0 / 0.06));
|
|
107
146
|
}
|
|
108
147
|
|
|
109
148
|
/* The format trigger opens a menu; the caret is what says so. */
|
|
@@ -121,19 +160,25 @@
|
|
|
121
160
|
position: relative;
|
|
122
161
|
}
|
|
123
162
|
|
|
124
|
-
/* Opens upward from
|
|
125
|
-
|
|
163
|
+
/* Opens upward from the pill: a menu anchored below it would open off the
|
|
164
|
+
bottom of the screen, or into the keyboard. Card-shaped rather than
|
|
165
|
+
full-bleed, so it reads as belonging to the bar it grew out of. */
|
|
126
166
|
.lalt-toolbar__menu {
|
|
127
167
|
position: fixed;
|
|
128
|
-
left:
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
168
|
+
left: 50%;
|
|
169
|
+
bottom: calc(var(--lalt-keyboard-inset, 0px) + env(safe-area-inset-bottom) + 3.75rem);
|
|
170
|
+
transform: translateX(-50%);
|
|
171
|
+
min-width: 12rem;
|
|
172
|
+
max-width: calc(100vw - 2rem);
|
|
173
|
+
max-height: 50vh;
|
|
132
174
|
overflow-y: auto;
|
|
133
175
|
padding: 0.25rem;
|
|
134
176
|
background: var(--lalt-editor-surface, #ffffff);
|
|
135
|
-
border
|
|
136
|
-
|
|
177
|
+
border: 1px solid var(--lalt-editor-border, rgb(0 0 0 / 0.1));
|
|
178
|
+
border-radius: 0.875rem;
|
|
179
|
+
box-shadow:
|
|
180
|
+
0 8px 24px rgb(0 0 0 / 0.18),
|
|
181
|
+
0 1px 3px rgb(0 0 0 / 0.1);
|
|
137
182
|
color: var(--lalt-editor-text, #111827);
|
|
138
183
|
}
|
|
139
184
|
|
|
@@ -144,9 +189,10 @@
|
|
|
144
189
|
right: 0;
|
|
145
190
|
bottom: auto;
|
|
146
191
|
top: calc(100% + 0.375rem);
|
|
192
|
+
transform: none;
|
|
147
193
|
min-width: 14rem;
|
|
148
|
-
|
|
149
|
-
border-radius: 0.
|
|
194
|
+
max-width: none;
|
|
195
|
+
border-radius: 0.625rem;
|
|
150
196
|
box-shadow: 0 8px 24px rgb(0 0 0 / 0.16);
|
|
151
197
|
}
|
|
152
198
|
}
|
|
@@ -159,7 +205,7 @@
|
|
|
159
205
|
width: 100%;
|
|
160
206
|
min-height: 2.75rem;
|
|
161
207
|
padding: 0.5rem 0.75rem;
|
|
162
|
-
border-radius: 0.
|
|
208
|
+
border-radius: 0.625rem;
|
|
163
209
|
background: none;
|
|
164
210
|
border: none;
|
|
165
211
|
font: inherit;
|
|
@@ -280,3 +326,255 @@
|
|
|
280
326
|
.lalt-suggestion__action:hover {
|
|
281
327
|
background: var(--lalt-editor-hover, rgb(0 0 0 / 0.06));
|
|
282
328
|
}
|
|
329
|
+
|
|
330
|
+
/* The `/` menu, anchored to the caret by the extension and painted here.
|
|
331
|
+
|
|
332
|
+
Fixed to the viewport rather than positioned in the document: the menu is
|
|
333
|
+
appended to the body so an editor inside an `overflow: hidden` pane cannot
|
|
334
|
+
clip it. */
|
|
335
|
+
.lalt-slash-anchor {
|
|
336
|
+
position: fixed;
|
|
337
|
+
z-index: 10000;
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
.lalt-slash {
|
|
341
|
+
min-width: 16rem;
|
|
342
|
+
max-width: min(20rem, calc(100vw - 1.5rem));
|
|
343
|
+
overflow: hidden;
|
|
344
|
+
border: 1px solid var(--lalt-editor-border);
|
|
345
|
+
border-radius: 0.5rem;
|
|
346
|
+
background: var(--lalt-editor-surface);
|
|
347
|
+
color: var(--lalt-editor-text);
|
|
348
|
+
box-shadow: 0 10px 20px rgb(0 0 0 / 0.12);
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
.lalt-slash--empty {
|
|
352
|
+
padding: 0.5rem 0.75rem;
|
|
353
|
+
font-size: 0.75rem;
|
|
354
|
+
opacity: 0.6;
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
.lalt-slash__item {
|
|
358
|
+
display: flex;
|
|
359
|
+
align-items: center;
|
|
360
|
+
gap: 0.625rem;
|
|
361
|
+
width: 100%;
|
|
362
|
+
padding: 0.5rem 0.75rem;
|
|
363
|
+
border: 0;
|
|
364
|
+
background: transparent;
|
|
365
|
+
color: inherit;
|
|
366
|
+
font: inherit;
|
|
367
|
+
font-size: 0.8125rem;
|
|
368
|
+
text-align: left;
|
|
369
|
+
cursor: pointer;
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
/* Room for a thumb where there is no pointer to aim with. */
|
|
373
|
+
@media not (hover: hover), (pointer: coarse) {
|
|
374
|
+
.lalt-slash__item {
|
|
375
|
+
padding: 0.6875rem 0.75rem;
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
.lalt-slash__item--active,
|
|
380
|
+
.lalt-slash__item:hover {
|
|
381
|
+
background: var(--lalt-editor-hover);
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
.lalt-slash__icon {
|
|
385
|
+
display: flex;
|
|
386
|
+
flex-shrink: 0;
|
|
387
|
+
opacity: 0.55;
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
.lalt-slash__text {
|
|
391
|
+
display: flex;
|
|
392
|
+
flex-direction: column;
|
|
393
|
+
min-width: 0;
|
|
394
|
+
flex: 1;
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
.lalt-slash__title {
|
|
398
|
+
font-weight: 500;
|
|
399
|
+
line-height: 1.25;
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
.lalt-slash__description {
|
|
403
|
+
overflow: hidden;
|
|
404
|
+
font-size: 0.6875rem;
|
|
405
|
+
line-height: 1.25;
|
|
406
|
+
text-overflow: ellipsis;
|
|
407
|
+
white-space: nowrap;
|
|
408
|
+
opacity: 0.55;
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
.lalt-slash__shortcut {
|
|
412
|
+
flex-shrink: 0;
|
|
413
|
+
margin-left: 0.5rem;
|
|
414
|
+
padding: 0.125rem 0.375rem;
|
|
415
|
+
border: 1px solid var(--lalt-editor-border);
|
|
416
|
+
border-radius: 0.25rem;
|
|
417
|
+
font-family: ui-monospace, monospace;
|
|
418
|
+
font-size: 0.625rem;
|
|
419
|
+
opacity: 0.6;
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
/* The page a writing surface sits in.
|
|
423
|
+
|
|
424
|
+
Sized in dvh rather than vh: on mobile Safari a vh unit keeps counting the
|
|
425
|
+
browser chrome that scrolls away, so the column runs past the bottom of the
|
|
426
|
+
screen and the last paragraph is unreachable. */
|
|
427
|
+
.lalt-screen {
|
|
428
|
+
display: flex;
|
|
429
|
+
flex-direction: column;
|
|
430
|
+
height: var(--lalt-screen-height, 100dvh);
|
|
431
|
+
min-height: 0;
|
|
432
|
+
color: var(--lalt-editor-text);
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
/* A host mounting the screen inside its own shell — a phone frame, a panel,
|
|
436
|
+
a route that already owns the page height — sets the height to 100% and
|
|
437
|
+
lets its container do the sizing. */
|
|
438
|
+
.lalt-screen--fill {
|
|
439
|
+
--lalt-screen-height: 100%;
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
.lalt-screen__bar {
|
|
443
|
+
position: sticky;
|
|
444
|
+
top: 0;
|
|
445
|
+
z-index: 20;
|
|
446
|
+
display: flex;
|
|
447
|
+
align-items: center;
|
|
448
|
+
gap: 0.5rem;
|
|
449
|
+
padding: 0.5rem 0.75rem;
|
|
450
|
+
border-bottom: 1px solid var(--lalt-editor-border);
|
|
451
|
+
background: var(--lalt-editor-surface);
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
.lalt-screen__status {
|
|
455
|
+
min-width: 0;
|
|
456
|
+
font-size: 0.75rem;
|
|
457
|
+
font-variant-numeric: tabular-nums;
|
|
458
|
+
opacity: 0.7;
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
.lalt-screen__actions {
|
|
462
|
+
display: flex;
|
|
463
|
+
align-items: center;
|
|
464
|
+
gap: 0.375rem;
|
|
465
|
+
margin-left: auto;
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
.lalt-screen__subnav {
|
|
469
|
+
position: sticky;
|
|
470
|
+
top: 0;
|
|
471
|
+
z-index: 15;
|
|
472
|
+
border-bottom: 1px solid var(--lalt-editor-border);
|
|
473
|
+
background: var(--lalt-editor-surface);
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
/* The scroller is here, not on the body: the bar above it has to stay put
|
|
477
|
+
while the prose moves, and a document taller than the screen must not drag
|
|
478
|
+
the whole page under a raised keyboard. */
|
|
479
|
+
.lalt-screen__scroll {
|
|
480
|
+
flex: 1;
|
|
481
|
+
min-height: 0;
|
|
482
|
+
overflow-y: auto;
|
|
483
|
+
-webkit-overflow-scrolling: touch;
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
.lalt-screen__column {
|
|
487
|
+
width: 100%;
|
|
488
|
+
max-width: 48rem;
|
|
489
|
+
margin: 0 auto;
|
|
490
|
+
padding: 1.25rem 1rem;
|
|
491
|
+
/* Clears the on-screen keyboard, plus room to scroll the last line up out
|
|
492
|
+
from under it. Zero wherever nothing covers the viewport. */
|
|
493
|
+
padding-bottom: calc(var(--lalt-keyboard, 0px) + 40vh);
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
/* No border, no box: the page is the document, not a form with a content
|
|
497
|
+
field in it. */
|
|
498
|
+
.lalt-screen__title {
|
|
499
|
+
width: 100%;
|
|
500
|
+
border: 0;
|
|
501
|
+
background: transparent;
|
|
502
|
+
color: inherit;
|
|
503
|
+
font-family: inherit;
|
|
504
|
+
font-size: 1.5rem;
|
|
505
|
+
font-weight: 600;
|
|
506
|
+
line-height: 1.25;
|
|
507
|
+
outline: none;
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
.lalt-screen__title::placeholder {
|
|
511
|
+
opacity: 0.35;
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
.lalt-screen__aside {
|
|
515
|
+
display: flex;
|
|
516
|
+
align-items: center;
|
|
517
|
+
justify-content: flex-end;
|
|
518
|
+
gap: 0.5rem;
|
|
519
|
+
margin-top: 0.5rem;
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
.lalt-screen__footer {
|
|
523
|
+
margin-top: 1.5rem;
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
.lalt-screen__save {
|
|
527
|
+
display: flex;
|
|
528
|
+
flex-shrink: 0;
|
|
529
|
+
align-items: center;
|
|
530
|
+
width: 0.5rem;
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
.lalt-screen__dot {
|
|
534
|
+
width: 0.5rem;
|
|
535
|
+
height: 0.5rem;
|
|
536
|
+
border-radius: 9999px;
|
|
537
|
+
transition: background-color 150ms;
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
.lalt-screen__dot--saving {
|
|
541
|
+
background: #f59e0b;
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
.lalt-screen__dot--saved {
|
|
545
|
+
background: #10b981;
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
.lalt-screen__dot--dirty {
|
|
549
|
+
background: currentColor;
|
|
550
|
+
opacity: 0.3;
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
.lalt-screen__sr {
|
|
554
|
+
position: absolute;
|
|
555
|
+
width: 1px;
|
|
556
|
+
height: 1px;
|
|
557
|
+
margin: -1px;
|
|
558
|
+
padding: 0;
|
|
559
|
+
overflow: hidden;
|
|
560
|
+
border: 0;
|
|
561
|
+
clip-path: inset(50%);
|
|
562
|
+
white-space: nowrap;
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
/* Room to breathe once there is a screen to spend it on. The phone layout
|
|
566
|
+
above is the one that has to work; this is the widening. */
|
|
567
|
+
@media (min-width: 640px) {
|
|
568
|
+
.lalt-screen__bar {
|
|
569
|
+
padding: 0.5rem 1.5rem;
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
.lalt-screen__column {
|
|
573
|
+
padding: 2rem 2rem;
|
|
574
|
+
padding-bottom: calc(var(--lalt-keyboard, 0px) + 30vh);
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
.lalt-screen__title {
|
|
578
|
+
font-size: 1.875rem;
|
|
579
|
+
}
|
|
580
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -2,7 +2,7 @@ import { R as RichDoc } from './diff-uFiP8hD5.js';
|
|
|
2
2
|
export { B as BlockChange, a as BlockShape, C as ChangeKind, b as RichNode, c as blocksFromDoc, d as diffBlocks, e as docFromBlocks, s as shapesFromDoc } from './diff-uFiP8hD5.js';
|
|
3
3
|
import * as react from 'react';
|
|
4
4
|
import { Editor } from '@tiptap/react';
|
|
5
|
-
import { Extension } from '@tiptap/core';
|
|
5
|
+
import { Range, Extension } from '@tiptap/core';
|
|
6
6
|
import { PluginKey } from '@tiptap/pm/state';
|
|
7
7
|
import { DecorationSet } from '@tiptap/pm/view';
|
|
8
8
|
|
|
@@ -76,6 +76,45 @@ interface SelectionInfo {
|
|
|
76
76
|
*/
|
|
77
77
|
declare function useSelectionToolbar(editor: Editor | null): SelectionInfo | null;
|
|
78
78
|
|
|
79
|
+
interface VisualViewportLike {
|
|
80
|
+
width: number;
|
|
81
|
+
height: number;
|
|
82
|
+
offsetTop: number;
|
|
83
|
+
}
|
|
84
|
+
interface ViewportMetrics {
|
|
85
|
+
width: number;
|
|
86
|
+
height: number;
|
|
87
|
+
offsetTop: number;
|
|
88
|
+
/** How much of the layout viewport's bottom is covered, the on-screen
|
|
89
|
+
* keyboard included. Zero on a desktop browser and wherever the API is
|
|
90
|
+
* missing. */
|
|
91
|
+
bottomInset: number;
|
|
92
|
+
}
|
|
93
|
+
interface ReadViewportInput {
|
|
94
|
+
innerWidth: number;
|
|
95
|
+
innerHeight: number;
|
|
96
|
+
visual?: VisualViewportLike | null;
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* readViewport reports the area a fixed element can actually occupy.
|
|
100
|
+
*
|
|
101
|
+
* The distinction the on-screen keyboard turns on: `innerHeight` measures the
|
|
102
|
+
* layout viewport, which the keyboard covers without shrinking, so an element
|
|
103
|
+
* pinned to its bottom edge ends up underneath the keys. Only `visualViewport`
|
|
104
|
+
* narrows, and the difference is the inset a docked toolbar has to clear.
|
|
105
|
+
*/
|
|
106
|
+
declare function readViewport({ innerWidth, innerHeight, visual, }: ReadViewportInput): ViewportMetrics;
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* useViewport tracks the area a fixed element can occupy, keyboard included.
|
|
110
|
+
*
|
|
111
|
+
* The visual viewport's own events are the only ones a raised keyboard fires:
|
|
112
|
+
* `window.resize` does not run on iOS when the keyboard opens, so a component
|
|
113
|
+
* listening to it alone keeps the measurements it took before the keys
|
|
114
|
+
* appeared.
|
|
115
|
+
*/
|
|
116
|
+
declare function useViewport(): ViewportMetrics;
|
|
117
|
+
|
|
79
118
|
interface FormatLabels {
|
|
80
119
|
paragraph: string;
|
|
81
120
|
heading1: string;
|
|
@@ -93,6 +132,133 @@ declare const defaultFormatLabels: FormatLabels;
|
|
|
93
132
|
*/
|
|
94
133
|
declare function blockFormats(editor: Editor | null, labels?: FormatLabels): BlockFormat[];
|
|
95
134
|
|
|
135
|
+
type SaveState = "idle" | "saving" | "saved" | "dirty";
|
|
136
|
+
interface EditorScreenProps {
|
|
137
|
+
/** The writing surface. Mounted by the host, which owns the TipTap instance
|
|
138
|
+
* and whichever extensions it needs. */
|
|
139
|
+
children: React.ReactNode;
|
|
140
|
+
title?: string;
|
|
141
|
+
onTitleChange?: (title: string) => void;
|
|
142
|
+
titlePlaceholder?: string;
|
|
143
|
+
titleLabel?: string;
|
|
144
|
+
saveState?: SaveState;
|
|
145
|
+
saveLabels?: SaveLabels;
|
|
146
|
+
/** Drawn at the far left of the bar; a back control belongs here. */
|
|
147
|
+
lead?: React.ReactNode;
|
|
148
|
+
/** Drawn at the far right. Keep it to what a thumb can reach on a phone —
|
|
149
|
+
* one primary control and an overflow, not a row of five. */
|
|
150
|
+
actions?: React.ReactNode;
|
|
151
|
+
/** Sits between the save dot and the actions: a character count, a badge. */
|
|
152
|
+
status?: React.ReactNode;
|
|
153
|
+
/** Between the title and the writing surface — a dictation control, a hint. */
|
|
154
|
+
aside?: React.ReactNode;
|
|
155
|
+
/** Below the writing surface, inside the scrolling column. */
|
|
156
|
+
footer?: React.ReactNode;
|
|
157
|
+
/** Above the writing surface and below the bar, pinned while the page
|
|
158
|
+
* scrolls: tabs over variants of one document belong here. */
|
|
159
|
+
subnav?: React.ReactNode;
|
|
160
|
+
/** Fills its container instead of the viewport, for a host whose shell
|
|
161
|
+
* already owns the page height. */
|
|
162
|
+
fill?: boolean;
|
|
163
|
+
className?: string;
|
|
164
|
+
}
|
|
165
|
+
interface SaveLabels {
|
|
166
|
+
saving: string;
|
|
167
|
+
saved: string;
|
|
168
|
+
dirty: string;
|
|
169
|
+
}
|
|
170
|
+
declare const defaultSaveLabels: SaveLabels;
|
|
171
|
+
/**
|
|
172
|
+
* EditorScreen is the page a writing surface sits in: a command bar, a title,
|
|
173
|
+
* one column of prose, and slots for whatever the host puts around it.
|
|
174
|
+
*
|
|
175
|
+
* It renders and measures; it never fetches, saves or routes. `saveState` is
|
|
176
|
+
* displayed, not computed — the host owns the debounce and the request, so the
|
|
177
|
+
* same screen serves an autosaving draft and a form with an explicit button.
|
|
178
|
+
*
|
|
179
|
+
* Commands sit at the top rather than in a bar at the bottom. Below the fold
|
|
180
|
+
* they are gone the moment the document runs past one screen, and pinned to
|
|
181
|
+
* the bottom they eat a sixth of what the on-screen keyboard leaves.
|
|
182
|
+
*/
|
|
183
|
+
declare function EditorScreen({ children, title, onTitleChange, titlePlaceholder, titleLabel, saveState, saveLabels, lead, actions, status, aside, footer, subnav, fill, className, }: EditorScreenProps): react.JSX.Element;
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* SlashItem is one entry of the `/` menu.
|
|
187
|
+
*
|
|
188
|
+
* icon is a node rather than a component type: the package carries no icon
|
|
189
|
+
* library, so the host renders whichever one it already ships.
|
|
190
|
+
*/
|
|
191
|
+
interface SlashItem {
|
|
192
|
+
id: string;
|
|
193
|
+
title: string;
|
|
194
|
+
description?: string;
|
|
195
|
+
shortcut?: string;
|
|
196
|
+
icon?: React.ReactNode;
|
|
197
|
+
command: (args: {
|
|
198
|
+
editor: Editor;
|
|
199
|
+
range: Range;
|
|
200
|
+
}) => void;
|
|
201
|
+
}
|
|
202
|
+
interface SlashLabels {
|
|
203
|
+
heading1: string;
|
|
204
|
+
heading2: string;
|
|
205
|
+
bulletList: string;
|
|
206
|
+
orderedList: string;
|
|
207
|
+
blockquote: string;
|
|
208
|
+
codeBlock: string;
|
|
209
|
+
horizontalRule: string;
|
|
210
|
+
}
|
|
211
|
+
declare const defaultSlashLabels: SlashLabels;
|
|
212
|
+
interface SlashDescriptions {
|
|
213
|
+
heading1?: string;
|
|
214
|
+
heading2?: string;
|
|
215
|
+
bulletList?: string;
|
|
216
|
+
orderedList?: string;
|
|
217
|
+
blockquote?: string;
|
|
218
|
+
codeBlock?: string;
|
|
219
|
+
horizontalRule?: string;
|
|
220
|
+
}
|
|
221
|
+
interface DefaultSlashItemsOptions {
|
|
222
|
+
labels?: SlashLabels;
|
|
223
|
+
descriptions?: SlashDescriptions;
|
|
224
|
+
icons?: Partial<Record<DefaultSlashId, React.ReactNode>>;
|
|
225
|
+
}
|
|
226
|
+
type DefaultSlashId = "heading1" | "heading2" | "bulletList" | "orderedList" | "blockquote" | "codeBlock" | "horizontalRule";
|
|
227
|
+
/**
|
|
228
|
+
* defaultSlashItems is the block set every writing surface shares.
|
|
229
|
+
*
|
|
230
|
+
* Returned as data rather than rendered behind flags: a host adds, removes or
|
|
231
|
+
* reorders entries by composing the array, so a surface that needs one more
|
|
232
|
+
* command does not push another boolean into this signature.
|
|
233
|
+
*/
|
|
234
|
+
declare function defaultSlashItems({ labels, descriptions, icons, }?: DefaultSlashItemsOptions): SlashItem[];
|
|
235
|
+
/** filterSlashItems matches the query against titles the way the menu does. */
|
|
236
|
+
declare function filterSlashItems(items: SlashItem[], query: string): SlashItem[];
|
|
237
|
+
|
|
238
|
+
interface SlashMenuHandle {
|
|
239
|
+
onKeyDown: (props: {
|
|
240
|
+
event: KeyboardEvent;
|
|
241
|
+
}) => boolean;
|
|
242
|
+
}
|
|
243
|
+
interface SlashMenuProps {
|
|
244
|
+
items: SlashItem[];
|
|
245
|
+
command: (item: SlashItem) => void;
|
|
246
|
+
emptyLabel?: string;
|
|
247
|
+
}
|
|
248
|
+
declare const SlashMenu: react.ForwardRefExoticComponent<SlashMenuProps & react.RefAttributes<SlashMenuHandle>>;
|
|
249
|
+
|
|
250
|
+
interface SlashCommandsOptions {
|
|
251
|
+
/** Read on every keystroke, so a host can swap the set without remounting
|
|
252
|
+
* TipTap — rebuilding the extensions array wipes the text being typed. */
|
|
253
|
+
items: () => SlashItem[];
|
|
254
|
+
char: string;
|
|
255
|
+
emptyLabel?: string;
|
|
256
|
+
/** Distinguishes concurrently mounted editors: two surfaces sharing one key
|
|
257
|
+
* would answer each other's suggestions. */
|
|
258
|
+
pluginKey: PluginKey;
|
|
259
|
+
}
|
|
260
|
+
declare const SlashCommands: Extension<SlashCommandsOptions, any>;
|
|
261
|
+
|
|
96
262
|
interface InlineSuggestion {
|
|
97
263
|
id: string;
|
|
98
264
|
/** Document positions of the passage this replaces. */
|
|
@@ -157,4 +323,4 @@ declare function splitRevision(before: string, after: string): RevisionPart[];
|
|
|
157
323
|
*/
|
|
158
324
|
declare function docToMarkdown(doc: RichDoc): string;
|
|
159
325
|
|
|
160
|
-
export { type BlockFormat, type FormatLabels, type InlineSuggestion, InlineSuggestions, type PlaceToolbarInput, type RevisionPart, RichDoc, type SelectionInfo, type SelectionRect, SelectionToolbar, type SelectionToolbarProps, type SuggestionActions, type TextRange, type ToolbarAction, type ToolbarPlacement, blockFormats, defaultFormatLabels, docToMarkdown, expandToWords, inlineSuggestionsKey, placeToolbar, splitRevision, useSelectionToolbar };
|
|
326
|
+
export { type BlockFormat, type DefaultSlashId, type DefaultSlashItemsOptions, EditorScreen, type EditorScreenProps, type FormatLabels, type InlineSuggestion, InlineSuggestions, type PlaceToolbarInput, type ReadViewportInput, type RevisionPart, RichDoc, type SaveLabels, type SaveState, type SelectionInfo, type SelectionRect, SelectionToolbar, type SelectionToolbarProps, SlashCommands, type SlashCommandsOptions, type SlashDescriptions, type SlashItem, type SlashLabels, SlashMenu, type SlashMenuHandle, type SlashMenuProps, type SuggestionActions, type TextRange, type ToolbarAction, type ToolbarPlacement, type ViewportMetrics, type VisualViewportLike, blockFormats, defaultFormatLabels, defaultSaveLabels, defaultSlashItems, defaultSlashLabels, docToMarkdown, expandToWords, filterSlashItems, inlineSuggestionsKey, placeToolbar, readViewport, splitRevision, useSelectionToolbar, useViewport };
|