@pyric/ui 0.1.0-alpha.20 → 0.1.0-alpha.22
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/agents/Modal.d.ts +1 -24
- package/dist/agents/Modal.d.ts.map +1 -1
- package/dist/agents/Modal.js +1 -27
- package/dist/agents/Modal.js.map +1 -1
- package/dist/auth/authApi.d.ts +1 -1
- package/dist/auth/authApi.js +1 -1
- package/dist/firestore/firestoreApi.d.ts +1 -1
- package/dist/firestore/firestoreApi.js +1 -1
- package/dist/primitives/Modal.d.ts +38 -0
- package/dist/primitives/Modal.d.ts.map +1 -0
- package/dist/primitives/Modal.js +162 -0
- package/dist/primitives/Modal.js.map +1 -0
- package/dist/primitives/index.d.ts +1 -0
- package/dist/primitives/index.d.ts.map +1 -1
- package/dist/primitives/index.js +1 -0
- package/dist/primitives/index.js.map +1 -1
- package/dist/storage/hooks/useStorageRulesGate.d.ts +4 -7
- package/dist/storage/hooks/useStorageRulesGate.d.ts.map +1 -1
- package/dist/storage/hooks/useStorageRulesGate.js +13 -2
- package/dist/storage/hooks/useStorageRulesGate.js.map +1 -1
- package/package.json +4 -4
- package/src/agents/Modal.tsx +1 -65
- package/src/auth/authApi.ts +1 -1
- package/src/firestore/firestoreApi.ts +1 -1
- package/src/primitives/Modal.tsx +223 -0
- package/src/primitives/index.ts +1 -0
- package/src/storage/hooks/useStorageRulesGate.ts +18 -9
package/dist/agents/Modal.d.ts
CHANGED
|
@@ -1,25 +1,2 @@
|
|
|
1
|
-
|
|
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,
|
|
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"}
|
package/dist/agents/Modal.js
CHANGED
|
@@ -1,28 +1,2 @@
|
|
|
1
|
-
|
|
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
|
package/dist/agents/Modal.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Modal.js","sourceRoot":"","sources":["../../src/agents/Modal.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"Modal.js","sourceRoot":"","sources":["../../src/agents/Modal.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAmB,MAAM,wBAAwB,CAAC"}
|
package/dist/auth/authApi.d.ts
CHANGED
|
@@ -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
|
|
26
|
+
* `pyric sandbox --ui`.
|
|
27
27
|
*/
|
|
28
28
|
export declare function AuthApiProvider({ value, children, }: {
|
|
29
29
|
value: AuthApi;
|
package/dist/auth/authApi.js
CHANGED
|
@@ -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
|
|
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
|
|
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
|
|
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"}
|
package/dist/primitives/index.js
CHANGED
|
@@ -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.
|
|
10
|
-
*
|
|
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
|
-
*
|
|
84
|
-
* verdict
|
|
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
|
|
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":"AAAA,OAAO,EAAE,WAAW,EAAE,SAAS,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAC;AAClE,OAAO,EACL,aAAa,EACb,iBAAiB,EACjB,oBAAoB,GAKrB,MAAM,eAAe,CAAC;AACvB,OAAO,EAAE,oBAAoB,EAAE,MAAM,mBAAmB,CAAC;
|
|
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.
|
|
3
|
+
"version": "0.1.0-alpha.22",
|
|
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.
|
|
96
|
+
"pyric": "^0.1.0-alpha.22"
|
|
97
97
|
},
|
|
98
98
|
"peerDependenciesMeta": {
|
|
99
99
|
"@inbrowser/agent": {
|
|
@@ -109,8 +109,8 @@
|
|
|
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.
|
|
113
|
-
"pyric-admin": "^0.1.0-alpha.
|
|
112
|
+
"pyric": "^0.1.0-alpha.22",
|
|
113
|
+
"pyric-admin": "^0.1.0-alpha.22",
|
|
114
114
|
"react": "^19.0.0",
|
|
115
115
|
"react-dom": "^19.0.0",
|
|
116
116
|
"react-test-renderer": "^19.2.6",
|
package/src/agents/Modal.tsx
CHANGED
|
@@ -1,65 +1 @@
|
|
|
1
|
-
|
|
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';
|
package/src/auth/authApi.ts
CHANGED
|
@@ -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
|
|
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
|
|
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
|
+
}
|
package/src/primitives/index.ts
CHANGED
|
@@ -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.
|
|
22
|
-
*
|
|
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
|
-
*
|
|
93
|
-
* verdict
|
|
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');
|