@jini-ai/admin 0.4.4 → 0.4.5

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.
@@ -2,6 +2,9 @@ import { createElement } from 'react';
2
2
  import { bindReact } from '../../core/react/bind-react.js';
3
3
  import { formsModule } from '../forms.module.js';
4
4
  import { FormsPortsContext, FormsOptionsContext } from './hooks/FormsPorts.hooks.js';
5
+ // The list must mount its bounded API read even when long-lived streams occupy every socket.
6
+ // A native dynamic import would queue before that read and leave the loading notice forever.
7
+ import { FormsList } from './pages/FormsList.js';
5
8
  export { FormsPortsContext, FormsOptionsContext, useFormsPorts } from './hooks/FormsPorts.hooks.js';
6
9
  export { useFormsList } from './hooks/use-forms-list.hooks.js';
7
10
  export { useFormEditor } from './hooks/use-form-editor.hooks.js';
@@ -9,7 +12,6 @@ export { useFormEditor } from './hooks/use-form-editor.hooks.js';
9
12
  export function forms(_required, options = {}) {
10
13
  const react = bindReact({ module: formsModule, views: {
11
14
  list: { page: async () => {
12
- const { FormsList } = await import('./pages/FormsList.js');
13
15
  return { default: (_props) => createElement(FormsList) };
14
16
  }, tabs: {} },
15
17
  editor: { page: async () => {
@@ -87,7 +87,9 @@ export declare function useConfirmDialogCancelLabel(_requiredArgs: Record<string
87
87
  * Focus moves to the *cancel* action on open, not confirm — an operator whose first keystroke after
88
88
  * a slow read-through is Enter should land on the safe action, not the destructive one.
89
89
  *
90
- * @complexity O(1) per open/close transition — one `showModal`/`close` call and one focus move.
90
+ * Native modal lifecycle restores the opener; panel-kit's shared trap contains Tab/Shift+Tab,
91
+ * including in DOM environments using the open-attribute fallback, and handles nested traps.
92
+ * @complexity O(1) per open/close transition; Tab boundaries scan focusable dialog content.
91
93
  */
92
94
  export declare function useConfirmDialog({ open, onCancel, document }: {
93
95
  readonly open: boolean;
@@ -1,6 +1,7 @@
1
1
  import { jsx as _jsx } from "react/jsx-runtime";
2
2
  import { createContext, useContext, useMemo, useRef, } from 'react';
3
3
  import { useNativeDialog } from '@jini-ai/ui-kit/react/native';
4
+ import { useFocusTrap } from '@jini-ai/ui/panel-kit';
4
5
  /** Empty object, not `undefined` — {@link useConfirmDialogCancelLabel} can always read `cancelLabel`
5
6
  * off it without an extra null check, whether or not a provider is mounted. */
6
7
  const ConfirmDialogDefaultsContext = createContext({});
@@ -51,7 +52,9 @@ export function useConfirmDialogCancelLabel(_requiredArgs, { explicit } = {}) {
51
52
  * Focus moves to the *cancel* action on open, not confirm — an operator whose first keystroke after
52
53
  * a slow read-through is Enter should land on the safe action, not the destructive one.
53
54
  *
54
- * @complexity O(1) per open/close transition — one `showModal`/`close` call and one focus move.
55
+ * Native modal lifecycle restores the opener; panel-kit's shared trap contains Tab/Shift+Tab,
56
+ * including in DOM environments using the open-attribute fallback, and handles nested traps.
57
+ * @complexity O(1) per open/close transition; Tab boundaries scan focusable dialog content.
55
58
  */
56
59
  export function useConfirmDialog({ open, onCancel, document }, { pending } = {}) {
57
60
  // `useId()`, not a string literal — a hardcoded id breaks the moment a screen mounts two
@@ -78,6 +81,7 @@ export function useConfirmDialog({ open, onCancel, document }, { pending } = {})
78
81
  const cancelRef = useRef(null);
79
82
  const vm = useNativeDialog({ open, title: '', onClose: () => onCancel(), ...(pending === undefined ? {} : { pending }) }, { document, initialFocus: () => cancelRef.current?.focus() });
80
83
  const titleId = vm.titleId, dialogRef = vm.props.ref;
84
+ useFocusTrap({ containerRef: dialogRef }, { active: open });
81
85
  const handleNativeCancel = vm.props.onCancel, handleBackdropClick = vm.props.onClick;
82
86
  return { titleId, dialogRef, cancelRef, handleNativeCancel, handleBackdropClick };
83
87
  }
@@ -0,0 +1,22 @@
1
+ import type { ConfirmDialogProps } from './ConfirmDialog.js';
2
+ /** Host copy and consequence styling; ConfirmDialog remains the owner of focus and dismissal. */
3
+ export type ConfirmRequestPresentation = Pick<ConfirmDialogProps, 'title' | 'body' | 'confirmLabel' | 'cancelLabel' | 'tone'>;
4
+ export interface ConfirmRequestController {
5
+ confirm: (required: {
6
+ dialog: ConfirmRequestPresentation;
7
+ }, optional?: Record<string, never>) => Promise<boolean>;
8
+ /** Keep the shared dialog mounted, including while closed. */
9
+ dialog: ConfirmDialogProps;
10
+ }
11
+ /**
12
+ * Adapts an async confirmation callback to the shared controlled ConfirmDialog.
13
+ * Replacing a request or unmounting denies the outstanding action; stale answers cannot accept
14
+ * a newer request. No destructive effect runs here: the caller acts only after receiving true.
15
+ * @param _required - No required host dependencies.
16
+ * @param _optional - Reserved options.
17
+ * @returns Promise confirmer and props for one always-mounted ConfirmDialog.
18
+ * @example const approval = useConfirmRequest({}); // <ConfirmDialog {...approval.dialog} />
19
+ * @complexity O(1) time and space per request; at most one unresolved request.
20
+ */
21
+ export declare function useConfirmRequest(_required: Record<string, never>, _optional?: Record<string, never>): ConfirmRequestController;
22
+ //# sourceMappingURL=use-confirm-request.d.ts.map
@@ -0,0 +1,53 @@
1
+ import { useCallback, useEffect, useRef, useState } from 'react';
2
+ /**
3
+ * Adapts an async confirmation callback to the shared controlled ConfirmDialog.
4
+ * Replacing a request or unmounting denies the outstanding action; stale answers cannot accept
5
+ * a newer request. No destructive effect runs here: the caller acts only after receiving true.
6
+ * @param _required - No required host dependencies.
7
+ * @param _optional - Reserved options.
8
+ * @returns Promise confirmer and props for one always-mounted ConfirmDialog.
9
+ * @example const approval = useConfirmRequest({}); // <ConfirmDialog {...approval.dialog} />
10
+ * @complexity O(1) time and space per request; at most one unresolved request.
11
+ */
12
+ export function useConfirmRequest(_required, _optional = {}) {
13
+ const [request, setRequest] = useState(null);
14
+ const pending = useRef(null);
15
+ const mounted = useRef(true);
16
+ useEffect(() => {
17
+ mounted.current = true;
18
+ return () => {
19
+ mounted.current = false;
20
+ pending.current?.resolve(false);
21
+ pending.current = null;
22
+ };
23
+ }, []);
24
+ /** Opens one request, denying any replaced request; retained callbacks fail closed after unmount. */
25
+ const confirm = useCallback(({ dialog }, _options = {}) => {
26
+ if (!mounted.current)
27
+ return Promise.resolve(false);
28
+ pending.current?.resolve(false);
29
+ return new Promise((resolve) => {
30
+ const next = { presentation: dialog, resolve };
31
+ pending.current = next;
32
+ setRequest(next);
33
+ });
34
+ }, []);
35
+ /** Only the dialog that opened this request may settle it, once; delayed older clicks are ignored. */
36
+ function settle(accepted) {
37
+ if (!request || pending.current !== request)
38
+ return;
39
+ pending.current = null;
40
+ setRequest(null);
41
+ request.resolve(accepted);
42
+ }
43
+ return {
44
+ confirm,
45
+ dialog: {
46
+ ...(request?.presentation ?? { title: '', body: '', confirmLabel: '' }),
47
+ open: request !== null,
48
+ onConfirm: () => settle(true),
49
+ onCancel: () => settle(false),
50
+ },
51
+ };
52
+ }
53
+ //# sourceMappingURL=use-confirm-request.js.map
@@ -37,6 +37,8 @@ export { ConfirmButton } from './components/ConfirmButton/ConfirmButton.js';
37
37
  export type { ConfirmButtonProps } from './components/ConfirmButton/ConfirmButton.js';
38
38
  export { ConfirmDialog } from './components/ConfirmDialog/ConfirmDialog.js';
39
39
  export type { ConfirmDialogProps } from './components/ConfirmDialog/ConfirmDialog.js';
40
+ export { useConfirmRequest } from './components/ConfirmDialog/use-confirm-request.js';
41
+ export type { ConfirmRequestController, ConfirmRequestPresentation } from './components/ConfirmDialog/use-confirm-request.js';
40
42
  export type { ConfirmDialogController, UseConfirmDialog } from './components/ConfirmDialog/ConfirmDialog.hooks.js';
41
43
  export { ConfirmDialogDefaultsProvider } from './components/ConfirmDialog/ConfirmDialog.hooks.js';
42
44
  export type { ConfirmDialogDefaults, ConfirmDialogDefaultsProviderProps, } from './components/ConfirmDialog/ConfirmDialog.hooks.js';
@@ -36,6 +36,7 @@
36
36
  export { resolveTone, toneClassName } from './types.js';
37
37
  export { ConfirmButton } from './components/ConfirmButton/ConfirmButton.js';
38
38
  export { ConfirmDialog } from './components/ConfirmDialog/ConfirmDialog.js';
39
+ export { useConfirmRequest } from './components/ConfirmDialog/use-confirm-request.js';
39
40
  // Host-wide `ConfirmDialog` defaults (today just a translated `cancelLabel`), set once at an app
40
41
  // root rather than passed at every call site — see `ConfirmDialogDefaultsProvider`'s doc comment.
41
42
  export { ConfirmDialogDefaultsProvider } from './components/ConfirmDialog/ConfirmDialog.hooks.js';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jini-ai/admin",
3
- "version": "0.4.4",
3
+ "version": "0.4.5",
4
4
  "description": "Composable admin surface for Jini-hosted products: framework-free contracts, panel registry, and route model in `/core`; React shell and panels in `/react`. Import only the layers you need.",
5
5
  "license": "Apache-2.0",
6
6
  "repository": {
@@ -428,10 +428,10 @@
428
428
  "react": "^18.3.0 || ^19.0.0",
429
429
  "react-dom": "^18.3.0 || ^19.0.0",
430
430
  "@jini-ai/agentic": "^0.4.2",
431
- "@jini-ai/ui": "^0.4.5",
431
+ "@jini-ai/ui": "^0.4.7",
432
432
  "@jini-ai/ui-kit": "^0.1.1",
433
433
  "@jini-ai/agent-plugins": "^0.4.1",
434
- "@jini-ai/cms": "^0.5.2",
434
+ "@jini-ai/cms": "^0.5.5",
435
435
  "@jini-ai/core": "^0.4.2"
436
436
  },
437
437
  "peerDependenciesMeta": {
@@ -470,12 +470,12 @@
470
470
  "react": "^19.2.0",
471
471
  "react-dom": "^19.2.0",
472
472
  "@testing-library/user-event": "^14.6.1",
473
- "@jini-ai/agentic": "^0.4.3",
474
- "@jini-ai/ui": "^0.4.6",
475
473
  "@jini-ai/ui-kit": "^0.1.2",
476
- "@jini-ai/core": "^0.4.2",
477
- "@jini-ai/cms": "^0.5.4",
478
- "@jini-ai/agent-plugins": "^0.4.2"
474
+ "@jini-ai/cms": "^0.5.5",
475
+ "@jini-ai/agentic": "^0.4.3",
476
+ "@jini-ai/agent-plugins": "^0.4.2",
477
+ "@jini-ai/ui": "^0.4.7",
478
+ "@jini-ai/core": "^0.4.2"
479
479
  },
480
480
  "typesVersions": {
481
481
  "*": {