@dloizides/ui-carousel 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 +15 -0
- package/LICENSE +21 -0
- package/README.md +72 -0
- package/dist/index.d.mts +159 -0
- package/dist/index.d.ts +159 -0
- package/dist/index.js +380 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +370 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +112 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## 0.1.0
|
|
4
|
+
|
|
5
|
+
First release.
|
|
6
|
+
|
|
7
|
+
- `CardDeck` — one card at a time with a verdict row, a back control and a progress line.
|
|
8
|
+
- `useDeck` — all deck logic, no rendering: advance, complete, answered-once, keyboard routing.
|
|
9
|
+
- `Card` / `RankBar` — the presentation halves, both themed from the UiProvider.
|
|
10
|
+
- `Pressable` throughout (never `TouchableOpacity`, whose hover callbacks RN-web drops), with a
|
|
11
|
+
test that greps `src` so the regression cannot land silently.
|
|
12
|
+
- A single in-flight latch guards every verdict emission, so a keyboard activation plus the click
|
|
13
|
+
a browser synthesizes from it counts once. Asserted at both the hook and the component level.
|
|
14
|
+
- Keyboard: `1`..`9` rank, `Backspace` / `ArrowLeft` step back. Fully operable without a pointer.
|
|
15
|
+
- No colour literals in `src`, enforced by a test.
|
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,72 @@
|
|
|
1
|
+
# @dloizides/ui-carousel
|
|
2
|
+
|
|
3
|
+
A themable, brand-agnostic React Native (RN-web) **card deck with per-card ranking**: one card at
|
|
4
|
+
a time, a row of verdict buttons, and a keyboard path that reaches every one of them.
|
|
5
|
+
|
|
6
|
+
```tsx
|
|
7
|
+
import { CardDeck } from '@dloizides/ui-carousel';
|
|
8
|
+
|
|
9
|
+
<CardDeck
|
|
10
|
+
cards={cards}
|
|
11
|
+
verdicts={['loved', 'liked', 'bounced', 'never']}
|
|
12
|
+
onVerdict={(cardId, verdict) => record(cardId, verdict)}
|
|
13
|
+
onComplete={() => goToResults()}
|
|
14
|
+
testID="quiz-deck"
|
|
15
|
+
labels={{
|
|
16
|
+
verdictLabels: { loved: FM('quiz.loved'), liked: FM('quiz.liked') },
|
|
17
|
+
verdictHints: { loved: FM('quiz.lovedHint'), liked: FM('quiz.likedHint') },
|
|
18
|
+
backLabel: FM('quiz.back'),
|
|
19
|
+
backHint: FM('quiz.backHint'),
|
|
20
|
+
progress: (position, total) => FM('quiz.progress', String(position), String(total)),
|
|
21
|
+
}}
|
|
22
|
+
/>;
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## Why this is a package and not app code
|
|
26
|
+
|
|
27
|
+
Two RN-web traps have cost this estate real defects. Both are handled once, here:
|
|
28
|
+
|
|
29
|
+
1. **`TouchableOpacity` silently drops `onHoverIn` / `onHoverOut` on RN-web.** A hover affordance
|
|
30
|
+
written that way is dead on the web build with nothing in the console to find it by. This
|
|
31
|
+
package uses `Pressable` (via `@dloizides/ui-motion`'s `PressableScale`) and a test greps `src`
|
|
32
|
+
to prove `TouchableOpacity` never comes back.
|
|
33
|
+
2. **Keyboard activation does not arrive as `onPress`.** The browser can deliver the key event AND
|
|
34
|
+
synthesize a click from the same press, which lands two verdicts in one tick. Every emission
|
|
35
|
+
passes a single in-flight latch in `useDeck` that is released only after React commits the
|
|
36
|
+
advance, so the second path is a no-op. `useDeck.test.ts` asserts the double-fire case directly.
|
|
37
|
+
|
|
38
|
+
## Keyboard contract
|
|
39
|
+
|
|
40
|
+
| Key | Effect |
|
|
41
|
+
|---|---|
|
|
42
|
+
| `1` .. `9` | rank the current card with the nth verdict (**verdict order is the contract**) |
|
|
43
|
+
| `Backspace`, `ArrowLeft` | step back one card |
|
|
44
|
+
|
|
45
|
+
A card is ranked **exactly once**. Stepping back shows a ranked card, it does not reopen it, so
|
|
46
|
+
navigating back and forth cannot re-emit a verdict the caller has already recorded.
|
|
47
|
+
|
|
48
|
+
## Theming and copy
|
|
49
|
+
|
|
50
|
+
Colours come from the app's UiProvider theme (`@dloizides/ui-feedback`, fed by
|
|
51
|
+
`@dloizides/design-tokens`). There is not one colour literal in `src`, and a test enforces it.
|
|
52
|
+
|
|
53
|
+
The kit has no i18n. Every string arrives already localized through `labels`; a missing entry falls
|
|
54
|
+
back to the verdict id, which is machine-readable rather than untranslated English.
|
|
55
|
+
|
|
56
|
+
## Accessibility
|
|
57
|
+
|
|
58
|
+
Every interactive control carries `testID` + `accessibilityLabel` + `accessibilityHint`, and the
|
|
59
|
+
deck is fully operable by keyboard alone. A deck that only works by swipe is not accessible.
|
|
60
|
+
|
|
61
|
+
## Exports
|
|
62
|
+
|
|
63
|
+
`CardDeck` · `Card` · `RankBar` · `useDeck` · `DECK_TEST_ID_SUFFIX` · `digitToVerdictIndex`
|
|
64
|
+
· types `CardDeckProps`, `DeckCard`, `DeckLabels`, `UseDeckOptions`, `UseDeckResult`.
|
|
65
|
+
|
|
66
|
+
## Peer dependencies
|
|
67
|
+
|
|
68
|
+
`react` >= 18 · `react-native` >= 0.74 · `@dloizides/ui-feedback` >= 1.2 (the theme provider).
|
|
69
|
+
|
|
70
|
+
## License
|
|
71
|
+
|
|
72
|
+
MIT
|
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Public shapes for `@dloizides/ui-carousel`.
|
|
5
|
+
*
|
|
6
|
+
* `CardDeckProps` is fixed by the consuming spec. Every extra prop is OPTIONAL, so a
|
|
7
|
+
* caller that knows only the five required fields keeps compiling.
|
|
8
|
+
*/
|
|
9
|
+
/** One card in the deck. Presentation only: the deck never fetches anything. */
|
|
10
|
+
interface DeckCard {
|
|
11
|
+
id: string;
|
|
12
|
+
title: string;
|
|
13
|
+
imageUrl?: string;
|
|
14
|
+
subtitle?: string;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Caller-supplied, already-localized copy. The kit has no i18n of its own (apps own
|
|
18
|
+
* `FM()`), so every string the deck renders arrives from here. A missing entry falls
|
|
19
|
+
* back to the verdict id, which is machine-readable rather than English prose.
|
|
20
|
+
*/
|
|
21
|
+
interface DeckLabels {
|
|
22
|
+
/** Per-verdict button label, keyed by verdict id. */
|
|
23
|
+
verdictLabels?: Readonly<Record<string, string>>;
|
|
24
|
+
/** Per-verdict `accessibilityHint`, keyed by verdict id. */
|
|
25
|
+
verdictHints?: Readonly<Record<string, string>>;
|
|
26
|
+
/** Label for the "go back one card" control. */
|
|
27
|
+
backLabel?: string;
|
|
28
|
+
/** Hint for the "go back one card" control. */
|
|
29
|
+
backHint?: string;
|
|
30
|
+
/** Progress line, e.g. "3 / 12". Receives the 1-based position and the total. */
|
|
31
|
+
progress?: (position: number, total: number) => string;
|
|
32
|
+
/** Label announced on the card region itself. Receives the card title. */
|
|
33
|
+
cardLabel?: (title: string) => string;
|
|
34
|
+
/** Hint announced on the card region (how to rank it). */
|
|
35
|
+
cardHint?: string;
|
|
36
|
+
}
|
|
37
|
+
interface CardDeckProps {
|
|
38
|
+
cards: DeckCard[];
|
|
39
|
+
/** Ordered verdict ids. Keyboard 1..n selects the nth, so ORDER IS THE CONTRACT. */
|
|
40
|
+
verdicts: readonly string[];
|
|
41
|
+
onVerdict: (cardId: string, verdict: string) => void;
|
|
42
|
+
onComplete: () => void;
|
|
43
|
+
testID: string;
|
|
44
|
+
/** Already-localized copy. Optional: verdict ids are used when a string is missing. */
|
|
45
|
+
labels?: DeckLabels;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* CardDeck — a ranked card deck that is fully operable by keyboard alone.
|
|
50
|
+
*
|
|
51
|
+
* `1..n` rank the current card with the nth verdict, `Backspace` / `ArrowLeft` step
|
|
52
|
+
* back. The listener is attached to the document on web, so the deck works without
|
|
53
|
+
* the user having to tab into it first; every verdict still routes through the same
|
|
54
|
+
* `useDeck.submitVerdict`, which holds the single in-flight latch. That is what stops
|
|
55
|
+
* a browser-synthesized click from turning one key press into two verdicts.
|
|
56
|
+
*
|
|
57
|
+
* Colours come from the app UiProvider theme (`@dloizides/design-tokens`). There is not
|
|
58
|
+
* one colour literal in this package.
|
|
59
|
+
*/
|
|
60
|
+
|
|
61
|
+
declare const CardDeck: ({ cards, verdicts, onVerdict, onComplete, testID, labels, }: CardDeckProps) => React.ReactElement;
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Card — the presentation face of one deck entry.
|
|
65
|
+
*
|
|
66
|
+
* Deliberately NOT interactive: ranking happens in `RankBar`, so there is exactly one
|
|
67
|
+
* place a verdict can be emitted from. `imageUrl` is optional and a missing cover
|
|
68
|
+
* renders as a typographic card rather than a broken image box.
|
|
69
|
+
*/
|
|
70
|
+
|
|
71
|
+
interface CardProps {
|
|
72
|
+
card: DeckCard;
|
|
73
|
+
testID: string;
|
|
74
|
+
imageTestID: string;
|
|
75
|
+
/** Already-localized region label. Falls back to the card title. */
|
|
76
|
+
accessibilityLabel?: string;
|
|
77
|
+
/** Already-localized region hint telling the user how to rank. */
|
|
78
|
+
accessibilityHint?: string;
|
|
79
|
+
}
|
|
80
|
+
declare const Card: ({ card, testID, imageTestID, accessibilityLabel, accessibilityHint, }: CardProps) => React.ReactElement;
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* RankBar — the verdict buttons, the back control and the progress line.
|
|
84
|
+
*
|
|
85
|
+
* Built on `PressableScale` (a `Pressable`), NEVER `TouchableOpacity`: RN-web silently
|
|
86
|
+
* drops `onHoverIn` / `onHoverOut` on TouchableOpacity, so a hover affordance written
|
|
87
|
+
* that way is dead on the web build with no error to find it by.
|
|
88
|
+
*
|
|
89
|
+
* Every control carries testID + accessibilityLabel + accessibilityHint. The labels are
|
|
90
|
+
* caller-supplied and already localized; a missing one falls back to the verdict id.
|
|
91
|
+
*/
|
|
92
|
+
|
|
93
|
+
interface RankBarProps {
|
|
94
|
+
verdicts: readonly string[];
|
|
95
|
+
labels?: DeckLabels;
|
|
96
|
+
onSelect: (verdict: string) => void;
|
|
97
|
+
onBack: () => void;
|
|
98
|
+
canGoBack: boolean;
|
|
99
|
+
/** 1-based position of the current card. */
|
|
100
|
+
position: number;
|
|
101
|
+
total: number;
|
|
102
|
+
testID: string;
|
|
103
|
+
backTestID: string;
|
|
104
|
+
progressTestID: string;
|
|
105
|
+
verdictTestIDPrefix: string;
|
|
106
|
+
}
|
|
107
|
+
declare const RankBar: ({ verdicts, labels, onSelect, onBack, canGoBack, position, total, testID, backTestID, progressTestID, verdictTestIDPrefix, }: RankBarProps) => React.ReactElement;
|
|
108
|
+
|
|
109
|
+
interface UseDeckOptions {
|
|
110
|
+
cards: DeckCard[];
|
|
111
|
+
/** Ordered verdict ids. Keyboard 1..n selects the nth. */
|
|
112
|
+
verdicts: readonly string[];
|
|
113
|
+
onVerdict: (cardId: string, verdict: string) => void;
|
|
114
|
+
onComplete: () => void;
|
|
115
|
+
}
|
|
116
|
+
interface UseDeckResult {
|
|
117
|
+
/** 0-based position. Equals `cards.length` once the deck is finished. */
|
|
118
|
+
index: number;
|
|
119
|
+
/** The card awaiting a verdict, or `undefined` when the deck is finished. */
|
|
120
|
+
current: DeckCard | undefined;
|
|
121
|
+
total: number;
|
|
122
|
+
isComplete: boolean;
|
|
123
|
+
/** cardId to verdict, for every card ranked so far. */
|
|
124
|
+
answered: Readonly<Record<string, string>>;
|
|
125
|
+
/** Rank the current card. Ignored when it is already ranked or an emission is in flight. */
|
|
126
|
+
submitVerdict: (verdict: string) => void;
|
|
127
|
+
/** Step back one card. A no-op on the first card. */
|
|
128
|
+
goBack: () => void;
|
|
129
|
+
/** Route a raw key name from keydown. Returns true when the deck consumed it. */
|
|
130
|
+
handleKey: (key: string) => boolean;
|
|
131
|
+
/**
|
|
132
|
+
* Release the keyboard-activation latch. Bind to keyup: the click a browser
|
|
133
|
+
* synthesizes from a key press arrives BEFORE keyup, which is what makes this the
|
|
134
|
+
* window that suppresses it.
|
|
135
|
+
*/
|
|
136
|
+
releaseKeyboardActivation: () => void;
|
|
137
|
+
}
|
|
138
|
+
declare function useDeck({ cards, verdicts, onVerdict, onComplete }: UseDeckOptions): UseDeckResult;
|
|
139
|
+
|
|
140
|
+
/** Test ids, key names and layout metrics for the deck. Nothing here is a colour. */
|
|
141
|
+
/** Stable test id suffixes. Each is appended to the caller-supplied `testID`. */
|
|
142
|
+
declare const DECK_TEST_ID_SUFFIX: {
|
|
143
|
+
readonly card: "card";
|
|
144
|
+
readonly image: "card-image";
|
|
145
|
+
readonly rankBar: "rank-bar";
|
|
146
|
+
readonly verdict: "verdict";
|
|
147
|
+
readonly back: "back";
|
|
148
|
+
readonly progress: "progress";
|
|
149
|
+
readonly complete: "complete";
|
|
150
|
+
};
|
|
151
|
+
/** Keys that navigate rather than rank. */
|
|
152
|
+
declare const KEY_BACK = "Backspace";
|
|
153
|
+
declare const KEY_ARROW_LEFT = "ArrowLeft";
|
|
154
|
+
/** Digit keys 1..9 pick the nth verdict. The deck never exposes more than nine. */
|
|
155
|
+
declare const MAX_KEYBOARD_VERDICTS = 9;
|
|
156
|
+
/** Parse a 1..9 digit key into a 0-based verdict index, or -1 when it is not one. */
|
|
157
|
+
declare const digitToVerdictIndex: (key: string) => number;
|
|
158
|
+
|
|
159
|
+
export { Card, CardDeck, type CardDeckProps, type CardProps, DECK_TEST_ID_SUFFIX, type DeckCard, type DeckLabels, KEY_ARROW_LEFT, KEY_BACK, MAX_KEYBOARD_VERDICTS, RankBar, type RankBarProps, type UseDeckOptions, type UseDeckResult, digitToVerdictIndex, useDeck };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Public shapes for `@dloizides/ui-carousel`.
|
|
5
|
+
*
|
|
6
|
+
* `CardDeckProps` is fixed by the consuming spec. Every extra prop is OPTIONAL, so a
|
|
7
|
+
* caller that knows only the five required fields keeps compiling.
|
|
8
|
+
*/
|
|
9
|
+
/** One card in the deck. Presentation only: the deck never fetches anything. */
|
|
10
|
+
interface DeckCard {
|
|
11
|
+
id: string;
|
|
12
|
+
title: string;
|
|
13
|
+
imageUrl?: string;
|
|
14
|
+
subtitle?: string;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Caller-supplied, already-localized copy. The kit has no i18n of its own (apps own
|
|
18
|
+
* `FM()`), so every string the deck renders arrives from here. A missing entry falls
|
|
19
|
+
* back to the verdict id, which is machine-readable rather than English prose.
|
|
20
|
+
*/
|
|
21
|
+
interface DeckLabels {
|
|
22
|
+
/** Per-verdict button label, keyed by verdict id. */
|
|
23
|
+
verdictLabels?: Readonly<Record<string, string>>;
|
|
24
|
+
/** Per-verdict `accessibilityHint`, keyed by verdict id. */
|
|
25
|
+
verdictHints?: Readonly<Record<string, string>>;
|
|
26
|
+
/** Label for the "go back one card" control. */
|
|
27
|
+
backLabel?: string;
|
|
28
|
+
/** Hint for the "go back one card" control. */
|
|
29
|
+
backHint?: string;
|
|
30
|
+
/** Progress line, e.g. "3 / 12". Receives the 1-based position and the total. */
|
|
31
|
+
progress?: (position: number, total: number) => string;
|
|
32
|
+
/** Label announced on the card region itself. Receives the card title. */
|
|
33
|
+
cardLabel?: (title: string) => string;
|
|
34
|
+
/** Hint announced on the card region (how to rank it). */
|
|
35
|
+
cardHint?: string;
|
|
36
|
+
}
|
|
37
|
+
interface CardDeckProps {
|
|
38
|
+
cards: DeckCard[];
|
|
39
|
+
/** Ordered verdict ids. Keyboard 1..n selects the nth, so ORDER IS THE CONTRACT. */
|
|
40
|
+
verdicts: readonly string[];
|
|
41
|
+
onVerdict: (cardId: string, verdict: string) => void;
|
|
42
|
+
onComplete: () => void;
|
|
43
|
+
testID: string;
|
|
44
|
+
/** Already-localized copy. Optional: verdict ids are used when a string is missing. */
|
|
45
|
+
labels?: DeckLabels;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* CardDeck — a ranked card deck that is fully operable by keyboard alone.
|
|
50
|
+
*
|
|
51
|
+
* `1..n` rank the current card with the nth verdict, `Backspace` / `ArrowLeft` step
|
|
52
|
+
* back. The listener is attached to the document on web, so the deck works without
|
|
53
|
+
* the user having to tab into it first; every verdict still routes through the same
|
|
54
|
+
* `useDeck.submitVerdict`, which holds the single in-flight latch. That is what stops
|
|
55
|
+
* a browser-synthesized click from turning one key press into two verdicts.
|
|
56
|
+
*
|
|
57
|
+
* Colours come from the app UiProvider theme (`@dloizides/design-tokens`). There is not
|
|
58
|
+
* one colour literal in this package.
|
|
59
|
+
*/
|
|
60
|
+
|
|
61
|
+
declare const CardDeck: ({ cards, verdicts, onVerdict, onComplete, testID, labels, }: CardDeckProps) => React.ReactElement;
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Card — the presentation face of one deck entry.
|
|
65
|
+
*
|
|
66
|
+
* Deliberately NOT interactive: ranking happens in `RankBar`, so there is exactly one
|
|
67
|
+
* place a verdict can be emitted from. `imageUrl` is optional and a missing cover
|
|
68
|
+
* renders as a typographic card rather than a broken image box.
|
|
69
|
+
*/
|
|
70
|
+
|
|
71
|
+
interface CardProps {
|
|
72
|
+
card: DeckCard;
|
|
73
|
+
testID: string;
|
|
74
|
+
imageTestID: string;
|
|
75
|
+
/** Already-localized region label. Falls back to the card title. */
|
|
76
|
+
accessibilityLabel?: string;
|
|
77
|
+
/** Already-localized region hint telling the user how to rank. */
|
|
78
|
+
accessibilityHint?: string;
|
|
79
|
+
}
|
|
80
|
+
declare const Card: ({ card, testID, imageTestID, accessibilityLabel, accessibilityHint, }: CardProps) => React.ReactElement;
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* RankBar — the verdict buttons, the back control and the progress line.
|
|
84
|
+
*
|
|
85
|
+
* Built on `PressableScale` (a `Pressable`), NEVER `TouchableOpacity`: RN-web silently
|
|
86
|
+
* drops `onHoverIn` / `onHoverOut` on TouchableOpacity, so a hover affordance written
|
|
87
|
+
* that way is dead on the web build with no error to find it by.
|
|
88
|
+
*
|
|
89
|
+
* Every control carries testID + accessibilityLabel + accessibilityHint. The labels are
|
|
90
|
+
* caller-supplied and already localized; a missing one falls back to the verdict id.
|
|
91
|
+
*/
|
|
92
|
+
|
|
93
|
+
interface RankBarProps {
|
|
94
|
+
verdicts: readonly string[];
|
|
95
|
+
labels?: DeckLabels;
|
|
96
|
+
onSelect: (verdict: string) => void;
|
|
97
|
+
onBack: () => void;
|
|
98
|
+
canGoBack: boolean;
|
|
99
|
+
/** 1-based position of the current card. */
|
|
100
|
+
position: number;
|
|
101
|
+
total: number;
|
|
102
|
+
testID: string;
|
|
103
|
+
backTestID: string;
|
|
104
|
+
progressTestID: string;
|
|
105
|
+
verdictTestIDPrefix: string;
|
|
106
|
+
}
|
|
107
|
+
declare const RankBar: ({ verdicts, labels, onSelect, onBack, canGoBack, position, total, testID, backTestID, progressTestID, verdictTestIDPrefix, }: RankBarProps) => React.ReactElement;
|
|
108
|
+
|
|
109
|
+
interface UseDeckOptions {
|
|
110
|
+
cards: DeckCard[];
|
|
111
|
+
/** Ordered verdict ids. Keyboard 1..n selects the nth. */
|
|
112
|
+
verdicts: readonly string[];
|
|
113
|
+
onVerdict: (cardId: string, verdict: string) => void;
|
|
114
|
+
onComplete: () => void;
|
|
115
|
+
}
|
|
116
|
+
interface UseDeckResult {
|
|
117
|
+
/** 0-based position. Equals `cards.length` once the deck is finished. */
|
|
118
|
+
index: number;
|
|
119
|
+
/** The card awaiting a verdict, or `undefined` when the deck is finished. */
|
|
120
|
+
current: DeckCard | undefined;
|
|
121
|
+
total: number;
|
|
122
|
+
isComplete: boolean;
|
|
123
|
+
/** cardId to verdict, for every card ranked so far. */
|
|
124
|
+
answered: Readonly<Record<string, string>>;
|
|
125
|
+
/** Rank the current card. Ignored when it is already ranked or an emission is in flight. */
|
|
126
|
+
submitVerdict: (verdict: string) => void;
|
|
127
|
+
/** Step back one card. A no-op on the first card. */
|
|
128
|
+
goBack: () => void;
|
|
129
|
+
/** Route a raw key name from keydown. Returns true when the deck consumed it. */
|
|
130
|
+
handleKey: (key: string) => boolean;
|
|
131
|
+
/**
|
|
132
|
+
* Release the keyboard-activation latch. Bind to keyup: the click a browser
|
|
133
|
+
* synthesizes from a key press arrives BEFORE keyup, which is what makes this the
|
|
134
|
+
* window that suppresses it.
|
|
135
|
+
*/
|
|
136
|
+
releaseKeyboardActivation: () => void;
|
|
137
|
+
}
|
|
138
|
+
declare function useDeck({ cards, verdicts, onVerdict, onComplete }: UseDeckOptions): UseDeckResult;
|
|
139
|
+
|
|
140
|
+
/** Test ids, key names and layout metrics for the deck. Nothing here is a colour. */
|
|
141
|
+
/** Stable test id suffixes. Each is appended to the caller-supplied `testID`. */
|
|
142
|
+
declare const DECK_TEST_ID_SUFFIX: {
|
|
143
|
+
readonly card: "card";
|
|
144
|
+
readonly image: "card-image";
|
|
145
|
+
readonly rankBar: "rank-bar";
|
|
146
|
+
readonly verdict: "verdict";
|
|
147
|
+
readonly back: "back";
|
|
148
|
+
readonly progress: "progress";
|
|
149
|
+
readonly complete: "complete";
|
|
150
|
+
};
|
|
151
|
+
/** Keys that navigate rather than rank. */
|
|
152
|
+
declare const KEY_BACK = "Backspace";
|
|
153
|
+
declare const KEY_ARROW_LEFT = "ArrowLeft";
|
|
154
|
+
/** Digit keys 1..9 pick the nth verdict. The deck never exposes more than nine. */
|
|
155
|
+
declare const MAX_KEYBOARD_VERDICTS = 9;
|
|
156
|
+
/** Parse a 1..9 digit key into a 0-based verdict index, or -1 when it is not one. */
|
|
157
|
+
declare const digitToVerdictIndex: (key: string) => number;
|
|
158
|
+
|
|
159
|
+
export { Card, CardDeck, type CardDeckProps, type CardProps, DECK_TEST_ID_SUFFIX, type DeckCard, type DeckLabels, KEY_ARROW_LEFT, KEY_BACK, MAX_KEYBOARD_VERDICTS, RankBar, type RankBarProps, type UseDeckOptions, type UseDeckResult, digitToVerdictIndex, useDeck };
|