@quillmark/svelte 0.16.0 → 0.17.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 +3 -1
- package/dist/core/theme.css +12 -1
- package/dist/visual/ArrayField.svelte +12 -1
- package/dist/visual/BooleanField.svelte +7 -0
- package/dist/visual/Card.svelte +60 -17
- package/dist/visual/Field.svelte +10 -2
- package/dist/visual/Field.svelte.d.ts +3 -0
- package/dist/visual/MatrixAdd.svelte +211 -0
- package/dist/visual/MatrixAdd.svelte.d.ts +16 -0
- package/dist/visual/MatrixField.svelte +329 -82
- package/dist/visual/MatrixField.svelte.d.ts +8 -5
- package/dist/visual/ObjectField.svelte +23 -6
- package/dist/visual/ObjectField.svelte.d.ts +3 -0
- package/dist/visual/VisualEditorInner.svelte +15 -0
- package/dist/visual/checklists.d.ts +82 -0
- package/dist/visual/checklists.js +171 -0
- package/dist/visual/strings.d.ts +15 -1
- package/dist/visual/strings.js +6 -0
- package/dist/visual/structure.d.ts +59 -31
- package/dist/visual/structure.js +110 -44
- package/package.json +2 -2
|
@@ -30,7 +30,9 @@
|
|
|
30
30
|
member's id. So a cell reads at whatever depth it sits, which nothing here knows.
|
|
31
31
|
|
|
32
32
|
`bare` is the table's row: the same cells with no labels and no figure of their own,
|
|
33
|
-
laid onto the tracks the table's header names ({@link ArrayField}).
|
|
33
|
+
laid onto the tracks the table's header names ({@link ArrayField}). `flat` is
|
|
34
|
+
`ui.layout: flat`: the cells keep their labels and lose the figure, standing at the
|
|
35
|
+
depth of whatever holds the subform.
|
|
34
36
|
-->
|
|
35
37
|
<script lang="ts">
|
|
36
38
|
import type { Content, PathStep, QuillFieldSchema } from '@quillmark/wasm';
|
|
@@ -39,6 +41,7 @@
|
|
|
39
41
|
baseType,
|
|
40
42
|
controlKind,
|
|
41
43
|
declaredContent,
|
|
44
|
+
flatObject,
|
|
42
45
|
humanize,
|
|
43
46
|
isContainer,
|
|
44
47
|
mountKey,
|
|
@@ -93,6 +96,9 @@
|
|
|
93
96
|
/** A table's row: the cells alone, on the tracks the table's header names, with
|
|
94
97
|
* no labels and no vertical of their own. */
|
|
95
98
|
bare?: boolean;
|
|
99
|
+
/** `ui.layout: flat`: no vertical and no rung, the properties at the depth of the
|
|
100
|
+
* list the subform stands in. */
|
|
101
|
+
flat?: boolean;
|
|
96
102
|
/** Raw keydown off a text or prose cell, for a row whose own keys run through its
|
|
97
103
|
* cells: the table's Enter and Backspace ({@link ArrayField}). */
|
|
98
104
|
onCellKey?: (e: KeyboardEvent, key: string) => void;
|
|
@@ -111,6 +117,7 @@
|
|
|
111
117
|
contentAt,
|
|
112
118
|
onCommit,
|
|
113
119
|
bare = false,
|
|
120
|
+
flat = false,
|
|
114
121
|
onCellKey,
|
|
115
122
|
diagnostics
|
|
116
123
|
}: Props = $props();
|
|
@@ -250,6 +257,7 @@
|
|
|
250
257
|
bind:this={rootEl}
|
|
251
258
|
class="qm-object"
|
|
252
259
|
class:qm-object-bare={bare}
|
|
260
|
+
class:qm-object-flat={flat}
|
|
253
261
|
role="group"
|
|
254
262
|
aria-labelledby={labelledBy}
|
|
255
263
|
aria-describedby={describedBy}
|
|
@@ -392,6 +400,7 @@
|
|
|
392
400
|
bind:this={nestedEls[key]}
|
|
393
401
|
value={obj[key] as Record<string, unknown> | undefined}
|
|
394
402
|
properties={sub.properties}
|
|
403
|
+
flat={flatObject(sub)}
|
|
395
404
|
label={ids ? undefined : fallbackName(key, sub)}
|
|
396
405
|
idBase={ids?.control}
|
|
397
406
|
labelledBy={ids?.label}
|
|
@@ -471,6 +480,8 @@
|
|
|
471
480
|
}
|
|
472
481
|
.qm-object:not(.qm-object-bare) {
|
|
473
482
|
container-type: inline-size;
|
|
483
|
+
}
|
|
484
|
+
.qm-object:not(.qm-object-bare, .qm-object-flat) {
|
|
474
485
|
border-inline-start: var(--_qm-vertical-width) solid var(--_qm-border);
|
|
475
486
|
padding-inline-start: var(--_qm-nest);
|
|
476
487
|
padding-block: var(--_qm-space);
|
|
@@ -481,7 +492,14 @@
|
|
|
481
492
|
.qm-object-grid {
|
|
482
493
|
display: grid;
|
|
483
494
|
grid-template-columns: repeat(var(--cols), 1fr);
|
|
484
|
-
gap: var(--_qm-space-2);
|
|
495
|
+
column-gap: var(--_qm-space-2);
|
|
496
|
+
--row-gap: var(--_qm-space-2);
|
|
497
|
+
row-gap: var(--row-gap);
|
|
498
|
+
}
|
|
499
|
+
/* Flat, the rows keep the rhythm of the list the subform stands in: a section's
|
|
500
|
+
(`Card.svelte`) or an enclosing subform's, inherited rather than restated. */
|
|
501
|
+
.qm-object-flat > .qm-object-grid {
|
|
502
|
+
--row-gap: inherit;
|
|
485
503
|
}
|
|
486
504
|
/* A property measures like a field: two tracks over the subform's own rows, so a
|
|
487
505
|
label wrapping in one column leaves its control on the row's baseline rather than a
|
|
@@ -546,10 +564,9 @@
|
|
|
546
564
|
grid-row: auto;
|
|
547
565
|
grid-template-rows: none;
|
|
548
566
|
row-gap: var(--_qm-space-half);
|
|
549
|
-
/* A row
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
align-items: center;
|
|
567
|
+
/* A row reads along its first line: a prose cell that wraps grows downward, and
|
|
568
|
+
the cells beside it stay on the line it started on. */
|
|
569
|
+
align-items: start;
|
|
553
570
|
}
|
|
554
571
|
/* What the row could not hand down stands across it: a diagnostic in the first
|
|
555
572
|
column alone would read as that column's. */
|
|
@@ -31,6 +31,9 @@ interface Props {
|
|
|
31
31
|
/** A table's row: the cells alone, on the tracks the table's header names, with
|
|
32
32
|
* no labels and no vertical of their own. */
|
|
33
33
|
bare?: boolean;
|
|
34
|
+
/** `ui.layout: flat`: no vertical and no rung, the properties at the depth of the
|
|
35
|
+
* list the subform stands in. */
|
|
36
|
+
flat?: boolean;
|
|
34
37
|
/** Raw keydown off a text or prose cell, for a row whose own keys run through its
|
|
35
38
|
* cells: the table's Enter and Backspace ({@link ArrayField}). */
|
|
36
39
|
onCellKey?: (e: KeyboardEvent, key: string) => void;
|
|
@@ -81,6 +81,7 @@
|
|
|
81
81
|
import { createLeafRegistry, type FieldControl } from './leaves.js';
|
|
82
82
|
import { reorder, reorderArm, reorderTrips } from './motion.js';
|
|
83
83
|
import { tipsChannel } from './tips.js';
|
|
84
|
+
import { cardChecklists, setChecklists } from './checklists.js';
|
|
84
85
|
import { patchEditorExt } from './ext.js';
|
|
85
86
|
import Card from './Card.svelte';
|
|
86
87
|
import TipsCard from './TipsCard.svelte';
|
|
@@ -661,6 +662,20 @@
|
|
|
661
662
|
};
|
|
662
663
|
});
|
|
663
664
|
|
|
665
|
+
// The document's checklists, which an open matrix's add box searches whole
|
|
666
|
+
// (`checklists.ts`): off the model, so a list in a closed group or another card is
|
|
667
|
+
// reached without being mounted.
|
|
668
|
+
const lists = $derived(
|
|
669
|
+
cardChecklists(
|
|
670
|
+
[model.main, ...model.cards],
|
|
671
|
+
(c) => c.titleOverride || c.titlePlaceholder,
|
|
672
|
+
(c, field) => pathFor(makeAddr(c.id, c.isMain, field)),
|
|
673
|
+
(c, field) => fieldKeyToString({ card: c.isMain ? undefined : c.id, field }),
|
|
674
|
+
(c, field, next) => commitScalar(c.id, c.isMain, field, next)
|
|
675
|
+
)
|
|
676
|
+
);
|
|
677
|
+
setChecklists({ list: () => lists, land: (path) => void focusField(path) });
|
|
678
|
+
|
|
664
679
|
// ── Public entry points ─────────────────────────────────────────────────────
|
|
665
680
|
/**
|
|
666
681
|
* Resolve a preview {@link Landing} to a mounted leaf and land in it.
|
|
@@ -0,0 +1,82 @@
|
|
|
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
|
+
/** The value a matrix prints: the stored map, else a mapping `default:`. */
|
|
28
|
+
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>[];
|
|
@@ -0,0 +1,171 @@
|
|
|
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
|
+
/** The value a matrix prints: the stored map, else a mapping `default:`. */
|
|
19
|
+
export function matrixPrinted(value, schema) {
|
|
20
|
+
const map = (v) => typeof v === 'object' && v !== null && !Array.isArray(v)
|
|
21
|
+
? v
|
|
22
|
+
: undefined;
|
|
23
|
+
return value == null ? map(schema.default) : map(value);
|
|
24
|
+
}
|
|
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
|
+
}
|
package/dist/visual/strings.d.ts
CHANGED
|
@@ -45,8 +45,22 @@ export interface VisualStrings extends TableChromeStrings, SlashStrings {
|
|
|
45
45
|
/** The tag on the unset sentinel's row, which is what tells it from the member it
|
|
46
46
|
* ghosts ({@link EnumField}). */
|
|
47
47
|
enumUnsetTag: string;
|
|
48
|
-
/** The count in the matrix's label row: how many of
|
|
48
|
+
/** The count in the matrix's label row: how many of its items are held. */
|
|
49
49
|
matrixHeld: (held: number, total: number) => string;
|
|
50
|
+
/** An open matrix's add box at rest, naming the list it adds to: `Add to Staff…`. The box
|
|
51
|
+
* draws its own `+`. `list` is empty for a matrix mounted with no label. */
|
|
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
|
+
/** An added item's title input, named for the list it stands in and its 1-based
|
|
60
|
+
* place among the added items; also what an untitled item is called. */
|
|
61
|
+
matrixItemTitle: (list: string, index: number) => string;
|
|
62
|
+
/** An added item's remove control, named for the item. */
|
|
63
|
+
matrixRemove: (title: string) => string;
|
|
50
64
|
/** The date field's toggle, pressed while the field holds `today`: the render
|
|
51
65
|
* date rather than a written one. */
|
|
52
66
|
dateToday: string;
|
package/dist/visual/strings.js
CHANGED
|
@@ -37,6 +37,12 @@ export const DEFAULT_VISUAL_STRINGS = {
|
|
|
37
37
|
elementUntitled: (label, index) => `${label} ${index}`,
|
|
38
38
|
enumUnsetTag: 'default',
|
|
39
39
|
matrixHeld: (held, total) => `${held} of ${total} held`,
|
|
40
|
+
matrixAdd: (list) => (list ? `Add to ${list}…` : 'Add…'),
|
|
41
|
+
matrixAddNew: (title) => `Add “${title}”`,
|
|
42
|
+
matrixIn: (list) => `in ${list}`,
|
|
43
|
+
matrixHeldTag: 'held',
|
|
44
|
+
matrixItemTitle: (list, index) => `${list ? `${list} item` : 'Item'} ${index}`,
|
|
45
|
+
matrixRemove: (title) => `Remove ${title}`,
|
|
40
46
|
dateToday: 'Today',
|
|
41
47
|
fieldRequired: 'required',
|
|
42
48
|
optionalGhost: 'None',
|
|
@@ -35,7 +35,13 @@ export interface GroupSection {
|
|
|
35
35
|
group: string | undefined;
|
|
36
36
|
label: string;
|
|
37
37
|
fields: FieldModel[];
|
|
38
|
+
/** The field the section's header names in place of a label of its own: the one
|
|
39
|
+
* field of a group section, where that field is {@link flatObject}. */
|
|
40
|
+
head?: FieldModel;
|
|
38
41
|
}
|
|
42
|
+
/** `ui.layout: flat` on a typed dictionary: no frame of its own, its properties
|
|
43
|
+
* standing at the depth of the fields around it rather than a rung in. */
|
|
44
|
+
export declare function flatObject(schema: QuillFieldSchema): boolean;
|
|
39
45
|
/**
|
|
40
46
|
* One card instance projected for rendering: the schema × payload join result
|
|
41
47
|
* the VisualEditor's `$derived` produces and hands to `<Card>`. Positional
|
|
@@ -177,11 +183,11 @@ export declare function controlKind(f: QuillFieldSchema): ControlKind;
|
|
|
177
183
|
* crosses inside untyped container data with no type to read it off, so a literal here
|
|
178
184
|
* would be a second spelling of a reserved key. No variant may declare it. */
|
|
179
185
|
export declare const VARIANT_DISCRIMINANT: "value";
|
|
180
|
-
/** The
|
|
181
|
-
* The boundary
|
|
182
|
-
*
|
|
183
|
-
export declare const
|
|
184
|
-
/** One
|
|
186
|
+
/** The label key of an open matrix's added item, the one cell a roster member never
|
|
187
|
+
* stores. The boundary exports no constant for it, so the package spells it once
|
|
188
|
+
* (canon `SCHEMAS.md` §Matrix). */
|
|
189
|
+
export declare const MATRIX_TITLE = "title";
|
|
190
|
+
/** One item of a matrix: its id and its title. */
|
|
185
191
|
export interface MatrixMember {
|
|
186
192
|
id: string;
|
|
187
193
|
title: string;
|
|
@@ -193,39 +199,60 @@ export declare function matrixMembers(roster: Record<string, string> | undefined
|
|
|
193
199
|
* spelled `constructor`. */
|
|
194
200
|
export declare function memberValue(map: Record<string, unknown> | undefined, id: string): unknown;
|
|
195
201
|
/**
|
|
196
|
-
* Whether a stored member is held
|
|
197
|
-
*
|
|
198
|
-
* `false`
|
|
199
|
-
*
|
|
200
|
-
* engine coerces to false — `false`, `0`, `"false"`, `null` — read false here; every other
|
|
201
|
-
* present scalar reads held, so a document the engine would refuse still draws the tick
|
|
202
|
-
* its spelling claims.
|
|
202
|
+
* Whether a stored member is held: by being present. A mapping is held whatever it
|
|
203
|
+
* carries, `{}` included, and a bare scalar is the tick itself, read as the engine's
|
|
204
|
+
* coercion reads it: `false`, `0`, `"false"` and `null` unheld, every other present
|
|
205
|
+
* scalar held, so a spelling the engine would refuse still draws the tick it claims.
|
|
203
206
|
*/
|
|
204
207
|
export declare function matrixHeld(stored: unknown): boolean;
|
|
205
|
-
/** A member's columns:
|
|
206
|
-
*
|
|
208
|
+
/** A member's columns: its mapping without the reserved keys, `{}` for a bare tick or an
|
|
209
|
+
* absent member. What the columns subform takes as its value, so an edit writes back
|
|
210
|
+
* no `held` a document stored (`validation::held_stored`). */
|
|
207
211
|
export declare function matrixColumns(stored: unknown): Record<string, unknown>;
|
|
212
|
+
/** What a matrix holding nothing commits: the unset field where unset prints nothing
|
|
213
|
+
* held, and the authored `{}` where it would print a `default:` or `none` instead. */
|
|
214
|
+
export declare function matrixEmpty(matrix: QuillFieldSchema): Record<string, unknown> | undefined;
|
|
208
215
|
/**
|
|
209
216
|
* The map after one member moves: `next` written under `id`, or the key dropped where
|
|
210
217
|
* `next` is `undefined`; a map left holding nothing is an unset field. Every other
|
|
211
|
-
* member rides through in the spelling the document had
|
|
212
|
-
* stays `true` while its neighbour is edited.
|
|
218
|
+
* member rides through in the spelling the document had.
|
|
213
219
|
*/
|
|
214
|
-
export declare function commitMember(map: Record<string, unknown> | undefined, id: string, next:
|
|
220
|
+
export declare function commitMember(map: Record<string, unknown> | undefined, id: string, next: unknown): Record<string, unknown> | undefined;
|
|
215
221
|
/**
|
|
216
|
-
* The member a tick or an edit writes
|
|
217
|
-
*
|
|
218
|
-
*
|
|
219
|
-
* Which columns are blank is the engine's rule and not restated here: a column control
|
|
220
|
-
* that clears drops its key (`ObjectField`), so a member emptied by hand arrives with no
|
|
221
|
-
* columns and only then leaves the map.
|
|
222
|
+
* The held member a tick or an edit writes: its columns, the bare tick `true` where
|
|
223
|
+
* it answers none, and an added item's `title` beside them. Unticking is removal
|
|
224
|
+
* ({@link commitMember} with `undefined`), never a write.
|
|
222
225
|
*/
|
|
223
|
-
export declare function memberWrite(
|
|
224
|
-
/**
|
|
225
|
-
*
|
|
226
|
-
export declare function
|
|
226
|
+
export declare function memberWrite(columns: Record<string, unknown>, title?: string): unknown;
|
|
227
|
+
/** Whether `id` is spelled as a matrix id: snake_case, the rule the roster's ids and
|
|
228
|
+
* an added item's key share. */
|
|
229
|
+
export declare function matrixId(id: string): boolean;
|
|
227
230
|
/** Whether `id` is on the roster. */
|
|
231
|
+
export declare function onRoster(matrix: QuillFieldSchema, id: string): boolean;
|
|
232
|
+
/**
|
|
233
|
+
* Whether a step under the matrix names a member, judged on the schema alone as an
|
|
234
|
+
* index step is: a roster id, or on an open matrix any key spelled as an id, the added
|
|
235
|
+
* item it would be. So an unheld member's address resolves.
|
|
236
|
+
*/
|
|
228
237
|
export declare function matrixDeclares(matrix: QuillFieldSchema, id: string): boolean;
|
|
238
|
+
/**
|
|
239
|
+
* The added items a stored map holds, sorted by id as the plate orders them: on an
|
|
240
|
+
* open matrix, each key off the roster spelled as an id whose mapping carries a
|
|
241
|
+
* `title`. Any other key off the roster is the engine's refusal, drawn nowhere and
|
|
242
|
+
* ridden through.
|
|
243
|
+
*/
|
|
244
|
+
export declare function addedItems(matrix: QuillFieldSchema, map: Record<string, unknown> | undefined): MatrixMember[];
|
|
245
|
+
/**
|
|
246
|
+
* The id an item added under `title` takes: the title folded to snake_case, prefixed
|
|
247
|
+
* where it opens on no letter, and suffixed past every id `taken` names, the roster's
|
|
248
|
+
* and the document's alike. The id is the wire's and never drawn, so a later retitle
|
|
249
|
+
* leaves it where it is.
|
|
250
|
+
*/
|
|
251
|
+
export declare function addedId(title: string, taken: (id: string) => boolean): string;
|
|
252
|
+
/** The `object` a matrix member is, for the schema walk: the declared columns, and an
|
|
253
|
+
* added item's `title` beside them. Composed here because the boundary serializes the
|
|
254
|
+
* roster and the columns alone. */
|
|
255
|
+
export declare function matrixMemberSchema(matrix: QuillFieldSchema, id: string): QuillFieldSchema;
|
|
229
256
|
/**
|
|
230
257
|
* The schema at `steps` under `field`, or `undefined` where the schema declares no such
|
|
231
258
|
* place: an index under an `array`, a key under an `object`'s properties, a member id
|
|
@@ -248,7 +275,7 @@ export type ArrayLayout = 'list' | 'table';
|
|
|
248
275
|
* The layout an array takes: `'table'` where the array's own `ui.layout` asks for it
|
|
249
276
|
* and every cell of the row is short (a block prose cell declines it, at load width and
|
|
250
277
|
* every width after), else `'list'`. That every column is a leaf is the loader's
|
|
251
|
-
* contract (`quill::
|
|
278
|
+
* contract (`quill::table_column_not_leaf`); whether a leaf fits a cell is the surface's
|
|
252
279
|
* answer (canon `SCHEMAS.md`): a table composes by position, so a row that would stack
|
|
253
280
|
* inside a cell is not one.
|
|
254
281
|
*/
|
|
@@ -273,9 +300,10 @@ export declare function commitDiscriminant(value: Record<string, unknown> | unde
|
|
|
273
300
|
/**
|
|
274
301
|
* Whether the schema obliges a cell: `default:`'s absence, which is the whole of the
|
|
275
302
|
* obligation (DOCUMENT_MODEL). Exempt are the cells whose absence of one says nothing:
|
|
276
|
-
* a typed dictionary
|
|
277
|
-
*
|
|
278
|
-
* declare none, its unanswered render
|
|
303
|
+
* a typed dictionary, since a namespace declares no `default:` at all and `validate`
|
|
304
|
+
* anchors obligation on the leaves under it; a matrix, whose blank is nothing held, a
|
|
305
|
+
* whole answer; and an optional cell, which can declare none, its unanswered render
|
|
306
|
+
* being `none`.
|
|
279
307
|
*/
|
|
280
308
|
export declare function obliged(schema: QuillFieldSchema): boolean;
|
|
281
309
|
/** `foo_bar` → `Foo bar`: the label fallback when a field declares no `title`. */
|