@graview/primitives 0.1.17 → 0.1.19

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.
Files changed (235) hide show
  1. package/README.md +5 -2
  2. package/dist/app-bar.d.ts +54 -1
  3. package/dist/app-bar.d.ts.map +1 -1
  4. package/dist/app-bar.js +277 -62
  5. package/dist/app-bar.js.map +1 -1
  6. package/dist/arrange-bar.d.ts +1 -1
  7. package/dist/arrange-bar.d.ts.map +1 -1
  8. package/dist/arrange-bar.js +48 -16
  9. package/dist/arrange-bar.js.map +1 -1
  10. package/dist/bar-panes-door.d.ts +8 -0
  11. package/dist/bar-panes-door.d.ts.map +1 -0
  12. package/dist/bar-panes-door.js +10 -0
  13. package/dist/bar-panes-door.js.map +1 -0
  14. package/dist/bar-panes.d.ts +3 -1
  15. package/dist/bar-panes.d.ts.map +1 -1
  16. package/dist/bar-panes.js +12 -5
  17. package/dist/bar-panes.js.map +1 -1
  18. package/dist/chat.d.ts +68 -30
  19. package/dist/chat.d.ts.map +1 -1
  20. package/dist/chat.js +206 -223
  21. package/dist/chat.js.map +1 -1
  22. package/dist/connections.d.ts +7 -1
  23. package/dist/connections.d.ts.map +1 -1
  24. package/dist/connections.js +13 -13
  25. package/dist/connections.js.map +1 -1
  26. package/dist/declared-lens-doors.d.ts +21 -3
  27. package/dist/declared-lens-doors.d.ts.map +1 -1
  28. package/dist/declared-lens-doors.js +62 -27
  29. package/dist/declared-lens-doors.js.map +1 -1
  30. package/dist/declared-lenses.d.ts.map +1 -1
  31. package/dist/declared-lenses.js +4 -7
  32. package/dist/declared-lenses.js.map +1 -1
  33. package/dist/default-view.d.ts +2 -0
  34. package/dist/default-view.d.ts.map +1 -1
  35. package/dist/default-view.js +10 -4
  36. package/dist/default-view.js.map +1 -1
  37. package/dist/default-views.d.ts.map +1 -1
  38. package/dist/default-views.js +195 -42
  39. package/dist/default-views.js.map +1 -1
  40. package/dist/draft-door.d.ts +3 -0
  41. package/dist/draft-door.d.ts.map +1 -0
  42. package/dist/draft-door.js +22 -0
  43. package/dist/draft-door.js.map +1 -0
  44. package/dist/draft-frame.d.ts +25 -0
  45. package/dist/draft-frame.d.ts.map +1 -0
  46. package/dist/draft-frame.js +69 -0
  47. package/dist/draft-frame.js.map +1 -0
  48. package/dist/editable.d.ts +35 -1
  49. package/dist/editable.d.ts.map +1 -1
  50. package/dist/editable.js +93 -23
  51. package/dist/editable.js.map +1 -1
  52. package/dist/figure.js +1 -1
  53. package/dist/figure.js.map +1 -1
  54. package/dist/find.d.ts.map +1 -1
  55. package/dist/find.js +46 -11
  56. package/dist/find.js.map +1 -1
  57. package/dist/frame.d.ts +8 -6
  58. package/dist/frame.d.ts.map +1 -1
  59. package/dist/frame.js +7 -5
  60. package/dist/frame.js.map +1 -1
  61. package/dist/index.d.ts +23 -11
  62. package/dist/index.d.ts.map +1 -1
  63. package/dist/index.js +20 -9
  64. package/dist/index.js.map +1 -1
  65. package/dist/lens/board.d.ts.map +1 -1
  66. package/dist/lens/board.js +7 -3
  67. package/dist/lens/board.js.map +1 -1
  68. package/dist/lens/calendar-dates.d.ts +2 -0
  69. package/dist/lens/calendar-dates.d.ts.map +1 -1
  70. package/dist/lens/calendar-dates.js +2 -0
  71. package/dist/lens/calendar-dates.js.map +1 -1
  72. package/dist/lens/calendar-drawing.d.ts +2 -2
  73. package/dist/lens/calendar-drawing.d.ts.map +1 -1
  74. package/dist/lens/calendar-drawing.js +1 -2
  75. package/dist/lens/calendar-drawing.js.map +1 -1
  76. package/dist/lens/calendar-options.d.ts +8 -1
  77. package/dist/lens/calendar-options.d.ts.map +1 -1
  78. package/dist/lens/calendar-options.js +5 -0
  79. package/dist/lens/calendar-options.js.map +1 -1
  80. package/dist/lens/calendar-spans.d.ts.map +1 -1
  81. package/dist/lens/calendar-spans.js +1 -2
  82. package/dist/lens/calendar-spans.js.map +1 -1
  83. package/dist/lens/calendar-view.d.ts +2 -1
  84. package/dist/lens/calendar-view.d.ts.map +1 -1
  85. package/dist/lens/calendar-view.js +4 -3
  86. package/dist/lens/calendar-view.js.map +1 -1
  87. package/dist/lens/columns.d.ts.map +1 -1
  88. package/dist/lens/columns.js +2 -2
  89. package/dist/lens/columns.js.map +1 -1
  90. package/dist/lens/coverage.d.ts.map +1 -1
  91. package/dist/lens/coverage.js +14 -10
  92. package/dist/lens/coverage.js.map +1 -1
  93. package/dist/lens/more.d.ts.map +1 -1
  94. package/dist/lens/more.js +2 -1
  95. package/dist/lens/more.js.map +1 -1
  96. package/dist/lens-keeping.d.ts +17 -0
  97. package/dist/lens-keeping.d.ts.map +1 -0
  98. package/dist/lens-keeping.js +127 -0
  99. package/dist/lens-keeping.js.map +1 -0
  100. package/dist/lines-key.d.ts +12 -0
  101. package/dist/lines-key.d.ts.map +1 -0
  102. package/dist/lines-key.js +74 -0
  103. package/dist/lines-key.js.map +1 -0
  104. package/dist/notice-place.d.ts +3 -3
  105. package/dist/notice-place.d.ts.map +1 -1
  106. package/dist/notice-place.js +6 -4
  107. package/dist/notice-place.js.map +1 -1
  108. package/dist/notices.d.ts +6 -0
  109. package/dist/notices.d.ts.map +1 -1
  110. package/dist/notices.js +7 -1
  111. package/dist/notices.js.map +1 -1
  112. package/dist/pages.d.ts +14 -8
  113. package/dist/pages.d.ts.map +1 -1
  114. package/dist/pages.js +14 -7
  115. package/dist/pages.js.map +1 -1
  116. package/dist/places.d.ts +0 -24
  117. package/dist/places.d.ts.map +1 -1
  118. package/dist/places.js +7 -3
  119. package/dist/places.js.map +1 -1
  120. package/dist/profile.d.ts +7 -1
  121. package/dist/profile.d.ts.map +1 -1
  122. package/dist/profile.js +35 -10
  123. package/dist/profile.js.map +1 -1
  124. package/dist/prose-editor.d.ts +9 -0
  125. package/dist/prose-editor.d.ts.map +1 -0
  126. package/dist/prose-editor.js +64 -0
  127. package/dist/prose-editor.js.map +1 -0
  128. package/dist/quick-relations.d.ts.map +1 -1
  129. package/dist/quick-relations.js +2 -2
  130. package/dist/quick-relations.js.map +1 -1
  131. package/dist/reader-lenses.d.ts +21 -0
  132. package/dist/reader-lenses.d.ts.map +1 -0
  133. package/dist/reader-lenses.js +27 -0
  134. package/dist/reader-lenses.js.map +1 -0
  135. package/dist/record-head.d.ts +28 -0
  136. package/dist/record-head.d.ts.map +1 -0
  137. package/dist/record-head.js +3 -0
  138. package/dist/record-head.js.map +1 -0
  139. package/dist/relation-key.d.ts +2 -0
  140. package/dist/relation-key.d.ts.map +1 -1
  141. package/dist/relation-key.js +14 -24
  142. package/dist/relation-key.js.map +1 -1
  143. package/dist/scene-bar.d.ts +29 -0
  144. package/dist/scene-bar.d.ts.map +1 -0
  145. package/dist/scene-bar.js +72 -0
  146. package/dist/scene-bar.js.map +1 -0
  147. package/dist/scene-css.d.ts.map +1 -1
  148. package/dist/scene-css.js +538 -0
  149. package/dist/scene-css.js.map +1 -1
  150. package/dist/scene-places.d.ts +37 -0
  151. package/dist/scene-places.d.ts.map +1 -0
  152. package/dist/scene-places.js +77 -0
  153. package/dist/scene-places.js.map +1 -0
  154. package/dist/scene.d.ts +5 -2
  155. package/dist/scene.d.ts.map +1 -1
  156. package/dist/scene.js +5 -1
  157. package/dist/scene.js.map +1 -1
  158. package/dist/seat-field.d.ts +58 -0
  159. package/dist/seat-field.d.ts.map +1 -0
  160. package/dist/seat-field.js +144 -0
  161. package/dist/seat-field.js.map +1 -0
  162. package/dist/seat-move.d.ts +32 -0
  163. package/dist/seat-move.d.ts.map +1 -0
  164. package/dist/seat-move.js +56 -0
  165. package/dist/seat-move.js.map +1 -0
  166. package/dist/seat-panel.d.ts +37 -0
  167. package/dist/seat-panel.d.ts.map +1 -0
  168. package/dist/seat-panel.js +131 -0
  169. package/dist/seat-panel.js.map +1 -0
  170. package/dist/seat.d.ts +20 -58
  171. package/dist/seat.d.ts.map +1 -1
  172. package/dist/seat.js +28 -45
  173. package/dist/seat.js.map +1 -1
  174. package/dist/seeding.d.ts.map +1 -1
  175. package/dist/seeding.js +4 -4
  176. package/dist/seeding.js.map +1 -1
  177. package/dist/shell.d.ts +25 -21
  178. package/dist/shell.d.ts.map +1 -1
  179. package/dist/shell.js +46 -116
  180. package/dist/shell.js.map +1 -1
  181. package/dist/spec-views.d.ts +9 -2
  182. package/dist/spec-views.d.ts.map +1 -1
  183. package/dist/spec-views.js +55 -6
  184. package/dist/spec-views.js.map +1 -1
  185. package/dist/subject.d.ts +19 -0
  186. package/dist/subject.d.ts.map +1 -0
  187. package/dist/subject.js +159 -0
  188. package/dist/subject.js.map +1 -0
  189. package/dist/text-body.d.ts +28 -0
  190. package/dist/text-body.d.ts.map +1 -0
  191. package/dist/text-body.js +64 -0
  192. package/dist/text-body.js.map +1 -0
  193. package/dist/theme.d.ts.map +1 -1
  194. package/dist/theme.js +6 -523
  195. package/dist/theme.js.map +1 -1
  196. package/dist/view-doors.d.ts +5 -1
  197. package/dist/view-doors.d.ts.map +1 -1
  198. package/dist/view-doors.js +28 -14
  199. package/dist/view-doors.js.map +1 -1
  200. package/dist/workbench/activity.d.ts.map +1 -1
  201. package/dist/workbench/activity.js +9 -9
  202. package/dist/workbench/activity.js.map +1 -1
  203. package/dist/workbench/answer-args.d.ts.map +1 -1
  204. package/dist/workbench/answer-args.js +26 -2
  205. package/dist/workbench/answer-args.js.map +1 -1
  206. package/dist/workbench/back-out.js +3 -3
  207. package/dist/workbench/back-out.js.map +1 -1
  208. package/dist/workbench/inspector.d.ts +11 -6
  209. package/dist/workbench/inspector.d.ts.map +1 -1
  210. package/dist/workbench/inspector.js +42 -56
  211. package/dist/workbench/inspector.js.map +1 -1
  212. package/dist/workbench/standing.d.ts.map +1 -1
  213. package/dist/workbench/standing.js +4 -3
  214. package/dist/workbench/standing.js.map +1 -1
  215. package/dist/workbench/trail.d.ts +10 -17
  216. package/dist/workbench/trail.d.ts.map +1 -1
  217. package/dist/workbench/trail.js +29 -41
  218. package/dist/workbench/trail.js.map +1 -1
  219. package/package.json +7 -7
  220. package/dist/companion.d.ts +0 -73
  221. package/dist/companion.d.ts.map +0 -1
  222. package/dist/companion.js +0 -594
  223. package/dist/companion.js.map +0 -1
  224. package/dist/ladder.d.ts +0 -2
  225. package/dist/ladder.d.ts.map +0 -1
  226. package/dist/ladder.js +0 -48
  227. package/dist/ladder.js.map +0 -1
  228. package/dist/popover.d.ts +0 -21
  229. package/dist/popover.d.ts.map +0 -1
  230. package/dist/popover.js +0 -28
  231. package/dist/popover.js.map +0 -1
  232. package/dist/wordmark.d.ts +0 -15
  233. package/dist/wordmark.d.ts.map +0 -1
  234. package/dist/wordmark.js +0 -50
  235. package/dist/wordmark.js.map +0 -1
