@astryxdesign/core 0.7.9-canary.a1a6151 → 0.7.9-canary.cfd25ce
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/CheckboxInput/CheckboxInput.d.ts.map +1 -1
- package/dist/CheckboxInput/CheckboxInput.js +54 -11
- package/dist/InputGroup/InputGroup.d.ts +4 -3
- package/dist/InputGroup/InputGroup.d.ts.map +1 -1
- package/dist/InputGroup/InputGroup.js +2 -2
- package/dist/InputGroup/groupStyles.d.ts +1 -1
- package/dist/InputGroup/groupStyles.d.ts.map +1 -1
- package/dist/InputGroup/groupStyles.js +1 -1
- package/dist/RadioList/RadioListItem.d.ts.map +1 -1
- package/dist/RadioList/RadioListItem.js +18 -3
- package/dist/astryx.css +5 -1
- package/dist/gestures/PanGestureRecognizer.d.ts +11 -1
- package/dist/gestures/PanGestureRecognizer.d.ts.map +1 -1
- package/dist/gestures/PanGestureRecognizer.js +15 -1
- package/dist/gestures/ScreenEdgePanGestureRecognizer.d.ts +6 -1
- package/dist/gestures/ScreenEdgePanGestureRecognizer.d.ts.map +1 -1
- package/dist/utils/touchHitArea.stylex.d.ts +35 -0
- package/dist/utils/touchHitArea.stylex.d.ts.map +1 -0
- package/dist/utils/touchHitArea.stylex.js +32 -0
- package/package.json +4 -4
- package/src/CheckboxInput/CheckboxInput.doc.mjs +3 -2
- package/src/CheckboxInput/CheckboxInput.tsx +71 -9
- package/src/InputGroup/InputGroup.doc.mjs +4 -3
- package/src/InputGroup/InputGroup.tsx +4 -3
- package/src/InputGroup/groupStyles.ts +3 -1
- package/src/RadioList/RadioListItem.doc.mjs +3 -2
- package/src/RadioList/RadioListItem.tsx +21 -2
- package/src/gestures/PanGestureRecognizer.ts +19 -2
- package/src/gestures/README.md +2 -1
- package/src/gestures/ScreenEdgePanGestureRecognizer.ts +6 -1
- package/src/utils/touchHitArea.stylex.ts +41 -0
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CheckboxInput.d.ts","sourceRoot":"","sources":["../../src/CheckboxInput/CheckboxInput.tsx"],"names":[],"mappings":"AAIA;;;;;;;;;;;;GAYG;AAEH,OAAO,EAML,KAAK,WAAW,EAChB,KAAK,UAAU,
|
|
1
|
+
{"version":3,"file":"CheckboxInput.d.ts","sourceRoot":"","sources":["../../src/CheckboxInput/CheckboxInput.tsx"],"names":[],"mappings":"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"}
|
|
@@ -36,6 +36,8 @@ import { usePressFeedback } from "../hooks/usePressFeedback.js";
|
|
|
36
36
|
import { useIndicator } from "../Indicator/useIndicator.js";
|
|
37
37
|
import { themeProps } from "../utils/themeProps.js";
|
|
38
38
|
import { useMergedRefs } from "../hooks/useMergedRefs.js";
|
|
39
|
+
import { watchHitArea, writeHitInsets } from "../utils/hitArea.js";
|
|
40
|
+
import { touchHitAreaStyles } from "../utils/touchHitArea.stylex.js";
|
|
39
41
|
import { registerEnglishFallback, useComponentTranslator } from "../i18n/useComponentTranslator.js";
|
|
40
42
|
import english from "../i18n/generated-english/CheckboxInput/CheckboxInput.generated.js";
|
|
41
43
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
@@ -62,6 +64,10 @@ const styles = {
|
|
|
62
64
|
kOIVth: "xxhr3t",
|
|
63
65
|
$$css: true
|
|
64
66
|
},
|
|
67
|
+
hitAreaHost: {
|
|
68
|
+
kVAEAm: "xrxqdxo",
|
|
69
|
+
$$css: true
|
|
70
|
+
},
|
|
65
71
|
indicatorPressImage: {
|
|
66
72
|
"--_indicator-press-image": "x1dueop1 x189o2zt",
|
|
67
73
|
$$css: true
|
|
@@ -210,6 +216,49 @@ export function CheckboxInput({
|
|
|
210
216
|
const {
|
|
211
217
|
focusProps
|
|
212
218
|
} = useIndicatorFocusRing(indicatorRef);
|
|
219
|
+
|
|
220
|
+
// Under a coarse pointer a labelled checkbox's row grows toward 44 px
|
|
221
|
+
// through an invisible band, and nothing changes size. A label-hidden
|
|
222
|
+
// checkbox is a marker in another component's row, which keeps its target.
|
|
223
|
+
const hasHitArea = !isLabelHidden && !isDisabled && !isReadOnly;
|
|
224
|
+
const inputRef = useRef(null);
|
|
225
|
+
// The watch lives as long as the row is attached: the ref's cleanup stops
|
|
226
|
+
// it, and a replaced row starts its own.
|
|
227
|
+
const watchRowHitArea = useCallback(row => {
|
|
228
|
+
if (row == null || !hasHitArea) {
|
|
229
|
+
return;
|
|
230
|
+
}
|
|
231
|
+
return watchHitArea(row, insets => writeHitInsets(row, insets));
|
|
232
|
+
}, [hasHitArea]);
|
|
233
|
+
// The disabled-message tooltip listens for hover and focus on the whole
|
|
234
|
+
// row, a larger trigger. It anchors on the checkbox itself (below), so it
|
|
235
|
+
// shows next to the control. Its handlers check isEnabled, so the row
|
|
236
|
+
// always attaches them.
|
|
237
|
+
const rowRef = useMergedRefs(disabledMessageTooltip.interactionRef, watchRowHitArea);
|
|
238
|
+
// Where a press on the row itself began. A browser can move a tap's click
|
|
239
|
+
// onto the row's edge, so the band is judged from the press.
|
|
240
|
+
const pressPointRef = useRef(null);
|
|
241
|
+
const handleRowPointerDown = event => {
|
|
242
|
+
pressPointRef.current = event.target === event.currentTarget ? {
|
|
243
|
+
x: event.clientX,
|
|
244
|
+
y: event.clientY
|
|
245
|
+
} : null;
|
|
246
|
+
};
|
|
247
|
+
// A press that began on the band, outside the row's box, toggles the
|
|
248
|
+
// checkbox, as a tap on the box does. A click from code has no press count
|
|
249
|
+
// (`detail` is 0), so it never toggles from here.
|
|
250
|
+
const handleRowClick = event => {
|
|
251
|
+
const press = pressPointRef.current;
|
|
252
|
+
pressPointRef.current = null;
|
|
253
|
+
if (press == null || event.detail === 0 || event.target !== event.currentTarget) {
|
|
254
|
+
return;
|
|
255
|
+
}
|
|
256
|
+
const box = event.currentTarget.getBoundingClientRect();
|
|
257
|
+
const isOnBand = press.x < box.left || press.x > box.right || press.y < box.top || press.y > box.bottom;
|
|
258
|
+
if (isOnBand) {
|
|
259
|
+
inputRef.current?.click();
|
|
260
|
+
}
|
|
261
|
+
};
|
|
213
262
|
const isIndeterminate = optimisticValue === 'indeterminate';
|
|
214
263
|
const isChecked = optimisticValue === true;
|
|
215
264
|
|
|
@@ -249,17 +298,11 @@ export function CheckboxInput({
|
|
|
249
298
|
size
|
|
250
299
|
}), stylex.props(width != null && dynamicWidthStyles.width(width), xstyle), className, style),
|
|
251
300
|
children: [/*#__PURE__*/_jsxs("div", {
|
|
252
|
-
ref:
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
// positioning anchors on the checkbox itself (below) so the tooltip
|
|
256
|
-
// appears next to the control, not the far edge of the row.
|
|
257
|
-
// Handlers are gated internally by isEnabled, so attaching
|
|
258
|
-
// unconditionally is safe.
|
|
259
|
-
disabledMessageTooltip.interactionRef(el);
|
|
260
|
-
},
|
|
301
|
+
ref: rowRef,
|
|
302
|
+
onPointerDown: hasHitArea ? handleRowPointerDown : undefined,
|
|
303
|
+
onClick: hasHitArea ? handleRowClick : undefined,
|
|
261
304
|
...(isDisabled ? undefined : pressable),
|
|
262
|
-
...stylex.props(styles.container, isLabelHidden && styles.containerLabelHidden,
|
|
305
|
+
...stylex.props(styles.container, isLabelHidden && styles.containerLabelHidden, hasHitArea && styles.hitAreaHost, hasHitArea && touchHitAreaStyles.layer,
|
|
263
306
|
// Hover and focus reach the checkbox visual through this ancestor
|
|
264
307
|
// marker rather than props, so the whole row drives it.
|
|
265
308
|
!isDisabled && indicatorScope, !isDisabled && styles.indicatorPressImage,
|
|
@@ -272,7 +315,7 @@ export function CheckboxInput({
|
|
|
272
315
|
...focusProps,
|
|
273
316
|
children: [/*#__PURE__*/_jsx("input", {
|
|
274
317
|
...rest,
|
|
275
|
-
ref: useMergedRefs(ref, indeterminateRef, disabledMessageTooltip.positionRef),
|
|
318
|
+
ref: useMergedRefs(ref, indeterminateRef, disabledMessageTooltip.positionRef, inputRef),
|
|
276
319
|
id: id,
|
|
277
320
|
type: "checkbox"
|
|
278
321
|
// Withhold the name while disabled: the input stays focusable (not
|
|
@@ -59,7 +59,8 @@ export interface InputGroupProps extends Omit<BaseProps<HTMLDivElement>, 'childr
|
|
|
59
59
|
*/
|
|
60
60
|
size?: InputGroupSize;
|
|
61
61
|
/**
|
|
62
|
-
*
|
|
62
|
+
* Validation status. Its message shows below the group. The group border
|
|
63
|
+
* does not change; a theme can style the row from `data-status`.
|
|
63
64
|
*/
|
|
64
65
|
status?: InputStatus;
|
|
65
66
|
/**
|
|
@@ -72,8 +73,8 @@ export interface InputGroupProps extends Omit<BaseProps<HTMLDivElement>, 'childr
|
|
|
72
73
|
'data-testid'?: string;
|
|
73
74
|
}
|
|
74
75
|
/**
|
|
75
|
-
* Groups an input with prefix
|
|
76
|
-
*
|
|
76
|
+
* Groups an input with prefix and suffix addons in one connected row. A
|
|
77
|
+
* focused member stays above its neighbours, so its focus ring shows in full.
|
|
77
78
|
*
|
|
78
79
|
* @example
|
|
79
80
|
* ```
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"InputGroup.d.ts","sourceRoot":"","sources":["../../src/InputGroup/InputGroup.tsx"],"names":[],"mappings":"AAIA;;;;;;;;;;;;;;;GAeG;AAEH,OAAO,EAAsB,KAAK,SAAS,EAAC,MAAM,OAAO,CAAC;AAG1D,OAAO,EAAQ,KAAK,WAAW,EAAC,MAAM,UAAU,CAAC;AAGjD,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,cAAc,CAAC;AAK5C,MAAM,MAAM,cAAc,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,CAAC;AA0BhD,MAAM,WAAW,eAAgB,SAAQ,IAAI,CAC3C,SAAS,CAAC,cAAc,CAAC,EACzB,UAAU,CACX;IACC,mDAAmD;IACnD,GAAG,CAAC,EAAE,KAAK,CAAC,GAAG,CAAC,cAAc,CAAC,CAAC;IAEhC;;OAEG;IACH,QAAQ,EAAE,SAAS,CAAC;IAEpB;;OAEG;IACH,KAAK,EAAE,MAAM,CAAC;IAEd;;;OAGG;IACH,aAAa,CAAC,EAAE,OAAO,CAAC;IAExB;;OAEG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;IAErB;;;OAGG;IACH,UAAU,CAAC,EAAE,OAAO,CAAC;IAErB;;;OAGG;IACH,UAAU,CAAC,EAAE,OAAO,CAAC;IAErB;;;OAGG;IACH,UAAU,CAAC,EAAE,OAAO,CAAC;IAErB;;;OAGG;IACH,IAAI,CAAC,EAAE,cAAc,CAAC;IAEtB
|
|
1
|
+
{"version":3,"file":"InputGroup.d.ts","sourceRoot":"","sources":["../../src/InputGroup/InputGroup.tsx"],"names":[],"mappings":"AAIA;;;;;;;;;;;;;;;GAeG;AAEH,OAAO,EAAsB,KAAK,SAAS,EAAC,MAAM,OAAO,CAAC;AAG1D,OAAO,EAAQ,KAAK,WAAW,EAAC,MAAM,UAAU,CAAC;AAGjD,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,cAAc,CAAC;AAK5C,MAAM,MAAM,cAAc,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,CAAC;AA0BhD,MAAM,WAAW,eAAgB,SAAQ,IAAI,CAC3C,SAAS,CAAC,cAAc,CAAC,EACzB,UAAU,CACX;IACC,mDAAmD;IACnD,GAAG,CAAC,EAAE,KAAK,CAAC,GAAG,CAAC,cAAc,CAAC,CAAC;IAEhC;;OAEG;IACH,QAAQ,EAAE,SAAS,CAAC;IAEpB;;OAEG;IACH,KAAK,EAAE,MAAM,CAAC;IAEd;;;OAGG;IACH,aAAa,CAAC,EAAE,OAAO,CAAC;IAExB;;OAEG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;IAErB;;;OAGG;IACH,UAAU,CAAC,EAAE,OAAO,CAAC;IAErB;;;OAGG;IACH,UAAU,CAAC,EAAE,OAAO,CAAC;IAErB;;;OAGG;IACH,UAAU,CAAC,EAAE,OAAO,CAAC;IAErB;;;OAGG;IACH,IAAI,CAAC,EAAE,cAAc,CAAC;IAEtB;;;OAGG;IACH,MAAM,CAAC,EAAE,WAAW,CAAC;IAErB;;OAEG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC;IAEtB;;OAEG;IACH,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,UAAU,CAAC,EACzB,QAAQ,EACR,KAAK,EACL,aAAqB,EACrB,WAAW,EACX,UAAU,EAAE,cAAsB,EAClC,UAAkB,EAClB,UAAkB,EAClB,IAAI,EAAE,QAAQ,EACd,MAAM,EACN,YAAY,EACZ,MAAM,EACN,SAAS,EACT,KAAK,EACL,GAAG,EACH,aAAa,EAAE,MAAM,EACrB,GAAG,IAAI,EACR,EAAE,eAAe,+BA8EjB;yBA/Fe,UAAU"}
|
|
@@ -57,8 +57,8 @@ const sizeStyles = {
|
|
|
57
57
|
}
|
|
58
58
|
};
|
|
59
59
|
/**
|
|
60
|
-
* Groups an input with prefix
|
|
61
|
-
*
|
|
60
|
+
* Groups an input with prefix and suffix addons in one connected row. A
|
|
61
|
+
* focused member stays above its neighbours, so its focus ring shows in full.
|
|
62
62
|
*
|
|
63
63
|
* @example
|
|
64
64
|
* ```
|
|
@@ -33,7 +33,7 @@ export declare const groupStyles: Readonly<{
|
|
|
33
33
|
readonly borderStartEndRadius: stylex.StyleXClassNameFor<"borderStartEndRadius", 0 | "8px">;
|
|
34
34
|
readonly borderEndEndRadius: stylex.StyleXClassNameFor<"borderEndEndRadius", 0 | "8px">;
|
|
35
35
|
readonly ':focus-within': stylex.StyleXClassNameFor<":focus-within", {
|
|
36
|
-
readonly zIndex:
|
|
36
|
+
readonly zIndex: 2;
|
|
37
37
|
}>;
|
|
38
38
|
}>;
|
|
39
39
|
}>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"groupStyles.d.ts","sourceRoot":"","sources":["../../src/InputGroup/groupStyles.ts"],"names":[],"mappings":"AAEA;;;;;GAKG;AAEH,OAAO,KAAK,MAAM,MAAM,kBAAkB,CAAC;AAG3C;;;;;GAKG;AACH,eAAO,MAAM,mBAAmB;;CAEtB,CAAC;AA0BX,eAAO,MAAM,WAAW;;;;;;;;;;;;;;;;;;;;;;
|
|
1
|
+
{"version":3,"file":"groupStyles.d.ts","sourceRoot":"","sources":["../../src/InputGroup/groupStyles.ts"],"names":[],"mappings":"AAEA;;;;;GAKG;AAEH,OAAO,KAAK,MAAM,MAAM,kBAAkB,CAAC;AAG3C;;;;;GAKG;AACH,eAAO,MAAM,mBAAmB;;CAEtB,CAAC;AA0BX,eAAO,MAAM,WAAW;;;;;;;;;;;;;;;;;;;;;;EA0DtB,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"RadioListItem.d.ts","sourceRoot":"","sources":["../../src/RadioList/RadioListItem.tsx"],"names":[],"mappings":"AAIA;;;;;;;;;;;;;;GAcG;AAEH,OAAO,KAAK,EAAE,
|
|
1
|
+
{"version":3,"file":"RadioListItem.d.ts","sourceRoot":"","sources":["../../src/RadioList/RadioListItem.tsx"],"names":[],"mappings":"AAIA;;;;;;;;;;;;;;GAcG;AAEH,OAAO,KAAK,EAAE,EAAkC,KAAK,SAAS,EAAC,MAAM,OAAO,CAAC;AAG7E,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,cAAc,CAAC;AAe5C,OAAO,EAAO,KAAK,SAAS,EAAC,MAAM,cAAc,CAAC;AAsIlD,MAAM,WAAW,kBAAmB,SAAQ,SAAS,CAAC,cAAc,CAAC;IACnE,GAAG,CAAC,EAAE,KAAK,CAAC,GAAG,CAAC,cAAc,CAAC,CAAC;IAChC;;;;;OAKG;IACH,KAAK,CAAC,EAAE,SAAS,CAAC;IAClB;;;;;;;;;;OAUG;IACH,KAAK,EAAE,SAAS,CAAC;IACjB;;;;;;;;;;;;;;OAcG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB;;OAEG;IACH,KAAK,EAAE,MAAM,CAAC;IACd;;;;;OAKG;IACH,WAAW,CAAC,EAAE,SAAS,CAAC;IACxB;;;OAGG;IACH,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB;;OAEG;IACH,YAAY,CAAC,EAAE,SAAS,CAAC;IACzB;;OAEG;IACH,UAAU,CAAC,EAAE,SAAS,CAAC;CACxB;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,aAAa,CAAC,EAC5B,GAAG,EACH,KAAK,EACL,YAAY,EAAE,SAAS,EACvB,KAAK,EACL,WAAW,EACX,UAAU,EAAE,cAAsB,EAClC,YAAY,EACZ,UAAU,EACV,KAAK,EACL,MAAM,EACN,SAAS,EACT,KAAK,EACL,OAAO,EACP,GAAG,IAAI,EACR,EAAE,kBAAkB,qBAuMpB;yBAtNe,aAAa"}
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
* - /apps/storybook/stories/RadioList.stories.tsx
|
|
18
18
|
* - /packages/cli/assets/templates/blocks/components/RadioList/ (showcase blocks)
|
|
19
19
|
*/
|
|
20
|
-
import React, { use, useId, useRef } from 'react';
|
|
20
|
+
import React, { use, useCallback, useId, useRef } from 'react';
|
|
21
21
|
import * as stylex from '@stylexjs/stylex';
|
|
22
22
|
import { RadioListContext } from "./RadioList.js";
|
|
23
23
|
import { RadioListReasonContext } from "./RadioListReasonContext.js";
|
|
@@ -31,6 +31,9 @@ import "../Indicator/indicator.markers.stylex.js";
|
|
|
31
31
|
import { indicatorScope } from "../Indicator/indicator.markers.stylex.js";
|
|
32
32
|
import { useIndicatorFocusRing } from "../hooks/useIndicatorFocusRing.js";
|
|
33
33
|
import { usePressFeedback } from "../hooks/usePressFeedback.js";
|
|
34
|
+
import { useMergedRefs } from "../hooks/useMergedRefs.js";
|
|
35
|
+
import { watchHitArea, writeHitInsets } from "../utils/hitArea.js";
|
|
36
|
+
import { touchHitAreaStyles } from "../utils/touchHitArea.stylex.js";
|
|
34
37
|
import { useIndicator } from "../Indicator/useIndicator.js";
|
|
35
38
|
import { Item } from "../Item/Item.js";
|
|
36
39
|
import { themeProps } from "../utils/themeProps.js";
|
|
@@ -186,6 +189,18 @@ export function RadioListItem({
|
|
|
186
189
|
// since the visible label is now a plain (non-`<label>`) text node — a real
|
|
187
190
|
// `<label htmlFor>` would double-fire under delegation.
|
|
188
191
|
const radioRef = useRef(null);
|
|
192
|
+
|
|
193
|
+
// Under a coarse pointer an option's row grows toward 44 px through an
|
|
194
|
+
// invisible band, and the row keeps its size. A tap on the band lands on
|
|
195
|
+
// the row, which hands it to the radio. The watch lives as long as the row
|
|
196
|
+
// is attached: the ref's cleanup stops it.
|
|
197
|
+
const watchRowHitArea = useCallback(row => {
|
|
198
|
+
if (row == null || isDisabled) {
|
|
199
|
+
return;
|
|
200
|
+
}
|
|
201
|
+
return watchHitArea(row, insets => writeHitInsets(row, insets));
|
|
202
|
+
}, [isDisabled]);
|
|
203
|
+
const mergedRowRef = useMergedRefs(ref, watchRowHitArea);
|
|
189
204
|
const radioCircle = /*#__PURE__*/_jsxs("div", {
|
|
190
205
|
...stylex.props(styles.radioWrapper, wrapperSizeStyles[size], !isDisabled && styles.indicatorPressOverlay),
|
|
191
206
|
...focusProps,
|
|
@@ -251,7 +266,7 @@ export function RadioListItem({
|
|
|
251
266
|
children: [radioCircle, startContent]
|
|
252
267
|
}) : radioCircle;
|
|
253
268
|
return /*#__PURE__*/_jsx(Item, {
|
|
254
|
-
ref:
|
|
269
|
+
ref: mergedRowRef,
|
|
255
270
|
...(isDisabled ? undefined : pressable),
|
|
256
271
|
startContent: mediaContent
|
|
257
272
|
// Delegate row-surface clicks (label text, description, and the empty
|
|
@@ -283,7 +298,7 @@ export function RadioListItem({
|
|
|
283
298
|
// interactive hover/press background. Applied after Item's own base
|
|
284
299
|
// styles so it wins within the base layer; a `radio-list-item` theme
|
|
285
300
|
// still overrides it from the higher `astryx-theme` layer.
|
|
286
|
-
rowStyles.root, !isDisabled && rowStyles.press, xstyle],
|
|
301
|
+
rowStyles.root, !isDisabled && rowStyles.press, !isDisabled && touchHitAreaStyles.layer, xstyle],
|
|
287
302
|
...mergeProps(
|
|
288
303
|
// One target for every row, carrying its size and runtime state so a
|
|
289
304
|
// theme can express "selected option at large" or restyle disabled
|
package/dist/astryx.css
CHANGED
|
@@ -1633,7 +1633,7 @@
|
|
|
1633
1633
|
.xttggg:is([dir="rtl"] *):not(#\#):not(#\#):not(#\#){transform:translateX(100%)}
|
|
1634
1634
|
.x1nyx83j:is([dir="rtl"] *):not(#\#):not(#\#):not(#\#){transform:translateX(50%)}
|
|
1635
1635
|
.x9cjr8z:popover-open:not(#\#):not(#\#):not(#\#){transform:translateY(0)}
|
|
1636
|
-
.
|
|
1636
|
+
.x1pc0aet:focus-within:not(#\#):not(#\#):not(#\#){z-index:2}
|
|
1637
1637
|
.xf5goe6.xf5goe6:where(.astryx-input-group > [data-astryx-input-group-item]):focus-visible:not(#\#):not(#\#):not(#\#){z-index:2}
|
|
1638
1638
|
.x151j4nw:has(input[aria-disabled="true"]:focus-visible):not(#\#):not(#\#):not(#\#){box-shadow:inset 0 0 0 2px var(--color-accent-muted)}
|
|
1639
1639
|
.xwmge8v:has(> [aria-disabled="true"]:focus-visible):not(#\#):not(#\#):not(#\#){box-shadow:inset 0 0 0 2px var(--color-accent-muted)}
|
|
@@ -2322,6 +2322,7 @@
|
|
|
2322
2322
|
@media (prefers-reduced-motion: reduce) and (not (pointer: coarse)) and (not (pointer: coarse)){.xxsp3er.xxsp3er[data-astryx-press="fading"]:not(#\#):not(#\#):not(#\#):not(#\#):not(#\#){animation-name:none}}
|
|
2323
2323
|
@media (forced-colors: active){.x1pjcgc3.x1pjcgc3[data-astryx-press="fading"]:not(#\#):not(#\#):not(#\#):not(#\#):not(#\#){background-color:Highlight}}
|
|
2324
2324
|
@media (forced-colors: active){.x1r4mieh.x1r4mieh[data-astryx-press="on"]:not(#\#):not(#\#):not(#\#):not(#\#):not(#\#){background-color:Highlight}}
|
|
2325
|
+
@media (pointer: coarse){.x16x82qr.x16x82qr:not(#\#):not(#\#):not(#\#):not(#\#):not(#\#)::after{inset:calc(var(--astryx-hit-inset-top,0px) * -1) calc(var(--astryx-hit-inset-right,0px) * -1) calc(var(--astryx-hit-inset-bottom,0px) * -1) calc(var(--astryx-hit-inset-left,0px) * -1)}}
|
|
2325
2326
|
@starting-style{@media (prefers-reduced-motion: reduce) and (pointer: coarse){.xgo67oj.xgo67oj.xgo67oj:not(#\#):not(#\#):not(#\#):not(#\#):not(#\#){opacity:0}}}
|
|
2326
2327
|
@starting-style{@media (prefers-reduced-motion: reduce){.xspp5ug.xspp5ug.xspp5ug:not(#\#):not(#\#):not(#\#):not(#\#):not(#\#){transform:translateY(0)}}}
|
|
2327
2328
|
@media (pointer: coarse){.x10ogxw0.x10ogxw0.x10ogxw0:where(:not(:disabled,[aria-disabled="true"]))[data-astryx-press="on"]:not(#\#):not(#\#):not(#\#):not(#\#):not(#\#){background-image:none}}
|
|
@@ -2493,7 +2494,10 @@
|
|
|
2493
2494
|
.x1c108yo:is([dir="rtl"] *):not(#\#):not(#\#):not(#\#):not(#\#):not(#\#):not(#\#):not(#\#)::after{transform:translateX(-100%)}
|
|
2494
2495
|
.x12ayhd5:is([dir="rtl"] *):not(#\#):not(#\#):not(#\#):not(#\#):not(#\#):not(#\#):not(#\#)::after{transform:translateX(100%)}
|
|
2495
2496
|
@media (forced-colors: none){.x1my3e5g.x1my3e5g:not(#\#):not(#\#):not(#\#):not(#\#):not(#\#):not(#\#):not(#\#)::before{background-color:var(--color-overlay-pressed)}}
|
|
2497
|
+
@media (pointer: coarse){.x1c440c0.x1c440c0:not(#\#):not(#\#):not(#\#):not(#\#):not(#\#):not(#\#):not(#\#)::after{clip-path:polygon(evenodd,0 0,100% 0,100% 100%,0 100%,0 0,var(--astryx-hit-inset-left,0px) var(--astryx-hit-inset-top,0px),calc(100% - var(--astryx-hit-inset-right,0px)) var(--astryx-hit-inset-top,0px),calc(100% - var(--astryx-hit-inset-right,0px)) calc(100% - var(--astryx-hit-inset-bottom,0px)),var(--astryx-hit-inset-left,0px) calc(100% - var(--astryx-hit-inset-bottom,0px)),var(--astryx-hit-inset-left,0px) var(--astryx-hit-inset-top,0px))}}
|
|
2498
|
+
@media (pointer: coarse){.x1fjtqre.x1fjtqre:not(#\#):not(#\#):not(#\#):not(#\#):not(#\#):not(#\#):not(#\#)::after{content:""}}
|
|
2496
2499
|
@media (pointer: coarse){.x1ep27e9.x1ep27e9:not(#\#):not(#\#):not(#\#):not(#\#):not(#\#):not(#\#):not(#\#)::after{display:block}}
|
|
2500
|
+
@media (pointer: coarse){.x7b72wb.x7b72wb:not(#\#):not(#\#):not(#\#):not(#\#):not(#\#):not(#\#):not(#\#)::after{position:absolute}}
|
|
2497
2501
|
@media (prefers-reduced-motion: reduce){.xhhm9v0.xhhm9v0:not(#\#):not(#\#):not(#\#):not(#\#):not(#\#):not(#\#):not(#\#)::before{transition-delay:0s}}
|
|
2498
2502
|
@media (prefers-reduced-motion: reduce){.x1viac0w.x1viac0w:not(#\#):not(#\#):not(#\#):not(#\#):not(#\#):not(#\#):not(#\#)::backdrop{transition-duration:.01s}}
|
|
2499
2503
|
@media (prefers-reduced-motion: reduce){.x13sdc92.x13sdc92:not(#\#):not(#\#):not(#\#):not(#\#):not(#\#):not(#\#):not(#\#)::before{transition-duration:0s}}
|
|
@@ -6,7 +6,12 @@ export interface PanGestureRecognizerOptions extends GestureRecognizerOptions<Pa
|
|
|
6
6
|
minimumNumberOfTouches?: number;
|
|
7
7
|
/** @default Infinity */
|
|
8
8
|
maximumNumberOfTouches?: number;
|
|
9
|
-
|
|
9
|
+
/**
|
|
10
|
+
* The travel that begins the pan, px. A function gives each press its own
|
|
11
|
+
* threshold: it reads the press's first touch when that touch lands.
|
|
12
|
+
* @default UIKit's 10
|
|
13
|
+
*/
|
|
14
|
+
hysteresis?: number | ((touch: GestureTouch) => number);
|
|
10
15
|
/** Movement that may begin the pan; anything else fails it. @default 'any' */
|
|
11
16
|
direction?: PanDirection;
|
|
12
17
|
/**
|
|
@@ -51,6 +56,11 @@ export declare class PanGestureRecognizer extends GestureRecognizer {
|
|
|
51
56
|
/** The movement the begin threshold took (excluded from `translation()`). */
|
|
52
57
|
travelAtBegan: Point;
|
|
53
58
|
private readonly tracker;
|
|
59
|
+
/**
|
|
60
|
+
* Read once per press. A finger that lands later keeps the press's
|
|
61
|
+
* threshold.
|
|
62
|
+
*/
|
|
63
|
+
private readonly hysteresisFor;
|
|
54
64
|
/** The lift's event time once the pan ended: its release (AST-100 FR2). */
|
|
55
65
|
private releaseTime;
|
|
56
66
|
/** The centroid where the gesture started, in client px. */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"PanGestureRecognizer.d.ts","sourceRoot":"","sources":["../../src/gestures/PanGestureRecognizer.ts"],"names":[],"mappings":"AAuCA,OAAO,EAEL,iBAAiB,EAEjB,KAAK,wBAAwB,EAC7B,KAAK,mBAAmB,EACzB,MAAM,qBAAqB,CAAC;AAC7B,OAAO,KAAK,EAAC,mBAAmB,EAAE,YAAY,EAAE,KAAK,EAAC,MAAM,SAAS,CAAC;AAGtE,MAAM,MAAM,YAAY,GACtB,KAAK,GAAG,YAAY,GAAG,UAAU,GAAG,MAAM,GAAG,OAAO,GAAG,IAAI,GAAG,MAAM,CAAC;AAEvE,MAAM,WAAW,2BAA4B,SAAQ,wBAAwB,CAAC,oBAAoB,CAAC;IACjG,iBAAiB;IACjB,sBAAsB,CAAC,EAAE,MAAM,CAAC;IAChC,wBAAwB;IACxB,sBAAsB,CAAC,EAAE,MAAM,CAAC;IAChC,UAAU,CAAC,EAAE,MAAM,CAAC;
|
|
1
|
+
{"version":3,"file":"PanGestureRecognizer.d.ts","sourceRoot":"","sources":["../../src/gestures/PanGestureRecognizer.ts"],"names":[],"mappings":"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"}
|
|
@@ -74,6 +74,12 @@ export class PanGestureRecognizer extends GestureRecognizer {
|
|
|
74
74
|
lockedAxis = null;
|
|
75
75
|
/** The movement the begin threshold took (excluded from `translation()`). */
|
|
76
76
|
travelAtBegan = ZERO;
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Read once per press. A finger that lands later keeps the press's
|
|
80
|
+
* threshold.
|
|
81
|
+
*/
|
|
82
|
+
|
|
77
83
|
/** The lift's event time once the pan ended: its release (AST-100 FR2). */
|
|
78
84
|
releaseTime = undefined;
|
|
79
85
|
/** The centroid where the gesture started, in client px. */
|
|
@@ -87,7 +93,11 @@ export class PanGestureRecognizer extends GestureRecognizer {
|
|
|
87
93
|
super(options);
|
|
88
94
|
this.minimumNumberOfTouches = options.minimumNumberOfTouches ?? 1;
|
|
89
95
|
this.maximumNumberOfTouches = options.maximumNumberOfTouches ?? Number.POSITIVE_INFINITY;
|
|
90
|
-
|
|
96
|
+
const {
|
|
97
|
+
hysteresis
|
|
98
|
+
} = options;
|
|
99
|
+
this.hysteresisFor = typeof hysteresis === 'function' ? hysteresis : null;
|
|
100
|
+
this.hysteresis = typeof hysteresis === 'number' ? hysteresis : UIKIT_PAN.hysteresis.value;
|
|
91
101
|
this.direction = options.direction ?? 'any';
|
|
92
102
|
this.axisRatio = options.axisRatio ?? 1;
|
|
93
103
|
this.lockAxis = options.lockAxis ?? false;
|
|
@@ -177,6 +187,10 @@ export class PanGestureRecognizer extends GestureRecognizer {
|
|
|
177
187
|
}
|
|
178
188
|
touchesBegan(touches, event) {
|
|
179
189
|
if (this.numberOfTouches === touches.length) {
|
|
190
|
+
const first = touches[0];
|
|
191
|
+
if (this.hysteresisFor != null && first != null) {
|
|
192
|
+
this.hysteresis = this.hysteresisFor(first);
|
|
193
|
+
}
|
|
180
194
|
this.origin = centroid(this.touches.values());
|
|
181
195
|
this.adjust = ZERO;
|
|
182
196
|
this.raw = ZERO;
|
|
@@ -12,11 +12,16 @@ type PhysicalEdge = 'left' | 'right';
|
|
|
12
12
|
* A screen-edge pan is a finger's: it takes no `allowedPointerTypes` and no
|
|
13
13
|
* `allowedScrollTypes`, so neither a mouse, a trackpad nor a pen drives it.
|
|
14
14
|
*/
|
|
15
|
-
export interface ScreenEdgePanGestureRecognizerOptions extends Omit<PanGestureRecognizerOptions, 'direction' | 'allowedPointerTypes' | 'allowedScrollTypes'> {
|
|
15
|
+
export interface ScreenEdgePanGestureRecognizerOptions extends Omit<PanGestureRecognizerOptions, 'direction' | 'allowedPointerTypes' | 'allowedScrollTypes' | 'hysteresis'> {
|
|
16
16
|
/** The edges a touch may start from. @default ['leading'] */
|
|
17
17
|
edges?: ReadonlyArray<ScreenEdge>;
|
|
18
18
|
/** Width of the band at each edge, px. */
|
|
19
19
|
edgeRegion?: number;
|
|
20
|
+
/**
|
|
21
|
+
* The travel that begins the pan in its edge zone, px. It is one number for
|
|
22
|
+
* every press, since the zone sets the threshold itself. @default UIKit's 15
|
|
23
|
+
*/
|
|
24
|
+
hysteresis?: number;
|
|
20
25
|
}
|
|
21
26
|
export declare class ScreenEdgePanGestureRecognizer extends PanGestureRecognizer {
|
|
22
27
|
get kind(): PanGestureRecognizer['kind'];
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ScreenEdgePanGestureRecognizer.d.ts","sourceRoot":"","sources":["../../src/gestures/ScreenEdgePanGestureRecognizer.ts"],"names":[],"mappings":"AAsCA,OAAO,EAAmB,KAAK,mBAAmB,EAAC,MAAM,qBAAqB,CAAC;AAC/E,OAAO,EACL,oBAAoB,EACpB,KAAK,2BAA2B,EACjC,MAAM,wBAAwB,CAAC;AAChC,OAAO,KAAK,EAAC,mBAAmB,EAAE,YAAY,EAAC,MAAM,SAAS,CAAC;AAS/D;;;GAGG;AACH,MAAM,MAAM,UAAU,GAAG,SAAS,GAAG,UAAU,CAAC;AAEhD,kDAAkD;AAClD,KAAK,YAAY,GAAG,MAAM,GAAG,OAAO,CAAC;AAarC;;;GAGG;AACH,MAAM,WAAW,qCAAsC,SAAQ,IAAI,CACjE,2BAA2B,EAC3B,WAAW,GAAG,qBAAqB,GAAG,oBAAoB,
|
|
1
|
+
{"version":3,"file":"ScreenEdgePanGestureRecognizer.d.ts","sourceRoot":"","sources":["../../src/gestures/ScreenEdgePanGestureRecognizer.ts"],"names":[],"mappings":"AAsCA,OAAO,EAAmB,KAAK,mBAAmB,EAAC,MAAM,qBAAqB,CAAC;AAC/E,OAAO,EACL,oBAAoB,EACpB,KAAK,2BAA2B,EACjC,MAAM,wBAAwB,CAAC;AAChC,OAAO,KAAK,EAAC,mBAAmB,EAAE,YAAY,EAAC,MAAM,SAAS,CAAC;AAS/D;;;GAGG;AACH,MAAM,MAAM,UAAU,GAAG,SAAS,GAAG,UAAU,CAAC;AAEhD,kDAAkD;AAClD,KAAK,YAAY,GAAG,MAAM,GAAG,OAAO,CAAC;AAarC;;;GAGG;AACH,MAAM,WAAW,qCAAsC,SAAQ,IAAI,CACjE,2BAA2B,EAC3B,WAAW,GAAG,qBAAqB,GAAG,oBAAoB,GAAG,YAAY,CAC1E;IACC,6DAA6D;IAC7D,KAAK,CAAC,EAAE,aAAa,CAAC,UAAU,CAAC,CAAC;IAClC,0CAA0C;IAC1C,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB;;;OAGG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,qBAAa,8BAA+B,SAAQ,oBAAoB;IACtE,IAAa,IAAI,IAAI,oBAAoB,CAAC,MAAM,CAAC,CAEhD;IAED,KAAK,EAAE,aAAa,CAAC,UAAU,CAAC,CAAC;IACjC,UAAU,EAAE,MAAM,CAAC;IACnB,0DAA0D;IAC1D,UAAU,EAAE,YAAY,GAAG,IAAI,CAAQ;IACvC;;;;OAIG;IACH,qBAAqB,EAAE,CAAC,MAAM,IAAI,CAAC,GAAG,IAAI,CAAQ;IAClD,mEAAmE;IACnE,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAS;IACxC,iFAAiF;IACjF,OAAO,CAAC,MAAM,CAA2B;gBAE7B,OAAO,GAAE,qCAA0C;IAiB/D;;;;;OAKG;IACH,IAAa,mBAAmB,IAAI,mBAAmB,CAWtD;IAED;;;;OAIG;IACH,IAAI,eAAe,IAAI,OAAO,CAE7B;IAED;;;;OAIG;IACH,aAAa,IAAI,IAAI;cAcF,YAAY,CAC7B,OAAO,EAAE,aAAa,CAAC,YAAY,CAAC,EACpC,KAAK,EAAE,mBAAmB,GACzB,IAAI;IAiCP;;;;;;;OAOG;IACH,OAAO,CAAC,QAAQ;cAyBG,KAAK,IAAI,IAAI;IAKhC,OAAO,CAAC,MAAM;CAaf"}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file touchHitArea.stylex.ts
|
|
3
|
+
* @input Uses StyleX and the insets that utils/hitArea.ts writes on a host
|
|
4
|
+
* @output Exports `touchHitAreaStyles.layer`, a host's invisible hit band
|
|
5
|
+
* under a coarse pointer
|
|
6
|
+
* @position Internal styling utility for CheckboxInput and RadioListItem
|
|
7
|
+
*
|
|
8
|
+
* The band is the host's `::after`, grown past each edge by the measured
|
|
9
|
+
* inset. A clip path cuts out the host's box, so its content keeps its taps.
|
|
10
|
+
* A tap on the band lands on the host. SYNC: hitArea.ts HIT_INSET_PROPERTIES.
|
|
11
|
+
*/
|
|
12
|
+
import * as stylex from '@stylexjs/stylex';
|
|
13
|
+
export declare const touchHitAreaStyles: Readonly<{
|
|
14
|
+
readonly layer: Readonly<{
|
|
15
|
+
readonly '::after': stylex.StyleXClassNameFor<"::after", {
|
|
16
|
+
readonly content: {
|
|
17
|
+
readonly default: null;
|
|
18
|
+
readonly "@media (pointer: coarse)": "\"\"";
|
|
19
|
+
};
|
|
20
|
+
readonly position: {
|
|
21
|
+
readonly default: null;
|
|
22
|
+
readonly "@media (pointer: coarse)": "absolute";
|
|
23
|
+
};
|
|
24
|
+
readonly inset: {
|
|
25
|
+
readonly default: null;
|
|
26
|
+
readonly "@media (pointer: coarse)": "calc(var(--astryx-hit-inset-top, 0px) * -1) calc(var(--astryx-hit-inset-right, 0px) * -1) calc(var(--astryx-hit-inset-bottom, 0px) * -1) calc(var(--astryx-hit-inset-left, 0px) * -1)";
|
|
27
|
+
};
|
|
28
|
+
readonly clipPath: {
|
|
29
|
+
readonly default: null;
|
|
30
|
+
readonly "@media (pointer: coarse)": "polygon(evenodd, 0 0, 100% 0, 100% 100%, 0 100%, 0 0, var(--astryx-hit-inset-left, 0px) var(--astryx-hit-inset-top, 0px), calc(100% - var(--astryx-hit-inset-right, 0px)) var(--astryx-hit-inset-top, 0px), calc(100% - var(--astryx-hit-inset-right, 0px)) calc(100% - var(--astryx-hit-inset-bottom, 0px)), var(--astryx-hit-inset-left, 0px) calc(100% - var(--astryx-hit-inset-bottom, 0px)), var(--astryx-hit-inset-left, 0px) var(--astryx-hit-inset-top, 0px))";
|
|
31
|
+
};
|
|
32
|
+
}>;
|
|
33
|
+
}>;
|
|
34
|
+
}>;
|
|
35
|
+
//# sourceMappingURL=touchHitArea.stylex.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"touchHitArea.stylex.d.ts","sourceRoot":"","sources":["../../src/utils/touchHitArea.stylex.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;GAUG;AAEH,OAAO,KAAK,MAAM,MAAM,kBAAkB,CAAC;AAU3C,eAAO,MAAM,kBAAkB;;;;;;;;;;;;;;;;;;;;;EAgB7B,CAAC"}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @file touchHitArea.stylex.ts
|
|
5
|
+
* @input Uses StyleX and the insets that utils/hitArea.ts writes on a host
|
|
6
|
+
* @output Exports `touchHitAreaStyles.layer`, a host's invisible hit band
|
|
7
|
+
* under a coarse pointer
|
|
8
|
+
* @position Internal styling utility for CheckboxInput and RadioListItem
|
|
9
|
+
*
|
|
10
|
+
* The band is the host's `::after`, grown past each edge by the measured
|
|
11
|
+
* inset. A clip path cuts out the host's box, so its content keeps its taps.
|
|
12
|
+
* A tap on the band lands on the host. SYNC: hitArea.ts HIT_INSET_PROPERTIES.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import * as stylex from '@stylexjs/stylex';
|
|
16
|
+
|
|
17
|
+
// StyleX reads these at build time, so they are spelled out.
|
|
18
|
+
const TOUCH = '@media (pointer: coarse)';
|
|
19
|
+
// The insets are measured in the viewport, so the sides are physical.
|
|
20
|
+
const TOP = 'var(--astryx-hit-inset-top, 0px)';
|
|
21
|
+
const RIGHT = 'var(--astryx-hit-inset-right, 0px)';
|
|
22
|
+
const BOTTOM = 'var(--astryx-hit-inset-bottom, 0px)';
|
|
23
|
+
const LEFT = 'var(--astryx-hit-inset-left, 0px)';
|
|
24
|
+
export const touchHitAreaStyles = {
|
|
25
|
+
layer: {
|
|
26
|
+
k5JduY: "x1fjtqre",
|
|
27
|
+
kwXMNM: "x7b72wb",
|
|
28
|
+
kv0HGH: "x16x82qr",
|
|
29
|
+
kJYu5r: "x1c440c0",
|
|
30
|
+
$$css: true
|
|
31
|
+
}
|
|
32
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@astryxdesign/core",
|
|
3
|
-
"version": "0.7.9-canary.
|
|
3
|
+
"version": "0.7.9-canary.cfd25ce",
|
|
4
4
|
"displayName": "Astryx Core",
|
|
5
5
|
"description": "The component library. Accessible, themeable React components with built-in spacing, dark mode, and StyleX styling.",
|
|
6
6
|
"author": "Meta Open Source",
|
|
@@ -870,9 +870,9 @@
|
|
|
870
870
|
"react-dom": ">=19.0.0"
|
|
871
871
|
},
|
|
872
872
|
"devDependencies": {
|
|
873
|
-
"@astryxdesign/a11y-spec": "0.7.9-canary.
|
|
874
|
-
"@astryxdesign/cli": "0.7.9-canary.
|
|
875
|
-
"@astryxdesign/interaction-harness": "0.7.9-canary.
|
|
873
|
+
"@astryxdesign/a11y-spec": "0.7.9-canary.cfd25ce",
|
|
874
|
+
"@astryxdesign/cli": "0.7.9-canary.cfd25ce",
|
|
875
|
+
"@astryxdesign/interaction-harness": "0.7.9-canary.cfd25ce",
|
|
876
876
|
"@babel/cli": "^7.29.7",
|
|
877
877
|
"@babel/core": "^7.29.7",
|
|
878
878
|
"@babel/preset-react": "^7.29.7",
|
|
@@ -109,7 +109,7 @@ export const docs = {
|
|
|
109
109
|
name: 'size',
|
|
110
110
|
type: "'sm' | 'md'",
|
|
111
111
|
description:
|
|
112
|
-
'The size of the checkbox. sm for compact layouts, md for default.',
|
|
112
|
+
'The size of the checkbox. sm for compact layouts, md for default. Under a coarse pointer a labelled checkbox’s target grows toward 44 px through an invisible hit area. It never shrinks below the row and stops at half the gap to a closer neighbour; nothing changes size.',
|
|
113
113
|
default: "'md'",
|
|
114
114
|
},
|
|
115
115
|
{
|
|
@@ -361,7 +361,8 @@ export const docsZh = {
|
|
|
361
361
|
{
|
|
362
362
|
name: 'size',
|
|
363
363
|
type: "'sm' | 'md'",
|
|
364
|
-
description:
|
|
364
|
+
description:
|
|
365
|
+
'复选框尺寸。sm 用于紧凑布局,md 为默认。在粗指针(触摸)下,带可见标签的复选框的目标通过不可见的点击区域向 44 px 扩展。它不小于所在行,与相邻控件较近时止于间距的一半;尺寸不变。',
|
|
365
366
|
default: "'md'",
|
|
366
367
|
},
|
|
367
368
|
{
|
|
@@ -24,6 +24,8 @@ import {
|
|
|
24
24
|
useTransition,
|
|
25
25
|
type ChangeEvent,
|
|
26
26
|
type FocusEvent,
|
|
27
|
+
type MouseEvent as ReactMouseEvent,
|
|
28
|
+
type PointerEvent as ReactPointerEvent,
|
|
27
29
|
type ReactNode,
|
|
28
30
|
} from 'react';
|
|
29
31
|
import * as stylex from '@stylexjs/stylex';
|
|
@@ -55,6 +57,8 @@ import {useIndicator} from '../Indicator/useIndicator';
|
|
|
55
57
|
import {themeProps} from '../utils/themeProps';
|
|
56
58
|
|
|
57
59
|
import {useMergedRefs} from '../hooks/useMergedRefs';
|
|
60
|
+
import {watchHitArea, writeHitInsets} from '../utils/hitArea';
|
|
61
|
+
import {touchHitAreaStyles} from '../utils/touchHitArea.stylex';
|
|
58
62
|
import {
|
|
59
63
|
registerEnglishFallback,
|
|
60
64
|
useComponentTranslator,
|
|
@@ -83,6 +87,10 @@ const styles = stylex.create({
|
|
|
83
87
|
containerLabelHidden: {
|
|
84
88
|
gap: 0,
|
|
85
89
|
},
|
|
90
|
+
// Under a coarse pointer the row contains its hit band.
|
|
91
|
+
hitAreaHost: {
|
|
92
|
+
position: {default: null, '@media (pointer: coarse)': 'relative'},
|
|
93
|
+
},
|
|
86
94
|
// An indicator in another package may not name this row's private press
|
|
87
95
|
// attribute. So the row hands its touch press image down to an indicator
|
|
88
96
|
// that paints its own press, but only in its own press states. A pressed
|
|
@@ -407,6 +415,63 @@ export function CheckboxInput({
|
|
|
407
415
|
useBusyAnnouncement(isBusy, t('@astryx.spinner.loading'), indicatorRef);
|
|
408
416
|
const {focusProps} = useIndicatorFocusRing(indicatorRef);
|
|
409
417
|
|
|
418
|
+
// Under a coarse pointer a labelled checkbox's row grows toward 44 px
|
|
419
|
+
// through an invisible band, and nothing changes size. A label-hidden
|
|
420
|
+
// checkbox is a marker in another component's row, which keeps its target.
|
|
421
|
+
const hasHitArea = !isLabelHidden && !isDisabled && !isReadOnly;
|
|
422
|
+
const inputRef = useRef<HTMLInputElement>(null);
|
|
423
|
+
// The watch lives as long as the row is attached: the ref's cleanup stops
|
|
424
|
+
// it, and a replaced row starts its own.
|
|
425
|
+
const watchRowHitArea = useCallback(
|
|
426
|
+
(row: HTMLDivElement | null) => {
|
|
427
|
+
if (row == null || !hasHitArea) {
|
|
428
|
+
return;
|
|
429
|
+
}
|
|
430
|
+
return watchHitArea(row, insets => writeHitInsets(row, insets));
|
|
431
|
+
},
|
|
432
|
+
[hasHitArea],
|
|
433
|
+
);
|
|
434
|
+
// The disabled-message tooltip listens for hover and focus on the whole
|
|
435
|
+
// row, a larger trigger. It anchors on the checkbox itself (below), so it
|
|
436
|
+
// shows next to the control. Its handlers check isEnabled, so the row
|
|
437
|
+
// always attaches them.
|
|
438
|
+
const rowRef = useMergedRefs(
|
|
439
|
+
disabledMessageTooltip.interactionRef,
|
|
440
|
+
watchRowHitArea,
|
|
441
|
+
);
|
|
442
|
+
// Where a press on the row itself began. A browser can move a tap's click
|
|
443
|
+
// onto the row's edge, so the band is judged from the press.
|
|
444
|
+
const pressPointRef = useRef<{x: number; y: number} | null>(null);
|
|
445
|
+
const handleRowPointerDown = (event: ReactPointerEvent<HTMLDivElement>) => {
|
|
446
|
+
pressPointRef.current =
|
|
447
|
+
event.target === event.currentTarget
|
|
448
|
+
? {x: event.clientX, y: event.clientY}
|
|
449
|
+
: null;
|
|
450
|
+
};
|
|
451
|
+
// A press that began on the band, outside the row's box, toggles the
|
|
452
|
+
// checkbox, as a tap on the box does. A click from code has no press count
|
|
453
|
+
// (`detail` is 0), so it never toggles from here.
|
|
454
|
+
const handleRowClick = (event: ReactMouseEvent<HTMLDivElement>) => {
|
|
455
|
+
const press = pressPointRef.current;
|
|
456
|
+
pressPointRef.current = null;
|
|
457
|
+
if (
|
|
458
|
+
press == null ||
|
|
459
|
+
event.detail === 0 ||
|
|
460
|
+
event.target !== event.currentTarget
|
|
461
|
+
) {
|
|
462
|
+
return;
|
|
463
|
+
}
|
|
464
|
+
const box = event.currentTarget.getBoundingClientRect();
|
|
465
|
+
const isOnBand =
|
|
466
|
+
press.x < box.left ||
|
|
467
|
+
press.x > box.right ||
|
|
468
|
+
press.y < box.top ||
|
|
469
|
+
press.y > box.bottom;
|
|
470
|
+
if (isOnBand) {
|
|
471
|
+
inputRef.current?.click();
|
|
472
|
+
}
|
|
473
|
+
};
|
|
474
|
+
|
|
410
475
|
const isIndeterminate = optimisticValue === 'indeterminate';
|
|
411
476
|
const isChecked = optimisticValue === true;
|
|
412
477
|
|
|
@@ -455,19 +520,15 @@ export function CheckboxInput({
|
|
|
455
520
|
style,
|
|
456
521
|
)}>
|
|
457
522
|
<div
|
|
458
|
-
ref={
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
// positioning anchors on the checkbox itself (below) so the tooltip
|
|
462
|
-
// appears next to the control, not the far edge of the row.
|
|
463
|
-
// Handlers are gated internally by isEnabled, so attaching
|
|
464
|
-
// unconditionally is safe.
|
|
465
|
-
disabledMessageTooltip.interactionRef(el);
|
|
466
|
-
}}
|
|
523
|
+
ref={rowRef}
|
|
524
|
+
onPointerDown={hasHitArea ? handleRowPointerDown : undefined}
|
|
525
|
+
onClick={hasHitArea ? handleRowClick : undefined}
|
|
467
526
|
{...(isDisabled ? undefined : pressable)}
|
|
468
527
|
{...stylex.props(
|
|
469
528
|
styles.container,
|
|
470
529
|
isLabelHidden && styles.containerLabelHidden,
|
|
530
|
+
hasHitArea && styles.hitAreaHost,
|
|
531
|
+
hasHitArea && touchHitAreaStyles.layer,
|
|
471
532
|
// Hover and focus reach the checkbox visual through this ancestor
|
|
472
533
|
// marker rather than props, so the whole row drives it.
|
|
473
534
|
!isDisabled && indicatorScope,
|
|
@@ -490,6 +551,7 @@ export function CheckboxInput({
|
|
|
490
551
|
ref,
|
|
491
552
|
indeterminateRef,
|
|
492
553
|
disabledMessageTooltip.positionRef,
|
|
554
|
+
inputRef,
|
|
493
555
|
)}
|
|
494
556
|
id={id}
|
|
495
557
|
type="checkbox"
|
|
@@ -24,7 +24,7 @@ export const docs = {
|
|
|
24
24
|
],
|
|
25
25
|
},
|
|
26
26
|
description:
|
|
27
|
-
'Groups an input with prefix
|
|
27
|
+
'Groups an input with prefix and suffix addons in one connected row. A focused member stays above its neighbours, so its focus ring shows in full.',
|
|
28
28
|
props: [
|
|
29
29
|
{
|
|
30
30
|
name: 'children',
|
|
@@ -59,7 +59,7 @@ export const docs = {
|
|
|
59
59
|
{
|
|
60
60
|
name: 'isOptional',
|
|
61
61
|
type: 'boolean',
|
|
62
|
-
description: 'Show "
|
|
62
|
+
description: 'Show the "Optional" indicator after the label.',
|
|
63
63
|
default: 'false',
|
|
64
64
|
},
|
|
65
65
|
{
|
|
@@ -77,7 +77,8 @@ export const docs = {
|
|
|
77
77
|
{
|
|
78
78
|
name: 'status',
|
|
79
79
|
type: "{type: 'warning' | 'error' | 'success', message?: string}",
|
|
80
|
-
description:
|
|
80
|
+
description:
|
|
81
|
+
'Validation status. Its message shows below the group. The group border does not change; a theme can style the row from `data-status`.',
|
|
81
82
|
},
|
|
82
83
|
{
|
|
83
84
|
name: 'labelTooltip',
|
|
@@ -109,7 +109,8 @@ export interface InputGroupProps extends Omit<
|
|
|
109
109
|
size?: InputGroupSize;
|
|
110
110
|
|
|
111
111
|
/**
|
|
112
|
-
*
|
|
112
|
+
* Validation status. Its message shows below the group. The group border
|
|
113
|
+
* does not change; a theme can style the row from `data-status`.
|
|
113
114
|
*/
|
|
114
115
|
status?: InputStatus;
|
|
115
116
|
|
|
@@ -125,8 +126,8 @@ export interface InputGroupProps extends Omit<
|
|
|
125
126
|
}
|
|
126
127
|
|
|
127
128
|
/**
|
|
128
|
-
* Groups an input with prefix
|
|
129
|
-
*
|
|
129
|
+
* Groups an input with prefix and suffix addons in one connected row. A
|
|
130
|
+
* focused member stays above its neighbours, so its focus ring shows in full.
|
|
130
131
|
*
|
|
131
132
|
* @example
|
|
132
133
|
* ```
|
|
@@ -96,8 +96,10 @@ export const groupStyles = stylex.create({
|
|
|
96
96
|
default: 0,
|
|
97
97
|
[IS_LAST_ITEM]: radiusVars['--radius-element'],
|
|
98
98
|
},
|
|
99
|
+
// Input wrappers already sit at z-index 1. A focused member goes one
|
|
100
|
+
// higher, so an unfocused input beside it cannot cover its focus ring.
|
|
99
101
|
':focus-within': {
|
|
100
|
-
zIndex:
|
|
102
|
+
zIndex: 2,
|
|
101
103
|
},
|
|
102
104
|
},
|
|
103
105
|
});
|
|
@@ -8,7 +8,7 @@ export const docs = {
|
|
|
8
8
|
displayName: 'Radio List Item',
|
|
9
9
|
isHiddenFromOverview: true,
|
|
10
10
|
description:
|
|
11
|
-
'Individual radio item with label, description, and content slots.',
|
|
11
|
+
'Individual radio item with label, description, and content slots. Under a coarse pointer an enabled option’s target grows toward 44 px through an invisible hit area. It never shrinks below the row and stops at half the gap to a closer neighbour; the row keeps its size. That hit area is the row’s `::after`, so decorate a row with a child.',
|
|
12
12
|
// RadioListItem requires RadioList context; wrap it so the preview doesn't throw.
|
|
13
13
|
playground: {
|
|
14
14
|
defaults: {value: 'option-1', label: 'Option'},
|
|
@@ -97,7 +97,8 @@ export const docsZh = {
|
|
|
97
97
|
name: 'RadioListItem',
|
|
98
98
|
isHiddenFromOverview: true,
|
|
99
99
|
displayName: 'Radio List Item',
|
|
100
|
-
description:
|
|
100
|
+
description:
|
|
101
|
+
'单个单选选项,包含标签、描述和内容插槽。在粗指针(触摸)下,已启用选项的目标通过不可见的点击区域向 44 px 扩展。它不小于所在行,与相邻控件较近时止于间距的一半;行尺寸不变。该点击区域是行的 `::after`,因此请用子元素装饰行。',
|
|
101
102
|
props: [
|
|
102
103
|
{
|
|
103
104
|
name: 'label',
|
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
* - /packages/cli/assets/templates/blocks/components/RadioList/ (showcase blocks)
|
|
19
19
|
*/
|
|
20
20
|
|
|
21
|
-
import React, {use, useId, useRef, type ReactNode} from 'react';
|
|
21
|
+
import React, {use, useCallback, useId, useRef, type ReactNode} from 'react';
|
|
22
22
|
import * as stylex from '@stylexjs/stylex';
|
|
23
23
|
import type {StyleXStyles} from '@stylexjs/stylex';
|
|
24
24
|
import type {BaseProps} from '../BaseProps';
|
|
@@ -32,6 +32,9 @@ import {rtlStyles} from '../utils/rtlStyles';
|
|
|
32
32
|
import {indicatorScope} from '../Indicator/indicator.markers.stylex';
|
|
33
33
|
import {useIndicatorFocusRing} from '../hooks/useIndicatorFocusRing';
|
|
34
34
|
import {usePressFeedback} from '../hooks/usePressFeedback';
|
|
35
|
+
import {useMergedRefs} from '../hooks/useMergedRefs';
|
|
36
|
+
import {watchHitArea, writeHitInsets} from '../utils/hitArea';
|
|
37
|
+
import {touchHitAreaStyles} from '../utils/touchHitArea.stylex';
|
|
35
38
|
import {useIndicator} from '../Indicator/useIndicator';
|
|
36
39
|
import {Item, type ItemAlign} from '../Item/Item';
|
|
37
40
|
import {themeProps} from '../utils/themeProps';
|
|
@@ -305,6 +308,21 @@ export function RadioListItem({
|
|
|
305
308
|
// `<label htmlFor>` would double-fire under delegation.
|
|
306
309
|
const radioRef = useRef<HTMLInputElement>(null);
|
|
307
310
|
|
|
311
|
+
// Under a coarse pointer an option's row grows toward 44 px through an
|
|
312
|
+
// invisible band, and the row keeps its size. A tap on the band lands on
|
|
313
|
+
// the row, which hands it to the radio. The watch lives as long as the row
|
|
314
|
+
// is attached: the ref's cleanup stops it.
|
|
315
|
+
const watchRowHitArea = useCallback(
|
|
316
|
+
(row: HTMLDivElement | null) => {
|
|
317
|
+
if (row == null || isDisabled) {
|
|
318
|
+
return;
|
|
319
|
+
}
|
|
320
|
+
return watchHitArea(row, insets => writeHitInsets(row, insets));
|
|
321
|
+
},
|
|
322
|
+
[isDisabled],
|
|
323
|
+
);
|
|
324
|
+
const mergedRowRef = useMergedRefs(ref, watchRowHitArea);
|
|
325
|
+
|
|
308
326
|
const radioCircle = (
|
|
309
327
|
<div
|
|
310
328
|
{...stylex.props(
|
|
@@ -387,7 +405,7 @@ export function RadioListItem({
|
|
|
387
405
|
|
|
388
406
|
return (
|
|
389
407
|
<Item
|
|
390
|
-
ref={
|
|
408
|
+
ref={mergedRowRef}
|
|
391
409
|
{...(isDisabled ? undefined : pressable)}
|
|
392
410
|
startContent={mediaContent}
|
|
393
411
|
// Delegate row-surface clicks (label text, description, and the empty
|
|
@@ -419,6 +437,7 @@ export function RadioListItem({
|
|
|
419
437
|
// still overrides it from the higher `astryx-theme` layer.
|
|
420
438
|
rowStyles.root,
|
|
421
439
|
!isDisabled && rowStyles.press,
|
|
440
|
+
!isDisabled && touchHitAreaStyles.layer,
|
|
422
441
|
xstyle,
|
|
423
442
|
] as StyleXStyles
|
|
424
443
|
}
|
|
@@ -55,7 +55,12 @@ export interface PanGestureRecognizerOptions extends GestureRecognizerOptions<Pa
|
|
|
55
55
|
minimumNumberOfTouches?: number;
|
|
56
56
|
/** @default Infinity */
|
|
57
57
|
maximumNumberOfTouches?: number;
|
|
58
|
-
|
|
58
|
+
/**
|
|
59
|
+
* The travel that begins the pan, px. A function gives each press its own
|
|
60
|
+
* threshold: it reads the press's first touch when that touch lands.
|
|
61
|
+
* @default UIKit's 10
|
|
62
|
+
*/
|
|
63
|
+
hysteresis?: number | ((touch: GestureTouch) => number);
|
|
59
64
|
/** Movement that may begin the pan; anything else fails it. @default 'any' */
|
|
60
65
|
direction?: PanDirection;
|
|
61
66
|
/**
|
|
@@ -117,6 +122,11 @@ export class PanGestureRecognizer extends GestureRecognizer {
|
|
|
117
122
|
travelAtBegan: Point = ZERO;
|
|
118
123
|
|
|
119
124
|
private readonly tracker: VelocityTracker;
|
|
125
|
+
/**
|
|
126
|
+
* Read once per press. A finger that lands later keeps the press's
|
|
127
|
+
* threshold.
|
|
128
|
+
*/
|
|
129
|
+
private readonly hysteresisFor: ((touch: GestureTouch) => number) | null;
|
|
120
130
|
/** The lift's event time once the pan ended: its release (AST-100 FR2). */
|
|
121
131
|
private releaseTime: number | undefined = undefined;
|
|
122
132
|
/** The centroid where the gesture started, in client px. */
|
|
@@ -132,7 +142,10 @@ export class PanGestureRecognizer extends GestureRecognizer {
|
|
|
132
142
|
this.minimumNumberOfTouches = options.minimumNumberOfTouches ?? 1;
|
|
133
143
|
this.maximumNumberOfTouches =
|
|
134
144
|
options.maximumNumberOfTouches ?? Number.POSITIVE_INFINITY;
|
|
135
|
-
|
|
145
|
+
const {hysteresis} = options;
|
|
146
|
+
this.hysteresisFor = typeof hysteresis === 'function' ? hysteresis : null;
|
|
147
|
+
this.hysteresis =
|
|
148
|
+
typeof hysteresis === 'number' ? hysteresis : UIKIT_PAN.hysteresis.value;
|
|
136
149
|
this.direction = options.direction ?? 'any';
|
|
137
150
|
this.axisRatio = options.axisRatio ?? 1;
|
|
138
151
|
this.lockAxis = options.lockAxis ?? false;
|
|
@@ -237,6 +250,10 @@ export class PanGestureRecognizer extends GestureRecognizer {
|
|
|
237
250
|
event: GestureEventContext,
|
|
238
251
|
): void {
|
|
239
252
|
if (this.numberOfTouches === touches.length) {
|
|
253
|
+
const first = touches[0];
|
|
254
|
+
if (this.hysteresisFor != null && first != null) {
|
|
255
|
+
this.hysteresis = this.hysteresisFor(first);
|
|
256
|
+
}
|
|
240
257
|
this.origin = centroid(this.touches.values());
|
|
241
258
|
this.adjust = ZERO;
|
|
242
259
|
this.raw = ZERO;
|
package/src/gestures/README.md
CHANGED
|
@@ -118,7 +118,8 @@ The pan reads its numbers the way `UIPanGestureRecognizer` does, as measured
|
|
|
118
118
|
in the UIKit lab:
|
|
119
119
|
|
|
120
120
|
- It begins once the centroid has moved `hysteresis` (10 px) along either
|
|
121
|
-
axis, not as a distance.
|
|
121
|
+
axis, not as a distance. A function gives each press its own threshold,
|
|
122
|
+
read from the press's first touch when it lands.
|
|
122
123
|
- `translation()` leaves out the travel the threshold took: a slow drag reads
|
|
123
124
|
zero when the pan begins, and a step that overshoots keeps only the
|
|
124
125
|
overshoot. A dragged surface does not jump. `totalTranslation()` is the
|
|
@@ -76,12 +76,17 @@ interface EdgeLayout {
|
|
|
76
76
|
*/
|
|
77
77
|
export interface ScreenEdgePanGestureRecognizerOptions extends Omit<
|
|
78
78
|
PanGestureRecognizerOptions,
|
|
79
|
-
'direction' | 'allowedPointerTypes' | 'allowedScrollTypes'
|
|
79
|
+
'direction' | 'allowedPointerTypes' | 'allowedScrollTypes' | 'hysteresis'
|
|
80
80
|
> {
|
|
81
81
|
/** The edges a touch may start from. @default ['leading'] */
|
|
82
82
|
edges?: ReadonlyArray<ScreenEdge>;
|
|
83
83
|
/** Width of the band at each edge, px. */
|
|
84
84
|
edgeRegion?: number;
|
|
85
|
+
/**
|
|
86
|
+
* The travel that begins the pan in its edge zone, px. It is one number for
|
|
87
|
+
* every press, since the zone sets the threshold itself. @default UIKit's 15
|
|
88
|
+
*/
|
|
89
|
+
hysteresis?: number;
|
|
85
90
|
}
|
|
86
91
|
|
|
87
92
|
export class ScreenEdgePanGestureRecognizer extends PanGestureRecognizer {
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @file touchHitArea.stylex.ts
|
|
5
|
+
* @input Uses StyleX and the insets that utils/hitArea.ts writes on a host
|
|
6
|
+
* @output Exports `touchHitAreaStyles.layer`, a host's invisible hit band
|
|
7
|
+
* under a coarse pointer
|
|
8
|
+
* @position Internal styling utility for CheckboxInput and RadioListItem
|
|
9
|
+
*
|
|
10
|
+
* The band is the host's `::after`, grown past each edge by the measured
|
|
11
|
+
* inset. A clip path cuts out the host's box, so its content keeps its taps.
|
|
12
|
+
* A tap on the band lands on the host. SYNC: hitArea.ts HIT_INSET_PROPERTIES.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import * as stylex from '@stylexjs/stylex';
|
|
16
|
+
|
|
17
|
+
// StyleX reads these at build time, so they are spelled out.
|
|
18
|
+
const TOUCH = '@media (pointer: coarse)';
|
|
19
|
+
// The insets are measured in the viewport, so the sides are physical.
|
|
20
|
+
const TOP = 'var(--astryx-hit-inset-top, 0px)';
|
|
21
|
+
const RIGHT = 'var(--astryx-hit-inset-right, 0px)';
|
|
22
|
+
const BOTTOM = 'var(--astryx-hit-inset-bottom, 0px)';
|
|
23
|
+
const LEFT = 'var(--astryx-hit-inset-left, 0px)';
|
|
24
|
+
|
|
25
|
+
export const touchHitAreaStyles = stylex.create({
|
|
26
|
+
layer: {
|
|
27
|
+
'::after': {
|
|
28
|
+
content: {default: null, [TOUCH]: '""'},
|
|
29
|
+
position: {default: null, [TOUCH]: 'absolute'},
|
|
30
|
+
inset: {
|
|
31
|
+
default: null,
|
|
32
|
+
[TOUCH]: `calc(${TOP} * -1) calc(${RIGHT} * -1) calc(${BOTTOM} * -1) calc(${LEFT} * -1)`,
|
|
33
|
+
},
|
|
34
|
+
// The outer rectangle, then the host's box as an even-odd hole.
|
|
35
|
+
clipPath: {
|
|
36
|
+
default: null,
|
|
37
|
+
[TOUCH]: `polygon(evenodd, 0 0, 100% 0, 100% 100%, 0 100%, 0 0, ${LEFT} ${TOP}, calc(100% - ${RIGHT}) ${TOP}, calc(100% - ${RIGHT}) calc(100% - ${BOTTOM}), ${LEFT} calc(100% - ${BOTTOM}), ${LEFT} ${TOP})`,
|
|
38
|
+
},
|
|
39
|
+
},
|
|
40
|
+
},
|
|
41
|
+
});
|