@quillmark/svelte 0.12.0 → 0.13.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/dist/core/codec/decode.d.ts +17 -0
- package/dist/core/codec/decode.js +57 -4
- package/dist/core/codec/field.d.ts +64 -15
- package/dist/core/codec/field.js +201 -65
- package/dist/core/codec/index.d.ts +3 -3
- package/dist/core/codec/index.js +3 -3
- package/dist/core/codec/keymap.d.ts +5 -4
- package/dist/core/codec/keymap.js +5 -4
- package/dist/core/codec/prose.css +42 -0
- package/dist/core/codec/schema.d.ts +24 -5
- package/dist/core/codec/schema.js +40 -9
- package/dist/core/errors.d.ts +3 -2
- package/dist/core/theme.css +9 -6
- package/dist/preview/paint.js +21 -17
- package/dist/visual/ArrayField.svelte +66 -22
- package/dist/visual/ArrayField.svelte.d.ts +2 -0
- package/dist/visual/BooleanField.svelte +28 -6
- package/dist/visual/BooleanField.svelte.d.ts +2 -0
- package/dist/visual/Card.svelte +3 -3
- package/dist/visual/DateField.svelte +66 -31
- package/dist/visual/EnumField.svelte +27 -9
- package/dist/visual/EnumField.svelte.d.ts +5 -0
- package/dist/visual/Field.svelte +45 -10
- package/dist/visual/FormatPopover.svelte +4 -3
- package/dist/visual/MatrixField.svelte +3 -2
- package/dist/visual/NumberField.svelte +31 -14
- package/dist/visual/NumberField.svelte.d.ts +5 -1
- package/dist/visual/ObjectField.svelte +62 -15
- package/dist/visual/ProseField.svelte +53 -23
- package/dist/visual/ProseField.svelte.d.ts +15 -3
- package/dist/visual/ProseValue.svelte +102 -46
- package/dist/visual/ProseValue.svelte.d.ts +17 -5
- package/dist/visual/TextField.svelte +35 -18
- package/dist/visual/TextField.svelte.d.ts +7 -0
- package/dist/visual/VariantField.svelte +1 -0
- package/dist/visual/VisualEditorInner.svelte +24 -24
- package/dist/visual/controls.css +16 -5
- package/dist/visual/strings.d.ts +8 -1
- package/dist/visual/strings.js +2 -0
- package/dist/visual/structure.d.ts +77 -43
- package/dist/visual/structure.js +132 -57
- package/package.json +2 -2
package/dist/visual/structure.js
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
// The schema × payload join, done by the editor (VISUAL_EDITOR §Structure). Pure
|
|
2
2
|
// functions only (no runes, no Document reads) so the ordering, control
|
|
3
|
-
// dispatch, group layout,
|
|
3
|
+
// dispatch, group layout, titles, ghosts, and session-identity bookkeeping
|
|
4
4
|
// are unit-testable in isolation (tests/visual/structure.test.ts). The reactive
|
|
5
5
|
// orchestration (revision counter, live doc reads) lives in VisualEditor.svelte;
|
|
6
6
|
// this module is the projection math it feeds.
|
|
7
7
|
import { VARIANT_DISCRIMINANT_KEY } from '@quillmark/wasm';
|
|
8
|
+
import { core } from '../core/lifecycle.js';
|
|
8
9
|
/** The container controls: the ones a nested address walks into and a deep
|
|
9
10
|
* diagnostic routes through. */
|
|
10
11
|
export function isContainer(kind) {
|
|
@@ -33,36 +34,112 @@ export const NO_RESOLVED_ROWS = { fields: [], body: null };
|
|
|
33
34
|
export function resolvedByCardIndex(resolved) {
|
|
34
35
|
return new Map((resolved?.cards ?? []).map((c) => [c.index, { fields: c.fields, body: c.body }]));
|
|
35
36
|
}
|
|
36
|
-
/**
|
|
37
|
-
*
|
|
38
|
-
*
|
|
37
|
+
/** What an unset field shows of its `default:`: the resolved value the render would
|
|
38
|
+
* use (`source === 'default'`), else undefined: an `authored` field shows its value,
|
|
39
|
+
* a `blank` field has no default to show. */
|
|
39
40
|
export function ghostDefault(row) {
|
|
40
41
|
return row?.source === 'default' ? row.value : undefined;
|
|
41
42
|
}
|
|
42
43
|
/** A ghost value's string form, or undefined for null/object (only text ghosts
|
|
43
|
-
* render a placeholder).
|
|
44
|
-
*
|
|
45
|
-
*
|
|
46
|
-
* thing to display as a placeholder (FIELD_PROVENANCE). */
|
|
44
|
+
* render a placeholder). A scalar's resolved default reads through it; a content
|
|
45
|
+
* field's resolves as `Content`, which a prose leaf reads by its text instead
|
|
46
|
+
* ({@link titleText}). */
|
|
47
47
|
export function stringifyGhost(ghost) {
|
|
48
48
|
return ghost != null && typeof ghost !== 'object' ? String(ghost) : undefined;
|
|
49
49
|
}
|
|
50
50
|
/**
|
|
51
|
-
* The empty body's ghost: the resolved body `default:`, else the
|
|
52
|
-
* wording, else the flat built-in. The
|
|
53
|
-
*
|
|
54
|
-
* written, and wording placed over it would make that
|
|
55
|
-
*
|
|
56
|
-
* promise
|
|
51
|
+
* The empty body's ghost: the resolved body `default:`, else the kind's
|
|
52
|
+
* `body.example`, else the consumer's wording, else the flat built-in. The
|
|
53
|
+
* `default:` wins because it is the only one that describes the render: it promises
|
|
54
|
+
* what prints if nothing is written, and wording placed over it would make that
|
|
55
|
+
* promise unreadable. The rest are invitations, and an invitation belongs only where
|
|
56
|
+
* there is no promise; the quill's own comes first, being the one an agent working
|
|
57
|
+
* from the blueprint sees too.
|
|
57
58
|
*/
|
|
58
|
-
export function resolveBodyGhost(resolvedDefault, custom, builtIn) {
|
|
59
|
-
return resolvedDefault || custom || builtIn;
|
|
59
|
+
export function resolveBodyGhost(resolvedDefault, example, custom, builtIn) {
|
|
60
|
+
return resolvedDefault || example || custom || builtIn;
|
|
61
|
+
}
|
|
62
|
+
/** Whether a cell is optional: a `t?` type, which renders `none` unanswered and so
|
|
63
|
+
* never declares a `default:` (canon `SCHEMAS.md` §"Optional cells"). */
|
|
64
|
+
export function optionalCell(f) {
|
|
65
|
+
return f.type.endsWith('?');
|
|
66
|
+
}
|
|
67
|
+
/** The type a cell's control reads: the declared one, an optional cell's `?` stripped.
|
|
68
|
+
* The `?` moves the render floor and nothing else, so every read of a type is this. */
|
|
69
|
+
export function baseType(f) {
|
|
70
|
+
return (optionalCell(f) ? f.type.slice(0, -1) : f.type);
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* A value the schema declares, as ghost text: a `default:`, an `example:` or a
|
|
74
|
+
* `body.example`. Markdown — a `richtext` value — ghosts as the text it renders, one
|
|
75
|
+
* block to a line, as a resolved default does ({@link titleText}); anything else as
|
|
76
|
+
* itself. `undefined` for a blank or a non-scalar.
|
|
77
|
+
*/
|
|
78
|
+
export function declaredGhost(v, markdown) {
|
|
79
|
+
const text = stringifyGhost(v)?.trim();
|
|
80
|
+
if (!text)
|
|
81
|
+
return undefined;
|
|
82
|
+
return (markdown ? core().importMarkdown(text).text.trim() : text) || undefined;
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* A scalar `default:` as the text an unset control holds, where it prints: as
|
|
86
|
+
* declared, `undefined` for none, a blank or a non-scalar. A default of spaces prints
|
|
87
|
+
* nothing a reader sees, so a control holds none and draws its example instead.
|
|
88
|
+
*/
|
|
89
|
+
export function printedText(v) {
|
|
90
|
+
const text = stringifyGhost(v);
|
|
91
|
+
return text?.trim() ? text : undefined;
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* A declared content literal as the `Content` it holds: a `richtext` one's markdown
|
|
95
|
+
* imported, a `plaintext` one's text taken literally, one paragraph whose later lines
|
|
96
|
+
* continue it, as the literal codec reads a stored one. What a cell's static
|
|
97
|
+
* `default:` stands in its leaf as, where no resolved row reaches it. `undefined` for
|
|
98
|
+
* a blank or a non-scalar.
|
|
99
|
+
*/
|
|
100
|
+
export function declaredContent(v, markdown) {
|
|
101
|
+
const text = printedText(v);
|
|
102
|
+
if (text === undefined)
|
|
103
|
+
return undefined;
|
|
104
|
+
if (markdown)
|
|
105
|
+
return core().importMarkdown(text);
|
|
106
|
+
return {
|
|
107
|
+
text,
|
|
108
|
+
lines: text
|
|
109
|
+
.split('\n')
|
|
110
|
+
.map((_, i) => i === 0
|
|
111
|
+
? { containers: [], kind: 'para' }
|
|
112
|
+
: { containers: [], kind: 'para', continues: true }),
|
|
113
|
+
marks: [],
|
|
114
|
+
islands: []
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* The `example:` an unset free-text cell ghosts: a `string`, `plaintext` or `richtext`
|
|
119
|
+
* whose `default:` prints nothing. Every other type takes none. A default that prints
|
|
120
|
+
* dominates, standing in the control as the value it is; a type-empty one, the
|
|
121
|
+
* skippable marker, prints nothing and leaves the example the only thing to draw.
|
|
122
|
+
*/
|
|
123
|
+
export function exampleGhost(f) {
|
|
124
|
+
const kind = controlKind(f);
|
|
125
|
+
if (kind !== 'text' && kind !== 'prose')
|
|
126
|
+
return undefined;
|
|
127
|
+
const markdown = baseType(f) === 'richtext';
|
|
128
|
+
if (declaredGhost(f.default, markdown) !== undefined)
|
|
129
|
+
return undefined;
|
|
130
|
+
return declaredGhost(f.example, markdown);
|
|
131
|
+
}
|
|
132
|
+
/** A field's `{#each}` key: its name and what a mounted leaf reads once, its type and
|
|
133
|
+
* `inline` and its elements'. A retype or a quill swap that changes any of them under a
|
|
134
|
+
* name remounts the field, where a kept leaf would commit on the old schema. */
|
|
135
|
+
export function mountKey(name, f) {
|
|
136
|
+
return `${name}\u0000${f.type}\u0000${f.inline === true}\u0000${f.items?.type ?? ''}\u0000${f.items?.inline === true}`;
|
|
60
137
|
}
|
|
61
138
|
/** Map a field schema to its control (precedence: prose › enum › text › …).
|
|
62
139
|
* An array's element control is this over `items`: a missing `items` is a text
|
|
63
140
|
* element. */
|
|
64
141
|
export function controlKind(f) {
|
|
65
|
-
switch (f
|
|
142
|
+
switch (baseType(f)) {
|
|
66
143
|
case 'richtext':
|
|
67
144
|
case 'plaintext':
|
|
68
145
|
return 'prose';
|
|
@@ -237,29 +314,22 @@ function stepInto(schema, step) {
|
|
|
237
314
|
}
|
|
238
315
|
// ── Row summaries and layouts ────────────────────────────────────────────────
|
|
239
316
|
/** Whether a cell is one line tall by declaration: the shape a table column and a row
|
|
240
|
-
* summary can hold. A
|
|
317
|
+
* summary can hold. A prose cell is one where it declares `inline`, `richtext` or
|
|
318
|
+
* `plaintext` alike. */
|
|
241
319
|
export function shortCell(sub) {
|
|
242
320
|
const kind = controlKind(sub);
|
|
243
321
|
if (kind === 'prose')
|
|
244
|
-
return
|
|
322
|
+
return !!sub.inline;
|
|
245
323
|
return (kind === 'text' || kind === 'enum' || kind === 'number' || kind === 'boolean' || kind === 'date');
|
|
246
324
|
}
|
|
247
325
|
/**
|
|
248
|
-
*
|
|
249
|
-
*
|
|
250
|
-
*
|
|
251
|
-
*
|
|
252
|
-
* describing a row, where `layout` sits on the array's own `ui`, describing the array.
|
|
326
|
+
* The first short text cell's words, in declaration order — a `string`, or an inline
|
|
327
|
+
* `richtext` / `plaintext` — read through {@link titleText}; `undefined` while none of
|
|
328
|
+
* them has any. What names an instance from its own values: a collapsed row, and a
|
|
329
|
+
* card whose kind declares no `title`.
|
|
253
330
|
*/
|
|
254
|
-
|
|
255
|
-
const
|
|
256
|
-
const template = items?.ui?.title;
|
|
257
|
-
if (template && template.trim()) {
|
|
258
|
-
const shown = interpolateTitle(template, values).trim();
|
|
259
|
-
if (shown)
|
|
260
|
-
return shown;
|
|
261
|
-
}
|
|
262
|
-
for (const [k, sub] of Object.entries(items?.properties ?? {})) {
|
|
331
|
+
function firstShortText(cells, values) {
|
|
332
|
+
for (const [k, sub] of Object.entries(cells ?? {})) {
|
|
263
333
|
const kind = controlKind(sub);
|
|
264
334
|
if (kind !== 'text' && !(kind === 'prose' && shortCell(sub)))
|
|
265
335
|
continue;
|
|
@@ -269,6 +339,11 @@ export function rowSummary(items, row) {
|
|
|
269
339
|
}
|
|
270
340
|
return undefined;
|
|
271
341
|
}
|
|
342
|
+
/** A collapsed row's own words ({@link firstShortText}); `undefined` while the row has
|
|
343
|
+
* nothing to say for itself. */
|
|
344
|
+
export function rowSummary(items, row) {
|
|
345
|
+
return firstShortText(items?.properties, (row ?? {}));
|
|
346
|
+
}
|
|
272
347
|
/**
|
|
273
348
|
* The layout an array takes: `'table'` where the array's own `ui.layout` asks for it
|
|
274
349
|
* and every cell of the row is short (a block prose cell declines it, at load width and
|
|
@@ -316,14 +391,18 @@ export function commitDiscriminant(value, member) {
|
|
|
316
391
|
}
|
|
317
392
|
/**
|
|
318
393
|
* Whether the schema obliges a cell: `default:`'s absence, which is the whole of the
|
|
319
|
-
* obligation (DOCUMENT_MODEL).
|
|
320
|
-
* declares no `default:` at all
|
|
321
|
-
*
|
|
394
|
+
* obligation (DOCUMENT_MODEL). Exempt are the cells whose absence of one says nothing:
|
|
395
|
+
* a typed dictionary and a matrix, since a namespace declares no `default:` at all and
|
|
396
|
+
* `validate` anchors obligation on the leaves under it; and an optional cell, which can
|
|
397
|
+
* declare none, its unanswered render being `none`.
|
|
322
398
|
*/
|
|
323
399
|
export function obliged(schema) {
|
|
324
|
-
return schema.type !== 'object' &&
|
|
400
|
+
return (schema.type !== 'object' &&
|
|
401
|
+
schema.type !== 'matrix' &&
|
|
402
|
+
!optionalCell(schema) &&
|
|
403
|
+
schema.default === undefined);
|
|
325
404
|
}
|
|
326
|
-
/** `foo_bar` → `Foo bar`: the label fallback when a field declares no `
|
|
405
|
+
/** `foo_bar` → `Foo bar`: the label fallback when a field declares no `title`. */
|
|
327
406
|
export function humanize(name) {
|
|
328
407
|
const spaced = name.replace(/_/g, ' ').trim();
|
|
329
408
|
return spaced ? spaced[0].toUpperCase() + spaced.slice(1) : spaced;
|
|
@@ -336,11 +415,11 @@ export function fieldModels(cardSchema) {
|
|
|
336
415
|
control: controlKind(schema),
|
|
337
416
|
group: schema.ui?.group,
|
|
338
417
|
compact: !!schema.ui?.compact,
|
|
339
|
-
label: schema.
|
|
418
|
+
label: schema.title ?? humanize(name),
|
|
340
419
|
description: schema.description,
|
|
341
420
|
required: obliged(schema),
|
|
342
421
|
inline: !!schema.inline,
|
|
343
|
-
plaintext: schema
|
|
422
|
+
plaintext: baseType(schema) === 'plaintext'
|
|
344
423
|
}));
|
|
345
424
|
}
|
|
346
425
|
/**
|
|
@@ -485,11 +564,7 @@ export function fieldValues(items) {
|
|
|
485
564
|
values[p.key] = p.value;
|
|
486
565
|
return values;
|
|
487
566
|
}
|
|
488
|
-
/**
|
|
489
|
-
export function interpolateTitle(template, values) {
|
|
490
|
-
return template.replace(/\{([A-Za-z_][A-Za-z0-9_]*)\}/g, (_m, name) => titleText(ownValue(values, name)));
|
|
491
|
-
}
|
|
492
|
-
/** A value by own key: a template or a declaration can name `constructor`. */
|
|
567
|
+
/** A value by own key: a declaration can name `constructor`. */
|
|
493
568
|
function ownValue(values, name) {
|
|
494
569
|
return Object.hasOwn(values, name) ? values[name] : undefined;
|
|
495
570
|
}
|
|
@@ -508,25 +583,25 @@ export function titleText(v) {
|
|
|
508
583
|
const member = v[VARIANT_DISCRIMINANT];
|
|
509
584
|
return typeof member === 'string' ? member : '';
|
|
510
585
|
}
|
|
511
|
-
/**
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
return
|
|
586
|
+
/** A card kind's name: its schema `title`, else the humanized kind. What names a kind
|
|
587
|
+
* before there is an instance of it: the add menu, the retype select. */
|
|
588
|
+
export function kindTitle(cardSchema, kind) {
|
|
589
|
+
const t = cardSchema?.title;
|
|
590
|
+
return t && t.trim() ? t : humanize(kind);
|
|
516
591
|
}
|
|
517
592
|
/**
|
|
518
|
-
*
|
|
519
|
-
*
|
|
520
|
-
*
|
|
521
|
-
* cleared rename reverts to the schema
|
|
593
|
+
* A card instance's header: the per-instance `$ext.editor.title` rename; else the
|
|
594
|
+
* kind's schema `title`, which names every instance alike; else the instance's own
|
|
595
|
+
* first short text cell ({@link firstShortText}); else the humanized kind. Empty
|
|
596
|
+
* renames fall through, so a cleared rename reverts to what the schema says.
|
|
522
597
|
*/
|
|
523
598
|
export function cardTitle(cardSchema, kind, values, extTitle) {
|
|
524
599
|
if (extTitle && extTitle.trim())
|
|
525
600
|
return extTitle;
|
|
526
|
-
const t = cardSchema?.
|
|
601
|
+
const t = cardSchema?.title;
|
|
527
602
|
if (t && t.trim())
|
|
528
|
-
return
|
|
529
|
-
return humanize(kind);
|
|
603
|
+
return t;
|
|
604
|
+
return firstShortText(cardSchema?.fields, values) ?? humanize(kind);
|
|
530
605
|
}
|
|
531
606
|
/** Whether a card kind renders a body leaf: gated by `body.enabled !== false`. */
|
|
532
607
|
export function bodyEnabled(cardSchema) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@quillmark/svelte",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.13.0",
|
|
4
4
|
"description": "Editor, live-preview and source surfaces over a Quillmark WASM session.",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -66,7 +66,7 @@
|
|
|
66
66
|
"check": "svelte-check --tsconfig ./tsconfig.json"
|
|
67
67
|
},
|
|
68
68
|
"peerDependencies": {
|
|
69
|
-
"@quillmark/wasm": ">=0.
|
|
69
|
+
"@quillmark/wasm": ">=0.116.0-0",
|
|
70
70
|
"svelte": "^5.0.0"
|
|
71
71
|
},
|
|
72
72
|
"dependencies": {
|