@quillmark/svelte 0.12.0 → 0.13.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/core/codec/decode.d.ts +17 -0
- package/dist/core/codec/decode.js +57 -4
- package/dist/core/codec/field.d.ts +64 -15
- package/dist/core/codec/field.js +201 -65
- package/dist/core/codec/index.d.ts +3 -3
- package/dist/core/codec/index.js +3 -3
- package/dist/core/codec/keymap.d.ts +5 -4
- package/dist/core/codec/keymap.js +5 -4
- package/dist/core/codec/prose.css +42 -0
- package/dist/core/codec/schema.d.ts +24 -5
- package/dist/core/codec/schema.js +40 -9
- package/dist/core/errors.d.ts +3 -2
- package/dist/core/theme.css +9 -6
- package/dist/preview/paint.js +21 -17
- package/dist/visual/ArrayField.svelte +66 -22
- package/dist/visual/ArrayField.svelte.d.ts +2 -0
- package/dist/visual/BooleanField.svelte +28 -6
- package/dist/visual/BooleanField.svelte.d.ts +2 -0
- package/dist/visual/Card.svelte +3 -3
- package/dist/visual/DateField.svelte +66 -31
- package/dist/visual/EnumField.svelte +27 -9
- package/dist/visual/EnumField.svelte.d.ts +5 -0
- package/dist/visual/Field.svelte +45 -10
- package/dist/visual/FormatPopover.svelte +4 -3
- package/dist/visual/MatrixField.svelte +3 -2
- package/dist/visual/NumberField.svelte +31 -14
- package/dist/visual/NumberField.svelte.d.ts +5 -1
- package/dist/visual/ObjectField.svelte +62 -15
- package/dist/visual/ProseField.svelte +53 -23
- package/dist/visual/ProseField.svelte.d.ts +15 -3
- package/dist/visual/ProseValue.svelte +102 -46
- package/dist/visual/ProseValue.svelte.d.ts +17 -5
- package/dist/visual/TextField.svelte +35 -18
- package/dist/visual/TextField.svelte.d.ts +7 -0
- package/dist/visual/VariantField.svelte +1 -0
- package/dist/visual/VisualEditorInner.svelte +24 -24
- package/dist/visual/controls.css +16 -5
- package/dist/visual/strings.d.ts +8 -1
- package/dist/visual/strings.js +2 -0
- package/dist/visual/structure.d.ts +77 -43
- package/dist/visual/structure.js +132 -57
- package/package.json +2 -2
|
@@ -17,3 +17,20 @@ export declare function usvLength(s: string): number;
|
|
|
17
17
|
export declare function renderContent(rt: Content, schema: Schema): Node;
|
|
18
18
|
/** Decode a `Content` to a PM document under `schema`. */
|
|
19
19
|
export declare function decode(rt: Content, schema: Schema): PMNode;
|
|
20
|
+
/** Whether `rt` decodes under an inline schema with nothing but whitespace dropped:
|
|
21
|
+
* upstream's `inline` rule, or one plain line and the empty one a trailing `\n`
|
|
22
|
+
* opens. That newline is the one a YAML `|` scalar keeps in a `plaintext` value,
|
|
23
|
+
* which upstream refuses (`trailingNewline`) and the inline decode drops. Marks are
|
|
24
|
+
* the schema's own business (`plaintextSchema` declares none). */
|
|
25
|
+
export declare function fitsInline(rt: Content): boolean;
|
|
26
|
+
/** Whether `rt` decodes under `plainSchema` losing nothing but its marks, which the
|
|
27
|
+
* next commit heals (CODEC §Inline mode): upstream's `isPlain`, asked of the content
|
|
28
|
+
* with them set aside. */
|
|
29
|
+
export declare function fitsPlain(rt: Content): boolean;
|
|
30
|
+
/** Whether `rt` decodes under the schema a leaf so declared mounts, losing nothing its
|
|
31
|
+
* first commit would store: {@link fitsInline} narrowed, {@link fitsPlain} plain, and
|
|
32
|
+
* anything on the block schema. What a leaf held over the rest reads. */
|
|
33
|
+
export declare function fitsLeaf(rt: Content, leaf: {
|
|
34
|
+
inline: boolean;
|
|
35
|
+
plaintext: boolean;
|
|
36
|
+
}): boolean;
|
|
@@ -10,7 +10,8 @@
|
|
|
10
10
|
import { DOMSerializer } from 'prosemirror-model';
|
|
11
11
|
import { ISLAND_SLOT, isBlockIsland } from './islands.js';
|
|
12
12
|
import { descriptorOf, markKey, pmMarkFromContent } from './marks.js';
|
|
13
|
-
import {
|
|
13
|
+
import { core } from '../lifecycle.js';
|
|
14
|
+
import { hasMarks, isBlockSchema, isInlineSchema, takesLineBreak } from './schema.js';
|
|
14
15
|
/** Code points of `s` (USV units): the iteration granularity the content speaks. */
|
|
15
16
|
export function codePoints(s) {
|
|
16
17
|
return Array.from(s);
|
|
@@ -54,6 +55,9 @@ export function decode(rt, schema) {
|
|
|
54
55
|
if (isInlineSchema(schema)) {
|
|
55
56
|
return decodeInline(rt, schema, lineTexts, starts, marks, cursor);
|
|
56
57
|
}
|
|
58
|
+
if (!isBlockSchema(schema)) {
|
|
59
|
+
return decodePlain(rt, schema, lineTexts, starts, marks, cursor);
|
|
60
|
+
}
|
|
57
61
|
// Build leaves (fold `continues` runs), then nest by container prefix.
|
|
58
62
|
const leaves = [];
|
|
59
63
|
for (let i = 0; i < rt.lines.length; i++) {
|
|
@@ -69,13 +73,46 @@ export function decode(rt, schema) {
|
|
|
69
73
|
const blocks = groupBlocks(schema, leaves, 0, marks, cursor);
|
|
70
74
|
return schema.nodes.doc.create(null, blocks.length ? blocks : schema.nodes.paragraph.create());
|
|
71
75
|
}
|
|
72
|
-
/**
|
|
76
|
+
/** Whether `rt` decodes under an inline schema with nothing but whitespace dropped:
|
|
77
|
+
* upstream's `inline` rule, or one plain line and the empty one a trailing `\n`
|
|
78
|
+
* opens. That newline is the one a YAML `|` scalar keeps in a `plaintext` value,
|
|
79
|
+
* which upstream refuses (`trailingNewline`) and the inline decode drops. Marks are
|
|
80
|
+
* the schema's own business (`plaintextSchema` declares none). */
|
|
81
|
+
export function fitsInline(rt) {
|
|
82
|
+
if (core().isInline(rt))
|
|
83
|
+
return true;
|
|
84
|
+
const plain = (line) => line.kind === 'para' && line.containers.length === 0;
|
|
85
|
+
return (rt.islands.length === 0 &&
|
|
86
|
+
rt.lines.length === 2 &&
|
|
87
|
+
rt.lines.every(plain) &&
|
|
88
|
+
!rt.lines[1].continues &&
|
|
89
|
+
rt.text.endsWith('\n'));
|
|
90
|
+
}
|
|
91
|
+
/** Whether `rt` decodes under `plainSchema` losing nothing but its marks, which the
|
|
92
|
+
* next commit heals (CODEC §Inline mode): upstream's `isPlain`, asked of the content
|
|
93
|
+
* with them set aside. */
|
|
94
|
+
export function fitsPlain(rt) {
|
|
95
|
+
return core().isPlain({ ...rt, marks: [] });
|
|
96
|
+
}
|
|
97
|
+
/** Whether `rt` decodes under the schema a leaf so declared mounts, losing nothing its
|
|
98
|
+
* first commit would store: {@link fitsInline} narrowed, {@link fitsPlain} plain, and
|
|
99
|
+
* anything on the block schema. What a leaf held over the rest reads. */
|
|
100
|
+
export function fitsLeaf(rt, leaf) {
|
|
101
|
+
if (leaf.inline)
|
|
102
|
+
return fitsInline(rt);
|
|
103
|
+
return !leaf.plaintext || fitsPlain(rt);
|
|
104
|
+
}
|
|
105
|
+
/** Inline decode: one paragraph, containers and islands stripped. */
|
|
73
106
|
function decodeInline(rt, schema, lineTexts, starts, marks, cursor) {
|
|
74
107
|
// An inline field is single-line; join any stray lines with a space (no
|
|
75
|
-
// hard_break node exists in this schema).
|
|
76
|
-
//
|
|
108
|
+
// hard_break node exists in this schema). The empty line a trailing `\n` opens is
|
|
109
|
+
// dropped rather than joined, so a first edit stores no space nobody typed. Islands
|
|
110
|
+
// are not representable inline: the slot char is dropped and its entry skipped.
|
|
77
111
|
const inline = [];
|
|
112
|
+
const last = lineTexts.length - 1;
|
|
78
113
|
for (let i = 0; i < lineTexts.length; i++) {
|
|
114
|
+
if (i > 0 && i === last && lineTexts[i] === '')
|
|
115
|
+
break;
|
|
79
116
|
if (i > 0)
|
|
80
117
|
inline.push(schema.text(' '));
|
|
81
118
|
inline.push(...buildInline(schema, lineTexts[i], starts[i], marks, cursor, true));
|
|
@@ -83,6 +120,22 @@ function decodeInline(rt, schema, lineTexts, starts, marks, cursor) {
|
|
|
83
120
|
const para = schema.nodes.paragraph.create(null, inline);
|
|
84
121
|
return schema.nodes.doc.create(null, para);
|
|
85
122
|
}
|
|
123
|
+
/** Plain decode: a `continues` line is a `hard_break` in the paragraph above and every
|
|
124
|
+
* other line opens one. A line's kind and containers and every island have no node
|
|
125
|
+
* here and are dropped, so a content {@link fitsPlain} refuses opens flattened. */
|
|
126
|
+
function decodePlain(rt, schema, lineTexts, starts, marks, cursor) {
|
|
127
|
+
const paras = [];
|
|
128
|
+
for (let i = 0; i < rt.lines.length; i++) {
|
|
129
|
+
const inline = buildInline(schema, lineTexts[i] ?? '', starts[i], marks, cursor, true);
|
|
130
|
+
const open = paras[paras.length - 1];
|
|
131
|
+
if (rt.lines[i].continues && open)
|
|
132
|
+
open.push(schema.nodes.hard_break.create(), ...inline);
|
|
133
|
+
else
|
|
134
|
+
paras.push(inline);
|
|
135
|
+
}
|
|
136
|
+
const blocks = paras.map((inline) => schema.nodes.paragraph.create(null, inline));
|
|
137
|
+
return schema.nodes.doc.create(null, blocks.length ? blocks : schema.nodes.paragraph.create());
|
|
138
|
+
}
|
|
86
139
|
/** Nest a run of leaves that share a `depth`-length container prefix into blocks. */
|
|
87
140
|
function groupBlocks(schema, leaves, depth, marks, cursor) {
|
|
88
141
|
const out = [];
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import { type Schema } from 'prosemirror-model';
|
|
2
2
|
import { Plugin } from 'prosemirror-state';
|
|
3
3
|
import { EditorView } from 'prosemirror-view';
|
|
4
4
|
import type { Document, Content, Addr, Quill } from '@quillmark/wasm';
|
|
@@ -14,11 +14,24 @@ export interface CreateFieldOpts {
|
|
|
14
14
|
quill: Quill;
|
|
15
15
|
addr: Addr;
|
|
16
16
|
container: HTMLElement;
|
|
17
|
-
/** Constrained single-textblock schema (a `
|
|
17
|
+
/** Constrained single-textblock schema (a field declaring `inline`). */
|
|
18
18
|
inline?: boolean;
|
|
19
|
-
/**
|
|
20
|
-
* and
|
|
19
|
+
/** A mark-free schema (a `plaintext` field): literal text, no formatting and no
|
|
20
|
+
* anchors, in paragraphs and hard breaks or, with `inline`, one textblock. */
|
|
21
21
|
plaintext?: boolean;
|
|
22
|
+
/** The id of the note the caller draws inside `container` for a held leaf →
|
|
23
|
+
* `aria-describedby` while it holds. */
|
|
24
|
+
heldNoteId?: string;
|
|
25
|
+
/**
|
|
26
|
+
* Fired when the leaf holds or releases, and at mount when it mounts held. A leaf
|
|
27
|
+
* declaring `inline`, or a `plaintext` one, is held over a value its schema cannot
|
|
28
|
+
* hold (`fitsLeaf`, upstream's `isInline` and `isPlain`): the decode would join lines
|
|
29
|
+
* and drop containers and islands for the first commit to store. A held leaf draws
|
|
30
|
+
* its content on the block schema, read-only, and commits nothing. Judged of the value
|
|
31
|
+
* each mount and re-hydrate reads, so {@link FieldController.applyExternal} is what
|
|
32
|
+
* re-evaluates it.
|
|
33
|
+
*/
|
|
34
|
+
onHold?(held: boolean): void;
|
|
22
35
|
/** Suppress the markdown-shorthand input rules. */
|
|
23
36
|
noInputRules?: boolean;
|
|
24
37
|
/** The island chrome's wording, read live (per render) so a consumer swapping
|
|
@@ -46,11 +59,28 @@ export interface CreateFieldOpts {
|
|
|
46
59
|
labelledBy?: string;
|
|
47
60
|
/** The parked `description` → `aria-describedby`; announced after the name. */
|
|
48
61
|
describedBy?: string;
|
|
49
|
-
/**
|
|
50
|
-
*
|
|
51
|
-
*
|
|
52
|
-
*
|
|
62
|
+
/**
|
|
63
|
+
* The resolved `default:` where it prints: the content an unset field's leaf holds,
|
|
64
|
+
* marked `data-default` on the editable for the default rung. Nothing is written
|
|
65
|
+
* until an edit, which commits the whole leaf as authored: the default taken, with
|
|
66
|
+
* the edit in it. The initial value; {@link FieldController.setFallback} moves it
|
|
67
|
+
* after mount.
|
|
68
|
+
*/
|
|
69
|
+
fallback?: Content;
|
|
70
|
+
/** Ghost text shown on the empty leaf: the `none` an unset optional field prints, or
|
|
71
|
+
* a body's invitation. The initial value; {@link FieldController.setPlaceholder}
|
|
72
|
+
* moves it after mount. Empty/absent shows no ghost. */
|
|
53
73
|
placeholder?: string;
|
|
74
|
+
/** The placeholder is what the unset field prints, so it goes at the leaf's first
|
|
75
|
+
* edit: the edit answers the field, and emptied the leaf holds an empty answer, which
|
|
76
|
+
* prints empty. Absent, the placeholder is an invitation (a body's) and returns
|
|
77
|
+
* whenever the leaf is empty. */
|
|
78
|
+
placeholderUntilEdit?: boolean;
|
|
79
|
+
/** Ghost text shown on the empty leaf until its first edit: an unset field's
|
|
80
|
+
* `example:`, at rest where there is no placeholder and in the placeholder's stead
|
|
81
|
+
* while the leaf holds the focus. The initial value; {@link FieldController.setExample}
|
|
82
|
+
* moves it after mount. */
|
|
83
|
+
example?: string;
|
|
54
84
|
onFocus?(addr: Addr): void;
|
|
55
85
|
/** Fired with the new USV caret after an edit or a selection move. */
|
|
56
86
|
onCaretMove?(addr: Addr, pos: number): void;
|
|
@@ -60,7 +90,8 @@ export interface CreateFieldOpts {
|
|
|
60
90
|
* move, so a host driving a recompile off it recompiles on every arrow key.
|
|
61
91
|
*
|
|
62
92
|
* Fires for every commit regardless of which branch it took (`applyChange`, the
|
|
63
|
-
* first-edit `overwrite`, the fallback `overwrite
|
|
93
|
+
* first-edit `overwrite`, the fallback `overwrite`, a `plaintext` leaf's typed
|
|
94
|
+
* write) and for an anchor mutation,
|
|
64
95
|
* which changes no text. A commit that failed outright (`commit-lost`) does not
|
|
65
96
|
* fire it: nothing landed.
|
|
66
97
|
*/
|
|
@@ -75,7 +106,8 @@ export interface FieldController {
|
|
|
75
106
|
readonly el: HTMLElement;
|
|
76
107
|
/** Place the caret at USV `pos` (preview onPick → usvToPM → here). */
|
|
77
108
|
setCaret(pos: number): void;
|
|
78
|
-
/** External content change → re-hydrate this leaf (gated by reconcile)
|
|
109
|
+
/** External content change → re-hydrate this leaf (gated by reconcile), and
|
|
110
|
+
* re-evaluate its hold against the value now stored. */
|
|
79
111
|
applyExternal(): void;
|
|
80
112
|
/**
|
|
81
113
|
* Move the empty-leaf ghost after mount: a card retyped to another kind takes
|
|
@@ -86,6 +118,12 @@ export interface FieldController {
|
|
|
86
118
|
* a transaction would fire `onCaretMove` at a moment the caret did not move.
|
|
87
119
|
*/
|
|
88
120
|
setPlaceholder(text: string | undefined): void;
|
|
121
|
+
/** Move the example ghost after mount, the way {@link setPlaceholder} moves the
|
|
122
|
+
* other one. */
|
|
123
|
+
setExample(text: string | undefined): void;
|
|
124
|
+
/** Move the default an unset leaf holds after mount: a retype or a quill swap
|
|
125
|
+
* re-seats an unset leaf on the new kind's default, and leaves a written one be. */
|
|
126
|
+
setFallback(content: Content | undefined): void;
|
|
89
127
|
focus(): void;
|
|
90
128
|
/** The current stored content for this addr (for tests / reconcile). */
|
|
91
129
|
getContent(): Content;
|
|
@@ -146,10 +184,17 @@ export declare function proseAttributes(opts: {
|
|
|
146
184
|
labelledBy?: string;
|
|
147
185
|
describedBy?: string;
|
|
148
186
|
}): Record<string, string> | undefined;
|
|
187
|
+
/** A held leaf's attributes, described by its note as well. A non-editable view is no
|
|
188
|
+
* textbox to assistive tech or to Tab, so a held leaf states both itself. */
|
|
189
|
+
export declare function heldAttributes(opts: {
|
|
190
|
+
label?: string;
|
|
191
|
+
labelledBy?: string;
|
|
192
|
+
describedBy?: string;
|
|
193
|
+
}, noteId: string | undefined): Record<string, string>;
|
|
149
194
|
export declare function createField(opts: CreateFieldOpts): FieldController;
|
|
150
195
|
/**
|
|
151
196
|
* The prose-leaf plugin stack (VISUAL_EDITOR §Surface): shared by
|
|
152
|
-
* {@link createField} and the by-value
|
|
197
|
+
* {@link createField} and the by-value editor (`ProseValue`), so the two
|
|
153
198
|
* never fork the keymap/plugin ordering. History first, then any leaf-specific
|
|
154
199
|
* plugins (`afterHistory`: the addressed leaf passes its anchor-position plugin;
|
|
155
200
|
* a by-value leaf passes none), then {@link linebreakPlugin}, which normalizes what
|
|
@@ -158,10 +203,10 @@ export declare function createField(opts: CreateFieldOpts): FieldController;
|
|
|
158
203
|
* shapes: the gap cursor, {@link pastAtomPlugin}, and the island paste pass
|
|
159
204
|
* (`islands.ts`).
|
|
160
205
|
*
|
|
161
|
-
* Every mark-shaped plugin reads the schema rather than a flag: over
|
|
162
|
-
*
|
|
163
|
-
*
|
|
164
|
-
*
|
|
206
|
+
* Every mark-shaped plugin reads the schema rather than a flag: over either plaintext
|
|
207
|
+
* schema the shorthand rules build nothing (each is guarded on its mark type) and the
|
|
208
|
+
* toggles bind no key, so a `**bold**` keeps the delimiters its author typed without
|
|
209
|
+
* a second rule saying so.
|
|
165
210
|
*/
|
|
166
211
|
export declare function proseLeafPlugins(schema: Schema, opts: {
|
|
167
212
|
inline: boolean;
|
|
@@ -173,5 +218,9 @@ export declare function proseLeafPlugins(schema: Schema, opts: {
|
|
|
173
218
|
* ({@link FieldController.setPlaceholder}), and a re-hydration rebuilds this
|
|
174
219
|
* stack, so the plugin must ask rather than hold. */
|
|
175
220
|
placeholder?: () => string | undefined;
|
|
221
|
+
/** {@link CreateFieldOpts.placeholderUntilEdit}. */
|
|
222
|
+
placeholderUntilEdit?: boolean;
|
|
223
|
+
/** The focused leaf's ghost, read live for the same reason. */
|
|
224
|
+
example?: () => string | undefined;
|
|
176
225
|
afterHistory?: Plugin[];
|
|
177
226
|
}): Plugin[];
|