@pyric/ui 0.1.0-alpha.19 → 0.1.0-alpha.21

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.
@@ -1,25 +1,2 @@
1
- import { type ReactNode } from 'react';
2
- export interface ModalProps {
3
- open: boolean;
4
- onClose: () => void;
5
- children: ReactNode;
6
- ariaLabel?: string;
7
- /** Forwarded to the root wrapper (covers the full viewport). */
8
- className?: string;
9
- /** Forwarded to the backdrop element. */
10
- backdropClassName?: string;
11
- /** Forwarded to the inner panel. */
12
- panelClassName?: string;
13
- }
14
- /**
15
- * Headless modal — provides behavior only (Escape-to-close, backdrop
16
- * click, `aria-modal`). Consumers attach all visual styling via the
17
- * three `className` slots or by selecting on the emitted data
18
- * attributes:
19
- *
20
- * [data-pyric-ui="modal"] — root viewport wrapper
21
- * [data-pyric-modal-backdrop] — clickable backdrop
22
- * [data-pyric-modal-panel] — inner panel containing children
23
- */
24
- export declare function Modal({ open, onClose, children, ariaLabel, className, backdropClassName, panelClassName, }: ModalProps): import("react").JSX.Element | null;
1
+ export { Modal, type ModalProps } from '../primitives/Modal.js';
25
2
  //# sourceMappingURL=Modal.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"Modal.d.ts","sourceRoot":"","sources":["../../src/agents/Modal.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAa,KAAK,SAAS,EAAE,MAAM,OAAO,CAAC;AAElD,MAAM,WAAW,UAAU;IACzB,IAAI,EAAE,OAAO,CAAC;IACd,OAAO,EAAE,MAAM,IAAI,CAAC;IACpB,QAAQ,EAAE,SAAS,CAAC;IACpB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,gEAAgE;IAChE,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,yCAAyC;IACzC,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,oCAAoC;IACpC,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB;AAED;;;;;;;;;GASG;AACH,wBAAgB,KAAK,CAAC,EACpB,IAAI,EACJ,OAAO,EACP,QAAQ,EACR,SAAS,EACT,SAAS,EACT,iBAAiB,EACjB,cAAc,GACf,EAAE,UAAU,sCA+BZ"}
