@graview/primitives 0.1.16 → 0.1.18
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/README.md +47 -2
- package/dist/app-bar.d.ts +87 -19
- package/dist/app-bar.d.ts.map +1 -1
- package/dist/app-bar.js +379 -141
- package/dist/app-bar.js.map +1 -1
- package/dist/bar-panes-door.d.ts +8 -0
- package/dist/bar-panes-door.d.ts.map +1 -0
- package/dist/bar-panes-door.js +10 -0
- package/dist/bar-panes-door.js.map +1 -0
- package/dist/bar-panes.d.ts.map +1 -1
- package/dist/bar-panes.js +3 -27
- package/dist/bar-panes.js.map +1 -1
- package/dist/chat.js +1 -1
- package/dist/chat.js.map +1 -1
- package/dist/choice.d.ts +9 -0
- package/dist/choice.d.ts.map +1 -0
- package/dist/choice.js +21 -0
- package/dist/choice.js.map +1 -0
- package/dist/companion.d.ts +0 -15
- package/dist/companion.d.ts.map +1 -1
- package/dist/companion.js +19 -150
- package/dist/companion.js.map +1 -1
- package/dist/connections.d.ts +7 -1
- package/dist/connections.d.ts.map +1 -1
- package/dist/connections.js +13 -13
- package/dist/connections.js.map +1 -1
- package/dist/declared-lens-doors.d.ts +3 -3
- package/dist/declared-lens-doors.d.ts.map +1 -1
- package/dist/declared-lens-doors.js +45 -25
- package/dist/declared-lens-doors.js.map +1 -1
- package/dist/default-view.d.ts +2 -0
- package/dist/default-view.d.ts.map +1 -1
- package/dist/default-view.js +10 -4
- package/dist/default-view.js.map +1 -1
- package/dist/default-views.d.ts.map +1 -1
- package/dist/default-views.js +195 -42
- package/dist/default-views.js.map +1 -1
- package/dist/editable.d.ts +31 -0
- package/dist/editable.d.ts.map +1 -1
- package/dist/editable.js +86 -17
- package/dist/editable.js.map +1 -1
- package/dist/figure.js +1 -1
- package/dist/figure.js.map +1 -1
- package/dist/find.d.ts.map +1 -1
- package/dist/find.js +23 -6
- package/dist/find.js.map +1 -1
- package/dist/frame.d.ts +3 -2
- package/dist/frame.d.ts.map +1 -1
- package/dist/frame.js +2 -1
- package/dist/frame.js.map +1 -1
- package/dist/graview-mark.d.ts +21 -0
- package/dist/graview-mark.d.ts.map +1 -0
- package/dist/graview-mark.js +24 -0
- package/dist/graview-mark.js.map +1 -0
- package/dist/index.d.ts +14 -8
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +10 -6
- package/dist/index.js.map +1 -1
- package/dist/ladder.d.ts.map +1 -1
- package/dist/ladder.js +4 -10
- package/dist/ladder.js.map +1 -1
- package/dist/lens/calendar-dates.d.ts +2 -0
- package/dist/lens/calendar-dates.d.ts.map +1 -1
- package/dist/lens/calendar-dates.js +2 -0
- package/dist/lens/calendar-dates.js.map +1 -1
- package/dist/lens/calendar-drawing.d.ts +2 -2
- package/dist/lens/calendar-drawing.d.ts.map +1 -1
- package/dist/lens/calendar-drawing.js +1 -2
- package/dist/lens/calendar-drawing.js.map +1 -1
- package/dist/lens/calendar-spans.d.ts.map +1 -1
- package/dist/lens/calendar-spans.js +1 -2
- package/dist/lens/calendar-spans.js.map +1 -1
- package/dist/lens/calendar-view.d.ts +2 -1
- package/dist/lens/calendar-view.d.ts.map +1 -1
- package/dist/lens/calendar-view.js +2 -1
- package/dist/lens/calendar-view.js.map +1 -1
- package/dist/notice-place.d.ts.map +1 -1
- package/dist/notice-place.js +2 -0
- package/dist/notice-place.js.map +1 -1
- package/dist/pages.d.ts +5 -3
- package/dist/pages.d.ts.map +1 -1
- package/dist/pages.js +5 -2
- package/dist/pages.js.map +1 -1
- package/dist/places.js +2 -2
- package/dist/places.js.map +1 -1
- package/dist/profile.d.ts.map +1 -1
- package/dist/profile.js +34 -9
- package/dist/profile.js.map +1 -1
- package/dist/prose-editor.d.ts +9 -0
- package/dist/prose-editor.d.ts.map +1 -0
- package/dist/prose-editor.js +64 -0
- package/dist/prose-editor.js.map +1 -0
- package/dist/record-head.d.ts +28 -0
- package/dist/record-head.d.ts.map +1 -0
- package/dist/record-head.js +3 -0
- package/dist/record-head.js.map +1 -0
- package/dist/relation-key.d.ts.map +1 -1
- package/dist/relation-key.js +2 -2
- package/dist/relation-key.js.map +1 -1
- package/dist/scene-css.d.ts.map +1 -1
- package/dist/scene-css.js +15 -2
- package/dist/scene-css.js.map +1 -1
- package/dist/scene-places.d.ts +37 -0
- package/dist/scene-places.d.ts.map +1 -0
- package/dist/scene-places.js +77 -0
- package/dist/scene-places.js.map +1 -0
- package/dist/scene.d.ts +0 -1
- package/dist/scene.d.ts.map +1 -1
- package/dist/scene.js +0 -1
- package/dist/scene.js.map +1 -1
- package/dist/seats.d.ts.map +1 -1
- package/dist/seats.js +2 -11
- package/dist/seats.js.map +1 -1
- package/dist/shell.d.ts.map +1 -1
- package/dist/shell.js +1 -2
- package/dist/shell.js.map +1 -1
- package/dist/spec-views.d.ts +17 -2
- package/dist/spec-views.d.ts.map +1 -1
- package/dist/spec-views.js +69 -7
- package/dist/spec-views.js.map +1 -1
- package/dist/subject.d.ts +17 -0
- package/dist/subject.d.ts.map +1 -0
- package/dist/subject.js +155 -0
- package/dist/subject.js.map +1 -0
- package/dist/text-body.d.ts +28 -0
- package/dist/text-body.d.ts.map +1 -0
- package/dist/text-body.js +64 -0
- package/dist/text-body.js.map +1 -0
- package/dist/theme.d.ts.map +1 -1
- package/dist/theme.js +13 -7
- package/dist/theme.js.map +1 -1
- package/dist/view-doors.d.ts +5 -1
- package/dist/view-doors.d.ts.map +1 -1
- package/dist/view-doors.js +28 -14
- package/dist/view-doors.js.map +1 -1
- package/dist/workbench/answer-args.d.ts.map +1 -1
- package/dist/workbench/answer-args.js +26 -2
- package/dist/workbench/answer-args.js.map +1 -1
- package/dist/workbench/inspector.js +1 -1
- package/dist/workbench/inspector.js.map +1 -1
- package/dist/workbench/standing.d.ts.map +1 -1
- package/dist/workbench/standing.js +4 -3
- package/dist/workbench/standing.js.map +1 -1
- package/fonts/OFL-Montserrat.txt +93 -0
- package/fonts/montserrat-latin.woff2 +0 -0
- package/fonts/montserrat.css +23 -0
- package/package.json +13 -8
- package/dist/home-landing.d.ts +0 -18
- package/dist/home-landing.d.ts.map +0 -1
- package/dist/home-landing.js +0 -59
- package/dist/home-landing.js.map +0 -1
package/dist/app-bar.js
CHANGED
|
@@ -1,17 +1,21 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
-
import {
|
|
2
|
+
import { hueFor, orderKinds, OVERVIEW_SLUG, placeSlug } from "@graview/core";
|
|
3
3
|
import { POPOVER_STYLE, usePopover } from "@graview/react/provider";
|
|
4
4
|
import { createContext, useCallback, useContext, useLayoutEffect, useRef, useState } from "react";
|
|
5
5
|
import { AppMark } from "./app-title.js";
|
|
6
|
-
/** The key the
|
|
7
|
-
export const
|
|
6
|
+
/** The key the home's place has (FR-136): the app's front page, first in the list, at the app's own address. */
|
|
7
|
+
export const HOME_KEY = "home";
|
|
8
|
+
/** The home's address on the routed face: the app's own. */
|
|
9
|
+
export const HOME_PATH = "/";
|
|
10
|
+
/** The key of the scene's first place (FR-144): the whole thing, no picture in view. */
|
|
11
|
+
export const WHOLE_KEY = "scene:whole";
|
|
8
12
|
const upper = (word) => word.charAt(0).toUpperCase() + word.slice(1);
|
|
9
13
|
/**
|
|
10
|
-
* THE APP'S PLACES, IN THE
|
|
11
|
-
* the
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
14
|
+
* THE APP'S PLACES, IN THE LIST'S ORDER (FR-138): the home; each kind's list
|
|
15
|
+
* in the declaration's order; each named picture, then how the kinds
|
|
16
|
+
* connect, when they do. What a seat may not see is not offered: a kind
|
|
17
|
+
* kept from it, a disabled module's kind, a picture over either. The scene
|
|
18
|
+
* is not a place here: it is the switch's (FR-137).
|
|
15
19
|
*/
|
|
16
20
|
export function barPlaces(input) {
|
|
17
21
|
const { store, principal, views } = input;
|
|
@@ -19,27 +23,26 @@ export function barPlaces(input) {
|
|
|
19
23
|
const live = store.schema.kinds.filter((kind) => !store.modules.disabledKinds.has(kind) && !kept.has(kind));
|
|
20
24
|
const arrangement = views?.arrangement?.();
|
|
21
25
|
const plural = (kind) => store.schema.tryDefinition(kind)?.plural ?? `${kind}s`;
|
|
22
|
-
const out = [];
|
|
23
|
-
|
|
24
|
-
out.push({ key:
|
|
26
|
+
const out = [{ key: HOME_KEY, label: "Home", path: HOME_PATH, group: "home" }];
|
|
27
|
+
for (const kind of orderKinds(live, arrangement?.order))
|
|
28
|
+
out.push({ key: `kind:${kind}`, label: upper(plural(kind)), path: `/${placeSlug(plural(kind))}`, group: "lists", kind });
|
|
25
29
|
const all = views?.places() ?? [];
|
|
26
30
|
for (const place of all) {
|
|
31
|
+
// A place at the scene's address is not offered: the address is the scene's (FR-132).
|
|
27
32
|
if (!live.includes(place.kind) || place.as === OVERVIEW_SLUG)
|
|
28
33
|
continue;
|
|
29
34
|
// Two kinds' pictures of one name say whose with `?of=` (as the routed face addresses them).
|
|
30
35
|
const shared = all.some((other) => other.as === place.as && other.kind !== place.kind);
|
|
31
36
|
const path = `/places/${encodeURIComponent(place.as)}${shared ? `?of=${placeSlug(plural(place.kind))}` : ""}`;
|
|
32
37
|
if (!out.some((held) => held.path === path))
|
|
33
|
-
out.push({ key: `place:${place.kind}:${place.as}`, label: place.title, path });
|
|
38
|
+
out.push({ key: `place:${place.kind}:${place.as}`, label: place.title, path, group: "pictures", kind: place.kind });
|
|
34
39
|
}
|
|
35
|
-
for (const kind of orderKinds(live, arrangement?.order))
|
|
36
|
-
out.push({ key: `kind:${kind}`, label: upper(plural(kind)), path: `/${placeSlug(plural(kind))}` });
|
|
37
40
|
const connects = live.some((kind) => Object.keys((store.schema.tryDefinition(kind)?.edges ?? {})).length > 0);
|
|
38
41
|
if (connects)
|
|
39
|
-
out.push({ key: "connections", label: "Connections", path: "/map" });
|
|
42
|
+
out.push({ key: "connections", label: "Connections", path: "/map", group: "pictures" });
|
|
40
43
|
return out;
|
|
41
44
|
}
|
|
42
|
-
/** Which of the places a path on the routed face is: its own, a record's kind's, or none (
|
|
45
|
+
/** Which of the places a path on the routed face is: its own, a record's kind's, or none (Find, the problems). */
|
|
43
46
|
export function barPlaceAt(places, path) {
|
|
44
47
|
const [pathname = "/", search = ""] = path.split("?");
|
|
45
48
|
const exact = places.find((place) => place.path === (search ? `${pathname}?${search}` : pathname)) ?? places.find((place) => place.path === pathname);
|
|
@@ -47,43 +50,124 @@ export function barPlaceAt(places, path) {
|
|
|
47
50
|
return exact.key;
|
|
48
51
|
const first = `/${pathname.split("/").filter(Boolean)[0] ?? ""}`;
|
|
49
52
|
// A record's page is under its kind's list.
|
|
50
|
-
return places.find((place) => place.
|
|
53
|
+
return places.find((place) => place.group === "lists" && place.path === first)?.key ?? null;
|
|
51
54
|
}
|
|
52
55
|
export const BarFindContext = createContext(null);
|
|
53
56
|
/** The bar's place for a Find box, when a bar above this face has one. */
|
|
54
57
|
export const useBarFind = () => useContext(BarFindContext);
|
|
55
58
|
/** One size for every tool on the bar (FR-131). */
|
|
56
59
|
export const TOOL = 30;
|
|
60
|
+
/** The bar's one row, its rule under it included, on a desk and a phone alike (FR-138). */
|
|
61
|
+
export const BAR_HEIGHT = 48;
|
|
62
|
+
/** Below this width of its own the bar is a phone's: the place is the page's first line, and Find a magnifier that opens the box over the row. */
|
|
63
|
+
export const BAR_PHONE = 640;
|
|
64
|
+
/** The room between two places standing on the row (FR-145). */
|
|
65
|
+
export const PLACE_GAP = 4;
|
|
66
|
+
/** The fewest places that stand on the row: fewer, and the one control says the same in less room. */
|
|
67
|
+
export const FEWEST_STANDING = 2;
|
|
68
|
+
/**
|
|
69
|
+
* WHICH PLACES STAND ON THE ROW (FR-145), from their widths and the room:
|
|
70
|
+
* every one when all fit; else the first ones in their order, the current
|
|
71
|
+
* one always among them (in its own place in the order, the last of the
|
|
72
|
+
* first ones giving way to it), with "More" after them; else none — null,
|
|
73
|
+
* the one control. Fewer than `FEWEST_STANDING` is none. A width that is
|
|
74
|
+
* not measured (nothing laid out) is none too.
|
|
75
|
+
*/
|
|
76
|
+
export function placesThatStand(input) {
|
|
77
|
+
const { widths, current, room, more, gap = PLACE_GAP } = input;
|
|
78
|
+
if (widths.length === 0 || !(room > 0) || !(more > 0) || widths.some((width) => !(width > 0)))
|
|
79
|
+
return null;
|
|
80
|
+
const span = (set, folded) => set.reduce((sum, at) => sum + widths[at], 0) + gap * Math.max(0, set.length - 1) + (folded ? gap + more : 0);
|
|
81
|
+
const all = widths.map((_, at) => at);
|
|
82
|
+
if (span(all, false) <= room)
|
|
83
|
+
return all;
|
|
84
|
+
for (let count = widths.length - 1; count >= FEWEST_STANDING; count--) {
|
|
85
|
+
const set = current < count ? all.slice(0, count) : [...all.slice(0, count - 1), current];
|
|
86
|
+
if (span(set, true) <= room)
|
|
87
|
+
return set;
|
|
88
|
+
}
|
|
89
|
+
return null;
|
|
90
|
+
}
|
|
91
|
+
/*
|
|
92
|
+
* THE BAR FITS ITS BOX. Every rule here reads the bar's own width (it is
|
|
93
|
+
* its own container), never the screen's: an embed in a 650 px box on a
|
|
94
|
+
* 1440 desk is a narrow bar, not a desk's bar squeezed. When the row is
|
|
95
|
+
* short, Find gives first (a small box that says its shortcut, drawn wide
|
|
96
|
+
* while it is used), then the place down to 9em of its name (whole in its
|
|
97
|
+
* title), then the app's name, which wraps between its words onto a second
|
|
98
|
+
* line and never inside one; the switch keeps its size, and draws its marks
|
|
99
|
+
* alone when its words do not fit (`roomForWords`).
|
|
100
|
+
*
|
|
101
|
+
* Where the places stand on the row (FR-145), Find gives first — down to
|
|
102
|
+
* its least — then the places fold into "More", then into the one control,
|
|
103
|
+
* which gives as above. The places are weighed with the switch's words
|
|
104
|
+
* kept: the words are worth more than a third place on the row.
|
|
105
|
+
*
|
|
106
|
+
* The order is the shrink factors: Find 600, the place 100, the app 1. A
|
|
107
|
+
* factor under 1 would not do — the free space is scaled by the factors
|
|
108
|
+
* left when the others have reached their least, so an app at 0.001 would
|
|
109
|
+
* give 0.1% of what is still needed and the row would run out of its box.
|
|
110
|
+
* At 1 the app gives a fraction of a pixel while the others still can,
|
|
111
|
+
* which would wrap its last word; its 2 px spacer (`::after`) takes that.
|
|
112
|
+
* The tools (`display:contents`) stand on the row themselves, so Find's
|
|
113
|
+
* box is what gives, not a column around it.
|
|
114
|
+
*/
|
|
57
115
|
const BAR_CSS = `
|
|
58
|
-
.graview-bar{container-
|
|
59
|
-
.graview-bar-row{display:
|
|
60
|
-
.graview-bar-
|
|
61
|
-
.graview-bar-
|
|
62
|
-
.graview-bar-
|
|
63
|
-
.graview-bar-
|
|
64
|
-
.graview-bar-
|
|
65
|
-
.graview-bar-
|
|
66
|
-
.graview-bar-
|
|
67
|
-
.graview-bar-
|
|
68
|
-
.graview-bar-
|
|
69
|
-
.graview-bar-
|
|
70
|
-
.graview-bar-
|
|
71
|
-
.graview-bar-
|
|
72
|
-
.graview-bar-
|
|
73
|
-
.graview-bar-
|
|
74
|
-
.graview-bar-
|
|
75
|
-
.graview-bar-
|
|
76
|
-
|
|
77
|
-
.graview-bar-
|
|
78
|
-
.graview-bar-
|
|
79
|
-
.graview-bar-places{
|
|
80
|
-
.graview-bar-
|
|
116
|
+
.graview-bar{display:block;margin:0;padding:0;container:graview-bar/inline-size;flex:0 0 auto;position:relative;background:var(--graview-bar);border-bottom:1px solid var(--graview-edge);color:var(--graview-ink);font-family:var(--graview-font-body,system-ui)}
|
|
117
|
+
.graview-bar-row{display:flex;flex-wrap:nowrap;align-items:center;gap:16px;height:${BAR_HEIGHT - 1}px;margin:0;padding:0 16px}
|
|
118
|
+
.graview-bar-home,.graview-bar-face,.graview-bar-place,.graview-bar-item,.graview-bar-at{display:inline-flex;align-items:center;box-sizing:border-box;min-width:0;margin:0;border:0;background:none;box-shadow:none;font:inherit;letter-spacing:normal;text-transform:none;text-decoration:none;color:var(--graview-ink);cursor:pointer;text-align:left}
|
|
119
|
+
.graview-bar-app{display:flex;flex:0 1 auto;max-width:50%}
|
|
120
|
+
.graview-bar-app::after{content:"";flex:0 100000 auto;width:2px;min-width:0}
|
|
121
|
+
.graview-bar-name{margin:0;display:flex;font-family:var(--graview-font-display,var(--graview-font-body,system-ui));font-size:.9375rem;font-weight:600;line-height:1.25}
|
|
122
|
+
.graview-bar-home{gap:8px;min-width:auto;min-height:30px;padding:0;letter-spacing:-.005em}
|
|
123
|
+
.graview-bar-app-name{display:-webkit-box;-webkit-box-orient:vertical;-webkit-line-clamp:2;overflow:hidden;text-overflow:ellipsis;overflow-wrap:normal;word-break:normal}
|
|
124
|
+
.graview-bar-faces{display:inline-flex;flex:0 0 auto;box-sizing:border-box;height:30px;margin:0;padding:2px;gap:2px;border:1px solid var(--graview-edge);border-radius:8px}
|
|
125
|
+
.graview-bar-face{gap:6px;padding:0 10px;border-radius:6px;font-size:.8125rem;white-space:nowrap;color:var(--graview-ink-muted)}
|
|
126
|
+
.graview-bar-face span{min-width:0;max-width:11em;overflow:hidden;text-overflow:ellipsis}
|
|
127
|
+
.graview-bar-face[aria-pressed=true]{color:var(--graview-ink);font-weight:600;background:color-mix(in srgb,var(--graview-accent) 16%,transparent)}
|
|
128
|
+
[data-switch-drawn=icons] .graview-bar-face{padding:0 8px}
|
|
129
|
+
[data-switch-drawn=icons] .graview-bar-face span{position:absolute;width:1px;height:1px;overflow:hidden;clip:rect(0 0 0 0)}
|
|
130
|
+
.graview-bar-mid{display:flex;flex:1 100 0;min-width:0}
|
|
131
|
+
.graview-bar-mid[data-place]{min-width:9em;flex-basis:auto}
|
|
132
|
+
.graview-bar-place-at{position:relative;display:inline-flex;min-width:0;max-width:100%}
|
|
133
|
+
.graview-bar-place{gap:6px;height:30px;max-width:100%;padding:0 8px;border:1px solid transparent;border-radius:8px;font-size:.875rem;font-weight:600}
|
|
134
|
+
.graview-bar-face:hover,.graview-bar-place:hover{border-color:var(--graview-edge);color:var(--graview-ink)}
|
|
135
|
+
.graview-bar-place-words{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
|
|
136
|
+
.graview-bar-mid[data-places=standing]{flex:0 0 auto}
|
|
137
|
+
.graview-bar-places{display:flex;align-items:center;gap:${PLACE_GAP}px;min-width:0;margin:0;padding:0}
|
|
138
|
+
.graview-bar-at{gap:6px;flex:0 0 auto;height:30px;padding:0 8px;border-block:2px solid transparent;border-radius:0;font-size:.875rem;font-weight:500;white-space:nowrap;color:var(--graview-ink-muted)}
|
|
139
|
+
.graview-bar-at:is(:hover,[aria-current],[aria-expanded=true]){color:var(--graview-ink)}
|
|
140
|
+
.graview-bar-at[aria-current]{font-weight:600;border-bottom-color:var(--graview-accent)}
|
|
141
|
+
.graview-bar-ruler{position:absolute;left:0;top:0;display:flex;width:0;height:0;overflow:hidden;visibility:hidden;pointer-events:none}
|
|
142
|
+
.graview-bar-ruler .graview-bar-at{font-weight:600}
|
|
143
|
+
.graview-bar-list{display:grid;align-content:start;width:min(320px,calc(100vw - 24px));height:auto;max-height:min(70vh,560px);overflow:auto;margin:0;padding:6px;border-radius:10px;border:1px solid var(--graview-edge);background:var(--graview-float);box-shadow:var(--graview-lift-high);color:var(--graview-ink)}
|
|
144
|
+
.graview-bar-list[hidden]{display:none}
|
|
145
|
+
.graview-bar-list p{margin:8px 8px 2px;font-size:.6875rem;font-weight:600;letter-spacing:.06em;text-transform:uppercase;color:var(--graview-ink-muted)}
|
|
146
|
+
.graview-bar-list ul,.graview-bar-list li{display:grid;margin:0;padding:0;list-style:none}
|
|
147
|
+
.graview-bar-item{align-items:flex-start;gap:10px;width:100%;padding:7px 8px;border-radius:7px;font-size:.875rem;line-height:1.3;overflow-wrap:anywhere}
|
|
148
|
+
.graview-bar-item:hover{background:color-mix(in srgb,var(--graview-ink) 7%,transparent)}
|
|
149
|
+
.graview-bar-item[aria-current]{font-weight:600;background:color-mix(in srgb,var(--graview-accent) 14%,transparent)}
|
|
150
|
+
.graview-bar-mark{display:inline-flex;align-items:center;justify-content:center;flex:0 0 auto;width:14px;height:1.3em;color:var(--graview-ink-muted)}
|
|
151
|
+
.graview-bar :focus-visible{outline:2px solid var(--graview-accent);outline-offset:1px}
|
|
152
|
+
.graview-bar-tools{display:contents}
|
|
153
|
+
.graview-bar-tools>*{margin-left:-8px}
|
|
154
|
+
.graview-bar-tools>:is(.graview-bar-find,.graview-bar-find-open){margin-left:auto}
|
|
155
|
+
.graview-bar-find{position:relative;display:flex;min-width:7.5rem;flex:0 600 9rem;--graview-bar-find-end:3.25em}
|
|
156
|
+
.graview-bar-find-slot{display:flex;flex:1;min-width:0}
|
|
157
|
+
.graview-bar-find-keys{position:absolute;right:10px;line-height:30px;pointer-events:none;font-size:.75rem;color:var(--graview-ink-faint)}
|
|
158
|
+
.graview-bar-find:is(:focus-within,:has(input:not(:placeholder-shown))){flex-basis:16rem;--graview-bar-find-end:10px}
|
|
159
|
+
:is(:focus-within,:has(input:not(:placeholder-shown)))>.graview-bar-find-keys,.graview-bar-find-open{display:none}
|
|
160
|
+
.graview-bar-line{display:flex;flex:0 0 auto;padding:6px 8px;background:var(--graview-ground);border-bottom:1px solid var(--graview-edge);font-family:var(--graview-font-body,system-ui)}
|
|
161
|
+
.graview-bar-line .graview-bar-place{height:auto;min-height:32px}
|
|
162
|
+
.graview-bar-line .graview-bar-place-words{white-space:normal}
|
|
163
|
+
@media (pointer:coarse){.graview-bar-find-keys{display:none}}
|
|
164
|
+
@container graview-bar (max-width: ${BAR_PHONE - 1}px){
|
|
165
|
+
.graview-bar-row{gap:10px;padding:0 12px}
|
|
166
|
+
.graview-bar-app{max-width:none;flex:1 1 auto}
|
|
167
|
+
.graview-bar-mid,.graview-bar-find{display:none}
|
|
81
168
|
.graview-bar-find-open{display:inline-flex}
|
|
82
|
-
.graview-bar[data-finding] .graview-bar-app{display:none}
|
|
83
|
-
.graview-bar[data-finding] .graview-bar-
|
|
84
|
-
.graview-bar[data-finding] .graview-bar-tools{flex:1 1 auto}
|
|
85
|
-
.graview-bar[data-finding] .graview-bar-find{display:flex}
|
|
86
|
-
.graview-bar[data-finding] .graview-bar-find-open{display:none}
|
|
169
|
+
.graview-bar[data-finding] :is(.graview-bar-app,.graview-bar-faces,.graview-bar-find-open){display:none}
|
|
170
|
+
.graview-bar[data-finding] .graview-bar-find{display:flex;flex:1 1 auto;min-width:0}
|
|
87
171
|
}`;
|
|
88
172
|
/** A tool's own box: one size, square, its name in words for whoever cannot see the mark. */
|
|
89
173
|
export const toolStyle = {
|
|
@@ -106,27 +190,67 @@ export const toolStyle = {
|
|
|
106
190
|
};
|
|
107
191
|
/**
|
|
108
192
|
* THE BAR. The app at the left — its mark, its name as the page's heading,
|
|
109
|
-
* the way home —
|
|
110
|
-
*
|
|
111
|
-
*
|
|
193
|
+
* the way home — then the switch between the scene and the pages (FR-137),
|
|
194
|
+
* then, on Pages, the place you are on (FR-138), and the tools at the
|
|
195
|
+
* right: the Find a face puts in it, then `tools` (the standing and the
|
|
196
|
+
* person). One row of one height on a desk and a phone; on a phone the
|
|
197
|
+
* place is the page's first line, under the bar.
|
|
112
198
|
*/
|
|
113
|
-
export function AppBar({ brand, name, heading = 1, home, places, current, reach, tools, find = true, description, findBox, onFind, }) {
|
|
199
|
+
export function AppBar({ brand, name, heading = 1, home, faces, places, current, reach, tools, find = true, description, findBox, onFind, switch: switchForm = "words", scenePlaces, }) {
|
|
114
200
|
const bar = useRef(null);
|
|
115
201
|
const [slot, setSlot] = useState(null);
|
|
116
202
|
const [compact, setCompact] = useState(false);
|
|
117
203
|
const [finding, setFinding] = useState(false);
|
|
204
|
+
// Whether the switch's words fit beside everything else the row holds (`roomForWords`).
|
|
205
|
+
const [roomy, setRoomy] = useState(true);
|
|
206
|
+
// Which places stand on the row, by their place in the list ("0,1,2"); null, the one control (FR-145).
|
|
207
|
+
const [standing, setStanding] = useState(null);
|
|
208
|
+
// The places on the face drawn: the routed face's, or the scene's (FR-144).
|
|
209
|
+
const onPages = !faces || faces.pages.current;
|
|
210
|
+
const shown = onPages ? { places, current, reach } : scenePlaces;
|
|
211
|
+
const list = shown && shown.places.length > 0 ? shown : null;
|
|
212
|
+
const currentAt = list ? list.places.findIndex((place) => place.key === list.current) : -1;
|
|
213
|
+
const wordsAsked = Boolean(faces) && switchForm === "words";
|
|
214
|
+
const weigh = useCallback(() => {
|
|
215
|
+
const element = bar.current;
|
|
216
|
+
if (!element)
|
|
217
|
+
return;
|
|
218
|
+
const stand = standsIn(element, currentAt, wordsAsked);
|
|
219
|
+
if (stand !== undefined)
|
|
220
|
+
setStanding(stand);
|
|
221
|
+
// Standing places were weighed with the switch's words kept.
|
|
222
|
+
const room = stand ? true : roomForWords(element);
|
|
223
|
+
if (room !== null)
|
|
224
|
+
setRoomy(room);
|
|
225
|
+
}, [currentAt, wordsAsked]);
|
|
118
226
|
useLayoutEffect(() => {
|
|
119
227
|
const element = bar.current;
|
|
120
228
|
if (!element)
|
|
121
229
|
return;
|
|
122
|
-
const read = () =>
|
|
230
|
+
const read = () => {
|
|
231
|
+
const width = element.getBoundingClientRect().width;
|
|
232
|
+
setCompact(width < BAR_PHONE);
|
|
233
|
+
// What hangs from the bar as wide as it (a phone's Find sheet) is told the bar's width, not the screen's.
|
|
234
|
+
element.style.setProperty("--graview-bar-width", `${Math.round(width)}px`);
|
|
235
|
+
weigh();
|
|
236
|
+
};
|
|
123
237
|
read();
|
|
238
|
+
// The brand's face, once it has come, is wider or narrower than the one the row was first weighed in.
|
|
239
|
+
const fonts = typeof document === "undefined" ? undefined : document.fonts;
|
|
240
|
+
void fonts?.ready.then(weigh);
|
|
241
|
+
fonts?.addEventListener?.("loadingdone", weigh);
|
|
124
242
|
if (typeof ResizeObserver === "undefined")
|
|
125
|
-
return;
|
|
243
|
+
return () => fonts?.removeEventListener?.("loadingdone", weigh);
|
|
126
244
|
const watch = new ResizeObserver(read);
|
|
127
245
|
watch.observe(element);
|
|
128
|
-
|
|
129
|
-
|
|
246
|
+
// What stands beside the places — the tools, a count on the standing — changes their room without changing the bar's width.
|
|
247
|
+
for (const tool of element.querySelectorAll(".graview-bar-tools > *"))
|
|
248
|
+
watch.observe(tool);
|
|
249
|
+
return () => {
|
|
250
|
+
watch.disconnect();
|
|
251
|
+
fonts?.removeEventListener?.("loadingdone", weigh);
|
|
252
|
+
};
|
|
253
|
+
}, [weigh]);
|
|
130
254
|
const told = useRef(onFind);
|
|
131
255
|
told.current = onFind;
|
|
132
256
|
useLayoutEffect(() => {
|
|
@@ -151,31 +275,108 @@ export function AppBar({ brand, name, heading = 1, home, places, current, reach,
|
|
|
151
275
|
requestAnimationFrame(() => opener.current?.focus());
|
|
152
276
|
}, []);
|
|
153
277
|
const Heading = heading === false ? "span" : `h${heading}`;
|
|
278
|
+
const Home = (home.href !== undefined ? "a" : "button");
|
|
154
279
|
const press = (go) => (event) => {
|
|
155
280
|
if (!go || event.button !== 0 || event.metaKey || event.ctrlKey || event.shiftKey || event.altKey)
|
|
156
281
|
return;
|
|
157
282
|
event.preventDefault();
|
|
158
283
|
go();
|
|
159
284
|
};
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
285
|
+
// The place control: in the bar on a desk, as the page's first line on a phone; the places themselves on the row when they fit (FR-145).
|
|
286
|
+
const scene = !onPages;
|
|
287
|
+
const stands = list && !compact && standing !== null ? standing.split(",").map(Number).filter((at) => at < list.places.length) : null;
|
|
288
|
+
const placeControl = list ? _jsx(Places, { places: list.places, current: list.current, reach: list.reach, scene: scene, stands: stands }) : null;
|
|
289
|
+
const said = list ? list.places.map((place) => place.label).join("\n") : "";
|
|
290
|
+
// What the row holds changed — the name, the places, the face, the brand — so the places and the words may fit now, or no longer.
|
|
291
|
+
useLayoutEffect(weigh, [weigh, name, brand, said, list?.current, onPages, compact, switchForm, find]);
|
|
292
|
+
// The marks alone on a phone's bar, when asked, or when the words do not fit.
|
|
293
|
+
const drawn = switchForm === "icons" || !roomy || compact ? "icons" : "words";
|
|
294
|
+
return (_jsxs(_Fragment, { children: [_jsxs("header", { ref: bar, className: "graview-bar", "data-testid": "app-bar", "data-graview-app-bar": "", ...(finding && compact ? { "data-finding": "" } : {}), children: [_jsx("style", { children: BAR_CSS }), _jsxs("div", { className: "graview-bar-row", children: [_jsx("div", { className: "graview-bar-app", children: _jsx(Heading, { className: "graview-bar-name", children: _jsxs(Home, { ...(home.href !== undefined ? { href: home.href, onClick: press(home.go) } : { type: "button", onClick: home.go }), "data-testid": "app-home", ...(home.current ? { "aria-current": "page" } : {}), title: description ? `${name} — ${description}` : `${name} — home`, className: "graview-bar-home", children: [_jsx(AppMark, { brand: brand, size: 20 }), _jsx("span", { className: "graview-bar-app-name", "data-testid": "app-name", children: name })] }) }) }), faces ? _jsx(FaceSwitch, { faces: faces, form: switchForm, drawn: drawn }) : null, _jsx("div", { className: "graview-bar-mid", ...(stands ? { "data-places": "standing" } : !compact && placeControl ? { "data-place": "" } : {}), children: compact ? null : placeControl }), !compact && list && list.places.length > 1 ? (_jsxs("span", { className: "graview-bar-ruler", "aria-hidden": "true", "data-graview-bar-ruler": "", children: [list.places.map((place) => (_jsx("span", { className: "graview-bar-at", children: place.label }, place.key))), _jsxs("span", { className: "graview-bar-at", children: [MORE, _jsx(Chevron, {})] })] })) : null, _jsxs("div", { className: "graview-bar-tools", children: [find ? (_jsxs(_Fragment, { children: [_jsx("button", { type: "button", className: "graview-bar-find-open", ref: opener, "data-testid": "app-find-open", "aria-label": "Find", title: "Find", onClick: openFind, style: { ...toolStyle, display: undefined }, children: _jsx(FindMark, {}) }), _jsxs("div", { className: "graview-bar-find", "data-testid": "app-find", onBlur: (event) => {
|
|
295
|
+
// Put away on a phone when the keyboard leaves it with nothing typed.
|
|
296
|
+
const next = event.relatedTarget;
|
|
297
|
+
if (next instanceof Node && event.currentTarget.contains(next))
|
|
298
|
+
return;
|
|
299
|
+
if (!event.currentTarget.querySelector("input")?.value)
|
|
300
|
+
putAway();
|
|
301
|
+
}, onKeyDownCapture: (event) => {
|
|
302
|
+
if (event.key === "Escape" && compact)
|
|
303
|
+
escaping.current = true;
|
|
304
|
+
}, onKeyDown: (event) => {
|
|
305
|
+
if (event.key !== "Escape" || !compact)
|
|
306
|
+
return;
|
|
307
|
+
// The scene's Find takes Escape for its words first; an empty box is put away.
|
|
308
|
+
if (!event.defaultPrevented || !event.currentTarget.querySelector("input")?.value)
|
|
309
|
+
putAway();
|
|
310
|
+
else
|
|
311
|
+
escaping.current = false;
|
|
312
|
+
}, children: [_jsx("div", { ref: setSlot, className: "graview-bar-find-slot", children: findBox }), _jsx("span", { className: "graview-bar-find-keys", "data-testid": "app-find-keys", "aria-hidden": "true", children: findKeys() })] })] })) : null, tools] })] })] }), compact && placeControl ? (_jsx("div", { className: "graview-bar-line", "data-graview-place-line": "", "data-testid": "app-place-line", children: placeControl })) : null] }));
|
|
313
|
+
}
|
|
314
|
+
/**
|
|
315
|
+
* THE SWITCH (FR-137): the scene and the pages, side by side, an icon and a
|
|
316
|
+
* word each — the words the accessible names alone on a phone — the one
|
|
317
|
+
* drawn pressed. Choosing the scene draws it under the bar; choosing the
|
|
318
|
+
* pages goes back to the page you were on.
|
|
319
|
+
*/
|
|
320
|
+
function FaceSwitch({ faces, form, drawn }) {
|
|
321
|
+
const face = (which) => {
|
|
322
|
+
const one = faces[which];
|
|
323
|
+
return (_jsxs("button", { type: "button", className: "graview-bar-face", "data-testid": `app-face-${which}`, "aria-pressed": one.current, title: one.label, onClick: () => {
|
|
324
|
+
if (!one.current)
|
|
325
|
+
one.go();
|
|
326
|
+
}, children: [which === "scene" ? _jsx(SceneMark, {}) : _jsx(PagesMark, {}), _jsx("span", { children: one.label })] }));
|
|
327
|
+
};
|
|
328
|
+
return (_jsxs("div", { className: "graview-bar-faces", role: "group", "aria-label": `${faces.scene.label} or ${faces.pages.label}`, "data-testid": "app-faces", "data-switch": form, "data-switch-drawn": drawn, children: [face("scene"), face("pages")] }));
|
|
329
|
+
}
|
|
330
|
+
/**
|
|
331
|
+
* WHETHER THE SWITCH'S WORDS FIT (the switch left to say its words where
|
|
332
|
+
* there is room). The row is weighed as it is drawn: the room the middle
|
|
333
|
+
* has past what the place wants (its words, up to 14em; none on the
|
|
334
|
+
* scene), less what Find has given up; none when the app's name has
|
|
335
|
+
* wrapped. The words fit when that room holds them — the words
|
|
336
|
+
* drawn already, or their width to come when the marks are drawn alone —
|
|
337
|
+
* so the switch never flips back and forth at one width. Not weighed while
|
|
338
|
+
* Find is in use (it is drawn wide then), on a phone's bar (the marks
|
|
339
|
+
* alone), or where nothing is laid out (null).
|
|
340
|
+
*/
|
|
341
|
+
function roomForWords(header) {
|
|
342
|
+
const faces = header.querySelector(".graview-bar-faces");
|
|
343
|
+
const mid = header.querySelector(".graview-bar-mid");
|
|
344
|
+
const find = header.querySelector(".graview-bar-find");
|
|
345
|
+
if (!faces || !mid || find?.matches(":focus-within"))
|
|
346
|
+
return null;
|
|
347
|
+
const row = header.getBoundingClientRect().width;
|
|
348
|
+
if (row < BAR_PHONE)
|
|
349
|
+
return null;
|
|
350
|
+
// The words to come, beside each mark: the word, the gap before it, and the button's wider padding.
|
|
351
|
+
const words = wordsOf(faces);
|
|
352
|
+
const place = mid.querySelector(".graview-bar-place-words");
|
|
353
|
+
const em = place ? Number.parseFloat(getComputedStyle(place).fontSize) || 14 : 0;
|
|
354
|
+
// The place's words, its chevron, its gap, padding and frame: up to 14em of them.
|
|
355
|
+
const wanted = place ? Math.min(place.scrollWidth + 34, 14 * em) : 0;
|
|
356
|
+
let spare = mid.getBoundingClientRect().width - wanted;
|
|
357
|
+
if (find && find.getBoundingClientRect().width > 0) {
|
|
358
|
+
const basis = Number.parseFloat(getComputedStyle(find).flexBasis);
|
|
359
|
+
if (Number.isFinite(basis))
|
|
360
|
+
spare -= Math.max(0, basis - find.getBoundingClientRect().width);
|
|
361
|
+
}
|
|
362
|
+
// The app's name on two lines has given its room already: no room for the words.
|
|
363
|
+
const name = header.querySelector(".graview-bar-app-name");
|
|
364
|
+
if (name && name.getBoundingClientRect().height > Number.parseFloat(getComputedStyle(name).lineHeight) * 1.5)
|
|
365
|
+
return false;
|
|
366
|
+
return faces.getAttribute("data-switch-drawn") === "icons" ? spare >= words + 2 : spare >= -1;
|
|
367
|
+
}
|
|
368
|
+
/** Find's shortcut as the keyboard says it: ⌘K on a Mac (and an iPad's keyboard), Ctrl K elsewhere. */
|
|
369
|
+
function findKeys() {
|
|
370
|
+
const platform = typeof navigator === "undefined" ? "" : (navigator.platform ?? "");
|
|
371
|
+
return /Mac|iPhone|iPad|iPod/.test(platform) ? "⌘K" : "Ctrl K";
|
|
372
|
+
}
|
|
373
|
+
/** The scene's mark: a plot of the city, seen from above at an angle. */
|
|
374
|
+
function SceneMark() {
|
|
375
|
+
return (_jsxs("svg", { width: "14", height: "14", viewBox: "0 0 14 14", "aria-hidden": "true", focusable: "false", style: { flex: "0 0 auto" }, children: [_jsx("path", { d: "M7 1.5 L12.5 4.5 L7 7.5 L1.5 4.5 Z", fill: "none", stroke: "currentColor", strokeWidth: "1.3", strokeLinejoin: "round" }), _jsx("path", { d: "M1.5 7.5 L7 10.5 L12.5 7.5", fill: "none", stroke: "currentColor", strokeWidth: "1.3", strokeLinejoin: "round", strokeLinecap: "round" })] }));
|
|
376
|
+
}
|
|
377
|
+
/** The pages' mark: a page of lines. */
|
|
378
|
+
function PagesMark() {
|
|
379
|
+
return (_jsxs("svg", { width: "14", height: "14", viewBox: "0 0 14 14", "aria-hidden": "true", focusable: "false", style: { flex: "0 0 auto" }, children: [_jsx("rect", { x: "2.25", y: "1.75", width: "9.5", height: "10.5", rx: "1.5", fill: "none", stroke: "currentColor", strokeWidth: "1.3" }), _jsx("path", { d: "M4.5 5 H9.5 M4.5 7.25 H9.5 M4.5 9.5 H7.5", stroke: "currentColor", strokeWidth: "1.2", strokeLinecap: "round" })] }));
|
|
179
380
|
}
|
|
180
381
|
/** The way a menu opens: a chevron drawn, not a glyph a face may draw as a dot. */
|
|
181
382
|
function Chevron() {
|
|
@@ -185,77 +386,114 @@ function Chevron() {
|
|
|
185
386
|
function FindMark() {
|
|
186
387
|
return (_jsxs("svg", { width: "16", height: "16", viewBox: "0 0 16 16", "aria-hidden": "true", focusable: "false", children: [_jsx("circle", { cx: "7", cy: "7", r: "4.75", fill: "none", stroke: "currentColor", strokeWidth: "1.5" }), _jsx("path", { d: "M10.5 10.5 L14 14", stroke: "currentColor", strokeWidth: "1.5", strokeLinecap: "round" })] }));
|
|
187
388
|
}
|
|
389
|
+
/** A place's mark in the list: the home's house, a kind's plot in its own hue. */
|
|
390
|
+
function PlaceMark({ place }) {
|
|
391
|
+
// The scene's whole thing: the scene's own mark, as on the switch (FR-144).
|
|
392
|
+
if (place.key === WHOLE_KEY)
|
|
393
|
+
return _jsx(SceneMark, {});
|
|
394
|
+
if (place.group === "home") {
|
|
395
|
+
return (_jsx("svg", { width: "13", height: "13", viewBox: "0 0 14 14", "aria-hidden": "true", focusable: "false", children: _jsx("path", { d: "M2 6.5 L7 2 L12 6.5 V12 H2 Z", fill: "none", stroke: "currentColor", strokeWidth: "1.3", strokeLinejoin: "round" }) }));
|
|
396
|
+
}
|
|
397
|
+
// A kind's plot, as the scene draws it from altitude (the mark FR-117 gave a kind with no drawing); a picture of it wears its outline; how the kinds connect, the ink's.
|
|
398
|
+
const hue = place.kind ? `hsl(${Math.round(hueFor(place.kind))} 55% 52%` : "rgb(128 128 128";
|
|
399
|
+
return (_jsx("span", { "aria-hidden": "true", style: {
|
|
400
|
+
display: "inline-block",
|
|
401
|
+
width: 12,
|
|
402
|
+
height: 8,
|
|
403
|
+
clipPath: "polygon(50% 0, 100% 50%, 50% 100%, 0 50%)",
|
|
404
|
+
background: place.group === "lists" ? `${hue})` : `linear-gradient(${hue}), ${hue})) center / 6px 4px no-repeat, ${hue} / 0.35)`,
|
|
405
|
+
} }));
|
|
406
|
+
}
|
|
407
|
+
const GROUPS = [{ group: "home" }, { group: "lists", heading: "Lists" }, { group: "pictures", heading: "Pictures" }];
|
|
408
|
+
/** The word the places that do not stand on the row fold under (FR-145). */
|
|
409
|
+
const MORE = "More";
|
|
188
410
|
/**
|
|
189
|
-
*
|
|
190
|
-
*
|
|
191
|
-
*
|
|
192
|
-
*
|
|
411
|
+
* ONE PLACE, AS THE BAR OFFERS IT — in the list, or standing on the row: a
|
|
412
|
+
* link where it has an address, a press where the bar decides; marked
|
|
413
|
+
* `aria-current` where the reader is. Whichever way it is drawn it is
|
|
414
|
+
* `app-place-<key>` and says its path (`data-place-path`), once in the bar.
|
|
193
415
|
*/
|
|
194
|
-
function
|
|
195
|
-
const
|
|
196
|
-
const
|
|
197
|
-
const
|
|
416
|
+
function PlaceEntry({ place, current, reach, standing, done }) {
|
|
417
|
+
const at = place.key === current;
|
|
418
|
+
const href = reach.href?.(place.path);
|
|
419
|
+
const Entry = (href !== undefined ? "a" : "button");
|
|
420
|
+
return (_jsxs(Entry, { className: standing ? "graview-bar-at" : "graview-bar-item", "data-testid": `app-place-${place.key}`, "data-place-path": place.path, ...(at ? { "aria-current": "page" } : {}), ...(href !== undefined ? { href } : { type: "button" }), onClick: (event) => {
|
|
421
|
+
done();
|
|
422
|
+
if (!reach.go)
|
|
423
|
+
return;
|
|
424
|
+
if (href !== undefined && (event.button !== 0 || event.metaKey || event.ctrlKey || event.shiftKey || event.altKey))
|
|
425
|
+
return;
|
|
426
|
+
event.preventDefault();
|
|
427
|
+
reach.go(place);
|
|
428
|
+
}, children: [standing ? null : (_jsx("span", { className: "graview-bar-mark", children: _jsx(PlaceMark, { place: place }) })), standing && at ? _jsx("span", { "data-testid": "app-place-current", children: place.label }) : _jsx("span", { children: place.label })] }));
|
|
429
|
+
}
|
|
430
|
+
/** The places of a list, grouped — the home (or the whole thing), the Lists, the Pictures — each group with its heading. */
|
|
431
|
+
function PlaceGroups({ places, current, reach, id, done }) {
|
|
432
|
+
return (_jsx(_Fragment, { children: GROUPS.map(({ group, heading }) => {
|
|
433
|
+
const listed = places.filter((place) => place.group === group);
|
|
434
|
+
if (listed.length === 0)
|
|
435
|
+
return null;
|
|
436
|
+
const named = `${id}-${group}`;
|
|
437
|
+
return (_jsxs("div", { role: "group", ...(heading ? { "aria-labelledby": named } : { "aria-label": listed[0].label }), "data-place-group": group, children: [heading ? _jsx("p", { id: named, children: heading }) : null, _jsx("ul", { children: listed.map((place) => (_jsx("li", { children: _jsx(PlaceEntry, { place: place, current: current, reach: reach, standing: false, done: done }) }, place.key))) })] }, group));
|
|
438
|
+
}) }));
|
|
439
|
+
}
|
|
440
|
+
/**
|
|
441
|
+
* THE PLACE YOU ARE ON, AND EVERY OTHER (FR-138): one control, the place's
|
|
442
|
+
* name and a chevron, that opens the app's places — the home first, then
|
|
443
|
+
* the Lists and the Pictures, each with its mark, a long name wrapped
|
|
444
|
+
* rather than cut. Every place is two presses away, and the bar is one row
|
|
445
|
+
* of one height however many there are. On the scene, what is in view and
|
|
446
|
+
* every picture it can show (FR-144).
|
|
447
|
+
*
|
|
448
|
+
* WHERE THEY FIT, THE PLACES THEMSELVES (FR-145): the ones that stand, as
|
|
449
|
+
* words in their order, the one you are on underlined; then "More", which
|
|
450
|
+
* opens the rest, grouped as the one control's list is — no "More" when
|
|
451
|
+
* every place stands. "More" is `app-places-open` and its list
|
|
452
|
+
* `app-places`, as the one control and its list are, so a place that does
|
|
453
|
+
* not stand is reached by the same two presses either way.
|
|
454
|
+
*/
|
|
455
|
+
function Places({ places, current, reach, scene, stands }) {
|
|
198
456
|
const popover = usePopover("places");
|
|
199
|
-
const
|
|
200
|
-
const
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
const href = reach.href?.(place.path);
|
|
241
|
-
const common = {
|
|
242
|
-
className: inMenu ? "graview-bar-item" : "graview-bar-tab",
|
|
243
|
-
"data-testid": `app-place-${place.key}`,
|
|
244
|
-
"data-place-path": place.path,
|
|
245
|
-
...(here ? { "aria-current": "page" } : {}),
|
|
246
|
-
onClick: (event) => {
|
|
247
|
-
if (inMenu)
|
|
248
|
-
popover.setOpen(false);
|
|
249
|
-
if (!reach.go)
|
|
250
|
-
return;
|
|
251
|
-
if (href !== undefined && (event.button !== 0 || event.metaKey || event.ctrlKey || event.shiftKey || event.altKey))
|
|
252
|
-
return;
|
|
253
|
-
event.preventDefault();
|
|
254
|
-
reach.go(place);
|
|
255
|
-
},
|
|
256
|
-
};
|
|
257
|
-
return href !== undefined ? (_jsx("a", { ...common, href: href, children: place.label }, place.key)) : (_jsx("button", { ...common, type: "button", children: place.label }, place.key));
|
|
258
|
-
};
|
|
259
|
-
return (_jsxs("nav", { ref: row, className: "graview-bar-places", "aria-label": "The app\u2019s places", "data-testid": "app-places", style: { position: "relative" }, children: [_jsxs("div", { ref: measure, className: "graview-bar-measure", "aria-hidden": "true", children: [places.map((place) => (_jsx("span", { className: "graview-bar-tab", style: { fontWeight: 600 }, children: place.label }, place.key))), _jsxs("span", { className: "graview-bar-tab", children: ["More", _jsx(Chevron, {})] })] }), shown.map((place) => tab(place, false)), rest.length > 0 ? (_jsxs("span", { style: { position: "relative", display: "inline-flex" }, children: [_jsxs("button", { type: "button", className: "graview-bar-tab", "data-testid": "app-places-more", ...popover.trigger, onClick: popover.toggle, "aria-label": "More places", children: ["More", _jsx(Chevron, {})] }), _jsx("ul", { ...popover.pane, "data-testid": "app-places-more-list", "aria-label": "More places", hidden: !popover.open, className: "graview-bar-more", style: POPOVER_STYLE, children: rest.map((place) => (_jsx("li", { children: tab(place, true) }, place.key))) })] })) : null] }));
|
|
457
|
+
const said = places.find((place) => place.key === current)?.label ?? "Places";
|
|
458
|
+
const rest = stands ? places.filter((_, at) => !stands.includes(at)) : places;
|
|
459
|
+
return (_jsxs("nav", { className: stands ? "graview-bar-places" : "graview-bar-place-at", "aria-label": scene ? "What the scene shows" : "The app’s places", ...(stands ? { "data-testid": "app-places-standing" } : {}), children: [stands?.map((at) => _jsx(PlaceEntry, { place: places[at], current: current, reach: reach, standing: true, done: () => undefined }, places[at].key)), rest.length > 0 ? (_jsxs(_Fragment, { children: [_jsxs("button", { type: "button", className: stands ? "graview-bar-at" : "graview-bar-place", "data-testid": "app-places-open", ...popover.trigger, onClick: popover.toggle, "aria-label": stands ? `${MORE} — ${rest.length} more ${scene ? "pictures" : "places"}` : `${said} — ${scene ? "everything the scene shows" : "every place"}`, title: stands ? rest.map((place) => place.label).join(", ") : said, children: [stands ? (MORE) : (_jsx("span", { className: "graview-bar-place-words", "data-testid": "app-place-current", children: said })), _jsx(Chevron, {})] }), _jsx("div", { ...popover.pane, "data-testid": "app-places", hidden: !popover.open, className: "graview-bar-list", style: POPOVER_STYLE, children: _jsx(PlaceGroups, { places: rest, current: current, reach: reach, id: popover.pane.id, done: () => popover.setOpen(false) }) })] })) : null] }));
|
|
460
|
+
}
|
|
461
|
+
/**
|
|
462
|
+
* WHICH PLACES STAND ON THE ROW, WEIGHED FROM THE ROW AS DRAWN (FR-145):
|
|
463
|
+
* the places' widths read off the ruler (each at its weight as the place
|
|
464
|
+
* you are on, so marking one never moves the others), and the room from
|
|
465
|
+
* where the places begin to where Find ends, less Find at its least, less
|
|
466
|
+
* the switch's words when they are asked for. The same answer whichever way
|
|
467
|
+
* the places are drawn, so the row never flips between the two at one
|
|
468
|
+
* width. Not weighed while Find is in use (`undefined`); none on a phone's
|
|
469
|
+
* bar, when the app's name has wrapped, or where nothing is laid out (null).
|
|
470
|
+
*/
|
|
471
|
+
function standsIn(header, current, wordsAsked) {
|
|
472
|
+
const ruler = header.querySelector(".graview-bar-ruler");
|
|
473
|
+
const mid = header.querySelector(".graview-bar-mid");
|
|
474
|
+
if (!ruler || !mid)
|
|
475
|
+
return null;
|
|
476
|
+
const find = header.querySelector(".graview-bar-find");
|
|
477
|
+
if (find?.matches(":focus-within"))
|
|
478
|
+
return undefined;
|
|
479
|
+
if (header.getBoundingClientRect().width < BAR_PHONE)
|
|
480
|
+
return null;
|
|
481
|
+
const name = header.querySelector(".graview-bar-app-name");
|
|
482
|
+
if (name && name.getBoundingClientRect().height > Number.parseFloat(getComputedStyle(name).lineHeight) * 1.5)
|
|
483
|
+
return null;
|
|
484
|
+
const widths = [...ruler.children].map((one) => one.getBoundingClientRect().width);
|
|
485
|
+
const more = widths.pop() ?? 0;
|
|
486
|
+
const faces = header.querySelector(".graview-bar-faces");
|
|
487
|
+
const words = faces && wordsAsked ? wordsOf(faces) : 0;
|
|
488
|
+
const drawnWords = faces?.getAttribute("data-switch-drawn") === "words" ? words : 0;
|
|
489
|
+
const from = mid.getBoundingClientRect().left;
|
|
490
|
+
const end = find?.getBoundingClientRect();
|
|
491
|
+
// To Find's end, less Find at its least and the room between them (the row's gap, less the tools' pull); with no Find, the middle's own room.
|
|
492
|
+
const room = end && end.width > 0 ? end.right - from - (Number.parseFloat(getComputedStyle(find).minWidth) || 0) - 8 : mid.getBoundingClientRect().width;
|
|
493
|
+
return placesThatStand({ widths, current, room: Math.floor(room + drawnWords - words), more })?.join(",") ?? null;
|
|
494
|
+
}
|
|
495
|
+
/** The switch's words, beside each mark: the word, the gap before it, and the button's wider padding. */
|
|
496
|
+
function wordsOf(faces) {
|
|
497
|
+
return [...faces.querySelectorAll(".graview-bar-face span")].reduce((sum, span) => sum + span.scrollWidth + 10, 0);
|
|
260
498
|
}
|
|
261
499
|
//# sourceMappingURL=app-bar.js.map
|