@astryxdesign/core 0.7.7-canary.36f8ea1 → 0.7.7-canary.550ff21
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/CommandPalette/CommandPaletteInput.d.ts.map +1 -1
- package/dist/CommandPalette/CommandPaletteInput.js +8 -1
- package/dist/DateInput/MonthScroller.d.ts.map +1 -1
- package/dist/DateInput/MonthScroller.js +72 -2
- package/dist/Field/PanelSearchInput.d.ts.map +1 -1
- package/dist/Field/PanelSearchInput.js +8 -1
- package/dist/Field/textEntryTraits.d.ts +6 -6
- package/dist/Field/textEntryTraits.js +6 -6
- package/dist/ProgressBar/ProgressBar.d.ts.map +1 -1
- package/dist/ProgressBar/ProgressBar.js +20 -9
- package/dist/ProgressBar/ProgressBarTouch.d.ts +29 -0
- package/dist/ProgressBar/ProgressBarTouch.d.ts.map +1 -0
- package/dist/ProgressBar/ProgressBarTouch.js +91 -0
- package/dist/Typeahead/BaseTypeahead.d.ts.map +1 -1
- package/dist/Typeahead/BaseTypeahead.js +9 -1
- package/dist/astryx.css +7 -0
- package/dist/gestures/GestureArena.d.ts +21 -2
- package/dist/gestures/GestureArena.d.ts.map +1 -1
- package/dist/gestures/GestureArena.js +19 -2
- package/dist/gestures/GestureHost.d.ts +51 -1
- package/dist/gestures/GestureHost.d.ts.map +1 -1
- package/dist/gestures/GestureHost.js +148 -7
- package/dist/motion/animator.d.ts.map +1 -1
- package/dist/motion/animator.js +63 -5
- package/dist/motion/paging.d.ts +58 -0
- package/dist/motion/paging.d.ts.map +1 -0
- package/dist/motion/paging.js +128 -0
- package/dist/motion/uikit/index.d.ts +4 -3
- package/dist/motion/uikit/index.d.ts.map +1 -1
- package/dist/motion/uikit/index.js +4 -3
- package/dist/motion/uikit/paging.d.ts +160 -0
- package/dist/motion/uikit/paging.d.ts.map +1 -0
- package/dist/motion/uikit/paging.js +162 -0
- package/dist/motion/uikit/progressBar.d.ts +59 -0
- package/dist/motion/uikit/progressBar.d.ts.map +1 -0
- package/dist/motion/uikit/progressBar.js +82 -0
- package/dist/motion/uikit/scroll.d.ts +0 -19
- package/dist/motion/uikit/scroll.d.ts.map +1 -1
- package/dist/motion/uikit/scroll.js +2 -21
- package/dist/utils/selectionLock.d.ts +43 -0
- package/dist/utils/selectionLock.d.ts.map +1 -1
- package/dist/utils/selectionLock.js +394 -6
- package/package.json +4 -4
- package/src/CommandPalette/CommandPaletteInput.tsx +6 -0
- package/src/DateInput/MonthScroller.tsx +63 -5
- package/src/Field/PanelSearchInput.tsx +6 -0
- package/src/Field/textEntryTraits.ts +6 -6
- package/src/ProgressBar/ProgressBar.doc.mjs +3 -3
- package/src/ProgressBar/ProgressBar.tsx +82 -11
- package/src/ProgressBar/ProgressBarTouch.ts +109 -0
- package/src/Typeahead/BaseTypeahead.tsx +7 -0
- package/src/gestures/GestureArena.ts +44 -4
- package/src/gestures/GestureHost.ts +184 -7
- package/src/gestures/README.md +19 -3
- package/src/motion/MEASUREMENTS.md +8 -1
- package/src/motion/README.md +6 -3
- package/src/motion/animator.ts +78 -6
- package/src/motion/paging.ts +138 -0
- package/src/motion/uikit/index.ts +4 -3
- package/src/motion/uikit/paging.ts +162 -0
- package/src/motion/uikit/progressBar.ts +82 -0
- package/src/motion/uikit/scroll.ts +2 -18
- package/src/utils/selectionLock.ts +501 -6
- package/dist/motion/selectPage.d.ts +0 -26
- package/dist/motion/selectPage.d.ts.map +0 -1
- package/dist/motion/selectPage.js +0 -55
- package/src/motion/selectPage.ts +0 -55
|
@@ -57,6 +57,7 @@ import {
|
|
|
57
57
|
typographyVars,
|
|
58
58
|
} from '../theme/tokens.stylex';
|
|
59
59
|
import type {BaseProps} from '../BaseProps';
|
|
60
|
+
import {textEntryTraitAttributes} from './textEntryTraits';
|
|
60
61
|
|
|
61
62
|
const styles = stylex.create({
|
|
62
63
|
// Gutter only. Matches the option list's own padding so the field below
|
|
@@ -273,6 +274,11 @@ export function PanelSearchInput({
|
|
|
273
274
|
<input
|
|
274
275
|
ref={mergedInputRef}
|
|
275
276
|
type="text"
|
|
277
|
+
// Autocorrect would rewrite names and identifiers.
|
|
278
|
+
{...textEntryTraitAttributes({
|
|
279
|
+
enterKeyHint: 'search',
|
|
280
|
+
hasAutoCorrect: false,
|
|
281
|
+
})}
|
|
276
282
|
aria-label={label}
|
|
277
283
|
placeholder={placeholder}
|
|
278
284
|
value={value}
|
|
@@ -3,16 +3,16 @@
|
|
|
3
3
|
/**
|
|
4
4
|
* @file textEntryTraits.ts
|
|
5
5
|
* @input The keyboard-trait props a text field accepts
|
|
6
|
-
* @output
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
* @position Shared by TextInput and TextArea: the one place that says how a
|
|
11
|
-
* field tells a touch keyboard what it holds
|
|
6
|
+
* @output EnterKeyHint, InputMode and AutoCapitalize (the package entry exports
|
|
7
|
+
* these), TextEntryTraitProps and textEntryTraitAttributes
|
|
8
|
+
* @position Shared by TextInput, TextArea and the search inputs: the one place
|
|
9
|
+
* that says how a field tells a touch keyboard what it holds
|
|
12
10
|
*
|
|
13
11
|
* SYNC: When modified, update:
|
|
14
12
|
* - /packages/core/src/TextInput/TextInput.tsx and TextInput.doc.mjs
|
|
15
13
|
* - /packages/core/src/TextArea/TextArea.tsx and TextArea.doc.mjs
|
|
14
|
+
* - /packages/core/src/Typeahead/BaseTypeahead.tsx, Field/PanelSearchInput.tsx
|
|
15
|
+
* and CommandPalette/CommandPaletteInput.tsx (the search inputs)
|
|
16
16
|
* - /packages/core/src/Field/index.ts (type exports)
|
|
17
17
|
*/
|
|
18
18
|
|
|
@@ -160,7 +160,7 @@ export const docs = {
|
|
|
160
160
|
usage: {
|
|
161
161
|
anatomy,
|
|
162
162
|
description:
|
|
163
|
-
"A horizontal bar showing the completion progress of a task. Use it for operations where the duration is known, or as an animated indicator when progress can't be calculated. Supports semantic color variants, value labels, and custom formatting.",
|
|
163
|
+
"A horizontal bar showing the completion progress of a task. Use it for operations where the duration is known, or as an animated indicator when progress can't be calculated. Supports semantic color variants, value labels, and custom formatting. Under a finger a new value animates as on iOS: linearly, in proportion to how far it moves (a full track takes one second at the theme's default tempo), and the fill keeps a minimum visible length above zero.",
|
|
164
164
|
bestPractices: [
|
|
165
165
|
{
|
|
166
166
|
guidance: true,
|
|
@@ -304,7 +304,7 @@ export const docsZh = {
|
|
|
304
304
|
usage: {
|
|
305
305
|
anatomy,
|
|
306
306
|
description:
|
|
307
|
-
"A horizontal bar showing the completion progress of a task. Use it for operations where the duration is known, or as an animated indicator when progress can't be calculated. Supports semantic color variants, value labels, and custom formatting.",
|
|
307
|
+
"A horizontal bar showing the completion progress of a task. Use it for operations where the duration is known, or as an animated indicator when progress can't be calculated. Supports semantic color variants, value labels, and custom formatting. Under a finger a new value animates as on iOS: linearly, in proportion to how far it moves (a full track takes one second at the theme's default tempo), and the fill keeps a minimum visible length above zero.",
|
|
308
308
|
bestPractices: [
|
|
309
309
|
{
|
|
310
310
|
guidance: true,
|
|
@@ -347,7 +347,7 @@ export const docsDense = {
|
|
|
347
347
|
usage: {
|
|
348
348
|
anatomy,
|
|
349
349
|
description:
|
|
350
|
-
"A horizontal bar showing the completion progress of a task. Use it for operations where the duration is known, or as an animated indicator when progress can't be calculated. Supports semantic color variants, value labels, and custom formatting.",
|
|
350
|
+
"A horizontal bar showing the completion progress of a task. Use it for operations where the duration is known, or as an animated indicator when progress can't be calculated. Supports semantic color variants, value labels, and custom formatting. Under a finger a new value animates as on iOS: linearly, in proportion to how far it moves (a full track takes one second at the theme's default tempo), and the fill keeps a minimum visible length above zero.",
|
|
351
351
|
bestPractices: [
|
|
352
352
|
{
|
|
353
353
|
guidance: true,
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* @file ProgressBar.tsx
|
|
7
|
-
* @input Uses React, useId, stylex,
|
|
7
|
+
* @input Uses React, useId, stylex, tokens and ProgressBarTouch
|
|
8
8
|
* @output Exports ProgressBar component, ProgressBarProps, ProgressBarVariant types
|
|
9
9
|
* @position Core implementation; consumed by index.ts
|
|
10
10
|
*
|
|
@@ -35,6 +35,7 @@ import type {BaseProps} from '../BaseProps';
|
|
|
35
35
|
import {themeProps} from '../utils/themeProps';
|
|
36
36
|
import {VisuallyHidden} from '../VisuallyHidden';
|
|
37
37
|
import type {ProgressBarVariantMap} from './index';
|
|
38
|
+
import {useProgressBarTouch} from './ProgressBarTouch';
|
|
38
39
|
|
|
39
40
|
// Every mark is wrapped in a Tooltip (marks always carry a label), so load it
|
|
40
41
|
// lazily: a ProgressBar with no marks never bundles the Tooltip chunk. While it
|
|
@@ -249,10 +250,26 @@ const styles = stylex.create({
|
|
|
249
250
|
// no transform), as before: the track's clip would cut the CanvasText
|
|
250
251
|
// outline's start cap off a slid fill, and that outline is what keeps the
|
|
251
252
|
// fill's shape visible there (architecture:theme-tokens INV11).
|
|
253
|
+
//
|
|
254
|
+
// Under a finger (TOUCH_QUERY, on every touch device), the fill moves as
|
|
255
|
+
// UIProgressView's setProgress(_:animated: true) does. It moves linearly
|
|
256
|
+
// from wherever it is drawn. Its duration is the `progress.fillTrackMs` role
|
|
257
|
+
// times the share of the track the value moved (`--_progress-bar-move`).
|
|
258
|
+
// Its curve is the `progress.curves.fill` role (`--_progress-bar-curve`).
|
|
259
|
+
// The touch layer (ProgressBarTouch) writes both, and this rule scales the
|
|
260
|
+
// duration by the theme's tempo. Above zero, the fill never draws shorter
|
|
261
|
+
// than the touch minimum (`--_progress-bar-min`, 16px on the 8px track). At
|
|
262
|
+
// zero it paints nothing (`fillEmpty`). Under reduced motion it lands at
|
|
263
|
+
// once, as everywhere (below). A mouse or trackpad keeps the theme's timing.
|
|
252
264
|
fill: {
|
|
253
265
|
width: {
|
|
254
266
|
default: '100%',
|
|
255
|
-
'@media (forced-colors: active)':
|
|
267
|
+
'@media (forced-colors: active)': {
|
|
268
|
+
default: 'var(--_progress-bar-fill, 0%)',
|
|
269
|
+
// Under a finger: the touch minimum and the flip offset (below).
|
|
270
|
+
'@media (pointer: coarse)':
|
|
271
|
+
'calc(max(var(--_progress-bar-fill, 0%), var(--_progress-bar-min, 0px)) + var(--_progress-bar-flip, 0) * 0.01px)',
|
|
272
|
+
},
|
|
256
273
|
},
|
|
257
274
|
height: '100%',
|
|
258
275
|
// Forced colors replace status fills with Canvas; this boundary keeps the
|
|
@@ -271,6 +288,19 @@ const styles = stylex.create({
|
|
|
271
288
|
default: 'translateX(calc(100% - var(--_progress-bar-fill, 0%)))',
|
|
272
289
|
'@media (forced-colors: active)': 'none',
|
|
273
290
|
},
|
|
291
|
+
'@media (pointer: coarse)': {
|
|
292
|
+
// `--_progress-bar-flip` steps 0, 1, 2, 0… on every change. It moves
|
|
293
|
+
// the fill by that many hundredths of a pixel. So a change back to the
|
|
294
|
+
// value it just left is never the running transition's own start
|
|
295
|
+
// value. CSS shortens a transition that reverses to its start, which
|
|
296
|
+
// would halve a return mid-flight (10 → 90 → 10). It takes three
|
|
297
|
+
// values because two would repeat on that return.
|
|
298
|
+
default:
|
|
299
|
+
'translateX(calc(max(var(--_progress-bar-fill, 0%), var(--_progress-bar-min, 0px)) - 100% + var(--_progress-bar-flip, 0) * 0.01px))',
|
|
300
|
+
':dir(rtl)':
|
|
301
|
+
'translateX(calc(min(calc(100% - var(--_progress-bar-fill, 0%)), calc(100% - var(--_progress-bar-min, 0px))) - var(--_progress-bar-flip, 0) * 0.01px))',
|
|
302
|
+
},
|
|
303
|
+
// After the touch key, so StyleX negates that key under forced colors.
|
|
274
304
|
'@media (forced-colors: active)': 'none',
|
|
275
305
|
},
|
|
276
306
|
willChange: {
|
|
@@ -281,17 +311,36 @@ const styles = stylex.create({
|
|
|
281
311
|
default: 'transform',
|
|
282
312
|
'@media (forced-colors: active)': 'width',
|
|
283
313
|
},
|
|
284
|
-
// Under reduced motion a new value lands at once,
|
|
285
|
-
// fill never travels
|
|
286
|
-
//
|
|
287
|
-
//
|
|
314
|
+
// Under reduced motion a new value lands at once, with a mouse as with a
|
|
315
|
+
// finger: the fill never travels. The keys are the shared reduced-motion
|
|
316
|
+
// queries, as the other transitions spell them. The coarse key comes after
|
|
317
|
+
// the touch key, so it wins there.
|
|
288
318
|
transitionDuration: {
|
|
289
319
|
default: durationVars['--duration-medium'],
|
|
320
|
+
// The touch layer's move (the `progress.fillTrackMs` role times the
|
|
321
|
+
// share moved), scaled by the theme's tempo.
|
|
322
|
+
'@media (pointer: coarse)': {
|
|
323
|
+
default:
|
|
324
|
+
'calc(var(--_progress-bar-move, 0ms) * var(--_astryx-motion-tempo, 1))',
|
|
325
|
+
// Under forced colors the fill is sized by `width` instead. Touch
|
|
326
|
+
// motion never animates a layout property, so with a finger the new
|
|
327
|
+
// size lands at once there.
|
|
328
|
+
'@media (forced-colors: active)': '0s',
|
|
329
|
+
},
|
|
290
330
|
'@media (prefers-reduced-motion: reduce) and (not (pointer: coarse))':
|
|
291
331
|
'0s',
|
|
292
332
|
'@media (prefers-reduced-motion: reduce) and (pointer: coarse)': '0s',
|
|
293
333
|
},
|
|
294
|
-
transitionTimingFunction:
|
|
334
|
+
transitionTimingFunction: {
|
|
335
|
+
default: easeVars['--ease-standard'],
|
|
336
|
+
'@media (pointer: coarse)': 'var(--_progress-bar-curve, linear)',
|
|
337
|
+
},
|
|
338
|
+
},
|
|
339
|
+
fillEmpty: {
|
|
340
|
+
opacity: {
|
|
341
|
+
default: null,
|
|
342
|
+
'@media (pointer: coarse)': 0,
|
|
343
|
+
},
|
|
295
344
|
},
|
|
296
345
|
indeterminateFill: {
|
|
297
346
|
height: '100%',
|
|
@@ -428,7 +477,15 @@ const markOnTrackStyles = stylex.create({
|
|
|
428
477
|
// `CSSProperties` has no index signature for custom properties, so the fill's
|
|
429
478
|
// inline style — which publishes the filled share as the private
|
|
430
479
|
// `--_progress-bar-fill` — needs this augmentation to typecheck.
|
|
431
|
-
type FillStyle = CSSProperties &
|
|
480
|
+
type FillStyle = CSSProperties &
|
|
481
|
+
Record<
|
|
482
|
+
| '--_progress-bar-fill'
|
|
483
|
+
| '--_progress-bar-move'
|
|
484
|
+
| '--_progress-bar-curve'
|
|
485
|
+
| '--_progress-bar-flip'
|
|
486
|
+
| '--_progress-bar-min',
|
|
487
|
+
string
|
|
488
|
+
>;
|
|
432
489
|
|
|
433
490
|
function defaultFormatValueLabel(value: number, max: number): string {
|
|
434
491
|
const pct = max > 0 ? Math.round((value / max) * 100) : 0;
|
|
@@ -509,7 +566,16 @@ export function ProgressBar({
|
|
|
509
566
|
const showValueLabel = hasValueLabel && !isIndeterminate;
|
|
510
567
|
|
|
511
568
|
const fillVariant = isDisabled ? 'disabled' : variant;
|
|
512
|
-
|
|
569
|
+
// Under a finger the fill moves as UIProgressView's does and draws at least
|
|
570
|
+
// 16px; what that needs from script is the touch layer's (ProgressBarTouch).
|
|
571
|
+
const touch = useProgressBarTouch(
|
|
572
|
+
percentage,
|
|
573
|
+
!isIndeterminate && (marks?.length ?? 0) > 0,
|
|
574
|
+
);
|
|
575
|
+
const fillStyle: FillStyle = {
|
|
576
|
+
'--_progress-bar-fill': `${percentage}%`,
|
|
577
|
+
...touch.fillVars,
|
|
578
|
+
};
|
|
513
579
|
|
|
514
580
|
// Marks make no sense without a determinate value, so they are only drawn
|
|
515
581
|
// in determinate mode. Non-finite mark values are dropped; the rest are
|
|
@@ -532,7 +598,7 @@ export function ProgressBar({
|
|
|
532
598
|
value: mark.value,
|
|
533
599
|
label: mark.label,
|
|
534
600
|
pct,
|
|
535
|
-
isOnFill: percentage > 0 && pct <=
|
|
601
|
+
isOnFill: percentage > 0 && pct <= touch.drawnPercentage,
|
|
536
602
|
};
|
|
537
603
|
})
|
|
538
604
|
: [];
|
|
@@ -582,6 +648,7 @@ export function ProgressBar({
|
|
|
582
648
|
aria-valuemax={isIndeterminate ? undefined : safeMax}
|
|
583
649
|
aria-labelledby={labelId}
|
|
584
650
|
aria-valuetext={isIndeterminate ? undefined : valueText}
|
|
651
|
+
ref={touch.trackRef}
|
|
585
652
|
{...mergeProps(
|
|
586
653
|
themeProps('progress-bar-track'),
|
|
587
654
|
stylex.props(styles.track),
|
|
@@ -600,7 +667,11 @@ export function ProgressBar({
|
|
|
600
667
|
<div
|
|
601
668
|
{...mergeProps(
|
|
602
669
|
themeProps('progress-bar-fill', {variant: fillVariant}),
|
|
603
|
-
stylex.props(
|
|
670
|
+
stylex.props(
|
|
671
|
+
styles.fill,
|
|
672
|
+
percentage === 0 && styles.fillEmpty,
|
|
673
|
+
variantStyles[fillVariant],
|
|
674
|
+
),
|
|
604
675
|
)}
|
|
605
676
|
style={fillStyle}
|
|
606
677
|
/>
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
|
|
3
|
+
'use client';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* @file ProgressBarTouch.ts
|
|
7
|
+
* @input Uses React, the touch presentation resolver, the shared resize
|
|
8
|
+
* observer, and the progress table's roles (through resolveMotion)
|
|
9
|
+
* @output useProgressBarTouch: the fill's touch variables (move length,
|
|
10
|
+
* curve, return offset, minimum length) and, under a finger with marks,
|
|
11
|
+
* the share of the track the fill draws
|
|
12
|
+
* @position ProgressBar's touch layer, imported only by ProgressBar.tsx
|
|
13
|
+
*
|
|
14
|
+
* SYNC: When modified, update /packages/core/src/ProgressBar/ProgressBar.tsx
|
|
15
|
+
* (its touch styles read these variables and the 16px minimum) and
|
|
16
|
+
* /packages/core/src/motion/uikit/progressBar.ts.
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
import {useEffect, useRef, useState, type RefObject} from 'react';
|
|
20
|
+
import {formatMotionRoleValue} from '../motion/roles';
|
|
21
|
+
import {resolveMotion} from '../motion/resolveMotion';
|
|
22
|
+
import {progressBar} from '../motion/uikit/progressBar';
|
|
23
|
+
import {observeResize} from '../utils/sharedResizeObserver';
|
|
24
|
+
import {useTouchPresentation} from '../utils/touchPresentation';
|
|
25
|
+
|
|
26
|
+
/** The track's height. */
|
|
27
|
+
const TRACK_HEIGHT_PX = 8;
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Under a finger the fill never draws shorter than this above zero:
|
|
31
|
+
* UIProgressView's minimum, two track heights, on ProgressBar's 8px track.
|
|
32
|
+
*/
|
|
33
|
+
export const TOUCH_MIN_FILL_PX =
|
|
34
|
+
progressBar.progressView.minimumFillHeights * TRACK_HEIGHT_PX;
|
|
35
|
+
|
|
36
|
+
export interface ProgressBarTouch {
|
|
37
|
+
/** The fill's touch timing variables, set inline on the fill. */
|
|
38
|
+
fillVars: Record<
|
|
39
|
+
| '--_progress-bar-move'
|
|
40
|
+
| '--_progress-bar-curve'
|
|
41
|
+
| '--_progress-bar-flip'
|
|
42
|
+
| '--_progress-bar-min',
|
|
43
|
+
string
|
|
44
|
+
>;
|
|
45
|
+
/** The track, measured under a finger when the bar has marks. */
|
|
46
|
+
trackRef: RefObject<HTMLDivElement | null>;
|
|
47
|
+
/** The share of the track the fill draws (0–100), for placing marks. */
|
|
48
|
+
drawnPercentage: number;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export function useProgressBarTouch(
|
|
52
|
+
percentage: number,
|
|
53
|
+
hasMarks: boolean,
|
|
54
|
+
): ProgressBarTouch {
|
|
55
|
+
// The touch fill's duration comes from how far the value moved since the
|
|
56
|
+
// last value, the model change that UIKit measures. It holds until the next
|
|
57
|
+
// change, so a re-render with the same value keeps it.
|
|
58
|
+
const [motion, setMotion] = useState({percentage, change: 0, flip: 0});
|
|
59
|
+
const isNewValue = motion.percentage !== percentage;
|
|
60
|
+
// The share of the track the value moved (0–1). `flip` steps through three
|
|
61
|
+
// values on every change (see the fill's touch transform).
|
|
62
|
+
const change = isNewValue
|
|
63
|
+
? Math.abs(percentage - motion.percentage) / 100
|
|
64
|
+
: motion.change;
|
|
65
|
+
const flip = isNewValue ? (motion.flip + 1) % 3 : motion.flip;
|
|
66
|
+
if (isNewValue) {
|
|
67
|
+
setMotion({percentage, change, flip});
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// Under a finger the fill draws at least TOUCH_MIN_FILL_PX long, so a mark
|
|
71
|
+
// it covers there sits on the fill. That length is a share of the track's
|
|
72
|
+
// width, which only layout knows. So the track is measured, but only under
|
|
73
|
+
// a finger and only when the bar has marks.
|
|
74
|
+
const {isTouch} = useTouchPresentation();
|
|
75
|
+
const watchesTrack = isTouch && hasMarks;
|
|
76
|
+
const trackRef = useRef<HTMLDivElement | null>(null);
|
|
77
|
+
const [trackWidth, setTrackWidth] = useState(0);
|
|
78
|
+
useEffect(() => {
|
|
79
|
+
const element = trackRef.current;
|
|
80
|
+
if (!watchesTrack || element == null) {
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
// The shared observer; its first call (on registration) carries no
|
|
84
|
+
// contentRect, so that one reads the box.
|
|
85
|
+
return observeResize(element, entry => {
|
|
86
|
+
setTrackWidth(
|
|
87
|
+
entry.contentRect?.width ?? element.getBoundingClientRect().width,
|
|
88
|
+
);
|
|
89
|
+
});
|
|
90
|
+
}, [watchesTrack]);
|
|
91
|
+
const drawnPercentage =
|
|
92
|
+
watchesTrack && percentage > 0 && trackWidth > 0
|
|
93
|
+
? Math.max(percentage, (TOUCH_MIN_FILL_PX / trackWidth) * 100)
|
|
94
|
+
: percentage;
|
|
95
|
+
|
|
96
|
+
// CSS applies the theme's tempo to this duration.
|
|
97
|
+
const fillTrackMs = resolveMotion(null, progressBar, 'fillTrackMs');
|
|
98
|
+
const curve = resolveMotion(null, progressBar.curves.fill);
|
|
99
|
+
return {
|
|
100
|
+
fillVars: {
|
|
101
|
+
'--_progress-bar-move': `${Math.round(change * fillTrackMs * 1000) / 1000}ms`,
|
|
102
|
+
'--_progress-bar-curve': formatMotionRoleValue('curve', curve),
|
|
103
|
+
'--_progress-bar-flip': String(flip),
|
|
104
|
+
'--_progress-bar-min': `${TOUCH_MIN_FILL_PX}px`,
|
|
105
|
+
},
|
|
106
|
+
trackRef,
|
|
107
|
+
drawnPercentage,
|
|
108
|
+
};
|
|
109
|
+
}
|
|
@@ -79,6 +79,7 @@ import {
|
|
|
79
79
|
useComponentTranslator,
|
|
80
80
|
} from '../i18n/useComponentTranslator';
|
|
81
81
|
import english from '../i18n/generated-english/Typeahead/BaseTypeahead.generated';
|
|
82
|
+
import {textEntryTraitAttributes} from '../Field/textEntryTraits';
|
|
82
83
|
|
|
83
84
|
registerEnglishFallback(english);
|
|
84
85
|
// =============================================================================
|
|
@@ -1723,6 +1724,12 @@ export const BaseTypeahead = function BaseTypeahead<T extends SearchableItem>({
|
|
|
1723
1724
|
autoFocus={hasAutoFocus}
|
|
1724
1725
|
data-autofocus={hasAutoFocus || undefined}
|
|
1725
1726
|
autoComplete="off"
|
|
1727
|
+
// Autocorrect would rewrite names and identifiers. The key is only a
|
|
1728
|
+
// label: Enter still commits the highlighted option.
|
|
1729
|
+
{...textEntryTraitAttributes({
|
|
1730
|
+
enterKeyHint: 'search',
|
|
1731
|
+
hasAutoCorrect: false,
|
|
1732
|
+
})}
|
|
1726
1733
|
{...mergeProps(
|
|
1727
1734
|
stylex.props(
|
|
1728
1735
|
styles.input,
|
|
@@ -90,8 +90,18 @@ export interface ArenaBeganInput extends TouchBeganInput {
|
|
|
90
90
|
export interface GestureArenaOptions {
|
|
91
91
|
clock?: GestureClock;
|
|
92
92
|
environment?: GestureEnvironment;
|
|
93
|
-
/**
|
|
94
|
-
|
|
93
|
+
/**
|
|
94
|
+
* Touches a recognizer now owns, and the recognizer that claimed them. A
|
|
95
|
+
* claim comes in any state: a recognizer that waits on another's failure
|
|
96
|
+
* claims while `possible`, and a swipe claims as `ended`. Each claim is
|
|
97
|
+
* reported once.
|
|
98
|
+
*/
|
|
99
|
+
onClaim?: (
|
|
100
|
+
touchIds: ReadonlyArray<number>,
|
|
101
|
+
claimer: GestureRecognizer,
|
|
102
|
+
) => void;
|
|
103
|
+
/** Claimed touches given back to the browser (releaseClaim). */
|
|
104
|
+
onRelease?: (touchIds: ReadonlyArray<number>) => void;
|
|
95
105
|
/** A click deferred at touch end resolved: replay it, or drop it. */
|
|
96
106
|
onDeferredClick?: (touch: GestureTouch, allowed: boolean) => void;
|
|
97
107
|
/**
|
|
@@ -100,6 +110,13 @@ export interface GestureArenaOptions {
|
|
|
100
110
|
* listening now (`spec:AST-094` FR24).
|
|
101
111
|
*/
|
|
102
112
|
onIdle?: () => void;
|
|
113
|
+
/**
|
|
114
|
+
* Whether the browser keeps a contact for itself while it is down. Then no
|
|
115
|
+
* claiming recognizer recognizes with it before its lift. So a mouse or pen
|
|
116
|
+
* drag that starts on selectable text selects that text. A recognition at
|
|
117
|
+
* the lift, such as a tap's, is unaffected.
|
|
118
|
+
*/
|
|
119
|
+
refusesClaim?: (touch: GestureTouch) => boolean;
|
|
103
120
|
/** Every recognizer transition, for debugging and tests. */
|
|
104
121
|
onTransition?: (
|
|
105
122
|
recognizer: GestureRecognizer,
|
|
@@ -553,7 +570,8 @@ export class GestureArena {
|
|
|
553
570
|
): void {
|
|
554
571
|
if (
|
|
555
572
|
!internals(recognizer).shouldBegin() ||
|
|
556
|
-
this.isVetoedByConsumer(recognizer)
|
|
573
|
+
this.isVetoedByConsumer(recognizer) ||
|
|
574
|
+
this.isRefusedByBrowser(recognizer)
|
|
557
575
|
) {
|
|
558
576
|
this.fail(recognizer);
|
|
559
577
|
return;
|
|
@@ -635,6 +653,23 @@ export class GestureArena {
|
|
|
635
653
|
return winner.canPrevent(other) && other.canBePrevented(winner);
|
|
636
654
|
}
|
|
637
655
|
|
|
656
|
+
/**
|
|
657
|
+
* Whether a recognizer that claims touches would take a contact the
|
|
658
|
+
* browser keeps while it is down (`refusesClaim`).
|
|
659
|
+
*/
|
|
660
|
+
private isRefusedByBrowser(recognizer: GestureRecognizer): boolean {
|
|
661
|
+
const refuses = this.options.refusesClaim;
|
|
662
|
+
if (refuses == null || !recognizer.claimsTouches) {
|
|
663
|
+
return false;
|
|
664
|
+
}
|
|
665
|
+
return recognizer.trackedTouches.some(
|
|
666
|
+
touch =>
|
|
667
|
+
touch.phase !== 'ended' &&
|
|
668
|
+
touch.phase !== 'cancelled' &&
|
|
669
|
+
refuses(touch),
|
|
670
|
+
);
|
|
671
|
+
}
|
|
672
|
+
|
|
638
673
|
/** UIControl's rule: a control may veto an overlapping ancestor recognizer. */
|
|
639
674
|
private isVetoedByConsumer(recognizer: GestureRecognizer): boolean {
|
|
640
675
|
for (const id of internals(recognizer).seenTouchIds) {
|
|
@@ -674,6 +709,7 @@ export class GestureArena {
|
|
|
674
709
|
recognizer: GestureRecognizer,
|
|
675
710
|
keep: ReadonlySet<number> = new Set(),
|
|
676
711
|
): void {
|
|
712
|
+
const released: number[] = [];
|
|
677
713
|
for (const touch of recognizer.trackedTouches) {
|
|
678
714
|
const record = this.records.get(touch.id);
|
|
679
715
|
if (record == null || !record.isClaimed || keep.has(touch.id)) {
|
|
@@ -692,8 +728,12 @@ export class GestureArena {
|
|
|
692
728
|
);
|
|
693
729
|
if (!isOwned) {
|
|
694
730
|
record.isClaimed = false;
|
|
731
|
+
released.push(touch.id);
|
|
695
732
|
}
|
|
696
733
|
}
|
|
734
|
+
if (released.length > 0) {
|
|
735
|
+
this.options.onRelease?.(released);
|
|
736
|
+
}
|
|
697
737
|
}
|
|
698
738
|
|
|
699
739
|
private apply(recognizer: GestureRecognizer, next: GestureState): void {
|
|
@@ -768,7 +808,7 @@ export class GestureArena {
|
|
|
768
808
|
}
|
|
769
809
|
}
|
|
770
810
|
if (claimed.length > 0) {
|
|
771
|
-
this.options.onClaim?.(claimed);
|
|
811
|
+
this.options.onClaim?.(claimed, recognizer);
|
|
772
812
|
}
|
|
773
813
|
}
|
|
774
814
|
|