@dloizides/ui-card-roster 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md ADDED
@@ -0,0 +1,7 @@
1
+ # Changelog
2
+
3
+ ## 0.1.0
4
+
5
+ - Initial release: `CardRoster` (groups, live card band, Reorder mode, ModalShell edit
6
+ sheet / wide split pane, uploading + upload-error + empty-group states), pure roster
7
+ helpers, `useRosterEditor`, `useRosterActions`.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024 dloizides
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,34 @@
1
+ # @dloizides/ui-card-roster
2
+
3
+ A generic grouped card roster editor for React Native / RN-web. The host supplies the
4
+ card body and the edit form as render props and every string as a prop; the package
5
+ owns the groups, the card band, Reorder mode, the edit sheet and the upload/empty states.
6
+
7
+ - **Groups**: add (`createGroup`), inline rename, remove (default: only when empty).
8
+ - **Live band**: a horizontal row of framed cards per group, each with an Edit button.
9
+ - **Reorder mode**: a separate mode with up/down buttons for cards and groups
10
+ (keyboard/switch reachable), animated with `@dloizides/ui-motion` `Reorder`.
11
+ - **Edit sheet slot**: `@dloizides/ui-layout` `ModalShell` below `wideBreakpoint`
12
+ (default `LAYOUT_COLLAPSE_BREAKPOINT`, 768); a split pane at/above it.
13
+ - **States**: per-item `busy` (uploading overlay) and `error` (+ retry) via
14
+ `getItemState`; empty-group message; roster-level `error` via ui-feedback `ErrorState`.
15
+ - 44x44 minimum hit box on every control; colours from the ui-feedback `UiProvider` theme.
16
+
17
+ ```tsx
18
+ <CardRoster
19
+ value={value} // { groups: [{ key, title, items: [{ id, ... }] }] }
20
+ onChange={setValue}
21
+ getItemName={(p) => p.name}
22
+ renderCard={(p) => <PersonCard person={p} />}
23
+ renderEditor={({ item, save, cancel, remove, isNew }) => <PersonForm ... />}
24
+ createItem={(groupKey) => ({ id: newId(), name: '' })}
25
+ createGroup={() => ({ key: newId(), title: FM('people.newGroup'), items: [] })}
26
+ getItemState={(p) => uploads[p.id]}
27
+ labels={labels} // CardRosterLabels — translated by the host
28
+ />
29
+ ```
30
+
31
+ Pure helpers (`addGroup`, `renameGroup`, `removeGroup`, `moveGroup`, `addItem`,
32
+ `updateItem`, `removeItem`, `moveItem`, `findItem`, `moveInArray`, `canMove`) return the
33
+ same reference on a no-op. Peer deps: `@dloizides/ui-feedback`, `@dloizides/ui-layout`,
34
+ `react`, `react-native`.
@@ -0,0 +1,208 @@
1
+ import React from 'react';
2
+
3
+ /** Anything with a stable id can live in a roster. */
4
+ interface RosterItem {
5
+ id: string;
6
+ }
7
+ /** One titled group of cards, e.g. a section of a list. */
8
+ interface RosterGroup<T extends RosterItem> {
9
+ key: string;
10
+ title: string;
11
+ items: T[];
12
+ }
13
+ /** The whole controlled value of a `<CardRoster>`. */
14
+ interface RosterValue<T extends RosterItem> {
15
+ groups: RosterGroup<T>[];
16
+ }
17
+ /** Transient per-item state the HOST owns (an upload in flight, an upload that failed). */
18
+ interface RosterItemState {
19
+ busy?: boolean;
20
+ error?: string;
21
+ }
22
+ /** What the edit-sheet render prop receives. */
23
+ interface RosterEditorContext<T extends RosterItem> {
24
+ item: T;
25
+ groupKey: string;
26
+ /** `true` when the item came from `createItem` and is not in the value yet. */
27
+ isNew: boolean;
28
+ /** Writes the item into the value (add or update) and closes the editor. */
29
+ save: (item: T) => void;
30
+ cancel: () => void;
31
+ /** Removes an existing item (no-op for a new one) and closes the editor. */
32
+ remove: () => void;
33
+ }
34
+ /**
35
+ * Every visible string and accessibility text. The package ships NO copy: the host
36
+ * passes translated strings. Functions receive the item/group name for a11y labels.
37
+ */
38
+ interface CardRosterLabels {
39
+ edit: string;
40
+ add: string;
41
+ rename: string;
42
+ remove: string;
43
+ reorder: string;
44
+ reorderHint: string;
45
+ doneReordering: string;
46
+ doneReorderingHint: string;
47
+ addGroup: string;
48
+ addGroupHint: string;
49
+ renameGroup: (title: string) => string;
50
+ renameGroupHint: string;
51
+ groupTitleInput: string;
52
+ groupTitleInputHint: string;
53
+ removeGroup: (title: string) => string;
54
+ removeGroupHint: string;
55
+ moveGroupUp: (title: string) => string;
56
+ moveGroupDown: (title: string) => string;
57
+ moveGroupHint: string;
58
+ addCard: (groupTitle: string) => string;
59
+ addCardHint: string;
60
+ editCard: (name: string) => string;
61
+ editCardHint: string;
62
+ moveCardUp: (name: string) => string;
63
+ moveCardDown: (name: string) => string;
64
+ moveCardHint: string;
65
+ emptyGroup: string;
66
+ editorTitleNew: string;
67
+ editorTitleEdit: string;
68
+ selectPrompt: string;
69
+ uploading: string;
70
+ retry: string;
71
+ retryHint: string;
72
+ }
73
+ /** A roster-level failure (e.g. the last save or upload failed). */
74
+ interface CardRosterError {
75
+ message: string;
76
+ title?: string;
77
+ onRetry?: () => void;
78
+ }
79
+ interface CardRosterProps<T extends RosterItem> {
80
+ value: RosterValue<T>;
81
+ onChange: (next: RosterValue<T>) => void;
82
+ /** The card body. The roster draws the frame, the edit button and the states. */
83
+ renderCard: (item: T, group: RosterGroup<T>) => React.ReactNode;
84
+ /** The edit form, shown in a sheet (narrow) or the right pane (wide). */
85
+ renderEditor: (context: RosterEditorContext<T>) => React.ReactNode;
86
+ /** Human name of an item, used in accessibility labels. */
87
+ getItemName: (item: T) => string;
88
+ labels: CardRosterLabels;
89
+ /** Enables the per-group Add button; returns a fresh unsaved item. */
90
+ createItem?: (groupKey: string) => T;
91
+ /** Enables the Add group button; returns a fresh group (host owns key generation). */
92
+ createGroup?: () => RosterGroup<T>;
93
+ /** Default: only an EMPTY group can be removed, so removal never drops cards. */
94
+ canRemoveGroup?: (group: RosterGroup<T>) => boolean;
95
+ getItemState?: (item: T) => RosterItemState | undefined;
96
+ onRetryItem?: (item: T) => void;
97
+ error?: CardRosterError;
98
+ /** Width at/above which the editor becomes a split pane. Default: ui-layout's breakpoint. */
99
+ wideBreakpoint?: number;
100
+ cardWidth?: number;
101
+ testID?: string;
102
+ }
103
+
104
+ /**
105
+ * CardRoster — a generic grouped card editor. Composed narrow-first: one column with the
106
+ * edit form in a sheet; at/above `wideBreakpoint` the form moves into a right-hand pane.
107
+ * The value is controlled; every edit is a pure helper from `utils/rosterOps`.
108
+ */
109
+
110
+ declare function CardRoster<T extends RosterItem>(props: CardRosterProps<T>): React.ReactElement;
111
+
112
+ /**
113
+ * Pure, immutable reducer helpers over a `RosterValue`. Every helper returns the SAME
114
+ * reference when the operation is a no-op (unknown key, out-of-range move, duplicate
115
+ * id), so callers can skip `onChange` with a reference check.
116
+ */
117
+
118
+ interface FoundItem<T extends RosterItem> {
119
+ group: RosterGroup<T>;
120
+ item: T;
121
+ index: number;
122
+ }
123
+ /** `true` when moving `index` by `delta` stays inside a list of `length`. */
124
+ declare function canMove(length: number, index: number, delta: number): boolean;
125
+ /** Moves one element; returns `list` itself when the move is out of range. */
126
+ declare function moveInArray<X>(list: X[], index: number, delta: number): X[];
127
+ declare function isGroupEmpty<T extends RosterItem>(group: RosterGroup<T>): boolean;
128
+ declare function findItem<T extends RosterItem>(value: RosterValue<T>, itemId: string): FoundItem<T> | undefined;
129
+ declare function addGroup<T extends RosterItem>(value: RosterValue<T>, group: RosterGroup<T>): RosterValue<T>;
130
+ /** Trims the title; an empty or unchanged title is a no-op. */
131
+ declare function renameGroup<T extends RosterItem>(value: RosterValue<T>, key: string, title: string): RosterValue<T>;
132
+ declare function removeGroup<T extends RosterItem>(value: RosterValue<T>, key: string): RosterValue<T>;
133
+ declare function moveGroup<T extends RosterItem>(value: RosterValue<T>, key: string, delta: number): RosterValue<T>;
134
+ /** Appends to the group; refuses an id already present anywhere in the roster. */
135
+ declare function addItem<T extends RosterItem>(value: RosterValue<T>, groupKey: string, item: T): RosterValue<T>;
136
+ declare function updateItem<T extends RosterItem>(value: RosterValue<T>, groupKey: string, item: T): RosterValue<T>;
137
+ declare function removeItem<T extends RosterItem>(value: RosterValue<T>, groupKey: string, itemId: string): RosterValue<T>;
138
+ declare function moveItem<T extends RosterItem>(value: RosterValue<T>, groupKey: string, itemId: string, delta: number): RosterValue<T>;
139
+
140
+ /**
141
+ * Pure edit-session logic: which item the editor shows, and how a save lands.
142
+ * Kept out of the hook so the rules are unit-tested without React.
143
+ */
144
+
145
+ /** What the user opened: an existing item (`itemId`) or a new one (`itemId: null`). */
146
+ interface EditTarget {
147
+ groupKey: string;
148
+ itemId: string | null;
149
+ }
150
+ interface EditSession<T extends RosterItem> {
151
+ item: T;
152
+ groupKey: string;
153
+ isNew: boolean;
154
+ }
155
+ /** `true` at/above the breakpoint: the editor renders as a split pane, not a sheet. */
156
+ declare function isSplitPane(width: number, breakpoint: number): boolean;
157
+ /**
158
+ * Resolves the target against the CURRENT value. An existing item that has since
159
+ * disappeared (removed elsewhere) resolves to `null`, which closes the editor; an
160
+ * item moved to another group resolves to its new group.
161
+ */
162
+ declare function resolveEditSession<T extends RosterItem>(value: RosterValue<T>, target: EditTarget | null, draft: T | null): EditSession<T> | null;
163
+ /** Applies a save: a new item is appended, an existing one replaced in place. */
164
+ declare function commitEdit<T extends RosterItem>(value: RosterValue<T>, session: EditSession<T>, next: T): RosterValue<T>;
165
+
166
+ interface UseRosterEditorResult<T extends RosterItem> {
167
+ /** `null` when the editor is closed. */
168
+ context: RosterEditorContext<T> | null;
169
+ /** Id of the existing item being edited (for the active-card highlight). */
170
+ activeItemId: string | null;
171
+ openEdit: (groupKey: string, itemId: string) => void;
172
+ openNew: (groupKey: string) => void;
173
+ close: () => void;
174
+ }
175
+ /** Owns the edit target + the unsaved draft of a new item; writes through `onChange`. */
176
+ declare function useRosterEditor<T extends RosterItem>(value: RosterValue<T>, onChange: (next: RosterValue<T>) => void, createItem?: (groupKey: string) => T): UseRosterEditorResult<T>;
177
+
178
+ interface RosterActions<T extends RosterItem> {
179
+ addGroup: (group: RosterGroup<T>) => void;
180
+ renameGroup: (key: string, title: string) => void;
181
+ removeGroup: (key: string) => void;
182
+ moveGroup: (key: string, delta: number) => void;
183
+ moveItem: (groupKey: string, itemId: string, delta: number) => void;
184
+ }
185
+ /** Binds the pure helpers to the controlled value; a no-op never calls `onChange`. */
186
+ declare function useRosterActions<T extends RosterItem>(value: RosterValue<T>, onChange: (next: RosterValue<T>) => void): RosterActions<T>;
187
+
188
+ /** Minimum hit box (CSS px) for every interactive element — mobile-first standard. */
189
+ declare const MIN_TOUCH_TARGET = 44;
190
+ declare const DEFAULT_CARD_WIDTH = 168;
191
+ /** Move deltas for the reorder helpers. */
192
+ declare const MOVE_UP = -1;
193
+ declare const MOVE_DOWN = 1;
194
+ declare const DEFAULT_TEST_ID = "card-roster";
195
+ /** Stable testIDs derived from the roster's root testID. */
196
+ declare const rosterTestIds: (base: string) => {
197
+ root: string;
198
+ reorderToggle: string;
199
+ addGroup: string;
200
+ error: string;
201
+ editor: string;
202
+ editorPlaceholder: string;
203
+ group: (key: string) => string;
204
+ card: (id: string) => string;
205
+ };
206
+ type RosterTestIds = ReturnType<typeof rosterTestIds>;
207
+
208
+ export { CardRoster, type CardRosterError, type CardRosterLabels, type CardRosterProps, DEFAULT_CARD_WIDTH, DEFAULT_TEST_ID, type EditSession, type EditTarget, type FoundItem, MIN_TOUCH_TARGET, MOVE_DOWN, MOVE_UP, type RosterActions, type RosterEditorContext, type RosterGroup, type RosterItem, type RosterItemState, type RosterTestIds, type RosterValue, type UseRosterEditorResult, addGroup, addItem, canMove, commitEdit, findItem, isGroupEmpty, isSplitPane, moveGroup, moveInArray, moveItem, removeGroup, removeItem, renameGroup, resolveEditSession, rosterTestIds, updateItem, useRosterActions, useRosterEditor };
@@ -0,0 +1,208 @@
1
+ import React from 'react';
2
+
3
+ /** Anything with a stable id can live in a roster. */
4
+ interface RosterItem {
5
+ id: string;
6
+ }
7
+ /** One titled group of cards, e.g. a section of a list. */
8
+ interface RosterGroup<T extends RosterItem> {
9
+ key: string;
10
+ title: string;
11
+ items: T[];
12
+ }
13
+ /** The whole controlled value of a `<CardRoster>`. */
14
+ interface RosterValue<T extends RosterItem> {
15
+ groups: RosterGroup<T>[];
16
+ }
17
+ /** Transient per-item state the HOST owns (an upload in flight, an upload that failed). */
18
+ interface RosterItemState {
19
+ busy?: boolean;
20
+ error?: string;
21
+ }
22
+ /** What the edit-sheet render prop receives. */
23
+ interface RosterEditorContext<T extends RosterItem> {
24
+ item: T;
25
+ groupKey: string;
26
+ /** `true` when the item came from `createItem` and is not in the value yet. */
27
+ isNew: boolean;
28
+ /** Writes the item into the value (add or update) and closes the editor. */
29
+ save: (item: T) => void;
30
+ cancel: () => void;
31
+ /** Removes an existing item (no-op for a new one) and closes the editor. */
32
+ remove: () => void;
33
+ }
34
+ /**
35
+ * Every visible string and accessibility text. The package ships NO copy: the host
36
+ * passes translated strings. Functions receive the item/group name for a11y labels.
37
+ */
38
+ interface CardRosterLabels {
39
+ edit: string;
40
+ add: string;
41
+ rename: string;
42
+ remove: string;
43
+ reorder: string;
44
+ reorderHint: string;
45
+ doneReordering: string;
46
+ doneReorderingHint: string;
47
+ addGroup: string;
48
+ addGroupHint: string;
49
+ renameGroup: (title: string) => string;
50
+ renameGroupHint: string;
51
+ groupTitleInput: string;
52
+ groupTitleInputHint: string;
53
+ removeGroup: (title: string) => string;
54
+ removeGroupHint: string;
55
+ moveGroupUp: (title: string) => string;
56
+ moveGroupDown: (title: string) => string;
57
+ moveGroupHint: string;
58
+ addCard: (groupTitle: string) => string;
59
+ addCardHint: string;
60
+ editCard: (name: string) => string;
61
+ editCardHint: string;
62
+ moveCardUp: (name: string) => string;
63
+ moveCardDown: (name: string) => string;
64
+ moveCardHint: string;
65
+ emptyGroup: string;
66
+ editorTitleNew: string;
67
+ editorTitleEdit: string;
68
+ selectPrompt: string;
69
+ uploading: string;
70
+ retry: string;
71
+ retryHint: string;
72
+ }
73
+ /** A roster-level failure (e.g. the last save or upload failed). */
74
+ interface CardRosterError {
75
+ message: string;
76
+ title?: string;
77
+ onRetry?: () => void;
78
+ }
79
+ interface CardRosterProps<T extends RosterItem> {
80
+ value: RosterValue<T>;
81
+ onChange: (next: RosterValue<T>) => void;
82
+ /** The card body. The roster draws the frame, the edit button and the states. */
83
+ renderCard: (item: T, group: RosterGroup<T>) => React.ReactNode;
84
+ /** The edit form, shown in a sheet (narrow) or the right pane (wide). */
85
+ renderEditor: (context: RosterEditorContext<T>) => React.ReactNode;
86
+ /** Human name of an item, used in accessibility labels. */
87
+ getItemName: (item: T) => string;
88
+ labels: CardRosterLabels;
89
+ /** Enables the per-group Add button; returns a fresh unsaved item. */
90
+ createItem?: (groupKey: string) => T;
91
+ /** Enables the Add group button; returns a fresh group (host owns key generation). */
92
+ createGroup?: () => RosterGroup<T>;
93
+ /** Default: only an EMPTY group can be removed, so removal never drops cards. */
94
+ canRemoveGroup?: (group: RosterGroup<T>) => boolean;
95
+ getItemState?: (item: T) => RosterItemState | undefined;
96
+ onRetryItem?: (item: T) => void;
97
+ error?: CardRosterError;
98
+ /** Width at/above which the editor becomes a split pane. Default: ui-layout's breakpoint. */
99
+ wideBreakpoint?: number;
100
+ cardWidth?: number;
101
+ testID?: string;
102
+ }
103
+
104
+ /**
105
+ * CardRoster — a generic grouped card editor. Composed narrow-first: one column with the
106
+ * edit form in a sheet; at/above `wideBreakpoint` the form moves into a right-hand pane.
107
+ * The value is controlled; every edit is a pure helper from `utils/rosterOps`.
108
+ */
109
+
110
+ declare function CardRoster<T extends RosterItem>(props: CardRosterProps<T>): React.ReactElement;
111
+
112
+ /**
113
+ * Pure, immutable reducer helpers over a `RosterValue`. Every helper returns the SAME
114
+ * reference when the operation is a no-op (unknown key, out-of-range move, duplicate
115
+ * id), so callers can skip `onChange` with a reference check.
116
+ */
117
+
118
+ interface FoundItem<T extends RosterItem> {
119
+ group: RosterGroup<T>;
120
+ item: T;
121
+ index: number;
122
+ }
123
+ /** `true` when moving `index` by `delta` stays inside a list of `length`. */
124
+ declare function canMove(length: number, index: number, delta: number): boolean;
125
+ /** Moves one element; returns `list` itself when the move is out of range. */
126
+ declare function moveInArray<X>(list: X[], index: number, delta: number): X[];
127
+ declare function isGroupEmpty<T extends RosterItem>(group: RosterGroup<T>): boolean;
128
+ declare function findItem<T extends RosterItem>(value: RosterValue<T>, itemId: string): FoundItem<T> | undefined;
129
+ declare function addGroup<T extends RosterItem>(value: RosterValue<T>, group: RosterGroup<T>): RosterValue<T>;
130
+ /** Trims the title; an empty or unchanged title is a no-op. */
131
+ declare function renameGroup<T extends RosterItem>(value: RosterValue<T>, key: string, title: string): RosterValue<T>;
132
+ declare function removeGroup<T extends RosterItem>(value: RosterValue<T>, key: string): RosterValue<T>;
133
+ declare function moveGroup<T extends RosterItem>(value: RosterValue<T>, key: string, delta: number): RosterValue<T>;
134
+ /** Appends to the group; refuses an id already present anywhere in the roster. */
135
+ declare function addItem<T extends RosterItem>(value: RosterValue<T>, groupKey: string, item: T): RosterValue<T>;
136
+ declare function updateItem<T extends RosterItem>(value: RosterValue<T>, groupKey: string, item: T): RosterValue<T>;
137
+ declare function removeItem<T extends RosterItem>(value: RosterValue<T>, groupKey: string, itemId: string): RosterValue<T>;
138
+ declare function moveItem<T extends RosterItem>(value: RosterValue<T>, groupKey: string, itemId: string, delta: number): RosterValue<T>;
139
+
140
+ /**
141
+ * Pure edit-session logic: which item the editor shows, and how a save lands.
142
+ * Kept out of the hook so the rules are unit-tested without React.
143
+ */
144
+
145
+ /** What the user opened: an existing item (`itemId`) or a new one (`itemId: null`). */
146
+ interface EditTarget {
147
+ groupKey: string;
148
+ itemId: string | null;
149
+ }
150
+ interface EditSession<T extends RosterItem> {
151
+ item: T;
152
+ groupKey: string;
153
+ isNew: boolean;
154
+ }
155
+ /** `true` at/above the breakpoint: the editor renders as a split pane, not a sheet. */
156
+ declare function isSplitPane(width: number, breakpoint: number): boolean;
157
+ /**
158
+ * Resolves the target against the CURRENT value. An existing item that has since
159
+ * disappeared (removed elsewhere) resolves to `null`, which closes the editor; an
160
+ * item moved to another group resolves to its new group.
161
+ */
162
+ declare function resolveEditSession<T extends RosterItem>(value: RosterValue<T>, target: EditTarget | null, draft: T | null): EditSession<T> | null;
163
+ /** Applies a save: a new item is appended, an existing one replaced in place. */
164
+ declare function commitEdit<T extends RosterItem>(value: RosterValue<T>, session: EditSession<T>, next: T): RosterValue<T>;
165
+
166
+ interface UseRosterEditorResult<T extends RosterItem> {
167
+ /** `null` when the editor is closed. */
168
+ context: RosterEditorContext<T> | null;
169
+ /** Id of the existing item being edited (for the active-card highlight). */
170
+ activeItemId: string | null;
171
+ openEdit: (groupKey: string, itemId: string) => void;
172
+ openNew: (groupKey: string) => void;
173
+ close: () => void;
174
+ }
175
+ /** Owns the edit target + the unsaved draft of a new item; writes through `onChange`. */
176
+ declare function useRosterEditor<T extends RosterItem>(value: RosterValue<T>, onChange: (next: RosterValue<T>) => void, createItem?: (groupKey: string) => T): UseRosterEditorResult<T>;
177
+
178
+ interface RosterActions<T extends RosterItem> {
179
+ addGroup: (group: RosterGroup<T>) => void;
180
+ renameGroup: (key: string, title: string) => void;
181
+ removeGroup: (key: string) => void;
182
+ moveGroup: (key: string, delta: number) => void;
183
+ moveItem: (groupKey: string, itemId: string, delta: number) => void;
184
+ }
185
+ /** Binds the pure helpers to the controlled value; a no-op never calls `onChange`. */
186
+ declare function useRosterActions<T extends RosterItem>(value: RosterValue<T>, onChange: (next: RosterValue<T>) => void): RosterActions<T>;
187
+
188
+ /** Minimum hit box (CSS px) for every interactive element — mobile-first standard. */
189
+ declare const MIN_TOUCH_TARGET = 44;
190
+ declare const DEFAULT_CARD_WIDTH = 168;
191
+ /** Move deltas for the reorder helpers. */
192
+ declare const MOVE_UP = -1;
193
+ declare const MOVE_DOWN = 1;
194
+ declare const DEFAULT_TEST_ID = "card-roster";
195
+ /** Stable testIDs derived from the roster's root testID. */
196
+ declare const rosterTestIds: (base: string) => {
197
+ root: string;
198
+ reorderToggle: string;
199
+ addGroup: string;
200
+ error: string;
201
+ editor: string;
202
+ editorPlaceholder: string;
203
+ group: (key: string) => string;
204
+ card: (id: string) => string;
205
+ };
206
+ type RosterTestIds = ReturnType<typeof rosterTestIds>;
207
+
208
+ export { CardRoster, type CardRosterError, type CardRosterLabels, type CardRosterProps, DEFAULT_CARD_WIDTH, DEFAULT_TEST_ID, type EditSession, type EditTarget, type FoundItem, MIN_TOUCH_TARGET, MOVE_DOWN, MOVE_UP, type RosterActions, type RosterEditorContext, type RosterGroup, type RosterItem, type RosterItemState, type RosterTestIds, type RosterValue, type UseRosterEditorResult, addGroup, addItem, canMove, commitEdit, findItem, isGroupEmpty, isSplitPane, moveGroup, moveInArray, moveItem, removeGroup, removeItem, renameGroup, resolveEditSession, rosterTestIds, updateItem, useRosterActions, useRosterEditor };