Audit fixes: a11y, gesture correctness, math edge cases
- Keyboard focus-visible ring on all controls (WCAG 2.4.7), suppressed for pointer grabs; focusRing prop to customize or disable - aria-orientation on track modes; data-dragging/-disabled/-readonly/ -focus-visible state attributes; disabled dims, readOnly drops the grab cursor - Escape cancels a drag and restores the pre-gesture value - Controlled mode no longer desyncs when the parent rejects onChange - Second concurrent pointer can no longer hijack a drag session; drags cancel when disabled/readOnly flips mid-gesture - Wheel: onChangeStart symmetry, settle flush on unsubscribe, dominant- axis direction (fixes macOS Shift+scroll reversal), ignored mid-drag - Shift+wheel and fine multipliers now work on stepped/values knobs - Fader/LEDFader track mapping inset-aligned with handle travel (no value jump when grabbing the cap); LEDFader zones sorted - roundTo no longer returns NaN for exponential-notation values; logTaper falls back to linear for invalid ranges; decimalsFromStep shaves float noise; findClosest([]) returns the input - Double-click reset brackets with onChangeStart/End - MetalKnob honors showValue/unit/format; VintageKnob omits them from its type; TickLabels extracted as a public primitive - SegmentDisplay: letters/hex charset, dash overflow instead of 888
This commit is contained in:
parent
60a02cb1db
commit
da352d3695
13 changed files with 378 additions and 82 deletions
|
|
@ -22,9 +22,41 @@ const CHAR_SEGMENTS: Record<string, SegmentKey[]> = {
|
|||
'8': ['a', 'b', 'c', 'd', 'e', 'f', 'g'],
|
||||
'9': ['a', 'b', 'c', 'd', 'f', 'g'],
|
||||
'-': ['g'],
|
||||
'_': ['d'],
|
||||
'=': ['g', 'd'],
|
||||
' ': [],
|
||||
// Letters, as far as seven segments allow — enough for hex, "OFF", "On",
|
||||
// "LP"/"HP", "Err", "dB" and friends.
|
||||
A: ['a', 'b', 'c', 'e', 'f', 'g'],
|
||||
b: ['c', 'd', 'e', 'f', 'g'],
|
||||
C: ['a', 'd', 'e', 'f'],
|
||||
c: ['d', 'e', 'g'],
|
||||
d: ['b', 'c', 'd', 'e', 'g'],
|
||||
E: ['a', 'd', 'e', 'f', 'g'],
|
||||
F: ['a', 'e', 'f', 'g'],
|
||||
G: ['a', 'c', 'd', 'e', 'f'],
|
||||
H: ['b', 'c', 'e', 'f', 'g'],
|
||||
h: ['c', 'e', 'f', 'g'],
|
||||
I: ['b', 'c'],
|
||||
J: ['b', 'c', 'd'],
|
||||
L: ['d', 'e', 'f'],
|
||||
n: ['c', 'e', 'g'],
|
||||
O: ['a', 'b', 'c', 'd', 'e', 'f'],
|
||||
o: ['c', 'd', 'e', 'g'],
|
||||
P: ['a', 'b', 'e', 'f', 'g'],
|
||||
q: ['a', 'b', 'c', 'f', 'g'],
|
||||
r: ['e', 'g'],
|
||||
S: ['a', 'c', 'd', 'f', 'g'],
|
||||
t: ['d', 'e', 'f', 'g'],
|
||||
U: ['b', 'c', 'd', 'e', 'f'],
|
||||
u: ['c', 'd', 'e'],
|
||||
y: ['b', 'c', 'd', 'f', 'g'],
|
||||
};
|
||||
|
||||
/** Resolve a character to its segments, trying the other case before giving up. */
|
||||
const segmentsFor = (ch: string): SegmentKey[] =>
|
||||
CHAR_SEGMENTS[ch] ?? CHAR_SEGMENTS[ch.toUpperCase()] ?? CHAR_SEGMENTS[ch.toLowerCase()] ?? [];
|
||||
|
||||
const H_HALF = 1.1; // half thickness
|
||||
const INSET = 0.45; // gap between adjacent segments
|
||||
|
||||
|
|
@ -78,7 +110,7 @@ export const renderSegmentText = (
|
|||
);
|
||||
continue;
|
||||
}
|
||||
const lit = new Set(CHAR_SEGMENTS[ch] ?? []);
|
||||
const lit = new Set(segmentsFor(ch));
|
||||
const digit: React.ReactNode[] = [];
|
||||
(Object.keys(SEGMENT_POINTS) as SegmentKey[]).forEach(seg => {
|
||||
const on = lit.has(seg);
|
||||
|
|
@ -117,6 +149,8 @@ export const formatForDisplay = (
|
|||
): string => {
|
||||
let text = value.toFixed(decimals);
|
||||
const cellCount = (s: string) => s.replace(/\./g, '').length;
|
||||
if (cellCount(text) > digits) text = ''.padStart(digits, '8'); // overflow
|
||||
// Doesn't fit: dashes, the hardware overflow convention. All-8s would read
|
||||
// as a legitimate value.
|
||||
if (cellCount(text) > digits) text = ''.padStart(digits, '-');
|
||||
return text.padStart(digits + (text.includes('.') ? 1 : 0), ' ');
|
||||
};
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue