@graview/primitives 0.1.65 β 0.1.66
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/app-bar.d.ts +88 -15
- package/dist/app-bar.d.ts.map +1 -1
- package/dist/app-bar.js +176 -130
- package/dist/app-bar.js.map +1 -1
- package/dist/app-menu.d.ts +9 -0
- package/dist/app-menu.d.ts.map +1 -0
- package/dist/app-menu.js +60 -0
- package/dist/app-menu.js.map +1 -0
- package/dist/bar-activity.d.ts +5 -9
- package/dist/bar-activity.d.ts.map +1 -1
- package/dist/bar-activity.js +24 -7
- package/dist/bar-activity.js.map +1 -1
- package/dist/bar-panes.d.ts +5 -5
- package/dist/bar-panes.d.ts.map +1 -1
- package/dist/bar-panes.js +9 -5
- package/dist/bar-panes.js.map +1 -1
- package/dist/bar-suggested.d.ts +9 -1
- package/dist/bar-suggested.d.ts.map +1 -1
- package/dist/bar-suggested.js +11 -17
- package/dist/bar-suggested.js.map +1 -1
- package/dist/better-bar.d.ts +65 -0
- package/dist/better-bar.d.ts.map +1 -0
- package/dist/better-bar.js +252 -0
- package/dist/better-bar.js.map +1 -0
- package/dist/better-panel.d.ts +50 -0
- package/dist/better-panel.d.ts.map +1 -0
- package/dist/better-panel.js +151 -0
- package/dist/better-panel.js.map +1 -0
- package/dist/editable.d.ts +8 -1
- package/dist/editable.d.ts.map +1 -1
- package/dist/editable.js +140 -16
- package/dist/editable.js.map +1 -1
- package/dist/first-look-door.d.ts +2 -2
- package/dist/first-look-door.js +2 -2
- package/dist/first-look.d.ts.map +1 -1
- package/dist/first-look.js +9 -7
- package/dist/first-look.js.map +1 -1
- package/dist/frame.d.ts +2 -2
- package/dist/frame.d.ts.map +1 -1
- package/dist/frame.js +1 -1
- package/dist/frame.js.map +1 -1
- package/dist/inbox-pane.d.ts +31 -0
- package/dist/inbox-pane.d.ts.map +1 -0
- package/dist/inbox-pane.js +89 -0
- package/dist/inbox-pane.js.map +1 -0
- package/dist/inbox.d.ts +17 -0
- package/dist/inbox.d.ts.map +1 -0
- package/dist/inbox.js +84 -0
- package/dist/inbox.js.map +1 -0
- package/dist/index.d.ts +4 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -2
- package/dist/index.js.map +1 -1
- package/dist/menu-suggested.d.ts +54 -0
- package/dist/menu-suggested.d.ts.map +1 -0
- package/dist/menu-suggested.js +140 -0
- package/dist/menu-suggested.js.map +1 -0
- package/dist/menu-told-door.js +1 -1
- package/dist/menu-told-door.js.map +1 -1
- package/dist/menu-told.d.ts +7 -0
- package/dist/menu-told.d.ts.map +1 -1
- package/dist/menu-told.js +24 -0
- package/dist/menu-told.js.map +1 -1
- package/dist/notify-sheet.js +2 -2
- package/dist/notify-sheet.js.map +1 -1
- package/dist/page-better.d.ts +13 -0
- package/dist/page-better.d.ts.map +1 -0
- package/dist/page-better.js +714 -0
- package/dist/page-better.js.map +1 -0
- package/dist/page-parts.d.ts +41 -0
- package/dist/page-parts.d.ts.map +1 -0
- package/dist/page-parts.js +100 -0
- package/dist/page-parts.js.map +1 -0
- package/dist/pages.d.ts +2 -3
- package/dist/pages.d.ts.map +1 -1
- package/dist/pages.js +2 -3
- package/dist/pages.js.map +1 -1
- package/dist/profile.d.ts +1 -10
- package/dist/profile.d.ts.map +1 -1
- package/dist/profile.js +2 -3
- package/dist/profile.js.map +1 -1
- package/dist/scene-bar.d.ts +1 -1
- package/dist/scene-bar.d.ts.map +1 -1
- package/dist/scene-bar.js +4 -7
- package/dist/scene-bar.js.map +1 -1
- package/dist/section-better.d.ts +47 -9
- package/dist/section-better.d.ts.map +1 -1
- package/dist/section-better.js +239 -121
- package/dist/section-better.js.map +1 -1
- package/dist/section-door.d.ts +7 -0
- package/dist/section-door.d.ts.map +1 -1
- package/dist/section-door.js +39 -5
- package/dist/section-door.js.map +1 -1
- package/dist/section-menu.d.ts +23 -9
- package/dist/section-menu.d.ts.map +1 -1
- package/dist/section-menu.js +176 -23
- package/dist/section-menu.js.map +1 -1
- package/dist/section-words.d.ts +8 -0
- package/dist/section-words.d.ts.map +1 -1
- package/dist/section-words.js +25 -0
- package/dist/section-words.js.map +1 -1
- package/dist/shell.d.ts +5 -1
- package/dist/shell.d.ts.map +1 -1
- package/dist/shell.js +4 -2
- package/dist/shell.js.map +1 -1
- package/dist/suggested.d.ts +21 -10
- package/dist/suggested.d.ts.map +1 -1
- package/dist/suggested.js +43 -64
- package/dist/suggested.js.map +1 -1
- package/dist/told-ui.d.ts +1 -1
- package/dist/told-ui.d.ts.map +1 -1
- package/dist/told-ui.js +1 -1
- package/dist/told-ui.js.map +1 -1
- package/dist/when-words.d.ts +21 -0
- package/dist/when-words.d.ts.map +1 -0
- package/dist/when-words.js +43 -0
- package/dist/when-words.js.map +1 -0
- package/dist/workbench/activity.d.ts +44 -0
- package/dist/workbench/activity.d.ts.map +1 -1
- package/dist/workbench/activity.js +74 -59
- package/dist/workbench/activity.js.map +1 -1
- package/dist/workbench/inspector.d.ts.map +1 -1
- package/dist/workbench/inspector.js +170 -31
- package/dist/workbench/inspector.js.map +1 -1
- package/dist/workbench/standing.d.ts.map +1 -1
- package/dist/workbench/standing.js +1 -1
- package/dist/workbench/standing.js.map +1 -1
- package/package.json +6 -6
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import { InvalidArguments, isLeftAlone, layer, nounOf, signInInstead, withArticle } from "@graview/core";
|
|
2
|
+
import { choiceWords, InvalidArguments, isLeftAlone, layer, nounOf, pluralLabel, signInInstead, withArticle } from "@graview/core";
|
|
3
3
|
import { edgeWords } from "@graview/core/words";
|
|
4
4
|
import { failureWords } from "@graview/core/words";
|
|
5
5
|
import { linkProvenance, provenanceWords } from "@graview/core/provenance";
|
|
@@ -11,9 +11,10 @@ import { AnswerArgs, nameOf } from "./answer-args.js";
|
|
|
11
11
|
import { VISUALLY_HIDDEN } from "../primitives/index.js";
|
|
12
12
|
import { ROVING_ITEM, useRoving } from "../roving.js";
|
|
13
13
|
import { menuMatches } from "../menu-words.js";
|
|
14
|
-
import { MENU_RANK_FLOOR, useFastRank } from "../menu-rank.js";
|
|
14
|
+
import { MENU_RANK_FLOOR, MENU_RANK_REST_MS, useFastRank } from "../menu-rank.js";
|
|
15
15
|
import { TOLD_SUGGESTED_MOST, useToldSuggestions } from "../menu-told-door.js";
|
|
16
16
|
import { openQuickAsk, preloadQuickAsk, useQuickAskDrawn, whereMenuStands } from "../quick-ask-desk.js";
|
|
17
|
+
import { filledActs, notARow, useSuggested } from "../menu-suggested.js";
|
|
17
18
|
import "../frame.js"; // A door: tied to what the page drew first (TIED DOORS, packages/primitives/src/frame.ts).
|
|
18
19
|
/**
|
|
19
20
|
* The pane itself (`Inspector` in `./inspector-door.tsx` draws it): what is
|
|
@@ -80,6 +81,30 @@ function ChosenActs() {
|
|
|
80
81
|
setMenuAt({ x: Math.round(at.right), y: Math.round(at.bottom + 4), on: one.id });
|
|
81
82
|
}, style: { position: "absolute", ...(rowRight !== null ? { right: rowRight, top: 14, minHeight: 38 } : { right: "calc(14px + var(--graview-dock, 0px))", top: 98 }), zIndex: "var(--graview-layer-overview, 10)", ...(rowRight === null ? { minHeight: 30 } : {}), padding: "0 12px", borderRadius: 8, fontSize: "0.8125rem", background: "var(--graview-float)", boxShadow: "var(--graview-lift-low)" }, children: "Acts\u2026" }));
|
|
82
83
|
}
|
|
84
|
+
/**
|
|
85
|
+
* WHY NOTHING IS OFFERED, IN TRUE WORDS (Roadmap f-make-any-page-better).
|
|
86
|
+
* "Nothing this app declares acts on it" was said of the lists in the
|
|
87
|
+
* scene β whose app renames a list, notes why it is there and adds a task
|
|
88
|
+
* to it β because the menu was on all the lists at once, and every act is
|
|
89
|
+
* done to one. So: what is done to one is said as such ("Right-click one
|
|
90
|
+
* list to see what you can do with it"); only where nothing in the app is
|
|
91
|
+
* done to the kind at all is that said, and never in the declaration's
|
|
92
|
+
* words.
|
|
93
|
+
*/
|
|
94
|
+
function emptyWords(store, kinds, selection) {
|
|
95
|
+
const noun = (kind) => nounOf(store.schema.tryDefinition(kind), kind);
|
|
96
|
+
const plural = (kind) => pluralLabel(store.schema, kind).toLowerCase();
|
|
97
|
+
const doneTo = kinds.filter((kind) => store.allMutations().some((mutation) => {
|
|
98
|
+
const subject = mutation.subject;
|
|
99
|
+
return subject?.kinds === "*" || (subject?.kinds ?? []).includes(kind);
|
|
100
|
+
}));
|
|
101
|
+
const many = selection.length !== 1 || selection.some((id) => id.startsWith("aggregate:"));
|
|
102
|
+
if (doneTo.length > 0 && many)
|
|
103
|
+
return kinds.length === 1 ? `Nothing here is done to all the ${plural(kinds[0])} at once. Right-click one ${noun(kinds[0])} to see what you can do with it.` : "Nothing here is done to all of these at once. Right-click one of them to see what you can do with it.";
|
|
104
|
+
if (doneTo.length > 0)
|
|
105
|
+
return `There is nothing you can do with ${kinds.length === 1 ? withArticle(noun(kinds[0])) : "these"} here right now.`;
|
|
106
|
+
return kinds.length === 1 ? `There is nothing to do with ${withArticle(noun(kinds[0]))} in this app yet.` : "There is nothing to do with these in this app yet.";
|
|
107
|
+
}
|
|
83
108
|
/** Whether the picture has room for a rail beside it: the scene's own box, not the window's. */
|
|
84
109
|
const RAIL_NEEDS = 14 + 236 + 14 + 300;
|
|
85
110
|
function Pane({ placement }) {
|
|
@@ -236,7 +261,7 @@ function Pane({ placement }) {
|
|
|
236
261
|
*/
|
|
237
262
|
const target = menuAt === null
|
|
238
263
|
? null
|
|
239
|
-
: { face: menuAt.face ?? "scene", on: menuAt.on, about: selection, field: menuAt.field, place: menuAt.place, href: menuAt.href, act: menuAt.act, column: menuAt.column, ...(menuAt.section ? { section: menuAt.section } : {}), element: menuAt.returnTo };
|
|
264
|
+
: { face: menuAt.face ?? "scene", on: menuAt.on, about: selection, field: menuAt.field, place: menuAt.place, href: menuAt.href, act: menuAt.act, column: menuAt.column, ...(menuAt.section ? { section: menuAt.section } : {}), ...(menuAt.page ? { page: menuAt.page } : {}), element: menuAt.returnTo };
|
|
240
265
|
const beside = useMenuSections(target);
|
|
241
266
|
/*
|
|
242
267
|
* Whether the derivation is withholding a creating act for want of the
|
|
@@ -305,6 +330,46 @@ function Pane({ placement }) {
|
|
|
305
330
|
if (!onRow)
|
|
306
331
|
heldRank.current = { words: typed.trim(), rank: fastRanked };
|
|
307
332
|
const fastRank = onRow ? (heldRank.current.words === typed.trim() ? heldRank.current.rank : null) : fastRanked;
|
|
333
|
+
/*
|
|
334
|
+
* TWO WAYS TO ASK, UNDER THE FIELD (Roadmap f-make-any-page-better): π¬
|
|
335
|
+
* Chat β the records, the app's structure, anything broader, the quick
|
|
336
|
+
* ask's card β and β¨ Better UI where the part under the cursor lends it
|
|
337
|
+
* (a section's `mode`). As words are typed the Fast slot says which they
|
|
338
|
+
* are and lights it; a click, or the arrows, choose the other; with
|
|
339
|
+
* nothing typed each opens its card.
|
|
340
|
+
*/
|
|
341
|
+
const better = useMemo(() => [...beside.start, ...beside.end].find((section) => section.mode)?.mode, [beside]);
|
|
342
|
+
const modes = menuAt === null ? [] : [...(asks ? ["chat"] : []), ...(better ? ["better"] : [])];
|
|
343
|
+
const [modeChosen, setModeChosen] = useState(null);
|
|
344
|
+
const [modeHeard, setModeHeard] = useState(null);
|
|
345
|
+
useEffect(() => setModeChosen(null), [menuAt, typed]);
|
|
346
|
+
useEffect(() => {
|
|
347
|
+
const asked = typed.trim();
|
|
348
|
+
if (!better || !asks || asked === "")
|
|
349
|
+
return;
|
|
350
|
+
let live = true;
|
|
351
|
+
const timer = setTimeout(() => void better.claims(asked).then((look) => live && setModeHeard({ words: asked, mode: look ? "better" : "chat" }), () => undefined), MENU_RANK_REST_MS);
|
|
352
|
+
return () => {
|
|
353
|
+
live = false;
|
|
354
|
+
clearTimeout(timer);
|
|
355
|
+
};
|
|
356
|
+
}, [typed, better, asks]);
|
|
357
|
+
const modeLit = modes.length === 0 ? null : (modeChosen ?? (modes.length === 1 ? modes[0] : modeHeard && modeHeard.words === typed.trim() ? modeHeard.mode : "chat"));
|
|
358
|
+
/* SUGGESTED: two or three things worth doing here, each tagged by its way, picked by the Fast slot once it answers. */
|
|
359
|
+
const suggestCandidates = useMemo(() => {
|
|
360
|
+
if (menuAt === null || modes.length === 0)
|
|
361
|
+
return [];
|
|
362
|
+
const nudges = (better?.nudges?.() ?? []).map((label, at) => ({ key: `nudge:${at}`, label, mode: "better", source: "nudge" }));
|
|
363
|
+
const told = toldLike.slice(0, 2).map((one) => ({ key: one.key, label: one.label, mode: "chat", source: "told" }));
|
|
364
|
+
// An act with its answer filled in, never the bare act a row below already offers.
|
|
365
|
+
const on = menuAt.on ?? (selection.length === 1 ? selection[0] : undefined);
|
|
366
|
+
const record = on !== undefined ? store.graph.getNode(on) : undefined;
|
|
367
|
+
const acts = filledActs(affordances, record, record ? choiceWords(store.schema, [record.kind])[record.kind] : undefined, record ? nameOf(store, record.id) : undefined).slice(0, 2);
|
|
368
|
+
return notARow(asks ? [...nudges, ...told, ...acts] : nudges, [...affordances, ...struck].map((one) => one.label));
|
|
369
|
+
}, [menuAt, modes.length, better, toldLike, affordances, struck, asks, store, selection]);
|
|
370
|
+
/* What the answer depends on: the record the menu is on (a new object whenever it changes), else the graph as it stands. */
|
|
371
|
+
const dependsOn = useMemo(() => (menuAt?.on !== undefined ? store.graph.getNode(menuAt.on) : undefined) ?? graphStamp(store), [menuAt, store, affordances]);
|
|
372
|
+
const suggested = useSuggested(ai, suggestCandidates, { name: menuAt?.title ?? (selection.length === 1 ? nameOf(store, selection[0]) : "this"), opened: menuAt, better: better !== undefined, depends: dependsOn }, onRow);
|
|
308
373
|
/*
|
|
309
374
|
* A REFUSAL IS A RESULT, said where the button was pressed.
|
|
310
375
|
*
|
|
@@ -497,7 +562,7 @@ function Pane({ placement }) {
|
|
|
497
562
|
* tail honest. Everything past nine scrolls inside the pane's own box
|
|
498
563
|
* once shown.
|
|
499
564
|
*/
|
|
500
|
-
const fits = Math.max(1, Math.min(9, affordances.length));
|
|
565
|
+
const fits = atPointer ? 4 : Math.max(1, Math.min(9, affordances.length));
|
|
501
566
|
/*
|
|
502
567
|
* ORDERED FOR READING, not only for ranking: what a broken rule demands,
|
|
503
568
|
* then the acts somebody PINNED (the person's own before the app's β a
|
|
@@ -538,11 +603,18 @@ function Pane({ placement }) {
|
|
|
538
603
|
const items = (section.items ?? []).filter((item) => fitsTyped(`item:${item.id}`, item.label));
|
|
539
604
|
return items.length > 0 ? [{ id: section.id, ...(section.heading ? { heading: section.heading } : {}), items }] : [];
|
|
540
605
|
});
|
|
541
|
-
|
|
542
|
-
const
|
|
606
|
+
/* A way to ask draws no rows: its button is under the field. Past the first acts, the rest wait behind More⦠until it is pressed or words are typed. */
|
|
607
|
+
const folded = atPointer && !expanded && words === "";
|
|
608
|
+
const startSections = narrowed(beside.start).filter((section) => (section.items?.length ?? 0) > 0 || section.render);
|
|
609
|
+
const endSections = narrowed(beside.end).filter((section) => (section.items?.length ?? 0) > 0 || section.render);
|
|
610
|
+
/* Which acts wait behind Moreβ¦: past the first few, while folded. Drawn, and hidden β no stop for the keyboard, nothing a screen reader is told β until Moreβ¦ is pressed. */
|
|
611
|
+
const behindMore = new Set(folded ? shown.slice(fits).map((entry) => entry.id) : []);
|
|
612
|
+
/* What this seat may not do waits behind More⦠only where the first acts fill the room; a seat offered little is told at once what is not theirs. */
|
|
613
|
+
const withheldFolded = folded && arranged.length >= fits;
|
|
614
|
+
const moreWaits = folded && (arranged.length > fits || (withheldFolded && withheld.length > 0) || observations.length > 0 || beside.end.some((section) => (section.items?.length ?? 0) > 0 || section.render));
|
|
543
615
|
/* What the words are asked about, said in the Ask row: the menu's title, the one record, else "this". */
|
|
544
616
|
const askedAbout = menuAt?.title ?? (selection.length === 1 ? nameOf(store, selection[0]) : selection.length > 1 ? `${selection.length} selected` : "this");
|
|
545
|
-
const askRow = atPointer && words !== "" &&
|
|
617
|
+
const askRow = atPointer && words !== "" && modes.length > 0;
|
|
546
618
|
/* Up to two that worked before, the Fast slot's likeliest first once it has answered. */
|
|
547
619
|
const toldShown = atPointer && words !== "" ? (fastRank === null ? toldLike : [...toldLike].sort((a, b) => (fastRank.get(b.key) ?? 0) - (fastRank.get(a.key) ?? 0))).slice(0, TOLD_SUGGESTED_MOST) : [];
|
|
548
620
|
/*
|
|
@@ -564,17 +636,32 @@ function Pane({ placement }) {
|
|
|
564
636
|
startSections.flatMap((section) => (section.items ?? []).map((item) => `item:${item.id}`))[0] ??
|
|
565
637
|
shown.filter((entry) => !entry.destructive).map((entry) => `act:${entry.id}`)[0] ??
|
|
566
638
|
endSections.flatMap((section) => (section.items ?? []).map((item) => `item:${item.id}`))[0] ??
|
|
567
|
-
(askRow ? "
|
|
568
|
-
const ask = () => {
|
|
639
|
+
(askRow && modeLit ? "mode" : null));
|
|
640
|
+
const ask = (asked = words) => {
|
|
569
641
|
if (!menuAt)
|
|
570
642
|
return;
|
|
571
|
-
const asked = words;
|
|
572
643
|
const back = menuAt.returnTo ?? null;
|
|
573
644
|
// The card opens where the menu stood, read before the menu goes.
|
|
574
645
|
const from = whereMenuStands(asideRef.current);
|
|
575
|
-
setMenuAt(null);
|
|
576
646
|
// The quick ask's card, where the menu stood, answering the words at once: its thinking marked on the thing itself.
|
|
577
|
-
|
|
647
|
+
setMenuAt(null);
|
|
648
|
+
openQuickAsk(aiDesk, { pointed: { on: menuAt.on, about: selection, field: menuAt.field, place: menuAt.place }, ...(asked ? { asked } : {}), returnTo: back, ...(from ? { from } : {}) });
|
|
649
|
+
};
|
|
650
|
+
/** A way to ask, pressed: Chat asks the quick ask (the words, or nothing yet); Better UI opens its card (the words asked at once, or to choose). */
|
|
651
|
+
const runMode = (mode, said = words) => {
|
|
652
|
+
if (!menuAt)
|
|
653
|
+
return;
|
|
654
|
+
if (mode === "chat")
|
|
655
|
+
return ask(said);
|
|
656
|
+
setMenuAt(null);
|
|
657
|
+
better?.open(said || undefined);
|
|
658
|
+
};
|
|
659
|
+
/** A suggestion taken: through its way's card, previewed there before anything lands. */
|
|
660
|
+
const takeSuggested = (one) => {
|
|
661
|
+
const told = one.source === "told" ? toldLike.find((each) => each.key === one.key) : undefined;
|
|
662
|
+
if (told && one.mode === "chat")
|
|
663
|
+
return takeTold(told);
|
|
664
|
+
runMode(one.mode === "better" && better ? "better" : "chat", one.label);
|
|
578
665
|
};
|
|
579
666
|
/* A suggestion taken: its words asked on the card, about what the menu is on, previewed like any plan β never applied from the menu. */
|
|
580
667
|
const takeTold = (one) => {
|
|
@@ -587,7 +674,8 @@ function Pane({ placement }) {
|
|
|
587
674
|
openQuickAsk(aiDesk, { pointed: { on: menuAt.on, about: selection, field: menuAt.field, place: menuAt.place }, asked: one.label, suggested: one.telling.id, returnTo: back, ...(from ? { from } : {}) });
|
|
588
675
|
};
|
|
589
676
|
/* The menu's rows the keyboard walks, in the order they are drawn: not a disabled one, nor a field of a form an act opened. */
|
|
590
|
-
|
|
677
|
+
/* The ways to ask are walked with the left and right arrows; the rows, up and down from the field. */
|
|
678
|
+
const walkable = () => [...(asideRef.current?.querySelectorAll("[data-affordance]:not([disabled]), [data-menu-item]:not([data-menu-mode])") ?? [])].filter((row) => row.closest("[data-graview-asking], [hidden]") === null);
|
|
591
679
|
const litRow = () => asideRef.current?.querySelector("[data-menu-lit]") ?? null;
|
|
592
680
|
/*
|
|
593
681
|
* A LETTER TYPED ANYWHERE IN THE MENU GOES INTO ITS FIELD, the keyboard
|
|
@@ -813,15 +901,7 @@ function Pane({ placement }) {
|
|
|
813
901
|
fontSize: "0.875rem",
|
|
814
902
|
lineHeight: 1,
|
|
815
903
|
borderRadius: 7,
|
|
816
|
-
}, children: "\u00D7" })),
|
|
817
|
-
["from", edgeEnds.from],
|
|
818
|
-
["to", edgeEnds.to],
|
|
819
|
-
].map(([which, end], index) => (_jsxs(Fragment, { children: [index === 1 ? (_jsx("span", { "aria-hidden": "true", style: { color: "var(--graview-ink-faint)" }, children: "\u2192" })) : null, end ? (_jsx("button", { type: "button", "data-testid": `edge-${which}`, onClick: () => set([end.id]), title: `Select ${nameOf(store, end.id)}`, style: { fontSize: "0.8125rem", padding: "3px 9px", borderRadius: 6 }, children: nameOf(store, end.id) })) : (_jsx("span", { style: { fontSize: "0.8125rem", color: "var(--graview-ink-faint)" }, children: "gone" }))] }, which))) })] })) : null, !edge && selection.length === 1 && isLeftAlone(store, selection[0]) ? (_jsx("p", { "data-testid": "left-alone", style: { margin: 0, fontSize: "0.8125rem", lineHeight: 1.45, color: "var(--graview-ink-muted)" }, children: "Left alone: nothing automated changes it." })) : null, said.length > 0 ? (_jsx("div", { "data-testid": "observations", title: said.map((observation) => observation.text).join("\n"), style: { display: "grid", gap: 3 }, children: said.slice(0, 2).map((observation) => (_jsxs("p", { style: {
|
|
820
|
-
margin: 0,
|
|
821
|
-
fontSize: "0.8125rem",
|
|
822
|
-
lineHeight: 1.45,
|
|
823
|
-
color: "var(--graview-ink-muted)",
|
|
824
|
-
}, children: [observation.text, observation === said[1] && said.length > 2 ? (_jsxs("span", { style: { color: "var(--graview-ink-faint)" }, children: [" +", said.length - 2] })) : null] }, observation.id))) })) : null, atPointer ? (_jsx("input", { ref: typeField, type: "text", "data-testid": "menu-type", "data-graview-not-first": "", value: typed, onChange: (event) => {
|
|
904
|
+
}, children: "\u00D7" })), atPointer ? (_jsx("input", { ref: typeField, type: "text", "data-testid": "menu-type", ...(FINE_POINTER() ? {} : { "data-graview-not-first": "" }), value: typed, onChange: (event) => {
|
|
825
905
|
// The first letter fetches what Enter will need; the filter itself never waits on it.
|
|
826
906
|
if (typed === "" && event.target.value !== "" && aiDesk)
|
|
827
907
|
preloadQuickAsk(aiDesk);
|
|
@@ -838,13 +918,22 @@ function Pane({ placement }) {
|
|
|
838
918
|
event.preventDefault();
|
|
839
919
|
(litRow() ?? walkable()[0])?.focus();
|
|
840
920
|
}
|
|
921
|
+
else if (event.key === "ArrowUp") {
|
|
922
|
+
// Round to the last row, as a menu's arrows go: More⦠where the rest wait behind it.
|
|
923
|
+
event.preventDefault();
|
|
924
|
+
walkable().at(-1)?.focus();
|
|
925
|
+
}
|
|
926
|
+
else if (event.key === "Enter" && !litRow() && modeLit) {
|
|
927
|
+
event.preventDefault();
|
|
928
|
+
runMode(modeLit);
|
|
929
|
+
}
|
|
841
930
|
else if (event.key === "Escape" && typed !== "") {
|
|
842
931
|
// The words go first; the next Escape puts the menu away.
|
|
843
932
|
event.preventDefault();
|
|
844
933
|
event.stopPropagation();
|
|
845
934
|
setTyped("");
|
|
846
935
|
}
|
|
847
|
-
}, placeholder:
|
|
936
|
+
}, placeholder: modes.length > 0 ? "Tell Graview what you wantβ¦" : "Type to filterβ¦", "aria-label": modes.length > 0 ? `Tell Graview what you want with ${askedAbout}, or filter what can be done here` : "Filter what can be done here", autoComplete: "off", autoCorrect: "off", spellCheck: false, enterKeyHint: "go", style: {
|
|
848
937
|
boxSizing: "border-box",
|
|
849
938
|
width: "100%",
|
|
850
939
|
font: "inherit",
|
|
@@ -855,7 +944,28 @@ function Pane({ placement }) {
|
|
|
855
944
|
border: "1px solid var(--graview-edge)",
|
|
856
945
|
background: "var(--graview-panel)",
|
|
857
946
|
color: "var(--graview-ink)",
|
|
858
|
-
} })) : null, atPointer
|
|
947
|
+
} })) : null, atPointer && modes.length > 0 ? (_jsx("div", { role: "group", "aria-label": modes.length > 1 ? "Chat or Better UI" : "Chat", "data-testid": "menu-modes", style: { display: "flex", gap: 6 }, children: modes.map((mode) => {
|
|
948
|
+
const on = modeLit === mode;
|
|
949
|
+
const label = mode === "chat" ? "Chat" : (better?.label ?? "Better UI");
|
|
950
|
+
const mark = mode === "chat" ? "π¬" : (better?.mark ?? "β¨");
|
|
951
|
+
return (_jsxs("button", { type: "button", "data-menu-mode": mode, ...(mode === "chat" ? { "data-menu-item": "ask-typed" } : {}), "data-menu-lit": (lit === "mode" && on) || undefined, "aria-pressed": on, "aria-label": words ? `${label}: ${words}` : label, onClick: () => runMode(mode), onKeyDown: (event) => {
|
|
952
|
+
if (event.key !== "ArrowLeft" && event.key !== "ArrowRight")
|
|
953
|
+
return;
|
|
954
|
+
event.preventDefault();
|
|
955
|
+
event.stopPropagation();
|
|
956
|
+
const next = modes[(modes.indexOf(mode) + (event.key === "ArrowRight" ? 1 : modes.length - 1)) % modes.length];
|
|
957
|
+
setModeChosen(next);
|
|
958
|
+
asideRef.current?.querySelector(`[data-menu-mode="${next}"]`)?.focus();
|
|
959
|
+
}, style: on ? MODE_ON : MODE_OFF, children: [_jsx("span", { "aria-hidden": "true", children: mark }), " ", label, lit === "mode" && on ? ENTER_MARK : null] }, mode));
|
|
960
|
+
}) })) : null, atPointer && words === "" && suggested.length > 0 ? (_jsxs("div", { "data-menu-section": "suggested", role: "group", "aria-label": "Suggested", style: { display: "grid", gap: 2, borderTop: "1px solid var(--graview-edge)", paddingTop: 5, minHeight: suggested.length * 31 + 22 }, children: [_jsx("span", { "aria-hidden": "true", style: GROUP_HEAD, children: "Suggested" }), suggested.map((one, at) => (_jsxs("button", { type: "button", "data-menu-item": `suggested-${at}`, "data-suggested-mode": one.mode, onClick: () => takeSuggested(one), style: { ...ROW_STYLE, display: "flex", gap: 8, alignItems: "baseline", minWidth: 0 }, children: [_jsx("span", { "aria-hidden": "true", style: { color: "var(--graview-accent)" }, children: "\u2726" }), _jsx("span", { title: one.label, style: { flex: "1 1 auto", minWidth: 0, overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }, children: one.label }), _jsxs("span", { title: one.mode === "better" ? "Better UI" : "Chat", style: { fontSize: "0.8125rem" }, children: [_jsx("span", { "aria-hidden": "true", children: one.mode === "better" ? "β¨" : "π¬" }), _jsx("span", { style: VISUALLY_HIDDEN, children: one.mode === "better" ? " (Better UI)" : " (Chat)" })] })] }, one.key)))] })) : null, edge && edgeEnds ? (_jsxs("div", { style: { display: "grid", gap: 6 }, children: [edgeSaid ? (_jsx("p", { "data-testid": "edge-said", style: { margin: 0, fontSize: "0.8125rem", lineHeight: 1.45, color: "var(--graview-ink-muted)" }, children: edgeSaid })) : null, unsure ? (_jsx("p", { "data-testid": "edge-provisional", style: { margin: 0, fontSize: "0.8125rem", lineHeight: 1.45, color: "var(--graview-ink-muted)" }, children: provenanceWords(unsure) })) : null, _jsx("div", { style: { display: "flex", alignItems: "center", gap: 6, flexWrap: "wrap" }, children: [
|
|
961
|
+
["from", edgeEnds.from],
|
|
962
|
+
["to", edgeEnds.to],
|
|
963
|
+
].map(([which, end], index) => (_jsxs(Fragment, { children: [index === 1 ? (_jsx("span", { "aria-hidden": "true", style: { color: "var(--graview-ink-faint)" }, children: "\u2192" })) : null, end ? (_jsx("button", { type: "button", "data-testid": `edge-${which}`, onClick: () => set([end.id]), title: `Select ${nameOf(store, end.id)}`, style: { fontSize: "0.8125rem", padding: "3px 9px", borderRadius: 6 }, children: nameOf(store, end.id) })) : (_jsx("span", { style: { fontSize: "0.8125rem", color: "var(--graview-ink-faint)" }, children: "gone" }))] }, which))) })] })) : null, !edge && selection.length === 1 && isLeftAlone(store, selection[0]) ? (_jsx("p", { "data-testid": "left-alone", style: { margin: 0, fontSize: "0.8125rem", lineHeight: 1.45, color: "var(--graview-ink-muted)" }, children: "Left alone: nothing automated changes it." })) : null, said.length > 0 ? (_jsx("div", { "data-testid": "observations", title: said.map((observation) => observation.text).join("\n"), hidden: folded, style: { display: folded ? "none" : "grid", gap: 3 }, children: said.slice(0, 2).map((observation) => (_jsxs("p", { style: {
|
|
964
|
+
margin: 0,
|
|
965
|
+
fontSize: "0.8125rem",
|
|
966
|
+
lineHeight: 1.45,
|
|
967
|
+
color: "var(--graview-ink-muted)",
|
|
968
|
+
}, children: [observation.text, observation === said[1] && said.length > 2 ? (_jsxs("span", { style: { color: "var(--graview-ink-faint)" }, children: [" +", said.length - 2] })) : null] }, observation.id))) })) : null, atPointer ? startSections.map((section) => _jsx(SectionDrawn, { section: section, lit: lit, close: () => setMenuAt(null) }, section.id)) : null, failed ? (_jsx("p", { "data-testid": "refused", style: { margin: 0, fontSize: "0.8125rem", lineHeight: 1.45, color: "var(--graview-warn)" }, children: failed })) : null, searchable ? (_jsx("input", { "data-testid": "action-filter", value: query, onChange: (event) => setQuery(event.target.value), placeholder: `Filter ${arranged.length} actionsβ¦`, "aria-label": "Filter actions", onKeyDown: (event) => {
|
|
859
969
|
if (event.key === "Escape") {
|
|
860
970
|
// Clearing the filter must not also clear the selection β
|
|
861
971
|
// the document-level Escape stays out of it either way.
|
|
@@ -907,8 +1017,8 @@ function Pane({ placement }) {
|
|
|
907
1017
|
? withArticle(nounOf(store.schema.tryDefinition(subjectKinds[0]), subjectKinds[0]))
|
|
908
1018
|
: "this mix of kinds"} β ${withheld.length} action${withheld.length === 1 ? "" : "s"} withheld.`
|
|
909
1019
|
: edge
|
|
910
|
-
? `
|
|
911
|
-
:
|
|
1020
|
+
? `There is nothing to do with this line here: nothing in this app makes or breaks β${edgeWords(store.schema, store.graph.getNode(edge.from)?.kind ?? "", edge.kind, "out")}β.`
|
|
1021
|
+
: emptyWords(store, subjectKinds, selection) })) : (_jsxs("ol", { ...roving, "data-testid": "affordances", style: {
|
|
912
1022
|
margin: 0,
|
|
913
1023
|
padding: 0,
|
|
914
1024
|
listStyle: "none",
|
|
@@ -935,7 +1045,7 @@ function Pane({ placement }) {
|
|
|
935
1045
|
: {}),
|
|
936
1046
|
padding: "2px 2px 1px",
|
|
937
1047
|
marginTop: index > 0 ? 6 : 0,
|
|
938
|
-
}, children: [section.tone === "violation" ? "β " : "", section.tone === "violation" ? trim(section.heading) : section.heading] })) : index > 0 ? (_jsx("li", { "aria-hidden": "true", style: { borderTop: "1px solid var(--graview-edge)", margin: "6px 0 3px" } })) : null, section.items.map((affordance) => (_jsxs("li", { style: { display: "flex", gap: 2, alignItems: "stretch" }, onKeyDown: (event) => {
|
|
1048
|
+
}, children: [section.tone === "violation" ? "β " : "", section.tone === "violation" ? trim(section.heading) : section.heading] })) : index > 0 ? (_jsx("li", { "aria-hidden": "true", style: { borderTop: "1px solid var(--graview-edge)", margin: "6px 0 3px" } })) : null, section.items.map((affordance) => (_jsxs("li", { hidden: behindMore.has(affordance.id), style: { display: behindMore.has(affordance.id) ? "none" : "flex", gap: 2, alignItems: "stretch" }, onKeyDown: (event) => {
|
|
939
1049
|
// Its pin is beside it: the arrow to the right, and back.
|
|
940
1050
|
if (event.metaKey || event.ctrlKey || event.altKey)
|
|
941
1051
|
return;
|
|
@@ -1021,18 +1131,18 @@ function Pane({ placement }) {
|
|
|
1021
1131
|
: affordance.pinned === "declared"
|
|
1022
1132
|
? "var(--graview-ink-muted)"
|
|
1023
1133
|
: "var(--graview-ink-faint)",
|
|
1024
|
-
}, children: affordance.pinned !== undefined ? "β
" : "β" })] }, affordance.id)))] }, `${section.heading ?? "plain"}-${index}`))), hidden > 0 ? (_jsx("li", { children: _jsxs("button", { type: "button", [ROVING_ITEM]: "", onClick: () => setExpanded(true),
|
|
1134
|
+
}, children: affordance.pinned !== undefined ? "β
" : "β" })] }, affordance.id)))] }, `${section.heading ?? "plain"}-${index}`))), hidden > 0 && !atPointer ? (_jsx("li", { children: _jsxs("button", { type: "button", [ROVING_ITEM]: "", onClick: () => setExpanded(true),
|
|
1025
1135
|
// What it DOES, not what is behind it. "+9 more" is a label
|
|
1026
1136
|
// on a quantity; this is a control, and a control says what
|
|
1027
1137
|
// pressing it will do.
|
|
1028
1138
|
title: `Show all ${affordances.length} actions`, style: { padding: "4px 10px", fontSize: "0.875rem", borderRadius: 8 }, children: ["Show ", hidden, " more"] }) })) : null] })), gain ? (_jsx("button", { type: "button", "data-testid": "sign-in", onClick: () => {
|
|
1029
1139
|
setMenuAt(null);
|
|
1030
1140
|
gain.run();
|
|
1031
|
-
}, style: { padding: "5px 10px", fontSize: "0.875rem", borderRadius: 8, width: "100%", textAlign: "left" }, children: gain.label })) : null, withheldShown.length > 0 ? (_jsxs("ul", { "data-testid": "withheld", style: {
|
|
1141
|
+
}, style: { padding: "5px 10px", fontSize: "0.875rem", borderRadius: 8, width: "100%", textAlign: "left" }, children: gain.label })) : null, withheldShown.length > 0 ? (_jsxs("ul", { hidden: withheldFolded, "data-testid": "withheld", style: {
|
|
1032
1142
|
margin: 0,
|
|
1033
1143
|
padding: 0,
|
|
1034
1144
|
listStyle: "none",
|
|
1035
|
-
display: "flex",
|
|
1145
|
+
display: withheldFolded ? "none" : "flex",
|
|
1036
1146
|
flexDirection: "column",
|
|
1037
1147
|
flexWrap: "nowrap",
|
|
1038
1148
|
gap: 2,
|
|
@@ -1049,7 +1159,19 @@ function Pane({ placement }) {
|
|
|
1049
1159
|
lineHeight: 1.4,
|
|
1050
1160
|
color: "var(--graview-ink-muted)",
|
|
1051
1161
|
padding: "0 10px",
|
|
1052
|
-
}, children: action.refusal.message })] }, action.id))), !atPointer && !allWithheld && withheld.length > 3 ? (_jsx("li", { children: _jsxs("button", { type: "button", "data-testid": "withheld-more", onClick: () => setAllWithheld(true), title: `Show all ${withheld.length} actions this seat may not take`, style: { padding: "4px 10px", fontSize: "0.875rem", borderRadius: 8 }, children: ["Show ", withheld.length - 3, " more withheld"] }) })) : null] })) : null,
|
|
1162
|
+
}, children: action.refusal.message })] }, action.id))), !atPointer && !allWithheld && withheld.length > 3 ? (_jsx("li", { children: _jsxs("button", { type: "button", "data-testid": "withheld-more", onClick: () => setAllWithheld(true), title: `Show all ${withheld.length} actions this seat may not take`, style: { padding: "4px 10px", fontSize: "0.875rem", borderRadius: 8 }, children: ["Show ", withheld.length - 3, " more withheld"] }) })) : null] })) : null, moreWaits ? (_jsx("button", { type: "button", "data-menu-item": "more", onClick: (event) => {
|
|
1163
|
+
/* The keyboard goes on to the first of what was waiting, where a person reading down would be next. */
|
|
1164
|
+
const menu = event.currentTarget.parentElement;
|
|
1165
|
+
const waiting = [...(menu?.querySelectorAll("li[hidden], [data-menu-more][hidden]") ?? [])];
|
|
1166
|
+
setExpanded(true);
|
|
1167
|
+
requestAnimationFrame(() => {
|
|
1168
|
+
for (const one of waiting) {
|
|
1169
|
+
const to = one.querySelector("button:not([disabled]), a[href], input");
|
|
1170
|
+
if (to && to.getClientRects().length > 0)
|
|
1171
|
+
return to.focus();
|
|
1172
|
+
}
|
|
1173
|
+
});
|
|
1174
|
+
}, style: { ...ROW_STYLE, color: "var(--graview-ink-muted)" }, children: "More\u2026" })) : null, atPointer && endSections.length > 0 ? (_jsx("div", { "data-menu-more": "", hidden: folded, style: { display: folded ? "none" : "contents" }, children: endSections.filter((section) => !folded || !section.render).map((section) => _jsx(SectionDrawn, { section: section, lit: lit, close: () => setMenuAt(null) }, section.id)) })) : null, toldShown.length > 0 ? (_jsx("div", { "data-menu-section": "told", role: "group", "aria-label": "Suggested", style: { display: "grid", gap: 2, borderTop: "1px solid var(--graview-edge)", paddingTop: 5 }, children: toldShown.map((one) => (_jsxs("button", { type: "button", "data-menu-item": one.key, "data-told-suggestion": one.telling.id, "data-menu-lit": lit === one.key || undefined, onClick: () => takeTold(one), style: { ...ROW_STYLE, ...(lit === one.key ? LIT_STYLE : {}) }, children: [one.label, lit === one.key ? ENTER_MARK : null] }, one.key))) })) : null, atPointer && words !== "" && !askRow && lit === null && shown.length === 0 && withheldShown.length === 0 ? (_jsxs("p", { "data-testid": "menu-no-match", style: { margin: 0, fontSize: "0.8125rem", lineHeight: 1.45, color: "var(--graview-ink-muted)" }, children: ["Nothing here matches \u201C", words, "\u201D."] })) : null, open ? (_jsx(AnswerArgs, { affordance: open,
|
|
1053
1175
|
// On a refusal the prompt stays put with the reason beside it, so
|
|
1054
1176
|
// a rejected answer can be corrected rather than retyped blind.
|
|
1055
1177
|
onApply: (args) => {
|
|
@@ -1068,6 +1190,23 @@ function typingIn(element) {
|
|
|
1068
1190
|
/** A menu's row, and the one Enter runs from the field, lit, with the key that runs it said. */
|
|
1069
1191
|
const ROW_STYLE = { padding: "5px 10px", fontSize: "0.875rem", borderRadius: 8, width: "100%", textAlign: "left", borderWidth: 1, borderStyle: "solid", borderColor: "transparent", background: "none", boxShadow: "none" };
|
|
1070
1192
|
const LIT_STYLE = { background: "var(--graview-panel-muted)" };
|
|
1193
|
+
/** One object for the graph as it stands, a new one once anything is applied: what a list's Suggested is asked again by. */
|
|
1194
|
+
const STAMPS = /* @__PURE__ */ new WeakMap();
|
|
1195
|
+
function graphStamp(store) {
|
|
1196
|
+
const length = store.log.all().length;
|
|
1197
|
+
const held = STAMPS.get(store);
|
|
1198
|
+
if (held && held.length === length)
|
|
1199
|
+
return held.stamp;
|
|
1200
|
+
const stamp = { length };
|
|
1201
|
+
STAMPS.set(store, { length, stamp });
|
|
1202
|
+
return stamp;
|
|
1203
|
+
}
|
|
1204
|
+
/** Whether the pointer is a fine one (a mouse, a trackpad): there the menu's field takes the keyboard as it opens. */
|
|
1205
|
+
const FINE_POINTER = () => typeof matchMedia === "function" && matchMedia("(pointer: fine)").matches;
|
|
1206
|
+
const MODE_BASE = { display: "inline-flex", alignItems: "center", gap: 5, minHeight: 30, padding: "0 11px", fontSize: "0.8125rem", borderRadius: 999, cursor: "pointer", borderWidth: 1, borderStyle: "solid", boxShadow: "none" };
|
|
1207
|
+
const MODE_ON = { ...MODE_BASE, borderColor: "var(--graview-accent)", background: "color-mix(in srgb, var(--graview-accent) 14%, transparent)", color: "var(--graview-ink)", fontWeight: 600 };
|
|
1208
|
+
const MODE_OFF = { ...MODE_BASE, borderColor: "var(--graview-edge)", background: "none", color: "var(--graview-ink-muted)" };
|
|
1209
|
+
const GROUP_HEAD = { fontSize: "0.6875rem", letterSpacing: "0.12em", textTransform: "uppercase", color: "var(--graview-ink-faint)", padding: "0 10px" };
|
|
1071
1210
|
const ENTER_MARK = (_jsx("span", { "aria-hidden": "true", title: "Enter runs it", style: { float: "right", color: "var(--graview-ink-faint)", fontSize: "0.75rem" }, children: "\u21B5" }));
|
|
1072
1211
|
/** One shared empty, for a visitor told the way in instead. */
|
|
1073
1212
|
const NOTHING_WITHHELD = [];
|