@quillmark/svelte 0.17.0 → 0.18.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/README.md +1 -1
- package/dist/core/codec/index.d.ts +0 -3
- package/dist/core/rebind.svelte.js +5 -8
- package/dist/preset/index.d.ts +1 -0
- package/dist/preview/Preview.svelte +14 -19
- package/dist/preview/Preview.svelte.d.ts +3 -3
- package/dist/preview/index.d.ts +0 -1
- package/dist/visual/ArrayField.svelte +35 -43
- package/dist/visual/ArrayField.svelte.d.ts +0 -1
- package/dist/visual/BooleanField.svelte +9 -14
- package/dist/visual/BooleanField.svelte.d.ts +0 -1
- package/dist/visual/Card.svelte +5 -18
- package/dist/visual/CardControls.svelte.d.ts +0 -1
- package/dist/visual/DateField.svelte +14 -17
- package/dist/visual/DateField.svelte.d.ts +0 -1
- package/dist/visual/EnumField.svelte +9 -12
- package/dist/visual/EnumField.svelte.d.ts +0 -1
- package/dist/visual/Field.svelte +7 -8
- package/dist/visual/FieldHint.svelte.d.ts +0 -1
- package/dist/visual/FieldLabel.svelte +1 -2
- package/dist/visual/FormatPopover.svelte +22 -27
- package/dist/visual/FormatPopover.svelte.d.ts +0 -1
- package/dist/visual/MatrixAdd.svelte +1 -2
- package/dist/visual/MatrixAdd.svelte.d.ts +0 -1
- package/dist/visual/MatrixField.svelte +3 -5
- package/dist/visual/MatrixField.svelte.d.ts +0 -1
- package/dist/visual/NumberField.svelte +6 -10
- package/dist/visual/NumberField.svelte.d.ts +0 -1
- package/dist/visual/ObjectField.svelte +8 -12
- package/dist/visual/ObjectField.svelte.d.ts +0 -1
- package/dist/visual/ProseField.svelte +42 -41
- package/dist/visual/ProseField.svelte.d.ts +0 -1
- package/dist/visual/ProseValue.svelte +56 -56
- package/dist/visual/ProseValue.svelte.d.ts +0 -1
- package/dist/visual/SlashMenu.svelte +5 -15
- package/dist/visual/SlashMenu.svelte.d.ts +0 -1
- package/dist/visual/TextField.svelte +5 -9
- package/dist/visual/TextField.svelte.d.ts +0 -1
- package/dist/visual/TipsCard.svelte +10 -11
- package/dist/visual/TipsCard.svelte.d.ts +0 -1
- package/dist/visual/VisualEditorInner.svelte +6 -14
- package/dist/visual/VisualEditorInner.svelte.d.ts +0 -1
- package/dist/visual/index.d.ts +0 -1
- package/dist/visual/structure.d.ts +4 -0
- package/dist/visual/structure.js +12 -0
- package/package.json +20 -16
- package/dist/visual/synced.svelte.d.ts +0 -15
- package/dist/visual/synced.svelte.js +0 -34
|
@@ -33,30 +33,20 @@
|
|
|
33
33
|
}
|
|
34
34
|
let { menu, leaf, label }: Props = $props();
|
|
35
35
|
|
|
36
|
-
|
|
37
|
-
* derived, because the primitive owns this prop: its own dismissal layer (an
|
|
38
|
-
* outside press) writes it, and a derived it cannot write to is an error rather
|
|
39
|
-
* than a dismissal. The next report re-syncs, so the codec stays the authority on
|
|
40
|
-
* whether a run is live. */
|
|
41
|
-
let open = $state(false);
|
|
42
|
-
$effect(() => {
|
|
43
|
-
open = !!menu && menu.items.length > 0;
|
|
44
|
-
});
|
|
36
|
+
let open = $derived(!!menu && menu.items.length > 0);
|
|
45
37
|
|
|
46
38
|
/** The root to portal into, resolved from the leaf's own DOM so the menu lands
|
|
47
39
|
* inside whichever `[data-qm-root]` raised it and inherits the consumer's dials;
|
|
48
40
|
* `undefined` falls back to bits-ui's `document.body`. */
|
|
49
41
|
const portalTarget = $derived(leaf()?.el.closest<HTMLElement>('[data-qm-root]') ?? undefined);
|
|
50
42
|
|
|
51
|
-
let surface = $state<HTMLElement | undefined>();
|
|
52
|
-
|
|
53
43
|
/** Keep the keyboard's cursor in the port: the arrows move an index rather than a
|
|
54
44
|
* focus, so a row the list scrolled past has nothing bringing it back. `nearest`
|
|
55
45
|
* scrolls the least that works, and no ancestor the row is already visible in. */
|
|
56
|
-
|
|
46
|
+
function followHighlight(surface: HTMLElement): void {
|
|
57
47
|
void menu?.index;
|
|
58
|
-
surface
|
|
59
|
-
}
|
|
48
|
+
surface.querySelector('[data-highlighted]')?.scrollIntoView({ block: 'nearest' });
|
|
49
|
+
}
|
|
60
50
|
|
|
61
51
|
/** Swallow the item's own mousedown: without it the browser focuses the item and
|
|
62
52
|
* blurs the leaf, taking the trigger run's caret with it. */
|
|
@@ -98,7 +88,7 @@
|
|
|
98
88
|
a thing to click, tab into, or read. -->
|
|
99
89
|
<div
|
|
100
90
|
{...props}
|
|
101
|
-
|
|
91
|
+
{@attach followHighlight}
|
|
102
92
|
class="qm-slash-menu qm-menu-surface"
|
|
103
93
|
data-qm-root
|
|
104
94
|
role="group"
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import type { FieldController, SlashState } from '../core/codec/index.js';
|
|
2
|
-
import './controls.css';
|
|
3
2
|
interface Props {
|
|
4
3
|
/** The live menu, or `undefined` when closed: the codec's report channel. Named
|
|
5
4
|
* `menu` rather than `state`, which is the rune's word in this file. */
|
|
@@ -14,7 +14,6 @@
|
|
|
14
14
|
unanswered, and an optional cell returns to `none`.
|
|
15
15
|
-->
|
|
16
16
|
<script lang="ts">
|
|
17
|
-
import { syncedLocal } from './synced.svelte.js';
|
|
18
17
|
import './controls.css';
|
|
19
18
|
|
|
20
19
|
interface Props {
|
|
@@ -40,11 +39,8 @@
|
|
|
40
39
|
}
|
|
41
40
|
let { value, fallback, placeholder, label, id, describedBy, onCommit, onKey }: Props = $props();
|
|
42
41
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
// `syncedLocal`).
|
|
46
|
-
const local = syncedLocal(() => value ?? fallback ?? '');
|
|
47
|
-
const defaulted = $derived(value == null && !!fallback && local.value === fallback);
|
|
42
|
+
let local = $derived(value ?? fallback ?? '');
|
|
43
|
+
const defaulted = $derived(value == null && !!fallback && local === fallback);
|
|
48
44
|
|
|
49
45
|
let inputEl: HTMLInputElement | undefined = $state();
|
|
50
46
|
/** Take the caret: what a parent placing focus on this control calls. */
|
|
@@ -57,7 +53,7 @@
|
|
|
57
53
|
bind:this={inputEl}
|
|
58
54
|
class="qm-input qm-focus-ring"
|
|
59
55
|
type="text"
|
|
60
|
-
value={local
|
|
56
|
+
value={local}
|
|
61
57
|
{id}
|
|
62
58
|
{placeholder}
|
|
63
59
|
data-default={defaulted ? '' : undefined}
|
|
@@ -65,7 +61,7 @@
|
|
|
65
61
|
aria-describedby={describedBy}
|
|
66
62
|
onkeydown={onKey}
|
|
67
63
|
oninput={(e) => {
|
|
68
|
-
local
|
|
69
|
-
onCommit(local
|
|
64
|
+
local = (e.currentTarget as HTMLInputElement).value;
|
|
65
|
+
onCommit(local !== '' ? local : fallback ? '' : undefined);
|
|
70
66
|
}}
|
|
71
67
|
/>
|
|
@@ -40,20 +40,19 @@
|
|
|
40
40
|
const index = $derived(Math.min(cursor, tips.length - 1));
|
|
41
41
|
const isLast = $derived(index >= tips.length - 1);
|
|
42
42
|
|
|
43
|
-
// The tip string is derived before the
|
|
44
|
-
// Reading `tips[index]` inside the
|
|
45
|
-
// dependency (a fresh object every `revision` bump) so the
|
|
46
|
-
// on every unrelated commit, re-crossing the WASM boundary
|
|
47
|
-
// `aria-live` region per keystroke. A derived string
|
|
43
|
+
// The tip string is derived before the attachment reads it, and that is
|
|
44
|
+
// load-bearing. Reading `tips[index]` inside the attachment makes the parent's
|
|
45
|
+
// `model` derive the dependency (a fresh object every `revision` bump) so the
|
|
46
|
+
// attachment would re-run on every unrelated commit, re-crossing the WASM boundary
|
|
47
|
+
// and rebuilding this `aria-live` region per keystroke. A derived string
|
|
48
|
+
// short-circuits on `===`.
|
|
48
49
|
const tip = $derived(tips[index] ?? '');
|
|
49
50
|
|
|
50
51
|
// The tip is DOM, not text: `renderTip` returns a fragment the codec's own
|
|
51
52
|
// `toDOM` built, so it is written in rather than interpolated.
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
if (el) el.replaceChildren(renderTip(tip, onError));
|
|
56
|
-
});
|
|
53
|
+
function paintTip(body: HTMLDivElement): void {
|
|
54
|
+
body.replaceChildren(renderTip(tip, onError));
|
|
55
|
+
}
|
|
57
56
|
|
|
58
57
|
function advance(): void {
|
|
59
58
|
if (isLast) onDismiss();
|
|
@@ -64,7 +63,7 @@
|
|
|
64
63
|
<aside class="qm-tips" aria-label={t.strings.tipsLabel}>
|
|
65
64
|
<!-- Advancing swaps the text under a button that keeps focus, so the region
|
|
66
65
|
announces rather than the change passing silently. -->
|
|
67
|
-
<div class="qm-tips-body" aria-live="polite"
|
|
66
|
+
<div class="qm-tips-body" aria-live="polite" {@attach paintTip}></div>
|
|
68
67
|
<div class="qm-tips-foot">
|
|
69
68
|
<!-- One tip is its own whole set, so a lone dot reports nothing the card does
|
|
70
69
|
not already show. -->
|
|
@@ -19,6 +19,7 @@
|
|
|
19
19
|
-->
|
|
20
20
|
<script lang="ts">
|
|
21
21
|
import { onDestroy, tick } from 'svelte';
|
|
22
|
+
import { SvelteMap } from 'svelte/reactivity';
|
|
22
23
|
import { DropdownMenu } from 'bits-ui';
|
|
23
24
|
import { isQuillmarkError, MAIN_CARD_ADDR } from '@quillmark/wasm';
|
|
24
25
|
import {
|
|
@@ -135,14 +136,7 @@
|
|
|
135
136
|
// Local commit-error diagnostics (VISUAL_EDITOR §Diagnostics, producer #2),
|
|
136
137
|
// id-keyed rather than positional so an error stays pinned to its field across a
|
|
137
138
|
// card reorder.
|
|
138
|
-
|
|
139
|
-
/** Edit the commit-error map copy-on-write: a `$state` Map is not deeply
|
|
140
|
-
* reactive, so a mutation in place re-derives nothing. */
|
|
141
|
-
function editCommitErrors(edit: (m: Map<string, RoutedDiagnostic>) => void): void {
|
|
142
|
-
const next = new Map(commitErrors);
|
|
143
|
-
edit(next);
|
|
144
|
-
commitErrors = next;
|
|
145
|
-
}
|
|
139
|
+
const commitErrors = new SvelteMap<string, RoutedDiagnostic>();
|
|
146
140
|
|
|
147
141
|
const kinds = $derived(Object.keys(quill.schema.card_kinds ?? {}));
|
|
148
142
|
|
|
@@ -350,14 +344,14 @@
|
|
|
350
344
|
w.card(i).set(name, value);
|
|
351
345
|
}
|
|
352
346
|
}
|
|
353
|
-
|
|
347
|
+
commitErrors.delete(keyStr);
|
|
354
348
|
bump('field', id, makeAddr(id, isMain, name));
|
|
355
349
|
} catch (e) {
|
|
356
350
|
const diagnostic: Diagnostic = (isQuillmarkError(e) ? e.diagnostics[0] : undefined) ?? {
|
|
357
351
|
severity: 'error',
|
|
358
352
|
message: errorMessage(e)
|
|
359
353
|
};
|
|
360
|
-
|
|
354
|
+
commitErrors.set(keyStr, { key, diagnostic });
|
|
361
355
|
// Both channels, deliberately: the diagnostic pins to the field on
|
|
362
356
|
// screen, the error reaches the app's sink (core/errors.ts).
|
|
363
357
|
reportError(onError, {
|
|
@@ -462,9 +456,7 @@
|
|
|
462
456
|
// because nothing else will ever clear it.
|
|
463
457
|
const prefix = `${id}:`;
|
|
464
458
|
const stale = [...commitErrors.keys()].filter((k) => k.startsWith(prefix));
|
|
465
|
-
|
|
466
|
-
// nothing to re-derive.
|
|
467
|
-
if (stale.length) editCommitErrors((m) => stale.forEach((k) => m.delete(k)));
|
|
459
|
+
for (const k of stale) commitErrors.delete(k);
|
|
468
460
|
// No addr: the removed card has none left and every survivor's shifted. The
|
|
469
461
|
// stack changed, not a leaf, and the id is the only handle the removal leaves.
|
|
470
462
|
bump('structure', id);
|
|
@@ -937,7 +929,7 @@
|
|
|
937
929
|
gaps between cards stay bare: the strip is the gap, and the pill each fills on
|
|
938
930
|
hover draws what a label would state, the space the new card takes. -->
|
|
939
931
|
{@const marked = atIndex === model.cards.length}
|
|
940
|
-
<div class=
|
|
932
|
+
<div class={['qm-add-card', { 'qm-add-card-marked': marked }]}>
|
|
941
933
|
<!-- Marked, the words are the accessible name and no `aria-label` doubles
|
|
942
934
|
them; bare, the same words land as the label, the strip having no
|
|
943
935
|
geometry to carry them. -->
|
|
@@ -2,7 +2,6 @@ import { type DocPath, type Landing } from '../core/address.js';
|
|
|
2
2
|
import type { VisualEditorProps } from './props.js';
|
|
3
3
|
import type { CardId } from './signals.js';
|
|
4
4
|
import type { FieldController } from '../core/codec/index.js';
|
|
5
|
-
import './controls.css';
|
|
6
5
|
declare const VisualEditorInner: import("svelte").Component<VisualEditorProps, {
|
|
7
6
|
setCaret: (at: Landing) => Promise<void>;
|
|
8
7
|
getActiveLeaf: () => FieldController | undefined;
|
package/dist/visual/index.d.ts
CHANGED
|
@@ -114,6 +114,10 @@ export declare function resolvedByCardIndex(resolved: Resolved | undefined): Map
|
|
|
114
114
|
* use (`source === 'default'`), else undefined: an `authored` field shows its value,
|
|
115
115
|
* a `blank` field has no default to show. */
|
|
116
116
|
export declare function ghostDefault(row: ResolvedField | undefined): unknown;
|
|
117
|
+
/** A stored `boolean` cell as the render reads it: a boolean, `true` or `false` in any
|
|
118
|
+
* case, or a number by whether it is non-zero; undefined where the render refuses the
|
|
119
|
+
* value. The engine's coercion, restated, so the suite holds it to `reader.resolve()`. */
|
|
120
|
+
export declare function renderedBoolean(value: unknown): boolean | undefined;
|
|
117
121
|
/** A ghost value's string form, or undefined for null/object (only text ghosts
|
|
118
122
|
* render a placeholder). A scalar's resolved default reads through it; a content
|
|
119
123
|
* field's resolves as `Content`, which a prose leaf reads by its text instead
|
package/dist/visual/structure.js
CHANGED
|
@@ -45,6 +45,18 @@ export function resolvedByCardIndex(resolved) {
|
|
|
45
45
|
export function ghostDefault(row) {
|
|
46
46
|
return row?.source === 'default' ? row.value : undefined;
|
|
47
47
|
}
|
|
48
|
+
/** A stored `boolean` cell as the render reads it: a boolean, `true` or `false` in any
|
|
49
|
+
* case, or a number by whether it is non-zero; undefined where the render refuses the
|
|
50
|
+
* value. The engine's coercion, restated, so the suite holds it to `reader.resolve()`. */
|
|
51
|
+
export function renderedBoolean(value) {
|
|
52
|
+
if (typeof value === 'boolean')
|
|
53
|
+
return value;
|
|
54
|
+
if (typeof value === 'number')
|
|
55
|
+
return value !== 0;
|
|
56
|
+
if (typeof value === 'string' && /^(true|false)$/i.test(value))
|
|
57
|
+
return value.toLowerCase() === 'true';
|
|
58
|
+
return undefined;
|
|
59
|
+
}
|
|
48
60
|
/** A ghost value's string form, or undefined for null/object (only text ghosts
|
|
49
61
|
* render a placeholder). A scalar's resolved default reads through it; a content
|
|
50
62
|
* field's resolves as `Content`, which a prose leaf reads by its text instead
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@quillmark/svelte",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.18.0",
|
|
4
4
|
"description": "Editor, live-preview and source surfaces over a Quillmark WASM session.",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -48,7 +48,10 @@
|
|
|
48
48
|
"svelte": "./dist/visual/index.js",
|
|
49
49
|
"default": "./dist/visual/index.js"
|
|
50
50
|
},
|
|
51
|
-
"./preset":
|
|
51
|
+
"./preset": {
|
|
52
|
+
"types": "./dist/preset/index.d.ts",
|
|
53
|
+
"default": "./dist/preset/index.css"
|
|
54
|
+
}
|
|
52
55
|
},
|
|
53
56
|
"files": [
|
|
54
57
|
"dist",
|
|
@@ -57,21 +60,22 @@
|
|
|
57
60
|
],
|
|
58
61
|
"sideEffects": true,
|
|
59
62
|
"scripts": {
|
|
60
|
-
"build": "svelte-package",
|
|
63
|
+
"build": "svelte-package && node scripts/strip-dts-css.mjs",
|
|
61
64
|
"prepare": "npm run build",
|
|
62
65
|
"prepack": "rm -rf dist && npm run build",
|
|
63
66
|
"check:pack": "publint",
|
|
64
67
|
"test": "vitest run",
|
|
65
68
|
"test:watch": "vitest",
|
|
66
|
-
"check": "svelte-check --tsconfig ./tsconfig.json"
|
|
69
|
+
"check": "svelte-check --tsconfig ./tsconfig.json --fail-on-warnings"
|
|
67
70
|
},
|
|
68
71
|
"peerDependencies": {
|
|
69
|
-
"@quillmark/wasm": ">=0.
|
|
70
|
-
"svelte": "^5.
|
|
72
|
+
"@quillmark/wasm": ">=0.122.0-0",
|
|
73
|
+
"svelte": "^5.33.0"
|
|
71
74
|
},
|
|
72
75
|
"dependencies": {
|
|
73
76
|
"@internationalized/date": "^3.12.2",
|
|
74
|
-
"bits-ui": "^2.
|
|
77
|
+
"bits-ui": "^2.19.0",
|
|
78
|
+
"esm-env": "^1.2.2",
|
|
75
79
|
"prosemirror-commands": "1.7.1",
|
|
76
80
|
"prosemirror-gapcursor": "1.3.2",
|
|
77
81
|
"prosemirror-history": "1.5.0",
|
|
@@ -84,15 +88,15 @@
|
|
|
84
88
|
"prosemirror-view": "1.42.1"
|
|
85
89
|
},
|
|
86
90
|
"devDependencies": {
|
|
87
|
-
"@sveltejs/package": "^
|
|
88
|
-
"@sveltejs/vite-plugin-svelte": "^
|
|
89
|
-
"@types/node": "^
|
|
90
|
-
"jsdom": "^
|
|
91
|
-
"publint": "^0.3.
|
|
92
|
-
"svelte": "^5.
|
|
93
|
-
"svelte-check": "^4.
|
|
94
|
-
"typescript": "^
|
|
95
|
-
"vitest": "^
|
|
91
|
+
"@sveltejs/package": "^3.0.0",
|
|
92
|
+
"@sveltejs/vite-plugin-svelte": "^7.3.0",
|
|
93
|
+
"@types/node": "^24.19.1",
|
|
94
|
+
"jsdom": "^30.1.0",
|
|
95
|
+
"publint": "^0.3.25",
|
|
96
|
+
"svelte": "^5.54.0",
|
|
97
|
+
"svelte-check": "^4.7.6",
|
|
98
|
+
"typescript": "^6.0.0",
|
|
99
|
+
"vitest": "^5.0.0"
|
|
96
100
|
},
|
|
97
101
|
"publishConfig": {
|
|
98
102
|
"access": "public"
|
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
/** A settable local synced to an external projection. Bind `.value` in the
|
|
2
|
-
* template; assign `.value` on input. */
|
|
3
|
-
export interface SyncedLocal<T> {
|
|
4
|
-
value: T;
|
|
5
|
-
}
|
|
6
|
-
/**
|
|
7
|
-
* Seed a local from `project` and reconcile only an external change into it.
|
|
8
|
-
* `project` reads the reactive source (a prop) and maps it to the local's type:
|
|
9
|
-
* ` => value ?? ''`, ` => (value != null ? String(value) : '')`, etc. The
|
|
10
|
-
* seed is `untrack`ed (the ongoing sync is the `$effect`, not the initializer),
|
|
11
|
-
* and each reconcile is `untrack`ed so an own-edit write never re-triggers it.
|
|
12
|
-
* Reassignment through the `value` setter is a plain local write; only a changed
|
|
13
|
-
* `project` result flows the other way.
|
|
14
|
-
*/
|
|
15
|
-
export declare function syncedLocal<T>(project: () => T): SyncedLocal<T>;
|
|
@@ -1,34 +0,0 @@
|
|
|
1
|
-
// The controlled-local reconcile shared by the scalar controls (TextField /
|
|
2
|
-
// NumberField / DateField / BooleanField / EnumField). Each seeds a local from a
|
|
3
|
-
// projection of its `value` prop and reconciles only an external change back into
|
|
4
|
-
// the local: own edits stay `untrack`ed, so typing never re-runs the sync and
|
|
5
|
-
// resets the caret. One copy for all five, rather than a character-identical block
|
|
6
|
-
// in each (VISUAL_EDITOR §Surface).
|
|
7
|
-
import { untrack } from 'svelte';
|
|
8
|
-
/**
|
|
9
|
-
* Seed a local from `project` and reconcile only an external change into it.
|
|
10
|
-
* `project` reads the reactive source (a prop) and maps it to the local's type:
|
|
11
|
-
* ` => value ?? ''`, ` => (value != null ? String(value) : '')`, etc. The
|
|
12
|
-
* seed is `untrack`ed (the ongoing sync is the `$effect`, not the initializer),
|
|
13
|
-
* and each reconcile is `untrack`ed so an own-edit write never re-triggers it.
|
|
14
|
-
* Reassignment through the `value` setter is a plain local write; only a changed
|
|
15
|
-
* `project` result flows the other way.
|
|
16
|
-
*/
|
|
17
|
-
export function syncedLocal(project) {
|
|
18
|
-
let local = $state(untrack(project));
|
|
19
|
-
$effect(() => {
|
|
20
|
-
const incoming = project();
|
|
21
|
-
untrack(() => {
|
|
22
|
-
if (incoming !== local)
|
|
23
|
-
local = incoming;
|
|
24
|
-
});
|
|
25
|
-
});
|
|
26
|
-
return {
|
|
27
|
-
get value() {
|
|
28
|
-
return local;
|
|
29
|
-
},
|
|
30
|
-
set value(v) {
|
|
31
|
-
local = v;
|
|
32
|
-
}
|
|
33
|
-
};
|
|
34
|
-
}
|