@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.
Files changed (143) hide show
  1. package/LICENSE +201 -0
  2. package/NOTICE +58 -0
  3. package/README.md +241 -0
  4. package/THEMING.md +153 -0
  5. package/dist/core/address.d.ts +56 -0
  6. package/dist/core/address.js +95 -0
  7. package/dist/core/bloom.d.ts +37 -0
  8. package/dist/core/bloom.js +116 -0
  9. package/dist/core/codec/anchor.d.ts +19 -0
  10. package/dist/core/codec/anchor.js +36 -0
  11. package/dist/core/codec/code.d.ts +12 -0
  12. package/dist/core/codec/code.js +118 -0
  13. package/dist/core/codec/decode.d.ts +24 -0
  14. package/dist/core/codec/decode.js +270 -0
  15. package/dist/core/codec/encode.d.ts +71 -0
  16. package/dist/core/codec/encode.js +480 -0
  17. package/dist/core/codec/field.d.ts +171 -0
  18. package/dist/core/codec/field.js +493 -0
  19. package/dist/core/codec/index.d.ts +22 -0
  20. package/dist/core/codec/index.js +43 -0
  21. package/dist/core/codec/inputrules.d.ts +7 -0
  22. package/dist/core/codec/inputrules.js +168 -0
  23. package/dist/core/codec/islands.d.ts +36 -0
  24. package/dist/core/codec/islands.js +82 -0
  25. package/dist/core/codec/keymap.d.ts +13 -0
  26. package/dist/core/codec/keymap.js +29 -0
  27. package/dist/core/codec/lists.d.ts +22 -0
  28. package/dist/core/codec/lists.js +104 -0
  29. package/dist/core/codec/marks.d.ts +37 -0
  30. package/dist/core/codec/marks.js +74 -0
  31. package/dist/core/codec/positions.d.ts +17 -0
  32. package/dist/core/codec/positions.js +108 -0
  33. package/dist/core/codec/prose.css +458 -0
  34. package/dist/core/codec/reconcile.d.ts +26 -0
  35. package/dist/core/codec/reconcile.js +64 -0
  36. package/dist/core/codec/schema.d.ts +13 -0
  37. package/dist/core/codec/schema.js +155 -0
  38. package/dist/core/codec/slash.d.ts +79 -0
  39. package/dist/core/codec/slash.js +0 -0
  40. package/dist/core/codec/table-view.d.ts +50 -0
  41. package/dist/core/codec/table-view.js +928 -0
  42. package/dist/core/codec/table.d.ts +85 -0
  43. package/dist/core/codec/table.js +224 -0
  44. package/dist/core/errors.d.ts +73 -0
  45. package/dist/core/errors.js +24 -0
  46. package/dist/core/index.d.ts +5 -0
  47. package/dist/core/index.js +19 -0
  48. package/dist/core/lifecycle.d.ts +7 -0
  49. package/dist/core/lifecycle.js +21 -0
  50. package/dist/core/rebind.svelte.d.ts +18 -0
  51. package/dist/core/rebind.svelte.js +44 -0
  52. package/dist/core/teardown.d.ts +20 -0
  53. package/dist/core/teardown.js +37 -0
  54. package/dist/core/theme.css +276 -0
  55. package/dist/index.d.ts +1 -0
  56. package/dist/index.js +9 -0
  57. package/dist/preset/index.css +10 -0
  58. package/dist/preset/recipes.css +188 -0
  59. package/dist/preset/scale.css +97 -0
  60. package/dist/preview/Preview.svelte +118 -0
  61. package/dist/preview/Preview.svelte.d.ts +39 -0
  62. package/dist/preview/bridge.d.ts +13 -0
  63. package/dist/preview/bridge.js +88 -0
  64. package/dist/preview/controller.d.ts +40 -0
  65. package/dist/preview/controller.js +141 -0
  66. package/dist/preview/geometry.d.ts +37 -0
  67. package/dist/preview/geometry.js +45 -0
  68. package/dist/preview/index.d.ts +5 -0
  69. package/dist/preview/index.js +14 -0
  70. package/dist/preview/overlay.d.ts +10 -0
  71. package/dist/preview/overlay.js +82 -0
  72. package/dist/preview/paint.d.ts +17 -0
  73. package/dist/preview/paint.js +262 -0
  74. package/dist/preview/strings.d.ts +14 -0
  75. package/dist/preview/strings.js +22 -0
  76. package/dist/visual/ArrayField.svelte +362 -0
  77. package/dist/visual/ArrayField.svelte.d.ts +24 -0
  78. package/dist/visual/BooleanField.svelte +88 -0
  79. package/dist/visual/BooleanField.svelte.d.ts +20 -0
  80. package/dist/visual/Card.svelte +849 -0
  81. package/dist/visual/Card.svelte.d.ts +43 -0
  82. package/dist/visual/CardControls.svelte +92 -0
  83. package/dist/visual/CardControls.svelte.d.ts +11 -0
  84. package/dist/visual/DateField.svelte +203 -0
  85. package/dist/visual/DateField.svelte.d.ts +28 -0
  86. package/dist/visual/DiagnosticList.svelte +50 -0
  87. package/dist/visual/DiagnosticList.svelte.d.ts +7 -0
  88. package/dist/visual/EnumField.svelte +169 -0
  89. package/dist/visual/EnumField.svelte.d.ts +25 -0
  90. package/dist/visual/Field.svelte +270 -0
  91. package/dist/visual/Field.svelte.d.ts +37 -0
  92. package/dist/visual/FieldHint.svelte +185 -0
  93. package/dist/visual/FieldHint.svelte.d.ts +13 -0
  94. package/dist/visual/FieldLabel.svelte +141 -0
  95. package/dist/visual/FieldLabel.svelte.d.ts +19 -0
  96. package/dist/visual/FormatPopover.svelte +396 -0
  97. package/dist/visual/FormatPopover.svelte.d.ts +9 -0
  98. package/dist/visual/NumberField.svelte +72 -0
  99. package/dist/visual/NumberField.svelte.d.ts +19 -0
  100. package/dist/visual/ObjectField.svelte +131 -0
  101. package/dist/visual/ObjectField.svelte.d.ts +17 -0
  102. package/dist/visual/ProseArrayElement.svelte +98 -0
  103. package/dist/visual/ProseArrayElement.svelte.d.ts +19 -0
  104. package/dist/visual/ProseField.svelte +213 -0
  105. package/dist/visual/ProseField.svelte.d.ts +50 -0
  106. package/dist/visual/SlashMenu.svelte +126 -0
  107. package/dist/visual/SlashMenu.svelte.d.ts +14 -0
  108. package/dist/visual/TextField.svelte +63 -0
  109. package/dist/visual/TextField.svelte.d.ts +23 -0
  110. package/dist/visual/TipsCard.svelte +191 -0
  111. package/dist/visual/TipsCard.svelte.d.ts +13 -0
  112. package/dist/visual/VisualEditor.svelte +96 -0
  113. package/dist/visual/VisualEditor.svelte.d.ts +14 -0
  114. package/dist/visual/VisualEditorInner.svelte +1032 -0
  115. package/dist/visual/VisualEditorInner.svelte.d.ts +17 -0
  116. package/dist/visual/controls.css +272 -0
  117. package/dist/visual/diagnostics.d.ts +52 -0
  118. package/dist/visual/diagnostics.js +83 -0
  119. package/dist/visual/domid.d.ts +21 -0
  120. package/dist/visual/domid.js +36 -0
  121. package/dist/visual/ext.d.ts +19 -0
  122. package/dist/visual/ext.js +29 -0
  123. package/dist/visual/icons/Icon.svelte +39 -0
  124. package/dist/visual/icons/Icon.svelte.d.ts +12 -0
  125. package/dist/visual/icons/nodes.d.ts +103 -0
  126. package/dist/visual/icons/nodes.js +55 -0
  127. package/dist/visual/index.d.ts +9 -0
  128. package/dist/visual/index.js +24 -0
  129. package/dist/visual/motion.d.ts +18 -0
  130. package/dist/visual/motion.js +53 -0
  131. package/dist/visual/props.d.ts +77 -0
  132. package/dist/visual/props.js +1 -0
  133. package/dist/visual/signals.d.ts +59 -0
  134. package/dist/visual/signals.js +1 -0
  135. package/dist/visual/strings.d.ts +123 -0
  136. package/dist/visual/strings.js +80 -0
  137. package/dist/visual/structure.d.ts +224 -0
  138. package/dist/visual/structure.js +270 -0
  139. package/dist/visual/synced.svelte.d.ts +15 -0
  140. package/dist/visual/synced.svelte.js +34 -0
  141. package/dist/visual/tips.d.ts +24 -0
  142. package/dist/visual/tips.js +50 -0
  143. package/package.json +102 -0
