@expokit/cli 0.4.4 → 0.5.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/README.md +30 -3
- package/bin/expo-kit.js +749 -288
- package/bin/lib.mjs +310 -3
- package/package.json +1 -1
- package/registry/_docs/action-sheet.json +1 -1
- package/registry/_docs/alert.json +6 -0
- package/registry/_docs/bottom-sheet.json +6 -0
- package/registry/_docs/bullet.json +6 -0
- package/registry/_docs/dialog.json +1 -1
- package/registry/_docs/drawer.json +3 -3
- package/registry/_docs/glass-search-bar.json +1 -1
- package/registry/_docs/grain.json +6 -1
- package/registry/_docs/isometric-bar.json +6 -0
- package/registry/_docs/liquidglass-tab.json +1 -1
- package/registry/_docs/password-input.json +1 -1
- package/registry/_docs/pixel-bar.json +6 -0
- package/registry/_docs/polar-area.json +6 -0
- package/registry/_docs/qr-code-button.json +9 -3
- package/registry/_docs/scratch-card.json +7 -1
- package/registry/_docs/search-bar.json +1 -1
- package/registry/_docs/select.json +2 -2
- package/registry/_docs/slide-to-confirm.json +2 -2
- package/registry/_docs/spinner.json +1 -1
- package/registry/_docs/stateful-button.json +2 -2
- package/registry/_docs/swipe-deck.json +1 -1
- package/registry/_docs/time-picker.json +2 -2
- package/registry/_docs/timeline-chart.json +6 -0
- package/registry/_docs/toast.json +15 -3
- package/registry/_docs/treemap.json +6 -0
- package/registry/_docs/waterfall.json +6 -0
- package/registry/accordion/files/src/components/ui/accordion.tsx +6 -4
- package/registry/accordion/files/src/lib/kit.tsx +363 -11
- package/registry/accordion.json +1 -0
- package/registry/action-sheet/files/src/components/ui/action-sheet.tsx +43 -48
- package/registry/action-sheet/files/src/lib/kit.tsx +363 -11
- package/registry/action-sheet.json +1 -0
- package/registry/alert/files/src/components/ui/alert.tsx +19 -12
- package/registry/alert/files/src/lib/kit.tsx +363 -11
- package/registry/alert.json +3 -4
- package/registry/assistant-app.json +1 -0
- package/registry/avatar/files/src/components/ui/avatar.tsx +84 -66
- package/registry/avatar/files/src/lib/kit.tsx +363 -11
- package/registry/avatar.json +1 -0
- package/registry/badge/files/src/components/ui/badge.tsx +34 -34
- package/registry/badge/files/src/lib/kit.tsx +363 -11
- package/registry/badge.json +1 -0
- package/registry/bottom-nav/files/src/components/ui/bottom-nav.tsx +28 -27
- package/registry/bottom-nav/files/src/lib/kit.tsx +363 -11
- package/registry/bottom-nav.json +5 -0
- package/registry/bottom-sheet/files/src/components/ui/bottom-sheet.tsx +353 -289
- package/registry/bottom-sheet/files/src/lib/kit.tsx +363 -11
- package/registry/bottom-sheet.json +5 -1
- package/registry/bullet.json +1 -0
- package/registry/button/files/src/components/ui/button.tsx +18 -16
- package/registry/button/files/src/lib/kit.tsx +363 -11
- package/registry/button-3d/files/src/components/ui/button-3d.tsx +2 -2
- package/registry/button-3d/files/src/lib/kit.tsx +363 -11
- package/registry/button-3d.json +1 -0
- package/registry/button.json +1 -0
- package/registry/calendar/files/src/components/ui/calendar.tsx +74 -76
- package/registry/calendar/files/src/lib/kit.tsx +363 -11
- package/registry/calendar.json +1 -0
- package/registry/card/files/src/components/ui/card.tsx +3 -3
- package/registry/card/files/src/lib/kit.tsx +363 -11
- package/registry/card-stack.json +1 -0
- package/registry/card.json +1 -0
- package/registry/carousel.json +1 -0
- package/registry/chat.json +7 -0
- package/registry/checkbox/files/src/components/ui/checkbox.tsx +4 -4
- package/registry/checkbox/files/src/lib/kit.tsx +363 -11
- package/registry/checkbox.json +1 -0
- package/registry/checkout.json +1 -0
- package/registry/chip-group/files/src/components/ui/chip-group.tsx +22 -22
- package/registry/chip-group/files/src/lib/kit.tsx +363 -11
- package/registry/chip-group.json +1 -0
- package/registry/confetti/files/src/components/ui/confetti.tsx +15 -14
- package/registry/confetti.json +3 -6
- package/registry/coupon/files/src/components/ui/coupon.tsx +42 -42
- package/registry/coupon/files/src/components/ui/flip-card.tsx +6 -5
- package/registry/coupon/files/src/components/ui/tilt-card.tsx +76 -91
- package/registry/coupon/files/src/lib/kit.tsx +363 -11
- package/registry/coupon/files/src/lib/ticket-shape.tsx +54 -44
- package/registry/coupon.json +1 -0
- package/registry/cube-pager.json +1 -0
- package/registry/dashboard.json +1 -0
- package/registry/date-picker/files/src/components/ui/calendar.tsx +74 -76
- package/registry/date-picker/files/src/components/ui/date-picker.tsx +55 -56
- package/registry/date-picker/files/src/lib/kit.tsx +363 -11
- package/registry/date-picker.json +1 -0
- package/registry/dialog/files/src/components/ui/dialog.tsx +60 -56
- package/registry/dialog/files/src/lib/kit.tsx +363 -11
- package/registry/dialog.json +1 -0
- package/registry/donut-shop.json +1 -0
- package/registry/drawer/files/src/components/ui/drawer.tsx +73 -69
- package/registry/drawer/files/src/lib/kit.tsx +363 -11
- package/registry/drawer.json +1 -0
- package/registry/fab.json +1 -0
- package/registry/fintech-app.json +1 -0
- package/registry/flip-calendar/files/src/components/ui/flip-calendar.tsx +92 -86
- package/registry/flip-calendar/files/src/lib/kit.tsx +363 -11
- package/registry/flip-calendar.json +7 -0
- package/registry/flip-card/files/src/components/ui/flip-card.tsx +6 -5
- package/registry/flip-card/files/src/lib/kit.tsx +363 -11
- package/registry/flip-card.json +1 -0
- package/registry/floating-input/files/src/components/ui/floating-input.tsx +33 -33
- package/registry/floating-input/files/src/lib/kit.tsx +363 -11
- package/registry/floating-input.json +1 -0
- package/registry/food-app.json +1 -0
- package/registry/gift-card.json +1 -0
- package/registry/glass-button.json +5 -0
- package/registry/glass-nav-bar.json +1 -0
- package/registry/glass-search-bar.json +1 -0
- package/registry/glass-segmented.json +1 -0
- package/registry/glass-sheet.json +1 -0
- package/registry/glass-tab-bar.json +5 -0
- package/registry/glass.json +1 -0
- package/registry/grain/files/src/components/ui/grain.tsx +16 -14
- package/registry/grain.json +8 -6
- package/registry/inbox.json +1 -0
- package/registry/isometric-bar.json +1 -0
- package/registry/kit/files/src/lib/kit.tsx +363 -11
- package/registry/kit.json +1 -0
- package/registry/liquidglass-tab/files/src/app/_layout.tsx +0 -1
- package/registry/liquidglass-tab/files/src/components/GlassTabBar.tsx +23 -9
- package/registry/liquidglass-tab/files/src/components/TabScreen.tsx +2 -2
- package/registry/liquidglass-tab/files/src/lib/tab-icons.ts +1 -1
- package/registry/liquidglass-tab.json +1 -0
- package/registry/list-item/files/src/components/ui/list-item.tsx +42 -42
- package/registry/list-item/files/src/lib/kit.tsx +363 -11
- package/registry/list-item.json +1 -0
- package/registry/loyalty-card.json +1 -0
- package/registry/morphing-button-group.json +1 -0
- package/registry/number-ticker.json +1 -0
- package/registry/offers.json +1 -0
- package/registry/onboarding.json +1 -0
- package/registry/orb-reveal.json +1 -0
- package/registry/otp-input/files/src/components/ui/otp-input.tsx +48 -29
- package/registry/otp-input/files/src/lib/kit.tsx +363 -11
- package/registry/otp-input.json +1 -0
- package/registry/parallax-layers.json +1 -0
- package/registry/parallax-scroll.json +1 -0
- package/registry/password-input/files/src/components/ui/floating-input.tsx +33 -33
- package/registry/password-input/files/src/components/ui/password-input.tsx +29 -25
- package/registry/password-input/files/src/lib/kit.tsx +363 -11
- package/registry/password-input.json +1 -0
- package/registry/payment-card/files/src/components/ui/flip-card.tsx +6 -5
- package/registry/payment-card/files/src/components/ui/payment-card.tsx +17 -17
- package/registry/payment-card/files/src/components/ui/tilt-card.tsx +76 -91
- package/registry/payment-card/files/src/lib/kit.tsx +363 -11
- package/registry/payment-card.json +1 -0
- package/registry/paywall.json +1 -0
- package/registry/pixel-bar.json +1 -0
- package/registry/polar-area.json +1 -0
- package/registry/profile.json +1 -0
- package/registry/progress/files/src/components/ui/progress.tsx +29 -29
- package/registry/progress/files/src/lib/kit.tsx +363 -11
- package/registry/progress-steps/files/src/components/ui/progress-steps.tsx +20 -20
- package/registry/progress-steps/files/src/lib/kit.tsx +363 -11
- package/registry/progress-steps.json +1 -0
- package/registry/progress.json +1 -0
- package/registry/qr-code-button.json +1 -0
- package/registry/radio-group/files/src/components/ui/radio-group.tsx +19 -19
- package/registry/radio-group/files/src/lib/kit.tsx +363 -11
- package/registry/radio-group.json +1 -0
- package/registry/rating.json +1 -0
- package/registry/ring-carousel.json +1 -0
- package/registry/ruler-time-picker/files/src/components/ui/ruler-time-picker.tsx +98 -82
- package/registry/ruler-time-picker/files/src/lib/kit.tsx +363 -11
- package/registry/ruler-time-picker.json +7 -0
- package/registry/scratch-card.json +1 -0
- package/registry/search-bar/files/src/components/ui/search-bar.tsx +25 -21
- package/registry/search-bar/files/src/lib/kit.tsx +363 -11
- package/registry/search-bar.json +1 -0
- package/registry/search-results.json +4 -0
- package/registry/segmented-control/files/src/components/ui/segmented-control.tsx +8 -7
- package/registry/segmented-control/files/src/lib/kit.tsx +363 -11
- package/registry/segmented-control.json +1 -0
- package/registry/select/files/src/components/ui/select.tsx +93 -88
- package/registry/select/files/src/lib/kit.tsx +363 -11
- package/registry/select.json +6 -0
- package/registry/settings.json +1 -0
- package/registry/sign-in.json +1 -0
- package/registry/skeleton/files/src/components/ui/skeleton.tsx +149 -80
- package/registry/skeleton/files/src/lib/kit.tsx +363 -11
- package/registry/skeleton.json +1 -0
- package/registry/slide-to-confirm.json +1 -0
- package/registry/slider/files/src/components/ui/slider.tsx +103 -96
- package/registry/slider/files/src/lib/kit.tsx +363 -11
- package/registry/slider.json +5 -0
- package/registry/spin-wheel.json +1 -0
- package/registry/spinner/files/src/components/ui/spinner.tsx +39 -29
- package/registry/spinner/files/src/lib/kit.tsx +363 -11
- package/registry/spinner.json +6 -0
- package/registry/stateful-button/files/src/components/ui/stateful-button.tsx +39 -32
- package/registry/stateful-button/files/src/lib/kit.tsx +363 -11
- package/registry/stateful-button.json +5 -0
- package/registry/stepper/files/src/components/ui/stepper.tsx +38 -36
- package/registry/stepper/files/src/lib/kit.tsx +363 -11
- package/registry/stepper.json +1 -0
- package/registry/swipe-deck.json +1 -0
- package/registry/swipeable-row.json +4 -0
- package/registry/switch/files/src/components/ui/switch.tsx +39 -42
- package/registry/switch/files/src/lib/kit.tsx +363 -11
- package/registry/switch.json +1 -0
- package/registry/tabs/files/src/components/ui/tabs.tsx +59 -62
- package/registry/tabs/files/src/lib/kit.tsx +363 -11
- package/registry/tabs.json +1 -0
- package/registry/text-animation/files/src/components/ui/text-animation.tsx +55 -42
- package/registry/text-animation/files/src/lib/kit.tsx +363 -11
- package/registry/text-animation.json +7 -0
- package/registry/textarea/files/src/components/ui/textarea.tsx +10 -10
- package/registry/textarea/files/src/lib/kit.tsx +363 -11
- package/registry/textarea.json +1 -0
- package/registry/ticket-card.json +1 -0
- package/registry/ticket-shape/files/src/lib/kit.tsx +363 -11
- package/registry/ticket-shape/files/src/lib/ticket-shape.tsx +54 -44
- package/registry/ticket-shape.json +1 -0
- package/registry/tilt-card/files/src/components/ui/tilt-card.tsx +76 -91
- package/registry/tilt-card/files/src/lib/kit.tsx +363 -11
- package/registry/tilt-card.json +1 -0
- package/registry/time-picker/files/src/components/ui/time-picker.tsx +122 -119
- package/registry/time-picker/files/src/lib/kit.tsx +363 -11
- package/registry/time-picker.json +1 -0
- package/registry/timeline/files/src/components/ui/avatar.tsx +84 -66
- package/registry/timeline/files/src/components/ui/timeline.tsx +44 -41
- package/registry/timeline/files/src/lib/kit.tsx +363 -11
- package/registry/timeline-chart.json +1 -0
- package/registry/timeline.json +1 -0
- package/registry/toast/files/src/components/ui/toast.tsx +541 -463
- package/registry/toast/files/src/lib/kit.tsx +363 -11
- package/registry/toast.json +7 -5
- package/registry/treemap.json +1 -0
- package/registry/waterfall.json +1 -0
- package/registry/wheel-list.json +1 -0
- package/schema/components.schema.json +1 -0
- package/registry/alert/files/src/components/ui/button.tsx +0 -290
- package/registry/confetti/files/src/lib/kit.tsx +0 -320
- package/registry/grain/files/src/lib/kit.tsx +0 -320
- package/registry/toast/files/src/components/ui/button.tsx +0 -290
|
@@ -1,12 +1,11 @@
|
|
|
1
1
|
import { createAudioPlayer, type AudioPlayer } from 'expo-audio';
|
|
2
2
|
import { File, Paths } from 'expo-file-system';
|
|
3
3
|
import * as Haptics from 'expo-haptics';
|
|
4
|
-
import React, { useEffect, useMemo, useRef, useState } from 'react';
|
|
4
|
+
import React, { useEffect, useLayoutEffect, useMemo, useRef, useState } from 'react';
|
|
5
5
|
import {
|
|
6
6
|
AccessibilityInfo,
|
|
7
7
|
Animated,
|
|
8
8
|
Easing,
|
|
9
|
-
PanResponder,
|
|
10
9
|
Platform,
|
|
11
10
|
StyleSheet,
|
|
12
11
|
Text,
|
|
@@ -18,7 +17,7 @@ import {
|
|
|
18
17
|
type ViewStyle,
|
|
19
18
|
} from 'react-native';
|
|
20
19
|
|
|
21
|
-
import { motion, useKit } from '@/lib/kit';
|
|
20
|
+
import { motion, useKit, usePanResponder } from '@/lib/kit';
|
|
22
21
|
|
|
23
22
|
/** A wall-clock time, 24-hour: `{ hours: 0–23, minutes: 0–59 }`. */
|
|
24
23
|
export interface TimeValue {
|
|
@@ -116,12 +115,19 @@ export function stepTime(base: number, ticks: number, step: number) {
|
|
|
116
115
|
return mod(base + ticks * step, DAY);
|
|
117
116
|
}
|
|
118
117
|
|
|
119
|
-
/**
|
|
120
|
-
|
|
118
|
+
/**
|
|
119
|
+
* `'02:15 am'` / `'14:15'` split into the glyphs the picker rolls. `period` is the key ('am' / 'pm');
|
|
120
|
+
* the spoken label names it with `periods` (default English, pass `useKit().labels` to localise).
|
|
121
|
+
*/
|
|
122
|
+
export function timeGlyphs(minutes: number, hour12: boolean, periods: { am: string; pm: string } = { am: 'AM', pm: 'PM' }) {
|
|
121
123
|
const { hours, minutes: m } = fromMinutes(minutes);
|
|
122
124
|
const h = hour12 ? hours % 12 || 12 : hours;
|
|
123
125
|
const digits = pad2(h) + pad2(m);
|
|
124
|
-
return {
|
|
126
|
+
return {
|
|
127
|
+
digits: digits.split(''),
|
|
128
|
+
period: hour12 ? (hours < 12 ? ('am' as const) : ('pm' as const)) : null,
|
|
129
|
+
label: `${pad2(h)}:${pad2(m)}${hour12 ? ` ${hours < 12 ? periods.am : periods.pm}` : ''}`,
|
|
130
|
+
};
|
|
125
131
|
}
|
|
126
132
|
|
|
127
133
|
export const dayOf = (date: Date): Day => ({ y: date.getFullYear(), m: date.getMonth(), d: date.getDate() });
|
|
@@ -188,9 +194,9 @@ export function dateParts(t: Day, showYear: 'auto' | 'always' | 'never' = 'auto'
|
|
|
188
194
|
const wd = new Date(utc(t)).getUTCDay();
|
|
189
195
|
const year = showYear === 'always' || (showYear === 'auto' && t.y !== thisYear) ? String(t.y) : null;
|
|
190
196
|
return {
|
|
191
|
-
weekday: n.days[wd],
|
|
197
|
+
weekday: n.days[wd] ?? '',
|
|
192
198
|
day: String(t.d).split(''),
|
|
193
|
-
month: n.months[t.m],
|
|
199
|
+
month: n.months[t.m] ?? '',
|
|
194
200
|
year,
|
|
195
201
|
label: `${n.daysLong[wd]}, ${t.d} ${n.monthsLong[t.m]} ${t.y}`,
|
|
196
202
|
};
|
|
@@ -207,13 +213,16 @@ export function RulerTimePicker(props: RulerTimePickerProps) {
|
|
|
207
213
|
}
|
|
208
214
|
|
|
209
215
|
function TimeRuler({ value, defaultValue, onChange, label, step: rawStep = 5, hour12 = true, size = 'md', accent, haptics = true, sound = true, accessibilityLabel, style, textStyle }: RulerTimeProps) {
|
|
216
|
+
const { labels } = useKit();
|
|
210
217
|
const step = Math.max(1, Math.min(60, Math.round(rawStep) || 5));
|
|
211
218
|
const initial = toMinutes(value ?? defaultValue ?? { hours: 9, minutes: 0 });
|
|
212
219
|
// Minutes shown, and which way the last change went (rolls go up for later, down for earlier).
|
|
213
220
|
const [shown, setShown] = useState({ minutes: initial, dir: 1 as 1 | -1 });
|
|
214
221
|
const base = useRef(initial);
|
|
215
222
|
const live = useRef({ onChange, step, shown: initial });
|
|
216
|
-
|
|
223
|
+
useLayoutEffect(() => {
|
|
224
|
+
live.current = { onChange, step, shown: shown.minutes };
|
|
225
|
+
});
|
|
217
226
|
|
|
218
227
|
const ruler = useRuler({
|
|
219
228
|
gap: TIME_GAP,
|
|
@@ -242,20 +251,22 @@ function TimeRuler({ value, defaultValue, onChange, label, step: rawStep = 5, ho
|
|
|
242
251
|
|
|
243
252
|
const fontSize = TIME_SIZES[size];
|
|
244
253
|
const { box, glyph } = useGlyph(fontSize, textStyle);
|
|
245
|
-
const t = timeGlyphs(shown.minutes, hour12);
|
|
254
|
+
const t = timeGlyphs(shown.minutes, hour12, labels);
|
|
255
|
+
const [h1 = '', h2 = '', m1 = '', m2 = ''] = t.digits;
|
|
246
256
|
const roll = { dir: shown.dir, height: box, style: glyph, reduce: ruler.reduce };
|
|
247
257
|
|
|
248
258
|
return (
|
|
249
|
-
<Frame ruler={ruler} label={label} name={accessibilityLabel ?? label?.replace(/:\s*$/, '') ??
|
|
259
|
+
<Frame ruler={ruler} label={label} name={accessibilityLabel ?? label?.replace(/:\s*$/, '') ?? labels.time} spoken={t.label} accent={accent} rulerWidth={fontSize * 5.6} style={style}>
|
|
250
260
|
<View style={[styles.row, { height: box }]}>
|
|
251
|
-
<Roll text={
|
|
252
|
-
<Roll text={
|
|
261
|
+
<Roll text={h1} {...roll} />
|
|
262
|
+
<Roll text={h2} {...roll} />
|
|
253
263
|
<Text maxFontSizeMultiplier={FONT_CAP} style={glyph}>:</Text>
|
|
254
|
-
<Roll text={
|
|
255
|
-
<Roll text={
|
|
264
|
+
<Roll text={m1} {...roll} />
|
|
265
|
+
<Roll text={m2} {...roll} />
|
|
256
266
|
{t.period ? (
|
|
257
267
|
<View style={{ marginLeft: fontSize * 0.24 }}>
|
|
258
|
-
|
|
268
|
+
{/* Shown lowercase, as the design has it ('am' / 'pm'); the text comes from the kit labels. */}
|
|
269
|
+
<Roll text={labels[t.period].toLowerCase()} {...roll} />
|
|
259
270
|
</View>
|
|
260
271
|
) : null}
|
|
261
272
|
</View>
|
|
@@ -264,17 +275,20 @@ function TimeRuler({ value, defaultValue, onChange, label, step: rawStep = 5, ho
|
|
|
264
275
|
}
|
|
265
276
|
|
|
266
277
|
function DateRuler({ value, defaultValue, onChange, label, minDate, maxDate, showYear = 'auto', locale, size = 'md', accent, haptics = true, sound = true, accessibilityLabel, style, textStyle }: RulerDateProps) {
|
|
267
|
-
const start =
|
|
278
|
+
const [start] = useState(() => value ?? defaultValue ?? new Date());
|
|
268
279
|
// Time of day handed back with every change, so picking a date never moves a set time.
|
|
269
280
|
const clock = useRef({ h: start.getHours(), min: start.getMinutes() });
|
|
270
281
|
const [shown, setShown] = useState({ day: dayOf(start), index: 0, dir: 1 as 1 | -1 });
|
|
271
|
-
|
|
282
|
+
// The day at tick 0; a controlled value from outside re-bases the ruler on it.
|
|
283
|
+
const [base, setBase] = useState(() => dayOf(start));
|
|
272
284
|
const live = useRef({ onChange, key: utc(shown.day) });
|
|
273
|
-
|
|
285
|
+
useLayoutEffect(() => {
|
|
286
|
+
live.current = { onChange, key: utc(shown.day) };
|
|
287
|
+
});
|
|
274
288
|
|
|
275
289
|
// Limits as offsets from the base day.
|
|
276
|
-
const lo = minDate ? daysBetween(base
|
|
277
|
-
const hi = maxDate ? Math.max(lo, daysBetween(base
|
|
290
|
+
const lo = minDate ? daysBetween(base, dayOf(minDate)) : -Infinity;
|
|
291
|
+
const hi = maxDate ? Math.max(lo, daysBetween(base, dayOf(maxDate))) : Infinity;
|
|
278
292
|
|
|
279
293
|
const ruler = useRuler({
|
|
280
294
|
gap: DATE_GAP,
|
|
@@ -282,9 +296,9 @@ function DateRuler({ value, defaultValue, onChange, label, minDate, maxDate, sho
|
|
|
282
296
|
lo,
|
|
283
297
|
hi,
|
|
284
298
|
sound,
|
|
285
|
-
firm: (i) => addDays(base
|
|
299
|
+
firm: (i) => addDays(base, i).d === 1,
|
|
286
300
|
onStep: (i, dir) => {
|
|
287
|
-
const day = addDays(base
|
|
301
|
+
const day = addDays(base, i);
|
|
288
302
|
live.current.key = utc(day);
|
|
289
303
|
setShown({ day, index: i, dir });
|
|
290
304
|
live.current.onChange?.(new Date(day.y, day.m, day.d, clock.current.h, clock.current.min));
|
|
@@ -298,14 +312,14 @@ function DateRuler({ value, defaultValue, onChange, label, minDate, maxDate, sho
|
|
|
298
312
|
const day = dayOf(value);
|
|
299
313
|
clock.current = { h: value.getHours(), min: value.getMinutes() };
|
|
300
314
|
const dir = external > live.current.key ? 1 : -1;
|
|
301
|
-
|
|
315
|
+
setBase(day);
|
|
302
316
|
live.current.key = external;
|
|
303
317
|
ruler.reset();
|
|
304
318
|
setShown({ day, index: 0, dir });
|
|
305
319
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
306
320
|
}, [external]);
|
|
307
321
|
|
|
308
|
-
const { colors, font } = useKit();
|
|
322
|
+
const { colors, font, labels } = useKit();
|
|
309
323
|
const fontSize = DATE_SIZES[size];
|
|
310
324
|
const { box, glyph } = useGlyph(fontSize, textStyle);
|
|
311
325
|
const gap = fontSize * 0.24;
|
|
@@ -317,18 +331,17 @@ function DateRuler({ value, defaultValue, onChange, label, minDate, maxDate, sho
|
|
|
317
331
|
const reach = ruler.width ? Math.ceil(ruler.width / 2 / DATE_GAP) + 45 : 0;
|
|
318
332
|
const months = useMemo(() => {
|
|
319
333
|
if (!reach) return [];
|
|
320
|
-
return monthStarts(base
|
|
321
|
-
const t = addDays(base
|
|
322
|
-
return { i, name: t.m === 0 ? `${names.months[0]} ${String(t.y).slice(2)}` : names.months[t.m] };
|
|
334
|
+
return monthStarts(base, shown.index - reach, shown.index + reach).map((i) => {
|
|
335
|
+
const t = addDays(base, i);
|
|
336
|
+
return { i, name: t.m === 0 ? `${names.months[0] ?? ''} ${String(t.y).slice(2)}` : (names.months[t.m] ?? '') };
|
|
323
337
|
});
|
|
324
|
-
|
|
325
|
-
}, [shown.index, reach, names, external]);
|
|
338
|
+
}, [shown.index, reach, names, base]);
|
|
326
339
|
|
|
327
340
|
return (
|
|
328
341
|
<Frame
|
|
329
342
|
ruler={ruler}
|
|
330
343
|
label={label}
|
|
331
|
-
name={accessibilityLabel ?? label?.replace(/:\s*$/, '') ??
|
|
344
|
+
name={accessibilityLabel ?? label?.replace(/:\s*$/, '') ?? labels.date}
|
|
332
345
|
spoken={p.label}
|
|
333
346
|
accent={accent}
|
|
334
347
|
rulerWidth={fontSize * 7}
|
|
@@ -381,14 +394,16 @@ interface RulerOptions {
|
|
|
381
394
|
function useRuler({ gap, haptics, sound, lo = -Infinity, hi = Infinity, firm, onStep }: RulerOptions) {
|
|
382
395
|
const reduce = useReduceMotion();
|
|
383
396
|
const tick = useTickSound(sound);
|
|
384
|
-
const pos =
|
|
385
|
-
const lag =
|
|
397
|
+
const [pos] = useState(() => new Animated.Value(0));
|
|
398
|
+
const [lag] = useState(() => new Animated.Value(0));
|
|
386
399
|
const index = useRef(0);
|
|
387
400
|
const posNow = useRef(0);
|
|
388
401
|
const moving = useRef(false);
|
|
389
402
|
const flinging = useRef(false);
|
|
390
403
|
const live = useRef({ gap, haptics, lo, hi, firm, onStep, tick });
|
|
391
|
-
|
|
404
|
+
useLayoutEffect(() => {
|
|
405
|
+
live.current = { gap, haptics, lo, hi, firm, onStep, tick };
|
|
406
|
+
});
|
|
392
407
|
const lagMax = gap * 2.5;
|
|
393
408
|
|
|
394
409
|
const snap = (to: number) => {
|
|
@@ -398,7 +413,9 @@ function useRuler({ gap, haptics, sound, lo = -Infinity, hi = Infinity, firm, on
|
|
|
398
413
|
});
|
|
399
414
|
};
|
|
400
415
|
const snapRef = useRef(snap);
|
|
401
|
-
|
|
416
|
+
useLayoutEffect(() => {
|
|
417
|
+
snapRef.current = snap;
|
|
418
|
+
});
|
|
402
419
|
const settle = (v: number) => clamp(Math.round(v), live.current.lo, live.current.hi);
|
|
403
420
|
|
|
404
421
|
useEffect(() => {
|
|
@@ -428,46 +445,43 @@ function useRuler({ gap, haptics, sound, lo = -Infinity, hi = Infinity, firm, on
|
|
|
428
445
|
// Where the ruler was when the finger came down (g.dx counts from the touch, so the ruler stays
|
|
429
446
|
// under the finger even though a sideways drag is claimed a few points in).
|
|
430
447
|
const drag = useRef(0);
|
|
431
|
-
const
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
448
|
+
const unlag = () => Animated.spring(lag, { toValue: 0, useNativeDriver: true, ...motion.gentle }).start();
|
|
449
|
+
const pan = usePanResponder({
|
|
450
|
+
// A touch only claims the ruler straight away to catch a fling; otherwise a sideways drag does.
|
|
451
|
+
onStartShouldSetPanResponder: () => moving.current,
|
|
452
|
+
onMoveShouldSetPanResponder: (_, g) => Math.abs(g.dx) > 4 && Math.abs(g.dx) > Math.abs(g.dy),
|
|
453
|
+
onPanResponderTerminationRequest: () => false,
|
|
454
|
+
onPanResponderGrant: (_, g) => {
|
|
455
|
+
flinging.current = false;
|
|
456
|
+
pos.stopAnimation((v) => (posNow.current = v));
|
|
457
|
+
lag.stopAnimation();
|
|
458
|
+
moving.current = true;
|
|
459
|
+
drag.current = posNow.current + g.dx / live.current.gap;
|
|
460
|
+
},
|
|
461
|
+
onPanResponderMove: (_, g) => {
|
|
462
|
+
const { lo: min, hi: max, gap: px } = live.current;
|
|
463
|
+
const raw = drag.current - g.dx / px;
|
|
464
|
+
pos.setValue(raw < min ? min - (min - raw) * RUBBER : raw > max ? max + (raw - max) * RUBBER : raw);
|
|
465
|
+
lag.setValue(clamp(-g.vx * 14, -lagMax, lagMax));
|
|
466
|
+
},
|
|
467
|
+
onPanResponderRelease: (_, g) => {
|
|
468
|
+
unlag();
|
|
469
|
+
const { lo: min, hi: max, gap: px } = live.current;
|
|
470
|
+
const velocity = -g.vx / px; // ticks per ms
|
|
471
|
+
const outside = posNow.current < min || posNow.current > max;
|
|
472
|
+
if (outside || Math.abs(velocity) < 0.03) return snapRef.current(settle(posNow.current));
|
|
473
|
+
flinging.current = true;
|
|
474
|
+
Animated.decay(pos, { velocity, deceleration: 0.993, useNativeDriver: true }).start(({ finished }) => {
|
|
475
|
+
if (!finished || !flinging.current) return;
|
|
439
476
|
flinging.current = false;
|
|
440
|
-
pos.stopAnimation((v) =>
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
pos.setValue(raw < min ? min - (min - raw) * RUBBER : raw > max ? max + (raw - max) * RUBBER : raw);
|
|
449
|
-
lag.setValue(clamp(-g.vx * 14, -lagMax, lagMax));
|
|
450
|
-
},
|
|
451
|
-
onPanResponderRelease: (_, g) => {
|
|
452
|
-
unlag();
|
|
453
|
-
const { lo: min, hi: max, gap: px } = live.current;
|
|
454
|
-
const velocity = -g.vx / px; // ticks per ms
|
|
455
|
-
const outside = posNow.current < min || posNow.current > max;
|
|
456
|
-
if (outside || Math.abs(velocity) < 0.03) return snapRef.current(settle(posNow.current));
|
|
457
|
-
flinging.current = true;
|
|
458
|
-
Animated.decay(pos, { velocity, deceleration: 0.993, useNativeDriver: true }).start(({ finished }) => {
|
|
459
|
-
if (!finished || !flinging.current) return;
|
|
460
|
-
flinging.current = false;
|
|
461
|
-
pos.stopAnimation((v) => snapRef.current(settle(v)));
|
|
462
|
-
});
|
|
463
|
-
},
|
|
464
|
-
onPanResponderTerminate: () => {
|
|
465
|
-
unlag();
|
|
466
|
-
snapRef.current(settle(posNow.current));
|
|
467
|
-
},
|
|
468
|
-
});
|
|
469
|
-
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
470
|
-
}, [pos, lag]);
|
|
477
|
+
pos.stopAnimation((v) => snapRef.current(settle(v)));
|
|
478
|
+
});
|
|
479
|
+
},
|
|
480
|
+
onPanResponderTerminate: () => {
|
|
481
|
+
unlag();
|
|
482
|
+
snapRef.current(settle(posNow.current));
|
|
483
|
+
},
|
|
484
|
+
});
|
|
471
485
|
|
|
472
486
|
const [width, setWidth] = useState(0);
|
|
473
487
|
return {
|
|
@@ -515,15 +529,15 @@ function useGlyph(fontSize: number, textStyle?: StyleProp<TextStyle>) {
|
|
|
515
529
|
|
|
516
530
|
interface FrameProps {
|
|
517
531
|
ruler: Ruler;
|
|
518
|
-
label?: string;
|
|
532
|
+
label?: string | undefined;
|
|
519
533
|
name: string;
|
|
520
534
|
spoken: string;
|
|
521
|
-
accent?: string;
|
|
535
|
+
accent?: string | undefined;
|
|
522
536
|
rulerWidth: number;
|
|
523
537
|
/** Extra height under the ticks (month labels). */
|
|
524
|
-
extra?: number;
|
|
538
|
+
extra?: number | undefined;
|
|
525
539
|
marks?: React.ReactNode;
|
|
526
|
-
style?: StyleProp<ViewStyle
|
|
540
|
+
style?: StyleProp<ViewStyle> | undefined;
|
|
527
541
|
children: React.ReactNode;
|
|
528
542
|
}
|
|
529
543
|
|
|
@@ -540,7 +554,7 @@ function Frame({ ruler, label, name, spoken, accent, rulerWidth, extra = 0, mark
|
|
|
540
554
|
accessibilityActions={[{ name: 'increment' }, { name: 'decrement' }]}
|
|
541
555
|
onAccessibilityAction={(e) => ruler.nudge(e.nativeEvent.actionName === 'increment' ? 1 : -1)}
|
|
542
556
|
style={[styles.root, styles.grab, style]}
|
|
543
|
-
{...ruler.pan
|
|
557
|
+
{...ruler.pan}
|
|
544
558
|
>
|
|
545
559
|
{label ? <Text style={[styles.label, { color: colors.foreground, fontFamily: font.family }]}>{label}</Text> : null}
|
|
546
560
|
{children}
|
|
@@ -561,7 +575,8 @@ function Frame({ ruler, label, name, spoken, accent, rulerWidth, extra = 0, mark
|
|
|
561
575
|
}
|
|
562
576
|
|
|
563
577
|
/** Ticks fade out toward both ends of the ruler. */
|
|
564
|
-
|
|
578
|
+
// ReturnType keeps this compiling on React Native 0.86 (generic AnimatedMultiplication) and 0.88+ (not generic).
|
|
579
|
+
function fade(x: Animated.AnimatedInterpolation<number> | ReturnType<typeof Animated.multiply>, half: number, gap: number) {
|
|
565
580
|
return x.interpolate({
|
|
566
581
|
inputRange: [-half, -half * 0.55, -gap * 2, 0, gap * 2, half * 0.55, half],
|
|
567
582
|
outputRange: [0, 0.35, 0.85, 1, 0.85, 0.35, 0],
|
|
@@ -622,7 +637,7 @@ interface MonthMarksProps {
|
|
|
622
637
|
months: { i: number; name: string }[];
|
|
623
638
|
tick: string;
|
|
624
639
|
text: string;
|
|
625
|
-
family?: string;
|
|
640
|
+
family?: string | undefined;
|
|
626
641
|
}
|
|
627
642
|
|
|
628
643
|
/** A taller tick and the month's name under the 1st of each month, riding the ruler natively. */
|
|
@@ -669,7 +684,7 @@ const GHOSTS: [number, number][] = Array.from({ length: 7 }, (_, i) => {
|
|
|
669
684
|
* the other side, both smeared by ghost copies that fade as the roll settles.
|
|
670
685
|
*/
|
|
671
686
|
const Roll = React.memo(function Roll({ text, dir, height, style, reduce }: RollProps) {
|
|
672
|
-
const t =
|
|
687
|
+
const [t] = useState(() => new Animated.Value(1));
|
|
673
688
|
const last = useRef(text);
|
|
674
689
|
const [pair, setPair] = useState({ prev: text, cur: text, dir });
|
|
675
690
|
|
|
@@ -776,6 +791,7 @@ function useTickSound(enabled: boolean) {
|
|
|
776
791
|
if (!pool.current.length || now - state.current.last < 25) return;
|
|
777
792
|
state.current.last = now;
|
|
778
793
|
const player = pool.current[state.current.next++ % pool.current.length];
|
|
794
|
+
if (!player) return;
|
|
779
795
|
player.volume = firm ? 0.9 : 0.5;
|
|
780
796
|
player.seekTo(0).catch(() => {});
|
|
781
797
|
player.play();
|