@immediately-run/sdk 0.68.2 → 0.69.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.
- package/dist/dialog.cjs +93 -0
- package/dist/dialog.cjs.map +1 -0
- package/dist/dialog.d.cts +47 -0
- package/dist/dialog.d.ts +47 -0
- package/dist/dialog.js +68 -0
- package/dist/dialog.js.map +1 -0
- package/dist/generated/spaces.cjs +89 -17
- package/dist/generated/spaces.cjs.map +1 -1
- package/dist/generated/spaces.d.cts +184 -12
- package/dist/generated/spaces.d.ts +184 -12
- package/dist/generated/spaces.js +84 -17
- package/dist/generated/spaces.js.map +1 -1
- package/dist/index.cjs +4 -0
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +4 -2
- package/dist/index.d.ts +4 -2
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/mounts.cjs +5 -16
- package/dist/mounts.cjs.map +1 -1
- package/dist/mounts.d.cts +3 -35
- package/dist/mounts.d.ts +3 -35
- package/dist/mounts.js +6 -12
- package/dist/mounts.js.map +1 -1
- package/dist/status.cjs +48 -0
- package/dist/status.cjs.map +1 -0
- package/dist/status.d.cts +43 -0
- package/dist/status.d.ts +43 -0
- package/dist/status.js +24 -0
- package/dist/status.js.map +1 -0
- package/dist/version.cjs +1 -1
- package/dist/version.cjs.map +1 -1
- package/dist/version.d.cts +1 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/dist/version.js.map +1 -1
- package/package.json +5 -4
package/dist/dialog.cjs
ADDED
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
var dialog_exports = {};
|
|
20
|
+
__export(dialog_exports, {
|
|
21
|
+
Dialog: () => Dialog,
|
|
22
|
+
useDialogDismiss: () => useDialogDismiss,
|
|
23
|
+
useDialogFocus: () => useDialogFocus
|
|
24
|
+
});
|
|
25
|
+
module.exports = __toCommonJS(dialog_exports);
|
|
26
|
+
var import_jsx_runtime = require("react/jsx-runtime");
|
|
27
|
+
var import_react = require("react");
|
|
28
|
+
const FOCUSABLE = 'button:not([disabled]), [href], input, select, textarea, iframe, [tabindex]:not([tabindex="-1"])';
|
|
29
|
+
const stack = [];
|
|
30
|
+
const onDocumentKeyDown = (e) => {
|
|
31
|
+
if (e.key !== "Escape") return;
|
|
32
|
+
const top = stack[stack.length - 1];
|
|
33
|
+
if (!top) return;
|
|
34
|
+
e.stopPropagation();
|
|
35
|
+
top.onDismiss();
|
|
36
|
+
};
|
|
37
|
+
function useDialogDismiss(onDismiss, { enabled = true } = {}) {
|
|
38
|
+
const ref = (0, import_react.useRef)(onDismiss);
|
|
39
|
+
ref.current = onDismiss;
|
|
40
|
+
(0, import_react.useEffect)(() => {
|
|
41
|
+
if (!enabled) return;
|
|
42
|
+
const entry = { onDismiss: () => ref.current() };
|
|
43
|
+
stack.push(entry);
|
|
44
|
+
if (stack.length === 1) document.addEventListener("keydown", onDocumentKeyDown, true);
|
|
45
|
+
return () => {
|
|
46
|
+
const i = stack.indexOf(entry);
|
|
47
|
+
if (i !== -1) stack.splice(i, 1);
|
|
48
|
+
if (stack.length === 0) document.removeEventListener("keydown", onDocumentKeyDown, true);
|
|
49
|
+
};
|
|
50
|
+
}, [enabled]);
|
|
51
|
+
}
|
|
52
|
+
function useDialogFocus(ref, { enabled = true } = {}) {
|
|
53
|
+
(0, import_react.useEffect)(() => {
|
|
54
|
+
const node = ref.current;
|
|
55
|
+
if (!enabled || !node) return;
|
|
56
|
+
const invoker = document.activeElement;
|
|
57
|
+
const first = node.querySelector(FOCUSABLE) ?? node;
|
|
58
|
+
first.focus();
|
|
59
|
+
const onKeyDown = (e) => {
|
|
60
|
+
if (e.key !== "Tab") return;
|
|
61
|
+
const list = Array.from(node.querySelectorAll(FOCUSABLE));
|
|
62
|
+
if (list.length === 0) return;
|
|
63
|
+
const firstEl = list[0];
|
|
64
|
+
const lastEl = list[list.length - 1];
|
|
65
|
+
const active = document.activeElement;
|
|
66
|
+
if (e.shiftKey && active === firstEl) {
|
|
67
|
+
e.preventDefault();
|
|
68
|
+
lastEl.focus();
|
|
69
|
+
} else if (!e.shiftKey && active === lastEl) {
|
|
70
|
+
e.preventDefault();
|
|
71
|
+
firstEl.focus();
|
|
72
|
+
}
|
|
73
|
+
};
|
|
74
|
+
node.addEventListener("keydown", onKeyDown);
|
|
75
|
+
return () => {
|
|
76
|
+
node.removeEventListener("keydown", onKeyDown);
|
|
77
|
+
invoker?.focus?.();
|
|
78
|
+
};
|
|
79
|
+
}, [enabled, ref]);
|
|
80
|
+
}
|
|
81
|
+
function Dialog({ children, onDismiss, className, style, ...aria }) {
|
|
82
|
+
const ref = (0, import_react.useRef)(null);
|
|
83
|
+
useDialogDismiss(onDismiss);
|
|
84
|
+
useDialogFocus(ref);
|
|
85
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { ref, role: "dialog", "aria-modal": "true", className, style, ...aria, children });
|
|
86
|
+
}
|
|
87
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
88
|
+
0 && (module.exports = {
|
|
89
|
+
Dialog,
|
|
90
|
+
useDialogDismiss,
|
|
91
|
+
useDialogFocus
|
|
92
|
+
});
|
|
93
|
+
//# sourceMappingURL=dialog.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/dialog.tsx"],"sourcesContent":["// In-app dialog primitives (interaction_standards R-IX-1; the dialog half of the\n// standards' delivery vehicle, R3-613).\n//\n// The APG dialog contract as imports: Escape dismisses the TOPMOST open dialog,\n// focus moves into the dialog on open and back to the invoker on close, and Tab\n// wraps inside it. Generalized from the host's own pair (site-main\n// `useHostDialogDismiss`/`useHostDialogFocus`, R3-592) — the same decisions,\n// renamed for an app context; site-main holds the two to identical behaviour\n// with a parity test, so \"generalized\" is a fact and an app can swap between\n// the primitives without re-litigating the contract.\n//\n// TRUST BOUNDARY (same rule as `./loading`): these render INSIDE the app's\n// iframe, under the app's principal — presentational only, NO capability, NO\n// host round-trip, no reserved landmark or host wordmark. Ordinary app a11y,\n// never trusted-chrome framing; adds nothing to a grant set.\n//\n// The dismiss listener is a capture-phase listener on the app's OWN document,\n// installed while the stack is non-empty. Capture is what makes the contract\n// hold on both sides: a keystroke the app dialog claims (Escape → stopPropagation)\n// never reaches an app-level bubble handler behind the dialog — and the host's\n// top-document listener lives in another document entirely, so neither side's\n// dismissal can suppress the other's, with no protocol between them.\nimport { useEffect, useRef, type CSSProperties, type ReactNode } from 'react';\n\n/** The focusable-selector constant. Deliberately NOT exported from `index.ts`:\n * it is an implementation detail of the trap, not author surface. Includes\n * `iframe` because a dialog whose body hosts a frame must let Tab reach it. */\nconst FOCUSABLE = 'button:not([disabled]), [href], input, select, textarea, iframe, [tabindex]:not([tabindex=\"-1\"])';\n\ntype Entry = { onDismiss: () => void };\n\n// A module-level stack is the whole registry: the ONE capture-phase document\n// listener is installed when the first dialog mounts and removed when the last\n// unmounts. Reading `stack` fresh on each keypress makes removal-of-a-non-top-\n// entry safe.\nconst stack: Entry[] = [];\n\nconst onDocumentKeyDown = (e: KeyboardEvent): void => {\n if (e.key !== 'Escape') return;\n const top = stack[stack.length - 1];\n if (!top) return;\n e.stopPropagation();\n top.onDismiss();\n};\n\n/**\n * Register `onDismiss` as this dialog's Escape handler. The topmost mounted\n * dialog wins: only its handler runs, and only on Escape.\n *\n * `onDismiss` is read through a ref so a new inline lambda on each render (the\n * near-universal shape of an `onClose` prop) does NOT re-register the dialog —\n * the entry is torn down and re-added only when `enabled` changes.\n */\nexport function useDialogDismiss(onDismiss: () => void, { enabled = true }: { enabled?: boolean } = {}): void {\n const ref = useRef(onDismiss);\n ref.current = onDismiss;\n\n useEffect(() => {\n if (!enabled) return;\n const entry: Entry = { onDismiss: () => ref.current() };\n stack.push(entry);\n if (stack.length === 1) document.addEventListener('keydown', onDocumentKeyDown, true);\n return () => {\n // Unmounting removes THIS dialog's entry, which may not be the top — a\n // dialog closed out of stack order must not leak its entry. Splice by\n // identity, never by \"pop\".\n const i = stack.indexOf(entry);\n if (i !== -1) stack.splice(i, 1);\n if (stack.length === 0) document.removeEventListener('keydown', onDocumentKeyDown, true);\n };\n }, [enabled]);\n}\n\n/**\n * Manage focus for one dialog rooted at `ref`: on open, record the invoking\n * element and focus the first focusable descendant (falling back to `ref`\n * itself — give the root `tabIndex={-1}` for that case); on close, return\n * focus to the invoker (guarded — it may itself have unmounted); Tab /\n * Shift-Tab wrap at the ends of the focusable list, read at keydown time\n * rather than cached on mount (dialog contents change).\n */\nexport function useDialogFocus(\n ref: { current: HTMLElement | null },\n { enabled = true }: { enabled?: boolean } = {},\n): void {\n useEffect(() => {\n const node = ref.current;\n if (!enabled || !node) return;\n\n // Record the invoker BEFORE stealing focus — the element that opened the dialog.\n const invoker = document.activeElement as HTMLElement | null;\n const first = node.querySelector<HTMLElement>(FOCUSABLE) ?? node;\n first.focus();\n\n const onKeyDown = (e: KeyboardEvent): void => {\n if (e.key !== 'Tab') return;\n const list = Array.from(node.querySelectorAll<HTMLElement>(FOCUSABLE));\n if (list.length === 0) return;\n const firstEl = list[0];\n const lastEl = list[list.length - 1];\n const active = document.activeElement;\n if (e.shiftKey && active === firstEl) {\n e.preventDefault();\n lastEl.focus();\n } else if (!e.shiftKey && active === lastEl) {\n e.preventDefault();\n firstEl.focus();\n }\n };\n node.addEventListener('keydown', onKeyDown);\n\n return () => {\n node.removeEventListener('keydown', onKeyDown);\n // The invoker may itself have unmounted by the time the dialog closes; the\n // optional calls are why this stays safe (and why it can never throw on teardown).\n invoker?.focus?.();\n };\n }, [enabled, ref]);\n}\n\n/** Props for {@link Dialog}. Styling is the app's — the primitive is structural. */\nexport interface DialogProps {\n children: ReactNode;\n /** Called on Escape (the topmost dialog wins). Click-outside/scrim handling is\n * the app's: the scrim is app chrome, and its click handler calls the same\n * `onDismiss`. */\n onDismiss: () => void;\n /** The accessible name — a dialog without one is unnamed to AT (4.1.2). */\n 'aria-label'?: string;\n className?: string;\n style?: CSSProperties;\n}\n\n/**\n * A dialog with the contract wired: `role=\"dialog\" aria-modal=\"true\"`, Escape\n * dismiss (topmost wins), focus in on open / back to the invoker on close, Tab\n * trapped inside. Render it when the dialog is open, with the invoker still\n * focused — the invoker is recorded at mount.\n */\nexport function Dialog({ children, onDismiss, className, style, ...aria }: DialogProps): ReactNode {\n const ref = useRef<HTMLDivElement>(null);\n useDialogDismiss(onDismiss);\n useDialogFocus(ref);\n return (\n <div ref={ref} role=\"dialog\" aria-modal=\"true\" className={className} style={style} {...aria}>\n {children}\n </div>\n );\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAgJI;AA1HJ,mBAAsE;AAKtE,MAAM,YAAY;AAQlB,MAAM,QAAiB,CAAC;AAExB,MAAM,oBAAoB,CAAC,MAA2B;AACpD,MAAI,EAAE,QAAQ,SAAU;AACxB,QAAM,MAAM,MAAM,MAAM,SAAS,CAAC;AAClC,MAAI,CAAC,IAAK;AACV,IAAE,gBAAgB;AAClB,MAAI,UAAU;AAChB;AAUO,SAAS,iBAAiB,WAAuB,EAAE,UAAU,KAAK,IAA2B,CAAC,GAAS;AAC5G,QAAM,UAAM,qBAAO,SAAS;AAC5B,MAAI,UAAU;AAEd,8BAAU,MAAM;AACd,QAAI,CAAC,QAAS;AACd,UAAM,QAAe,EAAE,WAAW,MAAM,IAAI,QAAQ,EAAE;AACtD,UAAM,KAAK,KAAK;AAChB,QAAI,MAAM,WAAW,EAAG,UAAS,iBAAiB,WAAW,mBAAmB,IAAI;AACpF,WAAO,MAAM;AAIX,YAAM,IAAI,MAAM,QAAQ,KAAK;AAC7B,UAAI,MAAM,GAAI,OAAM,OAAO,GAAG,CAAC;AAC/B,UAAI,MAAM,WAAW,EAAG,UAAS,oBAAoB,WAAW,mBAAmB,IAAI;AAAA,IACzF;AAAA,EACF,GAAG,CAAC,OAAO,CAAC;AACd;AAUO,SAAS,eACd,KACA,EAAE,UAAU,KAAK,IAA2B,CAAC,GACvC;AACN,8BAAU,MAAM;AACd,UAAM,OAAO,IAAI;AACjB,QAAI,CAAC,WAAW,CAAC,KAAM;AAGvB,UAAM,UAAU,SAAS;AACzB,UAAM,QAAQ,KAAK,cAA2B,SAAS,KAAK;AAC5D,UAAM,MAAM;AAEZ,UAAM,YAAY,CAAC,MAA2B;AAC5C,UAAI,EAAE,QAAQ,MAAO;AACrB,YAAM,OAAO,MAAM,KAAK,KAAK,iBAA8B,SAAS,CAAC;AACrE,UAAI,KAAK,WAAW,EAAG;AACvB,YAAM,UAAU,KAAK,CAAC;AACtB,YAAM,SAAS,KAAK,KAAK,SAAS,CAAC;AACnC,YAAM,SAAS,SAAS;AACxB,UAAI,EAAE,YAAY,WAAW,SAAS;AACpC,UAAE,eAAe;AACjB,eAAO,MAAM;AAAA,MACf,WAAW,CAAC,EAAE,YAAY,WAAW,QAAQ;AAC3C,UAAE,eAAe;AACjB,gBAAQ,MAAM;AAAA,MAChB;AAAA,IACF;AACA,SAAK,iBAAiB,WAAW,SAAS;AAE1C,WAAO,MAAM;AACX,WAAK,oBAAoB,WAAW,SAAS;AAG7C,eAAS,QAAQ;AAAA,IACnB;AAAA,EACF,GAAG,CAAC,SAAS,GAAG,CAAC;AACnB;AAqBO,SAAS,OAAO,EAAE,UAAU,WAAW,WAAW,OAAO,GAAG,KAAK,GAA2B;AACjG,QAAM,UAAM,qBAAuB,IAAI;AACvC,mBAAiB,SAAS;AAC1B,iBAAe,GAAG;AAClB,SACE,4CAAC,SAAI,KAAU,MAAK,UAAS,cAAW,QAAO,WAAsB,OAAe,GAAG,MACpF,UACH;AAEJ;","names":[]}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { ReactNode, CSSProperties } from 'react';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Register `onDismiss` as this dialog's Escape handler. The topmost mounted
|
|
5
|
+
* dialog wins: only its handler runs, and only on Escape.
|
|
6
|
+
*
|
|
7
|
+
* `onDismiss` is read through a ref so a new inline lambda on each render (the
|
|
8
|
+
* near-universal shape of an `onClose` prop) does NOT re-register the dialog —
|
|
9
|
+
* the entry is torn down and re-added only when `enabled` changes.
|
|
10
|
+
*/
|
|
11
|
+
declare function useDialogDismiss(onDismiss: () => void, { enabled }?: {
|
|
12
|
+
enabled?: boolean;
|
|
13
|
+
}): void;
|
|
14
|
+
/**
|
|
15
|
+
* Manage focus for one dialog rooted at `ref`: on open, record the invoking
|
|
16
|
+
* element and focus the first focusable descendant (falling back to `ref`
|
|
17
|
+
* itself — give the root `tabIndex={-1}` for that case); on close, return
|
|
18
|
+
* focus to the invoker (guarded — it may itself have unmounted); Tab /
|
|
19
|
+
* Shift-Tab wrap at the ends of the focusable list, read at keydown time
|
|
20
|
+
* rather than cached on mount (dialog contents change).
|
|
21
|
+
*/
|
|
22
|
+
declare function useDialogFocus(ref: {
|
|
23
|
+
current: HTMLElement | null;
|
|
24
|
+
}, { enabled }?: {
|
|
25
|
+
enabled?: boolean;
|
|
26
|
+
}): void;
|
|
27
|
+
/** Props for {@link Dialog}. Styling is the app's — the primitive is structural. */
|
|
28
|
+
interface DialogProps {
|
|
29
|
+
children: ReactNode;
|
|
30
|
+
/** Called on Escape (the topmost dialog wins). Click-outside/scrim handling is
|
|
31
|
+
* the app's: the scrim is app chrome, and its click handler calls the same
|
|
32
|
+
* `onDismiss`. */
|
|
33
|
+
onDismiss: () => void;
|
|
34
|
+
/** The accessible name — a dialog without one is unnamed to AT (4.1.2). */
|
|
35
|
+
'aria-label'?: string;
|
|
36
|
+
className?: string;
|
|
37
|
+
style?: CSSProperties;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* A dialog with the contract wired: `role="dialog" aria-modal="true"`, Escape
|
|
41
|
+
* dismiss (topmost wins), focus in on open / back to the invoker on close, Tab
|
|
42
|
+
* trapped inside. Render it when the dialog is open, with the invoker still
|
|
43
|
+
* focused — the invoker is recorded at mount.
|
|
44
|
+
*/
|
|
45
|
+
declare function Dialog({ children, onDismiss, className, style, ...aria }: DialogProps): ReactNode;
|
|
46
|
+
|
|
47
|
+
export { Dialog, type DialogProps, useDialogDismiss, useDialogFocus };
|
package/dist/dialog.d.ts
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { ReactNode, CSSProperties } from 'react';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Register `onDismiss` as this dialog's Escape handler. The topmost mounted
|
|
5
|
+
* dialog wins: only its handler runs, and only on Escape.
|
|
6
|
+
*
|
|
7
|
+
* `onDismiss` is read through a ref so a new inline lambda on each render (the
|
|
8
|
+
* near-universal shape of an `onClose` prop) does NOT re-register the dialog —
|
|
9
|
+
* the entry is torn down and re-added only when `enabled` changes.
|
|
10
|
+
*/
|
|
11
|
+
declare function useDialogDismiss(onDismiss: () => void, { enabled }?: {
|
|
12
|
+
enabled?: boolean;
|
|
13
|
+
}): void;
|
|
14
|
+
/**
|
|
15
|
+
* Manage focus for one dialog rooted at `ref`: on open, record the invoking
|
|
16
|
+
* element and focus the first focusable descendant (falling back to `ref`
|
|
17
|
+
* itself — give the root `tabIndex={-1}` for that case); on close, return
|
|
18
|
+
* focus to the invoker (guarded — it may itself have unmounted); Tab /
|
|
19
|
+
* Shift-Tab wrap at the ends of the focusable list, read at keydown time
|
|
20
|
+
* rather than cached on mount (dialog contents change).
|
|
21
|
+
*/
|
|
22
|
+
declare function useDialogFocus(ref: {
|
|
23
|
+
current: HTMLElement | null;
|
|
24
|
+
}, { enabled }?: {
|
|
25
|
+
enabled?: boolean;
|
|
26
|
+
}): void;
|
|
27
|
+
/** Props for {@link Dialog}. Styling is the app's — the primitive is structural. */
|
|
28
|
+
interface DialogProps {
|
|
29
|
+
children: ReactNode;
|
|
30
|
+
/** Called on Escape (the topmost dialog wins). Click-outside/scrim handling is
|
|
31
|
+
* the app's: the scrim is app chrome, and its click handler calls the same
|
|
32
|
+
* `onDismiss`. */
|
|
33
|
+
onDismiss: () => void;
|
|
34
|
+
/** The accessible name — a dialog without one is unnamed to AT (4.1.2). */
|
|
35
|
+
'aria-label'?: string;
|
|
36
|
+
className?: string;
|
|
37
|
+
style?: CSSProperties;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* A dialog with the contract wired: `role="dialog" aria-modal="true"`, Escape
|
|
41
|
+
* dismiss (topmost wins), focus in on open / back to the invoker on close, Tab
|
|
42
|
+
* trapped inside. Render it when the dialog is open, with the invoker still
|
|
43
|
+
* focused — the invoker is recorded at mount.
|
|
44
|
+
*/
|
|
45
|
+
declare function Dialog({ children, onDismiss, className, style, ...aria }: DialogProps): ReactNode;
|
|
46
|
+
|
|
47
|
+
export { Dialog, type DialogProps, useDialogDismiss, useDialogFocus };
|
package/dist/dialog.js
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import "./chunk-VHAA22YE.js";
|
|
2
|
+
import { jsx } from "react/jsx-runtime";
|
|
3
|
+
import { useEffect, useRef } from "react";
|
|
4
|
+
const FOCUSABLE = 'button:not([disabled]), [href], input, select, textarea, iframe, [tabindex]:not([tabindex="-1"])';
|
|
5
|
+
const stack = [];
|
|
6
|
+
const onDocumentKeyDown = (e) => {
|
|
7
|
+
if (e.key !== "Escape") return;
|
|
8
|
+
const top = stack[stack.length - 1];
|
|
9
|
+
if (!top) return;
|
|
10
|
+
e.stopPropagation();
|
|
11
|
+
top.onDismiss();
|
|
12
|
+
};
|
|
13
|
+
function useDialogDismiss(onDismiss, { enabled = true } = {}) {
|
|
14
|
+
const ref = useRef(onDismiss);
|
|
15
|
+
ref.current = onDismiss;
|
|
16
|
+
useEffect(() => {
|
|
17
|
+
if (!enabled) return;
|
|
18
|
+
const entry = { onDismiss: () => ref.current() };
|
|
19
|
+
stack.push(entry);
|
|
20
|
+
if (stack.length === 1) document.addEventListener("keydown", onDocumentKeyDown, true);
|
|
21
|
+
return () => {
|
|
22
|
+
const i = stack.indexOf(entry);
|
|
23
|
+
if (i !== -1) stack.splice(i, 1);
|
|
24
|
+
if (stack.length === 0) document.removeEventListener("keydown", onDocumentKeyDown, true);
|
|
25
|
+
};
|
|
26
|
+
}, [enabled]);
|
|
27
|
+
}
|
|
28
|
+
function useDialogFocus(ref, { enabled = true } = {}) {
|
|
29
|
+
useEffect(() => {
|
|
30
|
+
const node = ref.current;
|
|
31
|
+
if (!enabled || !node) return;
|
|
32
|
+
const invoker = document.activeElement;
|
|
33
|
+
const first = node.querySelector(FOCUSABLE) ?? node;
|
|
34
|
+
first.focus();
|
|
35
|
+
const onKeyDown = (e) => {
|
|
36
|
+
if (e.key !== "Tab") return;
|
|
37
|
+
const list = Array.from(node.querySelectorAll(FOCUSABLE));
|
|
38
|
+
if (list.length === 0) return;
|
|
39
|
+
const firstEl = list[0];
|
|
40
|
+
const lastEl = list[list.length - 1];
|
|
41
|
+
const active = document.activeElement;
|
|
42
|
+
if (e.shiftKey && active === firstEl) {
|
|
43
|
+
e.preventDefault();
|
|
44
|
+
lastEl.focus();
|
|
45
|
+
} else if (!e.shiftKey && active === lastEl) {
|
|
46
|
+
e.preventDefault();
|
|
47
|
+
firstEl.focus();
|
|
48
|
+
}
|
|
49
|
+
};
|
|
50
|
+
node.addEventListener("keydown", onKeyDown);
|
|
51
|
+
return () => {
|
|
52
|
+
node.removeEventListener("keydown", onKeyDown);
|
|
53
|
+
invoker?.focus?.();
|
|
54
|
+
};
|
|
55
|
+
}, [enabled, ref]);
|
|
56
|
+
}
|
|
57
|
+
function Dialog({ children, onDismiss, className, style, ...aria }) {
|
|
58
|
+
const ref = useRef(null);
|
|
59
|
+
useDialogDismiss(onDismiss);
|
|
60
|
+
useDialogFocus(ref);
|
|
61
|
+
return /* @__PURE__ */ jsx("div", { ref, role: "dialog", "aria-modal": "true", className, style, ...aria, children });
|
|
62
|
+
}
|
|
63
|
+
export {
|
|
64
|
+
Dialog,
|
|
65
|
+
useDialogDismiss,
|
|
66
|
+
useDialogFocus
|
|
67
|
+
};
|
|
68
|
+
//# sourceMappingURL=dialog.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/dialog.tsx"],"sourcesContent":["// In-app dialog primitives (interaction_standards R-IX-1; the dialog half of the\n// standards' delivery vehicle, R3-613).\n//\n// The APG dialog contract as imports: Escape dismisses the TOPMOST open dialog,\n// focus moves into the dialog on open and back to the invoker on close, and Tab\n// wraps inside it. Generalized from the host's own pair (site-main\n// `useHostDialogDismiss`/`useHostDialogFocus`, R3-592) — the same decisions,\n// renamed for an app context; site-main holds the two to identical behaviour\n// with a parity test, so \"generalized\" is a fact and an app can swap between\n// the primitives without re-litigating the contract.\n//\n// TRUST BOUNDARY (same rule as `./loading`): these render INSIDE the app's\n// iframe, under the app's principal — presentational only, NO capability, NO\n// host round-trip, no reserved landmark or host wordmark. Ordinary app a11y,\n// never trusted-chrome framing; adds nothing to a grant set.\n//\n// The dismiss listener is a capture-phase listener on the app's OWN document,\n// installed while the stack is non-empty. Capture is what makes the contract\n// hold on both sides: a keystroke the app dialog claims (Escape → stopPropagation)\n// never reaches an app-level bubble handler behind the dialog — and the host's\n// top-document listener lives in another document entirely, so neither side's\n// dismissal can suppress the other's, with no protocol between them.\nimport { useEffect, useRef, type CSSProperties, type ReactNode } from 'react';\n\n/** The focusable-selector constant. Deliberately NOT exported from `index.ts`:\n * it is an implementation detail of the trap, not author surface. Includes\n * `iframe` because a dialog whose body hosts a frame must let Tab reach it. */\nconst FOCUSABLE = 'button:not([disabled]), [href], input, select, textarea, iframe, [tabindex]:not([tabindex=\"-1\"])';\n\ntype Entry = { onDismiss: () => void };\n\n// A module-level stack is the whole registry: the ONE capture-phase document\n// listener is installed when the first dialog mounts and removed when the last\n// unmounts. Reading `stack` fresh on each keypress makes removal-of-a-non-top-\n// entry safe.\nconst stack: Entry[] = [];\n\nconst onDocumentKeyDown = (e: KeyboardEvent): void => {\n if (e.key !== 'Escape') return;\n const top = stack[stack.length - 1];\n if (!top) return;\n e.stopPropagation();\n top.onDismiss();\n};\n\n/**\n * Register `onDismiss` as this dialog's Escape handler. The topmost mounted\n * dialog wins: only its handler runs, and only on Escape.\n *\n * `onDismiss` is read through a ref so a new inline lambda on each render (the\n * near-universal shape of an `onClose` prop) does NOT re-register the dialog —\n * the entry is torn down and re-added only when `enabled` changes.\n */\nexport function useDialogDismiss(onDismiss: () => void, { enabled = true }: { enabled?: boolean } = {}): void {\n const ref = useRef(onDismiss);\n ref.current = onDismiss;\n\n useEffect(() => {\n if (!enabled) return;\n const entry: Entry = { onDismiss: () => ref.current() };\n stack.push(entry);\n if (stack.length === 1) document.addEventListener('keydown', onDocumentKeyDown, true);\n return () => {\n // Unmounting removes THIS dialog's entry, which may not be the top — a\n // dialog closed out of stack order must not leak its entry. Splice by\n // identity, never by \"pop\".\n const i = stack.indexOf(entry);\n if (i !== -1) stack.splice(i, 1);\n if (stack.length === 0) document.removeEventListener('keydown', onDocumentKeyDown, true);\n };\n }, [enabled]);\n}\n\n/**\n * Manage focus for one dialog rooted at `ref`: on open, record the invoking\n * element and focus the first focusable descendant (falling back to `ref`\n * itself — give the root `tabIndex={-1}` for that case); on close, return\n * focus to the invoker (guarded — it may itself have unmounted); Tab /\n * Shift-Tab wrap at the ends of the focusable list, read at keydown time\n * rather than cached on mount (dialog contents change).\n */\nexport function useDialogFocus(\n ref: { current: HTMLElement | null },\n { enabled = true }: { enabled?: boolean } = {},\n): void {\n useEffect(() => {\n const node = ref.current;\n if (!enabled || !node) return;\n\n // Record the invoker BEFORE stealing focus — the element that opened the dialog.\n const invoker = document.activeElement as HTMLElement | null;\n const first = node.querySelector<HTMLElement>(FOCUSABLE) ?? node;\n first.focus();\n\n const onKeyDown = (e: KeyboardEvent): void => {\n if (e.key !== 'Tab') return;\n const list = Array.from(node.querySelectorAll<HTMLElement>(FOCUSABLE));\n if (list.length === 0) return;\n const firstEl = list[0];\n const lastEl = list[list.length - 1];\n const active = document.activeElement;\n if (e.shiftKey && active === firstEl) {\n e.preventDefault();\n lastEl.focus();\n } else if (!e.shiftKey && active === lastEl) {\n e.preventDefault();\n firstEl.focus();\n }\n };\n node.addEventListener('keydown', onKeyDown);\n\n return () => {\n node.removeEventListener('keydown', onKeyDown);\n // The invoker may itself have unmounted by the time the dialog closes; the\n // optional calls are why this stays safe (and why it can never throw on teardown).\n invoker?.focus?.();\n };\n }, [enabled, ref]);\n}\n\n/** Props for {@link Dialog}. Styling is the app's — the primitive is structural. */\nexport interface DialogProps {\n children: ReactNode;\n /** Called on Escape (the topmost dialog wins). Click-outside/scrim handling is\n * the app's: the scrim is app chrome, and its click handler calls the same\n * `onDismiss`. */\n onDismiss: () => void;\n /** The accessible name — a dialog without one is unnamed to AT (4.1.2). */\n 'aria-label'?: string;\n className?: string;\n style?: CSSProperties;\n}\n\n/**\n * A dialog with the contract wired: `role=\"dialog\" aria-modal=\"true\"`, Escape\n * dismiss (topmost wins), focus in on open / back to the invoker on close, Tab\n * trapped inside. Render it when the dialog is open, with the invoker still\n * focused — the invoker is recorded at mount.\n */\nexport function Dialog({ children, onDismiss, className, style, ...aria }: DialogProps): ReactNode {\n const ref = useRef<HTMLDivElement>(null);\n useDialogDismiss(onDismiss);\n useDialogFocus(ref);\n return (\n <div ref={ref} role=\"dialog\" aria-modal=\"true\" className={className} style={style} {...aria}>\n {children}\n </div>\n );\n}\n"],"mappings":";AAgJI;AA1HJ,SAAS,WAAW,cAAkD;AAKtE,MAAM,YAAY;AAQlB,MAAM,QAAiB,CAAC;AAExB,MAAM,oBAAoB,CAAC,MAA2B;AACpD,MAAI,EAAE,QAAQ,SAAU;AACxB,QAAM,MAAM,MAAM,MAAM,SAAS,CAAC;AAClC,MAAI,CAAC,IAAK;AACV,IAAE,gBAAgB;AAClB,MAAI,UAAU;AAChB;AAUO,SAAS,iBAAiB,WAAuB,EAAE,UAAU,KAAK,IAA2B,CAAC,GAAS;AAC5G,QAAM,MAAM,OAAO,SAAS;AAC5B,MAAI,UAAU;AAEd,YAAU,MAAM;AACd,QAAI,CAAC,QAAS;AACd,UAAM,QAAe,EAAE,WAAW,MAAM,IAAI,QAAQ,EAAE;AACtD,UAAM,KAAK,KAAK;AAChB,QAAI,MAAM,WAAW,EAAG,UAAS,iBAAiB,WAAW,mBAAmB,IAAI;AACpF,WAAO,MAAM;AAIX,YAAM,IAAI,MAAM,QAAQ,KAAK;AAC7B,UAAI,MAAM,GAAI,OAAM,OAAO,GAAG,CAAC;AAC/B,UAAI,MAAM,WAAW,EAAG,UAAS,oBAAoB,WAAW,mBAAmB,IAAI;AAAA,IACzF;AAAA,EACF,GAAG,CAAC,OAAO,CAAC;AACd;AAUO,SAAS,eACd,KACA,EAAE,UAAU,KAAK,IAA2B,CAAC,GACvC;AACN,YAAU,MAAM;AACd,UAAM,OAAO,IAAI;AACjB,QAAI,CAAC,WAAW,CAAC,KAAM;AAGvB,UAAM,UAAU,SAAS;AACzB,UAAM,QAAQ,KAAK,cAA2B,SAAS,KAAK;AAC5D,UAAM,MAAM;AAEZ,UAAM,YAAY,CAAC,MAA2B;AAC5C,UAAI,EAAE,QAAQ,MAAO;AACrB,YAAM,OAAO,MAAM,KAAK,KAAK,iBAA8B,SAAS,CAAC;AACrE,UAAI,KAAK,WAAW,EAAG;AACvB,YAAM,UAAU,KAAK,CAAC;AACtB,YAAM,SAAS,KAAK,KAAK,SAAS,CAAC;AACnC,YAAM,SAAS,SAAS;AACxB,UAAI,EAAE,YAAY,WAAW,SAAS;AACpC,UAAE,eAAe;AACjB,eAAO,MAAM;AAAA,MACf,WAAW,CAAC,EAAE,YAAY,WAAW,QAAQ;AAC3C,UAAE,eAAe;AACjB,gBAAQ,MAAM;AAAA,MAChB;AAAA,IACF;AACA,SAAK,iBAAiB,WAAW,SAAS;AAE1C,WAAO,MAAM;AACX,WAAK,oBAAoB,WAAW,SAAS;AAG7C,eAAS,QAAQ;AAAA,IACnB;AAAA,EACF,GAAG,CAAC,SAAS,GAAG,CAAC;AACnB;AAqBO,SAAS,OAAO,EAAE,UAAU,WAAW,WAAW,OAAO,GAAG,KAAK,GAA2B;AACjG,QAAM,MAAM,OAAuB,IAAI;AACvC,mBAAiB,SAAS;AAC1B,iBAAe,GAAG;AAClB,SACE,oBAAC,SAAI,KAAU,MAAK,UAAS,cAAW,QAAO,WAAsB,OAAe,GAAG,MACpF,UACH;AAEJ;","names":[]}
|
|
@@ -18,44 +18,116 @@ var __copyProps = (to, from, except, desc) => {
|
|
|
18
18
|
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
19
|
var spaces_exports = {};
|
|
20
20
|
__export(spaces_exports, {
|
|
21
|
+
acceptInvite: () => acceptInvite,
|
|
22
|
+
declineInvite: () => declineInvite,
|
|
21
23
|
getSpaceMembers: () => getSpaceMembers,
|
|
22
24
|
inviteToSpace: () => inviteToSpace,
|
|
23
25
|
listAllSpaces: () => listAllSpaces,
|
|
24
26
|
listGrants: () => listGrants,
|
|
27
|
+
listMyInvites: () => listMyInvites,
|
|
28
|
+
listPendingInvites: () => listPendingInvites,
|
|
25
29
|
listSpaces: () => listSpaces,
|
|
26
30
|
lookupUser: () => lookupUser,
|
|
27
31
|
revokeGrant: () => revokeGrant,
|
|
32
|
+
revokeInvite: () => revokeInvite,
|
|
28
33
|
setSpaceRole: () => setSpaceRole,
|
|
29
34
|
unshareSpace: () => unshareSpace
|
|
30
35
|
});
|
|
31
36
|
module.exports = __toCommonJS(spaces_exports);
|
|
32
37
|
var import_catalog = require("../catalog");
|
|
33
|
-
const
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
};
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
};
|
|
42
|
-
const
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
const
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
38
|
+
const withTry = (fn, fallback) => Object.assign(fn, {
|
|
39
|
+
try: async (...args) => {
|
|
40
|
+
try {
|
|
41
|
+
return { ok: true, value: await fn(...args) };
|
|
42
|
+
} catch (e) {
|
|
43
|
+
return { ok: false, code: e.code ?? fallback };
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
});
|
|
47
|
+
const listSpaces = withTry(
|
|
48
|
+
(opts = {}) => (0, import_catalog.invoke)("spaces:list", opts),
|
|
49
|
+
"unknown"
|
|
50
|
+
);
|
|
51
|
+
const listAllSpaces = withTry(
|
|
52
|
+
() => (0, import_catalog.invoke)("spaces:listAll", {}),
|
|
53
|
+
"unknown"
|
|
54
|
+
);
|
|
55
|
+
const getSpaceMembers = withTry(
|
|
56
|
+
(spaceId) => (0, import_catalog.invoke)("spaces:members", { spaceId }),
|
|
57
|
+
"unknown"
|
|
58
|
+
);
|
|
59
|
+
const inviteToSpace = withTry(
|
|
60
|
+
async (spaceId, login, role) => {
|
|
61
|
+
await (0, import_catalog.invoke)("spaces:invite", { spaceId, login, role });
|
|
62
|
+
},
|
|
63
|
+
"unknown"
|
|
64
|
+
);
|
|
65
|
+
const unshareSpace = withTry(
|
|
66
|
+
async (spaceId, uid) => {
|
|
67
|
+
await (0, import_catalog.invoke)("spaces:unshare", { spaceId, uid });
|
|
68
|
+
},
|
|
69
|
+
"unknown"
|
|
70
|
+
);
|
|
71
|
+
const setSpaceRole = withTry(
|
|
72
|
+
async (spaceId, uid, role) => {
|
|
73
|
+
await (0, import_catalog.invoke)("spaces:setRole", { spaceId, uid, role });
|
|
74
|
+
},
|
|
75
|
+
"unknown"
|
|
76
|
+
);
|
|
77
|
+
const lookupUser = withTry(
|
|
78
|
+
(login) => (0, import_catalog.invoke)("spaces:lookupUser", { login }),
|
|
79
|
+
"unknown"
|
|
80
|
+
);
|
|
81
|
+
const listGrants = withTry(
|
|
82
|
+
() => (0, import_catalog.invoke)("spaces:grants", {}),
|
|
83
|
+
"unknown"
|
|
84
|
+
);
|
|
85
|
+
const revokeGrant = withTry(
|
|
86
|
+
async (appKey, spaceId) => {
|
|
87
|
+
await (0, import_catalog.invoke)("spaces:revokeGrant", { appKey, spaceId });
|
|
88
|
+
},
|
|
89
|
+
"unknown"
|
|
90
|
+
);
|
|
91
|
+
const listPendingInvites = withTry(
|
|
92
|
+
(spaceId) => (0, import_catalog.invoke)("spaces:pendingInvites", { spaceId }),
|
|
93
|
+
"unknown"
|
|
94
|
+
);
|
|
95
|
+
const revokeInvite = withTry(
|
|
96
|
+
async (spaceId, uid) => {
|
|
97
|
+
await (0, import_catalog.invoke)("spaces:revokeInvite", { spaceId, uid });
|
|
98
|
+
},
|
|
99
|
+
"unknown"
|
|
100
|
+
);
|
|
101
|
+
const listMyInvites = withTry(
|
|
102
|
+
() => (0, import_catalog.invoke)("spaces:listInvites", {}),
|
|
103
|
+
"unknown"
|
|
104
|
+
);
|
|
105
|
+
const acceptInvite = withTry(
|
|
106
|
+
async (spaceId) => {
|
|
107
|
+
await (0, import_catalog.invoke)("spaces:acceptInvite", { spaceId });
|
|
108
|
+
},
|
|
109
|
+
"unknown"
|
|
110
|
+
);
|
|
111
|
+
const declineInvite = withTry(
|
|
112
|
+
async (spaceId) => {
|
|
113
|
+
await (0, import_catalog.invoke)("spaces:declineInvite", { spaceId });
|
|
114
|
+
},
|
|
115
|
+
"unknown"
|
|
116
|
+
);
|
|
50
117
|
// Annotate the CommonJS export names for ESM import in node:
|
|
51
118
|
0 && (module.exports = {
|
|
119
|
+
acceptInvite,
|
|
120
|
+
declineInvite,
|
|
52
121
|
getSpaceMembers,
|
|
53
122
|
inviteToSpace,
|
|
54
123
|
listAllSpaces,
|
|
55
124
|
listGrants,
|
|
125
|
+
listMyInvites,
|
|
126
|
+
listPendingInvites,
|
|
56
127
|
listSpaces,
|
|
57
128
|
lookupUser,
|
|
58
129
|
revokeGrant,
|
|
130
|
+
revokeInvite,
|
|
59
131
|
setSpaceRole,
|
|
60
132
|
unshareSpace
|
|
61
133
|
});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/generated/spaces.ts"],"sourcesContent":["// GENERATED by scripts/codegen-prototype/generate.mjs — DO NOT EDIT.\n// Source of truth: descriptors.spaces.mjs\n// Family: spaces — Space management (the space-manager app) — UI_AS_APPS_SPEC §5.\n\nimport { invoke } from '../catalog';\n\n/** A collaborator's role on a shared space: full `owner`, read-write `writer`, or read-only `reader`. */\nexport type Role =\n | 'owner'\n | 'writer'\n | 'reader';\n\n/** Summary of a space, as returned by {@link listSpaces}. */\nexport interface SpaceInfo {\n spaceId: string;\n role?: Role;\n owner?: string;\n name?: string;\n}\n\n/** A member of a space (for the share/manage UI). */\nexport interface Member {\n /** The **grantee** — `user:{uid}` | `group:{gid}`. This is the canonical name (core_concepts §4: \"principal\" is reserved for the authority context; a space member is a *grantee*). The host populates this on every member row. */\n grantee: string;\n /** @deprecated Use {@link Member.grantee}. Kept as an alias (same value) for back-compat during the `principal`→`grantee` migration; will be removed in a future major. The host still populates both. */\n principal: string;\n role: Role;\n login?: string;\n avatarUrl?: string;\n}\n\n/** A handle resolved to a principal (handle → who). */\nexport interface ResolvedUser {\n uid: string;\n login: string;\n avatarUrl?: string;\n}\n\n/** One durable grant an app holds, for the §8.11 capability audit view. */\nexport interface GrantRecord {\n /** The app's provider-qualified **program** identity (AA-01 `appKey`). The DEFAULT program keys to the bare `provider__namespace__repository`; a NAMED mini-app appends a fourth `enc()`-escaped component (`provider__namespace__repository__name`) so its grants isolate from the repo's other programs. Host-supplied — the app never builds this key. */\n appKey: string;\n spaceId: string;\n /** Universal mount id (§3.5). */\n mountId: string;\n subtree?: string;\n mode: 'ro' | 'rw';\n name?: string;\n}\n\nexport type ListSpacesError =\n 'auth-required' | 'cancelled' | 'forbidden' | 'not-found' | 'unsupported-scheme' | 'unknown';\n\n/**\n * List spaces you can access — all of them, or just those bound to this app.\n *\n * Capability: `spaces:app`. Catalog name: `spaces:list`.\n * @throws `Error & { code: ListSpacesError }` on host refusal.\n */\nexport const listSpaces = (opts: { app?: boolean } = {}): Promise<SpaceInfo[]> =>\n invoke<SpaceInfo[]>(\"spaces:list\", opts);\n\nexport type ListAllSpacesError =\n 'auth-required' | 'cancelled' | 'forbidden' | 'not-found' | 'unsupported-scheme' | 'unknown';\n\n/**\n * Enumerate ALL the user's spaces (not just this app's).\n *\n * Capability: `spaces:user`. Catalog name: `spaces:listAll`.\n * @throws `Error & { code: ListAllSpacesError }` on host refusal.\n */\nexport const listAllSpaces = (): Promise<SpaceInfo[]> =>\n invoke<SpaceInfo[]>(\"spaces:listAll\", {});\n\nexport type GetSpaceMembersError =\n 'auth-required' | 'cancelled' | 'forbidden' | 'not-found' | 'unsupported-scheme' | 'unknown';\n\n/**\n * Read a space's members one-shot.\n *\n * Capability: `spaces:admin`. Catalog name: `spaces:members`.\n * @throws `Error & { code: GetSpaceMembersError }` on host refusal.\n */\nexport const getSpaceMembers = (spaceId: string): Promise<Member[]> =>\n invoke<Member[]>(\"spaces:members\", { spaceId });\n\nexport type InviteToSpaceError =\n 'auth-required' | 'cancelled' | 'forbidden' | 'not-found' | 'unsupported-scheme' | 'unknown';\n\n/**\n * Invite a user (by provider handle) to a space at a role. The host resolves\n * the handle, so the app never sees other users' uids except the one it\n * invited. Pull-based (FILE_SHARING_SPEC §6.4): this writes an INVITATION,\n * not membership — the recipient must {@link acceptInvite}. Re-inviting an\n * already-invited/member user is idempotent.\n *\n * Capability: `spaces:admin`. Catalog name: `spaces:invite`.\n * @throws `Error & { code: InviteToSpaceError }` on host refusal.\n */\nexport const inviteToSpace = async (spaceId: string, login: string, role: Role): Promise<void> => { await invoke<void>(\"spaces:invite\", { spaceId, login, role }); };\n\nexport type UnshareSpaceError =\n 'auth-required' | 'cancelled' | 'forbidden' | 'not-found' | 'unsupported-scheme' | 'unknown' | 'conflict';\n\n/**\n * Remove a member from a space. Refused if it would orphan the space\n * (owner-lockout, T41).\n *\n * Capability: `spaces:admin`. Catalog name: `spaces:unshare`.\n * @throws `Error & { code: UnshareSpaceError }` on host refusal.\n */\nexport const unshareSpace = async (spaceId: string, uid: string): Promise<void> => { await invoke<void>(\"spaces:unshare\", { spaceId, uid }); };\n\nexport type SetSpaceRoleError =\n 'auth-required' | 'cancelled' | 'forbidden' | 'not-found' | 'unsupported-scheme' | 'unknown' | 'conflict';\n\n/**\n * Change a member's role. Refused if it would drop the sole owner\n * (owner-lockout, T41).\n *\n * Capability: `spaces:admin`. Catalog name: `spaces:setRole`.\n * @throws `Error & { code: SetSpaceRoleError }` on host refusal.\n */\nexport const setSpaceRole = async (spaceId: string, uid: string, role: Role): Promise<void> => { await invoke<void>(\"spaces:setRole\", { spaceId, uid, role }); };\n\nexport type LookupUserError =\n 'auth-required' | 'cancelled' | 'forbidden' | 'not-found' | 'unsupported-scheme' | 'unknown';\n\n/**\n * Resolve a provider handle to a principal (for the invite flow).\n * Rate-limited host-side.\n *\n * Capability: `spaces:admin`. Catalog name: `spaces:lookupUser`.\n * @throws `Error & { code: LookupUserError }` on host refusal.\n */\nexport const lookupUser = (login: string): Promise<ResolvedUser> =>\n invoke<ResolvedUser>(\"spaces:lookupUser\", { login });\n\nexport type ListGrantsError =\n 'auth-required' | 'cancelled' | 'forbidden' | 'not-found' | 'unsupported-scheme' | 'unknown';\n\n/**\n * Enumerate every (app, mount) grant the user holds — the audit view\n * (§8.11). Elevated.\n *\n * Capability: `spaces:admin`. Catalog name: `spaces:grants`.\n * @throws `Error & { code: ListGrantsError }` on host refusal.\n */\nexport const listGrants = (): Promise<GrantRecord[]> =>\n invoke<GrantRecord[]>(\"spaces:grants\", {});\n\nexport type RevokeGrantError =\n 'auth-required' | 'cancelled' | 'forbidden' | 'not-found' | 'unsupported-scheme' | 'unknown';\n\n/**\n * Revoke one app's grant on a space — durable (the app can't re-mount) plus\n * a best-effort live teardown. Elevated.\n *\n * Capability: `spaces:admin`. Catalog name: `spaces:revokeGrant`.\n * @throws `Error & { code: RevokeGrantError }` on host refusal.\n */\nexport const revokeGrant = async (appKey: string, spaceId: string): Promise<void> => { await invoke<void>(\"spaces:revokeGrant\", { appKey, spaceId }); };\n\n"],"mappings":";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAIA,qBAAuB;AAuDhB,MAAM,aAAa,CAAC,OAA0B,CAAC,UACpD,uBAAoB,eAAe,IAAI;AAWlC,MAAM,gBAAgB,UAC3B,uBAAoB,kBAAkB,CAAC,CAAC;AAWnC,MAAM,kBAAkB,CAAC,gBAC9B,uBAAiB,kBAAkB,EAAE,QAAQ,CAAC;AAezC,MAAM,gBAAgB,OAAO,SAAiB,OAAe,SAA8B;AAAE,YAAM,uBAAa,iBAAiB,EAAE,SAAS,OAAO,KAAK,CAAC;AAAG;AAY5J,MAAM,eAAe,OAAO,SAAiB,QAA+B;AAAE,YAAM,uBAAa,kBAAkB,EAAE,SAAS,IAAI,CAAC;AAAG;AAYtI,MAAM,eAAe,OAAO,SAAiB,KAAa,SAA8B;AAAE,YAAM,uBAAa,kBAAkB,EAAE,SAAS,KAAK,KAAK,CAAC;AAAG;AAYxJ,MAAM,aAAa,CAAC,cACzB,uBAAqB,qBAAqB,EAAE,MAAM,CAAC;AAY9C,MAAM,aAAa,UACxB,uBAAsB,iBAAiB,CAAC,CAAC;AAYpC,MAAM,cAAc,OAAO,QAAgB,YAAmC;AAAE,YAAM,uBAAa,sBAAsB,EAAE,QAAQ,QAAQ,CAAC;AAAG;","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../src/generated/spaces.ts"],"sourcesContent":["// GENERATED by scripts/codegen-prototype/generate.mjs — DO NOT EDIT.\n// Source of truth: descriptors.spaces.mjs\n// Family: spaces — Space management (the space-manager app) — UI_AS_APPS_SPEC §5.\n\nimport { invoke } from '../catalog';\n\nconst withTry = <Args extends unknown[], R, E extends string>(\n fn: (...args: Args) => Promise<R>,\n fallback: E,\n): ((...args: Args) => Promise<R>) & { try: (...args: Args) => Promise<{ ok: true; value: R } | { ok: false; code: E }> } =>\n Object.assign(fn, {\n try: async (...args: Args) => {\n try {\n return { ok: true as const, value: await fn(...args) };\n } catch (e) {\n return { ok: false as const, code: (e as { code?: E }).code ?? fallback };\n }\n },\n });\n\n/** A collaborator's role on a shared space: full `owner`, read-write `writer`, or read-only `reader`. */\nexport type Role =\n | 'owner'\n | 'writer'\n | 'reader';\n\n/** Summary of a space, as returned by {@link listSpaces}. */\nexport interface SpaceInfo {\n spaceId: string;\n role?: Role;\n owner?: string;\n name?: string;\n}\n\n/** A member of a space (for the share/manage UI). */\nexport interface Member {\n /** The **grantee** — `user:{uid}` | `group:{gid}`. This is the canonical name (core_concepts §4: \"principal\" is reserved for the authority context; a space member is a *grantee*). The host populates this on every member row. */\n grantee: string;\n /** @deprecated Use {@link Member.grantee}. Kept as an alias (same value) for back-compat during the `principal`→`grantee` migration; will be removed in a future major. The host still populates both. */\n principal: string;\n role: Role;\n login?: string;\n avatarUrl?: string;\n}\n\n/** A handle resolved to a principal (handle → who). */\nexport interface ResolvedUser {\n uid: string;\n login: string;\n avatarUrl?: string;\n}\n\n/** One durable grant an app holds, for the §8.11 capability audit view. */\nexport interface GrantRecord {\n /** The app's provider-qualified **program** identity (AA-01 `appKey`). The DEFAULT program keys to the bare `provider__namespace__repository`; a NAMED mini-app appends a fourth `enc()`-escaped component (`provider__namespace__repository__name`) so its grants isolate from the repo's other programs. Host-supplied — the app never builds this key. */\n appKey: string;\n spaceId: string;\n /** Universal mount id (§3.5). */\n mountId: string;\n subtree?: string;\n mode: 'ro' | 'rw';\n name?: string;\n}\n\n/** A pending invitation to a space (pull-based sharing, FILE_SHARING_SPEC §6.4). It grants NO access until accepted — the recipient accepts it from their inbox ({@link listMyInvites} → {@link acceptInvite}), materializing membership. The display fields (`name`/`login`/`avatarUrl`) are untrusted for rendering. */\nexport interface Invite {\n spaceId: string;\n /** The invitee's uid — carried so the owner's pending list can {@link revokeInvite}(spaceId, uid). */\n uid: string;\n role: Role;\n owner: string;\n name?: string;\n invitedBy: string;\n /** epoch ms (server-stamped); absent until the write settles. */\n invitedAt?: number;\n login?: string;\n avatarUrl?: string;\n}\n\nexport type ListSpacesError =\n 'auth-required' | 'cancelled' | 'forbidden' | 'not-found' | 'unsupported-scheme' | 'unknown';\n\n/**\n * List spaces you can access — all of them, or just those bound to this app.\n *\n * Capability: `spaces:app`. Catalog name: `spaces:list`.\n * @throws `Error & { code: ListSpacesError }` on host refusal.\n */\nexport const listSpaces = withTry<[opts?: { app?: boolean }], SpaceInfo[], ListSpacesError>(\n (opts: { app?: boolean } = {}): Promise<SpaceInfo[]> =>\n invoke<SpaceInfo[]>(\"spaces:list\", opts),\n 'unknown',\n);\n\nexport type ListAllSpacesError =\n 'auth-required' | 'cancelled' | 'forbidden' | 'not-found' | 'unsupported-scheme' | 'unknown';\n\n/**\n * Enumerate ALL the user's spaces (not just this app's).\n *\n * Capability: `spaces:user`. Catalog name: `spaces:listAll`.\n * @throws `Error & { code: ListAllSpacesError }` on host refusal.\n */\nexport const listAllSpaces = withTry<[], SpaceInfo[], ListAllSpacesError>(\n (): Promise<SpaceInfo[]> =>\n invoke<SpaceInfo[]>(\"spaces:listAll\", {}),\n 'unknown',\n);\n\nexport type GetSpaceMembersError =\n 'auth-required' | 'cancelled' | 'forbidden' | 'not-found' | 'unsupported-scheme' | 'unknown';\n\n/**\n * Read a space's members one-shot.\n *\n * Capability: `spaces:admin`. Catalog name: `spaces:members`.\n * @throws `Error & { code: GetSpaceMembersError }` on host refusal.\n */\nexport const getSpaceMembers = withTry<[string], Member[], GetSpaceMembersError>(\n (spaceId: string): Promise<Member[]> =>\n invoke<Member[]>(\"spaces:members\", { spaceId }),\n 'unknown',\n);\n\nexport type InviteToSpaceError =\n 'auth-required' | 'cancelled' | 'forbidden' | 'not-found' | 'unsupported-scheme' | 'unknown' | 'quota-exceeded';\n\n/**\n * Invite a user (by provider handle) to a space at a role. The host resolves\n * the handle, so the app never sees other users' uids except the one it\n * invited. Pull-based (FILE_SHARING_SPEC §6.4): this writes an INVITATION,\n * not membership — the recipient must {@link acceptInvite}. Re-inviting an\n * already-invited/member user is idempotent.\n *\n * Capability: `spaces:admin`. Catalog name: `spaces:invite`.\n * @throws `Error & { code: InviteToSpaceError }` on host refusal.\n */\nexport const inviteToSpace = withTry<[string, string, Role], void, InviteToSpaceError>(\n async (spaceId: string, login: string, role: Role): Promise<void> => { await invoke<void>(\"spaces:invite\", { spaceId, login, role }); },\n 'unknown',\n);\n\nexport type UnshareSpaceError =\n 'auth-required' | 'cancelled' | 'forbidden' | 'not-found' | 'unsupported-scheme' | 'unknown' | 'conflict';\n\n/**\n * Remove a member from a space. Refused if it would orphan the space\n * (owner-lockout, T41).\n *\n * Capability: `spaces:admin`. Catalog name: `spaces:unshare`.\n * @throws `Error & { code: UnshareSpaceError }` on host refusal.\n */\nexport const unshareSpace = withTry<[string, string], void, UnshareSpaceError>(\n async (spaceId: string, uid: string): Promise<void> => { await invoke<void>(\"spaces:unshare\", { spaceId, uid }); },\n 'unknown',\n);\n\nexport type SetSpaceRoleError =\n 'auth-required' | 'cancelled' | 'forbidden' | 'not-found' | 'unsupported-scheme' | 'unknown' | 'conflict';\n\n/**\n * Change a member's role. Refused if it would drop the sole owner\n * (owner-lockout, T41).\n *\n * Capability: `spaces:admin`. Catalog name: `spaces:setRole`.\n * @throws `Error & { code: SetSpaceRoleError }` on host refusal.\n */\nexport const setSpaceRole = withTry<[string, string, Role], void, SetSpaceRoleError>(\n async (spaceId: string, uid: string, role: Role): Promise<void> => { await invoke<void>(\"spaces:setRole\", { spaceId, uid, role }); },\n 'unknown',\n);\n\nexport type LookupUserError =\n 'auth-required' | 'cancelled' | 'forbidden' | 'not-found' | 'unsupported-scheme' | 'unknown';\n\n/**\n * Resolve a provider handle to a principal (for the invite flow).\n * Rate-limited host-side.\n *\n * Capability: `spaces:admin`. Catalog name: `spaces:lookupUser`.\n * @throws `Error & { code: LookupUserError }` on host refusal.\n */\nexport const lookupUser = withTry<[string], ResolvedUser, LookupUserError>(\n (login: string): Promise<ResolvedUser> =>\n invoke<ResolvedUser>(\"spaces:lookupUser\", { login }),\n 'unknown',\n);\n\nexport type ListGrantsError =\n 'auth-required' | 'cancelled' | 'forbidden' | 'not-found' | 'unsupported-scheme' | 'unknown';\n\n/**\n * Enumerate every (app, mount) grant the user holds — the audit view\n * (§8.11). Elevated.\n *\n * Capability: `spaces:admin`. Catalog name: `spaces:grants`.\n * @throws `Error & { code: ListGrantsError }` on host refusal.\n */\nexport const listGrants = withTry<[], GrantRecord[], ListGrantsError>(\n (): Promise<GrantRecord[]> =>\n invoke<GrantRecord[]>(\"spaces:grants\", {}),\n 'unknown',\n);\n\nexport type RevokeGrantError =\n 'auth-required' | 'cancelled' | 'forbidden' | 'not-found' | 'unsupported-scheme' | 'unknown';\n\n/**\n * Revoke one app's grant on a space — durable (the app can't re-mount) plus\n * a best-effort live teardown. Elevated.\n *\n * Capability: `spaces:admin`. Catalog name: `spaces:revokeGrant`.\n * @throws `Error & { code: RevokeGrantError }` on host refusal.\n */\nexport const revokeGrant = withTry<[string, string], void, RevokeGrantError>(\n async (appKey: string, spaceId: string): Promise<void> => { await invoke<void>(\"spaces:revokeGrant\", { appKey, spaceId }); },\n 'unknown',\n);\n\nexport type ListPendingInvitesError =\n 'auth-required' | 'cancelled' | 'forbidden' | 'not-found' | 'unsupported-scheme' | 'unknown';\n\n/**\n * The owner's outstanding invitations for a space.\n *\n * Capability: `spaces:admin`. Catalog name: `spaces:pendingInvites`.\n * @throws `Error & { code: ListPendingInvitesError }` on host refusal.\n */\nexport const listPendingInvites = withTry<[string], Invite[], ListPendingInvitesError>(\n (spaceId: string): Promise<Invite[]> =>\n invoke<Invite[]>(\"spaces:pendingInvites\", { spaceId }),\n 'unknown',\n);\n\nexport type RevokeInviteError =\n 'auth-required' | 'cancelled' | 'forbidden' | 'not-found' | 'unsupported-scheme' | 'unknown';\n\n/**\n * Withdraw a pending invitation (distinct from {@link unshareSpace}, which\n * removes an ACCEPTED member).\n *\n * Capability: `spaces:admin`. Catalog name: `spaces:revokeInvite`.\n * @throws `Error & { code: RevokeInviteError }` on host refusal.\n */\nexport const revokeInvite = withTry<[string, string], void, RevokeInviteError>(\n async (spaceId: string, uid: string): Promise<void> => { await invoke<void>(\"spaces:revokeInvite\", { spaceId, uid }); },\n 'unknown',\n);\n\nexport type ListMyInvitesError =\n 'auth-required' | 'cancelled' | 'forbidden' | 'not-found' | 'unsupported-scheme' | 'unknown';\n\n/**\n * The caller's OWN invitation inbox.\n *\n * Capability: `spaces:user`. Catalog name: `spaces:listInvites`.\n * @throws `Error & { code: ListMyInvitesError }` on host refusal.\n */\nexport const listMyInvites = withTry<[], Invite[], ListMyInvitesError>(\n (): Promise<Invite[]> =>\n invoke<Invite[]>(\"spaces:listInvites\", {}),\n 'unknown',\n);\n\nexport type AcceptInviteError =\n 'auth-required' | 'cancelled' | 'forbidden' | 'not-found' | 'unsupported-scheme' | 'unknown';\n\n/**\n * Accept an invitation: materialize your membership at the invited role and\n * clear the invite. An invitation the caller doesn't hold rejects with\n * `forbidden` (indistinguishable from a nonexistent space; no existence\n * oracle).\n *\n * Capability: `spaces:user`. Catalog name: `spaces:acceptInvite`.\n * @throws `Error & { code: AcceptInviteError }` on host refusal.\n */\nexport const acceptInvite = withTry<[string], void, AcceptInviteError>(\n async (spaceId: string): Promise<void> => { await invoke<void>(\"spaces:acceptInvite\", { spaceId }); },\n 'unknown',\n);\n\nexport type DeclineInviteError =\n 'auth-required' | 'cancelled' | 'forbidden' | 'not-found' | 'unsupported-scheme' | 'unknown';\n\n/**\n * Decline (dismiss) an invitation from your inbox; writes no membership.\n *\n * Capability: `spaces:user`. Catalog name: `spaces:declineInvite`.\n * @throws `Error & { code: DeclineInviteError }` on host refusal.\n */\nexport const declineInvite = withTry<[string], void, DeclineInviteError>(\n async (spaceId: string): Promise<void> => { await invoke<void>(\"spaces:declineInvite\", { spaceId }); },\n 'unknown',\n);\n\n"],"mappings":";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAIA,qBAAuB;AAEvB,MAAM,UAAU,CACd,IACA,aAEA,OAAO,OAAO,IAAI;AAAA,EAChB,KAAK,UAAU,SAAe;AAC5B,QAAI;AACF,aAAO,EAAE,IAAI,MAAe,OAAO,MAAM,GAAG,GAAG,IAAI,EAAE;AAAA,IACvD,SAAS,GAAG;AACV,aAAO,EAAE,IAAI,OAAgB,MAAO,EAAmB,QAAQ,SAAS;AAAA,IAC1E;AAAA,EACF;AACF,CAAC;AAsEI,MAAM,aAAa;AAAA,EACxB,CAAC,OAA0B,CAAC,UAC1B,uBAAoB,eAAe,IAAI;AAAA,EACzC;AACF;AAWO,MAAM,gBAAgB;AAAA,EAC3B,UACE,uBAAoB,kBAAkB,CAAC,CAAC;AAAA,EAC1C;AACF;AAWO,MAAM,kBAAkB;AAAA,EAC7B,CAAC,gBACC,uBAAiB,kBAAkB,EAAE,QAAQ,CAAC;AAAA,EAChD;AACF;AAeO,MAAM,gBAAgB;AAAA,EAC3B,OAAO,SAAiB,OAAe,SAA8B;AAAE,cAAM,uBAAa,iBAAiB,EAAE,SAAS,OAAO,KAAK,CAAC;AAAA,EAAG;AAAA,EACtI;AACF;AAYO,MAAM,eAAe;AAAA,EAC1B,OAAO,SAAiB,QAA+B;AAAE,cAAM,uBAAa,kBAAkB,EAAE,SAAS,IAAI,CAAC;AAAA,EAAG;AAAA,EACjH;AACF;AAYO,MAAM,eAAe;AAAA,EAC1B,OAAO,SAAiB,KAAa,SAA8B;AAAE,cAAM,uBAAa,kBAAkB,EAAE,SAAS,KAAK,KAAK,CAAC;AAAA,EAAG;AAAA,EACnI;AACF;AAYO,MAAM,aAAa;AAAA,EACxB,CAAC,cACC,uBAAqB,qBAAqB,EAAE,MAAM,CAAC;AAAA,EACrD;AACF;AAYO,MAAM,aAAa;AAAA,EACxB,UACE,uBAAsB,iBAAiB,CAAC,CAAC;AAAA,EAC3C;AACF;AAYO,MAAM,cAAc;AAAA,EACzB,OAAO,QAAgB,YAAmC;AAAE,cAAM,uBAAa,sBAAsB,EAAE,QAAQ,QAAQ,CAAC;AAAA,EAAG;AAAA,EAC3H;AACF;AAWO,MAAM,qBAAqB;AAAA,EAChC,CAAC,gBACC,uBAAiB,yBAAyB,EAAE,QAAQ,CAAC;AAAA,EACvD;AACF;AAYO,MAAM,eAAe;AAAA,EAC1B,OAAO,SAAiB,QAA+B;AAAE,cAAM,uBAAa,uBAAuB,EAAE,SAAS,IAAI,CAAC;AAAA,EAAG;AAAA,EACtH;AACF;AAWO,MAAM,gBAAgB;AAAA,EAC3B,UACE,uBAAiB,sBAAsB,CAAC,CAAC;AAAA,EAC3C;AACF;AAcO,MAAM,eAAe;AAAA,EAC1B,OAAO,YAAmC;AAAE,cAAM,uBAAa,uBAAuB,EAAE,QAAQ,CAAC;AAAA,EAAG;AAAA,EACpG;AACF;AAWO,MAAM,gBAAgB;AAAA,EAC3B,OAAO,YAAmC;AAAE,cAAM,uBAAa,wBAAwB,EAAE,QAAQ,CAAC;AAAA,EAAG;AAAA,EACrG;AACF;","names":[]}
|