package/README.md CHANGED
@@ -41,8 +41,11 @@ the schema, the invariants and the op log.
41
41
  **The app, said once** — `AppBar`: one bar over every face (FR-131) — the
42
42
  brand's mark (`AppMark`, drawn as given, never an SVG that could act) and
43
43
  the app's name as the page's heading, the switch between the scene and the
44
- pages (`faces`, FR-137), on Pages the place you are on as one control that
45
- opens every place (`barPlaces`: the home, the Lists, the Pictures, FR-138),
44
+ pages (`faces`, FR-137), the place you are on as one control that
45
+ opens every place (`barPlaces`: the home, the Lists, the Pictures, FR-138;
46
+ on the scene `useScenePlaces`: the whole thing and each picture, FR-144),
47
+ the places standing on the row where they fit, the rest under "More"
48
+ (`placesThatStand`, FR-145),
46
49
  and three tools of one size: the Find a
47
50
  face puts in it (`useBarFind`), `Standing` and `Profile`. `AppTitle` draws
48
51
  the mark, the name and the line under it elsewhere; `useFavicon` is for a
package/dist/app-bar.d.ts CHANGED
@@ -16,6 +16,8 @@ export interface BarPlace {
16
16
  export declare const HOME_KEY = "home";
17
17
  /** The home's address on the routed face: the app's own. */
18
18
  export declare const HOME_PATH = "/";
19
+ /** The key of the scene's first place (FR-144): the whole thing, no picture in view. */
20
+ export declare const WHOLE_KEY = "scene:whole";
19
21
  /**
20
22
  * THE APP'S PLACES, IN THE LIST'S ORDER (FR-138): the home; each kind's list
21
23
  * in the declaration's order; each named picture, then how the kinds
@@ -42,6 +44,13 @@ export interface BarFind {
42
44
  readonly slot: HTMLElement;
43
45
  /** A phone's bar: the box opens over the bar's row when it is asked for. */
44
46
  readonly compact: boolean;
47
+ /**
48
+ * Where the face puts a tool of its own, after Find and before the
49
+ * standing: the scene's Activity — what happened, the turns to take back,
50
+ * the seat's agent — which only the scene's face holds, so a page that
51
+ * opens on the pages never fetches it.
52
+ */
53
+ readonly own: HTMLElement | null;
45
54
  }
