@graview/primitives 0.1.65 → 0.1.67

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 (136) hide show
  1. package/dist/app-bar.d.ts +114 -15
  2. package/dist/app-bar.d.ts.map +1 -1
  3. package/dist/app-bar.js +192 -130
  4. package/dist/app-bar.js.map +1 -1
  5. package/dist/app-menu.d.ts +9 -0
  6. package/dist/app-menu.d.ts.map +1 -0
  7. package/dist/app-menu.js +60 -0
  8. package/dist/app-menu.js.map +1 -0
  9. package/dist/bar-activity.d.ts +5 -9
  10. package/dist/bar-activity.d.ts.map +1 -1
  11. package/dist/bar-activity.js +24 -7
  12. package/dist/bar-activity.js.map +1 -1
  13. package/dist/bar-panes.d.ts +5 -5
  14. package/dist/bar-panes.d.ts.map +1 -1
  15. package/dist/bar-panes.js +9 -5
  16. package/dist/bar-panes.js.map +1 -1
  17. package/dist/bar-suggested.d.ts +9 -1
  18. package/dist/bar-suggested.d.ts.map +1 -1
  19. package/dist/bar-suggested.js +11 -17
  20. package/dist/bar-suggested.js.map +1 -1
  21. package/dist/better-bar.d.ts +77 -0
  22. package/dist/better-bar.d.ts.map +1 -0
  23. package/dist/better-bar.js +312 -0
  24. package/dist/better-bar.js.map +1 -0
  25. package/dist/better-panel.d.ts +55 -0
  26. package/dist/better-panel.d.ts.map +1 -0
  27. package/dist/better-panel.js +151 -0
  28. package/dist/better-panel.js.map +1 -0
  29. package/dist/editable.d.ts +8 -1
  30. package/dist/editable.d.ts.map +1 -1
  31. package/dist/editable.js +140 -16
  32. package/dist/editable.js.map +1 -1
  33. package/dist/first-look-door.d.ts +2 -2
  34. package/dist/first-look-door.js +2 -2
  35. package/dist/first-look.d.ts.map +1 -1
  36. package/dist/first-look.js +9 -7
  37. package/dist/first-look.js.map +1 -1
  38. package/dist/frame.d.ts +2 -2
  39. package/dist/frame.d.ts.map +1 -1
  40. package/dist/frame.js +1 -1
  41. package/dist/frame.js.map +1 -1
  42. package/dist/inbox-pane.d.ts +31 -0
  43. package/dist/inbox-pane.d.ts.map +1 -0
  44. package/dist/inbox-pane.js +89 -0
  45. package/dist/inbox-pane.js.map +1 -0
  46. package/dist/inbox.d.ts +17 -0
  47. package/dist/inbox.d.ts.map +1 -0
  48. package/dist/inbox.js +84 -0
  49. package/dist/inbox.js.map +1 -0
  50. package/dist/index.d.ts +4 -3
  51. package/dist/index.d.ts.map +1 -1
  52. package/dist/index.js +3 -2
  53. package/dist/index.js.map +1 -1
  54. package/dist/lens/calendar-drawing.js +7 -3
  55. package/dist/lens/calendar-drawing.js.map +1 -1
  56. package/dist/menu-suggested.d.ts +54 -0
  57. package/dist/menu-suggested.d.ts.map +1 -0
  58. package/dist/menu-suggested.js +140 -0
  59. package/dist/menu-suggested.js.map +1 -0
  60. package/dist/menu-told-door.js +1 -1
  61. package/dist/menu-told-door.js.map +1 -1
  62. package/dist/menu-told.d.ts +7 -0
  63. package/dist/menu-told.d.ts.map +1 -1
  64. package/dist/menu-told.js +24 -0
  65. package/dist/menu-told.js.map +1 -1
  66. package/dist/notify-sheet.js +2 -2
  67. package/dist/notify-sheet.js.map +1 -1
  68. package/dist/page-better.d.ts +27 -0
  69. package/dist/page-better.d.ts.map +1 -0
  70. package/dist/page-better.js +747 -0
  71. package/dist/page-better.js.map +1 -0
  72. package/dist/page-parts.d.ts +41 -0
  73. package/dist/page-parts.d.ts.map +1 -0
  74. package/dist/page-parts.js +101 -0
  75. package/dist/page-parts.js.map +1 -0
  76. package/dist/pages.d.ts +2 -3
  77. package/dist/pages.d.ts.map +1 -1
  78. package/dist/pages.js +2 -3
  79. package/dist/pages.js.map +1 -1
  80. package/dist/profile.d.ts +1 -10
  81. package/dist/profile.d.ts.map +1 -1
  82. package/dist/profile.js +2 -3
  83. package/dist/profile.js.map +1 -1
  84. package/dist/scene-bar.d.ts +1 -1
  85. package/dist/scene-bar.d.ts.map +1 -1
  86. package/dist/scene-bar.js +4 -7
  87. package/dist/scene-bar.js.map +1 -1
  88. package/dist/section-better.d.ts +66 -9
  89. package/dist/section-better.d.ts.map +1 -1
  90. package/dist/section-better.js +285 -136
  91. package/dist/section-better.js.map +1 -1
  92. package/dist/section-door.d.ts +7 -0
  93. package/dist/section-door.d.ts.map +1 -1
  94. package/dist/section-door.js +39 -5
  95. package/dist/section-door.js.map +1 -1
  96. package/dist/section-menu.d.ts +23 -9
  97. package/dist/section-menu.d.ts.map +1 -1
  98. package/dist/section-menu.js +176 -23
  99. package/dist/section-menu.js.map +1 -1
  100. package/dist/section-words.d.ts +8 -0
  101. package/dist/section-words.d.ts.map +1 -1
  102. package/dist/section-words.js +25 -0
  103. package/dist/section-words.js.map +1 -1
  104. package/dist/shell.d.ts +7 -1
  105. package/dist/shell.d.ts.map +1 -1
  106. package/dist/shell.js +4 -2
  107. package/dist/shell.js.map +1 -1
  108. package/dist/suggested.d.ts +21 -10
  109. package/dist/suggested.d.ts.map +1 -1
  110. package/dist/suggested.js +80 -66
  111. package/dist/suggested.js.map +1 -1
  112. package/dist/suggestion-sentence.d.ts +50 -0
  113. package/dist/suggestion-sentence.d.ts.map +1 -0
  114. package/dist/suggestion-sentence.js +119 -0
  115. package/dist/suggestion-sentence.js.map +1 -0
  116. package/dist/told-card.js +1 -1
  117. package/dist/told-card.js.map +1 -1
  118. package/dist/told-ui.d.ts +1 -1
  119. package/dist/told-ui.d.ts.map +1 -1
  120. package/dist/told-ui.js +1 -1
  121. package/dist/told-ui.js.map +1 -1
  122. package/dist/when-words.d.ts +21 -0
  123. package/dist/when-words.d.ts.map +1 -0
  124. package/dist/when-words.js +43 -0
  125. package/dist/when-words.js.map +1 -0
  126. package/dist/workbench/activity.d.ts +44 -0
  127. package/dist/workbench/activity.d.ts.map +1 -1
  128. package/dist/workbench/activity.js +74 -59
  129. package/dist/workbench/activity.js.map +1 -1
  130. package/dist/workbench/inspector.d.ts.map +1 -1
  131. package/dist/workbench/inspector.js +170 -31
  132. package/dist/workbench/inspector.js.map +1 -1
  133. package/dist/workbench/standing.d.ts.map +1 -1
  134. package/dist/workbench/standing.js +1 -1
  135. package/dist/workbench/standing.js.map +1 -1
  136. package/package.json +6 -6
