@kanso-labs/react-native-kanso-ui 0.4.0 → 0.6.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 (109) hide show
  1. package/README.md +4 -1
  2. package/dist/autocomplete/index.js +39 -0
  3. package/dist/autocomplete/index.js.map +1 -0
  4. package/dist/badge/index.js +74 -0
  5. package/dist/badge/index.js.map +1 -0
  6. package/dist/badge/styles.js +51 -0
  7. package/dist/badge/styles.js.map +1 -0
  8. package/dist/button/index.d.ts +2 -1
  9. package/dist/button/index.js +35 -2
  10. package/dist/button/index.js.map +1 -1
  11. package/dist/button/slots.d.ts +2 -1
  12. package/dist/button/slots.js.map +1 -1
  13. package/dist/chip/index.js +2 -2
  14. package/dist/components/app-bar/index.d.ts +111 -0
  15. package/dist/components/app-bar/index.js +341 -0
  16. package/dist/components/app-bar/index.js.map +1 -0
  17. package/dist/components/autocomplete/index.d.ts +49 -0
  18. package/dist/components/autocomplete/index.js +87 -0
  19. package/dist/components/autocomplete/index.js.map +1 -0
  20. package/dist/components/button/index.d.ts +1 -1
  21. package/dist/components/button/index.js +197 -151
  22. package/dist/components/button/index.js.map +1 -1
  23. package/dist/components/card/index.js +2 -2
  24. package/dist/components/checkbox/index.js +106 -193
  25. package/dist/components/checkbox/index.js.map +1 -1
  26. package/dist/components/chip/index.js +1 -1
  27. package/dist/components/chip-group/index.js +3 -3
  28. package/dist/components/dialog/index.js +4 -3
  29. package/dist/components/dialog/index.js.map +1 -1
  30. package/dist/components/icon-button/index.d.ts +1 -1
  31. package/dist/components/icon-button/index.js +206 -160
  32. package/dist/components/icon-button/index.js.map +1 -1
  33. package/dist/components/link/index.js +2 -2
  34. package/dist/components/list/index.d.ts +216 -0
  35. package/dist/components/list/index.js +1118 -0
  36. package/dist/components/list/index.js.map +1 -0
  37. package/dist/components/list-detail/index.d.ts +56 -0
  38. package/dist/components/list-detail/index.js +169 -0
  39. package/dist/components/list-detail/index.js.map +1 -0
  40. package/dist/components/list-item/index.d.ts +63 -0
  41. package/dist/components/list-item/index.js +275 -0
  42. package/dist/components/list-item/index.js.map +1 -0
  43. package/dist/components/menu/index.d.ts +309 -0
  44. package/dist/components/menu/index.js +1283 -0
  45. package/dist/components/menu/index.js.map +1 -0
  46. package/dist/components/navigation-bar/index.d.ts +170 -0
  47. package/dist/components/navigation-bar/index.js +494 -0
  48. package/dist/components/navigation-bar/index.js.map +1 -0
  49. package/dist/components/number-field/index.js +3 -3
  50. package/dist/components/popover/index.d.ts +140 -0
  51. package/dist/components/popover/index.js +704 -0
  52. package/dist/components/popover/index.js.map +1 -0
  53. package/dist/components/progress-indicator/index.js +3 -3
  54. package/dist/components/radio-group/index.js +2 -2
  55. package/dist/components/search-field/index.d.ts +11 -3
  56. package/dist/components/search-field/index.js +66 -60
  57. package/dist/components/search-field/index.js.map +1 -1
  58. package/dist/components/segmented-button/index.js +4 -4
  59. package/dist/components/sheet/index.js +5 -4
  60. package/dist/components/sheet/index.js.map +1 -1
  61. package/dist/components/snackbar/index.js +4 -4
  62. package/dist/components/switch/index.js +3 -3
  63. package/dist/control/checkbox-box.js +137 -0
  64. package/dist/control/checkbox-box.js.map +1 -0
  65. package/dist/field/index.d.ts +1 -1
  66. package/dist/field/index.js +3 -3
  67. package/dist/form/index.d.ts +1 -1
  68. package/dist/glyphs/index.js +22 -3
  69. package/dist/glyphs/index.js.map +1 -1
  70. package/dist/hooks/useAppBarScroll.d.ts +56 -0
  71. package/dist/hooks/useAppBarScroll.js +87 -0
  72. package/dist/hooks/useAppBarScroll.js.map +1 -0
  73. package/dist/i18n/index.js +16 -1
  74. package/dist/i18n/index.js.map +1 -1
  75. package/dist/i18n/messages.js +272 -34
  76. package/dist/i18n/messages.js.map +1 -1
  77. package/dist/index.d.ts +12 -3
  78. package/dist/index.js +12 -3
  79. package/dist/indicator/index.d.ts +2 -2
  80. package/dist/indicator/index.js +1 -1
  81. package/dist/indicator/motion.js +1 -1
  82. package/dist/mark/index.js +1 -1
  83. package/dist/modal/index.d.ts +1 -1
  84. package/dist/modal/index.js +2 -9
  85. package/dist/modal/index.js.map +1 -1
  86. package/dist/navigation/index.d.ts +29 -0
  87. package/dist/navigation/index.js +405 -0
  88. package/dist/navigation/index.js.map +1 -0
  89. package/dist/navigation/styles.js +61 -0
  90. package/dist/navigation/styles.js.map +1 -0
  91. package/dist/overlay/anchored.d.ts +2 -0
  92. package/dist/overlay/anchored.js +182 -0
  93. package/dist/overlay/anchored.js.map +1 -0
  94. package/dist/overlay/host.d.ts +2 -0
  95. package/dist/overlay/index.d.ts +4 -0
  96. package/dist/overlay/panel.js +215 -0
  97. package/dist/overlay/panel.js.map +1 -0
  98. package/dist/overlay/placement.d.ts +8 -0
  99. package/dist/overlay/placement.js +87 -0
  100. package/dist/overlay/placement.js.map +1 -0
  101. package/dist/press/index.js +74 -68
  102. package/dist/press/index.js.map +1 -1
  103. package/dist/ref/index.js +15 -0
  104. package/dist/ref/index.js.map +1 -0
  105. package/dist/row/index.js +158 -0
  106. package/dist/row/index.js.map +1 -0
  107. package/dist/row/styles.js +51 -0
  108. package/dist/row/styles.js.map +1 -0
  109. package/package.json +3 -3
