@quillmark/svelte 0.20.1 → 0.22.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/NOTICE +6 -7
- package/README.md +1 -1
- package/THEMING.md +1 -1
- package/dist/core/address.d.ts +6 -1
- package/dist/core/address.js +12 -3
- package/dist/core/bloom.d.ts +9 -4
- package/dist/core/bloom.js +18 -49
- package/dist/core/codec/atoms.d.ts +1 -1
- package/dist/core/codec/atoms.js +26 -11
- package/dist/core/codec/blocks.d.ts +5 -5
- package/dist/core/codec/blocks.js +27 -8
- package/dist/core/codec/code.d.ts +4 -4
- package/dist/core/codec/code.js +52 -22
- package/dist/core/codec/decode.d.ts +5 -5
- package/dist/core/codec/decode.js +18 -12
- package/dist/core/codec/encode.js +6 -1
- package/dist/core/codec/field.d.ts +22 -15
- package/dist/core/codec/field.js +66 -26
- package/dist/core/codec/index.d.ts +1 -1
- package/dist/core/codec/index.js +2 -2
- package/dist/core/codec/inputrules.d.ts +4 -0
- package/dist/core/codec/inputrules.js +44 -6
- package/dist/core/codec/link-paste.d.ts +11 -0
- package/dist/core/codec/link-paste.js +32 -0
- package/dist/core/codec/lists.d.ts +5 -4
- package/dist/core/codec/lists.js +87 -31
- package/dist/core/codec/nesting.d.ts +20 -0
- package/dist/core/codec/nesting.js +62 -0
- package/dist/core/codec/prose.css +145 -63
- package/dist/core/codec/schema.d.ts +2 -2
- package/dist/core/codec/schema.js +71 -18
- package/dist/core/codec/slash.js +0 -0
- package/dist/core/codec/table-copy.d.ts +17 -0
- package/dist/core/codec/table-copy.js +84 -0
- package/dist/core/codec/table-view.d.ts +6 -7
- package/dist/core/codec/table-view.js +281 -201
- package/dist/core/codec/table.d.ts +2 -9
- package/dist/core/codec/table.js +10 -19
- package/dist/core/codec/tasks.d.ts +19 -0
- package/dist/core/codec/tasks.js +140 -0
- package/dist/core/errors.d.ts +2 -2
- package/dist/core/rungs.d.ts +6 -0
- package/dist/core/rungs.js +11 -0
- package/dist/core/theme.css +24 -12
- package/dist/visual/ArrayField.svelte +236 -78
- package/dist/visual/ArrayField.svelte.d.ts +0 -2
- package/dist/visual/BooleanField.svelte +12 -1
- package/dist/visual/Card.svelte +41 -23
- package/dist/visual/Card.svelte.d.ts +3 -2
- package/dist/visual/CardControls.svelte +15 -15
- package/dist/visual/DateField.svelte +93 -64
- package/dist/visual/EnumField.svelte +9 -4
- package/dist/visual/Field.svelte +20 -16
- package/dist/visual/Field.svelte.d.ts +4 -2
- package/dist/visual/FieldHint.svelte +22 -5
- package/dist/visual/FieldLabel.svelte +5 -33
- package/dist/visual/FieldLabel.svelte.d.ts +0 -2
- package/dist/visual/FormatPopover.svelte +78 -81
- package/dist/visual/MatrixField.svelte +28 -19
- package/dist/visual/NumberField.svelte +32 -3
- package/dist/visual/ObjectField.svelte +40 -24
- package/dist/visual/ObjectField.svelte.d.ts +6 -0
- package/dist/visual/ProseField.svelte +10 -13
- package/dist/visual/ProseField.svelte.d.ts +5 -2
- package/dist/visual/ProseValue.svelte +25 -19
- package/dist/visual/ProseValue.svelte.d.ts +4 -4
- package/dist/visual/TextField.svelte +3 -2
- package/dist/visual/TipsCard.svelte +7 -7
- package/dist/visual/VariantField.svelte +1 -1
- package/dist/visual/VariantField.svelte.d.ts +1 -1
- package/dist/visual/VisualEditorInner.svelte +121 -39
- package/dist/visual/controls.css +21 -2
- package/dist/visual/icons/Icon.svelte +3 -2
- package/dist/visual/icons/Icon.svelte.d.ts +3 -2
- package/dist/visual/icons/nodes.d.ts +25 -21
- package/dist/visual/icons/nodes.js +9 -7
- package/dist/visual/leaves.d.ts +12 -9
- package/dist/visual/leaves.js +19 -0
- package/dist/visual/motion.d.ts +25 -3
- package/dist/visual/motion.js +58 -9
- package/dist/visual/strings.d.ts +15 -9
- package/dist/visual/strings.js +19 -12
- package/dist/visual/structure.d.ts +21 -31
- package/dist/visual/structure.js +54 -51
- package/package.json +3 -3
package/dist/core/codec/lists.js
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
|
-
import { liftListItem, sinkListItem
|
|
1
|
+
import { liftListItem, sinkListItem } from 'prosemirror-schema-list';
|
|
2
2
|
import { EditorState, Selection, TextSelection } from 'prosemirror-state';
|
|
3
|
-
import { chainCommands, joinTextblockBackward } from 'prosemirror-commands';
|
|
3
|
+
import { chainCommands, joinTextblockBackward, joinTextblockForward } from 'prosemirror-commands';
|
|
4
|
+
import { withinNesting } from './nesting.js';
|
|
5
|
+
import { splitTaskItem } from './tasks.js';
|
|
4
6
|
/** The caret's enclosing `list_item` when it sits at the very start of that item's
|
|
5
7
|
* first block: the position both structural keys branch on. `null` otherwise
|
|
6
8
|
* (mid-text, a non-first block of a multi-paragraph item, a non-empty selection),
|
|
@@ -72,41 +74,68 @@ function paragraphAboveList(itemType, paragraph) {
|
|
|
72
74
|
};
|
|
73
75
|
}
|
|
74
76
|
/**
|
|
75
|
-
* Backspace at the start of
|
|
76
|
-
*
|
|
77
|
+
* Backspace at the start of an item → lift it: one level out for a nested item, out of
|
|
78
|
+
* the list to a paragraph at the top, whichever item it is. The first press takes off
|
|
79
|
+
* the item's own shape and joins nothing; the press after is the join
|
|
80
|
+
* ({@link joinOntoList}).
|
|
81
|
+
*
|
|
82
|
+
* Lifting a later item out splits its list, and an ordered list's items past the cut
|
|
83
|
+
* keep their numbers: the list after what the item held starts where they were, where a
|
|
84
|
+
* fresh `ordered_list` would count from 1 again.
|
|
77
85
|
*/
|
|
78
|
-
function
|
|
86
|
+
function liftAtItemStart(itemType) {
|
|
79
87
|
return (state, dispatch) => {
|
|
80
88
|
const at = atItemStart(state, itemType);
|
|
81
|
-
if (!at
|
|
89
|
+
if (!at)
|
|
82
90
|
return false;
|
|
83
|
-
|
|
91
|
+
const list = state.doc.nodeAt(at.listPos);
|
|
92
|
+
const cut = list?.type.name === 'ordered_list' && !at.nested && at.itemIndex < list.childCount - 1;
|
|
93
|
+
const listEnd = at.listPos + (list?.nodeSize ?? 0);
|
|
94
|
+
return liftListItem(itemType)(state, dispatch &&
|
|
95
|
+
((tr) => {
|
|
96
|
+
if (cut)
|
|
97
|
+
renumberEndingAt(tr, tr.mapping.map(listEnd, -1), list.attrs.start + at.itemIndex + 1);
|
|
98
|
+
dispatch(tr);
|
|
99
|
+
}));
|
|
84
100
|
};
|
|
85
101
|
}
|
|
102
|
+
/** Start the ordered list ending at `end` at `start`: the items a lift cut off, which
|
|
103
|
+
* end where the whole list did, past whatever the lifted item held. */
|
|
104
|
+
function renumberEndingAt(tr, end, start) {
|
|
105
|
+
const list = tr.doc.resolve(end).nodeBefore;
|
|
106
|
+
if (list?.type.name === 'ordered_list' && list.attrs.start !== start)
|
|
107
|
+
tr.setNodeMarkup(end - list.nodeSize, undefined, { ...list.attrs, start });
|
|
108
|
+
}
|
|
86
109
|
/**
|
|
87
|
-
* Backspace at the
|
|
88
|
-
*
|
|
110
|
+
* Backspace at the head of a block that stands right after a list → join its text into
|
|
111
|
+
* the list's last line, as two paragraphs join. The base keymap's answer there wraps the
|
|
112
|
+
* block into a fresh item instead, which undoes the lift that just took it out of the
|
|
113
|
+
* list ({@link liftAtItemStart}) and leaves the key toggling.
|
|
89
114
|
*
|
|
90
|
-
*
|
|
91
|
-
*
|
|
92
|
-
*
|
|
93
|
-
*
|
|
94
|
-
*
|
|
95
|
-
* Only between two blocks of one kind. A fence is not a line to put another line on,
|
|
96
|
-
* and the join across that edge would retype one side's whole text as the other's
|
|
97
|
-
* (`code.ts`, which refuses the same join between siblings); the base keymap's answer
|
|
98
|
-
* stands here instead, merging the items with each block kept whole.
|
|
115
|
+
* Between a fence and prose the text does not join: a fence is not a line to put another
|
|
116
|
+
* line on, and the join would retype one side's whole text as the other's (`code.ts`,
|
|
117
|
+
* which refuses the same join between siblings). The block moves into the list's last
|
|
118
|
+
* item whole instead, a block of that item under its first.
|
|
99
119
|
*/
|
|
100
|
-
function
|
|
120
|
+
function joinOntoList(itemType) {
|
|
101
121
|
return (state, dispatch, view) => {
|
|
102
|
-
const
|
|
103
|
-
if (!
|
|
122
|
+
const { $from, empty } = state.selection;
|
|
123
|
+
if (!empty || !$from.parent.isTextblock || $from.parentOffset !== 0)
|
|
124
|
+
return false;
|
|
125
|
+
const before = state.doc.resolve($from.before()).nodeBefore;
|
|
126
|
+
if (before?.firstChild?.type !== itemType)
|
|
104
127
|
return false;
|
|
105
|
-
const into = lastBlockIn(
|
|
106
|
-
if (!into
|
|
128
|
+
const into = lastBlockIn(before);
|
|
129
|
+
if (!into)
|
|
107
130
|
return false;
|
|
131
|
+
if (!!into.type.spec.code === !!$from.parent.type.spec.code)
|
|
132
|
+
return joinTextblockBackward(state, dispatch, view);
|
|
133
|
+
if (dispatch) {
|
|
134
|
+
const at = $from.before() - 2;
|
|
135
|
+
const tr = state.tr.delete($from.before(), $from.after()).insert(at, $from.parent);
|
|
136
|
+
dispatch(tr.setSelection(TextSelection.create(tr.doc, at + 1)).scrollIntoView());
|
|
108
137
|
}
|
|
109
|
-
return
|
|
138
|
+
return true;
|
|
110
139
|
};
|
|
111
140
|
}
|
|
112
141
|
/** Whether the caret sits inside a `list_item` at any depth: a quote's paragraph
|
|
@@ -128,6 +157,10 @@ function inItem($pos, itemType) {
|
|
|
128
157
|
* The reflection carries the edit and not the caret, which stays where the key was
|
|
129
158
|
* pressed: a lift moves the item below, and Delete does not follow it.
|
|
130
159
|
*
|
|
160
|
+
* An empty item is the exception: its line is the whole of what Delete edits, so the
|
|
161
|
+
* item below moves up into it rather than being lifted, and the empty item is spent. A
|
|
162
|
+
* fence below takes no prose line (`joinOntoList`), so there the empty line goes alone.
|
|
163
|
+
*
|
|
131
164
|
* Declining wherever the chain does is what keeps this to the list's own seams: a
|
|
132
165
|
* boundary inside one item, and the list's outer edge, stay the base keymap's, where
|
|
133
166
|
* the two keys already answer alike.
|
|
@@ -142,13 +175,35 @@ function backspaceBelow(itemType, press) {
|
|
|
142
175
|
const below = Selection.near(state.doc.resolve($from.after()), 1);
|
|
143
176
|
if (below.from <= $from.pos)
|
|
144
177
|
return false;
|
|
178
|
+
if ($from.parent.content.size === 0) {
|
|
179
|
+
if (!!below.$from.parent.type.spec.code === !!$from.parent.type.spec.code)
|
|
180
|
+
return joinTextblockForward(state, dispatch);
|
|
181
|
+
if (dispatch) {
|
|
182
|
+
const tr = state.tr.deleteRange($from.before(), $from.after());
|
|
183
|
+
dispatch(tr.setSelection(Selection.near(tr.doc.resolve(tr.mapping.map(below.from)), 1)));
|
|
184
|
+
}
|
|
185
|
+
return true;
|
|
186
|
+
}
|
|
145
187
|
const pressed = $from.pos;
|
|
146
188
|
const keepCaret = (tr) => dispatch?.(tr.setSelection(TextSelection.create(tr.doc, tr.mapping.map(pressed))));
|
|
147
189
|
return press(EditorState.create({ doc: state.doc, selection: below }), dispatch && keepCaret);
|
|
148
190
|
};
|
|
149
191
|
}
|
|
192
|
+
/** Tab → `sinkListItem`, declined where the sink would nest past `MAX_NESTING_DEPTH`
|
|
193
|
+
* (`nesting.ts`), so Tab there leaves the body as it does on an item with nothing to
|
|
194
|
+
* sink under. */
|
|
195
|
+
function sinkWithin(itemType) {
|
|
196
|
+
const sink = sinkListItem(itemType);
|
|
197
|
+
return (state, dispatch) => {
|
|
198
|
+
let sunk;
|
|
199
|
+
if (!sink(state, (tr) => (sunk = tr)) || !sunk || !withinNesting(sunk.doc))
|
|
200
|
+
return false;
|
|
201
|
+
dispatch?.(sunk);
|
|
202
|
+
return true;
|
|
203
|
+
};
|
|
204
|
+
}
|
|
150
205
|
/**
|
|
151
|
-
* The list bindings for a block-schema leaf: `{}` for the
|
|
206
|
+
* The list bindings for a block-schema leaf: `{}` for the one-line and plain
|
|
152
207
|
* schemas, which declare no list nodes.
|
|
153
208
|
*
|
|
154
209
|
* `Tab` / `Shift-Tab` hold the list link of the body's Tab chain. Surfaces that own
|
|
@@ -160,11 +215,12 @@ function backspaceBelow(itemType, press) {
|
|
|
160
215
|
* shell structural keymap (VISUAL_EDITOR §Settled and open).
|
|
161
216
|
*
|
|
162
217
|
* `Enter` is a chain in precedence order: the escape-above gesture, then
|
|
163
|
-
* `splitListItem`,
|
|
218
|
+
* `splitListItem` (`splitTaskItem`, which leaves a task item's new half an open task),
|
|
219
|
+
* then `liftListItem`. The middle link carries two behaviors of
|
|
164
220
|
* its own: it splits a non-empty item, and on an empty item it either splits the
|
|
165
221
|
* wrapping item (a nested empty item, so Enter lifts exactly one level) or bails
|
|
166
|
-
* so the third link lifts a top-level empty item out to a paragraph. `Backspace`
|
|
167
|
-
*
|
|
222
|
+
* so the third link lifts a top-level empty item out to a paragraph. `Backspace` lifts
|
|
223
|
+
* an item at its start, and joins a block onto the list it stands after. `Delete` is
|
|
168
224
|
* that same chain at the seam below rather than a second table of cases beside it, so
|
|
169
225
|
* the two keys cannot come to differ over a seam this link reaches. An empty line is
|
|
170
226
|
* not one: the block link takes it ahead of this chain (`blocks.ts`), the caret's own
|
|
@@ -175,11 +231,11 @@ export function listKeymap(schema) {
|
|
|
175
231
|
const paragraph = schema.nodes.paragraph;
|
|
176
232
|
if (!itemType || !paragraph)
|
|
177
233
|
return {};
|
|
178
|
-
const backspace = chainCommands(
|
|
234
|
+
const backspace = chainCommands(liftAtItemStart(itemType), joinOntoList(itemType));
|
|
179
235
|
return {
|
|
180
|
-
Tab:
|
|
236
|
+
Tab: sinkWithin(itemType),
|
|
181
237
|
'Shift-Tab': liftListItem(itemType),
|
|
182
|
-
Enter: chainCommands(paragraphAboveList(itemType, paragraph),
|
|
238
|
+
Enter: chainCommands(paragraphAboveList(itemType, paragraph), splitTaskItem(itemType), liftListItem(itemType)),
|
|
183
239
|
Backspace: backspace,
|
|
184
240
|
Delete: backspaceBelow(itemType, backspace)
|
|
185
241
|
};
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { Node as PMNode } from 'prosemirror-model';
|
|
2
|
+
import { Plugin } from 'prosemirror-state';
|
|
3
|
+
/** The store's bound on the containers around one line: a commit nesting past it is
|
|
4
|
+
* refused, and `@quillmark/wasm` exports no constant to read it from. */
|
|
5
|
+
export declare const MAX_NESTING_DEPTH = 12;
|
|
6
|
+
/** The most containers any block in `doc` stands inside, counted as the store counts
|
|
7
|
+
* them: a `list_item` or a `blockquote` is one, the list around an item none, and no
|
|
8
|
+
* node holds an element (`decode.ts`). */
|
|
9
|
+
export declare function nestingDepth(doc: PMNode): number;
|
|
10
|
+
/** Whether `doc` nests within {@link MAX_NESTING_DEPTH}: what a command that nests asks
|
|
11
|
+
* of the doc it would dispatch. */
|
|
12
|
+
export declare function withinNesting(doc: PMNode): boolean;
|
|
13
|
+
/**
|
|
14
|
+
* Refuses a transaction leaving `doc` past {@link MAX_NESTING_DEPTH}, which the commit
|
|
15
|
+
* would lose: a paste or a drop into a deep item, whose slice the import bounded alone.
|
|
16
|
+
* The nesting commands ask first and keep their key; this is what reaches the rest, and
|
|
17
|
+
* a refused paste lands nothing. Past the bound, a transaction passes where it leaves the
|
|
18
|
+
* deepest level no deeper, so a body that arrived that deep keeps its Enter.
|
|
19
|
+
*/
|
|
20
|
+
export declare function nestingPlugin(): Plugin;
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { Plugin } from 'prosemirror-state';
|
|
2
|
+
import { ReplaceAroundStep, ReplaceStep } from 'prosemirror-transform';
|
|
3
|
+
/** The store's bound on the containers around one line: a commit nesting past it is
|
|
4
|
+
* refused, and `@quillmark/wasm` exports no constant to read it from. */
|
|
5
|
+
export const MAX_NESTING_DEPTH = 12;
|
|
6
|
+
/** The most containers any block in `doc` stands inside, counted as the store counts
|
|
7
|
+
* them: a `list_item` or a `blockquote` is one, the list around an item none, and no
|
|
8
|
+
* node holds an element (`decode.ts`). */
|
|
9
|
+
export function nestingDepth(doc) {
|
|
10
|
+
const walk = (node, depth) => {
|
|
11
|
+
let deepest = depth;
|
|
12
|
+
node.forEach((child) => {
|
|
13
|
+
if (child.isTextblock || child.isAtom)
|
|
14
|
+
return;
|
|
15
|
+
const name = child.type.name;
|
|
16
|
+
const d = depth + (name === 'list_item' || name === 'blockquote' ? 1 : 0);
|
|
17
|
+
deepest = Math.max(deepest, walk(child, d));
|
|
18
|
+
});
|
|
19
|
+
return deepest;
|
|
20
|
+
};
|
|
21
|
+
return walk(doc, 0);
|
|
22
|
+
}
|
|
23
|
+
/** Whether `doc` nests within {@link MAX_NESTING_DEPTH}: what a command that nests asks
|
|
24
|
+
* of the doc it would dispatch. */
|
|
25
|
+
export function withinNesting(doc) {
|
|
26
|
+
return nestingDepth(doc) <= MAX_NESTING_DEPTH;
|
|
27
|
+
}
|
|
28
|
+
/** Whether a step of `tr` can move a block under another container: any replace but
|
|
29
|
+
* inline content inside one textblock, which is every keystroke's. Mark and attribute
|
|
30
|
+
* steps move no node. */
|
|
31
|
+
function mayNest(tr) {
|
|
32
|
+
return tr.steps.some((step, i) => {
|
|
33
|
+
if (step instanceof ReplaceAroundStep)
|
|
34
|
+
return true;
|
|
35
|
+
if (!(step instanceof ReplaceStep))
|
|
36
|
+
return false;
|
|
37
|
+
const doc = tr.docs[i];
|
|
38
|
+
const $from = doc.resolve(step.from);
|
|
39
|
+
if (!$from.parent.isTextblock || !$from.sameParent(doc.resolve(step.to)))
|
|
40
|
+
return true;
|
|
41
|
+
let block = false;
|
|
42
|
+
step.slice.content.forEach((node) => (block ||= node.isBlock));
|
|
43
|
+
return block;
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Refuses a transaction leaving `doc` past {@link MAX_NESTING_DEPTH}, which the commit
|
|
48
|
+
* would lose: a paste or a drop into a deep item, whose slice the import bounded alone.
|
|
49
|
+
* The nesting commands ask first and keep their key; this is what reaches the rest, and
|
|
50
|
+
* a refused paste lands nothing. Past the bound, a transaction passes where it leaves the
|
|
51
|
+
* deepest level no deeper, so a body that arrived that deep keeps its Enter.
|
|
52
|
+
*/
|
|
53
|
+
export function nestingPlugin() {
|
|
54
|
+
return new Plugin({
|
|
55
|
+
filterTransaction: (tr) => {
|
|
56
|
+
if (!tr.docChanged || !mayNest(tr))
|
|
57
|
+
return true;
|
|
58
|
+
const after = nestingDepth(tr.doc);
|
|
59
|
+
return after <= MAX_NESTING_DEPTH || after <= nestingDepth(tr.before);
|
|
60
|
+
}
|
|
61
|
+
});
|
|
62
|
+
}
|
|
@@ -29,6 +29,14 @@ li.ProseMirror-selectednode::after {
|
|
|
29
29
|
border: var(--_qm-ring);
|
|
30
30
|
left: calc(var(--_qm-space-4) * -1);
|
|
31
31
|
}
|
|
32
|
+
/* A node selection is drawn while its leaf holds the focus, as a text selection is: a
|
|
33
|
+
ring left behind on a field the focus has moved off names a subject no key reaches. */
|
|
34
|
+
.ProseMirror:not(:focus-within) .ProseMirror-selectednode {
|
|
35
|
+
outline: none;
|
|
36
|
+
}
|
|
37
|
+
.ProseMirror:not(:focus-within) li.ProseMirror-selectednode::after {
|
|
38
|
+
border: none;
|
|
39
|
+
}
|
|
32
40
|
|
|
33
41
|
/* The gap cursor (`prosemirror-gapcursor`, imported beside this file): a caret
|
|
34
42
|
between two blocks, where the document holds no text position for an ordinary one.
|
|
@@ -140,6 +148,20 @@ li.ProseMirror-selectednode::after {
|
|
|
140
148
|
font-size: inherit;
|
|
141
149
|
}
|
|
142
150
|
|
|
151
|
+
/* A fence's language at the label rung in its top corner, read off the `data-lang` the
|
|
152
|
+
node writes (`schema.ts`). Generated content, so it is never text the caret reaches
|
|
153
|
+
or a copy takes; the code's lines wrap around it. Read-only: the language is changed
|
|
154
|
+
in source mode. */
|
|
155
|
+
.ProseMirror pre[data-lang]::before {
|
|
156
|
+
content: attr(data-lang);
|
|
157
|
+
float: inline-end;
|
|
158
|
+
margin-inline-start: var(--_qm-space-3);
|
|
159
|
+
font-size: var(--_qm-text-label);
|
|
160
|
+
color: var(--_qm-ink-label);
|
|
161
|
+
user-select: none;
|
|
162
|
+
pointer-events: none;
|
|
163
|
+
}
|
|
164
|
+
|
|
143
165
|
/* A link takes the surface's ink (the UA's blue is the `#8cf` defect again) and a
|
|
144
166
|
dotted underline. Dotted rather than solid because `underline` is a mark in the
|
|
145
167
|
same schema, and a solid rule renders an authored `<u>` and a link identically in a
|
|
@@ -183,6 +205,59 @@ li.ProseMirror-selectednode::after {
|
|
|
183
205
|
list-style: revert;
|
|
184
206
|
}
|
|
185
207
|
|
|
208
|
+
/* A task item's checkbox (`tasks.ts`) stands after the item's marker and ahead of its
|
|
209
|
+
first line, the text wrapping back under it, as the paper sets the box. Its face is a
|
|
210
|
+
matrix tick's: the input drawn `appearance: none` as an edged well, held at the
|
|
211
|
+
accent, the check glyph over it in the surface ink. A disabled box is a held leaf's
|
|
212
|
+
or a copy's, and takes no press. */
|
|
213
|
+
.ProseMirror .qm-task {
|
|
214
|
+
position: relative;
|
|
215
|
+
float: left;
|
|
216
|
+
display: flex;
|
|
217
|
+
align-items: center;
|
|
218
|
+
height: 1lh;
|
|
219
|
+
margin-right: var(--_qm-space-2);
|
|
220
|
+
}
|
|
221
|
+
.ProseMirror .qm-task-box {
|
|
222
|
+
appearance: none;
|
|
223
|
+
margin: 0;
|
|
224
|
+
width: var(--_qm-glyph-control);
|
|
225
|
+
height: var(--_qm-glyph-control);
|
|
226
|
+
border: var(--_qm-border-width) solid var(--_qm-border);
|
|
227
|
+
border-radius: calc(var(--_qm-radius-inner) / 2);
|
|
228
|
+
background: var(--_qm-surface-well);
|
|
229
|
+
cursor: pointer;
|
|
230
|
+
transition:
|
|
231
|
+
background-color var(--_qm-duration-fast) var(--_qm-ease-reverse),
|
|
232
|
+
border-color var(--_qm-duration-fast) var(--_qm-ease-reverse);
|
|
233
|
+
}
|
|
234
|
+
.ProseMirror .qm-task-box:checked {
|
|
235
|
+
background: var(--_qm-accent);
|
|
236
|
+
border-color: var(--_qm-accent);
|
|
237
|
+
}
|
|
238
|
+
.ProseMirror .qm-task-box:disabled {
|
|
239
|
+
cursor: default;
|
|
240
|
+
}
|
|
241
|
+
.ProseMirror .qm-task-box:focus-visible {
|
|
242
|
+
outline: var(--_qm-ring);
|
|
243
|
+
outline-offset: var(--_qm-ring-offset);
|
|
244
|
+
}
|
|
245
|
+
.ProseMirror .qm-task-mark {
|
|
246
|
+
position: absolute;
|
|
247
|
+
top: 50%;
|
|
248
|
+
left: 0;
|
|
249
|
+
width: var(--_qm-glyph-control);
|
|
250
|
+
height: var(--_qm-glyph-control);
|
|
251
|
+
translate: 0 -50%;
|
|
252
|
+
pointer-events: none;
|
|
253
|
+
color: var(--_qm-surface);
|
|
254
|
+
opacity: 0;
|
|
255
|
+
transition: opacity var(--_qm-duration-fast) var(--_qm-ease-reverse);
|
|
256
|
+
}
|
|
257
|
+
.ProseMirror .qm-task:has(:checked) .qm-task-mark {
|
|
258
|
+
opacity: 1;
|
|
259
|
+
}
|
|
260
|
+
|
|
186
261
|
/* Six levels, one weight, no size step. The quill reads no level at all, so a size
|
|
187
262
|
ramp here would be the editor predicting a hierarchy the paper does not set; the
|
|
188
263
|
level stays in the model, which is where the codec round-trips it. It is also all
|
|
@@ -227,9 +302,12 @@ li.ProseMirror-selectednode::after {
|
|
|
227
302
|
empty line the caret holds. Only while the leaf has the focus: the decoration follows
|
|
228
303
|
the selection, which an unfocused leaf keeps. Upright, being chrome about the key
|
|
229
304
|
rather than an invitation in the document's voice, and never over a ghost, which is
|
|
230
|
-
the leaf's own word for the same empty line.
|
|
305
|
+
the leaf's own word for the same empty line. It comes up once the caret has rested
|
|
306
|
+
there, a new empty line starting the wait over. */
|
|
231
307
|
.ProseMirror-focused .qm-slash-hint:not(.qm-prose-placeholder)[data-slash-hint]::after {
|
|
232
308
|
content: attr(data-slash-hint);
|
|
309
|
+
animation: qm-slash-hint-in var(--_qm-reveal) both;
|
|
310
|
+
animation-delay: var(--_qm-hint-wait);
|
|
233
311
|
display: block;
|
|
234
312
|
margin-top: -1lh;
|
|
235
313
|
white-space: nowrap;
|
|
@@ -239,6 +317,12 @@ li.ProseMirror-selectednode::after {
|
|
|
239
317
|
pointer-events: none;
|
|
240
318
|
}
|
|
241
319
|
|
|
320
|
+
@keyframes qm-slash-hint-in {
|
|
321
|
+
from {
|
|
322
|
+
opacity: 0;
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
|
|
242
326
|
/* ── The table island ────────────────────────────────────────────────────────
|
|
243
327
|
|
|
244
328
|
The one construct in this file with chrome in it. The rest of the stylesheet
|
|
@@ -313,19 +397,16 @@ li.ProseMirror-selectednode::after {
|
|
|
313
397
|
.qm-table {
|
|
314
398
|
border-collapse: separate;
|
|
315
399
|
border-spacing: 0;
|
|
316
|
-
border: var(--_qm-border-width) solid var(--_qm-
|
|
317
|
-
border-radius: var(--_qm-radius
|
|
400
|
+
border: var(--_qm-border-width) solid var(--_qm-rule);
|
|
401
|
+
border-radius: var(--_qm-radius);
|
|
318
402
|
}
|
|
319
|
-
/*
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
rather than as a stack of boxes. `separate` with zero spacing rather than
|
|
323
|
-
`collapse`: a collapsed border cannot be rounded, and the two tiers would fight
|
|
324
|
-
over which one wins an edge. */
|
|
403
|
+
/* One rule at one strength, the frame's and every line between cells, so the grid
|
|
404
|
+
reads at the weight of the type it holds. `separate` with zero spacing rather than
|
|
405
|
+
`collapse`: a collapsed border cannot be rounded. */
|
|
325
406
|
.qm-table-cell {
|
|
326
407
|
position: relative;
|
|
327
|
-
border-right: var(--_qm-border-width) solid var(--_qm-
|
|
328
|
-
border-bottom: var(--_qm-border-width) solid var(--_qm-
|
|
408
|
+
border-right: var(--_qm-border-width) solid var(--_qm-rule);
|
|
409
|
+
border-bottom: var(--_qm-border-width) solid var(--_qm-rule);
|
|
329
410
|
padding: var(--_qm-space) var(--_qm-space-2);
|
|
330
411
|
vertical-align: top;
|
|
331
412
|
font-weight: inherit;
|
|
@@ -339,13 +420,29 @@ li.ProseMirror-selectednode::after {
|
|
|
339
420
|
}
|
|
340
421
|
/* The header row is the one weight distinction the island draws: `header` is a
|
|
341
422
|
separate field in the model rather than a flag on a row, so the surface says so.
|
|
342
|
-
It takes a tone with it,
|
|
343
|
-
|
|
344
|
-
rather than a shadow. The rest of a table's type is paper's. */
|
|
423
|
+
It takes a tone with it, the band rung, a step toward the ink at both poles. The
|
|
424
|
+
rest of a table's type is paper's. */
|
|
345
425
|
.qm-table-header-row .qm-table-cell {
|
|
346
426
|
font-weight: var(--_qm-weight-strong);
|
|
347
|
-
background: var(--_qm-surface-
|
|
348
|
-
|
|
427
|
+
background: var(--_qm-surface-band);
|
|
428
|
+
}
|
|
429
|
+
/* The corner cells take the frame's corner, inside its stroke, so a header's band or a
|
|
430
|
+
selection's wash keeps to the rounded edge rather than painting a square one over
|
|
431
|
+
it. The first row is the colgroup's next sibling's, whichever section holds it. */
|
|
432
|
+
.qm-table > colgroup + * > tr:first-child > .qm-table-cell:first-child {
|
|
433
|
+
border-start-start-radius: calc(var(--_qm-radius) - var(--_qm-border-width));
|
|
434
|
+
}
|
|
435
|
+
.qm-table > colgroup + * > tr:first-child > .qm-table-cell:last-child {
|
|
436
|
+
border-start-end-radius: calc(var(--_qm-radius) - var(--_qm-border-width));
|
|
437
|
+
}
|
|
438
|
+
.qm-table
|
|
439
|
+
> :is(:last-child, thead:has(+ tbody:empty))
|
|
440
|
+
> tr:last-child
|
|
441
|
+
> .qm-table-cell:first-child {
|
|
442
|
+
border-end-start-radius: calc(var(--_qm-radius) - var(--_qm-border-width));
|
|
443
|
+
}
|
|
444
|
+
.qm-table > :is(:last-child, thead:has(+ tbody:empty)) > tr:last-child > .qm-table-cell:last-child {
|
|
445
|
+
border-end-end-radius: calc(var(--_qm-radius) - var(--_qm-border-width));
|
|
349
446
|
}
|
|
350
447
|
/* A cell holds a whole nested view. Its measure floor keeps an empty table from
|
|
351
448
|
collapsing to a row of hairlines with nothing to aim at, and the caret is the
|
|
@@ -400,20 +497,16 @@ li.ProseMirror-selectednode::after {
|
|
|
400
497
|
color: var(--_qm-ink);
|
|
401
498
|
cursor: pointer;
|
|
402
499
|
}
|
|
403
|
-
/* The
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
One tone, not a hover ladder: the control is drawn only while it is the one the
|
|
408
|
-
pointer is on or the one a gesture is about, so a resting tone under that would be a
|
|
409
|
-
rung nothing ever reaches. The selected grip's tint below is the one exception, and it
|
|
410
|
-
says which line the next command acts on rather than where the pointer is. */
|
|
500
|
+
/* The pill every one of them draws, a rhythm rung thick where the target is the tap
|
|
501
|
+
floor. Stated once, so a grip and an add bar cannot drift into two shapes. The selected
|
|
502
|
+
grip's tint below says which line the next command acts on rather than where the
|
|
503
|
+
pointer is. */
|
|
411
504
|
:is(.qm-table-grip-bar, .qm-table-add-bar) {
|
|
412
505
|
display: flex;
|
|
413
506
|
align-items: center;
|
|
414
507
|
justify-content: center;
|
|
415
508
|
border-radius: var(--_qm-radius-pill);
|
|
416
|
-
background: var(--_qm-
|
|
509
|
+
background: var(--_qm-surface-band);
|
|
417
510
|
transition:
|
|
418
511
|
background-color var(--_qm-duration-fast) var(--_qm-ease-reverse),
|
|
419
512
|
color var(--_qm-duration-fast) var(--_qm-ease-reverse);
|
|
@@ -424,32 +517,34 @@ li.ProseMirror-selectednode::after {
|
|
|
424
517
|
width: var(--_qm-space-3);
|
|
425
518
|
height: var(--_qm-space-3);
|
|
426
519
|
}
|
|
427
|
-
/*
|
|
428
|
-
|
|
429
|
-
is the one thing the rule above cannot share with them: a column's grip is as wide as
|
|
430
|
-
its column, and the bar that adds a column is as tall as the table. */
|
|
431
|
-
.qm-table-grip[data-axis='column'] .qm-table-grip-bar,
|
|
520
|
+
/* An add bar runs across the axis it appends to, the length of the table; a grip is a
|
|
521
|
+
short pill centred on the line it names, the tap floor long. */
|
|
432
522
|
.qm-table-add[data-axis='row'] .qm-table-add-bar {
|
|
433
523
|
width: 100%;
|
|
434
524
|
height: var(--_qm-space-3);
|
|
435
525
|
}
|
|
436
|
-
.qm-table-grip[data-axis='row'] .qm-table-grip-bar,
|
|
437
526
|
.qm-table-add[data-axis='column'] .qm-table-add-bar {
|
|
438
527
|
width: var(--_qm-space-3);
|
|
439
528
|
height: 100%;
|
|
440
529
|
}
|
|
530
|
+
.qm-table-grip[data-axis='column'] .qm-table-grip-bar {
|
|
531
|
+
width: var(--_qm-tap-min);
|
|
532
|
+
height: var(--_qm-space-3);
|
|
533
|
+
}
|
|
534
|
+
.qm-table-grip[data-axis='row'] .qm-table-grip-bar {
|
|
535
|
+
width: var(--_qm-space-3);
|
|
536
|
+
height: var(--_qm-tap-min);
|
|
537
|
+
}
|
|
441
538
|
|
|
442
|
-
/* One grip per line
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
you can pick up has everywhere else — so the control reads as draggable before it is
|
|
446
|
-
dragged.
|
|
539
|
+
/* One grip per line. The pill carries the grip's own marks at the label ink — two rows
|
|
540
|
+
of three, the shape a thing you can pick up has everywhere else — so the control reads
|
|
541
|
+
as draggable before it is dragged.
|
|
447
542
|
|
|
448
|
-
The target is the band and the
|
|
449
|
-
axis and the tap floor on the other
|
|
450
|
-
|
|
451
|
-
the pointer has to find. */
|
|
543
|
+
The target is the band and the pill is drawn inside it: a grip spans its line on one
|
|
544
|
+
axis and the tap floor on the other. A grip sized to its own pill would be a target
|
|
545
|
+
the size of the mark on it, on a control the pointer has to find. */
|
|
452
546
|
.qm-table-grip {
|
|
547
|
+
color: var(--_qm-ink-label);
|
|
453
548
|
/* The pointer owns this gesture, not the page's scroll: a grip drags. */
|
|
454
549
|
touch-action: none;
|
|
455
550
|
}
|
|
@@ -488,37 +583,24 @@ li.ProseMirror-selectednode::after {
|
|
|
488
583
|
height: var(--_qm-tap-min);
|
|
489
584
|
}
|
|
490
585
|
/* The reveal is the control's own (CODEC §"The table island"): one comes up while the
|
|
491
|
-
pointer is on that control, while it
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
the one line the pointer has picked out.
|
|
586
|
+
pointer is on that control, or while it is what a gesture is about — the selected line's grip, and the lifted one, which is drawn through a drag the
|
|
587
|
+
pointer has travelled off. A cell under the pointer draws its row's and its column's
|
|
588
|
+
grips (`table-view.ts` §`onHover`), one pill on each axis and none anywhere else.
|
|
495
589
|
|
|
496
590
|
Opacity rather than absence, so every control keeps its box and is hit-tested wherever
|
|
497
|
-
it sits. A grip's reach
|
|
498
|
-
focus rung: what a keyboard draws a line with is the cell the
|
|
499
|
-
(`table-view.ts` §`cellKeys`), and the wash and the selected
|
|
500
|
-
|
|
501
|
-
on them, so the rung there draws one a keyboard has reached. */
|
|
591
|
+
it sits. A grip's reach and a bar's are the pointer's alone and neither holds focus, so
|
|
592
|
+
neither carries a focus rung: what a keyboard draws a line with is the cell the
|
|
593
|
+
selection runs through (`table-view.ts` §`cellKeys`), and the wash and the selected
|
|
594
|
+
grip's tint are what say so. */
|
|
502
595
|
:is(.qm-table-grip, .qm-table-add) {
|
|
503
596
|
opacity: 0;
|
|
504
597
|
transition: opacity var(--_qm-duration-fast) var(--_qm-ease-reverse);
|
|
505
598
|
}
|
|
506
|
-
.qm-table-add:
|
|
507
|
-
.qm-table-grip:is(:hover, [aria-pressed='true'], .qm-table-lifted) {
|
|
599
|
+
.qm-table-add:hover,
|
|
600
|
+
.qm-table-grip:is([data-near], :hover, [aria-pressed='true'], .qm-table-lifted) {
|
|
508
601
|
opacity: 1;
|
|
509
602
|
}
|
|
510
603
|
|
|
511
|
-
/* The ring, on the two add bars and nowhere else in this file. A prose leaf's focus
|
|
512
|
-
indicator is its caret and a cell's is too, so the outline is dropped there; a control
|
|
513
|
-
that comes up out of nothing has the reveal, and a ring under a keyboard is what says
|
|
514
|
-
the focus is there rather than on the paper behind it. Declared here rather than
|
|
515
|
-
reached for as `.qm-focus-ring`: that recipe is the visual tier's stylesheet, and the
|
|
516
|
-
island is inside the leaf, which is the codec's. */
|
|
517
|
-
.qm-table-add:focus-visible {
|
|
518
|
-
outline: var(--_qm-ring);
|
|
519
|
-
outline-offset: var(--_qm-ring-offset);
|
|
520
|
-
}
|
|
521
|
-
|
|
522
604
|
/* A weighted table spans the leaf, laid out fixed, so the stored weights are the
|
|
523
605
|
proportions its columns draw, as the page's fractional columns span the text block
|
|
524
606
|
(`table-view.ts` §`weigh`). An auto-fit table keeps its content's width. */
|
|
@@ -582,7 +664,7 @@ li.ProseMirror-selectednode::after {
|
|
|
582
664
|
display: none;
|
|
583
665
|
}
|
|
584
666
|
/* The header toggle hangs under the first row's last cell, flush with the grid's
|
|
585
|
-
trailing edge
|
|
667
|
+
trailing edge. */
|
|
586
668
|
.qm-table-align[data-header] {
|
|
587
669
|
margin-inline: auto 0;
|
|
588
670
|
}
|
|
@@ -23,7 +23,7 @@ export declare const plaintextSchema: Schema<string, never>;
|
|
|
23
23
|
*/
|
|
24
24
|
export declare const cellSchema: Schema<"hard_break", string>;
|
|
25
25
|
/**
|
|
26
|
-
* A `
|
|
26
|
+
* A `text` field declaring `multiline: true`: paragraphs and hard breaks, no marks, no
|
|
27
27
|
* islands, no containers, which is the shape `Content::is_plain` admits. Mark-free
|
|
28
28
|
* for the reason {@link plaintextSchema} is.
|
|
29
29
|
*
|
|
@@ -38,7 +38,7 @@ export declare function leafSchema(opts: {
|
|
|
38
38
|
plaintext?: boolean;
|
|
39
39
|
inline?: boolean;
|
|
40
40
|
}): Schema;
|
|
41
|
-
/** Whether `schema` can carry formatting at all: false for the two
|
|
41
|
+
/** Whether `schema` can carry formatting at all: false for the two mark-free schemas.
|
|
42
42
|
* What a mark command asks before it offers itself. */
|
|
43
43
|
export declare function hasMarks(schema: Schema): boolean;
|
|
44
44
|
/** True for the one-line schemas, which declare no `hard_break`; decode branches on it. */
|