@quillmark/svelte 0.12.0 → 0.14.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/NOTICE +3 -2
- package/dist/core/codec/decode.d.ts +17 -0
- package/dist/core/codec/decode.js +57 -4
- package/dist/core/codec/field.d.ts +63 -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 +4 -3
- package/dist/visual/DateField.svelte +158 -40
- package/dist/visual/DateField.svelte.d.ts +5 -2
- package/dist/visual/EnumField.svelte +27 -9
- package/dist/visual/EnumField.svelte.d.ts +5 -0
- package/dist/visual/Field.svelte +48 -11
- package/dist/visual/FormatPopover.svelte +4 -3
- package/dist/visual/MatrixField.svelte +3 -2
- package/dist/visual/NumberField.svelte +73 -29
- package/dist/visual/NumberField.svelte.d.ts +5 -1
- package/dist/visual/ObjectField.svelte +63 -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/icons/nodes.d.ts +28 -0
- package/dist/visual/icons/nodes.js +14 -1
- package/dist/visual/strings.d.ts +11 -1
- package/dist/visual/strings.js +3 -0
- package/dist/visual/structure.d.ts +77 -43
- package/dist/visual/structure.js +138 -64
- package/package.json +2 -2
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type PathStep, type PayloadItem, type QuillCardSchema, type QuillFieldSchema, type ResolvedField, type Resolved } from '@quillmark/wasm';
|
|
1
|
+
import { type Content, type PathStep, type PayloadItem, type QuillCardSchema, type QuillFieldSchema, type QuillFieldType, type ResolvedField, type Resolved } from '@quillmark/wasm';
|
|
2
2
|
/** The control a field type maps to (VISUAL_EDITOR §"Structure mirrors the schema"). */
|
|
3
3
|
export type ControlKind = 'prose' | 'text' | 'enum' | 'variant' | 'number' | 'boolean' | 'date' | 'array' | 'object' | 'matrix';
|
|
4
4
|
/** The container controls: the ones a nested address walks into and a deep
|
|
@@ -14,7 +14,7 @@ export interface FieldModel {
|
|
|
14
14
|
/** `ui.compact`: asks to share a row with adjacent compacts. A request, not a
|
|
15
15
|
* guarantee: `placeFields` declines it for the shapes that grow (see `packable`). */
|
|
16
16
|
compact: boolean;
|
|
17
|
-
/** Display label: `
|
|
17
|
+
/** Display label: `title` when set, else the humanized field name. */
|
|
18
18
|
label: string;
|
|
19
19
|
/** Schema `description`: authoring help rendered beside the label,
|
|
20
20
|
* undefined when the field declares none. Chrome-only; never gates. */
|
|
@@ -56,7 +56,8 @@ export interface CardModel {
|
|
|
56
56
|
unschemable: boolean;
|
|
57
57
|
/** Raw `$ext.editor.title` override (composable cards). */
|
|
58
58
|
titleOverride: string;
|
|
59
|
-
/**
|
|
59
|
+
/** The header an unrenamed card shows ({@link cardTitle} below its rename rung),
|
|
60
|
+
* which the rename input ghosts (composable cards). */
|
|
60
61
|
titlePlaceholder: string;
|
|
61
62
|
/** Field name → current stored value (absent fields missing). */
|
|
62
63
|
values: Record<string, unknown>;
|
|
@@ -70,12 +71,11 @@ export interface CardModel {
|
|
|
70
71
|
sections: GroupSection[];
|
|
71
72
|
hasBody: boolean;
|
|
72
73
|
/**
|
|
73
|
-
* The empty-body ghost, or undefined when the card renders no body. Never
|
|
74
|
-
*
|
|
75
|
-
*
|
|
76
|
-
*
|
|
77
|
-
*
|
|
78
|
-
* the resolved default, and an invented one would read as a value.
|
|
74
|
+
* The empty-body ghost, or undefined when the card renders no body. Never empty
|
|
75
|
+
* for a card that does ({@link resolveBodyGhost}), so a body leaf always has
|
|
76
|
+
* something in it to write into, which a field at rest deliberately does not: it
|
|
77
|
+
* draws what prints or what its quill declares, and an invented ghost would read as
|
|
78
|
+
* a value.
|
|
79
79
|
*/
|
|
80
80
|
bodyGhost?: string;
|
|
81
81
|
}
|
|
@@ -104,15 +104,14 @@ export declare const NO_RESOLVED_ROWS: ResolvedCardRows;
|
|
|
104
104
|
* form).
|
|
105
105
|
*/
|
|
106
106
|
export declare function resolvedByCardIndex(resolved: Resolved | undefined): Map<number, ResolvedCardRows>;
|
|
107
|
-
/**
|
|
108
|
-
*
|
|
109
|
-
*
|
|
107
|
+
/** What an unset field shows of its `default:`: the resolved value the render would
|
|
108
|
+
* use (`source === 'default'`), else undefined: an `authored` field shows its value,
|
|
109
|
+
* a `blank` field has no default to show. */
|
|
110
110
|
export declare function ghostDefault(row: ResolvedField | undefined): unknown;
|
|
111
111
|
/** A ghost value's string form, or undefined for null/object (only text ghosts
|
|
112
|
-
* render a placeholder).
|
|
113
|
-
*
|
|
114
|
-
*
|
|
115
|
-
* thing to display as a placeholder (FIELD_PROVENANCE). */
|
|
112
|
+
* render a placeholder). A scalar's resolved default reads through it; a content
|
|
113
|
+
* field's resolves as `Content`, which a prose leaf reads by its text instead
|
|
114
|
+
* ({@link titleText}). */
|
|
116
115
|
export declare function stringifyGhost(ghost: unknown): string | undefined;
|
|
117
116
|
/** What a {@link BodyPlaceholder} is told about the body it words. The card's
|
|
118
117
|
* identity, not its chrome: `cardId` is what per-card wording keys off, `kind` keys
|
|
@@ -139,14 +138,53 @@ export interface BodyPlaceholderContext {
|
|
|
139
138
|
*/
|
|
140
139
|
export type BodyPlaceholder = (ctx: BodyPlaceholderContext) => string | undefined;
|
|
141
140
|
/**
|
|
142
|
-
* The empty body's ghost: the resolved body `default:`, else the
|
|
143
|
-
* wording, else the flat built-in. The
|
|
144
|
-
*
|
|
145
|
-
* written, and wording placed over it would make that
|
|
146
|
-
*
|
|
147
|
-
* promise
|
|
141
|
+
* The empty body's ghost: the resolved body `default:`, else the kind's
|
|
142
|
+
* `body.example`, else the consumer's wording, else the flat built-in. The
|
|
143
|
+
* `default:` wins because it is the only one that describes the render: it promises
|
|
144
|
+
* what prints if nothing is written, and wording placed over it would make that
|
|
145
|
+
* promise unreadable. The rest are invitations, and an invitation belongs only where
|
|
146
|
+
* there is no promise; the quill's own comes first, being the one an agent working
|
|
147
|
+
* from the blueprint sees too.
|
|
148
148
|
*/
|
|
149
|
-
export declare function resolveBodyGhost(resolvedDefault: string | undefined, custom: string | undefined, builtIn: string): string;
|
|
149
|
+
export declare function resolveBodyGhost(resolvedDefault: string | undefined, example: string | undefined, custom: string | undefined, builtIn: string): string;
|
|
150
|
+
/** Whether a cell is optional: a `t?` type, which renders `none` unanswered and so
|
|
151
|
+
* never declares a `default:` (canon `SCHEMAS.md` §"Optional cells"). */
|
|
152
|
+
export declare function optionalCell(f: QuillFieldSchema): boolean;
|
|
153
|
+
/** The type a cell's control reads: the declared one, an optional cell's `?` stripped.
|
|
154
|
+
* The `?` moves the render floor and nothing else, so every read of a type is this. */
|
|
155
|
+
export declare function baseType(f: QuillFieldSchema): QuillFieldType;
|
|
156
|
+
/**
|
|
157
|
+
* A value the schema declares, as ghost text: a `default:`, an `example:` or a
|
|
158
|
+
* `body.example`. Markdown — a `richtext` value — ghosts as the text it renders, one
|
|
159
|
+
* block to a line, as a resolved default does ({@link titleText}); anything else as
|
|
160
|
+
* itself. `undefined` for a blank or a non-scalar.
|
|
161
|
+
*/
|
|
162
|
+
export declare function declaredGhost(v: unknown, markdown: boolean): string | undefined;
|
|
163
|
+
/**
|
|
164
|
+
* A scalar `default:` as the text an unset control holds, where it prints: as
|
|
165
|
+
* declared, `undefined` for none, a blank or a non-scalar. A default of spaces prints
|
|
166
|
+
* nothing a reader sees, so a control holds none and draws its example instead.
|
|
167
|
+
*/
|
|
168
|
+
export declare function printedText(v: unknown): string | undefined;
|
|
169
|
+
/**
|
|
170
|
+
* A declared content literal as the `Content` it holds: a `richtext` one's markdown
|
|
171
|
+
* imported, a `plaintext` one's text taken literally, one paragraph whose later lines
|
|
172
|
+
* continue it, as the literal codec reads a stored one. What a cell's static
|
|
173
|
+
* `default:` stands in its leaf as, where no resolved row reaches it. `undefined` for
|
|
174
|
+
* a blank or a non-scalar.
|
|
175
|
+
*/
|
|
176
|
+
export declare function declaredContent(v: unknown, markdown: boolean): Content | undefined;
|
|
177
|
+
/**
|
|
178
|
+
* The `example:` an unset free-text cell ghosts: a `string`, `plaintext` or `richtext`
|
|
179
|
+
* whose `default:` prints nothing. Every other type takes none. A default that prints
|
|
180
|
+
* dominates, standing in the control as the value it is; a type-empty one, the
|
|
181
|
+
* skippable marker, prints nothing and leaves the example the only thing to draw.
|
|
182
|
+
*/
|
|
183
|
+
export declare function exampleGhost(f: QuillFieldSchema): string | undefined;
|
|
184
|
+
/** A field's `{#each}` key: its name and what a mounted leaf reads once, its type and
|
|
185
|
+
* `inline` and its elements'. A retype or a quill swap that changes any of them under a
|
|
186
|
+
* name remounts the field, where a kept leaf would commit on the old schema. */
|
|
187
|
+
export declare function mountKey(name: string, f: QuillFieldSchema): string;
|
|
150
188
|
/** Map a field schema to its control (precedence: prose › enum › text › …).
|
|
151
189
|
* An array's element control is this over `items`: a missing `items` is a text
|
|
152
190
|
* element. */
|
|
@@ -214,15 +252,11 @@ export declare function matrixDeclares(matrix: QuillFieldSchema, id: string): bo
|
|
|
214
252
|
*/
|
|
215
253
|
export declare function schemaAt(field: QuillFieldSchema, steps: readonly PathStep[]): QuillFieldSchema | undefined;
|
|
216
254
|
/** Whether a cell is one line tall by declaration: the shape a table column and a row
|
|
217
|
-
* summary can hold. A
|
|
255
|
+
* summary can hold. A prose cell is one where it declares `inline`, `richtext` or
|
|
256
|
+
* `plaintext` alike. */
|
|
218
257
|
export declare function shortCell(sub: QuillFieldSchema): boolean;
|
|
219
|
-
/**
|
|
220
|
-
*
|
|
221
|
-
* where the schema declares one, else the first short text cell in declaration order
|
|
222
|
-
* — a `string`, or an inline `richtext` / `plaintext` — read through {@link titleText};
|
|
223
|
-
* `undefined` while the row has nothing to say for itself. `title` sits on `items.ui`,
|
|
224
|
-
* describing a row, where `layout` sits on the array's own `ui`, describing the array.
|
|
225
|
-
*/
|
|
258
|
+
/** A collapsed row's own words ({@link firstShortText}); `undefined` while the row has
|
|
259
|
+
* nothing to say for itself. */
|
|
226
260
|
export declare function rowSummary(items: QuillFieldSchema | undefined, row: unknown): string | undefined;
|
|
227
261
|
/** How an `array` draws its elements. */
|
|
228
262
|
export type ArrayLayout = 'list' | 'table';
|
|
@@ -254,12 +288,13 @@ export declare function variantCells(schema: QuillFieldSchema, member: string |
|
|
|
254
288
|
export declare function commitDiscriminant(value: Record<string, unknown> | undefined, member: string | undefined): Record<string, unknown> | undefined;
|
|
255
289
|
/**
|
|
256
290
|
* Whether the schema obliges a cell: `default:`'s absence, which is the whole of the
|
|
257
|
-
* obligation (DOCUMENT_MODEL).
|
|
258
|
-
* declares no `default:` at all
|
|
259
|
-
*
|
|
291
|
+
* obligation (DOCUMENT_MODEL). Exempt are the cells whose absence of one says nothing:
|
|
292
|
+
* a typed dictionary and a matrix, since a namespace declares no `default:` at all and
|
|
293
|
+
* `validate` anchors obligation on the leaves under it; and an optional cell, which can
|
|
294
|
+
* declare none, its unanswered render being `none`.
|
|
260
295
|
*/
|
|
261
296
|
export declare function obliged(schema: QuillFieldSchema): boolean;
|
|
262
|
-
/** `foo_bar` → `Foo bar`: the label fallback when a field declares no `
|
|
297
|
+
/** `foo_bar` → `Foo bar`: the label fallback when a field declares no `title`. */
|
|
263
298
|
export declare function humanize(name: string): string;
|
|
264
299
|
/** Project a card schema's `fields` map (declaration = key order) into models. */
|
|
265
300
|
export declare function fieldModels(cardSchema: QuillCardSchema): FieldModel[];
|
|
@@ -319,20 +354,19 @@ export interface PlacedField {
|
|
|
319
354
|
export declare function placeFields(fields: FieldModel[]): PlacedField[];
|
|
320
355
|
/** A card's field values by key, off its payload items. */
|
|
321
356
|
export declare function fieldValues(items: readonly PayloadItem[]): Record<string, unknown>;
|
|
322
|
-
/** Interpolate a `{field}` card-title template against live field values. */
|
|
323
|
-
export declare function interpolateTitle(template: string, values: Record<string, unknown>): string;
|
|
324
357
|
/** A field value as title text. A parsed field rests as the authored string and a
|
|
325
358
|
* committed one as `Content`, whose `text` is the same words; a scalar reads as
|
|
326
359
|
* itself; a variant container reads as its discriminant member, the one cell of it
|
|
327
360
|
* that names the world; any other container as nothing. */
|
|
328
361
|
export declare function titleText(v: unknown): string;
|
|
329
|
-
/**
|
|
330
|
-
|
|
362
|
+
/** A card kind's name: its schema `title`, else the humanized kind. What names a kind
|
|
363
|
+
* before there is an instance of it: the add menu, the retype select. */
|
|
364
|
+
export declare function kindTitle(cardSchema: QuillCardSchema | undefined, kind: string): string;
|
|
331
365
|
/**
|
|
332
|
-
*
|
|
333
|
-
*
|
|
334
|
-
*
|
|
335
|
-
* cleared rename reverts to the schema
|
|
366
|
+
* A card instance's header: the per-instance `$ext.editor.title` rename; else the
|
|
367
|
+
* kind's schema `title`, which names every instance alike; else the instance's own
|
|
368
|
+
* first short text cell ({@link firstShortText}); else the humanized kind. Empty
|
|
369
|
+
* renames fall through, so a cleared rename reverts to what the schema says.
|
|
336
370
|
*/
|
|
337
371
|
export declare function cardTitle(cardSchema: QuillCardSchema | undefined, kind: string, values: Record<string, unknown>, extTitle: string | undefined): string;
|
|
338
372
|
/** Whether a card kind renders a body leaf: gated by `body.enabled !== false`. */
|
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,37 +314,34 @@ 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
|
-
*
|
|
326
|
+
* The first short text cell's words — the first `string`, or inline `richtext` /
|
|
327
|
+
* `plaintext`, in declaration order — read through {@link titleText}; `undefined` where
|
|
328
|
+
* there is no such cell or it is empty. The cell is picked by the schema alone, so an
|
|
329
|
+
* instance is named by one cell whatever the others hold. What names an instance from
|
|
330
|
+
* its own values: a collapsed row, and a card whose kind declares no `title`.
|
|
253
331
|
*/
|
|
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 ?? {})) {
|
|
332
|
+
function firstShortText(cells, values) {
|
|
333
|
+
const named = Object.entries(cells ?? {}).find(([, sub]) => {
|
|
263
334
|
const kind = controlKind(sub);
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
335
|
+
return kind === 'text' || (kind === 'prose' && shortCell(sub));
|
|
336
|
+
});
|
|
337
|
+
if (!named)
|
|
338
|
+
return undefined;
|
|
339
|
+
return titleText(ownValue(values, named[0])).trim() || undefined;
|
|
340
|
+
}
|
|
341
|
+
/** A collapsed row's own words ({@link firstShortText}); `undefined` while the row has
|
|
342
|
+
* nothing to say for itself. */
|
|
343
|
+
export function rowSummary(items, row) {
|
|
344
|
+
return firstShortText(items?.properties, (row ?? {}));
|
|
271
345
|
}
|
|
272
346
|
/**
|
|
273
347
|
* The layout an array takes: `'table'` where the array's own `ui.layout` asks for it
|
|
@@ -316,14 +390,18 @@ export function commitDiscriminant(value, member) {
|
|
|
316
390
|
}
|
|
317
391
|
/**
|
|
318
392
|
* 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
|
-
*
|
|
393
|
+
* obligation (DOCUMENT_MODEL). Exempt are the cells whose absence of one says nothing:
|
|
394
|
+
* a typed dictionary and a matrix, since a namespace declares no `default:` at all and
|
|
395
|
+
* `validate` anchors obligation on the leaves under it; and an optional cell, which can
|
|
396
|
+
* declare none, its unanswered render being `none`.
|
|
322
397
|
*/
|
|
323
398
|
export function obliged(schema) {
|
|
324
|
-
return schema.type !== 'object' &&
|
|
399
|
+
return (schema.type !== 'object' &&
|
|
400
|
+
schema.type !== 'matrix' &&
|
|
401
|
+
!optionalCell(schema) &&
|
|
402
|
+
schema.default === undefined);
|
|
325
403
|
}
|
|
326
|
-
/** `foo_bar` → `Foo bar`: the label fallback when a field declares no `
|
|
404
|
+
/** `foo_bar` → `Foo bar`: the label fallback when a field declares no `title`. */
|
|
327
405
|
export function humanize(name) {
|
|
328
406
|
const spaced = name.replace(/_/g, ' ').trim();
|
|
329
407
|
return spaced ? spaced[0].toUpperCase() + spaced.slice(1) : spaced;
|
|
@@ -336,11 +414,11 @@ export function fieldModels(cardSchema) {
|
|
|
336
414
|
control: controlKind(schema),
|
|
337
415
|
group: schema.ui?.group,
|
|
338
416
|
compact: !!schema.ui?.compact,
|
|
339
|
-
label: schema.
|
|
417
|
+
label: schema.title ?? humanize(name),
|
|
340
418
|
description: schema.description,
|
|
341
419
|
required: obliged(schema),
|
|
342
420
|
inline: !!schema.inline,
|
|
343
|
-
plaintext: schema
|
|
421
|
+
plaintext: baseType(schema) === 'plaintext'
|
|
344
422
|
}));
|
|
345
423
|
}
|
|
346
424
|
/**
|
|
@@ -485,11 +563,7 @@ export function fieldValues(items) {
|
|
|
485
563
|
values[p.key] = p.value;
|
|
486
564
|
return values;
|
|
487
565
|
}
|
|
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`. */
|
|
566
|
+
/** A value by own key: a declaration can name `constructor`. */
|
|
493
567
|
function ownValue(values, name) {
|
|
494
568
|
return Object.hasOwn(values, name) ? values[name] : undefined;
|
|
495
569
|
}
|
|
@@ -508,25 +582,25 @@ export function titleText(v) {
|
|
|
508
582
|
const member = v[VARIANT_DISCRIMINANT];
|
|
509
583
|
return typeof member === 'string' ? member : '';
|
|
510
584
|
}
|
|
511
|
-
/**
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
return
|
|
585
|
+
/** A card kind's name: its schema `title`, else the humanized kind. What names a kind
|
|
586
|
+
* before there is an instance of it: the add menu, the retype select. */
|
|
587
|
+
export function kindTitle(cardSchema, kind) {
|
|
588
|
+
const t = cardSchema?.title;
|
|
589
|
+
return t && t.trim() ? t : humanize(kind);
|
|
516
590
|
}
|
|
517
591
|
/**
|
|
518
|
-
*
|
|
519
|
-
*
|
|
520
|
-
*
|
|
521
|
-
* cleared rename reverts to the schema
|
|
592
|
+
* A card instance's header: the per-instance `$ext.editor.title` rename; else the
|
|
593
|
+
* kind's schema `title`, which names every instance alike; else the instance's own
|
|
594
|
+
* first short text cell ({@link firstShortText}); else the humanized kind. Empty
|
|
595
|
+
* renames fall through, so a cleared rename reverts to what the schema says.
|
|
522
596
|
*/
|
|
523
597
|
export function cardTitle(cardSchema, kind, values, extTitle) {
|
|
524
598
|
if (extTitle && extTitle.trim())
|
|
525
599
|
return extTitle;
|
|
526
|
-
const t = cardSchema?.
|
|
600
|
+
const t = cardSchema?.title;
|
|
527
601
|
if (t && t.trim())
|
|
528
|
-
return
|
|
529
|
-
return humanize(kind);
|
|
602
|
+
return t;
|
|
603
|
+
return firstShortText(cardSchema?.fields, values) ?? humanize(kind);
|
|
530
604
|
}
|
|
531
605
|
/** Whether a card kind renders a body leaf: gated by `body.enabled !== false`. */
|
|
532
606
|
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.14.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": {
|