@jini-ai/ui-kit 0.0.0-stage → 0.1.1

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.
Files changed (63) hide show
  1. package/CHANGELOG.md +9 -0
  2. package/LICENSE +201 -0
  3. package/README.md +150 -2
  4. package/dist/attrs.d.ts +18 -0
  5. package/dist/attrs.js +2 -0
  6. package/dist/confirm.d.ts +42 -0
  7. package/dist/confirm.js +41 -0
  8. package/dist/index.d.ts +10 -0
  9. package/dist/index.js +5 -0
  10. package/dist/kit.spec.d.ts +491 -0
  11. package/dist/kit.spec.js +40 -0
  12. package/dist/needs.d.ts +24 -0
  13. package/dist/needs.js +29 -0
  14. package/dist/react/Kit.hooks.d.ts +39 -0
  15. package/dist/react/Kit.hooks.js +87 -0
  16. package/dist/react/KitProvider.d.ts +13 -0
  17. package/dist/react/KitProvider.js +15 -0
  18. package/dist/react/confirm/ConfirmDialog.d.ts +3 -0
  19. package/dist/react/confirm/ConfirmDialog.hooks.d.ts +7 -0
  20. package/dist/react/confirm/ConfirmDialog.hooks.js +101 -0
  21. package/dist/react/confirm/ConfirmDialog.js +8 -0
  22. package/dist/react/confirm/guard.d.ts +7 -0
  23. package/dist/react/confirm/guard.js +54 -0
  24. package/dist/react/context.d.ts +9 -0
  25. package/dist/react/context.js +24 -0
  26. package/dist/react/facade.d.ts +37 -0
  27. package/dist/react/facade.js +36 -0
  28. package/dist/react/index.d.ts +8 -0
  29. package/dist/react/index.js +7 -0
  30. package/dist/react/kit.d.ts +35 -0
  31. package/dist/react/kit.js +44 -0
  32. package/dist/react/native/Controls.d.ts +13 -0
  33. package/dist/react/native/Controls.hooks.d.ts +164 -0
  34. package/dist/react/native/Controls.hooks.js +69 -0
  35. package/dist/react/native/Controls.js +49 -0
  36. package/dist/react/native/Dialog.d.ts +4 -0
  37. package/dist/react/native/Dialog.hooks.d.ts +60 -0
  38. package/dist/react/native/Dialog.hooks.js +56 -0
  39. package/dist/react/native/Dialog.js +13 -0
  40. package/dist/react/native/Navigation.d.ts +5 -0
  41. package/dist/react/native/Navigation.hooks.d.ts +96 -0
  42. package/dist/react/native/Navigation.hooks.js +110 -0
  43. package/dist/react/native/Navigation.js +15 -0
  44. package/dist/react/native/index.d.ts +3 -0
  45. package/dist/react/native/index.js +9 -0
  46. package/dist/react/testing/driver.d.ts +27 -0
  47. package/dist/react/testing/driver.js +102 -0
  48. package/dist/react/testing/index.d.ts +5 -0
  49. package/dist/react/testing/index.js +3 -0
  50. package/dist/react/types.d.ts +210 -0
  51. package/dist/react/types.js +2 -0
  52. package/dist/testing/scenarios.d.ts +31 -0
  53. package/dist/testing/scenarios.js +28 -0
  54. package/dist/toast.d.ts +34 -0
  55. package/dist/toast.js +52 -0
  56. package/docs/decisions/DR-001.md +32 -0
  57. package/docs/spec/api.spec.md +98 -0
  58. package/docs/spec/behavior.spec.md +49 -0
  59. package/docs/spec/errors.spec.md +41 -0
  60. package/docs/spec/state.spec.md +45 -0
  61. package/docs/spec/ui.spec.md +49 -0
  62. package/package.json +97 -3
  63. package/src/react/native/styles.css +4 -0
