@quillmark/svelte 0.20.1 → 0.22.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 +6 -7
- package/README.md +1 -1
- package/THEMING.md +1 -1
- package/dist/core/address.d.ts +6 -1
- package/dist/core/address.js +12 -3
- package/dist/core/bloom.d.ts +9 -4
- package/dist/core/bloom.js +18 -49
- package/dist/core/codec/atoms.d.ts +1 -1
- package/dist/core/codec/atoms.js +26 -11
- package/dist/core/codec/blocks.d.ts +5 -5
- package/dist/core/codec/blocks.js +27 -8
- package/dist/core/codec/code.d.ts +4 -4
- package/dist/core/codec/code.js +52 -22
- package/dist/core/codec/decode.d.ts +5 -5
- package/dist/core/codec/decode.js +18 -12
- package/dist/core/codec/encode.js +6 -1
- package/dist/core/codec/field.d.ts +22 -15
- package/dist/core/codec/field.js +66 -26
- package/dist/core/codec/index.d.ts +1 -1
- package/dist/core/codec/index.js +2 -2
- package/dist/core/codec/inputrules.d.ts +4 -0
- package/dist/core/codec/inputrules.js +44 -6
- 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 +5 -4
- package/dist/core/codec/lists.js +87 -31
- package/dist/core/codec/nesting.d.ts +20 -0
- package/dist/core/codec/nesting.js +62 -0
- package/dist/core/codec/prose.css +145 -63
- package/dist/core/codec/schema.d.ts +2 -2
- package/dist/core/codec/schema.js +71 -18
- package/dist/core/codec/slash.js +0 -0
- package/dist/core/codec/table-copy.d.ts +17 -0
- package/dist/core/codec/table-copy.js +84 -0
- package/dist/core/codec/table-view.d.ts +6 -7
- package/dist/core/codec/table-view.js +281 -201
- package/dist/core/codec/table.d.ts +2 -9
- package/dist/core/codec/table.js +10 -19
- package/dist/core/codec/tasks.d.ts +19 -0
- package/dist/core/codec/tasks.js +140 -0
- package/dist/core/errors.d.ts +2 -2
- 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 +236 -78
- package/dist/visual/ArrayField.svelte.d.ts +0 -2
- package/dist/visual/BooleanField.svelte +12 -1
- package/dist/visual/Card.svelte +41 -23
- package/dist/visual/Card.svelte.d.ts +3 -2
- package/dist/visual/CardControls.svelte +15 -15
- package/dist/visual/DateField.svelte +93 -64
- package/dist/visual/EnumField.svelte +9 -4
- package/dist/visual/Field.svelte +20 -16
- package/dist/visual/Field.svelte.d.ts +4 -2
- package/dist/visual/FieldHint.svelte +22 -5
- package/dist/visual/FieldLabel.svelte +5 -33
- package/dist/visual/FieldLabel.svelte.d.ts +0 -2
- package/dist/visual/FormatPopover.svelte +78 -81
- package/dist/visual/MatrixField.svelte +28 -19
- package/dist/visual/NumberField.svelte +32 -3
- package/dist/visual/ObjectField.svelte +40 -24
- package/dist/visual/ObjectField.svelte.d.ts +6 -0
- package/dist/visual/ProseField.svelte +10 -13
- package/dist/visual/ProseField.svelte.d.ts +5 -2
- package/dist/visual/ProseValue.svelte +25 -19
- package/dist/visual/ProseValue.svelte.d.ts +4 -4
- package/dist/visual/TextField.svelte +3 -2
- package/dist/visual/TipsCard.svelte +7 -7
- package/dist/visual/VariantField.svelte +1 -1
- package/dist/visual/VariantField.svelte.d.ts +1 -1
- package/dist/visual/VisualEditorInner.svelte +121 -39
- package/dist/visual/controls.css +21 -2
- 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 -21
- package/dist/visual/icons/nodes.js +9 -7
- package/dist/visual/leaves.d.ts +12 -9
- package/dist/visual/leaves.js +19 -0
- package/dist/visual/motion.d.ts +25 -3
- package/dist/visual/motion.js +58 -9
- package/dist/visual/strings.d.ts +15 -9
- package/dist/visual/strings.js +19 -12
- package/dist/visual/structure.d.ts +21 -31
- package/dist/visual/structure.js +54 -51
- package/package.json +3 -3
|
@@ -31,8 +31,9 @@
|
|
|
31
31
|
type Landing
|
|
32
32
|
} from '../core/address.js';
|
|
33
33
|
import { errorMessage, reportError } from '../core/errors.js';
|
|
34
|
-
import {
|
|
34
|
+
import { bloom } 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)[]>([]);
|
|
@@ -217,7 +220,7 @@
|
|
|
217
220
|
const card = addr.card;
|
|
218
221
|
return card != null ? { card, field: addr.field } : { field: addr.field };
|
|
219
222
|
}
|
|
220
|
-
function handleFocus(addr: Addr): void {
|
|
223
|
+
function handleFocus(addr: Addr, steps: readonly PathStep[] = []): void {
|
|
221
224
|
const plain = normalize(addr);
|
|
222
225
|
activeAddr = plain;
|
|
223
226
|
activeCardId = cardIdOf(plain);
|
|
@@ -225,7 +228,7 @@
|
|
|
225
228
|
// to at the offset it still names has to report again.
|
|
226
229
|
lastCaretField = undefined;
|
|
227
230
|
lastCaretPos = undefined;
|
|
228
|
-
const field = pathFor(plain);
|
|
231
|
+
const field = pathFor(plain, steps);
|
|
229
232
|
// Both halves or neither: a stale addr names no path and no live card.
|
|
230
233
|
if (field != null && activeCardId != null)
|
|
231
234
|
onActiveLeafChange?.({ field, cardId: activeCardId });
|
|
@@ -242,11 +245,11 @@
|
|
|
242
245
|
return model.cards.map((c) => c.kind);
|
|
243
246
|
}
|
|
244
247
|
/**
|
|
245
|
-
* A leaf's canonical `DocPath
|
|
246
|
-
* array (a stale addr: drop rather than mis-target).
|
|
248
|
+
* A leaf's canonical `DocPath`, `steps` down from the field. `undefined` for an
|
|
249
|
+
* address outside the live card array (a stale addr: drop rather than mis-target).
|
|
247
250
|
*/
|
|
248
|
-
function pathFor(addr: Addr): DocPath | undefined {
|
|
249
|
-
return fieldPathForAddr(addr, addr.card == null ? NO_KINDS : liveKinds());
|
|
251
|
+
function pathFor(addr: Addr, steps: readonly PathStep[] = []): DocPath | undefined {
|
|
252
|
+
return fieldPathForAddr(addr, addr.card == null ? NO_KINDS : liveKinds(), steps);
|
|
250
253
|
}
|
|
251
254
|
// The last place reported, so a caret that did not move is not news. A leaf fires
|
|
252
255
|
// one caret signal per transaction, and a transaction need not have moved the
|
|
@@ -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 });
|
|
@@ -678,10 +736,10 @@
|
|
|
678
736
|
// (where landing a caret changes nothing on screen) or one off-screen, where the
|
|
679
737
|
// browser's focus-scroll moves the page and leaves the caret to be hunted for.
|
|
680
738
|
const box = await land(found, at.granularity === 'segment' ? undefined : at.pos);
|
|
681
|
-
if (span.alive)
|
|
739
|
+
if (span.alive) bloom(box);
|
|
682
740
|
}
|
|
683
741
|
/** The active prose leaf: the formatting popover's observation seam. The active
|
|
684
|
-
* field's
|
|
742
|
+
* field's own leaf where it registered one, else the by-value leaf focused last
|
|
685
743
|
* under it (`leaves.ts`); `undefined` for a form control, which holds no marks to
|
|
686
744
|
* toggle. */
|
|
687
745
|
export function getActiveLeaf(): ActiveProse | undefined {
|
|
@@ -689,7 +747,7 @@
|
|
|
689
747
|
const card = activeAddr.card != null ? activeCardId : undefined;
|
|
690
748
|
const key = fieldKeyToString({ card, field: activeAddr.field });
|
|
691
749
|
const controller = leaves.prose(key);
|
|
692
|
-
if (controller) return { views: controller as FieldController & LeafViews
|
|
750
|
+
if (controller) return { views: controller as FieldController & LeafViews };
|
|
693
751
|
const views = leaves.value(key);
|
|
694
752
|
return views && { views };
|
|
695
753
|
}
|
|
@@ -768,7 +826,7 @@
|
|
|
768
826
|
const found = await revealLeaf(field);
|
|
769
827
|
if (!found) return missed(`no mounted field at ${field}`, field);
|
|
770
828
|
const box = await land(found, undefined);
|
|
771
|
-
if (span.alive)
|
|
829
|
+
if (span.alive) bloom(box);
|
|
772
830
|
}
|
|
773
831
|
/** Seed a card of `kind` and insert it at `at` (default: the end). Returns the new
|
|
774
832
|
* card's session key, or `undefined` when the quill seeds no card of that kind. */
|
|
@@ -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
|
|
@@ -936,9 +995,10 @@
|
|
|
936
995
|
{#if kinds.length === 1}
|
|
937
996
|
<button
|
|
938
997
|
type="button"
|
|
939
|
-
class="qm-add-btn qm-add-affordance qm-tap-floor"
|
|
998
|
+
class="qm-add-btn qm-add-affordance qm-tap-floor qm-focus-ring"
|
|
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
|
|
@@ -948,12 +1008,22 @@
|
|
|
948
1008
|
rules below reach it through `:global`. -->
|
|
949
1009
|
<DropdownMenu.Root>
|
|
950
1010
|
<DropdownMenu.Trigger
|
|
951
|
-
class="qm-add-btn qm-add-affordance qm-tap-floor"
|
|
1011
|
+
class="qm-add-btn qm-add-affordance qm-tap-floor qm-focus-ring"
|
|
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
|
@@ -233,7 +233,8 @@
|
|
|
233
233
|
:where(.qm-chip:hover) {
|
|
234
234
|
color: var(--_qm-ink);
|
|
235
235
|
}
|
|
236
|
-
:where(.qm-chip svg)
|
|
236
|
+
:where(.qm-chip svg),
|
|
237
|
+
:where(.qm-icon-btn svg) {
|
|
237
238
|
flex-shrink: 0;
|
|
238
239
|
width: var(--_qm-glyph-control);
|
|
239
240
|
height: var(--_qm-glyph-control);
|
|
@@ -266,6 +267,22 @@
|
|
|
266
267
|
transform: translate(-50%, -50%);
|
|
267
268
|
}
|
|
268
269
|
|
|
270
|
+
/* The arrival wash: a plane over the host's box at rest, which a landing plays
|
|
271
|
+
(`core/bloom.ts`). On the host from mount, because WebKit resizes a laid-out subgrid's
|
|
272
|
+
tracks when an out-of-flow box is added to it. */
|
|
273
|
+
:where(.qm-wash) {
|
|
274
|
+
position: relative;
|
|
275
|
+
}
|
|
276
|
+
:where(.qm-wash)::after {
|
|
277
|
+
content: '';
|
|
278
|
+
position: absolute;
|
|
279
|
+
inset: 0;
|
|
280
|
+
border-radius: inherit;
|
|
281
|
+
background: var(--_qm-accent-wash);
|
|
282
|
+
opacity: 0;
|
|
283
|
+
pointer-events: none;
|
|
284
|
+
}
|
|
285
|
+
|
|
269
286
|
/* The capacity ladder: how many tracks a grid of fields steps to, off the width of
|
|
270
287
|
the query container it stands in. One recipe, because three grids read it — a section's
|
|
271
288
|
fields (`Card.svelte`), a subform's properties (`ObjectField`), each over its own
|
|
@@ -310,7 +327,8 @@
|
|
|
310
327
|
highlight the keyboard cannot reach. It fills with the accent held rather than
|
|
311
328
|
the hover rung: the mark is a cursor a keyboard drives, so it has to read at a
|
|
312
329
|
glance from across the list, where a hover only has to confirm the pointer
|
|
313
|
-
already under it.
|
|
330
|
+
already under it. So the mark is the item's whole focus indicator, and the
|
|
331
|
+
outline a focused item would draw beside it is taken off.
|
|
314
332
|
|
|
315
333
|
The resting fill is the recipe's too: an item drawn on a `<button>` carries a UA
|
|
316
334
|
fill to take back, and a scoped block outranks this file, so a caller taking it
|
|
@@ -337,6 +355,7 @@
|
|
|
337
355
|
background: transparent;
|
|
338
356
|
cursor: pointer;
|
|
339
357
|
user-select: none;
|
|
358
|
+
outline: none;
|
|
340
359
|
}
|
|
341
360
|
:where(.qm-menu-item[data-highlighted]) {
|
|
342
361
|
background: var(--_qm-accent-tint);
|
|
@@ -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
|
}]];
|
|
@@ -62,27 +87,6 @@ export declare const ICONS: {
|
|
|
62
87
|
}], readonly ["path", {
|
|
63
88
|
readonly d: "M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71";
|
|
64
89
|
}]];
|
|
65
|
-
readonly hash: readonly [readonly ["line", {
|
|
66
|
-
readonly x1: "4";
|
|
67
|
-
readonly x2: "20";
|
|
68
|
-
readonly y1: "9";
|
|
69
|
-
readonly y2: "9";
|
|
70
|
-
}], readonly ["line", {
|
|
71
|
-
readonly x1: "4";
|
|
72
|
-
readonly x2: "20";
|
|
73
|
-
readonly y1: "15";
|
|
74
|
-
readonly y2: "15";
|
|
75
|
-
}], readonly ["line", {
|
|
76
|
-
readonly x1: "10";
|
|
77
|
-
readonly x2: "8";
|
|
78
|
-
readonly y1: "3";
|
|
79
|
-
readonly y2: "21";
|
|
80
|
-
}], readonly ["line", {
|
|
81
|
-
readonly x1: "16";
|
|
82
|
-
readonly x2: "14";
|
|
83
|
-
readonly y1: "3";
|
|
84
|
-
readonly y2: "21";
|
|
85
|
-
}]];
|
|
86
90
|
readonly info: readonly [readonly ["circle", {
|
|
87
91
|
readonly cx: "12";
|
|
88
92
|
readonly cy: "12";
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
//
|
|
6
6
|
// Verbatim from lucide 1.28.0 — the version is stated because a glyph that drifts
|
|
7
7
|
// from upstream is otherwise invisible, and because the notices in `NOTICE` are the
|
|
8
|
-
// ones that release carries (ISC, and MIT for the Feather-derived
|
|
8
|
+
// ones that release carries (ISC, and MIT for the Feather-derived glyphs).
|
|
9
9
|
export const ICONS = {
|
|
10
10
|
'chevron-right': [['path', { d: 'm9 18 6-6-6-6' }]],
|
|
11
11
|
'arrow-right': [
|
|
@@ -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' }],
|
|
@@ -37,12 +45,6 @@ export const ICONS = {
|
|
|
37
45
|
['path', { d: 'M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71' }],
|
|
38
46
|
['path', { d: 'M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71' }]
|
|
39
47
|
],
|
|
40
|
-
hash: [
|
|
41
|
-
['line', { x1: '4', x2: '20', y1: '9', y2: '9' }],
|
|
42
|
-
['line', { x1: '4', x2: '20', y1: '15', y2: '15' }],
|
|
43
|
-
['line', { x1: '10', x2: '8', y1: '3', y2: '21' }],
|
|
44
|
-
['line', { x1: '16', x2: '14', y1: '3', y2: '21' }]
|
|
45
|
-
],
|
|
46
48
|
info: [
|
|
47
49
|
['circle', { cx: '12', cy: '12', r: '10' }],
|
|
48
50
|
['path', { d: 'M12 16v-4' }],
|
package/dist/visual/leaves.d.ts
CHANGED
|
@@ -8,8 +8,8 @@ import type { FieldController, LeafViews } from '../core/codec/index.js';
|
|
|
8
8
|
*/
|
|
9
9
|
export type LandingBox = HTMLElement | undefined | Promise<HTMLElement | undefined>;
|
|
10
10
|
/**
|
|
11
|
-
* A mounted field's landing handle: focus it, and the
|
|
12
|
-
*
|
|
11
|
+
* A mounted field's landing handle: focus it, and the `.qm-wash` box the arrival wash
|
|
12
|
+
* plays over ({@link import('../core/bloom.js').bloom}).
|
|
13
13
|
*
|
|
14
14
|
* A `FieldController` is one, which is what lets the two lanes share a lookup; `Field`
|
|
15
15
|
* builds the same shape over a form control off the handoff a label click already
|
|
@@ -71,16 +71,19 @@ export interface LeafRegistry {
|
|
|
71
71
|
clear(): void;
|
|
72
72
|
}
|
|
73
73
|
export declare function createLeafRegistry(): LeafRegistry;
|
|
74
|
-
/**
|
|
75
|
-
*
|
|
76
|
-
* read from and a mark toggled at, and the controller where the leaf is a field's. A
|
|
77
|
-
* by-value leaf has none, its anchors being dropped on the parent's value write, so the
|
|
78
|
-
* anchor verbs the controller carries have nothing to act on there.
|
|
79
|
-
*/
|
|
74
|
+
/** The active prose leaf as the formatting popover reaches it: the views a selection is
|
|
75
|
+
* read from and a mark toggled at. */
|
|
80
76
|
export interface ActiveProse {
|
|
81
77
|
views: LeafViews;
|
|
82
|
-
controller?: FieldController;
|
|
83
78
|
}
|
|
79
|
+
/**
|
|
80
|
+
* The steps from a field's control box `root` down to the cell holding `target`, the
|
|
81
|
+
* reverse of {@link FieldControl.focusPath}: read off the marker each container rung
|
|
82
|
+
* stamps on the cell it draws, `data-qm-index` on an array's row and `data-qm-prop` on a
|
|
83
|
+
* key step (an object's property, a variant's cell, a matrix's member and an added
|
|
84
|
+
* item's title). `[]` for a target on the field's own control.
|
|
85
|
+
*/
|
|
86
|
+
export declare function stepsTo(target: EventTarget | null, root: Element): PathStep[];
|
|
84
87
|
/** The by-value lane with a field's key bound: what a leaf under that field announces
|
|
85
88
|
* itself through, knowing no key of its own. */
|
|
86
89
|
export interface ValueLeafSeat {
|
package/dist/visual/leaves.js
CHANGED
|
@@ -66,6 +66,25 @@ export function createLeafRegistry() {
|
|
|
66
66
|
}
|
|
67
67
|
};
|
|
68
68
|
}
|
|
69
|
+
/**
|
|
70
|
+
* The steps from a field's control box `root` down to the cell holding `target`, the
|
|
71
|
+
* reverse of {@link FieldControl.focusPath}: read off the marker each container rung
|
|
72
|
+
* stamps on the cell it draws, `data-qm-index` on an array's row and `data-qm-prop` on a
|
|
73
|
+
* key step (an object's property, a variant's cell, a matrix's member and an added
|
|
74
|
+
* item's title). `[]` for a target on the field's own control.
|
|
75
|
+
*/
|
|
76
|
+
export function stepsTo(target, root) {
|
|
77
|
+
const steps = [];
|
|
78
|
+
for (let el = target instanceof Element ? target : null; el && el !== root; el = el.parentElement) {
|
|
79
|
+
const index = el.getAttribute('data-qm-index');
|
|
80
|
+
const key = el.getAttribute('data-qm-prop');
|
|
81
|
+
if (index != null)
|
|
82
|
+
steps.push(Number(index));
|
|
83
|
+
else if (key != null)
|
|
84
|
+
steps.push(key);
|
|
85
|
+
}
|
|
86
|
+
return steps.reverse();
|
|
87
|
+
}
|
|
69
88
|
const SEAT = Symbol('qm-value-leaf');
|
|
70
89
|
/** Seat every by-value leaf under the calling field, at any depth of its containers. */
|
|
71
90
|
export function seatValueLeaves(seat) {
|
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>;
|