panelui-native 0.65.0 → 0.68.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 (209) hide show
  1. package/README.md +21 -1
  2. package/lib/module/components/area-chart/index.js +18 -1
  3. package/lib/module/components/area-chart/index.js.map +1 -1
  4. package/lib/module/components/bar-chart/index.js +18 -1
  5. package/lib/module/components/bar-chart/index.js.map +1 -1
  6. package/lib/module/components/bottom-sheet/index.js.map +1 -1
  7. package/lib/module/components/candlestick-chart/index.js +18 -1
  8. package/lib/module/components/candlestick-chart/index.js.map +1 -1
  9. package/lib/module/components/carousel/carousel-lifecycle.js +51 -0
  10. package/lib/module/components/carousel/carousel-lifecycle.js.map +1 -0
  11. package/lib/module/components/carousel/index.js +61 -24
  12. package/lib/module/components/carousel/index.js.map +1 -1
  13. package/lib/module/components/context-menu/context-menu-invocation.js +23 -0
  14. package/lib/module/components/context-menu/context-menu-invocation.js.map +1 -0
  15. package/lib/module/components/context-menu/index.js +78 -9
  16. package/lib/module/components/context-menu/index.js.map +1 -1
  17. package/lib/module/components/dialog/index.js.map +1 -1
  18. package/lib/module/components/drawer/index.js.map +1 -1
  19. package/lib/module/components/flow/flow-accessibility.js +18 -2
  20. package/lib/module/components/flow/flow-accessibility.js.map +1 -1
  21. package/lib/module/components/flow/flow-identifiers.js +81 -0
  22. package/lib/module/components/flow/flow-identifiers.js.map +1 -0
  23. package/lib/module/components/flow/index.js +12 -18
  24. package/lib/module/components/flow/index.js.map +1 -1
  25. package/lib/module/components/form/index.js +7 -5
  26. package/lib/module/components/form/index.js.map +1 -1
  27. package/lib/module/components/form/typed-form.js +11 -0
  28. package/lib/module/components/form/typed-form.js.map +1 -0
  29. package/lib/module/components/form/use-form.js +4 -0
  30. package/lib/module/components/form/use-form.js.map +1 -1
  31. package/lib/module/components/heatmap-chart/index.js +27 -1
  32. package/lib/module/components/heatmap-chart/index.js.map +1 -1
  33. package/lib/module/components/line-chart/index.js +18 -1
  34. package/lib/module/components/line-chart/index.js.map +1 -1
  35. package/lib/module/components/message-scroller/index.js +187 -17
  36. package/lib/module/components/message-scroller/index.js.map +1 -1
  37. package/lib/module/components/message-scroller/message-scroller-math.js +43 -0
  38. package/lib/module/components/message-scroller/message-scroller-math.js.map +1 -0
  39. package/lib/module/components/meter/index.js +250 -0
  40. package/lib/module/components/meter/index.js.map +1 -0
  41. package/lib/module/components/meter/meter-scale.js +88 -0
  42. package/lib/module/components/meter/meter-scale.js.map +1 -0
  43. package/lib/module/components/planner/index.js +714 -0
  44. package/lib/module/components/planner/index.js.map +1 -0
  45. package/lib/module/components/planner/planner-entries.js +124 -0
  46. package/lib/module/components/planner/planner-entries.js.map +1 -0
  47. package/lib/module/components/popover/index.js.map +1 -1
  48. package/lib/module/components/radar-chart/index.js +18 -1
  49. package/lib/module/components/radar-chart/index.js.map +1 -1
  50. package/lib/module/components/scatter-chart/index.js +18 -1
  51. package/lib/module/components/scatter-chart/index.js.map +1 -1
  52. package/lib/module/components/selection-mode/index.js +26 -31
  53. package/lib/module/components/selection-mode/index.js.map +1 -1
  54. package/lib/module/components/selection-mode/selection-mode-contracts.js +48 -0
  55. package/lib/module/components/selection-mode/selection-mode-contracts.js.map +1 -0
  56. package/lib/module/components/signature/index.js +62 -20
  57. package/lib/module/components/signature/index.js.map +1 -1
  58. package/lib/module/components/signature/signature-accessibility.js +58 -0
  59. package/lib/module/components/signature/signature-accessibility.js.map +1 -0
  60. package/lib/module/components/skeleton/index.js +45 -7
  61. package/lib/module/components/skeleton/index.js.map +1 -1
  62. package/lib/module/components/slider/index.js +22 -18
  63. package/lib/module/components/slider/index.js.map +1 -1
  64. package/lib/module/components/spinner/index.js +63 -14
  65. package/lib/module/components/spinner/index.js.map +1 -1
  66. package/lib/module/components/time-picker/index.js +46 -21
  67. package/lib/module/components/time-picker/index.js.map +1 -1
  68. package/lib/module/components/time-picker/ruler-window.js +25 -0
  69. package/lib/module/components/time-picker/ruler-window.js.map +1 -0
  70. package/lib/module/hooks/breakpoint-contract.js +29 -0
  71. package/lib/module/hooks/breakpoint-contract.js.map +1 -0
  72. package/lib/module/hooks/index.js +1 -1
  73. package/lib/module/hooks/index.js.map +1 -1
  74. package/lib/module/hooks/use-breakpoint.js +46 -1
  75. package/lib/module/hooks/use-breakpoint.js.map +1 -1
  76. package/lib/module/index.js +3 -1
  77. package/lib/module/index.js.map +1 -1
  78. package/lib/module/primitives/chart-accessibility.js +68 -0
  79. package/lib/module/primitives/chart-accessibility.js.map +1 -0
  80. package/lib/module/primitives/scrim.js +94 -3
  81. package/lib/module/primitives/scrim.js.map +1 -1
  82. package/lib/typescript/src/components/area-chart/index.d.ts +2 -1
  83. package/lib/typescript/src/components/area-chart/index.d.ts.map +1 -1
  84. package/lib/typescript/src/components/bar-chart/index.d.ts +2 -1
  85. package/lib/typescript/src/components/bar-chart/index.d.ts.map +1 -1
  86. package/lib/typescript/src/components/bottom-sheet/index.d.ts +3 -0
  87. package/lib/typescript/src/components/bottom-sheet/index.d.ts.map +1 -1
  88. package/lib/typescript/src/components/candlestick-chart/index.d.ts +2 -1
  89. package/lib/typescript/src/components/candlestick-chart/index.d.ts.map +1 -1
  90. package/lib/typescript/src/components/carousel/carousel-lifecycle.d.ts +23 -0
  91. package/lib/typescript/src/components/carousel/carousel-lifecycle.d.ts.map +1 -0
  92. package/lib/typescript/src/components/carousel/index.d.ts +5 -2
  93. package/lib/typescript/src/components/carousel/index.d.ts.map +1 -1
  94. package/lib/typescript/src/components/context-menu/context-menu-invocation.d.ts +20 -0
  95. package/lib/typescript/src/components/context-menu/context-menu-invocation.d.ts.map +1 -0
  96. package/lib/typescript/src/components/context-menu/index.d.ts +18 -8
  97. package/lib/typescript/src/components/context-menu/index.d.ts.map +1 -1
  98. package/lib/typescript/src/components/dialog/index.d.ts +3 -0
  99. package/lib/typescript/src/components/dialog/index.d.ts.map +1 -1
  100. package/lib/typescript/src/components/drawer/index.d.ts +3 -0
  101. package/lib/typescript/src/components/drawer/index.d.ts.map +1 -1
  102. package/lib/typescript/src/components/flow/flow-accessibility.d.ts +8 -0
  103. package/lib/typescript/src/components/flow/flow-accessibility.d.ts.map +1 -1
  104. package/lib/typescript/src/components/flow/flow-identifiers.d.ts +24 -0
  105. package/lib/typescript/src/components/flow/flow-identifiers.d.ts.map +1 -0
  106. package/lib/typescript/src/components/flow/index.d.ts +6 -5
  107. package/lib/typescript/src/components/flow/index.d.ts.map +1 -1
  108. package/lib/typescript/src/components/form/index.d.ts +18 -5
  109. package/lib/typescript/src/components/form/index.d.ts.map +1 -1
  110. package/lib/typescript/src/components/form/typed-form.d.ts +7 -0
  111. package/lib/typescript/src/components/form/typed-form.d.ts.map +1 -0
  112. package/lib/typescript/src/components/form/use-form.d.ts +4 -0
  113. package/lib/typescript/src/components/form/use-form.d.ts.map +1 -1
  114. package/lib/typescript/src/components/heatmap-chart/index.d.ts +2 -1
  115. package/lib/typescript/src/components/heatmap-chart/index.d.ts.map +1 -1
  116. package/lib/typescript/src/components/line-chart/index.d.ts +2 -1
  117. package/lib/typescript/src/components/line-chart/index.d.ts.map +1 -1
  118. package/lib/typescript/src/components/message-scroller/index.d.ts +30 -2
  119. package/lib/typescript/src/components/message-scroller/index.d.ts.map +1 -1
  120. package/lib/typescript/src/components/message-scroller/message-scroller-math.d.ts +18 -0
  121. package/lib/typescript/src/components/message-scroller/message-scroller-math.d.ts.map +1 -0
  122. package/lib/typescript/src/components/meter/index.d.ts +270 -0
  123. package/lib/typescript/src/components/meter/index.d.ts.map +1 -0
  124. package/lib/typescript/src/components/meter/meter-scale.d.ts +57 -0
  125. package/lib/typescript/src/components/meter/meter-scale.d.ts.map +1 -0
  126. package/lib/typescript/src/components/planner/index.d.ts +274 -0
  127. package/lib/typescript/src/components/planner/index.d.ts.map +1 -0
  128. package/lib/typescript/src/components/planner/planner-entries.d.ts +75 -0
  129. package/lib/typescript/src/components/planner/planner-entries.d.ts.map +1 -0
  130. package/lib/typescript/src/components/popover/index.d.ts +3 -0
  131. package/lib/typescript/src/components/popover/index.d.ts.map +1 -1
  132. package/lib/typescript/src/components/radar-chart/index.d.ts +4 -0
  133. package/lib/typescript/src/components/radar-chart/index.d.ts.map +1 -1
  134. package/lib/typescript/src/components/scatter-chart/index.d.ts +2 -1
  135. package/lib/typescript/src/components/scatter-chart/index.d.ts.map +1 -1
  136. package/lib/typescript/src/components/selection-mode/index.d.ts +6 -4
  137. package/lib/typescript/src/components/selection-mode/index.d.ts.map +1 -1
  138. package/lib/typescript/src/components/selection-mode/selection-mode-contracts.d.ts +18 -0
  139. package/lib/typescript/src/components/selection-mode/selection-mode-contracts.d.ts.map +1 -0
  140. package/lib/typescript/src/components/signature/index.d.ts +6 -0
  141. package/lib/typescript/src/components/signature/index.d.ts.map +1 -1
  142. package/lib/typescript/src/components/signature/signature-accessibility.d.ts +27 -0
  143. package/lib/typescript/src/components/signature/signature-accessibility.d.ts.map +1 -0
  144. package/lib/typescript/src/components/skeleton/index.d.ts +25 -2
  145. package/lib/typescript/src/components/skeleton/index.d.ts.map +1 -1
  146. package/lib/typescript/src/components/slider/index.d.ts.map +1 -1
  147. package/lib/typescript/src/components/spinner/index.d.ts +25 -2
  148. package/lib/typescript/src/components/spinner/index.d.ts.map +1 -1
  149. package/lib/typescript/src/components/time-picker/index.d.ts +5 -6
  150. package/lib/typescript/src/components/time-picker/index.d.ts.map +1 -1
  151. package/lib/typescript/src/components/time-picker/ruler-window.d.ts +12 -0
  152. package/lib/typescript/src/components/time-picker/ruler-window.d.ts.map +1 -0
  153. package/lib/typescript/src/hooks/breakpoint-contract.d.ts +6 -0
  154. package/lib/typescript/src/hooks/breakpoint-contract.d.ts.map +1 -0
  155. package/lib/typescript/src/hooks/index.d.ts +1 -1
  156. package/lib/typescript/src/hooks/index.d.ts.map +1 -1
  157. package/lib/typescript/src/hooks/use-breakpoint.d.ts +22 -0
  158. package/lib/typescript/src/hooks/use-breakpoint.d.ts.map +1 -1
  159. package/lib/typescript/src/index.d.ts +4 -2
  160. package/lib/typescript/src/index.d.ts.map +1 -1
  161. package/lib/typescript/src/primitives/chart-accessibility.d.ts +33 -0
  162. package/lib/typescript/src/primitives/chart-accessibility.d.ts.map +1 -0
  163. package/lib/typescript/src/primitives/scrim.d.ts +5 -3
  164. package/lib/typescript/src/primitives/scrim.d.ts.map +1 -1
  165. package/lib/typescript/types/form-api.type-test.d.ts +2 -0
  166. package/lib/typescript/types/form-api.type-test.d.ts.map +1 -0
  167. package/package.json +34 -5
  168. package/src/components/area-chart/index.tsx +27 -2
  169. package/src/components/bar-chart/index.tsx +27 -2
  170. package/src/components/bottom-sheet/index.tsx +3 -0
  171. package/src/components/candlestick-chart/index.tsx +30 -2
  172. package/src/components/carousel/carousel-lifecycle.ts +74 -0
  173. package/src/components/carousel/index.tsx +80 -25
  174. package/src/components/context-menu/context-menu-invocation.ts +44 -0
  175. package/src/components/context-menu/index.tsx +113 -13
  176. package/src/components/dialog/index.tsx +3 -0
  177. package/src/components/drawer/index.tsx +3 -0
  178. package/src/components/flow/flow-accessibility.ts +16 -2
  179. package/src/components/flow/flow-identifiers.ts +98 -0
  180. package/src/components/flow/index.tsx +29 -19
  181. package/src/components/form/index.tsx +32 -5
  182. package/src/components/form/typed-form.ts +13 -0
  183. package/src/components/form/use-form.ts +9 -0
  184. package/src/components/heatmap-chart/index.tsx +49 -3
  185. package/src/components/line-chart/index.tsx +27 -2
  186. package/src/components/message-scroller/index.tsx +273 -13
  187. package/src/components/message-scroller/message-scroller-math.ts +62 -0
  188. package/src/components/meter/index.tsx +318 -0
  189. package/src/components/meter/meter-scale.ts +109 -0
  190. package/src/components/planner/index.tsx +886 -0
  191. package/src/components/planner/planner-entries.ts +146 -0
  192. package/src/components/popover/index.tsx +3 -0
  193. package/src/components/radar-chart/index.tsx +24 -0
  194. package/src/components/scatter-chart/index.tsx +27 -2
  195. package/src/components/selection-mode/index.tsx +37 -30
  196. package/src/components/selection-mode/selection-mode-contracts.ts +53 -0
  197. package/src/components/signature/index.tsx +107 -20
  198. package/src/components/signature/signature-accessibility.ts +67 -0
  199. package/src/components/skeleton/index.tsx +59 -5
  200. package/src/components/slider/index.tsx +28 -18
  201. package/src/components/spinner/index.tsx +80 -13
  202. package/src/components/time-picker/index.tsx +57 -29
  203. package/src/components/time-picker/ruler-window.ts +24 -0
  204. package/src/hooks/breakpoint-contract.ts +31 -0
  205. package/src/hooks/index.ts +4 -0
  206. package/src/hooks/use-breakpoint.ts +61 -10
  207. package/src/index.ts +32 -0
  208. package/src/primitives/chart-accessibility.ts +102 -0
  209. package/src/primitives/scrim.tsx +127 -7
