@quillmark/svelte 0.19.0 → 0.20.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/NOTICE +4 -2
- package/dist/core/codec/blocks.d.ts +11 -2
- package/dist/core/codec/blocks.js +45 -14
- package/dist/core/codec/breaks.d.ts +3 -2
- package/dist/core/codec/breaks.js +3 -2
- package/dist/core/codec/decode.d.ts +7 -3
- package/dist/core/codec/decode.js +34 -8
- package/dist/core/codec/field.d.ts +11 -5
- package/dist/core/codec/field.js +11 -8
- package/dist/core/codec/index.d.ts +1 -1
- package/dist/core/codec/index.js +1 -1
- package/dist/core/codec/islands.d.ts +3 -1
- package/dist/core/codec/islands.js +8 -5
- package/dist/core/codec/markdown-paste.d.ts +9 -0
- package/dist/core/codec/markdown-paste.js +49 -0
- package/dist/core/codec/prose.css +109 -1
- package/dist/core/codec/schema.d.ts +8 -2
- package/dist/core/codec/schema.js +16 -5
- package/dist/core/codec/slash.d.ts +9 -6
- package/dist/core/codec/slash.js +0 -0
- package/dist/core/codec/table-paste.d.ts +7 -0
- package/dist/core/codec/table-paste.js +132 -0
- package/dist/core/codec/table-view.d.ts +12 -0
- package/dist/core/codec/table-view.js +401 -57
- package/dist/core/codec/table.d.ts +38 -16
- package/dist/core/codec/table.js +106 -35
- package/dist/core/theme.css +1 -1
- package/dist/visual/ArrayField.svelte +4 -0
- package/dist/visual/Field.svelte +8 -2
- package/dist/visual/Field.svelte.d.ts +1 -1
- package/dist/visual/FieldHint.svelte +62 -10
- package/dist/visual/FormatPopover.svelte +27 -31
- package/dist/visual/FormatPopover.svelte.d.ts +2 -2
- package/dist/visual/MatrixAdd.svelte +13 -132
- package/dist/visual/MatrixAdd.svelte.d.ts +2 -5
- package/dist/visual/MatrixField.svelte +5 -63
- package/dist/visual/MatrixField.svelte.d.ts +0 -3
- package/dist/visual/ObjectField.svelte +7 -3
- package/dist/visual/ObjectField.svelte.d.ts +4 -2
- package/dist/visual/ProseField.svelte +2 -1
- package/dist/visual/ProseValue.svelte +113 -40
- package/dist/visual/ProseValue.svelte.d.ts +8 -4
- package/dist/visual/VisualEditorInner.svelte +22 -22
- package/dist/visual/VisualEditorInner.svelte.d.ts +2 -2
- package/dist/visual/checklists.d.ts +0 -79
- package/dist/visual/checklists.js +0 -164
- package/dist/visual/guidance.d.ts +15 -0
- package/dist/visual/guidance.js +37 -0
- package/dist/visual/leaves.d.ts +27 -1
- package/dist/visual/leaves.js +46 -0
- package/dist/visual/strings.d.ts +3 -8
- package/dist/visual/strings.js +0 -3
- package/dist/visual/structure.js +1 -1
- package/dist/visual/tips.js +1 -1
- package/package.json +8 -8
|
@@ -1,10 +1,14 @@
|
|
|
1
1
|
import type { Content } from '@quillmark/wasm';
|
|
2
2
|
interface Props {
|
|
3
|
-
/** This leaf's content
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
* boundary read per leaf per render, all but
|
|
3
|
+
/** This leaf's content (the parent's boundary read), `undefined` where nothing is
|
|
4
|
+
* stored: read at mount and wherever {@link value} moves. A thunk rather than a
|
|
5
|
+
* value: the parent re-derives per revision, and a value prop would be a
|
|
6
|
+
* boundary read per leaf per render, all but the moved one discarded. */
|
|
7
7
|
content: () => Content | undefined;
|
|
8
|
+
/** This leaf's value as the parent's re-derive delivers it, stored form and all:
|
|
9
|
+
* compared with the last one delivered, never drawn, so a move is what re-reads
|
|
10
|
+
* {@link content}. */
|
|
11
|
+
value?: unknown;
|
|
8
12
|
/** The default an unset leaf holds, where it prints: drawn at the default rung,
|
|
9
13
|
* and handed up whole with the first edit in it, as `createField` takes one. */
|
|
10
14
|
fallback?: Content;
|
|
@@ -46,7 +46,7 @@
|
|
|
46
46
|
import type { VisualEditorProps } from './props.js';
|
|
47
47
|
import { mergeStrings, setWording } from './strings.js';
|
|
48
48
|
import type { CardId, ChangeSource } from './signals.js';
|
|
49
|
-
import type { FieldController } from '../core/codec/index.js';
|
|
49
|
+
import type { FieldController, LeafViews } from '../core/codec/index.js';
|
|
50
50
|
import {
|
|
51
51
|
IdSeq,
|
|
52
52
|
controlKind,
|
|
@@ -79,11 +79,11 @@
|
|
|
79
79
|
type RoutedDiagnostic
|
|
80
80
|
} from './diagnostics.js';
|
|
81
81
|
import { fieldDomIds, groupPanelId } from './domid.js';
|
|
82
|
-
import { createLeafRegistry, type FieldControl } from './leaves.js';
|
|
82
|
+
import { createLeafRegistry, type ActiveProse, type FieldControl } from './leaves.js';
|
|
83
83
|
import { reorder, reorderArm, reorderTrips } from './motion.js';
|
|
84
84
|
import { tipsChannel } from './tips.js';
|
|
85
|
-
import { cardChecklists, setChecklists } from './checklists.js';
|
|
86
85
|
import { patchEditorExt } from './ext.js';
|
|
86
|
+
import { raiseGuidance } from './guidance.js';
|
|
87
87
|
import Card from './Card.svelte';
|
|
88
88
|
import TipsCard from './TipsCard.svelte';
|
|
89
89
|
import FormatPopover from './FormatPopover.svelte';
|
|
@@ -654,20 +654,6 @@
|
|
|
654
654
|
};
|
|
655
655
|
});
|
|
656
656
|
|
|
657
|
-
// The document's checklists, which an open matrix's add box searches whole
|
|
658
|
-
// (`checklists.ts`): off the model, so a list in a closed group or another card is
|
|
659
|
-
// reached without being mounted.
|
|
660
|
-
const lists = $derived(
|
|
661
|
-
cardChecklists(
|
|
662
|
-
[model.main, ...model.cards],
|
|
663
|
-
(c) => c.titleOverride || c.titlePlaceholder,
|
|
664
|
-
(c, field) => pathFor(makeAddr(c.id, c.isMain, field)),
|
|
665
|
-
(c, field) => fieldKeyToString({ card: c.isMain ? undefined : c.id, field }),
|
|
666
|
-
(c, field, next) => commitScalar(c.id, c.isMain, field, next)
|
|
667
|
-
)
|
|
668
|
-
);
|
|
669
|
-
setChecklists({ list: () => lists, land: (path) => void focusField(path) });
|
|
670
|
-
|
|
671
657
|
// ── Public entry points ─────────────────────────────────────────────────────
|
|
672
658
|
/**
|
|
673
659
|
* Resolve a preview {@link Landing} to a mounted leaf and land in it.
|
|
@@ -694,12 +680,18 @@
|
|
|
694
680
|
const box = await land(found, at.granularity === 'segment' ? undefined : at.pos);
|
|
695
681
|
if (span.alive) bloomInside(box);
|
|
696
682
|
}
|
|
697
|
-
/** The active leaf
|
|
698
|
-
*
|
|
699
|
-
|
|
683
|
+
/** The active prose leaf: the formatting popover's observation seam. The active
|
|
684
|
+
* field's controller where it registered one, else the by-value leaf focused last
|
|
685
|
+
* under it (`leaves.ts`); `undefined` for a form control, which holds no marks to
|
|
686
|
+
* toggle. */
|
|
687
|
+
export function getActiveLeaf(): ActiveProse | undefined {
|
|
700
688
|
if (!activeAddr) return undefined;
|
|
701
689
|
const card = activeAddr.card != null ? activeCardId : undefined;
|
|
702
|
-
|
|
690
|
+
const key = fieldKeyToString({ card, field: activeAddr.field });
|
|
691
|
+
const controller = leaves.prose(key);
|
|
692
|
+
if (controller) return { views: controller as FieldController & LeafViews, controller };
|
|
693
|
+
const views = leaves.value(key);
|
|
694
|
+
return views && { views };
|
|
703
695
|
}
|
|
704
696
|
|
|
705
697
|
/**
|
|
@@ -856,7 +848,15 @@
|
|
|
856
848
|
}
|
|
857
849
|
</script>
|
|
858
850
|
|
|
859
|
-
|
|
851
|
+
<!-- The keydown is a control's, bubbled: the root resolves which description it raises. -->
|
|
852
|
+
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
|
853
|
+
<div
|
|
854
|
+
class="qm-editor {className ?? ''}"
|
|
855
|
+
{style}
|
|
856
|
+
data-qm-root
|
|
857
|
+
bind:this={rootEl}
|
|
858
|
+
onkeydown={raiseGuidance}
|
|
859
|
+
>
|
|
860
860
|
<!-- `main` and the tips card are one block in the stack: the tips card tucks under
|
|
861
861
|
`main`'s bottom corners, so the two share a seam rather than a gutter and the
|
|
862
862
|
wrapper is what holds them to it. -->
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
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
|
-
import type
|
|
4
|
+
import { type ActiveProse } from './leaves.js';
|
|
5
5
|
declare const VisualEditorInner: import("svelte").Component<VisualEditorProps, {
|
|
6
6
|
setCaret: (at: Landing) => Promise<void>;
|
|
7
|
-
getActiveLeaf: () =>
|
|
7
|
+
getActiveLeaf: () => ActiveProse | undefined;
|
|
8
8
|
focusField: (field: DocPath) => Promise<void>;
|
|
9
9
|
insertCard: (kind: string, at?: number) => CardId | undefined;
|
|
10
10
|
removeCard: (cardId: CardId) => void;
|
|
@@ -1,82 +1,3 @@
|
|
|
1
1
|
import type { QuillFieldSchema } from '@quillmark/wasm';
|
|
2
|
-
import type { DocPath } from '../core/address.js';
|
|
3
|
-
import { type CardModel, type MatrixMember } from './structure.js';
|
|
4
|
-
/** One matrix of the document, as the add box reaches it. */
|
|
5
|
-
export interface Checklist {
|
|
6
|
-
/** The field's leaf key: how a mounted matrix tells itself from the rest. */
|
|
7
|
-
key: string;
|
|
8
|
-
/** The field's path, the prefix a member's landing address extends. */
|
|
9
|
-
path: DocPath;
|
|
10
|
-
/** What a result from this list reads under: the field's label, and the card's
|
|
11
|
-
* where the field is not the main card's. */
|
|
12
|
-
label: string;
|
|
13
|
-
schema: QuillFieldSchema;
|
|
14
|
-
/** The held set as it prints: the stored map, else the declared `default:`. */
|
|
15
|
-
value: Record<string, unknown> | undefined;
|
|
16
|
-
/** Commit the whole map, as the field's own control does. */
|
|
17
|
-
commit: (next: Record<string, unknown> | undefined) => void;
|
|
18
|
-
}
|
|
19
|
-
export interface ChecklistIndex {
|
|
20
|
-
list: () => readonly Checklist[];
|
|
21
|
-
/** Reveal and focus a member by its address. */
|
|
22
|
-
land: (path: DocPath) => void;
|
|
23
|
-
}
|
|
24
|
-
export declare function setChecklists(index: ChecklistIndex): void;
|
|
25
|
-
/** The index under a mounted editor; `undefined` off-tree. */
|
|
26
|
-
export declare function checklists(): ChecklistIndex | undefined;
|
|
27
2
|
/** The value a matrix prints: the stored map, else a mapping `default:`. */
|
|
28
3
|
export declare function matrixPrinted(value: unknown, schema: QuillFieldSchema): Record<string, unknown> | undefined;
|
|
29
|
-
/** Every card-level matrix of the cards, in document order. */
|
|
30
|
-
export declare function cardChecklists(cards: readonly CardModel[], cardLabel: (card: CardModel) => string, path: (card: CardModel, field: string) => DocPath | undefined, leafKey: (card: CardModel, field: string) => string, commit: (card: CardModel, field: string, next: Record<string, unknown> | undefined) => void): Checklist[];
|
|
31
|
-
/** Every item a matrix can show: the roster in declaration order, then the items its
|
|
32
|
-
* map adds, in id order. */
|
|
33
|
-
export declare function checklistItems(schema: QuillFieldSchema, value: Record<string, unknown> | undefined): MatrixMember[];
|
|
34
|
-
/** One option of an add box. */
|
|
35
|
-
export interface AddOption {
|
|
36
|
-
/** Stable across one query's options: the option element's id suffix. */
|
|
37
|
-
key: string;
|
|
38
|
-
title: string;
|
|
39
|
-
/** Words after the title: the list a result stands in, its held state. */
|
|
40
|
-
note?: string;
|
|
41
|
-
/** The option that adds what was typed, rather than a result. */
|
|
42
|
-
add?: boolean;
|
|
43
|
-
/** A result the typed words name outright, which Enter takes unarrowed: an add box
|
|
44
|
-
* whose best result is weaker takes its add option instead. */
|
|
45
|
-
strong?: boolean;
|
|
46
|
-
/** What the owner reads back off a chosen option. */
|
|
47
|
-
payload?: unknown;
|
|
48
|
-
}
|
|
49
|
-
/** One search result: an item of some checklist, `list` naming which. */
|
|
50
|
-
export interface ChecklistHit<L> {
|
|
51
|
-
list: L;
|
|
52
|
-
id: string;
|
|
53
|
-
title: string;
|
|
54
|
-
held: boolean;
|
|
55
|
-
score: number;
|
|
56
|
-
/** Every query word claimed a title word whole or as its opening. */
|
|
57
|
-
strong: boolean;
|
|
58
|
-
}
|
|
59
|
-
/**
|
|
60
|
-
* How well `query` names `title`, or `undefined` for not at all. Each query word claims
|
|
61
|
-
* a title word — whole, as its opening, or as an abbreviation of it (`flt cc` names
|
|
62
|
-
* `Flight CC`) — the stronger claims taken first, so `c cc` names `CC Candidate`; a
|
|
63
|
-
* word left over may claim a run of words whose openings it spells (`fcc`, `jqo`). The
|
|
64
|
-
* title the query spells outright ranks first, then one it opens.
|
|
65
|
-
*/
|
|
66
|
-
export declare function matchTitle(query: string, title: string): {
|
|
67
|
-
score: number;
|
|
68
|
-
strong: boolean;
|
|
69
|
-
} | undefined;
|
|
70
|
-
/** {@link matchTitle}'s score, `0` for no match. */
|
|
71
|
-
export declare function matchScore(query: string, title: string): number;
|
|
72
|
-
/** What a search reads of one list: its items and their ticks. */
|
|
73
|
-
export interface Searchable<L> {
|
|
74
|
-
list: L;
|
|
75
|
-
schema: QuillFieldSchema;
|
|
76
|
-
value: Record<string, unknown> | undefined;
|
|
77
|
-
}
|
|
78
|
-
/**
|
|
79
|
-
* The items across `lists` that `query` names, best first; a tie keeps the order of
|
|
80
|
-
* `lists`, then of each list's items.
|
|
81
|
-
*/
|
|
82
|
-
export declare function searchChecklists<L>(query: string, lists: readonly Searchable<L>[], limit?: number): ChecklistHit<L>[];
|
|
@@ -1,20 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* The document's checklists, and the search an open matrix's add box runs over them
|
|
3
|
-
* (VISUAL_EDITOR §"The matrix"). The editor root publishes every card-level matrix off
|
|
4
|
-
* the model it derives, so a list in a collapsed group or another card is searched
|
|
5
|
-
* as surely as the one the author is typing in. A matrix mounted off-tree, or nested
|
|
6
|
-
* in a container, is not indexed and searches its own items alone.
|
|
7
|
-
*/
|
|
8
|
-
import { getContext, setContext } from 'svelte';
|
|
9
|
-
import { addedItems, matrixHeld, matrixMembers, memberValue } from './structure.js';
|
|
10
|
-
const KEY = Symbol('qm-checklists');
|
|
11
|
-
export function setChecklists(index) {
|
|
12
|
-
setContext(KEY, index);
|
|
13
|
-
}
|
|
14
|
-
/** The index under a mounted editor; `undefined` off-tree. */
|
|
15
|
-
export function checklists() {
|
|
16
|
-
return getContext(KEY);
|
|
17
|
-
}
|
|
18
1
|
/** The value a matrix prints: the stored map, else a mapping `default:`. */
|
|
19
2
|
export function matrixPrinted(value, schema) {
|
|
20
3
|
const map = (v) => typeof v === 'object' && v !== null && !Array.isArray(v)
|
|
@@ -22,150 +5,3 @@ export function matrixPrinted(value, schema) {
|
|
|
22
5
|
: undefined;
|
|
23
6
|
return value == null ? map(schema.default) : map(value);
|
|
24
7
|
}
|
|
25
|
-
/** Every card-level matrix of the cards, in document order. */
|
|
26
|
-
export function cardChecklists(cards, cardLabel, path, leafKey, commit) {
|
|
27
|
-
const out = [];
|
|
28
|
-
for (const card of cards) {
|
|
29
|
-
if (card.unschemable)
|
|
30
|
-
continue;
|
|
31
|
-
for (const section of card.sections)
|
|
32
|
-
for (const f of section.fields) {
|
|
33
|
-
if (f.control !== 'matrix')
|
|
34
|
-
continue;
|
|
35
|
-
const at = path(card, f.name);
|
|
36
|
-
if (at == null)
|
|
37
|
-
continue;
|
|
38
|
-
out.push({
|
|
39
|
-
key: leafKey(card, f.name),
|
|
40
|
-
path: at,
|
|
41
|
-
label: card.isMain ? f.label : `${f.label} · ${cardLabel(card)}`,
|
|
42
|
-
schema: f.schema,
|
|
43
|
-
value: matrixPrinted(card.values[f.name], f.schema),
|
|
44
|
-
commit: (next) => commit(card, f.name, next)
|
|
45
|
-
});
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
return out;
|
|
49
|
-
}
|
|
50
|
-
/** Every item a matrix can show: the roster in declaration order, then the items its
|
|
51
|
-
* map adds, in id order. */
|
|
52
|
-
export function checklistItems(schema, value) {
|
|
53
|
-
return [...matrixMembers(schema.members), ...addedItems(schema, value)];
|
|
54
|
-
}
|
|
55
|
-
/** Lowercase, unaccented words, any script: `Exec / Aide / CAG` → `exec aide cag`. */
|
|
56
|
-
function words(s) {
|
|
57
|
-
return s
|
|
58
|
-
.normalize('NFKD')
|
|
59
|
-
.replace(/[\u0300-\u036f]/g, '')
|
|
60
|
-
.toLowerCase()
|
|
61
|
-
.split(/[^\p{L}\p{N}]+/u)
|
|
62
|
-
.filter(Boolean);
|
|
63
|
-
}
|
|
64
|
-
/** `flt` in `flight`: every letter in order, the first letters equal. */
|
|
65
|
-
function abbreviates(token, word) {
|
|
66
|
-
if (token[0] !== word[0])
|
|
67
|
-
return false;
|
|
68
|
-
let i = 0;
|
|
69
|
-
for (const c of word)
|
|
70
|
-
if (c === token[i])
|
|
71
|
-
i++;
|
|
72
|
-
return i === token.length;
|
|
73
|
-
}
|
|
74
|
-
/** How a query word claims one title word: whole (3), as its opening (2), as an
|
|
75
|
-
* abbreviation (1), or not (0). */
|
|
76
|
-
function claim(token, word) {
|
|
77
|
-
return word === token ? 3 : word.startsWith(token) ? 2 : abbreviates(token, word) ? 1 : 0;
|
|
78
|
-
}
|
|
79
|
-
/** The end of the run of free words from `i` whose openings spell `token` in order —
|
|
80
|
-
* `fcc` over `flight cc`, `jqo` over `joint qualified officer` — or `-1`. */
|
|
81
|
-
function runFrom(token, w, free, i) {
|
|
82
|
-
if (i >= w.length || !free[i])
|
|
83
|
-
return -1;
|
|
84
|
-
for (let k = Math.min(token.length, w[i].length); k > 0; k--) {
|
|
85
|
-
if (!w[i].startsWith(token.slice(0, k)))
|
|
86
|
-
continue;
|
|
87
|
-
if (k === token.length)
|
|
88
|
-
return i + 1;
|
|
89
|
-
const end = runFrom(token.slice(k), w, free, i + 1);
|
|
90
|
-
if (end >= 0)
|
|
91
|
-
return end;
|
|
92
|
-
}
|
|
93
|
-
return -1;
|
|
94
|
-
}
|
|
95
|
-
/**
|
|
96
|
-
* How well `query` names `title`, or `undefined` for not at all. Each query word claims
|
|
97
|
-
* a title word — whole, as its opening, or as an abbreviation of it (`flt cc` names
|
|
98
|
-
* `Flight CC`) — the stronger claims taken first, so `c cc` names `CC Candidate`; a
|
|
99
|
-
* word left over may claim a run of words whose openings it spells (`fcc`, `jqo`). The
|
|
100
|
-
* title the query spells outright ranks first, then one it opens.
|
|
101
|
-
*/
|
|
102
|
-
export function matchTitle(query, title) {
|
|
103
|
-
const q = words(query);
|
|
104
|
-
const w = words(title);
|
|
105
|
-
if (!q.length || !w.length)
|
|
106
|
-
return undefined;
|
|
107
|
-
const free = w.map(() => true);
|
|
108
|
-
const left = q.map(() => true);
|
|
109
|
-
let score = 0;
|
|
110
|
-
let strong = true;
|
|
111
|
-
for (const level of [3, 2, 1])
|
|
112
|
-
q.forEach((token, t) => {
|
|
113
|
-
if (!left[t])
|
|
114
|
-
return;
|
|
115
|
-
const at = w.findIndex((word, i) => free[i] && claim(token, word) === level);
|
|
116
|
-
if (at < 0)
|
|
117
|
-
return;
|
|
118
|
-
free[at] = left[t] = false;
|
|
119
|
-
score += level;
|
|
120
|
-
if (level === 1)
|
|
121
|
-
strong = false;
|
|
122
|
-
});
|
|
123
|
-
for (let t = 0; t < q.length; t++) {
|
|
124
|
-
if (!left[t])
|
|
125
|
-
continue;
|
|
126
|
-
let found = false;
|
|
127
|
-
for (let i = 0; i < w.length && !found; i++) {
|
|
128
|
-
const end = runFrom(q[t], w, free, i);
|
|
129
|
-
if (end - i < 2)
|
|
130
|
-
continue;
|
|
131
|
-
for (let k = i; k < end; k++)
|
|
132
|
-
free[k] = false;
|
|
133
|
-
found = true;
|
|
134
|
-
}
|
|
135
|
-
if (!found)
|
|
136
|
-
return undefined;
|
|
137
|
-
score += 1;
|
|
138
|
-
strong = false;
|
|
139
|
-
}
|
|
140
|
-
const typed = q.join(' ');
|
|
141
|
-
const whole = w.join(' ');
|
|
142
|
-
return { score: score + (whole === typed ? 3 : whole.startsWith(typed) ? 1 : 0), strong };
|
|
143
|
-
}
|
|
144
|
-
/** {@link matchTitle}'s score, `0` for no match. */
|
|
145
|
-
export function matchScore(query, title) {
|
|
146
|
-
return matchTitle(query, title)?.score ?? 0;
|
|
147
|
-
}
|
|
148
|
-
/**
|
|
149
|
-
* The items across `lists` that `query` names, best first; a tie keeps the order of
|
|
150
|
-
* `lists`, then of each list's items.
|
|
151
|
-
*/
|
|
152
|
-
export function searchChecklists(query, lists, limit = 8) {
|
|
153
|
-
const hits = [];
|
|
154
|
-
for (const { list, schema, value } of lists)
|
|
155
|
-
for (const item of checklistItems(schema, value)) {
|
|
156
|
-
const match = matchTitle(query, item.title);
|
|
157
|
-
if (match)
|
|
158
|
-
hits.push({
|
|
159
|
-
list,
|
|
160
|
-
id: item.id,
|
|
161
|
-
title: item.title,
|
|
162
|
-
held: matrixHeld(memberValue(value, item.id)),
|
|
163
|
-
...match
|
|
164
|
-
});
|
|
165
|
-
}
|
|
166
|
-
return hits
|
|
167
|
-
.map((h, i) => [h, i])
|
|
168
|
-
.sort(([a, i], [b, j]) => b.score - a.score || i - j)
|
|
169
|
-
.slice(0, limit)
|
|
170
|
-
.map(([h]) => h);
|
|
171
|
-
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/** `Mod-/`, read off `key` so a layout that shifts to reach `/` still matches; either
|
|
2
|
+
* modifier stands for `Mod`. */
|
|
3
|
+
export declare function isGuidanceKey(e: KeyboardEvent): boolean;
|
|
4
|
+
/**
|
|
5
|
+
* The guidance trigger for the description nearest `from`: the innermost element
|
|
6
|
+
* carrying `aria-describedby` whose ids name a parked description with a trigger
|
|
7
|
+
* beside it. A trigger points at its own parked node through that same attribute,
|
|
8
|
+
* which is the relation read here; ids that are not a description (an array's count,
|
|
9
|
+
* a prose leaf's note) are skipped, and an undescribed property falls through to the
|
|
10
|
+
* group around it.
|
|
11
|
+
*/
|
|
12
|
+
export declare function guidanceTrigger(from: Element, root: Element): HTMLElement | undefined;
|
|
13
|
+
/** Root keydown: the key raises (or lowers) the nearest guidance. A trigger's `click()`
|
|
14
|
+
* is an activation with no pointer behind it, which `FieldHint` toggles on. */
|
|
15
|
+
export declare function raiseGuidance(e: KeyboardEvent): void;
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
// The guidance key: `Mod-/` on a control raises the description that control carries,
|
|
2
|
+
// the keyboard's way in to a `FieldHint` whose glyph is out of the tab order.
|
|
3
|
+
/** `Mod-/`, read off `key` so a layout that shifts to reach `/` still matches; either
|
|
4
|
+
* modifier stands for `Mod`. */
|
|
5
|
+
export function isGuidanceKey(e) {
|
|
6
|
+
return e.key === '/' && (e.ctrlKey || e.metaKey) && !e.altKey && !e.isComposing;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* The guidance trigger for the description nearest `from`: the innermost element
|
|
10
|
+
* carrying `aria-describedby` whose ids name a parked description with a trigger
|
|
11
|
+
* beside it. A trigger points at its own parked node through that same attribute,
|
|
12
|
+
* which is the relation read here; ids that are not a description (an array's count,
|
|
13
|
+
* a prose leaf's note) are skipped, and an undescribed property falls through to the
|
|
14
|
+
* group around it.
|
|
15
|
+
*/
|
|
16
|
+
export function guidanceTrigger(from, root) {
|
|
17
|
+
for (let el = from.closest('[aria-describedby]'); el;) {
|
|
18
|
+
for (const id of el.getAttribute('aria-describedby').split(/\s+/)) {
|
|
19
|
+
const trigger = root.querySelector(`.qm-field-hint[aria-describedby="${id}"]`);
|
|
20
|
+
if (trigger)
|
|
21
|
+
return trigger;
|
|
22
|
+
}
|
|
23
|
+
el = el.parentElement?.closest('[aria-describedby]') ?? null;
|
|
24
|
+
}
|
|
25
|
+
return undefined;
|
|
26
|
+
}
|
|
27
|
+
/** Root keydown: the key raises (or lowers) the nearest guidance. A trigger's `click()`
|
|
28
|
+
* is an activation with no pointer behind it, which `FieldHint` toggles on. */
|
|
29
|
+
export function raiseGuidance(e) {
|
|
30
|
+
if (!isGuidanceKey(e) || !(e.target instanceof Element))
|
|
31
|
+
return;
|
|
32
|
+
const trigger = guidanceTrigger(e.target, e.currentTarget);
|
|
33
|
+
if (!trigger)
|
|
34
|
+
return;
|
|
35
|
+
e.preventDefault();
|
|
36
|
+
trigger.click();
|
|
37
|
+
}
|
package/dist/visual/leaves.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { PathStep } from '@quillmark/wasm';
|
|
2
|
-
import type { FieldController } from '../core/codec/index.js';
|
|
2
|
+
import type { FieldController, LeafViews } from '../core/codec/index.js';
|
|
3
3
|
/**
|
|
4
4
|
* What a landing at depth hands back: the box the arrival wash blooms in, now or once
|
|
5
5
|
* the row it had to open has rendered. A row two closed boxes down does not exist
|
|
@@ -60,8 +60,34 @@ export interface LeafRegistry {
|
|
|
60
60
|
control(key: string): FieldControl | undefined;
|
|
61
61
|
/** The codec seam at `key`, `undefined` for a form control. */
|
|
62
62
|
prose(key: string): FieldController | undefined;
|
|
63
|
+
/** A by-value prose leaf under the field at `key`, by its views, on its focus. */
|
|
64
|
+
focusValue(key: string, leaf: LeafViews): void;
|
|
65
|
+
/** Dropped only by the leaf it names, so a leaf leaving takes no sibling's entry. */
|
|
66
|
+
releaseValue(key: string, leaf: LeafViews): void;
|
|
67
|
+
/** The by-value leaf focused last under `key`. */
|
|
68
|
+
value(key: string): LeafViews | undefined;
|
|
63
69
|
/** Drop everything: the surface going away as a whole, where a leaf's cleanup
|
|
64
70
|
* order relative to the parent's is Svelte's business. */
|
|
65
71
|
clear(): void;
|
|
66
72
|
}
|
|
67
73
|
export declare function createLeafRegistry(): LeafRegistry;
|
|
74
|
+
/**
|
|
75
|
+
* The active prose leaf as the formatting popover reaches it: the views a selection is
|
|
76
|
+
* read from and a mark toggled at, and the controller where the leaf is a field's. A
|
|
77
|
+
* by-value leaf has none, its anchors being dropped on the parent's value write, so the
|
|
78
|
+
* anchor verbs the controller carries have nothing to act on there.
|
|
79
|
+
*/
|
|
80
|
+
export interface ActiveProse {
|
|
81
|
+
views: LeafViews;
|
|
82
|
+
controller?: FieldController;
|
|
83
|
+
}
|
|
84
|
+
/** The by-value lane with a field's key bound: what a leaf under that field announces
|
|
85
|
+
* itself through, knowing no key of its own. */
|
|
86
|
+
export interface ValueLeafSeat {
|
|
87
|
+
focus(leaf: LeafViews): void;
|
|
88
|
+
release(leaf: LeafViews): void;
|
|
89
|
+
}
|
|
90
|
+
/** Seat every by-value leaf under the calling field, at any depth of its containers. */
|
|
91
|
+
export declare function seatValueLeaves(seat: ValueLeafSeat): void;
|
|
92
|
+
/** The seat under a mounted field; `undefined` off-tree. */
|
|
93
|
+
export declare function valueLeafSeat(): ValueLeafSeat | undefined;
|
package/dist/visual/leaves.js
CHANGED
|
@@ -1,6 +1,32 @@
|
|
|
1
|
+
// The leaf registry: what a landing verb resolves a `DocPath` to, and the seam the
|
|
2
|
+
// mounted tree registers itself into (VISUAL_EDITOR §"Focus and the preview bridge").
|
|
3
|
+
//
|
|
4
|
+
// Two lanes behind one lookup. Every mounted field registers a {@link FieldControl} —
|
|
5
|
+
// take the caret, and a box to bloom — which is the whole of what a reveal and a
|
|
6
|
+
// focus need, and the whole of what a form control has. A prose leaf registers its
|
|
7
|
+
// `FieldController` instead: a superset, carrying the codec seam (a caret at a USV
|
|
8
|
+
// offset, the formatting popover's observation) that no `<input>` answers. So
|
|
9
|
+
// `control` covers every field and `prose` covers the leaves that have one, and the
|
|
10
|
+
// verbs ask for the half they use: `focusField` never needs the codec, and
|
|
11
|
+
// `getActiveLeaf` returns nothing for a focused text input rather than raising a
|
|
12
|
+
// formatting toolbar over one.
|
|
13
|
+
//
|
|
14
|
+
// A by-value prose leaf (`ProseValue`) is in neither lane: it is not addressable, and
|
|
15
|
+
// every element of an array and every cell of a row shares its field's key. It
|
|
16
|
+
// announces its views on focus into a third lane under that key, which holds the one
|
|
17
|
+
// focused last and is the popover's alone: `getActiveLeaf` falls back to it where the
|
|
18
|
+
// active field registered no controller, and a landing never reads it.
|
|
19
|
+
//
|
|
20
|
+
// A registry rather than a DOM query, for the reason `Card.revealLeaf` is a call: a
|
|
21
|
+
// control's focus is its own (a PM view restores a selection, a date field lands on
|
|
22
|
+
// its first segment, an array lands on its first element or its add affordance), so
|
|
23
|
+
// the surface holds the answers it already gives a label click instead of re-deriving
|
|
24
|
+
// them from markup.
|
|
25
|
+
import { getContext, setContext } from 'svelte';
|
|
1
26
|
export function createLeafRegistry() {
|
|
2
27
|
const controls = new Map();
|
|
3
28
|
const proses = new Map();
|
|
29
|
+
const values = new Map();
|
|
4
30
|
return {
|
|
5
31
|
registerProse(key, controller) {
|
|
6
32
|
proses.set(key, controller);
|
|
@@ -23,9 +49,29 @@ export function createLeafRegistry() {
|
|
|
23
49
|
prose(key) {
|
|
24
50
|
return proses.get(key);
|
|
25
51
|
},
|
|
52
|
+
focusValue(key, leaf) {
|
|
53
|
+
values.set(key, leaf);
|
|
54
|
+
},
|
|
55
|
+
releaseValue(key, leaf) {
|
|
56
|
+
if (values.get(key) === leaf)
|
|
57
|
+
values.delete(key);
|
|
58
|
+
},
|
|
59
|
+
value(key) {
|
|
60
|
+
return values.get(key);
|
|
61
|
+
},
|
|
26
62
|
clear() {
|
|
27
63
|
controls.clear();
|
|
28
64
|
proses.clear();
|
|
65
|
+
values.clear();
|
|
29
66
|
}
|
|
30
67
|
};
|
|
31
68
|
}
|
|
69
|
+
const SEAT = Symbol('qm-value-leaf');
|
|
70
|
+
/** Seat every by-value leaf under the calling field, at any depth of its containers. */
|
|
71
|
+
export function seatValueLeaves(seat) {
|
|
72
|
+
setContext(SEAT, seat);
|
|
73
|
+
}
|
|
74
|
+
/** The seat under a mounted field; `undefined` off-tree. */
|
|
75
|
+
export function valueLeafSeat() {
|
|
76
|
+
return getContext(SEAT);
|
|
77
|
+
}
|
package/dist/visual/strings.d.ts
CHANGED
|
@@ -50,12 +50,6 @@ export interface VisualStrings extends TableChromeStrings, SlashStrings {
|
|
|
50
50
|
/** An open matrix's add box at rest, naming the list it adds to: `Add to Staff…`. The box
|
|
51
51
|
* draws its own `+`. `list` is empty for a matrix mounted with no label. */
|
|
52
52
|
matrixAdd: (list: string) => string;
|
|
53
|
-
/** The add box's last option, which adds what was typed as an item of this list. */
|
|
54
|
-
matrixAddNew: (title: string) => string;
|
|
55
|
-
/** Beside a result from another list: the list it would be ticked in. */
|
|
56
|
-
matrixIn: (list: string) => string;
|
|
57
|
-
/** Beside a result already held: picking it lands on it and ticks nothing. */
|
|
58
|
-
matrixHeldTag: string;
|
|
59
53
|
/** An added item's title input, named for the list it stands in and its 1-based
|
|
60
54
|
* place among the added items; also what an untitled item is called. */
|
|
61
55
|
matrixItemTitle: (list: string, index: number) => string;
|
|
@@ -69,8 +63,9 @@ export interface VisualStrings extends TableChromeStrings, SlashStrings {
|
|
|
69
63
|
/** What an unset optional cell (`t?`) ghosts at rest: the `none` it prints
|
|
70
64
|
* unanswered, in the product's words. */
|
|
71
65
|
optionalGhost: string;
|
|
72
|
-
/** Under a prose leaf holding more structure than it can edit (paragraphs, a list
|
|
73
|
-
* an island in a one-line cell), which it draws
|
|
66
|
+
/** Under a prose leaf holding more structure than it can edit (paragraphs, a list or
|
|
67
|
+
* an island in a one-line leaf, an island in any subform cell), which it draws
|
|
68
|
+
* read-only rather than lose. */
|
|
74
69
|
proseHeld: string;
|
|
75
70
|
formatGroup: string;
|
|
76
71
|
formatBold: string;
|
package/dist/visual/strings.js
CHANGED
|
@@ -38,9 +38,6 @@ export const DEFAULT_VISUAL_STRINGS = {
|
|
|
38
38
|
enumUnsetTag: 'default',
|
|
39
39
|
matrixHeld: (held, total) => `${held} of ${total} held`,
|
|
40
40
|
matrixAdd: (list) => (list ? `Add to ${list}…` : 'Add…'),
|
|
41
|
-
matrixAddNew: (title) => `Add “${title}”`,
|
|
42
|
-
matrixIn: (list) => `in ${list}`,
|
|
43
|
-
matrixHeldTag: 'held',
|
|
44
41
|
matrixItemTitle: (list, index) => `${list ? `${list} item` : 'Item'} ${index}`,
|
|
45
42
|
matrixRemove: (title) => `Remove ${title}`,
|
|
46
43
|
dateToday: 'Today',
|
package/dist/visual/structure.js
CHANGED
package/dist/visual/tips.js
CHANGED
|
@@ -39,7 +39,7 @@ export function tipsChannel(raw) {
|
|
|
39
39
|
export function renderTip(markdown, onError) {
|
|
40
40
|
const { importMarkdown } = core();
|
|
41
41
|
try {
|
|
42
|
-
return renderContent(importMarkdown(markdown), inlineSchema);
|
|
42
|
+
return renderContent(importMarkdown(markdown).content, inlineSchema);
|
|
43
43
|
}
|
|
44
44
|
catch (e) {
|
|
45
45
|
reportError(onError, {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@quillmark/svelte",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.20.1",
|
|
4
4
|
"description": "Editor, live-preview and source surfaces over a Quillmark WASM session.",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -69,23 +69,23 @@
|
|
|
69
69
|
"check": "svelte-check --tsconfig ./tsconfig.json --fail-on-warnings"
|
|
70
70
|
},
|
|
71
71
|
"peerDependencies": {
|
|
72
|
-
"@quillmark/wasm": ">=0.
|
|
72
|
+
"@quillmark/wasm": ">=0.124.0-0",
|
|
73
73
|
"svelte": "^5.33.0"
|
|
74
74
|
},
|
|
75
75
|
"dependencies": {
|
|
76
76
|
"@internationalized/date": "^3.12.2",
|
|
77
77
|
"bits-ui": "^2.19.0",
|
|
78
78
|
"esm-env": "^1.2.2",
|
|
79
|
-
"prosemirror-commands": "1.7.
|
|
80
|
-
"prosemirror-gapcursor": "1.
|
|
81
|
-
"prosemirror-history": "1.5.
|
|
79
|
+
"prosemirror-commands": "1.7.2",
|
|
80
|
+
"prosemirror-gapcursor": "1.4.1",
|
|
81
|
+
"prosemirror-history": "1.5.1",
|
|
82
82
|
"prosemirror-inputrules": "1.5.1",
|
|
83
83
|
"prosemirror-keymap": "1.2.3",
|
|
84
|
-
"prosemirror-model": "1.25.
|
|
84
|
+
"prosemirror-model": "1.25.12",
|
|
85
85
|
"prosemirror-schema-list": "1.5.1",
|
|
86
86
|
"prosemirror-state": "1.4.4",
|
|
87
|
-
"prosemirror-transform": "1.12.
|
|
88
|
-
"prosemirror-view": "1.42.
|
|
87
|
+
"prosemirror-transform": "1.12.2",
|
|
88
|
+
"prosemirror-view": "1.42.6"
|
|
89
89
|
},
|
|
90
90
|
"devDependencies": {
|
|
91
91
|
"@sveltejs/package": "^3.0.0",
|