@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.
Files changed (42) hide show
  1. package/dist/core/codec/decode.d.ts +17 -0
  2. package/dist/core/codec/decode.js +57 -4
  3. package/dist/core/codec/field.d.ts +64 -15
  4. package/dist/core/codec/field.js +201 -65
  5. package/dist/core/codec/index.d.ts +3 -3
  6. package/dist/core/codec/index.js +3 -3
  7. package/dist/core/codec/keymap.d.ts +5 -4
  8. package/dist/core/codec/keymap.js +5 -4
  9. package/dist/core/codec/prose.css +42 -0
  10. package/dist/core/codec/schema.d.ts +24 -5
  11. package/dist/core/codec/schema.js +40 -9
  12. package/dist/core/errors.d.ts +3 -2
  13. package/dist/core/theme.css +9 -6
  14. package/dist/preview/paint.js +21 -17
  15. package/dist/visual/ArrayField.svelte +66 -22
  16. package/dist/visual/ArrayField.svelte.d.ts +2 -0
  17. package/dist/visual/BooleanField.svelte +28 -6
  18. package/dist/visual/BooleanField.svelte.d.ts +2 -0
  19. package/dist/visual/Card.svelte +3 -3
  20. package/dist/visual/DateField.svelte +66 -31
  21. package/dist/visual/EnumField.svelte +27 -9
  22. package/dist/visual/EnumField.svelte.d.ts +5 -0
  23. package/dist/visual/Field.svelte +45 -10
  24. package/dist/visual/FormatPopover.svelte +4 -3
  25. package/dist/visual/MatrixField.svelte +3 -2
  26. package/dist/visual/NumberField.svelte +31 -14
  27. package/dist/visual/NumberField.svelte.d.ts +5 -1
  28. package/dist/visual/ObjectField.svelte +62 -15
  29. package/dist/visual/ProseField.svelte +53 -23
  30. package/dist/visual/ProseField.svelte.d.ts +15 -3
  31. package/dist/visual/ProseValue.svelte +102 -46
  32. package/dist/visual/ProseValue.svelte.d.ts +17 -5
  33. package/dist/visual/TextField.svelte +35 -18
  34. package/dist/visual/TextField.svelte.d.ts +7 -0
  35. package/dist/visual/VariantField.svelte +1 -0
  36. package/dist/visual/VisualEditorInner.svelte +24 -24
  37. package/dist/visual/controls.css +16 -5
  38. package/dist/visual/strings.d.ts +8 -1
  39. package/dist/visual/strings.js +2 -0
  40. package/dist/visual/structure.d.ts +77 -43
  41. package/dist/visual/structure.js +132 -57
  42. package/package.json +2 -2
@@ -1,12 +1,17 @@
1
1
  <!--