package/dist/app-bar.js CHANGED
@@ -2,12 +2,14 @@ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-run
2
2
  import { allLabel, arrangedEntries, hueFor, isInlineSvg, kindPath, MOST_STANDING, offeredPlaces, onePerPicture, OVERVIEW_SLUG, placeLabel, placePath, pluralLabel, primaryOf, samePicture, sharesItsName, supportingKinds } from "@graview/core";
3
3
  import { POPOVER_STYLE, useGraviewIfAny, usePopover } from "@graview/react/provider";
4
4
  import { useQuickAskDrawn } from "./quick-ask-desk.js";
5
- import { createContext, Suspense, useCallback, useContext, useEffect, useLayoutEffect, useRef, useState } from "react";
5
+ import { createContext, Suspense, useCallback, useContext, useEffect, useId, useLayoutEffect, useRef, useState } from "react";
6
6
  import { AppMark } from "./app-title.js";
7
7
  import { useMarkup } from "./markup.js";
8
8
  import { barPanes } from "./bar-panes-door.js";
9
9
  /* The places' foot, Hidden and Recently deleted (Roadmap f-place-menu-views): fetched with the panes, and the tidying with it, once there is something to list and the list is open. */
10
10
  const TidyFoot = barPanes.part((panes, props) => _jsx(panes.TidyFoot, { ...props }), { quiet: true });
11
+ /* The app menu's rows (Roadmap f-app-bar-calm): fetched with the panes when it is first reached for. */
12
+ const AppMenuRows = barPanes.part((panes, props) => _jsx(panes.AppMenuPane, { ...props }), { what: "The app menu" });
11
13
  /* Who else is here (FR-155): drawn once somebody is, so a reader alone never fetches it. */
12
14
  const Here = barPanes.part((panes, props) => _jsx(panes.Here, { ...props }), { quiet: true });
13
15
  /**
@@ -134,8 +136,10 @@ export const useBarFind = () => useContext(BarFindContext);
134
136
  export const TOOL = 30;
135
137
  /** The bar's one row, its rule under it included, on a desk and a phone alike (FR-138). */
136
138
  export const BAR_HEIGHT = 48;
137
- /** 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. */
139
+ /** Below this width of its own the bar is a phone's: the place is the page's first line. */
138
140
  export const BAR_PHONE = 640;
141
+ /** Below this width of its own Find is the magnifier, which opens the box over the row (Roadmap f-app-bar-calm): sooner than a phone's. */
142
+ export const BAR_FIND_FOLDS = 900;
139
143
  /** The room between two places standing on the row (FR-145). */
140
144
  export const PLACE_GAP = 4;
141
145
  /** The fewest places that stand on the row: fewer, and the one control says the same in less room. */
