@quillmark/svelte 0.0.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/LICENSE +201 -0
- package/NOTICE +58 -0
- package/README.md +241 -0
- package/THEMING.md +153 -0
- package/dist/core/address.d.ts +56 -0
- package/dist/core/address.js +95 -0
- package/dist/core/bloom.d.ts +37 -0
- package/dist/core/bloom.js +116 -0
- package/dist/core/codec/anchor.d.ts +19 -0
- package/dist/core/codec/anchor.js +36 -0
- package/dist/core/codec/code.d.ts +12 -0
- package/dist/core/codec/code.js +118 -0
- package/dist/core/codec/decode.d.ts +24 -0
- package/dist/core/codec/decode.js +270 -0
- package/dist/core/codec/encode.d.ts +71 -0
- package/dist/core/codec/encode.js +480 -0
- package/dist/core/codec/field.d.ts +171 -0
- package/dist/core/codec/field.js +493 -0
- package/dist/core/codec/index.d.ts +22 -0
- package/dist/core/codec/index.js +43 -0
- package/dist/core/codec/inputrules.d.ts +7 -0
- package/dist/core/codec/inputrules.js +168 -0
- package/dist/core/codec/islands.d.ts +36 -0
- package/dist/core/codec/islands.js +82 -0
- package/dist/core/codec/keymap.d.ts +13 -0
- package/dist/core/codec/keymap.js +29 -0
- package/dist/core/codec/lists.d.ts +22 -0
- package/dist/core/codec/lists.js +104 -0
- package/dist/core/codec/marks.d.ts +37 -0
- package/dist/core/codec/marks.js +74 -0
- package/dist/core/codec/positions.d.ts +17 -0
- package/dist/core/codec/positions.js +108 -0
- package/dist/core/codec/prose.css +458 -0
- package/dist/core/codec/reconcile.d.ts +26 -0
- package/dist/core/codec/reconcile.js +64 -0
- package/dist/core/codec/schema.d.ts +13 -0
- package/dist/core/codec/schema.js +155 -0
- package/dist/core/codec/slash.d.ts +79 -0
- package/dist/core/codec/slash.js +0 -0
- package/dist/core/codec/table-view.d.ts +50 -0
- package/dist/core/codec/table-view.js +928 -0
- package/dist/core/codec/table.d.ts +85 -0
- package/dist/core/codec/table.js +224 -0
- package/dist/core/errors.d.ts +73 -0
- package/dist/core/errors.js +24 -0
- package/dist/core/index.d.ts +5 -0
- package/dist/core/index.js +19 -0
- package/dist/core/lifecycle.d.ts +7 -0
- package/dist/core/lifecycle.js +21 -0
- package/dist/core/rebind.svelte.d.ts +18 -0
- package/dist/core/rebind.svelte.js +44 -0
- package/dist/core/teardown.d.ts +20 -0
- package/dist/core/teardown.js +37 -0
- package/dist/core/theme.css +276 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +9 -0
- package/dist/preset/index.css +10 -0
- package/dist/preset/recipes.css +188 -0
- package/dist/preset/scale.css +97 -0
- package/dist/preview/Preview.svelte +118 -0
- package/dist/preview/Preview.svelte.d.ts +39 -0
- package/dist/preview/bridge.d.ts +13 -0
- package/dist/preview/bridge.js +88 -0
- package/dist/preview/controller.d.ts +40 -0
- package/dist/preview/controller.js +141 -0
- package/dist/preview/geometry.d.ts +37 -0
- package/dist/preview/geometry.js +45 -0
- package/dist/preview/index.d.ts +5 -0
- package/dist/preview/index.js +14 -0
- package/dist/preview/overlay.d.ts +10 -0
- package/dist/preview/overlay.js +82 -0
- package/dist/preview/paint.d.ts +17 -0
- package/dist/preview/paint.js +262 -0
- package/dist/preview/strings.d.ts +14 -0
- package/dist/preview/strings.js +22 -0
- package/dist/visual/ArrayField.svelte +362 -0
- package/dist/visual/ArrayField.svelte.d.ts +24 -0
- package/dist/visual/BooleanField.svelte +88 -0
- package/dist/visual/BooleanField.svelte.d.ts +20 -0
- package/dist/visual/Card.svelte +849 -0
- package/dist/visual/Card.svelte.d.ts +43 -0
- package/dist/visual/CardControls.svelte +92 -0
- package/dist/visual/CardControls.svelte.d.ts +11 -0
- package/dist/visual/DateField.svelte +203 -0
- package/dist/visual/DateField.svelte.d.ts +28 -0
- package/dist/visual/DiagnosticList.svelte +50 -0
- package/dist/visual/DiagnosticList.svelte.d.ts +7 -0
- package/dist/visual/EnumField.svelte +169 -0
- package/dist/visual/EnumField.svelte.d.ts +25 -0
- package/dist/visual/Field.svelte +270 -0
- package/dist/visual/Field.svelte.d.ts +37 -0
- package/dist/visual/FieldHint.svelte +185 -0
- package/dist/visual/FieldHint.svelte.d.ts +13 -0
- package/dist/visual/FieldLabel.svelte +141 -0
- package/dist/visual/FieldLabel.svelte.d.ts +19 -0
- package/dist/visual/FormatPopover.svelte +396 -0
- package/dist/visual/FormatPopover.svelte.d.ts +9 -0
- package/dist/visual/NumberField.svelte +72 -0
- package/dist/visual/NumberField.svelte.d.ts +19 -0
- package/dist/visual/ObjectField.svelte +131 -0
- package/dist/visual/ObjectField.svelte.d.ts +17 -0
- package/dist/visual/ProseArrayElement.svelte +98 -0
- package/dist/visual/ProseArrayElement.svelte.d.ts +19 -0
- package/dist/visual/ProseField.svelte +213 -0
- package/dist/visual/ProseField.svelte.d.ts +50 -0
- package/dist/visual/SlashMenu.svelte +126 -0
- package/dist/visual/SlashMenu.svelte.d.ts +14 -0
- package/dist/visual/TextField.svelte +63 -0
- package/dist/visual/TextField.svelte.d.ts +23 -0
- package/dist/visual/TipsCard.svelte +191 -0
- package/dist/visual/TipsCard.svelte.d.ts +13 -0
- package/dist/visual/VisualEditor.svelte +96 -0
- package/dist/visual/VisualEditor.svelte.d.ts +14 -0
- package/dist/visual/VisualEditorInner.svelte +1032 -0
- package/dist/visual/VisualEditorInner.svelte.d.ts +17 -0
- package/dist/visual/controls.css +272 -0
- package/dist/visual/diagnostics.d.ts +52 -0
- package/dist/visual/diagnostics.js +83 -0
- package/dist/visual/domid.d.ts +21 -0
- package/dist/visual/domid.js +36 -0
- package/dist/visual/ext.d.ts +19 -0
- package/dist/visual/ext.js +29 -0
- package/dist/visual/icons/Icon.svelte +39 -0
- package/dist/visual/icons/Icon.svelte.d.ts +12 -0
- package/dist/visual/icons/nodes.d.ts +103 -0
- package/dist/visual/icons/nodes.js +55 -0
- package/dist/visual/index.d.ts +9 -0
- package/dist/visual/index.js +24 -0
- package/dist/visual/motion.d.ts +18 -0
- package/dist/visual/motion.js +53 -0
- package/dist/visual/props.d.ts +77 -0
- package/dist/visual/props.js +1 -0
- package/dist/visual/signals.d.ts +59 -0
- package/dist/visual/signals.js +1 -0
- package/dist/visual/strings.d.ts +123 -0
- package/dist/visual/strings.js +80 -0
- package/dist/visual/structure.d.ts +224 -0
- package/dist/visual/structure.js +270 -0
- package/dist/visual/synced.svelte.d.ts +15 -0
- package/dist/visual/synced.svelte.js +34 -0
- package/dist/visual/tips.d.ts +24 -0
- package/dist/visual/tips.js +50 -0
- package/package.json +102 -0
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import { scanDoc } from './encode.js';
|
|
2
|
+
/** Build the position index for `doc` (walks it once via the shared scanner). */
|
|
3
|
+
export function buildLineIndex(doc) {
|
|
4
|
+
const s = scanDoc(doc);
|
|
5
|
+
const runs = s.runs;
|
|
6
|
+
let pmEndContent = 1;
|
|
7
|
+
let firstContentStart = 1;
|
|
8
|
+
if (runs.length) {
|
|
9
|
+
const first = runs[0];
|
|
10
|
+
firstContentStart = first.pmStart;
|
|
11
|
+
const last = runs[runs.length - 1];
|
|
12
|
+
pmEndContent = runEndPm(last);
|
|
13
|
+
}
|
|
14
|
+
return { runs, usvEnd: s.usvEnd, pmEndContent, firstContentStart };
|
|
15
|
+
}
|
|
16
|
+
/** PM position just past a run (its exclusive end). */
|
|
17
|
+
function runEndPm(run) {
|
|
18
|
+
if (run.kind === 'text')
|
|
19
|
+
return run.pmStart + run.s.length;
|
|
20
|
+
if (run.kind === 'nl')
|
|
21
|
+
return run.pmEnd;
|
|
22
|
+
return run.pmStart + 1;
|
|
23
|
+
}
|
|
24
|
+
const usvStartOf = (run) => run.usvStart;
|
|
25
|
+
const pmStartOf = (run) => run.pmStart;
|
|
26
|
+
/**
|
|
27
|
+
* Index of the last run whose `coord` is ≤ `pos`, or `-1` when `pos` precedes the
|
|
28
|
+
* first run. The walk emits runs in document order and every run owns at least one
|
|
29
|
+
* position in each space, so both coordinates increase strictly across the list and
|
|
30
|
+
* the lookup bisects. Bulk conversion is what wants it: `readAnchorsUsv` converts
|
|
31
|
+
* once per anchor on every commit, and a scan would make that O(anchors × runs).
|
|
32
|
+
*/
|
|
33
|
+
function lastRunAtOrBefore(runs, pos, coord) {
|
|
34
|
+
let lo = 0;
|
|
35
|
+
let hi = runs.length - 1;
|
|
36
|
+
let found = -1;
|
|
37
|
+
while (lo <= hi) {
|
|
38
|
+
const mid = (lo + hi) >> 1;
|
|
39
|
+
if (coord(runs[mid]) <= pos) {
|
|
40
|
+
found = mid;
|
|
41
|
+
lo = mid + 1;
|
|
42
|
+
}
|
|
43
|
+
else {
|
|
44
|
+
hi = mid - 1;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
return found;
|
|
48
|
+
}
|
|
49
|
+
/** USV content offset → PM position. */
|
|
50
|
+
export function usvToPM(index, usvPos) {
|
|
51
|
+
const usv = clamp(usvPos, 0, index.usvEnd);
|
|
52
|
+
if (usv >= index.usvEnd)
|
|
53
|
+
return index.usvEnd === 0 ? index.firstContentStart : index.pmEndContent;
|
|
54
|
+
// The runs TILE the USV space from 0 (every text the walk appends pushes its run
|
|
55
|
+
// first), so below `usvEnd` the run at or before `usv` is the run that owns it.
|
|
56
|
+
const run = index.runs[lastRunAtOrBefore(index.runs, usv, usvStartOf)];
|
|
57
|
+
if (run.kind === 'text')
|
|
58
|
+
return run.pmStart + utf16OfCodePoints(run.s, usv - run.usvStart);
|
|
59
|
+
return run.pmStart; // nl / atom: the run's single owned position
|
|
60
|
+
}
|
|
61
|
+
/** PM position → USV content offset. */
|
|
62
|
+
export function pmToUsv(index, pmPos) {
|
|
63
|
+
const i = lastRunAtOrBefore(index.runs, pmPos, pmStartOf);
|
|
64
|
+
if (i < 0)
|
|
65
|
+
return 0; // before the first run's PM position (or no runs at all)
|
|
66
|
+
const run = index.runs[i];
|
|
67
|
+
// PM coverage, unlike USV, has gaps no run owns (a rule's own token). A position
|
|
68
|
+
// in one is past every run that could answer it → the end of the content text.
|
|
69
|
+
if (pmPos >= runEndPm(run))
|
|
70
|
+
return index.usvEnd;
|
|
71
|
+
if (run.kind === 'text')
|
|
72
|
+
return run.usvStart + codePointsOfUtf16(run.s, pmPos - run.pmStart);
|
|
73
|
+
return run.usvStart;
|
|
74
|
+
}
|
|
75
|
+
// ── UTF-16 ↔ USV conversions within one text run ────────────────────────────
|
|
76
|
+
/** UTF-16 length of the first `n` code points of `s`. */
|
|
77
|
+
function utf16OfCodePoints(s, n) {
|
|
78
|
+
let cp = 0;
|
|
79
|
+
let i = 0;
|
|
80
|
+
while (cp < n && i < s.length) {
|
|
81
|
+
i += charUnitLen(s, i);
|
|
82
|
+
cp++;
|
|
83
|
+
}
|
|
84
|
+
return i;
|
|
85
|
+
}
|
|
86
|
+
/** Number of whole code points within the first `k` UTF-16 units of `s`. */
|
|
87
|
+
function codePointsOfUtf16(s, k) {
|
|
88
|
+
let cp = 0;
|
|
89
|
+
let i = 0;
|
|
90
|
+
while (i < k && i < s.length) {
|
|
91
|
+
i += charUnitLen(s, i);
|
|
92
|
+
cp++;
|
|
93
|
+
}
|
|
94
|
+
return cp;
|
|
95
|
+
}
|
|
96
|
+
/** UTF-16 unit length (1 or 2) of the code point starting at `i`. */
|
|
97
|
+
function charUnitLen(s, i) {
|
|
98
|
+
const code = s.charCodeAt(i);
|
|
99
|
+
if (code >= 0xd800 && code <= 0xdbff && i + 1 < s.length) {
|
|
100
|
+
const next = s.charCodeAt(i + 1);
|
|
101
|
+
if (next >= 0xdc00 && next <= 0xdfff)
|
|
102
|
+
return 2;
|
|
103
|
+
}
|
|
104
|
+
return 1;
|
|
105
|
+
}
|
|
106
|
+
function clamp(n, lo, hi) {
|
|
107
|
+
return n < lo ? lo : n > hi ? hi : n;
|
|
108
|
+
}
|
|
@@ -0,0 +1,458 @@
|
|
|
1
|
+
/* ProseMirror's own base stylesheet is imported beside this one (`codec/index.ts`):
|
|
2
|
+
the structural rules a view needs to render at all: `white-space:
|
|
3
|
+
break-spaces`, `li { position: relative }` for list markers, the
|
|
4
|
+
hidden-selection and separator-image guards. It is imported whole; the rules are
|
|
5
|
+
interdependent and which ones a given schema needs is not visible from here.
|
|
6
|
+
|
|
7
|
+
What it does NOT carry is a block reset, so without the rules below every leaf
|
|
8
|
+
inherits the UA `p { margin: 1em 0 }` (32px of margin in a box drawn to hold one
|
|
9
|
+
line), which is enough on its own to make an inline richtext field twice the height
|
|
10
|
+
of the input beside it. The reset lives here rather than per component because every
|
|
11
|
+
prose leaf in the package mounts through the codec (a field, the body, an array
|
|
12
|
+
element) and picks it up without restating it, and a leaf that carries the
|
|
13
|
+
control recipe (`visual/ProseField.svelte`) can then match `.qm-input` by
|
|
14
|
+
construction instead of by a tuned `min-height`. The source view is NOT one of
|
|
15
|
+
them: it is a `<pre>` holding `textContent` (`source/view.ts`), outside
|
|
16
|
+
`.ProseMirror` and so outside every rule in this file.
|
|
17
|
+
|
|
18
|
+
It also carries hard-coded VALUES that answer to no rung: a `#8cf` node-selection
|
|
19
|
+
outline, twice, and the `-32px` placing a selected list item's. Retinted and
|
|
20
|
+
re-measured here rather than in `theme.css` because a layered rule loses to that
|
|
21
|
+
unlayered stylesheet: equal specificity and a later import is what wins. */
|
|
22
|
+
.ProseMirror-selectednode {
|
|
23
|
+
outline: var(--_qm-ring-width) solid var(--_qm-accent);
|
|
24
|
+
}
|
|
25
|
+
/* Upstream's `-32px` is measured against the UA's 40px list indent, which the list
|
|
26
|
+
rule below replaces with a rung. The offset moves with it, or a selected item's
|
|
27
|
+
outline hangs into the margin by the difference. */
|
|
28
|
+
li.ProseMirror-selectednode::after {
|
|
29
|
+
border: var(--_qm-ring-width) solid var(--_qm-accent);
|
|
30
|
+
left: calc(var(--_qm-space-4) * -1);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/* The GAP CURSOR (`prosemirror-gapcursor`, imported beside this file): a caret
|
|
34
|
+
between two blocks, where the document holds no text position for an ordinary one.
|
|
35
|
+
It is what a press beside an island lands, so it is drawn in the surface's ink
|
|
36
|
+
rather than the upstream sheet's `black`, which is the `#8cf` defect again. */
|
|
37
|
+
.ProseMirror-gapcursor::after {
|
|
38
|
+
border-top-color: var(--_qm-ink);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/* Everything starts flush. Not a list of the block tags: the subtree is entirely
|
|
42
|
+
what the codec emits, a paragraph-only reset leaves headings and lists on UA
|
|
43
|
+
margins (two rhythms in one leaf), and an enumeration silently lags the schema:
|
|
44
|
+
`island_block` renders a bare `div` (`islands.ts`) and would match no such list. */
|
|
45
|
+
.ProseMirror * {
|
|
46
|
+
margin: 0;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/* One rhythm between sibling blocks, in place of the UA's collapsed pair. Scoped by
|
|
50
|
+
PARENT rather than by tag, which is what keeps it true as the schema grows: the
|
|
51
|
+
four containers below are every node whose content is `block+` (`schema.ts`), so
|
|
52
|
+
their children are blocks by definition, whatever kinds exist. `ul`/`ol` are
|
|
53
|
+
deliberately absent: a run of list items is tight, and the gap before a list is
|
|
54
|
+
the list's, not each item's. */
|
|
55
|
+
.ProseMirror > * + *,
|
|
56
|
+
.ProseMirror :is(blockquote, li, [data-qm-unknown-container]) > * + * {
|
|
57
|
+
margin-top: var(--_qm-space-4);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/* A blockquote's UA indent was its only mark, and the reset above takes it. The rule
|
|
61
|
+
and the inset carry it instead: the same hairline every surface reads, so a quote
|
|
62
|
+
reads as set apart without a fill or a second box. */
|
|
63
|
+
.ProseMirror blockquote {
|
|
64
|
+
padding-left: var(--_qm-space-3);
|
|
65
|
+
border-left: var(--_qm-border-width) solid var(--_qm-border);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/* ── The positive half ───────────────────────────────────────────────────────
|
|
69
|
+
What the reset leaves bare, and the whole of it: every mark and node `schema.ts`
|
|
70
|
+
emits, minus the four the UA already renders correctly (`strong`, `em`, `u`, `s`)
|
|
71
|
+
and the blockquote above.
|
|
72
|
+
|
|
73
|
+
Each rule answers to the REFERENCE QUILL rather than to a browser default or to a
|
|
74
|
+
web editor's chrome, because the leaf sits beside a live preview of the same
|
|
75
|
+
content. `usaf_memo` sets `raw` in a monospace face and adds nothing; it renders a
|
|
76
|
+
heading as a bold RUN-IN prepended to the following paragraph, with the level
|
|
77
|
+
never read; it gives a link no decoration at all. So a leaf shows that a span IS
|
|
78
|
+
code and a line IS a heading: never a prediction of the size paper will set it
|
|
79
|
+
at, which the package cannot know for a quill it has not seen.
|
|
80
|
+
|
|
81
|
+
Scoping is by TAG, and the schema does the rest: `h1`–`h6`, `hr` and the lists
|
|
82
|
+
cannot exist under the inline schema, so no rule for them can land on a `subject`
|
|
83
|
+
field however that field is mounted. The two MARKS are the exception: `code` and
|
|
84
|
+
`link` are in both schemas, and a `plaintext` leaf strips them at DECODE while
|
|
85
|
+
mounting a schema that still carries them, so a paste can put either in one. The
|
|
86
|
+
rules stand there too, and correctly: they say the leaf holds code, or a link,
|
|
87
|
+
which is exactly what it then holds.
|
|
88
|
+
|
|
89
|
+
`visual/TipsCard.svelte` restates the two mark rules by hand, over the same
|
|
90
|
+
`toDOM` output injected outside any view. */
|
|
91
|
+
|
|
92
|
+
/* Code is a FACE: no chip, no fill, no inset, no size step. The blockquote above
|
|
93
|
+
already argues the general form: a span reads as set apart without a fill or a
|
|
94
|
+
second box, on the one surface in the card with no edge of its own. A chip has
|
|
95
|
+
nowhere to come from in any case: the card is
|
|
96
|
+
`--_qm-surface-raised` and the only rung over it is `--_qm-surface-hover`, the
|
|
97
|
+
hover lane, so a chip at rest would read as a hovered button.
|
|
98
|
+
|
|
99
|
+
ONE selector for the mark and the block: a `code_block` renders `pre > code`
|
|
100
|
+
(`schema.ts`), so every character of it is inside a `code` element and the `pre`
|
|
101
|
+
needs no face of its own. `pre` takes no `overflow-x` either, because there is no
|
|
102
|
+
overflow: ProseMirror's own stylesheet sets `white-space: pre-wrap` there, so a
|
|
103
|
+
long line wraps. A scroller means overriding that to `pre` inside an editable
|
|
104
|
+
`code` textblock, which is an editing change, not a display rule. */
|
|
105
|
+
.ProseMirror code {
|
|
106
|
+
font-family: var(--_qm-font-mono);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/* A link takes the surface's ink (the UA's blue is the `#8cf` defect again) and a
|
|
110
|
+
DOTTED underline. Dotted rather than solid because `underline` is a mark in the
|
|
111
|
+
same schema, and a solid rule renders an authored `<u>` and a link identically in a
|
|
112
|
+
paragraph that can hold both. An underline at all, where the tips card's link takes
|
|
113
|
+
none, because this leaf is EDITABLE: the `href` is otherwise imperceptible, so
|
|
114
|
+
removing the rule loses an answer rather than ornament. No hue:
|
|
115
|
+
`--_qm-accent` already means focus, and a body of accent-coloured links collides
|
|
116
|
+
with the ring and with the correlation wash. */
|
|
117
|
+
.ProseMirror a {
|
|
118
|
+
color: inherit;
|
|
119
|
+
text-decoration: underline dotted;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/* The UA draws a rule as an inset border in the browser's own grey. It is the same
|
|
123
|
+
hairline every other surface reads instead. Spacing is already the sibling gap's,
|
|
124
|
+
above and below, so the rule carries none of its own. */
|
|
125
|
+
.ProseMirror hr {
|
|
126
|
+
border: none;
|
|
127
|
+
border-top: var(--_qm-border-width) solid var(--_qm-border);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/* The UA's 40px list indent is the one box the reset above does not take (it is
|
|
131
|
+
padding, not margin), and it is a length nothing chose. A rung replaces it. The
|
|
132
|
+
MARKERS stay the UA's: `disc` and `decimal` are already right, and restating them
|
|
133
|
+
buys nothing. Per-item spacing is untouched, which is the point of putting this on
|
|
134
|
+
the list rather than the item: the sibling-gap rule deliberately omits `ul`/`ol`,
|
|
135
|
+
and padding on the container cannot reintroduce what it withholds. */
|
|
136
|
+
.ProseMirror :is(ul, ol) {
|
|
137
|
+
padding-left: var(--_qm-space-4);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/* Six levels, one weight, no size step. The quill reads no level at all, so a size
|
|
141
|
+
ramp here would be the editor predicting a hierarchy the paper does not set; the
|
|
142
|
+
level stays in the model, which is where the codec round-trips it. It is also all
|
|
143
|
+
the ramp could carry: `--_qm-text-title` is one 1.125 step over the body, the
|
|
144
|
+
body sits on `--_qm-text-body` by decision, and leading alone is what separates
|
|
145
|
+
paper from chrome. Six levels do not fit in one step, and
|
|
146
|
+
minting five more is the drift the scale exists to prevent. */
|
|
147
|
+
.ProseMirror :is(h1, h2, h3, h4, h5, h6) {
|
|
148
|
+
font-size: inherit;
|
|
149
|
+
font-weight: var(--_qm-weight-label);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/* ── The table island ────────────────────────────────────────────────────────
|
|
153
|
+
|
|
154
|
+
The one construct in this file with CHROME in it. The rest of the stylesheet
|
|
155
|
+
states what a block is and lets paper set its size; a table island is edited
|
|
156
|
+
through controls that do not print, so it also has a recede ladder, a target
|
|
157
|
+
floor and a reveal. Those live here rather than in `visual/controls.css` for the
|
|
158
|
+
same reason the NodeView is vanilla: the island is inside the leaf, and the leaf
|
|
159
|
+
is the codec's. What it does NOT restate is the surface's dials: every value
|
|
160
|
+
below is a `--_qm-*` rung, so the island retints with everything else.
|
|
161
|
+
|
|
162
|
+
THE CHROME IS IN NO TRACK. Every control is absolutely positioned inside a real
|
|
163
|
+
data cell, hanging into a band the scroller carries as PADDING. So the grid holds
|
|
164
|
+
the box a paragraph would, the accessibility tree is the data's shape, and a
|
|
165
|
+
handle still lines up with its line by CONSTRUCTION rather than by a measured
|
|
166
|
+
overlay: it is inside the cell it names. */
|
|
167
|
+
|
|
168
|
+
/* The band, and the whole of what it costs. `padding` opens it; the island's
|
|
169
|
+
negative `margin` spends the card's own inset buying it back, so the grid sits one
|
|
170
|
+
inset from the prose edge instead of a tap target plus one.
|
|
171
|
+
|
|
172
|
+
`margin-top: 0` overrides the sibling-gap rule rather than going negative: the band
|
|
173
|
+
ABSORBS the gap. A negative top margin would hang the band over the block above,
|
|
174
|
+
and an `opacity: 0` control is still hit-tested, so it would take that block's last
|
|
175
|
+
presses. */
|
|
176
|
+
.qm-table-island {
|
|
177
|
+
margin-top: 0;
|
|
178
|
+
margin-left: calc(var(--_qm-space-3) * -1);
|
|
179
|
+
}
|
|
180
|
+
.qm-table-scroller {
|
|
181
|
+
/* The one horizontally scrolling box in a leaf: a table is as wide as its
|
|
182
|
+
columns, and a wide one must not push the card's own layout. `overflow-y` is
|
|
183
|
+
`hidden` rather than left alone, because a non-`visible` axis computes the other
|
|
184
|
+
to `auto` and a table has nothing to scroll vertically. */
|
|
185
|
+
position: relative;
|
|
186
|
+
overflow-x: auto;
|
|
187
|
+
overflow-y: hidden;
|
|
188
|
+
padding: var(--_qm-tap-min) 0 0 var(--_qm-tap-min);
|
|
189
|
+
width: max-content;
|
|
190
|
+
max-width: 100%;
|
|
191
|
+
}
|
|
192
|
+
.qm-table {
|
|
193
|
+
border-collapse: separate;
|
|
194
|
+
border-spacing: 0;
|
|
195
|
+
border: var(--_qm-border-width) solid var(--_qm-border);
|
|
196
|
+
border-radius: var(--_qm-radius-inner);
|
|
197
|
+
}
|
|
198
|
+
/* Two tiers of rule, because a table draws two different things. The FRAME and the
|
|
199
|
+
header's rule are structure and keep the full hairline; the lines between cells are
|
|
200
|
+
rhythm and drop to the faint rung, so the grid reads as one object with divisions
|
|
201
|
+
rather than as a stack of boxes. `separate` with zero spacing rather than
|
|
202
|
+
`collapse`: a collapsed border cannot be rounded, and the two tiers would fight
|
|
203
|
+
over which one wins an edge. */
|
|
204
|
+
.qm-table-cell {
|
|
205
|
+
position: relative;
|
|
206
|
+
border-right: var(--_qm-border-width) solid var(--_qm-border-faint);
|
|
207
|
+
border-bottom: var(--_qm-border-width) solid var(--_qm-border-faint);
|
|
208
|
+
padding: var(--_qm-space) var(--_qm-space-2);
|
|
209
|
+
vertical-align: top;
|
|
210
|
+
font-weight: inherit;
|
|
211
|
+
text-align: inherit;
|
|
212
|
+
}
|
|
213
|
+
.qm-table tr > .qm-table-cell:last-child {
|
|
214
|
+
border-right: none;
|
|
215
|
+
}
|
|
216
|
+
.qm-table tbody tr:last-child > .qm-table-cell {
|
|
217
|
+
border-bottom: none;
|
|
218
|
+
}
|
|
219
|
+
/* The header row is the ONE weight distinction the island draws: `header` is a
|
|
220
|
+
separate field in the model rather than a flag on a row, so the surface says so.
|
|
221
|
+
It takes a tone with it, which is the same elevation move the cards make: a rung,
|
|
222
|
+
never a shadow. The rest of a table's type is paper's. */
|
|
223
|
+
.qm-table-header-row .qm-table-cell {
|
|
224
|
+
font-weight: var(--_qm-weight-label);
|
|
225
|
+
background: var(--_qm-surface-raised);
|
|
226
|
+
border-bottom: var(--_qm-border-width) solid var(--_qm-border);
|
|
227
|
+
}
|
|
228
|
+
/* A cell holds a whole nested view. Its measure floor keeps an empty table from
|
|
229
|
+
collapsing to a row of hairlines with nothing to aim at, and the caret is the
|
|
230
|
+
focus indicator here exactly as it is in the leaf: no ring.
|
|
231
|
+
|
|
232
|
+
The HEIGHT floor is one line box, named as size times leading so both factors move
|
|
233
|
+
with the type ramp (`.qm-prose-block` states the same expression for three). An
|
|
234
|
+
empty row is then the height it will be once typed, so the first keystroke changes
|
|
235
|
+
no geometry. */
|
|
236
|
+
.qm-table-cell-host {
|
|
237
|
+
min-width: 4rem;
|
|
238
|
+
min-height: calc(var(--_qm-text-body) * var(--_qm-leading-body));
|
|
239
|
+
}
|
|
240
|
+
.qm-table-cell-host .ProseMirror {
|
|
241
|
+
outline: none;
|
|
242
|
+
}
|
|
243
|
+
/* A selected island takes the node outline every leaf block does
|
|
244
|
+
(`.ProseMirror-selectednode`, above), and that is the state the corner and Escape
|
|
245
|
+
leave a line FOR: the caret is out, the island is what is selected, and Backspace
|
|
246
|
+
deletes it. A caret inside a cell is not that state and does not paint it. */
|
|
247
|
+
|
|
248
|
+
/* A SELECTED LINE, washed across its cells: what Backspace deletes and Alt+arrow
|
|
249
|
+
moves. The hover rung, because it makes a hover's claim (this line, the one the next
|
|
250
|
+
command is about) held rather than passing. */
|
|
251
|
+
.qm-table-cell.qm-table-line {
|
|
252
|
+
background: var(--_qm-surface-hover);
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
/* Every control in the band is a flat centred button hung out of flow; what differs
|
|
256
|
+
is where it hangs and what it draws. */
|
|
257
|
+
:is(.qm-table-handle, .qm-table-seam, .qm-table-align-item) {
|
|
258
|
+
display: flex;
|
|
259
|
+
align-items: center;
|
|
260
|
+
justify-content: center;
|
|
261
|
+
border: none;
|
|
262
|
+
background: none;
|
|
263
|
+
cursor: pointer;
|
|
264
|
+
}
|
|
265
|
+
:is(.qm-table-seam, .qm-table-align-item) svg {
|
|
266
|
+
width: var(--_qm-glyph-control);
|
|
267
|
+
height: var(--_qm-glyph-control);
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
/* ONE control per line, and it is a bar rather than a glyph: what a handle acts on
|
|
271
|
+
is the row or column it sits against, and position says that better than an icon
|
|
272
|
+
would.
|
|
273
|
+
|
|
274
|
+
The TARGET is the band and the MARK is drawn small inside it, which is the rule
|
|
275
|
+
the tips card's dots already state: the handle spans its line on one axis and the
|
|
276
|
+
tap minimum on the other, and the bar in the middle of it is 3px. A handle sized to
|
|
277
|
+
the bar would be a 3px target on a control a pointer has to find. */
|
|
278
|
+
.qm-table-handle {
|
|
279
|
+
position: absolute;
|
|
280
|
+
padding: var(--_qm-space-half);
|
|
281
|
+
/* The pointer owns this gesture, not the page's scroll: a handle drags. */
|
|
282
|
+
touch-action: none;
|
|
283
|
+
}
|
|
284
|
+
.qm-table-handle[data-axis='column'] {
|
|
285
|
+
inset-inline: 0;
|
|
286
|
+
bottom: 100%;
|
|
287
|
+
height: var(--_qm-tap-min);
|
|
288
|
+
}
|
|
289
|
+
.qm-table-handle[data-axis='row'] {
|
|
290
|
+
inset-block: 0;
|
|
291
|
+
right: 100%;
|
|
292
|
+
width: var(--_qm-tap-min);
|
|
293
|
+
}
|
|
294
|
+
.qm-table-handle-bar {
|
|
295
|
+
display: block;
|
|
296
|
+
border-radius: var(--_qm-radius-pill);
|
|
297
|
+
background: var(--_qm-border);
|
|
298
|
+
}
|
|
299
|
+
.qm-table-handle[data-axis='column'] .qm-table-handle-bar {
|
|
300
|
+
width: 100%;
|
|
301
|
+
height: 0.1875rem;
|
|
302
|
+
}
|
|
303
|
+
.qm-table-handle[data-axis='row'] .qm-table-handle-bar {
|
|
304
|
+
width: 0.1875rem;
|
|
305
|
+
height: 100%;
|
|
306
|
+
min-height: 0.75rem;
|
|
307
|
+
}
|
|
308
|
+
/* A selected handle carries the line's ink, so the bar and the wash say the same
|
|
309
|
+
thing at both ends of the gesture. */
|
|
310
|
+
.qm-table-handle[aria-pressed='true'] .qm-table-handle-bar {
|
|
311
|
+
background: var(--_qm-ink-label);
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
/* The corner is the ISLAND's handle: the grid origin, where the two bands converge,
|
|
315
|
+
and the one gesture that selects the whole table. Its mark is a square because what
|
|
316
|
+
it acts on is the rectangle and not a line of it. */
|
|
317
|
+
.qm-table-corner-handle {
|
|
318
|
+
right: 100%;
|
|
319
|
+
bottom: 100%;
|
|
320
|
+
width: var(--_qm-tap-min);
|
|
321
|
+
height: var(--_qm-tap-min);
|
|
322
|
+
}
|
|
323
|
+
.qm-table-corner-mark {
|
|
324
|
+
display: block;
|
|
325
|
+
width: 0.4375rem;
|
|
326
|
+
height: 0.4375rem;
|
|
327
|
+
border-radius: var(--_qm-radius-pill);
|
|
328
|
+
background: var(--_qm-border);
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
/* A SEAM: the boundary between two lines, and the whole of how a table grows. One per
|
|
332
|
+
boundary including the leading and trailing ones, so an append and an interior
|
|
333
|
+
insert are one gesture at different places.
|
|
334
|
+
|
|
335
|
+
It sits OVER the handles in the band, and it is narrow, so the two share the band
|
|
336
|
+
without a dead zone: the handle owns the line's length and the seam owns its ends. */
|
|
337
|
+
.qm-table-seam {
|
|
338
|
+
position: absolute;
|
|
339
|
+
z-index: 1;
|
|
340
|
+
width: var(--_qm-tap-min);
|
|
341
|
+
height: var(--_qm-tap-min);
|
|
342
|
+
border-radius: var(--_qm-radius-pill);
|
|
343
|
+
color: var(--_qm-accent);
|
|
344
|
+
}
|
|
345
|
+
.qm-table-seam[data-axis='column'] {
|
|
346
|
+
bottom: 100%;
|
|
347
|
+
left: calc(var(--_qm-tap-min) / -2);
|
|
348
|
+
}
|
|
349
|
+
/* The TRAILING seams sit flush inside their edge rather than centred on it: the last
|
|
350
|
+
boundary is the grid's own edge, and a seam centred on it would hang half outside
|
|
351
|
+
the scroller, where the padding does not reach and the clip does, making the append
|
|
352
|
+
affordance the one control that could not be pressed. */
|
|
353
|
+
.qm-table-seam[data-axis='column'][data-side='trail'] {
|
|
354
|
+
left: auto;
|
|
355
|
+
right: 0;
|
|
356
|
+
}
|
|
357
|
+
.qm-table-seam[data-axis='row'] {
|
|
358
|
+
right: 100%;
|
|
359
|
+
top: calc(var(--_qm-tap-min) / -2);
|
|
360
|
+
}
|
|
361
|
+
.qm-table-seam[data-axis='row'][data-side='trail'] {
|
|
362
|
+
top: auto;
|
|
363
|
+
bottom: 0;
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
/* The alignment cluster: the one op with no gesture, so the one surface the chrome
|
|
367
|
+
still raises. It rides the column's SELECTION rather than a hover, so it costs
|
|
368
|
+
nothing at rest, and it sits in the band beside the handle rather than portalled:
|
|
369
|
+
four glyphs in a row is not a list to read. */
|
|
370
|
+
.qm-table-align {
|
|
371
|
+
position: absolute;
|
|
372
|
+
z-index: 2;
|
|
373
|
+
display: flex;
|
|
374
|
+
bottom: 100%;
|
|
375
|
+
left: 0;
|
|
376
|
+
height: var(--_qm-tap-min);
|
|
377
|
+
background: var(--_qm-surface-raised);
|
|
378
|
+
border: var(--_qm-border-width) solid var(--_qm-border);
|
|
379
|
+
border-radius: var(--_qm-radius-pill);
|
|
380
|
+
}
|
|
381
|
+
/* The LAST column anchors right instead: a cluster is wider than a narrow column, and
|
|
382
|
+
one hung off the right edge would be the only chrome the scroller could clip. */
|
|
383
|
+
.qm-table-cell:last-child .qm-table-align {
|
|
384
|
+
left: auto;
|
|
385
|
+
right: 0;
|
|
386
|
+
}
|
|
387
|
+
.qm-table-align-item {
|
|
388
|
+
width: var(--_qm-tap-min);
|
|
389
|
+
height: 100%;
|
|
390
|
+
padding: 0;
|
|
391
|
+
border-radius: var(--_qm-radius-pill);
|
|
392
|
+
color: var(--_qm-ink-label);
|
|
393
|
+
}
|
|
394
|
+
.qm-table-align-item:hover {
|
|
395
|
+
background: var(--_qm-surface-hover);
|
|
396
|
+
color: var(--_qm-ink);
|
|
397
|
+
}
|
|
398
|
+
.qm-table-align-item[aria-pressed='true'] {
|
|
399
|
+
color: var(--_qm-accent);
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
/* Pointer OR focus, the card stack's rule unchanged: opacity rather than absence, so
|
|
403
|
+
the controls stay in the tab order, and `:focus-within` on the ISLAND, so a caret
|
|
404
|
+
in a cell holds the reveal as a focused control does.
|
|
405
|
+
|
|
406
|
+
The reveal is per LINE: the island's hover arms the band, and `qm-hot` (the line the
|
|
407
|
+
pointer is actually over) is what reaches the idle rung, so a hover answers about
|
|
408
|
+
one line rather than about every one at once. */
|
|
409
|
+
.qm-table-handle,
|
|
410
|
+
.qm-table-seam {
|
|
411
|
+
opacity: 0;
|
|
412
|
+
transition: opacity var(--_qm-duration-fast) var(--_qm-ease-reverse);
|
|
413
|
+
}
|
|
414
|
+
.qm-table-island:is(:hover, :focus-within)
|
|
415
|
+
:is(.qm-table-handle.qm-hot, .qm-table-corner-handle, .qm-table-seam) {
|
|
416
|
+
opacity: var(--_qm-opacity-idle);
|
|
417
|
+
}
|
|
418
|
+
.qm-table-handle:hover,
|
|
419
|
+
.qm-table-handle:focus-visible,
|
|
420
|
+
.qm-table-handle[aria-pressed='true'],
|
|
421
|
+
.qm-table-seam:hover,
|
|
422
|
+
.qm-table-seam:focus-visible {
|
|
423
|
+
opacity: 1;
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
/* The ring, on the chrome and nowhere else in this file. A prose leaf's focus
|
|
427
|
+
indicator is its CARET and a cell's is too, so the outline is dropped there; a
|
|
428
|
+
handle is a button, and a button that takes focus to hold a selection has to show
|
|
429
|
+
it. Declared here rather than reached for as `.qm-focus-ring`: that recipe is the
|
|
430
|
+
visual tier's stylesheet, and the island is inside the leaf, which is the codec's. */
|
|
431
|
+
.qm-table-handle:focus-visible,
|
|
432
|
+
.qm-table-seam:focus-visible,
|
|
433
|
+
.qm-table-align-item:focus-visible {
|
|
434
|
+
outline: var(--_qm-ring-focus);
|
|
435
|
+
outline-offset: var(--_qm-ring-offset);
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
/* A DRAG in flight. The lifted line reads as picked up by TONE, never a shadow:
|
|
439
|
+
elevation is a surface rung here, which says the same thing at both poles. The drop
|
|
440
|
+
rule is the accent, the surface's one saturated ink, spent where a change is about
|
|
441
|
+
to land. */
|
|
442
|
+
.qm-table-dragging .qm-table-cell.qm-table-lifted {
|
|
443
|
+
background: var(--_qm-surface-raised);
|
|
444
|
+
opacity: var(--_qm-opacity-muted);
|
|
445
|
+
}
|
|
446
|
+
.qm-table-drop {
|
|
447
|
+
position: absolute;
|
|
448
|
+
z-index: 3;
|
|
449
|
+
background: var(--_qm-accent);
|
|
450
|
+
border-radius: var(--_qm-radius-pill);
|
|
451
|
+
pointer-events: none;
|
|
452
|
+
}
|
|
453
|
+
.qm-table-drop[data-axis='row'] {
|
|
454
|
+
height: var(--_qm-ring-width);
|
|
455
|
+
}
|
|
456
|
+
.qm-table-drop[data-axis='column'] {
|
|
457
|
+
width: var(--_qm-ring-width);
|
|
458
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { Content } from '@quillmark/wasm';
|
|
2
|
+
/** Tracks the last content the codec knows for a leaf, to distinguish foreign edits. */
|
|
3
|
+
export interface Reconciler {
|
|
4
|
+
/** Should `current` (a fresh read of the leaf) trigger a re-hydrate? */
|
|
5
|
+
shouldRehydrate(current: Content): boolean;
|
|
6
|
+
/** Record `rt` as the codec's known state (after its own edit or a re-hydrate). */
|
|
7
|
+
commit(rt: Content): void;
|
|
8
|
+
/** The last known content. Seeded at construction and only ever replaced, so it
|
|
9
|
+
* is never absent: the field reads it directly rather than through a cast. */
|
|
10
|
+
readonly last: Content;
|
|
11
|
+
}
|
|
12
|
+
/** A reconciler seeded with the leaf's initial content. */
|
|
13
|
+
export declare function createReconciler(initial: Content): Reconciler;
|
|
14
|
+
/**
|
|
15
|
+
* Canonical content equality: a structural deep-equal, key-order-insensitive so a
|
|
16
|
+
* WASM read (`doc.get`) and a codec projection (`pmToContent`) compare by value.
|
|
17
|
+
* This is the per-field twin of `Document.equals`.
|
|
18
|
+
*/
|
|
19
|
+
export declare function contentEqual(a: Content, b: Content): boolean;
|
|
20
|
+
/**
|
|
21
|
+
* The same structural equality over any boundary value: what the island diff
|
|
22
|
+
* compares two `ContentIsland` entries with (`encode.ts`). Key order is the reason
|
|
23
|
+
* it is not `JSON.stringify`: an island's opaque `props` crosses from a WASM read
|
|
24
|
+
* on one side and a PM node attribute on the other.
|
|
25
|
+
*/
|
|
26
|
+
export declare function valueEqual(a: unknown, b: unknown): boolean;
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/** A reconciler seeded with the leaf's initial content. */
|
|
2
|
+
export function createReconciler(initial) {
|
|
3
|
+
let known = initial;
|
|
4
|
+
return {
|
|
5
|
+
shouldRehydrate(current) {
|
|
6
|
+
return !contentEqual(current, known);
|
|
7
|
+
},
|
|
8
|
+
commit(rt) {
|
|
9
|
+
known = rt;
|
|
10
|
+
},
|
|
11
|
+
get last() {
|
|
12
|
+
return known;
|
|
13
|
+
}
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Canonical content equality: a structural deep-equal, key-order-insensitive so a
|
|
18
|
+
* WASM read (`doc.get`) and a codec projection (`pmToContent`) compare by value.
|
|
19
|
+
* This is the per-field twin of `Document.equals`.
|
|
20
|
+
*/
|
|
21
|
+
export function contentEqual(a, b) {
|
|
22
|
+
return deepEqual(a, b);
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* The same structural equality over any boundary value: what the island diff
|
|
26
|
+
* compares two `ContentIsland` entries with (`encode.ts`). Key order is the reason
|
|
27
|
+
* it is not `JSON.stringify`: an island's opaque `props` crosses from a WASM read
|
|
28
|
+
* on one side and a PM node attribute on the other.
|
|
29
|
+
*/
|
|
30
|
+
export function valueEqual(a, b) {
|
|
31
|
+
return deepEqual(a, b);
|
|
32
|
+
}
|
|
33
|
+
function deepEqual(a, b) {
|
|
34
|
+
if (a === b)
|
|
35
|
+
return true;
|
|
36
|
+
if (typeof a !== typeof b)
|
|
37
|
+
return false;
|
|
38
|
+
if (a === null || b === null)
|
|
39
|
+
return a === b;
|
|
40
|
+
if (Array.isArray(a) || Array.isArray(b)) {
|
|
41
|
+
if (!Array.isArray(a) || !Array.isArray(b) || a.length !== b.length)
|
|
42
|
+
return false;
|
|
43
|
+
for (let i = 0; i < a.length; i++)
|
|
44
|
+
if (!deepEqual(a[i], b[i]))
|
|
45
|
+
return false;
|
|
46
|
+
return true;
|
|
47
|
+
}
|
|
48
|
+
if (typeof a === 'object' && typeof b === 'object') {
|
|
49
|
+
const ao = a;
|
|
50
|
+
const bo = b;
|
|
51
|
+
const ak = Object.keys(ao);
|
|
52
|
+
const bk = Object.keys(bo);
|
|
53
|
+
if (ak.length !== bk.length)
|
|
54
|
+
return false;
|
|
55
|
+
for (const k of ak) {
|
|
56
|
+
if (!Object.prototype.hasOwnProperty.call(bo, k))
|
|
57
|
+
return false;
|
|
58
|
+
if (!deepEqual(ao[k], bo[k]))
|
|
59
|
+
return false;
|
|
60
|
+
}
|
|
61
|
+
return true;
|
|
62
|
+
}
|
|
63
|
+
return false;
|
|
64
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { Schema } from 'prosemirror-model';
|
|
2
|
+
/** The full field schema: every block kind, container, mark, and island. */
|
|
3
|
+
export declare const blockSchema: Schema<string, string>;
|
|
4
|
+
/**
|
|
5
|
+
* The constrained inline schema: one paragraph, no block splitting, no
|
|
6
|
+
* containers, no islands. `doc: "paragraph"` (exactly one child) is what makes an
|
|
7
|
+
* Enter a no-op at the model level: there is no second block for a split to land.
|
|
8
|
+
* Marks are present; `plaintext` fields strip them at decode and mount no
|
|
9
|
+
* mark input (a schema-level distinction the field draws, per CODEC §Inline mode).
|
|
10
|
+
*/
|
|
11
|
+
export declare const inlineSchema: Schema<"text" | "doc" | "paragraph", string>;
|
|
12
|
+
/** True for the constrained inline schema (no block containers); decode branches on it. */
|
|
13
|
+
export declare function isInlineSchema(schema: Schema): boolean;
|