@quillmark/svelte 0.2.0 → 0.3.1
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 +6 -6
- package/README.md +3 -3
- package/THEMING.md +12 -8
- package/dist/core/address.d.ts +7 -7
- package/dist/core/address.js +6 -6
- package/dist/core/bloom.d.ts +3 -32
- package/dist/core/bloom.js +19 -51
- package/dist/core/codec/anchor.d.ts +1 -1
- package/dist/core/codec/anchor.js +1 -1
- package/dist/core/codec/atoms.d.ts +10 -0
- package/dist/core/codec/atoms.js +78 -0
- package/dist/core/codec/clipboard.d.ts +59 -0
- package/dist/core/codec/clipboard.js +202 -0
- package/dist/core/codec/code.d.ts +1 -1
- package/dist/core/codec/code.js +4 -4
- package/dist/core/codec/decode.d.ts +1 -1
- package/dist/core/codec/decode.js +8 -8
- package/dist/core/codec/encode.d.ts +4 -4
- package/dist/core/codec/encode.js +10 -10
- package/dist/core/codec/field.d.ts +21 -26
- package/dist/core/codec/field.js +33 -32
- package/dist/core/codec/index.js +5 -8
- package/dist/core/codec/inputrules.js +56 -18
- package/dist/core/codec/islands.d.ts +11 -7
- package/dist/core/codec/islands.js +16 -10
- package/dist/core/codec/keymap.d.ts +5 -6
- package/dist/core/codec/keymap.js +8 -7
- package/dist/core/codec/lists.d.ts +3 -3
- package/dist/core/codec/lists.js +32 -13
- package/dist/core/codec/marks.d.ts +1 -1
- package/dist/core/codec/marks.js +1 -1
- package/dist/core/codec/positions.js +1 -1
- package/dist/core/codec/prose.css +235 -221
- package/dist/core/codec/schema.js +4 -4
- package/dist/core/codec/slash.d.ts +39 -41
- package/dist/core/codec/slash.js +0 -0
- package/dist/core/codec/table-view.d.ts +12 -16
- package/dist/core/codec/table-view.js +488 -292
- package/dist/core/codec/table.d.ts +42 -15
- package/dist/core/codec/table.js +70 -26
- package/dist/core/errors.d.ts +5 -5
- package/dist/core/index.js +2 -2
- package/dist/core/lifecycle.js +1 -1
- package/dist/core/rebind.svelte.d.ts +2 -2
- package/dist/core/rebind.svelte.js +4 -4
- package/dist/core/teardown.d.ts +1 -1
- package/dist/core/teardown.js +1 -1
- package/dist/core/theme.css +170 -64
- package/dist/preset/index.css +1 -1
- package/dist/preset/recipes.css +31 -29
- package/dist/preset/scale.css +37 -26
- package/dist/preview/Preview.svelte +17 -27
- package/dist/preview/Preview.svelte.d.ts +4 -5
- package/dist/preview/bridge.js +6 -6
- package/dist/preview/controller.d.ts +8 -10
- package/dist/preview/controller.js +19 -44
- package/dist/preview/geometry.d.ts +8 -10
- package/dist/preview/geometry.js +8 -10
- package/dist/preview/index.js +4 -4
- package/dist/preview/paint.d.ts +1 -1
- package/dist/preview/paint.js +17 -19
- package/dist/visual/ArrayField.svelte +80 -66
- package/dist/visual/ArrayField.svelte.d.ts +1 -1
- package/dist/visual/BooleanField.svelte +8 -6
- package/dist/visual/BooleanField.svelte.d.ts +2 -2
- package/dist/visual/Card.svelte +160 -329
- package/dist/visual/Card.svelte.d.ts +2 -7
- package/dist/visual/CardControls.svelte +1 -1
- package/dist/visual/DateField.svelte +19 -19
- package/dist/visual/DateField.svelte.d.ts +3 -3
- package/dist/visual/DiagnosticList.svelte +1 -1
- package/dist/visual/EnumField.svelte +16 -16
- package/dist/visual/EnumField.svelte.d.ts +2 -2
- package/dist/visual/Field.svelte +128 -115
- package/dist/visual/Field.svelte.d.ts +1 -1
- package/dist/visual/FieldHint.svelte +24 -42
- package/dist/visual/FieldHint.svelte.d.ts +1 -1
- package/dist/visual/FieldLabel.svelte +8 -8
- package/dist/visual/FormatPopover.svelte +80 -35
- package/dist/visual/NumberField.svelte +5 -5
- package/dist/visual/NumberField.svelte.d.ts +1 -1
- package/dist/visual/ObjectField.svelte +9 -9
- package/dist/visual/ObjectField.svelte.d.ts +1 -1
- package/dist/visual/ProseArrayElement.svelte +11 -13
- package/dist/visual/ProseArrayElement.svelte.d.ts +1 -1
- package/dist/visual/ProseField.svelte +53 -87
- package/dist/visual/ProseField.svelte.d.ts +13 -19
- package/dist/visual/SlashMenu.svelte +42 -16
- package/dist/visual/TextField.svelte +5 -5
- package/dist/visual/TextField.svelte.d.ts +1 -1
- package/dist/visual/TipsCard.svelte +68 -56
- package/dist/visual/VisualEditor.svelte +6 -6
- package/dist/visual/VisualEditorInner.svelte +203 -290
- package/dist/visual/controls.css +74 -30
- package/dist/visual/diagnostics.d.ts +5 -5
- package/dist/visual/diagnostics.js +3 -3
- package/dist/visual/domid.js +3 -3
- package/dist/visual/ext.d.ts +1 -1
- package/dist/visual/ext.js +1 -1
- package/dist/visual/icons/nodes.d.ts +19 -0
- package/dist/visual/icons/nodes.js +13 -1
- package/dist/visual/index.js +4 -4
- package/dist/visual/leaves.d.ts +3 -3
- package/dist/visual/leaves.js +1 -1
- package/dist/visual/links.d.ts +24 -0
- package/dist/visual/links.js +67 -0
- package/dist/visual/props.d.ts +8 -8
- package/dist/visual/signals.d.ts +6 -6
- package/dist/visual/strings.d.ts +14 -19
- package/dist/visual/strings.js +5 -7
- package/dist/visual/structure.d.ts +24 -17
- package/dist/visual/structure.js +8 -8
- package/dist/visual/synced.svelte.d.ts +1 -1
- package/dist/visual/synced.svelte.js +4 -4
- package/dist/visual/tips.d.ts +2 -2
- package/dist/visual/tips.js +3 -3
- package/package.json +2 -2
- package/dist/preview/overlay.d.ts +0 -11
- package/dist/preview/overlay.js +0 -97
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
|
|
10
|
-
chevron-right, chevron-down, chevron-up, bold, italic, underline,
|
|
11
|
-
code, link, hash, info, plus and
|
|
9
|
+
`visual/icons/nodes.ts` holds sixteen verbatim from @lucide/svelte 1.28.0:
|
|
10
|
+
chevron-right, arrow-right, chevron-down, chevron-up, bold, italic, underline,
|
|
11
|
+
strikethrough, code, link, hash, info, plus, x, trash-2 and minus.
|
|
12
12
|
|
|
13
13
|
`core/codec/table-view.ts` holds a plus and four text-alignment glyphs derived
|
|
14
14
|
from an earlier release of the same project.
|
|
@@ -31,9 +31,9 @@ OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
|
|
31
31
|
|
|
32
32
|
--------------------------------------------------------------------------------
|
|
33
33
|
|
|
34
|
-
|
|
35
|
-
italic, link, plus and x — are
|
|
36
|
-
license as well.
|
|
34
|
+
Thirteen of those glyphs — arrow-right, chevron-down, chevron-right, chevron-up,
|
|
35
|
+
code, hash, info, italic, link, minus, plus, trash-2 and x — are drawn exactly as
|
|
36
|
+
the Feather project draws them and carry its license as well.
|
|
37
37
|
|
|
38
38
|
The MIT License (MIT)
|
|
39
39
|
|
package/README.md
CHANGED
|
@@ -83,7 +83,7 @@ preview.refresh(session.update(doc)); // repaint dirtyPages ∩ visible
|
|
|
83
83
|
|
|
84
84
|
In Svelte, `<Preview {session} onPick={…} />` exposes the same verbs (`refresh`, `scrollToField`, `focusPosition`, `setZoom`) via `bind:this`.
|
|
85
85
|
|
|
86
|
-
`<Preview>` binds **once**, at mount: swapping `session`, `margin`, `
|
|
86
|
+
`<Preview>` binds **once**, at mount: swapping `session`, `margin`, `onPick`, `onError` or `strings` in place changes nothing on screen and reports `rebind-ignored` through `onError` at `dev` severity, naming the prop. Swap by remounting (`{#key session}`); drive in-place edits through `refresh(change)`. `class` and `style` are the exceptions, landing on the root element and staying live.
|
|
87
87
|
|
|
88
88
|
## Visual editor
|
|
89
89
|
|
|
@@ -149,7 +149,7 @@ The package ships English and a seam to replace it. `strings` is keyed and **par
|
|
|
149
149
|
{quill}
|
|
150
150
|
strings={{
|
|
151
151
|
cardDelete: 'Supprimer la carte',
|
|
152
|
-
|
|
152
|
+
addCard: '+ Ajouter une carte'
|
|
153
153
|
}}
|
|
154
154
|
formatDiagnostic={(d) =>
|
|
155
155
|
d.code === 'validation::must_fill' ? `${d.path} est obligatoire` : undefined}
|
|
@@ -200,7 +200,7 @@ onPick: (at) => visualEditor.setCaret(at);
|
|
|
200
200
|
onCaretMove: preview.focusPosition;
|
|
201
201
|
```
|
|
202
202
|
|
|
203
|
-
A pick carries a caret where the compile tracks the content under the point, and the field alone where it tracks only the placement — a scalar the plate prints without tracking. `setCaret` takes both: an absent `pos` reveals and focuses the field, which is the whole of what a click on plate-placed ink can mean. The address may name an array
|
|
203
|
+
A pick carries a caret where the compile tracks the content under the point, and the field alone where it tracks only the placement — a scalar the plate prints without tracking. `setCaret` takes both: an absent `pos` reveals and focuses the field, which is the whole of what a click on plate-placed ink can mean. The address may name an array element (`main.references.0`), which lands on that row.
|
|
204
204
|
|
|
205
205
|
A consumer holding an `Addr` of its own maps it with `fieldPathForAddr` (from `@quillmark/svelte/core`). The playground app's split-pane route is the full reference shell: one session, both bridge directions, the preview following edits, and diagnostics routed inline.
|
|
206
206
|
|
package/THEMING.md
CHANGED
|
@@ -41,7 +41,9 @@ The whole contract is **ten CSS custom properties**. They are dials, not a palet
|
|
|
41
41
|
|
|
42
42
|
### Two of them are not branding
|
|
43
43
|
|
|
44
|
-
`--qm-bg` and `--qm-fg` are the two a themed host sets before any brand decision. Their defaults are a self-consistent neutral for a bare mounting site, not a guess at your page
|
|
44
|
+
`--qm-bg` and `--qm-fg` are the two a themed host sets before any brand decision. Their defaults are a self-consistent neutral for a bare mounting site, not a guess at your page.
|
|
45
|
+
|
|
46
|
+
`--qm-bg` is the **card**, not the column behind it: the plane a document is written on, which is also the sheet the preview paints. Everything else is a step off it, and the column the cards sit in is a step down, so a mounted editor draws its own ground and the cards read as islands on it. Give it the tone you want a card to be — white, in most light themes — and let the column fall out. If you want the column to meet your page instead, set a `background` of your own on `.qm-editor`; your CSS is unlayered and wins.
|
|
45
47
|
|
|
46
48
|
The poles take a colour, not `transparent` or `inherit`: the raised surfaces, the borders and the muted inks are each a `color-mix` against `--qm-bg`, so a transparent pole makes every one of them translucent instead of letting your page through. Hand over the value your page is painted with and the derivation steps off it.
|
|
47
49
|
|
|
@@ -49,9 +51,9 @@ The poles take a colour, not `transparent` or `inherit`: the raised surfaces, th
|
|
|
49
51
|
|
|
50
52
|
| Token | Default | What it sets |
|
|
51
53
|
| ---------------- | -------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
52
|
-
| `--qm-bg` | `#fff` / `#
|
|
53
|
-
| `--qm-fg` | `#
|
|
54
|
-
| `--qm-accent` | `#
|
|
54
|
+
| `--qm-bg` | `#fff` / `#272729` | Base surface: a card, and the preview's sheet. Fields, the popover and the column behind the cards each step off it. |
|
|
55
|
+
| `--qm-fg` | `#1c1c1c` / `#d7dadc` | Base ink. Body text, labels, and borders step off it. |
|
|
56
|
+
| `--qm-accent` | `#71717a` / `#a1a1aa` | Focus rings, active marks, the preview's active field box. The surface spends no hue of its own, so this is the one it wears. |
|
|
55
57
|
| `--qm-danger` | `#c5221f` | Error diagnostics, the required marker, the delete glyph. |
|
|
56
58
|
| `--qm-warning` | `#b25000` | Warning diagnostics. |
|
|
57
59
|
| `--qm-font` | `ui-sans-serif, system-ui, sans-serif` | The editor surface's font family. Controls and buttons take it too, in place of the UA face. |
|
|
@@ -76,7 +78,9 @@ Declared before our sheet it fixes the order outright; declared after, `qm` is a
|
|
|
76
78
|
|
|
77
79
|
### The surface follows your colour scheme
|
|
78
80
|
|
|
79
|
-
|
|
81
|
+
Ink steps `fg → bg` and the borders and hover fills step `bg → fg`, mixed in **oklab**, so inverting the two poles inverts all of them together.
|
|
82
|
+
|
|
83
|
+
**Planes are the exception**, and deliberately: the raised plane is the brighter one in both schemes, which is what makes a card read as a card rather than as a differently-tinted page. So the column under the cards sinks from `--qm-bg` toward shadow and a floating surface rises from it toward light, and neither reads the ink pole. Each direction takes a step sized to the scheme it is in, which is why pinning a dark palette while your `color-scheme` still says light leaves the column barely off its cards — the one case §"Make it yours" tells you to pin the scheme alongside the poles.
|
|
80
84
|
|
|
81
85
|
Which way the poles default is **your `color-scheme`**, not the operating system's. Declare the scheme your app is in and the surface lands on it, along with native chrome, which reads the same property:
|
|
82
86
|
|
|
@@ -137,7 +141,7 @@ It carries a `--qmh-*` scale for the page, derived from the same ten dials and c
|
|
|
137
141
|
|
|
138
142
|
### The shell, if you are building a tool
|
|
139
143
|
|
|
140
|
-
An app that mounts an editor beside a preview over one session needs a screen to put them in, and the
|
|
144
|
+
An app that mounts an editor beside a preview over one session needs a screen to put them in, and the shape of that screen is the same every time. Five more classes are that shape, so what you write is the wiring rather than the grid:
|
|
141
145
|
|
|
142
146
|
```html
|
|
143
147
|
<div class="qm-workspace">
|
|
@@ -169,9 +173,9 @@ An app that mounts an editor beside a preview over one session needs a screen to
|
|
|
169
173
|
| `.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. |
|
|
170
174
|
| `.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. |
|
|
171
175
|
|
|
172
|
-
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
|
|
176
|
+
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 repaints itself when it comes back. 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.
|
|
173
177
|
|
|
174
|
-
The shape is shared; the
|
|
178
|
+
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.
|
|
175
179
|
|
|
176
180
|
It is **unlayered**, so your own rules beat it by ordinary precedence, exactly as they beat the surfaces. It is also opt-in and side-effect free until imported: nothing that styles your document arrives with a component.
|
|
177
181
|
|
package/dist/core/address.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { Addr, HitGranularity } from '@quillmark/wasm';
|
|
2
2
|
/**
|
|
3
3
|
* A canonical field address: `main.<field>` / `main.body` /
|
|
4
|
-
* `cards.<kind>[<i>].<field>`, cards keyed by
|
|
4
|
+
* `cards.<kind>[<i>].<field>`, cards keyed by absolute document index.
|
|
5
5
|
*
|
|
6
6
|
* The grammar `parseDocPath` / `formatDocPath` speak, and the one
|
|
7
7
|
* `Diagnostic.path`, `ContentHit.field`, `FieldRegion.field` and
|
|
@@ -27,7 +27,7 @@ export interface Place {
|
|
|
27
27
|
* Where a preview click landed. What the preview surfaces and what the editor's
|
|
28
28
|
* `setCaret` takes.
|
|
29
29
|
*
|
|
30
|
-
* **An absent `pos` is the
|
|
30
|
+
* **An absent `pos` is the placement rung**, not a caret at zero: the click resolved
|
|
31
31
|
* a field the plate places without tracking its content (`fieldAt` answers,
|
|
32
32
|
* `positionAt` does not), so there is no offset and the landing is a focus. A `Place`
|
|
33
33
|
* is a landing carrying its caret, and a `ContentHit` is one too.
|
|
@@ -43,7 +43,7 @@ export interface Landing {
|
|
|
43
43
|
/**
|
|
44
44
|
* A field's canonical `DocPath`, or `undefined` when `addr.card` is outside the live
|
|
45
45
|
* `kinds` array (a stale address: drop it rather than mis-target). A field-less card
|
|
46
|
-
* addresses its
|
|
46
|
+
* addresses its body, which is the leaf `{card: i}` names.
|
|
47
47
|
*
|
|
48
48
|
* - `{}` → `"main.body"`
|
|
49
49
|
* - `{field}` → `"main.<field>"`
|
|
@@ -55,7 +55,7 @@ export interface Landing {
|
|
|
55
55
|
*/
|
|
56
56
|
export declare function fieldPathForAddr(addr: Addr, kinds: readonly string[]): DocPath | undefined;
|
|
57
57
|
/**
|
|
58
|
-
* A
|
|
58
|
+
* A card's own path (`cards.<kind>[i]`), not a leaf's: what a structure op names,
|
|
59
59
|
* where the change is the card rather than anything inside it. `undefined` for an
|
|
60
60
|
* index outside `kinds`.
|
|
61
61
|
*/
|
|
@@ -72,7 +72,7 @@ export declare function cardPath(index: number, kinds: readonly string[]): DocPa
|
|
|
72
72
|
*/
|
|
73
73
|
export declare function addrForFieldPath(path: DocPath): Addr | undefined;
|
|
74
74
|
/**
|
|
75
|
-
* An
|
|
75
|
+
* An array element's address, split. `main.references.0` and `main.references[0]`
|
|
76
76
|
* both land here; anything {@link addrForFieldPath} can name, and anything whose
|
|
77
77
|
* trailing segment is not an index, does not.
|
|
78
78
|
*
|
|
@@ -80,11 +80,11 @@ export declare function addrForFieldPath(path: DocPath): Addr | undefined;
|
|
|
80
80
|
* and `positionAt` emit `main.references.0`, which `parseDocPath` reads as a field
|
|
81
81
|
* literally named `"0"`, while `formatDocPath` spells the index segment
|
|
82
82
|
* `main.references[0]`. A trailing all-digits field name is therefore an index that
|
|
83
|
-
* lost its brackets — but only under a field the
|
|
83
|
+
* lost its brackets — but only under a field the schema declares an array, the
|
|
84
84
|
* caller's guard to apply: this module holds the grammar and no schema.
|
|
85
85
|
*/
|
|
86
86
|
export declare function elementAddrForFieldPath(path: DocPath): ElementAddr | undefined;
|
|
87
|
-
/** An array element's address: the
|
|
87
|
+
/** An array element's address: the array's `Addr`, and which element of it. */
|
|
88
88
|
export interface ElementAddr {
|
|
89
89
|
field: Addr;
|
|
90
90
|
index: number;
|
package/dist/core/address.js
CHANGED
|
@@ -8,7 +8,7 @@ import { core } from './lifecycle.js';
|
|
|
8
8
|
/**
|
|
9
9
|
* A field's canonical `DocPath`, or `undefined` when `addr.card` is outside the live
|
|
10
10
|
* `kinds` array (a stale address: drop it rather than mis-target). A field-less card
|
|
11
|
-
* addresses its
|
|
11
|
+
* addresses its body, which is the leaf `{card: i}` names.
|
|
12
12
|
*
|
|
13
13
|
* - `{}` → `"main.body"`
|
|
14
14
|
* - `{field}` → `"main.<field>"`
|
|
@@ -26,7 +26,7 @@ export function fieldPathForAddr(addr, kinds) {
|
|
|
26
26
|
return core().formatDocPath([head, tail]);
|
|
27
27
|
}
|
|
28
28
|
/**
|
|
29
|
-
* A
|
|
29
|
+
* A card's own path (`cards.<kind>[i]`), not a leaf's: what a structure op names,
|
|
30
30
|
* where the change is the card rather than anything inside it. `undefined` for an
|
|
31
31
|
* index outside `kinds`.
|
|
32
32
|
*/
|
|
@@ -49,7 +49,7 @@ export function addrForFieldPath(path) {
|
|
|
49
49
|
return segs && addrForSegs(segs);
|
|
50
50
|
}
|
|
51
51
|
/**
|
|
52
|
-
* An
|
|
52
|
+
* An array element's address, split. `main.references.0` and `main.references[0]`
|
|
53
53
|
* both land here; anything {@link addrForFieldPath} can name, and anything whose
|
|
54
54
|
* trailing segment is not an index, does not.
|
|
55
55
|
*
|
|
@@ -57,7 +57,7 @@ export function addrForFieldPath(path) {
|
|
|
57
57
|
* and `positionAt` emit `main.references.0`, which `parseDocPath` reads as a field
|
|
58
58
|
* literally named `"0"`, while `formatDocPath` spells the index segment
|
|
59
59
|
* `main.references[0]`. A trailing all-digits field name is therefore an index that
|
|
60
|
-
* lost its brackets — but only under a field the
|
|
60
|
+
* lost its brackets — but only under a field the schema declares an array, the
|
|
61
61
|
* caller's guard to apply: this module holds the grammar and no schema.
|
|
62
62
|
*/
|
|
63
63
|
export function elementAddrForFieldPath(path) {
|
|
@@ -73,11 +73,11 @@ export function elementAddrForFieldPath(path) {
|
|
|
73
73
|
if (index == null)
|
|
74
74
|
return undefined;
|
|
75
75
|
const field = addrForSegs(segs.slice(0, -1));
|
|
76
|
-
// A body has no elements: the parent must name a
|
|
76
|
+
// A body has no elements: the parent must name a field.
|
|
77
77
|
return field?.field != null ? { field, index } : undefined;
|
|
78
78
|
}
|
|
79
79
|
/** `parseDocPath`, with a malformed path as `undefined` rather than a throw. The gate
|
|
80
|
-
* is read
|
|
80
|
+
* is read outside the try: an uninitialized core is not a malformed path, and would
|
|
81
81
|
* otherwise leave here as one. */
|
|
82
82
|
function segsOf(path) {
|
|
83
83
|
const { parseDocPath } = core();
|
package/dist/core/bloom.d.ts
CHANGED
|
@@ -1,37 +1,8 @@
|
|
|
1
|
-
/** The resting state a bloom animates from and back to: the wash, held at zero. */
|
|
2
|
-
export declare function primeWash(el: HTMLElement): void;
|
|
3
|
-
/** What one bloom runs: resolved from the scale, and from the viewer's motion
|
|
4
|
-
* preference. */
|
|
5
|
-
export interface BloomTiming {
|
|
6
|
-
duration: number;
|
|
7
|
-
frames: Keyframe[];
|
|
8
|
-
}
|
|
9
1
|
/**
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
* ONE property, both motion preferences: `--_qm-bloom-dwell` already shortens under
|
|
13
|
-
* `prefers-reduced-motion` in the derivation, so what is left to `matchMedia` here is
|
|
14
|
-
* the SHAPE: a hold and a cut carry no ramps, which is a frame list rather than a
|
|
15
|
-
* number and the one half CSS cannot hand over.
|
|
16
|
-
*
|
|
17
|
-
* Read ONCE PER BATCH, not per element: `getComputedStyle` forces a style recalc and
|
|
18
|
-
* `animate` re-dirties style, so a read inside a loop flushes once per element (~0.15ms
|
|
19
|
-
* each). The rungs inherit, so every element in a batch resolves the same numbers.
|
|
20
|
-
*/
|
|
21
|
-
export declare function bloomTiming(el: HTMLElement): BloomTiming;
|
|
22
|
-
/**
|
|
23
|
-
* Bloom the wash on `el` itself: the caller owns the element and has primed it
|
|
24
|
-
* ({@link primeWash}). Returns the running animation, or `undefined` when there is
|
|
25
|
-
* nothing left to run: `elapsed` past the decay, or an environment without WAAPI.
|
|
26
|
-
* Pass `timing` to share one resolve across a batch.
|
|
27
|
-
*/
|
|
28
|
-
export declare function bloom(el: HTMLElement, elapsed?: number, timing?: BloomTiming): Animation | undefined;
|
|
29
|
-
/**
|
|
30
|
-
* Bloom over `host`'s CONTENT: an inset child, so the wash neither tints the host's
|
|
2
|
+
* Bloom over `host`'s content: an inset child, so the wash neither tints the host's
|
|
31
3
|
* own background nor fades the text under it. `host` must be positioned.
|
|
32
4
|
*
|
|
33
|
-
*
|
|
34
|
-
* must not stack two alphas over it.
|
|
35
|
-
* (editor leaves) are not rebuilt under the animation the way the preview's boxes are.
|
|
5
|
+
* One wash node per host, reused while it lives: two quick landings on the same leaf
|
|
6
|
+
* must not stack two alphas over it.
|
|
36
7
|
*/
|
|
37
8
|
export declare function bloomInside(host: HTMLElement): void;
|
package/dist/core/bloom.js
CHANGED
|
@@ -1,21 +1,11 @@
|
|
|
1
|
-
// The
|
|
2
|
-
// nothing.
|
|
3
|
-
//
|
|
4
|
-
// outlives the hop that caused it is ink the document did not ask for. Both
|
|
5
|
-
// directions land here: the preview's field boxes bloom themselves (they are already
|
|
6
|
-
// empty rects over the page), an editor leaf blooms a transient child over its
|
|
7
|
-
// surface.
|
|
1
|
+
// The arrival wash: a soft accent bloom that rises over a leaf the editor just
|
|
2
|
+
// landed on and decays to nothing. A landing is an event, not a state, so a
|
|
3
|
+
// highlight that outlives the hop that caused it is ink nothing asked for.
|
|
8
4
|
//
|
|
9
|
-
//
|
|
5
|
+
// Opacity is the animated property, never the colour. The wash's alpha is a peak
|
|
10
6
|
// fixed by `--_qm-accent-wash`, so interpolating 0→1→0 keeps the scale in CSS where
|
|
11
7
|
// `color-mix` resolves, instead of in keyframe values a script would have to
|
|
12
8
|
// pre-resolve and re-resolve on a theme change.
|
|
13
|
-
//
|
|
14
|
-
// `elapsed` is what lets a bloom survive the element that carries it. `overlay.ts`
|
|
15
|
-
// rebuilds every box on refresh and the playground recompiles 120ms after each
|
|
16
|
-
// keystroke burst, so a bloom re-STARTED per rebuild would re-bloom continuously
|
|
17
|
-
// while the user writes; a rebuilt node resumes at the offset the old one reached.
|
|
18
|
-
/** Marks a transient wash node: the wash is decoration, never a hit target. */
|
|
19
9
|
const BLOOM_CLASS = 'qm-bloom';
|
|
20
10
|
const WASH = 'var(--_qm-accent-wash)';
|
|
21
11
|
/** The dwell used when the derivation is out of reach (an unstyled root, or jsdom,
|
|
@@ -23,7 +13,7 @@ const WASH = 'var(--_qm-accent-wash)';
|
|
|
23
13
|
* absence of the scale, not a copy of it. */
|
|
24
14
|
const FALLBACK_MS = 1100;
|
|
25
15
|
// Rise fast, hold, then leave slowly: a highlighter's decay, not a linear fade. A
|
|
26
|
-
// keyframe's easing governs the segment that
|
|
16
|
+
// keyframe's easing governs the segment that starts at it. Under reduced motion the
|
|
27
17
|
// ramps are dropped entirely: the wash holds at full for a beat and cuts, which
|
|
28
18
|
// still answers "here" with nothing to track.
|
|
29
19
|
const FRAMES = [
|
|
@@ -33,53 +23,30 @@ const FRAMES = [
|
|
|
33
23
|
{ opacity: 0, offset: 1 }
|
|
34
24
|
];
|
|
35
25
|
const FRAMES_REDUCED = [{ opacity: 1 }, { opacity: 1 }];
|
|
36
|
-
/** The resting state a bloom animates from and back to: the wash, held at zero. */
|
|
37
|
-
export function primeWash(el) {
|
|
38
|
-
Object.assign(el.style, { background: WASH, opacity: '0' });
|
|
39
|
-
}
|
|
40
26
|
/**
|
|
41
|
-
*
|
|
27
|
+
* Run one wash on `el`, resolving the dwell off it so the number is single-sourced
|
|
28
|
+
* in CSS.
|
|
42
29
|
*
|
|
43
|
-
*
|
|
30
|
+
* One property, both motion preferences: `--_qm-bloom-dwell` already shortens under
|
|
44
31
|
* `prefers-reduced-motion` in the derivation, so what is left to `matchMedia` here is
|
|
45
|
-
* the
|
|
32
|
+
* the shape: a hold and a cut carry no ramps, which is a frame list rather than a
|
|
46
33
|
* number and the one half CSS cannot hand over.
|
|
47
|
-
*
|
|
48
|
-
* Read ONCE PER BATCH, not per element: `getComputedStyle` forces a style recalc and
|
|
49
|
-
* `animate` re-dirties style, so a read inside a loop flushes once per element (~0.15ms
|
|
50
|
-
* each). The rungs inherit, so every element in a batch resolves the same numbers.
|
|
51
34
|
*/
|
|
52
|
-
|
|
35
|
+
function bloom(el) {
|
|
36
|
+
if (typeof el.animate !== 'function')
|
|
37
|
+
return undefined;
|
|
53
38
|
const reduced = typeof matchMedia === 'function' && matchMedia('(prefers-reduced-motion: reduce)').matches;
|
|
54
39
|
const raw = getComputedStyle(el).getPropertyValue('--_qm-bloom-dwell').trim();
|
|
55
40
|
const n = Number.parseFloat(raw);
|
|
56
41
|
const duration = !Number.isFinite(n) ? FALLBACK_MS : raw.endsWith('ms') ? n : n * 1000;
|
|
57
|
-
return
|
|
58
|
-
}
|
|
59
|
-
/**
|
|
60
|
-
* Bloom the wash on `el` itself: the caller owns the element and has primed it
|
|
61
|
-
* ({@link primeWash}). Returns the running animation, or `undefined` when there is
|
|
62
|
-
* nothing left to run: `elapsed` past the decay, or an environment without WAAPI.
|
|
63
|
-
* Pass `timing` to share one resolve across a batch.
|
|
64
|
-
*/
|
|
65
|
-
export function bloom(el, elapsed = 0, timing) {
|
|
66
|
-
if (typeof el.animate !== 'function')
|
|
67
|
-
return undefined;
|
|
68
|
-
const { duration, frames } = timing ?? bloomTiming(el);
|
|
69
|
-
if (elapsed >= duration)
|
|
70
|
-
return undefined;
|
|
71
|
-
const anim = el.animate(frames, { duration });
|
|
72
|
-
if (elapsed > 0)
|
|
73
|
-
anim.currentTime = elapsed;
|
|
74
|
-
return anim;
|
|
42
|
+
return el.animate(reduced ? FRAMES_REDUCED : FRAMES, { duration });
|
|
75
43
|
}
|
|
76
44
|
/**
|
|
77
|
-
* Bloom over `host`'s
|
|
45
|
+
* Bloom over `host`'s content: an inset child, so the wash neither tints the host's
|
|
78
46
|
* own background nor fades the text under it. `host` must be positioned.
|
|
79
47
|
*
|
|
80
|
-
*
|
|
81
|
-
* must not stack two alphas over it.
|
|
82
|
-
* (editor leaves) are not rebuilt under the animation the way the preview's boxes are.
|
|
48
|
+
* One wash node per host, reused while it lives: two quick landings on the same leaf
|
|
49
|
+
* must not stack two alphas over it.
|
|
83
50
|
*/
|
|
84
51
|
export function bloomInside(host) {
|
|
85
52
|
let el = host.querySelector(`:scope > .${BLOOM_CLASS}`);
|
|
@@ -99,9 +66,10 @@ export function bloomInside(host) {
|
|
|
99
66
|
position: 'absolute',
|
|
100
67
|
inset: '0',
|
|
101
68
|
pointerEvents: 'none',
|
|
102
|
-
borderRadius: 'inherit'
|
|
69
|
+
borderRadius: 'inherit',
|
|
70
|
+
background: WASH,
|
|
71
|
+
opacity: '0'
|
|
103
72
|
});
|
|
104
|
-
primeWash(el);
|
|
105
73
|
host.appendChild(el);
|
|
106
74
|
}
|
|
107
75
|
const node = el;
|
|
@@ -8,7 +8,7 @@ export interface RangeAnchor {
|
|
|
8
8
|
/**
|
|
9
9
|
* A virtual anchor over one PM range, measured when asked rather than when made.
|
|
10
10
|
*
|
|
11
|
-
* The measure is
|
|
11
|
+
* The measure is total. `coordsAtPos` reads layout, and layout can be gone: a
|
|
12
12
|
* position the document has since invalidated, a leaf inside a collapsed section, a
|
|
13
13
|
* view torn down between a scroll firing and the measure running. `autoUpdate` calls
|
|
14
14
|
* this at moments none of its callers choose, so an unmeasurable anchor returns the
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* A virtual anchor over one PM range, measured when asked rather than when made.
|
|
3
3
|
*
|
|
4
|
-
* The measure is
|
|
4
|
+
* The measure is total. `coordsAtPos` reads layout, and layout can be gone: a
|
|
5
5
|
* position the document has since invalidated, a leaf inside a collapsed section, a
|
|
6
6
|
* view torn down between a scroll firing and the measure running. `autoUpdate` calls
|
|
7
7
|
* this at moments none of its callers choose, so an unmeasurable anchor returns the
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { type Command } from 'prosemirror-state';
|
|
2
|
+
import type { Schema } from 'prosemirror-model';
|
|
3
|
+
/**
|
|
4
|
+
* The block-atom bindings for a block-schema leaf: `{}` for the inline/plaintext
|
|
5
|
+
* schemas, which hold no block to be against.
|
|
6
|
+
*
|
|
7
|
+
* Both keys are the same command on opposite sides, because what a delete against an
|
|
8
|
+
* atom means does not depend on which side the caret approached from.
|
|
9
|
+
*/
|
|
10
|
+
export declare function atomKeymap(schema: Schema): Record<string, Command>;
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
// The block-atom link of the body's Backspace/Delete chains: a delete against an
|
|
2
|
+
// island or a divider selects it, and the press after that deletes it.
|
|
3
|
+
//
|
|
4
|
+
// This is the neighbour half of the rule the leaf already holds for a key typed over a
|
|
5
|
+
// selection (CODEC §"The table island"): a selection is the subject of the next
|
|
6
|
+
// command, never a thing armed for replacement. Without it the base keymap's
|
|
7
|
+
// `joinBackward` / `joinForward` reach their "the neighbour is an atom, delete it" arm
|
|
8
|
+
// and a table goes in one keystroke, from the paragraph a table insert parks the caret
|
|
9
|
+
// in — with nothing drawn first to say what is about to go.
|
|
10
|
+
//
|
|
11
|
+
// It runs last of the links, after the list ones: a Backspace at the start of a list's
|
|
12
|
+
// first item lifts the item, and that stays the item's answer whatever sits above the
|
|
13
|
+
// list.
|
|
14
|
+
import { NodeSelection, TextSelection } from 'prosemirror-state';
|
|
15
|
+
/** The caret, when it sits at a textblock edge with an empty selection: the only place
|
|
16
|
+
* a delete is about a neighbour rather than about a character. `null` everywhere else,
|
|
17
|
+
* which is where the key keeps its ordinary meaning. */
|
|
18
|
+
function atEdge(state, side) {
|
|
19
|
+
const { selection } = state;
|
|
20
|
+
if (!(selection instanceof TextSelection) || !selection.empty)
|
|
21
|
+
return null;
|
|
22
|
+
const $head = selection.$head;
|
|
23
|
+
const at = side < 0 ? 0 : $head.parent.content.size;
|
|
24
|
+
return $head.parentOffset === at ? $head : null;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* The cut the caret's block sits against on `side`: the shallowest boundary with a
|
|
28
|
+
* sibling beyond it. `prosemirror-commands` walks the same one to find the node a join
|
|
29
|
+
* would reach; this reads it to find the node a delete would take.
|
|
30
|
+
*/
|
|
31
|
+
function cutAt($pos, side) {
|
|
32
|
+
if ($pos.parent.type.spec.isolating)
|
|
33
|
+
return null;
|
|
34
|
+
for (let d = $pos.depth - 1; d >= 0; d--) {
|
|
35
|
+
if (side < 0) {
|
|
36
|
+
if ($pos.index(d) > 0)
|
|
37
|
+
return $pos.doc.resolve($pos.before(d + 1));
|
|
38
|
+
}
|
|
39
|
+
else if ($pos.index(d) + 1 < $pos.node(d).childCount) {
|
|
40
|
+
return $pos.doc.resolve($pos.after(d + 1));
|
|
41
|
+
}
|
|
42
|
+
if ($pos.node(d).type.spec.isolating)
|
|
43
|
+
break;
|
|
44
|
+
}
|
|
45
|
+
return null;
|
|
46
|
+
}
|
|
47
|
+
/** Select the block atom the caret is against, rather than deleting it. Declines when
|
|
48
|
+
* the neighbour is anything a caret can enter, so a paragraph, a heading, a quote and
|
|
49
|
+
* a list all keep the join the base keymap gives them. */
|
|
50
|
+
function selectAtom(side) {
|
|
51
|
+
return (state, dispatch) => {
|
|
52
|
+
const $head = atEdge(state, side);
|
|
53
|
+
const $cut = $head && cutAt($head, side);
|
|
54
|
+
if (!$cut)
|
|
55
|
+
return false;
|
|
56
|
+
const node = side < 0 ? $cut.nodeBefore : $cut.nodeAfter;
|
|
57
|
+
if (!node || !node.isAtom || !node.isBlock || !NodeSelection.isSelectable(node))
|
|
58
|
+
return false;
|
|
59
|
+
const at = side < 0 ? $cut.pos - node.nodeSize : $cut.pos;
|
|
60
|
+
dispatch?.(state.tr.setSelection(NodeSelection.create(state.doc, at)).scrollIntoView());
|
|
61
|
+
return true;
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* The block-atom bindings for a block-schema leaf: `{}` for the inline/plaintext
|
|
66
|
+
* schemas, which hold no block to be against.
|
|
67
|
+
*
|
|
68
|
+
* Both keys are the same command on opposite sides, because what a delete against an
|
|
69
|
+
* atom means does not depend on which side the caret approached from.
|
|
70
|
+
*/
|
|
71
|
+
export function atomKeymap(schema) {
|
|
72
|
+
if (!schema.nodes.island_block)
|
|
73
|
+
return {};
|
|
74
|
+
return {
|
|
75
|
+
Backspace: selectAtom(-1),
|
|
76
|
+
Delete: selectAtom(1)
|
|
77
|
+
};
|
|
78
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { DOMParser as PMDOMParser, Slice } from 'prosemirror-model';
|
|
2
|
+
import type { Node as PMNode, Schema } from 'prosemirror-model';
|
|
3
|
+
import { Plugin } from 'prosemirror-state';
|
|
4
|
+
import type { TableCell, TableProps } from '@quillmark/wasm';
|
|
5
|
+
import { type TableAlign } from './table.js';
|
|
6
|
+
/** What a clipboard event hands over, which is all either arm needs of it: jsdom has no
|
|
7
|
+
* `DataTransfer`, and the suite drives these paths with the two methods themselves. */
|
|
8
|
+
interface Clipboard {
|
|
9
|
+
getData: (type: string) => string;
|
|
10
|
+
setData: (type: string, data: string) => void;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* A `<table>` element as props. `rowspan` is not carried — a merged cell is not a
|
|
14
|
+
* rectangle, so a table with one arrives with the rows below it short and
|
|
15
|
+
* {@link normalizeTable} pads them at the end. `colspan` is, as the empty cells it
|
|
16
|
+
* covers, so the columns after it keep their index.
|
|
17
|
+
*
|
|
18
|
+
* Only the table's own rows: a `<tr>` inside a nested table belongs to a cell this
|
|
19
|
+
* reader is about to flatten, not to a rank of this one.
|
|
20
|
+
*/
|
|
21
|
+
export declare function tableFromDOM(dom: Element): TableProps;
|
|
22
|
+
/** The table a paste is carrying, or `undefined` for a paste that holds none. The first
|
|
23
|
+
* one: a clipboard holding a table among other blocks is a document paste, and only a
|
|
24
|
+
* surface that takes nothing but a table (a cell) reads it this way. */
|
|
25
|
+
export declare function tableFromClipboard(data: Clipboard | null | undefined): TableProps | undefined;
|
|
26
|
+
/**
|
|
27
|
+
* A rectangle as a `<table>`: the wire the HTML arm writes and reads. Its first row is
|
|
28
|
+
* a `<th>` row whatever row of the source it came from, that being the row index 0 a
|
|
29
|
+
* paste lands it at.
|
|
30
|
+
*/
|
|
31
|
+
export declare function tableToDOM(block: TableCell[][], aligns: TableAlign[], owner: Document): Element;
|
|
32
|
+
/**
|
|
33
|
+
* A rectangle as pipe rows, delimiter row included: what a plain-text target reads as a
|
|
34
|
+
* table and what markdown carries. The delimiter follows the first row for the reason
|
|
35
|
+
* that row serializes as `<th>` — a pasted rectangle's row 0 is a header.
|
|
36
|
+
*/
|
|
37
|
+
export declare function tableToPipe(block: TableCell[][], aligns: TableAlign[]): string;
|
|
38
|
+
/** Put a rectangle on the clipboard, both arms. The caller is a `copy` / `cut` handler
|
|
39
|
+
* over a selection its own surface holds, so the rectangle arrives already cut out and
|
|
40
|
+
* claiming the event is the caller's. */
|
|
41
|
+
export declare function writeRectangle(data: Clipboard, block: TableCell[][], aligns: TableAlign[], owner: Document): void;
|
|
42
|
+
/** The clipboard's parser: the schema's own rules with the table rule ahead of them.
|
|
43
|
+
* Ahead, because a `<table>` otherwise matches nothing and its cells' text falls
|
|
44
|
+
* through to the enclosing block. */
|
|
45
|
+
export declare function tableClipboardParser(schema: Schema): PMDOMParser;
|
|
46
|
+
/** Mint an id for every island a paste is carrying. An id is part of the document's
|
|
47
|
+
* canonical bytes and the channel that addresses an island (CODEC §Islands), so a
|
|
48
|
+
* pasted island with none reaches the store as an insert nothing can name afterwards.
|
|
49
|
+
* One minter for the whole slice, or a paste placing two tables hands them one id. */
|
|
50
|
+
export declare function stampIslandIds(slice: Slice, doc: PMNode): Slice;
|
|
51
|
+
/**
|
|
52
|
+
* The block leaf's clipboard plugin: the table rule on the way in, and the id mint over
|
|
53
|
+
* what it produced. A `clipboardParser` rather than the schema's own `parseDOM`,
|
|
54
|
+
* because what a `<table>` in the DOM means is a question only the clipboard asks — the
|
|
55
|
+
* schema's parse rules are also what a re-parse of the editor's own DOM runs through,
|
|
56
|
+
* where an island is already a node and a `<table>` is the chrome drawing one.
|
|
57
|
+
*/
|
|
58
|
+
export declare function tableClipboardPlugin(schema: Schema): Plugin;
|
|
59
|
+
export {};
|