@graview/primitives 0.1.57 → 0.1.59
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/figure.d.ts.map +1 -1
- package/dist/figure.js +5 -4
- package/dist/figure.js.map +1 -1
- package/dist/find.d.ts.map +1 -1
- package/dist/find.js +464 -122
- package/dist/find.js.map +1 -1
- package/dist/seeding.d.ts.map +1 -1
- package/dist/seeding.js +41 -19
- package/dist/seeding.js.map +1 -1
- package/dist/theme.d.ts.map +1 -1
- package/dist/theme.js +6 -1
- package/dist/theme.js.map +1 -1
- package/dist/way-back.d.ts.map +1 -1
- package/dist/way-back.js +2 -5
- package/dist/way-back.js.map +1 -1
- package/dist/workbench/activity.d.ts +6 -4
- package/dist/workbench/activity.d.ts.map +1 -1
- package/dist/workbench/activity.js +13 -11
- package/dist/workbench/activity.js.map +1 -1
- package/package.json +6 -6
package/dist/find.js
CHANGED
|
@@ -1,32 +1,50 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
-
import { counted, numberAsRead } from "@graview/core";
|
|
3
|
-
import { actsOn, search, touchWeights } from "@graview/core/search";
|
|
2
|
+
import { counted, nounOf, numberAsRead, pluralLabel, pluralOf } from "@graview/core";
|
|
3
|
+
import { actsOn, fold, search, touchWeights } from "@graview/core/search";
|
|
4
4
|
import { kindCardId, withFocus, withJackIn, withOverview, withPicture, withSelection } from "@graview/layout/view";
|
|
5
5
|
import { withoutSearch, withQuery } from "@graview/layout/view-moves";
|
|
6
6
|
import { useKit } from "@graview/react/drawing";
|
|
7
7
|
import { matchWith, POPOVER_STYLE, useFound, useGraview, usePopover, useViolations } from "@graview/react/provider";
|
|
8
8
|
import { hueFor } from "@graview/render";
|
|
9
|
-
import { useCallback, useEffect, useId, useMemo, useRef, useState } from "react";
|
|
9
|
+
import { Fragment, useCallback, useEffect, useId, useMemo, useRef, useState } from "react";
|
|
10
10
|
import { VISUALLY_HIDDEN } from "./primitives/index.js";
|
|
11
11
|
import { openQuickAsk, useQuickAskDrawn } from "./quick-ask-desk.js";
|
|
12
12
|
// The Find box searches: it hands the provider the matcher as it is fetched (FR-179).
|
|
13
13
|
matchWith({ search, touchWeights });
|
|
14
|
+
const isAct = (row) => row !== undefined && "act" in row;
|
|
15
|
+
const recordOf = (row) => (row !== undefined && "hit" in row && row.hit.about === "node" ? row : undefined);
|
|
14
16
|
/** The kind a hit belongs with in the strip; rules have a group of their own. */
|
|
15
17
|
const groupOf = (hit) => (hit.about === "rule" ? "rule:" : hit.about === "act" ? "act:" : hit.kind);
|
|
18
|
+
/** Whether the viewport is a desk's: room for the panel beside the list. */
|
|
19
|
+
function useWide(compact) {
|
|
20
|
+
const measure = () => typeof window === "undefined" || window.innerWidth >= WIDE_FROM;
|
|
21
|
+
const [wide, setWide] = useState(measure);
|
|
22
|
+
useEffect(() => {
|
|
23
|
+
const again = () => setWide(measure());
|
|
24
|
+
window.addEventListener("resize", again);
|
|
25
|
+
return () => window.removeEventListener("resize", again);
|
|
26
|
+
}, []);
|
|
27
|
+
return wide && !compact;
|
|
28
|
+
}
|
|
16
29
|
export function FindBox({ compact = false }) {
|
|
17
30
|
const { store, view, setView, principal, setMenuAt, brand, aiDesk, selection } = useGraview();
|
|
18
31
|
const asks = useQuickAskDrawn(aiDesk);
|
|
19
32
|
const found = useFound();
|
|
20
33
|
const violations = useViolations();
|
|
21
34
|
const flag = useKit().marks.flag;
|
|
35
|
+
const wide = useWide(compact);
|
|
22
36
|
const input = useRef(null);
|
|
37
|
+
const list = useRef(null);
|
|
23
38
|
// Where the keyboard waits between Enter and the place standing (see `hold`).
|
|
24
39
|
const holder = useRef(null);
|
|
25
40
|
const strip = useId();
|
|
26
41
|
const [open, setOpen] = useState(false);
|
|
42
|
+
// The row lit: the keys' or the pointer's.
|
|
27
43
|
const [active, setActive] = useState(null);
|
|
28
|
-
// The
|
|
29
|
-
const [
|
|
44
|
+
// The row the desk's panel says: the lit one, a moment behind a pointer crossing rows on its way to the panel.
|
|
45
|
+
const [shown, setShown] = useState(null);
|
|
46
|
+
// The records whose acts were asked for under their rows (a narrow strip's "⋯", or →).
|
|
47
|
+
const [opened, setOpened] = useState([]);
|
|
30
48
|
const q = view.q ?? "";
|
|
31
49
|
/*
|
|
32
50
|
* `/` OR ⌘K, FROM ANYWHERE. ⌘K is a chord nobody types into a field, so
|
|
@@ -74,10 +92,11 @@ export function FindBox({ compact = false }) {
|
|
|
74
92
|
return () => window.removeEventListener("keydown", onKey);
|
|
75
93
|
}, []);
|
|
76
94
|
/*
|
|
77
|
-
*
|
|
78
|
-
*
|
|
95
|
+
* THE GROUPS: a kind's rows under its heading, the kinds in the order of
|
|
96
|
+
* their best hit. Where several kinds share the strip each shows its best
|
|
97
|
+
* few and a row to all of them, so one kind of a hundred hits does not push
|
|
98
|
+
* every other kind out of sight; one kind alone shows its rows whole.
|
|
79
99
|
*/
|
|
80
|
-
const acts = useMemo(() => (anchor && q.trim() ? actsOn(store, anchor, found?.words ?? q, { principal, limit: 4 }) : []), [store, q, found, anchor, principal]);
|
|
81
100
|
const groups = useMemo(() => {
|
|
82
101
|
const held = new Map();
|
|
83
102
|
for (const [index, hit] of (found?.hits ?? []).entries()) {
|
|
@@ -87,21 +106,53 @@ export function FindBox({ compact = false }) {
|
|
|
87
106
|
const rows = held.get(key) ?? [];
|
|
88
107
|
rows.push({ id: `${strip}-${index}`, hit });
|
|
89
108
|
held.set(key, rows);
|
|
90
|
-
if (hit.about === "node" && hit.id === anchor) {
|
|
91
|
-
for (const [at, act] of acts.entries())
|
|
92
|
-
rows.push({ id: `${strip}-${index}-act-${at}`, hit: act });
|
|
93
|
-
}
|
|
94
109
|
}
|
|
95
|
-
|
|
96
|
-
|
|
110
|
+
const several = [...held.values()].filter((rows) => rows.some((row) => row.hit.about === "node")).length > 1;
|
|
111
|
+
return [...held.entries()].map(([key, entries]) => {
|
|
112
|
+
let records = 0;
|
|
113
|
+
const rows = entries.filter((row) => row.hit.about !== "node" || !several || ++records <= GROUP_CAP);
|
|
114
|
+
const listed = rows.filter((row) => row.hit.about === "node").length;
|
|
115
|
+
const count = key.endsWith(":") ? entries.length : (found?.byKind[key] ?? listed);
|
|
116
|
+
const more = !key.endsWith(":") && listed > 0 && count > listed ? { id: `${strip}-all-${key}`, all: key, count } : null;
|
|
117
|
+
return { key, rows, count, more };
|
|
118
|
+
});
|
|
119
|
+
}, [found, strip]);
|
|
97
120
|
const asking = asks && q.trim() ? { id: `${strip}-ask`, ask: q.trim() } : null;
|
|
98
|
-
const rows = useMemo(() => [...groups.flatMap(([,
|
|
99
|
-
const current = rows.find((row) => row.id === active) ?? rows[0];
|
|
121
|
+
const rows = useMemo(() => [...groups.flatMap((group) => (group.more ? [...group.rows, group.more] : group.rows)), ...(asking ? [asking] : [])], [groups, asking?.ask]);
|
|
100
122
|
const manyFound = (found?.hits ?? []).filter((hit) => hit.about === "node").length;
|
|
123
|
+
// The words as the matcher folded them, and what they mark in a row.
|
|
124
|
+
const words = found?.words ?? q;
|
|
125
|
+
const marking = useMemo(() => fold(words).split(" ").filter(Boolean), [words]);
|
|
126
|
+
/*
|
|
127
|
+
* WHAT CAN BE DONE ABOUT IT: the acts on a record, from the same matcher
|
|
128
|
+
* asked with that record as the subject — never subjectless, and only
|
|
129
|
+
* those this seat may run.
|
|
130
|
+
*/
|
|
131
|
+
const actsOf = useCallback((row) => row.hit.about === "node" ? actsOn(store, row.hit.id, words, { principal, limit: ACTS_MOST }).map((act, at) => ({ id: `${row.id}-act-${at}`, act, of: row.id })) : [], [store, words, principal]);
|
|
132
|
+
const lit = rows.find((row) => row.id === active);
|
|
133
|
+
const subject = rows.find((row) => row.id === shown) ?? lit ?? rows[0];
|
|
134
|
+
const panelActs = useMemo(() => (wide && recordOf(subject) ? actsOf(subject) : []), [wide, subject?.id, actsOf]);
|
|
135
|
+
const openActs = useMemo(() => {
|
|
136
|
+
if (wide)
|
|
137
|
+
return new Map();
|
|
138
|
+
return new Map(opened.flatMap((id) => {
|
|
139
|
+
const row = recordOf(rows.find((one) => one.id === id));
|
|
140
|
+
return row ? [[id, actsOf(row)]] : [];
|
|
141
|
+
}));
|
|
142
|
+
}, [wide, opened, rows, actsOf]);
|
|
143
|
+
// In a narrow strip, which records have acts to ask for: a "⋯" that opened on nothing would be offered and refused.
|
|
144
|
+
const hasActs = useMemo(() => {
|
|
145
|
+
if (wide)
|
|
146
|
+
return new Set();
|
|
147
|
+
return new Set(rows.flatMap((row) => (recordOf(row) && actsOf(row).length > 0 ? [row.id] : [])));
|
|
148
|
+
}, [wide, rows, actsOf]);
|
|
149
|
+
const actRows = useMemo(() => (wide ? panelActs : [...openActs.values()].flat()), [wide, panelActs, openActs]);
|
|
150
|
+
const current = lit ?? actRows.find((row) => row.id === active) ?? rows[0];
|
|
101
151
|
// A new answer starts at its first row, as a list does.
|
|
102
152
|
useEffect(() => {
|
|
103
153
|
setActive(null);
|
|
104
|
-
|
|
154
|
+
setShown(null);
|
|
155
|
+
setOpened([]);
|
|
105
156
|
}, [q]);
|
|
106
157
|
/*
|
|
107
158
|
* WHERE ENTER WENT, THE KEYS GO (the tenth walk). Into a kind or one of
|
|
@@ -164,22 +215,63 @@ export function FindBox({ compact = false }) {
|
|
|
164
215
|
waiting.setAttribute("aria-label", going);
|
|
165
216
|
waiting.focus({ preventScroll: true });
|
|
166
217
|
};
|
|
218
|
+
/*
|
|
219
|
+
* THE POINTER LIGHTS, IT DOES NOT MOVE ANYTHING. A row is lit by the
|
|
220
|
+
* pointer only when the pointer itself moved: a list scrolled, or drawn
|
|
221
|
+
* again, under a still pointer sends a mouse event at the same place, and
|
|
222
|
+
* that lit whatever row slid under it. And the panel follows the pointer a
|
|
223
|
+
* moment behind (`AIM`), so a pointer crossing rows on its way to an act
|
|
224
|
+
* in the panel finds the record it left still there.
|
|
225
|
+
*/
|
|
226
|
+
const pointer = useRef({ x: Number.NaN, y: Number.NaN, moved: false });
|
|
227
|
+
useEffect(() => {
|
|
228
|
+
const onMove = (event) => {
|
|
229
|
+
const at = pointer.current;
|
|
230
|
+
at.moved = event.clientX !== at.x || event.clientY !== at.y;
|
|
231
|
+
at.x = event.clientX;
|
|
232
|
+
at.y = event.clientY;
|
|
233
|
+
};
|
|
234
|
+
window.addEventListener("mousemove", onMove, { capture: true, passive: true });
|
|
235
|
+
return () => window.removeEventListener("mousemove", onMove, { capture: true });
|
|
236
|
+
}, []);
|
|
237
|
+
const aim = useRef(undefined);
|
|
238
|
+
useEffect(() => () => clearTimeout(aim.current), []);
|
|
239
|
+
/** Lights a row from the keyboard: the panel says it at once. */
|
|
167
240
|
const highlight = (row) => {
|
|
168
241
|
if (!row)
|
|
169
242
|
return;
|
|
243
|
+
clearTimeout(aim.current);
|
|
170
244
|
setActive(row.id);
|
|
171
|
-
if (
|
|
172
|
-
|
|
245
|
+
if (!isAct(row))
|
|
246
|
+
setShown(row.id);
|
|
247
|
+
};
|
|
248
|
+
/** Lights the row under a pointer that moved. */
|
|
249
|
+
const point = (event) => {
|
|
250
|
+
if (!pointer.current.moved)
|
|
173
251
|
return;
|
|
174
|
-
|
|
175
|
-
if (
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
252
|
+
const id = event.target.closest?.('[role="option"]')?.id;
|
|
253
|
+
if (!id || id === active)
|
|
254
|
+
return;
|
|
255
|
+
const row = rows.find((one) => one.id === id) ?? actRows.find((one) => one.id === id);
|
|
256
|
+
if (!row)
|
|
257
|
+
return;
|
|
258
|
+
setActive(row.id);
|
|
259
|
+
if (isAct(row))
|
|
260
|
+
return;
|
|
261
|
+
clearTimeout(aim.current);
|
|
262
|
+
aim.current = setTimeout(() => setShown(row.id), AIM);
|
|
263
|
+
};
|
|
264
|
+
/** Into the panel: whatever row the pointer crossed on its way, the record the panel says is the one lit. */
|
|
265
|
+
const settle = () => {
|
|
266
|
+
clearTimeout(aim.current);
|
|
267
|
+
if (subject && !isAct(current))
|
|
268
|
+
setActive(subject.id);
|
|
179
269
|
};
|
|
270
|
+
/** A record's acts, asked for under its row; asked again, put away. */
|
|
271
|
+
const toggle = (row) => setOpened((now) => (now.includes(row.id) ? now.filter((id) => id !== row.id) : [...now, row.id]));
|
|
180
272
|
/** How many rows a page of the strip is: what it shows at once, less one kept for context. */
|
|
181
273
|
const pageOf = (count) => {
|
|
182
|
-
const pane =
|
|
274
|
+
const pane = list.current;
|
|
183
275
|
const one = pane?.querySelector('[role="option"]')?.getBoundingClientRect().height ?? 0;
|
|
184
276
|
const room = pane?.clientHeight ?? 0;
|
|
185
277
|
return Math.min(count, one > 0 && room > 0 ? Math.max(1, Math.floor(room / one) - 1) : FIND_PAGE);
|
|
@@ -199,6 +291,23 @@ export function FindBox({ compact = false }) {
|
|
|
199
291
|
setOpen(false);
|
|
200
292
|
return;
|
|
201
293
|
}
|
|
294
|
+
if ("all" in row) {
|
|
295
|
+
// Every one the words found of the kind: into it, narrowed by the words, as the district's "N match" goes in.
|
|
296
|
+
setView((stop) => withJackIn(withOverview(stop, false), kindCardId(row.all), { ownPicture: true, ...(stop.q ? { carry: stop.q } : {}) }));
|
|
297
|
+
landOn.current = row.all;
|
|
298
|
+
setOpen(false);
|
|
299
|
+
return hold(row.all, `Opening ${pluralLabel(store.schema, row.all)} matching “${q.trim()}”…`);
|
|
300
|
+
}
|
|
301
|
+
if (isAct(row)) {
|
|
302
|
+
// The act lives in the menu on its subject, with its form and its preview.
|
|
303
|
+
const subjectId = row.act.subject;
|
|
304
|
+
setView((stop) => withSelection(stop, [subjectId]));
|
|
305
|
+
const box = input.current?.getBoundingClientRect();
|
|
306
|
+
setMenuAt({ x: box?.left ?? 0, y: (box?.bottom ?? 0) + 6, on: subjectId });
|
|
307
|
+
setOpen(false);
|
|
308
|
+
input.current?.blur();
|
|
309
|
+
return;
|
|
310
|
+
}
|
|
202
311
|
const hit = row.hit;
|
|
203
312
|
switch (hit.about) {
|
|
204
313
|
case "node":
|
|
@@ -226,13 +335,8 @@ export function FindBox({ compact = false }) {
|
|
|
226
335
|
setView((stop) => withSelection(stop, [...new Set(named)]));
|
|
227
336
|
break;
|
|
228
337
|
}
|
|
229
|
-
case "act":
|
|
230
|
-
// The act lives in the menu on its subject, with its form and its preview.
|
|
231
|
-
setView((stop) => withSelection(stop, [hit.subject]));
|
|
232
|
-
const box = input.current?.getBoundingClientRect();
|
|
233
|
-
setMenuAt({ x: box?.left ?? 0, y: (box?.bottom ?? 0) + 6, on: hit.subject });
|
|
338
|
+
case "act":
|
|
234
339
|
break;
|
|
235
|
-
}
|
|
236
340
|
}
|
|
237
341
|
setOpen(false);
|
|
238
342
|
input.current?.blur();
|
|
@@ -266,76 +370,100 @@ export function FindBox({ compact = false }) {
|
|
|
266
370
|
},
|
|
267
371
|
...(compact ? { at: underTheBar } : {}),
|
|
268
372
|
});
|
|
373
|
+
const nothing = rows.every((row) => "ask" in row);
|
|
374
|
+
const actsOfRecord = (row) => (isAct(row) ? actRows.filter((one) => one.of === row.of) : []);
|
|
375
|
+
const onKeyDown = (event) => {
|
|
376
|
+
/*
|
|
377
|
+
* RECORD TO RECORD, ONE KEY EACH (the tenth walk). ↓ and ↑ walk the
|
|
378
|
+
* records; → goes into the acts of the one lit — the desk's panel, or
|
|
379
|
+
* opened under it in a narrow strip — and ← back to it. In the acts ↓
|
|
380
|
+
* and ↑ walk the acts.
|
|
381
|
+
*/
|
|
382
|
+
const inActs = isAct(current) && active !== null;
|
|
383
|
+
const from = inActs ? rows.find((row) => row.id === current.of) : active !== null ? lit : undefined;
|
|
384
|
+
// Nothing lit yet: the first ↓ lands on the first row, not the second.
|
|
385
|
+
const at = from ? rows.indexOf(from) : -1;
|
|
386
|
+
if (event.key === "ArrowDown") {
|
|
387
|
+
event.preventDefault();
|
|
388
|
+
setOpen(true);
|
|
389
|
+
if (inActs) {
|
|
390
|
+
const acts = actsOfRecord(current);
|
|
391
|
+
const next = acts.indexOf(current) + 1;
|
|
392
|
+
if (next < acts.length)
|
|
393
|
+
return highlight(acts[next]);
|
|
394
|
+
// Past the last act: under a row, the next record; in the panel, it stays.
|
|
395
|
+
if (wide)
|
|
396
|
+
return;
|
|
397
|
+
}
|
|
398
|
+
highlight(rows[Math.min(rows.length - 1, at + 1)]);
|
|
399
|
+
}
|
|
400
|
+
else if (event.key === "ArrowUp") {
|
|
401
|
+
event.preventDefault();
|
|
402
|
+
if (inActs) {
|
|
403
|
+
const acts = actsOfRecord(current);
|
|
404
|
+
const back = acts.indexOf(current) - 1;
|
|
405
|
+
return highlight(back >= 0 ? acts[back] : from);
|
|
406
|
+
}
|
|
407
|
+
highlight(rows[Math.max(0, at - 1)]);
|
|
408
|
+
}
|
|
409
|
+
else if ((event.key === "PageDown" || event.key === "PageUp") && rows.length > 0) {
|
|
410
|
+
/*
|
|
411
|
+
* A PAGE OF RECORDS AT A TIME (the tenth walk): the strip's own
|
|
412
|
+
* height of them, so a hundred talks are ten keys and not a hundred.
|
|
413
|
+
* Home and End stay the words' own: the box is a field being written in.
|
|
414
|
+
*/
|
|
415
|
+
event.preventDefault();
|
|
416
|
+
setOpen(true);
|
|
417
|
+
const step = event.key === "PageDown" ? pageOf(rows.length) : -pageOf(rows.length);
|
|
418
|
+
highlight(rows[Math.min(rows.length - 1, Math.max(0, (at < 0 ? -1 : at) + step))]);
|
|
419
|
+
}
|
|
420
|
+
else if (event.key === "ArrowRight" && !inActs && recordOf(current)) {
|
|
421
|
+
const row = current;
|
|
422
|
+
const acts = wide ? (row.id === subject?.id ? panelActs : actsOf(row)) : (openActs.get(row.id) ?? actsOf(row));
|
|
423
|
+
if (acts.length === 0)
|
|
424
|
+
return;
|
|
425
|
+
event.preventDefault();
|
|
426
|
+
if (!wide && !opened.includes(row.id))
|
|
427
|
+
setOpened((now) => [...now, row.id]);
|
|
428
|
+
setShown(row.id);
|
|
429
|
+
setActive(acts[0].id);
|
|
430
|
+
}
|
|
431
|
+
else if (event.key === "ArrowLeft" && inActs) {
|
|
432
|
+
event.preventDefault();
|
|
433
|
+
const of = current.of;
|
|
434
|
+
if (!wide)
|
|
435
|
+
setOpened((now) => now.filter((id) => id !== of));
|
|
436
|
+
highlight(rows.find((row) => row.id === of));
|
|
437
|
+
}
|
|
438
|
+
else if (event.key === "Enter") {
|
|
439
|
+
event.preventDefault();
|
|
440
|
+
travel(current);
|
|
441
|
+
}
|
|
442
|
+
else if (event.key === "Escape") {
|
|
443
|
+
event.preventDefault();
|
|
444
|
+
// The words first; an empty box lets go of the keys.
|
|
445
|
+
if (q)
|
|
446
|
+
setView((stop) => withoutSearch(stop));
|
|
447
|
+
else
|
|
448
|
+
input.current?.blur();
|
|
449
|
+
}
|
|
450
|
+
};
|
|
451
|
+
const option = (row, body, extra = {}, props = {}) => {
|
|
452
|
+
const on = row.id === current?.id;
|
|
453
|
+
return (_jsx("div", { id: row.id, role: "option", "aria-selected": on, onClick: () => travel(row), style: { ...OPTION, ...(on ? LIT : null), ...extra }, ...props, children: body }, row.id));
|
|
454
|
+
};
|
|
455
|
+
const actOption = (row, under) => {
|
|
456
|
+
const on = row.id === current?.id;
|
|
457
|
+
return (_jsx("div", { id: row.id, role: "option", "aria-selected": on, "data-testid": "find-act", "data-about": "act", onClick: () => travel(row), style: { ...(under ? ACT_UNDER : ACT), ...(on ? ACT_LIT : null), ...(row.act.destructive ? { color: "var(--graview-warn)" } : null) }, children: row.act.title }, row.id));
|
|
458
|
+
};
|
|
269
459
|
return (_jsxs("div", { style: { position: "relative", minWidth: 0, flex: "1 1 auto" }, "data-testid": "find", children: [_jsx("input", { ref: (element) => {
|
|
270
460
|
input.current = element;
|
|
271
461
|
popover.trigger.ref(element);
|
|
272
462
|
}, "data-graview-popover-trigger": "find", type: "search", role: "combobox", "aria-label": "Find anything", "aria-expanded": expanded, "aria-controls": strip, "aria-autocomplete": "list", ...(expanded && current ? { "aria-activedescendant": current.id } : {}), "data-testid": "find-box", placeholder: "Find\u2026", "aria-keyshortcuts": "/ Meta+K Control+K", value: q, onChange: (event) => {
|
|
273
|
-
const
|
|
463
|
+
const typed = event.target.value;
|
|
274
464
|
setOpen(true);
|
|
275
|
-
setView((stop) => withQuery(stop,
|
|
276
|
-
}, onFocus: () => setOpen(true), onBlur: () => setOpen(false), onKeyDown:
|
|
277
|
-
// Nothing highlighted yet: the first ↓ lands on the first row, not the second.
|
|
278
|
-
const at = active !== null && current ? rows.indexOf(current) : -1;
|
|
279
|
-
/*
|
|
280
|
-
* RECORD TO RECORD, ONE KEY EACH (the tenth walk). A record lit
|
|
281
|
-
* unfolds its acts under it, and ↓ walked into them: five keys a
|
|
282
|
-
* record, and the gauntlet's longest title past the keyboard's
|
|
283
|
-
* reach. ↓ goes past the acts of the record lit; → goes into them,
|
|
284
|
-
* ← back to their record.
|
|
285
|
-
*/
|
|
286
|
-
const anAct = (row) => row !== undefined && !("ask" in row) && row.hit.about === "act";
|
|
287
|
-
const onRecord = at >= 0 && current !== undefined && !("ask" in current) && current.hit.about === "node";
|
|
288
|
-
if (event.key === "ArrowDown") {
|
|
289
|
-
event.preventDefault();
|
|
290
|
-
setOpen(true);
|
|
291
|
-
let next = at + 1;
|
|
292
|
-
if (onRecord)
|
|
293
|
-
while (next < rows.length - 1 && anAct(rows[next]))
|
|
294
|
-
next += 1;
|
|
295
|
-
highlight(rows[Math.min(rows.length - 1, next)]);
|
|
296
|
-
}
|
|
297
|
-
else if (event.key === "ArrowUp") {
|
|
298
|
-
event.preventDefault();
|
|
299
|
-
highlight(rows[Math.max(0, at - 1)]);
|
|
300
|
-
}
|
|
301
|
-
else if ((event.key === "PageDown" || event.key === "PageUp") && rows.length > 0) {
|
|
302
|
-
/*
|
|
303
|
-
* A PAGE OF RECORDS AT A TIME (the tenth walk): the strip's own
|
|
304
|
-
* height of them, past the acts unfolded under one, so a hundred
|
|
305
|
-
* talks are ten keys and not a hundred. Home and End stay the
|
|
306
|
-
* words' own: the box is a field being written in.
|
|
307
|
-
*/
|
|
308
|
-
event.preventDefault();
|
|
309
|
-
setOpen(true);
|
|
310
|
-
const records = rows.flatMap((row, index) => (anAct(row) ? [] : [index]));
|
|
311
|
-
const from = Math.max(0, records.findIndex((index) => index >= Math.max(at, 0)));
|
|
312
|
-
const step = event.key === "PageDown" ? pageOf(rows.length) : -pageOf(rows.length);
|
|
313
|
-
highlight(rows[records[Math.min(records.length - 1, Math.max(0, (at < 0 ? -1 : from) + step))]]);
|
|
314
|
-
}
|
|
315
|
-
else if (event.key === "ArrowRight" && onRecord && anAct(rows[at + 1])) {
|
|
316
|
-
event.preventDefault();
|
|
317
|
-
highlight(rows[at + 1]);
|
|
318
|
-
}
|
|
319
|
-
else if (event.key === "ArrowLeft" && anAct(current)) {
|
|
320
|
-
event.preventDefault();
|
|
321
|
-
let back = at - 1;
|
|
322
|
-
while (back > 0 && anAct(rows[back]))
|
|
323
|
-
back -= 1;
|
|
324
|
-
highlight(rows[back]);
|
|
325
|
-
}
|
|
326
|
-
else if (event.key === "Enter") {
|
|
327
|
-
event.preventDefault();
|
|
328
|
-
travel(current);
|
|
329
|
-
}
|
|
330
|
-
else if (event.key === "Escape") {
|
|
331
|
-
event.preventDefault();
|
|
332
|
-
// The words first; an empty box lets go of the keys.
|
|
333
|
-
if (q)
|
|
334
|
-
setView((stop) => withoutSearch(stop));
|
|
335
|
-
else
|
|
336
|
-
input.current?.blur();
|
|
337
|
-
}
|
|
338
|
-
}, style: {
|
|
465
|
+
setView((stop) => withQuery(stop, typed.length > 0 ? typed : null));
|
|
466
|
+
}, onFocus: () => setOpen(true), onBlur: () => setOpen(false), onKeyDown: onKeyDown, style: {
|
|
339
467
|
width: "100%",
|
|
340
468
|
boxSizing: "border-box",
|
|
341
469
|
minWidth: 0,
|
|
@@ -348,35 +476,136 @@ export function FindBox({ compact = false }) {
|
|
|
348
476
|
background: "var(--graview-panel)",
|
|
349
477
|
border: `1px solid ${q ? "var(--graview-accent)" : "var(--graview-edge)"}`,
|
|
350
478
|
borderRadius: 8,
|
|
351
|
-
} }), _jsx("span", { role: "status", "aria-live": "polite", style: VISUALLY_HIDDEN, "data-testid": "find-count", children: q.trim() ? announce : "" }), _jsx("span", { ref: holder, tabIndex: -1, role: "status", style: VISUALLY_HIDDEN, "data-testid": "find-landing" }), _jsxs("div", { ...popover.pane, id: strip, role: "listbox", "aria-label": q ? `What “${q}” finds` : "What the words find", "data-testid": "find-strip", hidden: !expanded,
|
|
479
|
+
} }), _jsx("span", { role: "status", "aria-live": "polite", style: VISUALLY_HIDDEN, "data-testid": "find-count", children: q.trim() ? announce : "" }), _jsx("span", { ref: holder, tabIndex: -1, role: "status", style: VISUALLY_HIDDEN, "data-testid": "find-landing" }), _jsxs("div", { ...popover.pane, id: strip, role: "listbox", "aria-label": q ? `What “${q}” finds` : "What the words find", "data-testid": "find-strip", "data-graview-find": wide ? "panel" : "narrow", hidden: !expanded,
|
|
352
480
|
// Mousedown on a row must not blur the box before the click lands.
|
|
353
|
-
onMouseDown: (event) => event.preventDefault(),
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
481
|
+
onMouseDown: (event) => event.preventDefault(),
|
|
482
|
+
// Shown, it is a column — the list, the panel beside it, the keys under them; put away, the popover's own rule hides it.
|
|
483
|
+
style: { ...STRIP, ...(compact ? SHEET : wide && !nothing ? WIDE : null), ...(expanded ? { display: "flex" } : null) }, children: [_jsxs("div", { style: { ...BODY, gridTemplateColumns: wide && !nothing ? `minmax(0, 1fr) ${PANEL_WIDTH}` : "minmax(0, 1fr)" }, children: [_jsxs("div", { ref: list, role: "presentation", "data-testid": "find-list", onMouseMove: point, style: LIST, children: [nothing ? (_jsxs("p", { role: "presentation", style: { margin: 0, padding: "10px 12px", color: "var(--graview-ink-muted)", fontSize: "0.875rem" }, "data-testid": "find-nothing", children: ["Nothing here is called \u201C", found?.words || q, "\u201D."] })) : (groups.map((group) => (_jsxs("div", { role: "group", "aria-label": headingOf(group.key, store.schema), children: [_jsxs("div", { role: "presentation", style: HEADING, children: [group.key !== "rule:" && group.key !== "act:" ? _jsx(Mark, { kind: group.key, accents: brand?.accents }) : null, _jsx("span", { style: { minWidth: 0, overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }, children: headingOf(group.key, store.schema) }), _jsx("span", { "aria-hidden": "true", style: COUNT, "data-testid": "find-group-count", children: numberAsRead(group.count) })] }), group.rows.map((row) => {
|
|
484
|
+
const hit = row.hit;
|
|
485
|
+
const record = hit.about === "node" ? hit : null;
|
|
486
|
+
const asks = !wide && record !== null && hasActs.has(row.id);
|
|
487
|
+
const under = openActs.get(row.id);
|
|
488
|
+
return (_jsxs(Fragment, { children: [option(row, _jsxs(_Fragment, { children: [_jsx(HitLine, { hit: hit, flag: flag, words: record?.near && record.why.matched ? [...marking, fold(record.why.matched)] : marking }), asks ? (_jsx("span", { "aria-hidden": "true", "data-testid": "find-acts-open", "data-open": under ? "" : undefined, title: under ? "Put its actions away" : "Actions", onClick: (event) => {
|
|
489
|
+
event.stopPropagation();
|
|
490
|
+
toggle(row);
|
|
491
|
+
}, style: { ...MORE, ...(under ? MORE_OPEN : null) }, children: _jsxs("svg", { width: "16", height: "16", viewBox: "0 0 16 16", "aria-hidden": "true", children: [_jsx("circle", { cx: "3.5", cy: "8", r: "1.4", fill: "currentColor" }), _jsx("circle", { cx: "8", cy: "8", r: "1.4", fill: "currentColor" }), _jsx("circle", { cx: "12.5", cy: "8", r: "1.4", fill: "currentColor" })] }) })) : null] }), asks ? { paddingRight: 48 } : {}, {
|
|
492
|
+
"data-testid": "find-hit",
|
|
493
|
+
"data-about": hit.about,
|
|
494
|
+
...(record?.near ? { "data-near": "" } : {}),
|
|
495
|
+
title: titleOf(hit),
|
|
496
|
+
}), under ? (_jsx("div", { role: "group", "aria-label": `What can be done about ${record?.label ?? "it"}`, style: UNDER, children: under.map((act) => actOption(act, true)) })) : null] }, row.id));
|
|
497
|
+
}), group.more
|
|
498
|
+
? option(group.more, _jsxs("span", { style: ONE_LINE, children: ["All ", numberAsRead(group.more.count), " ", pluralOf(store.schema, group.more.all).toLowerCase(), " matching \u201C", q.trim(), "\u201D"] }), ALL, { "data-testid": "find-all", "data-about": "all" })
|
|
499
|
+
: null] }, group.key)))), asking ? option(asking, _jsxs("span", { style: ONE_LINE, children: ["Ask: ", _jsxs("span", { style: { fontWeight: 600 }, children: ["\u2018", asking.ask, "\u2019"] })] }), { borderTop: "1px solid var(--graview-edge)", marginTop: 4 }, { "data-testid": "find-ask" }) : null, manyFound > MANY_FOUND ? (_jsxs("p", { role: "presentation", style: NOTE, "data-testid": "find-more-words", children: [numberAsRead(manyFound), " found: another word of the name narrows them."] })) : null, found && found.searched.past === false && found.searched.kinds.some((kind) => store.schema.tryDefinition(kind)?.lifecycle) ? (_jsx("p", { role: "presentation", style: NOTE, children: "Current ones; add is:any for the past." })) : null] }), wide && !nothing && subject ? (_jsxs("div", { role: "group", "aria-label": "The one lit", "data-testid": "find-panel", onMouseEnter: settle, onMouseMove: point, style: PANEL, children: [_jsx(Said, { row: subject, schema: store.schema, accents: brand?.accents, marking: marking, q: q.trim(), flag: flag }), panelActs.length > 0 ? (_jsxs("div", { role: "group", "aria-label": "Actions", style: { display: "grid", gap: 4 }, children: [_jsx("div", { role: "presentation", style: { ...HEADING, padding: "10px 0 4px" }, children: "Actions" }), panelActs.map((act) => actOption(act, false))] })) : null] })) : null] }), compact ? null : (_jsxs("div", { "aria-hidden": "true", style: FOOT, "data-testid": "find-keys", children: [_jsxs("span", { children: [_jsx("kbd", { style: KBD, children: "\u2191" }), _jsx("kbd", { style: KBD, children: "\u2193" }), " move"] }), _jsxs("span", { children: [_jsx("kbd", { style: KBD, children: "\u21B5" }), " open"] }), _jsxs("span", { children: [_jsx("kbd", { style: KBD, children: "\u2192" }), " actions"] }), _jsxs("span", { children: [_jsx("kbd", { style: KBD, children: "esc" }), " close"] })] }))] })] }));
|
|
500
|
+
}
|
|
501
|
+
/** The row lit, said whole in the desk's panel: what it is, its name, and why the words found it. */
|
|
502
|
+
function Said({ row, schema, accents, marking, q, flag }) {
|
|
503
|
+
let kind = null;
|
|
504
|
+
let over = "";
|
|
505
|
+
let name;
|
|
506
|
+
let lines = null;
|
|
507
|
+
if ("ask" in row) {
|
|
508
|
+
over = "Ask";
|
|
509
|
+
name = `‘${row.ask}’`;
|
|
510
|
+
lines = _jsx("p", { style: PANEL_LINE, children: "Asks it of what is selected; the answer comes on a card beside it." });
|
|
511
|
+
}
|
|
512
|
+
else if ("all" in row) {
|
|
513
|
+
kind = row.all;
|
|
514
|
+
over = pluralLabel(schema, row.all);
|
|
515
|
+
name = `All ${numberAsRead(row.count)} matching “${q}”`;
|
|
516
|
+
lines = _jsxs("p", { style: PANEL_LINE, children: ["Opens the ", pluralOf(schema, row.all).toLowerCase(), ", narrowed by these words."] });
|
|
517
|
+
}
|
|
518
|
+
else {
|
|
519
|
+
const hit = row.hit;
|
|
520
|
+
switch (hit.about) {
|
|
521
|
+
case "node": {
|
|
522
|
+
kind = hit.kind;
|
|
523
|
+
over = capitalized(nounOf(schema.tryDefinition(hit.kind), hit.kind));
|
|
524
|
+
name = (_jsxs(_Fragment, { children: [hit.flagged ? _jsxs("span", { style: { color: "var(--graview-warn)" }, children: [flag, " "] }) : null, _jsx(Marked, { text: hit.label, words: marking })] }));
|
|
525
|
+
const notes = [hit.apart, hit.current ? null : "past", hit.archived ? "set aside" : null].filter(Boolean).join(" · ");
|
|
526
|
+
lines = (_jsxs(_Fragment, { children: [notes ? _jsx("p", { style: PANEL_LINE, children: capitalized(notes) }) : null, hit.near ? (_jsxs("p", { style: PANEL_LINE, children: ["Near \u201C", hit.why.asked, "\u201D", hit.why.field !== "label" ? ` — ${hit.why.reading.toLowerCase()}: ${hit.why.fragment}` : ""] })) : hit.why.field !== "label" ? (_jsxs("p", { style: PANEL_LINE, children: [_jsx("span", { style: { color: "var(--graview-ink-faint)" }, children: hit.why.reading }), _jsx("br", {}), _jsx(Marked, { text: hit.why.fragment, words: marking })] })) : null] }));
|
|
527
|
+
break;
|
|
528
|
+
}
|
|
529
|
+
case "kind":
|
|
530
|
+
kind = hit.kind;
|
|
531
|
+
over = "Kind";
|
|
532
|
+
name = hit.label;
|
|
533
|
+
lines = _jsxs("p", { style: PANEL_LINE, children: ["Goes into the ", hit.label.toLowerCase(), ", all of them", hit.count > 0 ? `; ${numberAsRead(hit.count)} match these words` : "", "."] });
|
|
534
|
+
break;
|
|
535
|
+
case "place":
|
|
536
|
+
kind = hit.kind;
|
|
537
|
+
over = "Place";
|
|
538
|
+
name = hit.title;
|
|
539
|
+
lines = _jsxs("p", { style: PANEL_LINE, children: ["A picture of the ", pluralOf(schema, hit.kind).toLowerCase(), "."] });
|
|
540
|
+
break;
|
|
541
|
+
case "rule":
|
|
542
|
+
over = "Rule";
|
|
543
|
+
name = hit.label;
|
|
544
|
+
lines = _jsx("p", { style: PANEL_LINE, children: "Selects what it flags." });
|
|
545
|
+
break;
|
|
546
|
+
case "act":
|
|
547
|
+
over = "Act";
|
|
548
|
+
name = hit.title;
|
|
549
|
+
break;
|
|
550
|
+
}
|
|
551
|
+
}
|
|
552
|
+
return (_jsxs("div", { role: "presentation", "data-testid": "find-said", style: { display: "grid", gap: 6 }, children: [_jsxs("div", { style: { ...HEADING, padding: 0 }, children: [kind ? _jsx(Mark, { kind: kind, accents: accents }) : null, over] }), _jsx("div", { style: PANEL_NAME, children: name }), lines] }));
|
|
363
553
|
}
|
|
364
|
-
function HitLine({ hit, flag }) {
|
|
554
|
+
function HitLine({ hit, flag, words }) {
|
|
365
555
|
switch (hit.about) {
|
|
366
556
|
case "node":
|
|
367
|
-
return (_jsxs(_Fragment, { children: [_jsxs("span", { style:
|
|
557
|
+
return (_jsxs(_Fragment, { children: [_jsxs("span", { style: TITLE, children: [hit.flagged ? _jsxs("span", { style: { color: "var(--graview-warn)" }, children: [flag, " "] }) : null, _jsx(Marked, { text: hit.label, words: words }), hit.apart ? _jsxs("span", { style: MUTED, children: [" \u00B7 ", hit.apart] }) : null, hit.current ? null : _jsx("span", { style: MUTED, children: " \u00B7 past" })] }), hit.near ? (
|
|
368
558
|
// Found by a word near the one typed, and said so: "Near “florist” — tags: flowers".
|
|
369
|
-
_jsxs("span", { style: MUTED, "data-testid": "find-near", children: ["Near \u201C", hit.why.asked, "\u201D", hit.why.field !== "label" ? ` — ${hit.why.reading.toLowerCase()}:
|
|
559
|
+
_jsxs("span", { style: { ...ONE_LINE, ...MUTED }, "data-testid": "find-near", children: ["Near \u201C", hit.why.asked, "\u201D", hit.why.field !== "label" ? ` — ${hit.why.reading.toLowerCase()}: ` : "", hit.why.field !== "label" ? _jsx(Marked, { text: hit.why.fragment, words: words }) : null] })) : hit.why.field !== "label" ? (_jsxs("span", { style: { ...ONE_LINE, ...MUTED }, children: [hit.why.reading.toLowerCase(), ": ", _jsx(Marked, { text: hit.why.fragment, words: words })] })) : null] }));
|
|
370
560
|
case "kind":
|
|
371
|
-
return (_jsxs("span", { children: [hit.label, _jsxs("span", { style: MUTED, children: [" \u00B7 ", hit.count, " match"] })] }));
|
|
561
|
+
return (_jsxs("span", { style: ONE_LINE, children: [_jsx(Marked, { text: hit.label, words: words }), _jsxs("span", { style: MUTED, children: [" \u00B7 ", hit.count, " match"] })] }));
|
|
372
562
|
case "place":
|
|
373
|
-
return _jsxs("span", { children: ["Go to ", hit.title] });
|
|
563
|
+
return (_jsxs("span", { style: ONE_LINE, children: ["Go to ", _jsx(Marked, { text: hit.title, words: words })] }));
|
|
374
564
|
case "rule":
|
|
375
|
-
return _jsxs("span", { children: ["The rule \u201C", hit.label, "\u201D"] });
|
|
565
|
+
return (_jsxs("span", { style: ONE_LINE, children: ["The rule \u201C", _jsx(Marked, { text: hit.label, words: words }), "\u201D"] }));
|
|
376
566
|
case "act":
|
|
377
|
-
return (
|
|
567
|
+
return _jsx("span", { style: ONE_LINE, children: hit.title });
|
|
568
|
+
}
|
|
569
|
+
}
|
|
570
|
+
/**
|
|
571
|
+
* THE WORDS THAT MATCHED, MARKED: each word of the text that a word typed
|
|
572
|
+
* begins — "track" in "on-site tracking" — as the matcher reads them, case
|
|
573
|
+
* and accents aside, so the mark is where the match was.
|
|
574
|
+
*/
|
|
575
|
+
function markedParts(text, words) {
|
|
576
|
+
if (words.length === 0)
|
|
577
|
+
return [{ text }];
|
|
578
|
+
const parts = [];
|
|
579
|
+
let last = 0;
|
|
580
|
+
for (const match of text.matchAll(/[\p{L}\p{N}]+/gu)) {
|
|
581
|
+
const token = match[0];
|
|
582
|
+
const folded = fold(token).replace(/ /g, "");
|
|
583
|
+
const word = words.filter((one) => folded.startsWith(one)).sort((a, b) => b.length - a.length)[0];
|
|
584
|
+
if (!word)
|
|
585
|
+
continue;
|
|
586
|
+
// Where folding kept its length, the letters typed; where it did not (a ligature, a dotted I), the whole word.
|
|
587
|
+
const length = folded.length === token.length ? word.length : token.length;
|
|
588
|
+
const start = match.index ?? 0;
|
|
589
|
+
if (start > last)
|
|
590
|
+
parts.push({ text: text.slice(last, start) });
|
|
591
|
+
parts.push({ text: token.slice(0, length), marked: true });
|
|
592
|
+
last = start + length;
|
|
378
593
|
}
|
|
594
|
+
if (last < text.length)
|
|
595
|
+
parts.push({ text: text.slice(last) });
|
|
596
|
+
return parts;
|
|
597
|
+
}
|
|
598
|
+
function Marked({ text, words }) {
|
|
599
|
+
return (_jsx(_Fragment, { children: markedParts(text, words).map((part, at) => part.marked ? (_jsx("mark", { style: MARK, children: part.text }, at)) : (_jsx(Fragment, { children: part.text }, at))) }));
|
|
600
|
+
}
|
|
601
|
+
/** A row's whole text, for the pointer that rests on one cut short. */
|
|
602
|
+
function titleOf(hit) {
|
|
603
|
+
if (hit.about !== "node")
|
|
604
|
+
return "";
|
|
605
|
+
const why = hit.near ? `Near “${hit.why.asked}”` : hit.why.field !== "label" ? `${hit.why.reading}: ${hit.why.fragment}` : "";
|
|
606
|
+
return why ? `${hit.label}\n${why}` : hit.label;
|
|
379
607
|
}
|
|
608
|
+
const capitalized = (text) => (text ? text[0].toUpperCase() + text.slice(1) : text);
|
|
380
609
|
function Mark({ kind, accents }) {
|
|
381
610
|
const hue = Math.round(hueFor(kind, accents));
|
|
382
611
|
return _jsx("span", { "aria-hidden": "true", style: { width: 8, height: 8, borderRadius: 2, flex: "0 0 auto", background: `hsl(${hue} 55% 52%)` } });
|
|
@@ -386,8 +615,7 @@ function headingOf(key, schema) {
|
|
|
386
615
|
return "Rules";
|
|
387
616
|
if (key === "act:")
|
|
388
617
|
return "Acts";
|
|
389
|
-
|
|
390
|
-
return definition?.plural ?? `${key}s`;
|
|
618
|
+
return pluralLabel(schema, key);
|
|
391
619
|
}
|
|
392
620
|
/** "3 tasks, 1 list" — what a screen reader hears as the count changes. */
|
|
393
621
|
function spoken(byKind, hits, schema) {
|
|
@@ -404,21 +632,34 @@ function spoken(byKind, hits, schema) {
|
|
|
404
632
|
const FIND_PAGE = 10;
|
|
405
633
|
/** From how many records an answer asks for another word rather than being walked. */
|
|
406
634
|
const MANY_FOUND = 12;
|
|
635
|
+
/** Where several kinds share the strip, how many of each it shows before "All N …". */
|
|
636
|
+
const GROUP_CAP = 5;
|
|
637
|
+
/** How many acts a record offers in the strip; the rest are in its menu. */
|
|
638
|
+
const ACTS_MOST = 6;
|
|
639
|
+
/** How long the panel waits on a pointer crossing rows before it says the row lit (ms). */
|
|
640
|
+
const AIM = 140;
|
|
641
|
+
/** From what viewport width the strip has room for the panel beside the list. */
|
|
642
|
+
const WIDE_FROM = 760;
|
|
643
|
+
const PANEL_WIDTH = "15.5rem";
|
|
407
644
|
const STRIP = {
|
|
408
645
|
...POPOVER_STYLE,
|
|
646
|
+
flexDirection: "column",
|
|
409
647
|
width: "min(26rem, calc(100vw - 24px))",
|
|
410
|
-
maxHeight: "min(
|
|
411
|
-
|
|
648
|
+
maxHeight: "min(64vh, 30rem)",
|
|
649
|
+
overflow: "hidden",
|
|
412
650
|
boxSizing: "border-box",
|
|
413
|
-
|
|
651
|
+
// The panel's color over the ground's: where the scheme's panel is a glass, the picture behind does not read through the words.
|
|
652
|
+
background: "linear-gradient(var(--graview-panel), var(--graview-panel)) var(--graview-ground, var(--graview-panel))",
|
|
414
653
|
borderTop: "1px solid var(--graview-edge)",
|
|
415
654
|
borderRight: "1px solid var(--graview-edge)",
|
|
416
655
|
borderBottom: "1px solid var(--graview-edge)",
|
|
417
656
|
borderLeft: "1px solid var(--graview-edge)",
|
|
418
657
|
borderRadius: 10,
|
|
419
658
|
boxShadow: "0 10px 28px rgb(0 0 0 / 0.18)",
|
|
420
|
-
padding:
|
|
659
|
+
padding: 0,
|
|
421
660
|
};
|
|
661
|
+
/** On a desk, room for the panel beside the list. */
|
|
662
|
+
const WIDE = { width: "min(42rem, calc(100vw - 24px))" };
|
|
422
663
|
/** The gap `useTopLayer` leaves under an anchor, taken back so the sheet meets the bar's edge. */
|
|
423
664
|
const SHEET_GAP = 6;
|
|
424
665
|
/** On a phone's bar the strip is a sheet under the bar, the bar's own width (`--graview-bar-width`, the screen's where no bar says it). */
|
|
@@ -429,26 +670,127 @@ const SHEET = {
|
|
|
429
670
|
borderLeft: "none",
|
|
430
671
|
borderRight: "none",
|
|
431
672
|
};
|
|
673
|
+
const BODY = { display: "grid", gridTemplateRows: "minmax(0, 1fr)", flex: "1 1 auto", minHeight: 0 };
|
|
674
|
+
const LIST = { overflowY: "auto", overflowX: "hidden", minHeight: 0, padding: "4px 0", overscrollBehavior: "contain" };
|
|
675
|
+
const PANEL = {
|
|
676
|
+
overflowY: "auto",
|
|
677
|
+
minHeight: 0,
|
|
678
|
+
padding: "12px 14px",
|
|
679
|
+
borderLeft: "1px solid var(--graview-edge)",
|
|
680
|
+
background: "var(--graview-panel-muted)",
|
|
681
|
+
display: "flex",
|
|
682
|
+
flexDirection: "column",
|
|
683
|
+
gap: 4,
|
|
684
|
+
};
|
|
685
|
+
const PANEL_NAME = { fontWeight: 600, fontSize: "0.9375rem", lineHeight: 1.35, color: "var(--graview-ink)", overflowWrap: "anywhere" };
|
|
686
|
+
const PANEL_LINE = { margin: 0, fontSize: "0.8125rem", lineHeight: 1.45, color: "var(--graview-ink-muted)", overflowWrap: "anywhere" };
|
|
432
687
|
const HEADING = {
|
|
433
688
|
display: "flex",
|
|
434
689
|
alignItems: "center",
|
|
435
690
|
gap: 6,
|
|
436
|
-
padding: "
|
|
691
|
+
padding: "10px 12px 4px",
|
|
437
692
|
fontSize: "0.6875rem",
|
|
693
|
+
fontWeight: 600,
|
|
438
694
|
letterSpacing: "0.06em",
|
|
439
695
|
textTransform: "uppercase",
|
|
440
696
|
color: "var(--graview-ink-muted)",
|
|
441
697
|
};
|
|
698
|
+
const COUNT = { marginLeft: "auto", fontWeight: 400, letterSpacing: 0, fontVariantNumeric: "tabular-nums", color: "var(--graview-ink-faint)" };
|
|
442
699
|
const OPTION = {
|
|
700
|
+
position: "relative",
|
|
443
701
|
display: "grid",
|
|
444
702
|
gap: 1,
|
|
445
|
-
minHeight:
|
|
703
|
+
minHeight: 34,
|
|
446
704
|
padding: "5px 12px",
|
|
447
705
|
alignContent: "center",
|
|
448
706
|
cursor: "pointer",
|
|
449
707
|
fontSize: "0.875rem",
|
|
450
708
|
lineHeight: 1.35,
|
|
451
709
|
color: "var(--graview-ink)",
|
|
710
|
+
minWidth: 0,
|
|
711
|
+
};
|
|
712
|
+
/** The row lit: a wash and a bar at its edge, in the app's accent. */
|
|
713
|
+
const LIT = { background: "color-mix(in srgb, var(--graview-accent) 12%, var(--graview-panel))", boxShadow: "inset 3px 0 0 var(--graview-accent)" };
|
|
714
|
+
const ALL = { color: "var(--graview-accent)", fontSize: "0.8125rem", minHeight: 30 };
|
|
715
|
+
const ONE_LINE = { display: "block", minWidth: 0, overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" };
|
|
716
|
+
/** A record's name: two lines at most, so a long name still shows the word that found it, and never more, so no row is a paragraph. */
|
|
717
|
+
const TITLE = {
|
|
718
|
+
display: "-webkit-box",
|
|
719
|
+
WebkitBoxOrient: "vertical",
|
|
720
|
+
WebkitLineClamp: 2,
|
|
721
|
+
minWidth: 0,
|
|
722
|
+
overflow: "hidden",
|
|
723
|
+
overflowWrap: "anywhere",
|
|
724
|
+
fontWeight: 600,
|
|
452
725
|
};
|
|
453
726
|
const MUTED = { color: "var(--graview-ink-muted)", fontSize: "0.8125rem", fontWeight: 400 };
|
|
727
|
+
const MARK = {
|
|
728
|
+
background: "color-mix(in srgb, var(--graview-accent) 24%, transparent)",
|
|
729
|
+
color: "inherit",
|
|
730
|
+
borderRadius: 2,
|
|
731
|
+
padding: 0,
|
|
732
|
+
boxShadow: "0 1px 0 color-mix(in srgb, var(--graview-accent) 70%, transparent)",
|
|
733
|
+
};
|
|
734
|
+
const ACT = {
|
|
735
|
+
display: "flex",
|
|
736
|
+
alignItems: "center",
|
|
737
|
+
minHeight: 32,
|
|
738
|
+
padding: "4px 10px",
|
|
739
|
+
boxSizing: "border-box",
|
|
740
|
+
borderWidth: 1,
|
|
741
|
+
borderStyle: "solid",
|
|
742
|
+
borderColor: "var(--graview-edge)",
|
|
743
|
+
borderRadius: 6,
|
|
744
|
+
background: "var(--graview-panel)",
|
|
745
|
+
color: "var(--graview-ink)",
|
|
746
|
+
fontSize: "0.8125rem",
|
|
747
|
+
lineHeight: 1.3,
|
|
748
|
+
cursor: "pointer",
|
|
749
|
+
overflowWrap: "anywhere",
|
|
750
|
+
};
|
|
751
|
+
const ACT_UNDER = { ...ACT, minHeight: 32, margin: "0 12px 4px 24px" };
|
|
752
|
+
const ACT_LIT = { borderColor: "var(--graview-accent)", boxShadow: "inset 0 0 0 1px var(--graview-accent)", background: "color-mix(in srgb, var(--graview-accent) 12%, var(--graview-panel))" };
|
|
753
|
+
const UNDER = { display: "grid", padding: "2px 0 6px" };
|
|
754
|
+
/** A narrow strip's "⋯": its acts, asked for. Over the row's end, in room the row keeps for it, so the row's words never run under it. */
|
|
755
|
+
const MORE = {
|
|
756
|
+
position: "absolute",
|
|
757
|
+
top: "50%",
|
|
758
|
+
right: 6,
|
|
759
|
+
transform: "translateY(-50%)",
|
|
760
|
+
width: 36,
|
|
761
|
+
height: 32,
|
|
762
|
+
display: "grid",
|
|
763
|
+
placeItems: "center",
|
|
764
|
+
borderRadius: 6,
|
|
765
|
+
color: "var(--graview-ink-muted)",
|
|
766
|
+
borderWidth: 1,
|
|
767
|
+
borderStyle: "solid",
|
|
768
|
+
borderColor: "transparent",
|
|
769
|
+
};
|
|
770
|
+
const MORE_OPEN = { color: "var(--graview-accent)", borderColor: "var(--graview-edge)", background: "var(--graview-panel)" };
|
|
771
|
+
const NOTE = { margin: 0, padding: "6px 12px 4px", color: "var(--graview-ink-faint)", fontSize: "0.75rem" };
|
|
772
|
+
const FOOT = {
|
|
773
|
+
display: "flex",
|
|
774
|
+
flexWrap: "wrap",
|
|
775
|
+
gap: "4px 14px",
|
|
776
|
+
padding: "7px 12px",
|
|
777
|
+
borderTop: "1px solid var(--graview-edge)",
|
|
778
|
+
color: "var(--graview-ink-faint)",
|
|
779
|
+
fontSize: "0.75rem",
|
|
780
|
+
flex: "0 0 auto",
|
|
781
|
+
};
|
|
782
|
+
const KBD = {
|
|
783
|
+
display: "inline-block",
|
|
784
|
+
minWidth: 16,
|
|
785
|
+
padding: "0 4px",
|
|
786
|
+
marginRight: 3,
|
|
787
|
+
border: "1px solid var(--graview-edge)",
|
|
788
|
+
borderRadius: 4,
|
|
789
|
+
font: "inherit",
|
|
790
|
+
fontSize: "0.6875rem",
|
|
791
|
+
lineHeight: "16px",
|
|
792
|
+
textAlign: "center",
|
|
793
|
+
color: "var(--graview-ink-muted)",
|
|
794
|
+
background: "var(--graview-panel)",
|
|
795
|
+
};
|
|
454
796
|
//# sourceMappingURL=find.js.map
|