@himanshu-sorathiya/react-kit 1.0.33 → 1.0.34

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/state.d.ts CHANGED
@@ -1,22 +1,261 @@
1
1
  // Generated by dts-bundle-generator v9.5.1
2
2
 
3
+ export type Primitive = string | number | boolean | bigint | symbol | null | undefined;
4
+ 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>}`;
5
+ export type Path<T> = T extends object ? {
6
+ [K in keyof T & (string | number)]: PathImpl<K, T[K]>;
7
+ }[keyof T & (string | number)] : never;
8
+ 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;
9
+ /** The id type `useExpansion` operates on by default - a raw `string` or `number`. */
10
+ export type ExpansionId = string | number;
11
+ /**
12
+ * Options for {@link useExpansion}, in either of its two modes - id-only
13
+ * (`T` defaults to `TId`) or object mode (`T` is a distinct item shape,
14
+ * `TId` its resolved id type).
15
+ *
16
+ * @remarks
17
+ * The conditional shape is what makes `field` required in object mode and
18
+ * disallowed in id-only mode, entirely at the type level - see
19
+ * {@link useExpansion}'s remarks for why.
20
+ *
21
+ * @typeParam T - The item shape, or `TId` itself for id-only mode.
22
+ * @typeParam TId - The id type.
23
+ */
24
+ export type UseExpansionOptions<T = ExpansionId, TId extends ExpansionId = ExpansionId> = T extends TId ? {
25
+ /** The full list of expandable items - backs `expandedItems`/`collapsedItems`, and is the default target for `expandAll` when it's called with no argument. */
26
+ items?: readonly T[];
27
+ /** Ids expanded from the start - frozen at mount, see {@link useExpansion}'s remarks. */
28
+ initialExpandedIds?: readonly TId[];
29
+ /** 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. */
30
+ multiple?: boolean;
31
+ /**
32
+ * In single (`multiple: false`) mode, whether the one expanded item
33
+ * can be collapsed back down to nothing.
34
+ *
35
+ * @remarks
36
+ * Ignored when `multiple` is `true` - "can everything be collapsed"
37
+ * isn't a meaningful constraint once more than one item can be open
38
+ * at a time. Mirrors Radix UI Accordion's `collapsible` prop, though
39
+ * the default here is the opposite of Radix's: `true`, matching what
40
+ * this hook already did before `collapsible` existed, so leaving it
41
+ * unset doesn't change any existing behavior.
42
+ *
43
+ * @defaultValue `true`
44
+ */
45
+ collapsible?: boolean;
46
+ } : {
47
+ /** The full list of expandable items - backs `expandedItems`/`collapsedItems`, and is the default target for `expandAll` when it's called with no argument. */
48
+ items: readonly T[];
49
+ /** 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. */
50
+ field: Path<T> | (string & {});
51
+ /** Ids expanded from the start - frozen at mount, see {@link useExpansion}'s remarks. */
52
+ initialExpandedIds?: readonly TId[];
53
+ /** 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. */
54
+ multiple?: boolean;
55
+ /**
56
+ * In single (`multiple: false`) mode, whether the one expanded item
57
+ * can be collapsed back down to nothing.
58
+ *
59
+ * @remarks
60
+ * Ignored when `multiple` is `true` - "can everything be collapsed"
61
+ * isn't a meaningful constraint once more than one item can be open
62
+ * at a time. Mirrors Radix UI Accordion's `collapsible` prop, though
63
+ * the default here is the opposite of Radix's: `true`, matching what
64
+ * this hook already did before `collapsible` existed, so leaving it
65
+ * unset doesn't change any existing behavior.
66
+ *
67
+ * @defaultValue `true`
68
+ */
69
+ collapsible?: boolean;
70
+ };
71
+ /**
72
+ * Return value of {@link useExpansion}.
73
+ *
74
+ * @typeParam T - The item shape, or `TId` itself for id-only mode.
75
+ * @typeParam TId - The id type.
76
+ */
77
+ export interface UseExpansionReturn<T = ExpansionId, TId extends ExpansionId = ExpansionId> {
78
+ /** The currently expanded ids. */
79
+ expandedIds: readonly TId[];
80
+ /** The subset of `items` that are currently expanded. */
81
+ expandedItems: readonly T[];
82
+ /** The subset of `items` that are not currently expanded. */
83
+ collapsedItems: readonly T[];
84
+ /** `expandedIds.length`. */
85
+ expandedCount: number;
86
+ /** Whether anything at all is expanded. */
87
+ hasExpanded: boolean;
88
+ /** Whether `itemOrId` is currently expanded. */
89
+ isExpanded: (itemOrId: TId | T) => boolean;
90
+ /**
91
+ * Expands `itemOrId`.
92
+ * @remarks No-ops if it's already expanded. In single mode, also collapses whatever was previously expanded.
93
+ */
94
+ expand: (itemOrId: TId | T) => void;
95
+ /**
96
+ * Collapses `itemOrId`.
97
+ * @remarks No-ops if it isn't currently expanded, or (in single mode) if `collapsible` is `false`.
98
+ */
99
+ collapse: (itemOrId: TId | T) => void;
100
+ /**
101
+ * Expands `itemOrId` if it isn't expanded, collapses it if it is.
102
+ * @remarks In single mode, expanding also collapses whatever was previously expanded; collapsing is blocked when `collapsible` is `false`, same as {@link UseExpansionReturn.collapse}.
103
+ */
104
+ toggleExpansion: (itemOrId: TId | T) => void;
105
+ /**
106
+ * Expands every id/item given, or every item in `items` if called with
107
+ * no argument. Replaces the current expanded set rather than adding to
108
+ * it.
109
+ * @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.
110
+ */
111
+ expandAll: (itemsArray?: readonly TId[] | readonly T[]) => void;
112
+ /** 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. */
113
+ collapseAll: (itemsArray?: readonly TId[] | readonly T[]) => void;
114
+ /** 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. */
115
+ resetExpansion: () => void;
116
+ /** 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. */
117
+ replaceExpansion: (newExpandedItems: readonly TId[] | readonly T[]) => void;
118
+ }
119
+ /**
120
+ * Manages which item(s) in a list are expanded - accordions, expandable
121
+ * table rows, collapsible sections.
122
+ *
123
+ * @remarks
124
+ * - Uncontrolled only for now - controlled mode is planned separately and
125
+ * will be added without breaking this signature.
126
+ * - SSR-safe: performs no DOM/window access; `initialExpandedIds` must be
127
+ * deterministic between server and client renders to avoid hydration
128
+ * mismatches.
129
+ * - All returned callbacks are manually memoized with `useCallback` so this
130
+ * hook is safe to use even in codebases **without** the React Compiler.
131
+ * - Two modes, picked by whether `T` is assignable to `TId`: id-only
132
+ * (default) - `itemOrId` parameters only ever receive raw ids, `field`
133
+ * is disallowed; or object mode - `<Row, string>` plus a required
134
+ * `field`, letting `itemOrId` parameters take a full item too. See
135
+ * {@link UseExpansionOptions}.
136
+ * - The single/multiple invariant (at most one expanded id when `multiple`
137
+ * is `false`) is enforced at every entry point that can introduce more
138
+ * than one id at once - the initial mount, `resetExpansion`, and
139
+ * `replaceExpansion` - not just `expand`/`toggleExpansion`, which only
140
+ * ever add one id at a time by construction.
141
+ * - `collapsible` (single mode only) specifically gates the *interactive*
142
+ * collapse path - `collapse`/`toggleExpansion` closing the one expanded
143
+ * item. It does not gate `collapseAll` or `replaceExpansion([])`: those
144
+ * are explicit, deliberate "set state directly" calls, treated the same
145
+ * as `clearPins`/`replacePins` in `usePin` not respecting `maxPins`
146
+ * either - a soft interactive constraint doesn't override an explicit
147
+ * caller instruction.
148
+ *
149
+ * @typeParam T - The item shape, or `TId` itself for id-only mode.
150
+ * @typeParam TId - The id type.
151
+ * @param options - See {@link UseExpansionOptions}.
152
+ * @returns The current expansion state and the actions to change it. See {@link UseExpansionReturn}.
153
+ *
154
+ * @example
155
+ * Accordion (single, non-collapsible - always exactly one open):
156
+ * ```tsx
157
+ * const { isExpanded, toggleExpansion } = useExpansion({
158
+ * multiple: false,
159
+ * collapsible: false,
160
+ * initialExpandedIds: ["section-1"],
161
+ * });
162
+ * ```
163
+ *
164
+ * @example
165
+ * Object mode, multiple expandable rows:
166
+ * ```tsx
167
+ * interface Row { id: string; label: string }
168
+ * const { expandedItems, expand, expandAll } = useExpansion<Row, string>({
169
+ * items: rows,
170
+ * field: "id",
171
+ * multiple: true,
172
+ * });
173
+ * ```
174
+ */
175
+ export declare function useExpansion<T = ExpansionId, TId extends ExpansionId = ExpansionId>(options?: UseExpansionOptions<T, TId>): UseExpansionReturn<T, TId>;
176
+ /**
177
+ * `Omit<T, K>`, applied per union member instead of to the flattened union
178
+ * as a whole - plain `Omit` over a union loses the correlation between
179
+ * `type`/`operator` and that arm's own `value` shape, which is exactly the
180
+ * information {@link FilterConfigUpdate} needs to preserve.
181
+ */
182
+ export type DistributiveOmit<T, K extends PropertyKey> = T extends unknown ? Omit<T, K> : never;
183
+ /** `Partial<T>`, applied per union member - same distributive reasoning as {@link DistributiveOmit}. */
184
+ export type DistributivePartial<T> = T extends unknown ? Partial<T> : never;
185
+ /** The comparison strategy for a filter - selects which operator set (`text`, `number`, etc.) applies, or `custom` for a caller-supplied predicate. */
3
186
  export type FilterType = "text" | "number" | "boolean" | "date" | "select" | "multiselect" | "custom";
187
+ /** Operators available on a `text`-type filter. */
4
188
  export type TextOperator = "contains" | "equals" | "startsWith" | "endsWith" | "notContains";
189
+ /** Operators available on a `number`-type filter. */
5
190
  export type NumberOperator = "equals" | "greaterThan" | "lessThan" | "greaterThanOrEqual" | "lessThanOrEqual" | "between";
6
- export type BooleanOperator = "equals";
191
+ /** Operators available on a `boolean`-type filter. */
192
+ export type BooleanOperator = "equals" | "notEquals";
193
+ /** Operators available on a `date`-type filter. */
7
194
  export type DateOperator = "equals" | "before" | "after" | "between";
8
- export type SelectOperator = "equals" | "notEquals";
195
+ /** Operators available on a `select`-type filter (single value against a scalar field). */
196
+ export type SelectOperator = "equals" | "notEquals" | "in" | "notIn";
197
+ /** Operators available on a `multiselect`-type filter (against an array-valued field). */
9
198
  export type MultiselectOperator = "in" | "notIn" | "intersects";
199
+ /** 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. */
10
200
  export type CustomOperator = "custom";
201
+ /** Options shared across filter types, though not every option is meaningful for every type. */
11
202
  export interface FilterOptions<T = unknown> {
203
+ /**
204
+ * Case sensitivity for `text` comparisons. Ignored by every other
205
+ * filter type - `select` deliberately has no equivalent, since select
206
+ * values are enumerated tokens (a fixed options list, an HTML
207
+ * `<select>`), not free-typed input that could differ only by case.
208
+ * @defaultValue `false`
209
+ */
12
210
  caseSensitive?: boolean;
211
+ /**
212
+ * Comparison granularity for `date` filters.
213
+ * `"day"` ignores time-of-day (calendar-date comparison); `"instant"`
214
+ * compares exact milliseconds. Ignored by every other filter type.
215
+ * @defaultValue `"day"`
216
+ */
217
+ dateGranularity?: "day" | "instant";
218
+ /** Required for `type: "custom"` - the predicate deciding whether an item matches. Ignored by every other filter type. */
13
219
  compare?: (itemValue: unknown, filterValue: unknown, item: T) => boolean;
14
220
  }
221
+ /** Options for {@link useFilter}. */
222
+ export interface UseFilterOptions {
223
+ /**
224
+ * Defers the filtering recomputation (via `useDeferredValue`) so
225
+ * changing `filters` doesn't block a more urgent update (e.g. the
226
+ * keystroke that triggered the change). Only `filters` is deferred -
227
+ * `data` is not.
228
+ * @defaultValue `false`
229
+ */
230
+ defer?: boolean;
231
+ }
232
+ /** Fields common to every {@link FilterConfig}, regardless of `type`/`operator`. */
15
233
  export type BaseFilterConfig<T> = FilterOptions<T> & {
234
+ /** Unique identifier for this filter - used for lookups (`getFilter`, `isFilterActive`, etc.) and to distinguish filters in the `filters` array. */
16
235
  id: string;
17
- field: string;
236
+ /** 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. */
237
+ field?: string;
238
+ /**
239
+ * Whether this filter currently participates in filtering. An inactive
240
+ * filter stays in the `filters` array (so its configuration - operator,
241
+ * value, etc. - is preserved) but is skipped during evaluation.
242
+ * @defaultValue `true`
243
+ */
18
244
  isActive?: boolean;
19
245
  };
246
+ /**
247
+ * A single filter's full configuration.
248
+ *
249
+ * @remarks
250
+ * Each `type` has its own dedicated arm(s) - `number`/`date`/`select` each
251
+ * split further by operator (a `between` arm with a `{min,max}` value,
252
+ * separate from every other operator's scalar/array value) - rather than
253
+ * grouping multiple type literals under one shared arm. This is what lets
254
+ * TypeScript actually narrow `value`'s type based on `type`+`operator`,
255
+ * and what makes {@link FilterOptionsForType}-style per-type extraction
256
+ * (and IDE autocomplete while constructing a filter) work correctly -
257
+ * grouping literals under one arm silently breaks both.
258
+ */
20
259
  export type FilterConfig<T = unknown> = BaseFilterConfig<T> & ({
21
260
  type: "text";
22
261
  operator: TextOperator;
@@ -49,8 +288,12 @@ export type FilterConfig<T = unknown> = BaseFilterConfig<T> & ({
49
288
  value: string | number | Date;
50
289
  } | {
51
290
  type: "select";
52
- operator: SelectOperator;
291
+ operator: Exclude<SelectOperator, "in" | "notIn">;
53
292
  value: string | number;
293
+ } | {
294
+ type: "select";
295
+ operator: "in" | "notIn";
296
+ value: (string | number)[];
54
297
  } | {
55
298
  type: "multiselect";
56
299
  operator: MultiselectOperator;
@@ -60,75 +303,287 @@ export type FilterConfig<T = unknown> = BaseFilterConfig<T> & ({
60
303
  operator: CustomOperator;
61
304
  value: unknown;
62
305
  });
306
+ /** 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. */
63
307
  export type FilterState<T> = FilterConfig<T>[];
308
+ /**
309
+ * The update payload type for `updateFilterConfig`.
310
+ *
311
+ * @remarks
312
+ * Best-effort compile-time guidance, not a full guarantee: a *merge* of an
313
+ * update into an existing filter can still land on a structurally invalid
314
+ * `type`/`operator`/`value` combination that the type system alone can't
315
+ * catch (partial updates flatten across a union in ways whole-object
316
+ * construction doesn't). `isValueShapeValid`, run on the merged result
317
+ * inside `applyFilterUpdate`, is the actual runtime backstop.
318
+ */
319
+ export type FilterConfigUpdate<T> = DistributivePartial<DistributiveOmit<FilterConfig<T>, "id">>;
320
+ /** Return value of {@link useFilter}. */
64
321
  export interface UseFilterReturn<T> {
322
+ /** `data`, filtered by every active entry in `filters` (AND-combined). Same reference as `data` (no copy, no filter) when there are no active filters. */
65
323
  filteredItems: T[];
324
+ /** Every configured filter, active or not. */
66
325
  filters: FilterState<T>;
326
+ /** Count of filters with `isActive !== false`. */
327
+ activeFilterCount: number;
328
+ /** `activeFilterCount > 0`. */
329
+ hasActiveFilters: boolean;
330
+ /** Adds a new filter, or replaces the existing one with the same `id`. */
67
331
  upsertFilter: (filter: FilterConfig<T>) => void;
332
+ /** Removes one filter by `id`, or several at once by passing an array of ids. */
68
333
  removeFilter: (id: string | string[]) => void;
334
+ /** Clears every filter entirely - equivalent to `replaceFilters([])`. */
69
335
  clearFilters: () => void;
336
+ /** 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. */
70
337
  resetFilters: () => void;
338
+ /** Replaces the entire `filters` array at once. */
71
339
  replaceFilters: (filters: FilterConfig<T>[]) => void;
340
+ /** 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. */
72
341
  toggleFilter: (filter: FilterConfig<T>) => void;
73
- updateFilterConfig: (id: string, partialConfig: Partial<Omit<FilterConfig<T>, "id">>) => void;
342
+ /**
343
+ * Partially updates an existing filter's config by `id`.
344
+ * @remarks An unknown `id`, or an update that would produce a value
345
+ * incompatible with the filter's `type`/`operator`, is rejected - see
346
+ * {@link isValueShapeValid}.
347
+ */
348
+ updateFilterConfig: (id: string, partialConfig: FilterConfigUpdate<T>) => void;
349
+ /** Looks up a filter's full config by `id`. `undefined` if no such filter exists. */
350
+ getFilter: (id: string) => FilterConfig<T> | undefined;
351
+ /** Just a filter's `value`, by `id`. `undefined` if no such filter exists. */
74
352
  getFilterValue: (id: string) => unknown;
353
+ /** Sugar for `updateFilterConfig(id, { value })` - updates only a filter's value, leaving its type/operator/other options untouched. */
75
354
  updateFilterValue: (id: string, value: unknown) => void;
355
+ /** Whether a filter (by `id`) currently participates in filtering. `false` for a nonexistent `id`, same as an explicitly inactive one. */
76
356
  isFilterActive: (id: string) => boolean;
77
357
  }
78
- export declare function useFilter<T>(data?: T[], initialFilters?: FilterState<T>): UseFilterReturn<T>;
79
- export interface FuzzySearchOptions {
80
- threshold?: number;
81
- caseSensitive?: boolean;
82
- matchStrategy?: "any" | "all";
83
- exactPhraseBonus?: boolean;
84
- }
85
- export interface IndexedToken {
86
- text: string;
87
- weight: number;
88
- }
89
- export interface FlatIndexedItem<T> {
90
- item: T;
91
- index: number;
92
- tokens: IndexedToken[];
93
- combinedFlatText: string;
94
- }
95
- export interface ScoredItem<T> {
96
- item: T;
97
- score: number;
98
- index: number;
99
- }
100
- export type UseFuzzySearchFields<T> = {
101
- field: Extract<keyof T, string>;
102
- weight: number;
103
- }[];
104
- export type UseFuzzySearchReturn<T> = T[];
105
- export declare function useFuzzySearch<T>(data: T[], query: string, fields: UseFuzzySearchFields<T>, options?: FuzzySearchOptions): UseFuzzySearchReturn<T>;
358
+ /**
359
+ * Filters an array against one or more configured conditions - text
360
+ * matching, numeric/date comparisons and ranges, boolean/select/multiselect
361
+ * matching, or a fully custom predicate.
362
+ *
363
+ * @remarks
364
+ * - **Every active filter is AND-combined** - an item must satisfy all of
365
+ * them to be included. There's no built-in OR-across-filters; use a
366
+ * `custom` filter for that if needed.
367
+ * - **Missing/incomparable data always excludes the item**, uniformly
368
+ * across every built-in operator - including the "negative" ones like
369
+ * `notContains`/`notEquals`/`notIn`. A row with no value for the field is
370
+ * treated as "unknown," never as a confident non-match. See
371
+ * `FILTER_STRATEGIES`'s file-level `@remarks` for the full reasoning.
372
+ * - **Validation is dev/prod-split**, same convention as this library's
373
+ * other hooks: an unknown `type`/`operator` combination, or a `custom`
374
+ * filter missing its `compare` function, throws immediately in
375
+ * development, but is silently excluded (that filter matches nothing) in
376
+ * production.
377
+ * - **`field` falls back to `id`** when omitted - so a filter whose `id`
378
+ * already matches the data's field name doesn't need `field` set
379
+ * separately.
380
+ * - **Id lookups are O(1)**, backed by a `Map` built once per `filters`
381
+ * change, not a linear scan per call.
382
+ *
383
+ * @typeParam T - The type of each item in `data`.
384
+ * @param data - The items to filter. Defaults to `[]`.
385
+ * @param initialFilters - Filters applied at mount. Defaults to `[]` (no filtering).
386
+ * @param options - See {@link UseFilterOptions}.
387
+ * @returns The filtered items and the current filter state, plus the
388
+ * actions to change it. See {@link UseFilterReturn}.
389
+ *
390
+ * @example
391
+ * ```tsx
392
+ * const { filteredItems, upsertFilter } = useFilter(products, [
393
+ * { id: "category", type: "select", operator: "equals", value: "Books" },
394
+ * ]);
395
+ *
396
+ * upsertFilter({
397
+ * id: "price",
398
+ * type: "number",
399
+ * operator: "between",
400
+ * value: { min: 10, max: 50 },
401
+ * });
402
+ * ```
403
+ */
404
+ export declare function useFilter<T>(data?: T[], initialFilters?: FilterState<T>, options?: UseFilterOptions): UseFilterReturn<T>;
405
+ /** Bucket granularity for a `date`-type group level - see {@link GroupByDateLevel.bucket}. */
406
+ export type DateBucketGranularity = "day" | "month" | "year";
407
+ /**
408
+ * Groups by a field's raw value.
409
+ * @remarks If the resolved value is an array (e.g. a `tags` field), the
410
+ * item fans out into a separate group per element instead of being
411
+ * String()-coerced into one combined key - see {@link useGrouping}'s
412
+ * `@remarks` for details.
413
+ */
414
+ export type GroupByFieldLevel = {
415
+ type: "field";
416
+ /** Dot-separated path into each item, e.g. `"address.city"`. */
417
+ field: string;
418
+ };
419
+ /** Groups by a `Date`-valued field, bucketed to day/month/year rather than exact timestamp - avoids one group per unique millisecond. */
420
+ export type GroupByDateLevel = {
421
+ type: "date";
422
+ /** Dot-separated path into each item. */
423
+ field: string;
424
+ /**
425
+ * Bucket granularity.
426
+ * @defaultValue `"day"`
427
+ */
428
+ bucket?: DateBucketGranularity;
429
+ };
430
+ /** 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.). */
431
+ export type GroupByCustomLevel<T> = {
432
+ type: "custom";
433
+ /** Optional; used only to identify this level in dev warnings. */
434
+ id?: string;
435
+ /**
436
+ * Computes this item's group key(s).
437
+ * @remarks Return an array to fan the item out into multiple groups at
438
+ * this level, mirroring array-valued {@link GroupByFieldLevel}s.
439
+ * `null`/`undefined` places the item in the Unknown bucket. Throwing,
440
+ * or returning anything other than `string | string[] | null | undefined`,
441
+ * is treated as level misconfiguration - see {@link useGrouping}'s
442
+ * `@remarks`.
443
+ */
444
+ getKey: (item: T) => string | string[] | null | undefined;
445
+ };
446
+ /** A single grouping level - a plain string is shorthand for `{ type: "field", field: theString }`. */
447
+ export type GroupByLevel<T> = string | GroupByFieldLevel | GroupByDateLevel | GroupByCustomLevel<T>;
448
+ /** Same union as {@link GroupByLevel} with the string shorthand already expanded - what {@link useGrouping} stores internally and returns via `activeGroupBy`. */
449
+ export type NormalizedGroupByLevel<T> = GroupByFieldLevel | GroupByDateLevel | GroupByCustomLevel<T>;
450
+ /** A single group, as produced by {@link useGrouping}'s `groupedArray`. */
106
451
  export interface Group<T> {
452
+ /** 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. */
107
453
  key: string;
454
+ /** Human-readable display text. Usually equal to `key`, except for date buckets (e.g. key `"2026-08"`, label `"August 2026"`). */
455
+ label: string;
456
+ /** ALL items under this group, flattened across any deeper levels. */
108
457
  items: T[];
458
+ /** Present only when there's another grouping level below this one. */
459
+ subGroups?: Group<T>[];
460
+ }
461
+ /** Options for {@link useGrouping}. */
462
+ export interface UseGroupingOptions {
463
+ /**
464
+ * Defers the grouping recomputation (via `useDeferredValue`) so changing
465
+ * `activeGroupBy` doesn't block a more urgent update, e.g. the UI
466
+ * interaction that triggered the change. Only `activeGroupBy` is
467
+ * deferred - `items` is not.
468
+ *
469
+ * @defaultValue `false`
470
+ */
471
+ defer?: boolean;
472
+ /**
473
+ * Display label for the synthetic bucket holding items whose value at a
474
+ * given level is missing, blank, or otherwise unresolvable.
475
+ *
476
+ * @defaultValue `"Unknown"`
477
+ */
478
+ unknownGroupLabel?: string;
479
+ /**
480
+ * Display label for the single synthetic group returned when
481
+ * `activeGroupBy` is empty (no grouping applied).
482
+ *
483
+ * @defaultValue `"Ungrouped"`
484
+ */
485
+ ungroupedGroupLabel?: string;
109
486
  }
487
+ /** Return value of {@link useGrouping}. */
110
488
  export interface UseGroupingReturn<T> {
111
- groupedRecord: Record<string, T[]>;
489
+ /** 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. */
112
490
  groupedArray: Group<T>[];
491
+ /** Top-level groups only, flattened -- Record<key, items>. For deeper
492
+ * levels, traverse a group's `subGroups` directly. */
493
+ groupedRecord: Record<string, T[]>;
494
+ /** Top-level group keys only, in the same order as `groupedArray`. */
113
495
  groupKeys: string[];
496
+ /** Count of top-level groups only - `groupKeys.length`. */
114
497
  totalGroups: number;
115
- activeGroupBy: string | undefined;
116
- changeGroupBy: (newField: string) => void;
498
+ /** The currently-applied grouping levels, normalized (string shorthand already expanded). Empty when no grouping is applied. */
499
+ activeGroupBy: NormalizedGroupByLevel<T>[];
500
+ /** `activeGroupBy.length > 0`. */
501
+ hasActiveGrouping: boolean;
502
+ /** 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"`. */
503
+ unknownGroupKey: string;
504
+ /** 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"`. */
505
+ ungroupedGroupKey: string;
506
+ /**
507
+ * Replaces `activeGroupBy` with one level or an array of levels (for
508
+ * hierarchical grouping - order determines nesting, `newGroupBy[0]`
509
+ * becomes the top level).
510
+ * @remarks A malformed level (missing `field`, non-function `getKey`,
511
+ * etc.) is rejected - see {@link useGrouping}'s `@remarks`.
512
+ */
513
+ changeGroupBy: (newGroupBy: GroupByLevel<T> | GroupByLevel<T>[]) => void;
514
+ /** Clears `activeGroupBy` entirely - equivalent to `changeGroupBy([])`. */
117
515
  clearGrouping: () => void;