@@ -0,0 +1,191 @@
1
+ <!--
2
+ The ephemeral tips card. Renders the
3
+ `$ext.editor.tips` channel one tip at a time: dots reach any tip in the set, and the
4
+ foot's one button advances until the last, where it becomes the dismiss. Dismissal
5
+ CLEARS the channel in the Document, so the card is gone and does not reappear.
6
+
7
+ The cursor is LOCAL state, not the channel. Advancing writes nothing: a per-tip
8
+ write would round-trip the boundary, re-derive the card tree, and dirty the
9
+ document (firing a consumer's autosave) on what is a read gesture. Exactly one
10
+ write happens, at dismissal, and this component unmounts with it, so the cursor
11
+ needs no reset.
12
+ -->
13
+ <script lang="ts">
14
+ import { wording } from './strings.js';
15
+
16
+ // The surface's words, ambient from the editor root; the package's English
17
+ // off-tree, so this component renders standalone too.
18
+ const t = wording();
19
+ import Icon from './icons/Icon.svelte';
20
+ import { renderTip } from './tips.js';
21
+ import type { EditorErrorHandler } from '../core/errors.js';
22
+ import './controls.css';
23
+
24
+ interface Props {
25
+ /** The narrowed channel (`tipsChannel`); never empty; the parent gates on length. */
26
+ tips: string[];
27
+ /** Clear `$ext.editor.tips`: the patch-write that preserves the `title` sibling. */
28
+ onDismiss: () => void;
29
+ /** A tip whose markdown did not render; it shows literally either way. */
30
+ onError?: EditorErrorHandler;
31
+ }
32
+ let { tips, onDismiss, onError }: Props = $props();
33
+
34
+ /** Control-glyph size: the shared rule, as CardControls. */
35
+ const GLYPH = 14;
36
+
37
+ let cursor = $state(0);
38
+ // Clamped: the channel is consumer-authored and can be re-seeded shorter while
39
+ // the card is open, which would otherwise index past the end.
40
+ const index = $derived(Math.min(cursor, tips.length - 1));
41
+ const isLast = $derived(index >= tips.length - 1);
42
+
43
+ // The tip STRING is derived before the effect reads it, and that is load-bearing.
44
+ // Reading `tips[index]` inside the effect makes the parent's `model` derive the
45
+ // dependency (a fresh object every `revision` bump) so the effect would re-run
46
+ // on every unrelated commit, re-crossing the WASM boundary and rebuilding this
47
+ // `aria-live` region per keystroke. A derived string short-circuits on `===`.
48
+ const tip = $derived(tips[index] ?? '');
49
+
50
+ // The tip is DOM, not text: `renderTip` returns a fragment the codec's own
51
+ // `toDOM` built, so it is written in rather than interpolated.
52
+ let bodyEl: HTMLDivElement | undefined = $state();
53
+ $effect(() => {
54
+ const el = bodyEl;
55
+ if (el) el.replaceChildren(renderTip(tip, onError));
56
+ });
57
+
58
+ function advance(): void {
59
+ if (isLast) onDismiss();
60
+ else cursor = index + 1;
61
+ }
62
+ </script>
63
+
64
+ <aside class="qm-tips" aria-label={t.strings.tipsLabel}>
65
+ <!-- Advancing swaps the text under a button that keeps focus, so the region
66
+ announces rather than the change passing silently. -->
67
+ <div class="qm-tips-body" aria-live="polite" bind:this={bodyEl}></div>
68
+ <div class="qm-tips-foot">
69
+ <!-- One tip is its own whole set, so a lone dot reports nothing the card does
70
+ not already show. -->
71
+ {#if tips.length > 1}
72
+ <div class="qm-tips-dots">
73
+ {#each tips as _, i (i)}
74
+ <button
75
+ type="button"
76
+ class="qm-tips-dot"
77
+ aria-label={t.strings.tipsDot(i + 1, tips.length)}
78
+ aria-current={i === index}
79
+ onclick={() => (cursor = i)}
80
+ ></button>
81
+ {/each}
82
+ </div>
83
+ {/if}
84
+ <!-- ONE button, and its action is where the cursor stands: the set is walked,
85
+ and its end is the exit. -->
86
+ <button type="button" class="qm-icon-btn qm-tips-action" onclick={advance}>
87
+ {#if isLast}
88
+ {t.strings.tipsDismiss} <Icon name="x" size={GLYPH} />
89
+ {:else}
90
+ {t.strings.tipsNext} <Icon name="chevron-right" size={GLYPH} />
91
+ {/if}
92
+ </button>
93
+ </div>
94
+ </aside>
95
+
96
+ <style>
97
+ /* ATTACHED to `main`, not a block beside it: a square top pulled up by the radius,
98
+ so its background fills the notches `main`'s rounded bottom corners cut and `main`
99
+ paints over the rest. The seam is `main`'s own bottom hairline, and the two read as
100
+ one block, which is what document-level guidance is: the main card's, not a card of
101
+ its own. Still in-flow, one hairline, no lift, no fill beyond the card recipe. It
102
+ reads as guidance rather than as a field by TONE and TYPE: the label rung in the
103
+ muted label colour; not by a badge or an accent. It mints no token of its own;
104
+ every value here is an existing dial.
105
+
106
+ The top inset takes the tuck back, so the space above the tip is the space below
107
+ the foot.
108
+
109
+ Leading is the reading rung, not the tight one the label SIZE would suggest: a
110
+ tip is a passage that wraps, and the two axes are independent. */
111
+ .qm-tips {
112
+ margin-top: calc(var(--_qm-radius) * -1);
113
+ border: var(--_qm-border-width) solid var(--_qm-border);
114
+ border-radius: 0 0 var(--_qm-radius) var(--_qm-radius);
115
+ padding: calc(var(--_qm-space-3) + var(--_qm-radius)) var(--_qm-space-3) var(--_qm-space-3);
116
+ background: var(--_qm-surface-raised);
117
+ display: flex;
118
+ flex-direction: column;
119
+ gap: var(--_qm-space-2);
120
+ font-size: var(--_qm-text-label);
121
+ line-height: var(--_qm-leading-body);
122
+ color: var(--_qm-ink-label);
123
+ }
124
+ /* The rendered tip is injected DOM (the codec's `toDOM` output), so its element
125
+ styles are `:global`: the compiler never sees these tags in the markup. Three
126
+ rules restated from `core/codec/prose.css`, which is where they are argued and
127
+ which cannot reach here: its selectors are scoped to `.ProseMirror` and a tip is
128
+ not a view. The one deliberate divergence is the link, which takes no underline:
129
+ a tip is not editable, so a hidden `href` costs its reader nothing. */
130
+ .qm-tips-body :global(p) {
131
+ margin: 0;
132
+ }
133
+ .qm-tips-body :global(code) {
134
+ font-family: var(--_qm-font-mono);
135
+ }
136
+ .qm-tips-body :global(a) {
137
+ color: inherit;
138
+ }
139
+ .qm-tips-foot {
140
+ display: flex;
141
+ align-items: center;
142
+ justify-content: flex-end;
143
+ gap: var(--_qm-space-2);
144
+ }
145
+ .qm-tips-dots {
146
+ display: flex;
147
+ align-items: center;
148
+ margin-right: auto;
149
+ }
150
+ /* Random access to the set, and the PITCH is the tap floor: 2.5.8's spacing
151
+ exception measures centre distance, which is the number the floor already fixes,
152
+ so a tighter row of dots buys nothing it does not immediately owe back. Target and
153
+ mark are therefore not one rectangle (the floor is the button, the dot is drawn
154
+ inside it), which is also why the hover reads on the mark rather than filling the
155
+ box a `.qm-icon-btn` would. */
156
+ .qm-tips-dot {
157
+ display: flex;
158
+ align-items: center;
159
+ justify-content: center;
160
+ box-sizing: border-box;
161
+ min-width: var(--_qm-tap-min);
162
+ min-height: var(--_qm-tap-min);
163
+ border: none;
164
+ background: transparent;
165
+ cursor: pointer;
166
+ }
167
+ /* Three steps of ONE ink on the recede ladder (rest, hover, current), never a
168
+ second hue and never a size: a dot that grew would move the row it sits in. */
169
+ .qm-tips-dot::before {
170
+ content: '';
171
+ width: 5px;
172
+ height: 5px;
173
+ border-radius: var(--_qm-radius-pill);
174
+ background: currentColor;
175
+ opacity: var(--_qm-opacity-idle);
176
+ transition: opacity var(--_qm-duration-fast) var(--_qm-ease-reverse);
177
+ }
178
+ .qm-tips-dot:hover::before {
179
+ opacity: var(--_qm-opacity-muted);
180
+ }
181
+ .qm-tips-dot[aria-current='true']::before {
182
+ opacity: 1;
183
+ }
184
+ /* Box and disabled state come from `.qm-icon-btn` (controls.css); the foot's
185
+ button carries a label and so widens its inset and takes the meta type rung. */
186
+ .qm-tips-action {
187
+ padding: var(--_qm-space-half) var(--_qm-space-2);
188
+ font-size: var(--_qm-text-meta);
189
+ color: var(--_qm-ink-label);
190
+ }
191
+ </style>
@@ -0,0 +1,13 @@
1
+ import type { EditorErrorHandler } from '../core/errors.js';
2
+ import './controls.css';
3
+ interface Props {
4
+ /** The narrowed channel (`tipsChannel`); never empty; the parent gates on length. */
5
+ tips: string[];
6
+ /** Clear `$ext.editor.tips`: the patch-write that preserves the `title` sibling. */
7
+ onDismiss: () => void;
8
+ /** A tip whose markdown did not render; it shows literally either way. */
9
+ onError?: EditorErrorHandler;
10
+ }
11
+ declare const TipsCard: import("svelte").Component<Props, {}, "">;
12
+ type TipsCard = ReturnType<typeof TipsCard>;
13
+ export default TipsCard;
@@ -0,0 +1,96 @@
1
+ <!--
2
+ The editor's door, and the whole of what it adds is the re-key: swapping the
3
+ `doc` prop remounts the editor under it, so a consumer holding one mount across
4
+ two documents gets a surface that names the live handle.
5
+
6
+ IT IS A `{#key}`, NOT A RESEED. The state a swap invalidates is not one field:
7
+ composable cards key on session id, the main card is keyed on nothing, and each
8
+ prose leaf mounts once per stable leaf key with `createField` closing over the
9
+ `doc` it mounted against — so after an in-place swap the main card's leaves
10
+ commit to the PREVIOUS handle. Reseeding by hand means threading a generation
11
+ token through every leaf key and resetting the id state, the commit-error map,
12
+ the active address, the leaf registry, the card refs and any pending scroll,
13
+ which is a remount spelled out one field at a time. This spells it as one.
14
+
15
+ Teardown order is the one `core/teardown.ts` states: the outgoing mount
16
+ unregisters, cancels its pending ticks, and only then releases, so a continuation
17
+ awaiting a flush across the swap runs against nothing.
18
+
19
+ `quill` is NOT part of the key: the schema is re-read on every derive, so a quill
20
+ swap re-projects correctly on its own, and keying on it would throw away a card
21
+ tree that did not need rebuilding. What it cannot do is re-mount the leaves, so a
22
+ quill swapped WITHOUT a doc swap reports `rebind-ignored` rather than passing
23
+ silently.
24
+ -->
25
+ <script lang="ts">
26
+ import { untrack } from 'svelte';
27
+ import { reportError } from '../core/errors.js';
28
+ import type { ContentHit } from '@quillmark/wasm';
29
+ import type { DocPath } from '../core/address.js';
30
+ import type { CardId } from './signals.js';
31
+ import type { VisualEditorProps } from './props.js';
32
+ import VisualEditorInner from './VisualEditorInner.svelte';
33
+
34
+ let props: VisualEditorProps = $props();
35
+
36
+ let inner = $state<VisualEditorInner | undefined>();
37
+
38
+ // The pair the CURRENT mount bound to. Re-read on each re-key rather than held
39
+ // from the first mount: after a doc swap the incoming mount binds whatever quill
40
+ // came with it, and comparing against the original would report a pairing that
41
+ // is correct.
42
+ let boundDoc: unknown = undefined;
43
+ let boundQuill: unknown = undefined;
44
+ let reported = false;
45
+ $effect(() => {
46
+ const { doc, quill } = props;
47
+ untrack(() => {
48
+ // A doc swap re-keys, and the new mount binds this quill: nothing is stale.
49
+ if (doc !== boundDoc) {
50
+ boundDoc = doc;
51
+ boundQuill = quill;
52
+ reported = false;
53
+ return;
54
+ }
55
+ if (quill === boundQuill || reported) return;
56
+ reported = true;
57
+ reportError(props.onError, {
58
+ code: 'rebind-ignored',
59
+ severity: 'dev',
60
+ message:
61
+ 'quill swapped in place; the mounted leaves still commit against the quill this doc mounted with. Swap the doc with it, or remount the editor, to rebind.'
62
+ });
63
+ });
64
+ });
65
+
66
+ // The instance surface, every member a pass-through to the live mount: a call
67
+ // landing between a swap and the incoming mount is a no-op rather than a throw,
68
+ // which is what lets a host hold ONE `bind:this` across two documents.
69
+
70
+ /** Place the caret at a preview hit. */
71
+ export async function setCaret(hit: ContentHit): Promise<void> {
72
+ await inner?.setCaret(hit);
73
+ }
74
+ /** Reveal and focus the leaf at `field`, without placing a caret inside it. */
75
+ export async function focusField(field: DocPath): Promise<void> {
76
+ await inner?.focusField(field);
77
+ }
78
+ /** Seed a card of `kind` at `at` (default: the end); the new card's session key. */
79
+ export function insertCard(kind: string, at?: number): CardId | undefined {
80
+ return inner?.insertCard(kind, at);
81
+ }
82
+ export function removeCard(cardId: CardId): void {
83
+ inner?.removeCard(cardId);
84
+ }
85
+ /** Move a card one slot; at either edge it is a no-op. */
86
+ export function moveCard(cardId: CardId, dir: -1 | 1): void {
87
+ inner?.moveCard(cardId, dir);
88
+ }
89
+ export function setKind(cardId: CardId, kind: string): void {
90
+ inner?.setKind(cardId, kind);
91
+ }
92
+ </script>
93
+
94
+ {#key props.doc}
95
+ <VisualEditorInner bind:this={inner} {...props} />
96
+ {/key}
@@ -0,0 +1,14 @@
1
+ import type { ContentHit } from '@quillmark/wasm';
2
+ import type { DocPath } from '../core/address.js';
3
+ import type { CardId } from './signals.js';
4
+ import type { VisualEditorProps } from './props.js';
5
+ declare const VisualEditor: import("svelte").Component<VisualEditorProps, {
6
+ setCaret: (hit: ContentHit) => Promise<void>;
7
+ focusField: (field: DocPath) => Promise<void>;
8
+ insertCard: (kind: string, at?: number) => CardId | undefined;
9
+ removeCard: (cardId: CardId) => void;
10
+ moveCard: (cardId: CardId, dir: -1 | 1) => void;
11
+ setKind: (cardId: CardId, kind: string) => void;
12
+ }, "">;
13
+ type VisualEditor = ReturnType<typeof VisualEditor>;
14
+ export default VisualEditor;