1
+ {"version":3,"file":"Modal.d.ts","sourceRoot":"","sources":["../../src/agents/Modal.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,KAAK,UAAU,EAAE,MAAM,wBAAwB,CAAC"}
@@ -1,28 +1,2 @@
1
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { useEffect } from 'react';
3
- /**
4
- * Headless modal — provides behavior only (Escape-to-close, backdrop
5
- * click, `aria-modal`). Consumers attach all visual styling via the
6
- * three `className` slots or by selecting on the emitted data
7
- * attributes:
8
- *
9
- * [data-pyric-ui="modal"] — root viewport wrapper
10
- * [data-pyric-modal-backdrop] — clickable backdrop
11
- * [data-pyric-modal-panel] — inner panel containing children
12
- */
13
- export function Modal({ open, onClose, children, ariaLabel, className, backdropClassName, panelClassName, }) {
14
- useEffect(() => {
15
- if (!open)
16
- return;
17
- const handler = (e) => {
18
- if (e.key === 'Escape')
19
- onClose();
20
- };
21
- window.addEventListener('keydown', handler);
22
- return () => window.removeEventListener('keydown', handler);
23
- }, [open, onClose]);
24
- if (!open)
25
- return null;
26
- return (_jsxs("div", { role: "dialog", "aria-modal": "true", "aria-label": ariaLabel, "data-pyric-ui": "modal", className: className, children: [_jsx("div", { "data-pyric-modal-backdrop": true, className: backdropClassName, onClick: onClose, "aria-hidden": "true" }), _jsx("div", { "data-pyric-modal-panel": true, className: panelClassName, children: children })] }));
27
- }
1
+ export { Modal } from '../primitives/Modal.js';
28
2
  //# sourceMappingURL=Modal.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"Modal.js","sourceRoot":"","sources":["../../src/agents/Modal.tsx"],"names":[],"mappings":";AAAA,OAAO,EAAE,SAAS,EAAkB,MAAM,OAAO,CAAC;AAelD;;;;;;;;;GASG;AACH,MAAM,UAAU,KAAK,CAAC,EACpB,IAAI,EACJ,OAAO,EACP,QAAQ,EACR,SAAS,EACT,SAAS,EACT,iBAAiB,EACjB,cAAc,GACH;IACX,SAAS,CAAC,GAAG,EAAE;QACb,IAAI,CAAC,IAAI;YAAE,OAAO;QAClB,MAAM,OAAO,GAAG,CAAC,CAAgB,EAAE,EAAE;YACnC,IAAI,CAAC,CAAC,GAAG,KAAK,QAAQ;gBAAE,OAAO,EAAE,CAAC;QACpC,CAAC,CAAC;QACF,MAAM,CAAC,gBAAgB,CAAC,SAAS,EAAE,OAAO,CAAC,CAAC;QAC5C,OAAO,GAAG,EAAE,CAAC,MAAM,CAAC,mBAAmB,CAAC,SAAS,EAAE,OAAO,CAAC,CAAC;IAC9D,CAAC,EAAE,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC,CAAC;IAEpB,IAAI,CAAC,IAAI;QAAE,OAAO,IAAI,CAAC;IAEvB,OAAO,CACL,eACE,IAAI,EAAC,QAAQ,gBACF,MAAM,gBACL,SAAS,mBACP,OAAO,EACrB,SAAS,EAAE,SAAS,aAEpB,iDAEE,SAAS,EAAE,iBAAiB,EAC5B,OAAO,EAAE,OAAO,iBACJ,MAAM,GAClB,EACF,8CAA4B,SAAS,EAAE,cAAc,YAClD,QAAQ,GACL,IACF,CACP,CAAC;AACJ,CAAC"}
1
+ {"version":3,"file":"Modal.js","sourceRoot":"","sources":["../../src/agents/Modal.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAmB,MAAM,wBAAwB,CAAC"}
@@ -23,7 +23,7 @@ export declare function useAuthApi(): AuthApi;
23
23
  /**
24
24
  * Provide an auth API bundle to the subtree. Pyric Studio supplies the
25
25
  * in-process bundle for dev-seed review and the SharedWorker client bundle under
26
- * `pyric dev --ui`.
26
+ * `pyric sandbox --ui`.
27
27
  */
28
28
  export declare function AuthApiProvider({ value, children, }: {
29
29
  value: AuthApi;
@@ -19,7 +19,7 @@ export function useAuthApi() {
19
19
  /**
20
20
  * Provide an auth API bundle to the subtree. Pyric Studio supplies the
21
21
  * in-process bundle for dev-seed review and the SharedWorker client bundle under
22
- * `pyric dev --ui`.
22
+ * `pyric sandbox --ui`.
23
23
  */
24
24
  export function AuthApiProvider({ value, children, }) {
25
25
  return createElement(AuthApiContext.Provider, { value }, children);
@@ -26,7 +26,7 @@ export declare function useFirestoreApi(): FirestoreApi;
26
26
  /**
27
27
  * Provide a Firestore API bundle to the subtree. Pyric Studio wraps its data
28
28
  * surface with this, supplying the in-process bundle for dev-seed review and the
29
- * SharedWorker client bundle under `pyric dev --ui`.
29
+ * SharedWorker client bundle under `pyric sandbox --ui`.
30
30
  */
31
31
  export declare function FirestoreApiProvider({ value, children, }: {
32
32
  value: FirestoreApi;
@@ -21,7 +21,7 @@ export function useFirestoreApi() {
21
21
  /**
22
22
  * Provide a Firestore API bundle to the subtree. Pyric Studio wraps its data
23
23
  * surface with this, supplying the in-process bundle for dev-seed review and the
24
- * SharedWorker client bundle under `pyric dev --ui`.
24
+ * SharedWorker client bundle under `pyric sandbox --ui`.
25
25
  */
26
26
  export function FirestoreApiProvider({ value, children, }) {
27
27
  return createElement(FirestoreApiContext.Provider, { value }, children);
@@ -0,0 +1,38 @@
1
+ import { type ReactNode, type RefObject } from 'react';
2
+ export interface ModalProps {
3
+ /** Controlled open state. */
4
+ open: boolean;
5
+ /** Called when the user dismisses via backdrop click or Escape. */
6
+ onClose: () => void;
7
+ /** Content rendered inside the inner panel. */
8
+ children: ReactNode;
9
+ /** Accessible name for the dialog. */
10
+ ariaLabel?: string;
11
+ /** Accessible label element ID. */
12
+ ariaLabelledBy?: string;
13
+ /** Accessible description element ID. */
14
+ ariaDescribedBy?: string;
15
+ /** Optional ref to focus when the modal opens. Defaults to first focusable element. */
16
+ initialFocusRef?: RefObject<HTMLElement | null>;
17
+ /** Forwarded to the root wrapper (covers the full viewport). */
18
+ className?: string;
19
+ /** Forwarded to the backdrop element. */
20
+ backdropClassName?: string;
21
+ /** Forwarded to the inner panel. */
22
+ panelClassName?: string;
23
+ }
24
+ /**
25
+ * Headless modal primitive — provides accessible behavior:
26
+ * - Traps keyboard focus within panel (`Tab` and `Shift+Tab` cycling)
27
+ * - Restores focus to original trigger element on close
28
+ * - Dismisses on `Escape` key or backdrop click
29
+ * - Emits ARIA dialog attributes (`role="dialog"`, `aria-modal="true"`)
30
+ *
31
+ * Ships zero visual styling. Consumers attach styles via `className` slots
32
+ * or by targeting emitted attributes:
33
+ * [data-pyric-ui="modal"] — root viewport wrapper
34
+ * [data-pyric-modal-backdrop] — clickable backdrop
35
+ * [data-pyric-modal-panel] — inner panel containing children
36
+ */
37
+ export declare function Modal({ open, onClose, children, ariaLabel, ariaLabelledBy, ariaDescribedBy, initialFocusRef, className, backdropClassName, panelClassName, }: ModalProps): import("react").JSX.Element | null;
38
+ //# sourceMappingURL=Modal.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Modal.d.ts","sourceRoot":"","sources":["../../src/primitives/Modal.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAqB,KAAK,SAAS,EAAE,KAAK,SAAS,EAAE,MAAM,OAAO,CAAC;AAE1E,MAAM,WAAW,UAAU;IACzB,6BAA6B;IAC7B,IAAI,EAAE,OAAO,CAAC;IACd,mEAAmE;IACnE,OAAO,EAAE,MAAM,IAAI,CAAC;IACpB,+CAA+C;IAC/C,QAAQ,EAAE,SAAS,CAAC;IACpB,sCAAsC;IACtC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,mCAAmC;IACnC,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,yCAAyC;IACzC,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,uFAAuF;IACvF,eAAe,CAAC,EAAE,SAAS,CAAC,WAAW,GAAG,IAAI,CAAC,CAAC;IAChD,gEAAgE;IAChE,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,yCAAyC;IACzC,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,oCAAoC;IACpC,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB;AAoCD;;;;;;;;;;;;GAYG;AACH,wBAAgB,KAAK,CAAC,EACpB,IAAI,EACJ,OAAO,EACP,QAAQ,EACR,SAAS,EACT,cAAc,EACd,eAAe,EACf,eAAe,EACf,SAAS,EACT,iBAAiB,EACjB,cAAc,GACf,EAAE,UAAU,sCA2IZ"}
@@ -0,0 +1,162 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useEffect, useRef } from 'react';
3
+ const FOCUSABLE_SELECTOR = [
4
+ 'a[href]',
5
+ 'area[href]',
6
+ 'input:not([disabled]):not([type="hidden"])',
7
+ 'select:not([disabled])',
8
+ 'textarea:not([disabled])',
9
+ 'button:not([disabled])',
10
+ 'iframe',
11
+ 'object',
12
+ 'embed',
13
+ '[contenteditable="true"]',
14
+ '[tabindex]:not([tabindex^="-"])',
15
+ ].join(', ');
16
+ function getFocusableElements(container) {
17
+ return Array.from(container.querySelectorAll(FOCUSABLE_SELECTOR)).filter((el) => {
18
+ if (el.disabled)
19
+ return false;
20
+ if (el.hasAttribute('disabled'))
21
+ return false;
22
+ if (el.getAttribute('aria-hidden') === 'true')
23
+ return false;
24
+ if (el.tabIndex < 0)
25
+ return false;
26
+ if (typeof window !== 'undefined' && typeof window.getComputedStyle === 'function') {
27
+ try {
28
+ const style = window.getComputedStyle(el);
29
+ if (style.display === 'none' || style.visibility === 'hidden')
30
+ return false;
31
+ }
32
+ catch {
33
+ // Safe fallback in test environments
34
+ }
35
+ }
36
+ return true;
37
+ });
38
+ }
39
+ /**
40
+ * Headless modal primitive — provides accessible behavior:
41
+ * - Traps keyboard focus within panel (`Tab` and `Shift+Tab` cycling)
42
+ * - Restores focus to original trigger element on close
43
+ * - Dismisses on `Escape` key or backdrop click
44
+ * - Emits ARIA dialog attributes (`role="dialog"`, `aria-modal="true"`)
45
+ *
46
+ * Ships zero visual styling. Consumers attach styles via `className` slots
47
+ * or by targeting emitted attributes:
48
+ * [data-pyric-ui="modal"] — root viewport wrapper
49
+ * [data-pyric-modal-backdrop] — clickable backdrop
50
+ * [data-pyric-modal-panel] — inner panel containing children
51
+ */
52
+ export function Modal({ open, onClose, children, ariaLabel, ariaLabelledBy, ariaDescribedBy, initialFocusRef, className, backdropClassName, panelClassName, }) {
53
+ const panelRef = useRef(null);
54
+ const previouslyFocused = useRef(null);
55
+ const onCloseRef = useRef(onClose);
56
+ onCloseRef.current = onClose;
57
+ const initialFocusRefProp = useRef(initialFocusRef);
58
+ initialFocusRefProp.current = initialFocusRef;
59
+ // Focus management: capture activeElement on open, set initial focus,
60
+ // and restore focus to trigger element on close / unmount.
61
+ useEffect(() => {
62
+ if (!open)
63
+ return;
64
+ previouslyFocused.current = document.activeElement ?? null;
65
+ const panel = panelRef.current;
66
+ if (panel) {
67
+ const preferred = initialFocusRefProp.current?.current;
68
+ if (preferred && typeof preferred.focus === 'function') {
69
+ preferred.focus();
70
+ }
71
+ else {
72
+ const focusables = getFocusableElements(panel);
73
+ if (focusables.length > 0) {
74
+ focusables[0].focus();
75
+ }
76
+ else {
77
+ panel.focus();
78
+ }
79
+ }
80
+ }
81
+ return () => {
82
+ const prev = previouslyFocused.current;
83
+ if (prev && typeof prev.focus === 'function' && prev.isConnected !== false) {
84
+ prev.focus();
85
+ queueMicrotask(() => {
86
+ if (document.activeElement !== prev && typeof prev.focus === 'function' && prev.isConnected !== false) {
87
+ prev.focus();
88
+ }
89
+ });
90
+ }
91
+ };
92
+ }, [open]);
93
+ // Keyboard navigation: Escape-to-close and Tab/Shift+Tab focus cycling
94
+ useEffect(() => {
95
+ if (!open)
96
+ return;
97
+ const handleKeyDown = (e) => {
98
+ if (e.key === 'Escape') {
99
+ e.stopPropagation();
100
+ onCloseRef.current();
101
+ return;
102
+ }
103
+ if (e.key === 'Tab') {
104
+ const panel = panelRef.current;
105
+ if (!panel)
106
+ return;
107
+ const focusables = getFocusableElements(panel);
108
+ if (focusables.length === 0) {
109
+ e.preventDefault();
110
+ panel.focus();
111
+ return;
112
+ }
113
+ const first = focusables[0];
114
+ const last = focusables[focusables.length - 1];
115
+ const active = document.activeElement;
116
+ let currentIndex = focusables.indexOf(active);
117
+ if (currentIndex === -1 && active && panel.contains(active)) {
118
+ const parentFocusable = active.closest?.(FOCUSABLE_SELECTOR);
119
+ if (parentFocusable) {
120
+ currentIndex = focusables.indexOf(parentFocusable);
121
+ }
122
+ }
123
+ if (currentIndex === -1) {
124
+ e.preventDefault();
125
+ if (e.shiftKey) {
126
+ last.focus();
127
+ }
128
+ else {
129
+ first.focus();
130
+ }
131
+ return;
132
+ }
133
+ if (e.shiftKey) {
134
+ // Shift + Tab (backward)
135
+ e.preventDefault();
136
+ if (currentIndex <= 0) {
137
+ last.focus();
138
+ }
139
+ else {
140
+ focusables[currentIndex - 1].focus();
141
+ }
142
+ }
143
+ else {
144
+ // Tab (forward)
145
+ e.preventDefault();
146
+ if (currentIndex >= focusables.length - 1) {
147
+ first.focus();
148
+ }
149
+ else {
150
+ focusables[currentIndex + 1].focus();
151
+ }
152
+ }
153
+ }
154
+ };
155
+ window.addEventListener('keydown', handleKeyDown);
156
+ return () => window.removeEventListener('keydown', handleKeyDown);
157
+ }, [open]);
158
+ if (!open)
159
+ return null;
160
+ return (_jsxs("div", { role: "dialog", "aria-modal": "true", "aria-label": ariaLabel, "aria-labelledby": ariaLabelledBy, "aria-describedby": ariaDescribedBy, "data-pyric-ui": "modal", className: className, children: [_jsx("div", { "data-pyric-modal-backdrop": true, className: backdropClassName, onClick: onClose, "aria-hidden": "true" }), _jsx("div", { ref: panelRef, tabIndex: -1, "data-pyric-modal-panel": true, className: panelClassName, children: children })] }));
161
+ }
162
+ //# sourceMappingURL=Modal.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Modal.js","sourceRoot":"","sources":["../../src/primitives/Modal.tsx"],"names":[],"mappings":"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"}
@@ -8,4 +8,5 @@ export { ToastProvider, useToast, type ToastInput, type ToastRecord, type ToastK
8
8
  export { VirtualList, type VirtualListProps } from './VirtualList.js';
9
9
  export { useContainerSize, type ContainerSize, type UseContainerSizeOptions, } from './hooks/useContainerSize.js';
10
10
  export { useUpdateHighlights, type UpdateHighlight, type UpdateHighlightKind, type UseUpdateHighlightsOptions, } from './hooks/useUpdateHighlights.js';
11
+ export { Modal, type ModalProps } from './Modal.js';
11
12
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/primitives/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,KAAK,eAAe,EAAE,MAAM,iBAAiB,CAAC;AACnE,OAAO,EAAE,KAAK,EAAE,KAAK,UAAU,EAAE,MAAM,YAAY,CAAC;AACpD,OAAO,EACL,QAAQ,EACR,KAAK,aAAa,GACnB,MAAM,eAAe,CAAC;AACvB,OAAO,EACL,gBAAgB,EAChB,KAAK,qBAAqB,EAC1B,KAAK,eAAe,GACrB,MAAM,uBAAuB,CAAC;AAC/B,OAAO,EAAE,aAAa,EAAE,KAAK,kBAAkB,EAAE,MAAM,oBAAoB,CAAC;AAC5E,OAAO,EACL,eAAe,EACf,UAAU,EACV,KAAK,cAAc,EACnB,KAAK,SAAS,EACd,KAAK,oBAAoB,GAC1B,MAAM,iBAAiB,CAAC;AACzB,OAAO,EACL,aAAa,EACb,QAAQ,EACR,KAAK,UAAU,EACf,KAAK,WAAW,EAChB,KAAK,SAAS,EACd,KAAK,kBAAkB,GACxB,MAAM,YAAY,CAAC;AACpB,OAAO,EAAE,WAAW,EAAE,KAAK,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AACtE,OAAO,EACL,gBAAgB,EAChB,KAAK,aAAa,EAClB,KAAK,uBAAuB,GAC7B,MAAM,6BAA6B,CAAC;AACrC,OAAO,EACL,mBAAmB,EACnB,KAAK,eAAe,EACpB,KAAK,mBAAmB,EACxB,KAAK,0BAA0B,GAChC,MAAM,gCAAgC,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/primitives/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,KAAK,eAAe,EAAE,MAAM,iBAAiB,CAAC;AACnE,OAAO,EAAE,KAAK,EAAE,KAAK,UAAU,EAAE,MAAM,YAAY,CAAC;AACpD,OAAO,EACL,QAAQ,EACR,KAAK,aAAa,GACnB,MAAM,eAAe,CAAC;AACvB,OAAO,EACL,gBAAgB,EAChB,KAAK,qBAAqB,EAC1B,KAAK,eAAe,GACrB,MAAM,uBAAuB,CAAC;AAC/B,OAAO,EAAE,aAAa,EAAE,KAAK,kBAAkB,EAAE,MAAM,oBAAoB,CAAC;AAC5E,OAAO,EACL,eAAe,EACf,UAAU,EACV,KAAK,cAAc,EACnB,KAAK,SAAS,EACd,KAAK,oBAAoB,GAC1B,MAAM,iBAAiB,CAAC;AACzB,OAAO,EACL,aAAa,EACb,QAAQ,EACR,KAAK,UAAU,EACf,KAAK,WAAW,EAChB,KAAK,SAAS,EACd,KAAK,kBAAkB,GACxB,MAAM,YAAY,CAAC;AACpB,OAAO,EAAE,WAAW,EAAE,KAAK,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AACtE,OAAO,EACL,gBAAgB,EAChB,KAAK,aAAa,EAClB,KAAK,uBAAuB,GAC7B,MAAM,6BAA6B,CAAC;AACrC,OAAO,EACL,mBAAmB,EACnB,KAAK,eAAe,EACpB,KAAK,mBAAmB,EACxB,KAAK,0BAA0B,GAChC,MAAM,gCAAgC,CAAC;AACxC,OAAO,EAAE,KAAK,EAAE,KAAK,UAAU,EAAE,MAAM,YAAY,CAAC"}
@@ -8,4 +8,5 @@ export { ToastProvider, useToast, } from './Toast.js';
8
8
  export { VirtualList } from './VirtualList.js';
9
9
  export { useContainerSize, } from './hooks/useContainerSize.js';
10
10
  export { useUpdateHighlights, } from './hooks/useUpdateHighlights.js';
11
+ export { Modal } from './Modal.js';
11
12
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/primitives/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAwB,MAAM,iBAAiB,CAAC;AACnE,OAAO,EAAE,KAAK,EAAmB,MAAM,YAAY,CAAC;AACpD,OAAO,EACL,QAAQ,GAET,MAAM,eAAe,CAAC;AACvB,OAAO,EACL,gBAAgB,GAGjB,MAAM,uBAAuB,CAAC;AAC/B,OAAO,EAAE,aAAa,EAA2B,MAAM,oBAAoB,CAAC;AAC5E,OAAO,EACL,eAAe,EACf,UAAU,GAIX,MAAM,iBAAiB,CAAC;AACzB,OAAO,EACL,aAAa,EACb,QAAQ,GAKT,MAAM,YAAY,CAAC;AACpB,OAAO,EAAE,WAAW,EAAyB,MAAM,kBAAkB,CAAC;AACtE,OAAO,EACL,gBAAgB,GAGjB,MAAM,6BAA6B,CAAC;AACrC,OAAO,EACL,mBAAmB,GAIpB,MAAM,gCAAgC,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/primitives/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAwB,MAAM,iBAAiB,CAAC;AACnE,OAAO,EAAE,KAAK,EAAmB,MAAM,YAAY,CAAC;AACpD,OAAO,EACL,QAAQ,GAET,MAAM,eAAe,CAAC;AACvB,OAAO,EACL,gBAAgB,GAGjB,MAAM,uBAAuB,CAAC;AAC/B,OAAO,EAAE,aAAa,EAA2B,MAAM,oBAAoB,CAAC;AAC5E,OAAO,EACL,eAAe,EACf,UAAU,GAIX,MAAM,iBAAiB,CAAC;AACzB,OAAO,EACL,aAAa,EACb,QAAQ,GAKT,MAAM,YAAY,CAAC;AACpB,OAAO,EAAE,WAAW,EAAyB,MAAM,kBAAkB,CAAC;AACtE,OAAO,EACL,gBAAgB,GAGjB,MAAM,6BAA6B,CAAC;AACrC,OAAO,EACL,mBAAmB,GAIpB,MAAM,gCAAgC,CAAC;AACxC,OAAO,EAAE,KAAK,EAAmB,MAAM,YAAY,CAAC"}
@@ -6,9 +6,8 @@ export type StorageRulesGateStatus = 'idle' | 'loading' | 'ready' | 'error';
6
6
  * - `'sandbox'` — the ruleset deployed on the sandbox handle
7
7
  * (`getStorageSandbox(ctx, { rules })`), read off the handle's
8
8
  * `StorageService`.
9
- * - `'none'` — no rules reachable. Every verdict allows
10
- * (open-by-default, the same semantics `pyric/storage`'s
11
- * enforcement layer applies when no rules are configured).
9
+ * - `'none'` — no rules reachable. Once resolution completes, every verdict
10
+ * denies, matching `pyric/storage`'s fail-closed enforcement.
12
11
  */
13
12
  export type StorageRulesSource = 'option' | 'sandbox' | 'none';
14
13
  /**
@@ -80,10 +79,8 @@ export interface UseStorageRulesGateResult {
80
79
  /**
81
80
  * Evaluate an arbitrary path under the current ruleset + identity.
82
81
  * Pure and synchronous once `status` is `'ready'`; before that
83
- * (and whenever no rules are reachable) it returns the allow-all
84
- * verdict the gate FAILS OPEN, because affordances are advisory
85
- * and the real enforcement (sandbox throw / server denial) stays
86
- * authoritative.
82
+ * it returns the allow-all verdict. A ready rules-less handle returns the
83
+ * deny-all verdict, matching the enforcement layer's default.
87
84
  */
88
85
  verdictFor: (path: string) => StorageGateVerdict;
89
86
  /** Rules-resolution failure (e.g. a malformed `rules` string). */
@@ -1 +1 @@
1
- {"version":3,"file":"useStorageRulesGate.d.ts","sourceRoot":"","sources":["../../../src/storage/hooks/useStorageRulesGate.ts"],"names":[],"mappings":"AACA,OAAO,EAIL,KAAK,eAAe,EACpB,KAAK,WAAW,EAEhB,KAAK,YAAY,EAClB,MAAM,eAAe,CAAC;AAGvB,MAAM,MAAM,sBAAsB,GAAG,MAAM,GAAG,SAAS,GAAG,OAAO,GAAG,OAAO,CAAC;AAE5E;;;;;;;;;GASG;AACH,MAAM,MAAM,kBAAkB,GAAG,QAAQ,GAAG,SAAS,GAAG,MAAM,CAAC;AAE/D;;;;;GAKG;AACH,MAAM,WAAW,kBAAkB;IACjC,IAAI,EAAE,OAAO,CAAC;IACd,KAAK,EAAE,OAAO,CAAC;IACf,8DAA8D;IAC9D,MAAM,EAAE,OAAO,CAAC;IAChB,qEAAqE;IACrE,MAAM,EAAE,OAAO,CAAC;IAChB;;;;OAIG;IACH,OAAO,EAAE;QAAE,IAAI,EAAE,MAAM,EAAE,CAAC;QAAC,KAAK,EAAE,MAAM,EAAE,CAAA;KAAE,CAAC;CAC9C;AAED,MAAM,WAAW,0BAA0B;IACzC;;;;OAIG;IACH,KAAK,CAAC,EAAE,MAAM,GAAG,SAAS,MAAM,EAAE,CAAC;IACnC;;;;;OAKG;IACH,KAAK,CAAC,EAAE,MAAM,GAAG,YAAY,CAAC;IAC9B;;;OAGG;IACH,QAAQ,CAAC,EAAE,WAAW,GAAG,IAAI,CAAC;IAC9B;;;;;;;OAOG;IACH,aAAa,CAAC,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,WAAW,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;CACxD;AAED,MAAM,WAAW,yBAAyB;IACxC,sDAAsD;IACtD,MAAM,EAAE,sBAAsB,CAAC;IAC/B,0CAA0C;IAC1C,MAAM,EAAE,kBAAkB,CAAC;IAC3B,iEAAiE;IACjE,QAAQ,EAAE,OAAO,CAAC;IAClB,gDAAgD;IAChD,QAAQ,EAAE,WAAW,GAAG,IAAI,CAAC;IAC7B,4EAA4E;IAC5E,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,kBAAkB,CAAC,CAAC;IAC7C;;;;;;;OAOG;IACH,UAAU,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,kBAAkB,CAAC;IACjD,kEAAkE;IAClE,KAAK,EAAE,KAAK,GAAG,SAAS,CAAC;CAC1B;AAqBD;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAgB,mBAAmB,CACjC,OAAO,EAAE,eAAe,GAAG,IAAI,GAAG,SAAS,EAC3C,OAAO,GAAE,0BAA+B,GACvC,yBAAyB,CA6H3B"}
1
+ {"version":3,"file":"useStorageRulesGate.d.ts","sourceRoot":"","sources":["../../../src/storage/hooks/useStorageRulesGate.ts"],"names":[],"mappings":"AACA,OAAO,EAIL,KAAK,eAAe,EACpB,KAAK,WAAW,EAEhB,KAAK,YAAY,EAClB,MAAM,eAAe,CAAC;AAGvB,MAAM,MAAM,sBAAsB,GAAG,MAAM,GAAG,SAAS,GAAG,OAAO,GAAG,OAAO,CAAC;AAE5E;;;;;;;;GAQG;AACH,MAAM,MAAM,kBAAkB,GAAG,QAAQ,GAAG,SAAS,GAAG,MAAM,CAAC;AAE/D;;;;;GAKG;AACH,MAAM,WAAW,kBAAkB;IACjC,IAAI,EAAE,OAAO,CAAC;IACd,KAAK,EAAE,OAAO,CAAC;IACf,8DAA8D;IAC9D,MAAM,EAAE,OAAO,CAAC;IAChB,qEAAqE;IACrE,MAAM,EAAE,OAAO,CAAC;IAChB;;;;OAIG;IACH,OAAO,EAAE;QAAE,IAAI,EAAE,MAAM,EAAE,CAAC;QAAC,KAAK,EAAE,MAAM,EAAE,CAAA;KAAE,CAAC;CAC9C;AAED,MAAM,WAAW,0BAA0B;IACzC;;;;OAIG;IACH,KAAK,CAAC,EAAE,MAAM,GAAG,SAAS,MAAM,EAAE,CAAC;IACnC;;;;;OAKG;IACH,KAAK,CAAC,EAAE,MAAM,GAAG,YAAY,CAAC;IAC9B;;;OAGG;IACH,QAAQ,CAAC,EAAE,WAAW,GAAG,IAAI,CAAC;IAC9B;;;;;;;OAOG;IACH,aAAa,CAAC,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,WAAW,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;CACxD;AAED,MAAM,WAAW,yBAAyB;IACxC,sDAAsD;IACtD,MAAM,EAAE,sBAAsB,CAAC;IAC/B,0CAA0C;IAC1C,MAAM,EAAE,kBAAkB,CAAC;IAC3B,iEAAiE;IACjE,QAAQ,EAAE,OAAO,CAAC;IAClB,gDAAgD;IAChD,QAAQ,EAAE,WAAW,GAAG,IAAI,CAAC;IAC7B,4EAA4E;IAC5E,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,kBAAkB,CAAC,CAAC;IAC7C;;;;;OAKG;IACH,UAAU,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,kBAAkB,CAAC;IACjD,kEAAkE;IAClE,KAAK,EAAE,KAAK,GAAG,SAAS,CAAC;CAC1B;AAiCD;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAgB,mBAAmB,CACjC,OAAO,EAAE,eAAe,GAAG,IAAI,GAAG,SAAS,EAC3C,OAAO,GAAE,0BAA+B,GACvC,yBAAyB,CA6H3B"}
@@ -12,6 +12,17 @@ const ALLOW_ALL = Object.freeze({
12
12
  write: [],
13
13
  }),
14
14
  });
15
+ /** Shared fail-closed verdict for a ready handle with no deployed rules. */
16
+ const DENY_ALL = Object.freeze({
17
+ read: false,
18
+ write: false,
19
+ delete: false,
20
+ upload: false,
21
+ reasons: Object.freeze({
22
+ read: ['No Storage rules configured; default deny.'],
23
+ write: ['No Storage rules configured; default deny.'],
24
+ }),
25
+ });
15
26
  /**
16
27
  * Pre-flight rules evaluation — the M7 differentiator. Evaluates the
17
28
  * current identity against paths BEFORE the click, so components can
@@ -98,7 +109,7 @@ export function useStorageRulesGate(storage, options = {}) {
98
109
  const writeContentType = writeResource?.contentType;
99
110
  const verdictFor = useCallback((path) => {
100
111
  if (rules == null)
101
- return ALLOW_ALL;
112
+ return status === 'ready' ? DENY_ALL : ALLOW_ALL;
102
113
  const objectPath = normalizeStoragePath(path);
103
114
  const bucket = target?.bucket ?? 'pyric-default';
104
115
  const rulesPath = objectPath === '' ? `b/${bucket}/o` : `b/${bucket}/o/${objectPath}`;
@@ -127,7 +138,7 @@ export function useStorageRulesGate(storage, options = {}) {
127
138
  write: write.allowed ? [] : write.reasons,
128
139
  },
129
140
  };
130
- }, [rules, target?.bucket, identity, writeSize, writeContentType]);
141
+ }, [rules, status, target?.bucket, identity, writeSize, writeContentType]);
131
142
  const pathsKey = typeof paths === 'string' ? paths : (paths ?? []).join('\n');
132
143
  const verdicts = useMemo(() => {
133
144
  const list = typeof paths === 'string' ? [paths] : (paths ?? []);
@@ -1 +1 @@
1
- {"version":3,"file":"useStorageRulesGate.js","sourceRoot":"","sources":["../../../src/storage/hooks/useStorageRulesGate.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"useStorageRulesGate.js","sourceRoot":"","sources":["../../../src/storage/hooks/useStorageRulesGate.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pyric/ui",
3
- "version": "0.1.0-alpha.19",
3
+ "version": "0.1.0-alpha.21",
4
4
  "license": "Apache-2.0",
5
5
  "homepage": "https://pyric.dev",
6
6
  "repository": {
@@ -93,7 +93,7 @@
93
93
  "react": ">=18",
94
94
  "react-dom": ">=18",
95
95
  "firebase": ">=12",
96
- "pyric": "^0.1.0-alpha.19"
96
+ "pyric": "^0.1.0-alpha.21"
97
97
  },
98
98
  "peerDependenciesMeta": {
99
99
  "@inbrowser/agent": {
@@ -102,15 +102,15 @@
102
102
  },
103
103
  "devDependencies": {
104
104
  "@testing-library/react": "^16.1.0",
105
- "@types/bun": "latest",
105
+ "@types/bun": "1.3.14",
106
106
  "@types/react": "^19.0.0",
107
107
  "@types/react-dom": "^19.0.0",
108
108
  "@types/react-test-renderer": "^19.1.0",
109
109
  "fake-indexeddb": "^6.0.0",
110
110
  "firebase": "^12.12.0",
111
111
  "jsdom": "^29.1.1",
112
- "pyric": "^0.1.0-alpha.19",
113
- "pyric-admin": "^0.1.0-alpha.19",
112
+ "pyric": "^0.1.0-alpha.21",
113
+ "pyric-admin": "^0.1.0-alpha.21",
114
114
  "react": "^19.0.0",
115
115
  "react-dom": "^19.0.0",
116
116
  "react-test-renderer": "^19.2.6",
@@ -1,65 +1 @@
1
- import { useEffect, type ReactNode } from 'react';
2
-
3
- export interface ModalProps {
4
- open: boolean;
5
- onClose: () => void;
6
- children: ReactNode;
7
- ariaLabel?: string;
8
- /** Forwarded to the root wrapper (covers the full viewport). */
9
- className?: string;
10
- /** Forwarded to the backdrop element. */
11
- backdropClassName?: string;
12
- /** Forwarded to the inner panel. */
13
- panelClassName?: string;
14
- }
15
-
16
- /**
17
- * Headless modal — provides behavior only (Escape-to-close, backdrop
18
- * click, `aria-modal`). Consumers attach all visual styling via the
19
- * three `className` slots or by selecting on the emitted data
20
- * attributes:
21
- *
22
- * [data-pyric-ui="modal"] — root viewport wrapper
23
- * [data-pyric-modal-backdrop] — clickable backdrop
24
- * [data-pyric-modal-panel] — inner panel containing children
25
- */
26
- export function Modal({
27
- open,
28
- onClose,
29
- children,
30
- ariaLabel,
31
- className,
32
- backdropClassName,
33
- panelClassName,
34
- }: ModalProps) {
35
- useEffect(() => {
36
- if (!open) return;
37
- const handler = (e: KeyboardEvent) => {
38
- if (e.key === 'Escape') onClose();
39
- };
40
- window.addEventListener('keydown', handler);
41
- return () => window.removeEventListener('keydown', handler);
42
- }, [open, onClose]);
43
-
44
- if (!open) return null;
45
-
46
- return (
47
- <div
48
- role="dialog"
49
- aria-modal="true"
50
- aria-label={ariaLabel}
51
- data-pyric-ui="modal"
52
- className={className}
53
- >
54
- <div
55
- data-pyric-modal-backdrop
56
- className={backdropClassName}
57
- onClick={onClose}
58
- aria-hidden="true"
59
- />
60
- <div data-pyric-modal-panel className={panelClassName}>
61
- {children}
62
- </div>
63
- </div>
64
- );
65
- }
1
+ export { Modal, type ModalProps } from '../primitives/Modal.js';
@@ -59,7 +59,7 @@ export function useAuthApi(): AuthApi {
59
59
  /**
60
60
  * Provide an auth API bundle to the subtree. Pyric Studio supplies the
61
61
  * in-process bundle for dev-seed review and the SharedWorker client bundle under
62
- * `pyric dev --ui`.
62
+ * `pyric sandbox --ui`.
63
63
  */
64
64
  export function AuthApiProvider({
65
65
  value,
@@ -73,7 +73,7 @@ export function useFirestoreApi(): FirestoreApi {
73
73
  /**
74
74
  * Provide a Firestore API bundle to the subtree. Pyric Studio wraps its data
75
75
  * surface with this, supplying the in-process bundle for dev-seed review and the
76
- * SharedWorker client bundle under `pyric dev --ui`.
76
+ * SharedWorker client bundle under `pyric sandbox --ui`.
77
77
  */
78
78
  export function FirestoreApiProvider({
79
79
  value,
@@ -0,0 +1,223 @@
1
+ import { useEffect, useRef, type ReactNode, type RefObject } from 'react';
2
+
3
+ export interface ModalProps {
4
+ /** Controlled open state. */
5
+ open: boolean;
6
+ /** Called when the user dismisses via backdrop click or Escape. */
7
+ onClose: () => void;
8
+ /** Content rendered inside the inner panel. */
9
+ children: ReactNode;
10
+ /** Accessible name for the dialog. */
11
+ ariaLabel?: string;
12
+ /** Accessible label element ID. */
13
+ ariaLabelledBy?: string;
14
+ /** Accessible description element ID. */
15
+ ariaDescribedBy?: string;
16
+ /** Optional ref to focus when the modal opens. Defaults to first focusable element. */
17
+ initialFocusRef?: RefObject<HTMLElement | null>;
18
+ /** Forwarded to the root wrapper (covers the full viewport). */
19
+ className?: string;
20
+ /** Forwarded to the backdrop element. */
21
+ backdropClassName?: string;
22
+ /** Forwarded to the inner panel. */
23
+ panelClassName?: string;
24
+ }
25
+
26
+ const FOCUSABLE_SELECTOR = [
27
+ 'a[href]',
28
+ 'area[href]',
29
+ 'input:not([disabled]):not([type="hidden"])',
30
+ 'select:not([disabled])',
31
+ 'textarea:not([disabled])',
32
+ 'button:not([disabled])',
33
+ 'iframe',
34
+ 'object',
35
+ 'embed',
36
+ '[contenteditable="true"]',
37
+ '[tabindex]:not([tabindex^="-"])',
38
+ ].join(', ');
39
+
40
+ function getFocusableElements(container: HTMLElement): HTMLElement[] {
41
+ return Array.from(
42
+ container.querySelectorAll<HTMLElement>(FOCUSABLE_SELECTOR),
43
+ ).filter((el) => {
44
+ if ((el as any).disabled) return false;
45
+ if (el.hasAttribute('disabled')) return false;
46
+ if (el.getAttribute('aria-hidden') === 'true') return false;
47
+ if (el.tabIndex < 0) return false;
48
+ if (typeof window !== 'undefined' && typeof window.getComputedStyle === 'function') {
49
+ try {
50
+ const style = window.getComputedStyle(el);
51
+ if (style.display === 'none' || style.visibility === 'hidden') return false;
52
+ } catch {
53
+ // Safe fallback in test environments
54
+ }
55
+ }
56
+ return true;
57
+ });
58
+ }
59
+
60
+ /**
61
+ * Headless modal primitive — provides accessible behavior:
62
+ * - Traps keyboard focus within panel (`Tab` and `Shift+Tab` cycling)
63
+ * - Restores focus to original trigger element on close
64
+ * - Dismisses on `Escape` key or backdrop click
65
+ * - Emits ARIA dialog attributes (`role="dialog"`, `aria-modal="true"`)
66
+ *
67
+ * Ships zero visual styling. Consumers attach styles via `className` slots
68
+ * or by targeting emitted attributes:
69
+ * [data-pyric-ui="modal"] — root viewport wrapper
70
+ * [data-pyric-modal-backdrop] — clickable backdrop
71
+ * [data-pyric-modal-panel] — inner panel containing children
72
+ */
73
+ export function Modal({
74
+ open,
75
+ onClose,
76
+ children,
77
+ ariaLabel,
78
+ ariaLabelledBy,
79
+ ariaDescribedBy,
80
+ initialFocusRef,
81
+ className,
82
+ backdropClassName,
83
+ panelClassName,
84
+ }: ModalProps) {
85
+ const panelRef = useRef<HTMLDivElement | null>(null);
86
+ const previouslyFocused = useRef<HTMLElement | null>(null);
87
+ const onCloseRef = useRef(onClose);
88
+ onCloseRef.current = onClose;
89
+ const initialFocusRefProp = useRef(initialFocusRef);
90
+ initialFocusRefProp.current = initialFocusRef;
91
+
92
+ // Focus management: capture activeElement on open, set initial focus,
93
+ // and restore focus to trigger element on close / unmount.
94
+ useEffect(() => {
95
+ if (!open) return;
96
+
97
+ previouslyFocused.current = (document.activeElement as HTMLElement) ?? null;
98
+
99
+ const panel = panelRef.current;
100
+ if (panel) {
101
+ const preferred = initialFocusRefProp.current?.current;
102
+ if (preferred && typeof preferred.focus === 'function') {
103
+ preferred.focus();
104
+ } else {
105
+ const focusables = getFocusableElements(panel);
106
+ if (focusables.length > 0) {
107
+ focusables[0].focus();
108
+ } else {
109
+ panel.focus();
110
+ }
111
+ }
112
+ }
113
+
114
+ return () => {
115
+ const prev = previouslyFocused.current;
116
+ if (prev && typeof prev.focus === 'function' && prev.isConnected !== false) {
117
+ prev.focus();
118
+ queueMicrotask(() => {
119
+ if (document.activeElement !== prev && typeof prev.focus === 'function' && prev.isConnected !== false) {
120
+ prev.focus();
121
+ }
122
+ });
123
+ }
124
+ };
125
+ }, [open]);
126
+
127
+ // Keyboard navigation: Escape-to-close and Tab/Shift+Tab focus cycling
128
+ useEffect(() => {
129
+ if (!open) return;
130
+
131
+ const handleKeyDown = (e: KeyboardEvent) => {
132
+ if (e.key === 'Escape') {
133
+ e.stopPropagation();
134
+ onCloseRef.current();
135
+ return;
136
+ }
137
+
138
+ if (e.key === 'Tab') {
139
+ const panel = panelRef.current;
140
+ if (!panel) return;
141
+
142
+ const focusables = getFocusableElements(panel);
143
+ if (focusables.length === 0) {
144
+ e.preventDefault();
145
+ panel.focus();
146
+ return;
147
+ }
148
+
149
+ const first = focusables[0];
150
+ const last = focusables[focusables.length - 1];
151
+ const active = document.activeElement;
152
+ let currentIndex = focusables.indexOf(active as HTMLElement);
153
+
154
+ if (currentIndex === -1 && active && panel.contains(active)) {
155
+ const parentFocusable = (active as HTMLElement).closest?.(FOCUSABLE_SELECTOR);
156
+ if (parentFocusable) {
157
+ currentIndex = focusables.indexOf(parentFocusable as HTMLElement);
158
+ }
159
+ }
160
+
161
+ if (currentIndex === -1) {
162
+ e.preventDefault();
163
+ if (e.shiftKey) {
164
+ last.focus();
165
+ } else {
166
+ first.focus();
167
+ }
168
+ return;
169
+ }
170
+
171
+ if (e.shiftKey) {
172
+ // Shift + Tab (backward)
173
+ e.preventDefault();
174
+ if (currentIndex <= 0) {
175
+ last.focus();
176
+ } else {
177
+ focusables[currentIndex - 1].focus();
178
+ }
179
+ } else {
180
+ // Tab (forward)
181
+ e.preventDefault();
182
+ if (currentIndex >= focusables.length - 1) {
183
+ first.focus();
184
+ } else {
185
+ focusables[currentIndex + 1].focus();
186
+ }
187
+ }
188
+ }
189
+ };
190
+
191
+ window.addEventListener('keydown', handleKeyDown);
192
+ return () => window.removeEventListener('keydown', handleKeyDown);
193
+ }, [open]);
194
+
195
+ if (!open) return null;
196
+
197
+ return (
198
+ <div
199
+ role="dialog"
200
+ aria-modal="true"
201
+ aria-label={ariaLabel}
202
+ aria-labelledby={ariaLabelledBy}
203
+ aria-describedby={ariaDescribedBy}
204
+ data-pyric-ui="modal"
205
+ className={className}
206
+ >
207
+ <div
208
+ data-pyric-modal-backdrop
209
+ className={backdropClassName}
210
+ onClick={onClose}
211
+ aria-hidden="true"
212
+ />
213
+ <div
214
+ ref={panelRef}
215
+ tabIndex={-1}
216
+ data-pyric-modal-panel
217
+ className={panelClassName}
218
+ >
219
+ {children}
220
+ </div>
221
+ </div>
222
+ );
223
+ }
@@ -37,3 +37,4 @@ export {
37
37
  type UpdateHighlightKind,
38
38
  type UseUpdateHighlightsOptions,
39
39
  } from './hooks/useUpdateHighlights.js';
40
+ export { Modal, type ModalProps } from './Modal.js';
@@ -18,9 +18,8 @@ export type StorageRulesGateStatus = 'idle' | 'loading' | 'ready' | 'error';
18
18
  * - `'sandbox'` — the ruleset deployed on the sandbox handle
19
19
  * (`getStorageSandbox(ctx, { rules })`), read off the handle's
20
20
  * `StorageService`.
21
- * - `'none'` — no rules reachable. Every verdict allows
22
- * (open-by-default, the same semantics `pyric/storage`'s
23
- * enforcement layer applies when no rules are configured).
21
+ * - `'none'` — no rules reachable. Once resolution completes, every verdict
22
+ * denies, matching `pyric/storage`'s fail-closed enforcement.
24
23
  */
25
24
  export type StorageRulesSource = 'option' | 'sandbox' | 'none';
26
25
 
@@ -89,10 +88,8 @@ export interface UseStorageRulesGateResult {
89
88
  /**
90
89
  * Evaluate an arbitrary path under the current ruleset + identity.
91
90
  * Pure and synchronous once `status` is `'ready'`; before that
92
- * (and whenever no rules are reachable) it returns the allow-all
93
- * verdict the gate FAILS OPEN, because affordances are advisory
94
- * and the real enforcement (sandbox throw / server denial) stays
95
- * authoritative.
91
+ * it returns the allow-all verdict. A ready rules-less handle returns the
92
+ * deny-all verdict, matching the enforcement layer's default.
96
93
  */
97
94
  verdictFor: (path: string) => StorageGateVerdict;
98
95
  /** Rules-resolution failure (e.g. a malformed `rules` string). */
@@ -111,6 +108,18 @@ const ALLOW_ALL: StorageGateVerdict = Object.freeze({
111
108
  }) as StorageGateVerdict['reasons'],
112
109
  });
113
110
 
111
+ /** Shared fail-closed verdict for a ready handle with no deployed rules. */
112
+ const DENY_ALL: StorageGateVerdict = Object.freeze({
113
+ read: false,
114
+ write: false,
115
+ delete: false,
116
+ upload: false,
117
+ reasons: Object.freeze({
118
+ read: ['No Storage rules configured; default deny.'],
119
+ write: ['No Storage rules configured; default deny.'],
120
+ }) as StorageGateVerdict['reasons'],
121
+ });
122
+
114
123
  interface RulesState {
115
124
  status: StorageRulesGateStatus;
116
125
  rules: StorageRules | null;
@@ -212,7 +221,7 @@ export function useStorageRulesGate(
212
221
 
213
222
  const verdictFor = useCallback(
214
223
  (path: string): StorageGateVerdict => {
215
- if (rules == null) return ALLOW_ALL;
224
+ if (rules == null) return status === 'ready' ? DENY_ALL : ALLOW_ALL;
216
225
  const objectPath = normalizeStoragePath(path);
217
226
  const bucket = target?.bucket ?? 'pyric-default';
218
227
  const rulesPath = objectPath === '' ? `b/${bucket}/o` : `b/${bucket}/o/${objectPath}`;
@@ -243,7 +252,7 @@ export function useStorageRulesGate(
243
252
  },
244
253
  };
245
254
  },
246
- [rules, target?.bucket, identity, writeSize, writeContentType],
255
+ [rules, status, target?.bucket, identity, writeSize, writeContentType],
247
256
  );
248
257
 
249
258
  const pathsKey = typeof paths === 'string' ? paths : (paths ?? []).join('\n');