@quillmark/svelte 0.16.0 → 0.17.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -1
- package/dist/core/theme.css +12 -1
- package/dist/visual/ArrayField.svelte +12 -1
- package/dist/visual/BooleanField.svelte +7 -0
- package/dist/visual/Card.svelte +60 -17
- package/dist/visual/Field.svelte +10 -2
- package/dist/visual/Field.svelte.d.ts +3 -0
- package/dist/visual/MatrixAdd.svelte +211 -0
- package/dist/visual/MatrixAdd.svelte.d.ts +16 -0
- package/dist/visual/MatrixField.svelte +329 -82
- package/dist/visual/MatrixField.svelte.d.ts +8 -5
- package/dist/visual/ObjectField.svelte +23 -6
- package/dist/visual/ObjectField.svelte.d.ts +3 -0
- package/dist/visual/VisualEditorInner.svelte +15 -0
- package/dist/visual/checklists.d.ts +82 -0
- package/dist/visual/checklists.js +171 -0
- package/dist/visual/strings.d.ts +15 -1
- package/dist/visual/strings.js +6 -0
- package/dist/visual/structure.d.ts +59 -31
- package/dist/visual/structure.js +110 -44
- package/package.json +2 -2
package/dist/visual/structure.js
CHANGED
|
@@ -11,6 +11,11 @@ import { core } from '../core/lifecycle.js';
|
|
|
11
11
|
export function isContainer(kind) {
|
|
12
12
|
return kind === 'array' || kind === 'object' || kind === 'variant' || kind === 'matrix';
|
|
13
13
|
}
|
|
14
|
+
/** `ui.layout: flat` on a typed dictionary: no frame of its own, its properties
|
|
15
|
+
* standing at the depth of the fields around it rather than a rung in. */
|
|
16
|
+
export function flatObject(schema) {
|
|
17
|
+
return controlKind(schema) === 'object' && schema.ui?.layout === 'flat';
|
|
18
|
+
}
|
|
14
19
|
/**
|
|
15
20
|
* A card's resolved rows keyed by field name: the provenance channel parallel to
|
|
16
21
|
* {@link CardModel.values}. `resolve` returns rows in declaration order; the
|
|
@@ -142,10 +147,10 @@ export function controlKind(f) {
|
|
|
142
147
|
* crosses inside untyped container data with no type to read it off, so a literal here
|
|
143
148
|
* would be a second spelling of a reserved key. No variant may declare it. */
|
|
144
149
|
export const VARIANT_DISCRIMINANT = VARIANT_DISCRIMINANT_KEY;
|
|
145
|
-
/** The
|
|
146
|
-
* The boundary
|
|
147
|
-
*
|
|
148
|
-
export const
|
|
150
|
+
/** The label key of an open matrix's added item, the one cell a roster member never
|
|
151
|
+
* stores. The boundary exports no constant for it, so the package spells it once
|
|
152
|
+
* (canon `SCHEMAS.md` §Matrix). */
|
|
153
|
+
export const MATRIX_TITLE = 'title';
|
|
149
154
|
/** The roster's members, in declaration order. Own keys only: a schema map is indexed
|
|
150
155
|
* by a document-supplied id, and `'toString' in members` is true of every roster. */
|
|
151
156
|
export function matrixMembers(roster) {
|
|
@@ -157,17 +162,14 @@ export function memberValue(map, id) {
|
|
|
157
162
|
return map && Object.hasOwn(map, id) ? map[id] : undefined;
|
|
158
163
|
}
|
|
159
164
|
/**
|
|
160
|
-
* Whether a stored member is held
|
|
161
|
-
*
|
|
162
|
-
* `false`
|
|
163
|
-
*
|
|
164
|
-
* engine coerces to false — `false`, `0`, `"false"`, `null` — read false here; every other
|
|
165
|
-
* present scalar reads held, so a document the engine would refuse still draws the tick
|
|
166
|
-
* its spelling claims.
|
|
165
|
+
* Whether a stored member is held: by being present. A mapping is held whatever it
|
|
166
|
+
* carries, `{}` included, and a bare scalar is the tick itself, read as the engine's
|
|
167
|
+
* coercion reads it: `false`, `0`, `"false"` and `null` unheld, every other present
|
|
168
|
+
* scalar held, so a spelling the engine would refuse still draws the tick it claims.
|
|
167
169
|
*/
|
|
168
170
|
export function matrixHeld(stored) {
|
|
169
171
|
if (typeof stored === 'object' && stored !== null)
|
|
170
|
-
return
|
|
172
|
+
return true;
|
|
171
173
|
return tickOf(stored);
|
|
172
174
|
}
|
|
173
175
|
/** A tick's scalar reading: absent and the engine's false spellings unheld, the rest held. */
|
|
@@ -182,20 +184,29 @@ function tickOf(v) {
|
|
|
182
184
|
return v !== 'false';
|
|
183
185
|
return true;
|
|
184
186
|
}
|
|
185
|
-
/**
|
|
186
|
-
*
|
|
187
|
+
/** The keys no column may take (`quill::matrix_reserved_column`): an added item's
|
|
188
|
+
* label, and the `held` a member never stores. */
|
|
189
|
+
const MATRIX_RESERVED = [MATRIX_TITLE, 'held'];
|
|
190
|
+
/** A member's columns: its mapping without the reserved keys, `{}` for a bare tick or an
|
|
191
|
+
* absent member. What the columns subform takes as its value, so an edit writes back
|
|
192
|
+
* no `held` a document stored (`validation::held_stored`). */
|
|
187
193
|
export function matrixColumns(stored) {
|
|
188
194
|
if (typeof stored !== 'object' || stored === null || Array.isArray(stored))
|
|
189
195
|
return {};
|
|
190
196
|
const rest = { ...stored };
|
|
191
|
-
|
|
197
|
+
for (const key of MATRIX_RESERVED)
|
|
198
|
+
delete rest[key];
|
|
192
199
|
return rest;
|
|
193
200
|
}
|
|
201
|
+
/** What a matrix holding nothing commits: the unset field where unset prints nothing
|
|
202
|
+
* held, and the authored `{}` where it would print a `default:` or `none` instead. */
|
|
203
|
+
export function matrixEmpty(matrix) {
|
|
204
|
+
return matrix.default !== undefined || optionalCell(matrix) ? {} : undefined;
|
|
205
|
+
}
|
|
194
206
|
/**
|
|
195
207
|
* The map after one member moves: `next` written under `id`, or the key dropped where
|
|
196
208
|
* `next` is `undefined`; a map left holding nothing is an unset field. Every other
|
|
197
|
-
* member rides through in the spelling the document had
|
|
198
|
-
* stays `true` while its neighbour is edited.
|
|
209
|
+
* member rides through in the spelling the document had.
|
|
199
210
|
*/
|
|
200
211
|
export function commitMember(map, id, next) {
|
|
201
212
|
const out = { ...(map ?? {}) };
|
|
@@ -206,33 +217,86 @@ export function commitMember(map, id, next) {
|
|
|
206
217
|
return Object.keys(out).length ? out : undefined;
|
|
207
218
|
}
|
|
208
219
|
/**
|
|
209
|
-
* The member a tick or an edit writes
|
|
210
|
-
*
|
|
211
|
-
*
|
|
212
|
-
* Which columns are blank is the engine's rule and not restated here: a column control
|
|
213
|
-
* that clears drops its key (`ObjectField`), so a member emptied by hand arrives with no
|
|
214
|
-
* columns and only then leaves the map.
|
|
220
|
+
* The held member a tick or an edit writes: its columns, the bare tick `true` where
|
|
221
|
+
* it answers none, and an added item's `title` beside them. Unticking is removal
|
|
222
|
+
* ({@link commitMember} with `undefined`), never a write.
|
|
215
223
|
*/
|
|
216
|
-
export function memberWrite(
|
|
217
|
-
if (
|
|
218
|
-
return
|
|
219
|
-
return
|
|
224
|
+
export function memberWrite(columns, title) {
|
|
225
|
+
if (title !== undefined)
|
|
226
|
+
return { [MATRIX_TITLE]: title, ...columns };
|
|
227
|
+
return Object.keys(columns).length ? columns : true;
|
|
220
228
|
}
|
|
221
|
-
/**
|
|
222
|
-
*
|
|
223
|
-
export function
|
|
224
|
-
return
|
|
225
|
-
type: 'object',
|
|
226
|
-
properties: {
|
|
227
|
-
[MATRIX_HELD]: { type: 'boolean', default: false },
|
|
228
|
-
...(matrix.properties ?? {})
|
|
229
|
-
}
|
|
230
|
-
};
|
|
229
|
+
/** Whether `id` is spelled as a matrix id: snake_case, the rule the roster's ids and
|
|
230
|
+
* an added item's key share. */
|
|
231
|
+
export function matrixId(id) {
|
|
232
|
+
return /^[a-z][a-z0-9_]*$/.test(id);
|
|
231
233
|
}
|
|
232
234
|
/** Whether `id` is on the roster. */
|
|
233
|
-
export function
|
|
235
|
+
export function onRoster(matrix, id) {
|
|
234
236
|
return matrix.members != null && Object.hasOwn(matrix.members, id);
|
|
235
237
|
}
|
|
238
|
+
/**
|
|
239
|
+
* Whether a step under the matrix names a member, judged on the schema alone as an
|
|
240
|
+
* index step is: a roster id, or on an open matrix any key spelled as an id, the added
|
|
241
|
+
* item it would be. So an unheld member's address resolves.
|
|
242
|
+
*/
|
|
243
|
+
export function matrixDeclares(matrix, id) {
|
|
244
|
+
return onRoster(matrix, id) || (!!matrix.open && matrixId(id));
|
|
245
|
+
}
|
|
246
|
+
/**
|
|
247
|
+
* The added items a stored map holds, sorted by id as the plate orders them: on an
|
|
248
|
+
* open matrix, each key off the roster spelled as an id whose mapping carries a
|
|
249
|
+
* `title`. Any other key off the roster is the engine's refusal, drawn nowhere and
|
|
250
|
+
* ridden through.
|
|
251
|
+
*/
|
|
252
|
+
export function addedItems(matrix, map) {
|
|
253
|
+
if (!matrix.open || !map)
|
|
254
|
+
return [];
|
|
255
|
+
const out = [];
|
|
256
|
+
for (const id of Object.keys(map)) {
|
|
257
|
+
if (onRoster(matrix, id) || !matrixId(id))
|
|
258
|
+
continue;
|
|
259
|
+
const stored = map[id];
|
|
260
|
+
if (typeof stored !== 'object' || stored === null || Array.isArray(stored))
|
|
261
|
+
continue;
|
|
262
|
+
const title = stored[MATRIX_TITLE];
|
|
263
|
+
if (title == null)
|
|
264
|
+
continue;
|
|
265
|
+
out.push({ id, title: String(title) });
|
|
266
|
+
}
|
|
267
|
+
return out.sort((a, b) => (a.id < b.id ? -1 : a.id > b.id ? 1 : 0));
|
|
268
|
+
}
|
|
269
|
+
/**
|
|
270
|
+
* The id an item added under `title` takes: the title folded to snake_case, prefixed
|
|
271
|
+
* where it opens on no letter, and suffixed past every id `taken` names, the roster's
|
|
272
|
+
* and the document's alike. The id is the wire's and never drawn, so a later retitle
|
|
273
|
+
* leaves it where it is.
|
|
274
|
+
*/
|
|
275
|
+
export function addedId(title, taken) {
|
|
276
|
+
const folded = title
|
|
277
|
+
.normalize('NFKD')
|
|
278
|
+
.replace(/[\u0300-\u036f]/g, '')
|
|
279
|
+
.toLowerCase()
|
|
280
|
+
.replace(/[^a-z0-9]+/g, '_')
|
|
281
|
+
.replace(/^_+|_+$/g, '');
|
|
282
|
+
const base = /^[a-z]/.test(folded) ? folded : `item${folded ? `_${folded}` : ''}`;
|
|
283
|
+
if (!taken(base))
|
|
284
|
+
return base;
|
|
285
|
+
let n = 2;
|
|
286
|
+
while (taken(`${base}_${n}`))
|
|
287
|
+
n++;
|
|
288
|
+
return `${base}_${n}`;
|
|
289
|
+
}
|
|
290
|
+
/** The `object` a matrix member is, for the schema walk: the declared columns, and an
|
|
291
|
+
* added item's `title` beside them. Composed here because the boundary serializes the
|
|
292
|
+
* roster and the columns alone. */
|
|
293
|
+
export function matrixMemberSchema(matrix, id) {
|
|
294
|
+
const columns = matrix.properties ?? {};
|
|
295
|
+
return {
|
|
296
|
+
type: 'object',
|
|
297
|
+
properties: onRoster(matrix, id) ? columns : { [MATRIX_TITLE]: { type: 'string' }, ...columns }
|
|
298
|
+
};
|
|
299
|
+
}
|
|
236
300
|
// ── The schema walk ──────────────────────────────────────────────────────────
|
|
237
301
|
/**
|
|
238
302
|
* The schema at `steps` under `field`, or `undefined` where the schema declares no such
|
|
@@ -265,7 +329,7 @@ function stepInto(schema, step) {
|
|
|
265
329
|
: undefined;
|
|
266
330
|
case 'matrix':
|
|
267
331
|
return typeof step === 'string' && matrixDeclares(schema, step)
|
|
268
|
-
? matrixMemberSchema(schema)
|
|
332
|
+
? matrixMemberSchema(schema, step)
|
|
269
333
|
: undefined;
|
|
270
334
|
case 'variant': {
|
|
271
335
|
if (typeof step !== 'string')
|
|
@@ -318,7 +382,7 @@ export function rowSummary(items, row) {
|
|
|
318
382
|
* The layout an array takes: `'table'` where the array's own `ui.layout` asks for it
|
|
319
383
|
* and every cell of the row is short (a block prose cell declines it, at load width and
|
|
320
384
|
* every width after), else `'list'`. That every column is a leaf is the loader's
|
|
321
|
-
* contract (`quill::
|
|
385
|
+
* contract (`quill::table_column_not_leaf`); whether a leaf fits a cell is the surface's
|
|
322
386
|
* answer (canon `SCHEMAS.md`): a table composes by position, so a row that would stack
|
|
323
387
|
* inside a cell is not one.
|
|
324
388
|
*/
|
|
@@ -362,9 +426,10 @@ export function commitDiscriminant(value, member) {
|
|
|
362
426
|
/**
|
|
363
427
|
* Whether the schema obliges a cell: `default:`'s absence, which is the whole of the
|
|
364
428
|
* obligation (DOCUMENT_MODEL). Exempt are the cells whose absence of one says nothing:
|
|
365
|
-
* a typed dictionary
|
|
366
|
-
*
|
|
367
|
-
* declare none, its unanswered render
|
|
429
|
+
* a typed dictionary, since a namespace declares no `default:` at all and `validate`
|
|
430
|
+
* anchors obligation on the leaves under it; a matrix, whose blank is nothing held, a
|
|
431
|
+
* whole answer; and an optional cell, which can declare none, its unanswered render
|
|
432
|
+
* being `none`.
|
|
368
433
|
*/
|
|
369
434
|
export function obliged(schema) {
|
|
370
435
|
return (schema.type !== 'object' &&
|
|
@@ -431,7 +496,8 @@ export function groupSections(fields, order, labelFor) {
|
|
|
431
496
|
for (const g of order) {
|
|
432
497
|
const fs = fields.filter((f) => f.group === g);
|
|
433
498
|
if (fs.length) {
|
|
434
|
-
|
|
499
|
+
const head = fs.length === 1 && flatObject(fs[0].schema) ? fs[0] : undefined;
|
|
500
|
+
sections.push({ group: g, label: labelFor(g), fields: fs, head });
|
|
435
501
|
emitted.add(g);
|
|
436
502
|
}
|
|
437
503
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@quillmark/svelte",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.17.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.121.0-0",
|
|
70
70
|
"svelte": "^5.0.0"
|
|
71
71
|
},
|
|
72
72
|
"dependencies": {
|