2
- A `string` field → text input. Commits a non-empty edit live (on input) via the
3
- parent's typed `writer.set`, so the preview tracks typing. A cleared field is the
4
- unset rung of the commitment ladder (VISUAL_EDITOR §"Structure mirrors the schema"): it
5
- commits `undefined` (the parent removes the field, ghosted `default:` renders):
6
- but at `change` (blur), not per keystroke, so select-all-and-retype doesn't flash
7
- the field through its default between the delete and the first typed char.
8
- Trade-off (recorded in VISUAL_EDITOR): an explicit empty string over a non-empty
9
- default is inexpressible from the UI: clear and unset collapse to one gesture.
2
+ A `string` field → text input. Commits every edit live (on input) via the parent's
3
+ typed `writer.set`, so the preview tracks typing.
4
+
5
+ An unset field whose `default:` prints holds that default as its text, at the
6
+ default rung (theme.css): the value it prints, drawn as one and a step off a written
7
+ one. Nothing is written until an edit, which writes the whole text as authored: the
8
+ default taken, whatever the edit left of it. Tabbing through writes nothing; a
9
+ keystroke typed and removed pins the default.
10
+
11
+ An emptied input writes what empty means for this field (VISUAL_EDITOR §"The
12
+ commitment ladder"): `""` over a default that prints, the only answer that prints empty there;
13
+ the unset rung elsewhere, where unset already prints nothing and the field stays
14
+ unanswered, and an optional cell returns to `none`.
10
15
  -->
11
16
  <script lang="ts">
12
17
  import { syncedLocal } from './synced.svelte.js';
@@ -14,7 +19,14 @@
14
19
 
15
20
  interface Props {
16
21
  value: string | undefined;
22
+ /** The resolved `default:` where it prints: the text an unset field holds. */
23
+ fallback?: string;
24
+ /** Words at rest about an unset field that holds no text: the `None` an optional
25
+ * cell prints. */
17
26
  placeholder?: string;
27
+ /** An unset field's `example:` (`exampleGhost`): drawn wherever the field holds no
28
+ * text, and in `placeholder`'s stead while the input holds the focus. */
29
+ example?: string;
18
30
  /** Accessible name for an input nothing else names: an array element, whose
19
31
  * name is the field label plus its 1-based index. A field's own input takes
20
32
  * `id` instead and is named by the `<label for>` beside it. */
@@ -29,11 +41,17 @@
29
41
  * array repeater's Enter/Backspace (`ArrayField`). */
30
42
  onKey?: (e: KeyboardEvent) => void;
31
43
  }
32
- let { value, placeholder, label, id, describedBy, onCommit, onKey }: Props = $props();
44
+ let { value, fallback, placeholder, example, label, id, describedBy, onCommit, onKey }: Props =
45
+ $props();
33
46
 
34
- // Local input state synced to `value`: own-typing stays local, only an external
35
- // change reconciles back in (see `syncedLocal`).
36
- const local = syncedLocal(() => value ?? '');
47
+ // Local input state synced to `value`, or to the default an unset field holds:
48
+ // own-typing stays local, only an external change reconciles back in (see
49
+ // `syncedLocal`).
50
+ const local = syncedLocal(() => value ?? fallback ?? '');
51
+ const defaulted = $derived(value == null && !!fallback && local.value === fallback);
52
+
53
+ let focused = $state(false);
54
+ const ghost = $derived(focused ? (example ?? placeholder) : (placeholder ?? example));
37
55
 
38
56
  let inputEl: HTMLInputElement | undefined = $state();
39
57
  /** Take the caret: what a parent placing focus on this control calls. */
@@ -48,16 +66,15 @@
48
66
  type="text"
49
67
  value={local.value}
50
68
  {id}
51
- {placeholder}
69
+ placeholder={ghost}
70
+ data-default={defaulted ? '' : undefined}
52
71
  aria-label={id ? undefined : label}
53
72
  aria-describedby={describedBy}
54
73
  onkeydown={onKey}
74
+ onfocus={() => (focused = true)}
75
+ onblur={() => (focused = false)}
55
76
  oninput={(e) => {
56
77
  local.value = (e.currentTarget as HTMLInputElement).value;
57
- // Live-commit a non-empty edit; defer a cleared field to `change` (see header).
58
- if (local.value !== '') onCommit(local.value);
59
- }}
60
- onchange={() => {
61
- if (local.value === '') onCommit(undefined);
78
+ onCommit(local.value !== '' ? local.value : fallback ? '' : undefined);
62
79
  }}
63
80
  />
@@ -1,7 +1,14 @@
1
1
  import './controls.css';
2
2
  interface Props {
3
3
  value: string | undefined;
4
+ /** The resolved `default:` where it prints: the text an unset field holds. */
5
+ fallback?: string;
6
+ /** Words at rest about an unset field that holds no text: the `None` an optional
7
+ * cell prints. */
4
8
  placeholder?: string;
9
+ /** An unset field's `example:` (`exampleGhost`): drawn wherever the field holds no
10
+ * text, and in `placeholder`'s stead while the input holds the focus. */
11
+ example?: string;
5
12
  /** Accessible name for an input nothing else names: an array element, whose
6
13
  * name is the field label plus its 1-based index. A field's own input takes
7
14
  * `id` instead and is named by the `<label for>` beside it. */
@@ -116,6 +116,7 @@
116
116
  value={discriminant}
117
117
  values={schema.values ?? []}
118
118
  fallback={ghostMember}
119
+ blankTitle={schema.ui?.blank_title}
119
120
  {id}
120
121
  {describedBy}
121
122
  onCommit={(v) => onCommit(commitDiscriminant(value, v))}
@@ -50,20 +50,21 @@
50
50
  IdSeq,
51
51
  controlKind,
52
52
  isContainer,
53
+ shortCell,
53
54
  schemaAt,
54
55
  fieldModels,
55
56
  groupOrder,
56
57
  groupSections,
57
58
  groupLabel,
58
59
  cardTitle,
59
- titleFields,
60
+ kindTitle,
60
61
  fieldValues,
61
62
  bodyEnabled,
62
- humanize,
63
63
  provenanceMap,
64
64
  resolvedByCardIndex,
65
65
  ghostDefault,
66
- stringifyGhost,
66
+ declaredGhost,
67
+ titleText,
67
68
  resolveBodyGhost,
68
69
  NO_RESOLVED_ROWS,
69
70
  type CardModel,
@@ -286,24 +287,22 @@
286
287
  onChange?.({ source: 'prose', cardId, path: pathFor(plain) });
287
288
  }
288
289
 
289
- /** A card's header after a prose commit to a field its `{field}` title names: the
290
- * one piece of the model that lane changes, held beside the model and dropped on
291
- * the next bump, which rebuilds it from the document. The kind is the derived
292
- * tree's ({@link liveKinds}), so the document is read only for a title that
293
- * names the field. */
290
+ /** A header named by the card's own values, after a prose commit to one that can name
291
+ * it: the one piece of the model that lane moves, held beside the model and dropped
292
+ * on the next bump, which rebuilds it from the document. The card is read only for a
293
+ * one-line prose field of a kind declaring no `title` (`cardTitle`). */
294
294
  let liveTitles = $state<Record<CardId, string>>({});
295
295
  function refreshTitle(addr: Addr, cardId: CardId): void {
296
296
  const { card: at, field } = addr;
297
- if (field == null) return;
298
- const shown = at == null ? model.main : model.cards[at];
299
- if (!shown) return;
300
- const cardSchema = at == null ? quill.schema.main : quill.schema.card_kinds?.[shown.kind];
301
- if (!titleFields(cardSchema?.ui?.title).includes(field)) return;
302
- const live = at == null ? doc.main : doc.cards[at];
303
- if (!live) return;
297
+ if (at == null || field == null) return;
298
+ const shown = model.cards[at];
299
+ const cardSchema = shown && quill.schema.card_kinds?.[shown.kind];
300
+ if (!cardSchema || cardSchema.title?.trim()) return;
301
+ const sub = Object.hasOwn(cardSchema.fields, field) ? cardSchema.fields[field] : undefined;
302
+ if (!sub || !shortCell(sub)) return;
304
303
  liveTitles = {
305
304
  ...liveTitles,
306
- [cardId]: cardTitle(cardSchema, shown.kind, fieldValues(live.payloadItems), undefined)
305
+ [cardId]: cardTitle(cardSchema, shown.kind, fieldValues(doc.card(at).payloadItems), undefined)
307
306
  };
308
307
  }
309
308
  function titled(c: CardModel): CardModel {
@@ -328,11 +327,11 @@
328
327
  const keyStr = fieldKeyToString(key);
329
328
  try {
330
329
  if (value === undefined) {
331
- // The unset rung of the commitment ladder (VISUAL_EDITOR §"Structure mirrors
332
- // the schema"): removing the field leaves the engine's authored › `default:`
333
- // › blank-fill resolve to render the default, where writing one bakes a
334
- // snapshot the schema cannot track (canon SCHEMAS.md: the engine never
335
- // persists a default; nor do we). Removal writes no value, so there is
330
+ // The unset rung (VISUAL_EDITOR §"The commitment ladder"): removing the field
331
+ // leaves the engine's authored › `default:` › blank-fill resolve to render the
332
+ // default, where writing one bakes a snapshot the schema cannot track (canon
333
+ // SCHEMAS.md: the engine never writes a default; the editor writes one only
334
+ // as the edit that takes it). Removal writes no value, so there is
336
335
  // nothing for a schema to conform and the lane is the quill-free one.
337
336
  if (isMain) {
338
337
  doc.removeField(name);
@@ -612,7 +611,8 @@
612
611
  // still across a re-derive because the function does.
613
612
  bodyGhost: hasBody
614
613
  ? resolveBodyGhost(
615
- stringifyGhost(ghostDefault(rows.body ?? undefined)),
614
+ titleText(ghostDefault(rows.body ?? undefined)) || undefined,
615
+ declaredGhost(cardSchema?.body?.example, true),
616
616
  merged.bodyPlaceholder?.({ cardId: id, kind, isMain }),
617
617
  merged.bodyGhost
618
618
  )
@@ -858,7 +858,7 @@
858
858
  <div class="qm-primary">
859
859
  <Card
860
860
  bind:this={mainCard}
861
- card={titled(model.main)}
861
+ card={model.main}
862
862
  {doc}
863
863
  {quill}
864
864
  isFirst={true}
@@ -957,7 +957,7 @@
957
957
  <div class="qm-menu-surface" data-qm-root>
958
958
  {#each kinds as k (k)}
959
959
  <DropdownMenu.Item class="qm-menu-item" onSelect={() => addCard(atIndex, k)}
960
- >{humanize(k)}</DropdownMenu.Item
960
+ >{kindTitle(quill.schema.card_kinds?.[k], k)}</DropdownMenu.Item
961
961
  >
962
962
  {/each}
963
963
  </div>
@@ -105,14 +105,25 @@
105
105
  width: 100%;
106
106
  box-sizing: border-box;
107
107
  }
108
- /* The native placeholder is the label rung, not the UA's own grey: a text or
109
- number control's placeholder is the resolved `default:` (Field's `defaultStr`),
110
- which is content and reads at a content's contrast. What says shown-never-written
111
- is the step from the `ink` a written value takes, the same step the prose leaf's
112
- empty state reads (`ProseField`). */
108
+ /* An unset field's text where its `default:` prints (`TextField`, `NumberField`, a
109
+ prose leaf): the value, at the default rung, so it reads as an answer and never as a
110
+ written one. The first edit writes it and the rung goes with the attribute. */
111
+ :where(.qm-input[data-default], .ProseMirror[data-default]) {
112
+ color: var(--_qm-ink-default);
113
+ }
114
+ /* The native placeholder is the label rung, not the UA's own grey: what an input
115
+ ghosts is words about a value it does not hold — an `example:`, `None` — which is
116
+ content and reads at a content's contrast, the same step the prose leaf's ghost
117
+ reads (`prose.css`). */
113
118
  :where(.qm-input)::placeholder {
114
119
  color: var(--_qm-ink-label);
115
120
  }
121
+ /* The note a held prose leaf draws inside its box, under the text it is about. */
122
+ :where(.qm-prose-held-note) {
123
+ display: block;
124
+ font-size: var(--_qm-text-label);
125
+ color: var(--_qm-ink-label);
126
+ }
116
127
 
117
128
  /* The second recipe, and it is not the box above: a field is a box because you
118
129
  type into it. A button rests with no fill; the surface arrives on hover, off the
@@ -49,6 +49,12 @@ export interface VisualStrings extends TableChromeStrings, SlashStrings {
49
49
  matrixHeld: (held: number, total: number) => string;
50
50
  /** The required marker's accessible name; the glyph itself is a `*`. */
51
51
  fieldRequired: string;
52
+ /** What an unset optional cell (`t?`) ghosts at rest: the `none` it prints
53
+ * unanswered, in the product's words. */
54
+ optionalGhost: string;
55
+ /** Under a prose leaf holding more structure than it can edit (paragraphs, a list,
56
+ * an island in a one-line cell), which it draws read-only rather than flatten. */
57
+ proseHeld: string;
52
58
  formatGroup: string;
53
59
  formatBold: string;
54
60
  formatEmphasis: string;
@@ -84,7 +90,8 @@ export interface VisualStrings extends TableChromeStrings, SlashStrings {
84
90
  bodyGhost: string;
85
91
  /**
86
92
  * Per-CARD wording, in place of {@link VisualStrings.bodyGhost}; `undefined`
87
- * takes it. Pure and uncached ({@link BodyPlaceholder}).
93
+ * takes it. A body `default:` and the kind's `body.example` both outrank it
94
+ * (`resolveBodyGhost`). Pure and uncached ({@link BodyPlaceholder}).
88
95
  */
89
96
  bodyPlaceholder: BodyPlaceholder;
90
97
  }
@@ -38,6 +38,8 @@ export const DEFAULT_VISUAL_STRINGS = {
38
38
  enumUnsetTag: 'default',
39
39
  matrixHeld: (held, total) => `${held} of ${total} held`,
40
40
  fieldRequired: 'required',
41
+ optionalGhost: 'None',
42
+ proseHeld: 'Holds more than this field can edit here.',
41
43
  formatGroup: 'Formatting',
42
44
  formatBold: 'Bold (Mod-B)',
43
45
  formatEmphasis: 'Emphasis (Mod-I)',
@@ -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: `ui.title` when set, else the humanized field name. */
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
- /** Schema-resolved title used as the rename placeholder (composable cards). */
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
- * empty for a card that does: the resolved body `default:` when there is one,
75
- * else the consumer's wording, else the built-in invitation
76
- * ({@link resolveBodyGhost}). A body leaf therefore always has something in it
77
- * to write into, which the inline fields deliberately do not: their ghost is
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
- /** The ghost a field shows when unset: the resolved `default:` value the render
108
- * would use (`source === 'default'`), else undefined: an `authored` field shows
109
- * its value, a `zero` field has no default to ghost. */
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). The one text-ghost projection: a scalar field's
113
- * placeholder and a body leaf's both read `stringifyGhost ∘
114
- * ghostDefault`, since a richtext body resolves to a text render: the correct
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 consumer's
143
- * wording, else the flat built-in. The `default:` wins because it is the only one
144
- * of the three that describes the render: it promises what prints if nothing is
145
- * written, and wording placed over it would make that promise unreadable. The
146
- * other two are invitations, and an invitation belongs only where there is no
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 `plaintext` leaf is inline at the codec whatever it declares. */
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
- * A collapsed row's own words: `items.ui.title` interpolated with the row's values
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). A typed dictionary and a matrix are exempt — a namespace
258
- * declares no `default:` at all, and `validate` anchors obligation on the leaves under
259
- * it, so reading one off the container would mark every subform required.
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 `ui.title`. */
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
- /** The field names a `{field}` title reads. */
330
- export declare function titleFields(template: string | undefined): string[];
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
- * Resolve a card instance's header title: the per-instance `$ext.editor.title`
333
- * override wins; else the schema `ui.title` (literal or `{field}` template
334
- * interpolated); else the humanized kind. Empty overrides fall through so a
335
- * cleared rename reverts to the schema title.
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`. */