516
+ /** 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. */
118
517
  resetGrouping: () => void;
518
+ /** Looks up a single top-level group by its `key`. `undefined` if no such group exists.
519
+ * @remarks Top-level lookup only, matching `groupedRecord`'s scope - use `groupedArray`/`subGroups` directly for a nested group. */
520
+ getGroup: (groupKey: string) => Group<T> | undefined;
521
+ /** `getGroup(groupKey)?.items ?? []` - the items in a top-level group, or an empty array if it doesn't exist. */
119
522
  getGroupItems: (groupKey: string) => T[];
120
523
  }
121
- export declare function useGrouping<T = unknown>(options?: {
122
- items?: T[];
123
- initialGroupBy?: string;
124
- }): UseGroupingReturn<T>;
524
+ /**
525
+ * Partitions an array into groups by one or more field values, date
526
+ * buckets, or a custom key function - single-level or hierarchical
527
+ * (subgroups within groups).
528
+ *
529
+ * @remarks
530
+ * - **Fan-out, not partitioning.** If a level's resolved value for an item
531
+ * is an array (e.g. a `tags` field, or a `custom` level's `getKey`
532
+ * returning multiple keys), the item is placed in *every* matching group
533
+ * at that level, not just one. Summed item counts across sibling groups
534
+ * can therefore exceed the original array length - this reflects genuine
535
+ * multi-group membership, not a bug.
536
+ * - **Two distinct synthetic buckets**, both customizable via
537
+ * {@link UseGroupingOptions}: `unknownGroupLabel` (default `"Unknown"`)
538
+ * for items whose value at a level is missing, blank, or otherwise
539
+ * unresolvable; `ungroupedGroupLabel` (default `"Ungrouped"`) for the
540
+ * single group returned when no grouping is applied at all. The actual
541
+ * labels in use are returned as `unknownGroupKey`/`ungroupedGroupKey` -
542
+ * compare against those rather than hardcoding the default strings, in
543
+ * case they've been customized.
544
+ * - **Validation is dev/prod-split**, same convention as this library's
545
+ * other hooks: a malformed level (missing/empty `field`, a non-function
546
+ * `getKey`, an invalid `bucket`) throws immediately in development, but
547
+ * is rejected silently (falling back to the previous/empty grouping) in
548
+ * production. A `custom` level's `getKey` throwing, or returning
549
+ * something other than `string | string[] | null | undefined`, follows
550
+ * the same split - see {@link GroupByCustomLevel.getKey}.
551
+ * - **`groupedRecord`/`groupKeys`/`totalGroups`/`getGroup`/`getGroupItems`
552
+ * are all top-level only** - for anything below the first grouping
553
+ * level, traverse a `Group`'s `subGroups` directly via `groupedArray`.
554
+ * - Order of top-level (and each nested level's) groups follows first
555
+ * appearance in `items`, not any particular sort - re-sort `groupedArray`
556
+ * yourself if a specific order is needed.
557
+ *
558
+ * @typeParam T - The type of each item in `data`.
559
+ * @param data - The items to group. Defaults to `[]`.
560
+ * @param initialGroupBy - Grouping level(s) applied at mount. Omit for no
561
+ * initial grouping.
562
+ * @param options - See {@link UseGroupingOptions}.
563
+ * @returns The current grouping and the actions to change it. See
564
+ * {@link UseGroupingReturn}.
565
+ *
566
+ * @example
567
+ * Single-level, by a plain field:
568
+ * ```tsx
569
+ * const { groupedArray } = useGrouping(users, "department");
570
+ * // groupedArray: [{ key: "Engineering", items: [...] }, { key: "Sales", items: [...] }, ...]
571
+ * ```
572
+ *
573
+ * @example
574
+ * Hierarchical, by department then a date bucket:
575
+ * ```tsx
576
+ * const { groupedArray, changeGroupBy } = useGrouping(orders);
577
+ *
578
+ * changeGroupBy([
579
+ * "region",
580
+ * { type: "date", field: "placedAt", bucket: "month" },
581
+ * ]);
582
+ * // groupedArray: [{ key: "EMEA", items: [...], subGroups: [{ key: "2026-08", ... }] }, ...]
583
+ * ```
584
+ */
585
+ export declare function useGrouping<T = unknown>(data?: T[], initialGroupBy?: GroupByLevel<T> | GroupByLevel<T>[], options?: UseGroupingOptions): UseGroupingReturn<T>;
125
586
  export type SelectionId = string | number;
