@koobiq/react-primitives 0.9.0 → 0.11.0
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/behaviors/useMultiSelectState.d.ts +1 -1
- package/dist/behaviors/useMultiSelectState.js +3 -6
- package/dist/components/Form/Form.d.ts +17 -0
- package/dist/components/Form/Form.js +29 -0
- package/dist/components/Form/index.d.ts +1 -0
- package/dist/components/NumberField/NumberField.js +9 -2
- package/dist/components/Radio/RadioGroup.js +8 -2
- package/dist/components/TextField/TextField.js +6 -2
- package/dist/components/index.d.ts +1 -0
- package/dist/index.js +7 -1
- package/dist/utils/index.d.ts +30 -2
- package/dist/utils/index.js +48 -2
- package/package.json +3 -3
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import { type AsyncLoadable, type CollectionBase, type FocusableProps, type InputBase, type LabelableProps, type MultipleSelection, type TextInputBase, type Validation } from '@koobiq/react-core';
|
|
2
2
|
import type { FormValidationState } from '@react-stately/form';
|
|
3
3
|
import type { MenuTriggerState } from '@react-stately/menu';
|
|
4
4
|
import type { OverlayTriggerProps } from '@react-types/overlays';
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { useState } from "react";
|
|
2
|
+
import "@koobiq/react-core";
|
|
2
3
|
import { useFormValidationState } from "@react-stately/form";
|
|
3
4
|
import { useMenuTriggerState } from "@react-stately/menu";
|
|
4
5
|
import { useMultiSelectListState } from "./useMultiSelectListState.js";
|
|
@@ -42,14 +43,10 @@ function useMultiSelectState({
|
|
|
42
43
|
triggerState.close();
|
|
43
44
|
},
|
|
44
45
|
open() {
|
|
45
|
-
|
|
46
|
-
triggerState.open();
|
|
47
|
-
}
|
|
46
|
+
triggerState.open();
|
|
48
47
|
},
|
|
49
48
|
toggle(focusStrategy) {
|
|
50
|
-
|
|
51
|
-
triggerState.toggle(focusStrategy);
|
|
52
|
-
}
|
|
49
|
+
triggerState.toggle(focusStrategy);
|
|
53
50
|
},
|
|
54
51
|
isFocused,
|
|
55
52
|
setFocused
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { GlobalDOMAttributes, FormProps as SharedFormProps } from '@koobiq/react-core';
|
|
2
|
+
import type { ContextValue, DOMProps } from '../../utils';
|
|
3
|
+
export interface FormProps extends SharedFormProps, DOMProps, GlobalDOMAttributes<HTMLFormElement> {
|
|
4
|
+
/**
|
|
5
|
+
* Whether to use native HTML form validation to prevent form submission
|
|
6
|
+
* when a field value is missing or invalid, or mark fields as required
|
|
7
|
+
* or invalid via ARIA.
|
|
8
|
+
* @default 'native'
|
|
9
|
+
*/
|
|
10
|
+
validationBehavior?: 'aria' | 'native';
|
|
11
|
+
}
|
|
12
|
+
export declare const FormContext: import("react").Context<ContextValue<FormProps, HTMLFormElement>>;
|
|
13
|
+
/**
|
|
14
|
+
* A form is a group of inputs that allows users to submit data to a server,
|
|
15
|
+
* with support for providing field validation errors.
|
|
16
|
+
*/
|
|
17
|
+
export declare const Form: import("react").ForwardRefExoticComponent<FormProps & import("react").RefAttributes<HTMLFormElement>>;
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { jsx } from "react/jsx-runtime";
|
|
2
|
+
import { createContext, forwardRef } from "react";
|
|
3
|
+
import { FormValidationContext } from "@react-stately/form";
|
|
4
|
+
import { useContextProps } from "../../utils/index.js";
|
|
5
|
+
const FormContext = createContext(null);
|
|
6
|
+
const Form = forwardRef(function Form2(props, ref) {
|
|
7
|
+
[props, ref] = useContextProps(props, ref, FormContext);
|
|
8
|
+
const {
|
|
9
|
+
validationErrors,
|
|
10
|
+
validationBehavior = "native",
|
|
11
|
+
children,
|
|
12
|
+
className,
|
|
13
|
+
...domProps
|
|
14
|
+
} = props;
|
|
15
|
+
return /* @__PURE__ */ jsx(
|
|
16
|
+
"form",
|
|
17
|
+
{
|
|
18
|
+
noValidate: validationBehavior !== "native",
|
|
19
|
+
...domProps,
|
|
20
|
+
ref,
|
|
21
|
+
className,
|
|
22
|
+
children: /* @__PURE__ */ jsx(FormContext.Provider, { value: { ...props, validationBehavior }, children: /* @__PURE__ */ jsx(FormValidationContext.Provider, { value: validationErrors ?? {}, children }) })
|
|
23
|
+
}
|
|
24
|
+
);
|
|
25
|
+
});
|
|
26
|
+
export {
|
|
27
|
+
Form,
|
|
28
|
+
FormContext
|
|
29
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './Form';
|
|
@@ -5,18 +5,21 @@ import { filterDOMProps } from "@koobiq/react-core";
|
|
|
5
5
|
import { useLocale } from "@react-aria/i18n";
|
|
6
6
|
import { useNumberField } from "@react-aria/numberfield";
|
|
7
7
|
import { useNumberFieldState } from "@react-stately/numberfield";
|
|
8
|
-
import { removeDataAttributes, useRenderProps, Provider } from "../../utils/index.js";
|
|
8
|
+
import { useSlottedContext, removeDataAttributes, useRenderProps, Provider } from "../../utils/index.js";
|
|
9
9
|
import { GroupContext } from "../Group/GroupContext.js";
|
|
10
10
|
import { ButtonContext } from "../Button/ButtonContext.js";
|
|
11
11
|
import { LabelContext } from "../Label/LabelContext.js";
|
|
12
12
|
import { InputContext } from "../Input/InputContext.js";
|
|
13
13
|
import { TextContext } from "../Text/TextContext.js";
|
|
14
14
|
import { FieldErrorContext } from "../FieldError/FieldError.js";
|
|
15
|
+
import { FormContext } from "../Form/Form.js";
|
|
15
16
|
const NumberField = forwardRef(
|
|
16
17
|
(props, ref) => {
|
|
17
18
|
const { isDisabled, isReadOnly, isRequired } = props;
|
|
18
19
|
const inputRef = useRef(null);
|
|
19
20
|
const { locale } = useLocale();
|
|
21
|
+
const { validationBehavior: formValidationBehavior } = useSlottedContext(FormContext) || {};
|
|
22
|
+
const validationBehavior = props.validationBehavior ?? formValidationBehavior ?? "aria";
|
|
20
23
|
const state = useNumberFieldState({ ...props, locale });
|
|
21
24
|
const {
|
|
22
25
|
labelProps,
|
|
@@ -27,7 +30,11 @@ const NumberField = forwardRef(
|
|
|
27
30
|
incrementButtonProps,
|
|
28
31
|
decrementButtonProps,
|
|
29
32
|
...validation
|
|
30
|
-
} = useNumberField(
|
|
33
|
+
} = useNumberField(
|
|
34
|
+
{ ...removeDataAttributes(props), validationBehavior },
|
|
35
|
+
state,
|
|
36
|
+
inputRef
|
|
37
|
+
);
|
|
31
38
|
const DOMProps = filterDOMProps(props);
|
|
32
39
|
delete DOMProps.id;
|
|
33
40
|
const renderProps = useRenderProps({
|
|
@@ -2,23 +2,29 @@
|
|
|
2
2
|
import { jsx } from "react/jsx-runtime";
|
|
3
3
|
import { forwardRef } from "react";
|
|
4
4
|
import { filterDOMProps } from "@koobiq/react-core";
|
|
5
|
-
import { removeDataAttributes, useRenderProps, Provider } from "../../utils/index.js";
|
|
5
|
+
import { useSlottedContext, removeDataAttributes, useRenderProps, Provider } from "../../utils/index.js";
|
|
6
6
|
import { useRadioGroupState } from "../../behaviors/useRadioGroupState.js";
|
|
7
7
|
import { useRadioGroup } from "../../behaviors/useRadioGroup.js";
|
|
8
8
|
import { FieldErrorContext } from "../FieldError/FieldError.js";
|
|
9
|
+
import { FormContext } from "../Form/Form.js";
|
|
9
10
|
import { RadioContext } from "./RadioContext.js";
|
|
10
11
|
import { LabelContext } from "../Label/LabelContext.js";
|
|
11
12
|
import { TextContext } from "../Text/TextContext.js";
|
|
12
13
|
const RadioGroup = forwardRef(
|
|
13
14
|
(props, ref) => {
|
|
14
15
|
const state = useRadioGroupState(props);
|
|
16
|
+
const { validationBehavior: formValidationBehavior } = useSlottedContext(FormContext) || {};
|
|
17
|
+
const validationBehavior = props.validationBehavior ?? formValidationBehavior ?? "aria";
|
|
15
18
|
const {
|
|
16
19
|
radioGroupProps,
|
|
17
20
|
labelProps,
|
|
18
21
|
descriptionProps,
|
|
19
22
|
errorMessageProps,
|
|
20
23
|
...validation
|
|
21
|
-
} = useRadioGroup(
|
|
24
|
+
} = useRadioGroup(
|
|
25
|
+
{ ...removeDataAttributes(props), validationBehavior },
|
|
26
|
+
state
|
|
27
|
+
);
|
|
22
28
|
const renderProps = useRenderProps({
|
|
23
29
|
...props,
|
|
24
30
|
values: {
|
|
@@ -3,14 +3,17 @@ import { jsx } from "react/jsx-runtime";
|
|
|
3
3
|
import { forwardRef, useRef } from "react";
|
|
4
4
|
import { filterDOMProps } from "@koobiq/react-core";
|
|
5
5
|
import { useTextField } from "@react-aria/textfield";
|
|
6
|
-
import { removeDataAttributes, useRenderProps, Provider } from "../../utils/index.js";
|
|
6
|
+
import { useSlottedContext, removeDataAttributes, useRenderProps, Provider } from "../../utils/index.js";
|
|
7
7
|
import { InputContext } from "../Input/InputContext.js";
|
|
8
8
|
import { TextareaContext } from "../Textarea/TextareaContext.js";
|
|
9
|
+
import { FormContext } from "../Form/Form.js";
|
|
9
10
|
import { LabelContext } from "../Label/LabelContext.js";
|
|
10
11
|
import { TextContext } from "../Text/TextContext.js";
|
|
11
12
|
import { FieldErrorContext } from "../FieldError/FieldError.js";
|
|
12
13
|
function TextFieldRender(props, ref) {
|
|
13
14
|
const { isDisabled, isReadOnly, isRequired } = props;
|
|
15
|
+
const { validationBehavior: formValidationBehavior } = useSlottedContext(FormContext) || {};
|
|
16
|
+
const validationBehavior = props.validationBehavior ?? formValidationBehavior ?? "aria";
|
|
14
17
|
const inputRef = useRef(null);
|
|
15
18
|
const {
|
|
16
19
|
labelProps,
|
|
@@ -20,7 +23,8 @@ function TextFieldRender(props, ref) {
|
|
|
20
23
|
...validation
|
|
21
24
|
} = useTextField(
|
|
22
25
|
{
|
|
23
|
-
...removeDataAttributes(props)
|
|
26
|
+
...removeDataAttributes(props),
|
|
27
|
+
validationBehavior
|
|
24
28
|
},
|
|
25
29
|
inputRef
|
|
26
30
|
);
|
package/dist/index.js
CHANGED
|
@@ -30,7 +30,7 @@ import { useCalendar, useCalendarCell, useCalendarGrid } from "@react-aria/calen
|
|
|
30
30
|
export * from "@react-stately/calendar";
|
|
31
31
|
export * from "@react-stately/form";
|
|
32
32
|
export * from "@react-aria/selection";
|
|
33
|
-
import { Provider, removeDataAttributes, useRenderProps } from "./utils/index.js";
|
|
33
|
+
import { DEFAULT_SLOT, Provider, removeDataAttributes, useContextProps, useRenderProps, useSlottedContext } from "./utils/index.js";
|
|
34
34
|
import { useButton } from "./behaviors/useButton.js";
|
|
35
35
|
import { useCheckbox } from "./behaviors/useCheckbox.js";
|
|
36
36
|
import { useLink } from "./behaviors/useLink.js";
|
|
@@ -64,12 +64,16 @@ import { ProgressBar } from "./components/ProgressBar/ProgressBar.js";
|
|
|
64
64
|
import { TextField } from "./components/TextField/TextField.js";
|
|
65
65
|
import { NumberField } from "./components/NumberField/NumberField.js";
|
|
66
66
|
import { FieldError, FieldErrorContext } from "./components/FieldError/FieldError.js";
|
|
67
|
+
import { Form, FormContext } from "./components/Form/Form.js";
|
|
67
68
|
export {
|
|
68
69
|
Button,
|
|
69
70
|
ButtonContext,
|
|
70
71
|
Checkbox,
|
|
72
|
+
DEFAULT_SLOT,
|
|
71
73
|
FieldError,
|
|
72
74
|
FieldErrorContext,
|
|
75
|
+
Form,
|
|
76
|
+
FormContext,
|
|
73
77
|
Group,
|
|
74
78
|
GroupContext,
|
|
75
79
|
Input,
|
|
@@ -95,6 +99,7 @@ export {
|
|
|
95
99
|
useCalendarCell,
|
|
96
100
|
useCalendarGrid,
|
|
97
101
|
useCheckbox,
|
|
102
|
+
useContextProps,
|
|
98
103
|
useGroupContext,
|
|
99
104
|
useInputContext,
|
|
100
105
|
useLink,
|
|
@@ -107,6 +112,7 @@ export {
|
|
|
107
112
|
useRadioGroup,
|
|
108
113
|
useRadioGroupState,
|
|
109
114
|
useRenderProps,
|
|
115
|
+
useSlottedContext,
|
|
110
116
|
useSwitch,
|
|
111
117
|
useTextareaContext,
|
|
112
118
|
useToggleButtonGroup,
|
package/dist/utils/index.d.ts
CHANGED
|
@@ -1,5 +1,31 @@
|
|
|
1
|
-
import type { JSX, CSSProperties, ReactNode, Context } from 'react';
|
|
2
|
-
import type { AriaLabelingProps, DOMProps as SharedDOMProps } from '@react-
|
|
1
|
+
import type { JSX, CSSProperties, ReactNode, Context, ForwardedRef } from 'react';
|
|
2
|
+
import type { AriaLabelingProps, DOMProps as SharedDOMProps, RefObject } from '@koobiq/react-core';
|
|
3
|
+
export declare const DEFAULT_SLOT: unique symbol;
|
|
4
|
+
interface SlottedValue<T> {
|
|
5
|
+
slots?: Record<string | symbol, T>;
|
|
6
|
+
}
|
|
7
|
+
export interface SlotProps {
|
|
8
|
+
/**
|
|
9
|
+
* A slot name for the component. Slots allow the component to receive props from a parent component.
|
|
10
|
+
* An explicit `null` value indicates that the local props completely override all props received from a parent.
|
|
11
|
+
*/
|
|
12
|
+
slot?: string | null;
|
|
13
|
+
}
|
|
14
|
+
export type WithRef<T, E> = T & {
|
|
15
|
+
ref?: ForwardedRef<E>;
|
|
16
|
+
};
|
|
17
|
+
export type SlottedContextValue<T> = SlottedValue<T> | T | null | undefined;
|
|
18
|
+
export type ContextValue<T, E> = SlottedContextValue<WithRef<T, E>>;
|
|
19
|
+
export interface StyleProps {
|
|
20
|
+
/** The CSS [className](https://developer.mozilla.org/en-US/docs/Web/API/Element/className) for the element. */
|
|
21
|
+
className?: string;
|
|
22
|
+
/** The inline [style](https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/style) for the element. */
|
|
23
|
+
style?: CSSProperties;
|
|
24
|
+
}
|
|
25
|
+
export interface DOMProps extends StyleProps, SharedDOMProps {
|
|
26
|
+
/** The children of the component. */
|
|
27
|
+
children?: ReactNode;
|
|
28
|
+
}
|
|
3
29
|
export interface StyleRenderProps<T> {
|
|
4
30
|
/** The CSS [className](https://developer.mozilla.org/en-US/docs/Web/API/Element/className) for the element. A function may be provided to compute the class based on component state. */
|
|
5
31
|
className?: string | ((values: T & {
|
|
@@ -38,4 +64,6 @@ interface ProviderProps<T extends unknown[]> {
|
|
|
38
64
|
}
|
|
39
65
|
export declare function Provider<T extends unknown[]>({ values, children, }: ProviderProps<T>): JSX.Element;
|
|
40
66
|
export declare function removeDataAttributes<T>(props: T): T;
|
|
67
|
+
export declare function useSlottedContext<T>(context: Context<SlottedContextValue<T>>, slot?: string | null): T | null | undefined;
|
|
68
|
+
export declare function useContextProps<T, U extends SlotProps, E extends Element>(props: T & SlotProps, ref: ForwardedRef<E> | undefined, context: Context<ContextValue<U, E>>): [T, RefObject<E | null>];
|
|
41
69
|
export {};
|
package/dist/utils/index.js
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { jsx } from "react/jsx-runtime";
|
|
2
|
-
import { useMemo } from "react";
|
|
2
|
+
import { useMemo, useContext } from "react";
|
|
3
|
+
import { useObjectRef, mergeRefs, mergeProps } from "@koobiq/react-core";
|
|
4
|
+
const DEFAULT_SLOT = Symbol("default");
|
|
3
5
|
function useRenderProps(props) {
|
|
4
6
|
const {
|
|
5
7
|
className,
|
|
@@ -65,8 +67,52 @@ function removeDataAttributes(props) {
|
|
|
65
67
|
}
|
|
66
68
|
return filteredProps;
|
|
67
69
|
}
|
|
70
|
+
function useSlottedContext(context, slot) {
|
|
71
|
+
const ctx = useContext(context);
|
|
72
|
+
if (slot === null) {
|
|
73
|
+
return null;
|
|
74
|
+
}
|
|
75
|
+
if (ctx && typeof ctx === "object" && "slots" in ctx && ctx.slots) {
|
|
76
|
+
const slotKey = slot || DEFAULT_SLOT;
|
|
77
|
+
if (!ctx.slots[slotKey]) {
|
|
78
|
+
const availableSlots = new Intl.ListFormat().format(
|
|
79
|
+
Object.keys(ctx.slots).map((p) => `"${p}"`)
|
|
80
|
+
);
|
|
81
|
+
const errorMessage = slot ? `Invalid slot "${slot}".` : "A slot prop is required.";
|
|
82
|
+
throw new Error(
|
|
83
|
+
`${errorMessage} Valid slot names are ${availableSlots}.`
|
|
84
|
+
);
|
|
85
|
+
}
|
|
86
|
+
return ctx.slots[slotKey];
|
|
87
|
+
}
|
|
88
|
+
return ctx;
|
|
89
|
+
}
|
|
90
|
+
function useContextProps(props, ref, context) {
|
|
91
|
+
const ctx = useSlottedContext(context, props.slot) || {};
|
|
92
|
+
const { ref: contextRef, ...contextProps } = ctx;
|
|
93
|
+
const mergedRef = useObjectRef(
|
|
94
|
+
useMemo(() => mergeRefs(ref, contextRef), [ref, contextRef])
|
|
95
|
+
);
|
|
96
|
+
const mergedProps = mergeProps(contextProps, props);
|
|
97
|
+
if ("style" in contextProps && contextProps.style && "style" in props && props.style) {
|
|
98
|
+
if (typeof contextProps.style === "function" || typeof props.style === "function") {
|
|
99
|
+
mergedProps.style = (renderProps) => {
|
|
100
|
+
const contextStyle = typeof contextProps.style === "function" ? contextProps.style(renderProps) : contextProps.style;
|
|
101
|
+
const defaultStyle = { ...renderProps.defaultStyle, ...contextStyle };
|
|
102
|
+
const style = typeof props.style === "function" ? props.style({ ...renderProps, defaultStyle }) : props.style;
|
|
103
|
+
return { ...defaultStyle, ...style };
|
|
104
|
+
};
|
|
105
|
+
} else {
|
|
106
|
+
mergedProps.style = { ...contextProps.style, ...props.style };
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
return [mergedProps, mergedRef];
|
|
110
|
+
}
|
|
68
111
|
export {
|
|
112
|
+
DEFAULT_SLOT,
|
|
69
113
|
Provider,
|
|
70
114
|
removeDataAttributes,
|
|
71
|
-
|
|
115
|
+
useContextProps,
|
|
116
|
+
useRenderProps,
|
|
117
|
+
useSlottedContext
|
|
72
118
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@koobiq/react-primitives",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.11.0",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"types": "./dist/index.d.ts",
|
|
@@ -64,8 +64,8 @@
|
|
|
64
64
|
"@react-stately/toggle": "^3.7.0",
|
|
65
65
|
"@react-stately/tooltip": "^3.5.5",
|
|
66
66
|
"@react-stately/tree": "^3.8.9",
|
|
67
|
-
"@koobiq/logger": "0.
|
|
68
|
-
"@koobiq/react-core": "0.
|
|
67
|
+
"@koobiq/logger": "0.11.0",
|
|
68
|
+
"@koobiq/react-core": "0.11.0"
|
|
69
69
|
},
|
|
70
70
|
"peerDependencies": {
|
|
71
71
|
"react": "18.x || 19.x",
|