@quillmark/svelte 0.26.2 → 0.28.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/NOTICE +4 -4
- package/README.md +26 -8
- package/THEMING.md +40 -4
- package/dist/core/address.d.ts +35 -64
- package/dist/core/address.js +35 -55
- package/dist/core/bloom.d.ts +4 -10
- package/dist/core/bloom.js +14 -27
- package/dist/core/codec/blocks.d.ts +41 -43
- package/dist/core/codec/blocks.js +97 -130
- package/dist/core/codec/code.d.ts +7 -10
- package/dist/core/codec/code.js +37 -46
- package/dist/core/codec/decode.d.ts +19 -23
- package/dist/core/codec/decode.js +76 -111
- package/dist/core/codec/encode.d.ts +11 -15
- package/dist/core/codec/encode.js +60 -67
- package/dist/core/codec/field.d.ts +62 -95
- package/dist/core/codec/field.js +107 -179
- package/dist/core/codec/islands.d.ts +33 -41
- package/dist/core/codec/islands.js +76 -86
- package/dist/core/codec/lists.d.ts +17 -26
- package/dist/core/codec/lists.js +117 -157
- package/dist/core/codec/marks.d.ts +4 -3
- package/dist/core/codec/marks.js +8 -3
- package/dist/core/codec/prose.css +84 -46
- package/dist/core/codec/schema.d.ts +18 -23
- package/dist/core/codec/schema.js +84 -76
- package/dist/core/codec/slash.d.ts +42 -48
- package/dist/core/codec/slash.js +0 -0
- package/dist/core/codec/table-copy.d.ts +4 -3
- package/dist/core/codec/table-copy.js +25 -7
- package/dist/core/codec/table-view.d.ts +12 -12
- package/dist/core/codec/table-view.js +460 -383
- package/dist/core/codec/table.d.ts +94 -61
- package/dist/core/codec/table.js +331 -83
- package/dist/core/codec/tasks.d.ts +6 -0
- package/dist/core/codec/tasks.js +30 -3
- package/dist/core/errors.d.ts +15 -30
- package/dist/core/errors.js +2 -6
- package/dist/core/index.js +7 -12
- package/dist/core/lifecycle.d.ts +7 -16
- package/dist/core/lifecycle.js +10 -21
- package/dist/core/rebind.svelte.d.ts +10 -20
- package/dist/core/rebind.svelte.js +15 -27
- package/dist/core/rungs.d.ts +14 -11
- package/dist/core/rungs.js +17 -19
- package/dist/core/teardown.d.ts +4 -6
- package/dist/core/teardown.js +7 -10
- package/dist/core/theme.css +74 -54
- package/dist/index.js +3 -8
- package/dist/preset/app.css +71 -0
- package/dist/preset/index.css +8 -7
- package/dist/preset/recipes.css +19 -164
- package/dist/preset/scale.css +28 -18
- package/dist/preset/shell.css +157 -0
- package/dist/preview/Preview.svelte +8 -0
- package/dist/preview/bridge.d.ts +18 -22
- package/dist/preview/bridge.js +29 -58
- package/dist/preview/controller.d.ts +21 -29
- package/dist/preview/controller.js +14 -39
- package/dist/preview/geometry.d.ts +13 -34
- package/dist/preview/geometry.js +13 -34
- package/dist/preview/index.js +4 -16
- package/dist/preview/paint.d.ts +3 -3
- package/dist/preview/paint.js +28 -68
- package/dist/preview/strings.d.ts +1 -1
- package/dist/preview/strings.js +2 -14
- package/dist/preview/viewport.js +20 -15
- package/dist/preview/zoom.d.ts +21 -22
- package/dist/preview/zoom.js +53 -30
- package/dist/visual/ArrayField.svelte +77 -16
- package/dist/visual/BooleanField.svelte +28 -15
- package/dist/visual/Card.svelte +66 -51
- package/dist/visual/CardControls.svelte +5 -11
- package/dist/visual/DateField.svelte +20 -4
- package/dist/visual/EnumField.svelte +3 -0
- package/dist/visual/FieldHint.svelte +2 -1
- package/dist/visual/FormatPopover.svelte +82 -15
- package/dist/visual/FormatPopover.svelte.d.ts +4 -0
- package/dist/visual/MatrixAdd.svelte +30 -12
- package/dist/visual/MatrixField.svelte +24 -16
- package/dist/visual/NumberField.svelte +7 -0
- package/dist/visual/ObjectField.svelte +4 -4
- package/dist/visual/ProseField.svelte +0 -5
- package/dist/visual/TipsCard.svelte +11 -10
- package/dist/visual/VariantField.svelte +3 -1
- package/dist/visual/VisualEditorInner.svelte +19 -3
- package/dist/visual/controls.css +7 -2
- package/dist/visual/fonts.d.ts +18 -0
- package/dist/visual/fonts.js +59 -0
- package/dist/visual/icons/nodes.d.ts +0 -5
- package/dist/visual/icons/nodes.js +0 -4
- package/dist/visual/props.d.ts +14 -1
- package/dist/visual/strings.d.ts +7 -0
- package/dist/visual/strings.js +5 -2
- package/dist/visual/structure.d.ts +43 -87
- package/dist/visual/structure.js +42 -83
- package/package.json +2 -2
package/NOTICE
CHANGED
|
@@ -6,9 +6,9 @@ Copyright 2026 Nibs
|
|
|
6
6
|
This package contains SVG glyph geometry derived from Lucide, carried in source
|
|
7
7
|
rather than installed.
|
|
8
8
|
|
|
9
|
-
`visual/icons/nodes.ts` holds
|
|
9
|
+
`visual/icons/nodes.ts` holds seventeen verbatim from @lucide/svelte 1.28.0:
|
|
10
10
|
chevron-right, arrow-right, chevron-down, chevron-up, bold, italic, underline,
|
|
11
|
-
strikethrough, code, link, info, plus,
|
|
11
|
+
strikethrough, code, link, info, plus, trash-2, minus, check, calendar and
|
|
12
12
|
calendar-check.
|
|
13
13
|
|
|
14
14
|
`core/codec/table-view.ts` holds grip-horizontal, grip-vertical, align-left,
|
|
@@ -33,8 +33,8 @@ OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
|
|
33
33
|
|
|
34
34
|
--------------------------------------------------------------------------------
|
|
35
35
|
|
|
36
|
-
|
|
37
|
-
code, info, italic, link, minus, plus
|
|
36
|
+
Eleven of those glyphs — arrow-right, chevron-down, chevron-right, chevron-up,
|
|
37
|
+
code, info, italic, link, minus, plus and trash-2 — are drawn exactly as
|
|
38
38
|
the Feather project draws them and carry its license as well.
|
|
39
39
|
|
|
40
40
|
The MIT License (MIT)
|
package/README.md
CHANGED
|
@@ -29,12 +29,12 @@ The **consumer** owns the session and the handles, and drives every edit; the su
|
|
|
29
29
|
import { Engine } from '@quillmark/wasm';
|
|
30
30
|
import { init } from '@quillmark/svelte/core';
|
|
31
31
|
|
|
32
|
-
// Quill and Document are exported only through init()
|
|
32
|
+
// Quill and Document are exported only through init().
|
|
33
33
|
const { Quill, Document } = await init();
|
|
34
34
|
|
|
35
35
|
const quill = Quill.fromTree(tree); // tree: Map<string, Uint8Array> of the quill dir
|
|
36
36
|
|
|
37
|
-
// A NEW document: seeded from the quill's
|
|
37
|
+
// A NEW document: seeded from the quill's schema.
|
|
38
38
|
const doc = quill.seedDocument();
|
|
39
39
|
|
|
40
40
|
// An EXISTING document: parsed back from what you stored.
|
|
@@ -120,7 +120,7 @@ In Svelte, `<Preview {session} onPick={…} />` exposes the same verbs (`refresh
|
|
|
120
120
|
/>
|
|
121
121
|
```
|
|
122
122
|
|
|
123
|
-
Hand it a different `doc` and the editor **re-keys itself**: every leaf remounts against the new handle, and the id state, the commit-error map and the active address seed fresh.
|
|
123
|
+
Hand it a different `doc` and the editor **re-keys itself**: every leaf remounts against the new handle, and the id state, the commit-error map and the active address seed fresh. `quill` is not part of that key — the schema is re-read on every derive, so a quill swap re-projects on its own; swapping it _without_ the doc reports `rebind-ignored`.
|
|
124
124
|
|
|
125
125
|
### Driving it from outside
|
|
126
126
|
|
|
@@ -165,6 +165,26 @@ The package ships English and a seam to replace it. `strings` is keyed and **par
|
|
|
165
165
|
|
|
166
166
|
`formatDiagnostic` gets the whole `Diagnostic` and returns `undefined` to take its own message. Validation and edit diagnostics re-word from `code`, `path` and `args`; parse errors and render warnings carry their detail inside English text and stand as they are ([VISUAL_EDITOR.md](prose/canon/VISUAL_EDITOR.md)).
|
|
167
167
|
|
|
168
|
+
### Fonts
|
|
169
|
+
|
|
170
|
+
A run of text can be set in a font family the **host** supplies, spelled `<qm-span family="…">` in markdown. Build the engine with the font files (`.ttf`, `.otf`, `.ttc`, `.otc`) and hand the editor the faces it lists: the format popover then offers each family, and the editor registers the faces with the page so a run draws in the face the preview sets it in.
|
|
171
|
+
|
|
172
|
+
```ts
|
|
173
|
+
const carlito = new Uint8Array(await (await fetch('/fonts/Carlito-Regular.ttf')).arrayBuffer());
|
|
174
|
+
const engine = new Engine({ fonts: [carlito] });
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
```svelte
|
|
178
|
+
<script lang="ts">
|
|
179
|
+
let fonts = $state<FontFace[]>([]);
|
|
180
|
+
engine.fontFaces(quill).then((faces) => (fonts = faces));
|
|
181
|
+
</script>
|
|
182
|
+
|
|
183
|
+
<VisualEditor {doc} {quill} {fonts} />
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
[`@quillmark/fonts`](../fonts) is the library every Quillmark client builds its engine over, so a family a document names sets the same way in each; a host's own files go after it. `fontFaces` loads the render build, as `open` does, so mount the editor first and let the faces arrive behind it. A family the document already sets edits with no library at all, and the picker offers it so it can be cleared.
|
|
187
|
+
|
|
168
188
|
### Recompiling
|
|
169
189
|
|
|
170
190
|
`onChange` is the signal to recompile, and it covers **all three lanes**: a prose keystroke, a scalar write, a card operation. `onCaretMove` is a _selection_ signal, not a change signal: it fires on a bare arrow key, so a recompile hung off it recompiles on every one.
|
|
@@ -193,7 +213,7 @@ function recompileNow() {
|
|
|
193
213
|
|
|
194
214
|
`session.warnings` is a **getter on a handle Svelte does not track**: pull it per apply, or the editor shows the previous compile's errors.
|
|
195
215
|
|
|
196
|
-
|
|
216
|
+
The schedule is host policy, so the package ships no scheduler. Obligations: an edit of your own (an import, an undo, a direct `doc` write) recompiles by the same calls, since nothing polls the document; a compile re-reads the diagnostics; and teardown clears the timer **before** freeing the handles.
|
|
197
217
|
|
|
198
218
|
## The caret bridge
|
|
199
219
|
|
|
@@ -217,7 +237,7 @@ A consumer holding an `Addr` of its own maps it with `fieldPathForAddr` (from `@
|
|
|
217
237
|
|
|
218
238
|
## Errors
|
|
219
239
|
|
|
220
|
-
Every surface takes `onError`. It reports failures the surface **recovered from**: a commit the boundary refused, a card operation that threw, a page paint that failed, a serialize that threw. None of them stop editing. Wire nothing and each lands in the console
|
|
240
|
+
Every surface takes `onError`. It reports failures the surface **recovered from**: a commit the boundary refused, a card operation that threw, a page paint that failed, a serialize that threw. None of them stop editing. Wire nothing and each lands in the console.
|
|
221
241
|
|
|
222
242
|
```ts
|
|
223
243
|
onError: (err) => {
|
|
@@ -238,8 +258,6 @@ This is not the diagnostics channel. A `Diagnostic` is about the **document** an
|
|
|
238
258
|
el.textContent = doc.toMarkdown();
|
|
239
259
|
```
|
|
240
260
|
|
|
241
|
-
A worked one holds the scroll across the swap and shows a throw in place; the call under it is still the one line above.
|
|
242
|
-
|
|
243
261
|
## Theming
|
|
244
262
|
|
|
245
263
|
The surfaces carry the behavior against a neutral, overridable visual baseline: a set of `--qm-*` CSS custom properties you override on any ancestor. See [`THEMING.md`](THEMING.md).
|
|
@@ -250,7 +268,7 @@ This package is one workspace of [`quillmark-js`](../..); the gates are the root
|
|
|
250
268
|
|
|
251
269
|
```sh
|
|
252
270
|
npm run build -w packages/svelte # svelte-package → dist/
|
|
253
|
-
npm run test -w packages/svelte # Vitest
|
|
271
|
+
npm run test -w packages/svelte # Vitest
|
|
254
272
|
npm run check -w packages/svelte # svelte-check
|
|
255
273
|
npm run dev # the playground, from the root
|
|
256
274
|
```
|
package/THEMING.md
CHANGED
|
@@ -18,7 +18,7 @@ There are three depths to that. An app mounting into an unstyled page stops at t
|
|
|
18
18
|
|
|
19
19
|
Nothing to import and nothing to set. The package pulls its own stylesheet, and each surface owns its whole **column**, not only the cards in it: the gutter the stack sits in, the tone behind it, and (for the preview) the desk the painted sheet floats on. A bare `<div>` is a mounting site.
|
|
20
20
|
|
|
21
|
-
**Mounting into a fixed-height pane**: pass `class="qm-pane"`. The surface becomes its own scroll container and takes a scroll tail, so the last card can be read at the middle of your pane rather than against its bottom edge. Without it the editor grows to its content and your page scrolls, which is what you want when the editor _is_ the page. The preview always scrolls: a viewport onto a document has nothing else to be.
|
|
21
|
+
**Mounting into a fixed-height pane**: pass `class="qm-pane"`. The surface becomes its own scroll container and takes a scroll tail, so the last card can be read at the middle of your pane rather than against its bottom edge. The gutter around the cards narrows, since your frame already stands the stack off your page. Without it the editor grows to its content and your page scrolls, which is what you want when the editor _is_ the page. The preview always scrolls: a viewport onto a document has nothing else to be.
|
|
22
22
|
|
|
23
23
|
`.qm-pane` is opt-in on the editor rather than the default because the editor is what its own popovers portal into, and a scroll container clips them. In a pane you would have had that clipping from your own scrolling frame anyway; in a page you should not have it at all.
|
|
24
24
|
|
|
@@ -127,7 +127,7 @@ import '@quillmark/svelte/preset';
|
|
|
127
127
|
</body>
|
|
128
128
|
```
|
|
129
129
|
|
|
130
|
-
This is the stylesheet our own apps import: the playground and
|
|
130
|
+
This is the stylesheet our own apps import: the playground, studio and quillkit's app client draw with these rules and nothing else, so what you get is what we ship, not a reduction of it.
|
|
131
131
|
|
|
132
132
|
It carries a `--qmh-*` scale for the page, derived from the same nine dials and calibrated against the surfaces' own steps, so a plate of yours and a card of ours agree about which way "raised" goes. Turning a dial moves both.
|
|
133
133
|
|
|
@@ -173,13 +173,15 @@ An app that mounts an editor beside a preview over one session needs a screen to
|
|
|
173
173
|
| `.qm-bar` | The row a band puts its parts on, wrapping and centred. The rule under it, the gutter beside it and its depth stay yours: that is your page shell. |
|
|
174
174
|
| `.qm-split` | Two even tracks for two surfaces, the page gap between them. Under `50rem` wide or `30rem` tall, one track at a time; `data-qm-show="1"\|"2"` says which. |
|
|
175
175
|
| `.qm-switch` | The band that says which track is showing, and nothing at the widths where both are. Its controls read as even halves and stand at the touch floor (`--qmh-tap`, 44px); depth past that is yours. |
|
|
176
|
-
| `.qm-frame` | The mounting site: an edge, a corner, and the clip that holds a surface to them. What is inside it is the surface's own.
|
|
176
|
+
| `.qm-frame` | The mounting site: an edge, a corner, and the clip that holds a surface to them. Under the split's threshold it keeps only its top and bottom rules. What is inside it is the surface's own. |
|
|
177
177
|
|
|
178
178
|
The narrow shape is one mount at a time, not two of them stacked: half a narrow viewport is under the width either surface reads at. The track you are not showing is `display: none`, so it leaves the tab order, hit-testing and the accessibility tree together: you need no `inert` on it, and no breakpoint in your JS, since the only state you hold is which of the two you last asked for. Keep both mounted: the hidden pane keeps its pixels, its caret and its scroll, and comes back to them — it rasters nothing at a box it has none of, and repaints only where it returns to a box that draws its pages at another scale. A preview scroll asked for while it was hidden is held and run on the way back, so it opens at the caret rather than several edits behind it. A click that lands in the hidden pane is the one thing the shape adds to your wiring, so send the caret and reveal the track together, or the hit goes somewhere the reader cannot see.
|
|
179
179
|
|
|
180
|
+
Every rule behind the five is wrapped in `:where()`, so it computes at specificity zero: a rule of yours on the same element wins at any specificity and in any sheet order, which is what lets a stylesheet written by someone other than the app's author (an `app.css` over quillkit's app client) restyle a band without knowing which shell rules set it.
|
|
181
|
+
|
|
180
182
|
The shape is shared; the look of a band is not. Your wordmark, your nav and the depth of your head are the most app-specific things on the screen, and a preset that drew them would hand every tool built on this the same face, so they read `--qmh-*` like everything else and you write the rules. `.qm-split`'s gap is a default rather than a fact about splitting: close it to `0` and carry a hairline on one pane if you want the two mounts to meet instead of stand apart.
|
|
181
183
|
|
|
182
|
-
It is **unlayered
|
|
184
|
+
It is **unlayered**, and nothing in it outranks a rule of yours aimed at the same element: the page, type, status and control recipes compute at ordinary specificity, so your own rules beat them by ordinary precedence, and the shell and the app chrome compute at specificity zero, as the surfaces do. It is also opt-in and side-effect free until imported: nothing that styles your document arrives with a component.
|
|
183
185
|
|
|
184
186
|
One rule in it earns its place regardless of whether you take the rest, and you want it if you draw a focus ring of your own — cut the mounted surfaces out of the blanket:
|
|
185
187
|
|
|
@@ -191,6 +193,38 @@ One rule in it earns its place regardless of whether you take the rest, and you
|
|
|
191
193
|
|
|
192
194
|
A blanket `:focus-visible` of yours outranks ours, including on the controls that already draw one, so a ring written for the controls that draw none repaints the ones that do. The cut-out leaves yours everywhere else, and `:not(:where(…))` contributes no specificity, so your rule ranks exactly as it would without it.
|
|
193
195
|
|
|
196
|
+
### The app chrome
|
|
197
|
+
|
|
198
|
+
A tool's bands are yours, since a tool's head is the most app-specific thing on its screen. An app whose whole page is one form to fill in has less to say there: a name, the action the page leads to, and a line signing it. Four classes draw that much, quillkit's app client wears them, and a host of its own takes them the same way:
|
|
199
|
+
|
|
200
|
+
| Class | What it is |
|
|
201
|
+
| ----------------- | --------------------------------------------------------------------------------------------------------------- |
|
|
202
|
+
| `.qm-app-header` | The band naming the app: a plate, a gutter and a hairline under it. Put it beside `.qm-bar`. |
|
|
203
|
+
| `.qm-app-title` | What the app is called, at the title rung: the one line on the screen read as a name. |
|
|
204
|
+
| `.qm-app-footer` | The line signing the app, in meta type on the head's plate under a hairline; its links take the line's own ink. |
|
|
205
|
+
| `.qm-app-primary` | The one action the page leads to: the control wearing `--qm-accent`, standing at the touch floor. |
|
|
206
|
+
|
|
207
|
+
They compute at specificity zero, as the shell does, so `.qm-app-header { background: #00308f; color: #fff; }` is the whole of rebranding the head.
|
|
208
|
+
|
|
209
|
+
## What a stylesheet may aim at
|
|
210
|
+
|
|
211
|
+
A stylesheet you write against a mounted surface, or one an author writes over an app someone else built, may aim at these and nothing else:
|
|
212
|
+
|
|
213
|
+
- the nine dials (§"The dials");
|
|
214
|
+
- the surface roots, `.qm-editor` and `.qm-preview`, and `.qm-pane`;
|
|
215
|
+
- `data-qm-card="<kind>"`, on the box of every card the editor draws, its value the card's kind and `main` for the main card;
|
|
216
|
+
- the preset's classes, on your own DOM, if you import it (§"Match ours").
|
|
217
|
+
|
|
218
|
+
`data-qm-card` is the one hook named by the schema rather than by the package: a rule aimed at a kind lands on that kind's cards wherever the editor puts them, which is the one thing a quill-specific restyle needs and a dial cannot say.
|
|
219
|
+
|
|
220
|
+
```css
|
|
221
|
+
[data-qm-card='indorsement'] {
|
|
222
|
+
border-left: 3px solid var(--qm-accent);
|
|
223
|
+
}
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
Nothing names a field or a property. Every other `data-qm-*` attribute the surfaces carry, `data-qm-prop` among them, is something the surface queries itself, and moves when it does.
|
|
227
|
+
|
|
194
228
|
## What is deliberately not public
|
|
195
229
|
|
|
196
230
|
The two root classes and `.qm-pane` are the whole class contract on the surfaces. Every other `qm-*` class inside them is internal and renames without notice: a class contract over the interior would freeze the DOM shape the surfaces are free to re-cut, and the dials already reach the values a restyle wants. The preset's classes are a contract of their own: they land on _your_ DOM, so they freeze nothing of ours.
|
|
@@ -200,3 +234,5 @@ The derived surface scale (surface / border / ink rungs, the blur radius, the po
|
|
|
200
234
|
## Requirements
|
|
201
235
|
|
|
202
236
|
`:where()`, `color-mix()`, `light-dark()` and relative colour syntax (`oklab(from …)`): Baseline since 2021, 2023, 2024 and 2024 respectively — the last is what lets a plane step by the card's own lightness. `@property` is the one feature the surface does not require: where it is missing, an invalid length dial collapses the surface rather than falling back.
|
|
237
|
+
|
|
238
|
+
Build your stylesheets for browsers that carry `light-dark()` natively. A minifier targeting older ones lowers each pole to a pair of variables only an element declaring `color-scheme` defines, and the preset states its poles at `:root`, where that pair is two colours at once and no colour: the primary press and the error hue draw nothing. Under Vite, `build.cssTarget: ['chrome123', 'edge123', 'firefox128', 'safari18']`.
|
package/dist/core/address.d.ts
CHANGED
|
@@ -1,22 +1,16 @@
|
|
|
1
1
|
import type { Addr, HitGranularity, PathStep } from '@quillmark/wasm';
|
|
2
2
|
/**
|
|
3
|
-
* A canonical field address: `main.<field>` / `main.body` /
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
* `Diagnostic.path`, `ContentHit.field`, `FieldRegion.field` and
|
|
8
|
-
* `session.regions()` keys already use. An alias over `string`: the boundary
|
|
9
|
-
* exports the parser and the segment type, not a nominal type, so this names the
|
|
10
|
-
* grammar at a signature rather than inventing a checked one.
|
|
3
|
+
* A canonical field address: `main.<field>` / `main.body` / `cards.<kind>[<i>].<field>`, cards
|
|
4
|
+
* keyed by absolute document index. The grammar `parseDocPath` / `formatDocPath` speak, and the one
|
|
5
|
+
* `Diagnostic.path`, `ContentHit.field`, `FieldRegion.field` and `session.regions()` keys use. An
|
|
6
|
+
* alias over `string`: the boundary exports no nominal type.
|
|
11
7
|
*/
|
|
12
8
|
export type DocPath = string;
|
|
13
9
|
/**
|
|
14
|
-
* A place in the document: a field and a caret within it. What the editor reports
|
|
15
|
-
*
|
|
16
|
-
* is `
|
|
17
|
-
*
|
|
18
|
-
* `pos` is USV, the shared content coordinate on both sides of the boundary: a
|
|
19
|
-
* `ContentHit` is a `Place` with its own extras, and fits wherever one is taken.
|
|
10
|
+
* A place in the document: a field and a caret within it. What the editor reports when its caret
|
|
11
|
+
* moves and what the preview scrolls to, so `onCaretMove={preview.focusPosition}` translates
|
|
12
|
+
* nothing. `pos` is USV, the coordinate shared on both sides of the boundary; a `ContentHit` is a
|
|
13
|
+
* `Place` with extras.
|
|
20
14
|
*/
|
|
21
15
|
export interface Place {
|
|
22
16
|
field: DocPath;
|
|
@@ -24,13 +18,10 @@ export interface Place {
|
|
|
24
18
|
pos: number;
|
|
25
19
|
}
|
|
26
20
|
/**
|
|
27
|
-
* Where a preview click landed
|
|
28
|
-
* `
|
|
29
|
-
*
|
|
30
|
-
*
|
|
31
|
-
* a field the plate places without tracking its content (`fieldAt` answers,
|
|
32
|
-
* `positionAt` does not), so there is no offset and the landing is a focus. A `Place`
|
|
33
|
-
* is a landing carrying its caret, and a `ContentHit` is one too.
|
|
21
|
+
* Where a preview click landed: what the preview surfaces and the editor's `setCaret` takes. **An
|
|
22
|
+
* absent `pos` is the placement rung**, not a caret at zero: the click resolved a field the plate
|
|
23
|
+
* places without tracking its content (`fieldAt` answers, `positionAt` does not), so the landing is
|
|
24
|
+
* a focus.
|
|
34
25
|
*/
|
|
35
26
|
export interface Landing {
|
|
36
27
|
field: DocPath;
|
|
@@ -41,67 +32,47 @@ export interface Landing {
|
|
|
41
32
|
granularity?: HitGranularity;
|
|
42
33
|
}
|
|
43
34
|
/**
|
|
44
|
-
* A field's canonical `DocPath`, or `undefined` when `addr.card` is outside the live
|
|
45
|
-
*
|
|
46
|
-
* addresses its body, which is the leaf `{card: i}` names.
|
|
35
|
+
* A field's canonical `DocPath`, or `undefined` when `addr.card` is outside the live `kinds` array
|
|
36
|
+
* (a stale address: drop it rather than mis-target). A field-less card addresses its body.
|
|
47
37
|
*
|
|
48
38
|
* - `{}` → `"main.body"`
|
|
49
39
|
* - `{field}` → `"main.<field>"`
|
|
50
40
|
* - `{card: i}` → `"cards.<kind>[i].body"`
|
|
51
41
|
* - `{card: i, field}` → `"cards.<kind>[i].<field>"`
|
|
52
42
|
*
|
|
53
|
-
* `steps` descend from the field to a leaf inside it, the inverse of
|
|
54
|
-
*
|
|
55
|
-
*
|
|
56
|
-
*
|
|
57
|
-
*
|
|
58
|
-
* `cards[i]` stands in for an unknown or blank kind. The index is the addr's own: no
|
|
59
|
-
* per-kind counting, since `DocPath` addresses cards by document-array index.
|
|
43
|
+
* `steps` descend from the field to a leaf inside it, the inverse of {@link
|
|
44
|
+
* nestedAddrForFieldPath}: `{field: 'keywords'}` with `[0]` is `"main.keywords[0]"`. A body holds
|
|
45
|
+
* no cells, so a field-less address with steps is `undefined`. `cards[i]` stands in for an unknown
|
|
46
|
+
* or blank kind; the index is the addr's own, `DocPath` addressing cards by document-array index.
|
|
60
47
|
*/
|
|
61
48
|
export declare function fieldPathForAddr(addr: Addr, kinds: readonly string[], steps?: readonly PathStep[]): DocPath | undefined;
|
|
62
49
|
/**
|
|
63
|
-
* A card's own path (`cards.<kind>[i]`),
|
|
64
|
-
*
|
|
65
|
-
* index outside `kinds`.
|
|
50
|
+
* A card's own path (`cards.<kind>[i]`), what a structure op names. `undefined` for an index
|
|
51
|
+
* outside `kinds`.
|
|
66
52
|
*/
|
|
67
53
|
export declare function cardPath(index: number, kinds: readonly string[]): DocPath | undefined;
|
|
68
54
|
/**
|
|
69
|
-
* The inverse: a canonical `DocPath` back to the `Addr` the document verbs take, or
|
|
70
|
-
*
|
|
71
|
-
*
|
|
72
|
-
*
|
|
73
|
-
*
|
|
74
|
-
* the body leaf answers to.
|
|
75
|
-
*
|
|
76
|
-
* Needs no `kinds`: the path carries the absolute index, and the kind in it is
|
|
77
|
-
* decoration the `Addr` has no room for.
|
|
55
|
+
* The inverse: a canonical `DocPath` back to the `Addr` the document verbs take, or `undefined` for
|
|
56
|
+
* a path that names no single commit address: a nested or array-element path (see {@link
|
|
57
|
+
* nestedAddrForFieldPath} and {@link nearestAddrForFieldPath}), a field-rooted one, or a malformed
|
|
58
|
+
* one. A bare card and a `.body` terminal both land on the field-less `{card: i}`. Needs no
|
|
59
|
+
* `kinds`: the path carries the absolute index.
|
|
78
60
|
*/
|
|
79
61
|
export declare function addrForFieldPath(path: DocPath): Addr | undefined;
|
|
80
62
|
/**
|
|
81
|
-
* The nearest ancestor a commit address can name: {@link addrForFieldPath} where the
|
|
82
|
-
*
|
|
83
|
-
*
|
|
84
|
-
*
|
|
85
|
-
* (config-space) or malformed path.
|
|
86
|
-
*
|
|
87
|
-
* An `Addr` reaches a root and one field, so two segments is the whole search: a
|
|
88
|
-
* deeper prefix is unaddressable by arity alone. Addressability being `Addr`'s reach
|
|
89
|
-
* rather than the grammar's is what makes the truncation a consumer's to make
|
|
90
|
-
* (VISUAL_EDITOR §Diagnostics).
|
|
63
|
+
* The nearest ancestor a commit address can name: {@link addrForFieldPath} where the path has one,
|
|
64
|
+
* else the root and its first field (`main.contact.email` and `main.keywords[0]` land on their
|
|
65
|
+
* field). `undefined` for a field-rooted (config-space) or malformed path. An `Addr` reaches a root
|
|
66
|
+
* and one field, so the truncation is a consumer's to make (VISUAL_EDITOR §Diagnostics).
|
|
91
67
|
*/
|
|
92
68
|
export declare function nearestAddrForFieldPath(path: DocPath): Addr | undefined;
|
|
93
69
|
/**
|
|
94
|
-
* A nested address, split: the field's `Addr`, and the steps from it to the leaf
|
|
95
|
-
* `main.keywords[0]` is `{field: keywords, steps: [0]}`,
|
|
96
|
-
* `main.vectors[0].tours[2].title` is `[0, 'tours', 2,
|
|
97
|
-
*
|
|
98
|
-
* a
|
|
99
|
-
*
|
|
100
|
-
* here as `undefined`; so does a body with something after it, a body holding no
|
|
101
|
-
* cells. Whether the schema declares each step is the caller's to check: this module
|
|
102
|
-
* holds the grammar and no schema. `regions()`, `positionAt` and `formatDocPath` all
|
|
103
|
-
* spell the index segment bracketed, so there is one spelling to read and none to
|
|
104
|
-
* bridge.
|
|
70
|
+
* A nested address, split: the field's `Addr`, and the steps from it to the leaf (the `PathStep`
|
|
71
|
+
* grammar `reader.getContentAt` walks). `main.keywords[0]` is `{field: keywords, steps: [0]}`,
|
|
72
|
+
* `main.contact.email` is `['email']`, `main.vectors[0].tours[2].title` is `[0, 'tours', 2,
|
|
73
|
+
* 'title']`. Anything {@link addrForFieldPath} can name has no steps and lands here as `undefined`,
|
|
74
|
+
* as does a body with something after it. Whether the schema declares each step is the caller's to
|
|
75
|
+
* check.
|
|
105
76
|
*/
|
|
106
77
|
export declare function nestedAddrForFieldPath(path: DocPath): NestedAddr | undefined;
|
|
107
78
|
/** A nested address: the field's `Addr`, and the steps from it to the leaf. */
|
package/dist/core/address.js
CHANGED
|
@@ -1,27 +1,21 @@
|
|
|
1
1
|
import { core } from './lifecycle.js';
|
|
2
2
|
// ── Addr ↔ DocPath ──────────────────────────────────────────────────────────
|
|
3
|
-
// The one hop between the two vocabularies,
|
|
4
|
-
//
|
|
5
|
-
//
|
|
6
|
-
// mints it off its own derived card tree rather than re-reading `doc.cards`, which
|
|
7
|
-
// serializes every card per read and is not a thing to do per keystroke.
|
|
3
|
+
// The one hop between the two vocabularies, pure and document-free: `kinds` is the kind of each
|
|
4
|
+
// card by content index (`doc.cards.map(c => c.kind)`). The editor mints it off its own derived
|
|
5
|
+
// card tree, since `doc.cards` serializes every card per read.
|
|
8
6
|
/**
|
|
9
|
-
* A field's canonical `DocPath`, or `undefined` when `addr.card` is outside the live
|
|
10
|
-
*
|
|
11
|
-
* addresses its body, which is the leaf `{card: i}` names.
|
|
7
|
+
* A field's canonical `DocPath`, or `undefined` when `addr.card` is outside the live `kinds` array
|
|
8
|
+
* (a stale address: drop it rather than mis-target). A field-less card addresses its body.
|
|
12
9
|
*
|
|
13
10
|
* - `{}` → `"main.body"`
|
|
14
11
|
* - `{field}` → `"main.<field>"`
|
|
15
12
|
* - `{card: i}` → `"cards.<kind>[i].body"`
|
|
16
13
|
* - `{card: i, field}` → `"cards.<kind>[i].<field>"`
|
|
17
14
|
*
|
|
18
|
-
* `steps` descend from the field to a leaf inside it, the inverse of
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
* `cards[i]` stands in for an unknown or blank kind. The index is the addr's own: no
|
|
24
|
-
* per-kind counting, since `DocPath` addresses cards by document-array index.
|
|
15
|
+
* `steps` descend from the field to a leaf inside it, the inverse of {@link
|
|
16
|
+
* nestedAddrForFieldPath}: `{field: 'keywords'}` with `[0]` is `"main.keywords[0]"`. A body holds
|
|
17
|
+
* no cells, so a field-less address with steps is `undefined`. `cards[i]` stands in for an unknown
|
|
18
|
+
* or blank kind; the index is the addr's own, `DocPath` addressing cards by document-array index.
|
|
25
19
|
*/
|
|
26
20
|
export function fieldPathForAddr(addr, kinds, steps = []) {
|
|
27
21
|
const head = cardHead(addr.card, kinds);
|
|
@@ -35,57 +29,41 @@ export function fieldPathForAddr(addr, kinds, steps = []) {
|
|
|
35
29
|
return core().formatDocPath(segs);
|
|
36
30
|
}
|
|
37
31
|
/**
|
|
38
|
-
* A card's own path (`cards.<kind>[i]`),
|
|
39
|
-
*
|
|
40
|
-
* index outside `kinds`.
|
|
32
|
+
* A card's own path (`cards.<kind>[i]`), what a structure op names. `undefined` for an index
|
|
33
|
+
* outside `kinds`.
|
|
41
34
|
*/
|
|
42
35
|
export function cardPath(index, kinds) {
|
|
43
36
|
const head = cardHead(index, kinds);
|
|
44
37
|
return head ? core().formatDocPath([head]) : undefined;
|
|
45
38
|
}
|
|
46
39
|
/**
|
|
47
|
-
* The inverse: a canonical `DocPath` back to the `Addr` the document verbs take, or
|
|
48
|
-
*
|
|
49
|
-
*
|
|
50
|
-
*
|
|
51
|
-
*
|
|
52
|
-
* the body leaf answers to.
|
|
53
|
-
*
|
|
54
|
-
* Needs no `kinds`: the path carries the absolute index, and the kind in it is
|
|
55
|
-
* decoration the `Addr` has no room for.
|
|
40
|
+
* The inverse: a canonical `DocPath` back to the `Addr` the document verbs take, or `undefined` for
|
|
41
|
+
* a path that names no single commit address: a nested or array-element path (see {@link
|
|
42
|
+
* nestedAddrForFieldPath} and {@link nearestAddrForFieldPath}), a field-rooted one, or a malformed
|
|
43
|
+
* one. A bare card and a `.body` terminal both land on the field-less `{card: i}`. Needs no
|
|
44
|
+
* `kinds`: the path carries the absolute index.
|
|
56
45
|
*/
|
|
57
46
|
export function addrForFieldPath(path) {
|
|
58
47
|
const segs = segsOf(path);
|
|
59
48
|
return segs && addrForSegs(segs);
|
|
60
49
|
}
|
|
61
50
|
/**
|
|
62
|
-
* The nearest ancestor a commit address can name: {@link addrForFieldPath} where the
|
|
63
|
-
*
|
|
64
|
-
*
|
|
65
|
-
*
|
|
66
|
-
* (config-space) or malformed path.
|
|
67
|
-
*
|
|
68
|
-
* An `Addr` reaches a root and one field, so two segments is the whole search: a
|
|
69
|
-
* deeper prefix is unaddressable by arity alone. Addressability being `Addr`'s reach
|
|
70
|
-
* rather than the grammar's is what makes the truncation a consumer's to make
|
|
71
|
-
* (VISUAL_EDITOR §Diagnostics).
|
|
51
|
+
* The nearest ancestor a commit address can name: {@link addrForFieldPath} where the path has one,
|
|
52
|
+
* else the root and its first field (`main.contact.email` and `main.keywords[0]` land on their
|
|
53
|
+
* field). `undefined` for a field-rooted (config-space) or malformed path. An `Addr` reaches a root
|
|
54
|
+
* and one field, so the truncation is a consumer's to make (VISUAL_EDITOR §Diagnostics).
|
|
72
55
|
*/
|
|
73
56
|
export function nearestAddrForFieldPath(path) {
|
|
74
57
|
const segs = segsOf(path);
|
|
75
58
|
return segs && addrForSegs(segs.slice(0, 2));
|
|
76
59
|
}
|
|
77
60
|
/**
|
|
78
|
-
* A nested address, split: the field's `Addr`, and the steps from it to the leaf
|
|
79
|
-
* `main.keywords[0]` is `{field: keywords, steps: [0]}`,
|
|
80
|
-
* `main.vectors[0].tours[2].title` is `[0, 'tours', 2,
|
|
81
|
-
*
|
|
82
|
-
* a
|
|
83
|
-
*
|
|
84
|
-
* here as `undefined`; so does a body with something after it, a body holding no
|
|
85
|
-
* cells. Whether the schema declares each step is the caller's to check: this module
|
|
86
|
-
* holds the grammar and no schema. `regions()`, `positionAt` and `formatDocPath` all
|
|
87
|
-
* spell the index segment bracketed, so there is one spelling to read and none to
|
|
88
|
-
* bridge.
|
|
61
|
+
* A nested address, split: the field's `Addr`, and the steps from it to the leaf (the `PathStep`
|
|
62
|
+
* grammar `reader.getContentAt` walks). `main.keywords[0]` is `{field: keywords, steps: [0]}`,
|
|
63
|
+
* `main.contact.email` is `['email']`, `main.vectors[0].tours[2].title` is `[0, 'tours', 2,
|
|
64
|
+
* 'title']`. Anything {@link addrForFieldPath} can name has no steps and lands here as `undefined`,
|
|
65
|
+
* as does a body with something after it. Whether the schema declares each step is the caller's to
|
|
66
|
+
* check.
|
|
89
67
|
*/
|
|
90
68
|
export function nestedAddrForFieldPath(path) {
|
|
91
69
|
const segs = segsOf(path);
|
|
@@ -105,9 +83,10 @@ export function nestedAddrForFieldPath(path) {
|
|
|
105
83
|
}
|
|
106
84
|
return { field, steps };
|
|
107
85
|
}
|
|
108
|
-
/**
|
|
109
|
-
*
|
|
110
|
-
*
|
|
86
|
+
/**
|
|
87
|
+
* `parseDocPath`, with a malformed path as `undefined` rather than a throw. The gate is read
|
|
88
|
+
* outside the try: an uninitialized core is not a malformed path.
|
|
89
|
+
*/
|
|
111
90
|
function segsOf(path) {
|
|
112
91
|
const { parseDocPath } = core();
|
|
113
92
|
try {
|
|
@@ -117,8 +96,7 @@ function segsOf(path) {
|
|
|
117
96
|
return undefined;
|
|
118
97
|
}
|
|
119
98
|
}
|
|
120
|
-
/** {@link addrForFieldPath} over already-parsed segments
|
|
121
|
-
* it for the head of a path rather than re-serializing one. */
|
|
99
|
+
/** {@link addrForFieldPath} over already-parsed segments. */
|
|
122
100
|
function addrForSegs(segs) {
|
|
123
101
|
const [head, ...rest] = segs;
|
|
124
102
|
if (!head)
|
|
@@ -141,8 +119,10 @@ function addrForSegs(segs) {
|
|
|
141
119
|
return { ...addr, field: tail.name };
|
|
142
120
|
return undefined;
|
|
143
121
|
}
|
|
144
|
-
/**
|
|
145
|
-
*
|
|
122
|
+
/**
|
|
123
|
+
* The head segment a card index resolves to, or `undefined` when out of the live array. A falsy
|
|
124
|
+
* kind is the unknown-kind form `cards[i]`.
|
|
125
|
+
*/
|
|
146
126
|
function cardHead(index, kinds) {
|
|
147
127
|
if (index == null)
|
|
148
128
|
return { seg: 'main' };
|
package/dist/core/bloom.d.ts
CHANGED
|
@@ -1,13 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Run one wash over `host`, a `.qm-wash` box, resolving the dwell off its root so the
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
* `prefers-reduced-motion` in the derivation, so what is left to `matchMedia` here is
|
|
7
|
-
* the shape: a hold and a cut carry no ramps, which is a frame list rather than a
|
|
8
|
-
* number and the one half CSS cannot hand over.
|
|
9
|
-
*
|
|
10
|
-
* A run in flight on the same host is cancelled first: two quick landings on one leaf
|
|
11
|
-
* must not stack two alphas over it.
|
|
2
|
+
* Run one wash over `host`, a `.qm-wash` box, resolving the dwell off its root so the number is
|
|
3
|
+
* single-sourced in CSS (`core/rungs.ts`). `--_qm-bloom-dwell` already shortens under
|
|
4
|
+
* `prefers-reduced-motion`, so `matchMedia` here only picks the frame list. A run in flight on the
|
|
5
|
+
* same host is cancelled first, so two quick landings do not stack alphas.
|
|
12
6
|
*/
|
|
13
7
|
export declare function bloom(host: HTMLElement): void;
|
package/dist/core/bloom.js
CHANGED
|
@@ -1,24 +1,17 @@
|
|
|
1
|
-
// The arrival wash: a soft accent bloom that rises over a leaf the editor just
|
|
2
|
-
//
|
|
3
|
-
//
|
|
1
|
+
// The arrival wash: a soft accent bloom that rises over a leaf the editor just landed on and decays
|
|
2
|
+
// to nothing. A landing is an event, not a state, so a highlight that outlives the hop is ink
|
|
3
|
+
// nothing asked for.
|
|
4
4
|
//
|
|
5
|
-
// Opacity is the animated property, never the colour
|
|
6
|
-
//
|
|
7
|
-
// `
|
|
8
|
-
// pre-resolve and re-resolve on a theme change.
|
|
9
|
-
//
|
|
10
|
-
// The wash is the host's `::after` (`.qm-wash`, controls.css), drawn from mount: this
|
|
11
|
-
// module plays it and adds nothing to the tree.
|
|
5
|
+
// Opacity is the animated property, never the colour: the wash's alpha is a peak fixed by
|
|
6
|
+
// `--_qm-accent-wash`, so the scale stays in CSS where `color-mix` resolves. The wash is the host's
|
|
7
|
+
// `::after` (`.qm-wash`, controls.css); this module plays it and adds nothing to the tree.
|
|
12
8
|
import { rungStyle } from './rungs.js';
|
|
13
9
|
const WASH = '::after';
|
|
14
|
-
/** The dwell used when the derivation is out of reach (an unstyled root, or jsdom
|
|
15
|
-
* where `getComputedStyle` reports custom properties as empty). A fallback is the
|
|
16
|
-
* absence of the scale, not a copy of it. */
|
|
10
|
+
/** The dwell used when the derivation is out of reach (an unstyled root, or jsdom). */
|
|
17
11
|
const FALLBACK_MS = 1100;
|
|
18
|
-
// Rise fast, hold, then leave slowly: a highlighter's decay
|
|
19
|
-
//
|
|
20
|
-
//
|
|
21
|
-
// still answers "here" with nothing to track.
|
|
12
|
+
// Rise fast, hold, then leave slowly: a highlighter's decay. A keyframe's easing governs the
|
|
13
|
+
// segment that starts at it. Under reduced motion the ramps are dropped: the wash holds at full for
|
|
14
|
+
// a beat and cuts.
|
|
22
15
|
const FRAMES = [
|
|
23
16
|
{ opacity: 0, offset: 0, easing: 'ease-out' },
|
|
24
17
|
{ opacity: 1, offset: 0.08 },
|
|
@@ -27,16 +20,10 @@ const FRAMES = [
|
|
|
27
20
|
];
|
|
28
21
|
const FRAMES_REDUCED = [{ opacity: 1 }, { opacity: 1 }];
|
|
29
22
|
/**
|
|
30
|
-
* Run one wash over `host`, a `.qm-wash` box, resolving the dwell off its root so the
|
|
31
|
-
*
|
|
32
|
-
*
|
|
33
|
-
*
|
|
34
|
-
* `prefers-reduced-motion` in the derivation, so what is left to `matchMedia` here is
|
|
35
|
-
* the shape: a hold and a cut carry no ramps, which is a frame list rather than a
|
|
36
|
-
* number and the one half CSS cannot hand over.
|
|
37
|
-
*
|
|
38
|
-
* A run in flight on the same host is cancelled first: two quick landings on one leaf
|
|
39
|
-
* must not stack two alphas over it.
|
|
23
|
+
* Run one wash over `host`, a `.qm-wash` box, resolving the dwell off its root so the number is
|
|
24
|
+
* single-sourced in CSS (`core/rungs.ts`). `--_qm-bloom-dwell` already shortens under
|
|
25
|
+
* `prefers-reduced-motion`, so `matchMedia` here only picks the frame list. A run in flight on the
|
|
26
|
+
* same host is cancelled first, so two quick landings do not stack alphas.
|
|
40
27
|
*/
|
|
41
28
|
export function bloom(host) {
|
|
42
29
|
if (typeof host.animate !== 'function')
|