@graview/primitives 0.1.17 → 0.1.18

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (117) hide show
  1. package/README.md +5 -2
  2. package/dist/app-bar.d.ts +47 -1
  3. package/dist/app-bar.d.ts.map +1 -1
  4. package/dist/app-bar.js +270 -60
  5. package/dist/app-bar.js.map +1 -1
  6. package/dist/bar-panes-door.d.ts +8 -0
  7. package/dist/bar-panes-door.d.ts.map +1 -0
  8. package/dist/bar-panes-door.js +10 -0
  9. package/dist/bar-panes-door.js.map +1 -0
  10. package/dist/chat.js +1 -1
  11. package/dist/chat.js.map +1 -1
  12. package/dist/companion.d.ts +0 -15
  13. package/dist/companion.d.ts.map +1 -1
  14. package/dist/companion.js +19 -150
  15. package/dist/companion.js.map +1 -1
  16. package/dist/connections.d.ts +7 -1
  17. package/dist/connections.d.ts.map +1 -1
  18. package/dist/connections.js +13 -13
  19. package/dist/connections.js.map +1 -1
  20. package/dist/declared-lens-doors.d.ts +3 -3
  21. package/dist/declared-lens-doors.d.ts.map +1 -1
  22. package/dist/declared-lens-doors.js +37 -24
  23. package/dist/declared-lens-doors.js.map +1 -1
  24. package/dist/default-view.d.ts +2 -0
  25. package/dist/default-view.d.ts.map +1 -1
  26. package/dist/default-view.js +10 -4
  27. package/dist/default-view.js.map +1 -1
  28. package/dist/default-views.d.ts.map +1 -1
  29. package/dist/default-views.js +195 -42
  30. package/dist/default-views.js.map +1 -1
  31. package/dist/editable.d.ts +31 -0
  32. package/dist/editable.d.ts.map +1 -1
  33. package/dist/editable.js +86 -17
  34. package/dist/editable.js.map +1 -1
  35. package/dist/figure.js +1 -1
  36. package/dist/figure.js.map +1 -1
  37. package/dist/find.d.ts.map +1 -1
  38. package/dist/find.js +23 -6
  39. package/dist/find.js.map +1 -1
  40. package/dist/frame.d.ts +3 -2
  41. package/dist/frame.d.ts.map +1 -1
  42. package/dist/frame.js +2 -1
  43. package/dist/frame.js.map +1 -1
  44. package/dist/index.d.ts +13 -7
  45. package/dist/index.d.ts.map +1 -1
  46. package/dist/index.js +9 -5
  47. package/dist/index.js.map +1 -1
  48. package/dist/lens/calendar-dates.d.ts +2 -0
  49. package/dist/lens/calendar-dates.d.ts.map +1 -1
  50. package/dist/lens/calendar-dates.js +2 -0
  51. package/dist/lens/calendar-dates.js.map +1 -1
  52. package/dist/lens/calendar-drawing.d.ts +2 -2
  53. package/dist/lens/calendar-drawing.d.ts.map +1 -1
  54. package/dist/lens/calendar-drawing.js +1 -2
  55. package/dist/lens/calendar-drawing.js.map +1 -1
  56. package/dist/lens/calendar-spans.d.ts.map +1 -1
  57. package/dist/lens/calendar-spans.js +1 -2
  58. package/dist/lens/calendar-spans.js.map +1 -1
  59. package/dist/lens/calendar-view.d.ts +2 -1
  60. package/dist/lens/calendar-view.d.ts.map +1 -1
  61. package/dist/lens/calendar-view.js +2 -1
  62. package/dist/lens/calendar-view.js.map +1 -1
  63. package/dist/notice-place.d.ts.map +1 -1
  64. package/dist/notice-place.js +2 -0
  65. package/dist/notice-place.js.map +1 -1
  66. package/dist/pages.d.ts +5 -3
  67. package/dist/pages.d.ts.map +1 -1
  68. package/dist/pages.js +5 -2
  69. package/dist/pages.js.map +1 -1
  70. package/dist/places.js +2 -2
  71. package/dist/places.js.map +1 -1
  72. package/dist/profile.d.ts.map +1 -1
  73. package/dist/profile.js +34 -9
  74. package/dist/profile.js.map +1 -1
  75. package/dist/prose-editor.d.ts +9 -0
  76. package/dist/prose-editor.d.ts.map +1 -0
  77. package/dist/prose-editor.js +64 -0
  78. package/dist/prose-editor.js.map +1 -0
  79. package/dist/record-head.d.ts +28 -0
  80. package/dist/record-head.d.ts.map +1 -0
  81. package/dist/record-head.js +3 -0
  82. package/dist/record-head.js.map +1 -0
  83. package/dist/relation-key.d.ts.map +1 -1
  84. package/dist/relation-key.js +2 -2
  85. package/dist/relation-key.js.map +1 -1
  86. package/dist/scene-css.d.ts.map +1 -1
  87. package/dist/scene-css.js +13 -0
  88. package/dist/scene-css.js.map +1 -1
  89. package/dist/scene-places.d.ts +37 -0
  90. package/dist/scene-places.d.ts.map +1 -0
  91. package/dist/scene-places.js +77 -0
  92. package/dist/scene-places.js.map +1 -0
  93. package/dist/spec-views.d.ts +9 -2
  94. package/dist/spec-views.d.ts.map +1 -1
  95. package/dist/spec-views.js +52 -4
  96. package/dist/spec-views.js.map +1 -1
  97. package/dist/subject.d.ts +17 -0
  98. package/dist/subject.d.ts.map +1 -0
  99. package/dist/subject.js +155 -0
  100. package/dist/subject.js.map +1 -0
  101. package/dist/text-body.d.ts +28 -0
  102. package/dist/text-body.d.ts.map +1 -0
  103. package/dist/text-body.js +64 -0
  104. package/dist/text-body.js.map +1 -0
  105. package/dist/view-doors.d.ts +5 -1
  106. package/dist/view-doors.d.ts.map +1 -1
  107. package/dist/view-doors.js +28 -14
  108. package/dist/view-doors.js.map +1 -1
  109. package/dist/workbench/answer-args.d.ts.map +1 -1
  110. package/dist/workbench/answer-args.js +26 -2
  111. package/dist/workbench/answer-args.js.map +1 -1
  112. package/dist/workbench/inspector.js +1 -1
  113. package/dist/workbench/inspector.js.map +1 -1
  114. package/dist/workbench/standing.d.ts.map +1 -1
  115. package/dist/workbench/standing.js +4 -3
  116. package/dist/workbench/standing.js.map +1 -1
  117. package/package.json +6 -6
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
@@ -64,10 +66,37 @@ export interface BarFaces {
64
66
  readonly scene: BarFace;
65
67
  readonly pages: BarFace;
66
68
  }
