@quillmark/svelte 0.11.0 → 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/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/decode.js +21 -15
- package/dist/core/codec/encode.js +1 -1
- package/dist/core/codec/islands.d.ts +5 -4
- package/dist/core/codec/islands.js +23 -38
- package/dist/core/codec/slash.js +0 -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/visual/ArrayField.svelte +596 -167
- package/dist/visual/ArrayField.svelte.d.ts +14 -1
- package/dist/visual/BooleanField.svelte +10 -7
- package/dist/visual/Card.svelte +3 -22
- package/dist/visual/Field.svelte +69 -29
- package/dist/visual/MatrixField.svelte +352 -0
- package/dist/visual/MatrixField.svelte.d.ts +36 -0
- package/dist/visual/ObjectField.svelte +351 -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/VisualEditorInner.svelte +53 -49
- package/dist/visual/controls.css +31 -0
- package/dist/visual/diagnostics.d.ts +26 -2
- package/dist/visual/diagnostics.js +52 -5
- 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/leaves.d.ts +25 -12
- package/dist/visual/motion.d.ts +14 -5
- package/dist/visual/motion.js +13 -11
- package/dist/visual/strings.d.ts +19 -9
- package/dist/visual/strings.js +6 -2
- package/dist/visual/structure.d.ts +94 -9
- package/dist/visual/structure.js +229 -12
- package/package.json +2 -2
package/dist/core/address.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { Addr, HitGranularity } from '@quillmark/wasm';
|
|
1
|
+
import type { Addr, HitGranularity, PathStep } from '@quillmark/wasm';
|
|
2
2
|
/**
|
|
3
3
|
* A canonical field address: `main.<field>` / `main.body` /
|
|
4
4
|
* `cards.<kind>[<i>].<field>`, cards keyed by absolute document index.
|
|
@@ -63,7 +63,7 @@ export declare function cardPath(index: number, kinds: readonly string[]): DocPa
|
|
|
63
63
|
/**
|
|
64
64
|
* The inverse: a canonical `DocPath` back to the `Addr` the document verbs take, or
|
|
65
65
|
* `undefined` for a path that names no single commit address — a nested or
|
|
66
|
-
* array-element path (`main.keywords[0]`, which {@link
|
|
66
|
+
* array-element path (`main.keywords[0]`, which {@link nestedAddrForFieldPath} and
|
|
67
67
|
* {@link nearestAddrForFieldPath} take instead), a field-rooted one, or a malformed
|
|
68
68
|
* one. A bare card and a `.body` terminal both land on the field-less `{card: i}`
|
|
69
69
|
* the body leaf answers to.
|
|
@@ -86,14 +86,21 @@ export declare function addrForFieldPath(path: DocPath): Addr | undefined;
|
|
|
86
86
|
*/
|
|
87
87
|
export declare function nearestAddrForFieldPath(path: DocPath): Addr | undefined;
|
|
88
88
|
/**
|
|
89
|
-
*
|
|
90
|
-
*
|
|
91
|
-
*
|
|
92
|
-
*
|
|
89
|
+
* A nested address, split: the field's `Addr`, and the steps from it to the leaf.
|
|
90
|
+
* `main.keywords[0]` is `{field: keywords, steps: [0]}`,
|
|
91
|
+
* `main.vectors[0].tours[2].title` is `[0, 'tours', 2, 'title']`, and
|
|
92
|
+
* `main.contact.email` is `['email']`: an index segment reads as a number and a key as
|
|
93
|
+
* a string, the `PathStep` grammar `reader.getContentAt` walks. Anything
|
|
94
|
+
* {@link addrForFieldPath} can name — a field, a body, a card — has no steps and lands
|
|
95
|
+
* here as `undefined`; so does a body with something after it, a body holding no
|
|
96
|
+
* cells. Whether the schema declares each step is the caller's to check: this module
|
|
97
|
+
* holds the grammar and no schema. `regions()`, `positionAt` and `formatDocPath` all
|
|
98
|
+
* spell the index segment bracketed, so there is one spelling to read and none to
|
|
99
|
+
* bridge.
|
|
93
100
|
*/
|
|
94
|
-
export declare function
|
|
95
|
-
/**
|
|
96
|
-
export interface
|
|
101
|
+
export declare function nestedAddrForFieldPath(path: DocPath): NestedAddr | undefined;
|
|
102
|
+
/** A nested address: the field's `Addr`, and the steps from it to the leaf. */
|
|
103
|
+
export interface NestedAddr {
|
|
97
104
|
field: Addr;
|
|
98
|
-
|
|
105
|
+
steps: PathStep[];
|
|
99
106
|
}
|
package/dist/core/address.js
CHANGED
|
@@ -37,7 +37,7 @@ export function cardPath(index, kinds) {
|
|
|
37
37
|
/**
|
|
38
38
|
* The inverse: a canonical `DocPath` back to the `Addr` the document verbs take, or
|
|
39
39
|
* `undefined` for a path that names no single commit address — a nested or
|
|
40
|
-
* array-element path (`main.keywords[0]`, which {@link
|
|
40
|
+
* array-element path (`main.keywords[0]`, which {@link nestedAddrForFieldPath} and
|
|
41
41
|
* {@link nearestAddrForFieldPath} take instead), a field-rooted one, or a malformed
|
|
42
42
|
* one. A bare card and a `.body` terminal both land on the field-less `{card: i}`
|
|
43
43
|
* the body leaf answers to.
|
|
@@ -66,21 +66,35 @@ export function nearestAddrForFieldPath(path) {
|
|
|
66
66
|
return segs && addrForSegs(segs.slice(0, 2));
|
|
67
67
|
}
|
|
68
68
|
/**
|
|
69
|
-
*
|
|
70
|
-
*
|
|
71
|
-
*
|
|
72
|
-
*
|
|
69
|
+
* A nested address, split: the field's `Addr`, and the steps from it to the leaf.
|
|
70
|
+
* `main.keywords[0]` is `{field: keywords, steps: [0]}`,
|
|
71
|
+
* `main.vectors[0].tours[2].title` is `[0, 'tours', 2, 'title']`, and
|
|
72
|
+
* `main.contact.email` is `['email']`: an index segment reads as a number and a key as
|
|
73
|
+
* a string, the `PathStep` grammar `reader.getContentAt` walks. Anything
|
|
74
|
+
* {@link addrForFieldPath} can name — a field, a body, a card — has no steps and lands
|
|
75
|
+
* here as `undefined`; so does a body with something after it, a body holding no
|
|
76
|
+
* cells. Whether the schema declares each step is the caller's to check: this module
|
|
77
|
+
* holds the grammar and no schema. `regions()`, `positionAt` and `formatDocPath` all
|
|
78
|
+
* spell the index segment bracketed, so there is one spelling to read and none to
|
|
79
|
+
* bridge.
|
|
73
80
|
*/
|
|
74
|
-
export function
|
|
81
|
+
export function nestedAddrForFieldPath(path) {
|
|
75
82
|
const segs = segsOf(path);
|
|
76
|
-
if (!segs)
|
|
83
|
+
if (!segs || segs.length < 3)
|
|
77
84
|
return undefined;
|
|
78
|
-
const
|
|
79
|
-
if (
|
|
85
|
+
const field = addrForSegs(segs.slice(0, 2));
|
|
86
|
+
if (field?.field == null)
|
|
80
87
|
return undefined;
|
|
81
|
-
const
|
|
82
|
-
|
|
83
|
-
|
|
88
|
+
const steps = [];
|
|
89
|
+
for (const seg of segs.slice(2)) {
|
|
90
|
+
if (seg.seg === 'index')
|
|
91
|
+
steps.push(seg.index);
|
|
92
|
+
else if (seg.seg === 'field')
|
|
93
|
+
steps.push(seg.name);
|
|
94
|
+
else
|
|
95
|
+
return undefined;
|
|
96
|
+
}
|
|
97
|
+
return { field, steps };
|
|
84
98
|
}
|
|
85
99
|
/** `parseDocPath`, with a malformed path as `undefined` rather than a throw. The gate
|
|
86
100
|
* is read outside the try: an uninitialized core is not a malformed path, and would
|
package/dist/core/bloom.js
CHANGED
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
// fixed by `--_qm-accent-wash`, so interpolating 0→1→0 keeps the scale in CSS where
|
|
7
7
|
// `color-mix` resolves, instead of in keyframe values a script would have to
|
|
8
8
|
// pre-resolve and re-resolve on a theme change.
|
|
9
|
+
import { rungStyle } from './rungs.js';
|
|
9
10
|
const BLOOM_CLASS = 'qm-bloom';
|
|
10
11
|
const WASH = 'var(--_qm-accent-wash)';
|
|
11
12
|
/** The dwell used when the derivation is out of reach (an unstyled root, or jsdom,
|
|
@@ -24,8 +25,8 @@ const FRAMES = [
|
|
|
24
25
|
];
|
|
25
26
|
const FRAMES_REDUCED = [{ opacity: 1 }, { opacity: 1 }];
|
|
26
27
|
/**
|
|
27
|
-
* Run one wash on `el`, resolving the dwell off
|
|
28
|
-
* in CSS.
|
|
28
|
+
* Run one wash on `el`, resolving the dwell off its root so the number is
|
|
29
|
+
* single-sourced in CSS (`core/rungs.ts`).
|
|
29
30
|
*
|
|
30
31
|
* One property, both motion preferences: `--_qm-bloom-dwell` already shortens under
|
|
31
32
|
* `prefers-reduced-motion` in the derivation, so what is left to `matchMedia` here is
|
|
@@ -36,7 +37,7 @@ function bloom(el) {
|
|
|
36
37
|
if (typeof el.animate !== 'function')
|
|
37
38
|
return undefined;
|
|
38
39
|
const reduced = typeof matchMedia === 'function' && matchMedia('(prefers-reduced-motion: reduce)').matches;
|
|
39
|
-
const raw =
|
|
40
|
+
const raw = rungStyle(el).getPropertyValue('--_qm-bloom-dwell').trim();
|
|
40
41
|
const n = Number.parseFloat(raw);
|
|
41
42
|
const duration = !Number.isFinite(n) ? FALLBACK_MS : raw.endsWith('ms') ? n : n * 1000;
|
|
42
43
|
return el.animate(reduced ? FRAMES_REDUCED : FRAMES, { duration });
|
|
@@ -3,12 +3,12 @@
|
|
|
3
3
|
// nodes; a code fence's lines → one `code_block`), nest by shared `containers`
|
|
4
4
|
// prefix (`list_item`/`quote` → lists/blockquote), select the block node by
|
|
5
5
|
// `kind`, apply marks over their `[start,end)` USV ranges (PM splits inline nodes
|
|
6
|
-
// at mark boundaries), and lower island slots to leaf nodes (
|
|
7
|
-
//
|
|
8
|
-
// decorations (field.ts). Positions throughout are USV; `Array.from` iterates by
|
|
6
|
+
// at mark boundaries), and lower island slots to leaf nodes (block where a block
|
|
7
|
+
// island's slot stands alone on its line, inline otherwise). Anchors are not
|
|
8
|
+
// applied here; they are decorations (field.ts). Positions throughout are USV; `Array.from` iterates by
|
|
9
9
|
// code point so an astral char is one unit, never a surrogate half.
|
|
10
10
|
import { DOMSerializer } from 'prosemirror-model';
|
|
11
|
-
import { ISLAND_SLOT } from './islands.js';
|
|
11
|
+
import { ISLAND_SLOT, isBlockIsland } from './islands.js';
|
|
12
12
|
import { descriptorOf, markKey, pmMarkFromContent } from './marks.js';
|
|
13
13
|
import { hasMarks, isInlineSchema, takesLineBreak } from './schema.js';
|
|
14
14
|
/** Code points of `s` (USV units): the iteration granularity the content speaks. */
|
|
@@ -177,10 +177,6 @@ function makeLeaf(schema, leaf, marks, cursor) {
|
|
|
177
177
|
switch (line.kind) {
|
|
178
178
|
case 'rule':
|
|
179
179
|
return schema.nodes.horizontal_rule.create();
|
|
180
|
-
case 'island': {
|
|
181
|
-
const attrs = islandAttrs(cursor);
|
|
182
|
-
return attrs ? schema.nodes.island_block.create(attrs) : schema.nodes.paragraph.create();
|
|
183
|
-
}
|
|
184
180
|
case 'code': {
|
|
185
181
|
// One code_block: the segments' texts joined by literal `\n`, no marks.
|
|
186
182
|
const text = leaf.segments.map((s) => s.text).join('\n');
|
|
@@ -189,6 +185,8 @@ function makeLeaf(schema, leaf, marks, cursor) {
|
|
|
189
185
|
}
|
|
190
186
|
case 'para':
|
|
191
187
|
case 'heading': {
|
|
188
|
+
if (line.kind === 'para' && blockIslandLine(leaf, cursor))
|
|
189
|
+
return schema.nodes.island_block.create(islandAttrs(cursor));
|
|
192
190
|
// Inline content, the segment boundary a `hard_break` in a paragraph and a space
|
|
193
191
|
// in a heading, which takes none (`schema.ts` §`takesLineBreak`). A stored heading
|
|
194
192
|
// carries no continuation — the store clears one after a block of a single line —
|
|
@@ -210,15 +208,23 @@ function makeLeaf(schema, leaf, marks, cursor) {
|
|
|
210
208
|
}
|
|
211
209
|
}
|
|
212
210
|
}
|
|
213
|
-
/**
|
|
214
|
-
*
|
|
215
|
-
*
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
211
|
+
/** Whether a `para` leaf is one block island: a single line holding its slot alone,
|
|
212
|
+
* backed by an island whose markup is a block. The island is the one fact; the line
|
|
213
|
+
* carries no kind of its own for it. */
|
|
214
|
+
function blockIslandLine(leaf, cursor) {
|
|
215
|
+
if (leaf.segments.length !== 1 || leaf.segments[0].text !== ISLAND_SLOT)
|
|
216
|
+
return false;
|
|
217
|
+
const isl = cursor.rt.islands[cursor.i];
|
|
218
|
+
return isl != null && isBlockIsland(isl.type);
|
|
219
|
+
}
|
|
220
|
+
/** Consume the next island entry as PM node attrs (text-order matched to slots): the
|
|
221
|
+
* whole entry, which an island edit has to write back (`islands.ts`). `null` where a
|
|
222
|
+
* slot has no entry behind it, which is a malformed content: the slot draws nothing
|
|
223
|
+
* and its line an empty paragraph, so the decode stays total without minting a node
|
|
224
|
+
* whose `type` the vocabulary would refuse on the way back. */
|
|
219
225
|
function islandAttrs(cursor) {
|
|
220
226
|
const isl = cursor.rt.islands[cursor.i++];
|
|
221
|
-
return isl ? { id: isl.id, islandType: isl.type, props: isl.props
|
|
227
|
+
return isl ? { id: isl.id, islandType: isl.type, props: isl.props } : null;
|
|
222
228
|
}
|
|
223
229
|
/**
|
|
224
230
|
* Inline nodes for one segment: split text into maximal runs of a constant mark
|
|
@@ -104,7 +104,7 @@ function scanBlock(acc, node, nodePos, containers, instance) {
|
|
|
104
104
|
acc.lastContentEndPm = nodePos + 1;
|
|
105
105
|
break;
|
|
106
106
|
case 'island_block':
|
|
107
|
-
beginLine(acc, nodePos, containers, { kind: '
|
|
107
|
+
beginLine(acc, nodePos, containers, { kind: 'para' });
|
|
108
108
|
acc.runs.push({ kind: 'atom', pmStart: nodePos, usvStart: acc.usvEnd });
|
|
109
109
|
appendText(acc, ISLAND_SLOT);
|
|
110
110
|
acc.islands.push(islandEntryFromNode(node.attrs));
|
|
@@ -3,19 +3,20 @@ import { Plugin } from 'prosemirror-state';
|
|
|
3
3
|
import type { ContentIsland, TableProps } from '@quillmark/wasm';
|
|
4
4
|
/** The `U+FFFC` object-replacement char that occupies one island slot in `text`. */
|
|
5
5
|
export declare const ISLAND_SLOT = "\uFFFC";
|
|
6
|
-
/**
|
|
6
|
+
/** Whether an island of `type` is block markup: a `table` stands alone on its line. */
|
|
7
|
+
export declare function isBlockIsland(type: ContentIsland['type']): boolean;
|
|
8
|
+
/** Block island node (a table): a `U+FFFC` slot alone on its `para` line. Atom, unselectable content. */
|
|
7
9
|
export declare const islandBlockSpec: NodeSpec;
|
|
8
10
|
/** Inline island node (an image): a `U+FFFC` slot inside a `para` line. */
|
|
9
11
|
export declare const islandInlineSpec: NodeSpec;
|
|
10
12
|
/** A PM island node's attributes: the content entry, spelled the way a node spec
|
|
11
13
|
* spells it (`islandType`, because `type` on a PM node is the node type). `islandType`
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
+
* reads its set off the entry rather than restating it (DOCUMENT_MODEL §Stability
|
|
15
|
+
* seams). */
|
|
14
16
|
export interface IslandNodeAttrs {
|
|
15
17
|
id: string;
|
|
16
18
|
islandType: ContentIsland['type'];
|
|
17
19
|
props: unknown;
|
|
18
|
-
loss: ContentIsland['loss'];
|
|
19
20
|
}
|
|
20
21
|
/** Build a content island entry from a PM island node's attrs (block or inline). The
|
|
21
22
|
* cast pairs a closed `type` with a `props` that crossed the DOM as opaque JSON. The
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
// Islands: the `U+FFFC` slot + `ContentIsland` entry ↔ a PM leaf node.
|
|
2
|
-
//
|
|
3
|
-
//
|
|
1
|
+
// Islands: the `U+FFFC` slot + `ContentIsland` entry ↔ a PM leaf node. Every slot sits
|
|
2
|
+
// in a `para` line; the island's type says which node it is, a `table` a block alone on
|
|
3
|
+
// its line and an `image` inline. The node carries `id`, `islandType` and `props` verbatim.
|
|
4
4
|
//
|
|
5
5
|
// A props-aware consumer reads the shape straight off the boundary:
|
|
6
6
|
// `ContentIsland.props` is the typed union (`TableProps` for `table`,
|
|
@@ -13,22 +13,17 @@ import { Plugin } from 'prosemirror-state';
|
|
|
13
13
|
import { storableUrl } from './urls.js';
|
|
14
14
|
/** The `U+FFFC` object-replacement char that occupies one island slot in `text`. */
|
|
15
15
|
export const ISLAND_SLOT = '';
|
|
16
|
-
// The
|
|
17
|
-
// stable identity (rebases like an anchor), `islandType` the discriminator, `props`
|
|
18
|
-
// the payload the codec preserves byte-for-byte across a round-trip
|
|
19
|
-
//
|
|
20
|
-
//
|
|
21
|
-
// `loss` is authored, not derived: `applyChange` stores the class an island op gives
|
|
22
|
-
// it and re-derives nothing, so an entry that did not carry its own would promote a
|
|
23
|
-
// degraded table to lossless on the first cell edit. Its default is the weakest
|
|
24
|
-
// claim; decode always supplies the stored value.
|
|
16
|
+
// The three island node attributes carry the content entry verbatim: `id` is the
|
|
17
|
+
// stable identity (rebases like an anchor), `islandType` the discriminator, and `props`
|
|
18
|
+
// the payload the codec preserves byte-for-byte across a round-trip. `islandType` and
|
|
19
|
+
// `props` declare no default: a node standing for an island holds one, so the pair is
|
|
20
|
+
// supplied at every `create`.
|
|
25
21
|
const islandAttrs = {
|
|
26
22
|
id: { default: '' },
|
|
27
23
|
islandType: {},
|
|
28
|
-
props: {}
|
|
29
|
-
loss: { default: 'unrepresentable' }
|
|
24
|
+
props: {}
|
|
30
25
|
};
|
|
31
|
-
// All
|
|
26
|
+
// All three attributes cross the DOM: a copy and a paste inside one body run the document
|
|
32
27
|
// through `toDOM`/`parseDOM` (CODEC §"Markdown at the edges"), and an island is a leaf
|
|
33
28
|
// whose tags alone say only `[table]`. `props` crosses as JSON, the form the payload has
|
|
34
29
|
// at the boundary. The names are `data-qm-*` like every other carrier's, and the id is
|
|
@@ -39,21 +34,18 @@ function islandDOM(node) {
|
|
|
39
34
|
return {
|
|
40
35
|
'data-qm-island': node.attrs.islandType,
|
|
41
36
|
'data-qm-island-id': node.attrs.id,
|
|
42
|
-
'data-qm-island-loss': node.attrs.loss,
|
|
43
37
|
'data-qm-island-props': JSON.stringify(node.attrs.props ?? null)
|
|
44
38
|
};
|
|
45
39
|
}
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
const
|
|
51
|
-
/**
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
unrepresentable: true
|
|
56
|
-
};
|
|
40
|
+
/** The island types the content model names, each with whether its markup is a block,
|
|
41
|
+
* which decides the node its slot decodes to. A `Record` over the boundary's own union,
|
|
42
|
+
* so a member added upstream is a missing key here rather than a silent refusal at the
|
|
43
|
+
* one door with no decode behind it. */
|
|
44
|
+
const BLOCK_ISLAND = { table: true, image: false };
|
|
45
|
+
/** Whether an island of `type` is block markup: a `table` stands alone on its line. */
|
|
46
|
+
export function isBlockIsland(type) {
|
|
47
|
+
return BLOCK_ISLAND[type];
|
|
48
|
+
}
|
|
57
49
|
/** An island node's attributes off that DOM, or `false` where the id is absent or the
|
|
58
50
|
* type is one the content vocabulary does not name — a name no read produces and no
|
|
59
51
|
* write takes, so the element's text arrives as text. A `props` that is not the JSON
|
|
@@ -64,7 +56,7 @@ function islandAttrsFromDOM(el) {
|
|
|
64
56
|
const id = el.getAttribute('data-qm-island-id');
|
|
65
57
|
// Asserted off an untrusted attribute and checked against the set on the next line.
|
|
66
58
|
const type = el.getAttribute('data-qm-island');
|
|
67
|
-
if (id == null || type == null || !
|
|
59
|
+
if (id == null || type == null || !Object.hasOwn(BLOCK_ISLAND, type))
|
|
68
60
|
return false;
|
|
69
61
|
const raw = el.getAttribute('data-qm-island-props');
|
|
70
62
|
let props = null;
|
|
@@ -74,13 +66,7 @@ function islandAttrsFromDOM(el) {
|
|
|
74
66
|
catch {
|
|
75
67
|
props = null;
|
|
76
68
|
}
|
|
77
|
-
return { id, islandType: type, props: storableProps(type, props)
|
|
78
|
-
}
|
|
79
|
-
/** The `loss` an element claims, or the weakest class for a claim outside the set:
|
|
80
|
-
* an under-claim keeps a degraded island from re-crossing as a lossless one. */
|
|
81
|
-
function lossFromDOM(el) {
|
|
82
|
-
const raw = el.getAttribute('data-qm-island-loss');
|
|
83
|
-
return raw != null && LOSS_CLASSES[raw] ? raw : 'unrepresentable';
|
|
69
|
+
return { id, islandType: type, props: storableProps(type, props) };
|
|
84
70
|
}
|
|
85
71
|
/** `props` with the one key the authored lane refuses made storable: an `image`'s `url`
|
|
86
72
|
* carrying a line ending, which `IslandOp` and `overwrite` throw on. The rest crosses
|
|
@@ -91,7 +77,7 @@ function storableProps(type, props) {
|
|
|
91
77
|
const url = props.url;
|
|
92
78
|
return typeof url === 'string' ? { ...props, url: storableUrl(url) } : props;
|
|
93
79
|
}
|
|
94
|
-
/** Block island node (a table):
|
|
80
|
+
/** Block island node (a table): a `U+FFFC` slot alone on its `para` line. Atom, unselectable content. */
|
|
95
81
|
export const islandBlockSpec = {
|
|
96
82
|
group: 'block',
|
|
97
83
|
atom: true,
|
|
@@ -119,8 +105,7 @@ export function islandEntryFromNode(attrs) {
|
|
|
119
105
|
return {
|
|
120
106
|
id: attrs.id,
|
|
121
107
|
type: attrs.islandType,
|
|
122
|
-
props: attrs.props
|
|
123
|
-
loss: attrs.loss
|
|
108
|
+
props: attrs.props
|
|
124
109
|
};
|
|
125
110
|
}
|
|
126
111
|
/** The `TableProps` shape, over a value that reached the node off the DOM rather than
|
package/dist/core/codec/slash.js
CHANGED
|
Binary file
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/** The element the scale is declared on, for `node`: its root, or `node` where it stands
|
|
2
|
+
* outside one (a detached surface, a consumer's own mount). */
|
|
3
|
+
export declare function rungStyle(node: Element): CSSStyleDeclaration;
|
|
4
|
+
/** A duration rung off `style`, in ms; `undefined` where the derivation is out of reach
|
|
5
|
+
* (an unstyled root, or jsdom, where `getComputedStyle` reports custom properties as
|
|
6
|
+
* empty). No rung, no motion: a fallback here would be the scale restated in the one
|
|
7
|
+
* place `check:style` cannot read. */
|
|
8
|
+
export declare function rungMs(style: CSSStyleDeclaration, rung: string): number | undefined;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
// Where a script reads the scale. The derivation declares every `--_qm-*` rung on
|
|
2
|
+
// `[data-qm-root]` and nowhere else (`core/theme.css`), so a descendant's value is that
|
|
3
|
+
// one inherited and the root is the element the question is about: asking a leaf three
|
|
4
|
+
// containers deep is asking an arbitrary element what the surface is set to.
|
|
5
|
+
//
|
|
6
|
+
// The read is also the cost. `getComputedStyle` compounds with nesting depth under jsdom,
|
|
7
|
+
// so a suite that resolves rungs off landed elements pays the depth of whatever it landed
|
|
8
|
+
// on; resolving them off the root pays the root's.
|
|
9
|
+
/** The element the scale is declared on, for `node`: its root, or `node` where it stands
|
|
10
|
+
* outside one (a detached surface, a consumer's own mount). */
|
|
11
|
+
export function rungStyle(node) {
|
|
12
|
+
return getComputedStyle(node.closest('[data-qm-root]') ?? node);
|
|
13
|
+
}
|
|
14
|
+
/** A duration rung off `style`, in ms; `undefined` where the derivation is out of reach
|
|
15
|
+
* (an unstyled root, or jsdom, where `getComputedStyle` reports custom properties as
|
|
16
|
+
* empty). No rung, no motion: a fallback here would be the scale restated in the one
|
|
17
|
+
* place `check:style` cannot read. */
|
|
18
|
+
export function rungMs(style, rung) {
|
|
19
|
+
const raw = style.getPropertyValue(rung).trim();
|
|
20
|
+
const n = Number.parseFloat(raw);
|
|
21
|
+
if (!Number.isFinite(n))
|
|
22
|
+
return undefined;
|
|
23
|
+
return raw.endsWith('ms') ? n : n * 1000;
|
|
24
|
+
}
|
package/dist/core/theme.css
CHANGED
|
@@ -336,6 +336,16 @@
|
|
|
336
336
|
through the icon's `size`, so a stylesheet beats the SVG presentation attribute
|
|
337
337
|
and one rung is the whole of what a control draws. */
|
|
338
338
|
--_qm-glyph-control: 14px;
|
|
339
|
+
/* The narrowest track a control that fills its width stays usable in: a table's
|
|
340
|
+
column floors at it, rises no further than its content, and a row of them scrolls
|
|
341
|
+
sideways rather than crushing a cell. A column drawing a mark takes no floor — a
|
|
342
|
+
switch is one width at every width, so there is no width for it to be usable in
|
|
343
|
+
(`ArrayField`). What the field has spare stands after the last column, an inner
|
|
344
|
+
grid carrying its own rhythm rather than the card's. Under the field minimum the
|
|
345
|
+
capacity ladder steps at (`.qm-tracks`), since a cell in a row has the header to
|
|
346
|
+
name it and no label of its own to fit. A threshold rather than a rhythm choice,
|
|
347
|
+
so it does not move with `--qm-space`. */
|
|
348
|
+
--_qm-track-min: 7rem;
|
|
339
349
|
|
|
340
350
|
/* ── Type ────────────────────────────────────────────────────────────────
|
|
341
351
|
`--qm-font-size` anchors the chrome rung; 1.125 is the ratio between adjacent
|