@jini-ai/ui-kit 0.1.2 → 0.1.4

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/CHANGELOG.md CHANGED
@@ -1,5 +1,9 @@
1
1
  # @jini-ai/ui-kit
2
2
 
3
+ ## 0.1.4 — 2026-10-13
4
+
5
+ - Break facade/native import cycles: native dialogs pick the provider's Button through a context-only lookup (`NativeButton` fallback; `nativeActions` still forces native), and `ToastRegion` creates its Toast elements. No API change.
6
+
3
7
  ## 0.1.2
4
8
 
5
9
  ### Patch Changes
@@ -25,6 +25,7 @@ export declare function useFacade<K extends Exclude<ImplementedComponentName, 'C
25
25
  };
26
26
  };
27
27
  export declare function useToast(_required?: Record<string, never>, _optional?: Record<string, never>): import("../toast.js").ToastService;
28
+ /** Observe toast messages and disposal-owned service state; ToastRegion owns element creation. */
28
29
  export declare function useToastRegion(required: {
29
30
  service?: ReturnType<typeof createToastService>;
30
31
  attrs?: ToastProps['attrs'];
@@ -34,6 +35,7 @@ export declare function useToastRegion(required: {
34
35
  id?: string;
35
36
  'data-jini-part': string;
36
37
  };
37
- children: import("react").FunctionComponentElement<ToastProps>[];
38
+ messages: readonly import("../toast.js").ToastMessage[];
39
+ service: import("../toast.js").ToastService;
38
40
  };
39
41
  //# sourceMappingURL=Kit.hooks.d.ts.map
@@ -1,17 +1,16 @@
1
- import { useContext, useEffect, useMemo, useRef, useState, useSyncExternalStore, createElement } from 'react';
1
+ import { useEffect, useMemo, useRef, useState, useSyncExternalStore } from 'react';
2
2
  import { createPortal } from 'react-dom';
3
- import { KitContext } from './context.js';
3
+ import { useKitContext } from './context.js';
4
4
  import { createKit } from './kit.js';
5
5
  import { assertKitContract, KitConfigError } from '../needs.js';
6
6
  import { kitSpec } from '../kit.spec.js';
7
7
  import { createToastService } from '../toast.js';
8
- import { Toast } from './facade.js';
9
8
  let defaultValue;
10
9
  function getDefault() {
11
10
  return defaultValue ??= { kit: createKit({}), agent: undefined, overlayContainer: null, toast: undefined, cancelLabel: 'Cancel', guard: 'fallback' };
12
11
  }
13
12
  export function useKit(_required = {}, _optional = {}) {
14
- return useContext(KitContext) ?? getDefault();
13
+ return useKitContext({}) ?? getDefault();
15
14
  }
16
15
  export function useKitProvider(required, _optional = {}) {
17
16
  const fallback = useMemo(() => createKit({}), []);
@@ -74,14 +73,13 @@ export function useToast(_required = {}, _optional = {}) {
74
73
  throw new KitConfigError({ issues: ['useToast requires a KitProvider or an injected toast service'] });
75
74
  return service;
76
75
  }
76
+ /** Observe toast messages and disposal-owned service state; ToastRegion owns element creation. */
77
77
  export function useToastRegion(required, _optional = {}) {
78
78
  const context = useKit({});
79
79
  const service = required.service ?? context.toast;
80
80
  if (!service)
81
81
  throw new KitConfigError({ issues: ['ToastRegion requires a KitProvider or a service'] });
82
82
  const messages = useSyncExternalStore(listener => service.subscribe({ listener }), () => service.snapshot({}), () => service.snapshot({}));
83
- const children = messages.map(message => createElement(Toast, { key: message.id, message: message.message,
84
- ...(message.tone ? { tone: message.tone } : {}), onDismiss: () => service.dismiss({ id: message.id }) }));
85
- return { container: useOverlayContainer({}), attrs: { 'data-jini-part': 'kit.toast-region', ...required.attrs }, children };
83
+ return { container: useOverlayContainer({}), attrs: { 'data-jini-part': 'kit.toast-region', ...required.attrs }, messages, service };
86
84
  }
87
85
  //# sourceMappingURL=Kit.hooks.js.map
@@ -1,4 +1,5 @@
1
1
  import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { Toast } from './facade.js';
2
3
  import { KitContext } from './context.js';
3
4
  import { useKitProvider, useToastRegion, renderOverlay } from './Kit.hooks.js';
4
5
  export function KitProvider(props, _optional = {}) {
@@ -10,6 +11,6 @@ export function Overlay(props, _optional = {}) {
10
11
  }
11
12
  export function ToastRegion(props, _optional = {}) {
12
13
  const vm = useToastRegion(props);
13
- return _jsx(Overlay, { container: vm.container, children: _jsx("div", { ...vm.attrs, "aria-live": "polite", "aria-relevant": "additions removals", children: vm.children }) });
14
+ return _jsx(Overlay, { container: vm.container, children: _jsx("div", { ...vm.attrs, "aria-live": "polite", "aria-relevant": "additions removals", children: vm.messages.map(message => _jsx(Toast, { message: message.message, ...(message.tone ? { tone: message.tone } : {}), onDismiss: () => vm.service.dismiss({ id: message.id }) }, message.id)) }) });
14
15
  }
15
16
  //# sourceMappingURL=KitProvider.js.map
@@ -6,4 +6,8 @@ export declare function getSharedKitContext(required: {
6
6
  version: string;
7
7
  }, _optional?: Record<string, never>): Context<KitContextValue | null>;
8
8
  export declare const KitContext: Context<KitContextValue | null>;
9
+ /** Context-only lookup for native views: no default-kit assembly or facade dependency.
10
+ * @returns The nearest provider binding, or null so a native view can use its own defaults.
11
+ */
12
+ export declare function useKitContext(_required?: Record<string, never>, _optional?: Record<string, never>): KitContextValue | null;
9
13
  //# sourceMappingURL=context.d.ts.map
@@ -1,4 +1,4 @@
1
- import { createContext } from 'react';
1
+ import { createContext, useContext } from 'react';
2
2
  const CONTEXT_KEY = Symbol.for('@jini-ai/ui-kit/react/context@1');
3
3
  export function isDevelopment(_required, _optional = {}) {
4
4
  // Keep the standard expression intact for bundlers to replace. A typeof-process shortcut would
@@ -21,4 +21,10 @@ export function getSharedKitContext(required, _optional = {}) {
21
21
  return slot.context;
22
22
  }
23
23
  export const KitContext = getSharedKitContext({ version: '0.1.0' });
24
+ /** Context-only lookup for native views: no default-kit assembly or facade dependency.
25
+ * @returns The nearest provider binding, or null so a native view can use its own defaults.
26
+ */
27
+ export function useKitContext(_required = {}, _optional = {}) {
28
+ return useContext(KitContext);
29
+ }
24
30
  //# sourceMappingURL=context.js.map
@@ -1,5 +1,5 @@
1
1
  import type { MouseEvent, SyntheticEvent } from 'react';
2
- import type { DialogProps, ConfirmViewProps } from '../types.js';
2
+ import type { DialogProps, ConfirmViewProps, ReactKit } from '../types.js';
3
3
  /** Native modal lifecycle shared by kit views and compatibility adapters; DOM effects are O(1).
4
4
  * The document port preserves hosts' focus injection seam without introducing another lifecycle.
5
5
  * @example useNativeDialog({ open, title, onClose }, { initialFocus: () => cancelRef.current?.focus() })
@@ -33,9 +33,17 @@ export declare function useNativeDialog(required: DialogProps, optional?: {
33
33
  'aria-labelledby': string;
34
34
  };
35
35
  };
36
- export declare function useNativeConfirm(required: ConfirmViewProps, _optional?: Record<string, never>): {
36
+ /** Native confirmation lifecycle with the view-selected Button. A failed conformance guard
37
+ * forces native actions even when a provider supplied a custom Button.
38
+ * @param required The shared confirmation controller.
39
+ * @param options Button selected by the native view's context-only lookup.
40
+ * @returns Dialog bindings and the action component that preserves the safety override.
41
+ */
42
+ export declare function useNativeConfirm(required: ConfirmViewProps, { Button }?: {
43
+ Button?: ReactKit['Button'];
44
+ }): {
37
45
  controller: import("../types.js").ConfirmController;
38
- Button: import("react").ForwardRefExoticComponent<Omit<import("../types.js").ButtonProps, "ref"> & import("react").RefAttributes<HTMLButtonElement>>;
46
+ Button: import("react").ForwardRefExoticComponent<Omit<import("../types.js").ButtonProps, "ref"> & import("react").RefAttributes<HTMLButtonElement>> | import("react").ComponentType<import("../types.js").ButtonProps>;
39
47
  props: {
40
48
  className: string;
41
49
  role: string;
@@ -1,5 +1,4 @@
1
1
  import { useId, useImperativeHandle, useLayoutEffect, useRef } from 'react';
2
- import { Button } from '../facade.js';
3
2
  import { NativeButton } from './Controls.js';
4
3
  /** Native modal lifecycle shared by kit views and compatibility adapters; DOM effects are O(1).
5
4
  * The document port preserves hosts' focus injection seam without introducing another lifecycle.
@@ -48,10 +47,29 @@ export function useNativeDialog(required, optional = {}) {
48
47
  'aria-busy': required.pending,
49
48
  onCancel: (event) => { event.preventDefault(); if (!required.pending)
50
49
  required.onClose({}); },
51
- onClick: (event) => { if (event.target === event.currentTarget && !required.pending)
50
+ // A click whose target is the <dialog> itself is not always the backdrop: the dialog's own
51
+ // padding and gaps hit it too, and so does the click that dismisses a native date/select
52
+ // popup inside it (seen live: closing a datetime picker closed the whole modal). Only a point
53
+ // outside the dialog's box is a backdrop click.
54
+ onClick: (event) => { if (event.target === event.currentTarget && !required.pending && isOutsideBox(event))
52
55
  required.onClose({}); } } };
53
56
  }
54
- export function useNativeConfirm(required, _optional = {}) {
57
+ /** True when the pointer is outside the dialog's own rectangle, i.e. on the ::backdrop. A click with no
58
+ * coordinates (keyboard activation, synthetic test clicks) keeps the old target-only behaviour. */
59
+ function isOutsideBox(event) {
60
+ const { clientX: x, clientY: y } = event;
61
+ if (x === 0 && y === 0)
62
+ return true;
63
+ const r = event.currentTarget.getBoundingClientRect();
64
+ return x < r.left || x > r.right || y < r.top || y > r.bottom;
65
+ }
66
+ /** Native confirmation lifecycle with the view-selected Button. A failed conformance guard
67
+ * forces native actions even when a provider supplied a custom Button.
68
+ * @param required The shared confirmation controller.
69
+ * @param options Button selected by the native view's context-only lookup.
70
+ * @returns Dialog bindings and the action component that preserves the safety override.
71
+ */
72
+ export function useNativeConfirm(required, { Button = NativeButton } = {}) {
55
73
  const c = required.controller;
56
74
  const vm = useNativeDialog({ open: c.open, title: c.title, pending: c.pending, onClose: () => c.requestDismiss({ reason: 'close' }) }, { initialFocus: () => c.focusInitial({}) });
57
75
  return { ...vm, controller: c, Button: c.nativeActions ? NativeButton : Button,
@@ -1,13 +1,16 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { forwardRef } from 'react';
3
3
  import { useNativeDialog, useNativeConfirm } from './Dialog.hooks.js';
4
- import { Button } from '../facade.js';
4
+ import { useKitContext } from '../context.js';
5
+ import { NativeButton } from './Controls.js';
5
6
  export const NativeDialog = forwardRef(function NativeDialog(props, ref) {
7
+ const Button = useKitContext({})?.kit.components.Button ?? NativeButton;
6
8
  const vm = useNativeDialog({ ...props, ref });
7
9
  return _jsxs("dialog", { ...vm.props, children: [_jsx("h2", { className: "jini-dialog-title", id: vm.titleId, "data-jini-part": "kit.dialog-title", children: vm.title }), vm.children, _jsx(Button, { ...vm.closeProps, children: vm.closeLabel })] });
8
10
  });
9
11
  export function NativeConfirmDialog(props, _optional = {}) {
10
- const vm = useNativeConfirm(props);
12
+ const Button = useKitContext({})?.kit.components.Button ?? NativeButton;
13
+ const vm = useNativeConfirm(props, { Button });
11
14
  return _jsxs("dialog", { ...vm.props, children: [_jsx("h2", { className: "jini-confirm-dialog-title", ...vm.controller.titleAttrs, children: vm.controller.title }), _jsxs("div", { className: "jini-confirm-dialog-body", "data-jini-part": "confirm.body", children: [vm.controller.body, _jsx("p", { className: "jini-confirm-dialog-consequence", ...vm.controller.consequenceAttrs, children: vm.controller.consequence }), vm.controller.error] }), _jsxs("div", { className: "jini-confirm-dialog-actions", "data-jini-part": "confirm.actions", children: [_jsx(vm.Button, { ...vm.controller.cancel }), _jsx(vm.Button, { ...vm.controller.confirm })] })] });
12
15
  }
13
16
  //# sourceMappingURL=Dialog.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jini-ai/ui-kit",
3
- "version": "0.1.2",
3
+ "version": "0.1.4",
4
4
  "description": "Framework-free UI contract, injected React facades and native defaults.",
5
5
  "license": "Apache-2.0",
6
6
  "repository": {