@graview/pages 0.1.0

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 (79) hide show
  1. package/LICENSE +96 -0
  2. package/README.md +47 -0
  3. package/dist/ask.d.ts +21 -0
  4. package/dist/ask.d.ts.map +1 -0
  5. package/dist/ask.js +109 -0
  6. package/dist/ask.js.map +1 -0
  7. package/dist/face-controls.d.ts +68 -0
  8. package/dist/face-controls.d.ts.map +1 -0
  9. package/dist/face-controls.js +231 -0
  10. package/dist/face-controls.js.map +1 -0
  11. package/dist/face-placed.d.ts +16 -0
  12. package/dist/face-placed.d.ts.map +1 -0
  13. package/dist/face-placed.js +34 -0
  14. package/dist/face-placed.js.map +1 -0
  15. package/dist/facts.d.ts +115 -0
  16. package/dist/facts.d.ts.map +1 -0
  17. package/dist/facts.js +180 -0
  18. package/dist/facts.js.map +1 -0
  19. package/dist/form.d.ts +53 -0
  20. package/dist/form.d.ts.map +1 -0
  21. package/dist/form.js +183 -0
  22. package/dist/form.js.map +1 -0
  23. package/dist/index.d.ts +15 -0
  24. package/dist/index.d.ts.map +1 -0
  25. package/dist/index.js +8 -0
  26. package/dist/index.js.map +1 -0
  27. package/dist/page-context.d.ts +74 -0
  28. package/dist/page-context.d.ts.map +1 -0
  29. package/dist/page-context.js +38 -0
  30. package/dist/page-context.js.map +1 -0
  31. package/dist/page-home.d.ts +19 -0
  32. package/dist/page-home.d.ts.map +1 -0
  33. package/dist/page-home.js +67 -0
  34. package/dist/page-home.js.map +1 -0
  35. package/dist/page-list.d.ts +23 -0
  36. package/dist/page-list.d.ts.map +1 -0
  37. package/dist/page-list.js +196 -0
  38. package/dist/page-list.js.map +1 -0
  39. package/dist/page-map.d.ts +20 -0
  40. package/dist/page-map.d.ts.map +1 -0
  41. package/dist/page-map.js +71 -0
  42. package/dist/page-map.js.map +1 -0
  43. package/dist/page-places.d.ts +72 -0
  44. package/dist/page-places.d.ts.map +1 -0
  45. package/dist/page-places.js +304 -0
  46. package/dist/page-places.js.map +1 -0
  47. package/dist/page-problems.d.ts +58 -0
  48. package/dist/page-problems.d.ts.map +1 -0
  49. package/dist/page-problems.js +113 -0
  50. package/dist/page-problems.js.map +1 -0
  51. package/dist/page-record.d.ts +14 -0
  52. package/dist/page-record.d.ts.map +1 -0
  53. package/dist/page-record.js +111 -0
  54. package/dist/page-record.js.map +1 -0
  55. package/dist/page-search.d.ts +50 -0
  56. package/dist/page-search.d.ts.map +1 -0
  57. package/dist/page-search.js +108 -0
  58. package/dist/page-search.js.map +1 -0
  59. package/dist/page-shell.d.ts +43 -0
  60. package/dist/page-shell.d.ts.map +1 -0
  61. package/dist/page-shell.js +171 -0
  62. package/dist/page-shell.js.map +1 -0
  63. package/dist/page-typography.d.ts +54 -0
  64. package/dist/page-typography.d.ts.map +1 -0
  65. package/dist/page-typography.js +166 -0
  66. package/dist/page-typography.js.map +1 -0
  67. package/dist/pages.d.ts +19 -0
  68. package/dist/pages.d.ts.map +1 -0
  69. package/dist/pages.js +19 -0
  70. package/dist/pages.js.map +1 -0
  71. package/dist/registry.d.ts +111 -0
  72. package/dist/registry.d.ts.map +1 -0
  73. package/dist/registry.js +105 -0
  74. package/dist/registry.js.map +1 -0
  75. package/dist/router.d.ts +35 -0
  76. package/dist/router.d.ts.map +1 -0
  77. package/dist/router.js +155 -0
  78. package/dist/router.js.map +1 -0
  79. package/package.json +63 -0