@@ -0,0 +1,87 @@
1
+ import { useContext, useEffect, useMemo, useRef, useState, useSyncExternalStore, createElement } from 'react';
2
+ import { createPortal } from 'react-dom';
3
+ import { KitContext } from './context.js';
4
+ import { createKit } from './kit.js';
5
+ import { assertKitContract, KitConfigError } from '../needs.js';
6
+ import { kitSpec } from '../kit.spec.js';
7
+ import { createToastService } from '../toast.js';
8
+ import { Toast } from './facade.js';
9
+ let defaultValue;
10
+ function getDefault() {
11
+ return defaultValue ??= { kit: createKit({}), agent: undefined, overlayContainer: null, toast: undefined, cancelLabel: 'Cancel', guard: 'fallback' };
12
+ }
13
+ export function useKit(_required = {}, _optional = {}) {
14
+ return useContext(KitContext) ?? getDefault();
15
+ }
16
+ export function useKitProvider(required, _optional = {}) {
17
+ const fallback = useMemo(() => createKit({}), []);
18
+ const kit = required.kit ?? fallback;
19
+ for (const requirement of required.needs ?? []) {
20
+ assertKitContract({ contract: requirement.contract });
21
+ const missing = requirement.components.filter(name => kitSpec[name]?.status !== 'implemented');
22
+ if (missing.length)
23
+ throw new KitConfigError({ issues: missing.map(name => `${requirement.id}: ${name} is not implemented`) });
24
+ }
25
+ const overlayContainer = useOverlayRoot({ supplied: required.overlayContainer });
26
+ const toast = useMemo(() => required.toast ?? createToastService({}), [required.toast]);
27
+ const activeServices = useRef(new Set());
28
+ useEffect(() => {
29
+ activeServices.current.add(toast);
30
+ return () => {
31
+ activeServices.current.delete(toast);
32
+ // StrictMode immediately replays effects using the same instance. Defer owned-service disposal
33
+ // by one microtask so the replay can reclaim it; replaced or unmounted instances still dispose.
34
+ queueMicrotask(() => { if (!required.toast && !activeServices.current.has(toast))
35
+ toast.dispose({}); });
36
+ };
37
+ }, [toast, required.toast]);
38
+ return useMemo(() => ({ kit, overlayContainer, toast, agent: required.agent,
39
+ cancelLabel: required.cancelLabel ?? 'Cancel', guard: required.guard ?? kit.guard }), [kit, overlayContainer, toast, required.agent, required.cancelLabel, required.guard]);
40
+ }
41
+ function useOverlayRoot(required) {
42
+ const [root, setRoot] = useState(required.supplied ?? null);
43
+ useEffect(() => {
44
+ if (required.supplied) {
45
+ setRoot(required.supplied);
46
+ return;
47
+ }
48
+ const element = document.createElement('div');
49
+ element.dataset.jiniPart = 'kit.overlay';
50
+ document.body.appendChild(element);
51
+ setRoot(element);
52
+ return () => { element.remove(); };
53
+ }, [required.supplied]);
54
+ return required.supplied ?? root;
55
+ }
56
+ export function useOverlayContainer(_required = {}, _optional = {}) {
57
+ return useKit({}).overlayContainer ?? (typeof document === 'undefined' ? null : document.body);
58
+ }
59
+ export function renderOverlay(required, _optional = {}) {
60
+ return required.container ? createPortal(required.children, required.container) : required.children;
61
+ }
62
+ export function useKitComponent(required, _optional = {}) {
63
+ const context = useKit({});
64
+ return context.kit.components[required.name];
65
+ }
66
+ export function useFacade(required, _optional = {}) {
67
+ const Component = useKitComponent({ name: required.name });
68
+ const attrs = { 'data-jini-part': required.name === 'Tabs' ? 'kit.tab' : kitSpec[required.name].parts[0], ...required.props.attrs };
69
+ return { Component, props: { ...required.props, attrs } };
70
+ }
71
+ export function useToast(_required = {}, _optional = {}) {
72
+ const service = useKit({}).toast;
73
+ if (!service)
74
+ throw new KitConfigError({ issues: ['useToast requires a KitProvider or an injected toast service'] });
75
+ return service;
76
+ }
77
+ export function useToastRegion(required, _optional = {}) {
78
+ const context = useKit({});
79
+ const service = required.service ?? context.toast;
80
+ if (!service)
81
+ throw new KitConfigError({ issues: ['ToastRegion requires a KitProvider or a service'] });
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 };
86
+ }
87
+ //# sourceMappingURL=Kit.hooks.js.map
@@ -0,0 +1,13 @@
1
+ import type { ReactNode } from 'react';
2
+ import type { KitProviderProps, ToastProps } from './types.js';
3
+ import type { ToastService } from '../toast.js';
4
+ export declare function KitProvider(props: KitProviderProps, _optional?: Record<string, never>): import("react").JSX.Element;
5
+ export declare function Overlay(props: {
6
+ children: ReactNode;
7
+ container: HTMLElement | null;
8
+ }, _optional?: Record<string, never>): ReactNode;
9
+ export declare function ToastRegion(props: {
10
+ service?: ToastService;
11
+ attrs?: ToastProps['attrs'];
12
+ }, _optional?: Record<string, never>): import("react").JSX.Element;
13
+ //# sourceMappingURL=KitProvider.d.ts.map
@@ -0,0 +1,15 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { KitContext } from './context.js';
3
+ import { useKitProvider, useToastRegion, renderOverlay } from './Kit.hooks.js';
4
+ export function KitProvider(props, _optional = {}) {
5
+ const value = useKitProvider(props);
6
+ return _jsx(KitContext.Provider, { value: value, children: props.children });
7
+ }
8
+ export function Overlay(props, _optional = {}) {
9
+ return renderOverlay(props);
10
+ }
11
+ export function ToastRegion(props, _optional = {}) {
12
+ 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
+ }
15
+ //# sourceMappingURL=KitProvider.js.map
@@ -0,0 +1,3 @@
1
+ import type { ConfirmDialogProps } from '../types.js';
2
+ export declare function ConfirmDialog(props: ConfirmDialogProps, _optional?: Record<string, never>): import("react").JSX.Element;
3
+ //# sourceMappingURL=ConfirmDialog.d.ts.map
@@ -0,0 +1,7 @@
1
+ import type { ConfirmController, ConfirmDialogProps } from '../types.js';
2
+ export declare function useConfirmController(required: ConfirmDialogProps, _optional?: Record<string, never>): ConfirmController;
3
+ export declare function useConfirmFacade(required: ConfirmDialogProps, _optional?: Record<string, never>): {
4
+ Component: import("react").ComponentType<import("../types.js").ConfirmViewProps>;
5
+ controller: ConfirmController;
6
+ };
7
+ //# sourceMappingURL=ConfirmDialog.hooks.d.ts.map
@@ -0,0 +1,101 @@
1
+ import { createElement, useEffect, useId, useInsertionEffect, useLayoutEffect, useMemo, useRef, useState } from 'react';
2
+ import { createConfirmController, planConfirm } from '../../confirm.js';
3
+ import { useKit, useOverlayContainer } from '../Kit.hooks.js';
4
+ import { isDevelopment } from '../context.js';
5
+ import { nativeKit } from '../native/index.js';
6
+ import { inspectConfirm } from './guard.js';
7
+ export function useConfirmController(required, _optional = {}) {
8
+ const context = useKit({}), titleId = useId(), frameId = useId(), consequenceId = useId(), cancelRef = useRef(null);
9
+ const boundaryRef = useRef(null), latest = useRef(required), mounted = useRef(false);
10
+ const [executing, setExecuting] = useState(false), [error, setError] = useState(false);
11
+ latest.current = required;
12
+ useEffect(() => { mounted.current = true; return () => { mounted.current = false; }; }, []);
13
+ const model = useMemo(() => createConfirmController({ read: () => latest.current,
14
+ onCancel: () => latest.current.onCancel({}), onConfirm: () => latest.current.onConfirm({}) }), []);
15
+ const pending = !!required.pending || executing;
16
+ const plan = planConfirm({ ...required, cancelLabel: required.cancelLabel ?? context.cancelLabel, pending });
17
+ function actionAttrs(part, spec, label) {
18
+ const attrs = spec && context.agent ? context.agent({ handle: spec.handle }, { role: spec.role, label: spec.label }) : {};
19
+ return Object.freeze({ ...attrs, 'data-jini-part': part, 'aria-label': label,
20
+ ...(required.actionAccessibleNames && plan.consequence ? { 'aria-describedby': consequenceId } : {}),
21
+ 'aria-busy': pending, 'data-jini-state': pending ? 'pending' : 'ready' });
22
+ }
23
+ async function onPress() {
24
+ if (model.isExecuting({}) || !latest.current.open || latest.current.pending)
25
+ return;
26
+ setExecuting(true);
27
+ setError(false);
28
+ try {
29
+ await model.confirm({ actor: 'human' });
30
+ }
31
+ catch {
32
+ if (mounted.current)
33
+ setError(true);
34
+ }
35
+ finally {
36
+ if (mounted.current)
37
+ setExecuting(false);
38
+ }
39
+ }
40
+ return { className: required.className, open: required.open, pending, title: required.title, body: required.body, consequence: plan.consequence,
41
+ error: error ? createElement('p', { className: 'jini-notice jini-notice-error', role: 'alert' }, required.errorLabel ?? 'The action failed. Please try again.') : null,
42
+ frameAttrs: Object.freeze({ ...required.attrs, id: frameId, 'data-jini-part': 'confirm.dialog', role: 'alertdialog',
43
+ 'aria-labelledby': titleId, 'aria-modal': true, 'aria-busy': pending }),
44
+ titleAttrs: { id: titleId, 'data-jini-part': 'confirm.title' },
45
+ consequenceAttrs: { id: consequenceId, 'data-jini-part': 'confirm.consequence' },
46
+ cancel: Object.freeze({ attrs: actionAttrs('confirm.cancel', plan.cancel, required.actionAccessibleNames?.cancel ?? plan.cancel?.label ?? `${plan.cancelLabel} — no action taken`),
47
+ ref: cancelRef, disabled: pending, variant: 'secondary', children: plan.cancelLabel,
48
+ onPress: () => { model.requestDismiss({ reason: 'cancel-button' }); } }),
49
+ confirm: Object.freeze({ attrs: actionAttrs('confirm.confirm', plan.confirm, required.actionAccessibleNames?.confirm ?? plan.confirmLabel), disabled: pending, pending,
50
+ variant: plan.confirmVariant, children: required.confirmLabel, onPress }),
51
+ boundaryRef, overlayContainer: useOverlayContainer({}), agentMayConfirm: required.agentMayConfirm !== false,
52
+ requestDismiss: model.requestDismiss,
53
+ focusInitial(_required, _optional = {}) { cancelRef.current?.focus(); },
54
+ };
55
+ }
56
+ export function useConfirmFacade(required, _optional = {}) {
57
+ const c = useConfirmController(required), context = useKit({}), [, refresh] = useState(0);
58
+ const initial = useRef(true), opener = useRef(null);
59
+ const failed = context.guard !== 'off' && context.kit.failed.has('ConfirmDialog');
60
+ const Component = failed ? nativeKit.ConfirmDialog : context.kit.components.ConfirmDialog;
61
+ // Insertion effects precede every child's layout effects (including showModal's focus move).
62
+ // Capturing in a parent layout effect instead captures Cancel and restores focus into a closed dialog.
63
+ useInsertionEffect(() => { if (c.open)
64
+ opener.current = document.activeElement; }, [c.open]);
65
+ useLayoutEffect(() => {
66
+ if (!c.open) {
67
+ initial.current = true;
68
+ // React restores selection between cleanup and layout setup; restore the trigger after that step.
69
+ if (opener.current instanceof HTMLElement && opener.current.isConnected)
70
+ opener.current.focus();
71
+ return;
72
+ }
73
+ c.focusInitial({});
74
+ return () => { if (opener.current instanceof HTMLElement && opener.current.isConnected)
75
+ opener.current.focus(); };
76
+ }, [c.open, Component]);
77
+ useEffect(() => {
78
+ if (!c.open || failed || context.guard === 'off' || !isDevelopment({}))
79
+ return;
80
+ if (!context.kit.overrides.includes('ConfirmDialog') && !context.kit.overrides.includes('Button'))
81
+ return;
82
+ // After the committed view's effects, including library portal/focus synchronization.
83
+ const timer = setTimeout(() => {
84
+ const reasons = inspectConfirm({ controller: c, checkFocus: initial.current });
85
+ initial.current = false;
86
+ if (!reasons.length)
87
+ return;
88
+ if (!context.kit.violations.some(v => v.component === 'ConfirmDialog' && v.reasons.join() === reasons.join())) {
89
+ context.kit.violations.push({ component: 'ConfirmDialog', reasons });
90
+ console.error(`@jini-ai/ui-kit ConfirmDialog conformance violation: ${reasons.join('; ')}`);
91
+ }
92
+ if (context.guard === 'fallback') {
93
+ context.kit.failed.add('ConfirmDialog');
94
+ refresh(value => value + 1);
95
+ }
96
+ }, 0);
97
+ return () => clearTimeout(timer);
98
+ }, [c.open, c.pending, c.agentMayConfirm, c.consequence, Component, context, failed]);
99
+ return { Component, controller: failed ? { ...c, nativeActions: true } : c };
100
+ }
101
+ //# sourceMappingURL=ConfirmDialog.hooks.js.map
@@ -0,0 +1,8 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { useConfirmFacade } from './ConfirmDialog.hooks.js';
3
+ import { Overlay } from '../KitProvider.js';
4
+ export function ConfirmDialog(props, _optional = {}) {
5
+ const vm = useConfirmFacade(props);
6
+ return _jsx(Overlay, { container: vm.controller.overlayContainer, children: _jsx("span", { ref: vm.controller.boundaryRef, className: "jini-overlay-boundary", children: _jsx(vm.Component, { controller: vm.controller }) }) });
7
+ }
8
+ //# sourceMappingURL=ConfirmDialog.js.map
@@ -0,0 +1,7 @@
1
+ import type { ConfirmController } from '../types.js';
2
+ /** Checks real mounted DOM. jsdom cannot prove layout or browser top-layer behavior; those are browser scenarios. */
3
+ export declare function inspectConfirm(required: {
4
+ controller: ConfirmController;
5
+ checkFocus: boolean;
6
+ }, _optional?: Record<string, never>): string[];
7
+ //# sourceMappingURL=guard.d.ts.map
@@ -0,0 +1,54 @@
1
+ function visible(element) {
2
+ for (let current = element; current; current = current.parentElement) {
3
+ const style = getComputedStyle(current);
4
+ if (current.hidden || current.getAttribute('aria-hidden') === 'true' || style.display === 'none' || style.visibility === 'hidden' || style.visibility === 'collapse' || style.opacity === '0')
5
+ return false;
6
+ }
7
+ return true;
8
+ }
9
+ /** Checks real mounted DOM. jsdom cannot prove layout or browser top-layer behavior; those are browser scenarios. */
10
+ export function inspectConfirm(required, _optional = {}) {
11
+ const c = required.controller, issues = [];
12
+ const frame = c.frameAttrs.id ? document.getElementById(c.frameAttrs.id) : null;
13
+ if (!frame)
14
+ return ['missing confirm frame'];
15
+ if (frame.dataset.jiniPart !== 'confirm.dialog' || frame.getAttribute('role') !== 'alertdialog')
16
+ issues.push('missing alertdialog semantics');
17
+ if (!c.overlayContainer?.contains(frame))
18
+ issues.push('frame outside overlay container');
19
+ if (frame.getAttribute('aria-labelledby') !== c.frameAttrs['aria-labelledby'])
20
+ issues.push('missing title association');
21
+ const cancel = frame.querySelector('[data-jini-part="confirm.cancel"]');
22
+ const confirm = frame.querySelector('[data-jini-part="confirm.confirm"]');
23
+ for (const [name, element, bag] of [['cancel', cancel, c.cancel], ['confirm', confirm, c.confirm]]) {
24
+ if (!element) {
25
+ issues.push(`missing ${name} action`);
26
+ continue;
27
+ }
28
+ if (!element.matches('button,input,[role="button"][tabindex]'))
29
+ issues.push(`${name} part is not on a focusable action`);
30
+ if (!visible(element))
31
+ issues.push(`${name} action hidden`);
32
+ for (const [key, value] of Object.entries(bag.attrs ?? {})) {
33
+ if (value !== undefined && element.getAttribute(key) !== String(value))
34
+ issues.push(`${name} did not forward ${key}`);
35
+ }
36
+ if (c.pending && !element.matches(':disabled') && element.getAttribute('aria-disabled') !== 'true')
37
+ issues.push(`${name} enabled while pending`);
38
+ }
39
+ if (!c.pending && required.checkFocus && cancel && document.activeElement !== cancel)
40
+ issues.push('initial focus is not cancel');
41
+ if (!c.agentMayConfirm && confirm && [confirm, ...confirm.querySelectorAll('*')].some(element => element.getAttributeNames().some(name => name.startsWith('data-agent-'))))
42
+ issues.push('human-only confirm published agent metadata');
43
+ if (c.consequence && confirm && !confirm.getAttribute('aria-label')?.includes(c.consequence)) {
44
+ // A concise action name still has to expose the real, visible consequence as its
45
+ // description. An override dropping the associated paragraph must fail the guard.
46
+ const description = c.consequenceAttrs?.id ? document.getElementById(c.consequenceAttrs.id) : null;
47
+ const describedIds = confirm.getAttribute('aria-describedby')?.split(/\s+/) ?? [];
48
+ if (!description || !frame.contains(description) || !visible(description) ||
49
+ !describedIds.includes(description.id) || !description.textContent?.includes(c.consequence))
50
+ issues.push('missing consequence label');
51
+ }
52
+ return issues;
53
+ }
54
+ //# sourceMappingURL=guard.js.map
@@ -0,0 +1,9 @@
1
+ import { type Context } from 'react';
2
+ import type { KitContextValue } from './types.js';
3
+ export declare function isDevelopment(_required: Record<string, never>, _optional?: Record<string, never>): boolean;
4
+ /** Sharing the context object (not just a token) makes independently bundled copies interoperable. */
5
+ export declare function getSharedKitContext(required: {
6
+ version: string;
7
+ }, _optional?: Record<string, never>): Context<KitContextValue | null>;
8
+ export declare const KitContext: Context<KitContextValue | null>;
9
+ //# sourceMappingURL=context.d.ts.map
@@ -0,0 +1,24 @@
1
+ import { createContext } from 'react';
2
+ const CONTEXT_KEY = Symbol.for('@jini-ai/ui-kit/react/context@1');
3
+ export function isDevelopment(_required, _optional = {}) {
4
+ // Keep the standard expression intact for bundlers to replace. A typeof-process shortcut would
5
+ // force development mode in browsers even after a production NODE_ENV substitution.
6
+ try {
7
+ return process.env.NODE_ENV !== 'production';
8
+ }
9
+ catch {
10
+ return true;
11
+ }
12
+ }
13
+ /** Sharing the context object (not just a token) makes independently bundled copies interoperable. */
14
+ export function getSharedKitContext(required, _optional = {}) {
15
+ const shared = globalThis;
16
+ const slot = shared[CONTEXT_KEY] ??= { context: createContext(null), versions: new Set() };
17
+ if (isDevelopment({}) && slot.versions.size && !slot.versions.has(required.version)) {
18
+ console.warn(`@jini-ai/ui-kit version skew: ${[...slot.versions, required.version].join(', ')}; shared contract-major 1 context`);
19
+ }
20
+ slot.versions.add(required.version);
21
+ return slot.context;
22
+ }
23
+ export const KitContext = getSharedKitContext({ version: '0.1.0' });
24
+ //# sourceMappingURL=context.js.map
@@ -0,0 +1,37 @@
1
+ export declare const Button: import("react").ForwardRefExoticComponent<Omit<import("./types.js").ButtonProps, "ref"> & import("react").RefAttributes<HTMLButtonElement>>;
2
+ export declare const IconButton: import("react").ForwardRefExoticComponent<Omit<import("./types.js").IconButtonProps, "ref"> & import("react").RefAttributes<HTMLButtonElement>>;
3
+ export declare const TextField: import("react").ForwardRefExoticComponent<Omit<import("./types.js").TextFieldProps, "ref"> & import("react").RefAttributes<HTMLInputElement>>;
4
+ export declare const TextArea: import("react").ForwardRefExoticComponent<Omit<import("./types.js").TextAreaProps, "ref"> & import("react").RefAttributes<HTMLTextAreaElement>>;
5
+ export declare const Select: import("react").ForwardRefExoticComponent<Omit<import("./types.js").SelectProps, "ref"> & import("react").RefAttributes<HTMLSelectElement>>;
6
+ export declare const Checkbox: import("react").ForwardRefExoticComponent<Omit<import("./types.js").CheckboxProps, "ref"> & import("react").RefAttributes<HTMLInputElement>>;
7
+ export declare const Switch: import("react").ForwardRefExoticComponent<Omit<import("./types.js").SwitchProps, "ref"> & import("react").RefAttributes<HTMLInputElement>>;
8
+ export declare const Dialog: import("react").ForwardRefExoticComponent<Omit<import("./types.js").DialogProps, "ref"> & import("react").RefAttributes<HTMLDialogElement>>;
9
+ export declare const Tabs: {
10
+ (props: import("./types.js").TabsProps, _optional?: Record<string, never>): import("react").ReactElement<import("./types.js").TabsProps, string | import("react").JSXElementConstructor<any>>;
11
+ displayName: "Tabs";
12
+ };
13
+ export declare const Menu: {
14
+ (props: import("./types.js").MenuProps, _optional?: Record<string, never>): import("react").ReactElement<import("./types.js").MenuProps, string | import("react").JSXElementConstructor<any>>;
15
+ displayName: "Menu";
16
+ };
17
+ export declare const Toast: {
18
+ (props: import("./types.js").ToastProps, _optional?: Record<string, never>): import("react").ReactElement<import("./types.js").ToastProps, string | import("react").JSXElementConstructor<any>>;
19
+ displayName: "Toast";
20
+ };
21
+ export declare const Tooltip: {
22
+ (props: import("./types.js").TooltipProps, _optional?: Record<string, never>): import("react").ReactElement<import("./types.js").TooltipProps, string | import("react").JSXElementConstructor<any>>;
23
+ displayName: "Tooltip";
24
+ };
25
+ export declare const Notice: {
26
+ (props: import("./types.js").NoticeProps, _optional?: Record<string, never>): import("react").ReactElement<import("./types.js").NoticeProps, string | import("react").JSXElementConstructor<any>>;
27
+ displayName: "Notice";
28
+ };
29
+ export declare const Spinner: {
30
+ (props: import("./types.js").SpinnerProps, _optional?: Record<string, never>): import("react").ReactElement<import("./types.js").SpinnerProps, string | import("react").JSXElementConstructor<any>>;
31
+ displayName: "Spinner";
32
+ };
33
+ export declare const Badge: {
34
+ (props: import("./types.js").BadgeProps, _optional?: Record<string, never>): import("react").ReactElement<import("./types.js").BadgeProps, string | import("react").JSXElementConstructor<any>>;
35
+ displayName: "Badge";
36
+ };
37
+ //# sourceMappingURL=facade.d.ts.map
@@ -0,0 +1,36 @@
1
+ import { createElement, forwardRef } from 'react';
2
+ import { useFacade, renderOverlay, useOverlayContainer } from './Kit.hooks.js';
3
+ function control(required, _optional = {}) {
4
+ const Facade = forwardRef(function KitFacade(props, ref) {
5
+ const vm = useFacade({ name: required.name, props: { ...props, ref } });
6
+ const container = useOverlayContainer({});
7
+ const result = createElement(vm.Component, vm.props);
8
+ return required.name === 'Dialog' ? renderOverlay({ children: result, container }) : result;
9
+ });
10
+ Facade.displayName = required.name;
11
+ return Facade;
12
+ }
13
+ function view(required, _optional = {}) {
14
+ function Facade(props, _optional = {}) {
15
+ const vm = useFacade({ name: required.name, props });
16
+ return createElement(vm.Component, vm.props);
17
+ }
18
+ Facade.displayName = required.name;
19
+ return Facade;
20
+ }
21
+ export const Button = control({ name: 'Button' });
22
+ export const IconButton = control({ name: 'IconButton' });
23
+ export const TextField = control({ name: 'TextField' });
24
+ export const TextArea = control({ name: 'TextArea' });
25
+ export const Select = control({ name: 'Select' });
26
+ export const Checkbox = control({ name: 'Checkbox' });
27
+ export const Switch = control({ name: 'Switch' });
28
+ export const Dialog = control({ name: 'Dialog' });
29
+ export const Tabs = view({ name: 'Tabs' });
30
+ export const Menu = view({ name: 'Menu' });
31
+ export const Toast = view({ name: 'Toast' });
32
+ export const Tooltip = view({ name: 'Tooltip' });
33
+ export const Notice = view({ name: 'Notice' });
34
+ export const Spinner = view({ name: 'Spinner' });
35
+ export const Badge = view({ name: 'Badge' });
36
+ //# sourceMappingURL=facade.js.map
@@ -0,0 +1,8 @@
1
+ export { KitProvider, Overlay, ToastRegion } from './KitProvider.js';
2
+ export { createKit, createStrictKit, extendKit, describeKit } from './kit.js';
3
+ export { useKit, useKitComponent, useOverlayContainer, useToast } from './Kit.hooks.js';
4
+ export { Button, IconButton, TextField, TextArea, Select, Checkbox, Switch, Dialog, Tabs, Menu, Toast, Tooltip, Notice, Spinner, Badge } from './facade.js';
5
+ export { ConfirmDialog } from './confirm/ConfirmDialog.js';
6
+ export { useConfirmController } from './confirm/ConfirmDialog.hooks.js';
7
+ export type * from './types.js';
8
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1,7 @@
1
+ export { KitProvider, Overlay, ToastRegion } from './KitProvider.js';
2
+ export { createKit, createStrictKit, extendKit, describeKit } from './kit.js';
3
+ export { useKit, useKitComponent, useOverlayContainer, useToast } from './Kit.hooks.js';
4
+ export { Button, IconButton, TextField, TextArea, Select, Checkbox, Switch, Dialog, Tabs, Menu, Toast, Tooltip, Notice, Spinner, Badge } from './facade.js';
5
+ export { ConfirmDialog } from './confirm/ConfirmDialog.js';
6
+ export { useConfirmController } from './confirm/ConfirmDialog.hooks.js';
7
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1,35 @@
1
+ import { type ImplementedComponentName } from '../kit.spec.js';
2
+ import type { GuardMode, ReactKit, ResolvedKit } from './types.js';
3
+ type KitOptions = {
4
+ id?: string;
5
+ contract?: string;
6
+ guard?: GuardMode;
7
+ };
8
+ export declare function createKit(required: {
9
+ components?: Partial<ReactKit>;
10
+ }, optional?: KitOptions): ResolvedKit;
11
+ export declare function createStrictKit(required: {
12
+ components: ReactKit;
13
+ }, optional?: KitOptions): ResolvedKit;
14
+ export declare function extendKit(required: {
15
+ base: ResolvedKit;
16
+ components: Partial<ReactKit>;
17
+ }, optional?: KitOptions): ResolvedKit;
18
+ export declare function describeKit(required: {
19
+ kit: ResolvedKit;
20
+ }, _optional?: Record<string, never>): {
21
+ id: string;
22
+ contract: string;
23
+ coverage: {
24
+ component: ImplementedComponentName;
25
+ parts: readonly ["kit.button"] | readonly ["kit.icon-button"] | readonly ["kit.text-field", "kit.field-label"] | readonly ["kit.text-area", "kit.field-label"] | readonly ["kit.select", "kit.field-label"] | readonly ["kit.checkbox"] | readonly ["kit.switch"] | readonly ["kit.dialog", "kit.dialog-title", "kit.dialog-close", "kit.overlay"] | readonly ["confirm.dialog", "confirm.title", "confirm.body", "confirm.actions", "confirm.cancel", "confirm.confirm"] | readonly ["kit.tabs", "kit.tab", "kit.tab-panel"] | readonly ["kit.menu-trigger", "kit.menu", "kit.menu-item"] | readonly ["kit.toast", "kit.toast-dismiss", "kit.toast-region"] | readonly ["kit.tooltip-trigger", "kit.tooltip"] | readonly ["kit.notice"] | readonly ["kit.spinner"] | readonly ["kit.badge"];
26
+ source: "native" | "native-guard" | "override";
27
+ }[];
28
+ fallbacks: ImplementedComponentName[];
29
+ violations: {
30
+ reasons: string[];
31
+ component: ImplementedComponentName;
32
+ }[];
33
+ };
34
+ export {};
35
+ //# sourceMappingURL=kit.d.ts.map
@@ -0,0 +1,44 @@
1
+ import { KIT_CONTRACT, implementedComponents, kitSpec } from '../kit.spec.js';
2
+ import { assertKitContract, KitConfigError } from '../needs.js';
3
+ import { nativeKit } from './native/index.js';
4
+ export function createKit(required, optional = {}) {
5
+ const contract = optional.contract ?? KIT_CONTRACT.version;
6
+ assertKitContract({ contract });
7
+ const overrides = implementedComponents.filter(name => required.components?.[name] !== undefined);
8
+ const issues = overrides.filter(name => {
9
+ // Runtime callers can bypass TypeScript; validate unknown rather than assuming ComponentType's callable shape.
10
+ const component = required.components[name];
11
+ if (typeof component === 'function')
12
+ return false;
13
+ if (typeof component !== 'object' || component === null || !('$$typeof' in component))
14
+ return true;
15
+ return ![Symbol.for('react.forward_ref'), Symbol.for('react.memo'), Symbol.for('react.lazy')].includes(component.$$typeof);
16
+ });
17
+ if (issues.length)
18
+ throw new KitConfigError({ issues: issues.map(name => `${name} is not a component`) });
19
+ const components = { ...nativeKit };
20
+ // Ignore explicit undefined rather than erasing a working native default.
21
+ for (const name of overrides)
22
+ Object.assign(components, { [name]: required.components[name] });
23
+ return Object.freeze({ id: optional.id ?? 'native', contract, components: Object.freeze(components),
24
+ overrides: Object.freeze(overrides), violations: [], failed: new Set(), guard: optional.guard ?? 'fallback' });
25
+ }
26
+ export function createStrictKit(required, optional = {}) {
27
+ const missing = implementedComponents.filter(name => required.components[name] === undefined);
28
+ if (missing.length)
29
+ throw new KitConfigError({ issues: missing.map(name => `missing ${name}`) });
30
+ return createKit(required, optional);
31
+ }
32
+ export function extendKit(required, optional = {}) {
33
+ const inherited = Object.fromEntries(required.base.overrides.map(name => [name, required.base.components[name]]));
34
+ return createKit({ components: { ...inherited, ...required.components } }, { id: optional.id ?? required.base.id, contract: optional.contract ?? required.base.contract, guard: optional.guard ?? required.base.guard });
35
+ }
36
+ export function describeKit(required, _optional = {}) {
37
+ const { kit } = required;
38
+ const coverage = implementedComponents.map(name => ({ component: name, parts: kitSpec[name].parts,
39
+ source: kit.failed.has(name) ? 'native-guard' : kit.overrides.includes(name) ? 'override' : 'native' }));
40
+ return { id: kit.id, contract: kit.contract, coverage,
41
+ fallbacks: coverage.filter(entry => entry.source !== 'override').map(entry => entry.component),
42
+ violations: kit.violations.map(violation => ({ ...violation, reasons: [...violation.reasons] })) };
43
+ }
44
+ //# sourceMappingURL=kit.js.map
@@ -0,0 +1,13 @@
1
+ import type { ButtonProps, IconButtonProps, TextFieldProps, TextAreaProps, SelectProps, CheckboxProps, SwitchProps, ToastProps, NoticeProps, SpinnerProps, BadgeProps } from '../types.js';
2
+ export declare const NativeButton: import("react").ForwardRefExoticComponent<Omit<ButtonProps, "ref"> & import("react").RefAttributes<HTMLButtonElement>>;
3
+ export declare const NativeIconButton: import("react").ForwardRefExoticComponent<Omit<IconButtonProps, "ref"> & import("react").RefAttributes<HTMLButtonElement>>;
4
+ export declare const NativeTextField: import("react").ForwardRefExoticComponent<Omit<TextFieldProps, "ref"> & import("react").RefAttributes<HTMLInputElement>>;
5
+ export declare const NativeTextArea: import("react").ForwardRefExoticComponent<Omit<TextAreaProps, "ref"> & import("react").RefAttributes<HTMLTextAreaElement>>;
6
+ export declare const NativeSelect: import("react").ForwardRefExoticComponent<Omit<SelectProps, "ref"> & import("react").RefAttributes<HTMLSelectElement>>;
7
+ export declare const NativeCheckbox: import("react").ForwardRefExoticComponent<Omit<CheckboxProps, "ref"> & import("react").RefAttributes<HTMLInputElement>>;
8
+ export declare const NativeSwitch: import("react").ForwardRefExoticComponent<Omit<SwitchProps, "ref"> & import("react").RefAttributes<HTMLInputElement>>;
9
+ export declare function NativeToast(props: ToastProps, _optional?: Record<string, never>): import("react").JSX.Element;
10
+ export declare function NativeNotice(props: NoticeProps, _optional?: Record<string, never>): import("react").JSX.Element;
11
+ export declare function NativeSpinner(props: SpinnerProps, _optional?: Record<string, never>): import("react").JSX.Element;
12
+ export declare function NativeBadge(props: BadgeProps, _optional?: Record<string, never>): import("react").JSX.Element;
13
+ //# sourceMappingURL=Controls.d.ts.map