46
55
  export declare const BarFindContext: import("react").Context<BarFind | null>;
47
56
  /** The bar's place for a Find box, when a bar above this face has one. */
@@ -64,10 +73,37 @@ export interface BarFaces {
64
73
  readonly scene: BarFace;
65
74
  readonly pages: BarFace;
66
75
  }
76
+ /** How the switch is drawn: its words beside its marks where there is room, or its marks alone (the words its names). */
77
+ export type BarSwitch = "words" | "icons";
67
78
  /** One size for every tool on the bar (FR-131). */
68
79
  export declare const TOOL = 30;
69
80
  /** The bar's one row, its rule under it included, on a desk and a phone alike (FR-138). */
70
81
  export declare const BAR_HEIGHT = 48;
82
+ /** 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. */
83
+ export declare const BAR_PHONE = 640;
84
+ /** The room between two places standing on the row (FR-145). */
85
+ export declare const PLACE_GAP = 4;
86
+ /** The fewest places that stand on the row: fewer, and the one control says the same in less room. */
87
+ export declare const FEWEST_STANDING = 2;
88
+ /**
89
+ * WHICH PLACES STAND ON THE ROW (FR-145), from their widths and the room:
90
+ * every one when all fit; else the first ones in their order, the current
91
+ * one always among them (in its own place in the order, the last of the
92
+ * first ones giving way to it), with "More" after them; else none — null,
93
+ * the one control. Fewer than `FEWEST_STANDING` is none. A width that is
94
+ * not measured (nothing laid out) is none too.
95
+ */
96
+ export declare function placesThatStand(input: {
97
+ /** Each place's width as it would stand, in the order of the list. */
98
+ readonly widths: readonly number[];
99
+ /** Where the current place is in the list; -1 for none. */
100
+ readonly current: number;
101
+ /** The room the row has for them. */
102
+ readonly room: number;
103
+ /** "More"'s width. */
104
+ readonly more: number;
105
+ readonly gap?: number;
106
+ }): readonly number[] | null;
71
107
  /** A tool's own box: one size, square, its name in words for whoever cannot see the mark. */
