@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.
Files changed (37) hide show
  1. package/THEMING.md +1 -1
  2. package/dist/core/codec/field.d.ts +15 -4
  3. package/dist/core/codec/field.js +13 -10
  4. package/dist/core/codec/index.d.ts +1 -1
  5. package/dist/core/codec/index.js +1 -1
  6. package/dist/core/codec/table-view.d.ts +4 -0
  7. package/dist/core/codec/table-view.js +10 -1
  8. package/dist/core/theme.css +14 -9
  9. package/dist/preset/recipes.css +15 -4
  10. package/dist/preview/bridge.d.ts +23 -0
  11. package/dist/preview/bridge.js +92 -35
  12. package/dist/preview/controller.d.ts +6 -5
  13. package/dist/preview/controller.js +13 -7
  14. package/dist/preview/index.js +3 -2
  15. package/dist/preview/paint.js +9 -1
  16. package/dist/visual/ArrayField.svelte +30 -23
  17. package/dist/visual/ArrayField.svelte.d.ts +9 -7
  18. package/dist/visual/Card.svelte +88 -45
  19. package/dist/visual/EnumField.svelte +4 -2
  20. package/dist/visual/Field.svelte +22 -2
  21. package/dist/visual/ObjectField.svelte +95 -75
  22. package/dist/visual/ObjectField.svelte.d.ts +7 -3
  23. package/dist/visual/ProseValue.svelte +165 -0
  24. package/dist/visual/ProseValue.svelte.d.ts +33 -0
  25. package/dist/visual/SlashMenu.svelte +7 -0
  26. package/dist/visual/VariantField.svelte +11 -6
  27. package/dist/visual/VariantField.svelte.d.ts +5 -1
  28. package/dist/visual/VisualEditorInner.svelte +29 -11
  29. package/dist/visual/hold.d.ts +17 -0
  30. package/dist/visual/hold.js +39 -0
  31. package/dist/visual/index.js +1 -1
  32. package/dist/visual/motion.d.ts +9 -0
  33. package/dist/visual/motion.js +10 -2
  34. package/dist/visual/strings.d.ts +4 -3
  35. package/package.json +2 -2
  36. package/dist/visual/ProseArrayElement.svelte +0 -139
  37. 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
+ }
@@ -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` (rule 4) walks this file's own graph and holds the reach.
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
@@ -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
  *
@@ -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 and `check:style` holds.
45
+ // rung the derivation mints.
38
46
  if (!armed() || typeof node.animate !== 'function')
39
47
  return {};
40
48
  const style = getComputedStyle(node);
@@ -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 (a nested prose / array /
29
- * object property), and where to edit that property instead. The whole sentence,
30
- * since a translator orders the kind within it.
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.6.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.0-0",
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;