69
+ /** How the switch is drawn: its words beside its marks where there is room, or its marks alone (the words its names). */
70
+ export type BarSwitch = "words" | "icons";
67
71
  /** One size for every tool on the bar (FR-131). */
68
72
  export declare const TOOL = 30;
69
73
  /** The bar's one row, its rule under it included, on a desk and a phone alike (FR-138). */
70
74
  export declare const BAR_HEIGHT = 48;
75
+ /** 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. */
76
+ export declare const BAR_PHONE = 640;
77
+ /** The room between two places standing on the row (FR-145). */
78
+ export declare const PLACE_GAP = 4;
79
+ /** The fewest places that stand on the row: fewer, and the one control says the same in less room. */
80
+ export declare const FEWEST_STANDING = 2;
81
+ /**
82
+ * WHICH PLACES STAND ON THE ROW (FR-145), from their widths and the room:
83
+ * every one when all fit; else the first ones in their order, the current
84
+ * one always among them (in its own place in the order, the last of the
85
+ * first ones giving way to it), with "More" after them; else none — null,
86
+ * the one control. Fewer than `FEWEST_STANDING` is none. A width that is
87
+ * not measured (nothing laid out) is none too.
88
+ */
89
+ export declare function placesThatStand(input: {
90
+ /** Each place's width as it would stand, in the order of the list. */
91
+ readonly widths: readonly number[];
92
+ /** Where the current place is in the list; -1 for none. */
93
+ readonly current: number;
94
+ /** The room the row has for them. */
95
+ readonly room: number;
96
+ /** "More"'s width. */
97
+ readonly more: number;
98
+ readonly gap?: number;
99
+ }): readonly number[] | null;
71
100
  /** A tool's own box: one size, square, its name in words for whoever cannot see the mark. */
72
101
  export declare const toolStyle: CSSProperties;
73
102
  /**
@@ -78,7 +107,7 @@ export declare const toolStyle: CSSProperties;
78
107
  * person). One row of one height on a desk and a phone; on a phone the
79
108
  * place is the page's first line, under the bar.
80
109
  */
