@kanso-labs/react-native-kanso-ui 0.1.1 → 0.2.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.
Files changed (49) hide show
  1. package/dist/button/index.d.ts +24 -2
  2. package/dist/button/index.js.map +1 -1
  3. package/dist/button/pending.js +144 -0
  4. package/dist/button/pending.js.map +1 -0
  5. package/dist/components/button/index.d.ts +1 -1
  6. package/dist/components/button/index.js +154 -112
  7. package/dist/components/button/index.js.map +1 -1
  8. package/dist/components/icon-button/index.d.ts +1 -1
  9. package/dist/components/icon-button/index.js +160 -110
  10. package/dist/components/icon-button/index.js.map +1 -1
  11. package/dist/components/meter/index.d.ts +73 -0
  12. package/dist/components/meter/index.js +179 -0
  13. package/dist/components/meter/index.js.map +1 -0
  14. package/dist/components/number-field/index.js +1 -9
  15. package/dist/components/number-field/index.js.map +1 -1
  16. package/dist/components/progress-indicator/index.d.ts +113 -0
  17. package/dist/components/progress-indicator/index.js +1024 -0
  18. package/dist/components/progress-indicator/index.js.map +1 -0
  19. package/dist/components/snackbar/index.d.ts +120 -0
  20. package/dist/components/snackbar/index.js +540 -0
  21. package/dist/components/snackbar/index.js.map +1 -0
  22. package/dist/glyphs/hidden.js +13 -0
  23. package/dist/glyphs/hidden.js.map +1 -0
  24. package/dist/glyphs/index.js +2 -8
  25. package/dist/glyphs/index.js.map +1 -1
  26. package/dist/i18n/index.js +23 -0
  27. package/dist/i18n/index.js.map +1 -0
  28. package/dist/i18n/messages.js +41 -0
  29. package/dist/i18n/messages.js.map +1 -0
  30. package/dist/index.d.ts +4 -1
  31. package/dist/index.js +6 -3
  32. package/dist/indicator/index.d.ts +15 -0
  33. package/dist/indicator/index.js +256 -0
  34. package/dist/indicator/index.js.map +1 -0
  35. package/dist/indicator/motion.js +86 -0
  36. package/dist/indicator/motion.js.map +1 -0
  37. package/dist/indicator/paths.js +72 -0
  38. package/dist/indicator/paths.js.map +1 -0
  39. package/dist/indicator/styles.js +62 -0
  40. package/dist/indicator/styles.js.map +1 -0
  41. package/dist/number/locale.js +14 -0
  42. package/dist/number/locale.js.map +1 -0
  43. package/dist/overlay/host.js +65 -2
  44. package/dist/overlay/host.js.map +1 -1
  45. package/dist/press/index.js +10 -9
  46. package/dist/press/index.js.map +1 -1
  47. package/dist/theme/resolve.js +1 -1
  48. package/dist/theme/resolve.js.map +1 -1
  49. package/package.json +1 -1
@@ -14,11 +14,33 @@ type ButtonLinkForm = {
14
14
  * with a scheme of its own, and the button is announced as a link.
15
15
  */
16
16
  readonly href: string;
17
+ readonly isPending?: never;
17
18
  readonly isSelected?: never;
18
19
  readonly onChange?: never;
20
+ readonly pendingLabel?: never;
19
21
  readonly type?: never;
20
22
  };
