@quillmark/svelte 0.20.0 → 0.21.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/THEMING.md +1 -1
- package/dist/core/codec/atoms.js +25 -10
- package/dist/core/codec/blocks.d.ts +5 -5
- package/dist/core/codec/blocks.js +23 -6
- package/dist/core/codec/code.d.ts +3 -3
- package/dist/core/codec/code.js +51 -21
- package/dist/core/codec/field.d.ts +9 -7
- package/dist/core/codec/field.js +22 -11
- package/dist/core/codec/inputrules.d.ts +4 -0
- package/dist/core/codec/inputrules.js +43 -5
- package/dist/core/codec/link-paste.d.ts +11 -0
- package/dist/core/codec/link-paste.js +32 -0
- package/dist/core/codec/lists.d.ts +2 -2
- package/dist/core/codec/lists.js +66 -26
- package/dist/core/codec/prose.css +71 -43
- package/dist/core/codec/schema.js +55 -11
- package/dist/core/codec/slash.js +0 -0
- package/dist/core/codec/table-view.d.ts +5 -0
- package/dist/core/codec/table-view.js +232 -103
- package/dist/core/rungs.d.ts +6 -0
- package/dist/core/rungs.js +11 -0
- package/dist/core/theme.css +24 -12
- package/dist/visual/ArrayField.svelte +168 -48
- package/dist/visual/BooleanField.svelte +12 -1
- package/dist/visual/Card.svelte +32 -20
- package/dist/visual/Card.svelte.d.ts +1 -0
- package/dist/visual/CardControls.svelte +15 -15
- package/dist/visual/DateField.svelte +93 -64
- package/dist/visual/EnumField.svelte +9 -4
- package/dist/visual/FieldHint.svelte +22 -5
- package/dist/visual/FieldLabel.svelte +3 -3
- package/dist/visual/FormatPopover.svelte +25 -9
- package/dist/visual/MatrixField.svelte +10 -7
- package/dist/visual/ObjectField.svelte +2 -4
- package/dist/visual/TipsCard.svelte +2 -5
- package/dist/visual/VisualEditorInner.svelte +108 -26
- package/dist/visual/controls.css +2 -1
- package/dist/visual/icons/Icon.svelte +3 -2
- package/dist/visual/icons/Icon.svelte.d.ts +3 -2
- package/dist/visual/icons/nodes.d.ts +25 -0
- package/dist/visual/icons/nodes.js +8 -0
- package/dist/visual/motion.d.ts +25 -3
- package/dist/visual/motion.js +58 -9
- package/dist/visual/strings.d.ts +6 -5
- package/dist/visual/strings.js +3 -3
- package/package.json +8 -8
|
@@ -33,6 +33,7 @@
|
|
|
33
33
|
import { errorMessage, reportError } from '../core/errors.js';
|
|
34
34
|
import { bloomInside } from '../core/bloom.js';
|
|
35
35
|
import { createLifespan } from '../core/teardown.js';
|
|
36
|
+
import { spacePx } from '../core/rungs.js';
|
|
36
37
|
import type {
|
|
37
38
|
Addr,
|
|
38
39
|
CardAddr,
|
|
@@ -80,11 +81,12 @@
|
|
|
80
81
|
} from './diagnostics.js';
|
|
81
82
|
import { fieldDomIds, groupPanelId } from './domid.js';
|
|
82
83
|
import { createLeafRegistry, type ActiveProse, type FieldControl } from './leaves.js';
|
|
83
|
-
import { reorder, reorderArm, reorderTrips } from './motion.js';
|
|
84
|
+
import { reorder, reorderArm, reorderTrips, scrollSettled, slot } from './motion.js';
|
|
84
85
|
import { tipsChannel } from './tips.js';
|
|
85
86
|
import { patchEditorExt } from './ext.js';
|
|
86
87
|
import { raiseGuidance } from './guidance.js';
|
|
87
88
|
import Card from './Card.svelte';
|
|
89
|
+
import Icon from './icons/Icon.svelte';
|
|
88
90
|
import TipsCard from './TipsCard.svelte';
|
|
89
91
|
import FormatPopover from './FormatPopover.svelte';
|
|
90
92
|
// The add trigger and the kind menu draw shared recipes; a component carrying a
|
|
@@ -194,6 +196,7 @@
|
|
|
194
196
|
type CardHandle = {
|
|
195
197
|
revealLeaf(key: string): void;
|
|
196
198
|
scrollIntoViewCard(block: ScrollLogicalPosition): void;
|
|
199
|
+
focusHeader(): void;
|
|
197
200
|
};
|
|
198
201
|
let mainCard = $state<CardHandle | undefined>(undefined);
|
|
199
202
|
let cardRefs = $state<(CardHandle | undefined)[]>([]);
|
|
@@ -377,10 +380,10 @@
|
|
|
377
380
|
* naming one id, on different terms — `center` against `nearest`.
|
|
378
381
|
*/
|
|
379
382
|
let scrollTicket = 0;
|
|
380
|
-
async function scrollCardIntoView(id: string, block: ScrollLogicalPosition): Promise<
|
|
383
|
+
async function scrollCardIntoView(id: string, block: ScrollLogicalPosition): Promise<boolean> {
|
|
381
384
|
const ticket = ++scrollTicket;
|
|
382
|
-
if (!(await span.resumes(tick()))) return;
|
|
383
|
-
if (ticket !== scrollTicket) return;
|
|
385
|
+
if (!(await span.resumes(tick()))) return false;
|
|
386
|
+
if (ticket !== scrollTicket) return false;
|
|
384
387
|
// A reorder is running by this flush and holds the slot at its pre-move position
|
|
385
388
|
// for the length of the trip (`motion.ts`), so asking now measures the box the
|
|
386
389
|
// card is leaving — where a `nearest` would take the stack back to for the card
|
|
@@ -390,26 +393,33 @@
|
|
|
390
393
|
const slot = slotEls[cardIndexOf(id)];
|
|
391
394
|
const trips = slot ? reorderTrips(slot) : [];
|
|
392
395
|
if (trips.length) {
|
|
393
|
-
if (!(await span.resumes(Promise.allSettled(trips.map((trip) => trip.finished)))))
|
|
394
|
-
|
|
396
|
+
if (!(await span.resumes(Promise.allSettled(trips.map((trip) => trip.finished)))))
|
|
397
|
+
return false;
|
|
398
|
+
if (ticket !== scrollTicket) return false;
|
|
395
399
|
}
|
|
396
400
|
const i = cardIndexOf(id);
|
|
397
|
-
if (i
|
|
401
|
+
if (i < 0) return false;
|
|
402
|
+
cardRefs[i]?.scrollIntoViewCard(block);
|
|
403
|
+
return true;
|
|
398
404
|
}
|
|
399
405
|
/** Returns the new card's session key, or `undefined` when the kind seeds nothing
|
|
400
|
-
* or the insert threw: what `insertCard` hands a host that wants to track it.
|
|
401
|
-
|
|
406
|
+
* or the insert threw: what `insertCard` hands a host that wants to track it. The
|
|
407
|
+
* strip's own add `land`s focus in the new card; a host's leaves focus where it is. */
|
|
408
|
+
function addCard(atIndex: number, kind: string, land = false): CardId | undefined {
|
|
402
409
|
try {
|
|
403
410
|
const overlay = doc.seedOverlay(kind);
|
|
404
411
|
const card = quill.seedCard(kind, overlay);
|
|
405
412
|
if (!card) return undefined;
|
|
413
|
+
const from = document.activeElement;
|
|
406
414
|
doc.insertCard(card, atIndex);
|
|
407
415
|
const id = seq.next();
|
|
416
|
+
arm.arm();
|
|
408
417
|
cardIds = [...cardIds.slice(0, atIndex), id, ...cardIds.slice(atIndex)];
|
|
409
418
|
bump('structure', id, atIndex);
|
|
410
419
|
// Centred: the new card is the subject, and the neighbours it landed between
|
|
411
420
|
// are what say where it went.
|
|
412
|
-
|
|
421
|
+
const scrolled = scrollCardIntoView(id, 'center');
|
|
422
|
+
if (land) void landInCard(id, scrolled, from);
|
|
413
423
|
return id;
|
|
414
424
|
} catch (e) {
|
|
415
425
|
reportError(onError, {
|
|
@@ -421,8 +431,39 @@
|
|
|
421
431
|
return undefined;
|
|
422
432
|
}
|
|
423
433
|
}
|
|
424
|
-
|
|
425
|
-
|
|
434
|
+
/**
|
|
435
|
+
* Focus the card the strip just added: its first field, else its body, else its title.
|
|
436
|
+
*
|
|
437
|
+
* After the centring scroll has run rather than beside it: a control's `focus()`
|
|
438
|
+
* takes the browser's own scroll, which cuts a smooth one short wherever it had
|
|
439
|
+
* reached. A focus that went elsewhere while the scroll ran was the user's, and stays.
|
|
440
|
+
*/
|
|
441
|
+
async function landInCard(
|
|
442
|
+
id: CardId,
|
|
443
|
+
scrolled: Promise<boolean>,
|
|
444
|
+
from: Element | null
|
|
445
|
+
): Promise<void> {
|
|
446
|
+
if (!(await scrolled) || !(await span.resumes(scrollSettled()))) return;
|
|
447
|
+
const now = document.activeElement;
|
|
448
|
+
if (now !== from && now !== document.body) return;
|
|
449
|
+
const at = cardIndexOf(id);
|
|
450
|
+
const shown = model.cards[at];
|
|
451
|
+
if (!shown) return;
|
|
452
|
+
const first = (shown.sections.find((s) => s.group == null) ?? shown.sections[0])?.fields[0];
|
|
453
|
+
const path = first || shown.hasBody ? pathFor({ card: at, field: first?.name }) : undefined;
|
|
454
|
+
const found = path && (await revealLeaf(path));
|
|
455
|
+
if (found) await land(found, undefined);
|
|
456
|
+
else cardRefs[cardIndexOf(id)]?.focusHeader();
|
|
457
|
+
}
|
|
458
|
+
// A kind picked off the menu lands focus in the card it added, so the menu's close
|
|
459
|
+
// hands none back to its trigger; an Escape out of the menu still does.
|
|
460
|
+
let picked = false;
|
|
461
|
+
function addPicked(atIndex: number, kind: string): void {
|
|
462
|
+
picked = addCard(atIndex, kind, true) != null;
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
// The list gesture's arming window (`motion.ts`): the reconcile that moves a slot is
|
|
466
|
+
// the trip, and every other reconcile that happens to move one is not.
|
|
426
467
|
const arm = reorderArm();
|
|
427
468
|
span.onEnd(arm.cancel);
|
|
428
469
|
|
|
@@ -445,7 +486,12 @@
|
|
|
445
486
|
function removeCardById(id: string): void {
|
|
446
487
|
const i = cardIndexOf(id);
|
|
447
488
|
if (i < 0) return;
|
|
489
|
+
// Held visibly, which a pointer's press on delete is not: some engines focus a
|
|
490
|
+
// pressed button, and the hand-on is the keyboard's.
|
|
491
|
+
const focused = document.activeElement;
|
|
492
|
+
const held = !!focused && !!slotEls[i]?.contains(focused) && focused.matches(':focus-visible');
|
|
448
493
|
doc.removeCard(i);
|
|
494
|
+
arm.arm();
|
|
449
495
|
cardIds = cardIds.filter((_, k) => k !== i);
|
|
450
496
|
if (activeCardId === id) {
|
|
451
497
|
activeCardId = undefined;
|
|
@@ -460,6 +506,15 @@
|
|
|
460
506
|
// No addr: the removed card has none left and every survivor's shifted. The
|
|
461
507
|
// stack changed, not a leaf, and the id is the only handle the removal leaves.
|
|
462
508
|
bump('structure', id);
|
|
509
|
+
if (held) void focusAfterRemove(i);
|
|
510
|
+
}
|
|
511
|
+
/** Hand the focus a removed card held to the title of the card before it, else the
|
|
512
|
+
* one that took its place, else the strip that adds one: a focused node the flush
|
|
513
|
+
* drops leaves the focus on the page's body, and the keyboard's place with it. */
|
|
514
|
+
async function focusAfterRemove(at: number): Promise<void> {
|
|
515
|
+
if (!(await span.resumes(tick()))) return;
|
|
516
|
+
if (cardIds.length) cardRefs[Math.max(at - 1, 0)]?.focusHeader();
|
|
517
|
+
else rootEl?.querySelector<HTMLElement>('.qm-add-btn')?.focus();
|
|
463
518
|
}
|
|
464
519
|
function retypeCardById(id: string, kind: string): void {
|
|
465
520
|
const i = cardIndexOf(id);
|
|
@@ -481,7 +536,10 @@
|
|
|
481
536
|
});
|
|
482
537
|
}
|
|
483
538
|
}
|
|
539
|
+
/** A title's blur can fire as the surface is torn down, against a document that may be
|
|
540
|
+
* freed. */
|
|
484
541
|
function renameCardById(id: string, title: string): void {
|
|
542
|
+
if (!span.alive) return;
|
|
485
543
|
const i = cardIndexOf(id);
|
|
486
544
|
if (i < 0) return;
|
|
487
545
|
patchEditorExt(doc, { card: i }, { title });
|
|
@@ -892,7 +950,7 @@
|
|
|
892
950
|
rather than being slid across. It is also the shape `animate:` asks for, being the
|
|
893
951
|
keyed block's only child. -->
|
|
894
952
|
{#each model.cards as c, i (c.id)}
|
|
895
|
-
<div class="qm-card-slot" bind:this={slotEls[i]} animate:reorder={arm.armed}>
|
|
953
|
+
<div class="qm-card-slot" bind:this={slotEls[i]} animate:reorder={arm.armed} in:slot out:slot>
|
|
896
954
|
<Card
|
|
897
955
|
bind:this={cardRefs[i]}
|
|
898
956
|
card={titled(c)}
|
|
@@ -925,9 +983,10 @@
|
|
|
925
983
|
{#snippet addAffordance(atIndex: number)}
|
|
926
984
|
{#if kinds.length}
|
|
927
985
|
<!-- The strip past the last card is the append point, the one a reader looks for
|
|
928
|
-
rather than finds by position, so it alone states itself in words at rest.
|
|
929
|
-
|
|
930
|
-
|
|
986
|
+
rather than finds by position, so it alone states itself in words at rest. A gap
|
|
987
|
+
between cards carries a plus where the words would be: the strip is the gap, the
|
|
988
|
+
pill it fills on hover is the space the new card takes, and the plus says what a
|
|
989
|
+
press there does. -->
|
|
931
990
|
{@const marked = atIndex === model.cards.length}
|
|
932
991
|
<div class={['qm-add-card', { 'qm-add-card-marked': marked }]}>
|
|
933
992
|
<!-- Marked, the words are the accessible name and no `aria-label` doubles
|
|
@@ -938,7 +997,8 @@
|
|
|
938
997
|
type="button"
|
|
939
998
|
class="qm-add-btn qm-add-affordance qm-tap-floor"
|
|
940
999
|
aria-label={marked ? undefined : merged.addCard}
|
|
941
|
-
onclick={() => addCard(atIndex, kinds[0])}
|
|
1000
|
+
onclick={() => addCard(atIndex, kinds[0], true)}
|
|
1001
|
+
>{#if marked}{merged.addCard}{:else}<Icon name="plus" class="qm-add-glyph" />{/if}</button
|
|
942
1002
|
>
|
|
943
1003
|
{:else}
|
|
944
1004
|
<!-- Multi-kind add: pick the kind, then seed + insert. A menu rather than a
|
|
@@ -950,10 +1010,20 @@
|
|
|
950
1010
|
<DropdownMenu.Trigger
|
|
951
1011
|
class="qm-add-btn qm-add-affordance qm-tap-floor"
|
|
952
1012
|
aria-label={marked ? undefined : merged.addCard}
|
|
953
|
-
>{marked
|
|
1013
|
+
>{#if marked}{merged.addCard}{:else}<Icon
|
|
1014
|
+
name="plus"
|
|
1015
|
+
class="qm-add-glyph"
|
|
1016
|
+
/>{/if}</DropdownMenu.Trigger
|
|
954
1017
|
>
|
|
955
1018
|
<DropdownMenu.Portal to={rootEl}>
|
|
956
|
-
<DropdownMenu.Content
|
|
1019
|
+
<DropdownMenu.Content
|
|
1020
|
+
collisionBoundary={rootEl ?? []}
|
|
1021
|
+
sideOffset={spacePx(rootEl)}
|
|
1022
|
+
onCloseAutoFocus={(e) => {
|
|
1023
|
+
if (picked) e.preventDefault();
|
|
1024
|
+
picked = false;
|
|
1025
|
+
}}
|
|
1026
|
+
>
|
|
957
1027
|
<!-- Portalled out of the row but into the stack's root, and carrying the
|
|
958
1028
|
marker itself: floating is still a detached subtree to the
|
|
959
1029
|
derivation, like FormatPopover and the enum listbox. The root is the
|
|
@@ -961,7 +1031,7 @@
|
|
|
961
1031
|
the surface where the consumer scrolls one. -->
|
|
962
1032
|
<div class="qm-menu-surface" data-qm-root>
|
|
963
1033
|
{#each kinds as k (k)}
|
|
964
|
-
<DropdownMenu.Item class="qm-menu-item" onSelect={() =>
|
|
1034
|
+
<DropdownMenu.Item class="qm-menu-item" onSelect={() => addPicked(atIndex, k)}
|
|
965
1035
|
>{kindTitle(quill.schema.card_kinds?.[k], k)}</DropdownMenu.Item
|
|
966
1036
|
>
|
|
967
1037
|
{/each}
|
|
@@ -1072,9 +1142,10 @@
|
|
|
1072
1142
|
/* Unboxed, like every button, and not dashed: a dashed edge is the placeholder idiom
|
|
1073
1143
|
("nothing is here yet"), which on a button reads as disabled or as a drop target. It
|
|
1074
1144
|
stays honest in one place, the un-schemable card (`Card.svelte`), which is a state
|
|
1075
|
-
rather than a control. A
|
|
1076
|
-
what it shows: the pill fills the strip because the strip is what was
|
|
1077
|
-
the strip is the gap, so the fill is the card-to-be drawn where it will
|
|
1145
|
+
rather than a control. A gap's trigger rests invisible, so the fill and the plus are
|
|
1146
|
+
the whole of what it shows: the pill fills the strip because the strip is what was
|
|
1147
|
+
pressed, and the strip is the gap, so the fill is the card-to-be drawn where it will
|
|
1148
|
+
land.
|
|
1078
1149
|
|
|
1079
1150
|
No inset of its own: the pill and the gap are the same rectangle, and a bare strip is
|
|
1080
1151
|
a band of gutter, so its height is a rhythm rung. The press floor the box gives up is
|
|
@@ -1097,8 +1168,15 @@
|
|
|
1097
1168
|
color: var(--_qm-ink-label);
|
|
1098
1169
|
opacity: 0;
|
|
1099
1170
|
transition:
|
|
1100
|
-
opacity var(--_qm-
|
|
1101
|
-
background-color var(--_qm-
|
|
1171
|
+
opacity var(--_qm-duration-fast) var(--_qm-ease-reverse),
|
|
1172
|
+
background-color var(--_qm-duration-fast) var(--_qm-ease-reverse);
|
|
1173
|
+
}
|
|
1174
|
+
/* The pill's own height, so a turned-down `--qm-space` shrinks the glyph with the
|
|
1175
|
+
strip rather than spilling it past the pill. */
|
|
1176
|
+
.qm-add-card :global(.qm-add-glyph) {
|
|
1177
|
+
height: 100%;
|
|
1178
|
+
width: auto;
|
|
1179
|
+
aspect-ratio: 1;
|
|
1102
1180
|
}
|
|
1103
1181
|
/* The strip that holds words at rest takes the height that centres them, and rests dim
|
|
1104
1182
|
rather than absent, having something to be seen. */
|
|
@@ -1111,11 +1189,15 @@
|
|
|
1111
1189
|
/* One arrival, three ways in. Keyboard focus, because the fill is the only thing that
|
|
1112
1190
|
reads as the trigger on a bare strip; and an open menu, because it portals out of
|
|
1113
1191
|
the strip, so a pointer moving onto an item has left the row whose hover was drawing
|
|
1114
|
-
the pill the menu hangs from.
|
|
1192
|
+
the pill the menu hangs from. The arrival takes the long trip and the leave the fast
|
|
1193
|
+
rung, a transition being the state's it ends in. */
|
|
1115
1194
|
.qm-add-card :global(.qm-add-btn:hover),
|
|
1116
1195
|
.qm-add-card :global(.qm-add-btn:focus-visible),
|
|
1117
1196
|
.qm-add-card :global(.qm-add-btn[data-state='open']) {
|
|
1118
1197
|
opacity: 1;
|
|
1119
1198
|
background: var(--_qm-add-fill);
|
|
1199
|
+
transition:
|
|
1200
|
+
opacity var(--_qm-reveal),
|
|
1201
|
+
background-color var(--_qm-reveal);
|
|
1120
1202
|
}
|
|
1121
1203
|
</style>
|
package/dist/visual/controls.css
CHANGED
|
@@ -9,8 +9,9 @@
|
|
|
9
9
|
|
|
10
10
|
interface Props extends Omit<SVGAttributes<SVGSVGElement>, 'name'> {
|
|
11
11
|
name: IconName;
|
|
12
|
-
/** The frame's edge, in px. A chip sizes the glyph from
|
|
13
|
-
* in CSS, which beats this attribute; callers
|
|
12
|
+
/** The frame's edge, in px. A chip or a glyph button sizes the glyph from
|
|
13
|
+
* `--_qm-glyph-control` in CSS, which beats this attribute; other callers pass
|
|
14
|
+
* a size. */
|
|
14
15
|
size?: number;
|
|
15
16
|
}
|
|
16
17
|
let { name, size = 24, ...rest }: Props = $props();
|
|
@@ -2,8 +2,9 @@ import { type IconName } from './nodes.js';
|
|
|
2
2
|
import type { SVGAttributes } from 'svelte/elements';
|
|
3
3
|
interface Props extends Omit<SVGAttributes<SVGSVGElement>, 'name'> {
|
|
4
4
|
name: IconName;
|
|
5
|
-
/** The frame's edge, in px. A chip sizes the glyph from
|
|
6
|
-
* in CSS, which beats this attribute; callers
|
|
5
|
+
/** The frame's edge, in px. A chip or a glyph button sizes the glyph from
|
|
6
|
+
* `--_qm-glyph-control` in CSS, which beats this attribute; other callers pass
|
|
7
|
+
* a size. */
|
|
7
8
|
size?: number;
|
|
8
9
|
}
|
|
9
10
|
declare const Icon: import("svelte").Component<Props, {}, "">;
|
|
@@ -15,6 +15,31 @@ export declare const ICONS: {
|
|
|
15
15
|
readonly 'chevron-up': readonly [readonly ["path", {
|
|
16
16
|
readonly d: "m18 15-6-6-6 6";
|
|
17
17
|
}]];
|
|
18
|
+
readonly 'grip-vertical': readonly [readonly ["circle", {
|
|
19
|
+
readonly cx: "9";
|
|
20
|
+
readonly cy: "12";
|
|
21
|
+
readonly r: "1";
|
|
22
|
+
}], readonly ["circle", {
|
|
23
|
+
readonly cx: "9";
|
|
24
|
+
readonly cy: "5";
|
|
25
|
+
readonly r: "1";
|
|
26
|
+
}], readonly ["circle", {
|
|
27
|
+
readonly cx: "9";
|
|
28
|
+
readonly cy: "19";
|
|
29
|
+
readonly r: "1";
|
|
30
|
+
}], readonly ["circle", {
|
|
31
|
+
readonly cx: "15";
|
|
32
|
+
readonly cy: "12";
|
|
33
|
+
readonly r: "1";
|
|
34
|
+
}], readonly ["circle", {
|
|
35
|
+
readonly cx: "15";
|
|
36
|
+
readonly cy: "5";
|
|
37
|
+
readonly r: "1";
|
|
38
|
+
}], readonly ["circle", {
|
|
39
|
+
readonly cx: "15";
|
|
40
|
+
readonly cy: "19";
|
|
41
|
+
readonly r: "1";
|
|
42
|
+
}]];
|
|
18
43
|
readonly bold: readonly [readonly ["path", {
|
|
19
44
|
readonly d: "M6 12h9a4 4 0 0 1 0 8H7a1 1 0 0 1-1-1V5a1 1 0 0 1 1-1h7a4 4 0 0 1 0 8";
|
|
20
45
|
}]];
|
|
@@ -14,6 +14,14 @@ export const ICONS = {
|
|
|
14
14
|
],
|
|
15
15
|
'chevron-down': [['path', { d: 'm6 9 6 6 6-6' }]],
|
|
16
16
|
'chevron-up': [['path', { d: 'm18 15-6-6-6 6' }]],
|
|
17
|
+
'grip-vertical': [
|
|
18
|
+
['circle', { cx: '9', cy: '12', r: '1' }],
|
|
19
|
+
['circle', { cx: '9', cy: '5', r: '1' }],
|
|
20
|
+
['circle', { cx: '9', cy: '19', r: '1' }],
|
|
21
|
+
['circle', { cx: '15', cy: '12', r: '1' }],
|
|
22
|
+
['circle', { cx: '15', cy: '5', r: '1' }],
|
|
23
|
+
['circle', { cx: '15', cy: '19', r: '1' }]
|
|
24
|
+
],
|
|
17
25
|
bold: [['path', { d: 'M6 12h9a4 4 0 0 1 0 8H7a1 1 0 0 1-1-1V5a1 1 0 0 1 1-1h7a4 4 0 0 1 0 8' }]],
|
|
18
26
|
italic: [
|
|
19
27
|
['line', { x1: '19', x2: '10', y1: '4', y2: '4' }],
|
package/dist/visual/motion.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { AnimationConfig } from 'svelte/animate';
|
|
2
|
+
import type { TransitionConfig } from 'svelte/transition';
|
|
2
3
|
/** The trips this module is running on `node`. A run holds the slot at its pre-move
|
|
3
4
|
* position for its whole length, so anything that measures the node's box waits on
|
|
4
5
|
* these rather than reading the one a transform is holding. */
|
|
@@ -23,9 +24,11 @@ export declare function reorderArm(): ReorderArm;
|
|
|
23
24
|
* `armed` is why this is the reorder and not every reconcile: `animate:` fires wherever
|
|
24
25
|
* a slot's rect moved, and a card growing under the caret moves every slot below it,
|
|
25
26
|
* which is a layout change with no trip in it and one that would draw those slots over
|
|
26
|
-
* the content that displaced them for the length of the run. So
|
|
27
|
-
* arms the gesture and the frame it lands in disarms it, which is the
|
|
28
|
-
* read off the endpoints: two positions in the list, not two positions on
|
|
27
|
+
* the content that displaced them for the length of the run. So a command that moves
|
|
28
|
+
* slots in the list arms the gesture and the frame it lands in disarms it, which is the
|
|
29
|
+
* admission test read off the endpoints: two positions in the list, not two positions on
|
|
30
|
+
* the page. A move is one; so, in the card stack, are an insert and a removal, which move
|
|
31
|
+
* every slot past them by one.
|
|
29
32
|
*
|
|
30
33
|
* Both endpoints are rest states, so the curve is `--_qm-ease-reverse` (a second click
|
|
31
34
|
* lands mid-slide) and the rung is `slow` (the trip moves the stack around it).
|
|
@@ -34,3 +37,22 @@ export declare function reorder(node: HTMLElement, { from, to }: {
|
|
|
34
37
|
from: DOMRect;
|
|
35
38
|
to: DOMRect;
|
|
36
39
|
}, armed: () => boolean): AnimationConfig;
|
|
40
|
+
/**
|
|
41
|
+
* A slot entering or leaving the list fades where it stands, while the slots it displaces
|
|
42
|
+
* take the reorder trip: the stack is what slides, and a card is what appears or goes, under
|
|
43
|
+
* the slots crossing it. Opacity alone, because a leaving slot is held at its place by a
|
|
44
|
+
* transform `animate:` writes, which a keyframed transform would override.
|
|
45
|
+
*
|
|
46
|
+
* The returned duration is what keeps a leaving slot mounted until its fade has run, and
|
|
47
|
+
* the fill what keeps it from painting whole in the frame between the two.
|
|
48
|
+
*/
|
|
49
|
+
export declare function slot(node: HTMLElement, _params: unknown, { direction }: {
|
|
50
|
+
direction: 'in' | 'out' | 'both';
|
|
51
|
+
}): TransitionConfig;
|
|
52
|
+
/**
|
|
53
|
+
* Resolves once nothing in the document has scrolled for {@link STILL_FRAMES} frames
|
|
54
|
+
* running. A smooth scroll moves its scroller on every frame until it lands, so stillness
|
|
55
|
+
* is its end on every engine, whether or not it reports one; a scroll with nowhere to go
|
|
56
|
+
* is still from the first frame.
|
|
57
|
+
*/
|
|
58
|
+
export declare function scrollSettled(): Promise<void>;
|
package/dist/visual/motion.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
// The card stack's
|
|
2
|
-
//
|
|
3
|
-
//
|
|
4
|
-
// cannot see (the rects either side of the reconciliation
|
|
5
|
-
// the same seam `core/bloom.ts` takes
|
|
6
|
-
// script runs the animation.
|
|
1
|
+
// The card stack's trips: a slot's position ↔ position in a keyed list, the one row of
|
|
2
|
+
// the mechanism table no CSS rule can serve, because the two frames belong to different
|
|
3
|
+
// elements, and a slot entering or leaving the list. Svelte's `animate:` and `in:`/`out:`
|
|
4
|
+
// supply the halves CSS cannot see (the rects either side of the reconciliation, a node
|
|
5
|
+
// the list no longer holds) and WAAPI paints the trip, the same seam `core/bloom.ts` takes
|
|
6
|
+
// to keep a duration in the derivation while a script runs the animation.
|
|
7
7
|
import { rungMs, rungStyle } from '../core/rungs.js';
|
|
8
8
|
/** Marks this module's runs, so a second reorder cancels the first rather than layering
|
|
9
9
|
* a second transform over it. */
|
|
@@ -34,9 +34,11 @@ export function reorderArm() {
|
|
|
34
34
|
* `armed` is why this is the reorder and not every reconcile: `animate:` fires wherever
|
|
35
35
|
* a slot's rect moved, and a card growing under the caret moves every slot below it,
|
|
36
36
|
* which is a layout change with no trip in it and one that would draw those slots over
|
|
37
|
-
* the content that displaced them for the length of the run. So
|
|
38
|
-
* arms the gesture and the frame it lands in disarms it, which is the
|
|
39
|
-
* read off the endpoints: two positions in the list, not two positions on
|
|
37
|
+
* the content that displaced them for the length of the run. So a command that moves
|
|
38
|
+
* slots in the list arms the gesture and the frame it lands in disarms it, which is the
|
|
39
|
+
* admission test read off the endpoints: two positions in the list, not two positions on
|
|
40
|
+
* the page. A move is one; so, in the card stack, are an insert and a removal, which move
|
|
41
|
+
* every slot past them by one.
|
|
40
42
|
*
|
|
41
43
|
* Both endpoints are rest states, so the curve is `--_qm-ease-reverse` (a second click
|
|
42
44
|
* lands mid-slide) and the rung is `slow` (the trip moves the stack around it).
|
|
@@ -61,3 +63,50 @@ export function reorder(node, { from, to }, armed) {
|
|
|
61
63
|
], { duration, easing, id: RUN_ID });
|
|
62
64
|
return {};
|
|
63
65
|
}
|
|
66
|
+
/**
|
|
67
|
+
* A slot entering or leaving the list fades where it stands, while the slots it displaces
|
|
68
|
+
* take the reorder trip: the stack is what slides, and a card is what appears or goes, under
|
|
69
|
+
* the slots crossing it. Opacity alone, because a leaving slot is held at its place by a
|
|
70
|
+
* transform `animate:` writes, which a keyframed transform would override.
|
|
71
|
+
*
|
|
72
|
+
* The returned duration is what keeps a leaving slot mounted until its fade has run, and
|
|
73
|
+
* the fill what keeps it from painting whole in the frame between the two.
|
|
74
|
+
*/
|
|
75
|
+
export function slot(node, _params, { direction }) {
|
|
76
|
+
if (typeof node.animate !== 'function')
|
|
77
|
+
return {};
|
|
78
|
+
const style = rungStyle(node);
|
|
79
|
+
const duration = rungMs(style, '--_qm-duration-slow');
|
|
80
|
+
const easing = style.getPropertyValue('--_qm-ease-reverse').trim();
|
|
81
|
+
if (duration === undefined || !easing)
|
|
82
|
+
return {};
|
|
83
|
+
const opacity = direction === 'out' ? [1, 0] : [0, 1];
|
|
84
|
+
node.animate({ opacity }, { duration, easing, fill: direction === 'out' ? 'forwards' : 'none' });
|
|
85
|
+
return { duration };
|
|
86
|
+
}
|
|
87
|
+
/** Frames nothing may scroll in before a scroll counts as over. More than one, because a
|
|
88
|
+
* smooth scroll asked for in this frame takes its first step in a later one. */
|
|
89
|
+
const STILL_FRAMES = 3;
|
|
90
|
+
/** The wait's ceiling, in frames: a scroller a host keeps moving is not a trip that ends. */
|
|
91
|
+
const MAX_FRAMES = 60;
|
|
92
|
+
/**
|
|
93
|
+
* Resolves once nothing in the document has scrolled for {@link STILL_FRAMES} frames
|
|
94
|
+
* running. A smooth scroll moves its scroller on every frame until it lands, so stillness
|
|
95
|
+
* is its end on every engine, whether or not it reports one; a scroll with nowhere to go
|
|
96
|
+
* is still from the first frame.
|
|
97
|
+
*/
|
|
98
|
+
export function scrollSettled() {
|
|
99
|
+
return new Promise((resolve) => {
|
|
100
|
+
let still = 0;
|
|
101
|
+
let frames = 0;
|
|
102
|
+
const moved = () => void (still = 0);
|
|
103
|
+
document.addEventListener('scroll', moved, { capture: true, passive: true });
|
|
104
|
+
const frame = () => {
|
|
105
|
+
if (++still < STILL_FRAMES && ++frames < MAX_FRAMES)
|
|
106
|
+
return void requestAnimationFrame(frame);
|
|
107
|
+
document.removeEventListener('scroll', moved, { capture: true });
|
|
108
|
+
resolve();
|
|
109
|
+
};
|
|
110
|
+
requestAnimationFrame(frame);
|
|
111
|
+
});
|
|
112
|
+
}
|
package/dist/visual/strings.d.ts
CHANGED
|
@@ -21,9 +21,8 @@ export interface VisualStrings extends TableChromeStrings, SlashStrings {
|
|
|
21
21
|
addCard: string;
|
|
22
22
|
arrayAdd: string;
|
|
23
23
|
arrayRemove: string;
|
|
24
|
-
/** The
|
|
25
|
-
|
|
26
|
-
arrayMoveDown: string;
|
|
24
|
+
/** The drag handle on an `object` row: its tooltip and, with the row, its name. */
|
|
25
|
+
arrayDrag: string;
|
|
27
26
|
/**
|
|
28
27
|
* A row control's accessible name, the action and the row it acts on: `Remove
|
|
29
28
|
* Authors 2`. The action alone stays the tooltip. Parametric, so word order stays
|
|
@@ -42,9 +41,11 @@ export interface VisualStrings extends TableChromeStrings, SlashStrings {
|
|
|
42
41
|
* word order stays the translator's.
|
|
43
42
|
*/
|
|
44
43
|
elementUntitled: (label: string, index: number) => string;
|
|
45
|
-
/** The tag on the unset sentinel's row, which is what tells
|
|
46
|
-
*
|
|
44
|
+
/** The tag on the unset sentinel's row where it ghosts a member, which is what tells
|
|
45
|
+
* it from that member ({@link EnumField}). */
|
|
47
46
|
enumUnsetTag: string;
|
|
47
|
+
/** The same row's tag where it ghosts no member: no `default:`, or a blank one. */
|
|
48
|
+
enumUnsetTagNone: string;
|
|
48
49
|
/** The count in the matrix's label row: how many of its items are held. */
|
|
49
50
|
matrixHeld: (held: number, total: number) => string;
|
|
50
51
|
/** An open matrix's add box at rest, naming the list it adds to: `Add to Staff…`. The box
|
package/dist/visual/strings.js
CHANGED
|
@@ -30,12 +30,12 @@ export const DEFAULT_VISUAL_STRINGS = {
|
|
|
30
30
|
addCard: '+ Add Card',
|
|
31
31
|
arrayAdd: '+ Add',
|
|
32
32
|
arrayRemove: 'Remove',
|
|
33
|
-
|
|
34
|
-
arrayMoveDown: 'Move down',
|
|
33
|
+
arrayDrag: 'Drag to move',
|
|
35
34
|
arrayRowAction: (action, row) => `${action} ${row}`,
|
|
36
35
|
arrayCount: (count, max) => `${count} / ${max}`,
|
|
37
36
|
elementUntitled: (label, index) => `${label} ${index}`,
|
|
38
|
-
enumUnsetTag: '
|
|
37
|
+
enumUnsetTag: 'Default',
|
|
38
|
+
enumUnsetTagNone: 'Unset',
|
|
39
39
|
matrixHeld: (held, total) => `${held} of ${total} held`,
|
|
40
40
|
matrixAdd: (list) => (list ? `Add to ${list}…` : 'Add…'),
|
|
41
41
|
matrixItemTitle: (list, index) => `${list ? `${list} item` : 'Item'} ${index}`,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@quillmark/svelte",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.21.0",
|
|
4
4
|
"description": "Editor, live-preview and source surfaces over a Quillmark WASM session.",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -69,23 +69,23 @@
|
|
|
69
69
|
"check": "svelte-check --tsconfig ./tsconfig.json --fail-on-warnings"
|
|
70
70
|
},
|
|
71
71
|
"peerDependencies": {
|
|
72
|
-
"@quillmark/wasm": ">=0.
|
|
72
|
+
"@quillmark/wasm": ">=0.125.0-0",
|
|
73
73
|
"svelte": "^5.33.0"
|
|
74
74
|
},
|
|
75
75
|
"dependencies": {
|
|
76
76
|
"@internationalized/date": "^3.12.2",
|
|
77
77
|
"bits-ui": "^2.19.0",
|
|
78
78
|
"esm-env": "^1.2.2",
|
|
79
|
-
"prosemirror-commands": "1.7.
|
|
80
|
-
"prosemirror-gapcursor": "1.
|
|
81
|
-
"prosemirror-history": "1.5.
|
|
79
|
+
"prosemirror-commands": "1.7.2",
|
|
80
|
+
"prosemirror-gapcursor": "1.4.1",
|
|
81
|
+
"prosemirror-history": "1.5.1",
|
|
82
82
|
"prosemirror-inputrules": "1.5.1",
|
|
83
83
|
"prosemirror-keymap": "1.2.3",
|
|
84
|
-
"prosemirror-model": "1.25.
|
|
84
|
+
"prosemirror-model": "1.25.12",
|
|
85
85
|
"prosemirror-schema-list": "1.5.1",
|
|
86
86
|
"prosemirror-state": "1.4.4",
|
|
87
|
-
"prosemirror-transform": "1.12.
|
|
88
|
-
"prosemirror-view": "1.42.
|
|
87
|
+
"prosemirror-transform": "1.12.2",
|
|
88
|
+
"prosemirror-view": "1.42.6"
|
|
89
89
|
},
|
|
90
90
|
"devDependencies": {
|
|
91
91
|
"@sveltejs/package": "^3.0.0",
|