126
- export type Primitive = string | number | boolean | bigint | symbol | null | undefined;
127
- 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>}`;
128
- export type Path<T> = T extends object ? {
129
- [K in keyof T & (string | number)]: PathImpl<K, T[K]>;
130
- }[keyof T & (string | number)] : never;
131
- 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;
132
587
  /**
133
588
  * All dot-notation paths of `T` whose resolved value is assignable to `TId`.
134
589
  *
@@ -355,38 +810,395 @@ export interface UseMultipleSelectionReturn<T = SelectionId, TId extends Selecti
355
810
  * ```
356
811
  */
357
812
  export declare function useMultipleSelection<T = SelectionId, TId extends SelectionId = SelectionId>(options?: UseMultipleSelectionOptions<T, TId>): UseMultipleSelectionReturn<T, TId>;
358
- export interface UseOrderReturn<T> {
359
- orderedItems: T[];
360
- moveUp: (index: number) => void;
361
- moveDown: (index: number) => void;
362
- canMoveUp: (index: number) => boolean;
363
- canMoveDown: (index: number) => boolean;
364
- moveToTop: (index: number) => void;
365
- moveToBottom: (index: number) => void;
366
- move: (fromIndex: number, toIndex: number) => void;
367
- swap: (indexA: number, indexB: number) => void;
813
+ /**
814
+ * The shape of every `target` parameter across {@link UseOrderReturn}'s
815
+ * methods - a plain array index when no `field` is configured, or either an
816
+ * index *or* a full item once one is (letting callers resolve an item's
817
+ * current position for themselves instead of tracking indices by hand).
818
+ *
819
+ * @remarks
820
+ * Deliberately gated behind `WithField` rather than always allowing `T`:
821
+ * `useOrder`'s base (no-`field`) overload can be called with `T = number`
822
+ * (ordering a plain array of numbers), where allowing an item argument too
823
+ * would make a bare `number` genuinely ambiguous - "index `5`" or "the item
824
+ * `5`"? Restricting item-based targeting to the `field`-configured overload
825
+ * (see {@link UseOrderFieldOptions}) sidesteps that ambiguity entirely,
826
+ * since `field` only makes sense for object items in the first place.
827
+ *
828
+ * @typeParam T - The item shape.
829
+ * @typeParam WithField - Whether `field` was configured - see {@link UseOrderFieldOptions}.
830
+ */
831
+ export type OrderTarget<T, WithField extends boolean> = WithField extends true ? number | T : number;
832
+ /**
833
+ * Options shared by both {@link useOrder} overloads, with or without `field`
834
+ * configured.
835
+ *
836
+ * @typeParam T - The item shape.
837
+ */
838
+ export interface UseOrderBaseOptions<T> {
839
+ /**
840
+ * Marks an item as un-movable and un-displaceable - see
841
+ * {@link UseOrderReturn} for exactly which operations this blocks, and
842
+ * how.
843
+ *
844
+ * @param item - The item to check.
845
+ * @param index - That item's current index.
846
+ * @returns `true` if `item` should be locked in place.
847
+ */
848
+ isDisabled?: (item: T, index: number) => boolean;
849
+ }
850
+ /**
851
+ * Options for the `field`-configured overload of {@link useOrder}, which
852
+ * additionally accepts full items (not just indices) as move targets.
853
+ *
854
+ * @typeParam T - The item shape.
855
+ */
856
+ export interface UseOrderFieldOptions<T> extends UseOrderBaseOptions<T> {
857
+ /**
858
+ * A dot-path into `T`, used to resolve an item's id whenever a target is
859
+ * given as an item rather than a raw index - see {@link OrderTarget}.
860
+ */
861
+ field: Path<T> | (string & {});
862
+ }
863
+ /**
864
+ * Return value of {@link useOrder}.
865
+ *
866
+ * @typeParam T - The item shape.
867
+ * @typeParam WithField - Whether `field` was configured - see {@link OrderTarget}.
868
+ */
869
+ export interface UseOrderReturn<T, WithField extends boolean = false> {
870
+ /** The items in their current order. */
871
+ orderedItems: readonly T[];
872
+ /**
873
+ * Moves the item at `target` one position earlier (toward index `0`).
874
+ * @remarks No-ops at the start of the list, or if `target`'s item or the item before it is disabled.
875
+ */
876
+ movePrevious: (target: OrderTarget<T, WithField>) => void;
877
+ /**
878
+ * Moves the item at `target` one position later (toward the end).
879
+ * @remarks No-ops at the end of the list, or if `target`'s item or the item after it is disabled.
880
+ */
881
+ moveNext: (target: OrderTarget<T, WithField>) => void;
882
+ /** Whether {@link UseOrderReturn.movePrevious} would currently have an effect for `target`. */
883
+ canMovePrevious: (target: OrderTarget<T, WithField>) => boolean;
884
+ /** Whether {@link UseOrderReturn.moveNext} would currently have an effect for `target`. */
885
+ canMoveNext: (target: OrderTarget<T, WithField>) => boolean;
886
+ /**
887
+ * Moves the item at `target` to the very start of the list.
888
+ * @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.
889
+ */
890
+ moveToStart: (target: OrderTarget<T, WithField>) => void;
891
+ /**
892
+ * Moves the item at `target` to the very end of the list.
893
+ * @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.
894
+ */
895
+ moveToEnd: (target: OrderTarget<T, WithField>) => void;
896
+ /**
897
+ * Moves the item at `from` to the position at `to`, shifting everything
898
+ * in between by one slot.
899
+ * @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.
900
+ */
901
+ move: (from: OrderTarget<T, WithField>, to: OrderTarget<T, WithField>) => void;
902
+ /**
903
+ * Exchanges the positions of the items at `a` and `b`.
904
+ * @remarks No-ops if `a` and `b` resolve to the same index, or if either item is disabled.
905
+ */
906
+ swap: (a: OrderTarget<T, WithField>, b: OrderTarget<T, WithField>) => void;
907
+ /** Restores the order to the value `initialItems` had at mount - see {@link useOrder}'s remarks. */
368
908
  resetOrder: () => void;
369
- replaceOrder: (newOrderedItems: T[]) => void;
909
+ /** Replaces the entire order with exactly these items. */
910
+ replaceOrder: (newOrderedItems: readonly T[]) => void;
370
911
  }
371
- export declare function useOrder<T>(initialItems?: T[]): UseOrderReturn<T>;
912
+ /**
913
+ * Manages the order of a list of items - drag-to-reorder UIs, "move
914
+ * up"/"move down" controls, manual sort overrides layered on top of a base
915
+ * sort.
916
+ *
917
+ * @remarks
918
+ * - Uncontrolled only for now - controlled mode is planned separately and
919
+ * will be added without breaking this signature.
920
+ * - SSR-safe: performs no DOM/window access; `initialItems` must be
921
+ * deterministic between server and client renders to avoid hydration
922
+ * mismatches.
923
+ * - All returned callbacks are manually memoized with `useCallback` so this
924
+ * hook is safe to use even in codebases **without** the React Compiler.
925
+ * - Two overloads, picked by whether `field` is configured: without it,
926
+ * every method's `target` parameter is a plain `number` index; with it,
927
+ * `target` also accepts a full item, resolved to its current index via
928
+ * `field`. See {@link OrderTarget} for why this is gated behind `field`
929
+ * rather than always allowed.
930
+ * - `isDisabled` blocks differently depending on the operation. For the
931
+ * pairwise operations (`movePrevious`, `moveNext`, `swap`), *both* items
932
+ * involved must be movable, since both change position. For the
933
+ * repositioning operations (`moveToStart`, `moveToEnd`, `move`), only the
934
+ * item being moved is checked - items it displaces shift by one slot but
935
+ * are never asked to swap places, so their own `isDisabled` state isn't
936
+ * consulted for the move to proceed.
937
+ * - `resetOrder` restores the value `initialItems` had at mount, not
938
+ * whatever it is on the current render - a later change to the
939
+ * `initialItems` prop doesn't retroactively change what `resetOrder`
940
+ * restores to.
941
+ *
942
+ * @typeParam T - The item shape.
943
+ * @param initialItems - The items to manage, in their starting order.
944
+ * @param options - See {@link UseOrderBaseOptions} / {@link UseOrderFieldOptions}.
945
+ * @returns The current order and the actions to change it. See {@link UseOrderReturn}.
946
+ *
947
+ * @example
948
+ * Index-only:
949
+ * ```tsx
950
+ * const { orderedItems, movePrevious, moveNext } = useOrder(["a", "b", "c"]);
951
+ * ```
952
+ *
953
+ * @example
954
+ * With `field`, so items themselves can be passed as move targets:
955
+ * ```tsx
956
+ * interface Row { id: string; label: string }
957
+ * const { orderedItems, movePrevious } = useOrder<Row>(rows, { field: "id" });
958
+ * // movePrevious(rows[2]) works directly - no manual index lookup needed
959
+ * ```
960
+ */
961
+ export declare function useOrder<T extends Record<string, unknown>>(initialItems: readonly T[] | undefined, options: UseOrderFieldOptions<T>): UseOrderReturn<T, true>;
962
+ export declare function useOrder<T>(initialItems?: readonly T[], options?: UseOrderBaseOptions<T>): UseOrderReturn<T, false>;
963
+ /** Return value of {@link usePagination}. */
372
964
  export interface UsePaginationReturn<T> {
965
+ /** The items for the current page - a slice of `data`, `pageSize` items long (fewer on the last page if `totalCount` doesn't divide evenly). */
373
966
  pageItems: T[];
967
+ /**
968
+ * Current page, 1-based.
969
+ * @remarks Always clamped into `[1, totalPages]` for display, even if
970
+ * the underlying position becomes momentarily out of range (e.g. `data`
971
+ * shrinks). The clamp is display-only - see the `currentPageIndex` note
972
+ * in the implementation for why the real position isn't lost.
973
+ */
974
+ pageNumber: number;
975
+ /** Items per page. */
374
976
  pageSize: number;
375
- pageIndex: number;
977
+ /** `Math.max(1, Math.ceil(totalCount / pageSize))` - always at least `1`, even for an empty `data`. */
376
978
  totalPages: number;
377
- canPrevious: boolean;
378
- canNext: boolean;
979
+ /** `data.length` (after the array-safety check) - the un-paginated item count. */
980
+ totalCount: number;
981
+ /** 1-based index of the first item on the current page, for a "Showing X-Y of Z" display. `0` when `totalCount` is `0`. */
982
+ startIndex: number;
983
+ /** 1-based index of the last item on the current page. `0` when `totalCount` is `0`. */
984
+ endIndex: number;
985
+ /** Whether {@link UsePaginationReturn.previousPage} would move anywhere. */
986
+ canPreviousPage: boolean;
987
+ /** Whether {@link UsePaginationReturn.nextPage} would move anywhere. */
988
+ canNextPage: boolean;
989
+ /** Moves to the next page, if any. No-op (not a warning) on the last page - this is routine UI usage, not caller error. */
379
990
  nextPage: () => void;
991
+ /** Moves to the previous page, if any. No-op on the first page. */
380
992
  previousPage: () => void;
993
+ /** Jumps to page `1`. */
381
994
  goToFirstPage: () => void;
995
+ /** Jumps to the last page. */
382
996
  goToLastPage: () => void;
383
- goToPage: (newPageIndex: number) => void;
997
+ /**
998
+ * Jumps to a specific page.
999
+ * @remarks A well-formed but out-of-range page (e.g. `999` on a
1000
+ * 5-page list) is clamped to the nearest valid page, not rejected -
1001
+ * only a malformed value (non-integer, `< 1`, `NaN`, etc.) is treated
1002
+ * as caller error. See {@link isPositiveInteger}.
1003
+ */
1004
+ goToPage: (newPageNumber: number) => void;
1005
+ /**
1006
+ * Changes `pageSize`.
1007
+ * @remarks `pageNumber` is left untouched here - it re-bounds itself
1008
+ * automatically against the new `totalPages` on the next render.
1009
+ */
384
1010
  changePageSize: (newPageSize: number) => void;
385
- resetPageIndex: () => void;
1011
+ /** 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. */
1012
+ resetPageNumber: () => void;
1013
+ /** Restores `pageSize` to the value passed as `initialPageSize` at mount. */
386
1014
  resetPageSize: () => void;
1015
+ /** Restores both `pageNumber` and `pageSize` to their mount-time initial values, in one update. */
387
1016
  resetPagination: () => void;
388
1017
  }
389
- export declare function usePagination<T>(data: T[] | undefined, initialPageSize: number, initialPageIndex?: number): UsePaginationReturn<T>;
1018
+ /**
1019
+ * Paginates an array client-side - slices `data` into pages and exposes the
1020
+ * navigation state and actions to move between them.
1021
+ *
1022
+ * @remarks
1023
+ * - **Validation is dev/prod-split**: passing a malformed page number or
1024
+ * page size (non-integer, `< 1`, `NaN`, etc.) to the constructor or any
1025
+ * mutator throws immediately in development (surfacing the bug fast), but
1026
+ * silently falls back to the last valid value in production, rather than
1027
+ * crashing a real user's session over a caller mistake. See
1028
+ * {@link isPositiveInteger}.
1029
+ * - **Out-of-range is different from malformed.** `goToPage(999)` on a
1030
+ * 5-page list isn't an error - it's clamped to the last page. Only
1031
+ * structurally invalid input (see above) is treated as a mistake.
1032
+ * - **`pageNumber` is 1-based** in this public API; page count/index math
1033
+ * is kept 0-based internally.
1034
+ * - **Reset targets are frozen at mount.** `resetPageNumber`/`resetPageSize`/
1035
+ * `resetPagination` always restore the `initialPageNumber`/`initialPageSize`
1036
+ * values as they were on the very first render - passing different values
1037
+ * to `usePagination` on a later render does not change what reset
1038
+ * restores to.
1039
+ * - **No manual/server-side pagination mode** - `data` is always assumed to
1040
+ * be the complete, un-paginated dataset, sliced client-side.
1041
+ *
1042
+ * @typeParam T - The type of each item in `data`.
1043
+ * @param data - The full, un-paginated array. Defaults to `[]`.
1044
+ * @param initialPageSize - Items per page at mount.
1045
+ * @defaultValue initialPageSize `10`
1046
+ * @param initialPageNumber - Starting page (1-based) at mount.
1047
+ * @defaultValue initialPageNumber `1`
1048
+ * @returns The current page's items, position, and the actions to
1049
+ * navigate/resize/reset. See {@link UsePaginationReturn}.
1050
+ *
1051
+ * @example
1052
+ * ```tsx
1053
+ * const { pageItems, pageNumber, totalPages, nextPage, previousPage } =
1054
+ * usePagination(rows, 20);
1055
+ *
1056
+ * return (
1057
+ * <>
1058
+ * {pageItems.map((row) => <Row key={row.id} {...row} />)}
1059
+ * <button onClick={previousPage}>Prev</button>
1060
+ * <span>{pageNumber} / {totalPages}</span>
1061
+ * <button onClick={nextPage}>Next</button>
1062
+ * </>
1063
+ * );
1064
+ * ```
1065
+ */
1066
+ export declare function usePagination<T>(data?: T[], initialPageSize?: number, initialPageNumber?: number): UsePaginationReturn<T>;
1067
+ /** The id type `usePin` operates on by default - a raw `string` or `number`. */
1068
+ export type PinId = string | number;
1069
+ /**
1070
+ * Options for {@link usePin}, in either of its two modes - id-only (`T`
1071
+ * defaults to `TId`) or object mode (`T` is a distinct item shape, `TId`
1072
+ * its resolved id type).
1073
+ *
1074
+ * @remarks
1075
+ * The conditional shape is what makes `field` required in object mode and
1076
+ * disallowed in id-only mode, entirely at the type level - see
1077
+ * {@link usePin}'s remarks for why.
1078
+ *
1079
+ * @typeParam T - The item shape, or `TId` itself for id-only mode.
1080
+ * @typeParam TId - The id type.
1081
+ */
1082
+ export type UsePinOptions<T = PinId, TId extends PinId = PinId> = T extends TId ? {
1083
+ /** The full list of pinnable items - backs `pinnedItems`/`unpinnedItems`, and is the default target for `pinAll` when it's called with no argument. */
1084
+ items?: readonly T[];
1085
+ /** Ids pinned from the start - frozen at mount, see {@link usePin}'s remarks. */
1086
+ initialPinnedIds?: readonly TId[];
1087
+ /** The maximum number of ids that can be pinned at once. `undefined` means unlimited. */
1088
+ maxPins?: number;
1089
+ } : {
1090
+ /** The full list of pinnable items - backs `pinnedItems`/`unpinnedItems`, and is the default target for `pinAll` when it's called with no argument. */
1091
+ items: readonly T[];
1092
+ /** 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. */
1093
+ field: Path<T> | (string & {});
1094
+ /** Ids pinned from the start - frozen at mount, see {@link usePin}'s remarks. */
1095
+ initialPinnedIds?: readonly TId[];
1096
+ /** The maximum number of ids that can be pinned at once. `undefined` means unlimited. */
1097
+ maxPins?: number;
1098
+ };
1099
+ /**
1100
+ * Return value of {@link usePin}.
1101
+ *
1102
+ * @typeParam T - The item shape, or `TId` itself for id-only mode.
1103
+ * @typeParam TId - The id type.
1104
+ */
1105
+ export interface UsePinReturn<T = PinId, TId extends PinId = PinId> {
1106
+ /** The currently pinned ids. */
1107
+ pinnedIds: readonly TId[];
1108
+ /** The subset of `items` that are currently pinned. */
1109
+ pinnedItems: readonly T[];
1110
+ /** The subset of `items` that are not currently pinned. */
1111
+ unpinnedItems: readonly T[];
1112
+ /** `pinnedIds.length`. */
1113
+ pinnedCount: number;
1114
+ /** Whether anything at all is pinned. */
1115
+ hasPins: boolean;
1116
+ /** Whether `pinnedCount` has reached `maxPins`. */
1117
+ isAtMaxLimit: boolean;
1118
+ /** The current pin limit - `Number.MAX_SAFE_INTEGER` when unset/unlimited. */
1119
+ maxPins: number;
1120
+ /**
1121
+ * Pins `itemOrId`.
1122
+ * @remarks No-ops if it's already pinned, or if `maxPins` has been reached.
1123
+ */
1124
+ pin: (itemOrId: TId | T) => void;
1125
+ /** Unpins `itemOrId`. Always allowed, even past `maxPins` - removal never needs to check the limit. */
1126
+ unpin: (itemOrId: TId | T) => void;
1127
+ /**
1128
+ * Pins `itemOrId` if it isn't pinned, unpins it if it is.
1129
+ * @remarks The pin-direction is blocked once `maxPins` is reached; the unpin-direction never is.
1130
+ */
1131
+ togglePin: (itemOrId: TId | T) => void;
1132
+ /** Whether `itemOrId` is currently pinned. */
1133
+ isPinned: (itemOrId: TId | T) => boolean;
1134
+ /** 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`. */
1135
+ canPin: (itemOrId: TId | T) => boolean;
1136
+ /** Unpins everything. */
1137
+ clearPins: () => void;
1138
+ /** Restores the pinned set to the value `initialPinnedIds` had at mount - see {@link usePin}'s remarks. */
1139
+ resetPins: () => void;
1140
+ /** Replaces the entire pinned set with exactly these ids/items, truncated to the first `maxPins` if it exceeds the current limit. */
1141
+ replacePins: (newPinnedItems: readonly TId[] | readonly T[]) => void;
1142
+ /**
1143
+ * Pins every id/item given, or every item in `items` if called with no
1144
+ * argument.
1145
+ * @remarks If `maxPins` is reached partway through, whatever already fit stays pinned rather than the whole call being rejected.
1146
+ */
1147
+ pinAll: (itemsArray?: readonly TId[] | readonly T[]) => void;
1148
+ /** Unpins every id/item given, or unpins everything if called with no argument. */
1149
+ unpinAll: (itemsArray?: readonly TId[] | readonly T[]) => void;
1150
+ /** Changes `maxPins` to a new value. Does not retroactively unpin anything already over the new limit. */
1151
+ changeMaxPins: (newMaxPins: number) => void;
1152
+ /** Restores `maxPins` to the value it had at mount. */
1153
+ resetMaxPins: () => void;
1154
+ }
1155
+ /**
1156
+ * Manages a pinned/starred subset of a list - pinned rows in a table,
1157
+ * favorited items, "keep at top" behavior.
1158
+ *
1159
+ * @remarks
1160
+ * - Uncontrolled only for now - controlled mode is planned separately and
1161
+ * will be added without breaking this signature.
1162
+ * - SSR-safe: performs no DOM/window access; `initialPinnedIds` must be
1163
+ * deterministic between server and client renders to avoid hydration
1164
+ * mismatches.
1165
+ * - All returned callbacks are manually memoized with `useCallback` so this
1166
+ * hook is safe to use even in codebases **without** the React Compiler.
1167
+ * - Two modes, picked by whether `T` is assignable to `TId`: id-only
1168
+ * (default) - `itemOrId` parameters only ever receive raw ids, `field`
1169
+ * is disallowed; or object mode - `<Row, string>` plus a required
1170
+ * `field`, letting `itemOrId` parameters take a full item too. See
1171
+ * {@link UsePinOptions}.
1172
+ * - `maxPins` truncation always keeps the *first* ids/items and warns in
1173
+ * dev about the rest - this applies at mount (`initialPinnedIds`),
1174
+ * `replacePins`, and `pinAll`.
1175
+ * - `resetPins` and `resetMaxPins` are independent - resetting one doesn't
1176
+ * touch the other, and each restores exactly the value its own option
1177
+ * had at mount, not whatever it is on the current render.
1178
+ *
1179
+ * @typeParam T - The item shape, or `TId` itself for id-only mode.
1180
+ * @typeParam TId - The id type.
1181
+ * @param options - See {@link UsePinOptions}.
1182
+ * @returns The current pinned state and the actions to change it. See {@link UsePinReturn}.
1183
+ *
1184
+ * @example
1185
+ * Id-only:
1186
+ * ```tsx
1187
+ * const { pinnedIds, pin, isPinned } = usePin({ initialPinnedIds: ["row-1"] });
1188
+ * ```
1189
+ *
1190
+ * @example
1191
+ * Object mode, with a pin limit:
1192
+ * ```tsx
1193
+ * interface Row { id: string; label: string }
1194
+ * const { pinnedItems, pin, canPin } = usePin<Row, string>({
1195
+ * items: rows,
1196
+ * field: "id",
1197
+ * maxPins: 5,
1198
+ * });
1199
+ * ```
1200
+ */
1201
+ export declare function usePin<T = PinId, TId extends PinId = PinId>(options?: UsePinOptions<T, TId>): UsePinReturn<T, TId>;
390
1202
  /**
391
1203
  * Configuration options for {@link useSingleSelection}.
392
1204
  *
@@ -493,47 +1305,230 @@ export interface UseSingleSelectionReturn<TId extends SelectionId = SelectionId>
493
1305
  * ```
494
1306
  */
495
1307
  export declare function useSingleSelection<TId extends SelectionId = SelectionId>(options?: UseSingleSelectionOptions<TId>): UseSingleSelectionReturn<TId>;
1308
+ /** The comparison strategy for a sort key - selects which built-in comparator is used, or `custom` for a caller-supplied one. */
496
1309
  export type SortType = "numeric" | "alphabetical" | "alphanumeric" | "boolean" | "date" | "basic" | "custom";
1310
+ /**
1311
+ * How a missing value (per each sort type's own definition of "missing" -
1312
+ * e.g. non-numeric for `numeric`, unparseable for `date`) is positioned
1313
+ * relative to present values.
1314
+ *
1315
+ * @remarks
1316
+ * - `"first"`/`"last"` are **absolute positions** - they never flip with
1317
+ * sort direction, which is the entire reason to choose them over `-1`/`1`.
1318
+ * - `-1`/`1` **simulate a real extreme value** (very small / very large
1319
+ * respectively) - unlike `"first"`/`"last"`, these DO flip with
1320
+ * direction, exactly as a genuine value at that extreme would.
1321
+ *
1322
+ * @defaultValue `"last"`
1323
+ */
1324
+ export type SortUndefinedOption = "first" | "last" | -1 | 1;
1325
+ /** Options shared by every sort type, regardless of comparator. */
497
1326
  export interface BaseSortOptions {
1327
+ /**
1328
+ * Sort direction.
1329
+ * @defaultValue `false` (ascending)
1330
+ */
498
1331
  desc?: boolean;
1332
+ /**
1333
+ * When set, `toggleSort` cycles asc -> desc -> asc, skipping the
1334
+ * "remove this sort key" step it would otherwise land on after desc.
1335
+ * @defaultValue `false`
1336
+ */
499
1337
  disableSortRemoval?: boolean;
1338
+ /**
1339
+ * Flips the effective sort direction without changing `desc` itself -
1340
+ * useful for a column whose "natural" order is descending (e.g. a
1341
+ * priority field where higher should sort first by default).
1342
+ * @defaultValue `false`
1343
+ */
500
1344
  invertSorting?: boolean;
501
- sortUndefined?: "first" | "last" | -1 | 1;
1345
+ /** See {@link SortUndefinedOption}. */
1346
+ sortUndefined?: SortUndefinedOption;
502
1347
  }
1348
+ /** Fields common to every {@link SortConfig}, regardless of `type`. */
503
1349
  export interface BaseSortConfig extends BaseSortOptions {
1350
+ /** Unique identifier for this sort key - used for lookups (`getSort`, `getSortDirection`, etc.) and to distinguish sort keys in a multi-key `sorts` array. */
504
1351
  id: string;
1352
+ /** 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. */
505
1353
  field?: string;
506
1354
  }
1355
+ /**
1356
+ * A single sort key's full configuration.
1357
+ *
1358
+ * @remarks
1359
+ * Each `type` has its own dedicated union arm (rather than several type
1360
+ * literals sharing one arm) so that {@link SortOptionsForType} - which
1361
+ * extracts a single type's own options via `Extract<SortConfig, { type: T }>`
1362
+ * - resolves correctly per type. Grouping literals under one arm would
1363
+ * make that extraction silently fail for every type in the group.
1364
+ */
507
1365
  export type SortConfig = BaseSortConfig & ({
508
- type: "numeric" | "boolean" | "date" | "basic";
1366
+ type: "numeric";
1367
+ } | {
1368
+ type: "boolean";
1369
+ } | {
1370
+ type: "date";
1371
+ dateGranularity?: "day" | "instant";
509
1372
  } | {
510
- type: "alphabetical" | "alphanumeric";
1373
+ type: "basic";
1374
+ } | {
1375
+ type: "alphabetical";
1376
+ caseSensitive?: boolean;
1377
+ } | {
1378
+ type: "alphanumeric";
511
1379
  caseSensitive?: boolean;
512
1380
  } | {
513
1381
  type: "custom";
514
1382
  compare: (a: unknown, b: unknown) => number;
515
1383
  });
1384
+ /** All active sort keys, in priority order - `sorts[0]` is the primary sort, later entries only break ties left by earlier ones. */
516
1385
  export type SortState = SortConfig[];
1386
+ /**
1387
+ * A single sort type's own options, with `id`/`type`/`field` (which every
1388
+ * type shares, and which `toggleSort` already takes as separate arguments)
1389
+ * stripped out. Used to type `toggleSort`'s `options` parameter, narrowed
1390
+ * to only the options relevant to the specific `type` being toggled.
1391
+ */
517
1392
  export type SortOptionsForType<T extends SortType> = Omit<Extract<SortConfig, {
518
1393
  type: T;
519
1394
  }>, "id" | "type" | "field">;
1395
+ /**
1396
+ * The update payload type for `updateSortConfig`.
1397
+ *
1398
+ * @remarks
1399
+ * Preserves per-type shape correlation at the type level - e.g. TypeScript
1400
+ * will reject `{ dateGranularity: "day" }` paired with a `type` that isn't
1401
+ * `"date"`. This is best-effort compile-time guidance, not a full
1402
+ * guarantee: a *merge* of an update into an existing config can still land
1403
+ * on a structurally invalid combination that the type system alone can't
1404
+ * catch (partial updates flatten across a union in ways whole-object
1405
+ * construction doesn't). {@link isSortConfigShapeValid}, run on the merged
1406
+ * result inside `applySortUpdate`, is the actual runtime backstop.
1407
+ */
1408
+ export type SortConfigUpdate = DistributivePartial<DistributiveOmit<SortConfig, "id">>;
1409
+ /** Options for {@link useSort}. */
1410
+ export interface UseSortOptions {
1411
+ /**
1412
+ * Defers the sort recomputation (via `useDeferredValue`) so changing
1413
+ * `sorts` doesn't block a more urgent update. Only `sorts` is deferred -
1414
+ * `data` is not.
1415
+ * @defaultValue `false`
1416
+ */
1417
+ defer?: boolean;
1418
+ }
1419
+ /** Return value of {@link useSort}. */
520
1420
  export interface UseSortReturn<T> {
1421
+ /** `data`, sorted by every entry in `sorts`, applied in priority order. Same reference as `data` (no copy, no sort) when `sorts` is empty. */
521
1422
  sortedItems: T[];
1423
+ /** The currently-active sort keys, in priority order. */
522
1424
  sorts: SortState;
1425
+ /** `sorts.length`. */
1426
+ sortCount: number;
1427
+ /** `sortCount > 0`. */
1428
+ hasSorts: boolean;
1429
+ /** Adds a new sort key, or replaces the existing one with the same `id`. */
523
1430
  upsertSorts: (sort: SortConfig) => void;
1431
+ /** Removes one sort key by `id`, or several at once by passing an array of ids. */
524
1432
  removeSort: (id: string | string[]) => void;
1433
+ /** Clears every sort key - equivalent to `replaceSorts([])`. */
525
1434
  clearSorts: () => void;
1435
+ /** 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. */
526
1436
  resetSorts: () => void;
1437
+ /** Replaces the entire `sorts` array at once. */
527
1438
  replaceSorts: (sorts: SortState) => void;
1439
+ /**
1440
+ * Cycles a sort key through asc -> desc -> (reset to asc, or remove
1441
+ * entirely) - the standard three-state table-header sort interaction.
1442
+ *
1443
+ * @remarks
1444
+ * - Whether the last step resets to asc or removes the sort key
1445
+ * entirely depends on `disableSortRemoval` (checked on both the
1446
+ * existing config and the newly-passed `options`).
1447
+ * - `options.multi` (`false` by default) controls whether toggling
1448
+ * this key adds/updates it alongside any other active sort keys
1449
+ * (`true`), or replaces the entire `sorts` array with just this one
1450
+ * key (`false`) - the usual "click a column to sort by only that
1451
+ * column, shift-click to add a secondary sort" pattern.
1452
+ * - `type` must be supplied on every call, even for an already-active
1453
+ * sort key - this hook has no independent memory of a key's type
1454
+ * beyond what's in `sorts` itself.
1455
+ */
528
1456
  toggleSort: <TType extends SortType>(id: string, type: TType, options?: SortOptionsForType<TType> & {
529
1457
  multi?: boolean;
530
1458
  field?: string;
531
1459
  }) => void;
1460
+ /**
1461
+ * Partially updates an existing sort key's config by `id`.
1462
+ * @remarks An unknown `id`, or an update that would produce a
1463
+ * structurally invalid config for its `type`, is rejected - see
1464
+ * {@link isSortConfigShapeValid}.
1465
+ */
1466
+ updateSortConfig: (id: string, partialConfig: SortConfigUpdate) => void;
1467
+ /** Looks up a sort key's full config by `id`. `undefined` if no such key exists. */
1468
+ getSort: (id: string) => SortConfig | undefined;
1469
+ /** `"asc"`/`"desc"` for an active sort key, `undefined` if `id` isn't currently sorted. */
532
1470
  getSortDirection: (id: string) => "asc" | "desc" | undefined;
1471
+ /** What `toggleSort(id, ...)` would transition `id` to next, without actually calling it - useful for rendering the right sort-direction icon before the user clicks. */
533
1472
  getNextSortingOrder: (id: string) => "asc" | "desc" | "none";
1473
+ /** This sort key's 1-based priority among active sort keys (`1` = primary), or `undefined` if `id` isn't currently sorted. */
534
1474
  getSortIndex: (id: string) => number | undefined;
535
1475
  }
536
- export declare function useSort<T>(data?: T[], initialSorts?: SortState): UseSortReturn<T>;
1476
+ /**
1477
+ * Sorts an array by one or more keys - single-column or multi-column,
1478
+ * priority determined by array order.
1479
+ *
1480
+ * @remarks
1481
+ * - **Multi-key sort**: `sorts[0]` is the primary sort; later entries in
1482
+ * the array only break ties left unresolved by earlier ones - the same
1483
+ * convention as `Array.prototype.sort` with a compound comparator, or a
1484
+ * spreadsheet's "sort by, then by" dialog.
1485
+ * - **Validation is dev/prod-split**, uniformly across every mutator that
1486
+ * can produce a structurally invalid config (`toggleSort`,
1487
+ * `updateSortConfig`), and across an unresolvable sort key encountered
1488
+ * during sorting itself (unknown `type`, or a `custom` sort missing its
1489
+ * `compare` function): throws immediately in development, but degrades
1490
+ * gracefully in production (an invalid mutation is rejected/no-op; an
1491
+ * unresolvable sort key at compute time is simply skipped, later keys
1492
+ * still apply).
1493
+ * - **`sortUndefined`'s `"first"`/`"last"` are absolute positions**, never
1494
+ * affected by `desc`/`invertSorting`; `-1`/`1` simulate a real extreme
1495
+ * value and do flip with direction - see {@link SortUndefinedOption}.
1496
+ * - **`resetSorts` is frozen at mount** - it restores `initialSorts` as it
1497
+ * was on the very first render, not whatever value that argument holds
1498
+ * on a later render.
1499
+ * - **Id lookups are O(1)**, backed by a `Map` built once per `sorts`
1500
+ * change, not a linear scan per call - safe to call `getSort`/
1501
+ * `getSortDirection`/etc. once per rendered column header without a
1502
+ * performance concern.
1503
+ *
1504
+ * @typeParam T - The type of each item in `data`.
1505
+ * @param data - The items to sort. Defaults to `[]`.
1506
+ * @param initialSorts - Sort keys applied at mount. Defaults to `[]` (no sorting).
1507
+ * @param options - See {@link UseSortOptions}.
1508
+ * @returns The sorted items and the current sort state, plus the actions
1509
+ * to change it. See {@link UseSortReturn}.
1510
+ *
1511
+ * @example
1512
+ * Single column, via a table header click handler:
1513
+ * ```tsx
1514
+ * const { sortedItems, getSortDirection, toggleSort } = useSort(rows);
1515
+ *
1516
+ * <th onClick={() => toggleSort("name", "alphabetical")}>
1517
+ * Name {getSortDirection("name") === "asc" ? "▲" : "▼"}
1518
+ * </th>
1519
+ * ```
1520
+ *
1521
+ * @example
1522
+ * Multi-key, set directly:
1523
+ * ```tsx
1524
+ * const { sortedItems, replaceSorts } = useSort(orders, [
1525
+ * { id: "status", type: "alphabetical" },
1526
+ * { id: "placedAt", type: "date", desc: true },
1527
+ * ]);
1528
+ * // sorted by status first; same-status orders sorted by placedAt, newest first
1529
+ * ```
1530
+ */
1531
+ export declare function useSort<T>(data?: T[], initialSorts?: SortState, options?: UseSortOptions): UseSortReturn<T>;
537
1532
  /**
538
1533
  * Restricts `field` to a **top-level** key of `T` whose value is assignable to `TId`.
539
1534
  *
@@ -763,5 +1758,143 @@ export interface UseTreeSelectionReturn<T, TId extends SelectionId> {
763
1758
  * ```
764
1759
  */
765
1760
  export declare function useTreeSelection<T, TId extends SelectionId = SelectionId>(options: UseTreeSelectionOptions<T, TId>): UseTreeSelectionReturn<T, TId>;
1761
+ /** The id type `useVisibility` operates on by default - a raw `string` or `number`. */
1762
+ export type VisibilityId = string | number;
1763
+ /**
1764
+ * Options for {@link useVisibility}, in either of its two modes - id-only
1765
+ * (`T` defaults to `TId`) or object mode (`T` is a distinct item shape,
1766
+ * `TId` its resolved id type).
1767
+ *
1768
+ * @remarks
1769
+ * The conditional shape is what makes `field` required in object mode and
1770
+ * disallowed in id-only mode, entirely at the type level - see
1771
+ * {@link useVisibility}'s remarks for why.
1772
+ *
1773
+ * @typeParam T - The item shape, or `TId` itself for id-only mode.
1774
+ * @typeParam TId - The id type.
1775
+ */
1776
+ export type UseVisibilityOptions<T = VisibilityId, TId extends VisibilityId = VisibilityId> = T extends TId ? {
1777
+ /** 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. */
1778
+ items?: readonly T[];
1779
+ /** Ids visible from the start - frozen at mount, see {@link useVisibility}'s remarks. */
1780
+ initialVisibleIds?: readonly TId[];
1781
+ } : {
1782
+ /** 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. */
1783
+ items: readonly T[];
1784
+ /** 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. */
1785
+ field: Path<T> | (string & {});
1786
+ /** Ids visible from the start - frozen at mount, see {@link useVisibility}'s remarks. */
1787
+ initialVisibleIds?: readonly TId[];
1788
+ };
1789
+ /**
1790
+ * Return value of {@link useVisibility}.
1791
+ *
1792
+ * @typeParam T - The item shape, or `TId` itself for id-only mode.
1793
+ * @typeParam TId - The id type.
1794
+ */
1795
+ export interface UseVisibilityReturn<T = VisibilityId, TId extends VisibilityId = VisibilityId> {
1796
+ /** The currently visible ids. */
1797
+ visibleIds: readonly TId[];
1798
+ /** The subset of `items` that are currently visible. */
1799
+ visibleItems: readonly T[];
1800
+ /** The subset of `items` that are currently hidden. */
1801
+ hiddenItems: readonly T[];
1802
+ /** `visibleIds.length`. */
1803
+ visibleCount: number;
1804
+ /** `hiddenItems.length`. */
1805
+ hiddenCount: number;
1806
+ /** Whether anything at all is visible. */
1807
+ hasVisible: boolean;
1808
+ /** Whether `itemOrId` is currently visible. */
1809
+ isVisible: (itemOrId: TId | T) => boolean;
1810
+ /** Shows `itemOrId`. No-ops if it's already visible. */
1811
+ show: (itemOrId: TId | T) => void;
1812
+ /** Hides `itemOrId`. No-ops if it's already hidden. */
1813
+ hide: (itemOrId: TId | T) => void;
1814
+ /** Shows `itemOrId` if it's hidden, hides it if it's visible. */
1815
+ toggleVisibility: (itemOrId: TId | T) => void;
1816
+ /** 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. */
1817
+ showAll: (itemsArray?: readonly TId[] | readonly T[]) => void;
1818
+ /**
1819
+ * Hides every id/item given, or hides everything if called with no
1820
+ * argument.
1821
+ * @remarks Unlike {@link UseVisibilityReturn.showAll}, "no argument" means a blanket clear, not "act on the hook's own `items`" - see {@link useVisibility}'s remarks.
1822
+ */
1823
+ hideAll: (itemsArray?: readonly TId[] | readonly T[]) => void;
1824
+ /** Restores the visible set to the value `initialVisibleIds` had at mount - see {@link useVisibility}'s remarks. */
1825
+ resetVisibility: () => void;
1826
+ /** Replaces the entire visible set with exactly these ids/items. */
1827
+ replaceVisibility: (newVisibleItems: readonly TId[] | readonly T[]) => void;
1828
+ }
1829
+ /**
1830
+ * Manages which item(s) in a list are visible - show/hide toggles, column
1831
+ * visibility, filterable chip lists.
1832
+ *
1833
+ * @remarks
1834
+ * - Uncontrolled only for now - controlled mode is planned separately and
1835
+ * will be added without breaking this signature.
1836
+ * - SSR-safe: performs no DOM/window access; `initialVisibleIds` must be
1837
+ * deterministic between server and client renders to avoid hydration
1838
+ * mismatches.
1839
+ * - All returned callbacks are manually memoized with `useCallback` so this
1840
+ * hook is safe to use even in codebases **without** the React Compiler.
1841
+ * - Two modes, picked by whether `T` is assignable to `TId`: id-only
1842
+ * (default) - `itemOrId` parameters only ever receive raw ids, `field`
1843
+ * is disallowed; or object mode - `<Row, string>` plus a required
1844
+ * `field`, letting `itemOrId` parameters take a full item too. See
1845
+ * {@link UseVisibilityOptions}.
1846
+ * - `showAll`/`hideAll` intentionally differ in what "no argument" means:
1847
+ * `showAll()` acts on the hook's own `items` (additive - anything
1848
+ * already visible but no longer in `items` is left alone), while
1849
+ * `hideAll()` is a blanket clear regardless of `items` (so a
1850
+ * previously-visible id that's since fallen out of `items` doesn't stay
1851
+ * visible forever). Passing an explicit array to either scopes it to
1852
+ * just that subset.
1853
+ *
1854
+ * @typeParam T - The item shape, or `TId` itself for id-only mode.
1855
+ * @typeParam TId - The id type.
1856
+ * @param options - See {@link UseVisibilityOptions}.
1857
+ * @returns The current visibility state and the actions to change it. See {@link UseVisibilityReturn}.
1858
+ *
1859
+ * @example
1860
+ * Id-only:
1861
+ * ```tsx
1862
+ * const { isVisible, toggleVisibility } = useVisibility({
1863
+ * initialVisibleIds: ["col-name", "col-email"],
1864
+ * });
1865
+ * ```
1866
+ *
1867
+ * @example
1868
+ * Object mode:
1869
+ * ```tsx
1870
+ * interface Column { id: string; label: string }
1871
+ * const { visibleItems, hide, showAll } = useVisibility<Column, string>({
1872
+ * items: columns,
1873
+ * field: "id",
1874
+ * });
1875
+ * ```
1876
+ */
1877
+ export declare function useVisibility<T = VisibilityId, TId extends VisibilityId = VisibilityId>(options?: UseVisibilityOptions<T, TId>): UseVisibilityReturn<T, TId>;
1878
+ export interface FuzzySearchOptions {
1879
+ threshold?: number;
1880
+ caseSensitive?: boolean;
1881
+ matchStrategy?: "any" | "all";
1882
+ exactPhraseBonus?: boolean;
1883
+ }
1884
+ export interface IndexedToken {
1885
+ text: string;
1886
+ weight: number;
1887
+ }
1888
+ export interface FlatIndexedItem<T> {
1889
+ item: T;
1890
+ index: number;
1891
+ tokens: IndexedToken[];
1892
+ combinedFlatText: string;
1893
+ }
1894
+ export interface ScoredItem<T> {
1895
+ item: T;
1896
+ score: number;
1897
+ index: number;
1898
+ }
766
1899
 
767
1900
  export {};