@himanshu-sorathiya/react-kit 1.0.32 → 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 +1763 -160
- package/dist/index.js +4 -4
- package/dist/state.d.ts +1752 -86
- package/dist/state.js +2 -2
- package/dist/state2.js +1699 -538
- package/dist/ui.d.ts +0 -63
- package/dist/ui.js +2 -2
- package/dist/ui2.js +14 -220
- package/dist/utils.js +8 -4
- package/package.json +1 -1
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
|
-
|
|
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
|
-
|
|
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
|
|
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,176 +3024,1599 @@ 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
|
-
|
|
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
|
-
|
|
2800
|
-
|
|
2801
|
-
|
|
2802
|
-
|
|
2803
|
-
|
|
2804
|
-
|
|
2805
|
-
|
|
2806
|
-
|
|
2807
|
-
|
|
2808
|
-
|
|
2809
|
-
|
|
2810
|
-
|
|
2811
|
-
|
|
2812
|
-
|
|
2813
|
-
|
|
2814
|
-
|
|
2815
|
-
|
|
2816
|
-
|
|
2817
|
-
|
|
2818
|
-
|
|
2819
|
-
|
|
2820
|
-
|
|
2821
|
-
|
|
2822
|
-
|
|
2823
|
-
|
|
2824
|
-
|
|
2825
|
-
|
|
2826
|
-
|
|
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>[];
|
|
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;
|
|
2830
3207
|
}
|
|
3208
|
+
/** Return value of {@link useGrouping}. */
|
|
2831
3209
|
export interface UseGroupingReturn<T> {
|
|
2832
|
-
|
|
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
|
-
|
|
2837
|
-
|
|
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
|
-
|
|
2843
|
-
|
|
2844
|
-
|
|
2845
|
-
|
|
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
|
-
|
|
2848
|
-
|
|
3308
|
+
/**
|
|
3309
|
+
* All dot-notation paths of `T` whose resolved value is assignable to `TId`.
|
|
3310
|
+
*
|
|
3311
|
+
* @remarks
|
|
3312
|
+
* This is what makes `field` type-safe: a path pointing at a boolean, an
|
|
3313
|
+
* object, or a non-existent property simply isn't a member of this type, so
|
|
3314
|
+
* passing it is a compile-time error rather than a silent `undefined` at
|
|
3315
|
+
* runtime. Built on top of the `Path`/`PathValue` machinery already exported
|
|
3316
|
+
* by `getValue`'s module.
|
|
3317
|
+
*
|
|
3318
|
+
* @template T The item shape.
|
|
3319
|
+
* @template TId The id type the resolved value must be assignable to.
|
|
3320
|
+
*/
|
|
3321
|
+
export type SelectionIdPath<T, TId extends SelectionId> = {
|
|
3322
|
+
[P in Path<T> & string]: PathValue<T, P> extends TId ? P : never;
|
|
3323
|
+
}[Path<T> & string];
|
|
3324
|
+
/**
|
|
3325
|
+
* The options every {@link useMultipleSelection} call accepts regardless of
|
|
3326
|
+
* whether `T` is a raw id type or an object type.
|
|
3327
|
+
*
|
|
3328
|
+
* @typeParam T - The item shape (or `TId` itself, for id-only mode).
|
|
3329
|
+
* @typeParam TId - The id type used internally for `Set`/comparison purposes.
|
|
3330
|
+
*/
|
|
3331
|
+
export interface UseMultipleSelectionBaseOptions<T, TId extends SelectionId> {
|
|
3332
|
+
/**
|
|
3333
|
+
* The items this selection is over. Determines `selectedItems`, and is
|
|
3334
|
+
* what `selectAll`/`toggleAll`/`invertSelection` operate against.
|
|
3335
|
+
*
|
|
3336
|
+
* @remarks
|
|
3337
|
+
* Pass a stable/memoized array. An inline `items={data.filter(...)}` gets
|
|
3338
|
+
* a new reference every render, which cascades into every memoized value
|
|
3339
|
+
* derived from `items` recomputing every render too — this hook can't
|
|
3340
|
+
* cheaply detect "same contents, different reference" for you.
|
|
3341
|
+
*/
|
|
3342
|
+
items?: readonly T[];
|
|
3343
|
+
/**
|
|
3344
|
+
* The ids selected on mount, and what {@link UseMultipleSelectionReturn.reset}
|
|
3345
|
+
* returns the selection to.
|
|
3346
|
+
*
|
|
3347
|
+
* @remarks
|
|
3348
|
+
* Only the value present on the **first render** seeds state (standard
|
|
3349
|
+
* `useState` initializer semantics). `reset()` always reads the **latest**
|
|
3350
|
+
* `defaultSelectedIds` at call time, so if this changes across renders,
|
|
3351
|
+
* `reset()` restores to the newest default, not the original mount-time one.
|
|
3352
|
+
*/
|
|
3353
|
+
defaultSelectedIds?: readonly TId[];
|
|
3354
|
+
/**
|
|
3355
|
+
* Predicate that marks certain ids as non-selectable.
|
|
3356
|
+
*
|
|
3357
|
+
* @remarks
|
|
3358
|
+
* Enforced on every operation that **adds** to the selection (`select`,
|
|
3359
|
+
* `toggle`'s select-direction, `selectMultiple`, `selectAll`, `toggleAll`,
|
|
3360
|
+
* `invertSelection`, `replaceSelection`). It never blocks **removal**
|
|
3361
|
+
* (`deselect`, `deselectMultiple`, `retainOnly`, `toggle`'s deselect-direction,
|
|
3362
|
+
* `deselectAll`) — if an already-selected id becomes disabled later, you can
|
|
3363
|
+
* always deselect it, just not re-select it.
|
|
3364
|
+
*
|
|
3365
|
+
* @param id - The id being checked.
|
|
3366
|
+
* @returns `true` if the id must not be added to the selection.
|
|
3367
|
+
*/
|
|
3368
|
+
isDisabled?: (id: TId) => boolean;
|
|
3369
|
+
}
|
|
3370
|
+
/**
|
|
3371
|
+
* The `field` requirement, resolved conditionally on whether `T` is already
|
|
3372
|
+
* an id (`[T] extends [TId]`) or a full object.
|
|
3373
|
+
*
|
|
3374
|
+
* @remarks
|
|
3375
|
+
* - id-only mode (`T` is assignable to `TId`, e.g. the default `T = SelectionId`):
|
|
3376
|
+
* `field` is forbidden — there's nothing to extract a path from.
|
|
3377
|
+
* - object mode: `field` is **required**, and restricted to
|
|
3378
|
+
* {@link SelectionIdPath} — a path that doesn't exist on `T`, or whose
|
|
3379
|
+
* resolved value isn't assignable to `TId`, is a compile-time error rather
|
|
3380
|
+
* than a silent `undefined` id at runtime.
|
|
3381
|
+
*/
|
|
3382
|
+
export type UseMultipleSelectionFieldOptions<T, TId extends SelectionId> = [
|
|
3383
|
+
T
|
|
3384
|
+
] extends [
|
|
3385
|
+
TId
|
|
3386
|
+
] ? {
|
|
3387
|
+
field?: never;
|
|
3388
|
+
} : {
|
|
3389
|
+
field: SelectionIdPath<T, TId>;
|
|
3390
|
+
};
|
|
3391
|
+
/**
|
|
3392
|
+
* Combined options for {@link useMultipleSelection}. See
|
|
3393
|
+
* {@link UseMultipleSelectionBaseOptions} and {@link UseMultipleSelectionFieldOptions}.
|
|
3394
|
+
*
|
|
3395
|
+
* @typeParam T - The item shape. Defaults to `SelectionId` (id-only mode: pass
|
|
3396
|
+
* raw ids as "items", no `field` needed).
|
|
3397
|
+
* @typeParam TId - The id type. Defaults to `SelectionId`; narrow it (e.g. to a
|
|
3398
|
+
* branded `UserId`) for stronger inference.
|
|
3399
|
+
*/
|
|
3400
|
+
export type UseMultipleSelectionOptions<T = SelectionId, TId extends SelectionId = SelectionId> = UseMultipleSelectionBaseOptions<T, TId> & UseMultipleSelectionFieldOptions<T, TId>;
|
|
3401
|
+
/**
|
|
3402
|
+
* Return shape of {@link useMultipleSelection}.
|
|
3403
|
+
*
|
|
3404
|
+
* @typeParam T - The item shape.
|
|
3405
|
+
* @typeParam TId - The id type.
|
|
3406
|
+
*/
|
|
3407
|
+
export interface UseMultipleSelectionReturn<T = SelectionId, TId extends SelectionId = SelectionId> {
|
|
3408
|
+
/**
|
|
3409
|
+
* The currently selected ids.
|
|
3410
|
+
*
|
|
3411
|
+
* @remarks
|
|
3412
|
+
* Ordered to match `items`' order whenever every selected id is present in
|
|
3413
|
+
* `items`. If some selected ids aren't in the current `items` array (e.g.
|
|
3414
|
+
* a previously-selected item that's since been filtered out, or an id
|
|
3415
|
+
* selected directly without ever appearing in `items`), those "orphaned"
|
|
3416
|
+
* ids are preserved and appended at the end, rather than silently dropped —
|
|
3417
|
+
* this hook never discards selection state you didn't ask it to discard.
|
|
3418
|
+
*/
|
|
3419
|
+
selectedIds: readonly TId[];
|
|
3420
|
+
/** The number of currently selected ids (including any orphaned ones — see {@link UseMultipleSelectionReturn.selectedIds}). */
|
|
2849
3421
|
selectedCount: number;
|
|
2850
|
-
|
|
3422
|
+
/** The subset of `items` that are currently selected, in `items`' order. */
|
|
3423
|
+
selectedItems: readonly T[];
|
|
3424
|
+
/** Whether nothing at all is selected. */
|
|
2851
3425
|
isEmpty: boolean;
|
|
2852
|
-
|
|
2853
|
-
|
|
2854
|
-
|
|
2855
|
-
|
|
2856
|
-
|
|
2857
|
-
|
|
3426
|
+
/**
|
|
3427
|
+
* Whether every *selectable* (non-disabled) item in `items` is currently
|
|
3428
|
+
* selected. `false` when `items` is empty. Intended for a "select all"
|
|
3429
|
+
* checkbox's checked state.
|
|
3430
|
+
*/
|
|
3431
|
+
isAllSelected: boolean;
|
|
3432
|
+
/**
|
|
3433
|
+
* Whether some, but not all, selectable items in `items` are selected.
|
|
3434
|
+
* Intended for a "select all" checkbox's indeterminate state.
|
|
3435
|
+
*/
|
|
3436
|
+
isPartiallySelected: boolean;
|
|
3437
|
+
/**
|
|
3438
|
+
* Adds `item` to the selection.
|
|
3439
|
+
* @remarks No-ops if `item` resolves to a disabled id.
|
|
3440
|
+
* @param item - A raw id, or a full item (requires `field` to have been configured).
|
|
3441
|
+
*/
|
|
3442
|
+
select: (item: TId | T) => void;
|
|
3443
|
+
/**
|
|
3444
|
+
* Removes `item` from the selection. Always allowed, even for disabled ids.
|
|
3445
|
+
* @param item - A raw id, or a full item.
|
|
3446
|
+
*/
|
|
3447
|
+
deselect: (item: TId | T) => void;
|
|
3448
|
+
/**
|
|
3449
|
+
* Adds `item` if not selected, removes it if selected.
|
|
3450
|
+
* @remarks The add-direction is blocked for disabled ids; the remove-direction never is.
|
|
3451
|
+
* @param item - A raw id, or a full item.
|
|
3452
|
+
*/
|
|
3453
|
+
toggle: (item: TId | T) => void;
|
|
3454
|
+
/**
|
|
3455
|
+
* Checks whether `item` is currently selected.
|
|
3456
|
+
* @param item - A raw id, or a full item.
|
|
3457
|
+
* @returns `true` if currently selected.
|
|
3458
|
+
*/
|
|
3459
|
+
isSelected: (item: TId | T) => boolean;
|
|
3460
|
+
/** Restores the selection to the current `defaultSelectedIds` (or empty, if none was provided). */
|
|
3461
|
+
reset: () => void;
|
|
3462
|
+
/**
|
|
3463
|
+
* Replaces the entire selection with exactly these ids/items.
|
|
3464
|
+
* @remarks Disabled ids are filtered out of the replacement set.
|
|
3465
|
+
*/
|
|
3466
|
+
replaceSelection: (newSelectedItems: readonly TId[] | readonly T[]) => void;
|
|
3467
|
+
/** Selects every selectable (non-disabled) item in `items`. */
|
|
2858
3468
|
selectAll: () => void;
|
|
3469
|
+
/** Clears the entire selection, including any disabled-but-selected or orphaned ids. */
|
|
2859
3470
|
deselectAll: () => void;
|
|
3471
|
+
/**
|
|
3472
|
+
* If every selectable item in `items` is currently selected, clears the
|
|
3473
|
+
* whole selection; otherwise selects every selectable item in `items`.
|
|
3474
|
+
*
|
|
3475
|
+
* @remarks
|
|
3476
|
+
* Determined by actual set membership against every selectable item, not
|
|
3477
|
+
* a size comparison — this stays correct even if `items` contains duplicate
|
|
3478
|
+
* resolved ids, or the current selection contains ids no longer present in
|
|
3479
|
+
* `items` (both of which would silently misfire a naive `prev.size === items.length` check).
|
|
3480
|
+
*/
|
|
2860
3481
|
toggleAll: () => void;
|
|
3482
|
+
/** Selects every currently-unselected selectable item, and deselects everything else. */
|
|
2861
3483
|
invertSelection: () => void;
|
|
2862
|
-
|
|
2863
|
-
|
|
2864
|
-
|
|
3484
|
+
/**
|
|
3485
|
+
* Adds multiple ids/items to the selection at once.
|
|
3486
|
+
* @remarks Disabled ids are filtered out.
|
|
3487
|
+
*/
|
|
3488
|
+
selectMultiple: (newItems: readonly TId[] | readonly T[]) => void;
|
|
3489
|
+
/** Removes multiple ids/items from the selection at once. Always allowed. */
|
|
3490
|
+
deselectMultiple: (itemsToRemove: readonly TId[] | readonly T[]) => void;
|
|
3491
|
+
/** Keeps only the ids/items in `itemsToRetain` that are already selected (an intersection); never adds anything new. */
|
|
3492
|
+
retainOnly: (itemsToRetain: readonly TId[] | readonly T[]) => void;
|
|
2865
3493
|
}
|
|
2866
|
-
|
|
2867
|
-
|
|
2868
|
-
|
|
2869
|
-
|
|
2870
|
-
|
|
2871
|
-
|
|
2872
|
-
|
|
2873
|
-
|
|
2874
|
-
|
|
2875
|
-
|
|
2876
|
-
|
|
2877
|
-
|
|
2878
|
-
|
|
2879
|
-
|
|
2880
|
-
|
|
3494
|
+
/**
|
|
3495
|
+
* Manages multi-item selection state (e.g. checkboxes in a table, a
|
|
3496
|
+
* multi-select list, bulk-action UI).
|
|
3497
|
+
*
|
|
3498
|
+
* @remarks
|
|
3499
|
+
* - Uncontrolled only for now — controlled mode is planned separately and
|
|
3500
|
+
* will be added without breaking this signature.
|
|
3501
|
+
* - SSR-safe: no DOM/window access; `defaultSelectedIds` must be deterministic
|
|
3502
|
+
* between server and client renders to avoid hydration mismatches.
|
|
3503
|
+
* - All returned callbacks are manually memoized with `useCallback`/`useMemo`
|
|
3504
|
+
* so this hook is safe to use even in codebases **without** the React Compiler.
|
|
3505
|
+
* - Works in two modes, picked by whether `T` is assignable to `TId`:
|
|
3506
|
+
* - **id-only mode** (default): `useMultipleSelection()` — "items" are raw
|
|
3507
|
+
* ids, no `field` needed.
|
|
3508
|
+
* - **object mode**: `useMultipleSelection<Item, string>({ items, field: "id" })` —
|
|
3509
|
+
* `field` is a type-checked dot-path into `Item` and is required.
|
|
3510
|
+
*
|
|
3511
|
+
* @typeParam T - The item shape (or `TId` itself, for id-only mode).
|
|
3512
|
+
* @typeParam TId - The id type. Defaults to `SelectionId`; narrow it for branded-id inference.
|
|
3513
|
+
* @param options - See {@link UseMultipleSelectionOptions}.
|
|
3514
|
+
* @returns The current selection state and the actions to mutate it. See {@link UseMultipleSelectionReturn}.
|
|
3515
|
+
*
|
|
3516
|
+
* @example
|
|
3517
|
+
* ```tsx
|
|
3518
|
+
* // id-only mode
|
|
3519
|
+
* const { selectedIds, toggle } = useMultipleSelection({ defaultSelectedIds: ["a"] });
|
|
3520
|
+
* ```
|
|
3521
|
+
*
|
|
3522
|
+
* @example
|
|
3523
|
+
* ```tsx
|
|
3524
|
+
* // object mode, with a nested field path and disabled rows
|
|
3525
|
+
* interface Row { id: string; locked: boolean }
|
|
3526
|
+
* const { selectedItems, toggleAll, isAllSelected, isPartiallySelected } = useMultipleSelection<Row, string>({
|
|
3527
|
+
* items: rows,
|
|
3528
|
+
* field: "id",
|
|
3529
|
+
* isDisabled: (id) => rows.find((r) => r.id === id)?.locked ?? false,
|
|
3530
|
+
* });
|
|
3531
|
+
* ```
|
|
3532
|
+
*/
|
|
3533
|
+
export declare function useMultipleSelection<T = SelectionId, TId extends SelectionId = SelectionId>(options?: UseMultipleSelectionOptions<T, TId>): UseMultipleSelectionReturn<T, TId>;
|
|
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. */
|
|
2881
3629
|
resetOrder: () => void;
|
|
2882
|
-
|
|
3630
|
+
/** Replaces the entire order with exactly these items. */
|
|
3631
|
+
replaceOrder: (newOrderedItems: readonly T[]) => void;
|
|
3632
|
+
}
|
|
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}. */
|
|
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). */
|
|
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. */
|
|
3697
|
+
pageSize: number;
|
|
3698
|
+
/** `Math.max(1, Math.ceil(totalCount / pageSize))` - always at least `1`, even for an empty `data`. */
|
|
3699
|
+
totalPages: number;
|
|
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. */
|
|
3711
|
+
nextPage: () => void;
|
|
3712
|
+
/** Moves to the previous page, if any. No-op on the first page. */
|
|
3713
|
+
previousPage: () => void;
|
|
3714
|
+
/** Jumps to page `1`. */
|
|
3715
|
+
goToFirstPage: () => void;
|
|
3716
|
+
/** Jumps to the last page. */
|
|
3717
|
+
goToLastPage: () => 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
|
+
*/
|
|
3731
|
+
changePageSize: (newPageSize: number) => 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. */
|
|
3735
|
+
resetPageSize: () => void;
|
|
3736
|
+
/** Restores both `pageNumber` and `pageSize` to their mount-time initial values, in one update. */
|
|
3737
|
+
resetPagination: () => void;
|
|
3738
|
+
}
|
|
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;
|
|
2883
3875
|
}
|
|
2884
|
-
|
|
2885
|
-
|
|
2886
|
-
|
|
2887
|
-
|
|
2888
|
-
|
|
2889
|
-
|
|
2890
|
-
|
|
2891
|
-
|
|
2892
|
-
|
|
2893
|
-
|
|
2894
|
-
|
|
2895
|
-
|
|
2896
|
-
|
|
2897
|
-
|
|
2898
|
-
|
|
2899
|
-
|
|
2900
|
-
|
|
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>;
|
|
3923
|
+
/**
|
|
3924
|
+
* Configuration options for {@link useSingleSelection}.
|
|
3925
|
+
*
|
|
3926
|
+
* @typeParam TId - The concrete id type used by this selection instance.
|
|
3927
|
+
* Must be assignable to {@link SelectionId} (`string | number`), but can be
|
|
3928
|
+
* a narrower/branded type (e.g. `UserId`) for stronger inference at call sites.
|
|
3929
|
+
*/
|
|
3930
|
+
export interface UseSingleSelectionOptions<TId extends SelectionId = SelectionId> {
|
|
3931
|
+
/**
|
|
3932
|
+
* The id that is selected on mount, and the id that {@link UseSingleSelectionReturn.reset}
|
|
3933
|
+
* returns the selection to.
|
|
3934
|
+
*
|
|
3935
|
+
* @remarks
|
|
3936
|
+
* Only the value present on the **first render** is used to seed state
|
|
3937
|
+
* (standard `useState` initializer semantics — later changes to this value
|
|
3938
|
+
* do not retroactively change the current selection). However, `reset()`
|
|
3939
|
+
* always reads the **latest** `defaultSelectedId` at the time it's called,
|
|
3940
|
+
* so if this value changes across renders, `reset()` will restore to the
|
|
3941
|
+
* newest default, not the original mount-time one.
|
|
3942
|
+
*/
|
|
3943
|
+
defaultSelectedId?: TId;
|
|
3944
|
+
/**
|
|
3945
|
+
* Predicate that marks certain ids as non-selectable.
|
|
3946
|
+
*
|
|
3947
|
+
* @remarks
|
|
3948
|
+
* This guard is only enforced inside {@link UseSingleSelectionReturn.select}
|
|
3949
|
+
* and {@link UseSingleSelectionReturn.toggle}. It does **not** retroactively
|
|
3950
|
+
* clear a selection if an already-selected id later becomes disabled — the
|
|
3951
|
+
* hook has no way to know `isDisabled`'s result changed unless you call
|
|
3952
|
+
* `select`/`toggle` again. Reconcile that case yourself (e.g. via an effect)
|
|
3953
|
+
* if it matters for your use case.
|
|
3954
|
+
*
|
|
3955
|
+
* @param id - The id being checked before selection.
|
|
3956
|
+
* @returns `true` if the id must not be selectable.
|
|
3957
|
+
*/
|
|
3958
|
+
isDisabled?: (id: TId) => boolean;
|
|
2901
3959
|
}
|
|
2902
|
-
|
|
2903
|
-
|
|
2904
|
-
|
|
3960
|
+
/**
|
|
3961
|
+
* Return shape of {@link useSingleSelection}.
|
|
3962
|
+
*
|
|
3963
|
+
* @typeParam TId - The concrete id type used by this selection instance.
|
|
3964
|
+
*/
|
|
3965
|
+
export interface UseSingleSelectionReturn<TId extends SelectionId = SelectionId> {
|
|
3966
|
+
/** The currently selected id, or `undefined` if nothing is selected. */
|
|
3967
|
+
selectedId: TId | undefined;
|
|
3968
|
+
/**
|
|
3969
|
+
* Whether any id is currently selected.
|
|
3970
|
+
*
|
|
3971
|
+
* @remarks
|
|
3972
|
+
* Correctly distinguishes "nothing selected" from a falsy-but-valid id
|
|
3973
|
+
* such as `0` or `""` — this is `selectedId !== undefined`, not `!!selectedId`.
|
|
3974
|
+
*/
|
|
2905
3975
|
hasSelection: boolean;
|
|
2906
|
-
|
|
3976
|
+
/**
|
|
3977
|
+
* Selects the given id, replacing any current selection.
|
|
3978
|
+
*
|
|
3979
|
+
* @remarks No-ops if `id` is disabled per `isDisabled`.
|
|
3980
|
+
* @param id - The id to select.
|
|
3981
|
+
*/
|
|
3982
|
+
select: (id: TId) => void;
|
|
3983
|
+
/** Clears the current selection (sets it to `undefined`). */
|
|
2907
3984
|
deselect: () => void;
|
|
2908
|
-
|
|
2909
|
-
|
|
2910
|
-
|
|
3985
|
+
/**
|
|
3986
|
+
* Selects `id` if it isn't already selected; deselects it if it is.
|
|
3987
|
+
*
|
|
3988
|
+
* @remarks No-ops if `id` is disabled per `isDisabled`.
|
|
3989
|
+
* @param id - The id to toggle.
|
|
3990
|
+
*/
|
|
3991
|
+
toggle: (id: TId) => void;
|
|
3992
|
+
/**
|
|
3993
|
+
* Checks whether the given id is the currently selected one.
|
|
3994
|
+
*
|
|
3995
|
+
* @param id - The id to check.
|
|
3996
|
+
* @returns `true` if `id` is currently selected.
|
|
3997
|
+
*/
|
|
3998
|
+
isSelected: (id: TId) => boolean;
|
|
3999
|
+
/**
|
|
4000
|
+
* Restores the selection to the current `defaultSelectedId`
|
|
4001
|
+
* (or `undefined` if none was provided).
|
|
4002
|
+
*/
|
|
4003
|
+
reset: () => void;
|
|
2911
4004
|
}
|
|
2912
|
-
|
|
4005
|
+
/**
|
|
4006
|
+
* Manages single-item selection state (e.g. a radio group, a single-select
|
|
4007
|
+
* list, an active tab/row).
|
|
4008
|
+
*
|
|
4009
|
+
* @remarks
|
|
4010
|
+
* - Uncontrolled only for now — controlled mode (`selectedId` + `onSelectionChange`)
|
|
4011
|
+
* is planned separately and will be added without breaking this signature.
|
|
4012
|
+
* - SSR-safe: performs no DOM/window access; `defaultSelectedId` must be
|
|
4013
|
+
* deterministic between server and client renders to avoid hydration mismatches.
|
|
4014
|
+
* - All returned callbacks are manually memoized with `useCallback` so this
|
|
4015
|
+
* hook is safe to use even in codebases **without** the React Compiler.
|
|
4016
|
+
*
|
|
4017
|
+
* @typeParam TId - The concrete id type used by this selection instance.
|
|
4018
|
+
* @param options - Optional configuration. See {@link UseSingleSelectionOptions}.
|
|
4019
|
+
* @returns The current selection state and the actions to mutate it. See {@link UseSingleSelectionReturn}.
|
|
4020
|
+
*
|
|
4021
|
+
* @example
|
|
4022
|
+
* ```tsx
|
|
4023
|
+
* const { selectedId, select, isSelected } = useSingleSelection<string>({
|
|
4024
|
+
* defaultSelectedId: "row-1",
|
|
4025
|
+
* });
|
|
4026
|
+
* ```
|
|
4027
|
+
*/
|
|
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. */
|
|
2913
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. */
|
|
2914
4047
|
export interface BaseSortOptions {
|
|
4048
|
+
/**
|
|
4049
|
+
* Sort direction.
|
|
4050
|
+
* @defaultValue `false` (ascending)
|
|
4051
|
+
*/
|
|
2915
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
|
+
*/
|
|
2916
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
|
+
*/
|
|
2917
4065
|
invertSorting?: boolean;
|
|
2918
|
-
|
|
4066
|
+
/** See {@link SortUndefinedOption}. */
|
|
4067
|
+
sortUndefined?: SortUndefinedOption;
|
|
2919
4068
|
}
|
|
4069
|
+
/** Fields common to every {@link SortConfig}, regardless of `type`. */
|
|
2920
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. */
|
|
2921
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. */
|
|
2922
4074
|
field?: string;
|
|
2923
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
|
+
*/
|
|
2924
4086
|
export type SortConfig = BaseSortConfig & ({
|
|
2925
|
-
type: "numeric"
|
|
4087
|
+
type: "numeric";
|
|
4088
|
+
} | {
|
|
4089
|
+
type: "boolean";
|
|
4090
|
+
} | {
|
|
4091
|
+
type: "date";
|
|
4092
|
+
dateGranularity?: "day" | "instant";
|
|
2926
4093
|
} | {
|
|
2927
|
-
type: "
|
|
4094
|
+
type: "basic";
|
|
4095
|
+
} | {
|
|
4096
|
+
type: "alphabetical";
|
|
4097
|
+
caseSensitive?: boolean;
|
|
4098
|
+
} | {
|
|
4099
|
+
type: "alphanumeric";
|
|
2928
4100
|
caseSensitive?: boolean;
|
|
2929
4101
|
} | {
|
|
2930
4102
|
type: "custom";
|
|
2931
4103
|
compare: (a: unknown, b: unknown) => number;
|
|
2932
4104
|
});
|
|
4105
|
+
/** All active sort keys, in priority order - `sorts[0]` is the primary sort, later entries only break ties left by earlier ones. */
|
|
2933
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
|
+
*/
|
|
2934
4113
|
export type SortOptionsForType<T extends SortType> = Omit<Extract<SortConfig, {
|
|
2935
4114
|
type: T;
|
|
2936
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}. */
|
|
2937
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. */
|
|
2938
4143
|
sortedItems: T[];
|
|
4144
|
+
/** The currently-active sort keys, in priority order. */
|
|
2939
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`. */
|
|
2940
4151
|
upsertSorts: (sort: SortConfig) => void;
|
|
4152
|
+
/** Removes one sort key by `id`, or several at once by passing an array of ids. */
|
|
2941
4153
|
removeSort: (id: string | string[]) => void;
|
|
4154
|
+
/** Clears every sort key - equivalent to `replaceSorts([])`. */
|
|
2942
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. */
|
|
2943
4157
|
resetSorts: () => void;
|
|
4158
|
+
/** Replaces the entire `sorts` array at once. */
|
|
2944
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
|
+
*/
|
|
2945
4177
|
toggleSort: <TType extends SortType>(id: string, type: TType, options?: SortOptionsForType<TType> & {
|
|
2946
4178
|
multi?: boolean;
|
|
2947
4179
|
field?: string;
|
|
2948
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. */
|
|
2949
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. */
|
|
2950
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. */
|
|
2951
4195
|
getSortIndex: (id: string) => number | undefined;
|
|
2952
4196
|
}
|
|
2953
|
-
|
|
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>;
|
|
4253
|
+
/**
|
|
4254
|
+
* Restricts `field` to a **top-level** key of `T` whose value is assignable to `TId`.
|
|
4255
|
+
*
|
|
4256
|
+
* @remarks
|
|
4257
|
+
* Deliberately not the recursive dot-path style used by `useMultipleSelection`'s
|
|
4258
|
+
* `SelectionIdPath<T, TId>`. Tree nodes are self-referential (a `children` property
|
|
4259
|
+
* whose value is `T[]`, pointing back to `T`), and running the recursive `Path<T>`
|
|
4260
|
+
* machinery over a self-referential type produces a genuine TypeScript circular-reference
|
|
4261
|
+
* error. A tree node's id is virtually always a direct top-level property anyway — a
|
|
4262
|
+
* dot-path id nested inside a tree node would be an unusual shape — so this simpler,
|
|
4263
|
+
* non-recursive key filter is both the fix and the more correct constraint. The function
|
|
4264
|
+
* form below remains available for anything this doesn't cover.
|
|
4265
|
+
*/
|
|
4266
|
+
export type FieldKey<T, TId extends SelectionId> = {
|
|
4267
|
+
[K in keyof T]: T[K] extends TId ? K : never;
|
|
4268
|
+
}[keyof T] & string;
|
|
4269
|
+
/** Restricts `childrenField` to a top-level key of `T` whose value is `T[]` (or `undefined`). */
|
|
4270
|
+
export type ChildrenKey<T> = {
|
|
4271
|
+
[K in keyof T]: T[K] extends readonly T[] | undefined ? K : never;
|
|
4272
|
+
}[keyof T] & string;
|
|
4273
|
+
/** Either a type-checked property key, or a function, for extracting a node's id. */
|
|
4274
|
+
export type FieldAccessor<T, TId extends SelectionId> = FieldKey<T, TId> | ((item: T) => TId);
|
|
4275
|
+
/** Either a type-checked property key, or a function, for extracting a node's children. */
|
|
4276
|
+
export type ChildrenAccessor<T> = ChildrenKey<T> | ((item: T) => readonly T[] | undefined);
|
|
4277
|
+
/** The three states a tree node can be in with respect to the current selection. */
|
|
4278
|
+
export type TreeNodeState = "selected" | "indeterminate" | "unselected";
|
|
4279
|
+
/** One flattened tree entry, produced by {@link flattenForest}. */
|
|
4280
|
+
export interface FlatTreeEntry<T, TId extends SelectionId> {
|
|
4281
|
+
item: T;
|
|
4282
|
+
id: TId;
|
|
4283
|
+
parentId: TId | undefined;
|
|
4284
|
+
depth: number;
|
|
4285
|
+
}
|
|
4286
|
+
/** The precomputed structures every tree-selection operation is built on. */
|
|
4287
|
+
export interface FlattenedForest<T, TId extends SelectionId> {
|
|
4288
|
+
/** Every node in the forest, in DFS pre-order. */
|
|
4289
|
+
entries: readonly FlatTreeEntry<T, TId>[];
|
|
4290
|
+
/** O(1) lookup from id to its flattened entry. */
|
|
4291
|
+
entryById: ReadonlyMap<TId, FlatTreeEntry<T, TId>>;
|
|
4292
|
+
/** O(1) lookup from a node's id to its direct children's ids. Roots are keyed under `undefined`. */
|
|
4293
|
+
childIdsByParentId: ReadonlyMap<TId | undefined, readonly TId[]>;
|
|
4294
|
+
}
|
|
4295
|
+
export interface UseTreeSelectionOptions<T, TId extends SelectionId> {
|
|
4296
|
+
/** The forest — an array of root nodes. Each node's children are found via `childrenField`. */
|
|
4297
|
+
items: readonly T[];
|
|
4298
|
+
/** How to extract a node's id. A type-checked top-level key, or a function. */
|
|
4299
|
+
field: FieldAccessor<T, TId>;
|
|
4300
|
+
/** How to extract a node's children. A type-checked top-level key, or a function. */
|
|
4301
|
+
childrenField: ChildrenAccessor<T>;
|
|
4302
|
+
/**
|
|
4303
|
+
* The ids selected on mount, and what {@link UseTreeSelectionReturn.reset} returns to.
|
|
4304
|
+
*
|
|
4305
|
+
* @remarks
|
|
4306
|
+
* Not trusted as already cascade-consistent — always run through the same
|
|
4307
|
+
* full bottom-up normalization used by `reset`/`selectAll`/etc, so passing
|
|
4308
|
+
* e.g. only a leaf (without its ancestors) still produces correct indeterminate
|
|
4309
|
+
* ancestor state from the start.
|
|
4310
|
+
*/
|
|
4311
|
+
defaultSelectedIds?: readonly TId[];
|
|
4312
|
+
/**
|
|
4313
|
+
* Predicate marking certain ids as non-selectable.
|
|
4314
|
+
*
|
|
4315
|
+
* @remarks
|
|
4316
|
+
* A disabled node is skipped during cascade (never force-toggled) and excluded
|
|
4317
|
+
* from every "are all children selected" computation, but disabling a node does
|
|
4318
|
+
* **not** auto-disable its descendants, and does not retroactively clear it if
|
|
4319
|
+
* it was already selected before becoming disabled — same philosophy as the
|
|
4320
|
+
* flat selection hooks.
|
|
4321
|
+
*/
|
|
4322
|
+
isDisabled?: (id: TId) => boolean;
|
|
4323
|
+
/**
|
|
4324
|
+
* Independently controls whether selecting a node propagates to its descendants
|
|
4325
|
+
* (`down`) and/or its ancestors (`up`). Both default to `true`.
|
|
4326
|
+
*
|
|
4327
|
+
* @remarks
|
|
4328
|
+
* `{ down: false, up: false }` degenerates into flat, non-hierarchical
|
|
4329
|
+
* selection over tree-rendered nodes (no relationship between a node's
|
|
4330
|
+
* selection and its parent/children) — the same shape react-arborist/MUI's
|
|
4331
|
+
* default tree multi-select uses, as opposed to checkbox-tree cascading.
|
|
4332
|
+
*/
|
|
4333
|
+
cascade?: {
|
|
4334
|
+
down?: boolean;
|
|
4335
|
+
up?: boolean;
|
|
4336
|
+
};
|
|
4337
|
+
/**
|
|
4338
|
+
* When `true`, {@link UseTreeSelectionReturn.selectedIds} and
|
|
4339
|
+
* {@link UseTreeSelectionReturn.selectedItems} only include leaf nodes,
|
|
4340
|
+
* even if a fully-covered branch is internally tracked as selected.
|
|
4341
|
+
*
|
|
4342
|
+
* @remarks
|
|
4343
|
+
* This does not change `select`/`toggle`/cascade behavior at all — you can
|
|
4344
|
+
* still call `select` on a branch and it cascades normally, and
|
|
4345
|
+
* {@link UseTreeSelectionReturn.getNodeState} still reports branches correctly
|
|
4346
|
+
* as `"selected"`/`"indeterminate"` for display regardless of this option. It
|
|
4347
|
+
* only changes what the flat `selectedIds`/`selectedItems` arrays report — useful
|
|
4348
|
+
* when branches are just a UI grouping and the "real" selected resources are
|
|
4349
|
+
* always the leaves (e.g. sending a set of leaf resource ids to a backend).
|
|
4350
|
+
* {@link UseTreeSelectionReturn.selectedLeafIds} gives you the leaf subset
|
|
4351
|
+
* regardless of this option, if you want both views at once.
|
|
4352
|
+
*/
|
|
4353
|
+
leafOnly?: boolean;
|
|
4354
|
+
}
|
|
4355
|
+
export interface UseTreeSelectionReturn<T, TId extends SelectionId> {
|
|
4356
|
+
/**
|
|
4357
|
+
* Every fully-selected node's id, in forest (DFS pre-order) order.
|
|
4358
|
+
* @remarks Includes branch ids too, unless `leafOnly` is set. See {@link UseTreeSelectionOptions.leafOnly}.
|
|
4359
|
+
*/
|
|
4360
|
+
selectedIds: readonly TId[];
|
|
4361
|
+
/** Just the leaf ids among the current selection, regardless of `leafOnly`. */
|
|
4362
|
+
selectedLeafIds: readonly TId[];
|
|
4363
|
+
/** Every node currently in a partial (some-but-not-all-descendants-selected) state. */
|
|
4364
|
+
indeterminateIds: readonly TId[];
|
|
4365
|
+
/** The item objects corresponding to {@link UseTreeSelectionReturn.selectedIds}. */
|
|
4366
|
+
selectedItems: readonly T[];
|
|
4367
|
+
/** `selectedIds.length`. */
|
|
4368
|
+
selectedCount: number;
|
|
4369
|
+
/** Whether nothing at all is selected (not even indeterminate). */
|
|
4370
|
+
isEmpty: boolean;
|
|
4371
|
+
/** Whether every selectable root (and by construction, everything under it) is selected. */
|
|
4372
|
+
isAllSelected: boolean;
|
|
4373
|
+
/** Whether some, but not all, of the forest is selected or indeterminate. */
|
|
4374
|
+
isPartiallySelected: boolean;
|
|
4375
|
+
/**
|
|
4376
|
+
* The full tri-state read for a node — the primitive the rest of the boolean
|
|
4377
|
+
* getters below are built from.
|
|
4378
|
+
* @param item - A raw id, or a full item.
|
|
4379
|
+
*/
|
|
4380
|
+
getNodeState: (item: TId | T) => TreeNodeState;
|
|
4381
|
+
/** `getNodeState(item) === "selected"`. */
|
|
4382
|
+
isSelected: (item: TId | T) => boolean;
|
|
4383
|
+
/** `getNodeState(item) === "indeterminate"`. */
|
|
4384
|
+
isIndeterminate: (item: TId | T) => boolean;
|
|
4385
|
+
/**
|
|
4386
|
+
* Selects `item`, cascading per the `cascade` option.
|
|
4387
|
+
* @remarks No-ops if `item` itself resolves to a disabled id.
|
|
4388
|
+
*/
|
|
4389
|
+
select: (item: TId | T) => void;
|
|
4390
|
+
/** Deselects `item`, cascading per the `cascade` option. Always allowed, even for disabled ids. */
|
|
4391
|
+
deselect: (item: TId | T) => void;
|
|
4392
|
+
/**
|
|
4393
|
+
* Selects `item` if not selected, deselects it if selected, cascading per the `cascade` option.
|
|
4394
|
+
* @remarks The select-direction is blocked for disabled ids; the deselect-direction never is.
|
|
4395
|
+
*/
|
|
4396
|
+
toggle: (item: TId | T) => void;
|
|
4397
|
+
/** Restores the selection to the current `defaultSelectedIds` (normalized), or empty if none was given. */
|
|
4398
|
+
reset: () => void;
|
|
4399
|
+
/** Replaces the entire selection with exactly these ids/items (normalized — always internally consistent afterward). */
|
|
4400
|
+
replaceSelection: (newSelectedItems: readonly TId[] | readonly T[]) => void;
|
|
4401
|
+
/** Selects every selectable node in the forest. */
|
|
4402
|
+
selectAll: () => void;
|
|
4403
|
+
/** Clears the entire selection. */
|
|
4404
|
+
deselectAll: () => void;
|
|
4405
|
+
/** If everything selectable is currently selected, clears the selection; otherwise selects everything selectable. */
|
|
4406
|
+
toggleAll: () => void;
|
|
4407
|
+
/**
|
|
4408
|
+
* Selects multiple ids/items at once, cascading each per the `cascade` option.
|
|
4409
|
+
*
|
|
4410
|
+
* @remarks
|
|
4411
|
+
* More efficient than calling {@link UseTreeSelectionReturn.select} in a loop:
|
|
4412
|
+
* shared ancestors of multiple targets are only recomputed once each, not once
|
|
4413
|
+
* per target that shares them.
|
|
4414
|
+
*/
|
|
4415
|
+
selectMultiple: (newItems: readonly TId[] | readonly T[]) => void;
|
|
4416
|
+
/** Deselects multiple ids/items at once, cascading each per the `cascade` option. Always allowed, even for disabled ids. */
|
|
4417
|
+
deselectMultiple: (itemsToRemove: readonly TId[] | readonly T[]) => void;
|
|
4418
|
+
/**
|
|
4419
|
+
* Keeps only the ids/items in `itemsToRetain` that are already selected (an
|
|
4420
|
+
* intersection); never adds anything new. The result is re-normalized, so
|
|
4421
|
+
* ancestor indeterminate state stays correct after the shrink.
|
|
4422
|
+
*/
|
|
4423
|
+
retainOnly: (itemsToRetain: readonly TId[] | readonly T[]) => void;
|
|
4424
|
+
/**
|
|
4425
|
+
* Inverts the selection at the leaf level: every currently-unselected
|
|
4426
|
+
* selectable leaf becomes selected, every currently-selected one becomes
|
|
4427
|
+
* unselected. Branch/indeterminate state is re-derived from the result.
|
|
4428
|
+
* @remarks A disabled leaf that was selected before this call does not survive
|
|
4429
|
+
* it — like `useMultipleSelection`'s `invertSelection`, this is a full replace,
|
|
4430
|
+
* not a merge.
|
|
4431
|
+
*/
|
|
4432
|
+
invertSelection: () => void;
|
|
4433
|
+
/** The id of `item`'s direct parent, or `undefined` if it's a root. */
|
|
4434
|
+
getParentId: (item: TId | T) => TId | undefined;
|
|
4435
|
+
/** Every ancestor id of `item`, nearest first, root last. Empty if `item` is a root. */
|
|
4436
|
+
getAncestorIds: (item: TId | T) => readonly TId[];
|
|
4437
|
+
/** Every descendant id of `item`. Empty if `item` is a leaf. */
|
|
4438
|
+
getDescendantIds: (item: TId | T) => readonly TId[];
|
|
4439
|
+
}
|
|
4440
|
+
/**
|
|
4441
|
+
* Manages hierarchical (tree/forest) selection state — checkbox trees, nested
|
|
4442
|
+
* category pickers, permission trees, file explorers.
|
|
4443
|
+
*
|
|
4444
|
+
* @remarks
|
|
4445
|
+
* - Uncontrolled only for now, SSR-safe (no DOM access), all callbacks manually
|
|
4446
|
+
* memoized — same guarantees as the other selection hooks in this library.
|
|
4447
|
+
* - Expand/collapse state is explicitly **not** managed here — it's an orthogonal
|
|
4448
|
+
* view concern, not a selection concern (this hook only ever reads the full
|
|
4449
|
+
* `children` structure, regardless of what's currently expanded in the UI).
|
|
4450
|
+
* - **Performance**: a single `select`/`deselect`/`toggle` call is
|
|
4451
|
+
* O(affected subtree) for the cascade-down step plus O(depth × branching factor)
|
|
4452
|
+
* for the cascade-up step — it never re-walks the whole tree. Only whole-forest
|
|
4453
|
+
* operations (`selectAll`, `toggleAll`, `reset`, `replaceSelection`, and the
|
|
4454
|
+
* initial mount) do a full O(n) pass, which is the right complexity for
|
|
4455
|
+
* something that touches every node anyway. Verified independent of selection
|
|
4456
|
+
* order (a known bug class in at least one production tree-selection library
|
|
4457
|
+
* is indeterminate state differing based on the order nodes were selected in).
|
|
4458
|
+
* - If `items` (the tree **structure**) changes after mount — nodes added, removed,
|
|
4459
|
+
* or moved — the existing selection/indeterminate state is **not** automatically
|
|
4460
|
+
* re-normalized against the new shape (to avoid surprise O(n) work on every data
|
|
4461
|
+
* refresh). It stays correct for anything the change didn't touch, but the newly
|
|
4462
|
+
* changed area may need an explicit `reset()` or `replaceSelection()` to
|
|
4463
|
+
* guarantee full consistency again.
|
|
4464
|
+
*
|
|
4465
|
+
* @typeParam T - The tree node shape.
|
|
4466
|
+
* @typeParam TId - The id type. Defaults to `SelectionId`.
|
|
4467
|
+
* @param options - See {@link UseTreeSelectionOptions}.
|
|
4468
|
+
* @returns The current selection state and the actions to mutate it. See {@link UseTreeSelectionReturn}.
|
|
4469
|
+
*
|
|
4470
|
+
* @example
|
|
4471
|
+
* ```tsx
|
|
4472
|
+
* interface Category { id: string; name: string; children?: Category[] }
|
|
4473
|
+
*
|
|
4474
|
+
* const { getNodeState, toggle, isAllSelected, toggleAll } = useTreeSelection<Category, string>({
|
|
4475
|
+
* items: categoryTree,
|
|
4476
|
+
* field: "id",
|
|
4477
|
+
* childrenField: "children",
|
|
4478
|
+
* });
|
|
4479
|
+
* ```
|
|
4480
|
+
*/
|
|
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
|
+
}
|
|
2954
4620
|
/**
|
|
2955
4621
|
* Defines how a value of type `T` is converted to and from the string
|
|
2956
4622
|
* format that `localStorage`/`sessionStorage` can actually store — the Web
|
|
@@ -3248,69 +4914,6 @@ export interface FuzzyHighlighterProps {
|
|
|
3248
4914
|
caseSensitive?: boolean;
|
|
3249
4915
|
}
|
|
3250
4916
|
export declare function FuzzyHighlighter({ text, query, className, caseSensitive, }: FuzzyHighlighterProps): React$1.JSX.Element;
|
|
3251
|
-
export type ExpansionId = string | number;
|
|
3252
|
-
export interface UseExpansionReturn<T> {
|
|
3253
|
-
expandedIds: ExpansionId[];
|
|
3254
|
-
expandedItems: T[];
|
|
3255
|
-
isExpanded: (itemOrId: ExpansionId | T) => boolean;
|
|
3256
|
-
expand: (itemOrId: ExpansionId | T) => void;
|
|
3257
|
-
collapse: (itemOrId: ExpansionId | T) => void;
|
|
3258
|
-
toggle: (itemOrId: ExpansionId | T) => void;
|
|
3259
|
-
expandAll: () => void;
|
|
3260
|
-
collapseAll: () => void;
|
|
3261
|
-
resetExpansion: () => void;
|
|
3262
|
-
replaceExpansion: (newExpandedItems: ExpansionId[] | T[]) => void;
|
|
3263
|
-
}
|
|
3264
|
-
export declare function useExpansion<T = unknown>(options?: {
|
|
3265
|
-
items?: T[];
|
|
3266
|
-
field?: string;
|
|
3267
|
-
initialExpandedIds?: ExpansionId[];
|
|
3268
|
-
multiple?: boolean;
|
|
3269
|
-
}): UseExpansionReturn<T>;
|
|
3270
|
-
export type PinId = string | number;
|
|
3271
|
-
export interface UsePinReturn<T> {
|
|
3272
|
-
pinnedIds: PinId[];
|
|
3273
|
-
pinnedItems: T[];
|
|
3274
|
-
unpinnedItems: T[];
|
|
3275
|
-
pinnedCount: number;
|
|
3276
|
-
hasPins: boolean;
|
|
3277
|
-
isAtMaxLimit: boolean;
|
|
3278
|
-
pin: (itemOrId: PinId | T) => void;
|
|
3279
|
-
unpin: (itemOrId: PinId | T) => void;
|
|
3280
|
-
togglePin: (itemOrId: PinId | T) => void;
|
|
3281
|
-
isPinned: (itemOrId: PinId | T) => boolean;
|
|
3282
|
-
clearPins: () => void;
|
|
3283
|
-
resetPins: () => void;
|
|
3284
|
-
replacePins: (newPinnedItems: PinId[] | T[]) => void;
|
|
3285
|
-
pinMultiple: (newItems: PinId[] | T[]) => void;
|
|
3286
|
-
unpinMultiple: (itemsToRemove: PinId[] | T[]) => void;
|
|
3287
|
-
}
|
|
3288
|
-
export declare function usePin<T = unknown>(options?: {
|
|
3289
|
-
items?: T[];
|
|
3290
|
-
field?: string;
|
|
3291
|
-
initialPinnedIds?: PinId[];
|
|
3292
|
-
maxPins?: number;
|
|
3293
|
-
}): UsePinReturn<T>;
|
|
3294
|
-
export type VisibilityId = number | string;
|
|
3295
|
-
export interface UseVisibilityReturn<T> {
|
|
3296
|
-
visibleIds: VisibilityId[];
|
|
3297
|
-
visibleItems: T[];
|
|
3298
|
-
hiddenItems: T[];
|
|
3299
|
-
visibleCount: number;
|
|
3300
|
-
isVisible: (itemOrId: VisibilityId | T) => boolean;
|
|
3301
|
-
show: (itemOrId: VisibilityId | T) => void;
|
|
3302
|
-
hide: (itemOrId: VisibilityId | T) => void;
|
|
3303
|
-
toggleVisibility: (itemOrId: VisibilityId | T) => void;
|
|
3304
|
-
showAll: (itemsArray?: VisibilityId[] | T[]) => void;
|
|
3305
|
-
hideAll: (itemsArray?: VisibilityId[] | T[]) => void;
|
|
3306
|
-
resetVisibility: () => void;
|
|
3307
|
-
replaceVisibility: (newVisibleItems: VisibilityId[] | T[]) => void;
|
|
3308
|
-
}
|
|
3309
|
-
export declare function useVisibility<T = unknown>(options?: {
|
|
3310
|
-
items?: T[];
|
|
3311
|
-
field?: string;
|
|
3312
|
-
initialVisibleIds?: VisibilityId[];
|
|
3313
|
-
}): UseVisibilityReturn<T>;
|
|
3314
4917
|
|
|
3315
4918
|
export {
|
|
3316
4919
|
UseStorageEngineReturn as UseLocalStorageReturn,
|