72
108
  export declare const toolStyle: CSSProperties;
73
109
  /**
@@ -78,7 +114,7 @@ export declare const toolStyle: CSSProperties;
78
114
  * person). One row of one height on a desk and a phone; on a phone the
79
115
  * place is the page's first line, under the bar.
80
116
  */
81
- export declare function AppBar({ brand, name, heading, home, faces, places, current, reach, tools, find, description, findBox, onFind, }: {
117
+ export declare function AppBar({ brand, name, heading, home, faces, places, current, reach, tools, find, description, findBox, onFind, switch: switchForm, scenePlaces, }: {
82
118
  readonly brand: Brand | undefined;
83
119
  readonly name: string;
84
120
  /** The level the app's name is said at: 1 when the page is the app's, `false` when the host's own heading already says it. */
@@ -105,5 +141,22 @@ export declare function AppBar({ brand, name, heading, home, faces, places, curr
105
141
  readonly findBox?: ReactNode;
106
142
  /** Told where the bar keeps the Find box, for the face under it (`BarFindContext`). */
107
143
  readonly onFind?: (find: BarFind | null) => void;
144
+ /**
145
+ * How the switch is drawn: `"words"` (the default) says "Scene" and
146
+ * "Pages" beside their marks where the bar has room and draws the marks
147
+ * alone where it is narrow; `"icons"` draws the marks alone at every
148
+ * width. Either way the words are the buttons' names and their titles.
149
+ */
150
+ readonly switch?: BarSwitch;
151
+ /**
152
+ * On the scene, what it can show (FR-144): the whole thing and each
153
+ * picture, the one in view, and the way to each (`useScenePlaces`). None,
154
+ * and the scene's bar names no place.
155
+ */
156
+ readonly scenePlaces?: {
157
+ readonly places: readonly BarPlace[];
158
+ readonly current: string | null;
159
+ readonly reach: BarGo;
160
+ } | undefined;
108
161
  }): import("react").JSX.Element;
109
162
  //# sourceMappingURL=app-bar.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"app-bar.d.ts","sourceRoot":"","sources":["../src/app-bar.tsx"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"app-bar.d.ts","sourceRoot":"","sources":["../src/app-bar.tsx"],"names":[],"mappings":"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"}
package/dist/app-bar.js CHANGED
@@ -7,6 +7,8 @@ import { AppMark } from "./app-title.js";
7
7
  export const HOME_KEY = "home";
8
8
  /** The home's address on the routed face: the app's own. */
9
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";
10
12
  const upper = (word) => word.charAt(0).toUpperCase() + word.slice(1);
11
13
  /**
12
14
  * THE APP'S PLACES, IN THE LIST'S ORDER (FR-138): the home; each kind's list
@@ -57,23 +59,88 @@ export const useBarFind = () => useContext(BarFindContext);
57
59
  export const TOOL = 30;
58
60
  /** The bar's one row, its rule under it included, on a desk and a phone alike (FR-138). */
59
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
+ */
60
115
  const BAR_CSS = `
61
- .graview-bar{display:block;margin:0;padding:0;container-type: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)}
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)}
62
117
  .graview-bar-row{display:flex;flex-wrap:nowrap;align-items:center;gap:16px;height:${BAR_HEIGHT - 1}px;margin:0;padding:0 16px}
63
- .graview-bar-home,.graview-bar-face,.graview-bar-place,.graview-bar-item{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}
64
- .graview-bar-app{display:flex;flex:0 1 auto;min-width:0;max-width:34%}
65
- .graview-bar-name{margin:0;min-width: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}
66
- .graview-bar-home{gap:8px;min-height:30px;padding:0;letter-spacing:-.005em;overflow-wrap:anywhere}
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}
67
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}
68
125
  .graview-bar-face{gap:6px;padding:0 10px;border-radius:6px;font-size:.8125rem;white-space:nowrap;color:var(--graview-ink-muted)}
69
126
  .graview-bar-face span{min-width:0;max-width:11em;overflow:hidden;text-overflow:ellipsis}
70
127
  .graview-bar-face[aria-pressed=true]{color:var(--graview-ink);font-weight:600;background:color-mix(in srgb,var(--graview-accent) 16%,transparent)}
71
- .graview-bar-mid{display:flex;flex:1 1 auto;min-width:0}
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}
72
132
  .graview-bar-place-at{position:relative;display:inline-flex;min-width:0;max-width:100%}
73
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}
74
134
  .graview-bar-face:hover,.graview-bar-place:hover{border-color:var(--graview-edge);color:var(--graview-ink)}
75
135
  .graview-bar-place-words{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
76
- .graview-bar-list{display:grid;width:min(320px,calc(100vw - 24px));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)}
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)}
77
144
  .graview-bar-list[hidden]{display:none}
78
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)}
79
146
  .graview-bar-list ul,.graview-bar-list li{display:grid;margin:0;padding:0;list-style:none}
@@ -82,23 +149,29 @@ const BAR_CSS = `
82
149
  .graview-bar-item[aria-current]{font-weight:600;background:color-mix(in srgb,var(--graview-accent) 14%,transparent)}
83
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)}
84
151
  .graview-bar :focus-visible{outline:2px solid var(--graview-accent);outline-offset:1px}
85
- .graview-bar-tools{display:flex;align-items:center;flex:0 0 auto;gap:8px;margin-left:auto}
86
- .graview-bar-find{display:flex;width:15rem;min-width:7rem;flex:0 1 auto}
87
- .graview-bar-find-open{display:none}
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-own{display:flex;align-items:center;flex:0 0 auto}
156
+ .graview-bar-own:empty{display:none}
157
+ .graview-bar-find{position:relative;display:flex;min-width:7.5rem;flex:0 600 9rem;--graview-bar-find-end:3.25em}
158
+ .graview-bar-find-slot{display:flex;flex:1;min-width:0}
159
+ .graview-bar-find-keys{position:absolute;right:10px;line-height:30px;pointer-events:none;font-size:.75rem;color:var(--graview-ink-faint)}
160
+ .graview-bar-find:is(:focus-within,:has(input:not(:placeholder-shown))){flex-basis:16rem;--graview-bar-find-end:10px}
161
+ :is(:focus-within,:has(input:not(:placeholder-shown)))>.graview-bar-find-keys,.graview-bar-find-open{display:none}
88
162
  .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)}