81
- export declare function AppBar({ brand, name, heading, home, faces, places, current, reach, tools, find, description, findBox, onFind, }: {
110
+ export declare function AppBar({ brand, name, heading, home, faces, places, current, reach, tools, find, description, findBox, onFind, switch: switchForm, scenePlaces, }: {
82
111
  readonly brand: Brand | undefined;
83
112
  readonly name: string;
84
113
  /** 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 +134,22 @@ export declare function AppBar({ brand, name, heading, home, faces, places, curr
105
134
  readonly findBox?: ReactNode;
106
135
  /** Told where the bar keeps the Find box, for the face under it (`BarFindContext`). */
107
136
  readonly onFind?: (find: BarFind | null) => void;
137
+ /**
138
+ * How the switch is drawn: `"words"` (the default) says "Scene" and
139
+ * "Pages" beside their marks where the bar has room and draws the marks
140
+ * alone where it is narrow; `"icons"` draws the marks alone at every
141
+ * width. Either way the words are the buttons' names and their titles.
142
+ */
143
+ readonly switch?: BarSwitch;
144
+ /**
145
+ * On the scene, what it can show (FR-144): the whole thing and each
146
+ * picture, the one in view, and the way to each (`useScenePlaces`). None,
147
+ * and the scene's bar names no place.
148
+ */
149
+ readonly scenePlaces?: {
150
+ readonly places: readonly BarPlace[];
151
+ readonly current: string | null;
152
+ readonly reach: BarGo;
153
+ } | undefined;
108
154
  }): import("react").JSX.Element;
109
155
  //# sourceMappingURL=app-bar.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"app-bar.d.ts","sourceRoot":"","sources":["../src/app-bar.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAgD,KAAK,SAAS,EAAE,KAAK,KAAK,EAAE,KAAK,gBAAgB,EAAE,KAAK,KAAK,EAAE,KAAK,SAAS,EAAE,KAAK,KAAK,EAAE,MAAM,eAAe,CAAC;AAExK,OAAO,EAA6E,KAAK,aAAa,EAAmB,KAAK,SAAS,EAAE,MAAM,OAAO,CAAC;AAuBvJ,6EAA6E;AAC7E,MAAM,MAAM,aAAa,GAAG,MAAM,GAAG,OAAO,GAAG,UAAU,CAAC;AAE1D,2HAA2H;AAC3H,MAAM,WAAW,QAAQ;IACvB,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,mCAAmC;IACnC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,KAAK,EAAE,aAAa,CAAC;IAC9B,2DAA2D;IAC3D,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;CACxB;AAED,gHAAgH;AAChH,eAAO,MAAM,QAAQ,SAAS,CAAC;AAC/B,4DAA4D;AAC5D,eAAO,MAAM,SAAS,MAAM,CAAC;AAI7B;;;;;;GAMG;AACH,wBAAgB,SAAS,CAAC,KAAK,EAAE;IAC/B,QAAQ,CAAC,KAAK,EAAE,KAAK,CAAC,SAAS,CAAC,CAAC;IACjC,QAAQ,CAAC,SAAS,CAAC,EAAE,SAAS,GAAG,SAAS,CAAC;IAC3C,QAAQ,CAAC,KAAK,CAAC,EAAE;QAAE,MAAM,IAAI,SAAS,KAAK,EAAE,CAAC;QAAC,WAAW,CAAC,IAAI,gBAAgB,GAAG,SAAS,CAAA;KAAE,GAAG,SAAS,CAAC;CAC3G,GAAG,SAAS,QAAQ,EAAE,CAoBtB;AAED,kHAAkH;AAClH,wBAAgB,UAAU,CAAC,MAAM,EAAE,SAAS,QAAQ,EAAE,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAOnF;AAED;;;;GAIG;AACH,MAAM,WAAW,OAAO;IACtB,QAAQ,CAAC,IAAI,EAAE,WAAW,CAAC;IAC3B,4EAA4E;IAC5E,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;CAC3B;AACD,eAAO,MAAM,cAAc,yCAAsC,CAAC;AAClE,0EAA0E;AAC1E,eAAO,MAAM,UAAU,QAAO,OAAO,GAAG,IAAkC,CAAC;AAE3E,2FAA2F;AAC3F,MAAM,WAAW,KAAK;IACpB,2EAA2E;IAC3E,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,GAAG,SAAS,CAAC;IACrD,oFAAoF;IACpF,QAAQ,CAAC,EAAE,CAAC,EAAE,CAAC,KAAK,EAAE,QAAQ,KAAK,IAAI,CAAC;CACzC;AAED,gGAAgG;AAChG,MAAM,WAAW,OAAO;IACtB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;IAC1B,QAAQ,CAAC,EAAE,EAAE,MAAM,IAAI,CAAC;CACzB;AAED,yEAAyE;AACzE,MAAM,WAAW,QAAQ;IACvB,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAC;IACxB,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAC;CACzB;AAED,mDAAmD;AACnD,eAAO,MAAM,IAAI,KAAK,CAAC;AACvB,2FAA2F;AAC3F,eAAO,MAAM,UAAU,KAAK,CAAC;AA8C7B,6FAA6F;AAC7F,eAAO,MAAM,SAAS,EAAE,aAiBvB,CAAC;AAEF;;;;;;;GAOG;AACH,wBAAgB,MAAM,CAAC,EACrB,KAAK,EACL,IAAI,EACJ,OAAW,EACX,IAAI,EACJ,KAAK,EACL,MAAM,EACN,OAAO,EACP,KAAK,EACL,KAAK,EACL,IAAW,EACX,WAAW,EACX,OAAO,EACP,MAAM,GACP,EAAE;IACD,QAAQ,CAAC,KAAK,EAAE,KAAK,GAAG,SAAS,CAAC;IAClC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,8HAA8H;IAC9H,QAAQ,CAAC,OAAO,CAAC,EAAE,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,KAAK,CAAC;IACjD,qDAAqD;IACrD,QAAQ,CAAC,IAAI,EAAE;QAAE,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;QAAC,QAAQ,CAAC,EAAE,CAAC,EAAE,CAAC,MAAM,IAAI,CAAC,GAAG,SAAS,CAAC;QAAC,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAA;KAAE,CAAC;IACzH,6FAA6F;IAC7F,QAAQ,CAAC,KAAK,CAAC,EAAE,QAAQ,GAAG,SAAS,CAAC;IACtC,QAAQ,CAAC,MAAM,EAAE,SAAS,QAAQ,EAAE,CAAC;IACrC,4EAA4E;IAC5E,QAAQ,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IAChC,QAAQ,CAAC,KAAK,EAAE,KAAK,CAAC;IACtB,+CAA+C;IAC/C,QAAQ,CAAC,KAAK,EAAE,SAAS,CAAC;IAC1B,iDAAiD;IACjD,QAAQ,CAAC,IAAI,CAAC,EAAE,OAAO,CAAC;IACxB,8GAA8G;IAC9G,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC1C,4IAA4I;IAC5I,QAAQ,CAAC,OAAO,CAAC,EAAE,SAAS,CAAC;IAC7B,uFAAuF;IACvF,QAAQ,CAAC,MAAM,CAAC,EAAE,CAAC,IAAI,EAAE,OAAO,GAAG,IAAI,KAAK,IAAI,CAAC;CAClD,+BAgIA"}
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,25 @@ 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-find{position:relative;display:flex;min-width:7.5rem;flex:0 600 9rem;--graview-bar-find-end:3.25em}
156
+ .graview-bar-find-slot{display:flex;flex:1;min-width:0}
157
+ .graview-bar-find-keys{position:absolute;right:10px;line-height:30px;pointer-events:none;font-size:.75rem;color:var(--graview-ink-faint)}
158
+ .graview-bar-find:is(:focus-within,:has(input:not(:placeholder-shown))){flex-basis:16rem;--graview-bar-find-end:10px}
159
+ :is(:focus-within,:has(input:not(:placeholder-shown)))>.graview-bar-find-keys,.graview-bar-find-open{display:none}
88
160
  .graview-bar-line{display:flex;flex:0 0 auto;padding:6px 8px;background:var(--graview-ground);border-bottom:1px solid var(--graview-edge);font-family:var(--graview-font-body,system-ui)}
89
161
  .graview-bar-line .graview-bar-place{height:auto;min-height:32px}
90
162
  .graview-bar-line .graview-bar-place-words{white-space:normal}
91
- @container (max-width: 639px){
163
+ @media (pointer:coarse){.graview-bar-find-keys{display:none}}
164
+ @container graview-bar (max-width: ${BAR_PHONE - 1}px){
92
165
  .graview-bar-row{gap:10px;padding:0 12px}
93
166
  .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
167
  .graview-bar-mid,.graview-bar-find{display:none}
98
168
  .graview-bar-find-open{display:inline-flex}
99
169
  .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}
170
+ .graview-bar[data-finding] .graview-bar-find{display:flex;flex:1 1 auto;min-width:0}
102
171
  }`;
103
172
  /** A tool's own box: one size, square, its name in words for whoever cannot see the mark. */
104
173
  export const toolStyle = {
@@ -127,23 +196,61 @@ export const toolStyle = {
127
196
  * person). One row of one height on a desk and a phone; on a phone the
128
197
  * place is the page's first line, under the bar.
129
198
  */
130
- export function AppBar({ brand, name, heading = 1, home, faces, places, current, reach, tools, find = true, description, findBox, onFind, }) {
199
+ export function AppBar({ brand, name, heading = 1, home, faces, places, current, reach, tools, find = true, description, findBox, onFind, switch: switchForm = "words", scenePlaces, }) {
131
200
  const bar = useRef(null);
132
201
  const [slot, setSlot] = useState(null);
133
202
  const [compact, setCompact] = useState(false);
134
203
  const [finding, setFinding] = useState(false);
204
+ // Whether the switch's words fit beside everything else the row holds (`roomForWords`).
205
+ const [roomy, setRoomy] = useState(true);
206
+ // Which places stand on the row, by their place in the list ("0,1,2"); null, the one control (FR-145).
207
+ const [standing, setStanding] = useState(null);
208
+ // The places on the face drawn: the routed face's, or the scene's (FR-144).
209
+ const onPages = !faces || faces.pages.current;
210
+ const shown = onPages ? { places, current, reach } : scenePlaces;
211
+ const list = shown && shown.places.length > 0 ? shown : null;
212
+ const currentAt = list ? list.places.findIndex((place) => place.key === list.current) : -1;
213
+ const wordsAsked = Boolean(faces) && switchForm === "words";
214
+ const weigh = useCallback(() => {
215
+ const element = bar.current;
216
+ if (!element)
217
+ return;
218
+ const stand = standsIn(element, currentAt, wordsAsked);
219
+ if (stand !== undefined)
220
+ setStanding(stand);
221
+ // Standing places were weighed with the switch's words kept.
222
+ const room = stand ? true : roomForWords(element);
223
+ if (room !== null)
224
+ setRoomy(room);
225
+ }, [currentAt, wordsAsked]);
135
226
  useLayoutEffect(() => {
136
227
  const element = bar.current;
137
228
  if (!element)
138
229
  return;
139
- const read = () => setCompact(element.getBoundingClientRect().width < 640);
230
+ const read = () => {
231
+ const width = element.getBoundingClientRect().width;
232
+ setCompact(width < BAR_PHONE);
233
+ // What hangs from the bar as wide as it (a phone's Find sheet) is told the bar's width, not the screen's.
234
+ element.style.setProperty("--graview-bar-width", `${Math.round(width)}px`);
235
+ weigh();
236
+ };
140
237
  read();
238
+ // The brand's face, once it has come, is wider or narrower than the one the row was first weighed in.
239
+ const fonts = typeof document === "undefined" ? undefined : document.fonts;
240
+ void fonts?.ready.then(weigh);
241
+ fonts?.addEventListener?.("loadingdone", weigh);
141
242
  if (typeof ResizeObserver === "undefined")
142
- return;
243
+ return () => fonts?.removeEventListener?.("loadingdone", weigh);
143
244
  const watch = new ResizeObserver(read);
144
245
  watch.observe(element);
145
- return () => watch.disconnect();
146
- }, []);
246
+ // What stands beside the places — the tools, a count on the standing — changes their room without changing the bar's width.
247
+ for (const tool of element.querySelectorAll(".graview-bar-tools > *"))
248
+ watch.observe(tool);
249
+ return () => {
250
+ watch.disconnect();
251
+ fonts?.removeEventListener?.("loadingdone", weigh);
252
+ };
253
+ }, [weigh]);
147
254
  const told = useRef(onFind);
148
255
  told.current = onFind;
149
256
  useLayoutEffect(() => {
@@ -175,10 +282,16 @@ export function AppBar({ brand, name, heading = 1, home, faces, places, current,
175
282
  event.preventDefault();
176
283
  go();
177
284
  };
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) => {
285
+ // The place control: in the bar on a desk, as the page's first line on a phone; the places themselves on the row when they fit (FR-145).
286
+ const scene = !onPages;
287
+ const stands = list && !compact && standing !== null ? standing.split(",").map(Number).filter((at) => at < list.places.length) : null;
288
+ const placeControl = list ? _jsx(Places, { places: list.places, current: list.current, reach: list.reach, scene: scene, stands: stands }) : null;
289
+ const said = list ? list.places.map((place) => place.label).join("\n") : "";
290
+ // What the row holds changed — the name, the places, the face, the brand — so the places and the words may fit now, or no longer.
291
+ useLayoutEffect(weigh, [weigh, name, brand, said, list?.current, onPages, compact, switchForm, find]);
292
+ // The marks alone on a phone's bar, when asked, or when the words do not fit.
293
+ const drawn = switchForm === "icons" || !roomy || compact ? "icons" : "words";
294
+ return (_jsxs(_Fragment, { children: [_jsxs("header", { ref: bar, className: "graview-bar", "data-testid": "app-bar", "data-graview-app-bar": "", ...(finding && compact ? { "data-finding": "" } : {}), children: [_jsx("style", { children: BAR_CSS }), _jsxs("div", { className: "graview-bar-row", children: [_jsx("div", { className: "graview-bar-app", children: _jsx(Heading, { className: "graview-bar-name", children: _jsxs(Home, { ...(home.href !== undefined ? { href: home.href, onClick: press(home.go) } : { type: "button", onClick: home.go }), "data-testid": "app-home", ...(home.current ? { "aria-current": "page" } : {}), title: description ? `${name} — ${description}` : `${name} — home`, className: "graview-bar-home", children: [_jsx(AppMark, { brand: brand, size: 20 }), _jsx("span", { className: "graview-bar-app-name", "data-testid": "app-name", children: name })] }) }) }), faces ? _jsx(FaceSwitch, { faces: faces, form: switchForm, drawn: drawn }) : null, _jsx("div", { className: "graview-bar-mid", ...(stands ? { "data-places": "standing" } : !compact && placeControl ? { "data-place": "" } : {}), children: compact ? null : placeControl }), !compact && list && list.places.length > 1 ? (_jsxs("span", { className: "graview-bar-ruler", "aria-hidden": "true", "data-graview-bar-ruler": "", children: [list.places.map((place) => (_jsx("span", { className: "graview-bar-at", children: place.label }, place.key))), _jsxs("span", { className: "graview-bar-at", children: [MORE, _jsx(Chevron, {})] })] })) : null, _jsxs("div", { className: "graview-bar-tools", children: [find ? (_jsxs(_Fragment, { children: [_jsx("button", { type: "button", className: "graview-bar-find-open", ref: opener, "data-testid": "app-find-open", "aria-label": "Find", title: "Find", onClick: openFind, style: { ...toolStyle, display: undefined }, children: _jsx(FindMark, {}) }), _jsxs("div", { className: "graview-bar-find", "data-testid": "app-find", onBlur: (event) => {
182
295
  // Put away on a phone when the keyboard leaves it with nothing typed.
183
296
  const next = event.relatedTarget;
184
297
  if (next instanceof Node && event.currentTarget.contains(next))
@@ -196,7 +309,7 @@ export function AppBar({ brand, name, heading = 1, home, faces, places, current,
196
309
  putAway();
197
310
  else
198
311
  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] }));
312
+ }, children: [_jsx("div", { ref: setSlot, className: "graview-bar-find-slot", children: findBox }), _jsx("span", { className: "graview-bar-find-keys", "data-testid": "app-find-keys", "aria-hidden": "true", children: findKeys() })] })] })) : null, tools] })] })] }), compact && placeControl ? (_jsx("div", { className: "graview-bar-line", "data-graview-place-line": "", "data-testid": "app-place-line", children: placeControl })) : null] }));
200
313
  }
201
314
  /**
202
315
  * THE SWITCH (FR-137): the scene and the pages, side by side, an icon and a
@@ -204,7 +317,7 @@ export function AppBar({ brand, name, heading = 1, home, faces, places, current,
204
317
  * drawn pressed. Choosing the scene draws it under the bar; choosing the
205
318
  * pages goes back to the page you were on.
206
319
  */
207
- function FaceSwitch({ faces }) {
320
+ function FaceSwitch({ faces, form, drawn }) {
208
321
  const face = (which) => {
209
322
  const one = faces[which];
210
323
  return (_jsxs("button", { type: "button", className: "graview-bar-face", "data-testid": `app-face-${which}`, "aria-pressed": one.current, title: one.label, onClick: () => {
@@ -212,7 +325,50 @@ function FaceSwitch({ faces }) {
212
325
  one.go();
213
326
  }, children: [which === "scene" ? _jsx(SceneMark, {}) : _jsx(PagesMark, {}), _jsx("span", { children: one.label })] }));
214
327
  };
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")] }));
328
+ return (_jsxs("div", { className: "graview-bar-faces", role: "group", "aria-label": `${faces.scene.label} or ${faces.pages.label}`, "data-testid": "app-faces", "data-switch": form, "data-switch-drawn": drawn, children: [face("scene"), face("pages")] }));
329
+ }
330
+ /**
331
+ * WHETHER THE SWITCH'S WORDS FIT (the switch left to say its words where
332
+ * there is room). The row is weighed as it is drawn: the room the middle
333
+ * has past what the place wants (its words, up to 14em; none on the
334
+ * scene), less what Find has given up; none when the app's name has
335
+ * wrapped. The words fit when that room holds them — the words
336
+ * drawn already, or their width to come when the marks are drawn alone —
337
+ * so the switch never flips back and forth at one width. Not weighed while
338
+ * Find is in use (it is drawn wide then), on a phone's bar (the marks
339
+ * alone), or where nothing is laid out (null).
340
+ */
341
+ function roomForWords(header) {
342
+ const faces = header.querySelector(".graview-bar-faces");
343
+ const mid = header.querySelector(".graview-bar-mid");
344
+ const find = header.querySelector(".graview-bar-find");
345
+ if (!faces || !mid || find?.matches(":focus-within"))
346
+ return null;
347
+ const row = header.getBoundingClientRect().width;
348
+ if (row < BAR_PHONE)
349
+ return null;
350
+ // The words to come, beside each mark: the word, the gap before it, and the button's wider padding.
351
+ const words = wordsOf(faces);
352
+ const place = mid.querySelector(".graview-bar-place-words");
353
+ const em = place ? Number.parseFloat(getComputedStyle(place).fontSize) || 14 : 0;
354
+ // The place's words, its chevron, its gap, padding and frame: up to 14em of them.
355
+ const wanted = place ? Math.min(place.scrollWidth + 34, 14 * em) : 0;
356
+ let spare = mid.getBoundingClientRect().width - wanted;
357
+ if (find && find.getBoundingClientRect().width > 0) {
358
+ const basis = Number.parseFloat(getComputedStyle(find).flexBasis);
359
+ if (Number.isFinite(basis))
360
+ spare -= Math.max(0, basis - find.getBoundingClientRect().width);
361
+ }
362
+ // The app's name on two lines has given its room already: no room for the words.
363
+ const name = header.querySelector(".graview-bar-app-name");
364
+ if (name && name.getBoundingClientRect().height > Number.parseFloat(getComputedStyle(name).lineHeight) * 1.5)
365
+ return false;
366
+ return faces.getAttribute("data-switch-drawn") === "icons" ? spare >= words + 2 : spare >= -1;
367
+ }
368
+ /** Find's shortcut as the keyboard says it: ⌘K on a Mac (and an iPad's keyboard), Ctrl K elsewhere. */
369
+ function findKeys() {
370
+ const platform = typeof navigator === "undefined" ? "" : (navigator.platform ?? "");
371
+ return /Mac|iPhone|iPad|iPod/.test(platform) ? "⌘K" : "Ctrl K";
216
372
  }
217
373
  /** The scene's mark: a plot of the city, seen from above at an angle. */
218
374
  function SceneMark() {
@@ -232,6 +388,9 @@ function FindMark() {
232
388
  }
233
389
  /** A place's mark in the list: the home's house, a kind's plot in its own hue. */
234
390
  function PlaceMark({ place }) {
391
+ // The scene's whole thing: the scene's own mark, as on the switch (FR-144).
392
+ if (place.key === WHOLE_KEY)
393
+ return _jsx(SceneMark, {});
235
394
  if (place.group === "home") {
236
395
  return (_jsx("svg", { width: "13", height: "13", viewBox: "0 0 14 14", "aria-hidden": "true", focusable: "false", children: _jsx("path", { d: "M2 6.5 L7 2 L12 6.5 V12 H2 Z", fill: "none", stroke: "currentColor", strokeWidth: "1.3", strokeLinejoin: "round" }) }));
237
396
  }
@@ -246,44 +405,95 @@ function PlaceMark({ place }) {
246
405
  } }));
247
406
  }
248
407
  const GROUPS = [{ group: "home" }, { group: "lists", heading: "Lists" }, { group: "pictures", heading: "Pictures" }];
408
+ /** The word the places that do not stand on the row fold under (FR-145). */
409
+ const MORE = "More";
410
+ /**
411
+ * ONE PLACE, AS THE BAR OFFERS IT — in the list, or standing on the row: a
412
+ * link where it has an address, a press where the bar decides; marked
413
+ * `aria-current` where the reader is. Whichever way it is drawn it is
414
+ * `app-place-<key>` and says its path (`data-place-path`), once in the bar.
415
+ */
416
+ function PlaceEntry({ place, current, reach, standing, done }) {
417
+ const at = place.key === current;
418
+ const href = reach.href?.(place.path);
419
+ const Entry = (href !== undefined ? "a" : "button");
420
+ return (_jsxs(Entry, { className: standing ? "graview-bar-at" : "graview-bar-item", "data-testid": `app-place-${place.key}`, "data-place-path": place.path, ...(at ? { "aria-current": "page" } : {}), ...(href !== undefined ? { href } : { type: "button" }), onClick: (event) => {
421
+ done();
422
+ if (!reach.go)
423
+ return;
424
+ if (href !== undefined && (event.button !== 0 || event.metaKey || event.ctrlKey || event.shiftKey || event.altKey))
425
+ return;
426
+ event.preventDefault();
427
+ reach.go(place);
428
+ }, children: [standing ? null : (_jsx("span", { className: "graview-bar-mark", children: _jsx(PlaceMark, { place: place }) })), standing && at ? _jsx("span", { "data-testid": "app-place-current", children: place.label }) : _jsx("span", { children: place.label })] }));
429
+ }
430
+ /** The places of a list, grouped — the home (or the whole thing), the Lists, the Pictures — each group with its heading. */
431
+ function PlaceGroups({ places, current, reach, id, done }) {
432
+ return (_jsx(_Fragment, { children: GROUPS.map(({ group, heading }) => {
433
+ const listed = places.filter((place) => place.group === group);
434
+ if (listed.length === 0)
435
+ return null;
436
+ const named = `${id}-${group}`;
437
+ return (_jsxs("div", { role: "group", ...(heading ? { "aria-labelledby": named } : { "aria-label": listed[0].label }), "data-place-group": group, children: [heading ? _jsx("p", { id: named, children: heading }) : null, _jsx("ul", { children: listed.map((place) => (_jsx("li", { children: _jsx(PlaceEntry, { place: place, current: current, reach: reach, standing: false, done: done }) }, place.key))) })] }, group));
438
+ }) }));
439
+ }
249
440
  /**
250
441
  * THE PLACE YOU ARE ON, AND EVERY OTHER (FR-138): one control, the place's
251
442
  * name and a chevron, that opens the app's places — the home first, then
252
443
  * the Lists and the Pictures, each with its mark, a long name wrapped
253
444
  * rather than cut. Every place is two presses away, and the bar is one row
254
- * of one height however many there are.
445
+ * of one height however many there are. On the scene, what is in view and
446
+ * every picture it can show (FR-144).
447
+ *
448
+ * WHERE THEY FIT, THE PLACES THEMSELVES (FR-145): the ones that stand, as
449
+ * words in their order, the one you are on underlined; then "More", which
450
+ * opens the rest, grouped as the one control's list is — no "More" when
451
+ * every place stands. "More" is `app-places-open` and its list
452
+ * `app-places`, as the one control and its list are, so a place that does
453
+ * not stand is reached by the same two presses either way.
255
454
  */
256
- function PlaceControl({ places, current, reach }) {
455
+ function Places({ places, current, reach, scene, stands }) {
257
456
  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
- }) })] }));
457
+ const said = places.find((place) => place.key === current)?.label ?? "Places";
458
+ const rest = stands ? places.filter((_, at) => !stands.includes(at)) : places;
459
+ return (_jsxs("nav", { className: stands ? "graview-bar-places" : "graview-bar-place-at", "aria-label": scene ? "What the scene shows" : "The app’s places", ...(stands ? { "data-testid": "app-places-standing" } : {}), children: [stands?.map((at) => _jsx(PlaceEntry, { place: places[at], current: current, reach: reach, standing: true, done: () => undefined }, places[at].key)), rest.length > 0 ? (_jsxs(_Fragment, { children: [_jsxs("button", { type: "button", className: stands ? "graview-bar-at" : "graview-bar-place", "data-testid": "app-places-open", ...popover.trigger, onClick: popover.toggle, "aria-label": stands ? `${MORE} — ${rest.length} more ${scene ? "pictures" : "places"}` : `${said} — ${scene ? "everything the scene shows" : "every place"}`, title: stands ? rest.map((place) => place.label).join(", ") : said, children: [stands ? (MORE) : (_jsx("span", { className: "graview-bar-place-words", "data-testid": "app-place-current", children: said })), _jsx(Chevron, {})] }), _jsx("div", { ...popover.pane, "data-testid": "app-places", hidden: !popover.open, className: "graview-bar-list", style: POPOVER_STYLE, children: _jsx(PlaceGroups, { places: rest, current: current, reach: reach, id: popover.pane.id, done: () => popover.setOpen(false) }) })] })) : null] }));
460
+ }
461
+ /**
462
+ * WHICH PLACES STAND ON THE ROW, WEIGHED FROM THE ROW AS DRAWN (FR-145):
463
+ * the places' widths read off the ruler (each at its weight as the place
464
+ * you are on, so marking one never moves the others), and the room from
465
+ * where the places begin to where Find ends, less Find at its least, less
466
+ * the switch's words when they are asked for. The same answer whichever way
467
+ * the places are drawn, so the row never flips between the two at one
468
+ * width. Not weighed while Find is in use (`undefined`); none on a phone's
469
+ * bar, when the app's name has wrapped, or where nothing is laid out (null).
470
+ */
471
+ function standsIn(header, current, wordsAsked) {
472
+ const ruler = header.querySelector(".graview-bar-ruler");
473
+ const mid = header.querySelector(".graview-bar-mid");
474
+ if (!ruler || !mid)
475
+ return null;
476
+ const find = header.querySelector(".graview-bar-find");
477
+ if (find?.matches(":focus-within"))
478
+ return undefined;
479
+ if (header.getBoundingClientRect().width < BAR_PHONE)
480
+ return null;
481
+ const name = header.querySelector(".graview-bar-app-name");
482
+ if (name && name.getBoundingClientRect().height > Number.parseFloat(getComputedStyle(name).lineHeight) * 1.5)
483
+ return null;
484
+ const widths = [...ruler.children].map((one) => one.getBoundingClientRect().width);
485
+ const more = widths.pop() ?? 0;
486
+ const faces = header.querySelector(".graview-bar-faces");
487
+ const words = faces && wordsAsked ? wordsOf(faces) : 0;
488
+ const drawnWords = faces?.getAttribute("data-switch-drawn") === "words" ? words : 0;
489
+ const from = mid.getBoundingClientRect().left;
490
+ const end = find?.getBoundingClientRect();
491
+ // To Find's end, less Find at its least and the room between them (the row's gap, less the tools' pull); with no Find, the middle's own room.
492
+ const room = end && end.width > 0 ? end.right - from - (Number.parseFloat(getComputedStyle(find).minWidth) || 0) - 8 : mid.getBoundingClientRect().width;
493
+ return placesThatStand({ widths, current, room: Math.floor(room + drawnWords - words), more })?.join(",") ?? null;
494
+ }
495
+ /** The switch's words, beside each mark: the word, the gap before it, and the button's wider padding. */
496
+ function wordsOf(faces) {
497
+ return [...faces.querySelectorAll(".graview-bar-face span")].reduce((sum, span) => sum + span.scrollWidth + 10, 0);
288
498
  }
289
499
  //# sourceMappingURL=app-bar.js.map