package/README.md CHANGED
@@ -94,7 +94,10 @@ same.
94
94
 
95
95
  The insets come from safe-area-context's `SafeAreaProvider`, which Expo Router
96
96
  puts around an app already; elsewhere, wrap the app in one. Without it, the
97
- library keeps to the window's edge.
97
+ library keeps to the window's edge. `AppBar` and the panes of a `ListDetail` are
98
+ the exception: each measures its own insets through safe-area-context's
99
+ `SafeAreaView`, so it clears only the edges it actually meets, and on a device
100
+ it needs no provider. Under React Native Web that view reads the provider too.
98
101
 
99
102
  Gesture Handler 2 and 3 both satisfy the peer. A `Switch` draws Gesture
100
103
  Handler's root view around its own track, so an app need not wrap itself in
@@ -0,0 +1,39 @@
1
+ import { createContext } from "react";
2
+ //#region src/autocomplete/index.ts
3
+ const AutocompleteContext = createContext(null);
4
+ const collators = /* @__PURE__ */ new Map();
5
+ function collatorIn(locale) {
6
+ const made = collators.get(locale);
7
+ if (made !== void 0) return made;
8
+ const collator = new Intl.Collator(locale, {
9
+ sensitivity: "base",
10
+ usage: "search"
11
+ });
12
+ collators.set(locale, collator);
13
+ return collator;
14
+ }
15
+ /**
16
+ * A match anywhere in a row's text, as React Aria's `useFilter` makes it:
17
+ * each slice of the text as long as the search is compared with it by an
18
+ * `Intl.Collator` at base sensitivity, which tells letters apart and nothing
19
+ * else, so case and accents are ignored. Both are normalised first, so an
20
+ * accent typed as a combining mark matches one written as a single letter.
21
+ * An empty search matches every row.
22
+ */
23
+ function containsIn(locale) {
24
+ const collator = collatorIn(locale);
25
+ return (textValue, inputValue) => {
26
+ if (inputValue.length === 0) return true;
27
+ const text = textValue.normalize("NFC");
28
+ const search = inputValue.normalize("NFC");
29
+ for (let start = 0; start + search.length <= text.length; start++) {
30
+ const slice = text.slice(start, start + search.length);
31
+ if (collator.compare(search, slice) === 0) return true;
32
+ }
33
+ return false;
34
+ };
35
+ }
36
+ //#endregion
37
+ export { AutocompleteContext, containsIn };
38
+
39
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","names":["Node","createContext","AutocompleteValue","inputValue","matches","textValue","node","setInputValue","value","AutocompleteContext","collators","Map","Intl","Collator","collatorIn","locale","made","get","undefined","collator","sensitivity","usage","set","containsIn","length","text","normalize","search","start","slice","compare"],"sources":["../../src/autocomplete/index.ts"],"sourcesContent":["import type { Node } from 'react-stately'\n\nimport { createContext } from 'react'\n\n// What Autocomplete hands the search field and the list inside it. The field\n// shows `inputValue` and reports each edit through `setInputValue`; the list\n// keeps the rows `matches` keeps, which is the autocomplete's filter with\n// what has been typed already in it. Neither part takes it as a prop, so a\n// field and a list written out inside an Autocomplete are wired to it\n// wherever the page puts them.\n\ntype AutocompleteValue = {\n /** What has been typed into the search field. */\n readonly inputValue: string\n /** Whether the list keeps a row, given its text and its entry. */\n readonly matches: (textValue: string, node: Node<unknown>) => boolean\n /** Called by the search field with each edit. */\n readonly setInputValue: (value: string) => void\n}\n\nconst AutocompleteContext = createContext<AutocompleteValue | null>(null)\n\n// One collator per locale an app searches in, made the first time a search\n// asks for it.\nconst collators = new Map<string, Intl.Collator>()\n\n// The collator a search in `locale` compares with, made once per locale.\nfunction collatorIn(locale: string) {\n const made = collators.get(locale)\n\n if (made !== undefined) {\n return made\n }\n\n const collator = new Intl.Collator(locale, {\n sensitivity: 'base',\n usage: 'search',\n })\n collators.set(locale, collator)\n return collator\n}\n\n/**\n * A match anywhere in a row's text, as React Aria's `useFilter` makes it:\n * each slice of the text as long as the search is compared with it by an\n * `Intl.Collator` at base sensitivity, which tells letters apart and nothing\n * else, so case and accents are ignored. Both are normalised first, so an\n * accent typed as a combining mark matches one written as a single letter.\n * An empty search matches every row.\n */\nfunction containsIn(locale: string) {\n const collator = collatorIn(locale)\n\n return (textValue: string, inputValue: string) => {\n if (inputValue.length === 0) {\n return true\n }\n\n const text = textValue.normalize('NFC')\n const search = inputValue.normalize('NFC')\n\n for (let start = 0; start + search.length <= text.length; start++) {\n const slice = text.slice(start, start + search.length)\n\n if (collator.compare(search, slice) === 0) {\n return true\n }\n }\n\n return false\n }\n}\n\nexport { AutocompleteContext, containsIn }\nexport type { AutocompleteValue }\n"],"mappings":";;AAoBA,MAAMS,sBAAsBR,cAAwC,IAAI;AAIxE,MAAMS,4BAAY,IAAIC,IAA2B;AAGjD,SAASG,WAAWC,QAAgB;CAClC,MAAMC,OAAON,UAAUO,IAAIF,MAAM;CAEjC,IAAIC,SAASE,KAAAA,GACX,OAAOF;CAGT,MAAMG,WAAW,IAAIP,KAAKC,SAASE,QAAQ;EACzCK,aAAa;EACbC,OAAO;CACT,CAAC;CACDX,UAAUY,IAAIP,QAAQI,QAAQ;CAC9B,OAAOA;AACT;;;;;;;;;AAUA,SAASI,WAAWR,QAAgB;CAClC,MAAMI,WAAWL,WAAWC,MAAM;CAElC,QAAQV,WAAmBF,eAAuB;EAChD,IAAIA,WAAWqB,WAAW,GACxB,OAAO;EAGT,MAAMC,OAAOpB,UAAUqB,UAAU,KAAK;EACtC,MAAMC,SAASxB,WAAWuB,UAAU,KAAK;EAEzC,KAAK,IAAIE,QAAQ,GAAGA,QAAQD,OAAOH,UAAUC,KAAKD,QAAQI,SAAS;GACjE,MAAMC,QAAQJ,KAAKI,MAAMD,OAAOA,QAAQD,OAAOH,MAAM;GAErD,IAAIL,SAASW,QAAQH,QAAQE,KAAK,MAAM,GACtC,OAAO;EAEX;EAEA,OAAO;CACT;AACF"}
@@ -0,0 +1,74 @@
1
+ import { useTheme } from "../theme/scope.js";
2
+ import { composeStyles } from "../theme/styles.js";
3
+ import { useTypeRole } from "../typography/roles.js";
4
+ import { styles, stylesOf } from "./styles.js";
5
+ import { c } from "react/compiler-runtime";
6
+ import { Text, View } from "react-native";
7
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
8
+ //#region src/badge/index.tsx
9
+ let countFormat;
10
+ /**
11
+ * A badge on the top trailing corner of the icon it wraps: the small dot, or
12
+ * with `count` the large badge holding the number.
13
+ */
14
+ function Badge(t0) {
15
+ const $ = c(12);
16
+ const { children, count, inFlow: t1, max: t2 } = t0;
17
+ const inFlow = t1 === void 0 ? false : t1;
18
+ const max = t2 === void 0 ? 999 : t2;
19
+ const own = stylesOf(useTheme());
20
+ const role = useTypeRole("labelSmall");
21
+ let t3;
22
+ if ($[0] !== count || $[1] !== max) {
23
+ t3 = count === void 0 ? void 0 : countOf(count, max);
24
+ $[0] = count;
25
+ $[1] = max;
26
+ $[2] = t3;
27
+ } else t3 = $[2];
28
+ const label = t3;
29
+ const T0 = View;
30
+ const t4 = styles;
31
+ const T1 = View;
32
+ const t5 = true;
33
+ const t6 = composeStyles(own.mark, label === void 0 ? styles.small : [own.large, inFlow ? styles.inFlow : styles.over]);
34
+ const t7 = label === void 0 ? null : /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(Text, {
35
+ style: styles.space,
36
+ children: " "
37
+ }), /* @__PURE__ */ jsx(Text, {
38
+ style: composeStyles(role, own.count),
39
+ children: label
40
+ })] });
41
+ let t8;
42
+ if ($[3] !== T1 || $[4] !== t6 || $[5] !== t7) {
43
+ t8 = /* @__PURE__ */ jsx(T1, {
44
+ "aria-hidden": t5,
45
+ style: t6,
46
+ children: t7
47
+ });
48
+ $[3] = T1;
49
+ $[4] = t6;
50
+ $[5] = t7;
51
+ $[6] = t8;
52
+ } else t8 = $[6];
53
+ let t9;
54
+ if ($[7] !== T0 || $[8] !== children || $[9] !== t4.anchor || $[10] !== t8) {
55
+ t9 = /* @__PURE__ */ jsxs(T0, {
56
+ style: t4.anchor,
57
+ children: [children, t8]
58
+ });
59
+ $[7] = T0;
60
+ $[8] = children;
61
+ $[9] = t4.anchor;
62
+ $[10] = t8;
63
+ $[11] = t9;
64
+ } else t9 = $[11];
65
+ return t9;
66
+ }
67
+ function countOf(count, max) {
68
+ countFormat ??= new Intl.NumberFormat();
69
+ return count > max ? `${countFormat.format(max)}+` : countFormat.format(count);
70
+ }
71
+ //#endregion
72
+ export { Badge };
73
+
74
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","names":["ReactNode","TextStyle","ViewStyle","Text","NativeText","View","useTheme","composeStyles","useTypeRole","styles","stylesOf","BadgeProps","children","count","inFlow","max","countFormat","Intl","NumberFormat","Badge","t0","$","_c","t1","t2","undefined","own","role","t3","countOf","label","T0","T1","t5","t6","mark","small","large","over","t7","space","t8","t9","t4","anchor","format","_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 { 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\nlet countFormat: Intl.NumberFormat | undefined\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 label = count === undefined ? undefined : countOf(count, max)\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 device's digits and grouping, as web writes it in the\n// reader's locale. One formatter, since a badge's count changes often.\nfunction countOf(count: number, max: number) {\n countFormat ??= new Intl.NumberFormat()\n\n return count > max ? `${countFormat.format(max)}+` : countFormat.format(count)\n}\n\nexport type { BadgeProps }\n\nexport { Badge }\n"],"mappings":";;;;;;;;AA+DA,IAAIgB;;;;;AAMJ,SAAAG,MAAAC,IAAA;CAAA,MAAAC,IAAAC,EAAA,EAAA;CAAe,MAAA,EAAAV,UAAAC,OAAAC,QAAAS,IAAAR,KAAAS,OAAAJ;CAAmB,MAAAN,SAAAS,OAAAE,KAAAA,IAAA,QAAAF;CAAgB,MAAAR,MAAAS,OAAAC,KAAAA,IAAA,MAAAD;CAChD,MAAAE,MAAYhB,SAASJ,SAAS,CAAC;CAC/B,MAAAqB,OAAanB,YAAY,YAAY;CAAC,IAAAoB;CAAA,IAAAP,EAAA,OAAAR,SAAAQ,EAAA,OAAAN,KAAA;EACxBa,KAAAf,UAAUY,KAAAA,IAAVA,KAAAA,IAAkCI,QAAQhB,OAAOE,GAAG;EAACM,EAAA,KAAAR;EAAAQ,EAAA,KAAAN;EAAAM,EAAA,KAAAO;CAAA,OAAAA,KAAAP,EAAA;CAAnE,MAAAS,QAAcF;CAGX,MAAAG,KAAA1B;CAAYI,MAAAA,KAAAA;CAEV,MAAAuB,KAAA3B;CACC,MAAA4B,KAAA;CACO,MAAAC,KAAA3B,cACLmB,IAAGS,MACHL,UAAUL,KAAAA,IACNhB,OAAM2B,QADV,CAEKV,IAAGW,OAAQvB,SAASL,OAAMK,SAAUL,OAAM6B,IAAK,CACtD;CAEC,MAAAC,KAAAT,UAAUL,KAAAA,IAAV,OAAA,qBAAA,UAAA,EAAA,UAAA,CAEG,oBAACsB,MAAD;EAAmB,OAAAtC,OAAM+B;EAAQ,UAAA;CAAtB,CAAA,GACX,oBAACO,MAAD;EAAmB,OAAAxC,cAAyBoB,MAAMD,IAAGb,KAAM;EACxDiB,UAAAA;CADQ,CAAA,CAEE,EAAA,CAAA;CAEhB,IAAAW;CAAA,IAAApB,EAAA,OAAAW,MAAAX,EAAA,OAAAa,MAAAb,EAAA,OAAAkB,IAAA;EAhBHE,KAAA,oBAAC,IAAD;GACE,eAAAR;GACO,OAAAC;GAONK,UAAAA;EATE,CAAA;EAiBElB,EAAA,KAAAW;EAAAX,EAAA,KAAAa;EAAAb,EAAA,KAAAkB;EAAAlB,EAAA,KAAAoB;CAAA,OAAAA,KAAApB,EAAA;CAAA,IAAAqB;CAAA,IAAArB,EAAA,OAAAU,MAAAV,EAAA,OAAAT,YAAAS,EAAA,OAAAsB,GAAAC,UAAAvB,EAAA,QAAAoB,IAAA;EAnBTC,KAAA,qBAAC,IAAD;GAAa,OAAAjC,GAAMmC;GAAnB,UAAA,CACGhC,UACD6B,EAFG;;EAoBEpB,EAAA,KAAAU;EAAAV,EAAA,KAAAT;EAAAS,EAAA,KAAAsB,GAAAC;EAAAvB,EAAA,MAAAoB;EAAApB,EAAA,MAAAqB;CAAA,OAAAA,KAAArB,EAAA;CAAA,OApBPqB;AAoBO;AAMX,SAASb,QAAQhB,OAAeE,KAAa;CAC3CC,gBAAgB,IAAIC,KAAKC,aAAa;CAEtC,OAAOL,QAAQE,MAAM,GAAGC,YAAY6B,OAAO9B,GAAG,EAAC,KAAMC,YAAY6B,OAAOhC,KAAK;AAC/E"}
@@ -0,0 +1,51 @@
1
+ import { themedStyles } from "../theme/styles.js";
2
+ import { StyleSheet } from "react-native";
3
+ //#region src/badge/styles.ts
4
+ const SMALL_SIZE = 6;
5
+ const LARGE_SIZE = 16;
6
+ const styles = StyleSheet.create({
7
+ anchor: {
8
+ alignItems: "flex-start",
9
+ flexDirection: "row"
10
+ },
11
+ inFlow: {
12
+ marginStart: -12,
13
+ position: "relative",
14
+ top: -2
15
+ },
16
+ over: {
17
+ position: "absolute",
18
+ start: 12,
19
+ top: -2
20
+ },
21
+ small: {
22
+ end: 0,
23
+ height: SMALL_SIZE,
24
+ position: "absolute",
25
+ top: 0,
26
+ width: SMALL_SIZE
27
+ },
28
+ space: { position: "absolute" }
29
+ });
30
+ const stylesOf = themedStyles(({ colors, radii, spacing }) => StyleSheet.create({
31
+ count: {
32
+ color: colors.onError,
33
+ fontVariant: ["tabular-nums"]
34
+ },
35
+ large: {
36
+ alignItems: "center",
37
+ justifyContent: "center",
38
+ minHeight: LARGE_SIZE,
39
+ minWidth: LARGE_SIZE,
40
+ paddingHorizontal: spacing.xs
41
+ },
42
+ mark: {
43
+ backgroundColor: colors.error,
44
+ borderRadius: radii.pill,
45
+ pointerEvents: "none"
46
+ }
47
+ }));
48
+ //#endregion
49
+ export { styles, stylesOf };
50
+
51
+ //# sourceMappingURL=styles.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"styles.js","names":["StyleSheet","themedStyles","ICON_SIZE","OVERLAP","RISE","SMALL_SIZE","LARGE_SIZE","styles","create","anchor","alignItems","flexDirection","inFlow","marginStart","position","top","over","start","small","end","height","width","space","stylesOf","colors","radii","spacing","count","color","onError","fontVariant","large","justifyContent","minHeight","minWidth","paddingHorizontal","xs","mark","backgroundColor","error","borderRadius","pill","pointerEvents"],"sources":["../../src/badge/styles.ts"],"sourcesContent":["import { StyleSheet } from 'react-native'\n\nimport { themedStyles } from '../theme/styles'\n\n// The icon a badge is drawn on, which the large badge's corner is measured\n// from: the 24dp every destination draws.\nconst ICON_SIZE = 24\n\n// How far back over the icon the large badge reaches, from the icon's\n// trailing edge, and how far above the icon it stands. The page's own\n// figures: its bottom leading corner 14dp down and 12dp in from the icon's\n// top trailing corner, on a 16dp badge.\nconst OVERLAP = 12\nconst RISE = 2\n\n// The small badge's side, and the large badge's least height and width.\nconst SMALL_SIZE = 6\nconst LARGE_SIZE = 16\n\nconst styles = StyleSheet.create({\n // What the badge is drawn on. It shrinks to what it holds, so its box is\n // the icon's and the mark lands where the page puts it, and lays a mark\n // that joins the flow after the icon.\n anchor: {\n alignItems: 'flex-start',\n flexDirection: 'row',\n },\n // The large badge in the flow after the icon, 12dp back over it and 2dp\n // above it, which is where its own placement puts it, so what follows the\n // icon starts after the badge rather than under it.\n inFlow: {\n marginStart: -OVERLAP,\n position: 'relative',\n top: -RISE,\n },\n // The large badge over the icon's top trailing corner.\n over: {\n position: 'absolute',\n start: ICON_SIZE - OVERLAP,\n top: -RISE,\n },\n // The small badge, filling the icon's top trailing 6dp square.\n small: {\n end: 0,\n height: SMALL_SIZE,\n position: 'absolute',\n top: 0,\n width: SMALL_SIZE,\n },\n // The space before the count, out of the flow, so it sets the count apart\n // from the text before it without moving anything.\n space: {\n position: 'absolute',\n },\n})\n\nconst stylesOf = themedStyles(({ colors, radii, spacing }) =>\n StyleSheet.create({\n // The count, in label small with tabular figures, so a count that\n // changes does not change the badge's width digit by digit.\n count: {\n color: colors.onError,\n fontVariant: ['tabular-nums'],\n },\n // The page's large badge: at least 16dp tall and as wide, 4dp either\n // side of its count. A minimum rather than a size, so a larger text size\n // the device asks for grows the badge rather than cutting the count.\n large: {\n alignItems: 'center',\n justifyContent: 'center',\n minHeight: LARGE_SIZE,\n minWidth: LARGE_SIZE,\n paddingHorizontal: spacing.xs,\n },\n // The fill and corner both badges share. It takes no touches, so a press\n // on it lands on what it is drawn on.\n mark: {\n backgroundColor: colors.error,\n borderRadius: radii.pill,\n pointerEvents: 'none',\n },\n }),\n)\n\nexport { styles, stylesOf }\n"],"mappings":";;;AAgBA,MAAMK,aAAa;AACnB,MAAMC,aAAa;AAEnB,MAAMC,SAASP,WAAWQ,OAAO;CAI/BC,QAAQ;EACNC,YAAY;EACZC,eAAe;CACjB;CAIAC,QAAQ;EACNC,aAAa;EACbC,UAAU;EACVC,KAAK;CACP;CAEAC,MAAM;EACJF,UAAU;EACVG,OAAOf;EACPa,KAAK;CACP;CAEAG,OAAO;EACLC,KAAK;EACLC,QAAQf;EACRS,UAAU;EACVC,KAAK;EACLM,OAAOhB;CACT;CAGAiB,OAAO,EACLR,UAAU,WACZ;AACF,CAAC;AAED,MAAMS,WAAWtB,cAAc,EAAEuB,QAAQC,OAAOC,cAC9C1B,WAAWQ,OAAO;CAGhBmB,OAAO;EACLC,OAAOJ,OAAOK;EACdC,aAAa,CAAC,cAAc;CAC9B;CAIAC,OAAO;EACLrB,YAAY;EACZsB,gBAAgB;EAChBC,WAAW3B;EACX4B,UAAU5B;EACV6B,mBAAmBT,QAAQU;CAC7B;CAGAC,MAAM;EACJC,iBAAiBd,OAAOe;EACxBC,cAAcf,MAAMgB;EACpBC,eAAe;CACjB;AACF,CAAC,CACH"}
@@ -1,4 +1,5 @@
1
- import { PressableProps } from "react-native";
1
+ import { HostInstance, LayoutChangeEvent, PressableProps } from "react-native";
2
+ import { Ref } from "react";
2
3
  //#region src/button/index.d.ts
3
4
  /**
4
5
  * A plain button, a link, or a toggle, of which a call site writes exactly
@@ -1,11 +1,12 @@
1
1
  import { useTheme } from "../theme/scope.js";
2
2
  import { FormContext } from "../form/index.js";
3
+ import { refsOf } from "../ref/index.js";
3
4
  import { useOpenLink } from "../router/index.js";
4
5
  import { ButtonSlotContext } from "./slots.js";
5
6
  import { c } from "react/compiler-runtime";
6
7
  import { Platform, Pressable, StyleSheet } from "react-native";
7
- import { use, useState } from "react";
8
8
  import Animated, { Easing, useAnimatedStyle, useReducedMotion, useSharedValue, withTiming } from "react-native-reanimated";
9
+ import { use, useState } from "react";
9
10
  //#region src/button/index.ts
10
11
  const HEIGHTS = {
11
12
  lg: "controlLg",
@@ -42,6 +43,13 @@ const buttonStyles = StyleSheet.create({ clip: {
42
43
  right: 0,
43
44
  top: 0
44
45
  } });
46
+ function andThen(own, slot) {
47
+ if (slot === void 0) return own ?? void 0;
48
+ return (event) => {
49
+ own?.(event);
50
+ slot();
51
+ };
52
+ }
45
53
  function measureThen(height, onLayout) {
46
54
  return (event) => {
47
55
  height.set(event.nativeEvent.layout.height);
@@ -175,7 +183,32 @@ function useCorner(radius, onLayout) {
175
183
  } else t4 = $[10];
176
184
  return t4;
177
185
  }
186
+ /**
187
+ * What a button filling the default slot of an overlay drawn beside it takes
188
+ * on, as src/button's slots.ts sets out: its node is kept for the overlay to
189
+ * be placed against, alongside the call site's own `ref`, `aria-expanded`
190
+ * says whether the overlay is open, and the overlay's hover and long press
191
+ * handlers run after the call site's. A button filling no such slot, or a
192
+ * link, keeps its own props untouched.
193
+ */
194
+ function useSlotTrigger({ delayLongPress, href, onHoverIn, onHoverOut, onLongPress, ref, slot }) {
195
+ const slots = use(ButtonSlotContext);
196
+ const trigger = href === void 0 && slot === void 0 ? slots?.trigger : void 0;
197
+ return {
198
+ ...trigger === void 0 ? {
199
+ delayLongPress,
200
+ ref
201
+ } : {
202
+ "aria-expanded": trigger.expanded,
203
+ delayLongPress: trigger.delayLongPress ?? delayLongPress,
204
+ ref: refsOf(trigger.ref, ref)
205
+ },
206
+ onHoverIn: andThen(onHoverIn, trigger?.onHoverIn),
207
+ onHoverOut: andThen(onHoverOut, trigger?.onHoverOut),
208
+ onLongPress: andThen(onLongPress, trigger?.onLongPress)
209
+ };
210
+ }
178
211
  //#endregion
179
- export { AnimatedPressable, HEIGHTS, OUTLINE_WIDTHS, PRESSED_CORNERS, buttonStyles, toggleState, useButtonAction, useCorner };
212
+ export { AnimatedPressable, HEIGHTS, OUTLINE_WIDTHS, PRESSED_CORNERS, buttonStyles, toggleState, useButtonAction, useCorner, useSlotTrigger };
180
213
 
181
214
  //# sourceMappingURL=index.js.map
@@ -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","ButtonSlotContext","ButtonLinkForm","defaultSelected","href","isPending","isSelected","onChange","pendingLabel","slot","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","slots","ownSelected","setOwnSelected","filled","undefined","t1","follow","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'\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. */\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(\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\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":["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 +1,2 @@
1
- import "react-native";
1
+ import { HostInstance } from "react-native";
2
+ import { RefObject } from "react";
@@ -1 +1 @@
1
- {"version":3,"file":"slots.js","names":["GestureResponderEvent","createContext","ButtonSlots","close","event","default","ButtonSlotContext"],"sources":["../../src/button/slots.ts"],"sourcesContent":["import type { GestureResponderEvent } from 'react-native'\n\nimport { createContext } from 'react'\n\n/**\n * What a button does past its own press when it sits in an overlay's trigger\n * or content, as web's React Aria hands the same through a button's `slot`:\n * the default slot opens the overlay, which a button inside `Dialog` or\n * `Sheet` and outside its content takes, and `close` closes it, which one\n * given `slot=\"close\"` inside the content takes. The overlay provides the\n * slots it fills, and resets the default one inside its content, so a button\n * there opens nothing.\n */\ntype ButtonSlots = {\n readonly close?: (event: GestureResponderEvent) => void\n readonly default?: (event: GestureResponderEvent) => void\n}\n\nconst ButtonSlotContext = createContext<ButtonSlots | null>(null)\n\nexport { ButtonSlotContext }\nexport type { ButtonSlots }\n"],"mappings":";;AAkBA,MAAMM,oBAAoBL,cAAkC,IAAI"}
1
+ {"version":3,"file":"slots.js","names":["RefObject","GestureResponderEvent","HostInstance","createContext","ButtonSlots","close","event","default","trigger","ButtonSlotTrigger","delayLongPress","expanded","onHoverIn","onHoverOut","onLongPress","ref","ButtonSlotContext"],"sources":["../../src/button/slots.ts"],"sourcesContent":["import type { RefObject } from 'react'\nimport type { GestureResponderEvent, HostInstance } from 'react-native'\n\nimport { createContext } from 'react'\n\n/**\n * What a button does past its own press when it sits in an overlay's trigger\n * or content, as web's React Aria hands the same through a button's `slot`:\n * the default slot opens the overlay, which a button inside `Dialog`,\n * `Popover` or `Sheet` and outside its content takes, and `close` closes it,\n * which one given `slot=\"close\"` inside the content takes. The overlay\n * provides the slots it fills, and resets the default one inside its content,\n * so a button there opens nothing.\n */\ntype ButtonSlots = {\n readonly close?: (event: GestureResponderEvent) => void\n readonly default?: (event: GestureResponderEvent) => void\n /**\n * What the button filling the default slot takes on beside its press, for\n * an overlay drawn beside it.\n */\n readonly trigger?: ButtonSlotTrigger\n}\n\n/**\n * What a button filling the default slot takes on for an overlay drawn beside\n * it: its node, which the overlay is placed against, whether the overlay is\n * open, which a screen reader announces, and the hover and long press a\n * hovered popover opens on. The handlers run after the button's own.\n */\ntype ButtonSlotTrigger = {\n /** How long a press is held before it is a long press, in milliseconds. */\n readonly delayLongPress?: number\n /** Whether the overlay the button opens is open. */\n readonly expanded: boolean\n readonly onHoverIn?: () => void\n readonly onHoverOut?: () => void\n readonly onLongPress?: () => void\n /** Where the button's node is kept, for the overlay to be placed against. */\n readonly ref: RefObject<HostInstance | null>\n}\n\nconst ButtonSlotContext = createContext<ButtonSlots | null>(null)\n\nexport { ButtonSlotContext }\nexport type { ButtonSlots, ButtonSlotTrigger }\n"],"mappings":";;AA0CA,MAAMgB,oBAAoBb,cAAkC,IAAI"}
@@ -1,14 +1,14 @@
1
1
  import { IconStyleContext } from "../glyphs/icon-style.js";
2
- import { CheckGlyph } from "../glyphs/index.js";
3
2
  import { useTheme } from "../theme/scope.js";
4
3
  import { composeStyles } from "../theme/styles.js";
5
4
  import { useTypeRole } from "../typography/roles.js";
6
5
  import { withOpacity } from "../theme/color.js";
6
+ import { CheckGlyph } from "../glyphs/index.js";
7
7
  import { styles, stylesOf } from "./styles.js";
8
8
  import { c } from "react/compiler-runtime";
9
9
  import { View } from "react-native";
10
- import { jsx } from "react/jsx-runtime";
11
10
  import Animated, { Easing, useAnimatedStyle, useReducedMotion, withTiming } from "react-native-reanimated";
11
+ import { jsx } from "react/jsx-runtime";
12
12
  //#region src/chip/index.tsx
13
13
  const CHECK = /* @__PURE__ */ jsx(CheckGlyph, {});
14
14
  /**
@@ -0,0 +1,111 @@
1
+ import { View, ViewProps } from "react-native";
2
+ import { ComponentRef, ReactNode, Ref } from "react";
3
+ //#region src/components/app-bar/index.d.ts
4
+ type AppBarProps = Omit<ViewProps, 'children'> & {
5
+ /**
6
+ * Which of Material Design's alignments the text takes. `center` is the
7
+ * configuration that replaced the old center-aligned variant.
8
+ * @default 'start'
9
+ */
10
+ readonly align?: 'center' | 'start';
11
+ /**
12
+ * Collapses a flexible bar to the height and headline of the small one,
13
+ * for a `pinned` bar over content that has been scrolled. Without it a
14
+ * pinned `lg` keeps 152dp of the screen for as long as the screen is open.
15
+ *
16
+ * Controlled, as `scrolled` is: only the app knows which view scrolls and
17
+ * how far into it the bar should have collapsed. `useAppBarScroll` derives
18
+ * both from that view's scroll, and is the way to set them unless the app
19
+ * needs a rule of its own. Ignored on `sm`, which is already the height
20
+ * this collapses to, and the subtitle goes with the height.
21
+ * @default false
22
+ */
23
+ readonly collapsed?: boolean;
24
+ /**
25
+ * Where the bar's content starts on each edge, in dp: the headline where
26
+ * nothing comes before it, and the leading icon's glyph where something
27
+ * does. The trailing edge is the same mirrored. `0` draws a bar flush.
28
+ *
29
+ * The default is Material Design's margin for a top app bar, the spacing
30
+ * scale's `lg` step, which is also the margin it gives body content, so a
31
+ * bar and the screen beneath line up without either being told about the
32
+ * other.
33
+ * @default spacing.lg
34
+ */
35
+ readonly contentInset?: number;
36
+ /**
37
+ * How wide the bar's contents may run, in dp, before they stop growing,
38
+ * with the row centred in what is left. The bar's surface still paints
39
+ * edge to edge. Unset, the row is as wide as the bar.
40
+ */
41
+ readonly contentMaxInlineSize?: number;
42
+ /**
43
+ * The screen's title, a header for a screen reader. Wraps rather than
44
+ * truncating, and grows the bar with it.
45
+ */
46
+ readonly headline?: ReactNode;
47
+ /**
48
+ * Usually a back or menu icon button. Sits before the text, and hands an
49
+ * icon the on-surface colour and a 24dp size through `useIconStyle()`.
50
+ */
51
+ readonly leading?: ReactNode;
52
+ /**
53
+ * Marks a bar drawn above the screen's scroll view rather than inside it,
54
+ * so it stays put while the content scrolls beneath its lower edge, and is
55
+ * drawn over that content. A pinned bar is the one `collapsed` and
56
+ * `scrolled` are for.
57
+ * @default false
58
+ */
59
+ readonly pinned?: boolean;
60
+ readonly ref?: Ref<ComponentRef<typeof View>>;
61
+ /**
62
+ * Whether the content beneath has been scrolled. Material Design separates
63
+ * a bar from scrolled content with a fill colour rather than a shadow, the
64
+ * surface container role, and this is what applies it.
65
+ *
66
+ * Controlled, with no listener of its own: only the app knows which view
67
+ * scrolls. `useAppBarScroll` derives it.
68
+ * @default false
69
+ */
70
+ readonly scrolled?: boolean;
71
+ /**
72
+ * `sm` is Material Design's small bar, 64dp; `md` and `lg` are its medium
73
+ * and large flexible bars, 112dp and 120dp, or 136dp and 152dp with a
74
+ * subtitle, which hug their text. The headline is title large, headline
75
+ * small and headline medium in turn.
76
+ * @default 'sm'
77
+ */
78
+ readonly size?: AppBarSize;
79
+ /** A second line under the headline. Makes `md` and `lg` taller. */
80
+ readonly subtitle?: ReactNode;
81
+ /**
82
+ * Actions, at the far end, usually icon buttons. An icon here takes the
83
+ * on-surface-variant colour and a 24dp size through `useIconStyle()`.
84
+ */
85
+ readonly trailing?: ReactNode;
86
+ };
87
+ type AppBarSize = 'lg' | 'md' | 'sm';
88
+ /**
89
+ * Material Design's top app bar: the bar at the top of a screen carrying its
90
+ * title, one or two actions, and the way back out.
91
+ *
92
+ * ```tsx
93
+ * <AppBar
94
+ * headline="Headline"
95
+ * leading={<IconButton aria-label="Back"><ArrowBackGlyph /></IconButton>}
96
+ * />
97
+ * ```
98
+ *
99
+ * Three sizes. `sm` is a 64dp bar for a screen whose title is a label; `md`
100
+ * and `lg` are the flexible bars, which give the headline a larger type role
101
+ * and grow to fit a subtitle or a headline that wraps.
102
+ *
103
+ * It paints its own surface, from the screen's top edge, and keeps its
104
+ * contents clear of the status bar and the notch. Separating itself from the
105
+ * content beneath is its job, which is why `scrolled` and `collapsed` exist;
106
+ * `useAppBarScroll` sets both from the screen's scroll view.
107
+ */
108
+ declare function AppBar({ align, collapsed, contentInset, contentMaxInlineSize, headline, leading, pinned, scrolled, size, style, subtitle, trailing, ...props }: AppBarProps): import("react").JSX.Element;
109
+ //#endregion
110
+ export { AppBar, type AppBarProps };
111
+ //# sourceMappingURL=index.d.ts.map