@quillmark/svelte 0.6.0 → 0.7.1
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/THEMING.md +1 -1
- package/dist/core/codec/field.d.ts +15 -4
- package/dist/core/codec/field.js +13 -10
- package/dist/core/codec/index.d.ts +1 -1
- package/dist/core/codec/index.js +1 -1
- package/dist/core/codec/table-view.d.ts +4 -0
- package/dist/core/codec/table-view.js +10 -1
- package/dist/core/theme.css +14 -9
- package/dist/preset/recipes.css +15 -4
- package/dist/preview/bridge.d.ts +23 -0
- package/dist/preview/bridge.js +92 -35
- package/dist/preview/controller.d.ts +6 -5
- package/dist/preview/controller.js +13 -7
- package/dist/preview/index.js +3 -2
- package/dist/preview/paint.js +9 -1
- package/dist/visual/ArrayField.svelte +30 -23
- package/dist/visual/ArrayField.svelte.d.ts +9 -7
- package/dist/visual/Card.svelte +88 -45
- package/dist/visual/EnumField.svelte +4 -2
- package/dist/visual/Field.svelte +22 -2
- package/dist/visual/ObjectField.svelte +95 -75
- package/dist/visual/ObjectField.svelte.d.ts +7 -3
- package/dist/visual/ProseValue.svelte +165 -0
- package/dist/visual/ProseValue.svelte.d.ts +33 -0
- package/dist/visual/SlashMenu.svelte +7 -0
- package/dist/visual/VariantField.svelte +11 -6
- package/dist/visual/VariantField.svelte.d.ts +5 -1
- package/dist/visual/VisualEditorInner.svelte +29 -11
- package/dist/visual/hold.d.ts +17 -0
- package/dist/visual/hold.js +39 -0
- package/dist/visual/index.js +1 -1
- package/dist/visual/motion.d.ts +9 -0
- package/dist/visual/motion.js +10 -2
- package/dist/visual/strings.d.ts +4 -3
- package/package.json +2 -2
- package/dist/visual/ProseArrayElement.svelte +0 -139
- package/dist/visual/ProseArrayElement.svelte.d.ts +0 -25
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Run `change` and keep `anchor` inside the fold for the length of the move.
|
|
3
|
+
*
|
|
4
|
+
* A section closing above the pressed control carries that control off the fold with it,
|
|
5
|
+
* and what answers that is the trip every landing here already takes: the minimum, to the
|
|
6
|
+
* nearest edge, never a centring (VISUAL_EDITOR §"Focus and the preview bridge"). So a
|
|
7
|
+
* control the collapse never carries out of view costs no scroll at all, and one it would
|
|
8
|
+
* rides up and parks at the edge, which is where a section just opened wants its header:
|
|
9
|
+
* the fold below it is the room its fields unfold into, where a control held on the line
|
|
10
|
+
* it was pressed on opens its section into the screen it sits on the edge of.
|
|
11
|
+
*
|
|
12
|
+
* Per frame, because both panels move over the motion rung: a trip taken once is taken
|
|
13
|
+
* against a track that has not moved yet, the same flush a reveal's landing measures in
|
|
14
|
+
* (`Card.revealLeaf`). `instant`, because a host's `scroll-behavior: smooth` would
|
|
15
|
+
* otherwise animate each frame's correction toward the one before it.
|
|
16
|
+
*/
|
|
17
|
+
export declare function holdInView(anchor: HTMLElement | undefined, change: () => void): void;
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
// The gesture's anchor, which scroll anchoring is not: the platform picks its anchor off
|
|
2
|
+
// the layout, and what fills the viewport as a section collapses is that section's own
|
|
3
|
+
// content, whose top never moves.
|
|
4
|
+
import { flushSync } from 'svelte';
|
|
5
|
+
import { rungMs } from './motion.js';
|
|
6
|
+
/**
|
|
7
|
+
* Run `change` and keep `anchor` inside the fold for the length of the move.
|
|
8
|
+
*
|
|
9
|
+
* A section closing above the pressed control carries that control off the fold with it,
|
|
10
|
+
* and what answers that is the trip every landing here already takes: the minimum, to the
|
|
11
|
+
* nearest edge, never a centring (VISUAL_EDITOR §"Focus and the preview bridge"). So a
|
|
12
|
+
* control the collapse never carries out of view costs no scroll at all, and one it would
|
|
13
|
+
* rides up and parks at the edge, which is where a section just opened wants its header:
|
|
14
|
+
* the fold below it is the room its fields unfold into, where a control held on the line
|
|
15
|
+
* it was pressed on opens its section into the screen it sits on the edge of.
|
|
16
|
+
*
|
|
17
|
+
* Per frame, because both panels move over the motion rung: a trip taken once is taken
|
|
18
|
+
* against a track that has not moved yet, the same flush a reveal's landing measures in
|
|
19
|
+
* (`Card.revealLeaf`). `instant`, because a host's `scroll-behavior: smooth` would
|
|
20
|
+
* otherwise animate each frame's correction toward the one before it.
|
|
21
|
+
*/
|
|
22
|
+
export function holdInView(anchor, change) {
|
|
23
|
+
if (!anchor)
|
|
24
|
+
return void change();
|
|
25
|
+
change();
|
|
26
|
+
flushSync();
|
|
27
|
+
const reveal = () => anchor.scrollIntoView({ block: 'nearest', behavior: 'instant' });
|
|
28
|
+
reveal();
|
|
29
|
+
const rung = rungMs(getComputedStyle(anchor), '--_qm-duration-slow');
|
|
30
|
+
if (rung === undefined)
|
|
31
|
+
return;
|
|
32
|
+
const until = performance.now() + rung;
|
|
33
|
+
const step = (now) => {
|
|
34
|
+
reveal();
|
|
35
|
+
if (now < until)
|
|
36
|
+
requestAnimationFrame(step);
|
|
37
|
+
};
|
|
38
|
+
requestAnimationFrame(step);
|
|
39
|
+
}
|
package/dist/visual/index.js
CHANGED
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
// reaches modules inside `core/` and never its entry, so a sheet hanging off that
|
|
14
14
|
// entry arrives only for a consumer importing `/core` for some other reason. The
|
|
15
15
|
// sheet rather than the entry, because the barrel needs the derivation and not
|
|
16
|
-
// `init`. `check:deps`
|
|
16
|
+
// `init`. `check:deps` holds the reach.
|
|
17
17
|
import '../core/theme.css';
|
|
18
18
|
export { default as VisualEditor } from './VisualEditor.svelte';
|
|
19
19
|
// The prose leaf: the vanilla mount API a non-Svelte consumer (or a custom
|
package/dist/visual/motion.d.ts
CHANGED
|
@@ -1,4 +1,13 @@
|
|
|
1
1
|
import type { AnimationConfig } from 'svelte/animate';
|
|
2
|
+
/** The trips this module is running on `node`. A run holds the slot at its pre-move
|
|
3
|
+
* position for its whole length, so anything that measures the node's box waits on
|
|
4
|
+
* these rather than reading the one a transform is holding. */
|
|
5
|
+
export declare function reorderTrips(node: HTMLElement): Animation[];
|
|
6
|
+
/** A duration rung off `style`, in ms; `undefined` where the derivation is out of reach
|
|
7
|
+
* (an unstyled root, or jsdom, where `getComputedStyle` reports custom properties as
|
|
8
|
+
* empty). No rung, no motion: a fallback here would be the scale restated in the one
|
|
9
|
+
* place `check:style` cannot read. */
|
|
10
|
+
export declare function rungMs(style: CSSStyleDeclaration, rung: string): number | undefined;
|
|
2
11
|
/**
|
|
3
12
|
* Slide a keyed slot from where it was to where it now is.
|
|
4
13
|
*
|
package/dist/visual/motion.js
CHANGED
|
@@ -7,11 +7,19 @@
|
|
|
7
7
|
/** Marks this module's runs, so a second reorder cancels the first rather than layering
|
|
8
8
|
* a second transform over it. */
|
|
9
9
|
const RUN_ID = 'qm-reorder';
|
|
10
|
+
/** The trips this module is running on `node`. A run holds the slot at its pre-move
|
|
11
|
+
* position for its whole length, so anything that measures the node's box waits on
|
|
12
|
+
* these rather than reading the one a transform is holding. */
|
|
13
|
+
export function reorderTrips(node) {
|
|
14
|
+
if (typeof node.getAnimations !== 'function')
|
|
15
|
+
return [];
|
|
16
|
+
return node.getAnimations().filter((run) => run.id === RUN_ID);
|
|
17
|
+
}
|
|
10
18
|
/** A duration rung off `style`, in ms; `undefined` where the derivation is out of reach
|
|
11
19
|
* (an unstyled root, or jsdom, where `getComputedStyle` reports custom properties as
|
|
12
20
|
* empty). No rung, no motion: a fallback here would be the scale restated in the one
|
|
13
21
|
* place `check:style` cannot read. */
|
|
14
|
-
function rungMs(style, rung) {
|
|
22
|
+
export function rungMs(style, rung) {
|
|
15
23
|
const raw = style.getPropertyValue(rung).trim();
|
|
16
24
|
const n = Number.parseFloat(raw);
|
|
17
25
|
if (!Number.isFinite(n))
|
|
@@ -34,7 +42,7 @@ function rungMs(style, rung) {
|
|
|
34
42
|
export function reorder(node, { from, to }, armed) {
|
|
35
43
|
// The returned config is empty by design, in both exits and the run below: Svelte
|
|
36
44
|
// samples a config's keyframes through a JS easing function, and this curve is a
|
|
37
|
-
// rung the derivation mints
|
|
45
|
+
// rung the derivation mints.
|
|
38
46
|
if (!armed() || typeof node.animate !== 'function')
|
|
39
47
|
return {};
|
|
40
48
|
const style = getComputedStyle(node);
|
package/dist/visual/strings.d.ts
CHANGED
|
@@ -25,9 +25,10 @@ export interface VisualStrings extends TableChromeStrings, SlashStrings {
|
|
|
25
25
|
* ghosts ({@link EnumField}). */
|
|
26
26
|
enumUnsetTag: string;
|
|
27
27
|
/**
|
|
28
|
-
* What stands where a subform declines to recurse
|
|
29
|
-
*
|
|
30
|
-
*
|
|
28
|
+
* What stands where a subform declines to recurse — a nested `array` or `object`
|
|
29
|
+
* property, whose own figure would nest a field past the depth this one draws —
|
|
30
|
+
* and where to edit that property instead. The whole sentence, since a translator
|
|
31
|
+
* orders the kind within it.
|
|
31
32
|
*/
|
|
32
33
|
nestedUnsupported: (kind: string) => string;
|
|
33
34
|
/**
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@quillmark/svelte",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.1",
|
|
4
4
|
"description": "Editor, live-preview and source surfaces over a Quillmark WASM session.",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -66,7 +66,7 @@
|
|
|
66
66
|
"check": "svelte-check --tsconfig ./tsconfig.json"
|
|
67
67
|
},
|
|
68
68
|
"peerDependencies": {
|
|
69
|
-
"@quillmark/wasm": ">=0.108.
|
|
69
|
+
"@quillmark/wasm": ">=0.108.1-0",
|
|
70
70
|
"svelte": "^5.0.0"
|
|
71
71
|
},
|
|
72
72
|
"dependencies": {
|
|
@@ -1,139 +0,0 @@
|
|
|
1
|
-
<!--
|
|
2
|
-
One element of an array of `richtext` (fixture `keywords`) or `plaintext`
|
|
3
|
-
(`errata`). Array elements are not `applyChange`-addressable: `Addr.field` is a
|
|
4
|
-
flat name, so `keywords[0]` has no op address. So
|
|
5
|
-
this is not a `createField` leaf: it mounts a minimal PM view over the codec's
|
|
6
|
-
decode/encode + inline schema, and on every edit hands the re-encoded
|
|
7
|
-
`Content` up to the parent {@link ArrayField}, which commits the whole array
|
|
8
|
-
by value (`writer.set(field, arrayWithElementReplaced)`). Anchors within an
|
|
9
|
-
element are dropped on that value write: acceptable for inline refs. Mounts
|
|
10
|
-
once per stable element id (no reset on the parent's re-derive).
|
|
11
|
-
|
|
12
|
-
The content is read, not passed: `reader.getContentAt(addr, [k])` decodes an
|
|
13
|
-
element through the codec its `items` type declares, so what the element rests
|
|
14
|
-
as — the content object, a `plaintext` literal, the authored string a transport
|
|
15
|
-
door left — stops being this row's business. Read once, at mount, since that is
|
|
16
|
-
when this leaf takes its state.
|
|
17
|
-
-->
|
|
18
|
-
<script lang="ts">
|
|
19
|
-
import { onMount } from 'svelte';
|
|
20
|
-
import { EditorState, Selection } from 'prosemirror-state';
|
|
21
|
-
import { EditorView } from 'prosemirror-view';
|
|
22
|
-
import {
|
|
23
|
-
decode,
|
|
24
|
-
pmToContent,
|
|
25
|
-
inlineSchema,
|
|
26
|
-
plaintextSchema,
|
|
27
|
-
proseLeafPlugins,
|
|
28
|
-
buildLineIndex,
|
|
29
|
-
usvToPM
|
|
30
|
-
} from '../core/codec/index.js';
|
|
31
|
-
import './controls.css';
|
|
32
|
-
import type { Content } from '@quillmark/wasm';
|
|
33
|
-
|
|
34
|
-
interface Props {
|
|
35
|
-
/** This element's content, read at mount (`ArrayField`, through the boundary's
|
|
36
|
-
* element read). A thunk rather than a value: the parent re-derives per
|
|
37
|
-
* revision and this leaf takes its state once, so a value prop would be a
|
|
38
|
-
* boundary read per row per render, all but one of them discarded. */
|
|
39
|
-
content: () => Content;
|
|
40
|
-
/** The mark-free schema (a `plaintext` item): literal text, no formatting,
|
|
41
|
-
* exactly as the scalar field of that type mounts. */
|
|
42
|
-
plaintext?: boolean;
|
|
43
|
-
/** Accessible name for the editable region (the array has no per-element label). */
|
|
44
|
-
label?: string;
|
|
45
|
-
onChange: (rt: Content) => void;
|
|
46
|
-
/** Raw keydown, for a container whose own keys run through this element: the
|
|
47
|
-
* array repeater's Enter/Backspace (`ArrayField`). Fires before the view's own
|
|
48
|
-
* keymap, so the state it reads is the one this keystroke has yet to change. */
|
|
49
|
-
onKey?: (e: KeyboardEvent) => void;
|
|
50
|
-
}
|
|
51
|
-
let { content, plaintext = false, label, onChange, onKey }: Props = $props();
|
|
52
|
-
|
|
53
|
-
let containerEl: HTMLDivElement | undefined = $state();
|
|
54
|
-
let view: EditorView | undefined;
|
|
55
|
-
/** Take the caret: what a parent placing focus on this element calls. The view's
|
|
56
|
-
* focus, not the element's: a PM view restores its selection, where a bare DOM
|
|
57
|
-
* focus on a contenteditable leaves the caret unplaced. And the reveal beside it,
|
|
58
|
-
* since PM's focus prevents the scroll a `string` element's input takes by
|
|
59
|
-
* default — an array whose two item types revealed differently is one row of it
|
|
60
|
-
* landing off screen. */
|
|
61
|
-
export function focus(): void {
|
|
62
|
-
if (!view) return;
|
|
63
|
-
view.focus();
|
|
64
|
-
view.dom.scrollIntoView({ block: 'nearest' });
|
|
65
|
-
}
|
|
66
|
-
/** Take the caret to USV `pos` in this element's own content: what a landing on an
|
|
67
|
-
* element address resolves to when the compile answered a cluster-exact offset
|
|
68
|
-
* (`leaves.ts`). `createField`'s body over the view this component owns.
|
|
69
|
-
*
|
|
70
|
-
* The index is built here rather than held: a landing is a discrete act, and one
|
|
71
|
-
* doc walk per click is cheaper than a cache to invalidate per keystroke. `usvToPM`
|
|
72
|
-
* clamps, so an offset past a value the compile has moved on from lands the end.
|
|
73
|
-
*
|
|
74
|
-
* `Selection.near`, not `TextSelection.create`: the mapped position can be
|
|
75
|
-
* non-inline. Focus first, then the caret, flagged for scroll — the order and the
|
|
76
|
-
* reasons are `FieldController.setCaret`'s (`codec/field.ts`). */
|
|
77
|
-
export function setCaret(pos: number): void {
|
|
78
|
-
if (!view) return;
|
|
79
|
-
const pm = usvToPM(buildLineIndex(view.state.doc), pos);
|
|
80
|
-
const sel = Selection.near(view.state.doc.resolve(pm));
|
|
81
|
-
view.focus();
|
|
82
|
-
view.dispatch(view.state.tr.setSelection(sel).scrollIntoView());
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
onMount(() => {
|
|
86
|
-
if (!containerEl) return;
|
|
87
|
-
// An array element is an inline, value-by-value leaf: the same keymap and
|
|
88
|
-
// plugin stack a `createField` leaf mounts (shared `proseLeafPlugins`), minus
|
|
89
|
-
// the anchor-position plugin (element anchors are dropped on the array's value
|
|
90
|
-
// write, per the header). Its own `dispatchTransaction` hands `Content` up.
|
|
91
|
-
//
|
|
92
|
-
// The schema is the declared type's, as `createField` picks it: `plaintext`
|
|
93
|
-
// declares no mark types, so there is nothing to toggle, to paste in, or to
|
|
94
|
-
// mint a shorthand with.
|
|
95
|
-
const schema = plaintext ? plaintextSchema : inlineSchema;
|
|
96
|
-
const pmDoc = decode(content(), schema);
|
|
97
|
-
const state = EditorState.create({
|
|
98
|
-
doc: pmDoc,
|
|
99
|
-
plugins: proseLeafPlugins(schema, { inline: true })
|
|
100
|
-
});
|
|
101
|
-
const mounted = new EditorView(containerEl, {
|
|
102
|
-
state,
|
|
103
|
-
// Names the `contenteditable` for assistive tech (the array row has no
|
|
104
|
-
// label element to associate).
|
|
105
|
-
attributes: label ? { 'aria-label': label } : undefined,
|
|
106
|
-
dispatchTransaction(tr) {
|
|
107
|
-
const next = mounted.state.apply(tr);
|
|
108
|
-
mounted.updateState(next);
|
|
109
|
-
if (tr.docChanged) onChange(pmToContent(next.doc));
|
|
110
|
-
},
|
|
111
|
-
handleDOMEvents: {
|
|
112
|
-
keydown: (_v, e) => {
|
|
113
|
-
onKey?.(e);
|
|
114
|
-
return false;
|
|
115
|
-
}
|
|
116
|
-
}
|
|
117
|
-
});
|
|
118
|
-
view = mounted;
|
|
119
|
-
return () => {
|
|
120
|
-
view = undefined;
|
|
121
|
-
mounted.destroy();
|
|
122
|
-
};
|
|
123
|
-
});
|
|
124
|
-
</script>
|
|
125
|
-
|
|
126
|
-
<!-- `.qm-control-box` (controls.css) is the whole box, so an array of `richtext` and
|
|
127
|
-
an array of `string` render rows of equal height. No floor: the reset in
|
|
128
|
-
`core/codec/prose.css` makes one line of prose measure one line. Width is the
|
|
129
|
-
row's to give: the element fills the track it is placed in (`ArrayField`). -->
|
|
130
|
-
<div bind:this={containerEl} class="qm-array-prose qm-control-box qm-focus-ring-within"></div>
|
|
131
|
-
|
|
132
|
-
<style>
|
|
133
|
-
/* Caret-primary, matching ProseField: the contenteditable's own outline is
|
|
134
|
-
dropped and the ring rides the wrapper (`qm-focus-ring-within`,
|
|
135
|
-
controls.css), which is where the box is. */
|
|
136
|
-
.qm-array-prose :global(.ProseMirror) {
|
|
137
|
-
outline: none;
|
|
138
|
-
}
|
|
139
|
-
</style>
|
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
import './controls.css';
|
|
2
|
-
import type { Content } from '@quillmark/wasm';
|
|
3
|
-
interface Props {
|
|
4
|
-
/** This element's content, read at mount (`ArrayField`, through the boundary's
|
|
5
|
-
* element read). A thunk rather than a value: the parent re-derives per
|
|
6
|
-
* revision and this leaf takes its state once, so a value prop would be a
|
|
7
|
-
* boundary read per row per render, all but one of them discarded. */
|
|
8
|
-
content: () => Content;
|
|
9
|
-
/** The mark-free schema (a `plaintext` item): literal text, no formatting,
|
|
10
|
-
* exactly as the scalar field of that type mounts. */
|
|
11
|
-
plaintext?: boolean;
|
|
12
|
-
/** Accessible name for the editable region (the array has no per-element label). */
|
|
13
|
-
label?: string;
|
|
14
|
-
onChange: (rt: Content) => void;
|
|
15
|
-
/** Raw keydown, for a container whose own keys run through this element: the
|
|
16
|
-
* array repeater's Enter/Backspace (`ArrayField`). Fires before the view's own
|
|
17
|
-
* keymap, so the state it reads is the one this keystroke has yet to change. */
|
|
18
|
-
onKey?: (e: KeyboardEvent) => void;
|
|
19
|
-
}
|
|
20
|
-
declare const ProseArrayElement: import("svelte").Component<Props, {
|
|
21
|
-
focus: () => void;
|
|
22
|
-
setCaret: (pos: number) => void;
|
|
23
|
-
}, "">;
|
|
24
|
-
type ProseArrayElement = ReturnType<typeof ProseArrayElement>;
|
|
25
|
-
export default ProseArrayElement;
|