89
163
  .graview-bar-line .graview-bar-place{height:auto;min-height:32px}
90
164
  .graview-bar-line .graview-bar-place-words{white-space:normal}
91
- @container (max-width: 639px){
165
+ @media (pointer:coarse){.graview-bar-find-keys{display:none}}
166
+ @container graview-bar (max-width: ${BAR_PHONE - 1}px){
92
167
  .graview-bar-row{gap:10px;padding:0 12px}
93
168
  .graview-bar-app{max-width:none;flex:1 1 auto}
94
- .graview-bar-app-name{display:-webkit-box;-webkit-box-orient:vertical;-webkit-line-clamp:2;overflow:hidden}
95
- .graview-bar-face{padding:0 8px}
96
- .graview-bar-face span{position:absolute;width:1px;height:1px;overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap}
97
169
  .graview-bar-mid,.graview-bar-find{display:none}
98
170
  .graview-bar-find-open{display:inline-flex}
171
+ .graview-bar-own{--graview-activity-mark:inline}
172
+ .graview-bar-own .graview-activity-word{position:absolute;width:1px;height:1px;overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap}
99
173
  .graview-bar[data-finding] :is(.graview-bar-app,.graview-bar-faces,.graview-bar-find-open){display:none}
100
- .graview-bar[data-finding] .graview-bar-tools{flex:1 1 auto}
101
- .graview-bar[data-finding] .graview-bar-find{display:flex;flex:1 1 auto;width:auto;min-width:0}
174
+ .graview-bar[data-finding] .graview-bar-find{display:flex;flex:1 1 auto;min-width:0}
102
175
  }`;
103
176
  /** A tool's own box: one size, square, its name in words for whoever cannot see the mark. */
104
177
  export const toolStyle = {
@@ -127,28 +200,67 @@ export const toolStyle = {
127
200
  * person). One row of one height on a desk and a phone; on a phone the
128
201
  * place is the page's first line, under the bar.
129
202
  */
130
- export function AppBar({ brand, name, heading = 1, home, faces, places, current, reach, tools, find = true, description, findBox, onFind, }) {
203
+ export function AppBar({ brand, name, heading = 1, home, faces, places, current, reach, tools, find = true, description, findBox, onFind, switch: switchForm = "words", scenePlaces, }) {
131
204
  const bar = useRef(null);
132
205
  const [slot, setSlot] = useState(null);
206
+ const [own, setOwn] = useState(null);
133
207
  const [compact, setCompact] = useState(false);
134
208
  const [finding, setFinding] = useState(false);
209
+ // Whether the switch's words fit beside everything else the row holds (`roomForWords`).
210
+ const [roomy, setRoomy] = useState(true);
211
+ // Which places stand on the row, by their place in the list ("0,1,2"); null, the one control (FR-145).
212
+ const [standing, setStanding] = useState(null);
213
+ // The places on the face drawn: the routed face's, or the scene's (FR-144) — a scene with no pages to switch to has its places too.
214
+ const onPages = faces ? faces.pages.current : !scenePlaces;
215
+ const shown = onPages ? { places, current, reach } : scenePlaces;
216
+ const list = shown && shown.places.length > 0 ? shown : null;
217
+ const currentAt = list ? list.places.findIndex((place) => place.key === list.current) : -1;
218
+ const wordsAsked = Boolean(faces) && switchForm === "words";
219
+ const weigh = useCallback(() => {
220
+ const element = bar.current;
221
+ if (!element)
222
+ return;
223
+ const stand = standsIn(element, currentAt, wordsAsked);
224
+ if (stand !== undefined)
225
+ setStanding(stand);
226
+ // Standing places were weighed with the switch's words kept.
227
+ const room = stand ? true : roomForWords(element);
228
+ if (room !== null)
229
+ setRoomy(room);
230
+ }, [currentAt, wordsAsked]);
135
231
  useLayoutEffect(() => {
136
232
  const element = bar.current;
137
233
  if (!element)
138
234
  return;
139
- const read = () => setCompact(element.getBoundingClientRect().width < 640);
235
+ const read = () => {
236
+ const width = element.getBoundingClientRect().width;
237
+ setCompact(width < BAR_PHONE);
238
+ // What hangs from the bar as wide as it (a phone's Find sheet) is told the bar's width, not the screen's.
239
+ element.style.setProperty("--graview-bar-width", `${Math.round(width)}px`);
240
+ weigh();
241
+ };
140
242
  read();
243
+ // The brand's face, once it has come, is wider or narrower than the one the row was first weighed in.
244
+ const fonts = typeof document === "undefined" ? undefined : document.fonts;
245
+ void fonts?.ready.then(weigh);
246
+ fonts?.addEventListener?.("loadingdone", weigh);
141
247
  if (typeof ResizeObserver === "undefined")
142
- return;
248
+ return () => fonts?.removeEventListener?.("loadingdone", weigh);
143
249
  const watch = new ResizeObserver(read);
144
250
  watch.observe(element);
145
- return () => watch.disconnect();
146
- }, []);
251
+ // What stands beside the places — the tools, a count on the standing — changes their room without changing the bar's width.
252
+ for (const tool of element.querySelectorAll(".graview-bar-tools > *"))
253
+ watch.observe(tool);
254
+ return () => {
255
+ watch.disconnect();
256
+ fonts?.removeEventListener?.("loadingdone", weigh);
257
+ };
258
+ }, [weigh]);
147
259
  const told = useRef(onFind);
148
260
  told.current = onFind;
149
261
  useLayoutEffect(() => {
150
- told.current?.(find && slot ? { slot, compact } : null);
151
- }, [find, slot, compact]);
262
+ told.current?.(find && slot ? { slot, compact, own } : null);
263
+ }, [find, slot, compact, own]);
152
264
  useLayoutEffect(() => () => told.current?.(null), []);
153
265
  const openFind = useCallback(() => {
154
266
  setFinding(true);
@@ -175,10 +287,16 @@ export function AppBar({ brand, name, heading = 1, home, faces, places, current,
175
287
  event.preventDefault();
176
288
  go();
177
289
  };
178
- // The place control stands on Pages: in the bar on a desk, as the page's first line on a phone.
179
- const onPages = !faces || faces.pages.current;
180
- const placeControl = onPages && places.length > 0 ? _jsx(PlaceControl, { places: places, current: current, reach: reach }) : null;
181
- 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 }) : null, _jsx("div", { className: "graview-bar-mid", children: compact ? null : placeControl }), _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, {}) }), _jsx("div", { ref: setSlot, className: "graview-bar-find", "data-testid": "app-find", onBlur: (event) => {
290
+ // 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).
291
+ const scene = !onPages;
292
+ const stands = list && !compact && standing !== null ? standing.split(",").map(Number).filter((at) => at < list.places.length) : null;
293
+ const placeControl = list ? _jsx(Places, { places: list.places, current: list.current, reach: list.reach, scene: scene, stands: stands }) : null;
294
+ const said = list ? list.places.map((place) => place.label).join("\n") : "";
295
+ // 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.
296
+ useLayoutEffect(weigh, [weigh, name, brand, said, list?.current, onPages, compact, switchForm, find]);
297
+ // The marks alone on a phone's bar, when asked, or when the words do not fit.
298
+ const drawn = switchForm === "icons" || !roomy || compact ? "icons" : "words";
299
+ 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) => {
182
300
  // Put away on a phone when the keyboard leaves it with nothing typed.
183
301
  const next = event.relatedTarget;
184
302
  if (next instanceof Node && event.currentTarget.contains(next))
@@ -196,7 +314,7 @@ export function AppBar({ brand, name, heading = 1, home, faces, places, current,
196
314
  putAway();
197
315
  else
198
316
  escaping.current = false;
199
- }, children: findBox })] })) : null, tools] })] })] }), compact && placeControl ? (_jsx("div", { className: "graview-bar-line", "data-graview-place-line": "", "data-testid": "app-place-line", children: placeControl })) : null] }));
317
+ }, 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, _jsx("div", { ref: setOwn, className: "graview-bar-own", "data-graview-bar-own": "" }), tools] })] })] }), compact && placeControl ? (_jsx("div", { className: "graview-bar-line", "data-graview-place-line": "", "data-testid": "app-place-line", children: placeControl })) : null] }));
200
318
  }
201
319
  /**
202
320
  * THE SWITCH (FR-137): the scene and the pages, side by side, an icon and a
@@ -204,7 +322,7 @@ export function AppBar({ brand, name, heading = 1, home, faces, places, current,
204
322
  * drawn pressed. Choosing the scene draws it under the bar; choosing the
205
323
  * pages goes back to the page you were on.
206
324
  */
207
- function FaceSwitch({ faces }) {
325
+ function FaceSwitch({ faces, form, drawn }) {
208
326
  const face = (which) => {
209
327
  const one = faces[which];
210
328
  return (_jsxs("button", { type: "button", className: "graview-bar-face", "data-testid": `app-face-${which}`, "aria-pressed": one.current, title: one.label, onClick: () => {
@@ -212,7 +330,50 @@ function FaceSwitch({ faces }) {
212
330
  one.go();
213
331
  }, children: [which === "scene" ? _jsx(SceneMark, {}) : _jsx(PagesMark, {}), _jsx("span", { children: one.label })] }));
214
332
  };
215
- return (_jsxs("div", { className: "graview-bar-faces", role: "group", "aria-label": `${faces.scene.label} or ${faces.pages.label}`, "data-testid": "app-faces", children: [face("scene"), face("pages")] }));
333
+ 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")] }));
334
+ }
335
+ /**
336
+ * WHETHER THE SWITCH'S WORDS FIT (the switch left to say its words where
337
+ * there is room). The row is weighed as it is drawn: the room the middle
338
+ * has past what the place wants (its words, up to 14em; none on the
339
+ * scene), less what Find has given up; none when the app's name has
340
+ * wrapped. The words fit when that room holds them — the words
341
+ * drawn already, or their width to come when the marks are drawn alone —
342
+ * so the switch never flips back and forth at one width. Not weighed while
343
+ * Find is in use (it is drawn wide then), on a phone's bar (the marks
344
+ * alone), or where nothing is laid out (null).
345
+ */
346
+ function roomForWords(header) {
347
+ const faces = header.querySelector(".graview-bar-faces");
348
+ const mid = header.querySelector(".graview-bar-mid");
349
+ const find = header.querySelector(".graview-bar-find");
350
+ if (!faces || !mid || find?.matches(":focus-within"))
351
+ return null;
352
+ const row = header.getBoundingClientRect().width;
353
+ if (row < BAR_PHONE)
354
+ return null;
355
+ // The words to come, beside each mark: the word, the gap before it, and the button's wider padding.
356
+ const words = wordsOf(faces);
357
+ const place = mid.querySelector(".graview-bar-place-words");
358
+ const em = place ? Number.parseFloat(getComputedStyle(place).fontSize) || 14 : 0;
359
+ // The place's words, its chevron, its gap, padding and frame: up to 14em of them.
360
+ const wanted = place ? Math.min(place.scrollWidth + 34, 14 * em) : 0;
361
+ let spare = mid.getBoundingClientRect().width - wanted;
362
+ if (find && find.getBoundingClientRect().width > 0) {
363
+ const basis = Number.parseFloat(getComputedStyle(find).flexBasis);
364
+ if (Number.isFinite(basis))
365
+ spare -= Math.max(0, basis - find.getBoundingClientRect().width);
366
+ }
367
+ // The app's name on two lines has given its room already: no room for the words.
368
+ const name = header.querySelector(".graview-bar-app-name");
369
+ if (name && name.getBoundingClientRect().height > Number.parseFloat(getComputedStyle(name).lineHeight) * 1.5)
370
+ return false;
371
+ return faces.getAttribute("data-switch-drawn") === "icons" ? spare >= words + 2 : spare >= -1;
372
+ }
373
+ /** Find's shortcut as the keyboard says it: ⌘K on a Mac (and an iPad's keyboard), Ctrl K elsewhere. */
374
+ function findKeys() {
375
+ const platform = typeof navigator === "undefined" ? "" : (navigator.platform ?? "");
376
+ return /Mac|iPhone|iPad|iPod/.test(platform) ? "⌘K" : "Ctrl K";
216
377
  }
217
378
  /** The scene's mark: a plot of the city, seen from above at an angle. */
218
379
  function SceneMark() {
@@ -232,6 +393,9 @@ function FindMark() {
232
393
  }
233
394
  /** A place's mark in the list: the home's house, a kind's plot in its own hue. */
234
395
  function PlaceMark({ place }) {
396
+ // The scene's whole thing: the scene's own mark, as on the switch (FR-144).
397
+ if (place.key === WHOLE_KEY)
398
+ return _jsx(SceneMark, {});
235
399
  if (place.group === "home") {
236
400
  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" }) }));
237
401
  }
@@ -246,44 +410,95 @@ function PlaceMark({ place }) {
246
410
  } }));
247
411
  }
248
412
  const GROUPS = [{ group: "home" }, { group: "lists", heading: "Lists" }, { group: "pictures", heading: "Pictures" }];
413
+ /** The word the places that do not stand on the row fold under (FR-145). */
414
+ const MORE = "More";
415
+ /**
416
+ * ONE PLACE, AS THE BAR OFFERS IT — in the list, or standing on the row: a
417
+ * link where it has an address, a press where the bar decides; marked
418
+ * `aria-current` where the reader is. Whichever way it is drawn it is
419
+ * `app-place-<key>` and says its path (`data-place-path`), once in the bar.
420
+ */
421
+ function PlaceEntry({ place, current, reach, standing, done }) {
422
+ const at = place.key === current;
423
+ const href = reach.href?.(place.path);
424
+ const Entry = (href !== undefined ? "a" : "button");
425
+ 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) => {
426
+ done();
427
+ if (!reach.go)
428
+ return;
429
+ if (href !== undefined && (event.button !== 0 || event.metaKey || event.ctrlKey || event.shiftKey || event.altKey))
430
+ return;
431
+ event.preventDefault();
432
+ reach.go(place);
433
+ }, 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 })] }));
434
+ }
435
+ /** The places of a list, grouped — the home (or the whole thing), the Lists, the Pictures — each group with its heading. */
436
+ function PlaceGroups({ places, current, reach, id, done }) {
437
+ return (_jsx(_Fragment, { children: GROUPS.map(({ group, heading }) => {
438
+ const listed = places.filter((place) => place.group === group);
439
+ if (listed.length === 0)
440
+ return null;
441
+ const named = `${id}-${group}`;
442
+ 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));
443
+ }) }));
444
+ }
249
445
  /**
250
446
  * THE PLACE YOU ARE ON, AND EVERY OTHER (FR-138): one control, the place's
251
447
  * name and a chevron, that opens the app's places — the home first, then
252
448
  * the Lists and the Pictures, each with its mark, a long name wrapped
253
449
  * rather than cut. Every place is two presses away, and the bar is one row
254
- * of one height however many there are.
450
+ * of one height however many there are. On the scene, what is in view and
451
+ * every picture it can show (FR-144).
452
+ *
453
+ * WHERE THEY FIT, THE PLACES THEMSELVES (FR-145): the ones that stand, as
454
+ * words in their order, the one you are on underlined; then "More", which
455
+ * opens the rest, grouped as the one control's list is — no "More" when
456
+ * every place stands. "More" is `app-places-open` and its list
457
+ * `app-places`, as the one control and its list are, so a place that does
458
+ * not stand is reached by the same two presses either way.
255
459
  */
256
- function PlaceControl({ places, current, reach }) {
460
+ function Places({ places, current, reach, scene, stands }) {
257
461
  const popover = usePopover("places");
258
- const here = places.find((place) => place.key === current);
259
- const said = here?.label ?? "Places";
260
- const entry = (place) => {
261
- const at = place.key === current;
262
- const href = reach.href?.(place.path);
263
- const common = {
264
- className: "graview-bar-item",
265
- "data-testid": `app-place-${place.key}`,
266
- "data-place-path": place.path,
267
- ...(at ? { "aria-current": "page" } : {}),
268
- onClick: (event) => {
269
- popover.setOpen(false);
270
- if (!reach.go)
271
- return;
272
- if (href !== undefined && (event.button !== 0 || event.metaKey || event.ctrlKey || event.shiftKey || event.altKey))
273
- return;
274
- event.preventDefault();
275
- reach.go(place);
276
- },
277
- };
278
- const Entry = (href !== undefined ? "a" : "button");
279
- return (_jsxs(Entry, { ...common, ...(href !== undefined ? { href } : { type: "button" }), children: [_jsx("span", { className: "graview-bar-mark", children: _jsx(PlaceMark, { place: place }) }), _jsx("span", { children: place.label })] }, place.key));
280
- };
281
- return (_jsxs("span", { className: "graview-bar-place-at", children: [_jsxs("button", { type: "button", className: "graview-bar-place", "data-testid": "app-places-open", ...popover.trigger, onClick: popover.toggle, "aria-label": `${said} — every place`, title: said, children: [_jsx("span", { className: "graview-bar-place-words", "data-testid": "app-place-current", children: said }), _jsx(Chevron, {})] }), _jsx("nav", { ...popover.pane, "data-testid": "app-places", "aria-label": "The app\u2019s places", hidden: !popover.open, className: "graview-bar-list", style: POPOVER_STYLE, children: GROUPS.map(({ group, heading }) => {
282
- const listed = places.filter((place) => place.group === group);
283
- if (listed.length === 0)
284
- return null;
285
- const id = `${popover.pane.id}-${group}`;
286
- return (_jsxs("div", { role: "group", ...(heading ? { "aria-labelledby": id } : { "aria-label": "Home" }), "data-place-group": group, children: [heading ? (_jsx("p", { id: id, children: heading })) : null, _jsx("ul", { children: listed.map((place) => (_jsx("li", { children: entry(place) }, place.key))) })] }, group));
287
- }) })] }));
462
+ const said = places.find((place) => place.key === current)?.label ?? "Places";
463
+ const rest = stands ? places.filter((_, at) => !stands.includes(at)) : places;
464
+ 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] }));
465
+ }
466
+ /**
467
+ * WHICH PLACES STAND ON THE ROW, WEIGHED FROM THE ROW AS DRAWN (FR-145):
468
+ * the places' widths read off the ruler (each at its weight as the place
469
+ * you are on, so marking one never moves the others), and the room from
470
+ * where the places begin to where Find ends, less Find at its least, less
471
+ * the switch's words when they are asked for. The same answer whichever way
472
+ * the places are drawn, so the row never flips between the two at one
473
+ * width. Not weighed while Find is in use (`undefined`); none on a phone's
474
+ * bar, when the app's name has wrapped, or where nothing is laid out (null).
475
+ */
476
+ function standsIn(header, current, wordsAsked) {
477
+ const ruler = header.querySelector(".graview-bar-ruler");
478
+ const mid = header.querySelector(".graview-bar-mid");
479
+ if (!ruler || !mid)
480
+ return null;
481
+ const find = header.querySelector(".graview-bar-find");
482
+ if (find?.matches(":focus-within"))
483
+ return undefined;
484
+ if (header.getBoundingClientRect().width < BAR_PHONE)
485
+ return null;
486
+ const name = header.querySelector(".graview-bar-app-name");
487
+ if (name && name.getBoundingClientRect().height > Number.parseFloat(getComputedStyle(name).lineHeight) * 1.5)
488
+ return null;
489
+ const widths = [...ruler.children].map((one) => one.getBoundingClientRect().width);
490
+ const more = widths.pop() ?? 0;
491
+ const faces = header.querySelector(".graview-bar-faces");
492
+ const words = faces && wordsAsked ? wordsOf(faces) : 0;
493
+ const drawnWords = faces?.getAttribute("data-switch-drawn") === "words" ? words : 0;
494
+ const from = mid.getBoundingClientRect().left;
495
+ const end = find?.getBoundingClientRect();
496
+ // 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.
497
+ const room = end && end.width > 0 ? end.right - from - (Number.parseFloat(getComputedStyle(find).minWidth) || 0) - 8 : mid.getBoundingClientRect().width;
498
+ return placesThatStand({ widths, current, room: Math.floor(room + drawnWords - words), more })?.join(",") ?? null;
499
+ }
500
+ /** The switch's words, beside each mark: the word, the gap before it, and the button's wider padding. */
501
+ function wordsOf(faces) {
502
+ return [...faces.querySelectorAll(".graview-bar-face span")].reduce((sum, span) => sum + span.scrollWidth + 10, 0);
288
503
  }
289
504
  //# sourceMappingURL=app-bar.js.map