@jobber/components 9.14.4 → 9.15.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.
@@ -15,6 +15,7 @@ var MenuSubmenuTrigger = require('../MenuSubmenuTrigger-cjs.js');
15
15
  var ReactDOM = require('react-dom');
16
16
  var SelectGroupLabel = require('../SelectGroupLabel-cjs.js');
17
17
  var Separator = require('../Separator-cjs.js');
18
+ var useFieldValidation = require('../useFieldValidation-cjs.js');
18
19
  var NumberFieldInput = require('../NumberFieldInput-cjs.js');
19
20
  var useLabel = require('../useLabel-cjs.js');
20
21
  require('../stringifyLocale-cjs.js');
@@ -174,7 +175,7 @@ const AutocompleteItem = ComboboxSeparator.ComboboxItem;
174
175
  */
175
176
  const AutocompleteSeparator = ComboboxSeparator.ListboxSeparator;
176
177
 
177
- var index_parts$7 = /*#__PURE__*/Object.freeze({
178
+ var index_parts$8 = /*#__PURE__*/Object.freeze({
178
179
  __proto__: null,
179
180
  Arrow: ComboboxSeparator.ComboboxArrow,
180
181
  Backdrop: ComboboxSeparator.ComboboxBackdrop,
@@ -201,7 +202,7 @@ var index_parts$7 = /*#__PURE__*/Object.freeze({
201
202
  useFilteredItems: ComboboxSeparator.useFilteredItems
202
203
  });
203
204
 
204
- var index_parts$6 = /*#__PURE__*/Object.freeze({
205
+ var index_parts$7 = /*#__PURE__*/Object.freeze({
205
206
  __proto__: null,
206
207
  Backdrop: DrawerDescription.DialogBackdrop,
207
208
  Close: DrawerTitle.DialogClose,
@@ -1503,7 +1504,7 @@ function createDrawerHandle() {
1503
1504
  return new DrawerHandle();
1504
1505
  }
1505
1506
 
1506
- var index_parts$5 = /*#__PURE__*/Object.freeze({
1507
+ var index_parts$6 = /*#__PURE__*/Object.freeze({
1507
1508
  __proto__: null,
1508
1509
  Backdrop: ScrollAreaViewport.DrawerBackdrop,
1509
1510
  Close: DrawerTitle.DrawerClose,
@@ -2308,7 +2309,7 @@ function createMenuHandle() {
2308
2309
  return new MenuHandle();
2309
2310
  }
2310
2311
 
2311
- var index_parts$4 = /*#__PURE__*/Object.freeze({
2312
+ var index_parts$5 = /*#__PURE__*/Object.freeze({
2312
2313
  __proto__: null,
2313
2314
  Arrow: MenuArrow,
2314
2315
  Backdrop: MenuSubmenuTrigger.MenuBackdrop,
@@ -2681,7 +2682,7 @@ const ScrollAreaCorner = /*#__PURE__*/React__namespace.forwardRef(function Scrol
2681
2682
  });
2682
2683
  if (process.env.NODE_ENV !== "production") ScrollAreaCorner.displayName = "ScrollAreaCorner";
2683
2684
 
2684
- var index_parts$3 = /*#__PURE__*/Object.freeze({
2685
+ var index_parts$4 = /*#__PURE__*/Object.freeze({
2685
2686
  __proto__: null,
2686
2687
  Content: ScrollAreaContent,
2687
2688
  Corner: ScrollAreaCorner,
@@ -2691,6 +2692,689 @@ var index_parts$3 = /*#__PURE__*/Object.freeze({
2691
2692
  Viewport: ScrollAreaViewport.ScrollAreaViewport
2692
2693
  });
2693
2694
 
2695
+ /**
2696
+ * Returns the default button a browser uses for implicit form submission.
2697
+ *
2698
+ * This is useful for custom form controls that need to mirror native Enter key behavior.
2699
+ * Clicking the returned submitter preserves browser semantics such as the submitter's click
2700
+ * event, `SubmitEvent.submitter`, and submitter-specific attributes.
2701
+ *
2702
+ * The function follows the controls exposed by `form.elements`, which includes controls associated
2703
+ * through the `form` attribute. Disabled submitters can be returned because the default button is
2704
+ * determined before disabled state is considered; clicking a disabled submitter is a no-op.
2705
+ */
2706
+ function getDefaultFormSubmitter(form) {
2707
+ if (!form) {
2708
+ return null;
2709
+ }
2710
+ for (const candidate of form.elements) {
2711
+ const tagName = candidate.tagName;
2712
+ if (tagName === 'BUTTON' || tagName === 'INPUT') {
2713
+ const button = candidate;
2714
+
2715
+ // Intentionally excludes input[type="image"]: Chromium omits it from form.elements,
2716
+ // so supporting it would require separate traversal for an exotic submitter type.
2717
+ if (button.type === 'submit') {
2718
+ return button;
2719
+ }
2720
+ }
2721
+ }
2722
+ return null;
2723
+ }
2724
+
2725
+ function getCheckboxStateAttributesMapping(state) {
2726
+ return {
2727
+ checked(value) {
2728
+ if (state.indeterminate) {
2729
+ // `data-indeterminate` is already handled by the `indeterminate` prop.
2730
+ return {};
2731
+ }
2732
+ if (value) {
2733
+ return {
2734
+ 'data-checked': ''
2735
+ };
2736
+ }
2737
+ return {
2738
+ 'data-unchecked': ''
2739
+ };
2740
+ },
2741
+ ...useCompositeListItem.fieldValidityMapping
2742
+ };
2743
+ }
2744
+
2745
+ function useAriaLabelledBy(explicitAriaLabelledBy, labelId, labelSourceRef, enableFallback = true, labelSourceId) {
2746
+ const [fallbackAriaLabelledBy, setFallbackAriaLabelledBy] = React__namespace.useState();
2747
+ const generatedLabelId = useScrollLock.useBaseUiId(labelSourceId ? `${labelSourceId}-label` : undefined);
2748
+ const ariaLabelledBy = explicitAriaLabelledBy ?? labelId ?? fallbackAriaLabelledBy;
2749
+
2750
+ // Fallback for <span> controls labelled by wrapping/sibling native <label>.
2751
+ // Run after every commit so DOM association changes (e.g. label mount/unmount)
2752
+ // are reflected even when props/state deps are unchanged.
2753
+ // eslint-disable-next-line react-hooks/exhaustive-deps
2754
+ useScrollLock.useIsoLayoutEffect(() => {
2755
+ const nextAriaLabelledBy = explicitAriaLabelledBy || labelId || !enableFallback ? undefined : getAriaLabelledBy(labelSourceRef.current, generatedLabelId);
2756
+ if (fallbackAriaLabelledBy !== nextAriaLabelledBy) {
2757
+ setFallbackAriaLabelledBy(nextAriaLabelledBy);
2758
+ }
2759
+ });
2760
+ return ariaLabelledBy;
2761
+ }
2762
+ function getAriaLabelledBy(labelSource, generatedLabelId) {
2763
+ const label = findAssociatedLabel(labelSource);
2764
+ if (!label) {
2765
+ return undefined;
2766
+ }
2767
+ if (!label.id && generatedLabelId) {
2768
+ label.id = generatedLabelId;
2769
+ }
2770
+ return label.id || undefined;
2771
+ }
2772
+ function findAssociatedLabel(labelSource) {
2773
+ if (!labelSource) {
2774
+ return undefined;
2775
+ }
2776
+
2777
+ // Fast path before the expensive `.labels` read.
2778
+ const parent = labelSource.parentElement;
2779
+ if (parent && parent.tagName === 'LABEL') {
2780
+ return parent;
2781
+ }
2782
+ const controlId = labelSource.id;
2783
+ if (controlId) {
2784
+ const nextSibling = labelSource.nextElementSibling;
2785
+ if (nextSibling && nextSibling.htmlFor === controlId) {
2786
+ return nextSibling;
2787
+ }
2788
+ }
2789
+ const labels = labelSource.labels;
2790
+ return labels && labels[0];
2791
+ }
2792
+
2793
+ const CheckboxGroupContext = /*#__PURE__*/React__namespace.createContext(undefined);
2794
+ if (process.env.NODE_ENV !== "production") CheckboxGroupContext.displayName = "CheckboxGroupContext";
2795
+ function useCheckboxGroupContext() {
2796
+ return React__namespace.useContext(CheckboxGroupContext);
2797
+ }
2798
+
2799
+ const CheckboxRootContext = /*#__PURE__*/React__namespace.createContext(undefined);
2800
+ if (process.env.NODE_ENV !== "production") CheckboxRootContext.displayName = "CheckboxRootContext";
2801
+ function useCheckboxRootContext() {
2802
+ const context = React__namespace.useContext(CheckboxRootContext);
2803
+ if (context === undefined) {
2804
+ throw new Error(process.env.NODE_ENV !== "production" ? 'Base UI: CheckboxRootContext is missing. Checkbox parts must be placed within <Checkbox.Root>.' : useRenderElement.formatErrorMessage(14));
2805
+ }
2806
+ return context;
2807
+ }
2808
+
2809
+ const PARENT_CHECKBOX = 'data-parent';
2810
+
2811
+ /**
2812
+ * Represents the checkbox itself.
2813
+ * Renders a `<span>` element and a hidden `<input>` beside.
2814
+ *
2815
+ * Documentation: [Base UI Checkbox](https://base-ui.com/react/components/checkbox)
2816
+ */
2817
+ const CheckboxRoot = /*#__PURE__*/React__namespace.forwardRef(function CheckboxRoot(componentProps, forwardedRef) {
2818
+ const {
2819
+ checked: checkedProp,
2820
+ className,
2821
+ defaultChecked = false,
2822
+ 'aria-labelledby': ariaLabelledByProp,
2823
+ disabled: disabledProp = false,
2824
+ form,
2825
+ id: idProp,
2826
+ indeterminate = false,
2827
+ inputRef: inputRefProp,
2828
+ name: nameProp,
2829
+ onCheckedChange,
2830
+ parent = false,
2831
+ readOnly = false,
2832
+ render,
2833
+ required = false,
2834
+ uncheckedValue,
2835
+ value: valueProp,
2836
+ nativeButton = false,
2837
+ style,
2838
+ ...elementProps
2839
+ } = componentProps;
2840
+ const {
2841
+ clearErrors
2842
+ } = useCompositeListItem.useFormContext();
2843
+ const {
2844
+ disabled: rootDisabled,
2845
+ name: fieldName,
2846
+ setDirty,
2847
+ setFilled,
2848
+ setFocused,
2849
+ setTouched,
2850
+ state: fieldState,
2851
+ validationMode,
2852
+ validityData,
2853
+ validation: localValidation
2854
+ } = useCompositeListItem.useFieldRootContext();
2855
+ const fieldItemContext = useFieldValidation.useFieldItemContext();
2856
+ const {
2857
+ labelId,
2858
+ controlId,
2859
+ registerControlId,
2860
+ getDescriptionProps
2861
+ } = useCompositeListItem.useLabelableContext();
2862
+ const groupContext = useCheckboxGroupContext();
2863
+ const parentContext = groupContext?.allValues === undefined ? undefined : groupContext.parent;
2864
+ const isGroupedWithParent = parentContext !== undefined;
2865
+ const disabled = rootDisabled || fieldItemContext.disabled || groupContext?.disabled || disabledProp;
2866
+ const name = fieldName ?? nameProp;
2867
+ const value = valueProp ?? name;
2868
+ const id = useScrollLock.useBaseUiId();
2869
+ const generatedInputId = useScrollLock.useBaseUiId();
2870
+ let inputId = idProp || controlId;
2871
+ if (isGroupedWithParent) {
2872
+ if (parent) {
2873
+ inputId = generatedInputId;
2874
+ } else if (value !== undefined) {
2875
+ inputId = `${parentContext.id}-${value}`;
2876
+ } else {
2877
+ inputId ||= generatedInputId;
2878
+ }
2879
+ }
2880
+ let groupProps = {};
2881
+ if (isGroupedWithParent) {
2882
+ if (parent) {
2883
+ groupProps = parentContext.getParentProps();
2884
+ } else if (value !== undefined) {
2885
+ groupProps = parentContext.getChildProps(value);
2886
+ }
2887
+ }
2888
+ const {
2889
+ checked: groupChecked = checkedProp,
2890
+ indeterminate: groupIndeterminate = indeterminate,
2891
+ onCheckedChange: groupOnChange,
2892
+ ...otherGroupProps
2893
+ } = groupProps;
2894
+ const groupValue = groupContext?.value;
2895
+ const controlRef = React__namespace.useRef(null);
2896
+ const controlSourceRef = useRenderElement.useRefWithInit(() => Symbol());
2897
+ const hasRegisteredRef = React__namespace.useRef(false);
2898
+ const {
2899
+ getButtonProps,
2900
+ buttonRef
2901
+ } = useScrollLock.useButton({
2902
+ disabled,
2903
+ native: nativeButton
2904
+ });
2905
+ const validation = groupContext?.validation ?? localValidation;
2906
+ const [checked, setCheckedState] = useScrollLock.useControlled({
2907
+ controlled: value !== undefined && groupValue !== undefined && !parent ? groupValue.includes(value) : groupChecked,
2908
+ default: defaultChecked,
2909
+ name: 'Checkbox',
2910
+ state: 'checked'
2911
+ });
2912
+ const computedChecked = isGroupedWithParent ? Boolean(groupChecked) : checked;
2913
+ const computedIndeterminate = isGroupedWithParent ? groupIndeterminate || indeterminate : indeterminate;
2914
+
2915
+ // can't use useLabelableId because of optional groupContext and/or parent
2916
+ useScrollLock.useIsoLayoutEffect(() => {
2917
+ if (registerControlId === useRenderElement.NOOP) {
2918
+ return undefined;
2919
+ }
2920
+ hasRegisteredRef.current = true;
2921
+ registerControlId(controlSourceRef.current, inputId);
2922
+ return undefined;
2923
+ }, [inputId, registerControlId, controlSourceRef]);
2924
+ React__namespace.useEffect(() => {
2925
+ const controlSource = controlSourceRef.current;
2926
+ return () => {
2927
+ if (!hasRegisteredRef.current || registerControlId === useRenderElement.NOOP) {
2928
+ return;
2929
+ }
2930
+ hasRegisteredRef.current = false;
2931
+ registerControlId(controlSource, undefined);
2932
+ };
2933
+ }, [registerControlId, controlSourceRef]);
2934
+ useCompositeListItem.useRegisterFieldControl(controlRef, id, checked, undefined, !groupContext && !disabled, nameProp);
2935
+ const inputRef = React__namespace.useRef(null);
2936
+ const registerFieldInput = validation.registerInput;
2937
+ const registeredInputValue = groupContext ? value : undefined;
2938
+ const registerInput = React__namespace.useCallback(element => registerFieldInput(element, {
2939
+ controlRef,
2940
+ value: registeredInputValue
2941
+ }), [registerFieldInput, registeredInputValue]);
2942
+ const mergedInputRef = useRenderElement.useMergedRefs(inputRefProp, inputRef, parent ? undefined : registerInput);
2943
+ const ariaLabelledBy = useAriaLabelledBy(ariaLabelledByProp, labelId, inputRef, !nativeButton, inputId ?? undefined);
2944
+ useScrollLock.useIsoLayoutEffect(() => {
2945
+ if (inputRef.current) {
2946
+ inputRef.current.indeterminate = computedIndeterminate;
2947
+ if (checked) {
2948
+ setFilled(true);
2949
+ }
2950
+ }
2951
+ }, [checked, computedIndeterminate, setFilled]);
2952
+ useScrollLock.useValueChanged(checked, () => {
2953
+ if (groupContext) {
2954
+ return;
2955
+ }
2956
+ clearErrors(name);
2957
+ setFilled(checked);
2958
+ setDirty(checked !== validityData.initialValue);
2959
+ validation.change(checked);
2960
+ });
2961
+ const inputProps = useRenderElement.mergeProps({
2962
+ checked,
2963
+ disabled,
2964
+ form,
2965
+ // parent checkboxes unset `name` to be excluded from form submission
2966
+ name: parent ? undefined : name,
2967
+ // Set `id` to stop Chrome warning about an unassociated input.
2968
+ // When using a native button, the `id` is applied to the button instead.
2969
+ id: nativeButton ? undefined : inputId ?? undefined,
2970
+ required,
2971
+ ref: mergedInputRef,
2972
+ style: name ? useScrollLock.visuallyHiddenInput : useScrollLock.visuallyHidden,
2973
+ tabIndex: -1,
2974
+ type: 'checkbox',
2975
+ 'aria-hidden': true,
2976
+ onChange(event) {
2977
+ // Workaround for https://github.com/react/react/issues/9023
2978
+ if (event.nativeEvent.defaultPrevented) {
2979
+ return;
2980
+ }
2981
+ if (readOnly) {
2982
+ event.preventDefault();
2983
+ return;
2984
+ }
2985
+ const nextChecked = event.currentTarget.checked;
2986
+ const details = useScrollLock.createChangeEventDetails(useScrollLock.none, event.nativeEvent);
2987
+ onCheckedChange?.(nextChecked, details);
2988
+ if (details.isCanceled) {
2989
+ return;
2990
+ }
2991
+ groupOnChange?.(nextChecked, details);
2992
+ if (details.isCanceled) {
2993
+ return;
2994
+ }
2995
+ setCheckedState(nextChecked);
2996
+ if (value !== undefined && groupContext !== undefined && !parent && !isGroupedWithParent) {
2997
+ const nextGroupValue = nextChecked ? [...groupContext.value, value] : groupContext.value.filter(item => item !== value);
2998
+ groupContext.setValue(nextGroupValue, details);
2999
+ }
3000
+ },
3001
+ onClick(event) {
3002
+ // The click dispatched from the root's `onClick` is an implementation detail
3003
+ // and must not reach ancestors, which already receive the original click.
3004
+ event.stopPropagation();
3005
+ },
3006
+ onFocus() {
3007
+ controlRef.current?.focus();
3008
+ }
3009
+ },
3010
+ // React <19 sets an empty value if `undefined` is passed explicitly
3011
+ // To avoid this, we only set the value if it's defined
3012
+ valueProp !== undefined ? {
3013
+ value: (groupContext ? checked && valueProp : valueProp) || ''
3014
+ } : useRenderElement.EMPTY_OBJECT, getDescriptionProps, props => validation.getValidationProps(disabled, props));
3015
+ React__namespace.useEffect(() => {
3016
+ if (!parentContext || value === undefined) {
3017
+ return undefined;
3018
+ }
3019
+ const disabledStates = parentContext.disabledStatesRef.current;
3020
+ disabledStates.set(value, disabled);
3021
+ return () => {
3022
+ disabledStates.delete(value);
3023
+ };
3024
+ }, [parentContext, disabled, value]);
3025
+ const state = React__namespace.useMemo(() => ({
3026
+ ...fieldState,
3027
+ checked: computedChecked,
3028
+ disabled,
3029
+ readOnly,
3030
+ required,
3031
+ indeterminate: computedIndeterminate
3032
+ }), [fieldState, computedChecked, disabled, readOnly, required, computedIndeterminate]);
3033
+ const stateAttributesMapping = getCheckboxStateAttributesMapping(state);
3034
+ const element = useRenderElement.useRenderElement('span', componentProps, {
3035
+ state,
3036
+ ref: [buttonRef, controlRef, forwardedRef],
3037
+ props: [{
3038
+ id: nativeButton ? inputId ?? undefined : id,
3039
+ role: 'checkbox',
3040
+ 'aria-checked': computedIndeterminate ? 'mixed' : computedChecked,
3041
+ 'aria-readonly': readOnly || undefined,
3042
+ 'aria-required': required || undefined,
3043
+ 'aria-labelledby': ariaLabelledBy,
3044
+ [PARENT_CHECKBOX]: parent ? '' : undefined,
3045
+ onFocus() {
3046
+ if (!disabled) {
3047
+ setFocused(true);
3048
+ }
3049
+ },
3050
+ onBlur() {
3051
+ const inputEl = inputRef.current;
3052
+ if (!inputEl) {
3053
+ return;
3054
+ }
3055
+ setTouched(true);
3056
+ setFocused(false);
3057
+ if (validationMode === 'onBlur') {
3058
+ validation.commit(groupContext ? groupValue : inputEl.checked);
3059
+ }
3060
+ },
3061
+ onKeyDown(event) {
3062
+ if (event.key !== 'Enter') {
3063
+ return;
3064
+ }
3065
+
3066
+ // Let consumer `preventDefault()` handlers opt out while defensively stopping
3067
+ // any remaining Base UI Enter handling from treating the checkbox as a button.
3068
+ event.preventBaseUIHandler();
3069
+ if (event.defaultPrevented) {
3070
+ return;
3071
+ }
3072
+ const formToSubmit = inputRef.current?.form ?? null;
3073
+ const currentTarget = event.currentTarget;
3074
+ const nativeEvent = event.nativeEvent;
3075
+ const originalPreventDefault = event.preventDefault;
3076
+ const originalNativePreventDefault = nativeEvent.preventDefault;
3077
+ let preventDefaultCalledAfterPropagation = false;
3078
+ event.preventDefault = () => {
3079
+ preventDefaultCalledAfterPropagation = true;
3080
+ originalPreventDefault.call(event);
3081
+ };
3082
+ nativeEvent.preventDefault = () => {
3083
+ preventDefaultCalledAfterPropagation = true;
3084
+ originalNativePreventDefault.call(nativeEvent);
3085
+ };
3086
+
3087
+ // Enter should not activate/toggle the checkbox. Cancel the native button behavior
3088
+ // without setting React's synthetic `defaultPrevented`, so ancestor React handlers
3089
+ // can still opt out by calling `preventDefault()` during propagation.
3090
+ originalNativePreventDefault.call(nativeEvent);
3091
+ useScrollLock.getWindow(currentTarget).queueMicrotask(() => {
3092
+ event.preventDefault = originalPreventDefault;
3093
+ nativeEvent.preventDefault = originalNativePreventDefault;
3094
+ if (!preventDefaultCalledAfterPropagation) {
3095
+ getDefaultFormSubmitter(formToSubmit)?.click();
3096
+ }
3097
+ });
3098
+ },
3099
+ onClick(event) {
3100
+ if (readOnly || disabled) {
3101
+ return;
3102
+ }
3103
+ event.preventDefault();
3104
+ const input = inputRef.current;
3105
+ if (!input) {
3106
+ return;
3107
+ }
3108
+ useScrollLock.dispatchClickWithModifiers(input, event);
3109
+ }
3110
+ }, elementProps, otherGroupProps, getButtonProps, getDescriptionProps, props => validation.getValidationProps(disabled, props)],
3111
+ stateAttributesMapping
3112
+ });
3113
+ return /*#__PURE__*/jsxRuntime.jsxs(CheckboxRootContext.Provider, {
3114
+ value: state,
3115
+ children: [element, !checked && !groupContext && name && !parent && uncheckedValue !== undefined && /*#__PURE__*/jsxRuntime.jsx("input", {
3116
+ type: "hidden",
3117
+ form: form,
3118
+ name: name,
3119
+ value: uncheckedValue,
3120
+ disabled: disabled
3121
+ }), /*#__PURE__*/jsxRuntime.jsx("input", {
3122
+ ...inputProps,
3123
+ suppressHydrationWarning: true
3124
+ })]
3125
+ });
3126
+ });
3127
+ if (process.env.NODE_ENV !== "production") CheckboxRoot.displayName = "CheckboxRoot";
3128
+
3129
+ /**
3130
+ * Indicates whether the checkbox is ticked.
3131
+ * Renders a `<span>` element.
3132
+ *
3133
+ * Documentation: [Base UI Checkbox](https://base-ui.com/react/components/checkbox)
3134
+ */
3135
+ const CheckboxIndicator = /*#__PURE__*/React__namespace.forwardRef(function CheckboxIndicator(componentProps, forwardedRef) {
3136
+ const {
3137
+ render,
3138
+ className,
3139
+ style,
3140
+ keepMounted = false,
3141
+ ...elementProps
3142
+ } = componentProps;
3143
+ const rootState = useCheckboxRootContext();
3144
+ const rendered = rootState.checked || rootState.indeterminate;
3145
+ const {
3146
+ mounted,
3147
+ transitionStatus,
3148
+ setMounted
3149
+ } = useScrollLock.useTransitionStatus(rendered);
3150
+ const indicatorRef = React__namespace.useRef(null);
3151
+ const state = {
3152
+ ...rootState,
3153
+ transitionStatus
3154
+ };
3155
+ useScrollLock.useOpenChangeComplete({
3156
+ open: rendered,
3157
+ ref: indicatorRef,
3158
+ onComplete() {
3159
+ if (!rendered) {
3160
+ setMounted(false);
3161
+ }
3162
+ }
3163
+ });
3164
+ const baseStateAttributesMapping = getCheckboxStateAttributesMapping(rootState);
3165
+ const stateAttributesMapping = {
3166
+ ...baseStateAttributesMapping,
3167
+ ...useScrollLock.transitionStatusMapping
3168
+ };
3169
+ const shouldRender = keepMounted || mounted;
3170
+ const element = useRenderElement.useRenderElement('span', componentProps, {
3171
+ ref: [forwardedRef, indicatorRef],
3172
+ state,
3173
+ stateAttributesMapping,
3174
+ props: elementProps
3175
+ });
3176
+ if (!shouldRender) {
3177
+ return null;
3178
+ }
3179
+ return element;
3180
+ });
3181
+ if (process.env.NODE_ENV !== "production") CheckboxIndicator.displayName = "CheckboxIndicator";
3182
+
3183
+ var index_parts$3 = /*#__PURE__*/Object.freeze({
3184
+ __proto__: null,
3185
+ Indicator: CheckboxIndicator,
3186
+ Root: CheckboxRoot
3187
+ });
3188
+
3189
+ function useCheckboxGroupParent(params) {
3190
+ const {
3191
+ allValues = useRenderElement.EMPTY_ARRAY,
3192
+ value,
3193
+ onValueChange: onValueChangeProp
3194
+ } = params;
3195
+ const uncontrolledStateRef = React__namespace.useRef(value);
3196
+ const disabledStatesRef = React__namespace.useRef(new Map());
3197
+ const [status, setStatus] = React__namespace.useState('mixed');
3198
+ const id = useScrollLock.useBaseUiId();
3199
+ const checked = value.length === allValues.length;
3200
+ const indeterminate = value.length !== allValues.length && value.length > 0;
3201
+ const onValueChange = useScrollLock.useStableCallback(onValueChangeProp);
3202
+ const getParentProps = React__namespace.useCallback(() => ({
3203
+ id,
3204
+ indeterminate,
3205
+ checked,
3206
+ // TODO: custom `id` on child checkboxes breaks this
3207
+ // https://github.com/mui/base-ui/issues/2691
3208
+ 'aria-controls': allValues.map(v => `${id}-${v}`).join(' '),
3209
+ onCheckedChange(_, eventDetails) {
3210
+ const uncontrolledState = uncontrolledStateRef.current;
3211
+
3212
+ // None except the disabled ones that are checked, which can't be changed.
3213
+ const none = allValues.filter(v => disabledStatesRef.current.get(v) && uncontrolledState.includes(v));
3214
+ // "All" that are valid:
3215
+ // - any that aren't disabled
3216
+ // - disabled ones that are checked
3217
+ const all = allValues.filter(v => !disabledStatesRef.current.get(v) || uncontrolledState.includes(v));
3218
+ const allOnOrOff = uncontrolledState.length === all.length || uncontrolledState.length === 0;
3219
+ if (allOnOrOff) {
3220
+ if (value.length === all.length) {
3221
+ onValueChange(none, eventDetails);
3222
+ } else {
3223
+ onValueChange(all, eventDetails);
3224
+ }
3225
+ return;
3226
+ }
3227
+ let nextStatus = 'mixed';
3228
+ let nextValue = uncontrolledState;
3229
+ if (status === 'mixed') {
3230
+ nextStatus = 'on';
3231
+ nextValue = all;
3232
+ } else if (status === 'on') {
3233
+ nextStatus = 'off';
3234
+ nextValue = none;
3235
+ }
3236
+ onValueChange(nextValue, eventDetails);
3237
+ if (!eventDetails.isCanceled) {
3238
+ setStatus(nextStatus);
3239
+ }
3240
+ }
3241
+ }), [allValues, checked, id, indeterminate, onValueChange, status, value.length]);
3242
+ const getChildProps = React__namespace.useCallback(childValue => ({
3243
+ checked: value.includes(childValue),
3244
+ onCheckedChange(nextChecked, eventDetails) {
3245
+ const newValue = value.slice();
3246
+ if (nextChecked) {
3247
+ newValue.push(childValue);
3248
+ } else {
3249
+ newValue.splice(newValue.indexOf(childValue), 1);
3250
+ }
3251
+ onValueChange(newValue, eventDetails);
3252
+ if (!eventDetails.isCanceled) {
3253
+ uncontrolledStateRef.current = newValue;
3254
+ setStatus('mixed');
3255
+ }
3256
+ }
3257
+ }), [onValueChange, value]);
3258
+ return React__namespace.useMemo(() => ({
3259
+ id,
3260
+ getParentProps,
3261
+ getChildProps,
3262
+ disabledStatesRef
3263
+ }), [id, getParentProps, getChildProps]);
3264
+ }
3265
+
3266
+ const CheckboxGroup = /*#__PURE__*/React__namespace.forwardRef(function CheckboxGroup(componentProps, forwardedRef) {
3267
+ const {
3268
+ allValues,
3269
+ className,
3270
+ defaultValue: defaultValueProp,
3271
+ disabled: disabledProp = false,
3272
+ id: idProp,
3273
+ onValueChange,
3274
+ render,
3275
+ value: externalValue,
3276
+ style,
3277
+ ...elementProps
3278
+ } = componentProps;
3279
+ const {
3280
+ disabled: fieldDisabled,
3281
+ name: fieldName,
3282
+ state: fieldState,
3283
+ validation,
3284
+ setFilled,
3285
+ setDirty,
3286
+ validityData
3287
+ } = useCompositeListItem.useFieldRootContext();
3288
+ const {
3289
+ labelId,
3290
+ getDescriptionProps
3291
+ } = useCompositeListItem.useLabelableContext();
3292
+ const {
3293
+ clearErrors,
3294
+ elementRef
3295
+ } = useCompositeListItem.useFormContext();
3296
+ const disabled = fieldDisabled || disabledProp;
3297
+ const defaultValue = defaultValueProp ?? useRenderElement.EMPTY_ARRAY;
3298
+
3299
+ // A controlled value can still be `undefined` at runtime even though `useControlled`'s
3300
+ // generic return type says otherwise. Keep the fallback to prevent group consumers from crashing.
3301
+ const [value = useRenderElement.EMPTY_ARRAY, setValueUnwrapped] = useScrollLock.useControlled({
3302
+ controlled: externalValue,
3303
+ default: defaultValue,
3304
+ name: 'CheckboxGroup',
3305
+ state: 'value'
3306
+ });
3307
+ const setValue = useScrollLock.useStableCallback((v, eventDetails) => {
3308
+ onValueChange?.(v, eventDetails);
3309
+ if (eventDetails.isCanceled) {
3310
+ return;
3311
+ }
3312
+ setValueUnwrapped(v);
3313
+ });
3314
+ const parent = useCheckboxGroupParent({
3315
+ allValues,
3316
+ value,
3317
+ onValueChange: setValue
3318
+ });
3319
+ const id = useScrollLock.useBaseUiId(idProp);
3320
+ const getInputControl = validation.getInputControl;
3321
+ const controlRef = React__namespace.useMemo(() => ({
3322
+ get current() {
3323
+ return getInputControl();
3324
+ }
3325
+ }), [getInputControl]);
3326
+ const getFormValue = useScrollLock.useStableCallback(() => {
3327
+ const formElement = elementRef.current;
3328
+ if (!formElement) {
3329
+ return value;
3330
+ }
3331
+ const successfulValues = new Set();
3332
+ for (const [input, registration] of validation.registeredInputs) {
3333
+ if (registration.value !== undefined && input.checked && useFieldValidation.isEligibleInput(input, formElement)) {
3334
+ successfulValues.add(registration.value);
3335
+ }
3336
+ }
3337
+ return value.filter(inputValue => successfulValues.has(inputValue));
3338
+ });
3339
+ useCompositeListItem.useRegisterFieldControl(controlRef, id, value, getFormValue, !!fieldName && !disabled, fieldName);
3340
+ useScrollLock.useValueChanged(value, () => {
3341
+ if (fieldName) {
3342
+ clearErrors(fieldName);
3343
+ }
3344
+ const initialValue = Array.isArray(validityData.initialValue) ? validityData.initialValue : useRenderElement.EMPTY_ARRAY;
3345
+ setFilled(value.length > 0);
3346
+ setDirty(!useCompositeListItem.areArraysEqual(value, initialValue));
3347
+ validation.change(value);
3348
+ });
3349
+ const state = {
3350
+ ...fieldState,
3351
+ disabled
3352
+ };
3353
+ const contextValue = React__namespace.useMemo(() => ({
3354
+ allValues,
3355
+ value,
3356
+ setValue,
3357
+ parent,
3358
+ disabled,
3359
+ validation
3360
+ }), [allValues, value, setValue, parent, disabled, validation]);
3361
+ const element = useRenderElement.useRenderElement('div', componentProps, {
3362
+ state,
3363
+ ref: forwardedRef,
3364
+ props: [{
3365
+ id: idProp,
3366
+ role: 'group',
3367
+ 'aria-labelledby': labelId
3368
+ }, elementProps, getDescriptionProps],
3369
+ stateAttributesMapping: useCompositeListItem.fieldValidityMapping
3370
+ });
3371
+ return /*#__PURE__*/jsxRuntime.jsx(CheckboxGroupContext.Provider, {
3372
+ value: contextValue,
3373
+ children: element
3374
+ });
3375
+ });
3376
+ if (process.env.NODE_ENV !== "production") CheckboxGroup.displayName = "CheckboxGroup";
3377
+
2694
3378
  var index_parts$2 = /*#__PURE__*/Object.freeze({
2695
3379
  __proto__: null,
2696
3380
  Arrow: ComboboxSeparator.ComboboxArrow,
@@ -3471,12 +4155,14 @@ function useRender(params) {
3471
4155
  }
3472
4156
 
3473
4157
  exports.Separator = Separator.Separator;
3474
- exports.Autocomplete = index_parts$7;
4158
+ exports.Autocomplete = index_parts$8;
4159
+ exports.Checkbox = index_parts$3;
4160
+ exports.CheckboxGroup = CheckboxGroup;
3475
4161
  exports.Combobox = index_parts$2;
3476
- exports.Dialog = index_parts$6;
3477
- exports.Drawer = index_parts$5;
3478
- exports.Menu = index_parts$4;
4162
+ exports.Dialog = index_parts$7;
4163
+ exports.Drawer = index_parts$6;
4164
+ exports.Menu = index_parts$5;
3479
4165
  exports.NumberField = index_parts$1;
3480
- exports.ScrollArea = index_parts$3;
4166
+ exports.ScrollArea = index_parts$4;
3481
4167
  exports.Select = index_parts;
3482
4168
  exports.useRender = useRender;