package/LICENSE ADDED
@@ -0,0 +1,96 @@
1
+ Elastic License 2.0
2
+
3
+ ## Acceptance
4
+
5
+ By using the software, you agree to all of the terms and conditions below.
6
+
7
+ ## Copyright License
8
+
9
+ The licensor grants you a non-exclusive, royalty-free, worldwide,
10
+ non-sublicensable, non-transferable license to use, copy, distribute, make
11
+ available, and prepare derivative works of the software, in each case subject
12
+ to the limitations and conditions below.
13
+
14
+ ## Limitations
15
+
16
+ You may not provide the software to third parties as a hosted or managed
17
+ service, where the service provides users with access to any substantial set
18
+ of the features or functionality of the software.
19
+
20
+ You may not move, change, disable, or circumvent the license key
21
+ functionality in the software, and you may not remove or obscure any
22
+ functionality in the software that is protected by the license key.
23
+
24
+ You may not alter, remove, or obscure any licensing, copyright, or other
25
+ notices of the licensor in the software. Any use of the licensor's trademarks
26
+ is subject to applicable law.
27
+
28
+ ## Patents
29
+
30
+ The licensor grants you a license, under any patent claims the licensor can
31
+ license, or becomes able to license, to make, have made, use, sell, offer for
32
+ sale, import and have imported the software, in each case subject to the
33
+ limitations and conditions in this license. This license does not cover any
34
+ patent claims that you cause to be infringed by modifications or additions to
35
+ the software. If you or your company make any written claim that the software
36
+ infringes or contributes to infringement of any patent, your patent license
37
+ for the software granted under these terms ends immediately. If your company
38
+ makes such a claim, your patent license ends immediately for work on behalf
39
+ of your company.
40
+
41
+ ## Notices
42
+
43
+ You must ensure that anyone who gets a copy of any part of the software from
44
+ you also gets a copy of these terms.
45
+
46
+ If you modify the software, you must include in any modified copies of the
47
+ software prominent notices stating that you have modified the software.
48
+
49
+ ## No Other Rights
50
+
51
+ These terms do not imply any licenses other than those expressly granted in
52
+ these terms.
53
+
54
+ ## Termination
55
+
56
+ If you use the software in violation of these terms, such use is not
57
+ licensed, and your licenses will automatically terminate. If the licensor
58
+ provides you with a notice of your violation, and you cease all violation of
59
+ this license no later than 30 days after you receive that notice, your
60
+ licenses will be reinstated retroactively. However, if you violate these
61
+ terms after such reinstatement, any additional violation of these terms will
62
+ cause your licenses to terminate automatically and permanently.
63
+
64
+ ## No Liability
65
+
66
+ *As far as the law allows, the software comes as is, without any warranty or
67
+ condition, and the licensor will not be liable to you for any damages arising
68
+ out of these terms or the use or nature of the software, under any kind of
69
+ legal claim.*
70
+
71
+ ## Definitions
72
+
73
+ The **licensor** is the entity offering these terms, and the **software** is
74
+ the software the licensor makes available under these terms, including any
75
+ portion of it.
76
+
77
+ **you** refers to the individual or entity agreeing to these terms.
78
+
79
+ **your company** is any legal entity, sole proprietorship, or other kind of
80
+ organization that you work for, plus all organizations that have control
81
+ over, are under the control of, or are under common control with that
82
+ organization. **control** means ownership of substantially all the assets of
83
+ an entity, or the power to direct its management and policies by vote,
84
+ contract, or otherwise. Control can be direct or indirect.
85
+
86
+ **your licenses** are all the licenses granted to you for the software under
87
+ these terms.
88
+
89
+ **use** means anything you do with the software requiring one of your
90
+ licenses.
91
+
92
+ **trademark** means trademarks, service marks, and similar rights.
93
+
94
+ ---
95
+
96
+ Copyright 2025-2026 En Dash Consulting
package/README.md ADDED
@@ -0,0 +1,47 @@
1
+ # @graview/pages
2
+
3
+ The traditional face: a routed webapp derived from the same declaration that drives the
4
+ spatial scene. From one `defineApp` — schema, mutations, invariants, permissions, op log —
5
+ this package serves lists, records, forms, problems and history as ordinary linked pages:
6
+ `/` the gallery, `/:plural` per kind, `/:plural/:id` per node, `/places/:as` per picture,
7
+ `/map` the relations, `/problems`.
8
+
9
+ - **One store, no second path.** Pages read through the same `Store` and write through the
10
+ same mutations with the same principal; withheld actions render disabled with their
11
+ refusal. `recordFacts` composes the derivations the scene already uses (labels, readable
12
+ fields, violations touching, derived affordances) — the parity tests hold a record page
13
+ and the spatial detail to identical facts.
14
+ - **The page registry mirrors the view registry.** `(kind × page-type) → component`,
15
+ defaults overridable per cell; the shell, home and problems pages are themselves
16
+ registrations (`registry.surface(...)`).
17
+ - **Forms are derived.** `formFields` (in `@graview/core`) walks a mutation's zod input into
18
+ a control tree — scalars, dates, node pickers over real candidates, nested objects,
19
+ discriminated unions as a type picker plus that arm's fields, arrays as repeatable rows —
20
+ and `DerivedForm` renders it. Nothing renderable is hand-written; anything unrenderable
21
+ says so instead of hiding.
22
+ - **Two faces, one application.** Record pages link to their spatial stop
23
+ (`spatialHref(id)`); the scene links to the pages; the ids are shared. See
24
+ `apps/todo` for both faces mounted from one declaration (`/` scene, `/pages` routed).
25
+ - **Mobile is an acceptance criterion.** `pnpm pages` runs the phone-width harness
26
+ (390×844): no sideways scroll, named links, labelled controls, and a derived form that
27
+ actually applies.
28
+
29
+ - **It lands on a gallery.** The home opens with the standing as its headline — "2 gardeners,
30
+ 3 plots and 1 planting.", or "Nothing here yet." and which act begins it — then every
31
+ picture the app has as a large live card, two across at a desk and one on a phone: the
32
+ lens itself, drawn inert at a scale measured from the card, captioned with its name and how
33
+ much it is over. Handed the scene's view registry (`views` in the context), every titled
34
+ lens is a page at `/places/<as>` and a card on the home; a kind with no titled lens is
35
+ drawn anyway, as a contact sheet of its members, so a new app lands on a gallery on its
36
+ first afternoon. The kinds follow as one row of counts, the relations as one line that
37
+ opens `/map`, and Recently stays short at the foot. The shell is one row of navigation
38
+ that scrolls sideways on a phone; a new address opens at its top.
39
+ - **It reads like the product's own site.** A list opens with the plural and its
40
+ description, a record with its title and its kind's `describe`, controls receding below
41
+ the content. Typography rides the brand's display and body faces at a real scale; the
42
+ brand's mark, name and per-kind accents (`hueFor`, from `@graview/core`) carry through
43
+ every page. Still derivation: nothing here is a per-app template, and an app overrides a
44
+ cell the same way it overrides a view.
45
+
46
+ The host applies `themeCss` from `@graview/primitives` (or supplies its own `--graview-*`
47
+ tokens); the default pages render entirely from those tokens.
package/dist/ask.d.ts ADDED
@@ -0,0 +1,21 @@
1
+ import { type AnySchema } from "@graview/core";
2
+ import type { PageContext } from "./pages.js";
3
+ /**
4
+ * THE ASSISTANT ON EVERY PAGE — the same panel, not a second one.
5
+ *
6
+ * The scene keeps the seat in a rail on the left: a subject, its acts, its
7
+ * relations, the conversation. A routed face that grew its own chat box
8
+ * would be two assistants with two habits over one graph, so this is the
9
+ * companion, in a drawer beside the reading column (a sheet on a phone),
10
+ * opened by one control in the shell.
11
+ *
12
+ * WHAT "THIS" MEANS HERE IS THE ROUTE. On a record it is that record; on a
13
+ * kind's page, that kind; on a picture, the kind it is a picture of. The
14
+ * provider's selection is set from the address, so the same question means
15
+ * the same thing on both faces — and the companion's own subject rule (the
16
+ * selection first) does the rest.
17
+ */
18
+ export declare function PageAsk<S extends AnySchema>({ context }: {
19
+ readonly context: PageContext<S>;
20
+ }): import("react").JSX.Element | null;
21
+ //# sourceMappingURL=ask.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ask.d.ts","sourceRoot":"","sources":["../src/ask.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAW,KAAK,SAAS,EAAE,MAAM,eAAe,CAAC;AAOxD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAE9C;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,OAAO,CAAC,CAAC,SAAS,SAAS,EAAE,EAAE,OAAO,EAAE,EAAE;IAAE,QAAQ,CAAC,OAAO,EAAE,WAAW,CAAC,CAAC,CAAC,CAAA;CAAE,sCAqE7F"}
package/dist/ask.js ADDED
@@ -0,0 +1,109 @@
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
+ import { labelOf } from "@graview/core";
3
+ import { aggregateId, kindCardId } from "@graview/layout";
4
+ import { Companion } from "@graview/primitives";
5
+ import { useGraviewIfAny } from "@graview/react";
6
+ import { useEffect, useRef, useState } from "react";
7
+ import { useLocation, useNavigate } from "react-router-dom";
8
+ import { kindOfSlug, recordPath } from "./registry.js";
9
+ /**
10
+ * THE ASSISTANT ON EVERY PAGE — the same panel, not a second one.
11
+ *
12
+ * The scene keeps the seat in a rail on the left: a subject, its acts, its
13
+ * relations, the conversation. A routed face that grew its own chat box
14
+ * would be two assistants with two habits over one graph, so this is the
15
+ * companion, in a drawer beside the reading column (a sheet on a phone),
16
+ * opened by one control in the shell.
17
+ *
18
+ * WHAT "THIS" MEANS HERE IS THE ROUTE. On a record it is that record; on a
19
+ * kind's page, that kind; on a picture, the kind it is a picture of. The
20
+ * provider's selection is set from the address, so the same question means
21
+ * the same thing on both faces — and the companion's own subject rule (the
22
+ * selection first) does the rest.
23
+ */
24
+ export function PageAsk({ context }) {
25
+ const [open, setOpen] = useState(false);
26
+ const navigate = useNavigate();
27
+ const here = useGraviewIfAny();
28
+ if (!here)
29
+ return null;
30
+ return (_jsxs(_Fragment, { children: [_jsx(RouteSubject, { context: context }), _jsxs("button", { type: "button", "data-testid": "page-ask", "aria-expanded": open, onClick: () => setOpen((current) => !current), title: "Talk to the seat about this page", style: {
31
+ position: "fixed",
32
+ left: 16,
33
+ bottom: 16,
34
+ zIndex: 30,
35
+ display: "inline-flex",
36
+ alignItems: "center",
37
+ gap: 6,
38
+ minHeight: 40,
39
+ padding: "0 14px",
40
+ borderRadius: 999,
41
+ border: "1px solid var(--graview-edge)",
42
+ background: "var(--graview-float)",
43
+ color: "var(--graview-ink)",
44
+ font: "inherit",
45
+ fontSize: "0.9375rem",
46
+ boxShadow: "var(--graview-lift-low)",
47
+ cursor: "pointer",
48
+ }, children: [_jsx("span", { "aria-hidden": "true", children: "\u25C6" }), open ? "Close" : "Ask"] }), open ? (_jsx("div", { "data-testid": "page-ask-drawer",
49
+ /*
50
+ * The scene's companion positions itself inside the scene's box;
51
+ * here that box is this drawer, pinned beside the reading column
52
+ * on a desk and along the bottom on a phone.
53
+ */
54
+ style: {
55
+ position: "fixed",
56
+ left: 0,
57
+ top: 0,
58
+ bottom: 0,
59
+ width: "min(320px, 100vw)",
60
+ zIndex: 29,
61
+ pointerEvents: "none",
62
+ }, children: _jsx("div", { style: { position: "absolute", inset: "12px 12px 64px 12px", pointerEvents: "auto", display: "grid" }, children: _jsx(Companion, { framed: true, onPick: (id) => {
63
+ const node = context.store.graph.getNode(id);
64
+ if (node)
65
+ navigate(recordPath(context.store.schema, node.kind, id));
66
+ } }) }) })) : null] }));
67
+ }
68
+ /** The page's own subject, put where every surface reads it: the selection. */
69
+ function RouteSubject({ context }) {
70
+ const { store } = context;
71
+ const here = useGraviewIfAny();
72
+ const { pathname } = useLocation();
73
+ /*
74
+ * HELD IN A REF, because the setter is not stable: it closes over the
75
+ * current view, so a new one arrives on every render — and an effect
76
+ * that depended on it set the selection, re-rendered the provider, got a
77
+ * new setter and set the selection again, nine hundred times a second.
78
+ * The address is the only thing that decides the subject.
79
+ */
80
+ const set = useRef(here?.setSelection);
81
+ set.current = here?.setSelection;
82
+ /*
83
+ * THE ADDRESS, ONCE PER ADDRESS. `useParams()` hands back a fresh object
84
+ * every render, so putting it in the dependencies made this run on every
85
+ * render, set the selection, and render again — a loop that took the
86
+ * whole page down with it the moment the drawer opened. The path is the
87
+ * only thing that decides the subject, so the path is the dependency.
88
+ */
89
+ useEffect(() => {
90
+ const setSelection = set.current;
91
+ if (!setSelection)
92
+ return;
93
+ const [, slug, id] = pathname.split("/");
94
+ const chosen = (() => {
95
+ if (slug === "places") {
96
+ const place = context.views?.places().find((one) => one.as === decodeURIComponent(id ?? ""));
97
+ return place ? [aggregateId(place.kind)] : [];
98
+ }
99
+ const kind = slug ? kindOfSlug(store.schema, slug) : undefined;
100
+ if (kind && id)
101
+ return [decodeURIComponent(id)];
102
+ return kind ? [kindCardId(kind)] : [];
103
+ })();
104
+ setSelection((current) => current.length === chosen.length && current.every((one, at) => one === chosen[at]) ? current : chosen);
105
+ }, [pathname]);
106
+ void labelOf;
107
+ return null;
108
+ }
109
+ //# sourceMappingURL=ask.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ask.js","sourceRoot":"","sources":["../src/ask.tsx"],"names":[],"mappings":"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"}
@@ -0,0 +1,68 @@
1
+ import type { AnySchema, Principal, Store } from "@graview/core";
2
+ import { type ReactNode, type RefObject } from "react";
3
+ import type { PageContext } from "./page-context.js";
4
+ import { type PageRegistry } from "./registry.js";
5
+ /**
6
+ * WHAT THE ROUTED FACE OFFERS WHICHEVER SHELL DRAWS IT.
7
+ *
8
+ * Finding a record by part of its name and taking the last change back are
9
+ * core jobs, not chrome a design may happen to remember. The Find box lived
10
+ * in the derived shell only, so an app that replaced the shell — every
11
+ * example does — had Find exactly when its author thought to put
12
+ * `<PageFind>` in, and rota had none. Undo lived nowhere on this face at
13
+ * all: the scene's Activity rail takes a turn back, and on the pages a
14
+ * person who renamed the wrong record could not.
15
+ *
16
+ * So the face's root owns both. A shell that places `<PageFind>` or
17
+ * `<PageUndo>` itself says where they go, and the root sees them placed and
18
+ * draws nothing more; a shell that places neither still gets them — Find in
19
+ * a bar above it, the way back in a dock at the corner. Only a registry that
20
+ * says so (`surface("shell", Shell, { without: ["find"] })`) goes without.
21
+ */
22
+ export type { FaceControl } from "./face-placed.js";
23
+ export interface LastChange {
24
+ readonly batch: string;
25
+ /** What it did, in the store's own words — the line the Activity rail shows. */
26
+ readonly intent: string;
27
+ }
28
+ /**
29
+ * The change this person can take back: their own latest turn that is not
30
+ * already taken back, is not itself a take-back, that the log lets go of on
31
+ * its own, and that the policy lets them take back — undo is judged like a
32
+ * change ("what you may undo is what you may have done"), so a turn the
33
+ * store would refuse is never offered.
34
+ *
35
+ * "Their own" is the rule the Activity rail uses for "you": a human author
36
+ * who is this principal, or either side has no id to tell them apart. On a
37
+ * served store two seats share one log, and one person's corner should not
38
+ * offer to take back another's work.
39
+ */
40
+ export declare function lastChangeOf<S extends AnySchema>(store: Store<S>, principal?: Principal): LastChange | undefined;
41
+ /** "Take back “Rename to …”" — the words on the control and in the announcement. */
42
+ export declare const takeBackWords: (change: LastChange) => string;
43
+ /**
44
+ * THE WAY BACK, on the routed face: one control that says what it takes
45
+ * back — "Take back “Rename to …”" — and takes it back as the person at the
46
+ * keyboard, judged by the policy like any change.
47
+ *
48
+ * Exported for a shell that wants it somewhere of its own; a shell that
49
+ * does not place it gets it anyway, docked at the corner of the face.
50
+ */
51
+ export declare function PageUndo<S extends AnySchema>({ context, docked, }: {
52
+ readonly context: PageContext<S>;
53
+ /** Held at the corner of the face rather than in a shell's own row. Set by the face's root. */
54
+ readonly docked?: boolean;
55
+ }): import("react").JSX.Element;
56
+ /**
57
+ * The face's root: it knows which of the face's controls a shell placed,
58
+ * and draws the rest — Find in a bar above the shell, the way back docked
59
+ * at the corner — before the shell in the document, so the keyboard reaches
60
+ * them first.
61
+ */
62
+ export declare function FaceControlsRoot<S extends AnySchema>({ context, registry, root, children, }: {
63
+ readonly context: PageContext<S>;
64
+ readonly registry: PageRegistry<S, unknown> | undefined;
65
+ readonly root: RefObject<HTMLElement | null>;
66
+ readonly children: ReactNode;
67
+ }): import("react").JSX.Element;
68
+ //# sourceMappingURL=face-controls.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"face-controls.d.ts","sourceRoot":"","sources":["../src/face-controls.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,SAAS,EAAE,KAAK,EAAE,MAAM,eAAe,CAAC;AACjE,OAAO,EAA8D,KAAK,SAAS,EAAE,KAAK,SAAS,EAAE,MAAM,OAAO,CAAC;AAEnH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAErD,OAAO,EAAc,KAAK,YAAY,EAAE,MAAM,eAAe,CAAC;AAI9D;;;;;;;;;;;;;;;;GAgBG;AACH,YAAY,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAMpD,MAAM,WAAW,UAAU;IACzB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,gFAAgF;IAChF,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;CACzB;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,YAAY,CAAC,CAAC,SAAS,SAAS,EAAE,KAAK,EAAE,KAAK,CAAC,CAAC,CAAC,EAAE,SAAS,CAAC,EAAE,SAAS,GAAG,UAAU,GAAG,SAAS,CAgBhH;AAED,oFAAoF;AACpF,eAAO,MAAM,aAAa,GAAI,QAAQ,UAAU,WAAmC,CAAC;AAwCpF;;;;;;;GAOG;AACH,wBAAgB,QAAQ,CAAC,CAAC,SAAS,SAAS,EAAE,EAC5C,OAAO,EACP,MAAc,GACf,EAAE;IACD,QAAQ,CAAC,OAAO,EAAE,WAAW,CAAC,CAAC,CAAC,CAAC;IACjC,+FAA+F;IAC/F,QAAQ,CAAC,MAAM,CAAC,EAAE,OAAO,CAAC;CAC3B,+BAGA;AAmID;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAAC,CAAC,SAAS,SAAS,EAAE,EACpD,OAAO,EACP,QAAQ,EACR,IAAI,EACJ,QAAQ,GACT,EAAE;IACD,QAAQ,CAAC,OAAO,EAAE,WAAW,CAAC,CAAC,CAAC,CAAC;IACjC,QAAQ,CAAC,QAAQ,EAAE,YAAY,CAAC,CAAC,EAAE,OAAO,CAAC,GAAG,SAAS,CAAC;IACxD,QAAQ,CAAC,IAAI,EAAE,SAAS,CAAC,WAAW,GAAG,IAAI,CAAC,CAAC;IAC7C,QAAQ,CAAC,QAAQ,EAAE,SAAS,CAAC;CAC9B,+BAUA"}
@@ -0,0 +1,231 @@
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
+ import { useCallback, useEffect, useLayoutEffect, useMemo, useState } from "react";
3
+ import { useLocation } from "react-router-dom";
4
+ import { useStoreTick } from "./page-context.js";
5
+ import { kindOfSlug } from "./registry.js";
6
+ import { PageFind } from "./page-shell.js";
7
+ import { createPlaced, FaceControls, usePlaced, usePlacedOnTheFace } from "./face-placed.js";
8
+ /**
9
+ * The change this person can take back: their own latest turn that is not
10
+ * already taken back, is not itself a take-back, that the log lets go of on
11
+ * its own, and that the policy lets them take back — undo is judged like a
12
+ * change ("what you may undo is what you may have done"), so a turn the
13
+ * store would refuse is never offered.
14
+ *
15
+ * "Their own" is the rule the Activity rail uses for "you": a human author
16
+ * who is this principal, or either side has no id to tell them apart. On a
17
+ * served store two seats share one log, and one person's corner should not
18
+ * offer to take back another's work.
19
+ */
20
+ export function lastChangeOf(store, principal) {
21
+ const batches = store.batches();
22
+ for (let at = batches.length - 1; at >= 0; at--) {
23
+ const batch = batches[at];
24
+ if (batch.undone || batch.ops.length === 0)
25
+ continue;
26
+ if (batch.ops.every((op) => op.undoes !== undefined))
27
+ continue;
28
+ const author = batch.author;
29
+ const mine = author.kind === "human" && (principal?.id === undefined || author.id === undefined || author.id === principal.id);
30
+ if (!mine)
31
+ continue;
32
+ const check = store.canUndo(batch.id);
33
+ if (!check.ok)
34
+ continue;
35
+ const permitted = check.ops.every((op) => !op.mutation || store.permits(op.mutation, principal).ok);
36
+ if (!permitted)
37
+ continue;
38
+ return { batch: batch.id, intent: batch.intent };
39
+ }
40
+ return undefined;
41
+ }
42
+ /** "Take back “Rename to …”" — the words on the control and in the announcement. */
43
+ export const takeBackWords = (change) => `Take back “${change.intent}”`;
44
+ const editable = (target) => target instanceof HTMLElement &&
45
+ (target.isContentEditable || target.matches("input, textarea, select, [contenteditable]:not([contenteditable='false'])"));
46
+ /** Where the keyboard goes when the control it pressed is gone: the page's heading, where the change shows. */
47
+ function landOnThePage(face) {
48
+ const active = document.activeElement;
49
+ if (active !== null && active !== document.body && active !== document.documentElement && active.isConnected)
50
+ return;
51
+ const root = face ?? document;
52
+ const main = root.querySelector("main") ?? root.querySelector("[data-graview-page]") ?? null;
53
+ const target = main?.querySelector("h1") ?? root.querySelector("h1") ?? main;
54
+ if (!(target instanceof HTMLElement))
55
+ return;
56
+ if (!target.hasAttribute("tabindex"))
57
+ target.setAttribute("tabindex", "-1");
58
+ target.focus({ preventScroll: true });
59
+ }
60
+ function useTakeBack(context) {
61
+ const { store, principal } = context;
62
+ const tick = useStoreTick(store);
63
+ // eslint-disable-next-line react-hooks/exhaustive-deps
64
+ const change = useMemo(() => lastChangeOf(store, principal), [store, principal, tick]);
65
+ const [refused, setRefused] = useState(null);
66
+ const [said, setSaid] = useState("");
67
+ const takeBack = useCallback(() => {
68
+ const now = lastChangeOf(store, principal);
69
+ if (!now)
70
+ return false;
71
+ setRefused(null);
72
+ try {
73
+ store.undo(now.batch, principal ? { author: principal } : {});
74
+ setSaid(`Took back “${now.intent}”`);
75
+ }
76
+ catch (error) {
77
+ setRefused(error instanceof Error ? error.message : String(error));
78
+ }
79
+ return true;
80
+ }, [store, principal]);
81
+ return { change, takeBack, refused, said };
82
+ }
83
+ /**
84
+ * THE WAY BACK, on the routed face: one control that says what it takes
85
+ * back — "Take back “Rename to …”" — and takes it back as the person at the
86
+ * keyboard, judged by the policy like any change.
87
+ *
88
+ * Exported for a shell that wants it somewhere of its own; a shell that
89
+ * does not place it gets it anyway, docked at the corner of the face.
90
+ */
91
+ export function PageUndo({ context, docked = false, }) {
92
+ usePlacedOnTheFace("undo");
93
+ return _jsx(TakeBack, { context: context, docked: docked });
94
+ }
95
+ function TakeBack({ context, docked }) {
96
+ const { change, takeBack, refused, said } = useTakeBack(context);
97
+ const corner = docked
98
+ ? context.embedded
99
+ ? { position: "sticky", bottom: 12, marginLeft: "auto", marginRight: 12, width: "fit-content" }
100
+ : { position: "fixed", right: 16, bottom: 16, zIndex: 30 }
101
+ : {};
102
+ return (_jsxs("div", { "data-testid": docked ? "face-undo-dock" : "page-undo-place", style: {
103
+ ...corner,
104
+ display: "grid",
105
+ justifyItems: "end",
106
+ gap: 4,
107
+ // Clear of the Ask control at the other corner on a phone.
108
+ maxWidth: docked ? "min(30rem, calc(100vw - 140px))" : "100%",
109
+ minWidth: 0,
110
+ }, children: [_jsx("span", { role: "status", style: visuallyHidden, children: said }), change ? (_jsxs("button", { type: "button", "data-testid": "page-undo", title: `${takeBackWords(change)} — ⌘Z or Ctrl+Z`, "aria-keyshortcuts": "Meta+Z Control+Z", onClick: (event) => {
111
+ const face = event.currentTarget.closest("[data-graview-face]");
112
+ takeBack();
113
+ // After the face has drawn the change gone: if this control went with it, the keyboard lands on the page.
114
+ requestAnimationFrame(() => landOnThePage(face));
115
+ }, style: {
116
+ display: "inline-flex",
117
+ alignItems: "center",
118
+ gap: 6,
119
+ minHeight: 36,
120
+ maxWidth: "100%",
121
+ minWidth: 0,
122
+ padding: "0 14px",
123
+ borderRadius: 999,
124
+ border: "1px solid var(--graview-edge)",
125
+ background: "var(--graview-float)",
126
+ color: "var(--graview-ink)",
127
+ font: "inherit",
128
+ fontSize: "0.875rem",
129
+ boxShadow: docked ? "var(--graview-lift-low)" : undefined,
130
+ cursor: "pointer",
131
+ }, children: [_jsx("span", { "aria-hidden": "true", children: "\u21B6" }), _jsx("span", { style: { minWidth: 0, overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }, children: takeBackWords(change) })] })) : null, refused ? (_jsx("span", { "data-testid": "page-undo-refused", role: "alert", style: {
132
+ fontSize: "0.8125rem",
133
+ lineHeight: 1.4,
134
+ color: "var(--graview-warn)",
135
+ background: docked ? "var(--graview-float)" : undefined,
136
+ padding: docked ? "4px 8px" : undefined,
137
+ borderRadius: 6,
138
+ }, children: refused })) : null] }));
139
+ }
140
+ const visuallyHidden = {
141
+ position: "absolute",
142
+ width: 1,
143
+ height: 1,
144
+ margin: -1,
145
+ padding: 0,
146
+ overflow: "hidden",
147
+ clip: "rect(0 0 0 0)",
148
+ whiteSpace: "nowrap",
149
+ border: 0,
150
+ };
151
+ /* ------------------------------------------------------------------ */
152
+ /* The root */
153
+ /* ------------------------------------------------------------------ */
154
+ /**
155
+ * ⌘Z AND CTRL+Z, on the face — not in a text field, where they are the
156
+ * field's own undo and a person typing a name expects the letters back, not
157
+ * the last change. Heard on the document while the keyboard is in the face,
158
+ * or on nothing at all when the face owns the document; never while it is
159
+ * on the host page around an embed.
160
+ */
161
+ function useTakeBackKeys(context, root, on) {
162
+ const { store, principal, embedded } = context;
163
+ useEffect(() => {
164
+ if (!on)
165
+ return;
166
+ const key = (event) => {
167
+ if (event.defaultPrevented || event.altKey || event.shiftKey)
168
+ return;
169
+ if (!(event.metaKey || event.ctrlKey) || event.key.toLowerCase() !== "z")
170
+ return;
171
+ if (editable(event.target))
172
+ return;
173
+ const target = event.target;
174
+ const inFace = target instanceof Node && root.current?.contains(target);
175
+ const onNothing = target === document.body || target === document.documentElement || target === document;
176
+ if (!inFace && !(onNothing && !embedded))
177
+ return;
178
+ const change = lastChangeOf(store, principal);
179
+ if (!change)
180
+ return;
181
+ event.preventDefault();
182
+ try {
183
+ store.undo(change.batch, principal ? { author: principal } : {});
184
+ }
185
+ catch {
186
+ // The control says a refusal where it was asked; a key has nowhere to say it.
187
+ }
188
+ };
189
+ document.addEventListener("keydown", key);
190
+ return () => document.removeEventListener("keydown", key);
191
+ }, [store, principal, embedded, root, on]);
192
+ }
193
+ /**
194
+ * The face's root: it knows which of the face's controls a shell placed,
195
+ * and draws the rest — Find in a bar above the shell, the way back docked
196
+ * at the corner — before the shell in the document, so the keyboard reaches
197
+ * them first.
198
+ */
199
+ export function FaceControlsRoot({ context, registry, root, children, }) {
200
+ const placed = useMemo(createPlaced, []);
201
+ const without = registry?.without() ?? new Set();
202
+ useTakeBackKeys(context, root, !without.has("undo"));
203
+ return (_jsxs(FaceControls.Provider, { value: placed, children: [_jsx(Fallbacks, { context: context, registry: registry, placed: placed, without: without }), children] }));
204
+ }
205
+ function Fallbacks({ context, registry, placed, without, }) {
206
+ // Drawn once the shell has had its say: its controls are placed in the same commit, before paint.
207
+ const [ready, setReady] = useState(false);
208
+ useLayoutEffect(() => setReady(true), []);
209
+ const findPlaced = usePlaced(placed, "find");
210
+ const undoPlaced = usePlaced(placed, "undo");
211
+ const location = useLocation();
212
+ if (!ready)
213
+ return null;
214
+ const segments = location.pathname.split("/").filter(Boolean);
215
+ const listKind = segments.length === 1 ? kindOfSlug(context.store.schema, segments[0]) : undefined;
216
+ // `?q=` narrows a list the face derived; a list of the app's own has its own words for that.
217
+ const narrows = listKind !== undefined && registry?.lookup(listKind, "list") === undefined;
218
+ return (_jsxs(_Fragment, { children: [!findPlaced && !without.has("find") ? (_jsx("div", { "data-testid": "face-find-bar", style: {
219
+ display: "flex",
220
+ alignItems: "center",
221
+ padding: "8px 16px",
222
+ borderBottom: "1px solid var(--graview-edge)",
223
+ background: "var(--graview-bar)",
224
+ minWidth: 0,
225
+ }, children: _jsx(FindInBar, { context: context, narrowsLists: narrows }) })) : null, !undoPlaced && !without.has("undo") ? _jsx(TakeBack, { context: context, docked: true }) : null] }));
226
+ }
227
+ /** The bar's Find box: `PageFind` without counting as one the shell placed. */
228
+ function FindInBar({ context, narrowsLists }) {
229
+ return (_jsx(FaceControls.Provider, { value: null, children: _jsx(PageFind, { context: context, narrowsLists: narrowsLists }) }));
230
+ }
231
+ //# sourceMappingURL=face-controls.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"face-controls.js","sourceRoot":"","sources":["../src/face-controls.tsx"],"names":[],"mappings":"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"}
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Which of the face's own controls a shell placed itself — so the face's
3
+ * root (face-controls.tsx) draws only the ones it did not.
4
+ */
5
+ export type FaceControl = "find" | "undo";
6
+ export interface Placed {
7
+ readonly count: (control: FaceControl) => number;
8
+ readonly place: (control: FaceControl) => () => void;
9
+ readonly subscribe: (listener: () => void) => () => void;
10
+ }
11
+ export declare const FaceControls: import("react").Context<Placed | null>;
12
+ export declare function createPlaced(): Placed;
13
+ /** A control a shell drew itself: the face's root then draws no second one. */
14
+ export declare function usePlacedOnTheFace(control: FaceControl): void;
15
+ export declare function usePlaced(placed: Placed, control: FaceControl): boolean;
16
+ //# sourceMappingURL=face-placed.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"face-placed.d.ts","sourceRoot":"","sources":["../src/face-placed.ts"],"names":[],"mappings":"AAEA;;;GAGG;AACH,MAAM,MAAM,WAAW,GAAG,MAAM,GAAG,MAAM,CAAC;AAE1C,MAAM,WAAW,MAAM;IACrB,QAAQ,CAAC,KAAK,EAAE,CAAC,OAAO,EAAE,WAAW,KAAK,MAAM,CAAC;IACjD,QAAQ,CAAC,KAAK,EAAE,CAAC,OAAO,EAAE,WAAW,KAAK,MAAM,IAAI,CAAC;IACrD,QAAQ,CAAC,SAAS,EAAE,CAAC,QAAQ,EAAE,MAAM,IAAI,KAAK,MAAM,IAAI,CAAC;CAC1D;AAED,eAAO,MAAM,YAAY,wCAAqC,CAAC;AAE/D,wBAAgB,YAAY,IAAI,MAAM,CAqBrC;AAED,+EAA+E;AAC/E,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,WAAW,GAAG,IAAI,CAG7D;AAED,wBAAgB,SAAS,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,WAAW,GAAG,OAAO,CAMvE"}