@@ -0,0 +1,714 @@
1
+ "use strict";
2
+
3
+ /**
4
+ * Planner — a month of days, each carrying what falls on it.
5
+ *
6
+ * ```tsx
7
+ * const [month, setMonth] = useState(new Date());
8
+ *
9
+ * <Planner
10
+ * month={month}
11
+ * onMonthChange={setMonth}
12
+ * entries={renewals}
13
+ * categories={[
14
+ * { id: 'monthly', label: 'Monthly' },
15
+ * { id: 'yearly', label: 'Yearly', colorIndex: 4 },
16
+ * ]}
17
+ * >
18
+ * <Planner.Header>
19
+ * <Planner.Title />
20
+ * <Planner.Today />
21
+ * <Planner.Nav />
22
+ * </Planner.Header>
23
+ * <Planner.Grid />
24
+ * <Planner.Legend />
25
+ * <Planner.Details>
26
+ * {(date, entries) => <Text>{entries.length} on {date.toDateString()}</Text>}
27
+ * </Planner.Details>
28
+ * </Planner>
29
+ * ```
30
+ *
31
+ * ## How it differs from Calendar
32
+ *
33
+ * `Calendar` picks a date and answers with one. This shows what is already on
34
+ * the days and answers with the day you asked about — the selection exists to
35
+ * open something, not to be submitted.
36
+ *
37
+ * ## Why it draws its own Frame
38
+ *
39
+ * A month at a glance is a widget: a boundary, a strip along the top carrying
40
+ * the month and the way through it, and a footer that holds still while the
41
+ * middle changes. That is `Frame`, so the root renders one instead of leaving
42
+ * every caller to assemble the same shell. Pass `frame={false}` to drop it,
43
+ * for a planner in a sheet or a card that already draws its own edge.
44
+ *
45
+ * ## Why the grid is always six weeks
46
+ *
47
+ * A month can span five weeks or six. Drawn at its natural height the panel
48
+ * jumps as you page through the year and the days appear to move under your
49
+ * thumb, so the grid is always six rows and the last one is sometimes all
50
+ * next month. `Calendar` fixes its height for the same reason.
51
+ *
52
+ * ## Why a day says more than its date
53
+ *
54
+ * The marker on a day is a coloured dot, and colour is a signal that does not
55
+ * reach everyone looking at it. So the legend prints its label beside every
56
+ * swatch, and a day is spoken as its date, how many entries it carries and
57
+ * which categories they belong to. Neither is decoration: between them they
58
+ * are the whole content of the grid for somebody who cannot see it.
59
+ */
60
+ import { Children, createContext, forwardRef, isValidElement, useCallback, useContext, useEffect, useMemo, useState } from 'react';
61
+ import { AccessibilityInfo, AppState, Pressable, View } from 'react-native';
62
+ import { tv } from 'tailwind-variants';
63
+ import { useCSSVariable } from 'uniwind';
64
+ import { ChevronLeftIcon, ChevronRightIcon } from "../../icons/index.js";
65
+ import { Text } from "../../primitives/text.js";
66
+ import { cn } from "../../utils/cn.js";
67
+ import { addCalendarMonths, calendarDayNumber, calendarLongDate, calendarMonthLabel, isSameCalendarMonth, isSameDay, localeWeekStart, monthGrid, normalizeWeekStart, resolveCalendar, startOfCalendarMonth, startOfDay, weekdayNames } from "../../utils/date.js";
68
+ import { Dialog } from "../dialog/index.js";
69
+ import { Frame } from "../frame/index.js";
70
+ import { bucketByDay, dayAccessibilityLabel, entriesOn, summariseMonth, visibleEntries } from "./planner-entries.js";
71
+
72
+ /** How many of a day's entries a cell draws before it stops and counts. */
73
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
74
+ const DEFAULT_ENTRY_LIMIT = 2;
75
+
76
+ /** The palette a category takes its dot from when it does not name a colour. */
77
+ const PALETTE_SIZE = 5;
78
+ const plannerVariants = tv({
79
+ slots: {
80
+ grid: 'gap-1 px-3 pb-3 pt-1',
81
+ week: 'flex-row',
82
+ heading: 'flex-1 text-center',
83
+ legend: 'flex-row flex-wrap items-center gap-x-4 gap-y-2 px-4 py-3',
84
+ swatch: 'h-2 w-2 rounded-full'
85
+ }
86
+ });
87
+ const dayVariants = tv({
88
+ slots: {
89
+ /*
90
+ * A fixed height, and a square-ish tile rather than a circle.
91
+ *
92
+ * Fixed because the cell has to be the same size whether the day carries
93
+ * anything or not — sized to its contents, a row with one icon in it is
94
+ * taller than the five around it and the grid stops being a grid.
95
+ *
96
+ * Square-ish because a day here holds a number, a marker and sometimes an
97
+ * icon, and a circle wastes the corners it needs for them.
98
+ */
99
+ cell: 'mx-0.5 h-14 flex-1 rounded-xl px-1.5 pt-1.5',
100
+ number: 'text-xs leading-none',
101
+ marker: 'absolute right-1.5 top-1.5 h-1.5 w-1.5 rounded-full',
102
+ body: 'flex-1 flex-row items-center justify-center gap-0.5 pb-1'
103
+ },
104
+ variants: {
105
+ /** Inside the month being shown, as opposed to the days either side. */
106
+ inMonth: {
107
+ true: {
108
+ cell: 'bg-muted/40',
109
+ number: 'text-foreground'
110
+ },
111
+ false: {
112
+ cell: 'bg-muted/15',
113
+ number: 'text-muted-foreground/40'
114
+ }
115
+ },
116
+ /*
117
+ * Today is the number, not the tile. A ring on today and a ring on the
118
+ * open day are two rings that mean different things and look the same.
119
+ */
120
+ today: {
121
+ true: {
122
+ number: 'text-primary font-semibold'
123
+ }
124
+ },
125
+ selected: {
126
+ true: {
127
+ cell: 'border border-primary/60 bg-primary/10'
128
+ }
129
+ },
130
+ disabled: {
131
+ true: {
132
+ cell: 'opacity-40'
133
+ }
134
+ }
135
+ },
136
+ defaultVariants: {
137
+ inMonth: true
138
+ }
139
+ });
140
+
141
+ /* ------------------------------------------------------------------ *
142
+ * Data
143
+ * ------------------------------------------------------------------ */
144
+
145
+ /** One thing that falls on a day. */
146
+
147
+ /** A group of entries: the key to a colour, and a line in the legend. */
148
+
149
+ const PlannerContext = /*#__PURE__*/createContext(null);
150
+ function usePlanner(component) {
151
+ const context = useContext(PlannerContext);
152
+ if (!context) {
153
+ throw new Error(`${component} must be used within a <Planner>`);
154
+ }
155
+ return context;
156
+ }
157
+
158
+ /** Today, recomputed when the app comes back rather than only at mount. */
159
+ function useToday() {
160
+ const [today, setToday] = useState(() => startOfDay(new Date()));
161
+ useEffect(() => {
162
+ const refresh = () => {
163
+ const now = startOfDay(new Date());
164
+ // A new object every resume would invalidate every memo below it for a
165
+ // date that has not changed.
166
+ setToday(current => current.getTime() === now.getTime() ? current : now);
167
+ };
168
+ const subscription = AppState.addEventListener('change', state => {
169
+ if (state === 'active') refresh();
170
+ });
171
+ return () => subscription.remove();
172
+ }, []);
173
+ return today;
174
+ }
175
+
176
+ /**
177
+ * The five series colours, read once.
178
+ *
179
+ * A fixed number of reads rather than one per category: a hook cannot run in a
180
+ * loop whose length is a prop.
181
+ */
182
+ function usePalette() {
183
+ const one = useCSSVariable('--color-chart-1');
184
+ const two = useCSSVariable('--color-chart-2');
185
+ const three = useCSSVariable('--color-chart-3');
186
+ const four = useCSSVariable('--color-chart-4');
187
+ const five = useCSSVariable('--color-chart-5');
188
+ return useMemo(() => [one, two, three, four, five].map(v => typeof v === 'string' ? v : undefined), [one, two, three, four, five]);
189
+ }
190
+
191
+ /* ------------------------------------------------------------------ *
192
+ * Root
193
+ * ------------------------------------------------------------------ */
194
+
195
+ const PlannerRoot = /*#__PURE__*/forwardRef(({
196
+ className,
197
+ month: monthProp,
198
+ defaultMonth,
199
+ onMonthChange,
200
+ entries = [],
201
+ categories = [],
202
+ selected: selectedProp,
203
+ defaultSelected = null,
204
+ onSelectedChange,
205
+ onDayPress,
206
+ entryLimit = DEFAULT_ENTRY_LIMIT,
207
+ weekStartsOn = 'auto',
208
+ locale,
209
+ calendar = 'gregory',
210
+ frame = true,
211
+ children,
212
+ ...props
213
+ }, ref) => {
214
+ const system = resolveCalendar(calendar, locale);
215
+ const palette = usePalette();
216
+ const today = useToday();
217
+ const [internalMonth, setInternalMonth] = useState(() => startOfCalendarMonth(defaultMonth ?? new Date(), system, locale));
218
+ const month = monthProp ? startOfCalendarMonth(monthProp, system, locale) : internalMonth;
219
+ const setMonth = useCallback(next => {
220
+ const settled = startOfCalendarMonth(next, system, locale);
221
+ if (!monthProp) setInternalMonth(settled);
222
+ onMonthChange?.(settled);
223
+ }, [monthProp, onMonthChange, system, locale]);
224
+ const [internalSelected, setInternalSelected] = useState(defaultSelected);
225
+ const selected = selectedProp !== undefined ? selectedProp : internalSelected;
226
+ const select = useCallback(date => {
227
+ if (selectedProp === undefined) setInternalSelected(date);
228
+ onSelectedChange?.(date);
229
+ }, [selectedProp, onSelectedChange]);
230
+ const days = useMemo(() => bucketByDay(entries), [entries]);
231
+ const isInMonth = useCallback(date => isSameCalendarMonth(date, month, system, locale), [month, system, locale]);
232
+ const categoryLabels = useMemo(() => new Map(categories.map(category => [category.id, category.label])), [categories]);
233
+ const colorOf = useCallback(id => {
234
+ if (!id) return undefined;
235
+ const index = categories.findIndex(category => category.id === id);
236
+ if (index < 0) return undefined;
237
+ const category = categories[index];
238
+ if (category.color) return category.color;
239
+ const slot = (category.colorIndex ?? index + 1) - 1;
240
+ return palette[(slot % PALETTE_SIZE + PALETTE_SIZE) % PALETTE_SIZE];
241
+ }, [categories, palette]);
242
+ const summary = useMemo(() => summariseMonth(entries, categories, isInMonth), [entries, categories, isInMonth]);
243
+
244
+ /*
245
+ * The month is announced rather than left to the cells. Paging moves 42
246
+ * labels at once and a screen reader reads none of them, so without this
247
+ * the only thing that changes is silent.
248
+ */
249
+ const monthLabel = calendarMonthLabel(month, system, locale);
250
+ useEffect(() => {
251
+ AccessibilityInfo.announceForAccessibility(monthLabel);
252
+ }, [monthLabel]);
253
+ const context = useMemo(() => ({
254
+ month,
255
+ setMonth,
256
+ today,
257
+ selected,
258
+ select,
259
+ days,
260
+ categories,
261
+ categoryLabels,
262
+ colorOf,
263
+ summary,
264
+ entryLimit,
265
+ weekStartsOn: weekStartsOn === 'auto' ? localeWeekStart(locale) : normalizeWeekStart(weekStartsOn),
266
+ locale,
267
+ system,
268
+ isInMonth,
269
+ onDayPress
270
+ }), [month, setMonth, today, selected, select, days, categories, categoryLabels, colorOf, summary, entryLimit, weekStartsOn, locale, system, isInMonth, onDayPress]);
271
+
272
+ /*
273
+ * The header belongs in the Frame's strip and everything else in its
274
+ * panel, so the root sorts its children rather than asking the caller to
275
+ * nest them in two places to get one widget.
276
+ */
277
+ const parts = Children.toArray(children);
278
+ const header = parts.find(child => /*#__PURE__*/isValidElement(child) && child.type === PlannerHeader);
279
+ const body = parts.filter(child => child !== header);
280
+ return /*#__PURE__*/_jsx(PlannerContext.Provider, {
281
+ value: context,
282
+ children: frame ?
283
+ /*#__PURE__*/
284
+ // Full width by default: the grid is seven equal columns, and a
285
+ // container that sizes to its content collapses them to the width of
286
+ // a two-digit number.
287
+ _jsxs(Frame, {
288
+ ref: ref,
289
+ className: cn('w-full', className),
290
+ ...props,
291
+ children: [header, /*#__PURE__*/_jsx(Frame.Panel, {
292
+ dividers: false,
293
+ children: body
294
+ })]
295
+ }) : /*#__PURE__*/_jsxs(View, {
296
+ ref: ref,
297
+ className: cn('w-full', className),
298
+ ...props,
299
+ children: [header, body]
300
+ })
301
+ });
302
+ });
303
+ PlannerRoot.displayName = 'Planner';
304
+
305
+ /* ------------------------------------------------------------------ *
306
+ * Header strip
307
+ * ------------------------------------------------------------------ */
308
+
309
+ /**
310
+ * The strip along the top of the frame. Put `Title`, `Today` and `Nav` in it.
311
+ *
312
+ * The strip has two ends rather than an even spread. What the month *is* —
313
+ * its name, and the way back to today — reads from the leading edge; what
314
+ * *moves* it sits at the trailing edge, under the thumb that reaches for it.
315
+ * Spaced evenly across a full-width strip they read as three unrelated
316
+ * controls instead of a label and a pair of buttons.
317
+ */
318
+ function PlannerHeader({
319
+ children
320
+ }) {
321
+ const parts = Children.toArray(children);
322
+ const trails = child => /*#__PURE__*/isValidElement(child) && (child.type === PlannerNav || child.type === PlannerAction);
323
+ const trailing = parts.filter(trails);
324
+ const lead = parts.filter(child => !trails(child));
325
+ return /*#__PURE__*/_jsxs(Frame.Header, {
326
+ children: [/*#__PURE__*/_jsx(View, {
327
+ className: "min-w-0 flex-1 flex-row items-center gap-2",
328
+ children: lead
329
+ }), trailing.length > 0 ? /*#__PURE__*/_jsx(View, {
330
+ className: "shrink-0 flex-row items-center gap-1.5",
331
+ children: trailing
332
+ }) : null]
333
+ });
334
+ }
335
+ PlannerHeader.displayName = 'Planner.Header';
336
+ /** The month on show, in the calendar system and locale the grid uses. */
337
+ function PlannerTitle({
338
+ children,
339
+ className,
340
+ ...props
341
+ }) {
342
+ const {
343
+ month,
344
+ system,
345
+ locale
346
+ } = usePlanner('Planner.Title');
347
+ return /*#__PURE__*/_jsx(Text, {
348
+ weight: "medium",
349
+ className: cn('text-base', className),
350
+ ...props,
351
+ children: children ?? calendarMonthLabel(month, system, locale)
352
+ });
353
+ }
354
+ PlannerTitle.displayName = 'Planner.Title';
355
+ /** Jumps back to the month today is in, and selects nothing. */
356
+ function PlannerToday({
357
+ children = 'Today'
358
+ }) {
359
+ const {
360
+ today,
361
+ setMonth,
362
+ month,
363
+ system,
364
+ locale
365
+ } = usePlanner('Planner.Today');
366
+ const alreadyHere = isSameCalendarMonth(today, month, system, locale);
367
+ return /*#__PURE__*/_jsx(Pressable, {
368
+ onPress: () => setMonth(today),
369
+ disabled: alreadyHere,
370
+ hitSlop: 6,
371
+ accessibilityRole: "button",
372
+ accessibilityLabel: "Go to this month",
373
+ accessibilityState: {
374
+ disabled: alreadyHere
375
+ },
376
+ className: cn('rounded-full border border-border px-3 py-1', alreadyHere ? 'opacity-40' : 'active:bg-accent'),
377
+ children: /*#__PURE__*/_jsx(Text, {
378
+ size: "xs",
379
+ muted: true,
380
+ children: children
381
+ })
382
+ });
383
+ }
384
+ PlannerToday.displayName = 'Planner.Today';
385
+ /** Back and forward a month. */
386
+ function PlannerNav({
387
+ className
388
+ }) {
389
+ const {
390
+ month,
391
+ setMonth,
392
+ system,
393
+ locale
394
+ } = usePlanner('Planner.Nav');
395
+ return /*#__PURE__*/_jsxs(View, {
396
+ className: cn('flex-row items-center gap-1', className),
397
+ children: [/*#__PURE__*/_jsx(PlannerArrow, {
398
+ direction: "previous",
399
+ onPress: () => setMonth(addCalendarMonths(month, -1, system, locale))
400
+ }), /*#__PURE__*/_jsx(PlannerArrow, {
401
+ direction: "next",
402
+ onPress: () => setMonth(addCalendarMonths(month, 1, system, locale))
403
+ })]
404
+ });
405
+ }
406
+ PlannerNav.displayName = 'Planner.Nav';
407
+ function PlannerArrow({
408
+ direction,
409
+ onPress
410
+ }) {
411
+ return /*#__PURE__*/_jsx(Pressable, {
412
+ onPress: onPress,
413
+ hitSlop: 8,
414
+ accessibilityRole: "button",
415
+ accessibilityLabel: direction === 'next' ? 'Next month' : 'Previous month',
416
+ className: "h-8 w-8 items-center justify-center rounded-lg active:bg-accent",
417
+ children: direction === 'next' ? /*#__PURE__*/_jsx(ChevronRightIcon, {
418
+ size: 18
419
+ }) : /*#__PURE__*/_jsx(ChevronLeftIcon, {
420
+ size: 18
421
+ })
422
+ });
423
+ }
424
+ /** The trailing end of the header strip, for a button of the caller's. */
425
+ function PlannerAction({
426
+ children
427
+ }) {
428
+ return /*#__PURE__*/_jsx(Frame.Action, {
429
+ children: children
430
+ });
431
+ }
432
+ PlannerAction.displayName = 'Planner.Action';
433
+
434
+ /* ------------------------------------------------------------------ *
435
+ * Grid
436
+ * ------------------------------------------------------------------ */
437
+
438
+ /** What a custom cell is handed. Everything the default cell draws from. */
439
+
440
+ /**
441
+ * The weekday row and the six weeks below it.
442
+ *
443
+ * React Native has no per-cell grid vocabulary — no `gridcell`, no `row` — so
444
+ * a screen reader never hears "row three, column five" and cannot fall back on
445
+ * position for context. Every day therefore carries its own full date, and the
446
+ * weekday headings are hidden rather than read out 42 times over.
447
+ */
448
+ function PlannerGrid({
449
+ className,
450
+ renderDay
451
+ }) {
452
+ const {
453
+ month,
454
+ weekStartsOn,
455
+ locale,
456
+ system
457
+ } = usePlanner('Planner.Grid');
458
+ const {
459
+ grid,
460
+ week: weekRow,
461
+ heading
462
+ } = plannerVariants();
463
+ const weeks = useMemo(() => monthGrid(month, weekStartsOn, system, locale), [month, weekStartsOn, system, locale]);
464
+ const headings = useMemo(() => weekdayNames(locale, weekStartsOn), [locale, weekStartsOn]);
465
+ return /*#__PURE__*/_jsxs(View, {
466
+ className: grid({
467
+ className
468
+ }),
469
+ children: [/*#__PURE__*/_jsx(View, {
470
+ className: weekRow(),
471
+ accessibilityElementsHidden: true,
472
+ importantForAccessibility: "no-hide-descendants",
473
+ children: headings.map(label => /*#__PURE__*/_jsx(Text, {
474
+ size: "xs",
475
+ muted: true,
476
+ className: heading(),
477
+ children: label.toUpperCase()
478
+ }, label))
479
+ }), weeks.map((week, index) => /*#__PURE__*/_jsx(View, {
480
+ className: weekRow(),
481
+ children: week.map(date => /*#__PURE__*/_jsx(PlannerDay, {
482
+ date: date,
483
+ renderDay: renderDay
484
+ }, date.getTime()))
485
+ }, index))]
486
+ });
487
+ }
488
+ PlannerGrid.displayName = 'Planner.Grid';
489
+ /** One cell. Pressing it selects the day and opens whatever is bound to it. */
490
+ function PlannerDay({
491
+ date,
492
+ renderDay
493
+ }) {
494
+ const {
495
+ days,
496
+ today,
497
+ selected,
498
+ select,
499
+ colorOf,
500
+ categoryLabels,
501
+ entryLimit,
502
+ locale,
503
+ system,
504
+ isInMonth,
505
+ onDayPress
506
+ } = usePlanner('Planner.Day');
507
+ const entries = entriesOn(days, date);
508
+ const inMonth = isInMonth(date);
509
+ const isToday = isSameDay(date, today);
510
+ const isSelected = selected ? isSameDay(date, selected) : false;
511
+ const {
512
+ shown,
513
+ overflow
514
+ } = visibleEntries(entries, entryLimit);
515
+ const drawable = shown.filter(entry => entry.icon);
516
+ const styles = dayVariants({
517
+ inMonth,
518
+ today: isToday,
519
+ selected: isSelected
520
+ });
521
+ const label = dayAccessibilityLabel(calendarLongDate(date, system, locale), entries, categoryLabels);
522
+ const press = () => {
523
+ onDayPress?.(date, entries);
524
+ select(date);
525
+ };
526
+ if (renderDay) {
527
+ return /*#__PURE__*/_jsx(View, {
528
+ className: "flex-1",
529
+ children: renderDay({
530
+ date,
531
+ entries,
532
+ isToday,
533
+ isSelected,
534
+ isInMonth: inMonth
535
+ })
536
+ });
537
+ }
538
+ return /*#__PURE__*/_jsxs(Pressable, {
539
+ onPress: press,
540
+ accessibilityRole: "button",
541
+ accessibilityLabel: label,
542
+ accessibilityHint: entries.length > 0 ? 'Opens what is on this day' : undefined,
543
+ accessibilityState: {
544
+ selected: isSelected
545
+ },
546
+ className: styles.cell(),
547
+ children: [/*#__PURE__*/_jsx(Text, {
548
+ className: styles.number(),
549
+ children: calendarDayNumber(date, system, locale)
550
+ }), entries.length > 0 ? /*#__PURE__*/_jsx(View, {
551
+ className: styles.marker(),
552
+ style: {
553
+ backgroundColor: markerColor(entries, colorOf)
554
+ }
555
+ }) : null, drawable.length > 0 ? /*#__PURE__*/_jsxs(View, {
556
+ className: styles.body(),
557
+ children: [drawable.map(entry => /*#__PURE__*/_jsx(View, {
558
+ children: entry.icon
559
+ }, entry.id)), overflow > 0 ? /*#__PURE__*/_jsxs(Text, {
560
+ size: "xs",
561
+ muted: true,
562
+ className: "leading-none",
563
+ children: ["+", overflow]
564
+ }) : null]
565
+ }) : null]
566
+ });
567
+ }
568
+ PlannerDay.displayName = 'Planner.Day';
569
+
570
+ /**
571
+ * The dot's colour: the first entry that names a category the planner knows.
572
+ *
573
+ * One dot rather than one per entry. A cell this size fits a row of dots or a
574
+ * day number, and the count is already spoken — so the dot answers "is there
575
+ * anything here, and roughly what kind", which is all it has room to answer.
576
+ */
577
+ function markerColor(entries, colorOf) {
578
+ for (const entry of entries) {
579
+ const color = colorOf(entry.category);
580
+ if (color) return color;
581
+ }
582
+ return undefined;
583
+ }
584
+
585
+ /* ------------------------------------------------------------------ *
586
+ * Legend and summary
587
+ * ------------------------------------------------------------------ */
588
+
589
+ /**
590
+ * The key to the dots.
591
+ *
592
+ * It prints the label beside every swatch, because a column of coloured dots
593
+ * with nothing to read them against is a quiz.
594
+ */
595
+ function PlannerLegend({
596
+ className,
597
+ counts = false,
598
+ children
599
+ }) {
600
+ const {
601
+ summary,
602
+ colorOf
603
+ } = usePlanner('Planner.Legend');
604
+ const {
605
+ legend,
606
+ swatch
607
+ } = plannerVariants();
608
+ return /*#__PURE__*/_jsxs(View, {
609
+ className: cn(legend(), 'justify-between border-t border-border', className),
610
+ children: [/*#__PURE__*/_jsx(View, {
611
+ className: "flex-row flex-wrap items-center gap-x-4 gap-y-2",
612
+ children: summary.categories.map(category => /*#__PURE__*/_jsxs(View, {
613
+ className: "flex-row items-center gap-1.5",
614
+ children: [/*#__PURE__*/_jsx(View, {
615
+ className: swatch(),
616
+ style: {
617
+ backgroundColor: colorOf(category.id)
618
+ }
619
+ }), /*#__PURE__*/_jsx(Text, {
620
+ size: "xs",
621
+ muted: true,
622
+ children: counts ? `${category.label} ${category.count}` : category.label
623
+ })]
624
+ }, category.id))
625
+ }), children]
626
+ });
627
+ }
628
+ PlannerLegend.displayName = 'Planner.Legend';
629
+ /** What the month adds up to. Counts this month only, never the days either side. */
630
+ function PlannerSummary({
631
+ children,
632
+ ...props
633
+ }) {
634
+ const {
635
+ summary
636
+ } = usePlanner('Planner.Summary');
637
+ return /*#__PURE__*/_jsx(Text, {
638
+ size: "xs",
639
+ muted: true,
640
+ ...props,
641
+ children: children ?? `${summary.total} ${summary.total === 1 ? 'entry' : 'entries'}`
642
+ });
643
+ }
644
+ PlannerSummary.displayName = 'Planner.Summary';
645
+ /** The strip along the bottom, for tools that act on the month. */
646
+ function PlannerFooter({
647
+ className,
648
+ children
649
+ }) {
650
+ return /*#__PURE__*/_jsx(View, {
651
+ className: cn('flex-row items-center justify-between gap-3 px-4 py-3', className),
652
+ children: children
653
+ });
654
+ }
655
+ PlannerFooter.displayName = 'Planner.Footer';
656
+
657
+ /* ------------------------------------------------------------------ *
658
+ * Details
659
+ * ------------------------------------------------------------------ */
660
+
661
+ /**
662
+ * A dialog bound to the open day.
663
+ *
664
+ * The planner owns the grid and the binding; what the dialog says is the
665
+ * application's, because the contents of a day are its data and not the
666
+ * component's. Leave it out and `onDayPress` is still called — a planner that
667
+ * pushes a screen instead of opening a dialog wants that and nothing else.
668
+ */
669
+ function PlannerDetails({
670
+ className,
671
+ title,
672
+ description,
673
+ children
674
+ }) {
675
+ const {
676
+ selected,
677
+ select,
678
+ days,
679
+ system,
680
+ locale
681
+ } = usePlanner('Planner.Details');
682
+ const entries = selected ? entriesOn(days, selected) : [];
683
+ const count = entries.length === 1 ? '1 entry' : `${entries.length} entries`;
684
+ return /*#__PURE__*/_jsx(Dialog, {
685
+ open: selected !== null,
686
+ onOpenChange: open => !open && select(null),
687
+ children: /*#__PURE__*/_jsxs(Dialog.Content, {
688
+ className: cn('gap-3', className),
689
+ children: [/*#__PURE__*/_jsxs(View, {
690
+ className: "gap-0.5",
691
+ children: [/*#__PURE__*/_jsx(Dialog.Title, {
692
+ children: title ?? (selected ? calendarLongDate(selected, system, locale) : '')
693
+ }), description === null ? null : /*#__PURE__*/_jsx(Dialog.Description, {
694
+ children: description ?? (entries.length === 0 ? 'Nothing planned' : count)
695
+ })]
696
+ }), selected ? children(selected, entries) : null]
697
+ })
698
+ });
699
+ }
700
+ PlannerDetails.displayName = 'Planner.Details';
701
+ export const Planner = Object.assign(PlannerRoot, {
702
+ Header: PlannerHeader,
703
+ Title: PlannerTitle,
704
+ Today: PlannerToday,
705
+ Nav: PlannerNav,
706
+ Action: PlannerAction,
707
+ Grid: PlannerGrid,
708
+ Day: PlannerDay,
709
+ Legend: PlannerLegend,
710
+ Summary: PlannerSummary,
711
+ Footer: PlannerFooter,
712
+ Details: PlannerDetails
713
+ });
714
+ //# sourceMappingURL=index.js.map