@kanso-labs/react-native-kanso-ui 0.7.0 → 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +37 -0
- package/dist/badge/index.js +25 -23
- package/dist/badge/index.js.map +1 -1
- package/dist/button/index.d.ts +2 -1
- package/dist/button/index.js.map +1 -1
- package/dist/button/pending.js +17 -21
- package/dist/button/pending.js.map +1 -1
- package/dist/calendar/index.js +1 -1
- package/dist/components/autocomplete/index.js +28 -26
- package/dist/components/autocomplete/index.js.map +1 -1
- package/dist/components/avatar/index.d.ts +2 -2
- package/dist/components/avatar/index.js +2 -2
- package/dist/components/avatar/index.js.map +1 -1
- package/dist/components/calendar/index.d.ts +1 -1
- package/dist/components/calendar/index.js +77 -38
- package/dist/components/calendar/index.js.map +1 -1
- package/dist/components/chip-group/index.d.ts +4 -3
- package/dist/components/chip-group/index.js +47 -45
- package/dist/components/chip-group/index.js.map +1 -1
- package/dist/components/copy-field/index.d.ts +5 -5
- package/dist/components/copy-field/index.js +30 -28
- package/dist/components/copy-field/index.js.map +1 -1
- package/dist/components/currency/index.d.ts +2 -2
- package/dist/components/currency/index.js +3 -1
- package/dist/components/currency/index.js.map +1 -1
- package/dist/components/list/index.d.ts +6 -5
- package/dist/components/list/index.js +97 -106
- package/dist/components/list/index.js.map +1 -1
- package/dist/components/menu/index.d.ts +4 -3
- package/dist/components/menu/index.js +32 -39
- package/dist/components/menu/index.js.map +1 -1
- package/dist/components/meter/index.d.ts +2 -1
- package/dist/components/meter/index.js +9 -7
- package/dist/components/meter/index.js.map +1 -1
- package/dist/components/number-field/index.d.ts +7 -3
- package/dist/components/number-field/index.js +214 -192
- package/dist/components/number-field/index.js.map +1 -1
- package/dist/components/product-icon/index.d.ts +2 -2
- package/dist/components/product-icon/index.js +2 -2
- package/dist/components/product-icon/index.js.map +1 -1
- package/dist/components/progress-indicator/index.d.ts +2 -1
- package/dist/components/progress-indicator/index.js +15 -13
- package/dist/components/progress-indicator/index.js.map +1 -1
- package/dist/components/search-field/index.d.ts +3 -3
- package/dist/components/search-field/index.js +105 -104
- package/dist/components/search-field/index.js.map +1 -1
- package/dist/components/sheet/index.d.ts +2 -2
- package/dist/components/sheet/index.js +22 -26
- package/dist/components/sheet/index.js.map +1 -1
- package/dist/components/snackbar/index.d.ts +3 -3
- package/dist/components/snackbar/index.js +36 -34
- package/dist/components/snackbar/index.js.map +1 -1
- package/dist/components/tag/index.d.ts +58 -0
- package/dist/components/tag/index.js +205 -0
- package/dist/components/tag/index.js.map +1 -0
- package/dist/components/text-field/index.d.ts +6 -3
- package/dist/components/text-field/index.js +3 -0
- package/dist/components/text-field/index.js.map +1 -1
- package/dist/field/index.js +39 -36
- package/dist/field/index.js.map +1 -1
- package/dist/i18n/index.js +12 -1
- package/dist/i18n/index.js.map +1 -1
- package/dist/i18n/messages.js +367 -0
- package/dist/i18n/messages.js.map +1 -1
- package/dist/i18n/provider.d.ts +53 -0
- package/dist/i18n/provider.js +96 -0
- package/dist/i18n/provider.js.map +1 -0
- package/dist/index.d.ts +3 -1
- package/dist/index.js +3 -1
- package/dist/indicator/index.js +1 -2
- package/dist/indicator/index.js.map +1 -1
- package/dist/mark/index.js +77 -117
- package/dist/mark/index.js.map +1 -1
- package/dist/mark/initials.js.map +1 -1
- package/dist/number/count.js +15 -0
- package/dist/number/count.js.map +1 -0
- package/dist/number/locale.js.map +1 -1
- package/dist/overlay/host.js +30 -21
- package/dist/overlay/host.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -155,6 +155,43 @@ export default function App() {
|
|
|
155
155
|
}
|
|
156
156
|
```
|
|
157
157
|
|
|
158
|
+
## Localisation
|
|
159
|
+
|
|
160
|
+
Wrap the app in `I18nProvider`, as kanso-ui is wrapped in React Aria's, and the
|
|
161
|
+
components write in its locale: the library's own words, such as a pending
|
|
162
|
+
ring's name, a bottom sheet's handle, a list's or a menu's loading and empty
|
|
163
|
+
rows and a calendar's chevrons, and its numbers and dates, such as a
|
|
164
|
+
`Currency`'s amount, a field's count, a `Meter`'s value and a `Calendar`'s
|
|
165
|
+
month. A component given a `locale` of its own writes in that one instead.
|
|
166
|
+
|
|
167
|
+
Outside any provider, and for a locale the runtime cannot parse, the components
|
|
168
|
+
write in the device's. So an app that follows the device needs no provider, and
|
|
169
|
+
one with a language setting of its own passes the setting, falling back to the
|
|
170
|
+
device's first language from Expo Localization:
|
|
171
|
+
|
|
172
|
+
```tsx
|
|
173
|
+
// app/_layout.tsx
|
|
174
|
+
import { I18nProvider } from '@kanso-labs/react-native-kanso-ui'
|
|
175
|
+
import { useLocales } from 'expo-localization'
|
|
176
|
+
import { Stack } from 'expo-router'
|
|
177
|
+
|
|
178
|
+
export default function RootLayout() {
|
|
179
|
+
const [device] = useLocales()
|
|
180
|
+
const language = useLanguageSetting() // the app's own, or undefined
|
|
181
|
+
|
|
182
|
+
return (
|
|
183
|
+
<I18nProvider locale={language ?? device?.languageTag}>
|
|
184
|
+
<Stack />
|
|
185
|
+
</I18nProvider>
|
|
186
|
+
)
|
|
187
|
+
}
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
`useLocale()` returns `{ direction, locale }`, as React Aria's does on web, for
|
|
191
|
+
an app's own formatting to match. The direction is `I18nManager`'s, which is
|
|
192
|
+
what React Native lays out by: the provider sets none, and an app turns its
|
|
193
|
+
layout over with `I18nManager.forceRTL` and a reload.
|
|
194
|
+
|
|
158
195
|
## Development
|
|
159
196
|
|
|
160
197
|
Install with the Node version in `.tool-versions`:
|
package/dist/badge/index.js
CHANGED
|
@@ -1,30 +1,33 @@
|
|
|
1
1
|
import { useTheme } from "../theme/scope.js";
|
|
2
2
|
import { composeStyles } from "../theme/styles.js";
|
|
3
3
|
import { useTypeRole } from "../typography/roles.js";
|
|
4
|
+
import { formatCount } from "../number/count.js";
|
|
5
|
+
import { useLocaleOf } from "../i18n/provider.js";
|
|
4
6
|
import { styles, stylesOf } from "./styles.js";
|
|
5
7
|
import { c } from "react/compiler-runtime";
|
|
6
8
|
import { Text, View } from "react-native";
|
|
7
9
|
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
8
10
|
//#region src/badge/index.tsx
|
|
9
|
-
let countFormat;
|
|
10
11
|
/**
|
|
11
12
|
* A badge on the top trailing corner of the icon it wraps: the small dot, or
|
|
12
13
|
* with `count` the large badge holding the number.
|
|
13
14
|
*/
|
|
14
15
|
function Badge(t0) {
|
|
15
|
-
const $ = c(
|
|
16
|
+
const $ = c(13);
|
|
16
17
|
const { children, count, inFlow: t1, max: t2 } = t0;
|
|
17
18
|
const inFlow = t1 === void 0 ? false : t1;
|
|
18
19
|
const max = t2 === void 0 ? 999 : t2;
|
|
19
20
|
const own = stylesOf(useTheme());
|
|
20
21
|
const role = useTypeRole("labelSmall");
|
|
22
|
+
const locale = useLocaleOf(void 0);
|
|
21
23
|
let t3;
|
|
22
|
-
if ($[0] !== count || $[1] !== max) {
|
|
23
|
-
t3 = count === void 0 ? void 0 : countOf(count, max);
|
|
24
|
+
if ($[0] !== count || $[1] !== locale || $[2] !== max) {
|
|
25
|
+
t3 = count === void 0 ? void 0 : countOf(count, max, locale);
|
|
24
26
|
$[0] = count;
|
|
25
|
-
$[1] =
|
|
26
|
-
$[2] =
|
|
27
|
-
|
|
27
|
+
$[1] = locale;
|
|
28
|
+
$[2] = max;
|
|
29
|
+
$[3] = t3;
|
|
30
|
+
} else t3 = $[3];
|
|
28
31
|
const label = t3;
|
|
29
32
|
const T0 = View;
|
|
30
33
|
const t4 = styles;
|
|
@@ -39,34 +42,33 @@ function Badge(t0) {
|
|
|
39
42
|
children: label
|
|
40
43
|
})] });
|
|
41
44
|
let t8;
|
|
42
|
-
if ($[
|
|
45
|
+
if ($[4] !== T1 || $[5] !== t6 || $[6] !== t7) {
|
|
43
46
|
t8 = /* @__PURE__ */ jsx(T1, {
|
|
44
47
|
"aria-hidden": t5,
|
|
45
48
|
style: t6,
|
|
46
49
|
children: t7
|
|
47
50
|
});
|
|
48
|
-
$[
|
|
49
|
-
$[
|
|
50
|
-
$[
|
|
51
|
-
$[
|
|
52
|
-
} else t8 = $[
|
|
51
|
+
$[4] = T1;
|
|
52
|
+
$[5] = t6;
|
|
53
|
+
$[6] = t7;
|
|
54
|
+
$[7] = t8;
|
|
55
|
+
} else t8 = $[7];
|
|
53
56
|
let t9;
|
|
54
|
-
if ($[
|
|
57
|
+
if ($[8] !== T0 || $[9] !== children || $[10] !== t4.anchor || $[11] !== t8) {
|
|
55
58
|
t9 = /* @__PURE__ */ jsxs(T0, {
|
|
56
59
|
style: t4.anchor,
|
|
57
60
|
children: [children, t8]
|
|
58
61
|
});
|
|
59
|
-
$[
|
|
60
|
-
$[
|
|
61
|
-
$[
|
|
62
|
-
$[
|
|
63
|
-
$[
|
|
64
|
-
} else t9 = $[
|
|
62
|
+
$[8] = T0;
|
|
63
|
+
$[9] = children;
|
|
64
|
+
$[10] = t4.anchor;
|
|
65
|
+
$[11] = t8;
|
|
66
|
+
$[12] = t9;
|
|
67
|
+
} else t9 = $[12];
|
|
65
68
|
return t9;
|
|
66
69
|
}
|
|
67
|
-
function countOf(count, max) {
|
|
68
|
-
|
|
69
|
-
return count > max ? `${countFormat.format(max)}+` : countFormat.format(count);
|
|
70
|
+
function countOf(count, max, locale) {
|
|
71
|
+
return count > max ? `${formatCount(max, locale)}+` : formatCount(count, locale);
|
|
70
72
|
}
|
|
71
73
|
//#endregion
|
|
72
74
|
export { Badge };
|
package/dist/badge/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":["ReactNode","TextStyle","ViewStyle","Text","NativeText","View","useTheme","composeStyles","useTypeRole","styles","stylesOf","BadgeProps","children","count","inFlow","max","
|
|
1
|
+
{"version":3,"file":"index.js","names":["ReactNode","TextStyle","ViewStyle","Text","NativeText","View","useLocaleOf","formatCount","useTheme","composeStyles","useTypeRole","styles","stylesOf","BadgeProps","children","count","inFlow","max","Badge","t0","$","_c","t1","t2","undefined","own","role","locale","t3","countOf","label","T0","T1","t5","t6","mark","small","large","over","t7","space","t8","t9","t4","anchor","_c","NativeText"],"sources":["../../src/badge/index.tsx"],"sourcesContent":["import type { ReactNode } from 'react'\nimport type { TextStyle, ViewStyle } from 'react-native'\n\nimport { Text as NativeText, View } from 'react-native'\n\nimport { useLocaleOf } from '../i18n/provider'\nimport { formatCount } from '../number/count'\nimport { useTheme } from '../theme/scope'\nimport { composeStyles } from '../theme/styles'\nimport { useTypeRole } from '../typography/roles'\nimport { styles, stylesOf } from './styles'\n\n// The badges page's badge, as kanso-ui's Badge draws it on a navigation\n// destination: a mark on the corner of an icon that flags something new\n// there, either as the 6dp small badge or as the 16dp large badge holding a\n// count. Both are the error pair, the error role with the count in on error,\n// on a full corner, and the count is label small, capped with a plus, as\n// \"999+\".\n//\n// The badge wraps the icon and places the mark against the icon's box,\n// which is what the page measures from. The small badge fills the icon's top\n// trailing 6dp square. The large badge's bottom leading corner sits 14dp\n// down and 12dp in from the icon's top trailing corner, so it stands 2dp\n// above the icon and grows toward the end as its count lengthens. Both are\n// placed by `start` and `end`, so the mark stays on the trailing corner under\n// right-to-left.\n//\n// Where a label stands beside the icon rather than under it, the large badge\n// joins the flow after the icon instead, so the label starts after it rather\n// than under it: `inFlow`. The small badge stays inside the icon's box, so\n// nothing has to make room for it.\n//\n// The mark is hidden from a screen reader. A count read on its own is a\n// number with no subject; it belongs in the name of the control it sits on,\n// where a reader meets it with what it counts, \"First item, 3 new\".\n//\n// The count is set apart from the text before it by a space drawn out of the\n// flow. Under React Native Web the count is still text inside the control,\n// and with nothing between them a label and its count ran together as\n// \"Second item3\", a word in no name: what axe's label-in-name check fails,\n// and not what a speech-input user would say. Web draws the count as\n// generated content instead, which a style here has no counterpart for.\n\ntype BadgeProps = {\n /** What the badge is drawn on: an icon, at 24dp. */\n readonly children?: ReactNode\n /**\n * The number to show, which draws the large badge. Left out, the badge is\n * the small dot, which flags without counting.\n */\n readonly count?: number | undefined\n /**\n * Draws the large badge in the flow after the icon rather than over its\n * corner, for a label beside the icon.\n * @default false\n */\n readonly inFlow?: boolean\n /**\n * The largest count drawn as itself. Above it the badge reads `max` and a\n * plus, which is the page's 999+ at the default.\n * @default 999\n */\n readonly max?: number\n}\n\n/**\n * A badge on the top trailing corner of the icon it wraps: the small dot, or\n * with `count` the large badge holding the number.\n */\nfunction Badge({ children, count, inFlow = false, max = 999 }: BadgeProps) {\n const own = stylesOf(useTheme())\n const role = useTypeRole('labelSmall')\n const locale = useLocaleOf(undefined)\n const label = count === undefined ? undefined : countOf(count, max, locale)\n\n return (\n <View style={styles.anchor}>\n {children}\n <View\n aria-hidden\n style={composeStyles<ViewStyle>(\n own.mark,\n label === undefined\n ? styles.small\n : [own.large, inFlow ? styles.inFlow : styles.over],\n )}\n >\n {label === undefined ? null : (\n <>\n <NativeText style={styles.space}> </NativeText>\n <NativeText style={composeStyles<TextStyle>(role, own.count)}>\n {label}\n </NativeText>\n </>\n )}\n </View>\n </View>\n )\n}\n\n// The count in the locale's digits and grouping, as web writes it in the\n// provider's.\nfunction countOf(count: number, max: number, locale: string) {\n return count > max\n ? `${formatCount(max, locale)}+`\n : formatCount(count, locale)\n}\n\nexport type { BadgeProps }\n\nexport { Badge }\n"],"mappings":";;;;;;;;;;;;;;AAqEA,SAAAkB,MAAAC,IAAA;CAAA,MAAAC,IAAAC,EAAA,EAAA;CAAe,MAAA,EAAAP,UAAAC,OAAAC,QAAAM,IAAAL,KAAAM,OAAAJ;CAAmB,MAAAH,SAAAM,OAAAE,KAAAA,IAAA,QAAAF;CAAgB,MAAAL,MAAAM,OAAAC,KAAAA,IAAA,MAAAD;CAChD,MAAAE,MAAYb,SAASJ,SAAS,CAAC;CAC/B,MAAAkB,OAAahB,YAAY,YAAY;CACrC,MAAAiB,SAAerB,YAAYkB,KAAAA,CAAS;CAAC,IAAAI;CAAA,IAAAR,EAAA,OAAAL,SAAAK,EAAA,OAAAO,UAAAP,EAAA,OAAAH,KAAA;EACvBW,KAAAb,UAAUS,KAAAA,IAAVA,KAAAA,IAAkCK,QAAQd,OAAOE,KAAKU,MAAM;EAACP,EAAA,KAAAL;EAAAK,EAAA,KAAAO;EAAAP,EAAA,KAAAH;EAAAG,EAAA,KAAAQ;CAAA,OAAAA,KAAAR,EAAA;CAA3E,MAAAU,QAAcF;CAGX,MAAAG,KAAA1B;CAAYM,MAAAA,KAAAA;CAEV,MAAAqB,KAAA3B;CACC,MAAA4B,KAAA;CACO,MAAAC,KAAAzB,cACLgB,IAAGU,MACHL,UAAUN,KAAAA,IACNb,OAAMyB,QADV,CAEKX,IAAGY,OAAQrB,SAASL,OAAMK,SAAUL,OAAM2B,IAAK,CACtD;CAEC,MAAAC,KAAAT,UAAUN,KAAAA,IAAV,OAAA,qBAAA,UAAA,EAAA,UAAA,CAEG,oBAACsB,MAAD;EAAmB,OAAAnC,OAAM6B;EAAQ,UAAA;CAAtB,CAAA,GACX,oBAACM,MAAD;EAAmB,OAAArC,cAAyBiB,MAAMD,IAAGV,KAAM;EACxDe,UAAAA;CADQ,CAAA,CAEE,EAAA,CAAA;CAEhB,IAAAW;CAAA,IAAArB,EAAA,OAAAY,MAAAZ,EAAA,OAAAc,MAAAd,EAAA,OAAAmB,IAAA;EAhBHE,KAAA,oBAAC,IAAD;GACE,eAAAR;GACO,OAAAC;GAONK,UAAAA;EATE,CAAA;EAiBEnB,EAAA,KAAAY;EAAAZ,EAAA,KAAAc;EAAAd,EAAA,KAAAmB;EAAAnB,EAAA,KAAAqB;CAAA,OAAAA,KAAArB,EAAA;CAAA,IAAAsB;CAAA,IAAAtB,EAAA,OAAAW,MAAAX,EAAA,OAAAN,YAAAM,EAAA,QAAAuB,GAAAC,UAAAxB,EAAA,QAAAqB,IAAA;EAnBTC,KAAA,qBAAC,IAAD;GAAa,OAAA/B,GAAMiC;GAAnB,UAAA,CACG9B,UACD2B,EAFG;;EAoBErB,EAAA,KAAAW;EAAAX,EAAA,KAAAN;EAAAM,EAAA,MAAAuB,GAAAC;EAAAxB,EAAA,MAAAqB;EAAArB,EAAA,MAAAsB;CAAA,OAAAA,KAAAtB,EAAA;CAAA,OApBPsB;AAoBO;AAMX,SAASb,QAAQd,OAAeE,KAAaU,QAAgB;CAC3D,OAAOZ,QAAQE,MACX,GAAGV,YAAYU,KAAKU,MAAM,EAAC,KAC3BpB,YAAYQ,OAAOY,MAAM;AAC/B"}
|
package/dist/button/index.d.ts
CHANGED
|
@@ -38,7 +38,8 @@ type ButtonPending = {
|
|
|
38
38
|
/**
|
|
39
39
|
* The name of the ring shown while the button is pending, for a screen
|
|
40
40
|
* reader. The content it replaces is hidden while it shows. Left out, it is
|
|
41
|
-
* the word for it in the
|
|
41
|
+
* the word for it in the `I18nProvider`'s locale, or the device's outside
|
|
42
|
+
* one, "Loading" in English.
|
|
42
43
|
*/
|
|
43
44
|
readonly pendingLabel?: string;
|
|
44
45
|
};
|
package/dist/button/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":["Ref","GestureResponderEvent","HostInstance","LayoutChangeEvent","PressableProps","SharedValue","use","useState","Platform","Pressable","StyleSheet","Animated","Easing","useAnimatedStyle","useReducedMotion","useSharedValue","withTiming","ResolvedTheme","FormContext","refsOf","useOpenLink","useTheme","ButtonSlotContext","ButtonLinkForm","defaultSelected","href","isPending","isSelected","onChange","pendingLabel","slot","type","ButtonPending","ButtonPressForm","ButtonToggleForm","ControlSize","SlotTriggerProps","delayLongPress","onHoverIn","onHoverOut","onLongPress","ref","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","andThen","own","event","E","undefined","measureThen","height","onLayout","set","nativeEvent","layout","pressThen","onPress","then","useButtonAction","t0","$","_c","isToggle","openLink","form","slots","ownSelected","setOwnSelected","filled","t1","follow","requestSubmit","t2","t3","useCorner","radius","motion","reduceMotion","durationShort2","easingEmphasized","bezier","duration","easing","timing","corner","measured","get","target","Math","min","borderRadius","t4","useSlotTrigger","trigger","expanded","_c"],"sources":["../../src/button/index.ts"],"sourcesContent":["import type { Ref } from 'react'\nimport type {\n GestureResponderEvent,\n HostInstance,\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 { refsOf } from '../ref'\nimport { useOpenLink } from '../router'\nimport { useTheme } from '../theme/scope'\nimport { ButtonSlotContext } from './slots'\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, and what the slot it\n// fills in an overlay adds to it, the corner that eases between shapes, and\n// 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 slot?: 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 * `close` closes the overlay the button is in, as web's React Aria slot of\n * that name does: a `Dialog` given one inside its content closes when it is\n * pressed, after the button's own `onPress`.\n */\n readonly slot?: 'close'\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 slot?: never\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. */\n// What `useSlotTrigger` hands the Pressable, spelled out because the\n// composed ref's inferred type names a private React Native module, which a\n// declaration file cannot reach.\ntype SlotTriggerProps = {\n readonly 'aria-expanded'?: boolean\n readonly delayLongPress: PressableProps['delayLongPress']\n readonly onHoverIn: PressableProps['onHoverIn']\n readonly onHoverOut: PressableProps['onHoverOut']\n readonly onLongPress: PressableProps['onLongPress']\n readonly ref: Ref<HostInstance> | undefined\n}\n\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// A handler that runs the call site's own and then the slot's, which takes no\n// event, or the call site's alone where the slot adds nothing.\nfunction andThen<E>(\n own: ((event: E) => void) | null | undefined,\n slot: (() => void) | undefined,\n) {\n if (slot === undefined) {\n return own ?? undefined\n }\n\n return (event: E) => {\n own?.(event)\n slot()\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(\n onPress: PressableProps['onPress'],\n then: (event: GestureResponderEvent) => void,\n) {\n return (event: GestureResponderEvent) => {\n onPress?.(event)\n then(event)\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 *\n * Given a `slot`, a button that is not a link then does what that slot of\n * the overlay around it does, as src/button's slots.ts sets out: opens it\n * from the trigger, or closes it from its content.\n */\nfunction useButtonAction({\n defaultSelected,\n href,\n isSelected,\n isToggle,\n onChange,\n onPress,\n slot,\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 slot?: 'close' | 'default'\n readonly type: 'button' | 'submit' | undefined\n}) {\n const openLink = useOpenLink()\n const form = use(FormContext)\n const slots = use(ButtonSlotContext)\n const [ownSelected, setOwnSelected] = useState(defaultSelected ?? false)\n const selected = isToggle && (isSelected ?? ownSelected)\n const filled = slot === undefined ? undefined : slots?.[slot]\n\n function follow(event: GestureResponderEvent) {\n if (href !== undefined) {\n openLink(href)\n return\n }\n if (isToggle) {\n setOwnSelected(!selected)\n onChange?.(!selected)\n } else if (type === 'submit') {\n form?.requestSubmit()\n }\n filled?.(event)\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\n/**\n * What a button filling the default slot of an overlay drawn beside it takes\n * on, as src/button's slots.ts sets out: its node is kept for the overlay to\n * be placed against, alongside the call site's own `ref`, `aria-expanded`\n * says whether the overlay is open, and the overlay's hover and long press\n * handlers run after the call site's. A button filling no such slot, or a\n * link, keeps its own props untouched.\n */\nfunction useSlotTrigger({\n delayLongPress,\n href,\n onHoverIn,\n onHoverOut,\n onLongPress,\n ref,\n slot,\n}: {\n readonly delayLongPress: PressableProps['delayLongPress']\n readonly href: string | undefined\n readonly onHoverIn: PressableProps['onHoverIn']\n readonly onHoverOut: PressableProps['onHoverOut']\n readonly onLongPress: PressableProps['onLongPress']\n readonly ref: Ref<HostInstance> | undefined\n readonly slot: 'close' | undefined\n}): SlotTriggerProps {\n const slots = use(ButtonSlotContext)\n const trigger =\n href === undefined && slot === undefined ? slots?.trigger : undefined\n\n return {\n ...(trigger === undefined\n ? { delayLongPress, ref }\n : {\n 'aria-expanded': trigger.expanded,\n delayLongPress: trigger.delayLongPress ?? delayLongPress,\n ref: refsOf(trigger.ref, ref),\n }),\n onHoverIn: andThen(onHoverIn, trigger?.onHoverIn),\n onHoverOut: andThen(onHoverOut, trigger?.onHoverOut),\n onLongPress: andThen(onLongPress, trigger?.onLongPress),\n }\n}\n\nexport {\n AnimatedPressable,\n buttonStyles,\n HEIGHTS,\n OUTLINE_WIDTHS,\n PRESSED_CORNERS,\n toggleState,\n useButtonAction,\n useCorner,\n useSlotTrigger,\n}\nexport type { ButtonLinkForm, ButtonPressForm, ButtonToggleForm, ControlSize }\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.js","names":["Ref","GestureResponderEvent","HostInstance","LayoutChangeEvent","PressableProps","SharedValue","use","useState","Platform","Pressable","StyleSheet","Animated","Easing","useAnimatedStyle","useReducedMotion","useSharedValue","withTiming","ResolvedTheme","FormContext","refsOf","useOpenLink","useTheme","ButtonSlotContext","ButtonLinkForm","defaultSelected","href","isPending","isSelected","onChange","pendingLabel","slot","type","ButtonPending","ButtonPressForm","ButtonToggleForm","ControlSize","SlotTriggerProps","delayLongPress","onHoverIn","onHoverOut","onLongPress","ref","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","andThen","own","event","E","undefined","measureThen","height","onLayout","set","nativeEvent","layout","pressThen","onPress","then","useButtonAction","t0","$","_c","isToggle","openLink","form","slots","ownSelected","setOwnSelected","filled","t1","follow","requestSubmit","t2","t3","useCorner","radius","motion","reduceMotion","durationShort2","easingEmphasized","bezier","duration","easing","timing","corner","measured","get","target","Math","min","borderRadius","t4","useSlotTrigger","trigger","expanded","_c"],"sources":["../../src/button/index.ts"],"sourcesContent":["import type { Ref } from 'react'\nimport type {\n GestureResponderEvent,\n HostInstance,\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 { refsOf } from '../ref'\nimport { useOpenLink } from '../router'\nimport { useTheme } from '../theme/scope'\nimport { ButtonSlotContext } from './slots'\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, and what the slot it\n// fills in an overlay adds to it, the corner that eases between shapes, and\n// 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 slot?: 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 `I18nProvider`'s locale, or the device's outside\n * one, \"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 * `close` closes the overlay the button is in, as web's React Aria slot of\n * that name does: a `Dialog` given one inside its content closes when it is\n * pressed, after the button's own `onPress`.\n */\n readonly slot?: 'close'\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 slot?: never\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. */\n// What `useSlotTrigger` hands the Pressable, spelled out because the\n// composed ref's inferred type names a private React Native module, which a\n// declaration file cannot reach.\ntype SlotTriggerProps = {\n readonly 'aria-expanded'?: boolean\n readonly delayLongPress: PressableProps['delayLongPress']\n readonly onHoverIn: PressableProps['onHoverIn']\n readonly onHoverOut: PressableProps['onHoverOut']\n readonly onLongPress: PressableProps['onLongPress']\n readonly ref: Ref<HostInstance> | undefined\n}\n\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// A handler that runs the call site's own and then the slot's, which takes no\n// event, or the call site's alone where the slot adds nothing.\nfunction andThen<E>(\n own: ((event: E) => void) | null | undefined,\n slot: (() => void) | undefined,\n) {\n if (slot === undefined) {\n return own ?? undefined\n }\n\n return (event: E) => {\n own?.(event)\n slot()\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(\n onPress: PressableProps['onPress'],\n then: (event: GestureResponderEvent) => void,\n) {\n return (event: GestureResponderEvent) => {\n onPress?.(event)\n then(event)\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 *\n * Given a `slot`, a button that is not a link then does what that slot of\n * the overlay around it does, as src/button's slots.ts sets out: opens it\n * from the trigger, or closes it from its content.\n */\nfunction useButtonAction({\n defaultSelected,\n href,\n isSelected,\n isToggle,\n onChange,\n onPress,\n slot,\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 slot?: 'close' | 'default'\n readonly type: 'button' | 'submit' | undefined\n}) {\n const openLink = useOpenLink()\n const form = use(FormContext)\n const slots = use(ButtonSlotContext)\n const [ownSelected, setOwnSelected] = useState(defaultSelected ?? false)\n const selected = isToggle && (isSelected ?? ownSelected)\n const filled = slot === undefined ? undefined : slots?.[slot]\n\n function follow(event: GestureResponderEvent) {\n if (href !== undefined) {\n openLink(href)\n return\n }\n if (isToggle) {\n setOwnSelected(!selected)\n onChange?.(!selected)\n } else if (type === 'submit') {\n form?.requestSubmit()\n }\n filled?.(event)\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\n/**\n * What a button filling the default slot of an overlay drawn beside it takes\n * on, as src/button's slots.ts sets out: its node is kept for the overlay to\n * be placed against, alongside the call site's own `ref`, `aria-expanded`\n * says whether the overlay is open, and the overlay's hover and long press\n * handlers run after the call site's. A button filling no such slot, or a\n * link, keeps its own props untouched.\n */\nfunction useSlotTrigger({\n delayLongPress,\n href,\n onHoverIn,\n onHoverOut,\n onLongPress,\n ref,\n slot,\n}: {\n readonly delayLongPress: PressableProps['delayLongPress']\n readonly href: string | undefined\n readonly onHoverIn: PressableProps['onHoverIn']\n readonly onHoverOut: PressableProps['onHoverOut']\n readonly onLongPress: PressableProps['onLongPress']\n readonly ref: Ref<HostInstance> | undefined\n readonly slot: 'close' | undefined\n}): SlotTriggerProps {\n const slots = use(ButtonSlotContext)\n const trigger =\n href === undefined && slot === undefined ? slots?.trigger : undefined\n\n return {\n ...(trigger === undefined\n ? { delayLongPress, ref }\n : {\n 'aria-expanded': trigger.expanded,\n delayLongPress: trigger.delayLongPress ?? delayLongPress,\n ref: refsOf(trigger.ref, ref),\n }),\n onHoverIn: andThen(onHoverIn, trigger?.onHoverIn),\n onHoverOut: andThen(onHoverOut, trigger?.onHoverOut),\n onLongPress: andThen(onLongPress, trigger?.onLongPress),\n }\n}\n\nexport {\n AnimatedPressable,\n buttonStyles,\n HEIGHTS,\n OUTLINE_WIDTHS,\n PRESSED_CORNERS,\n toggleState,\n useButtonAction,\n useCorner,\n useSlotTrigger,\n}\nexport type { ButtonLinkForm, ButtonPressForm, ButtonToggleForm, ControlSize }\n"],"mappings":"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"}
|
package/dist/button/pending.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { IconStyleContext } from "../glyphs/icon-style.js";
|
|
2
|
+
import { useMessages } from "../i18n/provider.js";
|
|
2
3
|
import { ProgressIndicator } from "../components/progress-indicator/index.js";
|
|
3
|
-
import { messagesFor } from "../i18n/index.js";
|
|
4
4
|
import { c } from "react/compiler-runtime";
|
|
5
5
|
import { AccessibilityInfo, Platform, StyleSheet, View } from "react-native";
|
|
6
6
|
import { useEffect, useRef } from "react";
|
|
@@ -99,7 +99,8 @@ function slotOf(color, size) {
|
|
|
99
99
|
}
|
|
100
100
|
/**
|
|
101
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
|
|
102
|
+
* The name is `pendingLabel`, or the word for loading in the locale
|
|
103
|
+
* `useLocaleOf` resolves.
|
|
103
104
|
*
|
|
104
105
|
* On iOS and Android the change is said through VoiceOver and TalkBack, as
|
|
105
106
|
* web's is said through React Aria's live announcer, since a ring appearing
|
|
@@ -110,32 +111,27 @@ function slotOf(color, size) {
|
|
|
110
111
|
* has no announcer, so there the ring is read when the button next is.
|
|
111
112
|
*/
|
|
112
113
|
function usePending(isPending, pendingLabel) {
|
|
113
|
-
const $ = c(
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
t0 = pendingLabel ?? messagesFor().loading;
|
|
117
|
-
$[0] = pendingLabel;
|
|
118
|
-
$[1] = t0;
|
|
119
|
-
} else t0 = $[1];
|
|
120
|
-
const label = t0;
|
|
114
|
+
const $ = c(4);
|
|
115
|
+
const messages = useMessages();
|
|
116
|
+
const label = pendingLabel ?? messages.loading;
|
|
121
117
|
const was = useRef(isPending);
|
|
118
|
+
let t0;
|
|
122
119
|
let t1;
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
t1 = () => {
|
|
120
|
+
if ($[0] !== isPending || $[1] !== label) {
|
|
121
|
+
t0 = () => {
|
|
126
122
|
if (isPending && !was.current && (Platform.OS === "ios" || Platform.OS === "android")) AccessibilityInfo.announceForAccessibility(label);
|
|
127
123
|
was.current = isPending;
|
|
128
124
|
};
|
|
129
|
-
|
|
130
|
-
$[
|
|
131
|
-
$[
|
|
132
|
-
$[
|
|
133
|
-
$[
|
|
125
|
+
t1 = [isPending, label];
|
|
126
|
+
$[0] = isPending;
|
|
127
|
+
$[1] = label;
|
|
128
|
+
$[2] = t0;
|
|
129
|
+
$[3] = t1;
|
|
134
130
|
} else {
|
|
135
|
-
|
|
136
|
-
|
|
131
|
+
t0 = $[2];
|
|
132
|
+
t1 = $[3];
|
|
137
133
|
}
|
|
138
|
-
useEffect(
|
|
134
|
+
useEffect(t0, t1);
|
|
139
135
|
return label;
|
|
140
136
|
}
|
|
141
137
|
//#endregion
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"pending.js","names":["ReactNode","ColorValue","useEffect","useRef","AccessibilityInfo","Platform","StyleSheet","View","IconStyle","ProgressIndicator","IconStyleContext","
|
|
1
|
+
{"version":3,"file":"pending.js","names":["ReactNode","ColorValue","useEffect","useRef","AccessibilityInfo","Platform","StyleSheet","View","IconStyle","ProgressIndicator","IconStyleContext","useMessages","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","messages","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 { useMessages } from '../i18n/provider'\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 locale\n * `useLocaleOf` resolves.\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 messages = useMessages()\n const label = pendingLabel ?? messages.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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"}
|
package/dist/calendar/index.js
CHANGED
|
@@ -3,8 +3,8 @@ import { composeStyles } from "../theme/styles.js";
|
|
|
3
3
|
import { hitSlop, usePressFeedback } from "../press/index.js";
|
|
4
4
|
import { useTypeRole } from "../typography/roles.js";
|
|
5
5
|
import { withOpacity } from "../theme/color.js";
|
|
6
|
-
import { ArrowDropDownGlyph, CheckGlyph, ChevronEndGlyph } from "../glyphs/index.js";
|
|
7
6
|
import { messagesFor } from "../i18n/index.js";
|
|
7
|
+
import { ArrowDropDownGlyph, CheckGlyph, ChevronEndGlyph } from "../glyphs/index.js";
|
|
8
8
|
import { IconButton } from "../components/icon-button/index.js";
|
|
9
9
|
import { useFocusOnPlaced, useReturnFocus } from "../overlay/panel.js";
|
|
10
10
|
import { styles, stylesOf } from "./styles.js";
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { AutocompleteContext, containsIn } from "../../autocomplete/index.js";
|
|
2
|
-
import {
|
|
2
|
+
import { useLocaleOf } from "../../i18n/provider.js";
|
|
3
3
|
import { c } from "react/compiler-runtime";
|
|
4
4
|
import { useState } from "react";
|
|
5
5
|
import { jsx } from "react/jsx-runtime";
|
|
@@ -24,52 +24,54 @@ import { jsx } from "react/jsx-runtime";
|
|
|
24
24
|
* screen reader.
|
|
25
25
|
*/
|
|
26
26
|
function Autocomplete(t0) {
|
|
27
|
-
const $ = c(
|
|
27
|
+
const $ = c(15);
|
|
28
28
|
const { children, defaultInputValue: t1, filter, inputValue, onInputChange } = t0;
|
|
29
29
|
const [own, setOwn] = useState(t1 === void 0 ? "" : t1);
|
|
30
30
|
const current = inputValue ?? own;
|
|
31
|
+
const locale = useLocaleOf(void 0);
|
|
31
32
|
let t2;
|
|
32
|
-
if ($[0] !== filter) {
|
|
33
|
-
t2 = filter ?? containsIn(
|
|
33
|
+
if ($[0] !== filter || $[1] !== locale) {
|
|
34
|
+
t2 = filter ?? containsIn(locale);
|
|
34
35
|
$[0] = filter;
|
|
35
|
-
$[1] =
|
|
36
|
-
|
|
36
|
+
$[1] = locale;
|
|
37
|
+
$[2] = t2;
|
|
38
|
+
} else t2 = $[2];
|
|
37
39
|
let t3;
|
|
38
|
-
if ($[
|
|
40
|
+
if ($[3] !== current || $[4] !== t2) {
|
|
39
41
|
t3 = matchesOf(t2, current);
|
|
40
|
-
$[
|
|
41
|
-
$[
|
|
42
|
-
$[
|
|
43
|
-
} else t3 = $[
|
|
42
|
+
$[3] = current;
|
|
43
|
+
$[4] = t2;
|
|
44
|
+
$[5] = t3;
|
|
45
|
+
} else t3 = $[5];
|
|
44
46
|
let t4;
|
|
45
|
-
if ($[
|
|
47
|
+
if ($[6] !== onInputChange) {
|
|
46
48
|
t4 = setterOf(setOwn, onInputChange);
|
|
47
|
-
$[
|
|
48
|
-
$[
|
|
49
|
-
} else t4 = $[
|
|
49
|
+
$[6] = onInputChange;
|
|
50
|
+
$[7] = t4;
|
|
51
|
+
} else t4 = $[7];
|
|
50
52
|
let t5;
|
|
51
|
-
if ($[
|
|
53
|
+
if ($[8] !== current || $[9] !== t3 || $[10] !== t4) {
|
|
52
54
|
t5 = {
|
|
53
55
|
inputValue: current,
|
|
54
56
|
matches: t3,
|
|
55
57
|
setInputValue: t4
|
|
56
58
|
};
|
|
57
|
-
$[
|
|
58
|
-
$[
|
|
59
|
-
$[
|
|
60
|
-
$[
|
|
61
|
-
} else t5 = $[
|
|
59
|
+
$[8] = current;
|
|
60
|
+
$[9] = t3;
|
|
61
|
+
$[10] = t4;
|
|
62
|
+
$[11] = t5;
|
|
63
|
+
} else t5 = $[11];
|
|
62
64
|
const value = t5;
|
|
63
65
|
let t6;
|
|
64
|
-
if ($[
|
|
66
|
+
if ($[12] !== children || $[13] !== value) {
|
|
65
67
|
t6 = /* @__PURE__ */ jsx(AutocompleteContext, {
|
|
66
68
|
value,
|
|
67
69
|
children
|
|
68
70
|
});
|
|
69
|
-
$[
|
|
70
|
-
$[
|
|
71
|
-
$[
|
|
72
|
-
} else t6 = $[
|
|
71
|
+
$[12] = children;
|
|
72
|
+
$[13] = value;
|
|
73
|
+
$[14] = t6;
|
|
74
|
+
} else t6 = $[14];
|
|
73
75
|
return t6;
|
|
74
76
|
}
|
|
75
77
|
function matchesOf(filter, inputValue) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":["ReactNode","Node","useMemo","useState","AutocompleteValue","AutocompleteContext","containsIn","
|
|
1
|
+
{"version":3,"file":"index.js","names":["ReactNode","Node","useMemo","useState","AutocompleteValue","AutocompleteContext","containsIn","useLocaleOf","AutocompleteFilter","textValue","inputValue","node","T","AutocompleteProps","children","defaultInputValue","filter","onInputChange","value","Autocomplete","t0","$","_c","t1","undefined","own","setOwn","current","locale","t2","t3","matchesOf","t4","setterOf","t5","matches","setInputValue","t6","_c"],"sources":["../../../src/components/autocomplete/index.tsx"],"sourcesContent":["import type { ReactNode } from 'react'\nimport type { Node } from 'react-stately'\n\nimport { useMemo, useState } from 'react'\n\nimport type { AutocompleteValue } from '../../autocomplete'\n\nimport { AutocompleteContext, containsIn } from '../../autocomplete'\nimport { useLocaleOf } from '../../i18n/provider'\n\n// kanso-ui's Autocomplete, from the search page's search with suggestions: a\n// SearchField over a List, where typing narrows the rows the list shows. It\n// draws nothing of its own, as web's draws nothing: the bar is SearchField's,\n// the rows are List's, and where the two sit is the page's business. So it\n// takes no `style`, since there is nothing for one to land on.\n//\n// Three things are worth knowing, and the last two differ from web's.\n//\n// **Filtering is on by default,** as on web: `filter` falls back to a match\n// anywhere in a row's text, compared by an `Intl.Collator` in the\n// `I18nProvider`'s locale, or the device's outside one, at base sensitivity, so case and accents are ignored, which is what\n// web's falls back to as well.\n//\n// **There is no virtual focus.** Web keeps the keyboard in the input while\n// the arrow keys move through the list. A touch screen has no arrow keys, so\n// the list's rows are pressed where they are, and a press on one selects it\n// or runs its action, as it does in any List. A list inside an Autocomplete\n// keeps the keyboard up through that press, with `keyboardShouldPersistTaps`\n// at `handled`, so a row is reached on the first tap rather than the one\n// that would otherwise only close the keyboard.\n//\n// **The count is announced.** Web says only that a search left nothing.\n// Here the list says how many rows a search leaves each time the number\n// changes, in React Aria's words for a ComboBox's count, and that it left\n// none in the empty row's: through a polite live region on Android and\n// under React Native Web, and `AccessibilityInfo` on iOS, which has no live\n// regions. The list's empty row is drawn as ever when nothing matches.\n\n/**\n * Whether a search keeps a row: handed the row's text, what has been typed\n * and the row's entry in the collection, as React Aria's is.\n */\ntype AutocompleteFilter<T extends object = object> = (\n textValue: string,\n inputValue: string,\n node: Node<T>,\n) => boolean\n\ntype AutocompleteProps<T extends object = object> = {\n /**\n * The search field and the list it filters, in whatever the page puts\n * around them.\n */\n readonly children?: ReactNode\n /** What has been typed at first, when the autocomplete keeps its own. */\n readonly defaultInputValue?: string\n /**\n * Which rows a search keeps. Left out, a row is kept when its text\n * contains what has been typed anywhere in it, ignoring case and accents.\n */\n readonly filter?: AutocompleteFilter<T>\n /** What has been typed, when the call site holds it. */\n readonly inputValue?: string\n /** Called with what has been typed whenever an edit changes it. */\n readonly onInputChange?: (value: string) => void\n}\n\n/**\n * A search field that filters the list under it. What has been typed is the\n * autocomplete's: pass `inputValue` with `onInputChange` to control it, or\n * `defaultInputValue` to let it keep its own. The field inside shows it, and\n * its own `value` and `defaultValue` are not read there.\n *\n * ```tsx\n * <Autocomplete>\n * <SearchField label=\"Search\" placeholder=\"Search\" />\n * <List aria-label=\"Results\" selectionMode=\"single\">\n * <List.Item id=\"first\">First item</List.Item>\n * </List>\n * </Autocomplete>\n * ```\n *\n * It renders nothing of its own, so the field and the list sit wherever the\n * page puts them, and the list says how many rows each search leaves to a\n * screen reader.\n */\nfunction Autocomplete<T extends object = object>({\n children,\n defaultInputValue = '',\n filter,\n inputValue,\n onInputChange,\n}: AutocompleteProps<T>) {\n const [own, setOwn] = useState(defaultInputValue)\n const current = inputValue ?? own\n const locale = useLocaleOf(undefined)\n const value = useMemo<AutocompleteValue>(\n () => ({\n inputValue: current,\n matches: matchesOf(filter ?? containsIn(locale), current),\n setInputValue: setterOf(setOwn, onInputChange),\n }),\n [current, filter, locale, onInputChange],\n )\n\n return <AutocompleteContext value={value}>{children}</AutocompleteContext>\n}\n\n// The filter with what has been typed already in it, which is all a list\n// asks of a row.\nfunction matchesOf<T extends object>(\n filter: AutocompleteFilter<T>,\n inputValue: string,\n): AutocompleteValue['matches'] {\n return (textValue, node) =>\n // oxlint-disable-next-line typescript/no-unsafe-type-assertion -- the rows are the call site's, which typed the filter it wrote for them\n filter(textValue, inputValue, node as Node<T>)\n}\n\n// Keeps an edit as the autocomplete's own value and reports it, so one the\n// call site controls is told and one it does not still filters.\nfunction setterOf(\n setOwn: (value: string) => void,\n onInputChange: ((value: string) => void) | undefined,\n) {\n return (value: string) => {\n setOwn(value)\n onInputChange?.(value)\n }\n}\n\nexport { Autocomplete }\nexport type { AutocompleteFilter, AutocompleteProps }\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;AAsFA,SAAAmB,aAAAC,IAAA;CAAA,MAAAC,IAAAC,EAAA,EAAA;CAAiD,MAAA,EAAAR,UAAAC,mBAAAQ,IAAAP,QAAAN,YAAAO,kBAAAG;CAO/C,MAAA,CAAAK,KAAAC,UAAsBvB,SALtBoB,OAAAC,KAAAA,IAAA,KAAAD,EAKgD;CAChD,MAAAI,UAAgBjB,cAAAe;CAChB,MAAAG,SAAerB,YAAYiB,KAAAA,CAAS;CAAC,IAAAK;CAAA,IAAAR,EAAA,OAAAL,UAAAK,EAAA,OAAAO,QAAA;EAIdC,KAAAb,UAAUV,WAAWsB,MAAM;EAACP,EAAA,KAAAL;EAAAK,EAAA,KAAAO;EAAAP,EAAA,KAAAQ;CAAA,OAAAA,KAAAR,EAAA;CAAA,IAAAS;CAAA,IAAAT,EAAA,OAAAM,WAAAN,EAAA,OAAAQ,IAAA;EAAtCC,KAAAC,UAAUF,IAA8BF,OAAO;EAACN,EAAA,KAAAM;EAAAN,EAAA,KAAAQ;EAAAR,EAAA,KAAAS;CAAA,OAAAA,KAAAT,EAAA;CAAA,IAAAW;CAAA,IAAAX,EAAA,OAAAJ,eAAA;EAC1Ce,KAAAC,SAASP,QAAQT,aAAa;EAACI,EAAA,KAAAJ;EAAAI,EAAA,KAAAW;CAAA,OAAAA,KAAAX,EAAA;CAAA,IAAAa;CAAA,IAAAb,EAAA,OAAAM,WAAAN,EAAA,OAAAS,MAAAT,EAAA,QAAAW,IAAA;EAHzCE,KAAA;GAAAxB,YACOiB;GAAOQ,SACVL;GAAgDM,eAC1CJ;EACjB;EAACX,EAAA,KAAAM;EAAAN,EAAA,KAAAS;EAAAT,EAAA,MAAAW;EAAAX,EAAA,MAAAa;CAAA,OAAAA,KAAAb,EAAA;CALH,MAAAH,QACSgB;CAMR,IAAAG;CAAA,IAAAhB,EAAA,QAAAP,YAAAO,EAAA,QAAAH,OAAA;EAEMmB,KAAA,oBAAC,qBAAD;GAA4BnB;GAAQJ;EAAhB,CAAA;EAA+CO,EAAA,MAAAP;EAAAO,EAAA,MAAAH;EAAAG,EAAA,MAAAgB;CAAA,OAAAA,KAAAhB,EAAA;CAAA,OAAnEgB;AAAmE;AAK5E,SAASN,UACPf,QACAN,YAC8B;CAC9B,QAAQD,WAAWE,SAEjBK,OAAOP,WAAWC,YAAYC,IAAe;AACjD;AAIA,SAASsB,SACPP,QACAT,eACA;CACA,QAAQC,UAAkB;EACxBQ,OAAOR,KAAK;EACZD,gBAAgBC,KAAK;CACvB;AACF"}
|
|
@@ -73,8 +73,8 @@ type AvatarProps = Omit<ViewProps, 'children' | 'style'> & {
|
|
|
73
73
|
* Reach for ProductIcon when what is drawn is not a person; the two share
|
|
74
74
|
* their sizes, so either fills the same slot in a row or a card.
|
|
75
75
|
*
|
|
76
|
-
* The initials are taken in the
|
|
77
|
-
* land on the disc.
|
|
76
|
+
* The initials are taken in the `I18nProvider`'s locale, or the device's
|
|
77
|
+
* outside one. `style`, `ref` and `testID` land on the disc.
|
|
78
78
|
*/
|
|
79
79
|
declare function Avatar({ size, tone, ...props }: AvatarProps): import("react").JSX.Element;
|
|
80
80
|
//#endregion
|
|
@@ -20,8 +20,8 @@ import { jsx } from "react/jsx-runtime";
|
|
|
20
20
|
* Reach for ProductIcon when what is drawn is not a person; the two share
|
|
21
21
|
* their sizes, so either fills the same slot in a row or a card.
|
|
22
22
|
*
|
|
23
|
-
* The initials are taken in the
|
|
24
|
-
* land on the disc.
|
|
23
|
+
* The initials are taken in the `I18nProvider`'s locale, or the device's
|
|
24
|
+
* outside one. `style`, `ref` and `testID` land on the disc.
|
|
25
25
|
*/
|
|
26
26
|
function Avatar(t0) {
|
|
27
27
|
const $ = c(8);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":["ComponentRef","Ref","StyleProp","View","ViewProps","ViewStyle","MarkImageProps","Mark","AvatarProps","Omit","decorative","imgProps","name","ref","showPersonIcon","size","src","style","tone","Avatar","t0","$","_c","props","t1","t2","undefined","t3","_c"],"sources":["../../../src/components/avatar/index.tsx"],"sourcesContent":["import type { ComponentRef, Ref } from 'react'\nimport type { StyleProp, View, ViewProps, ViewStyle } from 'react-native'\n\nimport type { MarkImageProps } from '../../mark'\n\nimport { Mark } from '../../mark'\n\n// A person, drawn as a disc with the photo cropped to fill it, as kanso-ui's\n// Avatar draws one. The box, the sizes, the tones, the photo's loading and\n// the labelling are the mark's in `src/mark`, which ProductIcon draws a thing\n// through as well, so a person and a thing fill the same slot.\n//\n// Which tone a given person gets is the consuming app's decision, since the\n// cycle of colours belongs where the list of people is.\n\ntype AvatarProps = Omit<ViewProps, 'children' | 'style'> & {\n /**\n * Hides the avatar from screen readers, for one beside the person's name,\n * such as a row whose headline is the name or a byline, where it would\n * otherwise announce the name a second time. `name` still supplies the\n * initials, and is not announced.\n * @default false\n */\n readonly decorative?: boolean\n /**\n * Props for the photo's own `Image`: `crossOrigin` and `referrerPolicy`\n * for where it is served from, and `resizeMethod`, `fadeDuration` and\n * `progressiveRenderingEnabled` for how Android decodes and shows it.\n */\n readonly imgProps?: MarkImageProps\n /**\n * The person this represents. Supplies both the initials and the\n * accessible name, so a screen reader announces who it is rather than\n * reading two letters aloud.\n *\n * A display name, as a reader would see it written: the initials are its\n * first and last words. A title before the name (\"Dr.\"), a generational\n * suffix after it (\"Jr.\", \"III\") and an aside in brackets at its end\n * (\"(she/her)\") are passed over, as is punctuation a word opens with. A\n * name written without spaces between its words, as Japanese and Chinese\n * names are, is one word, and takes one initial.\n */\n readonly name: string\n readonly ref?: Ref<ComponentRef<typeof View>>\n /**\n * Whether an avatar with no initials to take, for a blank `name`, draws\n * Material Design's anonymous person. `false` leaves the tinted disc\n * empty, for a loading skeleton.\n * @default true\n */\n readonly showPersonIcon?: boolean\n /**\n * Diameter: `sm` 36dp, `md` 40dp, `lg` 56dp.\n * @default 'md'\n */\n readonly size?: 'lg' | 'md' | 'sm'\n /**\n * A photo to show in place of the initials. The initials stay until it has\n * loaded, and come back if it fails.\n */\n readonly src?: string\n readonly style?: StyleProp<ViewStyle>\n /**\n * Which container and on-container colour pair to tint with.\n * @default 'primary'\n */\n readonly tone?: 'negative' | 'positive' | 'primary' | 'secondary' | 'tertiary'\n}\n\n/**\n * A person, as a disc holding their photo or, until it loads or when there\n * is none, their initials. `name` is both where the initials come from and\n * what a screen reader announces, so it is required even with a photo.\n *\n * ```tsx\n * <Avatar name=\"Ada Lovelace\" size=\"lg\" src={photo} />\n * ```\n *\n * It is announced as an image named by `name`. When the person's name is\n * already written next to the avatar, mark it `decorative`, so a screen\n * reader reads the name once. A blank name names nothing, so the avatar is\n * then a plain view, which a screen reader passes over rather than reading\n * as an image with no name.\n *\n * Reach for ProductIcon when what is drawn is not a person; the two share\n * their sizes, so either fills the same slot in a row or a card.\n *\n * The initials are taken in the device's
|
|
1
|
+
{"version":3,"file":"index.js","names":["ComponentRef","Ref","StyleProp","View","ViewProps","ViewStyle","MarkImageProps","Mark","AvatarProps","Omit","decorative","imgProps","name","ref","showPersonIcon","size","src","style","tone","Avatar","t0","$","_c","props","t1","t2","undefined","t3","_c"],"sources":["../../../src/components/avatar/index.tsx"],"sourcesContent":["import type { ComponentRef, Ref } from 'react'\nimport type { StyleProp, View, ViewProps, ViewStyle } from 'react-native'\n\nimport type { MarkImageProps } from '../../mark'\n\nimport { Mark } from '../../mark'\n\n// A person, drawn as a disc with the photo cropped to fill it, as kanso-ui's\n// Avatar draws one. The box, the sizes, the tones, the photo's loading and\n// the labelling are the mark's in `src/mark`, which ProductIcon draws a thing\n// through as well, so a person and a thing fill the same slot.\n//\n// Which tone a given person gets is the consuming app's decision, since the\n// cycle of colours belongs where the list of people is.\n\ntype AvatarProps = Omit<ViewProps, 'children' | 'style'> & {\n /**\n * Hides the avatar from screen readers, for one beside the person's name,\n * such as a row whose headline is the name or a byline, where it would\n * otherwise announce the name a second time. `name` still supplies the\n * initials, and is not announced.\n * @default false\n */\n readonly decorative?: boolean\n /**\n * Props for the photo's own `Image`: `crossOrigin` and `referrerPolicy`\n * for where it is served from, and `resizeMethod`, `fadeDuration` and\n * `progressiveRenderingEnabled` for how Android decodes and shows it.\n */\n readonly imgProps?: MarkImageProps\n /**\n * The person this represents. Supplies both the initials and the\n * accessible name, so a screen reader announces who it is rather than\n * reading two letters aloud.\n *\n * A display name, as a reader would see it written: the initials are its\n * first and last words. A title before the name (\"Dr.\"), a generational\n * suffix after it (\"Jr.\", \"III\") and an aside in brackets at its end\n * (\"(she/her)\") are passed over, as is punctuation a word opens with. A\n * name written without spaces between its words, as Japanese and Chinese\n * names are, is one word, and takes one initial.\n */\n readonly name: string\n readonly ref?: Ref<ComponentRef<typeof View>>\n /**\n * Whether an avatar with no initials to take, for a blank `name`, draws\n * Material Design's anonymous person. `false` leaves the tinted disc\n * empty, for a loading skeleton.\n * @default true\n */\n readonly showPersonIcon?: boolean\n /**\n * Diameter: `sm` 36dp, `md` 40dp, `lg` 56dp.\n * @default 'md'\n */\n readonly size?: 'lg' | 'md' | 'sm'\n /**\n * A photo to show in place of the initials. The initials stay until it has\n * loaded, and come back if it fails.\n */\n readonly src?: string\n readonly style?: StyleProp<ViewStyle>\n /**\n * Which container and on-container colour pair to tint with.\n * @default 'primary'\n */\n readonly tone?: 'negative' | 'positive' | 'primary' | 'secondary' | 'tertiary'\n}\n\n/**\n * A person, as a disc holding their photo or, until it loads or when there\n * is none, their initials. `name` is both where the initials come from and\n * what a screen reader announces, so it is required even with a photo.\n *\n * ```tsx\n * <Avatar name=\"Ada Lovelace\" size=\"lg\" src={photo} />\n * ```\n *\n * It is announced as an image named by `name`. When the person's name is\n * already written next to the avatar, mark it `decorative`, so a screen\n * reader reads the name once. A blank name names nothing, so the avatar is\n * then a plain view, which a screen reader passes over rather than reading\n * as an image with no name.\n *\n * Reach for ProductIcon when what is drawn is not a person; the two share\n * their sizes, so either fills the same slot in a row or a card.\n *\n * The initials are taken in the `I18nProvider`'s locale, or the device's\n * outside one. `style`, `ref` and `testID` land on the disc.\n */\nfunction Avatar({ size = 'md', tone = 'primary', ...props }: AvatarProps) {\n return <Mark {...props} kind=\"person\" size={size} tone={tone} />\n}\n\nexport type { AvatarProps }\n\nexport { Avatar }\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;AA0FA,SAAAmB,OAAAC,IAAA;CAAA,MAAAC,IAAAC,EAAA,CAAA;CAAA,IAAAC;CAAA,IAAAC;CAAA,IAAAC;CAAA,IAAAJ,EAAA,OAAAD,IAAA;EAAgB,CAAA,CAAAL,MAAAS,IAAAN,MAAAO,IAAA,GAAAF,SAAAH;EAAwDC,EAAA,KAAAD;EAAAC,EAAA,KAAAE;EAAAF,EAAA,KAAAG;EAAAH,EAAA,KAAAI;CAAA,OAAA;EAAAF,QAAAF,EAAA;EAAAG,KAAAH,EAAA;EAAAI,KAAAJ,EAAA;CAAA;CAAtD,MAAAN,OAAAS,OAAAE,KAAAA,IAAA,OAAAF;CAAa,MAAAN,OAAAO,OAAAC,KAAAA,IAAA,YAAAD;CAAgB,IAAAE;CAAA,IAAAN,EAAA,OAAAE,SAAAF,EAAA,OAAAN,QAAAM,EAAA,OAAAH,MAAA;EACtCS,KAAA,oBAAC,MAAD;GAAK,GAAKJ;GAAY,MAAA;GAAeR;GAAYG;EAAI,CAAA;EAAIG,EAAA,KAAAE;EAAAF,EAAA,KAAAN;EAAAM,EAAA,KAAAH;EAAAG,EAAA,KAAAM;CAAA,OAAAA,KAAAN,EAAA;CAAA,OAAzDM;AAAyD"}
|
|
@@ -11,7 +11,7 @@ type CalendarProps$1<T extends DateValue = DateValue> = Pick<CalendarProps<T>, '
|
|
|
11
11
|
/**
|
|
12
12
|
* The BCP 47 locale the calendar is written in: its month and weekday
|
|
13
13
|
* names, its digits, its first day of the week and its calendar system.
|
|
14
|
-
* Defaults to the device's.
|
|
14
|
+
* Defaults to the `I18nProvider`'s, or the device's outside one.
|
|
15
15
|
*/
|
|
16
16
|
readonly locale?: string;
|
|
17
17
|
readonly ref?: Ref<ComponentRef<typeof View>>;
|