21
- type ButtonPressForm = {
23
+ /**
24
+ * Whether the button's action is under way, which the plain button and the
25
+ * toggle both take and the link does not.
26
+ */
27
+ type ButtonPending = {
28
+ /**
29
+ * Shows that the button's action is under way: a ring takes the place of
30
+ * the label and the icon, which keep their space, and the button keeps its
31
+ * focus and takes no press until it clears. A toggle may be pending, for a
32
+ * state saved somewhere slow.
33
+ * @default false
34
+ */
35
+ readonly isPending?: boolean;
36
+ /**
37
+ * The name of the ring shown while the button is pending, for a screen
38
+ * reader. The content it replaces is hidden while it shows. Left out, it is
39
+ * the word for it in the device's locale, "Loading" in English.
40
+ */
41
+ readonly pendingLabel?: string;
42
+ };
43
+ type ButtonPressForm = ButtonPending & {
22
44
  readonly defaultSelected?: never;
23
45
  readonly href?: never;
24
46
  readonly isSelected?: never;
@@ -30,7 +52,7 @@ type ButtonPressForm = {
30
52
  */
31
53
  readonly type?: 'button' | 'submit';
32
54
  };
33
- type ButtonToggleForm = {
55
+ type ButtonToggleForm = ButtonPending & {
34
56
  /**
35
57
  * Whether a toggle starts selected, when it keeps its own state. Passing
36
58
  * this, `isSelected` or `onChange` is what makes the button a toggle.
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":["GestureResponderEvent","LayoutChangeEvent","PressableProps","SharedValue","use","useState","Platform","Pressable","StyleSheet","Animated","Easing","useAnimatedStyle","useReducedMotion","useSharedValue","withTiming","ResolvedTheme","FormContext","useOpenLink","useTheme","ButtonLinkForm","defaultSelected","href","isSelected","onChange","type","ButtonPressForm","ButtonToggleForm","ControlSize","ToggleState","HEIGHTS","lg","md","xl","xs","xxl","const","Record","OUTLINE_WIDTHS","Readonly","PRESSED_CORNERS","AnimatedPressable","createAnimatedComponent","toggleState","select","selected","default","web","buttonStyles","create","clip","bottom","left","overflow","pointerEvents","position","right","top","measureThen","height","onLayout","event","set","nativeEvent","layout","pressThen","onPress","then","useButtonAction","t0","$","_c","isToggle","openLink","form","ownSelected","setOwnSelected","t1","follow","undefined","requestSubmit","t2","t3","useCorner","radius","motion","reduceMotion","durationShort2","easingEmphasized","bezier","duration","easing","timing","corner","measured","get","target","Math","min","borderRadius","t4","_c"],"sources":["../../src/button/index.ts"],"sourcesContent":["import type {\n GestureResponderEvent,\n LayoutChangeEvent,\n PressableProps,\n} from 'react-native'\nimport type { SharedValue } from 'react-native-reanimated'\n\nimport { use, useState } from 'react'\nimport { Platform, Pressable, StyleSheet } from 'react-native'\nimport Animated, {\n Easing,\n useAnimatedStyle,\n useReducedMotion,\n useSharedValue,\n withTiming,\n} from 'react-native-reanimated'\n\nimport type { ResolvedTheme } from '../theme/scope'\n\nimport { FormContext } from '../form'\nimport { useOpenLink } from '../router'\nimport { useTheme } from '../theme/scope'\n\n// What every button does past its own styles, shared by Button and\n// IconButton as web's src/button is: the three forms a button takes, the\n// toggle's state and how it is announced, what a press does after the call\n// site's own `onPress`, a submit button's included, the corner that eases\n// between shapes, and the metrics\n// the two components' pages give alike, since the two share their five\n// heights so that they line up in a row.\n\n/**\n * A plain button, a link, or a toggle, of which a call site writes exactly\n * one. Each form names the props of the other two as `never`, so a toggle\n * given `href` is a compile error rather than a prop that silently does\n * nothing.\n */\ntype ButtonLinkForm = {\n readonly defaultSelected?: never\n /**\n * Where the button leads. Given one, a press opens it through the\n * `RouterProvider` around the button, or through the platform for a URL\n * with a scheme of its own, and the button is announced as a link.\n */\n readonly href: string\n readonly isSelected?: never\n readonly onChange?: never\n readonly type?: never\n}\n\ntype ButtonPressForm = {\n readonly defaultSelected?: never\n readonly href?: never\n readonly isSelected?: never\n readonly onChange?: never\n /**\n * What a press does past `onPress`. `submit` submits the `Form` the button\n * is in, as a submit button does on web; `button` does nothing more.\n * @default 'button'\n */\n readonly type?: 'button' | 'submit'\n}\n\ntype ButtonToggleForm = {\n /**\n * Whether a toggle starts selected, when it keeps its own state. Passing\n * this, `isSelected` or `onChange` is what makes the button a toggle.\n */\n readonly defaultSelected?: boolean\n readonly href?: never\n /**\n * Whether a toggle is selected, when the call site holds the state. Pass\n * it with `onChange`; passing this, `defaultSelected` or `onChange` is what\n * makes the button a toggle.\n */\n readonly isSelected?: boolean\n /**\n * Called with the new state when a toggle is pressed, after `onPress`.\n * Passing this, `isSelected` or `defaultSelected` is what makes the button\n * a toggle.\n */\n readonly onChange?: (isSelected: boolean) => void\n readonly type?: never\n}\n\n/** The five control heights both pages give, by the names both props take. */\ntype ControlSize = 'lg' | 'md' | 'xl' | 'xs' | 'xxl'\n\n/** How a toggle reports its state to a screen reader. */\ntype ToggleState = {\n readonly 'aria-pressed'?: boolean\n readonly 'aria-selected'?: boolean\n}\n\n// The control height each size reads: the page's XS, S, M, L and XL, which\n// the theme's sizing scale names one step apart from this prop.\nconst HEIGHTS = {\n lg: 'controlLg',\n md: 'controlSm',\n xl: 'controlXl',\n xs: 'controlXs',\n xxl: 'controlXxl',\n} as const satisfies Record<ControlSize, keyof ResolvedTheme['sizing']>\n\n// The outlined rule thickens with the size, as the buttons page's size tokens\n// have it: 1dp up to M, 2dp at L, 3dp at XL. The icon buttons page keeps its\n// widths in a token set its widget will not open, so the icon button takes\n// these, which a 96dp icon button beside a 96dp outlined button needs anyway.\nconst OUTLINE_WIDTHS: Readonly<Record<ControlSize, number>> = {\n lg: 1,\n md: 1,\n xl: 2,\n xs: 1,\n xxl: 3,\n}\n\n// The corner each size presses to, which both pages give alike.\nconst PRESSED_CORNERS = {\n lg: 'md',\n md: 'sm',\n xl: 'lg',\n xs: 'sm',\n xxl: 'lg',\n} as const satisfies Record<ControlSize, keyof ResolvedTheme['radii']>\n\nconst AnimatedPressable = Animated.createAnimatedComponent(Pressable)\n\n// Web's toggle reports `aria-pressed`, which React Native Web writes onto the\n// element. React Native has no pressed state to report, and reports the\n// selected one instead, which VoiceOver and TalkBack both read out.\nconst toggleState = Platform.select<(selected: boolean) => ToggleState>({\n default: (selected) => ({ 'aria-selected': selected }),\n web: (selected) => ({ 'aria-pressed': selected }),\n})\n\nconst buttonStyles = StyleSheet.create({\n // Clips the state layer and the ripple to the corner as it morphs, and\n // takes no touches.\n clip: {\n bottom: 0,\n left: 0,\n overflow: 'hidden',\n pointerEvents: 'none',\n position: 'absolute',\n right: 0,\n top: 0,\n },\n})\n\n// Records the button's height for the corner, then calls the call site's own\n// `onLayout`. Built by a call rather than declared in a component, which is\n// what react-perf's jsx-no-new-function-as-prop is after; the React Compiler\n// memoises the result on its inputs.\nfunction measureThen(\n height: SharedValue<number>,\n onLayout: PressableProps['onLayout'],\n) {\n return (event: LayoutChangeEvent) => {\n height.set(event.nativeEvent.layout.height)\n onLayout?.(event)\n }\n}\n\n// The call site's own `onPress`, then what the button does with the press.\n// Built by a call for the reason `measureThen` is.\nfunction pressThen(onPress: PressableProps['onPress'], then: () => void) {\n return (event: GestureResponderEvent) => {\n onPress?.(event)\n then()\n }\n}\n\n/**\n * What a press does, and whether a toggle is selected. A press calls the call\n * site's own `onPress` first, then opens the link, changes the toggle's state\n * or submits the form the button is in, which is the order web reports them\n * in. A toggle keeps its own state from `defaultSelected` unless the call\n * site holds it in `isSelected`. A submit button outside a form submits\n * nothing, as on web.\n */\nfunction useButtonAction({\n defaultSelected,\n href,\n isSelected,\n isToggle,\n onChange,\n onPress,\n type,\n}: {\n readonly defaultSelected: boolean | undefined\n readonly href: string | undefined\n readonly isSelected: boolean | undefined\n readonly isToggle: boolean\n readonly onChange: ((isSelected: boolean) => void) | undefined\n readonly onPress: PressableProps['onPress']\n readonly type: 'button' | 'submit' | undefined\n}) {\n const openLink = useOpenLink()\n const form = use(FormContext)\n const [ownSelected, setOwnSelected] = useState(defaultSelected ?? false)\n const selected = isToggle && (isSelected ?? ownSelected)\n\n function follow() {\n if (href !== undefined) {\n openLink(href)\n } else if (isToggle) {\n setOwnSelected(!selected)\n onChange?.(!selected)\n } else if (type === 'submit') {\n form?.requestSubmit()\n }\n }\n\n return { onPress: pressThen(onPress, follow), selected }\n}\n\n/**\n * The corner a button eases to, over the motion tokens' short duration on the\n * emphasised curve, and at once for a reader who asks the device for reduced\n * motion. The pill is the radius scale's `pill`, far larger than any button,\n * so the ease runs from half the button's measured height rather than from\n * that: otherwise the corner would hold the pill for most of the ease and\n * jump at its end. The `onLayout` it hands back measures the button, then\n * calls the call site's own.\n */\nfunction useCorner(radius: number, onLayout: PressableProps['onLayout']) {\n const { motion } = useTheme()\n const reduceMotion = useReducedMotion()\n const height = useSharedValue(0)\n const timing = {\n duration: motion.durationShort2,\n easing: Easing.bezier(...motion.easingEmphasized),\n }\n const corner = useAnimatedStyle(() => {\n 'worklet'\n const measured = height.get()\n const target = measured > 0 ? Math.min(radius, measured / 2) : radius\n\n return {\n borderRadius: reduceMotion ? target : withTiming(target, timing),\n }\n })\n\n return { corner, onLayout: measureThen(height, onLayout) }\n}\n\nexport {\n AnimatedPressable,\n buttonStyles,\n HEIGHTS,\n OUTLINE_WIDTHS,\n PRESSED_CORNERS,\n toggleState,\n useButtonAction,\n useCorner,\n}\nexport type { ButtonLinkForm, ButtonPressForm, ButtonToggleForm, ControlSize }\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.js","names":["GestureResponderEvent","LayoutChangeEvent","PressableProps","SharedValue","use","useState","Platform","Pressable","StyleSheet","Animated","Easing","useAnimatedStyle","useReducedMotion","useSharedValue","withTiming","ResolvedTheme","FormContext","useOpenLink","useTheme","ButtonLinkForm","defaultSelected","href","isPending","isSelected","onChange","pendingLabel","type","ButtonPending","ButtonPressForm","ButtonToggleForm","ControlSize","ToggleState","HEIGHTS","lg","md","xl","xs","xxl","const","Record","OUTLINE_WIDTHS","Readonly","PRESSED_CORNERS","AnimatedPressable","createAnimatedComponent","toggleState","select","selected","default","web","buttonStyles","create","clip","bottom","left","overflow","pointerEvents","position","right","top","measureThen","height","onLayout","event","set","nativeEvent","layout","pressThen","onPress","then","useButtonAction","t0","$","_c","isToggle","openLink","form","ownSelected","setOwnSelected","t1","follow","undefined","requestSubmit","t2","t3","useCorner","radius","motion","reduceMotion","durationShort2","easingEmphasized","bezier","duration","easing","timing","corner","measured","get","target","Math","min","borderRadius","t4","_c"],"sources":["../../src/button/index.ts"],"sourcesContent":["import type {\n GestureResponderEvent,\n LayoutChangeEvent,\n PressableProps,\n} from 'react-native'\nimport type { SharedValue } from 'react-native-reanimated'\n\nimport { use, useState } from 'react'\nimport { Platform, Pressable, StyleSheet } from 'react-native'\nimport Animated, {\n Easing,\n useAnimatedStyle,\n useReducedMotion,\n useSharedValue,\n withTiming,\n} from 'react-native-reanimated'\n\nimport type { ResolvedTheme } from '../theme/scope'\n\nimport { FormContext } from '../form'\nimport { useOpenLink } from '../router'\nimport { useTheme } from '../theme/scope'\n\n// What every button does past its own styles, shared by Button and\n// IconButton as web's src/button is: the three forms a button takes, the\n// toggle's state and how it is announced, what a press does after the call\n// site's own `onPress`, a submit button's included, the corner that eases\n// between shapes, and the metrics\n// the two components' pages give alike, since the two share their five\n// heights so that they line up in a row. What a pending button draws and\n// says is ./pending.tsx.\n\n/**\n * A plain button, a link, or a toggle, of which a call site writes exactly\n * one. Each form names the props of the other two as `never`, so a toggle\n * given `href` is a compile error rather than a prop that silently does\n * nothing.\n */\ntype ButtonLinkForm = {\n readonly defaultSelected?: never\n /**\n * Where the button leads. Given one, a press opens it through the\n * `RouterProvider` around the button, or through the platform for a URL\n * with a scheme of its own, and the button is announced as a link.\n */\n readonly href: string\n readonly isPending?: never\n readonly isSelected?: never\n readonly onChange?: never\n readonly pendingLabel?: never\n readonly type?: never\n}\n\n/**\n * Whether the button's action is under way, which the plain button and the\n * toggle both take and the link does not.\n */\ntype ButtonPending = {\n /**\n * Shows that the button's action is under way: a ring takes the place of\n * the label and the icon, which keep their space, and the button keeps its\n * focus and takes no press until it clears. A toggle may be pending, for a\n * state saved somewhere slow.\n * @default false\n */\n readonly isPending?: boolean\n /**\n * The name of the ring shown while the button is pending, for a screen\n * reader. The content it replaces is hidden while it shows. Left out, it is\n * the word for it in the device's locale, \"Loading\" in English.\n */\n readonly pendingLabel?: string\n}\n\ntype ButtonPressForm = ButtonPending & {\n readonly defaultSelected?: never\n readonly href?: never\n readonly isSelected?: never\n readonly onChange?: never\n /**\n * What a press does past `onPress`. `submit` submits the `Form` the button\n * is in, as a submit button does on web; `button` does nothing more.\n * @default 'button'\n */\n readonly type?: 'button' | 'submit'\n}\n\ntype ButtonToggleForm = ButtonPending & {\n /**\n * Whether a toggle starts selected, when it keeps its own state. Passing\n * this, `isSelected` or `onChange` is what makes the button a toggle.\n */\n readonly defaultSelected?: boolean\n readonly href?: never\n /**\n * Whether a toggle is selected, when the call site holds the state. Pass\n * it with `onChange`; passing this, `defaultSelected` or `onChange` is what\n * makes the button a toggle.\n */\n readonly isSelected?: boolean\n /**\n * Called with the new state when a toggle is pressed, after `onPress`.\n * Passing this, `isSelected` or `defaultSelected` is what makes the button\n * a toggle.\n */\n readonly onChange?: (isSelected: boolean) => void\n readonly type?: never\n}\n\n/** The five control heights both pages give, by the names both props take. */\ntype ControlSize = 'lg' | 'md' | 'xl' | 'xs' | 'xxl'\n\n/** How a toggle reports its state to a screen reader. */\ntype ToggleState = {\n readonly 'aria-pressed'?: boolean\n readonly 'aria-selected'?: boolean\n}\n\n// The control height each size reads: the page's XS, S, M, L and XL, which\n// the theme's sizing scale names one step apart from this prop.\nconst HEIGHTS = {\n lg: 'controlLg',\n md: 'controlSm',\n xl: 'controlXl',\n xs: 'controlXs',\n xxl: 'controlXxl',\n} as const satisfies Record<ControlSize, keyof ResolvedTheme['sizing']>\n\n// The outlined rule thickens with the size, as the buttons page's size tokens\n// have it: 1dp up to M, 2dp at L, 3dp at XL. The icon buttons page keeps its\n// widths in a token set its widget will not open, so the icon button takes\n// these, which a 96dp icon button beside a 96dp outlined button needs anyway.\nconst OUTLINE_WIDTHS: Readonly<Record<ControlSize, number>> = {\n lg: 1,\n md: 1,\n xl: 2,\n xs: 1,\n xxl: 3,\n}\n\n// The corner each size presses to, which both pages give alike.\nconst PRESSED_CORNERS = {\n lg: 'md',\n md: 'sm',\n xl: 'lg',\n xs: 'sm',\n xxl: 'lg',\n} as const satisfies Record<ControlSize, keyof ResolvedTheme['radii']>\n\nconst AnimatedPressable = Animated.createAnimatedComponent(Pressable)\n\n// Web's toggle reports `aria-pressed`, which React Native Web writes onto the\n// element. React Native has no pressed state to report, and reports the\n// selected one instead, which VoiceOver and TalkBack both read out.\nconst toggleState = Platform.select<(selected: boolean) => ToggleState>({\n default: (selected) => ({ 'aria-selected': selected }),\n web: (selected) => ({ 'aria-pressed': selected }),\n})\n\nconst buttonStyles = StyleSheet.create({\n // Clips the state layer and the ripple to the corner as it morphs, and\n // takes no touches.\n clip: {\n bottom: 0,\n left: 0,\n overflow: 'hidden',\n pointerEvents: 'none',\n position: 'absolute',\n right: 0,\n top: 0,\n },\n})\n\n// Records the button's height for the corner, then calls the call site's own\n// `onLayout`. Built by a call rather than declared in a component, which is\n// what react-perf's jsx-no-new-function-as-prop is after; the React Compiler\n// memoises the result on its inputs.\nfunction measureThen(\n height: SharedValue<number>,\n onLayout: PressableProps['onLayout'],\n) {\n return (event: LayoutChangeEvent) => {\n height.set(event.nativeEvent.layout.height)\n onLayout?.(event)\n }\n}\n\n// The call site's own `onPress`, then what the button does with the press.\n// Built by a call for the reason `measureThen` is.\nfunction pressThen(onPress: PressableProps['onPress'], then: () => void) {\n return (event: GestureResponderEvent) => {\n onPress?.(event)\n then()\n }\n}\n\n/**\n * What a press does, and whether a toggle is selected. A press calls the call\n * site's own `onPress` first, then opens the link, changes the toggle's state\n * or submits the form the button is in, which is the order web reports them\n * in. A toggle keeps its own state from `defaultSelected` unless the call\n * site holds it in `isSelected`. A submit button outside a form submits\n * nothing, as on web.\n */\nfunction useButtonAction({\n defaultSelected,\n href,\n isSelected,\n isToggle,\n onChange,\n onPress,\n type,\n}: {\n readonly defaultSelected: boolean | undefined\n readonly href: string | undefined\n readonly isSelected: boolean | undefined\n readonly isToggle: boolean\n readonly onChange: ((isSelected: boolean) => void) | undefined\n readonly onPress: PressableProps['onPress']\n readonly type: 'button' | 'submit' | undefined\n}) {\n const openLink = useOpenLink()\n const form = use(FormContext)\n const [ownSelected, setOwnSelected] = useState(defaultSelected ?? false)\n const selected = isToggle && (isSelected ?? ownSelected)\n\n function follow() {\n if (href !== undefined) {\n openLink(href)\n } else if (isToggle) {\n setOwnSelected(!selected)\n onChange?.(!selected)\n } else if (type === 'submit') {\n form?.requestSubmit()\n }\n }\n\n return { onPress: pressThen(onPress, follow), selected }\n}\n\n/**\n * The corner a button eases to, over the motion tokens' short duration on the\n * emphasised curve, and at once for a reader who asks the device for reduced\n * motion. The pill is the radius scale's `pill`, far larger than any button,\n * so the ease runs from half the button's measured height rather than from\n * that: otherwise the corner would hold the pill for most of the ease and\n * jump at its end. The `onLayout` it hands back measures the button, then\n * calls the call site's own.\n */\nfunction useCorner(radius: number, onLayout: PressableProps['onLayout']) {\n const { motion } = useTheme()\n const reduceMotion = useReducedMotion()\n const height = useSharedValue(0)\n const timing = {\n duration: motion.durationShort2,\n easing: Easing.bezier(...motion.easingEmphasized),\n }\n const corner = useAnimatedStyle(() => {\n 'worklet'\n const measured = height.get()\n const target = measured > 0 ? Math.min(radius, measured / 2) : radius\n\n return {\n borderRadius: reduceMotion ? target : withTiming(target, timing),\n }\n })\n\n return { corner, onLayout: measureThen(height, onLayout) }\n}\n\nexport {\n AnimatedPressable,\n buttonStyles,\n HEIGHTS,\n OUTLINE_WIDTHS,\n PRESSED_CORNERS,\n toggleState,\n useButtonAction,\n useCorner,\n}\nexport type { ButtonLinkForm, ButtonPressForm, ButtonToggleForm, ControlSize }\n"],"mappings":"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"}
@@ -0,0 +1,144 @@
1
+ import { IconStyleContext } from "../glyphs/icon-style.js";
2
+ import { ProgressIndicator } from "../components/progress-indicator/index.js";
3
+ import { messagesFor } from "../i18n/index.js";
4
+ import { c } from "react/compiler-runtime";
5
+ import { AccessibilityInfo, Platform, StyleSheet, View } from "react-native";
6
+ import { useEffect, useRef } from "react";
7
+ import { jsx } from "react/jsx-runtime";
8
+ //#region src/button/pending.tsx
9
+ const pendingStyles = StyleSheet.create({
10
+ hidden: { opacity: 0 },
11
+ ring: {
12
+ alignItems: "center",
13
+ bottom: 0,
14
+ justifyContent: "center",
15
+ left: 0,
16
+ pointerEvents: "none",
17
+ position: "absolute",
18
+ right: 0,
19
+ top: 0
20
+ }
21
+ });
22
+ const BUSY = { "aria-busy": true };
23
+ const CONCEALED = { "aria-hidden": true };
24
+ /**
25
+ * The icon a button draws, in a view that hides it in place while the button
26
+ * is pending. The view is there whether or not the button is, so an icon
27
+ * keeps its state when the button goes pending and comes back.
28
+ */
29
+ function PendingContent(t0) {
30
+ const $ = c(4);
31
+ const { children, isPending } = t0;
32
+ const t1 = isPending ? CONCEALED : void 0;
33
+ const t2 = isPending ? pendingStyles.hidden : void 0;
34
+ let t3;
35
+ if ($[0] !== children || $[1] !== t1 || $[2] !== t2) {
36
+ t3 = /* @__PURE__ */ jsx(View, {
37
+ ...t1,
38
+ style: t2,
39
+ children
40
+ });
41
+ $[0] = children;
42
+ $[1] = t1;
43
+ $[2] = t2;
44
+ $[3] = t3;
45
+ } else t3 = $[3];
46
+ return t3;
47
+ }
48
+ /**
49
+ * The indeterminate ring a pending button draws over its content, in the
50
+ * button's content colour rather than the progress page's primary, which on a
51
+ * filled button is the fill itself. `diameter` is what the content it
52
+ * replaces measures, the label's type size on Button and the icon on
53
+ * IconButton, which is what web's `1em` reads in each.
54
+ */
55
+ function PendingRing(t0) {
56
+ const $ = c(9);
57
+ const { color, diameter, label } = t0;
58
+ let t1;
59
+ if ($[0] !== color || $[1] !== diameter) {
60
+ t1 = slotOf(color, diameter);
61
+ $[0] = color;
62
+ $[1] = diameter;
63
+ $[2] = t1;
64
+ } else t1 = $[2];
65
+ const t2 = `${diameter}px`;
66
+ let t3;
67
+ if ($[3] !== label || $[4] !== t2) {
68
+ t3 = /* @__PURE__ */ jsx(ProgressIndicator, {
69
+ "aria-label": label,
70
+ diameter: t2,
71
+ isIndeterminate: true,
72
+ tone: "inherit",
73
+ variant: "circular"
74
+ });
75
+ $[3] = label;
76
+ $[4] = t2;
77
+ $[5] = t3;
78
+ } else t3 = $[5];
79
+ let t4;
80
+ if ($[6] !== t1 || $[7] !== t3) {
81
+ t4 = /* @__PURE__ */ jsx(View, {
82
+ style: pendingStyles.ring,
83
+ children: /* @__PURE__ */ jsx(IconStyleContext, {
84
+ value: t1,
85
+ children: t3
86
+ })
87
+ });
88
+ $[6] = t1;
89
+ $[7] = t3;
90
+ $[8] = t4;
91
+ } else t4 = $[8];
92
+ return t4;
93
+ }
94
+ function slotOf(color, size) {
95
+ return {
96
+ color,
97
+ size
98
+ };
99
+ }
100
+ /**
101
+ * The ring's name, and the announcement a button makes as it goes pending.
102
+ * The name is `pendingLabel`, or the word for loading in the device's locale.
103
+ *
104
+ * On iOS and Android the change is said through VoiceOver and TalkBack, as
105
+ * web's is said through React Aria's live announcer, since a ring appearing
106
+ * under them is not reliably read. Web says it only to a reader whose focus
107
+ * is on the button; a screen reader's focus is not something a component
108
+ * here can read, so it is said whatever has focus. A button pending as it
109
+ * mounts says nothing, as on web, which announces a change. React Native Web
110
+ * has no announcer, so there the ring is read when the button next is.
111
+ */
112
+ function usePending(isPending, pendingLabel) {
113
+ const $ = c(6);
114
+ let t0;
115
+ if ($[0] !== pendingLabel) {
116
+ t0 = pendingLabel ?? messagesFor().loading;
117
+ $[0] = pendingLabel;
118
+ $[1] = t0;
119
+ } else t0 = $[1];
120
+ const label = t0;
121
+ const was = useRef(isPending);
122
+ let t1;
123
+ let t2;
124
+ if ($[2] !== isPending || $[3] !== label) {
125
+ t1 = () => {
126
+ if (isPending && !was.current && (Platform.OS === "ios" || Platform.OS === "android")) AccessibilityInfo.announceForAccessibility(label);
127
+ was.current = isPending;
128
+ };
129
+ t2 = [isPending, label];
130
+ $[2] = isPending;
131
+ $[3] = label;
132
+ $[4] = t1;
133
+ $[5] = t2;
134
+ } else {
135
+ t1 = $[4];
136
+ t2 = $[5];
137
+ }
138
+ useEffect(t1, t2);
139
+ return label;
140
+ }
141
+ //#endregion
142
+ export { BUSY, CONCEALED, PendingContent, PendingRing, pendingStyles, usePending };
143
+
144
+ //# sourceMappingURL=pending.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pending.js","names":["ReactNode","ColorValue","useEffect","useRef","AccessibilityInfo","Platform","StyleSheet","View","IconStyle","ProgressIndicator","IconStyleContext","messagesFor","pendingStyles","create","hidden","opacity","ring","alignItems","bottom","justifyContent","left","pointerEvents","position","right","top","BUSY","const","CONCEALED","PendingContent","t0","$","_c","children","isPending","t1","undefined","t2","t3","PendingRing","color","diameter","label","slotOf","t4","size","usePending","pendingLabel","loading","was","current","OS","announceForAccessibility","_c"],"sources":["../../src/button/pending.tsx"],"sourcesContent":["import type { ReactNode } from 'react'\nimport type { ColorValue } from 'react-native'\n\nimport { useEffect, useRef } from 'react'\nimport { AccessibilityInfo, Platform, StyleSheet, View } from 'react-native'\n\nimport type { IconStyle } from '../glyphs/icon-style'\n\nimport { ProgressIndicator } from '../components/progress-indicator'\nimport { IconStyleContext } from '../glyphs/icon-style'\nimport { messagesFor } from '../i18n'\n\n// What a pending button draws and says, shared by Button and IconButton as\n// web's ButtonBase draws it for both: the content hidden in place, the ring\n// over it, and the announcement that the button has gone pending.\n\nconst pendingStyles = StyleSheet.create({\n // The label and the icon while the ring shows: still laid out, so the\n // button keeps its width, which is what stops a form jumping the moment it\n // is submitted, and not drawn.\n hidden: {\n opacity: 0,\n },\n // The ring sits over the hidden content, centred in the button, and takes\n // no touches.\n ring: {\n alignItems: 'center',\n bottom: 0,\n justifyContent: 'center',\n left: 0,\n pointerEvents: 'none',\n position: 'absolute',\n right: 0,\n top: 0,\n },\n})\n\n// What a pending button reports to a screen reader, spread before the call\n// site's own props, so an `aria-busy` of its own still wins, as on web.\nconst BUSY = { 'aria-busy': true } as const\n\n// What takes the hidden content out of what a screen reader reads, so the\n// button is named by the ring while it shows.\nconst CONCEALED = { 'aria-hidden': true } as const\n\n/**\n * The icon a button draws, in a view that hides it in place while the button\n * is pending. The view is there whether or not the button is, so an icon\n * keeps its state when the button goes pending and comes back.\n */\nfunction PendingContent({\n children,\n isPending,\n}: {\n readonly children: ReactNode\n readonly isPending: boolean\n}) {\n return (\n <View\n {...(isPending ? CONCEALED : undefined)}\n style={isPending ? pendingStyles.hidden : undefined}\n >\n {children}\n </View>\n )\n}\n\n/**\n * The indeterminate ring a pending button draws over its content, in the\n * button's content colour rather than the progress page's primary, which on a\n * filled button is the fill itself. `diameter` is what the content it\n * replaces measures, the label's type size on Button and the icon on\n * IconButton, which is what web's `1em` reads in each.\n */\nfunction PendingRing({\n color,\n diameter,\n label,\n}: {\n readonly color: ColorValue\n readonly diameter: number\n readonly label: string\n}) {\n return (\n <View style={pendingStyles.ring}>\n <IconStyleContext value={slotOf(color, diameter)}>\n <ProgressIndicator\n aria-label={label}\n diameter={`${diameter}px`}\n isIndeterminate\n tone=\"inherit\"\n variant=\"circular\"\n />\n </IconStyleContext>\n </View>\n )\n}\n\nfunction slotOf(color: ColorValue, size: number): IconStyle {\n return { color, size }\n}\n\n/**\n * The ring's name, and the announcement a button makes as it goes pending.\n * The name is `pendingLabel`, or the word for loading in the device's locale.\n *\n * On iOS and Android the change is said through VoiceOver and TalkBack, as\n * web's is said through React Aria's live announcer, since a ring appearing\n * under them is not reliably read. Web says it only to a reader whose focus\n * is on the button; a screen reader's focus is not something a component\n * here can read, so it is said whatever has focus. A button pending as it\n * mounts says nothing, as on web, which announces a change. React Native Web\n * has no announcer, so there the ring is read when the button next is.\n */\nfunction usePending(isPending: boolean, pendingLabel: string | undefined) {\n const label = pendingLabel ?? messagesFor().loading\n const was = useRef(isPending)\n\n useEffect(() => {\n if (\n isPending &&\n !was.current &&\n (Platform.OS === 'ios' || Platform.OS === 'android')\n ) {\n AccessibilityInfo.announceForAccessibility(label)\n }\n was.current = isPending\n }, [isPending, label])\n\n return label\n}\n\nexport {\n BUSY,\n CONCEALED,\n PendingContent,\n PendingRing,\n pendingStyles,\n usePending,\n}\n"],"mappings":"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"}
@@ -81,7 +81,7 @@ type ButtonVariant = 'elevated' | 'filled' | 'outlined' | 'text' | 'tonal';
81
81
  * target through `hitSlop`, which a `hitSlop` of the call site's own replaces.
82
82
  * `style`, `ref` and `testID` land on the `Pressable`.
83
83
  */
84
- declare function Button({ children, defaultSelected, disableRipple, hitSlop: ownHitSlop, href, icon, isDisabled, isSelected, onBlur, onChange, onFocus, onHoverIn, onHoverOut, onLayout, onPress, onPressIn, onPressOut, shape, size, style, type, variant, ...props }: ButtonProps): import("react").JSX.Element;
84
+ declare function Button({ children, defaultSelected, disableRipple, hitSlop: ownHitSlop, href, icon, isDisabled, isPending, isSelected, onBlur, onChange, onFocus, onHoverIn, onHoverOut, onLayout, onLongPress, onPress, onPressIn, onPressOut, pendingLabel, shape, size, style, type, variant, ...props }: ButtonProps): import("react").JSX.Element;
85
85
  //#endregion
86
86
  export { Button, type ButtonProps, type ButtonShape, type ButtonSize, type ButtonVariant };
87
87
  //# sourceMappingURL=index.d.ts.map
@@ -4,9 +4,10 @@ import { composeStyles, themedStyles } from "../../theme/styles.js";
4
4
  import { useTypeRole } from "../../typography/roles.js";
5
5
  import { AnimatedPressable, HEIGHTS, OUTLINE_WIDTHS, PRESSED_CORNERS, buttonStyles, toggleState, useButtonAction, useCorner } from "../../button/index.js";
6
6
  import { palettesOf } from "../../button/palettes.js";
7
+ import { BUSY, CONCEALED, PendingContent, PendingRing, pendingStyles, usePending } from "../../button/pending.js";
7
8
  import { hitSlop, usePressFeedback } from "../../press/index.js";
8
9
  import { c } from "react/compiler-runtime";
9
- import { StyleSheet, Text } from "react-native";
10
+ import { StyleSheet, Text, useWindowDimensions } from "react-native";
10
11
  import { jsx, jsxs } from "react/jsx-runtime";
11
12
  import Animated from "react-native-reanimated";
12
13
  //#region src/components/button/index.tsx
@@ -160,7 +161,7 @@ const outlinedSizesOf = themedStyles((theme) => {
160
161
  * `style`, `ref` and `testID` land on the `Pressable`.
161
162
  */
162
163
  function Button(t0) {
163
- const $ = c(58);
164
+ const $ = c(71);
164
165
  let children;
165
166
  let defaultSelected;
166
167
  let href;
@@ -172,10 +173,12 @@ function Button(t0) {
172
173
  let onHoverIn;
173
174
  let onHoverOut;
174
175
  let onLayout;
176
+ let onLongPress;
175
177
  let onPress;
176
178
  let onPressIn;
177
179
  let onPressOut;
178
180
  let ownHitSlop;
181
+ let pendingLabel;
179
182
  let props;
180
183
  let style;
181
184
  let t1;
@@ -183,9 +186,10 @@ function Button(t0) {
183
186
  let t3;
184
187
  let t4;
185
188
  let t5;
189
+ let t6;
186
190
  let type;
187
191
  if ($[0] !== t0) {
188
- ({children, defaultSelected, disableRipple: t1, hitSlop: ownHitSlop, href, icon, isDisabled: t2, isSelected, onBlur, onChange, onFocus, onHoverIn, onHoverOut, onLayout, onPress, onPressIn, onPressOut, shape: t3, size: t4, style, type, variant: t5, ...props} = t0);
192
+ ({children, defaultSelected, disableRipple: t1, hitSlop: ownHitSlop, href, icon, isDisabled: t2, isPending: t3, isSelected, onBlur, onChange, onFocus, onHoverIn, onHoverOut, onLayout, onLongPress, onPress, onPressIn, onPressOut, pendingLabel, shape: t4, size: t5, style, type, variant: t6, ...props} = t0);
189
193
  $[0] = t0;
190
194
  $[1] = children;
191
195
  $[2] = defaultSelected;
@@ -198,18 +202,21 @@ function Button(t0) {
198
202
  $[9] = onHoverIn;
199
203
  $[10] = onHoverOut;
200
204
  $[11] = onLayout;
201
- $[12] = onPress;
202
- $[13] = onPressIn;
203
- $[14] = onPressOut;
204
- $[15] = ownHitSlop;
205
- $[16] = props;
206
- $[17] = style;
207
- $[18] = t1;
208
- $[19] = t2;
209
- $[20] = t3;
210
- $[21] = t4;
211
- $[22] = t5;
212
- $[23] = type;
205
+ $[12] = onLongPress;
206
+ $[13] = onPress;
207
+ $[14] = onPressIn;
208
+ $[15] = onPressOut;
209
+ $[16] = ownHitSlop;
210
+ $[17] = pendingLabel;
211
+ $[18] = props;
212
+ $[19] = style;
213
+ $[20] = t1;
214
+ $[21] = t2;
215
+ $[22] = t3;
216
+ $[23] = t4;
217
+ $[24] = t5;
218
+ $[25] = t6;
219
+ $[26] = type;
213
220
  } else {
214
221
  children = $[1];
215
222
  defaultSelected = $[2];
@@ -222,30 +229,37 @@ function Button(t0) {
222
229
  onHoverIn = $[9];
223
230
  onHoverOut = $[10];
224
231
  onLayout = $[11];
225
- onPress = $[12];
226
- onPressIn = $[13];
227
- onPressOut = $[14];
228
- ownHitSlop = $[15];
229
- props = $[16];
230
- style = $[17];
231
- t1 = $[18];
232
- t2 = $[19];
233
- t3 = $[20];
234
- t4 = $[21];
235
- t5 = $[22];
236
- type = $[23];
232
+ onLongPress = $[12];
233
+ onPress = $[13];
234
+ onPressIn = $[14];
235
+ onPressOut = $[15];
236
+ ownHitSlop = $[16];
237
+ pendingLabel = $[17];
238
+ props = $[18];
239
+ style = $[19];
240
+ t1 = $[20];
241
+ t2 = $[21];
242
+ t3 = $[22];
243
+ t4 = $[23];
244
+ t5 = $[24];
245
+ t6 = $[25];
246
+ type = $[26];
237
247
  }
238
248
  const disableRipple = t1 === void 0 ? false : t1;
239
249
  const isDisabled = t2 === void 0 ? false : t2;
240
- const shape = t3 === void 0 ? "round" : t3;
241
- const size = t4 === void 0 ? "md" : t4;
242
- const variant = t5 === void 0 ? "filled" : t5;
250
+ const isPending = t3 === void 0 ? false : t3;
251
+ const shape = t4 === void 0 ? "round" : t4;
252
+ const size = t5 === void 0 ? "md" : t5;
253
+ const variant = t6 === void 0 ? "filled" : t6;
243
254
  const theme = useTheme();
244
255
  const role = useTypeRole(ROLES[size]);
256
+ const { fontScale } = useWindowDimensions();
257
+ const pending = href === void 0 && isPending;
258
+ const ringLabel = usePending(pending, pendingLabel);
245
259
  const isToggle = variant !== "text" && (defaultSelected !== void 0 || isSelected !== void 0 || onChange !== void 0);
246
- let t6;
247
- if ($[24] !== defaultSelected || $[25] !== href || $[26] !== isSelected || $[27] !== isToggle || $[28] !== onChange || $[29] !== onPress || $[30] !== type) {
248
- t6 = {
260
+ let t7;
261
+ if ($[27] !== defaultSelected || $[28] !== href || $[29] !== isSelected || $[30] !== isToggle || $[31] !== onChange || $[32] !== onPress || $[33] !== type) {
262
+ t7 = {
249
263
  defaultSelected,
250
264
  href,
251
265
  isSelected,
@@ -254,16 +268,16 @@ function Button(t0) {
254
268
  onPress,
255
269
  type
256
270
  };
257
- $[24] = defaultSelected;
258
- $[25] = href;
259
- $[26] = isSelected;
260
- $[27] = isToggle;
261
- $[28] = onChange;
262
- $[29] = onPress;
263
- $[30] = type;
264
- $[31] = t6;
265
- } else t6 = $[31];
266
- const action = useButtonAction(t6);
271
+ $[27] = defaultSelected;
272
+ $[28] = href;
273
+ $[29] = isSelected;
274
+ $[30] = isToggle;
275
+ $[31] = onChange;
276
+ $[32] = onPress;
277
+ $[33] = type;
278
+ $[34] = t7;
279
+ } else t7 = $[34];
280
+ const action = useButtonAction(t7);
267
281
  const colors = palettesOf(theme)[isDisabled ? DISABLED[variant] : variant !== "text" && isToggle ? TOGGLE_PALETTES[variant][action.selected ? "selected" : "unselected"] : variant];
268
282
  const feedback = usePressFeedback({
269
283
  color: colors.content,
@@ -274,90 +288,118 @@ function Button(t0) {
274
288
  onHoverIn,
275
289
  onHoverOut,
276
290
  onPressIn,
277
- onPressOut
291
+ onPressOut,
292
+ pending
278
293
  });
279
294
  const { radii } = theme;
280
295
  const resting = shape === "square" !== action.selected ? radii[SQUARE_CORNERS[size]] : radii.pill;
281
296
  const shaped = useCorner(feedback.pressed ? radii[PRESSED_CORNERS[size]] : resting, onLayout);
282
297
  const T0 = AnimatedPressable;
283
- const t7 = href === void 0 ? "button" : "link";
284
- let t8;
285
- if ($[32] !== action.selected || $[33] !== isToggle) {
286
- t8 = isToggle ? toggleState(action.selected) : void 0;
287
- $[32] = action.selected;
288
- $[33] = isToggle;
289
- $[34] = t8;
290
- } else t8 = $[34];
291
- const t9 = feedback.handlers;
292
- let t10;
293
- if ($[35] !== ownHitSlop || $[36] !== size || $[37] !== theme.sizing) {
294
- t10 = ownHitSlop ?? hitSlop(theme.sizing[HEIGHTS[size]]);
295
- $[35] = ownHitSlop;
296
- $[36] = size;
297
- $[37] = theme.sizing;
298
- $[38] = t10;
299
- } else t10 = $[38];
300
- const t11 = shaped.onLayout;
301
- const t12 = action.onPress;
302
- const t13 = composeStyles(styles.base, sizesOf(theme)[size], variant === "outlined" ? outlinedSizesOf(theme)[size] : void 0, colors.container, shadowOf(variant, feedback, isDisabled, theme), shaped.corner, feedback.ring, style);
303
- let t14;
304
- if ($[39] !== shaped.corner) {
305
- t14 = composeStyles(buttonStyles.clip, shaped.corner);
306
- $[39] = shaped.corner;
307
- $[40] = t14;
308
- } else t14 = $[40];
309
- let t15;
310
- if ($[41] !== feedback.layer || $[42] !== t14) {
311
- t15 = /* @__PURE__ */ jsx(Animated.View, {
312
- style: t14,
298
+ const t8 = href === void 0 ? "button" : "link";
299
+ let t9;
300
+ if ($[35] !== action.selected || $[36] !== isToggle) {
301
+ t9 = isToggle ? toggleState(action.selected) : void 0;
302
+ $[35] = action.selected;
303
+ $[36] = isToggle;
304
+ $[37] = t9;
305
+ } else t9 = $[37];
306
+ const t10 = pending ? BUSY : void 0;
307
+ const t11 = feedback.handlers;
308
+ let t12;
309
+ if ($[38] !== ownHitSlop || $[39] !== size || $[40] !== theme.sizing) {
310
+ t12 = ownHitSlop ?? hitSlop(theme.sizing[HEIGHTS[size]]);
311
+ $[38] = ownHitSlop;
312
+ $[39] = size;
313
+ $[40] = theme.sizing;
314
+ $[41] = t12;
315
+ } else t12 = $[41];
316
+ const t13 = shaped.onLayout;
317
+ const t14 = pending ? void 0 : onLongPress;
318
+ const t15 = pending ? void 0 : action.onPress;
319
+ const t16 = composeStyles(styles.base, sizesOf(theme)[size], variant === "outlined" ? outlinedSizesOf(theme)[size] : void 0, colors.container, shadowOf(variant, feedback, isDisabled, theme), shaped.corner, feedback.ring, style);
320
+ let t17;
321
+ if ($[42] !== shaped.corner) {
322
+ t17 = composeStyles(buttonStyles.clip, shaped.corner);
323
+ $[42] = shaped.corner;
324
+ $[43] = t17;
325
+ } else t17 = $[43];
326
+ let t18;
327
+ if ($[44] !== feedback.layer || $[45] !== t17) {
328
+ t18 = /* @__PURE__ */ jsx(Animated.View, {
329
+ style: t17,
313
330
  children: feedback.layer
314
331
  });
315
- $[41] = feedback.layer;
316
- $[42] = t14;
317
- $[43] = t15;
318
- } else t15 = $[43];
319
- const t16 = icon === void 0 ? null : /* @__PURE__ */ jsx(IconStyleContext, {
320
- value: iconStyleOf(colors.content, size),
321
- children: icon
332
+ $[44] = feedback.layer;
333
+ $[45] = t17;
334
+ $[46] = t18;
335
+ } else t18 = $[46];
336
+ const t19 = icon === void 0 ? null : /* @__PURE__ */ jsx(PendingContent, {
337
+ isPending: pending,
338
+ children: /* @__PURE__ */ jsx(IconStyleContext, {
339
+ value: iconStyleOf(colors.content, size),
340
+ children: icon
341
+ })
322
342
  });
323
- const t17 = children === void 0 ? null : /* @__PURE__ */ jsx(Text, {
324
- style: composeStyles(role, styles.label, colors.label),
343
+ const t20 = children === void 0 ? null : /* @__PURE__ */ jsx(Text, {
344
+ ...pending ? CONCEALED : void 0,
345
+ style: composeStyles(role, styles.label, colors.label, pending ? pendingStyles.hidden : void 0),
325
346
  children
326
347
  });
327
- let t18;
328
- if ($[44] !== T0 || $[45] !== action.onPress || $[46] !== feedback.handlers || $[47] !== isDisabled || $[48] !== props || $[49] !== shaped.onLayout || $[50] !== t10 || $[51] !== t13 || $[52] !== t15 || $[53] !== t16 || $[54] !== t17 || $[55] !== t7 || $[56] !== t8) {
329
- t18 = /* @__PURE__ */ jsxs(T0, {
330
- role: t7,
331
- ...t8,
332
- ...props,
348
+ let t21;
349
+ if ($[47] !== colors || $[48] !== fontScale || $[49] !== pending || $[50] !== ringLabel || $[51] !== size || $[52] !== theme.typography) {
350
+ t21 = pending ? /* @__PURE__ */ jsx(PendingRing, {
351
+ color: colors.content,
352
+ diameter: theme.typography[`${ROLES[size]}Size`] * fontScale,
353
+ label: ringLabel
354
+ }) : null;
355
+ $[47] = colors;
356
+ $[48] = fontScale;
357
+ $[49] = pending;
358
+ $[50] = ringLabel;
359
+ $[51] = size;
360
+ $[52] = theme.typography;
361
+ $[53] = t21;
362
+ } else t21 = $[53];
363
+ let t22;
364
+ if ($[54] !== T0 || $[55] !== feedback.handlers || $[56] !== isDisabled || $[57] !== props || $[58] !== shaped.onLayout || $[59] !== t10 || $[60] !== t12 || $[61] !== t14 || $[62] !== t15 || $[63] !== t16 || $[64] !== t18 || $[65] !== t19 || $[66] !== t20 || $[67] !== t21 || $[68] !== t8 || $[69] !== t9) {
365
+ t22 = /* @__PURE__ */ jsxs(T0, {
366
+ role: t8,
333
367
  ...t9,
368
+ ...t10,
369
+ ...props,
370
+ ...t11,
334
371
  disabled: isDisabled,
335
- hitSlop: t10,
336
- onLayout: t11,
337
- onPress: t12,
338
- style: t13,
372
+ hitSlop: t12,
373
+ onLayout: t13,
374
+ onLongPress: t14,
375
+ onPress: t15,
376
+ style: t16,
339
377
  children: [
340
- t15,
341
- t16,
342
- t17
378
+ t18,
379
+ t19,
380
+ t20,
381
+ t21
343
382
  ]
344
383
  });
345
- $[44] = T0;
346
- $[45] = action.onPress;
347
- $[46] = feedback.handlers;
348
- $[47] = isDisabled;
349
- $[48] = props;
350
- $[49] = shaped.onLayout;
351
- $[50] = t10;
352
- $[51] = t13;
353
- $[52] = t15;
354
- $[53] = t16;
355
- $[54] = t17;
356
- $[55] = t7;
357
- $[56] = t8;
358
- $[57] = t18;
359
- } else t18 = $[57];
360
- return t18;
384
+ $[54] = T0;
385
+ $[55] = feedback.handlers;
386
+ $[56] = isDisabled;
387
+ $[57] = props;
388
+ $[58] = shaped.onLayout;
389
+ $[59] = t10;
390
+ $[60] = t12;
391
+ $[61] = t14;
392
+ $[62] = t15;
393
+ $[63] = t16;
394
+ $[64] = t18;
395
+ $[65] = t19;
396
+ $[66] = t20;
397
+ $[67] = t21;
398
+ $[68] = t8;
399
+ $[69] = t9;
400
+ $[70] = t22;
401
+ } else t22 = $[70];
402
+ return t22;
361
403
  }
362
404
  function iconStyleOf(color, size) {
363
405
  return {