@@ -180,17 +184,15 @@ export function placesThatStand(input) {
180
184
  * short, Find gives first (a small box that says its shortcut, drawn wide
181
185
  * while it is used), then the place down to 9em of its name (whole in its
182
186
  * title), then the app's name, which wraps between its words onto a second
183
- * line and never inside one; the switch keeps its size, and draws its marks
184
- * alone when its words do not fit (`roomForWords`). Whether the places stand
185
- * and the words fit is weighed with the name as it would stand on one line
187
+ * line and never inside one; the app menu's chevron keeps its size. Whether
188
+ * the places stand is weighed with the name as it would stand on one line
186
189
  * (`nameGiven`), never as it is drawn now: the name drawn on two lines because
187
190
  * the places stood would fold them, and the name back on one would stand
188
191
  * them again, frame after frame.
189
192
  *
190
193
  * Where the places stand on the row (FR-145), Find gives first — down to
191
194
  * its least — then the places fold into "More", then into the one control,
192
- * which gives as above. The places are weighed with the switch's words
193
- * kept: the words are worth more than a third place on the row.
195
+ * which gives as above.
194
196
  *
195
197
  * The order is the shrink factors: Find 600, the place 100, the app 1. A
196
198
  * factor under 1 would not do — the free space is scaled by the factors
@@ -204,23 +206,34 @@ export function placesThatStand(input) {
204
206
  const BAR_CSS = `
205
207
  .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)}
206
208
  .graview-bar-row{display:flex;flex-wrap:nowrap;align-items:center;gap:16px;height:${BAR_HEIGHT - 1}px;margin:0;padding:0 16px}
207
- .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}
208
- .graview-bar-app{display:flex;flex:0 1 auto;max-width:50%}
209
+ .graview-bar-home,.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}
210
+ .graview-bar-app{display:flex;align-items:center;gap:2px;flex:0 1 auto;max-width:50%}
209
211
  .graview-bar-app::after{content:"";flex:0 100000 auto;width:2px;min-width:0}
210
212
  .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}
211
213
  .graview-bar-home{gap:8px;min-width:auto;min-height:30px;padding:0;letter-spacing:-.005em}
212
214
  .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}
213
- .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}
214
- .graview-bar-face{gap:6px;padding:0 10px;border-radius:6px;font-size:.8125rem;white-space:nowrap;color:var(--graview-ink-muted)}
215
- .graview-bar-face span{min-width:0;max-width:11em;overflow:hidden;text-overflow:ellipsis}
216
- .graview-bar-face[aria-pressed=true]{color:var(--graview-ink);font-weight:600;background:color-mix(in srgb,var(--graview-accent) 16%,transparent)}
217
- [data-switch-drawn=icons] .graview-bar-face{padding:0 8px}
218
- .graview-bar-said,[data-switch-drawn=icons] .graview-bar-face span{position:absolute;width:1px;height:1px;overflow:hidden;clip:rect(0 0 0 0)}
215
+ .graview-bar-said{position:absolute;width:1px;height:1px;overflow:hidden;clip:rect(0 0 0 0)}
216
+ .graview-bar-menu-open{display:inline-flex;align-items:center;justify-content:center;flex:0 0 auto;box-sizing:border-box;width:24px;height:30px;margin:0;padding:0;border:1px solid transparent;border-radius:6px;background:none;box-shadow:none;color:var(--graview-ink-muted);cursor:pointer}
217
+ .graview-bar-menu-open:is(:hover,[aria-expanded=true]){border-color:var(--graview-edge);color:var(--graview-ink)}
218
+ .graview-bar-tool{position:relative;display:inline-flex;align-items:center;justify-content:center;gap:6px;flex:0 0 auto;box-sizing:border-box;min-width:${TOOL}px;height:${TOOL}px;margin:0;padding:0;border-radius:8px;border:1px solid transparent;background:none;box-shadow:none;font:inherit;font-size:.875rem;font-weight:500;letter-spacing:normal;text-transform:none;white-space:nowrap;color:var(--graview-ink-muted);cursor:pointer;text-decoration:none}
219
+ .graview-bar-tool:is(:hover,[aria-expanded=true]){border-color:var(--graview-edge);color:var(--graview-ink)}
220
+ .graview-bar-tool[data-running]{color:var(--graview-accent)}
221
+ .graview-bar-badge{position:absolute;top:-3px;right:-5px;box-sizing:border-box;min-width:16px;height:16px;padding:0 4px;border-radius:8px;background:var(--graview-accent);color:var(--graview-accent-ink);font-size:.625rem;font-weight:700;line-height:16px;text-align:center;font-variant-numeric:tabular-nums;box-shadow:0 0 0 2px var(--graview-bar);pointer-events:none}
222
+ .graview-bar-badge[data-dot]{min-width:8px;width:8px;height:8px;padding:0;top:2px;right:2px}
223
+ .graview-bar-badge[data-tone=warn]{background:var(--graview-warn)}
224
+ .graview-bar-share{gap:6px;padding:0 10px}
225
+ .graview-bar-share-pane{display:grid;gap:10px;width:min(300px,calc(100vw - 24px));padding:14px;border-radius:10px;border:1px solid var(--graview-edge);background:var(--graview-float);box-shadow:var(--graview-lift-high);color:var(--graview-ink);font-size:.875rem;line-height:1.45}
226
+ .graview-bar-share-pane[hidden]{display:none}
227
+ .graview-bar-share-pane p{margin:0}
228
+ .graview-bar-share-links{display:flex;flex-wrap:wrap;gap:4px 16px}
229
+ .graview-bar-share-pane a,.graview-bar-share-pane button{justify-self:start;display:inline-flex;align-items:center;min-height:28px;padding:0;border:0;background:none;box-shadow:none;font:inherit;font-weight:600;color:var(--graview-accent);text-decoration:none;cursor:pointer}
230
+ .graview-bar-status{gap:6px;padding:0 8px;cursor:default;font-weight:400}
231
+ a.graview-bar-status{cursor:pointer}
219
232
  .graview-bar-mid{display:flex;flex:1 100 0;min-width:0}
220
233
  .graview-bar-mid[data-place]{min-width:9em;flex-basis:auto}
221
234
  .graview-bar-place-at{position:relative;display:inline-flex;min-width:0;max-width:100%}
222
235
  .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}
223
- .graview-bar-face:hover,.graview-bar-place:hover{border-color:var(--graview-edge);color:var(--graview-ink)}
236
+ .graview-bar-place:hover{border-color:var(--graview-edge);color:var(--graview-ink)}
224
237
  .graview-bar-place-words{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
225
238
  .graview-bar-mid[data-places=standing]{flex:0 0 auto}
226
239
  .graview-bar-places{display:flex;align-items:center;gap:${PLACE_GAP}px;min-width:0;margin:0;padding:0}
@@ -247,10 +260,11 @@ const BAR_CSS = `
247
260
  .graview-bar-host{display:flex;align-items:center;gap:2px;flex:0 0 auto}
248
261
  .graview-bar-host-action{display:inline-flex;align-items:center;gap:6px;box-sizing:border-box;min-width:${TOOL}px;height:${TOOL}px;margin:0;padding:0 10px;border-radius:8px;border:1px solid transparent;background:none;box-shadow:none;font:inherit;font-size:.875rem;font-weight:500;letter-spacing:normal;text-transform:none;white-space:nowrap;color:var(--graview-ink-muted);cursor:pointer}
249
262
  .graview-bar-host-action:is(:hover,[aria-expanded=true]){border-color:var(--graview-edge);color:var(--graview-ink)}
250
- .graview-bar-host-action[data-folded]{padding:0}
263
+ .graview-bar-host-action:is([data-folded],[data-icon-only]){padding:0;justify-content:center}
251
264
  .graview-bar-host-mark{display:inline-flex;align-items:center;justify-content:center;flex:0 0 auto;width:16px;height:16px;font-size:.9375rem;line-height:1}
252
265
  .graview-bar-host-mark svg{width:16px;height:16px}
253
266
  .graview-bar-list .graview-bar-host-action{width:100%;justify-content:flex-start;white-space:normal;overflow-wrap:anywhere}
267
+ .graview-bar-host-action[data-icon-only]>.graview-bar-host-words{position:absolute;width:1px;height:1px;overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap}
254
268
  .graview-bar-find{position:relative;display:flex;min-width:7.5rem;flex:0 600 9rem;--graview-bar-find-end:3.25em}
255
269
  .graview-bar-find-slot{display:flex;flex:1;min-width:0}
256
270
  .graview-bar-find-keys{position:absolute;right:10px;line-height:30px;pointer-events:none;font-size:.75rem;color:var(--graview-ink-faint)}
@@ -260,16 +274,23 @@ const BAR_CSS = `
260
274
  .graview-bar-line .graview-bar-place{height:auto;min-height:32px}
261
275
  .graview-bar-line .graview-bar-place-words{white-space:normal}
262
276
  @media (pointer:coarse){.graview-bar-find-keys{display:none}}
277
+ @container graview-bar (max-width: ${BAR_FIND_FOLDS - 1}px){
278
+ .graview-bar-find{display:none}
279
+ .graview-bar-find-open{display:inline-flex}
280
+ .graview-bar-tools>.graview-bar-find-open{margin-left:auto}
281
+ .graview-bar[data-finding] :is(.graview-bar-app,.graview-bar-mid,.graview-bar-find-open){display:none}
282
+ .graview-bar[data-finding] .graview-bar-find{display:flex;flex:1 1 auto;min-width:0;margin-left:0}
283
+ }
263
284
  @container graview-bar (max-width: ${BAR_PHONE - 1}px){
264
285
  .graview-bar-row{gap:10px;padding:0 12px}
265
- .graview-bar-app,.graview-bar-home{max-width:none;flex:1 1 auto;min-width:28px}
286
+ .graview-bar-app{max-width:none;flex:1 1 auto;min-width:28px}
287
+ .graview-bar-home{min-width:28px}
266
288
  .graview-bar-name,.graview-bar-app-name{min-width:0}
267
- .graview-bar-mid,.graview-bar-find{display:none}
268
- .graview-bar-find-open{display:inline-flex}
269
- .graview-bar-own{--graview-activity-mark:inline}
270
- .graview-bar-own .graview-activity-word{position:absolute;width:1px;height:1px;overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap}
271
- .graview-bar[data-finding] :is(.graview-bar-app,.graview-bar-faces,.graview-bar-find-open){display:none}
272
- .graview-bar[data-finding] .graview-bar-find{display:flex;flex:1 1 auto;min-width:0}
289
+ .graview-bar-mid{display:none}
290
+ .graview-bar-share{padding:0}
291
+ .graview-bar-share>.graview-bar-share-words{position:absolute;width:1px;height:1px;overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap}
292
+ .graview-bar-status[data-icon]{padding:0}
293
+ .graview-bar-status[data-icon]>.graview-bar-status-words{position:absolute;width:1px;height:1px;overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap}
273
294
  }
274
295
  @container graview-bar (max-width: 300px){
275
296
  .graview-bar-row{gap:4px;padding:0 10px}
@@ -295,22 +316,23 @@ export const toolStyle = {
295
316
  };
296
317
  /**
297
318
  * THE BAR. The app at the left — its mark, its name as the page's heading,
298
- * the way home — then the switch between the scene and the pages (FR-137),
299
- * then, on Pages, the place you are on (FR-138), and the tools at the
300
- * right: the Find a face puts in it, then `tools` (the standing and the
301
- * person). One row of one height on a desk and a phone; on a phone the
319
+ * the way home — and the app menu beside it, which holds the scene and the
320
+ * pages (FR-137, Roadmap f-app-bar-calm), then, on Pages, the place you are
321
+ * on (FR-138), and the tools at the right: the Find a face puts in it, the
322
+ * inbox, a host's buttons, the people and a host's Share, then `tools` (the
323
+ * standing and the person). One row of one height on a desk and a phone; on a phone the
302
324
  * place is the page's first line, under the bar.
303
325
  */
304
- export function AppBar({ brand, name, heading = 1, home, faces, places, current, reach, tools, find = true, description, findBox, onFind, switch: switchForm = "words", scenePlaces, skips, named = true, actions, }) {
326
+ export function AppBar({ brand, name, heading = 1, home, faces, places, current, reach, tools, find = true, description, findBox, onFind, scenePlaces, skips, named = true, actions, appMenu = NO_ROWS, scheme, share, status, }) {
305
327
  const bar = useRef(null);
306
328
  // Anybody else here, by the provider's presence (FR-155); none where no provider is.
307
329
  const company = useGraviewIfAny()?.who.size;
308
330
  const [slot, setSlot] = useState(null);
309
331
  const [own, setOwn] = useState(null);
310
332
  const [compact, setCompact] = useState(false);
333
+ // Whether Find is the magnifier (below `BAR_FIND_FOLDS`), which opens the box over the row.
334
+ const [folded, setFolded] = useState(false);
311
335
  const [finding, setFinding] = useState(false);
312
- // Whether the switch's words fit beside everything else the row holds (`roomForWords`).
313
- const [roomy, setRoomy] = useState(true);
314
336
  // Which places stand on the row, by their place in the list ("0,1,2"); null, the one control (FR-145).
315
337
  const [standing, setStanding] = useState(null);
316
338
  // Whether the host's buttons stand on the row (FR-227): where the bar has their width over a phone's; folded where it does not.
@@ -327,21 +349,16 @@ export function AppBar({ brand, name, heading = 1, home, faces, places, current,
327
349
  const shown = from && { ...from, places: offeredPlaces(from.places), here: from.places.find((place) => place.key === from.current)?.label };
328
350
  const list = shown && shown.places.length > 0 ? shown : null;
329
351
  const currentAt = list ? list.places.findIndex((place) => place.key === list.current) : -1;
330
- const wordsAsked = Boolean(faces) && switchForm === "words";
331
352
  // Which places may stand, as one word for the weighing to depend on ("110010…").
332
353
  const ranks = list ? list.places.map((place) => (place.primary === false ? "0" : "1")).join("") : "";
333
354
  const weigh = useCallback(() => {
334
355
  const element = bar.current;
335
356
  if (!element)
336
357
  return;
337
- const stand = standsIn(element, currentAt, wordsAsked, ranks);
358
+ const stand = standsIn(element, currentAt, ranks);
338
359
  if (stand !== undefined)
339
360
  setStanding(stand);
340
- // Standing places were weighed with the switch's words kept.
341
- const room = stand ? true : roomForWords(element);
342
- if (room !== null)
343
- setRoomy(room);
344
- }, [currentAt, wordsAsked, ranks]);
361
+ }, [currentAt, ranks]);
345
362
  useLayoutEffect(() => {
346
363
  const element = bar.current;
347
364
  if (!element)
@@ -349,8 +366,10 @@ export function AppBar({ brand, name, heading = 1, home, faces, places, current,
349
366
  const read = () => {
350
367
  const width = element.getBoundingClientRect().width;
351
368
  setCompact(width < BAR_PHONE);
369
+ setFolded(width < BAR_FIND_FOLDS);
352
370
  // The host's buttons as they would stand, off their ruler: they stand where the bar has their width over a phone's.
353
371
  const host = element.querySelector("[data-graview-bar-host-ruler]");
372
+ // Two of the bar's tools drawn as marks alone stand at every width, as the bar's own do.
354
373
  setHostStands(width >= BAR_PHONE + (host?.getBoundingClientRect().width ?? 0));
355
374
  // What hangs from the bar as wide as it (a phone's Find sheet) is told the bar's width, not the screen's.
356
375
  element.style.setProperty("--graview-bar-width", `${Math.round(width)}px`);
@@ -384,8 +403,8 @@ export function AppBar({ brand, name, heading = 1, home, faces, places, current,
384
403
  const told = useRef(onFind);
385
404
  told.current = onFind;
386
405
  useLayoutEffect(() => {
387
- told.current?.(find && slot ? { slot, compact, own } : null);
388
- }, [find, slot, compact, own]);
406
+ told.current?.(find && slot ? { slot, compact: folded, own } : null);
407
+ }, [find, slot, folded, own]);
389
408
  useLayoutEffect(() => () => told.current?.(null), []);
390
409
  const openFind = useCallback(() => {
391
410
  setFinding(true);
@@ -404,6 +423,50 @@ export function AppBar({ brand, name, heading = 1, home, faces, places, current,
404
423
  escaping.current = false;
405
424
  requestAnimationFrame(() => opener.current?.focus());
406
425
  }, []);
426
+ /*
427
+ * HEARD ON THE ELEMENT ITSELF, not through React: the face's box is a
428
+ * portal from the face drawn under the bar, so its keys and its blur
429
+ * bubble through the face's tree and never reach a handler written here.
430
+ * Put away when folded (`BAR_FIND_FOLDS`) as the keyboard leaves it with
431
+ * nothing typed, or by Escape once its words are gone.
432
+ */
433
+ const findAt = useRef(null);
434
+ useEffect(() => {
435
+ const at = findAt.current;
436
+ if (!at || !folded)
437
+ return;
438
+ const typed = () => Boolean(at.querySelector("input")?.value);
439
+ const left = (event) => {
440
+ const next = event.relatedTarget;
441
+ if (next instanceof Node && at.contains(next))
442
+ return;
443
+ if (!typed())
444
+ putAway();
445
+ };
446
+ const marked = (event) => {
447
+ if (event.key === "Escape")
448
+ escaping.current = true;
449
+ };
450
+ const escaped = (event) => {
451
+ if (event.key !== "Escape")
452
+ return;
453
+ // The scene's Find takes Escape for its words first; an empty box is put away. Weighed once the face's own handler (React's, at its root, after this one) has had the key.
454
+ queueMicrotask(() => {
455
+ if (!event.defaultPrevented || !typed())
456
+ putAway();
457
+ else
458
+ escaping.current = false;
459
+ });
460
+ };
461
+ at.addEventListener("focusout", left);
462
+ at.addEventListener("keydown", marked, true);
463
+ at.addEventListener("keydown", escaped);
464
+ return () => {
465
+ at.removeEventListener("focusout", left);
466
+ at.removeEventListener("keydown", marked, true);
467
+ at.removeEventListener("keydown", escaped);
468
+ };
469
+ }, [folded, putAway]);
407
470
  const Heading = heading === false ? "span" : `h${heading}`;
408
471
  const Home = (home.href !== undefined ? "a" : "button");
409
472
  const press = (go) => (event) => {
@@ -423,83 +486,94 @@ export function AppBar({ brand, name, heading = 1, home, faces, places, current,
423
486
  const placeControl = list ? _jsx(Places, { places: list.places, current: list.current, here: list.here, reach: list.reach, scene: scene, stands: stands }) : null;
424
487
  const said = list ? list.places.map((place) => place.label).join("\n") : "";
425
488
  // 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.
426
- useLayoutEffect(weigh, [weigh, name, brand, said, list?.current, onPages, compact, switchForm, find]);
427
- // The marks alone on a phone's bar, when asked, or when the words do not fit.
428
- const drawn = switchForm === "icons" || !roomy || compact ? "icons" : "words";
429
- 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 }), skips, _jsxs("div", { className: "graview-bar-row", children: [named ? _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 })] }) }) }) : null, 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, actions && actions.length > 0 ? (_jsx("span", { className: "graview-bar-ruler", "aria-hidden": "true", "data-graview-bar-host-ruler": "", children: _jsx("span", { className: "graview-bar-host", children: actions.map((action) => (_jsxs("span", { className: "graview-bar-host-action", children: [action.icon ? _jsx(HostMark, { icon: action.icon }) : null, _jsx("span", { children: action.label })] }, action.label))) }) })) : null, named ? (_jsx("span", { className: "graview-bar-ruler", "aria-hidden": "true", children: _jsx("span", { className: "graview-bar-name", children: name }) })) : 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) => {
430
- // Put away on a phone when the keyboard leaves it with nothing typed.
431
- const next = event.relatedTarget;
432
- if (next instanceof Node && event.currentTarget.contains(next))
433
- return;
434
- if (!event.currentTarget.querySelector("input")?.value)
435
- putAway();
436
- }, onKeyDownCapture: (event) => {
437
- if (event.key === "Escape" && compact)
438
- escaping.current = true;
439
- }, onKeyDown: (event) => {
440
- if (event.key !== "Escape" || !compact)
441
- return;
442
- // The scene's Find takes Escape for its words first; an empty box is put away.
443
- if (!event.defaultPrevented || !event.currentTarget.querySelector("input")?.value)
444
- putAway();
445
- else
446
- escaping.current = false;
447
- }, children: [_jsx("div", { ref: setSlot, className: "graview-bar-find-slot", children: findBox }), _jsx(FindKeys, {})] })] })) : null, _jsx("div", { ref: setOwn, className: "graview-bar-own", "data-graview-bar-own": "" }), company ? (_jsx(Suspense, { fallback: null, children: _jsx(Here, { scene: !onPages, compact: compact }) })) : null, _jsx(TheirChanges, {}), actions && actions.length > 0 ? _jsx(HostButtons, { actions: actions, folded: compact || !hostStands }) : null, tools] })] })] }), compact && placeControl ? (_jsx("div", { className: "graview-bar-line", "data-graview-place-line": "", "data-testid": "app-place-line", children: placeControl })) : null] }));
489
+ useLayoutEffect(weigh, [weigh, name, brand, said, list?.current, onPages, compact, folded, find]);
490
+ // The app menu: the scene and the pages, the host's rows, the scheme — drawn where it holds anything.
491
+ const menuHolds = Boolean(faces) || appMenu.length > 0 || Boolean(scheme);
492
+ return (_jsxs(_Fragment, { children: [_jsxs("header", { ref: bar, className: "graview-bar", "data-testid": "app-bar", "data-graview-app-bar": "", ...(finding && folded ? { "data-finding": "" } : {}), children: [_jsx("style", { children: BAR_CSS }), skips, _jsxs("div", { className: "graview-bar-row", children: [named ? _jsxs("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 })] }) }), menuHolds ? _jsx(AppMenu, { name: name, faces: faces, rows: appMenu, scheme: scheme }) : null] }) : menuHolds ? _jsx(AppMenu, { name: name, faces: faces, rows: appMenu, scheme: scheme }) : 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, actions && actions.length > 0 ? (_jsx("span", { className: "graview-bar-ruler", "aria-hidden": "true", "data-graview-bar-host-ruler": "", children: _jsx("span", { className: "graview-bar-host", children: actions.map((action) => (_jsxs("span", { className: "graview-bar-host-action", ...(action.iconOnly && action.icon ? { "data-icon-only": "" } : {}), children: [action.icon ? _jsx(HostMark, { icon: action.icon }) : null, _jsx("span", { className: "graview-bar-host-words", children: action.label })] }, action.label))) }) })) : null, named ? (_jsx("span", { className: "graview-bar-ruler", "aria-hidden": "true", children: _jsx("span", { className: "graview-bar-name", children: name }) })) : 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", { ref: findAt, className: "graview-bar-find", "data-testid": "app-find", children: [_jsx("div", { ref: setSlot, className: "graview-bar-find-slot", children: findBox }), _jsx(FindKeys, {})] })] })) : null, _jsx("div", { ref: setOwn, className: "graview-bar-own", "data-graview-bar-own": "" }), actions && actions.length > 0 ? _jsx(HostButtons, { actions: actions, folded: !(actions.length <= 2 && actions.every((action) => action.iconOnly && action.icon)) && (compact || !hostStands) }) : null, status ? _jsx(StatusItem, { status: status }) : null, company ? (_jsx(Suspense, { fallback: null, children: _jsx(Here, { scene: !onPages, compact: compact }) })) : null, share ? _jsx(ShareSlot, { share: share }) : null, _jsx(TheirChanges, {}), tools] })] })] }), compact && placeControl ? (_jsx("div", { className: "graview-bar-line", "data-graview-place-line": "", "data-testid": "app-place-line", children: placeControl })) : null] }));
448
493
  }
494
+ const NO_ROWS = [];
449
495
  /**
450
- * THE SWITCH (FR-137): the scene and the pages, side by side, an icon and a
451
- * word each — the words the accessible names alone on a phone — the one
452
- * drawn pressed. Choosing the scene draws it under the bar; choosing the
453
- * pages goes back to the page you were on.
496
+ * THE APP MENU'S CONTROL (Roadmap f-app-bar-calm): a chevron beside the
497
+ * app's name — the name stays the way home, the page's heading — that opens
498
+ * the menu: the scene and the pages, a host's rows, the scheme. Its rows
499
+ * are fetched with the bar's panes when a hand first reaches for it.
454
500
  */
455
- function FaceSwitch({ faces, form, drawn }) {
456
- const face = (which) => {
457
- const one = faces[which];
458
- return (_jsxs("button", { type: "button", className: "graview-bar-face", "data-testid": `app-face-${which}`, "aria-pressed": one.current, title: one.label, ...(one.reach && !one.current ? { onPointerEnter: one.reach, onPointerDown: one.reach, onFocus: one.reach } : {}), onClick: () => {
459
- if (!one.current)
460
- one.go();
461
- }, children: [which === "scene" ? _jsx(SceneMark, {}) : _jsx(PagesMark, {}), _jsx("span", { children: one.label })] }));
462
- };
463
- 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")] }));
501
+ /**
502
+ * GOING TO THE OTHER FACE WITHOUT A BUTTON ON THE ROW (Roadmap
503
+ * f-app-bar-calm, f-make-any-page-better). The scene and the pages are rows
504
+ * of the app menu, drawn only while it is open, so a part that must switch
505
+ * faces — Better UI asked in the scene, opened on its page — cannot press
506
+ * them. It dispatches this on the app menu's control
507
+ * (`[data-testid="app-menu-open"]`, which says `data-pages` where the bar
508
+ * holds both faces), with the face as its detail, and the bar goes there as
509
+ * the menu's row would.
510
+ */
511
+ export const FACE_EVENT = "graview:go-to-face";
512
+ function AppMenu({ name, faces, rows, scheme }) {
513
+ const popover = usePopover("app-menu", { align: "start" });
514
+ const reach = () => barPanes.prefetch();
515
+ /* A part asked to go to the other face (`FACE_EVENT`): gone to as the menu's row would, without opening the menu. */
516
+ const opener = useRef(null);
517
+ const held = useRef(faces);
518
+ held.current = faces;
519
+ useEffect(() => {
520
+ const element = opener.current;
521
+ if (!element)
522
+ return;
523
+ const go = (event) => {
524
+ const one = held.current?.[event.detail];
525
+ if (!one || one.current)
526
+ return;
527
+ one.reach?.();
528
+ one.go();
529
+ };
530
+ element.addEventListener(FACE_EVENT, go);
531
+ return () => element.removeEventListener(FACE_EVENT, go);
532
+ }, []);
533
+ return (_jsxs(_Fragment, { children: [_jsx("button", { type: "button", className: "graview-bar-menu-open", "data-testid": "app-menu-open", ...popover.trigger, ref: (element) => {
534
+ popover.trigger.ref(element);
535
+ opener.current = element;
536
+ }, onClick: popover.toggle, onPointerEnter: reach, onFocus: reach, "aria-label": `${name} — app menu`, title: "App menu", ...(faces ? { "data-scene": faces.scene.label, "data-pages": faces.pages.label } : {}), children: _jsx(Chevron, {}) }), _jsx("div", { ...popover.pane, "data-testid": "app-menu", hidden: !popover.open, className: "graview-bar-list graview-app-menu", style: POPOVER_STYLE, "aria-label": `${name} — app menu`, role: "group", children: popover.open ? (_jsx(Suspense, { fallback: null, children: _jsx(AppMenuRows, { faces: faces, rows: rows, scheme: scheme, close: () => popover.setOpen(false) }) })) : null })] }));
537
+ }
538
+ /** Share's marks: a lock, a globe, two people. */
539
+ function ShareMark({ mark = "lock" }) {
540
+ if (mark === "lock")
541
+ return (_jsxs(Mark, { size: 14, children: [_jsx("rect", { x: "2.75", y: "6.25", width: "8.5", height: "6", rx: "1.25", fill: "none", stroke: "currentColor", strokeWidth: "1.3" }), _jsx("path", { d: "M4.75 6.25 V4.5 a2.25 2.25 0 0 1 4.5 0 V6.25", fill: "none", stroke: "currentColor", strokeWidth: "1.3" })] }));
542
+ if (mark === "globe")
543
+ return (_jsxs(Mark, { size: 14, children: [_jsx("circle", { cx: "7", cy: "7", r: "5.25", fill: "none", stroke: "currentColor", strokeWidth: "1.3" }), _jsx("path", { d: "M1.75 7 H12.25 M7 1.75 C4.75 4.5 4.75 9.5 7 12.25 M7 1.75 C9.25 4.5 9.25 9.5 7 12.25", fill: "none", stroke: "currentColor", strokeWidth: "1.1" })] }));
544
+ if (mark === "people")
545
+ return (_jsxs(Mark, { size: 14, children: [_jsx("circle", { cx: "5", cy: "5", r: "2", fill: "none", stroke: "currentColor", strokeWidth: "1.3" }), _jsx("path", { d: "M1.5 12 a3.5 3.5 0 0 1 7 0", fill: "none", stroke: "currentColor", strokeWidth: "1.3" }), _jsx("circle", { cx: "10", cy: "5.5", r: "1.6", fill: "none", stroke: "currentColor", strokeWidth: "1.2" }), _jsx("path", { d: "M9.5 9 a3 3 0 0 1 3.5 3", fill: "none", stroke: "currentColor", strokeWidth: "1.2" })] }));
546
+ return _jsx(HostMark, { icon: mark });
464
547
  }
465
548
  /**
466
- * WHETHER THE SWITCH'S WORDS FIT (the switch left to say its words where
467
- * there is room). The row is weighed as it is drawn: the room the middle
468
- * has past what the place wants (its words, up to 14em; none on the
469
- * scene), less what Find has given up and what the app's name has given
470
- * (`nameGiven`); none when the name is wider on one line than its room. The
471
- * words fit when that room holds them — the words
472
- * drawn already, or their width to come when the marks are drawn alone —
473
- * so the switch never flips back and forth at one width. Not weighed while
474
- * Find is in use (it is drawn wide then), on a phone's bar (the marks
475
- * alone), or where nothing is laid out (null).
549
+ * A HOST'S SHARE (Roadmap f-app-bar-calm, f-bar-status-item): its mark and
550
+ * its word, the sentence its description — on hover and to a screen reader —
551
+ * and, pressed, the sentence again with the link to where it changes. One
552
+ * of the bar's family of popovers.
476
553
  */
477
- function roomForWords(header) {
478
- const faces = header.querySelector(".graview-bar-faces");
479
- const mid = header.querySelector(".graview-bar-mid");
480
- const find = header.querySelector(".graview-bar-find");
481
- if (!faces || !mid || find?.matches(":focus-within"))
482
- return null;
483
- const row = header.getBoundingClientRect().width;
484
- if (row < BAR_PHONE)
485
- return null;
486
- // The words to come, beside each mark: the word, the gap before it, and the button's wider padding.
487
- const words = wordsOf(faces);
488
- const place = mid.querySelector(".graview-bar-place-words");
489
- const em = place ? parseFloat(getComputedStyle(place).fontSize) || 14 : 0;
490
- // The place's words, its chevron, its gap, padding and frame: up to 14em of them.
491
- const wanted = place ? Math.min(place.scrollWidth + 34, 14 * em) : 0;
492
- const given = nameGiven(header);
493
- // The app's name on two lines whatever stands beside it has given its room already: no room for the words.
494
- if (given === null)
495
- return false;
496
- let spare = mid.getBoundingClientRect().width - wanted - given - overrun(header);
497
- if (find && find.getBoundingClientRect().width > 0) {
498
- const basis = parseFloat(getComputedStyle(find).flexBasis);
499
- if (isFinite(basis))
500
- spare -= Math.max(0, basis - find.getBoundingClientRect().width);
501
- }
502
- return faces.getAttribute("data-switch-drawn") === "icons" ? spare >= words + 2 : spare >= -1;
554
+ function ShareSlot({ share }) {
555
+ const popover = usePopover("share");
556
+ const said = `graview-share-said-${useId().replace(/:/g, "")}`;
557
+ const label = share.label ?? "Share";
558
+ const link = share.link;
559
+ return (_jsxs("div", { className: "graview-bar-share-at", children: [_jsxs("button", { type: "button", className: "graview-bar-tool graview-bar-share", "data-testid": "app-share-open", ...popover.trigger, onClick: popover.toggle, "aria-describedby": said, title: share.description, children: [_jsx(ShareMark, { mark: share.mark }), _jsx("span", { className: "graview-bar-share-words", children: label })] }), _jsx("span", { id: said, className: "graview-bar-said", children: share.description }), _jsxs("div", { ...popover.pane, "data-testid": "app-share", hidden: !popover.open, className: "graview-bar-share-pane", style: POPOVER_STYLE, role: "group", "aria-label": label, children: [_jsx("p", { "data-testid": "app-share-said", children: share.description }), link || share.secondLink ? (_jsxs("div", { className: "graview-bar-share-links", children: [link ? _jsx(ShareLink, { link: link, testId: "app-share-link", close: () => popover.setOpen(false) }) : null, share.secondLink ? _jsx(ShareLink, { link: share.secondLink, testId: "app-share-second-link", close: () => popover.setOpen(false) }) : null] })) : null] })] }));
560
+ }
561
+ /** A host's status: its mark and its word, the sentence on hover and as its description; a link where it has an `href`. */
562
+ function StatusItem({ status }) {
563
+ const said = `graview-status-said-${useId().replace(/:/g, "")}`;
564
+ const inner = (_jsxs(_Fragment, { children: [status.icon ? _jsx(ShareMark, { mark: status.icon }) : null, _jsx("span", { className: "graview-bar-status-words", children: status.label })] }));
565
+ const common = { className: "graview-bar-tool graview-bar-status", "data-testid": "app-status", title: status.description, "aria-describedby": said, ...(status.icon ? { "data-icon": "" } : {}) };
566
+ return (_jsxs(_Fragment, { children: [status.href !== undefined ? (_jsx("a", { ...common, href: status.href, ...(status.target ? { target: status.target, rel: "noopener" } : {}), children: inner })) : (_jsx("span", { ...common, tabIndex: 0, children: inner })), _jsx("span", { id: said, className: "graview-bar-said", children: status.description })] }));
567
+ }
568
+ /** One of Share's links: an anchor where it has an `href`, else a button; either closes the popover. */
569
+ function ShareLink({ link, testId, close }) {
570
+ return link.href !== undefined ? (_jsx("a", { href: link.href, "data-testid": testId, ...(link.target ? { target: link.target, rel: "noopener" } : {}), onClick: () => {
571
+ link.onSelect?.();
572
+ close();
573
+ }, children: link.label })) : (_jsx("button", { type: "button", "data-testid": testId, onClick: () => {
574
+ close();
575
+ link.onSelect?.();
576
+ }, children: link.label }));
503
577
  }
504
578
  /**
505
579
  * HOW FAR THE ROW RUNS PAST ITS END, in pixels: what stands last on it — a
@@ -509,7 +583,7 @@ function roomForWords(header) {
509
583
  * Activity arriving after the places were weighed, an iPad's 1024 px with
510
584
  * five places standing), Find's end went past the row with them, the room
511
585
  * read as enough, and the person stood off the screen. What runs over is
512
- * room the places and the switch's words do not have.
586
+ * room the places do not have.
513
587
  */
514
588
  function overrun(header) {
515
589
  const row = header.querySelector(".graview-bar-row");
@@ -569,10 +643,6 @@ function Mark({ size, box = size, children }) {
569
643
  function SceneMark() {
570
644
  return (_jsxs(Mark, { size: 14, 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" })] }));
571
645
  }
572
- /** The pages' mark: a page of lines. */
573
- function PagesMark() {
574
- return (_jsxs(Mark, { size: 14, 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" })] }));
575
- }
576
646
  /** The way a menu opens: a chevron drawn, not a glyph a face may draw as a dot. */
577
647
  function Chevron() {
578
648
  return (_jsx(Mark, { size: 10, children: _jsx("path", { d: "M2 3.5 L5 6.5 L8 3.5", fill: "none", stroke: "currentColor", strokeWidth: "1.4", strokeLinecap: "round", strokeLinejoin: "round" }) }));
@@ -612,10 +682,10 @@ const MORE = "More";
612
682
  function HostButtons({ actions, folded }) {
613
683
  const popover = usePopover("host-actions", { popover: folded });
614
684
  const names = actions.map((action) => action.label).join(", ");
615
- const buttons = (done) => actions.map((action) => (_jsxs("button", { type: "button", className: folded ? "graview-bar-host-action graview-bar-item" : "graview-bar-host-action", "data-testid": "host-bar-action", onClick: () => {
685
+ const buttons = (done) => actions.map((action) => (_jsxs("button", { type: "button", className: folded ? "graview-bar-host-action graview-bar-item" : "graview-bar-host-action", "data-testid": "host-bar-action", ...(!folded && action.iconOnly && action.icon ? { "data-icon-only": "", title: action.label } : {}), onClick: () => {
616
686
  done();
617
687
  action.onSelect();
618
- }, children: [action.icon ? _jsx(HostMark, { icon: action.icon }) : null, _jsx("span", { children: action.label })] }, action.label)));
688
+ }, children: [action.icon ? _jsx(HostMark, { icon: action.icon }) : null, _jsx("span", { className: "graview-bar-host-words", children: action.label })] }, action.label)));
619
689
  if (!folded) {
620
690
  return (_jsx("div", { className: "graview-bar-host", "data-testid": "host-bar-actions-standing", role: "group", "aria-label": "From the host", children: buttons(() => undefined) }));
621
691
  }
@@ -737,8 +807,7 @@ function usePlaceGestures(places, current, scene) {
737
807
  * WHICH PLACES STAND ON THE ROW, WEIGHED FROM THE ROW AS DRAWN (FR-145):
738
808
  * the places' widths read off the ruler (each at its weight as the place
739
809
  * you are on, so marking one never moves the others), and the room from
740
- * where the places begin to where Find ends, less Find at its least, less
741
- * the switch's words when they are asked for. The same answer whichever way
810
+ * where the places begin to where Find ends, less Find at its least. The same answer whichever way
742
811
  * the places are drawn, so the row never flips between the two at one
743
812
  * width. The places begin where they would with the app's name on one
744
813
  * line (`nameGiven`), so they stand only where they leave it there, and the
@@ -747,7 +816,7 @@ function usePlaceGestures(places, current, scene) {
747
816
  * name is wider on one line than its room, or where nothing is laid out
748
817
  * (null).
749
818
  */
750
- function standsIn(header, current, wordsAsked, ranks) {
819
+ function standsIn(header, current, ranks) {
751
820
  const ruler = header.querySelector("[data-graview-bar-ruler]");
752
821
  const mid = header.querySelector(".graview-bar-mid");
753
822
  if (!ruler || !mid)
@@ -762,11 +831,8 @@ function standsIn(header, current, wordsAsked, ranks) {
762
831
  return null;
763
832
  const widths = [...ruler.children].map((one) => one.getBoundingClientRect().width);
764
833
  const more = widths.pop() ?? 0;
765
- const faces = header.querySelector(".graview-bar-faces");
766
- const words = faces && wordsAsked ? wordsOf(faces) : 0;
767
- const drawnWords = faces?.getAttribute("data-switch-drawn") === "words" ? words : 0;
768
834
  const from = mid.getBoundingClientRect().left + given;
769
- const end = find?.getBoundingClientRect();
835
+ const end = find && getComputedStyle(find).display !== "none" ? find.getBoundingClientRect() : undefined;
770
836
  /*
771
837
  * To Find's end, less Find at its least and the room between them — the
772
838
  * row's whole gap: Find stands at `margin-left:auto`, which is nothing
@@ -776,10 +842,6 @@ function standsIn(header, current, wordsAsked, ranks) {
776
842
  */
777
843
  const room = (end && end.width > 0 ? end.right - from - (parseFloat(getComputedStyle(find).minWidth) || 0) - 16 : mid.getBoundingClientRect().width - given) - overrun(header);
778
844
  const primary = [...ranks].map((rank) => rank !== "0");
779
- return placesThatStand({ widths, current, room: Math.floor(room + drawnWords - words), more, primary })?.join(",") ?? null;
780
- }
781
- /** The switch's words, beside each mark: the word, the gap before it, and the button's wider padding. */
782
- function wordsOf(faces) {
783
- return [...faces.querySelectorAll(".graview-bar-face span")].reduce((sum, span) => sum + span.scrollWidth + 10, 0);
845
+ return placesThatStand({ widths, current, room: Math.floor(room), more, primary })?.join(",") ?? null;
784
846
  }
785
847
  //# sourceMappingURL=app-bar.js.map