@himanshu-sorathiya/react-kit 1.0.33 → 1.0.34

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/index.d.ts CHANGED
@@ -2721,23 +2721,262 @@ export interface UseVirtualListReturn {
2721
2721
  * ```
2722
2722
  */
2723
2723
  export declare function useVirtualList<T = unknown>(options: UseVirtualListOptions<T>): UseVirtualListReturn;
2724
+ export type Primitive = string | number | boolean | bigint | symbol | null | undefined;
2725
+ export type PathImpl<K extends string | number, V> = V extends Primitive ? `${K}` : V extends readonly unknown[] ? `${K}` | `${K}.${number}` | (V[number] extends Primitive ? never : `${K}.${number}.${Path<V[number]>}`) : `${K}` | `${K}.${Path<V>}`;
2726
+ export type Path<T> = T extends object ? {
2727
+ [K in keyof T & (string | number)]: PathImpl<K, T[K]>;
2728
+ }[keyof T & (string | number)] : never;
2729
+ export type PathValue<T, P extends string> = P extends `${infer K}.${infer Rest}` ? K extends keyof T ? PathValue<T[K], Rest> : unknown : P extends keyof T ? T[P] : unknown;
2730
+ /** The id type `useExpansion` operates on by default - a raw `string` or `number`. */
2731
+ export type ExpansionId = string | number;
2732
+ /**
2733
+ * Options for {@link useExpansion}, in either of its two modes - id-only
2734
+ * (`T` defaults to `TId`) or object mode (`T` is a distinct item shape,
2735
+ * `TId` its resolved id type).
2736
+ *
2737
+ * @remarks
2738
+ * The conditional shape is what makes `field` required in object mode and
2739
+ * disallowed in id-only mode, entirely at the type level - see
2740
+ * {@link useExpansion}'s remarks for why.
2741
+ *
2742
+ * @typeParam T - The item shape, or `TId` itself for id-only mode.
2743
+ * @typeParam TId - The id type.
2744
+ */
2745
+ export type UseExpansionOptions<T = ExpansionId, TId extends ExpansionId = ExpansionId> = T extends TId ? {
2746
+ /** The full list of expandable items - backs `expandedItems`/`collapsedItems`, and is the default target for `expandAll` when it's called with no argument. */
2747
+ items?: readonly T[];
2748
+ /** Ids expanded from the start - frozen at mount, see {@link useExpansion}'s remarks. */
2749
+ initialExpandedIds?: readonly TId[];
2750
+ /** Whether more than one item can be expanded at once. `false` (the default) gives accordion-style behavior, where expanding one item collapses whatever was previously expanded. */
2751
+ multiple?: boolean;
2752
+ /**
2753
+ * In single (`multiple: false`) mode, whether the one expanded item
2754
+ * can be collapsed back down to nothing.
2755
+ *
2756
+ * @remarks
2757
+ * Ignored when `multiple` is `true` - "can everything be collapsed"
2758
+ * isn't a meaningful constraint once more than one item can be open
2759
+ * at a time. Mirrors Radix UI Accordion's `collapsible` prop, though
2760
+ * the default here is the opposite of Radix's: `true`, matching what
2761
+ * this hook already did before `collapsible` existed, so leaving it
2762
+ * unset doesn't change any existing behavior.
2763
+ *
2764
+ * @defaultValue `true`
2765
+ */
2766
+ collapsible?: boolean;
2767
+ } : {
2768
+ /** The full list of expandable items - backs `expandedItems`/`collapsedItems`, and is the default target for `expandAll` when it's called with no argument. */
2769
+ items: readonly T[];
2770
+ /** A dot-path into `T`, used to resolve an item's id whenever a target is given as an item rather than a raw id. */
2771
+ field: Path<T> | (string & {});
2772
+ /** Ids expanded from the start - frozen at mount, see {@link useExpansion}'s remarks. */
2773
+ initialExpandedIds?: readonly TId[];
2774
+ /** Whether more than one item can be expanded at once. `false` (the default) gives accordion-style behavior, where expanding one item collapses whatever was previously expanded. */
2775
+ multiple?: boolean;
2776
+ /**
2777
+ * In single (`multiple: false`) mode, whether the one expanded item
2778
+ * can be collapsed back down to nothing.
2779
+ *
2780
+ * @remarks
2781
+ * Ignored when `multiple` is `true` - "can everything be collapsed"
2782
+ * isn't a meaningful constraint once more than one item can be open
2783
+ * at a time. Mirrors Radix UI Accordion's `collapsible` prop, though
2784
+ * the default here is the opposite of Radix's: `true`, matching what
2785
+ * this hook already did before `collapsible` existed, so leaving it
2786
+ * unset doesn't change any existing behavior.
2787
+ *
2788
+ * @defaultValue `true`
2789
+ */
2790
+ collapsible?: boolean;
2791
+ };
2792
+ /**
2793
+ * Return value of {@link useExpansion}.
2794
+ *
2795
+ * @typeParam T - The item shape, or `TId` itself for id-only mode.
2796
+ * @typeParam TId - The id type.
2797
+ */
2798
+ export interface UseExpansionReturn<T = ExpansionId, TId extends ExpansionId = ExpansionId> {
2799
+ /** The currently expanded ids. */
2800
+ expandedIds: readonly TId[];
2801
+ /** The subset of `items` that are currently expanded. */
2802
+ expandedItems: readonly T[];
2803
+ /** The subset of `items` that are not currently expanded. */
2804
+ collapsedItems: readonly T[];
2805
+ /** `expandedIds.length`. */
2806
+ expandedCount: number;
2807
+ /** Whether anything at all is expanded. */
2808
+ hasExpanded: boolean;
2809
+ /** Whether `itemOrId` is currently expanded. */
2810
+ isExpanded: (itemOrId: TId | T) => boolean;
2811
+ /**
2812
+ * Expands `itemOrId`.
2813
+ * @remarks No-ops if it's already expanded. In single mode, also collapses whatever was previously expanded.
2814
+ */
2815
+ expand: (itemOrId: TId | T) => void;
2816
+ /**
2817
+ * Collapses `itemOrId`.
2818
+ * @remarks No-ops if it isn't currently expanded, or (in single mode) if `collapsible` is `false`.
2819
+ */
2820
+ collapse: (itemOrId: TId | T) => void;
2821
+ /**
2822
+ * Expands `itemOrId` if it isn't expanded, collapses it if it is.
2823
+ * @remarks In single mode, expanding also collapses whatever was previously expanded; collapsing is blocked when `collapsible` is `false`, same as {@link UseExpansionReturn.collapse}.
2824
+ */
2825
+ toggleExpansion: (itemOrId: TId | T) => void;
2826
+ /**
2827
+ * Expands every id/item given, or every item in `items` if called with
2828
+ * no argument. Replaces the current expanded set rather than adding to
2829
+ * it.
2830
+ * @remarks No-ops (with a dev warning) when `multiple` is `false` - expanding "all" is meaningless in a mode that only ever allows one item open.
2831
+ */
2832
+ expandAll: (itemsArray?: readonly TId[] | readonly T[]) => void;
2833
+ /** Collapses every id/item given, or collapses everything if called with no argument. Unlike {@link UseExpansionReturn.expandAll}, not gated by `multiple` - collapsing to nothing is always meaningful. */
2834
+ collapseAll: (itemsArray?: readonly TId[] | readonly T[]) => void;
2835
+ /** Restores the expanded set to the value `initialExpandedIds` had at mount - see {@link useExpansion}'s remarks. Re-truncated against the *current* `multiple` value, not whatever it was at mount. */
2836
+ resetExpansion: () => void;
2837
+ /** Replaces the entire expanded set with exactly these ids/items, truncated to just the first if `multiple` is `false` and more than one is given. */
2838
+ replaceExpansion: (newExpandedItems: readonly TId[] | readonly T[]) => void;
2839
+ }
2840
+ /**
2841
+ * Manages which item(s) in a list are expanded - accordions, expandable
2842
+ * table rows, collapsible sections.
2843
+ *
2844
+ * @remarks
2845
+ * - Uncontrolled only for now - controlled mode is planned separately and
2846
+ * will be added without breaking this signature.
2847
+ * - SSR-safe: performs no DOM/window access; `initialExpandedIds` must be
2848
+ * deterministic between server and client renders to avoid hydration
2849
+ * mismatches.
2850
+ * - All returned callbacks are manually memoized with `useCallback` so this
2851
+ * hook is safe to use even in codebases **without** the React Compiler.
2852
+ * - Two modes, picked by whether `T` is assignable to `TId`: id-only
2853
+ * (default) - `itemOrId` parameters only ever receive raw ids, `field`
2854
+ * is disallowed; or object mode - `<Row, string>` plus a required
2855
+ * `field`, letting `itemOrId` parameters take a full item too. See
2856
+ * {@link UseExpansionOptions}.
2857
+ * - The single/multiple invariant (at most one expanded id when `multiple`
2858
+ * is `false`) is enforced at every entry point that can introduce more
2859
+ * than one id at once - the initial mount, `resetExpansion`, and
2860
+ * `replaceExpansion` - not just `expand`/`toggleExpansion`, which only
2861
+ * ever add one id at a time by construction.
2862
+ * - `collapsible` (single mode only) specifically gates the *interactive*
2863
+ * collapse path - `collapse`/`toggleExpansion` closing the one expanded
2864
+ * item. It does not gate `collapseAll` or `replaceExpansion([])`: those
2865
+ * are explicit, deliberate "set state directly" calls, treated the same
2866
+ * as `clearPins`/`replacePins` in `usePin` not respecting `maxPins`
2867
+ * either - a soft interactive constraint doesn't override an explicit
2868
+ * caller instruction.
2869
+ *
2870
+ * @typeParam T - The item shape, or `TId` itself for id-only mode.
2871
+ * @typeParam TId - The id type.
2872
+ * @param options - See {@link UseExpansionOptions}.
2873
+ * @returns The current expansion state and the actions to change it. See {@link UseExpansionReturn}.
2874
+ *
2875
+ * @example
2876
+ * Accordion (single, non-collapsible - always exactly one open):
2877
+ * ```tsx
2878
+ * const { isExpanded, toggleExpansion } = useExpansion({
2879
+ * multiple: false,
2880
+ * collapsible: false,
2881
+ * initialExpandedIds: ["section-1"],
2882
+ * });
2883
+ * ```
2884
+ *
2885
+ * @example
2886
+ * Object mode, multiple expandable rows:
2887
+ * ```tsx
2888
+ * interface Row { id: string; label: string }
2889
+ * const { expandedItems, expand, expandAll } = useExpansion<Row, string>({
2890
+ * items: rows,
2891
+ * field: "id",
2892
+ * multiple: true,
2893
+ * });
2894
+ * ```
2895
+ */
2896
+ export declare function useExpansion<T = ExpansionId, TId extends ExpansionId = ExpansionId>(options?: UseExpansionOptions<T, TId>): UseExpansionReturn<T, TId>;
2897
+ /**
2898
+ * `Omit<T, K>`, applied per union member instead of to the flattened union
2899
+ * as a whole - plain `Omit` over a union loses the correlation between
2900
+ * `type`/`operator` and that arm's own `value` shape, which is exactly the
2901
+ * information {@link FilterConfigUpdate} needs to preserve.
2902
+ */
2903
+ export type DistributiveOmit<T, K extends PropertyKey> = T extends unknown ? Omit<T, K> : never;
2904
+ /** `Partial<T>`, applied per union member - same distributive reasoning as {@link DistributiveOmit}. */
2905
+ export type DistributivePartial<T> = T extends unknown ? Partial<T> : never;
2906
+ /** The comparison strategy for a filter - selects which operator set (`text`, `number`, etc.) applies, or `custom` for a caller-supplied predicate. */
2724
2907
  export type FilterType = "text" | "number" | "boolean" | "date" | "select" | "multiselect" | "custom";
2908
+ /** Operators available on a `text`-type filter. */
2725
2909
  export type TextOperator = "contains" | "equals" | "startsWith" | "endsWith" | "notContains";
2910
+ /** Operators available on a `number`-type filter. */
2726
2911
  export type NumberOperator = "equals" | "greaterThan" | "lessThan" | "greaterThanOrEqual" | "lessThanOrEqual" | "between";
2727
- export type BooleanOperator = "equals";
2912
+ /** Operators available on a `boolean`-type filter. */
2913
+ export type BooleanOperator = "equals" | "notEquals";
2914
+ /** Operators available on a `date`-type filter. */
2728
2915
  export type DateOperator = "equals" | "before" | "after" | "between";
2729
- export type SelectOperator = "equals" | "notEquals";
2916
+ /** Operators available on a `select`-type filter (single value against a scalar field). */
2917
+ export type SelectOperator = "equals" | "notEquals" | "in" | "notIn";
2918
+ /** Operators available on a `multiselect`-type filter (against an array-valued field). */
2730
2919
  export type MultiselectOperator = "in" | "notIn" | "intersects";
2920
+ /** The sole operator for a `custom`-type filter - present for structural symmetry with the other operator unions, not because there's a real choice to make. */
2731
2921
  export type CustomOperator = "custom";
2922
+ /** Options shared across filter types, though not every option is meaningful for every type. */
2732
2923
  export interface FilterOptions<T = unknown> {
2924
+ /**
2925
+ * Case sensitivity for `text` comparisons. Ignored by every other
2926
+ * filter type - `select` deliberately has no equivalent, since select
2927
+ * values are enumerated tokens (a fixed options list, an HTML
2928
+ * `<select>`), not free-typed input that could differ only by case.
2929
+ * @defaultValue `false`
2930
+ */
2733
2931
  caseSensitive?: boolean;
2932
+ /**
2933
+ * Comparison granularity for `date` filters.
2934
+ * `"day"` ignores time-of-day (calendar-date comparison); `"instant"`
2935
+ * compares exact milliseconds. Ignored by every other filter type.
2936
+ * @defaultValue `"day"`
2937
+ */
2938
+ dateGranularity?: "day" | "instant";
2939
+ /** Required for `type: "custom"` - the predicate deciding whether an item matches. Ignored by every other filter type. */
2734
2940
  compare?: (itemValue: unknown, filterValue: unknown, item: T) => boolean;
2735
2941
  }
2942
+ /** Options for {@link useFilter}. */
2943
+ export interface UseFilterOptions {
2944
+ /**
2945
+ * Defers the filtering recomputation (via `useDeferredValue`) so
2946
+ * changing `filters` doesn't block a more urgent update (e.g. the
2947
+ * keystroke that triggered the change). Only `filters` is deferred -
2948
+ * `data` is not.
2949
+ * @defaultValue `false`
2950
+ */
2951
+ defer?: boolean;
2952
+ }
2953
+ /** Fields common to every {@link FilterConfig}, regardless of `type`/`operator`. */
2736
2954
  export type BaseFilterConfig<T> = FilterOptions<T> & {
2955
+ /** Unique identifier for this filter - used for lookups (`getFilter`, `isFilterActive`, etc.) and to distinguish filters in the `filters` array. */
2737
2956
  id: string;
2738
- field: string;
2957
+ /** Dot-separated path into each item, e.g. `"address.city"`. Falls back to `id` when omitted - so `id` alone is often enough if it already matches the field name. */
2958
+ field?: string;
2959
+ /**
2960
+ * Whether this filter currently participates in filtering. An inactive
2961
+ * filter stays in the `filters` array (so its configuration - operator,
2962
+ * value, etc. - is preserved) but is skipped during evaluation.
2963
+ * @defaultValue `true`
2964
+ */
2739
2965
  isActive?: boolean;
2740
2966
  };
2967
+ /**
2968
+ * A single filter's full configuration.
2969
+ *
2970
+ * @remarks
2971
+ * Each `type` has its own dedicated arm(s) - `number`/`date`/`select` each
2972
+ * split further by operator (a `between` arm with a `{min,max}` value,
2973
+ * separate from every other operator's scalar/array value) - rather than
2974
+ * grouping multiple type literals under one shared arm. This is what lets
2975
+ * TypeScript actually narrow `value`'s type based on `type`+`operator`,
2976
+ * and what makes {@link FilterOptionsForType}-style per-type extraction
2977
+ * (and IDE autocomplete while constructing a filter) work correctly -
2978
+ * grouping literals under one arm silently breaks both.
2979
+ */
2741
2980
  export type FilterConfig<T = unknown> = BaseFilterConfig<T> & ({
2742
2981
  type: "text";
2743
2982
  operator: TextOperator;
@@ -2770,8 +3009,12 @@ export type FilterConfig<T = unknown> = BaseFilterConfig<T> & ({
2770
3009
  value: string | number | Date;
2771
3010
  } | {
2772
3011
  type: "select";
2773
- operator: SelectOperator;
3012
+ operator: Exclude<SelectOperator, "in" | "notIn">;
2774
3013
  value: string | number;
3014
+ } | {
3015
+ type: "select";
3016
+ operator: "in" | "notIn";
3017
+ value: (string | number)[];
2775
3018
  } | {
2776
3019
  type: "multiselect";
2777
3020
  operator: MultiselectOperator;
@@ -2781,75 +3024,287 @@ export type FilterConfig<T = unknown> = BaseFilterConfig<T> & ({
2781
3024
  operator: CustomOperator;
2782
3025
  value: unknown;
2783
3026
  });
3027
+ /** All configured filters. Order has no effect on filtering (every active filter is AND-combined), only on iteration order of `filters`/`groupedRecord`-style outputs elsewhere in this library. */
2784
3028
  export type FilterState<T> = FilterConfig<T>[];
3029
+ /**
3030
+ * The update payload type for `updateFilterConfig`.
3031
+ *
3032
+ * @remarks
3033
+ * Best-effort compile-time guidance, not a full guarantee: a *merge* of an
3034
+ * update into an existing filter can still land on a structurally invalid
3035
+ * `type`/`operator`/`value` combination that the type system alone can't
3036
+ * catch (partial updates flatten across a union in ways whole-object
3037
+ * construction doesn't). `isValueShapeValid`, run on the merged result
3038
+ * inside `applyFilterUpdate`, is the actual runtime backstop.
3039
+ */
3040
+ export type FilterConfigUpdate<T> = DistributivePartial<DistributiveOmit<FilterConfig<T>, "id">>;
3041
+ /** Return value of {@link useFilter}. */
2785
3042
  export interface UseFilterReturn<T> {
3043
+ /** `data`, filtered by every active entry in `filters` (AND-combined). Same reference as `data` (no copy, no filter) when there are no active filters. */
2786
3044
  filteredItems: T[];
3045
+ /** Every configured filter, active or not. */
2787
3046
  filters: FilterState<T>;
3047
+ /** Count of filters with `isActive !== false`. */
3048
+ activeFilterCount: number;
3049
+ /** `activeFilterCount > 0`. */
3050
+ hasActiveFilters: boolean;
3051
+ /** Adds a new filter, or replaces the existing one with the same `id`. */
2788
3052
  upsertFilter: (filter: FilterConfig<T>) => void;
3053
+ /** Removes one filter by `id`, or several at once by passing an array of ids. */
2789
3054
  removeFilter: (id: string | string[]) => void;
3055
+ /** Clears every filter entirely - equivalent to `replaceFilters([])`. */
2790
3056
  clearFilters: () => void;
3057
+ /** Restores `filters` to the value passed as `initialFilters` at mount. Later changes to that argument have no effect - the reset target is frozen at mount. */
2791
3058
  resetFilters: () => void;
3059
+ /** Replaces the entire `filters` array at once. */
2792
3060
  replaceFilters: (filters: FilterConfig<T>[]) => void;
3061
+ /** Flips a filter's `isActive` (defaulting to `true` if the filter doesn't exist yet, in which case it's added). Configuration (operator, value, etc.) is preserved either way. */
2793
3062
  toggleFilter: (filter: FilterConfig<T>) => void;
2794
- updateFilterConfig: (id: string, partialConfig: Partial<Omit<FilterConfig<T>, "id">>) => void;
3063
+ /**
3064
+ * Partially updates an existing filter's config by `id`.
3065
+ * @remarks An unknown `id`, or an update that would produce a value
3066
+ * incompatible with the filter's `type`/`operator`, is rejected - see
3067
+ * {@link isValueShapeValid}.
3068
+ */
3069
+ updateFilterConfig: (id: string, partialConfig: FilterConfigUpdate<T>) => void;
3070
+ /** Looks up a filter's full config by `id`. `undefined` if no such filter exists. */
3071
+ getFilter: (id: string) => FilterConfig<T> | undefined;
3072
+ /** Just a filter's `value`, by `id`. `undefined` if no such filter exists. */
2795
3073
  getFilterValue: (id: string) => unknown;
3074
+ /** Sugar for `updateFilterConfig(id, { value })` - updates only a filter's value, leaving its type/operator/other options untouched. */
2796
3075
  updateFilterValue: (id: string, value: unknown) => void;
3076
+ /** Whether a filter (by `id`) currently participates in filtering. `false` for a nonexistent `id`, same as an explicitly inactive one. */
2797
3077
  isFilterActive: (id: string) => boolean;
2798
3078
  }
2799
- export declare function useFilter<T>(data?: T[], initialFilters?: FilterState<T>): UseFilterReturn<T>;
2800
- export interface FuzzySearchOptions {
2801
- threshold?: number;
2802
- caseSensitive?: boolean;
2803
- matchStrategy?: "any" | "all";
2804
- exactPhraseBonus?: boolean;
2805
- }
2806
- export interface IndexedToken {
2807
- text: string;
2808
- weight: number;
2809
- }
2810
- export interface FlatIndexedItem<T> {
2811
- item: T;
2812
- index: number;
2813
- tokens: IndexedToken[];
2814
- combinedFlatText: string;
2815
- }
2816
- export interface ScoredItem<T> {
2817
- item: T;
2818
- score: number;
2819
- index: number;
2820
- }
2821
- export type UseFuzzySearchFields<T> = {
2822
- field: Extract<keyof T, string>;
2823
- weight: number;
2824
- }[];
2825
- export type UseFuzzySearchReturn<T> = T[];
2826
- export declare function useFuzzySearch<T>(data: T[], query: string, fields: UseFuzzySearchFields<T>, options?: FuzzySearchOptions): UseFuzzySearchReturn<T>;
3079
+ /**
3080
+ * Filters an array against one or more configured conditions - text
3081
+ * matching, numeric/date comparisons and ranges, boolean/select/multiselect
3082
+ * matching, or a fully custom predicate.
3083
+ *
3084
+ * @remarks
3085
+ * - **Every active filter is AND-combined** - an item must satisfy all of
3086
+ * them to be included. There's no built-in OR-across-filters; use a
3087
+ * `custom` filter for that if needed.
3088
+ * - **Missing/incomparable data always excludes the item**, uniformly
3089
+ * across every built-in operator - including the "negative" ones like
3090
+ * `notContains`/`notEquals`/`notIn`. A row with no value for the field is
3091
+ * treated as "unknown," never as a confident non-match. See
3092
+ * `FILTER_STRATEGIES`'s file-level `@remarks` for the full reasoning.
3093
+ * - **Validation is dev/prod-split**, same convention as this library's
3094
+ * other hooks: an unknown `type`/`operator` combination, or a `custom`
3095
+ * filter missing its `compare` function, throws immediately in
3096
+ * development, but is silently excluded (that filter matches nothing) in
3097
+ * production.
3098
+ * - **`field` falls back to `id`** when omitted - so a filter whose `id`
3099
+ * already matches the data's field name doesn't need `field` set
3100
+ * separately.
3101
+ * - **Id lookups are O(1)**, backed by a `Map` built once per `filters`
3102
+ * change, not a linear scan per call.
3103
+ *
3104
+ * @typeParam T - The type of each item in `data`.
3105
+ * @param data - The items to filter. Defaults to `[]`.
3106
+ * @param initialFilters - Filters applied at mount. Defaults to `[]` (no filtering).
3107
+ * @param options - See {@link UseFilterOptions}.
3108
+ * @returns The filtered items and the current filter state, plus the
3109
+ * actions to change it. See {@link UseFilterReturn}.
3110
+ *
3111
+ * @example
3112
+ * ```tsx
3113
+ * const { filteredItems, upsertFilter } = useFilter(products, [
3114
+ * { id: "category", type: "select", operator: "equals", value: "Books" },
3115
+ * ]);
3116
+ *
3117
+ * upsertFilter({
3118
+ * id: "price",
3119
+ * type: "number",
3120
+ * operator: "between",
3121
+ * value: { min: 10, max: 50 },
3122
+ * });
3123
+ * ```
3124
+ */
3125
+ export declare function useFilter<T>(data?: T[], initialFilters?: FilterState<T>, options?: UseFilterOptions): UseFilterReturn<T>;
3126
+ /** Bucket granularity for a `date`-type group level - see {@link GroupByDateLevel.bucket}. */
3127
+ export type DateBucketGranularity = "day" | "month" | "year";
3128
+ /**
3129
+ * Groups by a field's raw value.
3130
+ * @remarks If the resolved value is an array (e.g. a `tags` field), the
3131
+ * item fans out into a separate group per element instead of being
3132
+ * String()-coerced into one combined key - see {@link useGrouping}'s
3133
+ * `@remarks` for details.
3134
+ */
3135
+ export type GroupByFieldLevel = {
3136
+ type: "field";
3137
+ /** Dot-separated path into each item, e.g. `"address.city"`. */
3138
+ field: string;
3139
+ };
3140
+ /** Groups by a `Date`-valued field, bucketed to day/month/year rather than exact timestamp - avoids one group per unique millisecond. */
3141
+ export type GroupByDateLevel = {
3142
+ type: "date";
3143
+ /** Dot-separated path into each item. */
3144
+ field: string;
3145
+ /**
3146
+ * Bucket granularity.
3147
+ * @defaultValue `"day"`
3148
+ */
3149
+ bucket?: DateBucketGranularity;
3150
+ };
3151
+ /** Groups by a caller-supplied key function - for keys that can't be expressed as a plain field path (computed buckets, merging otherwise-distinct values into one group, etc.). */
3152
+ export type GroupByCustomLevel<T> = {
3153
+ type: "custom";
3154
+ /** Optional; used only to identify this level in dev warnings. */
3155
+ id?: string;
3156
+ /**
3157
+ * Computes this item's group key(s).
3158
+ * @remarks Return an array to fan the item out into multiple groups at
3159
+ * this level, mirroring array-valued {@link GroupByFieldLevel}s.
3160
+ * `null`/`undefined` places the item in the Unknown bucket. Throwing,
3161
+ * or returning anything other than `string | string[] | null | undefined`,
3162
+ * is treated as level misconfiguration - see {@link useGrouping}'s
3163
+ * `@remarks`.
3164
+ */
3165
+ getKey: (item: T) => string | string[] | null | undefined;
3166
+ };
3167
+ /** A single grouping level - a plain string is shorthand for `{ type: "field", field: theString }`. */
3168
+ export type GroupByLevel<T> = string | GroupByFieldLevel | GroupByDateLevel | GroupByCustomLevel<T>;
3169
+ /** Same union as {@link GroupByLevel} with the string shorthand already expanded - what {@link useGrouping} stores internally and returns via `activeGroupBy`. */
3170
+ export type NormalizedGroupByLevel<T> = GroupByFieldLevel | GroupByDateLevel | GroupByCustomLevel<T>;
3171
+ /** A single group, as produced by {@link useGrouping}'s `groupedArray`. */
2827
3172
  export interface Group<T> {
3173
+ /** Stable identifier for this group - e.g. the field's stringified value, or a date bucket like `"2026-08"`. Unique among sibling groups at the same level. */
2828
3174
  key: string;
3175
+ /** Human-readable display text. Usually equal to `key`, except for date buckets (e.g. key `"2026-08"`, label `"August 2026"`). */
3176
+ label: string;
3177
+ /** ALL items under this group, flattened across any deeper levels. */
2829
3178
  items: T[];
3179
+ /** Present only when there's another grouping level below this one. */
3180
+ subGroups?: Group<T>[];
2830
3181
  }
3182
+ /** Options for {@link useGrouping}. */
3183
+ export interface UseGroupingOptions {
3184
+ /**
3185
+ * Defers the grouping recomputation (via `useDeferredValue`) so changing
3186
+ * `activeGroupBy` doesn't block a more urgent update, e.g. the UI
3187
+ * interaction that triggered the change. Only `activeGroupBy` is
3188
+ * deferred - `items` is not.
3189
+ *
3190
+ * @defaultValue `false`
3191
+ */
3192
+ defer?: boolean;
3193
+ /**
3194
+ * Display label for the synthetic bucket holding items whose value at a
3195
+ * given level is missing, blank, or otherwise unresolvable.
3196
+ *
3197
+ * @defaultValue `"Unknown"`
3198
+ */
3199
+ unknownGroupLabel?: string;
3200
+ /**
3201
+ * Display label for the single synthetic group returned when
3202
+ * `activeGroupBy` is empty (no grouping applied).
3203
+ *
3204
+ * @defaultValue `"Ungrouped"`
3205
+ */
3206
+ ungroupedGroupLabel?: string;
3207
+ }
3208
+ /** Return value of {@link useGrouping}. */
2831
3209
  export interface UseGroupingReturn<T> {
2832
- groupedRecord: Record<string, T[]>;
3210
+ /** All groups, hierarchically - top-level groups from `activeGroupBy[0]`, each optionally holding `subGroups` from deeper levels. Render this for anything beyond a single flat level. */
2833
3211
  groupedArray: Group<T>[];
3212
+ /** Top-level groups only, flattened -- Record<key, items>. For deeper
3213
+ * levels, traverse a group's `subGroups` directly. */
3214
+ groupedRecord: Record<string, T[]>;
3215
+ /** Top-level group keys only, in the same order as `groupedArray`. */
2834
3216
  groupKeys: string[];
3217
+ /** Count of top-level groups only - `groupKeys.length`. */
2835
3218
  totalGroups: number;
2836
- activeGroupBy: string | undefined;
2837
- changeGroupBy: (newField: string) => void;
3219
+ /** The currently-applied grouping levels, normalized (string shorthand already expanded). Empty when no grouping is applied. */
3220
+ activeGroupBy: NormalizedGroupByLevel<T>[];
3221
+ /** `activeGroupBy.length > 0`. */
3222
+ hasActiveGrouping: boolean;
3223
+ /** The actual label in use for the "missing/unresolvable value" bucket - reflects `options.unknownGroupLabel` if customized, otherwise the default. Compare a group's `key` against this rather than hardcoding `"Unknown"`. */
3224
+ unknownGroupKey: string;
3225
+ /** The actual label in use for the "no grouping applied" bucket - reflects `options.ungroupedGroupLabel` if customized. Compare a group's `key` against this rather than hardcoding `"Ungrouped"`. */
3226
+ ungroupedGroupKey: string;
3227
+ /**
3228
+ * Replaces `activeGroupBy` with one level or an array of levels (for
3229
+ * hierarchical grouping - order determines nesting, `newGroupBy[0]`
3230
+ * becomes the top level).
3231
+ * @remarks A malformed level (missing `field`, non-function `getKey`,
3232
+ * etc.) is rejected - see {@link useGrouping}'s `@remarks`.
3233
+ */
3234
+ changeGroupBy: (newGroupBy: GroupByLevel<T> | GroupByLevel<T>[]) => void;
3235
+ /** Clears `activeGroupBy` entirely - equivalent to `changeGroupBy([])`. */
2838
3236
  clearGrouping: () => void;
3237
+ /** Restores `activeGroupBy` to the value passed as `initialGroupBy` at mount. Later changes to that argument have no effect - the reset target is frozen at mount. */
2839
3238
  resetGrouping: () => void;
3239
+ /** Looks up a single top-level group by its `key`. `undefined` if no such group exists.
3240
+ * @remarks Top-level lookup only, matching `groupedRecord`'s scope - use `groupedArray`/`subGroups` directly for a nested group. */
3241
+ getGroup: (groupKey: string) => Group<T> | undefined;
3242
+ /** `getGroup(groupKey)?.items ?? []` - the items in a top-level group, or an empty array if it doesn't exist. */
2840
3243
  getGroupItems: (groupKey: string) => T[];
2841
3244
  }
2842
- export declare function useGrouping<T = unknown>(options?: {
2843
- items?: T[];
2844
- initialGroupBy?: string;
2845
- }): UseGroupingReturn<T>;
3245
+ /**
3246
+ * Partitions an array into groups by one or more field values, date
3247
+ * buckets, or a custom key function - single-level or hierarchical
3248
+ * (subgroups within groups).
3249
+ *
3250
+ * @remarks
3251
+ * - **Fan-out, not partitioning.** If a level's resolved value for an item
3252
+ * is an array (e.g. a `tags` field, or a `custom` level's `getKey`
3253
+ * returning multiple keys), the item is placed in *every* matching group
3254
+ * at that level, not just one. Summed item counts across sibling groups
3255
+ * can therefore exceed the original array length - this reflects genuine
3256
+ * multi-group membership, not a bug.
3257
+ * - **Two distinct synthetic buckets**, both customizable via
3258
+ * {@link UseGroupingOptions}: `unknownGroupLabel` (default `"Unknown"`)
3259
+ * for items whose value at a level is missing, blank, or otherwise
3260
+ * unresolvable; `ungroupedGroupLabel` (default `"Ungrouped"`) for the
3261
+ * single group returned when no grouping is applied at all. The actual
3262
+ * labels in use are returned as `unknownGroupKey`/`ungroupedGroupKey` -
3263
+ * compare against those rather than hardcoding the default strings, in
3264
+ * case they've been customized.
3265
+ * - **Validation is dev/prod-split**, same convention as this library's
3266
+ * other hooks: a malformed level (missing/empty `field`, a non-function
3267
+ * `getKey`, an invalid `bucket`) throws immediately in development, but
3268
+ * is rejected silently (falling back to the previous/empty grouping) in
3269
+ * production. A `custom` level's `getKey` throwing, or returning
3270
+ * something other than `string | string[] | null | undefined`, follows
3271
+ * the same split - see {@link GroupByCustomLevel.getKey}.
3272
+ * - **`groupedRecord`/`groupKeys`/`totalGroups`/`getGroup`/`getGroupItems`
3273
+ * are all top-level only** - for anything below the first grouping
3274
+ * level, traverse a `Group`'s `subGroups` directly via `groupedArray`.
3275
+ * - Order of top-level (and each nested level's) groups follows first
3276
+ * appearance in `items`, not any particular sort - re-sort `groupedArray`
3277
+ * yourself if a specific order is needed.
3278
+ *
3279
+ * @typeParam T - The type of each item in `data`.
3280
+ * @param data - The items to group. Defaults to `[]`.
3281
+ * @param initialGroupBy - Grouping level(s) applied at mount. Omit for no
3282
+ * initial grouping.
3283
+ * @param options - See {@link UseGroupingOptions}.
3284
+ * @returns The current grouping and the actions to change it. See
3285
+ * {@link UseGroupingReturn}.
3286
+ *
3287
+ * @example
3288
+ * Single-level, by a plain field:
3289
+ * ```tsx
3290
+ * const { groupedArray } = useGrouping(users, "department");
3291
+ * // groupedArray: [{ key: "Engineering", items: [...] }, { key: "Sales", items: [...] }, ...]
3292
+ * ```
3293
+ *
3294
+ * @example
3295
+ * Hierarchical, by department then a date bucket:
3296
+ * ```tsx
3297
+ * const { groupedArray, changeGroupBy } = useGrouping(orders);
3298
+ *
3299
+ * changeGroupBy([
3300
+ * "region",
3301
+ * { type: "date", field: "placedAt", bucket: "month" },
3302
+ * ]);
3303
+ * // groupedArray: [{ key: "EMEA", items: [...], subGroups: [{ key: "2026-08", ... }] }, ...]
3304
+ * ```
3305
+ */
3306
+ export declare function useGrouping<T = unknown>(data?: T[], initialGroupBy?: GroupByLevel<T> | GroupByLevel<T>[], options?: UseGroupingOptions): UseGroupingReturn<T>;
2846
3307
  export type SelectionId = string | number;
2847
- export type Primitive = string | number | boolean | bigint | symbol | null | undefined;
2848
- export type PathImpl<K extends string | number, V> = V extends Primitive ? `${K}` : V extends readonly unknown[] ? `${K}` | `${K}.${number}` | (V[number] extends Primitive ? never : `${K}.${number}.${Path<V[number]>}`) : `${K}` | `${K}.${Path<V>}`;
2849
- export type Path<T> = T extends object ? {
2850
- [K in keyof T & (string | number)]: PathImpl<K, T[K]>;
2851
- }[keyof T & (string | number)] : never;
2852
- export type PathValue<T, P extends string> = P extends `${infer K}.${infer Rest}` ? K extends keyof T ? PathValue<T[K], Rest> : unknown : P extends keyof T ? T[P] : unknown;
2853
3308
  /**
2854
3309
  * All dot-notation paths of `T` whose resolved value is assignable to `TId`.
2855
3310
  *
@@ -3076,38 +3531,395 @@ export interface UseMultipleSelectionReturn<T = SelectionId, TId extends Selecti
3076
3531
  * ```
3077
3532
  */
3078
3533
  export declare function useMultipleSelection<T = SelectionId, TId extends SelectionId = SelectionId>(options?: UseMultipleSelectionOptions<T, TId>): UseMultipleSelectionReturn<T, TId>;
3079
- export interface UseOrderReturn<T> {
3080
- orderedItems: T[];
3081
- moveUp: (index: number) => void;
3082
- moveDown: (index: number) => void;
3083
- canMoveUp: (index: number) => boolean;
3084
- canMoveDown: (index: number) => boolean;
3085
- moveToTop: (index: number) => void;
3086
- moveToBottom: (index: number) => void;
3087
- move: (fromIndex: number, toIndex: number) => void;
3088
- swap: (indexA: number, indexB: number) => void;
3534
+ /**
3535
+ * The shape of every `target` parameter across {@link UseOrderReturn}'s
3536
+ * methods - a plain array index when no `field` is configured, or either an
3537
+ * index *or* a full item once one is (letting callers resolve an item's
3538
+ * current position for themselves instead of tracking indices by hand).
3539
+ *
3540
+ * @remarks
3541
+ * Deliberately gated behind `WithField` rather than always allowing `T`:
3542
+ * `useOrder`'s base (no-`field`) overload can be called with `T = number`
3543
+ * (ordering a plain array of numbers), where allowing an item argument too
3544
+ * would make a bare `number` genuinely ambiguous - "index `5`" or "the item
3545
+ * `5`"? Restricting item-based targeting to the `field`-configured overload
3546
+ * (see {@link UseOrderFieldOptions}) sidesteps that ambiguity entirely,
3547
+ * since `field` only makes sense for object items in the first place.
3548
+ *
3549
+ * @typeParam T - The item shape.
3550
+ * @typeParam WithField - Whether `field` was configured - see {@link UseOrderFieldOptions}.
3551
+ */
3552
+ export type OrderTarget<T, WithField extends boolean> = WithField extends true ? number | T : number;
3553
+ /**
3554
+ * Options shared by both {@link useOrder} overloads, with or without `field`
3555
+ * configured.
3556
+ *
3557
+ * @typeParam T - The item shape.
3558
+ */
3559
+ export interface UseOrderBaseOptions<T> {
3560
+ /**
3561
+ * Marks an item as un-movable and un-displaceable - see
3562
+ * {@link UseOrderReturn} for exactly which operations this blocks, and
3563
+ * how.
3564
+ *
3565
+ * @param item - The item to check.
3566
+ * @param index - That item's current index.
3567
+ * @returns `true` if `item` should be locked in place.
3568
+ */
3569
+ isDisabled?: (item: T, index: number) => boolean;
3570
+ }
3571
+ /**
3572
+ * Options for the `field`-configured overload of {@link useOrder}, which
3573
+ * additionally accepts full items (not just indices) as move targets.
3574
+ *
3575
+ * @typeParam T - The item shape.
3576
+ */
3577
+ export interface UseOrderFieldOptions<T> extends UseOrderBaseOptions<T> {
3578
+ /**
3579
+ * A dot-path into `T`, used to resolve an item's id whenever a target is
3580
+ * given as an item rather than a raw index - see {@link OrderTarget}.
3581
+ */
3582
+ field: Path<T> | (string & {});
3583
+ }
3584
+ /**
3585
+ * Return value of {@link useOrder}.
3586
+ *
3587
+ * @typeParam T - The item shape.
3588
+ * @typeParam WithField - Whether `field` was configured - see {@link OrderTarget}.
3589
+ */
3590
+ export interface UseOrderReturn<T, WithField extends boolean = false> {
3591
+ /** The items in their current order. */
3592
+ orderedItems: readonly T[];
3593
+ /**
3594
+ * Moves the item at `target` one position earlier (toward index `0`).
3595
+ * @remarks No-ops at the start of the list, or if `target`'s item or the item before it is disabled.
3596
+ */
3597
+ movePrevious: (target: OrderTarget<T, WithField>) => void;
3598
+ /**
3599
+ * Moves the item at `target` one position later (toward the end).
3600
+ * @remarks No-ops at the end of the list, or if `target`'s item or the item after it is disabled.
3601
+ */
3602
+ moveNext: (target: OrderTarget<T, WithField>) => void;
3603
+ /** Whether {@link UseOrderReturn.movePrevious} would currently have an effect for `target`. */
3604
+ canMovePrevious: (target: OrderTarget<T, WithField>) => boolean;
3605
+ /** Whether {@link UseOrderReturn.moveNext} would currently have an effect for `target`. */
3606
+ canMoveNext: (target: OrderTarget<T, WithField>) => boolean;
3607
+ /**
3608
+ * Moves the item at `target` to the very start of the list.
3609
+ * @remarks No-ops if it's already first, or if it's disabled. Items it moves past are shifted by one slot but not themselves checked - see {@link useOrder}'s remarks.
3610
+ */
3611
+ moveToStart: (target: OrderTarget<T, WithField>) => void;
3612
+ /**
3613
+ * Moves the item at `target` to the very end of the list.
3614
+ * @remarks No-ops if it's already last, or if it's disabled. Items it moves past are shifted by one slot but not themselves checked - see {@link useOrder}'s remarks.
3615
+ */
3616
+ moveToEnd: (target: OrderTarget<T, WithField>) => void;
3617
+ /**
3618
+ * Moves the item at `from` to the position at `to`, shifting everything
3619
+ * in between by one slot.
3620
+ * @remarks No-ops if `from` and `to` resolve to the same index, or if the item at `from` is disabled. Items shifted in between are not themselves checked - see {@link useOrder}'s remarks.
3621
+ */
3622
+ move: (from: OrderTarget<T, WithField>, to: OrderTarget<T, WithField>) => void;
3623
+ /**
3624
+ * Exchanges the positions of the items at `a` and `b`.
3625
+ * @remarks No-ops if `a` and `b` resolve to the same index, or if either item is disabled.
3626
+ */
3627
+ swap: (a: OrderTarget<T, WithField>, b: OrderTarget<T, WithField>) => void;
3628
+ /** Restores the order to the value `initialItems` had at mount - see {@link useOrder}'s remarks. */
3089
3629
  resetOrder: () => void;
3090
- replaceOrder: (newOrderedItems: T[]) => void;
3630
+ /** Replaces the entire order with exactly these items. */
3631
+ replaceOrder: (newOrderedItems: readonly T[]) => void;
3091
3632
  }
3092
- export declare function useOrder<T>(initialItems?: T[]): UseOrderReturn<T>;
3633
+ /**
3634
+ * Manages the order of a list of items - drag-to-reorder UIs, "move
3635
+ * up"/"move down" controls, manual sort overrides layered on top of a base
3636
+ * sort.
3637
+ *
3638
+ * @remarks
3639
+ * - Uncontrolled only for now - controlled mode is planned separately and
3640
+ * will be added without breaking this signature.
3641
+ * - SSR-safe: performs no DOM/window access; `initialItems` must be
3642
+ * deterministic between server and client renders to avoid hydration
3643
+ * mismatches.
3644
+ * - All returned callbacks are manually memoized with `useCallback` so this
3645
+ * hook is safe to use even in codebases **without** the React Compiler.
3646
+ * - Two overloads, picked by whether `field` is configured: without it,
3647
+ * every method's `target` parameter is a plain `number` index; with it,
3648
+ * `target` also accepts a full item, resolved to its current index via
3649
+ * `field`. See {@link OrderTarget} for why this is gated behind `field`
3650
+ * rather than always allowed.
3651
+ * - `isDisabled` blocks differently depending on the operation. For the
3652
+ * pairwise operations (`movePrevious`, `moveNext`, `swap`), *both* items
3653
+ * involved must be movable, since both change position. For the
3654
+ * repositioning operations (`moveToStart`, `moveToEnd`, `move`), only the
3655
+ * item being moved is checked - items it displaces shift by one slot but
3656
+ * are never asked to swap places, so their own `isDisabled` state isn't
3657
+ * consulted for the move to proceed.
3658
+ * - `resetOrder` restores the value `initialItems` had at mount, not
3659
+ * whatever it is on the current render - a later change to the
3660
+ * `initialItems` prop doesn't retroactively change what `resetOrder`
3661
+ * restores to.
3662
+ *
3663
+ * @typeParam T - The item shape.
3664
+ * @param initialItems - The items to manage, in their starting order.
3665
+ * @param options - See {@link UseOrderBaseOptions} / {@link UseOrderFieldOptions}.
3666
+ * @returns The current order and the actions to change it. See {@link UseOrderReturn}.
3667
+ *
3668
+ * @example
3669
+ * Index-only:
3670
+ * ```tsx
3671
+ * const { orderedItems, movePrevious, moveNext } = useOrder(["a", "b", "c"]);
3672
+ * ```
3673
+ *
3674
+ * @example
3675
+ * With `field`, so items themselves can be passed as move targets:
3676
+ * ```tsx
3677
+ * interface Row { id: string; label: string }
3678
+ * const { orderedItems, movePrevious } = useOrder<Row>(rows, { field: "id" });
3679
+ * // movePrevious(rows[2]) works directly - no manual index lookup needed
3680
+ * ```
3681
+ */
3682
+ export declare function useOrder<T extends Record<string, unknown>>(initialItems: readonly T[] | undefined, options: UseOrderFieldOptions<T>): UseOrderReturn<T, true>;
3683
+ export declare function useOrder<T>(initialItems?: readonly T[], options?: UseOrderBaseOptions<T>): UseOrderReturn<T, false>;
3684
+ /** Return value of {@link usePagination}. */
3093
3685
  export interface UsePaginationReturn<T> {
3686
+ /** The items for the current page - a slice of `data`, `pageSize` items long (fewer on the last page if `totalCount` doesn't divide evenly). */
3094
3687
  pageItems: T[];
3688
+ /**
3689
+ * Current page, 1-based.
3690
+ * @remarks Always clamped into `[1, totalPages]` for display, even if
3691
+ * the underlying position becomes momentarily out of range (e.g. `data`
3692
+ * shrinks). The clamp is display-only - see the `currentPageIndex` note
3693
+ * in the implementation for why the real position isn't lost.
3694
+ */
3695
+ pageNumber: number;
3696
+ /** Items per page. */
3095
3697
  pageSize: number;
3096
- pageIndex: number;
3698
+ /** `Math.max(1, Math.ceil(totalCount / pageSize))` - always at least `1`, even for an empty `data`. */
3097
3699
  totalPages: number;
3098
- canPrevious: boolean;
3099
- canNext: boolean;
3700
+ /** `data.length` (after the array-safety check) - the un-paginated item count. */
3701
+ totalCount: number;
3702
+ /** 1-based index of the first item on the current page, for a "Showing X-Y of Z" display. `0` when `totalCount` is `0`. */
3703
+ startIndex: number;
3704
+ /** 1-based index of the last item on the current page. `0` when `totalCount` is `0`. */
3705
+ endIndex: number;
3706
+ /** Whether {@link UsePaginationReturn.previousPage} would move anywhere. */
3707
+ canPreviousPage: boolean;
3708
+ /** Whether {@link UsePaginationReturn.nextPage} would move anywhere. */
3709
+ canNextPage: boolean;
3710
+ /** Moves to the next page, if any. No-op (not a warning) on the last page - this is routine UI usage, not caller error. */
3100
3711
  nextPage: () => void;
3712
+ /** Moves to the previous page, if any. No-op on the first page. */
3101
3713
  previousPage: () => void;
3714
+ /** Jumps to page `1`. */
3102
3715
  goToFirstPage: () => void;
3716
+ /** Jumps to the last page. */
3103
3717
  goToLastPage: () => void;
3104
- goToPage: (newPageIndex: number) => void;
3718
+ /**
3719
+ * Jumps to a specific page.
3720
+ * @remarks A well-formed but out-of-range page (e.g. `999` on a
3721
+ * 5-page list) is clamped to the nearest valid page, not rejected -
3722
+ * only a malformed value (non-integer, `< 1`, `NaN`, etc.) is treated
3723
+ * as caller error. See {@link isPositiveInteger}.
3724
+ */
3725
+ goToPage: (newPageNumber: number) => void;
3726
+ /**
3727
+ * Changes `pageSize`.
3728
+ * @remarks `pageNumber` is left untouched here - it re-bounds itself
3729
+ * automatically against the new `totalPages` on the next render.
3730
+ */
3105
3731
  changePageSize: (newPageSize: number) => void;
3106
- resetPageIndex: () => void;
3732
+ /** Restores `pageNumber` to the value passed as `initialPageNumber` at mount. Later changes to that argument have no effect - the reset target is frozen at mount, same as `pageSize`/full pagination reset below. */
3733
+ resetPageNumber: () => void;
3734
+ /** Restores `pageSize` to the value passed as `initialPageSize` at mount. */
3107
3735
  resetPageSize: () => void;
3736
+ /** Restores both `pageNumber` and `pageSize` to their mount-time initial values, in one update. */
3108
3737
  resetPagination: () => void;
3109
3738
  }
3110
- export declare function usePagination<T>(data: T[] | undefined, initialPageSize: number, initialPageIndex?: number): UsePaginationReturn<T>;
3739
+ /**
3740
+ * Paginates an array client-side - slices `data` into pages and exposes the
3741
+ * navigation state and actions to move between them.
3742
+ *
3743
+ * @remarks
3744
+ * - **Validation is dev/prod-split**: passing a malformed page number or
3745
+ * page size (non-integer, `< 1`, `NaN`, etc.) to the constructor or any
3746
+ * mutator throws immediately in development (surfacing the bug fast), but
3747
+ * silently falls back to the last valid value in production, rather than
3748
+ * crashing a real user's session over a caller mistake. See
3749
+ * {@link isPositiveInteger}.
3750
+ * - **Out-of-range is different from malformed.** `goToPage(999)` on a
3751
+ * 5-page list isn't an error - it's clamped to the last page. Only
3752
+ * structurally invalid input (see above) is treated as a mistake.
3753
+ * - **`pageNumber` is 1-based** in this public API; page count/index math
3754
+ * is kept 0-based internally.
3755
+ * - **Reset targets are frozen at mount.** `resetPageNumber`/`resetPageSize`/
3756
+ * `resetPagination` always restore the `initialPageNumber`/`initialPageSize`
3757
+ * values as they were on the very first render - passing different values
3758
+ * to `usePagination` on a later render does not change what reset
3759
+ * restores to.
3760
+ * - **No manual/server-side pagination mode** - `data` is always assumed to
3761
+ * be the complete, un-paginated dataset, sliced client-side.
3762
+ *
3763
+ * @typeParam T - The type of each item in `data`.
3764
+ * @param data - The full, un-paginated array. Defaults to `[]`.
3765
+ * @param initialPageSize - Items per page at mount.
3766
+ * @defaultValue initialPageSize `10`
3767
+ * @param initialPageNumber - Starting page (1-based) at mount.
3768
+ * @defaultValue initialPageNumber `1`
3769
+ * @returns The current page's items, position, and the actions to
3770
+ * navigate/resize/reset. See {@link UsePaginationReturn}.
3771
+ *
3772
+ * @example
3773
+ * ```tsx
3774
+ * const { pageItems, pageNumber, totalPages, nextPage, previousPage } =
3775
+ * usePagination(rows, 20);
3776
+ *
3777
+ * return (
3778
+ * <>
3779
+ * {pageItems.map((row) => <Row key={row.id} {...row} />)}
3780
+ * <button onClick={previousPage}>Prev</button>
3781
+ * <span>{pageNumber} / {totalPages}</span>
3782
+ * <button onClick={nextPage}>Next</button>
3783
+ * </>
3784
+ * );
3785
+ * ```
3786
+ */
3787
+ export declare function usePagination<T>(data?: T[], initialPageSize?: number, initialPageNumber?: number): UsePaginationReturn<T>;
3788
+ /** The id type `usePin` operates on by default - a raw `string` or `number`. */
3789
+ export type PinId = string | number;
3790
+ /**
3791
+ * Options for {@link usePin}, in either of its two modes - id-only (`T`
3792
+ * defaults to `TId`) or object mode (`T` is a distinct item shape, `TId`
3793
+ * its resolved id type).
3794
+ *
3795
+ * @remarks
3796
+ * The conditional shape is what makes `field` required in object mode and
3797
+ * disallowed in id-only mode, entirely at the type level - see
3798
+ * {@link usePin}'s remarks for why.
3799
+ *
3800
+ * @typeParam T - The item shape, or `TId` itself for id-only mode.
3801
+ * @typeParam TId - The id type.
3802
+ */
3803
+ export type UsePinOptions<T = PinId, TId extends PinId = PinId> = T extends TId ? {
3804
+ /** The full list of pinnable items - backs `pinnedItems`/`unpinnedItems`, and is the default target for `pinAll` when it's called with no argument. */
3805
+ items?: readonly T[];
3806
+ /** Ids pinned from the start - frozen at mount, see {@link usePin}'s remarks. */
3807
+ initialPinnedIds?: readonly TId[];
3808
+ /** The maximum number of ids that can be pinned at once. `undefined` means unlimited. */
3809
+ maxPins?: number;
3810
+ } : {
3811
+ /** The full list of pinnable items - backs `pinnedItems`/`unpinnedItems`, and is the default target for `pinAll` when it's called with no argument. */
3812
+ items: readonly T[];
3813
+ /** A dot-path into `T`, used to resolve an item's id whenever a target is given as an item rather than a raw id. */
3814
+ field: Path<T> | (string & {});
3815
+ /** Ids pinned from the start - frozen at mount, see {@link usePin}'s remarks. */
3816
+ initialPinnedIds?: readonly TId[];
3817
+ /** The maximum number of ids that can be pinned at once. `undefined` means unlimited. */
3818
+ maxPins?: number;
3819
+ };
3820
+ /**
3821
+ * Return value of {@link usePin}.
3822
+ *
3823
+ * @typeParam T - The item shape, or `TId` itself for id-only mode.
3824
+ * @typeParam TId - The id type.
3825
+ */
3826
+ export interface UsePinReturn<T = PinId, TId extends PinId = PinId> {
3827
+ /** The currently pinned ids. */
3828
+ pinnedIds: readonly TId[];
3829
+ /** The subset of `items` that are currently pinned. */
3830
+ pinnedItems: readonly T[];
3831
+ /** The subset of `items` that are not currently pinned. */
3832
+ unpinnedItems: readonly T[];
3833
+ /** `pinnedIds.length`. */
3834
+ pinnedCount: number;
3835
+ /** Whether anything at all is pinned. */
3836
+ hasPins: boolean;
3837
+ /** Whether `pinnedCount` has reached `maxPins`. */
3838
+ isAtMaxLimit: boolean;
3839
+ /** The current pin limit - `Number.MAX_SAFE_INTEGER` when unset/unlimited. */
3840
+ maxPins: number;
3841
+ /**
3842
+ * Pins `itemOrId`.
3843
+ * @remarks No-ops if it's already pinned, or if `maxPins` has been reached.
3844
+ */
3845
+ pin: (itemOrId: TId | T) => void;
3846
+ /** Unpins `itemOrId`. Always allowed, even past `maxPins` - removal never needs to check the limit. */
3847
+ unpin: (itemOrId: TId | T) => void;
3848
+ /**
3849
+ * Pins `itemOrId` if it isn't pinned, unpins it if it is.
3850
+ * @remarks The pin-direction is blocked once `maxPins` is reached; the unpin-direction never is.
3851
+ */
3852
+ togglePin: (itemOrId: TId | T) => void;
3853
+ /** Whether `itemOrId` is currently pinned. */
3854
+ isPinned: (itemOrId: TId | T) => boolean;
3855
+ /** Whether {@link UsePinReturn.pin} would currently have an effect for `itemOrId` - `true` if it's already pinned (a no-op call still "succeeds") or there's room under `maxPins`. */
3856
+ canPin: (itemOrId: TId | T) => boolean;
3857
+ /** Unpins everything. */
3858
+ clearPins: () => void;
3859
+ /** Restores the pinned set to the value `initialPinnedIds` had at mount - see {@link usePin}'s remarks. */
3860
+ resetPins: () => void;
3861
+ /** Replaces the entire pinned set with exactly these ids/items, truncated to the first `maxPins` if it exceeds the current limit. */
3862
+ replacePins: (newPinnedItems: readonly TId[] | readonly T[]) => void;
3863
+ /**
3864
+ * Pins every id/item given, or every item in `items` if called with no
3865
+ * argument.
3866
+ * @remarks If `maxPins` is reached partway through, whatever already fit stays pinned rather than the whole call being rejected.
3867
+ */
3868
+ pinAll: (itemsArray?: readonly TId[] | readonly T[]) => void;
3869
+ /** Unpins every id/item given, or unpins everything if called with no argument. */
3870
+ unpinAll: (itemsArray?: readonly TId[] | readonly T[]) => void;
3871
+ /** Changes `maxPins` to a new value. Does not retroactively unpin anything already over the new limit. */
3872
+ changeMaxPins: (newMaxPins: number) => void;
3873
+ /** Restores `maxPins` to the value it had at mount. */
3874
+ resetMaxPins: () => void;
3875
+ }
3876
+ /**
3877
+ * Manages a pinned/starred subset of a list - pinned rows in a table,
3878
+ * favorited items, "keep at top" behavior.
3879
+ *
3880
+ * @remarks
3881
+ * - Uncontrolled only for now - controlled mode is planned separately and
3882
+ * will be added without breaking this signature.
3883
+ * - SSR-safe: performs no DOM/window access; `initialPinnedIds` must be
3884
+ * deterministic between server and client renders to avoid hydration
3885
+ * mismatches.
3886
+ * - All returned callbacks are manually memoized with `useCallback` so this
3887
+ * hook is safe to use even in codebases **without** the React Compiler.
3888
+ * - Two modes, picked by whether `T` is assignable to `TId`: id-only
3889
+ * (default) - `itemOrId` parameters only ever receive raw ids, `field`
3890
+ * is disallowed; or object mode - `<Row, string>` plus a required
3891
+ * `field`, letting `itemOrId` parameters take a full item too. See
3892
+ * {@link UsePinOptions}.
3893
+ * - `maxPins` truncation always keeps the *first* ids/items and warns in
3894
+ * dev about the rest - this applies at mount (`initialPinnedIds`),
3895
+ * `replacePins`, and `pinAll`.
3896
+ * - `resetPins` and `resetMaxPins` are independent - resetting one doesn't
3897
+ * touch the other, and each restores exactly the value its own option
3898
+ * had at mount, not whatever it is on the current render.
3899
+ *
3900
+ * @typeParam T - The item shape, or `TId` itself for id-only mode.
3901
+ * @typeParam TId - The id type.
3902
+ * @param options - See {@link UsePinOptions}.
3903
+ * @returns The current pinned state and the actions to change it. See {@link UsePinReturn}.
3904
+ *
3905
+ * @example
3906
+ * Id-only:
3907
+ * ```tsx
3908
+ * const { pinnedIds, pin, isPinned } = usePin({ initialPinnedIds: ["row-1"] });
3909
+ * ```
3910
+ *
3911
+ * @example
3912
+ * Object mode, with a pin limit:
3913
+ * ```tsx
3914
+ * interface Row { id: string; label: string }
3915
+ * const { pinnedItems, pin, canPin } = usePin<Row, string>({
3916
+ * items: rows,
3917
+ * field: "id",
3918
+ * maxPins: 5,
3919
+ * });
3920
+ * ```
3921
+ */
3922
+ export declare function usePin<T = PinId, TId extends PinId = PinId>(options?: UsePinOptions<T, TId>): UsePinReturn<T, TId>;
3111
3923
  /**
3112
3924
  * Configuration options for {@link useSingleSelection}.
3113
3925
  *
@@ -3214,47 +4026,230 @@ export interface UseSingleSelectionReturn<TId extends SelectionId = SelectionId>
3214
4026
  * ```
3215
4027
  */
3216
4028
  export declare function useSingleSelection<TId extends SelectionId = SelectionId>(options?: UseSingleSelectionOptions<TId>): UseSingleSelectionReturn<TId>;
4029
+ /** The comparison strategy for a sort key - selects which built-in comparator is used, or `custom` for a caller-supplied one. */
3217
4030
  export type SortType = "numeric" | "alphabetical" | "alphanumeric" | "boolean" | "date" | "basic" | "custom";
4031
+ /**
4032
+ * How a missing value (per each sort type's own definition of "missing" -
4033
+ * e.g. non-numeric for `numeric`, unparseable for `date`) is positioned
4034
+ * relative to present values.
4035
+ *
4036
+ * @remarks
4037
+ * - `"first"`/`"last"` are **absolute positions** - they never flip with
4038
+ * sort direction, which is the entire reason to choose them over `-1`/`1`.
4039
+ * - `-1`/`1` **simulate a real extreme value** (very small / very large
4040
+ * respectively) - unlike `"first"`/`"last"`, these DO flip with
4041
+ * direction, exactly as a genuine value at that extreme would.
4042
+ *
4043
+ * @defaultValue `"last"`
4044
+ */
4045
+ export type SortUndefinedOption = "first" | "last" | -1 | 1;
4046
+ /** Options shared by every sort type, regardless of comparator. */
3218
4047
  export interface BaseSortOptions {
4048
+ /**
4049
+ * Sort direction.
4050
+ * @defaultValue `false` (ascending)
4051
+ */
3219
4052
  desc?: boolean;
4053
+ /**
4054
+ * When set, `toggleSort` cycles asc -> desc -> asc, skipping the
4055
+ * "remove this sort key" step it would otherwise land on after desc.
4056
+ * @defaultValue `false`
4057
+ */
3220
4058
  disableSortRemoval?: boolean;
4059
+ /**
4060
+ * Flips the effective sort direction without changing `desc` itself -
4061
+ * useful for a column whose "natural" order is descending (e.g. a
4062
+ * priority field where higher should sort first by default).
4063
+ * @defaultValue `false`
4064
+ */
3221
4065
  invertSorting?: boolean;
3222
- sortUndefined?: "first" | "last" | -1 | 1;
4066
+ /** See {@link SortUndefinedOption}. */
4067
+ sortUndefined?: SortUndefinedOption;
3223
4068
  }
4069
+ /** Fields common to every {@link SortConfig}, regardless of `type`. */
3224
4070
  export interface BaseSortConfig extends BaseSortOptions {
4071
+ /** Unique identifier for this sort key - used for lookups (`getSort`, `getSortDirection`, etc.) and to distinguish sort keys in a multi-key `sorts` array. */
3225
4072
  id: string;
4073
+ /** Dot-separated path into each item, e.g. `"address.city"`. Falls back to `id` when omitted - so `id` alone is often enough if it already matches the field name. */
3226
4074
  field?: string;
3227
4075
  }
4076
+ /**
4077
+ * A single sort key's full configuration.
4078
+ *
4079
+ * @remarks
4080
+ * Each `type` has its own dedicated union arm (rather than several type
4081
+ * literals sharing one arm) so that {@link SortOptionsForType} - which
4082
+ * extracts a single type's own options via `Extract<SortConfig, { type: T }>`
4083
+ * - resolves correctly per type. Grouping literals under one arm would
4084
+ * make that extraction silently fail for every type in the group.
4085
+ */
3228
4086
  export type SortConfig = BaseSortConfig & ({
3229
- type: "numeric" | "boolean" | "date" | "basic";
4087
+ type: "numeric";
4088
+ } | {
4089
+ type: "boolean";
4090
+ } | {
4091
+ type: "date";
4092
+ dateGranularity?: "day" | "instant";
3230
4093
  } | {
3231
- type: "alphabetical" | "alphanumeric";
4094
+ type: "basic";
4095
+ } | {
4096
+ type: "alphabetical";
4097
+ caseSensitive?: boolean;
4098
+ } | {
4099
+ type: "alphanumeric";
3232
4100
  caseSensitive?: boolean;
3233
4101
  } | {
3234
4102
  type: "custom";
3235
4103
  compare: (a: unknown, b: unknown) => number;
3236
4104
  });
4105
+ /** All active sort keys, in priority order - `sorts[0]` is the primary sort, later entries only break ties left by earlier ones. */
3237
4106
  export type SortState = SortConfig[];
4107
+ /**
4108
+ * A single sort type's own options, with `id`/`type`/`field` (which every
4109
+ * type shares, and which `toggleSort` already takes as separate arguments)
4110
+ * stripped out. Used to type `toggleSort`'s `options` parameter, narrowed
4111
+ * to only the options relevant to the specific `type` being toggled.
4112
+ */
3238
4113
  export type SortOptionsForType<T extends SortType> = Omit<Extract<SortConfig, {
3239
4114
  type: T;
3240
4115
  }>, "id" | "type" | "field">;
4116
+ /**
4117
+ * The update payload type for `updateSortConfig`.
4118
+ *
4119
+ * @remarks
4120
+ * Preserves per-type shape correlation at the type level - e.g. TypeScript
4121
+ * will reject `{ dateGranularity: "day" }` paired with a `type` that isn't
4122
+ * `"date"`. This is best-effort compile-time guidance, not a full
4123
+ * guarantee: a *merge* of an update into an existing config can still land
4124
+ * on a structurally invalid combination that the type system alone can't
4125
+ * catch (partial updates flatten across a union in ways whole-object
4126
+ * construction doesn't). {@link isSortConfigShapeValid}, run on the merged
4127
+ * result inside `applySortUpdate`, is the actual runtime backstop.
4128
+ */
4129
+ export type SortConfigUpdate = DistributivePartial<DistributiveOmit<SortConfig, "id">>;
4130
+ /** Options for {@link useSort}. */
4131
+ export interface UseSortOptions {
4132
+ /**
4133
+ * Defers the sort recomputation (via `useDeferredValue`) so changing
4134
+ * `sorts` doesn't block a more urgent update. Only `sorts` is deferred -
4135
+ * `data` is not.
4136
+ * @defaultValue `false`
4137
+ */
4138
+ defer?: boolean;
4139
+ }
4140
+ /** Return value of {@link useSort}. */
3241
4141
  export interface UseSortReturn<T> {
4142
+ /** `data`, sorted by every entry in `sorts`, applied in priority order. Same reference as `data` (no copy, no sort) when `sorts` is empty. */
3242
4143
  sortedItems: T[];
4144
+ /** The currently-active sort keys, in priority order. */
3243
4145
  sorts: SortState;
4146
+ /** `sorts.length`. */
4147
+ sortCount: number;
4148
+ /** `sortCount > 0`. */
4149
+ hasSorts: boolean;
4150
+ /** Adds a new sort key, or replaces the existing one with the same `id`. */
3244
4151
  upsertSorts: (sort: SortConfig) => void;
4152
+ /** Removes one sort key by `id`, or several at once by passing an array of ids. */
3245
4153
  removeSort: (id: string | string[]) => void;
4154
+ /** Clears every sort key - equivalent to `replaceSorts([])`. */
3246
4155
  clearSorts: () => void;
4156
+ /** Restores `sorts` to the value passed as `initialSorts` at mount. Later changes to that argument have no effect - the reset target is frozen at mount. */
3247
4157
  resetSorts: () => void;
4158
+ /** Replaces the entire `sorts` array at once. */
3248
4159
  replaceSorts: (sorts: SortState) => void;
4160
+ /**
4161
+ * Cycles a sort key through asc -> desc -> (reset to asc, or remove
4162
+ * entirely) - the standard three-state table-header sort interaction.
4163
+ *
4164
+ * @remarks
4165
+ * - Whether the last step resets to asc or removes the sort key
4166
+ * entirely depends on `disableSortRemoval` (checked on both the
4167
+ * existing config and the newly-passed `options`).
4168
+ * - `options.multi` (`false` by default) controls whether toggling
4169
+ * this key adds/updates it alongside any other active sort keys
4170
+ * (`true`), or replaces the entire `sorts` array with just this one
4171
+ * key (`false`) - the usual "click a column to sort by only that
4172
+ * column, shift-click to add a secondary sort" pattern.
4173
+ * - `type` must be supplied on every call, even for an already-active
4174
+ * sort key - this hook has no independent memory of a key's type
4175
+ * beyond what's in `sorts` itself.
4176
+ */
3249
4177
  toggleSort: <TType extends SortType>(id: string, type: TType, options?: SortOptionsForType<TType> & {
3250
4178
  multi?: boolean;
3251
4179
  field?: string;
3252
4180
  }) => void;
4181
+ /**
4182
+ * Partially updates an existing sort key's config by `id`.
4183
+ * @remarks An unknown `id`, or an update that would produce a
4184
+ * structurally invalid config for its `type`, is rejected - see
4185
+ * {@link isSortConfigShapeValid}.
4186
+ */
4187
+ updateSortConfig: (id: string, partialConfig: SortConfigUpdate) => void;
4188
+ /** Looks up a sort key's full config by `id`. `undefined` if no such key exists. */
4189
+ getSort: (id: string) => SortConfig | undefined;
4190
+ /** `"asc"`/`"desc"` for an active sort key, `undefined` if `id` isn't currently sorted. */
3253
4191
  getSortDirection: (id: string) => "asc" | "desc" | undefined;
4192
+ /** What `toggleSort(id, ...)` would transition `id` to next, without actually calling it - useful for rendering the right sort-direction icon before the user clicks. */
3254
4193
  getNextSortingOrder: (id: string) => "asc" | "desc" | "none";
4194
+ /** This sort key's 1-based priority among active sort keys (`1` = primary), or `undefined` if `id` isn't currently sorted. */
3255
4195
  getSortIndex: (id: string) => number | undefined;
3256
4196
  }
3257
- export declare function useSort<T>(data?: T[], initialSorts?: SortState): UseSortReturn<T>;
4197
+ /**
4198
+ * Sorts an array by one or more keys - single-column or multi-column,
4199
+ * priority determined by array order.
4200
+ *
4201
+ * @remarks
4202
+ * - **Multi-key sort**: `sorts[0]` is the primary sort; later entries in
4203
+ * the array only break ties left unresolved by earlier ones - the same
4204
+ * convention as `Array.prototype.sort` with a compound comparator, or a
4205
+ * spreadsheet's "sort by, then by" dialog.
4206
+ * - **Validation is dev/prod-split**, uniformly across every mutator that
4207
+ * can produce a structurally invalid config (`toggleSort`,
4208
+ * `updateSortConfig`), and across an unresolvable sort key encountered
4209
+ * during sorting itself (unknown `type`, or a `custom` sort missing its
4210
+ * `compare` function): throws immediately in development, but degrades
4211
+ * gracefully in production (an invalid mutation is rejected/no-op; an
4212
+ * unresolvable sort key at compute time is simply skipped, later keys
4213
+ * still apply).
4214
+ * - **`sortUndefined`'s `"first"`/`"last"` are absolute positions**, never
4215
+ * affected by `desc`/`invertSorting`; `-1`/`1` simulate a real extreme
4216
+ * value and do flip with direction - see {@link SortUndefinedOption}.
4217
+ * - **`resetSorts` is frozen at mount** - it restores `initialSorts` as it
4218
+ * was on the very first render, not whatever value that argument holds
4219
+ * on a later render.
4220
+ * - **Id lookups are O(1)**, backed by a `Map` built once per `sorts`
4221
+ * change, not a linear scan per call - safe to call `getSort`/
4222
+ * `getSortDirection`/etc. once per rendered column header without a
4223
+ * performance concern.
4224
+ *
4225
+ * @typeParam T - The type of each item in `data`.
4226
+ * @param data - The items to sort. Defaults to `[]`.
4227
+ * @param initialSorts - Sort keys applied at mount. Defaults to `[]` (no sorting).
4228
+ * @param options - See {@link UseSortOptions}.
4229
+ * @returns The sorted items and the current sort state, plus the actions
4230
+ * to change it. See {@link UseSortReturn}.
4231
+ *
4232
+ * @example
4233
+ * Single column, via a table header click handler:
4234
+ * ```tsx
4235
+ * const { sortedItems, getSortDirection, toggleSort } = useSort(rows);
4236
+ *
4237
+ * <th onClick={() => toggleSort("name", "alphabetical")}>
4238
+ * Name {getSortDirection("name") === "asc" ? "▲" : "▼"}
4239
+ * </th>
4240
+ * ```
4241
+ *
4242
+ * @example
4243
+ * Multi-key, set directly:
4244
+ * ```tsx
4245
+ * const { sortedItems, replaceSorts } = useSort(orders, [
4246
+ * { id: "status", type: "alphabetical" },
4247
+ * { id: "placedAt", type: "date", desc: true },
4248
+ * ]);
4249
+ * // sorted by status first; same-status orders sorted by placedAt, newest first
4250
+ * ```
4251
+ */
4252
+ export declare function useSort<T>(data?: T[], initialSorts?: SortState, options?: UseSortOptions): UseSortReturn<T>;
3258
4253
  /**
3259
4254
  * Restricts `field` to a **top-level** key of `T` whose value is assignable to `TId`.
3260
4255
  *
@@ -3484,6 +4479,144 @@ export interface UseTreeSelectionReturn<T, TId extends SelectionId> {
3484
4479
  * ```
3485
4480
  */
3486
4481
  export declare function useTreeSelection<T, TId extends SelectionId = SelectionId>(options: UseTreeSelectionOptions<T, TId>): UseTreeSelectionReturn<T, TId>;
4482
+ /** The id type `useVisibility` operates on by default - a raw `string` or `number`. */
4483
+ export type VisibilityId = string | number;
4484
+ /**
4485
+ * Options for {@link useVisibility}, in either of its two modes - id-only
4486
+ * (`T` defaults to `TId`) or object mode (`T` is a distinct item shape,
4487
+ * `TId` its resolved id type).
4488
+ *
4489
+ * @remarks
4490
+ * The conditional shape is what makes `field` required in object mode and
4491
+ * disallowed in id-only mode, entirely at the type level - see
4492
+ * {@link useVisibility}'s remarks for why.
4493
+ *
4494
+ * @typeParam T - The item shape, or `TId` itself for id-only mode.
4495
+ * @typeParam TId - The id type.
4496
+ */
4497
+ export type UseVisibilityOptions<T = VisibilityId, TId extends VisibilityId = VisibilityId> = T extends TId ? {
4498
+ /** The full list of items whose visibility is being tracked - backs `visibleItems`/`hiddenItems`, and is the default target for `showAll` when it's called with no argument. */
4499
+ items?: readonly T[];
4500
+ /** Ids visible from the start - frozen at mount, see {@link useVisibility}'s remarks. */
4501
+ initialVisibleIds?: readonly TId[];
4502
+ } : {
4503
+ /** The full list of items whose visibility is being tracked - backs `visibleItems`/`hiddenItems`, and is the default target for `showAll` when it's called with no argument. */
4504
+ items: readonly T[];
4505
+ /** A dot-path into `T`, used to resolve an item's id whenever a target is given as an item rather than a raw id. */
4506
+ field: Path<T> | (string & {});
4507
+ /** Ids visible from the start - frozen at mount, see {@link useVisibility}'s remarks. */
4508
+ initialVisibleIds?: readonly TId[];
4509
+ };
4510
+ /**
4511
+ * Return value of {@link useVisibility}.
4512
+ *
4513
+ * @typeParam T - The item shape, or `TId` itself for id-only mode.
4514
+ * @typeParam TId - The id type.
4515
+ */
4516
+ export interface UseVisibilityReturn<T = VisibilityId, TId extends VisibilityId = VisibilityId> {
4517
+ /** The currently visible ids. */
4518
+ visibleIds: readonly TId[];
4519
+ /** The subset of `items` that are currently visible. */
4520
+ visibleItems: readonly T[];
4521
+ /** The subset of `items` that are currently hidden. */
4522
+ hiddenItems: readonly T[];
4523
+ /** `visibleIds.length`. */
4524
+ visibleCount: number;
4525
+ /** `hiddenItems.length`. */
4526
+ hiddenCount: number;
4527
+ /** Whether anything at all is visible. */
4528
+ hasVisible: boolean;
4529
+ /** Whether `itemOrId` is currently visible. */
4530
+ isVisible: (itemOrId: TId | T) => boolean;
4531
+ /** Shows `itemOrId`. No-ops if it's already visible. */
4532
+ show: (itemOrId: TId | T) => void;
4533
+ /** Hides `itemOrId`. No-ops if it's already hidden. */
4534
+ hide: (itemOrId: TId | T) => void;
4535
+ /** Shows `itemOrId` if it's hidden, hides it if it's visible. */
4536
+ toggleVisibility: (itemOrId: TId | T) => void;
4537
+ /** Shows every id/item given, or every item in `items` if called with no argument. Adds to the current visible set rather than replacing it. */
4538
+ showAll: (itemsArray?: readonly TId[] | readonly T[]) => void;
4539
+ /**
4540
+ * Hides every id/item given, or hides everything if called with no
4541
+ * argument.
4542
+ * @remarks Unlike {@link UseVisibilityReturn.showAll}, "no argument" means a blanket clear, not "act on the hook's own `items`" - see {@link useVisibility}'s remarks.
4543
+ */
4544
+ hideAll: (itemsArray?: readonly TId[] | readonly T[]) => void;
4545
+ /** Restores the visible set to the value `initialVisibleIds` had at mount - see {@link useVisibility}'s remarks. */
4546
+ resetVisibility: () => void;
4547
+ /** Replaces the entire visible set with exactly these ids/items. */
4548
+ replaceVisibility: (newVisibleItems: readonly TId[] | readonly T[]) => void;
4549
+ }
4550
+ /**
4551
+ * Manages which item(s) in a list are visible - show/hide toggles, column
4552
+ * visibility, filterable chip lists.
4553
+ *
4554
+ * @remarks
4555
+ * - Uncontrolled only for now - controlled mode is planned separately and
4556
+ * will be added without breaking this signature.
4557
+ * - SSR-safe: performs no DOM/window access; `initialVisibleIds` must be
4558
+ * deterministic between server and client renders to avoid hydration
4559
+ * mismatches.
4560
+ * - All returned callbacks are manually memoized with `useCallback` so this
4561
+ * hook is safe to use even in codebases **without** the React Compiler.
4562
+ * - Two modes, picked by whether `T` is assignable to `TId`: id-only
4563
+ * (default) - `itemOrId` parameters only ever receive raw ids, `field`
4564
+ * is disallowed; or object mode - `<Row, string>` plus a required
4565
+ * `field`, letting `itemOrId` parameters take a full item too. See
4566
+ * {@link UseVisibilityOptions}.
4567
+ * - `showAll`/`hideAll` intentionally differ in what "no argument" means:
4568
+ * `showAll()` acts on the hook's own `items` (additive - anything
4569
+ * already visible but no longer in `items` is left alone), while
4570
+ * `hideAll()` is a blanket clear regardless of `items` (so a
4571
+ * previously-visible id that's since fallen out of `items` doesn't stay
4572
+ * visible forever). Passing an explicit array to either scopes it to
4573
+ * just that subset.
4574
+ *
4575
+ * @typeParam T - The item shape, or `TId` itself for id-only mode.
4576
+ * @typeParam TId - The id type.
4577
+ * @param options - See {@link UseVisibilityOptions}.
4578
+ * @returns The current visibility state and the actions to change it. See {@link UseVisibilityReturn}.
4579
+ *
4580
+ * @example
4581
+ * Id-only:
4582
+ * ```tsx
4583
+ * const { isVisible, toggleVisibility } = useVisibility({
4584
+ * initialVisibleIds: ["col-name", "col-email"],
4585
+ * });
4586
+ * ```
4587
+ *
4588
+ * @example
4589
+ * Object mode:
4590
+ * ```tsx
4591
+ * interface Column { id: string; label: string }
4592
+ * const { visibleItems, hide, showAll } = useVisibility<Column, string>({
4593
+ * items: columns,
4594
+ * field: "id",
4595
+ * });
4596
+ * ```
4597
+ */
4598
+ export declare function useVisibility<T = VisibilityId, TId extends VisibilityId = VisibilityId>(options?: UseVisibilityOptions<T, TId>): UseVisibilityReturn<T, TId>;
4599
+ export interface FuzzySearchOptions {
4600
+ threshold?: number;
4601
+ caseSensitive?: boolean;
4602
+ matchStrategy?: "any" | "all";
4603
+ exactPhraseBonus?: boolean;
4604
+ }
4605
+ export interface IndexedToken {
4606
+ text: string;
4607
+ weight: number;
4608
+ }
4609
+ export interface FlatIndexedItem<T> {
4610
+ item: T;
4611
+ index: number;
4612
+ tokens: IndexedToken[];
4613
+ combinedFlatText: string;
4614
+ }
4615
+ export interface ScoredItem<T> {
4616
+ item: T;
4617
+ score: number;
4618
+ index: number;
4619
+ }
3487
4620
  /**
3488
4621
  * Defines how a value of type `T` is converted to and from the string
3489
4622
  * format that `localStorage`/`sessionStorage` can actually store — the Web
@@ -3781,69 +4914,6 @@ export interface FuzzyHighlighterProps {
3781
4914
  caseSensitive?: boolean;
3782
4915
  }
3783
4916
  export declare function FuzzyHighlighter({ text, query, className, caseSensitive, }: FuzzyHighlighterProps): React$1.JSX.Element;
3784
- export type ExpansionId = string | number;
3785
- export interface UseExpansionReturn<T> {
3786
- expandedIds: ExpansionId[];
3787
- expandedItems: T[];
3788
- isExpanded: (itemOrId: ExpansionId | T) => boolean;
3789
- expand: (itemOrId: ExpansionId | T) => void;
3790
- collapse: (itemOrId: ExpansionId | T) => void;
3791
- toggle: (itemOrId: ExpansionId | T) => void;
3792
- expandAll: () => void;
3793
- collapseAll: () => void;
3794
- resetExpansion: () => void;
3795
- replaceExpansion: (newExpandedItems: ExpansionId[] | T[]) => void;
3796
- }
3797
- export declare function useExpansion<T = unknown>(options?: {
3798
- items?: T[];
3799
- field?: string;
3800
- initialExpandedIds?: ExpansionId[];
3801
- multiple?: boolean;
3802
- }): UseExpansionReturn<T>;
3803
- export type PinId = string | number;
3804
- export interface UsePinReturn<T> {
3805
- pinnedIds: PinId[];
3806
- pinnedItems: T[];
3807
- unpinnedItems: T[];
3808
- pinnedCount: number;
3809
- hasPins: boolean;
3810
- isAtMaxLimit: boolean;
3811
- pin: (itemOrId: PinId | T) => void;
3812
- unpin: (itemOrId: PinId | T) => void;
3813
- togglePin: (itemOrId: PinId | T) => void;
3814
- isPinned: (itemOrId: PinId | T) => boolean;
3815
- clearPins: () => void;
3816
- resetPins: () => void;
3817
- replacePins: (newPinnedItems: PinId[] | T[]) => void;
3818
- pinMultiple: (newItems: PinId[] | T[]) => void;
3819
- unpinMultiple: (itemsToRemove: PinId[] | T[]) => void;
3820
- }
3821
- export declare function usePin<T = unknown>(options?: {
3822
- items?: T[];
3823
- field?: string;
3824
- initialPinnedIds?: PinId[];
3825
- maxPins?: number;
3826
- }): UsePinReturn<T>;
3827
- export type VisibilityId = number | string;
3828
- export interface UseVisibilityReturn<T> {
3829
- visibleIds: VisibilityId[];
3830
- visibleItems: T[];
3831
- hiddenItems: T[];
3832
- visibleCount: number;
3833
- isVisible: (itemOrId: VisibilityId | T) => boolean;
3834
- show: (itemOrId: VisibilityId | T) => void;
3835
- hide: (itemOrId: VisibilityId | T) => void;
3836
- toggleVisibility: (itemOrId: VisibilityId | T) => void;
3837
- showAll: (itemsArray?: VisibilityId[] | T[]) => void;
3838
- hideAll: (itemsArray?: VisibilityId[] | T[]) => void;
3839
- resetVisibility: () => void;
3840
- replaceVisibility: (newVisibleItems: VisibilityId[] | T[]) => void;
3841
- }
3842
- export declare function useVisibility<T = unknown>(options?: {
3843
- items?: T[];
3844
- field?: string;
3845
- initialVisibleIds?: VisibilityId[];
3846
- }): UseVisibilityReturn<T>;
3847
4917
 
3848
4918
  export {
3849
4919
  UseStorageEngineReturn as UseLocalStorageReturn,