@quillmark/svelte 0.10.1 → 0.12.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 +1 -1
- package/THEMING.md +2 -2
- package/dist/core/address.d.ts +17 -10
- package/dist/core/address.js +26 -12
- package/dist/core/bloom.js +4 -3
- package/dist/core/codec/blocks.js +21 -1
- package/dist/core/codec/breaks.d.ts +10 -2
- package/dist/core/codec/breaks.js +65 -9
- package/dist/core/codec/code.d.ts +6 -4
- package/dist/core/codec/code.js +7 -4
- package/dist/core/codec/decode.js +103 -68
- package/dist/core/codec/encode.d.ts +5 -8
- package/dist/core/codec/encode.js +56 -86
- package/dist/core/codec/field.js +43 -24
- package/dist/core/codec/index.d.ts +2 -1
- package/dist/core/codec/index.js +6 -3
- package/dist/core/codec/islands.d.ts +16 -11
- package/dist/core/codec/islands.js +70 -59
- package/dist/core/codec/keymap.js +2 -1
- package/dist/core/codec/marks.d.ts +17 -13
- package/dist/core/codec/marks.js +39 -42
- package/dist/core/codec/positions.d.ts +4 -1
- package/dist/core/codec/positions.js +5 -1
- package/dist/core/codec/reconcile.d.ts +6 -0
- package/dist/core/codec/reconcile.js +19 -0
- package/dist/core/codec/schema.d.ts +5 -8
- package/dist/core/codec/schema.js +30 -156
- package/dist/core/codec/slash.js +0 -0
- package/dist/core/codec/table-view.js +77 -26
- package/dist/core/codec/table.d.ts +6 -5
- package/dist/core/codec/table.js +14 -31
- package/dist/core/codec/urls.d.ts +20 -0
- package/dist/core/codec/urls.js +45 -0
- package/dist/core/rungs.d.ts +8 -0
- package/dist/core/rungs.js +24 -0
- package/dist/core/theme.css +10 -0
- package/dist/preset/recipes.css +1 -1
- package/dist/preset/scale.css +7 -4
- package/dist/preview/bridge.d.ts +8 -1
- package/dist/preview/bridge.js +31 -25
- package/dist/preview/controller.js +13 -13
- package/dist/preview/geometry.d.ts +12 -1
- package/dist/preview/geometry.js +14 -1
- package/dist/preview/paint.js +6 -4
- package/dist/preview/strings.d.ts +0 -2
- package/dist/preview/strings.js +5 -6
- package/dist/visual/ArrayField.svelte +600 -173
- package/dist/visual/ArrayField.svelte.d.ts +14 -1
- package/dist/visual/BooleanField.svelte +10 -7
- package/dist/visual/Card.svelte +8 -30
- package/dist/visual/DateField.svelte +2 -2
- package/dist/visual/Field.svelte +76 -32
- package/dist/visual/FieldHint.svelte +1 -1
- package/dist/visual/MatrixField.svelte +352 -0
- package/dist/visual/MatrixField.svelte.d.ts +36 -0
- package/dist/visual/ObjectField.svelte +356 -133
- package/dist/visual/ObjectField.svelte.d.ts +17 -2
- package/dist/visual/VariantField.svelte +55 -2
- package/dist/visual/VariantField.svelte.d.ts +10 -1
- package/dist/visual/VisualEditor.svelte +1 -1
- package/dist/visual/VisualEditorInner.svelte +118 -67
- package/dist/visual/controls.css +39 -9
- package/dist/visual/diagnostics.d.ts +26 -2
- package/dist/visual/diagnostics.js +52 -5
- package/dist/visual/ext.d.ts +12 -13
- package/dist/visual/ext.js +22 -19
- package/dist/visual/hold.js +2 -2
- package/dist/visual/icons/nodes.d.ts +3 -0
- package/dist/visual/icons/nodes.js +2 -1
- package/dist/visual/index.js +2 -3
- package/dist/visual/leaves.d.ts +25 -12
- package/dist/visual/links.d.ts +4 -0
- package/dist/visual/links.js +15 -4
- package/dist/visual/motion.d.ts +14 -5
- package/dist/visual/motion.js +13 -11
- package/dist/visual/strings.d.ts +26 -13
- package/dist/visual/strings.js +7 -2
- package/dist/visual/structure.d.ts +101 -8
- package/dist/visual/structure.js +252 -13
- package/package.json +2 -2
|
@@ -1,6 +1,9 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import { type PathStep, type PayloadItem, type QuillCardSchema, type QuillFieldSchema, type ResolvedField, type Resolved } from '@quillmark/wasm';
|
|
2
2
|
/** The control a field type maps to (VISUAL_EDITOR §"Structure mirrors the schema"). */
|
|
3
|
-
export type ControlKind = 'prose' | 'text' | 'enum' | 'variant' | 'number' | 'boolean' | 'date' | 'array' | 'object';
|
|
3
|
+
export type ControlKind = 'prose' | 'text' | 'enum' | 'variant' | 'number' | 'boolean' | 'date' | 'array' | 'object' | 'matrix';
|
|
4
|
+
/** The container controls: the ones a nested address walks into and a deep
|
|
5
|
+
* diagnostic routes through. */
|
|
6
|
+
export declare function isContainer(kind: ControlKind): boolean;
|
|
4
7
|
/** One field, projected: its schema, the control it renders as, and its layout hints. */
|
|
5
8
|
export interface FieldModel {
|
|
6
9
|
name: string;
|
|
@@ -148,9 +151,90 @@ export declare function resolveBodyGhost(resolvedDefault: string | undefined, cu
|
|
|
148
151
|
* An array's element control is this over `items`: a missing `items` is a text
|
|
149
152
|
* element. */
|
|
150
153
|
export declare function controlKind(f: QuillFieldSchema): ControlKind;
|
|
151
|
-
/** The discriminant cell of a variant container
|
|
152
|
-
*
|
|
153
|
-
|
|
154
|
+
/** The discriminant cell of a variant container, off the boundary's own constant: it
|
|
155
|
+
* crosses inside untyped container data with no type to read it off, so a literal here
|
|
156
|
+
* would be a second spelling of a reserved key. No variant may declare it. */
|
|
157
|
+
export declare const VARIANT_DISCRIMINANT: "value";
|
|
158
|
+
/** The tick cell of a matrix member, the second reserved key beside the discriminant.
|
|
159
|
+
* The boundary synthesizes it on every member and exports no constant for it, so this
|
|
160
|
+
* is where the package spells it once (canon `SCHEMAS.md` §Matrix). */
|
|
161
|
+
export declare const MATRIX_HELD = "held";
|
|
162
|
+
/** One member of the roster: its id and its title. */
|
|
163
|
+
export interface MatrixMember {
|
|
164
|
+
id: string;
|
|
165
|
+
title: string;
|
|
166
|
+
}
|
|
167
|
+
/** The roster's members, in declaration order. Own keys only: a schema map is indexed
|
|
168
|
+
* by a document-supplied id, and `'toString' in members` is true of every roster. */
|
|
169
|
+
export declare function matrixMembers(roster: Record<string, string> | undefined): MatrixMember[];
|
|
170
|
+
/** A member's stored payload, off the sparse map by own key: a document key can be
|
|
171
|
+
* spelled `constructor`. */
|
|
172
|
+
export declare function memberValue(map: Record<string, unknown> | undefined, id: string): unknown;
|
|
173
|
+
/**
|
|
174
|
+
* Whether a stored member is held. A bare scalar is the tick and a mapping is the member
|
|
175
|
+
* object, whose `held` cell resolves as every absent cell does, to its `default:` of
|
|
176
|
+
* `false` (canon `SCHEMAS.md` §Matrix): an absent key is unheld, a bare `true` is held,
|
|
177
|
+
* and a member object is held only where its `held` cell says so. The spellings the
|
|
178
|
+
* engine coerces to false — `false`, `0`, `"false"`, `null` — read false here; every other
|
|
179
|
+
* present scalar reads held, so a document the engine would refuse still draws the tick
|
|
180
|
+
* its spelling claims.
|
|
181
|
+
*/
|
|
182
|
+
export declare function matrixHeld(stored: unknown): boolean;
|
|
183
|
+
/** A member's columns: the member object with its `held` cell taken out, `{}` for the
|
|
184
|
+
* bare and absent spellings. What the columns subform takes as its value. */
|
|
185
|
+
export declare function matrixColumns(stored: unknown): Record<string, unknown>;
|
|
186
|
+
/**
|
|
187
|
+
* The map after one member moves: `next` written under `id`, or the key dropped where
|
|
188
|
+
* `next` is `undefined`; a map left holding nothing is an unset field. Every other
|
|
189
|
+
* member rides through in the spelling the document had, so a stored `cyber_200: true`
|
|
190
|
+
* stays `true` while its neighbour is edited.
|
|
191
|
+
*/
|
|
192
|
+
export declare function commitMember(map: Record<string, unknown> | undefined, id: string, next: Record<string, unknown> | undefined): Record<string, unknown> | undefined;
|
|
193
|
+
/**
|
|
194
|
+
* The member a tick or an edit writes. Held with columns is the member object with an
|
|
195
|
+
* explicit `held: true`; unheld keeps the columns under `held: false`, so tick, type,
|
|
196
|
+
* untick, retick loses nothing; unheld with no columns is `undefined`, the key dropped.
|
|
197
|
+
* Which columns are blank is the engine's rule and not restated here: a column control
|
|
198
|
+
* that clears drops its key (`ObjectField`), so a member emptied by hand arrives with no
|
|
199
|
+
* columns and only then leaves the map.
|
|
200
|
+
*/
|
|
201
|
+
export declare function memberWrite(held: boolean, columns: Record<string, unknown>): Record<string, unknown> | undefined;
|
|
202
|
+
/** The `object` a matrix member desugars to, for the schema walk: `held` beside the
|
|
203
|
+
* declared columns. Composed here because the boundary serializes the roster alone. */
|
|
204
|
+
export declare function matrixMemberSchema(matrix: QuillFieldSchema): QuillFieldSchema;
|
|
205
|
+
/** Whether `id` is on the roster. */
|
|
206
|
+
export declare function matrixDeclares(matrix: QuillFieldSchema, id: string): boolean;
|
|
207
|
+
/**
|
|
208
|
+
* The schema at `steps` under `field`, or `undefined` where the schema declares no such
|
|
209
|
+
* place: an index under an `array`, a key under an `object`'s properties, a member id
|
|
210
|
+
* under a `matrix` and a key under that member, a cell of any world (or the
|
|
211
|
+
* discriminant) under a variant-bearing `enum`. The editor's copy of the boundary's
|
|
212
|
+
* `schema_at`, held here because the landing lane checks an address against the schema
|
|
213
|
+
* before it asks the mounted tree for it (VISUAL_EDITOR §Surface).
|
|
214
|
+
*/
|
|
215
|
+
export declare function schemaAt(field: QuillFieldSchema, steps: readonly PathStep[]): QuillFieldSchema | undefined;
|
|
216
|
+
/** 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. */
|
|
218
|
+
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
|
+
*/
|
|
226
|
+
export declare function rowSummary(items: QuillFieldSchema | undefined, row: unknown): string | undefined;
|
|
227
|
+
/** How an `array` draws its elements. */
|
|
228
|
+
export type ArrayLayout = 'list' | 'table';
|
|
229
|
+
/**
|
|
230
|
+
* The layout an array takes: `'table'` where the array's own `ui.layout` asks for it
|
|
231
|
+
* and every cell of the row is short (a block prose cell declines it, at load width and
|
|
232
|
+
* every width after), else `'list'`. That every column is a leaf is the loader's
|
|
233
|
+
* contract (`quill::table_column_not_flat`); whether a leaf fits a cell is the surface's
|
|
234
|
+
* answer (canon `SCHEMAS.md`): a table composes by position, so a row that would stack
|
|
235
|
+
* inside a cell is not one.
|
|
236
|
+
*/
|
|
237
|
+
export declare function arrayLayout(field: QuillFieldSchema): ArrayLayout;
|
|
154
238
|
/**
|
|
155
239
|
* Which world's cells to draw: the authored discriminant when the container carries
|
|
156
240
|
* one, else the ghosted default's member — an unset field renders its `default:`, so a
|
|
@@ -170,9 +254,9 @@ export declare function variantCells(schema: QuillFieldSchema, member: string |
|
|
|
170
254
|
export declare function commitDiscriminant(value: Record<string, unknown> | undefined, member: string | undefined): Record<string, unknown> | undefined;
|
|
171
255
|
/**
|
|
172
256
|
* Whether the schema obliges a cell: `default:`'s absence, which is the whole of the
|
|
173
|
-
* obligation (DOCUMENT_MODEL). A typed dictionary
|
|
174
|
-
* `default:` at all, and `validate` anchors obligation on the leaves under
|
|
175
|
-
* reading one off the container would mark every subform required.
|
|
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.
|
|
176
260
|
*/
|
|
177
261
|
export declare function obliged(schema: QuillFieldSchema): boolean;
|
|
178
262
|
/** `foo_bar` → `Foo bar`: the label fallback when a field declares no `ui.title`. */
|
|
@@ -233,8 +317,17 @@ export interface PlacedField {
|
|
|
233
317
|
* Pure, and stays pure: no width, no measurement, nothing to re-derive on resize.
|
|
234
318
|
*/
|
|
235
319
|
export declare function placeFields(fields: FieldModel[]): PlacedField[];
|
|
320
|
+
/** A card's field values by key, off its payload items. */
|
|
321
|
+
export declare function fieldValues(items: readonly PayloadItem[]): Record<string, unknown>;
|
|
236
322
|
/** Interpolate a `{field}` card-title template against live field values. */
|
|
237
323
|
export declare function interpolateTitle(template: string, values: Record<string, unknown>): string;
|
|
324
|
+
/** A field value as title text. A parsed field rests as the authored string and a
|
|
325
|
+
* committed one as `Content`, whose `text` is the same words; a scalar reads as
|
|
326
|
+
* itself; a variant container reads as its discriminant member, the one cell of it
|
|
327
|
+
* that names the world; any other container as nothing. */
|
|
328
|
+
export declare function titleText(v: unknown): string;
|
|
329
|
+
/** The field names a `{field}` title reads. */
|
|
330
|
+
export declare function titleFields(template: string | undefined): string[];
|
|
238
331
|
/**
|
|
239
332
|
* Resolve a card instance's header title: the per-instance `$ext.editor.title`
|
|
240
333
|
* override wins; else the schema `ui.title` (literal or `{field}` template
|
package/dist/visual/structure.js
CHANGED
|
@@ -1,3 +1,15 @@
|
|
|
1
|
+
// The schema × payload join, done by the editor (VISUAL_EDITOR §Structure). Pure
|
|
2
|
+
// functions only (no runes, no Document reads) so the ordering, control
|
|
3
|
+
// dispatch, group layout, title interpolation, and session-identity bookkeeping
|
|
4
|
+
// are unit-testable in isolation (tests/visual/structure.test.ts). The reactive
|
|
5
|
+
// orchestration (revision counter, live doc reads) lives in VisualEditor.svelte;
|
|
6
|
+
// this module is the projection math it feeds.
|
|
7
|
+
import { VARIANT_DISCRIMINANT_KEY } from '@quillmark/wasm';
|
|
8
|
+
/** The container controls: the ones a nested address walks into and a deep
|
|
9
|
+
* diagnostic routes through. */
|
|
10
|
+
export function isContainer(kind) {
|
|
11
|
+
return kind === 'array' || kind === 'object' || kind === 'variant' || kind === 'matrix';
|
|
12
|
+
}
|
|
1
13
|
/**
|
|
2
14
|
* A card's resolved rows keyed by field name: the provenance channel parallel to
|
|
3
15
|
* {@link CardModel.values}. `resolve` returns rows in declaration order; the
|
|
@@ -72,13 +84,208 @@ export function controlKind(f) {
|
|
|
72
84
|
return 'array';
|
|
73
85
|
case 'object':
|
|
74
86
|
return 'object';
|
|
87
|
+
case 'matrix':
|
|
88
|
+
return 'matrix';
|
|
75
89
|
default:
|
|
76
90
|
return 'text';
|
|
77
91
|
}
|
|
78
92
|
}
|
|
79
|
-
/** The discriminant cell of a variant container
|
|
80
|
-
*
|
|
81
|
-
|
|
93
|
+
/** The discriminant cell of a variant container, off the boundary's own constant: it
|
|
94
|
+
* crosses inside untyped container data with no type to read it off, so a literal here
|
|
95
|
+
* would be a second spelling of a reserved key. No variant may declare it. */
|
|
96
|
+
export const VARIANT_DISCRIMINANT = VARIANT_DISCRIMINANT_KEY;
|
|
97
|
+
/** The tick cell of a matrix member, the second reserved key beside the discriminant.
|
|
98
|
+
* The boundary synthesizes it on every member and exports no constant for it, so this
|
|
99
|
+
* is where the package spells it once (canon `SCHEMAS.md` §Matrix). */
|
|
100
|
+
export const MATRIX_HELD = 'held';
|
|
101
|
+
/** The roster's members, in declaration order. Own keys only: a schema map is indexed
|
|
102
|
+
* by a document-supplied id, and `'toString' in members` is true of every roster. */
|
|
103
|
+
export function matrixMembers(roster) {
|
|
104
|
+
return roster ? Object.keys(roster).map((id) => ({ id, title: roster[id] })) : [];
|
|
105
|
+
}
|
|
106
|
+
/** A member's stored payload, off the sparse map by own key: a document key can be
|
|
107
|
+
* spelled `constructor`. */
|
|
108
|
+
export function memberValue(map, id) {
|
|
109
|
+
return map && Object.hasOwn(map, id) ? map[id] : undefined;
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* Whether a stored member is held. A bare scalar is the tick and a mapping is the member
|
|
113
|
+
* object, whose `held` cell resolves as every absent cell does, to its `default:` of
|
|
114
|
+
* `false` (canon `SCHEMAS.md` §Matrix): an absent key is unheld, a bare `true` is held,
|
|
115
|
+
* and a member object is held only where its `held` cell says so. The spellings the
|
|
116
|
+
* engine coerces to false — `false`, `0`, `"false"`, `null` — read false here; every other
|
|
117
|
+
* present scalar reads held, so a document the engine would refuse still draws the tick
|
|
118
|
+
* its spelling claims.
|
|
119
|
+
*/
|
|
120
|
+
export function matrixHeld(stored) {
|
|
121
|
+
if (typeof stored === 'object' && stored !== null)
|
|
122
|
+
return (Object.hasOwn(stored, MATRIX_HELD) && tickOf(stored[MATRIX_HELD]));
|
|
123
|
+
return tickOf(stored);
|
|
124
|
+
}
|
|
125
|
+
/** A tick's scalar reading: absent and the engine's false spellings unheld, the rest held. */
|
|
126
|
+
function tickOf(v) {
|
|
127
|
+
if (v === undefined || v === null)
|
|
128
|
+
return false;
|
|
129
|
+
if (typeof v === 'boolean')
|
|
130
|
+
return v;
|
|
131
|
+
if (typeof v === 'number')
|
|
132
|
+
return v !== 0;
|
|
133
|
+
if (typeof v === 'string')
|
|
134
|
+
return v !== 'false';
|
|
135
|
+
return true;
|
|
136
|
+
}
|
|
137
|
+
/** A member's columns: the member object with its `held` cell taken out, `{}` for the
|
|
138
|
+
* bare and absent spellings. What the columns subform takes as its value. */
|
|
139
|
+
export function matrixColumns(stored) {
|
|
140
|
+
if (typeof stored !== 'object' || stored === null || Array.isArray(stored))
|
|
141
|
+
return {};
|
|
142
|
+
const rest = { ...stored };
|
|
143
|
+
delete rest[MATRIX_HELD];
|
|
144
|
+
return rest;
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
147
|
+
* The map after one member moves: `next` written under `id`, or the key dropped where
|
|
148
|
+
* `next` is `undefined`; a map left holding nothing is an unset field. Every other
|
|
149
|
+
* member rides through in the spelling the document had, so a stored `cyber_200: true`
|
|
150
|
+
* stays `true` while its neighbour is edited.
|
|
151
|
+
*/
|
|
152
|
+
export function commitMember(map, id, next) {
|
|
153
|
+
const out = { ...(map ?? {}) };
|
|
154
|
+
if (next === undefined)
|
|
155
|
+
delete out[id];
|
|
156
|
+
else
|
|
157
|
+
out[id] = next;
|
|
158
|
+
return Object.keys(out).length ? out : undefined;
|
|
159
|
+
}
|
|
160
|
+
/**
|
|
161
|
+
* The member a tick or an edit writes. Held with columns is the member object with an
|
|
162
|
+
* explicit `held: true`; unheld keeps the columns under `held: false`, so tick, type,
|
|
163
|
+
* untick, retick loses nothing; unheld with no columns is `undefined`, the key dropped.
|
|
164
|
+
* Which columns are blank is the engine's rule and not restated here: a column control
|
|
165
|
+
* that clears drops its key (`ObjectField`), so a member emptied by hand arrives with no
|
|
166
|
+
* columns and only then leaves the map.
|
|
167
|
+
*/
|
|
168
|
+
export function memberWrite(held, columns) {
|
|
169
|
+
if (!held && Object.keys(columns).length === 0)
|
|
170
|
+
return undefined;
|
|
171
|
+
return { [MATRIX_HELD]: held, ...columns };
|
|
172
|
+
}
|
|
173
|
+
/** The `object` a matrix member desugars to, for the schema walk: `held` beside the
|
|
174
|
+
* declared columns. Composed here because the boundary serializes the roster alone. */
|
|
175
|
+
export function matrixMemberSchema(matrix) {
|
|
176
|
+
return {
|
|
177
|
+
type: 'object',
|
|
178
|
+
properties: {
|
|
179
|
+
[MATRIX_HELD]: { type: 'boolean', default: false },
|
|
180
|
+
...(matrix.properties ?? {})
|
|
181
|
+
}
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
/** Whether `id` is on the roster. */
|
|
185
|
+
export function matrixDeclares(matrix, id) {
|
|
186
|
+
return matrix.members != null && Object.hasOwn(matrix.members, id);
|
|
187
|
+
}
|
|
188
|
+
// ── The schema walk ──────────────────────────────────────────────────────────
|
|
189
|
+
/**
|
|
190
|
+
* The schema at `steps` under `field`, or `undefined` where the schema declares no such
|
|
191
|
+
* place: an index under an `array`, a key under an `object`'s properties, a member id
|
|
192
|
+
* under a `matrix` and a key under that member, a cell of any world (or the
|
|
193
|
+
* discriminant) under a variant-bearing `enum`. The editor's copy of the boundary's
|
|
194
|
+
* `schema_at`, held here because the landing lane checks an address against the schema
|
|
195
|
+
* before it asks the mounted tree for it (VISUAL_EDITOR §Surface).
|
|
196
|
+
*/
|
|
197
|
+
export function schemaAt(field, steps) {
|
|
198
|
+
let cursor = field;
|
|
199
|
+
for (const step of steps) {
|
|
200
|
+
if (!cursor)
|
|
201
|
+
return undefined;
|
|
202
|
+
cursor = stepInto(cursor, step);
|
|
203
|
+
}
|
|
204
|
+
return cursor;
|
|
205
|
+
}
|
|
206
|
+
/** What an array declaring no `items` holds, the walk's copy of the element control
|
|
207
|
+
* {@link controlKind} names for one: a text element, so an index into such an array is
|
|
208
|
+
* a place and not a refusal. */
|
|
209
|
+
const UNDECLARED_ELEMENT = { type: 'string' };
|
|
210
|
+
function stepInto(schema, step) {
|
|
211
|
+
switch (controlKind(schema)) {
|
|
212
|
+
case 'array':
|
|
213
|
+
return typeof step === 'number' ? (schema.items ?? UNDECLARED_ELEMENT) : undefined;
|
|
214
|
+
case 'object':
|
|
215
|
+
return typeof step === 'string' && Object.hasOwn(schema.properties ?? {}, step)
|
|
216
|
+
? schema.properties[step]
|
|
217
|
+
: undefined;
|
|
218
|
+
case 'matrix':
|
|
219
|
+
return typeof step === 'string' && matrixDeclares(schema, step)
|
|
220
|
+
? matrixMemberSchema(schema)
|
|
221
|
+
: undefined;
|
|
222
|
+
case 'variant': {
|
|
223
|
+
if (typeof step !== 'string')
|
|
224
|
+
return undefined;
|
|
225
|
+
if (step === VARIANT_DISCRIMINANT)
|
|
226
|
+
return { type: 'enum', values: schema.values };
|
|
227
|
+
// Which world is live is a value-time fact, so the walk unions the worlds, as
|
|
228
|
+
// the boundary's does.
|
|
229
|
+
for (const cells of Object.values(schema.variants ?? {}))
|
|
230
|
+
if (Object.hasOwn(cells, step))
|
|
231
|
+
return cells[step];
|
|
232
|
+
return undefined;
|
|
233
|
+
}
|
|
234
|
+
default:
|
|
235
|
+
return undefined;
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
// ── Row summaries and layouts ────────────────────────────────────────────────
|
|
239
|
+
/** Whether a cell is one line tall by declaration: the shape a table column and a row
|
|
240
|
+
* summary can hold. A `plaintext` leaf is inline at the codec whatever it declares. */
|
|
241
|
+
export function shortCell(sub) {
|
|
242
|
+
const kind = controlKind(sub);
|
|
243
|
+
if (kind === 'prose')
|
|
244
|
+
return sub.type === 'plaintext' || !!sub.inline;
|
|
245
|
+
return (kind === 'text' || kind === 'enum' || kind === 'number' || kind === 'boolean' || kind === 'date');
|
|
246
|
+
}
|
|
247
|
+
/**
|
|
248
|
+
* A collapsed row's own words: `items.ui.title` interpolated with the row's values
|
|
249
|
+
* where the schema declares one, else the first short text cell in declaration order
|
|
250
|
+
* — a `string`, or an inline `richtext` / `plaintext` — read through {@link titleText};
|
|
251
|
+
* `undefined` while the row has nothing to say for itself. `title` sits on `items.ui`,
|
|
252
|
+
* describing a row, where `layout` sits on the array's own `ui`, describing the array.
|
|
253
|
+
*/
|
|
254
|
+
export function rowSummary(items, row) {
|
|
255
|
+
const values = (row ?? {});
|
|
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 ?? {})) {
|
|
263
|
+
const kind = controlKind(sub);
|
|
264
|
+
if (kind !== 'text' && !(kind === 'prose' && shortCell(sub)))
|
|
265
|
+
continue;
|
|
266
|
+
const text = titleText(ownValue(values, k)).trim();
|
|
267
|
+
if (text)
|
|
268
|
+
return text;
|
|
269
|
+
}
|
|
270
|
+
return undefined;
|
|
271
|
+
}
|
|
272
|
+
/**
|
|
273
|
+
* The layout an array takes: `'table'` where the array's own `ui.layout` asks for it
|
|
274
|
+
* and every cell of the row is short (a block prose cell declines it, at load width and
|
|
275
|
+
* every width after), else `'list'`. That every column is a leaf is the loader's
|
|
276
|
+
* contract (`quill::table_column_not_flat`); whether a leaf fits a cell is the surface's
|
|
277
|
+
* answer (canon `SCHEMAS.md`): a table composes by position, so a row that would stack
|
|
278
|
+
* inside a cell is not one.
|
|
279
|
+
*/
|
|
280
|
+
export function arrayLayout(field) {
|
|
281
|
+
if (field.ui?.layout !== 'table')
|
|
282
|
+
return 'list';
|
|
283
|
+
const items = field.items;
|
|
284
|
+
if (!items || controlKind(items) !== 'object')
|
|
285
|
+
return 'list';
|
|
286
|
+
const cells = Object.values(items.properties ?? {});
|
|
287
|
+
return cells.length > 0 && cells.every(shortCell) ? 'table' : 'list';
|
|
288
|
+
}
|
|
82
289
|
/**
|
|
83
290
|
* Which world's cells to draw: the authored discriminant when the container carries
|
|
84
291
|
* one, else the ghosted default's member — an unset field renders its `default:`, so a
|
|
@@ -109,12 +316,12 @@ export function commitDiscriminant(value, member) {
|
|
|
109
316
|
}
|
|
110
317
|
/**
|
|
111
318
|
* Whether the schema obliges a cell: `default:`'s absence, which is the whole of the
|
|
112
|
-
* obligation (DOCUMENT_MODEL). A typed dictionary
|
|
113
|
-
* `default:` at all, and `validate` anchors obligation on the leaves under
|
|
114
|
-
* reading one off the container would mark every subform required.
|
|
319
|
+
* obligation (DOCUMENT_MODEL). A typed dictionary and a matrix are exempt — a namespace
|
|
320
|
+
* declares no `default:` at all, and `validate` anchors obligation on the leaves under
|
|
321
|
+
* it, so reading one off the container would mark every subform required.
|
|
115
322
|
*/
|
|
116
323
|
export function obliged(schema) {
|
|
117
|
-
return schema.type !== 'object' && schema.default === undefined;
|
|
324
|
+
return schema.type !== 'object' && schema.type !== 'matrix' && schema.default === undefined;
|
|
118
325
|
}
|
|
119
326
|
/** `foo_bar` → `Foo bar`: the label fallback when a field declares no `ui.title`. */
|
|
120
327
|
export function humanize(name) {
|
|
@@ -225,12 +432,14 @@ export function initialExpandedGroup(sections) {
|
|
|
225
432
|
* An array declines whatever its items are: one-line elements make a one-line step,
|
|
226
433
|
* but nothing holds two arrays to the same number of them, so the shorter of a packed
|
|
227
434
|
* pair pays a cell of whitespace for every element the taller one has past it, and
|
|
228
|
-
* pays more of it as the document is filled.
|
|
435
|
+
* pays more of it as the document is filled. A matrix declines as a roster does: its
|
|
436
|
+
* height is the roster's, and grows again under every member ticked open. Its hint
|
|
437
|
+
* reaches the roster instead, whose members stand abreast (`MatrixField`).
|
|
229
438
|
*/
|
|
230
439
|
function packable(f) {
|
|
231
440
|
if (!f.compact)
|
|
232
441
|
return false;
|
|
233
|
-
if (f.control
|
|
442
|
+
if (isContainer(f.control))
|
|
234
443
|
return false;
|
|
235
444
|
return f.control !== 'prose' || f.inline;
|
|
236
445
|
}
|
|
@@ -268,12 +477,42 @@ export function placeFields(fields) {
|
|
|
268
477
|
flush();
|
|
269
478
|
return out;
|
|
270
479
|
}
|
|
480
|
+
/** A card's field values by key, off its payload items. */
|
|
481
|
+
export function fieldValues(items) {
|
|
482
|
+
const values = {};
|
|
483
|
+
for (const p of items)
|
|
484
|
+
if (p.type === 'field')
|
|
485
|
+
values[p.key] = p.value;
|
|
486
|
+
return values;
|
|
487
|
+
}
|
|
271
488
|
/** Interpolate a `{field}` card-title template against live field values. */
|
|
272
489
|
export function interpolateTitle(template, values) {
|
|
273
|
-
return template.replace(/\{([A-Za-z_][A-Za-z0-9_]*)\}/g, (_m, name) =>
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
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`. */
|
|
493
|
+
function ownValue(values, name) {
|
|
494
|
+
return Object.hasOwn(values, name) ? values[name] : undefined;
|
|
495
|
+
}
|
|
496
|
+
/** A field value as title text. A parsed field rests as the authored string and a
|
|
497
|
+
* committed one as `Content`, whose `text` is the same words; a scalar reads as
|
|
498
|
+
* itself; a variant container reads as its discriminant member, the one cell of it
|
|
499
|
+
* that names the world; any other container as nothing. */
|
|
500
|
+
export function titleText(v) {
|
|
501
|
+
if (v == null)
|
|
502
|
+
return '';
|
|
503
|
+
if (typeof v !== 'object')
|
|
504
|
+
return String(v);
|
|
505
|
+
const text = v.text;
|
|
506
|
+
if (typeof text === 'string')
|
|
507
|
+
return text;
|
|
508
|
+
const member = v[VARIANT_DISCRIMINANT];
|
|
509
|
+
return typeof member === 'string' ? member : '';
|
|
510
|
+
}
|
|
511
|
+
/** The field names a `{field}` title reads. */
|
|
512
|
+
export function titleFields(template) {
|
|
513
|
+
if (!template)
|
|
514
|
+
return [];
|
|
515
|
+
return [...template.matchAll(/\{([A-Za-z_][A-Za-z0-9_]*)\}/g)].map((m) => m[1]);
|
|
277
516
|
}
|
|
278
517
|
/**
|
|
279
518
|
* Resolve a card instance's header title: the per-instance `$ext.editor.title`
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@quillmark/svelte",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.12.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.115.0-0",
|
|
70
70
|
"svelte": "^5.0.0"
|
|
71
71
|
},
|
|
72
72
|
"dependencies": {
|